diff --git a/.github/workflows/_package-linux.yml b/.github/workflows/_package-linux.yml index 7fc89a5bcf..5a2fa7299b 100644 --- a/.github/workflows/_package-linux.yml +++ b/.github/workflows/_package-linux.yml @@ -126,12 +126,37 @@ jobs: - name: Package Linux run: pnpm exec electron-builder --linux --${{ inputs.arch }} --publish=never + - name: Build standalone Node Host + run: pnpm run headless:build + + - name: Verify standalone Node Host + run: pnpm run smoke:headless + - name: Verify packaged DuckDB VSS run: | extension_path="dist/${UNPACKED_DIRECTORY}/resources/app.asar.unpacked/runtime/duckdb/extensions/vss.duckdb_extension" test -f "${extension_path}" pnpm run smoke:duckdb:vss -- --platform linux --arch "${TARGET_ARCH}" --extension-path "${extension_path}" + - name: Verify packaged Memory runtime + run: >- + pnpm run smoke:harness:memory -- + --resources-path "dist/${{ env.UNPACKED_DIRECTORY }}/resources" + --executable "dist/${{ env.UNPACKED_DIRECTORY }}/deepchat.bin" + + - name: Verify packaged Core application + env: + DEEPCHAT_E2E_APP_MODE: packaged + DEEPCHAT_E2E_EXECUTABLE_PATH: ${{ github.workspace }}/dist/${{ env.UNPACKED_DIRECTORY }}/deepchat + DEEPCHAT_E2E_MEASURE_RESOURCES: '0' + PLAYWRIGHT_JSON_OUTPUT_NAME: test-results/package-core/report.json + run: >- + xvfb-run -a pnpm exec playwright test -c test/e2e/playwright.config.ts + test/e2e/specs/01-launch.smoke.spec.ts + test/e2e/specs/49-settings-snapshot.smoke.spec.ts + --grep "Core owner failure|pending database unlock|启动应用 @smoke|settings revisions" + --retries=0 --reporter=list,json --output=test-results/package-core + - name: Verify packaged OpenDAL native package run: >- pnpm run smoke:opendal:native -- @@ -229,6 +254,7 @@ jobs: path: | package-output/manifest.json package-output/reports/ + test-results/package-core/ dist/light-ocr-smoke-linux-${{ inputs.arch }}.json dist/package-size-linux-${{ inputs.arch }}.json if-no-files-found: warn diff --git a/.github/workflows/_package-macos.yml b/.github/workflows/_package-macos.yml index 14ccaf727d..285055c892 100644 --- a/.github/workflows/_package-macos.yml +++ b/.github/workflows/_package-macos.yml @@ -168,12 +168,37 @@ jobs: VITE_GITHUB_REDIRECT_URI: ${{ secrets.DC_GITHUB_REDIRECT_URI }} NODE_OPTIONS: '--max-old-space-size=4096' + - name: Build standalone Node Host + run: pnpm run headless:build + + - name: Verify standalone Node Host + run: pnpm run smoke:headless + - name: Verify packaged DuckDB VSS run: | extension_path="${APP_DIRECTORY}/Contents/Resources/app.asar.unpacked/runtime/duckdb/extensions/vss.duckdb_extension.b64" test -f "${extension_path}" pnpm run smoke:duckdb:vss -- --platform darwin --arch "${TARGET_ARCH}" --extension-base64-path "${extension_path}" + - name: Verify packaged Memory runtime + run: >- + pnpm run smoke:harness:memory -- + --resources-path "${{ env.APP_DIRECTORY }}/Contents/Resources" + --executable "${{ env.APP_DIRECTORY }}/Contents/MacOS/DeepChat" + + - name: Verify packaged Core application + env: + DEEPCHAT_E2E_APP_MODE: packaged + DEEPCHAT_E2E_EXECUTABLE_PATH: ${{ github.workspace }}/${{ env.APP_DIRECTORY }}/Contents/MacOS/DeepChat + DEEPCHAT_E2E_MEASURE_RESOURCES: '0' + PLAYWRIGHT_JSON_OUTPUT_NAME: test-results/package-core/report.json + run: >- + pnpm exec playwright test -c test/e2e/playwright.config.ts + test/e2e/specs/01-launch.smoke.spec.ts + test/e2e/specs/49-settings-snapshot.smoke.spec.ts + --grep "Core owner failure|pending database unlock|启动应用 @smoke|settings revisions" + --retries=0 --reporter=list,json --output=test-results/package-core + - name: Verify packaged OpenDAL native package run: >- pnpm run smoke:opendal:native -- @@ -262,6 +287,7 @@ jobs: path: | package-output/manifest.json package-output/reports/ + test-results/package-core/ dist/light-ocr-smoke-darwin-${{ inputs.arch }}-offline.json dist/light-ocr-smoke-darwin-${{ inputs.arch }}.json dist/package-size-darwin-${{ inputs.arch }}.json diff --git a/.github/workflows/_package-windows.yml b/.github/workflows/_package-windows.yml index 010ff779d0..1c45739106 100644 --- a/.github/workflows/_package-windows.yml +++ b/.github/workflows/_package-windows.yml @@ -126,6 +126,12 @@ jobs: VITE_GITHUB_CLIENT_SECRET: ${{ secrets.DC_GITHUB_CLIENT_SECRET }} VITE_GITHUB_REDIRECT_URI: ${{ secrets.DC_GITHUB_REDIRECT_URI }} + - name: Build standalone Node Host + run: pnpm run headless:build + + - name: Verify standalone Node Host + run: pnpm run smoke:headless + - name: Verify packaged DuckDB VSS shell: bash run: | @@ -133,6 +139,25 @@ jobs: test -f "${extension_path}" pnpm run smoke:duckdb:vss -- --platform win32 --arch "${TARGET_ARCH}" --extension-path "${extension_path}" + - name: Verify packaged Memory runtime + run: >- + pnpm run smoke:harness:memory -- + --resources-path "dist/${{ env.UNPACKED_DIRECTORY }}/resources" + --executable "dist/${{ env.UNPACKED_DIRECTORY }}/DeepChat.exe" + + - name: Verify packaged Core application + env: + DEEPCHAT_E2E_APP_MODE: packaged + DEEPCHAT_E2E_EXECUTABLE_PATH: ${{ github.workspace }}/dist/${{ env.UNPACKED_DIRECTORY }}/DeepChat.exe + DEEPCHAT_E2E_MEASURE_RESOURCES: '0' + PLAYWRIGHT_JSON_OUTPUT_NAME: test-results/package-core/report.json + run: >- + pnpm exec playwright test -c test/e2e/playwright.config.ts + test/e2e/specs/01-launch.smoke.spec.ts + test/e2e/specs/49-settings-snapshot.smoke.spec.ts + --grep "Core owner failure|pending database unlock|启动应用 @smoke|settings revisions" + --retries=0 --reporter=list,json --output=test-results/package-core + - name: Verify packaged OpenDAL native package run: >- pnpm run smoke:opendal:native -- @@ -219,6 +244,7 @@ jobs: path: | package-output/manifest.json package-output/reports/ + test-results/package-core/ dist/light-ocr-smoke-win32-${{ inputs.arch }}.json dist/package-size-win32-${{ inputs.arch }}.json if-no-files-found: warn diff --git a/.github/workflows/prcheck.yml b/.github/workflows/prcheck.yml index eb983edfc8..19d665b3f6 100644 --- a/.github/workflows/prcheck.yml +++ b/.github/workflows/prcheck.yml @@ -75,6 +75,12 @@ jobs: - name: Install dependencies run: pnpm install --frozen-lockfile + - name: Check Rust host + run: | + cargo fmt --check --manifest-path crates/deepchat-harness/Cargo.toml + cargo clippy --locked --manifest-path crates/deepchat-harness/Cargo.toml --all-targets -- -D warnings + cargo test --locked --manifest-path crates/deepchat-harness/Cargo.toml + - name: Test main process run: pnpm run test:main @@ -153,6 +159,9 @@ jobs: pnpm run installRuntime:duckdb:vss:linux:x64 pnpm run smoke:duckdb:vss -- --platform linux --arch x64 + - name: Build Rutis harness for native integration + run: pnpm run harness:build + - name: Validate portable memory behavior run: pnpm run test:memory @@ -163,11 +172,12 @@ jobs: - name: Smoke native SQLite run: node scripts/smoke-memory-native-sqlite.js - - name: Validate native Tape storage + - name: Validate native Store ownership and Tape env: DEEPCHAT_REQUIRE_NATIVE_SQLITE: '1' run: >- pnpm exec vitest --config vitest.config.ts --run + test/main/app/profileStartupNative.test.ts test/main/tape/compactionUsage.test.ts test/main/tape/skillMaterialization.test.ts test/main/tape/executionJournal.test.ts @@ -201,9 +211,10 @@ jobs: DEEPCHAT_REQUIRE_NATIVE_SQLITE: '1' run: pnpm run test:memory:eval - - name: Validate memory performance bounds + - name: Validate memory workload bounds and report timings env: DEEPCHAT_REQUIRE_NATIVE_SQLITE: '1' + DEEPCHAT_MEMORY_PERF_REPORT_ONLY: '1' run: pnpm run test:main:memory-perf - name: Upload memory retrieval report diff --git a/.gitignore b/.gitignore index d60b8a031b..501f09e043 100644 --- a/.gitignore +++ b/.gitignore @@ -28,3 +28,6 @@ test-results/ # tsc incremental build info *.tsbuildinfo + +# Rust harness build artifacts +crates/deepchat-harness/target/ diff --git a/AGENTS.md b/AGENTS.md index b1441bbe97..47228bb3db 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -2,8 +2,12 @@ - Prefer the smallest correct change; add no abstraction or dependency without a real need. - Preserve unrelated worktree changes; avoid destructive Git unless explicitly requested. -- Use pnpm only; require Node >=20.19 and pnpm >=10.11. +- Use pnpm only; match package.json engines: Node >=24.22.1 <25 and pnpm >=10.34.5 <11. +- Require Rust >=1.88 with Cargo; normal dev/build commands compile the Rutis host automatically. - Core code: `src/main`, `src/preload`, `src/renderer`, `src/shared`; stack: Electron/Vue 3/TS. +- Rutis host: `crates/deepchat-harness`; first-party TS capabilities/composition: `src/harness`. +- Standalone Node Session Host: `src/headless`; build with `pnpm headless:build`, verify with + `pnpm smoke:headless`. Keep its runtime closure Electron-free and excluded from desktop packaging. - Tests: use Vitest in `test/main`, Vitest with Vue Test Utils in `test/renderer`, and Playwright in `test/e2e`; run the smallest relevant suite. - Default to implementation-first development: inspect and design, complete the planned code change, diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 34c616b404..6b04ee9890 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -57,6 +57,7 @@ We use GitHub to host code, to track issues and feature requests, as well as acc 2. Install required development tools: - Install [Node.js](https://nodejs.org/) (Latest LTS version recommended) + - Install Rustup for the native Rutis host; `rust-toolchain.toml` pins Rust 1.95.0. `pnpm run dev` and `pnpm run build` build the host automatically; packaged users do not need Rust. 3. Additional setup based on your operating system: diff --git a/CONTRIBUTING.zh.md b/CONTRIBUTING.zh.md index fa6fbe50fa..7f5424d70d 100644 --- a/CONTRIBUTING.zh.md +++ b/CONTRIBUTING.zh.md @@ -57,6 +57,7 @@ 2. 安装必要的开发工具: - 安装 [Node.js](https://nodejs.org/) (推荐使用最新的 LTS 版本) + - Install Rustup for the native Rutis host; `rust-toolchain.toml` pins Rust 1.95.0. `pnpm run dev` and `pnpm run build` build the host automatically; packaged users do not need Rust. 3. 根据您的操作系统进行额外设置: diff --git a/README.jp.md b/README.jp.md index 5ce06caecc..66afc52c09 100644 --- a/README.jp.md +++ b/README.jp.md @@ -509,6 +509,10 @@ Mac関連の署名とパッケージングについては、[Mac リリースガ ### 依存関係のインストール +Use the Node.js and pnpm versions declared in [package.json](./package.json), and install Rust 1.88+ +with Cargo. Development and build commands compile the first-party Rutis host automatically; +the Rust target must match the requested app architecture. + ```bash $ pnpm install $ pnpm run installRuntime diff --git a/README.md b/README.md index ae7ff4d412..984459cbb3 100644 --- a/README.md +++ b/README.md @@ -512,6 +512,10 @@ For Mac-related signing and packaging, please refer to the [Mac Release Guide](h ### Install Dependencies +Use the Node.js and pnpm versions declared in [package.json](./package.json), and install Rust 1.88+ +with Cargo. Development and build commands compile the first-party Rutis host automatically; +the Rust target must match the requested app architecture. + ```bash $ pnpm install $ pnpm run installRuntime @@ -529,6 +533,22 @@ $ pnpm run dev ### Build +For an independent Node Session Host, run `pnpm headless:build` and copy the complete `out/headless` +directory to the matching platform/architecture. The host requires Node >=24.22.1 <25, an explicit +profile and a stable credential master key; it runs without Electron or a connected desktop: + +```bash +export DEEPCHAT_HOST_KEY='' +node out/headless/index.mjs --profile /path/to/host-profile +# In another terminal: +DEEPCHAT_USER_DATA_DIR=/path/to/host-profile node out/headless/cli/deepchat.mjs system status --json +``` + +Keep the same key across restarts. Sensitive CLI mutations require confirmation on the host TTY; +desktop-only operations report unavailable. See the +[runtime contract](docs/architecture/rutis-multiprocess-backend/spec.md#ordinary-node-session-host) +and [acceptance status](docs/architecture/rutis-multiprocess-backend/plan.md#g11-g12-deliver-everywhere-from-the-extracted-core). + ```bash # For Windows $ pnpm run build:win diff --git a/README.zh.md b/README.zh.md index 03a1a46bc2..1d71fd2526 100644 --- a/README.zh.md +++ b/README.zh.md @@ -506,6 +506,10 @@ Windows和Linux通过GitHub Action打包。 ### 安装依赖 +Use the Node.js and pnpm versions declared in [package.json](./package.json), and install Rust 1.88+ +with Cargo. Development and build commands compile the first-party Rutis host automatically; +the Rust target must match the requested app architecture. + ```bash $ pnpm install $ pnpm run installRuntime diff --git a/crates/deepchat-harness/Cargo.lock b/crates/deepchat-harness/Cargo.lock new file mode 100644 index 0000000000..e9546dbe0e --- /dev/null +++ b/crates/deepchat-harness/Cargo.lock @@ -0,0 +1,1242 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "assoc" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfdc70193dadb9d7287fa4b633f15f90c876915b31f6af17da307fc59c9859a8" + +[[package]] +name = "autocfg" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" + +[[package]] +name = "bitflags" +version = "2.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ded4057c258ba199e2d26386d3af3780957ecaee6c4ef4041c6b4b8b97c0b06" + +[[package]] +name = "bitvec" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddcec3d12c579d40898fe0a9a358a803c23e9c52ca3c425707f81c9436211837" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bytes" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04" + +[[package]] +name = "cfg-if" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e7648175b45a9a48536d676f68d918270699102aa8dab5496df06904c914600" + +[[package]] +name = "const-siphasher" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03efed02df0504d71e44bfd51d3329f401b8303a2fd14254acf05c95a0af0153" + +[[package]] +name = "corosensei" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6886a0c0f263965933c438626e7179139a62b978a33aa18281cbf0cd5a975f34" +dependencies = [ + "autocfg", + "cfg-if", + "libc", + "scopeguard", + "windows-sys 0.59.0", +] + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "crypto-common" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "deepchat-harness" +version = "0.1.0" +dependencies = [ + "futures-util", + "libc", + "rutis", + "rutis-bridge", + "rutis-loader", + "serde", + "serde_json", + "tokio", + "url", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "displaydoc" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6232dd377dcc64799954cbd3a9bb882e9cdc1308ccd87b1c098f1fb2eaf82a8" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.9", +] + +[[package]] +name = "dyn-clone" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" + +[[package]] +name = "equivalent" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00d174d5400e5e8fd687ad1049e2f578285fa914201b1af7e8b112a4546bd826" + +[[package]] +name = "erased-serde" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2add8a07dd6a8d93ff627029c51de145e12686fbc36ecb298ac22e74cf02dec" +dependencies = [ + "serde", + "serde_core", + "typeid", +] + +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "fastrand" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da7c62ceae207dd37ea5b845da6a0696c799f85e97da1ab5b7910be3c1c80223" + +[[package]] +name = "foldhash" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" + +[[package]] +name = "form_urlencoded" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + +[[package]] +name = "futures-core" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92d699e522242e69e3003b94ecc1f960f3a5e015aa7c5d7486e65ad01dd94f5e" + +[[package]] +name = "futures-macro" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fb9654ba8355388abeb8dcb4fc62f511300867002afc858860463bdd9fe0c44" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.9", +] + +[[package]] +name = "futures-sink" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1944426bf7d03f1d14f708785e4b33efd750b36d48a157b836b3efc15ede8e1d" + +[[package]] +name = "futures-task" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd417de3d1d015fc3bfd2b1ea46dfc7bab72ef86f1cc7cc9c78e728b34a6d1fd" + +[[package]] +name = "futures-util" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d50a92467f8ba5dd6e3ee5d4bd04d73ab2e4e1c44474a0674821dfce14b79bc" +dependencies = [ + "futures-core", + "futures-macro", + "futures-task", + "pin-project-lite", + "slab", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "getrandom" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099" +dependencies = [ + "cfg-if", + "libc", + "r-efi", +] + +[[package]] +name = "hashbrown" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "icu_collections" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe090a42a0d3140fed8c63263ed1fecc02422cb2f4bfe55cd8de2de1b8e105dd" +dependencies = [ + "displaydoc", + "potential_utf", + "utf8_iter", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25d29b57c17c6528d0dc4c2141cfa2d08200465acb537a3909c8b9b8a4a984c0" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "404a3b8c486f205c87fe7f23ab6894d84a4ad76d6e4671ae1981ba7def87c599" +dependencies = [ + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20f0088bfb7441f7a37fa0c9e20cb1e9cba1cac6b9ce8cd2a6dc44294f8e8cbc" + +[[package]] +name = "icu_properties" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ea83d991c56158f7cf898bf86436c1dfe7c07ee82fe88951f2695c9f37a379f" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b98f29af82d02b163fe50161388350e743bb3107cc8e8c2f7314cb505faddfcf" + +[[package]] +name = "icu_provider" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413099315636c588ee8f011190b329b971a09a8fae9d157e01dcae8379468514" +dependencies = [ + "displaydoc", + "icu_locale_core", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + +[[package]] +name = "idna" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "indexmap" +version = "2.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc4e190f5d26ca7051642629da2c52fc03bde85a03197c99408dcd291734c855" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] +name = "libc" +version = "0.2.191" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65cd87c9a6d60536580b316ad985726104730a5151c99845f2490428ef91240a" + +[[package]] +name = "linux-raw-sys" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" + +[[package]] +name = "litemap" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47d9d19d1d6efa0109d2f65ff4c85cddd50bd572e5a00127ab10987290bcefae" + +[[package]] +name = "memchr" +version = "2.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" + +[[package]] +name = "mio" +version = "1.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1788edb87fdc09c7e26304471e2f5be8cdefb1b6930d6e3985fc02ff53bf86ee" +dependencies = [ + "libc", + "wasi", + "windows-sys 0.61.2", +] + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "owo-colors" +version = "4.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c45bb4a6ae1280ec0803b1ef9d3455eb50f01efbbe1447ab020f1d54fba9d8" + +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + +[[package]] +name = "potential_utf" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d83eb9bc6d8e5cf568e7a1101d60ee05e81ed50ea106026f3d18deeb046d7661" +dependencies = [ + "zerovec", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "proc-macro2" +version = "1.0.107" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + +[[package]] +name = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + +[[package]] +name = "rand" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e058c7de0b26af77780c769414d6257830bb240f3c38477dbc2c16e5f54d6d4c" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.17", +] + +[[package]] +name = "rand_pcg" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59cad018caf63deb318e5a4586d99a24424a364f40f1e5778c29aca23f4fc73e" +dependencies = [ + "rand_core", +] + +[[package]] +name = "ref-cast" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e440fb4e4b4147295338efb76001ab9e4efc0e5839df2c47fc5ac2381d365c3" +dependencies = [ + "ref-cast-impl", +] + +[[package]] +name = "ref-cast-impl" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92ecd8964f8453721699a1ed72037b0db49ce2f5a5138486ee89bed6f67cdf3a" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.9", +] + +[[package]] +name = "rustix" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "891efababe418670775f199f0d233d84843c227a0949a883ce15b37c78d6629d" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.61.2", +] + +[[package]] +name = "rutis" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2979977c416f20b5ca9315b94538dc94d7424084461ec56b2af42b7314849a3a" +dependencies = [ + "erased-serde", + "rutis-macros", + "serde", + "serde_json", + "shuttle", + "thiserror", + "tokio", + "tokio-util", +] + +[[package]] +name = "rutis-bridge" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4dcf8d930b1c200a05fca2166bab4ecf412c44d0d57b35d0c4916e13da6ed59c" +dependencies = [ + "getrandom 0.2.17", + "libc", + "rutis", + "serde", + "serde_json", + "sha2", + "tempfile", + "thiserror", + "tokio", + "windows-sys 0.61.2", +] + +[[package]] +name = "rutis-loader" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b66ad6bd4f41d2cb91f6773766c4eaaa91bb8867f0c4c40e75c9d04153451db" +dependencies = [ + "libc", + "rutis", + "rutis-bridge", + "schemars", + "serde", + "serde_json", + "thiserror", + "tokio", + "url", +] + +[[package]] +name = "rutis-macros" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4632181bfad5038d3c412f153711791f3a9e63aeab3bef74822c630b9e088eac" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "schemars" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab508826f74a77ca9d5aba6ff19b522583ee3eaf28a19384ff3d0e5835fadf6e" +dependencies = [ + "dyn-clone", + "ref-cast", + "schemars_derive", + "serde", + "serde_json", +] + +[[package]] +name = "schemars_derive" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1c3a92094fa7d61aa124645844facb6b554dfc797136d0f5fd1f890e2bffc69" +dependencies = [ + "proc-macro2", + "quote", + "serde_derive_internals", + "syn 3.0.9", +] + +[[package]] +name = "scoped-tls" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "serde" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.9", +] + +[[package]] +name = "serde_derive_internals" +version = "0.30.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f852137cce035d6a4df67ccce505ff6b3e9fd3a10e3e52b24dc71e650bb1a9bd" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.9", +] + +[[package]] +name = "serde_json" +version = "1.0.154" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7e9cc8b1b85264074fbcc02a88680c4096b1e47df8f739dceb03bf482f04bd6" +dependencies = [ + "foldhash", + "indexmap", + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "shuttle" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90f796ae4996c3fbf039c26d69536f8e112f49fd5e9a25fd353fe4787134b7cb" +dependencies = [ + "bitvec", + "cfg-if", + "const-siphasher", + "corosensei", + "hex", + "owo-colors", + "rand", + "rand_core", + "rand_pcg", + "scoped-tls", + "shuttle-engine", + "shuttle-schedulers", + "shuttle-std", + "tracing", +] + +[[package]] +name = "shuttle-engine" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bf952ac286e32a98c2f1540b44747ee93a3435a4835eb266eeb53658b9782ca" +dependencies = [ + "assoc", + "bitvec", + "cfg-if", + "const-siphasher", + "corosensei", + "hex", + "owo-colors", + "rand", + "rand_core", + "rand_pcg", + "scoped-tls", + "smallvec", + "tracing", +] + +[[package]] +name = "shuttle-schedulers" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7673a579e1660404067f5aa1344d6530f15b59f53b0fe98767d7ab117d14067" +dependencies = [ + "rand", + "rand_pcg", + "shuttle-engine", + "smallvec", + "tracing", +] + +[[package]] +name = "shuttle-std" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05fa35d22b2671e0184a13d0221055ae9e07a570862798e866118acd4be50f15" +dependencies = [ + "assoc", + "owo-colors", + "shuttle-engine", + "smallvec", + "tracing", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" +dependencies = [ + "errno", + "libc", +] + +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + +[[package]] +name = "smallvec" +version = "1.16.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0d5d4b1981b8bbbb7aed204cae5726d683d5d6ac7e74ec54122ea28f315e7c8" + +[[package]] +name = "socket2" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d1e2c7f27f8d4cb10542a02c49005dbd6e93095799d6f3be745fae9f8fedd4" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + +[[package]] +name = "syn" +version = "2.0.119" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "3.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d78c8dee4c7bf0e14673097256fed6142ce9d3b85a408189d07482442145823b" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "synstructure" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "901704edd0dfe137f1987838ee4f259e4e063c31371bdb423f7ae38ec6f77f02" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.9", +] + +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + +[[package]] +name = "tempfile" +version = "3.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" +dependencies = [ + "fastrand", + "getrandom 0.4.3", + "once_cell", + "rustix", + "windows-sys 0.61.2", +] + +[[package]] +name = "thiserror" +version = "2.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09e52cb86a36cede5cb101bf8908837b3e4c6e5e59fe7fd85c23fb56200d189e" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe5197923287db20a58125f0bc85c062f7f2c892de97b18c356f9efb14b28524" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.9", +] + +[[package]] +name = "tinystr" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abc840c238154d58f9e86b2792261edfde565a17696e1cda1a449de27979801a" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "tokio" +version = "1.54.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b52fd3255258d26fac7264ab5a76b53c13367136dc011d3d6ad4f8ed070396a3" +dependencies = [ + "bytes", + "libc", + "mio", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "windows-sys 0.61.2", +] + +[[package]] +name = "tokio-macros" +version = "2.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78773a2a397f451582ce068015985c33193cf6dea8b74d2a639fe457b2f07b0e" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.9", +] + +[[package]] +name = "tokio-util" +version = "0.7.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e464cf451ba96ebfc6f9b6542f17ee8b8956e33f1e40d9690624e59d7a7f8a4b" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "futures-util", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tracing" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" +dependencies = [ + "pin-project-lite", + "tracing-core", +] + +[[package]] +name = "tracing-core" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" +dependencies = [ + "once_cell", +] + +[[package]] +name = "typeid" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c" + +[[package]] +name = "typenum" +version = "1.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" + +[[package]] +name = "unicode-ident" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2c754d6c33795a1c324727428e5a7dedb5b06195f9890bdbcba760d3e246563" + +[[package]] +name = "url" +version = "2.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", +] + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "writeable" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ad82d2a33cdc9674dc7465672f271e096168fcdbe0f799d9e6db8c5892679dc" + +[[package]] +name = "wyz" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" +dependencies = [ + "tap", +] + +[[package]] +name = "yoke" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "417ef3fcf6c300b7dbd25c85531a746931c02ad536f3cf4786ed1e88223340b3" +dependencies = [ + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec8ebde2db3681e8c9980cc27822030e68752690ddfa9473e739aeb4dbde6d71" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.9", + "synstructure", +] + +[[package]] +name = "zerocopy" +version = "0.8.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5fe1f8f1b06191a00962174c61aa5005e0bb391a6d80d07e24d115c01a92ed8" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "863ad3ac83293fb4d740aedbfdc9240dd8d1a50c1099acd76ce80ce7c7230c7f" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "zerofrom" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f75b4683f6c7f45248d4d64056a24298c6281e0993356d7d1b4a1a962ef10d4a" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.9", + "synstructure", +] + +[[package]] +name = "zerotrie" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ea269c3bd32f0a32c321907a2ae912ba6f4649bb0fc764a15627e99a7095a3f" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f58a2799cfe9a1670c7ea8edbd52ef8edb93cb37a7f83bf31ba9e35d9dae6a40" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3743ddbf47738d929cd1ae2e199d40d0162979dfe31aef8ae646d2bf00bf97a" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.9", +] + +[[package]] +name = "zmij" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b" diff --git a/crates/deepchat-harness/Cargo.toml b/crates/deepchat-harness/Cargo.toml new file mode 100644 index 0000000000..3ba14e0cc9 --- /dev/null +++ b/crates/deepchat-harness/Cargo.toml @@ -0,0 +1,27 @@ +[package] +name = "deepchat-harness" +version = "0.1.0" +edition = "2021" +rust-version = "1.88" +publish = false + +[dependencies] +futures-util = "0.3" +rutis = "=0.16.0" +rutis-bridge = { version = "=0.16.0", default-features = false, features = ["node"] } +rutis-loader = { version = "=0.16.0", features = ["node"] } +serde_json = "1" +serde = { version = "1", features = ["derive"] } +tokio = { version = "1", features = [ + "rt-multi-thread", + "macros", + "time", + "signal", + "sync", + "io-util", + "io-std" +] } +url = "2" + +[target.'cfg(target_os = "macos")'.dependencies] +libc = "0.2" diff --git a/crates/deepchat-harness/src/admission.rs b/crates/deepchat-harness/src/admission.rs new file mode 100644 index 0000000000..cb5c31101a --- /dev/null +++ b/crates/deepchat-harness/src/admission.rs @@ -0,0 +1,863 @@ +use super::{error, Json, Output, MAX_CALLS}; +use rutis::{BoxFuture, CordisError, Ctx, Effect, Plugin}; +use rutis_bridge::session::{host_key, Error as BridgeError, HostDispatch, Reply, Value}; +use serde_json::json; +use std::{ + collections::{BTreeMap, HashMap, VecDeque}, + sync::{Arc, Mutex}, + time::Instant, +}; +use tokio::sync::{oneshot, Notify}; + +type Outcome = std::result::Result; +type ReplySender = oneshot::Sender; +const MAX_SAFE_INTEGER: u64 = 9_007_199_254_740_991; +const OBSERVATIONS: usize = 512; +const SAMPLES: usize = 256; + +struct Acquisition { + owner: String, + limit: u64, + sequence: u64, + correlation: Option, + started: Instant, + granted: Option, + reply: Option, +} + +struct Command { + method: String, + args: Vec, + reply: ReplySender, +} + +struct Lane { + next: u64, + pending: BTreeMap, +} + +impl Default for Lane { + fn default() -> Self { + Self { + next: 1, + pending: BTreeMap::new(), + } + } +} + +struct State { + capacity: u64, + max_pending: u64, + closed: Option, + retired: bool, + acquisitions: HashMap, + owners: HashMap>, + owner_order: VecDeque, + ring: VecDeque, + active_by_owner: HashMap, + active: u64, + pending: u64, + acquisition_sequence: u64, + active_high_water: u64, + pending_high_water: u64, + granted: u64, + rejected: u64, + waits: VecDeque, + holds: VecDeque, + observations_enabled: bool, + observations: VecDeque, + observation_sequence: u64, + observations_dropped: u64, + observation_in_flight: bool, + flush_waiters: Vec, + main: Lane, + core: Lane, + core_generation: Option, +} + +fn invalid(message: &str) -> Json { + error("Error", message) +} + +fn closed(message: &str) -> Json { + error("AgentInvocationAdmissionClosedError", message) +} + +fn cancelled() -> Json { + error( + "AgentInvocationAdmissionAbortedError", + "Agent invocation admission was cancelled while queued.", + ) +} + +fn identifier(value: &Json) -> Option<&str> { + value + .as_str() + .filter(|value| !value.is_empty() && value.encode_utf16().count() <= 256) +} + +fn uuid(value: &str) -> bool { + value.len() == 36 + && value + .bytes() + .all(|c| c.is_ascii_digit() || (b'a'..=b'f').contains(&c) || c == b'-') +} + +fn normalize_owner(value: &Json) -> Option { + let value = value.as_str()?.trim_matches(|c| { + matches!(c, + '\u{0009}'..='\u{000d}' | '\u{0020}' | '\u{00a0}' | '\u{1680}' | + '\u{2000}'..='\u{200a}' | '\u{2028}' | '\u{2029}' | '\u{202f}' | + '\u{205f}' | '\u{3000}' | '\u{feff}') + }); + identifier(&json!(value)).map(str::to_owned) +} + +fn sample(values: &mut VecDeque, value: f64) { + if values.len() == SAMPLES { + values.pop_front(); + } + values.push_back(value); +} + +fn distribution(values: &VecDeque) -> Json { + let mut values: Vec<_> = values.iter().copied().collect(); + values.sort_by(f64::total_cmp); + let rank = |p: f64| { + values + .get((p * values.len() as f64).ceil().max(1.0) as usize - 1) + .copied() + }; + json!({ "samples": values.len(), "p50": rank(0.5), "p95": rank(0.95), "max": values.last() }) +} + +impl State { + fn new(capacity: u64, max_pending: u64) -> Self { + Self { + capacity, + max_pending, + closed: None, + retired: false, + acquisitions: HashMap::new(), + owners: HashMap::new(), + owner_order: VecDeque::new(), + ring: VecDeque::new(), + active_by_owner: HashMap::new(), + active: 0, + pending: 0, + acquisition_sequence: 0, + active_high_water: 0, + pending_high_water: 0, + granted: 0, + rejected: 0, + waits: VecDeque::new(), + holds: VecDeque::new(), + observations_enabled: false, + observations: VecDeque::new(), + observation_sequence: 0, + observations_dropped: 0, + observation_in_flight: false, + flush_waiters: Vec::new(), + main: Lane::default(), + core: Lane::default(), + core_generation: None, + } + } + + fn snapshot(&self) -> Json { + json!({ + "capacity": self.capacity, "active": self.active, "pending": self.pending, + "pendingOwners": self.owners.len(), "closed": self.closed.is_some(), + "activeHighWater": self.active_high_water, "pendingHighWater": self.pending_high_water, + "granted": self.granted, "rejected": self.rejected, + "observationsDropped": self.observations_dropped, + "waitMs": distribution(&self.waits), "holdMs": distribution(&self.holds), + }) + } + + fn emit(&mut self, mut observation: Json) { + if !self.observations_enabled { + return; + } + if self.observations.len() == OBSERVATIONS { + self.observations.pop_front(); + self.observations_dropped += 1; + } + if observation["type"] == "closed" { + observation["observationsDropped"] = self.observations_dropped.into(); + } + self.observations.push_back(observation); + } + + fn observe(&mut self, acquisition: &Acquisition, kind: &str, fields: Json) { + let Some(mut observation) = acquisition.correlation.clone() else { + return; + }; + observation["type"] = kind.into(); + observation["acquisitionSeq"] = acquisition.sequence.into(); + observation["active"] = self.active.into(); + observation["pending"] = self.pending.into(); + if kind != "released" { + observation["capacity"] = self.capacity.into(); + } + observation + .as_object_mut() + .unwrap() + .extend(fields.as_object().unwrap().clone()); + self.emit(observation); + } + + fn active_for(&self, owner: &str) -> u64 { + self.active_by_owner.get(owner).copied().unwrap_or(0) + } + + fn grant(&mut self, id: &str) { + let mut record = self.acquisitions.remove(id).unwrap(); + self.active += 1; + *self + .active_by_owner + .entry(record.owner.clone()) + .or_default() += 1; + self.active_high_water = self.active_high_water.max(self.active); + self.granted += 1; + let granted = Instant::now(); + let wait = granted.duration_since(record.started).as_secs_f64() * 1000.0; + sample(&mut self.waits, wait); + self.observe(&record, "granted", json!({ "waitMs": wait })); + record.granted = Some(granted); + let reply = record.reply.take().unwrap(); + self.acquisitions.insert(id.to_owned(), record); + if reply.send(Ok(id.into())).is_err() { + // A dropped reply cannot leave an unreachable permit holding capacity. + self.release_record(id, "permit_released"); + } + } + + fn reject(&mut self, mut record: Acquisition, reason: &str, failure: Json, queued: bool) { + self.rejected += 1; + let wait = if queued { + record.started.elapsed().as_secs_f64() * 1000.0 + } else { + 0.0 + }; + if queued { + sample(&mut self.waits, wait); + } + self.observe( + &record, + "rejected", + json!({ "reason": reason, "waitMs": wait }), + ); + let _ = record.reply.take().unwrap().send(Err(failure)); + } + + fn dispatch(&mut self) { + while self.active < self.capacity && self.pending > 0 && !self.ring.is_empty() { + let mut granted = false; + for _ in 0..self.ring.len() { + if self.active >= self.capacity { + break; + } + let owner = self.ring.pop_front().unwrap(); + let id = self.owners[&owner].front().unwrap().clone(); + if self.active_for(&owner) >= self.acquisitions[&id].limit { + self.ring.push_back(owner); + continue; + } + let queue = self.owners.get_mut(&owner).unwrap(); + queue.pop_front(); + if queue.is_empty() { + self.owners.remove(&owner); + self.owner_order.retain(|candidate| candidate != &owner); + } else { + self.ring.push_back(owner); + } + self.pending -= 1; + granted = true; + self.grant(&id); + } + if !granted { + break; + } + } + } + + fn acquire( + &mut self, + args: &[Json], + reply: &mut Option, + ) -> std::result::Result, Json> { + if args.len() != 3 { + return Err(invalid("Invalid acquire arguments")); + } + self.observations_enabled = args[2] == true; + let id = args[0] + .as_str() + .filter(|id| uuid(id) && !self.acquisitions.contains_key(*id)) + .ok_or_else(|| invalid("Invalid admission acquisition identity"))? + .to_owned(); + let options = args[1] + .as_object() + .ok_or_else(|| invalid("Invalid admission options"))?; + let owner = normalize_owner(&args[1]["ownerId"]) + .ok_or_else(|| invalid("Agent invocation ownerId must contain 1 to 256 characters."))?; + let limit = match options.get("maxActiveForOwner") { + None => self.capacity, + Some(value) => value + .as_u64() + .filter(|value| *value > 0 && *value <= MAX_SAFE_INTEGER) + .ok_or_else(|| { + invalid("Agent invocation owner limit must be a positive safe integer.") + })? + .min(self.capacity), + }; + let correlation = options.get("correlation").filter(|value| { + value["kind"] == "live_delegation" && ["parentSessionId", "delegationId", "turnId"].iter().all(|key| identifier(&value[*key]).is_some()) + }).map(|value| json!({ "kind": "live_delegation", "parentSessionId": value["parentSessionId"], "delegationId": value["delegationId"], "turnId": value["turnId"] })); + self.acquisition_sequence = if self.acquisition_sequence >= MAX_SAFE_INTEGER { + 1 + } else { + self.acquisition_sequence + 1 + }; + let record = Acquisition { + owner: owner.clone(), + limit, + sequence: self.acquisition_sequence, + correlation, + started: Instant::now(), + granted: None, + reply: reply.take(), + }; + if let Some(reason) = &self.closed { + self.reject(record, "closed", closed(reason), false); + } else if options.get("aborted") == Some(&Json::Bool(true)) { + self.reject(record, "aborted", cancelled(), false); + } else if self.active < self.capacity + && self.pending == 0 + && self.active_for(&owner) < limit + { + self.acquisitions.insert(id.clone(), record); + self.grant(&id); + } else if self.pending >= self.max_pending { + self.reject( + record, + "queue_full", + error( + "AgentInvocationAdmissionQueueFullError", + format!( + "Agent invocation admission queue is full ({} pending).", + self.max_pending + ), + ), + false, + ); + } else { + if !self.owners.contains_key(&owner) { + self.ring.push_back(owner.clone()); + self.owner_order.push_back(owner.clone()); + } + self.owners.entry(owner).or_default().push_back(id.clone()); + self.pending += 1; + self.pending_high_water = self.pending_high_water.max(self.pending); + self.observe(&record, "queued", json!({})); + self.acquisitions.insert(id, record); + self.dispatch(); + } + Ok(None) + } + + fn release(&mut self, id: &str, reason: &str) -> bool { + let released = self.release_record(id, reason); + self.dispatch(); + released + } + + fn release_record(&mut self, id: &str, reason: &str) -> bool { + let Some(record) = self.acquisitions.remove(id) else { + return false; + }; + if let Some(granted) = record.granted { + self.active -= 1; + let active = self.active_by_owner.get_mut(&record.owner).unwrap(); + *active -= 1; + if *active == 0 { + self.active_by_owner.remove(&record.owner); + } + let hold = granted.elapsed().as_secs_f64() * 1000.0; + sample(&mut self.holds, hold); + self.observe( + &record, + "released", + json!({ "reason": reason, "holdMs": hold }), + ); + } else { + let queue = self.owners.get_mut(&record.owner).unwrap(); + queue.retain(|pending| pending != id); + if queue.is_empty() { + self.owners.remove(&record.owner); + self.owner_order.retain(|owner| owner != &record.owner); + self.ring.retain(|owner| owner != &record.owner); + } + self.pending -= 1; + self.reject(record, "aborted", cancelled(), true); + } + true + } + + fn close(&mut self, reason: String) { + if self.closed.is_some() { + return; + } + self.closed = Some(reason.clone()); + let mut owners = std::mem::take(&mut self.owners); + self.ring.clear(); + for owner in std::mem::take(&mut self.owner_order) { + for id in owners.remove(&owner).unwrap() { + let record = self.acquisitions.remove(&id).unwrap(); + self.pending -= 1; + self.reject(record, "closed", closed(&reason), true); + } + } + let mut observation = self.snapshot(); + observation.as_object_mut().unwrap().remove("pendingOwners"); + observation.as_object_mut().unwrap().remove("closed"); + observation["type"] = "closed".into(); + self.emit(observation); + } + + fn flush(&mut self, reply: ReplySender) { + if self.observations.is_empty() && !self.observation_in_flight { + let _ = reply.send(Ok(Json::Null)); + } else { + self.flush_waiters.push(reply); + } + } + + fn command(&mut self, command: Command) { + let mut reply = Some(command.reply); + let result = match command.method.as_str() { + "acquire" => self.acquire(&command.args, &mut reply), + "release" => { + let args = &command.args; + if args.len() != 3 + || !matches!( + args[1].as_str(), + Some("permit_released" | "lease_suspended" | "lease_released") + ) + { + Err(invalid("Invalid admission release reason")) + } else { + self.observations_enabled = args[2] == true; + Ok(Some( + self.release(args[0].as_str().unwrap_or(""), args[1].as_str().unwrap()) + .into(), + )) + } + } + "snapshot" if command.args.is_empty() => { + Ok(Some(json!({ "admission": self.snapshot() }))) + } + "close" if command.args.len() <= 2 => { + if let Some(enabled) = command.args.get(1) { + self.observations_enabled = enabled == true; + } + self.close( + command + .args + .first() + .and_then(Json::as_str) + .unwrap_or("Agent invocation admission is closed.") + .to_owned(), + ); + self.flush(reply.take().unwrap()); + Ok(None) + } + "flushObservations" if command.args.is_empty() => { + self.flush(reply.take().unwrap()); + Ok(None) + } + _ => Err(invalid("Invalid admission command")), + }; + if let Some(reply) = reply { + let _ = reply.send(result.map(|value| value.unwrap_or(Json::Null))); + } + } + + fn ordered( + &mut self, + core: bool, + sequence: u64, + command: Command, + ) -> std::result::Result<(), BridgeError> { + let lane = if core { &mut self.core } else { &mut self.main }; + if sequence < lane.next + || sequence > MAX_SAFE_INTEGER + || sequence - lane.next > MAX_CALLS as u64 + || lane.pending.contains_key(&sequence) + { + return Err(BridgeError::Value( + "Invalid admission command sequence".into(), + )); + } + lane.pending.insert(sequence, command); + loop { + let lane = if core { &mut self.core } else { &mut self.main }; + let Some(command) = lane.pending.remove(&lane.next) else { + break; + }; + lane.next += 1; + self.command(command); + } + Ok(()) + } + + fn retire(&mut self) { + self.close("Admission generation ended".into()); + self.retired = true; + for (_, command) in std::mem::take(&mut self.main.pending) + .into_iter() + .chain(std::mem::take(&mut self.core.pending)) + { + let _ = command + .reply + .send(Err(closed("Admission generation ended"))); + } + let mut active: Vec<_> = self + .acquisitions + .iter() + .map(|(id, record)| (record.sequence, id.clone())) + .collect(); + active.sort_by_key(|(sequence, _)| *sequence); + for (_, id) in active { + self.release_record(&id, "permit_released"); + } + } +} + +pub struct Admission { + state: Arc>, + changed: Arc, +} + +impl Admission { + pub fn new(capacity: u64, max_pending: u64) -> Self { + Self { + state: Arc::new(Mutex::new(State::new(capacity, max_pending))), + changed: Arc::new(Notify::new()), + } + } + + async fn observations(&self, output: Output) { + loop { + self.changed.notified().await; + loop { + let next = { + let mut state = self.state.lock().unwrap(); + match state.observations.pop_front() { + Some(value) => { + state.observation_sequence += 1; + state.observation_in_flight = true; + Some( + json!({ "type": "observation", "sequence": state.observation_sequence, "value": value }), + ) + } + None => { + for reply in state.flush_waiters.drain(..) { + let _ = reply.send(Ok(Json::Null)); + } + if state.retired { + return; + } + None + } + } + }; + let Some(frame) = next else { + break; + }; + let _ = output.send(frame).await; + self.state.lock().unwrap().observation_in_flight = false; + } + } + } +} + +impl HostDispatch for Admission { + fn invoke(&self, method: &str, args: Value) -> Reply { + let args = args.json()?; + let args = args + .as_array() + .ok_or_else(|| BridgeError::Value("Invalid admission arguments".into()))?; + let (reply, receiver) = oneshot::channel(); + let mut state = self.state.lock().unwrap(); + if state.retired && method != "snapshot" { + return Err(BridgeError::Value("Admission generation ended".into())); + } + if matches!(method, "dispatch" | "dispatchCore") { + let core = method == "dispatchCore"; + let offset = usize::from(core); + if args.len() != 3 + offset { + return Err(BridgeError::Value("Invalid admission dispatch".into())); + } + if core { + let generation = args[0] + .as_str() + .filter(|value| uuid(value)) + .ok_or_else(|| { + BridgeError::Value("Invalid Core admission generation".into()) + })?; + if state + .core_generation + .as_deref() + .is_some_and(|current| current != generation) + { + return Err(BridgeError::Value( + "Admission Core generation cannot change".into(), + )); + } + state.core_generation = Some(generation.to_owned()); + } + let sequence = args[offset] + .as_u64() + .filter(|value| *value > 0) + .ok_or_else(|| BridgeError::Value("Invalid admission sequence".into()))?; + let method = args[offset + 1] + .as_str() + .ok_or_else(|| BridgeError::Value("Invalid admission method".into()))?; + let input = args[offset + 2] + .as_array() + .ok_or_else(|| BridgeError::Value("Invalid admission input".into()))?; + state.ordered( + core, + sequence, + Command { + method: method.into(), + args: input.clone(), + reply, + }, + )?; + } else { + state.command(Command { + method: method.into(), + args: args.clone(), + reply, + }); + } + drop(state); + self.changed.notify_one(); + Ok(Value::future(async move { + match receiver + .await + .map_err(|_| BridgeError::Transport("Admission reply closed".into()))? + { + Ok(value) => Ok(value.into()), + Err(failure) => Err(BridgeError::Remote { + name: failure["name"].as_str().unwrap_or("Error").into(), + message: failure["message"] + .as_str() + .unwrap_or("Admission failed") + .into(), + graph: None, + }), + } + })) + } +} + +pub struct Owner { + pub admission: Arc, + pub output: Output, +} + +impl Plugin for Owner { + fn name(&self) -> &str { + "deepchat-admission" + } + fn apply<'a>( + &'a self, + ctx: &'a Ctx, + ) -> BoxFuture<'a, std::result::Result> { + Box::pin(async move { + let admission = self.admission.clone(); + let output = self.output.clone(); + let worker_owner = admission.clone(); + let worker = tokio::spawn(async move { + worker_owner.observations(output).await; + }); + ctx.effect(|| { + Effect::AsyncDisposer(Box::new(move || { + Box::pin(async move { + admission.state.lock().unwrap().retire(); + admission.changed.notify_one(); + worker + .await + .map_err(|error| CordisError::PluginFailed(Box::new(error)))?; + Ok(()) + }) + })) + })?; + ctx.provide_as::( + host_key("harnessAdmission"), + self.admission.clone(), + )?; + Ok(Effect::Done) + }) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use rutis_bridge::session::settle; + + fn id(n: usize) -> String { + format!("00000000-0000-4000-8000-{n:012x}") + } + + fn acquire(owner: &Admission, n: usize, name: &str, limit: u64) -> Value { + owner + .invoke( + "acquire", + json!([id(n), { "ownerId": name, "maxActiveForOwner": limit }, false]).into(), + ) + .unwrap() + } + + async fn result(value: Value) -> Json { + settle(value).await.unwrap().json().unwrap() + } + + async fn snapshot(owner: &Admission) -> Json { + result(owner.invoke("snapshot", json!([]).into()).unwrap()).await["admission"].clone() + } + + async fn release(owner: &Admission, n: usize) -> Json { + result( + owner + .invoke("release", json!([id(n), "permit_released", false]).into()) + .unwrap(), + ) + .await + } + + #[tokio::test] + async fn owner_headroom_fifo_fairness_and_cancelled_waiters_preserve_capacity() { + let owner = Admission::new(2, 4); + assert_eq!(result(acquire(&owner, 1, "a", 1)).await, id(1)); + let a1 = acquire(&owner, 2, "a", 1); + let a2 = acquire(&owner, 3, "a", 1); + assert_eq!(result(acquire(&owner, 4, "b", 1)).await, id(4)); + let c = acquire(&owner, 5, "c", 1); + assert_eq!(snapshot(&owner).await["pending"], 3); + assert_eq!(release(&owner, 3).await, true); + assert!( + matches!(settle(a2).await, Err(BridgeError::Remote { name, .. }) if name == "AgentInvocationAdmissionAbortedError") + ); + assert_eq!(release(&owner, 4).await, true); + assert_eq!(result(c).await, id(5)); + assert_eq!(snapshot(&owner).await["pending"], 1); + assert_eq!(release(&owner, 1).await, true); + assert_eq!(result(a1).await, id(2)); + assert_eq!(release(&owner, 1).await, false); + release(&owner, 2).await; + release(&owner, 5).await; + assert_eq!(snapshot(&owner).await["active"], 0); + assert_eq!(snapshot(&owner).await["pending"], 0); + result(acquire(&owner, 6, "a", 1)).await; + let abandoned = acquire(&owner, 7, "a", 1); + result(acquire(&owner, 8, "b", 1)).await; + let next = acquire(&owner, 9, "c", 1); + drop(abandoned); + release(&owner, 6).await; + assert_eq!(result(next).await, id(9)); + release(&owner, 8).await; + release(&owner, 9).await; + assert_eq!(snapshot(&owner).await["active"], 0); + assert!(normalize_owner(&json!("😀".repeat(129))).is_none()); + assert_eq!( + normalize_owner(&json!("\u{feff} a \u{feff}")), + Some("a".into()) + ); + } + + #[tokio::test] + async fn plugin_retirement_fences_old_permits_and_pending_replies_before_replacement() { + let ctx = Ctx::root().unwrap(); + let (output, _responses) = Output::channel(); + let old = Arc::new(Admission::new(1, 1)); + let plugin = ctx.plugin(Owner { + admission: old.clone(), + output: output.clone(), + }); + (&plugin).await.unwrap(); + result(acquire(&old, 1, "a", 1)).await; + let waiting = acquire(&old, 2, "b", 1); + assert!( + matches!(settle(acquire(&old, 3, "c", 1)).await, Err(BridgeError::Remote { name, .. }) if name == "AgentInvocationAdmissionQueueFullError") + ); + plugin.dispose().await.unwrap(); + assert!( + matches!(settle(waiting).await, Err(BridgeError::Remote { name, .. }) if name == "AgentInvocationAdmissionClosedError") + ); + assert_eq!(snapshot(&old).await["active"], 0); + assert_eq!(snapshot(&old).await["pending"], 0); + assert_eq!(snapshot(&old).await["holdMs"]["samples"], 1); + assert!(old + .invoke( + "acquire", + json!([id(4), { "ownerId": "late" }, false]).into() + ) + .is_err()); + let replacement = Arc::new(Admission::new(2, 1)); + ctx.plugin(Owner { + admission: replacement.clone(), + output, + }) + .await + .unwrap(); + assert_eq!(snapshot(&replacement).await["closed"], false); + result(acquire(&replacement, 1, "a", 1)).await; + ctx.shutdown().await.unwrap(); + } + + #[tokio::test] + async fn stalled_diagnostics_stay_bounded_and_cannot_block_permits_or_close() { + let ctx = Ctx::root().unwrap(); + let (output, responses) = Output::channel(); + let owner = Arc::new(Admission::new(1, 1)); + ctx.plugin(Owner { + admission: owner.clone(), + output, + }) + .await + .unwrap(); + for n in 0..600 { + let correlation = json!({ "kind": "live_delegation", "parentSessionId": "parent", "delegationId": "delegation", "turnId": "turn" }); + result( + owner + .invoke( + "acquire", + json!([id(n), { "ownerId": "a", "correlation": correlation }, true]).into(), + ) + .unwrap(), + ) + .await; + result( + owner + .invoke("release", json!([id(n), "permit_released", true]).into()) + .unwrap(), + ) + .await; + } + let before = snapshot(&owner).await; + assert_eq!(before["active"], 0); + assert_eq!(before["waitMs"]["samples"], SAMPLES); + assert_eq!(before["holdMs"]["samples"], SAMPLES); + assert!(before["observationsDropped"].as_u64().unwrap() > 0); + assert!(owner.state.lock().unwrap().observations.len() <= OBSERVATIONS); + drop(responses); + result(owner.invoke("close", json!(["done", true]).into()).unwrap()).await; + assert_eq!(snapshot(&owner).await["closed"], true); + ctx.shutdown().await.unwrap(); + } +} diff --git a/crates/deepchat-harness/src/control.rs b/crates/deepchat-harness/src/control.rs new file mode 100644 index 0000000000..38e4c16c3f --- /dev/null +++ b/crates/deepchat-harness/src/control.rs @@ -0,0 +1,3802 @@ +use rutis_bridge::session::Error; +use serde::{Deserialize, Serialize}; +use serde_json::{json, Value}; +use std::{ + collections::{HashMap, HashSet, VecDeque}, + sync::Mutex, +}; + +const MAX_METADATA: usize = 16 * 1024 * 1024; +const MAX_SEQUENCE: u64 = 9_007_199_254_740_991; + +#[derive(Clone, Copy, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +enum Status { + #[default] + Idle, + Generating, + Error, +} + +#[derive(Clone, Serialize)] +#[serde(rename_all = "camelCase")] +struct Operation { + id: String, + aborted: bool, +} + +#[derive(Clone, Serialize)] +#[serde(rename_all = "camelCase")] +struct Run { + id: String, + message_id: String, + operation_id: String, +} + +struct Permission { + id: String, + message_id: String, + tool_call_id: String, + run_id: Option, + taken: bool, +} + +#[derive(Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "kebab-case")] +enum InteractionOrigin { + PreCheckPermission, + Question, + PostCallPermission, + SkillDraftConfirmation, + AcpPermission, +} + +#[derive(Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase", deny_unknown_fields)] +struct PendingInteraction { + message_id: String, + tool_call_id: String, + origin: InteractionOrigin, + order: u64, +} + +#[derive(Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase", deny_unknown_fields)] +struct ToolBatch { + id: String, + call_order: Vec, + invoked_call_ids: Vec, + committed_result_call_ids: Vec, + pending_interaction_call_ids: Vec, +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase", deny_unknown_fields)] +struct InteractionUpdate { + interactions: Vec, + #[serde(default, deserialize_with = "deserialize_batch_update")] + batch: Option>, + #[serde(default)] + reconcile: bool, +} + +#[derive(Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +enum InputSource { + Send, + Queue, + Steer, +} + +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +enum InputWakeReason { + Completed, + Enqueue, + Manual, +} + +#[derive(Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "snake_case")] +enum InputRowState { + Pending, + Blocked, + RetryRequired, +} + +#[derive(Deserialize)] +#[serde(deny_unknown_fields)] +struct InputRow { + id: String, + state: InputRowState, +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase", deny_unknown_fields)] +struct InputSnapshot { + queue: Vec, + steer_head: Option, + has_claimed: bool, + has_blocking: bool, + awaiting_follow_up: bool, +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase", deny_unknown_fields)] +struct InputRecovery { + session_id: String, + held_input_ids: Vec, +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +enum InputIntent { + Enqueue, + Drain, + Prepare, + Steer, +} + +#[derive(Deserialize)] +#[serde(rename_all = "kebab-case")] +enum InputHoldAction { + ReleaseItem, + Resume, +} + +#[derive(Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "kebab-case")] +enum ClaimAction { + Claim, + UserFact, + Rollback, + Consume, + Block, + ReleaseForMutation, + ReleaseBeforeUserFact, + ReleaseAfterRollback, +} + +impl ClaimAction { + fn terminal(self) -> bool { + !matches!(self, Self::Claim | Self::UserFact | Self::Rollback) + } +} + +#[derive(Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +enum InputEffectOutcome { + Applied, + Rejected, + Unknown, +} + +#[derive(Serialize)] +#[serde(rename_all = "camelCase")] +struct ClaimEffect { + id: String, + action: ClaimAction, + outcome: Option, +} + +#[derive(Serialize)] +#[serde(rename_all = "camelCase")] +struct InputClaim { + id: String, + input_id: Option, + source: InputSource, + disposition: Option, + outcome: Option, + consume_before_provider_stream: bool, + effect: Option, + #[serde(skip)] + scope_id: String, + #[serde(skip)] + operation_id: Option, + #[serde(skip)] + run_id: Option, + #[serde(skip)] + user_fact: bool, + #[serde(skip)] + rolled_back: bool, + #[serde(skip)] + preparation_only: bool, +} + +struct InputDrain { + id: String, + scope_id: String, + claim_id: String, + settled: bool, +} + +struct SteerEffect { + id: String, + scope_id: String, + operation_id: Option, + outcome: Option, + merge_item_id: Option, + promotion_id: Option, +} + +#[derive(Default)] +struct SessionInputs { + latest_scope_id: Option, + active_steer_id: Option, + active_claim_id: Option, + claims: HashMap, + restart_held_ids: HashSet, + manually_resumed_ids: HashSet, + wake_reason: Option, + drain: Option, + steer_effect: Option, + interrupted_operation: Option, +} + +impl SessionInputs { + fn has_transcript_effect(&self, scope_id: &str) -> bool { + self.claims.values().any(|claim| { + claim.scope_id == scope_id + && claim.effect.as_ref().is_some_and(|effect| { + matches!(effect.action, ClaimAction::UserFact | ClaimAction::Rollback) + }) + }) + } + + fn snapshot(&self) -> Value { + json!({ + "activeSteerId": self.active_steer_id, + "activeClaimId": self.active_claim_id, + "claims": self.claims.values().collect::>(), + "restartHeldIds": self.restart_held_ids, + "manuallyResumedIds": self.manually_resumed_ids, + "wakeReason": self.wake_reason, + }) + } +} + +fn deserialize_batch_update<'de, D>(deserializer: D) -> Result>, D::Error> +where + D: serde::Deserializer<'de>, +{ + Option::::deserialize(deserializer).map(Some) +} + +#[derive(Clone)] +enum Prepared { + Operation(Operation), + Run(Operation, Run), +} + +impl Prepared { + fn id(&self) -> &str { + match self { + Self::Operation(operation) => &operation.id, + Self::Run(_, run) => &run.id, + } + } +} + +#[derive(Serialize)] +#[serde(rename_all = "camelCase")] +struct Scope { + id: String, + session_id: String, + current: bool, + status: Status, + operation: Option, + run: Option, + first_turn_ready: bool, + interactions: Vec, + batch: Option, + #[serde(skip)] + prepared: Option, + #[serde(skip)] + cleared_operation: Option<(String, Option)>, + #[serde(skip)] + cleared_run: Option, + #[serde(skip)] + leases: HashMap, + #[serde(skip)] + allow_parking: bool, + #[serde(skip)] + permissions: HashMap, +} + +impl Scope { + fn input_status_allows(&self, reason: InputWakeReason) -> bool { + matches!(self.status, Status::Idle) + || (reason != InputWakeReason::Completed && matches!(self.status, Status::Error)) + } + + fn projected_operation(&self, owner: &StatusOwner) -> Option<&str> { + match owner { + StatusOwner::Operation { id } => self.operation.as_ref().map_or_else( + || { + self.cleared_operation + .as_ref() + .and_then(|(operation, _)| (operation == id).then_some(operation.as_str())) + }, + |operation| (operation.id == *id).then_some(operation.id.as_str()), + ), + StatusOwner::Run { id } => self.run.as_ref().map_or_else( + || { + self.cleared_operation + .as_ref() + .and_then(|(operation, run)| { + (run.as_ref() == Some(id)).then_some(operation.as_str()) + }) + }, + |run| (run.id == *id).then_some(run.operation_id.as_str()), + ), + StatusOwner::Orphan => self + .cleared_operation + .as_ref() + .map(|(operation, _)| operation.as_str()), + } + } + + fn is_first_interaction(&self, message_id: &str, tool_call_id: &str) -> bool { + self.interactions.first().is_some_and(|interaction| { + interaction.message_id == message_id && interaction.tool_call_id == tool_call_id + }) + } + + fn can_project(&self, owner: &StatusOwner) -> bool { + self.prepared.is_none() + && match owner { + StatusOwner::Operation { id } => self.operation.as_ref().map_or( + self.cleared_operation + .as_ref() + .is_some_and(|(cleared, run)| cleared == id && run.is_none()) + && self.run.is_none(), + |operation| operation.id == *id && self.run.is_none(), + ), + StatusOwner::Run { id } => self.run.as_ref().map_or( + self.cleared_run.as_ref() == Some(id) && self.operation.is_none(), + |run| run.id == *id, + ), + StatusOwner::Orphan => self.run.is_none() && self.operation.is_none(), + } + } + + fn update_interactions(&mut self, update: InteractionUpdate) -> Result<(), Error> { + let mut interactions = update.interactions; + if interactions.iter().any(|interaction| { + interaction.message_id.is_empty() + || interaction.tool_call_id.is_empty() + || interaction.order > MAX_SEQUENCE + }) { + return Err(protocol("Invalid pending interaction identity or order")); + } + if update.reconcile { + let mut known = HashMap::new(); + for interaction in &self.interactions { + known + .entry((&interaction.message_id, &interaction.tool_call_id)) + .or_insert((interaction.origin, interaction.order)); + } + for interaction in &mut interactions { + if let Some((origin, order)) = + known.get(&(&interaction.message_id, &interaction.tool_call_id)) + { + interaction.origin = *origin; + interaction.order = *order; + } + } + } + interactions.sort_by_key(|interaction| interaction.order); + let mut batch = update.batch.unwrap_or_else(|| self.batch.clone()); + if let Some(batch) = &mut batch { + if batch.id.is_empty() + || batch.call_order.iter().any(String::is_empty) + || batch.invoked_call_ids.iter().any(String::is_empty) + || batch.committed_result_call_ids.iter().any(String::is_empty) + || batch + .pending_interaction_call_ids + .iter() + .any(String::is_empty) + { + return Err(protocol("Tool batch identity is empty")); + } + batch.pending_interaction_call_ids = interactions + .iter() + .filter(|interaction| interaction.origin != InteractionOrigin::AcpPermission) + .map(|interaction| interaction.tool_call_id.clone()) + .collect(); + } + if batch + .as_ref() + .is_some_and(|batch| batch.pending_interaction_call_ids.is_empty()) + { + batch = None; + } + if serde_json::to_vec(&(&interactions, &batch)) + .map_err(protocol)? + .len() + > MAX_METADATA + { + return Err(protocol( + "Pending interaction metadata exceeds the runtime bound", + )); + } + self.interactions = interactions; + self.batch = batch; + Ok(()) + } + + fn can_start(&self, owner: &StatusOwner, message_id: &str) -> bool { + let Some(operation) = self + .operation + .as_ref() + .filter(|operation| !operation.aborted) + else { + return false; + }; + self.prepared.is_none() + && match owner { + StatusOwner::Operation { id } => operation.id == *id && self.run.is_none(), + StatusOwner::Run { id } => self.run.as_ref().is_some_and(|run| { + run.id == *id + && run.message_id == message_id + && run.operation_id == operation.id + }), + StatusOwner::Orphan => false, + } + } + + fn revoke_deferred(&mut self, parent: Option<&str>, fences: &mut Vec) { + self.leases.retain(|target, id| { + if let LeaseTarget::Deferred { + parent_lease_id, .. + } = target + { + if parent.is_none_or(|parent| parent == parent_lease_id) { + fences.push(Fence::Operation { id: id.clone() }); + return false; + } + } + true + }); + } + fn revoke_permissions(&mut self, fences: &mut Vec) { + for (_, permission) in self.permissions.drain() { + if !permission.taken { + fences.push(Fence::Permission { id: permission.id }); + } + } + } +} + +#[derive(Clone, Deserialize, PartialEq, Eq, Hash)] +#[serde( + tag = "kind", + rename_all = "camelCase", + rename_all_fields = "camelCase", + deny_unknown_fields +)] +enum LeaseTarget { + Interaction { + message_id: String, + tool_call_id: String, + }, + Resume { + message_id: String, + }, + Deferred { + message_id: String, + tool_call_id: String, + parent_lease_id: String, + owner: StatusOwner, + }, + Drain, +} + +#[derive(Clone, Copy, Deserialize, PartialEq, Eq, Hash)] +#[serde(rename_all = "camelCase")] +enum AcceptanceLane { + Send, + Steer, +} + +#[derive(Clone, Serialize)] +#[serde(rename_all = "camelCase")] +struct Ticket { + scope_id: String, + lease_id: String, +} + +struct Acceptance { + active: Ticket, + pending: VecDeque, +} + +#[derive(Clone, Deserialize, PartialEq, Eq, Hash)] +#[serde(tag = "kind", rename_all = "camelCase", deny_unknown_fields)] +enum StatusOwner { + Operation { id: String }, + Run { id: String }, + Orphan, +} + +#[derive(Deserialize)] +#[serde( + tag = "method", + rename_all = "camelCase", + rename_all_fields = "camelCase", + deny_unknown_fields +)] +enum Command { + InputRecovery { + sessions: Vec, + }, + QueryInput { + scope_id: String, + snapshot: InputSnapshot, + reason: Option, + }, + BeginInput { + scope_id: String, + request_id: String, + intent: InputIntent, + source: InputSource, + reason: Option, + item_id: Option, + snapshot: InputSnapshot, + }, + AckInputEffect { + scope_id: String, + claim_id: Option, + effect_id: String, + outcome: InputEffectOutcome, + input_id: Option, + message_id: Option, + }, + PrepareClaimEffect { + scope_id: String, + owner: StatusOwner, + claim_id: String, + effect_id: String, + action: ClaimAction, + }, + InputHold { + scope_id: String, + action: InputHoldAction, + item_id: Option, + queue_ids: Option>, + head_id: Option, + }, + EndInput { + scope_id: String, + drain_lease_id: String, + }, + Open { + session_id: String, + scope_id: String, + }, + PrepareOperation { + scope_id: String, + operation_id: String, + }, + PrepareRun { + scope_id: String, + operation_id: String, + run_id: String, + message_id: String, + }, + Commit { + scope_id: String, + transition_id: String, + aborted: bool, + }, + ClearOperation { + scope_id: String, + operation_id: String, + run_id: Option, + }, + ClearRun { + scope_id: String, + run_id: String, + }, + Abort { + scope_id: String, + }, + OperationAborted { + scope_id: String, + operation_id: String, + }, + Status { + scope_id: String, + owner: StatusOwner, + status: Status, + interactions: Option, + }, + UpdateInteractions { + scope_id: String, + owner: StatusOwner, + update: InteractionUpdate, + }, + AdvanceBatch { + scope_id: String, + owner: StatusOwner, + invoked_call_id: Option, + committed_result_call_id: Option, + }, + InteractionOrigin { + scope_id: String, + owner: StatusOwner, + message_id: String, + tool_call_id: String, + origin: InteractionOrigin, + }, + CheckInteraction { + scope_id: String, + owner: StatusOwner, + message_id: String, + tool_call_id: String, + lease_id: String, + }, + Ready { + scope_id: String, + run_id: String, + }, + ClearReady { + scope_id: String, + }, + AbortDeferred { + scope_id: String, + }, + RegisterPermission { + scope_id: String, + permission_id: String, + request_id: String, + message_id: String, + tool_call_id: String, + run_id: String, + }, + TakePermission { + scope_id: String, + permission_id: Option, + request_id: String, + message_id: String, + tool_call_id: String, + run_id: Option, + lease_id: String, + granted: bool, + resolution_id: String, + }, + AcquireLease { + scope_id: String, + lease_id: String, + target: LeaseTarget, + }, + ReleaseLease { + scope_id: String, + lease_id: String, + target: LeaseTarget, + }, + ParkInteraction { + scope_id: String, + lease_id: String, + message_id: String, + tool_call_id: String, + }, + ClearSession { + session_id: String, + origin_scope_id: Option, + }, + QueueAcceptance { + scope_id: String, + lease_id: String, + lane: AcceptanceLane, + }, + ReleaseAcceptance { + scope_id: String, + lease_id: String, + lane: AcceptanceLane, + }, + Retire { + scope_id: String, + }, + Release { + scope_id: String, + }, +} + +#[derive(Serialize)] +#[serde(tag = "kind", rename_all = "camelCase")] +enum Fence { + Operation { id: String }, + Run { id: String }, + Scope { id: String }, + Permission { id: String }, +} + +#[derive(Default)] +struct State { + next_sequence: u64, + revision: u64, + closed: bool, + current: HashMap, + scopes: HashMap, + parked: HashMap>, + acceptance: HashMap<(String, AcceptanceLane), Acceptance>, + inputs: HashMap, + inputs_ready: bool, +} + +#[derive(Default)] +pub struct Control(Mutex); + +fn protocol(message: impl ToString) -> Error { + Error::Remote { + name: "HarnessControlProtocolError".into(), + message: message.to_string(), + graph: None, + } +} + +impl Control { + pub fn dispatch(&self, sequence: u64, command: Value) -> Result { + if serde_json::to_vec(&command).map_err(protocol)?.len() > MAX_METADATA { + return Err(protocol( + "Harness control metadata exceeds the runtime message bound", + )); + } + let command: Command = serde_json::from_value(command).map_err(protocol)?; + let mut state = self.0.lock().map_err(protocol)?; + if state.closed { + return Err(protocol("Harness control generation ended")); + } + if sequence == 0 || sequence > MAX_SEQUENCE || sequence != state.next_sequence + 1 { + state.closed = true; + return Err(protocol("Invalid harness control command sequence")); + } + state.next_sequence = sequence; + state.revision += 1; + state.apply(command) + } + + pub fn retire(&self) { + if let Ok(mut state) = self.0.lock() { + state.closed = true; + state.current.clear(); + state.scopes.clear(); + state.parked.clear(); + state.acceptance.clear(); + state.inputs.clear(); + } + } +} + +impl State { + fn apply_input(&mut self, command: Command) -> Result { + if let Command::InputRecovery { sessions } = command { + let is_current = |session_id: &String, scope_id: &String| { + self.current.get(session_id) == Some(scope_id) + && self + .scopes + .get(scope_id) + .is_some_and(|scope| scope.current && scope.session_id == *session_id) + }; + if self.inputs.iter().any(|(session_id, input)| { + input + .claims + .values() + .any(|claim| is_current(session_id, &claim.scope_id)) + || input + .steer_effect + .as_ref() + .is_some_and(|effect| is_current(session_id, &effect.scope_id)) + || input + .drain + .as_ref() + .is_some_and(|drain| is_current(session_id, &drain.scope_id)) + }) { + return Ok(self.receipt("", "stale", json!(false), vec![])); + } + let mut recovered = self + .inputs + .iter() + .filter_map(|(session_id, input)| { + input.latest_scope_id.as_ref().map(|scope_id| { + ( + session_id.clone(), + SessionInputs { + latest_scope_id: Some(scope_id.clone()), + ..SessionInputs::default() + }, + ) + }) + }) + .collect::>(); + let mut seen = HashSet::new(); + for session in sessions { + if session.session_id.is_empty() + || session.held_input_ids.iter().any(String::is_empty) + { + return Err(protocol("Input recovery identity is empty")); + } + if !seen.insert(session.session_id.clone()) { + return Err(protocol("Input recovery repeats a Session")); + } + recovered + .entry(session.session_id) + .or_default() + .restart_held_ids = session.held_input_ids.into_iter().collect(); + } + self.inputs = recovered; + self.inputs_ready = true; + for scope in self.scopes.values_mut() { + scope.leases.remove(&LeaseTarget::Drain); + } + return Ok(self.receipt("", "accepted", json!(true), vec![])); + } + let scope_id = match &command { + Command::QueryInput { scope_id, .. } + | Command::BeginInput { scope_id, .. } + | Command::AckInputEffect { scope_id, .. } + | Command::PrepareClaimEffect { scope_id, .. } + | Command::InputHold { scope_id, .. } + | Command::EndInput { scope_id, .. } => scope_id.clone(), + _ => unreachable!(), + }; + let Some(scope) = self.scopes.get(&scope_id) else { + return Ok(self.receipt(&scope_id, "stale", Value::Null, vec![])); + }; + let session_id = scope.session_id.clone(); + let current = scope.current && self.current.get(&session_id) == Some(&scope_id); + if !self.inputs_ready { + return Ok(self.receipt(&scope_id, "stale", Value::Null, vec![])); + } + let input = self.inputs.entry(session_id.clone()).or_default(); + let mut result = Value::Null; + let mut outcome = "accepted"; + let mut collect_claim = None; + match command { + Command::QueryInput { + snapshot, reason, .. + } if current => { + let head = snapshot.queue.first(); + let has_restart_held = head.is_some_and(|head| { + head.state == InputRowState::Pending + && input.restart_held_ids.contains(&head.id) + }); + result = json!({ + "canDrain": scope.input_status_allows(reason.unwrap_or(InputWakeReason::Enqueue)) + && scope.interactions.is_empty() && !snapshot.awaiting_follow_up + && input.drain.is_none() && !scope.leases.contains_key(&LeaseTarget::Drain) + && input.steer_effect.is_none(), + "hasRestartHeld": has_restart_held, + "onlyRestartHeld": !snapshot.has_claimed && !snapshot.has_blocking + && input.active_claim_id.is_none() && snapshot.steer_head.is_none() + && !snapshot.queue.is_empty() + && snapshot.queue.iter().all(|row| input.restart_held_ids.contains(&row.id)) + }); + } + Command::BeginInput { + request_id, + intent, + source, + reason, + item_id, + snapshot, + .. + } if current => { + if request_id.is_empty() + || snapshot.queue.iter().any(|row| row.id.is_empty()) + || snapshot + .steer_head + .as_ref() + .is_some_and(|row| row.id.is_empty()) + { + return Err(protocol("Input selection identity is empty")); + } + let reason = reason.unwrap_or(InputWakeReason::Enqueue); + let base_ready = scope.input_status_allows(reason) + && scope.interactions.is_empty() + && !snapshot.awaiting_follow_up; + let drain_busy = + input.drain.is_some() || scope.leases.contains_key(&LeaseTarget::Drain); + let claim_busy = input.active_claim_id.is_some() + || input.steer_effect.is_some() + || snapshot.has_claimed; + let has_retry = snapshot + .queue + .iter() + .any(|row| row.state == InputRowState::RetryRequired); + let steer = snapshot + .steer_head + .as_ref() + .filter(|row| row.state == InputRowState::Pending); + let queue = snapshot + .queue + .first() + .filter(|row| row.state == InputRowState::Pending); + let mut selected = None; + let mut selected_source = source; + let mut has_drain = false; + let mut enqueue_wake = false; + let action = match intent { + InputIntent::Enqueue => { + let follow_up = source == InputSource::Send + && snapshot.awaiting_follow_up + && !has_retry + && !snapshot.has_blocking + && !claim_busy + && !drain_busy; + let has_waiting = has_retry + || steer.is_some() + || if source == InputSource::Send { + queue.is_some_and(|row| !input.restart_held_ids.contains(&row.id)) + } else { + queue.is_some() + }; + if source == InputSource::Steer { + return Err(protocol("Queue admission cannot use the steer source")); + } + if follow_up + || (base_ready + && !snapshot.has_blocking + && !claim_busy + && !drain_busy + && !has_waiting) + { + has_drain = true; + "claim" + } else { + enqueue_wake = base_ready + && !snapshot.has_blocking + && !claim_busy + && (!drain_busy + || input.drain.as_ref().is_some_and(|drain| { + drain.scope_id == scope_id && drain.settled + })); + "pending" + } + } + InputIntent::Drain => { + if drain_busy { + if input + .drain + .as_ref() + .is_some_and(|drain| drain.scope_id == scope_id && drain.settled) + && scope.input_status_allows(reason) + { + input.wake_reason = Some( + input + .wake_reason + .map_or(reason, |current| current.max(reason)), + ); + } + "busy" + } else if !base_ready || snapshot.has_blocking || claim_busy { + "blocked" + } else if let Some(steer) = steer { + selected = Some(steer.id.clone()); + selected_source = InputSource::Steer; + has_drain = true; + "claim" + } else if let Some(queue) = + queue.filter(|row| !input.restart_held_ids.contains(&row.id)) + { + selected = Some(queue.id.clone()); + selected_source = InputSource::Queue; + has_drain = true; + "claim" + } else { + "blocked" + } + } + InputIntent::Prepare => { + if claim_busy + || snapshot.has_blocking + || !scope.interactions.is_empty() + || snapshot.awaiting_follow_up + { + "blocked" + } else if let Some(row) = snapshot.queue.iter().find(|row| { + Some(&row.id) == item_id.as_ref() && row.state == InputRowState::Pending + }) { + selected = Some(row.id.clone()); + selected_source = InputSource::Queue; + "prepare" + } else { + "blocked" + } + } + InputIntent::Steer => { + let operation = scope.operation.as_ref(); + let active = scope.run.is_some() + || operation.is_some_and(|operation| { + !operation.aborted + || input.interrupted_operation.as_ref() == Some(&operation.id) + }); + let merge = input + .active_steer_id + .as_ref() + .filter(|id| steer.is_some_and(|row| &row.id == *id)) + .cloned(); + let preparing_promotion = item_id.is_some() + && input + .active_claim_id + .as_ref() + .and_then(|id| input.claims.get(id)) + .is_some_and(|claim| { + claim.scope_id == scope_id + && claim.preparation_only + && claim.input_id.as_ref() == item_id.as_ref() + && claim.disposition.is_none() + && claim.outcome.is_none() + && claim.effect.is_none() + }); + let promotion = item_id + .as_ref() + .filter(|id| { + preparing_promotion + || (input.active_claim_id.is_none() + && !snapshot.has_claimed + && snapshot.queue.iter().any(|row| { + &row.id == *id && row.state == InputRowState::Pending + })) + }) + .cloned(); + if input.steer_effect.is_some() { + "busy" + } else if !scope.interactions.is_empty() + || snapshot.awaiting_follow_up + || (item_id.is_some() && promotion.is_none()) + || !(active + || promotion.is_some() + || merge.is_some() + || (base_ready && !drain_busy)) + { + "blocked" + } else { + input.steer_effect = Some(SteerEffect { + id: request_id.clone(), + scope_id: scope_id.clone(), + operation_id: operation.map(|operation| operation.id.clone()), + outcome: None, + merge_item_id: if promotion.is_some() { + None + } else { + merge.clone() + }, + promotion_id: promotion.clone(), + }); + result = json!({ "action": "steer", "effectId": request_id, + "source": "steer", "consumeBeforeProviderStream": false }); + if let Some(merge) = merge.filter(|_| promotion.is_none()) { + result["mergeItemId"] = json!(merge); + } + "steer" + } + } + }; + if matches!(action, "claim" | "prepare") { + if input.claims.contains_key(&request_id) { + return Err(protocol("Input claim identity was reused")); + } + let consume_before_stream = selected_source == InputSource::Queue + && selected + .as_ref() + .is_some_and(|id| input.manually_resumed_ids.contains(id)); + input.active_claim_id = Some(request_id.clone()); + input.claims.insert( + request_id.clone(), + InputClaim { + id: request_id.clone(), + input_id: selected.clone(), + source: selected_source, + disposition: None, + outcome: None, + consume_before_provider_stream: consume_before_stream, + effect: Some(ClaimEffect { + id: request_id.clone(), + action: ClaimAction::Claim, + outcome: None, + }), + scope_id: scope_id.clone(), + operation_id: None, + run_id: None, + user_fact: false, + rolled_back: false, + preparation_only: action == "prepare", + }, + ); + if has_drain { + input.drain = Some(InputDrain { + id: request_id.clone(), + scope_id: scope_id.clone(), + claim_id: request_id.clone(), + settled: false, + }); + self.scopes + .get_mut(&scope_id) + .expect("Selected input scope") + .leases + .insert(LeaseTarget::Drain, request_id.clone()); + } + result = json!({ "action": action, "claimId": request_id, "effectId": request_id, + "source": selected_source, "consumeBeforeProviderStream": consume_before_stream }); + if let Some(selected) = selected { + result["inputId"] = json!(selected); + } + if has_drain { + result["drainLeaseId"] = json!(request_id); + } + } else if result.is_null() { + result = json!({ "action": action, "consumeBeforeProviderStream": false }); + if enqueue_wake { + result["wakeReason"] = json!("enqueue"); + } + } + } + Command::PrepareClaimEffect { + owner, + claim_id, + effect_id, + action, + .. + } => { + let Some(claim) = input + .claims + .get_mut(&claim_id) + .filter(|claim| claim.scope_id == scope_id) + else { + return Ok(self.receipt(&scope_id, "stale", Value::Null, vec![])); + }; + let projected_operation = scope.projected_operation(&owner); + let captured_operation = matches!(&owner, StatusOwner::Operation { id } + if projected_operation == Some(id.as_str())); + let owner_matches = (scope.can_project(&owner) || captured_operation) + && claim + .operation_id + .as_deref() + .is_none_or(|operation| projected_operation == Some(operation)); + let preparation_cleanup = claim.preparation_only && current; + let originating_owner = match (&claim.operation_id, &owner) { + (Some(operation), StatusOwner::Operation { id }) => operation == id, + (Some(_), StatusOwner::Run { id }) => claim.run_id.as_ref() == Some(id), + (None, StatusOwner::Orphan) => true, + _ => false, + }; + let allowed = claim.input_id.is_some() + && claim.effect.is_none() + && claim.disposition.is_none() + && claim.outcome != Some(InputEffectOutcome::Unknown) + && input.active_claim_id.as_ref() == Some(&claim_id) + && match action { + ClaimAction::Claim => false, + ClaimAction::UserFact => { + current + && scope.prepared.is_none() + && !claim.preparation_only + && owner_matches + && !claim.user_fact + && claim.source != InputSource::Steer + && scope.run.is_none() + && scope + .operation + .as_ref() + .is_some_and(|operation| !operation.aborted) + } + ClaimAction::Rollback => { + current + && scope.prepared.is_none() + && !claim.preparation_only + && owner_matches + && claim.user_fact + && claim.source != InputSource::Steer + && claim.run_id.as_ref().is_none_or(|run_id| { + scope.run.as_ref().map_or_else( + || scope.cleared_run.as_ref() == Some(run_id), + |run| run.id == *run_id, + ) + }) + } + ClaimAction::Consume => { + !claim.preparation_only && (owner_matches || originating_owner) + } + ClaimAction::Block => current && (owner_matches || preparation_cleanup), + ClaimAction::ReleaseForMutation => { + current + && (owner_matches || preparation_cleanup) + && !claim.user_fact + && claim.source != InputSource::Steer + } + ClaimAction::ReleaseBeforeUserFact => { + !claim.user_fact + && claim.source != InputSource::Steer + && (owner_matches || originating_owner || preparation_cleanup) + } + ClaimAction::ReleaseAfterRollback => { + (owner_matches || originating_owner) + && !claim.user_fact + && claim.rolled_back + && claim.source != InputSource::Steer + } + }; + if allowed && !effect_id.is_empty() { + if claim.operation_id.is_none() && !claim.preparation_only { + claim.operation_id = projected_operation.map(str::to_owned); + } + claim.effect = Some(ClaimEffect { + id: effect_id.clone(), + action, + outcome: None, + }); + result = json!({ "effectId": effect_id, "action": action }); + } else { + outcome = "stale"; + } + } + Command::AckInputEffect { + claim_id, + effect_id, + outcome: effect_outcome, + input_id, + message_id, + .. + } => { + if let Some(claim_id) = claim_id { + let Some(claim) = input + .claims + .get_mut(&claim_id) + .filter(|claim| claim.scope_id == scope_id) + else { + return Ok(self.receipt(&scope_id, "stale", Value::Null, vec![])); + }; + let Some(effect) = claim + .effect + .as_mut() + .filter(|effect| effect.id == effect_id && effect.outcome.is_none()) + else { + return Ok(self.receipt(&scope_id, "stale", Value::Null, vec![])); + }; + let action = effect.action; + if effect_outcome == InputEffectOutcome::Applied { + if action == ClaimAction::Claim { + let selected = input_id + .as_ref() + .or(claim.input_id.as_ref()) + .filter(|id| !id.is_empty()); + if selected.is_none() + || (claim.input_id.is_some() + && input_id + .as_ref() + .is_some_and(|id| Some(id) != claim.input_id.as_ref())) + { + return Err(protocol( + "Input claim acknowledgement changed its selected identity", + )); + } + claim.input_id = selected.cloned(); + claim.user_fact = claim.source == InputSource::Steer; + if input.active_steer_id == claim.input_id + && claim.source == InputSource::Steer + { + input.active_steer_id = None; + } + } else if action == ClaimAction::UserFact { + if message_id.as_ref().is_none_or(String::is_empty) { + return Err(protocol( + "User fact acknowledgement has no message identity", + )); + } + claim.user_fact = true; + claim.rolled_back = false; + } else if action == ClaimAction::Rollback { + claim.user_fact = false; + claim.rolled_back = true; + } else { + claim.disposition = Some(action); + claim.outcome = Some(effect_outcome); + input.active_claim_id = None; + if let Some(drain) = input + .drain + .as_mut() + .filter(|drain| drain.claim_id == claim_id) + { + drain.settled = true; + } + if action == ClaimAction::Consume { + if let Some(id) = &claim.input_id { + input.restart_held_ids.remove(id); + input.manually_resumed_ids.remove(id); + } + } + } + claim.effect = None; + } else if effect_outcome == InputEffectOutcome::Rejected { + claim.effect = None; + if action == ClaimAction::Claim { + input.active_claim_id = None; + if let Some(drain) = input + .drain + .as_mut() + .filter(|drain| drain.claim_id == claim_id) + { + drain.settled = true; + } + collect_claim = Some(claim_id.clone()); + } + } else { + effect.outcome = Some(InputEffectOutcome::Unknown); + claim.outcome = Some(InputEffectOutcome::Unknown); + if action.terminal() { + claim.disposition = Some(action); + } + } + if claim.disposition.is_some() + && claim.outcome == Some(InputEffectOutcome::Applied) + && input + .drain + .as_ref() + .is_none_or(|drain| drain.claim_id != claim_id) + { + collect_claim = Some(claim_id); + } + result = json!(true); + } else { + let Some(effect) = input.steer_effect.as_mut().filter(|effect| { + effect.scope_id == scope_id + && effect.id == effect_id + && effect.outcome.is_none() + }) else { + return Ok(self.receipt(&scope_id, "stale", Value::Null, vec![])); + }; + if effect_outcome == InputEffectOutcome::Applied { + let Some(id) = input_id.filter(|id| !id.is_empty()) else { + return Err(protocol("Steer acknowledgement has no input identity")); + }; + if effect + .merge_item_id + .as_ref() + .is_some_and(|merge| *merge != id) + { + return Err(protocol( + "Steer acknowledgement changed its merge identity", + )); + } + if effect + .promotion_id + .as_ref() + .is_some_and(|promotion| *promotion != id) + { + return Err(protocol( + "Steer acknowledgement changed its promoted identity", + )); + } + input.restart_held_ids.remove(&id); + input.manually_resumed_ids.remove(&id); + if effect.promotion_id.is_none() { + input.active_steer_id = Some(id); + } + input.interrupted_operation = effect.operation_id.clone(); + input.steer_effect = None; + } else if effect_outcome == InputEffectOutcome::Rejected { + input.steer_effect = None; + } else { + effect.outcome = Some(InputEffectOutcome::Unknown); + } + result = json!(true); + } + } + Command::InputHold { + action, + item_id, + queue_ids, + head_id, + .. + } if current => match action { + InputHoldAction::ReleaseItem => { + if let Some(id) = item_id { + input.restart_held_ids.remove(&id); + input.manually_resumed_ids.remove(&id); + if input.active_steer_id.as_ref() == Some(&id) { + input.active_steer_id = None; + } + result = json!(true); + } else { + outcome = "stale"; + } + } + InputHoldAction::Resume => { + let queue_ids = queue_ids.unwrap_or_default(); + if let Some(head) = head_id.filter(|head| { + queue_ids.first() == Some(head) + && input.restart_held_ids.contains(head) + && input.drain.is_none() + && input.active_claim_id.is_none() + && input.steer_effect.is_none() + && scope.input_status_allows(InputWakeReason::Manual) + && scope.interactions.is_empty() + }) { + for id in queue_ids { + input.restart_held_ids.remove(&id); + } + input.manually_resumed_ids.insert(head); + result = json!(true); + } else { + result = json!(false); + } + } + }, + Command::EndInput { drain_lease_id, .. } => { + let matches = input + .drain + .as_ref() + .is_some_and(|drain| drain.scope_id == scope_id && drain.id == drain_lease_id); + let mut next_wake = None; + if matches { + let drain = input.drain.take().expect("Matching input drain"); + if input + .claims + .get(&drain.claim_id) + .is_some_and(|claim| claim.outcome == Some(InputEffectOutcome::Applied)) + { + input.claims.remove(&drain.claim_id); + } + if input.active_claim_id.is_none() { + next_wake = input.wake_reason.take(); + } + let scope = self + .scopes + .get_mut(&scope_id) + .expect("Originating input scope"); + if scope.leases.get(&LeaseTarget::Drain) == Some(&drain_lease_id) { + scope.leases.remove(&LeaseTarget::Drain); + } + } + result = json!({ "released": matches, "nextWake": next_wake }); + } + _ => outcome = "stale", + } + let receipt = self.receipt(&scope_id, outcome, result, vec![]); + if let Some(claim_id) = collect_claim { + self.inputs + .get_mut(&session_id) + .expect("Input Session") + .claims + .remove(&claim_id); + } + self.collect_scope(&scope_id); + Ok(receipt) + } + + fn receipt(&self, scope_id: &str, outcome: &str, result: Value, fences: Vec) -> Value { + let scope = self.scopes.get(scope_id).map(|scope| { + let mut snapshot = serde_json::to_value(scope).expect("Control scope serialization"); + snapshot["drainLeaseId"] = json!(scope.leases.get(&LeaseTarget::Drain)); + snapshot["permissions"] = json!(scope.permissions.iter().filter(|(_, permission)| !permission.taken) + .map(|(request_id, permission)| json!({ "requestId": request_id, "id": permission.id })).collect::>()); + snapshot["deferred"] = json!(scope.leases.iter().filter_map(|(target, id)| match target { + LeaseTarget::Deferred { tool_call_id, .. } => Some(json!({ "toolCallId": tool_call_id, "id": id })), + _ => None, + }).collect::>()); + snapshot["input"] = self.inputs.get(&scope.session_id).map_or_else( + || SessionInputs::default().snapshot(), + SessionInputs::snapshot, + ); + snapshot + }); + json!({ "protocol": 1, "revision": self.revision, "outcome": outcome, + "scope": scope, "result": result, "fences": fences, "grants": [] }) + } + + fn collect_scope(&mut self, scope_id: &str) { + let empty = self.scopes.get(scope_id).is_some_and(|scope| { + !scope.current + && scope.operation.is_none() + && scope.run.is_none() + && scope.prepared.is_none() + && scope.leases.is_empty() + }); + if empty + && !self.acceptance.values().any(|lane| { + lane.active.scope_id == scope_id + || lane + .pending + .iter() + .any(|ticket| ticket.scope_id == scope_id) + }) + && !self.inputs.values().any(|input| { + input + .claims + .values() + .any(|claim| claim.scope_id == scope_id) + || input + .steer_effect + .as_ref() + .is_some_and(|effect| effect.scope_id == scope_id) + || input + .drain + .as_ref() + .is_some_and(|drain| drain.scope_id == scope_id) + }) + { + self.scopes.remove(scope_id); + } + } + + fn acceptance_transition( + &mut self, + scope_id: &str, + lease_id: &str, + lane: AcceptanceLane, + release: bool, + ) -> Result { + let Some(scope) = self.scopes.get(scope_id) else { + return Ok(self.receipt(scope_id, "stale", Value::Null, vec![])); + }; + let current = scope.current + && self + .current + .get(&scope.session_id) + .is_some_and(|id| id == scope_id); + if !release && !current { + return Ok(self.receipt(scope_id, "stale", Value::Null, vec![])); + } + if lease_id.is_empty() { + return Err(protocol("Acceptance identity is empty")); + } + let key = (scope.session_id.clone(), lane); + let ticket = Ticket { + scope_id: scope_id.into(), + lease_id: lease_id.into(), + }; + let result; + let mut grants = vec![]; + if !release { + let count: usize = self + .acceptance + .values() + .map(|lane| 1 + lane.pending.len()) + .sum(); + let bytes: usize = self + .acceptance + .iter() + .map(|((session, _), lane)| { + session.len() + + lane.active.scope_id.len() + + lane.active.lease_id.len() + + lane + .pending + .iter() + .map(|ticket| ticket.scope_id.len() + ticket.lease_id.len()) + .sum::() + }) + .sum(); + if count >= 1024 || bytes + scope_id.len() + lease_id.len() + key.0.len() > MAX_METADATA + { + return Err(protocol("Acceptance ticket queue exceeded its bound")); + } + if let Some(lane) = self.acceptance.get_mut(&key) { + lane.pending.push_back(ticket); + result = json!("queued"); + } else { + self.acceptance.insert( + key, + Acceptance { + active: ticket, + pending: VecDeque::new(), + }, + ); + result = json!("granted"); + } + } else if let Some(lane) = self.acceptance.get_mut(&key) { + if lane.active.scope_id == scope_id && lane.active.lease_id == lease_id { + if let Some(next) = lane.pending.pop_front() { + grants.push(next.clone()); + lane.active = next; + } else { + self.acceptance.remove(&key); + } + result = json!(true); + } else { + let before = lane.pending.len(); + lane.pending + .retain(|pending| pending.scope_id != scope_id || pending.lease_id != lease_id); + result = json!(before != lane.pending.len()); + } + } else { + result = json!(false); + } + let mut receipt = self.receipt(scope_id, "accepted", result, vec![]); + receipt["grants"] = json!(grants); + self.collect_scope(scope_id); + Ok(receipt) + } + + fn apply(&mut self, command: Command) -> Result { + if matches!( + command, + Command::InputRecovery { .. } + | Command::QueryInput { .. } + | Command::BeginInput { .. } + | Command::AckInputEffect { .. } + | Command::PrepareClaimEffect { .. } + | Command::InputHold { .. } + | Command::EndInput { .. } + ) { + return self.apply_input(command); + } + match &command { + Command::QueueAcceptance { + scope_id, + lease_id, + lane, + } => return self.acceptance_transition(scope_id, lease_id, *lane, false), + Command::ReleaseAcceptance { + scope_id, + lease_id, + lane, + } => return self.acceptance_transition(scope_id, lease_id, *lane, true), + _ => {} + } + if let Command::ClearSession { + session_id, + origin_scope_id, + } = &command + { + let latest = self + .inputs + .get(session_id) + .and_then(|input| input.latest_scope_id.as_ref()); + if self.current.contains_key(session_id) || latest != origin_scope_id.as_ref() { + return Ok(self.receipt("", "stale", json!(false), vec![])); + } + self.parked.remove(session_id); + self.inputs.remove(session_id); + self.acceptance + .retain(|(session, _), _| session != session_id); + let mut origins = vec![]; + for scope in self + .scopes + .values_mut() + .filter(|scope| scope.session_id == *session_id) + { + scope.allow_parking = false; + scope.leases.remove(&LeaseTarget::Drain); + origins.push(scope.id.clone()); + } + for origin in origins { + self.collect_scope(&origin); + } + return Ok(self.receipt("", "accepted", json!(true), vec![])); + } + if let Command::Open { + session_id, + scope_id, + } = &command + { + if session_id.is_empty() || scope_id.is_empty() { + return Err(protocol("Harness control scope identity is empty")); + } + if self + .current + .get(session_id) + .is_some_and(|current| current != scope_id) + || self + .scopes + .get(scope_id) + .is_some_and(|scope| scope.session_id != *session_id || !scope.current) + { + return Ok(self.receipt(scope_id, "stale", Value::Null, vec![])); + } + self.scopes + .entry(scope_id.clone()) + .or_insert_with(|| Scope { + id: scope_id.clone(), + session_id: session_id.clone(), + current: true, + status: Status::Idle, + operation: None, + run: None, + first_turn_ready: false, + interactions: vec![], + batch: None, + prepared: None, + cleared_operation: None, + cleared_run: None, + leases: HashMap::new(), + allow_parking: true, + permissions: HashMap::new(), + }); + self.current.insert(session_id.clone(), scope_id.clone()); + self.inputs + .entry(session_id.clone()) + .or_default() + .latest_scope_id = Some(scope_id.clone()); + return Ok(self.receipt(scope_id, "accepted", Value::Null, vec![])); + } + let scope_id = match &command { + Command::InputRecovery { .. } + | Command::QueryInput { .. } + | Command::BeginInput { .. } + | Command::AckInputEffect { .. } + | Command::PrepareClaimEffect { .. } + | Command::InputHold { .. } + | Command::EndInput { .. } + | Command::Open { .. } + | Command::ClearSession { .. } + | Command::QueueAcceptance { .. } + | Command::ReleaseAcceptance { .. } => unreachable!(), + Command::PrepareOperation { scope_id, .. } + | Command::PrepareRun { scope_id, .. } + | Command::Commit { scope_id, .. } + | Command::ClearOperation { scope_id, .. } + | Command::ClearRun { scope_id, .. } + | Command::Abort { scope_id } + | Command::OperationAborted { scope_id, .. } + | Command::Status { scope_id, .. } + | Command::UpdateInteractions { scope_id, .. } + | Command::AdvanceBatch { scope_id, .. } + | Command::InteractionOrigin { scope_id, .. } + | Command::CheckInteraction { scope_id, .. } + | Command::Ready { scope_id, .. } + | Command::ClearReady { scope_id } + | Command::AbortDeferred { scope_id } + | Command::RegisterPermission { scope_id, .. } + | Command::TakePermission { scope_id, .. } + | Command::AcquireLease { scope_id, .. } + | Command::ReleaseLease { scope_id, .. } + | Command::ParkInteraction { scope_id, .. } + | Command::Retire { scope_id } + | Command::Release { scope_id } => scope_id.clone(), + }; + let Some(scope) = self.scopes.get_mut(&scope_id) else { + return Ok(self.receipt(&scope_id, "stale", Value::Null, vec![])); + }; + let current = scope.current && self.current.get(&scope.session_id) == Some(&scope_id); + let mut result = Value::Null; + let mut fences = vec![]; + let mut outcome = "accepted"; + match command { + Command::PrepareOperation { operation_id, .. } if current => { + if operation_id.is_empty() { + return Err(protocol("Operation identity is empty")); + } + if let (Some(run), Some(operation)) = (&scope.run, &scope.operation) { + if run.operation_id == operation.id && !operation.aborted { + result = json!({ "operationId": operation.id, "reused": true }); + return Ok(self.receipt(&scope_id, outcome, result, fences)); + } + } + if self + .inputs + .get(&scope.session_id) + .is_some_and(|input| input.has_transcript_effect(&scope_id)) + { + return Ok(self.receipt(&scope_id, "stale", Value::Null, vec![])); + } + scope.revoke_permissions(&mut fences); + scope.revoke_deferred(None, &mut fences); + if let Some(operation) = &scope.operation { + fences.push(Fence::Operation { + id: operation.id.clone(), + }); + } + if let Some(run) = &scope.run { + fences.push(Fence::Run { id: run.id.clone() }); + } + scope.prepared = Some(Prepared::Operation(Operation { + id: operation_id.clone(), + aborted: false, + })); + result = json!({ "transitionId": operation_id, "reused": false }); + } + Command::PrepareRun { + operation_id, + run_id, + message_id, + .. + } if current => { + if operation_id.is_empty() || run_id.is_empty() || message_id.is_empty() { + return Err(protocol("Run identity is empty")); + } + if self + .inputs + .get(&scope.session_id) + .is_some_and(|input| input.has_transcript_effect(&scope_id)) + { + return Ok(self.receipt(&scope_id, "stale", Value::Null, vec![])); + } + if scope.run.as_ref().is_some_and(|run| run.id != run_id) { + scope.revoke_permissions(&mut fences); + scope.revoke_deferred(None, &mut fences); + } + if let Some(operation) = &scope.operation { + if operation.id != operation_id { + fences.push(Fence::Operation { + id: operation.id.clone(), + }); + } + } + if let Some(run) = &scope.run { + if run.id != run_id { + fences.push(Fence::Run { id: run.id.clone() }); + } else if run.operation_id != operation_id || run.message_id != message_id { + return Err(protocol("Registered Run identity cannot change")); + } + } + let operation = scope + .operation + .as_ref() + .filter(|operation| operation.id == operation_id) + .cloned() + .unwrap_or(Operation { + id: operation_id.clone(), + aborted: false, + }); + scope.prepared = Some(Prepared::Run( + operation, + Run { + id: run_id.clone(), + message_id, + operation_id, + }, + )); + result = json!({ "transitionId": run_id }); + } + Command::Commit { + transition_id, + aborted, + .. + } if current => { + if scope + .prepared + .as_ref() + .is_none_or(|prepared| prepared.id() != transition_id) + { + outcome = "stale"; + } else { + match scope.prepared.take().unwrap() { + Prepared::Operation(mut operation) => { + if let Some(input) = self.inputs.get_mut(&scope.session_id) { + input.interrupted_operation = None; + if let Some(claim) = input + .active_claim_id + .as_ref() + .and_then(|id| input.claims.get_mut(id)) + { + if claim.scope_id == scope_id + && !claim.preparation_only + && claim.operation_id.is_none() + { + claim.operation_id = Some(operation.id.clone()); + } + } + } + operation.aborted |= aborted; + scope.run = None; + scope.cleared_run = None; + scope.cleared_operation = None; + result = json!({ "operationId": operation.id }); + if operation.aborted { + outcome = "cancelled"; + } + scope.operation = Some(operation); + } + Prepared::Run(mut operation, run) => { + if let Some(input) = self.inputs.get_mut(&scope.session_id) { + if let Some(claim) = input + .active_claim_id + .as_ref() + .and_then(|id| input.claims.get_mut(id)) + { + if claim.scope_id == scope_id + && !claim.preparation_only + && claim.run_id.is_none() + && claim + .operation_id + .as_ref() + .is_none_or(|id| *id == operation.id) + { + claim.operation_id = Some(operation.id.clone()); + claim.run_id = Some(run.id.clone()); + } + } + } + operation.aborted |= aborted; + result = json!({ "operationId": operation.id, "runId": run.id }); + if operation.aborted { + outcome = "cancelled"; + } + scope.operation = Some(operation); + scope.run = Some(run); + scope.cleared_run = None; + scope.cleared_operation = None; + } + } + } + } + Command::ClearOperation { + operation_id, + run_id, + .. + } => { + let matches = scope + .operation + .as_ref() + .is_some_and(|operation| operation.id == operation_id) + && scope.run.is_none() + && run_id.is_none(); + let settles = current + && scope.prepared.is_none() + && (matches + || (scope.operation.is_none() + && scope.run.is_none() + && scope + .cleared_operation + .as_ref() + .is_some_and(|(id, run)| *id == operation_id && *run == run_id))); + if matches { + scope.operation = None; + scope.cleared_operation = Some((operation_id, None)); + } + result = json!({ "cleared": matches, "settles": settles }); + } + Command::ClearRun { run_id, .. } => { + let matches = scope.run.as_ref().is_some_and(|run| run.id == run_id); + if matches { + scope.revoke_permissions(&mut fences); + scope.revoke_deferred(None, &mut fences); + let run = scope.run.take().unwrap(); + if scope + .operation + .as_ref() + .is_some_and(|operation| operation.id == run.operation_id) + { + scope.cleared_operation = scope + .operation + .take() + .map(|operation| (operation.id, Some(run.id.clone()))); + } + scope.cleared_run = Some(run.id); + } + result = json!(matches); + } + Command::Abort { .. } if current => { + if let Some(input) = self.inputs.get_mut(&scope.session_id) { + input.interrupted_operation = None; + } + scope.revoke_permissions(&mut fences); + scope.revoke_deferred(None, &mut fences); + if let Some(operation) = &mut scope.operation { + operation.aborted = true; + fences.push(Fence::Operation { + id: operation.id.clone(), + }); + } + if scope.run.is_none() { + if let Some(operation) = scope.operation.take() { + scope.cleared_operation = Some((operation.id, None)); + } + } + if let Some(prepared) = &mut scope.prepared { + let operation = match prepared { + Prepared::Operation(operation) | Prepared::Run(operation, _) => operation, + }; + operation.aborted = true; + fences.push(Fence::Operation { + id: operation.id.clone(), + }); + } + } + Command::OperationAborted { operation_id, .. } => { + let mut aborted_current = false; + if let Some(operation) = &mut scope.operation { + if operation.id == operation_id { + operation.aborted = true; + aborted_current = true; + } + } + if aborted_current { + scope.revoke_permissions(&mut fences); + scope.revoke_deferred(None, &mut fences); + } + if let Some(prepared) = &mut scope.prepared { + let operation = match prepared { + Prepared::Operation(operation) | Prepared::Run(operation, _) => operation, + }; + if operation.id == operation_id { + operation.aborted = true; + } + } + } + Command::Status { + owner, + status, + interactions, + .. + } if current => { + if scope.can_project(&owner) { + if let Some(update) = interactions { + scope.update_interactions(update)?; + } + scope.status = status; + result = json!(true); + } else { + outcome = "stale"; + result = json!(false); + } + } + Command::UpdateInteractions { owner, update, .. } if current => { + if scope.can_project(&owner) { + scope.update_interactions(update)?; + result = json!(true); + } else { + outcome = "stale"; + result = json!(false); + } + } + Command::AdvanceBatch { + owner, + invoked_call_id, + committed_result_call_id, + .. + } if current => { + if scope.can_project(&owner) { + if let Some(mut batch) = scope.batch.clone() { + for (id, selected) in [ + (invoked_call_id, &mut batch.invoked_call_ids), + ( + committed_result_call_id, + &mut batch.committed_result_call_ids, + ), + ] { + if let Some(id) = id { + if id.is_empty() { + return Err(protocol("Tool batch advance identity is empty")); + } + if !selected.contains(&id) { + selected.push(id); + } + } + } + if serde_json::to_vec(&(&scope.interactions, &batch)) + .map_err(protocol)? + .len() + > MAX_METADATA + { + return Err(protocol( + "Pending interaction metadata exceeds the runtime bound", + )); + } + scope.batch = Some(batch); + result = json!(true); + } else { + result = json!(false); + } + } else { + outcome = "stale"; + result = json!(false); + } + } + Command::InteractionOrigin { + owner, + message_id, + tool_call_id, + origin, + .. + } if current => { + if scope.can_project(&owner) { + let interaction = scope.interactions.iter_mut().find(|interaction| { + interaction.message_id == message_id + && interaction.tool_call_id == tool_call_id + }); + if let Some(interaction) = interaction { + interaction.origin = origin; + if let Some(batch) = &mut scope.batch { + batch.pending_interaction_call_ids = scope + .interactions + .iter() + .filter(|interaction| { + interaction.origin != InteractionOrigin::AcpPermission + }) + .map(|interaction| interaction.tool_call_id.clone()) + .collect(); + if batch.pending_interaction_call_ids.is_empty() { + scope.batch = None; + } + } + result = json!(true); + } else { + result = json!(false); + } + } else { + outcome = "stale"; + result = json!(false); + } + } + Command::CheckInteraction { + owner, + message_id, + tool_call_id, + lease_id, + .. + } if current => { + let allowed = scope.can_project(&owner) + && scope.is_first_interaction(&message_id, &tool_call_id) + && scope.leases.get(&LeaseTarget::Interaction { + message_id, + tool_call_id, + }) == Some(&lease_id); + result = json!(allowed); + if !allowed { + outcome = "stale"; + } + } + Command::Ready { run_id, .. } if current => { + let allowed = scope.prepared.is_none() + && scope.run.as_ref().is_some_and(|run| run.id == run_id) + && scope + .operation + .as_ref() + .is_some_and(|operation| !operation.aborted); + if allowed { + scope.first_turn_ready = true; + } + result = json!(allowed); + } + Command::ClearReady { .. } if current => scope.first_turn_ready = false, + Command::AbortDeferred { .. } => scope.revoke_deferred(None, &mut fences), + Command::RegisterPermission { + permission_id, + request_id, + message_id, + tool_call_id, + run_id, + .. + } if current => { + if permission_id.is_empty() || request_id.is_empty() { + return Err(protocol("Permission identity is empty")); + } + let allowed = + scope.can_start(&StatusOwner::Run { id: run_id.clone() }, &message_id); + if allowed { + if let Some(previous) = scope.permissions.insert( + request_id, + Permission { + id: permission_id, + message_id, + tool_call_id, + run_id: Some(run_id), + taken: false, + }, + ) { + if !previous.taken { + fences.push(Fence::Permission { id: previous.id }); + } + } + } + result = json!(allowed); + } + Command::TakePermission { + permission_id, + request_id, + message_id, + tool_call_id, + run_id, + lease_id, + granted, + resolution_id, + .. + } if current => { + let lease = LeaseTarget::Interaction { + message_id: message_id.clone(), + tool_call_id: tool_call_id.clone(), + }; + let run_current = run_id.as_ref().is_some_and(|run_id| { + scope.can_start(&StatusOwner::Run { id: run_id.clone() }, &message_id) + }); + if scope.prepared.is_some() + || scope.leases.get(&lease) != Some(&lease_id) + || (run_id.is_some() && !run_current) + || (granted + && (!run_current + || !scope.is_first_interaction(&message_id, &tool_call_id))) + { + outcome = "stale"; + } else if let Some(permission) = scope.permissions.get_mut(&request_id) { + if permission.message_id != message_id + || permission.tool_call_id != tool_call_id + { + result = json!("conflict"); + } else if permission.taken { + result = json!("spent"); + } else if permission_id.as_ref() != Some(&permission.id) + || (run_id.is_some() && run_id != permission.run_id) + { + result = json!("conflict"); + } else { + permission.taken = true; + result = json!("callback"); + } + } else if permission_id.is_some() { + result = json!("spent"); + } else { + if request_id.is_empty() || resolution_id.is_empty() { + return Err(protocol("Permission resolution identity is empty")); + } + scope.permissions.insert( + request_id, + Permission { + id: resolution_id, + message_id, + tool_call_id, + run_id, + taken: true, + }, + ); + result = json!("direct"); + } + } + Command::AcquireLease { + lease_id, target, .. + } if current => { + if matches!(target, LeaseTarget::Drain) { + return Err(protocol("Input drains require input selection")); + } + let valid_target = match &target { + LeaseTarget::Interaction { + message_id, + tool_call_id, + } => !message_id.is_empty() && !tool_call_id.is_empty(), + LeaseTarget::Resume { message_id } => !message_id.is_empty(), + LeaseTarget::Deferred { + message_id, + tool_call_id, + parent_lease_id, + owner, + } => { + !message_id.is_empty() + && !tool_call_id.is_empty() + && scope.can_start(owner, message_id) + && scope.is_first_interaction(message_id, tool_call_id) + && scope.leases.get(&LeaseTarget::Interaction { + message_id: message_id.clone(), + tool_call_id: tool_call_id.clone(), + }) == Some(parent_lease_id) + } + LeaseTarget::Drain => unreachable!(), + }; + if lease_id.is_empty() || !valid_target { + if matches!(target, LeaseTarget::Deferred { .. }) { + return Ok(self.receipt(&scope_id, "stale", Value::Null, vec![])); + } + return Err(protocol("Control lease identity is empty")); + } + let parked = match &target { + LeaseTarget::Interaction { message_id, .. } => self + .parked + .get(&scope.session_id) + .is_some_and(|messages| messages.contains(message_id)), + _ => false, + }; + if parked { + result = json!("parked"); + } else if let std::collections::hash_map::Entry::Vacant(entry) = + scope.leases.entry(target) + { + entry.insert(lease_id); + result = json!("granted"); + } else { + result = json!("busy"); + } + } + Command::ReleaseLease { + lease_id, target, .. + } => { + if matches!(target, LeaseTarget::Drain) { + return Err(protocol("Input drains require originating input cleanup")); + } + let matches = scope.leases.get(&target) == Some(&lease_id); + if matches { + scope.leases.remove(&target); + if matches!(target, LeaseTarget::Interaction { .. }) { + scope.revoke_deferred(Some(&lease_id), &mut fences); + } + } + result = json!(matches); + } + Command::ParkInteraction { + lease_id, + message_id, + tool_call_id, + .. + } => { + let target = LeaseTarget::Interaction { + message_id: message_id.clone(), + tool_call_id, + }; + let matches = scope.allow_parking && scope.leases.get(&target) == Some(&lease_id); + if matches { + self.parked + .entry(scope.session_id.clone()) + .or_default() + .insert(message_id); + } + result = json!(matches); + } + Command::Retire { .. } | Command::Release { .. } => { + scope.revoke_permissions(&mut fences); + scope.revoke_deferred(None, &mut fences); + scope.interactions.clear(); + scope.batch = None; + scope.current = false; + if self.current.get(&scope.session_id) == Some(&scope_id) { + self.current.remove(&scope.session_id); + } + fences.push(Fence::Scope { + id: scope_id.clone(), + }); + scope.prepared = None; + for lane in self.acceptance.values_mut() { + lane.pending.retain(|ticket| ticket.scope_id != scope_id); + } + if scope.run.is_none() { + scope.cleared_operation = + scope.operation.take().map(|operation| (operation.id, None)); + } + } + _ => outcome = "stale", + } + let receipt = self.receipt(&scope_id, outcome, result, fences); + self.collect_scope(&scope_id); + Ok(receipt) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[derive(Default)] + struct Actor { + control: Control, + sequence: u64, + } + impl Actor { + fn send(&mut self, command: Value) -> Value { + self.sequence += 1; + self.control.dispatch(self.sequence, command).unwrap() + } + fn open(&mut self, scope: &str) -> Value { + self.send(json!({ "method": "open", "sessionId": "session", "scopeId": scope })) + } + fn operation(&mut self, scope: &str, operation: &str) { + self.send( + json!({ "method": "prepareOperation", "scopeId": scope, "operationId": operation }), + ); + self.send(json!({ "method": "commit", "scopeId": scope, "transitionId": operation, "aborted": false })); + } + fn run(&mut self, scope: &str, operation: &str, run: &str) -> Value { + self.send(json!({ "method": "prepareRun", "scopeId": scope, "operationId": operation, "runId": run, "messageId": format!("message-{run}") })); + self.send(json!({ "method": "commit", "scopeId": scope, "transitionId": run, "aborted": false })) + } + fn input_snapshot(queue: &[&str], steer: Option<&str>) -> Value { + json!({ + "queue": queue.iter().map(|id| json!({ "id": id, "state": "pending" })).collect::>(), + "steerHead": steer.map(|id| json!({ "id": id, "state": "pending" })), + "hasClaimed": false, "hasBlocking": false, "awaitingFollowUp": false + }) + } + fn begin_input( + &mut self, + scope: &str, + request: &str, + intent: &str, + source: &str, + snapshot: &Value, + ) -> Value { + self.send( + json!({ "method": "beginInput", "scopeId": scope, "requestId": request, + "intent": intent, "source": source, "snapshot": snapshot }), + ) + } + fn input_effect( + &mut self, + scope: &str, + claim: &str, + effect: &str, + action: &str, + owner: Value, + ) -> Value { + self.send( + json!({ "method": "prepareClaimEffect", "scopeId": scope, "claimId": claim, + "effectId": effect, "action": action, "owner": owner }), + ) + } + fn input_ack( + &mut self, + scope: &str, + claim: &str, + effect: &str, + outcome: &str, + input: Option<&str>, + message: Option<&str>, + ) -> Value { + let mut command = json!({ "method": "ackInputEffect", "scopeId": scope, "claimId": claim, + "effectId": effect, "outcome": outcome }); + if let Some(input) = input { + command["inputId"] = json!(input); + } + if let Some(message) = message { + command["messageId"] = json!(message); + } + self.send(command) + } + } + + #[test] + fn input_claims_serialize_selection_and_fence_unknown_or_replaced_origins() { + let mut actor = Actor::default(); + actor.open("old"); + let snapshot = Actor::input_snapshot(&[], None); + assert_eq!( + actor.begin_input("old", "before-ready", "enqueue", "send", &snapshot)["outcome"], + "stale" + ); + actor.send(json!({ "method": "inputRecovery", "sessions": [] })); + let selected = actor.begin_input("old", "send", "enqueue", "send", &snapshot); + assert_eq!(selected["result"]["action"], "claim"); + assert_eq!(selected["result"]["drainLeaseId"], "send"); + let held = actor.control.0.lock().unwrap().inputs["session"].snapshot(); + let blocked_recovery = actor.send(json!({ "method": "inputRecovery", "sessions": [{ "sessionId": "session", "heldInputIds": ["replacement-hold"] }] })); + assert_eq!(blocked_recovery["outcome"], "stale"); + assert_eq!(blocked_recovery["result"], false); + assert_eq!( + actor.control.0.lock().unwrap().inputs["session"].snapshot(), + held + ); + for method in ["acquireLease", "releaseLease"] { + actor.sequence += 1; + assert!(actor.control.dispatch(actor.sequence, json!({ "method": method, "scopeId": "old", "leaseId": "send", "target": { "kind": "drain" } })).is_err()); + } + assert_eq!( + actor.send(json!({ "method": "clearReady", "scopeId": "old" }))["scope"] + ["drainLeaseId"], + "send" + ); + assert_eq!( + actor.begin_input("old", "steer", "steer", "steer", &snapshot)["result"]["action"], + "blocked" + ); + assert_eq!( + actor.begin_input("old", "queue", "enqueue", "queue", &snapshot)["result"]["action"], + "pending" + ); + assert_eq!( + actor.begin_input( + "old", + "drain", + "drain", + "queue", + &Actor::input_snapshot(&[], Some("steer")) + )["result"]["action"], + "busy" + ); + actor.input_ack("old", "send", "send", "applied", Some("first-input"), None); + assert_eq!(actor.send(json!({ "method": "beginInput", "scopeId": "old", "requestId": "turn-promotion", + "intent": "steer", "source": "steer", "itemId": "first-input", "snapshot": Actor::input_snapshot(&["first-input"], None) }))["result"]["action"], "blocked"); + actor.operation("old", "operation"); + let owner = json!({ "kind": "operation", "id": "operation" }); + let wrong_owner = json!({ "kind": "operation", "id": "wrong" }); + assert_eq!( + actor.input_effect("old", "send", "wrong", "user-fact", wrong_owner)["outcome"], + "stale" + ); + assert_eq!( + actor.input_effect("old", "send", "fact", "user-fact", owner.clone())["result"] + ["effectId"], + "fact" + ); + assert_eq!( + actor.input_effect("old", "send", "overlap", "consume", owner.clone())["outcome"], + "stale" + ); + let blocked_run = actor.send(json!({ "method": "prepareRun", "scopeId": "old", "operationId": "operation", "runId": "too-early", "messageId": "too-early" })); + assert_eq!(blocked_run["outcome"], "stale"); + assert_eq!(blocked_run["fences"], json!([])); + assert_eq!(actor.send(json!({ "method": "prepareOperation", "scopeId": "old", "operationId": "too-early" }))["outcome"], "stale"); + actor.input_ack("old", "send", "fact", "applied", None, Some("user-message")); + actor.run("old", "operation", "first-run"); + assert_eq!( + actor.input_effect("old", "send", "rollback-current", "rollback", owner.clone()) + ["outcome"], + "accepted" + ); + assert_eq!(actor.send(json!({ "method": "prepareRun", "scopeId": "old", "operationId": "other-operation", "runId": "before-rollback", "messageId": "before-rollback" }))["outcome"], "stale"); + actor.input_ack("old", "send", "rollback-current", "rejected", None, None); + actor.run("old", "operation", "second-run"); + assert_eq!( + actor.input_effect( + "old", + "send", + "rollback-replaced", + "rollback", + owner.clone() + )["outcome"], + "stale" + ); + actor.send(json!({ "method": "abort", "scopeId": "old" })); + actor.operation("old", "superseding-operation"); + assert_eq!( + actor.input_effect( + "old", + "send", + "wrong-cleanup", + "consume", + json!({ "kind": "operation", "id": "superseding-operation" }) + )["outcome"], + "stale" + ); + assert_eq!( + actor.input_effect( + "old", + "send", + "superseded-rollback", + "rollback", + owner.clone() + )["outcome"], + "stale" + ); + assert_eq!( + actor.input_effect("old", "send", "consume-active", "consume", owner.clone()) + ["outcome"], + "accepted" + ); + actor.input_ack("old", "send", "consume-active", "rejected", None, None); + actor.send(json!({ "method": "retire", "scopeId": "old" })); + actor.open("new"); + assert_eq!( + actor.begin_input("new", "replacement", "enqueue", "send", &snapshot)["result"] + ["action"], + "pending" + ); + assert_eq!( + actor.input_effect("old", "send", "rollback", "rollback", owner.clone())["outcome"], + "stale" + ); + actor.input_effect("old", "send", "consume", "consume", owner); + assert_eq!( + actor.input_ack("new", "send", "consume", "applied", None, None)["outcome"], + "stale" + ); + let settled = actor.input_ack("old", "send", "consume", "applied", None, None); + assert_eq!(settled["scope"]["input"]["activeClaimId"], Value::Null); + assert_eq!( + settled["scope"]["input"]["claims"][0]["disposition"], + "consume" + ); + actor.send(json!({ "method": "clearRun", "scopeId": "old", "runId": "second-run" })); + assert_eq!( + actor.send(json!({ "method": "endInput", "scopeId": "new", "drainLeaseId": "send" })) + ["result"]["released"], + false + ); + assert_eq!( + actor.send(json!({ "method": "endInput", "scopeId": "old", "drainLeaseId": "send" })) + ["result"]["released"], + true + ); + assert!(!actor.control.0.lock().unwrap().scopes.contains_key("old")); + + actor.begin_input("new", "unknown", "enqueue", "send", &snapshot); + actor.input_ack( + "new", + "unknown", + "unknown", + "applied", + Some("second-input"), + None, + ); + actor.operation("new", "new-operation"); + actor.input_effect( + "new", + "unknown", + "uncertain-fact", + "user-fact", + json!({ "kind": "operation", "id": "new-operation" }), + ); + let unknown = actor.input_ack("new", "unknown", "uncertain-fact", "unknown", None, None); + assert_eq!(unknown["scope"]["input"]["activeClaimId"], "unknown"); + assert_eq!(unknown["scope"]["input"]["claims"][0]["outcome"], "unknown"); + actor.send(json!({ "method": "endInput", "scopeId": "new", "drainLeaseId": "unknown" })); + assert_eq!( + actor.begin_input("new", "after-unknown", "enqueue", "send", &snapshot)["result"] + ["action"], + "pending" + ); + assert_eq!( + actor.input_effect( + "new", + "unknown", + "release", + "release-before-user-fact", + json!({ "kind": "operation", "id": "new-operation" }) + )["outcome"], + "stale" + ); + let unknown_state = actor.control.0.lock().unwrap().inputs["session"].snapshot(); + assert_eq!( + actor.send(json!({ "method": "inputRecovery", "sessions": [] }))["outcome"], + "stale" + ); + assert_eq!( + actor.control.0.lock().unwrap().inputs["session"].snapshot(), + unknown_state + ); + actor.send(json!({ "method": "retire", "scopeId": "new" })); + assert_eq!( + actor.send(json!({ "method": "inputRecovery", "sessions": [] }))["result"], + true + ); + actor.open("recovered"); + actor.operation("recovered", "new-operation"); + assert_eq!( + actor.input_ack( + "new", + "unknown", + "uncertain-fact", + "applied", + None, + Some("late") + )["outcome"], + "stale" + ); + assert_eq!( + actor.begin_input("recovered", "visible", "steer", "steer", &snapshot)["result"] + ["action"], + "steer" + ); + assert_eq!( + actor.begin_input( + "recovered", + "send-behind-steer", + "enqueue", + "send", + &snapshot + )["result"]["action"], + "pending" + ); + assert_eq!( + actor.send(json!({ "method": "inputRecovery", "sessions": [] }))["outcome"], + "stale" + ); + actor.send(json!({ "method": "ackInputEffect", "scopeId": "recovered", "effectId": "visible", "outcome": "applied", "inputId": "open-steer" })); + actor.send(json!({ "method": "operationAborted", "scopeId": "recovered", "operationId": "new-operation" })); + let steer_snapshot = Actor::input_snapshot(&["promoted"], Some("open-steer")); + let merged = actor.begin_input("recovered", "merge", "steer", "steer", &steer_snapshot); + assert_eq!(merged["result"]["mergeItemId"], "open-steer"); + actor.send(json!({ "method": "ackInputEffect", "scopeId": "recovered", "effectId": "merge", "outcome": "rejected" })); + let promotion = actor.send(json!({ "method": "beginInput", "scopeId": "recovered", "requestId": "promotion", "intent": "steer", "source": "steer", "itemId": "promoted", "snapshot": steer_snapshot })); + assert_eq!(promotion["result"]["action"], "steer"); + assert_eq!(promotion["result"]["mergeItemId"], Value::Null); + let promoted = actor.send(json!({ "method": "ackInputEffect", "scopeId": "recovered", "effectId": "promotion", "outcome": "applied", "inputId": "promoted" })); + assert_eq!(promoted["scope"]["input"]["activeSteerId"], "open-steer"); + + actor.send( + json!({ "method": "open", "sessionId": "wake-session", "scopeId": "wake-scope" }), + ); + let waiting = Actor::input_snapshot(&["waiting"], None); + let status = |status: &str| json!({ "method": "status", "scopeId": "wake-scope", "owner": { "kind": "orphan" }, "status": status }); + actor.send(status("generating")); + let old_enqueue = + actor.begin_input("wake-scope", "old-enqueue", "enqueue", "queue", &waiting); + assert_eq!(old_enqueue["result"]["action"], "pending"); + assert!(old_enqueue["result"].get("wakeReason").is_none()); + actor.send(status("error")); + assert_eq!( + actor.send( + json!({ "method": "beginInput", "scopeId": "wake-scope", "requestId": "completed", + "intent": "drain", "source": "queue", "reason": "completed", "snapshot": waiting }) + )["result"]["action"], + "blocked" + ); + let fresh_enqueue = + actor.begin_input("wake-scope", "fresh-enqueue", "enqueue", "queue", &waiting); + assert_eq!(fresh_enqueue["result"]["wakeReason"], "enqueue"); + let mut blocked = waiting.clone(); + blocked["hasBlocking"] = json!(true); + assert!(actor.begin_input( + "wake-scope", + "blocked-enqueue", + "enqueue", + "queue", + &blocked + )["result"] + .get("wakeReason") + .is_none()); + let mut follow_up = waiting.clone(); + follow_up["awaitingFollowUp"] = json!(true); + assert!(actor.begin_input( + "wake-scope", + "followup-enqueue", + "enqueue", + "queue", + &follow_up + )["result"] + .get("wakeReason") + .is_none()); + actor.begin_input("wake-scope", "wake-drain", "drain", "queue", &waiting); + actor.input_ack( + "wake-scope", + "wake-drain", + "wake-drain", + "applied", + Some("waiting"), + None, + ); + assert!(actor.begin_input( + "wake-scope", + "unsettled-enqueue", + "enqueue", + "queue", + &waiting + )["result"] + .get("wakeReason") + .is_none()); + actor.input_effect( + "wake-scope", + "wake-drain", + "wake-consume", + "consume", + json!({ "kind": "orphan" }), + ); + actor.input_ack( + "wake-scope", + "wake-drain", + "wake-consume", + "applied", + None, + None, + ); + let settled = actor.begin_input( + "wake-scope", + "settled-enqueue", + "enqueue", + "queue", + &waiting, + ); + assert_eq!(settled["result"]["wakeReason"], "enqueue"); + assert_eq!( + actor.send(json!({ "method": "inputRecovery", "sessions": [] }))["outcome"], + "stale" + ); + actor.send( + json!({ "method": "endInput", "scopeId": "wake-scope", "drainLeaseId": "wake-drain" }), + ); + + actor.send(json!({ "method": "open", "sessionId": "rollback-session", "scopeId": "rollback-origin" })); + actor.begin_input( + "rollback-origin", + "rollback-claim", + "enqueue", + "queue", + &snapshot, + ); + actor.input_ack( + "rollback-origin", + "rollback-claim", + "rollback-claim", + "applied", + Some("rollback-input"), + None, + ); + actor.operation("rollback-origin", "rollback-operation"); + let original_owner = json!({ "kind": "operation", "id": "rollback-operation" }); + actor.input_effect( + "rollback-origin", + "rollback-claim", + "rollback-fact", + "user-fact", + original_owner.clone(), + ); + actor.input_ack( + "rollback-origin", + "rollback-claim", + "rollback-fact", + "applied", + None, + Some("rollback-message"), + ); + actor.input_effect( + "rollback-origin", + "rollback-claim", + "applied-rollback", + "rollback", + original_owner.clone(), + ); + actor.input_ack( + "rollback-origin", + "rollback-claim", + "applied-rollback", + "applied", + None, + None, + ); + actor.send(json!({ "method": "retire", "scopeId": "rollback-origin" })); + actor.send(json!({ "method": "open", "sessionId": "rollback-session", "scopeId": "rollback-replacement" })); + actor.operation("rollback-replacement", "replacement-operation"); + let replacement_owner = json!({ "kind": "operation", "id": "replacement-operation" }); + assert_eq!( + actor.input_effect( + "rollback-origin", + "rollback-claim", + "wrong-release", + "release-after-rollback", + replacement_owner + )["outcome"], + "stale" + ); + assert_eq!( + actor.input_effect( + "rollback-origin", + "rollback-claim", + "late-fact", + "user-fact", + original_owner.clone() + )["outcome"], + "stale" + ); + assert_eq!( + actor.input_effect( + "rollback-origin", + "rollback-claim", + "second-rollback", + "rollback", + original_owner.clone() + )["outcome"], + "stale" + ); + assert_eq!( + actor.input_effect( + "rollback-origin", + "rollback-claim", + "origin-release", + "release-after-rollback", + original_owner + )["outcome"], + "accepted" + ); + actor.input_ack( + "rollback-origin", + "rollback-claim", + "origin-release", + "applied", + None, + None, + ); + actor.send(json!({ "method": "endInput", "scopeId": "rollback-origin", "drainLeaseId": "rollback-claim" })); + assert_eq!( + actor.begin_input( + "rollback-replacement", + "after-rollback", + "enqueue", + "queue", + &snapshot + )["result"]["action"], + "claim" + ); + actor.input_ack( + "rollback-replacement", + "after-rollback", + "after-rollback", + "rejected", + None, + None, + ); + actor.send(json!({ "method": "endInput", "scopeId": "rollback-replacement", "drainLeaseId": "after-rollback" })); + } + + #[test] + fn input_recovery_manual_tickets_and_wake_priority_preserve_once_only_effects() { + let mut actor = Actor::default(); + actor.send(json!({ "method": "inputRecovery", "sessions": [{ "sessionId": "session", "heldInputIds": ["first", "second"] }] })); + actor.open("scope"); + let snapshot = Actor::input_snapshot(&["first", "second"], None); + let query = |snapshot: &Value| json!({ "method": "queryInput", "scopeId": "scope", "snapshot": snapshot }); + assert_eq!( + actor.send(query(&snapshot))["result"], + json!({ "canDrain": true, "hasRestartHeld": true, "onlyRestartHeld": true }) + ); + assert_eq!( + actor.begin_input("scope", "held", "drain", "queue", &snapshot)["result"]["action"], + "blocked" + ); + let bypass = actor.begin_input("scope", "send", "enqueue", "send", &snapshot); + assert_eq!(bypass["result"]["action"], "claim"); + actor.input_ack("scope", "send", "send", "rejected", None, None); + actor.send(json!({ "method": "endInput", "scopeId": "scope", "drainLeaseId": "send" })); + let mut blocked_snapshot = Actor::input_snapshot(&["blocked"], None); + blocked_snapshot["queue"][0]["state"] = json!("blocked"); + blocked_snapshot["hasBlocking"] = json!(true); + assert_eq!( + actor.send(query(&blocked_snapshot))["result"]["canDrain"], + true + ); + assert_eq!( + actor.begin_input( + "scope", + "visible-blocked", + "steer", + "steer", + &blocked_snapshot + )["result"]["action"], + "steer" + ); + actor.send(json!({ "method": "ackInputEffect", "scopeId": "scope", "effectId": "visible-blocked", "outcome": "applied", "inputId": "visible-pending" })); + assert_eq!( + actor.begin_input( + "scope", + "blocked-drain", + "drain", + "queue", + &blocked_snapshot + )["result"]["action"], + "blocked" + ); + assert_eq!(actor.send(json!({ "method": "inputHold", "scopeId": "scope", "action": "resume", "queueIds": ["first", "second"], "headId": "first" }))["result"], true); + let steer_first = actor.begin_input( + "scope", + "steer", + "drain", + "queue", + &Actor::input_snapshot(&["first", "second"], Some("steer-input")), + ); + assert_eq!(steer_first["result"]["inputId"], "steer-input"); + assert_eq!(steer_first["result"]["source"], "steer"); + actor.input_ack( + "scope", + "steer", + "steer", + "applied", + Some("steer-input"), + None, + ); + actor.input_effect( + "scope", + "steer", + "steer-consume", + "consume", + json!({ "kind": "orphan" }), + ); + actor.input_ack("scope", "steer", "steer-consume", "applied", None, None); + actor.send(json!({ "method": "endInput", "scopeId": "scope", "drainLeaseId": "steer" })); + + let manual = actor.begin_input("scope", "manual", "drain", "queue", &snapshot); + assert_eq!(manual["result"]["consumeBeforeProviderStream"], true); + actor.input_ack("scope", "manual", "manual", "applied", Some("first"), None); + actor.operation("scope", "operation"); + let owner = json!({ "kind": "operation", "id": "operation" }); + actor.input_effect("scope", "manual", "fact", "user-fact", owner.clone()); + actor.input_ack( + "scope", + "manual", + "fact", + "applied", + None, + Some("user-message"), + ); + let ticket = actor.input_effect("scope", "manual", "pre-consume", "consume", owner.clone()); + assert_eq!( + ticket["scope"]["input"]["claims"][0]["disposition"], + Value::Null + ); + assert_eq!( + actor.run("scope", "operation", "manual-run")["outcome"], + "accepted" + ); + assert_eq!( + actor.input_effect("scope", "manual", "overlap", "rollback", owner.clone())["outcome"], + "stale" + ); + actor.input_ack("scope", "manual", "pre-consume", "rejected", None, None); + actor.input_effect("scope", "manual", "rollback", "rollback", owner.clone()); + actor.input_ack("scope", "manual", "rollback", "applied", None, None); + actor.input_effect( + "scope", + "manual", + "release", + "release-after-rollback", + owner.clone(), + ); + actor.input_ack("scope", "manual", "release", "applied", None, None); + actor.send(json!({ "method": "clearRun", "scopeId": "scope", "runId": "manual-run" })); + for reason in ["completed", "manual", "enqueue"] { + actor.send( + json!({ "method": "beginInput", "scopeId": "scope", "requestId": reason, + "intent": "drain", "source": "queue", "reason": reason, "snapshot": snapshot }), + ); + } + assert_eq!( + actor.send( + json!({ "method": "endInput", "scopeId": "scope", "drainLeaseId": "manual" }) + )["result"]["nextWake"], + "manual" + ); + let resumed = actor.begin_input("scope", "resumed", "drain", "queue", &snapshot); + assert_eq!(resumed["result"]["consumeBeforeProviderStream"], true); + actor.input_ack( + "scope", + "resumed", + "resumed", + "applied", + Some("first"), + None, + ); + actor.operation("scope", "resumed-operation"); + let owner = json!({ "kind": "operation", "id": "resumed-operation" }); + actor.input_effect( + "scope", + "resumed", + "resumed-fact", + "user-fact", + owner.clone(), + ); + actor.input_ack( + "scope", + "resumed", + "resumed-fact", + "applied", + None, + Some("new-message"), + ); + actor.input_effect("scope", "resumed", "consumed", "consume", owner.clone()); + let consumed = actor.input_ack("scope", "resumed", "consumed", "applied", None, None); + assert_eq!(consumed["scope"]["input"]["manuallyResumedIds"], json!([])); + let preparation = actor.send( + json!({ "method": "beginInput", "scopeId": "scope", "requestId": "prepare", + "intent": "prepare", "source": "queue", "itemId": "second", "snapshot": snapshot }), + ); + assert_eq!(preparation["result"]["action"], "prepare"); + actor.input_ack( + "scope", + "prepare", + "prepare", + "applied", + Some("second"), + None, + ); + assert_eq!( + actor.input_effect("scope", "prepare", "no-fact", "user-fact", owner)["outcome"], + "stale" + ); + actor.input_effect( + "scope", + "prepare", + "block", + "block", + json!({ "kind": "orphan" }), + ); + actor.input_ack("scope", "prepare", "block", "applied", None, None); + assert_eq!( + actor.input_effect( + "scope", + "prepare", + "again", + "release-for-mutation", + json!({ "kind": "orphan" }) + )["outcome"], + "stale" + ); + actor.send(json!({ "method": "endInput", "scopeId": "scope", "drainLeaseId": "resumed" })); + let promotion_snapshot = Actor::input_snapshot(&["promotion-row", "other-row"], None); + actor.send(json!({ "method": "beginInput", "scopeId": "scope", "requestId": "promotion-preparation", "intent": "prepare", + "source": "queue", "itemId": "promotion-row", "snapshot": promotion_snapshot })); + actor.input_ack( + "scope", + "promotion-preparation", + "promotion-preparation", + "applied", + Some("promotion-row"), + None, + ); + let mut claimed_snapshot = Actor::input_snapshot(&["other-row"], None); + claimed_snapshot["hasClaimed"] = json!(true); + assert_eq!(actor.send(json!({ "method": "beginInput", "scopeId": "scope", "requestId": "wrong-promotion", + "intent": "steer", "source": "steer", "itemId": "other-row", "snapshot": claimed_snapshot }))["result"]["action"], "blocked"); + let marker = actor.control.0.lock().unwrap().inputs["session"] + .active_steer_id + .clone(); + let reserved = actor.send(json!({ "method": "beginInput", "scopeId": "scope", "requestId": "reserved-promotion", + "intent": "steer", "source": "steer", "itemId": "promotion-row", "snapshot": claimed_snapshot })); + assert_eq!(reserved["result"]["action"], "steer"); + assert_eq!( + reserved["scope"]["input"]["activeClaimId"], + "promotion-preparation" + ); + actor.input_effect( + "scope", + "promotion-preparation", + "release-mutation", + "release-for-mutation", + json!({ "kind": "orphan" }), + ); + let released = actor.input_ack( + "scope", + "promotion-preparation", + "release-mutation", + "applied", + None, + None, + ); + assert_eq!(released["scope"]["input"]["activeClaimId"], Value::Null); + assert_eq!( + actor.begin_input( + "scope", + "competing-drain", + "drain", + "queue", + &promotion_snapshot + )["result"]["action"], + "blocked" + ); + let promoted = actor.send(json!({ "method": "ackInputEffect", "scopeId": "scope", "effectId": "reserved-promotion", + "outcome": "applied", "inputId": "promotion-row" })); + assert_eq!(promoted["scope"]["input"]["activeSteerId"], json!(marker)); + let promoted_drain = actor.begin_input( + "scope", + "promoted-drain", + "drain", + "queue", + &Actor::input_snapshot(&["other-row"], Some("promotion-row")), + ); + assert_eq!(promoted_drain["result"]["inputId"], "promotion-row"); + assert_eq!(promoted_drain["result"]["source"], "steer"); + actor.input_ack( + "scope", + "promoted-drain", + "promoted-drain", + "applied", + Some("promotion-row"), + None, + ); + actor.input_effect( + "scope", + "promoted-drain", + "promoted-consume", + "consume", + json!({ "kind": "operation", "id": "resumed-operation" }), + ); + actor.input_ack( + "scope", + "promoted-drain", + "promoted-consume", + "applied", + None, + None, + ); + actor.send( + json!({ "method": "endInput", "scopeId": "scope", "drainLeaseId": "promoted-drain" }), + ); + let empty = Actor::input_snapshot(&[], None); + actor.begin_input("scope", "destroy-unknown", "enqueue", "send", &empty); + actor.input_ack( + "scope", + "destroy-unknown", + "destroy-unknown", + "unknown", + None, + None, + ); + let target = + json!({ "kind": "interaction", "messageId": "parked-message", "toolCallId": "tool" }); + actor.send(json!({ "method": "acquireLease", "scopeId": "scope", "leaseId": "park", "target": target })); + assert_eq!(actor.send(json!({ "method": "clearSession", "sessionId": "session", "originScopeId": "scope" }))["outcome"], "stale"); + actor.send(json!({ "method": "retire", "scopeId": "scope" })); + assert_eq!(actor.send(json!({ "method": "clearSession", "sessionId": "session", "originScopeId": "scope" }))["result"], true); + actor.open("replacement"); + assert_eq!( + actor.begin_input( + "replacement", + "replacement-input", + "enqueue", + "send", + &empty + )["result"]["action"], + "claim" + ); + assert_eq!( + actor.input_ack( + "scope", + "destroy-unknown", + "destroy-unknown", + "applied", + Some("late"), + None + )["outcome"], + "stale" + ); + let late_end = actor.send( + json!({ "method": "endInput", "scopeId": "scope", "drainLeaseId": "destroy-unknown" }), + ); + assert_eq!(late_end["result"]["released"], false); + assert_eq!( + late_end["scope"]["input"]["activeClaimId"], + "replacement-input" + ); + assert_eq!(actor.send(json!({ "method": "parkInteraction", "scopeId": "scope", "leaseId": "park", "messageId": "parked-message", "toolCallId": "tool" }))["result"], false); + assert_eq!(actor.send(json!({ "method": "clearSession", "sessionId": "session", "originScopeId": "scope" }))["outcome"], "stale"); + actor.input_ack( + "replacement", + "replacement-input", + "replacement-input", + "rejected", + None, + None, + ); + actor.send(json!({ "method": "endInput", "scopeId": "replacement", "drainLeaseId": "replacement-input" })); + + actor.begin_input("replacement", "pre-fact", "enqueue", "send", &empty); + actor.input_ack( + "replacement", + "pre-fact", + "pre-fact", + "applied", + Some("pre-fact-input"), + None, + ); + actor.operation("replacement", "bound"); + let bound = json!({ "kind": "operation", "id": "bound" }); + actor.send(json!({ "method": "prepareRun", "scopeId": "replacement", "operationId": "bound", "runId": "phase-run", "messageId": "phase-message" })); + assert_eq!( + actor.input_effect( + "replacement", + "pre-fact", + "during-phase", + "user-fact", + bound.clone() + )["outcome"], + "stale" + ); + actor.send(json!({ "method": "commit", "scopeId": "replacement", "transitionId": "phase-run", "aborted": false })); + actor.send(json!({ "method": "clearRun", "scopeId": "replacement", "runId": "phase-run" })); + actor.operation("replacement", "superseded"); + assert_eq!( + actor.input_effect( + "replacement", + "pre-fact", + "late-user-fact", + "user-fact", + bound.clone() + )["outcome"], + "stale" + ); + assert_eq!( + actor.input_effect( + "replacement", + "pre-fact", + "late-rollback", + "rollback", + bound.clone() + )["outcome"], + "stale" + ); + assert_eq!( + actor.input_effect( + "replacement", + "pre-fact", + "origin-consume", + "consume", + bound.clone() + )["outcome"], + "accepted" + ); + actor.input_ack( + "replacement", + "pre-fact", + "origin-consume", + "rejected", + None, + None, + ); + assert_eq!( + actor.input_effect( + "replacement", + "pre-fact", + "origin-release", + "release-before-user-fact", + bound + )["outcome"], + "accepted" + ); + actor.input_ack( + "replacement", + "pre-fact", + "origin-release", + "applied", + None, + None, + ); + actor.send( + json!({ "method": "endInput", "scopeId": "replacement", "drainLeaseId": "pre-fact" }), + ); + let foreign = Actor::input_snapshot(&["foreign-row"], None); + actor.send(json!({ "method": "beginInput", "scopeId": "replacement", "requestId": "foreign-preparation", + "intent": "prepare", "source": "queue", "itemId": "foreign-row", "snapshot": foreign })); + actor.input_ack( + "replacement", + "foreign-preparation", + "foreign-preparation", + "applied", + Some("foreign-row"), + None, + ); + actor.send(json!({ "method": "retire", "scopeId": "replacement" })); + actor.open("foreign-successor"); + let mut foreign_claimed = Actor::input_snapshot(&[], None); + foreign_claimed["hasClaimed"] = json!(true); + assert_eq!(actor.send(json!({ "method": "beginInput", "scopeId": "foreign-successor", "requestId": "foreign-promotion", + "intent": "steer", "source": "steer", "itemId": "foreign-row", "snapshot": foreign_claimed }))["result"]["action"], "blocked"); + actor.input_effect( + "replacement", + "foreign-preparation", + "foreign-release", + "release-before-user-fact", + json!({ "kind": "orphan" }), + ); + actor.input_ack( + "replacement", + "foreign-preparation", + "foreign-release", + "applied", + None, + None, + ); + } + + #[test] + fn replacement_requires_fencing_and_preserves_run_operation_and_message_identity() { + let mut actor = Actor::default(); + actor.open("scope"); + actor.operation("scope", "first"); + actor.run("scope", "first", "run-1"); + let reused = actor.send( + json!({ "method": "prepareOperation", "scopeId": "scope", "operationId": "unused" }), + ); + assert_eq!(reused["result"]["operationId"], "first"); + assert_eq!(reused["result"]["reused"], true); + let aborted = actor.send(json!({ "method": "abort", "scopeId": "scope" })); + assert_eq!(aborted["scope"]["run"]["id"], "run-1"); + let prepared = actor.send( + json!({ "method": "prepareOperation", "scopeId": "scope", "operationId": "next" }), + ); + assert_eq!(prepared["scope"]["operation"]["id"], "first"); + assert_eq!( + prepared["fences"], + json!([{ "kind": "operation", "id": "first" }, { "kind": "run", "id": "run-1" }]) + ); + let stale = actor.send(json!({ "method": "status", "scopeId": "scope", "owner": { "kind": "run", "id": "run-1" }, "status": "idle" })); + assert_eq!(stale["outcome"], "stale"); + let committed = actor.send(json!({ "method": "commit", "scopeId": "scope", "transitionId": "next", "aborted": false })); + assert_eq!(committed["scope"]["operation"]["id"], "next"); + assert_eq!(committed["scope"]["run"], Value::Null); + let late = actor.send( + json!({ "method": "clearOperation", "scopeId": "scope", "operationId": "first" }), + ); + assert_eq!(late["result"]["cleared"], false); + assert_eq!(late["result"]["settles"], false); + assert_eq!(late["scope"]["operation"]["id"], "next"); + actor.run("scope", "next", "run-2"); + actor.sequence += 1; + assert!(actor.control.dispatch(actor.sequence, json!({ "method": "prepareRun", "scopeId": "scope", "operationId": "next", "runId": "run-2", "messageId": "wrong-message" })).is_err()); + } + + #[test] + fn retired_incarnation_can_clean_its_run_without_mutating_a_replacement() { + let mut actor = Actor::default(); + actor.open("old"); + actor.operation("old", "operation-old"); + actor.run("old", "operation-old", "run-old"); + assert_eq!(actor.open("new")["outcome"], "stale"); + let retired = actor.send(json!({ "method": "retire", "scopeId": "old" })); + assert_eq!(retired["scope"]["current"], false); + assert_eq!(actor.open("new")["scope"]["current"], true); + actor.operation("new", "operation-new"); + let rejected = actor.send(json!({ "method": "status", "scopeId": "old", "owner": { "kind": "run", "id": "run-old" }, "status": "error" })); + assert_eq!(rejected["outcome"], "stale"); + let cleaned = + actor.send(json!({ "method": "clearRun", "scopeId": "old", "runId": "run-old" })); + assert_eq!(cleaned["result"], true); + let ready = actor.send(json!({ "method": "clearReady", "scopeId": "new" })); + assert_eq!(ready["scope"]["operation"]["id"], "operation-new"); + assert_eq!(ready["scope"]["status"], "idle"); + assert!(!actor.control.0.lock().unwrap().scopes.contains_key("old")); + } + + #[test] + fn exclusion_and_parking_keep_origin_identity_across_scope_retirement_and_destroy() { + let mut actor = Actor::default(); + actor.open("old"); + let target = json!({ "kind": "interaction", "messageId": "message", "toolCallId": "tool" }); + let acquire = |scope: &str, id: &str| json!({ "method": "acquireLease", "scopeId": scope, "leaseId": id, "target": target }); + actor.send(json!({ "method": "queueAcceptance", "scopeId": "old", "leaseId": "old-preparation", "lane": "send" })); + assert_eq!(actor.send(acquire("old", "first"))["result"], "granted"); + assert_eq!(actor.send(acquire("old", "second"))["result"], "busy"); + actor.send(json!({ "method": "abort", "scopeId": "old" })); + assert_eq!(actor.send(acquire("old", "second"))["result"], "busy"); + actor.send(json!({ "method": "retire", "scopeId": "old" })); + actor.open("new"); + let park = json!({ "method": "parkInteraction", "scopeId": "old", "leaseId": "first", "messageId": "message", "toolCallId": "tool" }); + assert_eq!(actor.send(park.clone())["result"], true); + assert_eq!( + actor.send(acquire("new", "replacement"))["result"], + "parked" + ); + assert_eq!( + actor.send( + json!({ "method": "clearSession", "sessionId": "session", "originScopeId": "old" }) + )["outcome"], + "stale" + ); + actor.send(json!({ "method": "retire", "scopeId": "new" })); + actor.send(json!({ "method": "inputRecovery", "sessions": [] })); + assert_eq!( + actor.send( + json!({ "method": "clearSession", "sessionId": "session", "originScopeId": null }) + )["outcome"], + "stale" + ); + assert_eq!( + actor.send( + json!({ "method": "clearSession", "sessionId": "session", "originScopeId": "old" }) + )["outcome"], + "stale" + ); + assert_eq!( + actor.send( + json!({ "method": "clearSession", "sessionId": "session", "originScopeId": "new" }) + )["result"], + true + ); + actor.open("replacement"); + assert_eq!(actor.send(json!({ "method": "queueAcceptance", "scopeId": "replacement", "leaseId": "new-preparation", "lane": "send" }))["result"], "granted"); + assert_eq!(actor.send(json!({ "method": "releaseAcceptance", "scopeId": "old", "leaseId": "old-preparation", "lane": "send" }))["result"], false); + assert_eq!(actor.send(json!({ "method": "queueAcceptance", "scopeId": "replacement", "leaseId": "waiting", "lane": "send" }))["result"], "queued"); + assert_eq!(actor.send(park)["result"], false); + assert_eq!( + actor.send(acquire("replacement", "replacement"))["result"], + "granted" + ); + assert_eq!(actor.send(json!({ "method": "releaseLease", "scopeId": "replacement", "leaseId": "first", "target": target }))["result"], false); + assert_eq!( + actor.send(acquire("replacement", "third"))["result"], + "busy" + ); + assert_eq!(actor.send(json!({ "method": "releaseLease", "scopeId": "old", "leaseId": "first", "target": target }))["result"], true); + assert_eq!( + actor.send(acquire("replacement", "third"))["result"], + "busy" + ); + } + + #[test] + fn acceptance_preserves_fifo_cancellation_and_active_preparation_across_replacement() { + let mut actor = Actor::default(); + actor.open("old"); + let queue = |scope: &str, id: &str, lane: &str| json!({ "method": "queueAcceptance", "scopeId": scope, "leaseId": id, "lane": lane }); + let release = |scope: &str, id: &str, lane: &str| json!({ "method": "releaseAcceptance", "scopeId": scope, "leaseId": id, "lane": lane }); + assert_eq!(actor.send(queue("old", "a", "send"))["result"], "granted"); + assert_eq!( + actor.send(queue("old", "steer", "steer"))["result"], + "granted" + ); + assert_eq!(actor.send(queue("old", "b", "send"))["result"], "queued"); + assert_eq!(actor.send(queue("old", "c", "send"))["result"], "queued"); + let cancelled = actor.send(release("old", "b", "send")); + assert_eq!(cancelled["result"], true); + assert_eq!(cancelled["grants"], json!([])); + actor.send(json!({ "method": "retire", "scopeId": "old" })); + actor.open("new"); + assert_eq!(actor.send(queue("new", "next", "send"))["result"], "queued"); + assert_eq!(actor.send(release("new", "a", "send"))["result"], false); + let granted = actor.send(release("old", "a", "send")); + assert_eq!( + granted["grants"], + json!([{ "scopeId": "new", "leaseId": "next" }]) + ); + assert_eq!(actor.send(release("old", "next", "send"))["result"], false); + assert_eq!(actor.send(release("new", "next", "send"))["result"], true); + assert_eq!(actor.send(release("old", "steer", "steer"))["result"], true); + } + + #[test] + fn deferred_children_require_the_origin_owner_and_are_revoked_with_the_parent() { + let mut actor = Actor::default(); + actor.open("scope"); + actor.operation("scope", "operation"); + let parent = json!({ "kind": "interaction", "messageId": "message", "toolCallId": "tool" }); + let child = json!({ "kind": "deferred", "messageId": "message", "toolCallId": "tool", "parentLeaseId": "parent", "owner": { "kind": "operation", "id": "operation" } }); + let acquire = |id: &str, target: &Value| json!({ "method": "acquireLease", "scopeId": "scope", "leaseId": id, "target": target }); + let first = |tool: &str| { + json!({ "method": "updateInteractions", "scopeId": "scope", "owner": { "kind": "operation", "id": "operation" }, + "update": { "interactions": [{ "messageId": "message", "toolCallId": tool, "origin": "pre-check-permission", "order": 0 }] } }) + }; + actor.send(first("tool")); + assert_eq!(actor.send(acquire("child", &child))["outcome"], "stale"); + actor.send(acquire("parent", &parent)); + actor.send(first("other")); + assert_eq!(actor.send(acquire("child", &child))["outcome"], "stale"); + actor.send(first("tool")); + assert_eq!(actor.send(acquire("child", &child))["result"], "granted"); + assert_eq!(actor.send(acquire("duplicate", &child))["result"], "busy"); + let release = actor.send(json!({ "method": "releaseLease", "scopeId": "scope", "leaseId": "parent", "target": parent })); + assert_eq!( + release["fences"], + json!([{ "kind": "operation", "id": "child" }]) + ); + assert_eq!(release["scope"]["deferred"], json!([])); + actor.send(acquire("parent", &parent)); + actor.send(acquire("replacement", &child)); + let old_clear = actor.send(json!({ "method": "releaseLease", "scopeId": "scope", "leaseId": "child", "target": child })); + assert_eq!(old_clear["result"], false); + assert_eq!( + old_clear["scope"]["deferred"], + json!([{ "toolCallId": "tool", "id": "replacement" }]) + ); + let stopped = actor.send(json!({ "method": "abortDeferred", "scopeId": "scope" })); + assert_eq!( + stopped["fences"], + json!([{ "kind": "operation", "id": "replacement" }]) + ); + } + + #[test] + fn permission_resolution_is_run_bound_once_only_and_preserves_conflicting_slots() { + let mut actor = Actor::default(); + actor.open("scope"); + actor.operation("scope", "operation"); + actor.run("scope", "operation", "run"); + let register = |id: &str, request: &str, tool: &str| json!({ "method": "registerPermission", "scopeId": "scope", "permissionId": id, "requestId": request, "messageId": "message-run", "toolCallId": tool, "runId": "run" }); + let take = |id: Option<&str>, request: &str, tool: &str| json!({ "method": "takePermission", "scopeId": "scope", "permissionId": id, "requestId": request, "messageId": "message-run", "toolCallId": tool, "runId": "run", "leaseId": "interaction", "granted": true, "resolutionId": "direct-resolution" }); + let first = |tool: &str| { + json!({ "method": "updateInteractions", "scopeId": "scope", "owner": { "kind": "run", "id": "run" }, + "update": { "interactions": [{ "messageId": "message-run", "toolCallId": tool, "origin": "acp-permission", "order": 0 }] } }) + }; + actor.send(json!({ "method": "acquireLease", "scopeId": "scope", "leaseId": "interaction", "target": { "kind": "interaction", "messageId": "message-run", "toolCallId": "tool" } })); + assert_eq!( + actor.send(register("callback", "request", "tool"))["result"], + true + ); + actor.send(first("other")); + assert_eq!( + actor.send(take(Some("callback"), "request", "tool"))["outcome"], + "stale" + ); + actor.send(first("tool")); + assert_eq!( + actor.send(take(Some("wrong"), "request", "tool"))["result"], + "conflict" + ); + let claimed = actor.send(take(Some("callback"), "request", "tool")); + assert_eq!(claimed["result"], "callback"); + assert_eq!(claimed["scope"]["permissions"], json!([])); + assert_eq!(actor.send(take(None, "request", "tool"))["result"], "spent"); + assert_eq!( + actor.send(take(None, "missing", "tool"))["result"], + "direct" + ); + assert_eq!(actor.send(take(None, "missing", "tool"))["result"], "spent"); + actor.send(register("replacement", "request", "other-tool")); + let conflict = actor.send(take(Some("callback"), "request", "tool")); + assert_eq!(conflict["result"], "conflict"); + assert_eq!( + conflict["scope"]["permissions"], + json!([{ "requestId": "request", "id": "replacement" }]) + ); + assert_eq!( + actor.send(register("empty-tool", "empty-request", ""))["result"], + true + ); + let aborted = actor.send(json!({ "method": "abort", "scopeId": "scope" })); + let fences = aborted["fences"].as_array().unwrap(); + assert!(fences.contains(&json!({ "kind": "permission", "id": "replacement" }))); + assert!(fences.contains(&json!({ "kind": "permission", "id": "empty-tool" }))); + assert_eq!(aborted["scope"]["permissions"], json!([])); + assert_eq!( + actor.send(register("late", "late", "tool"))["result"], + false + ); + assert_eq!(actor.send(take(None, "late", "tool"))["outcome"], "stale"); + actor.send(json!({ "method": "updateInteractions", "scopeId": "scope", "owner": { "kind": "run", "id": "run" }, + "update": { "interactions": [] } })); + let orphan_denial = actor.send( + json!({ "method": "takePermission", "scopeId": "scope", "permissionId": null, + "requestId": "orphan", "messageId": "message-run", "toolCallId": "tool", "runId": null, + "leaseId": "interaction", "granted": false, "resolutionId": "denial" }), + ); + assert_eq!(orphan_denial["result"], "direct"); + } + + #[test] + fn reused_controller_does_not_authorize_an_old_runs_status_or_local_cleanup() { + let mut actor = Actor::default(); + actor.open("scope"); + actor.operation("scope", "shared"); + actor.run("scope", "shared", "first"); + actor.run("scope", "shared", "second"); + let status = |kind: &str, id: &str| json!({ "method": "status", "scopeId": "scope", "owner": { "kind": kind, "id": id }, "status": "error" }); + let clear = |run: Option<&str>| json!({ "method": "clearOperation", "scopeId": "scope", "operationId": "shared", "runId": run }); + assert_eq!( + actor.send(status("operation", "shared"))["outcome"], + "stale" + ); + let late = actor.send(clear(Some("first"))); + assert_eq!( + late["result"], + json!({ "cleared": false, "settles": false }) + ); + assert_eq!(late["scope"]["run"]["id"], "second"); + actor.send(json!({ "method": "clearRun", "scopeId": "scope", "runId": "second" })); + assert_eq!( + actor.send(status("operation", "shared"))["outcome"], + "stale" + ); + assert_eq!(actor.send(status("run", "first"))["outcome"], "stale"); + assert_eq!(actor.send(clear(Some("first")))["result"]["settles"], false); + assert_eq!(actor.send(clear(Some("second")))["result"]["settles"], true); + assert_eq!(actor.send(status("run", "second"))["result"], true); + actor.operation("scope", "next"); + actor + .send(json!({ "method": "clearOperation", "scopeId": "scope", "operationId": "next" })); + assert_eq!(actor.send(status("run", "second"))["outcome"], "stale"); + assert_eq!(actor.send(status("operation", "next"))["result"], true); + } + + #[test] + fn interaction_order_origins_and_batch_projection_belong_to_the_originating_run() { + let mut actor = Actor::default(); + actor.open("scope"); + actor.operation("scope", "shared"); + actor.run("scope", "shared", "first"); + let owner = json!({ "kind": "run", "id": "first" }); + let entry = |tool: &str, origin: &str, order: u64| { + json!({ + "messageId": "message-first", "toolCallId": tool, "origin": origin, "order": order + }) + }; + let update = |interactions: Value, extra: Value| { + let mut update = json!({ "interactions": interactions }); + update + .as_object_mut() + .unwrap() + .extend(extra.as_object().unwrap().clone()); + json!({ "method": "updateInteractions", "scopeId": "scope", "owner": owner, "update": update }) + }; + let second = entry("second", "pre-check-permission", 1); + let first = entry("first", "question", 0); + let acp = entry("acp", "acp-permission", 2); + let projection = actor.send(update( + json!([second, acp, first]), + json!({ "batch": { + "id": "batch", "callOrder": ["first", "second"], "invokedCallIds": [], + "committedResultCallIds": [], "pendingInteractionCallIds": [] + }}), + )); + assert_eq!( + projection["scope"]["interactions"], + json!([first, second, acp]) + ); + assert_eq!( + projection["scope"]["batch"]["pendingInteractionCallIds"], + json!(["first", "second"]) + ); + for tool in ["first", "second"] { + actor.send(json!({ "method": "acquireLease", "scopeId": "scope", "leaseId": format!("lease-{tool}"), + "target": { "kind": "interaction", "messageId": "message-first", "toolCallId": tool } })); + } + let check = |tool: &str, lease: &str| { + json!({ "method": "checkInteraction", "scopeId": "scope", "owner": owner, + "messageId": "message-first", "toolCallId": tool, "leaseId": lease }) + }; + assert_eq!(actor.send(check("first", "lease-first"))["result"], true); + assert_eq!(actor.send(check("second", "lease-second"))["result"], false); + assert_eq!(actor.send(check("first", "wrong"))["result"], false); + let origin = actor.send(json!({ "method": "interactionOrigin", "scopeId": "scope", "owner": owner, + "messageId": "message-first", "toolCallId": "second", "origin": "post-call-permission" })); + assert_eq!(origin["result"], true); + let advance = json!({ "method": "advanceBatch", "scopeId": "scope", "owner": owner, + "invokedCallId": "second", "committedResultCallId": "first" }); + actor.send(advance.clone()); + let advanced = actor.send(advance); + assert_eq!( + advanced["scope"]["batch"]["invokedCallIds"], + json!(["second"]) + ); + assert_eq!( + advanced["scope"]["batch"]["committedResultCallIds"], + json!(["first"]) + ); + let reconciled = actor.send(update( + json!([ + entry("second", "pre-check-permission", 0), + entry("first", "question", 1), + acp + ]), + json!({ "reconcile": true }), + )); + assert_eq!(reconciled["scope"]["interactions"][0], first); + assert_eq!( + reconciled["scope"]["interactions"][1]["origin"], + "post-call-permission" + ); + assert_eq!(reconciled["scope"]["batch"]["id"], "batch"); + + actor.run("scope", "shared", "second"); + assert_eq!(actor.send(check("first", "lease-first"))["result"], false); + let stale = actor.send( + json!({ "method": "status", "scopeId": "scope", "owner": owner, + "status": "error", "interactions": { "interactions": [], "batch": null } }), + ); + assert_eq!(stale["outcome"], "stale"); + assert_eq!(stale["scope"]["status"], "idle"); + assert_eq!(stale["scope"]["batch"]["id"], "batch"); + let replacement = json!({ "kind": "run", "id": "second" }); + let only_acp = actor.send( + json!({ "method": "updateInteractions", "scopeId": "scope", "owner": replacement, + "update": { "interactions": [acp] } }), + ); + assert_eq!(only_acp["scope"]["interactions"], json!([acp])); + assert_eq!(only_acp["scope"]["batch"], Value::Null); + actor.send( + json!({ "method": "updateInteractions", "scopeId": "scope", "owner": replacement, + "update": { "interactions": [first], "batch": { + "id": "next-batch", "callOrder": ["first"], "invokedCallIds": [], + "committedResultCallIds": [], "pendingInteractionCallIds": ["first"] + } } }), + ); + let cleared = actor.send( + json!({ "method": "updateInteractions", "scopeId": "scope", "owner": replacement, + "update": { "interactions": [first], "batch": null } }), + ); + assert_eq!(cleared["scope"]["interactions"], json!([first])); + assert_eq!(cleared["scope"]["batch"], Value::Null); + actor.send(json!({ "method": "clearRun", "scopeId": "scope", "runId": "second" })); + let settled = actor.send( + json!({ "method": "status", "scopeId": "scope", "owner": replacement, + "status": "generating", "interactions": { "interactions": [first] } }), + ); + assert_eq!(settled["result"], true); + let retired = actor.send(json!({ "method": "retire", "scopeId": "scope" })); + assert_eq!(retired["scope"]["interactions"], json!([])); + assert_eq!(retired["scope"]["batch"], Value::Null); + } + + #[test] + fn cancelled_preparation_cannot_be_revived_and_protocol_reordering_fails_closed() { + let mut actor = Actor::default(); + actor.open("scope"); + actor.send( + json!({ "method": "prepareOperation", "scopeId": "scope", "operationId": "operation" }), + ); + actor.send(json!({ "method": "abort", "scopeId": "scope" })); + let late = actor.send(json!({ "method": "commit", "scopeId": "scope", "transitionId": "operation", "aborted": false })); + assert_eq!(late["outcome"], "cancelled"); + assert_eq!(late["scope"]["operation"]["aborted"], true); + actor.run("scope", "operation", "run"); + let ready = actor.send(json!({ "method": "ready", "scopeId": "scope", "runId": "run" })); + assert_eq!(ready["result"], false); + let terminal = actor.send(json!({ "method": "status", "scopeId": "scope", "owner": { "kind": "run", "id": "run" }, "status": "idle" })); + assert_eq!(terminal["result"], true); + assert!(actor + .control + .dispatch( + actor.sequence + 2, + json!({ "method": "clearReady", "scopeId": "scope" }) + ) + .is_err()); + assert!(actor + .control + .dispatch( + actor.sequence + 1, + json!({ "method": "clearReady", "scopeId": "scope" }) + ) + .is_err()); + } +} diff --git a/crates/deepchat-harness/src/core.rs b/crates/deepchat-harness/src/core.rs new file mode 100644 index 0000000000..5a39750d52 --- /dev/null +++ b/crates/deepchat-harness/src/core.rs @@ -0,0 +1,616 @@ +use super::*; +use rutis_bridge::session::caller_row; +use std::collections::HashMap; + +const MAX_NATIVE: usize = 16; + +#[derive(Default)] +struct NativeState { + generation: String, + caller: Option, + next: u64, + active: bool, + admission_used: bool, + admission_pending: usize, + pending: HashMap>>, +} + +pub struct Native { + state: Arc>, + outgoing: Output, + admission: Mutex, Json)>>, + control: control::Control, + pub(super) memory: Arc, +} + +impl Native { + pub fn new(outgoing: Output, memory: Arc) -> Self { + Self { + state: Arc::default(), + outgoing, + admission: Mutex::new(None), + control: control::Control::default(), + memory, + } + } + + pub fn complete(&self, id: u64, generation: &str, value: Option, failure: Option) { + let mut state = self.state.lock().unwrap(); + if generation != state.generation || value.is_some() == failure.is_some() { + return; + } + if let Some(reply) = state.pending.remove(&id) { + let _ = reply.send(match failure { + Some(error) => Err(error), + None => Ok(value.unwrap()), + }); + } + } + + pub fn bind_admission(&self, service: Arc, capacity: u64, max_pending: u64) { + *self.admission.lock().unwrap() = Some(( + service, + json!({ "capacity": capacity, "maxPending": max_pending }), + )); + } + + async fn retire(&self) { + let used = { + let mut state = self.state.lock().unwrap(); + state.active = false; + for (_, reply) in state.pending.drain() { + let _ = reply.send(Err(error("CoreUnavailableError", "Core generation ended"))); + } + std::mem::take(&mut state.admission_used) + }; + if used { + let admission = self + .admission + .lock() + .unwrap() + .as_ref() + .map(|(service, _)| service.clone()); + if let Some(service) = admission { + if let Ok(value) = service.invoke( + "close", + Value::List(vec![json!("Core admission owner retired").into()]), + ) { + let _ = settle(value).await; + } + } + } + self.memory.retire_core().await; + } + + fn memory(&self, args: Value) -> rutis_bridge::session::Reply { + let row = caller_row() + .filter(|row| row.id() == "core") + .ok_or_else(|| BridgeError::Value("Core Memory caller is not authorized".into()))?; + let Value::List(mut fields) = args else { + return Err(BridgeError::Value("Invalid Core Memory arguments".into())); + }; + if fields.len() != 5 { + return Err(BridgeError::Value("Invalid Core Memory arguments".into())); + } + let data = match fields.pop().unwrap() { + Value::Undefined | Value::Data(Json::Null) => None, + Value::Bytes(bytes) if bytes.len() <= MAX_MEMORY_CHUNK => Some(bytes), + _ => return Err(BridgeError::Value("Invalid Core Memory bytes".into())), + }; + let metadata = Value::List(fields).json()?; + let fields = metadata.as_array().unwrap(); + let generation = fields[0].as_str().unwrap_or(""); + let id = fields[1] + .as_u64() + .filter(|id| *id <= 9_007_199_254_740_991) + .ok_or_else(|| BridgeError::Value("Invalid Core Memory sequence".into()))?; + let method = fields[2].as_str().unwrap_or(""); + let input = fields[3].clone(); + let control = method != "call" + || matches!( + input["method"].as_str(), + Some("close" | "abort" | "reset" | "quarantine") + ); + let limit = if control { MAX_FRAME } else { MAX_MEMORY_FRAME }; + if serde_json::to_vec(&metadata) + .map_err(|e| BridgeError::Value(e.to_string()))? + .len() + + data.as_ref().map_or(0, Vec::len) + > limit + { + return Err(BridgeError::Value( + "Core Memory frame limit exceeded".into(), + )); + } + { + let mut state = self.state.lock().unwrap(); + if !state.active + || generation != state.generation + || state.caller.as_ref().is_some_and(|key| key != row.key()) + { + return Err(BridgeError::Value( + "Core Memory generation is unavailable".into(), + )); + } + state.caller = Some(row.key().to_owned()); + } + let link = self.memory.clone(); + if method == "retire" && id == 0 && input.is_null() && data.is_none() { + return Ok(Value::future(async move { + link.retire_core().await; + Ok(Json::Null.into()) + })); + } + let result = match method { + "start" if id == 0 && data.is_none() => link.start(input), + "call" if id > 0 => link.call(id, input, data), + _ => { + return Err(BridgeError::Value( + "Unsupported Core Memory operation".into(), + )) + } + }; + Ok(Value::future(async move { + let outcome = match result { + Ok(receiver) => receiver.await.unwrap_or_else(|_| { + Err(error( + "MemoryVectorRuntimeUnavailableError", + "Memory generation ended before its reply", + )) + }), + Err(error) => Err(error), + }; + let outcome = outcome.and_then(|value| { + let frame = json!({ "type": "memoryReply", "id": id, "value": value }); + if serde_json::to_vec(&frame).map_or(true, |bytes| bytes.len() > limit) { + let failure = error("MemoryVectorProtocolError", "Memory reply limit exceeded"); + link.failed(failure.clone()); + Err(failure) + } else { + Ok(value) + } + }); + if id > 0 { + link.complete(id); + } + outcome + .map(Value::from) + .map_err(|error| BridgeError::Remote { + name: error["name"] + .as_str() + .unwrap_or("MemoryVectorTransportError") + .to_owned(), + message: error["message"] + .as_str() + .unwrap_or("Memory request failed") + .to_owned(), + graph: None, + }) + })) + } + + fn admission(&self, args: Value) -> rutis_bridge::session::Reply { + let row = caller_row() + .filter(|row| row.id() == "core") + .ok_or_else(|| BridgeError::Value("Core admission caller is not authorized".into()))?; + let args = args.json()?; + let args = args + .as_array() + .filter(|args| args.len() == 4) + .ok_or_else(|| BridgeError::Value("Invalid Core admission arguments".into()))?; + if serde_json::to_vec(args) + .map_err(|e| BridgeError::Value(e.to_string()))? + .len() + > MAX_FRAME + { + return Err(BridgeError::Value( + "Core admission request is oversized".into(), + )); + } + let generation = args[0].as_str().unwrap_or(""); + let sequence = args[1] + .as_u64() + .filter(|id| *id <= 9_007_199_254_740_991) + .ok_or_else(|| BridgeError::Value("Invalid Core admission sequence".into()))?; + let method = args[2].as_str().unwrap_or(""); + let input = args[3] + .as_array() + .ok_or_else(|| BridgeError::Value("Invalid Core admission input".into()))?; + let (service, limits) = self + .admission + .lock() + .unwrap() + .clone() + .ok_or_else(|| BridgeError::Value("Admission service is unavailable".into()))?; + let parsed = if method == "limits" { + if sequence != 0 || !input.is_empty() { + return Err(BridgeError::Value( + "Invalid Core admission handshake".into(), + )); + } + None + } else { + if sequence == 0 { + return Err(BridgeError::Value("Invalid Core admission sequence".into())); + } + Some( + serde_json::from_value::(json!(method)) + .map_err(|_| BridgeError::Value("Unsupported Core admission method".into()))?, + ) + }; + let acquire = matches!(parsed, Some(Method::Acquire)); + { + let mut state = self.state.lock().unwrap(); + if !state.active + || generation != state.generation + || state.caller.as_ref().is_some_and(|key| key != row.key()) + { + return Err(BridgeError::Value( + "Core admission generation is unavailable".into(), + )); + } + state.caller = Some(row.key().to_owned()); + state.admission_used = true; + if parsed.is_none() { + return Ok(limits.into()); + } + if state.admission_pending >= if acquire { MAX_CALLS - 16 } else { MAX_CALLS } { + state.active = false; + return Ok(Value::future(async move { + if let Ok(value) = service.invoke( + "close", + Value::List(vec![json!("Core admission transport overflow").into()]), + ) { + let _ = settle(value).await; + } + Err(BridgeError::Value( + "Core admission request limit exceeded; retire the generation".into(), + )) + })); + } + state.admission_pending += 1; + } + let input: Vec = input.iter().cloned().map(Value::from).collect(); + let value = if acquire && input.len() != 3 { + Err(BridgeError::Value( + "Invalid Core acquisition arguments".into(), + )) + } else { + service.invoke( + "dispatchCore", + Value::List(vec![ + json!(generation).into(), + json!(sequence).into(), + json!(method).into(), + Value::List(input), + ]), + ) + }; + let state = self.state.clone(); + Ok(Value::future(async move { + let result = match value { + Ok(value) => settle(value).await, + Err(error) => Err(error), + }; + let mut state = state.lock().unwrap(); + state.admission_pending = state.admission_pending.saturating_sub(1); + result + })) + } +} + +impl HostDispatch for Native { + fn methods(&self) -> Option { + Some( + json!({ "invoke": "async", "admission": "async", "memory": "async", "control": "async" }), + ) + } + + fn invoke(&self, method: &str, args: Value) -> rutis_bridge::session::Reply { + if method == "control" { + let row = caller_row() + .filter(|row| row.id() == "core") + .ok_or_else(|| { + BridgeError::Value("Core control caller is not authorized".into()) + })?; + let args = args.json()?; + let args = args + .as_array() + .filter(|args| args.len() == 3) + .ok_or_else(|| BridgeError::Value("Invalid Core control arguments".into()))?; + { + let mut state = self.state.lock().unwrap(); + if !state.active + || args[0].as_str() != Some(&state.generation) + || state.caller.as_ref().is_some_and(|key| key != row.key()) + { + return Err(BridgeError::Value( + "Core control generation is unavailable".into(), + )); + } + state.caller = Some(row.key().to_owned()); + } + let sequence = args[1] + .as_u64() + .ok_or_else(|| BridgeError::Value("Invalid Core control sequence".into()))?; + return self + .control + .dispatch(sequence, args[2].clone()) + .map(Value::from); + } + if method == "admission" { + return self.admission(args); + } + if method == "memory" { + return self.memory(args); + } + if method != "invoke" { + return Err(BridgeError::Value("Unknown Core Native method".into())); + } + let row = caller_row() + .filter(|row| row.id() == "core") + .ok_or_else(|| BridgeError::Value("Core Native caller is not authorized".into()))?; + let args = args.json()?; + let args = args + .as_array() + .filter(|args| args.len() == 3) + .ok_or_else(|| BridgeError::Value("Invalid Core Native arguments".into()))?; + let generation = args[0].as_str().unwrap_or(""); + let method = args[1].as_str().unwrap_or(""); + if !matches!( + method, + "crypto.available" + | "crypto.encrypt" + | "crypto.decrypt" + | "crypto.backend" + | "profile.logging" + | "profile.progress" + | "profile.unlock" + | "profile.recovery" + | "profile.quit" + | "profile.observation" + | "core.failure" + | "configuration" + | "helpers" + | "browser" + | "desktop" + ) { + return Err(BridgeError::Value("Unknown Core Native capability".into())); + } + let (id, receiver) = { + let mut state = self.state.lock().unwrap(); + if !state.active + || generation != state.generation + || state.pending.len() >= MAX_NATIVE + || state.caller.as_ref().is_some_and(|key| key != row.key()) + { + return Err(BridgeError::Value( + "Core Native generation is unavailable".into(), + )); + } + state.caller = Some(row.key().to_owned()); + state.next += 1; + let id = state.next; + let (sender, receiver) = oneshot::channel(); + state.pending.insert(id, sender); + (id, receiver) + }; + let outgoing = self.outgoing.clone(); + let state = self.state.clone(); + let frame = json!({ "type": "coreNativeCall", "id": id, "generation": generation, "method": method, "args": args[2] }); + Ok(Value::future(async move { + if outgoing.send(frame).await.is_err() { + state.lock().unwrap().pending.remove(&id); + return Err(BridgeError::Value("Core Native channel closed".into())); + } + match receiver.await { + Ok(Ok(value)) => Ok(value.into()), + Ok(Err(value)) => Err(BridgeError::Value(format!( + "Core Native request failed: {}", + value["message"].as_str().unwrap_or("unavailable") + ))), + Err(_) => Err(BridgeError::Value("Core Native channel closed".into())), + } + })) + } +} + +pub struct Bootstrap { + runtime: Mutex>, + incoming: Mutex>>, + outgoing: Output, + layers: Arc, + plugin: url::Url, + native: Arc, +} + +impl Bootstrap { + pub fn new( + runtime: LocalRuntime, + incoming: mpsc::Receiver, + outgoing: Output, + layers: Arc, + plugin: url::Url, + native: Arc, + ) -> Self { + Self { + runtime: Mutex::new(Some(runtime)), + incoming: Mutex::new(Some(incoming)), + outgoing, + layers, + plugin: plugin.join("core.mjs").expect("Core plugin URL"), + native, + } + } +} + +impl Plugin for Bootstrap { + fn name(&self) -> &str { + "deepchat-core-bootstrap" + } + + fn apply<'a>( + &'a self, + ctx: &'a Ctx, + ) -> BoxFuture<'a, std::result::Result> { + Box::pin(async move { + let mut incoming = self.incoming.lock().unwrap().take().ok_or_else(|| { + CordisError::ServiceNotFound("Core generation cannot restart".into()) + })?; + let config = tokio::select! { + _ = ctx.cancelled() => return Ok(Effect::Done), + request = incoming.recv() => match request { + Some(Request::CoreStart { config }) => config, + _ => return Err(CordisError::ServiceNotFound("Core handshake required".into())) + } + }; + let generation = config["generation"] + .as_str() + .ok_or_else(|| CordisError::ServiceNotFound("Invalid Core generation".into()))?; + generation + .parse::() + .map_err(|e| CordisError::ServiceNotFound(e.to_string()))?; + { + let mut native = self.native.state.lock().unwrap(); + native.generation = generation.to_owned(); + native.active = true; + } + let runtime = self.runtime.lock().unwrap().take().ok_or_else(|| { + CordisError::ServiceNotFound("Core generation cannot restart".into()) + })?; + let handle = runtime.handle(); + let processes = runtime.processes(); + ctx.plugin(runtime); + ctx.plugin(Gateway { + incoming: Mutex::new(Some(incoming)), + outgoing: self.outgoing.clone(), + injects: [host_key("deepchatCore")], + native: self.native.clone(), + }); + let memory_plugin = self + .plugin + .join("memory.mjs") + .map_err(|e| CordisError::ServiceNotFound(e.to_string()))?; + let hooks_plugin = self + .plugin + .join("hooks.mjs") + .map_err(|e| CordisError::ServiceNotFound(e.to_string()))?; + let patches: Vec = serde_json::from_value(json!([{ "insert": [ + { "id": "core-memory", "name": format!("core:{memory_plugin}") }, + { "id": "core-hooks", "name": format!("core:{hooks_plugin}") }, + { "id": "core", "name": format!("core:{}", self.plugin), "config": config } + ] }])) + .map_err(|e| CordisError::ServiceNotFound(e.to_string()))?; + if self.layers.set("core", patches).await.is_err() { + self.native.retire().await; + processes.kill(); + processes.ended().await; + let _ = self + .outgoing + .send(json!({ "type": "coreStopped", "message": "Core plugin failed to load" })) + .await; + return Ok(Effect::Done); + } + tokio::select! { _ = ctx.cancelled() => {}, _ = runtime_loss(handle) => {} } + self.native.retire().await; + processes.kill(); + processes.ended().await; + self.native.control.retire(); + let _ = self + .outgoing + .send(json!({ "type": "coreStopped", "message": "Core runtime stopped" })) + .await; + Ok(Effect::Done) + }) + } +} + +struct Gateway { + incoming: Mutex>>, + outgoing: Output, + injects: [TypeKey; 1], + native: Arc, +} + +impl Plugin for Gateway { + fn name(&self) -> &str { + "deepchat-core-gateway" + } + fn injects(&self) -> &[TypeKey] { + &self.injects + } + fn apply<'a>( + &'a self, + ctx: &'a Ctx, + ) -> BoxFuture<'a, std::result::Result> { + Box::pin(async move { + let service = ctx.require_as::(self.injects[0].clone())?; + let identity = invoke( + service + .invoke("identity", Value::List(vec![])) + .map_err(|e| CordisError::PluginFailed(Box::new(e)))?, + ) + .await + .map_err(|e| CordisError::ServiceNotFound(e.to_string()))?; + let _ = self + .outgoing + .send(json!({ "type": "coreReady", "runtime": identity })) + .await; + let mut incoming = self + .incoming + .lock() + .unwrap() + .take() + .ok_or_else(|| CordisError::ServiceNotFound("Core generation ended".into()))?; + #[derive(Clone, Copy)] + enum Lane { + Ordinary, + RouteControl, + Control, + Observation, + NativeEvents, + } + type Call = Pin< + Box)> + Send>, + >; + let mut calls = FuturesUnordered::::new(); + let mut active = [0usize; 5]; + let mut last_id = 0; + loop { + tokio::select! { + biased; + _ = ctx.cancelled() => break, + Some((id, lane, stopping, result)) = calls.next(), if !calls.is_empty() => { + active[lane as usize] -= 1; + if stopping && result.is_ok() { self.native.retire().await; } + let frame = match result { Ok(value) => json!({"type":"coreReply","id":id,"value":value}), Err(error) => json!({"type":"coreReply","id":id,"error":error}) }; + if self.outgoing.send(frame).await.is_err() { break; } + }, + request = incoming.recv() => match request { + Some(Request::CoreCall { id, method, args }) => { + let lane = match method.as_str() { + "status" | "stop" | "beginShutdown" => Lane::Control, + "observeSystemTheme" => Lane::Observation, + "nativeEvents" => Lane::NativeEvents, + "invokeControlRoute" => Lane::RouteControl, + "openProfile" | "configure" | "assembleKernel" | "startKernel" | "invokeRoute" | "mcpAppProtocol" | "hostQuery" => Lane::Ordinary, + _ => return Err(CordisError::ServiceNotFound("Invalid Core operation".into())), + }; + if id == 0 || id > 9_007_199_254_740_991 || id <= last_id || active[lane as usize] >= [3, 1, 2, 1, 1][lane as usize] { + return Err(CordisError::ServiceNotFound("Invalid Core operation or quota".into())); + } + last_id = id; + let stopping = method == "stop"; + let value = service.invoke(&method, Value::List(if matches!(lane, Lane::Control | Lane::Observation) || method == "startKernel" { vec![] } else { vec![args.into()] })); + active[lane as usize] += 1; + calls.push(Box::pin(async move { (id, lane, stopping, match value { Ok(value) => invoke(value).await, Err(error) => Err(bridge_error(error)) }) })); + }, + None => break, + _ => return Err(CordisError::ServiceNotFound("Invalid Core request".into())), + } + } + } + Ok(Effect::Done) + }) + } +} diff --git a/crates/deepchat-harness/src/layers.rs b/crates/deepchat-harness/src/layers.rs new file mode 100644 index 0000000000..8badbcec5b --- /dev/null +++ b/crates/deepchat-harness/src/layers.rs @@ -0,0 +1,36 @@ +use super::*; +use rutis_loader::Loader; + +pub struct Layers { + loader: Loader, + current: tokio::sync::Mutex)>>, +} + +impl Layers { + pub fn new(loader: Loader) -> Self { + Self { + loader, + current: tokio::sync::Mutex::new(Vec::new()), + } + } + + pub async fn set(&self, name: &str, patches: Vec) -> Result<()> { + let mut current = self.current.lock().await; + let mut next = current.clone(); + if let Some(layer) = next.iter_mut().find(|layer| layer.0 == name) { + layer.1 = patches; + } else { + next.push((name.to_owned(), patches)); + } + self.loader + .reconcile( + next.iter() + .map(|(name, patches)| Layer::new(name, patches.clone())) + .collect(), + None, + ) + .await?; + *current = next; + Ok(()) + } +} diff --git a/crates/deepchat-harness/src/main.rs b/crates/deepchat-harness/src/main.rs new file mode 100644 index 0000000000..15697c54e8 --- /dev/null +++ b/crates/deepchat-harness/src/main.rs @@ -0,0 +1,552 @@ +use std::{ + collections::HashSet, + error::Error, + future::Future, + io::Read, + path::PathBuf, + pin::Pin, + sync::{Arc, Mutex}, + time::Duration, +}; + +use futures_util::{stream::FuturesUnordered, StreamExt}; +use rutis::{BoxFuture, CordisError, Ctx, Effect, Event, EventKey, Plugin, TypeKey}; +use rutis_bridge::{ + runtime::{Launcher, LocalRuntime, RuntimeHandle, RuntimeState, Stdio}, + session::{host_key, settle, Error as BridgeError, HostDispatch, Value}, + transport::local::{kill_processes, processes_ended}, +}; +use rutis_loader::{ + Chain, Layer, LoaderOptions, LoaderPlugin, Patch, RuntimeResolver, RuntimeRowsPlugin, + ServiceCatalog, +}; +use serde::{Deserialize, Serialize}; +use serde_json::{json, Value as Json}; +use tokio::{ + sync::{mpsc, oneshot}, + time::{sleep, timeout}, +}; + +mod admission; +mod control; +mod core; +mod layers; +mod memory; +mod process; +mod transport; +use transport::Output; + +type Result = std::result::Result>; +const MAX_FRAME: usize = 64 * 1024; +const MAX_MEMORY_FRAME: usize = 256 * 1024; +const MEMORY_FRAME_FLAG: u32 = 0x8000_0000; +const BINARY_FRAME_FLAG: u32 = 0x4000_0000; +const MAX_MEMORY_INITIAL_CHUNK: usize = 64 * 1024; +const MAX_MEMORY_CHUNK: usize = 240 * 1024; +const MAX_CALLS: usize = 1024; + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +enum Method { + Acquire, + Release, + Snapshot, + Close, + FlushObservations, +} +impl Method { + fn name(&self) -> &str { + match self { + Self::Acquire => "acquire", + Self::Release => "release", + Self::Snapshot => "snapshot", + Self::Close => "close", + Self::FlushObservations => "flushObservations", + } + } +} + +#[derive(Deserialize)] +#[serde(tag = "type", rename_all = "camelCase", deny_unknown_fields)] +enum Request { + Call { + id: u64, + method: Method, + args: Vec, + }, + MemoryStart { + config: Json, + }, + MemoryCall { + id: u64, + command: Json, + #[serde(skip)] + data: Option>, + }, + #[serde(skip)] + CoreMemoryStart { + config: Json, + reply: memory::Reply, + }, + #[serde(skip)] + CoreMemoryCall { + id: u64, + command: Json, + data: Option>, + reply: memory::Reply, + }, + CoreStart { + config: Json, + }, + CoreCall { + id: u64, + method: String, + args: Json, + }, + CoreNativeReply { + id: u64, + generation: String, + value: Option, + error: Option, + }, +} + +fn error(name: &str, message: impl ToString) -> Json { + fn bounded(value: &str, max: usize) -> &str { + let mut end = max.min(value.len()); + while !value.is_char_boundary(end) { + end -= 1; + } + &value[..end] + } + json!({ "name": bounded(name, 256), "message": bounded(&message.to_string(), 8192) }) +} + +fn response(id: u64, result: std::result::Result) -> Json { + match result { + Ok(value) => json!({ "type": "reply", "id": id, "value": value }), + Err(error) => json!({ "type": "reply", "id": id, "error": error }), + } +} + +fn bridge_error(error: BridgeError) -> Json { + match error { + BridgeError::Remote { name, message, .. } => self::error(&name, message), + other => self::error("HarnessTransportError", other), + } +} + +async fn invoke(value: Value) -> std::result::Result { + let value = settle(value).await.map_err(bridge_error)?; + match value { + Value::Undefined => Ok(Json::Null), + value => value.json().map_err(bridge_error), + } +} + +struct AdmissionGateway { + native: Arc, + capacity: u64, + max_pending: u64, + injects: [TypeKey; 1], + incoming: Mutex>>, + outgoing: Output, +} + +impl Plugin for AdmissionGateway { + fn name(&self) -> &str { + "deepchat-admission-gateway" + } + fn injects(&self) -> &[TypeKey] { + &self.injects + } + fn apply<'a>( + &'a self, + ctx: &'a Ctx, + ) -> BoxFuture<'a, std::result::Result> { + Box::pin(async move { + let service = ctx.require_as::(self.injects[0].clone())?; + self.native + .bind_admission(service.clone(), self.capacity, self.max_pending); + self.outgoing.send(json!({ "type": "ready", "protocol": 2, "hostPid": std::process::id(), "owner": "rust" })).await.map_err(|e| CordisError::ServiceNotFound(e.to_string()))?; + let mut incoming = self.incoming.lock().unwrap().take().ok_or_else(|| { + CordisError::ServiceNotFound("Admission generation was replaced".into()) + })?; + type Call = + Pin)> + Send>>; + let mut calls = FuturesUnordered::::new(); + let mut pending = HashSet::::new(); + loop { + tokio::select! { + biased; + _ = ctx.cancelled() => break, + Some((id, result)) = calls.next(), if !calls.is_empty() => { + pending.remove(&id); + if self.outgoing.send(response(id, result)).await.is_err() { break; } + } + request = incoming.recv() => { + match request { + None => break, + Some(Request::Call { id, method, args }) => { + if id == 0 || id > 9_007_199_254_740_991 || pending.contains(&id) || pending.len() >= MAX_CALLS { + return Err(CordisError::ServiceNotFound("Invalid or excessive harness request".into())); + } + let args: Vec = args.into_iter().map(Value::from).collect(); + let value = match service.invoke("dispatch", Value::List(vec![json!(id).into(), json!(method.name()).into(), Value::List(args)])) { + Ok(value) => value, + Err(error) => { + let _ = self.outgoing.send(response(id, Err(bridge_error(error)))).await; + continue; + } + }; + pending.insert(id); + calls.push(Box::pin(async move { (id, invoke(value).await) })); + } + Some(_) => return Err(CordisError::ServiceNotFound("Invalid admission request".into())), + } + } + } + } + Ok(Effect::Done) + }) + } +} + +fn decode_request(mut bytes: Vec, vector: bool, binary: bool) -> Result { + let data = if binary { + if !vector || bytes.len() < 4 { + return Err("Invalid binary memory frame".into()); + } + let metadata = u32::from_be_bytes(bytes[..4].try_into()?) as usize; + if metadata == 0 || metadata > bytes.len() - 4 { + return Err("Invalid memory metadata length".into()); + } + let data = bytes.split_off(4 + metadata); + if data.is_empty() || data.len() > MAX_MEMORY_CHUNK { + return Err("Invalid memory chunk size".into()); + } + Some(data) + } else { + None + }; + let mut request: Request = serde_json::from_slice(&bytes[if binary { 4 } else { 0 }..])?; + if vector != matches!(request, Request::MemoryCall { .. }) { + return Err("Invalid harness frame kind".into()); + } + if let Some(bytes) = data { + let Request::MemoryCall { command, data, .. } = &mut request else { + return Err("Invalid binary memory request".into()); + }; + if !matches!( + command["method"].as_str(), + Some("begin" | "chunk" | "commit") + ) || command.get("data").is_some() + { + return Err("Invalid binary memory command".into()); + } + *data = Some(bytes); + } + Ok(request) +} + +fn read_requests( + sender: mpsc::Sender, + memory: mpsc::Sender, + core: mpsc::Sender, + native: Arc, +) -> Result<()> { + let mut input = std::io::stdin().lock(); + loop { + let mut header = [0_u8; 4]; + let header = match input.read_exact(&mut header) { + Ok(_) => u32::from_be_bytes(header), + Err(e) if e.kind() == std::io::ErrorKind::UnexpectedEof => return Ok(()), + Err(e) => return Err(e.into()), + }; + let vector = header & MEMORY_FRAME_FLAG != 0; + let binary = header & BINARY_FRAME_FLAG != 0; + let length = (header & !(MEMORY_FRAME_FLAG | BINARY_FRAME_FLAG)) as usize; + if (binary && !vector) + || length == 0 + || length > if vector { MAX_MEMORY_FRAME } else { MAX_FRAME } + { + return Err("Invalid harness frame size".into()); + } + let mut bytes = vec![0; length]; + input.read_exact(&mut bytes)?; + let request = decode_request(bytes, vector, binary)?; + match request { + Request::CoreNativeReply { + id, + generation, + value, + error, + } => native.complete(id, &generation, value, error), + Request::CoreStart { .. } | Request::CoreCall { .. } => { + if matches!( + core.try_send(request), + Err(mpsc::error::TrySendError::Full(_)) + ) { + return Err("Core request queue exceeded".into()); + } + } + Request::Call { .. } => { + if sender.blocking_send(request).is_err() { + return Ok(()); + } + } + _ => { + // A dead vector generation cannot stop admission's inherited input channel. + let _ = memory.blocking_send(request); + } + } + } +} + +async fn runtime_loss(runtime: RuntimeHandle) -> String { + loop { + if let RuntimeState::Down(reason) = runtime.state() { + return reason; + } + sleep(Duration::from_millis(50)).await; + } +} + +async fn mount( + root: &Ctx, + args: &[std::ffi::OsString], + incoming: mpsc::Receiver, + memory_incoming: mpsc::Receiver, + core_incoming: mpsc::Receiver, + native: Arc, + outgoing: Output, +) -> Result<()> { + let electron = PathBuf::from(&args[0]).canonicalize()?; + let runtime = PathBuf::from(&args[1]).canonicalize()?; + let plugin = PathBuf::from(&args[2]).canonicalize()?; + let capacity: u64 = args[3].to_str().ok_or("Invalid capacity")?.parse()?; + let max_pending: u64 = args[4].to_str().ok_or("Invalid pending limit")?.parse()?; + if capacity == 0 || capacity > 9_007_199_254_740_991 || max_pending > 9_007_199_254_740_991 { + return Err("Invalid admission limits".into()); + } + let launcher = Launcher::new(electron) + .env("ELECTRON_RUN_AS_NODE", "1") + .env("NODE_OPTIONS", "") + .env("NODE_PATH", "") + .env("RUTIS_SHUTDOWN_TIMEOUT", "5") + .cwd(&runtime) + .stdout(Stdio::Null) + .stderr(Stdio::Inherit); + #[cfg(unix)] + let launcher = launcher.inherit_fd(); + // Bounded vector uploads do not need a throughput-grown idle nursery. + let memory_runtime = LocalRuntime::launcher( + "memory", + launcher + .clone() + .arg("--max-semi-space-size=8") + .arg(runtime.join("src/runner.mjs")), + runtime.join("package.json"), + ); + let core_runtime = LocalRuntime::launcher( + "core", + launcher.arg(runtime.join("src/runner.mjs")), + runtime.join("package.json"), + ); + let catalog = ServiceCatalog::new(); + let memory_rows = + Arc::new(RuntimeResolver::modules(memory_runtime.handle()).with_catalog(&catalog)); + let core_rows = + Arc::new(RuntimeResolver::modules(core_runtime.handle()).with_catalog(&catalog)); + root.plugin(admission::Owner { + admission: Arc::new(admission::Admission::new(capacity, max_pending)), + output: outgoing.clone(), + }); + let loader = LoaderPlugin::new( + Chain::new() + .with_shared(memory_rows.clone()) + .with_shared(core_rows.clone()), + LoaderOptions { + catalog, + ..LoaderOptions::default() + }, + ); + let loader_handle = loader.handle(); + let layers = Arc::new(layers::Layers::new(loader_handle)); + root.provide_as::(host_key("deepchatCoreNative"), native.clone())?; + root.plugin(loader).await?; + root.plugin(RuntimeRowsPlugin::new(memory_rows)); + root.plugin(RuntimeRowsPlugin::new(core_rows)); + root.plugin(AdmissionGateway { + native: native.clone(), + capacity, + max_pending, + injects: [host_key("harnessAdmission")], + incoming: Mutex::new(Some(incoming)), + outgoing: outgoing.clone(), + }); + let plugin_url = url::Url::from_file_path(plugin).map_err(|_| "Invalid plugin path")?; + let memory_link = native.memory.clone(); + root.plugin(core::Bootstrap::new( + core_runtime, + core_incoming, + outgoing.clone(), + layers.clone(), + plugin_url.clone(), + native, + )); + root.plugin(memory::Bootstrap::new( + memory_link, + memory_runtime, + memory_incoming, + outgoing, + layers, + plugin_url, + )); + Ok(()) +} + +fn main() -> Result<()> { + let args: Vec<_> = std::env::args_os().skip(1).collect(); + if args.first().is_some_and(|arg| arg == "--process-identity") { + if args.len() != 2 { + return Err("Usage: deepchat-harness --process-identity ".into()); + } + let pid: i32 = args[1].to_str().ok_or("Invalid process PID")?.parse()?; + if pid <= 0 { + return Err("Invalid process PID".into()); + } + println!("{}", process::inspect(pid)?); + return Ok(()); + } + if args.first().is_some_and(|arg| arg == "--exec-gate") { + process::launch(&args[1..])?; + return Ok(()); + } + if args + .first() + .is_some_and(|arg| arg == "--terminate-tree" || arg == "--terminate-owner") + { + if args.len() != 4 { + return Err( + "Usage: deepchat-harness --terminate-{tree,owner} " + .into(), + ); + } + let pid = args[1].to_str().ok_or("Invalid process PID")?.parse()?; + let identity = args[2].to_str().ok_or("Invalid process identity")?; + let grace_ms = args[3] + .to_str() + .ok_or("Invalid process grace period")? + .parse()?; + println!( + "{}", + serde_json::json!({ "retired": process::terminate_tree(pid, identity, grace_ms, args[0] == "--terminate-tree")? }) + ); + return Ok(()); + } + tokio::runtime::Builder::new_multi_thread() + .enable_all() + .build()? + .block_on(run(args)) +} + +async fn run(args: Vec) -> Result<()> { + if args.len() != 5 { + return Err( + "Usage: deepchat-harness ".into(), + ); + } + let root = Ctx::root()?; + let (requests, incoming) = mpsc::channel(64); + let (memory_requests, memory_incoming) = mpsc::channel(32); + let (core_requests, core_incoming) = mpsc::channel(8); + let (outgoing, responses) = Output::channel(); + let memory_link = Arc::new(memory::Link::new(memory_requests.clone())); + let native = Arc::new(core::Native::new(outgoing.clone(), memory_link.clone())); + let reader_native = native.clone(); + let (reader_end, mut reader) = oneshot::channel(); + // A detached blocking reader cannot hold Tokio shutdown open after runtime loss. + std::thread::spawn(move || { + let _ = reader_end.send(read_requests( + requests, + memory_requests, + core_requests, + reader_native, + )); + }); + let mut writer = tokio::spawn(transport::write_responses(responses)); + let result = async { + timeout( + Duration::from_secs(15), + mount( + &root, + &args, + incoming, + memory_incoming, + core_incoming, + native, + outgoing, + ), + ) + .await??; + tokio::select! { + result = &mut reader => result?, + result = &mut writer => { result??; Err("Harness output closed".into()) }, + result = tokio::signal::ctrl_c() => { result?; Ok(()) }, + } + } + .await; + let shutdown = timeout(Duration::from_secs(10), root.shutdown()).await; + if !matches!(&shutdown, Ok(Ok(()))) { + kill_processes(); + } + // Closing runtime channels does not mean their processes have been reaped yet. + processes_ended().await; + writer.abort(); + result?; + shutdown.map_err(|_| "Harness shutdown timed out")??; + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + + fn binary(command: Json, data: &[u8]) -> Vec { + let metadata = serde_json::to_vec(&json!({ + "type": "memoryCall", "id": 1, "command": command + })) + .unwrap(); + let mut frame = (metadata.len() as u32).to_be_bytes().to_vec(); + frame.extend(metadata); + frame.extend(data); + frame + } + + #[test] + fn binary_upload_preserves_bytes_and_rejects_ambiguous_frames() { + for method in ["begin", "chunk", "commit"] { + let bytes = vec![0, 255, 1, 0]; + let request = + decode_request(binary(json!({ "method": method }), &bytes), true, true).unwrap(); + assert!( + matches!(request, Request::MemoryCall { data: Some(data), .. } if data == bytes) + ); + } + let max = vec![0; MAX_MEMORY_CHUNK]; + assert!(decode_request(binary(json!({ "method": "chunk" }), &max), true, true).is_ok()); + for frame in [ + Vec::new(), + vec![0; 4], + u32::MAX.to_be_bytes().to_vec(), + binary(json!({ "method": "chunk" }), &[]), + binary(json!({ "method": "chunk" }), &vec![0; MAX_MEMORY_CHUNK + 1]), + binary(json!({ "method": "query" }), &[0]), + binary(json!({ "method": "chunk", "data": "AAAA" }), &[0]), + ] { + assert!(decode_request(frame, true, true).is_err()); + } + assert!(decode_request(binary(json!({ "method": "chunk" }), &[0]), false, true).is_err()); + } +} diff --git a/crates/deepchat-harness/src/memory.rs b/crates/deepchat-harness/src/memory.rs new file mode 100644 index 0000000000..91d75e4514 --- /dev/null +++ b/crates/deepchat-harness/src/memory.rs @@ -0,0 +1,609 @@ +use super::*; +mod link; +pub use link::{Link, Reply}; +use std::sync::atomic::{AtomicU64, Ordering}; + +const MAX_MEMORY_CALLS: usize = 32; + +const MAX_UPLOAD_BYTES: usize = 16 * 1024 * 1024; +const MAX_NATIVE_PART: usize = 8 * 1024 * 1024; + +struct Upload { + id: String, + resource: String, + memory_ids: Json, + expected: usize, + bytes: Vec, + commit: Option, +} + +enum Staged { + Reply(Json), + Call(Vec), +} + +fn stage( + upload: &mut Option, + id: u64, + command: Json, + data: Option>, + generation: &str, +) -> std::result::Result { + let invalid = || { + error( + "MemoryVectorUploadError", + "Invalid memory upload or sequence", + ) + }; + let method = command["method"].as_str().unwrap_or(""); + if !matches!(method, "begin" | "chunk" | "commit" | "abort") { + if data.is_some() { + return Err(invalid()); + } + return Ok(Staged::Call(vec![command.into()])); + } + let fields = command.as_object().ok_or_else(invalid)?; + for key in fields.keys() { + if !(matches!( + key.as_str(), + "method" | "generation" | "operation" | "resource" | "upload" + ) || method == "begin" && matches!(key.as_str(), "memoryIds" | "bytes") + || matches!(method, "chunk" | "commit") && key == "offset") + { + return Err(invalid()); + } + } + for key in ["generation", "operation", "resource", "upload"] { + command[key] + .as_str() + .ok_or_else(invalid)? + .parse::() + .map_err(|_| invalid())?; + } + if command["generation"] != generation { + return Err(invalid()); + } + if method == "begin" { + if upload.is_some() { + return Err(error( + "MemoryVectorUploadError", + "Memory vector upload is busy", + )); + } + let expected = command["bytes"] + .as_u64() + .filter(|n| *n > 0 && *n <= MAX_UPLOAD_BYTES as u64 && *n % 4 == 0) + .ok_or_else(invalid)? as usize; + let ids = command["memoryIds"] + .as_array() + .filter(|ids| !ids.is_empty() && ids.len() <= 50) + .ok_or_else(invalid)?; + let mut unique = HashSet::new(); + for value in ids { + let value = value + .as_str() + .filter(|v| !v.is_empty() && v.encode_utf16().count() <= 1024) + .ok_or_else(invalid)?; + if !unique.insert(value) { + return Err(invalid()); + } + } + let initial = data.unwrap_or_default(); + if initial.len() > MAX_MEMORY_INITIAL_CHUNK || initial.len() > expected { + return Err(invalid()); + } + let mut bytes = Vec::with_capacity(expected); + bytes.extend(initial); + *upload = Some(Upload { + id: command["upload"].as_str().unwrap().into(), + resource: command["resource"].as_str().unwrap().into(), + memory_ids: command["memoryIds"].clone(), + expected, + bytes, + commit: None, + }); + return Ok(Staged::Reply(Json::Null)); + } + let matches = upload + .as_ref() + .is_some_and(|u| command["upload"] == u.id && command["resource"] == u.resource); + if method == "abort" { + if matches && upload.as_ref().unwrap().commit.is_none() { + *upload = None; + } + return Ok(Staged::Reply(Json::Null)); + } + if !matches { + return Err(invalid()); + } + let current = upload.as_mut().unwrap(); + if current.commit.is_some() { + return Err(invalid()); + } + if let Some(bytes) = data { + if command["offset"].as_u64() != Some(current.bytes.len() as u64) + || bytes.is_empty() + || bytes.len() > MAX_MEMORY_CHUNK + || bytes.len() > current.expected - current.bytes.len() + { + return Err(invalid()); + } + current.bytes.extend(bytes); + } else if method == "chunk" || command.get("offset").is_some() { + return Err(invalid()); + } + if method == "chunk" { + return Ok(Staged::Reply(json!(current.bytes.len()))); + } + if current.bytes.len() != current.expected { + return Err(error( + "MemoryVectorUploadError", + "Memory vector upload is incomplete", + )); + } + current.commit = Some(id); + let bytes = std::mem::take(&mut current.bytes); + let calls = bytes + .chunks(MAX_NATIVE_PART) + .enumerate() + .map(|(part, bytes)| { + let mut fields: std::collections::BTreeMap = + ["generation", "operation", "resource", "upload"] + .into_iter() + .map(|key| (key.into(), command[key].clone().into())) + .collect(); + fields.insert("method".into(), json!("upsert").into()); + fields.insert("memoryIds".into(), current.memory_ids.clone().into()); + fields.insert("bytes".into(), json!(current.expected).into()); + fields.insert("offset".into(), json!(part * MAX_NATIVE_PART).into()); + fields.insert("data".into(), Value::Bytes(bytes.to_vec())); + Value::Record(fields) + }) + .collect(); + Ok(Staged::Call(calls)) +} + +#[derive(Serialize, Deserialize)] +struct Stopped(String, String); +impl Event for Stopped { + const NAME: &'static str = "deepchat/memory/stopped"; + type Value = (); +} + +pub struct Bootstrap { + link: Arc, + runtime: Mutex>, + incoming: Mutex>>, + outgoing: Output, + layers: Arc, + plugin: url::Url, +} + +impl Bootstrap { + pub fn new( + link: Arc, + runtime: LocalRuntime, + incoming: mpsc::Receiver, + outgoing: Output, + layers: Arc, + mut plugin: url::Url, + ) -> Self { + plugin = plugin.join("memory-vector.mjs").expect("Local plugin URL"); + Self { + link, + runtime: Mutex::new(Some(runtime)), + incoming: Mutex::new(Some(incoming)), + outgoing, + layers, + plugin, + } + } +} + +impl Plugin for Bootstrap { + fn name(&self) -> &str { + "deepchat-memory-bootstrap" + } + + fn apply<'a>( + &'a self, + ctx: &'a Ctx, + ) -> BoxFuture<'a, std::result::Result> { + Box::pin(async move { + let mut incoming = self.incoming.lock().unwrap().take().ok_or_else(|| { + CordisError::ServiceNotFound("Memory generation cannot restart".into()) + })?; + let (config, initial) = tokio::select! { + _ = ctx.cancelled() => return Ok(Effect::Done), + request = incoming.recv() => match request { + Some(Request::MemoryStart { config }) => (config, None), + Some(Request::CoreMemoryStart { config, reply }) => (config, Some(reply)), + _ => return Err(CordisError::ServiceNotFound("Memory handshake required".into())), + } + }; + let runtime = self.runtime.lock().unwrap().take().ok_or_else(|| { + CordisError::ServiceNotFound("Memory generation cannot restart".into()) + })?; + let handle = runtime.handle(); + let processes = runtime.processes(); + if !config["memoryDbDir"] + .as_str() + .is_some_and(|path| std::path::Path::new(path).is_absolute()) + { + let failure = error("MemoryVectorProtocolError", "Invalid memory directory"); + self.link.failed(failure.clone()); + if let Some(reply) = initial { + let _ = reply.send(Err(failure)); + } + return Err(CordisError::ServiceNotFound( + "Invalid memory directory".into(), + )); + } + let core = initial.is_some(); + if !self.link.bind(config.clone(), core, processes.clone()) { + if let Some(reply) = initial { + let _ = reply.send(Err(error( + "MemoryVectorRuntimeUnavailableError", + "Core Memory startup was retired", + ))); + } + return Ok(Effect::Done); + } + ctx.plugin(runtime); + let initial = Arc::new(Mutex::new(initial)); + ctx.plugin(Gateway { + link: self.link.clone(), + initial: initial.clone(), + core, + config: config.clone(), + injects: [host_key("harnessMemoryVector")], + incoming: Mutex::new(Some(incoming)), + outgoing: self.outgoing.clone(), + }); + let memory: Vec = serde_json::from_value(json!([{ "insert": [{ + "id": "memory-vector", "name": format!("memory:{}", self.plugin), "config": config + }] }])) + .map_err(|e| CordisError::ServiceNotFound(e.to_string()))?; + if let Err(cause) = self.layers.set("memory", memory).await { + processes.kill(); + processes.ended().await; + let failure = error( + "MemoryVectorRuntimeUnavailableError", + "Memory vector plugin failed to load", + ); + self.link.failed(failure.clone()); + if let Some(reply) = initial.lock().unwrap().take() { + let _ = reply.send(Err(failure)); + } + if !core { + let _ = self.outgoing.send(json!({ "type": "memoryStopped", "message": "Memory vector plugin failed to load" })).await; + } + eprintln!("Memory vector plugin failed: {cause}"); + return Ok(Effect::Done); + } + tokio::select! { + _ = ctx.cancelled() => {}, + _ = runtime_loss(handle) => { + processes.kill(); + processes.ended().await; + self.link.failed(error("MemoryVectorRuntimeUnavailableError", "Memory vector runtime stopped; restart DeepChat to restore vector recall")); + if core { + let generation = config["generation"].as_str().unwrap_or("").to_owned(); + let _ = ctx.events().emit(ctx, &EventKey::::of(), Arc::new(Stopped(generation, "Memory vector runtime stopped".into()))); + } else { + let _ = self.outgoing.send(json!({ "type": "memoryStopped", "message": "Memory vector runtime stopped; restart DeepChat to restore vector recall" })).await; + } + } + } + Ok(Effect::Done) + }) + } +} + +struct Gateway { + link: Arc, + initial: Arc>>, + core: bool, + config: Json, + injects: [TypeKey; 1], + incoming: Mutex>>, + outgoing: Output, +} + +impl Plugin for Gateway { + fn name(&self) -> &str { + "deepchat-memory-vector-gateway" + } + fn injects(&self) -> &[TypeKey] { + &self.injects + } + fn apply<'a>( + &'a self, + ctx: &'a Ctx, + ) -> BoxFuture<'a, std::result::Result> { + Box::pin(async move { + let service = ctx.require_as::(self.injects[0].clone())?; + let identity = match service.invoke("identity", Value::List(vec![])) { + Ok(value) => invoke(value).await, + Err(cause) => Err(bridge_error(cause)), + }; + let identity = match identity { + Ok(identity) => identity, + Err(failure) => { + self.link.failed(failure.clone()); + if let Some(reply) = self.initial.lock().unwrap().take() { + let _ = reply.send(Err(failure)); + } + return Err(CordisError::ServiceNotFound( + "Memory identity unavailable".into(), + )); + } + }; + self.link.ready(identity.clone()); + if self.core { + if let Some(reply) = self.initial.lock().unwrap().take() { + let _ = reply.send(Ok(identity.clone())); + } + } else { + let _ = self + .outgoing + .send(json!({ "type": "memoryReady", "protocol": 1, "runtime": identity })) + .await; + } + let mut incoming = self.incoming.lock().unwrap().take().ok_or_else(|| { + CordisError::ServiceNotFound("Memory generation cannot restart".into()) + })?; + type Call = Pin< + Box< + dyn Future< + Output = ( + u64, + bool, + Option, + Option, + std::result::Result, + ), + > + Send, + >, + >; + let mut calls = FuturesUnordered::::new(); + let mut pending = HashSet::new(); + let mut upload = None; + let generation = identity["generation"] + .as_str() + .ok_or_else(|| CordisError::ServiceNotFound("Missing memory generation".into()))?; + let mut next_request = 1; + let next_native = Arc::new(AtomicU64::new(1)); + loop { + tokio::select! { + biased; + _ = ctx.cancelled() => break, + Some((id, control, closed, reply, result)) = calls.next(), if !calls.is_empty() => { + pending.remove(&id); + if result.is_ok() && upload.as_ref().is_some_and(|u: &Upload| u.commit.is_none() && closed.as_ref() == Some(&u.resource)) { upload = None; } + if upload.as_ref().is_some_and(|u: &Upload| u.commit == Some(id)) { upload = None; } + if let Some(reply) = reply { let _ = reply.send(result); } + else { + let mut frame = response(id, result); + frame["type"] = json!("memoryReply"); + if self.outgoing.send_frame(frame, control).await.is_err() { break; } + } + } + request = incoming.recv() => { + let (id, command, data, reply) = match request { + Some(Request::MemoryCall { id, command, data }) if !self.core => (id, command, data, None), + Some(Request::CoreMemoryCall { id, command, data, reply }) if self.core => (id, command, data, Some(reply)), + Some(Request::CoreMemoryStart { config, reply }) => { + let _ = reply.send(if self.core && config == self.config { Ok(identity.clone()) } else { Err(error("MemoryVectorOwnershipUnavailableError", "Memory generation already has an owner")) }); + continue; + }, + Some(Request::MemoryStart { .. } | Request::MemoryCall { .. }) if self.core => { + let _ = self.outgoing.send(json!({ "type": "memoryStopped", "message": "Memory generation is owned by Core" })).await; + continue; + }, + Some(Request::CoreMemoryCall { reply, .. }) => { + let _ = reply.send(Err(error("MemoryVectorOwnershipUnavailableError", "Memory generation is owned by Main"))); + continue; + }, + None => break, + _ => return Err(CordisError::ServiceNotFound("Invalid Memory request".into())) + }; + if id != next_request || id > 9_007_199_254_740_991 || pending.len() >= MAX_MEMORY_CALLS { + return Err(CordisError::ServiceNotFound("Invalid or excessive memory request".into())); + } + let control = matches!(command["method"].as_str(), Some("close" | "abort" | "reset" | "quarantine")); + let closed = if command["method"] == "close" { command["resource"].as_str().map(str::to_owned) } else { None }; + let abort = (command["method"] == "commit").then(|| { + let mut abort = command.clone(); + abort["method"] = json!("abort"); + if let Some(fields) = abort.as_object_mut() { fields.remove("offset"); } + abort + }); + next_request += 1; + let command = match stage(&mut upload, id, command, data, generation) { + Ok(Staged::Call(command)) => command, + result => { + let result = match result { Ok(Staged::Reply(value)) => Ok(value), Err(error) => Err(error), _ => unreachable!() }; + if let Some(reply) = reply { let _ = reply.send(result); } + else { + let mut frame = response(id, result); + frame["type"] = json!("memoryReply"); + if self.outgoing.send_frame(frame, control).await.is_err() { break; } + } + continue; + } + }; + let service = service.clone(); + let next_native = next_native.clone(); + pending.insert(id); + calls.push(Box::pin(async move { + let mut result = Ok(Json::Null); + for command in command { + let sequence = next_native.fetch_add(1, Ordering::Relaxed); + result = match service.invoke("dispatch", Value::List(vec![json!(sequence).into(), command])) { + Ok(value) => invoke(value).await, + Err(error) => Err(bridge_error(error)), + }; + if result.is_err() { break; } + } + if let Some(abort) = abort.filter(|_| result.is_err()) { + let sequence = next_native.fetch_add(1, Ordering::Relaxed); + if let Ok(value) = service.invoke("dispatch", Value::List(vec![json!(sequence).into(), abort.into()])) { + let _ = invoke(value).await; + } + } + (id, control, closed, reply, result) + })); + } + } + } + Ok(Effect::Done) + }) + } +} + +#[cfg(test)] +mod tests { + use super::*; + const ID: &str = "00000000-0000-4000-8000-000000000001"; + + fn command(method: &str) -> Json { + json!({ "method": method, "generation": ID, "operation": ID, "resource": ID, "upload": ID }) + } + fn begin(bytes: usize) -> Json { + let mut value = command("begin"); + value["bytes"] = json!(bytes); + value["memoryIds"] = json!(["memory"]); + value + } + #[test] + fn upload_receipts_never_publish_an_incomplete_batch() { + let mut upload = None; + assert!(matches!( + stage(&mut upload, 1, begin(8), Some(vec![0; 4]), ID), + Ok(Staged::Reply(Json::Null)) + )); + assert!(stage(&mut upload, 2, command("commit"), None, ID).is_err()); + let mut chunk = command("chunk"); + chunk["offset"] = json!(0); + assert!(stage(&mut upload, 3, chunk.clone(), Some(vec![1; 4]), ID).is_err()); + chunk["offset"] = json!(4); + assert!( + matches!(stage(&mut upload, 4, chunk.clone(), Some(vec![1; 4]), ID), Ok(Staged::Reply(value)) if value == 8) + ); + assert!(stage(&mut upload, 5, chunk, Some(vec![1; 4]), ID).is_err()); + let Ok(Staged::Call(calls)) = stage(&mut upload, 6, command("commit"), None, ID) else { + panic!("Expected one native upsert") + }; + let Value::Record(fields) = &calls[0] else { + panic!("Expected native fields") + }; + assert_eq!(fields["method"], Value::from(json!("upsert"))); + assert_eq!(fields["data"], Value::Bytes(vec![0, 0, 0, 0, 1, 1, 1, 1])); + assert_eq!(upload.as_ref().unwrap().commit, Some(6)); + assert!(stage(&mut upload, 7, begin(8), None, ID).is_err()); + assert!(stage(&mut upload, 8, command("abort"), None, ID).is_ok()); + assert!(upload.is_some(), "An abort cannot disown a native write"); + } + #[test] + fn first_and_continuation_chunks_share_one_bounded_batch() { + let mut upload = None; + let expected = MAX_MEMORY_INITIAL_CHUNK + MAX_MEMORY_CHUNK; + assert!(stage( + &mut upload, + 1, + begin(expected), + Some(vec![0; MAX_MEMORY_INITIAL_CHUNK + 1]), + ID + ) + .is_err()); + assert!(upload.is_none()); + assert!(stage( + &mut upload, + 2, + begin(expected), + Some(vec![0; MAX_MEMORY_INITIAL_CHUNK]), + ID + ) + .is_ok()); + let mut commit = command("commit"); + commit["offset"] = json!(MAX_MEMORY_INITIAL_CHUNK); + assert!(stage( + &mut upload, + 3, + commit.clone(), + Some(vec![1; MAX_MEMORY_CHUNK + 1]), + ID + ) + .is_err()); + let Ok(Staged::Call(calls)) = + stage(&mut upload, 4, commit, Some(vec![1; MAX_MEMORY_CHUNK]), ID) + else { + panic!("Expected one complete native batch") + }; + assert_eq!(calls.len(), 1); + let Value::Record(fields) = &calls[0] else { + panic!("Expected native fields") + }; + let Value::Bytes(bytes) = &fields["data"] else { + panic!("Expected packed bytes") + }; + assert_eq!(bytes.len(), expected); + assert_eq!(bytes[MAX_MEMORY_INITIAL_CHUNK - 1], 0); + assert_eq!(bytes[MAX_MEMORY_INITIAL_CHUNK], 1); + } + #[test] + fn upload_bounds_and_identity_are_checked_before_staging() { + let mut upload = None; + for bytes in [0, 3, MAX_UPLOAD_BYTES + 4] { + assert!(stage(&mut upload, 1, begin(bytes), None, ID).is_err()); + assert!(upload.is_none()); + } + let mut invalid = begin(8); + invalid["memoryIds"] = json!(["memory", "memory"]); + assert!(stage(&mut upload, 1, invalid, None, ID).is_err()); + assert!(stage(&mut upload, 1, begin(8), None, "stale").is_err()); + assert!(stage(&mut upload, 1, begin(8), Some(vec![0; 9]), ID).is_err()); + assert!(stage(&mut upload, 1, begin(8), None, ID).is_ok()); + let mut wrong = command("abort"); + wrong["upload"] = json!("00000000-0000-4000-8000-000000000002"); + assert!(stage(&mut upload, 2, wrong, None, ID).is_ok()); + assert!(upload.is_some()); + assert!(stage(&mut upload, 3, command("abort"), None, ID).is_ok()); + assert!(upload.is_none()); + let mut final_chunk = command("commit"); + final_chunk["offset"] = json!(4); + assert!(stage(&mut upload, 4, begin(8), Some(vec![0; 4]), ID).is_ok()); + assert!(matches!( + stage(&mut upload, 5, final_chunk, Some(vec![1; 4]), ID), + Ok(Staged::Call(_)) + )); + } + #[test] + fn maximum_upload_uses_bounded_native_parts() { + let mut upload = None; + assert!(stage(&mut upload, 1, begin(MAX_UPLOAD_BYTES), None, ID).is_ok()); + for offset in (0..MAX_UPLOAD_BYTES).step_by(MAX_MEMORY_CHUNK) { + let mut chunk = command("chunk"); + chunk["offset"] = json!(offset); + let length = MAX_MEMORY_CHUNK.min(MAX_UPLOAD_BYTES - offset); + assert!(stage(&mut upload, 2, chunk, Some(vec![0; length]), ID).is_ok()); + } + let Ok(Staged::Call(calls)) = stage(&mut upload, 3, command("commit"), None, ID) else { + panic!("Expected native parts") + }; + assert_eq!(calls.len(), 2); + for (index, call) in calls.iter().enumerate() { + let Value::Record(fields) = call else { + panic!("Expected native fields") + }; + assert_eq!( + fields["offset"], + Value::from(json!(index * MAX_NATIVE_PART)) + ); + assert!( + matches!(&fields["data"], Value::Bytes(bytes) if bytes.len() == MAX_NATIVE_PART) + ); + } + } +} diff --git a/crates/deepchat-harness/src/memory/link.rs b/crates/deepchat-harness/src/memory/link.rs new file mode 100644 index 0000000000..d95c66278e --- /dev/null +++ b/crates/deepchat-harness/src/memory/link.rs @@ -0,0 +1,254 @@ +use super::*; +use rutis_bridge::transport::local::Processes; +use std::collections::{BTreeMap, HashSet}; + +pub type Reply = oneshot::Sender>; + +#[derive(Default)] +struct State { + config: Option, + core: bool, + core_requested: bool, + retired: bool, + settled: bool, + identity: Option, + failure: Option, + processes: Option, + next: u64, + waiting: BTreeMap, + pending: HashSet, +} + +pub struct Link { + input: mpsc::Sender, + state: Mutex, + changed: tokio::sync::Notify, +} + +impl Link { + pub fn new(input: mpsc::Sender) -> Self { + Self { + input, + state: Mutex::new(State::default()), + changed: tokio::sync::Notify::new(), + } + } + + pub fn bind(&self, config: Json, core: bool, processes: Processes) -> bool { + let mut state = self.state.lock().unwrap(); + state.config = Some(config); + state.core = core; + state.processes = Some(processes); + state.next = 1; + if core && state.retired { + state.failure = Some(error( + "MemoryVectorRuntimeUnavailableError", + "Core Memory startup was retired", + )); + state.settled = true; + } + self.changed.notify_waiters(); + !core || !state.retired + } + + pub fn ready(&self, identity: Json) { + let mut state = self.state.lock().unwrap(); + state.identity = Some(identity); + state.settled = true; + self.changed.notify_waiters(); + } + + pub fn failed(&self, failure: Json) { + let mut state = self.state.lock().unwrap(); + state.failure = Some(failure.clone()); + state.settled = true; + self.changed.notify_waiters(); + for (_, request) in std::mem::take(&mut state.waiting) { + if let Request::CoreMemoryCall { reply, .. } = request { + let _ = reply.send(Err(failure.clone())); + } + } + } + + pub fn start( + &self, + config: Json, + ) -> std::result::Result>, Json> { + let mut state = self.state.lock().unwrap(); + if state.retired { + return Err(error( + "MemoryVectorRuntimeUnavailableError", + "Core Memory generation retired", + )); + } + if let Some(failure) = &state.failure { + return Err(failure.clone()); + } + if let Some(current) = &state.config { + if !state.core || current != &config { + return Err(error( + "MemoryVectorOwnershipUnavailableError", + "Memory generation already has an owner", + )); + } + } + let (reply, result) = oneshot::channel(); + self.input + .try_send(Request::CoreMemoryStart { config, reply }) + .map_err(|_| { + error( + "MemoryVectorRuntimeUnavailableError", + "Memory startup channel is unavailable", + ) + })?; + state.core_requested = true; + Ok(result) + } + + pub fn call( + &self, + id: u64, + command: Json, + data: Option>, + ) -> std::result::Result>, Json> { + let mut state = self.state.lock().unwrap(); + if let Some(failure) = &state.failure { + return Err(failure.clone()); + } + if state.retired || !state.core || state.identity.is_none() { + return Err(error( + "MemoryVectorRuntimeUnavailableError", + "Core does not own a ready Memory generation", + )); + } + let control = matches!( + command["method"].as_str(), + Some("close" | "abort" | "reset" | "quarantine") + ); + if id < state.next + || id - state.next >= MAX_MEMORY_CALLS as u64 + || state.pending.contains(&id) + || state.pending.len() + >= if control { + MAX_MEMORY_CALLS + } else { + MAX_MEMORY_CALLS - 8 + } + { + return Err(error( + "MemoryVectorProtocolError", + "Invalid or excessive Core Memory request", + )); + } + if command["generation"] != state.identity.as_ref().unwrap()["generation"] { + return Err(error( + "MemoryVectorProtocolError", + "Memory generation changed", + )); + } + let (reply, result) = oneshot::channel(); + state.pending.insert(id); + state.waiting.insert( + id, + Request::CoreMemoryCall { + id, + command, + data, + reply, + }, + ); + loop { + let next = state.next; + let Some(request) = state.waiting.remove(&next) else { + break; + }; + if let Err(cause) = self.input.try_send(request) { + let failure = error( + "MemoryVectorRuntimeUnavailableError", + "Memory request channel is unavailable", + ); + if let Request::CoreMemoryCall { reply, .. } = cause.into_inner() { + let _ = reply.send(Err(failure.clone())); + } + state.failure = Some(failure.clone()); + for (_, request) in std::mem::take(&mut state.waiting) { + if let Request::CoreMemoryCall { reply, .. } = request { + let _ = reply.send(Err(failure.clone())); + } + } + break; + } + state.next += 1; + } + Ok(result) + } + + pub fn complete(&self, id: u64) { + self.state.lock().unwrap().pending.remove(&id); + } + + pub async fn retire_core(&self) { + loop { + let changed = self.changed.notified(); + tokio::pin!(changed); + changed.as_mut().enable(); + let (processes, waiting) = { + let mut state = self.state.lock().unwrap(); + if state.config.is_some() && !state.core { + return; + } + state.retired = true; + if !state.core_requested && !state.core { + return; + } + (state.processes.clone(), !state.settled) + }; + if waiting { + changed.await; + continue; + } + if let Some(processes) = processes { + processes.kill(); + processes.ended().await; + } + self.failed(error( + "MemoryVectorRuntimeUnavailableError", + "Core Memory generation retired", + )); + return; + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[tokio::test] + async fn retirement_fences_late_start_and_waits_for_accepted_startup() { + let (input, mut incoming) = mpsc::channel(32); + let unused = Link::new(input.clone()); + unused.retire_core().await; + assert!(unused.start(json!({})).is_err()); + assert!(incoming.try_recv().is_err()); + + let link = Arc::new(Link::new(input)); + let _reply = link.start(json!({})).unwrap(); + let retiring = link.clone(); + let retirement = tokio::spawn(async move { retiring.retire_core().await }); + timeout(Duration::from_secs(1), async { + while !link.state.lock().unwrap().retired { + tokio::task::yield_now().await; + } + }) + .await + .unwrap(); + assert!(!retirement.is_finished()); + link.failed(error( + "MemoryVectorRuntimeUnavailableError", + "Startup failed", + )); + retirement.await.unwrap(); + assert!(link.start(json!({})).is_err()); + } +} diff --git a/crates/deepchat-harness/src/process.rs b/crates/deepchat-harness/src/process.rs new file mode 100644 index 0000000000..77e04c1e96 --- /dev/null +++ b/crates/deepchat-harness/src/process.rs @@ -0,0 +1,560 @@ +use std::io; + +#[cfg(target_os = "macos")] +#[repr(C)] +#[derive(Clone, Default)] +struct UniqueIdentity { + executable_uuid: [u8; 16], + unique_id: u64, + parent_unique_id: u64, + pid_version: i32, + original_parent_version: i32, + reserved: [u64; 2], +} + +#[cfg(target_os = "macos")] +pub fn launch(args: &[std::ffi::OsString]) -> io::Result<()> { + use std::{ + fs::File, io::Read, os::fd::FromRawFd, os::unix::process::CommandExt, process::Command, + }; + + let executable = args + .first() + .filter(|value| !value.is_empty()) + .ok_or_else(|| io::Error::other("Missing execution command"))?; + // SAFETY: fcntl validates the inherited control descriptor before File takes ownership. + let flags = unsafe { libc::fcntl(3, libc::F_GETFL) }; + if flags < 0 { + return Err(io::Error::last_os_error()); + } + // SAFETY: descriptor 3 is owned by this process; only its blocking mode changes. + if flags & libc::O_NONBLOCK != 0 + && unsafe { libc::fcntl(3, libc::F_SETFL, flags & !libc::O_NONBLOCK) } < 0 + { + return Err(io::Error::last_os_error()); + } + let environment = { + // SAFETY: descriptor 3 is open and no other Rust object owns it. + let mut control = unsafe { File::from_raw_fd(3) }; + let mut header = [0; 4]; + control.read_exact(&mut header)?; + let length = u32::from_be_bytes(header) as usize; + if length > 2 * 1024 * 1024 { + return Err(io::Error::other( + "Execution environment exceeds launch limit", + )); + } + let mut data = vec![0; length]; + control.read_exact(&mut data)?; + serde_json::from_slice::>(&data)? + }; + // Close the control descriptor before exec; stdin remains exclusively the command's input. + Err(Command::new(executable) + .args(&args[1..]) + .env_clear() + .envs(environment) + .exec()) +} + +#[cfg(not(target_os = "macos"))] +pub fn launch(_args: &[std::ffi::OsString]) -> io::Result<()> { + Err(io::Error::new( + io::ErrorKind::Unsupported, + "Native execution gate is unavailable on this platform", + )) +} + +#[cfg(target_os = "macos")] +fn identity(pid: i32) -> io::Result { + use std::mem::size_of; + // XNU proc_uniqidentifierinfo is a stable 56-byte ABI; pidversion changes on exec. + // https://github.com/apple-oss-distributions/xnu/blob/main/bsd/sys/proc_info_private.h + const _: () = assert!(size_of::() == 56); + let mut identity = UniqueIdentity::default(); + // SAFETY: identity is aligned, writable and exactly the requested ABI size. + let bytes = unsafe { + libc::proc_pidinfo( + pid, + 17, // PROC_PIDUNIQIDENTIFIERINFO + 0, + (&mut identity as *mut UniqueIdentity).cast(), + size_of::() as i32, + ) + }; + if bytes != size_of::() as i32 || identity.unique_id == 0 { + return Err(io::Error::last_os_error()); + } + Ok(identity) +} + +#[cfg(target_os = "macos")] +fn boot_session() -> io::Result { + use std::ffi::CStr; + let mut boot = [0_u8; 64]; + let mut length = boot.len(); + // SAFETY: the key is NUL-terminated and boot is writable for length bytes. + let result = unsafe { + libc::sysctlbyname( + c"kern.bootsessionuuid".as_ptr(), + boot.as_mut_ptr().cast(), + &mut length, + std::ptr::null_mut(), + 0, + ) + }; + if result != 0 || length == 0 || length > boot.len() { + return Err(io::Error::last_os_error()); + } + let boot = CStr::from_bytes_until_nul(&boot[..length]) + .map_err(io::Error::other)? + .to_str() + .map_err(io::Error::other)?; + Ok(boot.to_owned()) +} + +#[cfg(target_os = "macos")] +pub fn inspect(pid: i32) -> io::Result { + let identity = identity(pid)?; + let boot = boot_session()?; + Ok(serde_json::json!({ + "pid": pid, + "nativeIdentity": format!("darwin:{boot}:{}", identity.unique_id) + })) +} + +#[cfg(target_os = "macos")] +pub fn terminate_tree( + pid: i32, + expected: &str, + grace_ms: u64, + owned_group: bool, +) -> io::Result { + use std::{ + collections::HashSet, + time::{Duration, Instant}, + }; + + if pid <= 0 || grace_ms > 2000 { + return Err(io::Error::other("Invalid owned process root")); + } + let boot = boot_session()?; + let (expected_boot, expected_birth) = expected + .strip_prefix("darwin:") + .and_then(|value| value.rsplit_once(':')) + .ok_or_else(|| io::Error::other("Invalid process identity"))?; + if expected_boot.len() != 36 + || !expected_boot.bytes().enumerate().all(|(i, c)| { + if [8, 13, 18, 23].contains(&i) { + c == b'-' + } else { + c.is_ascii_hexdigit() + } + }) + { + return Err(io::Error::other("Invalid process boot identity")); + } + let expected_id: u64 = expected_birth.parse().map_err(io::Error::other)?; + if expected_id == 0 || expected_birth != expected_id.to_string() { + return Err(io::Error::other("Invalid process birth identity")); + } + if !expected_boot.eq_ignore_ascii_case(&boot) { + return Ok(true); + } + if pid == std::process::id() as i32 { + return Err(io::Error::other("Cannot retire the inspector")); + } + let root_alive = || -> io::Result { + match identity(pid) { + Ok(root) => { + if root.unique_id != expected_id { + return Err(io::Error::other("Process birth identity changed")); + } + if owned_group { + // SAFETY: getpgid only observes the positive, validated root PID. + let group = unsafe { libc::getpgid(pid) }; + if group < 0 { + let error = io::Error::last_os_error(); + if error.raw_os_error() == Some(libc::ESRCH) { + return Ok(false); + } + return Err(error); + } + if group != pid { + return Err(io::Error::other("Process does not lead its owned group")); + } + } + match identity(pid) { + Ok(after) if after.unique_id == expected_id => Ok(true), + Ok(_) => Err(io::Error::other("Process birth identity changed")), + Err(error) if error.raw_os_error() == Some(libc::ESRCH) => Ok(false), + Err(error) => Err(error), + } + } + Err(error) if error.raw_os_error() == Some(libc::ESRCH) => Ok(false), + Err(error) => Err(error), + } + }; + let capture = |owned: &mut HashSet| -> io::Result> { + root_alive()?; + let mut pids = [0_i32; 4096]; + // libproc returns zero for both an empty group and errors. + // SAFETY: __error returns this thread's writable errno pointer. + unsafe { *libc::__error() = 0 }; + let count = if owned_group { + // SAFETY: pids is writable for the exact supplied byte count. + unsafe { + libc::proc_listpgrppids( + pid, + pids.as_mut_ptr().cast(), + std::mem::size_of_val(&pids) as i32, + ) + } + } else { + 0 + }; + let error = io::Error::last_os_error(); + if count == 0 && error.raw_os_error() != Some(0) { + return Err(error); + } + if count < 0 || count as usize >= pids.len() { + return Err(io::Error::other("Incomplete process group snapshot")); + } + let mut group = Vec::with_capacity(count as usize); + for &member in &pids[..count as usize] { + match identity(member) { + Ok(observed) => { + // Bind group membership to this birth rather than a reused listed PID. + // SAFETY: getpgid only observes a PID returned by libproc. + let member_group = unsafe { libc::getpgid(member) }; + if member_group < 0 { + let error = io::Error::last_os_error(); + if error.raw_os_error() == Some(libc::ESRCH) { + continue; + } + return Err(error); + } + let after = match identity(member) { + Ok(after) => after, + Err(error) if error.raw_os_error() == Some(libc::ESRCH) => continue, + Err(error) => return Err(error), + }; + if member_group != pid || after.unique_id != observed.unique_id { + continue; + } + group.push((member, observed)); + } + Err(error) if error.raw_os_error() == Some(libc::ESRCH) => {} + Err(error) => return Err(error), + } + } + // SAFETY: reset errno before the bounded UID query, with the exact writable buffer. + unsafe { *libc::__error() = 0 }; + let bytes = unsafe { + libc::proc_listpids( + 4, // PROC_UID_ONLY + libc::geteuid(), + pids.as_mut_ptr().cast(), + std::mem::size_of_val(&pids) as i32, + ) + }; + if bytes <= 0 + || bytes as usize >= std::mem::size_of_val(&pids) + || !(bytes as usize).is_multiple_of(std::mem::size_of::()) + { + return Err(io::Error::other("Incomplete same-user process snapshot")); + } + let mut candidates = Vec::with_capacity(bytes as usize / std::mem::size_of::()); + for &candidate in &pids[..bytes as usize / std::mem::size_of::()] { + match identity(candidate) { + Ok(observed) => { + if observed.unique_id == expected_id && candidate != pid { + return Err(io::Error::other("Root birth belongs to another PID")); + } + candidates.push((candidate, observed)); + } + Err(error) if error.raw_os_error() == Some(libc::ESRCH) => {} + Err(error) => return Err(error), + } + } + let root_present = root_alive()?; + for (member, observed) in &group { + if root_present { + owned.insert(observed.unique_id); + } + if !candidates + .iter() + .any(|(_, other)| other.unique_id == observed.unique_id) + { + candidates.push((*member, observed.clone())); + } + } + if candidates.len() >= pids.len() { + return Err(io::Error::other("Process snapshot exceeds ownership limit")); + } + loop { + let before = owned.len(); + for (_, candidate) in &candidates { + if owned.contains(&candidate.parent_unique_id) { + owned.insert(candidate.unique_id); + } + } + if owned.len() >= pids.len() { + return Err(io::Error::other("Process ancestry exceeds ownership limit")); + } + if before == owned.len() { + break; + } + } + if group + .iter() + .any(|(_, member)| !owned.contains(&member.unique_id)) + { + return Err(io::Error::other("Cannot attest ancestry after root exit")); + } + candidates.retain(|(_, member)| owned.contains(&member.unique_id)); + candidates.sort_by_key(|(member, _)| *member == pid); + Ok(candidates) + }; + let wait = |members: &[(i32, UniqueIdentity)], duration| -> io::Result { + let deadline = Instant::now() + duration; + loop { + let mut members_gone = true; + for (member, captured) in members { + match identity(*member) { + Ok(observed) => members_gone &= observed.unique_id != captured.unique_id, + Err(error) if error.raw_os_error() == Some(libc::ESRCH) => {} + Err(error) => return Err(error), + } + } + let group_gone = if owned_group { + // SAFETY: signal zero observes group liveness without sending a signal. + if unsafe { libc::kill(-pid, 0) } != 0 { + let error = io::Error::last_os_error(); + if error.raw_os_error() != Some(libc::ESRCH) { + return Err(error); + } + true + } else { + false + } + } else { + true + }; + if group_gone && members_gone { + return Ok(true); + } + if Instant::now() >= deadline { + return Ok(false); + } + std::thread::sleep(Duration::from_millis(25)); + } + }; + let mut owned = HashSet::from([expected_id]); + let members = capture(&mut owned)?; + if members.is_empty() && wait(&members, Duration::from_millis(500))? { + return Ok(capture(&mut owned)?.is_empty()); + } + type Signal = unsafe extern "C" fn(*const [u32; 8], i32) -> i32; + // SAFETY: dlsym resolves an optional system function with the libproc.h signature. + let symbol = + unsafe { libc::dlsym(libc::RTLD_DEFAULT, c"proc_signal_with_audittoken".as_ptr()) }; + if symbol.is_null() { + return Err(io::Error::new( + io::ErrorKind::Unsupported, + "Identity-bound process signals are unavailable", + )); + } + // SAFETY: the non-null symbol is the system function declared above. + let signal: Signal = unsafe { std::mem::transmute(symbol) }; + let send = |members: &[(i32, UniqueIdentity)], signum| -> io::Result<()> { + for &(member, ref captured) in members { + let observed = match identity(member) { + Ok(observed) => observed, + Err(error) if error.raw_os_error() == Some(libc::ESRCH) => continue, + Err(error) => return Err(error), + }; + if observed.unique_id != captured.unique_id { + continue; + } + let mut token = [0_u32; 8]; + token[5] = member as u32; + token[7] = observed.pid_version as u32; + // XNU validates PID/version and retains the process reference while signaling. + // SAFETY: token has the audit_token_t layout for this synchronous call. + if unsafe { signal(&token, signum) } != 0 { + let error = io::Error::last_os_error(); + if error.raw_os_error() == Some(libc::ESRCH) { + match identity(member) { + Ok(current) if current.unique_id != captured.unique_id => continue, + Err(gone) if gone.raw_os_error() == Some(libc::ESRCH) => continue, + _ => {} + } + } + return Err(error); + } + } + Ok(()) + }; + send(&members, libc::SIGTERM)?; + let exited = wait(&members, Duration::from_millis(grace_ms))?; + let remaining = capture(&mut owned)?; + if exited && remaining.is_empty() { + return Ok(true); + } + send(&remaining, libc::SIGKILL)?; + if !wait(&remaining, Duration::from_millis(500))? { + return Ok(false); + } + Ok(capture(&mut owned)?.is_empty()) +} + +#[cfg(not(target_os = "macos"))] +pub fn terminate_tree( + _pid: i32, + _expected: &str, + _grace_ms: u64, + _owned_group: bool, +) -> io::Result { + Err(io::Error::new( + io::ErrorKind::Unsupported, + "Identity-bound process signals are unavailable on this platform", + )) +} + +#[cfg(not(target_os = "macos"))] +pub fn inspect(_pid: i32) -> io::Result { + Err(io::Error::new( + io::ErrorKind::Unsupported, + "Native process identity is unavailable on this platform", + )) +} + +#[cfg(all(test, target_os = "macos"))] +mod tests { + use super::*; + use std::{ + io::{BufRead, BufReader, Write}, + process::{Command, Stdio}, + }; + + #[test] + fn native_identity_survives_exec_and_rejects_an_exited_process() { + let mut child = Command::new("/bin/sh") + .args(["-c", "read gate; exec /bin/cat"]) + .stdin(Stdio::piped()) + .stdout(Stdio::piped()) + .spawn() + .unwrap(); + let checked = (|| -> io::Result<_> { + let before = inspect(child.id() as i32)?; + child + .stdin + .as_mut() + .unwrap() + .write_all(b"go\ntransitioned\n")?; + let mut line = String::new(); + BufReader::new(child.stdout.as_mut().unwrap()).read_line(&mut line)?; + Ok((before, inspect(child.id() as i32)?, line)) + })(); + let _ = child.kill(); + child.wait().unwrap(); + let (before, after, line) = checked.unwrap(); + assert_eq!(line, "transitioned\n"); + assert_eq!(before, after); + assert!(inspect(child.id() as i32).is_err()); + } + + #[test] + fn group_recovery_refuses_another_birth_and_escalates_owned_members() { + use std::os::unix::process::CommandExt; + + for (root_exits, escaped, owned_group) in [ + (false, false, true), + (true, false, true), + (false, true, true), + (true, true, true), + (false, false, false), + (true, false, false), + (false, true, false), + (true, true, false), + ] { + let release = std::env::temp_dir().join(format!( + "deepchat-native-root-{}-{root_exits}-{escaped}-{owned_group}", + std::process::id() + )); + let quoted = release.to_string_lossy().replace('\'', "'\\''"); + let ending = if root_exits { + format!("while [ ! -f '{quoted}' ]; do sleep 0.01; done; exit 0") + } else { + "wait".to_owned() + }; + let mut command = Command::new("/bin/sh"); + if owned_group { + command.process_group(0); + } + let mut child = command + .args([ + "-c", + &format!( + "{}trap '' TERM; /bin/cat <&0 & echo $!; {ending}", + if escaped { "set -m; " } else { "" } + ), + ]) + .stdin(Stdio::piped()) + .stdout(Stdio::piped()) + .spawn() + .unwrap(); + let pid = child.id() as i32; + let input = child.stdin.take().unwrap(); + let mut output = BufReader::new(child.stdout.take().unwrap()); + let mut waiter = Some(std::thread::spawn(move || child.wait())); + let checked = (|| -> io::Result<_> { + let mut ready = String::new(); + output.read_line(&mut ready)?; + let member: i32 = ready.trim().parse().map_err(io::Error::other)?; + let member_identity = identity(member)?.unique_id; + // SAFETY: this only observes the fixture's announced live child. + if owned_group && (unsafe { libc::getpgid(member) } != pid) != escaped { + return Err(io::Error::other("Fixture process group mismatch")); + } + let expected = inspect(pid)?["nativeIdentity"] + .as_str() + .ok_or_else(|| io::Error::other("Missing native identity"))? + .to_owned(); + if root_exits { + std::fs::write(&release, b"release")?; + waiter.take().unwrap().join().unwrap()?; + } + let wrong = inspect(std::process::id() as i32)?["nativeIdentity"] + .as_str() + .unwrap() + .to_owned(); + let refused = terminate_tree(pid, &wrong, 20, owned_group).is_err(); + let old_boot = if expected.contains("00000000-0000-0000-0000-000000000000") { + "11111111-1111-1111-1111-111111111111" + } else { + "00000000-0000-0000-0000-000000000000" + }; + let stale_retired = terminate_tree( + pid, + &format!("darwin:{old_boot}:{}", expected.rsplit(':').next().unwrap()), + 20, + owned_group, + )?; + let still_owned = identity(member)?.unique_id == member_identity; + let retired = terminate_tree(pid, &expected, 20, owned_group)?; + Ok((refused && stale_retired, still_owned, retired)) + })(); + // EOF and the owned release file drain failures without signaling a reused PID. + drop(input); + std::fs::write(&release, b"release").unwrap(); + if let Some(waiter) = waiter { + waiter.join().unwrap().unwrap(); + } + std::fs::remove_file(release).unwrap(); + let (refused, still_owned, retired) = checked.unwrap(); + assert!(refused && still_owned && retired); + assert!(inspect(std::process::id() as i32).is_ok()); + } + } +} diff --git a/crates/deepchat-harness/src/transport.rs b/crates/deepchat-harness/src/transport.rs new file mode 100644 index 0000000000..94456d50b8 --- /dev/null +++ b/crates/deepchat-harness/src/transport.rs @@ -0,0 +1,103 @@ +use super::{Json, Result, MAX_FRAME, MAX_MEMORY_FRAME, MEMORY_FRAME_FLAG}; +use std::sync::Arc; +use tokio::{ + io::AsyncWriteExt, + sync::{mpsc, OwnedSemaphorePermit, Semaphore}, +}; + +const BUFFER_BYTES: usize = 1024 * 1024; +const CONTROL_BYTES: usize = MAX_FRAME + 4; + +struct Frame { + bytes: Vec, + vector: bool, + _permit: OwnedSemaphorePermit, +} + +#[derive(Clone)] +pub struct Output { + control: mpsc::Sender, + data: mpsc::Sender, + control_bytes: Arc, + data_bytes: Arc, +} + +pub struct Responses { + control: mpsc::Receiver, + data: mpsc::Receiver, +} + +impl Output { + pub fn channel() -> (Self, Responses) { + let (control, control_rx) = mpsc::channel(64); + let (data, data_rx) = mpsc::channel(32); + ( + Self { + control, + data, + control_bytes: Arc::new(Semaphore::new(CONTROL_BYTES)), + data_bytes: Arc::new(Semaphore::new(BUFFER_BYTES - CONTROL_BYTES)), + }, + Responses { + control: control_rx, + data: data_rx, + }, + ) + } + + pub async fn send(&self, value: Json) -> Result<()> { + self.send_frame(value, true).await + } + + pub async fn send_frame(&self, value: Json, control: bool) -> Result<()> { + let max = if control { MAX_FRAME } else { MAX_MEMORY_FRAME }; + let (sender, quota) = if control { + (&self.control, &self.control_bytes) + } else { + (&self.data, &self.data_bytes) + }; + let mut permit = quota.clone().acquire_many_owned((max + 4) as u32).await?; + let vector = value["type"] == "memoryReply"; + let mut bytes = serde_json::to_vec(&value)?; + if vector && bytes.len() > max { + bytes = serde_json::to_vec(&serde_json::json!({ + "type": "memoryReply", "id": value["id"], + "error": { "name": "MemoryVectorResponseLimitError", "message": "Memory vector response exceeds the frame limit" } + }))?; + } + if bytes.is_empty() || bytes.len() > max { + return Err("Harness response exceeds frame limit".into()); + } + let retained = permit + .split(bytes.len() + 4) + .ok_or("Invalid output byte quota")?; + drop(permit); + sender + .send(Frame { + bytes, + vector, + _permit: retained, + }) + .await + .map_err(|_| "Harness output closed".into()) + } +} + +pub async fn write_responses(mut responses: Responses) -> Result<()> { + let mut output = tokio::io::stdout(); + loop { + let frame = tokio::select! { + biased; + frame = responses.control.recv() => frame, + frame = responses.data.recv() => frame, + }; + let Some(frame) = frame else { + return Ok(()); + }; + output + .write_u32(frame.bytes.len() as u32 | if frame.vector { MEMORY_FRAME_FLAG } else { 0 }) + .await?; + output.write_all(&frame.bytes).await?; + output.flush().await?; + } +} diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index a43eabde62..195286219d 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -1,7 +1,8 @@ # DeepChat 当前架构概览 -本文档描述 `2026-07-16` 的 main 进程实际结构。旧的全局 `Presenter`、 -`LifecycleManager`、全局 `EventBus` 和业务模块查找入口已经删除。 +The default desktop runs its cohesive business Kernel and profile writer in a first-party Rutis +Core SDK worker. Electron Main owns Native presentation and authenticated ingress. The old global +`Presenter`, `LifecycleManager`, global `EventBus` and business service lookup entry are removed. ## 总体结构 @@ -9,14 +10,18 @@ flowchart TD Renderer["Renderer"] --> Preload["Preload bridge"] Preload --> Contracts["shared typed routes / events"] - Contracts --> RouteMaps["各模块 route map"] + Contracts --> Native["Main: Native routes and sender checks"] + Native --> Host["Rust/Rutis host"] + Host --> Core["TS Core Kernel and profile writer"] + Core --> RouteMaps["各模块 route map"] RouteMaps --> Modules["负责该行为的模块"] - App["App composition"] --> Platform["Platform / Settings / Data"] + App["Core composition"] --> Platform["Platform / Settings / Data"] + Core --> App App --> Capabilities["Provider / Tool / MCP / Skill / Plugin / Memory / Knowledge / Workspace"] App --> Agent["Agent: DeepChat / ACP"] App --> Session["Session"] - App --> Entries["Desktop / Remote / Scheduler / Deeplink"] + App --> Entries["Session entry services / Remote / Scheduler"] Entries --> Session Session --> Agent @@ -24,30 +29,40 @@ flowchart TD Capabilities --> Platform ``` -`src/main/app/composition.ts` 是唯一的组合入口。它创建模块、传入明确依赖、注册 route、 -排定启动与停止顺序,但不导出模块列表,也不提供按名称查找模块的方法。 +`src/main/app/composition.ts` connects the default host/Core generation, Native adapters and fixed +Native routes. `src/harness/plugins/core.ts` owns the SDK plugin lifecycle; +`src/harness/core/kernel.ts` composes business modules and the shared profile. Neither root exports +a module registry or allows name-based service lookup. There is no Main writer fallback. 依赖方向是: ```text -App composition - -> Desktop / Remote / Scheduler / Deeplink +Main Native adapters <-> Rust/Rutis <-> Core Kernel + -> Session entry services / Remote / Scheduler -> Session -> Agent runtime -> Provider / Tool / MCP / Skill / Plugin / Memory / Knowledge / Workspace -> Platform / Settings / Data ``` -下层模块不能反向读取 App、Desktop、Remote 或 Scheduler。需要通知 renderer 时,App 在创建模块时 -传入有类型的发送函数。 +Business modules receive typed dependencies rather than reading either composition root. +Renderer publication and platform operations cross acknowledged Native ports; helper and Browser +retirement must finish before releasing profile ownership. ## 生命周期 ### App -`src/main/appMain.ts` 负责 Electron 进程入口、single-instance、deeplink 缓存和退出请求。 -`src/main/app/mainProcess.ts` 负责数据库解锁、连接、迁移和启动失败清理。 -`src/main/app/composition.ts` 负责创建、连接、启动和停止业务模块。 +`src/main/appMain.ts` owns the Electron entry, single-instance/deeplink handling and quit requests. +`src/main/app/mainProcess.ts` owns splash presentation and startup cancellation. +`src/main/app/composition.ts` starts the default Core and supplies Native capabilities. +Core invokes `src/main/app/profileStartup.ts` for exclusive profile ownership, unlock/recovery, +database/config migration and the shared writer, then assembles and starts its Kernel. + +Quit cancels pending/future startup database prompts before awaiting startup completion. Intentional +startup cancellation releases the profile without a startup-failure dialog. Normal retirement +fences admission, drains business work and Native effects, then closes the profile and host. Core +loss retires the desktop generation and exits with failure; reopening requires application restart. `startMainProcess()` 只返回 `MainProcessControl`。它只能聚焦主窗口、处理 deeplink、清理权限、 确认退出、查询主窗口和停止 main 进程,不能读取业务模块。 @@ -78,24 +93,29 @@ request sequence 和临时输出状态。Session 拥有长期数据,Agent runt ## 模块职责 -| 模块 | 位置 | 负责内容 | -| --- | --- | --- | -| App | `src/main/app/` | 进程启动、退出、维护状态、组合依赖 | -| Desktop | `src/main/desktop/` | window、tab、tray、shortcut、floating、browser、renderer binding | -| Session | `src/main/session/` | Session 生命周期、Turn、查询、长期数据和删除规则 | -| Agent | `src/main/agent/` | Agent catalog、backend 选择、DeepChat/ACP instance 和执行 | -| Provider | `src/main/provider/` | Provider/model 配置、实例、请求和认证 | -| Tool | `src/main/tool/` | Tool catalog、执行、权限和本地 Agent tools | -| MCP | `src/main/mcp/` | MCP 配置、server/client 生命周期和 MCP 调用 | -| Skill | `src/main/skill/` | Skill 文件、扫描、同步、选择和贡献 | -| Plugin | `src/main/plugin/` | Plugin package、安装状态和能力登记 | -| Memory | `src/main/memory/` | 长期记忆、检索、写入、索引和后台维护 | -| Knowledge | `src/main/knowledge/` | 内置知识库、切片、索引和检索 | -| Workspace / File | `src/main/workspace/`、`src/main/file/` | Workspace 授权、文件树、搜索、转换和临时文件 | -| Remote | `src/main/remote/` | channel runtime、endpoint binding、远程命令和结果发送 | -| Scheduler | `src/main/scheduler/` | Cron job、run、delivery 和 detached Session | -| Settings | `src/main/settings/`、各模块 `settings.ts` | 底层设置存储和各模块自己的配置解释 | -| Data | `src/main/data/`、各模块 `data/` | SQLite 连接、schema,以及各模块自己的 table 访问 | +| 模块 | 位置 | 负责内容 | +| ---------------- | ------------------------------------------ | ---------------------------------------------------------------- | +| App | `src/main/app/` | 进程启动、退出、维护状态、组合依赖 | +| Desktop | `src/main/desktop/` | window、tab、tray、shortcut、floating、browser、renderer binding | +| Session | `src/main/session/` | Session 生命周期、Turn、查询、长期数据和删除规则 | +| Agent | `src/main/agent/` | Agent catalog、backend 选择、DeepChat/ACP instance 和执行 | +| Provider | `src/main/provider/` | Provider/model 配置、实例、请求和认证 | +| Tool | `src/main/tool/` | Tool catalog、执行、权限和本地 Agent tools | +| MCP | `src/main/mcp/` | MCP 配置、server/client 生命周期和 MCP 调用 | +| Skill | `src/main/skill/` | Skill 文件、扫描、同步、选择和贡献 | +| Plugin | `src/main/plugin/` | Plugin package、安装状态和能力登记 | +| Memory | `src/main/memory/` | 长期记忆、检索、写入、索引和后台维护 | +| Knowledge | `src/main/knowledge/` | 内置知识库、切片、索引和检索 | +| Workspace / File | `src/main/workspace/`、`src/main/file/` | Workspace 授权、文件树、搜索、转换和临时文件 | +| Remote | `src/main/remote/` | channel runtime、endpoint binding、远程命令和结果发送 | +| Scheduler | `src/main/scheduler/` | Cron job、run、delivery 和 detached Session | +| Settings | `src/main/settings/`、各模块 `settings.ts` | 底层设置存储和各模块自己的配置解释 | +| Data | `src/main/data/`、各模块 `data/` | SQLite 连接、schema,以及各模块自己的 table 访问 | + +These paths organize source code, not runtime placement. Portable business modules remain under +`src/main/` while their default owners are composed in Core. Electron-only effects stay in Main; +individual factory files do not each imply a plugin or process. The [Rutis plan](./architecture/rutis-multiprocess-backend/plan.md) +tracks full Memory extraction, conditional Rust work and the unimplemented headless/device horizons. Desktop 内仍有 `WindowPresenter`、`TabPresenter` 等历史类名。它们只是 Desktop 模块内部的具体实现, 不是全局入口,也不能被业务模块用来查找其他能力。 diff --git a/docs/FLOWS.md b/docs/FLOWS.md index 159be91169..a11c93cfb7 100644 --- a/docs/FLOWS.md +++ b/docs/FLOWS.md @@ -1,7 +1,8 @@ # DeepChat 当前核心流程 -本文档描述 `2026-07-16` 的实际流程。旧 `Presenter`、通用 lifecycle hook 和全局 `EventBus` -不再属于当前流程。 +These flows use the default Rust/Rutis Core owner. Business state, configuration and the shared +writer live in Core; Native presentation and authenticated renderer ingress remain in Main. +The old `Presenter`, generic lifecycle hooks and global `EventBus` are removed. ## 1. main 进程启动 @@ -9,27 +10,33 @@ sequenceDiagram participant Entry as appMain.ts participant Main as app/mainProcess.ts - participant DB as Database participant App as app/composition.ts - participant Desktop as Desktop + participant Host as Rust/Rutis + participant Core as Core SDK Kernel + participant DB as Profile / Database Entry->>Entry: single-instance / deeplink cache - Entry->>Main: startMainProcess() - Main->>Main: create splash and settings stores - Main->>DB: unlock, open, migrate - Main->>Main: migrate config storage and register protocols + Entry->>Main: startMainProcess(startup signal) + Main->>Main: create splash and register protocols Main->>App: createMainProcessControl(dependencies) - App->>App: create modules and connect narrow dependencies - App->>App: register module route maps - App->>Desktop: create first main window - App->>App: start shortcut, tray, Scheduler and Memory maintenance - App->>App: schedule deferred/background work + App->>Host: connect default Core generation + Host->>Core: load first-party SDK plugin + Core->>DB: acquire exclusive profile ownership + Core->>Main: Native crypto and unlock/recovery presentation + Core->>DB: open and migrate + Core->>Core: compose Kernel and business route maps + Core->>Core: finish startup migrations and activate + Core-->>App: business readiness acknowledged + App->>App: create main window, shortcuts and tray App-->>Main: MainProcessControl Main->>Main: close splash ``` -首个窗口之前必须完成数据库、配置迁移、route 注册和 ACP registry migration。Skill 扫描、MCP、 -Remote、Provider warmup、legacy import 和统计回填在窗口可用后调度。 +Database/config migration, required startup migrations and business route registration precede +business readiness. Core owns deferred/background work and its readiness/drain coordinator. +Quit during startup cancels pending/future unlock or recovery requests, then waits for profile +retirement. Startup cancellation does not display a failure dialog. Other business flows below run +in Core and use typed Main ports when they require Native effects. ## 2. 创建 Session 并发送消息 @@ -221,10 +228,11 @@ shutdown 会先停止接受新 run,再等待或取消已接收 run。 ## 10. Sync 和导入 -本地备份、数据库导入和 S3-compatible cloud sync 由 `src/main/sync/` 发起,并统一包在 App database -maintenance 中。Cloud flow 为:读取 SyncSettings 中的 endpoint/bucket/path/credential,生成或读取加密 -数据库备份,上传/下载对象,校验完成后再替换本地数据。Secret 只保存在 main process 的 secret store, -renderer 只接收脱敏状态。 +Core invokes `src/main/sync/` for local backups, database import and S3-compatible cloud sync under +database maintenance. Cloud sync reads endpoint/bucket/path/credentials, creates or reads the +encrypted backup, transfers and validates it before replacing local data. Core owns durable +credential access; Main supplies Native encryption. Maintenance and shutdown fence credential +generations, and the renderer receives only redacted state. 导入成功后重新打开数据库,各模块通过稳定 database owner 读取新 table;长期运行对象不能继续缓存 旧连接产生的 table。任何 close/import/reopen 失败都会让 App 进入 failed 并停止,不执行半恢复。 diff --git a/docs/architecture/deepchat-agent-harness-boundaries/spec.md b/docs/architecture/deepchat-agent-harness-boundaries/spec.md index 6f9a9b1bae..1ca0d601ab 100644 --- a/docs/architecture/deepchat-agent-harness-boundaries/spec.md +++ b/docs/architecture/deepchat-agent-harness-boundaries/spec.md @@ -756,3 +756,16 @@ execution continues to settle independently and commit results in provider call prompt the producer supplied. 9. Focused suites, the full main-process suite, type checks, formatting, i18n validation, lint, and the agent cleanup guard pass, and executed test count does not drop. + +## Invocation admission runtime + +Process-wide delegated-Agent invocation admission is owned by the Rust Rutis plugin in +`crates/deepchat-harness/src/admission.rs`. Main and Core use `RutisAgentInvocationAdmission` +clients with independent ordered lanes into one queue/permit authority. Caller-owned lease handles +remain behind the existing admission port. There is no dedicated admission Node runtime, TS queue +constructor or fallback. The Rust owner enforces fairness, cancellation, known-ID late-grant +cleanup and bounded diagnostics; host loss retires the generation without an automatic replacement. + +This boundary is distinct from the Harness pending-input pump and its four fences, which retain +their existing owners. The [Rutis runtime RFC](../rutis-multiprocess-backend/spec.md) specifies call +ordering, cancellation/late-grant cleanup, diagnostics and shutdown for this module migration. diff --git a/docs/architecture/main-process-structured-logging/spec.md b/docs/architecture/main-process-structured-logging/spec.md index 1520b982f8..38c8119788 100644 --- a/docs/architecture/main-process-structured-logging/spec.md +++ b/docs/architecture/main-process-structured-logging/spec.md @@ -212,8 +212,9 @@ and diagnostic clock failures cannot change the durable Run lifecycle. ## Admission Diagnostics -`AgentInvocationAdmission` records both low-volume lifecycle events and bounded in-memory -distributions: +The Rutis admission TS plugin's `AgentInvocationAdmission` records both low-volume lifecycle events +and bounded in-memory distributions. The main process consumes these through the typed Rutis client; +it does not own another admission queue: - `agent.admission.queued`; - `agent.admission.granted` with measured `waitMs`, acquisition sequence, active/pending counts; @@ -239,6 +240,12 @@ metrics may continue bounded in-memory accounting but skip observation queue ins `setImmediate` scheduling. Re-enabling an output affects subsequent observations only; disabled-time payloads are never replayed. +The runtime forwards observations with a generation-local sequence; main restores that order before +calling the existing logger. Forwarding is bounded and reports dropped observations in the terminal +summary. Each admission operation carries the current output-enabled snapshot, and main applies its +output gate again at delivery. Teardown flushes the observation stream before stopping the Rust host. +The monotonic intervals measure the queue and permit owner, excluding transport round-trip time. + Recent wait and hold distributions retain at most 256 samples. They are process-local diagnostics, reset on restart, and are not recovery or accounting facts. The admission implementation owns its collector instead of coupling to the Memory diagnostics lifecycle. diff --git a/docs/architecture/memory-service-boundaries/spec.md b/docs/architecture/memory-service-boundaries/spec.md index 7de7031bc8..bbe7044ee1 100644 --- a/docs/architecture/memory-service-boundaries/spec.md +++ b/docs/architecture/memory-service-boundaries/spec.md @@ -12,6 +12,17 @@ decomposition and a separate scheduler are out of scope. ## Design and invariants +- The first-party `deepchat-memory` Rutis plugin owns the facade and its lifecycle in the Core + worker, alongside the sole shared Store and Provider owners. Core injects this capability; + prompt, tools, management and Agent lifecycle use its one facade. Repository callbacks stay + owner-local. The existing lazy vector worker retains its separate native resource lifetime. +- Harness binds its ingestion observer explicitly. The capability coordinates pause/drain, + resume and final retirement; unload fences the observer and facade before awaiting cleanup. + Core cannot release the profile without retirement acknowledgement. Existing execution epochs, + read validation, transactions and cursor settlement remain authoritative. +- Execution tokens include the facade instance identity as well as the Agent epoch. A replacement + plugin rejects tokens issued by the retired instance, including equal numeric epochs. + - Retrieval shares readiness/warmup, vector failure classification and authoritative vector-row validation predicates, not a callback-driven traversal. Decision retrieval retains batching, snapshot reuse, conflict exclusion and pinned ordering. Recall retains cancellation, circuit diff --git a/docs/architecture/memory-system.md b/docs/architecture/memory-system.md index ef30457641..2737c484cf 100644 --- a/docs/architecture/memory-system.md +++ b/docs/architecture/memory-system.md @@ -27,7 +27,11 @@ flowchart LR - `src/main/agent/deepchat/memory/` 只负责每个 Session 的 prompt contribution、terminal ingestion、 epoch、cursor 和 fence。 - Session 保存 Memory cursor/settings,不拥有 Memory row 或 vector store。 -- App 负责 shutdown/database maintenance 时的全局 fence 和停止顺序,不解释 Memory 业务状态。 +- `deepchat-memory` Rutis 插件在 Core worker 内构造唯一 facade,统一负责 Memory 的暂停、排空、 + 恢复和关闭;Harness 显式绑定 ingestion observer。共享 Store 与 Provider 保持同进程,已有惰性 + 向量 worker 继续独立管理原生资源。 +- Core 负责 shutdown/database maintenance 的全局顺序,收到 Memory 退役回执后才能释放 profile; + 不解释 Memory 业务状态。 - Memory runtime 通过 `TapeRawEntryReader` 和 `TapeAnchorWriter` 读取执行事实、记录 `memory/view_assembled` 与 `memory/extract` anchor;Memory routes 只通过 `TapeInspectionReader` 获取 effective source span 和 manifest DTO,不接收 Tape table 或 raw Tape row。 diff --git a/docs/architecture/provider-catalog/spec.md b/docs/architecture/provider-catalog/spec.md index 18697d059f..92ddb8e85e 100644 --- a/docs/architecture/provider-catalog/spec.md +++ b/docs/architecture/provider-catalog/spec.md @@ -5,7 +5,7 @@ DeepChat currently resolves a provider's visible model catalog in the renderer. `modelStore` combines provider-db projections, persisted `provider_models`, custom models, model status, and per-model configuration, and separately decides whether an explicit refresh may contact the -provider. The main process also owns refresh paths: provider instance initialization, +provider. The backend also owns refresh paths: provider instance initialization, `models.listRuntime`, `providers.refreshModels`, and provider-db background refresh. This split produced a user-visible defect: once a provider catalog was persisted, the settings @@ -19,7 +19,7 @@ freshness contract. ## Goals -1. Make the main process the single owner of provider catalog orchestration and persistence. +1. Make the backend the single owner of provider catalog orchestration and persistence. 2. Keep source ownership unchanged: provider facts remain distinct from provider-db defaults, user model configuration, and user selection. 3. Make an explicit user refresh always contact the provider's current catalog source. @@ -64,9 +64,15 @@ Persistence owners remain: Derived effective values are never written back into `provider_models`, `model_configs`, or provider-db. The projection is recomputable from the sources above. +All asynchronous refreshes, including detached freshness/config work, run in Core's existing +database request scope through fetch and commit. Maintenance and shutdown drain that accepted +work before replacing or closing the profile; they do not leave an independent catalog writer. + ## Proposed Design -One main-process `ProviderCatalogService` owns catalog reads and refreshes. +One Core-owned `ProviderCatalogService` owns catalog reads and refreshes. On the Rutis desktop, +the Core SDK worker hosts the service and shared writer; Main authenticates ingress and supplies +Native ports. Source files under `src/main/provider` do not designate an Electron process owner. ### Read path diff --git a/docs/architecture/rutis-multiprocess-backend/plan.md b/docs/architecture/rutis-multiprocess-backend/plan.md new file mode 100644 index 0000000000..e33cf078d5 --- /dev/null +++ b/docs/architecture/rutis-multiprocess-backend/plan.md @@ -0,0 +1,5317 @@ +# DeepChat Harness and Capability Migration Plan + +## Objective and completion rule + +Extract a reusable Harness, Memory and peripheral capabilities through Rutis, then move selected +owners/computations to Rust where correctness, deployment or measured resource savings justify it. +Work on `dimagent/rutis-harness-bootstrap`, one complete module at a time. Replace actual app wiring, +run matching end-to-end baseline and migrated workloads, and remove the old path after parity. +There is no retained implementation switch or local fallback. + +Current scope: staged implementation in +[Draft PR #2465](https://github.com/ThinkInAIXYZ/deepchat/pull/2465), targeting `dev`. Admission is +committed as `ffcd98770` and the roadmap as `f35adff34`. G4 is complete at `356143755`; +G5 is complete at `1719f8075`; exact-source required checks, all six native verification +packages and the original integrated Native foreground case pass. G6 is complete at `bd64c2d53`; +G7 is complete at `f1b425e4b`: Rust admission and cohesive Run/interaction/input control pass +local functional acceptance, required CI and all six actual native package targets. G8 is the +completed selection no-go; G9 product Hooks and current-observation reconciliation are complete +at `6607e0b27`, with required CI and all six actual native package targets accepted. G10 completes +with a Store selection no-go; G11 headless Session Host is next. Further performance sampling is +not selected. +Later goals start only after their dependencies and the preceding selected goal pass validation. + +Use this file as the only tracker. [The RFC](spec.md) defines capability ownership, Memory +dependencies, Rust hypotheses, process placement and Everywhere contracts. Keep status and evidence +here as slices land; do not create a second task ledger. No GitHub issue sync is requested; +review status is maintained in the linked Draft PR. + +For each goal, finish the selected implementation and real default wiring, run the relevant tests +and baseline/migrated application comparison, review ownership/failure/resource behavior, and +remove replaced wiring. Run format, i18n, lint and typechecks before committing. Push each validated +goal separately to this same Draft PR, then update its status before starting the next goal. +Failed checks must be resolved or recorded as a concrete external blocker; they cannot be silently +skipped to advance. Keep the PR in draft and do not merge it as part of this execution. +If conflicts prevent CI from starting, compare against a freshly fetched target branch; cached PR +base metadata and local remote-tracking refs may describe an older target. + +Publication for verification is distinct from distribution. Finish a goal's complete source wiring, +local behavior comparisons and review before its coherent commit/push to the Draft PR. Run required +checks and the six native package targets on that exact published head; the goal stays active and +the next goal cannot start until those checks pass. PR verification packages deliberately have no +Apple distribution credentials or installers. Preserve signing/notarization and signed-package +acceptance as mandatory distribution gates in the release pipeline; they are not prerequisites for +pushing the source that CI must validate. This ordering governs G5 checkpoint publication notes. + +A documentation-only decision may reuse accepted runtime/package evidence when the complete diff +is limited to non-executable documentation excluded from application packaging. Record that code +source and the checked diff; run the documentation/static checks. This exception cannot cover a +source, test, dependency, resource, build or packaging change. + +Correctness, persistence, ownership, bounded resources and lifecycle behavior remain blocking. +Shared-CI wall-clock overruns are reports, not failed checks. Batch adjacent internal composition +changes and their verification within the selected goal. Reuse existing coverage; keep only durable +behavior, persistence, ownership or recovery tests and remove disposable implementation scaffolding. +Matching application E2E is required for the complete capability cutover and affected public or +lifecycle behavior, not each internal file move. Functional delivery takes priority. Resource +observations are informational; do not add sampling or investigate timing variance as a condition +of a structural migration. Investigate an observed user-visible regression when there is concrete +evidence. A module selected specifically to claim a performance gain needs supporting evidence; +without it, retain the current implementation and record no-go. Shared runners may enforce +correctness and deterministic work, payload and queue limits, but timing noise must not trigger +unchanged CI or benchmark reruns. + +After acceptance, remove obsolete source/build copies, disposable profiles and test media. Retain +compact receipts, raw measurements and only the immutable baselines still needed for pending work. +Reuse the retained baseline instead of accumulating a complete output tree for every checkpoint. + +## Live goals and dependencies + +| Goal | State | Dependencies | Completion condition | +| --------------------------------------- | --------------------------------- | ------------------------------ | ------------------------------------------------------------------------------------------------- | +| G0 Admission | Complete, `ffcd98770` | Existing branch baseline | Default Rutis wiring, matching real-app E2E and old path removed | +| G1 Whole-capability roadmap | Complete; documentation only | G0 and source research | Capability map, Memory boundary, Rust ranking, ordered goals and honest evidence | +| G2 Baselines and first service contract | Complete, `84e3e0ead` | G1 | Reproducible resource baselines and frozen operation/ownership budgets | +| G3 Memory vector resources | Complete, `7ee99619b` | G2 | Actual vector operations through Rutis, preserved native lifecycle and old factory wiring removed | +| G4 Store commands and desktop ports | Complete, `356143755` | G2 | Explicit durable operations, writer inventory and asynchronous host capabilities | +| G5 Cohesive TS Harness backend | Complete, `1719f8075` | G4 | Run/loop/Session/Tape/Provider composition operates under one backend authority | +| G6 Full Memory capability | Complete, `bd64c2d53` | G3, G4, G5 | Recall, ingestion, management and maintenance use the one Memory service | +| G7 Rust control owner | Complete, `f1b425e4b` | G2, G5 | Admission and cohesive run/interaction settlement preserve all fences | +| G8 Rust batch computations | Complete: selection no-go | G2, G4, G5; G6 for Memory work | Measured CPU/allocation improvement with output and quality parity, or a recorded no-go | +| G9 Peripheral capabilities | Complete, `6607e0b27` | G4, G5 | Independent useful lifetime, real consumer cutover and no duplicate supervisor | +| G10 Rust durable Store | Complete: selection no-go | G4, G5, G6, G7 | Compatible whole-owner cutover with a concrete benefit, or a recorded no-go | +| G11 Headless Session Host | In progress | G4, G5 and host-port audit | Real CLI execution without Electron and without a connected desktop | +| G12 Multiple clients and devices | Planned | G11 and explicit grants | Reconnect-safe clients and separately authorized, revocable device operations | + +G3 is selected for its bounded file/resource ownership, not an assumed native query speedup. +G2 may reject its process placement if transport or aggregate memory costs are unacceptable. +G4 and G5 remove the shared-storage and synchronous-call blockers for the full Memory service. +G7 and G8 are different objectives: a coherent Rust control owner can have architectural value +without a speed claim; CPU-oriented rewrites require measured gains. G11 does not wait for G8/G10. + +## G0. Admission migration: complete + +### Establish the baseline and contract + +- [x] Inspect all admission callers, leases, diagnostics, startup/shutdown, packaging and E2E fixtures. +- [x] Run existing admission and delegation tests on the original app wiring: 82 tests passed. +- [x] Build and run the original application with isolated profiles and a local model fixture. + Preserve baseline build artifacts outside the source tree for the same migration E2E workload. +- [x] Define ordering, cancellation/late-grant cleanup, per-owner limits, correlation, disconnect, + bounded framing, observation and shutdown requirements in the RFC. + +### Replace the module and its application wiring + +- [x] Move the existing admission queue owner into the TS runtime; preserve its behavior and tests. + Keep shared contracts and caller lease state explicit; remove the main-process queue owner. +- [x] Provide the complete admission operations through Rutis, including owner limits, correlation, + cancellation, release reasons, close and diagnostic flushing. +- [x] Replace the one-shot Rust probe with the application host and a declared admission gateway. + Preserve request order, bound transport resources and close pending work on generation loss. +- [x] Connect the main composition and actual LiveDelegationService to the Rutis client by default. + Stop new admission after runtime failure; keep one shutdown owner. +- [x] Integrate normal builds and packaged artifacts, including the unmodified runtime closure and + correct native host target. Remove the separate developer-only module path. + +### Compare actual execution and remove old wiring + +- [x] Run the same real-app E2E delegation workload against baseline and migrated builds: child + execution, admission queueing, cancellation/interrupt, completion, diagnostics and shutdown. +- [x] Verify process loss rejects pending work and cannot silently create another admission owner. +- [x] Add only durable lifecycle/contract regression coverage after implementation; reuse the + existing owner and delegation suites, avoiding mock implementations of Rutis. +- [x] Remove old composition wiring, obsolete probe code, unused dependencies and temporary + comparison artifacts from the deliverable. Retain no fallback or implementation toggle. + +### Whole-change review and validation + +- [x] Review ownership, public compatibility, queue fairness, lease races, diagnostics gates, + transport validation, resource limits, failure behavior, deployment and shutdown. +- [x] Update directly affected maintained architecture and logging contracts. +- [x] Run format, i18n, lint, typecheck, relevant Vitest suites, Cargo checks and the actual E2E + comparison. Record measured results and unexecuted targets without claiming performance gains. + +## G1. Source-backed roadmap: complete + +- [x] Commit the existing admission migration before broader planning: `ffcd98770`. +- [x] Inspect the actual Harness composition, synchronous ownership/fences, Memory facade and + vector resources, shared database, scheduler writes and existing helper processes. +- [x] Define coherent capabilities separately from process placement and implementation language. + Keep internal helpers internal and preserve desktop/external-plugin trust boundaries. +- [x] Specify Memory as one capability, its vector-resource first slice, shared-storage and + execution-token dependencies, ingestion consistency, maintenance and prompt-trust rules. +- [x] Rank Rust candidates by structural value or measured CPU/allocation/idle-runtime savings. + Account for existing native engines, bounded recall and existing resource eviction. +- [x] Define dependencies, acceptance and stop conditions for Harness, peripheral and Everywhere + goals. Reuse existing perf/quality diagnostics and make no unmeasured gain claims. +- [x] Review doc consistency, local references and scope; run repository format, i18n, lint and + typecheck. This goal changes only this RFC and plan; no new application code or tests. + +## G2. Establish baselines and freeze one extraction contract + +- [x] Preserve immutable pre-admission and `ffcd98770` artifacts, runtime versions and isolated + fixtures. Reproduce baseline workloads before using an old build as performance evidence. +- [x] Profile idle/startup, streaming and delegation, large context/Tape, native Memory recall and + background maintenance. Use at least five paired runs and fixed local Provider fixtures. +- [x] Measure CPU, native operation time and process-tree memory; distinguish Provider fixture + waits from local work and freeze the transport/resource envelope from actual callers. + Record allocation/GC and data-copy risks from source. Collect allocation-stack traces and + vector IPC byte counters with G3/G8 before attributing a measured performance gain. +- [x] Freeze numeric target and allowed regressions before judging the selected implementation; + record hardware, variability, workload sizes and whole-app measurement method here. +- [x] Freeze the selected service's versioned schemas, bounded batches/results, caller identity, + generation/operation IDs, cancellation, unknown outcomes, readiness and shutdown contract. + Keep the admission gateway allowlist unchanged until a deliberate service protocol exists. + +Completion: one concrete module and its placement have a measurable purpose and an executable +end-to-end comparison. Missing measurements do not justify a claimed Rust speedup or extra process. + +### G2 measurement evidence + +Environment: macOS 27.0.1, Apple M5, 10 CPU cores, 24 GiB RAM, Node 24.22.1, pnpm 10.34.5, +Electron 43.8.3, DuckDB Node API 1.5.5-r.4 and Rust 1.95.0. Use fixed exports of `3e9948b72` +(pre-admission) and `f35adff34` (admission code `ffcd98770` plus documentation), linked to the same +installed native dependencies. Build both with `pnpm exec electron-vite build`; include the locked +host/runtime closure in the admission artifact. Both are unpackaged builds with the Playwright +inspector, not signed-release or user-profile footprint measurements. The complete `out` tree hashes were unchanged +before and after all application comparisons: + +- Baseline: 1,054 files, SHA-256 `16c1efc66372897e4d3cb0814dc86c15073b0234f8cd0dfe6ea160e156119ee8`. +- Admission: 1,159 files, SHA-256 `acd0bdc1e65d37ded30f56b49cf14dbcc189cbe7f326b03657b007f8ec58f156`. + +Run the same current E2E harness against both immutable builds with +`DEEPCHAT_E2E_MEASURE_RESOURCES=1`, selecting `01-launch`, `36-chat-streaming` and +`44-admission-delegation` through `test/e2e/playwright.config.ts`. Alternate baseline/admission order +over five pairs. Every launch uses the fixture's disposable profile, except intentional relaunches +within the existing recovery cases. These are fresh-profile tests with a warm OS file cache, not +disk-cache-cold startup. All ten runs pass the same five checks: 50 E2E passes. + +The opt-in resource helper attaches numeric JSON reports without changing production code. It +samples all descendants every 500 ms using the public macOS `proc_pid_rusage` v0 layout, recording +resident size, physical footprint, process identity and cumulative CPU. Convert CPU Mach ticks with +`mach_timebase_info`; calibration matches a 0.2-second CPU-clock workload. Readiness is visibility +of the app root and sidebar. Event-loop histograms begin after inspector attachment. Short-lived +children and between-sample allocation peaks may be missed; sampled CPU excludes shutdown after +the last sample. Physical-footprint sums and RSS sums are separate process-tree measures, not a +claim about unique system-wide physical pages. The observer is identical for both builds. + +Event-loop delay does not attribute GC or allocations. G2 does not claim allocation-stack, GC-pause +or current wire-byte measurements. Vector IPC does not exist in this baseline; G3 instruments its +actual byte counts, and performance-driven Rust work needs stage-specific CPU/allocation profiles +before selection. This keeps the first isolation contract separate from an unproven optimization. + +The following are medians of five runs. Footprint/RSS are sampled per-run peaks; event-loop values +are medians of each run's p99. Provider work uses the existing local fixtures. + +| Case | Build | UI ready, ms | Footprint, MiB | RSS, MiB | Sampled CPU, s | Main loop p99, ms | +| ---------- | --------- | -----------: | -------------: | -------: | -------------: | ----------------: | +| Startup | Baseline | 1988.70 | 1012.96 | 1475.91 | 4.76 | 44.96 | +| Startup | Admission | 2058.19 | 1036.56 | 1545.00 | 4.68 | 68.81 | +| Streaming | Baseline | 1999.99 | 1372.99 | 1677.64 | 7.74 | 47.78 | +| Streaming | Admission | 2067.01 | 1395.93 | 1759.14 | 7.78 | 54.10 | +| Delegation | Baseline | 1995.45 | 1067.48 | 1482.30 | 6.76 | 45.15 | +| Delegation | Admission | 2069.24 | 1093.26 | 1565.52 | 6.77 | 50.86 | + +Current startup has a 1975-2336 ms range and 82.72 ms median absolute deviation (MAD). Streaming +readiness MAD is 80.01 ms; delegation readiness MAD is 66.13 ms. Current footprint MADs are 12.87, +0.73 and 58.09 MiB respectively; delegation loop-p99 MAD is 2.16 ms. The Rust host and admission TS +runtime have median individual sampled footprints of 2.70 and 28.75 MiB at startup. Admission adds +about 23-26 MiB to the measured application footprint and 67-74 ms to readiness in these cases. +This is a structural migration with a measured cost, not an observed performance improvement. + +Native SQLite/Memory performance checks pass at 1k/10k/50k rows, with Tape cases through 100k +entries. A temporary native-vector scale fixture calls the production `MemoryVectorStore` with +real DuckDB, deterministic 384-dimensional vectors, 50-row upserts and 11 warm query samples. +For row `r` and component `c`, use `((r * 31 + c * 17) % 65521) / 65521`; query row 17 with top 20 +and report the first query separately. Run the existing perf config with native SQLite required +and `--disableConsoleIntercept` to preserve its numeric reports. +Together, 7 performance files/11 checks pass with native SQLite required and no skipped check. +The temporary scale fixture stays outside the deliverable; existing performance suites and the +production adapter supply the repeatable operations. + +| Native operation | Scale | Median, ms | p95, ms | +| ------------------------------------ | -----------: | ---------: | ------: | +| FTS recall | 1k rows | 0.56 | 0.65 | +| FTS recall | 10k rows | 0.87 | 1.00 | +| FTS recall | 50k rows | 2.89 | 3.20 | +| Vector query, 384 dimensions, top 20 | 1k rows | 0.83 | 0.94 | +| Vector query, 384 dimensions, top 20 | 10k rows | 3.92 | 4.17 | +| Vector query, 384 dimensions, top 20 | 50k rows | 29.01 | 29.14 | +| Tape bounded range | 100k entries | 0.04 | 0.05 | +| Tape full view | 100k entries | 108.22 | 131.52 | + +These are fixture/native-adapter timings, not end-to-end application recall or a JS/Rust comparison. +The Tape result justifies checking range use and materialization before rewriting a fold; it does +not prove the normal turn takes the full-view path. Native DuckDB time is not a JS bottleneck. +Whole-app Memory-active, greater-than-eight-Agent and upload-saturation comparisons belong to G3's +actual module cutover, before deleting its old wiring. No raw history/vector IPC exists in G0; +G3's protocol byte, queue and native-concurrency caps are frozen in the RFC. + +### G3 adoption budgets + +These are policy targets, frozen before G3 implementation. Repeat at least five paired runs with +the same build/runtime, profiles, local fixtures and observer. Use the admission build as the next +module's baseline and preserve G2's pre-admission comparison for aggregate architecture cost. + +Shared PR runners report timing overruns with `DEEPCHAT_MEMORY_PERF_REPORT_ONLY=1`; correctness, +runtime/ABI identity, finite measurements, workload/queue bounds and test timeouts remain blocking. +There is no `continue-on-error` around the suite. The command enforces timing budgets by default. +The completed G3 comparison uses strict operation measurements and paired application runs on +this fixed machine. Its numeric budgets below remain the recorded G3 acceptance policy, not a +mandatory benchmark matrix for every later slice. Select later measurements by the execution rule +above. A documentation or test-only commit with verified identical runtime artifacts does not +require another complete resource capture. Do not repeat shared-runner benchmarks merely to +obtain a green timing sample. + +| Gate | Required result | +| --------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Unused Memory | No additional process; UI-ready medians add at most `max(100 ms, 2 x baseline MAD)` per case; sampled footprint medians add at most `max(32 MiB, 2 x baseline MAD)` | +| Memory-active resource cost | After drain, at most 64 MiB extra process-tree footprint; sampled active peaks at most 128 MiB extra for matching vectors/rows; one staged upload of at most 16 MiB; store leases and queues converge | +| Warm native query | p95 overhead at most `max(5 ms, 20% of native baseline p95)`, including every transport/copy; establish the matching real-app baseline before replacing the module | +| Upsert | Mean overhead at most `max(5 ms, 25% of baseline mean batch time)` per logical 50-row batch at the characterized sizes; preserve one atomic native transaction | +| First vector use | Runtime plus native open has median at most 500 ms and p95 at most 1 second; preserve the existing retrieval fallback/deadlines rather than extending them | +| Loaded control loop | Delegation loop-p99 median adds at most `max(10 ms, 2 x baseline MAD)`; bulk transfer cannot starve acquire/release/cancel | +| Correctness and isolation | Same recall selection/quality and durable data; atomic interrupted uploads, known-ID late-open cleanup, stale-generation rejection, FTS degradation and no replacement admission owner | + +Do not accept a rewrite for noise-level gains. Later performance-driven Rust work requires at least +20% lower total CPU for the selected local workload or 15% lower settled aggregate footprint, +including serialization and all participating processes. End-to-end gains must exceed measured +variance, with no readiness/response regression above 5% beyond that variance. A structural owner +migration can instead satisfy its declared lifecycle objective within its frozen resource budget. + +- [x] Repair the two complete-suite CI contracts for the required Harness build and Cargo steps; + both contract files pass locally (9 checks). Full main validation passes: 677 files passed, + 2 skipped; 9,827 tests passed and 4 skipped. +- [x] Typecheck the E2E fixture and measurement helper directly in addition to app typechecks. +- [x] Complete final format/i18n/lint/typechecks, all 29 local references and scope review. + The uninstrumented five-case application E2E also passes; no production module changed in G2. +- [x] Push G2 as `84e3e0ead` to Draft PR #2465. Required CI run `40193664779` passes on exact head + `84e3e0ead952d9ea769246c4ca2851a2338c7eca`: static, main, renderer, native Memory, build and + the required-check barrier all succeed before G3 starts. +- [x] Exact-head package check run `40193664778` passes all six macOS/Windows/Linux x64/arm64 + verification packages and the package-required barrier. This evidence covers G2 artifacts. + +## G3. Extract Memory vector resources through Rutis + +First characterize real native Memory search/management on the preserved application build. +This migration requires a matching executable baseline before replacing the factory, so the +shared application E2E fixture is written before implementation as contract characterization. + +Current evidence: + +The Float32 transport passes local operation budgets. Shared Linux runner timings vary enough +to pass and fail the same implementation; they are reported separately from fixed-device adoption. +G3 is accepted at checkpoint `7ee99619b`. The selected implementation sends bounded raw +upload bytes from Main to Rust. Rust stages one bounded upload and acknowledges each received +chunk; commit dispatches a native atomic upsert through Rutis `Value::Bytes`. Internal native parts +are bounded to eight MiB so encoded messages fit Rutis's 16-MiB limit; all parts belong to one batch +and only the complete batch reaches the existing transaction. This preserves +64-KiB first chunks, 240-KiB continuation chunks, one outstanding chunk acknowledgement, the 16-MiB/50-record logical +limit, atomic writes and control priority. The native owner keeps resource and file authority; +application transfer state belongs to the Rust gateway. Raw forwarding alone shows little local +latency improvement and does not justify retaining a second per-chunk native acknowledgement. +Pinned provider SDK patches validate a finite numeric array and clone it once, preserving response +validation and raw-response isolation without allocating a Zod parsing payload per component. +All 27 real SDK response contract checks pass across OpenAI, compatible, Azure, Google single/batch, +and Vertex Node/edge endpoints. Exact-head required CI, all six package checks and complete +matching resource comparisons pass before the next goal. + +- Actual recall, indexing, deletion, reset and maintenance use one lazy native TS vector runtime + under the existing Rust host. Admission retains its separate runtime and identity. Main keeps + the typed facade, Memory policy and canonical database; Knowledge still uses DuckDB in Main. + The replaced native factory and recovery wiring are removed. +- The vector worker holds the private kernel-owned database lock before recovery or file mutation. + Persistent Agent leases fence uncertain writes, late opens and disposal. Main can synchronously + append quarantine intent, including after worker loss, but only the locked native owner consumes + it. Native quarantine terminates the vector process before uncertain cleanup or GC; admission + survives, vector retrieval degrades to FTS, and restart requires explicit rebuilding. +- Typed native column buffers preserve atomic writes and Float32 values while avoiding per-component + native scalar allocation. Only the vector worker uses eight-MiB V8 semi-spaces. The admission heap + policy remains unchanged; there is no forced GC or retained implementation switch. + +### Matching application resource comparison + +The immutable baseline uses G2 admission source with the same current Provider/ACP catalogs and +shared E2E fixtures. The selected application implementation is `6a2544ddb`; later validation-only +changes preserve its complete output hash. Each disposable-profile vector scenario indexes +1k/10k/50k canonical memories with dense 1536-dimensional Float32 embeddings, checks vector-only +recall, deletion and native row counts, then relaunches and clears every memory plane. + +Six pairs at 1k exclude the first pair as warmup; five pairs at 10k and 50k alternate variant order. +All 32 vector application runs pass. The first launch observes unused Memory. The active launch +includes an unchanged 30-second post-work idle; settled footprint is the median in its final ten +seconds, then the median across five runs. This window does not establish a stationary heap. +Use the G2 macOS/M5 observer and hardware; builds are unpackaged with the Playwright inspector. +Sampled CPU excludes shutdown and processes that live entirely between samples. + +| Rows | Settled baseline / migrated (MiB) | Delta (MiB) | Sampled active peak baseline / migrated (MiB) | Sampled CPU baseline / migrated (s) | +| ------ | --------------------------------- | ----------- | --------------------------------------------- | ----------------------------------- | +| 1,000 | 943.18 / 961.20 | +18.02 | 1110.96 / 1122.17 | 7.27 / 6.50 | +| 10,000 | 700.82 / 722.83 | +22.00 | 1226.11 / 1272.08 | 21.31 / 11.18 | +| 50,000 | 1036.09 / 1023.17 | -12.92 | 1883.62 / 1538.78 | 121.12 / 71.79 | + +Every scale passes the unchanged 64-MiB settled and 128-MiB sampled-peak allowances. Reopen/clear +phases also pass their resource checks. All unused launches have the same process-name counts; +no vector worker starts before vector use. Across vector cases, the largest positive readiness +median delta is 17.72 ms and the largest footprint delta is 3.38 MiB, within their frozen limits. +These figures include SDK/native-buffer changes and do not establish a Rust query speedup. + +The original five loaded-control pairs all pass queue/cancel/release and Memory completion, but +unused-startup readiness fails the original independent-median rule: 2271.99/3376.21 ms gives +1104.22-ms extra against a 326.42-ms allowance. Both variants slow during that group; the median +of adjacent paired differences is 59.42 ms. This result is retained, not relabeled as a pass. + +One independent five-pair control confirmation was declared before collecting more samples. It +requires both the new group and all ten pooled control pairs to meet the original rules, with +all original samples retained. Both pass. Confirmation unused readiness is 2183.80/2105.51 ms; +pooled readiness is 2227.89/2178.13 ms. Pooled Main loop-p99 medians are 99.16/42.39 ms, satisfying +the unchanged loaded-control allowance. Pooled active peak/window footprint deltas are ++25.86/+27.01 MiB. All 20 loaded-control application runs pass. The nine vector phase comparisons +and two pooled control comparisons meet their gates; the independent control group also passes. +Do not use the original 42-run test pass count alone as resource acceptance evidence. + +Frozen inventory and evidence: + +- Baseline: 1,159 output files, SHA-256 + `ce5ddbad74d68dd7745bda91dcf35dc871930e6eebfb094dc5de6d1d6d00c7f4`. +- Migrated: 1,531 output files, SHA-256 + `2970d4a9178ffb4c9ece8959487695d797e68c573510028d7af5851e58faa74a`. +- `.deepchat/rutis-g3-acceptance/artifacts.json` records outputs, external SDK consumers, + shared catalogs/scenarios, patches and lockfiles. Both variants freeze 344 files across the + six SDK consumers, using original baseline and selected patched schemas respectively. +- Sort output path-component tuples, then hash the path including `out/`, a NUL and each raw file + SHA-256; the manifest preserves that exact order. Verify every file and external SDK/caller dependency before and after application runs. + Complete output, SDK, catalog, scenario and metadata hashes match after capture. +- `paired/state.json` and `paired-summary.json` retain the original 42-run data and its failed + readiness comparison. `control-confirmation-design.json` fixes the one confirmation and pooled + acceptance rule; `control-confirmation-summary.json` records both passes. Excluded sleep, + startup and incomplete-indexing attempts remain separate from completed resource samples. +- `resource-acceptance.json` combines the nine vector and two pooled control comparisons with + the independent confirmation, all 52 successful application runs and evidence hashes. + +### Native transport, lifecycle and distribution validation + +Checkpoint `7ee99619b9b21b27339322b36d0cfcce57ea4abe` passes required run `40262089127`: +static, Main, renderer, build, native Memory and the required barrier all succeed. Native job +`121376436278` passes 985 behavior, 306 Tape, 12 OCR, 362 native Memory, seven retrieval-quality +and 13 performance checks; two platform-specific native cases are skipped on Linux. +Its explicit report mode records the 1k write overhead of 5.421 ms against the unchanged 5-ms +allowance as a warning. Finite measurements, operation correctness and bounded work remain hard +checks. Five independent strict local runs below establish the fixed-device operation budgets. + +Package run `40262089169` passes all six macOS/Windows/Linux x64/arm64 verification jobs and +the package-required barrier at the same checkpoint. Every target executes the production +Memory runtime smoke against its packaged host and Electron executable. The application +implementation remains `6a2544ddb`; the selected checkpoint adds validation policy and evidence +without changing the measured runtime output. + +Shared-runner variation remains visible: the same implementation's earlier required run +`40245677754` fails the 1k write gate at 7.948/14.684 ms (6.737-ms extra against the unchanged +5-ms budget), while 10k/50k writes and all query/first-use gates pass. +Numeric budgets, the first 20 write samples and cold-worker +behavior are unchanged. Required run `40254813435` at `89a5a4ff6` again passes all deterministic +checks but fails only the 1k timing threshold (7.958/14.899 ms; 6.941-ms extra). Shared-runner +timing limits now use explicit reporting; the full suite still fails on invalid measurements, +wrong runtime/ABI, failed operations, unbounded work or timeout. Strict repeated fixed-device +measurements below satisfy the unchanged adoption budgets. Run `40248084214` at `07e3f688a` +passes the original shared-runner timing assertions on the same implementation, with write +overheads of 3.338/2.201/1.626 ms at 1k/10k/50k. No production fix is attributed to this variation. + +Five local comparisons of a Float32 exponent-bit finite check show no improvement (median extra +latency 1.864/1.900 ms for current/prototype). A standard native base64 decoder reduces median +extra latency from 1.902 to 1.786 ms locally, insufficient evidence for another compatibility +branch. Neither prototype is selected. Cold-worker native phase timings and V8 sampling leave no +measured justification for further native scalar or decoder changes. Temporary first/last sample, +phase and CPU probes are removed; formal adoption samples, runtime/ABI equality and all 13 +performance checks remain. + +The selected implementation keeps first chunks at 64 KiB, continuation chunks at 240 KiB and stages one 16-MiB/50-record +upload in Rust. Only commit dispatches the native upsert. Native parts are at most eight MiB, +so base64 and bounded metadata fit Rutis's 16-MiB internal message limit. The native owner +validates the complete batch before one transaction and reserves per-resource order from the first +native part. Subsequent reads/mutations wait; abort/close settles an incomplete reservation without +performing a write. Failed partial dispatch aborts its known +native upload; abort cannot abandon a committing write. Contiguous native dispatch sequences are +independent of locally acknowledged application chunks. Existing generation, resource, lease, +quarantine and control-priority rules remain in force. The direct binding dependency is the exact +version already shipped with the Node API; no additional native payload or implementation switch +is introduced. + +Five Rust boundary/lifecycle checks pass and run in required CI alongside Clippy for all targets. +The transport correction passes 362 native checks, with two Windows-only skips on macOS; +123 focused Harness/store/package-contract checks pass, including two runtime byte contracts. +The codec checks cover malformed padding/unused bits, whitespace/non-base64 rejection, exact +independent backing buffers and byte equality through the eight-MiB native-part limit. +The native runtime scope +also passes a valid 45-record, +49,152-dimensional batch spanning internal parts, persistence after close, incomplete-upload and +non-finite rejection, maximum-16-MiB failure cleanup, abort/close cleanup, admission continuity +and strict child exit. Retrieval evaluation passes seven cases. Scoped test types, app types, +format, i18n and lint pass. The full Main/renderer suites and six native package jobs pass +on the selected checkpoint `7ee99619b`. + +Five independent strict operation runs pass all three characterized scales: 15 checks with +matching Electron Node 24.22.0 / ABI 148. The complete seven-file/13-check suite separately passes +in both default strict and explicit report-only modes. The numeric budgets are unchanged. + +| Rows | 50-row write overhead range (ms) | Maximum query p95 overhead (ms) | Maximum first-open median / p95 (ms) | +| ------ | -------------------------------- | ------------------------------- | ------------------------------------ | +| 1,000 | 1.811-1.967 | 0.489 | 137.098 / 201.337 | +| 10,000 | 1.336-1.424 | 1.059 | 135.859 / 137.561 | +| 50,000 | 1.180-1.263 | 2.346 | 135.841 / 137.134 | + +Focused timing-policy, workflow, scope and entrypoint contracts pass 22 checks. Invalid timing +values still fail in both modes; only the explicit reporting value disables a wall-clock budget +assertion. No job/step uses `continue-on-error`. Format, lint, i18n and app/scoped test types pass. +Evidence logs use `.deepchat/rutis-g3-acceptance/{strict-transport-*,policy-*}.log`. + +Remote timings include encoding, gateway copies and native work. Thirty queries transfer +280,650-280,710 application gateway bytes including replies; pending calls drain to zero. +Five new workers per scale meet the 500-ms median and one-second p95 first-use limits. +Typical 300-KiB writes use only begin and commit; observed pending calls remain at one. +Persisted native checks cover packed batches with +unaligned source offsets, Float32 extremes/negative zero, ordinary/packed writer reuse, whole-batch +duplicate-key rollback and pre-effect rejection of malformed or non-finite input. Shared mutable +buffers are rejected at the native command boundary. + +Seven actual application checks pass: launch and plugin-host failure recovery, streaming chat, +delegation queue/cancel/release, Memory recall/deletion/restart/clear, nine-Agent cache convergence, +model/dimension changes, native-worker loss, FTS/chat continuity and explicit rebuilding. The +clear-after-loss case targets an evicted Agent so deleted vectors cannot reappear after restart. +Complete paired resource evidence above supersedes single diagnostic preflights. Both isolated +application trees pass the same actual Memory E2E. The selected runtime must retain its unchanged +artifact hash for this evidence to apply. +The 50k fixture reserves one minute of its unchanged 300-second test budget for recall, +30-second idle, shutdown and relaunch; smaller fixtures retain their 120-second completion wait. +Two incomplete baseline attempts ended at 120/180 seconds with 2,650/2,300 rows pending and are +excluded. Trace samples show continuous indexing progress through the latter wait. The 31 +completed runs retain their original source-manifest hashes: their successful execution did not +reach either timeout. Comparable protocol records preserve both scenarios/manifests and failed +attempts. Workloads, observer, production deadlines and all numeric adoption budgets are +unchanged; the final sample and all control runs complete under that recorded protocol. + +The six native package jobs execute `smoke:harness:memory` against the packaged Rust host and +Electron executable, using production clients for native CRUD, fractional values, restart/reset, +admission continuity and strict child shutdown. All six pass on the selected checkpoint +`7ee99619b` in run `40262089169`; copied layout checks alone cannot establish distribution parity. + +- [x] Retain DuckDB v2 in TS; move native handles and file-owner lifetime into one Rutis service. + Keep existing lease/embedding policy coherent; adapt synchronous health/quarantine and + object-returning factories through generation-bound resources and acknowledged operations. +- [x] Connect actual Memory recall, embedding writes, deletion, rebuild and maintenance callers. + Keep dense vectors near their resource owner and bound transfer without enlarging the + admission JSON protocol indiscriminately. Start one vector TS runtime lazily under the + existing Rust host; native poison must not force admission-runtime replacement. +- [x] Compare real app recall/FTS degradation, model/dimension changes, more-than-eight-Agent + resource convergence, clear/delete, native quarantine and shutdown against baseline. + Preserve migration/WAL and new-process recovery rules; vector runtime loss must preserve + admission and degrade only vector retrieval to the existing FTS behavior. +- [x] Review and validate the whole slice, including real native operations and relevant Memory + performance/quality checks. Remove old native factory/lifecycle wiring after parity. +- [x] Commit and push G3 checkpoint `7ee99619b`; exact-head required run `40262089127` and all + six platform package checks in `40262089169`, including packaged Memory smoke and both + required barriers, pass before G4. + +Completion: a real Memory resource module uses Rutis with one file owner and proven cleanup. +A standalone query demo or reduced main-process heap does not complete this goal. + +## G4. Define durable Store commands and desktop capability ports + +G4 starts from the accepted G3 application and its immutable candidate artifact. The first actual +cutover is the scheduler's independent shared-database connection. The full goal also includes +Session/Tape/Memory commands, configuration snapshots, maintenance and desktop capabilities; +removing the scheduler connection alone does not complete G4. + +### Audited ownership entrypoints + +| Connection or persistence owner | Current entrypoints | Required boundary | +| ------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Shared application database | `MainDatabase`; Session, Tape, Agent, Memory, Provider, MCP, settings, Project, delegation, scheduler and sync facades use its connection | Keep domain transactions together; move the normal writer and its business owners together in G5 | +| Scheduler utility process | Timer and heartbeat only; `CronJobsRepository.queueDueRuns` observes and advances due work in one transaction at the current Store owner | No database path/password/SQL crosses the helper port; preserve request correlation, generation fences and owner-side dispatch | +| Shared-file maintenance | `databaseSecurity.ts`, `mainDatabase.ts`, sync import/config import and backup helpers open active, source or staged files | Fence normal work, explicitly own each file and connection, preserve rekey/import/backup/reopen recovery; main loses these writable paths at G5 cutover | +| Import sources | Provider import, legacy chat import and browser profile import use source/snapshot connections; `DataImporter` currently opens both source and target with writable-capable configuration | Distinguish source ownership from the active target; keep target mutations inside the authoritative maintenance operation | +| Schema catalog | `schemaCatalog.ts` opens an in-memory database for schema inspection | Keep this temporary local connection internal to the Store owner | +| OCR artifacts | `ocrArtifactStore.ts` owns a separate encrypted derived-cache database | Preserve its explicit independent file owner; this is not a second writer of the shared application file | +| Vector stores | The G3 Memory worker and Knowledge's existing DuckDB owner use separate files | Preserve their existing resource authorities and maintenance fences | +| Configuration | Bootstrap constructs legacy app/MCP/ACP settings; runtime settings use database adapters; Provider model stores are required; attached ACP retains only an isolated legacy snapshot | Use acknowledged mutations and versioned read snapshots; hand off the complete legacy-file authority with the cohesive configuration owner at G5 | +| Credentials and desktop state | Main supplies async crypto/unlock; DatabaseSecurity, SecretStore, Sync, MCP and Codex/Grok own explicit private persistence; native configuration readers use a committed projection; the plugin owner uses its independent Conf file | Keep authorized crypto/native operations separate from persistence; preserve consent, one complete file writer and the final handoff inventory | + +### Ordered implementation slices + +1. Replace the scheduler worker's database access with bounded commands at the current Store + authority. Preserve the existing 100-job due-scan bound, schedule reconciliation, unique run + insertion, run claims, manual execution and idle/restart behavior. Timer requests cannot carry + a database path, password, SQL or callback. Committed run dispatch belongs to the owner, so a + lost timer reply cannot strand or replay a durable mutation. Keep at most one scan in flight + per worker generation and fence stale requests/replies before mutation or status publication. +2. Define the Session/Tape/Memory operation boundary around existing atomic business work: + transcript mutation and projection heads, pending-input settlement, Memory cursor/CAS and + maintenance. Keep synchronous repository helpers and transaction closures inside their owner; + do not convert every row method to RPC or split one atomic observation into separate reads. +3. Retarget configuration consumers through versioned snapshots and acknowledged mutations. + Inventory legacy/file adapters before choosing ownership; preserve machine-local bootstrap + settings and keep secrets outside general configuration snapshots. +4. Extract real crypto/unlock, OAuth, helper/native UI and power-event consumers behind narrow + async desktop ports. Preserve sender/principal checks, cancellation identity and maintenance + fencing. Complete the remaining writer/host audit before moving the cohesive core in G5. + +Characterize scheduler behavior on the accepted G3 build before its caller cutover. Reuse existing +scheduler tests and add only the matching real-application migration fixture needed to compare +the two implementations; this baseline characterization is the migration exception to +implementation-first validation. After the cutover, select durable tests for transactional +claims, generation loss and shutdown, then compare the same app workload and process resources. +Keep the existing timer cadence and process count; freeze any additional numeric resource +allowance from the matching baseline before judging the selected implementation. + +The scheduler slice retains G3's fixed-device resource envelope: no extra process while unused; +unused readiness/footprint median deltas within `max(100 ms, 2 x baseline MAD)` and +`max(32 MiB, 2 x baseline MAD)`; active settled/peak footprint deltas within 64/128 MiB; +Main loop-p99 median delta within `max(10 ms, 2 x baseline MAD)`. Use five alternating paired +application runs with the same 30-second quiet-worker window and observer. These unchanged +allowances are selected before implementation; no Rust speed or CPU saving is claimed. Preserve +the existing timer cadence and functional deadlines. The immutable G3 candidate output and SDK +graph form the new baseline, with the same scheduler fixture installed in its separate G4 tree. + +The existing scheduler service and run-session starter suites pass all 28 checks under Electron +Node mode with native persistence enabled and no skipped check. The actual application fixture +`47-scheduler-store.smoke.spec.ts` passes on the accepted G3 runtime: manual and timed runs, +unique scheduled-run identities, persisted outputs, relaunch, disabling and scheduler shutdown. +Its three launches complete in 8.8 seconds without resource instrumentation. +Logs: `.deepchat/rutis-g4-acceptance/baseline-scheduler-{tests,e2e}.log`. + +The scheduler now uses the Store command by default. The helper holds one correlated scan +request, coalesces reconciliation and fences a missing acknowledgement at the existing 30-second +maximum scan interval. The manager validates messages and generation identity before mutation; +committed runs are dispatched before replying to the helper. Scheduled-run insertion and head +advancement roll back together. The independent worker connection, database-password environment +and unused worker-side manual-run command are removed. Existing public routes and manual-run +execution keep their owners. The two native suites pass 36 checks with no skips, including atomic +rollback, catch-up/skip policy, the 100-job scan bound, missing/empty error replies, coalescing, +duplicate/stale requests and a spawn superseded by shutdown. The matching actual app scenario +also passes on the migrated build. Format, lint, i18n, app types and scoped scheduler/E2E types +pass. The normal build refresh of the Provider catalog is retained. + +### Scheduler slice resource evidence + +Five alternating baseline/migrated pairs pass the same application scenario: ten successful runs +and 30 launches with isolated profiles. Every active phase includes the same 30-second quiet +worker window, with no forced GC. Use G2's hardware and 500-ms process-tree physical-footprint +observer; settled values are per-run final-ten-second medians, then medians across five runs. +All three phase comparisons pass the unchanged allowances selected above. + +| Phase / metric | Baseline | Migrated | Delta | +| ---------------------------------- | -------: | -------: | ----: | +| Unused UI ready, ms | 1932.24 | 1927.26 | -4.98 | +| Unused sampled peak footprint, MiB | 1032.93 | 1025.47 | -7.47 | +| Active sampled peak footprint, MiB | 1038.36 | 1039.72 | +1.36 | +| Active settled footprint, MiB | 855.95 | 847.97 | -7.98 | +| Active Main loop p99, ms | 12.45 | 12.33 | -0.12 | +| Reopen sampled peak footprint, MiB | 1061.75 | 1054.50 | -7.25 | + +Unused process-name counts match in every pair. These measurements establish the cost of the +scheduler ownership change in this fixture; they do not establish a Rust or CPU speedup. +The baseline has 1,531 output files with SHA-256 +`2970d4a9178ffb4c9ece8959487695d797e68c573510028d7af5851e58faa74a`; the migrated build has +1,530 files with SHA-256 `0176d47a0a4b26d05def1677fa418cf55400b5763f61b0566fcbf8a440580947`. +Both use the same current catalogs, scenario/observer and 344 frozen files across six SDK +consumers. Full file, dependency and metadata hashes are verified before/after every run; +Root's source and runtime still match the measured candidate after capture. Durable evidence: +`.deepchat/rutis-g4-acceptance/{artifacts.json,paired/state.json,paired-summary.json,outcome-counts.json}`. +Scheduler checkpoint `0add1c1ee` passes required run `40268252480` and all six platform packages +in `40268252495`, including both required barriers. The remaining G4 contracts are still open. + +### Credential crypto port slice + +Extract the real `SecretStore` native dependency behind an async, data-only desktop port. The +pinned Electron runtime exposes `isAsyncEncryptionAvailable`, `encryptStringAsync` and +`decryptStringAsync`; use those operations in the main-process adapter. Keep wrapped values in +their existing storage format and verify that the async decryptor reads legacy synchronous +ciphertext. Reads do not rewrite credentials automatically. + +Retarget all actual consumers together: Sync settings/routes/cloud-service construction, MCP +variable bindings and connection startup, user-plugin configuration, Nowledge connections and +both exporters. Keep existing plugin mutation serialization and Nowledge's update exclusion. +Serialize cloud-config writes across the new awaits; preserve credential rollback if configuration +publication fails. Recheck MCP connection attempt identity after awaited binding resolution. + +The credential owner fences async operations by its lifecycle generation. Capture that generation +before work and check it immediately before returning sensitive reads or committing wrapped +values. Pause/invalidate pending work at database maintenance and shutdown; resume only after +the profile is usable. Existing owner-local guards remain local functions, never wire callbacks. +Do not add a decrypted credential cache or an alternate native implementation. + +Reuse the existing real-app Nowledge/REST/MCP/key-protection scenario. Add only legacy-ciphertext +compatibility and cloud credential persistence characterization before this migration. Existing +Nowledge E2E passes on `0add1c1ee` in 4.7 seconds with real native wrapping and the local MCP +fixture. Native async compatibility passes on Electron 43.8.3, including legacy ciphertext. +Cloud credential persistence passes across three launches, an unrelated configuration update and +relaunch in 6.1 seconds. These characterize the baseline before the production cutover. + +The default application now injects `CredentialCryptoPort` through the main-process native +adapter. `SecretStore` no longer imports Electron or handles native buffers. Every actual Sync, +MCP variable-binding, user-plugin, Nowledge and export consumer awaits credential operations; +no synchronous crypto fallback remains in this owner. Cloud writes are serialized across awaits, +merge the latest committed base, and retain wrapped-secret rollback. The acknowledged view is +computed before publication so a later native read failure cannot turn a successful write into +an apparent failure. Existing plugin serialization and Nowledge update exclusion remain active. +MCP startup rechecks cancellation/attempt identity after credential resolution and OAuth setup; +a stale attempt cannot clean up a newer attempt's transport or timer. + +Maintenance and shutdown synchronously invalidate pending credential operations. Maintenance +resumes the owner only after database reopen/reconciliation; shutdown cannot resume it. +Reads also reject a wrapped credential replaced or removed during native decryption. Consumer +guards run immediately before sensitive return or synchronous persistence, including after a +successful wrapping result. Plaintext is not cached and reads do not rewrite wrapped values. + +Local acceptance passes 251 checks in 12 affected test files, including native failures, +maintenance/resume fencing, pending/queued cloud writes, publication rollback, a maintenance +transition after wrapping, Nowledge mutation exclusion and MCP cancellation during credential +resolution. Scoped credential/E2E types, app types, format, lint, i18n and the normal build pass. +The matching actual application scenarios pass after cutover: Nowledge REST/MCP/key protection +in 5.1 seconds, native legacy compatibility in 2.1 seconds and cloud persistence in 6.1 seconds +(three tests, 13.5 seconds total). Evidence is under +`.deepchat/rutis-g4-crypto-acceptance/`, including baseline/migrated E2E and affected-test logs. +The normal build's Provider catalog refresh is retained. + +This slice adds no process and moves no storage ownership; the data-only port remains in-process +until G5 moves the cohesive backend. No full process-tree recapture or performance improvement +claim is required for this extraction. Native credential compatibility is verified on macOS arm64; +Windows/Linux credential-store behavior remains unverified locally. Credential checkpoint +`c80d36b69` passes required CI `40271838825`, including its barrier; package run `40271838879` +passes all six platform verifications and its required barrier. `DatabaseSecurityService`, +Provider/OAuth credential stores, power events +and the remaining Store/configuration contracts are still G4 work. + +### Scheduler system-resume input slice + +Keep the native `powerMonitor` subscription in the main composition and deliver system resume +through a data-only Scheduler service operation. Remove Electron imports, native event objects +and asynchronous fallback attachment from the Scheduler business owner. Main owns one listener +until shutdown and ignores events during maintenance or shutdown; Scheduler ignores events while +stopped and fences in-flight resume reconciliation across stop/start generations before durable +updates or timer dispatch. Preserve the existing schedule reconciliation and due-run semantics. + +Characterize the existing application with two native-emitter resume notifications in the current +scheduler E2E before changing this consumer. This is migration characterization, not test-first +implementation. Compare the same manual/timed execution and relaunch scenario after cutover; +select durable lifecycle tests after implementation. This port extraction adds no process and +does not require another whole-process performance capture. Actual OS suspend/resume remains +separate from delivery through Electron's native emitter. + +The default main composition now owns one native resume listener and removes it through the +shutdown coordinator. It filters maintenance/shutdown delivery and observes reconciliation +failures. `SchedulerService.handleSystemResume` receives no Electron object or callback. Its +start/stop generation rejects stale reconciliation before schedule writes and timer dispatch, +including an event whose Agent lookup finishes after a stop/start cycle. The business owner +has no Electron import or asynchronous fallback subscription. + +Local acceptance passes 38 native-enabled scheduler checks without skips, plus 18 existing +composition checks. The actual application receives two resume notifications through Electron's +native emitter with exactly one listener, then completes manual/timed execution, durable run +identity checks, relaunch, disabling and shutdown. The matching scenario passes before/after +cutover in 8.6/7.9 seconds. App/scoped types, build, format, lint and i18n pass. Evidence: +`.deepchat/rutis-g4-resume-acceptance/`. This establishes event delivery and lifecycle fencing; +physical device suspend/resume and Windows/Linux native delivery remain unverified locally. + +### Session transcript Store command slice + +Select Session transcript clearing as the next actual durable command. `createSessionDataFromDatabase` +already composes the transcript, pending inputs, settings and Tape against one database. Move the +clear transaction assembly into that owner and connect `SessionTranscriptMutations` to the coarse +operation. Remove main composition's `runInTransaction` callback dependency. Retry truncation +already owns its transaction inside `SessionTranscript.deleteFromOrderSeq`; retain that atomic +operation rather than introducing another generic transaction port. Preserve runtime preparation +before the mutation, post-commit invalidation, clear rollback, Tape incarnation/projection cleanup +and retry behavior. This does not turn internal row APIs or transaction callbacks into RPC. + +Baseline characterization passes 46 existing native-enabled transcript/Tape/runtime checks. +The real local-streaming scenario now also drives the existing renderer Clear Messages action, +then verifies an empty pending queue and empty transcript after graceful relaunch; it passes in +6.4 seconds before this Store cutover. That renderer action awaits the durable acknowledgement +and reloads its message cache. This migration characterization is the exception to implementation-first +validation; no production renderer behavior changes are needed. + +The default composition now connects `SessionTranscriptMutations` to the Session data owner's +`clearTranscript(sessionId)` operation. That owner assembles the one transaction across pending +inputs, transcript projections and Tape reset. The mutation coordinator retains runtime +preparation and post-commit cleanup; it receives no generic transaction callback or separate +settings/pending-input writers. Retry truncation directly uses the transcript owner's existing +atomic range operation. No schema, file location, transport or process placement changes. + +Local acceptance passes 430 native-enabled checks across five affected files without skips. +The real data owner is exercised with a SQLite trigger that rejects Tape deletion: transcript, +queued input, Tape incarnation/head and Memory ingestion projection all survive rollback, and +runtime cleanup is suppressed. Successful clear removes all corresponding rows and creates a +fresh Tape incarnation. A later retry-retraction failure also rolls back earlier Tape writes and +all transcript changes; runtime invalidation occurs only after a successful retry commit. + +The actual renderer Clear Messages action and graceful relaunch pass after cutover in 7.2 seconds +against the 6.4-second matching baseline. The scheduler resume/manual/timed/relaunch scenario +also passes on the combined runtime in 7.5 seconds (two E2E tests, 14.9 seconds total). These are +functional checks. Scoped transcript/E2E types, all 81 scoped Memory test types, app types, build, +format, lint and i18n pass. Evidence is under `.deepchat/rutis-g4-session-store-acceptance/`. +The selected business transaction uses the same database and writes as its baseline; no full +performance recapture or speed claim is required. Other Session/Tape/Memory command boundaries, +configuration snapshots and remaining desktop capabilities still belong to G4. + +The combined resume/Session checkpoint `826b3af16` passes required run `40274014870`, including +`pr-required` job `121414464019`, and package run `40274015002` with all six platform jobs and +`package-required` job `121418838344`. Both workflows completed before the next settings push. + +### Versioned settings snapshot and desktop mutation port + +Keep the existing `settings.getSnapshot`/`settings.update` fields and routes, backed by one +application settings service. That service supplies explicit data-only snapshot/update operations +to routes and acknowledged display mutations to Agent tools. Retain native effects, normalization, +activity recording and existing per-field batch failure behavior; do not introduce a second +persistent settings owner or a generic settings RPC. Configuration entry routes and other +configuration domains remain separate G4 work. + +Use one Main presentation revision sequence for settings snapshots, mutation replies and +`settings.changed` publications. This sequence is scoped to Main's lifetime, which also owns its +renderer contexts; backend-generation checks remain mandatory at the future private transport. +Preserve revisions in the renderer client. Apply values by per-key revision so a delayed initial +read, update reply or partial event cannot revert a newer value or discard unrelated fields. +This is not a durable database revision. At G5, portable preferences must be written directly at +the backend configuration owner and native device effects must stay at Main; avoid a +Core-to-Main-to-Core mutation loop. Refresh the selected snapshot after database maintenance. Reject new owner operations while +maintenance or shutdown is active. Keep secret values outside this allowlisted projection. + +Replace the Agent display port's synchronous native getters/setters with one async validated +mutation request and a data-only acknowledgement containing the owner's previous/applied value. +Skill authorization and dispatch-journal commitment stay at the Agent owner before the mutation. +The default routes and Agent tools now use this service; the replaced route-local implementation +and eight synchronous Agent display getters/setters are removed. The renderer client retains only +bounded per-key revision metadata. Existing stores and OCR controls retain ownership of UI values; +OCR releases its event listener and ignores late successful or failed mutations after unmount. +Launch-at-login applies the owner's accepted value, without a second assignment from the request. + +Local acceptance passes 217 checks across eleven affected files. Durable cases cover delayed +snapshot hydration, mixed partial events, reversed mutation acknowledgements, invalid revisions, +native-owner receipts, strict display requests, fenced operations, failed writes and teardown. +The actual privacy/system-settings scenarios pass before cutover in 4.5/3.6 seconds and after +cutover in 4.1/3.6 seconds. A separate real-app check passes in 6.7 seconds with Main's clock +frozen and then moved backwards: snapshots, events and acknowledgements share a monotonic +sequence, the other window updates, and graceful relaunch preserves the preference. These are +functional results, not performance claims. + +The runtime build, app typechecks, format, lint and i18n pass. Scoped owner/Agent/E2E and renderer +store/OCR test types pass. The broad API-client test fixture has existing unrelated type diagnostics; +its runtime tests pass. Evidence is under `.deepchat/rutis-g4-settings-acceptance/`. No process, +schema, transport, layout or style change requires a full performance recapture. The service remains +in-process; other configuration domains, native capabilities and Store boundaries remain open. + +### Skill host paths and native directory capability + +Move Skill, Skill settings and external-import path acquisition out of Electron-dependent business +code. Main supplies explicit home/temp/user-data/bundled-skill directories and one asynchronous +native directory-open capability. Keep discovery, imports, scoped authorization, operation leases, +plugin source validation and filesystem rollback inside their existing owners. Preserve development +and packaged resource fallback order. Do not add a runtime fallback to Electron or duplicate the +Skill service in Main. This removes a G5 host dependency; it does not create a separate Skill plugin. + +Keep the pure watcher request-ID helper independent of native watcher construction. Shared console +diagnostics must not import Electron through a toolkit package: Main initializes the existing +development debug gate explicitly, while captured console sinks remain console-only. Standalone +Node hosts default to quiet debug output and may set the same data-only flag at their own startup. + +Characterize the existing Skills and scan-worker application scenarios before the cutover, with +the fixture's managed Skills directory isolated in its disposable profile. This characterization +is a migration exception to implementation-first verification. + +The default composition now supplies those paths and the native directory capability to the same +Skill service. Skill settings and import routes receive their required home/temp directories. +The three business modules no longer import Electron; the request-ID helper is available without +constructing a watcher, and shared logger imports no Electron toolkit. Main preserves its existing +`!app.isPackaged` console-debug gate at startup. Existing captured diagnostics still bypass file +persistence. No new dependency, process, database writer or native watcher owner is introduced. + +Local acceptance passes 781 checks across 36 files covering the full Skill group, watcher and +logging suites, routes and composition. New durable cases hold a native directory acknowledgement +through shutdown on both success and failure, reject unsafe Agent scopes before native dispatch, +and verify the explicit console-debug gate and captured sinks. Scoped settings/import/logging/E2E +types, app types, the runtime build, format, lint and i18n pass. + +A separate ordinary Node 24.22.1 run bundles the three selected modules with Electron, electron-store +and Electron toolkit imports rejected. It exercises actual files, ZIP extraction and byte equality, +ordered bundled-directory fallback, external import conversion/cleanup, persisted assignments after +service recreation and real discovery workers. Native directory and watcher capabilities are supplied +by the embedding host in that run; watcher closures drain twice. This proves the selected Skill +owner can run without Electron, not that the Harness or headless Session Host has moved. + +The matching actual application scenarios pass before/after migration: Skills catalog/file/extension +reads in 3.1/3.2 seconds and scan-worker events in 2.9/2.7 seconds. Disposable profiles now seed +their own managed Skills directory, and the catalog check requires delivered builtins. These are +functional checks. Evidence is under `.deepchat/rutis-g4-skill-host-acceptance/`; actual OS file-manager +presentation is not exercised. Process placement, native watcher ownership and storage behavior +are unchanged, so no full resource recapture or speed claim is required. G4 remains open. + +The preceding settings checkpoint `03ebe520b` passes required run `40277920388`, including +`pr-required` job `121426387843`, and package run `40277920393` with all six platforms and +`package-required` job `121430324373`. Both workflows finished before the Skill slice push. + +### Sync host and peer credential ports + +Replace the remaining synchronous Sync host/peer token wrapping and reveal callbacks with +acknowledged asynchronous crypto operations through the existing credential adapter. Preserve +machine-local private files, legacy ciphertext, the Linux basic-text refusal, pairing consent and +one-time-code checks. Do not cache plaintext or route these private tokens through general settings. +Fence cancelled pairing before persistence, cancelled transfers before authenticated requests and +stale connection reads across pairing replacement, forget and shutdown. Retain host transition +serialization and prevent a stopped generation from launching a connector after crypto returns. + +Characterize native peer token persistence and legacy wrapped-token reuse through the actual app +before the cutover, with intercepted fixture-host responses and an isolated profile. This is a +migration compatibility exception to implementation-first verification. The existing full loopback +host scenario remains the matching application comparison; no transport/process/storage-path +change calls for a full performance recapture. + +The default host/peer composition now awaits the existing async native credential adapter. Opaque +unwrap operations use the same maintenance/shutdown generation fence as other SecretStore work. +The private files, encoding, connector-token environment delivery and Linux basic-text policy stay +unchanged. Peer cancellation is checked after crypto and immediately before atomic pairing-file +replacement; connection reads reject changed credential generations. Host stop advances its +generation so pending wrap/unwrap replies cannot start a stale connector; normal restart remains +supported. Replaced synchronous encryption/decryption wiring is removed. + +Local acceptance passes 91 checks across eight files. New durable cases cover cancelled preflight +and token wrapping, saved-pairing preservation, credentials superseded by re-pair/forget/stop, +cancelled transfer unwrap, native failures and maintenance fences. Real loopback sockets/private +files remain in the transfer tests. A controlled local connector starts and restarts through the +async ports; stop during wrap/unwrap never launches its binary or enables its listener. + +The full actual-app host/backup/consent/restart scenario passes before/after cutover in 8.3/9.8 +seconds. A native peer-credential app scenario passes in 4.8/5.0 seconds: pairing responses omit +token material, private persistence is wrapped, an older synchronous ciphertext authenticates +after graceful relaunch without being rewritten, and forget removes pairing. Only the fixture host's +network replies are intercepted; application routing, private persistence and OS crypto are real. +These are functional checks, not a performance claim. Native OS crypto is verified on macOS arm64; +Windows/Linux vault behavior and real Cloudflare account connectivity remain unverified locally. + +Runtime build, app types, scoped crypto/peer/connector/E2E types, format, lint and i18n pass. +Evidence is under `.deepchat/rutis-g4-sync-crypto-acceptance/`. The preceding Skill checkpoint +`e739f5810` passes required run `40280089324`, including `pr-required` job `121432996814`, and +package run `40280089473` with all six platforms and `package-required` job `121437226872`. +Both workflows finish before this slice's push. G4 remains open for other Store/config/native ports. + +### Database security native boundary and migration commit + +The default bootstrap supplies the database path, owner-local metadata facade and asynchronous +native crypto. DatabaseSecurity imports neither Electron nor ElectronStore; unlock presentation and +quit remain desktop-owned. Native encryption remains available to authorized maintenance while +ordinary SecretStore operations are paused. The metadata facade moves with this owner at G5, +without synchronous cross-process settings RPC or another writable instance in Main. + +The per-file lock covers native preparation and commit. Wrapped target metadata is prepared before +closing the writer; rollback remains until target reopen and metadata persistence both succeed. +Runtime failure restores the original writer and metadata. Shutdown fences pending native results +before validation, secret return or mutation; pending preparation excludes another migration, +metadata repair or constructor recovery for that profile. SQLCipher copying, validation, physical +paths, metadata shape and startup file recovery remain compatible. Cross-file crash atomicity is +not established by this slice and remains conditional G10 work. + +Enable/legacy-unlock/change-password/disable was characterized in a disposable application profile +before cutover; this compatibility characterization is an implementation-first exception. The same +two actual-app scenarios pass before/after: database settings/native status in 4.2/4.2 seconds, and +encryption, legacy synchronous ciphertext unlock, wrong-password rejection, rekey, disable and four +graceful launches in 10.8/10.8 seconds. Portable settings survive every transition, encrypted file +headers differ from plaintext SQLite and disable restores a plaintext header. These are functional +comparisons without timing gates or a speed claim. + +Local acceptance passes 122 checks across four behavior files and seven real SQLCipher checks. +Durable regressions cover stopped decrypt/wrap, valid manual unlock with native failure, desktop +quit ownership, native wrap failure before closing the writer, unavailable native storage, per-file +exclusion, original-key recovery after target reopen failure and rollback of both database and +metadata after enable/rekey/disable metadata writes fail. The native file is classified in the +existing required SQLite lane; it uses actual temporary files, filesystem/path modules and +SQLCipher, without Electron or mock SQL. + +Runtime build, app/scoped types, native scope/type gates, format, lint and i18n pass. Evidence is +under `.deepchat/rutis-g4-database-security-acceptance/`. No process, IPC, database path or data-volume +change requires a full resource recapture. Actual OS crypto is exercised on macOS arm64; real +Windows/Linux vaults and power-loss recovery are unverified locally. G4 remains open. + +The preceding Sync checkpoint `7c04844a1` passes required run `40282974786`, including +`pr-required` job `121442092801`, and package run `40282974887` with all six platforms and +`package-required` job `121448087538`. Both workflows finish before this slice's push. + +### Scheduler native launch boundary + +The default Scheduler requires its existing local manager factory, and the manager requires an +asynchronous cancelable launch capability. Main resolves the approved helper entry and creates +the Electron utility process. The manager receives a plain facade for typed commands, data events, +PID observation and termination. It imports no Electron, filesystem or path API. The native module/ +path fallback and both optional service-constructor fallbacks are removed. Factory callbacks stay +local to the business composition; G5 transports capability requests, not these functions. + +A local abort signal cancels pending startup when the manager stops; epochs also reject late +completion from a non-cooperative launcher. Native failure settles once, removes startup listeners +and requests child termination. A post-launch ownership check prevents stale reconciliation after +stop. The timer helper, private protocol, native arguments/environment marker/service label, +packaging paths, Store authority, process count and restart/idle behavior remain compatible. + +Local acceptance passes 63 checks across the complete Scheduler group, native launch and +composition suites. Seven durable launch cases cover the plain command/event facade, pre-canceled +launch, stop during pending native startup, cancellation while fork returns, native exit/error +before spawn and stop superseding a completed launch. Existing real SQLite due claims, dispatch, +restart, stale completion and resume checks remain active with typed host fixtures. + +The same actual-app manual/timed execution, unique run identities, system resume, relaunch, +disable and shutdown scenario passes before/after in 9.3/7.9 seconds. Runtime build, app and scoped +Scheduler/launch/E2E types, format/check, lint and i18n pass. Evidence is under +`.deepchat/rutis-g4-scheduler-launch-acceptance/`. These are functional comparisons. No process, +IPC, storage-path or workload-volume change calls for a full resource capture or speed claim. +Native app execution is verified on macOS arm64; actual Windows/Linux startup and forced native +startup-failure delivery are unverified locally. Other helper/native/config boundaries keep G4 open. + +The preceding database security checkpoint `9e8fa2762` passes required run `40286578817`, including +`pr-required` job `121453178038`, and package run `40286578821` with all six platforms and +`package-required` job `121458134091`. Both workflows finish before this slice's push. + +Integrated baseline: `dev` at `207685009` retains upstream OAuth issuer/redirect compatibility and +Provider/default-MCP additions. Both Baizhi example lifecycle cases use the async credential port; +malformed-issuer OAuth fixtures retain otherwise valid token fields. The integrated tree passes +464 affected checks across 26 files, including all Scheduler, MCP, plugin lifecycle and changed +Provider suites. The final fixture subset passes all 31 checks. Actual-app cloud-browser credential +lifecycle and Scheduler scenarios pass together in 20.4 seconds (9.7/10.4 seconds). Only the local +synthetic MCP server is contacted by the cloud-browser fixture. Build, app/scoped fixture/E2E +types and required quality checks pass; no resource recapture or speed claim is introduced. + +### MCP OAuth native credential and browser boundary + +The default MCP credential store, interactive SDK provider/manager and enterprise identity owner +receive explicit private paths, asynchronous secure crypto and browser-open capabilities from Main. +Their native imports and default Store construction are removed. The actual service, client, +connection-error and diagnostics consumers await these contracts. Replaced synchronous wiring is +removed; the Store remains the same private file owner until G5. + +One bounded mutation queue serializes acknowledged writes and atomic interactive binding-key moves. +RAM publication follows persistence; native encryption and atomic-rename failures retain both old +RAM and disk, and encrypted temporary files are cleaned. Initial decrypt is single-flight. Twenty-four +normal writes leave eight reserved control slots; stop bypasses the queue and rejects pending native +results. Existing v1/v2 formats, size/record limits, Linux basic-text refusal, memory-only fallback +and settings-window credential permissions remain compatible. No extra plaintext cache is added. + +Public discovery metadata supplies the synchronous loopback issuer validator. Discovered identity +is finalized through one settings-owner compare-and-set against server ID, generation, binding hash +and endpoint. Profile mutation rereads current metadata after credential cleanup, preserves other +profiles and rejects replacement of the same profile. Server/profile leases fence delayed SDK, +refresh, browser, status and runtime effects after logout, configuration changes or shutdown. Plugin-owned stops use the existing supervisor; +its auth-scoped leases survive probe waits, integrity/recovery checks, adapter/native starts and +symmetric stops, with cleanup after canceled startup. Connection failures await OAuth classification and suppress late notifications after a stopped +startup. Legacy issuer and registered-callback behavior remain active. + +The disposable-profile compatibility characterization precedes the cutover as an implementation-first +exception. Matching real-app read-only MCP snapshots pass before/after in 6.8/8.7 seconds. Private +machine-credential save, legacy native ciphertext read without file rewrite, settings-window +isolation, graceful relaunch and durable removal pass in 9.4/10.3 seconds. The cloud-browser private +HTTP credential lifecycle passes in 10.7/14.8 seconds around the supervisor change. The final three +scenarios pass together in 34.2 seconds. Only a local synthetic MCP server is contacted; no external +account is used. These are functional comparisons without timing gates or speed claims. + +Local acceptance passes 657 checks across 33 affected MCP, plugin, native-adapter, composition, +route and CLI files. Durable protection covers serialized partial updates, single-flight decrypt, +encryption/rename rollback, atomic key moves, queue reserves, stopped crypto/browser results, +stale runtime stops, pending identity reads, profile-edit conflicts, settings CAS and Linux secure +backend policy. Five supervisor regressions cover canceled launch verification, adapter/native +startup, authentication restart and a stop waiting for another start. Existing SDK issuer, +registered-callback, enterprise refresh/subject and plugin lifecycle checks remain active. App and +scoped test/E2E types, normal and final runtime builds, format/check, lint and i18n pass. Normal +Provider and ACP registry refreshes are retained. + +A broader supplemental E2E selection runs 16 cases: 13 pass, two skip and the unmodified Provider +headers fixture times out waiting for `disabled-providers-toggle`, a selector absent from the +current renderer. Its failure and artifacts remain recorded in `plugin-baseline-e2e.log`; it is not +reported as a passing OAuth acceptance check. Final OAuth/plugin validation uses the three explicit +files above, without retrying that unrelated fixture. + +Evidence is under `.deepchat/rutis-g4-mcp-oauth-acceptance/`. No process, IPC, workload-volume or +storage-path change calls for a full resource capture or speed claim. G5 must frame legal large +native envelopes through bounded transfers without truncation. Native credential compatibility is +verified on macOS arm64; real Windows/Linux vaults and external identity-provider/browser flows +remain unverified locally. Remaining Store, configuration and desktop ports keep G4 open. + +The preceding integrated checkpoint `6a43b8f70` passes required run `40290640902`, including +`pr-required` job `121467164668`, and package run `40290641026` with all six platforms and +`package-required` job `121472173105`. Both workflows finish before this slice's push. + +### Provider OAuth credential and browser ports + +Main supplies explicit private paths, the existing async credential crypto port and browser-open +capabilities to both Codex and Grok credential/auth owners. Their Electron imports and default +native Store construction are removed. Existing v1 encrypted/plain-file envelopes, corrupted-file +backup, fsync/rename commits, load errors and file fallback remain compatible. These provider-specific +formats retain their existing policy, which differs from MCP's memory-only fallback. + +Actual status/events, refresh, logout, global initialization, SDK/direct requests and shutdown await +the selected owners. Grok's synchronous runtime-provider construction carries public configuration; +its synchronous token peek is removed. Both trusted direct requests and SDK requests use the existing +async fetch adapter. Original auth scopes span token acquisition, dispatch and single 401 refresh; +request cancellation or owner replacement cannot become API-key fallback. Ordinary refresh failure +retains API-key fallback. Endpoint restrictions, refresh sharing and account binding remain active. + +Delayed native, browser, discovery, refresh and status results are fenced across logout, account +replacement, canceled attempts and shutdown. A login completing while an old refresh is pending +invalidates that refresh before persisting the new account. Status queries may retry once after an +observed account transition; publication tickets suppress delayed older events. Each auth owner +admits at most 32 HTTP requests, with an 8 MiB declared/streamed body bound. Existing request +deadlines and cancellation cover response bodies as well as headers. + +Each private file owner serializes at most 24 normal and eight reserved control mutations; stop +bypasses admission. Files/envelopes are bounded to 16 MiB and normalized/decrypted token payloads +to 8 MiB before native transport or parsing. Only pending reads coalesce; no plaintext token cache +is retained. Read revisions reject results superseded by admitted writes. Failed deletion or +directory enumeration still attempts known artifact cleanup and rejects logout acknowledgement. +G5 must frame legal payloads within the existing bounded transfer contract. + +The disposable-profile compatibility characterization precedes cutover as an implementation-first +exception. The same real-app scenario passes before/after in 11.4/11.7 seconds (11.6/12.0 seconds +total). Four graceful launches verify plaintext and legacy native ciphertext without rewriting, +public status without tokens, authenticated cancel, durable logout and signed-out relaunch. Discovery +and revocation responses are synthetic; no external account is contacted. These functional results +have no timing gate or speed claim. + +The affected suite passes 1,213 checks across 79 files. Final contract acceptance passes 150 checks +across nine selected files. Shared actual-file regression cases replace two duplicate +mock-only Store suites: encryption/fsync/rename rollback, corrupt backups, unreadable-file recovery, +private permissions, serialized save/clear/save, coalesced decrypt invalidation, queue reserves, +failed logout cleanup, late refresh wrapping, stalled HTTP bodies and upstream size limits remain +covered for both providers. Account replacement, canceled discovery/polling, request-adapter +cancellation and current OAuth headers on each direct catalog request retain separate regressions. +App and scoped test/E2E types, normal and final +runtime builds, format/check, lint and i18n pass. Normal Provider registry refreshes are retained. + +Evidence is under `.deepchat/rutis-g4-provider-oauth-acceptance/`. No process, IPC, workload-volume or +storage-path change requires a full resource capture. Native credential compatibility is verified +on macOS arm64; real Windows/Linux vaults and external identity-provider/browser flows remain +unverified locally. Other provider/browser-window, configuration, shared maintenance and Store +boundaries keep G4 open. + +The MCP OAuth checkpoint `ae0475a65` passes required run `40307676246`, including `pr-required` +job `121523620575`, and package run `40307676364` with all six platforms and `package-required` +job `121530148783`. Both workflows finish before this slice's push; no rerun is requested. + +The Provider OAuth checkpoint `bc4ad41db` passes required run `40317513595`, including +`pr-required` job `121556618773`, and package run `40317513761` with all six platforms and +`package-required` job `121562643275`. No rerun is requested. + +### Shared database maintenance commands + +Main's maintenance entrypoint accepts six explicit data commands for backup-file import, named +local/cloud-backup import, encryption enable/change/disable and live schema repair. Actual peer, +local/cloud import and security-route consumers use these commands; their supplied operation +callbacks are removed. Existing result contracts, route permissions and credential policy remain +active. Commands carry operation arguments, never SQL, a database handle or a transaction closure. +Live repair now uses the same exclusive admission as import and encryption; startup recovery +remains a separate pre-owner operation. + +The owner retains pause, drain, operation, reopen, reconciliation and resume. It admits one operation +at a time, rejects entry during backup or shutdown and rechecks shutdown after draining before +dispatch. Preparation failures enter the existing recovery path. Move this complete owner with the +writer in G5. Internal Session/Tape/Memory transactions stay local to their business owners; +their closures are not candidates for individual RPC methods. Sync snapshot locks, staged-copy/ +config import connections and imported-source reads also stay inside the cohesive Store owner. +This contract does not create another normal writer. + +Matching disposable-profile application workloads pass before/after cutover: encryption, legacy +unlock, failed-password recovery, rekey, disable and four graceful launches in 12.5/11.2 seconds; +backup, rejected import, incremental restoration, overwrite removal, schema repair and durable +session restoration after relaunch in 4.8/5.2 seconds. Empty sessions use the real public creation +route without a Provider request. A final concurrency case holds native encryption preparation, +verifies repair/import are rejected, then releases native work and verifies repair succeeds. +All three migrated scenarios pass together in 19.6 seconds. No external account is contacted. + +Nine directly affected suites pass 158 checks under Electron Node mode with required native SQLite +and no skips. Before cutover, four native suites pass all 37 checks. Existing SQLCipher rollback, +backup snapshot consistency, imported data normalization, route and composition contracts remain +active. App and scoped E2E types, the normal build, format/check, lint and i18n pass. Evidence is +under `.deepchat/rutis-g4-maintenance-acceptance/`; normal generated catalogs remain retained. +No process, IPC or storage-path change requires a full resource capture, and timings are functional +observations without a speed claim. Physical shutdown/power-loss faults and full native workloads +on Windows/Linux remain unverified. Remaining configuration snapshots, normal-writer maintenance +fences and native/window owners keep G4 open. + +The maintenance-command checkpoint `5c7efdc1e` passes required run `40340119446`, including +`pr-required` job `121636236992`, and package run `40340119785` with all six platforms and +`package-required` job `121645123643`. No rerun is requested. + +### Ordinary request admission and maintenance drain + +`DatabaseRequestGate` owns the existing running/maintenance/failed admission state. Main consumers +read that one state; the independent boolean and partial route-prefix checks are removed. Ordinary +requests are refused by default during maintenance. Explicit native window controls, private security +status, static CLI status/version/capabilities and identity-validated CLI approval answers remain +available. Permission and caller checks still run in their existing owners. + +All finite renderer/CLI routes, CLI authorization, audio/OCR RPC, uploads, compute streams and +programmatic-tool dispatch participate in request ownership. Admission is checked again when +startup-scheduled work actually executes, using the original task cancellation signal. A running +handler has its own lifetime: the startup coordinator may reject its reply immediately on cancel +while that handler still runs. Its lifetime remains counted until actual completion. The gate +retains only an active count and one drain waiter, without payload or credential snapshots. + +After session/background cancellation, maintenance drains ordinary requests before any command +can close or replace the writer. The drain has a 30-second failure deadline. If work does not settle, +maintenance fails before dispatch and recovery retains the current database. Rejected requests release +ownership; failed maintenance stays closed to ordinary requests. Native presentation cannot reopen +ordinary admission. + +Maintenance command routes are excluded from their own drain. Cloud download and verified-peer +imports still use the exclusive operation owner. CLI `events.subscribe` counts only its initial +database snapshot; subsequent `TypedEventHub` forwarding holds no database lease. Authenticated +callers, approval settlement and the original cancellation identity remain intact. All request +lifetime callbacks stay local to the owner and are not transport payloads. + +The accepted maintenance scenario is extended with actual built-CLI Provider-list requests before +backup and after relaunch. It passes before/after in 5.0/4.9 seconds; encryption/legacy unlock, +wrong-password recovery, rekey and disable retain parity in 11.2/11.0 seconds. Native preparation +also verifies configuration/Provider and CLI requests are refused while window/security/CLI status +still works. A delayed cloud-secret write must settle before encryption dispatch; its interrupted +result never commits a secret. All four real-app scenarios pass together in 21.5 seconds in owned +disposable profiles, without contacting an external account. + +The affected CLI/route/app suites pass 616 checks across 36 files. Durable regressions cover +rejection cleanup, maintenance self-deadlock, native presentation, bounded drain/retry, queued +execution, a canceled +reply with an uncooperative running body and an open watcher after its initial snapshot drains. +App types, scoped new gate/dispatcher regressions, CLI and E2E types, normal/final runtime builds, +format/check, lint and i18n pass. The existing large legacy dispatcher fixture has unrelated type +drift outside these scoped type checks; its runtime suite remains passing. + +Evidence is under `.deepchat/rutis-g4-request-drain-acceptance/`. There is no process, IPC, +storage-path or workload-volume change requiring a full resource capture; timings remain functional +observations without a performance claim. This slice owns ingress. Background work detached after +an acknowledged request, remaining configuration snapshots and native/window dependencies still +need explicit ownership/lifecycle contracts before G4 closes. Physical shutdown/power-loss and full +native Windows/Linux application workloads remain unverified locally. + +### Startup work completion before maintenance + +The request-ownership checkpoint `99fdb0710` passes required run `40352516093`, including +`pr-required` job `121679711290`, and package run `40352516371` with all six platforms and +`package-required` job `121689282632`. No rerun is requested. + +The startup coordinator separates cancellation acknowledgement from actual callback completion. +`cancelAndDrainTarget` uses the existing in-flight inventory, including canceled older runs, and +waits for actual callbacks. Canceled pending tasks never execute and release their deduplication +entries. Immediate replies and the existing CPU/IO lane limits remain intact. There is no additional +task registry or transport protocol. + +Main maintenance awaits that operation for its startup target before pausing/recreating services or +changing the writer, then rechecks shutdown. A 30-second preflight failure restores ordinary +admission before any service teardown or database replacement. It cannot duplicate an active +startup owner. Successful maintenance schedules background migrations again; a failed preflight +does not reschedule unfinished work. The old cancellation-only maintenance wiring is removed. + +Eight affected coordinator, request, route, composition, logging and migration/usage files pass +139 checks under Electron Node mode with native SQLite required and no skips. Durable regressions +cover an old canceled run and its replacement, late callback failure, unrelated-target isolation, +queued cancellation and a bounded failure followed by another drain. The deadline uses fake time; +these checks enforce lifecycle behavior rather than a shared-runner timing budget. + +The same four real-app scenarios pass before/after in 21.6/21.3 seconds in owned disposable profiles. +Encryption, legacy unlock, wrong-password recovery, rekey and disable take 11.1/10.8 seconds; +CLI Provider-list, backup, incremental/overwrite import, repair and relaunch take 4.9/4.8 seconds. +Concurrent maintenance rejection and delayed native credential drain retain parity in 2.8/2.8 and +2.6/2.6 seconds. No external account is contacted. App/scoped types, the normal build, format/check, +lint and i18n pass. Normal build-generated Provider/ACP catalog refreshes are retained separately. +Evidence is under `.deepchat/rutis-g4-startup-drain-acceptance/`. There is no process, IPC or +storage-path change requiring a full resource capture, and no speed claim is made. + +Held startup callbacks and timeout behavior are verified at the real coordinator boundary; the +application comparison exercises its default maintenance wiring without a held-startup fault. +Other background effects after acknowledgement, shutdown ownership, configuration snapshots and +remaining native/window dependencies keep G4 open. Physical shutdown/power-loss and full native +Windows/Linux application workloads remain unverified locally. + +The branch incorporates `dev` at `5128430a9`. Its business files merge without conflicts; the two +generated catalog conflicts are resolved by the normal build scripts. The integrated tree passes +217 main checks across ten files and 291 related renderer checks across seven files, app types, +normal build, format/check, lint and i18n. The same four real-app scenarios pass in 23.7 seconds +(12.3/5.3/3.0/2.9 seconds). All samples remain recorded as functional observations, without a timing +gate or speed claim. Integration evidence uses the `dev-integration-*` logs in the same directory. + +### Shutdown ownership before writer close + +The startup-drain integration checkpoint `ec74d772c` passes required run `40437135796`, including +`pr-required` job `121970616884`, and package run `40437135856` with all six platforms and +`package-required` job `121975288948`. No rerun is requested. + +`DatabaseRequestGate` owns stopping admission and a completion-only promise for the actual maintenance +operation. The six typed commands, including verified-peer imports, use that owner; results and +credentials are not retained. Ordinary admission may resume before the final asynchronous setup +returns, but another maintenance owner cannot overlap it. CLI authorization retains its ordinary +lifetime. Only the two device dispatches that await shutdown before restart/reset are excluded from +their own drain; validation and the exclusive terminal-action claim remain unchanged. + +Shutdown closes request admission and background availability synchronously, invalidates native +security/credential work and cancels startup replies. Actual startup callbacks then drain after CLI +and OAuth ingress stop, before their service dependencies are disposed. Ordinary requests and the +actual maintenance operation drain before writer close. These critical waits are not best-effort +destroy steps: a bounded failure rejects teardown before closing the database or granting an action. + +Interrupted maintenance checks shutdown before reopen/reconciliation and before resuming services. +A remote initialization that finishes after shutdown is compensated before completion. Failed +recovery starts cleanup only after releasing the maintenance lifetime, avoiding teardown waiting on +its own caller. The cancellation-only shutdown path and recovery-side reopen during shutdown are +replaced by these default paths, without a feature flag or fallback. + +Forty-one affected main/CLI/security/route files pass 722 checks under Electron Node mode with native +SQLite required and no skips. Durable contracts cover concurrent request/maintenance completion, +restart/reset self-waiting, authorization lifetime, bounded failure/retry and final maintenance +exclusivity. App and scoped gate/E2E types, normal build, format/check, lint and i18n pass. The branch +includes `dev` at `2a8a36b3a`; its Provider integration is covered by the selected suites. Normal +build-generated Provider catalog refreshes are retained. + +The baseline launch/close and encrypted-reopen scenarios pass in 14.8 seconds (3.7/10.9 seconds). +The final seven-scenario application matrix passes in 39.1 seconds: launch/close 7.7 seconds; +encryption/legacy unlock/rekey/disable 10.7 seconds; CLI backup/import/repair/relaunch 5.0 seconds; +concurrent maintenance rejection 2.9 seconds; native credential drain 2.7 seconds; shutdown with a +held native credential 4.7 seconds; shutdown with held maintenance 5.0 seconds. Native quit closes +the window while work remains owned, permits release through the still-live Main connection, exits +with code zero, and reopens without an interrupted secret or encryption commit. All profiles are +owned/disposable and no external account is contacted. These whole-scenario durations include +startup and presentation; they are functional observations without a timing gate or speed claim. + +The E2E fixture retains the launched child handle across debugger disposal and recognizes an already +completed exit-zero process as graceful. Resource-measurement errors and forced-close behavior still +fail or report their existing outcomes. Accepted app evidence is `final-e2e.log` and `final-results` +under `.deepchat/rutis-g4-shutdown-acceptance/`; earlier driver probes remain there for diagnosis. +No process, IPC or storage-path change requires a complete resource recapture. Remaining detached +background owners, configuration snapshots and native/window dependencies keep G4 open. Full +Windows/Linux native application and physical power-loss/suspend scenarios remain unverified. + +### Provider configuration authority + +Provider model facts and user overrides require their database adapters at construction. The +runtime helpers have no legacy JSON defaults or mutable store-factory setter, and Provider settings +no longer discovers paths, creates a legacy model directory or reads the Electron version. Main +owns the native settings factory and supplies its version to both migration and Provider +initialization. Legacy import still completes before settings attach to the one writer; no +configuration writer, per-key IPC, schema or public route has been added. + +- [x] Replace the actual helper defaults and mutable store-factory wiring, then remove direct + Electron/path discovery from Provider settings and native-store construction from the + shared settings wrapper. +- [x] Validate affected configuration/migration/Provider contracts and compare actual application + model save, override, reset and reopen behavior against the accepted application artifact. + Process placement, IPC and storage layout are unchanged; no full performance recapture is + required. Catalog bootstrap and the other native/configuration owners still keep G4 open. + +Validation: 83 affected files/1,125 checks pass under Electron Node with native SQLite required; +the modified configuration fixtures and E2E pass scoped types. Normal build, app node/web types, +format, lint and i18n pass. A temporary bundled-import probe loads the settings wrapper and +Provider model helper with both `electron` and `electron-store` forbidden. This does not establish +that the complete Provider graph is portable: catalog bootstrap still imports Electron. + +The immutable `38306fcda` application artifact passes the new model-store E2E in 6.9 s. The migrated +application passes the same case in 6.6 s: atomic custom-model/override save, graceful exit/reopen, +persisted values, reset/removal, another reopen, and unchanged legacy JSON contents throughout. +All five selected application cases pass in 43.8 s, including launch, obsolete-plugin repair, +plugin-startup failure and encrypted legacy unlock/rekey/disable. These are functional observations, +not a speed claim. Evidence and original diagnostic attempts remain under +`.deepchat/rutis-g4-provider-config-acceptance/`. Full native Windows/Linux application behavior +remains unverified locally. G4 remains active for the remaining configuration and host contracts. + +### MCP and ACP configuration bootstrap + +Both settings classes require storage at construction. Main creates the existing named files +from pure default builders. These retain +platform-specific MCP defaults, secret-operation dependencies, startup normalization, legacy +import and database attachment order. ACP's live Agent mutations already use the unified database +repository. Capture registry/manual/install legacy data into an isolated read-only snapshot at +attachment and remove unused legacy mutation entrypoints. Enabled/version/shared MCP selections +remain database-backed. G5 transfers configuration and its database owner together without a +writable Main shadow or per-key IPC. + +- [x] Replace default construction and all real callers, fixtures and exports; preserve MCP + configuration transactions and ACP migration behavior. +- [x] Compare actual MCP and ACP configuration changes across graceful exit/reopen, validate + affected settings/migration/plugin contracts, and verify the settings modules load without + Electron. No process, IPC, layout or storage schema change requires a full resource capture. + +The attached ACP adapter copies legacy input, returns isolated read values and rejects legacy +writes/deletes. A rejected mixed-key batch fails before changing database settings. Seven unused +legacy mutation/access helpers have been removed; actual Agent mutations continue through the +unified repository. Normalization still runs before database attachment and its migration snapshot. +Legacy config import reads the extracted source files and applies database payloads, so it does +not require an active writable JSON adapter. + +Validation: 30 native affected files/380 checks and the separate config-import file/six checks pass +under Electron Node with native SQLite required. Nine plugin files/201 checks pass under normal +Node, matching their child-process fixtures. Modified settings/plugin/E2E scoped types, app node/web +types, normal build, format, lint and i18n pass. A temporary bundled probe constructs both settings +classes and defaults with `electron` and `electron-store` forbidden. The initial combined run used +Electron's executable for Node-only hook fixtures and failed six child-process cases; the correct +Node run passes. That diagnostic and the corrected snapshot-test fixture failure remain in the +acceptance directory. No runtime timeout or environment guard has been relaxed, and no owned hook +process remains. + +The normal build refreshes the generated Provider catalog; three catalog files/50 checks pass. +The generated data is retained in its own commit so the configuration change can be reviewed +independently. + +The immutable `ec82c12b4` application passes the same new configuration E2E in 6.6 s; the migrated +application passes in 6.9 s. It adds a disabled MCP server and manual ACP Agent, updates Agent +configuration and shared MCP selection/global availability, reopens and checks persistence, +removes/resets the configuration, reopens again and verifies absence. Legacy MCP/ACP JSON bytes +remain unchanged. All three selected app cases pass in 27.0 s, including encrypted database +legacy unlock/rekey/disable and native MCP credential/settings-window ownership. These are +functional results without a timing gate or speed claim. Evidence is under +`.deepchat/rutis-g4-mcp-acp-config-acceptance/`. Normal generated catalog refreshes are retained +separately. Catalog bootstrap, other native dependencies and the cohesive writer move keep G4/G5 +open; full native Windows/Linux application behavior remains unverified locally. + +### Provider catalog and request bootstrap + +Remove native dependencies from Provider catalog/settings and HTTP execution bootstrap as one +dependency group. Main supplies the profile/catalog resource paths and default request headers; +the existing locale capability remains live. Package the one built-in catalog outside ASAR for +ordinary Node access. Separate Copilot token exchange from native device-login presentation. +Catalog import stays inert until explicit bootstrap. Retain existing cache ownership, privacy, +TTL, payload limits and request/error behavior. The existing Main idle Provider task owns automatic +refresh, so maintenance cancellation/drain also fences it. Stop and drain all refreshes at shutdown +and fence late writes/publications. + +- [x] Replace actual bootstrap/header/token imports, every affected caller and fixture, and + packaged resource wiring; remove the old native paths and header helper. +- [x] Validate catalog refresh/lifetime, HTTP metadata and token/security contracts, compare + matching real-app operations, verify ordinary Node imports/resource access and run the + affected application/static/package checks. No speed claim is part of this slice. + +The affected Provider, Device, image-cache, package and startup/app-boundary suites pass 1,165 +checks across 81 files with native SQLite required. Durable checks cover inert explicit bootstrap, +unchanged request metadata, token sanitization, absolute image-cache roots, three-platform catalog +resource validation, real refresh drain and late response/body fencing on shutdown or task +cancellation. Ordinary Node imports of Provider settings, Copilot HTTP and the AI SDK Provider pass +with Electron and electron-store forbidden. App/scoped types, normal build, format, lint and i18n +pass. Normal ACP registry refreshes remain in a separate generated-data commit. + +The immutable `7fb11102f` application passes the matching catalog/request E2E in 2.4 s; the migrated +application passes in 2.1 s. Four migrated application checks pass: catalog/header bootstrap, +held-refresh graceful shutdown/reopen without late cache writes, model persistence without legacy +JSON writes, and encrypted legacy unlock/rekey/disable. Evidence is under +`.deepchat/rutis-g4-provider-bootstrap-acceptance/`. These are functional observations. No process +topology, cache schema or data-volume change requires a performance capture, and no performance +suite was repeated. The one built-in catalog moves outside ASAR; package/resource checks cover +ordinary Node access. The existing `7fb11102f` required and all six package CI checks pass; the new +checkpoint still needs its own CI results. G4 remains active and G5 has not started. + +### ACP execution bootstrap + +Remove native version/home/temp lookups from ACP process, terminal and session-persistence owners +as one dependency group. Supply plain bootstrap through the actual lazy runtime composition; make +the existing path-expansion/default-path helper pure and retarget its ACP and MCP callers. Keep +the current launch, containment, protocol, persistence and process cleanup behavior. +The complete Provider import graph also requires passing Ollama's existing TLS debug policy from +Main through its environment port; retain the existing host decision and secure production default. + +- [x] Replace actual bootstrap/helper wiring and remove direct Electron dependencies and unused + native helper methods; do not retain an implicit native construction fallback. +- [x] Compare matching real-app ACP execution on immutable and migrated builds, validate affected + execution/session/terminal/MCP contracts and ordinary Node imports, then run required static + and package checks. No process/IPC/data-volume change or performance claim is selected. + +Main supplies application version and absolute home/temp roots through the existing lazy ACP owner, +client and process manager. Terminal and persistence owners receive their roots explicitly. The +shared helper now exports two pure path functions; its singleton and two unused native methods +are removed. Windows process hiding depends on the target platform rather than Electron presence. +Ollama receives the unchanged host TLS decision through the Provider environment. A host-denied +pull stays secure even if the backend's ambient debug flag is set; an explicit host grant allows +the existing debug behavior. The app's native TLS policy helper remains with Main. + +The final affected Electron suite passes 1,454 checks across 108 files with native SQLite required; +the real PTY auth case separately passes under ordinary Node, for 1,455 checks across 109 files. +The generated catalog suites also pass 59 checks. App types, normal build, final app build, +format/check, lint and i18n pass. Bootstrap validation, supplied terminal roots, protocol version, +session restoration, PATH precedence and TLS host-policy checks retain durable coverage. Four +ACP/core imports and the complete ProviderRuntime module graph load under ordinary Node with +Electron and electron-store forbidden. This proves import portability, not a composed headless host. + +The immutable `a217ff4e5` app passes the matching ACP protocol/structured-answer E2E (7.1 s); the +final app passes (7.7 s). The final group passes three cases in 14.6 s: ACP owning-prompt/child-output +behavior, Provider catalog/header bootstrap, and held-refresh shutdown/reopen without late writes. +ACP settings routes and encrypted legacy unlock/rekey/disable also pass in the preceding app +selection. Timings are functional observations. No performance suite or resource capture was run. +Evidence is `.deepchat/rutis-g4-acp-runtime-acceptance/`; immutable baseline hashes are verified. +Initial failures retain their logs: two tests spied on the deleted helper, and the exact-environment +PTY case needed its ordinary Node executable. No production timeout or security rule was relaxed. +The broader Provider import probe exposed the native TLS helper; passing its host policy removes +that dependency. Normal generated catalog data is retained separately. G4 remains active, and +native Windows/Linux full-app behavior and the cohesive G5 backend/writer move remain unverified. + +### ACP registry ownership and bootstrap + +Main supplies the registry's explicit profile/resource directories and native icon-fetch capability. +AgentSettings receives that owner directly. Load local catalog data without detached network work; +automatic refresh belongs to the existing Main startup task, and manual refresh retains route +ownership. Stop, cancel and drain before database disposal; fence late cache/icon writes, repository +updates, Provider refresh and events. Package one registry/icon snapshot outside ASAR. + +This migration characterizes actual refresh/icon persistence on the immutable application before +implementation, because its network, cache and repository effects must retain one owner. + +- [x] Replace actual registry construction, asset paths, startup/shutdown ownership and refresh + cancellation; remove native imports, cwd fallback and detached initialization. +- [x] Compare real refresh/icon/reopen behavior, validate late replies and task/shutdown fencing, + preserve privacy/TTL/URL/SVG contracts and run affected static/native/package checks. + +Main constructs the registry with absolute profile/built-in roots and a URL-checked, cancelable +native icon-text capability. The service and AgentSettings import no Electron or electron-store; +there is no cwd resource fallback. Initial loading is local. The existing idle Provider task owns +automatic registry and model-catalog refresh concurrently and waits for both to settle before +ending. Registry refresh is single-flight through its service and repository/Provider/event +continuations. Shutdown aborts it and drains actual work; task cancellation fences every late +effect without stopping the owner needed after maintenance. A manifest becomes committed after +its icon work settles, and cancellation cannot prune icons or replace the acknowledged catalog. + +The affected native suite passes 106 checks across eight files, including held headers/body/icon +responses, caller cancellation and fresh reuse, actual drain, late repository/Provider completion, +privacy/TTL, SVG/checksum normalization and three-platform package asset validation. App/scoped +regression types, normal build, format/check, lint and i18n pass. Registry, AgentSettings and the +complete ProviderRuntime module graph load in ordinary Node with Electron/electron-store forbidden. +The one bundled registry/icon snapshot moves outside ASAR; the package hook validates ordinary +files for the manifest and all declared registry icons. Unused implicit-resource constants are +removed. Normal builds introduce no generated-catalog diff in this slice. + +The immutable `45cbdf4c0` app passes the matching refresh/icon/persistence E2E in 4.6 s; the migrated +app passes in 4.4 s. Four selected app scenarios pass in 27.6 s, including real registry refresh, +sanitized icon cache, graceful reopen, unchanged legacy JSON, held-refresh shutdown/reopen without +late catalog or Agent writes, ACP prompt/child-output behavior and encrypted legacy unlock/rekey/ +disable. Timings are functional observations; no performance suite or resource recapture was run. +Evidence is `.deepchat/rutis-g4-acp-registry-acceptance/`. Full native Windows/Linux application +behavior is unverified locally, and the new packaging layout needs its exact-head CI evidence. +This slice owns registry refresh. ACP installation/prewarm jobs and remaining host/writer +dependencies remain G4 work before the cohesive G5 cutover; G4 is not complete. + +### ACP installation and launch preparation ownership + +Own explicit installation, enable-triggered preinstallation, repair, uninstall and binary launch +preparation through one installation resource. Coalesce identical work, serialize conflicting work +for one Agent, propagate cancellation through download/body/extraction boundaries and await real +settlement. Stage and validate a replacement before changing the installed directory. Track the +repository/Provider continuations, fence stale descriptors and preserve the previous acknowledged +install state on canceled work. The existing database admission gate also governs background work; +its local callback moves with the Store in G5 rather than becoming synchronous IPC. + +Characterize actual binary install/reopen behavior on the immutable application before replacement, +because the same cache files and persisted install state must retain a single owner. + +- [x] Connect installation/launch cancellation, actual drain and maintenance/stop admission to + the existing service and callers; preserve prompt enable acknowledgement and archive guards. +- [x] Compare installation/reopen, canceled late bodies, failed repair and maintenance recovery in + the app; run relevant native/static checks and remove the unowned background continuations. + +Install, repair and implicit launch share the existing physical installer. It admits at most 32 +physical installations, coalesces matching work and queues repair behind an ordinary install. +Uninstall reserves its Agent before awaiting canceled work. The Settings owner tracks actual +installation, launch, uninstall and derived Provider lifetimes; stop and database maintenance drain +them before writer teardown. Descriptor changes cancel prior work. Cancellation restores only an +unchanged pending SQL state and cannot publish a late installed/error result. Main supplies the +existing local database admission check; enable still acknowledges before background installation. +Replacements use validated staging and the existing directory activation/rollback helper. A failed +repair retains the original binary; ordinary interrupted activation can restore its `.prev` tree. + +Validation: **244 checks across 20 files pass**, with native SQLite required for the 16-file +affected group (176 checks); four generated-catalog files add 68 checks. App/scoped types, normal +build, final application build, format/check, lint and i18n pass. Installer, AgentSettings and the +complete ProviderRuntime module graph import in ordinary Node with Electron/electron-store +forbidden. The build-generated Provider catalog is retained separately. + +The immutable `3ede3013a` app passes the matching binary install/repair/reopen case (4.2 s); the +candidate passes (4.6 s). Six real-app cases pass in 34.8 s: acknowledged binary persistence, +held-repair graceful shutdown with no late replacement/state commit, database maintenance drain, +failed repair preserving the old binary, post-maintenance repair/reopen, registry refresh/shutdown +and encrypted legacy unlock/rekey/disable. The maintenance case also passes with public uninstall +and persisted `not_installed` verification (5.2 s). These are functional observations without a +timing gate or speed claim; no performance suite/resource capture was repeated. Immutable baseline +hashes match the accepted registry candidate. Evidence is +`.deepchat/rutis-g4-acp-installer-acceptance/`. + +The preceding `45cbdf4c0` required run `40458182691` and all six packages `40458182714`, including +package-required `122042123850`, pass before the registry checkpoint is published. The published +registry head `3ede3013a` passes required `40462217782`/barrier `122051412704` and all six packages +`40462217827`/barrier `122055725559` before this checkpoint is published. This installer checkpoint +still needs its own CI results after publication. Full native Windows/Linux app +behavior, power loss across filesystem activation/SQL publication and moving synchronous archive +extraction out of Main remain unverified. Remaining configuration/host/writer contracts keep G4 +active; G5 has not started. + +### Background execution helper ownership + +Replace the global Electron-aware proxy with one explicitly composed background execution owner. +Supply its typed helper command/event/lifecycle capability from Main, using the existing Scheduler +adapter pattern. Inject that owner into Bash, Tool and Skill consumers. Preserve the dedicated +helper, child/session leases, authority checks and output/Skill-tree cleanup. Fence pending startup +and post-authority continuations during shutdown, await real callbacks and reject terminal restart. +Do not add another process supervisor or generic launch endpoint. + +Characterize the real app's exec/process feedback and child cleanup before replacing ownership, +because command dispatch and helper lifetime must remain identical across the cutover. + +- [x] Compose the helper capability and one owner; replace all implicit singleton/native fallback + wiring and retain process/session recovery contracts. +- [x] Verify ordinary exec/process feedback, cancellation, held startup/authority shutdown and + restart boundaries; compare the actual app and validate affected callers and packaging. + +Main composes one BackgroundExecService with an absolute approved helper entry and a cancelable +native launch adapter. Bash, process tools and Skill execution receive that same owner. The owner +retains actual startup, authority callbacks, RPC and rejected Skill-tree cleanup; terminal stop +cannot restart the helper. Late events from an old helper cannot affect its replacement. Root +shutdown first cancels hydrated Harness generations without reading the database, then stops +command admission. Actual helper/callback cleanup drains before Skill owners and the writer close; +a failed drain cannot become a logged-and-continued teardown step. + +Validation: **988 checks across 36 affected files pass** under Electron with native SQLite required. +App and new regression/E2E types, normal/final application builds, format/check, lint and i18n pass. +The existing ported legacy fixtures retain 107 type errors on both an isolated immutable baseline +source tree and the candidate, with no new diagnostics; these are not represented as passing typed +fixtures. Runtime suites pass. No check, timeout or native-import guard was relaxed. + +Background execution, Skill execution, ProviderRuntime and the actual Harness entry import in +ordinary Node with Electron/electron-store forbidden. The 350-module Harness graph has no native +imports; this verifies module loading, not a composed headless backend or a relocated writer. + +The immutable `edfb8ab43` app passes matching exec/process completion and active-child shutdown/ +reopen cases (5.1/4.8 s); the final candidate passes (5.3/5.0 s). Six final app cases pass in 34.3 s, +including held helper startup shutdown with no command or subsequent model round, child exit and +persisted message recovery, encrypted legacy unlock/rekey/disable and shutdown while native +credential/database maintenance work is held. The held-start case exposed and verifies the early +in-memory generation cancellation requirement. Timings are functional observations only; no +performance/resource suite was repeated and no speed gain is claimed. Evidence is +`.deepchat/rutis-g4-background-exec-acceptance/`. + +The installer baseline `edfb8ab43` passes required `40464593274`/barrier `122059109164` and all six +packages `40464593265`/barrier `122065276883` before this checkpoint is published. Background head +`12a73394f` passes required `40467842999`/barrier `122069829980` and all six packages +`40467843051`/barrier `122075929024`. Native Windows/Linux full-app behavior, Code Mode and other +desktop capability ports, remaining Store ownership and the cohesive backend move remain open. +G4 is active; G5 has not started. + +### Code Mode helper and callback ownership + +Supply the existing Code Mode manager with a required approved helper capability. Keep native +launch configuration, events and bound RSS reads in Main; retain VM/protocol/journal bounds and +session state semantics. Reserve pending startup before any await, track actual callback lifetime +through cancellation and close/drain it before Store teardown. Coalesce RSS reads and fence old +cells without adding a generic process supervisor. + +Characterize actual Code Mode nested execution and isolated VM globals before the +host boundary changes, because tool feedback and journal commitment must remain compatible. + +- [x] Inject the host capability through ToolService; remove native defaults and own pending + startup, sampling and nested callbacks across cancel/shutdown. +- [x] Compare real Code Mode execution/reopen and held readiness shutdown; verify concurrency, + memory limits, lifecycle faults and affected Tool/Harness contracts. + +Main supplies the approved absolute helper entry and a plain host channel through ToolService. +The adapter retains the restricted environment, 64 MiB heap and unsigned-library policy; RSS +reads are bound to that helper rather than an arbitrary PID. The manager retains the existing +READY deadline, heartbeat, source/output bounds, 512 MiB hard RSS limit, execution journal, +permission retry and frontend-specific state semantics. Native imports and path guessing are +removed from the manager. + +Session startup is reserved before asynchronous effects. Cancellation returns promptly while +the owner retains actual startup completion and compensates a late host. Stop closes admission; +shutdown drains actual nested-tool and coalesced RSS callbacks before Skill/Store teardown. +Aborted callbacks cannot publish a late result. Main invokes this critical drain directly rather +than logging a failure and continuing to close the writer. + +Validation: **547 checks across 10 affected native suites and 23 generated-catalog checks across +two files pass**. App and scoped native-adapter/manager/E2E types, normal/final application builds, +format/check, lint and i18n pass. Regression cases cover concurrent startup, canceled startup +reuse, late host compensation, uncooperative nested callbacks, actual RSS drain/coalescing and +the unchanged READY and RSS limits. The touched manager fixtures also pass strict typechecking. + +The immutable `12a73394f` app and candidate pass the same nested execution, cell-global isolation +and persisted-message reopen scenario; the final exact-assertion comparison passes in 6.4/5.2 s. +Seven candidate app cases pass in 33.9 s, including held Code Mode READY shutdown with helper exit +and no subsequent tool/model round, background execution completion/shutdown/held startup and +native credential/database-maintenance shutdown holds. The function frontend is exercised in +the app; Codex store/yield/permission semantics remain covered by the VM/manager suites, without +claiming an authenticated Codex account E2E. + +The manager, actual 350-module Harness entry and ProviderRuntime import in ordinary Node with +Electron/electron-store forbidden and no native imports. This establishes module loading, not a +composed headless backend or a relocated writer. Evidence and artifact hashes are in +`.deepchat/rutis-g4-code-mode-acceptance/`. The normal generated Provider catalog refresh is retained +separately in `f560f941e`. No performance/resource suite was repeated, no timing gate was added and +no speed gain is claimed. The preceding `12a73394f` CI completed before publication. Code Mode head +`32f457ff7` passes required `40473854228`/barrier `122089643930` and all six packages +`40473854200`/barrier `122095514524`. +Remaining desktop/Store boundaries keep G4 active; G5 is unstarted. + +### Agent Tool host inputs + +Supply AgentToolManager's existing desktop port with explicit absolute temporary/profile-temporary +roots and an asynchronous image-dimension capability. Keep nativeImage and app paths in Main; +remove native imports from the business manager. Preserve the existing runtime directory allowlist, +default-workspace creation/fallback, file/symlink checks, image metadata and vision selection. +Recheck cancellation after the host result and before cache/Provider effects. Native image objects +and generic path/root discovery must not cross the boundary; do not introduce a second supervisor. + +- [x] Replace native defaults with required host inputs at the actual ToolService composition. +- [x] Compare real image metadata/text-read/reopen behavior against the accepted app; validate + injected roots, metadata failure, cancellation and affected Tool/Harness contracts. + +Main supplies the existing system/profile temporary roots and native image-size probe through the +required desktop port. The manager validates absolute roots and uses them for the unchanged read +allowlist and default-workspace fallback. It awaits dimensions, preserves unknown-metadata failure +behavior and checks cancellation before preview caching or vision work. The probe returns only +numeric dimensions. The settings-window consumer keeps its original three-method subset rather than +receiving image/filesystem capabilities. Seven obsolete native mocks are removed from pure Tool +suites; ToolService still imports the separate plugin policy Store and is not claimed portable. + +Validation: **689 checks across 14 affected native files and 23 generated-catalog checks across +two files pass**. App and new E2E types, normal/final application builds, format/check, lint and +i18n pass. Durable cases cover supplied roots, default workspace, approval for unrelated files, +metadata failure and cancellation of a delayed image result before cache/Provider effects. The +existing vision preference, journal, symlink and permission suites remain green. No production +guard or timeout was relaxed. + +The same actual image metadata, workspace read and persisted-message reopen scenario passes on +immutable `32f457ff7` and the final candidate (both 5.3 s). Four final app cases pass in 21.0 s, +including background process feedback, Code Mode nested execution and held-READY helper shutdown. +All profiles are disposable and the Provider fixture is local. Artifact hashes and evidence are in +`.deepchat/rutis-g4-agent-tool-host-acceptance/`. +The normal generated Provider catalog refresh is retained separately in `c4f306e01`. + +AgentToolManager's 126-module graph, the actual 350-module Harness entry and ProviderRuntime import +in ordinary Node with Electron/electron-store forbidden. This proves module loading, not a composed +headless backend or relocated writer. Native image decoding retains its current synchronous Main +placement. No performance/resource suite was repeated and no latency or memory gain is claimed. +The preceding `32f457ff7` required and six-platform CI completed before publication. This target +retains the accepted CI evidence below. Remaining configuration and native/Store ports keep G4 +active. G5 has not started. + +Agent Tool head `f0ca555fe` passes required `40478827867`/barrier `122106538990` and all six +packages `40478827913`/barrier `122113041876` before the policy checkpoint is published. + +### Portable application settings file owner + +Make the existing application settings file constructible by the future backend without Electron. +Keep its storage engine, defaults, filename, migration behavior and database attachment contract. +Main supplies required absolute profile/home paths and the current application version. + +- [x] Connect the portable factory to actual Main startup and remove the old app-settings native + factory. Keep the same SettingsStore instance and database-backed routing after attachment. +- [x] Validate legacy JSON, unknown fields, dot paths, corrupt-file rejection, defaults, database + routing and real configuration/credential persistence across graceful relaunch. + +The factory uses `conf@15.1.0`, the exact engine already resolved beneath ElectronStore 11.0.2. +It is now an explicit dependency; the lockfile adds only its root importer entry and resolves no +new engine/package version. `configName`, absolute `cwd` and `projectVersion` replace the native +wrapper's name/path/version mapping. The same defaults remain in the existing settings module. +No replacement JSON serializer, file format, watcher, cache, writable shadow or generic Store RPC +is introduced. Other desktop-owned ElectronStore files retain their existing owners. + +Sensitive and portable settings still route through AppSettingsDbBackedStore after the existing +migration and database attachment. Machine-local keys retain the same file adapter. Main remains +the active writer in G4; the portable constructor does not establish the G5 exclusive handoff or +allow Main and the backend to write different keys concurrently. DesktopSettings/AppSettingsService +still need native observation/effect and acknowledged snapshot/mutation contracts. OS login state +comes from the native API; it is not a persisted preference to copy into a second settings store. + +Validation: **184 checks across 16 affected native files, eight renderer icon checks and 14 +generated-catalog checks across two additional files pass**. Native SQLite is required for the +affected native group. Coverage includes +the six real-file/bootstrap contracts, config migration, attached settings/metadata, private crypto, +database security, public settings, onboarding and Sync settings. App and modified unit/E2E types, +the normal build, format/check, lint and i18n pass. The new file-contract suite explicitly uses the +real filesystem instead of the repository's default fs mock. + +The immutable `cd7cc392f` app and integrated candidate pass the same final legacy/bootstrap/credential/relaunch +workload (5.8/4.8 s). Both use the current package version and resources. It uses an owned profile with an old version, custom workspace and unknown +extension data, updates file-backed language/UI/limits and a database-backed preference, then +reopens with each value retained in its original store. A disabled loopback cloud configuration +keeps its secret wrapped on disk and readable after relaunch without rewriting the ciphertext. +No external service/account or OS login registration is modified. The comparison restores and +hash-verifies the candidate output after the frozen-baseline run; no output swap remains active. + +The settings factory graph (three modules) and database adapter (two) load with Electron and +electron-store forbidden at build/runtime. A real factory creates and writes the legacy file in +ordinary Node. This establishes a portable configuration owner, not a composed headless Harness +or relocated writer. No performance/resource capture or speed claim is added. The branch includes +`dev` at `b62eb0d2b`, including CloudGPU and `1.1.4-beta.7`. Its new Provider fixtures receive the +same required host environment as existing providers; no production fallback is added. App/scoped +types, build and matching application evidence validate this integrated source. The normal build's +Provider catalog refresh is retained. Evidence is `.deepchat/rutis-g4-settings-owner-acceptance/`. + +This checkpoint needs its own exact-head CI after publication. The Hook baseline `cd7cc392f` +passes required `40623544678`/barrier `122604697934` and all six packages `40623544682`/barrier +`122608652902` before this settings-owner checkpoint is pushed. G4 remains active; G5 is unstarted. + +### Hook metadata and command lifetimes + +Keep Hook configuration, subscriptions, enrichment and command ownership together. Supply the +application version from Main for both normal and test payloads. Diagnostic acknowledgement must +not release a native command that still owns open stdio or an accepted Session lookup. + +- [x] Remove HookService's Electron dependency and wire its required application version in the + actual Main composition, preserving the version-1 payload and command environment. +- [x] Retain child ownership through native close, including after timeout/error diagnostics; + stop admission and await accepted enrichment and child completion before Store teardown. +- [x] Compare the existing configuration/test/relaunch workload and verify actual shutdown with + a real model-triggered Hook whose descendant keeps the command's stdio open. + +The existing owner keeps per-session event eligibility/order and concurrent command execution. +Stop sends the existing shell termination signal and waits for actual child/stdio closure and +accepted enrichment. A 30-second drain failure rejects maintenance/shutdown; restart rejects while +prior work remains owned. New explicit test commands reject after stop. Main awaits Hook stop +directly before later teardown, so its failure cannot be swallowed by best-effort cleanup and +permit database closure. Command timeout diagnostics, output bounds/redaction, configuration +persistence, accepted subscriber identity and stopped-event suppression remain compatible. + +Validation: **530 distinct checks across five affected files pass**, with native SQLite required +for the affected group. The final four-file group passes 515 checks, and the RuntimeHookSink's +15 checks pass on the same production sources. App and modified Hook/unit/E2E types, the normal +build, format/check, lint and i18n pass. Durable cases cover both supplied-version payloads, +timeout/error acknowledgement before native close, blocked late test commands, failed drain/restart, +late enrichment suppression and successful restart after prior work finishes. + +The immutable `b7a50e3aa` app and candidate pass the same configuration/test/version/relaunch +workload (6.7/7.0 s). A separate baseline characterization reproduces premature shutdown: the +application closes while the real Hook descendant remains alive and holds stdio. The candidate's +same native-lifetime regression passes in 4.8 s: actual loopback Provider execution emits a +SessionStart payload with the native application version, shell termination leaves the descendant +holding stdio, the app remains alive until the fixture releases that child, shutdown is graceful, +and relaunch does not replay the Hook. Both candidate cases pass in 11.9 s. The baseline failure +and traces are retained. The existing direct-shell signal policy is unchanged; this cutover adds +no detached process group or descendant-tree termination guarantee. Missing close fails the drain. + +HookService (one module), HookSettings (two) and routes (85) load in ordinary Node with +Electron/electron-store forbidden at build/runtime; a plain Hook owner also starts/stops there. +This is module-loading/owner evidence, not a composed headless backend. No schema, public route, +process placement, dependency, layout or performance claim changes. No performance/resource suite +is repeated. Evidence and immutable build hashes are in `.deepchat/rutis-g4-hooks-acceptance/`. + +Published Hook head `cd7cc392f` passes required `40623544678`/barrier `122604697934` and all six +packages `40623544682`/barrier `122608652902`. +The Project baseline `b7a50e3aa` passes required `40621219551`/barrier `122597419315` and all six +packages `40621219651`/barrier `122602071741` before the Hook checkpoint is pushed. G4 remains +active and G5 is unstarted. + +### Project bootstrap and native capabilities + +Keep Project snapshots, settings revisions and environment transactions with their existing owner. +Supply native system/profile roots, optional Documents/Home roots, directory selection and path +opening from Main. Do not introduce awaited native discovery inside a synchronous snapshot. + +- [x] Wire the required host into the actual ProjectService constructor and remove its Electron + import/native directory discovery. Retain the existing shared database and settings owner. +- [x] Validate managed-workspace fallback, native failures, environment mutations, Session clearing, + persisted revisions and the matching baseline/candidate application workload. + +Main resolves the native directories once at bootstrap and preserves diagnostics for unavailable +Documents/Home paths. Project rejects relative host roots and captures normalized candidates; +later host-object changes cannot alter the roots of a versioned snapshot. Candidate order remains +Documents, Home, then the profile's managed workspace. Existing custom defaults, lazy preparation, +directory-creation fallback and temporary-path classification remain compatible. Main supplies the +same DeviceService and shell operations through required asynchronous capabilities. Native picker +cancellation/failure does not commit a selection; shell error strings still reject the operation. + +Project removal still clears Session assignments, project/preferences and the environment projection +in one database transaction. Snapshot/settings reads remain in one uninterrupted turn, revisions +persist through the same SettingsStore, and no second writer/cache, public route, process, IPC or +schema is added. G5 must move this owner with the shared database/configuration authority and carry +only native selection/open operations over the desktop capability boundary. + +Validation: **249 checks across nine affected files and 41 generated-catalog checks across four +files pass**, with native SQLite required. App and modified unit/fixture/new E2E types, the normal +build, format/check, lint and i18n pass. Durable host cases cover absolute roots, unavailable native +directories, stable captured candidates and selection failure without a mutation. Existing snapshot, +transaction, concurrent selection, Session lifecycle and authenticated route checks remain green. + +The immutable `c6758fe56` app and candidate pass the same Project/native-boundary/persistence +workload (5.0/5.8 s). It uses only an owned disposable profile, seeds a custom default before first +launch and creates the managed workspace beneath that profile. Cancellation, selection, open-path +error propagation, reorder/archive/restore, removal clearing a Session/default, graceful shutdown +and exact snapshot/revision persistence on relaunch pass. Native dialog/shell results are supplied +at the Electron API boundary; physical picker interaction and launching a file manager are not +exercised. The fixture's profile preparation is restricted to its owned profile before first launch. +Project (three modules) and its route graph (86) load in ordinary Node with Electron/electron-store +forbidden at build/runtime. This is module-loading evidence, not a composed headless backend. + +No performance/resource suite is repeated and no speed gain is claimed. Normal generated Provider +and ACP registry refreshes are retained separately. Artifact hashes and evidence are in +`.deepchat/rutis-g4-project-acceptance/`. Published head `b7a50e3aa` passes required +`40621219551`/barrier `122597419315` and all six packages `40621219651`/barrier `122602071741`. +Remaining Store/configuration and desktop boundaries keep G4 active. G5 is unstarted. + +The MCP App baseline `c6758fe56` passes required `40498105485`/barrier `122171636540` and all six +packages `40498105509` attempt 2/barrier `122594009042` before this Project checkpoint is pushed. +The single failed-job retry resolves a runner DNS failure downloading pinned Rust; production +checks, timing budgets and source remain unchanged. + +### MCP App registry and desktop adapter + +Keep one registry for instance/source/consent authority. Move native protocol, window observation, +link opening and session permissions to the Main adapter. Require owner observation before creating +an App; bound creation reservations with the existing instance limits and invalidate them on owner, +server and maintenance revocation. Keep authorization and source validation beside the registry. + +- [x] Connect the native-free registry/host to required desktop capabilities and asynchronous + native policy/permission commands; remove the old native registry wiring. +- [x] Compare real App hydration, sandbox policy, consent, tools, source changes, window closure + and relaunch; validate lifecycle/security limits and ordinary Node loading. + +One native-free registry retains instances, source validators, pending consent and pending creation. +Reservations count against the existing 64-instance and 32-per-owner limits. Owner/server revocation +and maintenance clear cancel them and abort observation/metadata waits; the SDK resource request +receives the same owner signal. Late results cannot publish a view. Shared MCP connection startup +and execution remain with the existing server owner. Creation always releases its reservation. + +Main's protocol adapter observes the actual native owner before App hydration and revalidates its +window identity before protocol responses, permission callbacks and approved links. It receives +only policy, browser-permission and owner-revocation business operations. Protocol policy is a +plain isolated projection; browser permission decisions remain with the one registry and require +current source/owner state after consent. Native callbacks settle once even if the caller disappears. +First-party/default-session behavior, separate sandbox origin, CSP, consent bounds/timeouts and +the absence of standing browser grants remain compatible. The old native registry imports, +observer and session-permission implementation are removed; their native adapter is wired by default. + +Validation: **964 checks across 34 affected files and 23 generated-catalog checks across two files +pass**, with native SQLite required. App and all affected MCP unit/new E2E types, normal/final app +builds, format/check, lint and i18n pass. Durable cases cover pending-creation capacity and release, +owner/server/maintenance cancellation, resource reads whose SDK ignores cancellation, revoked +approval, isolated policy, late native policy responses and single native callback settlement. + +The immutable `ef4e8052a` app and final candidate pass the same real model/MCP/persistence/native +protocol/consent/relaunch workload (6.7/6.3 s). A model tool call creates the saved App source; +actual protocol responses retain CSP and permission headers. App tool calls, settings-window +rejection, explicit link allow/deny, graceful shutdown with pending consent, reopening saved sources +and deletion making a saved App inert all pass. The native browser launch is recorded at the shell +boundary; external browser navigation and physical camera/microphone access are not exercised. +The three final policy, stdio and App cases pass in 28.1 s. Timings are functional observations, +without performance gates, resource recapture or speed claims. The normal generated Provider +catalog refresh is retained separately in `5a79f205d`. + +Registry (one module), AppHost (five), the full McpService module graph (34), ToolService (204), +actual Harness entry (350) and ProviderRuntime (131) import in ordinary Node with Electron and +electron-store forbidden at build/runtime. The native protocol adapter remains in Main. This +establishes module loading, not a composed headless backend or a relocated writer. G5 must move +the registry with its source and permission authority, keeping source closures/signals local and +transporting desktop policy/permission/ownership operations through the private gateway with +generation fences. Artifact hashes and evidence are in `.deepchat/rutis-g4-mcp-apps-acceptance/`. +G4 stays active and G5 is unstarted. Published head `c6758fe56` passes required `40498105485`/barrier +`122171636540` and all six packages `40498105509` attempt 2/barrier `122594009042`. + +The preceding MCP environment head `ef4e8052a` passes required `40493590061`/barrier `122156301597` +and all six packages `40493590206`/barrier `122162440119` before this App checkpoint is published. + +### MCP runtime environment + +Supply the existing MCP client and registry probe with required host values and capabilities. +Keep one toolchain owner, preserve live command/PATH selection, home expansion, client identity, +environment isolation and child cleanup. Remove runtime Electron/proxy imports and singleton lookup; +keep native discovery and proxy resolution at composition. Process placement is unchanged. + +- [x] Connect explicit home/version/toolchain and proxy capabilities to every production caller. +- [x] Compare real stdio MCP initialization, command/environment/cwd, calls, restart and cleanup; + validate existing MCP, toolchain and lifecycle contracts and ordinary Node loading. + +Main supplies the absolute home directory, application version and existing ToolchainService owner +to McpService and every client. Commands, configured runtime roots and PATH still read that owner's +current state. Registry probes use the current host proxy URL, including configured credentials, +through a required callback. The client no longer imports Electron or obtains a global toolchain; +the server manager no longer imports the native proxy module. Obsolete native/singleton test mocks +are removed. Native discovery and proxy resolution remain at composition. + +Validation: **976 checks across 36 files pass**, covering MCP transport/auth/settings/policy, +toolchains, plugin lifecycle, ToolService, Harness, composition, runtime bindings and generated +catalogs, with native SQLite required. App and affected MCP unit/E2E types, the normal build, +format/check, lint and i18n pass. Durable cases cover supplied home/version and absolute-path +validation plus live registry-proxy replacement. Environment restrictions, protocol deadlines, +process identity and cancellation tests remain enforced. + +The immutable `3b3890dad` app and candidate pass the same real stdio workload (7.3/7.5 s): public +custom-Node selection, home expansion, client identity, configured working directory and PATH +precedence, minimal-environment secret exclusion, tool calls, stop/restart, graceful close and +persisted relaunch. Every observed child exits. Normal candidate HTTP plugin-policy and stdio +cases pass in 20.9 s; both Code Mode helper cases separately pass. These are functional observations +without timing gates. The normal generated ACP registry refresh is isolated in `08ddc763f`. + +McpClient (18 modules), ServerManager (19), ToolchainService (13), full ToolService (204), actual +Harness entry (350) and ProviderRuntime (131) import in ordinary Node with Electron/electron-store +forbidden at build and runtime. This establishes those loading graphs; MCP AppHost presentation, +a composed headless backend and relocated persistence owners remain open. No process placement, +performance capture or speed claim changes. Evidence and artifact hashes are in +`.deepchat/rutis-g4-mcp-runtime-acceptance/`. G4 stays active and G5 is unstarted; publication and +exact-head CI remain separate acceptance evidence. + +### Plugin tool policy owner + +Move plugin policy reads and mutations into one required, native-free owner. Main constructs the +existing `plugin-tool-policies` file adapter; PluginService, MCP ToolManager/AppHost and ToolService +receive the same owner. Remove module-level native construction and global policy functions. +Preserve file shape, replacement/removal by owner, explicit allow/ask/deny, undeclared-tool closure, +ambiguity and disabled-owner behavior. Policy decisions remain local with the execution owners; +G5 moves this state with them and gives retained desktop writers acknowledged commands. + +- [x] Replace the global policy Store and retarget every production reader/writer to one owner. +- [x] Compare real MCP policy persistence, denied tools, ambiguous owners and disabled policies; + validate mutation failures, owner isolation and Tool/MCP/plugin contracts. + +Main supplies the existing named file adapter after profile selection. One native-free +PluginToolPolicyStore serves activation/migration/disable/shutdown writes and ToolService, MCP +ToolManager and MCP AppHost reads. The owner retains current backing-store reads without a second +decision cache. Owner/server replacement, owner-wide deletion, allow/ask/deny, undeclared closure, +ambiguity and disabled-record behavior remain unchanged. Legacy global functions and ToolService's +obsolete Electron mock are removed. Synchronous operations remain owner-local; G5 must provide +acknowledged mutations for any desktop writer retained outside the moved policy authority. + +Validation: **744 checks across 12 affected native files, 23 generated-model checks across two +files and 16 ACP registry checks across two files pass**. App and new owner-fixture/unit/E2E types, +the normal application build, +format/check, lint and i18n pass. Coverage includes persistence-error propagation, independent +owners, replacement/removal, policy ambiguity, existing plugin lifecycle/MCP App contracts and +current-policy `ask` enforcement even with full access. No production check or timeout is relaxed. + +The frozen `f0ca555fe` app and candidate pass the same real HTTP MCP fixture, stored policy file, +tool catalog filtering, permitted calls, ambiguous-owner rejection, disabled policy and relaunch +workload (13.2/13.3 s). Both comparison runs set the owned E2E profile before loading the unchanged +app entry: the old module-level native constructor ran before the application's profile override. +The driver restores the exact entry bytes afterward. This is test-profile isolation, not a policy +implementation or a performance comparison. Normal candidate startup additionally passes all +four app cases in 29.3 s, including Code Mode complete/held READY, Agent image/workspace reads and +the policy workload without that driver. No real user policy file is modified by the fixture. + +The policy owner, full 204-module ToolService graph, actual 350-module Harness entry and +ProviderRuntime import in ordinary Node with Electron/electron-store forbidden. MCP runtime and +native AppHost dependencies remain outside that claim; no composed headless backend or relocated +writer is established. Artifact hashes and evidence are in +`.deepchat/rutis-g4-plugin-policy-acceptance/`. No performance/resource capture or speed claim is +added. The new checkpoint needs its own exact-head CI. G4 stays active; G5 is unstarted. +The normal generated Provider catalog refreshes are retained in `1952466fd` and `8cabd095e`; the +latter also retains the ACP catalog's website update. + +The branch includes `dev` at `830745fec`, merged as `996efc3b0`. This preserves the upstream Code +Mode subagent contract fix: nested children retain their current authority under the frozen parent +entrypoint ceiling. Its two frontend fixtures use the branch's required public helper capability +and RSS method, with no private factory replacement. The merged app, contract/Tool suites and +matching policy workload are the final validation source; new CI must inspect this integrated head. + +### Desktop appearance and native settings commands + +- [x] Require plain bootstrap locale and asynchronous theme/login/UI capabilities; remove + Electron imports and native listener ownership from DesktopSettings. +- [x] Route public appearance changes, display receipts and settings snapshots through the same + bounded owner queue; wait for native completion before success and change events. +- [x] Fence/coalesce system-theme observations and drain actual host work during maintenance + and shutdown. Compare the matching application behavior and preserve existing sender checks. + +AppSettingsService serializes appearance commands, display mutations, settings updates and reads. +Normal admission stops at 64 outstanding commands. A coalesced system observation has a reserved +position so saturation cannot lose the latest OS color; an event during active observation can +schedule one following observation. Previous-value display receipts are read inside that ordering +boundary. Native theme and login operations return observed values. Selected snapshots query login +only when requested; a stored login preference cannot replace the OS result. Invalid/fenced/full +commands reject before work, and failed commands do not acknowledge success or wedge the queue. + +Main supplies the Chromium application locale after readiness, NativeTheme operations, observed +login registration and awaited UI refresh. Floating UI receives the resolved locale/color directly, +avoiding a callback into the core command waiting for that host. Tab context menus retain the +resolved locale for new tabs. Main owns the NativeTheme listener, fences it during maintenance and +reconciles after startup/resume. Shutdown removes the listener and directly awaits settings stop +before event/storage teardown. Stop rejects queued/new commands and waits for active host work; +the unchanged 30-second lifecycle deadline leaves the owner fenced and prevents restart while +work remains. It is not a performance budget. There is no additional process, file writer, native +fallback, broad configuration snapshot or public protocol/schema change. + +Validation: **180 distinct checks across 14 files pass**: 165 in 11 affected native-enabled files +and 15 in three renderer state suites. Durable checks cover awaited effects, concurrent display +receipts, OS-observed login values, selected reads, failures, saturation including system events, +coalescing, shutdown and a timed-out native drain. The floating host applies resolved appearance +without reentering pending core work. App and selected owner/native-settings/floating/E2E types, +normal application builds, format, lint and i18n pass. The legacy dispatcher fixture runs in the +native group; it is outside the additional selected test typecheck. + +Immutable `592d32b92` and the final candidate pass the same three real-app cases (18.8/16.7 s): +settings revisions under frozen/rolled-back clocks, legacy file/database/credential persistence, +and desktop appearance/login/relaunch. Theme and Persian/English language changes update both +chat and settings windows. The locale manifest preserves `rtl` for Persian and `auto` for English. +The login setter is replaced at the native SDK boundary inside an owned disposable process: +requesting true can still observe false, and the unrelated stored true remains untouched. No real +OS login registration is written. Both variants close gracefully and preserve restored values on +relaunch. Times are functional observations; no performance/resource capture or speed claim is +added. Baseline and candidate use the same fixture, package version and runtime resources. + +The DesktopSettings graph (three modules) and AppSettingsService graph (87) compile, import and +execute in ordinary Node with Electron/electron-store forbidden at build/runtime. The probe +constructs both owners and exercises bootstrap, language/theme commands, a full settings read and +stop. This proves that boundary, not a composed headless Harness or relocated shared writer. +Immutable hashes, diagnostics and acceptance logs are in +`.deepchat/rutis-g4-desktop-settings-acceptance/`; the final output is restored and hash-verified. + +The settings-file baseline `592d32b92` passes required `40627565171`/barrier `122617966427` and all +six packages `40627565315`/barrier `122622838387` before this slice is published. This slice needs +its own exact-head CI. G4 remains active: font enumeration, logging/restart and other native/config +consumer contracts still require audit/extraction before G5's exclusive profile/writer handoff. +G5–G12 retain their full scope and conditional adoption rules. + +### Font, logging and content-protection capabilities + +- [x] Move font enumeration and logging-directory access behind required asynchronous native + capabilities; keep normalization, cache and preference persistence with configuration. +- [x] Route font/folder requests and native content-protection/logging changes through the shared + settings queue. Publish success only after the corresponding native operation completes. +- [x] Give Main one restart intent for logging and content protection; remove both unowned timers + and the obsolete WindowPresenter restart callback. Exercise the actual application restart + teardown and preserve settings on relaunch. + +DesktopConfigurationHost uses the existing font-list implementation, the required absolute private +profile path and the Main logging gate. LoggingService and FontSettings no longer import native +platform packages. The existing font normalization, case matching, catalog cache and empty-catalog +failure behavior remain compatible. Public font lookup and folder opening enter AppSettingsService, +so actual native work belongs to its bounded command queue and drain. Main creates only the private +logs directory and checks the native folder-launch result; no renderer path is accepted. +Content-protection and logging mutations await their native effects before events and receipts. + +The native configuration host starts paused. One pending restart intent coalesces logging and +content-protection changes with the existing one-second delay. Main activates it after startup, +pauses it before maintenance drains, and resumes it after successful recovery, including the early +maintenance-rejection path. Shutdown closes it before any awaited teardown step, preventing a +pending timer from taking over a quit. A fired restart transfers to the existing shutdown +coordinator; closing the host never waits on its own restart. Closed hosts reject new work. +WindowPresenter now applies window flags without owning a restart timer or callback. There is no +additional process, supervisor, file writer, dependency, fallback or public schema change. + +Validation: **208 distinct checks across 17 files pass**: 179 native-enabled checks in 12 affected +files, 15 renderer state checks in three files and 14 generated-catalog checks in two files. +Selected owner/native-host/window/unit/E2E types and normal application builds pass. Durable cases +cover native completion and errors, fixed directory ownership, a close during directory creation, +font normalization/cache and actual pending-query drain, queued folder fencing, content/logging +acknowledgements, startup/maintenance pause-resume, shared restart coalescing, close cancellation, +failed restart reporting and transfer without self-deadlock. The legacy dispatcher fixture is +runtime-tested; its wider existing type debt remains outside the additional selected test typecheck. +Format, lint, i18n and app typechecks pass. + +Immutable `5635e8b14` and the candidate pass the same three real-app cases (22.0/21.9 s): settings +revision/clock behavior, desktop appearance/login/relaunch and the native font/logging restart +workload. Actual installed fonts populate the normalized cache and preserve selected families. +The logging-folder SDK boundary records the private path without opening an external folder. +Content-protection calls apply real window flags. The real delayed restart drains the application +and reaches relaunch/exit; the fixture observes those final native calls without spawning a second +app, then completes the intended process exit with code zero. Manual relaunch restores font, +content-protection and logging preferences and the native protected-window flag. Logging produces +the application shutdown record in the owned profile. These are functional observations, not a +performance claim; no performance/resource suite is repeated. + +DesktopSettings, FontSettings, LoggingService and AppSettingsService graphs (3/1/1/87 modules) +compile, import and execute in ordinary Node with Electron, electron-store and font-list forbidden +at build/runtime. The probe constructs all four owners and executes font discovery, canonical +selection, logging/content changes, folder access and stop through explicit host implementations. +This proves these boundaries, not a composed headless Harness or relocated shared writer. +Evidence and verified baseline/candidate hashes are in +`.deepchat/rutis-g4-font-logging-acceptance/`; the candidate output is restored after comparison. +The normal generated Provider catalog refresh is retained separately from the implementation. + +Baseline `5635e8b14` passes required `40630680323`/barrier `122628063550` and all six packages +`40630680405`/barrier `122632459124` before this checkpoint is published. The checkpoint requires +its own exact-head CI. G4 remains active: finish the concrete configuration/native-image/runtime +consumer and writer/reader audit before G5's exclusive profile handoff and cohesive backend move. +G5–G12 retain their full scope and conditional adoption requirements. + +### File preparation and native image actions + +- [x] Require an absolute profile root for FileService and remove its Electron dependency. + Preserve document/image adapters, validation, relative-read containment and generated-name + temporary image writes; await directory creation before the first write. +- [x] Keep save-dialog selection, clipboard decoding/writes and native HTTP image retrieval in + DesktopImageActions. Wire existing renderer routes directly to this Main capability. +- [x] Remove unused arbitrary profile write/delete and generic temporary-write methods. Keep + adapter creation private; the business port returns data rather than adapter instances. + +FileService owns Node-capable preparation and existing size/token/PDF/image behavior. Its explicit +profile root replaces app.getPath, and temporary writes no longer race constructor-side directory +creation. DesktopImageActions owns the existing data-URL, raw base64, image-cache and HTTP image +flows. Main retains Electron net.fetch and native image decoding, including existing MIME/filename +rules and fallback behavior. A canceled dialog performs no save; successful saves write the +original bytes to the selected target. NativeImage values remain inside Main. Existing public +schemas, sender checks and Main request admission/drain remain in place. No extra process, image +engine, dependency, generic filesystem route or fallback is added. + +Validation: **139 distinct checks across 10 files pass**: 125 native-enabled file/dispatcher/image +checks in eight files and 14 generated-catalog checks in two files. Durable image cases cover +dialog cancellation and selected-target byte preservation, encoded cache traversal, failed native +downloads and invalid clipboard decoding. App and selected file/image/unit/E2E types, normal build, +format, lint and i18n pass. The large legacy dispatcher remains runtime-tested; its existing wider +fixture type debt is outside the additional selected test typecheck. + +Immutable `36583fcfe` and the candidate pass the same real-app file/image workload (5.5/4.9 s). +The owned profile supplies actual text/PNG files. Real public routes prepare text and image +thumbnails, persist generated temporary image bytes, read relative files and reject traversal. +All four image source forms save identical bytes and reach actual native decoding. A local HTTP +server exercises Electron's network path. The fixture controls only native dialog selection and +clipboard output, so it writes no system clipboard or user-selected external file. Cancel and +decode failures retain their existing results; graceful close and relaunch preserve profile reads. +These times are functional observations; no performance/resource suite or speed claim is added. + +The full FileService graph (21 modules) compiles, imports and executes in ordinary Node with +Electron/electron-store forbidden at build and runtime. Actual text preparation, sharp image +compression/thumbnail generation, first temporary-directory write, relative reads and symlink/ +traversal fences pass. This proves the file boundary, not the cohesive headless Harness or the G5 +writer handoff. Evidence and verified/restored artifact hashes are in +`.deepchat/rutis-g4-file-boundary-acceptance/`. The normal generated Provider catalog refresh is +retained separately from the implementation. + +Baseline `36583fcfe` passes required `40644539422`/barrier `122672429696` and all six packages +`40644539477`/barrier `122677486296` before this checkpoint is published. This checkpoint needs its +own exact-head CI. G4 remains active: remaining configuration/runtime consumers and the final +writer/reader/desktop inventory precede G5's exclusive profile handoff. G5–G12 keep their full +scope and conditional adoption rules. + +### Storage roots and native folder acknowledgement + +- [x] Remove Electron path discovery from SyncService, SyncSettings and LegacyChatImportService. + Supply required absolute profile/temporary/home/source paths from actual Main composition. +- [x] Await the native sync-folder launch and reject both SDK errors and failed native results. + Keep directory selection and backup/import policy with the business owner. +- [x] Supply Knowledge's existing unpacked extension directory explicitly to the service and + every database create/open path. Keep its DuckDB/VSS engine and file ownership intact. + +Sync retains its existing filenames, archive paths, configuration filtering, snapshot/read locks, +encrypted backup behavior and incremental/overwrite rollback. Source and staged files remain +owned by the existing maintenance operations; this slice adds no writable Main shadow and does +not perform G5's handoff. Legacy startup import uses the same profile chat.db as an explicitly +read-only source and the same shared target facades. Knowledge removes its module-level native +lookup, retains existing VSS loading/fallback and keeps independent file/task state. Main supplies +runtime facts and the asynchronous shell capability. There is no additional process, engine, +dependency, fallback, public schema, generic path-discovery route or permission expansion. + +Validation: **97 distinct checks across nine files pass**: 83 native-enabled sync/Knowledge/import/ +composition checks in seven files and 14 generated-catalog checks in two. New durable cases cover +the supplied home default, waiting for actual native folder completion and propagating returned +errors/rejections. Existing native backup consistency cases retain encrypted snapshot, WAL and +concurrent-copy coverage. An obsolete source assertion requiring an undefined import path is +removed; actual application import/relaunch checks verify the explicit source. App and selected +sync-settings/new-E2E types, normal build, format, lint and i18n pass. + +Immutable `9c67d7431` and the candidate pass the same six application cases (24.4/24.7 s): five +maintenance/backup/rekey/credential/shutdown cases and the storage-root workload. A disposable +chat.db is populated through native SQLite before launch, imported from the expected source, +preserved byte-for-byte and restored on relaunch. Sync creates and opens the configured private +backup directory through the actual route; the fixture observes the final shell SDK call without +opening an external folder. Knowledge loads the existing native VSS extension, creates its file +under app_db/KnowledgeBase, closes gracefully and reopens after relaunch with configuration intact. +No model/provider network call is needed. These are functional observations, not a performance +claim; no performance/resource suite is repeated. + +The full SyncService/SyncSettings/LegacyChatImportService/KnowledgeService graphs (33/1/16/13 +modules) compile and import in ordinary Node with Electron/electron-store forbidden at build and +runtime. The probe executes default/custom sync folder operations and actual native Knowledge +initialization, VSS loading, file listing, close and reopen. It does not claim a native SQLite +backup/import under ordinary Node or a composed headless Harness. Evidence and verified/restored +artifact hashes are in `.deepchat/rutis-g4-storage-roots-acceptance/`. Normal generated Provider +catalog changes are retained separately. G4 remains active for remaining native/configuration +consumer contracts and the final writer/reader/desktop inventory; G5–G12 retain their full scope. + +Baseline `9c67d7431` passes required `40649481834`/barrier `122738133598` and all six packages +`40649481736`/barrier `122746406102` before publication. Initial static/macOS x64 jobs stopped +during dependency downloads, before application checks. Their diagnosed SheetJS connection reset +and npm registry timeout are retained with the retry evidence. Latest required/platform jobs and +both barriers pass. The storage checkpoint requires its own exact-head CI; the documentation-only +native-boundary audit reuses the accepted runtime artifacts without another build or E2E capture. + +- [x] Complete the writer, synchronous-reader and desktop-consumer inventory. + - [x] Enumerate SQLite/DuckDB construction and wrapper entrypoints, inherited shared facades, + staged/import/snapshot connections and independent derived-cache/vector owners. + - [x] Map Session/Tape/pending-input, Memory, Agent deletion, delegation, Scheduler, Project + and Sync transactions to indivisible business operation groups. + - [x] Classify remaining synchronous configuration consumers and desktop effects, including + mixed database/file settings, plugin trust state and native image/runtime inputs. +- [x] Replace cross-boundary closures/row APIs with bounded transactional business commands for + Session/Tape/Memory/config and scheduler claims. Preserve atomic head observations and CAS; + leave exactly one writer authority throughout each cutover. +- [x] Define versioned read snapshots and acknowledged mutations for desktop consumers. Extract + crypto/unlock, OAuth, helper launch, native UI and power events behind narrow async ports. +- [x] Preserve route sender validation, permissions, secret handling and cancellation identity. + Compare real operations and maintenance, remove only replaced dependencies and validate + directly affected storage/host contracts. + +Completion: the core can depend on serializable operations and host ports without synchronous IPC, +generic SQL, duplicated writer state or privileged Electron objects crossing the boundary. + +### Shared-writer inventory evidence + +The source audit at `32f457ff7` finds 11 direct SQLite constructors in seven files and six DuckDB +instance creation sites in four files. The RFC records each constructor family, wrappers, +production callers and file role, including the G3 vector worker's directory-lock database and +Knowledge's independent files. `openSQLiteDatabase` configures WAL and creates a writable-capable +handle; snapshot/read intent alone does not make its callers read-only. `DataImporter` opens +both source and path-based target through writable-capable constructors. Production incremental +import uses these paths while the normal application writer is closed under maintenance. + +The existing data facades inherit the startup `MainDatabase`; they are not independent connection +owners. Atomic groups include Agent deletion plus Memory namespace retirement and audit removal, +Project removal plus Session assignments, Sync conflict/trigger/cursor settlement, pending-input +receipts and Tape/transcript projections. Shared connection readers also include Session references, +history search, the built-in MCP conversation-search server and Tape inspector head subscriptions; +debug mock-session creation remains a writer operation. Memory's current-range query observes its +projection and Tape head in one SQL statement; replacing it with independent remote reads would lose that +guarantee. Existing local transaction closures can move with this cohesive backend without new +row-level RPCs. + +`AppSettingsDbBackedStore` mixes sensitive/portable database keys with machine-local legacy-file +keys; its merged `store` getter remains owner-local. Native consumers use the committed private +allowlist and acknowledged mutations, while general config routes execute beside their settings +authority. Plugin installation/safety and tool-policy persistence retain separate complete file +owners. A full merged Store snapshot is not a public backend payload. Different-key writes still +race because the pinned Conf adapter serializes the whole object before atomic replacement. +G5 hands the complete profile-file authority to the configuration owner after exclusive bootstrap; +Main keeps snapshots/commands, including for UI keys. The RFC's final profile-file inventory also +covers native key files, credentials, caches, bootstrap migration and offline reset. This inventory +does not claim the actual G5 writer handoff or a composed headless backend. +Constructor inventories and source-search evidence are in +`.deepchat/rutis-g4-code-mode-acceptance/`. + +The audit also records 34 direct SettingsStore import consumers and validates all 24 explicit +inventory source references. Format/check, lint, i18n and app typechecks pass. This documentation +checkpoint changes no application code; the accepted `32f457ff7` runtime evidence remains +applicable without repeating builds, application E2E or performance/resource captures. + +### Remaining native-runtime boundary audit + +Complete the remaining native/runtime/configuration contracts and final ownership inventory as +one G4 completion batch. Locally finish each affected owner and its relevant tests/application +comparison before advancing; publish the completed batch after normal quality checks and the +preceding CI barriers. Do not trigger another all-platform publication for each constructor fact +or documentation-only audit. Preserve the full contracts below, then verify the batch's actual +default composition before starting G5. + +| Owner/call chain | Current boundary | Required work before claiming the selected cutover | +| ------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------- | +| RemoteConversationRunner | Session/catalog/turn/projection and workspace-default queries are async; Main supplies the absolute profile root | Preserve file/workspace authorization and the existing cache/generated-asset paths at G5 handoff | +| ChannelAdapter and five built-in factories | Lifecycle/event adapter has no native import; actual downloads remain inside the clients/Runner | Co-locate those business owners at G5; require a carrier only for an actual later host/device transfer | +| RemoteService Feishu/Weixin login | Main owns windows behind operation-ID commands/events; Core drains startup/polls/callback servers and fences late results | Bridge this private port with authenticated G5 ownership and preserve actual cancellation/close/server settlement | +| Provider OAuthService/Copilot windows | Main owns presentation and validates new tokens; Core issues secret-free targets and atomically commits against current configuration | Preserve the private owner commands, maintenance invalidation and authenticated G5 caller grants | +| Window/Tray/Notification/Shortcut/Floating | Configuration readers use a private committed projection; floating bounds use acknowledged owner mutations; Agent/catalogue queries are already async | Preserve the projection and existing async owner queries at G5 handoff | +| Upgrade settings and renderer diagnostics | Core receives the explicit application version; Main reads projected channel/privacy/logging flags | Preserve normalization under the configuration owner and Main's native updater/diagnostic lifetime at G5 | +| Plugin installation, context Hooks and MCP supervision | One portable business owner keeps installation/safety persistence, Tape Hooks and synchronous supervisor decisions local; Main supplies async shell/window effects | Transfer this owner and its independent Conf file with the cohesive backend; preserve physical drain and private native caller grants | + +Attachment I/O follows the actual clients/Runner call chain and stays local to that business +owner. The base adapter's uncalled download methods and their unused 20 MiB constant are removed; +no new size limit, desktop proxy path or native transfer operation is imposed on live downloads. +Any later selected host/device transfer must retain the RFC's bounded carrier/authority contract. +Native window extraction retains auth session and callback-server lifetime; its implementation is +recorded below. These boundaries preserve the full G5–G12 scope. + +Native configuration consumers use resolved language, notifications, close-to-quit, content +protection, shortcut mappings, floating enablement/bounds, logging, privacy and update channel. +The private committed projection covers bounds writes and separate shortcut events explicitly, +including asynchronous error/drain handling for floating persistence. Update-channel normalization +belongs to the configuration owner. Plugin installation/safety metadata has an independent +plugin-settings file owner; tool-policy authority uses the separate plugin-tool-policies owner +already inventoried above. Keep activation, context Hooks/Tape and MCP supervision co-located with +their business consumers. Do not merge those files or retain a second writer during handoff. + +### Native configuration projection + +The actual Main composition supplies Window, Tray, Notification, Shortcut and Floating with a +private `DesktopConfigurationProjection`, replacing their direct DesktopSettings reads. The +configuration owner publishes explicit allowlisted values immediately after each stored mutation, +before native effects and public events. Nested shortcuts and bounds exclude unknown keys; the +projection rejects foreign owner identities, stale/invalid revisions and caller mutations of its +returned objects. It contains no credentials or merged Store state. Native theme reads stay in +Main, avoiding re-entry into a pending configuration command. + +Floating bounds writes receive the existing serialized settings owner's acknowledgement and +publish committed values without optimistic cache changes. Drag mutation handlers validate the +current floating window and main frame. Floating enable/disable awaits actual native presentation +inside the same owner queue, preventing a queued disable or teardown from overtaking creation. +Maintenance/shutdown close native write admission, stop and drain the settings owner, then drain +outstanding native writes; successful recovery refreshes the projection before reopening writes. +The existing 64-command bound and stop deadline remain unchanged. Mutation routes return the +same command's receipt rather than enqueueing another read that a subsequent edit could overtake. + +UpdateSettings receives the explicit application version and retains existing channel defaults and +normalization under the configuration owner. Upgrade reads the projected channel/privacy values. +Renderer diagnostics receives the projected logging flag and explicit profile directory; its file +rotation, sanitization and native lifetime remain Main-owned. The selected classes import without +Electron, electron-store or font-list in ordinary Node: DesktopSettings/FontSettings/Logging/ +AppSettingsService/projection/UpdateSettings/renderer diagnostics contain 3/1/1/88/1/1/85 modules. +The probe executes owner mutations, committed cache publication and shutdown/drain. This does not +prove a composed headless Harness, cross-process owner replacement or G5's exclusive writer move. + +Validation passes 330 checks across 29 files, app and selected test/E2E types, normal build, format, +lint and i18n. The same eight application cases pass on immutable `ae0be4fef` and the candidate: +five maintenance/rekey/credential/shutdown cases, desktop settings, font/logging and +the native projection workload. Actual floating-window drag persists/restores bounds after window +recreation; shortcut registration and updater channel selection observe committed owner values; +graceful profile relaunch preserves configuration. Only the final SDK registration/update check +is observed without registering a system shortcut or contacting the update service. These are +functional checks, with no performance capture or speed claim. Evidence and artifact hashes +are in `.deepchat/rutis-g4-native-projection-acceptance/`. + +One interrupted, uninstrumented follow-up records a forced maintenance close. Both the targeted +two-case diagnostic and the full eight-case run with native process output complete gracefully; +the isolated failure remains an unexplained observation, with its evidence retained. Final G4 +integration must recheck graceful maintenance/shutdown rather than classify it as timing noise. + +The published `ae0be4fef` passes required run `40668735420`/barrier `122752954520` and all six package +targets in `40668735436`/barrier `122759825407`. The native projection belongs to the remaining G4 +completion batch and is not independently published. The final native-consumer inventory +remain required. G4 stays active; G5–G12 retain their full scope. + +### Remote native authentication and profile roots + +RemoteService no longer imports Electron or retains BrowserWindow objects. Main owns Feishu and +Weixin login windows through the required `RemoteAuthWindowPort`: plain operation-ID open/close +commands and closed/error events. Core retains authorization state, nonce, expiry, HTTP callback +server and credential/pairing commitment. Window creation retains the existing dimensions, +brand/redirect navigation restrictions, popup denial and isolated web preferences. A native close +request cannot be retained by a page's beforeunload handler. Closing a loading window preserves +the cancellation outcome; navigation failures from an active window still reach the owner. + +Authorization starts reuse the existing runtime-operation queue. Stop is idempotent, closes +admission before cancellation, marks current Feishu sessions terminal and awaits actual startup, +registration, polling and callback settlement. Initialization waits for that physical stop before +opening another lifetime. Late native startup is compensated by its known operation ID. Feishu +cleanup awaits response completion and callback work before closing partial-header connections +and releasing the loopback server. Weixin HTTP requests and poll sleeps carry owner cancellation; +canceled QR sessions retire and late confirmed results cannot recreate accounts or write secrets. +Main's final native-window close remains separate from Core's credential authority. + +RemoteConversationRunner receives Main's explicit absolute profile directory for the existing +image-cache and fallback generated-asset locations. Its workspace default is queried asynchronously. +File/path authorization, workspace requirements, image promotion/deduplication and generated +filenames remain unchanged. The runner and Weixin SDK graphs compile/import in ordinary Node with +Electron/electron-store/font-list forbidden (10/1 modules); the complete Remote closure is verified +below. This is module portability evidence, not the composed G5 backend or G11 Session Host. + +Validation passes 305 checks across 36 files, app and selected test/E2E types, normal build, format, +lint and i18n. The same six application cases pass on immutable `e2c97edac` and the candidate: +five maintenance/rekey/credential/shutdown cases and native Remote authorization. Actual windows +preserve disabled Node access, invalid nonce rejection, user-close cancellation and physical +closure. Weixin credentials survive graceful profile relaunch with the channel disabled. The +fixture intercepts only vendor HTTP and native loadURL destinations with disposable data; no real +account or external authorization is used. Evidence and artifact hashes are retained in +`.deepchat/rutis-g4-remote-native-acceptance/`. No resource capture or performance claim is added. + +This work belongs to the unpublished G4 completion batch. The final writer/reader/native inventory +remains required before G5. Existing floating Agent/catalogue queries +already use async owner methods and need no extra synchronous-getter replacement. G4 remains active. + +### Remote module closure + +ChannelAdapter retains its existing lifecycle, event and send contract. Unused protected image/file +base64 download methods and their unreferenced 20 MiB constant are removed, eliminating the final +Electron import under `src/main/remote`. Repository consumers, built-in subclasses, plugin/schema +entrypoints and tests contain no calls to those helpers. Real attachment download/decryption stays +inside channel clients and RemoteConversationRunner; no body is sent over a new native/Rutis +operation. Existing transport/file-preparation policies, URL/base64/encrypted media behavior and +timeouts remain. There is no new carrier, download implementation, global cap or proxy substitution. + +The complete RemoteService graph (54 source modules) bundles and imports in ordinary Node with +Electron/electron-store/font-list forbidden at build and runtime. The probe constructs the actual +service with explicit business/native ports, rejects unexpected network/native work, initializes +all disabled channels, applies a configuration mutation, shuts down, reinitializes and shuts down +again. This proves this module's portable composition and selected lifecycle, not live vendor +channels, a moved Store writer, the cohesive Harness or CLI execution. + +The existing 305 Remote/native-window/composition checks across 36 files pass, along with app +typechecks, normal application bundling, format, lint and i18n. The unchanged actual-app Remote +login workload passes on accepted immutable `119a15949` and the candidate: nonce rejection, +user-close cancellation, window cleanup, Weixin account persistence and graceful relaunch. Accepted +baseline evidence from the identical fixture is reused; no unchanged baseline or performance +capture is repeated. Artifact hashes and evidence are in +`.deepchat/rutis-g4-remote-closure-acceptance/`. + +### Provider OAuth configuration settlement + +OAuthService uses required asynchronous Provider owner commands rather than synchronous settings +reads or full Provider writes. Core issues a target containing Provider ID, client ID and an opaque +configuration identity; the Native facade never receives the saved credential or unrelated +configuration. Device and browser login retain their existing presentation and GitHub token +validation, then await actual credential commitment before returning success. Persistence errors +propagate to the caller. Shutdown aborts token validation and fences pending target reads and late +validation results before they can start another effect or submit a write. + +ProviderRuntime compares current identity and writes without an intervening await. The comparison +covers Provider ID, protocol, saved token and trimmed client ID with a private per-runtime key; +current name, enablement and custom headers survive unrelated changes. Concurrent logins cannot +overwrite a winning commit. Foreign-runtime, revoked, removed, reconfigured and malformed-token +requests reject. Maintenance invalidates captured targets before Store work; shutdown rejects +reads and writes. This is a stale-state fence, not authorization or a persisted database revision. +The unregistered generic OAuth method, callback server and window implementation are removed; +registered public route names, inputs and outputs remain unchanged. + +Validation passes 79 checks across four files covering the real ProviderRuntime commands, Native +login facade, existing Copilot token/client behavior and composition. App and selected test/E2E +types, normal build, format, lint and i18n pass. The same owned-profile application workload passes +on immutable `3ddf2b657` and the candidate: actual device login instructions, GitHub verification, +credential receipt, rejection after concurrent configuration change and graceful persisted +relaunch. Vendor HTTP, clipboard and external browser launch are intercepted only in the disposable +app process; instruction windows are real and closed through Native APIs. The candidate also +passes all five existing maintenance/rekey/credential/shutdown cases. Their accepted baseline +remains available from the preceding integration. No resource capture or speed claim is added. +Evidence and verified hashes are in `.deepchat/rutis-g4-provider-oauth-acceptance/`. + +### Plugin business owner and native presentation + +The final G4 writer/reader/Native inventory includes the plugin boundary. It keeps +installation/consent, user-plugin activation, context Hooks and MCP supervision with the cohesive +core; its synchronous local transactions and integrity callbacks do not become row-level RPCs. + +- [x] Replace implicit Electron paths/version and plugin-settings construction with explicit roots + and the same Conf file format/defaults, retaining one installation/safety writer. +- [x] Require asynchronous native shell/settings-window capabilities and await actual native + closure while retaining renderer ownership checks in Main. +- [x] Drain accepted context-hook work before maintenance/shutdown can release Tape; preserve + active-run/update exclusion, cancellation, uncertain outcomes and restart behavior. +- [x] Verify default composition, portable import, relevant regressions and the same real-app + plugin scenarios against immutable artifacts. +- [x] Finish the final writer/reader/native inventory and local completed-batch validation. + The published batch's required and six-platform CI barriers pass at `356143755`. +- [x] Supply OCR's native cache-key provider explicitly from Main; preserve encrypted cache + persistence, unavailable-key fallback and the same cache clear/relaunch application behavior. +- [x] Supply Codex/Grok auth owner commands to the OAuth facade. Keep their controllers, + credential queues and synchronous storage observations with Provider consumers; Main must + not initialize those owners or receive writable credential-store instances at G5 handoff. +- [x] Supply provider-existence results and committed MCP startup readiness to the native + deep-link adapter; preserve latest-pending installation and sender/preview behavior without + passing complete Provider settings or the MCP business owner into native actions. +- [x] Await the native resolved-proxy capability before the existing MCP registry probe; preserve + proxy authentication, live selection, failure propagation and the three-registry batch. + +Native SDK presentation remains Main-owned. These contracts do not move the cohesive backend or +authorize an additional settings writer. Publish the complete G4 batch after final validation. + +PluginService requires absolute profile, home, working and application directories, resource +location, application version, the resolved command environment and native shell/window effects. +It uses Conf directly with the existing plugin-settings name/defaults/version; no file migration, +second installation/safety writer, generic persistence RPC or dependency is introduced. Runtime +supervision, tool-policy decisions, user-plugin update exclusion and Hook integrity callbacks +remain local to their business consumers. Main keeps the renderer ownership lookup. + +Settings-window close destroys the owned window, waits for the actual closed event and retains +sender ownership until then; a page's beforeunload handler cannot veto disable or shutdown. +Failed loading drains its window before propagating; cancellation during loading settles cleanly. +A pending reopen cannot outlive close-all. Context Hooks reject new work on stop, cancel the +captured lifetime and drain the accepted queue, including actual child closure even when process +termination fails. A 30-second drain deadline rejects without releasing ownership; restart stays +blocked until the queue settles. Main directly awaits this stop before database maintenance or +shutdown can release Tape and resumes only after reconciliation. Existing invocation budgets and +uncertain-result/retry rules remain. + +Validation passes **372 distinct checks across 16 affected files**: 231 plugin/native-window/ +composition checks and 141 MCP/Tool/compaction consumer checks. The held-verification test +also passes all 26 checks in its file with a fake-clock drain deadline; unchanged tests are not +counted twice. Changed-test types, app node/web types, normal full build, format/check, lint and +i18n pass. Lint retains the preceding Remote finally warning. + +The full PluginService graph imports in ordinary Node with Electron, electron-store and font-list +forbidden at bundle and runtime resolution: **18 source modules**. The actual owner initializes, +installs/enables a private user plugin, executes its Hook into Tape, stops/resumes, and reconstructs +from the same legacy-shaped Conf file. Enabled intent, unknown metadata and unrelated launch +evidence survive reconstruction. This verifies the portable owner, not a moved backend or headless +Session Host. + +Immutable `d456426f7` and the candidate pass the same two application plugin cases: ZIP review, +disabled installation, MCP activation/tools/disable/uninstall, reviewed context reaching actual +Provider requests and host-owned credential rotation. The final candidate passes **eight app +cases**, adding actual official settings windows with beforeunload handlers, disable, isolated +renderer globals, persisted disabled intent, graceful relaunch and window cleanup at shutdown, +plus all five existing maintenance/credential/shutdown cases. The five cases' accepted baseline +is retained. No performance suite or resource recapture is selected and no speed gain is claimed. +Evidence and verified hashes are in `.deepchat/rutis-g4-plugin-owner-acceptance/`. This work stays +local with the remaining G4 completion batch; G5 and later goals remain pending. + +### OCR native cache-key capability and final ownership inventory + +OcrRuntimeService requires its existing asynchronous key-provider interface. Main constructs the +safeStorage implementation for the unchanged profile `ocr/cache-key.json` path; resource creation +uses that required provider for `ocr/ocr-cache.db`. The runtime imports only the provider's type. +Wrapped-key persistence remains a single native file owner, separate from the OCR cache database; +the native driver, memory fallback, unreadable-key recovery, clear and resource close are unchanged. +No new capability route, default provider, storage engine or process is introduced. + +All **97 selected checks across eight files pass**: 85 runtime/key/asset/attachment/route/composition +checks and 12 native SQLite image/document artifact persistence checks, with SQLite required and no +skip. App and changed-test types, the normal build and the immutable-artifact check pass. Matching +`75-ocr-cache-key.smoke.spec.ts` passes on immutable `3eaf16a21` and the candidate: actual app routes +select the existing Node toolchain, clear the cache, close gracefully, relaunch and clear again. +The candidate reports persistent mode on both launches with native encryption available, an +encrypted database header and an unchanged wrapped-key file. This exercises cache ownership and +native key access; it does not claim a new OCR recognition or performance result. + +The integrated candidate passes **16 application cases across ten files**, including plugin +install/Hooks/settings windows, maintenance and credential drains, MCP child lifecycle, desktop +configuration, font/logging, file/image effects, Remote authorization, Copilot credential commitment +and the OCR cache. Each selected owner's immutable baseline evidence remains available. No +performance/resource recapture or speed claim is selected. OCR-specific evidence and hashes are +in `.deepchat/rutis-g4-ocr-key-acceptance/`. + +The source inventory checks **83 entries and 831 unique transitive inputs** without +Electron, electron-store, font-list or electron-toolkit runtime imports. Skill Sync's modulePath +reference is recorded separately and its actual worker source is checked as an additional entry; +the audit does not substitute a placeholder for runtime/package validation. All 63 direct native +import files have an explicit Main/device role, including UI/OS effects, ingress/bootstrap, +native crypto and key persistence, protocols, updater and OAuth presentation. Workspace's native +file/Git/preview owner has no shared database or settings import. Source graphs/classifications and +connection/configuration call sites are in `.deepchat/rutis-g4-final-inventory/`. + +This is source-boundary evidence, not a composed backend or exclusive writer handoff. The Node +Codex/Grok auth owners and credential queues are constructed alongside Provider consumers; Main's +OAuthService receives acknowledged account commands. Native deep-link actions use provider-existence +and MCP startup observations in place of complete business-owner dependencies. MCP registry selection +awaits the native resolved-proxy capability. Local complete-batch inventory and validation pass; +G4's published-head required and six platform package CI barriers pass. G5's Core profile and +bootstrap composition is in progress. + +### Provider account owner commands + +Main's OAuthService requires ProviderAccountAuthPort: the existing nine public Codex/Grok account +operations and a private asynchronous stop command. Core composition initializes each existing +controller with its one credential store, publisher and native browser-opening capability. The +same global controller continues to serve Provider token/refresh consumers. Native presentation +receives status/login/callback/cancel/logout/stop commands, with no credential-store access or +business-controller initialization. GitHub's native SDK presentation and credential-owner commands +retain their existing behavior. + +Stop first fences native Copilot work, submits account retirement and target invalidation, then +awaits their acknowledgements. A failed account stop cannot skip target invalidation. The account +receipt preserves the existing controller and credential generation fences; it does not redefine +callback-server or network closure. Credential commits remain synchronous after their owner-side +validation, with no await inside the file commit; backend teardown retains lifetime ownership. + +Validation passes **116 checks across ten files**, including delayed/failed account-stop receipts, +Codex/Grok controller/credential/provider adapters and composition checks. Ordinary Node imports +and runs the actual two owners/stores: status, private persistence, cancel, stop fencing and reopen +preserve the file and expose no access token in public status. Their combined graph has **11 +source modules**; Main's facade graph has **six**, with no Codex/Grok owner/store module. The probe +uses the existing unavailable-crypto file policy; real native wrapping and encrypted-file parity +are covered by the application fixture. + +Immutable `c585dacf5` and the candidate pass the same two application cases: legacy file/native +credential status, cancel, durable logout/relaunch and Copilot commitment/stale-configuration +handling. The integrated candidate passes **17 cases across eleven files**, retaining the prior +plugin/maintenance/MCP/desktop/font/file/image/Remote/OCR cases. App and changed-test types, normal +full build, format/check, lint and i18n pass. No process/resource or performance comparison is +selected. Evidence and artifact hashes are in `.deepchat/rutis-g4-account-auth-acceptance/`. + +### Native deep-link observations + +Native actions require only an asynchronous provider-existence result. Core performs that lookup +under running-database admission and returns a boolean; Provider settings and saved credentials +stay at their business owner. Provider preview validation awaits this result before opening +settings. Incoming credentials remain preview data until the existing user confirmation. + +MCP preview deferral observes successful Core startup in Main. The native adapter starts unready, +retains the latest pending URL, and consumes it once when readiness is observed. Repeated positive +observations do not replay it. Shutdown clears readiness, and a completed startup cannot publish +a positive observation after stopping or maintenance admission closes. This UI observation grants +no configuration or execution authority. Native actions receive no MCP business-service object. + +All **38 selected checks across two files pass**, including delayed negative provider lookup, +latest-pending behavior and composition. Matching application cases pass on immutable `7009bb955` +and the candidate: pending-preview replay and an actual OS open-url event for an existing Provider, +with settings presentation and saved credentials preserved. The candidate additionally passes MCP +child lifecycle and all five maintenance/shutdown cases: **eight cases across four files**. The +OS fixture observes the window opened by the link, preserving its provider navigation. + +App and changed-test types, normal build, format/check, lint and i18n pass. Evidence and verified +hashes are in `.deepchat/rutis-g4-deeplink-owner-acceptance/`. No performance/resource capture or +process change is selected. The shared writer and Harness have not moved. + +### Resolved proxy and final G4 batch + +Each existing three-registry probe batch awaits Main's current resolved proxy URL. Keep the native +system/custom resolution, authentication, cache/custom-registry short circuits and network failure +behavior. A failed host query propagates before any HTTP probe or registry-cache mutation. There +is no new proxy resolver, process, transport, cache or configuration writer. The focused MCP/native +proxy/composition suite passes **86 checks across four files**; the delayed-reply regression also +covers authenticated proxy options, a later direct selection and rejected host observation. + +The final inventory covers all **34 direct SettingsStore consumers**: 31 owner-local Core +configuration/command modules and three explicit bootstrap/composition entrypoints. Those Core +constructors are included in the **83-entry, 831-input** source audit rather than inferred from +type-only consumers. All 63 direct native-import files retain explicit Main/device roles. The RFC +records physical file owners as well as connections, including complete settings files, plugin +safety/policy, credentials, native wrapped keys, derived caches, bootstrap migration and offline +data reset. This establishes G4's handoff inventory, not actual G5 process or writer ownership. + +Final regression protection passes **807 distinct checks across 68 files**: 781 checks in 67 files +under Electron with native SQLite required, plus 26 child-script Hook checks under ordinary Node. +The Hook fixture uses process.execPath as its Node command; its seven failures under Electron are +retained in the initial log. Running that file with its intended Node runtime passes all 26 checks +without changing production code, test deadlines or assertions. Normal generated Provider catalog +checks contribute 44 of the 807 checks and the generated refresh remains a separate commit. + +The final default application passes **19 cases across 13 files**, covering plugin installation, +Hooks and native windows, account/Copilot authorization, maintenance and credential drains, MCP +child lifetime, desktop/font/file/image effects, configuration projection, Remote authorization, +OCR key/cache relaunch and OS deep links. Each owner retains its matching immutable application +comparison; the MCP proxy boundary reuses the verified b16d31799 baseline runtime case. No fresh +19-case baseline or performance result is claimed. App/changed-test types, normal full build, +format/check including Markdown, lint and i18n pass. Source hashes, artifact hashes and receipts +are in `.deepchat/rutis-g4-final-inventory/`; focused proxy receipts are in +`.deepchat/rutis-g4-proxy-port-acceptance/`. + +The complete batch is published at `356143755`. All 14 exact-head checks pass: required run +`40819020114` with `pr-required` barrier `123268789463`, and package run `40819020203` with all +six platform jobs and `package-required` barrier `123273186534`. The PR remains draft against +`dev` and is not merged. CI receipts are in the final inventory's `published-ci.json`. No timing +rerun was needed. G4 is complete; shared/configuration authority still moves physically in G5. + +## G5. Move the cohesive Harness and shared core into the TS backend + +Execution order: collect settings/security/database migration and recovery into the native-free +profile composition, then move the business composition with its existing owner-local repositories. +Connect that actual composition through the approved Rutis Core generation, replace Main's writer +and route/lifecycle wiring, then validate the complete cutover before publication. Main supplies +crypto, unlock/recovery presentation and native effects; it must retain no writable shadow. +Local bootstrap refactoring is preparation inside this goal, not a completed backend extraction. +Keep the full completion conditions below. Do not publish a partial G5 or add a fallback switch. + +### Profile startup composition + +The default desktop startup calls `initializeCoreProfile` for settings, private credential access, +encryption metadata, database recovery, attached adapters, legacy migration and tool-policy storage. +These constructors have no Electron dependency. Main supplies absolute roots/version, crypto, +logging, progress, unlock/recovery presentation and an acknowledged quit command. The database +initializer requires its explicit path. Legacy readers use the same pinned Conf engine with the +existing names, defaults, profile directory and application version. Migration still finishes before +adapters attach; an initialization failure closes its acquired database at the startup owner. + +All **80 selected checks across seven files pass**, including delayed native quit acknowledgement, +schema recovery, migration, file settings and database security. Changed-test types and app types +pass. The actual profile/database graph constructs under the Electron Node ABI with native imports +blocked at build and load; persistence/reopen and database close after a failed legacy migration +pass. This is constructor/runtime compatibility, not a headless or exclusive-writer handoff. + +Immutable `356143755` and the candidate pass the same four application cases: encryption/rekey, +legacy Provider model data, MCP/ACP settings and mixed legacy/database settings across relaunch. +The candidate additionally passes plugin install/Hooks/windows, credential persistence, all five +maintenance/shutdown cases, MCP child lifetime and OS deep links: **15 cases across nine files**. +Those additional cases retain their accepted `356143755` evidence; no new 15-case baseline is +claimed. Build, format/check, lint and i18n pass. Evidence and hashes are in +`.deepchat/rutis-g5-profile-bootstrap-acceptance/`. No process or transport change needs a resource +recapture at this preparation step. Main still invokes the local entry and holds its objects until +the complete Rutis Core composition and actual writer/route/lifecycle cutover below. + +- [x] Move the actual Harness composition and its Run/loop/interaction/pending-input feedback + cluster together. Keep Provider streaming and prompt/tool integration local initially. +- [x] Move the shared database facades, Session/Tape projection/recovery, configuration authority + and scheduler claim transactions with their business owners. Fence/drain old ownership + before opening the backend writer; remove main's writable shadow access. +- [x] Retarget existing renderer/CLI domain handlers without changing public behavior. Keep + authenticated ingress and native capability presentation in the desktop adapter. +- [x] Compare real streaming, delegation, pending-input settlement, approvals, cancel/resume, + restart recovery, backup/rekey/import and shutdown. Validate lost replies, stale callbacks, + runtime loss and all supported native/package targets before distribution. +- [x] Review resource deltas and whole-change contracts; retain only durable regression coverage, + run required checks and remove replaced main composition/lifecycle paths after parity. + +Completion: one backend owns active/durable business state, Electron main remains responsive under +the agreed workload, and ownership loss cannot create two profile owners or duplicate execution. + +The default application opens its profile through `CoreConnection.openProfile`, then assembles and +starts the actual SDK Kernel before exposing business readiness. Core owns the shared writer, +Harness, Provider, Memory, MCP, Session/Tape and scheduler composition. Main retains authenticated +ingress, read-only configuration projections and fixed Native effects; it constructs no writable +business shadow. Core retirement drains the Kernel, Browser, helpers, desktop effects, configuration, +Memory, admission and accepted callbacks before releasing profile ownership. Failed retirement +retains that ownership until physical process exit. The implementation items above are complete; +whole-goal acceptance is recorded below. Signed distribution remains release-gated. + +### Profile ownership and retirement + +The default profile entry acquires `.core-owner.lock` through an independent SQLite exclusive +transaction before any settings/crypto/recovery work. A pending startup and primary-database +maintenance retain that handle. Competing initialization fails before configuration or native +effects. No PID file, timeout takeover or live-lock deletion is used. The lock is outside `app_db` +and the named Sync backup/import entries, so maintenance never transfers its authority. + +`closeProfile` retires security/credential generations, closes the primary database and then +releases ownership. A primary-close failure keeps the lock. Actual final teardown calls this +critical barrier directly after request drain; it cannot be swallowed as best-effort cleanup. +Startup failure uses the same close path. Main's failed-start cleanup also closes the whole owner. + +All **95 checks across nine affected files pass under ordinary Node with native SQLite required**. +Five durable native cases cover pending startup, unchanged losing-owner configuration, maintenance +close/reopen, failed close, failed initialization, delayed credentials and real process death before +reacquisition. CI includes this native file in its required Store/Tape command. The native test +selector's return type includes Vitest's skip API; runtime selection and strict failure behavior +remain unchanged. Selected test types and the 82-file Memory type gate pass. + +The real profile/SQLite probe also runs under ordinary Node with Electron imports forbidden, +preserving persistence/reopen and failure cleanup. The candidate passes the same eight application +cases already accepted on immutable `83e799258`: encryption, all five maintenance/shutdown cases, +MCP child lifetime and file/database settings across relaunch. App types, normal build, +format/check including Markdown, lint and i18n pass. Evidence and hashes are in +`.deepchat/rutis-g5-profile-ownership-acceptance/`. This preparation adds no runtime/process or +performance claim; full Rutis composition, authenticated ports and writer/route/lifecycle cutover +are still required before G5 publication. + +### Route execution and native ingress + +The default application registers renderer IPC through `app/routeIpc.ts`. It authenticates the +native document before invoking the business dispatcher and builds caller identity from the native +sender. The dispatcher and app/ACP/MCP business route factories have no Electron runtime dependency. +App debug routes receive explicit packaged-build identity. Public routes, preload contracts and +structured values are unchanged; no JSON conversion or caller-supplied authority is introduced. + +Main-window/settings-window checks are acknowledged async queries. MCP authentication, credential +changes and app access await them before effects. Startup classification also awaits its query and +rechecks database admission before executing or scheduling work, preserving maintenance/shutdown +fences while a native answer is pending. Native registration consumes an invoke function, not the +business registry or database objects. + +All **133 checks across six affected files pass**, including delayed allow/deny, trusted caller +derivation and maintenance during a pending native query. Selected test types, app types, full +build, format/check including Markdown, lint and i18n pass. A real profile plus app-route dispatch +runs under ordinary Node with native SQLite and +Electron imports forbidden: 225 source files plus its one probe entry. This proves that route +boundary, not the complete Core process or a headless Session Host. The immutable `11eb02a87` +baseline and candidate each pass the same five app cases for native IPC, ACP questions, MCP +credentials, MCP Apps and persistent settings. Evidence and artifact/source hashes are recorded in +`.deepchat/rutis-g5-route-owner-acceptance/`. Main still owns the business composition and writer; +the full Rutis cutover remains required before publication. No performance recapture is selected. + +### Core execution composition + +The default application calls `src/harness/core/execution.ts` to construct the Harness, ACP Runtime, +AgentManager, Session projection/turn/assignment/deletion/lifecycle, CLI Runs, export/translation and +delegation feedback together. Main no longer constructs those owners inline. Native renderer +lifetime and browser teardown are supplied callbacks; native tabs and the floating presenter stay +in Main. The existing Harness/Session/Tape ports and synchronous local transactions are preserved. + +Interaction continuation, task-contract preparation, deletion safety and delegation summaries share +the factory's current delegation instance. Maintenance rebuilds it through the same factory and +updates that instance before resuming work. Live table resolution still follows database reopen. +The ownership guard also scans this Core directory, with no relaxed ownership rule or baseline. + +The affected Session/Harness/CLI/delegation suites pass **653 checks across 12 files**; all nine +ownership-guard checks pass separately. App types, normal full build, format/check including +Markdown, lint and i18n pass. The Core execution graph builds and imports under ordinary Node with +Electron/electron-store/font-list/electron-toolkit imports forbidden: 420 +source files plus the probe entry. This is an import-boundary check; actual factory construction +and execution use the real application comparison below, not a claimed headless backend. + +Immutable `34fad0512` and the candidate pass the same nine application cases: streaming, ACP question continuation, +delegation queue/cancel/release, manual/timed scheduler settlement, import/repair/credential drains +and both shutdown drains. Evidence, source and runtime hashes are in +`.deepchat/rutis-g5-execution-owner-acceptance/`. Core still executes in Main and receives local +Provider/Tool/Memory/Store owners from the remaining composition. Moving those owners, authenticated +transport, the shared writer and actual process lifecycle remain required for G5 publication. + +### Core data composition + +The default application calls `src/harness/core/data.ts` to assemble Provider settings, Session/Tape, +Memory tables, Agent/Project/scheduler data, delegation contracts, lifecycle gates, prompt settings, +legacy import and usage statistics. It reuses the initialized profile connection and attached +adapters. Main's inline constructors are removed. Tape head delivery and renderer lifetime are +supplied callbacks; business data instances remain local composition dependencies, not an RPC +schema. Migration table getters and the Session-to-CLI Run resolver keep their existing behavior. + +The relevant database, Agent, configuration, prompt, contract, watcher, migration and usage suites +pass **80 checks across ten files**, with native SQLite required. Existing composition assertions +follow the actual data owner. The real profile/data graph also constructs under ordinary Node with +Electron/electron-store/font-list/electron-toolkit imports forbidden: 233 source files plus its +probe entry. Actual checks preserve profile exclusivity, read/write after primary-database reopen +and prompt persistence after profile reopen. This constructs the data graph, not a complete +headless Session Host or a relocated Core process. + +Immutable `deb35ba5b` and the candidate pass the same ten application cases covering delegation, encryption/rekey, +maintenance/shutdown, Provider facts and overrides, MCP/ACP settings and mixed file/database settings. +Evidence, source and artifact hashes are in `.deepchat/rutis-g5-data-owner-acceptance/`. The shared +writer still opens in Main; remaining business composition, Rutis transport and process lifecycle +must cut over before G5 publication. App and changed-test types, normal build, format/check including +Markdown, lint and i18n pass. The normal generated Provider catalog refresh is retained separately; +its 44 checks pass and both catalog hashes are recorded with the application artifacts. No +process/IPC change selects a performance recapture here. + +### Core Provider and ACP composition + +The default application calls `src/harness/core/providers.ts` for Codex/Grok controllers and their +credential stores, Agent settings, ACP catalog/auth/runtime/persistence, Provider execution and +catalog updates. Adjacent CLI compute/transcription, Agent defaults and command-shell construction +share this composition. These factories remain parts of one Core capability, not separate plugins. +Main retains the OAuth facade with current-target Copilot commands and secret-free account +commands; browser, crypto, icon fetch and renderer lifetime/delivery remain supplied effects. + +The existing relevant suites pass **237 checks across 15 files**, without adding repository tests. +The immutable `de5c2fb63` baseline and candidate pass the same **14 application cases across seven +files** covering streaming, ACP questions, credential persistence/logout, current/stale Copilot +targets, ACP registry/install/repair and maintenance/shutdown drains. The actual profile/data/ +Provider/Agent/ACP/account/CLI-service graph also constructs under ordinary Node with Electron, +electron-store, font-list and electron-toolkit imports forbidden: 431 source files plus one entry. +It initializes Agent settings, reads account status and retires the owned services; private Native +effects are not invoked by construction. This does not establish a complete headless Session Host +or a relocated backend process. + +Evidence and hashes are in `.deepchat/rutis-g5-provider-owner-acceptance/`. The normal ACP registry +refresh is retained separately. App types, normal build, format/check, lint and i18n pass. Developer +requirements now match package engines and declare Rust/Cargo; the three README sponsor sections +and all content before their dependency sections remain byte-identical. Core composition still +runs in Main. Remaining business owners, private Rutis transport, the shared writer and process +lifecycle must cut over before G5 publication; no new performance capture is selected here. + +### Cohesive Core runtime composition + +The default application constructs Project, file preparation, Sync/replica, Knowledge, MCP, +Skills, Tool, Plugin, Hooks, Memory, execution, Remote and Scheduler through +`src/harness/core/runtime.ts`. The runtime owns the shared Plugin/Skill initialization barriers, +Skill scan and their initialization drain. Tool callbacks resolve the current delegation instance +after database maintenance. Existing transactions, live table getters and shutdown ordering remain. + +Main owns the Rust supervisor and injects admission/vector clients. Core composition does not +start a second Rust host. Workspace preview grants/protocols, open-application detection and the +native watcher host remain in Main; Skills receives the existing watcher interface. Browser, +renderer, crypto, helper spawning, proxy readiness and native notification effects remain explicit +dependencies. These local dependency objects are not a Core RPC contract. Main still owns the +profile writer, public ingress and application shutdown authority. + +The affected existing suites pass **540 distinct checks across 18 files**, including 29 Provider +catalog checks for the normal generated refresh. No repository test is added. The existing +composition checks share one source reader and follow the actual owners. The actual profile, +Provider and runtime graph constructs under ordinary Node with native SQLite and a live Rust +supervisor: **907 source files**, with Electron/electron-store/font-list/electron-toolkit imports +forbidden. The injected native watcher/desktop effects are not invoked. The Skill scan worker's +actual built module-path artifact is recorded separately and is not executed by this constructor +probe. This is not a complete headless CLI run or a relocated Core process. + +The first application batch passes 16 of 19 cases across 12 files; two native-close observations +and one prematurely closed startup fail. Business teardown reports completion in 0.1–0.3 seconds +where those failed-close logs are available. The final candidate and immutable `f511c078b` each +pass the same four targeted workspace, plugin-window and maintenance/close cases across three +files, with unchanged assertions and deadlines. The initial failures remain recorded without a +causal or performance claim; the other first-batch cases are not relabeled as a fresh final-build +comparison. Full G5 acceptance must cover actual native close/reopen after the process cutover. + +App types, normal full build, format/check, lint and i18n pass. The generated Provider catalog +refresh is retained separately. Evidence, source and artifact hashes are in +`.deepchat/rutis-g5-runtime-composition-acceptance/`. No performance/resource capture is repeated. +G5 remains active: remaining configuration/CLI/startup composition, business transport, +shared-writer transfer and Core/Native lifecycle cutover are required before publication. + +### Private Core profile bootstrap + +The Rust host can lazily mount a separate first-party Core Node worker through Rutis. Its private +bootstrap opens the actual profile and SQLite connection under the existing exclusive +ownership lease. Crypto and unlock/recovery presentation call Main through nine allowlisted Native +operations, bound to the actual Rutis caller and Core generation. Memory and Core reconcile through +one serialized layer owner so neither mount replaces admission or the other capability. + +Control frames retain the 64-KiB limit. Four ordinary calls, one status slot, one stop slot and +sixteen Native calls have independent bounds; bulk Memory and admission acquisition reserve pipe +capacity for Core control and Native replies. V8 serialization preserves existing Node values. +Values above 16 KiB use authenticated generation-private encrypted carriers, bounded to 64 MiB +and four retained files per sender. Larger legal public results still need paging or transfer +adaptation before route cutover; this private link is not an Everywhere or CLI artifact protocol. + +Stop awaits real initialization, Native effects and profile closure. Observation deadlines leave +missing outcomes unknown and do not free a call slot, retire the owner or permit replacement. +Failed primary close retains ownership. Core death retires its generation without automatically +adopting another one, and stale Core frames do not terminate admission. Cold profile/Kernel assembly +reports `businessReady: false`; acknowledged SDK Kernel activation reports readiness. The default +application still owns its profile and business routes in Main, does not start this lazy Core worker, +and has not transferred its writer or shutdown authority. + +Acceptance passes **39 distinct checks across seven affected files** and **five Rust checks**. +Seven new durable checks in two files cover encrypted carrier fidelity/tampering/quotas, a standalone +artifact's actual child-held SQLite lease, simultaneous Memory/admission, pending Native startup, +lost stop replies, physical Core death, stale calls and no replacement. The existing failed-close +check also verifies the authoritative ownership receipt. The artifact resolves Node conditions and +ships its target native SQLite binding; no browser shim, new dependency or nested supervisor is +introduced. + +Immutable `f511c078b` and the candidate each pass the same three application cases: streaming chat, +delegation admission and Memory eviction/model-change/worker-loss. This is a scoped default-path +regression comparison against the retained ancestor artifact, not a full Core business cutover. +App and changed-test types, normal build, format, i18n, lint and locked Rust format/clippy pass. +The existing Remote unsafe-finally warning remains. Normal generated Provider output is retained +separately. Evidence and source/artifact hashes are in +`.deepchat/rutis-g5-core-transport-acceptance/`. No performance/resource sampling is repeated and no +speed or memory saving is claimed. G5 stays active and unpublished until the actual default +composition, Native effects, public routes, writer and lifecycle handoff pass their acceptance. + +### Configuration business and Native projection receipts + +The default application and actual Core worker compose settings through +`src/harness/core/settings.ts`. Configuration owns desktop preferences, fonts, logging, OCR +preferences, Skill/trace settings, update channel, Agent defaults and command-shell preferences. +Main's inline constructors are removed. Provider composition no longer owns defaults or the +command shell; the cohesive runtime receives those existing instances from configuration. +Native theme/login/window/font/logging effects remain supplied ports with no writable shadow. + +The private Core configuration command supports validated settings reads/updates, its allowlisted +Native snapshot and desktop initialization. Mutations use the actual child-owned profile connection +and existing settings service. Thirteen fixed Native configuration operations validate their +inputs; ordinary settings observations cross as a batch of at most 64 existing event envelopes. +This uses the same Core generation, four-call admission limit and encrypted carrier transport, +without an additional plugin, process, dependency or generic store/property RPC. + +Configuration mutations now await the Native projection receipt before ordinary event publication +and caller acknowledgement. The owner queue retains that work through stop. A failed receipt +does not undo a durable write or authorize automatic mutation replay. Core stop fences queued +commands, drains active configuration and Native effects, then closes the actual profile lease. +The post-decode lifecycle check also fences work admitted before stop but not yet executed. + +Acceptance passes **214 distinct checks across 15 affected files**, including five actual Core +integration checks and 13 generated ACP registry checks. Two durable checks are added within +existing files for delayed projection receipts and child-held ownership during shutdown. The +standalone Core case additionally executes desktop initialization, configuration read/update, +Native publication and persisted values after profile reopen. Existing public route, configuration, +logging, fonts/projection, defaults, shell, OCR and composition suites remain in use. + +Immutable `f511c078b` and the candidate each pass the same five application cases for revision +ordering, legacy configuration persistence, desktop appearance/login observations, font/logging +restart and acknowledged Native projection/floating bounds. This is an ancestor-artifact +regression comparison; it is not a full Core business cutover. App and changed-test types, normal +build and refreshed affected artifacts, format/check, i18n, lint and locked Rust format/clippy pass. +Normal generated ACP output is retained separately. Evidence and source/artifact/resource hashes +are in `.deepchat/rutis-g5-configuration-acceptance/`; no performance sampling or gain is claimed. +G5 remains active and unpublished: the default profile writer, public routes and shutdown authority +still execute in Main. The actual cohesive runtime, CLI/startup/Toolchain composition, Native +effect/proxy/descendant authority and complete lifecycle handoff must pass before publication. + +### Shared Core admission and lost-operation settlement + +The actual Core worker obtains admission from the existing Rust/Rutis host through its SDK +connection. It reuses `RutisAgentInvocationAdmission` with a narrow transport, actual host limits +and existing lease/cancellation/error behavior. No second host, queue owner, Electron relay or +payload file carrier is introduced. Main and the one Core generation have separate bounded +command sequences while sharing the existing TS capacity, owner fairness and permit accounting. +This is a transport/ownership boundary; it does not implement the planned Rust control owner. + +The Rust host authenticates the Core row/key and generation, allows only existing admission +methods and reserves sixteen of the 1,024 call slots for control work. Overflow fences Core +authority and new execution rather than continuing across an uncertain sequence. Core closes +admission before releasing its profile. Core loss closes the global quota and rejects queued work; +unknown active permits remain fenced until the supervisor retires. The control path remains +observable, without authorizing replacement or claiming pending Native effects have finished. +Explicit observation flags support the supervisor's observer without duplicate local callbacks; +internal retirement retains the current flag. Default desktop process placement is unchanged. + +An actual in-flight Core-loss check exposes a transport reply that can precede the runtime-loss +notification. The Main client now treats that reply as an unknown posted-operation outcome and +fences the connection. It does not return a generic definite failure that could invite replay. + +Acceptance passes **99 distinct checks across six files** and **five Rust checks**. Two durable +checks and one SDK contract fixture cover the real host/Core link: shared Main/Core capacity, +independent ordering, cancelled acquisition, generation rejection, diagnostic closure, actual +Core death while holding a permit and Native effect, queued-work rejection and unknown outcome. +The fixture is a bounded service contract workload, not a complete Harness execution. Existing +Core profile/configuration/Memory coexistence, admission and delegation checks remain in use. + +Immutable `f511c078b` and the candidate pass the same three application cases for streaming chat, +delegation admission and Memory eviction/model-change/worker-loss. App and new-test types, normal +build and final affected artifacts, format/check, i18n, lint and locked Rust format/clippy pass. +No generated catalogs change. Evidence and source/artifact/resource hashes are in +`.deepchat/rutis-g5-core-admission-acceptance/`; no performance sampling or speed claim is made. +G5 remains active and unpublished. The actual cohesive Kernel, remaining +CLI/Toolchain/startup composition, public routes, Native effects/proxy/descendant authority and +default writer/lifecycle transfer still require their full acceptance before publication. + +### Core Memory transport and native ownership + +The private Core worker composes a lazy `RutisMemoryVectorStores` client using the same Rust +bootstrap, bounded upload gateway and native Memory worker as Main. Core calls the existing host +directly through authenticated SDK methods; vectors use SDK bytes and do not traverse Main or +encrypted payload carriers. This is not a zero-copy claim or the complete G6 Memory extraction. +Main or Core may own one Memory generation. A second owner is rejected, and Core retirement +preserves a Main-owned worker. Existing request, control, upload and native-part limits remain. + +Core drains stores and asks Rust to fence startup and confirm native child exit before releasing +its profile. Accepted startup remains owned until settlement; expiry cannot authorize takeover. +The host also retires Core-owned Memory after physical Core loss. Native loss makes the Core +client unavailable without replay or restart while leaving the Rust control path responsive. +Bootstrap includes explicit application/package paths, with no Electron import in the Core port. +Absent binary metadata fields are omitted before SDK encoding because `undefined` crosses as null. + +Acceptance passes **33 distinct checks across six files** and **six Rust checks**. Four durable +SDK/native cases and one fixture cover shared ownership in both directions, lazy worker reuse, +packed upload/list/delete fidelity, confirmed stop before native database reopen, physical Core +loss and physical Memory loss. One Rust contract covers startup retirement and late-start fencing. +Existing profile/configuration/admission and native vector regressions remain in use. Initial +fixture validation failures and the actual absent-field transport failure are retained in logs; +the final checks pass without increasing timeouts or weakening production limits. + +The final application passes the Memory eviction/model-change/worker-loss case. Its scenario is +byte-identical to immutable `f511c078b`, whose verified matching baseline and artifact hashes are +reused from the admission acceptance receipt. Main output is byte-identical to `9eea00f56`; the +affected Rust/Core artifacts are rebuilt. No unchanged baseline or performance matrix is repeated. +App and new-test types, normal build, format/check, lint, i18n, Memory test scope and locked Rust +format/clippy pass. Normal generated Provider output is retained separately. Evidence and hashes +are in `.deepchat/rutis-g5-core-memory-acceptance/`. + +Default Kernel, public routes, business writer and application lifecycle still execute in Main. +Full Core runtime/startup/CLI/Toolchain composition, Native proxy/descendant/effect authority, +stalled-startup shutdown and signed/all-platform lifecycle need acceptance before G5 publication. +G5 remains active and unpublished; G6-G12 retain their scope and dependency order. + +### Local control, Toolchain and startup composition + +The existing Core runtime now constructs the CLI server, request policy, shared approval broker, +command/file/settings permissions, programmatic dispatch and parent registry, audit and launcher. +Run/compute/audio/OCR/Skill dispatch uses its local services and the same database gate, Agent +token authority and artifact spool. Default Main wiring uses these objects and removes its inline +constructors. Trusted approval-target discovery and window request/close delivery remain native. +No additional plugin, process, abstraction, dependency or implementation switch is introduced. + +Toolchain discovery/demand, OCR runtime and attachment routing also compose with their consumers. +Main supplies the existing native OCR cache-key provider. Core organizes ACP/Skill startup +migrations, deferred MCP/Remote/Skill initialization, Provider warmup, legacy import, usage and +normalization backfill, capability cleanup and Toolchain GC. Existing task IDs and scheduling +semantics remain; maintenance supplies its new run ID, and failure delivery retains that actual +run identity. Main awaits migration completion before initial window admission and retains its +native floating-window effect and structured logging adapter. + +Acceptance passes **398 distinct checks across 20 affected files**, without new unit tests. +A disposable constructor check blocks Electron/native desktop imports at bundle and load time: +the graph of **915 source files** constructs the real SQLite-backed Core services, Rust +supervisor and local CLI listener under Node, then closes them. It establishes portability and composition, +not a complete Rutis Core execution or the headless CLI horizon. + +Immutable `f511c078b` and the candidate each pass the **same ten distinct application scenarios +across six files**: streaming, delegation, five maintenance/shutdown cases, Agent tools, OCR +native-key/cache persistence and CLI security approval/persistence. One durable application case +uses the built CLI and actual app to reject and approve a logging-security change, verify its +effect and reopen the profile. The initial batch passes the other nine cases on both artifacts; +its new CLI assertion uses the wrong denial code. The corrected assertion matches the documented +`approval_denied` contract and that case passes on both unchanged artifacts. Those initial results +remain recorded; only the corrected case is repeated, with no deadline increase or timing gate. + +App and new-test types, normal build, format/check, lint and i18n pass. Normal generated catalog +refreshes remain separate. Evidence and source/artifact hashes are under +`.deepchat/rutis-g5-core-control-acceptance/`. Default process placement is unchanged, so no +performance/resource capture is selected and no speed claim is made. + +G5 remains active and unpublished. The actual worker supports profile/configuration, cohesive +Kernel activation and bounded business ingress. Complete production Native callback/event +binding, descendant/proxy authority, default public route and lifecycle/maintenance adoption, +default writer handoff and supported signed/native package acceptance remain required. All +remaining goals keep their scope and dependency order. + +### Native settings navigation and theme observations + +Agent settings navigation now awaits the typed Native delivery receipt, returning `open_failed` +on a negative or failed receipt. The unused generic window-channel capability is removed from +the Agent port and its callers. The actual Core worker accepts one coalesced system-theme +observation independently of its four work slots and two status/stop slots. One work slot is reserved +for audited business control routes. It queries Native +state through the existing configuration capability and uses the same configuration/event queue. +Notifications during an active observation trigger a fresh query after its acknowledgement; +stop fences new observations and drains accepted Native effects before releasing profile ownership. + +Acceptance passes **160 distinct checks across 11 affected files**, including six actual Core +integration checks, plus six Rust tests and locked clippy. Durable coverage verifies navigation +acknowledgement/failure, observation coalescing, saturated ordinary calls and profile ownership +until actual Native completion. App/scoped test types, normal build, format/check, lint and i18n +pass. Normal generated catalogs remain separate. + +Immutable `f511c078b` and the candidate each pass the same three application scenarios: Agent +image/workspace access and actual MCP settings navigation with reopen, desktop theme/login +observations, and CLI security approval/persistence. The initial Agent assertion used the wrong +window path; correcting it to the existing `#/mcp` route requires repeating only that scenario. +Initial results are retained. Evidence is `.deepchat/rutis-g5-kernel-native-acceptance/`. + +Default process placement is unchanged, so no performance/resource capture is selected. Completed +acceptance copies, disposable profiles and media are pruned; compact evidence and the one needed +immutable baseline remain. G5 stays active and unpublished. The complete Kernel, writer, routes +and application lifecycle still require default Core adoption and acceptance. + +### Workspace watcher ownership + +- [x] Compose Workspace and its existing pooled watcher clients in Core, with required typed + helper launch supplied by Main; remove implicit Electron startup/path discovery from clients. +- [x] Fence pending startup, retired events and restart work during terminal shutdown; await actual + helper exit. Validate existing workspace/Skill consumers and matching application file/Git + events, then prune disposable acceptance artifacts. Default process placement is unchanged; + no performance capture or speed claim is selected for this boundary extraction. + +The default Main composition obtains Workspace and its pooled watcher clients from the existing +Core runtime. Main supplies an absolute helper entry and a plain cancelable command/event/lifecycle +port. Implicit Electron imports and helper path discovery are removed from the client. Canceled +startup and terminal shutdown await actual process exit; stale-host callbacks cannot mutate a new +generation. Pool teardown drains both helpers before reporting either failure. + +Workspace's four native file-open operations are acknowledged ports. Preview authorization stays +with the same business owner; scheme registration and response streams stay in Main. The protocol +queries the owner's asynchronous resolver and denies unavailable authorization. Shutdown fences new +work, waits for accepted desktop operations and revokes preview roots/exact files. File preparation +rechecks authorization after its await, preventing a late preview from restoring revoked access. + +Acceptance passes **235 checks across 12 affected files**, including eight durable additions for +helper startup/exit/restart, aggregate shutdown, desktop-effect drain and preview revocation. +Existing Workspace, installed-app launch, Skill, migration and composition coverage remains in use. +The obsolete concatenated source-order assertion is reduced to the actual Core startup admission +barrier. Two fixture types are corrected without weakening their contracts. New-test types, app +Node/web types, normal build, format/check, lint and i18n pass. The build-generated Provider catalog +remains separate. Initial failing fixture/guard logs are retained. + +Startup and desktop work register ownership before invoking Native effects. Synchronous shutdown +during fork cannot escape the drain, and queued effects cannot start after stop. The same accepted +contract selection and five candidate app cases pass after this lifecycle correction; the unchanged +baseline is reused. + +Immutable `f511c078b` and the candidate each pass the same **five application cases**: Workspace +read/search/Git routes, actual file/Git watcher invalidations with graceful shutdown, HTML module +preview isolation, preload navigation isolation and unauthorized-document IPC rejection. No new +application case is added; the watcher case now requires graceful close. A Node import probe +blocks Electron/native desktop dependencies across 928 source files; it proves module portability, +not actual complete Kernel execution. Evidence is `.deepchat/rutis-g5-workspace-acceptance/`. + +No new plugin, process, dependency or fallback is introduced. Disposable probe bundles are removed; +compact receipts, raw outcomes and the one needed baseline remain. G5 is active and unpublished: +actual SDK Kernel assembly, callback/event transport, default routes/writer/lifecycle adoption and +native package acceptance remain required before publication. G6-G12 retain their full scope. + +### Native helper command and event transport + +- [x] Implement fixed helper commands, Core proxies, a Native owner and the reserved event lane + in the Core link. Retain Main launch/RSS ownership and existing helper protocols; + preserve cancellation, callback ordering and actual process retirement with bounded queues. +- [x] Use these ports in actual SDK Kernel assembly and validate lifecycle, saturated ordinary + calls, payload bounds and matching application behavior. Full G5 writer/routes/lifecycle + adoption remains required; transport tests and constructors do not establish completion. + +The actual Core worker owns its helper proxy and event endpoint. Main's helper owner accepts only +five approved kinds, validated protocol messages and owned-host RSS requests. Startup admission is +separate from the opened/failed event. Cancellation can retire pending startup; an uncancelable +late host is compensated. Native Exec retirement awaits the owned host's asynchronous kill +receipt, which covers actual exit and profile-scoped registered-command recovery. The former +arbitrary-PID recovery callback is removed. Unexpected exit stays owned until recovery settles; +the execution service waits for that same receipt before replacement startup or shutdown. +Complete descendant/fd authority still requires acceptance during Kernel adoption. + +Native calls retain their sixteen-call bound with two retirement slots; ordinary work has a +64-request/64-MiB waiting bound. Queued values are snapshots and stop rejects queued ordinary work. +Helper commands/events have bounded queues, data snapshots and ordered batches; concurrent kills +coalesce into groups of at most 32. Unknown identities, malformed events, overflow and uncertain +delivery fence the generation. Retirement remains possible after logical failure, and accepted +Native effects receive their terminal outcome after actual settlement. No automatic replay occurs. + +Background execution, Scheduler and Code Mode native startup adapters now wait for actual exit +before rejecting canceled/failed startup. Their matching existing tests require this receipt; +an already exited process needs no repeated kill. Existing launch flags, Code Mode's restricted +environment/heap/library policy and Main-owned RSS sampling are preserved. + +Acceptance passes **169 distinct TypeScript checks across 15 affected files** and all six locked +Rust checks. Eight durable additions cover helper protocols and ownership, batched retirement, +late startup and descendant recovery, held RSS, queue bounds, Native-call admission and the actual +SDK event lane under saturated ordinary calls. The SDK fault case retains profile ownership until +an accepted Native effect settles, then proves real stop and reopen. Native SQLite is required. + +Immutable `f511c078b` and the candidate each pass the same **six application cases** across three +files: manual/timed Scheduler work and persistence, background execution and held-start shutdown, +and Code Mode execution and held-ready shutdown. This is default-application regression evidence; +SDK Kernel activation separately exercises the helper link with in-process Native owners. Main's +complete production helper factory and attested +Exec-recovery binding remain required at that adoption point. + +Normal build, Node/web and affected-test types, format/check, lint, i18n, Rust fmt and Clippy pass. +Evidence is `.deepchat/rutis-g5-native-helper-acceptance/`; no performance capture or claim is made. +No new build/profile copies are retained. G5 stays active and unpublished until complete Kernel, +writer, public-route and lifecycle adoption with old-path removal and native/package acceptance. + +### Native Browser dispatch and authorization transport + +- [x] Implement fixed Browser commands and callback receipts in the Core SDK link. Preserve + normalized dispatch commitment, cancellation, recoverable errors and native retirement; + serialize Browser/helper callbacks through the existing reserved event slot. +- [x] Bind this Browser port and Main's physical-retirement capability into the default SDK + Kernel, including user-opened pages when no Agent Browser tool was called. + +Core keeps the actual authorization callback and returns a distinct denial outcome without +fencing a healthy generation. Main retains native Browser objects and normalizes target arguments. +Canceled late admission is compensated; unknown callbacks and repeated authorization are fenced. +Only promised commits introduce an asynchronous wait. Native navigation and CDP dispatch recheck +cancellation and target lifetime after that wait. Current synchronous commit timing is retained. +Main's required retirement receipt must cover effects that survive a canceled caller wait. + +The helper and Browser sources share one bounded snapshot queue for the reserved event lane. +Browser cancellation/close reuse the two Native retirement slots. Recoverable Browser-unavailable +payloads and the original Core commit errors survive transport; no uncertain operation is replayed. + +Every assembled Kernel retires Main's Browser owner before profile release. Agent-tool usage does +not determine ownership of pages created by public desktop routes. Profile-only Memory/Admission +SDK operations do not acquire the desktop owner. Main caches each page's close receipt, fences new +page/preview allocation during shutdown and awaits the actual WebContents `destroyed` event without +a before-unload veto. Physical close failures remain failed and retain ownership; all pages are +attempted. Destruction callbacks address the same state object, so a late event cannot erase a +replacement with the same Session identity. No new capability, process or transport is added. + +Current verification passes **77 distinct TS checks across seven files**: 59 Browser presenter, +handler, caller and transport checks plus 18 actual SDK profile/Memory/Admission checks with native +SQLite required. Two small durable additions protect held physical destruction with a neighboring +close failure, and profile writer exclusion until the Native Browser receipt settles without Agent +Browser calls. The latter still passes after moving its setup under guaranteed fixture cleanup; +the targeted confirmation is not added to the distinct case count. Normal build, Node/web and +scoped test types, format, lint and i18n pass. Rust/Cargo and catalog hashes are unchanged; retain +their existing eight Rust/fmt/locked-Clippy and catalog evidence rather than rerunning those matrices. + +The current app passes **seven distinct scenarios**: the new Browser retirement case and six +first-message Workspace/Browser, image, OAuth and CLI regressions. The Browser case holds response +headers, response-body reads and a WebSocket, verifies they are still live before retirement, and +exercises explicit page destruction, normal quit and Core-owner failure. Each path physically +closes the three requests and reopens the persisted Session with a fresh uninitialized Browser. +The foreground focus case remains pending an unlocked Mac and is excluded from this selection. + +The same immutable f511 fixture passes explicit destruction and graceful quit, with physical network +closure receipts. It fails at line 232 in the Core-failure phase: refused Native renderer registration +leaves the process alive under the unchanged 30-second observation deadline. Later failure-phase +network closure/reopen assertions do not execute. The result is 0/1 complete cases, not proof that +the baseline cannot close ordinary Browser requests. The real SDK receipt case separately proves +profile exclusion for the migrated ownership boundary. Current/frozen trees, fixture and external +dependency hashes are restored and verified in `browser-retirement-comparison.json` under +`.deepchat/rutis-g5-kernel-assembly-acceptance/`. + +No new resource capture or performance claim is selected: the existing shutdown receipt changes +no process placement, transport or hot-path batch. Shared-CI timing remains report-only. Compact +raw reports and the one required immutable baseline remain; completed media and temporary configs +are pruned. G5 remains active/unpublished. Other Native/concurrent faults, lost ancestry/privilege changes, unlocked foreground and supported +signed/platform acceptance remain separate work before whole-G5 review, publication and exact-head +CI. G6-G12 remain unstarted. + +The browser download manager, its unused presenter method/type and obsolete preferred-page selector +are removed. No typed route or Agent tool called them; their constructor installed a default-session +listener that could never match an admitted download. Browser-origin downloads retain their existing +Chromium/Main application ownership. Upgrade and image downloads are separate active capabilities. + +Active Native views use one small `closeWebContents` implementation. Browser pages join it through +their existing owner; the temporary cookie-inspection view joins it in `finally`, including a +failed debugger detach. Application windows and floating UI belong to their Main application owners. +The old Tab presenter has no view-creation callers; its floating-window registration is a borrowed +capture/context lookup. It is not a second Native view owner or an additional Core teardown gate. +Its existing source and the Main composition retain their accepted hashes. + +The cleanup passes 75 existing/new checks across five Browser/import/composition files. Two compact +cases protect temporary-view closure for successful reads and failed debugger detachment. The +import fixture models Electron's documented empty defaults for omitted cookie names/values; +scoped test types include this fixture. Normal build, Node/web types, lint/i18n and formatting pass. +The current application passes seven Browser, MCP/private account, image and Copilot cases. The +same Browser fixture and frozen artifact/dependency hashes reuse the accepted baseline report; +no unchanged baseline rerun or new resource capture is selected. Core and Rust artifacts are +byte-identical to the preceding Browser receipt. The normal ACP registry agent refresh is retained +and passes 16 registry/migration checks; Provider catalog hashes are unchanged. The retained +Browser baseline exercises an OpenAI Session and no ACP Agent operation, so its scope establishes +page/request retirement rather than refreshed ACP catalog parity. + +### Cohesive Kernel root + +- [x] Compose the existing data, Provider and runtime factories with one event hub, session event + router, CLI token authority/artifact owner, startup coordinator and MCP Apps registry. + Keep native presentation/helper ports explicit and maintenance/route controls owner-local. +- [x] Invoke this root in the actual SDK worker with the fixed Native desktop transport and + standalone dependency closure; verify persistence and physical retirement before writer release. +- [x] Mount the owner-local route and maintenance controller. Reuse it in the default Main + application; remove its inline route/maintenance construction and SDK placeholder errors. +- [x] Activate the actual SDK Kernel and route business requests through its local controller, + with bounded queues, reserved control, Native caller revalidation and startup/shutdown fencing. +- [x] Adopt public ingress, shared writer and lifecycle in the default Core process. Whole-G5 + fault/resource/native/platform acceptance remains required before publication. + +`createCoreKernel` owns the cohesive business graph and its terminal drain. The same settings +version source feeds configuration events; renderer-session ownership retains its `null` meaning. +Shutdown fences request admission before draining startup, services, helpers, Memory ingestion, +Providers and outstanding requests. Failure of a partially constructed owner requires physical +retirement; it cannot prove writer release. The profile owner still releases its lock only after +the caller has also drained the Native bridge and shared admission/Memory resources. + +A native-free Node probe blocks Electron imports across **932 source files**. It runs real startup +migrations/Skill initialization, a CLI listener, detached-session creation/query/event delivery, +exclusive-writer rejection, terminal drain and persistent profile reopen using native SQLite and +the actual Rust supervisor. Native presentation is a controlled empty fixture, Skill watch launch +is unavailable, and no full route dispatcher is installed. This is composed-root evidence, not +successful SDK/default Kernel adoption, native-helper acceptance or headless CLI execution. + +The probe exposed a real late write: login-shell PATH refresh recreated `toolchains/state.json` +after profile teardown. The runtime now fences that callback when stopping and drains its accepted +refresh. Concurrent environment reads share one shell; a timed-out shell is hard-stopped and its +promise settles only after actual process/stdio closure. Failed or invalidated results do not +replace the current cache. This keeps the existing timeout and platform fallback behavior. + +The Sync token-storage guard now acknowledges an asynchronous Native check before wrapping a +token. Optional Computer Use preview eligibility can also await Native state while the completed +tool response remains independent; capture rechecks live policy and cancellation after the wait +and catalog validation. One existing preview test covers delayed eligibility and revocation; +one durable shell test covers shared startup and actual retirement after timeout. + +Affected service checks retain **150 tests across four files**. The actual SDK acceptance passes +**26 checks across seven files**, including Kernel assembly from a harness copied outside the +checkout, configuration events through its general event hub, persisted settings and exclusive +writer ownership while Native retirement is held. Two desktop contracts cover reordered aborts, +released cancellation identities, shared renderer subscriptions and physical closure. The private +assembly phase keeps `businessReady: false`. Its local dispatcher and snapshot-import controller +are mounted; default cross-process ingress/startup adoption remains required. + +The standalone build preserves Node package entry semantics, platform binaries, PDF assets and +dependency-specific versions. Its Skill scan entry runs successfully from a separate copied module +closure. Renderer client, Model store and Chat status-bar coverage passes **177 checks across three +files**, including one durable catalog-event regression. The existing MCP runtime application case +also passes. SDK assembly uses empty Native presentation and does not activate full startup or +default cross-process public ingress; these checks do not prove default adoption or headless CLI execution. +Evidence is `.deepchat/rutis-g5-kernel-assembly-acceptance/`. Complete default adoption, +helper/descendant and proxy authority, and native/signed packaging remain required. G5 is active +and unpublished; G6-G12 retain their full scope. + +### Owner-local routes and database maintenance + +The Kernel constructs one route dispatcher from its own data, settings, Provider and runtime +owners. The default application uses the same factory for its business routes and adds only its +native desktop/device/update/notification maps. Its inline business-route and maintenance bodies +are removed. Sync snapshot import now enters the local maintenance controller directly; no shared +database operation or mutable service callback is proxied through Main. + +Maintenance retains admission fencing, backup exclusion, configuration/secret stop and drain, +session/ingestion shutdown, database checkpoint/import/rekey/repair, reopen and ordered service +recovery. Recreated delegation state stays at the execution owner's getter. Kernel stopping also +fences the SDK configuration ingress rather than leaving a writable configuration service alive. + +Copilot credential-target selection, access validation and acknowledged commitment use the Core +OAuth facade. Native authorization presentation/token acquisition remains a typed Main capability; +its flows propagate cancellation and await owned-window, request and response-body retirement. +Complete production Core Native binding remains required before default adoption. +Plugin-window ownership checks await their Native query, while Main sender +authorization retains a synchronous registry and rejects promise/object results. Proxy mutations +await Native application and SDK dispatcher synchronization. The SDK preserves the existing +single-probe system policy, custom authentication and bypass rules; default-process adoption +still requires whole-goal acceptance. + +Existing affected verification passes **65 Main checks across five files**, **26 SDK/Native +checks across seven files** and **13 request-gate checks**. The default application passes seven +real scenarios: encrypted legacy +unlock/rekey/disable, Provider credential compatibility/logout, incremental/overwrite restore, +concurrent-maintenance rejection, delayed Native credential drain and both pending-work shutdown +cases. Two durable cases fence an uncertain Native pause until failed-owner cleanup settles and +wait for both session backends even when one cleanup fails; +existing fixtures exercise asynchronous plugin ownership and reject malformed Native ownership +responses. Native pause/resume acknowledgement failures cannot leave ordinary requests admitted. +The accepted baseline evidence remains available; +no unchanged baseline or performance matrix is repeated. G5 stays active and unpublished until +complete default adoption, Native retirement/descendant/proxy authority and required packages pass. + +### Core business ingress and activation + +The private SDK link dispatches existing renderer route contracts to the Kernel's local registry. +It rejects CLI/internal identity injection, desktop-owned routes and incorrect control-lane use. +CLI and internal calls retain their business owner's existing local authorization. Main's Native +IPC entry supplies a revalidation closure for its original sender/document; the Core connection +uses it at admission and after carrier preparation before sending. The closure stays in Main. +Queued requests from a retired document fail without reaching the business owner. + +The four existing work slots contain three ordinary slots and one audited cancellation/human-answer +slot. Status and stop retain their separate reserved slots. Waiting requests share a 64-entry, +64-MiB snapshot budget, and control pressure can reject unsent ordinary work explicitly. No accepted +operation is displaced or replayed. Startup accounting queries Native window roles only for its +tracked routes; approval and cancellation do not wait on unrelated window queries. Carrier limits +remain unchanged, and a legal 3-MiB request/result round trip retains the complete value. + +`startKernel` activates migrations/Skills, Scheduler, Memory maintenance, local CLI/artifacts, +Sync and the existing deferred/background work. Optional service failures retain their existing +reporting behavior. Only completed activation reports `running`/`businessReady: true`; business +ingress rejects cold or stopping owners. Stop fences new work and drains activation, all accepted +route continuations and actual Native effects before releasing the shared profile lock. + +Actual SDK verification uses a harness copied outside the checkout, native SQLite, and the existing +FileWatcher/Scheduler algorithms supplied as in-process Native fixtures. It exercises real Core +business handlers, full queue pressure, approval progress, stale-caller rejection, immutable queued +input, large data fidelity and persistence/reopen. One additional durable case holds an accepted +Native effect during activation, confirms status/stop progress and writer exclusion, then verifies +retirement without late CLI startup. These fixtures do not prove OS helper launch, external native +presentation, default Core adoption or the headless CLI horizon. + +Validation passes **164 TypeScript checks across 12 files**, **six Rust checks**, Rust fmt/locked +Clippy, normal build, Node/web/affected-test types, format/check, lint and i18n. The existing Remote +unsafe-finally warning remains. Seven application cases across three files pass: first-message +Workspace/Browser behavior, five maintenance/shutdown scenarios and CLI security approval with +reopen. Accepted baseline evidence remains available. One durable activation-race case is added; +existing SDK routing and Native sender cases cover the remaining contracts. No new test file or +temporary application API is introduced. Evidence is +`.deepchat/rutis-g5-kernel-assembly-acceptance/`; completed media/profiles/bundles are pruned and the +one needed immutable baseline remains. + +The default application still constructs its writer and runtime in Main. G5 remains active and +unpublished until complete production Native binding, default writer/routes/lifecycle handoff, +old-path removal, whole-goal acceptance and native/signed packages pass. No performance recapture +or speed claim accompanies this private ingress/startup work. + +### Native Copilot authorization retirement + +The default Main adapter creates one browser/device authorization operation per invocation. The +shared global device-flow instance and polling interval are removed. Each operation owns its +window, timers and HTTP work, propagates cancellation and waits for actual closure. Device polling +is serial, respects expiry and server slowdown, and cannot accept a token while an OAuth error is +present. The instruction page retains its layout and only opens the fixed GitHub URL or copies +its current code/link. Browser callbacks require the exact configured endpoint and nonce; callback +URLs and authorization codes are not logged. + +Window-local navigation/script commands can lose their replies during destruction. The actual +closed event retires those frame-local effects; independent external-browser work still requires +its own acknowledgement. The shared bounded response reader coalesces and awaits underlying +stream cancellation before releasing its reader. Existing body limits and presentation/transport +deadlines remain unchanged. + +The Core OAuth facade tracks accepted target reads, authorization, access validation and durable +credential commitment. Stop fences new work, aborts owned operations and waits for every accepted +login and sibling account/target retirement, including failure paths. It cannot acknowledge an +uncertain Native effect merely because another account stop failed. No new plugin, process, +dependency, persistent store or application debug route is introduced. + +```text +BEFORE +Quit -> abort flag -> authorization may remain active + +AFTER +Quit -> cancel request -> owned window closed + stream canceled -> owner drained +``` + +The same real-app acceptance scenario exercises credential commitment, stale-configuration +rejection, an open native authorization window during quit and persistence after reopen. The +immutable baseline requires forced exit with the pending window. Native unit contracts also hold +window closure, external browser acknowledgement and stream cancellation independently; a lost +frame reply is retired only by actual closure. Complete default Core writer/routes/lifecycle, +production helper/Browser bindings, descendant/proxy authority and native/signed packages remain +required. G5 remains active and unpublished. + +Validation passes **122 TypeScript checks across ten files**: 108 Native/auth contracts, 11 SDK +ownership/desktop contracts and three renderer checks. Six durable Native cases extend the existing +file; private-method spies are replaced with real public flow behavior. Existing stop cases hold +accepted authorization, late headers and underlying stream cancellation through account failure. +The seven affected app scenarios pass: six credential/maintenance/shutdown cases and the current +Copilot case with its actual closed-window receipt, no post-stop token poll and persistent reopen. +The response-body/frame corrections retain the bounded failure contracts; no presentation deadline +or performance gate is increased. Normal/current builds, Node/web/affected-test types, format/check, +lint and i18n pass, with only the existing Remote warning. Rust is unchanged. Evidence is +`.deepchat/rutis-g5-kernel-assembly-acceptance/`; completed heavy artifacts are pruned while compact +receipts and the one needed baseline remain. + +### G5 remote session window boundary + +Remote `/open` uses Native selection, Core-owned activation and Native presentation. Main keeps +the focused-chat/first-chat/create-window behavior, checks shutdown and validates the current +window/renderer pair before showing it. The former `openRemoteSession(sessionId)` callback is +removed; no session identifier or business activation crosses into Main's window operations. +Desktop bindings own the existing renderer-destruction subscription, release it on unbind and +reject activation when registration observes destruction. A late lookup cannot clear a newer +session binding. No dependency, process, debug route, performance threshold or UI layout is added. + +Validation passes **200 checks across six Main/SDK/session files**, including two new durable +renderer-lifetime cases in the existing file and extended Native result validation. Session test +fixtures match the current assignment and pending-queue contracts. One real application case +passes on both the immutable baseline and current build through public settings, pairing and +incoming Telegram commands: no-session rejection, existing-window activation, native destruction, +new-window creation, the same active session, independent settings-window binding and graceful +shutdown. Telegram HTTP is controlled by the test; no model request or external message is sent. +Normal build, Node/web/affected-test types, format/check, lint and i18n pass, with the existing +Remote warning only. Rust is unchanged and no performance matrix is repeated. Compact receipts +remain in `.deepchat/rutis-g5-kernel-assembly-acceptance/`; finished media are removed and the one +needed baseline is preserved. The default writer/routes/lifecycle still run in Main. Complete +Native/helper/Browser binding, descendant/proxy authority, default Core adoption and whole-goal +fault/native/signed package acceptance remain required; G5 stays active and unpublished. + +### G5 desktop business route ownership + +The existing Core settings map owns eleven language/theme/shortcut/floating-widget/update-channel +handlers. The Core Session map owns browser-focus and Computer Use preview admission, preserving +active-session/draft-owner checks and the inactive-preview stop exception. Main supplies three +fixed Native presentation methods, validates the current renderer/window pair and checks shutdown; +it does not read Session business state for these effects. Native desktop and upgrade maps hold +no AppSettings or Session owner. TabPresenter has no Session-binding dependency; renderer +destruction owns binding retirement. Existing desktop route names, processes and resource bounds +stay unchanged. + +The floating widget consumes two typed Session routes and the existing ACP icon query through +the Core dispatcher. Its Native presenter has no AgentSettings, SessionQuery or Session-binding +dependency. Core filters and orders regular sessions and shares the existing desktop-session +activation operation with Remote. Main keeps geometry, appearance and expanded state; all widget +IPC reads/actions authenticate its exact owned top-level document. Ordinary renderer APIs remain +unavailable to that document. One in-flight snapshot read coalesces updates and rejects retired +window replies. Native initialization acknowledges creation/appearance without awaiting a Core +query; the window displays after its initial refresh, avoiding startup reentrancy. + +Validation passes **144 checks across seven files**: 133 route/settings/SDK/Native checks plus eleven +window/binding checks. The actual standalone SDK exercises configuration writes/reads and preview +admission for owned drafts, active sessions, deactivation and cleanup. One durable Native identity +case extends the existing file; other coverage extends existing cases. Normal build including +Node/web types, affected-test types, format/check, lint and i18n pass with the existing Remote +warning only. Rust is unchanged. The real settings/shortcut/floating-widget persistence scenario +passes on the immutable baseline and current application. Foreground browser-focus acceptance is +pending an unlocked Mac: Native observation confirms a visible attached browser with a live +session while the OS reports the host window unfocused, and the desktop tool reports a locked +session. Both artifacts reject focus under that condition. The full foreground scenario and its +Native readiness check remain; no security guard or functional deadline is relaxed. Its receipt +is not counted as a pass. G5 remains active and unpublished, with default Core writer/routes/ +lifecycle adoption, remaining production Native bindings and whole-goal acceptance still required. + +Widget validation passes **129 checks across five existing files**, including actual standalone +SDK snapshot filtering, missing-session rejection, Core activation and the resulting binding. One +durable Native case verifies startup acknowledgement with a held Core read, no premature display, +replacement-window isolation and foreign-reader rejection. The latest Native/layout and SDK +selections pass fourteen and nine checks respectively. The same two real-app cases pass on both +the immutable baseline and current build: scoped widget IPC, snapshot/session opening/collapse, +plus settings/shortcut/floating-bounds persistence and reopen. Normal build, Node/web and affected +unit/E2E types, format/check, lint and i18n pass; Rust and generated catalogs are unchanged. No +benchmark matrix, timeout increase, new test file, dependency or process is introduced. Current +and baseline artifacts are restored and hash-verified after comparison; finished profiles and +bundles are pruned while the sole baseline and compact evidence remain. + +### G5 MCP App protocol ownership + +The Core runtime constructs the single MCP App registry used by its source/tool authorization +owners. Main's startup and protocol layer hold only two asynchronous policy/permission operations; +their registry construction, direct revocation and owner-subscription map are removed. Core owns +one renderer subscription while an owner has creations/views, cancels pending consent on retirement +and releases unused subscriptions. Maintenance clears the registry before database drain. Terminal +close also rejects new reservations, including a route admitted before shutdown that reaches view +creation later. Native owner validation and separate-origin protocol responses remain in Main. + +The SDK exposes the same two operations through a fixed private, schema-validated protocol. They +share the existing bounded ordinary queue and business drain; Native owner destruction uses the +existing reserved event lane. Human answers/cancellation and status/stop keep their reserved slots. +No public route, process, dependency, queue limit or functional deadline changes. + +Validation passes **49 affected checks across six files**, **75 catalog checks across five files** +and **six Rust checks**. One durable registry case covers shared subscription ownership, pending +consent retirement, synchronous owner destruction and terminal admission. Existing actual SDK +coverage verifies cold/unknown-instance denial and protocol progress after queued ordinary work, +while human control and status remain available. The same MCP App application scenario passes on +immutable `f511c078b` and the current build: saved sources, Native CSP/permission policy, consent, +revocation, graceful shutdown and reopen. Six current application regressions also pass: scoped +widget session opening and five maintenance/shutdown cases. Current/baseline Main and Rust hashes +are restored and verified. Normal build, Node/web and affected unit/E2E types, format/check, lint, +i18n, Rust fmt and locked Clippy pass, with the existing Remote warning only. Normal Provider/ACP +refreshes remain separate. Compact receipts and the one needed baseline remain; fixture profiles +and bundles retire automatically. Default writer/routes/lifecycle still run in Main. G5 remains +active and unpublished pending full Core adoption, remaining Native bindings and whole-goal +fault/native/signed acceptance; foreground browser focus still requires the unlocked Mac. + +### G5 desktop host controls + +Main's Workspace preview, Provider-deeplink and quit-preparation callbacks use the Core runtime's +host facade. Main no longer holds Provider settings, the three permission services or the approval +broker for these operations. Preview/Provider queries enter the database gate and existing bounded +ordinary queue. Two fixed host controls use the reserved Native event lane: permission clearing +and shutdown-confirmation data. Knowledge returns the existing dialog data and has no dialog +dependency or presentation method; Main presents it and interprets the response. The event lane +does not wait for user input. No public route, resource bound, process, dependency or deadline changes. + +Quit/update preparation awaits permission retirement. Update installation records its shutdown +promise before the first await, preserving exclusion of concurrent requests. Failed quit preparation +is reported and leaves the shutdown claim uncommitted. Kernel retirement clears permissions before +the CLI server drains. Private host operations join accepted business work before writer release; +ordinary query denial does not invalidate an otherwise live Native bridge. + +Validation passes **49 affected checks across five files**, including the actual SDK under full +ordinary load, plus **75 catalog checks** and **six Rust checks**. One durable Knowledge case covers +idle/pending confirmation data; SDK coverage extends its existing case. Eleven app scenarios pass +across Workspace/preload isolation, Provider deeplinks, CLI approvals, MCP Apps and five maintenance/ +shutdown cases. The final app build passes the same two affected preview/deeplink scenarios as the +immutable baseline. Normal build, final app rebuild, Node/web and affected unit/E2E types, format/ +check, lint, i18n, Rust fmt and locked Clippy pass, with the existing Remote warning only. Normal +Provider metadata refresh remains separate. Disposable profiles/bundles retire; compact evidence +and the one required baseline remain. Default profile/writer/routes/lifecycle still run in Main; +production helper/Browser retirement, default Core network adoption and +whole-goal acceptance remain required. G5 stays active and unpublished; locked-Mac foreground +acceptance is unchanged. + +### G5 Core HTTP ownership + +Main publishes its committed effective proxy URL, merged bypass rules and monotonically increasing +revision through one fixed Native operation. The SDK Kernel initializes its own Undici dispatcher +and proxy environment before constructing Provider/runtime owners. Proxy-setting acknowledgement +waits for that synchronization. Stale revisions are ignored, equal effective policies reuse the +pool, and failed Native resolution leaves the last committed policy intact. System resolution +preserves the existing single probe and first HTTP proxy result; per-destination PAC evaluation is +outside this change. Credential-bearing proxy URLs are no longer logged directly. + +Replaced pools close gracefully while accepted requests finish. Kernel stop destroys both current +and draining pools at the beginning of retirement, then waits for closure before releasing the +writer. Existing library/native transport owners retain their own cancellation contracts. Main +uses the same pure dispatcher/environment helpers; its default process placement is unchanged. + +Validation passes **25 checks across three existing files**, including the actual standalone SDK +with local HTTP endpoints: authenticated proxy requests, bypass, direct mode, live policy changes, +in-flight response completion, shutdown of current and draining connections, and profile reopen. +One durable SDK case covers this network/lifecycle contract. Existing Native tests cover snapshot +readiness, revision changes and last-good resolution. Normal build, Node/web and affected-test +types, format/check, lint and i18n pass. Rust and generated catalogs are unchanged, so their +unchanged suites are not repeated. No performance result is claimed. + +The immutable `f511c078b` application selection passes configuration reads and two Provider +save/error flows. Its Provider probe scenario exposes the startup-route result-sharing defect +described below. The corrected current artifact has passing evidence for all four scenarios, +including the complete probe/credential/save-error flow. Compact reports and restored baseline +hashes are in `.deepchat/rutis-g5-kernel-assembly-acceptance/`. Source changes remain uncommitted +and unpublished. +Default Core adoption, production Native/helper/Browser retirement, descendant fd/environment +authority, foreground focus and complete fault/native/signed acceptance remain open. G5 is active; +G6 has not started. + +### G5 startup route result isolation + +Each tracked business request has its own startup-work identity. The coordinator retains shared +progress labels and the existing CPU/IO concurrency limits, while arguments and caller identities +cannot inherit another request's result or authorization. Query caching stays with the existing +clients. This fixes concurrent Provider catalog reads returning the first Provider's catalog and +incorrectly marking an empty model list ready. The shared route dispatcher applies the same rule +to the default Main runtime and SDK Kernel; no new public route, dependency or process is added. + +Validation passes **102 distinct checks across five Main/SDK/Native files**, including a single +durable concurrency case using the real coordinator with different Provider IDs and caller IDs. +Existing checks retain maintenance revalidation, cancellation and accepted-handler drain. The +current application passes all four selected configuration/Provider scenarios. The complete +Provider flow covers model selection, denied/successful requests, saved credentials, disabled +Provider persistence, custom model filtering, rejected writes and retained input. Its fixture +explicitly disables the enabled-only filter before selecting an inactive model and uses current +localized save/error labels. Temporary read-only diagnostics are removed. + +Normal build, Node/web and affected-test types, format/check, lint and i18n pass. The normal +Provider catalog refresh passes **75 checks across five files** and is held separately in +`fe4ea751e`; G5 source remains uncommitted/unpublished. No unchanged benchmark, deadline increase +or Rust suite is repeated. Compact reports/source hashes and the sole needed baseline remain; +completed profiles and media are pruned. Default cross-process adoption, production Native/helper/ +Browser retirement, descendant authority, foreground focus and whole-goal fault/native/signed +acceptance remain required. G5 is active; G6 has not started. + +### G5 registered execution retirement + +The production Native Exec host owns an asynchronous kill receipt and replays exit to late +subscribers. Retirement is registered before synchronous exit callbacks can reenter it. It captures its helper PID +before exposing commands, waits for physical exit and performs profile-scoped recovery of that +helper's registered command roots using the existing start-time/command-line attestation. Unmatched +or unobservable identities are not killed. Unreadable/corrupted records and incomplete recovery +reject the receipt; live owned records cannot expire merely because they are old. Scoped recovery +requests serialize without sharing another owner's result, while startup recovery retains its +once-per-subsystem behavior. + +The default Main execution service awaits this receipt during shutdown, canceled startup and +unexpected-helper recovery before allowing replacement startup. The Core proxy forwards it through +the existing termination command. Its Native helper owner retains exited Exec hosts until that +receipt settles and propagates failure. The separate arbitrary-PID recovery callback is removed. +Other helper kinds keep their existing contracts, resource bounds and reserved control capacity. + +Validation passes **115 distinct checks across eight Main/SDK files**, including the actual SDK +Kernel. Three new durable unit cases cover scoped ownership/concurrency, unreadable records and +physical-exit/recovery failure; existing service/helper cases extend shutdown, replacement and late +startup coverage. Scoped unit/E2E types pass. The large 53-case manager fixture remains runtime +checked; its existing unrelated typing errors are recorded separately from source and scoped-test +checks. Normal build including Node/web types, format/check, lint and i18n pass. Normal Provider/ACP +metadata passes **75 checks across five files** and is held separately in `466791536`. + +The current application passes four execution scenarios: completion/output/persistence, shutdown +with an active command, canceled pending Native startup and unexpected helper loss. The immutable +baseline passes the same first three; its helper-loss scenario leaves the owned command running. +The new durable fault case uses the public Agent/tool flow, kills only its owned Native helper and +checks command retirement before app shutdown and profile reopen. Test failure cleanup releases +the owned command through its fixture sentinel; it does not signal a potentially reused PID. +No deadline, performance gate or production debug API is added. + +This proves registered-root recovery, not complete process containment. Failed registration, +legitimate exec identity changes, escaped descendants, process-group/fd retirement and supported +platform/signed behavior remain required for G5 acceptance. G5 remains active and unpublished. Compact reports and restored +artifact hashes remain in `.deepchat/rutis-g5-kernel-assembly-acceptance/`; completed bulk artifacts +are pruned and the sole needed immutable baseline is retained. G6 has not started. + +### G5 default Core composition + +- [x] Complete default Core acceptance: remaining streaming/delegation/interaction, Browser and + helper faults, selected identity-bound process retirement, representative resource/responsiveness evidence and + supported native package behavior. Complete local acceptance before publishing the coherent + G5 source; keep G5 active until exact-head CI passes. Signed distribution remains release-gated. + +The default process move selects one paired resource capture of the existing eight-delegation +workload on this fixed Mac, with identical local Provider fixtures, profiles and observer. Retain +all process-tree footprint/CPU samples, startup readiness and Main event-loop p95/p99. This is +structural-migration evidence, not a speedup adoption decision. Do not run a five-pair matrix or +turn an individual wall-clock difference into a gate; allow at most one confirmation batch for a +suspected material regression under the execution rule above. + +Main starts the existing Rutis host and Core connection, opens the profile in Core and activates +its cohesive Kernel. It no longer constructs the profile writer, data, Provider, Harness, Memory, +MCP, synchronization or business route/controller roots. Native routes keep their sender guard and +fixed Main handlers; business routes use bounded Core ingress with the original caller guard +rechecked before dispatch. Activation holds that existing queue until business readiness is +acknowledged; cold SDK calls remain denied. Widget reads wait for the same acknowledgement. + +Configuration snapshots remain read-only in Main; floating bounds use an exact private command. +Settings-window lifecycle and system resume reach the Core startup/scheduler owners. Resume +acknowledges dispatch without holding the Native event lane for helper replies; its finite work +joins the database lifetime gate. Core event targets and versions remain authoritative. Unsent full startup-progress snapshots and live-delegation projections supersede older queued +versions for the same target or parent/delegation identity. Projection revisions cannot regress. +Renderer publication does not retain a Promise waiter per superseded update; the owner retains +the actual Native delivery receipts. Other events keep their deliveries. Real eight-delegation +acceptance measured a 69-entry presentation peak, exceeding the preparation budget of 64. The +independent presentation budget is 256 entries/64 MiB; business ingress and Native callback/control +capacities retain their existing limits. + +Shutdown pauses Native writes and requires the Core shutdown fence before closing windows. That +fence pauses secrets/security mutations and execution admission; final closure still awaits +accepted Native effects and actual helper/Browser/desktop retirement before host release. Failed +receipts prevent a terminal reset/restart action. Full reset creates its filesystem-only launcher +cleanup owner after Core retires. Core connection loss and internal helper/Browser/desktop/HTTP owner failures notify Main once +through a fixed component-only capability with reserved control capacity. The app generation +retires without replaying business operations or reopening the writer in Main. Shutdown fences +and lifecycle events remain admissible after failure, so physical helper exit receipts cannot +deadlock retirement. A retired connection ignores its expected host exit. Complete Native fault +and process-containment acceptance remains required. + +Validation passes **58 checks across seven Main/SDK files**, including the actual Core worker, +and **six Rust checks**. Existing lifecycle cases cover activation queuing/cancellation, the +shutdown fence, physical retirement, configuration persistence and connection failure. One small +case verifies latest startup and delegation projections under a held Native operation, including +identity separation, stale revisions and independent business events. An actual SDK fault case +checks failure notification, continued shutdown control and writer exclusion until Native closure. Three source-order assertions for removed Main wiring are deleted. +Node/web and affected-test types, normal build with subsequent current harness/app rebuilds, +format/check, lint, i18n, Rust fmt and locked Clippy pass; the existing Remote warning remains. + +The actual default app passes **14 scenarios across five files**: configuration reads, all three +Provider model/credential/save-error flows, encryption/rekey/reopen, five maintenance/drain cases +and four Exec completion/shutdown/canceled-start/helper-loss cases. A separate launch/exit case +passes. A temporary OS-handle probe confirms the profile lock is held by one SDK worker beneath +the Rust host with a different PID from Electron Main; it and the temporary diagnostics are +removed. Original failure receipts retain the progress-queue overflow and held-Native shutdown +findings. Corrected application behavior uses no deadline increase or fallback. + +The immutable baseline passes **13 of the same 14 cases**; its helper-loss case leaves the owned +command alive. The current app passes that case and all 13 shared cases. Both artifact trees are +restored and hash-verified. Evidence is `.deepchat/rutis-g5-kernel-assembly-acceptance/`. Default adoption remains partial G5 evidence; +source wiring and these cases do not establish whole-goal completion. G6 has not started. Finished +bulk artifacts are pruned while reports, raw measurements and the sole needed baseline remain. + +Default failure/flow acceptance also covers a refused Native renderer registration: Core reports a +fatal owner failure while its process is still present, Main ends the generation with status 1, +and a fresh app opens the same profile without replaying the uncertain creation. The current app passes all six selected flows: this owner-failure/reopen case, normal streaming, +first-message Workspace/Browser, eight-delegation admission/cancel/release, Memory lazy worker +allocation/model changes/loss and CLI approval/persistence. The original public flows remain in +use. The affected unit/SDK selection passes 58 cases across seven files; the projection case also +verifies explicit overflow at the 256-entry boundary. The immutable baseline passes four of +the same six cases: its refused-registration case keeps the app alive, and its first-message +case cannot select the newly added model. Streaming, delegation, Memory recovery and CLI approval +pass on both. The current and baseline trees are restored and hash-verified. +Memory verification tracks all pre-existing SDK workers and exactly one newly allocated Memory +worker instead of assuming the Rust host has only one initial child. The default controller no +longer accepts the obsolete Native route-map injection option. Validation outcomes and matching +baseline evidence are recorded with the application receipts; no timing threshold is raised. + +Main owns interactive message dialogs and rejects pending responses during retirement, including +requests arriving after closure. The unused Core dialog invocation/response capabilities are +removed; shutdown confirmation crosses the boundary only as strictly validated data. The existing +CLI approval flow also verifies Core loss while approval remains unanswered: the generation exits +with status 1, the command does not succeed, and reopening preserves the last approved settings. +The current app passes; the immutable baseline leaves the app alive at the same injected Native +failure. Normal build refreshes retain the Provider catalog; its four focused suites pass 79 checks. + +The selected eight-delegation resource pair passes its functional assertions on both builds and +records seven process-tree samples per build. Current/baseline total footprint medians are +1108.1/993.4 MiB, peaks 1146.5/1038.8 MiB and last samples 1146.5/1038.8 MiB. Main's last footprint +is 138.5/330.2 MiB; the current tree adds one Core SDK worker, with nine/eight final processes. +Observed process CPU is 6.42/5.86 seconds, readiness 1933/2049 ms and Main loop p95/p99 +12.8/23.1 versus 33.8/57.4 ms. These short, single-pair observations establish the process/memory +tradeoff; they do not establish variance, sustained responsiveness, settled idle footprint, +input-to-paint latency or a performance gain. No confirmation batch or timing-gate change is +selected. Raw numeric samples and comparison receipts are retained; both artifact trees are +restored and hash-verified. Full G5 acceptance remains open. + +Native directory/file, image-save and context-menu pickers share the existing Main DialogService. +Its bounded FIFO accepts at most 16 requests and opens one physical picker at a time. Parent +selection is captured per request; stopping or parent destruction denies unsent work. Main ends +the active macOS sheet after the Core fence, waits for actual Native completion and then destroys +the parent. Other platforms destroy the parent before the completion drain. The owner retains +outstanding receipts throughout; it creates no extra process or public API and returns no fake +cancellation acknowledgement. + +One unit case protects FIFO results, the 16-request bound, unsent cancellation and physical-close +waiting. One macOS application case protects queued directory requests and an active image-save +picker during normal shutdown, with real OS dialogs. Project persistence, image/file actions and +CLI approval/failure recovery retain their public flows. The immutable baseline requires forced +exit with native pickers and stays alive after the Native failure while CLI approval is unanswered. +Both artifact trees are restored and hash-verified. +Native picker lifetime does not alter the delegation process layout or workload, so no additional +resource matrix is selected. Other supported platform behavior, foreground focus and +the remaining containment/fault/signed-package acceptance stay open. G5 remains active and unpublished. + +- [x] Validate the existing Main image owner's shutdown signal against held HTTP headers/body, + late Native effects and committed target writes. Connect it before Native settings-write + drain, retain the existing Native call receipts and compare the public application flow + against the retained baseline. No process, RPC method, timeout or resource matrix is added. + +The existing DesktopImageActions owns one Main-lifetime abort controller. Shutdown stops it before +awaiting Native settings writes, so held image calls cannot occupy every ordinary Core slot while +quit preparation waits. The signal cancels actual HTTP headers/body and cache reads. Checks after +awaited reads and picker results deny late picker, clipboard or file-write effects. A target write +already started completes normally; the existing Native call and business receipts retain it +until completion. No separate request map or cross-process AbortSignal is introduced. + +The selected Main/SDK suites pass 25 checks across five files. Image coverage retains byte/path +parity, rejects late data even when the Native stub ignores cancellation, and holds an accepted +target write across shutdown until its real file completion. All 46 existing Project checks pass, +including lazy preparation without getter side effects. The Project application case checks the +prepared directory and acknowledged version; live bootstrap/renderer callers can legitimately +create the directory before its explicit request, so transient-absence assertions are removed. + +The default app passes all four selected cases across Project and file/image flows. Held-download +coverage fills all three ordinary Core slots during quit, holds both HTTP headers and response +body, observes physical connection closure and reopens the same profile with a fresh image owner. +It also verifies Core-owner failure during two held downloads. The frozen baseline passes ordinary +Project and image/file flows; picker and download shutdown require forced exit under the existing +ten-second deadline. Its download case fails on quit before reaching the later failure subcase. +Both artifact trees are restored and hash-verified. G5 remains active and unpublished; remaining +Native/concurrent interaction, process containment, foreground and native/signed platform gates +are not implied by these selected passes. + +The unchanged CLI approval/failure-reopen case also passes on this same current app artifact. +Its matching frozen-baseline failure is retained with identical test source and baseline hashes; +no unchanged baseline retry is selected. Normal Provider catalog and Rust host inputs are unchanged. + +### G5 execution registration and failed retirement + +Exec startup waits for the existing registry's explicit recorded/exited/failed result before +acknowledging a live command. A failed registration terminates and removes the owned session, +then rejects startup. OS-observed exit waits for the existing completion receipt, so short commands +retain their output even when Node receives exit/close after the identity observation. MCP, ACP +and tunnel callers retain their existing behavior; no additional registry, RPC or process is added. + +A failed process termination retains its registry recovery evidence and owned Skill package tree, +and rejects kill/removal/shutdown. Local output finalization cannot stand in for physical exit. +Shutdown drains every accepted session, aggregates failed retirement and refuses subsequent starts, +including startup suspended on identity observation. A late root close cannot clear a failed +tree-retirement receipt. This does not establish escaped-descendant containment or recovery after helper +loss during the initial registration window; those boundaries remain required for G5. + +Validation passes **97 checks across five execution/registry/Native files**. Two durable manager +cases cover pending registration, failed physical retirement, retained package ownership, real +subprocess termination, fast-command completion and the shutdown start fence. One registry case +covers unavailable storage/identity and already-exited cleanup. The default-root fixture supplies +an owned identity instead of relying on an arbitrary live OS PID. + +The current application passes all **five execution scenarios**, including the new public Agent +flow with an unavailable registry directory, visible tool failure, restored storage, graceful +shutdown and persisted failure after reopening. The same new scenario fails on immutable +`f511c078b`: it returns `running` and a session ID despite failed registration. The unchanged +baseline's earlier execution scenarios reuse their existing receipts; no unchanged benchmark or +baseline matrix is repeated. Source Node/web types, scoped registry/E2E types, app build, format, +lint and i18n pass. The large manager test fixture still has the previously recorded unrelated +TypeScript errors; all its runtime cases pass and no new-case typing error was reported. + +Compact reports/raw receipts and the sole needed baseline remain; completed media and the +temporary test type configuration are removed. G5 remains active and unpublished. Exec identity +transitions, recovery across the initial-registration/helper-loss window, escaped descendants, +complete process-group/fd retirement, unlocked foreground focus and native/signed platform +acceptance remain required. G6-G12 retain their declared dependencies and scope. + +### G5 macOS execution identity across exec + +The existing Rust executable provides a read-only process-identity command before Tokio/Rutis +startup. Main supplies its absolute development/packaged artifact path to owned workers through +an internal environment value. On supported macOS, new launch records persist the boot-session +UUID and XNU process unique ID as an opaque string, preserving all 64 bits. The same identity +survives exec even though argv, executable UUID and pidversion can change. Observation replaces +the ordinary ps query for these records and creates no additional resident process or supervisor. + +Native identities never downgrade to timestamp/argv attestation during recovery. Missing identity +refuses cleanup and retains evidence; a changed native birth identity refuses signaling and drops +the stale record. Legacy records and other platforms retain conservative start-time/argv checks; +an argv mismatch alone no longer claims PID reuse or deletes uncertain ownership. Existing +MCP/ACP/tunnel callers keep the same registration API and lifecycle behavior. The sole additional +Cargo dependency declaration is libc, already present in the locked dependency graph. + +Apple defines the selected identity flavor in its private XNU header. Actual native/signed +platform acceptance remains required, and Windows/Linux process identity is not relaxed. This +selected change does not establish atomic identity-bound signaling, helper-loss recovery during +initial registration, escaped-descendant containment or complete process-group/fd retirement. +G5 remains active and unpublished; G6 has not started. + +Validation passes **119 checks across eight execution/registry/Native/runtime files**, including +real Rust host startup, SDK Core and the copied runtime closure. The single new registry case +covers same native identity after exec, missing identity, PID reuse and refused signaling. The +existing legacy mismatch case now protects retained uncertainty. All **seven Rust checks** pass; +the native test observes shell-to-cat exec at one PID, stable identity and rejected post-exit lookup. +The artifact CLI accepts a live owned PID and rejects zero without output. Source Node/web and +scoped registry/E2E types, the normal full build, format, lint, i18n and locked Clippy pass. Normal +Provider/ACP refresh output is retained; **92 catalog checks across five files** pass. + +The current application passes **seven Exec/MCP scenarios**. The new public Agent case first +registers a gated shell, releases exec into Node, verifies the same registered PID, loses its owned +Exec helper and observes command retirement before graceful shutdown and profile reopen. Its raw +record proves default native identity use. The matching new case on immutable `f511c078b` leaves +the command alive after helper loss. This is the observed failure, without attributing the entire +baseline's missing recovery behavior solely to argv comparison. Existing unchanged baseline +scenarios retain their earlier receipts. No unchanged benchmark or resource matrix is repeated; +this does not add a resident process or change the selected delegation process layout. + +Compact native records, raw reports, hashes and the sole needed immutable baseline remain. +Completed test media, copied test closures and temporary configs are removed. G5 stays active and +unpublished. Initial-registration/helper-loss recovery, atomic checked signaling, descendants, +group/fd/environment authority, foreground focus and native/signed acceptance remain required. + +### G5 explicit execution group retirement + +Explicit POSIX termination retains its acquired process-group target through root exit, waits for +the group and physical stdio close, and escalates the same group when TERM leaves a member alive. +It never falls back to the old root PID after acquiring that group. Unknown liveness does not +prove exit. If the root is already gone when termination starts and a group remains, refuse a +successful receipt without signaling that unproven group. Windows taskkill and the existing +non-group descendant fallback keep their interfaces. + +Exec records one termination promise before invoking the process helper. Kill callers join it; +root close, error and inherited-stdio watchdog paths cannot finalize output, remove registry +records or delete owned Skill packages ahead of the tree receipt. Failed tree retirement stays +failed after a late root close. Normal root completion is unchanged; descendants that detach, +root-loss recovery before termination admission and atomic identity-bound signaling still need +their separate ownership proof. G5 remains active and unpublished. + +Validation passes **273 cases across fourteen shared execution/registry/Native, MCP, Sync, +Skill, foreground shell and plugin files**. The new real-process case exercises both termination +APIs with a root that closes while its child ignores TERM and owns separate stdio. The existing +manager case now closes the root before resolving failed tree retirement and verifies retained +registry/package ownership and refused shutdown, including a late root close. Existing mock +coverage verifies that a vanished acquired group never falls back to a potentially reused root. +Fixture teardown releases its own sentinel and waits for known child/root exit before deleting +its files; it does not signal a potentially reused PID. + +The current application passes **eight Exec/MCP scenarios**. The new public Agent shutdown case +owns a parent and a TERM-resistant child, observes both gone before profile reopen and retains +the stored conversation. The same new case fails on immutable `f511c078b`: the app closes gracefully +and the root is gone, but the child remains alive. The raw failure points to the child-exit check; +fixture cleanup subsequently releases and drains that child through its own sentinel. Existing +unchanged baseline scenes reuse their receipts; no deadline or ownership assertion is weakened. + +Both Core SDK and Main are rebuilt. Node/web source types, scoped process/registry/E2E types, +format, docs format, lint and i18n pass. The existing manager fixture still has the exact same +22 unrelated TypeScript diagnostics, with no additions/removals; all runtime cases pass. Rust and +catalog sources/artifacts are unchanged from their accepted checks, so no unchanged Rust, catalog +or resource matrix is repeated. Compact raw reports and the sole needed baseline remain; completed +media and temporary configs are removed. G5 stays active/unpublished, G6-G12 unstarted. + +### G5 macOS pre-registration execution gate + +New macOS background commands first enter the existing Rust artifact's execution gate before +Tokio/Rutis startup. The native helper records the gated root's identity before releasing a +length-prefixed environment frame over a dedicated fourth descriptor. User environment overrides +are withheld from the gate process, so dynamic-loader overrides cannot run before admission; +stdin/stdout/stderr retain their existing roles. The control frame is bounded at two MiB on both +ends, and the control descriptor closes before exec. The requested executable/args and environment +then run at the same registered PID, without an additional resident process or supervisor. + +EOF or incomplete/malformed/oversized grants run no user command. Stop/timeout while registration +is pending cannot release the gate. Failed startup compensates through the existing single tree +retirement receipt; pending environment values are released when startup settles. New native +identity observation fails closed instead of downgrading a gated command to legacy argv/time. +Legacy records and other platforms keep their conservative observation path. The existing Rust +process module owns both inspection and the gate; no additional dependency, runtime queue, public +route or implementation flag is added. Complete physical retirement of unobserved gate roots, +escaped descendants, checked signaling and native/signed acceptance remain separate G5 work. + +Resource selection: run exactly one current/baseline pair of the existing public Exec completion +scenario with its ordinary resource sampler, including profile reopen. Do not add a confirmation +batch, unchanged matrix or wall-clock adoption gate. This end-to-end pair includes the cohesive +Core migration and cannot isolate gate cost or prove a performance gain. + +Registration binds native identity to the actual owned child object. It revalidates that child +after asynchronous inspection before persisting identity, so a reaped child cannot attest a reused +PID. PID-only callers retain their conservative legacy attestation. The existing pending-record +case adds child exit alongside clear/replace, without a separate test harness or ownership map. + +Validation passes **275 cases across fourteen execution/registry/Native, MCP, Sync, Skill, +foreground shell and plugin files**, plus **seven Rust cases**. One durable real-gate case covers +release, parent-side EOF and oversized control frames; it verifies deferred environment values, +untouched stdin, unchanged PID and closed control descriptor. The final application passes +**nine Exec/MCP scenarios**. Its new public Agent fault case holds identity observation, kills the +owned helper before registration completes, observes gate-root exit with no user marker, and +reopens the persisted request without starting that command. The same new case fails on immutable +`f511c078b` with the command root still alive after helper loss. That baseline fails before the +later no-marker/reopen checks; do not claim those baseline steps ran. Test-only observer wrappers +hold both the old ps path and the native inspector, then release/drain through their own sentinels. + +The predeclared Exec completion/reopen resource pair passes on both artifacts. The initial launch +retains six samples per build: current/baseline last total footprints are **1150.2/1038.7 MiB**, +Main last footprints **129.7/306.6 MiB**, and final process counts **10/9**. Observed cumulative +sampled CPU is **6.627/5.748 s**, UI readiness **1952/2026 ms**, and Main loop p95/p99 +**12.45/30.98 vs 27.80/112.72 ms**. Reopen retains five/four samples, with last totals +**1083.9/980.1 MiB**, Main **126.2/298.0 MiB** and process counts **9/8**. All samples, including +startup, remain in the raw reports. This includes Core composition changes, cannot isolate the +gate or transient peaks, and establishes neither variance, settled idle nor a gain. The ordinary +sampler may miss short-lived processes. The local post-inspection child check is added after that +pair without changing its Rust gate, process layout or workload. No confirmation batch or further +measurement is selected. + +Normal full build and final Core/Main rebuild, Node/web source types, scoped process/registry/E2E +types, format/docs, lint, i18n and locked Clippy/fmt pass. The existing manager fixture retains the +same 22 unrelated typing diagnostics with no additions/removals. Generated catalogs are unchanged +from their accepted checks and are retained. Current and frozen output hashes verify; completed +media/configs are removed while compact raw evidence and the sole needed baseline remain. +G5 stays active/unpublished. Unobserved pre-registration gate retirement, natural root loss, +escaped descendants, atomic checked signaling, remaining Native/fd/environment/proxy authority, +foreground focus and native/signed platform acceptance remain required before G6. + +### G5 macOS identity-bound group recovery + +Registered native recovery delegates its persisted birth identity to the existing Rust artifact. +A bounded group snapshot is accepted only while its root retains that identity and group. TERM +and force address each captured member through the system audit-token interface, which validates +PID/version in the kernel. Recovery never sends a numeric PID/group signal or reacquires members +after the root can have exited. Membership is checked between two observations of each member's +birth, preventing a reused PID in the listing from authorizing a different process. Unsupported +systems, uncertain snapshots, concurrent exec and new/surviving members retain failed ownership +evidence. Disappearance of both the captured births and group remains the receipt. No resident +process, dependency, supervisor or production switch is introduced. + +Validation passes **334 execution/registry, MCP, ACP, Sync, Skill, shell and plugin cases across +sixteen files**, **eight Rust cases** and **104 catalog checks across seven files**. One real Rust +case refuses another birth identity without killing the command, then escalates an owned root +and child that ignore TERM. Fixture EOF and an actual parent wait drain the commands even after +a failed assertion. One adapter case protects the native receipt and refuses unavailable or +malformed responses without numeric-PID fallback. The normal generated Provider/ACP refreshes are kept. + +The final application passes **ten Exec/MCP cases**, including the new macOS public Agent case +that loses its Exec helper while a child ignores TERM. The root and child exit before graceful +shutdown and profile reopen. The identical final-source selected case also passes independently; +its non-macOS skip guard does not change the ten-case macOS workload or application artifact. +Immutable `f511c078b` fails the new case with its root still alive after helper loss, before its +later child/reopen checks. This does not isolate the audit-token interface as the baseline's sole +cause. Owned release sentinels subsequently drain the fixture, and both trees are restored and +hash-verified. Earlier unchanged baseline results remain scoped evidence. + +Normal full build, final Rust/Core rebuild, Node/web source types, scoped process/registry/E2E +types, formatting, lint, i18n, Rust fmt and locked Clippy pass. The unchanged manager fixture's +22 existing type diagnostics retain their earlier receipt. No new performance measurements or +gain claim are selected; the earlier resource pair binds its own artifact snapshot. Completed +media and temporary configs are removed while compact evidence and the sole baseline remain. +Natural root loss before recovery, escaped descendants, explicit utility-owned signaling, +supported native/signed compatibility and whole G5 acceptance remain open. G5 stays active and +unpublished; G6-G12 are unstarted. + +### G5 shared native execution termination + +Successful registration supplies its existing observed identity directly to the session owner. +Admitted macOS commands use the same Rust identity-bound group implementation for explicit stop, +timeout and shutdown as for helper loss. Group retirement and actual owned-child stdio close are +both required before registry/package cleanup. A gate without a native identity cannot release +user code. Native root exit also joins that retirement. Rust authenticates surviving group members +through captured parent-birth chains when their root has exited; uncertain ancestry refuses a +receipt. Native recovery retains records after root PID loss until this check settles. +Pre-registration compensation, missing ancestry and escaped descendants retain their separate +ownership requirements. Root exit keeps its exit code and output; list/poll remain running while +its physical receipt is pending. It cannot report successful +completion while physical retirement is pending or failed. Late close cannot clear that failure. + +Validation passes **440 cases across twenty-three execution/registry/Native-host, MCP, ACP, Sync, +Skill, shell, plugin and catalog files**, including the normal generated Provider/ACP refreshes, +and **eight Rust cases**. One native completion case covers held and refused retirement, original +output/exit code, retained registry/package ownership, late close and refused shutdown. One +registry case verifies root disappearance cannot erase native group ownership and preserves a +custom attester's refusal. Existing gate/output-limit and process adapter cases cover identity +forwarding and actual stdio closure. The Rust real-process case covers both live and already-exited +roots, rejects another birth without killing its child, then forces its owned TERM-resistant child. +No exact incidental terminal-signal assertion or test-only production route is retained. + +The actual application passes **eleven Exec/MCP cases**. The new macOS public Agent case exits +its root with a TERM-resistant child on separate stdio, observes both gone, polls full output and +exit code 0, closes gracefully and reopens the stored conversation. Immutable `f511c078b` exits +the root but leaves the child alive, failing before its later output/persistence assertions. +Owned release sentinels drain its failed fixture. Same source and current/frozen artifacts are +hash-bound and both trees are restored; unchanged baseline cases reuse their existing receipts. +The final-source selected case follows ordinary `running` feedback with another uniquely +identified public poll rather than assuming PID disappearance synchronously settles the Native +receipt. It is included in the eleven scenarios, not counted as another distinct workflow. + +Full build, Node/web source types, scoped process/registry/E2E types, formatting, lint, i18n, +locked Rust Clippy and fmt pass. The manager fixture has exactly the same 22 type diagnostics +after normalized comparison, with no additions/removals; that probe is not called green. + +Resource selection for the new completion path: exactly one ordinary Exec completion/reopen +current/baseline pair at the existing 500 ms sampling interval. Retain all startup samples and +report aggregate/Main memory, process count, CPU, readiness and Main-loop delay. The deterministic +requirement is no additional resident process after the command's physical receipt. Timing is +report-only; no confirmation batch, unchanged matrix or relative CI gate is selected. Both +functional resource runs pass. Initial current/baseline samples are **8/6**, post-ready **4/3**; +total median **1079.9/981.9 MiB**, peak **1146.6/1058.7 MiB**, last **1141.4/1058.7 MiB**, Main +**130.4/306.0 MiB**, process count **10/9**, observed sampled cumulative CPU **8.051/5.940 s**, +readiness **2112/2074 ms**, and Main-loop p95/p99 **14.62/24.72 vs 25.46/158.47 ms**. Reopen +samples are **5/4**, post-ready **2/1**; last total **1102.7/988.0 MiB**, Main **127.8/295.6 MiB**, +processes **9/8**, CPU **5.760/5.039 s**, readiness **1913/1981 ms**, loop **12.63/25.43 vs +37.26/112.85 ms**. All startup samples remain in the raw receipt. Aggregate last/peak memory is +higher. This includes Core migration, may miss short-lived native checks and cannot isolate this +completion path, settled idle, input-to-paint, variance or a gain. The resource receipt identifies +its own artifact snapshot separately from final behavior acceptance; Rust implementation, process +layout and workload are unchanged, but final-artifact performance is not established. The declared +budget is exhausted. + +Compact evidence and the sole frozen baseline remain; ended media and temporary configurations +are removed. G5 remains active/unpublished, G6-G12 intact/unstarted. Missing ancestry, escaped +descendants, unobserved pre-registration gates, remaining Native/Browser/concurrent faults and +unlocked foreground/native/signed supported-platform acceptance still require proof. + +### G5 descendants outside the original process group + +Extend the existing Rust termination operation with bounded same-user birth snapshots. Keep +captured ownership across TERM/force, follow parent-birth chains across groups and verify a fresh +snapshot before success. Never signal a process merely because its UID or numeric PID matches. +Refuse unknown inspection, overflow, ambiguous ownership or surviving descendants. Keep the +existing registry and native caller; add no resident service or dependency. Unobservable ancestry, +privilege changes, pre-registration gates and supported native/signed compatibility remain open. + +The native command is `--terminate-tree`. Each bounded snapshot covers the original group and +same-user candidates; captured parent-birth chains authorize targets across groups and sessions. +The operation retains captured births across at most three snapshots. Unknown inspection, +overflow or uncertain ancestry refuses retirement. Root PID reuse within the same boot and record +age cannot discard native ownership; a validated identity from a different boot clears obsolete +ownership without signaling a current numeric PID. + +All 104 execution/registry/Native-host checks and eight Rust checks pass. The real Rust case +covers live/exited roots with children inside/outside the original group, rejects another birth, +proves stale-boot cleanup has no effect on a current child, and retires TERM-resistant members. +The normal build retains Provider/ACP refreshes. The refreshed ACP catalog passes 20 checks across +four files; unchanged caller/catalog evidence retains its own source snapshot. Node/web and scoped +process/registry/E2E types, format, lint, i18n, locked Rust Clippy and fmt pass. The unchanged manager +fixture retains its preceding 22 existing diagnostics; this is not a green whole-fixture type check. + +The rebuilt app passes all 12 Exec/MCP scenes. The public Agent escaped-root-exit case observes +separate root/child process groups before releasing the root, then verifies root/child disappearance, +full output, exit code 0, graceful close and persisted conversation after reopen. The immutable +f511 baseline fails at line 488: its root is gone but its escaped child remains alive. Later baseline +feedback/reopen checks do not execute. Owned release files drain the fixture. Current/frozen trees +are restored and hash-verified in `escaped-comparison.json`; reports remain under +`.deepchat/rutis-g5-kernel-assembly-acceptance/`. + +Exactly one declared ordinary completion/reopen resource pair passes at 500 ms, retaining all +startup samples. Initial current/baseline: 6/6 samples, post-ready 2/3; median 927.7/996.3 MiB, +peak/last 1152.5/1058.2 MiB, Main 131.8/308.4 MiB, processes 10/9, sampled cumulative CPU +6.815/5.913 s, readiness 2066/2041 ms, loop p95/p99 12.57/27.74 vs 23.82/138.94 ms. +Reopen: 5/4 samples, post-ready 2/1; median 926.9/843.5 MiB, peak 1083.8/992.7 MiB, +last 1083.8/981.5 MiB, Main 115.0/295.2 MiB, processes 9/8, CPU 5.608/4.990 s, +readiness 1953/1960 ms, loop 14.53/28.30 vs 34.73/112.85 ms. Aggregate last/peak memory +is higher. This sparse pair includes Core migration and can miss short-lived native checks; it +cannot isolate descendant-capture cost, settled idle, variance, input-to-paint or a gain. The budget +is exhausted. Shared-CI timing stays report-only; no confirmation batch or unchanged matrix. + +G5 remains active/unpublished; G6-G12 remain unstarted. Unobservable/lost ancestry, privilege +changes, pre-registration compensation, remaining Native/Browser/concurrent faults and unlocked +foreground/supported native/signed-platform acceptance still require proof. + +### G5 execution gates before command registration + +The Main-owned Exec helper captures its native birth before exposing the command channel. On +helper exit, reuse the existing Rust termination operation in owner-descendant mode, without +assuming that an Electron utility process leads a process group. Retire observable birth-linked +descendants before reaping command records and acknowledging retirement. Missing helper identity +refuses native readiness; failed descendant retirement retains the failed receipt. Do not add a +resident supervisor, second registry, dependency or renderer API. Utility-side cancellation before +native identity closes the ungranted gate and waits for actual child closure; it must not fall back +to numeric PID/group signals for that unobserved launch. + +The native `--terminate-owner` mode reuses the bounded birth-ancestry operation without observing +or signaling a numerically matching process group. Main reuses the existing identity observer, +validates the still-owned utility after inspection, and retains its birth in the existing host +handle. The native channel is unavailable until that attestation succeeds. Actual helper exit, +owner-descendant retirement and command-record recovery all precede the retirement receipt. +The session manager closes an ungranted gate through EOF and the existing physical-close wait; +there is no numeric PID/group mutation fallback while its native identity remains unknown. + +All 112 relevant TS checks across six files and all eight Rust checks pass, with locked Clippy and +fmt. The existing real Rust case covers group and non-group owner roots, live/exited roots and +children inside/outside the original group. New Native boundary coverage holds attestation and +retirement, refuses missing/canceled identity and failed descendant receipts, and prevents late +readiness. The existing real-gate EOF case now cancels through the session API and verifies actual +Rust process exit without invoking tree signaling. No additional application scenario is added. +The final normal build, Node/web and scoped types, format, lint and i18n pass. The manager fixture +still reports the same 22 existing diagnostics, with zero normalized additions/removals. Provider +and ACP catalog hashes are unchanged from their preceding accepted refresh checks. + +The final rebuilt app passes all 12 Exec/MCP scenes. The existing registration-loss case withholds +the gate executable and identity observer through helper loss, verifies both processes exit before +the fixture release, preserves the absence of user effects and reopens the persisted request. +This combines a stalled launch executable with real Rust gate/EOF regression protection; it is +not proof of arbitrary lost ancestry. The same immutable f511 baseline fails at line 324 with the +command root alive, before no-marker/reopen assertions. Owned release sentinels drain its fixture. +`gate-owner-comparison.json` binds current/frozen artifacts and the same final test source; both +artifact trees are restored. Evidence remains under the existing kernel-assembly acceptance root. + +The single declared completion/reopen resource pair passes at 500 ms, retaining all startup +samples. Initial current/baseline: 7/6 samples, post-ready 3/3; median 1048.4/979.3 MiB, +peak/last 1150.2/1052.1 MiB, Main 129.9/306.0 MiB, processes 10/9, sampled CPU +6.919/5.941 s, readiness 2109/2066 ms, loop p95/p99 14.34/29.41 vs 32.34/111.08 ms. +Reopen: 5/4 samples, post-ready 2/1; median 931.4/853.7 MiB, peak 1114.3/1023.8 MiB, +last 1114.3/991.8 MiB, Main 125.4/296.0 MiB, processes 9/8, CPU 5.526/4.999 s, +readiness 1880/1968 ms, loop 14.68/28.49 vs 36.93/119.08 ms. Total last/peak memory is +higher. Samples bind their own artifact snapshot: final behavior additionally changes utility-side +ungranted-gate cancellation, a branch outside this ordinary workload. Rust implementation and +process layout are unchanged, but final-artifact performance is not established. Sparse startup +sampling cannot isolate owner-retirement cost, settled idle, variance, input-to-paint or a gain. +Budget exhausted; shared-CI timing is report-only, with no confirmation batch or unchanged matrix. + +G5 remains active/unpublished and G6-G12 unstarted. Lost/unobservable ancestry and privilege +changes, remaining fd/environment/proxy authority and Native/Browser/concurrent faults, unlocked +foreground and supported native/signed-platform acceptance remain required. Compact raw evidence +and the sole immutable baseline remain; ended media and temporary configurations are removed. + +### G5 command environment authority + +Core resolves public command environment overrides into one complete launch snapshot before +Native dispatch. The reusable utility must not merge its older environment into that snapshot; +the Rust gate replaces its trusted bootstrap environment only after admission. Retain the existing +shell/PATH preparation, per-command overrides, separate stdin and same-PID exec. Clearing proxy +settings must remove old helper-inherited proxy variables; utility-only control markers must not +reach the admitted command. + +The public Exec service resolves overrides against Core's string-valued process environment. +The Native manager uses that complete snapshot, with its existing UTF-8 preparation, as the target +environment. Rust calls `env_clear` before applying the admitted grant; the trusted gate bootstrap +still receives its existing environment before admission. The default service, utility and Rust +artifact are connected directly. No dependency, process, option, alternate route or registry is added. + +Validation passes 223 TS checks across thirteen execution/registry/Native/helper, shell, command and +Skill files, plus all eight Rust checks, locked Clippy and fmt. One small service case protects +snapshot/override/deletion across helper reuse. The existing Native environment case refuses +utility inheritance, and the existing actual Rust gate case proves a bootstrap-only marker is +absent after admitted exec while stdin/PID behavior remains intact. Final normal build, Node/web +and scoped types, format, lint and i18n pass. The manager fixture retains the same 22 existing +diagnostic messages with zero normalized additions/removals; it is not a green fixture type check. +Provider and ACP catalogs match their preceding accepted refresh checks. + +The actual app passes all 13 Exec/MCP scenes. The new environment scene runs two commands through +one helper: a background command observes its custom proxy, public settings clear the proxy, and a +foreground command receives neither the old proxy nor the utility-only control marker. Both commands +finish through ordinary public feedback; the conversation survives graceful close and reopen. +The fixture uses the normal foreground wait for the second command and preserves the existing +no-progress guard. The retained failure diagnostic records actual no-progress feedback and is +not a passing acceptance result. The final-source immutable f511 baseline fails at +line 515 with both the old proxy and utility marker still present in the second command. Later +baseline final-feedback/reopen assertions do not execute. Owned release files drain the fixture. +`environment-comparison.json` binds the same final fixture/current/frozen artifacts and restored +trees under `.deepchat/rutis-g5-kernel-assembly-acceptance/`. + +No process placement or new transport is introduced; ordinary Exec callers already provide complete +prepared environments. No resource capture, unchanged benchmark matrix, timing gate or performance +gain is claimed for this correctness fix. Previous resource receipts retain their own source/artifact +scope and do not measure the current complete-environment launch cost. Shared-CI timing stays +report-only. Compact raw evidence and the sole immutable baseline remain; ended media, temporary +configurations and the diagnostic fixture instrumentation are removed. + +G5 remains active/unpublished, with G6-G12 unstarted. Unobservable/lost ancestry and privilege +changes, remaining Native Chromium/fd lifetimes and Browser/concurrent faults, unlocked foreground +and supported native/signed-platform acceptance still require proof. Main's global-fetch dispatcher +ownership is covered by the acceptance below; other transports retain their separate ownership. + +### G5 Main HTTP lifetime + +Main owns its current and draining HTTP dispatchers in the existing ProxyConfig. Proxy replacement +allows accepted requests to drain. Shutdown fences new resolutions, aborts current/draining transports, +awaits any accepted Electron proxy resolution and rejects late installation/environment changes. +The existing Native desktop receipt joins this retirement before Core releases its profile. Keep +one Main-scoped instance and report physical failures through the existing Core invalidation path. + +Undici 8.10.2 Agent clears its client map at the start of close. An owned held-header characterization +demonstrates that destroy can then return with its request and socket still alive. The existing pnpm +patch mechanism now preserves clients until graceful close settles, allowing forced retirement to +reach them in both Main and Core. The version is unchanged; frozen offline installation passes. +No new dependency, process, dispatcher wrapper or public option is added. + +Validation passes 106 distinct TS checks across nine files: 45 proxy/composition/Core/desktop checks, +31 Copilot OAuth checks and 30 Provider catalog checks. Private account credentials, callbacks and +adapters also pass 76 existing checks across six files. The real installed Agent regression holds headers, +begins graceful close, forces destroy and verifies physical request/socket retirement. Proxy tests +also cover current/draining pools, accepted late resolution, rejected queued resolution and retained +retirement failures. Native SQLite is required for Core checks. Final normal build, Node/web and +scoped test types, format, lint, i18n and diff checks pass. The unchanged Rust matrix retains its +eight-check evidence; the manager fixture's 22 existing diagnostics remain outside a green type +claim. Normal Provider/ACP refreshes are retained. + +The actual app passes all four selected OAuth/MCP scenarios, alongside 21 maintenance, Exec/MCP, +image and CLI regressions on the same application artifacts. One shared disposable HTTPS fixture +serves Copilot validation and private Provider discovery/revocation, with certificate verification +enabled and exact CONNECT host restrictions. It owns its sockets, temporary trust file and app-only +environment changes; no runtime OpenSSL dependency or OS trust change is needed. + +The private Provider case preserves both plaintext and native-wrapped legacy credential files +byte-for-byte through reads/cancellation. Public logout sends each saved refresh token and client ID +to its actual HTTPS authority, including trusted xAI discovery; both files disappear and signed-out +state survives reopen. The MCP case preserves native ciphertext, settings-window authorization and +durable credential removal. The Copilot case preserves a newer saved credential against a stale +response and verifies instruction-window retirement/reopen. The HTTP shutdown case holds two real +Native requests in old/current pools, ignores caller cancellation deliberately and verifies transport +disconnection, graceful quit and unchanged credentials after reopen. No new test case is added. + +With the same final fixture, the immutable f511 baseline passes 2/4: both credential compatibility +cases. Copilot instruction-window quit at line 179 and held-HTTP quit at line 276 require forced exit +under the unchanged ten-second deadline. Later retirement/reopen assertions do not execute. The +ordinary failure is not attributed solely to HTTP pools. Both Main builds externalize Undici and +use the same current patched install; +this comparison isolates Main ownership, not a pristine baseline dependency tree. The independent +pre-patch Agent characterization establishes the library defect. + +Current/frozen artifacts, fixture sources and dependency hashes are restored and verified in +`oauth-fixture-comparison.json` under `.deepchat/rutis-g5-kernel-assembly-acceptance/`. The same app's +21 regression cases are recorded in `native-http-regression-current-app.json`. Ended media, +the editable package copy, certificate-generation scratch files and temporary type configuration +are removed; compact raw reports and the sole frozen baseline remain. No resource capture, +unchanged timing matrix, timing gate or performance gain is claimed. Shared-CI timing is report-only. + +G5 remains active/unpublished; G6-G12 are unstarted. Native Chromium pools, remaining +fd/Browser/concurrent faults, unlocked foreground and supported signed +platforms remain separate acceptance work. Whole G5 review, commit/push and exact-head CI precede G6. + +### G5 application coverage and fixture ownership + +The selected local smoke inventory is fully accounted for: 117 of 122 cases pass and five are +skipped because they explicitly require live Provider configuration. The original Native foreground +focus case remains outside that inventory and unverified while the Mac is locked. Coverage is +assembled by case identity from terminal reports on the same unchanged application artifacts; +the two broad batches stopped at their failure limit and are not individually green. + +Provider catalog, ACP registry/installation, Sync peer credentials and Remote login/Telegram +fixtures use real HTTPS requests from their current business owner through the public proxy +settings. The shared fixture restricts CONNECT authorities, scopes its public test CA to disposable +application processes, preserves certificate verification and owns transport/environment cleanup. +Native icon fetch and window presentation interception remain at their actual Main owner. +Obsolete Main-only business fetch and MockAgent interception are removed. No production source, +dependency or test case is added for this acceptance work. + +The existing UI scenarios use the current Provider catalog and expand the sidebar before operating +on its rows. Attachment editing waits until the displayed input exists in the authoritative +Session projection; an optimistic bubble is not a persisted message receipt. Generated composer +screenshots use the test-owned output directory. The 17-case affected and remaining application +batch passes, including actual QR login, Telegram pairing/open/window replacement, legacy Sync +ciphertext, OAuth, held ACP refresh/repair, OCR and CLI approval. Scope, raw failures, final case +receipts and artifact hashes are retained under +`.deepchat/rutis-g5-kernel-assembly-acceptance/`. + +The same 14 Provider/ACP, Sync, Remote, catalog and composer scenarios pass against immutable +`f511c078b`. The comparison uses the identical final fixtures and preserves the current patched +Undici install shared by both Main builds; it is not a pristine-dependency baseline. Current and +frozen application artifacts, dependencies and fixture hashes are verified after restoration. +No new build, Rust suite or unchanged performance matrix is selected for these fixture-only changes. + +G5 remains active and unpublished. Remaining acceptance includes unlocked Native foreground +focus, sustained responsiveness/resource interpretation, supported native packages and whole-change +review. Signing/notarization remains a release gate. No performance improvement or G5 CI result +is claimed. + +### Packaged default Core verification + +The package workflows must launch the actual packaged app, rather than infer default Core startup +from the Admission/Memory subprocess check. Reuse the existing startup suite and settings case for +normal startup/graceful retirement, pending encrypted-startup quit, Core owner failure/profile reopen +and acknowledged settings persistence. +Run them on each of the existing six native targets with disposable fixture-owned profiles, no +retries and resource timing capture disabled. Linux uses Xvfb and the existing packaged launcher. +Do not introduce a separate mocked Kernel, test-only startup route or new smoke suite. + +The local verification uses the accepted current build in an unsigned unpacked macOS arm64 package; +native target CI results remain unproven until the complete source is published. Developer acceptance +and screenshot directories must be excluded from the application archive. Keep compact raw results +and remove the disposable package after acceptance. Native focus and responsiveness requirements +remain separate; these packaged startup cases do not establish either. + +Quit awaits the existing startup completion promise before resolving its shutdown owner. +Update-install preflight also waits before clearing permissions and retiring Core, preserving +lifecycle-log order. A visible main window must not expose a gap in which quit falls through +without a Core shutdown owner while the splash-close transition is pending. + +Database maintenance fences credentials before awaiting Native configuration pause or +Provider/ACP preparation. A preparation failure restores credentials +and admission only after Native configuration acknowledges resume; shutdown during that wait keeps +the old generation fenced. An uncertain Native pause fails the owner and completes cleanup before +reporting failure. + +Local macOS arm64 verification passes all three packaged application cases with zero retries and +resource capture disabled. Normal quit is graceful and preserves startup/shutdown JSONL order; +Core failure permits profile reopen, and settings revisions survive Main clock rollback and +relaunch. The ASAR contains neither `.deepchat` nor `.amp`; packaged Main, Exec utility, Core and +Rust host hashes match the normal build. Bundled Node integrity checks remain enabled. + +The same Core/Rust artifacts pass 141 relevant checks across 11 files, including actual SDK/native +SQLite, and 21 development-app cases covering credentials, database encryption/maintenance, +ACP repair, streaming and Native resource retirement. The final Root startup wait is verified by +the packaged cases. Two recovery/stop cases are retained in the existing database ownership test; +no new suite or production control interface is introduced. + +An outer macOS sandbox offline probe could not initialize Chromium's nested sandbox/SingletonSocket; +its three cases never reached application acceptance. It does not establish offline startup. +The successful package uses the standard launcher and retains normal web security. This unsigned +local package omits optional bundled plugins/helpers and does not establish complete distribution +or six-target CI acceptance. + +The owned temporary package (1,361,376 KiB) and 21 ended media/trace files (1,915,826 bytes) are +removed after all readers exit. Raw reports, classified failure metadata and one immutable baseline +remain. The retained `.deepchat` evidence is approximately 318 MiB, including the 207 MiB baseline. + +### Offline Core and failure boundary + +The default application starts, persists a settings change, quits gracefully and reopens that +change while macOS denies external IP egress to the actual Rust host and its Core descendants. +Both launches record the Main parent PID and the host's pre-exec checks: a TEST-NET external +connection fails with `EPERM`, while loopback works. The temporary test entry imports the unchanged +normal Main build after wrapping only the Rust host spawn; it assembles the real profile and +Kernel, with no alternate backend or production switch. Native Main and Chromium retain normal +security and are outside this network restriction. This proves Core startup without external +egress, not whole-application or packaged offline distribution. + +The application case passes once with zero retries and resource capture disabled. A preliminary +run using `NODE_OPTIONS` did not observe any restricted Core host; its three green ordinary smoke +cases are not offline evidence. Keep the explicit process receipts and raw results, then remove +the temporary entry, preload, sandbox profile, probe and test. + +Boundary review confirms that business route snapshots retain bounded queues and recheck the +original caller guard before dispatch, while Main constructs no profile/database writer or business +Kernel. The default Core failure callback retires Native owners and exits the desktop generation; +the verified failure/reopen cases do not establish in-place Core restart. The RFC describes that +same failure policy. Whole-change review remains open. + +### Sustained delegated-stream responsiveness + +Select one current/frozen pair of the existing eight-delegation application scenario with six +active children and two queued. During the 30-second observation window, each active local Provider +stream emits one small content fragment every 20 ms. Enter 120 trusted CDP keyboard characters at +a 250 ms cadence into the visible composer, preserving its complete unsent text and all existing +admission/cancellation/completion/shutdown assertions. There is no new production path or retained +test suite. Reuse the sole frozen baseline and the same current dependency installation. + +Record each input event through two animation frames as a paint-opportunity proxy, plus browser +Event Timing entries when available; report the latter's minimum-duration sampling explicitly. +Record Main event-loop delay for the observation window and process-tree footprint/CPU at the +existing 500 ms interval, retaining startup samples separately. First establish that trusted input +and animation frames are observable; do not interpret missing frames or Event Timing as zero +latency. The Mac's lock/foreground state remains a measurement limitation, and this capture does +not replace Native focus or physical keyboard-to-display acceptance. + +This new sustained workload addresses the unmeasured interaction window; it does not repeat the +earlier short resource matrix. One pair is structural evidence, with no universal speedup claim +or CI wall-clock gate. Preserve every sample. A suspected material regression may select at most +one confirmation pair under the execution rule; inconclusive variance stops measurement rather +than triggering retries. Functional draft preservation, owned teardown and unchanged artifact +restoration remain blocking. Remove the temporary scenario and ended media after inspection. + +The selected pair passes on both builds: all 120 trusted inputs remain in the unsent draft, seven +delegations finish, one queued delegation is interrupted and both app generations quit gracefully. +The active window spans 29.76 seconds per build, with 59 process-tree samples each; nine current +and seven baseline startup/outside samples remain in the raw reports. All current/frozen artifacts +and shared dependency hashes are restored, with no extra output copy retained. + +| Active-window observation | Current | Frozen baseline | +| ---------------------------------------------- | ------------: | --------------: | +| Input to two-frame proxy p95, ms | 12.0 | 11.9 | +| Sampled browser keydown presentation p95, ms | 24 | 24 | +| Main loop p95 / p99, ms | 12.1/12.6 | 12.5/13.1 | +| Main footprint median, MiB | 132.6 | 327.4 | +| Total footprint median / peak, MiB | 1082.2/1198.6 | 1038.3/1160.7 | +| Process CPU delta over sampled window, seconds | 5.99 | 5.12 | +| Stable process count | 9 | 8 | + +The browser observer returns 109/118 measured-window keydown entries with a 16 ms minimum and +rounded durations; omitted inputs are not zero-latency samples. All 120 inputs have a two-frame +proxy. DOM visibility/focus are true while IORegistry reports the Mac locked, so these observations +do not establish Native foreground or physical display behavior. Total footprint median/peak are +higher with Core, despite Main's lower footprint; the final active samples are 982.6/998.4 MiB, +which demonstrates why a last sample alone cannot establish settled idle cost. CPU is also higher +in this one pair. Input latency is similar here, and variance remains unproven. + +This completes the selected sustained workload and resource interpretation for structural G5 +review. No confirmation pair, unchanged benchmark rerun or performance gain claim is selected. +Native foreground focus and whole-change review remain open before coherent publication; exact-head +required/native-package CI must pass before G6. Keep the raw measurements and remove the temporary +scenario rather than adding a permanent benchmark suite. + +### G5 startup quit and profile preservation + +Application quit cancels pending and future database unlock/recovery requests before awaiting the +existing startup completion promise. The Core startup failure path still retires its generation and +releases the exclusive profile owner before exit. Intentional quit suppresses the startup error +dialog; ordinary startup failures retain their existing reporting. Ready-window quit and update +installation retain their startup/shutdown ordering. + +A real encrypted-profile reproduction on the preceding normal build remained alive after the +existing 10-second lifecycle deadline: quit waited for startup while startup waited for a password. +The temporary reproduction is removed. One durable case in the existing startup suite requests quit +at the password prompt, checks exit code zero and absence of error dialogs, then unlocks the same +owned profile and verifies the persisted setting. The Splash display suite covers cancellation of +both pending and future unlock/recovery requests. Existing environment setup uses Vitest stubs and +restores the inherited environment after each case. + +The normal build, format, i18n, lint and source types pass. Relevant lifecycle checks pass 42 cases +across three files. The affected development application run passes 24 cases; the new quit case +passes separately after its cleanup retains the original ChildProcess handle instead of reading a +released Playwright object. The initial cleanup failure remains in the raw report. No product +assertion or timeout is relaxed, and the other 24 cases are not repeated. + +The actual unsigned macOS arm64 package passes all four selected startup/settings cases with zero +retries and resource timing disabled. Its Main, Exec utility, Core and Rust hashes match the normal +build; the ASAR excludes `.deepchat` and `.amp`, and bundled Node integrity checks remain enabled. +The four-case selection is wired into the existing six native package targets. Local package +acceptance does not establish their CI results or signed distribution. + +After all package readers exit, the owned 1,351,404 KiB verification directory and eight ended media +files (1,032,437 bytes) are removed. Compact raw reports and the sole immutable baseline remain; +`.deepchat` occupies approximately 321 MiB, including the 207 MiB baseline. No process/resource +placement changed in this fix, so the accepted sustained comparison retains its original Main build +scope and is not repeated. + +The corrected test explicitly selects the Splash document and bounds failure cleanup to processes +it owns. That selection passes on the current normal build. The immutable `f511c078b` build does +not display the password input after the same encrypted-profile setup, so its run never reaches the +quit action and is not a valid behavior comparison. The first attempt also exposed unbounded test +cleanup; both failed reports remain, with no baseline pass or product regression inferred. The +failed process is retired, the frozen output is returned to its original location, and current +artifact/dependency hashes are verified. No further unchanged baseline run is selected. + +The source review covers default composition/profile ownership, maintenance and shutdown ordering, +renderer identity and revision fences, bounded dispatch, Native HTTP/Browser/Exec retirement and +package boundaries. It identifies the startup quit cycle and stale Main-only architecture text; +the lifecycle fix and the developer guide, architecture and flow descriptions now use the actual +Core/Native split. G5 remains active and unpublished. Original Native foreground acceptance still +requires manual Mac unlock. The extra frozen manual-password scenario remains unverified; the quit +fix uses its confirmed preceding/current normal-build reproduction rather than this additional +setup, and no further frozen manual-password run is selected. Required checks +and all six native packages must pass on the eventual coherent published head before G6. + +### G5 Native foreground acceptance and publication + +After manual Mac unlock, the unchanged original Browser route case passes on both the current +normal application and immutable `f511c078b` baseline, with zero retries and resource capture +disabled. It verifies actual Electron window visibility/foreground focus, active Browser content +focus, rejection after Session deactivation, status/events and destruction. Both runs use the same +source case and fixture-owned disposable profiles. Native focus is established by the actual +BrowserWindow; this acceptance makes no physical-keyboard/display latency claim. + +The baseline is selected by directory rename, then returned to its original location. Current Main, +Exec utility, Core, Rust and dependency hashes match the accepted startup-quit build after restore; +there is no copied output tree or remaining application reader. The initial invocation selected no +tests because its start-anchored grep omitted Playwright's file prefix; it launched no application +and is excluded from acceptance. The corrected selector lists exactly one case before execution. + +This closes the system-lock blocker. Selected local G5 behavior, ownership/resource interpretation +and source review are complete at their documented scopes. The extra frozen manual-password setup +remains unverified and has no further unchanged run selected; the quit fix uses its confirmed +preceding/current normal-build reproduction. Existing package and sustained resource evidence keep +their original artifact scopes and are not repeated. + +Publish the complete G5 source and default wiring to the existing Draft PR after final routine +checks. Required checks and all six native verification packages must pass on that published head +before G6 starts. G6–G12 remain intact and unstarted; the PR is not merged. The published CI result, +not local macOS acceptance, establishes the remaining supported-platform verification. + +### G5 current dev integration + +The current `dev` adds centralized Provider Catalog ownership and subagent model-selection fixes. +Their composition stays inside Core: ACP catalog replacement and imported model invalidation use +`ProviderRuntime`; subagent selection receives the availability predicate and the sanitized +execution snapshot. Main retains the G5 Native composition and does not reconstruct these business +owners. Upstream providers, renderer projections and catalog contracts are preserved. + +Detached catalog revalidation, config refresh and explicit refresh execute their entire fetch and +commit in the existing database request scope. Maintenance drains accepted refreshes before +rekey/import; shutdown starts HTTP retirement and drains requests before releasing the profile. +One existing catalog test covers a detached refresh holding maintenance until its write commits +and rejects a new refresh during maintenance. The catalog writer guard also scans `src/harness`. +The obsolete renderer catalog-cache invalidation calls are removed with the cache that `dev` +replaced; renderer subscriptions forward the authoritative backend notifications. + +The integrated normal build, format, i18n, lint, renderer architecture baseline and generated-icon +checks pass. All 19 selected actual-app startup/provider/delegation/maintenance/ACP cases pass at +their recorded scopes: 18 in the initial run, with the remaining provider case passing after two +obsolete picker-toggle clicks are removed. The picker now correctly shows all catalog models +when only a custom model is enabled; credential, selection, persistence and error assertions remain. +The 18 successful cases are not repeated. + +Catalog/Provider Runtime checks pass 57 cases across three files, including the detached-refresh +drain regression. A mock gains the new catalog-state method, and its unused ACP fixture follows +the current async ports. Targeted test types and lint pass. Model picker/store tests pass; renderer +API client tests pass 53 cases after deleting an obsolete test of the removed renderer catalog +cache. No production API is restored to satisfy that test. Earlier artifact evidence keeps its +original source scope; no unchanged performance matrix or package rebuild is selected. + +Publish the conflict-free integration, then require exact-head checks and all six native packages +before G6. Merge-conflicted pull requests cannot start these pull-request workflows. The PR remains +Draft against `dev` and is not merged. + +### G5 shared package dependencies and CI acceptance + +The desktop package places the Harness dependency closure in the shared unpacked `node_modules` +directory. Native payloads are included once; Core and Memory resolve them through Node's parent +module lookup. Development and standalone output retain their complete local dependency closure. +The Rutis runner resolves from the first-party entry's package anchor. DuckDB native/API identity +validation remains mandatory. Package-size budgets are unchanged. + +The portable dependency closure excludes package test fixtures, source maps, type declarations +and Windows debug symbols. Runtime JavaScript, native libraries/helpers, package metadata and +licenses remain. These files otherwise bypass electron-builder's normal dependency filtering when +copied through `extraResources`. The selected installed dependency payload falls from about +320 MiB to 259 MiB; this is disk usage, not a process-memory or latency result. + +The existing native Memory integration fixture uses the actual electron-builder resource matchers +and exercises the shared dependency layout outside the checkout. Admission still exercises the +standalone closure. The Core failure acceptance identifies its owned settings window through the +Playwright BrowserWindow handle, avoiding URL equality while preserving failure, exit and profile +reopen assertions. + +Main CI fixture corrections reset queued process mocks between cases, recognize the real working +directory on Linux, pass the explicit Provider environment, and use the current Tape port and native +workflow step name. Tape tool rejection still verifies no available query method is called. No +production compatibility API or platform skip is added. The eight selected test files pass 189 +cases; the normal build and repository format, i18n and lint checks pass. The actual-app Core failure +and profile reopen case passes with zero retries. The unsigned local macOS arm64 package passes all +four selected application cases and native Memory read/write, restart, reset and physical shutdown. +Its archive excludes developer artifacts, and its Harness has no duplicate private dependency tree. +Bundled plugins are absent from this local verification; it establishes neither installer size nor +signed distribution. Supported-platform packages and original integrated Native foreground acceptance +remain pending; G6 cannot start until the exact published head passes. + +Required PR checks pass on `4046f417c`, including 10,360 Main cases with the eight existing skips. +Its Windows x64 and Linux arm64 packages pass; Linux x64 and macOS arm64 still exceed the size +budgets. Runtime payload filtering passes the three affected suites, 47 cases, plus matching text +extraction for a 14-page PDF and a real PTY child exit. An unsigned macOS arm64 package outside the +checkout passes the four Core/settings cases, an additional actual-app PDF extraction case and the +native Memory smoke. The temporary PDF acceptance driver is removed. Bundled plugins are absent +from this local package, so installer-size and complete platform acceptance still require CI for +the published filtering source; this evidence does not establish G5 completion. + +### G5 completion + +Source `1719f8075bae3423dfd193166b56ccb024859896` passes required +[PR checks](https://github.com/ThinkInAIXYZ/deepchat/actions/runs/41300270510) and all six +[native verification packages](https://github.com/ThinkInAIXYZ/deepchat/actions/runs/41300270522). +Main reports 714 passing files, two skipped files, 10,360 passing cases and eight existing skips. +Every package target executes and passes DuckDB VSS, Memory, Core application, OpenDAL, offline +OCR and installer-size verification. No critical step is skipped and no size budget is increased. + +The same source's normal output passes the original Browser route case after manual Mac unlock +on 2026-11-02: one case, zero retries, no resource capture. Its actual Electron BrowserWindow is +visible and focused; active Browser focus succeeds, inactive focus is rejected, and status/events +and destruction pass. The unchanged case also has accepted immutable-baseline evidence above. +This closes integrated Native acceptance; it makes no physical-input latency claim. The earlier +auto-locked failure remains in the raw record. + +Default business composition and the sole shared writer are in Core; Main holds authenticated +ingress and Native ports. Functional, fault, lifecycle, package and selected resource evidence +meet the structural cutover requirements at their documented scopes. Main footprint is lower, +while aggregate footprint and sampled CPU are higher in the one sustained pair; no general speed +or memory improvement is claimed. Signing/notarization and complete offline distribution remain +release gates. G5 is complete, the PR remains Draft/unmerged, and G6 may start. Compact receipts +are in `.deepchat/rutis-g5-kernel-assembly-acceptance/`; ended packages have been removed. + +## G6. Extract the complete Memory capability + +Placement: one first-party `deepchat-memory` Rutis row in the existing Core worker, loaded before +the Core row. Preserve the lazy G3 native vector worker. Attach the current owner-local Store, +Provider, policy and change-event ports once; keep the ingestion coordinator in Harness and bind +its observer explicitly. Move Memory construction and pause/drain/resume/retirement into this +capability, then remove their manual Kernel/maintenance wiring. All domain consumers share its +one facade, preserving existing synchronous transaction and epoch checks without a new RPC. + +Validation selection: compare the existing Agent CRUD, Memory lineage/impact, native vector +relaunch/loss and database-maintenance application cases on unchanged G5 output and the extracted +output. Run affected native Memory, evaluation, ingestion and SDK lifecycle coverage; retain only +missing unload/revoked-epoch regression protection. This owner-local extraction adds no process, +IPC or storage path and has no speed objective, so no additional resource capture is selected. +Verify unchanged worker placement in the actual app and retain all baseline/candidate receipts. + +- [x] Move the existing facade and internal retrieval/extraction/reflection/persona/directive/ + maintenance services into one TS plugin. Preserve policy, Provider execution and Store ports; + keep Harness ingestion scheduling and execution fences explicit. +- [x] Replace synchronous cross-boundary assumptions with versioned reads and authoritative + epoch-validated operations. Retarget prompt injection, memory tools, management routes, + Agent lifecycle, cursor settlement and maintenance as one complete service migration. +- [x] Compare scope/temporal/tombstone filtering, recall quality, mixed-language budgets, + directive approval/trust, FTS/vector degradation, CAS/audit/lineage, background drain, + config changes, Agent delete and database maintenance in the real app. +- [x] Review resource/quality evidence, run native/evaluation and relevant E2E checks, and remove + the old Memory facade construction and manual lifecycle wiring after parity. +- [x] Publish the coherent G6 source and require exact-head PR checks and all six native + verification packages before G7. Keep this goal active until those checks pass. + +Completion: all consumers use the one capability; plugin unload/recovery cannot advance an +unsettled cursor, reuse a revoked execution token or resurrect forgotten data. + +The Rust host loads the Memory and Core rows in one existing runtime. Only the Memory plugin +constructs the facade in application source. Core supplies the same live repository, policy, +Provider and vector ports; prompt contribution, ingestion, tools, routes and Agent cleanup share +that instance. Kernel startup/retirement and database maintenance call its coordinated lifecycle, +with no remaining manual facade construction or separate ingestion-disposal sequence. + +Local validation passes the normal build, 986 Memory behavior cases across 56 files, seven +retrieval-quality cases, 372 native cases across 20 files (two existing Windows-only skips), +22 actual SDK/admission cases across three files, and eight Rust cases. The one new durable test +loads the actual Rutis plugin and holds extraction across unload: ingestion fences, the old +execution token is rejected, no late row is written, and the retired attachment cannot reopen. +Tokens bind the facade instance identity and Agent epoch; a reloaded plugin rejects an old token +even when its numeric Agent epoch matches, while accepting its own token. The existing unload +case provides minimal characterization of that recovery boundary without adding another case. +Existing tests retain scope, temporal, CAS/audit/lineage, cursor and maintenance coverage. + +Both unchanged G5 output and the extracted application pass the same 12 selected scenarios: +Agent CRUD; Memory lineage/impact; native vector persistence/deletion/clear; nine-Agent store +convergence, model changes and worker loss; five database-maintenance/shutdown cases; prompt +policy/trust; and nonempty Agent namespace deletion across relaunch. The initial baseline Agent +launch closes its renderer session before actions; its raw failure is retained, and a targeted +diagnostic run passes. Nine other baseline scenarios and ten candidate scenarios pass in their +original batch. No product change is attributed to that initial failure. + +The policy comparison preserves historical search evidence while current prompt recall excludes +expired states, foreign-project content and forgotten rows. Unapproved directives stay outside +the instruction lane; approval enables them. Fact text cannot forge the directive container. +Mixed-language budget/selection manifests match exactly: 234/256 total tokens, one selected fact +and one budget-dropped candidate. Agent deletion retires both claim and directive visibility after +restart. Temporary application policy/retirement drivers are removed after acceptance. + +Actual SDK and application cases verify the shared owner and existing lazy vector lifetime; this +change adds no Node worker or data-path RPC and makes no speed/RSS claim. Baseline and candidate +outputs are restored by rename and all file hashes verified. Compact raw evidence and the sole +needed baseline are in `.deepchat/rutis-g6-memory-capability-acceptance/`. Normal Provider/ACP +registry refreshes are retained. Signed distribution remains release-gated. + +Same-ID Memory row refreshes preserve the inline editor's draft and delete confirmation. The +reset watcher compares Agent, memory ID and mode as separate sources, so a freshly loaded row +object cannot reset those local states. The existing real-dialog regression also verifies a row +refresh preserves the draft, confirmation and subsequent progress/error behavior. DOM, layout, +preload and IPC contracts are unchanged. + +The instance-fence validation passes 986 Memory cases, 372 native cases with two existing platform +skips, seven quality cases, 383 Harness/plugin cases and 11 focused context/plugin cases. Its +policy/selection/allocation receipt matches the unchanged G5 baseline exactly. The application +batch passes 11 scenarios and exposes the inline-reset race above in the remaining impact case; +affected lineage/impact acceptance then passes both original cases with zero retries, retaining +all original receipts. Together these establish the same 12 selected application scenarios; +the ten unaffected successful cases are not repeated for the local watcher change. +The Memory renderer selection passes 63 cases across three existing files. Only the one new +plugin lifecycle case is retained; existing component and token fixtures are extended in place. + +G6 completion: published source `bd64c2d53a903cdcc6096ccab01848a6d3179e19` passes +[required checks](https://github.com/ThinkInAIXYZ/deepchat/actions/runs/41401181090) and all six +[native verification packages](https://github.com/ThinkInAIXYZ/deepchat/actions/runs/41401181085), +including both aggregate barriers. Main has 715 passing files, two existing skipped files, +10,361 passing cases and eight existing skips. Every platform executes and passes DuckDB VSS, +Memory, Core application, OpenDAL, offline OCR and unchanged installer-size verification. +The critical steps are successful, not skipped. The whole-goal audit confirms the sole facade +constructor is in the Memory plugin and replaced lifecycle wiring is absent. The PR remains +Draft against `dev`, mergeable and unmerged. Compact evidence includes +`six-platform-completion-audit.json` under the G6 acceptance directory. G7 may begin; signed +distribution remains release-gated and no performance improvement is claimed. + +## G7. Migrate the control owner to Rust + +Admission queues/permits belong to `crates/deepchat-harness/src/admission.rs`; scope, Run, +interaction and input control belong to the Rust control owner. The default-wired group is +published at `f1b425e4be3b28f496b446c5987fdf59363ae84e`. Its local functional acceptance and required +PR checks and all six actual native package targets pass, including both required aggregates. +G7 is complete; G8 records the selection no-go below. + +The Core worker retains Provider/loop effects, AbortControllers, rich Run data and shared-database +transaction closures. Instance, Run, operation and message fences span `DeepChatAgentRuntime`, +`DeepChatAgentInstance` and `RunLifecycleCoordinator`; Rust grants and acknowledgements govern +interaction, input and effect transitions through their local drivers. + +Source audit identifies a concrete distributed-settlement constraint: `DurablePendingInputClaim` +records a disposition even when post-persistence publication fails or durable verification is +unavailable. Preserve that fence across lost replies; do not translate an RPC rejection into an +automatic release/retry. Instance/controller identities, symbol drain leases, interaction locks, +resume exclusion and Provider resolvers must be classified as control authority or local effect +handles before defining the wire contract. `LoopRun` also contains messages, stream state and +prompt/tool resources that remain TS data, rather than moving the complete object through RPC. + +The remaining caller audit also includes Provider permissions, transcript mutation, Session +settings, compaction, context-occupancy projections and stream-request identity. Input preparation +has separate send/steer acceptance lanes. Its per-Session queue limit is ten, distinct from the +delegation owner's six-active/256-pending limits. User-fact creation and claimed-row linking are +one local Store transaction. The synchronous pre-stream callback consumes a manually resumed +claim once before rate-gate waiting or Provider streaming; an async RPC cannot be inserted as an +unawaited callback. Rollback requires the current instance, while originating Run/controller +cleanup may settle after replacement. Provider permission callbacks also have expected-object +identity and may use an empty tool-call ID; do not apply a generic nonempty-ID rule to every field. +The loop's 128-tool execution limit does not prove a matching legacy interaction-hydration bound. + +- [x] Inventory the complete cluster's writers, synchronous checks, awaited effects, durable + claims/dispositions and recovery paths; define bounded operation-level transition commands + and their accepted/stale/cancelled/unknown outcomes in the RFC before implementation. +- [x] Select the same public application control/interaction workloads for immutable G6 baseline + and candidate comparison. Keep resource observations informational; do not add further + performance sampling, a relative CI timing gate or a process per helper. + +- [x] Move admission queue/permit authority to Rust as a complete slice, preserving ordering, + cancellation/late grants, fairness and diagnostics. Delete the TS queue owner after E2E parity. +- [x] Move Run transitions, interaction parking/cancellation and pending-input settlement as one + cohesive control owner. Preserve instance/run/operation/message fences and durable claimed + dispositions; do not proxy individual checks or create two run registries. +- [x] Keep loop/Provider/tool effects behind bounded operation-level calls. Define revalidation + around awaited durable operations and deterministic event ordering back to TS consumers. +- [x] Compare all control/recovery behaviors in the actual app and remove replaced TS control + ownership. Preserve existing resource reports without additional performance sampling; + verify architectural changes without claiming an unmeasured speed gain. +- [x] Publish the verified control group to the same Draft PR; verify required CI and all six + actual native package targets on that exact source before G8. + +Completion: the Rust owner is authoritative, public behavior and lifecycle/recovery semantics are +verified, required checks pass and replaced control wiring is removed. Existing resource reports +are informational; further performance sampling does not gate this structural migration. This +phase need not eliminate the TS runtime used by Provider/protocol adapters. + +### G7 admission slice and acceptance + +The admission slice moves its complete owner into the existing Rust host and removes the +dedicated admission TS runtime. Its acceptance is separate from the cohesive control-group +verification below; admission alone does not complete G7. + +Use accepted source `bd64c2d53` and its verified normal output as the immutable G7 baseline. +Compare unchanged `01-launch`, `36-chat-streaming` and `44-admission-delegation` actual-app cases, +with disposable profiles, zero retries and the same local Provider fixture. Delegation exercises +two parent Sessions, six executing children, two queued children, queued interruption, release and +the final seven-completed/one-interrupted result. Existing actual-host suites also cover owner +fairness, owner headroom, suspend/resume, pre-abort/late grant, closure, host loss, caller ordering, +generation retirement and oversized-frame teardown. Retarget obsolete TS-owner tests to useful +Rust or actual-host behavior coverage; do not retain a second implementation solely for tests. + +Process placement changes, so capture one baseline/candidate delegation resource pair using the +existing observer, including the whole process tree. The structural objective is one fewer idle +Node runtime and one Rust admission authority. Retain the existing 64-MiB settled/128-MiB active +peak footprint allowances and report startup, CPU and loop-p99 alongside the G3 reference limits +(`max(100 ms, 2 x baseline MAD)` readiness, `max(10 ms, 2 x baseline MAD)` loop-p99). +One pair cannot establish variance or a speed gain. Investigate a concrete suspected material +regression with at most one confirmation batch under the RFC's measurement rule. Shared CI timings +remain report-only. Production deadlines, queue and transport bounds remain unchanged. + +Freeze and verify the G7 baseline before changing build output. Once that replacement baseline is +verified, remove the ended G6 source-`1719f8075` output tree and retain its compact manifest and +acceptance receipts. Do not accumulate completed acceptance packages or profiles. + +The default admission implementation and its old-path cleanup are locally verified. Main and Core +share the Rust queue through separate ordered lanes. The dedicated TS admission runtime, its +plugin and queue implementation are removed. Readiness protocol 2 reports a Rust owner; actual +native workers retain independent executable/ABI checks. Service tests use the real Rust owner; +there is no retained TS queue solely for fixtures. Three Rust cases protect scheduling/cancelled +reply cleanup, plugin retirement and bounded diagnostics, while three client cases retain input, +hostile-correlation and known-ID late-grant protection. + +Both immutable baseline and candidate pass the same seven actual-app cases: five launch/recovery +cases, editable streaming and the delegation workload. Zero retries. A normal full application +build and final host build pass. Local validation passes 11 Rust tests, 33 client/actual-host/package +cases, 294 delegation/tool consumer cases, 94 native-vector/tool-service cases and 16 Core/Memory +lifecycle cases. These scopes are separate; intermediate reruns are not additional coverage. +Format, i18n, lint, Node/web types and locked Cargo fmt/clippy pass; lint retains the existing +unrelated Remote `no-unsafe-finally` warning. Source `1b054b22a` passes +[PR Check](https://github.com/ThinkInAIXYZ/deepchat/actions/runs/41408347659) and +[Package Check](https://github.com/ThinkInAIXYZ/deepchat/actions/runs/41408347398), including both +required aggregates. Main has 714 passing files, two existing skipped files, 10,338 passing cases +and eight existing skips. All six macOS/Windows/Linux x64/arm64 packages actually execute and pass +the six critical native/installer-size steps; none are skipped. Compact audit: +`admission-six-platform-completion-audit.json`. This completes admission acceptance, and the next +The cohesive control group is accepted separately below; admission evidence remains valid. + +The single delegation resource pair observes Rust-host direct runtime children decreasing from +two to one. Whole-tree sampled footprint median is 1036.26/964.03 MiB and sampled active peak is +1083.12/1153.15 MiB (baseline/candidate): the peak increases 70.03 MiB, within the 128-MiB allowance. +Observed process-lifetime CPU totals are 8.4167/6.4002 seconds, UI readiness 2624.04/1923.68 ms and +Main loop-p99 29.25/27.36 ms. There are 11/7 time samples across unequal fixture durations. These +are descriptive observations, not repeated-run variance, stable settled-idle evidence or a speed +gain. No material over-budget sample selects a confirmation batch. Raw reports and the calculation +method are retained in `admission-resource-pair.json`; no unchanged benchmark rerun is selected. + +The G7 baseline is accepted source `bd64c2d53`, verified across 2187 output files. The ended G6 +source-`1719f8075` baseline is removed after verification, freeing 353,330,090 bytes; its manifest +and compact receipts remain. The full G7 control acceptance is recorded below. + +### G7 control owner implementation status + +The accepted source `f1b425e4b` contains the Rust scope/operation/Run state machine, the private +generation-authenticated asynchronous Native command, the bounded Core command client and local +effect-handle driver. Default Core assembly passes this port into the Harness. Instance creation, +Run registration/replacement, cancellation, readiness, status and originating cleanup use Rust +receipts. Turn, interaction, compaction, settings, prompt/tool resolution and transcript-mutation +callers await the required grants. Retiring a scope fences local handles immediately; the Rust +record retains originating Run cleanup. Status carries an explicit originating owner, and local +finalization runs while applying its Rust receipt, before the next command can replace that owner. + +The synchronous Rutis method was rejected after inspecting the SDK's unbounded session pump. +Finite asynchronous transitions use prepare, synchronous local fencing and commit acknowledgement. +Timeouts fail the Core generation; queued work and late replies cannot revive its effects. No +additional runtime, dependency, feature flag or alternative control owner is introduced. + +Rust now owns interaction, resume and drain exclusion through exact originating leases, and +Session parking survives Instance replacement. Destruction revokes older scopes' parking authority. +The separate send/steer attachment FIFO lanes also belong to Rust: queued calls return tickets, +release receipts grant the next waiter, and cancellation retains predecessor order. Active +preparation holds its lane across scope replacement; its driver checks the originating scope +before input writes. Root ticket count/metadata are bounded at 1024/16 MiB. TS retains only waiter +resolvers and opaque granted lease handles. The Instance lock/resume sets, symbol drain slot, +attachment promise-tail map and separate `InteractionParkingRegistry` are removed. Session +destruction fences the originating Instance before awaiting Rust cleanup. + +Provider permission selection and once-only resolution now belong to Rust. Callback objects stay +in TS under Rust-selected IDs, registration is acknowledged before stream notification, and +cancellation denies the originating callbacks. Deferred tool controllers are children of exact +interaction/Run leases; duplicate acquisition is busy and parent revocation fences the child. +The Instance resolver/controller owner maps are removed. Operation reuse carries an explicit Run +identity for status and local cleanup, preventing an old Run from settling a newer Run. + +Interaction references, stable order/origin reconciliation and tool-batch invocation/commit/pending +IDs now belong to Rust. Status and pending projection change atomically. TS retains the execution +contract under the Rust-selected batch ID. Handlers ask Root to validate the first interaction and +its exact lease; positive Provider/deferred grants repeat that validation. The resolved transcript +is persisted before advancing the pending projection. + +The final Rust owner passes 22 cases and scoped Clippy with warnings denied. The actual Rutis/Core +boundary suites pass 19 cases, with the expanded driver contract independently passing against +the final unified host. They verify batch identity/opaque contract retention, Root order and +first-item grants, duplicate advance, old-Run projection rejection and originating claim/drain +cleanup. Main process effects are covered by the complete checks below. + +Input selection, claim/effect dispositions, active steer identity, restart/manual holds and wake +priority are now default-wired to Rust. Claimed-row writes require a prior Root grant and a +synchronous Store-summary recheck. User facts retain their SQL transaction, and manual pre-stream +consume uses a fixed preauthorized ticket. Applied/unknown outcomes cannot authorize release or +replay. Rust blocks Run/operation replacement during an unacknowledged user-fact or rollback; +originating negative cleanup remains valid after retirement. Promotion reserves its steer effect +before releasing the preparation claim, closing the new cross-RPC claim window. A pending enqueue +receives an explicit Root wake decision, so an event recorded during generation cannot become an +error-state retry later. + +Startup and database reopen await Root recovery. Current claim, steer and drain references reject +recovery without state mutation. Maintenance fences local tickets, drains background turns/effect +acknowledgements before switching the database, and awaits the imported holds before reopening +business ingress. Session destruction fences all originating local input handles and forgets +Session control only with its last scope nonce. A retired or subsequently replaced scope cannot +clear the successor's inputs, parking or acceptance lane. The generic drain acquisition/release +wire and Instance methods are removed; input selection is the sole grant path. + +The unified normal application build passes, including Node/web typechecks, Rust host, Main, +renderer and CLI. Normal Provider/ACP registry refreshes are retained. The final affected group +passes 24 files and 903 cases without unhandled errors; a separate real-Rust promotion ACK gate +passes the release/reservation concurrency regression. The other Main run excludes those exact +24 paths. It passed 689 files and 9363 cases; two +remaining effect-only fixtures needed the new readiness method. After that localized correction, +the two files pass all 56 cases. Combined unique Main coverage is 715 passing files and 10312 +passing cases, with two unchanged skipped files/four skipped cases. The failed original run is +retained in `control-remaining-main-tests.log`, and the correction is verified by +`control-remaining-fixture-tests.log`; the unchanged 903 cases are not repeated. These final +results are retained in `control-final-affected-tests.log`, +`control-final-affected-files.json`, `control-promotion-ack-gate-tests.log` and +`control-application-build.log` under the local G7 acceptance directory. + +Existing effect suites use independent real Rutis hosts, with one shared bridge build per suite. +The old Admission fake-owner suite is removed; its four unique effect-failure protections are +retained in the real Harness suite, and existing Harness/Pump contracts cover the overlapping +public input behavior. The ownership guard tracks `beginInput`/`endInput` instead of removed drain +methods. Its thin-facade size allowance is 360 lines to include the required readiness, drain and +recovery forwarding ports; no control implementation moves into that facade. + +The selected full control application workload covers ordered question responses, one continuation +per resolved batch, visible Steer with explicit cancellation, exactly-once input facts, and +held queues across graceful restart/manual resume. It uses the same local model/server gates and +public routes against immutable G6 source `bd64c2d53` and the candidate. Existing launch, streaming +and delegation scenarios retain their verified baseline evidence. Functional delivery, behavior +compatibility, recovery and required correctness/platform checks take priority. Additional resource +sampling is not selected and does not gate this structural migration; existing observations remain +available without a performance claim. + +Both new public control cases pass against the candidate without resource observation. The +baseline passes ordered questions but changes one of two queued items to `blocked` during +graceful shutdown; the candidate preserves both as `pending` and completes manual restart +recovery. The baseline failure also reproduces without the observer. This is a characterized +baseline defect, and the candidate retains the strict queue-preservation assertion. The reports +and trace are retained without additional performance sampling. Each baseline comparison restores +and verifies all 2183 candidate artifact hashes before removing its temporary baseline copy. + +The candidate additionally passes all 12 existing startup, streaming, delegation, database +maintenance, credential and shutdown application cases, with zero retries. These validate actual +public wiring and database reopen after the new input-effect drain. Reports are +`control-candidate-functional.json` and `control-candidate-regressions.json`; baseline findings +are in `control-baseline-functional.json` and `control-baseline-functional-swap-receipt.json`. + +Whole-change review confirms one Rust control authority with local TS effect handles, no added +runtime/dependency/fallback, and removal of the replaced control paths. The group is committed and +pushed at `f1b425e4be3b28f496b446c5987fdf59363ae84e` on the same Draft PR. Its exact-source +[PR Check](https://github.com/ThinkInAIXYZ/deepchat/actions/runs/41572645470) and required aggregate +pass. [Package Check](https://github.com/ThinkInAIXYZ/deepchat/actions/runs/41572645550) and its +required aggregate pass. Every macOS/Windows/Linux x64/arm64 target actually executes and passes +packaged DuckDB VSS, Memory runtime, Core application, OpenDAL, offline OCR and installer-size +verification; none of those critical checks is skipped. The compact actual-step audit is +`control-six-platform-audit.json`. This completes G7 on source `f1b425e4b`; the G8 selection +no-go is recorded below. No merge or release is performed. + +## G8. Select measured Rust batch work + +- [x] Inspect Tape/context projection, tool-output processing and Memory computation through their + actual consumers, data owners and native bindings. +- [x] Decide whether a useful bounded Rust batch is justified before adding another protocol, + implementation or dependency. The current selection is **no-go**. +- [x] Preserve the single existing implementation and actual consumer wiring for each rejected + candidate. No alternate path, experimental flag or disposable benchmark is retained. +- [x] Review the decision against the functional priority and validate the documentation-only + change. Runtime behavior and package inputs remain the accepted G7 source. + +### Selection outcome + +No CPU-oriented Rust batch is selected. This is a selection no-go, not a claim that the current +code is fast or that Rust cannot improve it. No measured CPU/allocation bottleneck establishes a +reason to add the boundary, and functional delivery takes priority over speculative profiling. +The audited mechanisms are concrete: + +| Candidate | Current consumer and ownership evidence | Decision | +| --- | --- | --- | +| Tape effective view and replay/context projection | `TapeFactService.getMessageRecords` and `TapeRecallService.getEffectiveMessageSourceSpan` synchronously fold rows from the current Store. Replay consumes the same effective rows and canonical identities/hashes. | Retain the owner-local fold. Moving only computation to the separate Rust host requires transferring rows and projected output while keeping the Store/TS consumer. There is no selected bounded data-local operation or independent lifetime to justify that new boundary. | +| Tool-output processing | `ToolOutputGuard.fitToolBatchOutputs` already accepts a batch. Its fit policy consumes conversation messages, tool definitions and configured limits; fallback uses the existing Node filesystem artifact/cancellation path. | Retain the existing batch and offload path. Moving only scanning does not remove the caller, filesystem owner or TS runtime, and introduces context/output transfer plus duplicate policy/estimation semantics without demonstrated benefit. | +| Memory computation | Retrieval already batches embeddings and vector queries. Distance/order/limit execute inside native DuckDB. The remaining TS retrieval/refill policy caps candidates at 800 and applies current temporal, scope, lineage and ranking policy. | Keep native vector work and the bounded owner-local policy. A Rust wrapper does not change DuckDB's algorithm, and no significant residual scoring cost is established. | + +The normative source-backed decision and links are in the RFC. Reconsider only after a concrete +stage bottleneck is observed or a compatible Store/data-owner move makes a bounded operation +local to Rust. No measurements, production changes, test additions or package rebuild are selected +for this no-go. Existing G7 functional and six-platform evidence remains applicable because only +the RFC and this tracker change. G9 is the next functional capability selection after publication. + +Validation: all 39 local documentation links resolve. Repository format, i18n, lint and Node/web +typechecks pass; lint retains its existing unrelated Remote warning. The complete change from +accepted `f1b425e4b` is limited to this plan and the RFC. Application source, tests, dependencies, +resources and build/packaging inputs are unchanged; `electron-builder.yml` excludes documentation. +The accepted G7 real-app, required-CI and six-native-package evidence is therefore reused without +another build, benchmark or runtime-test batch. + +Completion: the explicit evidence-backed selection no-go above. No Rust performance improvement +is claimed and no partial migration is left connected. + +## G9. Extract peripheral services with independent useful lifetimes + +- [x] Select one actual ownership group: process jobs/scheduler/watchers, documents/OCR/search, + Knowledge/RAG, MCP/ACP connections, Skills/hooks or remote channels. Do not create all proposed plugins + merely to match the capability map. +- [x] Reuse current native helpers and supervisors, preserve tool/route/registration permissions, + and connect actual consumers through one generation-bound lifecycle owner. +- [x] Compare the module's real workload, unload/lost-reply/restart/revoke cases and resource + envelope; account for idle Node processes removed or added. +- [x] Review, validate and remove the replaced manual wiring. Repeat only for another selected + module with a concrete lifecycle or measured resource benefit. +- [x] Publish this coherent source to the same Draft PR and pass required CI plus all six actual + native package targets before starting G10/G11. + +Selected: product Hooks, as defined in the RFC. The Rutis attachment owns actual HookService +construction and permanent retirement in the same Core worker; maintenance retains temporary +pause/drain/resume. This removes manual terminal lifecycle wiring and fences stale service +references, including partial Kernel construction. No extra worker, transport or supervisor. +The accepted `f1b425e4b` application build is hash-verified before the existing Hooks application +comparison; baseline evidence is under `.deepchat/rutis-g9-hooks-acceptance/`. No new performance +sampling is selected. + +The default cutover is implemented. Rust loads `core-hooks` into the existing Core worker; +Core/Kernel pass its local capability to runtime composition, which no longer constructs +HookService. Kernel terminal cleanup delegates to the plugin's memoized retirement; Core cleanup +also retires an attachment left by partial construction. The existing observer/configuration/test +routes share that one service. Maintenance still uses temporary stop/start; retired service +references reject restart and Store configuration reads/writes. No compatibility switch, extra +worker, generic transport or alternate implementation remains. Idle Node process delta is zero; +the same-process SDK group keeps synchronous callback ports local. Transport loss remains covered +by the accepted Core generation retirement contracts; no Hooks-specific RPC was introduced. + +Local verification: + +- All 2,183 accepted application files match their manifest before baseline execution. +- The existing Hooks real-app workload passes both cases on baseline and candidate: configuration + save/test/persistence across restart; real Session hook payload and shutdown waiting for native + child stdio closure, followed by restart without replay. +- Candidate also passes the five existing database-maintenance/shutdown cases, seven actual-app + cases total, zero retries. Import/overwrite/repair, encryption exclusion, delayed Native + credentials and shutdown drains preserve temporary service recovery and Store ownership. +- Relevant Main tests pass: 17 files, 181 cases. One durable SDK unload case is added to the + existing HookService suite: pending enrichment and child closure must drain; old references and + configuration access stay fenced; a new plugin generation can open a fresh attachment. The + suite passes all 40 cases after its final stale-read assertion. No parallel fake owner is added. +- Normal application/CLI build, Node/web types, Rust 22 cases, Cargo fmt, all-target Clippy with + warnings denied, repository format, i18n and lint pass. The existing unrelated Remote lint + warning remains. Normal generated Provider refreshes are retained. +- Independent ownership review finds no blocking issue. Application/native checks remain the + authority for actual default assembly; no hot-reload or resource improvement is claimed. + +The source includes `dev` at `145e51763`, retaining beta.8, dependency updates, Wally, Bowmark and +all sponsors. Lockfile integration keeps the upstream resolved dependency graph and adds the +existing Rutis/Provider patches. The HTTP shutdown fix is applied to the actual resolved +`undici@8.12.3`: retain child dispatchers until graceful closure so forced shutdown can still abort +held requests. Patch applicability and a real held-request self-check pass against the +integrity-verified official release. The new Bowmark/Wally fixtures use the required owner-local +Store/Provider environment ports. Frozen installation passes. Integration verification includes +the changed MCP/HTTP dependencies and file-search/Nowledge boundaries, without new test files. +The integrated build passes Node/web typechecks and the normal application/CLI build. Across +disjoint relevant selections, 44 Main files/658 cases pass; the new Wally environment fixture is +corrected and its 63-case file passes without repeating the 429 already-passing dependency cases. +All 11 integrated actual-app cases pass with zero retries: the two matching baseline Hooks cases, +five maintenance/shutdown cases, MCP stdio ownership/child cleanup, MCP App sandbox/consent/reopen +and two Native Provider authorization/HTTP shutdown cases. Format, i18n, lint and diff checks pass; +the existing Remote warning remains. Integration evidence uses `hooks-integrated-*` logs/reports +and `integrated-candidate-manifest.json`; exact-source acceptance is recorded below. + +The `993b21a12` Main CI run reports 10,308 passing cases and four failures in three files. +An obsolete HookService constructor-source assertion is removed; the actual SDK unload and +application contracts cover that ownership. Fixed-duration test waits are replaced with existing +Provider-entry and durable assistant-completion signals while preserving nonblocking send, +immediately invisible claimed input, fact/content, event and stale-Run assertions. + +The CI also exposes a real interaction-observation race: an owner captured before entering the +Core lane may be stale after a queued prepare/commit, although the incarnation remains current. +A gated actual Native receipt proves the rejection. Reconciliation now checks readiness/scope and +rereads durable facts for the first attempt and every known owner rejection, with a bounded retry +budget; explicit old-Run writes, replacement scopes and unknown outcomes remain fenced. One +actual-Root regression in the existing RunLifecycle suite covers owner rotation, changed durable +facts, the readiness wait and rejection of an explicit old-Run write. Cold-recovery assertions +remain intact. No new protocol, positive local authority or test file is added. + +Repair validation passes: normal application/CLI build and Node/web types; 25 disjoint relevant +Main files/918 cases; and all 13 actual-app cases (the integrated 11 plus ordered question replies +and Steer cancellation/restart/manual queue recovery), zero retries. The new actual-Root +reconciliation regression passes with the existing 20 RunLifecycle cases. Runtime integration's +20 cases pass after replacing its fixed business waits with one completion helper and explicit +Provider-entry gates; immediately invisible pending input and context/content assertions remain. +The earlier logs retain fixture failures instead of rerunning the unchanged 440/477 passing groups +to erase their history. Format, i18n, lint, document links and diff checks pass. Repair evidence uses +`hooks-repair-*`, `hooks-owner-reconciliation-tests.log` and `repair-candidate-manifest.json`. +The accepted repair source is `6607e0b27`: + +- [PR Check 41582709161](https://github.com/ThinkInAIXYZ/deepchat/actions/runs/41582709161) + completes successfully, including aggregate `125744664604`, Main (715 passing files/10,312 + cases; two files/eight cases skipped as in the preceding CI), Renderer, Native Memory, + application build and static checks. +- [Package Check 41582709186](https://github.com/ThinkInAIXYZ/deepchat/actions/runs/41582709186) + completes successfully, including aggregate `125747684574` and all six actual macOS/Windows/Linux + x64/arm64 targets. Every target actually passes packaged DuckDB VSS, Memory runtime, Core + application, OpenDAL, offline Light OCR and installer-size verification; none of these checks + is skipped. This is PR verification, with distribution signing/notarization kept in release gates. +- `hooks-repair-six-platform-audit.json` records exact-source job and critical-step evidence. + The verified immutable baseline is the accepted `6607e0b27` output (2,185 files), recorded in + `accepted-baseline-manifest.json`. The ended G7 `bd64c2d53` build copy is removed only after + replacement hash verification; its manifest and raw comparison evidence remain. + +Evidence is under `.deepchat/rutis-g9-hooks-acceptance/`: `baseline-manifest.json`, +`hooks-{baseline,candidate}-functional.{log,json}`, `hooks-application-build.log`, +`hooks-affected-tests.log`, `hooks-final-service-tests.log`, Rust/static logs and the publication +receipt. Ended owned media/profiles are removed after compact receipts are retained. + +## G10. Durable Store selection: complete, no-go + +- [x] Audit the actual engine, cipher settings, binding ABI, packaging, shared writers and complete + maintenance/recovery boundary; assess plausible Rust linking routes without selecting a driver. +- [x] Review existing real-driver tests and distinguish current behavior from unexecuted Rust + interoperability and cross-file crash acceptance. +- [x] Retain the TS/native Store and its existing consumers. Record the functional/deployment + benefit limit; add no dependency, runtime protocol, prototype writer or performance sampling. + +The current native SQLite3MC engine uses the SQLCipher 4-compatible configuration and a Node-API +v10 binding. The accepted application already ships that binding on all six native targets. A +Store-only rewrite leaves the Node Core in place; ordinary Node execution does not require an +Electron-specific SQLite binding. The whole headless artifact still needs G11 validation. + +No independently useful table slice can transfer writer authority: pending-input claims and Tape, +Memory projections/CAS/FTS/audit, scheduler claims, settings revisions and Sync apply share atomic +operations. A complete Rust owner also requires the profile lock, maintenance/security/backup +connections, commit notifications, grouped asynchronous consumers and pre-stream durable consume. +That is a possible future migration, but no current functional or deployment requirement selects it. +The [RFC selection outcome](spec.md#rust-store-selection-outcome) records the source and primary +driver references. This is a present adoption decision, not a claim that compatible Rust storage +is impossible or that the existing Store is faster. + +Current regression evidence uses actual SQLite, SQLCipher, temporary files and child processes: +`profileStartupNative`, `databaseSecurityNative`, `mainDatabase`, `memoryNativeMigration`, +`backupConsistency`, `dataImporterNative` and `transcriptAtomicity`. Existing application scenarios +51/54 exercise encryption, import/maintenance and graceful restart. Journal SIGKILL tests do not +establish atomicity across encrypted database replacement and metadata files. No TS/Rust/TS file, +WAL, competing-lock, error-classification or encryption test is reported as passed for a Rust driver. + +If a concrete Store-bound failure or independent durable-owner deployment requirement reopens +selection, the conditional cutover requirements remain: + +- Use one compatible encrypted writer and finite business commands; preserve schemas, Unicode/FTS/ + notifications, once-only input consumption, snapshot reads and guarded Sync transactions. +- Prove real cross-engine file/WAL/key compatibility and writer exclusion, plus migration, + import/export, rekey, backup, partial failure, poisoned handles and crash/recovery behavior. +- Connect every actual consumer and maintenance owner, compare application behavior, validate all + native targets and remove replaced ownership. Signed distribution remains a release gate. Any + explicit resource-gain claim needs evidence; structural delivery does not require new sampling. + +G10 changes only this RFC and plan, which application packaging excludes. Runtime and native-package +evidence is reused from accepted source `6607e0b27` and the G9 required/all-six acceptance above. +The checked diff contains only these two documentation files. All 46 local references, diff +whitespace checks, repository format, i18n, lint and Node/web typechecks pass. Lint retains the +existing unrelated Remote `no-unsafe-finally` warning. No unchanged application or native workflow +is rerun solely for this decision. + +## G11-G12. Deliver Everywhere from the extracted core + +G11 uses the existing Rust host, Core worker, sole profile writer and authenticated local-control +CLI surface. `src/headless` supplies a foreground ordinary-Node owner and actual system/helper +adapters. `pnpm headless:build` creates a self-contained native closure for the build platform; +the running distribution needs Node 24, without Electron or a checkout. Desktop builds exclude +this separate artifact. No alternate Harness, database writer, approval policy or application +protocol is introduced. + +An explicit `--profile` and a stable `DEEPCHAT_HOST_KEY` are required. The 32-byte base64 master +key derives separate authenticated-encryption credential and OCR keys; it is not persisted or +inherited by child processes. This host does not claim an OS keychain backend. An encrypted database +may additionally use an explicit manual unlock password. Recovery failures preserve the profile; +there is no automatic reset or silent credential plaintext fallback. + +The existing CLI accepts `DEEPCHAT_USER_DATA_DIR` for profile discovery, retaining the E2E alias. +Approved work survives viewer disconnect; the foreground owner controls service lifetime. Native +desktop windows, Browser/CUA permissions and UI-only operations report unavailable. Sensitive CLI +mutations use the same policy/broker and require an exact per-request terminal confirmation; +without a host TTY, those mutations fail closed. Terminal approvals never acquire renderer IDs. +Cancellation aborts and physically retires a prompt before its request completes. + +Local acceptance passes on ordinary Node 24.22.1/macOS arm64. The complete distribution is copied +outside the checkout: no Electron resolution, startup/version, second-owner rejection, actual HTTP +Provider Sessions, query/cancel/completion, owner death, restart recovery without replay and all +three Node/Rust/Core PIDs retiring pass. Eight real PTY cases pass on the untouched final artifact: +deny, wrong request ID, exact approval and persistence, cancellation/disconnect followed by another +prompt, same-key restart and no-TTY refusal. The login-shell environment probe runs in a separate +session so it cannot steal the foreground terminal. Prompt construction/errors cannot poison the +next approval. Fifteen helper checks cover real operations, blocked asynchronous Code continuations, +parent death, descendants and bounded Node/Worker send queues. + +Existing relevant suites pass: four CLI/native-port files have 60 cases; the final shell-environment, +approval and real Core-runtime selection has 34 cases; SecretStore has nine cases. These selections +overlap and are not summed. The same actual desktop approval workload passes against the immutable +accepted `6607e0b27` build and the final candidate. Normal application build, final Harness/app/CLI +rebuild, headless build, format, i18n, lint, Node/web types and all 46 local links pass. Normal +Provider/ACP refreshes remain. All disposable comparison copies/profiles are removed after receipts; +the accepted G9 baseline is retained. + +Integration acceptance remains open until required checks and all six actual platform jobs pass on +the published source, including the new standalone build/smoke steps. Windows forced termination +is owner-death evidence, not proof of a POSIX graceful signal. The smoke checks dead-PID refusal +when a forced exit leaves a discovery descriptor. Offline Cargo metadata is filtered to the actual +Harness target: a fresh target-only cache reproduces failure without the filter and success with +it. Available dependency licenses are inventoried without claiming missing texts were included. +No release/distribution or performance gain is claimed. Compact evidence is under +`.deepchat/rutis-g11-headless-acceptance/`, including `headless-final-{build,smoke}.log`, +`headless-tty-approval.json`, helper receipts and matching desktop reports. + +- [x] G11: provide non-Electron configuration, credentials, filesystem/process and approval ports; + boot a declared headless Node/Rust distribution with actual CLI submit/query/cancel/recovery. +- [ ] G11: verify offline startup, no Electron imports/installation, native packages, storage + ownership, shutdown and service policy after all clients disconnect. +- [ ] G12: add authenticated application ingress and reconnect-safe bounded events/snapshots; + demonstrate desktop and browser clients observing the same authoritative Session. +- [ ] G12: enroll device capabilities with explicit resource grants and revoke/disconnect semantics; + exercise a real approved operation, stale reply rejection and no implied sync authority. +- [ ] Review each horizon separately, run its actual E2E comparison and platform/security/resource + checks, and delete replaced wiring when that horizon cuts over. + +## Validation evidence + +G0 baseline: Node 24.22.1, pinned pnpm 10.34.5, macOS arm64. The existing admission and +LiveDelegationService suites passed together: 2 files, 82 tests. Full application build passed; +normal provider/ACP registry refreshes are retained. + +- Baseline Electron startup/streaming E2E: 4 tests passed. The shared delegation migration workload + separately passed against the baseline build. +- The delegation workload uses two parent Sessions and 8 child turns against a local model fixture: + 6 execute, 2 queue, one queued turn is interrupted, a released slot starts the other queued turn, + and the final state is 7 completed turns and 1 interrupted turn. Graceful shutdown and admission + diagnostics are checked through the actual app, public IPC and stored log. +- Updated owner, delegation, runtime, route, composition/shutdown and package-contract suites pass: + 8 files, 143 tests. Runtime coverage uses the real Rust host and Electron Node mode; one test copies + the artifact closure outside the checkout and verifies launch without repository resolution. +- Runtime cases cover owner limits/fairness, pre-aborted and queued cancellation, lease + suspend/resume/release races, asynchronous observer failures, shutdown, forced runtime loss, and oversized-frame rejection with child teardown. + Runtime loss rejects pending work and leaves no live replacement host. +- Baseline and final migrated application each pass the same 5 Electron E2E checks: 3 startup/ + recovery checks, 1 streaming-chat check and 1 delegation-admission check. The final migrated run + completes all checks with graceful app shutdown. +- Repository format, typecheck, i18n, lint and locked Cargo fmt/clippy checks pass. +- Only the Rutis TS plugin constructs `AgentInvocationAdmission` in application source. Main uses + the typed client; there is no local queue fallback, separate probe command or implementation flag. + The normal build's provider/ACP registry refreshes are retained. +- G0 execution results are local macOS arm64. Signed release packages and native Windows/Linux app + execution remain unverified. G2 records the paired resource baselines; no performance gain is claimed. + +G1 validation: only this RFC and plan changed after the admission commit. All 28 local document +references resolve; diff whitespace checks and repository format, i18n, lint and Node/web typechecks +pass. No new source, dependency, build configuration or test changes were made. G0 tests/E2E were +not rerun for this documentation-only goal; no profiling, Rust comparison or Memory native benchmark +was executed for G1. Implementation evidence is recorded under the corresponding live goal. diff --git a/docs/architecture/rutis-multiprocess-backend/spec.md b/docs/architecture/rutis-multiprocess-backend/spec.md new file mode 100644 index 0000000000..dd86de1b06 --- /dev/null +++ b/docs/architecture/rutis-multiprocess-backend/spec.md @@ -0,0 +1,2385 @@ +# DeepChat Everywhere Runtime + +## Status and decision + +Status: G0-G10 complete, including the G8 Rust-batch and G10 Store selection no-go decisions. +G9 product Hooks and current-observation reconciliation are accepted at `6607e0b27`. +G11 headless Session Host is next; the existing TS/native Store remains its durable owner. +Evidence date: 2026-11-03. The accepted source passes required CI and all six native package +targets. G9's matching application comparison uses the hash-verified `f1b425e4b` build. +Implementation reference: Rutis `v0.16.0`, released on 2026-10-24. Crates and npm packages are pinned +to that version. + +Make DeepChat usable through multiple clients, runnable without a desktop window, and able to use +capabilities on explicitly enrolled devices. Separate client presentation, Session authority, and +device execution. Use Rutis to manage runtime dependencies, generations, and approved links between +execution nodes. The first production milestone is a multiprocess desktop backend. + +Keep Agent execution, Provider calls, Session state, Memory coordination, and Tape transactions +together in one TypeScript backend process initially. Create additional runtimes only for a concrete +isolation, resource-locality, or replacement requirement. The application core must accept host +ports instead of requiring Electron, even while the desktop distribution uses Electron's Node mode. + +The objectives are responsive desktop interaction under backend load, independently recoverable +backend processes, replaceable feature implementations, and access to one authoritative Session +Host from multiple surfaces. Additional processes and plugin counts are not success criteria. +No responsiveness or memory improvement is established; cross-device operation remains unimplemented. + +Prefer existing capabilities and dependencies. Composition files and factories are internal +organization, not separate plugins or processes. Split only at a useful ownership or runtime +boundary and preserve behavior while reducing coupling. Keep durable tests focused on behavior, +ownership and recovery; use disposable checks for implementation scaffolding. Update developer +setup and entry-point documentation when their contracts change, preserving sponsor content. + +Implementation proceeds through +[Draft PR #2465](https://github.com/ThinkInAIXYZ/deepchat/pull/2465). The admission migration is +committed as `ffcd98770` (`feat(harness): route admission through rutis`) and its roadmap as +`f35adff34`. Remaining modules follow the dependencies and acceptance gates in [the live plan](plan.md). +Each validated goal is committed and pushed separately before the next selected goal starts. + +Each module migration replaces its real application wiring on the migration branch. Completion +requires running the same end-to-end workload against the baseline and migrated application, +checking lifecycle/failure behavior, and deleting the superseded wiring. A standalone plugin check +is supporting evidence, not a completed module migration. There is no retained local fallback or +parallel production/development owner. + +## Admission module migration + +Agent invocation admission covers capacity, per-owner fairness, pending bounds, cancellation, +permits and diagnostics. Its single owner is a Rust Rutis plugin in the existing host. Main and +Core retain typed clients and caller-owned lease handles; neither constructs an admission queue. + +```text +Main client + lease handles Core client + lease handles + | | + bounded private pipes authenticated Rutis bridge + +------------------+---------------+ + | + Rust host / admission Rutis plugin + one queue, owner ring and permit registry +``` + +The client preserves synchronous lease suspend/release and asynchronous resume. Commands carry +an independent sequence for each caller lane. The Rust dispatcher starts operations in caller order +even when concurrent Rutis RPCs arrive out of order; it does not await a queued acquire before +starting a later release. Cancellation during acquire uses an acquisition identity chosen before the call and sends an +ordered release. The RPC remains tracked until it settles; the remote record aborts queued work +or releases a grant even if cancellation races the reply. Repeated release is idempotent. Per-owner limits, +correlation validation, queue bounds, monotonic measurements and bounded diagnostics remain the +existing owner's behavior. Log observers cannot change admission outcomes. + +The admission gateway exposes its method allowlist over inherited pipes. Its frames are limited to 64 KiB, buffered application writes to 1 MiB, and concurrent calls to 1024. +Diagnostics carry a separate sequence and use bounded forwarding/reordering; malformed observer +payloads and observer failures cannot change admission accounting. There is no public listener or renderer access to the +host protocol. Child generation loss rejects pending requests and closes admission; the application +does not start another owner or retry unknown operations. Graceful shutdown stops delegation, +closes admission, flushes diagnostics and then shuts down the host/runtime with bounded deadlines. + +Build the host, compiled Core/Memory plugins and unmodified Rutis runtime closure as application +artifacts. Admission starts within Rust; Core and the lazy vector worker use the packaged Electron +executable in Node mode. Readiness protocol 2 identifies the admission owner as Rust without an +invented Node identity. Missing artifacts fail startup with an actionable build error. +`pnpm harness:build` builds the locked host and compiled plugins; normal dev, test and application builds call it. The repository +pins Rust 1.95.0 through Rustup. `beforePack` rebuilds the native artifact for the selected target, +and `extraResources` installs the closure under `app.asar.unpacked/harness`. Runtime startup has a +15-second deadline, non-acquire RPCs 10 seconds, host root shutdown 10 seconds, and client force +termination 12 seconds. Queue waits remain caller-cancellable without a new task-duration limit. Runtime +packages are release dependencies through the copied closure, not a boot-time download. + +### Rust owner sequence + +| Owner | Current migration direction | Acceptance boundary | +| ------------------------------------------------------ | ------------------------------------------------------------- | ------------------------------------------------------------------------------------------------- | +| Runtime dependencies and child lifetimes | Rust/Rutis | Actual app startup, bounded shutdown and fail-closed disconnect | +| Invocation admission | Rust Rutis plugin with typed Main/Core clients | Fairness/leases/diagnostics and end-to-end delegation parity; no dedicated admission Node runtime | +| Run control, cancellation and pending-input settlement | Extract explicit ports, then migrate as a cohesive Rust owner | Preserve all four fences, synchronous queue feedback, ordering and durable dispositions | +| Provider, MCP, Skills and product hooks | TS plugins where independent resource lifetimes exist | Retain adapters and use operation-level calls, avoiding token-level RPC | +| Tape, persistence and recovery | Dedicated migration after Harness ports | Preserve schema, encryption, same-connection transactions, recovery and one mutation authority | +| UI, OAuth and native capability presentation | Desktop host adapters | Narrow async ports without Electron-main imports in the Harness | + +Every later module follows the same branch workflow: select owner, replace implementation and +callers, run the real application comparison, then remove the old path. Everywhere remains a +product destination; it does not require migrating all modules at once. + +## Harness and capability decomposition + +### Plugin, process and language are separate decisions + +A capability becomes a first-party Rutis plugin when it owns a coherent service, dependencies and +cleanup lifecycle. An ordinary helper, coordinator or memory category does not need its own plugin. +Plugins may share a runtime. Process separation is justified by blocking work, native crash +containment, resource locality or independent recovery; language selection follows measured cost +and deployment requirements. Neither choice follows automatically from making a plugin. + +First-party Rust services initially ship with the application-owned host executable. The +[upstream Rust host guide](https://github.com/arcships/rutis/blob/v0.16.0/docs/guide/rust-host.en.md) +also documents dylib plugins, but arbitrary binary loading, cross-version ABI compatibility +and live code replacement are separate distribution contracts, outside this migration. Preserve +the existing external-plugin permission boundary: a first-party Memory or Store service does not +give marketplace plugins unrestricted access to profiles, credentials or execution authority. + +Use the existing TS runtime for extracted TS capabilities unless a separate failure domain is +required. Shared-runtime plugins share native crashes and process memory. Rust services also share +the host's failure domain unless deliberately isolated; running blocking database/CPU work on the +host routing task would defeat the responsiveness objective. + +### Source-backed capability map + +The following are ownership groups, not a required plugin count or a new directory structure. +The default Rutis desktop hosts the cohesive Core backend and Memory capability, with Rust +admission and a lazy vector runtime. The map also names later extraction candidates; +their inclusion does not establish implemented isolation or a Rust performance gain. + +| Capability | Current owners and entry points | Proposed service and authority | Initial placement and Rust direction | +| --------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- | +| Runtime and admission | `src/main/harness/{runtime,admission}.ts`, `crates/deepchat-harness/src/admission.rs` | Required-service readiness, generation lifetime, bounded admission and permits | One Rust Rutis queue/permit owner; TS clients retain effect handles; Run/interaction control migration remains part of G7 | +| Run control and interaction | `RunLifecycleCoordinator`, `PendingInputAdmissionCoordinator`, `PendingInputPump`, `InteractionCoordinator`, `InteractionParkingRegistry`, `ProviderPermissionCoordinator` in `src/main/agent/deepchat` | One owner for run transitions, four fences, queued-input dispositions, cancellation and approval settlement | Keep cohesive with the TS Harness first; later one Rust control owner, using atomic transition commands rather than RPC per coordinator | +| Agent execution and context | `DeepChatLoopRunner`, `DeepChatLoopEngine`, `TurnCoordinator`, context/input/compaction coordinators, prompt assembly and `src/main/agent/deepchat/runtime/contextBuilder.ts` | Execute a turn from an admitted input; assemble bounded context; stream ordered results | TS core with Provider streaming nearby; profile JSON, projection, token estimation and pruning before selecting a batched Rust operation | +| Session, Store and Tape | Session lifecycle/settings/identity, transcript mutation and message projection; `src/main/tape`; shared database composition | Durable commands, Tape/projection consistency, recovery and configuration snapshots; exactly one authority for each mutation | Move existing SQLite-backed owners together into TS core; Rust storage is a separate later compatibility project | +| Memory | `src/main/memory/index.ts`, `core/injectionPort.ts`, `ports.ts`; Harness `memory/memoryRuntimeCoordinator.ts` | Recall/injection, extraction, approved directives, management and maintenance; Harness retains turn scheduling and fences | One first-class TS Memory capability, with an optional vector resource boundary; selectively Rust after profiling | +| Knowledge and RAG | `src/main/knowledge/{index,knowledgeBase,taskQueue}.ts`, `database/knowledgeDatabase.ts` | Document/chunk indexing, retrieval, durable file/task status and knowledge-store lifetime | TS capability with its own schema, VSS and file contracts; later evaluate native-worker placement alongside document jobs | +| Provider | `src/main/provider/providerService.ts`, `aiSdk/runtime.ts`, model/embedding/rate-limit managers and `src/main/app/memoryProviderBindings.ts` | Model resolution, request execution, embedding, streaming and request admission | TS ecosystem adapters; credential/OAuth operations through host ports; no per-token cross-language calls | +| Tool execution, MCP and ACP | Tool resolver/deferred executor/output guard in Harness; `src/main/tool`, `src/main/mcp`, `src/main/agent/acp` | Existing permission checks, operation identities, result/artifact bounds, protocol connections and subprocess leases | TS protocol adapters; Rust may own generic process resources later; MCP Apps and ACP terminals retain explicit desktop capability ports | +| Skills and hooks | `src/main/skill/{index,skillExecutionService}.ts`, discovery/sync workers, `src/main/hook`; Harness skill materialization and hook sink | Discover contributions, resolve trusted context, observe lifecycle, execute approved operations and remove registrations on unload | TS contribution plugins where independently useful; keep pure prompt helpers internal and preserve uncertain hook outcome recovery | +| Jobs and process resources | `src/main/scheduler`, `src/main/agent/shared/process`, `src/main/tool/codeMode`, file-watch owners | Schedule calculation, durable run claims, bounded jobs, process-tree cancellation and cleanup | Reuse existing utility processes; Rust supervision/timers are candidates only when an old process or lifecycle owner can actually be removed | +| Documents, OCR and local search | `src/main/ocr`, `src/main/platform/fileSearch/fffSearchService.ts`, attachment capability routing | Bounded extraction/search operations and artifact references, with cache and resource ownership | Reuse existing native engines/helpers; optional isolated job plugin; Rust wrappers alone do not accelerate native work | +| Remote channels and device capabilities | `src/main/remote/runtime/manager.ts`, channel runtimes, existing sync and local control plane | Channel lifecycle and authorized Session ingress; separately scoped device operations and durable sync | TS integration plugins; isolate noisy channels when justified; remote Rutis links do not grant application or filesystem authority | +| Desktop and observability | `src/main/app`, route/preload boundaries, platform adapters and structured logging | Native windows, unlock/crypto/OAuth, authenticated caller creation; bounded content-free diagnostics | Electron adapters remain desktop-owned; diagnostics accompany each capability instead of becoming a new central business owner | + +The Harness root is +[`createDeepChatAgentHarness.ts`](../../../src/main/agent/deepchat/harness/createDeepChatAgentHarness.ts). +Its [runtime services](../../../src/main/agent/deepchat/harness/runtimeServices.ts) already name useful +ports, but synchronous repositories, callbacks, `AbortSignal`, closures and object identities are +not remotely usable contracts. Extracting the composition unchanged into a plugin does not solve +those dependencies. The existing [Harness contract](../deepchat-agent-harness-boundaries/spec.md) +remains authoritative for the four fences and synchronous pending-input settlement. + +Keep run ownership, approval parking and pending-input feedback under one transition authority. +Do not create a transport round trip for each fence check. Similarly, keep Provider token delivery, +tool permission decisions and loop progress local until an operation-level protocol preserves +ordering and backpressure. Progressively remove Electron dependencies through desktop ports; +moving business code out of `src/main` is an outcome of ownership extraction, not a bulk rename. + +### Control migration constraints + +The current instance registry fences by object identity. `DeepChatAgentInstance` also owns active +Run/controller references, symbol-valued drain leases, interaction locks, resume exclusion and +Provider permission resolvers. These are not wire values. The Rust control owner must use explicit +instance, Run, operation, message and lease identities; TS retains the actual AbortControllers, +Provider callbacks, stream state, prompt/tool resources and context caches as effect-driver data. +A TS projection of control state cannot independently authorize an effect or become a second Run +registry. Select the operation boundary across this whole cluster before moving its implementation. + +`DurablePendingInputClaim.settle` synchronously applies a Store transition and records its outcome. +If notification throws after persistence, it checks the durable row; if the outcome cannot be +observed, it still fences the intended disposition against a conflicting second transition. +The control protocol must distinguish rejected, durably applied and unknown outcomes. A missing +reply or publication failure cannot mean that no write occurred, and cannot automatically release +or replay the claim. Queue consumption, attachment blocking, mutation release, pre-user-fact +release and post-rollback release retain their distinct behavior. + +The persisted pending-input rows remain the content and ordering authority. Startup restart holds, +manual-resume intent, consume-before-Provider semantics and deterministic post-settlement wakeup +must survive the control-owner move. Shared Store transaction closures stay in their TS owner; +neither a generic SQL service nor an independently timed RPC for each queue predicate preserves +this boundary. The existing Harness contract remains binding until its actual replacement is +implemented and validated. + +The control boundary is selected by operation, with the following ownership groups. Each group's +wire schema and effect ordering must be fixed before its implementation cuts over. + +| Operation group | Rust authority | TS effect driver and durable authority | +| ------------------------------------ | -------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- | +| Admit/release invocation | Capacity, owner ring, FIFO queues, owner limits, queued cancellation, permit IDs and bounded diagnostics | Provider invocation and lease suspend/resume handles; caller allocates acquisition ID before dispatch | +| Open/replace/retire Session instance | Incarnation identity, active Run and operation slots, current status and first-turn readiness | Hydrated Session configuration, real controllers and readiness waiters; replacement aborts the originating handles | +| Begin/attach/settle/cancel turn | Distinct instance/Run/operation/message fences, cancellation intent and ordered transition receipts | Loop messages/stream/prompt/tool data, transcript transactions and terminal publication; old-Run cleanup cannot mutate replacement status | +| Park/resolve/resume interaction | Ordered interaction references, message/tool-call exclusion, resume leases and permission identity | Tool effects, deferred AbortControllers, Provider resolve callbacks and persisted tool-batch content | +| Claim/drain/dispose pending input | Drain lease, in-flight claim identity, chosen disposition and acknowledged or unknown outcome | Existing Store selection/atomic mutation; persisted input ordering, restart reconciliation and publication | + +An awaited external effect resumes through its owning operation, rather than authorizing work +from a cached `isCurrent` reply. Durable effects require an explicit identity and outcome receipt; +cancellation cannot erase an already admitted mutation. An unknown receipt fences conflicting +dispositions until durable reconciliation. Runtime loss retires the generation instead of +reconstructing authority from TS snapshots. Cleanup remains bound to its originating incarnation. + +The first G7 slice uses the existing admission acquire/release/snapshot/close/flush method set, +64-KiB frames, 1-MiB output quota and 1024 concurrent-call limit. The Rust Rutis plugin replaces +the TS queue owner and its dedicated runtime. Main and Core retain independent ordered caller +lanes; a queued acquire never blocks a later release. Core retains its authenticated row/generation +binding, single-generation dispatcher and reserved cancellation/cleanup capacity. Permit IDs are +known before dispatch, so cancellation releases a late grant without replaying acquisition. +Owner IDs and correlations retain the 256-UTF-16-unit bound, owner limits clamp to global capacity, +and default capacity/pending limits remain 6/256. Duration samples remain bounded to 256 and +observation buffering to 512; diagnostic backpressure must not decide admission outcomes. +Readiness identifies the Rust owner truthfully. Node/Electron ABI validation belongs to the +actual Core/vector worker that loads a native binding, not a removed admission worker. + +### Cohesive control protocol + +The private `deepchatCoreNative.control(generation, sequence, command)` method is asynchronous, +authenticated to the existing Core row and generation. Its version-1 receipts carry accepted, +stale or cancelled results, an owner revision, explicit scope/operation/Run identities and local +fences. A transport error has an unknown outcome: synchronously fence Core's local effect handles, +report the existing required-service failure and retire the generation without replay. Main and +renderer callers cannot dispatch this method. + +Rust commands perform finite state transitions only. They never await Provider work, SQL, native +presentation or a Core callback. Core serializes command submission and receipt application; a +queued resource request returns its known ticket immediately instead of holding that command +lane. Later release receipts grant queued tickets. Retain the SDK's 16-MiB internal message bound, +a 16-MiB queued-metadata bound and 1024 queued commands, with cleanup/failure sent through reserved +capacity. Large data, function references and transaction callbacks are not control arguments. + +| Operation | Required identity and transition | +| --- | --- | +| Open/retire scope | Session ID and caller-allocated incarnation; Rust owns the current-incarnation index. Retired originating records remain eligible for their own cleanup | +| Prepare/commit operation | Incarnation and a caller-allocated controller/operation ID; prepare identifies superseded local handles, Core fences them, commit revalidates the transition before granting replacement | +| Prepare/commit Run | Incarnation, operation ID, Run ID and message ID; fence the previous Run independently from a reused controller; a newer Run cannot be authorized before Core's fence acknowledgement | +| Abort/clear/finalize | Expected originating operation or Run ID; cancellation retains a registered Run until its handler settles. Cleanup cannot clear a replacement slot. An operation-clear receipt distinguishes slot removal from permission to finish originating local data cleanup | +| Publish status/readiness | Explicit operation, Run or orphan-settlement owner; a cached predicate cannot authorize a later status mutation. Terminal observation and current-Session publication remain separate decisions | +| Park/respond/resume | Message/tool/permission references, interaction lease and resume lease; Rust owns order and exclusion. Core retains resolver callbacks and persisted execution-contract data | +| Accept/wake/drain | Separate send/steer acceptance tickets, wake reason and drain lease; retain Session input capacity ten and manual/enqueue/completed wake ordering | +| Settle claim | Claimed row/source, owning operation, phase and durable outcome; Rust grants a disposition, then records rejected/applied/unknown. Rollback and release are separate acknowledged phases | + +Core allocates local effect handles before dispatch. A fence can revoke a still-pending handle; +a late receipt cannot activate it. Scope, Run and controller fences are distinct: cancelling work +does not itself erase a Run's terminal-settlement ownership. TS retains rich context and Run data +indexed by Rust-issued identities, plus read-only projections. These lookups cannot grant a Run, +acquire a lock, select a conflicting claim disposition or replace a control slot. + +Prepare/commit is needed where a replacement would otherwise become current before Core has +stopped the old handle. Core performs local revocation synchronously, acknowledges it, and starts +effects only after Rust commits the grant. Root receipts are applied before subsequent commands; +release and cancellation never wait behind Provider or database work. Initial negative cancellation +also fences pending local handles, covering user stop before a grant arrives. + +The synchronous pre-stream boundary and local Store closures cannot acquire control remotely. +Their bounded operation must already hold its Rust authorization. Manual queue consumption, +ordinary queued error rollback, thrown-after-registration cleanup and steer consumption retain +their different transitions. The remaining interaction/claim wire schemas must be fixed against +their actual drivers before their implementation is connected. + +Interaction, resume and drain exclusion use an acquired lease with a caller-allocated ID and a +typed target: message/tool pair, message, or the Session drain. Acquire is a finite non-waiting +transition; its receipt distinguishes granted, busy and parked. Release requires the exact target +and lease ID, including originating cleanup after retirement. The Core driver retains opaque +lease handles, not a second lock set. Cancellation fences effects without releasing a lease while +its handler is still running. Retired records retain only the leases needed for originating +cleanup and are collected after their final release. + +An interaction's indeterminate dispatch parks its message through its originating lease. Parking +belongs to the Session for the Core generation and survives Instance cleanup/reload. Destroying a +Session clears parking and revokes older scopes' right to park it again. A retired handler may +record its indeterminate dispatch before destruction, but cannot revive parking after destruction. +Acquire consults this Rust state directly; no TS `isParked` predicate authorizes a response. + +Attachment acceptance uses two Rust FIFO lanes per Session, send and steer. A queue command returns +granted or queued immediately. A later release receipt names the next granted scope/ticket, and +Core resolves only that pre-registered local waiter. Cancelling a queued ticket removes it without +bypassing the active predecessor. Retiring a scope cancels its waiters, while an active preparation +retains its lane until its handler releases it; a replacement scope cannot overlap that work. +Both send and steer enter their lane, eliminating the cached `hasPriorSteerAcceptance` decision. +Tickets carry explicit scope and caller-allocated IDs; their payload and attachment work remain TS. +After preparation, the driver checks that originating scope before writing input facts. It cannot +rehydrate a replacement scope to apply a ticket granted to the retired producer. + +Provider permission registration binds a callback ID to its scope, Run, message, tool and Provider +request ID. The callback remains local TS data; Rust owns the selected slot. Registration is awaited +by the streaming event loop. Resolving requires the originating interaction lease and an exact +callback identity, or Rust's confirmation that the slot is absent. A direct positive fallback also +requires the matching live Run; an orphan may only be denied. A conflicting slot is never consumed. +Taking a resolution consumes it before invoking the Provider and retains that fact until Run +cleanup, so a failed or late reply cannot authorize another direct fallback. Fresh registration +has a new callback ID. Tool IDs may be empty for Provider callbacks. + +Run replacement, clear, abort and scope retirement atomically revoke the corresponding callback +slots and return local cancellation fences. Core may deny a pending handle immediately, including +before its registration reply, but cannot grant a positive response without the Rust receipt. +After the Provider effect, the driver rechecks the original scope/Run cancellation fence before +publishing its decision. It does not rehydrate another Instance to apply a late callback. +A denial may finish the same retained Run's cancellation cleanup; it cannot publish for a +replacement Run or retired scope. + +Deferred tool execution holds a child lease of its exact interaction and originating Run or +operation. Rust grants one child for that target; duplicate acquisition is busy. The child ID +selects a local AbortController, and cleanup releases that exact originating lease without looking +up a replacement Instance. Abort, owner replacement and retirement revoke children and fence their +controllers. Core allocates the controller before acquisition and propagates parent cancellation +locally, so a late grant cannot start a cancelled tool. It keeps no independent tool-to-controller +registry that can authorize execution. + +Controller reuse does not merge Run authority. Once registered, status and originating cleanup +carry the Run ID as well as the captured operation ID. Bare operation status cannot settle a live +or previously registered Run. A subsequent grant invalidates earlier terminal-status authority; +the last cleared operation records which Run it belonged to. Pending interaction projection is +updated atomically with an accepted Rust status transition before publication. Rust also owns +interaction origin/order reconciliation and tool-batch call/invocation/commit/pending IDs. TS keeps +the execution-contract object under the batch ID selected by Rust; it cannot select a batch from +its local object map. Clearing a batch drops that object. Restored transcript references are +reconciled through the same owner-bound command before they can authorize a continuation. + +Current-observation reconciliation waits for readiness, checks the incarnation, then synchronously +reads current durable interaction facts before selecting the cached Root owner and submitting. +A known owner rejection may repeat that observation while the same incarnation remains current; +it must reread durable facts, never rebind captured entries to a replacement owner. Retries have a +10-second budget after readiness; a late accepted result remains authoritative. Scope replacement +and unknown transport outcomes still propagate. Explicit Run/operation writes retain their captured +owner and cannot retry under a replacement owner. Session-state queries preserve cold transcript +hydration so restored approvals have actual Root references before continuation admission. + +An interaction handler acquires its originating exclusion lease before transcript hydration, +then asks Rust to validate that exact lease and the first pending tuple. Positive Provider and +deferred-tool grants recheck that first tuple at their own grant boundary. Persisting a resolved +interaction precedes advancing the Root projection; a response must not expose the next approval +while the previous one is only locally resolved. + +Input selection sends a body-free Store summary: ordered queue IDs/states, the steer head, +claimed/blocking flags and question-follow-up intent. Rust combines it with its status, +interactions, drain owner and restart/manual holds to choose an action and reserve the sole +Session claim before TS writes a claimed row. Ordinary pending enqueue does not reserve a claim. +After a grant, one synchronous local closure rechecks the observed summary and scope before +performing the existing Store operation. This check may reject a grant; it cannot choose a +different input. A changed summary is acknowledged as rejected before requesting another grant. +Queue capacity counts waiting queue entries, excluding claimed rows and steer inputs. + +Each granted claim has origin-bound effect tickets for user-fact creation, rollback and its finite +durable dispositions. The driver acknowledges applied, rejected or unknown after the synchronous +Store effect and its durable verification. Publication failure after persistence is still applied; +verification failure is unknown and cannot authorize release or replay. User-fact creation and +claimed-row linking remain one SQL transaction. Release after rollback requires an acknowledged +rollback. Cancelling or retiring a scope can narrow effects immediately, while originating cleanup +retains its exact claim identity. + +Manual queue consumption obtains its fixed consume ticket before entering the loop. The +synchronous once-only pre-stream boundary uses that ticket before either rate-gate waiting or +Provider streaming, then queues its outcome acknowledgement. A never-attempted ticket must be +explicitly rejected before selecting a different disposition; a lost reply is not rejection. +Rust owns the active steer marker, restart/manual holds and deferred wake priority +`manual > enqueue > completed`. A busy drain request records a wake only after Rust has observed +the owning claim's settlement; exact drain completion returns that wake once. + +One Harness belongs to one Core generation. Startup recovery imports profile holds through the +same ordered control lane, and input entrypoints await its acknowledgement. Database reopen first +fences outstanding local input tickets and awaits their outcomes under the existing maintenance +boundary, then reconciles durable Store rows and awaits Root recovery before resuming ingress. +Recovery cannot silently erase a live unknown claim or let a late preauthorized SQL callback +modify the reopened database. + +Rutis 0.16 exposes synchronous host methods, but its session pump has no per-call deadline. Use +the asynchronous control method rather than introducing an unbounded Core-thread wait or a new +watchdog. This choice does not change the existing native SQLite transaction boundary. + +## Memory as a first-class capability + +### Ownership and first extraction + +Memory is a suitable independent capability because it already has a facade, repository ports, +execution identities, provider bindings and vector-store lifetimes. Use one Memory plugin with +internal retrieval, working-memory, extraction, reflection, persona, conflict, merge, directive and +maintenance services. These are not separate processes or independently trusted instruction lanes. +The existing [Memory service boundaries](../memory-service-boundaries/spec.md), +[Memory architecture](../memory-system.md) and [vector v2 contract](../memory-vector-store-v2/spec.md) +continue to define behavior. + +The recommended first Memory slice is the vector resource service: query/upsert/delete/list and +open/close/reset/quarantine of per-Agent stores. Its files are separate from the shared database, +and most operations are already asynchronous. Keep the current DuckDB implementation in TS first. +This is a lifecycle and isolation experiment, not a claimed query-speed optimization. Use one +additional TS runtime, started lazily by the existing Rust host when the first store is needed. +Native poison requires a new process; loading DuckDB in Core would couple that recovery to +unrelated active Runs. No runtime is created per Agent, and unused Memory starts no extra process. +The additional runtime must pass the G2 resource budgets before adoption. + +`IMemoryVectorStore` still exposes synchronous `isUsable()`, and the factory has synchronous +quarantine notification and object-returning creation. A remote service must use generation-bound +resource identities, acknowledged mutations and explicit health/readiness observations. Cached +health may reject work early; it cannot authorize a stale handle. A query/upsert validates the +Agent, embedding model/dimensions and store generation at its actual owner. Do not serialize class +instances or assume that the current interface can be proxied unchanged. + +Keep `VectorStoreManager` admission/leases and embedding identity changes coherent with resource +disposal. A local facade can retain policy while the resource owner holds native handles, but +there must be one authority for close/quarantine/reset and no reopening while an old owner lives. +Preserve v2 sidecars, legacy migration, staging/rename, WAL handling and quarantine semantics. +The current recovery contract requires a new process after poisoned native state; same-process +reopen or automatic worker replacement is not established recovery behavior. + +### Full Memory service dependencies + +The full capability needs the following contracts before its real wiring can move: + +| Boundary | Existing coupling | Required extraction rule | +| --------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Durable facts and mutations | `MemoryDatabase` uses the same main database as Sessions, Tape, settings and Providers; repository methods are synchronous | Keep a single storage authority. Introduce coarse business commands for atomic Memory writes/CAS/tombstones/lineage/audit, or move the repositories with Store. No generic SQL gateway or row-by-row remote repository facade | +| Configuration and execution | `isEnabled`, budgets, directive contribution, execution-token capture/validation and access observation include synchronous operations | Use versioned policy snapshots only for reads that tolerate them; authoritative admission and writes validate execution epochs. Invalidate stale work on Agent delete, config change, clear and shutdown; no synchronous IPC | +| Tape ingestion | `MemoryRuntimeCoordinator` owns scheduling, Tape anchors, persisted Session cursors and late-work fences | Keep scheduling with Harness. Preserve the atomic observation of Tape/projection heads and acknowledge owned extraction before advancing its cursor; do not replace one database observation with independently timed RPC reads | +| Providers | Embedding, extraction, reflection and persona use current model resolution, queues and cancellation | Use the existing Provider execution port; preserve request admission, execution identity, deadlines and cancellation. A lost reply cannot automatically replay a durable write or provider side effect | +| Recall and prompt trust | Vector/FTS candidates require authoritative scope, temporal and tombstone checks; approved directives use a distinct lane | Revalidate recalled rows at the owner before injection. Facts, persona and working memory remain untrusted content; approved directive policy must survive plugin replacement | +| Management and maintenance | Routes, memory tools, backup/rekey/clear/delete, vector rebuild and background maintenance share service lifetimes | Retarget every caller, including management UI and tools. Quiesce and fence work before destructive maintenance, drain native/provider jobs and preserve failure/rollback behavior | + +[`MemoryRuntimeCoordinator`](../../../src/main/agent/deepchat/memory/memoryRuntimeCoordinator.ts) +must remain an explicit Harness collaborator rather than disappearing inside a generic plugin +lifecycle callback. A Rutis generation cannot replace the Agent execution epoch or persisted Tape +incarnation. Plugin unload must settle or fence extraction, release resources and leave ingestion +recovery deterministic; it cannot silently skip inputs or resurrect forgotten memories. + +During staged migration, the existing database owner may serve narrowly defined transactional +commands while it remains the only writer. Its shared repositories do not move merely because +Memory becomes a plugin. The whole-core database cutover moves all remaining facades and scheduler +claim transactions together; main then loses writable shadow access. + +After that cutover, the full Memory plugin shares the Core runtime with Store, Provider and +Harness. The Rust host loads it before Core, and Core declares its service dependency. Its +owner-local attachment accepts the existing policy, repository, Provider and vector-resource +ports; it is not a serialized repository API. All prompt, tool, management and Agent consumers +receive the same facade. Existing read epochs, execution tokens and atomic Tape observations stay +local to their authorities rather than becoming cached remote snapshots. + +The plugin owns facade construction and the complete Memory lifecycle. Harness explicitly binds +its ingestion observer; pause/drain, resume and final retirement coordinate that observer with +background maintenance and vector disposal. Unload fences ingestion and the facade before awaiting +provider/native retirement. A retired attachment cannot reopen, and stale references cannot revive +its execution epoch. Core's profile release still requires the plugin's retirement receipt. +Execution tokens bind both the Memory facade instance and the Agent epoch. A replacement facade +rejects the retired instance's tokens even when their Agent epoch numbers match; ordinary policy +changes retain the existing per-Agent epoch rules. This identity is ephemeral, not a schema field. +This extraction adds no worker, data-path RPC, schema or dependency; it makes no performance claim. + +### Existing bounds and the actual optimization question + +The current implementation already bounds recall materialization to 800 candidates, keeps a vector +store soft cap of 8 with a 15-minute idle TTL and active leases, batches embedding work, and limits +background/query embedding concurrency. Do not describe adding an LRU, batching or bounded queues +as a new Rust benefit. Existing numeric-only diagnostics report repository/materialization cost, +DuckDB statements, open stores, active leases, provider work and extraction outcomes. + +[`memoryVectorStore.ts`](../../../src/main/memory/infra/memoryVectorStore.ts) executes cosine queries +in native DuckDB through `@duckdb/node-api`. Writes reuse typed native column buffers, avoiding +one temporary native scalar per embedding component. Query array binding, handle lifetime and +duplicated buffers remain candidates for profiling. +A Rust wrapper using the same engine does not by itself improve the database algorithm. A bounded +800-row score pass is also a weaker candidate than an observed large-history JSON/projection cost. +Model inference and network waits remain Provider costs in either language. + +Knowledge/RAG also imports DuckDB in Electron main through +[`knowledgeDatabase.ts`](../../../src/main/knowledge/database/knowledgeDatabase.ts). Moving Memory +vectors alone therefore does not unload the driver from main or eliminate all native crash risk +there. Do not merge Knowledge's schema/VSS migrations into Memory's v2 format; assess its independent +capability and any shared-worker costs in G9. + +Evaluate query recall/selection, tombstones, scope, temporal rules and injection budget alongside +latency. Retain FTS/degraded behavior and failure diagnostics. Use the existing Memory performance, +native and quality suites; algorithmic fixtures alone do not measure DuckDB or whole-application +memory. G2 records native and fixture characterization in the live plan; those results do not +establish a whole-application Memory improvement. + +### First vector service contract + +G3 keeps the current v2 DuckDB engine and files. The Rust host supervises the lazy runtime generation; +one first-party TS plugin owns native handles, file locks and recovery leases. Memory policy, authoritative +rows, embedding scheduling and vector leases remain in their existing owners. There is no schema +migration or fallback to a second native owner in Electron main. + +- Negotiate a versioned `memoryVector` service at the private application handshake. Admit only + open, query, query-by-ID, atomic upsert, delete-by-IDs, list-IDs, close, reset and quarantine + operations. Validate the Agent and resource identity at both the application adapter and owner; + callers cannot choose a path, SQL statement, module or service name. +- Choose resource and operation UUIDs before dispatch. Every resource binds Agent, model, dimensions + and runtime generation. Late open replies must release the known resource; dropped futures cannot + abandon a native handle. A local `isUsable()` observation may reject early but cannot authorize + a stale operation. Revocation/quarantine immediately disables the local facade, and subsequent + open/reset/disposal waits for the authoritative control acknowledgement or proven process exit. +- Hold a private DuckDB ownership database open in the vector process before startup recovery + or database/WAL/staging mutation. Its native file lock fences an earlier process that outlives its desktop host. + Persist a per-Agent lease before opening vector files. Remove it only after acknowledged clean + closure; preserve it for uncertain writes and runtime disposal. After acquiring ownership, recover + stale leases through the existing quarantine path and require explicit index rebuilding before + issuing usable grants. Keep database writes, removal and marker consumption in the vector process. + The application adapter synchronously creates append-only quarantine intents so cleanup can be + deferred durably even after worker loss, including for previously closed stores. Preserve its + synchronous marker-error contract, fence the native owner even when marker creation fails, and + never unlink or recover database files from that adapter. +- Preserve existing admission calls and their 64-KiB limit. Add a distinct vector frame kind with + a 256-KiB maximum; retain the 1-MiB transport buffer budget and reserve 64 KiB for control. Only + one bulk chunk awaits acknowledgement at a time. Control frames take precedence over the next + chunk, and a loaded-vector E2E case must prove admission is not starved. +- Encode embeddings as little-endian Float32 data, matching the native `FLOAT[]` representation. + Bulk uploads carry raw bytes from Main to Rust: the vector and binary header bits are both set, + followed by a four-byte big-endian metadata length, JSON request metadata and one bounded byte + chunk. Only begin/chunk/commit accept this form; reject empty/oversized chunks and metadata that + already contains data. Rust owns one bounded ephemeral upload and acknowledges received chunks; + only commit forwards an atomic upsert through Rutis `Value::Bytes`. Native parts are at most + 8 MiB, keeping base64 plus bounded metadata below Rutis's 16-MiB internal message limit. + Native parts run sequentially under the same logical upload identity; the native owner assembles + and validates the complete batch before one transaction. Partial transfer failures abort the + known native upload. Reserve the resource's execution order from the first native part, so + subsequent reads and mutations cannot overtake the batch. Abort/close releases an incomplete + reservation; a committing write remains owned until settlement. Keep staging owned until then. + Rutis's internal base64 encoding remains an SDK detail. Query vectors retain canonical base64. + Preserve IDs, dimensions and ordering; validate byte lengths and finite values before effects. No dense-vector JSON number arrays or complete histories + cross this gateway. Verify Float32/native cast parity, including extreme inputs. +- Validate provider response vectors with a finite-number loop and clone the numeric array once, + preserving the SDK's rejection and raw-response isolation contracts without per-component Zod + parsing objects. Keep the installed provider versions pinned and patch only their embedding + response schemas, including Azure's internal OpenAI and Vertex's Node/edge entrypoints. +- Bound one logical upload to 16 MiB of decoded data and 50 records, with one active staged upload + in Rust until the native operation settles. Use explicit begin/chunk/commit/abort operations: + validate the complete batch and invoke the existing atomic native upsert once. Chunk acknowledgement is not a durable write + acknowledgement. Oversized/invalid operations fail before effects; never truncate or split a + logical transaction silently. Inventory every actual caller against these limits before cutover. + Begin and commit can each carry one bounded chunk, avoiding empty transport round trips without + changing the atomic write acknowledgement. Bound the first application chunk to 64 KiB to leave + room for IDs; bound subsequent chunks to 240 KiB within the unchanged 256-KiB frame limit. + Retain one outstanding chunk acknowledgement. Pack complete rows with native Float32 array + conversion and copy partial rows into the current chunk; retain no second full encoded matrix. +- Reuse one typed DuckDB data chunk per store and append bounded pages inside the existing + delete/insert transaction. Close the appender before commit; invalid dimensions or a failed + append roll back the whole logical batch. Preserve Float32 values and smaller subsequent batches. + Keep received Float32 matrices packed through insertion using the pinned DuckDB bindings already + shipped with the Node API. Validate finite values, exact byte length, little-endian host format, + actual FLOAT array type and row capacity before native copies. Set parent/child validity and + honor source byte offsets; never expose an unchecked native memory-copy operation over RPC. +- Keep padded base64 validation strict in the pinned Node runtime. Decode and verify by canonical + round trip with Node's Buffer implementation. Reuse an exact, independently allocated backing + buffer; copy pooled or partial buffers so decoded bytes cannot expose unrelated data. +- Launch only the vector runtime with `--max-semi-space-size=8`. Sustained bounded uploads must + not retain a throughput-grown idle nursery. Preserve the Core runtime's default heap policy + and validate query/write latency alongside aggregate footprint; no forced GC is used. +- Bound query results to the existing recall ceiling of 800, and list/delete operations to 512 + IDs. Current callers use single-item management deletion, at most 50 embedding-orphan IDs, + 256-row prune batches and 512-row orphan reconciliation; retain atomicity for each call. + Permit at most 32 pending vector operations and two active native + jobs; serialize writes and disposal for each store. Backpressure must not occupy admission slots. + Do not retain a second full encoded upload alongside the source vectors in main. +- Keep model/dimension mismatch, migration/staging/WAL, suspect-store leases, operation deadlines + and v1 progress/abandon rules from the current implementation. The 15-second runtime handshake + is separate from a store open/migration; do not impose admission's 10-second RPC deadline on + native work. Cancellation fences the consumer while the owner retains a running native operation + until it settles or its process has demonstrably exited. +- On native failure, preserve the existing quarantine marker and FTS degradation. On vector + runtime loss, invalidate its handles and pending work without replacing admission or replaying + unknown writes. Preserve new-process recovery semantics; G3 introduces no automatic native-worker + restart. Quarantine or terminal native recovery failure terminates the vector process before + native cleanup or GC can release uncertain ownership. The Rust host reaps it before reporting + loss. Map loss to the existing quarantine contract so clear/delete can report deferred cleanup; + erased canonical memories cannot reappear after restart. Normal shutdown drains/fences Memory + work and store leases before terminating that runtime. + +These bounds define the first service contract, not measured performance. G3 must prove native and +quality parity, atomicity under interrupted uploads/lost replies, lifecycle cleanup and resource +budgets through the real application before removing the original factory wiring. + +## Rust selection and resource evidence + +### Candidate ranking + +The ranking distinguishes architectural value from a performance hypothesis. No row below is an +observed speed or memory improvement. Existing native engines stay in place unless their actual +algorithm, binding or resource policy is the measured limitation. + +| Candidate | Priority and proposed language | Possible benefit to verify | Constraint or rejection condition | +| ------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Admission and coherent Run control | High structural priority; Rust after TS core boundaries | Headless control authority, compact state/leases, explicit cancellation and fewer JS ownership objects | Queue work is small. Porting it does not remove V8 while TS adapters remain; preserve all fences and settlement semantics | +| Tape effective-view/replay and context projection | First CPU-oriented Rust candidate if profiles confirm cost | Fewer repeated JSON parses, large-history allocations/sorts and GC pauses; batch projection near data | Preserve UTF-8 binary ordering, retractions/replacements, provenance and canonical hashes. Reject full-history IPC copies or per-helper RPC that erase the gain | +| Tool result scanning/pruning and local document preprocessing | Conditional Rust batch operations | Bound large text/JSON traversal and materialization away from the control loop | Existing limits and artifact semantics remain; reuse sharp/OCR/search native work and do not reimplement it based only on language preference | +| Process trees, watchers, timers and scheduler calculation | Conditional Rust resource owner | Remove an existing Node utility host, consolidate idle runtime overhead and improve cancellation ownership | Preserve schedule claims/misfires, shell environment, OS process identities and power events. Reject an extra supervisor around the same live owner | +| Memory vector binding/resource manager | TS first; Rust only for measured allocation/engine integration cost | Reduce JS boxing/copies or eliminate a dedicated Node host once its last TS task moves | Native DuckDB memory remains. Account for IPC buffers and all handles; independent recovery requires a proven file-owner transition | +| Memory ranking, budgeting and pure normalization | Low/conditional Rust priority | Lower allocation/CPU only if a measured stage is significant under bounded workloads | Recall candidates already capped at 800. Preserve selection quality, mixed-language estimates and budget policy; reject noise-level gains | +| Shared Store/Tape persistence | Later Rust decision | Potentially remove JS storage objects/runtime overhead and make durable commands available headlessly | Encryption, migration, SQLite extensions/collation, transaction/recovery and maintenance parity are prerequisites; a new driver is not a free substitution | +| Provider SDKs, MCP/ACP adapters, Skills/hooks, extraction/reflection orchestration and remote channels | Keep TS by default | Mature protocol ecosystem and cheaper integration changes | Mostly I/O or model time. Rust is justified by a proven adapter bottleneck or removal of a runtime, not by a general rewrite objective | +| SQLite, DuckDB, native search, sharp and OCR engines | Reuse existing native implementations | Resource lifetime, batch sizes and data movement may improve without rewriting engines | Do not count engine time as JavaScript time or claim a native-to-Rust wrapper migration changes its core performance | + +Inspect the current Tape folds in +[`effectiveView.ts`](../../../src/main/tape/domain/effectiveView.ts) and +[`replay.ts`](../../../src/main/tape/domain/replay.ts), plus the context builder, as one potential +batch computation. If a remote operation needs the entire history to produce a small view, +co-locate that computation with Store or return a bounded projection from the current owner first. +Durable transaction callbacks and arbitrary synchronous repository calls are not wire protocols. + +### Rust batch selection outcome + +G8 selects no additional Rust computation boundary. Functional delivery takes priority, and the +current candidates do not establish a useful data-local batch or a measured CPU bottleneck. This +decision retains the existing consumer wiring; it makes no claim about comparative execution speed. + +- Tape projection remains beside Store. [`TapeFactService`](../../../src/main/tape/application/factService.ts) + and [`TapeRecallService`](../../../src/main/tape/application/recallService.ts) read rows and call + [`buildEffectiveTapeView`](../../../src/main/tape/domain/effectiveView.ts) synchronously. The + [`replay service`](../../../src/main/tape/application/viewReplayService.ts) consumes the same + physical envelopes, provenance and effective identities. Moving only the fold adds row/result + transfer or a new native binding while retaining the current Store and consumer. Preserve UTF-8 + binary ordering, retractions and [`canonical hashing`](../../../src/main/tape/domain/canonicalJson.ts). +- [`ToolOutputGuard`](../../../src/main/agent/deepchat/runtime/toolOutputGuard.ts) already accepts + batches. Its decision uses messages, tool definitions, output limits and the shared context + estimator, then retains filesystem artifact ownership and cancellation. A separate scanner + neither removes that owner nor establishes an independent lifetime; it would add data transfer + and another implementation of the policy. +- [`Memory retrieval`](../../../src/main/memory/services/retrievalService.ts) batches embedding + and vector requests. [`MemoryVectorStore`](../../../src/main/memory/infra/memoryVectorStore.ts) + delegates distance, ordering and limits to native DuckDB. Residual + [`retrieval policy`](../../../src/main/memory/core/scoring.ts) operates under the + [`800-candidate bound`](../../../src/main/memory/core/retrievalBudget.ts). This bound alone does + not prove low cost, but no established residual bottleneck justifies another transfer boundary. + +Do not add an experimental flag, duplicate algorithm or benchmark framework for these rejected +candidates. Reopen selection only for an observed stage bottleneck or a compatible data-owner move +that supports a bounded Rust-local operation. G9 proceeds with independently useful capability +lifetimes; it does not depend on a speculative speed rewrite. + +### Rust Store selection outcome + +G10 retains the current TS/native Store. A Rust writer is technically plausible, but the selected +functional roadmap has no independently useful storage-owner cutover that removes a runtime or +enables a missing product capability. Headless execution does not require this rewrite. No Rust +driver, storage protocol, parallel writer or benchmark prototype is added. + +The locked `better-sqlite3-multiple-ciphers` 13.0.3 already executes native SQLite 3.53.4 with +SQLite3MC 2.4.0. Its binding uses Node-API v10; the +[`runtime builder`](../../../scripts/build-harness.mjs) ships per-platform prebuilds. The binding +is not tied to Electron's V8 ABI: [Node-API guarantees ABI stability for its supported API](https://nodejs.org/api/n-api.html#implications-of-abi-stability). +This does not prove the complete headless distribution; G11 must validate that distribution. +Provider, Harness, MCP and Memory policy still require Node after a Store-only rewrite. + +[`Connection configuration`](../../../src/main/data/connectionConfig.ts) selects the SQLCipher +cipher with `legacy=4`, UTF-8 key bytes, WAL, `synchronous=NORMAL` and a bounded page cache. +[SQLite3MC documents SQLCipher 4 compatibility parameters](https://utelle.github.io/SQLite3MultipleCiphers/docs/ciphers/cipher_sqlcipher/). +A Rust binding could link the same amalgamation or use a compatible SQLCipher build; +[rusqlite supports custom SQLite linking and SQLCipher builds](https://github.com/rusqlite/rusqlite#notes-on-building-rusqlite-and-libsqlite3-sys). +Neither route proves bidirectional file/WAL compatibility. Preserve actual extensions and compile +options, FTS, Unicode search, `deepchat_sync_notify` and Memory FTS-scope functions. A plain bundled +SQLite driver is insufficient. Driver/MSRV selection and native linking need six-platform evidence +if this migration is reopened; they are engineering choices, not proof of incompatibility. + +The useful future boundary is the complete durable owner, not selected tables: + +- [`Pending inputs`](../../../src/main/session/data/pendingInputs.ts), transcript and Tape commit + queue claims, materialization, links and cursor changes atomically. Tape append also maintains + provenance and Memory ingestion projections. Memory mutations need their CAS, audit, tombstone + and FTS savepoint behavior; scheduler claims and catalog revisions share the same writer. +- The current transaction ports accept local synchronous callbacks. Remote commands must encode + whole business operations. In particular, the + [`pre-stream input pump`](../../../src/main/agent/deepchat/runtime/pendingInputPump.ts) needs an + acknowledged once-only durable consume before Provider entry; a control grant is not a commit. + Authorized [`Session reads`](../../../src/main/session/data/sessionReferences.ts) need one + consistent snapshot. Sync apply must retain its runtime/deletion guards through commit. +- [`Profile ownership`](../../../src/main/app/profileStartup.ts) must follow the physical writer. + Core death cannot release its lock while a Rust writer survives. Maintenance must fence and + drain all consumers, close the writer, perform the file operation and reopen before admission. + Backup snapshots retain their read lock through actual copying. Rekey includes database files, + metadata, validation, replacement and rollback, not only a cipher PRAGMA. + +Existing real-driver tests cover profile exclusion and process death, encrypted enable/rekey/unlock/ +disable, migration rollback, concurrent-WAL backup, partial import rollback and transcript atomicity. +They establish current behavior, not Rust interoperability or process-kill atomicity between +database replacement and metadata persistence. Those candidate checks remain unexecuted. + +Reopen selection for an observed Store-bound product failure or a concrete independently useful +durable-owner deployment/lifecycle requirement. First preserve the complete finite command boundary, +then prove cross-engine files/WAL/locks and encryption/recovery before consumer cutover. Do not infer +a performance benefit from the language change, or add sampling to justify the current roadmap. + +### Measurement and adoption gates + +Functional delivery is the execution priority. Structural migrations are gated by actual default +wiring, preserved behavior, lifecycle/recovery correctness, appropriate tests and real-application +validation. Existing resource observations are retained as reports; additional sampling and timing +variance analysis do not block these migrations. Investigate a concrete user-visible performance +regression when observed. A later module selected specifically for a performance claim still +needs direct supporting evidence; do not claim a gain from its language or framework alone. + +Establish two baselines: the application before admission extraction and committed `ffcd98770`. +Use the first to account for the total architecture cost and the second to attribute each next +module. Preserve immutable artifacts and isolated profiles, with versions and workload parameters. +For an explicit performance adoption decision, run at least five paired samples on the same +machine and report warm/cold results and baseline variance. This sample count does not apply to +ordinary correctness E2E or every structural extraction. Use a deterministic local Provider +fixture for local CPU/memory comparisons, then separately validate real-provider behavior. +Do not benchmark against a live user database. + +| Workload | Evidence required | +| ------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Idle, cold startup and shutdown | All process identities, readiness time, CPU wakeups, aggregate memory, artifact size and bounded child cleanup | +| Streaming/delegation under load | Single and concurrent Sessions, existing 6-active/2-queued delegation case, interrupt/approval/pending input, renderer input-to-paint and main event-loop p95/p99, stream latency | +| Context/Tape scale | Fixed small/large histories and payload sizes; fold/build timings, CPU, allocation/GC, materialized bytes, transfer bytes and canonical output parity | +| Memory recall and maintenance | Fixed row counts/dimensions, cold/warm native queries, FTS degradation, reindex/clear and more than 8 Agents to exercise store eviction; quality, leases and queues must converge | +| Lifecycle failures | Lost replies, queue saturation, plugin/runtime death, stale callbacks, poisoned native handles, maintenance and shutdown; no duplicate writer, replay or leaked descendants | + +Reuse [Memory metrics](../memory-quality-gates-and-observability/metrics.md) and the existing +`test/main/performance/memory` workloads before adding another telemetry framework. Separate +orchestrator time, provider wait, native query time and IPC/serialization. Measure CPU seconds and +allocation as well as wall time. Record all process-tree members, not just the main V8 heap. + +For memory, report per-process RSS with its shared-page caveat and an appropriate platform measure: +private footprint on macOS, PSS/private memory on Linux, and private committed/working-set values +on Windows. Do not sum incompatible metrics or equate V8 heap reduction with whole-app savings. +The [Electron process API](https://www.electronjs.org/docs/latest/api/process#processgetprocessmemoryinfo) +documents macOS `private` memory as more representative than resident set size; the Rust +host and non-Electron children need corresponding OS measurements. Include native buffers, mapped +databases and retained IPC queues in interpretation. + +Before implementing a performance-driven Rust slice, record a numeric target and maximum accepted +startup, memory and responsiveness regressions in the live plan from that baseline. Adopt it only +when repeated end-to-end results exceed baseline variance, preserve output/quality and meet those +budgets when making that explicit performance claim. An isolation-driven slice is accepted on +functional and lifecycle evidence; retain resource observations as reports and describe its +architectural purpose without claiming a speed improvement. + +Shared PR runners report wall-clock timing budgets without blocking on an individual overrun. +They still enforce correctness, ownership, bounded work/queues, runtime compatibility, finite +measurements and test timeouts. The explicit test-only `DEEPCHAT_MEMORY_PERF_REPORT_ONLY=1` mode +changes timing assertions only; it must not suppress a failed test or native operation. +Timing checks enforce their unchanged budgets by default when explicitly run for controlled +benchmarking; they are not a mandatory timing gate for each migration slice. Batch adjacent +internal composition changes and their relevant checks. Require matching baseline/migrated +application E2E for a complete capability cutover or affected public/lifecycle behavior, rather +than repeating it for each internal file move. Shared runners may enforce deterministic work, +payload and queue limits; do not add relative wall-clock regression gates to them. +Use existing fixed-device observations when useful. Do not schedule additional resource captures +for a structural extraction solely because process placement or IPC changes, or repeat a workload +to resolve timing noise. Keep functional E2E and correctness gates independent of performance +reports. Select a focused measurement only for an observed material regression or an explicitly +selected performance-driven module, and stop once it answers that concrete question. +Size completion waits for the bounded fixture and shutdown work; a fixture timeout is not an +additional adoption latency budget. Production handshake, retrieval and ownership deadlines remain +unchanged when a large benchmark needs more completion headroom. + +The present 64-KiB JSON gateway is admission-only. Memory batches, dense vectors, histories and +artifacts cannot be routed through it by simply raising limits. Freeze service-specific schemas, +bounded batch/stream behavior, backpressure and cancellation identities first; keep large data at +its owner and transfer bounded results or authorized artifact references. Consider a binary data +path only when measured copies justify it. Every mutation needs defined acknowledgement, +idempotency/recovery and unknown-outcome behavior, plus stale-generation rejection and shutdown. + +G7 control and G9 Hooks/native-package acceptance are complete. G8 and G10 record their selection +no-go decisions above. The next execution gate is actual headless Session Host delivery. The +live plan orders Everywhere delivery by ownership dependencies. +Headless availability does not depend on rewriting every TS adapter in Rust. + +## Product destination + +"Everywhere" has three independently verifiable meanings: + +| Dimension | Product behavior | Rutis contribution | DeepChat responsibility | +| ---------------------- | ------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------- | +| Access anywhere | Desktop, browser/mobile web, CLI, and messaging channels can access an authorized Session Host | Internal services behind the host | Client UI, versioned application API, identity, permissions, event reconnect/resync | +| Run anywhere supported | The core can run as an embedded desktop backend or an unattended personal/self-hosted service | Runtime composition, language bridges, process lifecycle | Headless host adapter, packaging, configuration, secrets, storage and supervision | +| Use enrolled devices | A Session can request a capability on a selected machine, such as workspace files or desktop automation | Explicit peer services, remote runtimes, reconnect and resource ownership | Device enrollment, capability grants, workspace identity, operation settlement and locality policy | + +An example target workflow is opening a phone browser, submitting work to a home machine or server, +using an explicitly selected laptop workspace, and following the same Session from a desktop or +messaging channel. This is a product acceptance scenario, not current functionality. + +```text +Desktop Browser / Mobile web CLI Messaging adapters + \ | | / + +------- authenticated application API + events -------+ + | + Authoritative Session Host + Rust/Rutis + TypeScript core + Sessions / Tape / State / Policies + | | + Local capabilities Approved node links + | + Workspace / Desktop / Compute nodes +``` + +Rutis's cross-machine service protocol is an internal execution connection. It is not the browser +client protocol, a mobile UI toolkit, a user-account service, a replicated database, or a mechanism +for transferring a live Run between machines. Connecting a service by name also does not establish +equivalent business behavior; DeepChat owns the service's versioned contract and acceptance checks. + +### Deployment profiles + +| Profile | Session authority and lifetime | Client and native capabilities | +| ---------------------- | -------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- | +| Desktop local | Local profile and backend; desktop application retains its existing quit policy | Desktop IPC adapter, local OS capability ports, offline operation | +| Personal/headless host | An explicitly installed service owns the profile and continues independently of attached UIs | CLI and later authenticated remote clients; desktop-only capabilities may be absent | +| Remote client | The selected Session Host owns execution and persistent state | Desktop, browser or mobile web acts as a client; local device execution requires separate enrollment | + +These are architecture/deployment profiles, not three new user-facing configuration systems. The +first implementation remains desktop local. A headless host uses an explicit Node runtime and +server-native dependency build, without installing Electron. Its credential backend and bootstrap +ports must be configured for that host; desktop safeStorage ciphertext is not portable secret +storage. Core startup must not assume a renderer, window, tray, system keychain, or display server. + +Clients attach to a Session Host; they do not create another writer for its active Session. A +headless host owns its root lifetime through its service supervisor, not an Electron window or a +phone connection. Losing all viewers does not implicitly stop that host. In desktop-local mode, +closing/quitting behavior remains the current explicit application policy. + +### Delivery horizons + +1. **Desktop separation:** prove the Rust/Electron launch path and remove business workload from + main without changing stored data or public behavior. +2. **Headless core:** run the same backend with Node and explicit host ports; attach the existing + CLI to local business operations without any Electron process or window. +3. **Multiple clients:** expose a deliberate authenticated application surface, then a browser + client usable on a phone; desktop and web can observe and submit to the same Session Host. +4. **Device capabilities:** enroll one execution device and prove one workspace-bound operation, + revoke it, and recover correctly from a connection loss during execution. +5. **Selective distributed features:** add independently placed compute or integration runtimes + only when workload locality, replacement, or recovery requires them. + +Native iOS/Android engines, browser-local Node/Rust execution, an unrestricted mesh, multi-user SaaS, +automatic host failover, and live Run migration are outside these delivery horizons. A phone web +client is the initial mobile access path; it does not promise the desktop's native capabilities. + +## Current implementation evidence + +| Boundary | Evidence | Consequence | +| -------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- | +| Desktop and business composition | [`composition.ts`](../../../src/main/app/composition.ts) constructs business services, initializes plugins/MCP/Skills, closes services, and suspends/resumes database users | Extraction must move ownership, not copy the same composition into a second process | +| Shared persistent database | [`mainProcess.ts`](../../../src/main/app/mainProcess.ts) constructs Settings, Provider, MCP, and Agent database facades from one main database | Moving Session/Tape alone leaves shared storage and synchronous configuration dependencies across the boundary | +| Configuration access | [`settingsStore.ts`](../../../src/main/config/settingsStore.ts) switches a synchronous store to a database-backed implementation | A remote proxy cannot preserve arbitrary synchronous property reads; desktop consumers need explicit snapshots and asynchronous mutations | +| Desktop dependencies | Provider auth, MCP Apps, Skills, ACP, and Remote code import Electron APIs | These calls require narrow desktop ports before their business owners can move | +| Public routing | [`routeRegistry.ts`](../../../src/main/routes/routeRegistry.ts) has caller identity and output projection; shared contracts define route schemas | Preserve the existing renderer/CLI surface while adapting backend-owned handlers | +| Agent ownership | [`Harness boundaries`](../deepchat-agent-harness-boundaries/spec.md) define instance/run/operation/message fences and synchronous queue settlement | Framework generations do not replace these business identities or ordering requirements | +| Existing child processes | Scheduler, Code Mode, background execution, file watching, OCR, MCP, and ACP already use processes; Skill discovery also uses workers | Reuse their launch, cancellation, and recovery contracts; avoid duplicate supervisors | +| Scheduler writes | [`repository.ts`](../../../src/main/scheduler/repository.ts) owns atomic due-job claims; the utility host receives bounded scan replies without a database connection | Move this repository with the shared Store; keep the timer helper outside the writer authority | +| Runtime deployment | [`Managed Toolchains`](../managed-toolchains/spec.md) makes official Node optional; [`build-cli.mjs`](../../../scripts/build-cli.mjs) already uses Electron's Node mode | The core backend must start offline without downloading or selecting a user Node installation | + +The existing [`renderer API core`](../../../src/renderer/api/core.ts) resolves `window.deepchat`, +and preload implements its invocation/event transport. That seam is useful for a web adapter, but +it is not proof that the current renderer is browser-portable. Audit client-level capability and +Electron assumptions before selecting reusable UI code. This RFC introduces no renderer changes. + +The existing [`device sync contract`](../../features/cloudflare-tunnel-sync/spec.md) exchanges +portable durable units with LWW conflict resolution. It excludes active executions, approvals, +scheduler state, machine-local configuration, and arbitrary referenced files. Its pairing and +bounded transfer patterns are useful evidence, but sync access grants no Session control or device +execution authority. The current sync endpoint is not a remote application gateway. + +### Local feasibility observations + +Two ephemeral probes loaded `better-sqlite3-multiple-ciphers`, `@duckdb/node-api`, and `sharp` from the +current dependency tree. SQLite also completed `SELECT 1` against an in-memory database. The probes +did not open a user profile, install dependencies, or rebuild native modules. + +| Probe | Actual runtime | Result | +| -------------------------------------- | --------------------------------------------------------------- | ------------------------ | +| Shell Node | Node `24.21.0`, module ABI `137`, N-API `10` | All three modules loaded | +| Electron with `ELECTRON_RUN_AS_NODE=1` | Electron `43.7.0`, Node `24.21.0`, module ABI `148`, N-API `10` | All three modules loaded | + +These results establish a local runtime option, not packaged compatibility or full database/vector +operation. The checkout declares Node `>=24.22.1 <25` and Electron `43.8.3`; the installed versions +used by the probes differ. Verification must be repeated against the declared release runtime and +each packaged target. The admission integration check separately passed the real Rutis-to-Electron +handshake on Electron `43.8.3` / Node `24.22.0` / module ABI `148` on macOS arm64. That check does not +load SQLite, DuckDB, or image dependencies and establishes no packaged native-module compatibility. + +## Desktop-local process topology + +This is the target core topology. At `ffcd98770`, only admission uses the Rust host and TS runtime; +the remaining business composition and shared database still run in Electron main. + +```text +Renderer processes + Existing UI, typed clients, rendering + | +Electron main + Window lifecycle, OS APIs, renderer authorization, unlock UI, crypto + Renderer/CLI ingress, desktop configuration snapshots, backend availability + | private application gateway +DeepChat runtime host (Rust + Rutis) + Runtime ownership, dependency readiness, generation tracking, service routing + | + +-- Core backend (packaged Electron executable in Node mode) + | Shared database + configuration authority + | Agent + Provider + Session + Tape + Memory + | Business routes, approval lifecycle, plugin contributions + | + +-- Optional first-party feature runtimes, when justified + | For example, a separately restartable Remote channel group + | + +-- Existing tools/helpers through their existing lifecycle owners +``` + +In the desktop-local profile, Electron main starts and stops the Rust host. Rutis owns the core +backend process and its plugin +generation. Only one owner may restart a particular child. Existing utility processes may remain +launched by Electron main through a typed helper port when they require Electron's utility API; +their business leases belong to the requesting backend generation. The Rust host does not call +`utilityProcess.fork` directly. + +The initial migration adds one Rust host and one core backend process, excluding existing helpers +and renderers. Optional Node/Python/Go runtimes are not started at boot. No per-Session, per-Run, or +per-Service operating-system process is introduced. + +## Runtime integration + +### Host and launcher + +Use a small DeepChat Rust executable linking the pinned `rutis`, `rutis-bridge`, and, where needed, +`rutis-loader` crates. Reuse their runtime/session implementation. Do not implement another plugin +kernel, copy Rutis internals, or treat the upstream `rutis-host` CLI as an Electron integration API. + +For desktop-local deployment, the upstream public `Launcher::new`, `arg`, `env`, `cwd`, and +`inherit_fd` builders, together with +`LocalRuntime::launcher`, allow selecting the packaged Electron executable and the unmodified +Rutis Node runner. Set `ELECTRON_RUN_AS_NODE=1` for that child. Compile first-party TypeScript into +JavaScript before packaging; the core does not require a development TypeScript loader at startup. + +Use absolute verified paths for the executable, runner, package anchor, and backend entry. Keep +the external runtime dependency closure in an unpacked, application-owned directory readable by +the Rust host. Preserve the installed app's `runAsNode` capability, signing, and native-module +resolution. Never fall back to PATH Node, enable a managed download, or bundle another official +Node solely to make the desktop-local core start. A headless distribution has its own explicit Node +runtime/package and native-module validation; Electron's Node mode is not the core's API contract. + +Construct a deliberate launch environment for the host and core. Remove inherited debugger/import +overrides and validate required proxy/certificate settings through existing host policy. Do not +copy application secrets or user-plugin environment declarations into the core launcher. + +The admission integration demonstrates the actual launcher and runtime protocol. Each later +capability must also work through that application path; successful direct Electron execution +alone is insufficient. Unsupported native or packaged targets remain feasibility gates before +moving production data or selecting a different host. + +### Transport + +Use inherited stdin/stdout pipes for the application gateway between Electron main and the Rust +host, with a length-bounded JSON framing layer. Keep protocol output separate from diagnostic +output. Reuse Rutis for host-to-runtime traffic rather than exposing that protocol to renderers. + +Rutis `v0.16.0` supports inherited sockets or private Unix sockets on POSIX. Its local runtime +transport on Windows uses loopback TCP with a per-launch token. This internal transport is +distinct from the existing external CLI endpoint, which retains its UDS/named-pipe-only contract. +The Windows spike must verify loopback binding, token rejection, token disposal, and parent/child +termination. The desktop-local milestone does not advertise a public address or enable remote +node access. Later remote deployments require the explicit application/device boundaries below; +they do not expose the private desktop gateway unchanged. + +Application requests use asynchronous service calls. Synchronous cross-process business calls and +cycles through Electron main are excluded. In particular, main must not wait synchronously for a +backend operation that needs a desktop capability callback. + +## Ownership and interfaces + +| Owner | Responsibilities | Cross-process interface | +| -------------------- | ------------------------------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------- | +| Electron main | Windows/tabs, native shell/dialog/browser operations, safeStorage, unlock prompts, app lifecycle, trusted caller creation | Narrow desktop operations, targeted UI events, crypto operations, configuration snapshots | +| Rust host | Runtime lifetimes, readiness, routing to approved services, generation invalidation, bounded shutdown | Application gateway and upstream Rutis runtime/session protocol | +| Core backend | Persistent domain state, business configuration, Agent/Provider/Session/Tape, Memory, queue and approval decisions, plugin ownership | Audited domain commands/queries, cancellation, ordered domain events | +| Renderer | Existing rendering and user interaction | Existing typed routes/events through preload and main authorization | +| CLI ingress | Existing endpoint authentication, principal/scopes, transport quotas | Existing approved domain routes; agent identity and quotas survive forwarding | +| Feature/helper owner | Connections, listeners, tasks, and processes created for its generation | The smallest service and cancellation contract required by its consumers | + +### Database and configuration + +The core backend is the normal writer for the existing shared application database. Preserve its +schema, encryption, transaction boundaries, Tape incarnations, projection ownership, and file +location. Do not introduce a data migration solely to support the process split. + +All main-process database facades move with their owning business services. Database opening, +schema repair/migrations, import/export, rekey, backup coordination, and reopen logic execute in +the backend. Electron main supplies unlock interaction and platform crypto through asynchronous +ports. It does not retain a writable shadow connection after cutover. + +Single-writer authority does not mean one connection for every file and operation. The Store +owner may open maintenance and snapshot connections internally; separate derived-cache/vector +files retain their own owners. A connection used only for reading is still writable-capable when +opened without a read-only option. Do not use that intent as evidence that Main can retain it. + +The current connection inventory is: + +| Source under `src/` | Connection and file role | G5 ownership requirement | +| ------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `main/data/databaseConnection.ts`, `main/data/mainDatabase.ts` | Normal shared SQLite writer, reopen and the common writable-capable connection factory; file-repair helper also opens through that factory | Move factory, normal writer, schema and repair with the Store owner; Main receives no connection or password-bearing reopen callback | +| `main/app/mainProcess.ts`, `main/app/composition.ts` and domain `data/database.ts` facades | Settings, Provider, MCP and Agent attach to the same startup database; Session, Memory, Project, delegation, Scheduler and app facades reuse it | Move these facades and synchronous repositories together; constructing a new facade must not open a second shared writer | +| `main/app/databaseSecurity.ts` | Password validation, source copy, encrypted staged file and target verification open writable-capable handles | Execute inside the Store maintenance owner; native wrapping and unlock presentation remain explicit Main capabilities | +| `main/sync/dataImporter.ts`, `main/sync/configImportService.ts`, `main/sync/index.ts` | Incremental import opens source and target paths; configuration import opens the target; overwrite replaces the closed active file; backup cleanup mutates a private copied file | Keep target opening/replacement and all temporary connections in the exclusive maintenance operation; do not leave Main with a path-based target opener | +| `main/data/backupReadLock.ts`, `main/sync/index.ts` | Backup snapshots pin the active file through a connection opened by the writable-capable factory; the active owner drains/checkpoints WAL; fallback ships the matching WAL | Keep snapshot lifetime, checkpoint and file copy with the Store owner; a backup receipt cannot release the actual copy lifetime | +| `main/provider/providerImportService.ts`, `main/app/startupMigrations/legacyChatImportService.ts` | Provider and legacy chat source files are explicitly read-only; legacy import writes through the shared Session facade | Preserve read-only sources; run shared-target mutations at the Store owner, including bootstrap import before ordinary admission | +| `main/desktop/browser/BrowserProfileImportService.ts` | Read-only browser cookie source and staged snapshot; writes a private backup snapshot, not the application database | Keep native browser/keychain consent and temporary-file cleanup in the desktop owner; never treat this as a shared-writer exemption | +| `main/data/schemaCatalog.ts` | Temporary `:memory:` SQLite schema inspection | Keep internal to schema/repair work; no profile writer is created | +| `main/ocr/ocrArtifactStore.ts`, `main/ocr/ocrRuntimeService.ts` | Separate encrypted `ocr/ocr-cache.db`, optional memory fallback and native cache-key provider | Retain the OCR cache owner and explicit crypto/resource lifetime; this file is not the shared application writer | +| `harness/memory-vector.ts`, `main/memory/infra/memoryVectorStore.ts` | G3 worker owns the vector-directory lock database and per-Agent writable DuckDB files | Preserve worker ownership, leases, quarantine and generation fences; Main must not reopen those files | +| `main/memory/infra/legacyV1Reader.ts` | Temporary in-memory DuckDB attaches the legacy vector file with `READ_ONLY` | Keep migration source reads under the existing vector owner and abandonment fence | +| `main/knowledge/database/knowledgeDatabase.ts`, `main/knowledge/index.ts` | Knowledge has independent writable DuckDB files plus temporary extension inspection | Preserve its existing file and initialization owner; G3 did not migrate Knowledge and G5 cannot claim otherwise | + +OCR runtime composition requires its asynchronous cache-key provider. Main supplies the existing +safeStorage provider for the fixed `ocr/cache-key.json` path; the runtime does not construct or +import a native provider. Keep that wrapped-key file under one native owner and the independent +encrypted OCR database under its resource owner. Preserve unavailable-key memory fallback, +unreadable-key recovery, helper lifetime and cache clear/close behavior. G5 transports this private +key operation only to the approved core generation; key material never enters public status, +logs, loader configuration or command arguments. + +The profile handoff also covers file writers and synchronous configuration readers that do not +open a database connection: + +| File or state family | Authority and G5 handoff | +| -------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `app-settings.json`, `mcp-settings.json`, `acp_agents.json` and legacy Provider model/config files | Move the complete configuration authority and its attached adapters together. Main's existing bootstrap/migration stores must stop writing before backend admission; preserve legacy snapshots and formats without a second whole-file writer. | +| `plugin-settings.json`, installed plugin configuration and `plugin-tool-policies.json` | Keep installation/consent, activation, Hooks/Tape and synchronous tool-policy decisions with their one business owner. Main receives native presentation commands only. | +| `database-security.json`, wrapped Sync/Provider secrets and Codex/Grok/MCP OAuth credential files | Persistence, generation checks and queues follow their existing owners. Main supplies private crypto/unlock/browser effects, never writable stores or general secret snapshots. | +| OCR wrapped key, OCR derived database, Memory vector files and Knowledge files | Keep the native wrapped-key owner distinct from each encrypted/cache/vector resource owner. Transfer their actual lifetimes and maintenance fences; the shared-database handoff cannot imply these files have drained. | +| Provider/ACP catalogs, Skills metadata, toolchains, file/Remote/OCR caches and artifact spool | Preserve their explicit roots, existing cache writers and helper/resource lifetimes. Move business caches with their consumers; device file/image/browser operations retain narrow host capabilities. | +| Native windows, browser profiles, downloads, updater, logging and OS state | Main/device owners retain native effects and renderer identity checks. Business configuration reads use committed projections or async queries; native state is not a shared Store replica. | + +The current DeviceService reset implementation can remove shared files after the exclusive +shutdown-action claim. G5 must bind that claim to actual backend and independent-resource teardown +before deletion or relaunch; an RPC timeout or lost reply is not proof that writers have stopped. +Profile startup belongs to Core: construct settings, encryption metadata, the shared database and +its attached adapters in one owner, with schema/repair and legacy migration before normal admission. +Supply absolute profile/home roots, the application version and private crypto/presentation ports. +Logging and unlock/recovery/quit effects acknowledge their native completion. Failed startup closes +its acquired database before releasing the operation. Main retains no writable startup shadow when +the backend opens its files. + +Keep public route execution independent of Electron IPC registration. Main authenticates the +current renderer document before invoking the business dispatcher and derives caller identity from +the native sender; input fields cannot replace that context. Only plain route input/output and +caller values cross this boundary. Native window-role checks are awaited queries, not synchronous +window objects or cached permission grants. After an awaited startup classification, recheck +database admission before dispatch so maintenance or shutdown can retire pending work. Debug app +routes require explicit packaged-build identity in addition to the development-build guard. + +Compose Harness/ACP execution, Session projection/turn/assignment/deletion, CLI Runs and delegation +feedback as one owner-local graph. Interaction continuation and task-contract callbacks must resolve +the current delegation lifetime after maintenance. Keep Session/Tape transactions and live table +resolution inside this graph; database reopen must not leave captured table or service instances. +Native renderer lifetime/browser effects are supplied ports, not Electron objects in the factory. + +Remote session opening uses two Native window operations around owner-local activation. Main +selects or creates a chat window and returns its window/renderer identities; the Core runtime +activates the session and then asks Main to validate and present that same target. Main receives +no session identifier and does not call back into Session business state. Shutdown fences both +continuations. Renderer destruction removes the Core binding through the existing Native lifetime +subscription; registration that observes an already destroyed renderer cannot publish activation. +A late missing-session query cannot clear a newer binding. + +The Core settings route map owns language, theme, shortcut, floating-widget and update-channel +configuration. Native desktop and upgrade route maps retain no settings writer or Session binding. +Browser focus and Computer Use preview admission resolve active/draft ownership in the Core +Session route map. Three fixed Native presentation methods receive the authenticated renderer +context, recheck the actual current window/renderer pair and return validated results. Tab teardown +does not mutate Core bindings; the existing renderer-destruction subscription owns that lifetime. + +The floating widget reads a typed Session snapshot and opens sessions through the Core dispatcher; +the existing ACP registry icon query uses the same boundary. Core owns regular-session projection, +ordering and activation. Main owns native geometry, appearance and expanded state, authenticates +the exact floating window's top-level document and exposes only its scoped preload API. Snapshot +reads coalesce within that window lifetime; retired replies cannot update or show a replacement +window. Native initialization acknowledges window creation and appearance without awaiting a Core +query, so Core startup cannot deadlock. Display waits for the initial refresh. + +Construct the business data facades from the already initialized profile connection. Session/Tape, +Memory tables, Agent/Project/scheduler data and delegation contracts share that connection; moving +their composition must not introduce another database opener. Migration adapters keep live table +getters across maintenance reopen. Renderer polling/delivery remain supplied lifetime/event ports. + +Acquire the profile's `.core-owner.lock` SQLite exclusive transaction before constructing settings +or starting crypto/recovery. This independent, data-free handle remains owned while the primary +database closes/reopens for maintenance. Do not infer release from a pending reply, a PID file or +a timeout, and do not unlink a live lock. Process exit releases the OS lock; a later initializer +must acquire it before touching profile state. The existing named backup/import files exclude it. +Retire credential/security operations, drain accepted work and close the primary database before +releasing ownership. Primary-close failure retains the lease and rejects the terminal action. + +The boundary follows existing business operations, rather than these connection wrappers: + +| Existing owner and source | Work that stays indivisible inside the backend | Data crossing the boundary | +| ------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `SessionTranscript`, `SessionSettingsStore`, `SessionTape` and `main/session/data/index.ts` | Message facts, transcript projection/head changes, retry truncation and clear/reset; existing Tape bootstrap reads can also write | Existing bounded Session/Tape commands and projections with session/incarnation identity; no remote projection-head read followed by a separate mutation | +| `SessionPendingInputs`, `SessionPendingInputStore` and runtime pending-input coordinators | Claim/consume/release receipts, accepted user facts and steer-message settlement under the existing transaction/runtime authority | Input/claim identities and acknowledged outcomes; `runInTransaction` closures remain local | +| `DeepChatMemoryIngestionProjectionTable` and Memory tables | `readCurrentRange` observes Tape and projection heads in one SQL statement; claim/tombstone checks, revisions and ingestion progress retain their existing owner-side consistency | Bounded current-range observations and existing Memory operations, with stale/current results; never split head checks from their rows | +| `AgentRepository`, `DeepChatAgentRepository` | Agent deletion checks protected/session state, retires Memory claims/directives, clears audit and deletes the Agent in one transaction | Agent identity and deletion receipt; namespace-retirement callbacks remain in the same owner | +| `LiveDelegationRepository` and its service/runtime | Capacity observation, contract identity, turn/event writes, effect intent and terminal settlement/CAS | Existing authorized delegation commands and correlated outcomes; mutation callbacks and capacity reads do not become independent RPCs | +| `CronJobsRepository` | Due-run insertion and schedule advancement, run claims and job/run/delivery deletion | Existing bounded Scheduler commands and snapshot; the 100-job scan and owner-side committed dispatch remain unchanged | +| `ProjectService` | Environment removal clears Session project assignments and updates project/preferences/projection together; directory selection commits project activation after native selection | Selected path and acknowledged versioned Project snapshot; native dialogs/open-path operations remain desktop capabilities | +| `SyncReplicaStore` and composition guards | Authorization/session-deletion guard, conflict checks, trigger suppression, imported rows, tombstones and peer cursor advancement | Validated bounded sync batch and result; authenticated transport establishes authority, not a caller-supplied callback | +| Settings, Provider, MCP and ACP database adapters | Configuration identity/CAS and acknowledged mutations remain with their business owners | Explicit public snapshots and mutations; no raw Store, table, row getter or database handle | + +Project bootstrap receives absolute system/profile directories and nullable Documents/Home paths +from Main. Capture these roots before serving snapshots; do not await native directory discovery +between synchronous settings and database reads. Directory selection and open-path operations use +required asynchronous host capabilities. Main supplies the existing DeviceService and shell adapter, +including native lookup failure diagnostics; Project retains candidate ordering, managed-directory +fallback, persisted revisions and its original transaction boundary. No native constructor fallback, +second settings writer or row-level transport is introduced. G5 moves this owner with its shared +database/settings authority and transports only native selection/open commands and plain results. + +Hook configuration, subscriptions, event enrichment and command ownership move together. Supply +the application version at composition instead of importing Electron for payload metadata. A +command's bounded diagnostic result is distinct from native child/stdio closure: timeout or an +error report cannot release that lifetime. Stop rejects new commands, kills existing children and +waits for both accepted enrichment and actual child closure. A 30-second drain failure rejects +maintenance/shutdown before Store teardown, and restart rejects while prior work remains owned. +Main must await this critical stop directly. Preserve command timeout results, redaction, event +eligibility/order, cancellation before enrichment completes and no automatic replay of uncertain +effects. Retain Node command execution with the cohesive owner; it needs no Electron launch adapter. + +Database readers also move with these owners. `main/session/data/sessionReferences.ts`, +`main/session/sessionHistorySearch.ts` and +`main/mcp/inMemoryServers/conversationSearchServer.ts` query the inherited shared connection; +they do not justify a second desktop read facade with schema/migration access. Keep reference +authorization and bounded search contracts at their existing operation boundary. Tape inspector +subscriptions currently combine backend head reads with renderer lifecycle hooks in Main's +composition: split the committed head data from native subscription ownership, preserving initial +snapshot admission, incarnation identity, revisions and renderer destruction cleanup. The debug +mock-session route also writes through the shared connection and must execute at the writer when +enabled. Table getters that initialize or repair schema remain backend work even on a read path. + +This inventory identifies connections and atomic operation groups; it does not establish that +the corresponding owners have moved. Keep the actual Store cutover pending until remaining +configuration/desktop consumers are classified and all Main writable paths are removed. + +Move scheduler claim/update transactions into backend-owned batch operations. Its utility process +may retain timers and schedule calculation, but receives snapshots and requests durable claims +from the backend. Preserve existing misfire and duplicate-run behavior. Inventory all other +connections before cutover; read-only imports and vector stores have separate explicit owners. + +G4 first removes the scheduler utility process's writable shared-database connection. Its timer +requests a bounded durable operation from the current Store authority; the operation observes +schedule state, reconciles due work and commits run insertion and schedule advancement together. +Keep the existing 100-job scan limit, misfire rules, unique scheduled-run identity and executor +claim semantics. The committed run notification originates at the writer, rather than depending +on a later timer-worker reply. Bind requests and snapshots to the worker generation, allow one +scan in flight and reject stale work before effects. The timer process receives neither database +credentials nor SQL. Main remains the Store authority during G4; G5 moves that authority with +the cohesive business services and removes main's writable access. + +System resume is a data-only Scheduler input. Electron main owns the native event subscription +and removes it on shutdown; it suppresses delivery during maintenance and shutdown. The Scheduler +business owner imports no Electron power API, ignores notifications while stopped, and fences +resume reconciliation by its start/stop generation before durable updates or timer dispatch. +Native subscription objects and asynchronous native-loading fallbacks stay outside this owner. + +Scheduler construction requires its local manager factory; the manager requires an asynchronous, +cancelable launch capability. Main alone selects the approved timer-helper entry, arguments and +environment. The returned facade permits only Scheduler commands, data events, PID observation +and termination; it exposes no Electron process object or generic native-launch options. Stop +aborts pending startup, while manager epochs reject late completion from a non-cooperative host. +Native startup errors settle once and release their child/listeners. Recheck ownership before +post-launch reconciliation. Factories and callback subscriptions are local composition inputs, +not serialized wire payloads; G5 supplies the capability through its generation-bound adapter. + +Session/Tape/Memory Store contracts expose the existing atomic business operations and bounded +projections. Keep internal synchronous repositories, SQL and transaction callbacks local to that +authority. These are not wire operations. In particular, Tape/projection-head observations, +pending-input settlement and Memory cursor/CAS operations cannot become independently timed +remote reads and writes. Maintenance commands own their active/source/staged files explicitly +and fence consumers before changing the active writer or encryption state. + +The Session data owner supplies one `clearTranscript` command that deletes pending inputs and +transcript projections and resets Tape in the same transaction. Runtime preparation precedes +that command; runtime cleanup follows its successful commit. Retry truncation retains the atomic +transaction in `SessionTranscript.deleteFromOrderSeq` and invalidates runtime projections only +after it commits. The main composition passes these business operations; transaction callbacks +remain private to the data owner. + +Desktop consumers receive a versioned snapshot of the configuration fields they need. Mutations +are acknowledged by the backend before snapshots/events advance. Keep synchronous reads local to +that snapshot; do not turn every `SettingsStore.get` into RPC. Bootstrap-only legacy settings and +encryption metadata retain a documented file owner. Ownership applies to the physical file as +well as its keys: two processes writing different keys through whole-file serialization can still +overwrite each other's updates. Atomic rename does not provide inter-process read/modify/write +serialization. + +`AppSettingsDbBackedStore` routes sensitive/portable keys to SQLite and other keys to the existing +legacy `app-settings` file; its `store` getter merges both. G5's profile owner controls that file +along with the database-backed configuration authority. Main may perform the existing default/ +migration bootstrap only before an exclusive handoff; it stops file writes before the backend +accepts ownership. During normal operation Main receives allowlisted public snapshots and awaits +acknowledged mutations. It must not keep a second ElectronStore writer, including for unrelated +UI keys. Preserve file format, defaults, migrations and machine-local backup exclusions. Backend +business readers keep owner-local synchronous reads; native UI reads use acknowledged snapshots. + +The private native-consumer projection includes resolved language, notification enablement, +close-to-quit, content protection, shortcut mappings, floating enablement/bounds and the logging +flag needed by Main diagnostics. Native system-theme observation stays in Main. Keep the public +settings projection separate from these internal consumers. Derive committed values at the +configuration owner, fence snapshots by owner generation/revision and install the initial snapshot +before dependent native initialization. Existing events alone are insufficient: floating bounds +currently persist without an event, and shortcut changes use their own event. Every affected +mutation must acknowledge its committed value. Convert floating-bound persistence and its error/ +shutdown handling together; replacing a synchronous setter with an ignored Promise loses failures +and pending writes. Updater channel defaulting can write configuration, so normalize it at the +configuration owner from an explicit version fact rather than inside a Main snapshot getter. + +Construct the application settings file through the same pinned Conf engine used by ElectronStore, +with required absolute profile/home directories and an explicit application version. Main supplies +those bootstrap facts and uses that factory in the actual startup path. Retain `app-settings.json`, +defaults, dot-path semantics, default migration behavior and atomic writes; no replacement JSON +writer or second cache is needed. Declaring the already resolved engine directly makes backend +construction independent of Electron. The database-backed adapter still owns sensitive/portable +keys after attachment. This factory alone does not transfer ownership: G5 must close Main's writable +access and hand the existing file/database authority to the backend before it begins normal work. + +Configuration consumers include Agent defaults/prompt/trace/migration state, Provider-specific +options, Session usage backfill, Project preferences, Skill/Knowledge/Hook/Remote settings, Sync, +Nowledge/export, onboarding and file/Tool limits. Some getters normalize or migrate and write +state, and Nowledge cleanup enumerates the merged store; those operations remain owner-local. +Desktop theme/fonts/window/login, proxy application, updater and renderer logging retain their +native effects in Main. The selected AppSettingsService/public-route snapshot remains a public +projection, not a transport for the merged store or credential-bearing Remote/Hook/Nowledge data. +Desktop appearance and login commands share the settings owner's bounded serial queue. Native +theme/login observations and language/theme refreshes return awaited plain data or completion; +the host receives the resolved locale/color rather than calling back into a pending core command. +Resolve system language from the explicit process bootstrap locale. Query login state from the +OS only when requested, and publish a login mutation's observed OS result rather than a stored +preference. Preserve display receipts' previous value inside the same command ordering boundary. +Main owns the system-theme listener; its coalesced observations use that queue and are fenced +during maintenance. Stop admission and drain actual host work before closing configuration +storage; an expired drain cannot authorize teardown or a second owner. Resume observations after +maintenance and remove the native listener at shutdown. These contracts prepare the G5 handoff; +they do not establish a second writable desktop configuration cache. + +Font enumeration and logging-folder access are awaited native capabilities. Keep font family +normalization and selection persistence with configuration; preserve the current normalized catalog +cache and empty-catalog degradation. Font queries and folder requests enter the same bounded +settings owner as mutations, so shutdown drains actual native work rather than an abandoned reply. +Main owns the existing font-list implementation and private profile/logs path; no renderer-supplied +path or replacement platform enumerator is needed. Propagate a native folder-open error instead of +acknowledging an unsuccessful launch. Content-protection and logging changes await their native +effects before publishing success. Main coalesces logging and content-protection changes into one delayed restart request, +pauses its timer during maintenance without losing the intent, resumes after successful recovery, +and cancels it on shutdown. Once fired, restart authority belongs to the existing Main shutdown +coordinator; draining configuration must not wait on its own restart teardown. + +Storage business owners receive explicit runtime directories rather than importing Electron to +discover them. SyncSettings requires the absolute home directory for its unchanged default folder. +SyncService requires absolute profile and temporary directories for configuration/database files, +archive extraction, staged backup copies and validation. It awaits Main's folder-open capability +and propagates both native rejection and a returned error before acknowledging success. Main keeps +the native shell effect; no database connection or file writer is added there. Backup snapshots, +configuration import, rollback and active/source/staged connection lifetimes remain with the +existing maintenance owner and move with the shared writer at G5. + +LegacyChatImportService requires its absolute source path. Main supplies the existing profile +chat.db path; the importer retains read-only source access and writes through the existing shared +target facades. KnowledgeService receives its absolute DuckDB extension directory from Main and +passes it to every database create/open path. Main performs the existing ASAR-unpacked resource +resolution. Preserve VSS loading/fallback, schema/migrations, task recovery and independent Knowledge +files; these files are not Memory's G3 vector stores. These boundaries add no alternate database +engine, implicit native fallback, separate supervisor or second profile owner. + +Plugin installation, consent, user-plugin activation, context Hooks and MCP supervision stay with +their business owners in the cohesive core. Hook history and execution share Tape authority; +registration and tool authorization share the same supervisor. Their synchronous internal reads, +update leases and integrity callbacks are local operations, not candidate row-level RPCs. +The plugin owner uses the existing Conf format, defaults and independent `plugin-settings.json` +file with explicit profile/home/application/resource/working directories and application version. It is +the only writer of installation and launch-safety metadata; do not merge it with tool policies or +leave a writable Main shadow at G5 handoff. Supply the resolved command environment locally. + +Main owns plugin settings windows and shell presentation through required asynchronous capabilities. +The core awaits native effects; closing a settings window acknowledges its actual closure, and +the renderer-to-plugin ownership lookup remains private to Main. Preserve package validation, +integrity/quarantine, user consent, credential scoping, rollback and uncertain-hook recovery. +Maintenance and shutdown reject new context-hook work, abort and drain accepted work before Tape +teardown; resumption cannot overtake that drain. This preparation does not establish G5 process +ownership or grant third-party Rutis access. + +Plugin tool policies have one explicit business owner with a required local persistence adapter. +Main constructs the existing `plugin-tool-policies` file after the effective profile is selected; +PluginService, MCP ToolManager/AppHost and ToolService use that same owner. Preserve the existing +policy array and owner/server replacement, owner-wide removal, allow/ask/deny, undeclared-tool +closure and ambiguous/disabled-owner semantics. Reads expose decisions or denied tool names, not +the backing Store. No module-level native construction or implicit global lookup remains. +Keep current synchronous policy decisions and mutations local to their shared authority; G5 moves +the policy state with its execution consumers and supplies acknowledged commands to any retained +desktop writer. This extraction does not substitute a cached policy shadow or synchronous IPC. + +Split crypto from persistence: the backend stores wrapped credentials; Electron main performs +authorized wrapping/unwrapping. Send unlock material only through the private transient channel +to the approved core generation. It must not enter command arguments, environment variables, +persisted loader configuration, logs, or crash diagnostics. + +Credential crypto uses an async desktop port with string/boolean results; Electron objects and +native buffers remain in its main-process adapter. The pinned runtime's native async safeStorage +operations must open existing synchronous ciphertext. Retain opaque wrapped-value storage and +keep reads free of credential rewrites. Lifecycle generations fence in-flight crypto results +across maintenance and shutdown, with authoritative checks immediately before sensitive return +or persistence. Await every Sync, MCP, plugin and export consumer; preserve destination binding, +serialized mutations, rollback and cancelled connection-attempt identity. + +MCP OAuth receives its private credential path, secure async crypto and browser-open capability +from Main. Interactive and enterprise identity flows share one credential owner. Preserve v1/v2 +envelopes, bounded records/payloads, Linux basic-text refusal and memory-only behavior. Serialize +acknowledged writes and binding-key moves; publish the existing decrypted record cache only after +persistence succeeds. Initial decrypt is single-flight, and reads do not rewrite v2 envelopes. +Reserve eight of 32 pending mutations for control commands; ordinary writes use at most 24. +Stop bypasses the queue and fences pending native results. G5 must frame legal large envelopes +within bounded transfers rather than raising the gateway limit or truncating payloads. + +Keep loopback callback validation synchronous: expose only the issuer and issuer-parameter policy +from acknowledged public discovery metadata. Never use an asynchronous credential read as a void +callback validator. Finalizing discovered server identity is an owner-local compare-and-set command +against server ID, generation, binding hash and endpoint; preserve concurrent unrelated settings. +Profile updates re-read current metadata after credential cleanup and reject replacement of the same +profile. Runtime stop/restart effects carry the originating server/profile lease through awaits; +logout, replacement and shutdown invalidate old SDK providers and delayed callbacks. Preserve +settings-window credential permissions, registered callback compatibility and exact issuer checks. +Plugin-owned connection changes use their existing supervisor. Carry the originating auth lease +through its probe, integrity/recovery and native launch waits; finish owned cleanup after canceled +startup, and never bypass that owner through generic MCP stop routes. + +Provider-specific Codex/Grok credential owners receive explicit private paths and the existing +async string-only crypto port. Preserve their v1 encrypted/plain-file envelopes, corrupted-file +backups, fsync/rename commits and file fallback policy; it differs from MCP's memory-only fallback. +Bound each file/envelope to 16 MiB and each normalized/decrypted payload to 8 MiB. Serialize at most +24 normal and eight reserved control mutations, fence in-flight reads against queued mutations, +and coalesce only pending reads rather than retaining plaintext tokens. Shutdown bypasses queues; +failed durable credential removal cannot produce a successful logout acknowledgement. + +Provider auth owners receive browser interaction explicitly and fence pending browser/device login, +refresh, status events and token return across account replacement, logout and stop. Keep endpoint +restrictions, refresh sharing, API-key fallback and single 401 retry. Synchronous runtime-provider +construction carries public configuration only. Resolve Grok credentials in its existing async +fetch adapter or direct trusted request immediately before building request headers; do not add a +plaintext credential snapshot to preserve synchronous helpers. Keep global initialization and +shutdown explicit at the actual composition, without default native Store construction. +Carry the originating auth scope through token acquisition and recheck before request dispatch or +401 refresh; cancellation and replaced owners cannot become API-key fallback. Status reads may retry +once after an observed account transition, and newer status publications supersede delayed reads. +Each auth owner admits at most 32 HTTP requests. Bound auth response bodies to 8 MiB using declared +and streamed sizes, and retain cancellation and the existing request deadline through body reads. + +Sync host connector tokens and peer pairing tokens use asynchronous string-only crypto ports +through the same adapter. Their opaque values stay in existing private machine-local files and +remain compatible with legacy ciphertext. Keep Linux basic-text refusal and preflight protection +before consuming a pairing code. Cancelled pairing cannot replace the saved identity before its +atomic commit; stale reads cannot release credentials after re-pairing, forget or shutdown. Transfers +recheck cancellation after unwrap and before authenticated requests. Host lifecycle serialization +and generation checks reject crypto results superseded by stop before starting a connector. +Do not cache plaintext, rewrite credentials during reads or add them to general settings/backups. + +Database security receives its database path, owner-local encryption metadata and asynchronous +native crypto explicitly. Its SQLCipher copy, validation and file replacement stay with the +database owner; Main owns unlock presentation and application quit. Metadata keeps the existing +file shape and opaque native ciphertext. At G5 its one writable facade moves with the owner; +it must not become synchronous RPC to a second Main writer. Maintenance-authorized encryption +uses the native crypto adapter independently of the paused ordinary credential store. + +Hold the per-file migration lock through native preparation and commit. Prepare wrapped target +metadata before closing the writer, then retain the original database until target reopen and +metadata persistence both succeed. Roll back both on runtime failure; stop fences pending native +results before password validation, return or mutation. A second migration or metadata repair +cannot enter while preparation is pending. This runtime rollback preserves the existing startup +file-recovery policy; it does not provide crash-atomic commit across the database and metadata +files. Durable cross-file recovery belongs to the conditional Store compatibility project. + +Live maintenance uses explicit data commands for verified backup-file import, named backup import, +encryption enable/change/disable and schema repair. Keep one exclusive admission through pause, +drain, operation, reopen, reconciliation and resume; caller callbacks, SQL and database handles +cannot cross this boundary. Startup recovery runs before the normal owner exists. Normal business +transactions and maintenance snapshot/staging connections stay internal to the Store owner moved +in G5, including any synchronous closures that never cross an ownership boundary. + +Ordinary request admission shares the maintenance owner's state. Refuse new and not-yet-executing +ordinary requests while maintenance is active; retain only audited native presentation/status +operations. Drain admitted finite operations before closing or replacing the writer, with a bounded +failure path that keeps the existing database. Maintenance command requests cannot wait for their +own acknowledgement as ordinary work. CLI alternate ingress paths participate too; an event +subscription's initial database snapshot is finite work, while subsequent in-memory event forwarding +is not a database lease. Detached background effects keep their own explicit lifecycle ownership. +Track actual handler completion independently of a startup task's reply promise: cancellation can +reject that reply before the handler exits. Recheck the original task signal before execution, and +retain running-handler ownership through cancellation. Bound request drain to 30 seconds before +maintenance dispatch; a timeout must leave the current writer in place. + +Main startup work has a separate actual-completion lifetime from its reply. Before maintenance +pauses or recreates services, cancel the Main startup target and drain running callbacks, including +callbacks from an already canceled run. Pending work never executes and releases its deduplication +entry. Keep immediate cancellation replies and existing resource-lane limits. A 30-second drain +failure rejects maintenance before service teardown or writer replacement and restores ordinary +admission; it must not revive or duplicate a still-running startup owner. Background migrations are +scheduled again after successful maintenance or on the next launch. + +Shutdown closes ordinary admission and background availability before teardown. Cancel and drain +actual startup callbacks before disposing their service dependencies, then drain ordinary requests +and the actual database-maintenance owner before closing the writer. Keep completion-only ownership +for maintenance, including verified-peer commands; do not retain command results or credentials. +The two device commands that await shutdown before restarting or resetting data cannot wait for +their own request lease. Their existing validation and exclusive shutdown-action claim still apply. +CLI authorization completes before dispatch and retains its ordinary lifetime for those commands. + +A maintenance operation interrupted by shutdown must not reopen the database, reconcile runtime +state or resume background services. Recovery-triggered shutdown starts only after that operation +releases its own lifetime. Critical completion barriers have bounded failure paths and propagate +failure before writer close or a terminal action; best-effort cleanup must not swallow them or claim +successful ownership release. These are lifecycle deadlines, not performance acceptance budgets. + +### Public routes and desktop capabilities + +Reuse existing shared route names, schemas, caller types, and output projection. Classify each +route as desktop-owned, backend-owned, or an explicit orchestrated operation. A proxy is registered +only for the audited backend allowlist. Audit `z.custom` and non-JSON values instead of assuming +every current route is already a complete wire validator. + +Electron main validates the actual IPC sender/document or CLI connection, constructs the caller, +and forwards that identity. The backend revalidates input and applies domain permissions. Caller +identity, `internal` authority, and approved desktop capability names cannot come from a renderer +payload. Preserve plugin-settings route ownership and renderer-destruction cancellation. + +Desktop ports cover real operations such as OAuth browser interaction, crypto, approved helper +launch, and targeted approval presentation. They do not expose an Electron module, a generic +method dispatcher, arbitrary eval, or an unrestricted secret/settings store. Permission and +approval lifecycle state stays with the backend Run; main transports authenticated human answers. + +Skills receive explicit home, temporary, user-data and ordered bundled-resource directories at +bootstrap, plus an asynchronous directory-open capability. Settings defaults, temporary imports, +rollback paths and plugin-source checks use those supplied directories. Discovery, filesystem +mutations and scoped operation leases remain local to the Skill owner. Native watcher creation +stays behind its existing injected service; importing its pure request-ID helper must not load +the Electron host. Shared console diagnostics are platform-independent and retain captured sinks; +the desktop entry supplies the development debug flag. Moving a Skill owner must not introduce +Electron imports through logging or utility barrels, a second service instance or a native fallback. + +The desktop entry owns legacy settings-file construction and supplies the current application +version to configuration migration and Provider initialization. The shared settings wrapper must +not import Electron or construct a native store. Provider model facts and user overrides require +the existing database-backed stores at construction; no runtime helper may silently create legacy +JSON storage or replace its storage authority after use. Legacy JSON import remains a startup +operation that completes before module settings attach to the one database writer. Catalog-cache +bootstrap and other native configuration domains require separate explicit ownership before G5. + +MCP and ACP settings receive their initial storage explicitly; pure default builders retain the +existing platform-specific server defaults and ACP migration format. Main constructs native +bootstrap files before import and database attachment. After attachment, MCP configuration uses +its database adapter. ACP enabled/version/shared MCP selection also use the database; its legacy +registry/manual/install metadata becomes an isolated read-only snapshot for import and fallback +reads. Live Agent changes already use the unified Agent repository. Remove unused legacy mutation +entrypoints and reject writes through the attached legacy projection. Normalize legacy ACP data +before the migration snapshot, and transfer the configuration/database owner together at G5 +without retaining a writable Main shadow. + +Provider catalog bootstrap receives its profile cache directory and bundled catalog file from +Main. Importing model capability/settings modules performs no native path lookup or cache I/O +before this bootstrap. Packaged catalog bytes live outside ASAR so ordinary Node can read the +same file. Keep one catalog/cache owner, privacy-aware automatic refresh, existing payload/TTL +bounds and single-flight refresh. Initial catalog loading performs no network work. Automatic +refresh belongs to the existing Main idle Provider task, including its maintenance cancellation +and drain; a canceled task cannot publish or write a late response. Shutdown cancels every refresh, +fences late cache writes/events and waits for actual completion before disposing dependent owners. +Provider request metadata +is a plain bootstrap header snapshot beside the existing locale capability. Token exchange and +HTTP failure sanitization must not import the native Copilot device-login window. +Ollama model pulls receive the existing TLS debug policy from Main through the Provider environment +port. Keep the native development/explicit-debug decision at the host; a backend import must not +load Electron to evaluate it. Production certificate-validation policy is unchanged. +Shared image-cache reads/writes use the existing trusted `DEEPCHAT_USER_DATA_DIR` process bootstrap +set by Main, require an absolute profile directory and have no native/home-directory fallback. +Retain all path, symlink, size, cancellation and remote-address checks. This removes an image +mapper's transitive Electron dependency without changing cache location or granting new access. + +ACP connection/session execution receives application version, home and temporary directories as +plain host bootstrap values. Thread these through the existing lazy runtime owner to its process, +terminal and persistence owners. Keep shell/environment expansion pure and share it with MCP; +remove native lookups from the shared helper. Preserve workspace containment, controlled temporary +fallbacks, launch/toolchain selection, terminal output bounds, persisted links and process cleanup. +This preparation does not relocate ACP execution or its native PTY binding to another process. + +MCP client execution receives an absolute home directory, application version and the existing +toolchain owner explicitly from composition. Preserve command rewriting, live toolchain selection, +PATH precedence, environment binding isolation and process cleanup. Each registry probe batch awaits +the current resolved proxy URL through the host capability, preserving system/custom proxy and +authentication behavior without importing the native proxy module. Neither the client +nor its server manager may construct a native dependency or locate a process-global toolchain. +This boundary does not relocate MCP execution or its AppHost presentation owner. + +Core runtime constructs one native-free MCP App registry beside source and tool authorization +owners. Count pending creation against the existing total and per-owner limits. The registry owns +renderer-destruction subscriptions from reservation until its final creation/view retires; an +already destroyed owner cannot publish a reservation. Window/server revocation cancels pending +creation and consent. Maintenance clears them before database drain; terminal close also fences +new reservations. Aborted observation and metadata waits retire without closing the server owner's +shared connection. + +Main retains Native owner validation, link opening, protocol responses and session permission +handlers. It receives only asynchronous policy and browser-permission operations, never the +registry or its mutation methods. The fixed private Core protocol validates bounded commands and +results and shares the existing ordinary-call slots and immutable queue with public business +routes. Owner destruction uses the existing reserved Native event lane; cancellation, human answers +and status/stop retain their capacity. Accepted protocol calls join the business drain before +writer release. Native handlers recheck the current owner and deny uncertain or revoked work. +Preserve saved-source/configuration fences, separate sandbox origin/CSP, explicit per-request +consent and the absence of standing browser grants. + +Desktop host queries resolve Workspace preview authorization and Provider existence through the +Core owner and its database request gate. Permission-cache clearing and shutdown-confirmation data +use fixed private commands on the existing reserved Native event lane, so ordinary call saturation +cannot prevent quit preparation. The confirmation operation returns data without waiting for a +dialog; Main owns presentation and the user's response. Knowledge has no dialog dependency or +shutdown presentation method. Accepted private operations join the business drain, and a business +denial does not masquerade as Native transport failure. Quit/update preparation awaits permission +retirement; the update request records its shutdown promise before that await. Kernel retirement +also clears permissions before stopping the local CLI server. + +Only Main owns interactive message dialogs. Core has no dialog invocation or response capability; +its shutdown-confirmation result remains strictly validated data. Main retirement rejects pending +confirmations and refuses late requests before closing windows. Core loss while a CLI approval is +unanswered must close the application generation without approving or replaying the mutation. + +The same Main dialog owner accepts at most 16 native file-picker requests, reusing the Native call +budget. Requests retain their selected parent and enter one FIFO; only one physical picker opens +at a time. Directory/file selection, image saving and context-menu saving use this owner. A stopped +owner or destroyed parent cannot open a queued picker. Main stops accepting dialogs, obtains the +Core shutdown fence, and awaits the native callbacks before releasing their ownership. On macOS, +the active sheet ends before its parent is destroyed; queued requests retire without opening. +Other platforms retire the parent before awaiting native completion. Do not replace a physical +completion receipt with a synthetic canceled result or extend shutdown deadlines. + +File-route image save/copy uses the existing Main-owned DesktopImageActions lifetime. Main +shutdown aborts pending HTTP +headers/body and cache reads before waiting for ordinary Core calls. A retired image owner cannot +open a save picker, decode/write the clipboard or start a file write after an awaited read or +picker result. A target-file write already in progress completes before its Native receipt retires; +never cancel it midway. Existing accepted Native calls and business drains still precede writer +release. Do not serialize an AbortSignal across the Core protocol or create another request owner. + +ACP installation owns explicit install/repair, enable-triggered preinstallation, uninstall and +binary launch preparation, including their repository and Provider continuations. Identical work +shares one physical download; conflicting same-Agent mutations cannot publish stale state or +recreate files after uninstall. Cancellation covers headers, bodies and extraction boundaries; +drain waits for actual settlement before database maintenance or close. Canceled work restores its +previous acknowledged install state only while it still owns the pending state. Background +admission uses the existing local database gate, which moves with the Store in G5. Prepare, +validate and chmod a replacement in a managed staging directory before activating it; download or +validation failure preserves the installed binary. Keep archive traversal/checksum guards and the +120-second download timeout. Enable acknowledgement remains prompt. This does not establish +power-loss durability across filesystem activation and SQL publication or move synchronous +extraction out of Main. + +Background command execution keeps its existing dedicated helper and process/session ownership. +Main supplies an approved absolute helper entry and cancelable launch capability; the business +owner accepts a plain command/event/lifecycle facade with no Electron fallback or path guesses. +Tool, Bash and Skill consumers receive the same owner explicitly. Stop rejects new work before +canceling helper startup, fences requests after asynchronous authority checks, compensates a late +spawn and awaits actual callback settlement. Child identity attestation, conversation isolation, +permission/journal commitment, output offloading and owned Skill package cleanup remain intact. +Root shutdown requests current Harness generation cancellation before stopping the command owner, +so a rejected helper startup cannot begin another model round while the app is already stopping. +Helper loss can restart on subsequent ordinary work; terminal shutdown cannot reopen it. Native +process objects remain inside Main's adapter. G5 supplies the corresponding backend-owned launch +capability without exposing arbitrary process-launch IPC. + +Code Mode receives its approved helper launch through a required host capability. Main owns the +absolute helper entry, native events and per-host RSS sampling; preserve the restricted environment, +64 MiB heap, unsigned-library policy, protocol/READY deadline and memory/output limits. Reserve a +session before asynchronous startup. Caller cancellation keeps a prompt receipt while the owner +retains physical startup and compensates a late helper. Shutdown closes admission, cancels pending +startup and waits for actual startup, RSS and nested-tool callbacks before Store teardown. Sampling +coalesces per cell and cannot act after that cell stops. Preserve session-scoped state, permission +retry, capability/journal identities and existing result/yield semantics. No optional native +fallback, process object, raw metric list or generic PID/launch endpoint crosses the boundary. + +File watching keeps its existing content and Git helpers, pooling, fallback modes and restart +policy. The Core composition owns Workspace and watcher clients; Main supplies an approved +absolute helper entry and a cancelable command/event/lifecycle capability. The client has no +Electron import, implicit launcher or path search. Register startup ownership before invoking +the Native launcher so synchronous shutdown cannot miss it. Terminal shutdown fences new watches and +restarts, cancels pending startup, ignores retired-host events and waits for actual helper exit. +A shutdown RPC acknowledgement or kill request alone does not prove process retirement. + +Workspace retains path authorization, preview grants, file/Git operations and invalidation +production with that Core composition. Main supplies acknowledged reveal/open and installed-app +selection/launch operations; preserve path checks and the installed application allowlist. The +native preview protocol queries the Workspace owner asynchronously and denies unavailable or +revoked authorization. Main owns scheme registration and response streams, without a second +preview registry. Register desktop work before Native dispatch. Stop admission and drain accepted +desktop effects, revoke root/exact-file +grants, and recheck authorization after asynchronous file preparation before returning a preview. + +AgentToolManager receives absolute system/profile temporary roots and an asynchronous image-size +operation through its required desktop capability. Main owns app-path discovery and nativeImage; +only width/height values leave the adapter. Preserve the existing allowed runtime directories, +default workspace creation and temporary-root fallback, file/symlink authorization, image preview, +metadata failure fallback and vision selection. Carry cancellation through the image probe and +recheck before image caching or Provider work; a late result cannot start those effects. The +business manager has no native import or implicit native host construction. This capability does +not expose a new generic file-read or root-discovery route to renderers or external plugins. + +File preparation remains with its Node-capable business owner: MIME detection, document/image +adapters, validation, generated-name temporary image writes and message-file projections. +Require its absolute profile directory at construction and await temporary-directory creation when +writing. Keep adapter instances internal and expose no arbitrary profile write/delete operation. +Preserve existing size, token, PDF routing and symlink +read checks. Main owns image save dialogs, clipboard decoding/writes and native network image +retrieval as a separate desktop action capability. Route those UI actions directly to Main rather +than looping through the backend. Preserve data-URL/raw/cache/HTTP image handling, MIME and filename +rules, cancellation responses and actual-byte saves; nativeImage values never leave that owner. +Main's image-cache access is read-only, and a save writes only the native dialog's selected target. +This split adds no generic filesystem route, replacement image engine or gateway-size increase. + +Remote's channel/session policy, channel clients, attachment handling and binding/configuration +authority move together. RemoteConversationRunner requires its profile image/generated-asset roots +rather than discovering them through Electron. Actual downloads and decryption remain with the +co-located clients/Runner using their existing fetch implementations, timeouts and file-preparation +policies. ChannelAdapter contains lifecycle/events, with no native network dependency or unused +base64 download API. Preserve the active call chain; do not introduce a per-download host RPC or +a new global size cap for methods that no consumer calls. + +Keep attachment bytes local to this business owner during G5's process move. A later, actual +cross-host/device transfer must define bounded authenticated artifact/staged operations before +adoption. Do not return a Response, stream or oversized base64 control frame, enlarge the gateway +limit, silently truncate legal files or replay a lost download. CLI ArtifactSpool's human/Agent +principal is not channel authority: never fabricate a human CLI caller or borrow unrelated +artifacts. Bind any selected transfer to its trusted request/channel/backend lifetime, reserve +bounded capacity before reading and retain actual cancellation/cleanup ownership. + +Main owns Feishu/Weixin native login windows; the Remote owner retains auth/session state, callback +server, nonce, expiry and credential/configuration commitment. Native capabilities carry plain +operation IDs, URLs and closed/error events, never BrowserWindow/WebContents objects. Preserve +Feishu's brand/redirect navigation rules, popup denial and isolated secure web preferences. +Register an operation before awaiting native startup, fence late opens/close events by generation +and operation identity, compensate a late native open after cancellation, and drain callback-server +and window cleanup before owner teardown. Native events cannot synchronously re-enter a pending +business command. Native module loading alone does not complete Remote's cutover. + +The selected desktop login adapter owns Feishu/Weixin BrowserWindows behind asynchronous +operation-ID open/close commands and closed/error events. Authorization starts reuse Remote's +existing serialized runtime queue. Stop closes admission, aborts owner signals, fences late +registration/login results and waits for actual startup, polling, callback and native-close work; +initialization waits for that stop before admitting a new owner lifetime. A late native open is +closed by its original operation identity. No BrowserWindow, Response or app secret crosses this +port. Main also closes any retained native windows during teardown. + +Feishu retains its loopback server, nonce, TTL, token/user exchange and pairing commitment in Core. +Cleanup waits for active callbacks and response completion, then closes partial-header connections +and acknowledges server release. User window close remains cancellation even when destruction +rejects navigation before the native closed event; a load failure in an active window remains an +error. Native navigation and popup rules and isolated web preferences remain explicit. Weixin +login polling carries owner cancellation through HTTP timeouts and poll sleeps, retires canceled +QR sessions and cannot commit credentials after stop. Profile image/generated-asset paths are +derived from Main's explicit absolute profile directory; the workspace default is queried through +an asynchronous owner port. The complete Remote module can import and initialize in ordinary Node; +G5 still has to move its actual composition and authenticated host-capability wiring. + +ACP registry ownership includes its manifest, icon cache and acknowledged repository/Provider +updates. Main supplies absolute profile/built-in directories and an asynchronous icon-text +capability; the core receives no Electron Response or native object. Retain the existing URL, +SVG, privacy, TTL and request-timeout policies. Package one registry/icon snapshot outside ASAR +and remove implicit app/cwd path lookup. Local initialization performs no network work. The +existing Main startup task owns automatic refresh; manual refresh remains within route ownership. +Keep refresh single-flight, carry cancellation through icon work, and recheck before cache writes, +repository effects and event publication. Terminal stop aborts work; drain waits for actual +completion before database disposal. A canceled startup task cannot write after maintenance, +while a new task or manual request can still use the live owner. + +First-party configuration selects the core entry and service exports. Rutis dependency injection +and scopes are not authorization. This migration introduces no arbitrary third-party Rutis code +loading or untrusted peer access. Existing `.dcplugin` and portable user-plugin behavior retain +their reviewed package, consent, ownership, and execution contracts. + +Provider OAuth presentation stays in Main while the Core facade owns sign-in target selection, +access validation and acknowledged credential commitment. The private target retains Provider ID, +public client ID and opaque configuration identity at its owner. Native authorization receives only +the public client ID and cancellation scope; it receives no Provider settings mutation capability. +The acquired credential returns to Core for validation and the generation-checked Provider command. + +Codex/Grok login controllers, callback/refresh state and credential queues stay with their Provider +consumers. The Core OAuth facade uses their asynchronous status/login/callback/cancel/logout +commands without introducing another controller or writable credential store in Main. +Keep synchronous load-error/revision observations inside each controller's existing authority. +Native browser opening and crypto remain explicit host capabilities, with the existing public +auth status schemas and stop/drain behavior. + +Native deep-link actions receive an asynchronous provider-existence query instead of Provider +settings. Validation finishes before the settings preview opens; saved Provider credentials stay +at the business owner. MCP installation deferral uses Main's observation of acknowledged Core +startup, with latest-pending semantics. Reset this observation when stopping, and reject a late +startup acknowledgement after shutdown or maintenance admission closes. Readiness observation is +presentation state; actual MCP configuration and execution authority remain in Core. + +Core compares and commits in one command without an await between reading current configuration +and writing. The identity covers Provider ID, protocol, current credential and normalized client +ID using a private per-runtime key. It rejects targets from another runtime, stale concurrent +logins and malformed tokens while merging unrelated current settings. This identity detects stale +configuration; authenticated caller grants remain a separate G5 requirement. Maintenance rotates +the identity key; stopped runtimes reject queries and writes. Main fences late query/validation +results during shutdown and aborts token validation. Existing registered Copilot/Codex/Grok routes +keep their schemas; no generic OAuth fallback or unregistered loopback login server is retained. + +### Gateway and event flow + +The cohesive Core runtime constructs local-control authorization, the shared approval broker, +command/file/settings permissions, programmatic parent/child dispatch, audit and CLI server wiring. +CLI Run, compute, audio, OCR and Skill requests execute against those same local service owners +and the database request gate. Keep the one Agent token authority and artifact spool shared with +Provider/tool execution. Desktop presentation supplies trusted approval-target discovery and +acknowledged request/close delivery; it does not construct a parallel authorization policy. + +Toolchain discovery and demand, OCR execution/attachment routing, startup migrations, deferred +initialization, Provider warmup and background persistence tasks compose with Core's consumers. +Main supplies explicit roots, the wrapped OCR cache-key capability, native floating-window startup +and structured startup-error delivery. Preserve startup task identities, cancellation/drain and +the actual failed run identity, including a new run after database maintenance. Skill and ACP +migrations finish before ordinary renderer/background admission. Native presentation retains its +existing owner; these composition boundaries alone do not prove physical Core adoption or the +headless CLI horizon. + +Native Window, Tray, Notification, Shortcut and floating-widget readers use a private committed +configuration projection. Its explicit allowlist contains resolved language, notification, +close-to-quit, content-protection, shortcut, floating-widget, diagnostic logging, privacy and +update-channel values. Nested shortcut and bounds objects are allowlisted too. Never copy the +merged settings Store or credentials into this projection. Publish it immediately after the +configuration write, before native effects or ordinary settings events; native effect callbacks +must not query the pending configuration command again. Main reads native theme state directly. + +The projection carries an owner identity and monotonically increasing safe-integer revision, +rejects foreign/stale snapshots and returns defensive copies. These fences do not establish a +cross-process readiness or owner-replacement protocol; G5 must authenticate adoption of a new +backend generation. Main's projection is neither a writer nor a public renderer snapshot. +Floating bounds use an acknowledged owner mutation, with no optimistic cached update. Validate +the current floating window and its main frame before accepting drag writes. Stop native write +admission before draining the configuration queue and outstanding native writes for maintenance +or shutdown. Floating enable/disable awaits actual native completion within the same serialized +configuration command, so a queued disable or Store close cannot overtake window creation. +Update-channel normalization remains with the configuration owner; Main supplies the application +version, native updater and diagnostic-file lifetime explicitly. + +The selected sixteen-field application settings projection uses one Main presentation revision +sequence across snapshots, mutation replies and `settings.changed` events. Renderers retain this +revision and accept values per key, so late replies cannot reverse newer fields or discard unrelated +partial values. The sequence lasts for Main's lifetime and its renderer contexts; it is not a durable +database revision. Refresh the allowlisted projection after database maintenance and reject new +operations while maintenance or shutdown is active. + +Agent display changes use one validated asynchronous data-only mutation and an acknowledgement +with the native owner's previous/applied value. Keep skill authorization and durable dispatch +commitment at the Agent owner. Settings navigation awaits the Native delivery receipt before +reporting success; a negative or failed receipt reports `open_failed`. This capability carries +only typed settings navigation, without a generic window-channel endpoint. +G5 routes portable preferences directly to the backend configuration +owner and native device effects to Main; a Core-to-Main-to-Core mutation loop must not replace +direct ownership. Other configuration entry domains and host capabilities still require their own +G4 boundaries. Neither a second settings writer nor cached secret values belong in this projection. + +The application gateway carries request, reply, cancel, event, and readiness/control messages. +Every message identifies a protocol version and host epoch; requests/replies also identify a +request ID. A core generation change invalidates outstanding requests, subscriptions, and desktop +capability leases from that generation. Keep Session IDs, Run IDs, message IDs, and Tape +incarnations as their existing separate identities. + +The initial private Core bootstrap link uses the existing 64-KiB control frames and a separate +one-shot Core generation. Its four work slots reserve one for audited cancellation and human +interaction routes; up to three ordinary calls run concurrently. Main snapshots queued renderer +requests with a combined limit of 64 entries and 64 MiB, and drains the two lanes independently. +Under queue pressure, an incoming control request can reject unsent ordinary work with an explicit +non-admission outcome; accepted work is never displaced or replayed. Main rechecks the original +Native sender/document when accepting queued work and immediately before sending it, including +after an asynchronous carrier write. This local authorization closure is never serialized. +The authenticated Native IPC entry constructs renderer identity; the private link rejects CLI or +internal identities, which remain local to the Core CLI/runtime owners. Core revalidates route +input and lane selection against the owner-local registry. Public business dispatch requires +acknowledged Kernel startup, including migrations and service activation. Cold assembly does not +grant business readiness. Two additional call slots remain reserved for status/stop and one for a +coalesced system-theme observation. Untracked business routes do not query Native window roles; +ordinary presentation queries cannot delay cancellation or approval solely for startup accounting. +Terminal stop fences queued work and drains accepted route continuations, Native effects and +startup activation before releasing profile ownership. +The observation queries Native +theme state, serializes with configuration and repeats once for a notification received during +an active observation; it does not carry a caller-supplied color or retry an unknown outcome. +Up to sixteen reverse Native calls are tracked independently. Native replies +are settled independently of Core request execution, so profile startup can await a Native effect +while stop/status remain callable. A dropped reply does not release a profile or a Native write. +Status has a 5-second observation deadline and stop has a 30-second observation deadline; expiry +does not retire the operation, release its slot or authorize another owner. Status and stop each +have a reserved slot. Profile unlock/recovery may await human input without a timing budget. + +Native helper callbacks use one additional reserved Core call, independent of ordinary work and +theme observations. Ordered batches carry at most 32 generation-bound helper events over the +existing carrier transport. The five fixed kinds are background execution, Code Mode, Scheduler +and content/Git watching. Main retains approved launchers and owned-host RSS sampling. Launch +selects a fixed helper kind, not an executable entry; RSS addresses an opaque owned helper instead +of an arbitrary PID. Existing helper messages retain their data/authorization contracts. Track startup before +dispatch, compensate canceled/late startup and await actual process retirement. Queued commands +and events have count/byte bounds; overflow fences the generation without uncertain replay. +An open reply acknowledges admission. A separate opened/failed event settles the original host +capability; cancellation after admission can retire a launcher that has not returned yet. Exec +retirement uses the owned Native host's asynchronous kill receipt, covering physical helper exit +and profile-scoped attestation/recovery of its registered command roots. Keep an exited helper in +the Native owner until this recovery settles, including unexpected helper loss. The execution +service waits for that receipt before shutdown or replacement startup. Owner-scoped registry +reads reject unreadable/corrupted records; age does not authorize discarding live owned records. +Native hosts replay a recorded exit to late subscribers, and register retirement before a +synchronous exit callback can reenter it. +Exec startup awaits durable identity registration before acknowledging a live command. Failed +registration retires the owned command and rejects startup; already exited commands retain their +normal output/completion behavior. Failed retirement rejects command removal and shutdown instead +of treating local output finalization as physical exit. Shutdown rejects subsequent starts, +including starts whose identity observation was still pending. +Legacy POSIX termination keeps the acquired process-group target after the root closes, waits +for group exit and physical stdio closure, and escalates the same group if members survive TERM. +It never falls back to the old root PID after acquiring a group. Unknown liveness is not an exit +receipt. Exec finalization, package cleanup and registry removal wait for this single termination +receipt; root exit/close and inherited-stdio watchdogs cannot bypass it. Failed tree retirement +remains failed when a late root close arrives. This does not authorize later signaling after the +group has been released, or prove containment of descendants that leave that group. +On macOS, the existing Rust artifact also provides a bounded read-only process-identity command +before starting Tokio or Rutis. Main supplies its absolute packaged/development artifact path to +owned workers. Records backed by a live owned child use the boot-session UUID and XNU process unique ID, which survives +exec; executable UUID and pidversion are deliberately excluded. Native identity loss refuses +recovery without downgrading the recorded identity to timestamps. Legacy records and platforms +retain conservative start-time/argv attestation; an argv mismatch alone never clears ownership. +Registration revalidates the owned child after asynchronous inspection and before persisting its +native identity; a reaped child cannot attest a subsequently reused PID. PID-only launch callers +retain legacy attestation until they provide an actual child lifetime. +The XNU identity flavor is defined in Apple's private header and requires native/signed platform +verification. This introduces no resident inspector, alternate supervisor or renderer-facing API. +New macOS Exec roots first enter the same Rust artifact's execution gate before Tokio/Rutis +startup. A dedicated inherited descriptor carries one bounded length-prefixed environment grant +only after durable native identity registration. Command arguments are passed without a shell; +user environment overrides are withheld from the gate and applied after the grant. EOF, malformed +or incomplete grants execute no user command. The gate closes its control descriptor and execs +the requested program at the same registered PID. Caller stdin and output streams remain separate. +Core resolves command overrides against its environment into a complete launch snapshot before +Native dispatch. The reusable utility uses this snapshot as the command environment; it must not +reintroduce deleted variables or utility-only controls from its own older environment. The Rust +gate replaces its bootstrap environment only after admission, preserving shell/PATH preparation, +per-command overrides, independent stdin and same-PID exec. +Stop/timeout during registration cannot release the gate. Startup failure compensates through the +same owned termination receipt, and the pending environment is released after startup settles. +This closes the selected user-code admission window on macOS; observable pre-registration +retirement also requires the utility/owner receipts below. Other platforms remain unverified. +Different owner recoveries serialize without sharing results. Lost ancestry and complete +fd/environment/proxy retirement still require proof before G5 acceptance; registered-root recovery +alone does not establish that contract. +Native macOS termination passes its retained or recorded birth identity to the existing Rust artifact. Rust +captures a bounded group snapshot while revalidating its root and sends signals through +`proc_signal_with_audittoken`, whose kernel lookup checks the captured process version. TERM and +force target only those captured identities; neither phase falls back to numeric PID/group +signaling. Unsupported APIs, uncertain inspection, concurrent exec or surviving/new group +members refuse retirement and retain the registry record. A signal request is not an exit +receipt: termination waits for every captured birth and the group to disappear. Incomplete +captured ancestry and unobservable descendants remain outside this proof. +The system's [audit-token lookup](https://github.com/apple-oss-distributions/xnu/blob/main/bsd/kern/kern_proc.c) +and [signal dispatch](https://github.com/apple-oss-distributions/xnu/blob/main/bsd/kern/proc_info.c) +define the native identity checks; availability still requires supported signed-platform acceptance. +Successful registration returns its observed identity to the existing session owner. Admitted +native commands retain that identity for explicit stop, timeout and shutdown, which use the same +Rust group recovery as helper-loss recovery. A native recovery receipt also waits for the actual +owned child's stdio close before releasing registry or package ownership. Gate admission requires +a native identity; unavailable identity cannot select legacy signaling for an admitted command. +Native root exit joins that same retirement before publishing completed status or watchdog cleanup. When the +root has exited, Rust requires every surviving group member's captured parent-birth chain to +reach the registered root birth. Missing ancestry refuses signaling and retains ownership. +Retirement also takes bounded same-user process snapshots and follows captured parent-birth +chains outside the original process group. UID alone never authorizes a signal. Captured births +remain authoritative across the TERM and force phases; each phase takes a fresh snapshot so +known descendants born during retirement cannot disappear behind a dead intermediate parent. +Final success requires no reachable captured descendant and disappearance of the original group. +Unknown inspection or snapshot overflow refuses retirement. This does not prove containment of +unobservable processes, privilege changes or ancestry lost before capture. +Main captures the Exec utility's native birth before exposing its command channel. Retirement +waits for actual utility exit, then uses that birth to retire observable descendants before command +record recovery can acknowledge closure. The utility need not lead a process group; this owner +scope never observes or signals a numerically matching process group. Missing native identity +refuses readiness, and a failed descendant receipt refuses retirement. Utility-side cancellation +before native identity closes the ungranted control descriptor and waits for actual child/stdio +closure without numeric PID/group signaling. Helper-loss recovery uses the owner birth for +observable pre-registration launches without creating another registry or supervisor. +Native records survive root loss, same-boot root PID reuse and age-based expiration. A valid +identity from another boot can be cleared without signaling a current numeric PID. Missing +ancestry and privilege changes remain separate requirements. +Logical transport failure still admits retirement and settles already accepted Native effects; +ordinary caller outcomes remain fenced and are never replayed. +The existing sixteen reverse Native calls reserve two slots for retirement commands. Ordinary +work queues at fourteen calls, with at most 64 waiting requests and 64 MiB of queued values. +Coalesce helper termination identities into batches of 32, so many cancellations cannot consume +all control slots. Stop rejects queued ordinary work and waits for accepted Native calls to settle. + +Browser tool dispatch uses a fixed Native command and callback protocol on the same reserved event +lane. Core retains the dispatch-commit callback; Main supplies normalized arguments and awaits its +acknowledgement immediately before the native target. A rejected commit is a call outcome, not a +generation failure. Unknown identities or repeated authorization attempts fence the generation. +Preserve recoverable Browser-unavailable payloads and the original Core commit error. No functions, +live Browser objects or arbitrary native method names cross the link. + +Register Browser ownership before admission, retain cancellation across a late admission reply and +compensate failed admission. Cancellation and close use the reserved Native retirement capacity. +Main must provide a physical retirement receipt covering Browser closure and native effects that +outlived a canceled client wait; releasing a waiting promise alone is insufficient. +Kernel shutdown retires Main's Browser owner even when no Agent Browser tool was called: public +desktop routes can own pages independently. Profile-only SDK operations do not acquire this desktop +owner. Main fences new page/preview allocation during retirement, joins each page's existing close +receipt and waits for actual WebContents destruction without a before-unload veto. A failed close +retains failed ownership and prevents profile release; a late event cannot clear a replacement page. +Main's cookie reader owns its temporary WebContents for one operation and awaits destruction even +if debugger detachment fails. Application windows and floating UI retain their Main application +owners. A floating-window registration in `TabPresenter` is a borrowed lookup for capture/context, +not another view owner or Core resource. The unused browser download service has no typed route or +Agent-tool consumer; browser-origin downloads retain their Chromium/Main application ownership. +Synchronous dispatch commits retain their current timing; asynchronous commits are acknowledged before target +dispatch, followed by fresh cancellation and target-lifetime checks. Helper and Browser events +share a serialized, count/byte-bounded queue with value snapshots, rather than competing for the +single reserved slot. Business authorization denial does not retire the event transport. + +Client projections invalidate the affected model catalog cache before forwarding catalog or status +facts to consumers. A fact-triggered refresh cannot reuse a snapshot captured before that fact. + +The cohesive Kernel root owns the event hub/session router, startup work, CLI token/artifact +authority and MCP Apps registry with its data, Provider and runtime composition. Profile closure +follows its terminal drain, Native-effect retirement and shared admission/Memory retirement. +Partial construction is not a successful close receipt. Configuration events share the owner's +version source, and an ordinary renderer session's `null` CLI ownership must remain distinct from +unknown/deleted ownership. + +Native proxy resolution publishes a revisioned effective URL and merged `no_proxy` snapshot. +Main owns current and gracefully draining proxy dispatchers until physical retirement. Stopping +fences queued/late proxy resolutions, aborts both current and draining transport, and joins accepted +Electron resolution without allowing late dispatcher/environment changes. The Native desktop +retirement receipt includes this HTTP owner before profile release. Client references survive +Undici Agent graceful close until completion, so forced retirement can still address active clients. +Start Main transport retirement before awaiting Core shutdown: a Native request that ignores caller +cancellation must not prevent shutdown from reaching the desktop retirement receipt. OAuth acceptance +must exercise Core-owned validation through real certificate-verified HTTPS, with a disposable local +trust root rather than Main-only fetch interception. These receipts cover global fetch transports; +Chromium session networking and independently owned descriptors require their own lifetime evidence. +The SDK Kernel installs its own Undici dispatcher and proxy environment before Provider/runtime +construction, and acknowledges setting changes only after applying that snapshot. Older revisions +cannot replace newer state. Preserve the existing system resolver's single probe and first HTTP +proxy result; this is not per-destination PAC evaluation. Custom URL credentials and inherited +bypass rules remain effective, and failed resolution preserves the last committed snapshot. +Equal effective policies reuse the current pool. Replaced pools drain accepted requests while new +requests use the new policy. Kernel stop destroys current and draining pools, and awaits their +retirement before writer release. This ownership covers the global fetch dispatcher; separately +owned library/native transports keep their existing cancellation and retirement contracts. + +The default application opens its profile and assembles this root in the SDK worker through +private lifecycle requests. Business readiness remains false until startup and owner activation +are acknowledged. Default handoff requires real application and Native acceptance. Its +route dispatcher and import/rekey/repair controller use the same local data/Provider/runtime owners; +Main dispatches its fixed Native route maps locally and supplies presentation effects without +database-maintenance RPC. Business routes cross bounded Core ingress with the original sender +guard. Native settings-window lifecycle and system-resume events reach the same Core startup and +scheduler owners; the Native configuration projection writes only through exact typed commands. +Startup-tracked routes retain a distinct work identity for each request. Shared progress labels +and resource lanes cannot merge business results across arguments or caller identities. Unsent full Native startup-progress snapshots may supersede the previous snapshot for the same +target. Full delegation projections may supersede an older queued revision for the same parent/ +delegation identity; canonical business/CLI events retain their history. Presentation holds at +most 256 entries and 64 MiB, with one delivery receipt per accepted entry. Renderer publishers +retain no waiter per superseded snapshot. The 64-entry business ingress queue and existing Native +callback/control capacities remain unchanged. Each +accepted request executes its own authorization and handler under the maintenance/lifetime gate. +Native desktop methods come from a fixed capability table with validated arguments and event +payloads. Abort signals stay +local to each process; cancellation identifies the accepted effect and waits for actual settlement. +Completed cancellation identities are released after their original call settles. Renderer watches +share one Native subscription per renderer and retain registration and unsubscription receipts. +Native closure fences new effects and acknowledges physical retirement before profile release. An explicit Core +shutdown fence pauses credential/security mutations and execution before Main closes windows; +final profile release still waits for accepted Native effects. Activation queues public business +requests under the existing bounds until readiness is acknowledged. +The standalone harness includes its Node module closure, platform native packages and separately +bundled Skill scan worker; it must not rely on the development checkout's node_modules. + +Plugin route ownership queries await a typed Native result. Main's sender authorizer reads its +synchronous native window registry and accepts only a concrete owner identity; promise/object +results cannot establish renderer authority. Copilot credential-target selection, access validation +and commitment stay in Core. Native browser/device authorization returns its acquired credential, +and physical cancellation/closure must be acknowledged before default Core adoption. Each Copilot +login owns its window, timers and HTTP work. Core fences new logins and drains target reads, +authorization, access validation and accepted credential writes, including when sibling account +retirement fails. Native cancellation propagates into requests and waits for actual owned-window +closure and underlying response-stream cancellation. A destroyed frame's closure receipt retires +its local navigation/script effects when their replies are lost; external browser effects retain +their own acknowledgement. No timeout or abort flag substitutes for either receipt. +Device flows use independent operation state and serial polling with one expiry lifetime. Polling +honors the minimum interval and server slowdown from the +[GitHub device-flow contract](https://docs.github.com/en/apps/oauth-apps/building-oauth-apps/authorizing-oauth-apps#device-flow). +The temporary instruction page can copy only its current code or the fixed GitHub device URL and +can open only that URL. Browser callbacks require the configured endpoint and nonce; authorization +codes and nonce-bearing URLs are not logged. Both native windows retain context isolation and web +security. Existing native window presentation deadlines and transport limits remain unchanged. + +Accepted login-shell initialization belongs to runtime shutdown. Concurrent reads share its +process, stopping fences late environment/state refresh and timeout waits for actual process/stdio +closure. An asynchronous Native storage-availability check must settle before Sync token wrapping. +Optional Computer Use preview eligibility may await Native state without holding the completed +tool response; validate current policy and cancellation again before its private snapshot dispatch. + +Bootstrap values use Node V8 serialization, preserving `undefined`, dates and binary values. Values +up to 16 KiB stay inline in control frames. Larger values use authenticated AES-256-GCM carriers in +a generation-private 0700 temporary directory, with 0600 exclusive files, unpredictable identities, +direction/operation binding, a 64-MiB value limit and at most four retained files per sender. The +ephemeral key crosses only the private control link and is never written to disk or command-line +arguments. Carriers are first-party local IPC resources, not CLI artifact grants or renderer paths. +This bootstrap limit does not lower public route limits: legal larger history/tool results require +service-specific paging or transfer adaptation before public route cutover. It is not an Everywhere +network protocol. Core reports profile ownership separately from business readiness; opening the +profile alone cannot admit renderer/CLI business work. Stop must await actual initialization and +profile close. A timed-out or lost stop reply cannot authorize replacement of a live owner. + +The same private Core owner composes configuration, defaults and command-shell preferences. Its +configuration commands read/update validated public settings, return the allowlisted Native +snapshot or initialize desktop presentation. Main supplies a fixed configuration capability for +theme, locale, login, content protection, floating presentation, logging, font enumeration and +snapshot/event delivery; it receives no settings store. Settings writes await the Native projection +receipt before publishing ordinary events or acknowledging success. Failed projection delivery +does not roll back a committed setting or permit an automatic retry of that mutation. Configuration +commands serialize within the existing ordinary-call budget, and their events cross as a bounded +batch of at most 64 validated envelopes. Stop fences queued configuration work and awaits active +commands and Native effects before closing the profile. These administrative commands do not +establish complete Harness readiness or grant a renderer/CLI caller direct access to the link. + +Core obtains admission from the existing Rust/Rutis host over the SDK link, using the same typed +client and TS quota owner as Main. Main and the one Core generation have independent bounded +command sequences, while global capacity, per-owner fairness and permit accounting remain shared. +The host binds the Core caller row/key and generation, allows only existing admission methods and +reports its actual capacity/queue limits. Core requests stay within 64 KiB and 1,024 in-flight calls; +acquisition leaves sixteen slots for control work. Overflow retires Core admission authority and +fences new execution rather than skipping an uncertain command and continuing its sequence. +The bridge neither starts a second host nor forwards admission through Electron or file carriers. + +Core shutdown closes admission before releasing its profile. Physical Core loss closes the shared +quota, rejects queued work and prevents another grant while already accepted Native work may still +be settling. Unknown permits stay fenced until supervisor retirement; a live control/status path +does not authorize replacement. Internal retirement preserves the current observation policy, and +an explicit observation flag can use the supervisor's observer without a local duplicate callback. +A transport-error reply from a lost Core is an unknown outcome for a posted mutation, even if it +arrives before the runtime-loss notification. It must not be treated as a retryable definite failure. + +Core's lazy vector client uses the same Memory bootstrap and native worker through its SDK link. +The Rust host authenticates the Core row/key and generation and admits one Memory owner per host +generation: Main or Core. Another owner cannot start a second worker or take over the directory. +Core requests keep the existing 32 pending slots, eight reserved control slots, 64-KiB control and +256-KiB data bounds, staged 16-MiB uploads and 8-MiB native parts. Binary chunks use SDK byte values; +metadata must omit absent fields because SDK `undefined` becomes JSON null. This removes the Main +relay and file-carrier hop without claiming zero-copy transport or changing public payload limits. +Core-owned readiness and replies return on that SDK link; actual native death marks the client +unavailable through the host event bus. There is no automatic replay, replacement or restart. + +Core shutdown drains its vector stores and asks the host to retire Memory before closing admission +and releasing the profile. Retirement fences late startup and waits for accepted startup settlement +and actual child exit; an observation timeout cannot release ownership. Physical Core loss also +retires its Memory worker. A Main-owned generation survives Core retirement. Default business +composition uses Core; Main must not create a shadow writer or replay an unknown operation. A Core +connection loss or an internal helper/Browser/desktop/HTTP owner failure retires the Native owners +and ends that application generation. Internal failure uses a single component-only Native +notification through reserved capacity. Shutdown fences and bounded lifecycle event delivery +remain available after failure; business requests do not. Expected host exit after acknowledged +retirement must not be classified as a new failure. A terminal reset or +restart action requires acknowledged Core and physical Native retirement. The complete G5 writer, +route and lifecycle cutover still requires application, fault and signed-package acceptance. + +Frames and queues have finite byte/count budgets checked before allocation and parsing. Establish +production values from the route payload inventory and benchmark. Oversized legal results must +use existing paging/artifact capabilities or an explicitly bounded transfer contract; never +silently truncate a result or reduce an existing public limit. Cancellation and shutdown have +reserved capacity so progress traffic cannot block control messages. + +Keep Provider streaming, tool execution, context assembly, Tape writes, queue settlement, and +Memory coordination inside the backend. Forward UI-visible progress in bounded batches where +the existing event contract permits coalescing. Ordered facts, approvals, terminal state, and +errors cannot be coalesced away. Do not publish full transcript snapshots for every token or add +per-token service calls through the Rust host. + +Main fans backend events out through existing renderer and CLI projections. Streams carry a +generation and monotonic sequence. A gap or backend change triggers authoritative bounded +resynchronization; it does not create a second durable event store or infer success from a missing +reply. Main and renderer state remain projections of backend facts. + +## Product Hooks capability + +The selected G9 peripheral is product Hooks: its command children and queued Session enrichment +need a lifetime distinct from database maintenance and terminal Kernel retirement. Load a +first-party `deepchat-hooks` plugin beside Memory and Core in the existing Core worker. It owns +one HookService attachment, opened once with owner-local settings/query ports and the application +version. Do not serialize callbacks or add a process, shell implementation, supervisor, route, +configuration switch or remote execution grant. + +Maintenance keeps the existing temporary stop/drain/start contract. Plugin abort and Core shutdown +permanently retire the attachment, synchronously reject new work and prevent an old service's +`start()` from reviving it. Await queued enrichment and actual child closure before releasing +Store ownership; a drain deadline is a failure, not proof of process exit. Permanent retirement +also covers partial Kernel construction. The plugin's cleanup is the terminal owner; Kernel +shutdown delegates to it instead of manually managing the HookService lifetime. + +Existing notification payloads, subscriptions, FIFO within each Session, command redaction, +configuration/test routes and native shell behavior remain compatible. Compare the existing +actual application workload before and after the cutover, including persistence/restart and a +command whose descendant retains stdio during shutdown. Verify plugin unload with queued +enrichment, stale references and a fresh replacement generation. This extraction makes no +performance or independently hot-reloadable Core claim. + +## Everywhere application and execution boundaries + +### Ordinary Node Session Host + +The selected G11 distribution reuses the existing Rust/Rutis host and TS Core; its foreground +Node entry replaces Electron's Native host ports. Build it with `pnpm headless:build`, then copy +the entire `out/headless` directory to the same target platform/architecture. The target provides +Node >=24.22.1 <25; no Electron installation or running desktop is required. Core, its native +dependency closure, helper entry, CLI, catalogs and available license materials travel together. +This artifact is excluded from desktop packaging. + +Start `node index.mjs --profile ` with a stable `DEEPCHAT_HOST_KEY` containing a canonical +base64 32-byte master key. Credentials use versioned AES-256-GCM envelopes and separate HKDF usage +keys. The master key is supplied by the embedding environment, removed before child launch and +never written by the host; it is not an OS keychain claim. Preserve the key across restarts. +`DEEPCHAT_HOST_DATABASE_PASSWORD` optionally supplies an existing encrypted database's manual +unlock password. Database recovery cannot silently reset data. Desktop-wrapped credentials require +their original security adapter; sharing a profile does not convert its credential format. + +Use `DEEPCHAT_USER_DATA_DIR= node cli/deepchat.mjs ` for the same authenticated +UDS/named-pipe surface. The host retains authority after a CLI exits. Shutdown drains Core and owned +helpers before releasing the profile. Ordinary Node helpers reuse existing execution, Code Mode, +watcher and scheduler owners; a blocked Code VM cannot own its own IPC-death cleanup thread. +IPC and worker delivery are bounded. SIGINT/SIGTERM request graceful retirement where the OS +delivers those signals; forced Windows termination instead exercises owner loss and recovery. + +Sensitive CLI writes retain existing caller, scope, effect, argument binding, timeout and audit +checks. A host TTY can return one exact approval or denial through a terminal target; it cannot +claim a renderer identity. Request cancellation and timeout retire the native prompt before the +request closes. Without a TTY, operations requiring approval report unavailable. Desktop windows, +Browser/CUA and native presentation remain explicit unavailable capabilities on this host. +G11 remains under integration acceptance; this contract alone does not establish validated delivery. + +### Application clients + +Keep one application command/query/event contract with transport adapters. Desktop local uses +preload/IPC; a headless local CLI uses the existing UDS/named-pipe policy; remote web/CLI clients +use an explicitly enabled HTTPS application gateway and authenticated event stream. Reuse existing +bounded event/SSE patterns where appropriate instead of requiring another protocol for ordinary +chat interaction. No public endpoint is enabled by extracting the desktop backend. + +Remote exposure is an allowlist of application capabilities, not publication of every Electron +route or of the Rutis bus. Establish the authenticated client principal, profile/Session grants, +quotas, origin rules for browser access, credential expiry/revocation, and cancellation ownership +before dispatch. Network clients cannot claim a `webContentsId` or become an internal caller. +Existing local-control, sync-transfer, and native IPC credentials are not interchangeable tokens. + +The renderer bridge must be supplied by the selected client adapter rather than unconditionally +read from `window.deepchat`. Reuse domain clients/types and UI code only after auditing their +desktop assumptions. Capability discovery tells clients which operations the selected host and +device can perform. Native windows, shell APIs, file pickers, CUA permissions, and OAuth presentation +need an explicit supported adapter or an unavailable result; they cannot be simulated by a browser +transport. Network input receives full validation; trusted same-app validation shortcuts do not +automatically apply to remote events. + +Several authenticated clients may observe one Session. Submissions still pass through the same +backend admission/queue and permission owner. On disconnect, do not cancel an accepted Run solely +because one viewer disappeared. Cancel client-owned in-flight presentation/transfer requests +according to their contracts; explicit Run cancellation remains a separate authorized operation. +On reconnect, resume an ordered event cursor or read an authoritative bounded snapshot. Lost +replies retain the existing unknown-outcome and durable-identity rules. + +### Session authority and offline behavior + +Address a live Session by its authoritative Host and Session identity. One host owns its current +execution, queue, approvals, database mutation, and Tape lineage. Connecting another client does +not copy that authority. Keep the database on host-local storage; do not share a writable SQLite +file over a network filesystem or let browser clients write it. + +Existing LWW device sync remains a separate mechanism for portable durable records between +independent profiles. It does not replicate active permissions, pending execution, tool processes, +or provide distributed consensus. A thin client's event cache is a projection, not a writable sync +replica. Imported history never executes tools, and reconnect never imports a client history over +an active host Session. Preserve existing sync safe-application gates. + +Desktop-local operation continues offline. A client disconnected from a remote host can retain +its local view and unsent input, but cannot claim its task was accepted or executed remotely. +Reconnection requires explicit admission/deduplication of unsent work, not blind replay of a +transport queue. Starting a local offline Session creates its own authority; transferring an active +Session's ownership or merging two active executions is outside this design. + +### Device capabilities and locality + +An execution device is separately enrolled for a bounded capability set. Its registration identifies +the device, allowed services/workspaces, current connection generation, and availability. Reuse +existing pairing/transfer primitives where their contracts fit, but issue separate execution grants +with revocation; a paired sync device is not automatically an authorized executor. + +Keep a stable backend-owned capability directory/tool boundary and bind each admitted operation +to the selected device generation. The entire core must not inject every optional device service +as a required Rutis dependency: losing a laptop must not unload the Session Host. Individual feature +plugins and resource owners may depend on their required device service. Reuse existing tool/MCP +ownership patterns rather than making all business services dynamically discoverable. + +Workspace operations identify the enrolled device and registered workspace as well as a path +within its allowed root. A server path, phone attachment, and laptop project directory are not the +same resource. The authoritative host owns explicit uploads/artifacts; referenced local files stay +on their device unless an authorized transfer copies them. Do not infer file replication from a +synced absolute path. Keep local computation near its workspace/data and return bounded artifacts +or results; avoid remote calls for individual tokens, SQL statements, or internal loop functions. + +Native permission checks and execution constraints run on the device that performs the action. +An approval from a remote client cannot grant OS accessibility/screen-recording permission, unlock +another device's keychain, or make an offline workspace available. Provider credentials remain with +their chosen host; unrelated execution devices do not receive the profile's secret store. + +Prefer explicit direct links to the Session Host, including outbound device connections for NAT +where supported. Reuse Rutis peer/runtime transports with TLS for non-loopback links and explicit +service exports/imports. Rutis peer events travel one hop; the Session Host intentionally projects +application state to clients instead of relying on a transparent event mesh. + +Keep remote plugin loading (`host: true`) disabled for ordinary device enrollment. Rutis's static +token examples and reconnect are not a complete device identity/authorization system. An embedded +identity/revocation adapter must close the matching active connection and reject re-enrollment +with revoked credentials without restarting unrelated devices. Validate every operation against +the current grant and generation, including operations already queued when revocation arrives. + +A remote runtime disconnect unloads its rows; reconnect creates another runtime session and loads +them again. This is resource recovery, not continuation of its old function stack. Record remote +operation identity and settlement in the authoritative backend. A lost device reply can mean an +unknown side-effect outcome; query available durable evidence before an explicit retry. Device +loss marks the affected operation/capability unavailable and leaves unrelated Sessions usable. + +## Lifecycle, replacement, and recovery + +### Startup and readiness + +For the desktop-local milestone, create the desktop shell and private gateway first. Establish the protocol/build compatibility +handshake and desktop capabilities, then start the core runtime. The core opens/unlocks the +database, runs existing startup recovery, and publishes readiness only after required owners are +usable. Renderers may show the existing bootstrap/loading state before business readiness. + +Treat runtime process existence, plugin activation, database readiness, and model/tool availability +as distinct states. A missing required service is visible as blocked startup, with a bounded +diagnostic; it cannot leave a request waiting indefinitely. + +### Replacement boundaries + +The extracted Harness composition forms one core backend plugin. Admission and the bounded +vector-resource slice can move independently before that composition. Keep ordinary classes, +pure modules, and narrow ports inside their capability. Introduce additional plugin boundaries +only when they have independent startup/replacement/cleanup responsibilities and allow an old +manual lifecycle path to be removed. + +Optional feature runtimes may restart independently of active Sessions when their existing +contracts permit it. A core replacement first closes admission and settles or cancels active work +through the existing lifecycle owners. Do not hot-reload the whole core on an ordinary model or +settings update. A runtime's process is a shared failure domain for the plugins inside it. + +### Failure and restart + +On Core loss, Main fences old-generation calls and retires the desktop generation's Native +resources before exiting with failure. The default desktop does not automatically replace the +Core or leave a partially available window running. Pending transport calls distinguish unavailable +from unknown mutation outcomes. Never automatically retry a side-effecting request because its reply +was lost. Existing durable mutation identities and recovery records decide whether work committed. + +Before restarting, establish that the previous database owner and its owned descendants have +terminated. An unresponsive but live core is not a safe reason to create a second owner. Reuse +existing child-process identity checks and bounded termination. If ownership cannot be established, +refuse replacement with a diagnostic instead of opening the profile concurrently. + +Only the designated supervisor may apply bounded restart/backoff to a child whose contract permits +independent replacement; the default desktop Core requires application restart. A recovered Core +uses existing Tape, pending-input, interaction, plugin-hook, and delegation recovery contracts. +Uncertain hook/tool outcomes and held historical queue items are not automatically executed again. +Do not promise uninterrupted generation across a process crash. + +### Shutdown and database maintenance + +Native quit cancels pending and future startup unlock/recovery requests before awaiting startup +completion and resolving its shutdown owner, including the splash-close transition. Intentional +startup cancellation releases the profile without a startup-failure dialog. Update-install preflight +also waits for startup before retiring Core. An interactive window must not permit startup timing +to bypass the Core shutdown owner. + +Close new admission before cancelling or draining active work. Complete business settlement while +its storage is available, release owned tasks/connections, checkpoint/close the database, and then +stop the runtime and host. Apply bounded deadlines with explicit failure outcomes. Rutis invokes +registered cleanup; it does not implement business drain/fence semantics. + +Database maintenance uses the same ownership boundary: quiesce all writers and relevant helpers, +drain/fence Memory and Runs, perform the operation in the backend, reopen/reconcile, refresh +snapshots, and restore admission only after readiness. Preserve current rollback and failed-state +behavior. The four Agent fence semantics and deterministic pending-input feedback loop remain. + +Fence credential operations before awaiting Native configuration pause or Provider/ACP preparation. +Preparation recovery reopens credentials and admission only after Native resume acknowledges; +concurrent shutdown keeps the old generation fenced. An uncertain pause fails the owner rather than +restoring writes against an unacknowledged Native state. + +## Deployment and compatibility + +- Pin the compatible Rutis crate/SDK/runtime versions together; never fetch a runtime at app boot. +- Package the Rust host and first-party runtime closure as release-owned artifacts with integrity + verification. Apply existing macOS signing/notarization to the added executable and test the + packaged Electron launcher, its fuses, ASAR paths, and native modules. +- Preserve six Electron targets: macOS arm64/x64, Windows arm64/x64, and Linux arm64/x64. Upstream + `v0.16.0` provides no Windows ARM64 host binary. Building and exercising the application-owned + Rust host/runtime on that target is a release gate, not assumed support. +- Preserve the existing offline core startup and profile isolation. Optional language runtimes + continue to use explicit existing toolchain selection when a feature needs them. +- Validate headless artifacts independently with their declared Node/native dependencies, host-local + storage, credential backend, and service supervision. They must boot and operate without an + Electron installation; desktop packaging tests do not establish this property. +- Keep remote application ingress, Rutis device links, data sync, and local IPC as separately + authenticated surfaces. Remote deployment is explicitly configured and adds no automatic public + listener, tunnel, credential sharing, or hosted service requirement to desktop local mode. +- Keep public renderer/CLI contracts and stored data compatible. Update maintained contracts only + when their implementation actually crosses the new boundary. +- After successful cutover, remove the obsolete main business composition and duplicate lifecycle + paths. Do not ship two concurrently usable database owners or a permanent implementation switch. + +## Feasibility gates and acceptance + +| Gate | Required evidence | Current evidence | +| ------------------- | ------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- | +| Runtime launch | Rust host loads a compiled plugin through the upstream runner using packaged Electron, exchanges async calls, cancels, and shuts down | Default SDK Kernel, Memory capability and six-target packaged application checks pass at `bd64c2d53` | +| Platform extraction | OAuth, crypto, MCP Apps, helper launch, and desktop snapshots work through narrow ports without backend Electron-main imports | Default Native bindings/lifetimes and six-target package checks pass; original integrated foreground case passes locally | +| Storage ownership | One mutation authority, preserved transactions, scheduler claims, encryption, backup/rekey/import and maintenance recovery | Default profile/writer/routes/lifecycle run in Core; local exclusivity, maintenance and recovery checks pass | +| Failure behavior | Core/host loss, lost replies, stale callbacks, full queues, and shutdown cannot create duplicate execution or two profile owners | Core loss, shutdown/helper/Browser faults and profile exclusion pass at their recorded application/package scopes | +| Responsiveness | Repeated baseline/spike measurements show improvement beyond baseline variance under the same backend-heavy workload | Fixed-Mac sustained pair records input-paint/EventTiming and Main loop samples; lower Main footprint, higher total footprint/CPU; no speedup claim | +| Distribution | Offline, signed/packaged launch and cleanup work on every supported target | All six verification packages pass; signed/notarized and complete offline distribution remain release gates | + +Everywhere delivery has additional gates beyond desktop separation: + +| Horizon | Acceptance scenario | Current evidence | +| ------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------- | +| Headless host | Boot without Electron, submit/query/cancel through the local CLI, close all clients, and observe the host continue according to service policy | Required host-port and runtime boundaries designed; no headless build | +| Multiple clients | Desktop and phone browser observe one Session; a client reconnects without duplicate input, lost terminal state, or authority escalation | Current typed bridge seam identified; no remote application client | +| Device capabilities | A host performs an explicitly authorized workspace operation on one enrolled device; disconnect/revoke affects only that capability and prevents stale callbacks | Rutis remote-link behavior inspected; current sync pairing confers no execution grant | + +Use the existing desktop baseline for the first milestone. Later horizons also measure WAN event +lag, reconnect/resync cost, transfer bounds, device-operation settlement, and headless idle resource +cost. Do not present browser availability as proof of mobile native execution or data sync as proof +of live Session control. + +For an explicit performance adoption decision, use the same build, profile fixture, local Provider +fixture and machine for at least five paired baseline/spike runs. Other changes follow the affected +workload selection and bounded confirmation rules above. Measure idle and active workloads for the +selected comparison, including simultaneous Sessions and a +background scan. Record main event-loop delay p95/p99, renderer input-to-paint latency p95, startup +to business readiness, stream latency, aggregate and per-process RSS/CPU, IPC bytes/queue depth, and +restart-to-readiness. Reuse existing performance logging where applicable; never log request bodies, +credentials, tokens, SQL parameters, or arbitrary protocol frames. + +Select numeric release budgets after establishing the baseline and before judging the spike. A +result inside baseline variance is not a performance improvement. Report memory/startup costs +alongside responsiveness, and reject a topology that buys negligible isolation at disproportionate +cost. Renderer-local layout/rendering bottlenecks require their own work. + +## Remaining engineering questions + +1. Does the exact packaged Electron/Rutis runner combination preserve local transport, crypto, + cancellation, and native-module loading on all targets? Prove it before moving production data. +2. Which desktop configuration fields need bootstrap snapshots, and which route payloads need + bounded transfer? Resolve the inventory before defining the production wire budgets. +3. Does one core process provide sufficient isolation, or does measured background work justify + a separate feature runtime? Additional processes require measurement or an independent recovery + requirement. +4. Can the same core boot under the headless Node host with a real secret/configuration adapter + and no Electron imports? Prove this before promising unattended server deployment. +5. What exact application capabilities are exposed to remote clients and to each enrolled device? + Freeze the grant, resource-locality, and disconnect contracts before enabling network access. + +These are bounded technical investigations, not permission placeholders or assumed capabilities. + +## References + +- [Rutis v0.16.0 release](https://github.com/arcships/rutis/releases/tag/v0.16.0) +- [Rutis runtime launcher](https://github.com/arcships/rutis/blob/v0.16.0/crates/rutis-bridge/src/runtime/process.rs) +- [Rutis LocalRuntime](https://github.com/arcships/rutis/blob/v0.16.0/crates/rutis-bridge/src/runtime/local.rs) +- [Rutis local transport](https://github.com/arcships/rutis/blob/v0.16.0/crates/rutis-bridge/src/runtime/spawn.rs) +- [Rutis plugin lifecycle and boundary rules](https://github.com/arcships/rutis/blob/v0.16.0/docs/guide/plugin-api.en.md) +- [Rutis Rust services and plugin exports](https://github.com/arcships/rutis/blob/v0.16.0/docs/guide/rust-host.en.md) +- [Rutis nodes, remote runtimes, security and disconnect semantics](https://github.com/arcships/rutis/blob/v0.16.0/docs/guide/nodes.en.md) +- [Electron Node-mode environment variable](https://www.electronjs.org/docs/latest/api/environment-variables#electron_run_as_node) +- [Electron native modules](https://www.electronjs.org/docs/latest/tutorial/using-native-node-modules) +- [Electron process memory measurement](https://www.electronjs.org/docs/latest/api/process#processgetprocessmemoryinfo) +- [DuckDB Node client](https://duckdb.org/docs/stable/clients/node_neo/overview) +- [DuckDB array distance functions](https://duckdb.org/docs/stable/sql/functions/array) +- [DeepChat local control plane](../local-control-plane/spec.md) +- [DeepChat external plugin runtime lifecycle](../plugin-external-runtime-lifecycle/spec.md) +- [DeepChat Tape layering](../tape-layering/spec.md) +- [DeepChat structured logging](../main-process-structured-logging/spec.md) +- [DeepChat device data sync](../../features/cloudflare-tunnel-sync/spec.md) diff --git a/docs/guides/getting-started.md b/docs/guides/getting-started.md index 8a0f66abed..3cd74cab50 100644 --- a/docs/guides/getting-started.md +++ b/docs/guides/getting-started.md @@ -2,9 +2,13 @@ ## 环境 -- Node.js `>=24.18.0 <25` +- Node.js `>=24.22.1 <25` - pnpm `>=10.34.5 <11` - Git +- Rust `>=1.88` with Cargo, with the target matching the requested app architecture + +The dev/build scripts compile `crates/deepchat-harness` through `pnpm run harness:build`. +`pnpm run installRuntime` installs application toolchains; it does not install the Rust compiler. ```bash pnpm install @@ -24,8 +28,19 @@ Vue component / Pinia store -> owning main module ``` -main 进程只有一个 composition root:`src/main/app/composition.ts`。它创建模块、注入窄依赖、注册 -route、排定 start/stop,但不向业务代码提供模块查找入口。 +Main's composition root, `src/main/app/composition.ts`, connects Native adapters, fixed desktop +routes and the Core generation. Business modules receive explicit dependencies from the Core +composition root; neither root exposes a module lookup interface. + +The default Rust/Rutis host loads the first-party Core plugin in `src/harness/plugins/core.ts`. +`src/harness/core/kernel.ts` composes Harness, Provider, Memory, Session/Tape, scheduler and the +shared profile writer in that SDK worker. Main retains Native capabilities and authenticated +ingress through typed ports. See the [migration plan](../architecture/rutis-multiprocess-backend/plan.md) +for acceptance and remaining work; internal composition files do not each create a plugin/process. + +```text +Renderer <-> Main [Native ports] <-> Rust/Rutis <-> TS Core [Harness + profile writer] +``` ```text src/main/ @@ -64,18 +79,18 @@ src/main/ 常见任务入口: -| 任务 | 入口 | -| --- | --- | -| 发送、停止、交互回复 | `src/main/session/turn.ts`、`src/main/session/chatService.ts` | -| Session create/restore/delete | `src/main/session/` | -| DeepChat loop | `src/main/agent/deepchat/loop/`、`runtime/` | -| Direct ACP | `src/main/agent/acp/instance/`、`runtime/` | -| Provider/auth/model | `src/main/provider/` | -| Agent tools | `src/main/tool/agentTools/` | -| MCP | `src/main/mcp/` | -| Memory | `src/main/memory/`、`src/main/agent/deepchat/memory/` | -| import/export | `src/main/app/startupMigrations/`、`src/main/exporter/` | -| Renderer chat UI | `src/renderer/src/features/chat-page/ChatPage.vue`、`components/message/` | +| 任务 | 入口 | +| ----------------------------- | ------------------------------------------------------------------------- | +| 发送、停止、交互回复 | `src/main/session/turn.ts`、`src/main/session/chatService.ts` | +| Session create/restore/delete | `src/main/session/` | +| DeepChat loop | `src/main/agent/deepchat/loop/`、`runtime/` | +| Direct ACP | `src/main/agent/acp/instance/`、`runtime/` | +| Provider/auth/model | `src/main/provider/` | +| Agent tools | `src/main/tool/agentTools/` | +| MCP | `src/main/mcp/` | +| Memory | `src/main/memory/`、`src/main/agent/deepchat/memory/` | +| import/export | `src/main/app/startupMigrations/`、`src/main/exporter/` | +| Renderer chat UI | `src/renderer/src/features/chat-page/ChatPage.vue`、`components/message/` | ## 质量门 diff --git a/electron-builder.yml b/electron-builder.yml index 82d9d08295..23bf1f6509 100644 --- a/electron-builder.yml +++ b/electron-builder.yml @@ -3,6 +3,8 @@ productName: DeepChat directories: buildResources: build files: + - '!.deepchat/**' + - '!.amp/**' - '!**/.claude/*' - '!**/.github/*' - '!**/.cursor/*' @@ -15,6 +17,11 @@ files: - '!runtime/rtk/**' - '!out/main/lightOcrHelper.js' - '!out/cli/**' + - '!out/harness/**' + - '!out/headless/**' + - '!resources/model-db/**' + - '!resources/acp-registry/**' + - '!crates/**' - '!keys/*' - '!scripts/*' - '!.github/*' @@ -44,6 +51,18 @@ asarUnpack: - '**/node_modules/@arcships/light-ocr*/**/*' - '**/node_modules/@zerob13/nativekit/prebuilds/**/*' extraResources: + - from: ./resources/acp-registry/ + to: app.asar.unpacked/resources/acp-registry + filter: ['registry.json', 'icons/**/*'] + - from: ./resources/model-db/ + to: app.asar.unpacked/resources/model-db + filter: ['providers.json'] + - from: ./out/harness/ + to: app.asar.unpacked/harness + filter: ['**/*', '!node_modules/**'] + - from: ./out/harness/node_modules/ + to: app.asar.unpacked/node_modules + filter: ['**/*'] - from: ./out/cli/ to: app.asar.unpacked/cli filter: ['deepchat', 'deepchat.cmd', 'deepchat.mjs'] @@ -96,6 +115,7 @@ nsis: include: build/nsis-installer.nsh afterSign: scripts/notarize.js artifactBuildCompleted: scripts/notarize-dmg.js +beforePack: scripts/build-harness.mjs afterPack: scripts/afterPack.js mac: signIgnore: diff --git a/package.json b/package.json index 5f675f6215..73924dc818 100644 --- a/package.json +++ b/package.json @@ -13,18 +13,18 @@ "scripts": { "prebuild": "node scripts/generate-icon-collections.mjs && node scripts/fetch-provider-db.mjs && node scripts/fetch-acp-registry.mjs", "preinstall": "npx only-allow pnpm", - "test": "vitest run", - "test:main": "vitest run --config vitest.config.ts test/main", + "test": "pnpm run harness:build && vitest run", + "test:main": "pnpm run harness:build && vitest run --config vitest.config.ts test/main", "test:memory:scope": "node scripts/check-memory-test-scope.mjs", "test:memory:type": "node scripts/typecheck-memory-tests.mjs", "test:memory": "pnpm run test:memory:scope && pnpm run test:memory:type && vitest --config vitest.config.memory.ts --run", "test:memory:eval": "vitest --config vitest.config.memory-eval.ts --run", - "test:main:memory-perf": "vitest --config vitest.config.memory-perf.ts --run", + "test:main:memory-perf": "pnpm run harness:build && cross-env ELECTRON_RUN_AS_NODE=1 electron node_modules/vitest/vitest.mjs --config vitest.config.memory-perf.ts --run", "test:agent:eval": "vitest run --config vitest.config.ts test/main/evals/nativeAgent/nativeAgentBehavior.eval.test.ts", "test:renderer": "vitest run --config vitest.config.renderer.ts test/renderer", - "test:coverage": "vitest run --coverage", - "test:watch": "vitest --watch", - "test:ui": "vitest --ui", + "test:coverage": "pnpm run harness:build && vitest run --coverage", + "test:watch": "pnpm run harness:build && vitest --watch", + "test:ui": "pnpm run harness:build && vitest --ui", "e2e:smoke": "playwright test -c test/e2e/playwright.config.ts", "e2e:smoke:ci": "playwright test -c test/e2e/playwright.ci.config.ts", "format:check": "oxfmt --check .", @@ -41,12 +41,15 @@ "typecheck:web": "vue-tsc --noEmit -p tsconfig.app.json --composite false --incremental --tsBuildInfoFile node_modules/.cache/tsc-web.tsbuildinfo", "typecheck": "pnpm run typecheck:node && pnpm run typecheck:web", "start": "electron-vite preview", - "dev": "electron-vite dev --watch", - "dev:trace": "cross-env DEEPCHAT_VUE_DEVTOOLS_OVERLAY=0 electron-vite dev --watch", - "dev:inspect": "electron-vite dev --watch --inspect=9229", - "dev:linux": "electron-vite dev --watch --noSandbox", - "build": "pnpm run typecheck && electron-vite build && pnpm run cli:build", + "dev": "pnpm run harness:build && electron-vite dev --watch", + "dev:trace": "pnpm run harness:build && cross-env DEEPCHAT_VUE_DEVTOOLS_OVERLAY=0 electron-vite dev --watch", + "dev:inspect": "pnpm run harness:build && electron-vite dev --watch --inspect=9229", + "dev:linux": "pnpm run harness:build && electron-vite dev --watch --noSandbox", + "build": "pnpm run typecheck && pnpm run harness:build && electron-vite build && pnpm run cli:build", "cli:build": "node scripts/build-cli.mjs", + "harness:build": "node scripts/build-harness.mjs", + "headless:build": "node scripts/build-headless.mjs", + "smoke:headless": "node scripts/smoke-headless.mjs", "release:ff": "node scripts/release-fast-forward.mjs", "postinstall": "install-electron && electron-builder install-app-deps", "hooks:install": "git config core.hooksPath .githooks", @@ -93,6 +96,7 @@ "installRuntime:duckdb:vss:linux:x64": "node scripts/installVss.js --platform linux --arch x64", "installRuntime:duckdb:vss:linux:arm64": "node scripts/installVss.js --platform linux --arch arm64", "smoke:duckdb:vss": "node scripts/smoke-duckdb-vss.js", + "smoke:harness:memory": "node scripts/smoke-harness-memory.mjs", "smoke:light-ocr": "node scripts/smoke-light-ocr.js", "smoke:opendal:native": "node scripts/smoke-opendal-native.js", "i18n": "pnpm run i18n:validate && i18n-check -s zh-CN -f i18next --locales src/renderer/src/i18n", @@ -117,6 +121,7 @@ "@aws-sdk/client-bedrock": "^3.1128.0", "@aws-sdk/credential-providers": "^3.1128.0", "@duckdb/node-api": "1.5.5-r.4", + "@duckdb/node-bindings": "1.5.5-r.4", "@electron-toolkit/utils": "^4.0.0", "@ff-labs/fff-node": "^0.10.6", "@jxa/run": "^1.4.0", @@ -132,6 +137,7 @@ "axios": "^1.20.0", "better-sqlite3-multiple-ciphers": "13.0.3", "compare-versions": "^6.1.1", + "conf": "15.1.0", "cron-parser": "^5.10.0", "cross-spawn": "^7.0.6", "diff": "^9.0.0", @@ -165,6 +171,8 @@ }, "devDependencies": { "@antv/infographic": "^0.2.20", + "@arcships/rutis": "0.16.0", + "@arcships/rutis-runtime": "0.16.0", "@commitlint/cli": "^21.2.2", "@commitlint/config-conventional": "^21.2.2", "@electron-toolkit/tsconfig": "^2.0.0", @@ -286,7 +294,13 @@ "vue-demi" ], "patchedDependencies": { - "@electron/notarize@3.1.1": "patches/@electron__notarize@3.1.1.patch" + "@electron/notarize@3.1.1": "patches/@electron__notarize@3.1.1.patch", + "@ai-sdk/openai-compatible@3.0.45": "patches/@ai-sdk__openai-compatible@3.0.45.patch", + "@ai-sdk/openai@4.0.62": "patches/@ai-sdk__openai@4.0.62.patch", + "@ai-sdk/google@4.0.65": "patches/@ai-sdk__google@4.0.65.patch", + "@ai-sdk/google-vertex@5.0.77": "patches/@ai-sdk__google-vertex@5.0.77.patch", + "@arcships/rutis-runtime@0.16.0": "patches/@arcships__rutis-runtime@0.16.0.patch", + "undici@8.12.3": "patches/undici@8.12.3.patch" } } } diff --git a/patches/@ai-sdk__google-vertex@5.0.77.patch b/patches/@ai-sdk__google-vertex@5.0.77.patch new file mode 100644 index 0000000000..ec94fad06a --- /dev/null +++ b/patches/@ai-sdk__google-vertex@5.0.77.patch @@ -0,0 +1,122 @@ +diff --git a/dist/edge/index.js b/dist/edge/index.js +index d9ab3f4a1ac8e4114a9057dd725ad6371fd740bf..4f8d63f6cc8a6f545201d3d436abf2b8a13b7382 100644 +--- a/dist/edge/index.js ++++ b/dist/edge/index.js +@@ -35,6 +35,15 @@ import { + WORKFLOW_DESERIALIZE + } from "@ai-sdk/provider-utils"; + import { z as z3 } from "zod/v4"; ++// Validate and clone dense vectors without per-component parsing payloads. ++const finiteEmbeddingArraySchema = z3.custom((value) => { ++ if (!Array.isArray(value)) return false; ++ for (let index = 0; index < value.length; index++) { ++ if (!Number.isFinite(value[index])) return false; ++ } ++ return true; ++}).transform((value) => value.slice()); ++ + + // src/google-vertex-error.ts + import { createJsonErrorResponseHandler } from "@ai-sdk/provider-utils"; +@@ -236,7 +245,7 @@ var googleVertexTextEmbeddingResponseSchema = z3.object({ + predictions: z3.array( + z3.object({ + embeddings: z3.object({ +- values: z3.array(z3.number()), ++ values: finiteEmbeddingArraySchema, + statistics: z3.object({ + token_count: z3.number() + }) +@@ -246,7 +255,7 @@ var googleVertexTextEmbeddingResponseSchema = z3.object({ + }); + var googleVertexEmbedContentResponseSchema = z3.object({ + embedding: z3.object({ +- values: z3.array(z3.number()) ++ values: finiteEmbeddingArraySchema + }), + usageMetadata: z3.object({ + promptTokenCount: z3.number().nullish() +diff --git a/dist/index.js b/dist/index.js +index 58ba8ad2d175fe8cedd8f7f52ab39ff64f83f818..e055b0ede020cb217dcdea4ed40247bdb6d03be2 100644 +--- a/dist/index.js ++++ b/dist/index.js +@@ -50,6 +50,15 @@ import { + WORKFLOW_DESERIALIZE + } from "@ai-sdk/provider-utils"; + import { z as z3 } from "zod/v4"; ++// Validate and clone dense vectors without per-component parsing payloads. ++const finiteEmbeddingArraySchema = z3.custom((value) => { ++ if (!Array.isArray(value)) return false; ++ for (let index = 0; index < value.length; index++) { ++ if (!Number.isFinite(value[index])) return false; ++ } ++ return true; ++}).transform((value) => value.slice()); ++ + + // src/google-vertex-error.ts + import { createJsonErrorResponseHandler } from "@ai-sdk/provider-utils"; +@@ -251,7 +260,7 @@ var googleVertexTextEmbeddingResponseSchema = z3.object({ + predictions: z3.array( + z3.object({ + embeddings: z3.object({ +- values: z3.array(z3.number()), ++ values: finiteEmbeddingArraySchema, + statistics: z3.object({ + token_count: z3.number() + }) +@@ -261,7 +270,7 @@ var googleVertexTextEmbeddingResponseSchema = z3.object({ + }); + var googleVertexEmbedContentResponseSchema = z3.object({ + embedding: z3.object({ +- values: z3.array(z3.number()) ++ values: finiteEmbeddingArraySchema + }), + usageMetadata: z3.object({ + promptTokenCount: z3.number().nullish() +diff --git a/src/google-vertex-embedding-model.ts b/src/google-vertex-embedding-model.ts +index 1b606de29a4fa91575ec8b82f895a1ca3fab37fe..3a3fc77c90da7395368dd456126c7f74793634dd 100644 +--- a/src/google-vertex-embedding-model.ts ++++ b/src/google-vertex-embedding-model.ts +@@ -13,6 +13,7 @@ import { + WORKFLOW_DESERIALIZE, + } from '@ai-sdk/provider-utils'; + import { z } from 'zod/v4'; ++ + import { googleVertexFailedResponseHandler } from './google-vertex-error'; + import { + googleVertexEmbeddingModelOptions, +@@ -20,6 +21,15 @@ import { + } from './google-vertex-embedding-model-options'; + import type { GoogleVertexConfig } from './google-vertex-config'; + ++// Validate and clone dense vectors without per-component parsing payloads. ++const finiteEmbeddingArraySchema = z.custom((value) => { ++ if (!Array.isArray(value)) return false; ++ for (let index = 0; index < value.length; index++) { ++ if (!Number.isFinite(value[index])) return false; ++ } ++ return true; ++}).transform((value) => value.slice()); ++ + export class GoogleVertexEmbeddingModel implements EmbeddingModelV4 { + readonly specificationVersion = 'v4'; + readonly modelId: GoogleVertexEmbeddingModelId; +@@ -191,7 +201,7 @@ const googleVertexTextEmbeddingResponseSchema = z.object({ + predictions: z.array( + z.object({ + embeddings: z.object({ +- values: z.array(z.number()), ++ values: finiteEmbeddingArraySchema, + statistics: z.object({ + token_count: z.number(), + }), +@@ -202,7 +212,7 @@ const googleVertexTextEmbeddingResponseSchema = z.object({ + + const googleVertexEmbedContentResponseSchema = z.object({ + embedding: z.object({ +- values: z.array(z.number()), ++ values: finiteEmbeddingArraySchema, + }), + usageMetadata: z + .object({ diff --git a/patches/@ai-sdk__google@4.0.65.patch b/patches/@ai-sdk__google@4.0.65.patch new file mode 100644 index 0000000000..5230d58139 --- /dev/null +++ b/patches/@ai-sdk__google@4.0.65.patch @@ -0,0 +1,81 @@ +diff --git a/dist/index.js b/dist/index.js +index d4b1ce08e2a40e383bacce99cf4d26059533101a..0f37e09da67bbda26b95de782220ca3c72787a92 100644 +--- a/dist/index.js ++++ b/dist/index.js +@@ -26,6 +26,15 @@ import { + zodSchema as zodSchema3 + } from "@ai-sdk/provider-utils"; + import { z as z3 } from "zod/v4"; ++// Validate and clone dense vectors without per-component parsing payloads. ++const finiteEmbeddingArraySchema = z3.custom((value) => { ++ if (!Array.isArray(value)) return false; ++ for (let index = 0; index < value.length; index++) { ++ if (!Number.isFinite(value[index])) return false; ++ } ++ return true; ++}).transform((value) => value.slice()); ++ + + // src/google-error.ts + import { +@@ -239,14 +248,14 @@ var GoogleEmbeddingModel = class _GoogleEmbeddingModel { + var googleGenerativeAITextEmbeddingResponseSchema = lazySchema3( + () => zodSchema3( + z3.object({ +- embeddings: z3.array(z3.object({ values: z3.array(z3.number()) })) ++ embeddings: z3.array(z3.object({ values: finiteEmbeddingArraySchema })) + }) + ) + ); + var googleGenerativeAISingleEmbeddingResponseSchema = lazySchema3( + () => zodSchema3( + z3.object({ +- embedding: z3.object({ values: z3.array(z3.number()) }) ++ embedding: z3.object({ values: finiteEmbeddingArraySchema }) + }) + ) + ); +diff --git a/src/google-embedding-model.ts b/src/google-embedding-model.ts +index cb1afa7aa98bd3a89974aafd7ed7393345ac822e..02a5dd943c007ed612af70d5d029d132d1e468ce 100644 +--- a/src/google-embedding-model.ts ++++ b/src/google-embedding-model.ts +@@ -16,11 +16,21 @@ import { + type FetchFunction, + } from '@ai-sdk/provider-utils'; + import { z } from 'zod/v4'; ++ + import { googleFailedResponseHandler } from './google-error'; + import { + googleEmbeddingModelOptions, + type GoogleEmbeddingModelId, + } from './google-embedding-model-options'; ++ ++// Validate and clone dense vectors without per-component parsing payloads. ++const finiteEmbeddingArraySchema = z.custom((value) => { ++ if (!Array.isArray(value)) return false; ++ for (let index = 0; index < value.length; index++) { ++ if (!Number.isFinite(value[index])) return false; ++ } ++ return true; ++}).transform((value) => value.slice()); + type GoogleEmbeddingConfig = { + provider: string; + baseURL: string; +@@ -186,7 +196,7 @@ export class GoogleEmbeddingModel implements EmbeddingModelV4 { + const googleGenerativeAITextEmbeddingResponseSchema = lazySchema(() => + zodSchema( + z.object({ +- embeddings: z.array(z.object({ values: z.array(z.number()) })), ++ embeddings: z.array(z.object({ values: finiteEmbeddingArraySchema })), + }), + ), + ); +@@ -195,7 +205,7 @@ const googleGenerativeAITextEmbeddingResponseSchema = lazySchema(() => + const googleGenerativeAISingleEmbeddingResponseSchema = lazySchema(() => + zodSchema( + z.object({ +- embedding: z.object({ values: z.array(z.number()) }), ++ embedding: z.object({ values: finiteEmbeddingArraySchema }), + }), + ), + ); diff --git a/patches/@ai-sdk__openai-compatible@3.0.45.patch b/patches/@ai-sdk__openai-compatible@3.0.45.patch new file mode 100644 index 0000000000..f37d855799 --- /dev/null +++ b/patches/@ai-sdk__openai-compatible@3.0.45.patch @@ -0,0 +1,66 @@ +diff --git a/dist/index.js b/dist/index.js +index fa70e9b9a5d6c3510c19eea248ec9d5b8a6edbeb..44184aec6ee6548824f53b8e45cb778636d9933f 100644 +--- a/dist/index.js ++++ b/dist/index.js +@@ -1509,6 +1509,15 @@ import { + WORKFLOW_DESERIALIZE as WORKFLOW_DESERIALIZE3 + } from "@ai-sdk/provider-utils"; + import { z as z7 } from "zod/v4"; ++// Validate and clone dense vectors without per-component parsing payloads. ++const finiteEmbeddingArraySchema = z7.custom((value) => { ++ if (!Array.isArray(value)) return false; ++ for (let index = 0; index < value.length; index++) { ++ if (!Number.isFinite(value[index])) return false; ++ } ++ return true; ++}).transform((value) => value.slice()); ++ + + // src/embedding/openai-compatible-embedding-model-options.ts + import { z as z6 } from "zod/v4"; +@@ -1637,7 +1646,7 @@ var OpenAICompatibleEmbeddingModel = class _OpenAICompatibleEmbeddingModel { + } + }; + var openaiTextEmbeddingResponseSchema = z7.object({ +- data: z7.array(z7.object({ embedding: z7.array(z7.number()) })), ++ data: z7.array(z7.object({ embedding: finiteEmbeddingArraySchema })), + usage: z7.object({ prompt_tokens: z7.number() }).nullish(), + providerMetadata: z7.record(z7.string(), z7.record(z7.string(), z7.any())).optional() + }); +diff --git a/src/embedding/openai-compatible-embedding-model.ts b/src/embedding/openai-compatible-embedding-model.ts +index 3c628ad3af95222416cd67797deeea4773dc82bb..67049886a54d3fc494ff1dabe05e7f5f8ad93cfd 100644 +--- a/src/embedding/openai-compatible-embedding-model.ts ++++ b/src/embedding/openai-compatible-embedding-model.ts +@@ -15,6 +15,7 @@ import { + type FetchFunction, + } from '@ai-sdk/provider-utils'; + import { z } from 'zod/v4'; ++ + import { + openaiCompatibleEmbeddingModelOptions, + type OpenAICompatibleEmbeddingModelId, +@@ -25,6 +26,15 @@ import { + } from '../openai-compatible-error'; + import { warnIfDeprecatedProviderOptionsKey } from '../utils/to-camel-case'; + ++// Validate and clone dense vectors without per-component parsing payloads. ++const finiteEmbeddingArraySchema = z.custom((value) => { ++ if (!Array.isArray(value)) return false; ++ for (let index = 0; index < value.length; index++) { ++ if (!Number.isFinite(value[index])) return false; ++ } ++ return true; ++}).transform((value) => value.slice()); ++ + type OpenAICompatibleEmbeddingConfig = { + /** + * Override the maximum number of embeddings per call. +@@ -184,7 +194,7 @@ export class OpenAICompatibleEmbeddingModel implements EmbeddingModelV4 { + // minimal version of the schema, focussed on what is needed for the implementation + // this approach limits breakages when the API changes and increases efficiency + const openaiTextEmbeddingResponseSchema = z.object({ +- data: z.array(z.object({ embedding: z.array(z.number()) })), ++ data: z.array(z.object({ embedding: finiteEmbeddingArraySchema })), + usage: z.object({ prompt_tokens: z.number() }).nullish(), + providerMetadata: z + .record(z.string(), z.record(z.string(), z.any())) diff --git a/patches/@ai-sdk__openai@4.0.62.patch b/patches/@ai-sdk__openai@4.0.62.patch new file mode 100644 index 0000000000..de31bde5d4 --- /dev/null +++ b/patches/@ai-sdk__openai@4.0.62.patch @@ -0,0 +1,78 @@ +diff --git a/dist/index.js b/dist/index.js +index 5d2d710459e9b193a7fdcb43e50c4e3b086460d9..bbf1c5b15f0975e654737779564d092f4a0d93fb 100644 +--- a/dist/index.js ++++ b/dist/index.js +@@ -1980,10 +1980,19 @@ var openaiEmbeddingModelOptions = lazySchema5( + // src/embedding/openai-embedding-api.ts + import { lazySchema as lazySchema6, zodSchema as zodSchema6 } from "@ai-sdk/provider-utils"; + import { z as z7 } from "zod/v4"; ++// Validate and clone dense vectors without per-component parsing payloads. ++const finiteEmbeddingArraySchema = z7.custom((value) => { ++ if (!Array.isArray(value)) return false; ++ for (let index = 0; index < value.length; index++) { ++ if (!Number.isFinite(value[index])) return false; ++ } ++ return true; ++}).transform((value) => value.slice()); ++ + var openaiTextEmbeddingResponseSchema = lazySchema6( + () => zodSchema6( + z7.object({ +- data: z7.array(z7.object({ embedding: z7.array(z7.number()) })), ++ data: z7.array(z7.object({ embedding: finiteEmbeddingArraySchema })), + usage: z7.object({ prompt_tokens: z7.number() }).nullish() + }) + ) +diff --git a/dist/internal/index.js b/dist/internal/index.js +index a4084305fc47da60128b263311da7a7de84d5b63..d1ee9e78c72f64d669b01e82cf7b1fa86d18510f 100644 +--- a/dist/internal/index.js ++++ b/dist/internal/index.js +@@ -1971,10 +1971,19 @@ var openaiEmbeddingModelOptions = lazySchema5( + // src/embedding/openai-embedding-api.ts + import { lazySchema as lazySchema6, zodSchema as zodSchema6 } from "@ai-sdk/provider-utils"; + import { z as z7 } from "zod/v4"; ++// Validate and clone dense vectors without per-component parsing payloads. ++const finiteEmbeddingArraySchema = z7.custom((value) => { ++ if (!Array.isArray(value)) return false; ++ for (let index = 0; index < value.length; index++) { ++ if (!Number.isFinite(value[index])) return false; ++ } ++ return true; ++}).transform((value) => value.slice()); ++ + var openaiTextEmbeddingResponseSchema = lazySchema6( + () => zodSchema6( + z7.object({ +- data: z7.array(z7.object({ embedding: z7.array(z7.number()) })), ++ data: z7.array(z7.object({ embedding: finiteEmbeddingArraySchema })), + usage: z7.object({ prompt_tokens: z7.number() }).nullish() + }) + ) +diff --git a/src/embedding/openai-embedding-api.ts b/src/embedding/openai-embedding-api.ts +index 87ab6c10674dadb705a0093e32a2315203fa91e7..6174ba2091ab9a8591d58a75df5cf7c81fc02866 100644 +--- a/src/embedding/openai-embedding-api.ts ++++ b/src/embedding/openai-embedding-api.ts +@@ -1,12 +1,22 @@ + import { lazySchema, zodSchema } from '@ai-sdk/provider-utils'; + import { z } from 'zod/v4'; + ++// Validate and clone dense vectors without per-component parsing payloads. ++const finiteEmbeddingArraySchema = z.custom((value) => { ++ if (!Array.isArray(value)) return false; ++ for (let index = 0; index < value.length; index++) { ++ if (!Number.isFinite(value[index])) return false; ++ } ++ return true; ++}).transform((value) => value.slice()); ++ ++ + // minimal version of the schema, focussed on what is needed for the implementation + // this approach limits breakages when the API changes and increases efficiency + export const openaiTextEmbeddingResponseSchema = lazySchema(() => + zodSchema( + z.object({ +- data: z.array(z.object({ embedding: z.array(z.number()) })), ++ data: z.array(z.object({ embedding: finiteEmbeddingArraySchema })), + usage: z.object({ prompt_tokens: z.number() }).nullish(), + }), + ), diff --git a/patches/@arcships__rutis-runtime@0.16.0.patch b/patches/@arcships__rutis-runtime@0.16.0.patch new file mode 100644 index 0000000000..ed1ba2e036 --- /dev/null +++ b/patches/@arcships__rutis-runtime@0.16.0.patch @@ -0,0 +1,33 @@ +diff --git a/src/values.mjs b/src/values.mjs +index 4a1a5f56cba813067f410cb76c0eac4822626578..f6bc86e10033daa1cfb3f87ab3a28c2671da46b4 100644 +--- a/src/values.mjs ++++ b/src/values.mjs +@@ -130,8 +130,6 @@ export function instantText(date) { + + // ── Base64 (RFC 4648 §4, padded) ───────────────────────────────────── + +-const ALPHABET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/' +- + // The bytes of a Uint8Array, ArrayBuffer or DataView (a Buffer is a + // Uint8Array); undefined for anything else. + export function bytesOf(value) { +@@ -148,12 +146,13 @@ export function base64Encode(bytes) { + // The bytes of padded base64 text. Only the canonical text is taken: no + // whitespace, padding only at the end, unused bits zero. + export function base64Decode(text) { +- const padding = text.endsWith('==') ? 2 : text.endsWith('=') ? 1 : 0 +- const valid = text.length % 4 === 0 && /^[A-Za-z0-9+/]*={0,2}$/.test(text) +- // The bits of the last character before the padding that no byte uses. +- && (padding === 0 || (ALPHABET.indexOf(text[text.length - padding - 1]) & (padding === 2 ? 15 : 3)) === 0) +- if (!valid) throw new ValueError(`invalid bytes (${text.length} characters): expected base64 (RFC 4648 §4) with padding`) +- return new Uint8Array(Buffer.from(text, 'base64')) ++ const bytes = text.length % 4 === 0 ? Buffer.from(text, 'base64') : undefined ++ if (!bytes || bytes.toString('base64') !== text) throw new ValueError(`invalid bytes (${text.length} characters): expected base64 (RFC 4648 §4) with padding`) ++ // Pooled/partial buffers must not expose bytes outside the decoded value. ++ if (bytes.byteLength > 0 && bytes.byteOffset === 0 && bytes.byteLength === bytes.buffer.byteLength) ++ return new Uint8Array(bytes.buffer) ++ return new Uint8Array(bytes) ++ + } + + // ── What crosses as a tagged value ─────────────────────────────────── diff --git a/patches/undici@8.12.3.patch b/patches/undici@8.12.3.patch new file mode 100644 index 0000000000..93eea078d3 --- /dev/null +++ b/patches/undici@8.12.3.patch @@ -0,0 +1,15 @@ +diff --git a/lib/dispatcher/agent.js b/lib/dispatcher/agent.js +index 7f95e308700abe28dc2cb50150dc81f5461ca81b..37b1c671feb94cd558439f2c683509a452a5d398 100644 +--- a/lib/dispatcher/agent.js ++++ b/lib/dispatcher/agent.js +@@ -150,9 +150,7 @@ class Agent extends DispatcherBase { + for (const dispatcher of this[kClients].values()) { + closePromises.push(dispatcher.close()) + } +- this[kClients].clear() +- +- return Promise.all(closePromises) ++ return Promise.all(closePromises).then(() => this[kClients].clear()) + } + + [kDestroy] (err) { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b97f8e7348..535d1cc0f9 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -22,9 +22,27 @@ overrides: typescript: npm:typescript-native-bridge@6.0.3-bridge.16.tsgo.7.0.2 patchedDependencies: + '@ai-sdk/google-vertex@5.0.77': + hash: 42c1f1bdd677dd0c113abd81be9b17092ddafc7e6547f9004798b8ba6e04715a + path: patches/@ai-sdk__google-vertex@5.0.77.patch + '@ai-sdk/google@4.0.65': + hash: b8f1463345250c901d6c568ef8f18b1a084860495af5faaa13c93cee5607268f + path: patches/@ai-sdk__google@4.0.65.patch + '@ai-sdk/openai-compatible@3.0.45': + hash: 71a48986b4e5f640033a57b993be567406a112254c67de27e14d07e1ffbe73a8 + path: patches/@ai-sdk__openai-compatible@3.0.45.patch + '@ai-sdk/openai@4.0.62': + hash: d540f71958d27ae3c5f065c84b9fdb4d6714c067952674016ce2a0adc5dce783 + path: patches/@ai-sdk__openai@4.0.62.patch + '@arcships/rutis-runtime@0.16.0': + hash: 118a5067c0fb8b1a69ee71008145221773dd67f0722f44a1b71ad5b16113edb1 + path: patches/@arcships__rutis-runtime@0.16.0.patch '@electron/notarize@3.1.1': hash: b2e4a3334f86d72808116962914a4a2062da8c99b2d5cb86e67b73eb5ce3ab87 path: patches/@electron__notarize@3.1.1.patch + undici@8.12.3: + hash: 3b4133bd9786ff080c4fa0de1272123dd029e8c87da0ac32e891e285d6265936 + path: patches/undici@8.12.3.patch importers: @@ -44,19 +62,19 @@ importers: version: 4.0.65(zod@4.6.7) '@ai-sdk/google': specifier: ^4.0.65 - version: 4.0.65(zod@4.6.7) + version: 4.0.65(patch_hash=b8f1463345250c901d6c568ef8f18b1a084860495af5faaa13c93cee5607268f)(zod@4.6.7) '@ai-sdk/google-vertex': specifier: ^5.0.77 - version: 5.0.77(zod@4.6.7) + version: 5.0.77(patch_hash=42c1f1bdd677dd0c113abd81be9b17092ddafc7e6547f9004798b8ba6e04715a)(zod@4.6.7) '@ai-sdk/open-responses': specifier: ^2.0.40 version: 2.0.40(zod@4.6.7) '@ai-sdk/openai': specifier: ^4.0.62 - version: 4.0.62(zod@4.6.7) + version: 4.0.62(patch_hash=d540f71958d27ae3c5f065c84b9fdb4d6714c067952674016ce2a0adc5dce783)(zod@4.6.7) '@ai-sdk/openai-compatible': specifier: ^3.0.45 - version: 3.0.45(zod@4.6.7) + version: 3.0.45(patch_hash=71a48986b4e5f640033a57b993be567406a112254c67de27e14d07e1ffbe73a8)(zod@4.6.7) '@ai-sdk/provider': specifier: ^4.0.11 version: 4.0.11 @@ -72,6 +90,9 @@ importers: '@duckdb/node-api': specifier: 1.5.5-r.4 version: 1.5.5-r.4 + '@duckdb/node-bindings': + specifier: 1.5.5-r.4 + version: 1.5.5-r.4 '@electron-toolkit/utils': specifier: ^4.0.0 version: 4.0.0(electron@43.8.3) @@ -117,6 +138,9 @@ importers: compare-versions: specifier: ^6.1.1 version: 6.1.1 + conf: + specifier: 15.1.0 + version: 15.1.0 cron-parser: specifier: ^5.10.0 version: 5.10.0 @@ -197,7 +221,7 @@ importers: version: 7.2.4 undici: specifier: ^8.12.2 - version: 8.12.3 + version: 8.12.3(patch_hash=3b4133bd9786ff080c4fa0de1272123dd029e8c87da0ac32e891e285d6265936) xlsx: specifier: https://cdn.sheetjs.com/xlsx-0.20.3/xlsx-0.20.3.tgz version: https://cdn.sheetjs.com/xlsx-0.20.3/xlsx-0.20.3.tgz @@ -211,6 +235,12 @@ importers: '@antv/infographic': specifier: ^0.2.20 version: 0.2.20 + '@arcships/rutis': + specifier: 0.16.0 + version: 0.16.0 + '@arcships/rutis-runtime': + specifier: 0.16.0 + version: 0.16.0(patch_hash=118a5067c0fb8b1a69ee71008145221773dd67f0722f44a1b71ad5b16113edb1) '@commitlint/cli': specifier: ^21.2.2 version: 21.2.2(@types/node@24.13.3)(conventional-commits-parser@7.1.0)(typescript-native-bridge@6.0.3-bridge.16.tsgo.7.0.2) @@ -252,7 +282,7 @@ importers: version: 0.5.20(tailwindcss@4.3.3) '@tailwindcss/vite': specifier: ^4.3.3 - version: 4.3.3(vite@7.3.7(@types/node@24.13.3)(jiti@2.7.0)(lightningcss@1.32.0)(yaml@2.9.0)) + version: 4.3.3(vite@7.3.7(@types/node@24.13.3)(jiti@2.7.0)(lightningcss@1.32.0)(tsx@4.23.15)(yaml@2.9.0)) '@tiptap/core': specifier: ^3.31.3 version: 3.31.3(@tiptap/pm@3.31.3) @@ -300,7 +330,7 @@ importers: version: 1.7.0(@unovis/ts@1.7.0)(vue@3.5.42(typescript-native-bridge@6.0.3-bridge.16.tsgo.7.0.2)) '@vitejs/plugin-vue': specifier: ^6.0.8 - version: 6.0.8(vite@7.3.7(@types/node@24.13.3)(jiti@2.7.0)(lightningcss@1.32.0)(yaml@2.9.0))(vue@3.5.42(typescript-native-bridge@6.0.3-bridge.16.tsgo.7.0.2)) + version: 6.0.8(vite@7.3.7(@types/node@24.13.3)(jiti@2.7.0)(lightningcss@1.32.0)(tsx@4.23.15)(yaml@2.9.0))(vue@3.5.42(typescript-native-bridge@6.0.3-bridge.16.tsgo.7.0.2)) '@vitest/ui': specifier: ^4.1.12 version: 4.1.12(vitest@4.1.12) @@ -333,7 +363,7 @@ importers: version: 26.15.3(electron-builder-squirrel-windows@26.15.3) electron-vite: specifier: 5.0.0 - version: 5.0.0(vite@7.3.7(@types/node@24.13.3)(jiti@2.7.0)(lightningcss@1.32.0)(yaml@2.9.0)) + version: 5.0.0(vite@7.3.7(@types/node@24.13.3)(jiti@2.7.0)(lightningcss@1.32.0)(tsx@4.23.15)(yaml@2.9.0)) jsdom: specifier: ^26.1.0 version: 26.1.0 @@ -402,19 +432,19 @@ importers: version: 4.15.1(vue@3.5.42(typescript-native-bridge@6.0.3-bridge.16.tsgo.7.0.2)) vite: specifier: ^7.3.7 - version: 7.3.7(@types/node@24.13.3)(jiti@2.7.0)(lightningcss@1.32.0)(yaml@2.9.0) + version: 7.3.7(@types/node@24.13.3)(jiti@2.7.0)(lightningcss@1.32.0)(tsx@4.23.15)(yaml@2.9.0) vite-plugin-monaco-editor-esm: specifier: ^2.0.3 version: 2.0.3(monaco-editor@0.55.1) vite-plugin-vue-devtools: specifier: ^8.2.1 - version: 8.2.1(vite@7.3.7(@types/node@24.13.3)(jiti@2.7.0)(lightningcss@1.32.0)(yaml@2.9.0))(vue@3.5.42(typescript-native-bridge@6.0.3-bridge.16.tsgo.7.0.2)) + version: 8.2.1(vite@7.3.7(@types/node@24.13.3)(jiti@2.7.0)(lightningcss@1.32.0)(tsx@4.23.15)(yaml@2.9.0))(vue@3.5.42(typescript-native-bridge@6.0.3-bridge.16.tsgo.7.0.2)) vite-svg-loader: specifier: ^5.1.3 version: 5.1.3(vue@3.5.42(typescript-native-bridge@6.0.3-bridge.16.tsgo.7.0.2)) vitest: specifier: ^4.1.12 - version: 4.1.12(@types/node@24.13.3)(@vitest/ui@4.1.12)(jsdom@26.1.0)(vite@7.3.7(@types/node@24.13.3)(jiti@2.7.0)(lightningcss@1.32.0)(yaml@2.9.0)) + version: 4.1.12(@types/node@24.13.3)(@vitest/ui@4.1.12)(jsdom@26.1.0)(vite@7.3.7(@types/node@24.13.3)(jiti@2.7.0)(lightningcss@1.32.0)(tsx@4.23.15)(yaml@2.9.0)) vue: specifier: ^3.5.42 version: 3.5.42(typescript-native-bridge@6.0.3-bridge.16.tsgo.7.0.2) @@ -423,7 +453,7 @@ importers: version: 11.4.17(vue@3.5.42(typescript-native-bridge@6.0.3-bridge.16.tsgo.7.0.2)) vue-router: specifier: ^5.3.1 - version: 5.3.1(@pinia/colada@0.20.0(pinia@3.0.4(typescript-native-bridge@6.0.3-bridge.16.tsgo.7.0.2)(vue@3.5.42(typescript-native-bridge@6.0.3-bridge.16.tsgo.7.0.2)))(vue@3.5.42(typescript-native-bridge@6.0.3-bridge.16.tsgo.7.0.2)))(@vue/compiler-sfc@3.5.42)(esbuild@0.28.1)(pinia@3.0.4(typescript-native-bridge@6.0.3-bridge.16.tsgo.7.0.2)(vue@3.5.42(typescript-native-bridge@6.0.3-bridge.16.tsgo.7.0.2)))(rollup@4.62.2)(vite@7.3.7(@types/node@24.13.3)(jiti@2.7.0)(lightningcss@1.32.0)(yaml@2.9.0))(vue@3.5.42(typescript-native-bridge@6.0.3-bridge.16.tsgo.7.0.2)) + version: 5.3.1(@pinia/colada@0.20.0(pinia@3.0.4(typescript-native-bridge@6.0.3-bridge.16.tsgo.7.0.2)(vue@3.5.42(typescript-native-bridge@6.0.3-bridge.16.tsgo.7.0.2)))(vue@3.5.42(typescript-native-bridge@6.0.3-bridge.16.tsgo.7.0.2)))(@vue/compiler-sfc@3.5.42)(esbuild@0.28.1)(pinia@3.0.4(typescript-native-bridge@6.0.3-bridge.16.tsgo.7.0.2)(vue@3.5.42(typescript-native-bridge@6.0.3-bridge.16.tsgo.7.0.2)))(rollup@4.62.2)(vite@7.3.7(@types/node@24.13.3)(jiti@2.7.0)(lightningcss@1.32.0)(tsx@4.23.15)(yaml@2.9.0))(vue@3.5.42(typescript-native-bridge@6.0.3-bridge.16.tsgo.7.0.2)) vue-sonner: specifier: ^2.0.9 version: 2.0.9 @@ -621,6 +651,14 @@ packages: engines: {node: ^22.0.0 || ^24.0.0} hasBin: true + '@arcships/rutis-runtime@0.16.0': + resolution: {integrity: sha512-hp46Vu/ZpTN2RRo20X5oCTOh4bfftGa7OXrVf54zX6wSxBOx14R5DF4WD2iC2Fk6/fzrnG3J49hY/GaoXwC9/A==} + engines: {node: '>=22'} + + '@arcships/rutis@0.16.0': + resolution: {integrity: sha512-BS57sHhw5xhHp1oidbmHjGgHpUd9TmXwA3jmIX8TVjWop1hvs4SWK+NZjket3XRwYKfEuwQiJ6mB7aUmsoMHbA==} + engines: {bun: '>=1.4', node: '>=22'} + '@asamuzakjp/css-color@3.2.0': resolution: {integrity: sha512-K1A6z8tS3XsmCMM86xoWdn7Fkdn9m6RSVtocUrJYIwZnFVkng/PvkEoWtOWmP+Scc6saYWHWZYbndEEXxl24jw==} @@ -981,6 +1019,21 @@ packages: resolution: {integrity: sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==} engines: {node: '>=18'} + '@deepseek-ai/cordis@4.0.4': + resolution: {integrity: sha512-obgyxqWAmFn3Re8kvsuUnyW+ihrz6eJCnJO4fh1cQzDtmPYz/zzVeUkH9R94I0OwSVOocK67Kgakm04j/oQXzg==} + hasBin: true + peerDependencies: + '@deepseek-ai/cordis-plugin-include': ~1.0.9 + '@deepseek-ai/cordis-plugin-loader': ~1.0.5 + peerDependenciesMeta: + '@deepseek-ai/cordis-plugin-include': + optional: true + '@deepseek-ai/cordis-plugin-loader': + optional: true + + '@deepseek-ai/cosmokit@1.8.6': + resolution: {integrity: sha512-ZSD/fN52L498LBIOYwjIOlaLv5veY6ZEwI2yzvZUKQtctn0QAPqu8tKdJ+OF5+Tv6NBq1U+wJWH+QI6OGy5EvQ==} + '@duckdb/node-api@1.5.5-r.4': resolution: {integrity: sha512-8v0CZNo7aM6GQCNUHERGTrZWIfss8xKzZPF3ACNhPdMMrt7UjkNI5nRQ9FTFO7Yx8ghxYxVpdotmBBMQ5vXUOA==} @@ -6617,6 +6670,11 @@ packages: tslib@2.8.1: resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + tsx@4.23.15: + resolution: {integrity: sha512-Yiex1Ovn8z2xPpOWckIiysV1SSyRMY9BkLF++q0yKiDxCqRhosKfMg3janKkiLBwZ5c/YryloKwGZcrEmtwxKw==} + engines: {node: '>=18.0.0'} + hasBin: true + turndown@7.2.4: resolution: {integrity: sha512-I8yFsfRzmzK0WV1pNNOA4A7y4RDfFxPRxb3t+e3ui14qSGOxGtiSP6GjeX+Y6CHb7HYaFj7ECUD7VE5kQMZWGQ==} engines: {node: '>=18', npm: '>=9'} @@ -7079,6 +7137,18 @@ packages: utf-8-validate: optional: true + ws@8.22.1: + resolution: {integrity: sha512-oyL6OPYme/CmiFvNuGzeoxCJ4nOrZE2O1FwM9MWM1AsGri6nLXB2pg+lNA8aLeXMSav0J6eIVS1j6vUPzUtK9g==} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: '>=5.0.2' + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + wsl-utils@0.3.1: resolution: {integrity: sha512-g/eziiSUNBSsdDJtCLB8bdYEUMj4jR7AGeUo96p/3dTafgjHhpF4RiCFPiRILwjQoDXx5MqkBr4fwWtR3Ky4Wg==} engines: {node: '>=20'} @@ -7186,7 +7256,7 @@ snapshots: '@ai-sdk/amazon-bedrock@5.0.78(zod@4.6.7)': dependencies: '@ai-sdk/anthropic': 4.0.50(zod@4.6.7) - '@ai-sdk/openai': 4.0.62(zod@4.6.7) + '@ai-sdk/openai': 4.0.62(patch_hash=d540f71958d27ae3c5f065c84b9fdb4d6714c067952674016ce2a0adc5dce783)(zod@4.6.7) '@ai-sdk/provider': 4.0.11 '@ai-sdk/provider-utils': 5.0.37(zod@4.6.7) '@smithy/eventstream-codec': 4.4.16 @@ -7203,7 +7273,7 @@ snapshots: '@ai-sdk/azure@4.0.65(zod@4.6.7)': dependencies: '@ai-sdk/deepseek': 3.0.40(zod@4.6.7) - '@ai-sdk/openai': 4.0.62(zod@4.6.7) + '@ai-sdk/openai': 4.0.62(patch_hash=d540f71958d27ae3c5f065c84b9fdb4d6714c067952674016ce2a0adc5dce783)(zod@4.6.7) '@ai-sdk/provider': 4.0.11 '@ai-sdk/provider-utils': 5.0.37(zod@4.6.7) zod: 4.6.7 @@ -7221,11 +7291,11 @@ snapshots: '@vercel/oidc': 3.2.0 zod: 4.6.7 - '@ai-sdk/google-vertex@5.0.77(zod@4.6.7)': + '@ai-sdk/google-vertex@5.0.77(patch_hash=42c1f1bdd677dd0c113abd81be9b17092ddafc7e6547f9004798b8ba6e04715a)(zod@4.6.7)': dependencies: '@ai-sdk/anthropic': 4.0.50(zod@4.6.7) - '@ai-sdk/google': 4.0.65(zod@4.6.7) - '@ai-sdk/openai-compatible': 3.0.45(zod@4.6.7) + '@ai-sdk/google': 4.0.65(patch_hash=b8f1463345250c901d6c568ef8f18b1a084860495af5faaa13c93cee5607268f)(zod@4.6.7) + '@ai-sdk/openai-compatible': 3.0.45(patch_hash=71a48986b4e5f640033a57b993be567406a112254c67de27e14d07e1ffbe73a8)(zod@4.6.7) '@ai-sdk/provider': 4.0.11 '@ai-sdk/provider-utils': 5.0.37(zod@4.6.7) google-auth-library: 10.9.1 @@ -7233,7 +7303,7 @@ snapshots: transitivePeerDependencies: - supports-color - '@ai-sdk/google@4.0.65(zod@4.6.7)': + '@ai-sdk/google@4.0.65(patch_hash=b8f1463345250c901d6c568ef8f18b1a084860495af5faaa13c93cee5607268f)(zod@4.6.7)': dependencies: '@ai-sdk/provider': 4.0.11 '@ai-sdk/provider-utils': 5.0.37(zod@4.6.7) @@ -7245,13 +7315,13 @@ snapshots: '@ai-sdk/provider-utils': 5.0.37(zod@4.6.7) zod: 4.6.7 - '@ai-sdk/openai-compatible@3.0.45(zod@4.6.7)': + '@ai-sdk/openai-compatible@3.0.45(patch_hash=71a48986b4e5f640033a57b993be567406a112254c67de27e14d07e1ffbe73a8)(zod@4.6.7)': dependencies: '@ai-sdk/provider': 4.0.11 '@ai-sdk/provider-utils': 5.0.37(zod@4.6.7) zod: 4.6.7 - '@ai-sdk/openai@4.0.62(zod@4.6.7)': + '@ai-sdk/openai@4.0.62(patch_hash=d540f71958d27ae3c5f065c84b9fdb4d6714c067952674016ce2a0adc5dce783)(zod@4.6.7)': dependencies: '@ai-sdk/provider': 4.0.11 '@ai-sdk/provider-utils': 5.0.37(zod@4.6.7) @@ -7365,6 +7435,20 @@ snapshots: '@arcships/light-ocr-model-ppocrv6-small': 0.3.4 '@arcships/light-ocr-runtime': 0.1.7 + '@arcships/rutis-runtime@0.16.0(patch_hash=118a5067c0fb8b1a69ee71008145221773dd67f0722f44a1b71ad5b16113edb1)': + dependencies: + '@deepseek-ai/cordis': 4.0.4 + tsx: 4.23.15 + typescript: typescript-native-bridge@6.0.3-bridge.16.tsgo.7.0.2 + ws: 8.22.1 + transitivePeerDependencies: + - '@deepseek-ai/cordis-plugin-include' + - '@deepseek-ai/cordis-plugin-loader' + - bufferutil + - utf-8-validate + + '@arcships/rutis@0.16.0': {} + '@asamuzakjp/css-color@3.2.0': dependencies: '@csstools/css-calc': 2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) @@ -7907,6 +7991,13 @@ snapshots: '@csstools/css-tokenizer@3.0.4': {} + '@deepseek-ai/cordis@4.0.4': + dependencies: + '@deepseek-ai/cosmokit': 1.8.6 + '@standard-schema/spec': 1.1.0 + + '@deepseek-ai/cosmokit@1.8.6': {} + '@duckdb/node-api@1.5.5-r.4': dependencies: '@duckdb/node-bindings': 1.5.5-r.4 @@ -9359,12 +9450,12 @@ snapshots: postcss-selector-parser: 6.1.5 tailwindcss: 4.3.3 - '@tailwindcss/vite@4.3.3(vite@7.3.7(@types/node@24.13.3)(jiti@2.7.0)(lightningcss@1.32.0)(yaml@2.9.0))': + '@tailwindcss/vite@4.3.3(vite@7.3.7(@types/node@24.13.3)(jiti@2.7.0)(lightningcss@1.32.0)(tsx@4.23.15)(yaml@2.9.0))': dependencies: '@tailwindcss/node': 4.3.3 '@tailwindcss/oxide': 4.3.3 tailwindcss: 4.3.3 - vite: 7.3.7(@types/node@24.13.3)(jiti@2.7.0)(lightningcss@1.32.0)(yaml@2.9.0) + vite: 7.3.7(@types/node@24.13.3)(jiti@2.7.0)(lightningcss@1.32.0)(tsx@4.23.15)(yaml@2.9.0) '@tanstack/virtual-core@3.17.6': {} @@ -9843,10 +9934,10 @@ snapshots: '@vercel/oidc@3.2.0': {} - '@vitejs/plugin-vue@6.0.8(vite@7.3.7(@types/node@24.13.3)(jiti@2.7.0)(lightningcss@1.32.0)(yaml@2.9.0))(vue@3.5.42(typescript-native-bridge@6.0.3-bridge.16.tsgo.7.0.2))': + '@vitejs/plugin-vue@6.0.8(vite@7.3.7(@types/node@24.13.3)(jiti@2.7.0)(lightningcss@1.32.0)(tsx@4.23.15)(yaml@2.9.0))(vue@3.5.42(typescript-native-bridge@6.0.3-bridge.16.tsgo.7.0.2))': dependencies: '@rolldown/pluginutils': 1.0.1 - vite: 7.3.7(@types/node@24.13.3)(jiti@2.7.0)(lightningcss@1.32.0)(yaml@2.9.0) + vite: 7.3.7(@types/node@24.13.3)(jiti@2.7.0)(lightningcss@1.32.0)(tsx@4.23.15)(yaml@2.9.0) vue: 3.5.42(typescript-native-bridge@6.0.3-bridge.16.tsgo.7.0.2) '@vitest/expect@4.1.12': @@ -9858,13 +9949,13 @@ snapshots: chai: 6.2.2 tinyrainbow: 3.1.1 - '@vitest/mocker@4.1.12(vite@7.3.7(@types/node@24.13.3)(jiti@2.7.0)(lightningcss@1.32.0)(yaml@2.9.0))': + '@vitest/mocker@4.1.12(vite@7.3.7(@types/node@24.13.3)(jiti@2.7.0)(lightningcss@1.32.0)(tsx@4.23.15)(yaml@2.9.0))': dependencies: '@vitest/spy': 4.1.12 estree-walker: 3.0.3 magic-string: 0.30.21 optionalDependencies: - vite: 7.3.7(@types/node@24.13.3)(jiti@2.7.0)(lightningcss@1.32.0)(yaml@2.9.0) + vite: 7.3.7(@types/node@24.13.3)(jiti@2.7.0)(lightningcss@1.32.0)(tsx@4.23.15)(yaml@2.9.0) '@vitest/pretty-format@4.1.12': dependencies: @@ -9893,7 +9984,7 @@ snapshots: sirv: 3.0.2 tinyglobby: 0.2.17 tinyrainbow: 3.1.1 - vitest: 4.1.12(@types/node@24.13.3)(@vitest/ui@4.1.12)(jsdom@26.1.0)(vite@7.3.7(@types/node@24.13.3)(jiti@2.7.0)(lightningcss@1.32.0)(yaml@2.9.0)) + vitest: 4.1.12(@types/node@24.13.3)(@vitest/ui@4.1.12)(jsdom@26.1.0)(vite@7.3.7(@types/node@24.13.3)(jiti@2.7.0)(lightningcss@1.32.0)(tsx@4.23.15)(yaml@2.9.0)) '@vitest/utils@4.1.12': dependencies: @@ -11216,7 +11307,7 @@ snapshots: transitivePeerDependencies: - supports-color - electron-vite@5.0.0(vite@7.3.7(@types/node@24.13.3)(jiti@2.7.0)(lightningcss@1.32.0)(yaml@2.9.0)): + electron-vite@5.0.0(vite@7.3.7(@types/node@24.13.3)(jiti@2.7.0)(lightningcss@1.32.0)(tsx@4.23.15)(yaml@2.9.0)): dependencies: '@babel/core': 7.29.7 '@babel/plugin-transform-arrow-functions': 7.29.7(@babel/core@7.29.7) @@ -11224,7 +11315,7 @@ snapshots: esbuild: 0.25.12 magic-string: 0.30.21 picocolors: 1.1.1 - vite: 7.3.7(@types/node@24.13.3)(jiti@2.7.0)(lightningcss@1.32.0)(yaml@2.9.0) + vite: 7.3.7(@types/node@24.13.3)(jiti@2.7.0)(lightningcss@1.32.0)(tsx@4.23.15)(yaml@2.9.0) transitivePeerDependencies: - supports-color @@ -13660,6 +13751,12 @@ snapshots: tslib@2.8.1: {} + tsx@4.23.15: + dependencies: + esbuild: 0.28.1 + optionalDependencies: + fsevents: 2.3.3 + turndown@7.2.4: dependencies: '@mixmark-io/domino': 2.2.0 @@ -13720,7 +13817,7 @@ snapshots: undici@7.30.1: {} - undici@8.12.3: {} + undici@8.12.3(patch_hash=3b4133bd9786ff080c4fa0de1272123dd029e8c87da0ac32e891e285d6265936): {} unist-util-is@6.0.1: dependencies: @@ -13756,7 +13853,7 @@ snapshots: pathe: 2.0.3 picomatch: 4.0.5 - unplugin@3.3.0(esbuild@0.28.1)(rollup@4.62.2)(vite@7.3.7(@types/node@24.13.3)(jiti@2.7.0)(lightningcss@1.32.0)(yaml@2.9.0)): + unplugin@3.3.0(esbuild@0.28.1)(rollup@4.62.2)(vite@7.3.7(@types/node@24.13.3)(jiti@2.7.0)(lightningcss@1.32.0)(tsx@4.23.15)(yaml@2.9.0)): dependencies: '@jridgewell/remapping': 2.3.5 picomatch: 4.0.5 @@ -13764,7 +13861,7 @@ snapshots: optionalDependencies: esbuild: 0.28.1 rollup: 4.62.2 - vite: 7.3.7(@types/node@24.13.3)(jiti@2.7.0)(lightningcss@1.32.0)(yaml@2.9.0) + vite: 7.3.7(@types/node@24.13.3)(jiti@2.7.0)(lightningcss@1.32.0)(tsx@4.23.15)(yaml@2.9.0) unzipper@0.12.5: dependencies: @@ -13806,17 +13903,17 @@ snapshots: '@types/unist': 3.0.3 vfile-message: 4.0.3 - vite-dev-rpc@2.0.0(vite@7.3.7(@types/node@24.13.3)(jiti@2.7.0)(lightningcss@1.32.0)(yaml@2.9.0)): + vite-dev-rpc@2.0.0(vite@7.3.7(@types/node@24.13.3)(jiti@2.7.0)(lightningcss@1.32.0)(tsx@4.23.15)(yaml@2.9.0)): dependencies: birpc: 4.0.0 - vite: 7.3.7(@types/node@24.13.3)(jiti@2.7.0)(lightningcss@1.32.0)(yaml@2.9.0) - vite-hot-client: 2.2.0(vite@7.3.7(@types/node@24.13.3)(jiti@2.7.0)(lightningcss@1.32.0)(yaml@2.9.0)) + vite: 7.3.7(@types/node@24.13.3)(jiti@2.7.0)(lightningcss@1.32.0)(tsx@4.23.15)(yaml@2.9.0) + vite-hot-client: 2.2.0(vite@7.3.7(@types/node@24.13.3)(jiti@2.7.0)(lightningcss@1.32.0)(tsx@4.23.15)(yaml@2.9.0)) - vite-hot-client@2.2.0(vite@7.3.7(@types/node@24.13.3)(jiti@2.7.0)(lightningcss@1.32.0)(yaml@2.9.0)): + vite-hot-client@2.2.0(vite@7.3.7(@types/node@24.13.3)(jiti@2.7.0)(lightningcss@1.32.0)(tsx@4.23.15)(yaml@2.9.0)): dependencies: - vite: 7.3.7(@types/node@24.13.3)(jiti@2.7.0)(lightningcss@1.32.0)(yaml@2.9.0) + vite: 7.3.7(@types/node@24.13.3)(jiti@2.7.0)(lightningcss@1.32.0)(tsx@4.23.15)(yaml@2.9.0) - vite-plugin-inspect@11.4.1(vite@7.3.7(@types/node@24.13.3)(jiti@2.7.0)(lightningcss@1.32.0)(yaml@2.9.0)): + vite-plugin-inspect@11.4.1(vite@7.3.7(@types/node@24.13.3)(jiti@2.7.0)(lightningcss@1.32.0)(tsx@4.23.15)(yaml@2.9.0)): dependencies: ansis: 4.3.1 error-stack-parser-es: 1.0.5 @@ -13826,28 +13923,28 @@ snapshots: perfect-debounce: 2.1.0 sirv: 3.0.2 unplugin-utils: 0.3.2 - vite: 7.3.7(@types/node@24.13.3)(jiti@2.7.0)(lightningcss@1.32.0)(yaml@2.9.0) - vite-dev-rpc: 2.0.0(vite@7.3.7(@types/node@24.13.3)(jiti@2.7.0)(lightningcss@1.32.0)(yaml@2.9.0)) + vite: 7.3.7(@types/node@24.13.3)(jiti@2.7.0)(lightningcss@1.32.0)(tsx@4.23.15)(yaml@2.9.0) + vite-dev-rpc: 2.0.0(vite@7.3.7(@types/node@24.13.3)(jiti@2.7.0)(lightningcss@1.32.0)(tsx@4.23.15)(yaml@2.9.0)) vite-plugin-monaco-editor-esm@2.0.3(monaco-editor@0.55.1): dependencies: monaco-editor: 0.55.1 - vite-plugin-vue-devtools@8.2.1(vite@7.3.7(@types/node@24.13.3)(jiti@2.7.0)(lightningcss@1.32.0)(yaml@2.9.0))(vue@3.5.42(typescript-native-bridge@6.0.3-bridge.16.tsgo.7.0.2)): + vite-plugin-vue-devtools@8.2.1(vite@7.3.7(@types/node@24.13.3)(jiti@2.7.0)(lightningcss@1.32.0)(tsx@4.23.15)(yaml@2.9.0))(vue@3.5.42(typescript-native-bridge@6.0.3-bridge.16.tsgo.7.0.2)): dependencies: '@vue/devtools-core': 8.2.1(vue@3.5.42(typescript-native-bridge@6.0.3-bridge.16.tsgo.7.0.2)) '@vue/devtools-kit': 8.2.1 '@vue/devtools-shared': 8.2.1 sirv: 3.0.2 - vite: 7.3.7(@types/node@24.13.3)(jiti@2.7.0)(lightningcss@1.32.0)(yaml@2.9.0) - vite-plugin-inspect: 11.4.1(vite@7.3.7(@types/node@24.13.3)(jiti@2.7.0)(lightningcss@1.32.0)(yaml@2.9.0)) - vite-plugin-vue-inspector: 6.0.0(vite@7.3.7(@types/node@24.13.3)(jiti@2.7.0)(lightningcss@1.32.0)(yaml@2.9.0)) + vite: 7.3.7(@types/node@24.13.3)(jiti@2.7.0)(lightningcss@1.32.0)(tsx@4.23.15)(yaml@2.9.0) + vite-plugin-inspect: 11.4.1(vite@7.3.7(@types/node@24.13.3)(jiti@2.7.0)(lightningcss@1.32.0)(tsx@4.23.15)(yaml@2.9.0)) + vite-plugin-vue-inspector: 6.0.0(vite@7.3.7(@types/node@24.13.3)(jiti@2.7.0)(lightningcss@1.32.0)(tsx@4.23.15)(yaml@2.9.0)) transitivePeerDependencies: - '@nuxt/kit' - supports-color - vue - vite-plugin-vue-inspector@6.0.0(vite@7.3.7(@types/node@24.13.3)(jiti@2.7.0)(lightningcss@1.32.0)(yaml@2.9.0)): + vite-plugin-vue-inspector@6.0.0(vite@7.3.7(@types/node@24.13.3)(jiti@2.7.0)(lightningcss@1.32.0)(tsx@4.23.15)(yaml@2.9.0)): dependencies: '@babel/core': 7.29.7 '@babel/plugin-proposal-decorators': 7.29.7(@babel/core@7.29.7) @@ -13858,7 +13955,7 @@ snapshots: '@vue/compiler-dom': 3.5.40 kolorist: 1.8.0 magic-string: 0.30.21 - vite: 7.3.7(@types/node@24.13.3)(jiti@2.7.0)(lightningcss@1.32.0)(yaml@2.9.0) + vite: 7.3.7(@types/node@24.13.3)(jiti@2.7.0)(lightningcss@1.32.0)(tsx@4.23.15)(yaml@2.9.0) transitivePeerDependencies: - supports-color @@ -13870,7 +13967,7 @@ snapshots: transitivePeerDependencies: - supports-color - vite@7.3.7(@types/node@24.13.3)(jiti@2.7.0)(lightningcss@1.32.0)(yaml@2.9.0): + vite@7.3.7(@types/node@24.13.3)(jiti@2.7.0)(lightningcss@1.32.0)(tsx@4.23.15)(yaml@2.9.0): dependencies: esbuild: 0.28.1 fdir: 6.5.0(picomatch@4.0.7) @@ -13883,12 +13980,13 @@ snapshots: fsevents: 2.3.3 jiti: 2.7.0 lightningcss: 1.32.0 + tsx: 4.23.15 yaml: 2.9.0 - vitest@4.1.12(@types/node@24.13.3)(@vitest/ui@4.1.12)(jsdom@26.1.0)(vite@7.3.7(@types/node@24.13.3)(jiti@2.7.0)(lightningcss@1.32.0)(yaml@2.9.0)): + vitest@4.1.12(@types/node@24.13.3)(@vitest/ui@4.1.12)(jsdom@26.1.0)(vite@7.3.7(@types/node@24.13.3)(jiti@2.7.0)(lightningcss@1.32.0)(tsx@4.23.15)(yaml@2.9.0)): dependencies: '@vitest/expect': 4.1.12 - '@vitest/mocker': 4.1.12(vite@7.3.7(@types/node@24.13.3)(jiti@2.7.0)(lightningcss@1.32.0)(yaml@2.9.0)) + '@vitest/mocker': 4.1.12(vite@7.3.7(@types/node@24.13.3)(jiti@2.7.0)(lightningcss@1.32.0)(tsx@4.23.15)(yaml@2.9.0)) '@vitest/pretty-format': 4.1.12 '@vitest/runner': 4.1.12 '@vitest/snapshot': 4.1.12 @@ -13905,7 +14003,7 @@ snapshots: tinyexec: 1.3.0 tinyglobby: 0.2.17 tinyrainbow: 3.1.1 - vite: 7.3.7(@types/node@24.13.3)(jiti@2.7.0)(lightningcss@1.32.0)(yaml@2.9.0) + vite: 7.3.7(@types/node@24.13.3)(jiti@2.7.0)(lightningcss@1.32.0)(tsx@4.23.15)(yaml@2.9.0) why-is-node-running: 2.3.0 optionalDependencies: '@types/node': 24.13.3 @@ -13930,7 +14028,7 @@ snapshots: '@vue/devtools-api': 6.6.4 vue: 3.5.42(typescript-native-bridge@6.0.3-bridge.16.tsgo.7.0.2) - vue-router@5.3.1(@pinia/colada@0.20.0(pinia@3.0.4(typescript-native-bridge@6.0.3-bridge.16.tsgo.7.0.2)(vue@3.5.42(typescript-native-bridge@6.0.3-bridge.16.tsgo.7.0.2)))(vue@3.5.42(typescript-native-bridge@6.0.3-bridge.16.tsgo.7.0.2)))(@vue/compiler-sfc@3.5.42)(esbuild@0.28.1)(pinia@3.0.4(typescript-native-bridge@6.0.3-bridge.16.tsgo.7.0.2)(vue@3.5.42(typescript-native-bridge@6.0.3-bridge.16.tsgo.7.0.2)))(rollup@4.62.2)(vite@7.3.7(@types/node@24.13.3)(jiti@2.7.0)(lightningcss@1.32.0)(yaml@2.9.0))(vue@3.5.42(typescript-native-bridge@6.0.3-bridge.16.tsgo.7.0.2)): + vue-router@5.3.1(@pinia/colada@0.20.0(pinia@3.0.4(typescript-native-bridge@6.0.3-bridge.16.tsgo.7.0.2)(vue@3.5.42(typescript-native-bridge@6.0.3-bridge.16.tsgo.7.0.2)))(vue@3.5.42(typescript-native-bridge@6.0.3-bridge.16.tsgo.7.0.2)))(@vue/compiler-sfc@3.5.42)(esbuild@0.28.1)(pinia@3.0.4(typescript-native-bridge@6.0.3-bridge.16.tsgo.7.0.2)(vue@3.5.42(typescript-native-bridge@6.0.3-bridge.16.tsgo.7.0.2)))(rollup@4.62.2)(vite@7.3.7(@types/node@24.13.3)(jiti@2.7.0)(lightningcss@1.32.0)(tsx@4.23.15)(yaml@2.9.0))(vue@3.5.42(typescript-native-bridge@6.0.3-bridge.16.tsgo.7.0.2)): dependencies: '@vue-macros/common': 3.1.4(vue@3.5.42(typescript-native-bridge@6.0.3-bridge.16.tsgo.7.0.2)) '@vue/devtools-api': 8.1.5 @@ -13946,14 +14044,14 @@ snapshots: picomatch: 4.0.5 scule: 1.3.0 tinyglobby: 0.2.17 - unplugin: 3.3.0(esbuild@0.28.1)(rollup@4.62.2)(vite@7.3.7(@types/node@24.13.3)(jiti@2.7.0)(lightningcss@1.32.0)(yaml@2.9.0)) + unplugin: 3.3.0(esbuild@0.28.1)(rollup@4.62.2)(vite@7.3.7(@types/node@24.13.3)(jiti@2.7.0)(lightningcss@1.32.0)(tsx@4.23.15)(yaml@2.9.0)) unplugin-utils: 0.3.2 vue: 3.5.42(typescript-native-bridge@6.0.3-bridge.16.tsgo.7.0.2) optionalDependencies: '@pinia/colada': 0.20.0(pinia@3.0.4(typescript-native-bridge@6.0.3-bridge.16.tsgo.7.0.2)(vue@3.5.42(typescript-native-bridge@6.0.3-bridge.16.tsgo.7.0.2)))(vue@3.5.42(typescript-native-bridge@6.0.3-bridge.16.tsgo.7.0.2)) '@vue/compiler-sfc': 3.5.42 pinia: 3.0.4(typescript-native-bridge@6.0.3-bridge.16.tsgo.7.0.2)(vue@3.5.42(typescript-native-bridge@6.0.3-bridge.16.tsgo.7.0.2)) - vite: 7.3.7(@types/node@24.13.3)(jiti@2.7.0)(lightningcss@1.32.0)(yaml@2.9.0) + vite: 7.3.7(@types/node@24.13.3)(jiti@2.7.0)(lightningcss@1.32.0)(tsx@4.23.15)(yaml@2.9.0) transitivePeerDependencies: - '@farmfe/core' - '@rspack/core' @@ -14073,6 +14171,8 @@ snapshots: ws@8.21.1: {} + ws@8.22.1: {} + wsl-utils@0.3.1: dependencies: is-wsl: 3.1.1 diff --git a/resources/acp-registry/icons/kiro.svg b/resources/acp-registry/icons/kiro.svg new file mode 100644 index 0000000000..755a5b287f --- /dev/null +++ b/resources/acp-registry/icons/kiro.svg @@ -0,0 +1,3 @@ + + + diff --git a/resources/acp-registry/registry.json b/resources/acp-registry/registry.json index 258da7e3ea..949c1dc872 100644 --- a/resources/acp-registry/registry.json +++ b/resources/acp-registry/registry.json @@ -26,7 +26,7 @@ { "id": "amp-acp", "name": "Amp", - "version": "0.10.0", + "version": "0.12.0", "description": "ACP wrapper for Amp - the frontier coding agent", "repository": "https://github.com/tao12345666333/amp-acp", "authors": [ @@ -38,29 +38,29 @@ "distribution": { "binary": { "darwin-aarch64": { - "archive": "https://github.com/tao12345666333/amp-acp/releases/download/v0.10.0/amp-acp-darwin-aarch64.tar.gz", + "archive": "https://github.com/tao12345666333/amp-acp/releases/download/v0.12.0/amp-acp-darwin-aarch64.tar.gz", "cmd": "./amp-acp", - "sha256": "0434cab747e8f655bbf57dba2421dfd4e01f885ded7776d166150547f07385f3" + "sha256": "1c5f08f44f3711710769585855cefcdbaed53e4133c2263d59a368b5a1597151" }, "darwin-x86_64": { - "archive": "https://github.com/tao12345666333/amp-acp/releases/download/v0.10.0/amp-acp-darwin-x86_64.tar.gz", + "archive": "https://github.com/tao12345666333/amp-acp/releases/download/v0.12.0/amp-acp-darwin-x86_64.tar.gz", "cmd": "./amp-acp", - "sha256": "ceb87500641814bf0f5944f028328c45ff2db8205ab51a37cff622e91d7ea6a4" + "sha256": "4245776441b18c05621dd66376064e66aebba90806a0d787592ea558207c5a68" }, "linux-aarch64": { - "archive": "https://github.com/tao12345666333/amp-acp/releases/download/v0.10.0/amp-acp-linux-aarch64.tar.gz", + "archive": "https://github.com/tao12345666333/amp-acp/releases/download/v0.12.0/amp-acp-linux-aarch64.tar.gz", "cmd": "./amp-acp", - "sha256": "523e03dbf013dd9281aa8aa296691e472d039ed7f47c464ebdc4a6f0a0567ef1" + "sha256": "f75b1f4a2adc1c243601952070eb24bbcfecf328692f799815b59efeb9791528" }, "linux-x86_64": { - "archive": "https://github.com/tao12345666333/amp-acp/releases/download/v0.10.0/amp-acp-linux-x86_64.tar.gz", + "archive": "https://github.com/tao12345666333/amp-acp/releases/download/v0.12.0/amp-acp-linux-x86_64.tar.gz", "cmd": "./amp-acp", - "sha256": "320959bb1de650681413a3fc72febd62fded65c734b9ab3399855e007fcdd1ba" + "sha256": "c25e3635159ee12f8a77bd775dd8a8fc21cc4d25a3a23483ffafec73ccb910fe" }, "windows-x86_64": { - "archive": "https://github.com/tao12345666333/amp-acp/releases/download/v0.10.0/amp-acp-windows-x86_64.zip", + "archive": "https://github.com/tao12345666333/amp-acp/releases/download/v0.12.0/amp-acp-windows-x86_64.zip", "cmd": "amp-acp.exe", - "sha256": "af3fe51c6b555f25446cb18cc81218d0cc334227289215cacb5a53feb12ff33f" + "sha256": "7377158ddac570d50f1eb98e0ce39ca6ecf18990bf917c1930bf628509ae12b4" } } } @@ -146,18 +146,57 @@ { "id": "autohand", "name": "Autohand Code", - "version": "0.2.1", - "description": "Autohand Code - AI coding agent powered by Autohand AI", - "repository": "https://github.com/autohandai/autohand-acp", - "website": "https://www.autohand.ai/cli/", + "version": "0.9.9", + "description": "Autohand Code CLI with native ACP support", + "repository": "https://github.com/autohandai/code-cli", + "website": "https://github.com/autohandai/code-cli/blob/main/docs/guides/ACP.md", "authors": [ "Autohand AI" ], "license": "Apache-2.0", - "license_url": "https://github.com/autohandai/autohand-acp/blob/main/LICENSE", + "license_url": "https://github.com/autohandai/code-cli/blob/main/LICENSE", "distribution": { - "npx": { - "package": "@autohandai/autohand-acp@0.2.1" + "binary": { + "darwin-aarch64": { + "archive": "https://github.com/autohandai/code-cli/releases/download/v0.9.9/autohand-macos-arm64.tar.gz", + "sha256": "e873edb3a98f067365dbdc74fd0ee52a78e9b5cc7341ac6a76d9a71b95d74a4d", + "cmd": "./autohand", + "args": [ + "--acp" + ] + }, + "darwin-x86_64": { + "archive": "https://github.com/autohandai/code-cli/releases/download/v0.9.9/autohand-macos-x64.tar.gz", + "sha256": "cdd5395a52eb043efcb2d325367c0f364c76f254ebb67148d22227770336d8e7", + "cmd": "./autohand", + "args": [ + "--acp" + ] + }, + "linux-aarch64": { + "archive": "https://github.com/autohandai/code-cli/releases/download/v0.9.9/autohand-linux-arm64.tar.gz", + "sha256": "3fb41af063007b7f8ff4fd947051631c8c5089a8b931e99b40c07c56a1d4ba19", + "cmd": "./autohand", + "args": [ + "--acp" + ] + }, + "linux-x86_64": { + "archive": "https://github.com/autohandai/code-cli/releases/download/v0.9.9/autohand-linux-x64.tar.gz", + "sha256": "852ef7decae6405ae2139d04eb82649aab798145644b0136f7eeb958cd791775", + "cmd": "./autohand", + "args": [ + "--acp" + ] + }, + "windows-x86_64": { + "archive": "https://github.com/autohandai/code-cli/releases/download/v0.9.9/autohand-windows-x64.zip", + "sha256": "d109c17dd1a5669b73a259991766b1b143c1f727eb698b6c730609f330341dfd", + "cmd": "./autohand.exe", + "args": [ + "--acp" + ] + } } }, "icon": "https://cdn.agentclientprotocol.com/registry/v1/latest/autohand.svg" @@ -186,7 +225,7 @@ { "id": "claude-acp", "name": "Claude Agent", - "version": "0.99.1", + "version": "0.100.0", "description": "ACP wrapper for Anthropic's Claude", "repository": "https://github.com/agentclientprotocol/claude-agent-acp", "authors": [ @@ -198,7 +237,7 @@ "license_url": "https://github.com/agentclientprotocol/claude-agent-acp/blob/main/LICENSE", "distribution": { "npx": { - "package": "@agentclientprotocol/claude-agent-acp@0.99.1" + "package": "@agentclientprotocol/claude-agent-acp@0.100.0" } }, "icon": "https://cdn.agentclientprotocol.com/registry/v1/latest/claude-acp.svg" @@ -228,7 +267,7 @@ { "id": "codebuddy-code", "name": "Codebuddy Code", - "version": "2.179.0", + "version": "2.179.3", "description": "Tencent Cloud's official intelligent coding tool", "website": "https://www.codebuddy.cn/cli/", "authors": [ @@ -238,7 +277,7 @@ "license_url": "https://www.codebuddy.cn/agreement/", "distribution": { "npx": { - "package": "@tencent-ai/codebuddy-code@2.179.0", + "package": "@tencent-ai/codebuddy-code@2.179.3", "args": [ "--acp" ] @@ -368,47 +407,52 @@ { "id": "crow-cli", "name": "crow-cli", - "version": "0.1.24", + "version": "0.1.49", "description": "Minimal ACP Native Coding Agent", "repository": "https://github.com/crow-cli/crow-cli", "website": "https://crow-ai.dev", "authors": [ "Thomas Wood" ], - "license": "Apache-2.0", + "license": "MIT", "license_url": "https://github.com/crow-cli/crow-cli/blob/main/LICENSE.md", "distribution": { "binary": { "darwin-aarch64": { - "archive": "https://github.com/crow-cli/crow-cli/releases/download/v0.1.24/crow-cli-darwin-aarch64.tar.gz", + "archive": "https://github.com/crow-cli/crow-cli/releases/download/v0.1.49/crow-cli-darwin-aarch64.tar.gz", + "sha256": "e57bec141681d0843990ab2d536467a42cf33b4fe10a3374c152b83eb200cccd", "cmd": "./crow-cli", "args": [ "acp" ] }, "darwin-x86_64": { - "archive": "https://github.com/crow-cli/crow-cli/releases/download/v0.1.24/crow-cli-darwin-x86_64.tar.gz", + "archive": "https://github.com/crow-cli/crow-cli/releases/download/v0.1.49/crow-cli-darwin-x86_64.tar.gz", + "sha256": "62941f0f3d35c9e722d92bee3a4a9a5f8c4c92c2bcf408f7a7268eb0f8d52a1f", "cmd": "./crow-cli", "args": [ "acp" ] }, "linux-aarch64": { - "archive": "https://github.com/crow-cli/crow-cli/releases/download/v0.1.24/crow-cli-linux-aarch64.tar.gz", + "archive": "https://github.com/crow-cli/crow-cli/releases/download/v0.1.49/crow-cli-linux-aarch64.tar.gz", + "sha256": "7e4160ae3ffc0078a73fe78cab49cfe00bf854538991c47dbb25282d6a092685", "cmd": "./crow-cli", "args": [ "acp" ] }, "linux-x86_64": { - "archive": "https://github.com/crow-cli/crow-cli/releases/download/v0.1.24/crow-cli-linux-x86_64.tar.gz", + "archive": "https://github.com/crow-cli/crow-cli/releases/download/v0.1.49/crow-cli-linux-x86_64.tar.gz", + "sha256": "0001d5019ec35e8a080844daa96d3ba91afd38ec44a89e5102291ebd36a1083c", "cmd": "./crow-cli", "args": [ "acp" ] }, "windows-x86_64": { - "archive": "https://github.com/crow-cli/crow-cli/releases/download/v0.1.24/crow-cli-windows-x86_64.zip", + "archive": "https://github.com/crow-cli/crow-cli/releases/download/v0.1.49/crow-cli-windows-x86_64.zip", + "sha256": "bcee58f1af378436d93b3386b62ce9a90dc3ed9614861ad28581c86577c4323a", "cmd": "./crow-cli.exe", "args": [ "acp" @@ -560,7 +604,7 @@ { "id": "dimcode", "name": "DimCode", - "version": "0.5.22", + "version": "0.5.24", "description": "A coding agent that puts leading models at your command.", "website": "https://dimagent.cn/", "authors": [ @@ -570,7 +614,7 @@ "license_url": "https://dimagent.cn/terms", "distribution": { "npx": { - "package": "dimcode@0.5.22", + "package": "dimcode@0.5.24", "args": [ "acp" ] @@ -603,7 +647,7 @@ { "id": "factory-droid", "name": "Factory Droid", - "version": "1.1.0", + "version": "1.2.0", "description": "Factory Droid - AI coding agent powered by Factory AI", "website": "https://factory.ai/product/cli", "authors": [ @@ -613,7 +657,7 @@ "license_url": "https://factory.ai/legal/terms", "distribution": { "npx": { - "package": "droid@1.1.0", + "package": "droid@1.2.0", "args": [ "exec", "--output-format", @@ -676,7 +720,7 @@ }, { "id": "github-copilot-cli", - "name": "Copilot", + "name": "GitHub Copilot", "version": "1.0.105", "description": "GitHub's AI pair programmer", "repository": "https://github.com/github/copilot-cli", @@ -797,7 +841,7 @@ { "id": "harn", "name": "Harn", - "version": "0.10.182", + "version": "0.10.185", "description": "Harn runs .harn agent pipelines as a native ACP coding agent over stdio.", "repository": "https://github.com/burin-labs/harn", "website": "https://harnlang.com", @@ -809,49 +853,49 @@ "distribution": { "binary": { "darwin-aarch64": { - "archive": "https://github.com/burin-labs/harn/releases/download/v0.10.182/harn-aarch64-apple-darwin.tar.gz", + "archive": "https://github.com/burin-labs/harn/releases/download/v0.10.185/harn-aarch64-apple-darwin.tar.gz", "cmd": "./harn", "args": [ "serve", "acp" ], - "sha256": "6324efdd7f7bded86ac63e96a2216fa046aeb25b32d8a944f44424640c5c95b6" + "sha256": "28c29df3c27ce4be8677de0a6908e77999a03ed3662ea0404922992df106317d" }, "darwin-x86_64": { - "archive": "https://github.com/burin-labs/harn/releases/download/v0.10.182/harn-x86_64-apple-darwin.tar.gz", + "archive": "https://github.com/burin-labs/harn/releases/download/v0.10.185/harn-x86_64-apple-darwin.tar.gz", "cmd": "./harn", "args": [ "serve", "acp" ], - "sha256": "300dc6c24108bbd40e1bfcccfb4ece29ccd19dff794ba19722fc13b751e52aaf" + "sha256": "9c13468f816b3e0c1feb1428179f19cfa0648ee108738611ae6f02eafc5eedb8" }, "linux-aarch64": { - "archive": "https://github.com/burin-labs/harn/releases/download/v0.10.182/harn-aarch64-unknown-linux-gnu.tar.gz", + "archive": "https://github.com/burin-labs/harn/releases/download/v0.10.185/harn-aarch64-unknown-linux-gnu.tar.gz", "cmd": "./harn", "args": [ "serve", "acp" ], - "sha256": "0c22ee0a9909ab1578f6167298c54945399c9b03d126deae3062bf6ad96b19a1" + "sha256": "c1818ca6c49614e5600e9d8f8d82f5bc54a3cb97d0ba8a1da615a65ff7a3f295" }, "linux-x86_64": { - "archive": "https://github.com/burin-labs/harn/releases/download/v0.10.182/harn-x86_64-unknown-linux-gnu.tar.gz", + "archive": "https://github.com/burin-labs/harn/releases/download/v0.10.185/harn-x86_64-unknown-linux-gnu.tar.gz", "cmd": "./harn", "args": [ "serve", "acp" ], - "sha256": "9e97ba2a0755142421398e5dc3e6243f367f7f4986f1d3d9dd7b760724130e88" + "sha256": "ac55d0b1544b4f6175b26aaae65de74cd3767efff705bfcd0dfc3deb74a1e5ec" }, "windows-x86_64": { - "archive": "https://github.com/burin-labs/harn/releases/download/v0.10.182/harn-x86_64-pc-windows-msvc.zip", + "archive": "https://github.com/burin-labs/harn/releases/download/v0.10.185/harn-x86_64-pc-windows-msvc.zip", "cmd": "harn.exe", "args": [ "serve", "acp" ], - "sha256": "397b65adff05fcd3630a43eb26a36b05d3bf59b97c2b3ce322a15d98a0ffbe3f" + "sha256": "a6354030455e7fb82d3953907698b6ab60f2baa1f2dcc868723980a227c56195" } } }, @@ -984,7 +1028,7 @@ { "id": "kimchi", "name": "Kimchi", - "version": "1.25.1", + "version": "1.26.0", "description": "Coding agent powered by multi-model orchestration", "repository": "https://github.com/getkimchi/kimchi", "website": "https://kimchi.dev", @@ -996,8 +1040,8 @@ "distribution": { "binary": { "darwin-aarch64": { - "archive": "https://github.com/getkimchi/kimchi/releases/download/v1.25.1/kimchi_darwin_arm64.tar.gz", - "sha256": "f9b5f26b35198a4893c0c4847e6e6bfd24b12353decd4ae98025c0bea952c9bf", + "archive": "https://github.com/getkimchi/kimchi/releases/download/v1.26.0/kimchi_darwin_arm64.tar.gz", + "sha256": "ea69c20def2414c0f5816d40387c480e132e2dccacb1eddd1d753b6a3f81cfc8", "cmd": "./bin/kimchi", "args": [ "--mode", @@ -1005,8 +1049,8 @@ ] }, "darwin-x86_64": { - "archive": "https://github.com/getkimchi/kimchi/releases/download/v1.25.1/kimchi_darwin_amd64.tar.gz", - "sha256": "ade9e154ad5ef23a233a641bf7eae20722c3acb7cbf5c8323f8117777fbf1322", + "archive": "https://github.com/getkimchi/kimchi/releases/download/v1.26.0/kimchi_darwin_amd64.tar.gz", + "sha256": "076b0e53b3b6de0286399282c211c2821f15325efa8c5f4f09c27bd126a45a6c", "cmd": "./bin/kimchi", "args": [ "--mode", @@ -1014,8 +1058,8 @@ ] }, "linux-aarch64": { - "archive": "https://github.com/getkimchi/kimchi/releases/download/v1.25.1/kimchi_linux_arm64.tar.gz", - "sha256": "42301b962ec6c6feab6d92f90c077eddf194bac156e08acbd236acb7c4215f6f", + "archive": "https://github.com/getkimchi/kimchi/releases/download/v1.26.0/kimchi_linux_arm64.tar.gz", + "sha256": "48e9141691b852de09392cd8ff1eeb6f8dd4374d113ad8547d09541ab8ea271c", "cmd": "./bin/kimchi", "args": [ "--mode", @@ -1023,8 +1067,8 @@ ] }, "linux-x86_64": { - "archive": "https://github.com/getkimchi/kimchi/releases/download/v1.25.1/kimchi_linux_amd64.tar.gz", - "sha256": "2d782cf78d40d0303d72374ae6d827895b083afcfd8dc6be483d503a854cd242", + "archive": "https://github.com/getkimchi/kimchi/releases/download/v1.26.0/kimchi_linux_amd64.tar.gz", + "sha256": "a7a11fd5650630bc91600fbf941da99cf0349fdf33973d10e32c5091d14a478d", "cmd": "./bin/kimchi", "args": [ "--mode", @@ -1032,8 +1076,8 @@ ] }, "windows-x86_64": { - "archive": "https://github.com/getkimchi/kimchi/releases/download/v1.25.1/kimchi_windows_amd64.zip", - "sha256": "1b9f64e447d2a98ac4b44aacb7788ae33b85abe24a90a04b06bb8093434beb38", + "archive": "https://github.com/getkimchi/kimchi/releases/download/v1.26.0/kimchi_windows_amd64.zip", + "sha256": "df82e290154ccb1a1801eeecb5a5a3c78e74ed591396d5f74b75916e3dbb3982", "cmd": "./bin/kimchi.exe", "args": [ "--mode", @@ -1102,6 +1146,60 @@ }, "icon": "https://cdn.agentclientprotocol.com/registry/v1/latest/kimi.svg" }, + { + "id": "kiro", + "name": "Kiro CLI", + "version": "2.37.1", + "description": "The Kiro coding agent", + "repository": "https://github.com/kirodotdev/Kiro", + "website": "https://kiro.dev/docs/cli/acp/", + "authors": [ + "Amazon" + ], + "license": "proprietary", + "license_url": "https://kiro.dev/license/", + "distribution": { + "binary": { + "darwin-aarch64": { + "archive": "https://prod.download.cli.kiro.dev/stable/2.37.1/kirocli-universal-darwin.tar.gz", + "sha256": "c2b2bb628673cea6f05174528735ac1f82478dd936a27f0826f0cd7029699c8e", + "cmd": "./kirocli/bin/kiro-cli", + "args": [ + "acp", + "--agent-engine=v3" + ] + }, + "darwin-x86_64": { + "archive": "https://prod.download.cli.kiro.dev/stable/2.37.1/kirocli-universal-darwin.tar.gz", + "sha256": "c2b2bb628673cea6f05174528735ac1f82478dd936a27f0826f0cd7029699c8e", + "cmd": "./kirocli/bin/kiro-cli", + "args": [ + "acp", + "--agent-engine=v3" + ] + }, + "linux-aarch64": { + "archive": "https://prod.download.cli.kiro.dev/stable/2.37.1/kirocli-aarch64-linux.tar.gz", + "sha256": "10f0318a379b27ba9bbfb2a06b33db2abaedcde2ea0c10f46e3cffbce86368aa", + "cmd": "./kirocli/bin/kiro-cli-chat", + "args": [ + "acp", + "--agent-engine=v3" + ] + }, + "linux-x86_64": { + "archive": "https://prod.download.cli.kiro.dev/stable/2.37.1/kirocli-x86_64-linux.tar.gz", + "sha256": "cf6c7438934a169781b3d564e81bcdfccac901c5d659f35235ac19e0d01192a3", + "cmd": "./kirocli/bin/kiro-cli-chat", + "args": [ + "acp", + "--agent-engine=v3" + ] + } + } + }, + "icon": "https://cdn.agentclientprotocol.com/registry/v1/latest/kiro.svg" + }, { "id": "minimax-code", "name": "MiniMax Code", @@ -1449,7 +1547,7 @@ { "id": "qwen-code", "name": "Qwen Code", - "version": "0.26.4", + "version": "0.26.6", "description": "Alibaba's Qwen coding assistant", "repository": "https://github.com/QwenLM/qwen-code", "website": "https://qwenlm.github.io/qwen-code-docs/en/users/overview", @@ -1460,7 +1558,7 @@ "license_url": "https://github.com/QwenLM/qwen-code/blob/main/LICENSE", "distribution": { "npx": { - "package": "@qwen-code/qwen-code@0.26.4", + "package": "@qwen-code/qwen-code@0.26.6", "args": [ "--acp", "--experimental-skills" diff --git a/resources/model-db/providers.json b/resources/model-db/providers.json index ebc3cef051..03c25122a5 100644 --- a/resources/model-db/providers.json +++ b/resources/model-db/providers.json @@ -1,16 +1,16 @@ { "providers": { - "llmgateway": { - "id": "llmgateway", - "name": "DevPass (LLM Gateway)", - "display_name": "DevPass (LLM Gateway)", - "api": "https://api.llmgateway.io/v1", - "doc": "https://llmgateway.io/docs", + "inference": { + "id": "inference", + "name": "Inference", + "display_name": "Inference", + "api": "https://inference.net/v1", + "doc": "https://inference.net/models", "models": [ { - "id": "gemini-pro-latest", - "name": "Gemini Pro Latest", - "display_name": "Gemini Pro Latest", + "id": "google/gemma-3", + "name": "Google Gemma 3", + "display_name": "Google Gemma 3", "modalities": { "input": [ "text", @@ -21,193 +21,166 @@ ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 125000, + "output": 4096 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "low", - "medium", - "high" - ] - } + "supported": false }, "attachment": true, - "open_weights": false, - "release_date": "2026-02-27", - "last_updated": "2026-02-27", + "open_weights": true, + "knowledge": "2024-12", + "release_date": "2025-01-01", + "last_updated": "2025-01-01", "type": "chat" }, { - "id": "mistral-small-2506", - "name": "Mistral Small 3.2", - "display_name": "Mistral Small 3.2", + "id": "mistral/mistral-nemo-12b-instruct", + "name": "Mistral Nemo 12B Instruct", + "display_name": "Mistral Nemo 12B Instruct", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 16384 + "context": 16000, + "output": 4096 }, "temperature": true, "tool_call": true, "reasoning": { "supported": false }, - "attachment": true, + "attachment": false, "open_weights": true, - "knowledge": "2025-03", - "release_date": "2025-06-20", - "last_updated": "2025-06-20", + "knowledge": "2024-12", + "release_date": "2024-07-18", + "last_updated": "2024-07-18", "type": "chat" }, { - "id": "seed-1-8-251228", - "name": "Seed 1.8 (251228)", - "display_name": "Seed 1.8 (251228)", + "id": "osmosis/osmosis-structure-0.6b", + "name": "Osmosis Structure 0.6B", + "display_name": "Osmosis Structure 0.6B", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 256000, - "output": 8192 + "context": 4000, + "output": 2048 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "minimal", - "low", - "medium", - "high" - ] - } + "supported": false }, - "attachment": true, + "attachment": false, "open_weights": true, - "release_date": "2025-12-18", - "last_updated": "2025-12-18", + "knowledge": "2024-12", + "release_date": "2025-01-01", + "last_updated": "2025-01-01", "type": "chat" }, { - "id": "gemini-3-flash-preview", - "name": "Gemini 3 Flash Preview", - "display_name": "Gemini 3 Flash Preview", + "id": "qwen/qwen-2.5-7b-vision-instruct", + "name": "Qwen 2.5 7B Vision Instruct", + "display_name": "Qwen 2.5 7B Vision Instruct", "modalities": { "input": [ "text", - "image", - "video", - "audio", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 125000, + "output": 4096 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "level", - "level": "high", - "level_options": [ - "minimal", - "low", - "medium", - "high" - ], - "summaries": true, - "visibility": "summary", - "continuation": [ - "thought_signatures" - ] - } + "supported": false }, "attachment": true, - "open_weights": false, - "knowledge": "2025-01", - "release_date": "2025-12-17", - "last_updated": "2025-12-17", + "open_weights": true, + "knowledge": "2024-12", + "release_date": "2025-01-01", + "last_updated": "2025-01-01", "type": "chat" }, { - "id": "grok-4-1-fast-reasoning", - "name": "Grok 4.1 Fast Reasoning", - "display_name": "Grok 4.1 Fast Reasoning", + "id": "qwen/qwen3-embedding-4b", + "name": "Qwen 3 Embedding 4B", + "display_name": "Qwen 3 Embedding 4B", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 2000000, - "output": 30000 + "context": 32000, + "output": 2048 + }, + "temperature": false, + "tool_call": false, + "reasoning": { + "supported": false + }, + "attachment": false, + "open_weights": true, + "knowledge": "2024-12", + "release_date": "2025-06-05", + "last_updated": "2025-06-05", + "type": "embedding" + }, + { + "id": "meta/llama-3.1-8b-instruct", + "name": "Llama 3.1 8B Instruct", + "display_name": "Llama 3.1 8B Instruct", + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 16000, + "output": 4096 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "effort_options": [ - "low", - "medium", - "high" - ] - } + "supported": false }, - "attachment": true, - "open_weights": false, - "release_date": "2025-11-19", - "last_updated": "2025-11-19", + "attachment": false, + "open_weights": true, + "knowledge": "2023-12", + "release_date": "2024-07-23", + "last_updated": "2024-07-23", "type": "chat" }, { - "id": "gpt-5", - "name": "GPT-5", - "display_name": "GPT-5", + "id": "meta/llama-3.2-11b-vision-instruct", + "name": "Llama 3.2 11B Vision Instruct", + "display_name": "Llama 3.2 11B Vision Instruct", "modalities": { "input": [ "text", @@ -218,47 +191,25 @@ ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 16000, + "output": 4096 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", - "effort_options": [ - "minimal", - "low", - "medium", - "high" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" - } + "supported": false }, "attachment": true, - "open_weights": false, - "knowledge": "2024-09-30", - "release_date": "2025-08-07", - "last_updated": "2025-08-07", + "open_weights": true, + "knowledge": "2023-12", + "release_date": "2024-09-25", + "last_updated": "2024-09-25", "type": "chat" }, { - "id": "qwen3.7-max", - "name": "Qwen3.7 Max", - "display_name": "Qwen3.7 Max", + "id": "meta/llama-3.2-1b-instruct", + "name": "Llama 3.2 1B Instruct", + "display_name": "Llama 3.2 1B Instruct", "modalities": { "input": [ "text" @@ -268,39 +219,25 @@ ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 16000, + "output": 4096 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "effort_options": [ - "none", - "minimal", - "low", - "medium", - "high", - "xhigh", - "max" - ] - } + "supported": false }, "attachment": false, - "open_weights": false, - "release_date": "2026-05-21", - "last_updated": "2026-05-21", + "open_weights": true, + "knowledge": "2023-12", + "release_date": "2024-09-25", + "last_updated": "2024-09-25", "type": "chat" }, { - "id": "glm-4.7-flashx", - "name": "GLM-4.7-FlashX", - "display_name": "GLM-4.7-FlashX", + "id": "meta/llama-3.2-3b-instruct", + "name": "Llama 3.2 3B Instruct", + "display_name": "Llama 3.2 3B Instruct", "modalities": { "input": [ "text" @@ -310,39 +247,49 @@ ] }, "limit": { - "context": 200000, - "output": 131072 + "context": 16000, + "output": 4096 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true + "supported": false }, "attachment": false, "open_weights": true, - "knowledge": "2025-04", - "release_date": "2026-01-19", - "last_updated": "2026-01-19", + "knowledge": "2023-12", + "release_date": "2025-01-01", + "last_updated": "2025-01-01", "type": "chat" - }, + } + ] + }, + "tinfoil": { + "id": "tinfoil", + "name": "Tinfoil", + "display_name": "Tinfoil", + "api": "https://inference.tinfoil.sh/v1", + "doc": "https://docs.tinfoil.sh", + "models": [ { - "id": "hy4-preview", - "name": "Hy4 preview", - "display_name": "Hy4 preview", + "id": "kimi-k3", + "name": "Kimi K3", + "display_name": "Kimi K3", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 64000 + "context": 262144, + "output": 131072 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -352,56 +299,65 @@ "reasoning": { "supported": true, "effort_options": [ - "none", - "minimal", "low", - "medium", "high", - "xhigh", "max" ] } }, - "attachment": false, + "attachment": true, "open_weights": true, - "release_date": "2026-08-28", - "last_updated": "2026-08-28", + "release_date": "2026-07-16", + "last_updated": "2026-07-16", "type": "chat" }, { - "id": "gpt-4o-mini", - "name": "GPT-4o mini", - "display_name": "GPT-4o mini", + "id": "gemma4-31b", + "name": "Gemma 4 31B IT", + "display_name": "Gemma 4 31B IT", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 16384 + "context": 262144, + "output": 32768 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "none", + "minimal", + "low", + "medium", + "high", + "xhigh", + "max" + ] + } }, "attachment": true, - "open_weights": false, - "knowledge": "2023-09", - "release_date": "2024-07-18", - "last_updated": "2024-07-18", + "open_weights": true, + "knowledge": "2025-01", + "release_date": "2026-04-02", + "last_updated": "2026-04-02", "type": "chat" }, { - "id": "glm-5.3-fast", - "name": "GLM-5.3", - "display_name": "GLM-5.3", + "id": "gpt-oss-safeguard-120b", + "name": "gpt-oss-safeguard-120b", + "display_name": "gpt-oss-safeguard-120b", "modalities": { "input": [ "text" @@ -411,40 +367,38 @@ ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 131072, + "output": 32768 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true + "supported": false }, "attachment": false, "open_weights": true, - "release_date": "2026-08-14", - "last_updated": "2026-08-14", + "knowledge": "2024-06", + "release_date": "2025-10-29", + "last_updated": "2025-10-29", "type": "chat" }, { - "id": "gpt-5.5", - "name": "GPT-5.5", - "display_name": "GPT-5.5", + "id": "gpt-oss-120b", + "name": "gpt-oss-120b", + "display_name": "gpt-oss-120b", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 131072, + "output": 32768 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -453,35 +407,24 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", "effort_options": [ - "low", - "medium", - "high", - "xhigh" - ], - "verbosity": "medium", - "verbosity_options": [ "low", "medium", "high" - ], - "visibility": "hidden" + ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-12-01", - "release_date": "2026-04-23", - "last_updated": "2026-04-23", + "attachment": false, + "open_weights": true, + "knowledge": "2024-06", + "release_date": "2025-08-05", + "last_updated": "2025-08-05", "type": "chat" }, { - "id": "glm-4.5-airx", - "name": "GLM-4.5 AirX", - "display_name": "GLM-4.5 AirX", + "id": "glm-5-3", + "name": "GLM-5.3", + "display_name": "GLM-5.3", "modalities": { "input": [ "text" @@ -491,24 +434,34 @@ ] }, "limit": { - "context": 128000, - "output": 16384 + "context": 1048576, + "output": 131072 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "low", + "high", + "max" + ] + } }, "attachment": false, - "open_weights": false, - "release_date": "2025-07-28", - "last_updated": "2025-07-28", + "open_weights": true, + "release_date": "2026-08-14", + "last_updated": "2026-08-14", "type": "chat" }, { - "id": "muse-spark-1.3-contributor", - "name": "Muse Spark 1.3 Contributor", - "display_name": "Muse Spark 1.3 Contributor", + "id": "deepseek-v4-1-flash", + "name": "DeepSeek V4.1 Flash", + "display_name": "DeepSeek V4.1 Flash", "modalities": { "input": [ "text", @@ -520,7 +473,7 @@ }, "limit": { "context": 1048576, - "output": 1048576 + "output": 384000 }, "temperature": true, "tool_call": true, @@ -531,24 +484,27 @@ "extra_capabilities": { "reasoning": { "effort_options": [ + "none", "minimal", "low", "medium", "high", - "xhigh" + "xhigh", + "max" ] } }, "attachment": true, - "open_weights": false, - "release_date": "2026-09-02", - "last_updated": "2026-09-02", + "open_weights": true, + "knowledge": "2025-05", + "release_date": "2026-09-10", + "last_updated": "2026-09-10", "type": "chat" }, { - "id": "llama-3.1-70b-instruct", - "name": "Llama 3.1 70B Instruct", - "display_name": "Llama 3.1 70B Instruct", + "id": "llama3-3-70b", + "name": "Llama-3.3-70B-Instruct", + "display_name": "Llama-3.3-70B-Instruct", "modalities": { "input": [ "text" @@ -558,38 +514,73 @@ ] }, "limit": { - "context": 128000, - "output": 2048 + "context": 131072, + "output": 4096 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { "supported": false }, "attachment": false, "open_weights": true, "knowledge": "2023-12", - "release_date": "2024-07-23", - "last_updated": "2024-07-23", + "release_date": "2024-12-06", + "last_updated": "2024-12-06", "type": "chat" }, { - "id": "kimi-k2.8-preview", - "name": "Kimi K2.8 Preview", - "display_name": "Kimi K2.8 Preview", + "id": "nomic-embed-text", + "name": "Nomic Embed Text v1.5", + "display_name": "Nomic Embed Text v1.5", + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 8192, + "output": 768 + }, + "temperature": false, + "tool_call": false, + "reasoning": { + "supported": false + }, + "attachment": false, + "open_weights": true, + "release_date": "2024-02", + "last_updated": "2024-02", + "type": "chat" + } + ] + }, + "digitalocean": { + "id": "digitalocean", + "name": "DigitalOcean", + "display_name": "DigitalOcean", + "api": "https://inference.do-ai.run/v1", + "doc": "https://docs.digitalocean.com/products/gradient-ai-platform/details/models/", + "models": [ + { + "id": "openai-gpt-5.3-codex", + "name": "OpenAI GPT-5.3 Codex", + "display_name": "OpenAI GPT-5.3 Codex", "modalities": { "input": [ "text", - "image", - "video" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 1000000 + "context": 400000, + "output": 128000 }, "temperature": false, "tool_call": true, @@ -601,25 +592,24 @@ "reasoning": { "effort_options": [ "none", - "minimal", "low", "medium", "high", - "xhigh", - "max" + "xhigh" ] } }, "attachment": true, "open_weights": false, - "release_date": "2026-09-11", - "last_updated": "2026-09-11", + "knowledge": "2025-08-31", + "release_date": "2026-02-05", + "last_updated": "2026-02-05", "type": "chat" }, { - "id": "grok-4-6", - "name": "Grok 4.6", - "display_name": "Grok 4.6", + "id": "kimi-k3", + "name": "Kimi K3", + "display_name": "Kimi K3", "modalities": { "input": [ "text", @@ -630,10 +620,10 @@ ] }, "limit": { - "context": 500000, - "output": 500000 + "context": 1048576, + "output": 131072 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -641,26 +631,30 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ - "minimal", "low", - "medium", "high", - "xhigh" + "max" ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2026-02-01", - "release_date": "2026-08-12", - "last_updated": "2026-08-12", + "open_weights": true, + "release_date": "2026-07-16", + "last_updated": "2026-07-16", "type": "chat" }, { - "id": "minimax-text-01", - "name": "MiniMax Text 01", - "display_name": "MiniMax Text 01", + "id": "e5-large-v2", + "name": "E5 Large v2", + "display_name": "E5 Large v2", "modalities": { "input": [ "text" @@ -670,25 +664,50 @@ ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 512, + "output": 1024 }, - "temperature": true, + "temperature": false, "tool_call": false, "reasoning": { - "supported": true, - "default": true + "supported": false }, "attachment": false, "open_weights": true, - "release_date": "2025-01-15", - "last_updated": "2025-01-15", - "type": "chat" + "release_date": "2023-05", + "last_updated": "2023-05", + "type": "embedding" }, { - "id": "gpt-6-astra", - "name": "GPT-6 Astra", - "display_name": "GPT-6 Astra", + "id": "qwen3-tts-voicedesign", + "name": "Qwen3 TTS VoiceDesign", + "display_name": "Qwen3 TTS VoiceDesign", + "modalities": { + "input": [ + "text" + ], + "output": [ + "audio" + ] + }, + "limit": { + "context": 32768, + "output": 1 + }, + "temperature": false, + "tool_call": false, + "reasoning": { + "supported": false + }, + "attachment": false, + "open_weights": true, + "release_date": "2026-04-21", + "last_updated": "2026-04-30" + }, + { + "id": "openai-gpt-5.4-mini", + "name": "OpenAI GPT-5.4 Mini", + "display_name": "OpenAI GPT-5.4 Mini", "modalities": { "input": [ "text", @@ -699,8 +718,8 @@ ] }, "limit": { - "context": 1050000, - "output": 1050000 + "context": 400000, + "output": 128000 }, "temperature": false, "tool_call": true, @@ -710,97 +729,79 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, "effort_options": [ + "none", "low", "medium", "high", - "xhigh", - "max" + "xhigh" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-04-30", - "release_date": "2026-09-04", - "last_updated": "2026-09-04", + "knowledge": "2025-08-31", + "release_date": "2026-03-17", + "last_updated": "2026-03-17", "type": "chat" }, { - "id": "gpt-5.4", - "name": "GPT-5.4", - "display_name": "GPT-5.4", + "id": "minimax-m2.5", + "name": "MiniMax M2.5 (Public Preview)", + "display_name": "MiniMax M2.5 (Public Preview)", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 65536, + "output": 65536 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "none", "effort_options": [ - "none", - "low", - "medium", - "high", - "xhigh" - ], - "verbosity": "medium", - "verbosity_options": [ "low", "medium", "high" - ], - "visibility": "hidden" + ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-03-05", - "last_updated": "2026-03-05", + "attachment": false, + "open_weights": true, + "knowledge": "2025-08", + "release_date": "2026-02-12", + "last_updated": "2026-04-16", "type": "chat" }, { - "id": "gemini-3.5-flash-lite", - "name": "Gemini 3.5 Flash Lite", - "display_name": "Gemini 3.5 Flash Lite", + "id": "openai-gpt-6-astra", + "name": "OpenAI GPT-6 Astra", + "display_name": "OpenAI GPT-6 Astra", "modalities": { "input": [ "text", - "image", - "video", - "audio", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 1050000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -808,41 +809,40 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, "effort_options": [ - "minimal", "low", "medium", - "high" + "high", + "xhigh", + "max" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-03", - "release_date": "2026-07-21", - "last_updated": "2026-07-21", + "knowledge": "2026-04-30", + "release_date": "2026-09-04", + "last_updated": "2026-09-04", "type": "chat" }, { - "id": "qwen3.5-9b", - "name": "Qwen3.5 9B", - "display_name": "Qwen3.5 9B", + "id": "openai-o3", + "name": "OpenAI o3", + "display_name": "OpenAI o3", "modalities": { "input": [ "text", - "image", - "video" + "image" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 65536 + "context": 200000, + "output": 100000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -850,66 +850,79 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "low", + "medium", + "high" ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-02-23", - "last_updated": "2026-02-23", + "open_weights": false, + "knowledge": "2024-05", + "release_date": "2025-04-16", + "last_updated": "2025-04-16", "type": "chat" }, { - "id": "ministral-14b-2512", - "name": "Ministral 14B", - "display_name": "Ministral 14B", + "id": "nemotron-nano-12b-v2-vl", + "name": "Nemotron-nano 12b v2-vl", + "display_name": "Nemotron-nano 12b v2-vl", "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 8192 + "context": 128000, + "output": 16384 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "none", + "low", + "medium", + "high", + "max" + ] + } }, "attachment": true, "open_weights": true, - "release_date": "2025-12-02", - "last_updated": "2025-12-02", + "release_date": "2025-10-28", + "last_updated": "2025-10-28", "type": "chat" }, { - "id": "minimax-m2.1", - "name": "MiniMax-M2.1", - "display_name": "MiniMax-M2.1", + "id": "openai-gpt-6-luna", + "name": "OpenAI GPT-6 Luna", + "display_name": "OpenAI GPT-6 Luna", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 204800, - "output": 131072 + "context": 1050000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -917,57 +930,107 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, "effort_options": [ + "none", "low", "medium", "high", - "xhigh", - "max" + "xhigh" ] } }, + "attachment": true, + "open_weights": false, + "knowledge": "2026-05-18", + "release_date": "2026-09-22", + "last_updated": "2026-09-22", + "type": "chat" + }, + { + "id": "multi-qa-mpnet-base-dot-v1", + "name": "Multi-QA-mpnet-base-dot-v1", + "display_name": "Multi-QA-mpnet-base-dot-v1", + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 512, + "output": 768 + }, + "temperature": false, + "tool_call": false, + "reasoning": { + "supported": false + }, "attachment": false, "open_weights": true, - "release_date": "2025-12-23", - "last_updated": "2025-12-23", - "type": "chat" + "release_date": "2021-08-23", + "last_updated": "2021-08-23", + "type": "embedding" }, { - "id": "glm-4.5v", - "name": "GLM-4.5V", - "display_name": "GLM-4.5V", + "id": "openai-gpt-image-1.5", + "name": "OpenAI GPT Image 1.5", + "display_name": "OpenAI GPT Image 1.5", "modalities": { "input": [ "text", - "image", - "video" + "image" ], "output": [ - "text" + "text", + "image" ] }, "limit": { - "context": 128000, + "context": 8192, "output": 16384 }, - "temperature": true, - "tool_call": true, + "temperature": false, + "tool_call": false, "reasoning": { - "supported": true, - "default": true + "supported": false }, "attachment": true, + "open_weights": false, + "release_date": "2025-11-25", + "last_updated": "2025-11-25" + }, + { + "id": "stable-diffusion-3.5-large", + "name": "Stable Diffusion 3.5 Large", + "display_name": "Stable Diffusion 3.5 Large", + "modalities": { + "input": [ + "text" + ], + "output": [ + "image" + ] + }, + "limit": { + "context": 256, + "output": 1 + }, + "temperature": false, + "tool_call": false, + "reasoning": { + "supported": false + }, + "attachment": false, "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-08-11", - "last_updated": "2025-08-11", - "type": "chat" + "release_date": "2024-10-22", + "last_updated": "2026-04-30" }, { - "id": "devstral-2512", - "name": "Devstral 2", - "display_name": "Devstral 2", + "id": "qwen3-embedding-0.6b", + "name": "Qwen3 Embedding 0.6B", + "display_name": "Qwen3 Embedding 0.6B", "modalities": { "input": [ "text" @@ -977,25 +1040,24 @@ ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 8000, + "output": 1024 }, - "temperature": true, - "tool_call": true, + "temperature": false, + "tool_call": false, "reasoning": { "supported": false }, "attachment": false, "open_weights": true, - "knowledge": "2025-12", - "release_date": "2025-12-09", - "last_updated": "2025-12-09", - "type": "chat" + "release_date": "2025-06-03", + "last_updated": "2026-04-16", + "type": "embedding" }, { - "id": "seed-2-0-lite-260428", - "name": "Seed 2.0 Lite (260428)", - "display_name": "Seed 2.0 Lite (260428)", + "id": "openai-gpt-6-1-sol", + "name": "OpenAI GPT-6.1 Sol", + "display_name": "OpenAI GPT-6.1 Sol", "modalities": { "input": [ "text", @@ -1006,10 +1068,10 @@ ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 1050000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -1018,29 +1080,29 @@ "extra_capabilities": { "reasoning": { "effort_options": [ - "minimal", "low", "medium", - "high" + "high", + "xhigh", + "max" ] } }, "attachment": true, "open_weights": false, - "release_date": "2026-04-28", - "last_updated": "2026-04-28", + "knowledge": "2026-04-30", + "release_date": "2026-09-29", + "last_updated": "2026-09-29", "type": "chat" }, { - "id": "glm-5.3-flash", - "name": "GLM-5.3-Flash", - "display_name": "GLM-5.3-Flash", + "id": "nemotron-3.5-super-vl", + "name": "NVIDIA Nemotron 3.5 Super VL", + "display_name": "NVIDIA Nemotron 3.5 Super VL", "modalities": { "input": [ "text", - "image", - "video", - "pdf" + "image" ], "output": [ "text" @@ -1048,7 +1110,7 @@ }, "limit": { "context": 1048576, - "output": 131072 + "output": 1048576 }, "temperature": true, "tool_call": true, @@ -1056,33 +1118,20 @@ "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "effort_options": [ - "none", - "minimal", - "low", - "medium", - "high", - "xhigh", - "max" - ] - } - }, "attachment": true, "open_weights": true, - "release_date": "2026-08-26", - "last_updated": "2026-08-26", + "release_date": "2026-10-15", + "last_updated": "2026-10-15", "type": "chat" }, { - "id": "deepseek-v4-flash", - "name": "DeepSeek V4 Flash", - "display_name": "DeepSeek V4 Flash", + "id": "openai-gpt-5.6-luna", + "name": "OpenAI GPT-5.6 Luna", + "display_name": "OpenAI GPT-5.6 Luna", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" @@ -1090,9 +1139,9 @@ }, "limit": { "context": 1050000, - "output": 384000 + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -1100,53 +1149,41 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "high", "effort_options": [ + "none", "low", + "medium", "high", + "xhigh", "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." ] } }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-04-24", - "last_updated": "2026-04-24", + "attachment": true, + "open_weights": false, + "knowledge": "2026-02-16", + "release_date": "2026-07-09", + "last_updated": "2026-07-09", "type": "chat" }, { - "id": "mimo-v2.6-pro", - "name": "MiMo-V2.6-Pro", - "display_name": "MiMo-V2.6-Pro", + "id": "openai-gpt-5.2", + "name": "OpenAI GPT-5.2", + "display_name": "OpenAI GPT-5.2", "modalities": { "input": [ "text", - "image", - "audio", - "video" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 400000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -1155,40 +1192,36 @@ "extra_capabilities": { "reasoning": { "effort_options": [ - "none", "minimal", "low", "medium", - "high", - "xhigh", - "max" + "high" ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-09-22", - "last_updated": "2026-09-22", + "open_weights": false, + "knowledge": "2025-08-31", + "release_date": "2025-12-11", + "last_updated": "2025-12-11", "type": "chat" }, { - "id": "glm-5v-turbo", - "name": "GLM-5V-Turbo", - "display_name": "GLM-5V-Turbo", + "id": "deepseek-v4.1-flash-1017", + "name": "DeepSeek V4.1 Flash 1017", + "display_name": "DeepSeek V4.1 Flash 1017", "modalities": { "input": [ "text", - "image", - "video", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 131072 + "context": 1048576, + "output": 1048576 }, "temperature": true, "tool_call": true, @@ -1200,7 +1233,6 @@ "reasoning": { "effort_options": [ "low", - "medium", "high", "xhigh", "max" @@ -1208,30 +1240,29 @@ } }, "attachment": true, - "open_weights": false, - "release_date": "2026-04-01", - "last_updated": "2026-04-01", + "open_weights": true, + "knowledge": "2025-05", + "release_date": "2026-10-17", + "last_updated": "2026-10-17", "type": "chat" }, { - "id": "claude-sonnet-5-5", - "name": "Claude Sonnet 5.5", - "display_name": "Claude Sonnet 5.5", + "id": "mimo-v2.5-pro", + "name": "MiMo V2.5 Pro", + "display_name": "MiMo V2.5 Pro", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 262144, + "output": 262144 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -1239,26 +1270,30 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" + "none", + "high" ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-06", - "release_date": "2026-09-28", - "last_updated": "2026-09-28", + "attachment": false, + "open_weights": true, + "knowledge": "2024-12", + "release_date": "2026-04-22", + "last_updated": "2026-04-22", "type": "chat" }, { - "id": "qwen3-vl-30b-a3b-instruct", - "name": "Qwen3 VL 30B A3B Instruct", - "display_name": "Qwen3 VL 30B A3B Instruct", + "id": "openai-gpt-4o", + "name": "OpenAI GPT-4o", + "display_name": "OpenAI GPT-4o", "modalities": { "input": [ "text", @@ -1269,8 +1304,8 @@ ] }, "limit": { - "context": 262144, - "output": 8192 + "context": 128000, + "output": 16384 }, "temperature": true, "tool_call": true, @@ -1278,15 +1313,16 @@ "supported": false }, "attachment": true, - "open_weights": true, - "release_date": "2025-10-04", - "last_updated": "2025-10-04", + "open_weights": false, + "knowledge": "2023-09", + "release_date": "2024-05-13", + "last_updated": "2024-08-06", "type": "chat" }, { - "id": "gpt-oss-20b", - "name": "GPT OSS 20B", - "display_name": "GPT OSS 20B", + "id": "nemotron-3-ultra-550b", + "name": "Nemotron 3 Ultra", + "display_name": "Nemotron 3 Ultra", "modalities": { "input": [ "text" @@ -1297,17 +1333,73 @@ }, "limit": { "context": 131072, - "output": 32766 + "output": 131072 + }, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": false + }, + "attachment": false, + "open_weights": true, + "knowledge": "2025-09", + "release_date": "2026-06-04", + "last_updated": "2026-06-04", + "type": "chat" + }, + { + "id": "deepseek-4-flash", + "name": "Deepseek V4 Flash", + "display_name": "Deepseek V4 Flash", + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1048576, + "output": 384000 }, "temperature": true, "tool_call": true, + "reasoning": { + "supported": false + }, + "attachment": false, + "open_weights": true, + "knowledge": "2025-05", + "release_date": "2026-04-24", + "last_updated": "2026-04-24", + "type": "chat" + }, + { + "id": "openai-o1", + "name": "OpenAI o1", + "display_name": "OpenAI o1", + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 200000, + "output": 100000 + }, + "temperature": false, + "tool_call": true, "reasoning": { "supported": true, "default": true }, "extra_capabilities": { "reasoning": { - "supported": true, "effort_options": [ "low", "medium", @@ -1315,17 +1407,17 @@ ] } }, - "attachment": false, - "open_weights": true, - "knowledge": "2024-06", - "release_date": "2025-08-05", - "last_updated": "2025-08-05", + "attachment": true, + "open_weights": false, + "knowledge": "2023-09", + "release_date": "2024-12-05", + "last_updated": "2024-12-05", "type": "chat" }, { - "id": "glm-5-turbo", - "name": "GLM-5-Turbo", - "display_name": "GLM-5-Turbo", + "id": "glm-5.3-flash", + "name": "GLM5.3 Flash", + "display_name": "GLM5.3 Flash", "modalities": { "input": [ "text" @@ -1335,7 +1427,7 @@ ] }, "limit": { - "context": 200000, + "context": 1048576, "output": 131072 }, "temperature": true, @@ -1357,30 +1449,29 @@ } }, "attachment": false, - "open_weights": false, - "release_date": "2026-03-16", - "last_updated": "2026-03-16", + "open_weights": true, + "release_date": "2026-08-26", + "last_updated": "2026-08-26", "type": "chat" }, { - "id": "step-3.7-flash", - "name": "Step 3.7 Flash", - "display_name": "Step 3.7 Flash", + "id": "openai-gpt-5.6-terra", + "name": "OpenAI GPT-5.6 Terra", + "display_name": "OpenAI GPT-5.6 Terra", "modalities": { "input": [ "text", - "image", - "video" + "image" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 256000 + "context": 1050000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -1389,7 +1480,7 @@ "extra_capabilities": { "reasoning": { "effort_options": [ - "minimal", + "none", "low", "medium", "high", @@ -1399,59 +1490,72 @@ } }, "attachment": true, - "open_weights": true, - "knowledge": "2026-03-01", - "release_date": "2026-05-29", - "last_updated": "2026-05-29", + "open_weights": false, + "knowledge": "2026-02-16", + "release_date": "2026-07-09", + "last_updated": "2026-07-09", "type": "chat" }, { - "id": "claude-haiku-5-5", - "name": "Claude Haiku 5.5", - "display_name": "Claude Haiku 5.5", + "id": "anthropic-claude-opus-4.7", + "name": "Anthropic Claude Opus 4.7", + "display_name": "Anthropic Claude Opus 4.7", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 200000, + "output": 8192 }, "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": false, + "mode": "effort", + "effort": "high", "effort_options": [ - "none", "low", "medium", "high", "xhigh", "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "omitted", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Claude Opus 4.7 and newer Opus models require thinking.type = \"adaptive\" to enable thinking explicitly.", + "Manual budget_tokens requests return 400 on Claude Opus 4.7 and newer adaptive-only Opus models.", + "task_budget is separate from thinking control and should not be treated as a thinking budget." ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-06", - "release_date": "2026-10-07", - "last_updated": "2026-10-07", + "knowledge": "2026-01-31", + "release_date": "2026-04-16", + "last_updated": "2026-04-16", "type": "chat" }, { - "id": "deepseek-v4.1-flash-1017", - "name": "DeepSeek V4.1 Flash 1017", - "display_name": "DeepSeek V4.1 Flash 1017", + "id": "anthropic-claude-fable-5.1", + "name": "Anthropic Claude Fable 5.1", + "display_name": "Anthropic Claude Fable 5.1", "modalities": { "input": [ "text", @@ -1462,10 +1566,10 @@ ] }, "limit": { - "context": 1050000, - "output": 1050000 + "context": 1000000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -1473,24 +1577,41 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "high", "effort_options": [ "low", + "medium", "high", + "xhigh", "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "omitted", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Adaptive thinking is always on for Claude Fable 5 and Claude Mythos 5; thinking.type = \"disabled\" is rejected.", + "Manual budget_tokens requests return 400 on Claude Fable 5 and Claude Mythos 5.", + "thinking.display defaults to omitted; set display to summarized to receive readable thinking summaries." ] } }, "attachment": true, - "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-10-17", - "last_updated": "2026-10-17", + "open_weights": false, + "knowledge": "2026-06", + "release_date": "2026-09-01", + "last_updated": "2026-09-01", "type": "chat" }, { - "id": "mistral-large-2512", - "name": "Mistral Large 3", - "display_name": "Mistral Large 3", + "id": "openai-gpt-6-sol", + "name": "OpenAI GPT-6 Sol", + "display_name": "OpenAI GPT-6 Sol", "modalities": { "input": [ "text", @@ -1501,38 +1622,49 @@ ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 1050000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "none", + "low", + "medium", + "high", + "xhigh" + ] + } }, "attachment": true, - "open_weights": true, - "knowledge": "2024-11", - "release_date": "2025-12-02", - "last_updated": "2025-12-02", + "open_weights": false, + "knowledge": "2026-04-20", + "release_date": "2026-09-22", + "last_updated": "2026-09-22", "type": "chat" }, { - "id": "kimi-k3", - "name": "Kimi K3", - "display_name": "Kimi K3", + "id": "openai-gpt-5.4-nano", + "name": "OpenAI GPT-5.4 Nano", + "display_name": "OpenAI GPT-5.4 Nano", "modalities": { "input": [ "text", - "image", - "video" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 400000, + "output": 128000 }, "temperature": false, "tool_call": true, @@ -1542,28 +1674,26 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, "effort_options": [ "none", - "minimal", "low", "medium", "high", - "xhigh", - "max" + "xhigh" ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-07-16", - "last_updated": "2026-07-16", + "open_weights": false, + "knowledge": "2025-08-31", + "release_date": "2026-03-17", + "last_updated": "2026-03-17", "type": "chat" }, { - "id": "qwen3-vl-plus", - "name": "Qwen3-VL Plus", - "display_name": "Qwen3-VL Plus", + "id": "anthropic-claude-haiku-4.5", + "name": "Anthropic Claude Haiku 4.5", + "display_name": "Anthropic Claude Haiku 4.5", "modalities": { "input": [ "text", @@ -1574,37 +1704,47 @@ ] }, "limit": { - "context": 262144, - "output": 32768 + "context": 200000, + "output": 8192 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": false, + "mode": "budget", + "budget": { + "min": 1024, + "unit": "tokens" + }, "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ "thinking_blocks" + ], + "notes": [ + "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", + "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-04", - "release_date": "2025-09-23", - "last_updated": "2025-09-23", + "knowledge": "2025-02-28", + "release_date": "2025-10-15", + "last_updated": "2025-10-15", "type": "chat" }, { - "id": "glm-4-32b-0414-128k", - "name": "GLM-4 32B (0414-128k)", - "display_name": "GLM-4 32B (0414-128k)", + "id": "anthropic-claude-5-sonnet", + "name": "Anthropic Claude Sonnet 5", + "display_name": "Anthropic Claude Sonnet 5", "modalities": { "input": [ "text" @@ -1614,38 +1754,10 @@ ] }, "limit": { - "context": 128000, - "output": 16384 - }, - "temperature": true, - "tool_call": true, - "reasoning": { - "supported": false - }, - "attachment": false, - "open_weights": false, - "release_date": "2025-04-14", - "last_updated": "2025-04-14", - "type": "chat" - }, - { - "id": "seed-1-6-250915", - "name": "Seed 1.6 (250915)", - "display_name": "Seed 1.6 (250915)", - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 256000, - "output": 8192 + "context": 1000000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -1654,23 +1766,24 @@ "extra_capabilities": { "reasoning": { "effort_options": [ - "minimal", "low", "medium", - "high" + "high", + "xhigh" ] } }, - "attachment": true, - "open_weights": true, - "release_date": "2025-09-15", - "last_updated": "2025-09-15", + "attachment": false, + "open_weights": false, + "knowledge": "2026-01-31", + "release_date": "2026-06-30", + "last_updated": "2026-06-30", "type": "chat" }, { - "id": "deepseek-v4-pro", - "name": "DeepSeek V4 Pro", - "display_name": "DeepSeek V4 Pro", + "id": "openai-gpt-oss-20b", + "name": "OpenAI GPT-oss-20b", + "display_name": "OpenAI GPT-oss-20b", "modalities": { "input": [ "text" @@ -1680,8 +1793,8 @@ ] }, "limit": { - "context": 1050000, - "output": 384000 + "context": 128000, + "output": 4096 }, "temperature": true, "tool_call": true, @@ -1691,52 +1804,38 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "high", "effort_options": [ "low", - "high", - "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." + "medium", + "high" ] } }, "attachment": false, "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-04-24", - "last_updated": "2026-04-24", + "knowledge": "2024-06", + "release_date": "2025-08-05", + "last_updated": "2025-08-05", "type": "chat" }, { - "id": "claude-opus-5", - "name": "Claude Opus 5", - "display_name": "Claude Opus 5", + "id": "deepseek-v4.1-flash", + "name": "DeepSeek V4.1 Flash", + "display_name": "DeepSeek V4.1 Flash", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 1048576, + "output": 1048576 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -1746,9 +1845,7 @@ "reasoning": { "supported": true, "effort_options": [ - "none", "low", - "medium", "high", "xhigh", "max" @@ -1756,29 +1853,27 @@ } }, "attachment": true, - "open_weights": false, - "knowledge": "2026-05", - "release_date": "2026-07-24", - "last_updated": "2026-07-24", + "open_weights": true, + "knowledge": "2025-05", + "release_date": "2026-09-10", + "last_updated": "2026-09-10", "type": "chat" }, { - "id": "glm-4.6v", - "name": "GLM-4.6V", - "display_name": "GLM-4.6V", + "id": "arcee-trinity-large-thinking", + "name": "Arcee Trinity Large Thinking (Public Preview)", + "display_name": "Arcee Trinity Large Thinking (Public Preview)", "modalities": { "input": [ - "text", - "image", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 32768 + "context": 128000, + "output": 32000 }, "temperature": true, "tool_call": true, @@ -1786,17 +1881,25 @@ "supported": true, "default": true }, - "attachment": true, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "low", + "medium", + "high" + ] + } + }, + "attachment": false, "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-12-08", - "last_updated": "2025-12-08", + "release_date": "2026-04-01", + "last_updated": "2026-05-28", "type": "chat" }, { - "id": "glm-4.7", - "name": "GLM-4.7", - "display_name": "GLM-4.7", + "id": "bge-reranker-v2-m3", + "name": "BGE Reranker v2 M3", + "display_name": "BGE Reranker v2 M3", "modalities": { "input": [ "text" @@ -1806,41 +1909,27 @@ ] }, "limit": { - "context": 204800, - "output": 131072 + "context": 8192, + "output": 1 }, - "temperature": true, - "tool_call": true, + "temperature": false, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } + "supported": false }, "attachment": false, "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-12-22", - "last_updated": "2025-12-22", - "type": "chat" + "release_date": "2024-03-15", + "last_updated": "2024-03-15", + "type": "rerank" }, { - "id": "qwen3-vl-235b-a22b-instruct", - "name": "Qwen3 VL 235B A22B Instruct", - "display_name": "Qwen3 VL 235B A22B Instruct", + "id": "mistral-3-14B", + "name": "Ministral 3 14B", + "display_name": "Ministral 3 14B", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" @@ -1848,38 +1937,34 @@ }, "limit": { "context": 262144, - "output": 8192 + "output": 128000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": false }, - "attachment": true, + "attachment": false, "open_weights": true, - "knowledge": "2025-03-31", - "release_date": "2025-09-23", - "last_updated": "2025-09-23", + "release_date": "2025-12-02", + "last_updated": "2025-12-02", "type": "chat" }, { - "id": "mimo-v2.6-flash", - "name": "MiMo-V2.6-Flash", - "display_name": "MiMo-V2.6-Flash", + "id": "openai-gpt-oss-120b", + "name": "OpenAI GPT-oss-120b", + "display_name": "OpenAI GPT-oss-120b", "modalities": { "input": [ - "text", - "image", - "audio", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 128000, + "output": 4096 }, "temperature": true, "tool_call": true, @@ -1889,42 +1974,45 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ - "none", - "minimal", "low", "medium", - "high", - "xhigh", - "max" + "high" ] } }, - "attachment": true, + "attachment": false, "open_weights": true, - "release_date": "2026-09-22", - "last_updated": "2026-09-22", + "knowledge": "2024-06", + "release_date": "2025-08-05", + "last_updated": "2025-08-05", "type": "chat" }, { - "id": "qwen3.6-flash", - "name": "Qwen3.6 Flash", - "display_name": "Qwen3.6 Flash", + "id": "anthropic-claude-opus-5.5", + "name": "Anthropic Claude Opus 5.5", + "display_name": "Anthropic Claude Opus 5.5", "modalities": { "input": [ "text", - "image", - "video" + "image" ], "output": [ "text" ] }, "limit": { - "context": 983616, - "output": 65536 + "context": 1000000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -1932,16 +2020,7 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], "effort_options": [ - "none", - "minimal", "low", "medium", "high", @@ -1952,14 +2031,15 @@ }, "attachment": true, "open_weights": false, - "release_date": "2026-04-27", - "last_updated": "2026-04-27", + "knowledge": "2026-06", + "release_date": "2026-09-22", + "last_updated": "2026-09-22", "type": "chat" }, { - "id": "qwen-flash", - "name": "Qwen Flash", - "display_name": "Qwen Flash", + "id": "deepseek-v4-pro", + "name": "Deepseek V4 Pro", + "display_name": "Deepseek V4 Pro", "modalities": { "input": [ "text" @@ -1969,8 +2049,8 @@ ] }, "limit": { - "context": 1000000, - "output": 32768 + "context": 1048576, + "output": 384000 }, "temperature": true, "tool_call": true, @@ -1981,39 +2061,47 @@ "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "high", + "effort_options": [ + "low", + "high", + "max" + ], "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ "thinking_blocks" + ], + "notes": [ + "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." ] } }, "attachment": false, - "open_weights": false, - "knowledge": "2024-04", - "release_date": "2025-07-28", - "last_updated": "2025-07-28", + "open_weights": true, + "knowledge": "2025-05", + "release_date": "2026-04-24", + "last_updated": "2026-04-24", "type": "chat" }, { - "id": "qwen3.8-omni-flash", - "name": "Qwen3.8 Omni Flash", - "display_name": "Qwen3.8 Omni Flash", + "id": "glm-5.3", + "name": "GLM5.3", + "display_name": "GLM5.3", "modalities": { "input": [ - "text", - "image", - "audio", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 983616, - "output": 131072 + "context": 1048576, + "output": 128000 }, "temperature": true, "tool_call": true, @@ -2023,40 +2111,38 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, "effort_options": [ - "none", - "minimal", "low", - "medium", "high", - "xhigh", "max" ] } }, - "attachment": true, - "open_weights": false, - "release_date": "2026-09-17", - "last_updated": "2026-09-17", + "attachment": false, + "open_weights": true, + "release_date": "2026-08-14", + "last_updated": "2026-08-14", "type": "chat" }, { - "id": "qwen3.8-2.4t-a95b", - "name": "Qwen3.8 2.4T A95B", - "display_name": "Qwen3.8 2.4T A95B", + "id": "openai-gpt-5.2-pro", + "name": "OpenAI GPT-5.2 Pro", + "display_name": "OpenAI GPT-5.2 Pro", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 1010000, - "output": 131072 + "context": 400000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -2064,28 +2150,52 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, "effort_options": [ - "none", "minimal", "low", "medium", - "high", - "xhigh", - "max" + "high" ] } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-08-12", - "last_updated": "2026-08-12", + "attachment": true, + "open_weights": false, + "knowledge": "2025-08-31", + "release_date": "2025-12-11", + "last_updated": "2025-12-11", "type": "chat" }, { - "id": "gemma-4-31b-it", - "name": "Gemma 4 31B IT", - "display_name": "Gemma 4 31B IT", + "id": "openai-gpt-image-1", + "name": "GPT Image 1", + "display_name": "GPT Image 1", + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "image" + ] + }, + "limit": { + "context": 8192, + "output": 8192 + }, + "temperature": false, + "tool_call": false, + "reasoning": { + "supported": false + }, + "attachment": true, + "open_weights": false, + "release_date": "2025-04-24", + "last_updated": "2025-04-24" + }, + { + "id": "openai-gpt-5-nano", + "name": "OpenAI GPT-5 Nano", + "display_name": "OpenAI GPT-5 Nano", "modalities": { "input": [ "text", @@ -2096,10 +2206,10 @@ ] }, "limit": { - "context": 262144, - "output": 32768 + "context": 400000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -2107,35 +2217,39 @@ }, "extra_capabilities": { "reasoning": { - "supported": true + "effort_options": [ + "minimal", + "low", + "medium", + "high" + ] } }, "attachment": true, - "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-04-02", - "last_updated": "2026-04-02", + "open_weights": false, + "knowledge": "2024-05-30", + "release_date": "2025-08-07", + "last_updated": "2025-08-07", "type": "chat" }, { - "id": "claude-opus-4-7", - "name": "Claude Opus 4.7", - "display_name": "Claude Opus 4.7", + "id": "anthropic-claude-opus-4.6", + "name": "Anthropic Claude Opus 4.6", + "display_name": "Anthropic Claude Opus 4.6", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 200000, + "output": 8192 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -2145,53 +2259,56 @@ "reasoning": { "supported": true, "default_enabled": false, - "mode": "effort", + "mode": "mixed", + "budget": { + "min": 1024, + "unit": "tokens" + }, "effort": "high", "effort_options": [ "low", "medium", "high", - "xhigh", "max" ], "interleaved": true, "summaries": true, - "visibility": "omitted", + "visibility": "summary", "continuation": [ "thinking_blocks" ], "notes": [ - "Claude Opus 4.7 and newer Opus models require thinking.type = \"adaptive\" to enable thinking explicitly.", - "Manual budget_tokens requests return 400 on Claude Opus 4.7 and newer adaptive-only Opus models.", - "task_budget is separate from thinking control and should not be treated as a thinking budget." + "Anthropic recommends adaptive thinking with effort for Claude 4.6; budget_tokens remains a deprecated compatibility path.", + "Anthropic API defaults effort to high; lower effort levels should be chosen per workload." ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-01-31", - "release_date": "2026-04-16", - "last_updated": "2026-04-16", + "knowledge": "2025-05-31", + "release_date": "2026-02-05", + "last_updated": "2026-03-13", "type": "chat" }, { - "id": "minimax-m2.1-lightning", - "name": "MiniMax M2.1 Lightning", - "display_name": "MiniMax M2.1 Lightning", + "id": "openai-gpt-6.1-terra", + "name": "OpenAI GPT-6.1 Terra", + "display_name": "OpenAI GPT-6.1 Terra", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 196608, - "output": 131072 + "context": 1050000, + "output": 128000 }, - "temperature": true, - "tool_call": false, + "temperature": false, + "tool_call": true, "reasoning": { "supported": true, "default": true @@ -2199,6 +2316,7 @@ "extra_capabilities": { "reasoning": { "effort_options": [ + "none", "low", "medium", "high", @@ -2207,81 +2325,112 @@ ] } }, - "attachment": false, - "open_weights": true, - "release_date": "2025-12-23", - "last_updated": "2025-12-23", + "attachment": true, + "open_weights": false, + "knowledge": "2026-06-01", + "release_date": "2026-10-26", + "last_updated": "2026-10-26", "type": "chat" }, { - "id": "qwen3.5-35b-a3b", - "name": "Qwen3.5 35B-A3B", - "display_name": "Qwen3.5 35B-A3B", + "id": "anthropic-claude-opus-4.5", + "name": "Anthropic Claude Opus 4.5", + "display_name": "Anthropic Claude Opus 4.5", "modalities": { "input": [ "text", - "image", - "video" + "image" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 65536 + "context": 200000, + "output": 8192 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": false, + "mode": "mixed", + "budget": { + "min": 1024, + "unit": "tokens" + }, + "effort": "high", + "effort_options": [ + "low", + "medium", + "high" + ], "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ "thinking_blocks" ], - "effort_options": [ - "none", - "minimal", - "low", - "medium", - "high", - "xhigh", - "max" + "notes": [ + "Claude Opus 4.5 uses manual thinking.type = \"enabled\" with budget_tokens; effort can be used alongside the thinking budget.", + "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header." ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-02-23", - "last_updated": "2026-02-23", + "open_weights": false, + "knowledge": "2025-05", + "release_date": "2025-11-24", + "last_updated": "2025-11-24", "type": "chat" }, { - "id": "muse-wonder-1.0", - "name": "Muse Wonder", - "display_name": "Muse Wonder", + "id": "gte-large-en-v1.5", + "name": "GTE Large (v1.5)", + "display_name": "GTE Large (v1.5)", "modalities": { "input": [ - "text", - "image", - "video", - "pdf", - "audio" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 8192, + "output": 1024 + }, + "temperature": false, + "tool_call": false, + "reasoning": { + "supported": false + }, + "attachment": false, + "open_weights": true, + "release_date": "2024-03-27", + "last_updated": "2026-04-16", + "type": "embedding" + }, + { + "id": "deepseek-3.2", + "name": "Deepseek 3.2", + "display_name": "Deepseek 3.2", + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 163840, + "output": 163840 }, "temperature": true, "tool_call": true, @@ -2291,30 +2440,36 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ - "minimal", + "none", "low", "medium", - "high", - "xhigh" + "high" ] } }, - "attachment": true, - "open_weights": false, - "release_date": "2026-10-12", - "last_updated": "2026-10-12", + "attachment": false, + "open_weights": true, + "knowledge": "2024-07", + "release_date": "2025-12-01", + "last_updated": "2025-12-01", "type": "chat" }, { - "id": "gpt-4o", - "name": "GPT-4o", - "display_name": "GPT-4o", + "id": "llama-4-maverick", + "name": "Llama 4 Maverick", + "display_name": "Llama 4 Maverick", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" @@ -2330,30 +2485,31 @@ "supported": false }, "attachment": true, - "open_weights": false, - "knowledge": "2023-09", - "release_date": "2024-05-13", - "last_updated": "2024-08-06", + "open_weights": true, + "knowledge": "2024-08", + "release_date": "2025-04-05", + "last_updated": "2025-04-05", "type": "chat" }, { - "id": "gpt-5-pro", - "name": "GPT-5 Pro", - "display_name": "GPT-5 Pro", + "id": "nemotron-3-nano-omni", + "name": "Nemotron 3 Nano Omni", + "display_name": "Nemotron 3 Nano Omni", "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 400000, - "output": 272000 + "context": 65536, + "output": 65536 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -2361,94 +2517,93 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "fixed", - "effort": "high", - "verbosity": "medium", - "verbosity_options": [ + "effort_options": [ + "none", "low", "medium", - "high" - ], - "visibility": "hidden" + "high", + "max" + ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2024-09-30", - "release_date": "2025-10-06", - "last_updated": "2025-10-06", + "open_weights": true, + "release_date": "2026-04-28", + "last_updated": "2026-04-28", "type": "chat" }, { - "id": "kimi-k2.6", - "name": "Kimi K2.6", - "display_name": "Kimi K2.6", + "id": "anthropic-claude-opus-4.8", + "name": "Anthropic Claude Opus 4.8", + "display_name": "Anthropic Claude Opus 4.8", "modalities": { "input": [ "text", - "image", - "video" + "image" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 1000000, + "output": 128000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], + "default_enabled": false, + "mode": "effort", + "effort": "high", "effort_options": [ - "none", - "minimal", "low", "medium", "high", "xhigh", "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "omitted", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Claude Opus 4.7 and newer Opus models require thinking.type = \"adaptive\" to enable thinking explicitly.", + "Manual budget_tokens requests return 400 on Claude Opus 4.7 and newer adaptive-only Opus models.", + "task_budget is separate from thinking control and should not be treated as a thinking budget." ] } }, "attachment": true, - "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-04-20", - "last_updated": "2026-04-20", + "open_weights": false, + "knowledge": "2026-01", + "release_date": "2026-05-28", + "last_updated": "2026-05-28", "type": "chat" }, { - "id": "grok-4-20-beta-0309-non-reasoning", - "name": "Grok 4.20 (Non-Reasoning)", - "display_name": "Grok 4.20 (Non-Reasoning)", + "id": "openai-gpt-4.1", + "name": "OpenAI GPT-4.1", + "display_name": "OpenAI GPT-4.1", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 2000000, - "output": 30000 + "context": 1047576, + "output": 32768 }, "temperature": true, "tool_call": true, @@ -2457,19 +2612,18 @@ }, "attachment": true, "open_weights": false, - "release_date": "2026-03-09", - "last_updated": "2026-03-09", + "knowledge": "2024-04", + "release_date": "2025-04-14", + "last_updated": "2025-04-14", "type": "chat" }, { - "id": "kimi-k2.5", - "name": "Kimi K2.5", - "display_name": "Kimi K2.5", + "id": "glm-5.2", + "name": "GLM-5.2", + "display_name": "GLM-5.2", "modalities": { "input": [ - "text", - "image", - "video" + "text" ], "output": [ "text" @@ -2477,9 +2631,9 @@ }, "limit": { "context": 262144, - "output": 262144 + "output": 131072 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -2495,32 +2649,26 @@ "thinking_blocks" ], "effort_options": [ - "none", - "minimal", - "low", "medium", "high", - "xhigh", - "max" + "xhigh" ] } }, - "attachment": true, + "attachment": false, "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-01-27", - "last_updated": "2026-01-27", + "release_date": "2026-06-13", + "last_updated": "2026-06-13", "type": "chat" }, { - "id": "claude-opus-5-5", - "name": "Claude Opus 5.5", - "display_name": "Claude Opus 5.5", + "id": "anthropic-claude-sonnet-5.5", + "name": "Anthropic Claude Sonnet 5.5", + "display_name": "Anthropic Claude Sonnet 5.5", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" @@ -2550,28 +2698,56 @@ "attachment": true, "open_weights": false, "knowledge": "2026-06", - "release_date": "2026-09-22", - "last_updated": "2026-09-22", + "release_date": "2026-09-28", + "last_updated": "2026-09-28", "type": "chat" }, { - "id": "kimi-k3.1-pro-highspeed", - "name": "Kimi K3.1 Pro Highspeed", - "display_name": "Kimi K3.1 Pro Highspeed", + "id": "bge-m3", + "name": "BGE M3", + "display_name": "BGE M3", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 1048576 + "context": 8192, + "output": 1024 }, "temperature": false, + "tool_call": false, + "reasoning": { + "supported": false + }, + "attachment": false, + "open_weights": true, + "release_date": "2024-01-30", + "last_updated": "2024-01-30", + "type": "embedding" + }, + { + "id": "qwen3.8-max", + "name": "Qwen3.8-Max", + "display_name": "Qwen3.8-Max", + "modalities": { + "input": [ + "text", + "image", + "video" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1000000, + "output": 262144 + }, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -2579,8 +2755,11 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, "effort_options": [ + "none", "low", + "medium", "high", "max" ] @@ -2588,57 +2767,92 @@ }, "attachment": true, "open_weights": false, - "release_date": "2026-10-23", - "last_updated": "2026-10-23", + "release_date": "2026-08-03", + "last_updated": "2026-08-03", "type": "chat" }, { - "id": "glm-4.5-x", - "name": "GLM-4.5 X", - "display_name": "GLM-4.5 X", + "id": "openai-gpt-5.4-pro", + "name": "OpenAI GPT-5.4 Pro", + "display_name": "OpenAI GPT-5.4 Pro", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 16384 + "context": 1050000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, "default": true }, - "attachment": false, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "medium", + "high", + "xhigh" + ] + } + }, + "attachment": true, "open_weights": false, - "release_date": "2025-07-28", - "last_updated": "2025-07-28", + "knowledge": "2025-08-31", + "release_date": "2026-03-05", + "last_updated": "2026-03-05", "type": "chat" }, { - "id": "o3", - "name": "o3", - "display_name": "o3", + "id": "wan2-2-t2v-a14b", + "name": "Wan2.2-T2V-A14B", + "display_name": "Wan2.2-T2V-A14B", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ - "text" + "video" ] }, "limit": { - "context": 200000, - "output": 100000 + "context": 100, + "output": 1 }, "temperature": false, + "tool_call": false, + "reasoning": { + "supported": false + }, + "attachment": false, + "open_weights": true, + "release_date": "2025-07-28", + "last_updated": "2026-04-30" + }, + { + "id": "reflection-beam", + "name": "Beam", + "display_name": "Beam", + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1048576, + "output": 1048576 + }, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -2646,34 +2860,28 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", "effort_options": [ "low", "medium", - "high" - ], - "visibility": "hidden" + "high", + "xhigh", + "max" + ] } }, - "attachment": true, + "attachment": false, "open_weights": false, - "knowledge": "2024-05", - "release_date": "2025-04-16", - "last_updated": "2025-04-16", + "release_date": "2026-10-21", + "last_updated": "2026-10-21", "type": "chat" }, { - "id": "minimax-m3", - "name": "MiniMax-M3", - "display_name": "MiniMax-M3", + "id": "deepseek-v4-pro-0813", + "name": "DeepSeek V4 Pro 0813", + "display_name": "DeepSeek V4 Pro 0813", "modalities": { "input": [ - "text", - "image", - "video" + "text" ], "output": [ "text" @@ -2681,7 +2889,7 @@ }, "limit": { "context": 1048576, - "output": 512000 + "output": 1048576 }, "temperature": true, "tool_call": true, @@ -2694,25 +2902,22 @@ "supported": true, "effort_options": [ "none", - "minimal", "low", - "medium", "high", - "xhigh", "max" ] } }, - "attachment": true, + "attachment": false, "open_weights": true, - "release_date": "2026-06-01", - "last_updated": "2026-06-01", + "release_date": "2026-08-13", + "last_updated": "2026-08-22", "type": "chat" }, { - "id": "kimi-k3.1-pro", - "name": "Kimi K3.1 Pro", - "display_name": "Kimi K3.1 Pro", + "id": "qwen3.5-397b-a17b", + "name": "Qwen 3.5 397B A17B", + "display_name": "Qwen 3.5 397B A17B", "modalities": { "input": [ "text", @@ -2724,10 +2929,10 @@ ] }, "limit": { - "context": 1048576, + "context": 131072, "output": 131072 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -2735,36 +2940,43 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ + "none", "low", - "high", - "max" + "medium", + "high" ] } }, "attachment": true, - "open_weights": false, - "release_date": "2026-10-23", - "last_updated": "2026-10-23", + "open_weights": true, + "release_date": "2026-02-15", + "last_updated": "2026-02-15", "type": "chat" }, { - "id": "minimax-m3.1-flash", - "name": "MiniMax-M3.1-Flash", - "display_name": "MiniMax-M3.1-Flash", + "id": "anthropic-claude-4.6-sonnet", + "name": "Anthropic Claude Sonnet 4.6", + "display_name": "Anthropic Claude Sonnet 4.6", "modalities": { "input": [ "text", - "image", - "video" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 512000 + "context": 200000, + "output": 8192 }, "temperature": true, "tool_call": true, @@ -2775,26 +2987,24 @@ "extra_capabilities": { "reasoning": { "effort_options": [ - "none", - "minimal", "low", "medium", "high", - "xhigh", "max" ] } }, "attachment": true, "open_weights": false, - "release_date": "2026-10-19", - "last_updated": "2026-10-19", + "knowledge": "2025-08-31", + "release_date": "2026-02-17", + "last_updated": "2026-03-13", "type": "chat" }, { - "id": "claude-sonnet-5", - "name": "Claude Sonnet 5", - "display_name": "Claude Sonnet 5", + "id": "anthropic-claude-opus-5", + "name": "Anthropic Claude Opus 5", + "display_name": "Anthropic Claude Opus 5", "modalities": { "input": [ "text", @@ -2806,19 +3016,17 @@ }, "limit": { "context": 1000000, - "output": 1000000 + "output": 128000 }, "temperature": false, - "tool_call": false, + "tool_call": true, "reasoning": { "supported": true, "default": true }, "extra_capabilities": { "reasoning": { - "supported": true, "effort_options": [ - "none", "low", "medium", "high", @@ -2829,15 +3037,15 @@ }, "attachment": true, "open_weights": false, - "knowledge": "2026-01-31", - "release_date": "2026-06-30", - "last_updated": "2026-06-30", + "knowledge": "2026-05", + "release_date": "2026-07-24", + "last_updated": "2026-07-24", "type": "chat" }, { - "id": "ling-3.0-flash", - "name": "InclusionAI Ling 3.0 Flash", - "display_name": "InclusionAI Ling 3.0 Flash", + "id": "deepseek-v4-flash-0731", + "name": "DeepSeek V4 Flash 0731", + "display_name": "DeepSeek V4 Flash 0731", "modalities": { "input": [ "text" @@ -2847,8 +3055,8 @@ ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 1048576, + "output": 1048576 }, "temperature": true, "tool_call": true, @@ -2858,75 +3066,64 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, "effort_options": [ - "none" + "low", + "medium", + "high" ] } }, "attachment": false, "open_weights": true, - "release_date": "2026-07-23", - "last_updated": "2026-07-23", + "knowledge": "2025-05", + "release_date": "2026-07-31", + "last_updated": "2026-07-31", "type": "chat" }, { - "id": "qwen35-397b-a17b", - "name": "Qwen3.5 397B-A17B", - "display_name": "Qwen3.5 397B-A17B", + "id": "all-mini-lm-l6-v2", + "name": "All-MiniLM-L6-v2", + "display_name": "All-MiniLM-L6-v2", "modalities": { "input": [ - "text", - "image", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 65536 + "context": 256, + "output": 384 }, - "temperature": true, - "tool_call": true, + "temperature": false, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "none", - "minimal", - "low", - "medium", - "high", - "xhigh", - "max" - ] - } + "supported": false }, - "attachment": true, + "attachment": false, "open_weights": true, - "release_date": "2026-02-15", - "last_updated": "2026-02-15", - "type": "chat" + "release_date": "2021-08-30", + "last_updated": "2021-08-30", + "type": "embedding" }, { - "id": "minimax-m2", - "name": "MiniMax-M2", - "display_name": "MiniMax-M2", + "id": "openai-gpt-4o-mini", + "name": "OpenAI GPT-4o mini", + "display_name": "OpenAI GPT-4o mini", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 196608, - "output": 131072 + "context": 128000, + "output": 16384 }, "temperature": true, "tool_call": true, @@ -2939,39 +3136,35 @@ "effort_options": [ "low", "medium", - "high", - "xhigh", - "max" + "high" ] } }, - "attachment": false, - "open_weights": true, - "release_date": "2025-10-27", - "last_updated": "2025-10-27", + "attachment": true, + "open_weights": false, + "knowledge": "2023-09", + "release_date": "2024-07-18", + "last_updated": "2024-07-18", "type": "chat" }, { - "id": "muse-spark-1.3", - "name": "Muse Spark 1.3", - "display_name": "Muse Spark 1.3", + "id": "openai-gpt-5.6-sol", + "name": "OpenAI GPT-5.6 Sol", + "display_name": "OpenAI GPT-5.6 Sol", "modalities": { "input": [ "text", - "image", - "video", - "pdf", - "audio" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 1050000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -2979,39 +3172,39 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, "effort_options": [ - "minimal", + "none", "low", "medium", "high", - "xhigh" + "xhigh", + "max" ] } }, "attachment": true, "open_weights": false, - "release_date": "2026-09-02", - "last_updated": "2026-09-02", + "knowledge": "2026-02-16", + "release_date": "2026-07-09", + "last_updated": "2026-07-09", "type": "chat" }, { - "id": "kimi-k3-fast", - "name": "Kimi K3", - "display_name": "Kimi K3", + "id": "anthropic-claude-fable-5", + "name": "Anthropic Claude Fable 5", + "display_name": "Anthropic Claude Fable 5", "modalities": { "input": [ "text", - "image", - "video" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1040384, - "output": 131072 + "context": 1000000, + "output": 128000 }, "temperature": false, "tool_call": true, @@ -3021,43 +3214,55 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "high", "effort_options": [ - "none", "low", "medium", "high", "xhigh", "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "omitted", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Adaptive thinking is always on for Claude Fable 5 and Claude Mythos 5; thinking.type = \"disabled\" is rejected.", + "Manual budget_tokens requests return 400 on Claude Fable 5 and Claude Mythos 5.", + "thinking.display defaults to omitted; set display to summarized to receive readable thinking summaries." ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-07-16", - "last_updated": "2026-07-16", + "open_weights": false, + "knowledge": "2026-01-31", + "release_date": "2026-06-09", + "last_updated": "2026-06-09", "type": "chat" }, { - "id": "gemini-3.1-flash-lite", - "name": "Gemini 3.1 Flash Lite", - "display_name": "Gemini 3.1 Flash Lite", + "id": "openai-gpt-5.4", + "name": "OpenAI GPT-5.4", + "display_name": "OpenAI GPT-5.4", "modalities": { "input": [ "text", - "image", - "video", - "audio", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 400000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -3065,79 +3270,75 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, "effort_options": [ - "minimal", + "none", "low", "medium", - "high" + "high", + "xhigh" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-01", - "release_date": "2026-05-07", - "last_updated": "2026-05-07", + "knowledge": "2025-08-31", + "release_date": "2026-03-05", + "last_updated": "2026-03-05", "type": "chat" }, { - "id": "qwen-plus", - "name": "Qwen Plus", - "display_name": "Qwen Plus", + "id": "gemma-4-31B-it", + "name": "Gemma 4", + "display_name": "Gemma 4", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 32768 + "context": 256000, + "output": 8192 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true + "supported": true }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] + "supported": true } }, - "attachment": false, - "open_weights": false, - "knowledge": "2024-04", - "release_date": "2024-01-25", - "last_updated": "2025-09-11", + "attachment": true, + "open_weights": true, + "knowledge": "2025-01", + "release_date": "2026-04-02", + "last_updated": "2026-04-02", "type": "chat" }, { - "id": "minimax-m2.5", - "name": "MiniMax-M2.5", - "display_name": "MiniMax-M2.5", + "id": "openai-gpt-5", + "name": "OpenAI GPT-5", + "display_name": "OpenAI GPT-5", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 228700, - "output": 131072 + "context": 400000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -3145,37 +3346,37 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, "effort_options": [ + "minimal", "low", "medium", - "high", - "xhigh", - "max" + "high" ] } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-02-12", - "last_updated": "2026-02-12", + "attachment": true, + "open_weights": false, + "knowledge": "2024-09-30", + "release_date": "2025-08-07", + "last_updated": "2025-08-07", "type": "chat" }, { - "id": "minimax-m2.7", - "name": "MiniMax-M2.7", - "display_name": "MiniMax-M2.7", + "id": "minimax-m3.1-flash", + "name": "MiniMax M3.1-Flash (Public Preview)", + "display_name": "MiniMax M3.1-Flash (Public Preview)", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 204800, - "output": 131072 + "context": 1048576, + "output": 1048576 }, "temperature": true, "tool_call": true, @@ -3185,13 +3386,6 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], "effort_options": [ "low", "medium", @@ -3201,33 +3395,30 @@ ] } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-03-18", - "last_updated": "2026-03-18", + "attachment": true, + "open_weights": false, + "release_date": "2026-10-19", + "last_updated": "2026-10-19", "type": "chat" }, { - "id": "gemini-3.7-flash", - "name": "Gemini 3.7 Flash", - "display_name": "Gemini 3.7 Flash", + "id": "openai-gpt-5-mini", + "name": "OpenAI GPT-5 Mini", + "display_name": "OpenAI GPT-5 Mini", "modalities": { "input": [ "text", - "image", - "video", - "audio", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 400000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -3235,37 +3426,29 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "level", - "level": "high", - "level_options": [ + "effort_options": [ "minimal", "low", "medium", "high" - ], - "summaries": true, - "visibility": "summary", - "continuation": [ - "thought_signatures" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-03", - "release_date": "2026-08-13", - "last_updated": "2026-08-13", + "knowledge": "2024-05-30", + "release_date": "2025-08-07", + "last_updated": "2025-08-07", "type": "chat" }, { - "id": "glm-5.2-fast", - "name": "GLM-5.2", - "display_name": "GLM-5.2", + "id": "anthropic-claude-5.5-haiku", + "name": "Anthropic Claude 5.5 Haiku", + "display_name": "Anthropic Claude 5.5 Haiku", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" @@ -3273,37 +3456,48 @@ }, "limit": { "context": 1000000, - "output": 131072 + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, "default": true }, - "attachment": false, - "open_weights": true, - "release_date": "2026-06-13", - "last_updated": "2026-06-13", + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" + ] + } + }, + "attachment": true, + "open_weights": false, + "knowledge": "2026-06", + "release_date": "2026-10-07", + "last_updated": "2026-10-07", "type": "chat" }, { - "id": "gpt-6-sol", - "name": "GPT-6 Sol", - "display_name": "GPT-6 Sol", + "id": "openai-o3-mini", + "name": "OpenAI o3 mini", + "display_name": "OpenAI o3 mini", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 200000, + "output": 100000 }, "temperature": false, "tool_call": true, @@ -3314,41 +3508,37 @@ "extra_capabilities": { "reasoning": { "effort_options": [ - "none", "low", "medium", - "high", - "xhigh", - "max" + "high" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-04-20", - "release_date": "2026-09-22", - "last_updated": "2026-09-22", + "knowledge": "2024-05", + "release_date": "2024-12-20", + "last_updated": "2025-01-29", "type": "chat" }, { - "id": "gpt-5.6-sol", - "name": "GPT-5.6 Sol", - "display_name": "GPT-5.6 Sol", + "id": "anthropic-claude-4.5-sonnet", + "name": "Anthropic Claude 4.5 Sonnet", + "display_name": "Anthropic Claude 4.5 Sonnet", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 200000, + "output": 64000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -3356,43 +3546,28 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", "effort_options": [ - "none", - "low", - "medium", - "high", - "xhigh", - "max" - ], - "verbosity": "medium", - "verbosity_options": [ "low", "medium", "high" - ], - "visibility": "hidden" + ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-02-16", - "release_date": "2026-07-09", - "last_updated": "2026-07-09", + "knowledge": "2025-07-31", + "release_date": "2025-09-29", + "last_updated": "2025-09-29", "type": "chat" }, { - "id": "claude-sonnet-4-6", - "name": "Claude Sonnet 4.6", - "display_name": "Claude Sonnet 4.6", + "id": "openai-gpt-5.5", + "name": "OpenAI GPT-5.5", + "display_name": "OpenAI GPT-5.5", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" @@ -3400,86 +3575,64 @@ }, "limit": { "context": 1000000, - "output": 64000 + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "mixed", - "budget": { - "min": 1024, - "unit": "tokens" - }, - "effort": "high", "effort_options": [ + "none", "low", "medium", "high", - "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Anthropic recommends adaptive thinking with effort for Claude 4.6; budget_tokens remains a deprecated compatibility path.", - "Anthropic API defaults effort to high; lower effort levels should be chosen per workload." + "xhigh" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-02-17", - "last_updated": "2026-03-13", + "knowledge": "2025-12-01", + "release_date": "2026-04-23", + "last_updated": "2026-04-23", "type": "chat" }, { - "id": "auto", - "name": "Auto Route", - "display_name": "Auto Route", + "id": "openai-gpt-image-2", + "name": "OpenAI GPT Image 2", + "display_name": "OpenAI GPT Image 2", "modalities": { "input": [ "text", "image" ], "output": [ + "image", "text" ] }, "limit": { - "context": 128000, + "context": 8192, "output": 16384 }, - "temperature": true, - "tool_call": true, + "temperature": false, + "tool_call": false, "reasoning": { - "supported": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true - } + "supported": false }, "attachment": true, "open_weights": false, - "release_date": "2024-01-01", - "last_updated": "2024-01-01", - "type": "chat" + "release_date": "2026-04-21", + "last_updated": "2026-04-21" }, { - "id": "qwen3.8-max", - "name": "Qwen3.8 Max Preview", - "display_name": "Qwen3.8 Max Preview", + "id": "kimi-k2.6", + "name": "Kimi K2.6", + "display_name": "Kimi K2.6", "modalities": { "input": [ "text", @@ -3490,10 +3643,10 @@ ] }, "limit": { - "context": 1000000, - "output": 1000000 + "context": 262144, + "output": 262144 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -3502,239 +3655,188 @@ "extra_capabilities": { "reasoning": { "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ + "none", "low", "medium", - "xhigh" + "high" ] } }, "attachment": true, - "open_weights": false, - "release_date": "2026-07-19", - "last_updated": "2026-07-19", + "open_weights": true, + "knowledge": "2025-01", + "release_date": "2026-04-20", + "last_updated": "2026-04-20", "type": "chat" }, { - "id": "gpt-5.6-terra", - "name": "GPT-5.6 Terra", - "display_name": "GPT-5.6 Terra", + "id": "fal-ai/fast-sdxl", + "name": "Fast SDXL", + "display_name": "Fast SDXL", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ - "text" + "image" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 8192, + "output": 8192 }, "temperature": false, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", - "effort_options": [ - "none", - "low", - "medium", - "high", - "xhigh", - "max" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" - } + "supported": false }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-02-16", - "release_date": "2026-07-09", - "last_updated": "2026-07-09", - "type": "chat" + "attachment": false, + "open_weights": true, + "release_date": "2023-07-26", + "last_updated": "2026-04-16" }, { - "id": "gpt-oss-120b", - "name": "GPT OSS 120B", - "display_name": "GPT OSS 120B", + "id": "fal-ai/flux/schnell", + "name": "FLUX.1 [schnell]", + "display_name": "FLUX.1 [schnell]", "modalities": { "input": [ "text" ], "output": [ - "text" + "image" ] }, "limit": { - "context": 131072, - "output": 32766 + "context": 8192, + "output": 8192 }, - "temperature": true, - "tool_call": true, + "temperature": false, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "effort_options": [ - "low", - "medium", - "high" - ] - } + "supported": false }, "attachment": false, "open_weights": true, - "knowledge": "2024-06", - "release_date": "2025-08-05", - "last_updated": "2025-08-05", - "type": "chat" + "release_date": "2024-08-01", + "last_updated": "2026-04-16" }, { - "id": "qwen3-14b", - "name": "Qwen3 14B", - "display_name": "Qwen3 14B", + "id": "fal-ai/elevenlabs/tts/multilingual-v2", + "name": "ElevenLabs Multilingual TTS v2", + "display_name": "ElevenLabs Multilingual TTS v2", "modalities": { "input": [ "text" ], "output": [ - "text" + "audio" ] }, "limit": { - "context": 32768, - "output": 32768 + "context": 8192, + "output": 8192 }, - "temperature": true, - "tool_call": true, + "temperature": false, + "tool_call": false, "reasoning": { - "supported": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } + "supported": false }, "attachment": false, "open_weights": false, - "release_date": "2025-04-28", - "last_updated": "2025-04-28", - "type": "chat" + "release_date": "2023-08-22", + "last_updated": "2023-08-22" }, { - "id": "glm-4.6", - "name": "GLM-4.6", - "display_name": "GLM-4.6", + "id": "fal-ai/stable-audio-25/text-to-audio", + "name": "Stable Audio 2.5 (Text-to-Audio)", + "display_name": "Stable Audio 2.5 (Text-to-Audio)", "modalities": { "input": [ "text" ], "output": [ - "text" + "audio" ] }, "limit": { - "context": 204800, - "output": 131072 + "context": 8192, + "output": 8192 }, - "temperature": true, - "tool_call": true, + "temperature": false, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true - } + "supported": false }, "attachment": false, - "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-09-30", - "last_updated": "2025-09-30", - "type": "chat" - }, + "open_weights": false, + "release_date": "2025-10-08", + "last_updated": "2026-04-16" + } + ] + }, + "opencode-go": { + "id": "opencode-go", + "name": "OpenCode Go", + "display_name": "OpenCode Go", + "api": "https://opencode.ai/inference/go/openai/v1", + "doc": "https://opencode.ai/v2/docs/console/go/", + "models": [ { - "id": "gpt-5.1-codex", - "name": "GPT-5.1 Codex", - "display_name": "GPT-5.1 Codex", + "id": "kimi-k3", + "name": "Kimi K3", + "display_name": "Kimi K3", "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 1048576, + "output": 131072 }, "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "none", - "effort_options": [ - "none", - "low", - "medium", - "high" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ], - "visibility": "hidden" + "effort_options": [ + "max" + ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2024-09-30", - "release_date": "2025-11-13", - "last_updated": "2025-11-13", + "open_weights": true, + "release_date": "2026-07-16", + "last_updated": "2026-07-16", "type": "chat" }, { - "id": "qwen3.5-122b-a10b", - "name": "Qwen3.5 122B-A10B", - "display_name": "Qwen3.5 122B-A10B", + "id": "step-5-preview", + "name": "Step 5 Preview", + "display_name": "Step 5 Preview", "modalities": { "input": [ "text", @@ -3746,7 +3848,7 @@ ] }, "limit": { - "context": 262144, + "context": 1000000, "output": 65536 }, "temperature": true, @@ -3765,26 +3867,22 @@ "thinking_blocks" ], "effort_options": [ - "none", - "minimal", "low", "medium", - "high", - "xhigh", - "max" + "high" ] } }, "attachment": true, "open_weights": true, - "release_date": "2026-02-23", - "last_updated": "2026-02-23", + "release_date": "2026-09-16", + "last_updated": "2026-09-20", "type": "chat" }, { - "id": "gpt-6-luna", - "name": "GPT-6 Luna", - "display_name": "GPT-6 Luna", + "id": "claude-haiku-5-5", + "name": "Claude Haiku 5.5", + "display_name": "Claude Haiku 5.5", "modalities": { "input": [ "text", @@ -3796,7 +3894,7 @@ ] }, "limit": { - "context": 1050000, + "context": 1000000, "output": 128000 }, "temperature": false, @@ -3808,7 +3906,6 @@ "extra_capabilities": { "reasoning": { "effort_options": [ - "none", "low", "medium", "high", @@ -3819,29 +3916,32 @@ }, "attachment": true, "open_weights": false, - "knowledge": "2026-05-18", - "release_date": "2026-09-22", - "last_updated": "2026-09-22", + "knowledge": "2026-06", + "release_date": "2026-10-07", + "last_updated": "2026-10-07", "type": "chat" }, { - "id": "deepseek-v4.1-flash", - "name": "DeepSeek V4.1 Flash", - "display_name": "DeepSeek V4.1 Flash", + "id": "gemini-4-argon-contributor", + "name": "Gemini 4 Argon Contributor", + "display_name": "Gemini 4 Argon Contributor", "modalities": { "input": [ "text", - "image" + "image", + "video", + "audio", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 384000 + "context": 1048576, + "output": 65536 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -3849,39 +3949,39 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, "effort_options": [ "low", - "high", - "max" + "medium", + "high" ] } }, "attachment": true, - "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-09-10", - "last_updated": "2026-09-10", + "open_weights": false, + "knowledge": "2026-03", + "release_date": "2026-10-13", + "last_updated": "2026-10-13", "type": "chat" }, { - "id": "grok-4-7", - "name": "Grok 4.7", - "display_name": "Grok 4.7", + "id": "kimi-k2.7-code", + "name": "Kimi K2.7 Code", + "display_name": "Kimi K2.7 Code", "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 524288, - "output": 500000 + "context": 262144, + "output": 262144 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -3889,40 +3989,39 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "minimal", - "low", - "medium", - "high", - "xhigh" + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2026-05", - "release_date": "2026-09-21", - "last_updated": "2026-09-21", + "open_weights": true, + "knowledge": "2025-01", + "release_date": "2026-06-12", + "last_updated": "2026-06-12", "type": "chat" }, { - "id": "gpt-5-mini", - "name": "GPT-5 Mini", - "display_name": "GPT-5 Mini", + "id": "cat-jam-free", + "name": "Cat Jam Free", + "display_name": "Cat Jam Free", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 1048576, + "output": 131072 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -3931,47 +4030,45 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", - "effort_options": [ - "minimal", - "low", - "medium", - "high" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ], - "verbosity": "medium", - "verbosity_options": [ + "effort_options": [ "low", "medium", - "high" - ], - "visibility": "hidden" + "high", + "xhigh", + "max" + ] } }, - "attachment": true, + "attachment": false, "open_weights": false, - "knowledge": "2024-05-30", - "release_date": "2025-08-07", - "last_updated": "2025-08-07", + "release_date": "2026-10-12", + "last_updated": "2026-10-12", "type": "chat" }, { - "id": "seed-1-6-flash-250715", - "name": "Seed 1.6 Flash (250715)", - "display_name": "Seed 1.6 Flash (250715)", + "id": "mimo-v2.6-pro", + "name": "MiMo-V2.6-Pro", + "display_name": "MiMo-V2.6-Pro", "modalities": { "input": [ "text", - "image" + "image", + "audio", + "video" ], "output": [ "text" ] }, "limit": { - "context": 256000, - "output": 8192 + "context": 1048576, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -3981,38 +4078,42 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "minimal", - "low", - "medium", - "high" + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, "attachment": true, "open_weights": true, - "release_date": "2025-07-26", - "last_updated": "2025-07-26", + "release_date": "2026-09-22", + "last_updated": "2026-09-22", "type": "chat" }, { - "id": "deepseek-v3.2", - "name": "DeepSeek V3.2", - "display_name": "DeepSeek V3.2", + "id": "gemini-4-argon", + "name": "Gemini 4 Argon", + "display_name": "Gemini 4 Argon", "modalities": { "input": [ "text", - "image" + "image", + "video", + "audio", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 163840, - "output": 16384 + "context": 1048576, + "output": 65536 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -4020,33 +4121,39 @@ }, "extra_capabilities": { "reasoning": { - "supported": true + "effort_options": [ + "low", + "medium", + "high" + ] } }, "attachment": true, - "open_weights": true, - "knowledge": "2024-07", - "release_date": "2025-12-01", - "last_updated": "2025-12-01", + "open_weights": false, + "knowledge": "2026-03", + "release_date": "2026-10-13", + "last_updated": "2026-10-13", "type": "chat" }, { - "id": "grok-4-3", - "name": "Grok 4.3", - "display_name": "Grok 4.3", + "id": "muse-spark-1.3-contributor", + "name": "Muse Spark 1.3 Contributor", + "display_name": "Muse Spark 1.3 Contributor", "modalities": { "input": [ "text", "image", - "pdf" + "video", + "pdf", + "audio" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 30000 + "context": 1048576, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -4057,77 +4164,65 @@ "extra_capabilities": { "reasoning": { "effort_options": [ - "none", + "minimal", "low", "medium", - "high" + "high", + "xhigh" ] } }, "attachment": true, "open_weights": false, - "release_date": "2026-04-17", - "last_updated": "2026-04-17", + "release_date": "2026-09-02", + "last_updated": "2026-09-02", "type": "chat" }, { - "id": "kimi-k2", - "name": "Kimi K2", - "display_name": "Kimi K2", + "id": "grok-4.7", + "name": "Grok 4.7", + "display_name": "Grok 4.7", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 256000, - "output": 16384 + "context": 500000, + "output": 500000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false - }, - "attachment": false, - "open_weights": true, - "release_date": "2025-07-11", - "last_updated": "2025-07-11", - "type": "chat" - }, - { - "id": "gpt-live-transcribe", - "name": "GPT-Live-Transcribe", - "display_name": "GPT-Live-Transcribe", - "modalities": { - "input": [ - "text", - "audio" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 8192, - "output": 8192 + "supported": true, + "default": true }, - "temperature": false, - "tool_call": false, - "reasoning": { - "supported": false + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "low", + "medium", + "high", + "xhigh" + ] + } }, "attachment": true, "open_weights": false, - "release_date": "2026-07-28", - "last_updated": "2026-07-28" + "knowledge": "2026-05", + "release_date": "2026-09-21", + "last_updated": "2026-09-21", + "type": "chat" }, { - "id": "qwen3.6-plus", - "name": "Qwen3.6 Plus", - "display_name": "Qwen3.6 Plus", + "id": "qwen3.8-flash", + "name": "Qwen3.8 Flash", + "display_name": "Qwen3.8 Flash", "modalities": { "input": [ "text", @@ -4139,8 +4234,8 @@ ] }, "limit": { - "context": 983616, - "output": 65536 + "context": 1000000, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -4151,48 +4246,36 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], "effort_options": [ - "none", - "minimal", "low", "medium", - "high", - "xhigh", - "max" + "xhigh" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-04", - "release_date": "2026-04-02", - "last_updated": "2026-04-02", + "release_date": "2026-08-26", + "last_updated": "2026-08-26", "type": "chat" }, { - "id": "gpt-5.4-pro", - "name": "GPT-5.4 Pro", - "display_name": "GPT-5.4 Pro", + "id": "mimo-v2.5-pro", + "name": "MiMo V2.5 Pro", + "display_name": "MiMo V2.5 Pro", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1050000, + "context": 1048576, "output": 128000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -4201,47 +4284,36 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "high", - "effort_options": [ - "medium", - "high", - "xhigh" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-03-05", - "last_updated": "2026-03-05", + "attachment": false, + "open_weights": true, + "knowledge": "2024-12", + "release_date": "2026-04-22", + "last_updated": "2026-04-22", "type": "chat" }, { - "id": "grok-build-0-1", - "name": "Grok Build 0.1", - "display_name": "Grok Build 0.1", + "id": "longcat-2.0", + "name": "LongCat-2.0", + "display_name": "LongCat-2.0", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 256000, - "output": 256000 + "context": 1000000, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -4251,74 +4323,72 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "low", - "medium", - "high" + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, - "attachment": true, - "open_weights": false, - "release_date": "2026-04-16", - "last_updated": "2026-04-16", + "attachment": false, + "open_weights": true, + "release_date": "2026-06-30", + "last_updated": "2026-06-30", "type": "chat" }, { - "id": "gpt-5.1-codex-mini", - "name": "GPT-5.1 Codex mini", - "display_name": "GPT-5.1 Codex mini", + "id": "space-bunny", + "name": "Space Bunny", + "display_name": "Space Bunny", "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 1048576, + "output": 524288 }, - "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "none", - "effort_options": [ - "none", - "low", - "medium", - "high" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ], - "verbosity": "medium", - "verbosity_options": [ + "effort_options": [ "low", "medium", - "high" - ], - "visibility": "hidden" + "high", + "xhigh", + "max" + ] } }, "attachment": true, "open_weights": false, - "knowledge": "2024-09-30", - "release_date": "2025-11-13", - "last_updated": "2025-11-13", + "release_date": "2026-09-23", + "last_updated": "2026-09-23", "type": "chat" }, { - "id": "llama-3.3-70b-instruct", - "name": "Llama-3.3-70B-Instruct", - "display_name": "Llama-3.3-70B-Instruct", + "id": "beam-free", + "name": "Beam Free", + "display_name": "Beam Free", "modalities": { "input": [ "text" @@ -4328,36 +4398,57 @@ ] }, "limit": { - "context": 131072, - "output": 4096 + "context": 1048576, + "output": 131072 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" + ] + } }, "attachment": false, "open_weights": true, - "knowledge": "2023-12", - "release_date": "2024-12-06", - "last_updated": "2024-12-06", + "knowledge": "2026-06-30", + "release_date": "2026-10-05", + "last_updated": "2026-10-16", "type": "chat" }, { - "id": "hy3", - "name": "Hy3", - "display_name": "Hy3", + "id": "glm-5.3-flash", + "name": "GLM-5.3-Flash", + "display_name": "GLM-5.3-Flash", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 128000 + "context": 1000000, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -4368,38 +4459,43 @@ "extra_capabilities": { "reasoning": { "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ - "none", - "minimal", "low", - "medium", "high", - "xhigh" + "max" ] } }, - "attachment": false, + "attachment": true, "open_weights": true, - "release_date": "2026-07-06", - "last_updated": "2026-07-06", + "release_date": "2026-08-26", + "last_updated": "2026-08-26", "type": "chat" }, { - "id": "gemma-4-26b-a4b-it", - "name": "Gemma 4 26B A4B IT", - "display_name": "Gemma 4 26B A4B IT", + "id": "mimo-v2.6-flash", + "name": "MiMo-V2.6-Flash", + "display_name": "MiMo-V2.6-Flash", "modalities": { "input": [ "text", - "image" + "image", + "audio", + "video" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 32768 + "context": 1048576, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -4409,49 +4505,58 @@ }, "extra_capabilities": { "reasoning": { - "supported": true + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] } }, "attachment": true, "open_weights": true, - "release_date": "2026-04-02", - "last_updated": "2026-04-02", + "release_date": "2026-09-22", + "last_updated": "2026-09-22", "type": "chat" }, { - "id": "gpt-5.1", - "name": "GPT-5.1", - "display_name": "GPT-5.1", + "id": "gpt-5.6-luna", + "name": "GPT-5.6 Luna", + "display_name": "GPT-5.6 Luna", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 400000, + "context": 1050000, "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, + "default_enabled": true, "mode": "effort", - "effort": "none", + "effort": "medium", "effort_options": [ "none", "low", "medium", - "high" + "high", + "xhigh", + "max" ], "verbosity": "medium", "verbosity_options": [ @@ -4464,117 +4569,67 @@ }, "attachment": true, "open_weights": false, - "knowledge": "2024-09-30", - "release_date": "2025-11-13", - "last_updated": "2025-11-13", + "knowledge": "2026-02-16", + "release_date": "2026-07-09", + "last_updated": "2026-07-09", "type": "chat" }, { - "id": "ministral-3b-2512", - "name": "Ministral 3B", - "display_name": "Ministral 3B", + "id": "hy3", + "name": "Hy3", + "display_name": "Hy3", "modalities": { "input": [ - "text", - "image" - ], - "output": [ "text" - ] - }, - "limit": { - "context": 131072, - "output": 8192 - }, - "temperature": true, - "tool_call": false, - "reasoning": { - "supported": false - }, - "attachment": true, - "open_weights": true, - "release_date": "2025-12-02", - "last_updated": "2025-12-02", - "type": "chat" - }, - { - "id": "claude-opus-4-6", - "name": "Claude Opus 4.6", - "display_name": "Claude Opus 4.6", - "modalities": { - "input": [ - "text", - "image", - "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1000000, + "context": 256000, "output": 128000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "mixed", - "budget": { - "min": 1024, - "unit": "tokens" - }, - "effort": "high", "effort_options": [ + "none", "low", - "medium", - "high", - "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Anthropic recommends adaptive thinking with effort for Claude 4.6; budget_tokens remains a deprecated compatibility path.", - "Anthropic API defaults effort to high; lower effort levels should be chosen per workload." + "high" ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-05-31", - "release_date": "2026-02-05", - "last_updated": "2026-03-13", + "attachment": false, + "open_weights": true, + "release_date": "2026-07-06", + "last_updated": "2026-07-06", "type": "chat" }, { - "id": "kimi-k2.7-code", - "name": "Kimi K2.7 Code", - "display_name": "Kimi K2.7 Code", + "id": "deepseek-v4.1-flash", + "name": "DeepSeek V4.1 Flash", + "display_name": "DeepSeek V4.1 Flash", "modalities": { "input": [ "text", - "image", - "video" + "image" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 1000000, + "output": 384000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -4590,39 +4645,34 @@ "thinking_blocks" ], "effort_options": [ - "none", - "minimal", "low", - "medium", "high", - "xhigh", "max" ] } }, "attachment": true, "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-06-12", - "last_updated": "2026-06-12", + "knowledge": "2025-05", + "release_date": "2026-09-10", + "last_updated": "2026-09-10", "type": "chat" }, { - "id": "seed-2-0-mini-260428", - "name": "Seed 2.0 Mini (260428)", - "display_name": "Seed 2.0 Mini (260428)", + "id": "hy4-preview", + "name": "Hy4 preview", + "display_name": "Hy4 preview", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 1024000, + "output": 64000 }, "temperature": true, "tool_call": true, @@ -4632,28 +4682,28 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, "effort_options": [ - "minimal", - "low", - "medium", + "none", "high" ] } }, - "attachment": true, - "open_weights": false, - "release_date": "2026-04-28", - "last_updated": "2026-04-28", + "attachment": false, + "open_weights": true, + "release_date": "2026-08-28", + "last_updated": "2026-08-28", "type": "chat" }, { - "id": "fugu-max", - "name": "Fugu Max", - "display_name": "Fugu Max", + "id": "minimax-m3.1-flash-free", + "name": "MiniMax M3.1 Flash Free", + "display_name": "MiniMax M3.1 Flash Free", "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ "text" @@ -4661,7 +4711,7 @@ }, "limit": { "context": 1000000, - "output": 1000000 + "output": 512000 }, "temperature": true, "tool_call": true, @@ -4671,7 +4721,16 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ + "low", + "medium", "high", "xhigh", "max" @@ -4680,58 +4739,73 @@ }, "attachment": true, "open_weights": false, - "release_date": "2026-09-11", - "last_updated": "2026-09-11", + "release_date": "2026-10-19", + "last_updated": "2026-10-19", "type": "chat" }, { - "id": "glm-4.5-air", - "name": "GLM-4.5-Air", - "display_name": "GLM-4.5-Air", + "id": "kimi-k3.1-pro", + "name": "Kimi K3.1 Pro", + "display_name": "Kimi K3.1 Pro", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 98304 + "context": 1048576, + "output": 131072 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, "default": true }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-07-28", - "last_updated": "2025-07-28", + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "effort_options": [ + "low", + "high", + "max" + ] + } + }, + "attachment": true, + "open_weights": false, + "release_date": "2026-10-23", + "last_updated": "2026-10-23", "type": "chat" }, { - "id": "gpt-5.6-luna", - "name": "GPT-5.6 Luna", - "display_name": "GPT-5.6 Luna", + "id": "deepseek-v4-pro", + "name": "DeepSeek V4 Pro (New)", + "display_name": "DeepSeek V4 Pro (New)", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 1000000, + "output": 384000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -4742,35 +4816,34 @@ "supported": true, "default_enabled": true, "mode": "effort", - "effort": "medium", + "effort": "high", "effort_options": [ - "none", "low", - "medium", "high", - "xhigh", "max" ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ], - "visibility": "hidden" + "notes": [ + "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." + ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-02-16", - "release_date": "2026-07-09", - "last_updated": "2026-07-09", + "attachment": false, + "open_weights": true, + "knowledge": "2025-05", + "release_date": "2026-04-24", + "last_updated": "2026-04-24", "type": "chat" }, { - "id": "qwen3-32b", - "name": "Qwen3 32B", - "display_name": "Qwen3 32B", + "id": "glm-5.3", + "name": "GLM-5.3", + "display_name": "GLM-5.3", "modalities": { "input": [ "text" @@ -4780,8 +4853,8 @@ ] }, "limit": { - "context": 40960, - "output": 16384 + "context": 1000000, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -4797,48 +4870,67 @@ "visibility": "summary", "continuation": [ "thinking_blocks" + ], + "effort_options": [ + "low", + "high", + "max" ] } }, "attachment": false, "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-04-28", - "last_updated": "2025-04-28", + "release_date": "2026-08-14", + "last_updated": "2026-08-14", "type": "chat" }, { - "id": "custom", - "name": "Custom Model", - "display_name": "Custom Model", + "id": "mimo-v2.5", + "name": "MiMo V2.5", + "display_name": "MiMo V2.5", "modalities": { "input": [ "text", - "image" + "image", + "audio", + "video" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 16384 + "context": 1000000, + "output": 128000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } }, "attachment": true, - "open_weights": false, - "release_date": "2024-01-01", - "last_updated": "2024-01-01", + "open_weights": true, + "knowledge": "2024-12", + "release_date": "2026-04-22", + "last_updated": "2026-04-22", "type": "chat" }, { - "id": "grok-4", - "name": "Grok 4", - "display_name": "Grok 4", + "id": "longcat-2.5-preview-free", + "name": "LongCat 2.5 Preview Free", + "display_name": "LongCat 2.5 Preview Free", "modalities": { "input": [ "text", @@ -4849,85 +4941,84 @@ ] }, "limit": { - "context": 256000, - "output": 256000 + "context": 1000000, + "output": 131072 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { - "supported": true + "supported": true, + "default": true }, "extra_capabilities": { "reasoning": { - "supported": true + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] } }, "attachment": true, "open_weights": false, - "release_date": "2025-07-09", - "last_updated": "2025-07-09", + "release_date": "2026-09-25", + "last_updated": "2026-09-25", "type": "chat" }, { - "id": "claude-sonnet-4-5-20250929", - "name": "Claude Sonnet 4.5", - "display_name": "Claude Sonnet 4.5", + "id": "glm-5.2", + "name": "GLM-5.2", + "display_name": "GLM-5.2", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 64000 + "context": 1000000, + "output": 131072 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "budget", - "budget": { - "min": 1024, - "unit": "tokens" - }, "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ "thinking_blocks" ], - "notes": [ - "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", - "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." + "effort_options": [ + "high", + "max" ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-07-31", - "release_date": "2025-09-29", - "last_updated": "2025-09-29", + "attachment": false, + "open_weights": true, + "release_date": "2026-06-13", + "last_updated": "2026-06-13", "type": "chat" }, { - "id": "fugu-ultra-v2.0", - "name": "Fugu Ultra v2.0", - "display_name": "Fugu Ultra v2.0", + "id": "qwen3.8-max", + "name": "Qwen3.8 Max", + "display_name": "Qwen3.8 Max", "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ "text" @@ -4935,7 +5026,7 @@ }, "limit": { "context": 1000000, - "output": 1000000 + "output": 131072 }, "temperature": true, "tool_call": true, @@ -4945,79 +5036,65 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, "effort_options": [ - "high", - "xhigh", - "max" + "low", + "medium", + "xhigh" ] } }, "attachment": true, "open_weights": false, - "release_date": "2026-09-11", - "last_updated": "2026-09-11", + "release_date": "2026-08-03", + "last_updated": "2026-08-03", "type": "chat" }, { - "id": "gpt-5.4-mini", - "name": "GPT-5.4 mini", - "display_name": "GPT-5.4 mini", + "id": "minimax-m3", + "name": "MiniMax-M3", + "display_name": "MiniMax-M3", "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 1000000, + "output": 131072 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "none", - "effort_options": [ - "none", - "low", - "medium", - "high", - "xhigh" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + "supported": true } }, "attachment": true, - "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-03-17", - "last_updated": "2026-03-17", + "open_weights": true, + "knowledge": "2025-01", + "release_date": "2026-06-01", + "last_updated": "2026-06-01", "type": "chat" }, { - "id": "glm-5.3-flashx", - "name": "GLM-5.3 FlashX", - "display_name": "GLM-5.3 FlashX", + "id": "qwen3.7-plus", + "name": "Qwen3.7 Plus", + "display_name": "Qwen3.7 Plus", "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ "text" @@ -5025,7 +5102,7 @@ }, "limit": { "context": 1000000, - "output": 1000000 + "output": 65536 }, "temperature": true, "tool_call": true, @@ -5035,25 +5112,26 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "xhigh", - "max" - ] + "supported": true } }, "attachment": true, "open_weights": false, - "release_date": "2026-09-18", - "last_updated": "2026-09-18", + "knowledge": "2025-04", + "release_date": "2026-06-02", + "last_updated": "2026-06-02", "type": "chat" }, { - "id": "gpt-4o-mini-transcribe", - "name": "GPT-4o Mini Transcribe", - "display_name": "GPT-4o Mini Transcribe", + "id": "muse-spark-1.2-contributor", + "name": "Muse Spark 1.2 Contributor", + "display_name": "Muse Spark 1.2 Contributor", "modalities": { "input": [ "text", + "image", + "video", + "pdf", "audio" ], "output": [ @@ -5061,83 +5139,106 @@ ] }, "limit": { - "context": 16000, - "output": 16000 + "context": 1048576, + "output": 131072 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "minimal", + "low", + "medium", + "high", + "xhigh" + ] + } }, "attachment": true, "open_weights": false, - "release_date": "2025-03-20", - "last_updated": "2025-03-20", + "release_date": "2026-08-05", + "last_updated": "2026-08-05", "type": "chat" }, { - "id": "mistral-large-4", - "name": "Mistral Large 4", - "display_name": "Mistral Large 4", + "id": "iceman-contributor-free", + "name": "Iceman Contributor Free", + "display_name": "Iceman Contributor Free", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 262144 + "context": 262144, + "output": 131072 }, - "temperature": true, "tool_call": true, "reasoning": { "supported": true, "default": true }, - "attachment": true, + "attachment": false, "open_weights": false, - "release_date": "2026-10-06", - "last_updated": "2026-10-06", + "release_date": "2026-10-24", + "last_updated": "2026-10-24", "type": "chat" }, { - "id": "mistral-small-2603", - "name": "Mistral Small 4", - "display_name": "Mistral Small 4", + "id": "gpt-6-luna", + "name": "GPT-6 Luna", + "display_name": "GPT-6 Luna", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 256000 + "context": 1050000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, "default": true }, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "none", + "low", + "medium", + "high", + "xhigh", + "max" + ] + } + }, "attachment": true, - "open_weights": true, - "knowledge": "2025-06", - "release_date": "2026-03-16", - "last_updated": "2026-03-16", + "open_weights": false, + "knowledge": "2026-05-18", + "release_date": "2026-09-22", + "last_updated": "2026-09-22", "type": "chat" }, { - "id": "sonar", - "name": "Sonar", - "display_name": "Sonar", + "id": "minimax-m2.7", + "name": "MiniMax-M2.7", + "display_name": "MiniMax-M2.7", "modalities": { "input": [ "text" @@ -5147,25 +5248,37 @@ ] }, "limit": { - "context": 130000, - "output": 4096 + "context": 204800, + "output": 131072 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } }, "attachment": false, - "open_weights": false, - "knowledge": "2025-09-01", - "release_date": "2024-01-01", - "last_updated": "2025-09-01", + "open_weights": true, + "knowledge": "2025-01", + "release_date": "2026-03-18", + "last_updated": "2026-03-18", "type": "chat" }, { - "id": "qwen3.6-35b-a3b", - "name": "Qwen3.6 35B-A3B", - "display_name": "Qwen3.6 35B-A3B", + "id": "minimax-m3.1-flash", + "name": "MiniMax M3.1 Flash", + "display_name": "MiniMax M3.1 Flash", "modalities": { "input": [ "text", @@ -5177,8 +5290,8 @@ ] }, "limit": { - "context": 262144, - "output": 65536 + "context": 1000000, + "output": 512000 }, "temperature": true, "tool_call": true, @@ -5196,8 +5309,6 @@ "thinking_blocks" ], "effort_options": [ - "none", - "minimal", "low", "medium", "high", @@ -5207,28 +5318,28 @@ } }, "attachment": true, - "open_weights": true, - "release_date": "2026-04-17", - "last_updated": "2026-04-17", + "open_weights": false, + "release_date": "2026-10-19", + "last_updated": "2026-10-19", "type": "chat" }, { - "id": "gpt-5.3-codex", - "name": "GPT-5.3 Codex", - "display_name": "GPT-5.3 Codex", + "id": "step-5-preview-free", + "name": "Step 5 Preview Free", + "display_name": "Step 5 Preview Free", "modalities": { "input": [ "text", "image", - "pdf" + "video" ], "output": [ "text" ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 1000000, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -5236,14 +5347,67 @@ "supported": true, "default": true }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "effort_options": [ + "low", + "medium", + "high" + ] + } + }, + "attachment": true, + "open_weights": true, + "release_date": "2026-09-16", + "last_updated": "2026-09-20", + "type": "chat" + } + ] + }, + "llmgateway-providers": { + "id": "llmgateway-providers", + "name": "LLM Gateway", + "display_name": "LLM Gateway", + "api": "https://api.llmgateway.io/v1", + "doc": "https://llmgateway.io/docs", + "models": [ + { + "id": "openai/gpt-5.2-pro", + "name": "GPT-5.2 Pro (OpenAI)", + "display_name": "GPT-5.2 Pro (OpenAI)", + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 400000, + "output": 272000 + }, + "temperature": false, + "tool_call": true, + "reasoning": { + "supported": true, + "default": true + }, "extra_capabilities": { "reasoning": { "supported": true, "default_enabled": true, "mode": "effort", - "effort": "medium", + "effort": "high", "effort_options": [ - "low", "medium", "high", "xhigh" @@ -5260,14 +5424,14 @@ "attachment": true, "open_weights": false, "knowledge": "2025-08-31", - "release_date": "2026-02-05", - "last_updated": "2026-02-05", + "release_date": "2025-12-11", + "last_updated": "2025-12-11", "type": "chat" }, { - "id": "qwen-plus-latest", - "name": "Qwen Plus Latest", - "display_name": "Qwen Plus Latest", + "id": "openai/gpt-5-pro", + "name": "GPT-5 Pro (OpenAI)", + "display_name": "GPT-5 Pro (OpenAI)", "modalities": { "input": [ "text", @@ -5278,35 +5442,41 @@ ] }, "limit": { - "context": 1000000, - "output": 8192 + "context": 400000, + "output": 272000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { - "supported": true + "supported": true, + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] + "default_enabled": true, + "mode": "fixed", + "effort": "high", + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, "attachment": true, "open_weights": false, - "release_date": "2025-01-25", - "last_updated": "2025-01-25", + "knowledge": "2024-09-30", + "release_date": "2025-10-06", + "last_updated": "2025-10-06", "type": "chat" }, { - "id": "ling-3.0-flash-vl", - "name": "Ling 3.0 Flash VL", - "display_name": "Ling 3.0 Flash VL", + "id": "openai/gpt-5.4-nano", + "name": "GPT-5.4 Nano (OpenAI)", + "display_name": "GPT-5.4 Nano (OpenAI)", "modalities": { "input": [ "text", @@ -5317,48 +5487,63 @@ ] }, "limit": { - "context": 131072, - "output": 131072 + "context": 400000, + "output": 128000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": false, + "mode": "effort", + "effort": "none", "effort_options": [ "none", + "low", + "medium", + "high", + "xhigh" + ], + "verbosity": "medium", + "verbosity_options": [ "low", "medium", "high" - ] + ], + "visibility": "hidden" } }, "attachment": true, - "open_weights": true, - "release_date": "2026-09-10", - "last_updated": "2026-09-10", + "open_weights": false, + "knowledge": "2025-08-31", + "release_date": "2026-03-17", + "last_updated": "2026-03-17", "type": "chat" }, { - "id": "glm-5.1", - "name": "GLM-5.1", - "display_name": "GLM-5.1", + "id": "openai/gpt-6.1-terra", + "name": "GPT-6.1 Terra (OpenAI)", + "display_name": "GPT-6.1 Terra (OpenAI)", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 204800, - "output": 131072 + "context": 1050000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -5366,16 +5551,8 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], "effort_options": [ "none", - "minimal", "low", "medium", "high", @@ -5384,107 +5561,137 @@ ] } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-04-07", - "last_updated": "2026-04-07", + "attachment": true, + "open_weights": false, + "knowledge": "2026-06-01", + "release_date": "2026-10-26", + "last_updated": "2026-10-26", "type": "chat" }, { - "id": "qwen3-30b-a3b", - "name": "Qwen3 30B A3B", - "display_name": "Qwen3 30B A3B", + "id": "openai/gpt-5.4", + "name": "GPT-5.4 (OpenAI)", + "display_name": "GPT-5.4 (OpenAI)", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 32768, - "output": 16384 + "context": 1050000, + "output": 128000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] + "default_enabled": false, + "mode": "effort", + "effort": "none", + "effort_options": [ + "none", + "low", + "medium", + "high", + "xhigh" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, - "attachment": false, - "open_weights": true, - "release_date": "2025-04-28", - "last_updated": "2025-04-28", + "attachment": true, + "open_weights": false, + "knowledge": "2025-08-31", + "release_date": "2026-03-05", + "last_updated": "2026-03-05", "type": "chat" }, { - "id": "Qwen3.8-27B", - "name": "Qwen3.8 27B", - "display_name": "Qwen3.8 27B", + "id": "openai/gpt-5.4-mini", + "name": "GPT-5.4 Mini (OpenAI)", + "display_name": "GPT-5.4 Mini (OpenAI)", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 262000, - "output": 32768 + "context": 400000, + "output": 128000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": false, + "mode": "effort", + "effort": "none", "effort_options": [ + "none", "low", "medium", + "high", "xhigh" - ] + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-08-14", - "last_updated": "2026-08-14", + "attachment": true, + "open_weights": false, + "knowledge": "2025-08-31", + "release_date": "2026-03-17", + "last_updated": "2026-03-17", "type": "chat" }, { - "id": "qwen3.8-flash", - "name": "Qwen3.8 Flash", - "display_name": "Qwen3.8 Flash", + "id": "openai/gpt-6-sol", + "name": "GPT-6 Sol (OpenAI)", + "display_name": "GPT-6 Sol (OpenAI)", "modalities": { "input": [ "text", "image", - "video" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 1050000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -5492,10 +5699,8 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, "effort_options": [ "none", - "minimal", "low", "medium", "high", @@ -5506,20 +5711,19 @@ }, "attachment": true, "open_weights": false, - "release_date": "2026-08-26", - "last_updated": "2026-08-26", + "knowledge": "2026-04-20", + "release_date": "2026-09-22", + "last_updated": "2026-09-22", "type": "chat" }, { - "id": "gemini-3.1-pro-preview", - "name": "Gemini 3.1 Pro Preview", - "display_name": "Gemini 3.1 Pro Preview", + "id": "openai/gpt-5.6-terra", + "name": "GPT-5.6 Terra (OpenAI)", + "display_name": "GPT-5.6 Terra (OpenAI)", "modalities": { "input": [ "text", "image", - "video", - "audio", "pdf" ], "output": [ @@ -5527,10 +5731,10 @@ ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 1050000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -5540,30 +5744,36 @@ "reasoning": { "supported": true, "default_enabled": true, - "mode": "level", - "level": "high", - "level_options": [ + "mode": "effort", + "effort": "medium", + "effort_options": [ + "none", + "low", + "medium", + "high", + "xhigh", + "max" + ], + "verbosity": "medium", + "verbosity_options": [ "low", + "medium", "high" ], - "summaries": true, - "visibility": "summary", - "continuation": [ - "thought_signatures" - ] + "visibility": "hidden" } }, "attachment": true, "open_weights": false, - "knowledge": "2025-01", - "release_date": "2026-02-19", - "last_updated": "2026-02-19", + "knowledge": "2026-02-16", + "release_date": "2026-07-09", + "last_updated": "2026-07-09", "type": "chat" }, { - "id": "gpt-4.1-mini", - "name": "GPT-4.1 mini", - "display_name": "GPT-4.1 mini", + "id": "openai/gpt-5.3-codex", + "name": "GPT-5.3 Codex (OpenAI)", + "display_name": "GPT-5.3 Codex (OpenAI)", "modalities": { "input": [ "text", @@ -5575,95 +5785,102 @@ ] }, "limit": { - "context": 1000000, - "output": 32768 + "context": 400000, + "output": 128000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", + "effort_options": [ + "low", + "medium", + "high", + "xhigh" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" + } }, "attachment": true, "open_weights": false, - "knowledge": "2024-04", - "release_date": "2025-04-14", - "last_updated": "2025-04-14", + "knowledge": "2025-08-31", + "release_date": "2026-02-05", + "last_updated": "2026-02-05", "type": "chat" }, { - "id": "llama-4-maverick-17b-instruct", - "name": "Llama 4 Maverick 17B Instruct", - "display_name": "Llama 4 Maverick 17B Instruct", + "id": "openai/gpt-6-astra", + "name": "GPT-6 Astra (OpenAI)", + "display_name": "GPT-6 Astra (OpenAI)", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 2048 + "context": 1050000, + "output": 128000 }, - "temperature": true, - "tool_call": false, + "temperature": false, + "tool_call": true, "reasoning": { - "supported": false - }, - "attachment": true, - "open_weights": true, - "knowledge": "2024-08", - "release_date": "2025-04-05", - "last_updated": "2025-04-05", - "type": "chat" - }, - { - "id": "ministral-8b-2512", - "name": "Ministral 8B", - "display_name": "Ministral 8B", - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 262144, - "output": 8192 + "supported": true, + "default": true }, - "temperature": true, - "tool_call": false, - "reasoning": { - "supported": false + "extra_capabilities": { + "reasoning": { + "supported": true, + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" + ] + } }, "attachment": true, - "open_weights": true, - "release_date": "2025-12-02", - "last_updated": "2025-12-02", + "open_weights": false, + "knowledge": "2026-04-30", + "release_date": "2026-09-04", + "last_updated": "2026-09-04", "type": "chat" }, { - "id": "claude-haiku-4-5", - "name": "Claude Haiku 4.5 (latest)", - "display_name": "Claude Haiku 4.5 (latest)", + "id": "openai/gpt-5.2", + "name": "GPT-5.2 (OpenAI)", + "display_name": "GPT-5.2 (OpenAI)", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 64000 + "context": 400000, + "output": 128000 }, "temperature": true, "tool_call": true, @@ -5675,62 +5892,85 @@ "reasoning": { "supported": true, "default_enabled": false, - "mode": "budget", - "budget": { - "min": 1024, - "unit": "tokens" - }, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "mode": "effort", + "effort": "none", + "effort_options": [ + "none", + "low", + "medium", + "high", + "xhigh" ], - "notes": [ - "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", - "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." - ] + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, "attachment": true, "open_weights": false, - "knowledge": "2025-02-28", - "release_date": "2025-10-15", - "last_updated": "2025-10-15", + "knowledge": "2025-08-31", + "release_date": "2025-12-11", + "last_updated": "2025-12-11", "type": "chat" }, { - "id": "gpt-4o-transcribe", - "name": "GPT-4o Transcribe", - "display_name": "GPT-4o Transcribe", + "id": "openai/gpt-5.4-pro", + "name": "GPT-5.4 Pro (OpenAI)", + "display_name": "GPT-5.4 Pro (OpenAI)", "modalities": { "input": [ "text", - "audio" + "image" ], "output": [ "text" ] }, "limit": { - "context": 16000, - "output": 16000 + "context": 1050000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "high", + "effort_options": [ + "medium", + "high", + "xhigh" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" + } }, "attachment": true, "open_weights": false, - "release_date": "2025-03-20", - "last_updated": "2025-03-20", + "knowledge": "2025-08-31", + "release_date": "2026-03-05", + "last_updated": "2026-03-05", "type": "chat" }, { - "id": "claude-haiku-4-5-20251001", - "name": "Claude Haiku 4.5", - "display_name": "Claude Haiku 4.5", + "id": "openai/gpt-6.1-sol", + "name": "GPT-6.1 Sol (OpenAI)", + "display_name": "GPT-6.1 Sol (OpenAI)", "modalities": { "input": [ "text", @@ -5742,88 +5982,78 @@ ] }, "limit": { - "context": 200000, - "output": 64000 + "context": 1050000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "budget", - "budget": { - "min": 1024, - "unit": "tokens" - }, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", - "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-02-28", - "release_date": "2025-10-15", - "last_updated": "2025-10-15", + "knowledge": "2026-04-30", + "release_date": "2026-09-29", + "last_updated": "2026-09-29", "type": "chat" }, { - "id": "hy-mt2-plus", - "name": "Hy-MT2 Plus", - "display_name": "Hy-MT2 Plus", + "id": "openai/gpt-4o-mini-transcribe", + "name": "GPT-4o Mini Transcribe (OpenAI)", + "display_name": "GPT-4o Mini Transcribe (OpenAI)", "modalities": { "input": [ - "text" + "text", + "audio" ], "output": [ "text" ] }, "limit": { - "context": 8192, - "output": 8192 + "context": 16000, + "output": 2000 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { "supported": false }, - "attachment": false, + "attachment": true, "open_weights": false, - "release_date": "2026-05-21", - "last_updated": "2026-05-21", + "release_date": "2025-03-20", + "last_updated": "2025-03-20", "type": "chat" }, { - "id": "qwen3.7-flash", - "name": "Qwen3.7 Flash", - "display_name": "Qwen3.7 Flash", + "id": "openai/gpt-5-nano", + "name": "GPT-5 Nano (OpenAI)", + "display_name": "GPT-5 Nano (OpenAI)", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 983616, - "output": 1000000 + "context": 400000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -5832,42 +6062,50 @@ "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", "effort_options": [ - "none", "minimal", "low", "medium", - "high", - "xhigh", - "max" - ] + "high" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, - "attachment": true, + "attachment": false, "open_weights": false, - "release_date": "2026-07-15", - "last_updated": "2026-07-15", + "knowledge": "2024-05-30", + "release_date": "2025-08-07", + "last_updated": "2025-08-07", "type": "chat" }, { - "id": "gemini-3.8-flash", - "name": "Gemini 3.8 Flash", - "display_name": "Gemini 3.8 Flash", + "id": "openai/gpt-5.5-pro", + "name": "GPT-5.5 Pro (OpenAI)", + "display_name": "GPT-5.5 Pro (OpenAI)", "modalities": { "input": [ "text", "image", - "audio" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 1048576 + "context": 1050000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -5876,45 +6114,39 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "level", - "level": "high", - "level_options": [ - "minimal", - "low", + "effort_options": [ "medium", - "high" - ], - "summaries": true, - "visibility": "summary", - "continuation": [ - "thought_signatures" + "high", + "xhigh" ] } }, "attachment": true, "open_weights": false, - "release_date": "2026-09-02", - "last_updated": "2026-09-02", + "knowledge": "2025-12-01", + "release_date": "2026-04-23", + "last_updated": "2026-04-23", "type": "chat" }, { - "id": "glm-5", - "name": "GLM-5", - "display_name": "GLM-5", + "id": "openai/gpt-5.6-luna", + "name": "GPT-5.6 Luna (OpenAI)", + "display_name": "GPT-5.6 Luna (OpenAI)", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 202800, - "output": 131072 + "context": 1050000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -5923,60 +6155,37 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], + "default_enabled": true, + "mode": "effort", + "effort": "medium", "effort_options": [ "none", - "minimal", "low", "medium", "high", "xhigh", "max" - ] + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-02-12", - "last_updated": "2026-02-12", - "type": "chat" - }, - { - "id": "gpt-transcribe", - "name": "GPT-Transcribe", - "display_name": "GPT-Transcribe", - "modalities": { - "input": [ - "text", - "audio" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 8192, - "output": 8192 - }, - "temperature": true, - "tool_call": false, - "reasoning": { - "supported": false - }, "attachment": true, "open_weights": false, - "release_date": "2026-07-28", - "last_updated": "2026-07-28" + "knowledge": "2026-02-16", + "release_date": "2026-07-09", + "last_updated": "2026-07-09", + "type": "chat" }, { - "id": "gpt-5.4-nano", - "name": "GPT-5.4 nano", - "display_name": "GPT-5.4 nano", + "id": "openai/gpt-5", + "name": "GPT-5 (OpenAI)", + "display_name": "GPT-5 (OpenAI)", "modalities": { "input": [ "text", @@ -5990,24 +6199,23 @@ "context": 400000, "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, + "default_enabled": true, "mode": "effort", - "effort": "none", + "effort": "medium", "effort_options": [ - "none", + "minimal", "low", "medium", - "high", - "xhigh" + "high" ], "verbosity": "medium", "verbosity_options": [ @@ -6020,28 +6228,29 @@ }, "attachment": true, "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-03-17", - "last_updated": "2026-03-17", + "knowledge": "2024-09-30", + "release_date": "2025-08-07", + "last_updated": "2025-08-07", "type": "chat" }, { - "id": "minimax-m2.7-highspeed", - "name": "MiniMax-M2.7-highspeed", - "display_name": "MiniMax-M2.7-highspeed", + "id": "openai/gpt-5-mini", + "name": "GPT-5 Mini (OpenAI)", + "display_name": "GPT-5 Mini (OpenAI)", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 204800, - "output": 131072 + "context": 400000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -6050,71 +6259,65 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], + "default_enabled": true, + "mode": "effort", + "effort": "medium", "effort_options": [ + "minimal", "low", "medium", - "high", - "xhigh", - "max" - ] + "high" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-03-18", - "last_updated": "2026-03-18", + "attachment": true, + "open_weights": false, + "knowledge": "2024-05-30", + "release_date": "2025-08-07", + "last_updated": "2025-08-07", "type": "chat" }, { - "id": "deepseek-v4.1-flash-cyber", - "name": "DeepSeek V4.1 Flash Cyber", - "display_name": "DeepSeek V4.1 Flash Cyber", + "id": "openai/gpt-4o-mini", + "name": "GPT-4o Mini (OpenAI)", + "display_name": "GPT-4o Mini (OpenAI)", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1040000, - "output": 1040000 + "context": 128000, + "output": 16384 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "none", - "low", - "high", - "xhigh", - "max" - ] - } + "supported": false }, "attachment": true, "open_weights": false, - "release_date": "2026-10-29", - "last_updated": "2026-10-29", + "knowledge": "2023-09", + "release_date": "2024-07-18", + "last_updated": "2024-07-18", "type": "chat" }, { - "id": "gpt-5.5-pro", - "name": "GPT-5.5 Pro", - "display_name": "GPT-5.5 Pro", + "id": "openai/gpt-4o", + "name": "GPT-4o (OpenAI)", + "display_name": "GPT-4o (OpenAI)", "modalities": { "input": [ "text", @@ -6126,47 +6329,37 @@ ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 128000, + "output": 16384 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "effort_options": [ - "medium", - "high", - "xhigh" - ] - } + "supported": false }, "attachment": true, "open_weights": false, - "knowledge": "2025-12-01", - "release_date": "2026-04-23", - "last_updated": "2026-04-23", + "knowledge": "2023-09", + "release_date": "2024-05-13", + "last_updated": "2024-08-06", "type": "chat" }, { - "id": "gpt-5.2-pro", - "name": "GPT-5.2 Pro", - "display_name": "GPT-5.2 Pro", + "id": "openai/gpt-5.6-sol", + "name": "GPT-5.6 Sol (OpenAI)", + "display_name": "GPT-5.6 Sol (OpenAI)", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 400000, + "context": 1050000, "output": 128000 }, "temperature": false, @@ -6180,11 +6373,14 @@ "supported": true, "default_enabled": true, "mode": "effort", - "effort": "high", + "effort": "medium", "effort_options": [ + "none", + "low", "medium", "high", - "xhigh" + "xhigh", + "max" ], "verbosity": "medium", "verbosity_options": [ @@ -6197,57 +6393,66 @@ }, "attachment": true, "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2025-12-11", - "last_updated": "2025-12-11", + "knowledge": "2026-02-16", + "release_date": "2026-07-09", + "last_updated": "2026-07-09", "type": "chat" }, { - "id": "glm-5.3", - "name": "GLM-5.3", - "display_name": "GLM-5.3", + "id": "openai/gpt-5.1", + "name": "GPT-5.1 (OpenAI)", + "display_name": "GPT-5.1 (OpenAI)", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 400000, + "output": 128000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": false, + "mode": "effort", + "effort": "none", "effort_options": [ "none", - "minimal", "low", "medium", - "high", - "xhigh", - "max" - ] + "high" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-08-14", - "last_updated": "2026-08-14", + "attachment": true, + "open_weights": false, + "knowledge": "2024-09-30", + "release_date": "2025-11-13", + "last_updated": "2025-11-13", "type": "chat" }, { - "id": "claude-opus-4-8", - "name": "Claude Opus 4.8", - "display_name": "Claude Opus 4.8", + "id": "openai/gpt-5.5", + "name": "GPT-5.5 (OpenAI)", + "display_name": "GPT-5.5 (OpenAI)", "modalities": { "input": [ "text", @@ -6259,184 +6464,142 @@ ] }, "limit": { - "context": 1000000, + "context": 1050000, "output": 128000 }, "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, + "default_enabled": true, "mode": "effort", - "effort": "high", + "effort": "medium", "effort_options": [ "low", "medium", "high", - "xhigh", - "max" + "xhigh" ], - "interleaved": true, - "summaries": true, - "visibility": "omitted", - "continuation": [ - "thinking_blocks" + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" ], - "notes": [ - "Claude Opus 4.7 and newer Opus models require thinking.type = \"adaptive\" to enable thinking explicitly.", - "Manual budget_tokens requests return 400 on Claude Opus 4.7 and newer adaptive-only Opus models.", - "task_budget is separate from thinking control and should not be treated as a thinking budget." - ] + "visibility": "hidden" } }, "attachment": true, "open_weights": false, - "knowledge": "2026-01", - "release_date": "2026-05-28", - "last_updated": "2026-05-28", + "knowledge": "2025-12-01", + "release_date": "2026-04-23", + "last_updated": "2026-04-23", "type": "chat" }, { - "id": "granite-4.2-8b", - "name": "Granite 4.2 8B", - "display_name": "Granite 4.2 8B", + "id": "openai/gpt-live-transcribe", + "name": "GPT Live Transcribe (OpenAI)", + "display_name": "GPT Live Transcribe (OpenAI)", "modalities": { "input": [ - "text" + "text", + "audio" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 131072 + "context": 8192, + "output": 8192 }, - "temperature": true, - "tool_call": true, + "temperature": false, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "none", - "low", - "medium", - "high" - ] - } + "supported": false }, - "attachment": false, - "open_weights": true, - "release_date": "2026-08-25", - "last_updated": "2026-08-25", - "type": "chat" + "attachment": true, + "open_weights": false, + "release_date": "2026-07-28", + "last_updated": "2026-07-28" }, { - "id": "muse-spark-1.1", - "name": "Muse Spark 1.1", - "display_name": "Muse Spark 1.1", + "id": "openai/gpt-4o-transcribe", + "name": "GPT-4o Transcribe (OpenAI)", + "display_name": "GPT-4o Transcribe (OpenAI)", "modalities": { "input": [ "text", - "image", - "pdf", - "video" + "audio" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 16000, + "output": 2000 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "effort_options": [ - "minimal", - "low", - "medium", - "high", - "xhigh" - ] - } + "supported": false }, "attachment": true, "open_weights": false, - "release_date": "2026-04-08", - "last_updated": "2026-07-09", + "release_date": "2025-03-20", + "last_updated": "2025-03-20", "type": "chat" }, { - "id": "grok-4-20-beta-0309-reasoning", - "name": "Grok 4.20 (Reasoning)", - "display_name": "Grok 4.20 (Reasoning)", + "id": "openai/gpt-transcribe", + "name": "GPT Transcribe (OpenAI)", + "display_name": "GPT Transcribe (OpenAI)", "modalities": { "input": [ "text", - "image", - "pdf" + "audio" ], "output": [ "text" ] }, "limit": { - "context": 2000000, - "output": 30000 + "context": 8192, + "output": 8192 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "low", - "medium", - "high" - ] - } + "supported": false }, "attachment": true, "open_weights": false, - "release_date": "2026-03-09", - "last_updated": "2026-03-09", - "type": "chat" + "release_date": "2026-07-28", + "last_updated": "2026-07-28" }, { - "id": "reka-edge-2603", - "name": "Reka Edge 2603", - "display_name": "Reka Edge 2603", + "id": "openai/gpt-4.1-mini", + "name": "GPT-4.1 Mini (OpenAI)", + "display_name": "GPT-4.1 Mini (OpenAI)", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 16384, - "output": 16384 + "context": 1000000, + "output": 32768 }, "temperature": true, "tool_call": true, @@ -6445,66 +6608,60 @@ }, "attachment": true, "open_weights": false, - "release_date": "2026-10-09", - "last_updated": "2026-10-09", + "knowledge": "2024-04", + "release_date": "2025-04-14", + "last_updated": "2025-04-14", "type": "chat" }, { - "id": "gemini-4-argon", - "name": "Gemini 4 Argon", - "display_name": "Gemini 4 Argon", + "id": "openai/gpt-4.1", + "name": "GPT-4.1 (OpenAI)", + "display_name": "GPT-4.1 (OpenAI)", "modalities": { "input": [ "text", "image", - "audio" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 1048576 + "context": 1000000, + "output": 32768 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "high" - ] - } + "supported": false }, "attachment": true, "open_weights": false, - "knowledge": "2026-03", - "release_date": "2026-10-13", - "last_updated": "2026-10-13", + "knowledge": "2024-04", + "release_date": "2025-04-14", + "last_updated": "2025-04-14", "type": "chat" }, { - "id": "seed-2-0-code-preview-260328", - "name": "Seed 2.0 Code Preview (260328)", - "display_name": "Seed 2.0 Code Preview (260328)", + "id": "openai/gpt-6-luna", + "name": "GPT-6 Luna (OpenAI)", + "display_name": "GPT-6 Luna (OpenAI)", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 1050000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -6513,29 +6670,30 @@ "extra_capabilities": { "reasoning": { "effort_options": [ - "minimal", + "none", "low", "medium", - "high" + "high", + "xhigh", + "max" ] } }, "attachment": true, "open_weights": false, - "release_date": "2026-03-28", - "last_updated": "2026-03-28", + "knowledge": "2026-05-18", + "release_date": "2026-09-22", + "last_updated": "2026-09-22", "type": "chat" }, { - "id": "gemini-3.6-flash", - "name": "Gemini 3.6 Flash", - "display_name": "Gemini 3.6 Flash", + "id": "openai/o3", + "name": "o3 (OpenAI)", + "display_name": "o3 (OpenAI)", "modalities": { "input": [ "text", "image", - "video", - "audio", "pdf" ], "output": [ @@ -6543,11 +6701,11 @@ ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 200000, + "output": 100000 }, - "temperature": true, - "tool_call": true, + "temperature": false, + "tool_call": false, "reasoning": { "supported": true, "default": true @@ -6556,75 +6714,39 @@ "reasoning": { "supported": true, "default_enabled": true, - "mode": "level", - "level": "high", - "level_options": [ - "minimal", + "mode": "effort", + "effort": "medium", + "effort_options": [ "low", "medium", "high" ], - "summaries": true, - "visibility": "summary", - "continuation": [ - "thought_signatures" - ] + "visibility": "hidden" } }, "attachment": true, "open_weights": false, - "knowledge": "2026-03", - "release_date": "2026-07-21", - "last_updated": "2026-07-21", - "type": "chat" - }, - { - "id": "gpt-4.1", - "name": "GPT-4.1", - "display_name": "GPT-4.1", - "modalities": { - "input": [ - "text", - "image", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 1000000, - "output": 32768 - }, - "temperature": true, - "tool_call": true, - "reasoning": { - "supported": false - }, - "attachment": true, - "open_weights": false, - "knowledge": "2024-04", - "release_date": "2025-04-14", - "last_updated": "2025-04-14", + "knowledge": "2024-05", + "release_date": "2025-04-16", + "last_updated": "2025-04-16", "type": "chat" }, { - "id": "qwen3.6-27b", - "name": "Qwen3.6 27B", - "display_name": "Qwen3.6 27B", + "id": "meta-contributor/muse-spark-1.3-contributor", + "name": "Muse Spark 1.3 Contributor (Meta Contributor)", + "display_name": "Muse Spark 1.3 Contributor (Meta Contributor)", "modalities": { "input": [ "text", - "image", - "video" + "image" ], "output": [ "text" ] }, "limit": { - "context": 16384, - "output": 65536 + "context": 1048576, + "output": 1048576 }, "temperature": true, "tool_call": true, @@ -6634,40 +6756,39 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "minimal", + "low", + "medium", + "high", + "xhigh" ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-04-22", - "last_updated": "2026-04-22", + "open_weights": false, + "release_date": "2026-09-02", + "last_updated": "2026-09-02", "type": "chat" }, { - "id": "gpt-5.2-codex", - "name": "GPT-5.2 Codex", - "display_name": "GPT-5.2 Codex", + "id": "meta-contributor/muse-spark-1.2-contributor", + "name": "Muse Spark 1.2 Contributor (Meta Contributor)", + "display_name": "Muse Spark 1.2 Contributor (Meta Contributor)", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 1048576, + "output": 131072 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -6675,47 +6796,38 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", "effort_options": [ + "minimal", "low", "medium", "high", "xhigh" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2025-12-11", - "last_updated": "2025-12-11", + "release_date": "2026-08-06", + "last_updated": "2026-08-06", "type": "chat" }, { - "id": "nemotron-3-ultra-550b", - "name": "Nemotron 3 Ultra 550B A55B", - "display_name": "Nemotron 3 Ultra 550B A55B", + "id": "sakana/fugu-max", + "name": "Fugu Max (Sakana AI)", + "display_name": "Fugu Max (Sakana AI)", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 128000 + "context": 1000000, + "output": 1000000 }, "temperature": true, "tool_call": true, @@ -6723,17 +6835,25 @@ "supported": true, "default": true }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-09", - "release_date": "2026-06-04", - "last_updated": "2026-06-04", + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "high", + "xhigh", + "max" + ] + } + }, + "attachment": true, + "open_weights": false, + "release_date": "2026-09-11", + "last_updated": "2026-09-11", "type": "chat" }, { - "id": "claude-sonnet-4-5", - "name": "Claude Sonnet 4.5 (latest)", - "display_name": "Claude Sonnet 4.5 (latest)", + "id": "sakana/fugu-ultra-v2.0", + "name": "Fugu Ultra v2.0 (Sakana AI)", + "display_name": "Fugu Ultra v2.0 (Sakana AI)", "modalities": { "input": [ "text", @@ -6745,52 +6865,39 @@ ] }, "limit": { - "context": 200000, - "output": 64000 + "context": 1000000, + "output": 1000000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "budget", - "budget": { - "min": 1024, - "unit": "tokens" - }, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", - "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." + "effort_options": [ + "high", + "xhigh", + "max" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-07-31", - "release_date": "2025-09-29", - "last_updated": "2025-09-29", + "knowledge": "2026-08-28", + "release_date": "2026-09-11", + "last_updated": "2026-09-11", "type": "chat" }, { - "id": "step-5-preview", - "name": "Step 5 Preview", - "display_name": "Step 5 Preview", + "id": "sakana/fugu-ultra", + "name": "Fugu Ultra (Sakana AI)", + "display_name": "Fugu Ultra (Sakana AI)", "modalities": { "input": [ "text", - "image", - "video" + "image" ], "output": [ "text" @@ -6800,29 +6907,36 @@ "context": 1000000, "output": 1000000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, "default": true }, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "high", + "xhigh", + "max" + ] + } + }, "attachment": true, - "open_weights": true, - "release_date": "2026-09-16", - "last_updated": "2026-09-20", + "open_weights": false, + "release_date": "2026-06-15", + "last_updated": "2026-06-15", "type": "chat" }, { - "id": "gemini-3.5-flash", - "name": "Gemini 3.5 Flash", - "display_name": "Gemini 3.5 Flash", + "id": "nebius/minimax-m3.1-flash", + "name": "MiniMax M3.1 Flash (Nebius AI)", + "display_name": "MiniMax M3.1 Flash (Nebius AI)", "modalities": { "input": [ "text", "image", - "video", - "audio", - "pdf" + "video" ], "output": [ "text" @@ -6830,7 +6944,7 @@ }, "limit": { "context": 1048576, - "output": 65536 + "output": 1048576 }, "temperature": true, "tool_call": true, @@ -6840,78 +6954,84 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "level", - "level": "high", - "level_options": [ + "effort_options": [ + "none", "minimal", "low", "medium", - "high" - ], - "summaries": true, - "visibility": "summary", - "continuation": [ - "thought_signatures" + "high", + "xhigh", + "max" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-01", - "release_date": "2026-05-19", - "last_updated": "2026-05-19", + "release_date": "2026-10-19", + "last_updated": "2026-10-19", "type": "chat" }, { - "id": "muse-spark-1.2", - "name": "Muse Spark 1.2", - "display_name": "Muse Spark 1.2", + "id": "vertex-anthropic/claude-opus-4-7", + "name": "Claude Opus 4.7 (Vertex AI (Anthropic))", + "display_name": "Claude Opus 4.7 (Vertex AI (Anthropic))", "modalities": { "input": [ "text", "image", - "video", - "pdf", - "audio" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 1000000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": false, + "mode": "effort", + "effort": "high", "effort_options": [ - "minimal", "low", "medium", "high", - "xhigh" + "xhigh", + "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "omitted", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Claude Opus 4.7 and newer Opus models require thinking.type = \"adaptive\" to enable thinking explicitly.", + "Manual budget_tokens requests return 400 on Claude Opus 4.7 and newer adaptive-only Opus models.", + "task_budget is separate from thinking control and should not be treated as a thinking budget." ] } }, "attachment": true, "open_weights": false, - "release_date": "2026-08-05", - "last_updated": "2026-08-05", + "knowledge": "2026-01-31", + "release_date": "2026-04-16", + "last_updated": "2026-04-16", "type": "chat" }, { - "id": "claude-opus-4-5-20251101", - "name": "Claude Opus 4.5", - "display_name": "Claude Opus 4.5", + "id": "vertex-anthropic/claude-haiku-4-5", + "name": "Claude Haiku 4.5 (Vertex AI (Anthropic))", + "display_name": "Claude Haiku 4.5 (Vertex AI (Anthropic))", "modalities": { "input": [ "text", @@ -6936,17 +7056,11 @@ "reasoning": { "supported": true, "default_enabled": false, - "mode": "mixed", + "mode": "budget", "budget": { "min": 1024, "unit": "tokens" }, - "effort": "high", - "effort_options": [ - "low", - "medium", - "high" - ], "interleaved": true, "summaries": true, "visibility": "summary", @@ -6954,36 +7068,37 @@ "thinking_blocks" ], "notes": [ - "Claude Opus 4.5 uses manual thinking.type = \"enabled\" with budget_tokens; effort can be used alongside the thinking budget.", - "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header." + "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", + "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-05", - "release_date": "2025-11-24", - "last_updated": "2025-11-24", + "knowledge": "2025-02-28", + "release_date": "2025-10-15", + "last_updated": "2025-10-15", "type": "chat" }, { - "id": "seed-1-6-250615", - "name": "Seed 1.6 (250615)", - "display_name": "Seed 1.6 (250615)", + "id": "vertex-anthropic/claude-sonnet-5", + "name": "Claude Sonnet 5 (Vertex AI (Anthropic))", + "display_name": "Claude Sonnet 5 (Vertex AI (Anthropic))", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 256000, - "output": 8192 + "context": 1000000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -6991,74 +7106,80 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, "effort_options": [ - "minimal", + "none", "low", "medium", - "high" + "high", + "xhigh", + "max" ] } }, "attachment": true, - "open_weights": true, - "release_date": "2025-06-25", - "last_updated": "2025-06-25", + "open_weights": false, + "knowledge": "2026-01-31", + "release_date": "2026-06-30", + "last_updated": "2026-06-30", "type": "chat" }, { - "id": "kimi-k2.7-code-highspeed", - "name": "Kimi K2.7 Code Highspeed", - "display_name": "Kimi K2.7 Code Highspeed", + "id": "vertex-anthropic/claude-sonnet-4-5", + "name": "Claude Sonnet 4.5 (Vertex AI (Anthropic))", + "display_name": "Claude Sonnet 4.5 (Vertex AI (Anthropic))", "modalities": { "input": [ "text", "image", - "video" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 200000, + "output": 64000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": false, + "mode": "budget", + "budget": { + "min": 1024, + "unit": "tokens" + }, "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ "thinking_blocks" ], - "effort_options": [ - "minimal", - "low", - "medium", - "high", - "xhigh", - "max" + "notes": [ + "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", + "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." ] } }, "attachment": true, - "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-06-12", - "last_updated": "2026-06-12", + "open_weights": false, + "knowledge": "2025-07-31", + "release_date": "2025-09-29", + "last_updated": "2025-09-29", "type": "chat" }, { - "id": "claude-fable-5", - "name": "Claude Fable 5", - "display_name": "Claude Fable 5", + "id": "vertex-anthropic/claude-sonnet-4-6", + "name": "Claude Sonnet 4.6 (Vertex AI (Anthropic))", + "display_name": "Claude Sonnet 4.6 (Vertex AI (Anthropic))", "modalities": { "input": [ "text", @@ -7071,51 +7192,53 @@ }, "limit": { "context": 1000000, - "output": 128000 + "output": 64000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", + "default_enabled": false, + "mode": "mixed", + "budget": { + "min": 1024, + "unit": "tokens" + }, "effort": "high", "effort_options": [ "low", "medium", "high", - "xhigh", "max" ], "interleaved": true, "summaries": true, - "visibility": "omitted", + "visibility": "summary", "continuation": [ "thinking_blocks" ], "notes": [ - "Adaptive thinking is always on for Claude Fable 5 and Claude Mythos 5; thinking.type = \"disabled\" is rejected.", - "Manual budget_tokens requests return 400 on Claude Fable 5 and Claude Mythos 5.", - "thinking.display defaults to omitted; set display to summarized to receive readable thinking summaries." + "Anthropic recommends adaptive thinking with effort for Claude 4.6; budget_tokens remains a deprecated compatibility path.", + "Anthropic API defaults effort to high; lower effort levels should be chosen per workload." ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-01-31", - "release_date": "2026-06-09", - "last_updated": "2026-06-09", + "knowledge": "2025-08-31", + "release_date": "2026-02-17", + "last_updated": "2026-03-13", "type": "chat" }, { - "id": "gpt-6.1-terra", - "name": "GPT-6.1 Terra", - "display_name": "GPT-6.1 Terra", + "id": "vertex-anthropic/claude-opus-4-6", + "name": "Claude Opus 4.6 (Vertex AI (Anthropic))", + "display_name": "Claude Opus 4.6 (Vertex AI (Anthropic))", "modalities": { "input": [ "text", @@ -7127,38 +7250,54 @@ ] }, "limit": { - "context": 1050000, + "context": 1000000, "output": 128000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": false, + "mode": "mixed", + "budget": { + "min": 1024, + "unit": "tokens" + }, + "effort": "high", "effort_options": [ - "none", "low", "medium", "high", - "xhigh", "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Anthropic recommends adaptive thinking with effort for Claude 4.6; budget_tokens remains a deprecated compatibility path.", + "Anthropic API defaults effort to high; lower effort levels should be chosen per workload." ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-06-01", - "release_date": "2026-10-26", - "last_updated": "2026-10-26", + "knowledge": "2025-05-31", + "release_date": "2026-02-05", + "last_updated": "2026-03-13", "type": "chat" }, { - "id": "grok-4-20-reasoning", - "name": "Grok 4.20 (Reasoning)", - "display_name": "Grok 4.20 (Reasoning)", + "id": "vertex-anthropic/claude-opus-4-5-20251101", + "name": "Claude Opus 4.5 (Vertex AI (Anthropic))", + "display_name": "Claude Opus 4.5 (Vertex AI (Anthropic))", "modalities": { "input": [ "text", @@ -7170,73 +7309,97 @@ ] }, "limit": { - "context": 2000000, - "output": 30000 + "context": 200000, + "output": 32000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": false, + "mode": "mixed", + "budget": { + "min": 1024, + "unit": "tokens" + }, + "effort": "high", "effort_options": [ "low", "medium", "high" + ], + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Claude Opus 4.5 uses manual thinking.type = \"enabled\" with budget_tokens; effort can be used alongside the thinking budget.", + "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header." ] } }, "attachment": true, "open_weights": false, - "release_date": "2026-03-09", - "last_updated": "2026-03-09", + "knowledge": "2025-05", + "release_date": "2025-11-24", + "last_updated": "2025-11-24", "type": "chat" }, { - "id": "grok-4-20-non-reasoning", - "name": "Grok 4.20 (Non-Reasoning)", - "display_name": "Grok 4.20 (Non-Reasoning)", + "id": "greenference/qwen3-30b-a3b", + "name": "Qwen3 30B A3B (greenference)", + "display_name": "Qwen3 30B A3B (greenference)", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 2000000, - "output": 30000 + "context": 8192, + "output": 4096 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { "supported": true }, "extra_capabilities": { "reasoning": { - "supported": true + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] } }, - "attachment": true, - "open_weights": false, - "release_date": "2026-03-09", - "last_updated": "2026-03-09", + "attachment": false, + "open_weights": true, + "release_date": "2025-04-28", + "last_updated": "2025-04-28", "type": "chat" }, { - "id": "muse-spark-1.2-contributor", - "name": "Muse Spark 1.2 Contributor", - "display_name": "Muse Spark 1.2 Contributor", + "id": "greenference/glm-5.3-flash", + "name": "GLM-5.3 Flash (greenference)", + "display_name": "GLM-5.3 Flash (greenference)", "modalities": { "input": [ "text", - "image" + "image", + "video", + "pdf" ], "output": [ "text" @@ -7244,7 +7407,7 @@ }, "limit": { "context": 1048576, - "output": 1048576 + "output": 131072 }, "temperature": true, "tool_call": true, @@ -7254,207 +7417,171 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, "effort_options": [ - "minimal", "low", "medium", - "high", - "xhigh" + "high" ] } }, "attachment": true, - "open_weights": false, - "release_date": "2026-08-06", - "last_updated": "2026-08-06", + "open_weights": true, + "release_date": "2026-08-26", + "last_updated": "2026-08-26", "type": "chat" }, { - "id": "mistral-large-latest", - "name": "Mistral Large (latest)", - "display_name": "Mistral Large (latest)", + "id": "greenference/qwen3-14b", + "name": "Qwen3 14B (greenference)", + "display_name": "Qwen3 14B (greenference)", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 262144 + "context": 16384, + "output": 8192 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true }, - "attachment": true, - "open_weights": true, - "knowledge": "2024-11", - "release_date": "2024-11-01", - "last_updated": "2025-12-02", + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } + }, + "attachment": false, + "open_weights": false, + "release_date": "2025-04-28", + "last_updated": "2025-04-28", "type": "chat" }, { - "id": "claude-fable-5-1", - "name": "Claude Fable 5.1", - "display_name": "Claude Fable 5.1", + "id": "greenference/qwen3.6-27b", + "name": "Qwen3.6 27B (greenference)", + "display_name": "Qwen3.6 27B (greenference)", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 16384, + "output": 8192 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true + "supported": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "high", - "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" - ], "interleaved": true, "summaries": true, - "visibility": "omitted", + "visibility": "summary", "continuation": [ "thinking_blocks" - ], - "notes": [ - "Adaptive thinking is always on for Claude Fable 5 and Claude Mythos 5; thinking.type = \"disabled\" is rejected.", - "Manual budget_tokens requests return 400 on Claude Fable 5 and Claude Mythos 5.", - "thinking.display defaults to omitted; set display to summarized to receive readable thinking summaries." ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-06", - "release_date": "2026-09-01", - "last_updated": "2026-09-01", + "attachment": false, + "open_weights": true, + "release_date": "2026-04-22", + "last_updated": "2026-04-22", "type": "chat" }, { - "id": "glm-4.5", - "name": "GLM-4.5", - "display_name": "GLM-4.5", + "id": "deepinfra/qwen3-vl-235b-a22b-instruct", + "name": "Qwen3 VL 235B A22B Instruct (DeepInfra)", + "display_name": "Qwen3 VL 235B A22B Instruct (DeepInfra)", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 98304 + "context": 262144, + "output": 32768 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true + "supported": false }, - "attachment": false, + "attachment": true, "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-07-28", - "last_updated": "2025-07-28", + "knowledge": "2025-03-31", + "release_date": "2025-09-23", + "last_updated": "2025-09-23", "type": "chat" }, { - "id": "gpt-5.2", - "name": "GPT-5.2", - "display_name": "GPT-5.2", + "id": "deepinfra/qwen3-vl-30b-a3b-instruct", + "name": "Qwen3 VL 30B A3B Instruct (DeepInfra)", + "display_name": "Qwen3 VL 30B A3B Instruct (DeepInfra)", "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 262144, + "output": 32768 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": false - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "none", - "effort_options": [ - "none", - "low", - "medium", - "high", - "xhigh" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" - } + "supported": false }, "attachment": true, - "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2025-12-11", - "last_updated": "2025-12-11", + "open_weights": true, + "release_date": "2025-10-04", + "last_updated": "2025-10-04", "type": "chat" }, { - "id": "gpt-6.1-sol", - "name": "GPT-6.1 Sol", - "display_name": "GPT-6.1 Sol", + "id": "deepinfra/gemma-4-26b-a4b-it", + "name": "Gemma 4 26B A4B IT (DeepInfra)", + "display_name": "Gemma 4 26B A4B IT (DeepInfra)", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 262144, + "output": 32768 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -7462,38 +7589,33 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" - ] + "supported": true } }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-04-30", - "release_date": "2026-09-29", - "last_updated": "2026-09-29", + "attachment": false, + "open_weights": true, + "release_date": "2026-04-02", + "last_updated": "2026-04-02", "type": "chat" }, { - "id": "muse-glimmer-30b", - "name": "Muse Glimmer 30B", - "display_name": "Muse Glimmer 30B", + "id": "deepinfra/mimo-v2.6-pro", + "name": "MiMo V2.6 Pro (DeepInfra)", + "display_name": "MiMo V2.6 Pro (DeepInfra)", "modalities": { "input": [ "text", - "image" + "image", + "audio", + "video" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 131072 + "context": 1048576, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -7504,6 +7626,7 @@ "extra_capabilities": { "reasoning": { "effort_options": [ + "none", "minimal", "low", "medium", @@ -7515,15 +7638,14 @@ }, "attachment": true, "open_weights": true, - "knowledge": "2026-01-04", - "release_date": "2026-08-10", - "last_updated": "2026-08-10", + "release_date": "2026-09-22", + "last_updated": "2026-09-22", "type": "chat" }, { - "id": "llama-4-scout-17b-instruct", - "name": "Llama 4 Scout 17B Instruct", - "display_name": "Llama 4 Scout 17B Instruct", + "id": "deepinfra/deepseek-v4.1-flash-1017", + "name": "DeepSeek V4.1 Flash 1017 (DeepInfra)", + "display_name": "DeepSeek V4.1 Flash 1017 (DeepInfra)", "modalities": { "input": [ "text", @@ -7534,57 +7656,72 @@ ] }, "limit": { - "context": 131072, - "output": 2048 + "context": 1048576, + "output": 393216 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "none", + "minimal", + "low", + "medium", + "high", + "xhigh", + "max" + ] + } }, "attachment": true, "open_weights": true, - "knowledge": "2024-08", - "release_date": "2025-04-05", - "last_updated": "2025-04-05", + "knowledge": "2025-05", + "release_date": "2026-10-17", + "last_updated": "2026-10-17", "type": "chat" }, { - "id": "qwen-max", - "name": "Qwen Max", - "display_name": "Qwen Max", + "id": "deepinfra/nemotron-3-ultra-550b", + "name": "Nemotron 3 Ultra 550B (DeepInfra)", + "display_name": "Nemotron 3 Ultra 550B (DeepInfra)", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 32768, - "output": 8192 + "context": 262144, + "output": 262144 }, "temperature": true, "tool_call": true, "reasoning": { "supported": false }, - "attachment": false, + "attachment": true, "open_weights": false, - "knowledge": "2024-04", - "release_date": "2024-04-03", - "last_updated": "2025-01-25", + "release_date": "2026-06-01", + "last_updated": "2026-06-01", "type": "chat" }, { - "id": "qwen3.5-27b", - "name": "Qwen3.5 27B", - "display_name": "Qwen3.5 27B", + "id": "deepinfra/mimo-v2.6-flash", + "name": "MiMo V2.6 Flash (DeepInfra)", + "display_name": "MiMo V2.6 Flash (DeepInfra)", "modalities": { "input": [ "text", "image", + "audio", "video" ], "output": [ @@ -7592,7 +7729,7 @@ ] }, "limit": { - "context": 262144, + "context": 1048576, "output": 65536 }, "temperature": true, @@ -7603,13 +7740,6 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], "effort_options": [ "none", "minimal", @@ -7623,14 +7753,47 @@ }, "attachment": true, "open_weights": true, - "release_date": "2026-02-23", - "last_updated": "2026-02-23", + "release_date": "2026-09-22", + "last_updated": "2026-09-22", "type": "chat" }, { - "id": "seed-2-0-pro-260328", - "name": "Seed 2.0 Pro (260328)", - "display_name": "Seed 2.0 Pro (260328)", + "id": "deepinfra/deepseek-v3.2", + "name": "DeepSeek V3.2 (DeepInfra)", + "display_name": "DeepSeek V3.2 (DeepInfra)", + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 160000, + "output": 65536 + }, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true + } + }, + "attachment": false, + "open_weights": true, + "knowledge": "2024-07", + "release_date": "2025-12-01", + "last_updated": "2025-12-01", + "type": "chat" + }, + { + "id": "deepinfra/deepseek-v4.1-flash", + "name": "DeepSeek V4.1 Flash (DeepInfra)", + "display_name": "DeepSeek V4.1 Flash (DeepInfra)", "modalities": { "input": [ "text", @@ -7641,8 +7804,8 @@ ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 1048576, + "output": 393216 }, "temperature": true, "tool_call": true, @@ -7652,36 +7815,39 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, "effort_options": [ + "none", "minimal", "low", "medium", - "high" + "high", + "xhigh", + "max" ] } }, "attachment": true, - "open_weights": false, - "release_date": "2026-03-28", - "last_updated": "2026-03-28", + "open_weights": true, + "knowledge": "2025-05", + "release_date": "2026-09-10", + "last_updated": "2026-09-10", "type": "chat" }, { - "id": "qwen3.7-plus", - "name": "Qwen3.7 Plus", - "display_name": "Qwen3.7 Plus", + "id": "deepinfra/deepseek-v4-pro", + "name": "DeepSeek V4 Pro (DeepInfra)", + "display_name": "DeepSeek V4 Pro (DeepInfra)", "modalities": { "input": [ - "text", - "image", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 983616, + "context": 1048576, "output": 64000 }, "temperature": true, @@ -7693,68 +7859,87 @@ "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "high", "effort_options": [ - "none", - "minimal", "low", - "medium", "high", - "xhigh", "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-04", - "release_date": "2026-06-02", - "last_updated": "2026-06-02", + "attachment": false, + "open_weights": true, + "knowledge": "2025-05", + "release_date": "2026-04-24", + "last_updated": "2026-04-24", "type": "chat" }, { - "id": "mistral-medium-3-5", - "name": "Mistral Medium 3.5", - "display_name": "Mistral Medium 3.5", + "id": "deepinfra/granite-4.2-8b", + "name": "Granite 4.2 8B (DeepInfra)", + "display_name": "Granite 4.2 8B (DeepInfra)", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 131072, + "output": 117964 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": true, - "open_weights": false, - "release_date": "2026-04-30", - "last_updated": "2026-04-30", + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "none", + "low", + "medium", + "high" + ] + } + }, + "attachment": false, + "open_weights": true, + "release_date": "2026-08-25", + "last_updated": "2026-08-25", "type": "chat" }, { - "id": "grok-4-5", - "name": "Grok 4.5", - "display_name": "Grok 4.5", + "id": "deepinfra/qwen3.5-9b", + "name": "Qwen3.5 9B (DeepInfra)", + "display_name": "Qwen3.5 9B (DeepInfra)", "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 500000, - "output": 500000 + "context": 262144, + "output": 32768 }, "temperature": true, "tool_call": true, @@ -7764,23 +7949,25 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "low", - "medium", - "high" + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, "attachment": true, - "open_weights": false, - "release_date": "2026-07-08", - "last_updated": "2026-07-08", + "open_weights": true, + "release_date": "2026-02-23", + "last_updated": "2026-02-23", "type": "chat" }, { - "id": "qwen3-coder-flash", - "name": "Qwen3 Coder Flash", - "display_name": "Qwen3 Coder Flash", + "id": "deepinfra/deepseek-v4-flash", + "name": "DeepSeek V4 Flash (DeepInfra)", + "display_name": "DeepSeek V4 Flash (DeepInfra)", "modalities": { "input": [ "text" @@ -7790,37 +7977,61 @@ ] }, "limit": { - "context": 997952, - "output": 65536 + "context": 1000000, + "output": 393216 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "high", + "effort_options": [ + "low", + "high", + "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." + ] + } }, "attachment": false, - "open_weights": false, - "knowledge": "2025-04", - "release_date": "2025-07-28", - "last_updated": "2025-07-28", + "open_weights": true, + "knowledge": "2025-05", + "release_date": "2026-04-24", + "last_updated": "2026-04-24", "type": "chat" }, { - "id": "glm-4.6v-flashx", - "name": "GLM-4.6V FlashX", - "display_name": "GLM-4.6V FlashX", + "id": "deepinfra/ling-3.0-flash-vl", + "name": "Ling 3.0 Flash VL (DeepInfra)", + "display_name": "Ling 3.0 Flash VL (DeepInfra)", "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 16000 + "context": 131072, + "output": 32768 }, "temperature": true, "tool_call": true, @@ -7828,16 +8039,26 @@ "supported": true, "default": true }, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "none", + "low", + "medium", + "high" + ] + } + }, "attachment": true, - "open_weights": false, - "release_date": "2025-12-08", - "last_updated": "2025-12-08", + "open_weights": true, + "release_date": "2026-09-10", + "last_updated": "2026-09-10", "type": "chat" }, { - "id": "minimax-m2.5-highspeed", - "name": "MiniMax-M2.5-highspeed", - "display_name": "MiniMax-M2.5-highspeed", + "id": "deepinfra/gemma-4-31b-it", + "name": "Gemma 4 31B IT (DeepInfra)", + "display_name": "Gemma 4 31B IT (DeepInfra)", "modalities": { "input": [ "text" @@ -7847,8 +8068,8 @@ ] }, "limit": { - "context": 204800, - "output": 131072 + "context": 262144, + "output": 32768 }, "temperature": true, "tool_call": true, @@ -7860,6 +8081,8 @@ "reasoning": { "supported": true, "effort_options": [ + "none", + "minimal", "low", "medium", "high", @@ -7870,14 +8093,15 @@ }, "attachment": false, "open_weights": true, - "release_date": "2026-02-13", - "last_updated": "2026-02-13", + "knowledge": "2025-01", + "release_date": "2026-04-02", + "last_updated": "2026-04-02", "type": "chat" }, { - "id": "gpt-5-nano", - "name": "GPT-5 Nano", - "display_name": "GPT-5 Nano", + "id": "deepinfra/muse-glimmer-30b", + "name": "Muse Glimmer 30B (DeepInfra)", + "display_name": "Muse Glimmer 30B (DeepInfra)", "modalities": { "input": [ "text", @@ -7888,10 +8112,10 @@ ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 131072, + "output": 16384 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -7899,36 +8123,27 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", "effort_options": [ "minimal", "low", "medium", - "high" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + "high", + "xhigh", + "max" + ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2024-05-30", - "release_date": "2025-08-07", - "last_updated": "2025-08-07", + "open_weights": true, + "knowledge": "2026-01-04", + "release_date": "2026-08-10", + "last_updated": "2026-08-10", "type": "chat" }, { - "id": "llama-3.2-11b-instruct", - "name": "Llama 3.2 11B Instruct", - "display_name": "Llama 3.2 11B Instruct", + "id": "deepinfra/ling-3.0-flash", + "name": "InclusionAI Ling 3.0 Flash (DeepInfra)", + "display_name": "InclusionAI Ling 3.0 Flash (DeepInfra)", "modalities": { "input": [ "text" @@ -7938,35 +8153,38 @@ ] }, "limit": { - "context": 128000, - "output": 8192 + "context": 262144, + "output": 32768 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, "attachment": false, "open_weights": true, - "release_date": "2024-09-25", - "last_updated": "2024-09-25", + "release_date": "2026-07-23", + "last_updated": "2026-07-23", "type": "chat" }, { - "id": "glm-5.2", - "name": "GLM-5.2", - "display_name": "GLM-5.2", + "id": "xai/grok-build-0-1", + "name": "Grok Build 0.1 (xAI)", + "display_name": "Grok Build 0.1 (xAI)", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 256000, + "output": 256000 }, "temperature": true, "tool_call": true, @@ -7976,73 +8194,76 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], "effort_options": [ - "none", - "minimal", "low", "medium", - "high", - "xhigh", - "max" + "high" ] } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-06-13", - "last_updated": "2026-06-13", + "attachment": true, + "open_weights": false, + "release_date": "2026-04-16", + "last_updated": "2026-04-16", "type": "chat" }, { - "id": "codestral-2508", - "name": "Codestral", - "display_name": "Codestral", + "id": "xai/grok-4-3", + "name": "Grok 4.3 (xAI)", + "display_name": "Grok 4.3 (xAI)", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 256000, - "output": 16384 + "context": 1000000, + "output": 1000000 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "none", + "low", + "medium", + "high" + ] + } + }, + "attachment": true, "open_weights": false, - "release_date": "2025-07-30", - "last_updated": "2025-07-30", + "release_date": "2026-04-17", + "last_updated": "2026-04-17", "type": "chat" }, { - "id": "fugu-ultra", - "name": "Fugu Ultra", - "display_name": "Fugu Ultra", + "id": "xai/grok-4-7", + "name": "Grok 4.7 (xAI)", + "display_name": "Grok 4.7 (xAI)", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 1000000 + "context": 500000, + "output": 500000 }, "temperature": true, "tool_call": true, @@ -8053,31 +8274,24 @@ "extra_capabilities": { "reasoning": { "effort_options": [ + "low", + "medium", "high", - "xhigh", - "max" + "xhigh" ] } }, "attachment": true, "open_weights": false, - "release_date": "2026-06-15", - "last_updated": "2026-06-15", + "knowledge": "2026-05", + "release_date": "2026-09-21", + "last_updated": "2026-09-21", "type": "chat" - } - ] - }, - "above": { - "id": "above", - "name": "above.dev", - "display_name": "above.dev", - "api": "https://api.above.dev/v1", - "doc": "https://above.dev/docs", - "models": [ + }, { - "id": "glm-5.3-flash", - "name": "GLM 5.3 Flash", - "display_name": "GLM 5.3 Flash", + "id": "xai/grok-4-5", + "name": "Grok 4.5 (xAI)", + "display_name": "Grok 4.5 (xAI)", "modalities": { "input": [ "text", @@ -8088,8 +8302,8 @@ ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 500000, + "output": 500000 }, "temperature": true, "tool_call": true, @@ -8099,29 +8313,52 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], "effort_options": [ - "high", - "max" + "low", + "medium", + "high" ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-08-26", - "last_updated": "2026-08-26", + "open_weights": false, + "release_date": "2026-07-08", + "last_updated": "2026-07-08", "type": "chat" }, { - "id": "deepseek-v4-flash", - "name": "DeepSeek V4 Flash", - "display_name": "DeepSeek V4 Flash", + "id": "xai/grok-4-20-beta-0309-non-reasoning", + "name": "Grok 4.20 Beta Non-Reasoning (0309) (xAI)", + "display_name": "Grok 4.20 Beta Non-Reasoning (0309) (xAI)", + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 2000000, + "output": 30000 + }, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": false + }, + "attachment": true, + "open_weights": false, + "release_date": "2026-03-09", + "last_updated": "2026-03-09", + "type": "chat" + }, + { + "id": "xai/grok-4-6", + "name": "Grok 4.6 (xAI)", + "display_name": "Grok 4.6 (xAI)", "modalities": { "input": [ "text", @@ -8132,8 +8369,8 @@ ] }, "limit": { - "context": 1000000, - "output": 384000 + "context": 500000, + "output": 500000 }, "temperature": true, "tool_call": true, @@ -8143,51 +8380,38 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "high", "effort_options": [ "low", + "medium", "high", - "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." + "xhigh" ] } }, "attachment": true, - "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-09-10", - "last_updated": "2026-09-10", + "open_weights": false, + "knowledge": "2026-02-01", + "release_date": "2026-08-12", + "last_updated": "2026-08-12", "type": "chat" }, { - "id": "mimo-v2.6-pro", - "name": "MiMo V2.6 Pro", - "display_name": "MiMo V2.6 Pro", + "id": "xai/grok-4-20-beta-0309-reasoning", + "name": "Grok 4.20 Beta Reasoning (0309) (xAI)", + "display_name": "Grok 4.20 Beta Reasoning (0309) (xAI)", "modalities": { "input": [ "text", "image", - "audio", - "video" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 2000000, + "output": 30000 }, "temperature": true, "tool_call": true, @@ -8197,36 +8421,35 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "low", + "medium", + "high" ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-09-22", - "last_updated": "2026-09-22", + "open_weights": false, + "release_date": "2026-03-09", + "last_updated": "2026-03-09", "type": "chat" }, { - "id": "deepseek-v4-pro", - "name": "DeepSeek V4 Pro", - "display_name": "DeepSeek V4 Pro", + "id": "xai/grok-4", + "name": "Grok 4 (xAI)", + "display_name": "Grok 4 (xAI)", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 384000 + "context": 256000, + "output": 256000 }, "temperature": true, "tool_call": true, @@ -8236,42 +8459,23 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "high", - "effort_options": [ - "low", - "high", - "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." - ] + "supported": true } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-08-13", - "last_updated": "2026-08-22", + "attachment": true, + "open_weights": false, + "release_date": "2025-07-09", + "last_updated": "2025-07-09", "type": "chat" }, { - "id": "mimo-v2.6-flash", - "name": "MiMo V2.6 Flash", - "display_name": "MiMo V2.6 Flash", + "id": "baidu/deepseek-v4.1-flash-1017", + "name": "DeepSeek V4.1 Flash 1017 (Baidu)", + "display_name": "DeepSeek V4.1 Flash 1017 (Baidu)", "modalities": { "input": [ "text", - "image", - "audio", - "video" + "image" ], "output": [ "text" @@ -8279,7 +8483,7 @@ }, "limit": { "context": 1048576, - "output": 131072 + "output": 393216 }, "temperature": true, "tool_call": true, @@ -8289,31 +8493,34 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "none", + "minimal", + "low", + "medium", + "high", + "xhigh", + "max" ] } }, "attachment": true, "open_weights": true, - "release_date": "2026-09-22", - "last_updated": "2026-09-22", + "knowledge": "2025-05", + "release_date": "2026-10-17", + "last_updated": "2026-10-17", "type": "chat" }, { - "id": "mimo-v2.6-pro-ultraspeed", - "name": "MiMo V2.6 Pro UltraSpeed", - "display_name": "MiMo V2.6 Pro UltraSpeed", + "id": "baidu/glm-5.3-flash", + "name": "GLM-5.3 Flash (Baidu)", + "display_name": "GLM-5.3 Flash (Baidu)", "modalities": { "input": [ "text", "image", - "audio", - "video" + "video", + "pdf" ], "output": [ "text" @@ -8332,35 +8539,34 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "xhigh", + "max" ] } }, "attachment": true, "open_weights": true, - "release_date": "2026-09-21", - "last_updated": "2026-09-21", + "release_date": "2026-08-26", + "last_updated": "2026-08-26", "type": "chat" }, { - "id": "glm-5.2-fast", - "name": "GLM 5.2 Fast", - "display_name": "GLM 5.2 Fast", + "id": "baidu/deepseek-v4.1-flash", + "name": "DeepSeek V4.1 Flash (Baidu)", + "display_name": "DeepSeek V4.1 Flash (Baidu)", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 1048576, + "output": 393216 }, "temperature": true, "tool_call": true, @@ -8371,41 +8577,39 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], "effort_options": [ "none", + "minimal", + "low", + "medium", "high", + "xhigh", "max" ] } }, - "attachment": false, + "attachment": true, "open_weights": true, - "release_date": "2026-06-13", - "last_updated": "2026-06-13", + "knowledge": "2025-05", + "release_date": "2026-09-10", + "last_updated": "2026-09-10", "type": "chat" }, { - "id": "qwen3.8-max", - "name": "Qwen 3.8 Max", - "display_name": "Qwen 3.8 Max", + "id": "baidu/deepseek-v4-pro", + "name": "DeepSeek V4 Pro (Baidu)", + "display_name": "DeepSeek V4 Pro (Baidu)", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 32768 + "context": 1048576, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -8416,36 +8620,47 @@ "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "high", + "effort_options": [ + "low", + "high", + "max" + ], "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ "thinking_blocks" + ], + "notes": [ + "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." ] } }, - "attachment": true, - "open_weights": false, - "release_date": "2026-08-03", - "last_updated": "2026-08-03", + "attachment": false, + "open_weights": true, + "knowledge": "2025-05", + "release_date": "2026-04-24", + "last_updated": "2026-04-24", "type": "chat" }, { - "id": "deepseek-v4.1-flash", - "name": "DeepSeek V4.1 Flash", - "display_name": "DeepSeek V4.1 Flash", + "id": "baidu/glm-5.3", + "name": "GLM-5.3 (Baidu)", + "display_name": "GLM-5.3 (Baidu)", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 384000 + "context": 1048576, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -8456,12 +8671,6 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], "effort_options": [ "low", "high", @@ -8469,17 +8678,16 @@ ] } }, - "attachment": true, + "attachment": false, "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-09-10", - "last_updated": "2026-09-10", + "release_date": "2026-08-14", + "last_updated": "2026-08-14", "type": "chat" }, { - "id": "glm-5.2", - "name": "GLM 5.2", - "display_name": "GLM 5.2", + "id": "baidu/deepseek-v4-flash", + "name": "DeepSeek V4 Flash (Baidu)", + "display_name": "DeepSeek V4 Flash (Baidu)", "modalities": { "input": [ "text" @@ -8489,7 +8697,7 @@ ] }, "limit": { - "context": 1000000, + "context": 1048576, "output": 131072 }, "temperature": true, @@ -8501,38 +8709,36 @@ "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "high", + "effort_options": [ + "low", + "high", + "max" + ], "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ "thinking_blocks" ], - "effort_options": [ - "none", - "high", - "max" + "notes": [ + "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." ] } }, "attachment": false, "open_weights": true, - "release_date": "2026-06-13", - "last_updated": "2026-06-13", + "knowledge": "2025-05", + "release_date": "2026-04-24", + "last_updated": "2026-04-24", "type": "chat" - } - ] - }, - "modelscope": { - "id": "modelscope", - "name": "ModelScope", - "display_name": "ModelScope", - "api": "https://api-inference.modelscope.cn/v1", - "doc": "https://modelscope.cn/docs/model-service/API-Inference/intro", - "models": [ + }, { - "id": "Qwen/Qwen3-235B-A22B-Thinking-2507", - "name": "Qwen3-235B-A22B-Thinking-2507", - "display_name": "Qwen3-235B-A22B-Thinking-2507", + "id": "baidu/glm-5.1", + "name": "GLM-5.1 (Baidu)", + "display_name": "GLM-5.1 (Baidu)", "modalities": { "input": [ "text" @@ -8542,7 +8748,7 @@ ] }, "limit": { - "context": 262144, + "context": 202752, "output": 131072 }, "temperature": true, @@ -8559,20 +8765,28 @@ "visibility": "summary", "continuation": [ "thinking_blocks" + ], + "effort_options": [ + "none", + "minimal", + "low", + "medium", + "high", + "xhigh", + "max" ] } }, "attachment": false, "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-07-25", - "last_updated": "2025-07-25", + "release_date": "2026-04-07", + "last_updated": "2026-04-07", "type": "chat" }, { - "id": "Qwen/Qwen3-Coder-30B-A3B-Instruct", - "name": "Qwen3 Coder 30B A3B Instruct", - "display_name": "Qwen3 Coder 30B A3B Instruct", + "id": "baidu/glm-5.2", + "name": "GLM-5.2 (Baidu)", + "display_name": "GLM-5.2 (Baidu)", "modalities": { "input": [ "text" @@ -8582,25 +8796,45 @@ ] }, "limit": { - "context": 262144, - "output": 65536 + "context": 1048576, + "output": 131072 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "effort_options": [ + "none", + "minimal", + "low", + "medium", + "high", + "xhigh", + "max" + ] + } }, "attachment": false, "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-07-31", - "last_updated": "2025-07-31", + "release_date": "2026-06-13", + "last_updated": "2026-06-13", "type": "chat" }, { - "id": "Qwen/Qwen3-30B-A3B-Instruct-2507", - "name": "Qwen3 30B A3B Instruct 2507", - "display_name": "Qwen3 30B A3B Instruct 2507", + "id": "minimax/minimax-m2.5", + "name": "MiniMax M2.5 (MiniMax)", + "display_name": "MiniMax M2.5 (MiniMax)", "modalities": { "input": [ "text" @@ -8610,25 +8844,37 @@ ] }, "limit": { - "context": 262144, - "output": 16384 + "context": 204800, + "output": 131100 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" + ] + } }, "attachment": false, "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-07-28", - "last_updated": "2025-07-28", + "release_date": "2026-02-12", + "last_updated": "2026-02-12", "type": "chat" }, { - "id": "Qwen/Qwen3-30B-A3B-Thinking-2507", - "name": "Qwen3 30B A3B Thinking 2507", - "display_name": "Qwen3 30B A3B Thinking 2507", + "id": "minimax/minimax-m2.5-highspeed", + "name": "MiniMax M2.5 Highspeed (MiniMax)", + "display_name": "MiniMax M2.5 Highspeed (MiniMax)", "modalities": { "input": [ "text" @@ -8638,8 +8884,8 @@ ] }, "limit": { - "context": 262144, - "output": 32768 + "context": 204800, + "output": 131100 }, "temperature": true, "tool_call": true, @@ -8650,25 +8896,25 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" ] } }, "attachment": false, "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-07-30", - "last_updated": "2025-07-30", + "release_date": "2026-02-13", + "last_updated": "2026-02-13", "type": "chat" }, { - "id": "Qwen/Qwen3-235B-A22B-Instruct-2507", - "name": "Qwen3 235B A22B Instruct 2507", - "display_name": "Qwen3 235B A22B Instruct 2507", + "id": "minimax/minimax-text-01", + "name": "MiniMax Text 01 (MiniMax)", + "display_name": "MiniMax Text 01 (MiniMax)", "modalities": { "input": [ "text" @@ -8678,25 +8924,25 @@ ] }, "limit": { - "context": 262144, + "context": 1000000, "output": 131072 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, "attachment": false, - "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-07-21", - "last_updated": "2025-07-21", + "open_weights": false, + "release_date": "2025-01-15", + "last_updated": "2025-01-15", "type": "chat" }, { - "id": "ZhipuAI/GLM-4.5", - "name": "GLM-4.5", - "display_name": "GLM-4.5", + "id": "minimax/minimax-m2.1-lightning", + "name": "MiniMax M2.1 Lightning (MiniMax)", + "display_name": "MiniMax M2.1 Lightning (MiniMax)", "modalities": { "input": [ "text" @@ -8706,8 +8952,8 @@ ] }, "limit": { - "context": 131072, - "output": 98304 + "context": 196608, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -8715,17 +8961,27 @@ "supported": true, "default": true }, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" + ] + } + }, "attachment": false, - "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-07-28", - "last_updated": "2025-07-28", + "open_weights": false, + "release_date": "2025-12-23", + "last_updated": "2025-12-23", "type": "chat" }, { - "id": "ZhipuAI/GLM-4.6", - "name": "GLM-4.6", - "display_name": "GLM-4.6", + "id": "minimax/minimax-m2.7-highspeed", + "name": "MiniMax M2.7 Highspeed (MiniMax)", + "display_name": "MiniMax M2.7 Highspeed (MiniMax)", "modalities": { "input": [ "text" @@ -8735,8 +8991,8 @@ ] }, "limit": { - "context": 202752, - "output": 98304 + "context": 204800, + "output": 131100 }, "temperature": true, "tool_call": true, @@ -8746,44 +9002,45 @@ }, "extra_capabilities": { "reasoning": { - "supported": true + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" + ] } }, "attachment": false, "open_weights": true, - "knowledge": "2025-07", - "release_date": "2025-09-30", - "last_updated": "2025-09-30", + "release_date": "2026-03-18", + "last_updated": "2026-03-18", "type": "chat" - } - ] - }, - "commandcode": { - "id": "commandcode", - "name": "CommandCode", - "display_name": "CommandCode", - "api": "https://api.commandcode.ai/provider/v1", - "doc": "https://commandcode.ai/docs/provider", - "models": [ + }, { - "id": "gpt-5.5", - "name": "GPT-5.5", - "display_name": "GPT-5.5", + "id": "minimax/minimax-m2", + "name": "MiniMax M2 (MiniMax)", + "display_name": "MiniMax M2 (MiniMax)", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 196608, + "output": 131072 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -8791,51 +9048,40 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", "effort_options": [ "low", "medium", "high", - "xhigh" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + "xhigh", + "max" + ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-12-01", - "release_date": "2026-04-23", - "last_updated": "2026-04-23", + "attachment": false, + "open_weights": true, + "release_date": "2025-10-27", + "last_updated": "2025-10-27", "type": "chat" }, { - "id": "gpt-6-astra", - "name": "GPT-6 Astra", - "display_name": "GPT-6 Astra", + "id": "minimax/minimax-m3", + "name": "MiniMax M3 (MiniMax)", + "display_name": "MiniMax M3 (MiniMax)", "modalities": { "input": [ "text", "image", - "pdf" + "video" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 512000, + "output": 131072 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -8845,6 +9091,8 @@ "reasoning": { "supported": true, "effort_options": [ + "none", + "minimal", "low", "medium", "high", @@ -8854,84 +9102,68 @@ } }, "attachment": true, - "open_weights": false, - "knowledge": "2026-04-30", - "release_date": "2026-09-04", - "last_updated": "2026-09-04", + "open_weights": true, + "release_date": "2026-06-01", + "last_updated": "2026-06-01", "type": "chat" }, { - "id": "gpt-5.4", - "name": "GPT-5.4", - "display_name": "GPT-5.4", + "id": "minimax/minimax-m2.1", + "name": "MiniMax M2.1 (MiniMax)", + "display_name": "MiniMax M2.1 (MiniMax)", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 196608, + "output": 131072 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "none", "effort_options": [ - "none", "low", "medium", "high", - "xhigh" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + "xhigh", + "max" + ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-03-05", - "last_updated": "2026-03-05", + "attachment": false, + "open_weights": true, + "release_date": "2025-12-23", + "last_updated": "2025-12-23", "type": "chat" }, { - "id": "claude-sonnet-5-5", - "name": "Claude Sonnet 5.5", - "display_name": "Claude Sonnet 5.5", + "id": "minimax/minimax-m2.7", + "name": "MiniMax M2.7 (MiniMax)", + "display_name": "MiniMax M2.7 (MiniMax)", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 204800, + "output": 131100 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -8939,6 +9171,13 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ "low", "medium", @@ -8948,32 +9187,31 @@ ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-06", - "release_date": "2026-09-28", - "last_updated": "2026-09-28", + "attachment": false, + "open_weights": true, + "release_date": "2026-03-18", + "last_updated": "2026-03-18", "type": "chat" }, { - "id": "claude-haiku-5-5", - "name": "Claude Haiku 5.5", - "display_name": "Claude Haiku 5.5", + "id": "minimax/minimax-m3.1-flash", + "name": "MiniMax M3.1 Flash (MiniMax)", + "display_name": "MiniMax M3.1 Flash (MiniMax)", "modalities": { "input": [ "text", "image", - "pdf" + "video" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 1048576, + "output": 524288 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -8992,30 +9230,28 @@ }, "attachment": true, "open_weights": false, - "knowledge": "2026-06", - "release_date": "2026-10-07", - "last_updated": "2026-10-07", + "release_date": "2026-10-19", + "last_updated": "2026-10-19", "type": "chat" }, { - "id": "claude-opus-5", - "name": "Claude Opus 5", - "display_name": "Claude Opus 5", + "id": "bytedance/seed-1-6-250915", + "name": "Seed 1.6 (250915) (ByteDance)", + "display_name": "Seed 1.6 (250915) (ByteDance)", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 256000, + "output": 256000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -9023,99 +9259,77 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, "effort_options": [ + "minimal", "low", "medium", - "high", - "xhigh", - "max" + "high" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-05", - "release_date": "2026-07-24", - "last_updated": "2026-07-24", + "release_date": "2025-09-15", + "last_updated": "2025-09-15", "type": "chat" }, { - "id": "claude-opus-4-7", - "name": "Claude Opus 4.7", - "display_name": "Claude Opus 4.7", + "id": "bytedance/seed-1-8-251228", + "name": "Seed 1.8 (251228) (ByteDance)", + "display_name": "Seed 1.8 (251228) (ByteDance)", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 256000, + "output": 256000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "high", "effort_options": [ + "minimal", "low", "medium", - "high", - "xhigh", - "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "omitted", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Claude Opus 4.7 and newer Opus models require thinking.type = \"adaptive\" to enable thinking explicitly.", - "Manual budget_tokens requests return 400 on Claude Opus 4.7 and newer adaptive-only Opus models.", - "task_budget is separate from thinking control and should not be treated as a thinking budget." + "high" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-01-31", - "release_date": "2026-04-16", - "last_updated": "2026-04-16", + "release_date": "2025-12-18", + "last_updated": "2025-12-18", "type": "chat" }, { - "id": "claude-opus-5-5", - "name": "Claude Opus 5.5", - "display_name": "Claude Opus 5.5", + "id": "bytedance/seed-2-0-pro-260328", + "name": "Seed 2.0 Pro (260328) (ByteDance)", + "display_name": "Seed 2.0 Pro (260328) (ByteDance)", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 262144, + "output": 131072 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -9124,40 +9338,37 @@ "extra_capabilities": { "reasoning": { "effort_options": [ + "minimal", "low", "medium", - "high", - "xhigh", - "max" + "high" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-06", - "release_date": "2026-09-22", - "last_updated": "2026-09-22", + "release_date": "2026-03-28", + "last_updated": "2026-03-28", "type": "chat" }, { - "id": "claude-sonnet-5", - "name": "Claude Sonnet 5", - "display_name": "Claude Sonnet 5", + "id": "bytedance/seed-2-0-code-preview-260328", + "name": "Seed 2.0 Code Preview (260328) (ByteDance)", + "display_name": "Seed 2.0 Code Preview (260328) (ByteDance)", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 262144, + "output": 131072 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -9165,42 +9376,38 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, "effort_options": [ + "minimal", "low", "medium", - "high", - "xhigh", - "max" + "high" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-01-31", - "release_date": "2026-06-30", - "last_updated": "2026-06-30", + "release_date": "2026-03-28", + "last_updated": "2026-03-28", "type": "chat" }, { - "id": "gpt-6-sol", - "name": "GPT-6 Sol", - "display_name": "GPT-6 Sol", + "id": "bytedance/seed-1-6-250615", + "name": "Seed 1.6 (250615) (ByteDance)", + "display_name": "Seed 1.6 (250615) (ByteDance)", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 256000, + "output": 256000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -9209,40 +9416,36 @@ "extra_capabilities": { "reasoning": { "effort_options": [ + "minimal", "low", "medium", - "high", - "xhigh", - "max" + "high" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-04-20", - "release_date": "2026-09-22", - "last_updated": "2026-09-22", + "release_date": "2025-06-25", + "last_updated": "2025-06-25", "type": "chat" }, { - "id": "gpt-5.6-sol", - "name": "GPT-5.6 Sol", - "display_name": "GPT-5.6 Sol", + "id": "bytedance/glm-4.7", + "name": "GLM-4.7 (ByteDance)", + "display_name": "GLM-4.7 (ByteDance)", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1050000, + "context": 200000, "output": 128000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -9251,70 +9454,202 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } + }, + "attachment": false, + "open_weights": true, + "knowledge": "2025-04", + "release_date": "2025-12-22", + "last_updated": "2025-12-22", + "type": "chat" + }, + { + "id": "bytedance/deepseek-v3.2", + "name": "DeepSeek V3.2 (ByteDance)", + "display_name": "DeepSeek V3.2 (ByteDance)", + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "output": 32768 + }, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true + } + }, + "attachment": false, + "open_weights": true, + "knowledge": "2024-07", + "release_date": "2025-12-01", + "last_updated": "2025-12-01", + "type": "chat" + }, + { + "id": "bytedance/seed-2-0-mini-260428", + "name": "Seed 2.0 Mini (260428) (ByteDance)", + "display_name": "Seed 2.0 Mini (260428) (ByteDance)", + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 262144, + "output": 131072 + }, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { "effort_options": [ - "none", + "minimal", "low", "medium", - "high", - "xhigh", - "max" - ], - "verbosity": "medium", - "verbosity_options": [ + "high" + ] + } + }, + "attachment": true, + "open_weights": false, + "release_date": "2026-04-28", + "last_updated": "2026-04-28", + "type": "chat" + }, + { + "id": "bytedance/seed-2-0-lite-260428", + "name": "Seed 2.0 Lite (260428) (ByteDance)", + "display_name": "Seed 2.0 Lite (260428) (ByteDance)", + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 262144, + "output": 131072 + }, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "minimal", "low", "medium", "high" - ], - "visibility": "hidden" + ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-02-16", - "release_date": "2026-07-09", - "last_updated": "2026-07-09", + "release_date": "2026-04-28", + "last_updated": "2026-04-28", "type": "chat" }, { - "id": "claude-sonnet-4-6", - "name": "Claude Sonnet 4.6", - "display_name": "Claude Sonnet 4.6", + "id": "bytedance/gpt-oss-120b", + "name": "GPT OSS 120B (ByteDance)", + "display_name": "GPT OSS 120B (ByteDance)", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 64000 + "context": 128000, + "output": 32000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "mixed", - "budget": { - "min": 1024, - "unit": "tokens" - }, - "effort": "high", "effort_options": [ "low", "medium", + "high" + ] + } + }, + "attachment": false, + "open_weights": true, + "knowledge": "2024-06", + "release_date": "2025-08-05", + "last_updated": "2025-08-05", + "type": "chat" + }, + { + "id": "bytedance/deepseek-v4-pro", + "name": "DeepSeek V4 Pro (ByteDance)", + "display_name": "DeepSeek V4 Pro (ByteDance)", + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1048576, + "output": 393216 + }, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "high", + "effort_options": [ + "low", "high", "max" ], @@ -9325,37 +9660,73 @@ "thinking_blocks" ], "notes": [ - "Anthropic recommends adaptive thinking with effort for Claude 4.6; budget_tokens remains a deprecated compatibility path.", - "Anthropic API defaults effort to high; lower effort levels should be chosen per workload." + "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." + ] + } + }, + "attachment": false, + "open_weights": true, + "knowledge": "2025-05", + "release_date": "2026-04-24", + "last_updated": "2026-04-24", + "type": "chat" + }, + { + "id": "bytedance/seed-1-6-flash-250715", + "name": "Seed 1.6 Flash (250715) (ByteDance)", + "display_name": "Seed 1.6 Flash (250715) (ByteDance)", + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 256000, + "output": 256000 + }, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "minimal", + "low", + "medium", + "high" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-02-17", - "last_updated": "2026-03-13", + "release_date": "2025-07-26", + "last_updated": "2025-07-26", "type": "chat" }, { - "id": "gpt-5.6-terra", - "name": "GPT-5.6 Terra", - "display_name": "GPT-5.6 Terra", + "id": "bytedance/deepseek-v4-flash", + "name": "DeepSeek V4 Flash (ByteDance)", + "display_name": "DeepSeek V4 Flash (ByteDance)", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 1048576, + "output": 393216 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -9366,50 +9737,47 @@ "supported": true, "default_enabled": true, "mode": "effort", - "effort": "medium", + "effort": "high", "effort_options": [ - "none", "low", - "medium", "high", - "xhigh", "max" ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ], - "visibility": "hidden" + "notes": [ + "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." + ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-02-16", - "release_date": "2026-07-09", - "last_updated": "2026-07-09", + "attachment": false, + "open_weights": true, + "knowledge": "2025-05", + "release_date": "2026-04-24", + "last_updated": "2026-04-24", "type": "chat" }, { - "id": "gpt-6-luna", - "name": "GPT-6 Luna", - "display_name": "GPT-6 Luna", + "id": "bytedance/glm-5.2", + "name": "GLM-5.2 (ByteDance)", + "display_name": "GLM-5.2 (ByteDance)", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1050000, + "context": 1024000, "output": 128000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -9417,7 +9785,16 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ + "none", + "minimal", "low", "medium", "high", @@ -9426,17 +9803,16 @@ ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-05-18", - "release_date": "2026-09-22", - "last_updated": "2026-09-22", + "attachment": false, + "open_weights": true, + "release_date": "2026-06-13", + "last_updated": "2026-06-13", "type": "chat" }, { - "id": "gpt-5.6-luna", - "name": "GPT-5.6 Luna", - "display_name": "GPT-5.6 Luna", + "id": "aws-mantle/gpt-5.6-terra", + "name": "GPT-5.6 Terra (AWS Mantle)", + "display_name": "GPT-5.6 Terra (AWS Mantle)", "modalities": { "input": [ "text", @@ -9448,7 +9824,7 @@ ] }, "limit": { - "context": 1050000, + "context": 921600, "output": 128000 }, "temperature": false, @@ -9488,61 +9864,52 @@ "type": "chat" }, { - "id": "gpt-5.4-mini", - "name": "GPT-5.4 mini", - "display_name": "GPT-5.4 mini", + "id": "aws-mantle/gpt-6-astra", + "name": "GPT-6 Astra (AWS Mantle)", + "display_name": "GPT-6 Astra (AWS Mantle)", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 400000, + "context": 1050000, "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "none", "effort_options": [ - "none", "low", "medium", "high", - "xhigh" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + "xhigh", + "max" + ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-03-17", - "last_updated": "2026-03-17", + "knowledge": "2026-04-30", + "release_date": "2026-09-04", + "last_updated": "2026-09-04", "type": "chat" }, { - "id": "gpt-5.3-codex", - "name": "GPT-5.3 Codex", - "display_name": "GPT-5.3 Codex", + "id": "aws-mantle/gpt-5.6-luna", + "name": "GPT-5.6 Luna (AWS Mantle)", + "display_name": "GPT-5.6 Luna (AWS Mantle)", "modalities": { "input": [ "text", @@ -9554,10 +9921,10 @@ ] }, "limit": { - "context": 400000, + "context": 921600, "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -9570,10 +9937,12 @@ "mode": "effort", "effort": "medium", "effort_options": [ + "none", "low", "medium", "high", - "xhigh" + "xhigh", + "max" ], "verbosity": "medium", "verbosity_options": [ @@ -9586,15 +9955,15 @@ }, "attachment": true, "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-02-05", - "last_updated": "2026-02-05", + "knowledge": "2026-02-16", + "release_date": "2026-07-09", + "last_updated": "2026-07-09", "type": "chat" }, { - "id": "claude-haiku-4-5-20251001", - "name": "Claude Haiku 4.5", - "display_name": "Claude Haiku 4.5", + "id": "aws-mantle/gpt-5.6-sol", + "name": "GPT-5.6 Sol (AWS Mantle)", + "display_name": "GPT-5.6 Sol (AWS Mantle)", "modalities": { "input": [ "text", @@ -9606,52 +9975,53 @@ ] }, "limit": { - "context": 200000, - "output": 64000 + "context": 921600, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "budget", - "budget": { - "min": 1024, - "unit": "tokens" - }, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "default_enabled": true, + "mode": "effort", + "effort": "medium", + "effort_options": [ + "none", + "low", + "medium", + "high", + "xhigh", + "max" ], - "notes": [ - "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", - "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." - ] + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, "attachment": true, "open_weights": false, - "knowledge": "2025-02-28", - "release_date": "2025-10-15", - "last_updated": "2025-10-15", + "knowledge": "2026-02-16", + "release_date": "2026-07-09", + "last_updated": "2026-07-09", "type": "chat" }, { - "id": "claude-opus-4-8", - "name": "Claude Opus 4.8", - "display_name": "Claude Opus 4.8", + "id": "luminal/deepseek-v4.1-flash", + "name": "DeepSeek V4.1 Flash (luminal)", + "display_name": "DeepSeek V4.1 Flash (luminal)", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" @@ -9659,51 +10029,39 @@ }, "limit": { "context": 1000000, - "output": 128000 + "output": 384000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "high", "effort_options": [ + "none", + "minimal", "low", "medium", "high", "xhigh", "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "omitted", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Claude Opus 4.7 and newer Opus models require thinking.type = \"adaptive\" to enable thinking explicitly.", - "Manual budget_tokens requests return 400 on Claude Opus 4.7 and newer adaptive-only Opus models.", - "task_budget is separate from thinking control and should not be treated as a thinking budget." ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2026-01", - "release_date": "2026-05-28", - "last_updated": "2026-05-28", + "open_weights": true, + "knowledge": "2025-05", + "release_date": "2026-09-10", + "last_updated": "2026-09-10", "type": "chat" }, { - "id": "claude-fable-5", - "name": "Claude Fable 5", - "display_name": "Claude Fable 5", + "id": "azure-anthropic/claude-opus-4-7", + "name": "Claude Opus 4.7 (Azure Anthropic)", + "display_name": "Claude Opus 4.7 (Azure Anthropic)", "modalities": { "input": [ "text", @@ -9722,12 +10080,12 @@ "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, + "default_enabled": false, "mode": "effort", "effort": "high", "effort_options": [ @@ -9744,23 +10102,23 @@ "thinking_blocks" ], "notes": [ - "Adaptive thinking is always on for Claude Fable 5 and Claude Mythos 5; thinking.type = \"disabled\" is rejected.", - "Manual budget_tokens requests return 400 on Claude Fable 5 and Claude Mythos 5.", - "thinking.display defaults to omitted; set display to summarized to receive readable thinking summaries." + "Claude Opus 4.7 and newer Opus models require thinking.type = \"adaptive\" to enable thinking explicitly.", + "Manual budget_tokens requests return 400 on Claude Opus 4.7 and newer adaptive-only Opus models.", + "task_budget is separate from thinking control and should not be treated as a thinking budget." ] } }, "attachment": true, "open_weights": false, "knowledge": "2026-01-31", - "release_date": "2026-06-09", - "last_updated": "2026-06-09", + "release_date": "2026-04-16", + "last_updated": "2026-04-16", "type": "chat" }, { - "id": "gpt-6.1-terra", - "name": "GPT-6.1 Terra", - "display_name": "GPT-6.1 Terra", + "id": "azure-anthropic/claude-sonnet-5", + "name": "Claude Sonnet 5 (Azure Anthropic)", + "display_name": "Claude Sonnet 5 (Azure Anthropic)", "modalities": { "input": [ "text", @@ -9772,7 +10130,7 @@ ] }, "limit": { - "context": 1050000, + "context": 1000000, "output": 128000 }, "temperature": false, @@ -9783,7 +10141,9 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, "effort_options": [ + "none", "low", "medium", "high", @@ -9794,15 +10154,15 @@ }, "attachment": true, "open_weights": false, - "knowledge": "2026-06-01", - "release_date": "2026-10-26", - "last_updated": "2026-10-26", + "knowledge": "2026-01-31", + "release_date": "2026-06-30", + "last_updated": "2026-06-30", "type": "chat" }, { - "id": "claude-fable-5-1", - "name": "Claude Fable 5.1", - "display_name": "Claude Fable 5.1", + "id": "azure-anthropic/claude-fable-5", + "name": "Claude Fable 5 (Azure Anthropic)", + "display_name": "Claude Fable 5 (Azure Anthropic)", "modalities": { "input": [ "text", @@ -9851,15 +10211,15 @@ }, "attachment": true, "open_weights": false, - "knowledge": "2026-06", - "release_date": "2026-09-01", - "last_updated": "2026-09-01", + "knowledge": "2026-01-31", + "release_date": "2026-06-09", + "last_updated": "2026-06-09", "type": "chat" }, { - "id": "gpt-6.1-sol", - "name": "GPT-6.1 Sol", - "display_name": "GPT-6.1 Sol", + "id": "azure-anthropic/claude-opus-5-5", + "name": "Claude Opus 5.5 (Azure Anthropic)", + "display_name": "Claude Opus 5.5 (Azure Anthropic)", "modalities": { "input": [ "text", @@ -9871,7 +10231,7 @@ ] }, "limit": { - "context": 1050000, + "context": 1000000, "output": 128000 }, "temperature": false, @@ -9893,18 +10253,20 @@ }, "attachment": true, "open_weights": false, - "knowledge": "2026-04-30", - "release_date": "2026-09-29", - "last_updated": "2026-09-29", + "knowledge": "2026-06", + "release_date": "2026-09-22", + "last_updated": "2026-09-22", "type": "chat" }, { - "id": "zai-org/GLM-5.2-Fast", - "name": "GLM-5.2-Fast", - "display_name": "GLM-5.2-Fast", + "id": "azure-anthropic/claude-sonnet-5-5", + "name": "Claude Sonnet 5.5 (Azure Anthropic)", + "display_name": "Claude Sonnet 5.5 (Azure Anthropic)", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" @@ -9912,9 +10274,9 @@ }, "limit": { "context": 1000000, - "output": 131072 + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -9923,24 +10285,30 @@ "extra_capabilities": { "reasoning": { "effort_options": [ + "low", + "medium", "high", + "xhigh", "max" ] } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-06-13", - "last_updated": "2026-06-13", + "attachment": true, + "open_weights": false, + "knowledge": "2026-06", + "release_date": "2026-09-28", + "last_updated": "2026-09-28", "type": "chat" }, { - "id": "zai-org/GLM-5.2", - "name": "GLM-5.2", - "display_name": "GLM-5.2", + "id": "azure-anthropic/claude-opus-4-8", + "name": "Claude Opus 4.8 (Azure Anthropic)", + "display_name": "Claude Opus 4.8 (Azure Anthropic)", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" @@ -9948,36 +10316,56 @@ }, "limit": { "context": 1000000, - "output": 131072 + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": false, + "mode": "effort", + "effort": "high", "effort_options": [ + "low", + "medium", "high", + "xhigh", "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "omitted", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Claude Opus 4.7 and newer Opus models require thinking.type = \"adaptive\" to enable thinking explicitly.", + "Manual budget_tokens requests return 400 on Claude Opus 4.7 and newer adaptive-only Opus models.", + "task_budget is separate from thinking control and should not be treated as a thinking budget." ] } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-06-13", - "last_updated": "2026-06-13", + "attachment": true, + "open_weights": false, + "knowledge": "2026-01", + "release_date": "2026-05-28", + "last_updated": "2026-05-28", "type": "chat" }, { - "id": "zai-org/GLM-5.3", - "name": "GLM-5.3", - "display_name": "GLM-5.3", + "id": "azure-anthropic/claude-opus-4-6", + "name": "Claude Opus 4.6 (Azure Anthropic)", + "display_name": "Claude Opus 4.6 (Azure Anthropic)", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" @@ -9985,40 +10373,58 @@ }, "limit": { "context": 1000000, - "output": 131072 + "output": 128000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": false, + "mode": "mixed", + "budget": { + "min": 1024, + "unit": "tokens" + }, + "effort": "high", "effort_options": [ "low", + "medium", "high", "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Anthropic recommends adaptive thinking with effort for Claude 4.6; budget_tokens remains a deprecated compatibility path.", + "Anthropic API defaults effort to high; lower effort levels should be chosen per workload." ] } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-08-14", - "last_updated": "2026-08-14", + "attachment": true, + "open_weights": false, + "knowledge": "2025-05-31", + "release_date": "2026-02-05", + "last_updated": "2026-03-13", "type": "chat" }, { - "id": "Qwen/Qwen3.8-Omni-Flash", - "name": "Qwen3.8 Omni Flash", - "display_name": "Qwen3.8 Omni Flash", + "id": "azure-anthropic/claude-fable-5-1", + "name": "Claude Fable 5.1 (Azure Anthropic)", + "display_name": "Claude Fable 5.1 (Azure Anthropic)", "modalities": { "input": [ "text", "image", - "audio", - "video" + "pdf" ], "output": [ "text" @@ -10026,9 +10432,9 @@ }, "limit": { "context": 1000000, - "output": 131072 + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -10036,28 +10442,45 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "high", "effort_options": [ "low", "medium", - "xhigh" + "high", + "xhigh", + "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "omitted", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Adaptive thinking is always on for Claude Fable 5 and Claude Mythos 5; thinking.type = \"disabled\" is rejected.", + "Manual budget_tokens requests return 400 on Claude Fable 5 and Claude Mythos 5.", + "thinking.display defaults to omitted; set display to summarized to receive readable thinking summaries." ] } }, "attachment": true, "open_weights": false, - "release_date": "2026-09-17", - "last_updated": "2026-09-17", + "knowledge": "2026-06", + "release_date": "2026-09-01", + "last_updated": "2026-09-01", "type": "chat" }, { - "id": "Qwen/Qwen3.8-Max", - "name": "Qwen3.8 Max", - "display_name": "Qwen3.8 Max", + "id": "azure-anthropic/claude-opus-5", + "name": "Claude Opus 5 (Azure Anthropic)", + "display_name": "Claude Opus 5 (Azure Anthropic)", "modalities": { "input": [ "text", "image", - "video", "pdf" ], "output": [ @@ -10066,9 +10489,9 @@ }, "limit": { "context": 1000000, - "output": 131072 + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -10078,22 +10501,26 @@ "reasoning": { "supported": true, "effort_options": [ + "none", "low", "medium", - "xhigh" + "high", + "xhigh", + "max" ] } }, "attachment": true, "open_weights": false, - "release_date": "2026-08-03", - "last_updated": "2026-08-03", + "knowledge": "2026-05", + "release_date": "2026-07-24", + "last_updated": "2026-07-24", "type": "chat" }, { - "id": "Qwen/Qwen3.8-27B", - "name": "Qwen3.8 27B", - "display_name": "Qwen3.8 27B", + "id": "tencent/kimi-k3", + "name": "Kimi K3 (Tencent Cloud)", + "display_name": "Kimi K3 (Tencent Cloud)", "modalities": { "input": [ "text", @@ -10105,10 +10532,10 @@ ] }, "limit": { - "context": 262144, - "output": 32768 + "context": 1048576, + "output": 1048576 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -10116,29 +10543,32 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, "effort_options": [ + "none", + "minimal", "low", "medium", - "xhigh" + "high", + "xhigh", + "max" ] } }, "attachment": true, "open_weights": true, - "release_date": "2026-08-14", - "last_updated": "2026-08-14", + "release_date": "2026-07-16", + "last_updated": "2026-07-16", "type": "chat" }, { - "id": "Qwen/Qwen3.8-Max-0902", - "name": "Qwen3.8 Max 0902", - "display_name": "Qwen3.8 Max 0902", + "id": "tencent/glm-5.3-flashx", + "name": "GLM-5.3 FlashX (Tencent Cloud)", + "display_name": "GLM-5.3 FlashX (Tencent Cloud)", "modalities": { "input": [ "text", - "image", - "video", - "pdf" + "image" ], "output": [ "text" @@ -10157,22 +10587,21 @@ "extra_capabilities": { "reasoning": { "effort_options": [ - "low", - "medium", - "xhigh" + "xhigh", + "max" ] } }, "attachment": true, "open_weights": false, - "release_date": "2026-09-02", - "last_updated": "2026-09-02", + "release_date": "2026-09-18", + "last_updated": "2026-09-18", "type": "chat" }, { - "id": "Qwen/Qwen3.8-Flash", - "name": "Qwen3.8 Flash", - "display_name": "Qwen3.8 Flash", + "id": "tencent/step-5-preview", + "name": "Step 5 Preview (Tencent Cloud)", + "display_name": "Step 5 Preview (Tencent Cloud)", "modalities": { "input": [ "text", @@ -10185,7 +10614,7 @@ }, "limit": { "context": 1000000, - "output": 131072 + "output": 65536 }, "temperature": true, "tool_call": true, @@ -10193,40 +10622,31 @@ "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "effort_options": [ - "low", - "medium", - "xhigh" - ] - } - }, "attachment": true, - "open_weights": false, - "release_date": "2026-08-26", - "last_updated": "2026-08-26", + "open_weights": true, + "release_date": "2026-09-16", + "last_updated": "2026-09-20", "type": "chat" }, { - "id": "xai/grok-4.5", - "name": "Grok 4.5", - "display_name": "Grok 4.5", + "id": "tencent/kimi-k2.7-code", + "name": "Kimi K2.7 Code (Tencent Cloud)", + "display_name": "Kimi K2.7 Code (Tencent Cloud)", "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 500000, - "output": 500000 + "context": 262144, + "output": 262144 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -10235,36 +10655,47 @@ "extra_capabilities": { "reasoning": { "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ + "minimal", "low", "medium", - "high" + "high", + "xhigh", + "max" ] } }, "attachment": true, - "open_weights": false, - "release_date": "2026-07-08", - "last_updated": "2026-07-08", + "open_weights": true, + "knowledge": "2025-01", + "release_date": "2026-06-12", + "last_updated": "2026-06-12", "type": "chat" }, { - "id": "xai/grok-4.7", - "name": "Grok 4.7", - "display_name": "Grok 4.7", + "id": "tencent/mimo-v2.6-pro", + "name": "MiMo V2.6 Pro (Tencent Cloud)", + "display_name": "MiMo V2.6 Pro (Tencent Cloud)", "modalities": { "input": [ "text", "image", - "pdf" + "audio", + "video" ], "output": [ "text" ] }, "limit": { - "context": 500000, - "output": 500000 + "context": 1000000, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -10275,24 +10706,24 @@ "extra_capabilities": { "reasoning": { "effort_options": [ + "none", "low", "medium", "high", - "xhigh" + "max" ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2026-05", - "release_date": "2026-09-21", - "last_updated": "2026-09-21", + "open_weights": true, + "release_date": "2026-09-22", + "last_updated": "2026-09-22", "type": "chat" }, { - "id": "xai/grok-4.6", - "name": "Grok 4.6", - "display_name": "Grok 4.6", + "id": "tencent/deepseek-v4.1-flash-1017", + "name": "DeepSeek V4.1 Flash 1017 (Tencent Cloud)", + "display_name": "DeepSeek V4.1 Flash 1017 (Tencent Cloud)", "modalities": { "input": [ "text", @@ -10303,8 +10734,8 @@ ] }, "limit": { - "context": 500000, - "output": 500000 + "context": 1000000, + "output": 393216 }, "temperature": true, "tool_call": true, @@ -10314,38 +10745,41 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, "effort_options": [ + "none", + "minimal", "low", "medium", "high", - "xhigh" + "xhigh", + "max" ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2026-02-01", - "release_date": "2026-08-12", - "last_updated": "2026-08-12", + "open_weights": true, + "knowledge": "2025-05", + "release_date": "2026-10-17", + "last_updated": "2026-10-17", "type": "chat" }, { - "id": "sakana/fugu-ultra", - "name": "Fugu Ultra", - "display_name": "Fugu Ultra", + "id": "tencent/kimi-k2.7-code-highspeed", + "name": "Kimi K2.7 Code Highspeed (Tencent Cloud)", + "display_name": "Kimi K2.7 Code Highspeed (Tencent Cloud)", "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 262144, + "output": 262144 }, "temperature": false, "tool_call": true, @@ -10355,25 +10789,40 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ + "minimal", + "low", + "medium", "high", - "xhigh" + "xhigh", + "max" ] } }, "attachment": true, - "open_weights": false, - "release_date": "2026-06-15", - "last_updated": "2026-06-15", + "open_weights": true, + "knowledge": "2025-01", + "release_date": "2026-06-12", + "last_updated": "2026-06-12", "type": "chat" }, { - "id": "nvidia/nemotron-3-ultra-550b-a55b", - "name": "Nemotron 3 Ultra 550B A55B", - "display_name": "Nemotron 3 Ultra 550B A55B", + "id": "tencent/glm-5.3-flash", + "name": "GLM-5.3 Flash (Tencent Cloud)", + "display_name": "GLM-5.3 Flash (Tencent Cloud)", "modalities": { "input": [ - "text" + "text", + "image", + "video", + "pdf" ], "output": [ "text" @@ -10381,7 +10830,7 @@ }, "limit": { "context": 1000000, - "output": 128000 + "output": 131072 }, "temperature": true, "tool_call": true, @@ -10391,26 +10840,29 @@ }, "extra_capabilities": { "reasoning": { - "supported": true + "supported": true, + "effort_options": [ + "xhigh", + "max" + ] } }, - "attachment": false, + "attachment": true, "open_weights": true, - "knowledge": "2025-09", - "release_date": "2026-06-04", - "last_updated": "2026-06-04", + "release_date": "2026-08-26", + "last_updated": "2026-08-26", "type": "chat" }, { - "id": "z-ai/glm-5.3-flash", - "name": "GLM-5.3-Flash", - "display_name": "GLM-5.3-Flash", + "id": "tencent/mimo-v2.6-flash", + "name": "MiMo V2.6 Flash (Tencent Cloud)", + "display_name": "MiMo V2.6 Flash (Tencent Cloud)", "modalities": { "input": [ "text", "image", - "video", - "pdf" + "audio", + "video" ], "output": [ "text" @@ -10428,9 +10880,10 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, "effort_options": [ + "none", "low", + "medium", "high", "max" ] @@ -10438,20 +10891,59 @@ }, "attachment": true, "open_weights": true, - "release_date": "2026-08-26", - "last_updated": "2026-08-26", + "release_date": "2026-09-22", + "last_updated": "2026-09-22", "type": "chat" }, { - "id": "z-ai/glm-5.3-flashx", - "name": "GLM-5.3-FlashX", - "display_name": "GLM-5.3-FlashX", + "id": "tencent/hy3", + "name": "Hy3 (Tencent Cloud)", + "display_name": "Hy3 (Tencent Cloud)", + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 262144, + "output": 131072 + }, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "effort_options": [ + "none", + "minimal", + "low", + "medium", + "high", + "xhigh" + ] + } + }, + "attachment": false, + "open_weights": true, + "release_date": "2026-07-06", + "last_updated": "2026-07-06", + "type": "chat" + }, + { + "id": "tencent/deepseek-v4.1-flash", + "name": "DeepSeek V4.1 Flash (Tencent Cloud)", + "display_name": "DeepSeek V4.1 Flash (Tencent Cloud)", "modalities": { "input": [ "text", - "image", - "video", - "pdf" + "image" ], "output": [ "text" @@ -10459,7 +10951,7 @@ }, "limit": { "context": 1000000, - "output": 131072 + "output": 393216 }, "temperature": true, "tool_call": true, @@ -10469,23 +10961,29 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, "effort_options": [ + "none", + "minimal", "low", + "medium", "high", + "xhigh", "max" ] } }, "attachment": true, "open_weights": true, - "release_date": "2026-08-26", - "last_updated": "2026-08-26", + "knowledge": "2025-05", + "release_date": "2026-09-10", + "last_updated": "2026-09-10", "type": "chat" }, { - "id": "inclusionai/ling-3.1-flash:free", - "name": "Ling 3.1 Flash", - "display_name": "Ling 3.1 Flash", + "id": "tencent/hy4-preview", + "name": "Hy4 Preview (Tencent Cloud)", + "display_name": "Hy4 Preview (Tencent Cloud)", "modalities": { "input": [ "text" @@ -10495,8 +10993,8 @@ ] }, "limit": { - "context": 262144, - "output": 32768 + "context": 1000000, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -10506,23 +11004,55 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, "effort_options": [ + "none", + "minimal", "low", "medium", - "high" + "high", + "xhigh", + "max" ] } }, "attachment": false, "open_weights": true, - "release_date": "2026-09-29", - "last_updated": "2026-09-29", + "release_date": "2026-08-28", + "last_updated": "2026-08-28", "type": "chat" }, { - "id": "deepseek/deepseek-v4-flash", - "name": "DeepSeek V4 Flash", - "display_name": "DeepSeek V4 Flash", + "id": "tencent/hy-mt2-plus", + "name": "Hy-MT2 Plus (Tencent Cloud)", + "display_name": "Hy-MT2 Plus (Tencent Cloud)", + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 8192, + "output": 4096 + }, + "temperature": true, + "tool_call": false, + "reasoning": { + "supported": false + }, + "attachment": false, + "open_weights": false, + "release_date": "2026-05-21", + "last_updated": "2026-05-21", + "type": "chat" + }, + { + "id": "tencent/deepseek-v4-pro", + "name": "DeepSeek V4 Pro (Tencent Cloud)", + "display_name": "DeepSeek V4 Pro (Tencent Cloud)", "modalities": { "input": [ "text" @@ -10533,7 +11063,7 @@ }, "limit": { "context": 1000000, - "output": 384000 + "output": 393216 }, "temperature": true, "tool_call": true, @@ -10571,9 +11101,9 @@ "type": "chat" }, { - "id": "deepseek/deepseek-v4-pro", - "name": "DeepSeek V4 Pro", - "display_name": "DeepSeek V4 Pro", + "id": "tencent/glm-5.3", + "name": "GLM-5.3 (Tencent Cloud)", + "display_name": "GLM-5.3 (Tencent Cloud)", "modalities": { "input": [ "text" @@ -10584,7 +11114,7 @@ }, "limit": { "context": 1000000, - "output": 384000 + "output": 128000 }, "temperature": true, "tool_call": true, @@ -10595,48 +11125,36 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "high", "effort_options": [ "low", + "medium", "high", + "xhigh", "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." ] } }, "attachment": false, "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-04-24", - "last_updated": "2026-04-24", + "release_date": "2026-08-14", + "last_updated": "2026-08-14", "type": "chat" }, { - "id": "deepseek/deepseek-v4.1-flash-fast", - "name": "DeepSeek V4.1 Flash Fast", - "display_name": "DeepSeek V4.1 Flash Fast", + "id": "tencent/glm-5", + "name": "GLM-5 (Tencent Cloud)", + "display_name": "GLM-5 (Tencent Cloud)", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 384000 + "context": 200000, + "output": 128000 }, "temperature": true, "tool_call": true, @@ -10646,28 +11164,37 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ + "none", + "minimal", "low", + "medium", "high", + "xhigh", "max" ] } }, - "attachment": true, + "attachment": false, "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-09-10", - "last_updated": "2026-09-10", + "release_date": "2026-02-12", + "last_updated": "2026-02-12", "type": "chat" }, { - "id": "deepseek/deepseek-v4-flash-vision-exp", - "name": "DeepSeek V4 Flash Vision Exp", - "display_name": "DeepSeek V4 Flash Vision Exp", + "id": "tencent/deepseek-v4-flash", + "name": "DeepSeek V4 Flash (Tencent Cloud)", + "display_name": "DeepSeek V4 Flash (Tencent Cloud)", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" @@ -10675,7 +11202,7 @@ }, "limit": { "context": 1000000, - "output": 384000 + "output": 393216 }, "temperature": true, "tool_call": true, @@ -10705,16 +11232,17 @@ ] } }, - "attachment": true, + "attachment": false, "open_weights": true, - "release_date": "2026-08-21", - "last_updated": "2026-09-01", + "knowledge": "2025-05", + "release_date": "2026-04-24", + "last_updated": "2026-04-24", "type": "chat" }, { - "id": "deepseek/deepseek-v4-flash-fast", - "name": "DeepSeek V4 Flash Fast", - "display_name": "DeepSeek V4 Flash Fast", + "id": "tencent/glm-5.1", + "name": "GLM-5.1 (Tencent Cloud)", + "display_name": "GLM-5.1 (Tencent Cloud)", "modalities": { "input": [ "text" @@ -10724,8 +11252,8 @@ ] }, "limit": { - "context": 1000000, - "output": 384000 + "context": 200000, + "output": 128000 }, "temperature": true, "tool_call": true, @@ -10735,28 +11263,77 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ "low", + "medium", "high", + "xhigh", "max" ] } }, "attachment": false, "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-04-24", - "last_updated": "2026-04-24", + "release_date": "2026-04-07", + "last_updated": "2026-04-07", "type": "chat" }, { - "id": "deepseek/deepseek-v4.1-flash", - "name": "DeepSeek V4.1 Flash", - "display_name": "DeepSeek V4.1 Flash", + "id": "tencent/glm-5v-turbo", + "name": "GLM-5V Turbo (Tencent Cloud)", + "display_name": "GLM-5V Turbo (Tencent Cloud)", "modalities": { "input": [ "text", - "image" + "image", + "video", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 200000, + "output": 128000 + }, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" + ] + } + }, + "attachment": true, + "open_weights": false, + "release_date": "2026-04-01", + "last_updated": "2026-04-01", + "type": "chat" + }, + { + "id": "tencent/glm-5.2", + "name": "GLM-5.2 (Tencent Cloud)", + "display_name": "GLM-5.2 (Tencent Cloud)", + "modalities": { + "input": [ + "text" ], "output": [ "text" @@ -10764,7 +11341,7 @@ }, "limit": { "context": 1000000, - "output": 384000 + "output": 128000 }, "temperature": true, "tool_call": true, @@ -10775,31 +11352,38 @@ "extra_capabilities": { "reasoning": { "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ + "none", + "minimal", "low", + "medium", "high", + "xhigh", "max" ] } }, - "attachment": true, + "attachment": false, "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-09-10", - "last_updated": "2026-09-10", + "release_date": "2026-06-13", + "last_updated": "2026-06-13", "type": "chat" }, { - "id": "google/gemini-3.5-flash-lite", - "name": "Gemini 3.5 Flash Lite", - "display_name": "Gemini 3.5 Flash Lite", + "id": "tencent/kimi-k2.8-preview", + "name": "Kimi K2.8 Preview (Tencent Cloud)", + "display_name": "Kimi K2.8 Preview (Tencent Cloud)", "modalities": { "input": [ "text", "image", - "video", - "audio", - "pdf" + "video" ], "output": [ "text" @@ -10807,7 +11391,48 @@ }, "limit": { "context": 1000000, - "output": 65536 + "output": 1000000 + }, + "temperature": false, + "tool_call": true, + "reasoning": { + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "none", + "minimal", + "low", + "medium", + "high", + "xhigh", + "max" + ] + } + }, + "attachment": true, + "open_weights": false, + "release_date": "2026-09-11", + "last_updated": "2026-09-11", + "type": "chat" + }, + { + "id": "tencent/glm-5-turbo", + "name": "GLM-5 Turbo (Tencent Cloud)", + "display_name": "GLM-5 Turbo (Tencent Cloud)", + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 200000, + "output": 128000 }, "temperature": true, "tool_call": true, @@ -10821,28 +11446,27 @@ "effort_options": [ "low", "medium", - "high" + "high", + "xhigh", + "max" ] } }, - "attachment": true, + "attachment": false, "open_weights": false, - "knowledge": "2026-03", - "release_date": "2026-07-21", - "last_updated": "2026-07-21", + "release_date": "2026-03-16", + "last_updated": "2026-03-16", "type": "chat" }, { - "id": "google/gemini-3.1-flash-lite", - "name": "Gemini 3.1 Flash Lite", - "display_name": "Gemini 3.1 Flash Lite", + "id": "tencent/minimax-m3", + "name": "MiniMax M3 (Tencent Cloud)", + "display_name": "MiniMax M3 (Tencent Cloud)", "modalities": { "input": [ "text", "image", - "video", - "audio", - "pdf" + "video" ], "output": [ "text" @@ -10850,7 +11474,7 @@ }, "limit": { "context": 1000000, - "output": 65536 + "output": 131072 }, "temperature": true, "tool_call": true, @@ -10864,36 +11488,33 @@ "effort_options": [ "low", "medium", - "high" + "high", + "xhigh", + "max" ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2025-01", - "release_date": "2026-05-07", - "last_updated": "2026-05-07", + "open_weights": true, + "release_date": "2026-06-01", + "last_updated": "2026-06-01", "type": "chat" }, { - "id": "google/gemini-3.7-flash", - "name": "Gemini 3.7 Flash", - "display_name": "Gemini 3.7 Flash", + "id": "tencent/minimax-m2.7", + "name": "MiniMax M2.7 (Tencent Cloud)", + "display_name": "MiniMax M2.7 (Tencent Cloud)", "modalities": { "input": [ - "text", - "image", - "video", - "audio", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 200000, + "output": 128000 }, "temperature": true, "tool_call": true, @@ -10904,33 +11525,82 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "level", - "level": "high", - "level_options": [ - "minimal", + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "effort_options": [ "low", "medium", - "high" - ], + "high", + "xhigh", + "max" + ] + } + }, + "attachment": false, + "open_weights": true, + "release_date": "2026-03-18", + "last_updated": "2026-03-18", + "type": "chat" + }, + { + "id": "tencent/kimi-k2.6", + "name": "Kimi K2.6 (Tencent Cloud)", + "display_name": "Kimi K2.6 (Tencent Cloud)", + "modalities": { + "input": [ + "text", + "image", + "video" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 262144, + "output": 262144 + }, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ - "thought_signatures" + "thinking_blocks" + ], + "effort_options": [ + "none", + "minimal", + "low", + "medium", + "high", + "xhigh", + "max" ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2026-03", - "release_date": "2026-08-13", - "last_updated": "2026-08-13", + "open_weights": true, + "knowledge": "2025-01", + "release_date": "2026-04-20", + "last_updated": "2026-04-20", "type": "chat" }, { - "id": "google/gemini-3.8-flash", - "name": "Gemini 3.8 Flash", - "display_name": "Gemini 3.8 Flash", + "id": "google-vertex/gemini-3.5-flash", + "name": "Gemini 3.5 Flash (Google Vertex AI)", + "display_name": "Gemini 3.5 Flash (Google Vertex AI)", "modalities": { "input": [ "text", @@ -10944,7 +11614,7 @@ ] }, "limit": { - "context": 1000000, + "context": 1048576, "output": 65536 }, "temperature": true, @@ -10974,14 +11644,15 @@ }, "attachment": true, "open_weights": false, - "release_date": "2026-09-02", - "last_updated": "2026-09-02", + "knowledge": "2025-01", + "release_date": "2026-05-19", + "last_updated": "2026-05-19", "type": "chat" }, { - "id": "google/gemini-4-argon", - "name": "Gemini 4 Argon", - "display_name": "Gemini 4 Argon", + "id": "google-vertex/gemini-4-argon", + "name": "Gemini 4 Argon (Google Vertex AI)", + "display_name": "Gemini 4 Argon (Google Vertex AI)", "modalities": { "input": [ "text", @@ -10995,8 +11666,8 @@ ] }, "limit": { - "context": 1000000, - "output": 65536 + "context": 1048576, + "output": 1048576 }, "temperature": false, "tool_call": true, @@ -11007,8 +11678,6 @@ "extra_capabilities": { "reasoning": { "effort_options": [ - "low", - "medium", "high" ] } @@ -11021,9 +11690,9 @@ "type": "chat" }, { - "id": "google/gemini-3.6-flash", - "name": "Gemini 3.6 Flash", - "display_name": "Gemini 3.6 Flash", + "id": "google-vertex/gemini-3.6-flash", + "name": "Gemini 3.6 Flash (Google Vertex AI)", + "display_name": "Gemini 3.6 Flash (Google Vertex AI)", "modalities": { "input": [ "text", @@ -11037,7 +11706,7 @@ ] }, "limit": { - "context": 1000000, + "context": 1048576, "output": 65536 }, "temperature": true, @@ -11073,9 +11742,9 @@ "type": "chat" }, { - "id": "google/gemini-3.5-flash", - "name": "Gemini 3.5 Flash", - "display_name": "Gemini 3.5 Flash", + "id": "google-vertex/gemini-3.8-flash", + "name": "Gemini 3.8 Flash (Google Vertex AI)", + "display_name": "Gemini 3.8 Flash (Google Vertex AI)", "modalities": { "input": [ "text", @@ -11089,7 +11758,7 @@ ] }, "limit": { - "context": 1000000, + "context": 1048576, "output": 65536 }, "temperature": true, @@ -11119,28 +11788,29 @@ }, "attachment": true, "open_weights": false, - "knowledge": "2025-01", - "release_date": "2026-05-19", - "last_updated": "2026-05-19", + "release_date": "2026-09-02", + "last_updated": "2026-09-02", "type": "chat" }, { - "id": "thinkingmachines/inkling", - "name": "Inkling", - "display_name": "Inkling", + "id": "google-vertex/gemini-3.1-pro-preview", + "name": "Gemini 3.1 Pro (Preview) (Google Vertex AI)", + "display_name": "Gemini 3.1 Pro (Preview) (Google Vertex AI)", "modalities": { "input": [ "text", "image", - "audio" + "video", + "audio", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 256000, - "output": 256000 + "context": 1048576, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -11150,37 +11820,47 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ + "supported": true, + "default_enabled": true, + "mode": "level", + "level": "high", + "level_options": [ "low", - "medium", - "high", - "max" + "high" + ], + "summaries": true, + "visibility": "summary", + "continuation": [ + "thought_signatures" ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-07-15", - "last_updated": "2026-07-15", + "open_weights": false, + "knowledge": "2025-01", + "release_date": "2026-02-19", + "last_updated": "2026-02-19", "type": "chat" }, { - "id": "thinkingmachines/inkling-small", - "name": "Inkling Small", - "display_name": "Inkling Small", + "id": "google-vertex/gemini-3.7-flash", + "name": "Gemini 3.7 Flash (Google Vertex AI)", + "display_name": "Gemini 3.7 Flash (Google Vertex AI)", "modalities": { "input": [ "text", "image", - "audio" + "video", + "audio", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 1000000 + "context": 1048576, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -11190,35 +11870,49 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ + "supported": true, + "default_enabled": true, + "mode": "level", + "level": "high", + "level_options": [ + "minimal", "low", "medium", - "high", - "max" + "high" + ], + "summaries": true, + "visibility": "summary", + "continuation": [ + "thought_signatures" ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-07-30", - "last_updated": "2026-07-30", + "open_weights": false, + "knowledge": "2026-03", + "release_date": "2026-08-13", + "last_updated": "2026-08-13", "type": "chat" }, { - "id": "tencent/hy4-preview", - "name": "Hy4 preview", - "display_name": "Hy4 preview", + "id": "google-vertex/gemini-3.1-flash-lite", + "name": "Gemini 3.1 Flash Lite (Google Vertex AI)", + "display_name": "Gemini 3.1 Flash Lite (Google Vertex AI)", "modalities": { "input": [ - "text" + "text", + "image", + "video", + "audio", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1024000, - "output": 64000 + "context": 1048576, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -11230,31 +11924,39 @@ "reasoning": { "supported": true, "effort_options": [ + "minimal", + "low", + "medium", "high" ] } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-08-28", - "last_updated": "2026-08-28", + "attachment": true, + "open_weights": false, + "knowledge": "2025-01", + "release_date": "2026-05-07", + "last_updated": "2026-05-07", "type": "chat" }, { - "id": "tencent/hy3-paid", - "name": "Hy3", - "display_name": "Hy3", + "id": "google-vertex/gemini-3-flash-preview", + "name": "Gemini 3 Flash (Preview) (Google Vertex AI)", + "display_name": "Gemini 3 Flash (Preview) (Google Vertex AI)", "modalities": { "input": [ - "text" + "text", + "image", + "video", + "audio", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 256000, - "output": 128000 + "context": 1048576, + "output": 65535 }, "temperature": true, "tool_call": true, @@ -11264,35 +11966,49 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ + "supported": true, + "default_enabled": true, + "mode": "level", + "level": "high", + "level_options": [ + "minimal", "low", + "medium", "high" + ], + "summaries": true, + "visibility": "summary", + "continuation": [ + "thought_signatures" ] } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-07-06", - "last_updated": "2026-07-06", + "attachment": true, + "open_weights": false, + "knowledge": "2025-01", + "release_date": "2025-12-17", + "last_updated": "2025-12-17", "type": "chat" }, { - "id": "MiniMaxAI/MiniMax-M3", - "name": "MiniMax-M3", - "display_name": "MiniMax-M3", + "id": "google-vertex/gemini-3.5-flash-lite", + "name": "Gemini 3.5 Flash Lite (Google Vertex AI)", + "display_name": "Gemini 3.5 Flash Lite (Google Vertex AI)", "modalities": { "input": [ "text", "image", - "video" + "video", + "audio", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 512000 + "context": 1048576, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -11302,19 +12018,26 @@ }, "extra_capabilities": { "reasoning": { - "supported": true + "supported": true, + "effort_options": [ + "minimal", + "low", + "medium", + "high" + ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-06-01", - "last_updated": "2026-06-01", + "open_weights": false, + "knowledge": "2026-03", + "release_date": "2026-07-21", + "last_updated": "2026-07-21", "type": "chat" }, { - "id": "MiniMaxAI/MiniMax-M2.7", - "name": "MiniMax-M2.7", - "display_name": "MiniMax-M2.7", + "id": "ranoai/deepseek-v4-flash", + "name": "DeepSeek V4 Flash (RanoAI)", + "display_name": "DeepSeek V4 Flash (RanoAI)", "modalities": { "input": [ "text" @@ -11324,8 +12047,8 @@ ] }, "limit": { - "context": 200000, - "output": 131072 + "context": 1000000, + "output": 393216 }, "temperature": true, "tool_call": true, @@ -11336,37 +12059,51 @@ "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "high", + "effort_options": [ + "low", + "high", + "max" + ], "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ "thinking_blocks" + ], + "notes": [ + "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." ] } }, "attachment": false, "open_weights": true, - "release_date": "2026-03-18", - "last_updated": "2026-03-18", + "knowledge": "2025-05", + "release_date": "2026-04-24", + "last_updated": "2026-04-24", "type": "chat" }, { - "id": "MiniMaxAI/MiniMax-M2.5", - "name": "MiniMax-M2.5", - "display_name": "MiniMax-M2.5", + "id": "scx-ai-gp/kimi-k3", + "name": "Kimi K3 (SCX.ai)", + "display_name": "Kimi K3 (SCX.ai)", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 131072 + "context": 1048576, + "output": 1048576 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -11374,33 +12111,43 @@ }, "extra_capabilities": { "reasoning": { - "supported": true + "supported": true, + "effort_options": [ + "none", + "minimal", + "low", + "medium", + "high", + "xhigh", + "max" + ] } }, - "attachment": false, + "attachment": true, "open_weights": true, - "release_date": "2026-02-12", - "last_updated": "2026-02-12", + "release_date": "2026-07-16", + "last_updated": "2026-07-16", "type": "chat" }, { - "id": "mistral/mistral-large-4", - "name": "Mistral Large 4", - "display_name": "Mistral Large 4", + "id": "scx-ai-gp/kimi-k2.7-code", + "name": "Kimi K2.7 Code (SCX.ai)", + "display_name": "Kimi K2.7 Code (SCX.ai)", "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 524288, + "context": 262144, "output": 262144 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -11408,36 +12155,50 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ - "high" + "none", + "minimal", + "low", + "medium", + "high", + "xhigh" ] } }, "attachment": true, - "open_weights": false, - "release_date": "2026-10-06", - "last_updated": "2026-10-06", + "open_weights": true, + "knowledge": "2025-01", + "release_date": "2026-06-12", + "last_updated": "2026-06-12", "type": "chat" }, { - "id": "moonshotai/Kimi-K2.7-Code", - "name": "Kimi K2.7 Code", - "display_name": "Kimi K2.7 Code", + "id": "scx-ai-gp/glm-5.3-flash", + "name": "GLM-5.3 Flash (SCX.ai)", + "display_name": "GLM-5.3 Flash (SCX.ai)", "modalities": { "input": [ "text", "image", - "video" + "video", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 256000, - "output": 256000 + "context": 1048576, + "output": 131072 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -11445,61 +12206,60 @@ }, "extra_capabilities": { "reasoning": { - "supported": true + "supported": true, + "effort_options": [ + "low", + "high", + "max" + ] } }, "attachment": true, "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-06-12", - "last_updated": "2026-06-12", + "release_date": "2026-08-26", + "last_updated": "2026-08-26", "type": "chat" }, { - "id": "moonshotai/Kimi-K2.7-Code-Highspeed", - "name": "Kimi K2.7 Code Highspeed", - "display_name": "Kimi K2.7 Code Highspeed", + "id": "scx-ai-gp/deepseek-v4.1-flash", + "name": "DeepSeek V4.1 Flash (SCX.ai)", + "display_name": "DeepSeek V4.1 Flash (SCX.ai)", "modalities": { "input": [ - "text", - "image", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 262000, - "output": 262000 + "context": 1000000, + "output": 384000 }, - "temperature": false, - "tool_call": true, + "temperature": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true + "supported": true }, "extra_capabilities": { "reasoning": { "supported": true } }, - "attachment": true, + "attachment": false, "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-06-12", - "last_updated": "2026-06-12", + "knowledge": "2025-05", + "release_date": "2026-09-10", + "last_updated": "2026-09-10", "type": "chat" }, { - "id": "moonshotai/Kimi-K3", - "name": "Kimi K3", - "display_name": "Kimi K3", + "id": "scx-ai-gp/glm-5.3", + "name": "GLM-5.3 (SCX.ai)", + "display_name": "GLM-5.3 (SCX.ai)", "modalities": { "input": [ - "text", - "image", - "video" + "text" ], "output": [ "text" @@ -11507,9 +12267,9 @@ }, "limit": { "context": 1000000, - "output": 131072 + "output": 128000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -11525,72 +12285,53 @@ ] } }, - "attachment": true, + "attachment": false, "open_weights": true, - "release_date": "2026-07-16", - "last_updated": "2026-07-16", + "release_date": "2026-08-14", + "last_updated": "2026-08-14", "type": "chat" }, { - "id": "meta/muse-spark-1.3-contributor", - "name": "Muse Spark 1.3 (Contributor)", - "display_name": "Muse Spark 1.3 (Contributor)", + "id": "scx-ai-gp/glm-5.3-fast", + "name": "GLM 5.3 Fast (SCX.ai)", + "display_name": "GLM 5.3 Fast (SCX.ai)", "modalities": { "input": [ - "text", - "image", - "video", - "pdf", - "audio" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 1000000, + "output": 128000 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true + "supported": false }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "low", - "medium", - "high", - "xhigh" - ] - } - }, - "attachment": true, - "open_weights": false, - "release_date": "2026-09-02", - "last_updated": "2026-09-02", + "attachment": false, + "open_weights": true, + "release_date": "2026-08-14", + "last_updated": "2026-08-14", "type": "chat" }, { - "id": "meta/muse-wonder-1.0", - "name": "Muse Wonder", - "display_name": "Muse Wonder", + "id": "scx-ai-gp/glm-5.2", + "name": "GLM-5.2 (SCX.ai)", + "display_name": "GLM-5.2 (SCX.ai)", "modalities": { "input": [ - "text", - "image", - "video", - "pdf", - "audio" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, + "context": 1000000, "output": 131072 }, "temperature": true, @@ -11601,7 +12342,16 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ + "none", + "minimal", "low", "medium", "high", @@ -11610,30 +12360,29 @@ ] } }, - "attachment": true, - "open_weights": false, - "release_date": "2026-10-12", - "last_updated": "2026-10-12", + "attachment": false, + "open_weights": true, + "release_date": "2026-06-13", + "last_updated": "2026-06-13", "type": "chat" }, { - "id": "meta/muse-spark-1.3", - "name": "Muse Spark 1.3", - "display_name": "Muse Spark 1.3", + "id": "scx-ai-gp/qwen3.8-max", + "name": "Qwen3.8 Max (SCX.ai)", + "display_name": "Qwen3.8 Max (SCX.ai)", "modalities": { "input": [ "text", "image", "video", - "pdf", - "audio" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1048576, + "context": 1000000, "output": 131072 }, "temperature": true, @@ -11648,35 +12397,30 @@ "effort_options": [ "low", "medium", - "high", - "xhigh", - "max" + "xhigh" ] } }, "attachment": true, "open_weights": false, - "release_date": "2026-09-02", - "last_updated": "2026-09-02", + "release_date": "2026-08-03", + "last_updated": "2026-08-03", "type": "chat" }, { - "id": "meta/muse-spark-1.1", - "name": "Muse Spark 1.1", - "display_name": "Muse Spark 1.1", + "id": "scx-ai-gp/glm-5.2-fast", + "name": "GLM-5.2 Turbo (SCX.ai)", + "display_name": "GLM-5.2 Turbo (SCX.ai)", "modalities": { "input": [ - "text", - "image", - "pdf", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, + "context": 1000000, "output": 131072 }, "temperature": true, @@ -11685,34 +12429,23 @@ "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "effort_options": [ - "low", - "medium", - "high", - "xhigh" - ] - } - }, - "attachment": true, - "open_weights": false, - "release_date": "2026-04-08", - "last_updated": "2026-07-09", + "attachment": false, + "open_weights": true, + "release_date": "2026-06-13", + "last_updated": "2026-06-13", "type": "chat" }, { - "id": "meta/muse-spark-1.2", - "name": "Muse Spark 1.2", - "display_name": "Muse Spark 1.2", + "id": "google-ai-studio/gemini-4-argon", + "name": "Gemini 4 Argon (Google AI Studio)", + "display_name": "Gemini 4 Argon (Google AI Studio)", "modalities": { "input": [ "text", "image", "video", - "pdf", - "audio" + "audio", + "pdf" ], "output": [ "text" @@ -11720,9 +12453,9 @@ }, "limit": { "context": 1048576, - "output": 131072 + "output": 1048576 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -11730,32 +12463,29 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, "effort_options": [ - "low", - "medium", - "high", - "xhigh" + "high" ] } }, "attachment": true, "open_weights": false, - "release_date": "2026-08-05", - "last_updated": "2026-08-05", + "knowledge": "2026-03", + "release_date": "2026-10-13", + "last_updated": "2026-10-13", "type": "chat" }, { - "id": "meta/muse-spark-1.2-contributor", - "name": "Muse Spark 1.2 (Contributor)", - "display_name": "Muse Spark 1.2 (Contributor)", + "id": "google-ai-studio/gemini-3.6-flash", + "name": "Gemini 3.6 Flash (Google AI Studio)", + "display_name": "Gemini 3.6 Flash (Google AI Studio)", "modalities": { "input": [ "text", "image", "video", - "pdf", - "audio" + "audio", + "pdf" ], "output": [ "text" @@ -11763,7 +12493,7 @@ }, "limit": { "context": 1048576, - "output": 131072 + "output": 65536 }, "temperature": true, "tool_call": true, @@ -11773,37 +12503,49 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ + "supported": true, + "default_enabled": true, + "mode": "level", + "level": "high", + "level_options": [ + "minimal", "low", "medium", - "high", - "xhigh" + "high" + ], + "summaries": true, + "visibility": "summary", + "continuation": [ + "thought_signatures" ] } }, "attachment": true, "open_weights": false, - "release_date": "2026-08-05", - "last_updated": "2026-08-05", + "knowledge": "2026-03", + "release_date": "2026-07-21", + "last_updated": "2026-07-21", "type": "chat" }, { - "id": "stepfun/Step-5-Preview", - "name": "Step 5 Preview", - "display_name": "Step 5 Preview", + "id": "google-ai-studio/gemini-3.8-flash", + "name": "Gemini 3.8 Flash (Google AI Studio)", + "display_name": "Gemini 3.8 Flash (Google AI Studio)", "modalities": { "input": [ "text", "image", - "video" + "video", + "audio", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 1000000 + "context": 1048576, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -11813,36 +12555,46 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ + "supported": true, + "default_enabled": true, + "mode": "level", + "level": "high", + "level_options": [ + "minimal", "low", "medium", "high" + ], + "summaries": true, + "visibility": "summary", + "continuation": [ + "thought_signatures" ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-09-16", - "last_updated": "2026-09-20", + "open_weights": false, + "release_date": "2026-09-02", + "last_updated": "2026-09-02", "type": "chat" }, { - "id": "stepfun/Step-3.7-Flash", - "name": "Step 3.7 Flash", - "display_name": "Step 3.7 Flash", + "id": "google-ai-studio/gemini-pro-latest", + "name": "Gemini Pro Latest (Google AI Studio)", + "display_name": "Gemini Pro Latest (Google AI Studio)", "modalities": { "input": [ "text", "image", - "video" + "audio" ], "output": [ "text" ] }, "limit": { - "context": 256000, - "output": 256000 + "context": 1048576, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -11860,27 +12612,30 @@ } }, "attachment": true, - "open_weights": true, - "knowledge": "2026-03-01", - "release_date": "2026-05-29", - "last_updated": "2026-05-29", + "open_weights": false, + "release_date": "2026-02-27", + "last_updated": "2026-02-27", "type": "chat" }, { - "id": "stepfun/Step-3.5-Flash", - "name": "Step 3.5 Flash", - "display_name": "Step 3.5 Flash", + "id": "google-ai-studio/gemini-3.1-pro-preview", + "name": "Gemini 3.1 Pro (Preview) (Google AI Studio)", + "display_name": "Gemini 3.1 Pro (Preview) (Google AI Studio)", "modalities": { "input": [ - "text" + "text", + "image", + "video", + "audio", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 256000, - "output": 256000 + "context": 1048576, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -11890,87 +12645,82 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ + "supported": true, + "default_enabled": true, + "mode": "level", + "level": "high", + "level_options": [ "low", "high" + ], + "summaries": true, + "visibility": "summary", + "continuation": [ + "thought_signatures" ] } }, - "attachment": false, - "open_weights": true, + "attachment": true, + "open_weights": false, "knowledge": "2025-01", - "release_date": "2026-01-29", - "last_updated": "2026-02-13", + "release_date": "2026-02-19", + "last_updated": "2026-02-19", "type": "chat" - } - ] - }, - "vivgrid": { - "id": "vivgrid", - "name": "Vivgrid", - "display_name": "Vivgrid", - "api": "https://api.vivgrid.com/v1", - "doc": "https://docs.vivgrid.com/models", - "models": [ + }, { - "id": "gpt-5.1-codex-max", - "name": "GPT-5.1 Codex Max", - "display_name": "GPT-5.1 Codex Max", + "id": "google-ai-studio/gemini-3.1-flash-lite", + "name": "Gemini 3.1 Flash Lite (Google AI Studio)", + "display_name": "Gemini 3.1 Flash Lite (Google AI Studio)", "modalities": { "input": [ "text", - "image" + "image", + "video", + "audio", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 1048576, + "output": 65536 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "none", "effort_options": [ - "none", - "low", - "medium", - "high" - ], - "verbosity": "medium", - "verbosity_options": [ + "minimal", "low", "medium", "high" - ], - "visibility": "hidden" + ] } }, "attachment": true, "open_weights": false, - "knowledge": "2024-09-30", - "release_date": "2025-11-13", - "last_updated": "2025-11-13", + "knowledge": "2025-01", + "release_date": "2026-05-07", + "last_updated": "2026-05-07", "type": "chat" }, { - "id": "gpt-5.5", - "name": "GPT-5.5", - "display_name": "GPT-5.5", + "id": "google-ai-studio/gemini-3-flash-preview", + "name": "Gemini 3 Flash (Preview) (Google AI Studio)", + "display_name": "Gemini 3 Flash (Preview) (Google AI Studio)", "modalities": { "input": [ "text", "image", + "video", + "audio", "pdf" ], "output": [ @@ -11978,10 +12728,10 @@ ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 1048576, + "output": 65535 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -11991,38 +12741,38 @@ "reasoning": { "supported": true, "default_enabled": true, - "mode": "effort", - "effort": "medium", - "effort_options": [ - "low", - "medium", - "high", - "xhigh" - ], - "verbosity": "medium", - "verbosity_options": [ + "mode": "level", + "level": "high", + "level_options": [ + "minimal", "low", "medium", "high" ], - "visibility": "hidden" + "summaries": true, + "visibility": "summary", + "continuation": [ + "thought_signatures" + ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-12-01", - "release_date": "2026-04-23", - "last_updated": "2026-04-23", + "knowledge": "2025-01", + "release_date": "2025-12-17", + "last_updated": "2025-12-17", "type": "chat" }, { - "id": "gpt-6-astra", - "name": "GPT-6 Astra", - "display_name": "GPT-6 Astra", + "id": "google-ai-studio/gemini-3.5-flash-lite", + "name": "Gemini 3.5 Flash Lite (Google AI Studio)", + "display_name": "Gemini 3.5 Flash Lite (Google AI Studio)", "modalities": { "input": [ "text", "image", + "video", + "audio", "pdf" ], "output": [ @@ -12030,10 +12780,10 @@ ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 1048576, + "output": 65536 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -12043,83 +12793,71 @@ "reasoning": { "supported": true, "effort_options": [ + "minimal", "low", "medium", - "high", - "xhigh", - "max" + "high" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-04-30", - "release_date": "2026-09-04", - "last_updated": "2026-09-04", + "knowledge": "2026-03", + "release_date": "2026-07-21", + "last_updated": "2026-07-21", "type": "chat" }, { - "id": "gpt-5.4", - "name": "GPT-5.4", - "display_name": "GPT-5.4", + "id": "aws-bedrock/kimi-k3", + "name": "Kimi K3 (AWS Bedrock)", + "display_name": "Kimi K3 (AWS Bedrock)", "modalities": { "input": [ "text", "image", - "pdf" + "video" ], "output": [ "text" ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 1048576, + "output": 1048576 }, "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "none", "effort_options": [ "none", "low", "medium", "high", - "xhigh" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + "xhigh", + "max" + ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-03-05", - "last_updated": "2026-03-05", + "open_weights": true, + "release_date": "2026-07-16", + "last_updated": "2026-07-16", "type": "chat" }, { - "id": "glm-5.3-flash", - "name": "GLM-5.3-Flash", - "display_name": "GLM-5.3-Flash", + "id": "aws-bedrock/claude-haiku-4-5-20251001", + "name": "Claude Haiku 4.5 (2025-10-01) (AWS Bedrock)", + "display_name": "Claude Haiku 4.5 (2025-10-01) (AWS Bedrock)", "modalities": { "input": [ "text", "image", - "video", "pdf" ], "output": [ @@ -12127,44 +12865,52 @@ ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 200000, + "output": 64000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": false, + "mode": "budget", + "budget": { + "min": 1024, + "unit": "tokens" + }, "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ "thinking_blocks" ], - "effort_options": [ - "low", - "high", - "max" + "notes": [ + "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", + "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-08-26", - "last_updated": "2026-08-26", + "open_weights": false, + "knowledge": "2025-02-28", + "release_date": "2025-10-15", + "last_updated": "2025-10-15", "type": "chat" }, { - "id": "deepseek-v4-flash", - "name": "DeepSeek V4 Flash", - "display_name": "DeepSeek V4 Flash", + "id": "aws-bedrock/claude-haiku-5-5", + "name": "Claude Haiku 5.5 (AWS Bedrock)", + "display_name": "Claude Haiku 5.5 (AWS Bedrock)", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" @@ -12172,9 +12918,9 @@ }, "limit": { "context": 1000000, - "output": 384000 + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -12182,37 +12928,56 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "high", "effort_options": [ + "none", "low", + "medium", "high", + "xhigh", "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." ] } }, - "attachment": false, + "attachment": true, + "open_weights": false, + "knowledge": "2026-06", + "release_date": "2026-10-07", + "last_updated": "2026-10-07", + "type": "chat" + }, + { + "id": "aws-bedrock/llama-4-maverick-17b-instruct", + "name": "Llama 4 Maverick 17B Instruct (AWS Bedrock)", + "display_name": "Llama 4 Maverick 17B Instruct (AWS Bedrock)", + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 8192, + "output": 2048 + }, + "temperature": true, + "tool_call": false, + "reasoning": { + "supported": false + }, + "attachment": true, "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-07-31", - "last_updated": "2026-07-31", + "knowledge": "2024-08", + "release_date": "2025-04-05", + "last_updated": "2025-04-05", "type": "chat" }, { - "id": "claude-sonnet-5-5", - "name": "Claude Sonnet 5.5", - "display_name": "Claude Sonnet 5.5", + "id": "aws-bedrock/claude-sonnet-4-5-20250929", + "name": "Claude Sonnet 4.5 (2025-09-29) (AWS Bedrock)", + "display_name": "Claude Sonnet 4.5 (2025-09-29) (AWS Bedrock)", "modalities": { "input": [ "text", @@ -12224,37 +12989,47 @@ ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 200000, + "output": 8192 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" + "supported": true, + "default_enabled": false, + "mode": "budget", + "budget": { + "min": 1024, + "unit": "tokens" + }, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", + "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-06", - "release_date": "2026-09-28", - "last_updated": "2026-09-28", + "knowledge": "2025-07-31", + "release_date": "2025-09-29", + "last_updated": "2025-09-29", "type": "chat" }, { - "id": "claude-haiku-5-5", - "name": "Claude Haiku 5.5", - "display_name": "Claude Haiku 5.5", + "id": "aws-bedrock/grok-4-3", + "name": "Grok 4.3 (AWS Bedrock)", + "display_name": "Grok 4.3 (AWS Bedrock)", "modalities": { "input": [ "text", @@ -12267,9 +13042,9 @@ }, "limit": { "context": 1000000, - "output": 128000 + "output": 131072 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -12278,25 +13053,23 @@ "extra_capabilities": { "reasoning": { "effort_options": [ + "none", "low", "medium", - "high", - "xhigh", - "max" + "high" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-06", - "release_date": "2026-10-07", - "last_updated": "2026-10-07", + "release_date": "2026-04-17", + "last_updated": "2026-04-17", "type": "chat" }, { - "id": "kimi-k3", - "name": "Kimi K3", - "display_name": "Kimi K3", + "id": "aws-bedrock/claude-opus-4-7", + "name": "Claude Opus 4.7 (AWS Bedrock)", + "display_name": "Claude Opus 4.7 (AWS Bedrock)", "modalities": { "input": [ "text", @@ -12309,58 +13082,80 @@ }, "limit": { "context": 1000000, - "output": 131072 + "output": 128000 }, "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { - "supported": true + "supported": true, + "default_enabled": false, + "mode": "effort", + "effort": "high", + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "omitted", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Claude Opus 4.7 and newer Opus models require thinking.type = \"adaptive\" to enable thinking explicitly.", + "Manual budget_tokens requests return 400 on Claude Opus 4.7 and newer adaptive-only Opus models.", + "task_budget is separate from thinking control and should not be treated as a thinking budget." + ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-07-16", - "last_updated": "2026-07-16", + "open_weights": false, + "knowledge": "2026-01-31", + "release_date": "2026-04-16", + "last_updated": "2026-04-16", "type": "chat" }, { - "id": "deepseek-v4-pro", - "name": "DeepSeek V4 Pro", - "display_name": "DeepSeek V4 Pro", + "id": "aws-bedrock/claude-haiku-4-5", + "name": "Claude Haiku 4.5 (AWS Bedrock)", + "display_name": "Claude Haiku 4.5 (AWS Bedrock)", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 384000 + "context": 200000, + "output": 64000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "high", - "effort_options": [ - "low", - "high", - "max" - ], + "default_enabled": false, + "mode": "budget", + "budget": { + "min": 1024, + "unit": "tokens" + }, "interleaved": true, "summaries": true, "visibility": "summary", @@ -12368,21 +13163,22 @@ "thinking_blocks" ], "notes": [ - "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." + "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", + "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." ] } }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-04-24", - "last_updated": "2026-04-24", + "attachment": true, + "open_weights": false, + "knowledge": "2025-02-28", + "release_date": "2025-10-15", + "last_updated": "2025-10-15", "type": "chat" }, { - "id": "claude-opus-5", - "name": "Claude Opus 5", - "display_name": "Claude Opus 5", + "id": "aws-bedrock/claude-sonnet-5", + "name": "Claude Sonnet 5 (AWS Bedrock)", + "display_name": "Claude Sonnet 5 (AWS Bedrock)", "modalities": { "input": [ "text", @@ -12407,6 +13203,7 @@ "reasoning": { "supported": true, "effort_options": [ + "none", "low", "medium", "high", @@ -12417,15 +13214,15 @@ }, "attachment": true, "open_weights": false, - "knowledge": "2026-05", - "release_date": "2026-07-24", - "last_updated": "2026-07-24", + "knowledge": "2026-01-31", + "release_date": "2026-06-30", + "last_updated": "2026-06-30", "type": "chat" }, { - "id": "claude-opus-5-5", - "name": "Claude Opus 5.5", - "display_name": "Claude Opus 5.5", + "id": "aws-bedrock/claude-fable-5", + "name": "Claude Fable 5 (AWS Bedrock)", + "display_name": "Claude Fable 5 (AWS Bedrock)", "modalities": { "input": [ "text", @@ -12448,26 +13245,41 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "high", "effort_options": [ "low", "medium", "high", "xhigh", "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "omitted", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Adaptive thinking is always on for Claude Fable 5 and Claude Mythos 5; thinking.type = \"disabled\" is rejected.", + "Manual budget_tokens requests return 400 on Claude Fable 5 and Claude Mythos 5.", + "thinking.display defaults to omitted; set display to summarized to receive readable thinking summaries." ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-06", - "release_date": "2026-09-22", - "last_updated": "2026-09-22", + "knowledge": "2026-01-31", + "release_date": "2026-06-09", + "last_updated": "2026-06-09", "type": "chat" }, { - "id": "claude-sonnet-5", - "name": "Claude Sonnet 5", - "display_name": "Claude Sonnet 5", + "id": "aws-bedrock/grok-4-7", + "name": "Grok 4.7 (AWS Bedrock)", + "display_name": "Grok 4.7 (AWS Bedrock)", "modalities": { "input": [ "text", @@ -12479,10 +13291,10 @@ ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 524288, + "output": 524288 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -12490,33 +13302,29 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, "effort_options": [ "low", "medium", "high", - "xhigh", - "max" + "xhigh" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-01-31", - "release_date": "2026-06-30", - "last_updated": "2026-06-30", + "knowledge": "2026-05", + "release_date": "2026-09-21", + "last_updated": "2026-09-21", "type": "chat" }, { - "id": "gemini-3.7-flash", - "name": "Gemini 3.7 Flash", - "display_name": "Gemini 3.7 Flash", + "id": "aws-bedrock/claude-sonnet-4-5", + "name": "Claude Sonnet 4.5 (AWS Bedrock)", + "display_name": "Claude Sonnet 4.5 (AWS Bedrock)", "modalities": { "input": [ "text", "image", - "video", - "audio", "pdf" ], "output": [ @@ -12524,88 +13332,75 @@ ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 200000, + "output": 8192 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "level", - "level": "high", - "level_options": [ - "minimal", - "low", - "medium", - "high" - ], + "default_enabled": false, + "mode": "budget", + "budget": { + "min": 1024, + "unit": "tokens" + }, + "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ - "thought_signatures" + "thinking_blocks" + ], + "notes": [ + "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", + "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-03", - "release_date": "2026-08-13", - "last_updated": "2026-08-13", + "knowledge": "2025-07-31", + "release_date": "2025-09-29", + "last_updated": "2025-09-29", "type": "chat" }, { - "id": "gpt-6-sol", - "name": "GPT-6 Sol", - "display_name": "GPT-6 Sol", + "id": "aws-bedrock/llama-3.1-70b-instruct", + "name": "Llama 3.1 70B Instruct (AWS Bedrock)", + "display_name": "Llama 3.1 70B Instruct (AWS Bedrock)", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 128000, + "output": 2048 }, - "temperature": false, - "tool_call": true, + "temperature": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "none", - "low", - "medium", - "high", - "xhigh", - "max" - ] - } + "supported": false }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-04-20", - "release_date": "2026-09-22", - "last_updated": "2026-09-22", + "attachment": false, + "open_weights": true, + "knowledge": "2023-12", + "release_date": "2024-07-23", + "last_updated": "2024-07-23", "type": "chat" }, { - "id": "gpt-5.6-sol", - "name": "GPT 5.6 Sol", - "display_name": "GPT 5.6 Sol", + "id": "aws-bedrock/claude-opus-5-5", + "name": "Claude Opus 5.5 (AWS Bedrock)", + "display_name": "Claude Opus 5.5 (AWS Bedrock)", "modalities": { "input": [ "text", @@ -12617,10 +13412,10 @@ ] }, "limit": { - "context": 1050000, + "context": 1000000, "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -12628,38 +13423,26 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", "effort_options": [ - "none", "low", "medium", "high", "xhigh", "max" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-02-16", - "release_date": "2026-07-09", - "last_updated": "2026-07-09", + "knowledge": "2026-06", + "release_date": "2026-09-22", + "last_updated": "2026-09-22", "type": "chat" }, { - "id": "gpt-5.6-terra", - "name": "GPT 5.6 Terra", - "display_name": "GPT 5.6 Terra", + "id": "aws-bedrock/claude-sonnet-5-5", + "name": "Claude Sonnet 5.5 (AWS Bedrock)", + "display_name": "Claude Sonnet 5.5 (AWS Bedrock)", "modalities": { "input": [ "text", @@ -12671,10 +13454,10 @@ ] }, "limit": { - "context": 1050000, + "context": 1000000, "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -12682,42 +13465,31 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", "effort_options": [ - "none", "low", "medium", "high", "xhigh", "max" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-02-16", - "release_date": "2026-07-09", - "last_updated": "2026-07-09", + "knowledge": "2026-06", + "release_date": "2026-09-28", + "last_updated": "2026-09-28", "type": "chat" }, { - "id": "viv-fast", - "name": "Viv Fast", - "display_name": "Viv Fast", + "id": "aws-bedrock/claude-sonnet-4-6", + "name": "Claude Sonnet 4.6 (AWS Bedrock)", + "display_name": "Claude Sonnet 4.6 (AWS Bedrock)", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" @@ -12725,45 +13497,65 @@ }, "limit": { "context": 1000000, - "output": 256000 + "output": 64000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": false, + "mode": "mixed", + "budget": { + "min": 1024, + "unit": "tokens" + }, + "effort": "high", "effort_options": [ "low", + "medium", "high", "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Anthropic recommends adaptive thinking with effort for Claude 4.6; budget_tokens remains a deprecated compatibility path.", + "Anthropic API defaults effort to high; lower effort levels should be chosen per workload." ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-09", - "release_date": "2026-09-21", - "last_updated": "2026-09-21", + "knowledge": "2025-08-31", + "release_date": "2026-02-17", + "last_updated": "2026-03-13", "type": "chat" }, { - "id": "gpt-5.1-codex", - "name": "GPT-5.1 Codex", - "display_name": "GPT-5.1 Codex", + "id": "aws-bedrock/claude-opus-4-8", + "name": "Claude Opus 4.8 (AWS Bedrock)", + "display_name": "Claude Opus 4.8 (AWS Bedrock)", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 400000, + "context": 1000000, "output": 128000 }, "temperature": false, @@ -12777,48 +13569,52 @@ "supported": true, "default_enabled": false, "mode": "effort", - "effort": "none", + "effort": "high", "effort_options": [ - "none", "low", "medium", - "high" + "high", + "xhigh", + "max" ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" + "interleaved": true, + "summaries": true, + "visibility": "omitted", + "continuation": [ + "thinking_blocks" ], - "visibility": "hidden" + "notes": [ + "Claude Opus 4.7 and newer Opus models require thinking.type = \"adaptive\" to enable thinking explicitly.", + "Manual budget_tokens requests return 400 on Claude Opus 4.7 and newer adaptive-only Opus models.", + "task_budget is separate from thinking control and should not be treated as a thinking budget." + ] } }, "attachment": true, "open_weights": false, - "knowledge": "2024-09-30", - "release_date": "2025-11-13", - "last_updated": "2025-11-13", + "knowledge": "2026-01", + "release_date": "2026-05-28", + "last_updated": "2026-05-28", "type": "chat" }, { - "id": "gpt-6-luna", - "name": "GPT-6 Luna", - "display_name": "GPT-6 Luna", + "id": "aws-bedrock/grok-4-6", + "name": "Grok 4.6 (AWS Bedrock)", + "display_name": "Grok 4.6 (AWS Bedrock)", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 500000, + "output": 500000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -12827,30 +13623,29 @@ "extra_capabilities": { "reasoning": { "effort_options": [ - "none", "low", "medium", "high", - "xhigh", - "max" + "xhigh" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-05-18", - "release_date": "2026-09-22", - "last_updated": "2026-09-22", + "knowledge": "2026-02-01", + "release_date": "2026-08-12", + "last_updated": "2026-08-12", "type": "chat" }, { - "id": "deepseek-v4.1-flash", - "name": "DeepSeek V4.1 Flash", - "display_name": "DeepSeek V4.1 Flash", + "id": "aws-bedrock/claude-opus-4-6", + "name": "Claude Opus 4.6 (AWS Bedrock)", + "display_name": "Claude Opus 4.6 (AWS Bedrock)", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" @@ -12858,52 +13653,65 @@ }, "limit": { "context": 1000000, - "output": 384000 + "output": 128000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": false, + "mode": "mixed", + "budget": { + "min": 1024, + "unit": "tokens" + }, + "effort": "high", + "effort_options": [ + "low", + "medium", + "high", + "max" + ], "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ "thinking_blocks" ], - "effort_options": [ - "low", - "high", - "max" + "notes": [ + "Anthropic recommends adaptive thinking with effort for Claude 4.6; budget_tokens remains a deprecated compatibility path.", + "Anthropic API defaults effort to high; lower effort levels should be chosen per workload." ] } }, "attachment": true, - "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-09-10", - "last_updated": "2026-09-10", + "open_weights": false, + "knowledge": "2025-05-31", + "release_date": "2026-02-05", + "last_updated": "2026-03-13", "type": "chat" }, { - "id": "gpt-5-mini", - "name": "GPT-5 Mini", - "display_name": "GPT-5 Mini", + "id": "aws-bedrock/claude-fable-5-1", + "name": "Claude Fable 5.1 (AWS Bedrock)", + "display_name": "Claude Fable 5.1 (AWS Bedrock)", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 272000, + "context": 1000000, "output": 128000 }, "temperature": false, @@ -12917,79 +13725,71 @@ "supported": true, "default_enabled": true, "mode": "effort", - "effort": "medium", + "effort": "high", "effort_options": [ - "minimal", "low", "medium", - "high" + "high", + "xhigh", + "max" ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" + "interleaved": true, + "summaries": true, + "visibility": "omitted", + "continuation": [ + "thinking_blocks" ], - "visibility": "hidden" + "notes": [ + "Adaptive thinking is always on for Claude Fable 5 and Claude Mythos 5; thinking.type = \"disabled\" is rejected.", + "Manual budget_tokens requests return 400 on Claude Fable 5 and Claude Mythos 5.", + "thinking.display defaults to omitted; set display to summarized to receive readable thinking summaries." + ] } }, "attachment": true, "open_weights": false, - "knowledge": "2024-05-30", - "release_date": "2025-08-07", - "last_updated": "2025-08-07", + "knowledge": "2026-06", + "release_date": "2026-09-01", + "last_updated": "2026-09-01", "type": "chat" }, { - "id": "deepseek-v3.2", - "name": "DeepSeek-V3.2", - "display_name": "DeepSeek-V3.2", + "id": "aws-bedrock/llama-4-scout-17b-instruct", + "name": "Llama 4 Scout 17B Instruct (AWS Bedrock)", + "display_name": "Llama 4 Scout 17B Instruct (AWS Bedrock)", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 128000 + "context": 8192, + "output": 2048 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "effort_options": [ - "none", - "low", - "medium", - "high" - ] - } + "supported": false }, - "attachment": false, + "attachment": true, "open_weights": true, - "knowledge": "2024-07", - "release_date": "2025-12-01", - "last_updated": "2025-12-01", + "knowledge": "2024-08", + "release_date": "2025-04-05", + "last_updated": "2025-04-05", "type": "chat" }, { - "id": "gemini-3.1-flash-lite-preview", - "name": "Gemini 3.1 Flash Lite Preview", - "display_name": "Gemini 3.1 Flash Lite Preview", + "id": "aws-bedrock/claude-opus-5", + "name": "Claude Opus 5 (AWS Bedrock)", + "display_name": "Claude Opus 5 (AWS Bedrock)", "modalities": { "input": [ "text", "image", - "video", - "audio", "pdf" ], "output": [ @@ -12997,10 +13797,10 @@ ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 1000000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -13008,72 +13808,91 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, "effort_options": [ "none", "low", "medium", - "high" + "high", + "xhigh", + "max" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-01", - "release_date": "2026-03-03", - "last_updated": "2026-03-03", + "knowledge": "2026-05", + "release_date": "2026-07-24", + "last_updated": "2026-07-24", "type": "chat" }, { - "id": "deepseek-v4-pro-0813", - "name": "DeepSeek V4 Pro 0813", - "display_name": "DeepSeek V4 Pro 0813", + "id": "aws-bedrock/claude-opus-4-5-20251101", + "name": "Claude Opus 4.5 (AWS Bedrock)", + "display_name": "Claude Opus 4.5 (AWS Bedrock)", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 384000 + "context": 200000, + "output": 32000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": false, + "mode": "mixed", + "budget": { + "min": 1024, + "unit": "tokens" + }, + "effort": "high", + "effort_options": [ + "low", + "medium", + "high" + ], "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ "thinking_blocks" ], - "effort_options": [ - "high", - "max" + "notes": [ + "Claude Opus 4.5 uses manual thinking.type = \"enabled\" with budget_tokens; effort can be used alongside the thinking budget.", + "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header." ] } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-08-13", - "last_updated": "2026-08-22", + "attachment": true, + "open_weights": false, + "knowledge": "2025-05", + "release_date": "2025-11-24", + "last_updated": "2025-11-24", "type": "chat" }, { - "id": "gpt-5.6-luna", - "name": "GPT 5.6 Luna", - "display_name": "GPT 5.6 Luna", + "id": "consensusprotocol/glm-5.3-flash", + "name": "GLM-5.3 Flash (Consensus Protocol)", + "display_name": "GLM-5.3 Flash (Consensus Protocol)", "modalities": { "input": [ "text", "image", + "video", "pdf" ], "output": [ @@ -13081,8 +13900,8 @@ ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 1048576, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -13093,90 +13912,64 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", "effort_options": [ - "none", "low", - "medium", "high", - "xhigh", "max" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2026-02-16", - "release_date": "2026-07-09", - "last_updated": "2026-07-09", + "open_weights": true, + "release_date": "2026-08-26", + "last_updated": "2026-08-26", "type": "chat" }, { - "id": "gpt-5.4-mini", - "name": "GPT-5.4 Mini", - "display_name": "GPT-5.4 Mini", + "id": "consensusprotocol/mimo-v2.6-flash", + "name": "MiMo V2.6 Flash (Consensus Protocol)", + "display_name": "MiMo V2.6 Flash (Consensus Protocol)", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 1048576, + "output": 131072 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "none", "effort_options": [ "none", + "minimal", "low", "medium", "high", - "xhigh" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + "xhigh", + "max" + ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-03-17", - "last_updated": "2026-03-17", + "attachment": false, + "open_weights": true, + "release_date": "2026-09-22", + "last_updated": "2026-09-22", "type": "chat" }, { - "id": "gpt-5.3-codex", - "name": "GPT-5.3 Codex", - "display_name": "GPT-5.3 Codex", + "id": "consensusprotocol/deepseek-v4.1-flash", + "name": "DeepSeek V4.1 Flash (Consensus Protocol)", + "display_name": "DeepSeek V4.1 Flash (Consensus Protocol)", "modalities": { "input": [ "text", @@ -13187,10 +13980,10 @@ ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 1048576, + "output": 384000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -13199,50 +13992,36 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", "effort_options": [ "low", - "medium", "high", - "xhigh" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + "xhigh", + "max" + ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-02-05", - "last_updated": "2026-02-05", + "open_weights": true, + "knowledge": "2025-05", + "release_date": "2026-09-10", + "last_updated": "2026-09-10", "type": "chat" }, { - "id": "gemini-3.1-pro-preview", - "name": "Gemini 3.1 Pro Preview", - "display_name": "Gemini 3.1 Pro Preview", + "id": "consensusprotocol/deepseek-v4-flash", + "name": "DeepSeek V4 Flash (Consensus Protocol)", + "display_name": "DeepSeek V4 Flash (Consensus Protocol)", "modalities": { "input": [ - "text", - "image", - "video", - "audio", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 1000000, + "output": 384000 }, "temperature": true, "tool_call": true, @@ -13254,45 +14033,48 @@ "reasoning": { "supported": true, "default_enabled": true, - "mode": "level", - "level": "high", - "level_options": [ + "mode": "effort", + "effort": "high", + "effort_options": [ "low", - "high" + "high", + "max" ], + "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ - "thought_signatures" + "thinking_blocks" + ], + "notes": [ + "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-01", - "release_date": "2026-02-19", - "last_updated": "2026-02-19", + "attachment": false, + "open_weights": true, + "knowledge": "2025-05", + "release_date": "2026-04-24", + "last_updated": "2026-04-24", "type": "chat" }, { - "id": "gemini-3.8-flash", - "name": "Gemini 3.8 Flash", - "display_name": "Gemini 3.8 Flash", + "id": "consensusprotocol/Qwen3.8-27B", + "name": "Qwen3.8 27B (Consensus Protocol)", + "display_name": "Qwen3.8 27B (Consensus Protocol)", "modalities": { "input": [ "text", "image", - "video", - "audio", - "pdf" + "video" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 128000 + "context": 262000, + "output": 32768 }, "temperature": true, "tool_call": true, @@ -13302,86 +14084,63 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "level", - "level": "high", - "level_options": [ - "minimal", + "effort_options": [ "low", "medium", - "high" - ], - "summaries": true, - "visibility": "summary", - "continuation": [ - "thought_signatures" + "xhigh" ] } }, "attachment": true, - "open_weights": false, - "release_date": "2026-09-02", - "last_updated": "2026-09-02", + "open_weights": true, + "release_date": "2026-08-14", + "last_updated": "2026-08-14", "type": "chat" }, { - "id": "gpt-5.4-nano", - "name": "GPT-5.4 Nano", - "display_name": "GPT-5.4 Nano", + "id": "consensusprotocol/gemma-4-31b-it", + "name": "Gemma 4 31B IT (Consensus Protocol)", + "display_name": "Gemma 4 31B IT (Consensus Protocol)", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 262144, + "output": 32768 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "none", "effort_options": [ "none", - "low", - "medium", "high", - "xhigh" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + "xhigh", + "max" + ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-03-17", - "last_updated": "2026-03-17", + "attachment": false, + "open_weights": true, + "knowledge": "2025-01", + "release_date": "2026-04-02", + "last_updated": "2026-04-02", "type": "chat" }, { - "id": "glm-5.3", - "name": "GLM-5.3", - "display_name": "GLM-5.3", + "id": "consensusprotocol/minimax-m3", + "name": "MiniMax M3 (Consensus Protocol)", + "display_name": "MiniMax M3 (Consensus Protocol)", "modalities": { "input": [ "text" @@ -13391,8 +14150,8 @@ ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 1048576, + "output": 512000 }, "temperature": true, "tool_call": true, @@ -13403,43 +14162,40 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], "effort_options": [ + "none", + "minimal", "low", + "medium", "high", + "xhigh", "max" ] } }, "attachment": false, "open_weights": true, - "release_date": "2026-08-14", - "last_updated": "2026-08-14", + "release_date": "2026-06-01", + "last_updated": "2026-06-01", "type": "chat" }, { - "id": "gpt-5.2-codex", - "name": "GPT-5.2 Codex", - "display_name": "GPT-5.2 Codex", + "id": "consensusprotocol/gpt-oss-20b", + "name": "GPT OSS 20B (Consensus Protocol)", + "display_name": "GPT OSS 20B (Consensus Protocol)", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 128000, + "output": 32768 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -13448,48 +14204,37 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", "effort_options": [ - "low", - "medium", - "high", - "xhigh" - ], - "verbosity": "medium", - "verbosity_options": [ "low", "medium", "high" - ], - "visibility": "hidden" + ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2025-12-11", - "last_updated": "2025-12-11", + "attachment": false, + "open_weights": true, + "knowledge": "2024-06", + "release_date": "2025-08-05", + "last_updated": "2025-08-05", "type": "chat" }, { - "id": "claude-fable-5", - "name": "Claude Fable 5", - "display_name": "Claude Fable 5", + "id": "runware/kimi-k3", + "name": "Kimi K3 (Runware)", + "display_name": "Kimi K3 (Runware)", "modalities": { "input": [ "text", "image", - "pdf" + "video" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 1048576, + "output": 1048576 }, "temperature": false, "tool_call": true, @@ -13500,55 +14245,38 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "high", "effort_options": [ + "none", "low", - "medium", "high", - "xhigh", "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "omitted", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Adaptive thinking is always on for Claude Fable 5 and Claude Mythos 5; thinking.type = \"disabled\" is rejected.", - "Manual budget_tokens requests return 400 on Claude Fable 5 and Claude Mythos 5.", - "thinking.display defaults to omitted; set display to summarized to receive readable thinking summaries." ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2026-01-31", - "release_date": "2026-06-09", - "last_updated": "2026-06-09", + "open_weights": true, + "release_date": "2026-07-16", + "last_updated": "2026-07-16", "type": "chat" }, { - "id": "gpt-6.1-terra", - "name": "GPT-6.1 Terra", - "display_name": "GPT-6.1 Terra", + "id": "runware/deepseek-v4.1-flash-1017", + "name": "DeepSeek V4.1 Flash 1017 (runware)", + "display_name": "DeepSeek V4.1 Flash 1017 (runware)", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 1048576, + "output": 1048576 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -13559,7 +14287,6 @@ "effort_options": [ "none", "low", - "medium", "high", "xhigh", "max" @@ -13567,20 +14294,21 @@ } }, "attachment": true, - "open_weights": false, - "knowledge": "2026-06-01", - "release_date": "2026-10-26", - "last_updated": "2026-10-26", + "open_weights": true, + "knowledge": "2025-05", + "release_date": "2026-10-17", + "last_updated": "2026-10-17", "type": "chat" }, { - "id": "claude-fable-5-1", - "name": "Claude Fable 5.1", - "display_name": "Claude Fable 5.1", + "id": "runware/glm-5.3-flash", + "name": "GLM-5.3 Flash (Runware)", + "display_name": "GLM-5.3 Flash (Runware)", "modalities": { "input": [ "text", "image", + "video", "pdf" ], "output": [ @@ -13589,9 +14317,9 @@ }, "limit": { "context": 1000000, - "output": 128000 + "output": 131072 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -13600,55 +14328,41 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "high", "effort_options": [ + "none", + "minimal", "low", "medium", "high", "xhigh", "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "omitted", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Adaptive thinking is always on for Claude Fable 5 and Claude Mythos 5; thinking.type = \"disabled\" is rejected.", - "Manual budget_tokens requests return 400 on Claude Fable 5 and Claude Mythos 5.", - "thinking.display defaults to omitted; set display to summarized to receive readable thinking summaries." ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2026-06", - "release_date": "2026-09-01", - "last_updated": "2026-09-01", + "open_weights": true, + "release_date": "2026-08-26", + "last_updated": "2026-08-26", "type": "chat" }, { - "id": "gpt-6.1-sol", - "name": "GPT-6.1 Sol", - "display_name": "GPT-6.1 Sol", + "id": "runware/deepseek-v4.1-flash", + "name": "DeepSeek V4.1 Flash (Runware)", + "display_name": "DeepSeek V4.1 Flash (Runware)", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 1048576, + "output": 384000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -13656,9 +14370,10 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, "effort_options": [ + "none", "low", - "medium", "high", "xhigh", "max" @@ -13666,16 +14381,16 @@ } }, "attachment": true, - "open_weights": false, - "knowledge": "2026-04-30", - "release_date": "2026-09-29", - "last_updated": "2026-09-29", + "open_weights": true, + "knowledge": "2025-05", + "release_date": "2026-09-10", + "last_updated": "2026-09-10", "type": "chat" }, { - "id": "glm-5.2", - "name": "GLM-5.2", - "display_name": "GLM-5.2", + "id": "runware/deepseek-v4-pro", + "name": "DeepSeek V4 Pro (Runware)", + "display_name": "DeepSeek V4 Pro (Runware)", "modalities": { "input": [ "text" @@ -13685,8 +14400,8 @@ ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 1048576, + "output": 384000 }, "temperature": true, "tool_call": true, @@ -13697,37 +14412,36 @@ "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "high", + "effort_options": [ + "low", + "high", + "max" + ], "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ "thinking_blocks" ], - "effort_options": [ - "high", - "max" + "notes": [ + "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." ] } }, "attachment": false, "open_weights": true, - "release_date": "2026-06-13", - "last_updated": "2026-06-13", + "knowledge": "2025-05", + "release_date": "2026-04-24", + "last_updated": "2026-04-24", "type": "chat" - } - ] - }, - "alibaba-token-plan": { - "id": "alibaba-token-plan", - "name": "Alibaba Token Plan", - "display_name": "Alibaba Token Plan", - "api": "https://token-plan.ap-southeast-1.maas.aliyuncs.com/compatible-mode/v1", - "doc": "https://www.alibabacloud.com/help/en/model-studio/token-plan-overview", - "models": [ + }, { - "id": "qwen3.7-max", - "name": "Qwen3.7 Max", - "display_name": "Qwen3.7 Max", + "id": "runware/glm-5.3", + "name": "GLM-5.3 (Runware)", + "display_name": "GLM-5.3 (Runware)", "modalities": { "input": [ "text" @@ -13749,77 +14463,23 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "low", + "high", + "max" ] } }, "attachment": false, - "open_weights": false, - "release_date": "2026-05-21", - "last_updated": "2026-05-21", + "open_weights": true, + "release_date": "2026-08-14", + "last_updated": "2026-08-14", "type": "chat" }, { - "id": "happyhorse-1.1-i2v", - "name": "HappyHorse 1.1 Image-to-Video", - "display_name": "HappyHorse 1.1 Image-to-Video", - "modalities": { - "input": [ - "image", - "text" - ], - "output": [ - "video" - ] - }, - "limit": { - "context": 8192, - "output": 8192 - }, - "temperature": true, - "tool_call": false, - "reasoning": { - "supported": false - }, - "attachment": true, - "open_weights": false, - "release_date": "2026-07-17", - "last_updated": "2026-07-17" - }, - { - "id": "qwen-image-2.0", - "name": "Qwen Image 2.0", - "display_name": "Qwen Image 2.0", - "modalities": { - "input": [ - "text" - ], - "output": [ - "image" - ] - }, - "limit": { - "context": 8192, - "output": 8192 - }, - "temperature": true, - "tool_call": false, - "reasoning": { - "supported": false - }, - "attachment": false, - "open_weights": false, - "release_date": "2026-03-03", - "last_updated": "2026-03-03" - }, - { - "id": "deepseek-v4-flash", - "name": "DeepSeek V4 Flash", - "display_name": "DeepSeek V4 Flash", + "id": "runware/deepseek-v4-flash", + "name": "DeepSeek V4 Flash (Runware)", + "display_name": "DeepSeek V4 Flash (Runware)", "modalities": { "input": [ "text" @@ -13829,7 +14489,7 @@ ] }, "limit": { - "context": 1000000, + "context": 1048576, "output": 384000 }, "temperature": true, @@ -13868,9 +14528,9 @@ "type": "chat" }, { - "id": "deepseek-v4.1-flash-1017", - "name": "DeepSeek V4.1 Flash 1017", - "display_name": "DeepSeek V4.1 Flash 1017", + "id": "runware/gemma-4-31b-it", + "name": "Gemma 4 31B IT (Runware)", + "display_name": "Gemma 4 31B IT (Runware)", "modalities": { "input": [ "text", @@ -13881,8 +14541,8 @@ ] }, "limit": { - "context": 1000000, - "output": 384000 + "context": 262144, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -13893,56 +14553,25 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], "effort_options": [ - "low", + "none", "high", + "xhigh", "max" ] } }, "attachment": true, "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-10-17", - "last_updated": "2026-10-17", + "knowledge": "2025-01", + "release_date": "2026-04-02", + "last_updated": "2026-04-02", "type": "chat" }, { - "id": "qwen-image-2.0-pro", - "name": "Qwen Image 2.0 Pro", - "display_name": "Qwen Image 2.0 Pro", - "modalities": { - "input": [ - "text" - ], - "output": [ - "image" - ] - }, - "limit": { - "context": 8192, - "output": 8192 - }, - "temperature": true, - "tool_call": false, - "reasoning": { - "supported": false - }, - "attachment": false, - "open_weights": false, - "release_date": "2026-03-03", - "last_updated": "2026-03-03" - }, - { - "id": "deepseek-v4-pro", - "name": "DeepSeek V4 Pro", - "display_name": "DeepSeek V4 Pro", + "id": "runware/glm-5.2", + "name": "GLM-5.2 (Runware)", + "display_name": "GLM-5.2 (Runware)", "modalities": { "input": [ "text" @@ -13952,8 +14581,8 @@ ] }, "limit": { - "context": 1000000, - "output": 384000 + "context": 1024000, + "output": 128000 }, "temperature": true, "tool_call": true, @@ -13964,36 +14593,33 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "high", - "effort_options": [ - "low", - "high", - "max" - ], "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ "thinking_blocks" ], - "notes": [ - "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." + "effort_options": [ + "none", + "minimal", + "low", + "medium", + "high", + "xhigh", + "max" ] } }, "attachment": false, "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-04-24", - "last_updated": "2026-04-24", + "release_date": "2026-06-13", + "last_updated": "2026-06-13", "type": "chat" }, { - "id": "qwen3.6-flash", - "name": "Qwen3.6 Flash", - "display_name": "Qwen3.6 Flash", + "id": "fireworks/kimi-k3", + "name": "Kimi K3 (Fireworks AI)", + "display_name": "Kimi K3 (Fireworks AI)", "modalities": { "input": [ "text", @@ -14005,10 +14631,10 @@ ] }, "limit": { - "context": 1000000, - "output": 65536 + "context": 1040384, + "output": 1040384 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -14017,64 +14643,40 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "none", + "low", + "medium", + "high", + "xhigh", + "max" ] } }, "attachment": true, - "open_weights": false, - "release_date": "2026-04-27", - "last_updated": "2026-04-27", + "open_weights": true, + "release_date": "2026-07-16", + "last_updated": "2026-07-16", "type": "chat" }, { - "id": "happyhorse-1.1-r2v", - "name": "HappyHorse 1.1 Reference-to-Video", - "display_name": "HappyHorse 1.1 Reference-to-Video", - "modalities": { - "input": [ - "image", - "text" - ], - "output": [ - "video" - ] - }, - "limit": { - "context": 8192, - "output": 8192 - }, - "temperature": true, - "tool_call": false, - "reasoning": { - "supported": false - }, - "attachment": true, - "open_weights": false, - "release_date": "2026-07-17", - "last_updated": "2026-07-17" - }, - { - "id": "kimi-k2.6", - "name": "Kimi K2.6", - "display_name": "Kimi K2.6", + "id": "fireworks/glm-5.3-flash", + "name": "GLM-5.3 Flash (Fireworks AI)", + "display_name": "GLM-5.3 Flash (Fireworks AI)", "modalities": { "input": [ "text", "image", - "video" + "video", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 1048576, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -14085,25 +14687,22 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "xhigh", + "max" ] } }, "attachment": true, "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-04-20", - "last_updated": "2026-04-20", + "release_date": "2026-08-26", + "last_updated": "2026-08-26", "type": "chat" }, { - "id": "kimi-k2.5", - "name": "Kimi K2.5", - "display_name": "Kimi K2.5", + "id": "fireworks/kimi-k3-fast", + "name": "Kimi K3 Fast (Fireworks AI)", + "display_name": "Kimi K3 Fast (Fireworks AI)", "modalities": { "input": [ "text", @@ -14115,10 +14714,10 @@ ] }, "limit": { - "context": 262144, - "output": 98304 + "context": 1040384, + "output": 1040384 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -14126,40 +14725,38 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "none", + "low", + "medium", + "high", + "xhigh", + "max" ] } }, "attachment": true, "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-01-27", - "last_updated": "2026-01-27", + "release_date": "2026-07-16", + "last_updated": "2026-07-16", "type": "chat" }, { - "id": "qwen3.8-max", - "name": "Qwen3.8 Max", - "display_name": "Qwen3.8 Max", + "id": "fireworks/deepseek-v4.1-flash", + "name": "DeepSeek V4.1 Flash (Fireworks AI)", + "display_name": "DeepSeek V4.1 Flash (Fireworks AI)", "modalities": { "input": [ "text", - "image", - "video", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 1048576, + "output": 393216 }, "temperature": true, "tool_call": true, @@ -14170,41 +14767,38 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], "effort_options": [ + "none", "low", "medium", - "xhigh" + "high", + "xhigh", + "max" ] } }, "attachment": true, - "open_weights": false, - "release_date": "2026-08-03", - "last_updated": "2026-08-03", + "open_weights": true, + "knowledge": "2025-05", + "release_date": "2026-09-10", + "last_updated": "2026-09-10", "type": "chat" }, { - "id": "deepseek-v4.1-flash", - "name": "DeepSeek V4.1 Flash", - "display_name": "DeepSeek V4.1 Flash", + "id": "fireworks/glm-5.3", + "name": "GLM-5.3 (Fireworks AI)", + "display_name": "GLM-5.3 (Fireworks AI)", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 384000 + "context": 1048576, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -14215,41 +14809,34 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], "effort_options": [ - "low", - "high", + "xhigh", "max" ] } }, - "attachment": true, + "attachment": false, "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-09-10", - "last_updated": "2026-09-10", + "release_date": "2026-08-14", + "last_updated": "2026-08-14", "type": "chat" }, { - "id": "deepseek-v3.2", - "name": "DeepSeek V3.2", - "display_name": "DeepSeek V3.2", + "id": "cloudflare/deepseek-v4.1-flash-1017", + "name": "DeepSeek V4.1 Flash 1017 (Cloudflare)", + "display_name": "DeepSeek V4.1 Flash 1017 (Cloudflare)", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 65536 + "context": 1048576, + "output": 393216 }, "temperature": true, "tool_call": true, @@ -14259,59 +14846,42 @@ }, "extra_capabilities": { "reasoning": { - "supported": true + "effort_options": [ + "none", + "minimal", + "low", + "medium", + "high", + "xhigh", + "max" + ] } }, - "attachment": false, + "attachment": true, "open_weights": true, - "knowledge": "2025-01", - "release_date": "2025-12-03", - "last_updated": "2025-12-05", + "knowledge": "2025-05", + "release_date": "2026-10-17", + "last_updated": "2026-10-17", "type": "chat" }, { - "id": "happyhorse-1.1-t2v", - "name": "HappyHorse 1.1 Text-to-Video", - "display_name": "HappyHorse 1.1 Text-to-Video", - "modalities": { - "input": [ - "text" - ], - "output": [ - "video" - ] - }, - "limit": { - "context": 8192, - "output": 8192 - }, - "temperature": true, - "tool_call": false, - "reasoning": { - "supported": false - }, - "attachment": false, - "open_weights": false, - "release_date": "2026-07-17", - "last_updated": "2026-07-17" - }, - { - "id": "qwen3.6-plus", - "name": "Qwen3.6 Plus", - "display_name": "Qwen3.6 Plus", + "id": "cloudflare/glm-5.3-flash", + "name": "GLM-5.3 Flash (Cloudflare)", + "display_name": "GLM-5.3 Flash (Cloudflare)", "modalities": { "input": [ "text", "image", - "video" + "video", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 65536 + "context": 1048576, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -14322,25 +14892,23 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "low", + "high", + "max" ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2025-04", - "release_date": "2026-04-02", - "last_updated": "2026-04-02", + "open_weights": true, + "release_date": "2026-08-26", + "last_updated": "2026-08-26", "type": "chat" }, { - "id": "deepseek-v4-flash-0731", - "name": "DeepSeek V4 Flash 0731", - "display_name": "DeepSeek V4 Flash 0731", + "id": "cloudflare/deepseek-v4-pro", + "name": "DeepSeek V4 Pro (Cloudflare)", + "display_name": "DeepSeek V4 Pro (Cloudflare)", "modalities": { "input": [ "text" @@ -14350,8 +14918,8 @@ ] }, "limit": { - "context": 1000000, - "output": 384000 + "context": 1048576, + "output": 393216 }, "temperature": true, "tool_call": true, @@ -14362,29 +14930,36 @@ "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "high", + "effort_options": [ + "low", + "high", + "max" + ], "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ "thinking_blocks" ], - "effort_options": [ - "high", - "max" + "notes": [ + "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." ] } }, "attachment": false, "open_weights": true, "knowledge": "2025-05", - "release_date": "2026-07-31", - "last_updated": "2026-07-31", + "release_date": "2026-04-24", + "last_updated": "2026-04-24", "type": "chat" }, { - "id": "deepseek-v4-pro-0813", - "name": "DeepSeek V4 Pro 0813", - "display_name": "DeepSeek V4 Pro 0813", + "id": "cloudflare/glm-5.3", + "name": "GLM-5.3 (Cloudflare)", + "display_name": "GLM-5.3 (Cloudflare)", "modalities": { "input": [ "text" @@ -14394,8 +14969,8 @@ ] }, "limit": { - "context": 1000000, - "output": 384000 + "context": 1048576, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -14406,13 +14981,8 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], "effort_options": [ + "low", "high", "max" ] @@ -14420,29 +14990,27 @@ }, "attachment": false, "open_weights": true, - "release_date": "2026-08-13", - "last_updated": "2026-08-22", + "release_date": "2026-08-14", + "last_updated": "2026-08-14", "type": "chat" }, { - "id": "kimi-k2.7-code", - "name": "Kimi K2.7 Code", - "display_name": "Kimi K2.7 Code", + "id": "cloudflare/deepseek-v4-flash", + "name": "DeepSeek V4 Flash (Cloudflare)", + "display_name": "DeepSeek V4 Flash (Cloudflare)", "modalities": { "input": [ - "text", - "image", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 1048576, + "output": 393216 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -14451,64 +15019,94 @@ "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "high", + "effort_options": [ + "low", + "high", + "max" + ], "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ "thinking_blocks" + ], + "notes": [ + "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." ] } }, - "attachment": true, + "attachment": false, "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-06-12", - "last_updated": "2026-06-12", + "knowledge": "2025-05", + "release_date": "2026-04-24", + "last_updated": "2026-04-24", "type": "chat" }, { - "id": "wan2.7-image", - "name": "Wan2.7 Image", - "display_name": "Wan2.7 Image", + "id": "runpod/kimi-k3", + "name": "Kimi K3 (Runpod)", + "display_name": "Kimi K3 (Runpod)", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ - "image" + "text" ] }, "limit": { - "context": 8192, - "output": 8192 + "context": 1048576, + "output": 1048576 }, - "temperature": true, - "tool_call": false, + "temperature": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, - "open_weights": false, - "release_date": "2026-05-29", - "last_updated": "2026-05-29" + "extra_capabilities": { + "reasoning": { + "supported": true, + "effort_options": [ + "none", + "minimal", + "low", + "medium", + "high", + "xhigh", + "max" + ] + } + }, + "attachment": true, + "open_weights": true, + "release_date": "2026-07-16", + "last_updated": "2026-07-16", + "type": "chat" }, { - "id": "glm-5.1", - "name": "GLM-5.1", - "display_name": "GLM-5.1", + "id": "runpod/kimi-k3.1-pro-highspeed", + "name": "Kimi K3.1 Pro Highspeed (Runpod)", + "display_name": "Kimi K3.1 Pro Highspeed (Runpod)", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 202752, - "output": 128000 + "context": 1048576, + "output": 1048576 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -14516,25 +15114,27 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "none", + "minimal", + "low", + "medium", + "high", + "xhigh", + "max" ] } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-04-07", - "last_updated": "2026-04-07", + "attachment": true, + "open_weights": false, + "release_date": "2026-10-23", + "last_updated": "2026-10-23", "type": "chat" }, { - "id": "qwen3.8-flash", - "name": "Qwen3.8 Flash", - "display_name": "Qwen3.8 Flash", + "id": "runpod/kimi-k3.1-pro", + "name": "Kimi K3.1 Pro (Runpod)", + "display_name": "Kimi K3.1 Pro (Runpod)", "modalities": { "input": [ "text", @@ -14546,10 +15146,10 @@ ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 1048576, + "output": 1048576 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -14557,30 +15157,27 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], "effort_options": [ + "none", + "minimal", "low", "medium", - "xhigh" + "high", + "xhigh", + "max" ] } }, "attachment": true, "open_weights": false, - "release_date": "2026-08-26", - "last_updated": "2026-08-26", + "release_date": "2026-10-23", + "last_updated": "2026-10-23", "type": "chat" }, { - "id": "glm-5", - "name": "GLM-5", - "display_name": "GLM-5", + "id": "runpod/glm-5.3", + "name": "GLM-5.3 (Runpod)", + "display_name": "GLM-5.3 (Runpod)", "modalities": { "input": [ "text" @@ -14590,8 +15187,8 @@ ] }, "limit": { - "context": 202752, - "output": 16384 + "context": 262144, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -14602,27 +15199,169 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" ] } }, "attachment": false, + "open_weights": true, + "release_date": "2026-08-14", + "last_updated": "2026-08-14", + "type": "chat" + }, + { + "id": "mistral/mistral-large-2512", + "name": "Mistral Large 3 (Mistral AI)", + "display_name": "Mistral Large 3 (Mistral AI)", + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 262144, + "output": 262144 + }, + "temperature": true, + "tool_call": false, + "reasoning": { + "supported": false + }, + "attachment": true, + "open_weights": true, + "knowledge": "2024-11", + "release_date": "2025-12-02", + "last_updated": "2025-12-02", + "type": "chat" + }, + { + "id": "mistral/ministral-14b-2512", + "name": "Ministral 14B (Mistral AI)", + "display_name": "Ministral 14B (Mistral AI)", + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 262144, + "output": 262144 + }, + "temperature": true, + "tool_call": false, + "reasoning": { + "supported": false + }, + "attachment": true, + "open_weights": true, + "release_date": "2025-12-02", + "last_updated": "2025-12-02", + "type": "chat" + }, + { + "id": "mistral/codestral-2508", + "name": "Codestral (Mistral AI)", + "display_name": "Codestral (Mistral AI)", + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 256000, + "output": 256000 + }, + "temperature": true, + "tool_call": false, + "reasoning": { + "supported": false + }, + "attachment": false, "open_weights": false, - "release_date": "2026-02-12", - "last_updated": "2026-02-12", + "release_date": "2025-07-30", + "last_updated": "2025-07-30", "type": "chat" }, { - "id": "glm-5.3", - "name": "GLM-5.3", - "display_name": "GLM-5.3", + "id": "mistral/ministral-8b-2512", + "name": "Ministral 8B (Mistral AI)", + "display_name": "Ministral 8B (Mistral AI)", + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 262144, + "output": 262144 + }, + "temperature": true, + "tool_call": false, + "reasoning": { + "supported": false + }, + "attachment": true, + "open_weights": true, + "release_date": "2025-12-02", + "last_updated": "2025-12-02", + "type": "chat" + }, + { + "id": "mistral/ministral-3b-2512", + "name": "Ministral 3B (Mistral AI)", + "display_name": "Ministral 3B (Mistral AI)", "modalities": { "input": [ + "text", + "image" + ], + "output": [ "text" + ] + }, + "limit": { + "context": 131072, + "output": 131072 + }, + "temperature": true, + "tool_call": false, + "reasoning": { + "supported": false + }, + "attachment": true, + "open_weights": true, + "release_date": "2025-12-02", + "last_updated": "2025-12-02", + "type": "chat" + }, + { + "id": "mistral/mistral-large-4", + "name": "Mistral Large 4 (Mistral AI)", + "display_name": "Mistral Large 4 (Mistral AI)", + "modalities": { + "input": [ + "text", + "image" ], "output": [ "text" @@ -14630,6 +15369,33 @@ }, "limit": { "context": 1000000, + "output": 262144 + }, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": false + }, + "attachment": true, + "open_weights": false, + "release_date": "2026-10-06", + "last_updated": "2026-10-06", + "type": "chat" + }, + { + "id": "mistral/glm-5.3", + "name": "GLM-5.3 (Mistral AI)", + "display_name": "GLM-5.3 (Mistral AI)", + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1048576, "output": 131072 }, "temperature": true, @@ -14641,12 +15407,6 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], "effort_options": [ "low", "high", @@ -14661,9 +15421,9 @@ "type": "chat" }, { - "id": "MiniMax-M2.5", - "name": "MiniMax-M2.5", - "display_name": "MiniMax-M2.5", + "id": "mistral/devstral-2512", + "name": "Devstral 2 (Mistral AI)", + "display_name": "Devstral 2 (Mistral AI)", "modalities": { "input": [ "text" @@ -14673,93 +15433,123 @@ ] }, "limit": { - "context": 196608, - "output": 32768 + "context": 262144, + "output": 262144 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } + "supported": false }, "attachment": false, "open_weights": true, - "release_date": "2026-02-12", - "last_updated": "2026-02-12", + "knowledge": "2025-12", + "release_date": "2025-12-09", + "last_updated": "2025-12-09", "type": "chat" }, { - "id": "qwen3.8-max-preview", - "name": "Qwen3.8 Max Preview", - "display_name": "Qwen3.8 Max Preview", + "id": "mistral/mistral-medium-3-5", + "name": "Mistral Medium 3.5 (Mistral AI)", + "display_name": "Mistral Medium 3.5 (Mistral AI)", "modalities": { "input": [ "text", "image", - "video" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 262144, + "output": 262144 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "effort_options": [ - "low", - "medium", - "xhigh" - ] - } + "supported": false }, "attachment": true, "open_weights": false, - "release_date": "2026-07-19", - "last_updated": "2026-07-19", + "release_date": "2026-04-30", + "last_updated": "2026-04-30", "type": "chat" }, { - "id": "wan2.7-image-pro", - "name": "Wan2.7 Image Pro", - "display_name": "Wan2.7 Image Pro", + "id": "mistral/mistral-small-2603", + "name": "Mistral Small 4 (Mistral AI)", + "display_name": "Mistral Small 4 (Mistral AI)", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ + "text" + ] + }, + "limit": { + "context": 262144, + "output": 256000 + }, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": false + }, + "attachment": true, + "open_weights": true, + "knowledge": "2025-06", + "release_date": "2026-03-16", + "last_updated": "2026-03-16", + "type": "chat" + }, + { + "id": "mistral/mistral-small-2506", + "name": "Mistral Small 3.2 (Mistral AI)", + "display_name": "Mistral Small 3.2 (Mistral AI)", + "modalities": { + "input": [ + "text", "image" + ], + "output": [ + "text" ] }, "limit": { - "context": 8192, - "output": 8192 + "context": 128000, + "output": 16384 + }, + "temperature": true, + "tool_call": false, + "reasoning": { + "supported": false + }, + "attachment": true, + "open_weights": true, + "knowledge": "2025-03", + "release_date": "2025-06-20", + "last_updated": "2025-06-20", + "type": "chat" + }, + { + "id": "mistral/mistral-large-latest", + "name": "Mistral Large Latest (Mistral AI)", + "display_name": "Mistral Large Latest (Mistral AI)", + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 262144 }, "temperature": true, "tool_call": false, @@ -14767,27 +15557,27 @@ "supported": false }, "attachment": false, - "open_weights": false, - "release_date": "2026-05-29", - "last_updated": "2026-05-29" + "open_weights": true, + "knowledge": "2024-11", + "release_date": "2024-11-01", + "last_updated": "2025-12-02", + "type": "chat" }, { - "id": "qwen3.7-plus", - "name": "Qwen3.7 Plus", - "display_name": "Qwen3.7 Plus", + "id": "groq/gpt-oss-120b", + "name": "GPT OSS 120B (Groq)", + "display_name": "GPT OSS 120B (Groq)", "modalities": { "input": [ - "text", - "image", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 65536 + "context": 131072, + "output": 32766 }, "temperature": true, "tool_call": true, @@ -14798,25 +15588,24 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "low", + "medium", + "high" ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-04", - "release_date": "2026-06-02", - "last_updated": "2026-06-02", + "attachment": false, + "open_weights": true, + "knowledge": "2024-06", + "release_date": "2025-08-05", + "last_updated": "2025-08-05", "type": "chat" }, { - "id": "glm-5.2", - "name": "GLM-5.2", - "display_name": "GLM-5.2", + "id": "groq/gpt-oss-20b", + "name": "GPT OSS 20B (Groq)", + "display_name": "GPT OSS 20B (Groq)", "modalities": { "input": [ "text" @@ -14826,8 +15615,8 @@ ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 131072, + "output": 32766 }, "temperature": true, "tool_call": true, @@ -14838,41 +15627,29 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], "effort_options": [ - "high", - "max" + "low", + "medium", + "high" ] } }, "attachment": false, "open_weights": true, - "release_date": "2026-06-13", - "last_updated": "2026-06-13", + "knowledge": "2024-06", + "release_date": "2025-08-05", + "last_updated": "2025-08-05", "type": "chat" - } - ] - }, - "scx-ai": { - "id": "scx-ai", - "name": "SCX.ai", - "display_name": "SCX.ai", - "api": "https://api.scx.ai/v1", - "doc": "https://platform.scx.ai/docs", - "models": [ + }, { - "id": "Qwen3.8-Max", - "name": "Qwen3.8 Max", - "display_name": "Qwen3.8 Max", + "id": "alibaba/qwen3.8-omni-flash", + "name": "Qwen3.8 Omni Flash (Alibaba Cloud)", + "display_name": "Qwen3.8 Omni Flash (Alibaba Cloud)", "modalities": { "input": [ "text", "image", + "audio", "video" ], "output": [ @@ -14880,7 +15657,7 @@ ] }, "limit": { - "context": 1000000, + "context": 983616, "output": 131072 }, "temperature": true, @@ -14891,44 +15668,42 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], "effort_options": [ "none", + "minimal", "low", "medium", - "xhigh" + "high", + "xhigh", + "max" ] } }, "attachment": true, "open_weights": false, - "release_date": "2026-08-03", - "last_updated": "2026-08-03", + "release_date": "2026-09-17", + "last_updated": "2026-09-17", "type": "chat" }, { - "id": "gpt-oss-120b", - "name": "GPT OSS 120B", - "display_name": "GPT OSS 120B", + "id": "alibaba/kimi-k3", + "name": "Kimi K3 (Alibaba Cloud)", + "display_name": "Kimi K3 (Alibaba Cloud)", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 131072 + "context": 1048576, + "output": 1048576 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -14937,41 +15712,40 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], "effort_options": [ + "none", + "minimal", "low", "medium", - "high" + "high", + "xhigh", + "max" ] } }, - "attachment": false, + "attachment": true, "open_weights": true, - "knowledge": "2024-06", - "release_date": "2025-08-05", - "last_updated": "2025-08-05", + "release_date": "2026-07-16", + "last_updated": "2026-07-16", "type": "chat" }, { - "id": "MiniMax-M2.7", - "name": "MiniMax-M2.7", - "display_name": "MiniMax-M2.7", + "id": "alibaba/qwen35-397b-a17b", + "name": "Qwen3.5 397B A17B (Alibaba Cloud)", + "display_name": "Qwen3.5 397B A17B (Alibaba Cloud)", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 196608, - "output": 196608 + "context": 262144, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -14981,41 +15755,40 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], "effort_options": [ + "none", + "minimal", "low", "medium", - "high" + "high", + "xhigh", + "max" ] } }, - "attachment": false, + "attachment": true, "open_weights": true, - "release_date": "2026-03-18", - "last_updated": "2026-03-18", + "release_date": "2026-02-15", + "last_updated": "2026-02-15", "type": "chat" }, { - "id": "GLM-5.2", - "name": "GLM-5.2", - "display_name": "GLM-5.2", + "id": "alibaba/qwen3.5-27b", + "name": "Qwen3.5 27B (Alibaba Cloud)", + "display_name": "Qwen3.5 27B (Alibaba Cloud)", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 262144, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -15034,30 +15807,25 @@ ], "effort_options": [ "none", + "minimal", + "low", + "medium", "high", + "xhigh", "max" ] } }, - "attachment": false, + "attachment": true, "open_weights": true, - "release_date": "2026-06-13", - "last_updated": "2026-06-13", + "release_date": "2026-02-23", + "last_updated": "2026-02-23", "type": "chat" - } - ] - }, - "tencent-coding-plan": { - "id": "tencent-coding-plan", - "name": "Tencent Coding Plan (China)", - "display_name": "Tencent Coding Plan (China)", - "api": "https://api.lkeap.cloud.tencent.com/coding/v3", - "doc": "https://cloud.tencent.com/document/product/1772/128947", - "models": [ + }, { - "id": "hunyuan-2.0-thinking", - "name": "Tencent HY 2.0 Think", - "display_name": "Tencent HY 2.0 Think", + "id": "alibaba/qwen-plus-latest", + "name": "Qwen Plus Latest (Alibaba Cloud)", + "display_name": "Qwen Plus Latest (Alibaba Cloud)", "modalities": { "input": [ "text" @@ -15067,14 +15835,13 @@ ] }, "limit": { - "context": 131072, - "output": 16384 + "context": 1000000, + "output": 32000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true + "supported": true }, "extra_capabilities": { "reasoning": { @@ -15089,14 +15856,14 @@ }, "attachment": false, "open_weights": false, - "release_date": "2026-03-08", - "last_updated": "2026-03-08", + "release_date": "2024-09-09", + "last_updated": "2024-09-09", "type": "chat" }, { - "id": "hunyuan-2.0-instruct", - "name": "Tencent HY 2.0 Instruct", - "display_name": "Tencent HY 2.0 Instruct", + "id": "alibaba/qwen3.7-max", + "name": "Qwen3.7 Max (Alibaba Cloud)", + "display_name": "Qwen3.7 Max (Alibaba Cloud)", "modalities": { "input": [ "text" @@ -15106,24 +15873,39 @@ ] }, "limit": { - "context": 131072, - "output": 16384 + "context": 983616, + "output": 65536 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "effort_options": [ + "none", + "minimal", + "low", + "medium", + "high", + "xhigh", + "max" + ] + } }, "attachment": false, "open_weights": false, - "release_date": "2026-03-08", - "last_updated": "2026-03-08", + "release_date": "2026-05-21", + "last_updated": "2026-05-21", "type": "chat" }, { - "id": "kimi-k2.5", - "name": "Kimi-K2.5", - "display_name": "Kimi-K2.5", + "id": "alibaba/qwen3.6-flash", + "name": "Qwen3.6 Flash (Alibaba Cloud)", + "display_name": "Qwen3.6 Flash (Alibaba Cloud)", "modalities": { "input": [ "text", @@ -15135,8 +15917,8 @@ ] }, "limit": { - "context": 262144, - "output": 32768 + "context": 983616, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -15152,20 +15934,28 @@ "visibility": "summary", "continuation": [ "thinking_blocks" + ], + "effort_options": [ + "none", + "minimal", + "low", + "medium", + "high", + "xhigh", + "max" ] } }, "attachment": true, - "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-01-27", - "last_updated": "2026-01-27", + "open_weights": false, + "release_date": "2026-04-27", + "last_updated": "2026-04-27", "type": "chat" }, { - "id": "minimax-m2.5", - "name": "MiniMax-M2.5", - "display_name": "MiniMax-M2.5", + "id": "alibaba/qwen-plus", + "name": "Qwen Plus (Alibaba Cloud)", + "display_name": "Qwen Plus (Alibaba Cloud)", "modalities": { "input": [ "text" @@ -15175,14 +15965,13 @@ ] }, "limit": { - "context": 204800, - "output": 32768 + "context": 131072, + "output": 32000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true + "supported": true }, "extra_capabilities": { "reasoning": { @@ -15196,53 +15985,73 @@ } }, "attachment": false, - "open_weights": true, - "release_date": "2026-02-12", - "last_updated": "2026-02-12", + "open_weights": false, + "knowledge": "2024-04", + "release_date": "2024-01-25", + "last_updated": "2025-09-11", "type": "chat" }, { - "id": "tc-code-latest", - "name": "Auto", - "display_name": "Auto", + "id": "alibaba/qwen3.8-flash", + "name": "Qwen3.8 Flash (Alibaba Cloud)", + "display_name": "Qwen3.8 Flash (Alibaba Cloud)", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 16384 + "context": 983616, + "output": 131072 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, + "extra_capabilities": { + "reasoning": { + "supported": true, + "effort_options": [ + "none", + "minimal", + "low", + "medium", + "high", + "xhigh", + "max" + ] + } + }, + "attachment": true, "open_weights": false, - "release_date": "2026-03-08", - "last_updated": "2026-03-08", + "release_date": "2026-08-26", + "last_updated": "2026-08-26", "type": "chat" }, { - "id": "glm-5", - "name": "GLM-5", - "display_name": "GLM-5", + "id": "alibaba/qwen3.6-35b-a3b", + "name": "Qwen3.6 35B A3B (Alibaba Cloud)", + "display_name": "Qwen3.6 35B A3B (Alibaba Cloud)", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 202752, - "output": 16384 + "context": 262144, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -15258,30 +16067,40 @@ "visibility": "summary", "continuation": [ "thinking_blocks" + ], + "effort_options": [ + "none", + "minimal", + "low", + "medium", + "high", + "xhigh", + "max" ] } }, - "attachment": false, + "attachment": true, "open_weights": true, - "release_date": "2026-02-12", - "last_updated": "2026-02-12", + "release_date": "2026-04-17", + "last_updated": "2026-04-17", "type": "chat" }, { - "id": "hunyuan-t1", - "name": "Hunyuan-T1", - "display_name": "Hunyuan-T1", + "id": "alibaba/deepseek-v4.1-flash-1017", + "name": "DeepSeek V4.1 Flash 1017 (Alibaba Cloud)", + "display_name": "DeepSeek V4.1 Flash 1017 (Alibaba Cloud)", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 16384 + "context": 1000000, + "output": 393216 }, "temperature": true, "tool_call": true, @@ -15291,60 +16110,72 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "none", + "minimal", + "low", + "medium", + "high", + "xhigh", + "max" ] } }, - "attachment": false, - "open_weights": false, - "release_date": "2026-03-08", - "last_updated": "2026-03-08", + "attachment": true, + "open_weights": true, + "knowledge": "2025-05", + "release_date": "2026-10-17", + "last_updated": "2026-10-17", "type": "chat" }, { - "id": "hunyuan-turbos", - "name": "Hunyuan-TurboS", - "display_name": "Hunyuan-TurboS", + "id": "alibaba/qwen3.7-flash", + "name": "Qwen3.7 Flash (Alibaba Cloud)", + "display_name": "Qwen3.7 Flash (Alibaba Cloud)", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 16384 + "context": 983616, + "output": 65536 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, + "extra_capabilities": { + "reasoning": { + "supported": true, + "effort_options": [ + "none", + "minimal", + "low", + "medium", + "high", + "xhigh", + "max" + ] + } + }, + "attachment": true, "open_weights": false, - "release_date": "2026-03-08", - "last_updated": "2026-03-08", + "release_date": "2026-07-15", + "last_updated": "2026-07-15", "type": "chat" - } - ] - }, - "vercel": { - "id": "vercel", - "name": "Vercel AI Gateway", - "display_name": "Vercel AI Gateway", - "doc": "https://github.com/vercel/ai/tree/5eb85cc45a259553501f535b8ac79a77d0e79223/packages/gateway", - "models": [ + }, { - "id": "meituan/longcat-2.5-preview", - "name": "LongCat 2.5 Preview", - "display_name": "LongCat 2.5 Preview", + "id": "alibaba/deepseek-v4.1-flash", + "name": "DeepSeek V4.1 Flash (Alibaba Cloud)", + "display_name": "DeepSeek V4.1 Flash (Alibaba Cloud)", "modalities": { "input": [ "text", @@ -15355,25 +16186,40 @@ ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 1000000, + "output": 393216 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, "default": true }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "effort_options": [ + "none", + "minimal", + "low", + "medium", + "high", + "xhigh", + "max" + ] + } + }, "attachment": true, - "open_weights": false, - "release_date": "2026-09-25", - "last_updated": "2026-09-25", + "open_weights": true, + "knowledge": "2025-05", + "release_date": "2026-09-10", + "last_updated": "2026-09-10", "type": "chat" }, { - "id": "inference-net/schematron-v2-small", - "name": "Schematron V2 Small", - "display_name": "Schematron V2 Small", + "id": "alibaba/qwen3-coder-flash", + "name": "Qwen3 Coder Flash (Alibaba Cloud)", + "display_name": "Qwen3 Coder Flash (Alibaba Cloud)", "modalities": { "input": [ "text" @@ -15383,24 +16229,25 @@ ] }, "limit": { - "context": 128000, - "output": 4096 + "context": 997952, + "output": 65536 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { "supported": false }, "attachment": false, "open_weights": false, - "release_date": "2026-04-16", - "last_updated": "2026-04-16", + "knowledge": "2025-04", + "release_date": "2025-07-28", + "last_updated": "2025-07-28", "type": "chat" }, { - "id": "inference-net/schematron-v2-turbo", - "name": "Schematron V2 Turbo", - "display_name": "Schematron V2 Turbo", + "id": "alibaba/deepseek-v4-pro", + "name": "DeepSeek V4 Pro (Alibaba Cloud)", + "display_name": "DeepSeek V4 Pro (Alibaba Cloud)", "modalities": { "input": [ "text" @@ -15410,92 +16257,183 @@ ] }, "limit": { - "context": 128000, - "output": 8192 + "context": 1000000, + "output": 393216 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "high", + "effort_options": [ + "low", + "high", + "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." + ] + } }, "attachment": false, - "open_weights": false, - "release_date": "2026-04-16", - "last_updated": "2026-04-16", + "open_weights": true, + "knowledge": "2025-05", + "release_date": "2026-04-24", + "last_updated": "2026-04-24", "type": "chat" }, { - "id": "bytedance/seedance-v1.5-pro", - "name": "Seedance v1.5 Pro", - "display_name": "Seedance v1.5 Pro", + "id": "alibaba/glm-5.3", + "name": "GLM-5.3 (Alibaba Cloud)", + "display_name": "GLM-5.3 (Alibaba Cloud)", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ - "video" + "text" ] }, "limit": { - "context": 8192, - "output": 8192 + "context": 1048576, + "output": 131072 }, - "temperature": false, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": true, - "open_weights": false, - "release_date": "2025-12-16", - "last_updated": "2025-12-16" + "extra_capabilities": { + "reasoning": { + "supported": true, + "effort_options": [ + "low", + "high", + "max" + ] + } + }, + "attachment": false, + "open_weights": true, + "release_date": "2026-08-14", + "last_updated": "2026-08-14", + "type": "chat" }, { - "id": "bytedance/seedance-2.0-fast", - "name": "Seedance 2.0 Fast", - "display_name": "Seedance 2.0 Fast", + "id": "alibaba/glm-5", + "name": "GLM-5 (Alibaba Cloud)", + "display_name": "GLM-5 (Alibaba Cloud)", "modalities": { "input": [ - "text", - "image", - "video", - "audio" + "text" ], "output": [ - "video" + "text" ] }, "limit": { - "context": 8192, - "output": 8192 + "context": 202752, + "output": 16384 }, - "temperature": false, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": true, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "effort_options": [ + "none", + "minimal", + "low", + "medium", + "high", + "xhigh" + ] + } + }, + "attachment": false, + "open_weights": true, + "release_date": "2026-02-12", + "last_updated": "2026-02-12", + "type": "chat" + }, + { + "id": "alibaba/qwen-flash", + "name": "Qwen Flash (Alibaba Cloud)", + "display_name": "Qwen Flash (Alibaba Cloud)", + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1000000, + "output": 32000 + }, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } + }, + "attachment": false, "open_weights": false, - "release_date": "2026-04-14", - "last_updated": "2026-04-14" + "knowledge": "2024-04", + "release_date": "2025-07-28", + "last_updated": "2025-07-28", + "type": "chat" }, { - "id": "bytedance/seed-2.1-turbo", - "name": "Seed 2.1 Turbo", - "display_name": "Seed 2.1 Turbo", + "id": "alibaba/deepseek-v4-flash", + "name": "DeepSeek V4 Flash (Alibaba Cloud)", + "display_name": "DeepSeek V4 Flash (Alibaba Cloud)", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 1000000, + "output": 393216 }, "temperature": true, "tool_call": true, @@ -15505,36 +16443,50 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "high", "effort_options": [ - "none", "low", - "medium", - "high" + "high", + "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." ] } }, - "attachment": true, - "open_weights": false, - "release_date": "2026-06-23", - "last_updated": "2026-06-23", + "attachment": false, + "open_weights": true, + "knowledge": "2025-05", + "release_date": "2026-04-24", + "last_updated": "2026-04-24", "type": "chat" }, { - "id": "bytedance/seed-1.8", - "name": "Seed 1.8", - "display_name": "Seed 1.8", + "id": "alibaba/qwen3.6-plus", + "name": "Qwen3.6 Plus (Alibaba Cloud)", + "display_name": "Qwen3.6 Plus (Alibaba Cloud)", "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 256000, - "output": 32768 + "context": 983616, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -15544,268 +16496,438 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ + "none", "minimal", "low", "medium", - "high" + "high", + "xhigh", + "max" ] } }, "attachment": true, "open_weights": false, - "release_date": "2025-12-28", - "last_updated": "2025-12-28", + "knowledge": "2025-04", + "release_date": "2026-04-02", + "last_updated": "2026-04-02", "type": "chat" }, { - "id": "bytedance/seedream-4.0", - "name": "Seedream 4.0", - "display_name": "Seedream 4.0", + "id": "alibaba/glm-5.2", + "name": "GLM-5.2 (Alibaba Cloud)", + "display_name": "GLM-5.2 (Alibaba Cloud)", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ - "image" + "text" ] }, "limit": { - "context": 8192, - "output": 8192 + "context": 1000000, + "output": 131072 }, - "temperature": false, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": true, - "open_weights": false, - "release_date": "2025-09-09", - "last_updated": "2025-08-28" + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "effort_options": [ + "none", + "minimal", + "low", + "medium", + "high", + "xhigh", + "max" + ] + } + }, + "attachment": false, + "open_weights": true, + "release_date": "2026-06-13", + "last_updated": "2026-06-13", + "type": "chat" }, { - "id": "bytedance/seedream-4.5", - "name": "Seedream 4.5", - "display_name": "Seedream 4.5", + "id": "alibaba/qwen3.5-35b-a3b", + "name": "Qwen3.5 35B A3B (Alibaba Cloud)", + "display_name": "Qwen3.5 35B A3B (Alibaba Cloud)", "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ - "image" + "text" ] }, "limit": { - "context": 8192, - "output": 8192 + "context": 262144, + "output": 65536 }, - "temperature": false, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "effort_options": [ + "none", + "minimal", + "low", + "medium", + "high", + "xhigh", + "max" + ] + } }, "attachment": true, - "open_weights": false, - "release_date": "2025-12-03", - "last_updated": "2025-11-28" + "open_weights": true, + "release_date": "2026-02-23", + "last_updated": "2026-02-23", + "type": "chat" }, { - "id": "bytedance/seedance-2.0", - "name": "Seedance 2.0", - "display_name": "Seedance 2.0", + "id": "alibaba/qwen3.8-max", + "name": "Qwen3.8 Max (Alibaba Cloud)", + "display_name": "Qwen3.8 Max (Alibaba Cloud)", "modalities": { "input": [ "text", - "image" + "image", + "video", + "pdf" ], "output": [ - "video" + "text" ] }, "limit": { - "context": 8192, - "output": 8192 + "context": 983616, + "output": 131072 }, - "temperature": false, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "effort_options": [ + "low", + "medium", + "xhigh" + ] + } }, "attachment": true, "open_weights": false, - "release_date": "2026-02-12", - "last_updated": "2026-10-04" + "release_date": "2026-08-03", + "last_updated": "2026-08-03", + "type": "chat" }, { - "id": "bytedance/seedance-v1.0-pro-fast", - "name": "Seedance v1.0 Pro Fast", - "display_name": "Seedance v1.0 Pro Fast", + "id": "alibaba/qwen3.7-plus", + "name": "Qwen3.7 Plus (Alibaba Cloud)", + "display_name": "Qwen3.7 Plus (Alibaba Cloud)", "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ - "video" + "text" ] }, "limit": { - "context": 8192, - "output": 8192 + "context": 983616, + "output": 65536 }, - "temperature": false, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "effort_options": [ + "none", + "minimal", + "low", + "medium", + "high", + "xhigh", + "max" + ] + } }, "attachment": true, "open_weights": false, - "release_date": "2025-10-24", - "last_updated": "2025-10-31" + "knowledge": "2025-04", + "release_date": "2026-06-02", + "last_updated": "2026-06-02", + "type": "chat" }, { - "id": "bytedance/seedream-5.0-lite", - "name": "Seedream 5.0 Lite", - "display_name": "Seedream 5.0 Lite", + "id": "alibaba/qwen3.5-122b-a10b", + "name": "Qwen3.5 122B A10B (Alibaba Cloud)", + "display_name": "Qwen3.5 122B A10B (Alibaba Cloud)", "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ - "image" + "text" ] }, "limit": { - "context": 8192, - "output": 8192 + "context": 262144, + "output": 65536 }, - "temperature": false, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "effort_options": [ + "none", + "minimal", + "low", + "medium", + "high", + "xhigh", + "max" + ] + } }, "attachment": true, - "open_weights": false, - "release_date": "2026-02-13", - "last_updated": "2026-01-28" + "open_weights": true, + "release_date": "2026-02-23", + "last_updated": "2026-02-23", + "type": "chat" }, { - "id": "bytedance/seedance-2.0-mini", - "name": "Seedance 2.0 Mini", - "display_name": "Seedance 2.0 Mini", + "id": "alibaba/qwen-max", + "name": "Qwen Max (Alibaba Cloud)", + "display_name": "Qwen Max (Alibaba Cloud)", "modalities": { "input": [ - "text", - "image", - "video", - "audio" + "text" ], "output": [ - "video" + "text" ] }, "limit": { - "context": 8192, + "context": 32768, "output": 8192 }, - "temperature": false, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { "supported": false }, - "attachment": true, + "attachment": false, "open_weights": false, - "release_date": "2026-06-22", - "last_updated": "2026-06-22" + "knowledge": "2024-04", + "release_date": "2024-04-03", + "last_updated": "2025-01-25", + "type": "chat" }, { - "id": "bytedance/seedance-v1.0-pro", - "name": "Seedance v1.0 Pro", - "display_name": "Seedance v1.0 Pro", + "id": "alibaba/qwen3-vl-plus", + "name": "Qwen3 VL Plus (Alibaba Cloud)", + "display_name": "Qwen3 VL Plus (Alibaba Cloud)", "modalities": { "input": [ "text", "image" ], "output": [ - "video" + "text" ] }, "limit": { - "context": 8192, - "output": 8192 + "context": 262144, + "output": 32768 }, - "temperature": false, + "temperature": true, "tool_call": false, "reasoning": { - "supported": false + "supported": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } }, "attachment": true, "open_weights": false, - "release_date": "2025-06-11", - "last_updated": "2025-06-11" + "knowledge": "2025-04", + "release_date": "2025-09-23", + "last_updated": "2025-09-23", + "type": "chat" }, { - "id": "bytedance/seedream-5.0-pro", - "name": "Seedream 5.0 Pro", - "display_name": "Seedream 5.0 Pro", + "id": "alibaba/kimi-k2.5", + "name": "Kimi K2.5 (Alibaba Cloud)", + "display_name": "Kimi K2.5 (Alibaba Cloud)", "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ - "image" + "text" ] }, "limit": { - "context": 8192, - "output": 8192 + "context": 262144, + "output": 98304 }, "temperature": false, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "effort_options": [ + "none", + "minimal", + "low", + "medium", + "high", + "xhigh", + "max" + ] + } }, "attachment": true, - "open_weights": false, - "release_date": "2026-07-11", - "last_updated": "2026-07-11" + "open_weights": true, + "knowledge": "2025-01", + "release_date": "2026-01-27", + "last_updated": "2026-01-27", + "type": "chat" }, { - "id": "bytedance/seedance-2.5", - "name": "Seedance 2.5", - "display_name": "Seedance 2.5", + "id": "quartz/gemini-3.1-pro-preview", + "name": "Gemini 3.1 Pro (Preview) (Quartz)", + "display_name": "Gemini 3.1 Pro (Preview) (Quartz)", "modalities": { "input": [ - "text" + "text", + "image", + "video", + "audio", + "pdf" ], "output": [ - "video" + "text" ] }, "limit": { - "context": 8192, - "output": 8192 + "context": 1048576, + "output": 65536 }, - "temperature": false, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "level", + "level": "high", + "level_options": [ + "low", + "high" + ], + "summaries": true, + "visibility": "summary", + "continuation": [ + "thought_signatures" + ] + } + }, + "attachment": true, "open_weights": false, - "release_date": "2026-07-31", - "last_updated": "2026-10-04" + "knowledge": "2025-01", + "release_date": "2026-02-19", + "last_updated": "2026-02-19", + "type": "chat" }, { - "id": "bytedance/seed-1.6", - "name": "Seed 1.6", - "display_name": "Seed 1.6", + "id": "inference.net/deepseek-v4.1-flash-cyber", + "name": "DeepSeek V4.1 Flash Cyber (Inference.net)", + "display_name": "DeepSeek V4.1 Flash Cyber (Inference.net)", "modalities": { "input": [ "text", @@ -15816,8 +16938,8 @@ ] }, "limit": { - "context": 256000, - "output": 32000 + "context": 1040000, + "output": 393216 }, "temperature": true, "tool_call": true, @@ -15825,54 +16947,82 @@ "supported": true, "default": true }, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "none", + "low", + "high", + "xhigh", + "max" + ] + } + }, "attachment": true, "open_weights": false, - "release_date": "2025-10-15", - "last_updated": "2025-10-15", + "release_date": "2026-10-29", + "last_updated": "2026-10-29", "type": "chat" }, { - "id": "minimax/minimax-h3", - "name": "MiniMax H3", - "display_name": "MiniMax H3", + "id": "inference.net/deepseek-v4.1-flash-1017", + "name": "DeepSeek V4.1 Flash 1017 (Inference.net)", + "display_name": "DeepSeek V4.1 Flash 1017 (Inference.net)", "modalities": { "input": [ "text", "image" ], "output": [ - "video" + "text" ] }, "limit": { - "context": 8192, - "output": 8192 + "context": 262144, + "output": 131072 }, - "temperature": false, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "none", + "low", + "high", + "xhigh", + "max" + ] + } }, "attachment": true, "open_weights": true, - "release_date": "2026-07-31", - "last_updated": "2026-10-04" + "knowledge": "2025-05", + "release_date": "2026-10-17", + "last_updated": "2026-10-17", + "type": "chat" }, { - "id": "minimax/minimax-m2.1", - "name": "MiniMax M2.1", - "display_name": "MiniMax M2.1", + "id": "inference.net/glm-5.3-flash", + "name": "GLM-5.3 Flash (Inference.net)", + "display_name": "GLM-5.3 Flash (Inference.net)", "modalities": { "input": [ - "text" + "text", + "image", + "video", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 204800, - "output": 131072 + "context": 1048576, + "output": 128000 }, "temperature": true, "tool_call": true, @@ -15883,19 +17033,23 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true + "effort_options": [ + "low", + "high", + "max" + ] } }, - "attachment": false, + "attachment": true, "open_weights": true, - "release_date": "2025-12-23", - "last_updated": "2025-12-23", + "release_date": "2026-08-26", + "last_updated": "2026-08-26", "type": "chat" }, { - "id": "minimax/minimax-m2.1-lightning", - "name": "MiniMax M2.1 Lightning", - "display_name": "MiniMax M2.1 Lightning", + "id": "inference.net/glm-5.3", + "name": "GLM-5.3 (Inference.net)", + "display_name": "GLM-5.3 (Inference.net)", "modalities": { "input": [ "text" @@ -15905,7 +17059,7 @@ ] }, "limit": { - "context": 204800, + "context": 1048576, "output": 131072 }, "temperature": true, @@ -15914,64 +17068,66 @@ "supported": true, "default": true }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "effort_options": [ + "low", + "high", + "max" + ] + } + }, "attachment": false, - "open_weights": false, - "knowledge": "2024-10", - "release_date": "2025-12-23", - "last_updated": "2025-10-27", + "open_weights": true, + "release_date": "2026-08-14", + "last_updated": "2026-08-14", "type": "chat" }, { - "id": "minimax/minimax-m3", - "name": "MiniMax M3", - "display_name": "MiniMax M3", + "id": "inference.net/llama-3.2-11b-instruct", + "name": "Llama 3.2 11B Instruct (Inference.net)", + "display_name": "Llama 3.2 11B Instruct (Inference.net)", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 512000, - "output": 512000 + "context": 128000, + "output": 128000 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true - } + "supported": false }, - "attachment": true, - "open_weights": true, - "release_date": "2026-06-01", - "last_updated": "2026-06-01", + "attachment": false, + "open_weights": false, + "release_date": "2024-09-25", + "last_updated": "2024-09-25", "type": "chat" }, { - "id": "minimax/minimax-m3.1-flash", - "name": "MiniMax-M3.1-Flash", - "display_name": "MiniMax-M3.1-Flash", + "id": "inference.net/minimax-m3.1-flash", + "name": "MiniMax M3.1 Flash (Inference.net)", + "display_name": "MiniMax M3.1 Flash (Inference.net)", "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 1048576 + "context": 1000000, + "output": 524288 }, "temperature": true, "tool_call": true, @@ -15997,20 +17153,21 @@ "type": "chat" }, { - "id": "minimax/minimax-m2", - "name": "MiniMax M2", - "display_name": "MiniMax M2", + "id": "entrim/deepseek-v4.1-flash-1017", + "name": "DeepSeek V4.1 Flash 1017 (entrim)", + "display_name": "DeepSeek V4.1 Flash 1017 (entrim)", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 205000, - "output": 196608 + "context": 1048576, + "output": 384000 }, "temperature": true, "tool_call": true, @@ -16020,31 +17177,40 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" + ] } }, - "attachment": false, + "attachment": true, "open_weights": true, - "release_date": "2025-10-27", - "last_updated": "2025-10-27", + "knowledge": "2025-05", + "release_date": "2026-10-17", + "last_updated": "2026-10-17", "type": "chat" }, { - "id": "minimax/minimax-m2.5", - "name": "MiniMax M2.5", - "display_name": "MiniMax M2.5", + "id": "entrim/glm-5.3-flash", + "name": "GLM-5.3 Flash (entrim)", + "display_name": "GLM-5.3 Flash (entrim)", "modalities": { "input": [ - "text" + "text", + "image", + "video", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 204800, - "output": 131000 + "context": 1048576, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -16054,19 +17220,26 @@ }, "extra_capabilities": { "reasoning": { - "supported": true + "supported": true, + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" + ] } }, - "attachment": false, + "attachment": true, "open_weights": true, - "release_date": "2026-02-12", - "last_updated": "2026-02-12", + "release_date": "2026-08-26", + "last_updated": "2026-08-26", "type": "chat" }, { - "id": "minimax/minimax-m2.7", - "name": "Minimax M2.7", - "display_name": "Minimax M2.7", + "id": "entrim/glm-5.3", + "name": "GLM-5.3 (entrim)", + "display_name": "GLM-5.3 (entrim)", "modalities": { "input": [ "text" @@ -16076,8 +17249,8 @@ ] }, "limit": { - "context": 204800, - "output": 131000 + "context": 1048576, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -16088,35 +17261,37 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" ] } }, "attachment": false, "open_weights": true, - "release_date": "2026-03-18", - "last_updated": "2026-03-18", + "release_date": "2026-08-14", + "last_updated": "2026-08-14", "type": "chat" }, { - "id": "minimax/minimax-m2.7-highspeed", - "name": "MiniMax M2.7 High Speed", - "display_name": "MiniMax M2.7 High Speed", + "id": "reka-ai/deepseek-v4.1-flash-1017", + "name": "DeepSeek V4.1 Flash 1017 (reka-ai)", + "display_name": "DeepSeek V4.1 Flash 1017 (reka-ai)", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 204800, - "output": 131100 + "context": 1048576, + "output": 393216 }, "temperature": true, "tool_call": true, @@ -16126,63 +17301,70 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "none", + "minimal", + "low", + "medium", + "high", + "xhigh", + "max" ] } }, - "attachment": false, + "attachment": true, "open_weights": true, - "release_date": "2026-03-18", - "last_updated": "2026-03-18", + "knowledge": "2025-05", + "release_date": "2026-10-17", + "last_updated": "2026-10-17", "type": "chat" }, { - "id": "minimax/minimax-h3-max", - "name": "MiniMax H3 Max", - "display_name": "MiniMax H3 Max", + "id": "reka-ai/reka-edge-2603", + "name": "Reka Edge 2603 (reka-ai)", + "display_name": "Reka Edge 2603 (reka-ai)", "modalities": { "input": [ "text", "image" ], "output": [ - "video" + "text" ] }, "limit": { - "context": 8192, - "output": 8192 + "context": 16384, + "output": 16384 }, - "temperature": false, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { "supported": false }, "attachment": true, "open_weights": false, - "release_date": "2026-08-26", - "last_updated": "2026-08-26" + "release_date": "2026-10-09", + "last_updated": "2026-10-09", + "type": "chat" }, { - "id": "minimax/minimax-m2.5-highspeed", - "name": "MiniMax M2.5 High Speed", - "display_name": "MiniMax M2.5 High Speed", + "id": "reka-ai/glm-5.3-flash", + "name": "GLM-5.3 Flash (reka-ai)", + "display_name": "GLM-5.3 Flash (reka-ai)", "modalities": { "input": [ - "text" + "text", + "image", + "video", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 204800, - "output": 131000 + "context": 1048576, + "output": 262144 }, "temperature": true, "tool_call": true, @@ -16192,30 +17374,40 @@ }, "extra_capabilities": { "reasoning": { - "supported": true + "supported": true, + "effort_options": [ + "none", + "minimal", + "low", + "medium", + "high", + "xhigh", + "max" + ] } }, - "attachment": false, + "attachment": true, "open_weights": true, - "release_date": "2026-02-13", - "last_updated": "2026-02-13", + "release_date": "2026-08-26", + "last_updated": "2026-08-26", "type": "chat" }, { - "id": "poolside/laguna-s-2.1", - "name": "Laguna S 2.1", - "display_name": "Laguna S 2.1", + "id": "reka-ai/deepseek-v4.1-flash", + "name": "DeepSeek V4.1 Flash (reka-ai)", + "display_name": "DeepSeek V4.1 Flash (reka-ai)", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 1048576, + "output": 393216 }, "temperature": true, "tool_call": true, @@ -16225,23 +17417,32 @@ }, "extra_capabilities": { "reasoning": { - "supported": true + "supported": true, + "effort_options": [ + "none", + "minimal", + "low", + "medium", + "high", + "xhigh", + "max" + ] } }, - "attachment": false, + "attachment": true, "open_weights": true, - "release_date": "2026-07-21", - "last_updated": "2026-07-21", + "knowledge": "2025-05", + "release_date": "2026-09-10", + "last_updated": "2026-09-10", "type": "chat" }, { - "id": "fireworks/ember-1", - "name": "Ember-1", - "display_name": "Ember-1", + "id": "reka-ai/glm-5.3", + "name": "GLM-5.3 (reka-ai)", + "display_name": "GLM-5.3 (reka-ai)", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" @@ -16249,7 +17450,7 @@ }, "limit": { "context": 1048576, - "output": 1048576 + "output": 262144 }, "temperature": true, "tool_call": true, @@ -16259,542 +17460,709 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, "effort_options": [ + "none", + "minimal", "low", "medium", - "high" + "high", + "xhigh", + "max" ] } }, - "attachment": true, - "open_weights": false, - "release_date": "2026-09-22", - "last_updated": "2026-09-22", + "attachment": false, + "open_weights": true, + "release_date": "2026-08-14", + "last_updated": "2026-08-14", "type": "chat" }, { - "id": "topaz/starlight-precise-2.6", - "name": "Starlight Precise 2.6", - "display_name": "Starlight Precise 2.6", + "id": "anthropic/claude-haiku-4-5-20251001", + "name": "Claude Haiku 4.5 (2025-10-01) (Anthropic)", + "display_name": "Claude Haiku 4.5 (2025-10-01) (Anthropic)", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ - "video" - ] - }, - "limit": { - "context": 8192, - "output": 8192 - }, - "temperature": false, - "tool_call": false, - "reasoning": { - "supported": false - }, - "attachment": false, - "open_weights": false, - "release_date": "2026-10-06", - "last_updated": "2026-10-06" - }, - { - "id": "topaz/proteus", - "name": "Proteus", - "display_name": "Proteus", - "modalities": { - "input": [ "text" - ], - "output": [ - "video" ] }, "limit": { - "context": 8192, - "output": 8192 + "context": 200000, + "output": 64000 }, - "temperature": false, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { - "supported": false - }, - "attachment": false, - "open_weights": false, - "release_date": "2026-10-06", - "last_updated": "2026-10-06" - }, - { - "id": "topaz/wonder-3.5", - "name": "Wonder 3.5", - "display_name": "Wonder 3.5", - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "image" - ] - }, - "limit": { - "context": 8192, - "output": 8192 + "supported": true, + "default": false }, - "temperature": false, - "tool_call": false, - "reasoning": { - "supported": false + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": false, + "mode": "budget", + "budget": { + "min": 1024, + "unit": "tokens" + }, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", + "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." + ] + } }, "attachment": true, "open_weights": false, - "release_date": "2026-10-06", - "last_updated": "2026-10-06" + "knowledge": "2025-02-28", + "release_date": "2025-10-15", + "last_updated": "2025-10-15", + "type": "chat" }, { - "id": "bfl/flux-2-klein-9b", - "name": "FLUX.2 [klein] 9B", - "display_name": "FLUX.2 [klein] 9B", + "id": "anthropic/claude-haiku-5-5", + "name": "Claude Haiku 5.5 (Anthropic)", + "display_name": "Claude Haiku 5.5 (Anthropic)", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ - "image" + "text" ] }, "limit": { - "context": 8192, - "output": 8192 + "context": 1000000, + "output": 128000 }, "temperature": false, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "none", + "low", + "medium", + "high", + "xhigh", + "max" + ] + } }, "attachment": true, "open_weights": false, - "release_date": "2026-01-15", - "last_updated": "2026-06-08" + "knowledge": "2026-06", + "release_date": "2026-10-07", + "last_updated": "2026-10-07", + "type": "chat" }, { - "id": "bfl/flux-3-image", - "name": "FLUX 3 Image", - "display_name": "FLUX 3 Image", + "id": "anthropic/claude-sonnet-4-5-20250929", + "name": "Claude Sonnet 4.5 (2025-09-29) (Anthropic)", + "display_name": "Claude Sonnet 4.5 (2025-09-29) (Anthropic)", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ - "image" + "text" ] }, "limit": { - "context": 8192, - "output": 8192 + "context": 200000, + "output": 64000 }, - "temperature": false, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": false + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": false, + "mode": "budget", + "budget": { + "min": 1024, + "unit": "tokens" + }, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", + "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." + ] + } }, "attachment": true, "open_weights": false, - "release_date": "2026-10-05", - "last_updated": "2026-10-05" + "knowledge": "2025-07-31", + "release_date": "2025-09-29", + "last_updated": "2025-09-29", + "type": "chat" }, { - "id": "bfl/flux-pro-1.0-fill", - "name": "FLUX.1 Fill [pro]", - "display_name": "FLUX.1 Fill [pro]", + "id": "anthropic/claude-opus-4-7", + "name": "Claude Opus 4.7 (Anthropic)", + "display_name": "Claude Opus 4.7 (Anthropic)", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ - "image" + "text" ] }, "limit": { - "context": 512, - "output": 8192 + "context": 1000000, + "output": 128000 }, "temperature": false, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": false + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": false, + "mode": "effort", + "effort": "high", + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "omitted", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Claude Opus 4.7 and newer Opus models require thinking.type = \"adaptive\" to enable thinking explicitly.", + "Manual budget_tokens requests return 400 on Claude Opus 4.7 and newer adaptive-only Opus models.", + "task_budget is separate from thinking control and should not be treated as a thinking budget." + ] + } }, "attachment": true, "open_weights": false, - "release_date": "2024-10-01", - "last_updated": "2024-10" + "knowledge": "2026-01-31", + "release_date": "2026-04-16", + "last_updated": "2026-04-16", + "type": "chat" }, { - "id": "bfl/flux-2-flex", - "name": "FLUX.2 [flex]", - "display_name": "FLUX.2 [flex]", + "id": "anthropic/claude-haiku-4-5", + "name": "Claude Haiku 4.5 (Anthropic)", + "display_name": "Claude Haiku 4.5 (Anthropic)", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ - "image" + "text" ] }, "limit": { - "context": 8192, - "output": 8192 + "context": 200000, + "output": 64000 }, - "temperature": false, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": false + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": false, + "mode": "budget", + "budget": { + "min": 1024, + "unit": "tokens" + }, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", + "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." + ] + } }, "attachment": true, "open_weights": false, - "release_date": "2025-11-25", - "last_updated": "2026-06-08" + "knowledge": "2025-02-28", + "release_date": "2025-10-15", + "last_updated": "2025-10-15", + "type": "chat" }, { - "id": "bfl/flux-kontext-max", - "name": "FLUX.1 Kontext Max", - "display_name": "FLUX.1 Kontext Max", + "id": "anthropic/claude-sonnet-5", + "name": "Claude Sonnet 5 (Anthropic)", + "display_name": "Claude Sonnet 5 (Anthropic)", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ - "image" + "text" ] }, "limit": { - "context": 512, - "output": 8192 + "context": 1000000, + "output": 128000 }, "temperature": false, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "effort_options": [ + "none", + "low", + "medium", + "high", + "xhigh", + "max" + ] + } }, "attachment": true, "open_weights": false, - "release_date": "2025-05-29", - "last_updated": "2025-06" + "knowledge": "2026-01-31", + "release_date": "2026-06-30", + "last_updated": "2026-06-30", + "type": "chat" }, { - "id": "bfl/flux-2-max", - "name": "FLUX.2 [max]", - "display_name": "FLUX.2 [max]", + "id": "anthropic/claude-fable-5", + "name": "Claude Fable 5 (Anthropic)", + "display_name": "Claude Fable 5 (Anthropic)", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ - "image" - ] - }, - "limit": { - "context": 67300, - "output": 67300 - }, - "temperature": false, - "tool_call": false, - "reasoning": { - "supported": false - }, - "attachment": true, - "open_weights": false, - "release_date": "2025-12-16", - "last_updated": "2026-06-08" - }, - { - "id": "bfl/flux-pro-1.1-ultra", - "name": "FLUX1.1 [pro] Ultra", - "display_name": "FLUX1.1 [pro] Ultra", - "modalities": { - "input": [ "text" - ], - "output": [ - "image" ] }, "limit": { - "context": 512, - "output": 8192 + "context": 1000000, + "output": 128000 }, "temperature": false, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": false - }, - "attachment": false, - "open_weights": false, - "release_date": "2024-11-01", - "last_updated": "2024-11" - }, - { - "id": "bfl/flux-3-image-klein", - "name": "FLUX 3 Image Klein", - "display_name": "FLUX 3 Image Klein", - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "image" - ] - }, - "limit": { - "context": 8192, - "output": 8192 + "supported": true, + "default": true }, - "temperature": false, - "tool_call": false, - "reasoning": { - "supported": false + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "high", + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "omitted", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Adaptive thinking is always on for Claude Fable 5 and Claude Mythos 5; thinking.type = \"disabled\" is rejected.", + "Manual budget_tokens requests return 400 on Claude Fable 5 and Claude Mythos 5.", + "thinking.display defaults to omitted; set display to summarized to receive readable thinking summaries." + ] + } }, "attachment": true, "open_weights": false, - "release_date": "2026-10-15", - "last_updated": "2026-10-15" - }, - { - "id": "bfl/flux-pro-1.1", - "name": "FLUX1.1 [pro]", - "display_name": "FLUX1.1 [pro]", - "modalities": { - "input": [ - "text" - ], - "output": [ - "image" - ] - }, - "limit": { - "context": 512, - "output": 8192 - }, - "temperature": false, - "tool_call": false, - "reasoning": { - "supported": false - }, - "attachment": false, - "open_weights": false, - "release_date": "2024-10-02", - "last_updated": "2024-10" + "knowledge": "2026-01-31", + "release_date": "2026-06-09", + "last_updated": "2026-06-09", + "type": "chat" }, { - "id": "bfl/flux-3-video", - "name": "Flux 3", - "display_name": "Flux 3", + "id": "anthropic/claude-sonnet-4-5", + "name": "Claude Sonnet 4.5 (Anthropic)", + "display_name": "Claude Sonnet 4.5 (Anthropic)", "modalities": { "input": [ "text", "image", - "video" + "pdf" ], "output": [ - "video" + "text" ] }, "limit": { - "context": 8192, - "output": 8192 + "context": 200000, + "output": 64000 }, - "temperature": false, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": false + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": false, + "mode": "budget", + "budget": { + "min": 1024, + "unit": "tokens" + }, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", + "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." + ] + } }, "attachment": true, "open_weights": false, - "release_date": "2026-08-04", - "last_updated": "2026-08-04" + "knowledge": "2025-07-31", + "release_date": "2025-09-29", + "last_updated": "2025-09-29", + "type": "chat" }, { - "id": "bfl/flux-2-pro", - "name": "FLUX.2 [pro]", - "display_name": "FLUX.2 [pro]", + "id": "anthropic/claude-opus-5-5", + "name": "Claude Opus 5.5 (Anthropic)", + "display_name": "Claude Opus 5.5 (Anthropic)", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ - "image" + "text" ] }, "limit": { - "context": 67300, - "output": 67300 + "context": 1000000, + "output": 128000 }, "temperature": false, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" + ] + } }, "attachment": true, "open_weights": false, - "release_date": "2025-11-25", - "last_updated": "2026-06-08" + "knowledge": "2026-06", + "release_date": "2026-09-22", + "last_updated": "2026-09-22", + "type": "chat" }, { - "id": "bfl/flux-kontext-pro", - "name": "FLUX.1 Kontext Pro", - "display_name": "FLUX.1 Kontext Pro", + "id": "anthropic/claude-sonnet-5-5", + "name": "Claude Sonnet 5.5 (Anthropic)", + "display_name": "Claude Sonnet 5.5 (Anthropic)", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ - "image" + "text" ] }, "limit": { - "context": 512, - "output": 8192 + "context": 1000000, + "output": 128000 }, "temperature": false, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" + ] + } }, "attachment": true, "open_weights": false, - "release_date": "2025-05-29", - "last_updated": "2025-06" + "knowledge": "2026-06", + "release_date": "2026-09-28", + "last_updated": "2026-09-28", + "type": "chat" }, { - "id": "bfl/flux-2-klein-4b", - "name": "FLUX.2 [klein] 4B", - "display_name": "FLUX.2 [klein] 4B", + "id": "anthropic/claude-sonnet-4-6", + "name": "Claude Sonnet 4.6 (Anthropic)", + "display_name": "Claude Sonnet 4.6 (Anthropic)", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ - "image" + "text" ] }, "limit": { - "context": 8192, - "output": 8192 + "context": 1000000, + "output": 64000 }, - "temperature": false, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": false + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": false, + "mode": "mixed", + "budget": { + "min": 1024, + "unit": "tokens" + }, + "effort": "high", + "effort_options": [ + "low", + "medium", + "high", + "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Anthropic recommends adaptive thinking with effort for Claude 4.6; budget_tokens remains a deprecated compatibility path.", + "Anthropic API defaults effort to high; lower effort levels should be chosen per workload." + ] + } }, "attachment": true, "open_weights": false, - "release_date": "2026-01-15", - "last_updated": "2026-06-08" + "knowledge": "2025-08-31", + "release_date": "2026-02-17", + "last_updated": "2026-03-13", + "type": "chat" }, { - "id": "elorian/glimpse", - "name": "Glimpse", - "display_name": "Glimpse", + "id": "anthropic/claude-opus-4-8", + "name": "Claude Opus 4.8 (Anthropic)", + "display_name": "Claude Opus 4.8 (Anthropic)", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 220000, - "output": 4096 + "context": 1000000, + "output": 128000 }, - "tool_call": false, + "temperature": false, + "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": false, + "mode": "effort", + "effort": "high", "effort_options": [ "low", "medium", - "xhigh" + "high", + "xhigh", + "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "omitted", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Claude Opus 4.7 and newer Opus models require thinking.type = \"adaptive\" to enable thinking explicitly.", + "Manual budget_tokens requests return 400 on Claude Opus 4.7 and newer adaptive-only Opus models.", + "task_budget is separate from thinking control and should not be treated as a thinking budget." ] } }, "attachment": true, "open_weights": false, - "release_date": "2026-10-16", - "last_updated": "2026-10-16", + "knowledge": "2026-01", + "release_date": "2026-05-28", + "last_updated": "2026-05-28", "type": "chat" }, { - "id": "quiverai/arrow-2", - "name": "Arrow 2", - "display_name": "Arrow 2", + "id": "anthropic/claude-opus-4-6", + "name": "Claude Opus 4.6 (Anthropic)", + "display_name": "Claude Opus 4.6 (Anthropic)", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ - "text", - "image" + "text" ] }, "limit": { - "context": 131072, - "output": 65536 + "context": 1000000, + "output": 128000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": false, + "mode": "mixed", + "budget": { + "min": 1024, + "unit": "tokens" + }, + "effort": "high", "effort_options": [ "low", "medium", "high", - "xhigh" + "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Anthropic recommends adaptive thinking with effort for Claude 4.6; budget_tokens remains a deprecated compatibility path.", + "Anthropic API defaults effort to high; lower effort levels should be chosen per workload." ] } }, "attachment": true, "open_weights": false, - "release_date": "2026-09-16", - "last_updated": "2026-09-16", + "knowledge": "2025-05-31", + "release_date": "2026-02-05", + "last_updated": "2026-03-13", "type": "chat" }, { - "id": "quiverai/arrow-2-telos", - "name": "Arrow 2 Telos", - "display_name": "Arrow 2 Telos", + "id": "anthropic/claude-fable-5-1", + "name": "Claude Fable 5.1 (Anthropic)", + "display_name": "Claude Fable 5.1 (Anthropic)", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ - "text", - "image" + "text" ] }, "limit": { - "context": 131072, - "output": 65536 + "context": 1000000, + "output": 128000 }, "temperature": false, "tool_call": true, @@ -16804,37 +18172,56 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "high", "effort_options": [ "low", "medium", "high", - "xhigh" + "xhigh", + "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "omitted", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Adaptive thinking is always on for Claude Fable 5 and Claude Mythos 5; thinking.type = \"disabled\" is rejected.", + "Manual budget_tokens requests return 400 on Claude Fable 5 and Claude Mythos 5.", + "thinking.display defaults to omitted; set display to summarized to receive readable thinking summaries." ] } }, "attachment": true, "open_weights": false, - "release_date": "2026-09-16", - "last_updated": "2026-09-16", + "knowledge": "2026-06", + "release_date": "2026-09-01", + "last_updated": "2026-09-01", "type": "chat" }, { - "id": "alibaba/qwen3.7-max", - "name": "Qwen 3.7 Max", - "display_name": "Qwen 3.7 Max", + "id": "anthropic/claude-opus-5", + "name": "Claude Opus 5 (Anthropic)", + "display_name": "Claude Opus 5 (Anthropic)", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 991000, - "output": 64000 + "context": 1000000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -16845,7 +18232,6 @@ "supported": true, "effort_options": [ "none", - "minimal", "low", "medium", "high", @@ -16854,95 +18240,90 @@ ] } }, - "attachment": false, + "attachment": true, "open_weights": false, - "release_date": "2026-05-21", - "last_updated": "2026-05-21", + "knowledge": "2026-05", + "release_date": "2026-07-24", + "last_updated": "2026-07-24", "type": "chat" }, { - "id": "alibaba/qwen3.8-max-prime", - "name": "Qwen 3.8 Max Prime", - "display_name": "Qwen 3.8 Max Prime", + "id": "anthropic/claude-opus-4-5-20251101", + "name": "Claude Opus 4.5 (Anthropic)", + "display_name": "Claude Opus 4.5 (Anthropic)", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 200000, + "output": 32000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": false, + "mode": "mixed", + "budget": { + "min": 1024, + "unit": "tokens" + }, + "effort": "high", "effort_options": [ "low", "medium", - "xhigh" + "high" + ], + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Claude Opus 4.5 uses manual thinking.type = \"enabled\" with budget_tokens; effort can be used alongside the thinking budget.", + "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header." ] } }, "attachment": true, "open_weights": false, - "release_date": "2026-09-23", - "last_updated": "2026-09-23", + "knowledge": "2025-05", + "release_date": "2025-11-24", + "last_updated": "2025-11-24", "type": "chat" }, { - "id": "alibaba/wan-v2.6-i2v-flash", - "name": "Wan v2.6 Image-to-Video Flash", - "display_name": "Wan v2.6 Image-to-Video Flash", + "id": "novita/kimi-k3", + "name": "Kimi K3 (NovitaAI)", + "display_name": "Kimi K3 (NovitaAI)", "modalities": { "input": [ "text", "image", - "audio" - ], - "output": [ "video" - ] - }, - "limit": { - "context": 8192, - "output": 8192 - }, - "temperature": false, - "tool_call": false, - "reasoning": { - "supported": false - }, - "attachment": true, - "open_weights": false, - "release_date": "2025-12-16", - "last_updated": "2025-12-16" - }, - { - "id": "alibaba/qwen-3-235b", - "name": "Qwen3 235B A22B Instruct 2507", - "display_name": "Qwen3 235B A22B Instruct 2507", - "modalities": { - "input": [ - "text" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 16384 + "context": 1048576, + "output": 1048576 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -16950,37 +18331,39 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, "effort_options": [ "none", + "minimal", "low", "medium", - "high" + "high", + "xhigh", + "max" ] } }, - "attachment": false, + "attachment": true, "open_weights": true, - "release_date": "2025-07-21", - "last_updated": "2025-07-21", + "release_date": "2026-07-16", + "last_updated": "2026-07-16", "type": "chat" }, { - "id": "alibaba/qwen3.8-max-0902", - "name": "Qwen3.8 Max 0902", - "display_name": "Qwen3.8 Max 0902", + "id": "novita/minimax-m2.5", + "name": "MiniMax M2.5 (NovitaAI)", + "display_name": "MiniMax M2.5 (NovitaAI)", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 991000, - "output": 128000 + "context": 204800, + "output": 131100 }, "temperature": true, "tool_call": true, @@ -16990,36 +18373,39 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, "effort_options": [ "low", "medium", - "xhigh" + "high", + "xhigh", + "max" ] } }, - "attachment": true, - "open_weights": false, - "release_date": "2026-09-02", - "last_updated": "2026-09-02", + "attachment": false, + "open_weights": true, + "release_date": "2026-02-12", + "last_updated": "2026-02-12", "type": "chat" }, { - "id": "alibaba/qwen3.8-27b", - "name": "Qwen3.8 27B", - "display_name": "Qwen3.8 27B", + "id": "novita/qwen35-397b-a17b", + "name": "Qwen3.5 397B A17B (NovitaAI)", + "display_name": "Qwen3.5 397B A17B (NovitaAI)", "modalities": { "input": [ "text", "image", - "pdf" + "video" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 262144, + "output": 64000 }, "temperature": true, "tool_call": true, @@ -17031,82 +18417,86 @@ "reasoning": { "effort_options": [ "none", + "minimal", "low", "medium", - "xhigh" + "high", + "xhigh", + "max" ] } }, "attachment": true, "open_weights": true, - "release_date": "2026-08-14", - "last_updated": "2026-08-14", + "release_date": "2026-02-15", + "last_updated": "2026-02-15", "type": "chat" }, { - "id": "alibaba/qwen-3-14b", - "name": "Qwen3-14B", - "display_name": "Qwen3-14B", + "id": "novita/qwen3-vl-30b-a3b-instruct", + "name": "Qwen3 VL 30B A3B Instruct (NovitaAI)", + "display_name": "Qwen3 VL 30B A3B Instruct (NovitaAI)", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 40960, - "output": 16384 + "context": 131072, + "output": 32768 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true + "supported": false }, - "attachment": false, - "open_weights": false, - "knowledge": "2025-04", - "release_date": "2025-04-28", - "last_updated": "2025-04", + "attachment": true, + "open_weights": true, + "release_date": "2025-10-04", + "last_updated": "2025-10-04", "type": "chat" }, { - "id": "alibaba/wan-v2.6-t2v", - "name": "Wan v2.6 Text-to-Video", - "display_name": "Wan v2.6 Text-to-Video", + "id": "novita/llama-4-maverick-17b-instruct", + "name": "Llama 4 Maverick 17B Instruct (NovitaAI)", + "display_name": "Llama 4 Maverick 17B Instruct (NovitaAI)", "modalities": { "input": [ "text", - "audio" + "image" ], "output": [ - "video" + "text" ] }, "limit": { - "context": 8192, + "context": 1048576, "output": 8192 }, - "temperature": false, + "temperature": true, "tool_call": false, "reasoning": { "supported": false }, "attachment": true, - "open_weights": false, - "release_date": "2025-12-16", - "last_updated": "2025-12-16" + "open_weights": true, + "knowledge": "2024-08", + "release_date": "2025-04-05", + "last_updated": "2025-04-05", + "type": "chat" }, { - "id": "alibaba/qwen3.8-omni-flash", - "name": "Qwen 3.8 Omni Flash", - "display_name": "Qwen 3.8 Omni Flash", + "id": "novita/qwen3.8-2.4t-a95b", + "name": "Qwen3.8 2.4T A95B (NovitaAI)", + "display_name": "Qwen3.8 2.4T A95B (NovitaAI)", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" @@ -17124,24 +18514,28 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, "effort_options": [ "none", + "minimal", "low", "medium", - "xhigh" + "high", + "xhigh", + "max" ] } }, - "attachment": true, - "open_weights": false, - "release_date": "2026-09-17", - "last_updated": "2026-09-17", + "attachment": false, + "open_weights": true, + "release_date": "2026-08-12", + "last_updated": "2026-08-12", "type": "chat" }, { - "id": "alibaba/qwen-3-32b", - "name": "Qwen 3.32B", - "display_name": "Qwen 3.32B", + "id": "novita/gemma-4-26b-a4b-it", + "name": "Gemma 4 26B A4B IT (NovitaAI)", + "display_name": "Gemma 4 26B A4B IT (NovitaAI)", "modalities": { "input": [ "text" @@ -17151,40 +18545,34 @@ ] }, "limit": { - "context": 128000, - "output": 8192 + "context": 262144, + "output": 32768 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true + "supported": true }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "none", - "low", - "medium", - "high" - ] + "supported": true } }, "attachment": false, "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-04-28", - "last_updated": "2025-04-28", + "release_date": "2026-04-02", + "last_updated": "2026-04-02", "type": "chat" }, { - "id": "alibaba/qwen3.8-2.4t-a95b", - "name": "Qwen3.8 2.4T A95B", - "display_name": "Qwen3.8 2.4T A95B", + "id": "novita/kimi-k2.7-code", + "name": "Kimi K2.7 Code (NovitaAI)", + "display_name": "Kimi K2.7 Code (NovitaAI)", "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ "text" @@ -17192,9 +18580,9 @@ }, "limit": { "context": 262144, - "output": 128000 + "output": 262144 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -17203,50 +18591,74 @@ "extra_capabilities": { "reasoning": { "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ "low", "medium", - "xhigh" + "high", + "xhigh", + "max" ] } }, "attachment": true, "open_weights": true, - "release_date": "2026-08-12", - "last_updated": "2026-08-12", + "knowledge": "2025-01", + "release_date": "2026-06-12", + "last_updated": "2026-06-12", "type": "chat" }, { - "id": "alibaba/wan-v2.5-t2v-preview", - "name": "Wan v2.5 Text-to-Video Preview", - "display_name": "Wan v2.5 Text-to-Video Preview", + "id": "novita/qwen3.7-max", + "name": "Qwen3.7 Max (NovitaAI)", + "display_name": "Qwen3.7 Max (NovitaAI)", "modalities": { "input": [ - "text", - "audio" + "text" ], "output": [ - "video" + "text" ] }, "limit": { - "context": 8192, - "output": 8192 + "context": 1000000, + "output": 65536 }, - "temperature": false, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": true, - "open_weights": false, - "release_date": "2025-09-24", - "last_updated": "2025-09-24" + "extra_capabilities": { + "reasoning": { + "supported": true, + "effort_options": [ + "none", + "minimal", + "low", + "medium", + "high", + "xhigh", + "max" + ] + } + }, + "attachment": false, + "open_weights": false, + "release_date": "2026-05-21", + "last_updated": "2026-05-21", + "type": "chat" }, { - "id": "alibaba/qwen3-coder-next", - "name": "Qwen3 Coder Next", - "display_name": "Qwen3 Coder Next", + "id": "novita/qwen3.8-flash", + "name": "Qwen3.8 Flash (NovitaAI)", + "display_name": "Qwen3.8 Flash (NovitaAI)", "modalities": { "input": [ "text" @@ -17256,8 +18668,8 @@ ] }, "limit": { - "context": 256000, - "output": 256000 + "context": 1000000, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -17265,57 +18677,42 @@ "supported": true, "default": true }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "effort_options": [ + "none", + "minimal", + "low", + "medium", + "high", + "xhigh", + "max" + ] + } + }, "attachment": false, - "open_weights": true, - "knowledge": "2025-09", - "release_date": "2026-02-03", - "last_updated": "2026-02-03", + "open_weights": false, + "release_date": "2026-08-26", + "last_updated": "2026-08-26", "type": "chat" }, { - "id": "alibaba/wan-v2.6-r2v", - "name": "Wan v2.6 Reference-to-Video", - "display_name": "Wan v2.6 Reference-to-Video", + "id": "novita/qwen3.6-35b-a3b", + "name": "Qwen3.6 35B A3B (NovitaAI)", + "display_name": "Qwen3.6 35B A3B (NovitaAI)", "modalities": { "input": [ "text", "image", "video" ], - "output": [ - "video" - ] - }, - "limit": { - "context": 8192, - "output": 8192 - }, - "temperature": false, - "tool_call": false, - "reasoning": { - "supported": false - }, - "attachment": true, - "open_weights": false, - "release_date": "2025-12-16", - "last_updated": "2025-12-16" - }, - { - "id": "alibaba/qwen3.5-plus", - "name": "Qwen 3.5 Plus", - "display_name": "Qwen 3.5 Plus", - "modalities": { - "input": [ - "text", - "image", - "pdf" - ], "output": [ "text" ] }, "limit": { - "context": 1000000, + "context": 262144, "output": 64000 }, "temperature": true, @@ -17345,29 +18742,27 @@ } }, "attachment": true, - "open_weights": false, - "knowledge": "2025-04", - "release_date": "2026-02-16", - "last_updated": "2026-02-16", + "open_weights": true, + "release_date": "2026-04-17", + "last_updated": "2026-04-17", "type": "chat" }, { - "id": "alibaba/qwen3.5-flash", - "name": "Qwen 3.5 Flash", - "display_name": "Qwen 3.5 Flash", + "id": "novita/deepseek-v4.1-flash-1017", + "name": "DeepSeek V4.1 Flash 1017 (NovitaAI)", + "display_name": "DeepSeek V4.1 Flash 1017 (NovitaAI)", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 64000 + "context": 1048576, + "output": 393216 }, "temperature": true, "tool_call": true, @@ -17377,13 +18772,6 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], "effort_options": [ "none", "minimal", @@ -17396,15 +18784,16 @@ } }, "attachment": true, - "open_weights": false, - "release_date": "2026-02-23", - "last_updated": "2026-02-23", + "open_weights": true, + "knowledge": "2025-05", + "release_date": "2026-10-17", + "last_updated": "2026-10-17", "type": "chat" }, { - "id": "alibaba/qwen3-next-80b-a3b-instruct", - "name": "Qwen3 Next 80B A3B Instruct", - "display_name": "Qwen3 Next 80B A3B Instruct", + "id": "novita/llama-3.3-70b-instruct", + "name": "Llama 3.3 70B Instruct (NovitaAI)", + "display_name": "Llama 3.3 70B Instruct (NovitaAI)", "modalities": { "input": [ "text" @@ -17414,8 +18803,8 @@ ] }, "limit": { - "context": 262114, - "output": 262114 + "context": 131072, + "output": 120000 }, "temperature": true, "tool_call": true, @@ -17424,82 +18813,97 @@ }, "attachment": false, "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-09", - "last_updated": "2025-09", + "knowledge": "2023-12", + "release_date": "2024-12-06", + "last_updated": "2024-12-06", "type": "chat" }, { - "id": "alibaba/wan-v2.7-r2v", - "name": "Wan v2.7 Reference-to-Video", - "display_name": "Wan v2.7 Reference-to-Video", + "id": "novita/glm-4.6v", + "name": "GLM-4.6V (NovitaAI)", + "display_name": "GLM-4.6V (NovitaAI)", "modalities": { "input": [ "text", "image", - "video", - "audio" + "video" ], "output": [ - "video" + "text" ] }, "limit": { - "context": 8192, - "output": 8192 + "context": 131072, + "output": 16000 }, - "temperature": false, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, "attachment": true, - "open_weights": false, - "release_date": "2026-04-07", - "last_updated": "2026-04-07" + "open_weights": true, + "knowledge": "2025-04", + "release_date": "2025-12-08", + "last_updated": "2025-12-08", + "type": "chat" }, { - "id": "alibaba/wan-v2.7-t2v", - "name": "Wan v2.7 Text-to-Video", - "display_name": "Wan v2.7 Text-to-Video", + "id": "novita/glm-5.3-flash", + "name": "GLM-5.3 Flash (NovitaAI)", + "display_name": "GLM-5.3 Flash (NovitaAI)", "modalities": { "input": [ "text", - "audio" + "image", + "video", + "pdf" ], "output": [ - "video" + "text" ] }, "limit": { - "context": 8192, - "output": 8192 + "context": 1048576, + "output": 131072 }, - "temperature": false, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "effort_options": [ + "low", + "high", + "max" + ] + } }, "attachment": true, - "open_weights": false, - "release_date": "2026-04-07", - "last_updated": "2026-04-07" + "open_weights": true, + "release_date": "2026-08-26", + "last_updated": "2026-08-26", + "type": "chat" }, { - "id": "alibaba/qwen3.8-max", - "name": "Qwen 3.8 Max", - "display_name": "Qwen 3.8 Max", + "id": "novita/glm-4.7", + "name": "GLM-4.7 (NovitaAI)", + "display_name": "GLM-4.7 (NovitaAI)", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 262144, + "context": 204800, "output": 128000 }, "temperature": true, @@ -17511,52 +18915,59 @@ "extra_capabilities": { "reasoning": { "supported": true, - "effort_options": [ - "low", - "medium", - "xhigh" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, - "attachment": true, - "open_weights": false, - "release_date": "2026-07-19", - "last_updated": "2026-07-19", + "attachment": false, + "open_weights": true, + "knowledge": "2025-04", + "release_date": "2025-12-22", + "last_updated": "2025-12-22", "type": "chat" }, { - "id": "alibaba/wan-v3.0-video", - "name": "Wan v3.0 Video", - "display_name": "Wan v3.0 Video", + "id": "novita/glm-4.6", + "name": "GLM-4.6 (NovitaAI)", + "display_name": "GLM-4.6 (NovitaAI)", "modalities": { "input": [ - "text", - "image", - "video", - "audio" + "text" ], "output": [ - "video" + "text" ] }, "limit": { - "context": 8192, - "output": 8192 + "context": 204800, + "output": 131072 }, - "temperature": false, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": true, - "open_weights": false, - "release_date": "2026-08-23", - "last_updated": "2026-08-23" + "extra_capabilities": { + "reasoning": { + "supported": true + } + }, + "attachment": false, + "open_weights": true, + "knowledge": "2025-04", + "release_date": "2025-09-30", + "last_updated": "2025-09-30", + "type": "chat" }, { - "id": "alibaba/qwen3-next-80b-a3b-thinking", - "name": "Qwen3 Next 80B A3B Thinking", - "display_name": "Qwen3 Next 80B A3B Thinking", + "id": "novita/hy3", + "name": "Hy3 (NovitaAI)", + "display_name": "Hy3 (NovitaAI)", "modalities": { "input": [ "text" @@ -17578,38 +18989,35 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "none", + "low", + "high" ] } }, "attachment": false, "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-09", - "last_updated": "2025-09", + "release_date": "2026-07-06", + "last_updated": "2026-07-06", "type": "chat" }, { - "id": "alibaba/qwen3.6-plus", - "name": "Qwen 3.6 Plus", - "display_name": "Qwen 3.6 Plus", + "id": "novita/deepseek-v4.1-flash", + "name": "DeepSeek V4.1 Flash (NovitaAI)", + "display_name": "DeepSeek V4.1 Flash (NovitaAI)", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 64000 + "context": 1048576, + "output": 393216 }, "temperature": true, "tool_call": true, @@ -17620,12 +19028,6 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], "effort_options": [ "none", "minimal", @@ -17638,16 +19040,16 @@ } }, "attachment": true, - "open_weights": false, - "knowledge": "2025-04", - "release_date": "2026-04-02", - "last_updated": "2026-04-02", + "open_weights": true, + "knowledge": "2025-05", + "release_date": "2026-09-10", + "last_updated": "2026-09-10", "type": "chat" }, { - "id": "alibaba/qwen3-coder-30b-a3b", - "name": "Qwen 3 Coder 30B A3B Instruct", - "display_name": "Qwen 3 Coder 30B A3B Instruct", + "id": "novita/qwen3.8-27b", + "name": "Qwen3.8 27B (NovitaAI)", + "display_name": "Qwen3.8 27B (NovitaAI)", "modalities": { "input": [ "text" @@ -17657,8 +19059,8 @@ ] }, "limit": { - "context": 262144, - "output": 8192 + "context": 1000000, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -17668,42 +19070,14 @@ }, "attachment": false, "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-07-31", - "last_updated": "2025-07-31", + "release_date": "2026-08-14", + "last_updated": "2026-08-14", "type": "chat" }, { - "id": "alibaba/qwen3-embedding-0.6b", - "name": "Qwen3 Embedding 0.6B", - "display_name": "Qwen3 Embedding 0.6B", - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 32768, - "output": 8192 - }, - "temperature": false, - "tool_call": false, - "reasoning": { - "supported": false - }, - "attachment": false, - "open_weights": true, - "release_date": "2025-06-05", - "last_updated": "2025-06-05", - "type": "embedding" - }, - { - "id": "alibaba/qwen3-coder", - "name": "Qwen3 Coder 480B A35B Instruct", - "display_name": "Qwen3 Coder 480B A35B Instruct", + "id": "novita/glm-5.3", + "name": "GLM-5.3 (NovitaAI)", + "display_name": "GLM-5.3 (NovitaAI)", "modalities": { "input": [ "text" @@ -17713,8 +19087,8 @@ ] }, "limit": { - "context": 262144, - "output": 32768 + "context": 1048576, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -17722,59 +19096,68 @@ "supported": true, "default": true }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "effort_options": [ + "low", + "high", + "max" + ] + } + }, "attachment": false, "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-07-22", - "last_updated": "2025-07-22", + "release_date": "2026-08-14", + "last_updated": "2026-08-14", "type": "chat" }, { - "id": "alibaba/qwen3-vl-instruct", - "name": "Qwen3 VL Instruct", - "display_name": "Qwen3 VL Instruct", + "id": "novita/glm-4.5v", + "name": "GLM-4.5V (NovitaAI)", + "display_name": "GLM-4.5V (NovitaAI)", "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 65536, + "output": 16000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, "attachment": true, "open_weights": true, "knowledge": "2025-04", - "release_date": "2025-09-23", - "last_updated": "2025-09-24", + "release_date": "2025-08-11", + "last_updated": "2025-08-11", "type": "chat" }, { - "id": "alibaba/qwen3.8-flash", - "name": "Qwen 3.8 Flash", - "display_name": "Qwen 3.8 Flash", + "id": "novita/glm-5", + "name": "GLM-5 (NovitaAI)", + "display_name": "GLM-5 (NovitaAI)", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 991000, - "output": 128000 + "context": 202800, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -17785,23 +19168,33 @@ "extra_capabilities": { "reasoning": { "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ + "none", + "minimal", "low", "medium", - "xhigh" + "high", + "xhigh", + "max" ] } }, - "attachment": true, - "open_weights": false, - "release_date": "2026-08-26", - "last_updated": "2026-08-26", + "attachment": false, + "open_weights": true, + "release_date": "2026-02-12", + "last_updated": "2026-02-12", "type": "chat" }, { - "id": "alibaba/qwen-3-30b", - "name": "Qwen3-30B-A3B", - "display_name": "Qwen3-30B-A3B", + "id": "novita/deepseek-v4-flash", + "name": "DeepSeek V4 Flash (NovitaAI)", + "display_name": "DeepSeek V4 Flash (NovitaAI)", "modalities": { "input": [ "text" @@ -17811,8 +19204,8 @@ ] }, "limit": { - "context": 40960, - "output": 16384 + "context": 1050000, + "output": 393216 }, "temperature": true, "tool_call": true, @@ -17820,29 +19213,50 @@ "supported": true, "default": true }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "high", + "effort_options": [ + "low", + "high", + "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." + ] + } + }, "attachment": false, "open_weights": true, - "release_date": "2025-04-28", - "last_updated": "2025-04-28", + "knowledge": "2025-05", + "release_date": "2026-04-24", + "last_updated": "2026-04-24", "type": "chat" }, { - "id": "alibaba/qwen3.7-flash", - "name": "Qwen 3.7 Flash", - "display_name": "Qwen 3.7 Flash", + "id": "novita/glm-5.1", + "name": "GLM-5.1 (NovitaAI)", + "display_name": "GLM-5.1 (NovitaAI)", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 991000, - "output": 64000 + "context": 204800, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -17853,6 +19267,12 @@ "extra_capabilities": { "reasoning": { "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ "none", "minimal", @@ -17864,141 +19284,105 @@ ] } }, - "attachment": true, - "open_weights": false, - "release_date": "2026-07-15", - "last_updated": "2026-07-15", + "attachment": false, + "open_weights": true, + "release_date": "2026-04-07", + "last_updated": "2026-04-07", "type": "chat" }, { - "id": "alibaba/wan-v3.0-video-prime", - "name": "Wan v3.0 Video Prime", - "display_name": "Wan v3.0 Video Prime", + "id": "novita/llama-4-scout-17b-instruct", + "name": "Llama 4 Scout 17B Instruct (NovitaAI)", + "display_name": "Llama 4 Scout 17B Instruct (NovitaAI)", "modalities": { "input": [ "text", - "image", - "video", - "audio" - ], - "output": [ - "video" - ] - }, - "limit": { - "context": 8192, - "output": 8192 - }, - "temperature": false, - "tool_call": false, - "reasoning": { - "supported": false - }, - "attachment": true, - "open_weights": false, - "release_date": "2026-08-28", - "last_updated": "2026-08-28" - }, - { - "id": "alibaba/qwen3-embedding-8b", - "name": "Qwen3 Embedding 8B", - "display_name": "Qwen3 Embedding 8B", - "modalities": { - "input": [ - "text" + "image" ], "output": [ "text" ] }, "limit": { - "context": 32768, - "output": 8192 + "context": 131072, + "output": 131072 }, - "temperature": false, + "temperature": true, "tool_call": false, "reasoning": { "supported": false }, - "attachment": false, + "attachment": true, "open_weights": true, - "release_date": "2025-06-05", - "last_updated": "2025-06-05", - "type": "embedding" + "knowledge": "2024-08", + "release_date": "2025-04-05", + "last_updated": "2025-04-05", + "type": "chat" }, { - "id": "alibaba/qwen3.6-27b", - "name": "Qwen 3.6 27B", - "display_name": "Qwen 3.6 27B", + "id": "novita/gemma-4-31b-it", + "name": "Gemma 4 31B IT (NovitaAI)", + "display_name": "Gemma 4 31B IT (NovitaAI)", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 256000, - "output": 65536 + "context": 262144, + "output": 32768 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true + "supported": true }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] + "supported": true } }, - "attachment": true, + "attachment": false, "open_weights": true, - "release_date": "2026-04-22", - "last_updated": "2026-04-22", + "knowledge": "2025-01", + "release_date": "2026-04-02", + "last_updated": "2026-04-02", "type": "chat" }, { - "id": "alibaba/wan-v2.6-i2v", - "name": "Wan v2.6 Image-to-Video", - "display_name": "Wan v2.6 Image-to-Video", + "id": "novita/kimi-k2", + "name": "Kimi K2 (NovitaAI)", + "display_name": "Kimi K2 (NovitaAI)", "modalities": { "input": [ - "text", - "image", - "audio" + "text" ], "output": [ - "video" + "text" ] }, "limit": { - "context": 8192, - "output": 8192 + "context": 131072, + "output": 131072 }, - "temperature": false, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { "supported": false }, - "attachment": true, + "attachment": false, "open_weights": false, - "release_date": "2025-12-16", - "last_updated": "2025-12-16" + "release_date": "2025-07-11", + "last_updated": "2025-07-11", + "type": "chat" }, { - "id": "alibaba/qwen3-max-thinking", - "name": "Qwen 3 Max Thinking", - "display_name": "Qwen 3 Max Thinking", + "id": "novita/glm-5.2", + "name": "GLM-5.2 (NovitaAI)", + "display_name": "GLM-5.2 (NovitaAI)", "modalities": { "input": [ "text" @@ -18008,8 +19392,8 @@ ] }, "limit": { - "context": 256000, - "output": 65536 + "context": 1048576, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -18025,61 +19409,82 @@ "visibility": "summary", "continuation": [ "thinking_blocks" + ], + "effort_options": [ + "none", + "minimal", + "low", + "medium", + "high", + "xhigh", + "max" ] } }, "attachment": false, "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-01-23", - "last_updated": "2025-01", + "release_date": "2026-06-13", + "last_updated": "2026-06-13", "type": "chat" }, { - "id": "alibaba/wan-v2.6-r2v-flash", - "name": "Wan v2.6 Reference-to-Video Flash", - "display_name": "Wan v2.6 Reference-to-Video Flash", + "id": "novita/qwen3.8-max", + "name": "Qwen3.8 Max (NovitaAI)", + "display_name": "Qwen3.8 Max (NovitaAI)", "modalities": { "input": [ "text", "image", - "video" + "video", + "pdf" ], "output": [ - "video" + "text" ] }, "limit": { - "context": 8192, - "output": 8192 + "context": 1000000, + "output": 131072 }, - "temperature": false, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "effort_options": [ + "low", + "medium", + "xhigh" + ] + } }, "attachment": true, "open_weights": false, - "release_date": "2025-12-16", - "last_updated": "2025-12-16" + "release_date": "2026-08-03", + "last_updated": "2026-08-03", + "type": "chat" }, { - "id": "alibaba/qwen3.7-plus", - "name": "Qwen 3.7 Plus", - "display_name": "Qwen 3.7 Plus", + "id": "novita/qwen3.5-122b-a10b", + "name": "Qwen3.5 122B A10B (NovitaAI)", + "display_name": "Qwen3.5 122B A10B (NovitaAI)", "modalities": { "input": [ "text", "image", - "pdf" + "video" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 64000 + "context": 262144, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -18090,6 +19495,12 @@ "extra_capabilities": { "reasoning": { "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ "none", "minimal", @@ -18102,43 +19513,58 @@ } }, "attachment": true, - "open_weights": false, - "knowledge": "2025-04", - "release_date": "2026-06-02", - "last_updated": "2026-06-02", + "open_weights": true, + "release_date": "2026-02-23", + "last_updated": "2026-02-23", "type": "chat" }, { - "id": "alibaba/qwen3-embedding-4b", - "name": "Qwen3 Embedding 4B", - "display_name": "Qwen3 Embedding 4B", + "id": "novita/step-3.7-flash", + "name": "Step 3.7 Flash (NovitaAI)", + "display_name": "Step 3.7 Flash (NovitaAI)", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 32768, - "output": 8192 + "context": 262144, + "output": 256000 }, - "temperature": false, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "minimal", + "low", + "medium", + "high", + "xhigh", + "max" + ] + } + }, + "attachment": true, "open_weights": true, - "release_date": "2025-06-05", - "last_updated": "2025-06-05", - "type": "embedding" + "knowledge": "2026-03-01", + "release_date": "2026-05-29", + "last_updated": "2026-05-29", + "type": "chat" }, { - "id": "zai/glm-4.7-flashx", - "name": "GLM 4.7 FlashX", - "display_name": "GLM 4.7 FlashX", + "id": "novita/minimax-m2.7", + "name": "MiniMax M2.7 (NovitaAI)", + "display_name": "MiniMax M2.7 (NovitaAI)", "modalities": { "input": [ "text" @@ -18148,8 +19574,8 @@ ] }, "limit": { - "context": 200000, - "output": 128000 + "context": 204800, + "output": 131100 }, "temperature": true, "tool_call": true, @@ -18161,36 +19587,43 @@ "reasoning": { "supported": true, "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ - "none", "low", "medium", - "high" + "high", + "xhigh", + "max" ] } }, "attachment": false, "open_weights": true, - "knowledge": "2025-04", - "release_date": "2026-01-19", - "last_updated": "2026-01-19", + "release_date": "2026-03-18", + "last_updated": "2026-03-18", "type": "chat" }, { - "id": "zai/glm-5.3-fast", - "name": "GLM 5.3 Fast", - "display_name": "GLM 5.3 Fast", + "id": "novita/minimax-m3.1-flash", + "name": "MiniMax M3.1 Flash (NovitaAI)", + "display_name": "MiniMax M3.1 Flash (NovitaAI)", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 262144 + "context": 1000000, + "output": 524288 }, "temperature": true, "tool_call": true, @@ -18201,35 +19634,35 @@ "extra_capabilities": { "reasoning": { "effort_options": [ - "none", "low", "medium", - "high" + "high", + "xhigh", + "max" ] } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-08-14", - "last_updated": "2026-08-14", + "attachment": true, + "open_weights": false, + "release_date": "2026-10-19", + "last_updated": "2026-10-19", "type": "chat" }, { - "id": "zai/glm-4.5v", - "name": "GLM 4.5V", - "display_name": "GLM 4.5V", + "id": "novita/ling-3.0-flash", + "name": "InclusionAI Ling 3.0 Flash (NovitaAI)", + "display_name": "InclusionAI Ling 3.0 Flash (NovitaAI)", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 66000, - "output": 16000 + "context": 262144, + "output": 32768 }, "temperature": true, "tool_call": true, @@ -18237,29 +19670,36 @@ "supported": true, "default": true }, - "attachment": true, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "none" + ] + } + }, + "attachment": false, "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-08-11", - "last_updated": "2025-08-11", + "release_date": "2026-07-23", + "last_updated": "2026-07-23", "type": "chat" }, { - "id": "zai/glm-5.3-flash", - "name": "GLM 5.3 Flash", - "display_name": "GLM 5.3 Flash", + "id": "novita/kimi-k2.6", + "name": "Kimi K2.6 (NovitaAI)", + "display_name": "Kimi K2.6 (NovitaAI)", "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 131000 + "context": 262144, + "output": 262144 }, "temperature": true, "tool_call": true, @@ -18270,38 +19710,48 @@ "extra_capabilities": { "reasoning": { "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ + "none", + "minimal", "low", + "medium", "high", + "xhigh", "max" ] } }, "attachment": true, "open_weights": true, - "release_date": "2026-08-26", - "last_updated": "2026-08-26", + "knowledge": "2025-01", + "release_date": "2026-04-20", + "last_updated": "2026-04-20", "type": "chat" }, { - "id": "zai/glm-5v-turbo", - "name": "GLM 5V Turbo", - "display_name": "GLM 5V Turbo", + "id": "azure/gpt-5-pro", + "name": "GPT-5 Pro (Azure)", + "display_name": "GPT-5 Pro (Azure)", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 200000, + "context": 400000, "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -18309,76 +19759,97 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "none", + "supported": true, + "default_enabled": true, + "mode": "fixed", + "effort": "high", + "verbosity": "medium", + "verbosity_options": [ "low", "medium", "high" - ] + ], + "visibility": "hidden" } }, "attachment": true, "open_weights": false, - "release_date": "2026-04-01", - "last_updated": "2026-04-01", + "knowledge": "2024-09-30", + "release_date": "2025-10-06", + "last_updated": "2025-10-06", "type": "chat" }, { - "id": "zai/glm-5-turbo", - "name": "GLM 5 Turbo", - "display_name": "GLM 5 Turbo", + "id": "azure/gpt-5.4-nano", + "name": "GPT-5.4 Nano (Azure)", + "display_name": "GPT-5.4 Nano (Azure)", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 202800, - "output": 131072 + "context": 400000, + "output": 128000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": false, + "mode": "effort", + "effort": "none", "effort_options": [ "none", + "low", + "medium", + "high", + "xhigh" + ], + "verbosity": "medium", + "verbosity_options": [ "low", "medium", "high" - ] + ], + "visibility": "hidden" } }, - "attachment": false, + "attachment": true, "open_weights": false, - "release_date": "2026-03-16", - "last_updated": "2026-03-16", + "knowledge": "2025-08-31", + "release_date": "2026-03-17", + "last_updated": "2026-03-17", "type": "chat" }, { - "id": "zai/glm-4.7", - "name": "GLM 4.7", - "display_name": "GLM 4.7", + "id": "azure/gpt-6.1-terra", + "name": "GPT-6.1 Terra (Azure)", + "display_name": "GPT-6.1 Terra (Azure)", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 120000 + "context": 1050000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -18386,148 +19857,147 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], "effort_options": [ "none", "low", "medium", - "high" + "high", + "xhigh", + "max" ] } }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-12-22", - "last_updated": "2025-12-22", + "attachment": true, + "open_weights": false, + "knowledge": "2026-06-01", + "release_date": "2026-10-26", + "last_updated": "2026-10-26", "type": "chat" }, { - "id": "zai/glm-5.2-fast", - "name": "GLM 5.2 Fast", - "display_name": "GLM 5.2 Fast", + "id": "azure/gpt-5.4", + "name": "GPT-5.4 (Azure)", + "display_name": "GPT-5.4 (Azure)", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1000000, + "context": 1050000, "output": 128000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": false, + "mode": "effort", + "effort": "none", "effort_options": [ "none", + "low", + "medium", + "high", + "xhigh" + ], + "verbosity": "medium", + "verbosity_options": [ "low", "medium", "high" - ] + ], + "visibility": "hidden" } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-06-13", - "last_updated": "2026-06-13", + "attachment": true, + "open_weights": false, + "knowledge": "2025-08-31", + "release_date": "2026-03-05", + "last_updated": "2026-03-05", "type": "chat" }, { - "id": "zai/glm-4.6", - "name": "GLM 4.6", - "display_name": "GLM 4.6", + "id": "azure/gpt-5.4-mini", + "name": "GPT-5.4 Mini (Azure)", + "display_name": "GPT-5.4 Mini (Azure)", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 96000 + "context": 400000, + "output": 128000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true + "default_enabled": false, + "mode": "effort", + "effort": "none", + "effort_options": [ + "none", + "low", + "medium", + "high", + "xhigh" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-09-30", - "last_updated": "2025-09-30", - "type": "chat" - }, - { - "id": "zai/glm-4.5-air", - "name": "GLM 4.5 Air", - "display_name": "GLM 4.5 Air", - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 96000 - }, - "temperature": true, - "tool_call": true, - "reasoning": { - "supported": true, - "default": true - }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-07-28", - "last_updated": "2025-07-28", + "attachment": true, + "open_weights": false, + "knowledge": "2025-08-31", + "release_date": "2026-03-17", + "last_updated": "2026-03-17", "type": "chat" }, { - "id": "zai/glm-5.3-flashx", - "name": "GLM 5.3 FlashX", - "display_name": "GLM 5.3 FlashX", + "id": "azure/gpt-6-sol", + "name": "GPT-6 Sol (Azure)", + "display_name": "GPT-6 Sol (Azure)", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 1050000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -18536,35 +20006,41 @@ "extra_capabilities": { "reasoning": { "effort_options": [ + "none", "low", + "medium", "high", + "xhigh", "max" ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-08-26", - "last_updated": "2026-08-26", + "open_weights": false, + "knowledge": "2026-04-20", + "release_date": "2026-09-22", + "last_updated": "2026-09-22", "type": "chat" }, { - "id": "zai/glm-5.1", - "name": "GLM 5.1", - "display_name": "GLM 5.1", + "id": "azure/gpt-5.6-terra", + "name": "GPT-5.6 Terra (Azure)", + "display_name": "GPT-5.6 Terra (Azure)", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 202800, - "output": 64000 + "context": 1050000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -18573,35 +20049,50 @@ "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", "effort_options": [ "none", + "low", + "medium", + "high", + "xhigh", + "max" + ], + "verbosity": "medium", + "verbosity_options": [ "low", "medium", "high" - ] + ], + "visibility": "hidden" } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-04-07", - "last_updated": "2026-04-07", + "attachment": true, + "open_weights": false, + "knowledge": "2026-02-16", + "release_date": "2026-07-09", + "last_updated": "2026-07-09", "type": "chat" }, { - "id": "zai/glm-5", - "name": "GLM-5", - "display_name": "GLM-5", + "id": "azure/gpt-5.3-codex", + "name": "GPT-5.3 Codex (Azure)", + "display_name": "GPT-5.3 Codex (Azure)", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 202800, - "output": 131100 + "context": 400000, + "output": 128000 }, "temperature": true, "tool_call": true, @@ -18612,43 +20103,50 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], + "default_enabled": true, + "mode": "effort", + "effort": "medium", "effort_options": [ - "none", + "low", + "medium", + "high", + "xhigh" + ], + "verbosity": "medium", + "verbosity_options": [ "low", "medium", "high" - ] + ], + "visibility": "hidden" } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-02-12", - "last_updated": "2026-02-12", + "attachment": true, + "open_weights": false, + "knowledge": "2025-08-31", + "release_date": "2026-02-05", + "last_updated": "2026-02-05", "type": "chat" }, { - "id": "zai/glm-5.3", - "name": "GLM 5.3", - "display_name": "GLM 5.3", + "id": "azure/gpt-6-astra", + "name": "GPT-6 Astra (Azure)", + "display_name": "GPT-6 Astra (Azure)", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 1000000 + "context": 1050000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -18659,69 +20157,90 @@ "supported": true, "effort_options": [ "low", + "medium", "high", + "xhigh", "max" ] } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-08-14", - "last_updated": "2026-08-14", + "attachment": true, + "open_weights": false, + "knowledge": "2026-04-30", + "release_date": "2026-09-04", + "last_updated": "2026-09-04", "type": "chat" }, { - "id": "zai/glm-4.5", - "name": "GLM 4.5", - "display_name": "GLM 4.5", + "id": "azure/gpt-5.1-codex", + "name": "GPT-5.1 Codex (Azure)", + "display_name": "GPT-5.1 Codex (Azure)", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 96000 + "context": 400000, + "output": 272000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true + "default_enabled": false, + "mode": "effort", + "effort": "none", + "effort_options": [ + "none", + "low", + "medium", + "high" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-07-28", - "last_updated": "2025-07-28", + "attachment": true, + "open_weights": false, + "knowledge": "2024-09-30", + "release_date": "2025-11-13", + "last_updated": "2025-11-13", "type": "chat" }, { - "id": "zai/glm-4.7-flash", - "name": "GLM 4.7 Flash", - "display_name": "GLM 4.7 Flash", + "id": "azure/gpt-5.2-codex", + "name": "GPT-5.2 Codex (Azure)", + "display_name": "GPT-5.2 Codex (Azure)", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 131000 + "context": 400000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -18730,77 +20249,101 @@ "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", "effort_options": [ - "none", + "low", + "medium", + "high", + "xhigh" + ], + "verbosity": "medium", + "verbosity_options": [ "low", "medium", "high" - ] + ], + "visibility": "hidden" } }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-04", - "release_date": "2026-01-19", - "last_updated": "2026-01-19", + "attachment": true, + "open_weights": false, + "knowledge": "2025-08-31", + "release_date": "2025-12-11", + "last_updated": "2025-12-11", "type": "chat" }, { - "id": "zai/glm-5.2", - "name": "GLM 5.2", - "display_name": "GLM 5.2", + "id": "azure/gpt-5.2", + "name": "GPT-5.2 (Azure)", + "display_name": "GPT-5.2 (Azure)", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, + "context": 400000, "output": 128000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": false, + "mode": "effort", + "effort": "none", "effort_options": [ "none", + "low", + "medium", "high", - "max" - ] + "xhigh" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-06-13", - "last_updated": "2026-06-13", + "attachment": true, + "open_weights": false, + "knowledge": "2025-08-31", + "release_date": "2025-12-11", + "last_updated": "2025-12-11", "type": "chat" }, { - "id": "spacexai/grok-4.20-multi-agent", - "name": "Grok 4.20 Multi-Agent", - "display_name": "Grok 4.20 Multi-Agent", + "id": "azure/gpt-5.4-pro", + "name": "GPT-5.4 Pro (Azure)", + "display_name": "GPT-5.4 Pro (Azure)", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 2000000, - "output": 2000000 + "context": 1050000, + "output": 128000 }, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -18808,50 +20351,73 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "high", "effort_options": [ - "none", + "medium", + "high", + "xhigh" + ], + "verbosity": "medium", + "verbosity_options": [ "low", "medium", "high" - ] + ], + "visibility": "hidden" } }, "attachment": true, "open_weights": false, - "release_date": "2026-03-10", - "last_updated": "2026-03-10", + "knowledge": "2025-08-31", + "release_date": "2026-03-05", + "last_updated": "2026-03-05", "type": "chat" }, { - "id": "spacexai/grok-stt", - "name": "Grok STT", - "display_name": "Grok STT", + "id": "azure/grok-4-3", + "name": "Grok 4.3 (Azure)", + "display_name": "Grok 4.3 (Azure)", "modalities": { "input": [ - "audio" + "text" ], "output": [ "text" ] }, "limit": { - "context": 8192, + "context": 200000, "output": 8192 }, - "temperature": false, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": true, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "none", + "low", + "medium", + "high" + ] + } + }, + "attachment": false, "open_weights": false, - "release_date": "2026-03-16", - "last_updated": "2026-03-16" + "release_date": "2026-04-30", + "last_updated": "2026-04-30", + "type": "chat" }, { - "id": "spacexai/grok-4.20-non-reasoning-beta", - "name": "Grok 4.20 Beta Non-Reasoning", - "display_name": "Grok 4.20 Beta Non-Reasoning", + "id": "azure/gpt-6.1-sol", + "name": "GPT-6.1 Sol (Azure)", + "display_name": "GPT-6.1 Sol (Azure)", "modalities": { "input": [ "text", @@ -18863,38 +20429,50 @@ ] }, "limit": { - "context": 2000000, - "output": 2000000 + "context": 1050000, + "output": 128000 }, + "temperature": false, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" + ] + } }, "attachment": true, "open_weights": false, - "release_date": "2026-03-11", - "last_updated": "2026-03-11", + "knowledge": "2026-04-30", + "release_date": "2026-09-29", + "last_updated": "2026-09-29", "type": "chat" }, { - "id": "spacexai/grok-4.20-reasoning", - "name": "Grok 4.20 Reasoning", - "display_name": "Grok 4.20 Reasoning", + "id": "azure/gpt-5-nano", + "name": "GPT-5 Nano (Azure)", + "display_name": "GPT-5 Nano (Azure)", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 2000000, - "output": 2000000 + "context": 400000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -18902,24 +20480,36 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", "effort_options": [ - "none", + "minimal", "low", "medium", "high" - ] + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, - "attachment": true, + "attachment": false, "open_weights": false, - "release_date": "2026-03-09", - "last_updated": "2026-03-09", + "knowledge": "2024-05-30", + "release_date": "2025-08-07", + "last_updated": "2025-08-07", "type": "chat" }, { - "id": "spacexai/grok-4.20-non-reasoning", - "name": "Grok 4.20 Non-Reasoning", - "display_name": "Grok 4.20 Non-Reasoning", + "id": "azure/gpt-5.6-luna", + "name": "GPT-5.6 Luna (Azure)", + "display_name": "GPT-5.6 Luna (Azure)", "modalities": { "input": [ "text", @@ -18931,92 +20521,162 @@ ] }, "limit": { - "context": 2000000, - "output": 2000000 + "context": 1050000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", + "effort_options": [ + "none", + "low", + "medium", + "high", + "xhigh", + "max" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" + } }, "attachment": true, "open_weights": false, - "release_date": "2026-03-09", - "last_updated": "2026-03-09", + "knowledge": "2026-02-16", + "release_date": "2026-07-09", + "last_updated": "2026-07-09", "type": "chat" }, { - "id": "spacexai/grok-voice-think-fast-2.0", - "name": "Grok Voice Think Fast 2.0", - "display_name": "Grok Voice Think Fast 2.0", + "id": "azure/gpt-5", + "name": "GPT-5 (Azure)", + "display_name": "GPT-5 (Azure)", "modalities": { "input": [ "text", - "audio" + "image" ], "output": [ - "text", - "audio" + "text" ] }, "limit": { - "context": 8192, - "output": 8192 + "context": 400000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", + "effort_options": [ + "minimal", + "low", + "medium", + "high" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" + } }, "attachment": true, "open_weights": false, - "release_date": "2026-07-29", - "last_updated": "2026-07-29" + "knowledge": "2024-09-30", + "release_date": "2025-08-07", + "last_updated": "2025-08-07", + "type": "chat" }, { - "id": "spacexai/grok-imagine-image", - "name": "Grok Imagine Image", - "display_name": "Grok Imagine Image", + "id": "azure/gpt-5-mini", + "name": "GPT-5 Mini (Azure)", + "display_name": "GPT-5 Mini (Azure)", "modalities": { "input": [ "text", "image" ], "output": [ - "image" + "text" ] }, "limit": { - "context": 16000, - "output": 8192 + "context": 400000, + "output": 128000 }, "temperature": false, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", + "effort_options": [ + "minimal", + "low", + "medium", + "high" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" + } }, "attachment": true, "open_weights": false, - "release_date": "2026-01-28", - "last_updated": "2026-01-28" + "knowledge": "2024-05-30", + "release_date": "2025-08-07", + "last_updated": "2025-08-07", + "type": "chat" }, { - "id": "spacexai/grok-4.5", - "name": "Grok 4.5", - "display_name": "Grok 4.5", + "id": "azure/gpt-oss-120b", + "name": "GPT OSS 120B (Azure)", + "display_name": "GPT OSS 120B (Azure)", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 500000, - "output": 500000 + "context": 131072, + "output": 32768 }, "temperature": true, "tool_call": true, @@ -19034,43 +20694,47 @@ ] } }, - "attachment": true, - "open_weights": false, - "release_date": "2026-07-08", - "last_updated": "2026-07-08", + "attachment": false, + "open_weights": true, + "knowledge": "2024-06", + "release_date": "2025-08-05", + "last_updated": "2025-08-05", "type": "chat" }, { - "id": "spacexai/grok-imagine-image-2.0", - "name": "Grok Imagine Image 2.0", - "display_name": "Grok Imagine Image 2.0", + "id": "azure/gpt-4o", + "name": "GPT-4o (Azure)", + "display_name": "GPT-4o (Azure)", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ - "image" + "text" ] }, "limit": { - "context": 64000, - "output": 8192 + "context": 128000, + "output": 16384 }, - "temperature": false, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { "supported": false }, "attachment": true, "open_weights": false, - "release_date": "2026-08-07", - "last_updated": "2026-08-07" + "knowledge": "2023-09", + "release_date": "2024-05-13", + "last_updated": "2024-08-06", + "type": "chat" }, { - "id": "spacexai/grok-4.20-multi-agent-beta", - "name": "Grok 4.20 Multi Agent Beta", - "display_name": "Grok 4.20 Multi Agent Beta", + "id": "azure/gpt-5.6-sol", + "name": "GPT-5.6 Sol (Azure)", + "display_name": "GPT-5.6 Sol (Azure)", "modalities": { "input": [ "text", @@ -19082,9 +20746,10 @@ ] }, "limit": { - "context": 2000000, - "output": 2000000 + "context": 1050000, + "output": 128000 }, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -19092,78 +20757,89 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", "effort_options": [ "none", + "low", + "medium", + "high", + "xhigh", + "max" + ], + "verbosity": "medium", + "verbosity_options": [ "low", "medium", "high" - ] + ], + "visibility": "hidden" } }, "attachment": true, "open_weights": false, - "release_date": "2026-03-11", - "last_updated": "2026-03-11", + "knowledge": "2026-02-16", + "release_date": "2026-07-09", + "last_updated": "2026-07-09", "type": "chat" }, { - "id": "spacexai/grok-imagine-video-1.5-lite", - "name": "Grok Imagine Video 1.5 Lite", - "display_name": "Grok Imagine Video 1.5 Lite", - "modalities": { - "input": [ - "text" - ], - "output": [ - "video" - ] - }, - "limit": { - "context": 1024, - "output": 8192 - }, - "temperature": false, - "tool_call": false, - "reasoning": { - "supported": false - }, - "attachment": false, - "open_weights": false, - "release_date": "2026-10-01", - "last_updated": "2026-10-01" - }, - { - "id": "spacexai/grok-imagine-video", - "name": "Grok Imagine", - "display_name": "Grok Imagine", + "id": "azure/gpt-5.1", + "name": "GPT-5.1 (Azure)", + "display_name": "GPT-5.1 (Azure)", "modalities": { "input": [ "text", - "image", - "video" + "image" ], "output": [ - "video" + "text" ] }, "limit": { - "context": 8192, - "output": 8192 + "context": 400000, + "output": 128000 }, - "temperature": false, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": false + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": false, + "mode": "effort", + "effort": "none", + "effort_options": [ + "none", + "low", + "medium", + "high" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" + } }, "attachment": true, "open_weights": false, - "release_date": "2026-01-28", - "last_updated": "2026-01-28" + "knowledge": "2024-09-30", + "release_date": "2025-11-13", + "last_updated": "2025-11-13", + "type": "chat" }, { - "id": "spacexai/grok-build-0.1", - "name": "Grok Build 0.1", - "display_name": "Grok Build 0.1", + "id": "azure/grok-4-1-fast-reasoning", + "name": "Grok 4.1 Fast Reasoning (Azure)", + "display_name": "Grok 4.1 Fast Reasoning (Azure)", "modalities": { "input": [ "text", @@ -19174,8 +20850,8 @@ ] }, "limit": { - "context": 256000, - "output": 256000 + "context": 128000, + "output": 128000 }, "temperature": true, "tool_call": true, @@ -19187,7 +20863,6 @@ "reasoning": { "supported": true, "effort_options": [ - "none", "low", "medium", "high" @@ -19196,54 +20871,29 @@ }, "attachment": true, "open_weights": false, - "release_date": "2026-04-16", - "last_updated": "2026-04-16", + "release_date": "2025-11-19", + "last_updated": "2025-11-19", "type": "chat" }, { - "id": "spacexai/grok-imagine-video-1.5", - "name": "Grok Imagine Video 1.5", - "display_name": "Grok Imagine Video 1.5", - "modalities": { - "input": [ - "text" - ], - "output": [ - "video" - ] - }, - "limit": { - "context": 1024, - "output": 8192 - }, - "temperature": false, - "tool_call": false, - "reasoning": { - "supported": false - }, - "attachment": false, - "open_weights": false, - "release_date": "2026-05-30", - "last_updated": "2026-05-30" - }, - { - "id": "spacexai/grok-4.7", - "name": "Grok 4.7", - "display_name": "Grok 4.7", + "id": "azure/gpt-5.5", + "name": "GPT-5.5 (Azure)", + "display_name": "GPT-5.5 (Azure)", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 500000, - "output": 500000 + "context": 1050000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -19251,90 +20901,117 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", "effort_options": [ "low", "medium", "high", "xhigh" - ] + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, "attachment": true, "open_weights": false, - "knowledge": "2026-05", - "release_date": "2026-09-21", - "last_updated": "2026-09-21", + "knowledge": "2025-12-01", + "release_date": "2026-04-23", + "last_updated": "2026-04-23", "type": "chat" }, { - "id": "spacexai/grok-4.20-reasoning-beta", - "name": "Grok 4.20 Beta Reasoning", - "display_name": "Grok 4.20 Beta Reasoning", + "id": "azure/gpt-5.1-codex-mini", + "name": "GPT-5.1 Codex mini (Azure)", + "display_name": "GPT-5.1 Codex mini (Azure)", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 2000000, - "output": 2000000 + "context": 400000, + "output": 128000 }, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": false, + "mode": "effort", + "effort": "none", "effort_options": [ "none", "low", "medium", "high" - ] + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, "attachment": true, "open_weights": false, - "release_date": "2026-03-11", - "last_updated": "2026-03-11", + "knowledge": "2024-09-30", + "release_date": "2025-11-13", + "last_updated": "2025-11-13", "type": "chat" }, { - "id": "spacexai/grok-tts", - "name": "Grok TTS", - "display_name": "Grok TTS", + "id": "azure/gpt-4.1-mini", + "name": "GPT-4.1 Mini (Azure)", + "display_name": "GPT-4.1 Mini (Azure)", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ - "audio" + "text" ] }, "limit": { - "context": 15000, - "output": 15000 + "context": 1000000, + "output": 32768 }, - "temperature": false, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { "supported": false }, - "attachment": false, + "attachment": true, "open_weights": false, - "release_date": "2026-03-16", - "last_updated": "2026-03-16" + "knowledge": "2024-04", + "release_date": "2025-04-14", + "last_updated": "2025-04-14", + "type": "chat" }, { - "id": "spacexai/grok-4.3", - "name": "Grok 4.3", - "display_name": "Grok 4.3", + "id": "azure/gpt-4.1", + "name": "GPT-4.1 (Azure)", + "display_name": "GPT-4.1 (Azure)", "modalities": { "input": [ "text", @@ -19347,48 +21024,39 @@ }, "limit": { "context": 1000000, - "output": 1000000 + "output": 32768 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "effort_options": [ - "low", - "medium", - "high" - ] - } + "supported": false }, "attachment": true, "open_weights": false, - "release_date": "2026-04-17", - "last_updated": "2026-04-17", + "knowledge": "2024-04", + "release_date": "2025-04-14", + "last_updated": "2025-04-14", "type": "chat" }, { - "id": "spacexai/grok-4.6", - "name": "Grok 4.6", - "display_name": "Grok 4.6", + "id": "azure/gpt-6-luna", + "name": "GPT-6 Luna (Azure)", + "display_name": "GPT-6 Luna (Azure)", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 500000, - "output": 500000 + "context": 1050000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -19396,26 +21064,27 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, "effort_options": [ + "none", "low", "medium", "high", - "xhigh" + "xhigh", + "max" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-02-01", - "release_date": "2026-08-12", - "last_updated": "2026-08-12", + "knowledge": "2026-05-18", + "release_date": "2026-09-22", + "last_updated": "2026-09-22", "type": "chat" }, { - "id": "cohere/embed-v5.0-pro", - "name": "Embed 5 Pro", - "display_name": "Embed 5 Pro", + "id": "cerebras/gpt-oss-120b", + "name": "GPT OSS 120B (Cerebras)", + "display_name": "GPT OSS 120B (Cerebras)", "modalities": { "input": [ "text" @@ -19425,24 +21094,36 @@ ] }, "limit": { - "context": 128000, - "output": 2048 + "context": 131072, + "output": 32768 }, - "temperature": false, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "effort_options": [ + "low", + "medium", + "high" + ] + } }, "attachment": false, - "open_weights": false, - "release_date": "2026-09-30", - "last_updated": "2026-09-30", - "type": "embedding" + "open_weights": true, + "knowledge": "2024-06", + "release_date": "2025-08-05", + "last_updated": "2025-08-05", + "type": "chat" }, { - "id": "cohere/rerank-v4-fast", - "name": "Cohere Rerank 4 Fast", - "display_name": "Cohere Rerank 4 Fast", + "id": "perplexity/sonar", + "name": "Sonar (Perplexity)", + "display_name": "Sonar (Perplexity)", "modalities": { "input": [ "text" @@ -19452,50 +21133,65 @@ ] }, "limit": { - "context": 32000, - "output": 8192 + "context": 130000, + "output": 4096 }, - "temperature": false, + "temperature": true, "tool_call": false, "reasoning": { "supported": false }, "attachment": false, "open_weights": false, - "release_date": "2025-12-11", - "last_updated": "2025-12-11" + "knowledge": "2025-09-01", + "release_date": "2024-01-01", + "last_updated": "2025-09-01", + "type": "chat" }, { - "id": "cohere/embed-v5.0-fast", - "name": "Embed 5 Fast", - "display_name": "Embed 5 Fast", + "id": "deepseek/deepseek-v4.1-flash-1017", + "name": "DeepSeek V4.1 Flash 1017 (DeepSeek)", + "display_name": "DeepSeek V4.1 Flash 1017 (DeepSeek)", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 2048 + "context": 1050000, + "output": 393216 }, - "temperature": false, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, - "open_weights": false, - "release_date": "2026-09-30", - "last_updated": "2026-09-30", - "type": "embedding" + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "none", + "low", + "high", + "max" + ] + } + }, + "attachment": true, + "open_weights": true, + "knowledge": "2025-05", + "release_date": "2026-10-17", + "last_updated": "2026-10-17", + "type": "chat" }, { - "id": "cohere/rerank-v5-pro", - "name": "Cohere Rerank 5 Pro", - "display_name": "Cohere Rerank 5 Pro", + "id": "deepseek/deepseek-v4-pro", + "name": "DeepSeek V4 Pro (DeepSeek)", + "display_name": "DeepSeek V4 Pro (DeepSeek)", "modalities": { "input": [ "text" @@ -19505,156 +21201,186 @@ ] }, "limit": { - "context": 32768, - "output": 8192 + "context": 1050000, + "output": 393216 }, - "temperature": false, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, - "open_weights": false, - "release_date": "2026-10-21", - "last_updated": "2026-10-21" - }, - { - "id": "cohere/rerank-v4-pro", - "name": "Cohere Rerank 4 Pro", - "display_name": "Cohere Rerank 4 Pro", - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 32000, - "output": 8192 - }, - "temperature": false, - "tool_call": false, - "reasoning": { - "supported": false + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "high", + "effort_options": [ + "low", + "high", + "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." + ] + } }, "attachment": false, - "open_weights": false, - "release_date": "2025-12-11", - "last_updated": "2025-12-11" + "open_weights": true, + "knowledge": "2025-05", + "release_date": "2026-04-24", + "last_updated": "2026-04-24", + "type": "chat" }, { - "id": "cohere/embed-v4.0", - "name": "Embed v4.0", - "display_name": "Embed v4.0", + "id": "moonshot/kimi-k3", + "name": "Kimi K3 (Moonshot AI)", + "display_name": "Kimi K3 (Moonshot AI)", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 1536 + "context": 1048576, + "output": 1048576 }, "temperature": false, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": false - }, - "attachment": false, - "open_weights": false, - "release_date": "2025-04-15", - "last_updated": "2025-04-15", - "type": "embedding" - }, - { - "id": "cohere/rerank-v3.5", - "name": "Cohere Rerank 3.5", - "display_name": "Cohere Rerank 3.5", - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 4096, - "output": 8192 + "supported": true, + "default": true }, - "temperature": false, - "tool_call": false, - "reasoning": { - "supported": false + "extra_capabilities": { + "reasoning": { + "supported": true, + "effort_options": [ + "low", + "high", + "max" + ] + } }, - "attachment": false, - "open_weights": false, - "release_date": "2024-12-02", - "last_updated": "2024-12-02" + "attachment": true, + "open_weights": true, + "release_date": "2026-07-16", + "last_updated": "2026-07-16", + "type": "chat" }, { - "id": "cohere/command-a", - "name": "Command A", - "display_name": "Command A", + "id": "moonshot/kimi-k2.7-code", + "name": "Kimi K2.7 Code (Moonshot AI)", + "display_name": "Kimi K2.7 Code (Moonshot AI)", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 256000, - "output": 8000 + "context": 262144, + "output": 262144 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" + ] + } + }, + "attachment": true, "open_weights": true, - "knowledge": "2024-08-31", - "release_date": "2025-03-13", - "last_updated": "2025-03-13", + "knowledge": "2025-01", + "release_date": "2026-06-12", + "last_updated": "2026-06-12", "type": "chat" }, { - "id": "cohere/rerank-v5-fast", - "name": "Cohere Rerank 5 Fast", - "display_name": "Cohere Rerank 5 Fast", + "id": "moonshot/kimi-k2.7-code-highspeed", + "name": "Kimi K2.7 Code Highspeed (Moonshot AI)", + "display_name": "Kimi K2.7 Code Highspeed (Moonshot AI)", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 32768, - "output": 8192 + "context": 262144, + "output": 262144 }, "temperature": false, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, - "open_weights": false, - "release_date": "2026-10-21", - "last_updated": "2026-10-21" + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" + ] + } + }, + "attachment": true, + "open_weights": true, + "knowledge": "2025-01", + "release_date": "2026-06-12", + "last_updated": "2026-06-12", + "type": "chat" }, { - "id": "sakana/fugu-ultra-v2", - "name": "Fugu Ultra v2", - "display_name": "Fugu Ultra v2", + "id": "moonshot/kimi-k3.1-pro-highspeed", + "name": "Kimi K3.1 Pro Highspeed (Moonshot AI)", + "display_name": "Kimi K3.1 Pro Highspeed (Moonshot AI)", "modalities": { "input": [ "text", @@ -19665,8 +21391,8 @@ ] }, "limit": { - "context": 1000000, - "output": 1000000 + "context": 1048576, + "output": 1048576 }, "temperature": false, "tool_call": true, @@ -19677,38 +21403,37 @@ "extra_capabilities": { "reasoning": { "effort_options": [ + "low", "high", - "xhigh", "max" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-08-28", - "release_date": "2026-09-11", - "last_updated": "2026-09-11", + "release_date": "2026-10-23", + "last_updated": "2026-10-23", "type": "chat" }, { - "id": "sakana/namazu", - "name": "Sakana Namazu", - "display_name": "Sakana Namazu", + "id": "moonshot/kimi-k3.1-pro", + "name": "Kimi K3.1 Pro (Moonshot AI)", + "display_name": "Kimi K3.1 Pro (Moonshot AI)", "modalities": { "input": [ "text", "image", - "pdf" + "video" ], "output": [ "text" ] }, "limit": { - "context": 256000, - "output": 256000 + "context": 1048576, + "output": 1048576 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -19717,38 +21442,35 @@ "extra_capabilities": { "reasoning": { "effort_options": [ - "none", - "minimal", "low", - "medium", "high", - "xhigh", "max" ] } }, "attachment": true, "open_weights": false, - "release_date": "2026-08-03", - "last_updated": "2026-08-03", + "release_date": "2026-10-23", + "last_updated": "2026-10-23", "type": "chat" }, { - "id": "sakana/fugu-max", - "name": "Fugu Max", - "display_name": "Fugu Max", + "id": "moonshot/kimi-k2.5", + "name": "Kimi K2.5 (Moonshot AI)", + "display_name": "Kimi K2.5 (Moonshot AI)", "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 1000000 + "context": 262144, + "output": 32768 }, "temperature": false, "tool_call": true, @@ -19758,7 +21480,18 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ + "none", + "minimal", + "low", + "medium", "high", "xhigh", "max" @@ -19766,27 +21499,29 @@ } }, "attachment": true, - "open_weights": false, - "release_date": "2026-09-11", - "last_updated": "2026-09-11", + "open_weights": true, + "knowledge": "2025-01", + "release_date": "2026-01-27", + "last_updated": "2026-01-27", "type": "chat" }, { - "id": "sakana/fugu-ultra", - "name": "Fugu Ultra", - "display_name": "Fugu Ultra", + "id": "moonshot/kimi-k2.6", + "name": "Kimi K2.6 (Moonshot AI)", + "display_name": "Kimi K2.6 (Moonshot AI)", "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 1000000 + "context": 262144, + "output": 262144 }, "temperature": true, "tool_call": true, @@ -19796,7 +21531,18 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ + "none", + "minimal", + "low", + "medium", "high", "xhigh", "max" @@ -19804,156 +21550,193 @@ } }, "attachment": true, - "open_weights": false, - "release_date": "2026-06-15", - "last_updated": "2026-06-15", + "open_weights": true, + "knowledge": "2025-01", + "release_date": "2026-04-20", + "last_updated": "2026-04-20", "type": "chat" }, { - "id": "fish-audio/transcribe-1", - "name": "Transcribe-1", - "display_name": "Transcribe-1", + "id": "zai/glm-4-32b-0414-128k", + "name": "GLM-4 32B (0414-128k) (Z AI)", + "display_name": "GLM-4 32B (0414-128k) (Z AI)", "modalities": { "input": [ - "audio" + "text" ], "output": [ "text" ] }, "limit": { - "context": 8192, - "output": 8192 + "context": 128000, + "output": 128000 }, - "temperature": false, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { "supported": false }, - "attachment": true, + "attachment": false, "open_weights": false, - "release_date": "2026-03-01", - "last_updated": "2026-03-01" + "release_date": "2025-04-14", + "last_updated": "2025-04-14", + "type": "chat" }, { - "id": "fish-audio/s1", - "name": "S1", - "display_name": "S1", + "id": "zai/glm-4.5-x", + "name": "GLM-4.5 X (Z AI)", + "display_name": "GLM-4.5 X (Z AI)", "modalities": { "input": [ "text" ], "output": [ - "audio" + "text" ] }, "limit": { - "context": 8192, - "output": 8192 + "context": 128000, + "output": 128000 }, - "temperature": false, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, "attachment": false, "open_weights": false, - "release_date": "2025-10-20", - "last_updated": "2025-10-20" + "release_date": "2025-07-28", + "last_updated": "2025-07-28", + "type": "chat" }, { - "id": "fish-audio/s2.1-pro", - "name": "S2.1 Pro", - "display_name": "S2.1 Pro", + "id": "zai/glm-4.6v", + "name": "GLM-4.6V (Z AI)", + "display_name": "GLM-4.6V (Z AI)", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ - "audio" + "text" ] }, "limit": { - "context": 8192, - "output": 8192 + "context": 128000, + "output": 16000 }, - "temperature": false, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, - "open_weights": false, - "release_date": "2026-07-28", - "last_updated": "2026-07-28" + "attachment": true, + "open_weights": true, + "knowledge": "2025-04", + "release_date": "2025-12-08", + "last_updated": "2025-12-08", + "type": "chat" }, { - "id": "fish-audio/drama-3", - "name": "Drama 3", - "display_name": "Drama 3", + "id": "zai/glm-5.3-flash", + "name": "GLM-5.3 Flash (Z AI)", + "display_name": "GLM-5.3 Flash (Z AI)", "modalities": { "input": [ - "text" + "text", + "image", + "video", + "pdf" ], "output": [ - "audio" + "text" ] }, "limit": { - "context": 8192, - "output": 8192 + "context": 1048576, + "output": 131072 }, - "temperature": false, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, - "open_weights": false, - "release_date": "2026-10-13", - "last_updated": "2026-10-13" + "extra_capabilities": { + "reasoning": { + "supported": true, + "effort_options": [ + "low", + "high", + "max" + ] + } + }, + "attachment": true, + "open_weights": true, + "release_date": "2026-08-26", + "last_updated": "2026-08-26", + "type": "chat" }, { - "id": "fish-audio/s2-pro", - "name": "S2 Pro", - "display_name": "S2 Pro", + "id": "zai/glm-4.7", + "name": "GLM-4.7 (Z AI)", + "display_name": "GLM-4.7 (Z AI)", "modalities": { "input": [ "text" ], "output": [ - "audio" + "text" ] }, "limit": { - "context": 8192, - "output": 8192 + "context": 200000, + "output": 128000 }, - "temperature": false, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } }, "attachment": false, - "open_weights": false, - "release_date": "2026-03-09", - "last_updated": "2026-03-09" + "open_weights": true, + "knowledge": "2025-04", + "release_date": "2025-12-22", + "last_updated": "2025-12-22", + "type": "chat" }, { - "id": "xiaomi/mimo-v2.6-pro", - "name": "MiMo V2.6 Pro", - "display_name": "MiMo V2.6 Pro", + "id": "zai/glm-4.6", + "name": "GLM-4.6 (Z AI)", + "display_name": "GLM-4.6 (Z AI)", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, + "context": 200000, "output": 131072 }, "temperature": true, @@ -19964,77 +21747,59 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "none", - "minimal", - "low", - "medium", - "high" - ] + "supported": true } }, - "attachment": true, + "attachment": false, "open_weights": true, - "release_date": "2026-09-22", - "last_updated": "2026-09-22", + "knowledge": "2025-04", + "release_date": "2025-09-30", + "last_updated": "2025-09-30", "type": "chat" }, { - "id": "xiaomi/mimo-v2.6-flash", - "name": "MiMo V2.6 Flash", - "display_name": "MiMo V2.6 Flash", + "id": "zai/glm-4.5-air", + "name": "GLM-4.5 Air (Z AI)", + "display_name": "GLM-4.5 Air (Z AI)", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 128000, + "output": 98304 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "none", - "minimal", - "low", - "medium", - "high" - ] - } + "supported": false }, - "attachment": true, + "attachment": false, "open_weights": true, - "release_date": "2026-09-22", - "last_updated": "2026-09-22", + "knowledge": "2025-04", + "release_date": "2025-07-28", + "last_updated": "2025-07-28", "type": "chat" }, { - "id": "xiaomi/mimo-v2.6-pro-ultraspeed", - "name": "MiMo V2.6 Pro UltraSpeed", - "display_name": "MiMo V2.6 Pro UltraSpeed", + "id": "zai/glm-5.3", + "name": "GLM-5.3 (Z AI)", + "display_name": "GLM-5.3 (Z AI)", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 1000000, + "output": 128000 }, "temperature": true, "tool_call": true, @@ -20044,37 +21809,37 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, "effort_options": [ - "none", - "minimal", "low", - "medium", - "high" + "high", + "max" ] } }, - "attachment": true, + "attachment": false, "open_weights": true, - "release_date": "2026-09-21", - "last_updated": "2026-09-21", + "release_date": "2026-08-14", + "last_updated": "2026-08-14", "type": "chat" }, { - "id": "xiaomi/mimo-v2.5", - "name": "MiMo M2.5", - "display_name": "MiMo M2.5", + "id": "zai/glm-4.5v", + "name": "GLM-4.5V (Z AI)", + "display_name": "GLM-4.5V (Z AI)", "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 131100 + "context": 128000, + "output": 16000 }, "temperature": true, "tool_call": true, @@ -20084,15 +21849,15 @@ }, "attachment": true, "open_weights": true, - "knowledge": "2024-12", - "release_date": "2026-04-22", - "last_updated": "2026-04-22", + "knowledge": "2025-04", + "release_date": "2025-08-11", + "last_updated": "2025-08-11", "type": "chat" }, { - "id": "xiaomi/mimo-v2.5-pro", - "name": "MiMo V2.5 Pro", - "display_name": "MiMo V2.5 Pro", + "id": "zai/glm-4.5-airx", + "name": "GLM-4.5 AirX (Z AI)", + "display_name": "GLM-4.5 AirX (Z AI)", "modalities": { "input": [ "text" @@ -20102,26 +21867,24 @@ ] }, "limit": { - "context": 1050000, - "output": 131000 + "context": 128000, + "output": 128000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true + "supported": false }, "attachment": false, - "open_weights": true, - "knowledge": "2024-12", - "release_date": "2026-04-22", - "last_updated": "2026-04-22", + "open_weights": false, + "release_date": "2025-07-28", + "last_updated": "2025-07-28", "type": "chat" }, { - "id": "nvidia/nemotron-3.5-lightning-free", - "name": "Nemotron 3.5 Lightning Free", - "display_name": "Nemotron 3.5 Lightning Free", + "id": "zai/glm-5", + "name": "GLM-5 (Z AI)", + "display_name": "GLM-5 (Z AI)", "modalities": { "input": [ "text" @@ -20131,8 +21894,8 @@ ] }, "limit": { - "context": 1000000, - "output": 65000 + "context": 202800, + "output": 131100 }, "temperature": true, "tool_call": true, @@ -20143,25 +21906,33 @@ "extra_capabilities": { "reasoning": { "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ "none", + "minimal", "low", "medium", - "high" + "high", + "xhigh", + "max" ] } }, "attachment": false, "open_weights": true, - "knowledge": "2025-09", - "release_date": "2026-08-11", - "last_updated": "2026-08-11", + "release_date": "2026-02-12", + "last_updated": "2026-02-12", "type": "chat" }, { - "id": "nvidia/nemotron-nano-9b-v2", - "name": "Nvidia Nemotron Nano 9B V2", - "display_name": "Nvidia Nemotron Nano 9B V2", + "id": "zai/glm-4.7-flashx", + "name": "GLM-4.7 FlashX (Z AI)", + "display_name": "GLM-4.7 FlashX (Z AI)", "modalities": { "input": [ "text" @@ -20171,8 +21942,8 @@ ] }, "limit": { - "context": 131072, - "output": 131072 + "context": 200000, + "output": 128000 }, "temperature": true, "tool_call": true, @@ -20180,39 +21951,28 @@ "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "none", - "low", - "medium", - "high" - ] - } - }, "attachment": false, "open_weights": true, - "knowledge": "2024-09", - "release_date": "2025-08-18", - "last_updated": "2025-08-18", + "knowledge": "2025-04", + "release_date": "2026-01-19", + "last_updated": "2026-01-19", "type": "chat" }, { - "id": "nvidia/nemotron-nano-12b-v2-vl", - "name": "Nvidia Nemotron Nano 12B V2 VL", - "display_name": "Nvidia Nemotron Nano 12B V2 VL", + "id": "zai/glm-5.1", + "name": "GLM-5.1 (Z AI)", + "display_name": "GLM-5.1 (Z AI)", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 131072 + "context": 200000, + "output": 128000 }, "temperature": true, "tool_call": true, @@ -20222,24 +21982,34 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ "none", + "minimal", "low", "medium", - "high" + "high", + "xhigh", + "max" ] } }, - "attachment": true, + "attachment": false, "open_weights": true, - "release_date": "2025-10-28", - "last_updated": "2025-10-28", + "release_date": "2026-04-07", + "last_updated": "2026-04-07", "type": "chat" }, { - "id": "nvidia/nemotron-3-ultra-550b-a55b", - "name": "Nemotron 3 Ultra", - "display_name": "Nemotron 3 Ultra", + "id": "zai/glm-5.2", + "name": "GLM-5.2 (Z AI)", + "display_name": "GLM-5.2 (Z AI)", "modalities": { "input": [ "text" @@ -20250,7 +22020,7 @@ }, "limit": { "context": 1000000, - "output": 65000 + "output": 128000 }, "temperature": true, "tool_call": true, @@ -20261,25 +22031,29 @@ "extra_capabilities": { "reasoning": { "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ "none", - "low", - "medium", - "high" + "high", + "max" ] } }, "attachment": false, "open_weights": true, - "knowledge": "2025-09", - "release_date": "2026-06-04", - "last_updated": "2026-06-04", + "release_date": "2026-06-13", + "last_updated": "2026-06-13", "type": "chat" }, { - "id": "nvidia/nemotron-3.5-lightning", - "name": "Nemotron 3.5 Lightning 30B", - "display_name": "Nemotron 3.5 Lightning 30B", + "id": "zai/glm-4.5", + "name": "GLM-4.5 (Z AI)", + "display_name": "GLM-4.5 (Z AI)", "modalities": { "input": [ "text" @@ -20289,8 +22063,8 @@ ] }, "limit": { - "context": 262144, - "output": 131072 + "context": 128000, + "output": 98304 }, "temperature": true, "tool_call": true, @@ -20298,118 +22072,101 @@ "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "effort_options": [ - "none", - "low", - "medium", - "high" - ] - } - }, "attachment": false, "open_weights": true, - "knowledge": "2025-09", - "release_date": "2026-08-11", - "last_updated": "2026-08-11", + "knowledge": "2025-04", + "release_date": "2025-07-28", + "last_updated": "2025-07-28", "type": "chat" }, { - "id": "nvidia/nemotron-3-super-120b-a12b", - "name": "NVIDIA Nemotron 3 Super 120B A12B", - "display_name": "NVIDIA Nemotron 3 Super 120B A12B", + "id": "zai/glm-4.6v-flashx", + "name": "GLM-4.6V FlashX (Z AI)", + "display_name": "GLM-4.6V FlashX (Z AI)", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 256000, - "output": 32000 + "context": 128000, + "output": 16000 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "none", - "low", - "medium", - "high" - ] - } - }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-06", - "release_date": "2026-03-11", - "last_updated": "2026-03-11", + "attachment": true, + "open_weights": false, + "release_date": "2025-12-08", + "last_updated": "2025-12-08", "type": "chat" }, { - "id": "nvidia/nemotron-3-nano-30b-a3b", - "name": "Nemotron 3 Nano 30B A3B", - "display_name": "Nemotron 3 Nano 30B A3B", + "id": "canopywave/kimi-k3", + "name": "Kimi K3 (CanopyWave)", + "display_name": "Kimi K3 (CanopyWave)", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 1048576, + "output": 1048576 }, - "temperature": true, - "tool_call": false, + "temperature": false, + "tool_call": true, "reasoning": { "supported": true, "default": true }, "extra_capabilities": { "reasoning": { + "supported": true, "effort_options": [ "none", + "minimal", "low", "medium", - "high" + "high", + "xhigh", + "max" ] } }, - "attachment": false, + "attachment": true, "open_weights": true, - "knowledge": "2025-06-25", - "release_date": "2025-12-15", - "last_updated": "2025-12-15", + "release_date": "2026-07-16", + "last_updated": "2026-07-16", "type": "chat" }, { - "id": "nvidia/nemotron-3.5-super-vl", - "name": "NVIDIA Nemotron 3.5 Super VL", - "display_name": "NVIDIA Nemotron 3.5 Super VL", + "id": "canopywave/deepseek-v4-pro", + "name": "DeepSeek V4 Pro (CanopyWave)", + "display_name": "DeepSeek V4 Pro (CanopyWave)", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 1048576 + "context": 1000000, + "output": 393216 }, "temperature": true, "tool_call": true, @@ -20419,29 +22176,40 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "high", "effort_options": [ - "none", "low", - "medium", - "high" + "high", + "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." ] } }, - "attachment": true, + "attachment": false, "open_weights": true, - "release_date": "2026-10-15", - "last_updated": "2026-10-15", + "knowledge": "2025-05", + "release_date": "2026-04-24", + "last_updated": "2026-04-24", "type": "chat" }, { - "id": "anthropic/claude-fable-5.1", - "name": "Claude Fable 5.1", - "display_name": "Claude Fable 5.1", + "id": "canopywave/deepseek-v4-flash", + "name": "DeepSeek V4 Flash (CanopyWave)", + "display_name": "DeepSeek V4 Flash (CanopyWave)", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" @@ -20449,9 +22217,9 @@ }, "limit": { "context": 1000000, - "output": 128000 + "output": 393216 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -20465,50 +22233,44 @@ "effort": "high", "effort_options": [ "low", - "medium", "high", - "xhigh", "max" ], "interleaved": true, "summaries": true, - "visibility": "omitted", + "visibility": "summary", "continuation": [ "thinking_blocks" ], "notes": [ - "Adaptive thinking is always on for Claude Fable 5 and Claude Mythos 5; thinking.type = \"disabled\" is rejected.", - "Manual budget_tokens requests return 400 on Claude Fable 5 and Claude Mythos 5.", - "thinking.display defaults to omitted; set display to summarized to receive readable thinking summaries." + "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-06", - "release_date": "2026-09-01", - "last_updated": "2026-09-01", + "attachment": false, + "open_weights": true, + "knowledge": "2025-05", + "release_date": "2026-04-24", + "last_updated": "2026-04-24", "type": "chat" }, { - "id": "anthropic/claude-opus-5", - "name": "Claude Opus 5", - "display_name": "Claude Opus 5", + "id": "canopywave/glm-5.2", + "name": "GLM-5.2 (CanopyWave)", + "display_name": "GLM-5.2 (CanopyWave)", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 200000, + "output": 32768 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -20517,8 +22279,15 @@ "extra_capabilities": { "reasoning": { "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ "none", + "minimal", "low", "medium", "high", @@ -20527,89 +22296,80 @@ ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-05", - "release_date": "2026-07-24", - "last_updated": "2026-07-24", + "attachment": false, + "open_weights": true, + "release_date": "2026-06-13", + "last_updated": "2026-06-13", "type": "chat" }, { - "id": "anthropic/claude-opus-4.8", - "name": "Claude Opus 4.8", - "display_name": "Claude Opus 4.8", + "id": "canopywave/kimi-k2.6", + "name": "Kimi K2.6 (CanopyWave)", + "display_name": "Kimi K2.6 (CanopyWave)", "modalities": { "input": [ "text", "image", - "pdf" + "video" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 262144, + "output": 32768 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "high", + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ + "none", + "minimal", "low", "medium", "high", "xhigh", "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "omitted", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Claude Opus 4.7 and newer Opus models require thinking.type = \"adaptive\" to enable thinking explicitly.", - "Manual budget_tokens requests return 400 on Claude Opus 4.7 and newer adaptive-only Opus models.", - "task_budget is separate from thinking control and should not be treated as a thinking budget." ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2026-01", - "release_date": "2026-05-28", - "last_updated": "2026-05-28", + "open_weights": true, + "knowledge": "2025-01", + "release_date": "2026-04-20", + "last_updated": "2026-04-20", "type": "chat" }, { - "id": "anthropic/claude-sonnet-5", - "name": "Claude Sonnet 5", - "display_name": "Claude Sonnet 5", + "id": "scx-ai/gpt-oss-120b", + "name": "GPT OSS 120B (SCX.ai (Turbo))", + "display_name": "GPT OSS 120B (SCX.ai (Turbo))", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 131072, + "output": 32768 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -20619,148 +22379,114 @@ "reasoning": { "supported": true, "effort_options": [ - "none", "low", "medium", - "high", - "xhigh", - "max" + "high" ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-01-31", - "release_date": "2026-06-30", - "last_updated": "2026-06-30", + "attachment": false, + "open_weights": true, + "knowledge": "2024-06", + "release_date": "2025-08-05", + "last_updated": "2025-08-05", "type": "chat" }, { - "id": "anthropic/claude-opus-4.6", - "name": "Claude Opus 4.6", - "display_name": "Claude Opus 4.6", + "id": "scx-ai/qwen3-32b", + "name": "Qwen3 32B (SCX.ai (Turbo))", + "display_name": "Qwen3 32B (SCX.ai (Turbo))", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 32768, + "output": 8192 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "mixed", - "budget": { - "min": 1024, - "unit": "tokens" - }, - "effort": "high", - "effort_options": [ - "low", - "medium", - "high", - "max" - ], "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ "thinking_blocks" - ], - "notes": [ - "Anthropic recommends adaptive thinking with effort for Claude 4.6; budget_tokens remains a deprecated compatibility path.", - "Anthropic API defaults effort to high; lower effort levels should be chosen per workload." ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-05-31", - "release_date": "2026-02-05", - "last_updated": "2026-03-13", + "attachment": false, + "open_weights": true, + "knowledge": "2025-04", + "release_date": "2025-04-28", + "last_updated": "2025-04-28", "type": "chat" }, { - "id": "anthropic/claude-opus-4.5", - "name": "Claude Opus 4.5", - "display_name": "Claude Opus 4.5", + "id": "scx-ai/minimax-m2.7", + "name": "MiniMax M2.7 (SCX.ai (Turbo))", + "display_name": "MiniMax M2.7 (SCX.ai (Turbo))", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 64000 + "context": 196608, + "output": 196608 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "mixed", - "budget": { - "min": 1024, - "unit": "tokens" - }, - "effort": "high", - "effort_options": [ - "low", - "medium", - "high" - ], "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ "thinking_blocks" ], - "notes": [ - "Claude Opus 4.5 uses manual thinking.type = \"enabled\" with budget_tokens; effort can be used alongside the thinking budget.", - "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header." + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-05", - "release_date": "2025-11-24", - "last_updated": "2025-11-24", + "attachment": false, + "open_weights": true, + "release_date": "2026-03-18", + "last_updated": "2026-03-18", "type": "chat" }, { - "id": "anthropic/claude-opus-5.5-fast", - "name": "Claude Opus 5.5 (Fast)", - "display_name": "Claude Opus 5.5 (Fast)", + "id": "together-ai/kimi-k3", + "name": "Kimi K3 (Together AI)", + "display_name": "Kimi K3 (Together AI)", "modalities": { "input": [ "text", "image", - "pdf" + "video" ], "output": [ "text" @@ -20768,7 +22494,7 @@ }, "limit": { "context": 1000000, - "output": 128000 + "output": 1000000 }, "temperature": false, "tool_call": true, @@ -20778,146 +22504,104 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" - ] + "supported": true } }, "attachment": true, - "open_weights": false, - "knowledge": "2026-06", - "release_date": "2026-09-22", - "last_updated": "2026-09-22", + "open_weights": true, + "release_date": "2026-07-16", + "last_updated": "2026-07-16", "type": "chat" }, { - "id": "anthropic/claude-sonnet-4.6", - "name": "Claude Sonnet 4.6", - "display_name": "Claude Sonnet 4.6", + "id": "together-ai/qwen3.8-2.4t-a95b", + "name": "Qwen3.8 2.4T A95B (Together AI)", + "display_name": "Qwen3.8 2.4T A95B (Together AI)", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 1010000, + "output": 909000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "mixed", - "budget": { - "min": 1024, - "unit": "tokens" - }, - "effort": "high", "effort_options": [ + "none", "low", "medium", - "high", - "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Anthropic recommends adaptive thinking with effort for Claude 4.6; budget_tokens remains a deprecated compatibility path.", - "Anthropic API defaults effort to high; lower effort levels should be chosen per workload." + "xhigh" ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-02-17", - "last_updated": "2026-03-13", + "attachment": false, + "open_weights": true, + "release_date": "2026-08-12", + "last_updated": "2026-08-12", "type": "chat" }, { - "id": "anthropic/claude-opus-4.8-fast", - "name": "Claude Opus 4.8 (Fast)", - "display_name": "Claude Opus 4.8 (Fast)", + "id": "together-ai/deepseek-v4.1-flash-1017", + "name": "DeepSeek V4.1 Flash 1017 (Together AI)", + "display_name": "DeepSeek V4.1 Flash 1017 (Together AI)", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 1048576, + "output": 393216 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "high", "effort_options": [ + "none", "low", - "medium", "high", "xhigh", "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "omitted", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Claude Opus 4.7 and newer Opus models require thinking.type = \"adaptive\" to enable thinking explicitly.", - "Manual budget_tokens requests return 400 on Claude Opus 4.7 and newer adaptive-only Opus models.", - "task_budget is separate from thinking control and should not be treated as a thinking budget." ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2026-01", - "release_date": "2026-05-28", - "last_updated": "2026-05-28", + "open_weights": true, + "knowledge": "2025-05", + "release_date": "2026-10-17", + "last_updated": "2026-10-17", "type": "chat" }, { - "id": "anthropic/claude-haiku-4.5", - "name": "Claude Haiku 4.5", - "display_name": "Claude Haiku 4.5", + "id": "together-ai/glm-5.3-flash", + "name": "GLM-5.3 Flash (Together AI)", + "display_name": "GLM-5.3 Flash (Together AI)", "modalities": { "input": [ "text", "image", + "video", "pdf" ], "output": [ @@ -20925,162 +22609,130 @@ ] }, "limit": { - "context": 200000, - "output": 64000 + "context": 1048576, + "output": 943717 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "budget", - "budget": { - "min": 1024, - "unit": "tokens" - }, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", - "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." + "effort_options": [ + "low", + "high", + "max" ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2025-02-28", - "release_date": "2025-10-15", - "last_updated": "2025-10-15", + "open_weights": true, + "release_date": "2026-08-26", + "last_updated": "2026-08-26", "type": "chat" }, { - "id": "anthropic/claude-opus-5-fast", - "name": "Claude Opus 5 (Fast)", - "display_name": "Claude Opus 5 (Fast)", + "id": "together-ai/glm-4.7", + "name": "GLM-4.7 (Together AI)", + "display_name": "GLM-4.7 (Together AI)", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, + "context": 202752, "output": 128000 }, - "temperature": false, - "tool_call": true, + "temperature": true, + "tool_call": false, "reasoning": { "supported": true, "default": true }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "none", - "low", - "medium", - "high", - "xhigh", - "max" + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-05", - "release_date": "2026-07-24", - "last_updated": "2026-07-24", + "attachment": false, + "open_weights": true, + "knowledge": "2025-04", + "release_date": "2025-12-22", + "last_updated": "2025-12-22", "type": "chat" }, { - "id": "anthropic/claude-opus-4.7", - "name": "Claude Opus 4.7", - "display_name": "Claude Opus 4.7", + "id": "together-ai/deepseek-v4.1-flash", + "name": "DeepSeek V4.1 Flash (Together AI)", + "display_name": "DeepSeek V4.1 Flash (Together AI)", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 1048576, + "output": 393216 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "high", "effort_options": [ + "none", "low", - "medium", "high", "xhigh", "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "omitted", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Claude Opus 4.7 and newer Opus models require thinking.type = \"adaptive\" to enable thinking explicitly.", - "Manual budget_tokens requests return 400 on Claude Opus 4.7 and newer adaptive-only Opus models.", - "task_budget is separate from thinking control and should not be treated as a thinking budget." ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2026-01-31", - "release_date": "2026-04-16", - "last_updated": "2026-04-16", + "open_weights": true, + "knowledge": "2025-05", + "release_date": "2026-09-10", + "last_updated": "2026-09-10", "type": "chat" }, { - "id": "anthropic/claude-sonnet-5.5", - "name": "Claude Sonnet 5.5", - "display_name": "Claude Sonnet 5.5", + "id": "together-ai/gpt-oss-120b", + "name": "GPT OSS 120B (Together AI)", + "display_name": "GPT OSS 120B (Together AI)", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 131072, + "output": 32768 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -21088,41 +22740,38 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, "effort_options": [ "low", "medium", - "high", - "xhigh", - "max" + "high" ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-06", - "release_date": "2026-09-28", - "last_updated": "2026-09-28", + "attachment": false, + "open_weights": true, + "knowledge": "2024-06", + "release_date": "2025-08-05", + "last_updated": "2025-08-05", "type": "chat" }, { - "id": "anthropic/claude-fable-5", - "name": "Claude Fable 5", - "display_name": "Claude Fable 5", + "id": "together-ai/deepseek-v4-pro", + "name": "DeepSeek V4 Pro (Together AI)", + "display_name": "DeepSeek V4 Pro (Together AI)", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 1048576, + "output": 163840 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -21136,145 +22785,116 @@ "effort": "high", "effort_options": [ "low", - "medium", "high", - "xhigh", "max" ], "interleaved": true, "summaries": true, - "visibility": "omitted", + "visibility": "summary", "continuation": [ "thinking_blocks" ], "notes": [ - "Adaptive thinking is always on for Claude Fable 5 and Claude Mythos 5; thinking.type = \"disabled\" is rejected.", - "Manual budget_tokens requests return 400 on Claude Fable 5 and Claude Mythos 5.", - "thinking.display defaults to omitted; set display to summarized to receive readable thinking summaries." + "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-01-31", - "release_date": "2026-06-09", - "last_updated": "2026-06-09", + "attachment": false, + "open_weights": true, + "knowledge": "2025-05", + "release_date": "2026-04-24", + "last_updated": "2026-04-24", "type": "chat" }, { - "id": "anthropic/claude-sonnet-4.5", - "name": "Claude Sonnet 4.5", - "display_name": "Claude Sonnet 4.5", + "id": "together-ai/glm-5.3", + "name": "GLM-5.3 (Together AI)", + "display_name": "GLM-5.3 (Together AI)", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 64000 + "context": 1048576, + "output": 943717 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "budget", - "budget": { - "min": 1024, - "unit": "tokens" - }, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", - "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." + "effort_options": [ + "low", + "high", + "max" ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-07-31", - "release_date": "2025-09-29", - "last_updated": "2025-09-29", + "attachment": false, + "open_weights": true, + "release_date": "2026-08-14", + "last_updated": "2026-08-14", "type": "chat" }, { - "id": "anthropic/claude-haiku-5.5", - "name": "Claude Haiku 5.5", - "display_name": "Claude Haiku 5.5", + "id": "together-ai/minimax-m3", + "name": "MiniMax M3 (Together AI)", + "display_name": "MiniMax M3 (Together AI)", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 524288, + "output": 131072 }, - "temperature": false, - "tool_call": true, + "temperature": true, + "tool_call": false, "reasoning": { "supported": true, "default": true }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "none", - "low", - "medium", - "high", - "xhigh", - "max" - ] + "supported": true } }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-06", - "release_date": "2026-10-07", - "last_updated": "2026-10-07", + "attachment": false, + "open_weights": true, + "release_date": "2026-06-01", + "last_updated": "2026-06-01", "type": "chat" }, { - "id": "anthropic/claude-opus-5.5", - "name": "Claude Opus 5.5", - "display_name": "Claude Opus 5.5", + "id": "together-ai/muse-glimmer-30b", + "name": "Muse Glimmer 30B (Together AI)", + "display_name": "Muse Glimmer 30B (Together AI)", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 131072, + "output": 117964 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -21283,6 +22903,7 @@ "extra_capabilities": { "reasoning": { "effort_options": [ + "minimal", "low", "medium", "high", @@ -21292,188 +22913,136 @@ } }, "attachment": true, - "open_weights": false, - "knowledge": "2026-06", - "release_date": "2026-09-22", - "last_updated": "2026-09-22", + "open_weights": true, + "knowledge": "2026-01-04", + "release_date": "2026-08-10", + "last_updated": "2026-08-10", "type": "chat" }, { - "id": "stealth/glyph-cluster", - "name": "Glyph Cluster", - "display_name": "Glyph Cluster", + "id": "together-ai/minimax-m3.1-flash", + "name": "MiniMax M3.1 Flash (Together AI)", + "display_name": "MiniMax M3.1 Flash (Together AI)", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 256000, - "output": 256000 + "context": 1048576, + "output": 1048576 }, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "low", - "medium", - "high", - "xhigh" - ] - } - }, - "attachment": false, + "attachment": true, "open_weights": false, - "release_date": "2026-10-07", - "last_updated": "2026-10-07", + "release_date": "2026-10-19", + "last_updated": "2026-10-19", "type": "chat" }, { - "id": "morph/morph-v3-large", - "name": "Morph V3 Large", - "display_name": "Morph V3 Large", + "id": "sciforium/deepseek-v4.1-flash-1017", + "name": "DeepSeek V4.1 Flash 1017 (sciforium)", + "display_name": "DeepSeek V4.1 Flash 1017 (sciforium)", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 131072 + "context": 1048576, + "output": 384000 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, - "open_weights": false, - "release_date": "2025-07-07", - "last_updated": "2025-07-07", + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "none", + "low", + "high", + "xhigh", + "max" + ] + } + }, + "attachment": true, + "open_weights": true, + "knowledge": "2025-05", + "release_date": "2026-10-17", + "last_updated": "2026-10-17", "type": "chat" }, { - "id": "morph/morph-v3-fast", - "name": "Morph V3 Fast", - "display_name": "Morph V3 Fast", + "id": "meta/muse-spark-1.1", + "name": "Muse Spark 1.1 (Meta)", + "display_name": "Muse Spark 1.1 (Meta)", "modalities": { "input": [ - "text" + "text", + "image", + "pdf", + "video" ], "output": [ "text" ] }, "limit": { - "context": 262144, + "context": 1048576, "output": 131072 }, "temperature": true, - "tool_call": false, - "reasoning": { - "supported": false - }, - "attachment": false, - "open_weights": false, - "release_date": "2025-07-07", - "last_updated": "2025-07-07", - "type": "chat" - }, - { - "id": "microsoft/mai-voice-2", - "name": "MAI-Voice-2", - "display_name": "MAI-Voice-2", - "modalities": { - "input": [ - "text" - ], - "output": [ - "audio" - ] - }, - "limit": { - "context": 8192, - "output": 8192 - }, - "temperature": false, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": false - }, - "attachment": false, - "open_weights": false, - "release_date": "2026-06-02", - "last_updated": "2026-06-02" - }, - { - "id": "microsoft/mai-image-2.6", - "name": "MAI-Image-2.6", - "display_name": "MAI-Image-2.6", - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "image" - ] - }, - "limit": { - "context": 8192, - "output": 8192 + "supported": true, + "default": true }, - "temperature": false, - "tool_call": false, - "reasoning": { - "supported": false + "extra_capabilities": { + "reasoning": { + "supported": true, + "effort_options": [ + "minimal", + "low", + "medium", + "high", + "xhigh" + ] + } }, "attachment": true, "open_weights": false, - "release_date": "2026-10-13", - "last_updated": "2026-10-13" - }, - { - "id": "microsoft/mai-voice-2.1", - "name": "MAI-Voice-2.1", - "display_name": "MAI-Voice-2.1", - "modalities": { - "input": [ - "text" - ], - "output": [ - "audio" - ] - }, - "limit": { - "context": 8192, - "output": 8192 - }, - "temperature": false, - "tool_call": false, - "reasoning": { - "supported": false - }, - "attachment": false, - "open_weights": false, - "release_date": "2026-10-01", - "last_updated": "2026-10-01" + "release_date": "2026-04-08", + "last_updated": "2026-07-09", + "type": "chat" }, { - "id": "microsoft/mai-transcribe-2", - "name": "MAI-Transcribe 2", - "display_name": "MAI-Transcribe 2", + "id": "meta/muse-wonder-1.0", + "name": "Muse Wonder 1.0 (Meta)", + "display_name": "Muse Wonder 1.0 (Meta)", "modalities": { "input": [ + "text", + "image", + "video", + "pdf", "audio" ], "output": [ @@ -21481,78 +23050,86 @@ ] }, "limit": { - "context": 8192, - "output": 8192 + "context": 1048576, + "output": 1048576 }, - "temperature": false, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { - "supported": false - }, - "attachment": true, - "open_weights": false, - "release_date": "2026-09-03", - "last_updated": "2026-09-03" - }, - { - "id": "microsoft/mai-voice-2-flash", - "name": "MAI-Voice-2-Flash", - "display_name": "MAI-Voice-2-Flash", - "modalities": { - "input": [ - "text" - ], - "output": [ - "audio" - ] - }, - "limit": { - "context": 8192, - "output": 8192 + "supported": true, + "default": true }, - "temperature": false, - "tool_call": false, - "reasoning": { - "supported": false + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "minimal", + "low", + "medium", + "high", + "xhigh" + ] + } }, - "attachment": false, + "attachment": true, "open_weights": false, - "release_date": "2026-07-23", - "last_updated": "2026-07-23" + "release_date": "2026-10-12", + "last_updated": "2026-10-12", + "type": "chat" }, { - "id": "microsoft/mai-image-2.6-flash", - "name": "MAI-Image-2.6-Flash", - "display_name": "MAI-Image-2.6-Flash", + "id": "meta/muse-spark-1.2", + "name": "Muse Spark 1.2 (Meta)", + "display_name": "Muse Spark 1.2 (Meta)", "modalities": { "input": [ "text", - "image" + "image", + "video", + "pdf", + "audio" ], "output": [ - "image" + "text" ] }, "limit": { - "context": 8192, - "output": 8192 + "context": 1048576, + "output": 131072 }, - "temperature": false, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "effort_options": [ + "minimal", + "low", + "medium", + "high", + "xhigh" + ] + } }, "attachment": true, "open_weights": false, - "release_date": "2026-10-13", - "last_updated": "2026-10-13" + "release_date": "2026-08-05", + "last_updated": "2026-08-05", + "type": "chat" }, { - "id": "microsoft/mai-transcribe-2-streaming", - "name": "MAI-Transcribe-2-Streaming", - "display_name": "MAI-Transcribe-2-Streaming", + "id": "meta/muse-spark-1.3", + "name": "Muse Spark 1.3 (Meta)", + "display_name": "Muse Spark 1.3 (Meta)", "modalities": { "input": [ + "text", + "image", + "video", + "pdf", "audio" ], "output": [ @@ -21560,61 +23137,86 @@ ] }, "limit": { - "context": 8192, - "output": 8192 + "context": 1048576, + "output": 1048576 }, - "temperature": false, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "effort_options": [ + "minimal", + "low", + "medium", + "high", + "xhigh" + ] + } }, "attachment": true, "open_weights": false, - "release_date": "2026-10-01", - "last_updated": "2026-10-01" + "release_date": "2026-09-02", + "last_updated": "2026-09-02", + "type": "chat" }, { - "id": "microsoft/mai-voice-2.1-flash", - "name": "MAI-Voice-2.1-Flash", - "display_name": "MAI-Voice-2.1-Flash", + "id": "vertex-openai/grok-4-20-non-reasoning", + "name": "Grok 4.20 Non-Reasoning (Vertex AI (OpenAI-compatible))", + "display_name": "Grok 4.20 Non-Reasoning (Vertex AI (OpenAI-compatible))", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ - "audio" + "text" ] }, "limit": { - "context": 8192, - "output": 8192 + "context": 2000000, + "output": 30000 }, - "temperature": false, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { - "supported": false + "supported": true }, - "attachment": false, + "extra_capabilities": { + "reasoning": { + "supported": true + } + }, + "attachment": true, "open_weights": false, - "release_date": "2026-10-01", - "last_updated": "2026-10-01" + "release_date": "2026-03-09", + "last_updated": "2026-03-09", + "type": "chat" }, { - "id": "inclusionai/ling-3.1-flash-1019", - "name": "Ling 3.1 Flash 1019", - "display_name": "Ling 3.1 Flash 1019", + "id": "vertex-openai/grok-4-20-reasoning", + "name": "Grok 4.20 Reasoning (Vertex AI (OpenAI-compatible))", + "display_name": "Grok 4.20 Reasoning (Vertex AI (OpenAI-compatible))", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 262144 + "context": 2000000, + "output": 30000 }, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -21622,35 +23224,37 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, "effort_options": [ - "none", "low", "medium", "high" ] } }, - "attachment": false, + "attachment": true, "open_weights": false, - "release_date": "2026-10-30", - "last_updated": "2026-10-30", + "release_date": "2026-03-09", + "last_updated": "2026-03-09", "type": "chat" }, { - "id": "inclusionai/ling-3.0-flash-fin", - "name": "Ling 3.0 Flash Fin", - "display_name": "Ling 3.0 Flash Fin", + "id": "vertex-openai/grok-4-7", + "name": "Grok 4.7 (Vertex AI (OpenAI-compatible))", + "display_name": "Grok 4.7 (Vertex AI (OpenAI-compatible))", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 256000, - "output": 32000 + "context": 524288, + "output": 524288 }, "temperature": true, "tool_call": true, @@ -21658,27 +23262,39 @@ "supported": true, "default": true }, - "attachment": false, - "open_weights": true, - "release_date": "2026-08-27", - "last_updated": "2026-09-03", + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "minimal", + "low", + "medium", + "high" + ] + } + }, + "attachment": true, + "open_weights": false, + "knowledge": "2026-05", + "release_date": "2026-09-21", + "last_updated": "2026-09-21", "type": "chat" }, { - "id": "inclusionai/ling-3.0-flash", - "name": "Ling 3.0 Flash", - "display_name": "Ling 3.0 Flash", + "id": "vertex-openai/grok-4-6", + "name": "Grok 4.6 (Vertex AI (OpenAI-compatible))", + "display_name": "Grok 4.6 (Vertex AI (OpenAI-compatible))", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 256000, - "output": 32000 + "context": 500000, + "output": 500000 }, "temperature": true, "tool_call": true, @@ -21686,16 +23302,27 @@ "supported": true, "default": true }, - "attachment": false, - "open_weights": true, - "release_date": "2026-07-23", - "last_updated": "2026-07-23", + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "minimal", + "low", + "medium", + "high" + ] + } + }, + "attachment": true, + "open_weights": false, + "knowledge": "2026-02-01", + "release_date": "2026-08-12", + "last_updated": "2026-08-12", "type": "chat" }, { - "id": "inclusionai/ling-3.1-flash", - "name": "Ling 3.1 Flash", - "display_name": "Ling 3.1 Flash", + "id": "azure-ai-foundry/grok-4-3", + "name": "Grok 4.3 (Azure AI Foundry)", + "display_name": "Grok 4.3 (Azure AI Foundry)", "modalities": { "input": [ "text" @@ -21705,8 +23332,8 @@ ] }, "limit": { - "context": 262144, - "output": 32768 + "context": 20000, + "output": 8192 }, "temperature": true, "tool_call": true, @@ -21725,15 +23352,15 @@ } }, "attachment": false, - "open_weights": true, - "release_date": "2026-09-29", - "last_updated": "2026-09-29", + "open_weights": false, + "release_date": "2026-04-17", + "last_updated": "2026-04-17", "type": "chat" }, { - "id": "inclusionai/ling-3.0-flash-vl", - "name": "Ling 3.0 Flash VL", - "display_name": "Ling 3.0 Flash VL", + "id": "azure-ai-foundry/grok-4-1-fast-reasoning", + "name": "Grok 4.1 Fast Reasoning (Azure AI Foundry)", + "display_name": "Grok 4.1 Fast Reasoning (Azure AI Foundry)", "modalities": { "input": [ "text", @@ -21744,8 +23371,8 @@ ] }, "limit": { - "context": 256000, - "output": 32000 + "context": 128000, + "output": 30000 }, "temperature": true, "tool_call": true, @@ -21753,27 +23380,40 @@ "supported": true, "default": true }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "effort_options": [ + "low", + "medium", + "high" + ] + } + }, "attachment": true, - "open_weights": true, - "release_date": "2026-09-10", - "last_updated": "2026-09-10", + "open_weights": false, + "release_date": "2025-11-19", + "last_updated": "2025-11-19", "type": "chat" }, { - "id": "inclusionai/ling-3.0-flash-sante", - "name": "Ling 3.0 Flash Sante", - "display_name": "Ling 3.0 Flash Sante", + "id": "xiaomi/mimo-v2.6-pro", + "name": "MiMo V2.6 Pro (Xiaomi)", + "display_name": "MiMo V2.6 Pro (Xiaomi)", "modalities": { "input": [ - "text" + "text", + "image", + "audio", + "video" ], "output": [ "text" ] }, "limit": { - "context": 256000, - "output": 32000 + "context": 1000000, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -21791,19 +23431,22 @@ ] } }, - "attachment": false, - "open_weights": false, - "release_date": "2026-09-04", - "last_updated": "2026-09-04", + "attachment": true, + "open_weights": true, + "release_date": "2026-09-22", + "last_updated": "2026-09-22", "type": "chat" }, { - "id": "deepseek/deepseek-v4-flash", - "name": "DeepSeek V4 Flash", - "display_name": "DeepSeek V4 Flash", + "id": "xiaomi/mimo-v2.6-flash", + "name": "MiMo V2.6 Flash (Xiaomi)", + "display_name": "MiMo V2.6 Flash (Xiaomi)", "modalities": { "input": [ - "text" + "text", + "image", + "audio", + "video" ], "output": [ "text" @@ -21811,7 +23454,7 @@ }, "limit": { "context": 1000000, - "output": 384000 + "output": 131072 }, "temperature": true, "tool_call": true, @@ -21821,41 +23464,38 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "high", "effort_options": [ + "none", "low", - "high", - "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." + "medium", + "high" ] } }, - "attachment": false, + "attachment": true, "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-04-24", - "last_updated": "2026-04-24", + "release_date": "2026-09-22", + "last_updated": "2026-09-22", "type": "chat" - }, + } + ] + }, + "cline-pass": { + "id": "cline-pass", + "name": "ClinePass", + "display_name": "ClinePass", + "api": "https://api.cline.bot/api/v1", + "doc": "https://docs.cline.bot/getting-started/clinepass", + "models": [ { - "id": "deepseek/deepseek-v4.1-flash-1017", - "name": "DeepSeek V4.1 Flash 1017", - "display_name": "DeepSeek V4.1 Flash 1017", + "id": "cline-pass/kimi-k3", + "name": "Kimi K3", + "display_name": "Kimi K3", "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ "text" @@ -21863,9 +23503,9 @@ }, "limit": { "context": 1048576, - "output": 262144 + "output": 131072 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -21873,6 +23513,7 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, "effort_options": [ "none", "low", @@ -21883,28 +23524,29 @@ }, "attachment": true, "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-10-17", - "last_updated": "2026-10-17", + "release_date": "2026-07-16", + "last_updated": "2026-07-16", "type": "chat" }, { - "id": "deepseek/deepseek-v4-pro", - "name": "DeepSeek V4 Pro", - "display_name": "DeepSeek V4 Pro", + "id": "cline-pass/kimi-k2.7-code", + "name": "Kimi K2.7 Code", + "display_name": "Kimi K2.7 Code", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 384000 + "context": 262144, + "output": 262144 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -21912,41 +23554,26 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "high", - "effort_options": [ - "low", - "high", - "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." - ] + "supported": true } }, - "attachment": false, + "attachment": true, "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-04-24", - "last_updated": "2026-04-24", + "knowledge": "2025-01", + "release_date": "2026-06-12", + "last_updated": "2026-06-12", "type": "chat" }, { - "id": "deepseek/deepseek-v4.1-flash-fast", - "name": "DeepSeek V4.1 Flash Fast", - "display_name": "DeepSeek V4.1 Flash Fast", + "id": "cline-pass/mimo-v2.6-pro", + "name": "MiMo-V2.6-Pro", + "display_name": "MiMo-V2.6-Pro", "modalities": { "input": [ "text", - "image" + "image", + "audio", + "video" ], "output": [ "text" @@ -21954,7 +23581,7 @@ }, "limit": { "context": 1048576, - "output": 32768 + "output": 131072 }, "temperature": true, "tool_call": true, @@ -21968,25 +23595,28 @@ "none", "low", "medium", - "high" + "high", + "xhigh" ] } }, "attachment": true, "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-09-10", - "last_updated": "2026-09-10", + "release_date": "2026-09-22", + "last_updated": "2026-09-22", "type": "chat" }, { - "id": "deepseek/deepseek-v4-flash-vision-exp", - "name": "DeepSeek V4 Flash Vision Exp", - "display_name": "DeepSeek V4 Flash Vision Exp", + "id": "cline-pass/muse-spark-1.3-contributor", + "name": "Muse Spark 1.3 Contributor", + "display_name": "Muse Spark 1.3 Contributor", "modalities": { "input": [ "text", - "image" + "image", + "video", + "pdf", + "audio" ], "output": [ "text" @@ -21994,7 +23624,7 @@ }, "limit": { "context": 1048576, - "output": 1048576 + "output": 131072 }, "temperature": true, "tool_call": true, @@ -22004,36 +23634,25 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "high", "effort_options": [ + "minimal", "low", + "medium", "high", - "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." + "xhigh" ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-08-21", - "last_updated": "2026-09-01", + "open_weights": false, + "release_date": "2026-09-02", + "last_updated": "2026-09-02", "type": "chat" }, { - "id": "deepseek/deepseek-v3.1", - "name": "DeepSeek-V3.1", - "display_name": "DeepSeek-V3.1", + "id": "cline-pass/qwen3.7-max", + "name": "Qwen3.7 Max", + "display_name": "Qwen3.7 Max", "modalities": { "input": [ "text" @@ -22043,8 +23662,8 @@ ] }, "limit": { - "context": 163840, - "output": 128000 + "context": 1000000, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -22054,28 +23673,53 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "none", - "low", - "medium", - "high" - ] + "supported": true } }, "attachment": false, + "open_weights": false, + "release_date": "2026-05-21", + "last_updated": "2026-05-21", + "type": "chat" + }, + { + "id": "cline-pass/mimo-v2.5-pro", + "name": "MiMo-V2.5-Pro", + "display_name": "MiMo-V2.5-Pro", + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1048576, + "output": 131072 + }, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": true, + "default": true + }, + "attachment": false, "open_weights": true, - "release_date": "2025-08-21", - "last_updated": "2025-08-21", + "knowledge": "2024-12", + "release_date": "2026-04-22", + "last_updated": "2026-04-22", "type": "chat" }, { - "id": "deepseek/deepseek-v4.1-flash", - "name": "DeepSeek V4.1 Flash", - "display_name": "DeepSeek V4.1 Flash", + "id": "cline-pass/glm-5.3-flash", + "name": "cline-pass/glm-5.3-flash", + "display_name": "cline-pass/glm-5.3-flash", "modalities": { "input": [ "text", "image", + "video", "pdf" ], "output": [ @@ -22083,8 +23727,8 @@ ] }, "limit": { - "context": 1048576, - "output": 32768 + "context": 1000000, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -22095,8 +23739,13 @@ "extra_capabilities": { "reasoning": { "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ - "none", "low", "high", "max" @@ -22105,51 +23754,60 @@ }, "attachment": true, "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-09-10", - "last_updated": "2026-09-10", + "release_date": "2026-08-26", + "last_updated": "2026-08-26", "type": "chat" }, { - "id": "deepseek/deepseek-v3.2", - "name": "DeepSeek V3.2", - "display_name": "DeepSeek V3.2", + "id": "cline-pass/mimo-v2.6-flash", + "name": "MiMo-V2.6-Flash", + "display_name": "MiMo-V2.6-Flash", "modalities": { "input": [ - "text" + "text", + "image", + "audio", + "video" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 8000 + "context": 1048576, + "output": 131072 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": true + "supported": true, + "default": true }, "extra_capabilities": { "reasoning": { - "supported": true + "effort_options": [ + "none", + "low", + "medium", + "high", + "xhigh" + ] } }, - "attachment": false, + "attachment": true, "open_weights": true, - "knowledge": "2024-07", - "release_date": "2025-12-01", - "last_updated": "2025-12-01", + "release_date": "2026-09-22", + "last_updated": "2026-09-22", "type": "chat" }, { - "id": "deepseek/deepseek-v4-flash-0731", - "name": "DeepSeek V4 Flash 0731", - "display_name": "DeepSeek V4 Flash 0731", + "id": "cline-pass/deepseek-v4.1-flash", + "name": "DeepSeek V4.1 Flash", + "display_name": "DeepSeek V4.1 Flash", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" @@ -22171,22 +23829,22 @@ "effort_options": [ "none", "low", - "medium", - "high" + "high", + "max" ] } }, - "attachment": false, + "attachment": true, "open_weights": true, "knowledge": "2025-05", - "release_date": "2026-07-31", - "last_updated": "2026-07-31", + "release_date": "2026-09-10", + "last_updated": "2026-09-10", "type": "chat" }, { - "id": "deepseek/deepseek-v4-pro-0813", - "name": "DeepSeek V4 Pro 0813", - "display_name": "DeepSeek V4 Pro 0813", + "id": "cline-pass/deepseek-v4-pro", + "name": "DeepSeek V4 Pro", + "display_name": "DeepSeek V4 Pro", "modalities": { "input": [ "text" @@ -22208,36 +23866,47 @@ "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "high", "effort_options": [ - "none", "low", "high", "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." ] } }, "attachment": false, "open_weights": true, - "release_date": "2026-08-13", - "last_updated": "2026-08-22", + "knowledge": "2025-05", + "release_date": "2026-04-24", + "last_updated": "2026-04-24", "type": "chat" }, { - "id": "deepseek/deepseek-v4.1-flash-1017-fast", - "name": "DeepSeek V4.1 Flash 1017 Fast", - "display_name": "DeepSeek V4.1 Flash 1017 Fast", + "id": "cline-pass/glm-5.3", + "name": "GLM-5.3", + "display_name": "GLM-5.3", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 262144 + "context": 1000000, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -22247,25 +23916,62 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ - "none", "low", - "medium", - "high" + "high", + "max" ] } }, + "attachment": false, + "open_weights": true, + "release_date": "2026-08-14", + "last_updated": "2026-08-14", + "type": "chat" + }, + { + "id": "cline-pass/mimo-v2.5", + "name": "MiMo-V2.5", + "display_name": "MiMo-V2.5", + "modalities": { + "input": [ + "text", + "image", + "audio", + "video" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1048576, + "output": 131072 + }, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": true, + "default": true + }, "attachment": true, "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-10-17", - "last_updated": "2026-10-17", + "knowledge": "2024-12", + "release_date": "2026-04-22", + "last_updated": "2026-04-22", "type": "chat" }, { - "id": "deepseek/deepseek-v3.2-thinking", - "name": "DeepSeek V3.2 Thinking", - "display_name": "DeepSeek V3.2 Thinking", + "id": "cline-pass/deepseek-v4-flash", + "name": "DeepSeek V4 Flash", + "display_name": "DeepSeek V4 Flash", "modalities": { "input": [ "text" @@ -22275,8 +23981,8 @@ ] }, "limit": { - "context": 128000, - "output": 8000 + "context": 1000000, + "output": 384000 }, "temperature": true, "tool_call": true, @@ -22287,26 +23993,36 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, + "default_enabled": true, + "mode": "effort", + "effort": "high", "effort_options": [ - "none", "low", - "medium", - "high" + "high", + "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." ] } }, "attachment": false, "open_weights": true, - "knowledge": "2024-07", - "release_date": "2025-12-01", - "last_updated": "2025-12-01", + "knowledge": "2025-05", + "release_date": "2026-04-24", + "last_updated": "2026-04-24", "type": "chat" }, { - "id": "deepseek/deepseek-r1", - "name": "DeepSeek-R1", - "display_name": "DeepSeek-R1", + "id": "cline-pass/glm-5.2", + "name": "GLM-5.2", + "display_name": "GLM-5.2", "modalities": { "input": [ "text" @@ -22316,11 +24032,11 @@ ] }, "limit": { - "context": 128000, - "output": 32768 + "context": 1000000, + "output": 131072 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { "supported": true, "default": true @@ -22328,29 +24044,28 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "none", + "high", + "max" ] } }, "attachment": false, "open_weights": true, - "knowledge": "2024-07", - "release_date": "2025-01-20", - "last_updated": "2025-05-29", + "release_date": "2026-06-13", + "last_updated": "2026-06-13", "type": "chat" }, { - "id": "google/gemini-2.5-pro", - "name": "Gemini 2.5 Pro", - "display_name": "Gemini 2.5 Pro", + "id": "cline-pass/qwen3.8-max", + "name": "Qwen3.8 Max", + "display_name": "Qwen3.8 Max", "modalities": { "input": [ "text", "image", + "video", "pdf" ], "output": [ @@ -22358,8 +24073,8 @@ ] }, "limit": { - "context": 1048576, - "output": 65535 + "context": 1000000, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -22370,67 +24085,64 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "budget", - "budget": { - "default": -1, - "min": 128, - "max": 32768, - "auto": -1, - "unit": "tokens" - }, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thought_signatures" + "effort_options": [ + "none", + "low", + "medium", + "xhigh" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-01", - "release_date": "2025-06-17", - "last_updated": "2025-06-17", + "release_date": "2026-08-03", + "last_updated": "2026-08-03", "type": "chat" }, { - "id": "google/gemini-2.5-flash-image", - "name": "Nano Banana (Gemini 2.5 Flash Image)", - "display_name": "Nano Banana (Gemini 2.5 Flash Image)", + "id": "cline-pass/minimax-m3", + "name": "MiniMax-M3", + "display_name": "MiniMax-M3", "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ - "text", - "image" + "text" ] }, "limit": { - "context": 32768, - "output": 65535 + "context": 1048576, + "output": 512000 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true + } }, "attachment": true, - "open_weights": false, - "knowledge": "2024-06", - "release_date": "2025-08-26", - "last_updated": "2025-08-26" + "open_weights": true, + "release_date": "2026-06-01", + "last_updated": "2026-06-01", + "type": "chat" }, { - "id": "google/gemini-3.5-flash-lite", - "name": "Gemini 3.5 Flash Lite", - "display_name": "Gemini 3.5 Flash Lite", + "id": "cline-pass/qwen3.7-plus", + "name": "Qwen3.7 Plus", + "display_name": "Qwen3.7 Plus", "modalities": { "input": [ "text", "image", - "pdf" + "video" ], "output": [ "text" @@ -22438,7 +24150,7 @@ }, "limit": { "context": 1000000, - "output": 65000 + "output": 64000 }, "temperature": true, "tool_call": true, @@ -22448,66 +24160,79 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "effort_options": [ - "low", - "high" - ] + "supported": true } }, "attachment": true, "open_weights": false, - "knowledge": "2026-03", - "release_date": "2026-07-21", - "last_updated": "2026-07-21", + "knowledge": "2025-04", + "release_date": "2026-06-02", + "last_updated": "2026-06-02", "type": "chat" }, { - "id": "google/gemini-3.5-transcribe", - "name": "Gemini 3.5 Transcribe", - "display_name": "Gemini 3.5 Transcribe", + "id": "cline-pass/kimi-k2.6", + "name": "Kimi K2.6", + "display_name": "Kimi K2.6", "modalities": { "input": [ - "audio" + "text", + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 98304, - "output": 32768 + "context": 262144, + "output": 262144 }, - "temperature": false, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true + } }, "attachment": true, - "open_weights": false, - "release_date": "2026-08-26", - "last_updated": "2026-08-26" - }, + "open_weights": true, + "knowledge": "2025-01", + "release_date": "2026-04-20", + "last_updated": "2026-04-20", + "type": "chat" + } + ] + }, + "openai": { + "id": "openai", + "name": "OpenAI", + "display_name": "OpenAI", + "doc": "https://platform.openai.com/docs/models", + "models": [ { - "id": "google/gemini-3.1-flash-image", - "name": "Nano Banana 2", - "display_name": "Nano Banana 2", + "id": "gpt-5.2-pro", + "name": "GPT-5.2 Pro", + "display_name": "GPT-5.2 Pro", "modalities": { "input": [ "text", "image" ], "output": [ - "text", - "image" + "text" ] }, "limit": { - "context": 131072, - "output": 32768 + "context": 400000, + "output": 128000 }, - "temperature": true, - "tool_call": false, + "temperature": false, + "tool_call": true, "reasoning": { "supported": true, "default": true @@ -22515,138 +24240,217 @@ "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "high", "effort_options": [ + "medium", + "high", + "xhigh" + ], + "verbosity": "medium", + "verbosity_options": [ "low", + "medium", "high" - ] + ], + "visibility": "hidden" } }, "attachment": true, "open_weights": false, - "knowledge": "2025-01", - "release_date": "2026-05-28", - "last_updated": "2026-05-28" + "knowledge": "2025-08-31", + "release_date": "2025-12-11", + "last_updated": "2025-12-11", + "type": "chat" }, { - "id": "google/veo-3.0-fast-generate-001", - "name": "Veo 3.0 Fast Generate", - "display_name": "Veo 3.0 Fast Generate", + "id": "gpt-5-pro", + "name": "GPT-5 Pro", + "display_name": "GPT-5 Pro", "modalities": { "input": [ "text", "image" ], "output": [ - "video" + "text" ] }, "limit": { - "context": 8192, - "output": 8192 + "context": 400000, + "output": 272000 }, "temperature": false, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "fixed", + "effort": "high", + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" + } }, "attachment": true, "open_weights": false, - "release_date": "2025-07-31", - "last_updated": "2026-06-08" + "knowledge": "2024-09-30", + "release_date": "2025-10-06", + "last_updated": "2025-10-06", + "type": "chat" }, { - "id": "google/gemini-3.5-transcribe-live", - "name": "Gemini 3.5 Transcribe Live", - "display_name": "Gemini 3.5 Transcribe Live", + "id": "o3-mini", + "name": "o3-mini", + "display_name": "o3-mini", "modalities": { "input": [ - "audio" + "text" ], "output": [ "text" ] }, "limit": { - "context": 8192, - "output": 8192 + "context": 200000, + "output": 100000 }, "temperature": false, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": true, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", + "effort_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" + } + }, + "attachment": false, "open_weights": false, - "release_date": "2026-08-26", - "last_updated": "2026-08-26" + "knowledge": "2024-05", + "release_date": "2024-12-20", + "last_updated": "2025-01-29", + "type": "chat" }, { - "id": "google/gemma-4-31b-it", - "name": "Gemma 4 31B IT", - "display_name": "Gemma 4 31B IT", + "id": "gpt-5.4-nano", + "name": "GPT-5.4 nano", + "display_name": "GPT-5.4 nano", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 131072 + "context": 400000, + "output": 128000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true + "supported": true, + "default": false }, "extra_capabilities": { "reasoning": { - "supported": true + "supported": true, + "default_enabled": false, + "mode": "effort", + "effort": "none", + "effort_options": [ + "none", + "low", + "medium", + "high", + "xhigh" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, "attachment": true, - "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-04-02", - "last_updated": "2026-04-02", + "open_weights": false, + "knowledge": "2025-08-31", + "release_date": "2026-03-17", + "last_updated": "2026-03-17", "type": "chat" }, { - "id": "google/gemini-3.8-live", - "name": "Gemini 3.8 Live", - "display_name": "Gemini 3.8 Live", + "id": "gpt-6.1-terra", + "name": "GPT-6.1 Terra", + "display_name": "GPT-6.1 Terra", "modalities": { "input": [ "text", - "audio" + "image", + "pdf" ], "output": [ - "text", - "audio" + "text" ] }, "limit": { - "context": 131072, - "output": 65536 + "context": 1050000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "none", + "low", + "medium", + "high", + "xhigh", + "max" + ] + } }, "attachment": true, "open_weights": false, - "release_date": "2026-09-15", - "last_updated": "2026-09-15" + "knowledge": "2026-06-01", + "release_date": "2026-10-26", + "last_updated": "2026-10-26", + "type": "chat" }, { - "id": "google/gemini-2.5-flash", - "name": "Gemini 2.5 Flash", - "display_name": "Gemini 2.5 Flash", + "id": "gpt-5.4", + "name": "GPT-5.4", + "display_name": "GPT-5.4", "modalities": { "input": [ "text", @@ -22658,86 +24462,100 @@ ] }, "limit": { - "context": 1000000, - "output": 65535 + "context": 1050000, + "output": 128000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "budget", - "budget": { - "default": -1, - "min": 0, - "max": 24576, - "auto": -1, - "off": 0, - "unit": "tokens" - }, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thought_signatures" - ] + "default_enabled": false, + "mode": "effort", + "effort": "none", + "effort_options": [ + "none", + "low", + "medium", + "high", + "xhigh" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, "attachment": true, "open_weights": false, - "knowledge": "2025-01", - "release_date": "2025-06-17", - "last_updated": "2025-06-17", + "knowledge": "2025-08-31", + "release_date": "2026-03-05", + "last_updated": "2026-03-05", "type": "chat" }, { - "id": "google/gemini-3.1-flash-lite", - "name": "Gemini 3.1 Flash Lite", - "display_name": "Gemini 3.1 Flash Lite", + "id": "gpt-5.4-mini", + "name": "GPT-5.4 mini", + "display_name": "GPT-5.4 mini", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 65000 + "context": 400000, + "output": 128000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": false, + "mode": "effort", + "effort": "none", "effort_options": [ + "none", "low", + "medium", + "high", + "xhigh" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", "high" - ] + ], + "visibility": "hidden" } }, "attachment": true, "open_weights": false, - "knowledge": "2025-01", - "release_date": "2026-05-07", - "last_updated": "2026-05-07", + "knowledge": "2025-08-31", + "release_date": "2026-03-17", + "last_updated": "2026-03-17", "type": "chat" }, { - "id": "google/gemini-3.7-flash", - "name": "Gemini 3.7 Flash", - "display_name": "Gemini 3.7 Flash", + "id": "gpt-6-sol", + "name": "GPT-6 Sol", + "display_name": "GPT-6 Sol", "modalities": { "input": [ "text", @@ -22749,10 +24567,10 @@ ] }, "limit": { - "context": 1000000, - "output": 65535 + "context": 1050000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -22760,77 +24578,97 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "level", - "level": "high", - "level_options": [ - "minimal", + "effort_options": [ + "none", "low", "medium", - "high" - ], - "summaries": true, - "visibility": "summary", - "continuation": [ - "thought_signatures" + "high", + "xhigh", + "max" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-03", - "release_date": "2026-08-13", - "last_updated": "2026-08-13", + "knowledge": "2026-04-20", + "release_date": "2026-09-22", + "last_updated": "2026-09-22", "type": "chat" }, { - "id": "google/text-embedding-005", - "name": "Text Embedding 005", - "display_name": "Text Embedding 005", + "id": "gpt-5.6-terra", + "name": "GPT-5.6 Terra", + "display_name": "GPT-5.6 Terra", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 8192, - "output": 8192 + "context": 1050000, + "output": 128000 }, "temperature": false, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", + "effort_options": [ + "none", + "low", + "medium", + "high", + "xhigh", + "max" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" + } + }, + "attachment": true, "open_weights": false, - "release_date": "2024-08-01", - "last_updated": "2024-08", - "type": "embedding" + "knowledge": "2026-02-16", + "release_date": "2026-07-09", + "last_updated": "2026-07-09", + "type": "chat" }, { - "id": "google/gemini-3-pro-image", - "name": "Nano Banana Pro", - "display_name": "Nano Banana Pro", + "id": "gpt-5.3-codex", + "name": "GPT-5.3 Codex", + "display_name": "GPT-5.3 Codex", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ - "text", - "image" + "text" ] }, "limit": { - "context": 65536, - "output": 32768 + "context": 400000, + "output": 128000 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { "supported": true, "default": true @@ -22839,29 +24677,34 @@ "reasoning": { "supported": true, "default_enabled": true, - "mode": "level", - "level": "high", - "level_options": [ + "mode": "effort", + "effort": "medium", + "effort_options": [ + "low", + "medium", + "high", + "xhigh" + ], + "verbosity": "medium", + "verbosity_options": [ "low", + "medium", "high" ], - "summaries": true, - "visibility": "summary", - "continuation": [ - "thought_signatures" - ] + "visibility": "hidden" } }, "attachment": true, "open_weights": false, - "knowledge": "2025-01", - "release_date": "2026-05-28", - "last_updated": "2026-05-28" + "knowledge": "2025-08-31", + "release_date": "2026-02-05", + "last_updated": "2026-02-05", + "type": "chat" }, { - "id": "google/gemma-4-26b-a4b-it", - "name": "Gemma 4 26B A4B IT", - "display_name": "Gemma 4 26B A4B IT", + "id": "gpt-6-astra", + "name": "GPT-6 Astra", + "display_name": "GPT-6 Astra", "modalities": { "input": [ "text", @@ -22873,10 +24716,10 @@ ] }, "limit": { - "context": 262144, - "output": 131072 + "context": 1050000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -22884,119 +24727,123 @@ }, "extra_capabilities": { "reasoning": { - "supported": true + "supported": true, + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" + ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-04-02", - "last_updated": "2026-04-02", + "open_weights": false, + "knowledge": "2026-04-30", + "release_date": "2026-09-04", + "last_updated": "2026-09-04", "type": "chat" }, { - "id": "google/gemini-embedding-2", - "name": "Gemini Embedding 2", - "display_name": "Gemini Embedding 2", + "id": "gpt-4o-2024-08-06", + "name": "GPT-4o (2024-08-06)", + "display_name": "GPT-4o (2024-08-06)", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 8192, - "output": 3072 + "context": 128000, + "output": 16384 }, - "temperature": false, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { "supported": false }, - "attachment": false, + "attachment": true, "open_weights": false, - "knowledge": "2025-11", - "release_date": "2026-04-22", - "last_updated": "2026-04-22", - "type": "embedding" + "knowledge": "2023-09", + "release_date": "2024-08-06", + "last_updated": "2024-08-06", + "type": "chat" }, { - "id": "google/veo-3.1-fast-generate-001", - "name": "Veo 3.1 Fast Generate", - "display_name": "Veo 3.1 Fast Generate", + "id": "gpt-5.2", + "name": "GPT-5.2", + "display_name": "GPT-5.2", "modalities": { "input": [ "text", - "image", - "video" + "image" ], "output": [ - "video" + "text" ] }, "limit": { - "context": 8192, - "output": 8192 + "context": 400000, + "output": 128000 }, - "temperature": false, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": false + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": false, + "mode": "effort", + "effort": "none", + "effort_options": [ + "none", + "low", + "medium", + "high", + "xhigh" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" + } }, "attachment": true, "open_weights": false, - "release_date": "2025-10-15", - "last_updated": "2026-06-08" + "knowledge": "2025-08-31", + "release_date": "2025-12-11", + "last_updated": "2025-12-11", + "type": "chat" }, { - "id": "google/gemini-3.8-live-extended-thinking", - "name": "Gemini 3.8 Live Extended Thinking", - "display_name": "Gemini 3.8 Live Extended Thinking", + "id": "gpt-5.4-pro", + "name": "GPT-5.4 Pro", + "display_name": "GPT-5.4 Pro", "modalities": { "input": [ "text", - "audio" + "image" ], "output": [ - "text", - "audio" + "text" ] }, "limit": { - "context": 131072, - "output": 65536 + "context": 1050000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, - "reasoning": { - "supported": false - }, - "attachment": true, - "open_weights": false, - "release_date": "2026-09-15", - "last_updated": "2026-09-15" - }, - { - "id": "google/gemini-3.1-flash-image-preview", - "name": "Gemini 3.1 Flash Image Preview (Nano Banana 2)", - "display_name": "Gemini 3.1 Flash Image Preview (Nano Banana 2)", - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text", - "image" - ] - }, - "limit": { - "context": 131072, - "output": 32768 - }, - "temperature": true, - "tool_call": false, "reasoning": { "supported": true, "default": true @@ -23004,22 +24851,34 @@ "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "high", "effort_options": [ + "medium", + "high", + "xhigh" + ], + "verbosity": "medium", + "verbosity_options": [ "low", + "medium", "high" - ] + ], + "visibility": "hidden" } }, "attachment": true, "open_weights": false, - "knowledge": "2025-01", - "release_date": "2026-02-26", - "last_updated": "2026-02-26" + "knowledge": "2025-08-31", + "release_date": "2026-03-05", + "last_updated": "2026-03-05", + "type": "chat" }, { - "id": "google/gemini-3.1-flash-lite-image", - "name": "Gemini 3.1 Flash Lite Image (Nano Banana 2 Lite)", - "display_name": "Gemini 3.1 Flash Lite Image (Nano Banana 2 Lite)", + "id": "gpt-image-1.5", + "name": "gpt-image-1.5", + "display_name": "gpt-image-1.5", "modalities": { "input": [ "text", @@ -23031,49 +24890,37 @@ ] }, "limit": { - "context": 65536, - "output": 4096 + "context": 8192, + "output": 8192 }, - "temperature": true, + "temperature": false, "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "effort_options": [ - "low", - "high" - ] - } + "supported": false }, "attachment": true, "open_weights": false, - "knowledge": "2025-01", - "release_date": "2026-06-30", - "last_updated": "2026-06-30" + "release_date": "2025-11-25", + "last_updated": "2025-11-25" }, { - "id": "google/gemini-3-flash", - "name": "Gemini 3 Flash", - "display_name": "Gemini 3 Flash", + "id": "o1-pro", + "name": "o1-pro", + "display_name": "o1-pro", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 65000 + "context": 200000, + "output": 100000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -23083,32 +24930,27 @@ "reasoning": { "supported": true, "default_enabled": true, - "mode": "level", - "level": "high", - "level_options": [ - "minimal", + "mode": "effort", + "effort": "medium", + "effort_options": [ "low", "medium", "high" ], - "summaries": true, - "visibility": "summary", - "continuation": [ - "thought_signatures" - ] + "visibility": "hidden" } }, "attachment": true, "open_weights": false, - "knowledge": "2025-03", - "release_date": "2025-12-17", - "last_updated": "2025-12-17", + "knowledge": "2023-09", + "release_date": "2025-03-19", + "last_updated": "2025-03-19", "type": "chat" }, { - "id": "google/gemini-3.1-pro-preview", - "name": "Gemini 3.1 Pro Preview", - "display_name": "Gemini 3.1 Pro Preview", + "id": "gpt-6.1-sol", + "name": "GPT-6.1 Sol", + "display_name": "GPT-6.1 Sol", "modalities": { "input": [ "text", @@ -23120,10 +24962,10 @@ ] }, "limit": { - "context": 1000000, - "output": 64000 + "context": 1050000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -23131,98 +24973,41 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "level", - "level": "high", - "level_options": [ + "effort_options": [ "low", - "high" - ], - "summaries": true, - "visibility": "summary", - "continuation": [ - "thought_signatures" + "medium", + "high", + "xhigh", + "max" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-01", - "release_date": "2026-02-19", - "last_updated": "2026-02-19", + "knowledge": "2026-04-30", + "release_date": "2026-09-29", + "last_updated": "2026-09-29", "type": "chat" }, { - "id": "google/gemini-2.5-flash-lite", - "name": "Gemini 2.5 Flash Lite", - "display_name": "Gemini 2.5 Flash Lite", + "id": "gpt-daybreak-red-latest", + "name": "Daybreak Red", + "display_name": "Daybreak Red", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 65535 + "context": 400000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, - "reasoning": { - "supported": true, - "default": false - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "budget", - "budget": { - "default": -1, - "min": 512, - "max": 24576, - "auto": -1, - "unit": "tokens" - }, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thought_signatures" - ] - } - }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-01", - "release_date": "2025-06-17", - "last_updated": "2025-06-17", - "type": "chat" - }, - { - "id": "google/gemini-omni-flash-preview", - "name": "Gemini Omni Flash Preview", - "display_name": "Gemini Omni Flash Preview", - "modalities": { - "input": [ - "text", - "image", - "pdf" - ], - "output": [ - "video" - ] - }, - "limit": { - "context": 1000000, - "output": 57920 - }, - "temperature": true, - "tool_call": false, "reasoning": { "supported": true, "default": true @@ -23233,73 +25018,81 @@ "none", "low", "medium", - "high" + "high", + "xhigh", + "max" ] } }, "attachment": true, "open_weights": false, - "release_date": "2026-06-30", - "last_updated": "2026-06-30" + "knowledge": "2026-02-16", + "release_date": "2026-08-07", + "last_updated": "2026-08-07", + "type": "chat" }, { - "id": "google/gemini-3.8-flash-lite-tts", - "name": "Gemini 3.8 Flash-Lite TTS", - "display_name": "Gemini 3.8 Flash-Lite TTS", + "id": "gpt-4o-2024-11-20", + "name": "GPT-4o (2024-11-20)", + "display_name": "GPT-4o (2024-11-20)", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ - "audio" + "text" ] }, "limit": { - "context": 8192, + "context": 128000, "output": 16384 }, - "temperature": false, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { "supported": false }, - "attachment": false, + "attachment": true, "open_weights": false, - "release_date": "2026-09-22", - "last_updated": "2026-09-22" + "knowledge": "2023-09", + "release_date": "2024-11-20", + "last_updated": "2024-11-20", + "type": "chat" }, { - "id": "google/veo-3.1-lite-generate-001", - "name": "Veo 3.1 Lite Generate", - "display_name": "Veo 3.1 Lite Generate", + "id": "gpt-4o-2024-05-13", + "name": "GPT-4o (2024-05-13)", + "display_name": "GPT-4o (2024-05-13)", "modalities": { "input": [ "text", - "image", - "video" + "image" ], "output": [ - "video" + "text" ] }, "limit": { - "context": 8192, - "output": 8192 + "context": 128000, + "output": 4096 }, - "temperature": false, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { "supported": false }, "attachment": true, "open_weights": false, - "release_date": "2026-04-02", - "last_updated": "2026-04-02" + "knowledge": "2023-09", + "release_date": "2024-05-13", + "last_updated": "2024-05-13", + "type": "chat" }, { - "id": "google/gemini-3.8-flash", - "name": "Gemini 3.8 Flash", - "display_name": "Gemini 3.8 Flash", + "id": "gpt-5.3-codex-spark", + "name": "GPT-5.3 Codex Spark", + "display_name": "GPT-5.3 Codex Spark", "modalities": { "input": [ "text", @@ -23311,10 +25104,10 @@ ] }, "limit": { - "context": 1000000, - "output": 65535 + "context": 128000, + "output": 32000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -23324,113 +25117,105 @@ "reasoning": { "supported": true, "default_enabled": true, - "mode": "level", - "level": "high", - "level_options": [ - "minimal", + "mode": "effort", + "effort": "medium", + "effort_options": [ + "low", + "medium", + "high", + "xhigh" + ], + "verbosity": "medium", + "verbosity_options": [ "low", "medium", "high" ], - "summaries": true, - "visibility": "summary", - "continuation": [ - "thought_signatures" - ] + "visibility": "hidden" } }, "attachment": true, "open_weights": false, - "release_date": "2026-09-02", - "last_updated": "2026-09-02", + "knowledge": "2025-08-31", + "release_date": "2026-02-05", + "last_updated": "2026-02-05", "type": "chat" }, { - "id": "google/veo-3.0-generate-001", - "name": "Veo 3.0", - "display_name": "Veo 3.0", + "id": "text-embedding-3-large", + "name": "text-embedding-3-large", + "display_name": "text-embedding-3-large", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ - "video" + "text" ] }, "limit": { - "context": 8192, - "output": 8192 + "context": 8191, + "output": 3072 }, "temperature": false, "tool_call": false, "reasoning": { "supported": false }, - "attachment": true, + "attachment": false, "open_weights": false, - "release_date": "2025-05-20", - "last_updated": "2026-06-08" + "knowledge": "2024-01", + "release_date": "2024-01-25", + "last_updated": "2024-01-25", + "type": "embedding" }, { - "id": "google/gemini-4-argon", - "name": "Gemini 4 Argon", - "display_name": "Gemini 4 Argon", + "id": "gpt-4.1-nano", + "name": "GPT-4.1 nano", + "display_name": "GPT-4.1 nano", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 1000000 + "context": 1047576, + "output": 32768 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "low", - "medium", - "high" - ] - } + "supported": false }, "attachment": true, "open_weights": false, - "knowledge": "2026-03", - "release_date": "2026-10-13", - "last_updated": "2026-10-13", + "knowledge": "2024-04", + "release_date": "2025-04-14", + "last_updated": "2025-04-14", "type": "chat" }, { - "id": "google/gemini-3.6-flash", - "name": "Gemini 3.6 Flash", - "display_name": "Gemini 3.6 Flash", + "id": "gpt-5-nano", + "name": "GPT-5 Nano", + "display_name": "GPT-5 Nano", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 64000 + "context": 400000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -23440,32 +25225,34 @@ "reasoning": { "supported": true, "default_enabled": true, - "mode": "level", - "level": "high", - "level_options": [ + "mode": "effort", + "effort": "medium", + "effort_options": [ "minimal", "low", "medium", "high" ], - "summaries": true, - "visibility": "summary", - "continuation": [ - "thought_signatures" - ] + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, "attachment": true, "open_weights": false, - "knowledge": "2026-03", - "release_date": "2026-07-21", - "last_updated": "2026-07-21", + "knowledge": "2024-05-30", + "release_date": "2025-08-07", + "last_updated": "2025-08-07", "type": "chat" }, { - "id": "google/gemini-embedding-001", - "name": "Gemini Embedding 001", - "display_name": "Gemini Embedding 001", + "id": "text-embedding-ada-002", + "name": "text-embedding-ada-002", + "display_name": "text-embedding-ada-002", "modalities": { "input": [ "text" @@ -23475,8 +25262,8 @@ ] }, "limit": { - "context": 2048, - "output": 1 + "context": 8192, + "output": 1536 }, "temperature": false, "tool_call": false, @@ -23485,30 +25272,29 @@ }, "attachment": false, "open_weights": false, - "knowledge": "2025-05", - "release_date": "2025-05-20", - "last_updated": "2025-05-20", + "knowledge": "2022-12", + "release_date": "2022-12-15", + "last_updated": "2022-12-15", "type": "embedding" }, { - "id": "google/gemini-3.5-flash", - "name": "Gemini 3.5 Flash", - "display_name": "Gemini 3.5 Flash", + "id": "o3-pro", + "name": "o3-pro", + "display_name": "o3-pro", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 64000 + "context": 200000, + "output": 100000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -23518,166 +25304,180 @@ "reasoning": { "supported": true, "default_enabled": true, - "mode": "level", - "level": "high", - "level_options": [ - "minimal", + "mode": "effort", + "effort": "medium", + "effort_options": [ "low", "medium", "high" ], - "summaries": true, - "visibility": "summary", - "continuation": [ - "thought_signatures" - ] + "visibility": "hidden" } }, "attachment": true, "open_weights": false, - "knowledge": "2025-01", - "release_date": "2026-05-19", - "last_updated": "2026-05-19", + "knowledge": "2024-05", + "release_date": "2025-06-10", + "last_updated": "2025-06-10", "type": "chat" }, { - "id": "google/gemini-nano-banana-2.1", - "name": "Gemini Nano Banana 2.1", - "display_name": "Gemini Nano Banana 2.1", + "id": "gpt-5.5-pro", + "name": "GPT-5.5 Pro", + "display_name": "GPT-5.5 Pro", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ - "text", - "image" + "text" ] }, "limit": { - "context": 131072, - "output": 32768 + "context": 1050000, + "output": 128000 }, "temperature": false, - "tool_call": false, + "tool_call": true, "reasoning": { "supported": true, "default": true }, "extra_capabilities": { "reasoning": { + "supported": true, "effort_options": [ - "minimal", "medium", - "high" + "high", + "xhigh" ] } }, "attachment": true, "open_weights": false, - "release_date": "2026-10-06", - "last_updated": "2026-10-06" - }, - { - "id": "google/gemini-3.8-flash-tts", - "name": "Gemini 3.8 Flash TTS", - "display_name": "Gemini 3.8 Flash TTS", - "modalities": { - "input": [ - "text" - ], - "output": [ - "audio" - ] - }, - "limit": { - "context": 8192, - "output": 16384 - }, - "temperature": false, - "tool_call": false, - "reasoning": { - "supported": false - }, - "attachment": false, - "open_weights": false, - "release_date": "2026-09-22", - "last_updated": "2026-09-22" + "knowledge": "2025-12-01", + "release_date": "2026-04-23", + "last_updated": "2026-04-23", + "type": "chat" }, { - "id": "google/veo-3.1-generate-001", - "name": "Veo 3.1", - "display_name": "Veo 3.1", + "id": "gpt-5.6-luna", + "name": "GPT-5.6 Luna", + "display_name": "GPT-5.6 Luna", "modalities": { "input": [ "text", "image", - "video" + "pdf" ], "output": [ - "video" + "text" ] }, "limit": { - "context": 8192, - "output": 8192 + "context": 1050000, + "output": 128000 }, "temperature": false, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", + "effort_options": [ + "none", + "low", + "medium", + "high", + "xhigh", + "max" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" + } }, "attachment": true, "open_weights": false, - "release_date": "2025-10-15", - "last_updated": "2026-06-08" + "knowledge": "2026-02-16", + "release_date": "2026-07-09", + "last_updated": "2026-07-09", + "type": "chat" }, { - "id": "google/text-multilingual-embedding-002", - "name": "Text Multilingual Embedding 002", - "display_name": "Text Multilingual Embedding 002", + "id": "o4-mini", + "name": "o4-mini", + "display_name": "o4-mini", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 8192, - "output": 8192 + "context": 200000, + "output": 100000 }, "temperature": false, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", + "effort_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" + } + }, + "attachment": true, "open_weights": false, - "release_date": "2024-03-01", - "last_updated": "2024-03", - "type": "embedding" + "knowledge": "2024-05", + "release_date": "2025-04-16", + "last_updated": "2025-04-16", + "type": "chat" }, { - "id": "thinkingmachines/inkling", - "name": "Inkling", - "display_name": "Inkling", + "id": "gpt-5", + "name": "GPT-5", + "display_name": "GPT-5", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 256000, - "output": 256000 + "context": 400000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -23685,42 +25485,50 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", "effort_options": [ - "none", "minimal", "low", "medium", - "high", - "xhigh", - "max" - ] + "high" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, "attachment": true, - "open_weights": true, - "release_date": "2026-07-15", - "last_updated": "2026-07-15", + "open_weights": false, + "knowledge": "2024-09-30", + "release_date": "2025-08-07", + "last_updated": "2025-08-07", "type": "chat" }, { - "id": "thinkingmachines/inkling-small", - "name": "Inkling Small", - "display_name": "Inkling Small", + "id": "gpt-5-mini", + "name": "GPT-5 Mini", + "display_name": "GPT-5 Mini", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 1000000 + "context": 400000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -23728,64 +25536,47 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", "effort_options": [ - "none", "minimal", "low", "medium", - "high", - "xhigh", - "max" - ] + "high" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, "attachment": true, - "open_weights": true, - "release_date": "2026-07-30", - "last_updated": "2026-07-30", + "open_weights": false, + "knowledge": "2024-05-30", + "release_date": "2025-08-07", + "last_updated": "2025-08-07", "type": "chat" }, { - "id": "recraft/recraft-v2", - "name": "Recraft V2", - "display_name": "Recraft V2", + "id": "text-embedding-3-small", + "name": "text-embedding-3-small", + "display_name": "text-embedding-3-small", "modalities": { "input": [ "text" ], "output": [ - "image" - ] - }, - "limit": { - "context": 512, - "output": 8192 - }, - "temperature": false, - "tool_call": false, - "reasoning": { - "supported": false - }, - "attachment": false, - "open_weights": false, - "release_date": "2024-03-13", - "last_updated": "2024-03" - }, - { - "id": "recraft/recraft-v4.1-utility", - "name": "Recraft V4.1 Utility", - "display_name": "Recraft V4.1 Utility", - "modalities": { - "input": [ "text" - ], - "output": [ - "image" ] }, "limit": { - "context": 8192, - "output": 8192 + "context": 8191, + "output": 1536 }, "temperature": false, "tool_call": false, @@ -23794,195 +25585,160 @@ }, "attachment": false, "open_weights": false, - "release_date": "2026-05-14", - "last_updated": "2026-05-14" + "knowledge": "2024-01", + "release_date": "2024-01-25", + "last_updated": "2024-01-25", + "type": "embedding" }, { - "id": "recraft/recraft-v3", - "name": "Recraft V3", - "display_name": "Recraft V3", + "id": "gpt-4o-mini", + "name": "GPT-4o mini", + "display_name": "GPT-4o mini", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ - "image" - ] - }, - "limit": { - "context": 512, - "output": 8192 - }, - "temperature": false, - "tool_call": false, - "reasoning": { - "supported": false - }, - "attachment": false, - "open_weights": false, - "release_date": "2024-10-30", - "last_updated": "2024-10" - }, - { - "id": "recraft/recraft-v4.1-utility-pro", - "name": "Recraft V4.1 Utility Pro", - "display_name": "Recraft V4.1 Utility Pro", - "modalities": { - "input": [ "text" - ], - "output": [ - "image" ] }, "limit": { - "context": 8192, - "output": 8192 + "context": 128000, + "output": 16384 }, - "temperature": false, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { "supported": false }, - "attachment": false, + "attachment": true, "open_weights": false, - "release_date": "2026-05-14", - "last_updated": "2026-05-14" + "knowledge": "2023-09", + "release_date": "2024-07-18", + "last_updated": "2024-07-18", + "type": "chat" }, { - "id": "recraft/recraft-v4.1-flash", - "name": "Recraft V4.1 Flash", - "display_name": "Recraft V4.1 Flash", + "id": "gpt-realtime-2.1", + "name": "GPT-Realtime-2.1", + "display_name": "GPT-Realtime-2.1", "modalities": { "input": [ - "text" - ], - "output": [ + "text", + "audio", "image" - ] - }, - "limit": { - "context": 8192, - "output": 8192 - }, - "temperature": false, - "tool_call": false, - "reasoning": { - "supported": false - }, - "attachment": false, - "open_weights": false, - "release_date": "2026-09-23", - "last_updated": "2026-09-23" - }, - { - "id": "recraft/recraft-v4", - "name": "Recraft V4", - "display_name": "Recraft V4", - "modalities": { - "input": [ - "text" ], "output": [ - "image" + "text", + "audio" ] }, "limit": { - "context": 8192, - "output": 8192 + "context": 128000, + "output": 32000 }, "temperature": false, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": false - }, - "attachment": false, - "open_weights": false, - "release_date": "2026-02-17", - "last_updated": "2026-02-17" - }, - { - "id": "recraft/recraft-v4-pro", - "name": "Recraft V4 Pro", - "display_name": "Recraft V4 Pro", - "modalities": { - "input": [ - "text" - ], - "output": [ - "image" - ] - }, - "limit": { - "context": 8192, - "output": 8192 + "supported": true, + "default": true }, - "temperature": false, - "tool_call": false, - "reasoning": { - "supported": false + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "minimal", + "low", + "medium", + "high", + "xhigh" + ] + } }, - "attachment": false, + "attachment": true, "open_weights": false, - "release_date": "2026-02-17", - "last_updated": "2026-02-17" + "knowledge": "2024-09-30", + "release_date": "2026-07-06", + "last_updated": "2026-07-06" }, { - "id": "recraft/recraft-v4.1-pro", - "name": "Recraft V4.1 Pro", - "display_name": "Recraft V4.1 Pro", + "id": "o1", + "name": "o1", + "display_name": "o1", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ - "image" + "text" ] }, "limit": { - "context": 8192, - "output": 8192 + "context": 200000, + "output": 100000 }, "temperature": false, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", + "effort_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" + } + }, + "attachment": true, "open_weights": false, - "release_date": "2026-05-14", - "last_updated": "2026-05-14" + "knowledge": "2023-09", + "release_date": "2024-12-05", + "last_updated": "2024-12-05", + "type": "chat" }, { - "id": "recraft/recraft-v4.1", - "name": "Recraft V4.1", - "display_name": "Recraft V4.1", + "id": "gpt-4", + "name": "GPT-4", + "display_name": "GPT-4", "modalities": { "input": [ "text" ], "output": [ - "image" + "text" ] }, "limit": { "context": 8192, "output": 8192 }, - "temperature": false, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { "supported": false }, "attachment": false, "open_weights": false, - "release_date": "2026-05-14", - "last_updated": "2026-05-14" + "knowledge": "2023-11", + "release_date": "2023-11-06", + "last_updated": "2024-04-09", + "type": "chat" }, { - "id": "openai/gpt-4.1-mini-fast", - "name": "GPT-4.1 mini (Fast)", - "display_name": "GPT-4.1 mini (Fast)", + "id": "gpt-4o", + "name": "GPT-4o", + "display_name": "GPT-4o", "modalities": { "input": [ "text", @@ -23994,8 +25750,8 @@ ] }, "limit": { - "context": 1047576, - "output": 32768 + "context": 128000, + "output": 16384 }, "temperature": true, "tool_call": true, @@ -24004,15 +25760,15 @@ }, "attachment": true, "open_weights": false, - "knowledge": "2024-04", - "release_date": "2025-04-14", - "last_updated": "2025-04-14", + "knowledge": "2023-09", + "release_date": "2024-05-13", + "last_updated": "2024-08-06", "type": "chat" }, { - "id": "openai/text-embedding-ada-002", - "name": "text-embedding-ada-002", - "display_name": "text-embedding-ada-002", + "id": "gpt-3.5-turbo", + "name": "GPT-3.5-turbo", + "display_name": "GPT-3.5-turbo", "modalities": { "input": [ "text" @@ -24022,24 +25778,25 @@ ] }, "limit": { - "context": 8192, - "output": 8192 + "context": 16385, + "output": 4096 }, - "temperature": false, + "temperature": true, "tool_call": false, "reasoning": { "supported": false }, "attachment": false, "open_weights": false, - "release_date": "2022-12-15", - "last_updated": "2022-12-15", - "type": "embedding" + "knowledge": "2021-09-01", + "release_date": "2023-03-01", + "last_updated": "2023-11-06", + "type": "chat" }, { - "id": "openai/gpt-5.1-codex-max", - "name": "GPT 5.1 Codex Max", - "display_name": "GPT 5.1 Codex Max", + "id": "gpt-5.6-sol", + "name": "GPT-5.6 Sol", + "display_name": "GPT-5.6 Sol", "modalities": { "input": [ "text", @@ -24051,26 +25808,28 @@ ] }, "limit": { - "context": 400000, + "context": 1050000, "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, + "default_enabled": true, "mode": "effort", - "effort": "none", + "effort": "medium", "effort_options": [ "none", "low", "medium", - "high" + "high", + "xhigh", + "max" ], "verbosity": "medium", "verbosity_options": [ @@ -24083,15 +25842,15 @@ }, "attachment": true, "open_weights": false, - "knowledge": "2024-09-30", - "release_date": "2025-11-13", - "last_updated": "2025-11-13", + "knowledge": "2026-02-16", + "release_date": "2026-07-09", + "last_updated": "2026-07-09", "type": "chat" }, { - "id": "openai/gpt-5", - "name": "GPT-5", - "display_name": "GPT-5", + "id": "gpt-daybreak-blue-latest", + "name": "Daybreak Blue", + "display_name": "Daybreak Blue", "modalities": { "input": [ "text", @@ -24103,7 +25862,7 @@ ] }, "limit": { - "context": 400000, + "context": 1050000, "output": 128000 }, "temperature": false, @@ -24114,41 +25873,31 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", "effort_options": [ - "minimal", - "low", - "medium", - "high" - ], - "verbosity": "medium", - "verbosity_options": [ + "none", "low", "medium", - "high" - ], - "visibility": "hidden" + "high", + "xhigh", + "max" + ] } }, "attachment": true, "open_weights": false, - "knowledge": "2024-09-30", - "release_date": "2025-08-07", - "last_updated": "2025-08-07", + "knowledge": "2026-02-16", + "release_date": "2026-08-07", + "last_updated": "2026-08-07", "type": "chat" }, { - "id": "openai/gpt-5.3-codex-fast", - "name": "GPT 5.3 Codex (Fast)", - "display_name": "GPT 5.3 Codex (Fast)", + "id": "gpt-5.1", + "name": "GPT-5.1", + "display_name": "GPT-5.1", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" @@ -24162,19 +25911,19 @@ "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, + "default_enabled": false, "mode": "effort", - "effort": "medium", + "effort": "none", "effort_options": [ + "none", "low", "medium", - "high", - "xhigh" + "high" ], "verbosity": "medium", "verbosity_options": [ @@ -24187,45 +25936,43 @@ }, "attachment": true, "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-02-05", - "last_updated": "2026-02-05", + "knowledge": "2024-09-30", + "release_date": "2025-11-13", + "last_updated": "2025-11-13", "type": "chat" }, { - "id": "openai/gpt-4o-mini", - "name": "GPT-4o mini", - "display_name": "GPT-4o mini", + "id": "chatgpt-image-latest", + "name": "chatgpt-image-latest", + "display_name": "chatgpt-image-latest", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ - "text" + "text", + "image" ] }, "limit": { - "context": 128000, - "output": 16384 + "context": 8192, + "output": 8192 }, - "temperature": true, - "tool_call": true, + "temperature": false, + "tool_call": false, "reasoning": { "supported": false }, "attachment": true, "open_weights": false, - "knowledge": "2023-09", - "release_date": "2024-07-18", - "last_updated": "2024-07-18", - "type": "chat" + "release_date": "2025-12-16", + "last_updated": "2025-12-16" }, { - "id": "openai/gpt-4o-fast", - "name": "GPT-4o (Fast)", - "display_name": "GPT-4o (Fast)", + "id": "gpt-5.5", + "name": "GPT-5.5", + "display_name": "GPT-5.5", "modalities": { "input": [ "text", @@ -24237,52 +25984,47 @@ ] }, "limit": { - "context": 128000, - "output": 16384 + "context": 1050000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { - "supported": false - }, - "attachment": true, - "open_weights": false, - "knowledge": "2023-09", - "release_date": "2024-05-13", - "last_updated": "2024-08-06", - "type": "chat" - }, - { - "id": "openai/gpt-image-1-mini", - "name": "GPT Image 1 Mini", - "display_name": "GPT Image 1 Mini", - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "image" - ] - }, - "limit": { - "context": 8192, - "output": 8192 + "supported": true, + "default": true }, - "temperature": false, - "tool_call": false, - "reasoning": { - "supported": false + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", + "effort_options": [ + "low", + "medium", + "high", + "xhigh" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" + } }, "attachment": true, "open_weights": false, - "release_date": "2025-10-06", - "last_updated": "2025-10-06" + "knowledge": "2025-12-01", + "release_date": "2026-04-23", + "last_updated": "2026-04-23", + "type": "chat" }, { - "id": "openai/gpt-5.5", - "name": "GPT 5.5", - "display_name": "GPT 5.5", + "id": "gpt-5.6", + "name": "GPT-5.6", + "display_name": "GPT-5.6", "modalities": { "input": [ "text", @@ -24294,10 +26036,10 @@ ] }, "limit": { - "context": 1000000, + "context": 1050000, "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -24310,10 +26052,12 @@ "mode": "effort", "effort": "medium", "effort_options": [ + "none", "low", "medium", "high", - "xhigh" + "xhigh", + "max" ], "verbosity": "medium", "verbosity_options": [ @@ -24326,28 +26070,55 @@ }, "attachment": true, "open_weights": false, - "knowledge": "2025-12-01", - "release_date": "2026-04-23", - "last_updated": "2026-04-23", + "knowledge": "2026-02-16", + "release_date": "2026-07-09", + "last_updated": "2026-07-09", "type": "chat" }, { - "id": "openai/gpt-4.1-nano", - "name": "GPT-4.1 nano", - "display_name": "GPT-4.1 nano", + "id": "gpt-image-1-mini", + "name": "gpt-image-1-mini", + "display_name": "gpt-image-1-mini", "modalities": { "input": [ "text", - "image", - "pdf" + "image" + ], + "output": [ + "text", + "image" + ] + }, + "limit": { + "context": 8192, + "output": 8192 + }, + "temperature": false, + "tool_call": false, + "reasoning": { + "supported": false + }, + "attachment": true, + "open_weights": false, + "release_date": "2025-09-26", + "last_updated": "2025-09-26" + }, + { + "id": "gpt-4-turbo", + "name": "GPT-4 Turbo", + "display_name": "GPT-4 Turbo", + "modalities": { + "input": [ + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 1047576, - "output": 32768 + "context": 128000, + "output": 4096 }, "temperature": true, "tool_call": true, @@ -24356,15 +26127,15 @@ }, "attachment": true, "open_weights": false, - "knowledge": "2024-04", - "release_date": "2025-04-14", - "last_updated": "2025-04-14", + "knowledge": "2023-12", + "release_date": "2023-11-06", + "last_updated": "2024-04-09", "type": "chat" }, { - "id": "openai/o1", - "name": "o1", - "display_name": "o1", + "id": "gpt-4.1-mini", + "name": "GPT-4.1 mini", + "display_name": "GPT-4.1 mini", "modalities": { "input": [ "text", @@ -24376,67 +26147,55 @@ ] }, "limit": { - "context": 200000, - "output": 100000 + "context": 1047576, + "output": 32768 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", - "effort_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" - } + "supported": false }, "attachment": true, "open_weights": false, - "knowledge": "2023-09", - "release_date": "2024-12-05", - "last_updated": "2024-12-05", + "knowledge": "2024-04", + "release_date": "2025-04-14", + "last_updated": "2025-04-14", "type": "chat" }, { - "id": "openai/gpt-image-2", - "name": "GPT Image 2", - "display_name": "GPT Image 2", + "id": "gpt-4.1", + "name": "GPT-4.1", + "display_name": "GPT-4.1", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ - "image" + "text" ] }, "limit": { - "context": 8192, - "output": 8192 + "context": 1047576, + "output": 32768 }, - "temperature": false, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { "supported": false }, "attachment": true, "open_weights": false, - "release_date": "2026-04-21", - "last_updated": "2026-04-21" + "knowledge": "2024-04", + "release_date": "2025-04-14", + "last_updated": "2025-04-14", + "type": "chat" }, { - "id": "openai/gpt-6-astra", - "name": "GPT-6 Astra", - "display_name": "GPT-6 Astra", + "id": "gpt-6-luna", + "name": "GPT-6 Luna", + "display_name": "GPT-6 Luna", "modalities": { "input": [ "text", @@ -24459,8 +26218,8 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, "effort_options": [ + "none", "low", "medium", "high", @@ -24471,15 +26230,15 @@ }, "attachment": true, "open_weights": false, - "knowledge": "2026-04-30", - "release_date": "2026-09-04", - "last_updated": "2026-09-04", + "knowledge": "2026-05-18", + "release_date": "2026-09-22", + "last_updated": "2026-09-22", "type": "chat" }, { - "id": "openai/gpt-5.4", - "name": "GPT 5.4", - "display_name": "GPT 5.4", + "id": "o3", + "name": "o3", + "display_name": "o3", "modalities": { "input": [ "text", @@ -24491,30 +26250,22 @@ ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 200000, + "output": 100000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, + "default_enabled": true, "mode": "effort", - "effort": "none", + "effort": "medium", "effort_options": [ - "none", - "low", - "medium", - "high", - "xhigh" - ], - "verbosity": "medium", - "verbosity_options": [ "low", "medium", "high" @@ -24524,59 +26275,52 @@ }, "attachment": true, "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-03-05", - "last_updated": "2026-03-05", + "knowledge": "2024-05", + "release_date": "2025-04-16", + "last_updated": "2025-04-16", "type": "chat" }, { - "id": "openai/gpt-5.6-terra-fast", - "name": "GPT 5.6 Terra (Fast)", - "display_name": "GPT 5.6 Terra (Fast)", + "id": "gpt-image-2", + "name": "gpt-image-2", + "display_name": "gpt-image-2", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ - "text" + "image" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 8192, + "output": 8192 }, "temperature": false, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "none", - "minimal", - "low", - "medium", - "high", - "xhigh", - "max" - ] - } + "supported": false }, "attachment": true, "open_weights": false, - "knowledge": "2026-02-16", - "release_date": "2026-07-09", - "last_updated": "2026-07-09", - "type": "chat" - }, + "release_date": "2026-04-21", + "last_updated": "2026-04-21", + "type": "imageGeneration" + } + ] + }, + "greenpt": { + "id": "greenpt", + "name": "GreenPT", + "display_name": "GreenPT", + "api": "https://api.greenpt.ai/v1", + "doc": "https://docs.greenpt.ai", + "models": [ { - "id": "openai/gpt-oss-20b", - "name": "GPT OSS 20B", - "display_name": "GPT OSS 20B", + "id": "glm-5.2-honey", + "name": "GLM-5.2 Honey", + "display_name": "GLM-5.2 Honey", "modalities": { "input": [ "text" @@ -24586,8 +26330,8 @@ ] }, "limit": { - "context": 131072, - "output": 8192 + "context": 1000000, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -24597,8 +26341,9 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, "effort_options": [ + "none", + "minimal", "low", "medium", "high" @@ -24607,89 +26352,107 @@ }, "attachment": false, "open_weights": true, - "knowledge": "2024-06", - "release_date": "2025-08-05", - "last_updated": "2025-08-05", + "release_date": "2026-06-13", + "last_updated": "2026-06-13", "type": "chat" }, { - "id": "openai/gpt-4.1-fast", - "name": "GPT-4.1 (Fast)", - "display_name": "GPT-4.1 (Fast)", + "id": "kimi-k3", + "name": "Kimi K3", + "display_name": "Kimi K3", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1047576, - "output": 32768 + "context": 1048576, + "output": 131072 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "effort_options": [ + "none", + "minimal", + "low", + "medium", + "high" + ] + } }, "attachment": true, - "open_weights": false, - "knowledge": "2024-04", - "release_date": "2025-04-14", - "last_updated": "2025-04-14", + "open_weights": true, + "release_date": "2026-07-16", + "last_updated": "2026-07-16", "type": "chat" }, { - "id": "openai/gpt-realtime-1.5", - "name": "GPT-Realtime-1.5", - "display_name": "GPT-Realtime-1.5", + "id": "glm-5.2-caveman-lite", + "name": "GLM-5.2 Caveman Lite", + "display_name": "GLM-5.2 Caveman Lite", "modalities": { "input": [ - "text", - "audio" + "text" ], "output": [ - "text", - "audio" + "text" ] }, "limit": { - "context": 32000, - "output": 4096 + "context": 1000000, + "output": 131072 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": true, - "open_weights": false, - "knowledge": "2024-09-30", - "release_date": "2026-02-23", - "last_updated": "2026-02-23" + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "none", + "minimal", + "low", + "medium", + "high" + ] + } + }, + "attachment": false, + "open_weights": true, + "release_date": "2026-06-13", + "last_updated": "2026-06-13", + "type": "chat" }, { - "id": "openai/gpt-6.1-sol-fast", - "name": "GPT-6.1 Sol (Fast)", - "display_name": "GPT-6.1 Sol (Fast)", + "id": "minimax-m2.5", + "name": "MiniMax-M2.5", + "display_name": "MiniMax-M2.5", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 204800, + "output": 131072 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -24697,41 +26460,39 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, "effort_options": [ + "none", + "minimal", "low", "medium", - "high", - "xhigh", - "max" + "high" ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-04-30", - "release_date": "2026-09-29", - "last_updated": "2026-09-29", + "attachment": false, + "open_weights": true, + "release_date": "2026-02-12", + "last_updated": "2026-02-12", "type": "chat" }, { - "id": "openai/gpt-6.1-terra-fast", - "name": "GPT-6.1 Terra (Fast)", - "display_name": "GPT-6.1 Terra (Fast)", + "id": "glm-5.2-ponytail-ultra", + "name": "GLM-5.2 Ponytail Ultra", + "display_name": "GLM-5.2 Ponytail Ultra", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 1000000, + "output": 131072 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -24741,55 +26502,64 @@ "reasoning": { "effort_options": [ "none", + "minimal", "low", "medium", - "high", - "xhigh", - "max" + "high" ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-06-01", - "release_date": "2026-10-26", - "last_updated": "2026-10-26", + "attachment": false, + "open_weights": true, + "release_date": "2026-06-13", + "last_updated": "2026-06-13", "type": "chat" }, { - "id": "openai/gpt-4o", - "name": "GPT-4o", - "display_name": "GPT-4o", + "id": "kimi-k2.7-code", + "name": "Kimi K2.7 Code", + "display_name": "Kimi K2.7 Code", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 16384 + "context": 262144, + "output": 262144 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": true, - "open_weights": false, - "knowledge": "2023-09", - "release_date": "2024-05-13", - "last_updated": "2024-08-06", + "extra_capabilities": { + "reasoning": { + "supported": true, + "effort_options": [ + "none", + "minimal", + "low", + "medium", + "high" + ] + } + }, + "attachment": false, + "open_weights": true, + "knowledge": "2025-01", + "release_date": "2026-06-12", + "last_updated": "2026-06-12", "type": "chat" }, { - "id": "openai/gpt-realtime-whisper", - "name": "gpt-realtime-whisper", - "display_name": "gpt-realtime-whisper", + "id": "green-s", + "name": "Green S", + "display_name": "Green S", "modalities": { "input": [ "audio" @@ -24799,8 +26569,8 @@ ] }, "limit": { - "context": 16000, - "output": 2000 + "context": 8192, + "output": 8192 }, "temperature": false, "tool_call": false, @@ -24809,79 +26579,63 @@ }, "attachment": true, "open_weights": false, - "release_date": "2026-05-07", - "last_updated": "2026-05-07" + "release_date": "2025-01", + "last_updated": "2025-01" }, { - "id": "openai/gpt-5.4-mini-fast", - "name": "GPT 5.4 Mini (Fast)", - "display_name": "GPT 5.4 Mini (Fast)", + "id": "glm-5.2-ponytail", + "name": "GLM-5.2 Ponytail", + "display_name": "GLM-5.2 Ponytail", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 1000000, + "output": 131072 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "none", "effort_options": [ "none", - "low", - "medium", - "high", - "xhigh" - ], - "verbosity": "medium", - "verbosity_options": [ + "minimal", "low", "medium", "high" - ], - "visibility": "hidden" + ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-03-17", - "last_updated": "2026-03-17", + "attachment": false, + "open_weights": true, + "release_date": "2026-06-13", + "last_updated": "2026-06-13", "type": "chat" }, { - "id": "openai/gpt-5-pro", - "name": "GPT-5 pro", - "display_name": "GPT-5 pro", + "id": "glm-5.2-caveman-ultra", + "name": "GLM-5.2 Caveman Ultra", + "display_name": "GLM-5.2 Caveman Ultra", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 400000, - "output": 272000 + "context": 1000000, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -24891,45 +26645,39 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "fixed", - "effort": "high", - "verbosity": "medium", - "verbosity_options": [ + "effort_options": [ + "none", + "minimal", "low", "medium", "high" - ], - "visibility": "hidden" + ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2024-09-30", - "release_date": "2025-10-06", - "last_updated": "2025-10-06", + "attachment": false, + "open_weights": true, + "release_date": "2026-06-13", + "last_updated": "2026-06-13", "type": "chat" }, { - "id": "openai/o3", - "name": "o3", - "display_name": "o3", + "id": "qwen3.6-35b-a3b", + "name": "Qwen3.6 35B-A3B", + "display_name": "Qwen3.6 35B-A3B", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 100000 + "context": 262144, + "output": 32768 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -24938,28 +26686,31 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ + "none", + "minimal", "low", "medium", "high" - ], - "visibility": "hidden" + ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2024-05", - "release_date": "2025-04-16", - "last_updated": "2025-04-16", + "open_weights": true, + "release_date": "2026-04-17", + "last_updated": "2026-04-17", "type": "chat" }, { - "id": "openai/text-embedding-3-small", - "name": "text-embedding-3-small", - "display_name": "text-embedding-3-small", + "id": "llama-3.3-70b-instruct", + "name": "Llama-3.3-70B-Instruct", + "display_name": "Llama-3.3-70B-Instruct", "modalities": { "input": [ "text" @@ -24969,72 +26720,60 @@ ] }, "limit": { - "context": 8192, - "output": 8192 + "context": 100000, + "output": 16384 }, - "temperature": false, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { "supported": false }, "attachment": false, - "open_weights": false, - "release_date": "2024-01-25", - "last_updated": "2024-01-25", - "type": "embedding" + "open_weights": true, + "knowledge": "2023-12", + "release_date": "2024-12-06", + "last_updated": "2024-12-06", + "type": "chat" }, { - "id": "openai/gpt-5-mini-fast", - "name": "GPT-5 mini (Fast)", - "display_name": "GPT-5 mini (Fast)", + "id": "mistral-small-3.2-24b-instruct-2506", + "name": "Mistral Small 3.2", + "display_name": "Mistral Small 3.2", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 128000, + "output": 32768 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "minimal", - "low", - "medium", - "high" - ] - } + "supported": false }, "attachment": true, - "open_weights": false, - "knowledge": "2024-05-30", - "release_date": "2025-08-07", - "last_updated": "2025-08-07", + "open_weights": true, + "knowledge": "2025-03", + "release_date": "2025-06-20", + "last_updated": "2025-06-20", "type": "chat" }, { - "id": "openai/gpt-image-2.5-flare", - "name": "GPT Image 2.5 Flare", - "display_name": "GPT Image 2.5 Flare", + "id": "green-s-pro", + "name": "Green S Pro", + "display_name": "Green S Pro", "modalities": { "input": [ - "text", - "image" + "audio" ], "output": [ - "image" + "text" ] }, "limit": { @@ -25048,26 +26787,24 @@ }, "attachment": true, "open_weights": false, - "release_date": "2026-09-08", - "last_updated": "2026-09-08" + "release_date": "2025-02", + "last_updated": "2025-02" }, { - "id": "openai/gpt-realtime-2.1", - "name": "gpt-realtime-2.1", - "display_name": "gpt-realtime-2.1", + "id": "glm-5.3-flash", + "name": "GLM-5.3-Flash", + "display_name": "GLM-5.3-Flash", "modalities": { "input": [ - "text", - "audio" + "text" ], "output": [ - "text", - "audio" + "text" ] }, "limit": { - "context": 128000, - "output": 32000 + "context": 1000000, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -25077,40 +26814,39 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, "effort_options": [ + "none", "minimal", "low", "medium", - "high", - "xhigh" + "high" ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2024-09-30", - "release_date": "2026-07-06", - "last_updated": "2026-07-06" + "attachment": false, + "open_weights": true, + "release_date": "2026-08-26", + "last_updated": "2026-08-26", + "type": "chat" }, { - "id": "openai/gpt-6-sol", - "name": "GPT-6 Sol", - "display_name": "GPT-6 Sol", + "id": "glm-5.2-ponytail-lite", + "name": "GLM-5.2 Ponytail Lite", + "display_name": "GLM-5.2 Ponytail Lite", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 1000000, + "output": 131072 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -25120,30 +26856,55 @@ "reasoning": { "effort_options": [ "none", + "minimal", "low", "medium", - "high", - "xhigh", - "max" + "high" ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-04-20", - "release_date": "2026-09-22", - "last_updated": "2026-09-22", + "attachment": false, + "open_weights": true, + "release_date": "2026-06-13", + "last_updated": "2026-06-13", "type": "chat" }, { - "id": "openai/o4-mini", - "name": "o4-mini", - "display_name": "o4-mini", + "id": "gemma-3-27b-it", + "name": "Gemma 3 27B", + "display_name": "Gemma 3 27B", "modalities": { "input": [ "text", - "image", - "pdf" + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 40000, + "output": 8192 + }, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": false + }, + "attachment": true, + "open_weights": true, + "knowledge": "2024-08", + "release_date": "2025-03-12", + "last_updated": "2025-03-12", + "type": "chat" + }, + { + "id": "devstral-2-123b-instruct-2512", + "name": "Devstral 2", + "display_name": "Devstral 2", + "modalities": { + "input": [ + "text" ], "output": [ "text" @@ -25151,52 +26912,123 @@ }, "limit": { "context": 200000, - "output": 100000 + "output": 16384 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true + "supported": false }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", - "effort_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" - } + "attachment": false, + "open_weights": true, + "knowledge": "2025-12", + "release_date": "2025-12-09", + "last_updated": "2025-12-09", + "type": "chat" + }, + { + "id": "voxtral-small-24b-2507", + "name": "Voxtral Small 24B", + "display_name": "Voxtral Small 24B", + "modalities": { + "input": [ + "text", + "audio" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 32768, + "output": 16384 + }, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": false }, "attachment": true, - "open_weights": false, - "knowledge": "2024-05", - "release_date": "2025-04-16", - "last_updated": "2025-04-16", + "open_weights": true, + "knowledge": "2025-07", + "release_date": "2025-07-15", + "last_updated": "2025-07-15", "type": "chat" }, { - "id": "openai/gpt-5.6-sol", - "name": "GPT 5.6 Sol", - "display_name": "GPT 5.6 Sol", + "id": "green-l", + "name": "Green L", + "display_name": "Green L", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 128000, + "output": 32768 + }, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": false + }, + "attachment": true, + "open_weights": true, + "knowledge": "2025-03", + "release_date": "2025-06-20", + "last_updated": "2025-06-20", + "type": "chat" + }, + { + "id": "pixtral-12b-2409", + "name": "Pixtral 12B", + "display_name": "Pixtral 12B", + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 4096 + }, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": false + }, + "attachment": true, + "open_weights": true, + "knowledge": "2024-09", + "release_date": "2024-09-11", + "last_updated": "2024-09-11", + "type": "chat" + }, + { + "id": "deepseek-v4.1-flash", + "name": "DeepSeek V4.1 Flash", + "display_name": "DeepSeek V4.1 Flash", + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1000000, + "output": 384000 }, "temperature": true, "tool_call": true, @@ -25207,50 +27039,38 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", "effort_options": [ "none", - "low", - "medium", - "high", - "xhigh", - "max" - ], - "verbosity": "medium", - "verbosity_options": [ + "minimal", "low", "medium", "high" - ], - "visibility": "hidden" + ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2026-02-16", - "release_date": "2026-07-09", - "last_updated": "2026-07-09", + "open_weights": true, + "knowledge": "2025-05", + "release_date": "2026-09-10", + "last_updated": "2026-09-10", "type": "chat" }, { - "id": "openai/gpt-5.6-terra", - "name": "GPT 5.6 Terra", - "display_name": "GPT 5.6 Terra", + "id": "gemma4", + "name": "gemma4", + "display_name": "gemma4", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 262144, + "output": 32768 }, "temperature": true, "tool_call": true, @@ -25260,41 +27080,29 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", "effort_options": [ "none", - "low", - "medium", - "high", - "xhigh", - "max" - ], - "verbosity": "medium", - "verbosity_options": [ + "minimal", "low", "medium", "high" - ], - "visibility": "hidden" + ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2026-02-16", - "release_date": "2026-07-09", - "last_updated": "2026-07-09", + "open_weights": true, + "release_date": "2026-04-02", + "last_updated": "2026-04-02", "type": "chat" }, { - "id": "openai/gpt-oss-120b", + "id": "gpt-oss-120b", "name": "GPT OSS 120B", "display_name": "GPT OSS 120B", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" @@ -25302,7 +27110,7 @@ }, "limit": { "context": 131072, - "output": 131072 + "output": 32768 }, "temperature": true, "tool_call": true, @@ -25320,7 +27128,7 @@ ] } }, - "attachment": false, + "attachment": true, "open_weights": true, "knowledge": "2024-06", "release_date": "2025-08-05", @@ -25328,103 +27136,91 @@ "type": "chat" }, { - "id": "openai/text-embedding-3-large", - "name": "text-embedding-3-large", - "display_name": "text-embedding-3-large", + "id": "green-l-raw", + "name": "Green L Raw", + "display_name": "Green L Raw", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 8192, - "output": 8192 + "context": 128000, + "output": 32768 }, - "temperature": false, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { "supported": false }, - "attachment": false, - "open_weights": false, - "release_date": "2024-01-25", - "last_updated": "2024-01-25", - "type": "embedding" + "attachment": true, + "open_weights": true, + "knowledge": "2025-03", + "release_date": "2025-06-20", + "last_updated": "2025-06-20", + "type": "chat" }, { - "id": "openai/gpt-5.1-codex", - "name": "GPT-5.1-Codex", - "display_name": "GPT-5.1-Codex", + "id": "glm-5.2-honey-ultra", + "name": "GLM-5.2 Honey Ultra", + "display_name": "GLM-5.2 Honey Ultra", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 1000000, + "output": 131072 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "none", "effort_options": [ "none", + "minimal", "low", "medium", "high" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2024-09-30", - "release_date": "2025-11-13", - "last_updated": "2025-11-13", + "attachment": false, + "open_weights": true, + "release_date": "2026-06-13", + "last_updated": "2026-06-13", "type": "chat" }, { - "id": "openai/gpt-6-luna", - "name": "GPT-6 Luna", - "display_name": "GPT-6 Luna", + "id": "green-r", + "name": "Green R", + "display_name": "Green R", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 131072, + "output": 32768 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -25433,41 +27229,36 @@ "extra_capabilities": { "reasoning": { "effort_options": [ - "none", "low", "medium", - "high", - "xhigh", - "max" + "high" ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2026-05-18", - "release_date": "2026-09-22", - "last_updated": "2026-09-22", + "open_weights": true, + "knowledge": "2024-06", + "release_date": "2025-08-05", + "last_updated": "2025-08-05", "type": "chat" }, { - "id": "openai/gpt-6-luna-fast", - "name": "GPT-6 Luna (Fast)", - "display_name": "GPT-6 Luna (Fast)", + "id": "glm-5.3", + "name": "GLM-5.3", + "display_name": "GLM-5.3", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 1000000, + "output": 131072 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -25475,42 +27266,69 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, "effort_options": [ "none", + "minimal", "low", "medium", - "high", - "xhigh", - "max" + "high" ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-05-18", - "release_date": "2026-09-22", - "last_updated": "2026-09-22", + "attachment": false, + "open_weights": true, + "release_date": "2026-08-14", + "last_updated": "2026-08-14", "type": "chat" }, { - "id": "openai/gpt-5-mini", - "name": "GPT-5 Mini", - "display_name": "GPT-5 Mini", + "id": "kimi-k2.6-fast", + "name": "Kimi K2.6 Fast", + "display_name": "Kimi K2.6 Fast", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 262144, + "output": 262144 }, - "temperature": false, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": true, + "default": true + }, + "attachment": true, + "open_weights": true, + "knowledge": "2025-01", + "release_date": "2026-04-20", + "last_updated": "2026-04-20", + "type": "chat" + }, + { + "id": "glm-5.2-caveman", + "name": "GLM-5.2 Caveman", + "display_name": "GLM-5.2 Caveman", + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1000000, + "output": 131072 + }, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -25518,132 +27336,106 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", "effort_options": [ + "none", "minimal", "low", "medium", "high" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2024-05-30", - "release_date": "2025-08-07", - "last_updated": "2025-08-07", + "attachment": false, + "open_weights": true, + "release_date": "2026-06-13", + "last_updated": "2026-06-13", "type": "chat" }, { - "id": "openai/gpt-5.1-thinking", - "name": "GPT 5.1 Thinking", - "display_name": "GPT 5.1 Thinking", + "id": "green-r-raw", + "name": "Green R Raw", + "display_name": "Green R Raw", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 131072, + "output": 32768 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "none", "effort_options": [ - "none", - "low", - "medium", - "high" - ], - "verbosity": "medium", - "verbosity_options": [ "low", "medium", "high" - ], - "visibility": "hidden" + ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2024-10", - "release_date": "2025-11-12", - "last_updated": "2025-08-07", + "open_weights": true, + "knowledge": "2024-06", + "release_date": "2025-08-05", + "last_updated": "2025-08-05", "type": "chat" }, { - "id": "openai/gpt-live-1", - "name": "GPT-Live 1", - "display_name": "GPT-Live 1", + "id": "qwen3-coder-30b-a3b-instruct", + "name": "Qwen3-Coder 30B-A3B Instruct", + "display_name": "Qwen3-Coder 30B-A3B Instruct", "modalities": { "input": [ - "text", - "audio" + "text" ], "output": [ - "text", - "audio" + "text" ] }, "limit": { - "context": 8192, - "output": 8192 + "context": 128000, + "output": 32768 }, "temperature": true, "tool_call": true, "reasoning": { "supported": false }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-07-31", - "release_date": "2026-09-10", - "last_updated": "2026-09-10" + "attachment": false, + "open_weights": true, + "knowledge": "2025-04", + "release_date": "2025-07-31", + "last_updated": "2025-07-31", + "type": "chat" }, { - "id": "openai/gpt-5-codex", - "name": "GPT-5-Codex", - "display_name": "GPT-5-Codex", + "id": "mistral-medium-3.5-128b", + "name": "Mistral Medium 3.5", + "display_name": "Mistral Medium 3.5", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 262144, + "output": 16384 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -25651,49 +27443,33 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", "effort_options": [ - "minimal", - "low", - "medium", - "high" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", + "none", "high" - ], - "visibility": "hidden" + ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2024-09-30", - "release_date": "2025-09-15", - "last_updated": "2025-09-15", + "open_weights": true, + "release_date": "2026-04-29", + "last_updated": "2026-04-29", "type": "chat" }, { - "id": "openai/gpt-5.4-pro", - "name": "GPT 5.4 Pro", - "display_name": "GPT 5.4 Pro", + "id": "glm-5.2-honey-lite", + "name": "GLM-5.2 Honey Lite", + "display_name": "GLM-5.2 Honey Lite", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 1000000, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -25703,100 +27479,72 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "high", "effort_options": [ - "medium", - "high", - "xhigh" - ], - "verbosity": "medium", - "verbosity_options": [ + "none", + "minimal", "low", "medium", "high" - ], - "visibility": "hidden" + ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-03-05", - "last_updated": "2026-03-05", + "attachment": false, + "open_weights": true, + "release_date": "2026-06-13", + "last_updated": "2026-06-13", "type": "chat" }, { - "id": "openai/gpt-5.1-codex-mini", - "name": "GPT-5.1 Codex mini", - "display_name": "GPT-5.1 Codex mini", + "id": "glm-5.1", + "name": "GLM-5.1", + "display_name": "GLM-5.1", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 200000, + "output": 131072 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "none", - "effort_options": [ - "none", - "low", - "medium", - "high" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + "supported": true } }, - "attachment": true, - "open_weights": false, - "knowledge": "2024-09-30", - "release_date": "2025-11-13", - "last_updated": "2025-11-13", + "attachment": false, + "open_weights": true, + "release_date": "2026-04-07", + "last_updated": "2026-04-07", "type": "chat" }, { - "id": "openai/o3-mini", - "name": "o3-mini", - "display_name": "o3-mini", + "id": "holo2-30b-a3b", + "name": "Holo2 30B A3B", + "display_name": "Holo2 30B A3B", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 100000 + "context": 22016, + "output": 16384 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -25804,95 +27552,102 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", "effort_options": [ "low", "medium", "high" - ], - "visibility": "hidden" + ] } }, - "attachment": false, - "open_weights": false, - "knowledge": "2024-05", - "release_date": "2024-12-20", - "last_updated": "2025-01-29", + "attachment": true, + "open_weights": true, + "release_date": "2025-11", + "last_updated": "2025-11", "type": "chat" }, { - "id": "openai/gpt-image-1.5", - "name": "GPT Image 1.5", - "display_name": "GPT Image 1.5", + "id": "qwen3-235b-a22b-instruct-2507", + "name": "Qwen3 235B A22B Instruct 2507", + "display_name": "Qwen3 235B A22B Instruct 2507", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ - "image" + "text" ] }, "limit": { - "context": 8192, - "output": 8192 + "context": 262144, + "output": 16384 }, - "temperature": false, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { "supported": false }, - "attachment": true, - "open_weights": false, - "release_date": "2025-11-25", - "last_updated": "2025-11-25" + "attachment": false, + "open_weights": true, + "knowledge": "2025-04", + "release_date": "2025-07-21", + "last_updated": "2025-07-21", + "type": "chat" }, { - "id": "openai/whisper-1", - "name": "Whisper", - "display_name": "Whisper", + "id": "glm-5.2", + "name": "GLM-5.2", + "display_name": "GLM-5.2", "modalities": { "input": [ - "audio" + "text" ], "output": [ "text" ] }, "limit": { - "context": 8192, - "output": 8192 + "context": 1000000, + "output": 131072 }, - "temperature": false, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": true, - "open_weights": false, - "release_date": "2023-03-01", - "last_updated": "2023-03-01" + "extra_capabilities": { + "reasoning": { + "supported": true, + "effort_options": [ + "none", + "minimal", + "low", + "medium", + "high" + ] + } + }, + "attachment": false, + "open_weights": true, + "release_date": "2026-06-13", + "last_updated": "2026-06-13", + "type": "chat" }, { - "id": "openai/gpt-5.6-luna", - "name": "GPT 5.6 Luna", - "display_name": "GPT 5.6 Luna", + "id": "qwen3.5-397b-a17b", + "name": "Qwen3.5 397B-A17B", + "display_name": "Qwen3.5 397B-A17B", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 262144, + "output": 16384 }, "temperature": true, "tool_call": true, @@ -25903,79 +27658,86 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ "none", - "low", - "medium", - "high", - "xhigh", - "max" - ], - "verbosity": "medium", - "verbosity_options": [ + "minimal", "low", "medium", "high" - ], - "visibility": "hidden" + ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-02-16", - "release_date": "2026-07-09", - "last_updated": "2026-07-09", + "attachment": false, + "open_weights": true, + "release_date": "2026-02-15", + "last_updated": "2026-02-15", "type": "chat" }, { - "id": "openai/tts-1-hd", - "name": "TTS-1 HD", - "display_name": "TTS-1 HD", + "id": "deepseek-v4-flash-0731", + "name": "DeepSeek V4 Flash 0731", + "display_name": "DeepSeek V4 Flash 0731", "modalities": { "input": [ "text" ], "output": [ - "audio" + "text" ] }, "limit": { - "context": 8192, - "output": 8192 + "context": 1000000, + "output": 384000 }, - "temperature": false, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "effort_options": [ + "none", + "minimal", + "low", + "medium", + "high" + ] + } }, "attachment": false, - "open_weights": false, - "release_date": "2023-11-06", - "last_updated": "2023-11-06", - "type": "tts" + "open_weights": true, + "knowledge": "2025-05", + "release_date": "2026-07-31", + "last_updated": "2026-07-31", + "type": "chat" }, { - "id": "openai/gpt-5-fast", - "name": "GPT-5 (Fast)", - "display_name": "GPT-5 (Fast)", + "id": "kimi-k2.6", + "name": "Kimi K2.6", + "display_name": "Kimi K2.6", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 262144, + "output": 262144 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -25983,7 +27745,9 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, "effort_options": [ + "none", "minimal", "low", "medium", @@ -25992,16 +27756,24 @@ } }, "attachment": true, - "open_weights": false, - "knowledge": "2024-09-30", - "release_date": "2025-08-07", - "last_updated": "2025-08-07", + "open_weights": true, + "knowledge": "2025-01", + "release_date": "2026-04-20", + "last_updated": "2026-04-20", "type": "chat" - }, + } + ] + }, + "vercel": { + "id": "vercel", + "name": "Vercel AI Gateway", + "display_name": "Vercel AI Gateway", + "doc": "https://github.com/vercel/ai/tree/5eb85cc45a259553501f535b8ac79a77d0e79223/packages/gateway", + "models": [ { - "id": "openai/gpt-5.4-mini", - "name": "GPT 5.4 Mini", - "display_name": "GPT 5.4 Mini", + "id": "openai/gpt-5.2-pro", + "name": "GPT 5.2 ", + "display_name": "GPT 5.2 ", "modalities": { "input": [ "text", @@ -26020,17 +27792,15 @@ "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, + "default_enabled": true, "mode": "effort", - "effort": "none", + "effort": "high", "effort_options": [ - "none", - "low", "medium", "high", "xhigh" @@ -26047,41 +27817,14 @@ "attachment": true, "open_weights": false, "knowledge": "2025-08-31", - "release_date": "2026-03-17", - "last_updated": "2026-03-17", + "release_date": "2025-12-11", + "last_updated": "2025-12-11", "type": "chat" }, { - "id": "openai/gpt-4o-mini-transcribe", - "name": "GPT-4o mini Transcribe", - "display_name": "GPT-4o mini Transcribe", - "modalities": { - "input": [ - "audio" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 16000, - "output": 2000 - }, - "temperature": false, - "tool_call": false, - "reasoning": { - "supported": false - }, - "attachment": true, - "open_weights": false, - "knowledge": "2024-06-01", - "release_date": "2025-03-20", - "last_updated": "2025-03-20" - }, - { - "id": "openai/gpt-6-sol-fast", - "name": "GPT-6 Sol (Fast)", - "display_name": "GPT-6 Sol (Fast)", + "id": "openai/gpt-5-pro", + "name": "GPT-5 pro", + "display_name": "GPT-5 pro", "modalities": { "input": [ "text", @@ -26093,10 +27836,10 @@ ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 400000, + "output": 272000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -26104,40 +27847,41 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "none", + "supported": true, + "default_enabled": true, + "mode": "fixed", + "effort": "high", + "verbosity": "medium", + "verbosity_options": [ "low", "medium", - "high", - "xhigh", - "max" - ] + "high" + ], + "visibility": "hidden" } }, "attachment": true, "open_weights": false, - "knowledge": "2026-04-20", - "release_date": "2026-09-22", - "last_updated": "2026-09-22", + "knowledge": "2024-09-30", + "release_date": "2025-10-06", + "last_updated": "2025-10-06", "type": "chat" }, { - "id": "openai/gpt-5.6-luna-fast", - "name": "GPT 5.6 Luna (Fast)", - "display_name": "GPT 5.6 Luna (Fast)", + "id": "openai/o3-mini", + "name": "o3-mini", + "display_name": "o3-mini", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 200000, + "output": 100000 }, "temperature": false, "tool_call": true, @@ -26147,27 +27891,29 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", "effort_options": [ - "none", "low", "medium", - "high", - "xhigh", - "max" - ] + "high" + ], + "visibility": "hidden" } }, - "attachment": true, + "attachment": false, "open_weights": false, - "knowledge": "2026-02-16", - "release_date": "2026-07-09", - "last_updated": "2026-07-09", + "knowledge": "2024-05", + "release_date": "2024-12-20", + "last_updated": "2025-01-29", "type": "chat" }, { - "id": "openai/gpt-5.3-codex", - "name": "GPT 5.3 Codex", - "display_name": "GPT 5.3 Codex", + "id": "openai/gpt-5.4-nano", + "name": "GPT 5.4 Nano", + "display_name": "GPT 5.4 Nano", "modalities": { "input": [ "text", @@ -26186,15 +27932,16 @@ "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, + "default_enabled": false, "mode": "effort", - "effort": "medium", + "effort": "none", "effort_options": [ + "none", "low", "medium", "high", @@ -26212,43 +27959,57 @@ "attachment": true, "open_weights": false, "knowledge": "2025-08-31", - "release_date": "2026-02-05", - "last_updated": "2026-02-05", + "release_date": "2026-03-17", + "last_updated": "2026-03-17", "type": "chat" }, { - "id": "openai/gpt-realtime-2", - "name": "gpt-realtime-2", - "display_name": "gpt-realtime-2", + "id": "openai/gpt-6.1-terra", + "name": "GPT-6.1 Terra", + "display_name": "GPT-6.1 Terra", "modalities": { "input": [ "text", - "audio" + "image", + "pdf" ], "output": [ - "text", - "audio" + "text" ] }, "limit": { - "context": 128000, - "output": 32000 + "context": 1050000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "none", + "low", + "medium", + "high", + "xhigh", + "max" + ] + } }, "attachment": true, "open_weights": false, - "knowledge": "2024-09-30", - "release_date": "2026-05-07", - "last_updated": "2026-05-07" + "knowledge": "2026-06-01", + "release_date": "2026-10-26", + "last_updated": "2026-10-26", + "type": "chat" }, { - "id": "openai/gpt-4.1-mini", - "name": "GPT-4.1 mini", - "display_name": "GPT-4.1 mini", + "id": "openai/gpt-5.4", + "name": "GPT 5.4", + "display_name": "GPT 5.4", "modalities": { "input": [ "text", @@ -26260,52 +28021,48 @@ ] }, "limit": { - "context": 1047576, - "output": 32768 + "context": 1050000, + "output": 128000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false - }, - "attachment": true, - "open_weights": false, - "knowledge": "2024-04", - "release_date": "2025-04-14", - "last_updated": "2025-04-14", - "type": "chat" - }, - { - "id": "openai/gpt-4o-transcribe", - "name": "GPT-4o Transcribe", - "display_name": "GPT-4o Transcribe", - "modalities": { - "input": [ - "audio" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 16000, - "output": 2000 + "supported": true, + "default": false }, - "temperature": false, - "tool_call": false, - "reasoning": { - "supported": false + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": false, + "mode": "effort", + "effort": "none", + "effort_options": [ + "none", + "low", + "medium", + "high", + "xhigh" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" + } }, "attachment": true, "open_weights": false, - "knowledge": "2024-06-01", - "release_date": "2025-03-20", - "last_updated": "2025-03-20" + "knowledge": "2025-08-31", + "release_date": "2026-03-05", + "last_updated": "2026-03-05", + "type": "chat" }, { - "id": "openai/gpt-5.4-nano", - "name": "GPT 5.4 Nano", - "display_name": "GPT 5.4 Nano", + "id": "openai/gpt-5.4-mini", + "name": "GPT 5.4 Mini", + "display_name": "GPT 5.4 Mini", "modalities": { "input": [ "text", @@ -26356,9 +28113,9 @@ "type": "chat" }, { - "id": "openai/gpt-5.2-fast", - "name": "GPT 5.2 (Fast)", - "display_name": "GPT 5.2 (Fast)", + "id": "openai/gpt-5.4-fast", + "name": "GPT 5.4 (Fast)", + "display_name": "GPT 5.4 (Fast)", "modalities": { "input": [ "text", @@ -26370,7 +28127,7 @@ ] }, "limit": { - "context": 400000, + "context": 1050000, "output": 128000 }, "temperature": true, @@ -26404,14 +28161,14 @@ "attachment": true, "open_weights": false, "knowledge": "2025-08-31", - "release_date": "2025-12-11", - "last_updated": "2025-12-11", + "release_date": "2026-03-05", + "last_updated": "2026-03-05", "type": "chat" }, { - "id": "openai/o3-pro", - "name": "o3 Pro", - "display_name": "o3 Pro", + "id": "openai/gpt-5.1-codex-max", + "name": "GPT 5.1 Codex Max", + "display_name": "GPT 5.1 Codex Max", "modalities": { "input": [ "text", @@ -26423,22 +28180,29 @@ ] }, "limit": { - "context": 200000, - "output": 100000 + "context": 400000, + "output": 128000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, + "default_enabled": false, "mode": "effort", - "effort": "medium", + "effort": "none", "effort_options": [ + "none", + "low", + "medium", + "high" + ], + "verbosity": "medium", + "verbosity_options": [ "low", "medium", "high" @@ -26448,15 +28212,15 @@ }, "attachment": true, "open_weights": false, - "knowledge": "2024-05", - "release_date": "2025-06-10", - "last_updated": "2025-06-10", + "knowledge": "2024-09-30", + "release_date": "2025-11-13", + "last_updated": "2025-11-13", "type": "chat" }, { - "id": "openai/gpt-5.5-pro", - "name": "GPT 5.5 Pro", - "display_name": "GPT 5.5 Pro", + "id": "openai/gpt-6-sol", + "name": "GPT-6 Sol", + "display_name": "GPT-6 Sol", "modalities": { "input": [ "text", @@ -26468,10 +28232,10 @@ ] }, "limit": { - "context": 1000000, + "context": 1050000, "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -26479,25 +28243,27 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, "effort_options": [ + "none", + "low", "medium", "high", - "xhigh" + "xhigh", + "max" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-12-01", - "release_date": "2026-04-23", - "last_updated": "2026-04-23", + "knowledge": "2026-04-20", + "release_date": "2026-09-22", + "last_updated": "2026-09-22", "type": "chat" }, { - "id": "openai/gpt-5.2-pro", - "name": "GPT 5.2 ", - "display_name": "GPT 5.2 ", + "id": "openai/gpt-5.6-terra", + "name": "GPT 5.6 Terra", + "display_name": "GPT 5.6 Terra", "modalities": { "input": [ "text", @@ -26509,7 +28275,7 @@ ] }, "limit": { - "context": 400000, + "context": 1050000, "output": 128000 }, "temperature": true, @@ -26523,11 +28289,14 @@ "supported": true, "default_enabled": true, "mode": "effort", - "effort": "high", + "effort": "medium", "effort_options": [ + "none", + "low", "medium", "high", - "xhigh" + "xhigh", + "max" ], "verbosity": "medium", "verbosity_options": [ @@ -26540,28 +28309,28 @@ }, "attachment": true, "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2025-12-11", - "last_updated": "2025-12-11", + "knowledge": "2026-02-16", + "release_date": "2026-07-09", + "last_updated": "2026-07-09", "type": "chat" }, { - "id": "openai/gpt-4o-mini-fast", - "name": "GPT-4o mini (Fast)", - "display_name": "GPT-4o mini (Fast)", + "id": "openai/gpt-live-1", + "name": "GPT-Live 1", + "display_name": "GPT-Live 1", "modalities": { "input": [ "text", - "image", - "pdf" + "audio" ], "output": [ - "text" + "text", + "audio" ] }, "limit": { - "context": 128000, - "output": 16384 + "context": 8192, + "output": 8192 }, "temperature": true, "tool_call": true, @@ -26570,26 +28339,27 @@ }, "attachment": true, "open_weights": false, - "knowledge": "2023-09", - "release_date": "2024-07-18", - "last_updated": "2024-07-18", - "type": "chat" + "knowledge": "2025-07-31", + "release_date": "2026-09-10", + "last_updated": "2026-09-10" }, { - "id": "openai/gpt-oss-safeguard-20b", - "name": "gpt-oss-safeguard-20b", - "display_name": "gpt-oss-safeguard-20b", + "id": "openai/gpt-5.3-codex", + "name": "GPT 5.3 Codex", + "display_name": "GPT 5.3 Codex", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 16000 + "context": 400000, + "output": 128000 }, "temperature": true, "tool_call": true, @@ -26599,53 +28369,36 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", "effort_options": [ + "low", + "medium", + "high", + "xhigh" + ], + "verbosity": "medium", + "verbosity_options": [ "low", "medium", "high" - ] + ], + "visibility": "hidden" } }, - "attachment": false, - "open_weights": true, - "release_date": "2025-10-29", - "last_updated": "2025-10-29", - "type": "chat" - }, - { - "id": "openai/gpt-4.1", - "name": "GPT-4.1", - "display_name": "GPT-4.1", - "modalities": { - "input": [ - "text", - "image", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 1047576, - "output": 32768 - }, - "temperature": true, - "tool_call": true, - "reasoning": { - "supported": false - }, "attachment": true, "open_weights": false, - "knowledge": "2024-04", - "release_date": "2025-04-14", - "last_updated": "2025-04-14", + "knowledge": "2025-08-31", + "release_date": "2026-02-05", + "last_updated": "2026-02-05", "type": "chat" }, { - "id": "openai/gpt-5.6-sol-fast", - "name": "GPT 5.6 Sol (Fast)", - "display_name": "GPT 5.6 Sol (Fast)", + "id": "openai/gpt-6-astra", + "name": "GPT-6 Astra", + "display_name": "GPT-6 Astra", "modalities": { "input": [ "text", @@ -26668,9 +28421,8 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, "effort_options": [ - "none", - "minimal", "low", "medium", "high", @@ -26681,15 +28433,15 @@ }, "attachment": true, "open_weights": false, - "knowledge": "2026-02-16", - "release_date": "2026-07-09", - "last_updated": "2026-07-09", + "knowledge": "2026-04-30", + "release_date": "2026-09-04", + "last_updated": "2026-09-04", "type": "chat" }, { - "id": "openai/gpt-5.2-codex", - "name": "GPT-5.2-Codex", - "display_name": "GPT-5.2-Codex", + "id": "openai/gpt-5.1-codex", + "name": "GPT-5.1-Codex", + "display_name": "GPT-5.1-Codex", "modalities": { "input": [ "text", @@ -26708,19 +28460,19 @@ "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, + "default_enabled": false, "mode": "effort", - "effort": "medium", + "effort": "none", "effort_options": [ + "none", "low", "medium", - "high", - "xhigh" + "high" ], "verbosity": "medium", "verbosity_options": [ @@ -26733,15 +28485,15 @@ }, "attachment": true, "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2025-12-11", - "last_updated": "2025-12-11", + "knowledge": "2024-09-30", + "release_date": "2025-11-13", + "last_updated": "2025-11-13", "type": "chat" }, { - "id": "openai/gpt-6-astra-fast", - "name": "GPT-6 Astra (Fast)", - "display_name": "GPT-6 Astra (Fast)", + "id": "openai/gpt-6.1-terra-fast", + "name": "GPT-6.1 Terra (Fast)", + "display_name": "GPT-6.1 Terra (Fast)", "modalities": { "input": [ "text", @@ -26765,6 +28517,7 @@ "extra_capabilities": { "reasoning": { "effort_options": [ + "none", "low", "medium", "high", @@ -26775,59 +28528,37 @@ }, "attachment": true, "open_weights": false, - "release_date": "2026-09-04", - "last_updated": "2026-09-04", + "knowledge": "2026-06-01", + "release_date": "2026-10-26", + "last_updated": "2026-10-26", "type": "chat" }, { - "id": "openai/gpt-5.1-thinking-fast", - "name": "GPT 5.1 Thinking (Fast)", - "display_name": "GPT 5.1 Thinking (Fast)", + "id": "openai/gpt-image-2.5-sunburst", + "name": "GPT Image 2.5 Sunburst", + "display_name": "GPT Image 2.5 Sunburst", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ - "text" + "image" ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 8192, + "output": 8192 }, - "tool_call": true, + "temperature": false, + "tool_call": false, "reasoning": { - "supported": true, - "default": false - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "none", - "effort_options": [ - "none", - "low", - "medium", - "high" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" - } + "supported": false }, "attachment": true, "open_weights": false, - "release_date": "2025-11-12", - "last_updated": "2025-11-12", - "type": "chat" + "release_date": "2026-09-08", + "last_updated": "2026-09-08" }, { "id": "openai/gpt-oss-safeguard-120b", @@ -26867,36 +28598,9 @@ "type": "chat" }, { - "id": "openai/gpt-image-2.5-sunburst", - "name": "GPT Image 2.5 Sunburst", - "display_name": "GPT Image 2.5 Sunburst", - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "image" - ] - }, - "limit": { - "context": 8192, - "output": 8192 - }, - "temperature": false, - "tool_call": false, - "reasoning": { - "supported": false - }, - "attachment": true, - "open_weights": false, - "release_date": "2026-09-08", - "last_updated": "2026-09-08" - }, - { - "id": "openai/gpt-6.1-terra", - "name": "GPT-6.1 Terra", - "display_name": "GPT-6.1 Terra", + "id": "openai/gpt-5.2-codex", + "name": "GPT-5.2-Codex", + "display_name": "GPT-5.2-Codex", "modalities": { "input": [ "text", @@ -26908,10 +28612,10 @@ ] }, "limit": { - "context": 1050000, + "context": 400000, "output": 128000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -26919,27 +28623,36 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", "effort_options": [ - "none", "low", "medium", "high", - "xhigh", - "max" - ] + "xhigh" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, "attachment": true, "open_weights": false, - "knowledge": "2026-06-01", - "release_date": "2026-10-26", - "last_updated": "2026-10-26", + "knowledge": "2025-08-31", + "release_date": "2025-12-11", + "last_updated": "2025-12-11", "type": "chat" }, { - "id": "openai/gpt-5.4-fast", - "name": "GPT 5.4 (Fast)", - "display_name": "GPT 5.4 (Fast)", + "id": "openai/gpt-5.2", + "name": "GPT-5.2", + "display_name": "GPT-5.2", "modalities": { "input": [ "text", @@ -26951,7 +28664,7 @@ ] }, "limit": { - "context": 1050000, + "context": 400000, "output": 128000 }, "temperature": true, @@ -26985,86 +28698,92 @@ "attachment": true, "open_weights": false, "knowledge": "2025-08-31", - "release_date": "2026-03-05", - "last_updated": "2026-03-05", + "release_date": "2025-12-11", + "last_updated": "2025-12-11", "type": "chat" }, { - "id": "openai/gpt-realtime-mini", - "name": "GPT-Realtime mini", - "display_name": "GPT-Realtime mini", + "id": "openai/gpt-5.4-pro", + "name": "GPT 5.4 Pro", + "display_name": "GPT 5.4 Pro", "modalities": { "input": [ "text", - "audio" + "image", + "pdf" ], "output": [ - "text", - "audio" + "text" ] }, "limit": { - "context": 32000, - "output": 4096 + "context": 1050000, + "output": 128000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "high", + "effort_options": [ + "medium", + "high", + "xhigh" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" + } }, "attachment": true, "open_weights": false, - "knowledge": "2023-10-01", - "release_date": "2025-10-06", - "last_updated": "2025-10-06" + "knowledge": "2025-08-31", + "release_date": "2026-03-05", + "last_updated": "2026-03-05", + "type": "chat" }, { - "id": "openai/gpt-5.5-fast", - "name": "GPT 5.5 (Fast)", - "display_name": "GPT 5.5 (Fast)", + "id": "openai/gpt-image-1.5", + "name": "GPT Image 1.5", + "display_name": "GPT Image 1.5", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ - "text" + "image" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 8192, + "output": 8192 }, "temperature": false, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "none", - "minimal", - "low", - "medium", - "high", - "xhigh" - ] - } + "supported": false }, "attachment": true, "open_weights": false, - "knowledge": "2025-12-01", - "release_date": "2026-04-23", - "last_updated": "2026-04-23", - "type": "chat" + "release_date": "2025-11-25", + "last_updated": "2025-11-25" }, { - "id": "openai/gpt-5.2", - "name": "GPT-5.2", - "display_name": "GPT-5.2", + "id": "openai/gpt-5.1-thinking", + "name": "GPT 5.1 Thinking", + "display_name": "GPT 5.1 Thinking", "modalities": { "input": [ "text", @@ -27095,8 +28814,7 @@ "none", "low", "medium", - "high", - "xhigh" + "high" ], "verbosity": "medium", "verbosity_options": [ @@ -27109,9 +28827,9 @@ }, "attachment": true, "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2025-12-11", - "last_updated": "2025-12-11", + "knowledge": "2024-10", + "release_date": "2025-11-12", + "last_updated": "2025-08-07", "type": "chat" }, { @@ -27157,36 +28875,9 @@ "type": "chat" }, { - "id": "openai/tts-1", - "name": "TTS-1", - "display_name": "TTS-1", - "modalities": { - "input": [ - "text" - ], - "output": [ - "audio" - ] - }, - "limit": { - "context": 8192, - "output": 8192 - }, - "temperature": false, - "tool_call": false, - "reasoning": { - "supported": false - }, - "attachment": false, - "open_weights": false, - "release_date": "2023-11-06", - "last_updated": "2023-11-06", - "type": "tts" - }, - { - "id": "openai/gpt-5-nano", - "name": "GPT-5 Nano", - "display_name": "GPT-5 Nano", + "id": "openai/gpt-5.3-codex-fast", + "name": "GPT 5.3 Codex (Fast)", + "display_name": "GPT 5.3 Codex (Fast)", "modalities": { "input": [ "text", @@ -27201,7 +28892,7 @@ "context": 400000, "output": 128000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -27214,10 +28905,10 @@ "mode": "effort", "effort": "medium", "effort_options": [ - "minimal", "low", "medium", - "high" + "high", + "xhigh" ], "verbosity": "medium", "verbosity_options": [ @@ -27230,11 +28921,38 @@ }, "attachment": true, "open_weights": false, - "knowledge": "2024-05-30", - "release_date": "2025-08-07", - "last_updated": "2025-08-07", + "knowledge": "2025-08-31", + "release_date": "2026-02-05", + "last_updated": "2026-02-05", "type": "chat" }, + { + "id": "openai/gpt-4o-mini-transcribe", + "name": "GPT-4o mini Transcribe", + "display_name": "GPT-4o mini Transcribe", + "modalities": { + "input": [ + "audio" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 16000, + "output": 2000 + }, + "temperature": false, + "tool_call": false, + "reasoning": { + "supported": false + }, + "attachment": true, + "open_weights": false, + "knowledge": "2024-06-01", + "release_date": "2025-03-20", + "last_updated": "2025-03-20" + }, { "id": "openai/o3-fast", "name": "o3 (Fast)", @@ -27281,22 +28999,24 @@ "type": "chat" }, { - "id": "tencent/hy4-preview", - "name": "Tencent Hy4 Preview", - "display_name": "Tencent Hy4 Preview", + "id": "openai/gpt-5-codex", + "name": "GPT-5-Codex", + "display_name": "GPT-5-Codex", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1024000, - "output": 64000 + "context": 400000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -27305,22 +29025,35 @@ "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", "effort_options": [ - "none", + "minimal", + "low", + "medium", "high" - ] + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-08-28", - "last_updated": "2026-08-28", + "attachment": true, + "open_weights": false, + "knowledge": "2024-09-30", + "release_date": "2025-09-15", + "last_updated": "2025-09-15", "type": "chat" }, { - "id": "tencent/hy-mt2-pro", - "name": "Tencent Hy-MT2-Pro", - "display_name": "Tencent Hy-MT2-Pro", + "id": "openai/text-embedding-3-large", + "name": "text-embedding-3-large", + "display_name": "text-embedding-3-large", "modalities": { "input": [ "text" @@ -27330,194 +29063,233 @@ ] }, "limit": { - "context": 8000, - "output": 4000 + "context": 8192, + "output": 8192 }, + "temperature": false, "tool_call": false, "reasoning": { "supported": false }, "attachment": false, "open_weights": false, - "release_date": "2026-05-21", - "last_updated": "2026-05-21", - "type": "chat" + "release_date": "2024-01-25", + "last_updated": "2024-01-25", + "type": "embedding" }, { - "id": "tencent/hy3", - "name": "Hy3", - "display_name": "Hy3", + "id": "openai/gpt-4.1-nano", + "name": "GPT-4.1 nano", + "display_name": "GPT-4.1 nano", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 1047576, + "output": 32768 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "effort_options": [ - "none", - "low", - "high" - ] - } + "supported": false }, - "attachment": false, - "open_weights": true, - "release_date": "2026-07-06", - "last_updated": "2026-07-06", + "attachment": true, + "open_weights": false, + "knowledge": "2024-04", + "release_date": "2025-04-14", + "last_updated": "2025-04-14", "type": "chat" }, { - "id": "tencent/hy-mt2-plus", - "name": "Tencent Hy-MT2-Plus", - "display_name": "Tencent Hy-MT2-Plus", + "id": "openai/gpt-image-2.5-flare", + "name": "GPT Image 2.5 Flare", + "display_name": "GPT Image 2.5 Flare", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ - "text" + "image" ] }, "limit": { - "context": 8000, - "output": 4000 + "context": 8192, + "output": 8192 }, + "temperature": false, "tool_call": false, "reasoning": { "supported": false }, - "attachment": false, + "attachment": true, "open_weights": false, - "release_date": "2026-06-12", - "last_updated": "2026-06-12", - "type": "chat" + "release_date": "2026-09-08", + "last_updated": "2026-09-08" }, { - "id": "tencent/hy-mt2-lite", - "name": "Tencent Hy-MT2-Lite", - "display_name": "Tencent Hy-MT2-Lite", + "id": "openai/gpt-realtime-2", + "name": "gpt-realtime-2", + "display_name": "gpt-realtime-2", "modalities": { "input": [ - "text" + "text", + "audio" ], "output": [ - "text" + "text", + "audio" ] }, "limit": { - "context": 8000, - "output": 4000 + "context": 128000, + "output": 32000 }, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { "supported": false }, - "attachment": false, + "attachment": true, "open_weights": false, - "release_date": "2026-06-12", - "last_updated": "2026-06-12", - "type": "chat" + "knowledge": "2024-09-30", + "release_date": "2026-05-07", + "last_updated": "2026-05-07" }, { - "id": "voyage/voyage-4-large", - "name": "voyage-4-large", - "display_name": "voyage-4-large", + "id": "openai/gpt-5-nano", + "name": "GPT-5 Nano", + "display_name": "GPT-5 Nano", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 32000, - "output": 8192 + "context": 400000, + "output": 128000 }, "temperature": false, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", + "effort_options": [ + "minimal", + "low", + "medium", + "high" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" + } + }, + "attachment": true, "open_weights": false, - "release_date": "2026-01-15", - "last_updated": "2026-01-15", - "type": "embedding" + "knowledge": "2024-05-30", + "release_date": "2025-08-07", + "last_updated": "2025-08-07", + "type": "chat" }, { - "id": "voyage/voyage-code-2", - "name": "voyage-code-2", - "display_name": "voyage-code-2", + "id": "openai/gpt-4.1-mini-fast", + "name": "GPT-4.1 mini (Fast)", + "display_name": "GPT-4.1 mini (Fast)", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 16000, - "output": 8192 + "context": 1047576, + "output": 32768 }, - "temperature": false, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { "supported": false }, - "attachment": false, + "attachment": true, "open_weights": false, - "release_date": "2024-01-01", - "last_updated": "2024-01", - "type": "embedding" + "knowledge": "2024-04", + "release_date": "2025-04-14", + "last_updated": "2025-04-14", + "type": "chat" }, { - "id": "voyage/voyage-code-3", - "name": "voyage-code-3", - "display_name": "voyage-code-3", + "id": "openai/gpt-5-fast", + "name": "GPT-5 (Fast)", + "display_name": "GPT-5 (Fast)", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 32000, - "output": 8192 + "context": 400000, + "output": 128000 }, "temperature": false, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "minimal", + "low", + "medium", + "high" + ] + } + }, + "attachment": true, "open_weights": false, - "release_date": "2024-12-04", - "last_updated": "2024-09", - "type": "embedding" + "knowledge": "2024-09-30", + "release_date": "2025-08-07", + "last_updated": "2025-08-07", + "type": "chat" }, { - "id": "voyage/voyage-4-lite", - "name": "voyage-4-lite", - "display_name": "voyage-4-lite", + "id": "openai/text-embedding-ada-002", + "name": "text-embedding-ada-002", + "display_name": "text-embedding-ada-002", "modalities": { "input": [ "text" @@ -27527,7 +29299,7 @@ ] }, "limit": { - "context": 32000, + "context": 8192, "output": 8192 }, "temperature": false, @@ -27537,104 +29309,141 @@ }, "attachment": false, "open_weights": false, - "release_date": "2026-01-15", - "last_updated": "2026-01-15", + "release_date": "2022-12-15", + "last_updated": "2022-12-15", "type": "embedding" }, { - "id": "voyage/rerank-2.5", - "name": "Voyage Rerank 2.5", - "display_name": "Voyage Rerank 2.5", + "id": "openai/gpt-5.5-fast", + "name": "GPT 5.5 (Fast)", + "display_name": "GPT 5.5 (Fast)", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 32000, - "output": 8192 + "context": 1000000, + "output": 128000 }, "temperature": false, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "none", + "minimal", + "low", + "medium", + "high", + "xhigh" + ] + } + }, + "attachment": true, "open_weights": false, - "release_date": "2025-08-11", - "last_updated": "2025-08-11" + "knowledge": "2025-12-01", + "release_date": "2026-04-23", + "last_updated": "2026-04-23", + "type": "chat" }, { - "id": "voyage/voyage-law-2", - "name": "voyage-law-2", - "display_name": "voyage-law-2", + "id": "openai/gpt-realtime-1.5", + "name": "GPT-Realtime-1.5", + "display_name": "GPT-Realtime-1.5", "modalities": { "input": [ - "text" + "text", + "audio" ], "output": [ - "text" + "text", + "audio" ] }, "limit": { - "context": 16000, - "output": 8192 + "context": 32000, + "output": 4096 }, - "temperature": false, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { "supported": false }, - "attachment": false, + "attachment": true, "open_weights": false, - "release_date": "2024-04-15", - "last_updated": "2024-03", - "type": "embedding" + "knowledge": "2024-09-30", + "release_date": "2026-02-23", + "last_updated": "2026-02-23" }, { - "id": "voyage/voyage-finance-2", - "name": "voyage-finance-2", - "display_name": "voyage-finance-2", + "id": "openai/o3-pro", + "name": "o3 Pro", + "display_name": "o3 Pro", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 32000, - "output": 8192 + "context": 200000, + "output": 100000 }, - "temperature": false, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", + "effort_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" + } + }, + "attachment": true, "open_weights": false, - "release_date": "2024-06-03", - "last_updated": "2024-03", - "type": "embedding" + "knowledge": "2024-05", + "release_date": "2025-06-10", + "last_updated": "2025-06-10", + "type": "chat" }, { - "id": "voyage/rerank-2.5-lite", - "name": "Voyage Rerank 2.5 Lite", - "display_name": "Voyage Rerank 2.5 Lite", + "id": "openai/tts-1", + "name": "TTS-1", + "display_name": "TTS-1", "modalities": { "input": [ "text" ], "output": [ - "text" + "audio" ] }, "limit": { - "context": 32000, + "context": 8192, "output": 8192 }, "temperature": false, @@ -27644,157 +29453,257 @@ }, "attachment": false, "open_weights": false, - "release_date": "2025-08-11", - "last_updated": "2025-08-11" + "release_date": "2023-11-06", + "last_updated": "2023-11-06", + "type": "tts" }, { - "id": "voyage/rerank-3-lite", - "name": "Voyage Rerank 3 Lite", - "display_name": "Voyage Rerank 3 Lite", + "id": "openai/gpt-5.5-pro", + "name": "GPT 5.5 Pro", + "display_name": "GPT 5.5 Pro", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 32000, - "output": 8192 + "context": 1000000, + "output": 128000 }, - "temperature": false, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, + "extra_capabilities": { + "reasoning": { + "supported": true, + "effort_options": [ + "medium", + "high", + "xhigh" + ] + } + }, + "attachment": true, "open_weights": false, - "release_date": "2026-09-30", - "last_updated": "2026-09-30" + "knowledge": "2025-12-01", + "release_date": "2026-04-23", + "last_updated": "2026-04-23", + "type": "chat" }, { - "id": "voyage/voyage-3.5-lite", - "name": "voyage-3.5-lite", - "display_name": "voyage-3.5-lite", + "id": "openai/gpt-5.6-luna", + "name": "GPT 5.6 Luna", + "display_name": "GPT 5.6 Luna", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 32000, - "output": 8192 + "context": 1050000, + "output": 128000 }, - "temperature": false, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", + "effort_options": [ + "none", + "low", + "medium", + "high", + "xhigh", + "max" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" + } + }, + "attachment": true, "open_weights": false, - "release_date": "2025-05-20", - "last_updated": "2025-05-20", - "type": "embedding" + "knowledge": "2026-02-16", + "release_date": "2026-07-09", + "last_updated": "2026-07-09", + "type": "chat" }, { - "id": "voyage/voyage-3.5", - "name": "voyage-3.5", - "display_name": "voyage-3.5", + "id": "openai/o4-mini", + "name": "o4-mini", + "display_name": "o4-mini", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 32000, - "output": 8192 + "context": 200000, + "output": 100000 }, "temperature": false, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", + "effort_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" + } + }, + "attachment": true, "open_weights": false, - "release_date": "2025-05-20", - "last_updated": "2025-05-20", - "type": "embedding" + "knowledge": "2024-05", + "release_date": "2025-04-16", + "last_updated": "2025-04-16", + "type": "chat" }, { - "id": "voyage/voyage-4", - "name": "voyage-4", - "display_name": "voyage-4", + "id": "openai/gpt-6-astra-fast", + "name": "GPT-6 Astra (Fast)", + "display_name": "GPT-6 Astra (Fast)", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 32000, - "output": 8192 + "context": 1050000, + "output": 128000 }, "temperature": false, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" + ] + } + }, + "attachment": true, "open_weights": false, - "release_date": "2026-01-15", - "last_updated": "2026-01-15", - "type": "embedding" + "release_date": "2026-09-04", + "last_updated": "2026-09-04", + "type": "chat" }, { - "id": "voyage/voyage-3-large", - "name": "voyage-3-large", - "display_name": "voyage-3-large", + "id": "openai/gpt-5", + "name": "GPT-5", + "display_name": "GPT-5", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 32000, - "output": 8192 + "context": 400000, + "output": 128000 }, "temperature": false, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", + "effort_options": [ + "minimal", + "low", + "medium", + "high" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" + } + }, + "attachment": true, "open_weights": false, - "release_date": "2025-01-07", - "last_updated": "2024-09", - "type": "embedding" + "knowledge": "2024-09-30", + "release_date": "2025-08-07", + "last_updated": "2025-08-07", + "type": "chat" }, { - "id": "voyage/rerank-3", - "name": "Voyage Rerank 3", - "display_name": "Voyage Rerank 3", + "id": "openai/tts-1-hd", + "name": "TTS-1 HD", + "display_name": "TTS-1 HD", "modalities": { "input": [ "text" ], "output": [ - "text" + "audio" ] }, "limit": { - "context": 32000, + "context": 8192, "output": 8192 }, "temperature": false, @@ -27804,13 +29713,14 @@ }, "attachment": false, "open_weights": false, - "release_date": "2026-09-30", - "last_updated": "2026-09-30" + "release_date": "2023-11-06", + "last_updated": "2023-11-06", + "type": "tts" }, { - "id": "amazon/nova-2-lite", - "name": "Nova 2 Lite", - "display_name": "Nova 2 Lite", + "id": "openai/gpt-5-mini", + "name": "GPT-5 Mini", + "display_name": "GPT-5 Mini", "modalities": { "input": [ "text", @@ -27822,36 +29732,47 @@ ] }, "limit": { - "context": 1000000, - "output": 65535 + "context": 400000, + "output": 128000 }, - "temperature": true, - "tool_call": false, + "temperature": false, + "tool_call": true, "reasoning": { "supported": true, "default": true }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", "effort_options": [ - "none", + "minimal", "low", "medium", "high" - ] + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, "attachment": true, "open_weights": false, - "knowledge": "2025-10", - "release_date": "2025-12-02", - "last_updated": "2025-12-01", + "knowledge": "2024-05-30", + "release_date": "2025-08-07", + "last_updated": "2025-08-07", "type": "chat" }, { - "id": "amazon/titan-embed-text-v2", - "name": "Titan Text Embeddings V2", - "display_name": "Titan Text Embeddings V2", + "id": "openai/text-embedding-3-small", + "name": "text-embedding-3-small", + "display_name": "text-embedding-3-small", "modalities": { "input": [ "text" @@ -27871,14 +29792,14 @@ }, "attachment": false, "open_weights": false, - "release_date": "2024-04-30", - "last_updated": "2024-04", + "release_date": "2024-01-25", + "last_updated": "2024-01-25", "type": "embedding" }, { - "id": "amazon/nova-micro", - "name": "Nova Micro", - "display_name": "Nova Micro", + "id": "openai/gpt-oss-safeguard-20b", + "name": "gpt-oss-safeguard-20b", + "display_name": "gpt-oss-safeguard-20b", "modalities": { "input": [ "text" @@ -27889,29 +29810,37 @@ }, "limit": { "context": 128000, - "output": 10000 + "output": 16000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "low", + "medium", + "high" + ] + } }, "attachment": false, - "open_weights": false, - "knowledge": "2024-10", - "release_date": "2024-12-03", - "last_updated": "2024-12-03", - "type": "chat" - }, + "open_weights": true, + "release_date": "2025-10-29", + "last_updated": "2025-10-29", + "type": "chat" + }, { - "id": "amazon/nova-lite", - "name": "Nova Lite", - "display_name": "Nova Lite", + "id": "openai/gpt-4o-mini", + "name": "GPT-4o mini", + "display_name": "GPT-4o mini", "modalities": { "input": [ "text", "image", - "video", "pdf" ], "output": [ @@ -27919,8 +29848,8 @@ ] }, "limit": { - "context": 300000, - "output": 10000 + "context": 128000, + "output": 16384 }, "temperature": true, "tool_call": true, @@ -27929,186 +29858,267 @@ }, "attachment": true, "open_weights": false, - "knowledge": "2024-10", - "release_date": "2024-12-03", - "last_updated": "2024-12-03", + "knowledge": "2023-09", + "release_date": "2024-07-18", + "last_updated": "2024-07-18", "type": "chat" }, { - "id": "amazon/nova-pro", - "name": "Nova Pro", - "display_name": "Nova Pro", + "id": "openai/gpt-oss-120b", + "name": "GPT OSS 120B", + "display_name": "GPT OSS 120B", "modalities": { "input": [ - "text", - "image", - "video", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 300000, - "output": 10000 + "context": 131072, + "output": 131072 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": true, - "open_weights": false, - "knowledge": "2024-10", - "release_date": "2024-12-03", - "last_updated": "2024-12-03", + "extra_capabilities": { + "reasoning": { + "supported": true, + "effort_options": [ + "low", + "medium", + "high" + ] + } + }, + "attachment": false, + "open_weights": true, + "knowledge": "2024-06", + "release_date": "2025-08-05", + "last_updated": "2025-08-05", "type": "chat" }, { - "id": "mistral/ministral-8b", - "name": "Ministral 8B (latest)", - "display_name": "Ministral 8B (latest)", + "id": "openai/gpt-realtime-2.1", + "name": "gpt-realtime-2.1", + "display_name": "gpt-realtime-2.1", "modalities": { "input": [ "text", - "image" + "audio" ], "output": [ - "text" + "text", + "audio" ] }, "limit": { - "context": 262144, - "output": 4000 + "context": 128000, + "output": 32000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "minimal", + "low", + "medium", + "high", + "xhigh" + ] + } }, "attachment": true, - "open_weights": true, - "knowledge": "2024-10", - "release_date": "2024-10-16", - "last_updated": "2024-10-04", - "type": "chat" + "open_weights": false, + "knowledge": "2024-09-30", + "release_date": "2026-07-06", + "last_updated": "2026-07-06" }, { - "id": "mistral/voxtral-small-transcribe-3", - "name": "Voxtral Small Transcribe 3", - "display_name": "Voxtral Small Transcribe 3", + "id": "openai/o1", + "name": "o1", + "display_name": "o1", "modalities": { "input": [ - "audio" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 8192, - "output": 8192 + "context": 200000, + "output": 100000 }, "temperature": false, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", + "effort_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" + } }, "attachment": true, "open_weights": false, - "release_date": "2026-10-20", - "last_updated": "2026-10-20" + "knowledge": "2023-09", + "release_date": "2024-12-05", + "last_updated": "2024-12-05", + "type": "chat" }, { - "id": "mistral/mistral-embed", - "name": "Mistral Embed", - "display_name": "Mistral Embed", + "id": "openai/gpt-4o", + "name": "GPT-4o", + "display_name": "GPT-4o", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 8000, - "output": 3072 + "context": 128000, + "output": 16384 }, - "temperature": false, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { "supported": false }, - "attachment": false, + "attachment": true, "open_weights": false, - "release_date": "2023-12-11", - "last_updated": "2023-12-11", - "type": "embedding" + "knowledge": "2023-09", + "release_date": "2024-05-13", + "last_updated": "2024-08-06", + "type": "chat" }, { - "id": "mistral/mistral-small", - "name": "Mistral Small (latest)", - "display_name": "Mistral Small (latest)", + "id": "openai/gpt-5.6-sol", + "name": "GPT 5.6 Sol", + "display_name": "GPT 5.6 Sol", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 4000 + "context": 1050000, + "output": 128000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", + "effort_options": [ + "none", + "low", + "medium", + "high", + "xhigh", + "max" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" + } }, "attachment": true, - "open_weights": true, - "knowledge": "2025-06", - "release_date": "2026-03-16", - "last_updated": "2026-03-16", + "open_weights": false, + "knowledge": "2026-02-16", + "release_date": "2026-07-09", + "last_updated": "2026-07-09", "type": "chat" }, { - "id": "mistral/mistral-large-3", - "name": "Mistral Large 3", - "display_name": "Mistral Large 3", + "id": "openai/gpt-6-luna-fast", + "name": "GPT-6 Luna (Fast)", + "display_name": "GPT-6 Luna (Fast)", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 256000 + "context": 1050000, + "output": 128000 }, - "temperature": true, - "tool_call": false, + "temperature": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "none", + "low", + "medium", + "high", + "xhigh", + "max" + ] + } }, "attachment": true, - "open_weights": true, - "knowledge": "2024-11", - "release_date": "2025-12-02", - "last_updated": "2025-12-02", + "open_weights": false, + "knowledge": "2026-05-18", + "release_date": "2026-09-22", + "last_updated": "2026-09-22", "type": "chat" }, { - "id": "mistral/ministral-14b", - "name": "Ministral 14B", - "display_name": "Ministral 14B", + "id": "openai/gpt-5.2-fast", + "name": "GPT 5.2 (Fast)", + "display_name": "GPT 5.2 (Fast)", "modalities": { "input": [ "text", @@ -28120,38 +30130,63 @@ ] }, "limit": { - "context": 262144, - "output": 256000 + "context": 400000, + "output": 128000 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": false + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": false, + "mode": "effort", + "effort": "none", + "effort_options": [ + "none", + "low", + "medium", + "high", + "xhigh" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" + } }, "attachment": true, - "open_weights": true, - "release_date": "2025-12-02", - "last_updated": "2025-12-02", + "open_weights": false, + "knowledge": "2025-08-31", + "release_date": "2025-12-11", + "last_updated": "2025-12-11", "type": "chat" }, { - "id": "mistral/mistral-large-4", - "name": "Mistral Large 4", - "display_name": "Mistral Large 4", + "id": "openai/gpt-6.1-sol-fast", + "name": "GPT-6.1 Sol (Fast)", + "display_name": "GPT-6.1 Sol (Fast)", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 262144 + "context": 1050000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -28160,21 +30195,55 @@ "extra_capabilities": { "reasoning": { "effort_options": [ - "none", - "high" + "low", + "medium", + "high", + "xhigh", + "max" ] } }, "attachment": true, "open_weights": false, - "release_date": "2026-10-06", - "last_updated": "2026-10-06", + "knowledge": "2026-04-30", + "release_date": "2026-09-29", + "last_updated": "2026-09-29", "type": "chat" }, { - "id": "mistral/voxtral-mini-transcribe-3", - "name": "Voxtral Mini Transcribe 3", - "display_name": "Voxtral Mini Transcribe 3", + "id": "openai/gpt-4o-fast", + "name": "GPT-4o (Fast)", + "display_name": "GPT-4o (Fast)", + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 16384 + }, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": false + }, + "attachment": true, + "open_weights": false, + "knowledge": "2023-09", + "release_date": "2024-05-13", + "last_updated": "2024-08-06", + "type": "chat" + }, + { + "id": "openai/whisper-1", + "name": "Whisper", + "display_name": "Whisper", "modalities": { "input": [ "audio" @@ -28194,117 +30263,183 @@ }, "attachment": true, "open_weights": false, - "release_date": "2026-10-20", - "last_updated": "2026-10-20" + "release_date": "2023-03-01", + "last_updated": "2023-03-01" }, { - "id": "mistral/mistral-medium-3.5", - "name": "Mistral Medium Latest", - "display_name": "Mistral Medium Latest", + "id": "openai/gpt-5.4-mini-fast", + "name": "GPT 5.4 Mini (Fast)", + "display_name": "GPT 5.4 Mini (Fast)", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 256000 + "context": 400000, + "output": 128000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": false, + "mode": "effort", + "effort": "none", "effort_options": [ "none", + "low", + "medium", + "high", + "xhigh" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", "high" - ] + ], + "visibility": "hidden" } }, "attachment": true, "open_weights": false, - "release_date": "2026-04-30", - "last_updated": "2026-04-30", + "knowledge": "2025-08-31", + "release_date": "2026-03-17", + "last_updated": "2026-03-17", "type": "chat" }, { - "id": "mistral/codestral-embed", - "name": "Codestral Embed", - "display_name": "Codestral Embed", + "id": "openai/gpt-5.5", + "name": "GPT 5.5", + "display_name": "GPT 5.5", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 8192, - "output": 3072 + "context": 1000000, + "output": 128000 }, - "temperature": false, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", + "effort_options": [ + "low", + "medium", + "high", + "xhigh" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" + } + }, + "attachment": true, "open_weights": false, - "release_date": "2025-05-28", - "last_updated": "2025-05-28", - "type": "embedding" + "knowledge": "2025-12-01", + "release_date": "2026-04-23", + "last_updated": "2026-04-23", + "type": "chat" }, { - "id": "mistral/mistral-nemo", - "name": "Mistral Nemo", - "display_name": "Mistral Nemo", + "id": "openai/gpt-5.1-codex-mini", + "name": "GPT-5.1 Codex mini", + "display_name": "GPT-5.1 Codex mini", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 131072 + "context": 400000, + "output": 128000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": false }, - "attachment": false, - "open_weights": true, - "knowledge": "2024-07", - "release_date": "2024-07-18", - "last_updated": "2024-07-18", + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": false, + "mode": "effort", + "effort": "none", + "effort_options": [ + "none", + "low", + "medium", + "high" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" + } + }, + "attachment": true, + "open_weights": false, + "knowledge": "2024-09-30", + "release_date": "2025-11-13", + "last_updated": "2025-11-13", "type": "chat" }, { - "id": "mistral/ministral-3b", - "name": "Ministral 3B (latest)", - "display_name": "Ministral 3B (latest)", + "id": "openai/gpt-4o-mini-fast", + "name": "GPT-4o mini (Fast)", + "display_name": "GPT-4o mini (Fast)", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 4000 + "context": 128000, + "output": 16384 }, "temperature": true, "tool_call": true, @@ -28312,50 +30447,79 @@ "supported": false }, "attachment": true, - "open_weights": true, - "knowledge": "2024-10", - "release_date": "2024-10-16", - "last_updated": "2024-10-04", + "open_weights": false, + "knowledge": "2023-09", + "release_date": "2024-07-18", + "last_updated": "2024-07-18", "type": "chat" }, { - "id": "mistral/codestral", - "name": "Codestral (latest)", - "display_name": "Codestral (latest)", + "id": "openai/gpt-realtime-mini", + "name": "GPT-Realtime mini", + "display_name": "GPT-Realtime mini", "modalities": { "input": [ - "text" + "text", + "audio" ], "output": [ - "text" + "text", + "audio" ] }, "limit": { - "context": 128000, - "output": 4000 + "context": 32000, + "output": 4096 }, "temperature": true, "tool_call": true, "reasoning": { "supported": false }, - "attachment": false, - "open_weights": true, - "knowledge": "2024-10", - "release_date": "2024-05-29", - "last_updated": "2025-01-04", - "type": "chat" + "attachment": true, + "open_weights": false, + "knowledge": "2023-10-01", + "release_date": "2025-10-06", + "last_updated": "2025-10-06" }, { - "id": "mistral/voxtral-mini-tts-3", - "name": "Voxtral TTS 3", - "display_name": "Voxtral TTS 3", + "id": "openai/gpt-4o-transcribe", + "name": "GPT-4o Transcribe", + "display_name": "GPT-4o Transcribe", "modalities": { "input": [ + "audio" + ], + "output": [ "text" + ] + }, + "limit": { + "context": 16000, + "output": 2000 + }, + "temperature": false, + "tool_call": false, + "reasoning": { + "supported": false + }, + "attachment": true, + "open_weights": false, + "knowledge": "2024-06-01", + "release_date": "2025-03-20", + "last_updated": "2025-03-20" + }, + { + "id": "openai/gpt-image-1-mini", + "name": "GPT Image 1 Mini", + "display_name": "GPT Image 1 Mini", + "modalities": { + "input": [ + "text", + "image" ], "output": [ - "audio" + "image" ] }, "limit": { @@ -28367,68 +30531,80 @@ "reasoning": { "supported": false }, - "attachment": false, + "attachment": true, "open_weights": false, - "release_date": "2026-10-20", - "last_updated": "2026-10-20" + "release_date": "2025-10-06", + "last_updated": "2025-10-06" }, { - "id": "reflection/beam-free", - "name": "Beam 1.0 (Free)", - "display_name": "Beam 1.0 (Free)", + "id": "openai/gpt-5.1-thinking-fast", + "name": "GPT 5.1 Thinking (Fast)", + "display_name": "GPT 5.1 Thinking (Fast)", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 400000, + "output": 128000 }, - "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": false, + "mode": "effort", + "effort": "none", "effort_options": [ + "none", "low", "medium", - "high", - "xhigh", - "max" - ] + "high" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, - "attachment": false, - "open_weights": true, - "knowledge": "2026-06-30", - "release_date": "2026-10-05", - "last_updated": "2026-10-16", + "attachment": true, + "open_weights": false, + "release_date": "2025-11-12", + "last_updated": "2025-11-12", "type": "chat" }, { - "id": "reflection/beam", - "name": "Beam 1.0", - "display_name": "Beam 1.0", + "id": "openai/gpt-5.6-sol-fast", + "name": "GPT 5.6 Sol (Fast)", + "display_name": "GPT 5.6 Sol (Fast)", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 1050000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -28437,6 +30613,8 @@ "extra_capabilities": { "reasoning": { "effort_options": [ + "none", + "minimal", "low", "medium", "high", @@ -28445,29 +30623,32 @@ ] } }, - "attachment": false, - "open_weights": true, - "knowledge": "2026-06-30", - "release_date": "2026-10-05", - "last_updated": "2026-10-16", + "attachment": true, + "open_weights": false, + "knowledge": "2026-02-16", + "release_date": "2026-07-09", + "last_updated": "2026-07-09", "type": "chat" }, { - "id": "liquid/lfm2.5-24b-a2b", - "name": "LFM2.5-24B-A2B", - "display_name": "LFM2.5-24B-A2B", + "id": "openai/gpt-5.6-terra-fast", + "name": "GPT 5.6 Terra (Fast)", + "display_name": "GPT 5.6 Terra (Fast)", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 8200 + "context": 1050000, + "output": 128000 }, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -28476,6 +30657,7 @@ "extra_capabilities": { "reasoning": { "effort_options": [ + "none", "minimal", "low", "medium", @@ -28485,68 +30667,73 @@ ] } }, - "attachment": false, + "attachment": true, "open_weights": false, - "release_date": "2026-10-13", - "last_updated": "2026-10-13", + "knowledge": "2026-02-16", + "release_date": "2026-07-09", + "last_updated": "2026-07-09", "type": "chat" }, { - "id": "scaledown/scaledown-extract-1", - "name": "Extract-1", - "display_name": "Extract-1", + "id": "openai/gpt-4.1-fast", + "name": "GPT-4.1 (Fast)", + "display_name": "GPT-4.1 (Fast)", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 1000000 + "context": 1047576, + "output": 32768 }, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { "supported": false }, - "attachment": false, + "attachment": true, "open_weights": false, - "release_date": "2026-10-20", - "last_updated": "2026-10-20", + "knowledge": "2024-04", + "release_date": "2025-04-14", + "last_updated": "2025-04-14", "type": "chat" }, { - "id": "scaledown/scaledown-summarize-1", - "name": "Summarize-1", - "display_name": "Summarize-1", + "id": "openai/gpt-realtime-whisper", + "name": "gpt-realtime-whisper", + "display_name": "gpt-realtime-whisper", "modalities": { "input": [ - "text" + "audio" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 1000 + "context": 16000, + "output": 2000 }, + "temperature": false, "tool_call": false, "reasoning": { "supported": false }, - "attachment": false, + "attachment": true, "open_weights": false, - "release_date": "2026-10-20", - "last_updated": "2026-10-20", - "type": "chat" + "release_date": "2026-05-07", + "last_updated": "2026-05-07" }, { - "id": "interfaze/interfaze-beta", - "name": "Interfaze Beta", - "display_name": "Interfaze Beta", + "id": "openai/gpt-5-mini-fast", + "name": "GPT-5 mini (Fast)", + "display_name": "GPT-5 mini (Fast)", "modalities": { "input": [ "text", @@ -28558,11 +30745,11 @@ ] }, "limit": { - "context": 1000000, - "output": 32000 + "context": 400000, + "output": 128000 }, - "temperature": true, - "tool_call": false, + "temperature": false, + "tool_call": true, "reasoning": { "supported": true, "default": true @@ -28570,6 +30757,7 @@ "extra_capabilities": { "reasoning": { "effort_options": [ + "minimal", "low", "medium", "high" @@ -28578,52 +30766,45 @@ }, "attachment": true, "open_weights": false, - "release_date": "2025-10-07", - "last_updated": "2026-04-29", + "knowledge": "2024-05-30", + "release_date": "2025-08-07", + "last_updated": "2025-08-07", "type": "chat" }, { - "id": "particle/particle-1", - "name": "Particle-1", - "display_name": "Particle-1", + "id": "openai/gpt-4.1-mini", + "name": "GPT-4.1 mini", + "display_name": "GPT-4.1 mini", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 1047576, + "output": 32768 }, + "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "none", - "low", - "high", - "max" - ] - } + "supported": false }, "attachment": true, "open_weights": false, - "release_date": "2026-10-28", - "last_updated": "2026-10-28", + "knowledge": "2024-04", + "release_date": "2025-04-14", + "last_updated": "2025-04-14", "type": "chat" }, { - "id": "moonshotai/kimi-k3", - "name": "Kimi K3", - "display_name": "Kimi K3", + "id": "openai/gpt-5.6-luna-fast", + "name": "GPT 5.6 Luna (Fast)", + "display_name": "GPT 5.6 Luna (Fast)", "modalities": { "input": [ "text", @@ -28635,8 +30816,8 @@ ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 1050000, + "output": 128000 }, "temperature": false, "tool_call": true, @@ -28646,73 +30827,72 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, "effort_options": [ + "none", "low", + "medium", "high", + "xhigh", "max" ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-07-16", - "last_updated": "2026-07-16", + "open_weights": false, + "knowledge": "2026-02-16", + "release_date": "2026-07-09", + "last_updated": "2026-07-09", "type": "chat" }, { - "id": "moonshotai/kimi-k2.6", - "name": "Kimi K2.6", - "display_name": "Kimi K2.6", + "id": "openai/gpt-4.1", + "name": "GPT-4.1", + "display_name": "GPT-4.1", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 262000, - "output": 262000 + "context": 1047576, + "output": 32768 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true - } + "supported": false }, "attachment": true, - "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-04-20", - "last_updated": "2026-04-20", + "open_weights": false, + "knowledge": "2024-04", + "release_date": "2025-04-14", + "last_updated": "2025-04-14", "type": "chat" }, { - "id": "moonshotai/kimi-k2.5", - "name": "Kimi K2.5", - "display_name": "Kimi K2.5", + "id": "openai/gpt-6-sol-fast", + "name": "GPT-6 Sol (Fast)", + "display_name": "GPT-6 Sol (Fast)", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 256000, - "output": 256000 + "context": 1050000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -28720,81 +30900,40 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], "effort_options": [ "none", "low", "medium", - "high" - ] - } - }, - "attachment": true, - "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-01-27", - "last_updated": "2026-01-27", - "type": "chat" - }, - { - "id": "moonshotai/kimi-k3.1-pro-highspeed", - "name": "Kimi K3.1 Pro Highspeed", - "display_name": "Kimi K3.1 Pro Highspeed", - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 1048576, - "output": 1048576 - }, - "tool_call": true, - "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "low", "high", + "xhigh", "max" ] } }, "attachment": true, "open_weights": false, - "release_date": "2026-10-23", - "last_updated": "2026-10-23", + "knowledge": "2026-04-20", + "release_date": "2026-09-22", + "last_updated": "2026-09-22", "type": "chat" }, { - "id": "moonshotai/kimi-k3.1-pro", - "name": "Kimi K3.1 Pro", - "display_name": "Kimi K3.1 Pro", + "id": "openai/gpt-6-luna", + "name": "GPT-6 Luna", + "display_name": "GPT-6 Luna", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 1048576 + "context": 1050000, + "output": 128000 }, "temperature": false, "tool_call": true, @@ -28805,22 +30944,26 @@ "extra_capabilities": { "reasoning": { "effort_options": [ + "none", "low", + "medium", "high", + "xhigh", "max" ] } }, "attachment": true, "open_weights": false, - "release_date": "2026-10-23", - "last_updated": "2026-10-23", + "knowledge": "2026-05-18", + "release_date": "2026-09-22", + "last_updated": "2026-09-22", "type": "chat" }, { - "id": "moonshotai/kimi-k3-fast", - "name": "Kimi K3 Fast", - "display_name": "Kimi K3 Fast", + "id": "openai/o3", + "name": "o3", + "display_name": "o3", "modalities": { "input": [ "text", @@ -28832,8 +30975,8 @@ ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 200000, + "output": 100000 }, "temperature": false, "tool_call": true, @@ -28843,66 +30986,67 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", "effort_options": [ - "none", "low", "medium", - "high", - "max" - ] + "high" + ], + "visibility": "hidden" } }, "attachment": true, - "open_weights": true, - "release_date": "2026-07-16", - "last_updated": "2026-07-16", + "open_weights": false, + "knowledge": "2024-05", + "release_date": "2025-04-16", + "last_updated": "2025-04-16", "type": "chat" }, { - "id": "moonshotai/kimi-k2", - "name": "Kimi K2 Instruct", - "display_name": "Kimi K2 Instruct", + "id": "openai/gpt-image-2", + "name": "GPT Image 2", + "display_name": "GPT Image 2", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ - "text" + "image" ] }, "limit": { - "context": 131072, - "output": 131072 + "context": 8192, + "output": 8192 }, - "temperature": true, - "tool_call": true, + "temperature": false, + "tool_call": false, "reasoning": { "supported": false }, - "attachment": false, - "open_weights": true, - "knowledge": "2024-12-31", - "release_date": "2025-07-11", - "last_updated": "2025-07-11", - "type": "chat" + "attachment": true, + "open_weights": false, + "release_date": "2026-04-21", + "last_updated": "2026-04-21" }, { - "id": "moonshotai/kimi-k2.7-code", - "name": "Kimi K2.7 Code", - "display_name": "Kimi K2.7 Code", + "id": "openai/gpt-oss-20b", + "name": "GPT OSS 20B", + "display_name": "GPT OSS 20B", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 256000, - "output": 32768 + "context": 131072, + "output": 8192 }, "temperature": true, "tool_call": true, @@ -28914,39 +31058,36 @@ "reasoning": { "supported": true, "effort_options": [ - "none", "low", "medium", "high" ] } }, - "attachment": true, + "attachment": false, "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-06-12", - "last_updated": "2026-06-12", + "knowledge": "2024-06", + "release_date": "2025-08-05", + "last_updated": "2025-08-05", "type": "chat" }, { - "id": "moonshotai/kimi-k2.7-code-highspeed", - "name": "Kimi K2.7 Code High Speed", - "display_name": "Kimi K2.7 Code High Speed", + "id": "meituan/longcat-2.5", + "name": "LongCat 2.5", + "display_name": "LongCat 2.5", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 32768 + "context": 1048576, + "output": 131072 }, - "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -28954,7 +31095,6 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, "effort_options": [ "none", "low", @@ -28964,30 +31104,29 @@ } }, "attachment": true, - "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-06-12", - "last_updated": "2026-06-12", + "open_weights": false, + "release_date": "2026-11-02", + "last_updated": "2026-11-02", "type": "chat" }, { - "id": "meta/muse-spark-1.3-contributor", - "name": "Muse Spark 1.3 Contributor", - "display_name": "Muse Spark 1.3 Contributor", + "id": "sakana/fugu-max", + "name": "Fugu Max", + "display_name": "Fugu Max", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 1048576 + "context": 1000000, + "output": 1000000 }, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -28996,9 +31135,6 @@ "extra_capabilities": { "reasoning": { "effort_options": [ - "minimal", - "low", - "medium", "high", "xhigh", "max" @@ -29007,14 +31143,14 @@ }, "attachment": true, "open_weights": false, - "release_date": "2026-09-02", - "last_updated": "2026-09-02", + "release_date": "2026-09-11", + "last_updated": "2026-09-11", "type": "chat" }, { - "id": "meta/muse-spark-open", - "name": "Muse Spark Open", - "display_name": "Muse Spark Open", + "id": "sakana/fugu-ultra-v2", + "name": "Fugu Ultra v2", + "display_name": "Fugu Ultra v2", "modalities": { "input": [ "text", @@ -29025,10 +31161,10 @@ ] }, "limit": { - "context": 1048576, - "output": 1048576 + "context": 1000000, + "output": 1000000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -29037,24 +31173,23 @@ "extra_capabilities": { "reasoning": { "effort_options": [ - "low", - "medium", "high", - "xhigh" + "xhigh", + "max" ] } }, "attachment": true, - "open_weights": true, - "knowledge": "2026-01-04", - "release_date": "2026-10-15", - "last_updated": "2026-10-15", + "open_weights": false, + "knowledge": "2026-08-28", + "release_date": "2026-09-11", + "last_updated": "2026-09-11", "type": "chat" }, { - "id": "meta/muse-wonder-1.0", - "name": "Muse Wonder", - "display_name": "Muse Wonder", + "id": "sakana/namazu", + "name": "Sakana Namazu", + "display_name": "Sakana Namazu", "modalities": { "input": [ "text", @@ -29066,8 +31201,8 @@ ] }, "limit": { - "context": 1048576, - "output": 1048576 + "context": 256000, + "output": 256000 }, "temperature": true, "tool_call": true, @@ -29078,6 +31213,7 @@ "extra_capabilities": { "reasoning": { "effort_options": [ + "none", "minimal", "low", "medium", @@ -29089,27 +31225,26 @@ }, "attachment": true, "open_weights": false, - "release_date": "2026-10-12", - "last_updated": "2026-10-12", + "release_date": "2026-08-03", + "last_updated": "2026-08-03", "type": "chat" }, { - "id": "meta/muse-spark-1.3", - "name": "Muse Spark 1.3", - "display_name": "Muse Spark 1.3", + "id": "sakana/fugu-ultra", + "name": "Fugu Ultra", + "display_name": "Fugu Ultra", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 1048576 + "context": 1000000, + "output": 1000000 }, "temperature": true, "tool_call": true, @@ -29119,11 +31254,7 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, "effort_options": [ - "minimal", - "low", - "medium", "high", "xhigh", "max" @@ -29132,74 +31263,253 @@ }, "attachment": true, "open_weights": false, - "release_date": "2026-09-02", - "last_updated": "2026-09-02", + "release_date": "2026-06-15", + "last_updated": "2026-06-15", "type": "chat" }, { - "id": "meta/llama-3.1-70b", - "name": "Llama 3.1 70B Instruct", - "display_name": "Llama 3.1 70B Instruct", + "id": "quiverai/arrow-2", + "name": "Arrow 2", + "display_name": "Arrow 2", + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text", + "image" + ] + }, + "limit": { + "context": 131072, + "output": 65536 + }, + "temperature": false, + "tool_call": true, + "reasoning": { + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "low", + "medium", + "high", + "xhigh" + ] + } + }, + "attachment": true, + "open_weights": false, + "release_date": "2026-09-16", + "last_updated": "2026-09-16", + "type": "chat" + }, + { + "id": "quiverai/arrow-2-telos", + "name": "Arrow 2 Telos", + "display_name": "Arrow 2 Telos", + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text", + "image" + ] + }, + "limit": { + "context": 131072, + "output": 65536 + }, + "temperature": false, + "tool_call": true, + "reasoning": { + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "low", + "medium", + "high", + "xhigh" + ] + } + }, + "attachment": true, + "open_weights": false, + "release_date": "2026-09-16", + "last_updated": "2026-09-16", + "type": "chat" + }, + { + "id": "recraft/recraft-v3", + "name": "Recraft V3", + "display_name": "Recraft V3", "modalities": { "input": [ "text" ], "output": [ + "image" + ] + }, + "limit": { + "context": 512, + "output": 8192 + }, + "temperature": false, + "tool_call": false, + "reasoning": { + "supported": false + }, + "attachment": false, + "open_weights": false, + "release_date": "2024-10-30", + "last_updated": "2024-10" + }, + { + "id": "recraft/recraft-v4", + "name": "Recraft V4", + "display_name": "Recraft V4", + "modalities": { + "input": [ "text" + ], + "output": [ + "image" ] }, "limit": { - "context": 128000, + "context": 8192, "output": 8192 }, - "temperature": true, - "tool_call": true, + "temperature": false, + "tool_call": false, "reasoning": { "supported": false }, "attachment": false, - "open_weights": true, - "knowledge": "2023-12", - "release_date": "2024-07-23", - "last_updated": "2024-07-23", - "type": "chat" + "open_weights": false, + "release_date": "2026-02-17", + "last_updated": "2026-02-17" }, { - "id": "meta/llama-3.1-8b", - "name": "Llama 3.1 8B Instruct", - "display_name": "Llama 3.1 8B Instruct", + "id": "recraft/recraft-v2", + "name": "Recraft V2", + "display_name": "Recraft V2", "modalities": { "input": [ "text" ], "output": [ + "image" + ] + }, + "limit": { + "context": 512, + "output": 8192 + }, + "temperature": false, + "tool_call": false, + "reasoning": { + "supported": false + }, + "attachment": false, + "open_weights": false, + "release_date": "2024-03-13", + "last_updated": "2024-03" + }, + { + "id": "recraft/recraft-v4.1-utility-pro", + "name": "Recraft V4.1 Utility Pro", + "display_name": "Recraft V4.1 Utility Pro", + "modalities": { + "input": [ "text" + ], + "output": [ + "image" ] }, "limit": { - "context": 128000, + "context": 8192, "output": 8192 }, - "temperature": true, - "tool_call": true, + "temperature": false, + "tool_call": false, "reasoning": { "supported": false }, "attachment": false, - "open_weights": true, - "knowledge": "2023-12", - "release_date": "2024-07-23", - "last_updated": "2024-07-23", - "type": "chat" + "open_weights": false, + "release_date": "2026-05-14", + "last_updated": "2026-05-14" }, { - "id": "meta/muse-image-1.0", - "name": "Muse Image 1.0", - "display_name": "Muse Image 1.0", + "id": "recraft/recraft-v4.1-pro", + "name": "Recraft V4.1 Pro", + "display_name": "Recraft V4.1 Pro", "modalities": { "input": [ - "text", + "text" + ], + "output": [ + "image" + ] + }, + "limit": { + "context": 8192, + "output": 8192 + }, + "temperature": false, + "tool_call": false, + "reasoning": { + "supported": false + }, + "attachment": false, + "open_weights": false, + "release_date": "2026-05-14", + "last_updated": "2026-05-14" + }, + { + "id": "recraft/recraft-v4.1-flash", + "name": "Recraft V4.1 Flash", + "display_name": "Recraft V4.1 Flash", + "modalities": { + "input": [ + "text" + ], + "output": [ "image" + ] + }, + "limit": { + "context": 8192, + "output": 8192 + }, + "temperature": false, + "tool_call": false, + "reasoning": { + "supported": false + }, + "attachment": false, + "open_weights": false, + "release_date": "2026-09-23", + "last_updated": "2026-09-23" + }, + { + "id": "recraft/recraft-v4.1", + "name": "Recraft V4.1", + "display_name": "Recraft V4.1", + "modalities": { + "input": [ + "text" ], "output": [ "image" @@ -29214,28 +31524,78 @@ "reasoning": { "supported": false }, - "attachment": true, + "attachment": false, "open_weights": false, - "release_date": "2026-07-07", - "last_updated": "2026-07-07" + "release_date": "2026-05-14", + "last_updated": "2026-05-14" }, { - "id": "meta/muse-spark-1.1", - "name": "Muse Spark 1.1", - "display_name": "Muse Spark 1.1", + "id": "recraft/recraft-v4.1-utility", + "name": "Recraft V4.1 Utility", + "display_name": "Recraft V4.1 Utility", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" + ], + "output": [ + "image" + ] + }, + "limit": { + "context": 8192, + "output": 8192 + }, + "temperature": false, + "tool_call": false, + "reasoning": { + "supported": false + }, + "attachment": false, + "open_weights": false, + "release_date": "2026-05-14", + "last_updated": "2026-05-14" + }, + { + "id": "recraft/recraft-v4-pro", + "name": "Recraft V4 Pro", + "display_name": "Recraft V4 Pro", + "modalities": { + "input": [ + "text" + ], + "output": [ + "image" + ] + }, + "limit": { + "context": 8192, + "output": 8192 + }, + "temperature": false, + "tool_call": false, + "reasoning": { + "supported": false + }, + "attachment": false, + "open_weights": false, + "release_date": "2026-02-17", + "last_updated": "2026-02-17" + }, + { + "id": "nvidia/nemotron-3.5-lightning-free", + "name": "Nemotron 3.5 Lightning Free", + "display_name": "Nemotron 3.5 Lightning Free", + "modalities": { + "input": [ + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 1048576 + "context": 1000000, + "output": 65000 }, "temperature": true, "tool_call": true, @@ -29247,37 +31607,36 @@ "reasoning": { "supported": true, "effort_options": [ - "minimal", + "none", "low", "medium", - "high", - "xhigh" + "high" ] } }, - "attachment": true, - "open_weights": false, - "release_date": "2026-04-08", - "last_updated": "2026-07-09", + "attachment": false, + "open_weights": true, + "knowledge": "2025-09", + "release_date": "2026-08-11", + "last_updated": "2026-08-11", "type": "chat" }, { - "id": "meta/muse-spark-1.2", - "name": "Muse Spark 1.2", - "display_name": "Muse Spark 1.2", + "id": "nvidia/nemotron-nano-12b-v2-vl", + "name": "Nvidia Nemotron Nano 12B V2 VL", + "display_name": "Nvidia Nemotron Nano 12B V2 VL", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 1048576 + "context": 131072, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -29287,26 +31646,24 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, "effort_options": [ - "minimal", + "none", "low", "medium", - "high", - "xhigh" + "high" ] } }, "attachment": true, - "open_weights": false, - "release_date": "2026-08-05", - "last_updated": "2026-08-05", + "open_weights": true, + "release_date": "2025-10-28", + "last_updated": "2025-10-28", "type": "chat" }, { - "id": "meta/llama-3.3-70b", - "name": "Llama-3.3-70B-Instruct", - "display_name": "Llama-3.3-70B-Instruct", + "id": "nvidia/nemotron-3.5-lightning", + "name": "Nemotron 3.5 Lightning 30B", + "display_name": "Nemotron 3.5 Lightning 30B", "modalities": { "input": [ "text" @@ -29316,30 +31673,41 @@ ] }, "limit": { - "context": 128000, - "output": 8192 + "context": 262144, + "output": 131072 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "effort_options": [ + "none", + "low", + "medium", + "high" + ] + } }, "attachment": false, "open_weights": true, - "knowledge": "2023-12", - "release_date": "2024-12-06", - "last_updated": "2024-12-06", + "knowledge": "2025-09", + "release_date": "2026-08-11", + "last_updated": "2026-08-11", "type": "chat" }, { - "id": "meta/muse-spark-1.2-contributor", - "name": "Muse Spark 1.2 Contributor", - "display_name": "Muse Spark 1.2 Contributor", + "id": "nvidia/nemotron-3.5-super-vl", + "name": "NVIDIA Nemotron 3.5 Super VL", + "display_name": "NVIDIA Nemotron 3.5 Super VL", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" @@ -29349,6 +31717,7 @@ "context": 1048576, "output": 1048576 }, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -29357,36 +31726,34 @@ "extra_capabilities": { "reasoning": { "effort_options": [ - "minimal", + "none", "low", "medium", - "high", - "xhigh" + "high" ] } }, "attachment": true, - "open_weights": false, - "release_date": "2026-08-05", - "last_updated": "2026-08-05", + "open_weights": true, + "release_date": "2026-10-15", + "last_updated": "2026-10-15", "type": "chat" }, { - "id": "meta/muse-glimmer-30b", - "name": "Muse Glimmer 30B", - "display_name": "Muse Glimmer 30B", + "id": "nvidia/nemotron-3-ultra-550b-a55b", + "name": "Nemotron 3 Ultra", + "display_name": "Nemotron 3 Ultra", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 131072 + "context": 1000000, + "output": 65000 }, "temperature": true, "tool_call": true, @@ -29396,6 +31763,7 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, "effort_options": [ "none", "low", @@ -29404,79 +31772,98 @@ ] } }, - "attachment": true, + "attachment": false, "open_weights": true, - "knowledge": "2026-01-04", - "release_date": "2026-08-10", - "last_updated": "2026-08-10", + "knowledge": "2025-09", + "release_date": "2026-06-04", + "last_updated": "2026-06-04", "type": "chat" }, { - "id": "meta/llama-4-maverick", - "name": "Llama-4-Maverick-17B-128E-Instruct-FP8", - "display_name": "Llama-4-Maverick-17B-128E-Instruct-FP8", + "id": "nvidia/nemotron-nano-9b-v2", + "name": "Nvidia Nemotron Nano 9B V2", + "display_name": "Nvidia Nemotron Nano 9B V2", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 8192 + "context": 131072, + "output": 131072 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": true, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "none", + "low", + "medium", + "high" + ] + } + }, + "attachment": false, "open_weights": true, - "knowledge": "2024-08", - "release_date": "2025-04-05", - "last_updated": "2025-04-05", + "knowledge": "2024-09", + "release_date": "2025-08-18", + "last_updated": "2025-08-18", "type": "chat" }, { - "id": "meta/llama-4-scout", - "name": "Llama-4-Scout-17B-16E-Instruct-FP8", - "display_name": "Llama-4-Scout-17B-16E-Instruct-FP8", + "id": "nvidia/nemotron-3-nano-30b-a3b", + "name": "Nemotron 3 Nano 30B A3B", + "display_name": "Nemotron 3 Nano 30B A3B", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 8192 + "context": 262144, + "output": 262144 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": true, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "none", + "low", + "medium", + "high" + ] + } + }, + "attachment": false, "open_weights": true, - "knowledge": "2024-08", - "release_date": "2025-04-05", - "last_updated": "2025-04-05", + "knowledge": "2025-06-25", + "release_date": "2025-12-15", + "last_updated": "2025-12-15", "type": "chat" }, { - "id": "stepfun/step-3.7-flash", - "name": "Step 3.7 Flash", - "display_name": "Step 3.7 Flash", + "id": "nvidia/nemotron-3-super-120b-a12b", + "name": "NVIDIA Nemotron 3 Super 120B A12B", + "display_name": "NVIDIA Nemotron 3 Super 120B A12B", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" @@ -29484,10 +31871,10 @@ }, "limit": { "context": 256000, - "output": 256000 + "output": 32000 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { "supported": true, "default": true @@ -29495,62 +31882,60 @@ "extra_capabilities": { "reasoning": { "effort_options": [ + "none", "low", "medium", "high" ] } }, - "attachment": true, + "attachment": false, "open_weights": true, - "knowledge": "2026-03-01", - "release_date": "2026-05-29", - "last_updated": "2026-05-29", + "knowledge": "2025-06", + "release_date": "2026-03-11", + "last_updated": "2026-03-11", "type": "chat" }, { - "id": "stepfun/step-5-preview", - "name": "Step 5 Preview", - "display_name": "Step 5 Preview", + "id": "voyage/rerank-3", + "name": "Voyage Rerank 3", + "display_name": "Voyage Rerank 3", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 1048576 + "context": 32000, + "output": 8192 }, - "temperature": true, + "temperature": false, "tool_call": false, "reasoning": { "supported": false }, - "attachment": true, - "open_weights": true, - "release_date": "2026-09-16", - "last_updated": "2026-09-20", - "type": "chat" + "attachment": false, + "open_weights": false, + "release_date": "2026-09-30", + "last_updated": "2026-09-30" }, { - "id": "klingai/kling-v2.6-i2v", - "name": "Kling v2.6 Image-to-Video", - "display_name": "Kling v2.6 Image-to-Video", + "id": "voyage/voyage-4-large", + "name": "voyage-4-large", + "display_name": "voyage-4-large", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ - "video" + "text" ] }, "limit": { - "context": 8192, + "context": 32000, "output": 8192 }, "temperature": false, @@ -29558,25 +31943,26 @@ "reasoning": { "supported": false }, - "attachment": true, + "attachment": false, "open_weights": false, - "release_date": "2025-12-03", - "last_updated": "2025-12-21" + "release_date": "2026-01-15", + "last_updated": "2026-01-15", + "type": "embedding" }, { - "id": "klingai/kling-v2.5-turbo-t2v", - "name": "Kling v2.5 Turbo Text-to-Video", - "display_name": "Kling v2.5 Turbo Text-to-Video", + "id": "voyage/voyage-4-lite", + "name": "voyage-4-lite", + "display_name": "voyage-4-lite", "modalities": { "input": [ "text" ], "output": [ - "video" + "text" ] }, "limit": { - "context": 8192, + "context": 32000, "output": 8192 }, "temperature": false, @@ -29586,24 +31972,24 @@ }, "attachment": false, "open_weights": false, - "release_date": "2025-09-23", - "last_updated": "2025-09-23" + "release_date": "2026-01-15", + "last_updated": "2026-01-15", + "type": "embedding" }, { - "id": "klingai/kling-v2.5-turbo-i2v", - "name": "Kling v2.5 Turbo Image-to-Video", - "display_name": "Kling v2.5 Turbo Image-to-Video", + "id": "voyage/rerank-2.5", + "name": "Voyage Rerank 2.5", + "display_name": "Voyage Rerank 2.5", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ - "video" + "text" ] }, "limit": { - "context": 8192, + "context": 32000, "output": 8192 }, "temperature": false, @@ -29611,25 +31997,25 @@ "reasoning": { "supported": false }, - "attachment": true, + "attachment": false, "open_weights": false, - "release_date": "2025-09-23", - "last_updated": "2025-09-23" + "release_date": "2025-08-11", + "last_updated": "2025-08-11" }, { - "id": "klingai/kling-v3.0-t2v", - "name": "Kling v3.0 Text-to-Video", - "display_name": "Kling v3.0 Text-to-Video", + "id": "voyage/rerank-2.5-lite", + "name": "Voyage Rerank 2.5 Lite", + "display_name": "Voyage Rerank 2.5 Lite", "modalities": { "input": [ "text" ], "output": [ - "video" + "text" ] }, "limit": { - "context": 8192, + "context": 32000, "output": 8192 }, "temperature": false, @@ -29639,24 +32025,23 @@ }, "attachment": false, "open_weights": false, - "release_date": "2026-02-05", - "last_updated": "2026-02-05" + "release_date": "2025-08-11", + "last_updated": "2025-08-11" }, { - "id": "klingai/kling-v3.0-i2v", - "name": "Kling v3.0 Image-to-Video", - "display_name": "Kling v3.0 Image-to-Video", + "id": "voyage/voyage-3-large", + "name": "voyage-3-large", + "display_name": "voyage-3-large", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ - "video" + "text" ] }, "limit": { - "context": 8192, + "context": 32000, "output": 8192 }, "temperature": false, @@ -29664,27 +32049,26 @@ "reasoning": { "supported": false }, - "attachment": true, + "attachment": false, "open_weights": false, - "release_date": "2026-02-05", - "last_updated": "2026-02-05" + "release_date": "2025-01-07", + "last_updated": "2024-09", + "type": "embedding" }, { - "id": "klingai/kling-v2.6-motion-control", - "name": "Kling v2.6 Motion Control", - "display_name": "Kling v2.6 Motion Control", + "id": "voyage/voyage-code-3", + "name": "voyage-code-3", + "display_name": "voyage-code-3", "modalities": { "input": [ - "text", - "image", - "video" + "text" ], "output": [ - "video" + "text" ] }, "limit": { - "context": 8192, + "context": 32000, "output": 8192 }, "temperature": false, @@ -29692,27 +32076,26 @@ "reasoning": { "supported": false }, - "attachment": true, + "attachment": false, "open_weights": false, - "release_date": "2025-12-18", - "last_updated": "2025-12-21" + "release_date": "2024-12-04", + "last_updated": "2024-09", + "type": "embedding" }, { - "id": "klingai/kling-v3.0-motion-control", - "name": "Kling v3.0 Motion Control", - "display_name": "Kling v3.0 Motion Control", + "id": "voyage/voyage-law-2", + "name": "voyage-law-2", + "display_name": "voyage-law-2", "modalities": { "input": [ - "text", - "image", - "video" + "text" ], "output": [ - "video" + "text" ] }, "limit": { - "context": 8192, + "context": 16000, "output": 8192 }, "temperature": false, @@ -29720,25 +32103,26 @@ "reasoning": { "supported": false }, - "attachment": true, + "attachment": false, "open_weights": false, - "release_date": "2026-03-04", - "last_updated": "2026-03-04" + "release_date": "2024-04-15", + "last_updated": "2024-03", + "type": "embedding" }, { - "id": "klingai/kling-v2.6-t2v", - "name": "Kling v2.6 Text-to-Video", - "display_name": "Kling v2.6 Text-to-Video", + "id": "voyage/voyage-3.5", + "name": "voyage-3.5", + "display_name": "voyage-3.5", "modalities": { "input": [ "text" ], "output": [ - "video" + "text" ] }, "limit": { - "context": 8192, + "context": 32000, "output": 8192 }, "temperature": false, @@ -29748,13 +32132,14 @@ }, "attachment": false, "open_weights": false, - "release_date": "2025-12-03", - "last_updated": "2025-12-21" + "release_date": "2025-05-20", + "last_updated": "2025-05-20", + "type": "embedding" }, { - "id": "perplexity/pplx-embed-v1-4b", - "name": "Embed v1 4b", - "display_name": "Embed v1 4b", + "id": "voyage/rerank-3-lite", + "name": "Voyage Rerank 3 Lite", + "display_name": "Voyage Rerank 3 Lite", "modalities": { "input": [ "text" @@ -29774,14 +32159,40 @@ }, "attachment": false, "open_weights": false, - "release_date": "2026-02-26", - "last_updated": "2026-02-26", + "release_date": "2026-09-30", + "last_updated": "2026-09-30" + }, + { + "id": "voyage/voyage-code-2", + "name": "voyage-code-2", + "display_name": "voyage-code-2", + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 16000, + "output": 8192 + }, + "temperature": false, + "tool_call": false, + "reasoning": { + "supported": false + }, + "attachment": false, + "open_weights": false, + "release_date": "2024-01-01", + "last_updated": "2024-01", "type": "embedding" }, { - "id": "perplexity/pplx-embed-v1-0.6b", - "name": "Embed v1 0.6b", - "display_name": "Embed v1 0.6b", + "id": "voyage/voyage-3.5-lite", + "name": "voyage-3.5-lite", + "display_name": "voyage-3.5-lite", "modalities": { "input": [ "text" @@ -29801,38 +32212,63 @@ }, "attachment": false, "open_weights": false, - "release_date": "2026-02-26", - "last_updated": "2026-02-26", + "release_date": "2025-05-20", + "last_updated": "2025-05-20", "type": "embedding" }, { - "id": "perplexity/sonar", - "name": "Sonar", - "display_name": "Sonar", + "id": "voyage/voyage-finance-2", + "name": "voyage-finance-2", + "display_name": "voyage-finance-2", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 127000, - "output": 8000 + "context": 32000, + "output": 8192 }, - "temperature": true, - "tool_call": true, + "temperature": false, + "tool_call": false, "reasoning": { "supported": false }, - "attachment": true, + "attachment": false, "open_weights": false, - "knowledge": "2025-02", - "release_date": "2025-02-19", - "last_updated": "2025-02-19", - "type": "chat" + "release_date": "2024-06-03", + "last_updated": "2024-03", + "type": "embedding" + }, + { + "id": "voyage/voyage-4", + "name": "voyage-4", + "display_name": "voyage-4", + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 32000, + "output": 8192 + }, + "temperature": false, + "tool_call": false, + "reasoning": { + "supported": false + }, + "attachment": false, + "open_weights": false, + "release_date": "2026-01-15", + "last_updated": "2026-01-15", + "type": "embedding" }, { "id": "mixedbread/toast-1", @@ -29862,21 +32298,22 @@ "type": "chat" }, { - "id": "callstack/apex", - "name": "Apex", - "display_name": "Apex", + "id": "spacexai/grok-4.20-multi-agent-beta", + "name": "Grok 4.20 Multi Agent Beta", + "display_name": "Grok 4.20 Multi Agent Beta", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 131072 + "context": 2000000, + "output": 2000000 }, "tool_call": true, "reasoning": { @@ -29889,60 +32326,32 @@ "none", "low", "medium", - "xhigh" + "high" ] } }, "attachment": true, "open_weights": false, - "release_date": "2026-10-08", - "last_updated": "2026-10-08", + "release_date": "2026-03-11", + "last_updated": "2026-03-11", "type": "chat" }, { - "id": "heygen/heygen-video-1", - "name": "HeyGen Video", - "display_name": "HeyGen Video", + "id": "spacexai/grok-build-0.1", + "name": "Grok Build 0.1", + "display_name": "Grok Build 0.1", "modalities": { "input": [ "text", - "image", - "video", - "audio" - ], - "output": [ - "video" - ] - }, - "limit": { - "context": 8192, - "output": 8192 - }, - "temperature": false, - "tool_call": false, - "reasoning": { - "supported": false - }, - "attachment": true, - "open_weights": false, - "release_date": "2026-10-09", - "last_updated": "2026-10-09" - }, - { - "id": "inception/mercury-2", - "name": "Mercury 2", - "display_name": "Mercury 2", - "modalities": { - "input": [ - "text" + "image" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 50000 + "context": 256000, + "output": 256000 }, "temperature": true, "tool_call": true, @@ -29952,124 +32361,117 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, "effort_options": [ + "none", "low", "medium", "high" ] } }, - "attachment": false, + "attachment": true, "open_weights": false, - "release_date": "2026-02-24", - "last_updated": "2026-02-24", + "release_date": "2026-04-16", + "last_updated": "2026-04-16", "type": "chat" }, { - "id": "inception/mercury-coder-small", - "name": "Mercury Coder Small Beta", - "display_name": "Mercury Coder Small Beta", + "id": "spacexai/grok-imagine-image-2.0", + "name": "Grok Imagine Image 2.0", + "display_name": "Grok Imagine Image 2.0", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ - "text" + "image" ] }, "limit": { - "context": 32000, - "output": 16384 + "context": 64000, + "output": 8192 }, - "temperature": true, - "tool_call": true, + "temperature": false, + "tool_call": false, "reasoning": { "supported": false }, - "attachment": false, + "attachment": true, "open_weights": false, - "release_date": "2025-02-26", - "last_updated": "2025-02-26", - "type": "chat" + "release_date": "2026-08-07", + "last_updated": "2026-08-07" }, { - "id": "inception/mercury-2.5", - "name": "Mercury 2.5", - "display_name": "Mercury 2.5", + "id": "spacexai/grok-imagine-video-1.5", + "name": "Grok Imagine Video 1.5", + "display_name": "Grok Imagine Video 1.5", "modalities": { "input": [ "text" ], "output": [ - "text" + "video" ] }, "limit": { - "context": 260000, - "output": 65536 + "context": 1024, + "output": 8192 }, - "temperature": true, - "tool_call": true, + "temperature": false, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "none", - "low", - "medium", - "high" - ] - } + "supported": false }, "attachment": false, "open_weights": false, - "release_date": "2026-09-08", - "last_updated": "2026-09-08", - "type": "chat" + "release_date": "2026-05-30", + "last_updated": "2026-05-30" }, { - "id": "inception/mercury-3", - "name": "Mercury 3", - "display_name": "Mercury 3", + "id": "spacexai/grok-4.20-reasoning-beta", + "name": "Grok 4.20 Beta Reasoning", + "display_name": "Grok 4.20 Beta Reasoning", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 2000000, + "output": 2000000 }, "tool_call": true, "reasoning": { "supported": true, "default": true }, - "attachment": false, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "none", + "low", + "medium", + "high" + ] + } + }, + "attachment": true, "open_weights": false, - "release_date": "2026-10-19", - "last_updated": "2026-10-19", + "release_date": "2026-03-11", + "last_updated": "2026-03-11", "type": "chat" - } - ] - }, - "nebius": { - "id": "nebius", - "name": "Nebius Token Factory", - "display_name": "Nebius Token Factory", - "api": "https://api.tokenfactory.nebius.com/v1", - "doc": "https://docs.tokenfactory.nebius.com/", - "models": [ + }, { - "id": "zai-org/GLM-5.3-Flash", - "name": "GLM-5.3-Flash", - "display_name": "GLM-5.3-Flash", + "id": "spacexai/grok-4.7", + "name": "Grok 4.7", + "display_name": "Grok 4.7", "modalities": { "input": [ "text", @@ -30080,8 +32482,8 @@ ] }, "limit": { - "context": 1024000, - "output": 1024000 + "context": 500000, + "output": 500000 }, "temperature": true, "tool_call": true, @@ -30091,41 +32493,38 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], "effort_options": [ "low", + "medium", "high", - "max" + "xhigh" ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-08-26", - "last_updated": "2026-08-26", + "open_weights": false, + "knowledge": "2026-05", + "release_date": "2026-09-21", + "last_updated": "2026-09-21", "type": "chat" }, { - "id": "zai-org/GLM-5.2", - "name": "GLM-5.2", - "display_name": "GLM-5.2", + "id": "spacexai/grok-4.5", + "name": "Grok 4.5", + "display_name": "Grok 4.5", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 1048576 + "context": 500000, + "output": 500000 }, "temperature": true, "tool_call": true, @@ -30136,12 +32535,6 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], "effort_options": [ "low", "medium", @@ -30149,184 +32542,108 @@ ] } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-06-13", - "last_updated": "2026-06-13", + "attachment": true, + "open_weights": false, + "release_date": "2026-07-08", + "last_updated": "2026-07-08", "type": "chat" }, { - "id": "zai-org/GLM-5.3", - "name": "GLM-5.3", - "display_name": "GLM-5.3", + "id": "spacexai/grok-4.20-non-reasoning-beta", + "name": "Grok 4.20 Beta Non-Reasoning", + "display_name": "Grok 4.20 Beta Non-Reasoning", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1024000, - "output": 1024000 + "context": 2000000, + "output": 2000000 }, - "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "effort_options": [ - "low", - "high", - "max" - ] - } + "supported": false }, - "attachment": false, - "open_weights": true, - "release_date": "2026-08-14", - "last_updated": "2026-08-14", + "attachment": true, + "open_weights": false, + "release_date": "2026-03-11", + "last_updated": "2026-03-11", "type": "chat" }, { - "id": "Qwen/Qwen3.5-397B-A17B", - "name": "Qwen3.5-397B-A17B", - "display_name": "Qwen3.5-397B-A17B", + "id": "spacexai/grok-imagine-video", + "name": "Grok Imagine", + "display_name": "Grok Imagine", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ - "text" + "video" ] }, "limit": { - "context": 262144, + "context": 8192, "output": 8192 }, - "temperature": true, - "tool_call": true, + "temperature": false, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } + "supported": false }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-07", - "release_date": "2026-02-15", - "last_updated": "2026-02-15", - "type": "chat" + "attachment": true, + "open_weights": false, + "release_date": "2026-01-28", + "last_updated": "2026-01-28" }, { - "id": "Qwen/Qwen3-30B-A3B-Instruct-2507", - "name": "Qwen3-30B-A3B-Instruct-2507", - "display_name": "Qwen3-30B-A3B-Instruct-2507", + "id": "spacexai/grok-imagine-video-1.5-lite", + "name": "Grok Imagine Video 1.5 Lite", + "display_name": "Grok Imagine Video 1.5 Lite", "modalities": { "input": [ "text" ], "output": [ - "text" + "video" ] }, "limit": { - "context": 262144, + "context": 1024, "output": 8192 }, - "temperature": true, - "tool_call": true, + "temperature": false, + "tool_call": false, "reasoning": { "supported": false }, "attachment": false, - "open_weights": true, - "knowledge": "2025-12", - "release_date": "2025-07-28", - "last_updated": "2025-07-28", - "type": "chat" - }, - { - "id": "Qwen/Qwen3.8-27B", - "name": "Qwen3.8 27B", - "display_name": "Qwen3.8 27B", - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 262144, - "output": 262144 - }, - "temperature": true, - "tool_call": true, - "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "effort_options": [ - "none", - "low", - "medium", - "xhigh" - ] - } - }, - "attachment": true, - "open_weights": true, - "release_date": "2026-08-14", - "last_updated": "2026-08-14", - "type": "chat" + "open_weights": false, + "release_date": "2026-10-01", + "last_updated": "2026-10-01" }, { - "id": "Qwen/Qwen3-Embedding-8B", - "name": "Qwen3-Embedding-8B", - "display_name": "Qwen3-Embedding-8B", + "id": "spacexai/grok-stt", + "name": "Grok STT", + "display_name": "Grok STT", "modalities": { "input": [ - "text" + "audio" ], "output": [ "text" ] }, "limit": { - "context": 40960, + "context": 8192, "output": 8192 }, "temperature": false, @@ -30334,27 +32651,27 @@ "reasoning": { "supported": false }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-10", - "release_date": "2025-06-05", - "last_updated": "2025-06-05", - "type": "embedding" + "attachment": true, + "open_weights": false, + "release_date": "2026-03-16", + "last_updated": "2026-03-16" }, { - "id": "Qwen/Qwen3-235B-A22B-Instruct-2507", - "name": "Qwen3 235B A22B Instruct 2507", - "display_name": "Qwen3 235B A22B Instruct 2507", + "id": "spacexai/grok-voice-think-fast-2.0", + "name": "Grok Voice Think Fast 2.0", + "display_name": "Grok Voice Think Fast 2.0", "modalities": { "input": [ - "text" + "text", + "audio" ], "output": [ - "text" + "text", + "audio" ] }, "limit": { - "context": 262144, + "context": 8192, "output": 8192 }, "temperature": true, @@ -30362,30 +32679,28 @@ "reasoning": { "supported": false }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-07", - "release_date": "2025-07-21", - "last_updated": "2025-07-21", - "type": "chat" + "attachment": true, + "open_weights": false, + "release_date": "2026-07-29", + "last_updated": "2026-07-29" }, { - "id": "meta-models/muse-spark-open", - "name": "Muse Spark Open", - "display_name": "Muse Spark Open", + "id": "spacexai/grok-4.3", + "name": "Grok 4.3", + "display_name": "Grok 4.3", "modalities": { "input": [ "text", "image", - "video" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 1048576 + "context": 1000000, + "output": 1000000 }, "temperature": true, "tool_call": true, @@ -30396,116 +32711,90 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], "effort_options": [ "low", "medium", - "high", - "xhigh" + "high" ] } }, "attachment": true, - "open_weights": true, - "knowledge": "2026-01-04", - "release_date": "2026-10-15", - "last_updated": "2026-10-15", + "open_weights": false, + "release_date": "2026-04-17", + "last_updated": "2026-04-17", "type": "chat" }, { - "id": "nvidia/Nemotron-3.5-Super-VL", - "name": "Nemotron 3.5 Super VL 120B A12B", - "display_name": "Nemotron 3.5 Super VL 120B A12B", + "id": "spacexai/grok-4.20-non-reasoning", + "name": "Grok 4.20 Non-Reasoning", + "display_name": "Grok 4.20 Non-Reasoning", "modalities": { "input": [ "text", "image", - "video" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 1048576 + "context": 2000000, + "output": 2000000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "effort_options": [ - "none", - "high" - ] - } + "supported": false }, "attachment": true, - "open_weights": true, - "release_date": "2026-10-15", - "last_updated": "2026-10-15", + "open_weights": false, + "release_date": "2026-03-09", + "last_updated": "2026-03-09", "type": "chat" }, { - "id": "nvidia/nemotron-3-super-120b-a12b", - "name": "Nemotron-3-Super-120B-A12B", - "display_name": "Nemotron-3-Super-120B-A12B", + "id": "spacexai/grok-tts", + "name": "Grok TTS", + "display_name": "Grok TTS", "modalities": { "input": [ "text" ], "output": [ - "text" + "audio" ] }, "limit": { - "context": 262144, - "output": 32768 + "context": 15000, + "output": 15000 }, - "temperature": true, - "tool_call": true, + "temperature": false, + "tool_call": false, "reasoning": { - "supported": true, - "default": true + "supported": false }, "attachment": false, - "open_weights": true, - "knowledge": "2025-06", - "release_date": "2026-03-11", - "last_updated": "2026-03-11", - "type": "chat" + "open_weights": false, + "release_date": "2026-03-16", + "last_updated": "2026-03-16" }, { - "id": "nvidia/Nemotron-3-Ultra-550b-a55b", - "name": "Nemotron 3 Ultra 550B A55B", - "display_name": "Nemotron 3 Ultra 550B A55B", + "id": "spacexai/grok-4.6", + "name": "Grok 4.6", + "display_name": "Grok 4.6", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 1048576 + "context": 500000, + "output": 500000 }, "temperature": true, "tool_call": true, @@ -30517,35 +32806,38 @@ "reasoning": { "supported": true, "effort_options": [ - "none", - "high" + "low", + "medium", + "high", + "xhigh" ] } }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-09", - "release_date": "2026-06-04", - "last_updated": "2026-06-04", + "attachment": true, + "open_weights": false, + "knowledge": "2026-02-01", + "release_date": "2026-08-12", + "last_updated": "2026-08-12", "type": "chat" }, { - "id": "nvidia/Nemotron-3_5-Lightning", - "name": "Nemotron 3.5 Lightning 30B A3B", - "display_name": "Nemotron 3.5 Lightning 30B A3B", + "id": "spacexai/grok-4.20-multi-agent", + "name": "Grok 4.20 Multi-Agent", + "display_name": "Grok 4.20 Multi-Agent", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 1048576 + "context": 2000000, + "output": 2000000 }, - "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -30555,61 +32847,62 @@ "reasoning": { "effort_options": [ "none", + "low", + "medium", "high" ] } }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-09", - "release_date": "2026-08-11", - "last_updated": "2026-08-11", + "attachment": true, + "open_weights": false, + "release_date": "2026-03-10", + "last_updated": "2026-03-10", "type": "chat" }, { - "id": "google/gemma-3-27b-it", - "name": "Gemma-3-27b-it", - "display_name": "Gemma-3-27b-it", + "id": "spacexai/grok-imagine-image", + "name": "Grok Imagine Image", + "display_name": "Grok Imagine Image", "modalities": { "input": [ "text", "image" ], "output": [ - "text" + "image" ] }, "limit": { - "context": 110000, + "context": 16000, "output": 8192 }, - "temperature": true, - "tool_call": true, + "temperature": false, + "tool_call": false, "reasoning": { "supported": false }, "attachment": true, - "open_weights": true, - "knowledge": "2024-08", - "release_date": "2025-03-12", - "last_updated": "2025-03-12", - "type": "chat" + "open_weights": false, + "release_date": "2026-01-28", + "last_updated": "2026-01-28" }, { - "id": "openai/gpt-oss-120b", - "name": "gpt-oss-120b", - "display_name": "gpt-oss-120b", + "id": "spacexai/grok-4.20-reasoning", + "name": "Grok 4.20 Reasoning", + "display_name": "Grok 4.20 Reasoning", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 8192 + "context": 2000000, + "output": 2000000 }, "temperature": true, "tool_call": true, @@ -30619,43 +32912,37 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], "effort_options": [ + "none", "low", "medium", "high" ] } }, - "attachment": false, - "open_weights": true, - "knowledge": "2024-06", - "release_date": "2025-08-05", - "last_updated": "2025-08-05", + "attachment": true, + "open_weights": false, + "release_date": "2026-03-09", + "last_updated": "2026-03-09", "type": "chat" }, { - "id": "MiniMaxAI/MiniMax-M3.1-Flash", - "name": "MiniMax-M3.1-Flash", - "display_name": "MiniMax-M3.1-Flash", + "id": "thinkingmachines/inkling", + "name": "Inkling", + "display_name": "Inkling", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 1048576 + "context": 256000, + "output": 256000 }, "temperature": true, "tool_call": true, @@ -30665,15 +32952,9 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], "effort_options": [ "none", + "minimal", "low", "medium", "high", @@ -30683,26 +32964,28 @@ } }, "attachment": true, - "open_weights": false, - "release_date": "2026-10-19", - "last_updated": "2026-10-19", + "open_weights": true, + "release_date": "2026-07-15", + "last_updated": "2026-07-15", "type": "chat" }, { - "id": "MiniMaxAI/MiniMax-M3", - "name": "MiniMax-M3", - "display_name": "MiniMax-M3", + "id": "thinkingmachines/inkling-small", + "name": "Inkling Small", + "display_name": "Inkling Small", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 1048576 + "context": 1000000, + "output": 1000000 }, "temperature": true, "tool_call": true, @@ -30712,19 +32995,27 @@ }, "extra_capabilities": { "reasoning": { - "supported": true + "effort_options": [ + "none", + "minimal", + "low", + "medium", + "high", + "xhigh", + "max" + ] } }, - "attachment": false, + "attachment": true, "open_weights": true, - "release_date": "2026-06-01", - "last_updated": "2026-06-01", + "release_date": "2026-07-30", + "last_updated": "2026-07-30", "type": "chat" }, { - "id": "reflection/beam", - "name": "Beam 1.0 501B A23B", - "display_name": "Beam 1.0 501B A23B", + "id": "morph/morph-v3-large", + "name": "Morph V3 Large", + "display_name": "Morph V3 Large", "modalities": { "input": [ "text" @@ -30734,45 +33025,24 @@ ] }, "limit": { - "context": 1048576, + "context": 262144, "output": 131072 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "effort_options": [ - "minimal", - "low", - "medium", - "high", - "xhigh", - "max" - ] - } + "supported": false }, "attachment": false, - "open_weights": true, - "knowledge": "2026-06-30", - "release_date": "2026-10-05", - "last_updated": "2026-10-16", + "open_weights": false, + "release_date": "2025-07-07", + "last_updated": "2025-07-07", "type": "chat" }, { - "id": "moonshotai/Kimi-K2.7-Code", - "name": "Kimi K2.7 Code", - "display_name": "Kimi K2.7 Code", + "id": "morph/morph-v3-fast", + "name": "Morph V3 Fast", + "display_name": "Morph V3 Fast", "modalities": { "input": [ "text" @@ -30783,170 +33053,102 @@ }, "limit": { "context": 262144, - "output": 8000 + "output": 131072 }, - "temperature": false, - "tool_call": true, + "temperature": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } + "supported": false }, "attachment": false, - "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-06-12", - "last_updated": "2026-06-12", + "open_weights": false, + "release_date": "2025-07-07", + "last_updated": "2025-07-07", "type": "chat" }, { - "id": "moonshotai/Kimi-K3", - "name": "Kimi K3", - "display_name": "Kimi K3", + "id": "topaz/starlight-precise-2.6", + "name": "Starlight Precise 2.6", + "display_name": "Starlight Precise 2.6", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ - "text" + "video" ] }, "limit": { - "context": 1024000, - "output": 8000 + "context": 8192, + "output": 8192 }, "temperature": false, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "effort_options": [ - "none", - "low", - "medium", - "high", - "max" - ] - } + "supported": false }, - "attachment": true, - "open_weights": true, - "release_date": "2026-07-16", - "last_updated": "2026-07-16", - "type": "chat" + "attachment": false, + "open_weights": false, + "release_date": "2026-10-06", + "last_updated": "2026-10-06" }, { - "id": "NousResearch/Hermes-4-405B", - "name": "Hermes-4-405B", - "display_name": "Hermes-4-405B", + "id": "topaz/proteus", + "name": "Proteus", + "display_name": "Proteus", "modalities": { "input": [ "text" ], "output": [ - "text" + "video" ] }, "limit": { - "context": 131072, + "context": 8192, "output": 8192 }, - "temperature": true, - "tool_call": true, + "temperature": false, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } + "supported": false }, "attachment": false, - "open_weights": true, - "knowledge": "2024-08-31", - "release_date": "2025-08-26", - "last_updated": "2025-08-26", - "type": "chat" + "open_weights": false, + "release_date": "2026-10-06", + "last_updated": "2026-10-06" }, { - "id": "deepseek-ai/DeepSeek-V4.1-Flash", - "name": "DeepSeek V4.1 Flash", - "display_name": "DeepSeek V4.1 Flash", + "id": "topaz/wonder-3.5", + "name": "Wonder 3.5", + "display_name": "Wonder 3.5", "modalities": { "input": [ "text", "image" ], "output": [ - "text" + "image" ] }, "limit": { - "context": 1048000, - "output": 1048000 + "context": 8192, + "output": 8192 }, - "temperature": true, - "tool_call": true, + "temperature": false, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "effort_options": [ - "none", - "low", - "high", - "max" - ] - } + "supported": false }, "attachment": true, - "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-09-10", - "last_updated": "2026-09-10", - "type": "chat" + "open_weights": false, + "release_date": "2026-10-06", + "last_updated": "2026-10-06" }, { - "id": "deepseek-ai/DeepSeek-V4-Flash-0731", - "name": "DeepSeek V4 Flash 0731", - "display_name": "DeepSeek V4 Flash 0731", + "id": "inception/mercury-coder-small", + "name": "Mercury Coder Small Beta", + "display_name": "Mercury Coder Small Beta", "modalities": { "input": [ "text" @@ -30956,43 +33158,24 @@ ] }, "limit": { - "context": 1024000, - "output": 1024000 + "context": 32000, + "output": 16384 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "effort_options": [ - "none", - "low", - "high", - "max" - ] - } + "supported": false }, "attachment": false, - "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-07-31", - "last_updated": "2026-07-31", + "open_weights": false, + "release_date": "2025-02-26", + "last_updated": "2025-02-26", "type": "chat" }, { - "id": "deepseek-ai/DeepSeek-V4-Pro-0813", - "name": "DeepSeek V4 Pro 0813", - "display_name": "DeepSeek V4 Pro 0813", + "id": "inception/mercury-2", + "name": "Mercury 2", + "display_name": "Mercury 2", "modalities": { "input": [ "text" @@ -31002,8 +33185,8 @@ ] }, "limit": { - "context": 979000, - "output": 979000 + "context": 128000, + "output": 50000 }, "temperature": true, "tool_call": true, @@ -31013,31 +33196,23 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], "effort_options": [ - "none", "low", - "high", - "max" + "medium", + "high" ] } }, "attachment": false, - "open_weights": true, - "release_date": "2026-08-13", - "last_updated": "2026-08-22", + "open_weights": false, + "release_date": "2026-02-24", + "last_updated": "2026-02-24", "type": "chat" }, { - "id": "deepseek-ai/DeepSeek-V4-Pro", - "name": "DeepSeek V4 Pro", - "display_name": "DeepSeek V4 Pro", + "id": "inception/mercury-3", + "name": "Mercury 3", + "display_name": "Mercury 3", "modalities": { "input": [ "text" @@ -31047,60 +33222,35 @@ ] }, "limit": { - "context": 1048576, - "output": 1048576 + "context": 1000000, + "output": 131072 }, - "temperature": true, "tool_call": true, "reasoning": { "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "high", - "effort_options": [ - "low", - "high", - "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." - ] - } - }, "attachment": false, - "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-04-24", - "last_updated": "2026-04-24", + "open_weights": false, + "release_date": "2026-10-19", + "last_updated": "2026-10-19", "type": "chat" }, { - "id": "deepseek-ai/DeepSeek-V4.1-Flash-1017", - "name": "DeepSeek V4.1 Flash 1017", - "display_name": "DeepSeek V4.1 Flash 1017", + "id": "inception/mercury-2.5", + "name": "Mercury 2.5", + "display_name": "Mercury 2.5", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 1048576 + "context": 260000, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -31113,271 +33263,206 @@ "effort_options": [ "none", "low", - "high", - "max" + "medium", + "high" ] } }, - "attachment": true, - "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-10-17", - "last_updated": "2026-10-17", + "attachment": false, + "open_weights": false, + "release_date": "2026-09-08", + "last_updated": "2026-09-08", "type": "chat" - } - ] - }, - "opencode": { - "id": "opencode", - "name": "OpenCode", - "display_name": "OpenCode", - "api": "https://opencode.ai/inference/openai/v1", - "doc": "https://opencode.ai/v2/docs/console/models/", - "models": [ + }, { - "id": "gpt-5.1-codex-max", - "name": "GPT-5.1 Codex Max", - "display_name": "GPT-5.1 Codex Max", + "id": "cohere/embed-v5.0-pro", + "name": "Embed 5 Pro", + "display_name": "Embed 5 Pro", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 128000, + "output": 2048 }, "temperature": false, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": false - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "none", - "effort_options": [ - "none", - "low", - "medium", - "high" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" - } + "supported": false }, - "attachment": true, + "attachment": false, "open_weights": false, - "knowledge": "2024-09-30", - "release_date": "2025-11-13", - "last_updated": "2025-11-13", - "type": "chat" + "release_date": "2026-09-30", + "last_updated": "2026-09-30", + "type": "embedding" }, { - "id": "gpt-5", - "name": "GPT-5", - "display_name": "GPT-5", + "id": "cohere/embed-v5.0-fast", + "name": "Embed 5 Fast", + "display_name": "Embed 5 Fast", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 128000, + "output": 2048 }, "temperature": false, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", - "effort_options": [ - "minimal", - "low", - "medium", - "high" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" - } + "supported": false }, - "attachment": true, + "attachment": false, "open_weights": false, - "knowledge": "2024-09-30", - "release_date": "2025-08-07", - "last_updated": "2025-08-07", - "type": "chat" + "release_date": "2026-09-30", + "last_updated": "2026-09-30", + "type": "embedding" }, { - "id": "muse-spark-1.2-contributor-free", - "name": "Muse Spark 1.2 Free", - "display_name": "Muse Spark 1.2 Free", + "id": "cohere/rerank-v4-pro", + "name": "Cohere Rerank 4 Pro", + "display_name": "Cohere Rerank 4 Pro", "modalities": { "input": [ - "text", - "image", - "video", - "pdf", - "audio" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 32000, + "output": 8192 }, - "temperature": true, - "tool_call": true, + "temperature": false, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "minimal", - "low", - "medium", - "high", - "xhigh" - ] - } + "supported": false }, - "attachment": true, + "attachment": false, "open_weights": false, - "release_date": "2026-08-05", - "last_updated": "2026-08-05", - "type": "chat" + "release_date": "2025-12-11", + "last_updated": "2025-12-11" }, { - "id": "gpt-5.5", - "name": "GPT-5.5", - "display_name": "GPT-5.5", + "id": "cohere/rerank-v5-fast", + "name": "Cohere Rerank 5 Fast", + "display_name": "Cohere Rerank 5 Fast", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 32768, + "output": 8192 }, "temperature": false, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", - "effort_options": [ - "low", - "medium", - "high", - "xhigh" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" - } + "supported": false }, - "attachment": true, + "attachment": false, "open_weights": false, - "knowledge": "2025-12-01", - "release_date": "2026-04-23", - "last_updated": "2026-04-23", - "type": "chat" + "release_date": "2026-10-21", + "last_updated": "2026-10-21" }, { - "id": "gpt-6-astra", - "name": "GPT-6 Astra", - "display_name": "GPT-6 Astra", + "id": "cohere/embed-v4.0", + "name": "Embed v4.0", + "display_name": "Embed v4.0", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 128000, + "output": 1536 }, "temperature": false, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true + "supported": false }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" - ] - } + "attachment": false, + "open_weights": false, + "release_date": "2025-04-15", + "last_updated": "2025-04-15", + "type": "embedding" + }, + { + "id": "cohere/rerank-v3.5", + "name": "Cohere Rerank 3.5", + "display_name": "Cohere Rerank 3.5", + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] }, - "attachment": true, + "limit": { + "context": 4096, + "output": 8192 + }, + "temperature": false, + "tool_call": false, + "reasoning": { + "supported": false + }, + "attachment": false, "open_weights": false, - "knowledge": "2026-04-30", - "release_date": "2026-09-04", - "last_updated": "2026-09-04", - "type": "chat" + "release_date": "2024-12-02", + "last_updated": "2024-12-02" }, { - "id": "grok-code", - "name": "Grok Code Fast 1", - "display_name": "Grok Code Fast 1", + "id": "cohere/rerank-v4-fast", + "name": "Cohere Rerank 4 Fast", + "display_name": "Cohere Rerank 4 Fast", + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 32000, + "output": 8192 + }, + "temperature": false, + "tool_call": false, + "reasoning": { + "supported": false + }, + "attachment": false, + "open_weights": false, + "release_date": "2025-12-11", + "last_updated": "2025-12-11" + }, + { + "id": "cohere/command-a", + "name": "Command A", + "display_name": "Command A", "modalities": { "input": [ "text" @@ -31388,36 +33473,61 @@ }, "limit": { "context": 256000, - "output": 256000 + "output": 8000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true + "supported": false }, "attachment": false, - "open_weights": false, - "release_date": "2025-09-02", - "last_updated": "2025-09-02", + "open_weights": true, + "knowledge": "2024-08-31", + "release_date": "2025-03-13", + "last_updated": "2025-03-13", "type": "chat" }, { - "id": "deepseek-v4.1-flash-free", - "name": "DeepSeek V4.1 Flash Free", - "display_name": "DeepSeek V4.1 Flash Free", + "id": "cohere/rerank-v5-pro", + "name": "Cohere Rerank 5 Pro", + "display_name": "Cohere Rerank 5 Pro", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 32000 + "context": 32768, + "output": 8192 + }, + "temperature": false, + "tool_call": false, + "reasoning": { + "supported": false + }, + "attachment": false, + "open_weights": false, + "release_date": "2026-10-21", + "last_updated": "2026-10-21" + }, + { + "id": "minimax/minimax-m2.5", + "name": "MiniMax M2.5", + "display_name": "MiniMax M2.5", + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 204800, + "output": 131000 }, "temperature": true, "tool_call": true, @@ -31427,187 +33537,117 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "effort_options": [ - "low", - "high", - "max" - ] + "supported": true } }, - "attachment": true, + "attachment": false, "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-09-10", - "last_updated": "2026-09-10", + "release_date": "2026-02-12", + "last_updated": "2026-02-12", "type": "chat" }, { - "id": "claude-opus-4-5", - "name": "Claude Opus 4.5", - "display_name": "Claude Opus 4.5", + "id": "minimax/minimax-m2.5-highspeed", + "name": "MiniMax M2.5 High Speed", + "display_name": "MiniMax M2.5 High Speed", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 64000 + "context": 204800, + "output": 131000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "mixed", - "budget": { - "min": 1024, - "unit": "tokens" - }, - "effort": "high", - "effort_options": [ - "low", - "medium", - "high" - ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Claude Opus 4.5 uses manual thinking.type = \"enabled\" with budget_tokens; effort can be used alongside the thinking budget.", - "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header." - ] + "supported": true } }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-05", - "release_date": "2025-11-24", - "last_updated": "2025-11-24", + "attachment": false, + "open_weights": true, + "release_date": "2026-02-13", + "last_updated": "2026-02-13", "type": "chat" }, { - "id": "gpt-5.4", - "name": "GPT-5.4", - "display_name": "GPT-5.4", + "id": "minimax/minimax-h3", + "name": "MiniMax H3", + "display_name": "MiniMax H3", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ - "text" + "video" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 8192, + "output": 8192 }, - "temperature": true, - "tool_call": true, + "temperature": false, + "tool_call": false, "reasoning": { - "supported": true, - "default": false - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "none", - "effort_options": [ - "none", - "low", - "medium", - "high", - "xhigh" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" - } + "supported": false }, "attachment": true, - "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-03-05", - "last_updated": "2026-03-05", - "type": "chat" + "open_weights": true, + "release_date": "2026-07-31", + "last_updated": "2026-10-04" }, { - "id": "claude-3-5-haiku", - "name": "Claude Haiku 3.5", - "display_name": "Claude Haiku 3.5", + "id": "minimax/minimax-h3-max", + "name": "MiniMax H3 Max", + "display_name": "MiniMax H3 Max", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ - "text" + "video" ] }, "limit": { - "context": 200000, + "context": 8192, "output": 8192 }, - "temperature": true, - "tool_call": true, + "temperature": false, + "tool_call": false, "reasoning": { "supported": false }, "attachment": true, "open_weights": false, - "knowledge": "2024-07-31", - "release_date": "2024-10-22", - "last_updated": "2024-10-22", - "type": "chat" + "release_date": "2026-08-26", + "last_updated": "2026-08-26" }, { - "id": "gemini-3.5-flash-lite", - "name": "Gemini 3.5 Flash Lite", - "display_name": "Gemini 3.5 Flash Lite", + "id": "minimax/minimax-m2.1-lightning", + "name": "MiniMax M2.1 Lightning", + "display_name": "MiniMax M2.1 Lightning", "modalities": { "input": [ - "text", - "image", - "video", - "audio", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 204800, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -31615,28 +33655,17 @@ "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "effort_options": [ - "minimal", - "low", - "medium", - "high" - ] - } - }, - "attachment": true, + "attachment": false, "open_weights": false, - "knowledge": "2026-03", - "release_date": "2026-07-21", - "last_updated": "2026-07-21", + "knowledge": "2024-10", + "release_date": "2025-12-23", + "last_updated": "2025-10-27", "type": "chat" }, { - "id": "minimax-m2.1", - "name": "MiniMax-M2.1", - "display_name": "MiniMax-M2.1", + "id": "minimax/minimax-m2.7-highspeed", + "name": "MiniMax M2.7 High Speed", + "display_name": "MiniMax M2.7 High Speed", "modalities": { "input": [ "text" @@ -31647,7 +33676,7 @@ }, "limit": { "context": 204800, - "output": 131072 + "output": 131100 }, "temperature": true, "tool_call": true, @@ -31668,27 +33697,93 @@ }, "attachment": false, "open_weights": true, - "knowledge": "2025-01", - "release_date": "2025-12-23", - "last_updated": "2025-12-23", + "release_date": "2026-03-18", + "last_updated": "2026-03-18", "type": "chat" }, { - "id": "glm-5.3-flash", - "name": "GLM-5.3-Flash", - "display_name": "GLM-5.3-Flash", + "id": "minimax/minimax-m2", + "name": "MiniMax M2", + "display_name": "MiniMax M2", + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 205000, + "output": 196608 + }, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true + } + }, + "attachment": false, + "open_weights": true, + "release_date": "2025-10-27", + "last_updated": "2025-10-27", + "type": "chat" + }, + { + "id": "minimax/minimax-m3", + "name": "MiniMax M3", + "display_name": "MiniMax M3", "modalities": { "input": [ "text", "image", - "video" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1000000, + "context": 512000, + "output": 512000 + }, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true + } + }, + "attachment": true, + "open_weights": true, + "release_date": "2026-06-01", + "last_updated": "2026-06-01", + "type": "chat" + }, + { + "id": "minimax/minimax-m2.1", + "name": "MiniMax M2.1", + "display_name": "MiniMax M2.1", + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 204800, "output": 131072 }, "temperature": true, @@ -31700,29 +33795,19 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "effort_options": [ - "low", - "high", - "max" - ] + "interleaved": true } }, - "attachment": true, + "attachment": false, "open_weights": true, - "release_date": "2026-08-26", - "last_updated": "2026-08-26", + "release_date": "2025-12-23", + "last_updated": "2025-12-23", "type": "chat" }, { - "id": "deepseek-v4-flash", - "name": "DeepSeek V4 Flash", - "display_name": "DeepSeek V4 Flash", + "id": "minimax/minimax-m2.7", + "name": "Minimax M2.7", + "display_name": "Minimax M2.7", "modalities": { "input": [ "text" @@ -31732,8 +33817,8 @@ ] }, "limit": { - "context": 1000000, - "output": 384000 + "context": 204800, + "output": 131000 }, "temperature": true, "tool_call": true, @@ -31744,51 +33829,38 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "high", - "effort_options": [ - "low", - "high", - "max" - ], "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ "thinking_blocks" - ], - "notes": [ - "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." ] } }, "attachment": false, "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-07-31", - "last_updated": "2026-07-31", + "release_date": "2026-03-18", + "last_updated": "2026-03-18", "type": "chat" }, { - "id": "claude-sonnet-5-5", - "name": "Claude Sonnet 5.5", - "display_name": "Claude Sonnet 5.5", + "id": "minimax/minimax-m3.1-flash", + "name": "MiniMax-M3.1-Flash", + "display_name": "MiniMax-M3.1-Flash", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 1048576, + "output": 1048576 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -31807,58 +33879,54 @@ }, "attachment": true, "open_weights": false, - "knowledge": "2026-06", - "release_date": "2026-09-28", - "last_updated": "2026-09-28", + "release_date": "2026-10-19", + "last_updated": "2026-10-19", "type": "chat" }, { - "id": "ling-3.0-tiny-free", - "name": "Ling-3.0-tiny Free", - "display_name": "Ling-3.0-tiny Free", + "id": "bytedance/seedance-2.5", + "name": "Seedance 2.5", + "display_name": "Seedance 2.5", "modalities": { "input": [ "text" ], "output": [ - "text" + "video" ] }, "limit": { - "context": 262144, - "output": 32768 + "context": 8192, + "output": 8192 }, - "temperature": true, - "tool_call": true, + "temperature": false, + "tool_call": false, "reasoning": { - "supported": true, - "default": true + "supported": false }, "attachment": false, "open_weights": false, - "release_date": "2026-08-06", - "last_updated": "2026-08-06", - "type": "chat" + "release_date": "2026-07-31", + "last_updated": "2026-10-04" }, { - "id": "claude-haiku-5-5", - "name": "Claude Haiku 5.5", - "display_name": "Claude Haiku 5.5", + "id": "bytedance/seed-1.8", + "name": "Seed 1.8", + "display_name": "Seed 1.8", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 256000, + "output": 32768 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -31867,36 +33935,89 @@ "extra_capabilities": { "reasoning": { "effort_options": [ + "minimal", "low", "medium", - "high", - "xhigh", - "max" + "high" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-06", - "release_date": "2026-10-07", - "last_updated": "2026-10-07", + "release_date": "2025-12-28", + "last_updated": "2025-12-28", "type": "chat" }, { - "id": "minimax-m2.1-free", - "name": "MiniMax-M2.1 Free", - "display_name": "MiniMax-M2.1 Free", + "id": "bytedance/seedream-5.0-lite", + "name": "Seedream 5.0 Lite", + "display_name": "Seedream 5.0 Lite", "modalities": { "input": [ - "text" + "text", + "image" + ], + "output": [ + "image" + ] + }, + "limit": { + "context": 8192, + "output": 8192 + }, + "temperature": false, + "tool_call": false, + "reasoning": { + "supported": false + }, + "attachment": true, + "open_weights": false, + "release_date": "2026-02-13", + "last_updated": "2026-01-28" + }, + { + "id": "bytedance/seedance-v1.0-pro", + "name": "Seedance v1.0 Pro", + "display_name": "Seedance v1.0 Pro", + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "video" + ] + }, + "limit": { + "context": 8192, + "output": 8192 + }, + "temperature": false, + "tool_call": false, + "reasoning": { + "supported": false + }, + "attachment": true, + "open_weights": false, + "release_date": "2025-06-11", + "last_updated": "2025-06-11" + }, + { + "id": "bytedance/seed-1.6", + "name": "Seed 1.6", + "display_name": "Seed 1.6", + "modalities": { + "input": [ + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 204800, - "output": 131072 + "context": 256000, + "output": 32000 }, "temperature": true, "tool_call": true, @@ -31904,33 +34025,57 @@ "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "supported": true - } - }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-01", - "release_date": "2025-12-23", - "last_updated": "2025-12-23", + "attachment": true, + "open_weights": false, + "release_date": "2025-10-15", + "last_updated": "2025-10-15", "type": "chat" }, { - "id": "ring-2.6-1t-free", - "name": "Ring 2.6 1T Free", - "display_name": "Ring 2.6 1T Free", + "id": "bytedance/seedance-2.0-fast", + "name": "Seedance 2.0 Fast", + "display_name": "Seedance 2.0 Fast", "modalities": { "input": [ - "text" + "text", + "image", + "video", + "audio" + ], + "output": [ + "video" + ] + }, + "limit": { + "context": 8192, + "output": 8192 + }, + "temperature": false, + "tool_call": false, + "reasoning": { + "supported": false + }, + "attachment": true, + "open_weights": false, + "release_date": "2026-04-14", + "last_updated": "2026-04-14" + }, + { + "id": "bytedance/seed-2.1-turbo", + "name": "Seed 2.1 Turbo", + "display_name": "Seed 2.1 Turbo", + "modalities": { + "input": [ + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 262000, - "output": 66000 + "context": 262144, + "output": 262144 }, "temperature": true, "tool_call": true, @@ -31940,38 +34085,227 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "none", + "low", + "medium", + "high" ] } }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-06", - "release_date": "2026-05-08", - "last_updated": "2026-05-08", + "attachment": true, + "open_weights": false, + "release_date": "2026-06-23", + "last_updated": "2026-06-23", "type": "chat" }, { - "id": "kimi-k3", + "id": "bytedance/seedream-5.0-pro", + "name": "Seedream 5.0 Pro", + "display_name": "Seedream 5.0 Pro", + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "image" + ] + }, + "limit": { + "context": 8192, + "output": 8192 + }, + "temperature": false, + "tool_call": false, + "reasoning": { + "supported": false + }, + "attachment": true, + "open_weights": false, + "release_date": "2026-07-11", + "last_updated": "2026-07-11" + }, + { + "id": "bytedance/seedance-2.0", + "name": "Seedance 2.0", + "display_name": "Seedance 2.0", + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "video" + ] + }, + "limit": { + "context": 8192, + "output": 8192 + }, + "temperature": false, + "tool_call": false, + "reasoning": { + "supported": false + }, + "attachment": true, + "open_weights": false, + "release_date": "2026-02-12", + "last_updated": "2026-10-04" + }, + { + "id": "bytedance/seedance-v1.5-pro", + "name": "Seedance v1.5 Pro", + "display_name": "Seedance v1.5 Pro", + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "video" + ] + }, + "limit": { + "context": 8192, + "output": 8192 + }, + "temperature": false, + "tool_call": false, + "reasoning": { + "supported": false + }, + "attachment": true, + "open_weights": false, + "release_date": "2025-12-16", + "last_updated": "2025-12-16" + }, + { + "id": "bytedance/seedream-4.5", + "name": "Seedream 4.5", + "display_name": "Seedream 4.5", + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "image" + ] + }, + "limit": { + "context": 8192, + "output": 8192 + }, + "temperature": false, + "tool_call": false, + "reasoning": { + "supported": false + }, + "attachment": true, + "open_weights": false, + "release_date": "2025-12-03", + "last_updated": "2025-11-28" + }, + { + "id": "bytedance/seedance-2.0-mini", + "name": "Seedance 2.0 Mini", + "display_name": "Seedance 2.0 Mini", + "modalities": { + "input": [ + "text", + "image", + "video", + "audio" + ], + "output": [ + "video" + ] + }, + "limit": { + "context": 8192, + "output": 8192 + }, + "temperature": false, + "tool_call": false, + "reasoning": { + "supported": false + }, + "attachment": true, + "open_weights": false, + "release_date": "2026-06-22", + "last_updated": "2026-06-22" + }, + { + "id": "bytedance/seedance-v1.0-pro-fast", + "name": "Seedance v1.0 Pro Fast", + "display_name": "Seedance v1.0 Pro Fast", + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "video" + ] + }, + "limit": { + "context": 8192, + "output": 8192 + }, + "temperature": false, + "tool_call": false, + "reasoning": { + "supported": false + }, + "attachment": true, + "open_weights": false, + "release_date": "2025-10-24", + "last_updated": "2025-10-31" + }, + { + "id": "bytedance/seedream-4.0", + "name": "Seedream 4.0", + "display_name": "Seedream 4.0", + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "image" + ] + }, + "limit": { + "context": 8192, + "output": 8192 + }, + "temperature": false, + "tool_call": false, + "reasoning": { + "supported": false + }, + "attachment": true, + "open_weights": false, + "release_date": "2025-09-09", + "last_updated": "2025-08-28" + }, + { + "id": "moonshotai/kimi-k3", "name": "Kimi K3", "display_name": "Kimi K3", "modalities": { "input": [ "text", "image", - "video" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1048576, + "context": 1000000, "output": 131072 }, "temperature": false, @@ -31983,12 +34317,6 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], "effort_options": [ "low", "high", @@ -32003,22 +34331,22 @@ "type": "chat" }, { - "id": "step-5-preview-free", - "name": "Step 5 Preview Free", - "display_name": "Step 5 Preview Free", + "id": "moonshotai/kimi-k2.7-code", + "name": "Kimi K2.7 Code", + "display_name": "Kimi K2.7 Code", "modalities": { "input": [ "text", "image", - "video" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 65536 + "context": 256000, + "output": 32768 }, "temperature": true, "tool_call": true, @@ -32029,13 +34357,8 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], "effort_options": [ + "none", "low", "medium", "high" @@ -32044,25 +34367,28 @@ }, "attachment": true, "open_weights": true, - "release_date": "2026-09-16", - "last_updated": "2026-09-20", + "knowledge": "2025-01", + "release_date": "2026-06-12", + "last_updated": "2026-06-12", "type": "chat" }, { - "id": "deepseek-v4-pro", - "name": "DeepSeek V4 Pro", - "display_name": "DeepSeek V4 Pro", + "id": "moonshotai/kimi-k2.7-code-highspeed", + "name": "Kimi K2.7 Code High Speed", + "display_name": "Kimi K2.7 Code High Speed", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 384000 + "context": 262144, + "output": 32768 }, "temperature": true, "tool_call": true, @@ -32073,49 +34399,38 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "high", "effort_options": [ + "none", "low", - "high", - "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." + "medium", + "high" ] } }, - "attachment": false, + "attachment": true, "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-04-24", - "last_updated": "2026-04-24", + "knowledge": "2025-01", + "release_date": "2026-06-12", + "last_updated": "2026-06-12", "type": "chat" }, { - "id": "nemotron-3.5-lightning-free", - "name": "Nemotron 3.5 Lightning Free", - "display_name": "Nemotron 3.5 Lightning Free", + "id": "moonshotai/kimi-k3.1-pro-highspeed", + "name": "Kimi K3.1 Pro Highspeed", + "display_name": "Kimi K3.1 Pro Highspeed", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 1048576, + "output": 1048576 }, - "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -32123,26 +34438,23 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "low", + "high", + "max" ] } }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-09", - "release_date": "2026-08-11", - "last_updated": "2026-08-11", + "attachment": true, + "open_weights": false, + "release_date": "2026-10-23", + "last_updated": "2026-10-23", "type": "chat" }, { - "id": "claude-opus-5", - "name": "Claude Opus 5", - "display_name": "Claude Opus 5", + "id": "moonshotai/kimi-k3-fast", + "name": "Kimi K3 Fast", + "display_name": "Kimi K3 Fast", "modalities": { "input": [ "text", @@ -32155,7 +34467,7 @@ }, "limit": { "context": 1000000, - "output": 128000 + "output": 131072 }, "temperature": false, "tool_call": true, @@ -32165,43 +34477,39 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, "effort_options": [ + "none", "low", "medium", "high", - "xhigh", "max" ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2026-05", - "release_date": "2026-07-24", - "last_updated": "2026-07-24", + "open_weights": true, + "release_date": "2026-07-16", + "last_updated": "2026-07-16", "type": "chat" }, { - "id": "mimo-v2.6-flash-free", - "name": "MiMo-V2.6-Flash Free", - "display_name": "MiMo-V2.6-Flash Free", + "id": "moonshotai/kimi-k3.1-pro", + "name": "Kimi K3.1 Pro", + "display_name": "Kimi K3.1 Pro", "modalities": { "input": [ "text", - "image", - "audio", - "video" + "image" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 32000 + "context": 1048576, + "output": 1048576 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -32209,25 +34517,23 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "low", + "high", + "max" ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-09-22", - "last_updated": "2026-09-22", + "open_weights": false, + "release_date": "2026-10-23", + "last_updated": "2026-10-23", "type": "chat" }, { - "id": "glm-4.7", - "name": "GLM-4.7", - "display_name": "GLM-4.7", + "id": "moonshotai/kimi-k2", + "name": "Kimi K2 Instruct", + "display_name": "Kimi K2 Instruct", "modalities": { "input": [ "text" @@ -32237,37 +34543,25 @@ ] }, "limit": { - "context": 204800, + "context": 131072, "output": 131072 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } + "supported": false }, "attachment": false, "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-12-22", - "last_updated": "2025-12-22", + "knowledge": "2024-12-31", + "release_date": "2025-07-11", + "last_updated": "2025-07-11", "type": "chat" }, { - "id": "longcat-2.5-preview-free", - "name": "LongCat 2.5 Preview Free", - "display_name": "LongCat 2.5 Preview Free", + "id": "moonshotai/kimi-k2.5", + "name": "Kimi K2.5", + "display_name": "Kimi K2.5", "modalities": { "input": [ "text", @@ -32278,10 +34572,10 @@ ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 256000, + "output": 256000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -32295,19 +34589,26 @@ "visibility": "summary", "continuation": [ "thinking_blocks" + ], + "effort_options": [ + "none", + "low", + "medium", + "high" ] } }, "attachment": true, - "open_weights": false, - "release_date": "2026-09-25", - "last_updated": "2026-09-25", + "open_weights": true, + "knowledge": "2025-01", + "release_date": "2026-01-27", + "last_updated": "2026-01-27", "type": "chat" }, { - "id": "nemotron-3.5-super-vl-free", - "name": "Nemotron 3.5 Super VL Free", - "display_name": "Nemotron 3.5 Super VL Free", + "id": "moonshotai/kimi-k2.6", + "name": "Kimi K2.6", + "display_name": "Kimi K2.6", "modalities": { "input": [ "text", @@ -32318,8 +34619,8 @@ ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 262000, + "output": 262000 }, "temperature": true, "tool_call": true, @@ -32329,33 +34630,20 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "effort_options": [ - "none", - "minimal", - "low", - "medium", - "high", - "xhigh" - ] + "supported": true } }, "attachment": true, "open_weights": true, - "release_date": "2026-10-15", - "last_updated": "2026-10-15", + "knowledge": "2025-01", + "release_date": "2026-04-20", + "last_updated": "2026-04-20", "type": "chat" }, { - "id": "nemotron-3-ultra-free", - "name": "Nemotron 3 Ultra Free", - "display_name": "Nemotron 3 Ultra Free", + "id": "reflection/beam", + "name": "Beam 1.0", + "display_name": "Beam 1.0", "modalities": { "input": [ "text" @@ -32365,8 +34653,8 @@ ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 1048576, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -32376,38 +34664,37 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" ] } }, "attachment": false, "open_weights": true, - "knowledge": "2025-09", - "release_date": "2026-06-04", - "last_updated": "2026-06-04", + "knowledge": "2026-06-30", + "release_date": "2026-10-05", + "last_updated": "2026-10-16", "type": "chat" }, { - "id": "deepseek-v4-flash-vision-exp", - "name": "DeepSeek V4 Flash Vision Exp", - "display_name": "DeepSeek V4 Flash Vision Exp", + "id": "reflection/beam-free", + "name": "Beam 1.0 (Free)", + "display_name": "Beam 1.0 (Free)", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 384000 + "context": 1048576, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -32417,107 +34704,68 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "high", "effort_options": [ "low", + "medium", "high", + "xhigh", "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." ] } }, - "attachment": true, + "attachment": false, "open_weights": true, - "release_date": "2026-08-21", - "last_updated": "2026-09-01", + "knowledge": "2026-06-30", + "release_date": "2026-10-05", + "last_updated": "2026-10-16", "type": "chat" }, { - "id": "claude-opus-4-7", - "name": "Claude Opus 4.7", - "display_name": "Claude Opus 4.7", + "id": "stepfun/step-5-preview", + "name": "Step 5 Preview", + "display_name": "Step 5 Preview", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 1048576, + "output": 1048576 }, - "temperature": false, - "tool_call": true, + "temperature": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": false - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "high", - "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "omitted", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Claude Opus 4.7 and newer Opus models require thinking.type = \"adaptive\" to enable thinking explicitly.", - "Manual budget_tokens requests return 400 on Claude Opus 4.7 and newer adaptive-only Opus models.", - "task_budget is separate from thinking control and should not be treated as a thinking budget." - ] - } + "supported": false }, "attachment": true, - "open_weights": false, - "knowledge": "2026-01-31", - "release_date": "2026-04-16", - "last_updated": "2026-04-16", + "open_weights": true, + "release_date": "2026-09-16", + "last_updated": "2026-09-20", "type": "chat" }, { - "id": "space-bunny-free", - "name": "Space Bunny Free", - "display_name": "Space Bunny Free", + "id": "stepfun/step-3.7-flash", + "name": "Step 3.7 Flash", + "display_name": "Step 3.7 Flash", "modalities": { "input": [ "text", - "image", - "video" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 524288 + "context": 256000, + "output": 256000 }, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -32525,32 +34773,24 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], "effort_options": [ "low", "medium", - "high", - "xhigh", - "max" + "high" ] } }, "attachment": true, - "open_weights": false, - "release_date": "2026-09-23", - "last_updated": "2026-09-23", + "open_weights": true, + "knowledge": "2026-03-01", + "release_date": "2026-05-29", + "last_updated": "2026-05-29", "type": "chat" }, { - "id": "gpt-5.3-codex-spark", - "name": "GPT-5.3 Codex Spark", - "display_name": "GPT-5.3 Codex Spark", + "id": "tencent/hy-mt2-lite", + "name": "Tencent Hy-MT2-Lite", + "display_name": "Tencent Hy-MT2-Lite", "modalities": { "input": [ "text" @@ -32560,47 +34800,23 @@ ] }, "limit": { - "context": 128000, - "output": 128000 + "context": 8000, + "output": 4000 }, - "temperature": false, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", - "effort_options": [ - "low", - "medium", - "high", - "xhigh" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" - } + "supported": false }, "attachment": false, "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-02-05", - "last_updated": "2026-02-05", + "release_date": "2026-06-12", + "last_updated": "2026-06-12", "type": "chat" }, { - "id": "beam-free", - "name": "Beam Free", - "display_name": "Beam Free", + "id": "tencent/hy3", + "name": "Hy3", + "display_name": "Hy3", "modalities": { "input": [ "text" @@ -32610,8 +34826,8 @@ ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 262144, + "output": 262144 }, "temperature": true, "tool_call": true, @@ -32622,47 +34838,34 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], "effort_options": [ + "none", "low", - "medium", - "high", - "xhigh", - "max" + "high" ] } }, "attachment": false, "open_weights": true, - "knowledge": "2026-06-30", - "release_date": "2026-10-05", - "last_updated": "2026-10-16", + "release_date": "2026-07-06", + "last_updated": "2026-07-06", "type": "chat" }, { - "id": "muse-wonder-1.0", - "name": "Muse Wonder", - "display_name": "Muse Wonder", + "id": "tencent/hy4-preview", + "name": "Tencent Hy4 Preview", + "display_name": "Tencent Hy4 Preview", "modalities": { "input": [ - "text", - "image", - "video", - "pdf", - "audio" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 1024000, + "output": 64000 }, "temperature": true, "tool_call": true, @@ -32672,65 +34875,49 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, "effort_options": [ - "minimal", - "low", - "medium", - "high", - "xhigh", - "max" + "none", + "high" ] } }, - "attachment": true, - "open_weights": false, - "release_date": "2026-10-12", - "last_updated": "2026-10-12", + "attachment": false, + "open_weights": true, + "release_date": "2026-08-28", + "last_updated": "2026-08-28", "type": "chat" }, { - "id": "grok-4.5", - "name": "Grok 4.5", - "display_name": "Grok 4.5", + "id": "tencent/hy-mt2-plus", + "name": "Tencent Hy-MT2-Plus", + "display_name": "Tencent Hy-MT2-Plus", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 500000, - "output": 500000 + "context": 8000, + "output": 4000 }, - "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "effort_options": [ - "low", - "medium", - "high" - ] - } + "supported": false }, - "attachment": true, + "attachment": false, "open_weights": false, - "release_date": "2026-07-08", - "last_updated": "2026-07-08", + "release_date": "2026-06-12", + "last_updated": "2026-06-12", "type": "chat" }, { - "id": "flint-40b-free", - "name": "Flint 40B Free", - "display_name": "Flint 40B Free", + "id": "tencent/hy-mt2-pro", + "name": "Tencent Hy-MT2-Pro", + "display_name": "Tencent Hy-MT2-Pro", "modalities": { "input": [ "text" @@ -32740,131 +34927,80 @@ ] }, "limit": { - "context": 262144, - "output": 65536 + "context": 8000, + "output": 4000 }, - "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" - ] - } + "supported": false }, "attachment": false, - "open_weights": true, - "knowledge": "2026-06", - "release_date": "2026-10-21", - "last_updated": "2026-10-21", + "open_weights": false, + "release_date": "2026-05-21", + "last_updated": "2026-05-21", "type": "chat" }, { - "id": "kimi-k2.6", - "name": "Kimi K2.6", - "display_name": "Kimi K2.6", + "id": "scaledown/scaledown-extract-1", + "name": "Extract-1", + "display_name": "Extract-1", "modalities": { "input": [ - "text", - "image", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 65536 + "context": 1000000, + "output": 1000000 }, - "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } + "supported": false }, - "attachment": true, - "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-04-20", - "last_updated": "2026-04-20", + "attachment": false, + "open_weights": false, + "release_date": "2026-10-20", + "last_updated": "2026-10-20", "type": "chat" }, { - "id": "qwen3.5-plus", - "name": "Qwen3.5 Plus", - "display_name": "Qwen3.5 Plus", + "id": "scaledown/scaledown-summarize-1", + "name": "Summarize-1", + "display_name": "Summarize-1", "modalities": { "input": [ - "text", - "image", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 65536 + "context": 1000000, + "output": 1000 }, - "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } + "supported": false }, - "attachment": true, + "attachment": false, "open_weights": false, - "knowledge": "2025-04", - "release_date": "2026-02-16", - "last_updated": "2026-02-16", + "release_date": "2026-10-20", + "last_updated": "2026-10-20", "type": "chat" }, { - "id": "longcat-2.0-free", - "name": "LongCat-2.0 Free", - "display_name": "LongCat-2.0 Free", + "id": "interfaze/interfaze-beta", + "name": "Interfaze Beta", + "display_name": "Interfaze Beta", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" @@ -32872,50 +35008,45 @@ }, "limit": { "context": 1000000, - "output": 131072 + "output": 32000 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { "supported": true, "default": true }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "low", + "medium", + "high" ] } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-06-30", - "last_updated": "2026-06-30", + "attachment": true, + "open_weights": false, + "release_date": "2025-10-07", + "last_updated": "2026-04-29", "type": "chat" }, { - "id": "kimi-k2.5", - "name": "Kimi K2.5", - "display_name": "Kimi K2.5", + "id": "stealth/glyph-cluster", + "name": "Glyph Cluster", + "display_name": "Glyph Cluster", "modalities": { "input": [ - "text", - "image", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 65536 + "context": 256000, + "output": 256000 }, - "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -32923,41 +35054,38 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "low", + "medium", + "high", + "xhigh" ] } }, - "attachment": true, - "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-01-27", - "last_updated": "2026-01-27", + "attachment": false, + "open_weights": false, + "release_date": "2026-10-07", + "last_updated": "2026-10-07", "type": "chat" }, { - "id": "claude-opus-5-5", - "name": "Claude Opus 5.5", - "display_name": "Claude Opus 5.5", + "id": "fireworks/ember-1", + "name": "Ember-1", + "display_name": "Ember-1", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 1048576, + "output": 1048576 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -32968,36 +35096,33 @@ "effort_options": [ "low", "medium", - "high", - "xhigh", - "max" + "high" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-06", "release_date": "2026-09-22", "last_updated": "2026-09-22", "type": "chat" }, { - "id": "minimax-m3", - "name": "MiniMax-M3", - "display_name": "MiniMax-M3", + "id": "google/gemini-3.5-flash", + "name": "Gemini 3.5 Flash", + "display_name": "Gemini 3.5 Flash", "modalities": { "input": [ "text", "image", - "video" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 512000, - "output": 128000 + "context": 1000000, + "output": 64000 }, "temperature": true, "tool_call": true, @@ -33008,157 +35133,142 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, + "default_enabled": true, + "mode": "level", + "level": "high", + "level_options": [ + "minimal", + "low", + "medium", + "high" + ], "summaries": true, "visibility": "summary", "continuation": [ - "thinking_blocks" + "thought_signatures" ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-06-01", - "last_updated": "2026-06-01", + "open_weights": false, + "knowledge": "2025-01", + "release_date": "2026-05-19", + "last_updated": "2026-05-19", "type": "chat" }, { - "id": "claude-sonnet-5", - "name": "Claude Sonnet 5", - "display_name": "Claude Sonnet 5", + "id": "google/veo-3.0-generate-001", + "name": "Veo 3.0", + "display_name": "Veo 3.0", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ - "text" + "video" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 8192, + "output": 8192 }, "temperature": false, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" - ] - } + "supported": false }, "attachment": true, "open_weights": false, - "knowledge": "2026-01-31", - "release_date": "2026-06-30", - "last_updated": "2026-06-30", - "type": "chat" + "release_date": "2025-05-20", + "last_updated": "2026-06-08" }, { - "id": "ling-3.0-flash-fin-free", - "name": "Ling 3.0 Flash Fin Free", - "display_name": "Ling 3.0 Flash Fin Free", + "id": "google/gemini-3.8-live", + "name": "Gemini 3.8 Live", + "display_name": "Gemini 3.8 Live", "modalities": { "input": [ - "text" + "text", + "audio" ], "output": [ - "text" + "text", + "audio" ] }, "limit": { - "context": 262144, - "output": 32768 + "context": 131072, + "output": 65536 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true + "supported": false }, - "attachment": false, - "open_weights": true, - "release_date": "2026-08-27", - "last_updated": "2026-09-03", - "type": "chat" + "attachment": true, + "open_weights": false, + "release_date": "2026-09-15", + "last_updated": "2026-09-15" }, { - "id": "muse-spark-1.3", - "name": "Muse Spark 1.3", - "display_name": "Muse Spark 1.3", + "id": "google/gemini-nano-banana-2.1", + "name": "Gemini Nano Banana 2.1", + "display_name": "Gemini Nano Banana 2.1", "modalities": { "input": [ "text", - "image", - "video", - "pdf", - "audio" + "image" ], "output": [ - "text" + "text", + "image" ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 131072, + "output": 32768 }, - "temperature": true, - "tool_call": true, + "temperature": false, + "tool_call": false, "reasoning": { "supported": true, "default": true }, "extra_capabilities": { "reasoning": { - "supported": true, "effort_options": [ "minimal", - "low", "medium", - "high", - "xhigh" + "high" ] } }, "attachment": true, "open_weights": false, - "release_date": "2026-09-02", - "last_updated": "2026-09-02", - "type": "chat" + "release_date": "2026-10-06", + "last_updated": "2026-10-06" }, { - "id": "gemini-3-pro", - "name": "Gemini 3 Pro", - "display_name": "Gemini 3 Pro", + "id": "google/gemini-3.1-flash-image", + "name": "Nano Banana 2", + "display_name": "Nano Banana 2", "modalities": { "input": [ "text", - "image", - "video", - "audio", - "pdf" + "image" ], "output": [ - "text" + "text", + "image" ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 131072, + "output": 32768 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { "supported": true, "default": true @@ -33166,41 +35276,34 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "level", - "level": "high", - "level_options": [ + "effort_options": [ "low", "high" - ], - "summaries": true, - "visibility": "summary", - "continuation": [ - "thought_signatures" ] } }, "attachment": true, "open_weights": false, "knowledge": "2025-01", - "release_date": "2025-11-18", - "last_updated": "2025-11-18", - "type": "chat" + "release_date": "2026-05-28", + "last_updated": "2026-05-28" }, { - "id": "minimax-m2.5", - "name": "MiniMax-M2.5", - "display_name": "MiniMax-M2.5", + "id": "google/gemma-4-26b-a4b-it", + "name": "Gemma 4 26B A4B IT", + "display_name": "Gemma 4 26B A4B IT", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 204800, + "context": 262144, "output": 131072 }, "temperature": true, @@ -33211,38 +35314,32 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] + "supported": true } }, - "attachment": false, + "attachment": true, "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-02-12", - "last_updated": "2026-02-12", + "release_date": "2026-04-02", + "last_updated": "2026-04-02", "type": "chat" }, { - "id": "exo-free", - "name": "Exo Free", - "display_name": "Exo Free", + "id": "google/gemini-4-argon", + "name": "Gemini 4 Argon", + "display_name": "Gemini 4 Argon", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 1000000, + "output": 1000000 }, "temperature": false, "tool_call": true, @@ -33253,34 +35350,39 @@ "extra_capabilities": { "reasoning": { "effort_options": [ + "low", + "medium", "high" ] } }, "attachment": true, "open_weights": false, - "release_date": "2026-10-06", - "last_updated": "2026-10-06", + "knowledge": "2026-03", + "release_date": "2026-10-13", + "last_updated": "2026-10-13", "type": "chat" }, { - "id": "minimax-m2.7", - "name": "MiniMax-M2.7", - "display_name": "MiniMax-M2.7", + "id": "google/gemini-3-pro-image", + "name": "Nano Banana Pro", + "display_name": "Nano Banana Pro", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ - "text" + "text", + "image" ] }, "limit": { - "context": 204800, - "output": 131072 + "context": 65536, + "output": 32768 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { "supported": true, "default": true @@ -33288,77 +35390,86 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, + "default_enabled": true, + "mode": "level", + "level": "high", + "level_options": [ + "low", + "high" + ], "summaries": true, "visibility": "summary", "continuation": [ - "thinking_blocks" + "thought_signatures" ] } }, - "attachment": false, - "open_weights": true, + "attachment": true, + "open_weights": false, "knowledge": "2025-01", - "release_date": "2026-03-18", - "last_updated": "2026-03-18", - "type": "chat" + "release_date": "2026-05-28", + "last_updated": "2026-05-28" }, { - "id": "gemini-3.7-flash", - "name": "Gemini 3.7 Flash", - "display_name": "Gemini 3.7 Flash", + "id": "google/veo-3.1-generate-001", + "name": "Veo 3.1", + "display_name": "Veo 3.1", "modalities": { "input": [ "text", "image", - "video", - "audio", - "pdf" + "video" ], "output": [ - "text" + "video" ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 8192, + "output": 8192 }, - "temperature": true, - "tool_call": true, + "temperature": false, + "tool_call": false, "reasoning": { - "supported": true, - "default": true + "supported": false }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "level", - "level": "high", - "level_options": [ - "minimal", - "low", - "medium", - "high" - ], - "summaries": true, - "visibility": "summary", - "continuation": [ - "thought_signatures" - ] - } + "attachment": true, + "open_weights": false, + "release_date": "2025-10-15", + "last_updated": "2026-06-08" + }, + { + "id": "google/veo-3.1-lite-generate-001", + "name": "Veo 3.1 Lite Generate", + "display_name": "Veo 3.1 Lite Generate", + "modalities": { + "input": [ + "text", + "image", + "video" + ], + "output": [ + "video" + ] + }, + "limit": { + "context": 8192, + "output": 8192 + }, + "temperature": false, + "tool_call": false, + "reasoning": { + "supported": false }, "attachment": true, "open_weights": false, - "knowledge": "2026-03", - "release_date": "2026-08-13", - "last_updated": "2026-08-13", - "type": "chat" + "release_date": "2026-04-02", + "last_updated": "2026-04-02" }, { - "id": "gpt-6-sol", - "name": "GPT-6 Sol", - "display_name": "GPT-6 Sol", + "id": "google/gemini-3.6-flash", + "name": "Gemini 3.6 Flash", + "display_name": "Gemini 3.6 Flash", "modalities": { "input": [ "text", @@ -33370,10 +35481,10 @@ ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 1000000, + "output": 64000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -33381,27 +35492,34 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "none", + "supported": true, + "default_enabled": true, + "mode": "level", + "level": "high", + "level_options": [ + "minimal", "low", "medium", - "high", - "xhigh", - "max" + "high" + ], + "summaries": true, + "visibility": "summary", + "continuation": [ + "thought_signatures" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-04-20", - "release_date": "2026-09-22", - "last_updated": "2026-09-22", + "knowledge": "2026-03", + "release_date": "2026-07-21", + "last_updated": "2026-07-21", "type": "chat" }, { - "id": "gpt-5.6-sol", - "name": "GPT-5.6 Sol", - "display_name": "GPT-5.6 Sol", + "id": "google/gemini-3-flash", + "name": "Gemini 3 Flash", + "display_name": "Gemini 3 Flash", "modalities": { "input": [ "text", @@ -33413,10 +35531,10 @@ ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 1000000, + "output": 65000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -33426,36 +35544,32 @@ "reasoning": { "supported": true, "default_enabled": true, - "mode": "effort", - "effort": "medium", - "effort_options": [ - "none", - "low", - "medium", - "high", - "xhigh", - "max" - ], - "verbosity": "medium", - "verbosity_options": [ + "mode": "level", + "level": "high", + "level_options": [ + "minimal", "low", "medium", "high" ], - "visibility": "hidden" + "summaries": true, + "visibility": "summary", + "continuation": [ + "thought_signatures" + ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-02-16", - "release_date": "2026-07-09", - "last_updated": "2026-07-09", + "knowledge": "2025-03", + "release_date": "2025-12-17", + "last_updated": "2025-12-17", "type": "chat" }, { - "id": "claude-sonnet-4-6", - "name": "Claude Sonnet 4.6", - "display_name": "Claude Sonnet 4.6", + "id": "google/gemini-3.8-flash", + "name": "Gemini 3.8 Flash", + "display_name": "Gemini 3.8 Flash", "modalities": { "input": [ "text", @@ -33468,68 +35582,59 @@ }, "limit": { "context": 1000000, - "output": 64000 + "output": 65535 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "mixed", - "budget": { - "min": 1024, - "unit": "tokens" - }, - "effort": "high", - "effort_options": [ + "default_enabled": true, + "mode": "level", + "level": "high", + "level_options": [ + "minimal", "low", "medium", - "high", - "max" + "high" ], - "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Anthropic recommends adaptive thinking with effort for Claude 4.6; budget_tokens remains a deprecated compatibility path.", - "Anthropic API defaults effort to high; lower effort levels should be chosen per workload." + "thought_signatures" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-02-17", - "last_updated": "2026-03-13", + "release_date": "2026-09-02", + "last_updated": "2026-09-02", "type": "chat" }, { - "id": "qwen3.8-max", - "name": "Qwen3.8 Max", - "display_name": "Qwen3.8 Max", + "id": "google/gemini-3.1-flash-lite-image", + "name": "Gemini 3.1 Flash Lite Image (Nano Banana 2 Lite)", + "display_name": "Gemini 3.1 Flash Lite Image (Nano Banana 2 Lite)", "modalities": { "input": [ "text", "image" ], "output": [ - "text" + "text", + "image" ] }, "limit": { - "context": 262144, - "output": 131072 + "context": 65536, + "output": 4096 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { "supported": true, "default": true @@ -33539,86 +35644,61 @@ "supported": true, "effort_options": [ "low", - "medium", - "xhigh" + "high" ] } }, "attachment": true, "open_weights": false, - "release_date": "2026-08-03", - "last_updated": "2026-08-03", - "type": "chat" + "knowledge": "2025-01", + "release_date": "2026-06-30", + "last_updated": "2026-06-30" }, { - "id": "gpt-5.6-terra", - "name": "GPT-5.6 Terra", - "display_name": "GPT-5.6 Terra", + "id": "google/gemini-3.8-live-extended-thinking", + "name": "Gemini 3.8 Live Extended Thinking", + "display_name": "Gemini 3.8 Live Extended Thinking", "modalities": { "input": [ "text", - "image", - "pdf" + "audio" ], "output": [ - "text" + "text", + "audio" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 131072, + "output": 65536 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", - "effort_options": [ - "none", - "low", - "medium", - "high", - "xhigh", - "max" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" - } + "supported": false }, "attachment": true, "open_weights": false, - "knowledge": "2026-02-16", - "release_date": "2026-07-09", - "last_updated": "2026-07-09", - "type": "chat" + "release_date": "2026-09-15", + "last_updated": "2026-09-15" }, { - "id": "deepseek-v4-flash-free", - "name": "DeepSeek V4 Flash Free", - "display_name": "DeepSeek V4 Flash Free", + "id": "google/gemini-2.5-pro", + "name": "Gemini 2.5 Pro", + "display_name": "Gemini 2.5 Pro", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 128000 + "context": 1048576, + "output": 65535 }, "temperature": true, "tool_call": true, @@ -33629,115 +35709,139 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, + "default_enabled": true, + "mode": "budget", + "budget": { + "default": -1, + "min": 128, + "max": 32768, + "auto": -1, + "unit": "tokens" + }, "summaries": true, "visibility": "summary", "continuation": [ - "thinking_blocks" - ], - "effort_options": [ - "low", - "high", - "max" + "thought_signatures" ] } }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-07-31", - "last_updated": "2026-07-31", + "attachment": true, + "open_weights": false, + "knowledge": "2025-01", + "release_date": "2025-06-17", + "last_updated": "2025-06-17", "type": "chat" }, { - "id": "glm-4.6", - "name": "GLM-4.6", - "display_name": "GLM-4.6", + "id": "google/gemini-3.5-transcribe-live", + "name": "Gemini 3.5 Transcribe Live", + "display_name": "Gemini 3.5 Transcribe Live", "modalities": { "input": [ - "text" + "audio" ], "output": [ "text" ] }, "limit": { - "context": 204800, - "output": 131072 + "context": 8192, + "output": 8192 }, - "temperature": true, - "tool_call": true, + "temperature": false, + "tool_call": false, "reasoning": { - "supported": true, - "default": true + "supported": false }, - "extra_capabilities": { - "reasoning": { - "supported": true - } + "attachment": true, + "open_weights": false, + "release_date": "2026-08-26", + "last_updated": "2026-08-26" + }, + { + "id": "google/text-multilingual-embedding-002", + "name": "Text Multilingual Embedding 002", + "display_name": "Text Multilingual Embedding 002", + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 8192, + "output": 8192 + }, + "temperature": false, + "tool_call": false, + "reasoning": { + "supported": false }, "attachment": false, - "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-09-30", - "last_updated": "2025-09-30", - "type": "chat" + "open_weights": false, + "release_date": "2024-03-01", + "last_updated": "2024-03", + "type": "embedding" }, { - "id": "gpt-5.1-codex", - "name": "GPT-5.1 Codex", - "display_name": "GPT-5.1 Codex", + "id": "google/veo-3.0-fast-generate-001", + "name": "Veo 3.0 Fast Generate", + "display_name": "Veo 3.0 Fast Generate", "modalities": { "input": [ "text", "image" ], "output": [ - "text" + "video" ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 8192, + "output": 8192 }, "temperature": false, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": false - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "none", - "effort_options": [ - "none", - "low", - "medium", - "high" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" - } + "supported": false }, "attachment": true, "open_weights": false, - "knowledge": "2024-09-30", - "release_date": "2025-11-13", - "last_updated": "2025-11-13", - "type": "chat" + "release_date": "2025-07-31", + "last_updated": "2026-06-08" }, { - "id": "gpt-6-luna", - "name": "GPT-6 Luna", - "display_name": "GPT-6 Luna", + "id": "google/gemini-3.8-flash-lite-tts", + "name": "Gemini 3.8 Flash-Lite TTS", + "display_name": "Gemini 3.8 Flash-Lite TTS", + "modalities": { + "input": [ + "text" + ], + "output": [ + "audio" + ] + }, + "limit": { + "context": 8192, + "output": 16384 + }, + "temperature": false, + "tool_call": false, + "reasoning": { + "supported": false + }, + "attachment": false, + "open_weights": false, + "release_date": "2026-09-22", + "last_updated": "2026-09-22" + }, + { + "id": "google/gemini-2.5-flash-lite", + "name": "Gemini 2.5 Flash Lite", + "display_name": "Gemini 2.5 Flash Lite", "modalities": { "input": [ "text", @@ -33749,42 +35853,50 @@ ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 1048576, + "output": 65535 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "none", - "low", - "medium", - "high", - "xhigh", - "max" + "supported": true, + "default_enabled": false, + "mode": "budget", + "budget": { + "default": -1, + "min": 512, + "max": 24576, + "auto": -1, + "unit": "tokens" + }, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thought_signatures" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-05-18", - "release_date": "2026-09-22", - "last_updated": "2026-09-22", + "knowledge": "2025-01", + "release_date": "2025-06-17", + "last_updated": "2025-06-17", "type": "chat" }, { - "id": "deepseek-v4.1-flash", - "name": "DeepSeek V4.1 Flash", - "display_name": "DeepSeek V4.1 Flash", + "id": "google/gemini-2.5-flash", + "name": "Gemini 2.5 Flash", + "display_name": "Gemini 2.5 Flash", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" @@ -33792,7 +35904,7 @@ }, "limit": { "context": 1000000, - "output": 384000 + "output": 65535 }, "temperature": true, "tool_call": true, @@ -33803,36 +35915,38 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, + "default_enabled": true, + "mode": "budget", + "budget": { + "default": -1, + "min": 0, + "max": 24576, + "auto": -1, + "off": 0, + "unit": "tokens" + }, "summaries": true, "visibility": "summary", "continuation": [ - "thinking_blocks" - ], - "effort_options": [ - "low", - "high", - "max" + "thought_signatures" ] } }, "attachment": true, - "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-09-10", - "last_updated": "2026-09-10", + "open_weights": false, + "knowledge": "2025-01", + "release_date": "2025-06-17", + "last_updated": "2025-06-17", "type": "chat" }, { - "id": "gemini-3.1-pro", + "id": "google/gemini-3.1-pro-preview", "name": "Gemini 3.1 Pro Preview", "display_name": "Gemini 3.1 Pro Preview", "modalities": { "input": [ "text", "image", - "video", - "audio", "pdf" ], "output": [ @@ -33840,8 +35954,8 @@ ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 1000000, + "output": 64000 }, "temperature": true, "tool_call": true, @@ -33874,106 +35988,80 @@ "type": "chat" }, { - "id": "minimax-m3.1-flash-free", - "name": "MiniMax M3.1 Flash Free", - "display_name": "MiniMax M3.1 Flash Free", + "id": "google/gemini-embedding-2", + "name": "Gemini Embedding 2", + "display_name": "Gemini Embedding 2", "modalities": { "input": [ - "text", - "image", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 512000 + "context": 8192, + "output": 3072 }, - "temperature": true, - "tool_call": true, + "temperature": false, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" - ] - } + "supported": false }, - "attachment": true, + "attachment": false, "open_weights": false, - "release_date": "2026-10-19", - "last_updated": "2026-10-19", - "type": "chat" + "knowledge": "2025-11", + "release_date": "2026-04-22", + "last_updated": "2026-04-22", + "type": "embedding" }, { - "id": "grok-build-0.1", - "name": "Grok Build 0.1", - "display_name": "Grok Build 0.1", + "id": "google/gemini-embedding-001", + "name": "Gemini Embedding 001", + "display_name": "Gemini Embedding 001", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 256000, - "output": 256000 + "context": 2048, + "output": 1 }, - "temperature": true, - "tool_call": true, + "temperature": false, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true - } + "supported": false }, - "attachment": true, + "attachment": false, "open_weights": false, - "release_date": "2026-04-16", - "last_updated": "2026-04-16", - "type": "chat" + "knowledge": "2025-05", + "release_date": "2025-05-20", + "last_updated": "2025-05-20", + "type": "embedding" }, { - "id": "gpt-5-codex", - "name": "GPT-5 Codex", - "display_name": "GPT-5 Codex", + "id": "google/gemini-3.7-flash", + "name": "Gemini 3.7 Flash", + "display_name": "Gemini 3.7 Flash", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 1000000, + "output": 65535 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -33983,39 +36071,36 @@ "reasoning": { "supported": true, "default_enabled": true, - "mode": "effort", - "effort": "medium", - "effort_options": [ + "mode": "level", + "level": "high", + "level_options": [ "minimal", "low", "medium", "high" ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + "summaries": true, + "visibility": "summary", + "continuation": [ + "thought_signatures" + ] } }, "attachment": true, "open_weights": false, - "knowledge": "2024-09-30", - "release_date": "2025-09-15", - "last_updated": "2025-09-15", + "knowledge": "2026-03", + "release_date": "2026-08-13", + "last_updated": "2026-08-13", "type": "chat" }, { - "id": "mimo-v2-omni-free", - "name": "MiMo V2 Omni Free", - "display_name": "MiMo V2 Omni Free", + "id": "google/gemma-4-31b-it", + "name": "Gemma 4 31B IT", + "display_name": "Gemma 4 31B IT", "modalities": { "input": [ "text", "image", - "audio", "pdf" ], "output": [ @@ -34024,36 +36109,29 @@ }, "limit": { "context": 262144, - "output": 64000 + "output": 131072 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true + "supported": true }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] + "supported": true } }, "attachment": true, - "open_weights": false, - "knowledge": "2024-12", - "release_date": "2026-03-18", - "last_updated": "2026-03-18", + "open_weights": true, + "knowledge": "2025-01", + "release_date": "2026-04-02", + "last_updated": "2026-04-02", "type": "chat" }, { - "id": "grok-4.7", - "name": "Grok 4.7", - "display_name": "Grok 4.7", + "id": "google/gemini-omni-flash-preview", + "name": "Gemini Omni Flash Preview", + "display_name": "Gemini Omni Flash Preview", "modalities": { "input": [ "text", @@ -34061,15 +36139,15 @@ "pdf" ], "output": [ - "text" + "video" ] }, "limit": { - "context": 500000, - "output": 500000 + "context": 1000000, + "output": 57920 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { "supported": true, "default": true @@ -34077,65 +36155,61 @@ "extra_capabilities": { "reasoning": { "effort_options": [ + "none", "low", "medium", - "high", - "xhigh" + "high" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-05", - "release_date": "2026-09-21", - "last_updated": "2026-09-21", - "type": "chat" + "release_date": "2026-06-30", + "last_updated": "2026-06-30" }, { - "id": "kimi-k2", - "name": "Kimi K2", - "display_name": "Kimi K2", + "id": "google/gemini-3.5-transcribe", + "name": "Gemini 3.5 Transcribe", + "display_name": "Gemini 3.5 Transcribe", "modalities": { "input": [ - "text" + "audio" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 98304, + "output": 32768 }, - "temperature": true, - "tool_call": true, + "temperature": false, + "tool_call": false, "reasoning": { "supported": false }, - "attachment": false, - "open_weights": true, - "knowledge": "2024-10", - "release_date": "2025-09-05", - "last_updated": "2025-09-05", - "type": "chat" + "attachment": true, + "open_weights": false, + "release_date": "2026-08-26", + "last_updated": "2026-08-26" }, { - "id": "qwen3.6-plus", - "name": "Qwen3.6 Plus", - "display_name": "Qwen3.6 Plus", + "id": "google/gemini-3.1-flash-lite", + "name": "Gemini 3.1 Flash Lite", + "display_name": "Gemini 3.1 Flash Lite", "modalities": { "input": [ "text", "image", - "video" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 65536 + "context": 1000000, + "output": 65000 }, "temperature": true, "tool_call": true, @@ -34146,41 +36220,96 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "low", + "high" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-04", - "release_date": "2026-04-02", - "last_updated": "2026-04-02", + "knowledge": "2025-01", + "release_date": "2026-05-07", + "last_updated": "2026-05-07", "type": "chat" }, { - "id": "gpt-5.4-pro", - "name": "GPT-5.4 Pro", - "display_name": "GPT-5.4 Pro", + "id": "google/veo-3.1-fast-generate-001", + "name": "Veo 3.1 Fast Generate", + "display_name": "Veo 3.1 Fast Generate", "modalities": { "input": [ "text", "image", - "pdf" + "video" ], "output": [ - "text" + "video" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 8192, + "output": 8192 }, "temperature": false, - "tool_call": true, + "tool_call": false, + "reasoning": { + "supported": false + }, + "attachment": true, + "open_weights": false, + "release_date": "2025-10-15", + "last_updated": "2026-06-08" + }, + { + "id": "google/gemini-2.5-flash-image", + "name": "Nano Banana (Gemini 2.5 Flash Image)", + "display_name": "Nano Banana (Gemini 2.5 Flash Image)", + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text", + "image" + ] + }, + "limit": { + "context": 32768, + "output": 65535 + }, + "temperature": true, + "tool_call": false, + "reasoning": { + "supported": false + }, + "attachment": true, + "open_weights": false, + "knowledge": "2024-06", + "release_date": "2025-08-26", + "last_updated": "2025-08-26" + }, + { + "id": "google/gemini-3.1-flash-image-preview", + "name": "Gemini 3.1 Flash Image Preview (Nano Banana 2)", + "display_name": "Gemini 3.1 Flash Image Preview (Nano Banana 2)", + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text", + "image" + ] + }, + "limit": { + "context": 131072, + "output": 32768 + }, + "temperature": true, + "tool_call": false, "reasoning": { "supported": true, "default": true @@ -34188,34 +36317,22 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "high", "effort_options": [ - "medium", - "high", - "xhigh" - ], - "verbosity": "medium", - "verbosity_options": [ "low", - "medium", "high" - ], - "visibility": "hidden" + ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-03-05", - "last_updated": "2026-03-05", - "type": "chat" + "knowledge": "2025-01", + "release_date": "2026-02-26", + "last_updated": "2026-02-26" }, { - "id": "kimi-k2-thinking", - "name": "Kimi K2 Thinking", - "display_name": "Kimi K2 Thinking", + "id": "google/text-embedding-005", + "name": "Text Embedding 005", + "display_name": "Text Embedding 005", "modalities": { "input": [ "text" @@ -34225,48 +36342,37 @@ ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 8192, + "output": 8192 }, - "temperature": true, - "tool_call": true, + "temperature": false, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } + "supported": false }, "attachment": false, - "open_weights": true, - "knowledge": "2024-08", - "release_date": "2025-11-06", - "last_updated": "2025-11-06", - "type": "chat" + "open_weights": false, + "release_date": "2024-08-01", + "last_updated": "2024-08", + "type": "embedding" }, { - "id": "laguna-s-2.1-free", - "name": "Laguna S 2.1 Free", - "display_name": "Laguna S 2.1 Free", + "id": "google/gemini-3.5-flash-lite", + "name": "Gemini 3.5 Flash Lite", + "display_name": "Gemini 3.5 Flash Lite", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 256000, - "output": 32000 + "context": 1000000, + "output": 65000 }, "temperature": true, "tool_call": true, @@ -34279,75 +36385,78 @@ "supported": true, "effort_options": [ "low", - "medium", "high" ] } }, - "attachment": false, - "open_weights": true, + "attachment": true, + "open_weights": false, + "knowledge": "2026-03", "release_date": "2026-07-21", "last_updated": "2026-07-21", "type": "chat" }, { - "id": "gpt-5.1-codex-mini", - "name": "GPT-5.1 Codex Mini", - "display_name": "GPT-5.1 Codex Mini", + "id": "google/gemini-3.8-flash-tts", + "name": "Gemini 3.8 Flash TTS", + "display_name": "Gemini 3.8 Flash TTS", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ - "text" + "audio" ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 8192, + "output": 16384 }, "temperature": false, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": false + "supported": false }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "none", - "effort_options": [ - "none", - "low", - "medium", - "high" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" - } + "attachment": false, + "open_weights": false, + "release_date": "2026-09-22", + "last_updated": "2026-09-22" + }, + { + "id": "mistral/voxtral-small-transcribe-3", + "name": "Voxtral Small Transcribe 3", + "display_name": "Voxtral Small Transcribe 3", + "modalities": { + "input": [ + "audio" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 8192, + "output": 8192 + }, + "temperature": false, + "tool_call": false, + "reasoning": { + "supported": false }, "attachment": true, "open_weights": false, - "knowledge": "2024-09-30", - "release_date": "2025-11-13", - "last_updated": "2025-11-13", - "type": "chat" + "release_date": "2026-10-20", + "last_updated": "2026-10-20" }, { - "id": "ling-3.0-flash-free", - "name": "Ling-3.0-flash Free", - "display_name": "Ling-3.0-flash Free", + "id": "mistral/ministral-14b", + "name": "Ministral 14B", + "display_name": "Ministral 14B", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" @@ -34355,79 +36464,52 @@ }, "limit": { "context": 262144, - "output": 32768 + "output": 256000 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "low", - "medium", - "high" - ] - } + "supported": false }, - "attachment": false, + "attachment": true, "open_weights": true, - "release_date": "2026-07-23", - "last_updated": "2026-07-23", + "release_date": "2025-12-02", + "last_updated": "2025-12-02", "type": "chat" }, { - "id": "glm-5.3-flash-free", - "name": "GLM-5.3-Flash Free", - "display_name": "GLM-5.3-Flash Free", + "id": "mistral/ministral-8b", + "name": "Ministral 8B (latest)", + "display_name": "Ministral 8B (latest)", "modalities": { "input": [ "text", - "image", - "video" + "image" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 32000 + "context": 262144, + "output": 4000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "effort_options": [ - "low", - "high", - "max" - ] - } + "supported": false }, "attachment": true, "open_weights": true, - "release_date": "2026-08-26", - "last_updated": "2026-08-26", + "knowledge": "2024-10", + "release_date": "2024-10-16", + "last_updated": "2024-10-04", "type": "chat" }, { - "id": "gpt-5.1", - "name": "GPT-5.1", - "display_name": "GPT-5.1", + "id": "mistral/mistral-medium-3.5", + "name": "Mistral Medium Latest", + "display_name": "Mistral Medium Latest", "modalities": { "input": [ "text", @@ -34438,151 +36520,94 @@ ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 262144, + "output": 256000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "none", "effort_options": [ "none", - "low", - "medium", - "high" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", "high" - ], - "visibility": "hidden" + ] } }, "attachment": true, "open_weights": false, - "knowledge": "2024-09-30", - "release_date": "2025-11-13", - "last_updated": "2025-11-13", + "release_date": "2026-04-30", + "last_updated": "2026-04-30", "type": "chat" }, { - "id": "claude-opus-4-6", - "name": "Claude Opus 4.6", - "display_name": "Claude Opus 4.6", + "id": "mistral/mistral-nemo", + "name": "Mistral Nemo", + "display_name": "Mistral Nemo", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 131072, + "output": 131072 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": false - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "mixed", - "budget": { - "min": 1024, - "unit": "tokens" - }, - "effort": "high", - "effort_options": [ - "low", - "medium", - "high", - "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Anthropic recommends adaptive thinking with effort for Claude 4.6; budget_tokens remains a deprecated compatibility path.", - "Anthropic API defaults effort to high; lower effort levels should be chosen per workload." - ] - } + "supported": false }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-05-31", - "release_date": "2026-02-05", - "last_updated": "2026-03-13", + "attachment": false, + "open_weights": true, + "knowledge": "2024-07", + "release_date": "2024-07-18", + "last_updated": "2024-07-18", "type": "chat" }, { - "id": "kimi-k2.7-code", - "name": "Kimi K2.7 Code", - "display_name": "Kimi K2.7 Code", + "id": "mistral/ministral-3b", + "name": "Ministral 3B (latest)", + "display_name": "Ministral 3B (latest)", "modalities": { "input": [ "text", - "image", - "video" + "image" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 131072, + "output": 4000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } + "supported": false }, "attachment": true, "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-06-12", - "last_updated": "2026-06-12", + "knowledge": "2024-10", + "release_date": "2024-10-16", + "last_updated": "2024-10-04", "type": "chat" }, { - "id": "mimo-v2-flash-free", - "name": "MiMo V2 Flash Free", - "display_name": "MiMo V2 Flash Free", + "id": "mistral/mistral-large-3", + "name": "Mistral Large 3", + "display_name": "Mistral Large 3", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" @@ -34590,142 +36615,89 @@ }, "limit": { "context": 262144, - "output": 65536 + "output": 256000 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } + "supported": false }, - "attachment": false, + "attachment": true, "open_weights": true, - "knowledge": "2024-12-01", - "release_date": "2025-12-16", - "last_updated": "2026-02-04", + "knowledge": "2024-11", + "release_date": "2025-12-02", + "last_updated": "2025-12-02", "type": "chat" }, { - "id": "claude-opus-4-1", - "name": "Claude Opus 4.1", - "display_name": "Claude Opus 4.1", + "id": "mistral/mistral-large-4", + "name": "Mistral Large 4", + "display_name": "Mistral Large 4", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 32000 + "context": 1048576, + "output": 262144 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "budget", - "budget": { - "min": 1024, - "unit": "tokens" - }, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", - "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." + "effort_options": [ + "none", + "high" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-03-31", - "release_date": "2025-08-05", - "last_updated": "2025-08-05", + "release_date": "2026-10-06", + "last_updated": "2026-10-06", "type": "chat" }, { - "id": "gpt-5.6-luna", - "name": "GPT-5.6 Luna", - "display_name": "GPT-5.6 Luna", - "modalities": { + "id": "mistral/codestral", + "name": "Codestral (latest)", + "display_name": "Codestral (latest)", + "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 128000, + "output": 4000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", - "effort_options": [ - "none", - "low", - "medium", - "high", - "xhigh", - "max" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" - } + "supported": false }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-02-16", - "release_date": "2026-07-09", - "last_updated": "2026-07-09", + "attachment": false, + "open_weights": true, + "knowledge": "2024-10", + "release_date": "2024-05-29", + "last_updated": "2025-01-04", "type": "chat" }, { - "id": "qwen3-coder", - "name": "Qwen3 Coder", - "display_name": "Qwen3 Coder", + "id": "mistral/codestral-embed", + "name": "Codestral Embed", + "display_name": "Codestral Embed", "modalities": { "input": [ "text" @@ -34735,173 +36707,143 @@ ] }, "limit": { - "context": 262144, - "output": 65536 + "context": 8192, + "output": 3072 }, - "temperature": true, - "tool_call": true, + "temperature": false, + "tool_call": false, "reasoning": { "supported": false }, "attachment": false, - "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-07-22", - "last_updated": "2025-07-22", - "type": "chat" + "open_weights": false, + "release_date": "2025-05-28", + "last_updated": "2025-05-28", + "type": "embedding" }, { - "id": "gpt-5.4-mini", - "name": "GPT-5.4 Mini", - "display_name": "GPT-5.4 Mini", + "id": "mistral/mistral-small", + "name": "Mistral Small (latest)", + "display_name": "Mistral Small (latest)", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 262144, + "output": 4000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": false - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "none", - "effort_options": [ - "none", - "low", - "medium", - "high", - "xhigh" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" - } + "supported": false }, "attachment": true, - "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-03-17", - "last_updated": "2026-03-17", + "open_weights": true, + "knowledge": "2025-06", + "release_date": "2026-03-16", + "last_updated": "2026-03-16", "type": "chat" }, { - "id": "mistral-large-4", - "name": "Mistral Large 4", - "display_name": "Mistral Large 4", + "id": "mistral/mistral-embed", + "name": "Mistral Embed", + "display_name": "Mistral Embed", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 524288, - "output": 262144 + "context": 8000, + "output": 3072 }, - "temperature": true, - "tool_call": true, + "temperature": false, + "tool_call": false, "reasoning": { - "supported": true, - "default": true + "supported": false }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "none", - "high" - ] - } + "attachment": false, + "open_weights": false, + "release_date": "2023-12-11", + "last_updated": "2023-12-11", + "type": "embedding" + }, + { + "id": "mistral/voxtral-mini-transcribe-3", + "name": "Voxtral Mini Transcribe 3", + "display_name": "Voxtral Mini Transcribe 3", + "modalities": { + "input": [ + "audio" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 8192, + "output": 8192 + }, + "temperature": false, + "tool_call": false, + "reasoning": { + "supported": false }, "attachment": true, "open_weights": false, - "release_date": "2026-10-06", - "last_updated": "2026-10-06", - "type": "chat" + "release_date": "2026-10-20", + "last_updated": "2026-10-20" }, { - "id": "north-mini-code-free", - "name": "North Mini Code Free", - "display_name": "North Mini Code Free", + "id": "mistral/voxtral-mini-tts-3", + "name": "Voxtral TTS 3", + "display_name": "Voxtral TTS 3", "modalities": { "input": [ "text" ], "output": [ - "text" + "audio" ] }, "limit": { - "context": 256000, - "output": 64000 + "context": 8192, + "output": 8192 }, - "temperature": true, - "tool_call": true, + "temperature": false, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "effort_options": [ - "none", - "high" - ] - } + "supported": false }, "attachment": false, - "open_weights": true, - "knowledge": "2025-09-23", - "release_date": "2026-06-09", - "last_updated": "2026-06-09", - "type": "chat" + "open_weights": false, + "release_date": "2026-10-20", + "last_updated": "2026-10-20" }, { - "id": "muse-spark-1.3-contributor-free", - "name": "Muse Spark 1.3 Free", - "display_name": "Muse Spark 1.3 Free", + "id": "alibaba/qwen3.8-omni-flash", + "name": "Qwen 3.8 Omni Flash", + "display_name": "Qwen 3.8 Omni Flash", "modalities": { "input": [ "text", - "image", - "video", - "pdf", - "audio" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1048576, + "context": 1000000, "output": 131072 }, "temperature": true, @@ -34913,78 +36855,90 @@ "extra_capabilities": { "reasoning": { "effort_options": [ - "minimal", + "none", "low", "medium", - "high", "xhigh" ] } }, "attachment": true, "open_weights": false, - "release_date": "2026-09-02", - "last_updated": "2026-09-02", + "release_date": "2026-09-17", + "last_updated": "2026-09-17", "type": "chat" }, { - "id": "nemotron-3-super-free", - "name": "Nemotron 3 Super Free", - "display_name": "Nemotron 3 Super Free", + "id": "alibaba/wan-v2.6-i2v-flash", + "name": "Wan v2.6 Image-to-Video Flash", + "display_name": "Wan v2.6 Image-to-Video Flash", "modalities": { "input": [ - "text" + "text", + "image", + "audio" ], "output": [ - "text" + "video" ] }, "limit": { - "context": 204800, - "output": 128000 + "context": 8192, + "output": 8192 }, - "temperature": true, - "tool_call": true, + "temperature": false, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } + "supported": false }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-06", - "release_date": "2026-03-11", - "last_updated": "2026-03-11", - "type": "chat" + "attachment": true, + "open_weights": false, + "release_date": "2025-12-16", + "last_updated": "2025-12-16" }, { - "id": "mimo-v2.5-free", - "name": "MiMo V2.5 Free", - "display_name": "MiMo V2.5 Free", + "id": "alibaba/wan-v2.7-t2v", + "name": "Wan v2.7 Text-to-Video", + "display_name": "Wan v2.7 Text-to-Video", "modalities": { "input": [ "text", - "image", - "audio", + "audio" + ], + "output": [ "video" + ] + }, + "limit": { + "context": 8192, + "output": 8192 + }, + "temperature": false, + "tool_call": false, + "reasoning": { + "supported": false + }, + "attachment": true, + "open_weights": false, + "release_date": "2026-04-07", + "last_updated": "2026-04-07" + }, + { + "id": "alibaba/qwen3.8-2.4t-a95b", + "name": "Qwen3.8 2.4T A95B", + "display_name": "Qwen3.8 2.4T A95B", + "modalities": { + "input": [ + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 32000 + "context": 262144, + "output": 128000 }, "temperature": true, "tool_call": true, @@ -34995,25 +36949,23 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "low", + "medium", + "xhigh" ] } }, "attachment": true, "open_weights": true, - "knowledge": "2024-12", - "release_date": "2026-04-22", - "last_updated": "2026-04-22", + "release_date": "2026-08-12", + "last_updated": "2026-08-12", "type": "chat" }, { - "id": "mimo-v2-pro-free", - "name": "MiMo V2 Pro Free", - "display_name": "MiMo V2 Pro Free", + "id": "alibaba/qwen3-next-80b-a3b-instruct", + "name": "Qwen3 Next 80B A3B Instruct", + "display_name": "Qwen3 Next 80B A3B Instruct", "modalities": { "input": [ "text" @@ -35023,37 +36975,25 @@ ] }, "limit": { - "context": 1048576, - "output": 64000 + "context": 262114, + "output": 262114 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } + "supported": false }, "attachment": false, - "open_weights": false, - "knowledge": "2024-12", - "release_date": "2026-03-18", - "last_updated": "2026-03-18", + "open_weights": true, + "knowledge": "2025-04", + "release_date": "2025-09", + "last_updated": "2025-09", "type": "chat" }, { - "id": "ll-alpha-free", - "name": "LL Alpha Free", - "display_name": "LL Alpha Free", + "id": "alibaba/qwen3-embedding-0.6b", + "name": "Qwen3 Embedding 0.6B", + "display_name": "Qwen3 Embedding 0.6B", "modalities": { "input": [ "text" @@ -35063,8 +37003,35 @@ ] }, "limit": { - "context": 262144, - "output": 65536 + "context": 32768, + "output": 8192 + }, + "temperature": false, + "tool_call": false, + "reasoning": { + "supported": false + }, + "attachment": false, + "open_weights": true, + "release_date": "2025-06-05", + "last_updated": "2025-06-05", + "type": "embedding" + }, + { + "id": "alibaba/qwen3.7-max", + "name": "Qwen 3.7 Max", + "display_name": "Qwen 3.7 Max", + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 991000, + "output": 64000 }, "temperature": true, "tool_call": true, @@ -35075,13 +37042,9 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], "effort_options": [ + "none", + "minimal", "low", "medium", "high", @@ -35091,16 +37054,15 @@ } }, "attachment": false, - "open_weights": true, - "knowledge": "2026-06", - "release_date": "2026-10-15", - "last_updated": "2026-10-15", + "open_weights": false, + "release_date": "2026-05-21", + "last_updated": "2026-05-21", "type": "chat" }, { - "id": "gpt-5.3-codex", - "name": "GPT-5.3 Codex", - "display_name": "GPT-5.3 Codex", + "id": "alibaba/qwen3.8-flash", + "name": "Qwen 3.8 Flash", + "display_name": "Qwen 3.8 Flash", "modalities": { "input": [ "text", @@ -35112,7 +37074,7 @@ ] }, "limit": { - "context": 400000, + "context": 991000, "output": 128000 }, "temperature": true, @@ -35124,46 +37086,36 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", "effort_options": [ "low", "medium", - "high", "xhigh" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-02-05", - "last_updated": "2026-02-05", + "release_date": "2026-08-26", + "last_updated": "2026-08-26", "type": "chat" }, { - "id": "glm-5.1", - "name": "GLM-5.1", - "display_name": "GLM-5.1", + "id": "alibaba/qwen3.5-plus", + "name": "Qwen 3.5 Plus", + "display_name": "Qwen 3.5 Plus", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 204800, - "output": 131072 + "context": 1000000, + "output": 64000 }, "temperature": true, "tool_call": true, @@ -35179,25 +37131,34 @@ "visibility": "summary", "continuation": [ "thinking_blocks" + ], + "effort_options": [ + "none", + "minimal", + "low", + "medium", + "high", + "xhigh", + "max" ] } }, - "attachment": false, - "open_weights": true, + "attachment": true, + "open_weights": false, "knowledge": "2025-04", - "release_date": "2026-04-07", - "last_updated": "2026-04-07", + "release_date": "2026-02-16", + "last_updated": "2026-02-16", "type": "chat" }, { - "id": "qwen3.8-flash", - "name": "Qwen3.8 Flash", - "display_name": "Qwen3.8 Flash", + "id": "alibaba/qwen3.5-flash", + "name": "Qwen 3.5 Flash", + "display_name": "Qwen 3.5 Flash", "modalities": { "input": [ "text", "image", - "video" + "pdf" ], "output": [ "text" @@ -35205,7 +37166,7 @@ }, "limit": { "context": 1000000, - "output": 131072 + "output": 64000 }, "temperature": true, "tool_call": true, @@ -35216,75 +37177,62 @@ "extra_capabilities": { "reasoning": { "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ + "none", + "minimal", "low", "medium", - "xhigh" + "high", + "xhigh", + "max" ] } }, "attachment": true, "open_weights": false, - "release_date": "2026-08-26", - "last_updated": "2026-08-26", + "release_date": "2026-02-23", + "last_updated": "2026-02-23", "type": "chat" }, { - "id": "gemini-3-flash", - "name": "Gemini 3 Flash", - "display_name": "Gemini 3 Flash", + "id": "alibaba/qwen3-vl-instruct", + "name": "Qwen3 VL Instruct", + "display_name": "Qwen3 VL Instruct", "modalities": { "input": [ "text", - "image", - "video", - "audio", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 262144, + "output": 262144 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "level", - "level": "high", - "level_options": [ - "minimal", - "low", - "medium", - "high" - ], - "summaries": true, - "visibility": "summary", - "continuation": [ - "thought_signatures" - ] - } + "supported": false }, "attachment": true, - "open_weights": false, - "knowledge": "2025-01", - "release_date": "2025-12-17", - "last_updated": "2025-12-17", + "open_weights": true, + "knowledge": "2025-04", + "release_date": "2025-09-23", + "last_updated": "2025-09-24", "type": "chat" }, { - "id": "claude-haiku-4-5", - "name": "Claude Haiku 4.5", - "display_name": "Claude Haiku 4.5", + "id": "alibaba/qwen3.7-flash", + "name": "Qwen 3.7 Flash", + "display_name": "Qwen 3.7 Flash", "modalities": { "input": [ "text", @@ -35296,61 +37244,49 @@ ] }, "limit": { - "context": 200000, + "context": 991000, "output": 64000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "budget", - "budget": { - "min": 1024, - "unit": "tokens" - }, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", - "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." + "effort_options": [ + "none", + "minimal", + "low", + "medium", + "high", + "xhigh", + "max" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-02-28", - "release_date": "2025-10-15", - "last_updated": "2025-10-15", + "release_date": "2026-07-15", + "last_updated": "2026-07-15", "type": "chat" }, { - "id": "gemini-3.8-flash", - "name": "Gemini 3.8 Flash", - "display_name": "Gemini 3.8 Flash", + "id": "alibaba/qwen3-max-thinking", + "name": "Qwen 3 Max Thinking", + "display_name": "Qwen 3 Max Thinking", "modalities": { "input": [ - "text", - "image", - "video", - "audio", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, + "context": 256000, "output": 65536 }, "temperature": true, @@ -35362,32 +37298,25 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "level", - "level": "high", - "level_options": [ - "minimal", - "low", - "medium", - "high" - ], + "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ - "thought_signatures" + "thinking_blocks" ] } }, - "attachment": true, - "open_weights": false, - "release_date": "2026-09-02", - "last_updated": "2026-09-02", + "attachment": false, + "open_weights": true, + "knowledge": "2025-01", + "release_date": "2026-01-23", + "last_updated": "2025-01", "type": "chat" }, { - "id": "glm-5", - "name": "GLM-5", - "display_name": "GLM-5", + "id": "alibaba/qwen-3-30b", + "name": "Qwen3-30B-A3B", + "display_name": "Qwen3-30B-A3B", "modalities": { "input": [ "text" @@ -35397,8 +37326,8 @@ ] }, "limit": { - "context": 204800, - "output": 131072 + "context": 40960, + "output": 16384 }, "temperature": true, "tool_call": true, @@ -35406,28 +37335,16 @@ "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } - }, "attachment": false, "open_weights": true, - "knowledge": "2025-04", - "release_date": "2026-02-12", - "last_updated": "2026-02-12", + "release_date": "2025-04-28", + "last_updated": "2025-04-28", "type": "chat" }, { - "id": "glm-4.7-free", - "name": "GLM-4.7 Free", - "display_name": "GLM-4.7 Free", + "id": "alibaba/qwen3-coder-next", + "name": "Qwen3 Coder Next", + "display_name": "Qwen3 Coder Next", "modalities": { "input": [ "text" @@ -35437,8 +37354,8 @@ ] }, "limit": { - "context": 204800, - "output": 131072 + "context": 256000, + "output": 256000 }, "temperature": true, "tool_call": true, @@ -35446,28 +37363,17 @@ "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } - }, "attachment": false, "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-12-22", - "last_updated": "2025-12-22", + "knowledge": "2025-09", + "release_date": "2026-02-03", + "last_updated": "2026-02-03", "type": "chat" }, { - "id": "gpt-5.4-nano", - "name": "GPT-5.4 Nano", - "display_name": "GPT-5.4 Nano", + "id": "alibaba/qwen3.8-max-0902", + "name": "Qwen3.8 Max 0902", + "display_name": "Qwen3.8 Max 0902", "modalities": { "input": [ "text", @@ -35479,63 +37385,47 @@ ] }, "limit": { - "context": 400000, + "context": 991000, "output": 128000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "none", "effort_options": [ - "none", "low", "medium", - "high", "xhigh" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-03-17", - "last_updated": "2026-03-17", + "release_date": "2026-09-02", + "last_updated": "2026-09-02", "type": "chat" }, { - "id": "gpt-5.5-pro", - "name": "GPT-5.5 Pro", - "display_name": "GPT-5.5 Pro", + "id": "alibaba/qwen-3-235b", + "name": "Qwen3 235B A22B Instruct 2507", + "display_name": "Qwen3 235B A22B Instruct 2507", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 262144, + "output": 16384 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -35543,25 +37433,52 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, "effort_options": [ + "none", + "low", "medium", - "high", - "xhigh" + "high" ] } }, + "attachment": false, + "open_weights": true, + "release_date": "2025-07-21", + "last_updated": "2025-07-21", + "type": "chat" + }, + { + "id": "alibaba/wan-v2.6-r2v", + "name": "Wan v2.6 Reference-to-Video", + "display_name": "Wan v2.6 Reference-to-Video", + "modalities": { + "input": [ + "text", + "image", + "video" + ], + "output": [ + "video" + ] + }, + "limit": { + "context": 8192, + "output": 8192 + }, + "temperature": false, + "tool_call": false, + "reasoning": { + "supported": false + }, "attachment": true, "open_weights": false, - "knowledge": "2025-12-01", - "release_date": "2026-04-23", - "last_updated": "2026-04-23", - "type": "chat" + "release_date": "2025-12-16", + "last_updated": "2025-12-16" }, { - "id": "glm-5.3", - "name": "GLM-5.3", - "display_name": "GLM-5.3", + "id": "alibaba/qwen3-embedding-8b", + "name": "Qwen3 Embedding 8B", + "display_name": "Qwen3 Embedding 8B", "modalities": { "input": [ "text" @@ -35571,46 +37488,57 @@ ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 32768, + "output": 8192 }, - "temperature": true, - "tool_call": true, + "temperature": false, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "effort_options": [ - "low", - "high", - "max" - ] - } + "supported": false }, "attachment": false, "open_weights": true, - "release_date": "2026-08-14", - "last_updated": "2026-08-14", - "type": "chat" + "release_date": "2025-06-05", + "last_updated": "2025-06-05", + "type": "embedding" }, { - "id": "claude-opus-4-8", - "name": "Claude Opus 4.8", - "display_name": "Claude Opus 4.8", + "id": "alibaba/wan-v3.0-video", + "name": "Wan v3.0 Video", + "display_name": "Wan v3.0 Video", "modalities": { "input": [ "text", "image", - "pdf" + "video", + "audio" + ], + "output": [ + "video" + ] + }, + "limit": { + "context": 8192, + "output": 8192 + }, + "temperature": false, + "tool_call": false, + "reasoning": { + "supported": false + }, + "attachment": true, + "open_weights": false, + "release_date": "2026-08-23", + "last_updated": "2026-08-23" + }, + { + "id": "alibaba/qwen3.8-max-prime", + "name": "Qwen 3.8 Max Prime", + "display_name": "Qwen 3.8 Max Prime", + "modalities": { + "input": [ + "text", + "image" ], "output": [ "text" @@ -35618,51 +37546,33 @@ }, "limit": { "context": 1000000, - "output": 128000 + "output": 131072 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "high", "effort_options": [ "low", "medium", - "high", - "xhigh", - "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "omitted", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Claude Opus 4.7 and newer Opus models require thinking.type = \"adaptive\" to enable thinking explicitly.", - "Manual budget_tokens requests return 400 on Claude Opus 4.7 and newer adaptive-only Opus models.", - "task_budget is separate from thinking control and should not be treated as a thinking budget." + "xhigh" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-01", - "release_date": "2026-05-28", - "last_updated": "2026-05-28", + "release_date": "2026-09-23", + "last_updated": "2026-09-23", "type": "chat" }, { - "id": "big-pickle", - "name": "Big Pickle", - "display_name": "Big Pickle", + "id": "alibaba/qwen3-next-80b-a3b-thinking", + "name": "Qwen3 Next 80B A3B Thinking", + "display_name": "Qwen3 Next 80B A3B Thinking", "modalities": { "input": [ "text" @@ -35672,8 +37582,8 @@ ] }, "limit": { - "context": 200000, - "output": 32000 + "context": 262144, + "output": 262144 }, "temperature": true, "tool_call": true, @@ -35693,16 +37603,16 @@ } }, "attachment": false, - "open_weights": false, - "knowledge": "2025-01", - "release_date": "2025-10-17", - "last_updated": "2025-10-17", + "open_weights": true, + "knowledge": "2025-04", + "release_date": "2025-09", + "last_updated": "2025-09", "type": "chat" }, { - "id": "hy3-preview-free", - "name": "Hy3 preview Free", - "display_name": "Hy3 preview Free", + "id": "alibaba/qwen3-embedding-4b", + "name": "Qwen3 Embedding 4B", + "display_name": "Qwen3 Embedding 4B", "modalities": { "input": [ "text" @@ -35712,74 +37622,55 @@ ] }, "limit": { - "context": 256000, - "output": 64000 + "context": 32768, + "output": 8192 }, - "temperature": true, - "tool_call": true, + "temperature": false, + "tool_call": false, "reasoning": { - "supported": true, - "default": true + "supported": false }, "attachment": false, "open_weights": true, - "knowledge": "2025-06", - "release_date": "2026-04-20", - "last_updated": "2026-04-20", - "type": "chat" + "release_date": "2025-06-05", + "last_updated": "2025-06-05", + "type": "embedding" }, { - "id": "gemini-4-argon", - "name": "Gemini 4 Argon", - "display_name": "Gemini 4 Argon", + "id": "alibaba/wan-v2.5-t2v-preview", + "name": "Wan v2.5 Text-to-Video Preview", + "display_name": "Wan v2.5 Text-to-Video Preview", "modalities": { "input": [ "text", - "image", - "video", - "audio", - "pdf" + "audio" ], "output": [ - "text" + "video" ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 8192, + "output": 8192 }, "temperature": false, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "low", - "medium", - "high" - ] - } + "supported": false }, "attachment": true, "open_weights": false, - "knowledge": "2026-03", - "release_date": "2026-10-13", - "last_updated": "2026-10-13", - "type": "chat" + "release_date": "2025-09-24", + "last_updated": "2025-09-24" }, { - "id": "gemini-3.6-flash", - "name": "Gemini 3.6 Flash", - "display_name": "Gemini 3.6 Flash", + "id": "alibaba/qwen3.8-27b", + "name": "Qwen3.8 27B", + "display_name": "Qwen3.8 27B", "modalities": { "input": [ "text", "image", - "video", - "audio", "pdf" ], "output": [ @@ -35787,8 +37678,8 @@ ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 1000000, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -35798,46 +37689,36 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "level", - "level": "high", - "level_options": [ - "minimal", + "effort_options": [ + "none", "low", "medium", - "high" - ], - "summaries": true, - "visibility": "summary", - "continuation": [ - "thought_signatures" + "xhigh" ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2026-03", - "release_date": "2026-07-21", - "last_updated": "2026-07-21", + "open_weights": true, + "release_date": "2026-08-14", + "last_updated": "2026-08-14", "type": "chat" }, { - "id": "qwen3.6-plus-free", - "name": "Qwen3.6 Plus Free", - "display_name": "Qwen3.6 Plus Free", + "id": "alibaba/qwen3.6-27b", + "name": "Qwen 3.6 27B", + "display_name": "Qwen 3.6 27B", "modalities": { "input": [ "text", "image", - "video" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 262144, + "context": 256000, "output": 65536 }, "temperature": true, @@ -35858,56 +37739,43 @@ } }, "attachment": true, - "open_weights": false, - "knowledge": "2025-04", - "release_date": "2026-04-02", - "last_updated": "2026-04-02", + "open_weights": true, + "release_date": "2026-04-22", + "last_updated": "2026-04-22", "type": "chat" }, { - "id": "glm-5-free", - "name": "GLM-5 Free", - "display_name": "GLM-5 Free", + "id": "alibaba/wan-v2.6-i2v", + "name": "Wan v2.6 Image-to-Video", + "display_name": "Wan v2.6 Image-to-Video", "modalities": { "input": [ - "text" + "text", + "image", + "audio" ], "output": [ - "text" + "video" ] }, "limit": { - "context": 204800, - "output": 131072 + "context": 8192, + "output": 8192 }, - "temperature": true, - "tool_call": true, + "temperature": false, + "tool_call": false, "reasoning": { - "supported": true, - "default": true + "supported": false }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } - }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-04", - "release_date": "2026-02-12", - "last_updated": "2026-02-12", - "type": "chat" + "attachment": true, + "open_weights": false, + "release_date": "2025-12-16", + "last_updated": "2025-12-16" }, { - "id": "kimi-k2.5-free", - "name": "Kimi K2.5 Free", - "display_name": "Kimi K2.5 Free", + "id": "alibaba/wan-v2.6-r2v-flash", + "name": "Wan v2.6 Reference-to-Video Flash", + "display_name": "Wan v2.6 Reference-to-Video Flash", "modalities": { "input": [ "text", @@ -35915,41 +37783,27 @@ "video" ], "output": [ - "text" + "video" ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 8192, + "output": 8192 }, "temperature": false, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } + "supported": false }, "attachment": true, - "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-01-27", - "last_updated": "2026-01-27", - "type": "chat" + "open_weights": false, + "release_date": "2025-12-16", + "last_updated": "2025-12-16" }, { - "id": "gpt-5.2-codex", - "name": "GPT-5.2 Codex", - "display_name": "GPT-5.2 Codex", + "id": "alibaba/qwen3.6-plus", + "name": "Qwen 3.6 Plus", + "display_name": "Qwen 3.6 Plus", "modalities": { "input": [ "text", @@ -35961,10 +37815,10 @@ ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 1000000, + "output": 64000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -35973,87 +37827,63 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ + "none", + "minimal", "low", "medium", "high", - "xhigh" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + "xhigh", + "max" + ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2025-12-11", - "last_updated": "2025-12-11", + "knowledge": "2025-04", + "release_date": "2026-04-02", + "last_updated": "2026-04-02", "type": "chat" }, { - "id": "claude-sonnet-4-5", - "name": "Claude Sonnet 4.5", - "display_name": "Claude Sonnet 4.5", + "id": "alibaba/wan-v2.7-r2v", + "name": "Wan v2.7 Reference-to-Video", + "display_name": "Wan v2.7 Reference-to-Video", "modalities": { "input": [ "text", "image", - "pdf" + "video", + "audio" ], "output": [ - "text" + "video" ] }, "limit": { - "context": 1000000, - "output": 64000 + "context": 8192, + "output": 8192 }, - "temperature": true, - "tool_call": true, + "temperature": false, + "tool_call": false, "reasoning": { - "supported": true, - "default": false - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "budget", - "budget": { - "min": 1024, - "unit": "tokens" - }, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", - "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." - ] - } + "supported": false }, "attachment": true, "open_weights": false, - "knowledge": "2025-07-31", - "release_date": "2025-09-29", - "last_updated": "2025-09-29", - "type": "chat" + "release_date": "2026-04-07", + "last_updated": "2026-04-07" }, { - "id": "hy3-free", - "name": "Hy3 Free", - "display_name": "Hy3 Free", + "id": "alibaba/qwen3-coder-30b-a3b", + "name": "Qwen 3 Coder 30B A3B Instruct", + "display_name": "Qwen 3 Coder 30B A3B Instruct", "modalities": { "input": [ "text" @@ -36063,8 +37893,8 @@ ] }, "limit": { - "context": 190000, - "output": 64000 + "context": 262144, + "output": 8192 }, "temperature": true, "tool_call": true, @@ -36072,47 +37902,28 @@ "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "effort_options": [ - "low", - "medium", - "high" - ] - } - }, "attachment": false, "open_weights": true, - "release_date": "2026-07-06", - "last_updated": "2026-07-06", + "knowledge": "2025-04", + "release_date": "2025-07-31", + "last_updated": "2025-07-31", "type": "chat" }, { - "id": "gemini-3.5-flash", - "name": "Gemini 3.5 Flash", - "display_name": "Gemini 3.5 Flash", + "id": "alibaba/qwen3-coder", + "name": "Qwen3 Coder 480B A35B Instruct", + "display_name": "Qwen3 Coder 480B A35B Instruct", "modalities": { "input": [ - "text", - "image", - "video", - "audio", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 262144, + "output": 32768 }, "temperature": true, "tool_call": true, @@ -36120,132 +37931,112 @@ "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "level", - "level": "high", - "level_options": [ - "minimal", - "low", - "medium", - "high" - ], - "summaries": true, - "visibility": "summary", - "continuation": [ - "thought_signatures" - ] - } + "attachment": false, + "open_weights": true, + "knowledge": "2025-04", + "release_date": "2025-07-22", + "last_updated": "2025-07-22", + "type": "chat" + }, + { + "id": "alibaba/wan-v2.6-t2v", + "name": "Wan v2.6 Text-to-Video", + "display_name": "Wan v2.6 Text-to-Video", + "modalities": { + "input": [ + "text", + "audio" + ], + "output": [ + "video" + ] + }, + "limit": { + "context": 8192, + "output": 8192 + }, + "temperature": false, + "tool_call": false, + "reasoning": { + "supported": false }, "attachment": true, "open_weights": false, - "knowledge": "2025-01", - "release_date": "2026-05-19", - "last_updated": "2026-05-19", - "type": "chat" + "release_date": "2025-12-16", + "last_updated": "2025-12-16" }, { - "id": "muse-spark-1.2", - "name": "Muse Spark 1.2", - "display_name": "Muse Spark 1.2", + "id": "alibaba/wan-v3.0-video-prime", + "name": "Wan v3.0 Video Prime", + "display_name": "Wan v3.0 Video Prime", "modalities": { "input": [ "text", "image", "video", - "pdf", "audio" ], "output": [ - "text" + "video" ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 8192, + "output": 8192 }, - "temperature": true, - "tool_call": true, + "temperature": false, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "effort_options": [ - "minimal", - "low", - "medium", - "high", - "xhigh" - ] - } + "supported": false }, "attachment": true, "open_weights": false, - "release_date": "2026-08-05", - "last_updated": "2026-08-05", - "type": "chat" + "release_date": "2026-08-28", + "last_updated": "2026-08-28" }, { - "id": "claude-sonnet-4", - "name": "Claude Sonnet 4", - "display_name": "Claude Sonnet 4", + "id": "alibaba/qwen3.8-max", + "name": "Qwen 3.8 Max", + "display_name": "Qwen 3.8 Max", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 64000 + "context": 262144, + "output": 128000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "budget", - "budget": { - "min": 1024, - "unit": "tokens" - }, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", - "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." + "effort_options": [ + "low", + "medium", + "xhigh" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-03-31", - "release_date": "2025-05-22", - "last_updated": "2025-05-22", + "release_date": "2026-07-19", + "last_updated": "2026-07-19", "type": "chat" }, { - "id": "claude-fable-5", - "name": "Claude Fable 5", - "display_name": "Claude Fable 5", + "id": "alibaba/qwen3.7-plus", + "name": "Qwen 3.7 Plus", + "display_name": "Qwen 3.7 Plus", "modalities": { "input": [ "text", @@ -36258,9 +38049,9 @@ }, "limit": { "context": 1000000, - "output": 128000 + "output": 64000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -36269,52 +38060,39 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "high", "effort_options": [ + "none", + "minimal", "low", "medium", "high", "xhigh", "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "omitted", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Adaptive thinking is always on for Claude Fable 5 and Claude Mythos 5; thinking.type = \"disabled\" is rejected.", - "Manual budget_tokens requests return 400 on Claude Fable 5 and Claude Mythos 5.", - "thinking.display defaults to omitted; set display to summarized to receive readable thinking summaries." ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-01-31", - "release_date": "2026-06-09", - "last_updated": "2026-06-09", + "knowledge": "2025-04", + "release_date": "2026-06-02", + "last_updated": "2026-06-02", "type": "chat" }, { - "id": "fledge-alpha-free", - "name": "Fledge Alpha Free", - "display_name": "Fledge Alpha Free", + "id": "alibaba/qwen-3-14b", + "name": "Qwen3-14B", + "display_name": "Qwen3-14B", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 40960, + "output": 16384 }, "temperature": true, "tool_call": true, @@ -36322,38 +38100,28 @@ "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "low", - "high", - "max" - ] - } - }, - "attachment": true, + "attachment": false, "open_weights": false, - "release_date": "2026-10-01", - "last_updated": "2026-10-01", + "knowledge": "2025-04", + "release_date": "2025-04-28", + "last_updated": "2025-04", "type": "chat" }, { - "id": "minimax-m3-free", - "name": "MiniMax-M3 Free", - "display_name": "MiniMax-M3 Free", + "id": "alibaba/qwen-3-32b", + "name": "Qwen 3.32B", + "display_name": "Qwen 3.32B", "modalities": { "input": [ - "text", - "image", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 32000 + "context": 128000, + "output": 8192 }, "temperature": true, "tool_call": true, @@ -36363,35 +38131,37 @@ }, "extra_capabilities": { "reasoning": { - "supported": true + "effort_options": [ + "none", + "low", + "medium", + "high" + ] } }, - "attachment": true, + "attachment": false, "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-06-01", - "last_updated": "2026-06-01", + "knowledge": "2025-04", + "release_date": "2025-04-28", + "last_updated": "2025-04-28", "type": "chat" }, { - "id": "claude-fable-5-1", - "name": "Claude Fable 5.1", - "display_name": "Claude Fable 5.1", + "id": "liquid/lfm2.5-24b-a2b", + "name": "LFM2.5-24B-A2B", + "display_name": "LFM2.5-24B-A2B", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 128000, + "output": 8200 }, - "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -36399,294 +38169,300 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "high", "effort_options": [ + "minimal", "low", "medium", "high", "xhigh", "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "omitted", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Adaptive thinking is always on for Claude Fable 5 and Claude Mythos 5; thinking.type = \"disabled\" is rejected.", - "Manual budget_tokens requests return 400 on Claude Fable 5 and Claude Mythos 5.", - "thinking.display defaults to omitted; set display to summarized to receive readable thinking summaries." ] } }, - "attachment": true, + "attachment": false, "open_weights": false, - "knowledge": "2026-06", - "release_date": "2026-09-01", - "last_updated": "2026-09-01", + "release_date": "2026-10-13", + "last_updated": "2026-10-13", "type": "chat" }, { - "id": "gpt-5.2", - "name": "GPT-5.2", - "display_name": "GPT-5.2", + "id": "microsoft/mai-voice-2-flash", + "name": "MAI-Voice-2-Flash", + "display_name": "MAI-Voice-2-Flash", + "modalities": { + "input": [ + "text" + ], + "output": [ + "audio" + ] + }, + "limit": { + "context": 8192, + "output": 8192 + }, + "temperature": false, + "tool_call": false, + "reasoning": { + "supported": false + }, + "attachment": false, + "open_weights": false, + "release_date": "2026-07-23", + "last_updated": "2026-07-23" + }, + { + "id": "microsoft/mai-image-2.6", + "name": "MAI-Image-2.6", + "display_name": "MAI-Image-2.6", "modalities": { "input": [ "text", "image" ], "output": [ - "text" + "image" ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 8192, + "output": 8192 }, - "temperature": true, - "tool_call": true, + "temperature": false, + "tool_call": false, "reasoning": { - "supported": true, - "default": false - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "none", - "effort_options": [ - "none", - "low", - "medium", - "high", - "xhigh" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" - } + "supported": false }, "attachment": true, "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2025-12-11", - "last_updated": "2025-12-11", - "type": "chat" + "release_date": "2026-10-13", + "last_updated": "2026-10-13" }, { - "id": "gpt-6.1-sol", - "name": "GPT-6.1 Sol", - "display_name": "GPT-6.1 Sol", + "id": "microsoft/mai-voice-2.1-flash", + "name": "MAI-Voice-2.1-Flash", + "display_name": "MAI-Voice-2.1-Flash", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ - "text" + "audio" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 8192, + "output": 8192 }, "temperature": false, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true + "supported": false }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" - ] - } + "attachment": false, + "open_weights": false, + "release_date": "2026-10-01", + "last_updated": "2026-10-01" + }, + { + "id": "microsoft/mai-transcribe-2-streaming", + "name": "MAI-Transcribe-2-Streaming", + "display_name": "MAI-Transcribe-2-Streaming", + "modalities": { + "input": [ + "audio" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 8192, + "output": 8192 + }, + "temperature": false, + "tool_call": false, + "reasoning": { + "supported": false }, "attachment": true, "open_weights": false, - "knowledge": "2026-04-30", - "release_date": "2026-09-29", - "last_updated": "2026-09-29", - "type": "chat" + "release_date": "2026-10-01", + "last_updated": "2026-10-01" }, { - "id": "cat-jam-free", - "name": "Cat Jam Free", - "display_name": "Cat Jam Free", + "id": "microsoft/mai-voice-2", + "name": "MAI-Voice-2", + "display_name": "MAI-Voice-2", "modalities": { "input": [ "text" ], "output": [ - "text" + "audio" ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 8192, + "output": 8192 }, - "temperature": true, - "tool_call": true, + "temperature": false, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" - ] - } + "supported": false }, "attachment": false, "open_weights": false, - "release_date": "2026-10-12", - "last_updated": "2026-10-12", - "type": "chat" + "release_date": "2026-06-02", + "last_updated": "2026-06-02" }, { - "id": "trinity-large-preview-free", - "name": "Trinity Large Preview", - "display_name": "Trinity Large Preview", + "id": "microsoft/mai-voice-2.1", + "name": "MAI-Voice-2.1", + "display_name": "MAI-Voice-2.1", "modalities": { "input": [ "text" ], "output": [ - "text" + "audio" ] }, "limit": { - "context": 131072, - "output": 131072 + "context": 8192, + "output": 8192 }, - "temperature": true, - "tool_call": true, + "temperature": false, + "tool_call": false, "reasoning": { "supported": false }, "attachment": false, - "open_weights": true, - "knowledge": "2025-06", - "release_date": "2026-01-27", - "last_updated": "2026-05-28", - "type": "chat" + "open_weights": false, + "release_date": "2026-10-01", + "last_updated": "2026-10-01" }, { - "id": "ling-2.6-flash-free", - "name": "Ling 2.6 Flash Free", - "display_name": "Ling 2.6 Flash Free", + "id": "microsoft/mai-image-2.6-flash", + "name": "MAI-Image-2.6-Flash", + "display_name": "MAI-Image-2.6-Flash", "modalities": { "input": [ - "text" + "text", + "image" + ], + "output": [ + "image" + ] + }, + "limit": { + "context": 8192, + "output": 8192 + }, + "temperature": false, + "tool_call": false, + "reasoning": { + "supported": false + }, + "attachment": true, + "open_weights": false, + "release_date": "2026-10-13", + "last_updated": "2026-10-13" + }, + { + "id": "microsoft/mai-transcribe-2", + "name": "MAI-Transcribe 2", + "display_name": "MAI-Transcribe 2", + "modalities": { + "input": [ + "audio" ], "output": [ "text" ] }, "limit": { - "context": 262100, - "output": 32800 + "context": 8192, + "output": 8192 }, - "temperature": true, - "tool_call": true, + "temperature": false, + "tool_call": false, "reasoning": { "supported": false }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-06", - "release_date": "2026-04-21", - "last_updated": "2026-04-21", - "type": "chat" + "attachment": true, + "open_weights": false, + "release_date": "2026-09-03", + "last_updated": "2026-09-03" }, { - "id": "x-preview-f-free", - "name": "Ox Alpha Free (Unlimited)", - "display_name": "Ox Alpha Free (Unlimited)", + "id": "anthropic/claude-haiku-4.5", + "name": "Claude Haiku 4.5", + "display_name": "Claude Haiku 4.5", "modalities": { "input": [ "text", "image", - "video" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 200000, + "output": 64000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": false, + "mode": "budget", + "budget": { + "min": 1024, + "unit": "tokens" + }, "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ "thinking_blocks" ], - "effort_options": [ - "low", - "high", - "max" + "notes": [ + "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", + "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." ] } }, "attachment": true, "open_weights": false, - "release_date": "2026-08-21", - "last_updated": "2026-08-21", + "knowledge": "2025-02-28", + "release_date": "2025-10-15", + "last_updated": "2025-10-15", "type": "chat" }, { - "id": "gpt-5-nano", - "name": "GPT-5 Nano", - "display_name": "GPT-5 Nano", + "id": "anthropic/claude-fable-5.1", + "name": "Claude Fable 5.1", + "display_name": "Claude Fable 5.1", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 400000, + "context": 1000000, "output": 128000 }, "temperature": false, @@ -36700,46 +38476,53 @@ "supported": true, "default_enabled": true, "mode": "effort", - "effort": "medium", + "effort": "high", "effort_options": [ - "minimal", "low", "medium", - "high" + "high", + "xhigh", + "max" ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" + "interleaved": true, + "summaries": true, + "visibility": "omitted", + "continuation": [ + "thinking_blocks" ], - "visibility": "hidden" + "notes": [ + "Adaptive thinking is always on for Claude Fable 5 and Claude Mythos 5; thinking.type = \"disabled\" is rejected.", + "Manual budget_tokens requests return 400 on Claude Fable 5 and Claude Mythos 5.", + "thinking.display defaults to omitted; set display to summarized to receive readable thinking summaries." + ] } }, "attachment": true, "open_weights": false, - "knowledge": "2024-05-30", - "release_date": "2025-08-07", - "last_updated": "2025-08-07", + "knowledge": "2026-06", + "release_date": "2026-09-01", + "last_updated": "2026-09-01", "type": "chat" }, { - "id": "minimax-m2.5-free", - "name": "MiniMax-M2.5 Free", - "display_name": "MiniMax-M2.5 Free", + "id": "anthropic/claude-opus-5.5-fast", + "name": "Claude Opus 5.5 (Fast)", + "display_name": "Claude Opus 5.5 (Fast)", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 204800, - "output": 131072 + "context": 1000000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -36747,23 +38530,31 @@ }, "extra_capabilities": { "reasoning": { - "supported": true + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" + ] } }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-02-12", - "last_updated": "2026-02-12", + "attachment": true, + "open_weights": false, + "knowledge": "2026-06", + "release_date": "2026-09-22", + "last_updated": "2026-09-22", "type": "chat" }, { - "id": "glm-5.2", - "name": "GLM-5.2", - "display_name": "GLM-5.2", + "id": "anthropic/claude-sonnet-4.5", + "name": "Claude Sonnet 4.5", + "display_name": "Claude Sonnet 4.5", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" @@ -36771,130 +38562,160 @@ }, "limit": { "context": 1000000, - "output": 131072 + "output": 64000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": false, + "mode": "budget", + "budget": { + "min": 1024, + "unit": "tokens" + }, "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ "thinking_blocks" ], - "effort_options": [ - "high", - "max" + "notes": [ + "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", + "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." ] } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-06-13", - "last_updated": "2026-06-13", + "attachment": true, + "open_weights": false, + "knowledge": "2025-07-31", + "release_date": "2025-09-29", + "last_updated": "2025-09-29", "type": "chat" }, { - "id": "ling-3.1-flash-free", - "name": "Ling 3.1 Flash Free", - "display_name": "Ling 3.1 Flash Free", + "id": "anthropic/claude-sonnet-5", + "name": "Claude Sonnet 5", + "display_name": "Claude Sonnet 5", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 32768 + "context": 1000000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, "default": true }, - "attachment": false, - "open_weights": true, - "release_date": "2026-09-29", - "last_updated": "2026-09-29", + "extra_capabilities": { + "reasoning": { + "supported": true, + "effort_options": [ + "none", + "low", + "medium", + "high", + "xhigh", + "max" + ] + } + }, + "attachment": true, + "open_weights": false, + "knowledge": "2026-01-31", + "release_date": "2026-06-30", + "last_updated": "2026-06-30", "type": "chat" }, { - "id": "grok-4.6", - "name": "Grok 4.6", - "display_name": "Grok 4.6", + "id": "anthropic/claude-opus-4.8-fast", + "name": "Claude Opus 4.8 (Fast)", + "display_name": "Claude Opus 4.8 (Fast)", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 500000, - "output": 500000 + "context": 1000000, + "output": 128000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": false, + "mode": "effort", + "effort": "high", "effort_options": [ "low", "medium", "high", - "xhigh" + "xhigh", + "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "omitted", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Claude Opus 4.7 and newer Opus models require thinking.type = \"adaptive\" to enable thinking explicitly.", + "Manual budget_tokens requests return 400 on Claude Opus 4.7 and newer adaptive-only Opus models.", + "task_budget is separate from thinking control and should not be treated as a thinking budget." ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-02-01", - "release_date": "2026-08-12", - "last_updated": "2026-08-12", + "knowledge": "2026-01", + "release_date": "2026-05-28", + "last_updated": "2026-05-28", "type": "chat" - } - ] - }, - "moonshot": { - "id": "moonshot", - "name": "Moonshot AI (China)", - "display_name": "Moonshot AI (China)", - "api": "https://api.moonshot.cn/v1", - "doc": "https://platform.moonshot.cn/docs/api/chat", - "models": [ + }, { - "id": "kimi-k3", - "name": "Kimi K3", - "display_name": "Kimi K3", + "id": "anthropic/claude-opus-5-fast", + "name": "Claude Opus 5 (Fast)", + "display_name": "Claude Opus 5 (Fast)", "modalities": { "input": [ "text", "image", - "video" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 1048576 + "context": 1000000, + "output": 128000 }, "temperature": false, "tool_call": true, @@ -36904,43 +38725,40 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], "effort_options": [ + "none", "low", + "medium", "high", + "xhigh", "max" ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-07-16", - "last_updated": "2026-07-16", + "open_weights": false, + "knowledge": "2026-05", + "release_date": "2026-07-24", + "last_updated": "2026-07-24", "type": "chat" }, { - "id": "kimi-k2.6", - "name": "Kimi K2.6", - "display_name": "Kimi K2.6", + "id": "anthropic/claude-fable-5", + "name": "Claude Fable 5", + "display_name": "Claude Fable 5", "modalities": { "input": [ "text", "image", - "video" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 1000000, + "output": 128000 }, "temperature": false, "tool_call": true, @@ -36951,38 +38769,53 @@ "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "high", + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" + ], "interleaved": true, "summaries": true, - "visibility": "summary", + "visibility": "omitted", "continuation": [ "thinking_blocks" + ], + "notes": [ + "Adaptive thinking is always on for Claude Fable 5 and Claude Mythos 5; thinking.type = \"disabled\" is rejected.", + "Manual budget_tokens requests return 400 on Claude Fable 5 and Claude Mythos 5.", + "thinking.display defaults to omitted; set display to summarized to receive readable thinking summaries." ] } }, "attachment": true, - "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-04-20", - "last_updated": "2026-04-20", + "open_weights": false, + "knowledge": "2026-01-31", + "release_date": "2026-06-09", + "last_updated": "2026-06-09", "type": "chat" }, { - "id": "kimi-k3.1-pro-highspeed", - "name": "Kimi K3.1 Pro Highspeed", - "display_name": "Kimi K3.1 Pro Highspeed", + "id": "anthropic/claude-haiku-5.5", + "name": "Claude Haiku 5.5", + "display_name": "Claude Haiku 5.5", "modalities": { "input": [ "text", "image", - "video" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 1048576 + "context": 1000000, + "output": 128000 }, "temperature": false, "tool_call": true, @@ -36992,135 +38825,159 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], "effort_options": [ + "none", "low", + "medium", "high", + "xhigh", "max" ] } }, "attachment": true, "open_weights": false, - "release_date": "2026-10-23", - "last_updated": "2026-10-23", + "knowledge": "2026-06", + "release_date": "2026-10-07", + "last_updated": "2026-10-07", "type": "chat" }, { - "id": "kimi-k3.1-pro", - "name": "Kimi K3.1 Pro", - "display_name": "Kimi K3.1 Pro", - "modalities": { + "id": "anthropic/claude-opus-4.5", + "name": "Claude Opus 4.5", + "display_name": "Claude Opus 4.5", + "modalities": { "input": [ "text", "image", - "video" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 1048576 + "context": 200000, + "output": 64000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": false, + "mode": "mixed", + "budget": { + "min": 1024, + "unit": "tokens" + }, + "effort": "high", + "effort_options": [ + "low", + "medium", + "high" + ], "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ "thinking_blocks" ], - "effort_options": [ - "low", - "high", - "max" + "notes": [ + "Claude Opus 4.5 uses manual thinking.type = \"enabled\" with budget_tokens; effort can be used alongside the thinking budget.", + "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header." ] } }, "attachment": true, "open_weights": false, - "release_date": "2026-10-23", - "last_updated": "2026-10-23", + "knowledge": "2025-05", + "release_date": "2025-11-24", + "last_updated": "2025-11-24", "type": "chat" }, { - "id": "kimi-k2.7-code", - "name": "Kimi K2.7 Code", - "display_name": "Kimi K2.7 Code", + "id": "anthropic/claude-opus-4.6", + "name": "Claude Opus 4.6", + "display_name": "Claude Opus 4.6", "modalities": { "input": [ "text", "image", - "video" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 1000000, + "output": 128000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "fixed", + "default_enabled": false, + "mode": "mixed", + "budget": { + "min": 1024, + "unit": "tokens" + }, + "effort": "high", + "effort_options": [ + "low", + "medium", + "high", + "max" + ], "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ "thinking_blocks" + ], + "notes": [ + "Anthropic recommends adaptive thinking with effort for Claude 4.6; budget_tokens remains a deprecated compatibility path.", + "Anthropic API defaults effort to high; lower effort levels should be chosen per workload." ] } }, "attachment": true, - "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-04-21", - "last_updated": "2026-04-21", + "open_weights": false, + "knowledge": "2025-05-31", + "release_date": "2026-02-05", + "last_updated": "2026-03-13", "type": "chat" }, { - "id": "kimi-k2.7-code-highspeed", - "name": "Kimi K2.7 Code Highspeed", - "display_name": "Kimi K2.7 Code Highspeed", + "id": "anthropic/claude-sonnet-5.5", + "name": "Claude Sonnet 5.5", + "display_name": "Claude Sonnet 5.5", "modalities": { "input": [ "text", "image", - "video" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 1000000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -37128,135 +38985,155 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "fixed", - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" ] } }, "attachment": true, - "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-04-21", - "last_updated": "2026-04-21", + "open_weights": false, + "knowledge": "2026-06", + "release_date": "2026-09-28", + "last_updated": "2026-09-28", "type": "chat" - } - ] - }, - "friendli": { - "id": "friendli", - "name": "Friendli", - "display_name": "Friendli", - "api": "https://api.friendli.ai/serverless/v1", - "doc": "https://friendli.ai/docs/guides/serverless_endpoints/introduction", - "models": [ + }, { - "id": "zai-org/GLM-5.1", - "name": "GLM-5.1", - "display_name": "GLM-5.1", + "id": "anthropic/claude-opus-4.7", + "name": "Claude Opus 4.7", + "display_name": "Claude Opus 4.7", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 202752, - "output": 131072 + "context": 1000000, + "output": 128000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": false, + "mode": "effort", + "effort": "high", + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" + ], "interleaved": true, "summaries": true, - "visibility": "summary", + "visibility": "omitted", "continuation": [ "thinking_blocks" + ], + "notes": [ + "Claude Opus 4.7 and newer Opus models require thinking.type = \"adaptive\" to enable thinking explicitly.", + "Manual budget_tokens requests return 400 on Claude Opus 4.7 and newer adaptive-only Opus models.", + "task_budget is separate from thinking control and should not be treated as a thinking budget." ] } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-04-07", - "last_updated": "2026-04-07", + "attachment": true, + "open_weights": false, + "knowledge": "2026-01-31", + "release_date": "2026-04-16", + "last_updated": "2026-04-16", "type": "chat" }, { - "id": "zai-org/GLM-5.3-Flash", - "name": "GLM-5.3-Flash", - "display_name": "GLM-5.3-Flash", + "id": "anthropic/claude-opus-4.8", + "name": "Claude Opus 4.8", + "display_name": "Claude Opus 4.8", "modalities": { "input": [ "text", "image", - "video" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 1000000, + "output": 128000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": false, + "mode": "effort", + "effort": "high", + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" + ], "interleaved": true, "summaries": true, - "visibility": "summary", + "visibility": "omitted", "continuation": [ "thinking_blocks" ], - "effort_options": [ - "low", - "high", - "max" + "notes": [ + "Claude Opus 4.7 and newer Opus models require thinking.type = \"adaptive\" to enable thinking explicitly.", + "Manual budget_tokens requests return 400 on Claude Opus 4.7 and newer adaptive-only Opus models.", + "task_budget is separate from thinking control and should not be treated as a thinking budget." ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-08-26", - "last_updated": "2026-08-26", + "open_weights": false, + "knowledge": "2026-01", + "release_date": "2026-05-28", + "last_updated": "2026-05-28", "type": "chat" }, { - "id": "zai-org/GLM-5.2", - "name": "GLM-5.2", - "display_name": "GLM-5.2", + "id": "anthropic/claude-opus-5.5", + "name": "Claude Opus 5.5", + "display_name": "Claude Opus 5.5", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 1000000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -37264,87 +39141,100 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], "effort_options": [ + "low", + "medium", "high", + "xhigh", "max" ] } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-06-13", - "last_updated": "2026-06-13", + "attachment": true, + "open_weights": false, + "knowledge": "2026-06", + "release_date": "2026-09-22", + "last_updated": "2026-09-22", "type": "chat" }, { - "id": "zai-org/GLM-5.3", - "name": "GLM-5.3", - "display_name": "GLM-5.3", + "id": "anthropic/claude-sonnet-4.6", + "name": "Claude Sonnet 4.6", + "display_name": "Claude Sonnet 4.6", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 1000000, + "output": 128000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": false, + "mode": "mixed", + "budget": { + "min": 1024, + "unit": "tokens" + }, + "effort": "high", + "effort_options": [ + "low", + "medium", + "high", + "max" + ], "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ "thinking_blocks" ], - "effort_options": [ - "low", - "high", - "max" + "notes": [ + "Anthropic recommends adaptive thinking with effort for Claude 4.6; budget_tokens remains a deprecated compatibility path.", + "Anthropic API defaults effort to high; lower effort levels should be chosen per workload." ] } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-08-14", - "last_updated": "2026-08-14", + "attachment": true, + "open_weights": false, + "knowledge": "2025-08-31", + "release_date": "2026-02-17", + "last_updated": "2026-03-13", "type": "chat" }, { - "id": "google/gemma-4-31B-it", - "name": "Gemma 4 31B IT", - "display_name": "Gemma 4 31B IT", + "id": "anthropic/claude-opus-5", + "name": "Claude Opus 5", + "display_name": "Claude Opus 5", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 32768 + "context": 1000000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -37353,34 +39243,27 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "none", + "low", + "medium", + "high", + "xhigh", + "max" ] } }, "attachment": true, - "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-04-02", - "last_updated": "2026-04-02", + "open_weights": false, + "knowledge": "2026-05", + "release_date": "2026-07-24", + "last_updated": "2026-07-24", "type": "chat" - } - ] - }, - "infomaniak": { - "id": "infomaniak", - "name": "Infomaniak", - "display_name": "Infomaniak", - "api": "https://api.infomaniak.com/2/ai/${INFOMANIAK_PRODUCT_ID}/openai/v1", - "doc": "https://www.infomaniak.com/en/hosting/ai-services/open-source-models", - "models": [ + }, { - "id": "mini_lm_l12_v2", - "name": "All-MiniLM-L12-v2", - "display_name": "All-MiniLM-L12-v2", + "id": "amazon/titan-embed-text-v2", + "name": "Titan Text Embeddings V2", + "display_name": "Titan Text Embeddings V2", "modalities": { "input": [ "text" @@ -37390,8 +39273,8 @@ ] }, "limit": { - "context": 128, - "output": 384 + "context": 8192, + "output": 8192 }, "temperature": false, "tool_call": false, @@ -37399,135 +39282,129 @@ "supported": false }, "attachment": false, - "open_weights": true, - "release_date": "2021-08-30", - "last_updated": "2021-08-30", + "open_weights": false, + "release_date": "2024-04-30", + "last_updated": "2024-04", "type": "embedding" }, { - "id": "bge_multilingual_gemma2", - "name": "BGE Multilingual Gemma2", - "display_name": "BGE Multilingual Gemma2", + "id": "amazon/nova-2-lite", + "name": "Nova 2 Lite", + "display_name": "Nova 2 Lite", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 8000, - "output": 3584 + "context": 1000000, + "output": 65535 }, - "temperature": false, + "temperature": true, "tool_call": false, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, - "open_weights": true, - "release_date": "2024-07-25", - "last_updated": "2026-08-01", - "type": "embedding" + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "none", + "low", + "medium", + "high" + ] + } + }, + "attachment": true, + "open_weights": false, + "knowledge": "2025-10", + "release_date": "2025-12-02", + "last_updated": "2025-12-01", + "type": "chat" }, { - "id": "Qwen/Qwen3.5-397B-A17B-FP8", - "name": "Qwen3.5 397B-A17B FP8", - "display_name": "Qwen3.5 397B-A17B FP8", + "id": "amazon/nova-micro", + "name": "Nova Micro", + "display_name": "Nova Micro", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 65536 + "context": 128000, + "output": 10000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } + "supported": false }, - "attachment": true, - "open_weights": true, - "release_date": "2026-02-15", - "last_updated": "2026-02-15", + "attachment": false, + "open_weights": false, + "knowledge": "2024-10", + "release_date": "2024-12-03", + "last_updated": "2024-12-03", "type": "chat" }, { - "id": "Qwen/Qwen3.5-122B-A10B-FP8", - "name": "Qwen3.5 122B-A10B FP8", - "display_name": "Qwen3.5 122B-A10B FP8", + "id": "amazon/nova-pro", + "name": "Nova Pro", + "display_name": "Nova Pro", "modalities": { "input": [ "text", - "image" + "image", + "video", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 65536 + "context": 300000, + "output": 10000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } + "supported": false }, "attachment": true, - "open_weights": true, - "release_date": "2026-02-23", - "last_updated": "2026-02-23", + "open_weights": false, + "knowledge": "2024-10", + "release_date": "2024-12-03", + "last_updated": "2024-12-03", "type": "chat" }, { - "id": "swiss-ai/Apertus-v1.5-70B", - "name": "Apertus v1.5 70B", - "display_name": "Apertus v1.5 70B", + "id": "amazon/nova-lite", + "name": "Nova Lite", + "display_name": "Nova Lite", "modalities": { "input": [ "text", "image", - "audio" + "video", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 100000, - "output": 8192 + "context": 300000, + "output": 10000 }, "temperature": true, "tool_call": true, @@ -37535,15 +39412,16 @@ "supported": false }, "attachment": true, - "open_weights": true, - "release_date": "2026-07-24", - "last_updated": "2026-08-01", + "open_weights": false, + "knowledge": "2024-10", + "release_date": "2024-12-03", + "last_updated": "2024-12-03", "type": "chat" }, { - "id": "nvidia/NVIDIA-Nemotron-3-Nano-30B-A3B-FP8", - "name": "Nemotron 3 Nano 30B A3B FP8", - "display_name": "Nemotron 3 Nano 30B A3B FP8", + "id": "poolside/laguna-s-2.1", + "name": "Laguna S 2.1", + "display_name": "Laguna S 2.1", "modalities": { "input": [ "text" @@ -37554,7 +39432,7 @@ }, "limit": { "context": 1000000, - "output": 262144 + "output": 131072 }, "temperature": true, "tool_call": true, @@ -37562,23 +39440,16 @@ "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true - } - }, "attachment": false, "open_weights": true, - "knowledge": "2025-06-25", - "release_date": "2025-12-15", - "last_updated": "2025-12-15", + "release_date": "2026-07-21", + "last_updated": "2026-07-21", "type": "chat" }, { - "id": "google/gemma-4-31B-it", - "name": "Gemma 4 31B IT", - "display_name": "Gemma 4 31B IT", + "id": "inference-net/schematron-v2-turbo", + "name": "Schematron V2 Turbo", + "display_name": "Schematron V2 Turbo", "modalities": { "input": [ "text" @@ -37588,103 +39459,78 @@ ] }, "limit": { - "context": 100000, - "output": 32768 + "context": 128000, + "output": 8192 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true - } + "supported": false }, "attachment": false, - "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-04-02", - "last_updated": "2026-04-02", + "open_weights": false, + "release_date": "2026-04-16", + "last_updated": "2026-04-16", "type": "chat" }, { - "id": "moonshotai/Kimi-K2.6", - "name": "Kimi K2.6", - "display_name": "Kimi K2.6", + "id": "inference-net/schematron-v2-small", + "name": "Schematron V2 Small", + "display_name": "Schematron V2 Small", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 256000, - "output": 256000 + "context": 128000, + "output": 4096 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true - } + "supported": false }, - "attachment": true, - "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-04-20", - "last_updated": "2026-04-20", + "attachment": false, + "open_weights": false, + "release_date": "2026-04-16", + "last_updated": "2026-04-16", "type": "chat" }, { - "id": "mistralai/Mistral-Small-4-119B-2603", - "name": "Mistral Small 4", - "display_name": "Mistral Small 4", + "id": "perplexity/pplx-embed-v1-4b", + "name": "Embed v1 4b", + "display_name": "Embed v1 4b", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 256000, - "output": 256000 + "context": 32000, + "output": 8192 }, - "temperature": true, - "tool_call": true, + "temperature": false, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true - } + "supported": false }, - "attachment": true, - "open_weights": true, - "knowledge": "2025-06", - "release_date": "2026-03-16", - "last_updated": "2026-03-16", - "type": "chat" + "attachment": false, + "open_weights": false, + "release_date": "2026-02-26", + "last_updated": "2026-02-26", + "type": "embedding" }, { - "id": "mistralai/Ministral-3-14B-Instruct-2512", - "name": "Ministral 3 14B Instruct", - "display_name": "Ministral 3 14B Instruct", + "id": "perplexity/sonar", + "name": "Sonar", + "display_name": "Sonar", "modalities": { "input": [ "text", @@ -37695,8 +39541,8 @@ ] }, "limit": { - "context": 100000, - "output": 25600 + "context": 127000, + "output": 8000 }, "temperature": true, "tool_call": true, @@ -37704,197 +39550,132 @@ "supported": false }, "attachment": true, - "open_weights": true, - "release_date": "2025-12-02", - "last_updated": "2025-12-02", + "open_weights": false, + "knowledge": "2025-02", + "release_date": "2025-02-19", + "last_updated": "2025-02-19", "type": "chat" - } - ] - }, - "xiaomi-token-plan-sgp": { - "id": "xiaomi-token-plan-sgp", - "name": "Xiaomi Token Plan (Singapore)", - "display_name": "Xiaomi Token Plan (Singapore)", - "api": "https://token-plan-sgp.xiaomimimo.com/v1", - "doc": "https://platform.xiaomimimo.com/#/docs", - "models": [ + }, { - "id": "mimo-v2.6-pro", - "name": "MiMo-V2.6-Pro", - "display_name": "MiMo-V2.6-Pro", + "id": "perplexity/pplx-embed-v1-0.6b", + "name": "Embed v1 0.6b", + "display_name": "Embed v1 0.6b", "modalities": { "input": [ - "text", - "image", - "audio", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 32000, + "output": 8192 }, - "temperature": true, - "tool_call": true, + "temperature": false, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } + "supported": false }, - "attachment": true, - "open_weights": true, - "release_date": "2026-09-22", - "last_updated": "2026-09-22", - "type": "chat" + "attachment": false, + "open_weights": false, + "release_date": "2026-02-26", + "last_updated": "2026-02-26", + "type": "embedding" }, { - "id": "mimo-v2.6-flash", - "name": "MiMo-V2.6-Flash", - "display_name": "MiMo-V2.6-Flash", + "id": "klingai/kling-v3.0-motion-control", + "name": "Kling v3.0 Motion Control", + "display_name": "Kling v3.0 Motion Control", "modalities": { "input": [ "text", "image", - "audio", "video" ], "output": [ - "text" + "video" ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 8192, + "output": 8192 }, - "temperature": true, - "tool_call": true, + "temperature": false, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } + "supported": false }, "attachment": true, - "open_weights": true, - "release_date": "2026-09-22", - "last_updated": "2026-09-22", - "type": "chat" + "open_weights": false, + "release_date": "2026-03-04", + "last_updated": "2026-03-04" }, { - "id": "mimo-v2.5-pro", - "name": "MiMo-V2.5-Pro", - "display_name": "MiMo-V2.5-Pro", + "id": "klingai/kling-v3.0-i2v", + "name": "Kling v3.0 Image-to-Video", + "display_name": "Kling v3.0 Image-to-Video", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ - "text" + "video" ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 8192, + "output": 8192 }, - "temperature": true, - "tool_call": true, + "temperature": false, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } + "supported": false }, - "attachment": false, - "open_weights": true, - "knowledge": "2024-12", - "release_date": "2026-04-22", - "last_updated": "2026-04-22", - "type": "chat" + "attachment": true, + "open_weights": false, + "release_date": "2026-02-05", + "last_updated": "2026-02-05" }, { - "id": "mimo-v2.5", - "name": "MiMo-V2.5", - "display_name": "MiMo-V2.5", + "id": "klingai/kling-v2.6-i2v", + "name": "Kling v2.6 Image-to-Video", + "display_name": "Kling v2.6 Image-to-Video", "modalities": { "input": [ "text", - "image", - "audio", - "video" + "image" ], "output": [ - "text" + "video" ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 8192, + "output": 8192 }, - "temperature": true, - "tool_call": true, + "temperature": false, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } + "supported": false }, "attachment": true, - "open_weights": true, - "knowledge": "2024-12", - "release_date": "2026-04-22", - "last_updated": "2026-04-22", - "type": "chat" + "open_weights": false, + "release_date": "2025-12-03", + "last_updated": "2025-12-21" }, { - "id": "mimo-v2.5-tts", - "name": "MiMo-V2.5-TTS", - "display_name": "MiMo-V2.5-TTS", + "id": "klingai/kling-v2.5-turbo-i2v", + "name": "Kling v2.5 Turbo Image-to-Video", + "display_name": "Kling v2.5 Turbo Image-to-Video", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ - "audio" + "video" ] }, "limit": { @@ -37906,21 +39687,23 @@ "reasoning": { "supported": false }, - "attachment": false, + "attachment": true, "open_weights": false, - "release_date": "2026-04-22", - "last_updated": "2026-04-22" + "release_date": "2025-09-23", + "last_updated": "2025-09-23" }, { - "id": "mimo-v2.5-tts-voicedesign", - "name": "MiMo-V2.5-TTS-VoiceDesign", - "display_name": "MiMo-V2.5-TTS-VoiceDesign", + "id": "klingai/kling-v2.6-motion-control", + "name": "Kling v2.6 Motion Control", + "display_name": "Kling v2.6 Motion Control", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ - "audio" + "video" ] }, "limit": { @@ -37932,21 +39715,21 @@ "reasoning": { "supported": false }, - "attachment": false, + "attachment": true, "open_weights": false, - "release_date": "2026-04-22", - "last_updated": "2026-04-22" + "release_date": "2025-12-18", + "last_updated": "2025-12-21" }, { - "id": "mimo-v2.5-tts-voiceclone", - "name": "MiMo-V2.5-TTS-VoiceClone", - "display_name": "MiMo-V2.5-TTS-VoiceClone", + "id": "klingai/kling-v2.6-t2v", + "name": "Kling v2.6 Text-to-Video", + "display_name": "Kling v2.6 Text-to-Video", "modalities": { "input": [ "text" ], "output": [ - "audio" + "video" ] }, "limit": { @@ -37960,258 +39743,199 @@ }, "attachment": false, "open_weights": false, - "release_date": "2026-04-22", - "last_updated": "2026-04-22" - } - ] - }, - "aki-io": { - "id": "aki-io", - "name": "AKI.IO", - "display_name": "AKI.IO", - "api": "https://aki.io/v1", - "doc": "https://aki.io/docs/", - "models": [ + "release_date": "2025-12-03", + "last_updated": "2025-12-21" + }, { - "id": "deepseek-v4.1-flash-552b", - "name": "DeepSeek V4.1 Flash", - "display_name": "DeepSeek V4.1 Flash", + "id": "klingai/kling-v3.0-t2v", + "name": "Kling v3.0 Text-to-Video", + "display_name": "Kling v3.0 Text-to-Video", "modalities": { "input": [ "text" ], "output": [ - "text" + "video" ] }, "limit": { - "context": 1048576, - "output": 81920 + "context": 8192, + "output": 8192 }, - "temperature": true, - "tool_call": true, + "temperature": false, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "none", - "low", - "medium", - "high", - "max" - ] - } + "supported": false }, "attachment": false, - "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-09-10", - "last_updated": "2026-09-10", - "type": "chat" + "open_weights": false, + "release_date": "2026-02-05", + "last_updated": "2026-02-05" }, { - "id": "qwen3.8-27b", - "name": "Qwen3.8 27B", - "display_name": "Qwen3.8 27B", + "id": "klingai/kling-v2.5-turbo-t2v", + "name": "Kling v2.5 Turbo Text-to-Video", + "display_name": "Kling v2.5 Turbo Text-to-Video", "modalities": { "input": [ "text" ], "output": [ - "text" + "video" ] }, "limit": { - "context": 262144, - "output": 32768 + "context": 8192, + "output": 8192 }, - "temperature": true, - "tool_call": true, + "temperature": false, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "none", - "low", - "medium", - "xhigh" - ] - } + "supported": false }, "attachment": false, - "open_weights": true, - "release_date": "2026-08-14", - "last_updated": "2026-08-14", - "type": "chat" + "open_weights": false, + "release_date": "2025-09-23", + "last_updated": "2025-09-23" }, { - "id": "glm5.3-754b", - "name": "GLM-5.3", - "display_name": "GLM-5.3", + "id": "fish-audio/s2.1-pro", + "name": "S2.1 Pro", + "display_name": "S2.1 Pro", "modalities": { "input": [ "text" ], "output": [ - "text" + "audio" ] }, "limit": { - "context": 524288, - "output": 81920 + "context": 8192, + "output": 8192 }, - "temperature": true, - "tool_call": true, + "temperature": false, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "low", - "high", - "max" - ] - } + "supported": false }, "attachment": false, - "open_weights": true, - "release_date": "2026-08-14", - "last_updated": "2026-08-14", - "type": "chat" + "open_weights": false, + "release_date": "2026-07-28", + "last_updated": "2026-07-28" }, { - "id": "gpt-oss-120b", - "name": "GPT OSS 120B", - "display_name": "GPT OSS 120B", + "id": "fish-audio/s1", + "name": "S1", + "display_name": "S1", "modalities": { "input": [ "text" ], "output": [ - "text" + "audio" ] }, "limit": { - "context": 128000, - "output": 32768 + "context": 8192, + "output": 8192 }, - "temperature": true, - "tool_call": true, + "temperature": false, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "effort_options": [ - "low", - "medium", - "high" - ] - } + "supported": false }, "attachment": false, - "open_weights": true, - "knowledge": "2024-06", - "release_date": "2025-08-05", - "last_updated": "2025-08-05", - "type": "chat" + "open_weights": false, + "release_date": "2025-10-20", + "last_updated": "2025-10-20" }, { - "id": "gemma4-26b", - "name": "Gemma 4 26B A4B IT", - "display_name": "Gemma 4 26B A4B IT", + "id": "fish-audio/s2-pro", + "name": "S2 Pro", + "display_name": "S2 Pro", "modalities": { "input": [ "text" ], "output": [ - "text" + "audio" ] }, "limit": { - "context": 256000, - "output": 32768 + "context": 8192, + "output": 8192 }, - "temperature": true, - "tool_call": true, + "temperature": false, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "none", - "high" - ] - } + "supported": false }, "attachment": false, - "open_weights": true, - "release_date": "2026-04-02", - "last_updated": "2026-04-02", - "type": "chat" + "open_weights": false, + "release_date": "2026-03-09", + "last_updated": "2026-03-09" }, { - "id": "qwen3.6-35b", - "name": "Qwen3.6 35B-A3B", - "display_name": "Qwen3.6 35B-A3B", + "id": "fish-audio/transcribe-1", + "name": "Transcribe-1", + "display_name": "Transcribe-1", "modalities": { "input": [ - "text" + "audio" ], "output": [ "text" ] }, "limit": { - "context": 256000, - "output": 32768 + "context": 8192, + "output": 8192 }, - "temperature": true, - "tool_call": true, + "temperature": false, + "tool_call": false, "reasoning": { - "supported": true, - "default": true + "supported": false }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "effort_options": [ - "none", - "high" - ] - } + "attachment": true, + "open_weights": false, + "release_date": "2026-03-01", + "last_updated": "2026-03-01" + }, + { + "id": "fish-audio/drama-3", + "name": "Drama 3", + "display_name": "Drama 3", + "modalities": { + "input": [ + "text" + ], + "output": [ + "audio" + ] + }, + "limit": { + "context": 8192, + "output": 8192 + }, + "temperature": false, + "tool_call": false, + "reasoning": { + "supported": false }, "attachment": false, - "open_weights": true, - "release_date": "2026-04-17", - "last_updated": "2026-04-17", - "type": "chat" + "open_weights": false, + "release_date": "2026-10-13", + "last_updated": "2026-10-13" }, { - "id": "mistral4-119b", - "name": "Mistral Small 4", - "display_name": "Mistral Small 4", + "id": "callstack/apex", + "name": "Apex", + "display_name": "Apex", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" @@ -38219,9 +39943,8 @@ }, "limit": { "context": 262144, - "output": 81920 + "output": 131072 }, - "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -38231,307 +39954,383 @@ "reasoning": { "effort_options": [ "none", - "high" + "low", + "medium", + "xhigh" ] } }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-06", - "release_date": "2026-03-16", - "last_updated": "2026-03-16", + "attachment": true, + "open_weights": false, + "release_date": "2026-10-08", + "last_updated": "2026-10-08", "type": "chat" - } - ] - }, - "wally": { - "id": "wally", - "name": "Wally", - "display_name": "Wally", - "api": "https://inference.runanywhere.ai/v1", - "doc": "https://docs.runanywhere.ai/wally-cloud/api", - "models": [ + }, { - "id": "deepseek-v4-pro-fast", - "name": "DeepSeek V4 Pro Fast", - "display_name": "DeepSeek V4 Pro Fast", + "id": "bfl/flux-pro-1.1-ultra", + "name": "FLUX1.1 [pro] Ultra", + "display_name": "FLUX1.1 [pro] Ultra", "modalities": { "input": [ "text" ], "output": [ - "text" + "image" ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 512, + "output": 8192 }, - "temperature": true, - "tool_call": true, + "temperature": false, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "effort_options": [ - "none", - "high", - "max" - ] - } + "supported": false }, "attachment": false, - "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-04-24", - "last_updated": "2026-04-24", - "type": "chat" + "open_weights": false, + "release_date": "2024-11-01", + "last_updated": "2024-11" }, { - "id": "glm-5.3-fast", - "name": "GLM-5.3 Fast", - "display_name": "GLM-5.3 Fast", + "id": "bfl/flux-3-video", + "name": "Flux 3", + "display_name": "Flux 3", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ - "text" + "video" ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 8192, + "output": 8192 }, - "temperature": true, - "tool_call": true, + "temperature": false, + "tool_call": false, "reasoning": { - "supported": true, - "default": true + "supported": false }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "effort_options": [ - "low", - "high", - "max" - ] - } + "attachment": true, + "open_weights": false, + "release_date": "2026-08-04", + "last_updated": "2026-08-04" + }, + { + "id": "bfl/flux-kontext-pro", + "name": "FLUX.1 Kontext Pro", + "display_name": "FLUX.1 Kontext Pro", + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "image" + ] }, - "attachment": false, - "open_weights": true, - "release_date": "2026-08-14", - "last_updated": "2026-08-14", - "type": "chat" + "limit": { + "context": 512, + "output": 8192 + }, + "temperature": false, + "tool_call": false, + "reasoning": { + "supported": false + }, + "attachment": true, + "open_weights": false, + "release_date": "2025-05-29", + "last_updated": "2025-06" }, { - "id": "glm-5.3-flash", - "name": "GLM-5.3-Flash", - "display_name": "GLM-5.3-Flash", + "id": "bfl/flux-3-image", + "name": "FLUX 3 Image", + "display_name": "FLUX 3 Image", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ - "text" + "image" ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 8192, + "output": 8192 }, - "temperature": true, - "tool_call": true, + "temperature": false, + "tool_call": false, "reasoning": { - "supported": true, - "default": true + "supported": false }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "effort_options": [ - "low", - "high", - "max" - ] - } + "attachment": true, + "open_weights": false, + "release_date": "2026-10-05", + "last_updated": "2026-10-05" + }, + { + "id": "bfl/flux-kontext-max", + "name": "FLUX.1 Kontext Max", + "display_name": "FLUX.1 Kontext Max", + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "image" + ] }, - "attachment": false, - "open_weights": true, - "release_date": "2026-08-26", - "last_updated": "2026-08-26", - "type": "chat" + "limit": { + "context": 512, + "output": 8192 + }, + "temperature": false, + "tool_call": false, + "reasoning": { + "supported": false + }, + "attachment": true, + "open_weights": false, + "release_date": "2025-05-29", + "last_updated": "2025-06" }, { - "id": "kimi-k3", - "name": "Kimi K3", - "display_name": "Kimi K3", + "id": "bfl/flux-2-klein-9b", + "name": "FLUX.2 [klein] 9B", + "display_name": "FLUX.2 [klein] 9B", "modalities": { "input": [ "text", "image" ], "output": [ - "text" + "image" ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 8192, + "output": 8192 }, - "temperature": true, - "tool_call": true, + "temperature": false, + "tool_call": false, "reasoning": { - "supported": true, - "default": true + "supported": false }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "effort_options": [ - "none", - "low", - "high", - "max" - ] - } + "attachment": true, + "open_weights": false, + "release_date": "2026-01-15", + "last_updated": "2026-06-08" + }, + { + "id": "bfl/flux-2-max", + "name": "FLUX.2 [max]", + "display_name": "FLUX.2 [max]", + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "image" + ] + }, + "limit": { + "context": 67300, + "output": 67300 + }, + "temperature": false, + "tool_call": false, + "reasoning": { + "supported": false }, "attachment": true, - "open_weights": true, - "release_date": "2026-07-16", - "last_updated": "2026-07-16", - "type": "chat" + "open_weights": false, + "release_date": "2025-12-16", + "last_updated": "2026-06-08" }, { - "id": "deepseek-v4.1-flash-fast", - "name": "DeepSeek V4.1 Flash Fast", - "display_name": "DeepSeek V4.1 Flash Fast", + "id": "bfl/flux-pro-1.0-fill", + "name": "FLUX.1 Fill [pro]", + "display_name": "FLUX.1 Fill [pro]", "modalities": { "input": [ "text", "image" ], "output": [ + "image" + ] + }, + "limit": { + "context": 512, + "output": 8192 + }, + "temperature": false, + "tool_call": false, + "reasoning": { + "supported": false + }, + "attachment": true, + "open_weights": false, + "release_date": "2024-10-01", + "last_updated": "2024-10" + }, + { + "id": "bfl/flux-pro-1.1", + "name": "FLUX1.1 [pro]", + "display_name": "FLUX1.1 [pro]", + "modalities": { + "input": [ "text" + ], + "output": [ + "image" ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 512, + "output": 8192 }, - "temperature": true, - "tool_call": true, + "temperature": false, + "tool_call": false, "reasoning": { - "supported": true, - "default": true + "supported": false }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "effort_options": [ - "none", - "low", - "high", - "max" - ] - } + "attachment": false, + "open_weights": false, + "release_date": "2024-10-02", + "last_updated": "2024-10" + }, + { + "id": "bfl/flux-2-pro", + "name": "FLUX.2 [pro]", + "display_name": "FLUX.2 [pro]", + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "image" + ] + }, + "limit": { + "context": 67300, + "output": 67300 + }, + "temperature": false, + "tool_call": false, + "reasoning": { + "supported": false }, "attachment": true, - "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-09-10", - "last_updated": "2026-09-10", - "type": "chat" + "open_weights": false, + "release_date": "2025-11-25", + "last_updated": "2026-06-08" }, { - "id": "minimax-m3", - "name": "MiniMax-M3", - "display_name": "MiniMax-M3", + "id": "bfl/flux-2-flex", + "name": "FLUX.2 [flex]", + "display_name": "FLUX.2 [flex]", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ - "text" + "image" ] }, "limit": { - "context": 524287, - "output": 131072 + "context": 8192, + "output": 8192 }, - "temperature": true, - "tool_call": true, + "temperature": false, + "tool_call": false, "reasoning": { - "supported": true, - "default": true + "supported": false }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } + "attachment": true, + "open_weights": false, + "release_date": "2025-11-25", + "last_updated": "2026-06-08" + }, + { + "id": "bfl/flux-3-image-klein", + "name": "FLUX 3 Image Klein", + "display_name": "FLUX 3 Image Klein", + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "image" + ] }, - "attachment": false, - "open_weights": true, - "release_date": "2026-06-01", - "last_updated": "2026-06-01", - "type": "chat" + "limit": { + "context": 8192, + "output": 8192 + }, + "temperature": false, + "tool_call": false, + "reasoning": { + "supported": false + }, + "attachment": true, + "open_weights": false, + "release_date": "2026-10-15", + "last_updated": "2026-10-15" }, { - "id": "deepseek-v4.1-flash", - "name": "DeepSeek V4.1 Flash", - "display_name": "DeepSeek V4.1 Flash", + "id": "bfl/flux-2-klein-4b", + "name": "FLUX.2 [klein] 4B", + "display_name": "FLUX.2 [klein] 4B", "modalities": { "input": [ "text", "image" ], + "output": [ + "image" + ] + }, + "limit": { + "context": 8192, + "output": 8192 + }, + "temperature": false, + "tool_call": false, + "reasoning": { + "supported": false + }, + "attachment": true, + "open_weights": false, + "release_date": "2026-01-15", + "last_updated": "2026-06-08" + }, + { + "id": "deepseek/deepseek-v3.2-thinking", + "name": "DeepSeek V3.2 Thinking", + "display_name": "DeepSeek V3.2 Thinking", + "modalities": { + "input": [ + "text" + ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 128000, + "output": 8000 }, "temperature": true, "tool_call": true, @@ -38543,33 +40342,29 @@ "reasoning": { "supported": true, "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], "effort_options": [ "none", "low", - "high", - "max" + "medium", + "high" ] } }, - "attachment": true, + "attachment": false, "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-09-10", - "last_updated": "2026-09-10", + "knowledge": "2024-07", + "release_date": "2025-12-01", + "last_updated": "2025-12-01", "type": "chat" }, { - "id": "deepseek-v4-flash-0731", - "name": "DeepSeek V4 Flash 0731", - "display_name": "DeepSeek V4 Flash 0731", + "id": "deepseek/deepseek-v4.1-flash-1017-fast", + "name": "DeepSeek V4.1 Flash 1017 Fast", + "display_name": "DeepSeek V4.1 Flash 1017 Fast", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" @@ -38577,7 +40372,7 @@ }, "limit": { "context": 1048576, - "output": 65536 + "output": 262144 }, "temperature": true, "tool_call": true, @@ -38587,41 +40382,25 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], "effort_options": [ "none", "low", - "high", - "max" + "medium", + "high" ] } }, - "attachment": false, + "attachment": true, "open_weights": true, "knowledge": "2025-05", - "release_date": "2026-07-31", - "last_updated": "2026-07-31", + "release_date": "2026-10-17", + "last_updated": "2026-10-17", "type": "chat" - } - ] - }, - "tokengo": { - "id": "tokengo", - "name": "TokenGo", - "display_name": "TokenGo", - "api": "https://api.tokengo.com/v1", - "doc": "https://www.tokengo.com/docs", - "models": [ + }, { - "id": "minimax/minimax-m2.5", - "name": "MiniMax-M2.5", - "display_name": "MiniMax-M2.5", + "id": "deepseek/deepseek-v3.1", + "name": "DeepSeek-V3.1", + "display_name": "DeepSeek-V3.1", "modalities": { "input": [ "text" @@ -38631,8 +40410,8 @@ ] }, "limit": { - "context": 204800, - "output": 131072 + "context": 163840, + "output": 128000 }, "temperature": true, "tool_call": true, @@ -38642,33 +40421,36 @@ }, "extra_capabilities": { "reasoning": { - "supported": true + "effort_options": [ + "none", + "low", + "medium", + "high" + ] } }, "attachment": false, "open_weights": true, - "release_date": "2026-02-12", - "last_updated": "2026-02-12", + "release_date": "2025-08-21", + "last_updated": "2025-08-21", "type": "chat" }, { - "id": "z-ai/glm-5.3-flash", - "name": "GLM-5.3-Flash", - "display_name": "GLM-5.3-Flash", + "id": "deepseek/deepseek-v4.1-flash-1017", + "name": "DeepSeek V4.1 Flash 1017", + "display_name": "DeepSeek V4.1 Flash 1017", "modalities": { "input": [ "text", - "image", - "video", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 1048576, + "output": 262144 }, "temperature": true, "tool_call": true, @@ -38678,8 +40460,8 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, "effort_options": [ + "none", "low", "high", "max" @@ -38688,14 +40470,15 @@ }, "attachment": true, "open_weights": true, - "release_date": "2026-08-26", - "last_updated": "2026-08-26", + "knowledge": "2025-05", + "release_date": "2026-10-17", + "last_updated": "2026-10-17", "type": "chat" }, { - "id": "z-ai/glm-5.1", - "name": "GLM-5.1", - "display_name": "GLM-5.1", + "id": "deepseek/deepseek-v3.2", + "name": "DeepSeek V3.2", + "display_name": "DeepSeek V3.2", "modalities": { "input": [ "text" @@ -38705,14 +40488,13 @@ ] }, "limit": { - "context": 200000, - "output": 131072 + "context": 128000, + "output": 8000 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true + "supported": true }, "extra_capabilities": { "reasoning": { @@ -38721,25 +40503,27 @@ }, "attachment": false, "open_weights": true, - "release_date": "2026-04-07", - "last_updated": "2026-04-07", + "knowledge": "2024-07", + "release_date": "2025-12-01", + "last_updated": "2025-12-01", "type": "chat" }, { - "id": "z-ai/glm-5", - "name": "GLM-5", - "display_name": "GLM-5", + "id": "deepseek/deepseek-v4.1-flash-fast", + "name": "DeepSeek V4.1 Flash Fast", + "display_name": "DeepSeek V4.1 Flash Fast", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 204800, - "output": 131072 + "context": 1048576, + "output": 32768 }, "temperature": true, "tool_call": true, @@ -38749,36 +40533,38 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "none", + "low", + "medium", + "high" ] } }, - "attachment": false, + "attachment": true, "open_weights": true, - "release_date": "2026-02-12", - "last_updated": "2026-02-12", + "knowledge": "2025-05", + "release_date": "2026-09-10", + "last_updated": "2026-09-10", "type": "chat" }, { - "id": "z-ai/glm-5.3", - "name": "GLM-5.3", - "display_name": "GLM-5.3", + "id": "deepseek/deepseek-v4.1-flash", + "name": "DeepSeek V4.1 Flash", + "display_name": "DeepSeek V4.1 Flash", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 1048576, + "output": 32768 }, "temperature": true, "tool_call": true, @@ -38790,33 +40576,36 @@ "reasoning": { "supported": true, "effort_options": [ + "none", "low", "high", "max" ] } }, - "attachment": false, + "attachment": true, "open_weights": true, - "release_date": "2026-08-14", - "last_updated": "2026-08-14", + "knowledge": "2025-05", + "release_date": "2026-09-10", + "last_updated": "2026-09-10", "type": "chat" }, { - "id": "z-ai/glm-5.2", - "name": "GLM-5.2", - "display_name": "GLM-5.2", + "id": "deepseek/deepseek-v4-flash-vision-exp", + "name": "DeepSeek V4 Flash Vision Exp", + "display_name": "DeepSeek V4 Flash Vision Exp", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 1048576, + "output": 1048576 }, "temperature": true, "tool_call": true, @@ -38827,22 +40616,35 @@ "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "high", "effort_options": [ + "low", "high", "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." ] } }, - "attachment": false, + "attachment": true, "open_weights": true, - "release_date": "2026-06-13", - "last_updated": "2026-06-13", + "release_date": "2026-08-21", + "last_updated": "2026-09-01", "type": "chat" }, { - "id": "deepseek/deepseek-v4-flash", - "name": "DeepSeek V4 Flash", - "display_name": "DeepSeek V4 Flash", + "id": "deepseek/deepseek-v4-pro", + "name": "DeepSeek V4 Pro", + "display_name": "DeepSeek V4 Pro", "modalities": { "input": [ "text" @@ -38891,9 +40693,9 @@ "type": "chat" }, { - "id": "deepseek/deepseek-v4-pro", - "name": "DeepSeek V4 Pro", - "display_name": "DeepSeek V4 Pro", + "id": "deepseek/deepseek-v4-flash", + "name": "DeepSeek V4 Flash", + "display_name": "DeepSeek V4 Flash", "modalities": { "input": [ "text" @@ -38942,9 +40744,9 @@ "type": "chat" }, { - "id": "deepseek/deepseek-v3.1", - "name": "DeepSeek-V3.1", - "display_name": "DeepSeek-V3.1", + "id": "deepseek/deepseek-v4-pro-0813", + "name": "DeepSeek V4 Pro 0813", + "display_name": "DeepSeek V4 Pro 0813", "modalities": { "input": [ "text" @@ -38954,8 +40756,8 @@ ] }, "limit": { - "context": 131072, - "output": 8192 + "context": 1000000, + "output": 384000 }, "temperature": true, "tool_call": true, @@ -38963,16 +40765,27 @@ "supported": true, "default": true }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "effort_options": [ + "none", + "low", + "high", + "max" + ] + } + }, "attachment": false, "open_weights": true, - "release_date": "2025-08-21", - "last_updated": "2025-08-21", + "release_date": "2026-08-13", + "last_updated": "2026-08-22", "type": "chat" }, { - "id": "deepseek/deepseek-v3.2", - "name": "DeepSeek V3.2", - "display_name": "DeepSeek V3.2", + "id": "deepseek/deepseek-v4-flash-0731", + "name": "DeepSeek V4 Flash 0731", + "display_name": "DeepSeek V4 Flash 0731", "modalities": { "input": [ "text" @@ -38982,8 +40795,8 @@ ] }, "limit": { - "context": 128000, - "output": 64000 + "context": 1000000, + "output": 384000 }, "temperature": true, "tool_call": true, @@ -38993,35 +40806,80 @@ }, "extra_capabilities": { "reasoning": { - "supported": true + "supported": true, + "effort_options": [ + "none", + "low", + "medium", + "high" + ] + } + }, + "attachment": false, + "open_weights": true, + "knowledge": "2025-05", + "release_date": "2026-07-31", + "last_updated": "2026-07-31", + "type": "chat" + }, + { + "id": "deepseek/deepseek-r1", + "name": "DeepSeek-R1", + "display_name": "DeepSeek-R1", + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 32768 + }, + "temperature": true, + "tool_call": false, + "reasoning": { + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] } }, "attachment": false, "open_weights": true, "knowledge": "2024-07", - "release_date": "2025-12-01", - "last_updated": "2025-12-01", + "release_date": "2025-01-20", + "last_updated": "2025-05-29", "type": "chat" }, { - "id": "moonshotai/kimi-k3", - "name": "Kimi K3", - "display_name": "Kimi K3", + "id": "zai/glm-5.3-flashx", + "name": "GLM 5.3 FlashX", + "display_name": "GLM 5.3 FlashX", "modalities": { "input": [ "text", - "image", - "video" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1048576, + "context": 1000000, "output": 131072 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -39029,7 +40887,6 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, "effort_options": [ "low", "high", @@ -39039,29 +40896,27 @@ }, "attachment": true, "open_weights": true, - "release_date": "2026-07-16", - "last_updated": "2026-07-16", + "release_date": "2026-08-26", + "last_updated": "2026-08-26", "type": "chat" }, { - "id": "moonshotai/kimi-k2.6", - "name": "Kimi K2.6", - "display_name": "Kimi K2.6", + "id": "zai/glm-5.3-flashx-legal", + "name": "GLM 5.3 FlashX Legal", + "display_name": "GLM 5.3 FlashX Legal", "modalities": { "input": [ "text", - "image", - "video" + "image" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 1000000, + "output": 131072 }, - "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -39069,33 +40924,35 @@ }, "extra_capabilities": { "reasoning": { - "supported": true + "effort_options": [ + "low", + "high", + "max" + ] } }, "attachment": true, - "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-04-20", - "last_updated": "2026-04-20", + "open_weights": false, + "release_date": "2026-11-02", + "last_updated": "2026-11-02", "type": "chat" }, { - "id": "qwen/qwen3.5-397b-a17b", - "name": "Qwen3.5 397B-A17B", - "display_name": "Qwen3.5 397B-A17B", + "id": "zai/glm-5.3-flash", + "name": "GLM 5.3 Flash", + "display_name": "GLM 5.3 Flash", "modalities": { "input": [ "text", - "image", - "video" + "image" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 65536 + "context": 1000000, + "output": 131000 }, "temperature": true, "tool_call": true, @@ -39106,33 +40963,23 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "low", + "high", + "max" ] } }, "attachment": true, "open_weights": true, - "release_date": "2026-02-15", - "last_updated": "2026-02-15", + "release_date": "2026-08-26", + "last_updated": "2026-08-26", "type": "chat" - } - ] - }, - "jalapeno": { - "id": "jalapeno", - "name": "Jalapeno Cloud", - "display_name": "Jalapeno Cloud", - "api": "https://api.jalapeno-cloud.ai/v1", - "doc": "https://www.jalapeno-cloud.ai/docs/", - "models": [ + }, { - "id": "Hy3", - "name": "Hy3", - "display_name": "Hy3", + "id": "zai/glm-4.7", + "name": "GLM 4.7", + "display_name": "GLM 4.7", "modalities": { "input": [ "text" @@ -39142,8 +40989,8 @@ ] }, "limit": { - "context": 202752, - "output": 128000 + "context": 200000, + "output": 120000 }, "temperature": true, "tool_call": true, @@ -39159,32 +41006,37 @@ "visibility": "summary", "continuation": [ "thinking_blocks" + ], + "effort_options": [ + "none", + "low", + "medium", + "high" ] } }, "attachment": false, "open_weights": true, - "release_date": "2026-07-06", - "last_updated": "2026-07-06", + "knowledge": "2025-04", + "release_date": "2025-12-22", + "last_updated": "2025-12-22", "type": "chat" }, { - "id": "Qwen3.5-397B-A17B", - "name": "Qwen3.5 397B-A17B", - "display_name": "Qwen3.5 397B-A17B", + "id": "zai/glm-4.6", + "name": "GLM 4.6", + "display_name": "GLM 4.6", "modalities": { "input": [ - "text", - "image", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 65536 + "context": 200000, + "output": 96000 }, "temperature": true, "tool_call": true, @@ -39195,24 +41047,20 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] + "interleaved": true } }, - "attachment": true, + "attachment": false, "open_weights": true, - "release_date": "2026-02-15", - "last_updated": "2026-02-15", + "knowledge": "2025-04", + "release_date": "2025-09-30", + "last_updated": "2025-09-30", "type": "chat" }, { - "id": "Qwen3-Next-80B-A3B-Instruct", - "name": "Qwen3-Next 80B-A3B Instruct", - "display_name": "Qwen3-Next 80B-A3B Instruct", + "id": "zai/glm-4.5-air", + "name": "GLM 4.5 Air", + "display_name": "GLM 4.5 Air", "modalities": { "input": [ "text" @@ -39222,40 +41070,39 @@ ] }, "limit": { - "context": 129024, - "output": 32768 + "context": 128000, + "output": 96000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, "attachment": false, "open_weights": true, "knowledge": "2025-04", - "release_date": "2025-09", - "last_updated": "2025-09", + "release_date": "2025-07-28", + "last_updated": "2025-07-28", "type": "chat" }, { - "id": "Kimi-K2.5", - "name": "Kimi K2.5", - "display_name": "Kimi K2.5", + "id": "zai/glm-5.3", + "name": "GLM 5.3", + "display_name": "GLM 5.3", "modalities": { "input": [ - "text", - "image", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 180224 + "context": 1000000, + "output": 1000000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -39264,25 +41111,53 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "low", + "high", + "max" ] } }, + "attachment": false, + "open_weights": true, + "release_date": "2026-08-14", + "last_updated": "2026-08-14", + "type": "chat" + }, + { + "id": "zai/glm-4.5v", + "name": "GLM 4.5V", + "display_name": "GLM 4.5V", + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 66000, + "output": 16000 + }, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": true, + "default": true + }, "attachment": true, "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-01-27", - "last_updated": "2026-01-27", + "knowledge": "2025-04", + "release_date": "2025-08-11", + "last_updated": "2025-08-11", "type": "chat" }, { - "id": "GLM-5.1", - "name": "GLM-5.1", - "display_name": "GLM-5.1", + "id": "zai/glm-5", + "name": "GLM-5", + "display_name": "GLM-5", "modalities": { "input": [ "text" @@ -39292,8 +41167,8 @@ ] }, "limit": { - "context": 202752, - "output": 131072 + "context": 202800, + "output": 131100 }, "temperature": true, "tool_call": true, @@ -39309,34 +41184,38 @@ "visibility": "summary", "continuation": [ "thinking_blocks" + ], + "effort_options": [ + "none", + "low", + "medium", + "high" ] } }, "attachment": false, "open_weights": true, - "release_date": "2026-04-07", - "last_updated": "2026-04-07", + "release_date": "2026-02-12", + "last_updated": "2026-02-12", "type": "chat" }, { - "id": "Kimi-K2.7-Code", - "name": "Kimi K2.7 Code", - "display_name": "Kimi K2.7 Code", + "id": "zai/glm-5.3-fast", + "name": "GLM 5.3 Fast", + "display_name": "GLM 5.3 Fast", "modalities": { "input": [ - "text", - "image", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 271360, + "context": 1048576, "output": 262144 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -39344,39 +41223,35 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "none", + "low", + "medium", + "high" ] } }, - "attachment": true, + "attachment": false, "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-06-12", - "last_updated": "2026-06-12", + "release_date": "2026-08-14", + "last_updated": "2026-08-14", "type": "chat" }, { - "id": "Qwen3.5-27B", - "name": "Qwen3.5 27B", - "display_name": "Qwen3.5 27B", + "id": "zai/glm-4.7-flashx", + "name": "GLM 4.7 FlashX", + "display_name": "GLM 4.7 FlashX", "modalities": { "input": [ - "text", - "image", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 65536 + "context": 200000, + "output": 128000 }, "temperature": true, "tool_call": true, @@ -39388,23 +41263,25 @@ "reasoning": { "supported": true, "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "none", + "low", + "medium", + "high" ] } }, - "attachment": true, + "attachment": false, "open_weights": true, - "release_date": "2026-02-23", - "last_updated": "2026-02-23", + "knowledge": "2025-04", + "release_date": "2026-01-19", + "last_updated": "2026-01-19", "type": "chat" }, { - "id": "Qwen3-Next-80B-A3B-Thinking", - "name": "Qwen3-Next 80B-A3B (Thinking)", - "display_name": "Qwen3-Next 80B-A3B (Thinking)", + "id": "zai/glm-4.7-flash", + "name": "GLM 4.7 Flash", + "display_name": "GLM 4.7 Flash", "modalities": { "input": [ "text" @@ -39414,8 +41291,8 @@ ] }, "limit": { - "context": 262144, - "output": 32768 + "context": 200000, + "output": 131000 }, "temperature": true, "tool_call": true, @@ -39426,38 +41303,36 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "none", + "low", + "medium", + "high" ] } }, "attachment": false, "open_weights": true, "knowledge": "2025-04", - "release_date": "2025-09", - "last_updated": "2025-09", + "release_date": "2026-01-19", + "last_updated": "2026-01-19", "type": "chat" }, { - "id": "Qwen3.5-122B-A10B", - "name": "Qwen3.5 122B-A10B", - "display_name": "Qwen3.5 122B-A10B", + "id": "zai/glm-5.1", + "name": "GLM 5.1", + "display_name": "GLM 5.1", "modalities": { "input": [ - "text", - "image", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 65536 + "context": 202800, + "output": 64000 }, "temperature": true, "tool_call": true, @@ -39468,37 +41343,37 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "none", + "low", + "medium", + "high" ] } }, - "attachment": true, + "attachment": false, "open_weights": true, - "release_date": "2026-02-23", - "last_updated": "2026-02-23", + "release_date": "2026-04-07", + "last_updated": "2026-04-07", "type": "chat" }, { - "id": "MiniMax-M3", - "name": "MiniMax-M3", - "display_name": "MiniMax-M3", + "id": "zai/glm-5v-turbo", + "name": "GLM 5V Turbo", + "display_name": "GLM 5V Turbo", "modalities": { "input": [ "text", "image", - "video" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 524288, - "output": 512000 + "context": 200000, + "output": 128000 }, "temperature": true, "tool_call": true, @@ -39508,25 +41383,24 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "none", + "low", + "medium", + "high" ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-06-01", - "last_updated": "2026-06-01", + "open_weights": false, + "release_date": "2026-04-01", + "last_updated": "2026-04-01", "type": "chat" }, { - "id": "DeepSeek-V4-Pro", - "name": "DeepSeek V4 Pro", - "display_name": "DeepSeek V4 Pro", + "id": "zai/glm-5.2", + "name": "GLM 5.2", + "display_name": "GLM 5.2", "modalities": { "input": [ "text" @@ -39536,8 +41410,8 @@ ] }, "limit": { - "context": 1048576, - "output": 384000 + "context": 1000000, + "output": 128000 }, "temperature": true, "tool_call": true, @@ -39548,77 +41422,73 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "high", "effort_options": [ - "low", + "none", "high", "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." ] } }, "attachment": false, "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-04-24", - "last_updated": "2026-04-24", + "release_date": "2026-06-13", + "last_updated": "2026-06-13", "type": "chat" }, { - "id": "Qwen3-VL-235B-A22B-Instruct", - "name": "Qwen3 VL 235B A22B Instruct", - "display_name": "Qwen3 VL 235B A22B Instruct", + "id": "zai/glm-5-turbo", + "name": "GLM 5 Turbo", + "display_name": "GLM 5 Turbo", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 129024, - "output": 32768 + "context": 202800, + "output": 131072 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": true, - "open_weights": true, - "knowledge": "2025-03-31", - "release_date": "2025-09-23", - "last_updated": "2025-09-23", + "extra_capabilities": { + "reasoning": { + "supported": true, + "effort_options": [ + "none", + "low", + "medium", + "high" + ] + } + }, + "attachment": false, + "open_weights": false, + "release_date": "2026-03-16", + "last_updated": "2026-03-16", "type": "chat" }, { - "id": "Qwen3-VL-235B-A22B-Thinking", - "name": "Qwen3 VL 235B A22B Thinking", - "display_name": "Qwen3 VL 235B A22B Thinking", + "id": "zai/glm-4.5", + "name": "GLM 4.5", + "display_name": "GLM 4.5", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 32768 + "context": 128000, + "output": 96000 }, "temperature": true, "tool_call": true, @@ -39629,38 +41499,31 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] + "interleaved": true } }, - "attachment": true, + "attachment": false, "open_weights": true, - "knowledge": "2025-03-31", - "release_date": "2025-09-23", - "last_updated": "2025-09-23", + "knowledge": "2025-04", + "release_date": "2025-07-28", + "last_updated": "2025-07-28", "type": "chat" }, { - "id": "Qwen3.5-35B-A3B", - "name": "Qwen3.5 35B-A3B", - "display_name": "Qwen3.5 35B-A3B", + "id": "zai/glm-5.2-fast", + "name": "GLM 5.2 Fast", + "display_name": "GLM 5.2 Fast", "modalities": { "input": [ - "text", - "image", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 65536 + "context": 1000000, + "output": 128000 }, "temperature": true, "tool_call": true, @@ -39670,69 +41533,65 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "none", + "low", + "medium", + "high" ] } }, - "attachment": true, + "attachment": false, "open_weights": true, - "release_date": "2026-02-23", - "last_updated": "2026-02-23", + "release_date": "2026-06-13", + "last_updated": "2026-06-13", "type": "chat" }, { - "id": "Kimi-K3", - "name": "Kimi K3", - "display_name": "Kimi K3", + "id": "elorian/glimpse", + "name": "Glimpse", + "display_name": "Glimpse", "modalities": { "input": [ "text", - "image", - "video" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 220000, + "output": 4096 }, - "temperature": false, - "tool_call": true, + "tool_call": false, "reasoning": { "supported": true, "default": true }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "low", + "medium", + "xhigh" ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-07-16", - "last_updated": "2026-07-16", + "open_weights": false, + "release_date": "2026-10-16", + "last_updated": "2026-10-16", "type": "chat" }, { - "id": "DeepSeek-V4-Flash", - "name": "DeepSeek V4 Flash", - "display_name": "DeepSeek V4 Flash", + "id": "particle/particle-1", + "name": "Particle-1", + "display_name": "Particle-1", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" @@ -39740,9 +41599,8 @@ }, "limit": { "context": 1048576, - "output": 384000 + "output": 131072 }, - "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -39750,40 +41608,85 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "high", "effort_options": [ + "none", "low", "high", "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." ] } }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-04-24", - "last_updated": "2026-04-24", + "attachment": true, + "open_weights": false, + "release_date": "2026-10-28", + "last_updated": "2026-10-28", "type": "chat" }, { - "id": "GLM-5.2", - "name": "GLM-5.2", - "display_name": "GLM-5.2", + "id": "heygen/heygen-video-1", + "name": "HeyGen Video", + "display_name": "HeyGen Video", "modalities": { "input": [ - "text" + "text", + "image", + "video", + "audio" + ], + "output": [ + "video" + ] + }, + "limit": { + "context": 8192, + "output": 8192 + }, + "temperature": false, + "tool_call": false, + "reasoning": { + "supported": false + }, + "attachment": true, + "open_weights": false, + "release_date": "2026-10-09", + "last_updated": "2026-10-09" + }, + { + "id": "meta/muse-image-1.0", + "name": "Muse Image 1.0", + "display_name": "Muse Image 1.0", + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "image" + ] + }, + "limit": { + "context": 8192, + "output": 8192 + }, + "temperature": false, + "tool_call": false, + "reasoning": { + "supported": false + }, + "attachment": true, + "open_weights": false, + "release_date": "2026-07-07", + "last_updated": "2026-07-07" + }, + { + "id": "meta/muse-spark-1.1", + "name": "Muse Spark 1.1", + "display_name": "Muse Spark 1.1", + "modalities": { + "input": [ + "text", + "image", + "pdf" ], "output": [ "text" @@ -39791,7 +41694,7 @@ }, "limit": { "context": 1048576, - "output": 131072 + "output": 1048576 }, "temperature": true, "tool_call": true, @@ -39802,46 +41705,39 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "minimal", + "low", + "medium", + "high", + "xhigh" ] } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-06-13", - "last_updated": "2026-06-13", + "attachment": true, + "open_weights": false, + "release_date": "2026-04-08", + "last_updated": "2026-07-09", "type": "chat" - } - ] - }, - "meganova": { - "id": "meganova", - "name": "Meganova", - "display_name": "Meganova", - "api": "https://api.meganova.ai/v1", - "doc": "https://docs.meganova.ai", - "models": [ + }, { - "id": "zai-org/GLM-4.7", - "name": "GLM-4.7", - "display_name": "GLM-4.7", + "id": "meta/muse-spark-1.3-contributor", + "name": "Muse Spark 1.3 Contributor", + "display_name": "Muse Spark 1.3 Contributor", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 202752, - "output": 131072 + "context": 1048576, + "output": 1048576 }, - "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -39849,76 +41745,67 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "minimal", + "low", + "medium", + "high", + "xhigh", + "max" ] } }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-12-22", - "last_updated": "2025-12-22", + "attachment": true, + "open_weights": false, + "release_date": "2026-09-02", + "last_updated": "2026-09-02", "type": "chat" }, { - "id": "zai-org/GLM-5", - "name": "GLM-5", - "display_name": "GLM-5", + "id": "meta/llama-4-scout", + "name": "Llama-4-Scout-17B-16E-Instruct-FP8", + "display_name": "Llama-4-Scout-17B-16E-Instruct-FP8", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 202752, - "output": 131072 + "context": 128000, + "output": 8192 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } + "supported": false }, - "attachment": false, + "attachment": true, "open_weights": true, - "release_date": "2026-02-12", - "last_updated": "2026-02-12", + "knowledge": "2024-08", + "release_date": "2025-04-05", + "last_updated": "2025-04-05", "type": "chat" }, { - "id": "zai-org/GLM-4.6", - "name": "GLM-4.6", - "display_name": "GLM-4.6", + "id": "meta/muse-spark-open", + "name": "Muse Spark Open", + "display_name": "Muse Spark Open", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 202752, - "output": 131072 + "context": 1048576, + "output": 1048576 }, "temperature": true, "tool_call": true, @@ -39928,67 +41815,66 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "low", + "medium", + "high", + "xhigh" ] } }, - "attachment": false, + "attachment": true, "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-09-30", - "last_updated": "2025-09-30", + "knowledge": "2026-01-04", + "release_date": "2026-10-15", + "last_updated": "2026-10-15", "type": "chat" }, { - "id": "Qwen/Qwen2.5-VL-32B-Instruct", - "name": "Qwen2.5 VL 32B Instruct", - "display_name": "Qwen2.5 VL 32B Instruct", + "id": "meta/llama-3.1-8b", + "name": "Llama 3.1 8B Instruct", + "display_name": "Llama 3.1 8B Instruct", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 16384, - "output": 16384 + "context": 128000, + "output": 8192 }, "temperature": true, "tool_call": true, "reasoning": { "supported": false }, - "attachment": true, + "attachment": false, "open_weights": true, - "release_date": "2025-03-24", - "last_updated": "2025-03-24", + "knowledge": "2023-12", + "release_date": "2024-07-23", + "last_updated": "2024-07-23", "type": "chat" }, { - "id": "Qwen/Qwen3.5-Plus", - "name": "Qwen3.5 Plus", - "display_name": "Qwen3.5 Plus", + "id": "meta/muse-wonder-1.0", + "name": "Muse Wonder", + "display_name": "Muse Wonder", "modalities": { "input": [ "text", "image", - "video" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 65536 + "context": 1048576, + "output": 1048576 }, "temperature": true, "tool_call": true, @@ -39998,53 +41884,55 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "minimal", + "low", + "medium", + "high", + "xhigh", + "max" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-04", - "release_date": "2026-02-16", - "last_updated": "2026-02-16", + "release_date": "2026-10-12", + "last_updated": "2026-10-12", "type": "chat" }, { - "id": "Qwen/Qwen3-235B-A22B-Instruct-2507", - "name": "Qwen3 235B A22B Instruct 2507", - "display_name": "Qwen3 235B A22B Instruct 2507", + "id": "meta/llama-4-maverick", + "name": "Llama-4-Maverick-17B-128E-Instruct-FP8", + "display_name": "Llama-4-Maverick-17B-128E-Instruct-FP8", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 262000, - "output": 262000 + "context": 128000, + "output": 8192 }, "temperature": true, "tool_call": true, "reasoning": { "supported": false }, - "attachment": false, + "attachment": true, "open_weights": true, - "release_date": "2025-07-21", - "last_updated": "2025-07-21", + "knowledge": "2024-08", + "release_date": "2025-04-05", + "last_updated": "2025-04-05", "type": "chat" }, { - "id": "XiaomiMiMo/MiMo-V2-Flash", - "name": "MiMo V2 Flash", - "display_name": "MiMo V2 Flash", + "id": "meta/llama-3.3-70b", + "name": "Llama-3.3-70B-Instruct", + "display_name": "Llama-3.3-70B-Instruct", "modalities": { "input": [ "text" @@ -40054,37 +41942,38 @@ ] }, "limit": { - "context": 262144, - "output": 32000 + "context": 128000, + "output": 8192 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true + "supported": false }, "attachment": false, "open_weights": true, - "knowledge": "2024-12-01", - "release_date": "2025-12-16", - "last_updated": "2026-02-04", + "knowledge": "2023-12", + "release_date": "2024-12-06", + "last_updated": "2024-12-06", "type": "chat" }, { - "id": "MiniMaxAI/MiniMax-M2.1", - "name": "MiniMax M2.1", - "display_name": "MiniMax M2.1", + "id": "meta/muse-spark-1.2", + "name": "Muse Spark 1.2", + "display_name": "Muse Spark 1.2", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 196608, - "output": 131072 + "context": 1048576, + "output": 1048576 }, "temperature": true, "tool_call": true, @@ -40095,37 +41984,39 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "minimal", + "low", + "medium", + "high", + "xhigh" ] } }, - "attachment": false, - "open_weights": true, - "release_date": "2025-12-23", - "last_updated": "2025-12-23", + "attachment": true, + "open_weights": false, + "release_date": "2026-08-05", + "last_updated": "2026-08-05", "type": "chat" }, { - "id": "MiniMaxAI/MiniMax-M2.5", - "name": "MiniMax M2.5", - "display_name": "MiniMax M2.5", + "id": "meta/muse-spark-1.2-contributor", + "name": "Muse Spark 1.2 Contributor", + "display_name": "Muse Spark 1.2 Contributor", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 204800, - "output": 131072 + "context": 1048576, + "output": 1048576 }, - "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -40133,37 +42024,38 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "minimal", + "low", + "medium", + "high", + "xhigh" ] } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-02-12", - "last_updated": "2026-02-12", + "attachment": true, + "open_weights": false, + "release_date": "2026-08-05", + "last_updated": "2026-08-05", "type": "chat" }, { - "id": "moonshotai/Kimi-K2.5", - "name": "Kimi K2.5", - "display_name": "Kimi K2.5", + "id": "meta/muse-spark-1.3", + "name": "Muse Spark 1.3", + "display_name": "Muse Spark 1.3", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 1048576, + "output": 1048576 }, "temperature": true, "tool_call": true, @@ -40174,36 +42066,38 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "minimal", + "low", + "medium", + "high", + "xhigh", + "max" ] } }, "attachment": true, - "open_weights": true, - "knowledge": "2026-01", - "release_date": "2026-01-27", - "last_updated": "2026-01-27", + "open_weights": false, + "release_date": "2026-09-02", + "last_updated": "2026-09-02", "type": "chat" }, { - "id": "moonshotai/Kimi-K2-Thinking", - "name": "Kimi K2 Thinking", - "display_name": "Kimi K2 Thinking", + "id": "meta/muse-glimmer-30b", + "name": "Muse Glimmer 30B", + "display_name": "Muse Glimmer 30B", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 131072, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -40213,26 +42107,25 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "none", + "low", + "medium", + "high" ] } }, - "attachment": false, + "attachment": true, "open_weights": true, - "knowledge": "2024-08", - "release_date": "2025-11-06", - "last_updated": "2025-11-06", + "knowledge": "2026-01-04", + "release_date": "2026-08-10", + "last_updated": "2026-08-10", "type": "chat" }, { - "id": "mistralai/Mistral-Nemo-Instruct-2407", - "name": "Mistral Nemo Instruct 2407", - "display_name": "Mistral Nemo Instruct 2407", + "id": "meta/llama-3.1-70b", + "name": "Llama 3.1 70B Instruct", + "display_name": "Llama 3.1 70B Instruct", "modalities": { "input": [ "text" @@ -40242,8 +42135,8 @@ ] }, "limit": { - "context": 131072, - "output": 65536 + "context": 128000, + "output": 8192 }, "temperature": true, "tool_call": true, @@ -40252,44 +42145,53 @@ }, "attachment": false, "open_weights": true, - "knowledge": "2024-07", - "release_date": "2024-07-18", - "last_updated": "2024-07-18", + "knowledge": "2023-12", + "release_date": "2024-07-23", + "last_updated": "2024-07-23", "type": "chat" }, { - "id": "mistralai/Mistral-Small-3.2-24B-Instruct-2506", - "name": "Mistral Small 3.2 24B Instruct", - "display_name": "Mistral Small 3.2 24B Instruct", + "id": "inclusionai/ling-3.1-flash", + "name": "Ling 3.1 Flash", + "display_name": "Ling 3.1 Flash", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 32768, - "output": 8192 + "context": 262144, + "output": 32768 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": true, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "none", + "low", + "medium", + "high" + ] + } + }, + "attachment": false, "open_weights": true, - "knowledge": "2024-10", - "release_date": "2025-06-20", - "last_updated": "2025-06-20", + "release_date": "2026-09-29", + "last_updated": "2026-09-29", "type": "chat" }, { - "id": "meta-llama/Llama-3.3-70B-Instruct", - "name": "Llama 3.3 70B Instruct", - "display_name": "Llama 3.3 70B Instruct", + "id": "inclusionai/ling-3.0-flash-fin", + "name": "Ling 3.0 Flash Fin", + "display_name": "Ling 3.0 Flash Fin", "modalities": { "input": [ "text" @@ -40299,25 +42201,25 @@ ] }, "limit": { - "context": 131072, - "output": 16384 + "context": 256000, + "output": 32000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, "attachment": false, "open_weights": true, - "knowledge": "2023-12", - "release_date": "2024-12-06", - "last_updated": "2024-12-06", + "release_date": "2026-08-27", + "last_updated": "2026-09-03", "type": "chat" }, { - "id": "deepseek-ai/DeepSeek-V3-0324", - "name": "DeepSeek V3 0324", - "display_name": "DeepSeek V3 0324", + "id": "inclusionai/ling-3.0-flash-sante", + "name": "Ling 3.0 Flash Sante", + "display_name": "Ling 3.0 Flash Sante", "modalities": { "input": [ "text" @@ -40327,24 +42229,35 @@ ] }, "limit": { - "context": 163840, - "output": 163840 + "context": 256000, + "output": 32000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "none", + "low", + "medium", + "high" + ] + } }, "attachment": false, - "open_weights": true, - "release_date": "2025-03-24", - "last_updated": "2025-03-24", + "open_weights": false, + "release_date": "2026-09-04", + "last_updated": "2026-09-04", "type": "chat" }, { - "id": "deepseek-ai/DeepSeek-V3.2-Exp", - "name": "DeepSeek V3.2 Exp", - "display_name": "DeepSeek V3.2 Exp", + "id": "inclusionai/ling-3.1-flash-1019", + "name": "Ling 3.1 Flash 1019", + "display_name": "Ling 3.1 Flash 1019", "modalities": { "input": [ "text" @@ -40354,57 +42267,63 @@ ] }, "limit": { - "context": 164000, - "output": 164000 + "context": 1000000, + "output": 262144 }, - "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "none", + "low", + "medium", + "high" + ] + } }, "attachment": false, - "open_weights": true, - "release_date": "2025-10-10", - "last_updated": "2025-10-10", + "open_weights": false, + "release_date": "2026-10-30", + "last_updated": "2026-10-30", "type": "chat" }, { - "id": "deepseek-ai/DeepSeek-V3.2", - "name": "DeepSeek V3.2", - "display_name": "DeepSeek V3.2", + "id": "inclusionai/ling-3.0-flash-vl", + "name": "Ling 3.0 Flash VL", + "display_name": "Ling 3.0 Flash VL", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 164000, - "output": 164000 + "context": 256000, + "output": 32000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true - } + "supported": true, + "default": true }, - "attachment": false, + "attachment": true, "open_weights": true, - "knowledge": "2024-07", - "release_date": "2025-12-01", - "last_updated": "2025-12-01", + "release_date": "2026-09-10", + "last_updated": "2026-09-10", "type": "chat" }, { - "id": "deepseek-ai/DeepSeek-V3.1", - "name": "DeepSeek V3.1", - "display_name": "DeepSeek V3.1", + "id": "inclusionai/ling-3.0-flash", + "name": "Ling 3.0 Flash", + "display_name": "Ling 3.0 Flash", "modalities": { "input": [ "text" @@ -40414,87 +42333,77 @@ ] }, "limit": { - "context": 164000, - "output": 164000 + "context": 256000, + "output": 32000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, "attachment": false, "open_weights": true, - "release_date": "2025-08-21", - "last_updated": "2025-08-21", + "release_date": "2026-07-23", + "last_updated": "2026-07-23", "type": "chat" }, { - "id": "deepseek-ai/DeepSeek-R1-0528", - "name": "DeepSeek R1 0528", - "display_name": "DeepSeek R1 0528", + "id": "xiaomi/mimo-v2.6-pro", + "name": "MiMo V2.6 Pro", + "display_name": "MiMo V2.6 Pro", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 163840, - "output": 64000 + "context": 1048576, + "output": 131072 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { "supported": true, "default": true }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "none", + "minimal", + "low", + "medium", + "high" ] } }, - "attachment": false, + "attachment": true, "open_weights": true, - "knowledge": "2024-07", - "release_date": "2025-05-28", - "last_updated": "2025-05-28", + "release_date": "2026-09-22", + "last_updated": "2026-09-22", "type": "chat" - } - ] - }, - "bee": { - "id": "bee", - "name": "Bee by HEOSSI", - "display_name": "Bee by HEOSSI", - "api": "https://api.bee.heossi.com/bee", - "doc": "https://bee.heossi.com/docs/sdks", - "models": [ + }, { - "id": "bee-comb", - "name": "Bee Comb 2.0", - "display_name": "Bee Comb 2.0", + "id": "xiaomi/mimo-v2.6-flash", + "name": "MiMo V2.6 Flash", + "display_name": "MiMo V2.6 Flash", "modalities": { "input": [ "text", - "image", - "video", - "audio" + "image" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 16384 + "context": 1048576, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -40505,38 +42414,36 @@ "extra_capabilities": { "reasoning": { "effort_options": [ + "none", + "minimal", "low", "medium", - "high", - "xhigh", - "max" + "high" ] } }, "attachment": true, - "open_weights": false, - "release_date": "2026-07-10", - "last_updated": "2026-09-28", + "open_weights": true, + "release_date": "2026-09-22", + "last_updated": "2026-09-22", "type": "chat" }, { - "id": "bee-buzz", - "name": "Bee Buzz 1.0", - "display_name": "Bee Buzz 1.0", + "id": "xiaomi/mimo-v2.6-pro-ultraspeed", + "name": "MiMo V2.6 Pro UltraSpeed", + "display_name": "MiMo V2.6 Pro UltraSpeed", "modalities": { "input": [ "text", - "image", - "video", - "audio" + "image" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 24576 + "context": 1048576, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -40547,38 +42454,44 @@ "extra_capabilities": { "reasoning": { "effort_options": [ + "none", + "minimal", "low", "medium", - "high", - "xhigh", - "max" + "high" ] } }, "attachment": true, - "open_weights": false, - "release_date": "2026-07-10", - "last_updated": "2026-09-28", + "open_weights": true, + "release_date": "2026-09-21", + "last_updated": "2026-09-21", "type": "chat" - }, + } + ] + }, + "sarvam": { + "id": "sarvam", + "name": "Sarvam AI", + "display_name": "Sarvam AI", + "api": "https://api.sarvam.ai/v1", + "doc": "https://docs.sarvam.ai/api-reference-docs/getting-started/models", + "models": [ { - "id": "bee-hive", - "name": "Bee Hive 1.0", - "display_name": "Bee Hive 1.0", + "id": "sarvam-30b", + "name": "Sarvam-30B", + "display_name": "Sarvam-30B", "modalities": { "input": [ - "text", - "image", - "video", - "audio" + "text" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 32768 + "context": 65536, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -40588,30 +42501,33 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ "low", "medium", - "high", - "xhigh", - "max" + "high" ] } }, - "attachment": true, - "open_weights": false, - "release_date": "2026-07-10", - "last_updated": "2026-09-28", + "attachment": false, + "open_weights": true, + "release_date": "2026-02-18", + "last_updated": "2026-02-18", "type": "chat" }, { - "id": "bee-cell", - "name": "Bee Cell 1.0", - "display_name": "Bee Cell 1.0", + "id": "sarvam-105b", + "name": "Sarvam-105B", + "display_name": "Sarvam-105B", "modalities": { "input": [ - "text", - "image", - "audio" + "text" ], "output": [ "text" @@ -40619,77 +42535,62 @@ }, "limit": { "context": 131072, - "output": 8192 + "output": 131072 }, "temperature": true, "tool_call": true, - "reasoning": { - "supported": false - }, - "attachment": true, - "open_weights": false, - "release_date": "2026-07-10", - "last_updated": "2026-09-28", - "type": "chat" - }, - { - "id": "bee-swarm", - "name": "Bee Swarm 1.0", - "display_name": "Bee Swarm 1.0", - "modalities": { - "input": [ - "text", - "image", - "audio" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 1000000, - "output": 64000 - }, - "tool_call": false, "reasoning": { "supported": true, "default": true }, "extra_capabilities": { "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ "low", "medium", - "high", - "xhigh", - "max" + "high" ] } }, - "attachment": true, - "open_weights": false, - "release_date": "2026-07-10", - "last_updated": "2026-09-28", + "attachment": false, + "open_weights": true, + "release_date": "2026-02-18", + "last_updated": "2026-02-18", "type": "chat" - }, + } + ] + }, + "pareto": { + "id": "pareto", + "name": "Pareto Inference", + "display_name": "Pareto Inference", + "api": "https://api.paretoinference.com/v1", + "doc": "https://docs.paretoinference.com/", + "models": [ { - "id": "bee-brood", - "name": "Bee Brood 1.0", - "display_name": "Bee Brood 1.0", + "id": "z-ai/glm-5.3-flash", + "name": "GLM-5.3-Flash", + "display_name": "GLM-5.3-Flash", "modalities": { "input": [ "text", "image", - "video", - "audio" + "video" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 16384 + "context": 1000000, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -40699,49 +42600,53 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ "low", - "medium", "high", - "xhigh", "max" ] } }, "attachment": true, - "open_weights": false, - "release_date": "2026-07-10", - "last_updated": "2026-09-28", + "open_weights": true, + "release_date": "2026-08-26", + "last_updated": "2026-08-26", "type": "chat" } ] }, - "orcarouter": { - "id": "orcarouter", - "name": "OrcaRouter", - "display_name": "OrcaRouter", - "api": "https://api.orcarouter.ai/v1", - "doc": "https://docs.orcarouter.ai", + "berget": { + "id": "berget", + "name": "Berget.AI", + "display_name": "Berget.AI", + "api": "https://api.berget.ai/v1", + "doc": "https://api.berget.ai", "models": [ { - "id": "orcarouter/fusion", - "name": "OrcaRouter Fusion", - "display_name": "OrcaRouter Fusion", + "id": "Qwen/Qwen3.8-27B-FP8", + "name": "Qwen3.8 27B", + "display_name": "Qwen3.8 27B", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 262144, + "output": 65536 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -40749,99 +42654,91 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ + "none", "low", "medium", - "high" + "xhigh" ] } }, "attachment": true, - "open_weights": false, - "release_date": "2026-06-15", - "last_updated": "2026-06-15", + "open_weights": true, + "release_date": "2026-08-14", + "last_updated": "2026-08-14", "type": "chat" }, { - "id": "orcarouter/free", - "name": "OrcaRouter Free", - "display_name": "OrcaRouter Free", + "id": "moonshotai/Kimi-K3", + "name": "Kimi K3", + "display_name": "Kimi K3", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 65536, + "context": 327680, "output": 32768 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, "default": true }, - "attachment": false, - "open_weights": false, - "release_date": "2026-08-27", - "last_updated": "2026-08-27", - "type": "chat" - }, - { - "id": "orcarouter/auto", - "name": "OrcaRouter Auto", - "display_name": "OrcaRouter Auto", - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 16384 - }, - "temperature": true, - "tool_call": true, - "reasoning": { - "supported": true - }, "extra_capabilities": { "reasoning": { - "supported": true + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "effort_options": [ + "low", + "high", + "max" + ] } }, "attachment": true, - "open_weights": false, - "release_date": "2025-01-01", - "last_updated": "2026-05-14", + "open_weights": true, + "release_date": "2026-07-16", + "last_updated": "2026-07-16", "type": "chat" }, { - "id": "orcarouter/fusion-mini", - "name": "OrcaRouter Fusion Mini", - "display_name": "OrcaRouter Fusion Mini", + "id": "zai-org/GLM-5.3-Flash", + "name": "GLM-5.3-Flash", + "display_name": "GLM-5.3-Flash", "modalities": { "input": [ "text", "image", - "pdf" + "video" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 1048576, + "output": 16384 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -40849,23 +42746,30 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ "low", - "medium", - "high" + "high", + "max" ] } }, "attachment": true, - "open_weights": false, - "release_date": "2026-06-15", - "last_updated": "2026-06-15", + "open_weights": true, + "release_date": "2026-08-26", + "last_updated": "2026-08-26", "type": "chat" }, { - "id": "orcarouter/fusion-flash", - "name": "OrcaRouter Fusion Flash", - "display_name": "OrcaRouter Fusion Flash", + "id": "zai-org/GLM-5.2", + "name": "GLM-5.2", + "display_name": "GLM-5.2", "modalities": { "input": [ "text" @@ -40875,8 +42779,8 @@ ] }, "limit": { - "context": 200000, - "output": 128000 + "context": 524288, + "output": 32768 }, "temperature": true, "tool_call": true, @@ -40886,27 +42790,28 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, "effort_options": [ - "low", - "medium", + "none", "high" ] } }, "attachment": false, - "open_weights": false, - "release_date": "2026-06-15", - "last_updated": "2026-06-15", + "open_weights": true, + "release_date": "2026-06-13", + "last_updated": "2026-06-13", "type": "chat" }, { - "id": "minimax/minimax-m3", - "name": "MiniMax-M3", - "display_name": "MiniMax-M3", + "id": "google/gemma-4-31B-it", + "name": "Gemma 4 31B Instruct", + "display_name": "Gemma 4 31B Instruct", "modalities": { "input": [ - "text", + "audio", "image", + "text", "video" ], "output": [ @@ -40914,8 +42819,8 @@ ] }, "limit": { - "context": 1048576, - "output": 512000 + "context": 128000, + "output": 8192 }, "temperature": true, "tool_call": true, @@ -40930,14 +42835,15 @@ }, "attachment": true, "open_weights": true, - "release_date": "2026-06-01", - "last_updated": "2026-06-01", + "knowledge": "2025-12", + "release_date": "2026-04-02", + "last_updated": "2026-04-02", "type": "chat" }, { - "id": "minimax/minimax-m2.5", - "name": "MiniMax-M2.5", - "display_name": "MiniMax-M2.5", + "id": "mistralai/Mistral-Small-3.2-24B-Instruct-2506", + "name": "Mistral Small 3.2 24B Instruct 2506", + "display_name": "Mistral Small 3.2 24B Instruct 2506", "modalities": { "input": [ "text" @@ -40947,8 +42853,8 @@ ] }, "limit": { - "context": 204800, - "output": 131072 + "context": 32000, + "output": 8192 }, "temperature": true, "tool_call": true, @@ -40956,34 +42862,41 @@ "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "supported": true - } - }, "attachment": false, "open_weights": true, - "release_date": "2026-02-12", - "last_updated": "2026-02-12", + "knowledge": "2025-09", + "release_date": "2025-06-20", + "last_updated": "2025-06-20", "type": "chat" - }, + } + ] + }, + "melious": { + "id": "melious", + "name": "Melious", + "display_name": "Melious", + "api": "https://api.melious.ai/v1", + "doc": "https://melious.ai/docs/reference/models", + "models": [ { - "id": "minimax/minimax-m2.7", - "name": "MiniMax-M2.7", - "display_name": "MiniMax-M2.7", + "id": "kimi-k3", + "name": "Kimi K3", + "display_name": "Kimi K3", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 204800, + "context": 1000000, "output": 131072 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -40997,32 +42910,40 @@ "visibility": "summary", "continuation": [ "thinking_blocks" + ], + "effort_options": [ + "none", + "low", + "high", + "max" ] } }, - "attachment": false, + "attachment": true, "open_weights": true, - "release_date": "2026-03-18", - "last_updated": "2026-03-18", + "release_date": "2026-07-16", + "last_updated": "2026-07-16", "type": "chat" }, { - "id": "minimax/minimax-m2.7-highspeed", - "name": "MiniMax-M2.7-highspeed", - "display_name": "MiniMax-M2.7-highspeed", + "id": "kimi-k2.7-code", + "name": "Kimi K2.7 Code", + "display_name": "Kimi K2.7 Code", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 204800, - "output": 131072 + "context": 262144, + "output": 262144 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -41039,54 +42960,21 @@ ] } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-03-18", - "last_updated": "2026-03-18", - "type": "chat" - }, - { - "id": "minimax/minimax-m2.5-highspeed", - "name": "MiniMax-M2.5-highspeed", - "display_name": "MiniMax-M2.5-highspeed", - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 204800, - "output": 131072 - }, - "temperature": true, - "tool_call": true, - "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true - } - }, - "attachment": false, + "attachment": true, "open_weights": true, - "release_date": "2026-02-13", - "last_updated": "2026-02-13", + "knowledge": "2025-01", + "release_date": "2026-06-12", + "last_updated": "2026-06-12", "type": "chat" }, { - "id": "anthropic/claude-opus-5", - "name": "Claude Opus 5", - "display_name": "Claude Opus 5", + "id": "deepseek-v4.1-flash-1017", + "name": "DeepSeek V4.1 Flash 1017", + "display_name": "DeepSeek V4.1 Flash 1017", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" @@ -41094,9 +42982,9 @@ }, "limit": { "context": 1000000, - "output": 128000 + "output": 384000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -41105,29 +42993,36 @@ "extra_capabilities": { "reasoning": { "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ + "none", "low", - "medium", - "high" + "high", + "max" ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2026-05", - "release_date": "2026-07-24", - "last_updated": "2026-07-24", + "open_weights": true, + "knowledge": "2025-05", + "release_date": "2026-10-17", + "last_updated": "2026-10-17", "type": "chat" }, { - "id": "anthropic/claude-opus-4.8", - "name": "Claude Opus 4.8", - "display_name": "Claude Opus 4.8", + "id": "glm-5.3-flash", + "name": "GLM-5.3-Flash", + "display_name": "GLM-5.3-Flash", "modalities": { "input": [ "text", "image", - "pdf" + "video" ], "output": [ "text" @@ -41135,56 +43030,44 @@ }, "limit": { "context": 1000000, - "output": 128000 + "output": 131072 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "high", - "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" - ], "interleaved": true, "summaries": true, - "visibility": "omitted", + "visibility": "summary", "continuation": [ "thinking_blocks" ], - "notes": [ - "Claude Opus 4.7 and newer Opus models require thinking.type = \"adaptive\" to enable thinking explicitly.", - "Manual budget_tokens requests return 400 on Claude Opus 4.7 and newer adaptive-only Opus models.", - "task_budget is separate from thinking control and should not be treated as a thinking budget." + "effort_options": [ + "low", + "high", + "max" ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2026-01", - "release_date": "2026-05-28", - "last_updated": "2026-05-28", + "open_weights": true, + "release_date": "2026-08-26", + "last_updated": "2026-08-26", "type": "chat" }, { - "id": "anthropic/claude-sonnet-5", - "name": "Claude Sonnet 5", - "display_name": "Claude Sonnet 5", + "id": "deepseek-v4.1-flash", + "name": "DeepSeek V4.1 Flash", + "display_name": "DeepSeek V4.1 Flash", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" @@ -41192,9 +43075,9 @@ }, "limit": { "context": 1000000, - "output": 128000 + "output": 384000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -41203,29 +43086,34 @@ "extra_capabilities": { "reasoning": { "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ + "none", "low", - "medium", - "high" + "high", + "max" ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2026-01-31", - "release_date": "2026-06-30", - "last_updated": "2026-06-30", + "open_weights": true, + "knowledge": "2025-05", + "release_date": "2026-09-10", + "last_updated": "2026-09-10", "type": "chat" }, { - "id": "anthropic/claude-opus-4.6", - "name": "Claude Opus 4.6", - "display_name": "Claude Opus 4.6", + "id": "glm-5.3", + "name": "GLM-5.3", + "display_name": "GLM-5.3", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" @@ -41233,402 +43121,272 @@ }, "limit": { "context": 1000000, - "output": 128000 + "output": 131072 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "mixed", - "budget": { - "min": 1024, - "unit": "tokens" - }, - "effort": "high", - "effort_options": [ - "low", - "medium", - "high", - "max" - ], "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ "thinking_blocks" ], - "notes": [ - "Anthropic recommends adaptive thinking with effort for Claude 4.6; budget_tokens remains a deprecated compatibility path.", - "Anthropic API defaults effort to high; lower effort levels should be chosen per workload." + "effort_options": [ + "low", + "high", + "max" ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-05-31", - "release_date": "2026-02-05", - "last_updated": "2026-03-13", + "attachment": false, + "open_weights": true, + "release_date": "2026-08-14", + "last_updated": "2026-08-14", "type": "chat" }, { - "id": "anthropic/claude-opus-4.5", - "name": "Claude Opus 4.5 (latest)", - "display_name": "Claude Opus 4.5 (latest)", + "id": "deepseek-v4-flash-0731", + "name": "DeepSeek V4 Flash 0731", + "display_name": "DeepSeek V4 Flash 0731", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 64000 + "context": 1000000, + "output": 384000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "mixed", - "budget": { - "min": 1024, - "unit": "tokens" - }, - "effort": "high", - "effort_options": [ - "low", - "medium", - "high" - ], "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ "thinking_blocks" ], - "notes": [ - "Claude Opus 4.5 uses manual thinking.type = \"enabled\" with budget_tokens; effort can be used alongside the thinking budget.", - "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header." + "effort_options": [ + "none", + "low", + "high", + "max" ] } }, - "attachment": true, - "open_weights": false, + "attachment": false, + "open_weights": true, "knowledge": "2025-05", - "release_date": "2025-11-24", - "last_updated": "2025-11-24", + "release_date": "2026-07-31", + "last_updated": "2026-07-31", "type": "chat" }, { - "id": "anthropic/claude-sonnet-4.6", - "name": "Claude Sonnet 4.6", - "display_name": "Claude Sonnet 4.6", + "id": "kimi-k2.6", + "name": "Kimi K2.6", + "display_name": "Kimi K2.6", "modalities": { "input": [ "text", "image", - "pdf" + "video" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 64000 + "context": 256000, + "output": 256000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "mixed", - "budget": { - "min": 1024, - "unit": "tokens" - }, - "effort": "high", - "effort_options": [ - "low", - "medium", - "high", - "max" - ], "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ "thinking_blocks" - ], - "notes": [ - "Anthropic recommends adaptive thinking with effort for Claude 4.6; budget_tokens remains a deprecated compatibility path.", - "Anthropic API defaults effort to high; lower effort levels should be chosen per workload." ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-02-17", - "last_updated": "2026-03-13", + "open_weights": true, + "knowledge": "2025-01", + "release_date": "2026-04-20", + "last_updated": "2026-04-20", "type": "chat" - }, + } + ] + }, + "modelscope": { + "id": "modelscope", + "name": "ModelScope", + "display_name": "ModelScope", + "api": "https://api-inference.modelscope.cn/v1", + "doc": "https://modelscope.cn/docs/model-service/API-Inference/intro", + "models": [ { - "id": "anthropic/claude-haiku-4.5", - "name": "Claude Haiku 4.5 (latest)", - "display_name": "Claude Haiku 4.5 (latest)", + "id": "Qwen/Qwen3-Coder-30B-A3B-Instruct", + "name": "Qwen3 Coder 30B A3B Instruct", + "display_name": "Qwen3 Coder 30B A3B Instruct", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 64000 + "context": 262144, + "output": 65536 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": false - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "budget", - "budget": { - "min": 1024, - "unit": "tokens" - }, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", - "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." - ] - } + "supported": false }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-02-28", - "release_date": "2025-10-15", - "last_updated": "2025-10-15", + "attachment": false, + "open_weights": true, + "knowledge": "2025-04", + "release_date": "2025-07-31", + "last_updated": "2025-07-31", "type": "chat" }, { - "id": "anthropic/claude-opus-4.7", - "name": "Claude Opus 4.7", - "display_name": "Claude Opus 4.7", + "id": "Qwen/Qwen3-30B-A3B-Instruct-2507", + "name": "Qwen3 30B A3B Instruct 2507", + "display_name": "Qwen3 30B A3B Instruct 2507", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 262144, + "output": 16384 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": false - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "high", - "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "omitted", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Claude Opus 4.7 and newer Opus models require thinking.type = \"adaptive\" to enable thinking explicitly.", - "Manual budget_tokens requests return 400 on Claude Opus 4.7 and newer adaptive-only Opus models.", - "task_budget is separate from thinking control and should not be treated as a thinking budget." - ] - } + "supported": false }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-01-31", - "release_date": "2026-04-16", - "last_updated": "2026-04-16", + "attachment": false, + "open_weights": true, + "knowledge": "2025-04", + "release_date": "2025-07-28", + "last_updated": "2025-07-28", "type": "chat" }, { - "id": "anthropic/claude-fable-5", - "name": "Claude Fable 5", - "display_name": "Claude Fable 5", + "id": "Qwen/Qwen3-235B-A22B-Instruct-2507", + "name": "Qwen3 235B A22B Instruct 2507", + "display_name": "Qwen3 235B A22B Instruct 2507", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 262144, + "output": 131072 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "high", - "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "omitted", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Adaptive thinking is always on for Claude Fable 5 and Claude Mythos 5; thinking.type = \"disabled\" is rejected.", - "Manual budget_tokens requests return 400 on Claude Fable 5 and Claude Mythos 5.", - "thinking.display defaults to omitted; set display to summarized to receive readable thinking summaries." - ] - } + "supported": false }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-01-31", - "release_date": "2026-06-09", - "last_updated": "2026-06-09", + "attachment": false, + "open_weights": true, + "knowledge": "2025-04", + "release_date": "2025-07-21", + "last_updated": "2025-07-21", "type": "chat" }, { - "id": "anthropic/claude-sonnet-4.5", - "name": "Claude Sonnet 4.5 (latest)", - "display_name": "Claude Sonnet 4.5 (latest)", + "id": "Qwen/Qwen3-30B-A3B-Thinking-2507", + "name": "Qwen3 30B A3B Thinking 2507", + "display_name": "Qwen3 30B A3B Thinking 2507", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 64000 + "context": 262144, + "output": 32768 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "budget", - "budget": { - "min": 1024, - "unit": "tokens" - }, "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ "thinking_blocks" - ], - "notes": [ - "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", - "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-07-31", - "release_date": "2025-09-29", - "last_updated": "2025-09-29", + "attachment": false, + "open_weights": true, + "knowledge": "2025-04", + "release_date": "2025-07-30", + "last_updated": "2025-07-30", "type": "chat" }, { - "id": "z-ai/glm-5.3-flash", - "name": "GLM-5.3-Flash", - "display_name": "GLM-5.3-Flash", + "id": "Qwen/Qwen3-235B-A22B-Thinking-2507", + "name": "Qwen3-235B-A22B-Thinking-2507", + "display_name": "Qwen3-235B-A22B-Thinking-2507", "modalities": { "input": [ - "text", - "image", - "video", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 262144, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -41644,24 +43402,20 @@ "visibility": "summary", "continuation": [ "thinking_blocks" - ], - "effort_options": [ - "low", - "high", - "max" ] } }, - "attachment": true, + "attachment": false, "open_weights": true, - "release_date": "2026-08-26", - "last_updated": "2026-08-26", + "knowledge": "2025-04", + "release_date": "2025-07-25", + "last_updated": "2025-07-25", "type": "chat" }, { - "id": "z-ai/glm-4.7", - "name": "GLM-4.7", - "display_name": "GLM-4.7", + "id": "ZhipuAI/GLM-4.5", + "name": "GLM-4.5", + "display_name": "GLM-4.5", "modalities": { "input": [ "text" @@ -41671,8 +43425,8 @@ ] }, "limit": { - "context": 204800, - "output": 131072 + "context": 131072, + "output": 98304 }, "temperature": true, "tool_call": true, @@ -41680,26 +43434,15 @@ "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } - }, "attachment": false, "open_weights": true, "knowledge": "2025-04", - "release_date": "2025-12-22", - "last_updated": "2025-12-22", + "release_date": "2025-07-28", + "last_updated": "2025-07-28", "type": "chat" }, { - "id": "z-ai/glm-4.6", + "id": "ZhipuAI/GLM-4.6", "name": "GLM-4.6", "display_name": "GLM-4.6", "modalities": { @@ -41711,8 +43454,8 @@ ] }, "limit": { - "context": 204800, - "output": 131072 + "context": 202752, + "output": 98304 }, "temperature": true, "tool_call": true, @@ -41727,29 +43470,35 @@ }, "attachment": false, "open_weights": true, - "knowledge": "2025-04", + "knowledge": "2025-07", "release_date": "2025-09-30", "last_updated": "2025-09-30", "type": "chat" - }, + } + ] + }, + "wandb": { + "id": "wandb", + "name": "CoreWeave", + "display_name": "CoreWeave", + "api": "https://api.inference.wandb.ai/v1", + "doc": "https://docs.wandb.ai/inference", + "models": [ { - "id": "z-ai/glm-5.3-flash-free", - "name": "GLM-5.3-Flash (free)", - "display_name": "GLM-5.3-Flash (free)", + "id": "openai/gpt-oss-120b", + "name": "gpt-oss-120b", + "display_name": "gpt-oss-120b", "modalities": { "input": [ - "text", - "image", - "video", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 131072, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -41759,30 +43508,20 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "effort_options": [ - "low", - "high", - "max" - ] + "supported": true } }, - "attachment": true, + "attachment": false, "open_weights": true, - "release_date": "2026-08-26", - "last_updated": "2026-08-26", + "knowledge": "2024-06", + "release_date": "2025-08-05", + "last_updated": "2025-08-05", "type": "chat" }, { - "id": "z-ai/glm-4.5-air", - "name": "GLM-4.5-Air", - "display_name": "GLM-4.5-Air", + "id": "openai/gpt-oss-20b", + "name": "gpt-oss-20b", + "display_name": "gpt-oss-20b", "modalities": { "input": [ "text" @@ -41793,7 +43532,7 @@ }, "limit": { "context": 131072, - "output": 98304 + "output": 131072 }, "temperature": true, "tool_call": true, @@ -41801,28 +43540,34 @@ "supported": true, "default": true }, + "extra_capabilities": { + "reasoning": { + "supported": true + } + }, "attachment": false, "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-07-28", - "last_updated": "2025-07-28", + "knowledge": "2024-06", + "release_date": "2025-08-05", + "last_updated": "2025-08-05", "type": "chat" }, { - "id": "z-ai/glm-5.1", - "name": "GLM-5.1", - "display_name": "GLM-5.1", + "id": "Qwen/Qwen3.8-27B", + "name": "Qwen3.8 27B", + "display_name": "Qwen3.8 27B", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 131072 + "context": 262144, + "output": 262144 }, "temperature": true, "tool_call": true, @@ -41830,38 +43575,28 @@ "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } - }, - "attachment": false, + "attachment": true, "open_weights": true, - "release_date": "2026-04-07", - "last_updated": "2026-04-07", + "release_date": "2026-08-14", + "last_updated": "2026-08-14", "type": "chat" }, { - "id": "z-ai/glm-5", - "name": "GLM-5", - "display_name": "GLM-5", + "id": "Qwen/Qwen3.6-35B-A3B", + "name": "Qwen3.6 35B A3B", + "display_name": "Qwen3.6 35B A3B", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 204800, - "output": 131072 + "context": 262144, + "output": 262144 }, "temperature": true, "tool_call": true, @@ -41880,27 +43615,28 @@ ] } }, - "attachment": false, + "attachment": true, "open_weights": true, - "release_date": "2026-02-12", - "last_updated": "2026-02-12", + "release_date": "2026-04-17", + "last_updated": "2026-04-17", "type": "chat" }, { - "id": "z-ai/glm-5.3", - "name": "GLM-5.3", - "display_name": "GLM-5.3", + "id": "nvidia/NVIDIA-Nemotron-3.5-Super-VL-120B-A12B", + "name": "Nemotron 3.5 Super VL", + "display_name": "Nemotron 3.5 Super VL", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 1048576, + "output": 1048576 }, "temperature": true, "tool_call": true, @@ -41908,32 +43644,16 @@ "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "effort_options": [ - "low", - "high", - "max" - ] - } - }, - "attachment": false, + "attachment": true, "open_weights": true, - "release_date": "2026-08-14", - "last_updated": "2026-08-14", + "release_date": "2026-10-15", + "last_updated": "2026-10-15", "type": "chat" }, { - "id": "z-ai/glm-4.5", - "name": "GLM-4.5", - "display_name": "GLM-4.5", + "id": "nvidia/NVIDIA-Nemotron-3.5-Lightning-30B-A3B", + "name": "Nemotron 3.5 Lightning", + "display_name": "Nemotron 3.5 Lightning", "modalities": { "input": [ "text" @@ -41943,8 +43663,8 @@ ] }, "limit": { - "context": 131072, - "output": 98304 + "context": 262144, + "output": 262144 }, "temperature": true, "tool_call": true, @@ -41954,15 +43674,15 @@ }, "attachment": false, "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-07-28", - "last_updated": "2025-07-28", + "knowledge": "2025-09", + "release_date": "2026-08-11", + "last_updated": "2026-08-11", "type": "chat" }, { - "id": "z-ai/glm-5.2", - "name": "GLM-5.2", - "display_name": "GLM-5.2", + "id": "nvidia/NVIDIA-Nemotron-3-Ultra-550B-A55B", + "name": "Nemotron 3 Ultra", + "display_name": "Nemotron 3 Ultra", "modalities": { "input": [ "text" @@ -41972,8 +43692,8 @@ ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 262144, + "output": 262144 }, "temperature": true, "tool_call": true, @@ -41981,31 +43701,17 @@ "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "effort_options": [ - "high", - "max" - ] - } - }, "attachment": false, "open_weights": true, - "release_date": "2026-06-13", - "last_updated": "2026-06-13", + "knowledge": "2025-09", + "release_date": "2026-06-04", + "last_updated": "2026-06-04", "type": "chat" }, { - "id": "deepseek/deepseek-reasoner", - "name": "DeepSeek Reasoner", - "display_name": "DeepSeek Reasoner", + "id": "meta-llama/Llama-3.1-8B-Instruct", + "name": "Llama 3.1 8B", + "display_name": "Llama 3.1 8B", "modalities": { "input": [ "text" @@ -42015,37 +43721,25 @@ ] }, "limit": { - "context": 1000000, - "output": 384000 + "context": 131072, + "output": 131072 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } + "supported": false }, "attachment": false, "open_weights": true, - "knowledge": "2025-09", - "release_date": "2025-12-01", - "last_updated": "2026-02-28", + "knowledge": "2023-12", + "release_date": "2024-07-23", + "last_updated": "2024-07-23", "type": "chat" }, { - "id": "deepseek/deepseek-v4-flash", - "name": "DeepSeek V4 Flash", - "display_name": "DeepSeek V4 Flash", + "id": "meta-llama/Llama-3.3-70B-Instruct", + "name": "Llama 3.3 70B", + "display_name": "Llama 3.3 70B", "modalities": { "input": [ "text" @@ -42055,59 +43749,37 @@ ] }, "limit": { - "context": 1000000, - "output": 384000 + "context": 128000, + "output": 128000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "high", - "effort_options": [ - "low", - "high", - "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." - ] - } + "supported": false }, "attachment": false, "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-04-24", - "last_updated": "2026-04-24", + "knowledge": "2023-12", + "release_date": "2024-12-06", + "last_updated": "2024-12-06", "type": "chat" }, { - "id": "deepseek/deepseek-v4-pro", - "name": "DeepSeek V4 Pro", - "display_name": "DeepSeek V4 Pro", + "id": "moonshotai/Kimi-K2.7-Code", + "name": "Kimi K2.7 Code", + "display_name": "Kimi K2.7 Code", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 384000 + "context": 262144, + "output": 262144 }, "temperature": true, "tool_call": true, @@ -42117,37 +43789,20 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "high", - "effort_options": [ - "low", - "high", - "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." - ] + "supported": true } }, - "attachment": false, + "attachment": true, "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-04-24", - "last_updated": "2026-04-24", + "knowledge": "2025-01", + "release_date": "2026-06-12", + "last_updated": "2026-06-12", "type": "chat" }, { - "id": "deepseek/deepseek-v4-flash-vision-exp", - "name": "DeepSeek V4 Flash Vision Exp", - "display_name": "DeepSeek V4 Flash Vision Exp", + "id": "moonshotai/Kimi-K2.6", + "name": "Kimi K2.6", + "display_name": "Kimi K2.6", "modalities": { "input": [ "text", @@ -42158,8 +43813,8 @@ ] }, "limit": { - "context": 1000000, - "output": 384000 + "context": 262144, + "output": 262144 }, "temperature": true, "tool_call": true, @@ -42169,47 +43824,32 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "high", - "effort_options": [ - "low", - "high", - "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." - ] + "supported": true } }, "attachment": true, "open_weights": true, - "release_date": "2026-08-21", - "last_updated": "2026-09-01", + "knowledge": "2025-01", + "release_date": "2026-04-20", + "last_updated": "2026-04-20", "type": "chat" }, { - "id": "deepseek/deepseek-v4-flash-free", - "name": "DeepSeek V4 Flash (free)", - "display_name": "DeepSeek V4 Flash (free)", + "id": "zai-org/GLM-5.3-Flash", + "name": "GLM 5.3 Flash", + "display_name": "GLM 5.3 Flash", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 384000 + "context": 1048576, + "output": 1048576 }, "temperature": true, "tool_call": true, @@ -42219,31 +43859,19 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "effort_options": [ - "low", - "high", - "max" - ] + "supported": true } }, - "attachment": false, + "attachment": true, "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-04-24", - "last_updated": "2026-04-24", + "release_date": "2026-08-26", + "last_updated": "2026-08-26", "type": "chat" }, { - "id": "deepseek/deepseek-v4-flash-0731", - "name": "DeepSeek V4 Flash 0731", - "display_name": "DeepSeek V4 Flash 0731", + "id": "zai-org/GLM-5.2", + "name": "GLM 5.2", + "display_name": "GLM 5.2", "modalities": { "input": [ "text" @@ -42253,8 +43881,8 @@ ] }, "limit": { - "context": 1000000, - "output": 384000 + "context": 1048576, + "output": 1048576 }, "temperature": true, "tool_call": true, @@ -42264,31 +43892,19 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "effort_options": [ - "low", - "high", - "max" - ] + "supported": true } }, "attachment": false, "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-07-31", - "last_updated": "2026-07-31", + "release_date": "2026-06-13", + "last_updated": "2026-06-13", "type": "chat" }, { - "id": "deepseek/deepseek-v4-pro-0813", - "name": "DeepSeek V4 Pro 0813", - "display_name": "DeepSeek V4 Pro 0813", + "id": "zai-org/GLM-5.3", + "name": "GLM 5.3", + "display_name": "GLM 5.3", "modalities": { "input": [ "text" @@ -42298,8 +43914,8 @@ ] }, "limit": { - "context": 1000000, - "output": 384000 + "context": 1048576, + "output": 1048576 }, "temperature": true, "tool_call": true, @@ -42310,13 +43926,8 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], "effort_options": [ + "low", "high", "max" ] @@ -42324,14 +43935,14 @@ }, "attachment": false, "open_weights": true, - "release_date": "2026-08-13", - "last_updated": "2026-08-22", + "release_date": "2026-08-14", + "last_updated": "2026-08-14", "type": "chat" }, { - "id": "deepseek/deepseek-chat", - "name": "DeepSeek Chat", - "display_name": "DeepSeek Chat", + "id": "reflection/Beam-1.0-501B-A23B", + "name": "Beam 1.0 501B A23B", + "display_name": "Beam 1.0 501B A23B", "modalities": { "input": [ "text" @@ -42341,40 +43952,50 @@ ] }, "limit": { - "context": 1000000, - "output": 384000 + "context": 1048576, + "output": 1048576 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "minimal", + "low", + "medium", + "high", + "xhigh", + "max" + ] + } }, "attachment": false, "open_weights": true, - "knowledge": "2025-09", - "release_date": "2025-12-01", - "last_updated": "2026-02-28", + "knowledge": "2026-06-30", + "release_date": "2026-10-05", + "last_updated": "2026-10-16", "type": "chat" }, { - "id": "google/gemini-3-flash-preview", - "name": "Gemini 3 Flash Preview", - "display_name": "Gemini 3 Flash Preview", + "id": "google/gemma-4-26B-A4B-it", + "name": "Gemma 4 26B A4B", + "display_name": "Gemma 4 26B A4B", "modalities": { "input": [ "text", - "image", - "video", - "audio", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 262144, + "output": 262144 }, "temperature": true, "tool_call": true, @@ -42384,49 +44005,31 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "level", - "level": "high", - "level_options": [ - "minimal", - "low", - "medium", - "high" - ], - "summaries": true, - "visibility": "summary", - "continuation": [ - "thought_signatures" - ] + "supported": true } }, "attachment": true, - "open_weights": false, - "knowledge": "2025-01", - "release_date": "2025-12-17", - "last_updated": "2025-12-17", + "open_weights": true, + "release_date": "2026-04-02", + "last_updated": "2026-04-02", "type": "chat" }, { - "id": "google/gemini-2.5-pro", - "name": "Gemini 2.5 Pro", - "display_name": "Gemini 2.5 Pro", + "id": "google/gemma-4-31B-it", + "name": "Gemma 4 31B", + "display_name": "Gemma 4 31B", "modalities": { "input": [ "text", - "image", - "audio", - "video", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 262144, + "output": 262144 }, "temperature": true, "tool_call": true, @@ -42436,49 +44039,32 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "budget", - "budget": { - "default": -1, - "min": 128, - "max": 32768, - "auto": -1, - "unit": "tokens" - }, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thought_signatures" - ] + "supported": true } }, "attachment": true, - "open_weights": false, + "open_weights": true, "knowledge": "2025-01", - "release_date": "2025-06-17", - "last_updated": "2025-06-17", + "release_date": "2026-04-02", + "last_updated": "2026-04-02", "type": "chat" }, { - "id": "google/gemini-3.5-flash-lite", - "name": "Gemini 3.5 Flash Lite", - "display_name": "Gemini 3.5 Flash Lite", + "id": "MiniMaxAI/MiniMax-M3", + "name": "MiniMax M3", + "display_name": "MiniMax M3", "modalities": { "input": [ "text", - "image", - "video", - "audio", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 262144, + "output": 262144 }, "temperature": true, "tool_call": true, @@ -42488,38 +44074,30 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "effort_options": [ - "minimal", - "low", - "medium", - "high" - ] + "supported": true } }, "attachment": true, - "open_weights": false, - "knowledge": "2026-03", - "release_date": "2026-07-21", - "last_updated": "2026-07-21", + "open_weights": true, + "release_date": "2026-06-01", + "last_updated": "2026-06-01", "type": "chat" }, { - "id": "google/gemma-4-31b-it", - "name": "Gemma 4 31B IT", - "display_name": "Gemma 4 31B IT", + "id": "ibm-granite/granite-4.2-8b", + "name": "Granite 4.2 8B", + "display_name": "Granite 4.2 8B", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 32768 + "context": 131072, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -42527,29 +44105,20 @@ "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "supported": true - } - }, - "attachment": true, + "attachment": false, "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-04-02", - "last_updated": "2026-04-02", + "release_date": "2026-08-25", + "last_updated": "2026-08-25", "type": "chat" }, { - "id": "google/gemini-2.5-flash", - "name": "Gemini 2.5 Flash", - "display_name": "Gemini 2.5 Flash", + "id": "deepseek-ai/DeepSeek-V4.1-Flash", + "name": "DeepSeek V4.1 Flash", + "display_name": "DeepSeek V4.1 Flash", "modalities": { "input": [ "text", - "image", - "audio", - "video", - "pdf" + "image" ], "output": [ "text" @@ -42557,7 +44126,7 @@ }, "limit": { "context": 1048576, - "output": 65536 + "output": 1048576 }, "temperature": true, "tool_call": true, @@ -42567,94 +44136,58 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "budget", - "budget": { - "default": -1, - "min": 0, - "max": 24576, - "auto": -1, - "off": 0, - "unit": "tokens" - }, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thought_signatures" - ] + "supported": true } }, "attachment": true, - "open_weights": false, - "knowledge": "2025-01", - "release_date": "2025-06-17", - "last_updated": "2025-06-17", + "open_weights": true, + "knowledge": "2025-05", + "release_date": "2026-09-10", + "last_updated": "2026-09-10", "type": "chat" }, { - "id": "google/gemini-3.1-flash-lite", - "name": "Gemini 3.1 Flash Lite", - "display_name": "Gemini 3.1 Flash Lite", + "id": "deepseek-ai/DeepSeek-V3.1", + "name": "DeepSeek V3.1", + "display_name": "DeepSeek V3.1", "modalities": { "input": [ - "text", - "image", - "video", - "audio", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 161000, + "output": 161000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "effort_options": [ - "minimal", - "low", - "medium", - "high" - ] - } + "supported": false }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-01", - "release_date": "2026-05-07", - "last_updated": "2026-05-07", + "attachment": false, + "open_weights": true, + "release_date": "2025-08-21", + "last_updated": "2025-08-21", "type": "chat" }, { - "id": "google/gemini-3.1-flash-lite-preview", - "name": "Gemini 3.1 Flash Lite Preview", - "display_name": "Gemini 3.1 Flash Lite Preview", + "id": "deepseek-ai/DeepSeek-V4-Flash-0731", + "name": "DeepSeek V4 Flash 0731", + "display_name": "DeepSeek V4 Flash 0731", "modalities": { "input": [ - "text", - "image", - "video", - "audio", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 262144, + "output": 262144 }, "temperature": true, "tool_call": true, @@ -42664,37 +44197,31 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "minimal", - "low", - "medium", - "high" - ] + "supported": true } }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-01", - "release_date": "2026-03-03", - "last_updated": "2026-03-03", + "attachment": false, + "open_weights": true, + "knowledge": "2025-05", + "release_date": "2026-07-31", + "last_updated": "2026-07-31", "type": "chat" }, { - "id": "google/gemma-4-26b-a4b-it", - "name": "Gemma 4 26B A4B IT", - "display_name": "Gemma 4 26B A4B IT", + "id": "deepseek-ai/DeepSeek-V4-Pro-0813", + "name": "DeepSeek V4 Pro 0813", + "display_name": "DeepSeek V4 Pro 0813", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 32768 + "context": 1048576, + "output": 1048576 }, "temperature": true, "tool_call": true, @@ -42707,33 +44234,40 @@ "supported": true } }, - "attachment": true, + "attachment": false, "open_weights": true, - "release_date": "2026-04-02", - "last_updated": "2026-04-02", + "release_date": "2026-08-13", + "last_updated": "2026-08-22", "type": "chat" - }, + } + ] + }, + "kimi-code-plan-cn": { + "id": "kimi-code-plan-cn", + "name": "Kimi For Coding (kimi.com)", + "display_name": "Kimi For Coding (kimi.com)", + "api": "https://api.kimi.com/coding/v1", + "doc": "https://www.kimi.com/code/docs/en/kimi-code/models.html", + "models": [ { - "id": "google/gemini-3.1-pro-preview-customtools", - "name": "Gemini 3.1 Pro Preview Custom Tools", - "display_name": "Gemini 3.1 Pro Preview Custom Tools", + "id": "kimi-for-coding-highspeed", + "name": "Kimi For Coding HighSpeed", + "display_name": "Kimi For Coding HighSpeed", "modalities": { "input": [ "text", "image", - "video", - "audio", - "pdf" + "video" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 262144, + "output": 32768 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -42742,38 +44276,30 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "level", - "level": "high", - "level_options": [ - "low", - "high" - ], + "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ - "thought_signatures" + "thinking_blocks" ] } }, "attachment": true, - "open_weights": false, + "open_weights": true, "knowledge": "2025-01", - "release_date": "2026-02-19", - "last_updated": "2026-02-19", + "release_date": "2026-06-12", + "last_updated": "2026-06-12", "type": "chat" }, { - "id": "google/gemini-flash-latest", - "name": "Gemini Flash Latest", - "display_name": "Gemini Flash Latest", + "id": "k3.1-pro", + "name": "Kimi K3.1 Pro", + "display_name": "Kimi K3.1 Pro", "modalities": { "input": [ "text", "image", - "video", - "audio", - "pdf" + "video" ], "output": [ "text" @@ -42781,9 +44307,9 @@ }, "limit": { "context": 1048576, - "output": 65536 + "output": 131072 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -42791,31 +44317,35 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ "low", - "medium", - "high" + "high", + "max" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-03", - "release_date": "2026-08-13", - "last_updated": "2026-08-13", + "release_date": "2026-10-23", + "last_updated": "2026-10-23", "type": "chat" }, { - "id": "google/gemini-3.1-pro-preview", - "name": "Gemini 3.1 Pro Preview", - "display_name": "Gemini 3.1 Pro Preview", + "id": "k3", + "name": "Kimi K3", + "display_name": "Kimi K3", "modalities": { "input": [ "text", "image", - "video", - "audio", - "pdf" + "video" ], "output": [ "text" @@ -42823,9 +44353,9 @@ }, "limit": { "context": 1048576, - "output": 65536 + "output": 131072 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -42834,38 +44364,35 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "level", - "level": "high", - "level_options": [ - "low", - "high" - ], + "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ - "thought_signatures" + "thinking_blocks" + ], + "effort_options": [ + "none", + "low", + "high", + "max" ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2025-01", - "release_date": "2026-02-19", - "last_updated": "2026-02-19", + "open_weights": true, + "release_date": "2026-07-16", + "last_updated": "2026-07-16", "type": "chat" }, { - "id": "google/gemini-2.5-flash-lite", - "name": "Gemini 2.5 Flash-Lite", - "display_name": "Gemini 2.5 Flash-Lite", + "id": "k3.1-pro-highspeed", + "name": "Kimi K3.1 Pro Highspeed", + "display_name": "Kimi K3.1 Pro Highspeed", "modalities": { "input": [ "text", "image", - "audio", - "video", - "pdf" + "video" ], "output": [ "text" @@ -42873,61 +44400,54 @@ }, "limit": { "context": 1048576, - "output": 65536 + "output": 131072 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "budget", - "budget": { - "default": -1, - "min": 512, - "max": 24576, - "auto": -1, - "unit": "tokens" - }, + "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ - "thought_signatures" + "thinking_blocks" + ], + "effort_options": [ + "low", + "high", + "max" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-01", - "release_date": "2025-06-17", - "last_updated": "2025-06-17", + "release_date": "2026-10-23", + "last_updated": "2026-10-23", "type": "chat" }, { - "id": "google/gemini-3.6-flash", - "name": "Gemini 3.6 Flash", - "display_name": "Gemini 3.6 Flash", + "id": "k3-256k", + "name": "Kimi K3-256K", + "display_name": "Kimi K3-256K", "modalities": { "input": [ "text", - "image", - "video", - "audio", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 262144, + "output": 131072 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -42936,40 +44456,35 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "level", - "level": "high", - "level_options": [ - "minimal", - "low", - "medium", - "high" - ], + "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ - "thought_signatures" + "thinking_blocks" + ], + "effort_options": [ + "none", + "low", + "high", + "max" ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2026-03", - "release_date": "2026-07-21", - "last_updated": "2026-07-21", + "open_weights": true, + "release_date": "2026-07-16", + "last_updated": "2026-07-16", "type": "chat" }, { - "id": "google/gemini-3.5-flash", - "name": "Gemini 3.5 Flash", - "display_name": "Gemini 3.5 Flash", + "id": "kimi-for-coding", + "name": "Kimi K2.8 Preview", + "display_name": "Kimi K2.8 Preview", "modalities": { "input": [ "text", "image", - "video", - "audio", - "pdf" + "video" ], "output": [ "text" @@ -42977,9 +44492,9 @@ }, "limit": { "context": 1048576, - "output": 65536 + "output": 32768 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -42988,47 +44503,50 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "level", - "level": "high", - "level_options": [ - "minimal", - "low", - "medium", - "high" - ], + "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ - "thought_signatures" + "thinking_blocks" + ], + "effort_options": [ + "none", + "low", + "high", + "max" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-01", - "release_date": "2026-05-19", - "last_updated": "2026-05-19", + "release_date": "2026-09-11", + "last_updated": "2026-09-11", "type": "chat" - }, + } + ] + }, + "kuae-cloud-coding-plan": { + "id": "kuae-cloud-coding-plan", + "name": "KUAE Cloud Coding Plan", + "display_name": "KUAE Cloud Coding Plan", + "api": "https://coding-plan-endpoint.kuaecloud.net/v1", + "doc": "https://docs.mthreads.com/kuaecloud/kuaecloud-doc-online/coding_plan/", + "models": [ { - "id": "google/gemini-robotics-er-1.6-preview", - "name": "Gemini Robotics-ER 1.6 Preview", - "display_name": "Gemini Robotics-ER 1.6 Preview", + "id": "GLM-4.7", + "name": "GLM-4.7", + "display_name": "GLM-4.7", "modalities": { "input": [ - "text", - "image", - "video", - "audio" + "text" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 65536 + "context": 204800, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -43036,32 +44554,49 @@ "supported": true, "default": true }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-01", - "release_date": "2026-04-14", - "last_updated": "2026-04-14", + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } + }, + "attachment": false, + "open_weights": true, + "knowledge": "2025-04", + "release_date": "2025-12-22", + "last_updated": "2025-12-22", "type": "chat" - }, + } + ] + }, + "xpersona": { + "id": "xpersona", + "name": "Xpersona", + "display_name": "Xpersona", + "api": "https://www.xpersona.co/v1", + "doc": "https://www.xpersona.co/docs", + "models": [ { - "id": "google/gemini-flash-lite-latest", - "name": "Gemini Flash-Lite Latest", - "display_name": "Gemini Flash-Lite Latest", + "id": "gemini-3.5-flash", + "name": "Gemini 3.5 Flash", + "display_name": "Gemini 3.5 Flash", "modalities": { "input": [ "text", - "image", - "video", - "audio", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 1000000, + "output": 128000 }, "temperature": true, "tool_call": true, @@ -43071,38 +44606,47 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ + "supported": true, + "default_enabled": true, + "mode": "level", + "level": "high", + "level_options": [ "minimal", "low", "medium", "high" + ], + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-03", - "release_date": "2026-07-21", - "last_updated": "2026-07-21", + "knowledge": "2025-01", + "release_date": "2026-05-19", + "last_updated": "2026-05-19", "type": "chat" }, { - "id": "kimi/kimi-k3", - "name": "Kimi K3", - "display_name": "Kimi K3", + "id": "xpersona-frieren-coder", + "name": "Xpersona Frieren 1", + "display_name": "Xpersona Frieren 1", "modalities": { "input": [ "text", - "image", - "video" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 1000000, + "output": 384000 }, "temperature": false, "tool_call": true, @@ -43112,53 +44656,64 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], "effort_options": [ "low", + "medium", "high", + "xhigh", "max" ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-07-16", - "last_updated": "2026-07-16", + "open_weights": false, + "knowledge": "2025-12-30", + "release_date": "2026-05-01", + "last_updated": "2026-05-25", "type": "chat" }, { - "id": "kimi/kimi-k2.6", - "name": "Kimi K2.6", - "display_name": "Kimi K2.6", + "id": "gpt-5.4", + "name": "GPT-5.4", + "display_name": "GPT-5.4", "modalities": { "input": [ "text", - "image", - "video" + "image" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 32768 + "context": 1050000, + "output": 128000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": false, + "mode": "effort", + "effort": "none", + "effort_options": [ + "none", + "low", + "medium", + "high", + "xhigh" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], "interleaved": true, "summaries": true, "visibility": "summary", @@ -43168,39 +44723,54 @@ } }, "attachment": true, - "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-04-20", - "last_updated": "2026-04-20", + "open_weights": false, + "knowledge": "2025-08-31", + "release_date": "2026-03-05", + "last_updated": "2026-03-05", "type": "chat" }, { - "id": "kimi/kimi-k2.5", - "name": "Kimi K2.5", - "display_name": "Kimi K2.5", + "id": "gpt-5.4-mini", + "name": "GPT-5.4 mini", + "display_name": "GPT-5.4 mini", "modalities": { "input": [ "text", - "image", - "video" + "image" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 32768 + "context": 272000, + "output": 128000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": false, + "mode": "effort", + "effort": "none", + "effort_options": [ + "none", + "low", + "medium", + "high", + "xhigh" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], "interleaved": true, "summaries": true, "visibility": "summary", @@ -43210,29 +44780,28 @@ } }, "attachment": true, - "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-01-27", - "last_updated": "2026-01-27", + "open_weights": false, + "knowledge": "2025-08-31", + "release_date": "2026-03-17", + "last_updated": "2026-03-17", "type": "chat" }, { - "id": "kimi/kimi-k2.7-code", - "name": "Kimi K2.7 Code", - "display_name": "Kimi K2.7 Code", + "id": "gpt-5.6-terra", + "name": "GPT-5.6 Terra", + "display_name": "GPT-5.6 Terra", "modalities": { "input": [ "text", - "image", - "video" + "image" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 372000, + "output": 128000 }, "temperature": false, "tool_call": true, @@ -43243,6 +44812,23 @@ "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", + "effort_options": [ + "none", + "low", + "medium", + "high", + "xhigh", + "max" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], "interleaved": true, "summaries": true, "visibility": "summary", @@ -43252,16 +44838,16 @@ } }, "attachment": true, - "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-06-12", - "last_updated": "2026-06-12", + "open_weights": false, + "knowledge": "2026-02-16", + "release_date": "2026-07-09", + "last_updated": "2026-07-09", "type": "chat" }, { - "id": "openai/gpt-5-chat-latest", - "name": "GPT-5 Chat (latest)", - "display_name": "GPT-5 Chat (latest)", + "id": "claude-haiku-4-5", + "name": "Claude Haiku 4.5 (latest)", + "display_name": "Claude Haiku 4.5 (latest)", "modalities": { "input": [ "text", @@ -43272,26 +44858,47 @@ ] }, "limit": { - "context": 400000, - "output": 100000 + "context": 200000, + "output": 128000 }, - "temperature": false, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": false, + "mode": "budget", + "budget": { + "min": 1024, + "unit": "tokens" + }, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", + "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." + ] + } }, "attachment": true, "open_weights": false, - "knowledge": "2024-09-30", - "release_date": "2025-08-07", - "last_updated": "2025-08-07", + "knowledge": "2025-02-28", + "release_date": "2025-10-15", + "last_updated": "2025-10-15", "type": "chat" }, { - "id": "openai/gpt-5", - "name": "GPT-5", - "display_name": "GPT-5", + "id": "claude-fable-5", + "name": "Claude Fable 5", + "display_name": "Claude Fable 5", "modalities": { "input": [ "text", @@ -43302,7 +44909,7 @@ ] }, "limit": { - "context": 400000, + "context": 1000000, "output": 128000 }, "temperature": false, @@ -43316,75 +44923,49 @@ "supported": true, "default_enabled": true, "mode": "effort", - "effort": "medium", + "effort": "high", "effort_options": [ - "minimal", "low", "medium", - "high" + "high", + "xhigh", + "max" ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ], - "visibility": "hidden" + "notes": [ + "Adaptive thinking is always on for Claude Fable 5 and Claude Mythos 5; thinking.type = \"disabled\" is rejected.", + "Manual budget_tokens requests return 400 on Claude Fable 5 and Claude Mythos 5.", + "thinking.display defaults to omitted; set display to summarized to receive readable thinking summaries." + ] } }, "attachment": true, "open_weights": false, - "knowledge": "2024-09-30", - "release_date": "2025-08-07", - "last_updated": "2025-08-07", - "type": "chat" - }, - { - "id": "openai/gpt-4o-mini", - "name": "GPT-4o mini", - "display_name": "GPT-4o mini", - "modalities": { - "input": [ - "text", - "image", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 16384 - }, - "temperature": true, - "tool_call": true, - "reasoning": { - "supported": false - }, - "attachment": true, - "open_weights": false, - "knowledge": "2023-09", - "release_date": "2024-07-18", - "last_updated": "2024-07-18", + "knowledge": "2026-01-31", + "release_date": "2026-06-09", + "last_updated": "2026-06-09", "type": "chat" }, { - "id": "openai/gpt-5.5", + "id": "xpersona-gpt-5.5", "name": "GPT-5.5", "display_name": "GPT-5.5", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1050000, + "context": 1000000, "output": 128000 }, "temperature": false, @@ -43396,35 +44977,32 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ "low", "medium", "high", - "xhigh" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + "xhigh", + "max" + ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-12-01", + "knowledge": "2025-12-30", "release_date": "2026-04-23", "last_updated": "2026-04-23", "type": "chat" }, { - "id": "openai/gpt-4.1-nano", - "name": "GPT-4.1 nano", - "display_name": "GPT-4.1 nano", + "id": "gpt-5.6-sol", + "name": "GPT-5.6 Sol", + "display_name": "GPT-5.6 Sol", "modalities": { "input": [ "text", @@ -43435,57 +45013,28 @@ ] }, "limit": { - "context": 1047576, - "output": 32768 - }, - "temperature": true, - "tool_call": true, - "reasoning": { - "supported": false - }, - "attachment": true, - "open_weights": false, - "knowledge": "2024-04", - "release_date": "2025-04-14", - "last_updated": "2025-04-14", - "type": "chat" - }, - { - "id": "openai/gpt-5.4", - "name": "GPT-5.4", - "display_name": "GPT-5.4", - "modalities": { - "input": [ - "text", - "image", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 1050000, + "context": 372000, "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, + "default_enabled": true, "mode": "effort", - "effort": "none", + "effort": "medium", "effort_options": [ "none", "low", "medium", "high", - "xhigh" + "xhigh", + "max" ], "verbosity": "medium", "verbosity_options": [ @@ -43493,50 +45042,25 @@ "medium", "high" ], - "visibility": "hidden" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-03-05", - "last_updated": "2026-03-05", - "type": "chat" - }, - { - "id": "openai/gpt-5.1-chat-latest", - "name": "GPT-5.1 Chat", - "display_name": "GPT-5.1 Chat", - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 16384 - }, - "temperature": false, - "tool_call": true, - "reasoning": { - "supported": true, - "default": true - }, - "attachment": true, - "open_weights": false, - "knowledge": "2024-09-30", - "release_date": "2025-11-13", - "last_updated": "2025-11-13", + "knowledge": "2026-02-16", + "release_date": "2026-07-09", + "last_updated": "2026-07-09", "type": "chat" }, { - "id": "openai/gpt-4-turbo", - "name": "GPT-4 Turbo", - "display_name": "GPT-4 Turbo", + "id": "claude-sonnet-4-6", + "name": "Claude Sonnet 4.6", + "display_name": "Claude Sonnet 4.6", "modalities": { "input": [ "text", @@ -43547,100 +45071,54 @@ ] }, "limit": { - "context": 128000, - "output": 4096 - }, - "temperature": true, - "tool_call": true, - "reasoning": { - "supported": false - }, - "attachment": true, - "open_weights": false, - "knowledge": "2023-12", - "release_date": "2023-11-06", - "last_updated": "2024-04-09", - "type": "chat" - }, - { - "id": "openai/gpt-4o", - "name": "GPT-4o", - "display_name": "GPT-4o", - "modalities": { - "input": [ - "text", - "image", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 16384 + "context": 200000, + "output": 128000 }, "temperature": true, "tool_call": true, - "reasoning": { - "supported": false - }, - "attachment": true, - "open_weights": false, - "knowledge": "2023-09", - "release_date": "2024-05-13", - "last_updated": "2024-08-06", - "type": "chat" - }, - { - "id": "openai/gpt-5-pro", - "name": "GPT-5 Pro", - "display_name": "GPT-5 Pro", - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 400000, - "output": 272000 - }, - "temperature": false, - "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "fixed", + "default_enabled": false, + "mode": "mixed", + "budget": { + "min": 1024, + "unit": "tokens" + }, "effort": "high", - "verbosity": "medium", - "verbosity_options": [ + "effort_options": [ "low", "medium", - "high" + "high", + "max" ], - "visibility": "hidden" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Anthropic recommends adaptive thinking with effort for Claude 4.6; budget_tokens remains a deprecated compatibility path.", + "Anthropic API defaults effort to high; lower effort levels should be chosen per workload." + ] } }, "attachment": true, "open_weights": false, - "knowledge": "2024-09-30", - "release_date": "2025-10-06", - "last_updated": "2025-10-06", + "knowledge": "2025-08-31", + "release_date": "2026-02-17", + "last_updated": "2026-03-13", "type": "chat" }, { - "id": "openai/gpt-5.2-chat-latest", - "name": "GPT-5.2 Chat", - "display_name": "GPT-5.2 Chat", + "id": "claude-opus-4-8", + "name": "Claude Opus 4.8", + "display_name": "Claude Opus 4.8", "modalities": { "input": [ "text", @@ -43651,38 +45129,56 @@ ] }, "limit": { - "context": 128000, - "output": 16384 + "context": 200000, + "output": 128000 }, "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": false, + "mode": "effort", + "effort": "high", "effort_options": [ - "medium" + "low", + "medium", + "high", + "xhigh", + "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Claude Opus 4.7 and newer Opus models require thinking.type = \"adaptive\" to enable thinking explicitly.", + "Manual budget_tokens requests return 400 on Claude Opus 4.7 and newer adaptive-only Opus models.", + "task_budget is separate from thinking control and should not be treated as a thinking budget." ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2025-12-11", - "last_updated": "2025-12-11", + "knowledge": "2026-01", + "release_date": "2026-05-28", + "last_updated": "2026-05-28", "type": "chat" }, { - "id": "openai/gpt-5.6-sol", - "name": "GPT-5.6 Sol", - "display_name": "GPT-5.6 Sol", + "id": "gpt-5.5", + "name": "GPT-5.5", + "display_name": "GPT-5.5", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" @@ -43705,12 +45201,10 @@ "mode": "effort", "effort": "medium", "effort_options": [ - "none", "low", "medium", "high", - "xhigh", - "max" + "xhigh" ], "verbosity": "medium", "verbosity_options": [ @@ -43718,20 +45212,25 @@ "medium", "high" ], - "visibility": "hidden" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-02-16", - "release_date": "2026-07-09", - "last_updated": "2026-07-09", + "knowledge": "2025-12-01", + "release_date": "2026-04-23", + "last_updated": "2026-04-23", "type": "chat" }, { - "id": "openai/gpt-4o-2024-11-20", - "name": "GPT-4o (2024-11-20)", - "display_name": "GPT-4o (2024-11-20)", + "id": "gpt-5.6", + "name": "GPT-5.6", + "display_name": "GPT-5.6", "modalities": { "input": [ "text", @@ -43742,37 +45241,7 @@ ] }, "limit": { - "context": 128000, - "output": 16384 - }, - "temperature": true, - "tool_call": true, - "reasoning": { - "supported": false - }, - "attachment": true, - "open_weights": false, - "knowledge": "2023-09", - "release_date": "2024-11-20", - "last_updated": "2024-11-20", - "type": "chat" - }, - { - "id": "openai/gpt-5.6-terra", - "name": "GPT-5.6 Terra", - "display_name": "GPT-5.6 Terra", - "modalities": { - "input": [ - "text", - "image", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 1050000, + "context": 372000, "output": 128000 }, "temperature": false, @@ -43801,7 +45270,12 @@ "medium", "high" ], - "visibility": "hidden" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] } }, "attachment": true, @@ -43810,24 +45284,34 @@ "release_date": "2026-07-09", "last_updated": "2026-07-09", "type": "chat" - }, + } + ] + }, + "sakana": { + "id": "sakana", + "name": "Sakana AI", + "display_name": "Sakana AI", + "api": "https://api.sakana.ai/v1", + "doc": "https://console.sakana.ai/models", + "models": [ { - "id": "openai/gpt-oss-120b", - "name": "GPT OSS 120B", - "display_name": "GPT OSS 120B", + "id": "fugu-ultra-20260615", + "name": "Fugu Ultra", + "display_name": "Fugu Ultra", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 32768 + "context": 1000000, + "output": 1000000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -43835,25 +45319,22 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, "effort_options": [ - "low", - "medium", - "high" + "high", + "xhigh" ] } }, - "attachment": false, - "open_weights": true, - "knowledge": "2024-06", - "release_date": "2025-08-05", - "last_updated": "2025-08-05", + "attachment": true, + "open_weights": false, + "release_date": "2026-06-15", + "last_updated": "2026-06-15", "type": "chat" }, { - "id": "openai/gpt-5.1-codex", - "name": "GPT-5.1 Codex", - "display_name": "GPT-5.1 Codex", + "id": "fugu", + "name": "Fugu", + "display_name": "Fugu", "modalities": { "input": [ "text", @@ -43864,47 +45345,33 @@ ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 1000000, + "output": 1000000 }, "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "none", "effort_options": [ - "none", - "low", - "medium", - "high" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + "high", + "xhigh" + ] } }, "attachment": true, "open_weights": false, - "knowledge": "2024-09-30", - "release_date": "2025-11-13", - "last_updated": "2025-11-13", + "release_date": "2026-06-15", + "last_updated": "2026-06-15", "type": "chat" }, { - "id": "openai/gpt-5-mini", - "name": "GPT-5 Mini", - "display_name": "GPT-5 Mini", + "id": "fugu-ultra", + "name": "Fugu Ultra", + "display_name": "Fugu Ultra", "modalities": { "input": [ "text", @@ -43915,8 +45382,8 @@ ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 1000000, + "output": 1000000 }, "temperature": false, "tool_call": true, @@ -43926,65 +45393,61 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", "effort_options": [ - "minimal", - "low", - "medium", - "high" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + "high", + "xhigh" + ] } }, "attachment": true, "open_weights": false, - "knowledge": "2024-05-30", - "release_date": "2025-08-07", - "last_updated": "2025-08-07", + "release_date": "2026-06-15", + "last_updated": "2026-06-15", "type": "chat" }, { - "id": "openai/gpt-4o-2024-08-06", - "name": "GPT-4o (2024-08-06)", - "display_name": "GPT-4o (2024-08-06)", + "id": "sakana-namazu", + "name": "Sakana Namazu", + "display_name": "Sakana Namazu", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 16384 + "context": 262144, + "output": 65536 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, "attachment": true, "open_weights": false, - "knowledge": "2023-09", - "release_date": "2024-08-06", - "last_updated": "2024-08-06", + "release_date": "2026-08-03", + "last_updated": "2026-08-03", "type": "chat" - }, + } + ] + }, + "inceptron": { + "id": "inceptron", + "name": "Inceptron", + "display_name": "Inceptron", + "api": "https://api.inceptron.io/v1", + "doc": "https://docs.inceptron.io", + "models": [ { - "id": "openai/gpt-5.4-pro", - "name": "GPT-5.4 Pro", - "display_name": "GPT-5.4 Pro", + "id": "moonshotai/Kimi-K2.7-Code", + "name": "Kimi K2.7 Code", + "display_name": "Kimi K2.7 Code", "modalities": { "input": [ "text", @@ -43995,10 +45458,10 @@ ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 262144, + "output": 262144 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -44007,34 +45470,25 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "high", - "effort_options": [ - "medium", - "high", - "xhigh" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-03-05", - "last_updated": "2026-03-05", + "open_weights": true, + "knowledge": "2025-01", + "release_date": "2026-06-12", + "last_updated": "2026-06-12", "type": "chat" }, { - "id": "openai/gpt-5.1-codex-mini", - "name": "GPT-5.1 Codex mini", - "display_name": "GPT-5.1 Codex mini", + "id": "moonshotai/Kimi-K2.6", + "name": "Kimi K2.6", + "display_name": "Kimi K2.6", "modalities": { "input": [ "text", @@ -44045,47 +45499,37 @@ ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 262144, + "output": 262144 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "none", - "effort_options": [ - "none", - "low", - "medium", - "high" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2024-09-30", - "release_date": "2025-11-13", - "last_updated": "2025-11-13", + "open_weights": true, + "knowledge": "2025-01", + "release_date": "2026-04-20", + "last_updated": "2026-04-20", "type": "chat" }, { - "id": "openai/gpt-4", - "name": "GPT-4", - "display_name": "GPT-4", + "id": "zai-org/GLM-5.3-Flash", + "name": "GLM 5.3 Flash", + "display_name": "GLM 5.3 Flash", "modalities": { "input": [ "text" @@ -44095,91 +45539,97 @@ ] }, "limit": { - "context": 8192, - "output": 8192 + "context": 1048576, + "output": 1048576 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "effort_options": [ + "low", + "high", + "max" + ] + } }, "attachment": false, - "open_weights": false, - "knowledge": "2023-11", - "release_date": "2023-11-06", - "last_updated": "2024-04-09", + "open_weights": true, + "release_date": "2026-08-26", + "last_updated": "2026-08-26", "type": "chat" }, { - "id": "openai/gpt-5.1", - "name": "GPT-5.1", - "display_name": "GPT-5.1", + "id": "zai-org/GLM-5.2", + "name": "GLM 5.2", + "display_name": "GLM 5.2", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 1048576, + "output": 1048576 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "none", - "effort_options": [ - "none", - "low", - "medium", - "high" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ], - "visibility": "hidden" + "effort_options": [ + "high", + "max" + ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2024-09-30", - "release_date": "2025-11-13", - "last_updated": "2025-11-13", + "attachment": false, + "open_weights": true, + "release_date": "2026-06-13", + "last_updated": "2026-06-13", "type": "chat" }, { - "id": "openai/gpt-5.6-luna", - "name": "GPT-5.6 Luna", - "display_name": "GPT-5.6 Luna", + "id": "zai-org/GLM-5.3", + "name": "GLM 5.3", + "display_name": "GLM 5.3", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 1048576, + "output": 1048576 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -44188,104 +45638,97 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ - "none", "low", - "medium", "high", - "xhigh", "max" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-02-16", - "release_date": "2026-07-09", - "last_updated": "2026-07-09", + "attachment": false, + "open_weights": true, + "release_date": "2026-08-14", + "last_updated": "2026-08-14", "type": "chat" }, { - "id": "openai/gpt-5.4-mini", - "name": "GPT-5.4 mini", - "display_name": "GPT-5.4 mini", + "id": "deepseek-ai/DeepSeek-V4-Flash-0731", + "name": "DeepSeek V4 Flash 0731", + "display_name": "DeepSeek V4 Flash 0731", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 1048576, + "output": 1048576 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "none", + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ - "none", - "low", - "medium", "high", - "xhigh" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + "max" + ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-03-17", - "last_updated": "2026-03-17", + "attachment": false, + "open_weights": true, + "knowledge": "2025-05", + "release_date": "2026-07-31", + "last_updated": "2026-07-31", "type": "chat" - }, + } + ] + }, + "moonshot": { + "id": "moonshot", + "name": "Moonshot AI (China)", + "display_name": "Moonshot AI (China)", + "api": "https://api.moonshot.cn/v1", + "doc": "https://platform.moonshot.cn/docs/api/chat", + "models": [ { - "id": "openai/gpt-5.3-codex", - "name": "GPT-5.3 Codex", - "display_name": "GPT-5.3 Codex", + "id": "kimi-k3", + "name": "Kimi K3", + "display_name": "Kimi K3", "modalities": { "input": [ "text", "image", - "pdf" + "video" ], "output": [ "text" ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 1048576, + "output": 1048576 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -44294,130 +45737,130 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ "low", - "medium", "high", - "xhigh" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + "max" + ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-02-05", - "last_updated": "2026-02-05", + "open_weights": true, + "release_date": "2026-07-16", + "last_updated": "2026-07-16", "type": "chat" }, { - "id": "openai/gpt-4.1-mini", - "name": "GPT-4.1 mini", - "display_name": "GPT-4.1 mini", + "id": "kimi-k2.7-code", + "name": "Kimi K2.7 Code", + "display_name": "Kimi K2.7 Code", "modalities": { "input": [ "text", "image", - "pdf" + "video" ], "output": [ "text" ] }, "limit": { - "context": 1047576, - "output": 32768 + "context": 262144, + "output": 262144 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "fixed", + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } }, "attachment": true, - "open_weights": false, - "knowledge": "2024-04", - "release_date": "2025-04-14", - "last_updated": "2025-04-14", + "open_weights": true, + "knowledge": "2025-01", + "release_date": "2026-04-21", + "last_updated": "2026-04-21", "type": "chat" }, { - "id": "openai/gpt-5.4-nano", - "name": "GPT-5.4 nano", - "display_name": "GPT-5.4 nano", + "id": "kimi-k2.7-code-highspeed", + "name": "Kimi K2.7 Code Highspeed", + "display_name": "Kimi K2.7 Code Highspeed", "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 262144, + "output": 262144 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "none", - "effort_options": [ - "none", - "low", - "medium", - "high", - "xhigh" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + "default_enabled": true, + "mode": "fixed", + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-03-17", - "last_updated": "2026-03-17", + "open_weights": true, + "knowledge": "2025-01", + "release_date": "2026-04-21", + "last_updated": "2026-04-21", "type": "chat" }, { - "id": "openai/gpt-5.5-pro", - "name": "GPT-5.5 Pro", - "display_name": "GPT-5.5 Pro", + "id": "kimi-k3.1-pro-highspeed", + "name": "Kimi K3.1 Pro Highspeed", + "display_name": "Kimi K3.1 Pro Highspeed", "modalities": { "input": [ "text", "image", - "pdf" + "video" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 100000 + "context": 1048576, + "output": 1048576 }, "temperature": false, "tool_call": true, @@ -44428,36 +45871,42 @@ "extra_capabilities": { "reasoning": { "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ - "medium", + "low", "high", - "xhigh" + "max" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-12-01", - "release_date": "2026-04-23", - "last_updated": "2026-04-23", + "release_date": "2026-10-23", + "last_updated": "2026-10-23", "type": "chat" }, { - "id": "openai/gpt-5.2-pro", - "name": "GPT-5.2 Pro", - "display_name": "GPT-5.2 Pro", + "id": "kimi-k3.1-pro", + "name": "Kimi K3.1 Pro", + "display_name": "Kimi K3.1 Pro", "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 1048576, + "output": 1048576 }, "temperature": false, "tool_call": true, @@ -44468,96 +45917,85 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "high", - "effort_options": [ - "medium", - "high", - "xhigh" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ], - "verbosity": "medium", - "verbosity_options": [ + "effort_options": [ "low", - "medium", - "high" - ], - "visibility": "hidden" + "high", + "max" + ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2025-12-11", - "last_updated": "2025-12-11", + "release_date": "2026-10-23", + "last_updated": "2026-10-23", "type": "chat" }, { - "id": "openai/gpt-4.1", - "name": "GPT-4.1", - "display_name": "GPT-4.1", + "id": "kimi-k2.6", + "name": "Kimi K2.6", + "display_name": "Kimi K2.6", "modalities": { "input": [ "text", "image", - "pdf" + "video" ], "output": [ "text" ] }, "limit": { - "context": 1047576, - "output": 32768 + "context": 262144, + "output": 262144 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { - "supported": false - }, - "attachment": true, - "open_weights": false, - "knowledge": "2024-04", - "release_date": "2025-04-14", - "last_updated": "2025-04-14", - "type": "chat" - }, - { - "id": "openai/gpt-3.5-turbo", - "name": "GPT-3.5-turbo", - "display_name": "GPT-3.5-turbo", - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 16385, - "output": 4096 + "supported": true, + "default": true }, - "temperature": true, - "tool_call": false, - "reasoning": { - "supported": false + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } }, - "attachment": false, - "open_weights": false, - "knowledge": "2021-09-01", - "release_date": "2023-03-01", - "last_updated": "2023-11-06", + "attachment": true, + "open_weights": true, + "knowledge": "2025-01", + "release_date": "2026-04-20", + "last_updated": "2026-04-20", "type": "chat" - }, + } + ] + }, + "nova": { + "id": "nova", + "name": "Nova", + "display_name": "Nova", + "api": "https://api.nova.amazon.com/v1", + "doc": "https://nova.amazon.com/dev/documentation", + "models": [ { - "id": "openai/gpt-5.2-codex", - "name": "GPT-5.2 Codex", - "display_name": "GPT-5.2 Codex", + "id": "nova-2-lite-v1", + "name": "Nova 2 Lite", + "display_name": "Nova 2 Lite", "modalities": { "input": [ "text", "image", + "video", "pdf" ], "output": [ @@ -44565,10 +46003,10 @@ ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 1000000, + "output": 64000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -44576,168 +46014,137 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", "effort_options": [ - "low", - "medium", - "high", - "xhigh" - ], - "verbosity": "medium", - "verbosity_options": [ "low", "medium", "high" - ], - "visibility": "hidden" + ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2025-12-11", - "last_updated": "2025-12-11", + "knowledge": "2025-10", + "release_date": "2025-12-02", + "last_updated": "2025-12-02", "type": "chat" }, { - "id": "openai/gpt-4o-2024-05-13", - "name": "GPT-4o (2024-05-13)", - "display_name": "GPT-4o (2024-05-13)", + "id": "nova-2-pro-v1", + "name": "Nova 2 Pro", + "display_name": "Nova 2 Pro", "modalities": { "input": [ "text", - "image" + "image", + "video", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 4096 + "context": 1000000, + "output": 64000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, "attachment": true, "open_weights": false, - "knowledge": "2023-09", - "release_date": "2024-05-13", - "last_updated": "2024-05-13", + "release_date": "2025-12-03", + "last_updated": "2026-01-03", "type": "chat" - }, + } + ] + }, + "nebius": { + "id": "nebius", + "name": "Nebius Token Factory", + "display_name": "Nebius Token Factory", + "api": "https://api.tokenfactory.nebius.com/v1", + "doc": "https://docs.tokenfactory.nebius.com/", + "models": [ { - "id": "openai/gpt-5.2", - "name": "GPT-5.2", - "display_name": "GPT-5.2", + "id": "openai/gpt-oss-120b", + "name": "gpt-oss-120b", + "display_name": "gpt-oss-120b", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 131072, + "output": 8192 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "none", - "effort_options": [ - "none", - "low", - "medium", - "high", - "xhigh" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ], - "verbosity": "medium", - "verbosity_options": [ + "effort_options": [ "low", "medium", "high" - ], - "visibility": "hidden" + ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2025-12-11", - "last_updated": "2025-12-11", + "attachment": false, + "open_weights": true, + "knowledge": "2024-06", + "release_date": "2025-08-05", + "last_updated": "2025-08-05", "type": "chat" }, { - "id": "openai/gpt-5-nano", - "name": "GPT-5 Nano", - "display_name": "GPT-5 Nano", + "id": "Qwen/Qwen3-30B-A3B-Instruct-2507", + "name": "Qwen3-30B-A3B-Instruct-2507", + "display_name": "Qwen3-30B-A3B-Instruct-2507", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 262144, + "output": 8192 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", - "effort_options": [ - "minimal", - "low", - "medium", - "high" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" - } + "supported": false }, - "attachment": true, - "open_weights": false, - "knowledge": "2024-05-30", - "release_date": "2025-08-07", - "last_updated": "2025-08-07", + "attachment": false, + "open_weights": true, + "knowledge": "2025-12", + "release_date": "2025-07-28", + "last_updated": "2025-07-28", "type": "chat" }, { - "id": "tencent/hy3", - "name": "Hy3", - "display_name": "Hy3", + "id": "Qwen/Qwen3.5-397B-A17B", + "name": "Qwen3.5-397B-A17B", + "display_name": "Qwen3.5-397B-A17B", "modalities": { "input": [ "text" @@ -44747,8 +46154,8 @@ ] }, "limit": { - "context": 256000, - "output": 128000 + "context": 262144, + "output": 8192 }, "temperature": true, "tool_call": true, @@ -44759,23 +46166,25 @@ "extra_capabilities": { "reasoning": { "supported": true, - "effort_options": [ - "none", - "low", - "high" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, "attachment": false, "open_weights": true, - "release_date": "2026-07-06", - "last_updated": "2026-07-06", + "knowledge": "2025-07", + "release_date": "2026-02-15", + "last_updated": "2026-02-15", "type": "chat" }, { - "id": "tencent/hy3-free", - "name": "Hy3 (free)", - "display_name": "Hy3 (free)", + "id": "Qwen/Qwen3-Embedding-8B", + "name": "Qwen3-Embedding-8B", + "display_name": "Qwen3-Embedding-8B", "modalities": { "input": [ "text" @@ -44785,35 +46194,25 @@ ] }, "limit": { - "context": 256000, - "output": 128000 + "context": 40960, + "output": 8192 }, - "temperature": true, - "tool_call": true, + "temperature": false, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "effort_options": [ - "none", - "low", - "high" - ] - } + "supported": false }, "attachment": false, "open_weights": true, - "release_date": "2026-07-06", - "last_updated": "2026-07-06", - "type": "chat" + "knowledge": "2025-10", + "release_date": "2025-06-05", + "last_updated": "2025-06-05", + "type": "embedding" }, { - "id": "grok/grok-4.5", - "name": "Grok 4.5", - "display_name": "Grok 4.5", + "id": "Qwen/Qwen3.8-27B", + "name": "Qwen3.8 27B", + "display_name": "Qwen3.8 27B", "modalities": { "input": [ "text", @@ -44824,8 +46223,8 @@ ] }, "limit": { - "context": 500000, - "output": 500000 + "context": 262144, + "output": 262144 }, "temperature": true, "tool_call": true, @@ -44836,76 +46235,69 @@ "extra_capabilities": { "reasoning": { "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ + "none", "low", "medium", - "high" + "xhigh" ] } }, "attachment": true, - "open_weights": false, - "release_date": "2026-07-08", - "last_updated": "2026-07-08", + "open_weights": true, + "release_date": "2026-08-14", + "last_updated": "2026-08-14", "type": "chat" }, { - "id": "grok/grok-4.3", - "name": "Grok 4.3", - "display_name": "Grok 4.3", + "id": "Qwen/Qwen3-235B-A22B-Instruct-2507", + "name": "Qwen3 235B A22B Instruct 2507", + "display_name": "Qwen3 235B A22B Instruct 2507", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 30000 + "context": 262144, + "output": 8192 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "effort_options": [ - "none", - "low", - "medium", - "high" - ] - } + "supported": false }, - "attachment": true, - "open_weights": false, - "release_date": "2026-04-17", - "last_updated": "2026-04-17", + "attachment": false, + "open_weights": true, + "knowledge": "2025-07", + "release_date": "2025-07-21", + "last_updated": "2025-07-21", "type": "chat" }, { - "id": "grok/grok-4.6", - "name": "Grok 4.6", - "display_name": "Grok 4.6", + "id": "nvidia/Nemotron-3_5-Lightning", + "name": "Nemotron 3.5 Lightning 30B A3B", + "display_name": "Nemotron 3.5 Lightning 30B A3B", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 500000, - "output": 500000 + "context": 1048576, + "output": 1048576 }, "temperature": true, "tool_call": true, @@ -44915,26 +46307,23 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, "effort_options": [ - "low", - "medium", - "high", - "xhigh" + "none", + "high" ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-02-01", - "release_date": "2026-08-12", - "last_updated": "2026-08-12", + "attachment": false, + "open_weights": true, + "knowledge": "2025-09", + "release_date": "2026-08-11", + "last_updated": "2026-08-11", "type": "chat" }, { - "id": "qwen/qwen3.7-max", - "name": "Qwen3.7 Max", - "display_name": "Qwen3.7 Max", + "id": "nvidia/nemotron-3-super-120b-a12b", + "name": "Nemotron-3-Super-120B-A12B", + "display_name": "Nemotron-3-Super-120B-A12B", "modalities": { "input": [ "text" @@ -44944,8 +46333,8 @@ ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 262144, + "output": 32768 }, "temperature": true, "tool_call": true, @@ -44953,37 +46342,36 @@ "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "supported": true - } - }, "attachment": false, - "open_weights": false, - "release_date": "2026-05-21", - "last_updated": "2026-05-21", + "open_weights": true, + "knowledge": "2025-06", + "release_date": "2026-03-11", + "last_updated": "2026-03-11", "type": "chat" }, { - "id": "qwen/qwen3-max", - "name": "Qwen3 Max", - "display_name": "Qwen3 Max", + "id": "nvidia/Nemotron-3.5-Super-VL", + "name": "Nemotron 3.5 Super VL 120B A12B", + "display_name": "Nemotron 3.5 Super VL 120B A12B", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 65536 + "context": 1048576, + "output": 1048576 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true + "supported": true, + "default": true }, "extra_capabilities": { "reasoning": { @@ -44993,33 +46381,34 @@ "visibility": "summary", "continuation": [ "thinking_blocks" + ], + "effort_options": [ + "none", + "high" ] } }, - "attachment": false, - "open_weights": false, - "knowledge": "2025-04", - "release_date": "2025-09-23", - "last_updated": "2025-09-23", + "attachment": true, + "open_weights": true, + "release_date": "2026-10-15", + "last_updated": "2026-10-15", "type": "chat" }, { - "id": "qwen/qwen3.8-27b", - "name": "Qwen3.8 27B", - "display_name": "Qwen3.8 27B", + "id": "nvidia/Nemotron-3-Ultra-550b-a55b", + "name": "Nemotron 3 Ultra 550B A55B", + "display_name": "Nemotron 3 Ultra 550B A55B", "modalities": { "input": [ - "text", - "image", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 32768 + "context": 1048576, + "output": 1048576 }, "temperature": true, "tool_call": true, @@ -45029,68 +46418,78 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, "effort_options": [ "none", - "low", - "medium", - "xhigh" + "high" ] } }, - "attachment": true, + "attachment": false, "open_weights": true, - "release_date": "2026-08-14", - "last_updated": "2026-08-14", + "knowledge": "2025-09", + "release_date": "2026-06-04", + "last_updated": "2026-06-04", "type": "chat" }, { - "id": "qwen/qwen3-vl-235b-a22b-instruct", - "name": "Qwen3 VL 235B A22B Instruct", - "display_name": "Qwen3 VL 235B A22B Instruct", + "id": "moonshotai/Kimi-K2.7-Code", + "name": "Kimi K2.7 Code", + "display_name": "Kimi K2.7 Code", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 32768 + "context": 262144, + "output": 8000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": true, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } + }, + "attachment": false, "open_weights": true, - "knowledge": "2025-03-31", - "release_date": "2025-09-23", - "last_updated": "2025-09-23", + "knowledge": "2025-01", + "release_date": "2026-06-12", + "last_updated": "2026-06-12", "type": "chat" }, { - "id": "qwen/qwen3.6-flash", - "name": "Qwen3.6 Flash", - "display_name": "Qwen3.6 Flash", + "id": "moonshotai/Kimi-K3", + "name": "Kimi K3", + "display_name": "Kimi K3", "modalities": { "input": [ "text", - "image", - "video" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 65536 + "context": 1024000, + "output": 8000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -45104,32 +46503,38 @@ "visibility": "summary", "continuation": [ "thinking_blocks" + ], + "effort_options": [ + "none", + "low", + "medium", + "high", + "max" ] } }, "attachment": true, - "open_weights": false, - "release_date": "2026-04-27", - "last_updated": "2026-04-27", + "open_weights": true, + "release_date": "2026-07-16", + "last_updated": "2026-07-16", "type": "chat" }, { - "id": "qwen/qwen3.5-35b-a3b", - "name": "Qwen3.5 35B-A3B", - "display_name": "Qwen3.5 35B-A3B", + "id": "zai-org/GLM-5.3-Flash", + "name": "GLM-5.3-Flash", + "display_name": "GLM-5.3-Flash", "modalities": { "input": [ "text", - "image", - "video" + "image" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 65536 + "context": 1024000, + "output": 1024000 }, "temperature": true, "tool_call": true, @@ -45145,32 +46550,35 @@ "visibility": "summary", "continuation": [ "thinking_blocks" + ], + "effort_options": [ + "low", + "high", + "max" ] } }, "attachment": true, "open_weights": true, - "release_date": "2026-02-23", - "last_updated": "2026-02-23", + "release_date": "2026-08-26", + "last_updated": "2026-08-26", "type": "chat" }, { - "id": "qwen/qwen3.5-plus", - "name": "Qwen3.5 Plus", - "display_name": "Qwen3.5 Plus", + "id": "zai-org/GLM-5.2", + "name": "GLM-5.2", + "display_name": "GLM-5.2", "modalities": { "input": [ - "text", - "image", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 65536 + "context": 1048576, + "output": 1048576 }, "temperature": true, "tool_call": true, @@ -45186,33 +46594,35 @@ "visibility": "summary", "continuation": [ "thinking_blocks" + ], + "effort_options": [ + "low", + "medium", + "high" ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-04", - "release_date": "2026-02-16", - "last_updated": "2026-02-16", + "attachment": false, + "open_weights": true, + "release_date": "2026-06-13", + "last_updated": "2026-06-13", "type": "chat" }, { - "id": "qwen/qwen3.5-flash", - "name": "Qwen3.5 Flash", - "display_name": "Qwen3.5 Flash", + "id": "zai-org/GLM-5.3", + "name": "GLM-5.3", + "display_name": "GLM-5.3", "modalities": { "input": [ - "text", - "image", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 65536 + "context": 1024000, + "output": 1024000 }, "temperature": true, "tool_call": true, @@ -45228,32 +46638,34 @@ "visibility": "summary", "continuation": [ "thinking_blocks" + ], + "effort_options": [ + "low", + "high", + "max" ] } }, - "attachment": true, - "open_weights": false, - "release_date": "2026-02-23", - "last_updated": "2026-02-23", + "attachment": false, + "open_weights": true, + "release_date": "2026-08-14", + "last_updated": "2026-08-14", "type": "chat" }, { - "id": "qwen/qwen3.8-max", - "name": "Qwen3.8 Max", - "display_name": "Qwen3.8 Max", + "id": "reflection/beam", + "name": "Beam 1.0 501B A23B", + "display_name": "Beam 1.0 501B A23B", "modalities": { "input": [ - "text", - "image", - "video", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, + "context": 1048576, "output": 131072 }, "temperature": true, @@ -45272,35 +46684,66 @@ "thinking_blocks" ], "effort_options": [ + "minimal", "low", "medium", - "xhigh" + "high", + "xhigh", + "max" ] } }, - "attachment": true, - "open_weights": false, - "release_date": "2026-08-03", - "last_updated": "2026-08-03", + "attachment": false, + "open_weights": true, + "knowledge": "2026-06-30", + "release_date": "2026-10-05", + "last_updated": "2026-10-16", "type": "chat" }, { - "id": "qwen/qwen3.5-122b-a10b", - "name": "Qwen3.5 122B-A10B", - "display_name": "Qwen3.5 122B-A10B", + "id": "google/gemma-3-27b-it", + "name": "Gemma-3-27b-it", + "display_name": "Gemma-3-27b-it", "modalities": { "input": [ "text", - "image", - "video" + "image" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 65536 + "context": 110000, + "output": 8192 + }, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": false + }, + "attachment": true, + "open_weights": true, + "knowledge": "2024-08", + "release_date": "2025-03-12", + "last_updated": "2025-03-12", + "type": "chat" + }, + { + "id": "NousResearch/Hermes-4-405B", + "name": "Hermes-4-405B", + "display_name": "Hermes-4-405B", + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "output": 8192 }, "temperature": true, "tool_call": true, @@ -45319,16 +46762,17 @@ ] } }, - "attachment": true, + "attachment": false, "open_weights": true, - "release_date": "2026-02-23", - "last_updated": "2026-02-23", + "knowledge": "2024-08-31", + "release_date": "2025-08-26", + "last_updated": "2025-08-26", "type": "chat" }, { - "id": "qwen/qwen3.6-plus", - "name": "Qwen3.6 Plus", - "display_name": "Qwen3.6 Plus", + "id": "meta-models/muse-spark-open", + "name": "Muse Spark Open", + "display_name": "Muse Spark Open", "modalities": { "input": [ "text", @@ -45340,8 +46784,8 @@ ] }, "limit": { - "context": 1000000, - "output": 65536 + "context": 1048576, + "output": 1048576 }, "temperature": true, "tool_call": true, @@ -45357,33 +46801,38 @@ "visibility": "summary", "continuation": [ "thinking_blocks" + ], + "effort_options": [ + "low", + "medium", + "high", + "xhigh" ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2025-04", - "release_date": "2026-04-02", - "last_updated": "2026-04-02", + "open_weights": true, + "knowledge": "2026-01-04", + "release_date": "2026-10-15", + "last_updated": "2026-10-15", "type": "chat" }, { - "id": "qwen/qwen3.6-35b-a3b", - "name": "Qwen3.6 35B-A3B", - "display_name": "Qwen3.6 35B-A3B", + "id": "MiniMaxAI/MiniMax-M3.1-Flash", + "name": "MiniMax-M3.1-Flash", + "display_name": "MiniMax-M3.1-Flash", "modalities": { "input": [ "text", - "image", - "video" + "image" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 65536 + "context": 1048576, + "output": 1048576 }, "temperature": true, "tool_call": true, @@ -45399,32 +46848,38 @@ "visibility": "summary", "continuation": [ "thinking_blocks" + ], + "effort_options": [ + "none", + "low", + "medium", + "high", + "xhigh", + "max" ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-04-17", - "last_updated": "2026-04-17", + "open_weights": false, + "release_date": "2026-10-19", + "last_updated": "2026-10-19", "type": "chat" }, { - "id": "qwen/qwen3.7-flash", - "name": "Qwen3.7 Flash", - "display_name": "Qwen3.7 Flash", + "id": "MiniMaxAI/MiniMax-M3", + "name": "MiniMax-M3", + "display_name": "MiniMax-M3", "modalities": { "input": [ - "text", - "image", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 1048576, + "output": 1048576 }, "temperature": true, "tool_call": true, @@ -45437,29 +46892,27 @@ "supported": true } }, - "attachment": true, - "open_weights": false, - "release_date": "2026-07-15", - "last_updated": "2026-07-15", + "attachment": false, + "open_weights": true, + "release_date": "2026-06-01", + "last_updated": "2026-06-01", "type": "chat" }, { - "id": "qwen/qwen3.5-397b-a17b", - "name": "Qwen3.5 397B-A17B", - "display_name": "Qwen3.5 397B-A17B", + "id": "deepseek-ai/DeepSeek-V4-Pro", + "name": "DeepSeek V4 Pro", + "display_name": "DeepSeek V4 Pro", "modalities": { "input": [ - "text", - "image", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 65536 + "context": 1048576, + "output": 1048576 }, "temperature": true, "tool_call": true, @@ -45470,24 +46923,36 @@ "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "high", + "effort_options": [ + "low", + "high", + "max" + ], "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ "thinking_blocks" + ], + "notes": [ + "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." ] } }, - "attachment": true, + "attachment": false, "open_weights": true, - "release_date": "2026-02-15", - "last_updated": "2026-02-15", + "knowledge": "2025-05", + "release_date": "2026-04-24", + "last_updated": "2026-04-24", "type": "chat" }, { - "id": "qwen/qwen3-vl-235b-a22b-thinking", - "name": "Qwen3 VL 235B A22B Thinking", - "display_name": "Qwen3 VL 235B A22B Thinking", + "id": "deepseek-ai/DeepSeek-V4.1-Flash-1017", + "name": "DeepSeek V4.1 Flash 1017", + "display_name": "DeepSeek V4.1 Flash 1017", "modalities": { "input": [ "text", @@ -45498,8 +46963,8 @@ ] }, "limit": { - "context": 131072, - "output": 40960 + "context": 1048576, + "output": 1048576 }, "temperature": true, "tool_call": true, @@ -45509,39 +46974,37 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "none", + "low", + "high", + "max" ] } }, "attachment": true, "open_weights": true, - "knowledge": "2025-03-31", - "release_date": "2025-09-23", - "last_updated": "2025-09-23", + "knowledge": "2025-05", + "release_date": "2026-10-17", + "last_updated": "2026-10-17", "type": "chat" }, { - "id": "qwen/qwen3.5-27b", - "name": "Qwen3.5 27B", - "display_name": "Qwen3.5 27B", + "id": "deepseek-ai/DeepSeek-V4.1-Flash", + "name": "DeepSeek V4.1 Flash", + "display_name": "DeepSeek V4.1 Flash", "modalities": { "input": [ "text", - "image", - "video" + "image" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 65536 + "context": 1048000, + "output": 1048000 }, "temperature": true, "tool_call": true, @@ -45557,32 +47020,37 @@ "visibility": "summary", "continuation": [ "thinking_blocks" + ], + "effort_options": [ + "none", + "low", + "high", + "max" ] } }, "attachment": true, "open_weights": true, - "release_date": "2026-02-23", - "last_updated": "2026-02-23", + "knowledge": "2025-05", + "release_date": "2026-09-10", + "last_updated": "2026-09-10", "type": "chat" }, { - "id": "qwen/qwen3.7-plus", - "name": "Qwen3.7 Plus", - "display_name": "Qwen3.7 Plus", + "id": "deepseek-ai/DeepSeek-V4-Flash-0731", + "name": "DeepSeek V4 Flash 0731", + "display_name": "DeepSeek V4 Flash 0731", "modalities": { "input": [ - "text", - "image", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 64000 + "context": 1024000, + "output": 1024000 }, "temperature": true, "tool_call": true, @@ -45592,34 +47060,43 @@ }, "extra_capabilities": { "reasoning": { - "supported": true + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "effort_options": [ + "none", + "low", + "high", + "max" + ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-04", - "release_date": "2026-06-02", - "last_updated": "2026-06-02", + "attachment": false, + "open_weights": true, + "knowledge": "2025-05", + "release_date": "2026-07-31", + "last_updated": "2026-07-31", "type": "chat" }, { - "id": "meta/muse-spark-1.1", - "name": "Muse Spark 1.1", - "display_name": "Muse Spark 1.1", - "modalities": { - "input": [ - "text", - "image", - "pdf", - "video" + "id": "deepseek-ai/DeepSeek-V4-Pro-0813", + "name": "DeepSeek V4 Pro 0813", + "display_name": "DeepSeek V4 Pro 0813", + "modalities": { + "input": [ + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 979000, + "output": 979000 }, "temperature": true, "tool_call": true, @@ -45630,42 +47107,52 @@ "extra_capabilities": { "reasoning": { "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ - "minimal", + "none", "low", - "medium", "high", - "xhigh" + "max" ] } }, - "attachment": true, - "open_weights": false, - "release_date": "2026-04-08", - "last_updated": "2026-07-09", + "attachment": false, + "open_weights": true, + "release_date": "2026-08-13", + "last_updated": "2026-08-22", "type": "chat" - }, + } + ] + }, + "venice": { + "id": "venice", + "name": "Venice AI", + "display_name": "Venice AI", + "doc": "https://docs.venice.ai", + "models": [ { - "id": "meta/muse-spark-1.2", - "name": "Muse Spark 1.2", - "display_name": "Muse Spark 1.2", + "id": "openai-gpt-61-sol-ultrafast", + "name": "GPT-6.1 Sol Ultrafast", + "display_name": "GPT-6.1 Sol Ultrafast", "modalities": { "input": [ "text", - "image", - "video", - "pdf", - "audio" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 1050000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -45673,50 +47160,40 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, "effort_options": [ - "minimal", "low", "medium", "high", - "xhigh" + "xhigh", + "max" ] } }, "attachment": true, "open_weights": false, - "release_date": "2026-08-05", - "last_updated": "2026-08-05", + "knowledge": "2026-04-30", + "release_date": "2026-09-29", + "last_updated": "2026-09-29", "type": "chat" - } - ] - }, - "minimax": { - "id": "minimax", - "name": "MiniMax (minimax.io)", - "display_name": "MiniMax (minimax.io)", - "api": "https://api.minimax.io/anthropic/v1", - "doc": "https://platform.minimax.io/docs/guides/quickstart", - "models": [ + }, { - "id": "MiniMax-M3.1-Flash", - "name": "MiniMax-M3.1-Flash", - "display_name": "MiniMax-M3.1-Flash", + "id": "openai-gpt-6-astra-pro", + "name": "GPT-6 Astra Pro", + "display_name": "GPT-6 Astra Pro", "modalities": { "input": [ "text", - "image", - "video" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 524288 + "context": 1050000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -45735,25 +47212,27 @@ }, "attachment": true, "open_weights": false, - "release_date": "2026-10-19", - "last_updated": "2026-10-19", + "knowledge": "2026-04-30", + "release_date": "2026-09-04", + "last_updated": "2026-09-04", "type": "chat" }, { - "id": "MiniMax-M2.5-highspeed", - "name": "MiniMax-M2.5-highspeed", - "display_name": "MiniMax-M2.5-highspeed", + "id": "z-ai-glm-5v-turbo", + "name": "GLM 5V Turbo", + "display_name": "GLM 5V Turbo", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 204800, - "output": 131072 + "context": 200000, + "output": 32768 }, "temperature": true, "tool_call": true, @@ -45763,32 +47242,38 @@ }, "extra_capabilities": { "reasoning": { - "supported": true + "effort_options": [ + "none", + "low", + "medium", + "high" + ] } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-02-13", - "last_updated": "2026-02-13", + "attachment": true, + "open_weights": false, + "release_date": "2026-04-01", + "last_updated": "2026-04-01", "type": "chat" }, { - "id": "MiniMax-M2.7-highspeed", - "name": "MiniMax-M2.7-highspeed", - "display_name": "MiniMax-M2.7-highspeed", + "id": "kimi-k3", + "name": "Kimi K3", + "display_name": "Kimi K3", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 204800, + "context": 1000000, "output": 131072 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -45797,24 +47282,24 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "none", + "low", + "high", + "max" ] } }, - "attachment": false, + "attachment": true, "open_weights": true, - "release_date": "2026-03-18", - "last_updated": "2026-03-18", + "release_date": "2026-07-16", + "last_updated": "2026-07-16", "type": "chat" }, { - "id": "MiniMax-M2", - "name": "MiniMax-M2", - "display_name": "MiniMax-M2", + "id": "abliteration-abliterated-model-large-v2", + "name": "Abliterated Large V2", + "display_name": "Abliterated Large V2", "modalities": { "input": [ "text" @@ -45824,38 +47309,36 @@ ] }, "limit": { - "context": 204800, - "output": 131072 + "context": 1000000, + "output": 32768 }, - "temperature": true, "tool_call": true, "reasoning": { "supported": true, "default": true }, "attachment": false, - "open_weights": true, - "release_date": "2025-10-27", - "last_updated": "2025-10-27", + "open_weights": false, + "release_date": "2026-10-01", + "last_updated": "2026-10-01", "type": "chat" }, { - "id": "MiniMax-M3", - "name": "MiniMax-M3", - "display_name": "MiniMax-M3", + "id": "grok-build-0-1", + "name": "Grok Build 0.1", + "display_name": "Grok Build 0.1", "modalities": { "input": [ "text", - "image", - "video" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 524288 + "context": 256000, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -45863,34 +47346,30 @@ "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "supported": true - } - }, "attachment": true, - "open_weights": true, - "release_date": "2026-06-01", - "last_updated": "2026-06-25", + "open_weights": false, + "release_date": "2026-04-16", + "last_updated": "2026-04-16", "type": "chat" }, { - "id": "MiniMax-M2.1", - "name": "MiniMax-M2.1", - "display_name": "MiniMax-M2.1", + "id": "openai-gpt-6-astra", + "name": "GPT-6 Astra", + "display_name": "GPT-6 Astra", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 204800, - "output": 131072 + "context": 1050000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -45898,32 +47377,40 @@ }, "extra_capabilities": { "reasoning": { - "supported": true + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" + ] } }, - "attachment": false, - "open_weights": true, - "release_date": "2025-12-23", - "last_updated": "2025-12-23", + "attachment": true, + "open_weights": false, + "knowledge": "2026-04-30", + "release_date": "2026-09-04", + "last_updated": "2026-09-04", "type": "chat" }, { - "id": "MiniMax-M2.7", - "name": "MiniMax-M2.7", - "display_name": "MiniMax-M2.7", + "id": "kimi-k2-5", + "name": "Kimi K2.5", + "display_name": "Kimi K2.5", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 204800, - "output": 131072 + "context": 256000, + "output": 65536 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -45937,19 +47424,26 @@ "visibility": "summary", "continuation": [ "thinking_blocks" + ], + "effort_options": [ + "none", + "low", + "medium", + "high" ] } }, - "attachment": false, + "attachment": true, "open_weights": true, - "release_date": "2026-03-18", - "last_updated": "2026-03-18", + "knowledge": "2025-01", + "release_date": "2026-01-27", + "last_updated": "2026-01-27", "type": "chat" }, { - "id": "MiniMax-M2.5", - "name": "MiniMax-M2.5", - "display_name": "MiniMax-M2.5", + "id": "openai-gpt-52", + "name": "GPT-5.2", + "display_name": "GPT-5.2", "modalities": { "input": [ "text" @@ -45959,8 +47453,8 @@ ] }, "limit": { - "context": 204800, - "output": 131072 + "context": 256000, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -45970,84 +47464,68 @@ }, "extra_capabilities": { "reasoning": { - "supported": true + "effort_options": [ + "none", + "low", + "medium", + "high", + "xhigh" + ] } }, "attachment": false, - "open_weights": true, - "release_date": "2026-02-12", - "last_updated": "2026-02-12", + "open_weights": false, + "knowledge": "2025-08-31", + "release_date": "2025-12-11", + "last_updated": "2025-12-11", "type": "chat" - } - ] - }, - "gitlab": { - "id": "gitlab", - "name": "GitLab Duo", - "display_name": "GitLab Duo", - "doc": "https://docs.gitlab.com/user/duo_agent_platform/", - "models": [ + }, { - "id": "duo-chat-fable-5", - "name": "Agentic Chat (Claude Fable 5)", - "display_name": "Agentic Chat (Claude Fable 5)", + "id": "aion-labs-aion-3-5-mini", + "name": "Aion 3.5 Mini", + "display_name": "Aion 3.5 Mini", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 262144, + "output": 32768 }, - "temperature": false, "tool_call": true, "reasoning": { "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" - ] - } - }, - "attachment": true, + "attachment": false, "open_weights": false, - "knowledge": "2026-01-31", - "release_date": "2026-06-09", - "last_updated": "2026-06-09", + "release_date": "2026-09-23", + "last_updated": "2026-09-23", "type": "chat" }, { - "id": "duo-chat-gpt-5-5", - "name": "Agentic Chat (GPT-5.5)", - "display_name": "Agentic Chat (GPT-5.5)", + "id": "qwen-3-8-27b", + "name": "Qwen 3.8 27B", + "display_name": "Qwen 3.8 27B", "modalities": { "input": [ "text", "image", - "pdf" + "video" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 262144, + "output": 65536 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -46059,37 +47537,35 @@ "none", "low", "medium", - "high", "xhigh" ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2025-12-01", - "release_date": "2026-04-23", - "last_updated": "2026-04-23", + "open_weights": true, + "release_date": "2026-08-14", + "last_updated": "2026-08-14", "type": "chat" }, { - "id": "duo-chat-gpt-5-6-luna", - "name": "Agentic Chat (GPT-5.6 Luna)", - "display_name": "Agentic Chat (GPT-5.6 Luna)", + "id": "google-gemma-4-31b-it", + "name": "Google Gemma 4 31B Instruct", + "display_name": "Google Gemma 4 31B Instruct", "modalities": { "input": [ "text", "image", - "pdf" + "video" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 256000, + "output": 8192 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -46101,36 +47577,32 @@ "none", "low", "medium", - "high", - "xhigh", - "max" + "high" ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2026-02-16", - "release_date": "2026-07-09", - "last_updated": "2026-07-09", + "open_weights": true, + "knowledge": "2025-01", + "release_date": "2026-04-02", + "last_updated": "2026-04-02", "type": "chat" }, { - "id": "duo-chat-sonnet-4-5", - "name": "Agentic Chat (Claude Sonnet 4.5)", - "display_name": "Agentic Chat (Claude Sonnet 4.5)", + "id": "mercury-2-5", + "name": "Mercury 2.5", + "display_name": "Mercury 2.5", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 64000 + "context": 260000, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -46138,17 +47610,26 @@ "supported": true, "default": true }, - "attachment": true, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "none", + "low", + "medium", + "high" + ] + } + }, + "attachment": false, "open_weights": false, - "knowledge": "2025-07-31", - "release_date": "2025-09-29", - "last_updated": "2025-09-29", + "release_date": "2026-09-08", + "last_updated": "2026-09-08", "type": "chat" }, { - "id": "duo-chat-gpt-5-2", - "name": "Agentic Chat (GPT-5.2)", - "display_name": "Agentic Chat (GPT-5.2)", + "id": "claude-haiku-5-5", + "name": "Claude Haiku 5.5", + "display_name": "Claude Haiku 5.5", "modalities": { "input": [ "text", @@ -46159,10 +47640,10 @@ ] }, "limit": { - "context": 400000, + "context": 1000000, "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -46175,36 +47656,36 @@ "low", "medium", "high", - "xhigh" + "xhigh", + "max" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2025-12-11", - "last_updated": "2025-12-11", + "knowledge": "2026-06", + "release_date": "2026-10-07", + "last_updated": "2026-10-07", "type": "chat" }, { - "id": "duo-chat-gpt-5-3-codex", - "name": "Agentic Chat (GPT-5.3 Codex)", - "display_name": "Agentic Chat (GPT-5.3 Codex)", + "id": "openai-gpt-6-luna", + "name": "GPT-6 Luna", + "display_name": "GPT-6 Luna", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 400000, + "context": 1050000, "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -46217,34 +47698,33 @@ "low", "medium", "high", - "xhigh" + "xhigh", + "max" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-02-05", - "last_updated": "2026-02-05", + "knowledge": "2026-05-18", + "release_date": "2026-09-22", + "last_updated": "2026-09-22", "type": "chat" }, { - "id": "duo-chat-opus-4-6", - "name": "Agentic Chat (Claude Opus 4.6)", - "display_name": "Agentic Chat (Claude Opus 4.6)", + "id": "zai-org-glm-4.7", + "name": "GLM 4.7", + "display_name": "GLM 4.7", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 198000, + "output": 16384 }, "temperature": true, "tool_call": true, @@ -46254,30 +47734,37 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ "low", "medium", - "high", - "max" + "high" ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-05-31", - "release_date": "2026-02-05", - "last_updated": "2026-03-13", + "attachment": false, + "open_weights": true, + "knowledge": "2025-04", + "release_date": "2025-12-22", + "last_updated": "2025-12-22", "type": "chat" }, { - "id": "duo-chat-opus-4-7", - "name": "Agentic Chat (Claude Opus 4.7)", - "display_name": "Agentic Chat (Claude Opus 4.7)", + "id": "xiaomi-mimo-v2-5", + "name": "MiMo-V2.5", + "display_name": "MiMo-V2.5", "modalities": { "input": [ "text", "image", - "pdf" + "audio", + "video" ], "output": [ "text" @@ -46285,9 +47772,9 @@ }, "limit": { "context": 1000000, - "output": 128000 + "output": 65536 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -46296,30 +47783,29 @@ "extra_capabilities": { "reasoning": { "effort_options": [ + "none", "low", "medium", - "high", - "xhigh", - "max" + "high" ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2026-01-31", - "release_date": "2026-04-16", - "last_updated": "2026-04-16", + "open_weights": true, + "knowledge": "2024-12", + "release_date": "2026-04-22", + "last_updated": "2026-04-22", "type": "chat" }, { - "id": "duo-chat-sonnet-5", - "name": "Agentic Chat (Claude Sonnet 5)", - "display_name": "Agentic Chat (Claude Sonnet 5)", + "id": "minimax-m3-1-flash", + "name": "MiniMax M3.1 Flash", + "display_name": "MiniMax M3.1 Flash", "modalities": { "input": [ "text", "image", - "pdf" + "video" ], "output": [ "text" @@ -46327,93 +47813,92 @@ }, "limit": { "context": 1000000, - "output": 64000 + "output": 131072 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" - ] - } - }, "attachment": true, "open_weights": false, - "knowledge": "2026-01-31", - "release_date": "2026-06-30", - "last_updated": "2026-06-30", + "release_date": "2026-10-19", + "last_updated": "2026-10-19", "type": "chat" }, { - "id": "duo-chat-opus-4-8", - "name": "Agentic Chat (Claude Opus 4.8)", - "display_name": "Agentic Chat (Claude Opus 4.8)", + "id": "nvidia-nemotron-3-nano-30b-a3b", + "name": "NVIDIA Nemotron 3 Nano 30B", + "display_name": "NVIDIA Nemotron 3 Nano 30B", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 128000, + "output": 16384 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true + "supported": false }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" - ] - } + "attachment": false, + "open_weights": true, + "knowledge": "2025-06-25", + "release_date": "2025-12-15", + "last_updated": "2025-12-15", + "type": "chat" + }, + { + "id": "gemma-4-uncensored", + "name": "Gemma 4 Uncensored", + "display_name": "Gemma 4 Uncensored", + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 256000, + "output": 8192 + }, + "tool_call": true, + "reasoning": { + "supported": false }, "attachment": true, - "open_weights": false, - "knowledge": "2026-01", - "release_date": "2026-05-28", - "last_updated": "2026-05-28", + "open_weights": true, + "release_date": "2026-04-13", + "last_updated": "2026-06-11", "type": "chat" }, { - "id": "duo-chat-gpt-5-4", - "name": "Agentic Chat (GPT-5.4)", - "display_name": "Agentic Chat (GPT-5.4)", + "id": "deepseek-v4-1-flash-uncensored", + "name": "DeepSeek V4.1 Flash Uncensored", + "display_name": "DeepSeek V4.1 Flash Uncensored", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 1000000, + "output": 131072 }, - "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -46424,23 +47909,21 @@ "effort_options": [ "none", "low", - "medium", "high", - "xhigh" + "max" ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-03-05", - "last_updated": "2026-03-05", + "open_weights": true, + "release_date": "2026-10-23", + "last_updated": "2026-10-23", "type": "chat" }, { - "id": "duo-chat-gpt-5-1", - "name": "Agentic Chat (GPT-5.1)", - "display_name": "Agentic Chat (GPT-5.1)", + "id": "grok-4-3", + "name": "Grok 4.3", + "display_name": "Grok 4.3", "modalities": { "input": [ "text", @@ -46451,8 +47934,8 @@ ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 1000000, + "output": 32000 }, "temperature": true, "tool_call": true, @@ -46472,28 +47955,27 @@ }, "attachment": true, "open_weights": false, - "knowledge": "2024-09-30", - "release_date": "2025-11-13", - "last_updated": "2025-11-13", + "release_date": "2026-04-17", + "last_updated": "2026-04-17", "type": "chat" }, { - "id": "duo-chat-haiku-4-5", - "name": "Agentic Chat (Claude Haiku 4.5)", - "display_name": "Agentic Chat (Claude Haiku 4.5)", + "id": "qwen-3-7-plus", + "name": "Qwen 3.7 Plus", + "display_name": "Qwen 3.7 Plus", "modalities": { "input": [ "text", "image", - "pdf" + "video" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 64000 + "context": 1000000, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -46503,29 +47985,28 @@ }, "attachment": true, "open_weights": false, - "knowledge": "2025-02-28", - "release_date": "2025-10-15", - "last_updated": "2025-10-15", + "knowledge": "2025-04", + "release_date": "2026-06-02", + "last_updated": "2026-06-02", "type": "chat" }, { - "id": "duo-chat-gpt-5-codex", - "name": "Agentic Chat (GPT-5 Codex)", - "display_name": "Agentic Chat (GPT-5 Codex)", + "id": "mercury-2", + "name": "Mercury 2", + "display_name": "Mercury 2", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 128000, + "output": 50000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -46534,38 +48015,38 @@ "extra_capabilities": { "reasoning": { "effort_options": [ + "none", "low", "medium", "high" ] } }, - "attachment": true, + "attachment": false, "open_weights": false, - "knowledge": "2024-09-30", - "release_date": "2025-09-15", - "last_updated": "2025-09-15", + "release_date": "2026-02-24", + "last_updated": "2026-02-24", "type": "chat" }, { - "id": "duo-chat-gpt-5-6-sol", - "name": "Agentic Chat (GPT-5.6 Sol)", - "display_name": "Agentic Chat (GPT-5.6 Sol)", + "id": "qwen3-5-35b-a3b", + "name": "Qwen 3.5 35B A3B", + "display_name": "Qwen 3.5 35B A3B", "modalities": { "input": [ "text", "image", - "pdf" + "video" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 256000, + "output": 16384 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -46577,28 +48058,26 @@ "none", "low", "medium", - "high", - "xhigh", - "max" + "high" ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2026-02-16", - "release_date": "2026-07-09", - "last_updated": "2026-07-09", + "open_weights": true, + "release_date": "2026-02-23", + "last_updated": "2026-02-23", "type": "chat" }, { - "id": "duo-chat-sonnet-4-6", - "name": "Agentic Chat (Claude Sonnet 4.6)", - "display_name": "Agentic Chat (Claude Sonnet 4.6)", + "id": "gemini-4-argon", + "name": "Gemini 4 Argon", + "display_name": "Gemini 4 Argon", "modalities": { "input": [ "text", "image", - "pdf" + "audio", + "video" ], "output": [ "text" @@ -46606,50 +48085,38 @@ }, "limit": { "context": 1000000, - "output": 64000 + "output": 65536 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "low", - "medium", - "high", - "max" - ] - } - }, "attachment": true, "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-02-17", - "last_updated": "2026-03-13", + "knowledge": "2026-03", + "release_date": "2026-10-13", + "last_updated": "2026-10-13", "type": "chat" }, { - "id": "duo-chat-gpt-6-luna", - "name": "Agentic Chat (GPT-6 Luna)", - "display_name": "Agentic Chat (GPT-6 Luna)", + "id": "nvidia-nemotron-3-ultra-550b-a55b", + "name": "NVIDIA Nemotron 3 Ultra", + "display_name": "NVIDIA Nemotron 3 Ultra", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 256000, + "output": 32768 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -46661,28 +48128,25 @@ "none", "low", "medium", - "high", - "xhigh", - "max" + "high" ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-05-18", - "release_date": "2026-09-22", - "last_updated": "2026-09-22", + "attachment": false, + "open_weights": true, + "knowledge": "2025-09", + "release_date": "2026-06-04", + "last_updated": "2026-06-04", "type": "chat" }, { - "id": "duo-chat-sonnet-5-5", - "name": "Agentic Chat (Claude Sonnet 5.5)", - "display_name": "Agentic Chat (Claude Sonnet 5.5)", + "id": "claude-opus-4-7", + "name": "Claude Opus 4.7", + "display_name": "Claude Opus 4.7", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" @@ -46690,78 +48154,78 @@ }, "limit": { "context": 1000000, - "output": 64000 + "output": 128000 }, "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": false, + "mode": "effort", + "effort": "high", "effort_options": [ "low", "medium", "high", "xhigh", "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "omitted", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Claude Opus 4.7 and newer Opus models require thinking.type = \"adaptive\" to enable thinking explicitly.", + "Manual budget_tokens requests return 400 on Claude Opus 4.7 and newer adaptive-only Opus models.", + "task_budget is separate from thinking control and should not be treated as a thinking budget." ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-06", - "release_date": "2026-09-28", - "last_updated": "2026-09-28", + "knowledge": "2026-01-31", + "release_date": "2026-04-16", + "last_updated": "2026-04-16", "type": "chat" }, { - "id": "duo-chat-opus-5-5", - "name": "Agentic Chat (Claude Opus 5.5)", - "display_name": "Agentic Chat (Claude Opus 5.5)", + "id": "aion-labs-aion-3-5", + "name": "Aion 3.5", + "display_name": "Aion 3.5", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 262144, + "output": 32768 }, - "temperature": false, "tool_call": true, "reasoning": { "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" - ] - } - }, - "attachment": true, + "attachment": false, "open_weights": false, - "knowledge": "2026-06", - "release_date": "2026-09-22", - "last_updated": "2026-09-22", + "release_date": "2026-09-23", + "last_updated": "2026-09-23", "type": "chat" }, { - "id": "duo-chat-gpt-5-4-mini", - "name": "Agentic Chat (GPT-5.4 Mini)", - "display_name": "Agentic Chat (GPT-5.4 Mini)", + "id": "openai-gpt-56-terra-pro", + "name": "GPT-5.6 Terra Pro", + "display_name": "GPT-5.6 Terra Pro", "modalities": { "input": [ "text", @@ -46772,10 +48236,10 @@ ] }, "limit": { - "context": 400000, + "context": 1000000, "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -46784,39 +48248,38 @@ "extra_capabilities": { "reasoning": { "effort_options": [ - "none", "low", "medium", "high", - "xhigh" + "xhigh", + "max" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-03-17", - "last_updated": "2026-03-17", + "knowledge": "2026-02-16", + "release_date": "2026-07-09", + "last_updated": "2026-07-09", "type": "chat" }, { - "id": "duo-chat-gpt-5-mini", - "name": "Agentic Chat (GPT-5 Mini)", - "display_name": "Agentic Chat (GPT-5 Mini)", + "id": "zai-org-glm-5-2", + "name": "GLM 5.2", + "display_name": "GLM 5.2", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 1000000, + "output": 131072 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -46825,36 +48288,33 @@ "extra_capabilities": { "reasoning": { "effort_options": [ - "minimal", - "low", - "medium", - "high" + "none", + "high", + "max" ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2024-05-30", - "release_date": "2025-08-07", - "last_updated": "2025-08-07", + "attachment": false, + "open_weights": true, + "release_date": "2026-06-13", + "last_updated": "2026-06-13", "type": "chat" }, { - "id": "duo-chat-gpt-5-4-nano", - "name": "Agentic Chat (GPT-5.4 Nano)", - "display_name": "Agentic Chat (GPT-5.4 Nano)", + "id": "deepseek-v4-flash-0731-fast", + "name": "DeepSeek V4 Flash 0731 Fast", + "display_name": "DeepSeek V4 Flash 0731 Fast", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 1000000, + "output": 32768 }, "temperature": true, "tool_call": true, @@ -46864,38 +48324,37 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, "effort_options": [ "none", "low", - "medium", "high", - "xhigh" + "max" ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-03-17", - "last_updated": "2026-03-17", + "attachment": false, + "open_weights": true, + "knowledge": "2025-05", + "release_date": "2026-07-31", + "last_updated": "2026-07-31", "type": "chat" }, { - "id": "duo-chat-gpt-5-6-terra", - "name": "Agentic Chat (GPT-5.6 Terra)", - "display_name": "Agentic Chat (GPT-5.6 Terra)", + "id": "openai-gpt-56-luna-pro", + "name": "GPT-5.6 Luna Pro", + "display_name": "GPT-5.6 Luna Pro", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1050000, + "context": 1000000, "output": 128000 }, "temperature": false, @@ -46907,7 +48366,6 @@ "extra_capabilities": { "reasoning": { "effort_options": [ - "none", "low", "medium", "high", @@ -46924,22 +48382,21 @@ "type": "chat" }, { - "id": "duo-chat-gpt-5-2-codex", - "name": "Agentic Chat (GPT-5.2 Codex)", - "display_name": "Agentic Chat (GPT-5.2 Codex)", + "id": "claude-sonnet-5", + "name": "Claude Sonnet 5", + "display_name": "Claude Sonnet 5", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 1000000, + "output": 64000 }, "temperature": false, "tool_call": true, @@ -46949,40 +48406,42 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, "effort_options": [ "low", "medium", "high", - "xhigh" + "xhigh", + "max" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2025-12-11", - "last_updated": "2025-12-11", + "knowledge": "2026-01-31", + "release_date": "2026-06-30", + "last_updated": "2026-06-30", "type": "chat" }, { - "id": "duo-chat-fable-5-1", - "name": "Agentic Chat (Claude Fable 5.1)", - "display_name": "Agentic Chat (Claude Fable 5.1)", + "id": "qwen3-5-397b-a17b", + "name": "Qwen 3.5 397B", + "display_name": "Qwen 3.5 397B", "modalities": { "input": [ "text", "image", - "pdf" + "video" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 128000, + "output": 32768 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -46991,80 +48450,66 @@ "extra_capabilities": { "reasoning": { "effort_options": [ + "none", "low", "medium", - "high", - "xhigh", - "max" + "high" ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2026-06", - "release_date": "2026-09-01", - "last_updated": "2026-09-01", + "open_weights": true, + "release_date": "2026-02-15", + "last_updated": "2026-02-15", "type": "chat" }, { - "id": "duo-chat-gpt-6-1-sol", - "name": "Agentic Chat (GPT-6.1 Sol)", - "display_name": "Agentic Chat (GPT-6.1 Sol)", + "id": "qwen-3-6-plus", + "name": "Qwen 3.6 Plus Uncensored", + "display_name": "Qwen 3.6 Plus Uncensored", "modalities": { "input": [ "text", "image", - "pdf" + "video" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 1000000, + "output": 65536 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" - ] - } - }, "attachment": true, "open_weights": false, - "knowledge": "2026-04-30", - "release_date": "2026-09-29", - "last_updated": "2026-09-29", + "knowledge": "2025-04", + "release_date": "2026-04-02", + "last_updated": "2026-04-02", "type": "chat" }, { - "id": "duo-chat-opus-4-5", - "name": "Agentic Chat (Claude Opus 4.5)", - "display_name": "Agentic Chat (Claude Opus 4.5)", + "id": "kimi-k2-6", + "name": "Kimi K2.6", + "display_name": "Kimi K2.6", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 64000 + "context": 256000, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -47074,7 +48519,15 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ + "none", "low", "medium", "high" @@ -47082,21 +48535,20 @@ } }, "attachment": true, - "open_weights": false, - "knowledge": "2025-05", - "release_date": "2025-11-24", - "last_updated": "2025-11-24", + "open_weights": true, + "knowledge": "2025-01", + "release_date": "2026-04-20", + "last_updated": "2026-04-20", "type": "chat" }, { - "id": "duo-chat-haiku-5-5", - "name": "Agentic Chat (Claude Haiku 5.5)", - "display_name": "Agentic Chat (Claude Haiku 5.5)", + "id": "claude-opus-5-fast", + "name": "Claude Opus 5 Fast", + "display_name": "Claude Opus 5 Fast", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" @@ -47125,30 +48577,29 @@ }, "attachment": true, "open_weights": false, - "knowledge": "2026-06", - "release_date": "2026-10-07", - "last_updated": "2026-10-07", + "knowledge": "2026-05", + "release_date": "2026-07-24", + "last_updated": "2026-07-24", "type": "chat" }, { - "id": "duo-chat-gpt-6-sol", - "name": "Agentic Chat (GPT-6 Sol)", - "display_name": "Agentic Chat (GPT-6 Sol)", + "id": "muse-spark-open", + "name": "Muse Spark Open", + "display_name": "Muse Spark Open", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 1048576, + "output": 131072 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -47157,80 +48608,64 @@ "extra_capabilities": { "reasoning": { "effort_options": [ - "none", "low", "medium", "high", - "xhigh", - "max" + "xhigh" ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2026-04-20", - "release_date": "2026-09-22", - "last_updated": "2026-09-22", + "open_weights": true, + "knowledge": "2026-01-04", + "release_date": "2026-10-15", + "last_updated": "2026-10-15", "type": "chat" }, { - "id": "duo-chat-opus-5", - "name": "Agentic Chat (Claude Opus 5)", - "display_name": "Agentic Chat (Claude Opus 5)", + "id": "minimax-m3-preview", + "name": "MiniMax M3 Preview", + "display_name": "MiniMax M3 Preview", "modalities": { "input": [ "text", "image", - "pdf" + "video" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 524288, + "output": 65536 }, - "temperature": false, "tool_call": true, "reasoning": { "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" - ] - } - }, "attachment": true, - "open_weights": false, - "knowledge": "2026-05", - "release_date": "2026-07-24", - "last_updated": "2026-07-24", + "open_weights": true, + "release_date": "2026-06-12", + "last_updated": "2026-06-13", "type": "chat" }, { - "id": "duo-chat-gpt-6-astra", - "name": "Agentic Chat (GPT-6 Astra)", - "display_name": "Agentic Chat (GPT-6 Astra)", + "id": "claude-fable-5", + "name": "Claude Fable 5", + "display_name": "Claude Fable 5", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1050000, + "context": 1000000, "output": 128000 }, "temperature": false, @@ -47241,48 +48676,55 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "high", "effort_options": [ "low", "medium", "high", "xhigh", "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "omitted", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Adaptive thinking is always on for Claude Fable 5 and Claude Mythos 5; thinking.type = \"disabled\" is rejected.", + "Manual budget_tokens requests return 400 on Claude Fable 5 and Claude Mythos 5.", + "thinking.display defaults to omitted; set display to summarized to receive readable thinking summaries." ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-04-30", - "release_date": "2026-09-04", - "last_updated": "2026-09-04", + "knowledge": "2026-01-31", + "release_date": "2026-06-09", + "last_updated": "2026-06-09", "type": "chat" - } - ] - }, - "huggingface": { - "id": "huggingface", - "name": "Hugging Face", - "display_name": "Hugging Face", - "api": "https://router.huggingface.co/v1", - "doc": "https://huggingface.co/docs/inference-providers", - "models": [ + }, { - "id": "zai-org/GLM-4.7", - "name": "GLM-4.7", - "display_name": "GLM-4.7", + "id": "openai-gpt-55", + "name": "GPT-5.5", + "display_name": "GPT-5.5", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 204800, + "context": 1000000, "output": 131072 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -47290,26 +48732,26 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "none", + "low", + "medium", + "high", + "xhigh" ] } }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-12-22", - "last_updated": "2025-12-22", + "attachment": true, + "open_weights": false, + "knowledge": "2025-12-01", + "release_date": "2026-04-23", + "last_updated": "2026-04-23", "type": "chat" }, { - "id": "zai-org/AutoGLM-Phone-9B-Multilingual", - "name": "AutoGLM Phone 9B Multilingual", - "display_name": "AutoGLM Phone 9B Multilingual", + "id": "grok-4-7", + "name": "Grok 4.7", + "display_name": "Grok 4.7", "modalities": { "input": [ "text", @@ -47320,35 +48762,8 @@ ] }, "limit": { - "context": 65536, - "output": 65536 - }, - "temperature": true, - "tool_call": false, - "reasoning": { - "supported": false - }, - "attachment": true, - "open_weights": true, - "release_date": "2025-12-10", - "last_updated": "2025-12-10", - "type": "chat" - }, - { - "id": "zai-org/GLM-5.1", - "name": "GLM-5.1", - "display_name": "GLM-5.1", - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 202752, - "output": 131072 + "context": 500000, + "output": 200000 }, "temperature": true, "tool_call": true, @@ -47358,25 +48773,25 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "low", + "medium", + "high", + "xhigh" ] } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-04-07", - "last_updated": "2026-04-07", + "attachment": true, + "open_weights": false, + "knowledge": "2026-05", + "release_date": "2026-09-21", + "last_updated": "2026-09-21", "type": "chat" }, { - "id": "zai-org/GLM-5.3-Flash", - "name": "GLM-5.3-Flash", - "display_name": "GLM-5.3-Flash", + "id": "openai-gpt-54", + "name": "GPT-5.4", + "display_name": "GPT-5.4", "modalities": { "input": [ "text", @@ -47387,7 +48802,7 @@ ] }, "limit": { - "context": 1048576, + "context": 1000000, "output": 131072 }, "temperature": true, @@ -47398,37 +48813,40 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, "effort_options": [ + "none", "low", + "medium", "high", - "max" + "xhigh" ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-08-26", - "last_updated": "2026-08-26", + "open_weights": false, + "knowledge": "2025-08-31", + "release_date": "2026-03-05", + "last_updated": "2026-03-05", "type": "chat" }, { - "id": "zai-org/GLM-4.7-Flash", - "name": "GLM-4.7-Flash", - "display_name": "GLM-4.7-Flash", + "id": "kimi-k2-7-code", + "name": "Kimi K2.7 Code", + "display_name": "Kimi K2.7 Code", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 128000 + "context": 256000, + "output": 65536 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -47436,28 +48854,27 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "low", + "medium", + "high" ] } }, - "attachment": false, + "attachment": true, "open_weights": true, - "knowledge": "2025-04", - "release_date": "2026-01-19", - "last_updated": "2026-01-19", + "knowledge": "2025-01", + "release_date": "2026-06-12", + "last_updated": "2026-06-12", "type": "chat" }, { - "id": "zai-org/GLM-4.5-Air", - "name": "GLM-4.5-Air", - "display_name": "GLM-4.5-Air", + "id": "mistral-small-3-2-24b-instruct", + "name": "Mistral Small 3.2 24B Instruct", + "display_name": "Mistral Small 3.2 24B Instruct", "modalities": { "input": [ + "image", "text" ], "output": [ @@ -47465,37 +48882,37 @@ ] }, "limit": { - "context": 131072, - "output": 98304 + "context": 256000, + "output": 16384 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true + "supported": false }, - "attachment": false, + "attachment": true, "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-07-28", - "last_updated": "2025-07-28", + "knowledge": "2023-10-31", + "release_date": "2025-06-20", + "last_updated": "2025-06-20", "type": "chat" }, { - "id": "zai-org/GLM-5", - "name": "GLM-5", - "display_name": "GLM-5", + "id": "grok-4-5", + "name": "Grok 4.5", + "display_name": "Grok 4.5", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 202752, - "output": 131072 + "context": 500000, + "output": 32000 }, "temperature": true, "tool_call": true, @@ -47505,70 +48922,62 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "low", + "medium", + "high" ] } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-02-12", - "last_updated": "2026-02-12", + "attachment": true, + "open_weights": false, + "release_date": "2026-07-08", + "last_updated": "2026-07-08", "type": "chat" }, { - "id": "zai-org/GLM-5.2", - "name": "GLM-5.2", - "display_name": "GLM-5.2", + "id": "grok-4-20-multi-agent", + "name": "Grok 4.20 Multi-Agent", + "display_name": "Grok 4.20 Multi-Agent", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 131072 + "context": 2000000, + "output": 128000 }, - "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "supported": true - } - }, - "attachment": false, - "open_weights": true, - "release_date": "2026-06-13", - "last_updated": "2026-06-13", + "attachment": true, + "open_weights": false, + "release_date": "2026-03-12", + "last_updated": "2026-06-11", "type": "chat" }, { - "id": "zai-org/GLM-4.6V-Flash", - "name": "GLM-4.6V-Flash", - "display_name": "GLM-4.6V-Flash", + "id": "qwen-3-8-2-4t-a95b", + "name": "Qwen 3.8 2.4T", + "display_name": "Qwen 3.8 2.4T", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 32768 + "context": 262144, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -47576,16 +48985,16 @@ "supported": true, "default": true }, - "attachment": true, + "attachment": false, "open_weights": true, - "release_date": "2025-12-08", - "last_updated": "2025-12-08", + "release_date": "2026-08-12", + "last_updated": "2026-08-12", "type": "chat" }, { - "id": "zai-org/GLM-5.3", - "name": "GLM-5.3", - "display_name": "GLM-5.3", + "id": "minimax-m25", + "name": "MiniMax M2.5", + "display_name": "MiniMax M2.5", "modalities": { "input": [ "text" @@ -47595,8 +49004,8 @@ ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 198000, + "output": 32768 }, "temperature": true, "tool_call": true, @@ -47607,36 +49016,43 @@ "extra_capabilities": { "reasoning": { "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ "low", - "high", - "max" + "medium", + "high" ] } }, "attachment": false, "open_weights": true, - "release_date": "2026-08-14", - "last_updated": "2026-08-14", + "release_date": "2026-02-12", + "last_updated": "2026-02-12", "type": "chat" }, { - "id": "zai-org/GLM-4.6", - "name": "GLM-4.6", - "display_name": "GLM-4.6", + "id": "openai-gpt-56-sol-pro", + "name": "GPT-5.6 Sol Pro", + "display_name": "GPT-5.6 Sol Pro", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 204800, - "output": 131072 + "context": 1000000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -47644,32 +49060,38 @@ }, "extra_capabilities": { "reasoning": { - "supported": true + "effort_options": [ + "none", + "low", + "medium", + "high", + "xhigh", + "max" + ] } }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-09-30", - "last_updated": "2025-09-30", + "attachment": true, + "open_weights": false, + "knowledge": "2026-02-16", + "release_date": "2026-07-09", + "last_updated": "2026-07-09", "type": "chat" }, { - "id": "zai-org/GLM-4.5V", - "name": "GLM-4.5V", - "display_name": "GLM-4.5V", + "id": "deepseek-v3.2", + "name": "DeepSeek V3.2", + "display_name": "DeepSeek V3.2", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 65536, - "output": 16384 + "context": 160000, + "output": 32768 }, "temperature": true, "tool_call": true, @@ -47677,44 +49099,67 @@ "supported": true, "default": true }, - "attachment": true, + "extra_capabilities": { + "reasoning": { + "supported": true, + "effort_options": [ + "none", + "low", + "medium", + "high" + ] + } + }, + "attachment": false, "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-08-11", - "last_updated": "2025-08-11", + "knowledge": "2024-07", + "release_date": "2025-12-01", + "last_updated": "2025-12-01", "type": "chat" }, { - "id": "Qwen/Qwen2.5-Coder-32B-Instruct", - "name": "Qwen2.5-Coder-32B-Instruct", - "display_name": "Qwen2.5-Coder-32B-Instruct", + "id": "openai-gpt-55-pro", + "name": "GPT-5.5 Pro", + "display_name": "GPT-5.5 Pro", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 8192 + "context": 1000000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, - "open_weights": true, - "release_date": "2024-11-12", - "last_updated": "2024-11-12", + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "medium", + "high", + "xhigh" + ] + } + }, + "attachment": true, + "open_weights": false, + "knowledge": "2025-12-01", + "release_date": "2026-04-23", + "last_updated": "2026-04-23", "type": "chat" }, { - "id": "Qwen/Qwen3-VL-30B-A3B-Instruct", - "name": "Qwen3 VL 30B A3B Instruct", - "display_name": "Qwen3 VL 30B A3B Instruct", + "id": "abliteration-abliterated-model-large-flash", + "name": "Abliterated Large Flash", + "display_name": "Abliterated Large Flash", "modalities": { "input": [ "text", @@ -47725,24 +49170,24 @@ ] }, "limit": { - "context": 131072, - "output": 32768 + "context": 1000000, + "output": 131072 }, - "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, "attachment": true, - "open_weights": true, - "release_date": "2025-10-04", - "last_updated": "2025-10-04", + "open_weights": false, + "release_date": "2026-10-23", + "last_updated": "2026-10-23", "type": "chat" }, { - "id": "Qwen/Qwen3.5-397B-A17B", - "name": "Qwen3.5-397B-A17B", - "display_name": "Qwen3.5-397B-A17B", + "id": "kimi-k3-1-pro-highspeed", + "name": "Kimi K3.1 Pro Highspeed", + "display_name": "Kimi K3.1 Pro Highspeed", "modalities": { "input": [ "text", @@ -47753,10 +49198,9 @@ ] }, "limit": { - "context": 262144, - "output": 32768 + "context": 1000000, + "output": 131072 }, - "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -47764,32 +49208,24 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], "effort_options": [ "none", "low", - "medium", - "high" + "high", + "max" ] } }, "attachment": true, - "open_weights": true, - "knowledge": "2025-04", - "release_date": "2026-02-15", - "last_updated": "2026-02-15", + "open_weights": false, + "release_date": "2026-10-23", + "last_updated": "2026-10-23", "type": "chat" }, { - "id": "Qwen/Qwen3.6-27B", - "name": "Qwen3.6 27B", - "display_name": "Qwen3.6 27B", + "id": "openai-gpt-61-sol", + "name": "GPT-6.1 Sol", + "display_name": "GPT-6.1 Sol", "modalities": { "input": [ "text", @@ -47800,10 +49236,10 @@ ] }, "limit": { - "context": 262144, - "output": 65536 + "context": 1050000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -47811,38 +49247,40 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-04-22", - "last_updated": "2026-04-22", + "open_weights": false, + "knowledge": "2026-04-30", + "release_date": "2026-09-29", + "last_updated": "2026-09-29", "type": "chat" }, { - "id": "Qwen/Qwen3-8B", - "name": "Qwen3 8B", - "display_name": "Qwen3 8B", + "id": "claude-opus-5-5", + "name": "Claude Opus 5.5", + "display_name": "Claude Opus 5.5", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 40960, - "output": 32768 + "context": 1000000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -47850,87 +49288,66 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], "effort_options": [ - "none", "low", "medium", - "high" + "high", + "xhigh", + "max" ] } }, - "attachment": false, - "open_weights": true, - "release_date": "2025-04-29", - "last_updated": "2025-04-29", + "attachment": true, + "open_weights": false, + "knowledge": "2026-06", + "release_date": "2026-09-22", + "last_updated": "2026-09-22", "type": "chat" }, { - "id": "Qwen/Qwen3-Next-80B-A3B-Instruct", - "name": "Qwen3-Next-80B-A3B-Instruct", - "display_name": "Qwen3-Next-80B-A3B-Instruct", + "id": "google-gemma-4-26b-a4b-it", + "name": "Google Gemma 4 26B A4B Instruct", + "display_name": "Google Gemma 4 26B A4B Instruct", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 32768 + "context": 256000, + "output": 8192 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false - }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-09", - "last_updated": "2025-09", - "type": "chat" - }, - { - "id": "Qwen/Qwen3-Embedding-4B", - "name": "Qwen 3 Embedding 4B", - "display_name": "Qwen 3 Embedding 4B", - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 32768, - "output": 8192 + "supported": true, + "default": true }, - "temperature": false, - "tool_call": false, - "reasoning": { - "supported": false + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "none", + "low", + "medium", + "high" + ] + } }, - "attachment": false, + "attachment": true, "open_weights": true, - "knowledge": "2024-12", - "release_date": "2025-06-05", - "last_updated": "2025-06-05", - "type": "embedding" + "release_date": "2026-04-02", + "last_updated": "2026-04-02", + "type": "chat" }, { - "id": "Qwen/Qwen3.6-35B-A3B", - "name": "Qwen3.6 35B-A3B", - "display_name": "Qwen3.6 35B-A3B", + "id": "openai-gpt-6-sol", + "name": "GPT-6 Sol", + "display_name": "GPT-6 Sol", "modalities": { "input": [ "text", @@ -47941,10 +49358,10 @@ ] }, "limit": { - "context": 262144, - "output": 65536 + "context": 1050000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -47952,25 +49369,27 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "none", + "low", + "medium", + "high", + "xhigh", + "max" ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-04-17", - "last_updated": "2026-04-17", + "open_weights": false, + "knowledge": "2026-04-20", + "release_date": "2026-09-22", + "last_updated": "2026-09-22", "type": "chat" }, { - "id": "Qwen/Qwen3-235B-A22B-Thinking-2507", - "name": "Qwen3-235B-A22B-Thinking-2507", - "display_name": "Qwen3-235B-A22B-Thinking-2507", + "id": "mercury-3", + "name": "Mercury 3", + "display_name": "Mercury 3", "modalities": { "input": [ "text" @@ -47980,8 +49399,8 @@ ] }, "limit": { - "context": 262144, - "output": 81920 + "context": 1000000, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -47991,54 +49410,24 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "none", + "low", + "medium", + "high" ] } }, "attachment": false, - "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-07-25", - "last_updated": "2025-07-25", - "type": "chat" - }, - { - "id": "Qwen/Qwen3-Coder-30B-A3B-Instruct", - "name": "Qwen3-Coder 30B-A3B Instruct", - "display_name": "Qwen3-Coder 30B-A3B Instruct", - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 262144, - "output": 65536 - }, - "temperature": true, - "tool_call": true, - "reasoning": { - "supported": false - }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-07-31", - "last_updated": "2025-07-31", + "open_weights": false, + "release_date": "2026-10-16", + "last_updated": "2026-10-20", "type": "chat" }, { - "id": "Qwen/Qwen3.5-27B", - "name": "Qwen3.5 27B", - "display_name": "Qwen3.5 27B", + "id": "openai-gpt-54-pro", + "name": "GPT-5.4 Pro", + "display_name": "GPT-5.4 Pro", "modalities": { "input": [ "text", @@ -48049,10 +49438,10 @@ ] }, "limit": { - "context": 262144, - "output": 65536 + "context": 1000000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -48060,36 +49449,38 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "none", + "medium", + "high", + "xhigh" ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-02-23", - "last_updated": "2026-02-23", + "open_weights": false, + "knowledge": "2025-08-31", + "release_date": "2026-03-05", + "last_updated": "2026-03-05", "type": "chat" }, { - "id": "Qwen/Qwen3-32B", - "name": "Qwen3 32B", - "display_name": "Qwen3 32B", + "id": "qwen-3-7-max", + "name": "Qwen 3.7 Max", + "display_name": "Qwen 3.7 Max", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 16384 + "context": 1000000, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -48097,28 +49488,16 @@ "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } - }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-04-28", - "last_updated": "2025-04-28", + "attachment": true, + "open_weights": false, + "release_date": "2026-05-21", + "last_updated": "2026-05-21", "type": "chat" }, { - "id": "Qwen/Qwen3.5-122B-A10B", - "name": "Qwen3.5 122B-A10B", - "display_name": "Qwen3.5 122B-A10B", + "id": "claude-opus-4-5", + "name": "Claude Opus 4.5", + "display_name": "Claude Opus 4.5", "modalities": { "input": [ "text", @@ -48129,65 +49508,83 @@ ] }, "limit": { - "context": 262144, - "output": 65536 + "context": 198000, + "output": 32768 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": false, + "mode": "mixed", + "budget": { + "min": 1024, + "unit": "tokens" + }, + "effort": "high", + "effort_options": [ + "low", + "medium", + "high" + ], "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ "thinking_blocks" + ], + "notes": [ + "Claude Opus 4.5 uses manual thinking.type = \"enabled\" with budget_tokens; effort can be used alongside the thinking budget.", + "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header." ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-02-23", - "last_updated": "2026-02-23", + "open_weights": false, + "knowledge": "2025-05", + "release_date": "2025-11-24", + "last_updated": "2025-11-24", "type": "chat" }, { - "id": "Qwen/Qwen3-VL-235B-A22B-Instruct", - "name": "Qwen3 VL 235B A22B Instruct", - "display_name": "Qwen3 VL 235B A22B Instruct", + "id": "qwen-3-8-flash", + "name": "Qwen 3.8 Flash", + "display_name": "Qwen 3.8 Flash", "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 32768 + "context": 1000000, + "output": 131072 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, "attachment": true, - "open_weights": true, - "knowledge": "2025-03-31", - "release_date": "2025-09-23", - "last_updated": "2025-09-23", + "open_weights": false, + "release_date": "2026-08-26", + "last_updated": "2026-08-26", "type": "chat" }, { - "id": "Qwen/Qwen3-VL-235B-A22B-Thinking", - "name": "Qwen3 VL 235B A22B Thinking", - "display_name": "Qwen3 VL 235B A22B Thinking", + "id": "openai-gpt-56-luna", + "name": "GPT-5.6 Luna", + "display_name": "GPT-5.6 Luna", "modalities": { "input": [ "text", @@ -48198,10 +49595,10 @@ ] }, "limit": { - "context": 131072, - "output": 32768 + "context": 1000000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -48209,76 +49606,75 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], "effort_options": [ + "none", "low", "medium", - "high" + "high", + "xhigh", + "max" ] } }, "attachment": true, - "open_weights": true, - "knowledge": "2025-03-31", - "release_date": "2025-09-23", - "last_updated": "2025-09-23", + "open_weights": false, + "knowledge": "2026-02-16", + "release_date": "2026-07-09", + "last_updated": "2026-07-09", "type": "chat" }, { - "id": "Qwen/Qwen3-Coder-Next", - "name": "Qwen3-Coder-Next", - "display_name": "Qwen3-Coder-Next", + "id": "gemini-3-5-flash-lite", + "name": "Gemini 3.5 Flash-Lite", + "display_name": "Gemini 3.5 Flash-Lite", "modalities": { "input": [ - "text" + "text", + "image", + "audio", + "video" ], "output": [ "text" ] }, "limit": { - "context": 262144, + "context": 1000000, "output": 65536 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-09", - "release_date": "2026-02-03", - "last_updated": "2026-02-03", + "attachment": true, + "open_weights": false, + "knowledge": "2026-03", + "release_date": "2026-07-21", + "last_updated": "2026-07-21", "type": "chat" }, { - "id": "Qwen/Qwen3-30B-A3B", - "name": "Qwen3 30B A3B", - "display_name": "Qwen3 30B A3B", + "id": "qwen3-vl-235b-a22b", + "name": "Qwen3 VL 235B", + "display_name": "Qwen3 VL 235B", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 40960, + "context": 128000, "output": 16384 }, - "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true + "supported": true }, "extra_capabilities": { "reasoning": { @@ -48291,16 +49687,16 @@ ] } }, - "attachment": false, + "attachment": true, "open_weights": true, - "release_date": "2025-04-28", - "last_updated": "2025-04-28", + "release_date": "2026-01-16", + "last_updated": "2026-06-11", "type": "chat" }, { - "id": "Qwen/Qwen3.8-27B", - "name": "Qwen3.8 27B", - "display_name": "Qwen3.8 27B", + "id": "openai-gpt-54-mini", + "name": "GPT-5.4 Mini", + "display_name": "GPT-5.4 Mini", "modalities": { "input": [ "text", @@ -48311,8 +49707,8 @@ ] }, "limit": { - "context": 262144, - "output": 32768 + "context": 400000, + "output": 128000 }, "temperature": true, "tool_call": true, @@ -48323,22 +49719,25 @@ "extra_capabilities": { "reasoning": { "effort_options": [ + "none", "low", "medium", + "high", "xhigh" ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-08-14", - "last_updated": "2026-08-14", + "open_weights": false, + "knowledge": "2025-08-31", + "release_date": "2026-03-17", + "last_updated": "2026-03-17", "type": "chat" }, { - "id": "Qwen/Qwen3.5-35B-A3B", - "name": "Qwen3.5 35B-A3B", - "display_name": "Qwen3.5 35B-A3B", + "id": "claude-opus-4-8-fast", + "name": "Claude Opus 4.8 Fast", + "display_name": "Claude Opus 4.8 Fast", "modalities": { "input": [ "text", @@ -48349,48 +49748,130 @@ ] }, "limit": { - "context": 262144, - "output": 65536 + "context": 1000000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": false, + "mode": "effort", + "effort": "high", + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" + ], "interleaved": true, "summaries": true, - "visibility": "summary", + "visibility": "omitted", "continuation": [ "thinking_blocks" + ], + "notes": [ + "Claude Opus 4.7 and newer Opus models require thinking.type = \"adaptive\" to enable thinking explicitly.", + "Manual budget_tokens requests return 400 on Claude Opus 4.7 and newer adaptive-only Opus models.", + "task_budget is separate from thinking control and should not be treated as a thinking budget." ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-02-23", - "last_updated": "2026-02-23", + "open_weights": false, + "knowledge": "2026-01", + "release_date": "2026-05-28", + "last_updated": "2026-05-28", "type": "chat" }, { - "id": "Qwen/Qwen3.8-2.4T-A95B", - "name": "Qwen3.8 2.4T A95B", - "display_name": "Qwen3.8 2.4T A95B", + "id": "kimi-k3-1-pro", + "name": "Kimi K3.1 Pro", + "display_name": "Kimi K3.1 Pro", "modalities": { "input": [ - "text" - ], + "text", + "image" + ], "output": [ "text" ] }, "limit": { - "context": 262144, + "context": 1000000, "output": 131072 }, + "temperature": false, + "tool_call": true, + "reasoning": { + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "none", + "low", + "high", + "max" + ] + } + }, + "attachment": true, + "open_weights": false, + "release_date": "2026-10-23", + "last_updated": "2026-10-23", + "type": "chat" + }, + { + "id": "venice-uncensored-role-play", + "name": "Venice Role Play Uncensored", + "display_name": "Venice Role Play Uncensored", + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 4096 + }, + "tool_call": true, + "reasoning": { + "supported": false + }, + "attachment": true, + "open_weights": true, + "release_date": "2026-02-20", + "last_updated": "2026-06-11", + "type": "chat" + }, + { + "id": "hy4-preview", + "name": "Hy4 Preview", + "display_name": "Hy4 Preview", + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1048576, + "output": 64000 + }, "temperature": true, "tool_call": true, "reasoning": { @@ -48401,22 +49882,22 @@ "reasoning": { "supported": true, "effort_options": [ + "none", "low", - "medium", - "xhigh" + "high" ] } }, "attachment": false, "open_weights": true, - "release_date": "2026-08-12", - "last_updated": "2026-08-12", + "release_date": "2026-08-28", + "last_updated": "2026-08-28", "type": "chat" }, { - "id": "Qwen/Qwen3-Coder-480B-A35B-Instruct", - "name": "Qwen3-Coder-480B-A35B-Instruct", - "display_name": "Qwen3-Coder-480B-A35B-Instruct", + "id": "z-ai-glm-5-turbo", + "name": "GLM 5 Turbo", + "display_name": "GLM 5 Turbo", "modalities": { "input": [ "text" @@ -48426,25 +49907,35 @@ ] }, "limit": { - "context": 262144, - "output": 65536 + "context": 200000, + "output": 32768 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "none", + "low", + "medium", + "high" + ] + } }, "attachment": false, - "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-07-22", - "last_updated": "2025-07-22", + "open_weights": false, + "release_date": "2026-03-16", + "last_updated": "2026-03-16", "type": "chat" }, { - "id": "Qwen/Qwen3-Embedding-8B", - "name": "Qwen 3 Embedding 8B", - "display_name": "Qwen 3 Embedding 8B", + "id": "aion-labs-aion-3-0", + "name": "Aion 3.0", + "display_name": "Aion 3.0", "modalities": { "input": [ "text" @@ -48454,37 +49945,36 @@ ] }, "limit": { - "context": 32768, - "output": 8192 + "context": 128000, + "output": 32768 }, - "temperature": false, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, "attachment": false, - "open_weights": true, - "knowledge": "2024-12", - "release_date": "2025-06-05", - "last_updated": "2025-06-05", - "type": "embedding" + "open_weights": false, + "release_date": "2026-05-05", + "last_updated": "2026-05-05", + "type": "chat" }, { - "id": "Qwen/Qwen3.5-9B", - "name": "Qwen3.5 9B", - "display_name": "Qwen3.5 9B", + "id": "zai-org-glm-5", + "name": "GLM 5", + "display_name": "GLM 5", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 65536 + "context": 198000, + "output": 32000 }, "temperature": true, "tool_call": true, @@ -48500,19 +49990,25 @@ "visibility": "summary", "continuation": [ "thinking_blocks" + ], + "effort_options": [ + "none", + "low", + "medium", + "high" ] } }, - "attachment": true, + "attachment": false, "open_weights": true, - "release_date": "2026-02-23", - "last_updated": "2026-02-23", + "release_date": "2026-02-12", + "last_updated": "2026-02-12", "type": "chat" }, { - "id": "Qwen/Qwen3-235B-A22B-Instruct-2507", - "name": "Qwen3 235B-A22B Instruct 2507", - "display_name": "Qwen3 235B-A22B Instruct 2507", + "id": "openai-gpt-oss-120b", + "name": "OpenAI GPT OSS 120B", + "display_name": "OpenAI GPT OSS 120B", "modalities": { "input": [ "text" @@ -48522,24 +50018,36 @@ ] }, "limit": { - "context": 262144, + "context": 128000, "output": 16384 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "none", + "low", + "medium", + "high" + ] + } }, "attachment": false, "open_weights": true, - "release_date": "2025-07-21", - "last_updated": "2025-07-21", + "knowledge": "2024-06", + "release_date": "2025-08-05", + "last_updated": "2025-08-05", "type": "chat" }, { - "id": "Qwen/Qwen3-235B-A22B", - "name": "Qwen3 235B-A22B", - "display_name": "Qwen3 235B-A22B", + "id": "deepseek-v4-pro", + "name": "DeepSeek V4 Pro", + "display_name": "DeepSeek V4 Pro", "modalities": { "input": [ "text" @@ -48549,8 +50057,8 @@ ] }, "limit": { - "context": 40960, - "output": 16384 + "context": 1000000, + "output": 32768 }, "temperature": true, "tool_call": true, @@ -48561,38 +50069,50 @@ "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "high", + "effort_options": [ + "low", + "high", + "max" + ], "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ "thinking_blocks" + ], + "notes": [ + "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." ] } }, "attachment": false, "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-04-28", - "last_updated": "2025-04-28", + "knowledge": "2025-05", + "release_date": "2026-04-24", + "last_updated": "2026-04-24", "type": "chat" }, { - "id": "XiaomiMiMo/MiMo-V2.5", - "name": "MiMo-V2.5", - "display_name": "MiMo-V2.5", + "id": "claude-sonnet-5-5", + "name": "Claude Sonnet 5.5", + "display_name": "Claude Sonnet 5.5", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 131072 + "context": 1000000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -48601,94 +50121,111 @@ "extra_capabilities": { "reasoning": { "effort_options": [ - "none", "low", "medium", "high", - "xhigh" + "xhigh", + "max" ] } }, - "attachment": false, - "open_weights": true, - "knowledge": "2024-12", - "release_date": "2026-04-22", - "last_updated": "2026-04-22", + "attachment": true, + "open_weights": false, + "knowledge": "2026-06", + "release_date": "2026-09-28", + "last_updated": "2026-09-28", "type": "chat" }, { - "id": "XiaomiMiMo/MiMo-V2.5-Pro", - "name": "MiMo-V2.5-Pro", - "display_name": "MiMo-V2.5-Pro", + "id": "claude-sonnet-4-6", + "name": "Claude Sonnet 4.6", + "display_name": "Claude Sonnet 4.6", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 1000000, + "output": 64000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": false, + "mode": "mixed", + "budget": { + "min": 1024, + "unit": "tokens" + }, + "effort": "high", "effort_options": [ - "none", "low", "medium", "high", - "xhigh" + "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Anthropic recommends adaptive thinking with effort for Claude 4.6; budget_tokens remains a deprecated compatibility path.", + "Anthropic API defaults effort to high; lower effort levels should be chosen per workload." ] } }, - "attachment": false, - "open_weights": true, - "knowledge": "2024-12", - "release_date": "2026-04-22", - "last_updated": "2026-04-22", + "attachment": true, + "open_weights": false, + "knowledge": "2025-08-31", + "release_date": "2026-02-17", + "last_updated": "2026-03-13", "type": "chat" }, { - "id": "google/gemma-3-12b-it", - "name": "Gemma 3 12B IT", - "display_name": "Gemma 3 12B IT", + "id": "llama-3.2-3b", + "name": "Llama 3.2 3B", + "display_name": "Llama 3.2 3B", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 131072 + "context": 128000, + "output": 4096 }, "temperature": true, "tool_call": true, "reasoning": { "supported": false }, - "attachment": true, + "attachment": false, "open_weights": true, - "knowledge": "2024-08", - "release_date": "2025-03-12", - "last_updated": "2025-03-12", + "knowledge": "2023-12", + "release_date": "2024-09-25", + "last_updated": "2024-09-25", "type": "chat" }, { - "id": "google/gemma-4-31B-it", - "name": "Gemma 4 31B IT", - "display_name": "Gemma 4 31B IT", + "id": "openai-gpt-56-sol", + "name": "GPT-5.6 Sol", + "display_name": "GPT-5.6 Sol", "modalities": { "input": [ "text", @@ -48699,10 +50236,10 @@ ] }, "limit": { - "context": 262144, - "output": 32768 + "context": 1000000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -48710,32 +50247,40 @@ }, "extra_capabilities": { "reasoning": { - "supported": true + "effort_options": [ + "none", + "low", + "medium", + "high", + "xhigh", + "max" + ] } }, "attachment": true, - "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-04-02", - "last_updated": "2026-04-02", + "open_weights": false, + "knowledge": "2026-02-16", + "release_date": "2026-07-09", + "last_updated": "2026-07-09", "type": "chat" }, { - "id": "google/gemma-4-26B-A4B-it", - "name": "Gemma 4 26B A4B IT", - "display_name": "Gemma 4 26B A4B IT", + "id": "seed-2-1-turbo", + "name": "Seed 2.1 Turbo", + "display_name": "Seed 2.1 Turbo", "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 32768 + "context": 256000, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -48743,50 +50288,48 @@ "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "supported": true - } - }, "attachment": true, - "open_weights": true, - "release_date": "2026-04-02", - "last_updated": "2026-04-02", + "open_weights": false, + "release_date": "2026-06-23", + "last_updated": "2026-06-23", "type": "chat" }, { - "id": "google/gemma-3-4b-it", - "name": "Gemma 3 4B IT", - "display_name": "Gemma 3 4B IT", + "id": "gemini-3-7-flash", + "name": "Gemini 3.7 Flash", + "display_name": "Gemini 3.7 Flash", "modalities": { "input": [ "text", - "image" + "image", + "audio", + "video" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 131072 + "context": 1000000, + "output": 65536 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, "attachment": true, - "open_weights": true, - "knowledge": "2024-08", - "release_date": "2025-03-12", - "last_updated": "2025-03-12", + "open_weights": false, + "knowledge": "2026-03", + "release_date": "2026-08-13", + "last_updated": "2026-08-13", "type": "chat" }, { - "id": "google/gemma-3-27b-it", - "name": "Gemma 3 27B IT", - "display_name": "Gemma 3 27B IT", + "id": "venice-uncensored-1-2", + "name": "Venice Uncensored 1.2", + "display_name": "Venice Uncensored 1.2", "modalities": { "input": [ "text", @@ -48797,25 +50340,23 @@ ] }, "limit": { - "context": 131072, - "output": 131072 + "context": 128000, + "output": 8192 }, - "temperature": true, "tool_call": true, "reasoning": { "supported": false }, "attachment": true, "open_weights": true, - "knowledge": "2024-08", - "release_date": "2025-03-12", - "last_updated": "2025-03-12", + "release_date": "2026-04-01", + "last_updated": "2026-06-11", "type": "chat" }, { - "id": "thinkingmachines/Inkling", - "name": "Inkling", - "display_name": "Inkling", + "id": "qwen3-6-35b-a3b", + "name": "Qwen 3.6 35B A3B", + "display_name": "Qwen 3.6 35B A3B", "modalities": { "input": [ "text", @@ -48826,8 +50367,8 @@ ] }, "limit": { - "context": 1048576, - "output": 1048576 + "context": 256000, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -48835,25 +50376,16 @@ "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "low", - "medium", - "high" - ] - } - }, "attachment": true, "open_weights": true, - "release_date": "2026-07-15", - "last_updated": "2026-07-15", + "release_date": "2026-04-17", + "last_updated": "2026-04-17", "type": "chat" }, { - "id": "thinkingmachines/Inkling-Small", - "name": "Inkling Small", - "display_name": "Inkling Small", + "id": "openai-gpt-4o-mini-2024-07-18", + "name": "GPT-4o Mini", + "display_name": "GPT-4o Mini", "modalities": { "input": [ "text", @@ -48864,36 +50396,38 @@ ] }, "limit": { - "context": 524288, - "output": 1048576 + "context": 128000, + "output": 16384 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true + "supported": false }, "attachment": true, - "open_weights": true, - "release_date": "2026-07-30", - "last_updated": "2026-07-30", + "open_weights": false, + "knowledge": "2023-09", + "release_date": "2024-07-18", + "last_updated": "2024-07-18", "type": "chat" }, { - "id": "openai/gpt-oss-20b", - "name": "GPT OSS 20B", - "display_name": "GPT OSS 20B", + "id": "qwen3-6-27b", + "name": "Qwen 3.6 27B", + "display_name": "Qwen 3.6 27B", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 32768 + "context": 256000, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -48903,75 +50437,92 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, "effort_options": [ + "none", "low", "medium", "high" ] } }, - "attachment": false, + "attachment": true, "open_weights": true, - "knowledge": "2024-06", - "release_date": "2025-08-05", - "last_updated": "2025-08-05", + "release_date": "2026-04-22", + "last_updated": "2026-04-22", "type": "chat" }, { - "id": "openai/gpt-oss-120b", - "name": "GPT OSS 120B", - "display_name": "GPT OSS 120B", + "id": "claude-opus-4-8", + "name": "Claude Opus 4.8", + "display_name": "Claude Opus 4.8", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 32768 + "context": 1000000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": false, + "mode": "effort", + "effort": "high", "effort_options": [ "low", "medium", - "high" + "high", + "xhigh", + "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "omitted", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Claude Opus 4.7 and newer Opus models require thinking.type = \"adaptive\" to enable thinking explicitly.", + "Manual budget_tokens requests return 400 on Claude Opus 4.7 and newer adaptive-only Opus models.", + "task_budget is separate from thinking control and should not be treated as a thinking budget." ] } }, - "attachment": false, - "open_weights": true, - "knowledge": "2024-06", - "release_date": "2025-08-05", - "last_updated": "2025-08-05", + "attachment": true, + "open_weights": false, + "knowledge": "2026-01", + "release_date": "2026-05-28", + "last_updated": "2026-05-28", "type": "chat" }, { - "id": "tencent/Hy3", - "name": "Hy3", - "display_name": "Hy3", + "id": "grok-4-6", + "name": "Grok 4.6", + "display_name": "Grok 4.6", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 128000 + "context": 500000, + "output": 200000 }, "temperature": true, "tool_call": true, @@ -48981,24 +50532,25 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, "effort_options": [ - "none", "low", - "high" + "medium", + "high", + "xhigh" ] } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-07-06", - "last_updated": "2026-07-06", + "attachment": true, + "open_weights": false, + "knowledge": "2026-02-01", + "release_date": "2026-08-12", + "last_updated": "2026-08-12", "type": "chat" }, { - "id": "tencent/Hy4-preview", - "name": "Hy4 preview", - "display_name": "Hy4 preview", + "id": "aion-labs-aion-3-0-mini", + "name": "Aion 3.0 Mini", + "display_name": "Aion 3.0 Mini", "modalities": { "input": [ "text" @@ -49008,8 +50560,8 @@ ] }, "limit": { - "context": 1000000, - "output": 64000 + "context": 128000, + "output": 32768 }, "temperature": true, "tool_call": true, @@ -49017,37 +50569,27 @@ "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "effort_options": [ - "none", - "high" - ] - } - }, "attachment": false, - "open_weights": true, - "release_date": "2026-08-28", - "last_updated": "2026-08-28", + "open_weights": false, + "release_date": "2026-05-14", + "last_updated": "2026-05-14", "type": "chat" }, { - "id": "MiniMaxAI/MiniMax-M3.1-Flash", - "name": "MiniMax-M3.1-Flash", - "display_name": "MiniMax-M3.1-Flash", + "id": "minimax-m27", + "name": "MiniMax M2.7", + "display_name": "MiniMax M2.7", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 512000 + "context": 198000, + "output": 32768 }, "temperature": true, "tool_call": true, @@ -49057,60 +50599,53 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], "effort_options": [ + "none", "low", "medium", - "high", - "xhigh", - "max" + "high" ] } }, - "attachment": true, - "open_weights": false, - "release_date": "2026-10-19", - "last_updated": "2026-10-19", + "attachment": false, + "open_weights": true, + "release_date": "2026-03-18", + "last_updated": "2026-03-18", "type": "chat" }, { - "id": "MiniMaxAI/MiniMax-M1-80k", - "name": "MiniMax M1 80K", - "display_name": "MiniMax M1 80K", + "id": "google-gemma-3-27b-it", + "name": "Google Gemma 3 27B Instruct", + "display_name": "Google Gemma 3 27B Instruct", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 80000 + "context": 198000, + "output": 16384 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true + "supported": false }, - "attachment": false, + "attachment": true, "open_weights": true, - "release_date": "2025-06-17", - "last_updated": "2025-06-17", + "knowledge": "2024-08", + "release_date": "2025-03-12", + "last_updated": "2025-03-12", "type": "chat" }, { - "id": "MiniMaxAI/MiniMax-M2", - "name": "MiniMax-M2", - "display_name": "MiniMax-M2", + "id": "llama-3.3-70b", + "name": "Llama 3.3 70B", + "display_name": "Llama 3.3 70B", "modalities": { "input": [ "text" @@ -49120,37 +50655,36 @@ ] }, "limit": { - "context": 204800, - "output": 131072 + "context": 128000, + "output": 4096 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true + "supported": false }, "attachment": false, "open_weights": true, - "release_date": "2025-10-27", - "last_updated": "2025-10-27", + "knowledge": "2023-12", + "release_date": "2024-12-06", + "last_updated": "2024-12-06", "type": "chat" }, { - "id": "MiniMaxAI/MiniMax-M3", - "name": "MiniMax-M3", - "display_name": "MiniMax-M3", + "id": "zai-org-glm-4.6", + "name": "GLM 4.6", + "display_name": "GLM 4.6", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 524288, - "output": 512000 + "context": 198000, + "output": 16384 }, "temperature": true, "tool_call": true, @@ -49160,19 +50694,31 @@ }, "extra_capabilities": { "reasoning": { - "supported": true + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "effort_options": [ + "low", + "medium", + "high" + ] } }, - "attachment": true, + "attachment": false, "open_weights": true, - "release_date": "2026-06-01", - "last_updated": "2026-06-01", + "knowledge": "2025-04", + "release_date": "2025-09-30", + "last_updated": "2025-09-30", "type": "chat" }, { - "id": "MiniMaxAI/MiniMax-M2.1", - "name": "MiniMax-M2.1", - "display_name": "MiniMax-M2.1", + "id": "deepseek-v4-flash", + "name": "DeepSeek V4 Flash 0423", + "display_name": "DeepSeek V4 Flash 0423", "modalities": { "input": [ "text" @@ -49182,8 +50728,8 @@ ] }, "limit": { - "context": 204800, - "output": 131072 + "context": 1000000, + "output": 32768 }, "temperature": true, "tool_call": true, @@ -49194,74 +50740,107 @@ "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "high", + "effort_options": [ + "low", + "high", + "max" + ], "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ "thinking_blocks" + ], + "notes": [ + "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." ] } }, "attachment": false, "open_weights": true, - "knowledge": "2025-10", - "release_date": "2025-12-23", - "last_updated": "2025-12-23", + "knowledge": "2025-05", + "release_date": "2026-04-24", + "last_updated": "2026-04-24", "type": "chat" }, { - "id": "MiniMaxAI/MiniMax-M2.7", - "name": "MiniMax-M2.7", - "display_name": "MiniMax-M2.7", + "id": "claude-opus-4-6", + "name": "Claude Opus 4.6", + "display_name": "Claude Opus 4.6", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 204800, - "output": 131072 + "context": 1000000, + "output": 128000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": false, + "mode": "mixed", + "budget": { + "min": 1024, + "unit": "tokens" + }, + "effort": "high", + "effort_options": [ + "low", + "medium", + "high", + "max" + ], "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ "thinking_blocks" + ], + "notes": [ + "Anthropic recommends adaptive thinking with effort for Claude 4.6; budget_tokens remains a deprecated compatibility path.", + "Anthropic API defaults effort to high; lower effort levels should be chosen per workload." ] } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-03-18", - "last_updated": "2026-03-18", + "attachment": true, + "open_weights": false, + "knowledge": "2025-05-31", + "release_date": "2026-02-05", + "last_updated": "2026-03-13", "type": "chat" }, { - "id": "MiniMaxAI/MiniMax-M2.5", - "name": "MiniMax-M2.5", - "display_name": "MiniMax-M2.5", + "id": "xiaomi-mimo-v2-6-flash", + "name": "MiMo-V2.6-Flash", + "display_name": "MiMo-V2.6-Flash", "modalities": { "input": [ - "text" + "text", + "image", + "audio", + "video" ], "output": [ "text" ] }, "limit": { - "context": 204800, + "context": 1000000, "output": 131072 }, "temperature": true, @@ -49272,40 +50851,38 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "none", + "low", + "medium", + "high" ] } }, - "attachment": false, + "attachment": true, "open_weights": true, - "release_date": "2026-02-12", - "last_updated": "2026-02-12", + "release_date": "2026-09-22", + "last_updated": "2026-09-22", "type": "chat" }, { - "id": "moonshotai/Kimi-K2.5", - "name": "Kimi-K2.5", - "display_name": "Kimi-K2.5", + "id": "claude-fable-5-1", + "name": "Claude Fable 5.1", + "display_name": "Claude Fable 5.1", "modalities": { "input": [ "text", - "image", - "video" + "image" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 1000000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -49314,25 +50891,40 @@ "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "high", + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" + ], "interleaved": true, "summaries": true, - "visibility": "summary", + "visibility": "omitted", "continuation": [ "thinking_blocks" + ], + "notes": [ + "Adaptive thinking is always on for Claude Fable 5 and Claude Mythos 5; thinking.type = \"disabled\" is rejected.", + "Manual budget_tokens requests return 400 on Claude Fable 5 and Claude Mythos 5.", + "thinking.display defaults to omitted; set display to summarized to receive readable thinking summaries." ] } }, "attachment": true, - "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-01-27", - "last_updated": "2026-01-27", + "open_weights": false, + "knowledge": "2026-06", + "release_date": "2026-09-01", + "last_updated": "2026-09-01", "type": "chat" }, { - "id": "moonshotai/Kimi-K2.7-Code", - "name": "Kimi K2.7 Code", - "display_name": "Kimi K2.7 Code", + "id": "claude-opus-5-5-fast", + "name": "Claude Opus 5.5 Fast", + "display_name": "Claude Opus 5.5 Fast", "modalities": { "input": [ "text", @@ -49343,8 +50935,8 @@ ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 1000000, + "output": 128000 }, "temperature": false, "tool_call": true, @@ -49354,63 +50946,70 @@ }, "extra_capabilities": { "reasoning": { - "supported": true + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" + ] } }, "attachment": true, - "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-06-12", - "last_updated": "2026-06-12", + "open_weights": false, + "knowledge": "2026-06", + "release_date": "2026-09-22", + "last_updated": "2026-09-22", "type": "chat" }, { - "id": "moonshotai/Kimi-K2-Instruct-0905", - "name": "Kimi-K2-Instruct-0905", - "display_name": "Kimi-K2-Instruct-0905", + "id": "z-ai-glm-5-3-flash", + "name": "GLM 5.3 Flash", + "display_name": "GLM 5.3 Flash", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 16384 + "context": 1048576, + "output": 131072 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, + "attachment": true, "open_weights": true, - "knowledge": "2024-10", - "release_date": "2025-09-05", - "last_updated": "2025-09-05", + "release_date": "2026-08-26", + "last_updated": "2026-08-26", "type": "chat" }, { - "id": "moonshotai/Kimi-K2.6", - "name": "Kimi-K2.6", - "display_name": "Kimi-K2.6", + "id": "openai-gpt-56-terra", + "name": "GPT-5.6 Terra", + "display_name": "GPT-5.6 Terra", "modalities": { "input": [ "text", - "image", - "video" + "image" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 1000000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -49418,37 +51017,39 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "none", + "low", + "medium", + "high", + "xhigh", + "max" ] } }, "attachment": true, - "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-04-20", - "last_updated": "2026-04-20", + "open_weights": false, + "knowledge": "2026-02-16", + "release_date": "2026-07-09", + "last_updated": "2026-07-09", "type": "chat" }, { - "id": "moonshotai/Kimi-K2-Thinking", - "name": "Kimi-K2-Thinking", - "display_name": "Kimi-K2-Thinking", + "id": "deepseek-v4-1-flash", + "name": "DeepSeek V4.1 Flash", + "display_name": "DeepSeek V4.1 Flash", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 1000000, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -49458,40 +51059,40 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "none", + "low", + "high", + "max" ] } }, - "attachment": false, + "attachment": true, "open_weights": true, - "knowledge": "2024-08", - "release_date": "2025-11-06", - "last_updated": "2025-11-06", + "knowledge": "2025-05", + "release_date": "2026-09-10", + "last_updated": "2026-09-10", "type": "chat" }, { - "id": "moonshotai/Kimi-K3", - "name": "Kimi K3", - "display_name": "Kimi K3", + "id": "muse-wonder", + "name": "Muse Wonder", + "display_name": "Muse Wonder", "modalities": { "input": [ "text", - "image" + "image", + "audio", + "video" ], "output": [ "text" ] }, "limit": { - "context": 1000000, + "context": 1048576, "output": 131072 }, - "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -49499,65 +51100,74 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, "effort_options": [ + "none", "low", + "medium", "high", "max" ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-07-16", - "last_updated": "2026-07-16", + "open_weights": false, + "release_date": "2026-10-12", + "last_updated": "2026-10-15", "type": "chat" }, { - "id": "moonshotai/Kimi-K2-Instruct", - "name": "Kimi-K2-Instruct", - "display_name": "Kimi-K2-Instruct", + "id": "deepseek-v4-1-flash-1017-uncensored", + "name": "DeepSeek V4.1 Flash 1017 Uncensored", + "display_name": "DeepSeek V4.1 Flash 1017 Uncensored", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 16384 + "context": 1000000, + "output": 131072 }, - "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "none", + "low", + "high", + "max" + ] + } + }, + "attachment": true, "open_weights": true, - "knowledge": "2024-10", - "release_date": "2025-07-11", - "last_updated": "2025-07-11", + "release_date": "2026-10-27", + "last_updated": "2026-10-27", "type": "chat" }, { - "id": "stepfun-ai/Step-3.7-Flash", - "name": "Step 3.7 Flash", - "display_name": "Step 3.7 Flash", + "id": "zai-org-glm-5-1", + "name": "GLM 5.1", + "display_name": "GLM 5.1", "modalities": { "input": [ - "text", - "image", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 256000 + "context": 200000, + "output": 80000 }, "temperature": true, "tool_call": true, @@ -49567,24 +51177,31 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ + "none", "low", "medium", "high" ] } }, - "attachment": true, + "attachment": false, "open_weights": true, - "knowledge": "2026-03-01", - "release_date": "2026-05-29", - "last_updated": "2026-05-29", + "release_date": "2026-04-07", + "last_updated": "2026-04-07", "type": "chat" }, { - "id": "stepfun-ai/Step-3.5-Flash", - "name": "Step 3.5 Flash", - "display_name": "Step 3.5 Flash", + "id": "qwen3-next-80b", + "name": "Qwen 3 Next 80b", + "display_name": "Qwen 3 Next 80b", "modalities": { "input": [ "text" @@ -49594,82 +51211,96 @@ ] }, "limit": { - "context": 262144, - "output": 256000 + "context": 256000, + "output": 16384 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true + "supported": false }, "attachment": false, "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-01-29", - "last_updated": "2026-02-13", + "knowledge": "2025-04", + "release_date": "2025-09", + "last_updated": "2025-09", "type": "chat" }, { - "id": "meta-llama/Llama-Guard-4-12B", - "name": "Llama Guard 4 12B", - "display_name": "Llama Guard 4 12B", + "id": "nvidia-nemotron-3-5-super-vl-120b-a12b", + "name": "NVIDIA Nemotron 3.5 Super VL 120B", + "display_name": "NVIDIA Nemotron 3.5 Super VL 120B", "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 163840, - "output": 163840 + "context": 262144, + "output": 32768 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, "attachment": true, "open_weights": true, - "release_date": "2025-04-29", - "last_updated": "2025-04-29", + "release_date": "2026-10-15", + "last_updated": "2026-10-15", "type": "chat" }, { - "id": "meta-llama/Llama-3.3-70B-Instruct", - "name": "Llama-3.3-70B-Instruct", - "display_name": "Llama-3.3-70B-Instruct", + "id": "gemini-3-5-flash", + "name": "Gemini 3.5 Flash", + "display_name": "Gemini 3.5 Flash", "modalities": { "input": [ - "text" + "text", + "image", + "audio", + "video" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 4096 + "context": 1000000, + "output": 65536 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, - "open_weights": true, - "knowledge": "2023-12", - "release_date": "2024-12-06", - "last_updated": "2024-12-06", + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "low", + "medium", + "high" + ] + } + }, + "attachment": true, + "open_weights": false, + "knowledge": "2025-01", + "release_date": "2026-05-19", + "last_updated": "2026-05-19", "type": "chat" }, { - "id": "meta-llama/Llama-3.1-8B-Instruct", - "name": "Llama-3.1-8B-Instruct", - "display_name": "Llama-3.1-8B-Instruct", + "id": "qwen3-235b-a22b-instruct-2507", + "name": "Qwen 3 235B A22B Instruct 2507", + "display_name": "Qwen 3 235B A22B Instruct 2507", "modalities": { "input": [ "text" @@ -49679,8 +51310,8 @@ ] }, "limit": { - "context": 131072, - "output": 4096 + "context": 128000, + "output": 16384 }, "temperature": true, "tool_call": true, @@ -49689,27 +51320,28 @@ }, "attachment": false, "open_weights": true, - "knowledge": "2023-12", - "release_date": "2024-07-23", - "last_updated": "2024-07-23", + "release_date": "2025-07-21", + "last_updated": "2025-07-21", "type": "chat" }, { - "id": "deepseek-ai/DeepSeek-V4.1-Flash", - "name": "DeepSeek V4.1 Flash", - "display_name": "DeepSeek V4.1 Flash", + "id": "gemini-3-1-pro-preview", + "name": "Gemini 3.1 Pro Preview", + "display_name": "Gemini 3.1 Pro Preview", "modalities": { "input": [ "text", - "image" + "image", + "audio", + "video" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 384000 + "context": 1000000, + "output": 32768 }, "temperature": true, "tool_call": true, @@ -49717,12 +51349,51 @@ "supported": true, "default": true }, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "low", + "medium", + "high" + ] + } + }, + "attachment": true, + "open_weights": false, + "knowledge": "2025-01", + "release_date": "2026-02-19", + "last_updated": "2026-02-19", + "type": "chat" + }, + { + "id": "claude-opus-5", + "name": "Claude Opus 5", + "display_name": "Claude Opus 5", + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1000000, + "output": 128000 + }, + "temperature": false, + "tool_call": true, + "reasoning": { + "supported": true, + "default": true + }, "extra_capabilities": { "reasoning": { "supported": true, "effort_options": [ - "none", "low", + "medium", "high", "xhigh", "max" @@ -49730,43 +51401,71 @@ } }, "attachment": true, - "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-09-10", - "last_updated": "2026-09-10", + "open_weights": false, + "knowledge": "2026-05", + "release_date": "2026-07-24", + "last_updated": "2026-07-24", "type": "chat" }, { - "id": "deepseek-ai/DeepSeek-V3-0324", - "name": "DeepSeek V3 0324", - "display_name": "DeepSeek V3 0324", + "id": "openai-gpt-4o-2024-11-20", + "name": "GPT-4o", + "display_name": "GPT-4o", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 163840, - "output": 163840 + "context": 128000, + "output": 16384 }, "temperature": true, "tool_call": true, "reasoning": { "supported": false }, + "attachment": true, + "open_weights": false, + "knowledge": "2023-09", + "release_date": "2024-11-20", + "last_updated": "2024-11-20", + "type": "chat" + }, + { + "id": "qwen3-coder-480b-a35b-instruct-turbo", + "name": "Qwen 3 Coder 480B Turbo", + "display_name": "Qwen 3 Coder 480B Turbo", + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 256000, + "output": 65536 + }, + "tool_call": true, + "reasoning": { + "supported": false + }, "attachment": false, "open_weights": true, - "release_date": "2025-03-24", - "last_updated": "2025-03-24", + "release_date": "2026-01-27", + "last_updated": "2026-06-11", "type": "chat" }, { - "id": "deepseek-ai/DeepSeek-V4-Flash-0731", - "name": "DeepSeek V4 Flash 0731", - "display_name": "DeepSeek V4 Flash 0731", + "id": "qwen3-235b-a22b-thinking-2507", + "name": "Qwen 3 235B A22B Thinking 2507", + "display_name": "Qwen 3 235B A22B Thinking 2507", "modalities": { "input": [ "text" @@ -49776,8 +51475,8 @@ ] }, "limit": { - "context": 1048576, - "output": 384000 + "context": 128000, + "output": 16384 }, "temperature": true, "tool_call": true, @@ -49795,71 +51494,62 @@ "thinking_blocks" ], "effort_options": [ - "high", - "max" + "low", + "medium", + "high" ] } }, "attachment": false, "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-07-31", - "last_updated": "2026-07-31", + "release_date": "2025-07-25", + "last_updated": "2025-07-25", "type": "chat" }, { - "id": "deepseek-ai/DeepSeek-V4-Pro-0813", - "name": "DeepSeek V4 Pro 0813", - "display_name": "DeepSeek V4 Pro 0813", + "id": "grok-4-20", + "name": "Grok 4.20", + "display_name": "Grok 4.20", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 384000 + "context": 2000000, + "output": 128000 }, - "temperature": true, "tool_call": true, "reasoning": { "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "effort_options": [ - "low", - "high", - "max" - ] - } - }, - "attachment": false, - "open_weights": true, - "release_date": "2026-08-13", - "last_updated": "2026-08-22", + "attachment": true, + "open_weights": false, + "release_date": "2026-03-12", + "last_updated": "2026-06-11", "type": "chat" }, { - "id": "deepseek-ai/DeepSeek-R1", - "name": "DeepSeek-R1", - "display_name": "DeepSeek-R1", + "id": "openai-gpt-53-codex", + "name": "GPT-5.3 Codex", + "display_name": "GPT-5.3 Codex", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 64000, - "output": 32768 + "context": 400000, + "output": 128000 }, "temperature": true, "tool_call": true, @@ -49869,26 +51559,26 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "none", + "low", + "medium", + "high", + "xhigh" ] } }, - "attachment": false, - "open_weights": true, - "knowledge": "2024-07", - "release_date": "2025-01-20", - "last_updated": "2025-05-29", + "attachment": true, + "open_weights": false, + "knowledge": "2025-08-31", + "release_date": "2026-02-05", + "last_updated": "2026-02-05", "type": "chat" }, { - "id": "deepseek-ai/DeepSeek-V4-Pro", - "name": "DeepSeek V4 Pro", - "display_name": "DeepSeek V4 Pro", + "id": "deepseek-v4-pro-0813", + "name": "DeepSeek V4 Pro 0813", + "display_name": "DeepSeek V4 Pro 0813", "modalities": { "input": [ "text" @@ -49898,8 +51588,8 @@ ] }, "limit": { - "context": 1048576, - "output": 393216 + "context": 1000000, + "output": 32768 }, "temperature": true, "tool_call": true, @@ -49910,36 +51600,24 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "high", "effort_options": [ + "none", "low", "high", "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." ] } }, "attachment": false, "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-04-24", - "last_updated": "2026-04-24", + "release_date": "2026-08-13", + "last_updated": "2026-08-22", "type": "chat" }, { - "id": "deepseek-ai/DeepSeek-V3.2", - "name": "DeepSeek-V3.2", - "display_name": "DeepSeek-V3.2", + "id": "olafangensan-glm-4.7-flash-heretic", + "name": "GLM 4.7 Flash Heretic", + "display_name": "GLM 4.7 Flash Heretic", "modalities": { "input": [ "text" @@ -49949,10 +51627,9 @@ ] }, "limit": { - "context": 163840, - "output": 65536 + "context": 200000, + "output": 24000 }, - "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -49960,31 +51637,38 @@ }, "extra_capabilities": { "reasoning": { - "supported": true + "effort_options": [ + "none", + "low", + "medium", + "high" + ] } }, "attachment": false, "open_weights": true, - "knowledge": "2024-07", - "release_date": "2025-12-01", - "last_updated": "2025-12-01", + "release_date": "2026-02-04", + "last_updated": "2026-06-11", "type": "chat" }, { - "id": "deepseek-ai/DeepSeek-V3.1", - "name": "DeepSeek-V3.1", - "display_name": "DeepSeek-V3.1", + "id": "gemini-3-6-flash", + "name": "Gemini 3.6 Flash", + "display_name": "Gemini 3.6 Flash", "modalities": { "input": [ - "text" + "text", + "image", + "audio", + "video" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 8192 + "context": 1000000, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -49992,16 +51676,17 @@ "supported": true, "default": true }, - "attachment": false, - "open_weights": true, - "release_date": "2025-08-21", - "last_updated": "2025-08-21", + "attachment": true, + "open_weights": false, + "knowledge": "2026-03", + "release_date": "2026-07-21", + "last_updated": "2026-07-21", "type": "chat" }, { - "id": "deepseek-ai/DeepSeek-V4-Flash-Vision-Exp", - "name": "DeepSeek V4 Flash Vision Exp", - "display_name": "DeepSeek V4 Flash Vision Exp", + "id": "deepseek-v4-1-flash-1017", + "name": "DeepSeek v4.1 Flash (1017)", + "display_name": "DeepSeek v4.1 Flash (1017)", "modalities": { "input": [ "text", @@ -50012,8 +51697,8 @@ ] }, "limit": { - "context": 1048576, - "output": 384000 + "context": 1000000, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -50023,48 +51708,36 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "high", "effort_options": [ + "none", "low", "high", "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." ] } }, "attachment": true, "open_weights": true, - "release_date": "2026-08-21", - "last_updated": "2026-09-01", + "knowledge": "2025-05", + "release_date": "2026-10-17", + "last_updated": "2026-10-17", "type": "chat" }, { - "id": "deepseek-ai/DeepSeek-V4.1-Flash-1017", - "name": "DeepSeek V4.1 Flash 1017", - "display_name": "DeepSeek V4.1 Flash 1017", + "id": "deepseek-v4-flash-0731", + "name": "DeepSeek V4 Flash 0731", + "display_name": "DeepSeek V4 Flash 0731", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 384000 + "context": 1000000, + "output": 32768 }, "temperature": true, "tool_call": true, @@ -50074,35 +51747,41 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, "effort_options": [ + "none", "low", "high", "max" ] } }, - "attachment": true, + "attachment": false, "open_weights": true, "knowledge": "2025-05", - "release_date": "2026-10-17", - "last_updated": "2026-10-17", + "release_date": "2026-07-31", + "last_updated": "2026-07-31", "type": "chat" }, { - "id": "deepseek-ai/DeepSeek-V4-Flash", - "name": "DeepSeek V4 Flash", - "display_name": "DeepSeek V4 Flash", + "id": "gemini-3-flash-preview", + "name": "Gemini 3 Flash Preview", + "display_name": "Gemini 3 Flash Preview", "modalities": { "input": [ - "text" + "text", + "image", + "video", + "audio", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 384000 + "context": 256000, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -50114,48 +51793,47 @@ "reasoning": { "supported": true, "default_enabled": true, - "mode": "effort", - "effort": "high", - "effort_options": [ + "mode": "level", + "level": "high", + "level_options": [ + "minimal", "low", - "high", - "max" + "medium", + "high" ], "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ "thinking_blocks" - ], - "notes": [ - "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." ] } }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-04-24", - "last_updated": "2026-04-24", + "attachment": true, + "open_weights": false, + "knowledge": "2025-01", + "release_date": "2025-12-17", + "last_updated": "2025-12-17", "type": "chat" }, { - "id": "deepseek-ai/DeepSeek-R1-0528", - "name": "DeepSeek-R1-0528", - "display_name": "DeepSeek-R1-0528", + "id": "openai-gpt-61-terra", + "name": "GPT-6.1 Terra", + "display_name": "GPT-6.1 Terra", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 163840, - "output": 32768 + "context": 1050000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -50163,75 +51841,71 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "none", + "low", + "medium", + "high", + "xhigh", + "max" ] } }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-05", - "release_date": "2025-05-28", - "last_updated": "2025-05-28", + "attachment": true, + "open_weights": false, + "knowledge": "2026-06-01", + "release_date": "2026-10-26", + "last_updated": "2026-10-26", "type": "chat" }, { - "id": "deepseek-ai/DeepSeek-V3", - "name": "DeepSeek-V3", - "display_name": "DeepSeek-V3", + "id": "qwen-3-8-max", + "name": "Qwen 3.8 Max", + "display_name": "Qwen 3.8 Max", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 64000, - "output": 8192 + "context": 1000000, + "output": 131072 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, - "open_weights": true, - "release_date": "2024-12-26", - "last_updated": "2024-12-26", + "attachment": true, + "open_weights": false, + "release_date": "2026-07-19", + "last_updated": "2026-07-19", "type": "chat" - } - ] - }, - "lmstudio": { - "id": "lmstudio", - "name": "LMStudio", - "display_name": "LMStudio", - "api": "http://127.0.0.1:1234/v1", - "doc": "https://lmstudio.ai/models", - "models": [ + }, { - "id": "openai/gpt-oss-20b", - "name": "GPT OSS 20B", - "display_name": "GPT OSS 20B", + "id": "kimi-k3-fast-api", + "name": "Kimi K3 Fast", + "display_name": "Kimi K3 Fast", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 32768 + "context": 1000000, + "output": 131072 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -50239,25 +51913,24 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, "effort_options": [ + "none", "low", - "medium", - "high" + "high", + "max" ] } }, - "attachment": false, + "attachment": true, "open_weights": true, - "knowledge": "2024-06", - "release_date": "2025-08-05", - "last_updated": "2025-08-05", + "release_date": "2026-07-16", + "last_updated": "2026-07-16", "type": "chat" }, { - "id": "qwen/qwen3-coder-30b", - "name": "Qwen3 Coder 30B", - "display_name": "Qwen3 Coder 30B", + "id": "z-ai-glm-5-3", + "name": "GLM 5.3", + "display_name": "GLM 5.3", "modalities": { "input": [ "text" @@ -50267,150 +51940,182 @@ ] }, "limit": { - "context": 262144, - "output": 65536 + "context": 1000000, + "output": 131072 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, "attachment": false, "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-07-31", - "last_updated": "2025-07-31", + "release_date": "2026-08-14", + "last_updated": "2026-08-14", "type": "chat" }, { - "id": "qwen/qwen3-30b-a3b-2507", - "name": "Qwen3 30B A3B 2507", - "display_name": "Qwen3 30B A3B 2507", + "id": "gemini-3-8-flash", + "name": "Gemini 3.8 Flash", + "display_name": "Gemini 3.8 Flash", "modalities": { "input": [ - "text" + "text", + "image", + "audio", + "video" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 16384 + "context": 1000000, + "output": 65536 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-07-28", - "last_updated": "2025-07-28", + "attachment": true, + "open_weights": false, + "release_date": "2026-09-02", + "last_updated": "2026-09-02", "type": "chat" } ] }, - "aimlapi": { - "id": "aimlapi", - "name": "AI/ML API", - "display_name": "AI/ML API", - "api": "https://api.aimlapi.com/v1", - "doc": "https://docs.aimlapi.com", + "fastrouter": { + "id": "fastrouter", + "name": "FastRouter", + "display_name": "FastRouter", + "api": "https://go.fastrouter.ai/api/v1", + "doc": "https://fastrouter.ai/models", "models": [ { - "id": "bytedance-seed/seed-1.6-flash", - "name": "Seed 1.6 Flash", - "display_name": "Seed 1.6 Flash", + "id": "openai/gpt-5.4-nano", + "name": "GPT-5.4 nano", + "display_name": "GPT-5.4 nano", "modalities": { "input": [ "text", - "image", - "video" + "image" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 32768 + "context": 400000, + "output": 128000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": false + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": false, + "mode": "effort", + "effort": "none", + "effort_options": [ + "none", + "low", + "medium", + "high", + "xhigh" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" + } }, "attachment": true, "open_weights": false, - "release_date": "2025-08-28", - "last_updated": "2025-08-28", + "knowledge": "2025-08-31", + "release_date": "2026-03-17", + "last_updated": "2026-03-17", "type": "chat" }, { - "id": "bytedance-seed/seed-2.0-mini", - "name": "Seed-2.0-Mini", - "display_name": "Seed-2.0-Mini", + "id": "openai/gpt-5.4-mini", + "name": "GPT-5.4 mini", + "display_name": "GPT-5.4 mini", "modalities": { "input": [ "text", - "image", - "video" + "image" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 131072 + "context": 400000, + "output": 128000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], + "default_enabled": false, + "mode": "effort", + "effort": "none", "effort_options": [ + "none", + "low", + "medium", + "high", + "xhigh" + ], + "verbosity": "medium", + "verbosity_options": [ "low", "medium", "high" - ] + ], + "visibility": "hidden" } }, "attachment": true, "open_weights": false, - "release_date": "2026-02-14", - "last_updated": "2026-02-14", + "knowledge": "2025-08-31", + "release_date": "2026-03-17", + "last_updated": "2026-03-17", "type": "chat" }, { - "id": "bytedance-seed/seed-2.0-code", - "name": "Seed-2.0-Code", - "display_name": "Seed-2.0-Code", + "id": "openai/gpt-5.3-codex", + "name": "GPT-5.3 Codex", + "display_name": "GPT-5.3 Codex", "modalities": { "input": [ "text", "image", - "video" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 131072 + "context": 400000, + "output": 128000 }, "temperature": true, "tool_call": true, @@ -50421,44 +52126,49 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], + "default_enabled": true, + "mode": "effort", + "effort": "medium", "effort_options": [ + "low", + "medium", + "high", + "xhigh" + ], + "verbosity": "medium", + "verbosity_options": [ "low", "medium", "high" - ] + ], + "visibility": "hidden" } }, "attachment": true, "open_weights": false, - "release_date": "2026-02-14", - "last_updated": "2026-02-14", + "knowledge": "2025-08-31", + "release_date": "2026-02-05", + "last_updated": "2026-02-05", "type": "chat" }, { - "id": "bytedance-seed/seed-2.0-lite", - "name": "Seed-2.0-Lite", - "display_name": "Seed-2.0-Lite", + "id": "openai/gpt-5-nano", + "name": "GPT-5 Nano", + "display_name": "GPT-5 Nano", "modalities": { "input": [ "text", - "image", - "video" + "image" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 131072 + "context": 400000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -50467,111 +52177,178 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], + "default_enabled": true, + "mode": "effort", + "effort": "medium", "effort_options": [ + "minimal", "low", "medium", "high" - ] + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, "attachment": true, "open_weights": false, - "release_date": "2026-02-14", - "last_updated": "2026-02-14", + "knowledge": "2024-10-01", + "release_date": "2025-08-07", + "last_updated": "2025-08-07", "type": "chat" }, { - "id": "undi95/remm-slerp-l2-13b", - "name": "ReMM SLERP 13B", - "display_name": "ReMM SLERP 13B", + "id": "openai/gpt-5.5-pro", + "name": "GPT-5.5 Pro", + "display_name": "GPT-5.5 Pro", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 6144, - "output": 4096 + "context": 1050000, + "output": 128000 }, - "temperature": true, - "tool_call": false, + "temperature": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, - "open_weights": true, - "knowledge": "2023-06-30", - "release_date": "2023-07-22", - "last_updated": "2023-07-22", + "extra_capabilities": { + "reasoning": { + "supported": true, + "effort_options": [ + "low", + "medium", + "high" + ] + } + }, + "attachment": true, + "open_weights": false, + "knowledge": "2025-12-01", + "release_date": "2026-04-23", + "last_updated": "2026-04-23", "type": "chat" }, { - "id": "inference-net/schematron-v2-small", - "name": "Schematron V2 Small", - "display_name": "Schematron V2 Small", + "id": "openai/gpt-5", + "name": "GPT-5", + "display_name": "GPT-5", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 4096 + "context": 400000, + "output": 128000 }, - "temperature": true, - "tool_call": false, + "temperature": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", + "effort_options": [ + "minimal", + "low", + "medium", + "high" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" + } + }, + "attachment": true, "open_weights": false, - "release_date": "2026-04-16", - "last_updated": "2026-04-16", + "knowledge": "2024-10-01", + "release_date": "2025-08-07", + "last_updated": "2025-08-07", "type": "chat" }, { - "id": "inference-net/schematron-v2-turbo", - "name": "Schematron V2 Turbo", - "display_name": "Schematron V2 Turbo", + "id": "openai/gpt-5-mini", + "name": "GPT-5 Mini", + "display_name": "GPT-5 Mini", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 8192 + "context": 400000, + "output": 128000 }, - "temperature": true, - "tool_call": false, + "temperature": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", + "effort_options": [ + "minimal", + "low", + "medium", + "high" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" + } + }, + "attachment": true, "open_weights": false, - "release_date": "2026-04-16", - "last_updated": "2026-04-16", + "knowledge": "2024-10-01", + "release_date": "2025-08-07", + "last_updated": "2025-08-07", "type": "chat" }, { - "id": "gryphe/mythomax-l2-13b", - "name": "MythoMax 13B", - "display_name": "MythoMax 13B", + "id": "openai/gpt-oss-120b", + "name": "GPT OSS 120B", + "display_name": "GPT OSS 120B", "modalities": { "input": [ "text" @@ -50581,65 +52358,100 @@ ] }, "limit": { - "context": 4096, - "output": 4096 + "context": 131072, + "output": 32768 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "effort_options": [ + "low", + "medium", + "high" + ] + } }, "attachment": false, "open_weights": true, - "release_date": "2023-08-10", - "last_updated": "2023-08-10", + "knowledge": "2024-06", + "release_date": "2025-08-05", + "last_updated": "2025-08-05", "type": "chat" }, { - "id": "unbiased/pareto", - "name": "Pareto", - "display_name": "Pareto", + "id": "openai/gpt-5.5", + "name": "GPT-5.5", + "display_name": "GPT-5.5", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 131072 + "context": 1050000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", + "effort_options": [ + "low", + "medium", + "high", + "xhigh" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" + } }, "attachment": true, "open_weights": false, - "release_date": "2026-09-17", - "last_updated": "2026-09-17", + "knowledge": "2025-12-01", + "release_date": "2026-04-23", + "last_updated": "2026-04-23", "type": "chat" }, { - "id": "x-ai/grok-4-20-0309-non-reasoning", - "name": "Grok 4.20 Non-Reasoning", - "display_name": "Grok 4.20 Non-Reasoning", + "id": "openai/gpt-4.1", + "name": "GPT-4.1", + "display_name": "GPT-4.1", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 2000000, - "output": 1999000 + "context": 1047576, + "output": 32768 }, "temperature": true, "tool_call": true, @@ -50648,44 +52460,42 @@ }, "attachment": true, "open_weights": false, - "release_date": "2026-03-09", - "last_updated": "2026-03-09", + "knowledge": "2024-04", + "release_date": "2025-04-14", + "last_updated": "2025-04-14", "type": "chat" }, { - "id": "x-ai/grok-4-fast-non-reasoning", - "name": "Grok 4 Fast Non-Reasoning", - "display_name": "Grok 4 Fast Non-Reasoning", + "id": "openai/gpt-image-2", + "name": "GPT Image 2", + "display_name": "GPT Image 2", "modalities": { "input": [ "text", - "image", - "audio", - "video" + "image" ], "output": [ - "text" + "image" ] }, "limit": { - "context": 2000000, - "output": 1999000 + "context": 128000, + "output": 8192 }, - "temperature": true, - "tool_call": true, + "temperature": false, + "tool_call": false, "reasoning": { "supported": false }, "attachment": true, "open_weights": false, - "release_date": "2025-12-18", - "last_updated": "2025-12-18", - "type": "chat" + "release_date": "2026-04-21", + "last_updated": "2026-04-21" }, { - "id": "anthracite-org/magnum-v4-72b", - "name": "Magnum v4 72B", - "display_name": "Magnum v4 72B", + "id": "openai/gpt-oss-20b", + "name": "GPT OSS 20B", + "display_name": "GPT OSS 20B", "modalities": { "input": [ "text" @@ -50695,38 +52505,8 @@ ] }, "limit": { - "context": 32000, - "output": 16384 - }, - "temperature": true, - "tool_call": false, - "reasoning": { - "supported": false - }, - "attachment": false, - "open_weights": true, - "knowledge": "2024-06-30", - "release_date": "2024-10-22", - "last_updated": "2024-10-22", - "type": "chat" - }, - { - "id": "alibaba/qwen3.8-max-prime", - "name": "Qwen3.8 Max Prime", - "display_name": "Qwen3.8 Max Prime", - "modalities": { - "input": [ - "text", - "image", - "video" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 1000000, - "output": 131072 + "context": 131072, + "output": 32768 }, "temperature": true, "tool_call": true, @@ -50736,37 +52516,35 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, "effort_options": [ - "minimal", "low", "medium", - "high", - "xhigh" + "high" ] } }, - "attachment": true, - "open_weights": false, - "release_date": "2026-09-23", - "last_updated": "2026-09-23", + "attachment": false, + "open_weights": true, + "knowledge": "2024-06", + "release_date": "2025-08-05", + "last_updated": "2025-08-05", "type": "chat" }, { - "id": "alibaba/qwen3.8-27b", - "name": "Qwen3.8 27B", - "display_name": "Qwen3.8 27B", + "id": "sarvam/sarvam-105b", + "name": "Sarvam 105B", + "display_name": "Sarvam 105B", "modalities": { "input": [ - "text", - "image", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 262144, + "context": 131072, "output": 131072 }, "temperature": true, @@ -50775,102 +52553,68 @@ "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "effort_options": [ - "low", - "medium" - ] - } - }, - "attachment": true, + "attachment": false, "open_weights": true, - "release_date": "2026-08-14", - "last_updated": "2026-08-14", + "release_date": "2026-02-18", + "last_updated": "2026-02-18", "type": "chat" }, { - "id": "alibaba/qwen3.8-2.4t-a95b", - "name": "Qwen3.8 2.4T A95B", - "display_name": "Qwen3.8 2.4T A95B", + "id": "leonardo-ai/lucid-origin", + "name": "Lucid Origin", + "display_name": "Lucid Origin", "modalities": { "input": [ "text" ], "output": [ - "text" + "image" ] }, "limit": { - "context": 262144, - "output": 52429 + "context": 4096, + "output": 8192 }, - "temperature": true, - "tool_call": true, + "temperature": false, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "effort_options": [ - "low", - "medium" - ] - } + "supported": false }, "attachment": false, - "open_weights": true, - "release_date": "2026-08-12", - "last_updated": "2026-08-12", - "type": "chat" + "open_weights": false, + "release_date": "2025-06-01", + "last_updated": "2025-06-01" }, { - "id": "alibaba/qwen3-coder-480b-a35b-instruct", - "name": "Qwen3 Coder", - "display_name": "Qwen3 Coder", + "id": "leonardo-ai/lucid-realism", + "name": "Lucid Realism", + "display_name": "Lucid Realism", "modalities": { "input": [ "text" ], "output": [ - "text" + "image" ] }, "limit": { - "context": 262000, - "output": 65536 + "context": 4096, + "output": 8192 }, - "temperature": true, - "tool_call": true, + "temperature": false, + "tool_call": false, "reasoning": { "supported": false }, "attachment": false, - "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-07-22", - "last_updated": "2025-07-22", - "type": "chat" + "open_weights": false, + "release_date": "2025-06-01", + "last_updated": "2025-06-01" }, { - "id": "alibaba/qwen3-next-80b-a3b-instruct", - "name": "Qwen3 Next 80B A3B Instruct", - "display_name": "Qwen3 Next 80B A3B Instruct", + "id": "z-ai/glm-5", + "name": "GLM-5", + "display_name": "GLM-5", "modalities": { "input": [ "text" @@ -50880,25 +52624,36 @@ ] }, "limit": { - "context": 129024, - "output": 16384 + "context": 204800, + "output": 131072 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } }, "attachment": false, "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-09", - "last_updated": "2025-09", + "release_date": "2026-02-12", + "last_updated": "2026-02-12", "type": "chat" }, { - "id": "alibaba/qwen-plus", - "name": "Qwen Plus", - "display_name": "Qwen Plus", + "id": "z-ai/glm-5.1", + "name": "GLM-5.1", + "display_name": "GLM-5.1", "modalities": { "input": [ "text" @@ -50908,8 +52663,8 @@ ] }, "limit": { - "context": 131000, - "output": 16384 + "context": 200000, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -50919,39 +52674,29 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] + "supported": true } }, "attachment": false, - "open_weights": false, - "knowledge": "2024-04", - "release_date": "2024-01-25", - "last_updated": "2025-09-11", + "open_weights": true, + "release_date": "2026-04-07", + "last_updated": "2026-04-07", "type": "chat" }, { - "id": "alibaba/qwen3.8-max", - "name": "Qwen3.8 Max", - "display_name": "Qwen3.8 Max", + "id": "minimax/minimax-m2.7-highspeed", + "name": "MiniMax-M2.7-highspeed", + "display_name": "MiniMax-M2.7-highspeed", "modalities": { "input": [ - "text", - "image", - "video", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, + "context": 204800, "output": 131072 }, "temperature": true, @@ -50968,35 +52713,29 @@ "visibility": "summary", "continuation": [ "thinking_blocks" - ], - "effort_options": [ - "low", - "medium" ] } }, - "attachment": true, - "open_weights": false, - "release_date": "2026-08-03", - "last_updated": "2026-08-03", + "attachment": false, + "open_weights": true, + "release_date": "2026-03-18", + "last_updated": "2026-03-18", "type": "chat" }, { - "id": "alibaba/qwen3.8-flash", - "name": "Qwen3.8 Flash", - "display_name": "Qwen3.8 Flash", + "id": "minimax/minimax-m2.7", + "name": "MiniMax-M2.7", + "display_name": "MiniMax-M2.7", "modalities": { "input": [ - "text", - "image", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, + "context": 204800, "output": 131072 }, "temperature": true, @@ -51013,167 +52752,243 @@ "visibility": "summary", "continuation": [ "thinking_blocks" - ], - "effort_options": [ - "low", - "medium" ] } }, - "attachment": true, - "open_weights": false, - "release_date": "2026-08-26", - "last_updated": "2026-08-26", + "attachment": false, + "open_weights": true, + "release_date": "2026-03-18", + "last_updated": "2026-03-18", "type": "chat" }, { - "id": "alibaba/qwen-max", - "name": "Qwen Max", - "display_name": "Qwen Max", + "id": "bytedance/seedance-2", + "name": "Seedance 2", + "display_name": "Seedance 2", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ - "text" + "video" ] }, "limit": { - "context": 32000, + "context": 4096, "output": 8192 }, - "temperature": true, - "tool_call": true, + "temperature": false, + "tool_call": false, "reasoning": { "supported": false }, - "attachment": false, + "attachment": true, "open_weights": false, - "knowledge": "2024-04", - "release_date": "2024-04-03", - "last_updated": "2025-01-25", - "type": "chat" + "release_date": "2026-02-12", + "last_updated": "2026-10-04" }, { - "id": "cohere/command-r-08-2024", - "name": "Command R (08-2024)", - "display_name": "Command R (08-2024)", + "id": "moonshotai/kimi-k2.6", + "name": "Kimi K2.6", + "display_name": "Kimi K2.6", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 4000 + "context": 262144, + "output": 262144 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, + "extra_capabilities": { + "reasoning": { + "supported": true + } + }, + "attachment": true, "open_weights": true, - "knowledge": "2024-06-01", - "release_date": "2024-08-30", - "last_updated": "2024-08-30", + "knowledge": "2025-01", + "release_date": "2026-04-20", + "last_updated": "2026-04-20", "type": "chat" }, { - "id": "cohere/command-a", - "name": "Command A", - "display_name": "Command A", + "id": "google/gemini-3.5-flash", + "name": "Gemini 3.5 Flash", + "display_name": "Gemini 3.5 Flash", "modalities": { "input": [ - "text" + "text", + "image", + "video", + "audio", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 256000, - "output": 256000 + "context": 1048576, + "output": 65536 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, - "open_weights": true, - "knowledge": "2024-08-31", - "release_date": "2025-03-13", - "last_updated": "2025-03-13", + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "level", + "level": "high", + "level_options": [ + "minimal", + "low", + "medium", + "high" + ], + "summaries": true, + "visibility": "summary", + "continuation": [ + "thought_signatures" + ] + } + }, + "attachment": true, + "open_weights": false, + "knowledge": "2025-01", + "release_date": "2026-05-19", + "last_updated": "2026-05-19", "type": "chat" }, { - "id": "cohere/command-r7b-12-2024", - "name": "Command R7B (12-2024)", - "display_name": "Command R7B (12-2024)", + "id": "google/veo3.1", + "name": "Veo 3.1", + "display_name": "Veo 3.1", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ - "text" + "video" ] }, "limit": { - "context": 128000, - "output": 4000 + "context": 400000, + "output": 8192 }, - "temperature": true, - "tool_call": true, + "temperature": false, + "tool_call": false, "reasoning": { "supported": false }, - "attachment": false, - "open_weights": true, - "knowledge": "2024-06-01", - "release_date": "2024-12-13", - "last_updated": "2024-12-13", - "type": "chat" + "attachment": true, + "open_weights": false, + "release_date": "2025-10-15", + "last_updated": "2026-01" }, { - "id": "cohere/command-r-plus-08-2024", - "name": "Command R+ (08-2024)", - "display_name": "Command R+ (08-2024)", + "id": "google/gemini-2.5-pro", + "name": "Gemini 2.5 Pro", + "display_name": "Gemini 2.5 Pro", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 4000 + "context": 1048576, + "output": 65536 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, - "open_weights": true, - "knowledge": "2024-06-01", - "release_date": "2024-08-30", - "last_updated": "2024-08-30", + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "budget", + "budget": { + "default": -1, + "min": 128, + "max": 32768, + "auto": -1, + "unit": "tokens" + }, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thought_signatures" + ] + } + }, + "attachment": true, + "open_weights": false, + "knowledge": "2025-01", + "release_date": "2025-06-17", + "last_updated": "2025-06-17", "type": "chat" }, { - "id": "sakana/fugu-ultra-v2", - "name": "Fugu Ultra v2", - "display_name": "Fugu Ultra v2", + "id": "google/veo3.1-lite", + "name": "Veo 3.1 Lite", + "display_name": "Veo 3.1 Lite", + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "video" + ] + }, + "limit": { + "context": 400000, + "output": 8192 + }, + "temperature": false, + "tool_call": false, + "reasoning": { + "supported": false + }, + "attachment": true, + "open_weights": false, + "release_date": "2026-03-31", + "last_updated": "2026-03-31" + }, + { + "id": "google/gemini-2.5-flash", + "name": "Gemini 2.5 Flash", + "display_name": "Gemini 2.5 Flash", "modalities": { "input": [ "text", "image", + "audio", "pdf" ], "output": [ @@ -51181,10 +52996,10 @@ ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 1048576, + "output": 65536 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -51192,26 +53007,41 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "high" + "supported": true, + "default_enabled": true, + "mode": "budget", + "budget": { + "default": -1, + "min": 0, + "max": 24576, + "auto": -1, + "off": 0, + "unit": "tokens" + }, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thought_signatures" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-08-28", - "release_date": "2026-09-11", - "last_updated": "2026-09-11", + "knowledge": "2025-01", + "release_date": "2025-06-17", + "last_updated": "2025-06-17", "type": "chat" }, { - "id": "sakana/fugu-max", - "name": "Fugu Max", - "display_name": "Fugu Max", + "id": "google/gemini-3.1-pro-preview", + "name": "Gemini 3.1 Pro Preview", + "display_name": "Gemini 3.1 Pro Preview", "modalities": { "input": [ "text", "image", + "video", + "audio", "pdf" ], "output": [ @@ -51219,10 +53049,10 @@ ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 1048576, + "output": 65536 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -51230,71 +53060,71 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ + "supported": true, + "default_enabled": true, + "mode": "level", + "level": "high", + "level_options": [ + "low", "high" + ], + "summaries": true, + "visibility": "summary", + "continuation": [ + "thought_signatures" ] } }, "attachment": true, "open_weights": false, - "release_date": "2026-09-11", - "last_updated": "2026-09-11", + "knowledge": "2025-01", + "release_date": "2026-02-19", + "last_updated": "2026-02-19", "type": "chat" }, { - "id": "sakana/fugu-ultra", - "name": "Fugu Ultra", - "display_name": "Fugu Ultra", + "id": "google/veo3.1-fast", + "name": "Veo 3.1 Fast", + "display_name": "Veo 3.1 Fast", "modalities": { "input": [ "text", "image" ], "output": [ - "text" + "video" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 400000, + "output": 8192 }, "temperature": false, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "high" - ] - } + "supported": false }, "attachment": true, "open_weights": false, - "release_date": "2026-06-15", - "last_updated": "2026-06-15", - "type": "chat" + "release_date": "2025-10-15", + "last_updated": "2026-01-01" }, { - "id": "xiaomi/mimo-v2.5", - "name": "MiMo V2.5", - "display_name": "MiMo V2.5", + "id": "google/gemma-4-31b-it", + "name": "Gemma 4 31B IT", + "display_name": "Gemma 4 31B IT", "modalities": { "input": [ "text", - "image", - "audio", - "video" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 262144, + "output": 32768 }, "temperature": true, "tool_call": true, @@ -51304,40 +53134,37 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] + "supported": true } }, "attachment": true, "open_weights": true, - "knowledge": "2024-12", - "release_date": "2026-04-22", - "last_updated": "2026-04-22", + "knowledge": "2025-01", + "release_date": "2026-04-02", + "last_updated": "2026-04-02", "type": "chat" }, { - "id": "xiaomi/mimo-v2.5-pro", - "name": "MiMo V2.5 Pro", - "display_name": "MiMo V2.5 Pro", + "id": "google/gemini-3.1-flash-image-preview", + "name": "Nano Banana 2 Preview", + "display_name": "Nano Banana 2 Preview", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ - "text" + "text", + "image" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 65536, + "output": 65536 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { "supported": true, "default": true @@ -51345,41 +53172,39 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "low", + "medium", + "high" ] } }, - "attachment": false, - "open_weights": true, - "knowledge": "2024-12", - "release_date": "2026-04-22", - "last_updated": "2026-04-22", - "type": "chat" + "attachment": true, + "open_weights": false, + "knowledge": "2025-01", + "release_date": "2026-02-26", + "last_updated": "2026-02-26" }, { - "id": "anthropic/claude-fable-5.1", - "name": "Claude Fable 5.1", - "display_name": "Claude Fable 5.1", + "id": "google/gemini-3-pro-image-preview", + "name": "Nano Banana Pro Preview", + "display_name": "Nano Banana Pro Preview", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ - "text" + "text", + "image" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 65536, + "output": 32768 }, - "temperature": false, - "tool_call": true, + "temperature": true, + "tool_call": false, "reasoning": { "supported": true, "default": true @@ -51388,82 +53213,56 @@ "reasoning": { "supported": true, "default_enabled": true, - "mode": "effort", - "effort": "high", - "effort_options": [ + "mode": "level", + "level": "high", + "level_options": [ "low", - "medium", - "high", - "xhigh", - "max" + "high" ], - "interleaved": true, "summaries": true, - "visibility": "omitted", + "visibility": "summary", "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Adaptive thinking is always on for Claude Fable 5 and Claude Mythos 5; thinking.type = \"disabled\" is rejected.", - "Manual budget_tokens requests return 400 on Claude Fable 5 and Claude Mythos 5.", - "thinking.display defaults to omitted; set display to summarized to receive readable thinking summaries." + "thought_signatures" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-06", - "release_date": "2026-09-01", - "last_updated": "2026-09-01", - "type": "chat" + "knowledge": "2025-01", + "release_date": "2025-11-20", + "last_updated": "2025-11-20" }, { - "id": "anthropic/claude-opus-5", - "name": "Claude Opus 5", - "display_name": "Claude Opus 5", + "id": "wanx/wan-v2-6", + "name": "Wan 2.6", + "display_name": "Wan 2.6", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ - "text" + "video" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 400000, + "output": 8192 }, "temperature": false, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" - ] - } + "supported": false }, "attachment": true, "open_weights": false, - "knowledge": "2026-05", - "release_date": "2026-07-24", - "last_updated": "2026-07-24", - "type": "chat" + "release_date": "2025-12-03", + "last_updated": "2025-12-03" }, { "id": "anthropic/claude-opus-4.8", - "name": "Claude 4.8 Opus", - "display_name": "Claude 4.8 Opus", + "name": "Claude Opus 4.8", + "display_name": "Claude Opus 4.8", "modalities": { "input": [ "text", @@ -51518,58 +53317,9 @@ "type": "chat" }, { - "id": "anthropic/claude-sonnet-5", - "name": "Claude Sonnet 5", - "display_name": "Claude Sonnet 5", - "modalities": { - "input": [ - "text", - "image", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 1000000, - "output": 128000 - }, - "temperature": false, - "tool_call": true, - "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" - ] - } - }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-01-31", - "release_date": "2026-06-30", - "last_updated": "2026-06-30", - "type": "chat" - }, - { - "id": "anthropic/claude-opus-4.5", - "name": "Claude 4.5 Opus", - "display_name": "Claude 4.5 Opus", + "id": "anthropic/claude-opus-4.1", + "name": "Claude Opus 4.1", + "display_name": "Claude Opus 4.1", "modalities": { "input": [ "text", @@ -51582,7 +53332,7 @@ }, "limit": { "context": 200000, - "output": 64000 + "output": 32000 }, "temperature": true, "tool_call": true, @@ -51594,17 +53344,11 @@ "reasoning": { "supported": true, "default_enabled": false, - "mode": "mixed", + "mode": "budget", "budget": { "min": 1024, "unit": "tokens" }, - "effort": "high", - "effort_options": [ - "low", - "medium", - "high" - ], "interleaved": true, "summaries": true, "visibility": "summary", @@ -51612,22 +53356,22 @@ "thinking_blocks" ], "notes": [ - "Claude Opus 4.5 uses manual thinking.type = \"enabled\" with budget_tokens; effort can be used alongside the thinking budget.", - "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header." + "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", + "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-05", - "release_date": "2025-11-24", - "last_updated": "2025-11-24", + "knowledge": "2025-03-31", + "release_date": "2025-08-05", + "last_updated": "2025-08-05", "type": "chat" }, { "id": "anthropic/claude-sonnet-4.6", - "name": "Claude 4.6 Sonnet", - "display_name": "Claude 4.6 Sonnet", + "name": "Claude Sonnet 4.6", + "display_name": "Claude Sonnet 4.6", "modalities": { "input": [ "text", @@ -51639,7 +53383,7 @@ ] }, "limit": { - "context": 200000, + "context": 1000000, "output": 64000 }, "temperature": true, @@ -51684,38 +53428,38 @@ "type": "chat" }, { - "id": "anthropic/claude-haiku-4.5", - "name": "Claude 4.5 Haiku", - "display_name": "Claude 4.5 Haiku", + "id": "deepseek/deepseek-v4-pro", + "name": "DeepSeek V4 Pro", + "display_name": "DeepSeek V4 Pro", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 64000 + "context": 1000000, + "output": 384000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "budget", - "budget": { - "min": 1024, - "unit": "tokens" - }, + "default_enabled": true, + "mode": "effort", + "effort": "high", + "effort_options": [ + "low", + "high", + "max" + ], "interleaved": true, "summaries": true, "visibility": "summary", @@ -51723,22 +53467,21 @@ "thinking_blocks" ], "notes": [ - "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", - "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." + "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-02-28", - "release_date": "2025-10-15", - "last_updated": "2025-10-15", + "attachment": false, + "open_weights": true, + "knowledge": "2025-05", + "release_date": "2026-04-24", + "last_updated": "2026-04-24", "type": "chat" }, { - "id": "anthropic/claude-opus-4.7", - "name": "Claude 4.7 Opus", - "display_name": "Claude 4.7 Opus", + "id": "x-ai/grok-build-0.1", + "name": "Grok Build 0.1", + "display_name": "Grok Build 0.1", "modalities": { "input": [ "text", @@ -51750,52 +53493,35 @@ ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 256000, + "output": 256000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "high", "effort_options": [ "low", "medium", - "high", - "xhigh", - "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "omitted", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Claude Opus 4.7 and newer Opus models require thinking.type = \"adaptive\" to enable thinking explicitly.", - "Manual budget_tokens requests return 400 on Claude Opus 4.7 and newer adaptive-only Opus models.", - "task_budget is separate from thinking control and should not be treated as a thinking budget." + "high" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-01-31", "release_date": "2026-04-16", "last_updated": "2026-04-16", "type": "chat" }, { - "id": "anthropic/claude-sonnet-5.5", - "name": "Claude Sonnet 5.5", - "display_name": "Claude Sonnet 5.5", + "id": "x-ai/grok-4.3", + "name": "Grok 4.3", + "display_name": "Grok 4.3", "modalities": { "input": [ "text", @@ -51808,9 +53534,9 @@ }, "limit": { "context": 1000000, - "output": 128000 + "output": 30000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -51818,41 +53544,37 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, "effort_options": [ "low", "medium", - "high", - "xhigh", - "max" + "high" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-06", - "release_date": "2026-09-28", - "last_updated": "2026-09-28", + "release_date": "2026-04-17", + "last_updated": "2026-04-17", "type": "chat" }, { - "id": "anthropic/claude-fable-5", - "name": "Claude Fable 5", - "display_name": "Claude Fable 5", + "id": "x-ai/grok-4", + "name": "Grok 4", + "display_name": "Grok 4", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 256000, + "output": 64000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -51860,150 +53582,154 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "high", - "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Adaptive thinking is always on for Claude Fable 5 and Claude Mythos 5; thinking.type = \"disabled\" is rejected.", - "Manual budget_tokens requests return 400 on Claude Fable 5 and Claude Mythos 5.", - "thinking.display defaults to omitted; set display to summarized to receive readable thinking summaries." - ] + "supported": true } }, - "attachment": true, + "attachment": false, "open_weights": false, - "knowledge": "2026-01-31", - "release_date": "2026-06-09", - "last_updated": "2026-06-09", + "knowledge": "2025-07", + "release_date": "2025-07-09", + "last_updated": "2025-07-09", "type": "chat" - }, + } + ] + }, + "nano-gpt": { + "id": "nano-gpt", + "name": "NanoGPT", + "display_name": "NanoGPT", + "api": "https://nano-gpt.com/api/v1", + "doc": "https://docs.nano-gpt.com", + "models": [ { - "id": "anthropic/claude-sonnet-4.5", - "name": "Claude 4.5 Sonnet", - "display_name": "Claude 4.5 Sonnet", + "id": "Gemma-4-31B-MeroMero-v2:thinking", + "name": "Gemma 4 31B MeroMero v2 Thinking", + "display_name": "Gemma 4 31B MeroMero v2 Thinking", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 64000 + "context": 262144, + "output": 32768 }, - "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "budget", - "budget": { - "min": 1024, - "unit": "tokens" - }, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", - "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." - ] - } + "default": true }, "attachment": true, - "open_weights": false, - "knowledge": "2025-07-31", - "release_date": "2025-09-29", - "last_updated": "2025-09-29", + "open_weights": true, + "release_date": "2026-07-29", + "last_updated": "2026-08-24", "type": "chat" }, { - "id": "anthropic/claude-haiku-5.5", - "name": "Claude Haiku 5.5", - "display_name": "Claude Haiku 5.5", + "id": "gemini-2.0-pro-exp-02-05", + "name": "Gemini 2.0 Pro 0205", + "display_name": "Gemini 2.0 Pro 0205", "modalities": { "input": [ "text", "image", - "pdf" + "audio" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 2097152, + "output": 8192 }, - "temperature": false, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" - ] - } + "supported": false }, "attachment": true, "open_weights": false, - "knowledge": "2026-06", - "release_date": "2026-10-07", - "last_updated": "2026-10-07", + "release_date": "2025-02-05", + "last_updated": "2025-02-05", "type": "chat" }, { - "id": "anthropic/claude-opus-5.5", - "name": "Claude Opus 5.5", - "display_name": "Claude Opus 5.5", + "id": "Gemma-4-26B-A4B-MeroMero", + "name": "Gemma 4 26B A4B MeroMero", + "display_name": "Gemma 4 26B A4B MeroMero", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 262144, + "output": 32768 + }, + "tool_call": true, + "reasoning": { + "supported": true, + "default": true + }, + "attachment": true, + "open_weights": true, + "release_date": "2026-07-29", + "last_updated": "2026-08-26", + "type": "chat" + }, + { + "id": "gemma-4-31b-it-darkidol", + "name": "DarkIdol", + "display_name": "DarkIdol", + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 262144, + "output": 32768 + }, + "tool_call": true, + "reasoning": { + "supported": true, + "default": true + }, + "attachment": true, + "open_weights": true, + "release_date": "2026-07-29", + "last_updated": "2026-07-29", + "type": "chat" + }, + { + "id": "Gemma-4-31B-Animus-V14.1", + "name": "Gemma 4 31B Animus V14.1", + "display_name": "Gemma 4 31B Animus V14.1", + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 262144, + "output": 262144 }, - "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -52012,53 +53738,49 @@ "extra_capabilities": { "reasoning": { "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" + "none", + "high" ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2026-06", - "release_date": "2026-09-22", - "last_updated": "2026-09-22", + "open_weights": true, + "release_date": "2026-05-15", + "last_updated": "2026-05-15", "type": "chat" }, { - "id": "thedrummer/skyfall-36b-v2", - "name": "Skyfall 36B V2", - "display_name": "Skyfall 36B V2", + "id": "gemma-4-12b-it-semancer", + "name": "Gemma 4 12B Semancer", + "display_name": "Gemma 4 12B Semancer", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 32768, + "context": 131072, "output": 32768 }, - "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, + "attachment": true, "open_weights": true, - "knowledge": "2024-06-30", - "release_date": "2025-03-10", - "last_updated": "2025-03-10", + "release_date": "2026-09-09", + "last_updated": "2026-09-09", "type": "chat" }, { - "id": "thedrummer/cydonia-24b-v4.1", - "name": "Cydonia 24B V4.1", - "display_name": "Cydonia 24B V4.1", + "id": "auto-model-basic", + "name": "Auto model (Basic)", + "display_name": "Auto model (Basic)", "modalities": { "input": [ "text" @@ -52068,25 +53790,23 @@ ] }, "limit": { - "context": 131072, - "output": 131072 + "context": 1000000, + "output": 1000000 }, - "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { "supported": false }, "attachment": false, - "open_weights": true, - "knowledge": "2024-04-30", - "release_date": "2025-09-27", - "last_updated": "2025-09-27", + "open_weights": false, + "release_date": "2025-04-16", + "last_updated": "2024-06-01", "type": "chat" }, { - "id": "thedrummer/unslopnemo-12b", - "name": "UnslopNemo 12B", - "display_name": "UnslopNemo 12B", + "id": "venice-uncensored", + "name": "Venice Uncensored", + "display_name": "Venice Uncensored", "modalities": { "input": [ "text" @@ -52096,52 +53816,51 @@ ] }, "limit": { - "context": 32768, - "output": 32768 + "context": 128000, + "output": 8192 }, - "temperature": true, "tool_call": false, "reasoning": { "supported": false }, "attachment": false, "open_weights": true, - "knowledge": "2024-04-30", - "release_date": "2024-11-08", - "last_updated": "2024-11-08", + "release_date": "2025-10-01", + "last_updated": "2025-02-24", "type": "chat" }, { - "id": "morph/morph-v3-fast", - "name": "V3 Fast", - "display_name": "V3 Fast", + "id": "gemma-4-26b-a4b-it-chimerax", + "name": "Chimera X", + "display_name": "Chimera X", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 81920, - "output": 38000 + "context": 262144, + "output": 32768 }, - "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, - "open_weights": false, - "release_date": "2025-07-07", - "last_updated": "2025-07-07", + "attachment": true, + "open_weights": true, + "release_date": "2026-07-29", + "last_updated": "2026-07-29", "type": "chat" }, { - "id": "morph/morph-v3-large", - "name": "V3 Large", - "display_name": "V3 Large", + "id": "Meta-Llama-3-1-8B-Instruct-FP8", + "name": "Llama 3.1 8B (decentralized)", + "display_name": "Llama 3.1 8B (decentralized)", "modalities": { "input": [ "text" @@ -52151,39 +53870,36 @@ ] }, "limit": { - "context": 262144, - "output": 131072 + "context": 128000, + "output": 16384 }, - "temperature": true, "tool_call": false, "reasoning": { "supported": false }, "attachment": false, - "open_weights": false, - "release_date": "2025-07-07", - "last_updated": "2025-07-07", + "open_weights": true, + "release_date": "2024-01-01", + "last_updated": "2024-07-23", "type": "chat" }, { - "id": "z-ai/glm-4.5v", - "name": "GLM 4.5V", - "display_name": "GLM 4.5V", + "id": "Qwen3.5-27B-Queen-Derestricted", + "name": "Qwen3.5 27B Queen Derestricted", + "display_name": "Qwen3.5 27B Queen Derestricted", "modalities": { "input": [ "text", - "image", - "video" + "image" ], "output": [ "text" ] }, "limit": { - "context": 65536, + "context": 262144, "output": 16384 }, - "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -52197,36 +53913,63 @@ "visibility": "summary", "continuation": [ "thinking_blocks" + ], + "effort_options": [ + "none", + "high" ] } }, "attachment": true, "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-08-11", - "last_updated": "2025-08-11", + "release_date": "2026-04-30", + "last_updated": "2026-04-30", "type": "chat" }, { - "id": "z-ai/glm-5.3-flash", - "name": "GLM 5.3 Flash", - "display_name": "GLM 5.3 Flash", + "id": "doubao-seed-2-0-mini-260215", + "name": "Doubao Seed 2.0 Mini", + "display_name": "Doubao Seed 2.0 Mini", "modalities": { "input": [ - "text", - "image", - "video", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 256000, + "output": 32000 }, "temperature": true, + "tool_call": false, + "reasoning": { + "supported": false + }, + "attachment": false, + "open_weights": false, + "release_date": "2026-02-14", + "last_updated": "2026-02-14", + "type": "chat" + }, + { + "id": "Qwen3.5-27B-BlueStar-v3-Derestricted", + "name": "Qwen3.5 27B BlueStar v3 Derestricted", + "display_name": "Qwen3.5 27B BlueStar v3 Derestricted", + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 262144, + "output": 16384 + }, "tool_call": true, "reasoning": { "supported": true, @@ -52242,56 +53985,51 @@ "thinking_blocks" ], "effort_options": [ - "low", - "high", - "max" + "none", + "high" ] } }, "attachment": true, "open_weights": true, - "release_date": "2026-08-26", - "last_updated": "2026-08-26", + "release_date": "2026-04-30", + "last_updated": "2026-04-30", "type": "chat" }, { - "id": "z-ai/glm-5v-turbo", - "name": "GLM 5V Turbo", - "display_name": "GLM 5V Turbo", + "id": "mercury-coder-small", + "name": "Mercury Coder Small", + "display_name": "Mercury Coder Small", "modalities": { "input": [ - "text", - "image", - "video", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 202752, - "output": 131072 + "context": 32768, + "output": 16384 }, - "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true + "supported": false }, - "attachment": true, + "attachment": false, "open_weights": false, - "release_date": "2026-04-01", - "last_updated": "2026-04-01", + "release_date": "2024-01-01", + "last_updated": "2024-01-01", "type": "chat" }, { - "id": "z-ai/glm-5-turbo", - "name": "GLM 5 Turbo", - "display_name": "GLM 5 Turbo", + "id": "Gemma-4-31B-Claude-4.6-Opus-Reasoning-Distilled", + "name": "Gemma 4 31B Claude 4.6 Opus Reasoning Distilled", + "display_name": "Gemma 4 31B Claude 4.6 Opus Reasoning Distilled", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" @@ -52299,9 +54037,8 @@ }, "limit": { "context": 262144, - "output": 131072 + "output": 16384 }, - "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -52309,35 +54046,39 @@ }, "extra_capabilities": { "reasoning": { - "supported": true + "effort_options": [ + "none", + "high" + ] } }, - "attachment": false, - "open_weights": false, - "release_date": "2026-03-16", - "last_updated": "2026-03-16", + "attachment": true, + "open_weights": true, + "release_date": "2026-05-01", + "last_updated": "2026-05-01", "type": "chat" }, { - "id": "z-ai/glm-4.6v", - "name": "GLM 4.6V", - "display_name": "GLM 4.6V", + "id": "gemini-2.5-flash-nothinking", + "name": "Gemini 2.5 Flash (No Thinking)", + "display_name": "Gemini 2.5 Flash (No Thinking)", "modalities": { "input": [ "text", "image", - "video" + "video", + "audio", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 32768 + "context": 1048576, + "output": 65536 }, - "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { "supported": true, "default": true @@ -52345,65 +54086,90 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, + "default_enabled": true, + "mode": "budget", + "budget": { + "default": -1, + "min": 0, + "max": 24576, + "auto": -1, + "off": 0, + "unit": "tokens" + }, "summaries": true, "visibility": "summary", "continuation": [ - "thinking_blocks" + "thought_signatures" ] } }, "attachment": true, - "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-12-08", - "last_updated": "2025-12-08", + "open_weights": false, + "release_date": "2025-06-05", + "last_updated": "2025-06-05", "type": "chat" }, { - "id": "z-ai/glm-4.7-flash", - "name": "GLM 4.7 Flash", - "display_name": "GLM 4.7 Flash", + "id": "deepseek-chat", + "name": "DeepSeek V3/Deepseek Chat", + "display_name": "DeepSeek V3/Deepseek Chat", "modalities": { "input": [ - "text" + "text", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 202752, - "output": 16384 + "context": 128000, + "output": 8192 }, "temperature": true, "tool_call": true, + "reasoning": { + "supported": false + }, + "attachment": true, + "open_weights": true, + "knowledge": "2025-09", + "release_date": "2025-12-01", + "last_updated": "2026-02-28", + "type": "chat" + }, + { + "id": "gemma-4-12b-it-station-keeper", + "name": "Gemma 4 12B StationKeeper", + "display_name": "Gemma 4 12B StationKeeper", + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "output": 32768 + }, + "tool_call": true, "reasoning": { "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } - }, - "attachment": false, + "attachment": true, "open_weights": true, - "knowledge": "2025-04", - "release_date": "2026-01-19", - "last_updated": "2026-01-19", + "release_date": "2026-09-09", + "last_updated": "2026-09-09", "type": "chat" }, { - "id": "ibm-granite/granite-4.0-h-micro", - "name": "Granite 4.0 Micro", - "display_name": "Granite 4.0 Micro", + "id": "doubao-seed-2-0-pro-260215", + "name": "Doubao Seed 2.0 Pro", + "display_name": "Doubao Seed 2.0 Pro", "modalities": { "input": [ "text" @@ -52413,62 +54179,74 @@ ] }, "limit": { - "context": 131000, - "output": 131000 + "context": 256000, + "output": 128000 }, - "temperature": true, - "tool_call": true, + "temperature": false, + "tool_call": false, "reasoning": { "supported": false }, "attachment": false, - "open_weights": true, - "release_date": "2025-10-02", - "last_updated": "2025-10-02", + "open_weights": false, + "release_date": "2026-02-14", + "last_updated": "2026-02-14", "type": "chat" }, { - "id": "upstage/solar-pro-3", - "name": "Solar Pro 3", - "display_name": "Solar Pro 3", + "id": "gemini-2.5-pro-preview-06-05", + "name": "Gemini 2.5 Pro Preview 0605", + "display_name": "Gemini 2.5 Pro Preview 0605", "modalities": { "input": [ - "text" + "text", + "image", + "audio" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 8192 + "context": 1048576, + "output": 65536 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { "supported": true, "default": true }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "low", - "medium", - "high" + "supported": true, + "default_enabled": true, + "mode": "budget", + "budget": { + "default": -1, + "min": 128, + "max": 32768, + "auto": -1, + "unit": "tokens" + }, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thought_signatures" ] } }, - "attachment": false, + "attachment": true, "open_weights": false, - "knowledge": "2025-07", - "release_date": "2026-01-26", - "last_updated": "2026-03-23", + "knowledge": "2025-01-31", + "release_date": "2025-06-05", + "last_updated": "2025-06-05", "type": "chat" }, { - "id": "upstage/solar-pro4", - "name": "Solar Pro 4", - "display_name": "Solar Pro 4", + "id": "mercury-2", + "name": "Mercury 2", + "display_name": "Mercury 2", "modalities": { "input": [ "text" @@ -52478,8 +54256,8 @@ ] }, "limit": { - "context": 524288, - "output": 131072 + "context": 128000, + "output": 50000 }, "temperature": true, "tool_call": true, @@ -52487,106 +54265,72 @@ "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "none", - "low", - "medium", - "high" - ] - } - }, "attachment": false, "open_weights": false, - "knowledge": "2026-02", - "release_date": "2026-08-06", - "last_updated": "2026-08-06", + "release_date": "2026-02-24", + "last_updated": "2026-02-24", "type": "chat" }, { - "id": "deepseek/deepseek-chat-v3-0324", - "name": "DeepSeek V3", - "display_name": "DeepSeek V3", + "id": "gemma-4-31b-it-gembrain", + "name": "Gembrain", + "display_name": "Gembrain", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 124000 + "context": 262144, + "output": 32768 }, - "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, + "attachment": true, "open_weights": true, - "knowledge": "2024-07-31", - "release_date": "2025-03-24", - "last_updated": "2025-03-24", + "release_date": "2026-07-29", + "last_updated": "2026-07-29", "type": "chat" }, { - "id": "deepseek/deepseek-v4-flash", - "name": "DeepSeek V4 Flash", - "display_name": "DeepSeek V4 Flash", + "id": "gemma-4-31b-it-gemsicle", + "name": "Gemsicle", + "display_name": "Gemsicle", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 384000 + "context": 262144, + "output": 32768 }, - "temperature": true, "tool_call": true, "reasoning": { "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "high", - "effort_options": [ - "low", - "high", - "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." - ] - } - }, - "attachment": false, + "attachment": true, "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-04-24", - "last_updated": "2026-04-24", + "release_date": "2026-07-29", + "last_updated": "2026-07-29", "type": "chat" }, { - "id": "deepseek/deepseek-v4-pro", - "name": "DeepSeek V4 Pro", - "display_name": "DeepSeek V4 Pro", + "id": "doubao-seed-2-0-lite-260215", + "name": "Doubao Seed 2.0 Lite", + "display_name": "Doubao Seed 2.0 Lite", "modalities": { "input": [ "text" @@ -52596,48 +54340,55 @@ ] }, "limit": { - "context": 1000000, - "output": 384000 + "context": 256000, + "output": 32000 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true + "supported": true }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "high", - "effort_options": [ - "low", - "high", - "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." - ] + "supported": true } }, "attachment": false, - "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-04-24", - "last_updated": "2026-04-24", + "open_weights": false, + "release_date": "2026-02-14", + "last_updated": "2026-02-14", "type": "chat" }, { - "id": "deepseek/deepseek-v4-flash-vision-exp", - "name": "DeepSeek V4 Flash Vision Exp", - "display_name": "DeepSeek V4 Flash Vision Exp", + "id": "fastgpt", + "name": "Web Answer", + "display_name": "Web Answer", + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 32768, + "output": 32768 + }, + "tool_call": false, + "reasoning": { + "supported": false + }, + "attachment": false, + "open_weights": false, + "release_date": "2025-12-23", + "last_updated": "2024-01-01", + "type": "chat" + }, + { + "id": "gemma-4-31b-it-scarlet", + "name": "Gemma 4 31B Scarlet", + "display_name": "Gemma 4 31B Scarlet", "modalities": { "input": [ "text", @@ -52648,47 +54399,24 @@ ] }, "limit": { - "context": 1048576, - "output": 384000 + "context": 262144, + "output": 32768 }, - "temperature": true, "tool_call": true, "reasoning": { "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "high", - "effort_options": [ - "low", - "high", - "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." - ] - } - }, "attachment": true, "open_weights": true, - "release_date": "2026-08-21", - "last_updated": "2026-09-01", + "release_date": "2026-10-08", + "last_updated": "2026-10-08", "type": "chat" }, { - "id": "deepseek/deepseek-v4.1-flash", - "name": "DeepSeek V4.1 Flash", - "display_name": "DeepSeek V4.1 Flash", + "id": "gemma-4-26b-a4b-it-chimeraxheretic", + "name": "Gemma 4 26B A4B Chimera X Heretic", + "display_name": "Gemma 4 26B A4B Chimera X Heretic", "modalities": { "input": [ "text", @@ -52699,38 +54427,30 @@ ] }, "limit": { - "context": 1048576, - "output": 384000 + "context": 262144, + "output": 32768 }, - "temperature": true, "tool_call": true, "reasoning": { "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "effort_options": [ - "low", - "high" - ] - } - }, "attachment": true, "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-09-10", - "last_updated": "2026-09-10", + "release_date": "2026-10-08", + "last_updated": "2026-10-08", "type": "chat" }, { - "id": "deepseek/deepseek-v4-pro-0813", - "name": "DeepSeek V4 Pro 0813", - "display_name": "DeepSeek V4 Pro 0813", + "id": "gemini-2.5-flash-preview-09-2025", + "name": "Gemini 2.5 Flash Preview (09/2025)", + "display_name": "Gemini 2.5 Flash Preview (09/2025)", "modalities": { "input": [ - "text" + "text", + "image", + "audio", + "pdf" ], "output": [ "text" @@ -52738,9 +54458,8 @@ }, "limit": { "context": 1048576, - "output": 384000 + "output": 65536 }, - "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -52749,144 +54468,174 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, + "default_enabled": true, + "mode": "budget", + "budget": { + "default": -1, + "min": 0, + "max": 24576, + "auto": -1, + "off": 0, + "unit": "tokens" + }, "summaries": true, "visibility": "summary", "continuation": [ - "thinking_blocks" - ], - "effort_options": [ - "none", - "low", - "high" + "thought_signatures" ] } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-08-13", - "last_updated": "2026-08-22", + "attachment": true, + "open_weights": false, + "release_date": "2025-09-25", + "last_updated": "2025-09-25", "type": "chat" }, { - "id": "deepseek/deepseek-chat", - "name": "DeepSeek V4 Flash", - "display_name": "DeepSeek V4 Flash", + "id": "gemini-2.5-flash-lite-preview-09-2025", + "name": "Gemini 2.5 Flash Lite Preview (09/2025)", + "display_name": "Gemini 2.5 Flash Lite Preview (09/2025)", "modalities": { "input": [ - "text" + "text", + "image", + "audio", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 384000 + "context": 1048576, + "output": 65536 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, + "default_enabled": false, + "mode": "budget", + "budget": { + "default": -1, + "min": 512, + "max": 24576, + "auto": -1, + "unit": "tokens" + }, "summaries": true, "visibility": "summary", "continuation": [ - "thinking_blocks" - ], - "effort_options": [ - "none", - "low", - "high" + "thought_signatures" ] } }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-04-24", - "last_updated": "2026-04-24", + "attachment": true, + "open_weights": false, + "knowledge": "2025-01", + "release_date": "2025-09-25", + "last_updated": "2025-09-25", "type": "chat" }, { - "id": "google/gemma-2-27b-it", - "name": "Gemma 2 27B", - "display_name": "Gemma 2 27B", + "id": "gemma-4-31b-it-fabled", + "name": "Fabled", + "display_name": "Fabled", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 8192, - "output": 2048 + "context": 262144, + "output": 32768 }, - "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, + "attachment": true, "open_weights": true, - "knowledge": "2024-06-30", - "release_date": "2024-07-13", - "last_updated": "2024-07-13", + "release_date": "2026-07-29", + "last_updated": "2026-07-29", "type": "chat" }, { - "id": "google/gemma-4-31b-it", - "name": "Gemma 4 31B Instruct", - "display_name": "Gemma 4 31B Instruct", + "id": "longcat-2.0", + "name": "LongCat 2.0", + "display_name": "LongCat 2.0", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 256000, - "output": 16384 + "context": 1000000, + "output": 131072 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true + "supported": true }, "extra_capabilities": { "reasoning": { "supported": true } }, - "attachment": true, + "attachment": false, "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-04-02", - "last_updated": "2026-04-02", + "release_date": "2026-06-30", + "last_updated": "2026-06-30", "type": "chat" }, { - "id": "google/gemini-3.7-flash", - "name": "Gemini 3.7 Flash", - "display_name": "Gemini 3.7 Flash", + "id": "gemma-4-26b-a4b-it-luminous", + "name": "Luminous Mirror", + "display_name": "Luminous Mirror", "modalities": { "input": [ "text", - "image", - "video", - "audio", - "pdf" + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 262144, + "output": 32768 + }, + "tool_call": true, + "reasoning": { + "supported": false + }, + "attachment": true, + "open_weights": true, + "release_date": "2026-07-29", + "last_updated": "2026-07-29", + "type": "chat" + }, + { + "id": "hermes-low", + "name": "Hermes Low", + "display_name": "Hermes Low", + "modalities": { + "input": [ + "text" ], "output": [ "text" @@ -52894,9 +54643,8 @@ }, "limit": { "context": 1048576, - "output": 65536 + "output": 131072 }, - "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -52904,80 +54652,62 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "level", - "level": "high", - "level_options": [ - "minimal", + "effort_options": [ "low", - "medium", - "high" - ], - "summaries": true, - "visibility": "summary", - "continuation": [ - "thought_signatures" + "high", + "max" ] } }, - "attachment": true, + "attachment": false, "open_weights": false, - "knowledge": "2026-03", - "release_date": "2026-08-13", - "last_updated": "2026-08-13", + "release_date": "2026-05-11", + "last_updated": "2026-05-11", "type": "chat" }, { - "id": "google/gemma-3-12b-it", - "name": "Gemma 3 12B", - "display_name": "Gemma 3 12B", + "id": "universal-summarizer", + "name": "Universal Summarizer", + "display_name": "Universal Summarizer", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 131000, - "output": 96000 + "context": 32768, + "output": 32768 }, - "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { "supported": false }, - "attachment": true, - "open_weights": true, - "knowledge": "2024-08", - "release_date": "2025-03-12", - "last_updated": "2025-03-12", + "attachment": false, + "open_weights": false, + "release_date": "2025-12-23", + "last_updated": "2024-01-01", "type": "chat" }, { - "id": "google/gemini-3.1-flash-lite-preview", - "name": "Gemini 3.1 Flash Lite Preview", - "display_name": "Gemini 3.1 Flash Lite Preview", + "id": "Gemma-4-31B-AssGuard", + "name": "Gemma 4 31B AssGuard", + "display_name": "Gemma 4 31B AssGuard", "modalities": { "input": [ "text", - "image", - "video", - "audio", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 65536 + "context": 262144, + "output": 262144 }, - "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -52985,31 +54715,22 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], "effort_options": [ - "low", - "medium", + "none", "high" ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2025-01", - "release_date": "2026-03-03", - "last_updated": "2026-03-03", + "open_weights": true, + "release_date": "2026-05-15", + "last_updated": "2026-05-15", "type": "chat" }, { - "id": "google/gemma-4-26b-a4b-it", - "name": "Gemma 4 26B A4B IT", - "display_name": "Gemma 4 26B A4B IT", + "id": "Gemma-4-31B-DarkIdol", + "name": "Gemma 4 31B DarkIdol", + "display_name": "Gemma 4 31B DarkIdol", "modalities": { "input": [ "text", @@ -53021,9 +54742,8 @@ }, "limit": { "context": 262144, - "output": 128000 + "output": 16384 }, - "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -53031,36 +54751,35 @@ }, "extra_capabilities": { "reasoning": { - "supported": true + "effort_options": [ + "none", + "high" + ] } }, "attachment": true, "open_weights": true, - "release_date": "2026-04-02", - "last_updated": "2026-04-02", + "release_date": "2026-05-01", + "last_updated": "2026-05-01", "type": "chat" }, { - "id": "google/gemini-3.1-pro-preview-customtools", - "name": "Gemini 3.1 Pro Preview Custom Tools", - "display_name": "Gemini 3.1 Pro Preview Custom Tools", + "id": "Gemma-4-31B-Fabled", + "name": "Gemma 4 31B Fabled (ArliAI)", + "display_name": "Gemma 4 31B Fabled (ArliAI)", "modalities": { "input": [ "text", - "image", - "video", - "audio", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1048756, - "output": 65536 + "context": 262144, + "output": 262144 }, - "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -53068,50 +54787,35 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "level", - "level": "high", - "level_options": [ - "low", + "effort_options": [ + "none", "high" - ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2025-01", - "release_date": "2026-02-19", - "last_updated": "2026-02-19", + "open_weights": true, + "release_date": "2026-05-02", + "last_updated": "2026-05-02", "type": "chat" }, { - "id": "google/gemini-3-8-flash", - "name": "Gemini 3.8 Flash", - "display_name": "Gemini 3.8 Flash", + "id": "gemma-4-12b-it-starry-ocean", + "name": "Gemma 4 12B Starry Ocean", + "display_name": "Gemma 4 12B Starry Ocean", "modalities": { "input": [ "text", - "image", - "video", - "audio", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 131072, + "output": 131072 }, - "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -53120,121 +54824,76 @@ "extra_capabilities": { "reasoning": { "effort_options": [ - "low", - "medium", + "none", "high" ] } }, "attachment": true, - "open_weights": false, - "release_date": "2026-09-02", - "last_updated": "2026-09-02", + "open_weights": true, + "release_date": "2026-10-15", + "last_updated": "2026-10-15", "type": "chat" }, { - "id": "google/gemini-flash-latest", - "name": "Gemini Flash Latest", - "display_name": "Gemini Flash Latest", + "id": "phi-4-multimodal-instruct", + "name": "Phi 4 Multimodal", + "display_name": "Phi 4 Multimodal", "modalities": { "input": [ - "text", - "image", - "video", - "audio", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 128000, + "output": 16384 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "effort_options": [ - "low", - "medium", - "high" - ] - } + "supported": false }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-03", - "release_date": "2026-08-13", - "last_updated": "2026-08-13", + "attachment": false, + "open_weights": true, + "knowledge": "2024-06", + "release_date": "2025-02-26", + "last_updated": "2025-02-26", "type": "chat" }, { - "id": "google/gemini-3.1-pro-preview", - "name": "Gemini 3.1 Pro Preview", - "display_name": "Gemini 3.1 Pro Preview", + "id": "gemini-2.0-pro-reasoner", + "name": "Gemini 2.0 Pro Reasoner", + "display_name": "Gemini 2.0 Pro Reasoner", "modalities": { "input": [ "text", - "image", - "video", - "audio", - "pdf" + "audio" ], "output": [ "text" ] }, "limit": { - "context": 1000000, + "context": 128000, "output": 65536 }, - "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "level", - "level": "high", - "level_options": [ - "low", - "high" - ], - "summaries": true, - "visibility": "summary", - "continuation": [ - "thought_signatures" - ] - } + "supported": false }, "attachment": true, "open_weights": false, - "knowledge": "2025-01", - "release_date": "2026-02-19", - "last_updated": "2026-02-19", + "release_date": "2024-01-01", + "last_updated": "2025-02-05", "type": "chat" }, { - "id": "google/gemma-3-4b-it", - "name": "Gemma 3 4B", - "display_name": "Gemma 3 4B", + "id": "gemma-4-12b-it", + "name": "Gemma 4 12B Instruct", + "display_name": "Gemma 4 12B Instruct", "modalities": { "input": [ "text", @@ -53245,7 +54904,7 @@ ] }, "limit": { - "context": 131000, + "context": 131072, "output": 32768 }, "temperature": true, @@ -53255,22 +54914,17 @@ }, "attachment": true, "open_weights": true, - "knowledge": "2024-08", - "release_date": "2025-03-12", - "last_updated": "2025-03-12", + "release_date": "2026-06-09", + "last_updated": "2026-06-09", "type": "chat" }, { - "id": "google/gemini-3.8-flash", - "name": "Gemini 3.8 Flash", - "display_name": "Gemini 3.8 Flash", + "id": "claw-low", + "name": "Claw Low", + "display_name": "Claw Low", "modalities": { "input": [ - "text", - "image", - "video", - "audio", - "pdf" + "text" ], "output": [ "text" @@ -53278,9 +54932,8 @@ }, "limit": { "context": 1048576, - "output": 65536 + "output": 131072 }, - "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -53288,50 +54941,36 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "level", - "level": "high", - "level_options": [ - "minimal", + "effort_options": [ "low", - "medium", - "high" - ], - "summaries": true, - "visibility": "summary", - "continuation": [ - "thought_signatures" + "high", + "max" ] } }, - "attachment": true, + "attachment": false, "open_weights": false, - "release_date": "2026-09-02", - "last_updated": "2026-09-02", + "release_date": "2026-05-11", + "last_updated": "2026-05-11", "type": "chat" }, { - "id": "google/gemini-3.6-flash", - "name": "Gemini 3.6 Flash", - "display_name": "Gemini 3.6 Flash", + "id": "Gemma-4-31B-Gembrain-X-Core", + "name": "Gemma 4 31B Gembrain X Core", + "display_name": "Gemma 4 31B Gembrain X Core", "modalities": { "input": [ "text", - "image", - "video", - "audio", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 262144, + "output": 262144 }, - "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -53339,134 +54978,117 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "level", - "level": "high", - "level_options": [ - "minimal", - "low", - "medium", + "effort_options": [ + "none", "high" - ], - "summaries": true, - "visibility": "summary", - "continuation": [ - "thought_signatures" ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2026-03", - "release_date": "2026-07-21", - "last_updated": "2026-07-21", + "open_weights": true, + "release_date": "2026-05-15", + "last_updated": "2026-05-15", "type": "chat" }, { - "id": "google/gemma-3-27b-it", - "name": "Gemma 3 27B", - "display_name": "Gemma 3 27B", + "id": "synth-2.5-flash", + "name": "Synth 2.5 Flash Preview", + "display_name": "Synth 2.5 Flash Preview", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 117964 + "context": 30000, + "output": 30000 }, - "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { "supported": false }, - "attachment": true, - "open_weights": true, - "knowledge": "2024-08", - "release_date": "2025-03-12", - "last_updated": "2025-03-12", + "attachment": false, + "open_weights": false, + "release_date": "2026-09-05", + "last_updated": "2026-09-05", "type": "chat" }, { - "id": "nousresearch/hermes-3-llama-3.1-405b", - "name": "Hermes 3 405B Instruct", - "display_name": "Hermes 3 405B Instruct", + "id": "gemma-4-31b-it-novelist", + "name": "Novelist", + "display_name": "Novelist", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 16384 + "context": 262144, + "output": 32768 }, - "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, + "attachment": true, "open_weights": true, - "knowledge": "2023-12-31", - "release_date": "2024-08-16", - "last_updated": "2024-08-16", + "release_date": "2026-07-29", + "last_updated": "2026-07-29", "type": "chat" }, { - "id": "nousresearch/hermes-3-llama-3.1-70b", - "name": "Hermes 3 70B Instruct", - "display_name": "Hermes 3 70B Instruct", + "id": "gemma-4-26b-a4b-it-opusdistill", + "name": "Opus Distill", + "display_name": "Opus Distill", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 16384 + "context": 262144, + "output": 32768 }, - "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, + "attachment": true, "open_weights": true, - "knowledge": "2023-12-31", - "release_date": "2024-08-18", - "last_updated": "2024-08-18", + "release_date": "2026-07-29", + "last_updated": "2026-07-29", "type": "chat" }, { - "id": "thinkingmachines/inkling", - "name": "Inkling", - "display_name": "Inkling", + "id": "Gemma-4-31B-SDFT-Heretic-RP", + "name": "Gemma 4 31B SDFT Heretic RP", + "display_name": "Gemma 4 31B SDFT Heretic RP", "modalities": { "input": [ "text", - "image", - "audio" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 1048576 + "context": 262144, + "output": 262144 }, - "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -53474,45 +55096,34 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], "effort_options": [ - "low", - "medium", + "none", "high" ] } }, "attachment": true, "open_weights": true, - "release_date": "2026-07-15", - "last_updated": "2026-07-15", + "release_date": "2026-05-15", + "last_updated": "2026-05-15", "type": "chat" }, { - "id": "thinkingmachines/inkling-small", - "name": "Inkling Small", - "display_name": "Inkling Small", + "id": "hermes-medium", + "name": "Hermes Medium", + "display_name": "Hermes Medium", "modalities": { "input": [ - "text", - "image", - "audio" + "text" ], "output": [ "text" ] }, "limit": { - "context": 524288, - "output": 262144 + "context": 1048576, + "output": 131072 }, - "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -53520,86 +55131,50 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], "effort_options": [ "low", - "medium", - "high" + "high", + "max" ] } }, - "attachment": true, - "open_weights": true, - "release_date": "2026-07-30", - "last_updated": "2026-07-30", + "attachment": false, + "open_weights": false, + "release_date": "2026-05-11", + "last_updated": "2026-05-11", "type": "chat" }, { - "id": "openai/gpt-5.1-codex-max", - "name": "GPT-5.1-Codex-Max", - "display_name": "GPT-5.1-Codex-Max", + "id": "ernie-5.1:thinking", + "name": "ERNIE 5.1 Thinking", + "display_name": "ERNIE 5.1 Thinking", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 119000, + "output": 64000 }, - "temperature": false, - "tool_call": true, + "tool_call": false, "reasoning": { "supported": true, - "default": false - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "none", - "effort_options": [ - "none", - "low", - "medium", - "high" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } + "default": true }, - "attachment": true, + "attachment": false, "open_weights": false, - "knowledge": "2024-09-30", - "release_date": "2025-11-13", - "last_updated": "2025-11-13", + "release_date": "2026-05-10", + "last_updated": "2026-05-10", "type": "chat" }, { - "id": "openai/gpt-5", - "name": "GPT-5", - "display_name": "GPT-5", + "id": "Gemma-4-31B-StyleTune", + "name": "Gemma 4 31B StyleTune", + "display_name": "Gemma 4 31B StyleTune", "modalities": { "input": [ "text", @@ -53610,10 +55185,9 @@ ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 262144, + "output": 262144 }, - "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -53621,86 +55195,63 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", "effort_options": [ - "minimal", - "low", - "medium", - "high" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", + "none", "high" - ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2024-09-30", - "release_date": "2025-08-07", - "last_updated": "2025-08-07", + "open_weights": true, + "release_date": "2026-05-15", + "last_updated": "2026-05-15", "type": "chat" }, { - "id": "openai/gpt-4o-mini", - "name": "GPT-4o Mini", - "display_name": "GPT-4o Mini", + "id": "gemma-4-26b-a4b-it-darksoul", + "name": "Dark Soul", + "display_name": "Dark Soul", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 16384 + "context": 262144, + "output": 32768 }, - "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, "attachment": true, - "open_weights": false, - "knowledge": "2023-09", - "release_date": "2024-07-18", - "last_updated": "2024-07-18", + "open_weights": true, + "release_date": "2026-07-29", + "last_updated": "2026-07-29", "type": "chat" }, { - "id": "openai/gpt-6-astra", - "name": "GPT-6 Astra", - "display_name": "GPT-6 Astra", + "id": "Gemma-4-31B-Dark-Gemistry", + "name": "Gemma 4 31B Dark Gemistry", + "display_name": "Gemma 4 31B Dark Gemistry", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 262144, + "output": 262144 }, - "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -53708,41 +55259,37 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" + "none", + "high" ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2026-04-30", - "release_date": "2026-09-04", - "last_updated": "2026-09-04", + "open_weights": true, + "release_date": "2026-05-15", + "last_updated": "2026-05-15", "type": "chat" }, { - "id": "openai/gpt-oss-20b", - "name": "GPT OSS 20B", - "display_name": "GPT OSS 20B", + "id": "gemini-2.5-flash-preview-04-17", + "name": "Gemini 2.5 Flash Preview", + "display_name": "Gemini 2.5 Flash Preview", "modalities": { "input": [ - "text" + "text", + "image", + "audio" ], "output": [ "text" ] }, "limit": { - "context": 131000, - "output": 117964 + "context": 1048576, + "output": 65536 }, - "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { "supported": true, "default": true @@ -53750,177 +55297,102 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, + "default_enabled": true, + "mode": "budget", + "budget": { + "default": -1, + "min": 0, + "max": 24576, + "auto": -1, + "off": 0, + "unit": "tokens" + }, "summaries": true, "visibility": "summary", "continuation": [ - "thinking_blocks" - ], - "effort_options": [ - "low", - "medium", - "high" + "thought_signatures" ] } }, - "attachment": false, - "open_weights": true, - "knowledge": "2024-06", - "release_date": "2025-08-05", - "last_updated": "2025-08-05", - "type": "chat" - }, - { - "id": "openai/gpt-4o", - "name": "GPT-4o", - "display_name": "GPT-4o", - "modalities": { - "input": [ - "text", - "image", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 16384 - }, - "temperature": true, - "tool_call": true, - "reasoning": { - "supported": false - }, "attachment": true, "open_weights": false, - "knowledge": "2023-09", - "release_date": "2024-05-13", - "last_updated": "2024-08-06", + "release_date": "2025-04-17", + "last_updated": "2025-04-17", "type": "chat" }, { - "id": "openai/gpt-5.4-image-2", - "name": "GPT-5.4 Image 2", - "display_name": "GPT-5.4 Image 2", + "id": "Gemma-4-31B-MeroMero-v2", + "name": "Gemma 4 31B MeroMero v2", + "display_name": "Gemma 4 31B MeroMero v2", "modalities": { "input": [ - "image", "text", - "pdf" + "image" ], "output": [ - "image", "text" ] }, "limit": { - "context": 272000, - "output": 128000 + "context": 262144, + "output": 32768 }, - "temperature": false, - "tool_call": false, + "tool_call": true, "reasoning": { "supported": true, - "default": false - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "none", - "effort_options": [ - "none", - "low", - "medium", - "high", - "xhigh" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" - } + "default": true }, "attachment": true, - "open_weights": false, - "release_date": "2026-04-21", - "last_updated": "2026-04-21" + "open_weights": true, + "release_date": "2026-07-29", + "last_updated": "2026-08-23", + "type": "chat" }, { - "id": "openai/gpt-5-pro", - "name": "GPT-5 Pro", - "display_name": "GPT-5 Pro", + "id": "longcat-2.0:thinking", + "name": "LongCat 2.0 Thinking", + "display_name": "LongCat 2.0 Thinking", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 400000, - "output": 272000 + "context": 1000000, + "output": 131072 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "fixed", - "effort": "high", - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } - }, - "attachment": true, - "open_weights": false, - "knowledge": "2024-09-30", - "release_date": "2025-10-06", - "last_updated": "2025-10-06", + "attachment": false, + "open_weights": true, + "release_date": "2026-06-30", + "last_updated": "2026-06-30", "type": "chat" }, { - "id": "openai/gpt-6-sol", - "name": "GPT-6 Sol", - "display_name": "GPT-6 Sol", + "id": "Gemma-4-31B-GarnetV2", + "name": "Gemma 4 31B Garnet V2", + "display_name": "Gemma 4 31B Garnet V2", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 262144, + "output": 16384 }, - "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -53930,39 +55402,35 @@ "reasoning": { "effort_options": [ "none", - "low", - "medium", "high" ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2026-04-20", - "release_date": "2026-09-22", - "last_updated": "2026-09-22", + "open_weights": true, + "release_date": "2026-05-01", + "last_updated": "2026-05-01", "type": "chat" }, { - "id": "openai/gpt-5.6-sol", - "name": "GPT-5.6 Sol", - "display_name": "GPT-5.6 Sol", + "id": "gemini-2.5-pro-preview-03-25", + "name": "Gemini 2.5 Pro Preview 0325", + "display_name": "Gemini 2.5 Pro Preview 0325", "modalities": { "input": [ "text", "image", - "pdf" + "audio" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 1048576, + "output": 65536 }, - "temperature": false, - "tool_call": true, + "tool_call": false, "reasoning": { "supported": true, "default": true @@ -53971,36 +55439,31 @@ "reasoning": { "supported": true, "default_enabled": true, - "mode": "effort", - "effort": "medium", - "effort_options": [ - "none", - "low", - "medium", - "high", - "xhigh", - "max" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + "mode": "budget", + "budget": { + "default": -1, + "min": 128, + "max": 32768, + "auto": -1, + "unit": "tokens" + }, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thought_signatures" + ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-02-16", - "release_date": "2026-07-09", - "last_updated": "2026-07-09", + "release_date": "2025-03-25", + "last_updated": "2025-03-25", "type": "chat" }, { - "id": "openai/gpt-4o-2024-11-20", - "name": "GPT-4o", - "display_name": "GPT-4o", + "id": "gemma-4-31b-it-garnet", + "name": "Garnet", + "display_name": "Garnet", "modalities": { "input": [ "text", @@ -54011,40 +55474,40 @@ ] }, "limit": { - "context": 128000, - "output": 16384 + "context": 262144, + "output": 32768 }, - "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, "attachment": true, - "open_weights": false, - "knowledge": "2023-09", - "release_date": "2024-11-20", - "last_updated": "2024-11-20", + "open_weights": true, + "release_date": "2026-07-29", + "last_updated": "2026-07-29", "type": "chat" }, { - "id": "openai/gpt-5.6-terra", - "name": "GPT-5.6 Terra", - "display_name": "GPT-5.6 Terra", + "id": "gemini-2.5-pro", + "name": "Gemini 2.5 Pro", + "display_name": "Gemini 2.5 Pro", "modalities": { "input": [ "text", "image", - "pdf" + "video", + "audio" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 1048576, + "output": 65536 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -54054,36 +55517,32 @@ "reasoning": { "supported": true, "default_enabled": true, - "mode": "effort", - "effort": "medium", - "effort_options": [ - "none", - "low", - "medium", - "high", - "xhigh", - "max" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + "mode": "budget", + "budget": { + "default": -1, + "min": 128, + "max": 32768, + "auto": -1, + "unit": "tokens" + }, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thought_signatures" + ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-02-16", - "release_date": "2026-07-09", - "last_updated": "2026-07-09", + "knowledge": "2025-01", + "release_date": "2025-06-17", + "last_updated": "2025-06-17", "type": "chat" }, { - "id": "openai/gpt-oss-120b", - "name": "GPT OSS 120B", - "display_name": "GPT OSS 120B", + "id": "pokee-isaac", + "name": "Pokee-Isaac 28B", + "display_name": "Pokee-Isaac 28B", "modalities": { "input": [ "text" @@ -54093,83 +55552,55 @@ ] }, "limit": { - "context": 131000, - "output": 117964 + "context": 10000000, + "output": 60000 }, - "temperature": true, "tool_call": true, "reasoning": { "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "effort_options": [ - "low", - "medium", - "high" - ] - } - }, "attachment": false, - "open_weights": true, - "knowledge": "2024-06", - "release_date": "2025-08-05", - "last_updated": "2025-08-05", + "open_weights": false, + "release_date": "2026-08-04", + "last_updated": "2026-08-04", "type": "chat" }, { - "id": "openai/gpt-6-luna", - "name": "GPT-6 Luna", - "display_name": "GPT-6 Luna", + "id": "ernie-5.1", + "name": "ERNIE 5.1", + "display_name": "ERNIE 5.1", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 119000, + "output": 64000 }, - "temperature": false, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true + "supported": true }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "none", - "low", - "medium", - "high" - ] + "supported": true } }, - "attachment": true, + "attachment": false, "open_weights": false, - "knowledge": "2026-05-18", - "release_date": "2026-09-22", - "last_updated": "2026-09-22", + "release_date": "2026-05-10", + "last_updated": "2026-05-10", "type": "chat" }, { - "id": "openai/gpt-5-mini", - "name": "GPT-5 Mini", - "display_name": "GPT-5 Mini", + "id": "gemini-2.5-flash-preview-05-20", + "name": "Gemini 2.5 Flash 0520", + "display_name": "Gemini 2.5 Flash 0520", "modalities": { "input": [ "text", @@ -54180,11 +55611,11 @@ ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 1048000, + "output": 65536 }, - "temperature": false, - "tool_call": true, + "temperature": true, + "tool_call": false, "reasoning": { "supported": true, "default": true @@ -54193,43 +55624,36 @@ "reasoning": { "supported": true, "default_enabled": true, - "mode": "effort", - "effort": "medium", - "effort_options": [ - "minimal", - "low", - "medium", - "high" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "interleaved": true, + "mode": "budget", + "budget": { + "default": -1, + "min": 0, + "max": 24576, + "auto": -1, + "off": 0, + "unit": "tokens" + }, "summaries": true, "visibility": "summary", "continuation": [ - "thinking_blocks" + "thought_signatures" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2024-05-30", - "release_date": "2025-08-07", - "last_updated": "2025-08-07", + "knowledge": "2025-01", + "release_date": "2025-05-20", + "last_updated": "2025-05-20", "type": "chat" }, { - "id": "openai/gpt-4o-2024-08-06", - "name": "GPT-4o", - "display_name": "GPT-4o", + "id": "deepseek-v3-0324", + "name": "DeepSeek Chat 0324", + "display_name": "DeepSeek Chat 0324", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" @@ -54237,24 +55661,23 @@ }, "limit": { "context": 128000, - "output": 16384 + "output": 8192 }, "temperature": true, "tool_call": true, "reasoning": { "supported": false }, - "attachment": true, - "open_weights": false, - "knowledge": "2023-09", - "release_date": "2024-08-06", - "last_updated": "2024-08-06", + "attachment": false, + "open_weights": true, + "release_date": "2025-03-24", + "last_updated": "2025-03-24", "type": "chat" }, { - "id": "openai/gpt-5-1", - "name": "GPT-5.1", - "display_name": "GPT-5.1", + "id": "holo3-35b-a3b:thinking", + "name": "Holo3-35B-A3B Thinking", + "display_name": "Holo3-35B-A3B Thinking", "modalities": { "input": [ "text", @@ -54265,47 +55688,24 @@ ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 65536, + "output": 8192 }, - "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "none", - "effort_options": [ - "none", - "low", - "medium", - "high" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" - } + "default": true }, "attachment": true, - "open_weights": false, - "knowledge": "2024-09-30", - "release_date": "2025-11-13", - "last_updated": "2025-11-13", + "open_weights": true, + "release_date": "2024-01-01", + "last_updated": "2024-01-01", "type": "chat" }, { - "id": "openai/gpt-5.4-pro", - "name": "GPT-5.4 Pro", - "display_name": "GPT-5.4 Pro", + "id": "Qwen3.5-27B-Claude-4.6-Opus-Reasoning-Distilled-Derestricted-Lite", + "name": "Qwen3.5 27B Claude 4.6 Opus Reasoning Distilled Derestricted Lite", + "display_name": "Qwen3.5 27B Claude 4.6 Opus Reasoning Distilled Derestricted Lite", "modalities": { "input": [ "text", @@ -54316,10 +55716,9 @@ ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 262144, + "output": 262144 }, - "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -54328,161 +55727,97 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "high", - "effort_options": [ - "medium", - "high", - "xhigh" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ "thinking_blocks" + ], + "effort_options": [ + "none", + "high" ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-03-05", - "last_updated": "2026-03-05", + "open_weights": true, + "release_date": "2026-05-15", + "last_updated": "2026-05-15", "type": "chat" }, { - "id": "openai/gpt-5.6-luna", - "name": "GPT-5.6 Luna", - "display_name": "GPT-5.6 Luna", + "id": "mistral-code-latest", + "name": "Mistral Code Latest", + "display_name": "Mistral Code Latest", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 256000, + "output": 32768 }, - "temperature": false, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", - "effort_options": [ - "none", - "low", - "medium", - "high", - "xhigh", - "max" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" - } + "supported": false }, - "attachment": true, + "attachment": false, "open_weights": false, - "knowledge": "2026-02-16", - "release_date": "2026-07-09", - "last_updated": "2026-07-09", + "release_date": "2026-06-02", + "last_updated": "2026-06-02", "type": "chat" }, { - "id": "openai/gpt-5.4-mini", - "name": "GPT-5.4 Mini", - "display_name": "GPT-5.4 Mini", + "id": "command-a-reasoning-08-2025", + "name": "Cohere Command A (08/2025)", + "display_name": "Cohere Command A (08/2025)", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 256000, + "output": 8192 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": false - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "none", - "effort_options": [ - "none", - "low", - "medium", - "high", - "xhigh" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" - } + "supported": false }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-03-17", - "last_updated": "2026-03-17", + "attachment": false, + "open_weights": true, + "knowledge": "2024-06-01", + "release_date": "2025-08-21", + "last_updated": "2025-08-21", "type": "chat" }, { - "id": "openai/gpt-5.3-codex", - "name": "GPT-5.3 Codex", - "display_name": "GPT-5.3 Codex", + "id": "gemini-2.5-flash-preview-04-17:thinking", + "name": "Gemini 2.5 Flash Preview Thinking", + "display_name": "Gemini 2.5 Flash Preview Thinking", "modalities": { "input": [ "text", "image", - "pdf" + "audio" ], "output": [ "text" ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 1048576, + "output": 65536 }, - "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { "supported": true, "default": true @@ -54491,54 +55826,45 @@ "reasoning": { "supported": true, "default_enabled": true, - "mode": "effort", - "effort": "medium", - "effort_options": [ - "low", - "medium", - "high", - "xhigh" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "interleaved": true, + "mode": "budget", + "budget": { + "default": -1, + "min": 0, + "max": 24576, + "auto": -1, + "off": 0, + "unit": "tokens" + }, "summaries": true, "visibility": "summary", "continuation": [ - "thinking_blocks" + "thought_signatures" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-02-05", - "last_updated": "2026-02-05", + "release_date": "2025-04-17", + "last_updated": "2025-04-17", "type": "chat" }, { - "id": "openai/gpt-5.6-terra-pro", - "name": "GPT-5.6 Terra Pro", - "display_name": "GPT-5.6 Terra Pro", + "id": "Gemma-4-31B-Cognitive-Unshackled", + "name": "Gemma 4 31B Cognitive Unshackled", + "display_name": "Gemma 4 31B Cognitive Unshackled", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 262144, + "output": 16384 }, - "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -54546,48 +55872,37 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], "effort_options": [ "none", - "low", - "medium", "high" ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2026-02-16", - "release_date": "2026-07-09", - "last_updated": "2026-07-09", + "open_weights": true, + "release_date": "2026-05-01", + "last_updated": "2026-05-01", "type": "chat" }, { - "id": "openai/gpt-5.6-sol-pro", - "name": "GPT-5.6 Sol Pro", - "display_name": "GPT-5.6 Sol Pro", + "id": "gemini-2.5-pro-preview-05-06", + "name": "Gemini 2.5 Pro Preview 0506", + "display_name": "Gemini 2.5 Pro Preview 0506", "modalities": { "input": [ "text", "image", - "pdf" + "audio" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 1048576, + "output": 65536 }, - "temperature": false, - "tool_call": true, + "tool_call": false, "reasoning": { "supported": true, "default": true @@ -54595,36 +55910,35 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, + "default_enabled": true, + "mode": "budget", + "budget": { + "default": -1, + "min": 128, + "max": 32768, + "auto": -1, + "unit": "tokens" + }, "summaries": true, "visibility": "summary", "continuation": [ - "thinking_blocks" - ], - "effort_options": [ - "none", - "low", - "medium", - "high" + "thought_signatures" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-02-16", - "release_date": "2026-07-09", - "last_updated": "2026-07-09", + "release_date": "2025-05-06", + "last_updated": "2025-05-06", "type": "chat" }, { - "id": "openai/gpt-4.1-mini", - "name": "GPT-4.1 Mini", - "display_name": "GPT-4.1 Mini", + "id": "MiniMax-M1", + "name": "MiniMax M1", + "display_name": "MiniMax M1", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" @@ -54632,24 +55946,22 @@ }, "limit": { "context": 1000000, - "output": 32768 + "output": 131072 }, - "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { "supported": false }, - "attachment": true, - "open_weights": false, - "knowledge": "2024-04", - "release_date": "2025-04-14", - "last_updated": "2025-04-14", + "attachment": false, + "open_weights": true, + "release_date": "2025-01-08", + "last_updated": "2025-06-16", "type": "chat" }, { - "id": "openai/gpt-5.4-nano", - "name": "GPT-5.4 Nano", - "display_name": "GPT-5.4 Nano", + "id": "holo3-35b-a3b", + "name": "Holo3-35B-A3B", + "display_name": "Holo3-35B-A3B", "modalities": { "input": [ "text", @@ -54660,269 +55972,220 @@ ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 65536, + "output": 8192 }, - "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "none", - "effort_options": [ - "none", - "low", - "medium", - "high", - "xhigh" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" - } + "default": true }, "attachment": true, - "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-03-17", - "last_updated": "2026-03-17", + "open_weights": true, + "release_date": "2024-01-01", + "last_updated": "2024-01-01", "type": "chat" }, { - "id": "openai/gpt-5-5", - "name": "GPT-5.5", - "display_name": "GPT-5.5", + "id": "gemini-2.5-flash-lite", + "name": "Gemini 2.5 Flash Lite", + "display_name": "Gemini 2.5 Flash Lite", "modalities": { "input": [ "text", "image", - "pdf" + "video", + "audio" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 1048576, + "output": 65536 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", - "effort_options": [ - "low", - "medium", - "high", - "xhigh" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + "default_enabled": false, + "mode": "budget", + "budget": { + "default": -1, + "min": 512, + "max": 24576, + "auto": -1, + "unit": "tokens" + }, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thought_signatures" + ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-12-01", - "release_date": "2026-04-23", - "last_updated": "2026-04-23", + "knowledge": "2025-01", + "release_date": "2025-06-17", + "last_updated": "2025-06-17", "type": "chat" }, { - "id": "openai/gpt-5-4", - "name": "GPT-5.4", - "display_name": "GPT-5.4", + "id": "gemini-2.5-flash", + "name": "Gemini 2.5 Flash", + "display_name": "Gemini 2.5 Flash", "modalities": { "input": [ "text", "image", - "pdf" + "video", + "audio" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 1048576, + "output": 65536 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "none", - "effort_options": [ - "none", - "low", - "medium", - "high", - "xhigh" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + "default_enabled": true, + "mode": "budget", + "budget": { + "default": -1, + "min": 0, + "max": 24576, + "auto": -1, + "off": 0, + "unit": "tokens" + }, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thought_signatures" + ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-03-05", - "last_updated": "2026-03-05", + "knowledge": "2025-01", + "release_date": "2025-06-17", + "last_updated": "2025-06-17", "type": "chat" }, { - "id": "openai/o3-pro", - "name": "o3 Pro", - "display_name": "o3 Pro", + "id": "asi1-mini", + "name": "ASI1 Mini", + "display_name": "ASI1 Mini", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 100000 + "context": 128000, + "output": 16384 }, - "temperature": false, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", - "effort_options": [ - "low", - "medium", - "high" - ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } + "supported": false }, - "attachment": true, + "attachment": false, "open_weights": false, - "knowledge": "2024-05", - "release_date": "2025-06-10", - "last_updated": "2025-06-10", + "release_date": "2025-03-25", + "last_updated": "2025-03-25", "type": "chat" }, { - "id": "openai/gpt-5-2", - "name": "GPT-5.2", - "display_name": "GPT-5.2", + "id": "kimi-k2-instruct-fast", + "name": "Kimi K2 0711 Fast", + "display_name": "Kimi K2 0711 Fast", "modalities": { "input": [ "text", - "image" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 131072, + "output": 16384 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": false - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "none", - "effort_options": [ - "none", - "low", - "medium", - "high", - "xhigh" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" - } + "supported": false }, "attachment": true, + "open_weights": true, + "release_date": "2025-07-11", + "last_updated": "2025-07-11", + "type": "chat" + }, + { + "id": "auto-model-standard", + "name": "Auto model (Standard)", + "display_name": "Auto model (Standard)", + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1000000, + "output": 1000000 + }, + "tool_call": true, + "reasoning": { + "supported": false + }, + "attachment": false, "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2025-12-11", - "last_updated": "2025-12-11", + "release_date": "2025-04-16", + "last_updated": "2024-06-01", "type": "chat" }, { - "id": "openai/gpt-5.5-pro", - "name": "GPT-5.5 Pro", - "display_name": "GPT-5.5 Pro", + "id": "agnes-3.0-flash", + "name": "Agnes 3.0 Flash", + "display_name": "Agnes 3.0 Flash", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 524288, + "output": 65536 }, - "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -54930,30 +56193,19 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "effort_options": [ - "medium", - "high" - ] + "supported": true } }, "attachment": true, "open_weights": false, - "knowledge": "2025-12-01", - "release_date": "2026-04-23", - "last_updated": "2026-04-23", + "release_date": "2026-09-09", + "last_updated": "2026-09-09", "type": "chat" }, { - "id": "openai/gpt-5.2-pro", - "name": "GPT-5.2 Pro", - "display_name": "GPT-5.2 Pro", + "id": "Gemma-4-26B-A4B-MeroMero:thinking", + "name": "Gemma 4 26B A4B MeroMero Thinking", + "display_name": "Gemma 4 26B A4B MeroMero Thinking", "modalities": { "input": [ "text", @@ -54964,92 +56216,55 @@ ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 262144, + "output": 32768 }, - "temperature": false, "tool_call": true, "reasoning": { "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "high", - "effort_options": [ - "medium", - "high", - "xhigh" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } - }, "attachment": true, - "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2025-12-11", - "last_updated": "2025-12-11", + "open_weights": true, + "release_date": "2026-07-29", + "last_updated": "2026-08-26", "type": "chat" }, { - "id": "openai/gpt-oss-safeguard-20b", - "name": "gpt-oss-safeguard-20b", - "display_name": "gpt-oss-safeguard-20b", + "id": "gemma-4-26b-a4b-it-moonlight", + "name": "Moonlight Dusk", + "display_name": "Moonlight Dusk", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 65536 + "context": 262144, + "output": 32768 }, - "temperature": true, "tool_call": true, "reasoning": { "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "low", - "medium", - "high" - ] - } - }, - "attachment": false, + "attachment": true, "open_weights": true, - "release_date": "2025-10-29", - "last_updated": "2025-10-29", + "release_date": "2026-07-29", + "last_updated": "2026-07-29", "type": "chat" }, { - "id": "openai/gpt-4.1", - "name": "GPT-4.1", - "display_name": "GPT-4.1", + "id": "deepseek-chat-cheaper", + "name": "DeepSeek V3/Chat Cheaper", + "display_name": "DeepSeek V3/Chat Cheaper", "modalities": { "input": [ "text", - "image", "pdf" ], "output": [ @@ -55057,40 +56272,35 @@ ] }, "limit": { - "context": 1047576, - "output": 32768 + "context": 128000, + "output": 8192 }, - "temperature": true, "tool_call": true, "reasoning": { "supported": false }, "attachment": true, - "open_weights": false, - "knowledge": "2024-04", - "release_date": "2025-04-14", - "last_updated": "2025-04-14", + "open_weights": true, + "release_date": "2025-04-15", + "last_updated": "2025-04-15", "type": "chat" }, { - "id": "openai/gpt-6.1-terra", - "name": "GPT-6.1 Terra", - "display_name": "GPT-6.1 Terra", + "id": "GLM-4.6-Derestricted-v5", + "name": "GLM 4.6 Derestricted v5", + "display_name": "GLM 4.6 Derestricted v5", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 131072, + "output": 8192 }, - "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -55099,23 +56309,21 @@ "extra_capabilities": { "reasoning": { "effort_options": [ - "low", - "medium", + "none", "high" ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-06-01", - "release_date": "2026-10-26", - "last_updated": "2026-10-26", + "attachment": false, + "open_weights": true, + "release_date": "2025-12-23", + "last_updated": "2025-12-23", "type": "chat" }, { - "id": "openai/gpt-4o-2024-05-13", - "name": "GPT-4o (2024-05-13)", - "display_name": "GPT-4o (2024-05-13)", + "id": "Gemma-4-31B-Queen", + "name": "Gemma 4 31B Queen", + "display_name": "Gemma 4 31B Queen", "modalities": { "input": [ "text", @@ -55126,40 +56334,45 @@ ] }, "limit": { - "context": 128000, + "context": 262144, "output": 16384 }, - "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "none", + "high" + ] + } }, "attachment": true, - "open_weights": false, - "knowledge": "2023-09", - "release_date": "2024-05-13", - "last_updated": "2024-05-13", + "open_weights": true, + "release_date": "2026-05-01", + "last_updated": "2026-05-01", "type": "chat" }, { - "id": "openai/gpt-5.6-luna-pro", - "name": "GPT-5.6 Luna Pro", - "display_name": "GPT-5.6 Luna Pro", + "id": "Qwen3.5-27B-Claude-4.6-Opus-Reasoning-Distilled-Derestricted", + "name": "Qwen3.5 27B Claude 4.6 Opus Reasoning Distilled Derestricted", + "display_name": "Qwen3.5 27B Claude 4.6 Opus Reasoning Distilled Derestricted", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 262144, + "output": 262144 }, - "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -55176,27 +56389,25 @@ ], "effort_options": [ "none", - "low", - "medium", "high" ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2026-02-16", - "release_date": "2026-07-09", - "last_updated": "2026-07-09", + "open_weights": true, + "release_date": "2026-05-15", + "last_updated": "2026-05-15", "type": "chat" }, { - "id": "openai/gpt-6.1-sol", - "name": "GPT-6.1 Sol", - "display_name": "GPT-6.1 Sol", + "id": "gemini-2.5-flash-lite-preview-09-2025-thinking", + "name": "Gemini 2.5 Flash Lite Preview (09/2025) – Thinking", + "display_name": "Gemini 2.5 Flash Lite Preview (09/2025) – Thinking", "modalities": { "input": [ "text", "image", + "audio", "pdf" ], "output": [ @@ -55204,35 +56415,45 @@ ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 1048576, + "output": 65536 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "low", - "medium", - "high" + "supported": true, + "default_enabled": false, + "mode": "budget", + "budget": { + "default": -1, + "min": 512, + "max": 24576, + "auto": -1, + "unit": "tokens" + }, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thought_signatures" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-04-30", - "release_date": "2026-09-29", - "last_updated": "2026-09-29", + "knowledge": "2025-01", + "release_date": "2025-09-25", + "last_updated": "2025-09-25", "type": "chat" }, { - "id": "openai/gpt-5-nano", - "name": "GPT-5 Nano", - "display_name": "GPT-5 Nano", + "id": "Gemma-4-31B-Gembrain-uncensored-heretic", + "name": "Gemma 4 31B Gembrain Uncensored Heretic", + "display_name": "Gemma 4 31B Gembrain Uncensored Heretic", "modalities": { "input": [ "text", @@ -55243,10 +56464,9 @@ ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 262144, + "output": 262144 }, - "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -55254,84 +56474,51 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", "effort_options": [ - "minimal", - "low", - "medium", - "high" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", + "none", "high" - ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2024-05-30", - "release_date": "2025-08-07", - "last_updated": "2025-08-07", + "open_weights": true, + "release_date": "2026-05-15", + "last_updated": "2026-05-15", "type": "chat" }, { - "id": "tencent/hy4-preview", - "name": "Hy4 preview", - "display_name": "Hy4 preview", + "id": "qwen3-vl-235b-a22b-instruct-original", + "name": "Qwen3 VL 235B A22B Instruct Original", + "display_name": "Qwen3 VL 235B A22B Instruct Original", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 64000 + "context": 32768, + "output": 32768 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "effort_options": [ - "low", - "high" - ] - } + "supported": false }, - "attachment": false, + "attachment": true, "open_weights": true, - "release_date": "2026-08-28", - "last_updated": "2026-08-28", + "knowledge": "2025-03-31", + "release_date": "2025-09-23", + "last_updated": "2025-09-23", "type": "chat" }, { - "id": "tencent/hy-mt2-30b-a3b", - "name": "Hy-MT2-30B-A3B", - "display_name": "Hy-MT2-30B-A3B", + "id": "nano-gpt-help", + "name": "NanoGPT Help", + "display_name": "NanoGPT Help", "modalities": { "input": [ "text" @@ -55341,27 +56528,27 @@ ] }, "limit": { - "context": 8192, - "output": 4096 + "context": 6000, + "output": 512 }, - "temperature": true, "tool_call": false, "reasoning": { "supported": false }, "attachment": false, - "open_weights": true, - "release_date": "2026-05-21", - "last_updated": "2026-05-21", + "open_weights": false, + "release_date": "2026-06-06", + "last_updated": "2026-06-06", "type": "chat" }, { - "id": "tencent/hy3", - "name": "Hy3", - "display_name": "Hy3", + "id": "Gemma-4-31B-Agares-v1", + "name": "Gemma 4 31B Agares v1", + "display_name": "Gemma 4 31B Agares v1", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" @@ -55369,9 +56556,8 @@ }, "limit": { "context": 262144, - "output": 131072 + "output": 262144 }, - "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -55379,29 +56565,22 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], "effort_options": [ - "low", + "none", "high" ] } }, - "attachment": false, + "attachment": true, "open_weights": true, - "release_date": "2026-07-06", - "last_updated": "2026-07-06", + "release_date": "2026-05-15", + "last_updated": "2026-05-15", "type": "chat" }, { - "id": "tencent/hy-mt2-7b", - "name": "Hy-MT2-7B", - "display_name": "Hy-MT2-7B", + "id": "phi-4-mini-instruct", + "name": "Phi 4 Mini", + "display_name": "Phi 4 Mini", "modalities": { "input": [ "text" @@ -55411,8 +56590,8 @@ ] }, "limit": { - "context": 8192, - "output": 4096 + "context": 128000, + "output": 16384 }, "temperature": true, "tool_call": false, @@ -55421,14 +56600,15 @@ }, "attachment": false, "open_weights": true, - "release_date": "2026-05-21", - "last_updated": "2026-05-21", + "knowledge": "2024-06", + "release_date": "2025-02-26", + "last_updated": "2025-02-26", "type": "chat" }, { - "id": "tencent/hy-mt2-1.8b", - "name": "Hy-MT2-1.8B", - "display_name": "Hy-MT2-1.8B", + "id": "synth-2.5-pro", + "name": "Synth 2.5 Pro Preview", + "display_name": "Synth 2.5 Pro Preview", "modalities": { "input": [ "text" @@ -55438,52 +56618,23 @@ ] }, "limit": { - "context": 8192, - "output": 4096 + "context": 30000, + "output": 30000 }, - "temperature": true, "tool_call": false, "reasoning": { "supported": false }, "attachment": false, - "open_weights": true, - "release_date": "2026-05-21", - "last_updated": "2026-05-21", - "type": "chat" - }, - { - "id": "amazon/nova-micro-v1", - "name": "Nova Micro 1.0", - "display_name": "Nova Micro 1.0", - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 5120 - }, - "temperature": true, - "tool_call": true, - "reasoning": { - "supported": false - }, - "attachment": false, "open_weights": false, - "knowledge": "2024-10-31", - "release_date": "2024-12-05", - "last_updated": "2024-12-05", + "release_date": "2026-09-05", + "last_updated": "2026-09-05", "type": "chat" }, { - "id": "amazon/nova-pro-v1", - "name": "Nova Pro 1.0", - "display_name": "Nova Pro 1.0", + "id": "gemma-4-26b-a4b-it-shadowsiren", + "name": "Shadow Siren", + "display_name": "Shadow Siren", "modalities": { "input": [ "text", @@ -55494,83 +56645,100 @@ ] }, "limit": { - "context": 300000, - "output": 5120 + "context": 262144, + "output": 32768 }, - "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, "attachment": true, - "open_weights": false, - "knowledge": "2024-10-31", - "release_date": "2024-12-05", - "last_updated": "2024-12-05", + "open_weights": true, + "release_date": "2026-07-29", + "last_updated": "2026-07-29", "type": "chat" }, { - "id": "amazon/nova-lite-v1", - "name": "Nova Lite 1.0", - "display_name": "Nova Lite 1.0", + "id": "gemini-2.5-flash-preview-09-2025-thinking", + "name": "Gemini 2.5 Flash Preview (09/2025) – Thinking", + "display_name": "Gemini 2.5 Flash Preview (09/2025) – Thinking", "modalities": { "input": [ "text", - "image" + "image", + "audio", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 300000, - "output": 5120 + "context": 1048576, + "output": 65536 }, - "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "budget", + "budget": { + "default": -1, + "min": 0, + "max": 24576, + "auto": -1, + "off": 0, + "unit": "tokens" + }, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thought_signatures" + ] + } }, "attachment": true, "open_weights": false, - "knowledge": "2024-10-31", - "release_date": "2024-12-05", - "last_updated": "2024-12-05", + "release_date": "2025-09-25", + "last_updated": "2025-09-25", "type": "chat" }, { - "id": "rekaai/reka-edge", - "name": "Reka Edge", - "display_name": "Reka Edge", + "id": "mistral-code-agent-latest", + "name": "Mistral Code Agent Latest", + "display_name": "Mistral Code Agent Latest", "modalities": { "input": [ - "image", - "text", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 16384, - "output": 16384 + "context": 262144, + "output": 32768 }, - "temperature": true, "tool_call": true, "reasoning": { "supported": false }, - "attachment": true, + "attachment": false, "open_weights": true, - "release_date": "2026-03-11", - "last_updated": "2026-03-11", + "release_date": "2026-06-02", + "last_updated": "2026-06-02", "type": "chat" }, { - "id": "mancer/weaver", - "name": "Weaver (alpha)", - "display_name": "Weaver (alpha)", + "id": "claw-high", + "name": "Claw High", + "display_name": "Claw High", "modalities": { "input": [ "text" @@ -55580,40 +56748,46 @@ ] }, "limit": { - "context": 8000, - "output": 2000 + "context": 1048576, + "output": 131072 }, - "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "low", + "high", + "max" + ] + } }, "attachment": false, "open_weights": false, - "knowledge": "2023-06-30", - "release_date": "2023-08-02", - "last_updated": "2023-08-02", + "release_date": "2026-05-11", + "last_updated": "2026-05-11", "type": "chat" }, { - "id": "moonshot/kimi-k3", - "name": "Kimi K3", - "display_name": "Kimi K3", + "id": "Qwen3.5-27B-Blossom-V6.4-Derestricted", + "name": "Qwen3.5 27B Blossom V6.4 Derestricted", + "display_name": "Qwen3.5 27B Blossom V6.4 Derestricted", "modalities": { "input": [ "text", - "image", - "video" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 262144, + "output": 262144 }, - "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -55629,229 +56803,135 @@ "thinking_blocks" ], "effort_options": [ - "low", - "high", - "max" + "none", + "high" ] } }, "attachment": true, "open_weights": true, - "release_date": "2026-07-16", - "last_updated": "2026-07-16", + "release_date": "2026-05-15", + "last_updated": "2026-05-15", "type": "chat" }, { - "id": "aion-labs/aion-rp-llama-3.1-8b", - "name": "Aion-RP 1.0 (8B)", - "display_name": "Aion-RP 1.0 (8B)", + "id": "Gemma-4-31B-K1-v5", + "name": "Gemma 4 31B K1 v5", + "display_name": "Gemma 4 31B K1 v5", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 32768, - "output": 32768 + "context": 262144, + "output": 262144 }, - "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, - "open_weights": true, - "knowledge": "2023-12-31", - "release_date": "2025-02-04", - "last_updated": "2025-02-04", - "type": "chat" - }, - { - "id": "moonshotai/kimi-k2-0905", - "name": "Kimi K2 0905", - "display_name": "Kimi K2 0905", - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 262144, - "output": 98304 - }, - "temperature": true, - "tool_call": true, - "reasoning": { - "supported": false + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "none", + "high" + ] + } }, - "attachment": false, + "attachment": true, "open_weights": true, - "release_date": "2025-09-05", - "last_updated": "2025-09-05", + "release_date": "2026-05-02", + "last_updated": "2026-05-02", "type": "chat" }, { - "id": "moonshotai/kimi-k2", - "name": "Kimi K2 0711", - "display_name": "Kimi K2 0711", + "id": "gemma-4-31b-it-isometry", + "name": "Isometry", + "display_name": "Isometry", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 98304 + "context": 262144, + "output": 32768 }, - "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, + "attachment": true, "open_weights": true, - "knowledge": "2024-12-31", - "release_date": "2025-07-11", - "last_updated": "2025-07-11", + "release_date": "2026-07-29", + "last_updated": "2026-07-29", "type": "chat" }, { - "id": "moonshotai/kimi-k2-thinking", - "name": "Kimi K2 Thinking", - "display_name": "Kimi K2 Thinking", + "id": "gemini-2.5-flash-lite-preview-06-17", + "name": "Gemini 2.5 Flash Lite Preview", + "display_name": "Gemini 2.5 Flash Lite Preview", "modalities": { "input": [ - "text" + "text", + "image", + "audio" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 98304 + "context": 1048576, + "output": 65536 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, + "default_enabled": false, + "mode": "budget", + "budget": { + "default": -1, + "min": 512, + "max": 24576, + "auto": -1, + "unit": "tokens" + }, "summaries": true, "visibility": "summary", "continuation": [ - "thinking_blocks" + "thought_signatures" ] } }, - "attachment": false, - "open_weights": true, - "knowledge": "2024-08", - "release_date": "2025-11-06", - "last_updated": "2025-11-06", - "type": "chat" - }, - { - "id": "sao10k/l3-lunaris-8b", - "name": "Llama 3 8B Lunaris", - "display_name": "Llama 3 8B Lunaris", - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 8192, - "output": 7372 - }, - "temperature": true, - "tool_call": false, - "reasoning": { - "supported": false - }, - "attachment": false, - "open_weights": true, - "knowledge": "2023-12-31", - "release_date": "2024-08-13", - "last_updated": "2024-08-13", - "type": "chat" - }, - { - "id": "sao10k/l3.1-euryale-70b", - "name": "Llama 3.1 Euryale 70B v2.2", - "display_name": "Llama 3.1 Euryale 70B v2.2", - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 16384 - }, - "temperature": true, - "tool_call": true, - "reasoning": { - "supported": false - }, - "attachment": false, - "open_weights": true, - "knowledge": "2023-12-31", - "release_date": "2024-08-28", - "last_updated": "2024-08-28", - "type": "chat" - }, - { - "id": "sao10k/l3.3-euryale-70b", - "name": "Llama 3.3 Euryale 70B", - "display_name": "Llama 3.3 Euryale 70B", - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 16384 - }, - "temperature": true, - "tool_call": false, - "reasoning": { - "supported": false - }, - "attachment": false, - "open_weights": true, - "knowledge": "2023-12-31", - "release_date": "2024-12-18", - "last_updated": "2024-12-18", + "attachment": true, + "open_weights": false, + "knowledge": "2025-01", + "release_date": "2025-06-17", + "last_updated": "2025-06-17", "type": "chat" }, { - "id": "zhipu/glm-5.3", - "name": "GLM 5.3", - "display_name": "GLM 5.3", + "id": "claw-medium", + "name": "Claw Medium", + "display_name": "Claw Medium", "modalities": { "input": [ "text" @@ -55861,10 +56941,9 @@ ] }, "limit": { - "context": 1024000, + "context": 1048576, "output": 131072 }, - "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -55872,23 +56951,23 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, "effort_options": [ "low", - "high" + "high", + "max" ] } }, "attachment": false, - "open_weights": true, - "release_date": "2026-08-14", - "last_updated": "2026-08-14", + "open_weights": false, + "release_date": "2026-05-11", + "last_updated": "2026-05-11", "type": "chat" }, { - "id": "zhipu/glm-5.2", - "name": "GLM 5.2", - "display_name": "GLM 5.2", + "id": "sarvam-105b", + "name": "Sarvam 105B", + "display_name": "Sarvam 105B", "modalities": { "input": [ "text" @@ -55898,8 +56977,8 @@ ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 131072, + "output": 4096 }, "temperature": true, "tool_call": true, @@ -55909,29 +56988,26 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, "effort_options": [ + "low", + "medium", "high" ] } }, "attachment": false, "open_weights": true, - "release_date": "2026-06-13", - "last_updated": "2026-06-13", + "release_date": "2026-02-18", + "last_updated": "2026-02-18", "type": "chat" }, { - "id": "meta/muse-spark-1.3", - "name": "Muse Spark 1.3", - "display_name": "Muse Spark 1.3", + "id": "hermes-high", + "name": "Hermes High", + "display_name": "Hermes High", "modalities": { "input": [ - "text", - "image", - "video", - "pdf", - "audio" + "text" ], "output": [ "text" @@ -55941,7 +57017,6 @@ "context": 1048576, "output": 131072 }, - "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -55949,77 +57024,60 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, "effort_options": [ "low", - "medium", - "high" + "high", + "max" ] } }, - "attachment": true, + "attachment": false, "open_weights": false, - "release_date": "2026-09-02", - "last_updated": "2026-09-02", + "release_date": "2026-05-11", + "last_updated": "2026-05-11", "type": "chat" }, { - "id": "meta/muse-spark-1.1", - "name": "Muse Spark 1.1", - "display_name": "Muse Spark 1.1", + "id": "ernie-5.0-thinking-preview", + "name": "Ernie 5.0 Thinking Preview", + "display_name": "Ernie 5.0 Thinking Preview", "modalities": { "input": [ "text", - "image", - "pdf", - "video" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 128000, + "output": 16384 }, - "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { "supported": true, "default": true }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "effort_options": [ - "low", - "medium", - "high" - ] + "supported": true } }, "attachment": true, "open_weights": false, - "release_date": "2026-04-08", - "last_updated": "2026-07-09", + "release_date": "2025-11-18", + "last_updated": "2025-11-18", "type": "chat" }, { - "id": "meta/muse-spark-1.2", - "name": "Muse Spark 1.2", - "display_name": "Muse Spark 1.2", + "id": "gemini-exp-1206", + "name": "Gemini 2.0 Pro 1206", + "display_name": "Gemini 2.0 Pro 1206", "modalities": { "input": [ "text", "image", - "video", - "pdf", "audio" ], "output": [ @@ -56027,41 +57085,23 @@ ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 2097152, + "output": 8192 }, - "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "effort_options": [ - "low", - "medium", - "high" - ] - } + "supported": false }, "attachment": true, "open_weights": false, - "release_date": "2026-08-05", - "last_updated": "2026-08-05", + "release_date": "2024-12-06", + "last_updated": "2024-12-06", "type": "chat" }, { - "id": "meta/muse-glimmer-30b", - "name": "Muse Glimmer 30B", - "display_name": "Muse Glimmer 30B", + "id": "gemma-4-26b-a4b-it-musica", + "name": "Musica", + "display_name": "Musica", "modalities": { "input": [ "text", @@ -56072,110 +57112,99 @@ ] }, "limit": { - "context": 131072, - "output": 131072 + "context": 262144, + "output": 32768 }, - "temperature": true, "tool_call": true, "reasoning": { "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "effort_options": [ - "low", - "medium", - "high" - ] - } - }, "attachment": true, "open_weights": true, - "knowledge": "2026-01-04", - "release_date": "2026-08-10", - "last_updated": "2026-08-10", + "release_date": "2026-07-29", + "last_updated": "2026-07-29", "type": "chat" }, { - "id": "stepfun/step-5-preview", - "name": "Step 5 Preview", - "display_name": "Step 5 Preview", + "id": "gemini-2.5-pro-exp-03-25", + "name": "Gemini 2.5 Pro Experimental 0325", + "display_name": "Gemini 2.5 Pro Experimental 0325", "modalities": { "input": [ "text", "image", - "video" + "audio" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 64000 + "context": 1048576, + "output": 65536 }, - "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { "supported": true, "default": true }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "low", - "medium", - "high" + "supported": true, + "default_enabled": true, + "mode": "budget", + "budget": { + "default": -1, + "min": 128, + "max": 32768, + "auto": -1, + "unit": "tokens" + }, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thought_signatures" ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-09-16", - "last_updated": "2026-09-20", + "open_weights": false, + "release_date": "2025-03-25", + "last_updated": "2025-03-25", "type": "chat" }, { - "id": "perplexity/sonar-pro", - "name": "Sonar Pro", - "display_name": "Sonar Pro", + "id": "doubao-seed-2-0-code-preview-260215", + "name": "Doubao Seed 2.0 Code Preview", + "display_name": "Doubao Seed 2.0 Code Preview", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 100000 + "context": 256000, + "output": 128000 }, "temperature": true, "tool_call": false, "reasoning": { "supported": false }, - "attachment": true, + "attachment": false, "open_weights": false, - "knowledge": "2025-09-01", - "release_date": "2024-01-01", - "last_updated": "2025-09-01", + "release_date": "2026-02-14", + "last_updated": "2026-02-14", "type": "chat" }, { - "id": "perplexity/sonar", - "name": "Sonar", - "display_name": "Sonar", + "id": "auto-model-premium", + "name": "Auto model (Premium)", + "display_name": "Auto model (Premium)", "modalities": { "input": [ "text" @@ -56185,111 +57214,134 @@ ] }, "limit": { - "context": 128000, - "output": 100000 + "context": 1000000, + "output": 1000000 }, - "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { "supported": false }, "attachment": false, "open_weights": false, - "knowledge": "2025-09-01", - "release_date": "2024-01-01", - "last_updated": "2025-09-01", + "release_date": "2025-04-16", + "last_updated": "2024-06-01", "type": "chat" }, { - "id": "relace/relace-apply-3", - "name": "Apply 3", - "display_name": "Apply 3", + "id": "Gemma-4-31B-Novelist", + "name": "Gemma 4 31B Novelist (ArliAI)", + "display_name": "Gemma 4 31B Novelist (ArliAI)", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 256000, - "output": 128000 + "context": 262144, + "output": 262144 }, - "temperature": false, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, - "open_weights": false, - "release_date": "2025-09-26", - "last_updated": "2025-09-26", + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "none", + "high" + ] + } + }, + "attachment": true, + "open_weights": true, + "release_date": "2026-05-15", + "last_updated": "2026-05-15", "type": "chat" }, { - "id": "relace/relace-search", - "name": "Search", - "display_name": "Search", + "id": "gemini-3-pro-image-preview", + "name": "Gemini 3 Pro Image", + "display_name": "Gemini 3 Pro Image", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 256000, - "output": 128000 + "context": 65536, + "output": 32768 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "level", + "level": "high", + "level_options": [ + "low", + "high" + ], + "summaries": true, + "visibility": "summary", + "continuation": [ + "thought_signatures" + ] + } + }, + "attachment": true, "open_weights": false, - "release_date": "2025-12-08", - "last_updated": "2025-12-08", + "knowledge": "2025-01", + "release_date": "2025-11-20", + "last_updated": "2025-11-20", "type": "chat" }, { - "id": "mistralai/mistral-large-2407", - "name": "Large 2407", - "display_name": "Large 2407", + "id": "celeris-1", + "name": "Celeris 1", + "display_name": "Celeris 1", "modalities": { "input": [ - "text", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 104857 + "context": 8192, + "output": 8192 }, - "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { "supported": false }, - "attachment": true, - "open_weights": true, - "knowledge": "2024-03-31", - "release_date": "2024-07-24", - "last_updated": "2024-07-24", + "attachment": false, + "open_weights": false, + "release_date": "2026-07-25", + "last_updated": "2026-07-25", "type": "chat" }, { - "id": "mistralai/mistral-small-3.2-24b-instruct", - "name": "Small 3.2 24B", - "display_name": "Small 3.2 24B", + "id": "glm-4-long", + "name": "GLM-4 Long", + "display_name": "GLM-4 Long", "modalities": { "input": [ - "image", "text" ], "output": [ @@ -56297,25 +57349,23 @@ ] }, "limit": { - "context": 128000, - "output": 16384 + "context": 1000000, + "output": 4095 }, - "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { "supported": false }, - "attachment": true, - "open_weights": true, - "knowledge": "2023-10-31", - "release_date": "2025-06-20", - "last_updated": "2025-06-20", + "attachment": false, + "open_weights": false, + "release_date": "2024-01-01", + "last_updated": "2024-08-01", "type": "chat" }, { - "id": "mistralai/ministral-14b-2512", - "name": "Ministral 3 14B 2512", - "display_name": "Ministral 3 14B 2512", + "id": "Gemma-4-31B-MeroMero", + "name": "Gemma 4 31B MeroMero", + "display_name": "Gemma 4 31B MeroMero", "modalities": { "input": [ "text", @@ -56327,53 +57377,67 @@ }, "limit": { "context": 262144, - "output": 209715 + "output": 262144 }, - "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "none", + "high" + ] + } }, "attachment": true, "open_weights": true, - "release_date": "2025-12-02", - "last_updated": "2025-12-02", + "release_date": "2026-05-02", + "last_updated": "2026-05-02", "type": "chat" }, { - "id": "mistralai/mistral-medium-3", - "name": "Medium 3", - "display_name": "Medium 3", + "id": "Gemma-4-31B-Isometry-RP", + "name": "Gemma 4 31B Isometry RP", + "display_name": "Gemma 4 31B Isometry RP", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 104857 + "context": 262144, + "output": 262144 }, - "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "none", + "high" + ] + } }, "attachment": true, - "open_weights": false, - "knowledge": "2025-03-31", - "release_date": "2025-05-07", - "last_updated": "2025-05-07", + "open_weights": true, + "release_date": "2026-05-15", + "last_updated": "2026-05-15", "type": "chat" }, { - "id": "mistralai/devstral-2512", - "name": "Devstral 2 2512", - "display_name": "Devstral 2 2512", + "id": "auto-model", + "name": "Auto model", + "display_name": "Auto model", "modalities": { "input": [ "text" @@ -56383,111 +57447,129 @@ ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 1000000, + "output": 1000000 }, - "temperature": true, "tool_call": true, "reasoning": { "supported": false }, "attachment": false, - "open_weights": true, - "knowledge": "2025-12", - "release_date": "2025-12-09", - "last_updated": "2025-12-09", + "open_weights": false, + "release_date": "2025-04-16", + "last_updated": "2024-06-01", "type": "chat" }, { - "id": "mistralai/voxtral-small-24b-2507", - "name": "Voxtral Small 24B 2507", - "display_name": "Voxtral Small 24B 2507", + "id": "gemini-2.5-flash-preview-05-20:thinking", + "name": "Gemini 2.5 Flash 0520 Thinking", + "display_name": "Gemini 2.5 Flash 0520 Thinking", "modalities": { "input": [ "text", - "audio" + "image" ], "output": [ "text" ] }, "limit": { - "context": 32000, - "output": 32768 + "context": 1048000, + "output": 65536 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "budget", + "budget": { + "default": -1, + "min": 0, + "max": 24576, + "auto": -1, + "off": 0, + "unit": "tokens" + }, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thought_signatures" + ] + } }, "attachment": true, - "open_weights": true, - "release_date": "2025-07-15", - "last_updated": "2025-07-15", + "open_weights": false, + "knowledge": "2025-01", + "release_date": "2025-05-20", + "last_updated": "2025-05-20", "type": "chat" }, { - "id": "mistralai/mistral-saba", - "name": "Saba", - "display_name": "Saba", + "id": "Sao10K/L3.1-70B-Euryale-v2.2", + "name": "Llama 3.1 70B Euryale", + "display_name": "Llama 3.1 70B Euryale", "modalities": { "input": [ - "text", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 32768, - "output": 26214 + "context": 20480, + "output": 16384 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { "supported": false }, - "attachment": true, - "open_weights": false, - "knowledge": "2024-09-30", - "release_date": "2025-02-17", - "last_updated": "2025-02-17", + "attachment": false, + "open_weights": true, + "knowledge": "2023-12-31", + "release_date": "2024-08-28", + "last_updated": "2024-08-28", "type": "chat" }, { - "id": "mistralai/mixtral-8x22b-instruct", - "name": "Mixtral 8x22B Instruct", - "display_name": "Mixtral 8x22B Instruct", + "id": "Sao10K/L3.3-70B-Euryale-v2.3", + "name": "Llama 3.3 70B Euryale", + "display_name": "Llama 3.3 70B Euryale", "modalities": { "input": [ - "text", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 65536, - "output": 52428 + "context": 20480, + "output": 16384 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { "supported": false }, - "attachment": true, + "attachment": false, "open_weights": true, - "knowledge": "2024-01-31", - "release_date": "2024-04-17", - "last_updated": "2024-04-17", + "knowledge": "2023-12-31", + "release_date": "2024-12-18", + "last_updated": "2024-12-18", "type": "chat" }, { - "id": "mistralai/mistral-small-24b-instruct-2501", - "name": "Small 3", - "display_name": "Small 3", + "id": "Sao10K/L3.1-70B-Hanami-x1", + "name": "Llama 3.1 70B Hanami", + "display_name": "Llama 3.1 70B Hanami", "modalities": { "input": [ "text" @@ -56500,50 +57582,46 @@ "context": 32768, "output": 16384 }, - "temperature": true, "tool_call": false, "reasoning": { "supported": false }, "attachment": false, "open_weights": true, - "knowledge": "2023-10-31", - "release_date": "2025-01-30", - "last_updated": "2025-01-30", + "release_date": "2024-01-01", + "last_updated": "2024-07-23", "type": "chat" }, { - "id": "mistralai/ministral-3b-2512", - "name": "Ministral 3 3B 2512", - "display_name": "Ministral 3 3B 2512", + "id": "Sao10K/L3-8B-Stheno-v3.2", + "name": "Sao10K Stheno 8b", + "display_name": "Sao10K Stheno 8b", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 104857 + "context": 16384, + "output": 8192 }, - "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { "supported": false }, - "attachment": true, + "attachment": false, "open_weights": true, - "release_date": "2025-12-02", - "last_updated": "2025-12-02", + "release_date": "2024-01-01", + "last_updated": "2024-11-29", "type": "chat" }, { - "id": "mistralai/mistral-small-2603", - "name": "Small 4", - "display_name": "Small 4", + "id": "openai/gpt-5-pro", + "name": "GPT 5 Pro", + "display_name": "GPT 5 Pro", "modalities": { "input": [ "text", @@ -56554,34 +57632,41 @@ ] }, "limit": { - "context": 262144, - "output": 256000 + "context": 400000, + "output": 128000 }, - "temperature": true, - "tool_call": true, + "temperature": false, + "tool_call": false, "reasoning": { "supported": true, "default": true }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "none", + "supported": true, + "default_enabled": true, + "mode": "fixed", + "effort": "high", + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", "high" - ] + ], + "visibility": "hidden" } }, "attachment": true, - "open_weights": true, - "knowledge": "2025-06", - "release_date": "2026-03-16", - "last_updated": "2026-03-16", + "open_weights": false, + "knowledge": "2024-09-30", + "release_date": "2025-10-06", + "last_updated": "2025-10-06", "type": "chat" }, { - "id": "mistralai/mistral-medium-3.1", - "name": "Medium 3.1", - "display_name": "Medium 3.1", + "id": "openai/gpt-5.4-nano", + "name": "GPT 5.4 Nano", + "display_name": "GPT 5.4 Nano", "modalities": { "input": [ "text", @@ -56593,113 +57678,188 @@ ] }, "limit": { - "context": 131072, - "output": 104857 + "context": 400000, + "output": 128000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": false + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": false, + "mode": "effort", + "effort": "none", + "effort_options": [ + "none", + "low", + "medium", + "high", + "xhigh" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" + } }, "attachment": true, "open_weights": false, - "knowledge": "2025-06-30", - "release_date": "2025-08-12", - "last_updated": "2025-08-12", + "knowledge": "2025-08-31", + "release_date": "2026-03-17", + "last_updated": "2026-03-17", "type": "chat" }, { - "id": "mistralai/ministral-8b-2512", - "name": "Ministral 3 8B 2512", - "display_name": "Ministral 3 8B 2512", + "id": "openai/gpt-6-astra-pro", + "name": "GPT 6 Astra Pro", + "display_name": "GPT 6 Astra Pro", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 209715 + "context": 1050000, + "output": 128000 }, - "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" + ] + } }, "attachment": true, - "open_weights": true, - "release_date": "2025-12-02", - "last_updated": "2025-12-02", + "open_weights": false, + "release_date": "2026-09-04", + "last_updated": "2026-09-04", "type": "chat" }, { - "id": "mistralai/mistral-small-3.1-24b-instruct", - "name": "Small 3.1 24B", - "display_name": "Small 3.1 24B", + "id": "openai/gpt-6.1-terra", + "name": "GPT 6.1 Terra", + "display_name": "GPT 6.1 Terra", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 128000, + "context": 1050000, "output": 128000 }, - "temperature": true, - "tool_call": false, + "temperature": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "none", + "low", + "medium", + "high", + "xhigh", + "max" + ] + } }, "attachment": true, - "open_weights": true, - "knowledge": "2023-10-31", - "release_date": "2025-03-17", - "last_updated": "2025-03-17", + "open_weights": false, + "knowledge": "2026-06-01", + "release_date": "2026-10-26", + "last_updated": "2026-10-26", "type": "chat" }, { - "id": "mistralai/mistral-nemo", - "name": "Mistral NeMo", - "display_name": "Mistral NeMo", + "id": "openai/gpt-5.4", + "name": "GPT 5.4", + "display_name": "GPT 5.4", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 16384 + "context": 1050000, + "output": 128000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": false }, - "attachment": false, - "open_weights": true, - "knowledge": "2024-07", - "release_date": "2024-07-18", - "last_updated": "2024-07-18", + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": false, + "mode": "effort", + "effort": "none", + "effort_options": [ + "none", + "low", + "medium", + "high", + "xhigh" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" + } + }, + "attachment": true, + "open_weights": false, + "knowledge": "2025-08-31", + "release_date": "2026-03-05", + "last_updated": "2026-03-05", "type": "chat" }, { - "id": "mistralai/mistral-large", - "name": "Large", - "display_name": "Large", + "id": "openai/gpt-5.4-mini", + "name": "GPT 5.4 Mini", + "display_name": "GPT 5.4 Mini", "modalities": { "input": [ "text", + "image", "pdf" ], "output": [ @@ -56707,25 +57867,48 @@ ] }, "limit": { - "context": 128000, - "output": 102400 + "context": 400000, + "output": 128000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": false + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": false, + "mode": "effort", + "effort": "none", + "effort_options": [ + "none", + "low", + "medium", + "high", + "xhigh" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" + } }, "attachment": true, "open_weights": false, - "knowledge": "2024-11-30", - "release_date": "2024-02-26", - "last_updated": "2024-02-26", + "knowledge": "2025-08-31", + "release_date": "2026-03-17", + "last_updated": "2026-03-17", "type": "chat" }, { - "id": "mistralai/mistral-medium-3-5", - "name": "Medium 3.5", - "display_name": "Medium 3.5", + "id": "openai/gpt-5.1-codex-max", + "name": "GPT 5.1 Codex Max", + "display_name": "GPT 5.1 Codex Max", "modalities": { "input": [ "text", @@ -56737,131 +57920,104 @@ ] }, "limit": { - "context": 262144, - "output": 209715 + "context": 400000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": false, + "mode": "effort", + "effort": "none", "effort_options": [ + "none", + "low", + "medium", "high" - ] + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, "attachment": true, "open_weights": false, - "release_date": "2026-04-30", - "last_updated": "2026-04-30", + "knowledge": "2024-09-30", + "release_date": "2025-11-13", + "last_updated": "2025-11-13", "type": "chat" }, { - "id": "mistralai/codestral-2508", - "name": "Codestral 2508", - "display_name": "Codestral 2508", + "id": "openai/gpt-6-sol", + "name": "GPT 6 Sol", + "display_name": "GPT 6 Sol", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 256000, - "output": 8192 + "context": 1050000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { - "supported": false - }, - "attachment": false, - "open_weights": false, - "release_date": "2025-07-30", - "last_updated": "2025-07-30", - "type": "chat" - }, - { - "id": "cognitivecomputations/dolphin-mistral-24b-venice-edition", - "name": "Venice Uncensored", - "display_name": "Venice Uncensored", - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 8192 - }, - "temperature": true, - "tool_call": false, - "reasoning": { - "supported": false - }, - "attachment": false, - "open_weights": true, - "knowledge": "2024-04-30", - "release_date": "2025-07-09", - "last_updated": "2025-07-09", - "type": "chat" - }, - { - "id": "meta-llama/Llama-3.3-70B-Instruct-Turbo", - "name": "Llama 3.3 70B Instruct Turbo", - "display_name": "Llama 3.3 70B Instruct Turbo", - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 127000 + "supported": true, + "default": true }, - "temperature": true, - "tool_call": true, - "reasoning": { - "supported": false + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "none", + "low", + "medium", + "high", + "xhigh", + "max" + ] + } }, - "attachment": false, - "open_weights": true, - "knowledge": "2023-12", - "release_date": "2024-12-06", - "last_updated": "2024-12-06", + "attachment": true, + "open_weights": false, + "knowledge": "2026-04-20", + "release_date": "2026-09-22", + "last_updated": "2026-09-22", "type": "chat" }, { - "id": "perceptron/perceptron-mk1.5", - "name": "Mk1.5", - "display_name": "Mk1.5", + "id": "openai/gpt-terra-latest", + "name": "GPT Terra Latest", + "display_name": "GPT Terra Latest", "modalities": { "input": [ "text", "image", - "video", - "audio" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 36864, - "output": 8192 + "context": 1050000, + "output": 128000 }, - "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -56870,36 +58026,40 @@ "extra_capabilities": { "reasoning": { "effort_options": [ - "minimal", + "none", "low", "medium", - "high" + "high", + "xhigh", + "max" ] } }, "attachment": true, "open_weights": false, - "release_date": "2026-09-25", - "last_updated": "2026-09-25", + "release_date": "2026-09-11", + "last_updated": "2026-09-11", "type": "chat" }, { - "id": "inception/mercury-2", - "name": "Mercury 2", - "display_name": "Mercury 2", + "id": "openai/gpt-5.6-terra", + "name": "GPT 5.6 Terra", + "display_name": "GPT 5.6 Terra", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 50000 + "context": 1050000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -56907,34 +58067,51 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", "effort_options": [ + "none", + "low", + "medium", + "high", + "xhigh", + "max" + ], + "verbosity": "medium", + "verbosity_options": [ "low", "medium", "high" - ] + ], + "visibility": "hidden" } }, - "attachment": false, + "attachment": true, "open_weights": false, - "release_date": "2026-02-24", - "last_updated": "2026-02-24", + "knowledge": "2026-02-16", + "release_date": "2026-07-09", + "last_updated": "2026-07-09", "type": "chat" }, { - "id": "inception/mercury-2.5", - "name": "Mercury 2.5", - "display_name": "Mercury 2.5", + "id": "openai/gpt-5.3-codex", + "name": "GPT 5.3 Codex", + "display_name": "GPT 5.3 Codex", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 260000, - "output": 65536 + "context": 400000, + "output": 128000 }, "temperature": true, "tool_call": true, @@ -56944,43 +58121,49 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", "effort_options": [ + "low", + "medium", + "high", + "xhigh" + ], + "verbosity": "medium", + "verbosity_options": [ "low", "medium", "high" - ] + ], + "visibility": "hidden" } }, - "attachment": false, + "attachment": true, "open_weights": false, - "release_date": "2026-09-08", - "last_updated": "2026-09-08", + "knowledge": "2025-08-31", + "release_date": "2026-02-05", + "last_updated": "2026-02-05", "type": "chat" - } - ] - }, - "longcat": { - "id": "longcat", - "name": "LongCat", - "display_name": "LongCat", - "api": "https://api.longcat.chat/openai", - "doc": "https://longcat.chat/platform/docs/", - "models": [ + }, { - "id": "LongCat-2.0", - "name": "LongCat-2.0", - "display_name": "LongCat-2.0", + "id": "openai/gpt-6-astra", + "name": "GPT 6 Astra", + "display_name": "GPT 6 Astra", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 1050000, + "output": 128000 }, "temperature": false, "tool_call": true, @@ -56991,24 +58174,26 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" ] } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-06-30", - "last_updated": "2026-06-30", + "attachment": true, + "open_weights": false, + "knowledge": "2026-04-30", + "release_date": "2026-09-04", + "last_updated": "2026-09-04", "type": "chat" }, { - "id": "LongCat-2.5-Preview", - "name": "LongCat-2.5-Preview", - "display_name": "LongCat-2.5-Preview", + "id": "openai/gpt-5.1-codex", + "name": "GPT 5.1 Codex", + "display_name": "GPT 5.1 Codex", "modalities": { "input": [ "text", @@ -57019,45 +58204,47 @@ ] }, "limit": { - "context": 1048576, - "output": 262144 + "context": 400000, + "output": 128000 }, "temperature": false, - "tool_call": true, + "tool_call": false, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] + "default_enabled": false, + "mode": "effort", + "effort": "none", + "effort_options": [ + "none", + "low", + "medium", + "high" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, "attachment": true, "open_weights": false, - "release_date": "2026-09-25", - "last_updated": "2026-09-25", + "knowledge": "2024-09-30", + "release_date": "2025-11-13", + "last_updated": "2025-11-13", "type": "chat" - } - ] - }, - "openreason": { - "id": "openreason", - "name": "OpenReason", - "display_name": "OpenReason", - "api": "https://api.openreason.app/v1", - "doc": "https://openreason.app/docs", - "models": [ + }, { - "id": "openai/gpt-oss-120b", - "name": "GPT OSS 120B", - "display_name": "GPT OSS 120B", + "id": "openai/o3-mini-low", + "name": "OpenAI o3-mini (Low)", + "display_name": "OpenAI o3-mini (Low)", "modalities": { "input": [ "text" @@ -57067,10 +58254,10 @@ ] }, "limit": { - "context": 131072, - "output": 32768 + "context": 200000, + "output": 100000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -57079,30 +58266,28 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], + "default_enabled": true, + "mode": "effort", + "effort": "medium", "effort_options": [ "low", "medium", "high" - ] + ], + "visibility": "hidden" } }, "attachment": false, - "open_weights": true, - "knowledge": "2024-06", - "release_date": "2025-08-05", - "last_updated": "2025-08-05", + "open_weights": false, + "knowledge": "2024-05", + "release_date": "2024-12-20", + "last_updated": "2025-01-29", "type": "chat" }, { - "id": "moonshotai/kimi-k2.7-code", - "name": "Kimi K2.7 Code", - "display_name": "Kimi K2.7 Code", + "id": "openai/gpt-4o-2024-08-06", + "name": "GPT-4o (2024-08-06)", + "display_name": "GPT-4o (2024-08-06)", "modalities": { "input": [ "text", @@ -57113,50 +58298,40 @@ ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 128000, + "output": 16384 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } + "supported": false }, "attachment": true, - "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-06-12", - "last_updated": "2026-06-12", + "open_weights": false, + "knowledge": "2023-09", + "release_date": "2024-08-06", + "last_updated": "2024-08-06", "type": "chat" }, { - "id": "deepseek-ai/deepseek-v4-flash-0731", - "name": "DeepSeek V4 Flash 0731", - "display_name": "DeepSeek V4 Flash 0731", + "id": "openai/gpt-5.2-codex", + "name": "GPT 5.2 Codex", + "display_name": "GPT 5.2 Codex", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 384000 + "context": 400000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -57165,96 +58340,104 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], + "default_enabled": true, + "mode": "effort", + "effort": "medium", "effort_options": [ - "none", - "minimal", "low", "medium", "high", - "xhigh", - "max" - ] + "xhigh" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-07-31", - "last_updated": "2026-07-31", + "attachment": true, + "open_weights": false, + "knowledge": "2025-08-31", + "release_date": "2025-12-11", + "last_updated": "2025-12-11", "type": "chat" - } - ] - }, - "baseten": { - "id": "baseten", - "name": "Baseten", - "display_name": "Baseten", - "api": "https://inference.baseten.co/v1", - "doc": "https://docs.baseten.co/inference/model-apis/overview", - "models": [ + }, { - "id": "zai-org/GLM-5.3-Fast", - "name": "GLM 5.3 Fast", - "display_name": "GLM 5.3 Fast", + "id": "openai/gpt-5.2", + "name": "GPT 5.2", + "display_name": "GPT 5.2", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 262144 + "context": 400000, + "output": 128000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": false, + "mode": "effort", + "effort": "none", "effort_options": [ + "none", "low", + "medium", "high", - "max" - ] + "xhigh" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, "attachment": true, - "open_weights": true, - "release_date": "2026-08-14", - "last_updated": "2026-08-14", + "open_weights": false, + "knowledge": "2025-08-31", + "release_date": "2025-12-11", + "last_updated": "2025-12-11", "type": "chat" }, { - "id": "zai-org/GLM-5.3-Flash", - "name": "GLM 5.3 Flash", - "display_name": "GLM 5.3 Flash", + "id": "openai/o1-pro", + "name": "OpenAI o1 Pro", + "display_name": "OpenAI o1 Pro", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 200000, + "output": 100000 }, - "temperature": true, - "tool_call": true, + "temperature": false, + "tool_call": false, "reasoning": { "supported": true, "default": true @@ -57262,43 +58445,43 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], + "default_enabled": true, + "mode": "effort", + "effort": "medium", "effort_options": [ "low", - "high", - "max" - ] + "medium", + "high" + ], + "visibility": "hidden" } }, "attachment": true, - "open_weights": true, - "release_date": "2026-08-26", - "last_updated": "2026-08-26", + "open_weights": false, + "knowledge": "2023-09", + "release_date": "2025-03-19", + "last_updated": "2025-03-19", "type": "chat" }, { - "id": "zai-org/GLM-5.2-Fast", - "name": "GLM 5.2 Fast", - "display_name": "GLM 5.2 Fast", + "id": "openai/gpt-6.1-sol", + "name": "GPT 6.1 Sol", + "display_name": "GPT 6.1 Sol", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 262144 + "context": 1050000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -57306,31 +58489,26 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], "effort_options": [ - "none", "low", + "medium", "high", + "xhigh", "max" ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-06-13", - "last_updated": "2026-06-13", + "open_weights": false, + "knowledge": "2026-04-30", + "release_date": "2026-09-29", + "last_updated": "2026-09-29", "type": "chat" }, { - "id": "zai-org/GLM-5.2", - "name": "GLM 5.2", - "display_name": "GLM 5.2", + "id": "openai/gpt-4o-2024-11-20", + "name": "GPT-4o (2024-11-20)", + "display_name": "GPT-4o (2024-11-20)", "modalities": { "input": [ "text", @@ -57341,56 +58519,39 @@ ] }, "limit": { - "context": 1048576, - "output": 262144 + "context": 128000, + "output": 16384 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "effort_options": [ - "none", - "low", - "high", - "max" - ] - } + "supported": false }, "attachment": true, - "open_weights": true, - "release_date": "2026-06-13", - "last_updated": "2026-06-13", + "open_weights": false, + "knowledge": "2023-09", + "release_date": "2024-11-20", + "last_updated": "2024-11-20", "type": "chat" }, { - "id": "zai-org/GLM-5.3", - "name": "GLM 5.3", - "display_name": "GLM 5.3", + "id": "openai/gpt-6.1-sol-pro", + "name": "GPT 6.1 Sol Pro", + "display_name": "GPT 6.1 Sol Pro", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 262144 + "context": 1050000, + "output": 128000 }, - "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -57398,43 +58559,39 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], "effort_options": [ "low", + "medium", "high", + "xhigh", "max" ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-08-14", - "last_updated": "2026-08-14", + "open_weights": false, + "release_date": "2026-09-29", + "last_updated": "2026-09-29", "type": "chat" }, { - "id": "nvidia/NVIDIA-Nemotron-3-Ultra-550B-A55B", - "name": "Nemotron Ultra", - "display_name": "Nemotron Ultra", + "id": "openai/gpt-6-sol-pro", + "name": "GPT 6 Sol Pro", + "display_name": "GPT 6 Sol Pro", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 202800, - "output": 202800 + "context": 1050000, + "output": 128000 }, - "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -57442,43 +58599,39 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], "effort_options": [ - "none", - "high" + "low", + "medium", + "high", + "xhigh", + "max" ] } }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-09", - "release_date": "2026-06-04", - "last_updated": "2026-06-04", + "attachment": true, + "open_weights": false, + "release_date": "2026-09-22", + "last_updated": "2026-09-22", "type": "chat" }, { - "id": "openai/gpt-oss-120b", - "name": "OpenAI GPT 120B", - "display_name": "OpenAI GPT 120B", + "id": "openai/gpt-astra-latest", + "name": "GPT Astra Latest", + "display_name": "GPT Astra Latest", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 128072, - "output": 128072 + "context": 1050000, + "output": 128000 }, - "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -57486,16 +58639,7 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], "effort_options": [ - "none", - "minimal", "low", "medium", "high", @@ -57504,63 +58648,46 @@ ] } }, - "attachment": false, - "open_weights": true, - "knowledge": "2024-06", - "release_date": "2025-08-05", - "last_updated": "2025-08-05", + "attachment": true, + "open_weights": false, + "release_date": "2026-09-11", + "last_updated": "2026-09-11", "type": "chat" }, { - "id": "moonshotai/Kimi-K3", - "name": "Kimi K3", - "display_name": "Kimi K3", + "id": "openai/gpt-4.1-nano", + "name": "GPT 4.1 Nano", + "display_name": "GPT 4.1 Nano", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 262144 + "context": 1047576, + "output": 32768 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "effort_options": [ - "none", - "low", - "high", - "max" - ] - } + "supported": false }, "attachment": true, - "open_weights": true, - "release_date": "2026-07-16", - "last_updated": "2026-07-16", + "open_weights": false, + "knowledge": "2024-04", + "release_date": "2025-04-14", + "last_updated": "2025-04-14", "type": "chat" }, { - "id": "deepseek-ai/DeepSeek-V4.1-Flash-1017-Fast", - "name": "DeepSeek V4.1 Flash 1017 Fast", - "display_name": "DeepSeek V4.1 Flash 1017 Fast", + "id": "openai/gpt-5-nano", + "name": "GPT 5 Nano", + "display_name": "GPT 5 Nano", "modalities": { "input": [ "text", @@ -57571,11 +58698,11 @@ ] }, "limit": { - "context": 1048576, - "output": 262144 + "context": 400000, + "output": 128000 }, - "temperature": true, - "tool_call": true, + "temperature": false, + "tool_call": false, "reasoning": { "supported": true, "default": true @@ -57583,46 +58710,49 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], + "default_enabled": true, + "mode": "effort", + "effort": "medium", "effort_options": [ - "none", + "minimal", "low", "medium", - "high", - "max" - ] + "high" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, "attachment": true, - "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-10-17", - "last_updated": "2026-10-17", + "open_weights": false, + "knowledge": "2024-05-30", + "release_date": "2025-08-07", + "last_updated": "2025-08-07", "type": "chat" }, { - "id": "deepseek-ai/DeepSeek-V4.1-Flash", - "name": "DeepSeek V4.1 Flash", - "display_name": "DeepSeek V4.1 Flash", + "id": "openai/gpt-chat-latest", + "name": "GPT Chat Latest", + "display_name": "GPT Chat Latest", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 32768 + "context": 1050000, + "output": 128000 }, - "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -57631,30 +58761,25 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], "effort_options": [ - "none", + "low", + "medium", "high", + "xhigh", "max" ] } }, "attachment": true, - "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-09-10", - "last_updated": "2026-09-10", + "open_weights": false, + "release_date": "2026-05-03", + "last_updated": "2026-05-03", "type": "chat" }, { - "id": "deepseek-ai/DeepSeek-V4-Flash-0731", - "name": "DeepSeek V4 Flash 0731", - "display_name": "DeepSeek V4 Flash 0731", + "id": "openai/gpt-5.1-2025-11-13", + "name": "GPT-5.1 (2025-11-13)", + "display_name": "GPT-5.1 (2025-11-13)", "modalities": { "input": [ "text" @@ -57664,56 +58789,62 @@ ] }, "limit": { - "context": 1048576, - "output": 384000 + "context": 400000, + "output": 128000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], + "default_enabled": false, + "mode": "effort", + "effort": "none", "effort_options": [ "none", "low", - "high", - "max" - ] + "medium", + "high" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, "attachment": false, - "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-07-31", - "last_updated": "2026-07-31", + "open_weights": false, + "knowledge": "2024-09-30", + "release_date": "2025-11-13", + "last_updated": "2025-11-13", "type": "chat" }, { - "id": "deepseek-ai/DeepSeek-V4-Pro-0813", - "name": "DeepSeek V4 Pro 0813", - "display_name": "DeepSeek V4 Pro 0813", + "id": "openai/gpt-5.6-luna", + "name": "GPT 5.6 Luna", + "display_name": "GPT 5.6 Luna", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 262144 + "context": 1050000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -57722,42 +58853,50 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], + "default_enabled": true, + "mode": "effort", + "effort": "medium", "effort_options": [ "none", "low", + "medium", "high", + "xhigh", "max" - ] + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-08-13", - "last_updated": "2026-08-22", + "attachment": true, + "open_weights": false, + "knowledge": "2026-02-16", + "release_date": "2026-07-09", + "last_updated": "2026-07-09", "type": "chat" }, { - "id": "deepseek-ai/DeepSeek-V4.1-Flash-Fast", - "name": "DeepSeek V4.1 Flash Fast", - "display_name": "DeepSeek V4.1 Flash Fast", + "id": "openai/gpt-5", + "name": "GPT 5", + "display_name": "GPT 5", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 32768 + "context": 400000, + "output": 128000 }, "temperature": false, "tool_call": true, @@ -57768,44 +58907,49 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], + "default_enabled": true, + "mode": "effort", + "effort": "medium", "effort_options": [ - "none", - "high", - "max" - ] + "minimal", + "low", + "medium", + "high" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, "attachment": true, - "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-09-10", - "last_updated": "2026-09-10", + "open_weights": false, + "knowledge": "2024-09-30", + "release_date": "2025-08-07", + "last_updated": "2025-08-07", "type": "chat" }, { - "id": "deepseek-ai/DeepSeek-V4.1-Flash-1017", - "name": "DeepSeek V4.1 Flash 1017", - "display_name": "DeepSeek V4.1 Flash 1017", + "id": "openai/gpt-6-luna-pro", + "name": "GPT 6 Luna Pro", + "display_name": "GPT 6 Luna Pro", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 262144 + "context": 1050000, + "output": 128000 }, - "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -57813,80 +58957,76 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], "effort_options": [ - "none", "low", "medium", "high", + "xhigh", "max" ] } }, "attachment": true, - "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-10-17", - "last_updated": "2026-10-17", + "open_weights": false, + "release_date": "2026-09-22", + "last_updated": "2026-09-22", "type": "chat" - } - ] - }, - "databricks": { - "id": "databricks", - "name": "Databricks", - "display_name": "Databricks", - "api": "https://${DATABRICKS_HOST}/ai-gateway/mlflow/v1", - "doc": "https://docs.databricks.com/aws/en/machine-learning/foundation-models/", - "models": [ + }, { - "id": "databricks-gpt-oss-20b", - "name": "GPT OSS 20B", - "display_name": "GPT OSS 20B", + "id": "openai/gpt-5-mini", + "name": "GPT 5 Mini", + "display_name": "GPT 5 Mini", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 25000 + "context": 400000, + "output": 128000 }, - "temperature": true, - "tool_call": true, + "temperature": false, + "tool_call": false, "reasoning": { "supported": true, "default": true }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", "effort_options": [ + "minimal", "low", "medium", "high" - ] + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, - "attachment": false, - "open_weights": true, - "knowledge": "2024-06", - "release_date": "2025-08-05", - "last_updated": "2025-08-05", + "attachment": true, + "open_weights": false, + "knowledge": "2024-05-30", + "release_date": "2025-08-07", + "last_updated": "2025-08-07", "type": "chat" }, { - "id": "databricks-gpt-oss-120b", - "name": "GPT OSS 120B", - "display_name": "GPT OSS 120B", + "id": "openai/gpt-oss-safeguard-20b", + "name": "GPT OSS Safeguard 20B", + "display_name": "GPT OSS Safeguard 20B", "modalities": { "input": [ "text" @@ -57897,7 +59037,7 @@ }, "limit": { "context": 131072, - "output": 25000 + "output": 65536 }, "temperature": true, "tool_call": true, @@ -57916,74 +59056,56 @@ }, "attachment": false, "open_weights": true, - "knowledge": "2024-06", - "release_date": "2025-08-05", - "last_updated": "2025-08-05", + "release_date": "2025-10-29", + "last_updated": "2025-10-29", "type": "chat" }, { - "id": "databricks-gemini-3-flash", - "name": "Gemini 3 Flash Preview", - "display_name": "Gemini 3 Flash Preview", + "id": "openai/gpt-4o-mini", + "name": "GPT-4o mini", + "display_name": "GPT-4o mini", "modalities": { "input": [ "text", - "image", - "video", - "audio", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 128000, + "output": 16384 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "minimal", - "low", - "medium", - "high" - ] - } + "supported": false }, "attachment": true, "open_weights": false, - "knowledge": "2025-01", - "release_date": "2025-12-17", - "last_updated": "2025-12-17", + "knowledge": "2023-09", + "release_date": "2024-07-18", + "last_updated": "2024-07-18", "type": "chat" }, { - "id": "databricks-gemini-3-5-flash", - "name": "Gemini 3.5 Flash", - "display_name": "Gemini 3.5 Flash", + "id": "openai/o3-pro-2025-06-10", + "name": "OpenAI o3-pro (2025-06-10)", + "display_name": "OpenAI o3-pro (2025-06-10)", "modalities": { "input": [ - "text", - "image", - "video", - "audio" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 200000, + "output": 100000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -57991,39 +59113,42 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", "effort_options": [ - "minimal", "low", "medium", "high" - ] + ], + "visibility": "hidden" } }, - "attachment": true, + "attachment": false, "open_weights": false, - "knowledge": "2025-01", - "release_date": "2026-05-19", - "last_updated": "2026-05-19", + "knowledge": "2024-05", + "release_date": "2025-06-10", + "last_updated": "2025-06-10", "type": "chat" }, { - "id": "databricks-gpt-5-mini", - "name": "GPT-5 Mini", - "display_name": "GPT-5 Mini", + "id": "openai/gpt-oss-120b", + "name": "GPT OSS 120B", + "display_name": "GPT OSS 120B", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 128000, + "output": 16384 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -58031,118 +59156,109 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, "effort_options": [ - "minimal", "low", "medium", "high" ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2024-05-30", - "release_date": "2025-08-07", - "last_updated": "2025-08-07", + "attachment": false, + "open_weights": true, + "knowledge": "2024-06", + "release_date": "2025-08-05", + "last_updated": "2025-08-05", "type": "chat" }, { - "id": "databricks-gpt-5-4-nano", - "name": "GPT-5.4 nano", - "display_name": "GPT-5.4 nano", + "id": "openai/o1", + "name": "OpenAI o1", + "display_name": "OpenAI o1", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 200000, + "output": 100000 }, - "temperature": true, - "tool_call": true, + "temperature": false, + "tool_call": false, "reasoning": { "supported": true, "default": true }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", "effort_options": [ "low", "medium", "high" - ] + ], + "visibility": "hidden" } }, - "attachment": true, + "attachment": false, "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-03-17", - "last_updated": "2026-03-17", + "knowledge": "2023-09", + "release_date": "2024-12-05", + "last_updated": "2024-12-05", "type": "chat" }, { - "id": "databricks-gemini-3-1-flash-lite", - "name": "Gemini 3.1 Flash Lite Preview", - "display_name": "Gemini 3.1 Flash Lite Preview", + "id": "openai/gpt-4o", + "name": "GPT-4o", + "display_name": "GPT-4o", "modalities": { "input": [ "text", - "image", - "video", - "audio", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 128000, + "output": 16384 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "minimal", - "low", - "medium", - "high" - ] - } + "supported": false }, "attachment": true, "open_weights": false, - "knowledge": "2025-01", - "release_date": "2026-03-03", - "last_updated": "2026-03-03", + "knowledge": "2023-09", + "release_date": "2024-05-13", + "last_updated": "2024-08-06", "type": "chat" }, { - "id": "databricks-gpt-5-nano", - "name": "GPT-5 Nano", - "display_name": "GPT-5 Nano", + "id": "openai/gpt-5.6-terra-pro", + "name": "GPT 5.6 Terra Pro", + "display_name": "GPT 5.6 Terra Pro", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 400000, + "context": 1050000, "output": 128000 }, "temperature": false, @@ -58154,24 +59270,26 @@ "extra_capabilities": { "reasoning": { "effort_options": [ - "minimal", + "none", "low", "medium", - "high" + "high", + "xhigh", + "max" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2024-05-30", - "release_date": "2025-08-07", - "last_updated": "2025-08-07", + "knowledge": "2026-02-16", + "release_date": "2026-07-09", + "last_updated": "2026-07-09", "type": "chat" }, { - "id": "databricks-gpt-5-6-luna", - "name": "GPT-5.6 Luna", - "display_name": "GPT-5.6 Luna", + "id": "openai/gpt-5.6-luna-pro", + "name": "GPT 5.6 Luna Pro", + "display_name": "GPT 5.6 Luna Pro", "modalities": { "input": [ "text", @@ -58195,9 +59313,12 @@ "extra_capabilities": { "reasoning": { "effort_options": [ + "none", "low", "medium", - "high" + "high", + "xhigh", + "max" ] } }, @@ -58209,23 +59330,24 @@ "type": "chat" }, { - "id": "databricks-gpt-5-1", - "name": "GPT-5.1", - "display_name": "GPT-5.1", + "id": "openai/gpt-5.6-sol", + "name": "GPT 5.6 Sol", + "display_name": "GPT 5.6 Sol", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 400000, + "context": 1050000, "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -58233,31 +59355,42 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", "effort_options": [ "none", + "low", + "medium", + "high", + "xhigh", + "max" + ], + "verbosity": "medium", + "verbosity_options": [ "low", "medium", "high" - ] + ], + "visibility": "hidden" } }, "attachment": true, "open_weights": false, - "knowledge": "2024-09-30", - "release_date": "2025-11-13", - "last_updated": "2025-11-13", + "knowledge": "2026-02-16", + "release_date": "2026-07-09", + "last_updated": "2026-07-09", "type": "chat" }, { - "id": "databricks-gemini-3-pro", - "name": "Gemini 3 Pro Preview", - "display_name": "Gemini 3 Pro Preview", + "id": "openai/gpt-5.1", + "name": "GPT 5.1", + "display_name": "GPT 5.1", "modalities": { "input": [ "text", "image", - "video", - "audio", "pdf" ], "output": [ @@ -58265,35 +59398,47 @@ ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 400000, + "output": 128000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": false, + "mode": "effort", + "effort": "none", "effort_options": [ + "none", "low", "medium", "high" - ] + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, "attachment": true, "open_weights": false, - "knowledge": "2025-01", - "release_date": "2025-11-18", - "last_updated": "2025-11-18", + "knowledge": "2024-09-30", + "release_date": "2025-11-13", + "last_updated": "2025-11-13", "type": "chat" }, { - "id": "databricks-gpt-5-6-terra", - "name": "GPT-5.6 Terra", - "display_name": "GPT-5.6 Terra", + "id": "openai/gpt-latest", + "name": "GPT Latest", + "display_name": "GPT Latest", "modalities": { "input": [ "text", @@ -58308,7 +59453,6 @@ "context": 1050000, "output": 128000 }, - "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -58319,35 +59463,37 @@ "effort_options": [ "low", "medium", - "high" + "high", + "xhigh", + "max" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-02-16", - "release_date": "2026-07-09", - "last_updated": "2026-07-09", + "release_date": "2026-03-29", + "last_updated": "2026-03-29", "type": "chat" }, { - "id": "databricks-gpt-5-2", - "name": "GPT-5.2", - "display_name": "GPT-5.2", + "id": "openai/gpt-5.5", + "name": "GPT 5.5", + "display_name": "GPT 5.5", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 400000, + "context": 1050000, "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -58355,41 +59501,51 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", "effort_options": [ - "none", + "low", + "medium", + "high", + "xhigh" + ], + "verbosity": "medium", + "verbosity_options": [ "low", "medium", "high" - ] + ], + "visibility": "hidden" } }, "attachment": true, "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2025-12-11", - "last_updated": "2025-12-11", + "knowledge": "2025-12-01", + "release_date": "2026-04-23", + "last_updated": "2026-04-23", "type": "chat" }, { - "id": "databricks-claude-sonnet-4-6", - "name": "Claude Sonnet 4.6", - "display_name": "Claude Sonnet 4.6", + "id": "openai/gpt-5.1-codex-mini", + "name": "GPT 5.1 Codex Mini", + "display_name": "GPT 5.1 Codex Mini", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 64000 + "context": 400000, + "output": 128000 }, - "temperature": true, - "tool_call": true, + "temperature": false, + "tool_call": false, "reasoning": { "supported": true, "default": false @@ -58398,47 +59554,38 @@ "reasoning": { "supported": true, "default_enabled": false, - "mode": "mixed", - "budget": { - "min": 1024, - "unit": "tokens" - }, - "effort": "high", + "mode": "effort", + "effort": "none", "effort_options": [ + "none", "low", "medium", - "high", - "max" + "high" ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" ], - "notes": [ - "Anthropic recommends adaptive thinking with effort for Claude 4.6; budget_tokens remains a deprecated compatibility path.", - "Anthropic API defaults effort to high; lower effort levels should be chosen per workload." - ] + "visibility": "hidden" } }, "attachment": true, "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-02-17", - "last_updated": "2026-03-13", + "knowledge": "2024-09-30", + "release_date": "2025-11-13", + "last_updated": "2025-11-13", "type": "chat" }, { - "id": "databricks-gemini-3-1-pro", - "name": "Gemini 3.1 Pro Preview", - "display_name": "Gemini 3.1 Pro Preview", + "id": "openai/gpt-6.1-terra-pro", + "name": "GPT 6.1 Terra Pro", + "display_name": "GPT 6.1 Terra Pro", "modalities": { "input": [ "text", "image", - "video", - "audio", "pdf" ], "output": [ @@ -58446,10 +59593,9 @@ ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 1050000, + "output": 128000 }, - "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -58458,24 +59604,24 @@ "extra_capabilities": { "reasoning": { "effort_options": [ - "minimal", "low", "medium", - "high" + "high", + "xhigh", + "max" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-01", - "release_date": "2026-02-19", - "last_updated": "2026-02-19", + "release_date": "2026-10-27", + "last_updated": "2026-10-27", "type": "chat" }, { - "id": "databricks-gpt-5-4", - "name": "GPT-5.4", - "display_name": "GPT-5.4", + "id": "openai/gpt-luna-latest", + "name": "GPT Luna Latest", + "display_name": "GPT Luna Latest", "modalities": { "input": [ "text", @@ -58490,7 +59636,6 @@ "context": 1050000, "output": 128000 }, - "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -58499,29 +59644,29 @@ "extra_capabilities": { "reasoning": { "effort_options": [ + "none", "low", "medium", - "high" + "high", + "xhigh", + "max" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-03-05", - "last_updated": "2026-03-05", + "release_date": "2026-09-11", + "last_updated": "2026-09-11", "type": "chat" }, { - "id": "databricks-gemini-2-5-flash", - "name": "Gemini 2.5 Flash", - "display_name": "Gemini 2.5 Flash", + "id": "openai/gpt-sol-latest", + "name": "GPT Sol Latest", + "display_name": "GPT Sol Latest", "modalities": { "input": [ "text", "image", - "audio", - "video", "pdf" ], "output": [ @@ -58529,26 +59674,35 @@ ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 1050000, + "output": 128000 }, - "temperature": true, "tool_call": true, "reasoning": { "supported": true, "default": true }, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" + ] + } + }, "attachment": true, "open_weights": false, - "knowledge": "2025-01", - "release_date": "2025-06-17", - "last_updated": "2025-06-17", + "release_date": "2026-09-11", + "last_updated": "2026-09-11", "type": "chat" }, { - "id": "databricks-gpt-5-6-sol", - "name": "GPT-5.6 Sol", - "display_name": "GPT-5.6 Sol", + "id": "openai/gpt-5.6-sol-pro", + "name": "GPT 5.6 Sol Pro", + "display_name": "GPT 5.6 Sol Pro", "modalities": { "input": [ "text", @@ -58572,9 +59726,11 @@ "extra_capabilities": { "reasoning": { "effort_options": [ + "none", "low", "medium", "high", + "xhigh", "max" ] } @@ -58587,61 +59743,38 @@ "type": "chat" }, { - "id": "databricks-claude-opus-4-1", - "name": "Claude Opus 4.1 (latest)", - "display_name": "Claude Opus 4.1 (latest)", + "id": "openai/gpt-4.1-mini", + "name": "GPT 4.1 Mini", + "display_name": "GPT 4.1 Mini", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 32000 + "context": 1047576, + "output": 32768 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": false - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "budget", - "budget": { - "min": 1024, - "unit": "tokens" - }, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", - "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." - ] - } + "supported": false }, "attachment": true, "open_weights": false, - "knowledge": "2025-03-31", - "release_date": "2025-08-05", - "last_updated": "2025-08-05", + "knowledge": "2024-04", + "release_date": "2025-04-14", + "last_updated": "2025-04-14", "type": "chat" }, { - "id": "databricks-claude-opus-4-7", - "name": "Claude Opus 4.7", - "display_name": "Claude Opus 4.7", + "id": "openai/gpt-4.1", + "name": "GPT 4.1", + "display_name": "GPT 4.1", "modalities": { "input": [ "text", @@ -58653,52 +59786,25 @@ ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 1047576, + "output": 32768 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": false - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "high", - "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "omitted", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Claude Opus 4.7 and newer Opus models require thinking.type = \"adaptive\" to enable thinking explicitly.", - "Manual budget_tokens requests return 400 on Claude Opus 4.7 and newer adaptive-only Opus models.", - "task_budget is separate from thinking control and should not be treated as a thinking budget." - ] - } + "supported": false }, "attachment": true, "open_weights": false, - "knowledge": "2026-01-31", - "release_date": "2026-04-16", - "last_updated": "2026-04-16", + "knowledge": "2024-04", + "release_date": "2025-04-14", + "last_updated": "2025-04-14", "type": "chat" }, { - "id": "databricks-gpt-5-5", - "name": "GPT-5.5", - "display_name": "GPT-5.5", + "id": "openai/gpt-6-luna", + "name": "GPT 6 Luna", + "display_name": "GPT 6 Luna", "modalities": { "input": [ "text", @@ -58722,112 +59828,108 @@ "extra_capabilities": { "reasoning": { "effort_options": [ + "none", "low", "medium", - "high" + "high", + "xhigh", + "max" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-12-01", - "release_date": "2026-04-23", - "last_updated": "2026-04-23", + "knowledge": "2026-05-18", + "release_date": "2026-09-22", + "last_updated": "2026-09-22", "type": "chat" }, { - "id": "databricks-kimi-k2-7-code", - "name": "Kimi K2.7 Code", - "display_name": "Kimi K2.7 Code", + "id": "openai/o3", + "name": "OpenAI o3", + "display_name": "OpenAI o3", "modalities": { "input": [ - "text", - "image", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 200000, + "output": 100000 }, "temperature": false, - "tool_call": true, + "tool_call": false, "reasoning": { "supported": true, "default": true }, - "attachment": true, - "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-06-12", - "last_updated": "2026-06-12", + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", + "effort_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" + } + }, + "attachment": false, + "open_weights": false, + "knowledge": "2024-05", + "release_date": "2025-04-16", + "last_updated": "2025-04-16", "type": "chat" }, { - "id": "databricks-claude-opus-4-5", - "name": "Claude Opus 4.5 (latest)", - "display_name": "Claude Opus 4.5 (latest)", + "id": "openai/gpt-oss-20b", + "name": "GPT OSS 20B", + "display_name": "GPT OSS 20B", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 64000 + "context": 131072, + "output": 16384 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "mixed", - "budget": { - "min": 1024, - "unit": "tokens" - }, - "effort": "high", "effort_options": [ "low", "medium", "high" - ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Claude Opus 4.5 uses manual thinking.type = \"enabled\" with budget_tokens; effort can be used alongside the thinking budget.", - "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header." ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-05", - "release_date": "2025-11-24", - "last_updated": "2025-11-24", + "attachment": false, + "open_weights": true, + "knowledge": "2024-06", + "release_date": "2025-08-05", + "last_updated": "2025-08-05", "type": "chat" }, { - "id": "databricks-gpt-5", - "name": "GPT-5", - "display_name": "GPT-5", + "id": "nex-agi/nex-n2.5-pro", + "name": "Nex N2.5 Pro", + "display_name": "Nex N2.5 Pro", "modalities": { "input": [ "text", @@ -58838,10 +59940,9 @@ ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 262144, + "output": 262144 }, - "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -58850,164 +59951,119 @@ "extra_capabilities": { "reasoning": { "effort_options": [ - "minimal", - "low", + "none", "medium", "high" ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2024-09-30", - "release_date": "2025-08-07", - "last_updated": "2025-08-07", + "open_weights": true, + "release_date": "2026-09-22", + "last_updated": "2026-09-22", "type": "chat" }, { - "id": "databricks-claude-haiku-4-5", - "name": "Claude Haiku 4.5 (latest)", - "display_name": "Claude Haiku 4.5 (latest)", + "id": "nex-agi/nex-n2.5-mini", + "name": "Nex N2.5 Mini", + "display_name": "Nex N2.5 Mini", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 64000 + "context": 262144, + "output": 262144 }, - "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "budget", - "budget": { - "min": 1024, - "unit": "tokens" - }, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", - "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." + "effort_options": [ + "none", + "medium", + "high" ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2025-02-28", - "release_date": "2025-10-15", - "last_updated": "2025-10-15", + "open_weights": true, + "release_date": "2026-09-22", + "last_updated": "2026-09-22", "type": "chat" }, { - "id": "databricks-gemini-2-5-pro", - "name": "Gemini 2.5 Pro", - "display_name": "Gemini 2.5 Pro", + "id": "VongolaChouko/Starcannon-Unleashed-12B-v1.0", + "name": "Mistral Nemo Starcannon 12b v1", + "display_name": "Mistral Nemo Starcannon 12b v1", "modalities": { "input": [ - "text", - "image", - "audio", - "video", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 16384, + "output": 8192 }, - "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true + "supported": false }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-01", - "release_date": "2025-06-17", - "last_updated": "2025-06-17", + "attachment": false, + "open_weights": true, + "release_date": "2024-01-01", + "last_updated": "2024-07-01", "type": "chat" }, { - "id": "databricks-claude-sonnet-4-5", - "name": "Claude Sonnet 4.5 (latest)", - "display_name": "Claude Sonnet 4.5 (latest)", + "id": "meituan/longcat-2.5-preview", + "name": "LongCat 2.5 Preview", + "display_name": "LongCat 2.5 Preview", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 64000 + "context": 1000000, + "output": 131072 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": false - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "budget", - "budget": { - "min": 1024, - "unit": "tokens" - }, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", - "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." - ] - } + "default": true }, "attachment": true, "open_weights": false, - "knowledge": "2025-07-31", - "release_date": "2025-09-29", - "last_updated": "2025-09-29", + "release_date": "2026-09-25", + "last_updated": "2026-09-25", "type": "chat" }, { - "id": "databricks-glm-5-2", - "name": "GLM-5.2", - "display_name": "GLM-5.2", + "id": "sakana/fugu-max", + "name": "Fugu Max", + "display_name": "Fugu Max", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" @@ -59015,9 +60071,9 @@ }, "limit": { "context": 1000000, - "output": 131072 + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -59026,22 +60082,22 @@ "extra_capabilities": { "reasoning": { "effort_options": [ - "none", "high", + "xhigh", "max" ] } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-06-13", - "last_updated": "2026-06-13", + "attachment": true, + "open_weights": false, + "release_date": "2026-09-11", + "last_updated": "2026-09-11", "type": "chat" }, { - "id": "databricks-gpt-5-4-mini", - "name": "GPT-5.4 mini", - "display_name": "GPT-5.4 mini", + "id": "sakana/fugu-ultra", + "name": "Fugu Ultra", + "display_name": "Fugu Ultra", "modalities": { "input": [ "text", @@ -59052,10 +60108,10 @@ ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 1000000, + "output": 16384 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -59064,28 +60120,26 @@ "extra_capabilities": { "reasoning": { "effort_options": [ - "low", - "medium", - "high" + "high", + "xhigh", + "max" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-03-17", - "last_updated": "2026-03-17", + "release_date": "2026-06-15", + "last_updated": "2026-06-15", "type": "chat" }, { - "id": "databricks-claude-opus-4-6", - "name": "Claude Opus 4.6", - "display_name": "Claude Opus 4.6", + "id": "sakana/fugu-ultra-v1.1", + "name": "Fugu Ultra v1.1", + "display_name": "Fugu Ultra v1.1", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" @@ -59093,155 +60147,110 @@ }, "limit": { "context": 1000000, - "output": 128000 + "output": 16384 }, - "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "mixed", - "budget": { - "min": 1024, - "unit": "tokens" - }, - "effort": "high", "effort_options": [ - "low", - "medium", "high", + "xhigh", "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Anthropic recommends adaptive thinking with effort for Claude 4.6; budget_tokens remains a deprecated compatibility path.", - "Anthropic API defaults effort to high; lower effort levels should be chosen per workload." ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-05-31", - "release_date": "2026-02-05", - "last_updated": "2026-03-13", + "release_date": "2026-07-24", + "last_updated": "2026-07-24", "type": "chat" }, { - "id": "databricks-claude-sonnet-4", - "name": "Claude Sonnet 4", - "display_name": "Claude Sonnet 4", + "id": "deepcogito/cogito-v1-preview-qwen-32B", + "name": "Cogito v1 Preview Qwen 32B", + "display_name": "Cogito v1 Preview Qwen 32B", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 64000 + "context": 128000, + "output": 32768 }, - "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": false - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "budget", - "budget": { - "min": 1024, - "unit": "tokens" - }, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", - "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." - ] - } + "supported": false }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-03-31", - "release_date": "2025-05-22", - "last_updated": "2025-05-22", + "attachment": false, + "open_weights": true, + "release_date": "2025-05-10", + "last_updated": "2025-05-10", "type": "chat" - } - ] - }, - "zhipuai": { - "id": "zhipuai", - "name": "Zhipu AI", - "display_name": "Zhipu AI", - "api": "https://open.bigmodel.cn/api/paas/v4", - "doc": "https://docs.bigmodel.cn/cn/guide/start/pricing", - "models": [ + }, { - "id": "glm-4.6v-flash", - "name": "GLM-4.6V-Flash", - "display_name": "GLM-4.6V-Flash", + "id": "Envoid/Llama-3.05-Nemotron-Tenyxchat-Storybreaker-70B", + "name": "Nemotron Tenyxchat Storybreaker 70b", + "display_name": "Nemotron Tenyxchat Storybreaker 70b", "modalities": { "input": [ - "text", - "image", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 32768 + "context": 16384, + "output": 8192 }, - "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true + "supported": false }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } + "attachment": false, + "open_weights": true, + "release_date": "2024-01-01", + "last_updated": "2024-12-01", + "type": "chat" + }, + { + "id": "Envoid/Llama-3.05-NT-Storybreaker-Ministral-70B", + "name": "Llama 3.05 Storybreaker Ministral 70b", + "display_name": "Llama 3.05 Storybreaker Ministral 70b", + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] }, - "attachment": true, + "limit": { + "context": 16384, + "output": 8192 + }, + "tool_call": false, + "reasoning": { + "supported": false + }, + "attachment": false, "open_weights": true, - "release_date": "2025-12-08", - "last_updated": "2025-12-08", + "release_date": "2024-01-01", + "last_updated": "2024-12-01", "type": "chat" }, { - "id": "glm-4.7-flashx", - "name": "GLM-4.7-FlashX", - "display_name": "GLM-4.7-FlashX", + "id": "nvidia/nemotron-3.5-lightning", + "name": "Nvidia Nemotron 3.5 Lightning", + "display_name": "Nvidia Nemotron 3.5 Lightning", "modalities": { "input": [ "text" @@ -59251,8 +60260,8 @@ ] }, "limit": { - "context": 200000, - "output": 131072 + "context": 1000000, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -59263,25 +60272,23 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "none", + "high" ] } }, "attachment": false, "open_weights": true, - "knowledge": "2025-04", - "release_date": "2026-01-19", - "last_updated": "2026-01-19", + "knowledge": "2025-09", + "release_date": "2026-08-11", + "last_updated": "2026-08-11", "type": "chat" }, { - "id": "glm-4.5v", - "name": "GLM-4.5V", - "display_name": "GLM-4.5V", + "id": "nvidia/nemotron-3.5-super-vl", + "name": "Nvidia Nemotron 3.5 Super VL", + "display_name": "Nvidia Nemotron 3.5 Super VL", "modalities": { "input": [ "text", @@ -59293,7 +60300,7 @@ ] }, "limit": { - "context": 64000, + "context": 262144, "output": 16384 }, "temperature": true, @@ -59304,40 +60311,34 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "none", + "medium", + "high" ] } }, "attachment": true, "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-08-11", - "last_updated": "2025-08-11", + "release_date": "2026-10-15", + "last_updated": "2026-10-15", "type": "chat" }, { - "id": "glm-5.3-flash", - "name": "GLM-5.3-Flash", - "display_name": "GLM-5.3-Flash", + "id": "nvidia/nemotron-3-ultra-550b-a55b", + "name": "Nvidia Nemotron 3 Ultra 550B", + "display_name": "Nvidia Nemotron 3 Ultra 550B", "modalities": { "input": [ - "text", - "image", - "video", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 512288, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -59347,85 +60348,58 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "effort_options": [ - "low", - "high", - "max" - ] + "supported": true } }, - "attachment": true, + "attachment": false, "open_weights": true, - "release_date": "2026-08-26", - "last_updated": "2026-08-26", + "knowledge": "2025-09", + "release_date": "2026-06-04", + "last_updated": "2026-06-04", "type": "chat" }, { - "id": "glm-5v-turbo", - "name": "GLM-5V-Turbo", - "display_name": "GLM-5V-Turbo", + "id": "nvidia/Llama-3.3-Nemotron-Super-49B-v1", + "name": "Nvidia Nemotron Super 49B", + "display_name": "Nvidia Nemotron Super 49B", "modalities": { "input": [ - "text", - "image", - "video", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 131072 + "context": 128000, + "output": 16384 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } + "supported": false }, - "attachment": true, - "open_weights": false, - "release_date": "2026-04-01", - "last_updated": "2026-04-01", + "attachment": false, + "open_weights": true, + "release_date": "2025-03-18", + "last_updated": "2025-03-18", "type": "chat" }, { - "id": "glm-4.6v", - "name": "GLM-4.6V", - "display_name": "GLM-4.6V", + "id": "nvidia/nemotron-3.5-lightning:thinking", + "name": "Nvidia Nemotron 3.5 Lightning Thinking", + "display_name": "Nvidia Nemotron 3.5 Lightning Thinking", "modalities": { "input": [ - "text", - "image", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 32768 + "context": 1000000, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -59435,26 +60409,23 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "none", + "high" ] } }, - "attachment": true, + "attachment": false, "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-12-08", - "last_updated": "2025-12-08", + "knowledge": "2025-09", + "release_date": "2026-08-11", + "last_updated": "2026-08-11", "type": "chat" }, { - "id": "glm-4.7", - "name": "GLM-4.7", - "display_name": "GLM-4.7", + "id": "nvidia/nemotron-3-super-120b-a12b:thinking", + "name": "Nvidia Nemotron 3 Super 120B Thinking", + "display_name": "Nvidia Nemotron 3 Super 120B Thinking", "modalities": { "input": [ "text" @@ -59464,37 +60435,26 @@ ] }, "limit": { - "context": 204800, - "output": 131072 + "context": 262144, + "output": 16384 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } - }, "attachment": false, "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-12-22", - "last_updated": "2025-12-22", + "knowledge": "2025-06", + "release_date": "2026-03-11", + "last_updated": "2026-03-11", "type": "chat" }, { - "id": "glm-4.6", - "name": "GLM-4.6", - "display_name": "GLM-4.6", + "id": "nvidia/nemotron-3-ultra-550b-a55b:thinking", + "name": "Nvidia Nemotron 3 Ultra 550B Thinking", + "display_name": "Nvidia Nemotron 3 Ultra 550B Thinking", "modalities": { "input": [ "text" @@ -59504,8 +60464,8 @@ ] }, "limit": { - "context": 204800, - "output": 131072 + "context": 1000000, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -59513,28 +60473,17 @@ "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } - }, "attachment": false, "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-09-30", - "last_updated": "2025-09-30", + "knowledge": "2025-09", + "release_date": "2026-06-04", + "last_updated": "2026-06-04", "type": "chat" }, { - "id": "glm-4.5-air", - "name": "GLM-4.5-Air", - "display_name": "GLM-4.5-Air", + "id": "nvidia/nemotron-3-nano-30b-a3b", + "name": "Nvidia Nemotron 3 Nano 30B", + "display_name": "Nvidia Nemotron 3 Nano 30B", "modalities": { "input": [ "text" @@ -59544,84 +60493,58 @@ ] }, "limit": { - "context": 131072, - "output": 98304 + "context": 262144, + "output": 235929 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } + "supported": false }, "attachment": false, "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-07-28", - "last_updated": "2025-07-28", + "knowledge": "2025-06-25", + "release_date": "2025-12-15", + "last_updated": "2025-12-15", "type": "chat" }, { - "id": "glm-5.3-flashx", - "name": "GLM-5.3-FlashX", - "display_name": "GLM-5.3-FlashX", + "id": "nvidia/nemotron-3.5-content-safety", + "name": "Nemotron 3.5 Content Safety", + "display_name": "Nemotron 3.5 Content Safety", "modalities": { "input": [ - "text", - "image", - "video", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 131072, + "output": 32768 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { "supported": true, "default": true }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "effort_options": [ - "low", - "high", - "max" - ] + "supported": true } }, - "attachment": true, + "attachment": false, "open_weights": true, - "release_date": "2026-09-18", - "last_updated": "2026-09-18", + "release_date": "2026-06-02", + "last_updated": "2026-06-02", "type": "chat" }, { - "id": "glm-5.1", - "name": "GLM-5.1", - "display_name": "GLM-5.1", + "id": "nvidia/nemotron-3-super-120b-a12b", + "name": "Nvidia Nemotron 3 Super 120B", + "display_name": "Nvidia Nemotron 3 Super 120B", "modalities": { "input": [ "text" @@ -59631,36 +60554,26 @@ ] }, "limit": { - "context": 200000, - "output": 131072 + "context": 262144, + "output": 16384 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } - }, "attachment": false, "open_weights": true, - "release_date": "2026-04-07", - "last_updated": "2026-04-07", + "knowledge": "2025-06", + "release_date": "2026-03-11", + "last_updated": "2026-03-11", "type": "chat" }, { - "id": "glm-5", - "name": "GLM-5", - "display_name": "GLM-5", + "id": "nvidia/Llama-3.1-Nemotron-70B-Instruct-HF", + "name": "Nvidia Nemotron 70b", + "display_name": "Nvidia Nemotron 70b", "modalities": { "input": [ "text" @@ -59670,160 +60583,108 @@ ] }, "limit": { - "context": 204800, - "output": 131072 + "context": 16384, + "output": 8192 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } + "supported": false }, "attachment": false, "open_weights": true, - "release_date": "2026-02-12", - "last_updated": "2026-02-12", + "release_date": "2025-04-15", + "last_updated": "2025-04-15", "type": "chat" }, { - "id": "glm-5.3", - "name": "GLM-5.3", - "display_name": "GLM-5.3", + "id": "ornith-ai/ornith-1.5-35b-a3b", + "name": "Ornith 1.5 35B", + "display_name": "Ornith 1.5 35B", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 262144, + "output": 32768 }, - "temperature": true, "tool_call": true, "reasoning": { "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "effort_options": [ - "low", - "high", - "max" - ] - } - }, - "attachment": false, + "attachment": true, "open_weights": true, - "release_date": "2026-08-14", - "last_updated": "2026-08-14", + "release_date": "2026-07-29", + "last_updated": "2026-08-20", "type": "chat" }, { - "id": "glm-4.5-flash", - "name": "GLM-4.5-Flash", - "display_name": "GLM-4.5-Flash", + "id": "ornith-ai/ornith-1.5-122b", + "name": "Ornith 1.5 122B", + "display_name": "Ornith 1.5 122B", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 98304 + "context": 262144, + "output": 32768 }, - "temperature": true, "tool_call": true, "reasoning": { "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } - }, - "attachment": false, - "open_weights": false, - "knowledge": "2025-04", - "release_date": "2025-07-28", - "last_updated": "2025-07-28", + "attachment": true, + "open_weights": true, + "release_date": "2026-10-20", + "last_updated": "2026-10-20", "type": "chat" }, { - "id": "glm-4.5", - "name": "GLM-4.5", - "display_name": "GLM-4.5", + "id": "ornith-ai/ornith-1.5-35b-a3b:thinking", + "name": "Ornith 1.5 35B Thinking", + "display_name": "Ornith 1.5 35B Thinking", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 98304 + "context": 262144, + "output": 32768 }, - "temperature": true, "tool_call": true, "reasoning": { "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } - }, - "attachment": false, + "attachment": true, "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-07-28", - "last_updated": "2025-07-28", + "release_date": "2026-07-29", + "last_updated": "2026-08-20", "type": "chat" }, { - "id": "glm-4.7-flash", - "name": "GLM-4.7-Flash", - "display_name": "GLM-4.7-Flash", + "id": "speakleash/bielik-11b-v3.0-instruct", + "name": "Bielik 11B v3.0", + "display_name": "Bielik 11B v3.0", "modalities": { "input": [ "text" @@ -59833,48 +60694,91 @@ ] }, "limit": { - "context": 200000, - "output": 131072 + "context": 32768, + "output": 32768 }, - "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true + "supported": false }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } + "attachment": false, + "open_weights": true, + "release_date": "2026-10-15", + "last_updated": "2026-10-15", + "type": "chat" + }, + { + "id": "mlabonne/NeuralDaredevil-8B-abliterated", + "name": "Neural Daredevil 8B abliterated", + "display_name": "Neural Daredevil 8B abliterated", + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 8192, + "output": 8192 + }, + "tool_call": false, + "reasoning": { + "supported": false }, "attachment": false, "open_weights": true, - "knowledge": "2025-04", - "release_date": "2026-01-19", - "last_updated": "2026-01-19", + "release_date": "2024-01-01", + "last_updated": "2024-12-01", "type": "chat" }, { - "id": "glm-5.2", - "name": "GLM-5.2", - "display_name": "GLM-5.2", + "id": "thinkingmachines/Inkling-Small", + "name": "Inkling Small", + "display_name": "Inkling Small", "modalities": { "input": [ + "text", + "image", + "audio" + ], + "output": [ "text" + ] + }, + "limit": { + "context": 524288, + "output": 32768 + }, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": false + }, + "attachment": true, + "open_weights": true, + "release_date": "2026-07-30", + "last_updated": "2026-07-30", + "type": "chat" + }, + { + "id": "thinkingmachines/Inkling-Small:thinking", + "name": "Inkling Small Thinking", + "display_name": "Inkling Small Thinking", + "modalities": { + "input": [ + "text", + "image", + "audio" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 524288, + "output": 32768 }, "temperature": true, "tool_call": true, @@ -59884,51 +60788,67 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], "effort_options": [ + "minimal", + "low", + "medium", "high", "max" ] } }, - "attachment": false, + "attachment": true, "open_weights": true, - "release_date": "2026-06-13", - "last_updated": "2026-06-13", + "release_date": "2026-07-30", + "last_updated": "2026-07-30", "type": "chat" - } - ] - }, - "boundless": { - "id": "boundless", - "name": "Boundless", - "display_name": "Boundless", - "api": "https://api.inference.boundless.network/v1", - "doc": "https://inference.boundless.network/models", - "models": [ + }, { - "id": "glm-5.3-flash", - "name": "GLM-5.3-Flash", - "display_name": "GLM-5.3-Flash", + "id": "thinkingmachines/inkling", + "name": "Inkling", + "display_name": "Inkling", "modalities": { "input": [ "text", "image", - "video" + "audio" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 1048000, + "output": 32768 + }, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": false + }, + "attachment": true, + "open_weights": true, + "release_date": "2026-07-15", + "last_updated": "2026-07-15", + "type": "chat" + }, + { + "id": "thinkingmachines/inkling:thinking", + "name": "Inkling Thinking", + "display_name": "Inkling Thinking", + "modalities": { + "input": [ + "text", + "image", + "audio" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1040000, + "output": 32768 }, "temperature": true, "tool_call": true, @@ -59938,37 +60858,36 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, "effort_options": [ + "minimal", "low", - "high" + "medium", + "high", + "xhigh" ] } }, "attachment": true, "open_weights": true, - "release_date": "2026-08-26", - "last_updated": "2026-08-26", + "release_date": "2026-07-15", + "last_updated": "2026-07-15", "type": "chat" }, { - "id": "mimo-v2.6-pro", - "name": "MiMo-V2.6-Pro", - "display_name": "MiMo-V2.6-Pro", + "id": "z-ai/glm-4.6-original", + "name": "GLM 4.6 Original", + "display_name": "GLM 4.6 Original", "modalities": { "input": [ - "text", - "image", - "audio", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 256000, + "output": 65535 }, "temperature": true, "tool_call": true, @@ -59976,16 +60895,47 @@ "supported": true, "default": true }, + "attachment": false, + "open_weights": true, + "knowledge": "2025-04", + "release_date": "2025-09-30", + "last_updated": "2025-09-30", + "type": "chat" + }, + { + "id": "z-ai/glm-4.5v:thinking", + "name": "GLM 4.5V Thinking", + "display_name": "GLM 4.5V Thinking", + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 65536, + "output": 16384 + }, + "temperature": true, + "tool_call": false, + "reasoning": { + "supported": true, + "default": true + }, "attachment": true, "open_weights": true, - "release_date": "2026-09-22", - "last_updated": "2026-09-22", + "knowledge": "2025-04", + "release_date": "2025-08-11", + "last_updated": "2025-08-11", "type": "chat" }, { - "id": "claude-fable-5.1", - "name": "Claude Fable 5.1", - "display_name": "Claude Fable 5.1", + "id": "z-ai/glm-5.1:thinking", + "name": "GLM 5.1 Thinking", + "display_name": "GLM 5.1 Thinking", "modalities": { "input": [ "text" @@ -59995,10 +60945,10 @@ ] }, "limit": { - "context": 262144, - "output": 128000 + "context": 200000, + "output": 131072 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -60006,41 +60956,22 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "high", "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "omitted", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Adaptive thinking is always on for Claude Fable 5 and Claude Mythos 5; thinking.type = \"disabled\" is rejected.", - "Manual budget_tokens requests return 400 on Claude Fable 5 and Claude Mythos 5.", - "thinking.display defaults to omitted; set display to summarized to receive readable thinking summaries." + "none", + "high" ] } }, "attachment": false, - "open_weights": false, - "knowledge": "2026-06", - "release_date": "2026-09-01", - "last_updated": "2026-09-01", + "open_weights": true, + "release_date": "2026-04-07", + "last_updated": "2026-04-07", "type": "chat" }, { - "id": "kimi-k3", - "name": "Kimi K3", - "display_name": "Kimi K3", + "id": "z-ai/glm-5.3-flashx-legal", + "name": "GLM 5.3 FlashX Legal", + "display_name": "GLM 5.3 FlashX Legal", "modalities": { "input": [ "text", @@ -60054,7 +60985,6 @@ "context": 1048576, "output": 131072 }, - "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -60062,24 +60992,23 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, "effort_options": [ - "none", "low", - "high" + "high", + "max" ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-07-16", - "last_updated": "2026-07-16", + "open_weights": false, + "release_date": "2026-11-02", + "last_updated": "2026-11-02", "type": "chat" }, { - "id": "nemotron3-super", - "name": "Nemotron 3 Super 120B A12B", - "display_name": "Nemotron 3 Super 120B A12B", + "id": "z-ai/glm-4.6:thinking", + "name": "GLM 4.6 Thinking", + "display_name": "GLM 4.6 Thinking", "modalities": { "input": [ "text" @@ -60089,8 +61018,8 @@ ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 200000, + "output": 65535 }, "temperature": true, "tool_call": true, @@ -60102,22 +61031,21 @@ "reasoning": { "effort_options": [ "none", - "low", - "medium" + "high" ] } }, "attachment": false, "open_weights": true, - "knowledge": "2025-06", - "release_date": "2026-03-11", - "last_updated": "2026-03-11", + "knowledge": "2025-04", + "release_date": "2025-09-30", + "last_updated": "2025-09-30", "type": "chat" }, { - "id": "claude-opus-5", - "name": "Claude Opus 5", - "display_name": "Claude Opus 5", + "id": "z-ai/glm-5-original:thinking", + "name": "GLM 5 Original Thinking", + "display_name": "GLM 5 Original Thinking", "modalities": { "input": [ "text" @@ -60127,51 +61055,36 @@ ] }, "limit": { - "context": 262144, + "context": 200000, "output": 128000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "effort_options": [ - "none", - "low", - "medium", - "high" - ] - } - }, "attachment": false, - "open_weights": false, - "knowledge": "2026-05", - "release_date": "2026-07-24", - "last_updated": "2026-07-24", + "open_weights": true, + "release_date": "2026-02-12", + "last_updated": "2026-02-12", "type": "chat" }, { - "id": "mimo-v2.6-flash", - "name": "MiMo-V2.6-Flash", - "display_name": "MiMo-V2.6-Flash", + "id": "z-ai/glm-4.7-original", + "name": "GLM 4.7 Original", + "display_name": "GLM 4.7 Original", "modalities": { "input": [ - "text", - "image", - "audio", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 200000, + "output": 65535 }, "temperature": true, "tool_call": true, @@ -60179,16 +61092,17 @@ "supported": true, "default": true }, - "attachment": true, + "attachment": false, "open_weights": true, - "release_date": "2026-09-22", - "last_updated": "2026-09-22", + "knowledge": "2025-04", + "release_date": "2025-12-22", + "last_updated": "2025-12-22", "type": "chat" }, { - "id": "minimax-m3", - "name": "MiniMax-M3", - "display_name": "MiniMax-M3", + "id": "z-ai/glm-4.6v", + "name": "GLM 4.6V", + "display_name": "GLM 4.6V", "modalities": { "input": [ "text", @@ -60200,30 +61114,25 @@ ] }, "limit": { - "context": 1048576, - "output": 512000 + "context": 128000, + "output": 24000 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true - } + "supported": false }, "attachment": true, "open_weights": true, - "release_date": "2026-06-01", - "last_updated": "2026-06-01", + "knowledge": "2025-04", + "release_date": "2025-12-08", + "last_updated": "2025-12-08", "type": "chat" }, { - "id": "gpt-6-sol", - "name": "GPT-6 Sol", - "display_name": "GPT-6 Sol", + "id": "z-ai/glm-5.3-flash-apex-abliterated", + "name": "Apex Flash 1 Abliterated", + "display_name": "Apex Flash 1 Abliterated", "modalities": { "input": [ "text", @@ -60234,10 +61143,9 @@ ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 1048576, + "output": 32768 }, - "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -60246,24 +61154,22 @@ "extra_capabilities": { "reasoning": { "effort_options": [ - "none", "low", - "medium", - "high" + "high", + "max" ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2026-04-20", - "release_date": "2026-09-22", - "last_updated": "2026-09-22", + "open_weights": true, + "release_date": "2026-10-05", + "last_updated": "2026-10-05", "type": "chat" }, { - "id": "gpt-5.6-sol", - "name": "GPT-5.6 Sol", - "display_name": "GPT-5.6 Sol", + "id": "z-ai/GLM-4.6-turbo:thinking", + "name": "GLM 4.6 Turbo (Thinking)", + "display_name": "GLM 4.6 Turbo (Thinking)", "modalities": { "input": [ "text" @@ -60273,63 +61179,36 @@ ] }, "limit": { - "context": 262144, - "output": 128000 + "context": 204800, + "output": 131072 }, - "temperature": false, - "tool_call": true, + "tool_call": false, "reasoning": { "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", - "effort_options": [ - "none", - "low", - "medium", - "high", - "xhigh", - "max" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" - } - }, "attachment": false, - "open_weights": false, - "knowledge": "2026-02-16", - "release_date": "2026-07-09", - "last_updated": "2026-07-09", + "open_weights": true, + "release_date": "2025-10-02", + "last_updated": "2025-10-02", "type": "chat" }, { - "id": "gpt-6-luna", - "name": "GPT-6 Luna", - "display_name": "GPT-6 Luna", + "id": "z-ai/glm-5.3-uncensored", + "name": "GLM 5.3 Uncensored", + "display_name": "GLM 5.3 Uncensored", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 1048576, + "output": 131072 }, - "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -60338,28 +61217,27 @@ "extra_capabilities": { "reasoning": { "effort_options": [ - "none", "low", - "medium", - "high" + "high", + "max" ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-05-18", - "release_date": "2026-09-22", - "last_updated": "2026-09-22", + "attachment": false, + "open_weights": true, + "release_date": "2026-09-26", + "last_updated": "2026-09-26", "type": "chat" }, { - "id": "deepseek-v4.1-flash", - "name": "DeepSeek V4.1 Flash", - "display_name": "DeepSeek V4.1 Flash", + "id": "z-ai/glm-5.3-flash", + "name": "GLM 5.3 Flash", + "display_name": "GLM 5.3 Flash", "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ "text" @@ -60367,7 +61245,7 @@ }, "limit": { "context": 1048576, - "output": 384000 + "output": 131072 }, "temperature": true, "tool_call": true, @@ -60379,23 +61257,22 @@ "reasoning": { "supported": true, "effort_options": [ - "none", "low", - "high" + "high", + "max" ] } }, "attachment": true, "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-09-10", - "last_updated": "2026-09-10", + "release_date": "2026-08-26", + "last_updated": "2026-08-26", "type": "chat" }, { - "id": "deepseek-v4-pro-0813", - "name": "DeepSeek V4 Pro 0813", - "display_name": "DeepSeek V4 Pro 0813", + "id": "z-ai/glm-4.7", + "name": "GLM 4.7", + "display_name": "GLM 4.7", "modalities": { "input": [ "text" @@ -60405,8 +61282,8 @@ ] }, "limit": { - "context": 1048576, - "output": 384000 + "context": 200000, + "output": 65535 }, "temperature": true, "tool_call": true, @@ -60417,23 +61294,25 @@ "extra_capabilities": { "reasoning": { "supported": true, - "effort_options": [ - "none", - "low", - "high" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, "attachment": false, "open_weights": true, - "release_date": "2026-08-13", - "last_updated": "2026-08-22", + "knowledge": "2025-04", + "release_date": "2025-12-22", + "last_updated": "2025-12-22", "type": "chat" }, { - "id": "gpt-5.6-luna", - "name": "GPT-5.6 Luna", - "display_name": "GPT-5.6 Luna", + "id": "z-ai/glm-4.7:thinking", + "name": "GLM 4.7 Thinking", + "display_name": "GLM 4.7 Thinking", "modalities": { "input": [ "text" @@ -60443,49 +61322,26 @@ ] }, "limit": { - "context": 262144, - "output": 128000 + "context": 200000, + "output": 65535 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", - "effort_options": [ - "none", - "low", - "medium", - "high", - "xhigh", - "max" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" - } - }, "attachment": false, - "open_weights": false, - "knowledge": "2026-02-16", - "release_date": "2026-07-09", - "last_updated": "2026-07-09", + "open_weights": true, + "knowledge": "2025-04", + "release_date": "2025-12-22", + "last_updated": "2025-12-22", "type": "chat" }, { - "id": "dsv4", - "name": "DeepSeek V4 Flash 0731", - "display_name": "DeepSeek V4 Flash 0731", + "id": "z-ai/glm-4.6", + "name": "GLM 4.6", + "display_name": "GLM 4.6", "modalities": { "input": [ "text" @@ -60495,8 +61351,8 @@ ] }, "limit": { - "context": 1048576, - "output": 384000 + "context": 200000, + "output": 65535 }, "temperature": true, "tool_call": true, @@ -60506,24 +61362,24 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, "effort_options": [ "none", - "low", "high" ] } }, "attachment": false, "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-07-31", - "last_updated": "2026-07-31", + "knowledge": "2025-04", + "release_date": "2025-09-30", + "last_updated": "2025-09-30", "type": "chat" }, { - "id": "qwen3.8-flash", - "name": "Qwen3.8 Flash", - "display_name": "Qwen3.8 Flash", + "id": "z-ai/glm-5.3-flash-uncensored", + "name": "GLM 5.3 Flash Uncensored", + "display_name": "GLM 5.3 Flash Uncensored", "modalities": { "input": [ "text", @@ -60534,10 +61390,9 @@ ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 1048576, + "output": 32768 }, - "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -60545,23 +61400,23 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, "effort_options": [ "low", - "medium" + "high", + "max" ] } }, "attachment": true, - "open_weights": false, - "release_date": "2026-08-26", - "last_updated": "2026-08-26", + "open_weights": true, + "release_date": "2026-07-29", + "last_updated": "2026-08-27", "type": "chat" }, { - "id": "glm-5.3", - "name": "GLM-5.3", - "display_name": "GLM-5.3", + "id": "z-ai/glm-5-original", + "name": "GLM 5 Original", + "display_name": "GLM 5 Original", "modalities": { "input": [ "text" @@ -60571,8 +61426,8 @@ ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 200000, + "output": 128000 }, "temperature": true, "tool_call": true, @@ -60580,25 +61435,16 @@ "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "effort_options": [ - "low", - "high" - ] - } - }, "attachment": false, "open_weights": true, - "release_date": "2026-08-14", - "last_updated": "2026-08-14", + "release_date": "2026-02-12", + "last_updated": "2026-02-12", "type": "chat" }, { - "id": "qwen3.6", - "name": "Qwen3.6 35B-A3B", - "display_name": "Qwen3.6 35B-A3B", + "id": "z-ai/glm-4.6v-original", + "name": "GLM 4.6V Original", + "display_name": "GLM 4.6V Original", "modalities": { "input": [ "text", @@ -60609,39 +61455,38 @@ ] }, "limit": { - "context": 262144, - "output": 65536 + "context": 128000, + "output": 24000 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true + "supported": false }, "attachment": true, "open_weights": true, - "release_date": "2026-04-17", - "last_updated": "2026-04-17", + "knowledge": "2025-04", + "release_date": "2025-12-08", + "last_updated": "2025-12-08", "type": "chat" }, { - "id": "claude-opus-5.5", - "name": "Claude Opus 5.5", - "display_name": "Claude Opus 5.5", + "id": "z-ai/glm-5.3:thinking", + "name": "GLM 5.3 Thinking", + "display_name": "GLM 5.3 Thinking", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 1048576, + "output": 131072 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -60650,23 +61495,21 @@ "extra_capabilities": { "reasoning": { "effort_options": [ - "low", - "medium", - "high" + "high", + "max" ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-06", - "release_date": "2026-09-22", - "last_updated": "2026-09-22", + "attachment": false, + "open_weights": true, + "release_date": "2026-08-14", + "last_updated": "2026-08-14", "type": "chat" }, { - "id": "glm-5.2", - "name": "GLM-5.2", - "display_name": "GLM-5.2", + "id": "z-ai/glm-5.2:thinking", + "name": "GLM 5.2 Thinking", + "display_name": "GLM 5.2 Thinking", "modalities": { "input": [ "text" @@ -60687,10 +61530,9 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, "effort_options": [ - "none", - "high" + "high", + "max" ] } }, @@ -60699,27 +61541,14 @@ "release_date": "2026-06-13", "last_updated": "2026-06-13", "type": "chat" - } - ] - }, - "frogbot": { - "id": "frogbot", - "name": "FrogBot", - "display_name": "FrogBot", - "api": "https://app.frogbot.ai/api/v1", - "doc": "https://docs.frogbot.ai", - "models": [ + }, { - "id": "gemini-3-flash-preview", - "name": "Gemini 3 Flash Preview", - "display_name": "Gemini 3 Flash Preview", + "id": "z-ai/glm-5.3", + "name": "GLM 5.3", + "display_name": "GLM 5.3", "modalities": { "input": [ - "text", - "image", - "video", - "audio", - "pdf" + "text" ], "output": [ "text" @@ -60727,7 +61556,7 @@ }, "limit": { "context": 1048576, - "output": 65536 + "output": 131072 }, "temperature": true, "tool_call": true, @@ -60738,33 +61567,23 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "level", - "level": "high", - "level_options": [ - "minimal", + "effort_options": [ "low", - "medium", - "high" - ], - "summaries": true, - "visibility": "summary", - "continuation": [ - "thought_signatures" + "high", + "max" ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-01", - "release_date": "2025-12-17", - "last_updated": "2025-12-17", + "attachment": false, + "open_weights": true, + "release_date": "2026-08-14", + "last_updated": "2026-08-14", "type": "chat" }, { - "id": "grok-4-1-fast-reasoning", - "name": "Grok 4.1 Fast (Reasoning)", - "display_name": "Grok 4.1 Fast (Reasoning)", + "id": "z-ai/glm-4.5v", + "name": "GLM 4.5V", + "display_name": "GLM 4.5V", "modalities": { "input": [ "text", @@ -60775,46 +61594,66 @@ ] }, "limit": { - "context": 2000000, - "output": 128000 + "context": 65536, + "output": 16384 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "supported": true - } - }, "attachment": true, - "open_weights": false, - "knowledge": "2025-11", - "release_date": "2025-11-19", - "last_updated": "2025-11-19", + "open_weights": true, + "knowledge": "2025-04", + "release_date": "2025-08-11", + "last_updated": "2025-08-11", "type": "chat" }, { - "id": "gemini-2.5-pro", - "name": "Gemini 2.5 Pro", - "display_name": "Gemini 2.5 Pro", + "id": "z-ai/glm-4.7-original:thinking", + "name": "GLM 4.7 Original Thinking", + "display_name": "GLM 4.7 Original Thinking", "modalities": { "input": [ - "text", - "image", - "audio", - "video", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 200000, + "output": 65535 + }, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": true, + "default": true + }, + "attachment": false, + "open_weights": true, + "knowledge": "2025-04", + "release_date": "2025-12-22", + "last_updated": "2025-12-22", + "type": "chat" + }, + { + "id": "z-ai/glm-5", + "name": "GLM 5", + "display_name": "GLM 5", + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 200000, + "output": 128000 }, "temperature": true, "tool_call": true, @@ -60825,33 +61664,54 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "budget", - "budget": { - "default": -1, - "min": 128, - "max": 32768, - "auto": -1, - "unit": "tokens" - }, + "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ - "thought_signatures" + "thinking_blocks" ] } }, + "attachment": false, + "open_weights": true, + "release_date": "2026-02-12", + "last_updated": "2026-02-12", + "type": "chat" + }, + { + "id": "z-ai/glm-5v-turbo:thinking", + "name": "GLM 5V Turbo Thinking", + "display_name": "GLM 5V Turbo Thinking", + "modalities": { + "input": [ + "text", + "image", + "video" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 202800, + "output": 131072 + }, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": true, + "default": true + }, "attachment": true, "open_weights": false, - "knowledge": "2025-01", - "release_date": "2025-06-17", - "last_updated": "2025-06-17", + "release_date": "2026-04-01", + "last_updated": "2026-04-01", "type": "chat" }, { - "id": "gpt-oss-20b", - "name": "GPT OSS 20B", - "display_name": "GPT OSS 20B", + "id": "z-ai/GLM-4.5-Air", + "name": "GLM 4.5 Air", + "display_name": "GLM 4.5 Air", "modalities": { "input": [ "text" @@ -60861,8 +61721,36 @@ ] }, "limit": { - "context": 131072, - "output": 32768 + "context": 128000, + "output": 98304 + }, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": false + }, + "attachment": false, + "open_weights": true, + "knowledge": "2025-04", + "release_date": "2025-07-28", + "last_updated": "2025-07-28", + "type": "chat" + }, + { + "id": "z-ai/glm-4.7-flash", + "name": "GLM 4.7 Flash", + "display_name": "GLM 4.7 Flash", + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 200000, + "output": 128000 }, "temperature": true, "tool_call": true, @@ -60872,25 +61760,20 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "effort_options": [ - "low", - "medium", - "high" - ] + "supported": true } }, "attachment": false, "open_weights": true, - "knowledge": "2024-06", - "release_date": "2025-08-05", - "last_updated": "2025-08-05", + "knowledge": "2025-04", + "release_date": "2026-01-19", + "last_updated": "2026-01-19", "type": "chat" }, { - "id": "deepseek-v4-pro", - "name": "DeepSeek v4 Pro", - "display_name": "DeepSeek v4 Pro", + "id": "z-ai/glm-4.7-flash:thinking", + "name": "GLM 4.7 Flash Thinking", + "display_name": "GLM 4.7 Flash Thinking", "modalities": { "input": [ "text" @@ -60900,8 +61783,8 @@ ] }, "limit": { - "context": 128000, - "output": 8192 + "context": 200000, + "output": 128000 }, "temperature": true, "tool_call": true, @@ -60909,39 +61792,54 @@ "supported": true, "default": true }, + "attachment": false, + "open_weights": true, + "knowledge": "2025-04", + "release_date": "2026-01-19", + "last_updated": "2026-01-19", + "type": "chat" + }, + { + "id": "z-ai/glm-5.3-flash-apex", + "name": "Apex Flash 1", + "display_name": "Apex Flash 1", + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1048576, + "output": 32768 + }, + "tool_call": true, + "reasoning": { + "supported": true, + "default": true + }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "high", "effort_options": [ "low", "high", "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." ] } }, - "attachment": false, + "attachment": true, "open_weights": true, - "knowledge": "2026-01", - "release_date": "2026-04-24", - "last_updated": "2026-04-24", + "release_date": "2026-10-05", + "last_updated": "2026-10-05", "type": "chat" }, { - "id": "minimax-m2-5", - "name": "MiniMax-M2.5", - "display_name": "MiniMax-M2.5", + "id": "z-ai/GLM-4.5:thinking", + "name": "GLM 4.5 (Thinking)", + "display_name": "GLM 4.5 (Thinking)", "modalities": { "input": [ "text" @@ -60951,30 +61849,29 @@ ] }, "limit": { - "context": 192000, - "output": 8192 + "context": 128000, + "output": 65536 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": false + "supported": true, + "default": true }, "attachment": false, "open_weights": true, - "knowledge": "2024-09", - "release_date": "2026-02-12", - "last_updated": "2026-02-12", + "knowledge": "2025-04", + "release_date": "2025-07-28", + "last_updated": "2025-07-28", "type": "chat" }, { - "id": "claude-opus-4-7", - "name": "Claude Opus 4.7", - "display_name": "Claude Opus 4.7", + "id": "z-ai/glm-5.1", + "name": "GLM 5.1", + "display_name": "GLM 5.1", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" @@ -60982,55 +61879,36 @@ }, "limit": { "context": 200000, - "output": 128000 + "output": 131072 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "high", "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "omitted", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Claude Opus 4.7 and newer Opus models require thinking.type = \"adaptive\" to enable thinking explicitly.", - "Manual budget_tokens requests return 400 on Claude Opus 4.7 and newer adaptive-only Opus models.", - "task_budget is separate from thinking control and should not be treated as a thinking budget." + "none", + "high" ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-01-31", - "release_date": "2026-04-16", - "last_updated": "2026-04-16", + "attachment": false, + "open_weights": true, + "release_date": "2026-04-07", + "last_updated": "2026-04-07", "type": "chat" }, { - "id": "gpt-4o", - "name": "GPT-4o", - "display_name": "GPT-4o", + "id": "z-ai/GLM-4.5-Air:thinking", + "name": "GLM 4.5 Air (Thinking)", + "display_name": "GLM 4.5 Air (Thinking)", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" @@ -61038,36 +61916,38 @@ }, "limit": { "context": 128000, - "output": 16384 + "output": 98304 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": true, - "open_weights": false, - "knowledge": "2023-09", - "release_date": "2024-05-13", - "last_updated": "2024-08-06", + "attachment": false, + "open_weights": true, + "knowledge": "2025-04", + "release_date": "2025-07-28", + "last_updated": "2025-07-28", "type": "chat" }, { - "id": "grok-4-1-fast-non-reasoning", - "name": "Grok 4.1 Fast (Non-Reasoning)", - "display_name": "Grok 4.1 Fast (Non-Reasoning)", + "id": "z-ai/glm-5v-turbo", + "name": "GLM 5V Turbo", + "display_name": "GLM 5V Turbo", "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 2000000, - "output": 128000 + "context": 202800, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -61076,27 +61956,25 @@ }, "attachment": true, "open_weights": false, - "knowledge": "2025-11", - "release_date": "2025-11-19", - "last_updated": "2025-11-19", + "release_date": "2026-04-01", + "last_updated": "2026-04-01", "type": "chat" }, { - "id": "kimi-k2.5", - "name": "Kimi-K2.5", - "display_name": "Kimi-K2.5", + "id": "z-ai/glm-5.2", + "name": "GLM 5.2", + "display_name": "GLM 5.2", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 256000, - "output": 128000 + "context": 1048576, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -61107,32 +61985,27 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "none", + "high", + "max" ] } }, - "attachment": true, + "attachment": false, "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-01-27", - "last_updated": "2026-01-27", + "release_date": "2026-06-13", + "last_updated": "2026-06-13", "type": "chat" }, { - "id": "gemini-2.5-flash", - "name": "Gemini 2.5 Flash", - "display_name": "Gemini 2.5 Flash", + "id": "z-ai/glm-5.3-flash-cybersecurity", + "name": "GLM 5.3 Flash Cybersecurity", + "display_name": "GLM 5.3 Flash Cybersecurity", "modalities": { "input": [ "text", - "image", - "audio", - "video", - "pdf" + "image" ], "output": [ "text" @@ -61140,9 +62013,8 @@ }, "limit": { "context": 1048576, - "output": 65536 + "output": 32768 }, - "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -61150,35 +62022,23 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "budget", - "budget": { - "default": -1, - "min": 0, - "max": 24576, - "auto": -1, - "off": 0, - "unit": "tokens" - }, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thought_signatures" + "effort_options": [ + "low", + "high", + "max" ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2025-01", - "release_date": "2025-06-17", - "last_updated": "2025-06-17", + "open_weights": true, + "release_date": "2026-09-19", + "last_updated": "2026-09-19", "type": "chat" }, { - "id": "grok-code-fast-1", - "name": "Grok 4.1 Fast (Reasoning)", - "display_name": "Grok 4.1 Fast (Reasoning)", + "id": "z-ai/glm-5-turbo", + "name": "GLM 5 Turbo", + "display_name": "GLM 5 Turbo", "modalities": { "input": [ "text" @@ -61188,14 +62048,13 @@ ] }, "limit": { - "context": 256000, - "output": 128000 + "context": 202800, + "output": 131072 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true + "supported": true }, "extra_capabilities": { "reasoning": { @@ -61204,20 +62063,17 @@ }, "attachment": false, "open_weights": false, - "knowledge": "2023-10", - "release_date": "2025-09-02", - "last_updated": "2025-09-02", + "release_date": "2026-03-16", + "last_updated": "2026-03-16", "type": "chat" }, { - "id": "claude-sonnet-4-6", - "name": "Claude Sonnet 4.6", - "display_name": "Claude Sonnet 4.6", + "id": "z-ai/glm-5:thinking", + "name": "GLM 5 Thinking", + "display_name": "GLM 5 Thinking", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" @@ -61225,53 +62081,24 @@ }, "limit": { "context": 200000, - "output": 64000 + "output": 128000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "mixed", - "budget": { - "min": 1024, - "unit": "tokens" - }, - "effort": "high", - "effort_options": [ - "low", - "medium", - "high", - "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Anthropic recommends adaptive thinking with effort for Claude 4.6; budget_tokens remains a deprecated compatibility path.", - "Anthropic API defaults effort to high; lower effort levels should be chosen per workload." - ] - } + "default": true }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-02-17", - "last_updated": "2026-03-13", + "attachment": false, + "open_weights": true, + "release_date": "2026-02-12", + "last_updated": "2026-02-12", "type": "chat" }, { - "id": "gpt-oss-120b", - "name": "GPT OSS 120B", - "display_name": "GPT OSS 120B", + "id": "z-ai/glm-4.5", + "name": "GLM 4.5", + "display_name": "GLM 4.5", "modalities": { "input": [ "text" @@ -61281,36 +62108,78 @@ ] }, "limit": { - "context": 131072, - "output": 32768 + "context": 128000, + "output": 65536 }, "temperature": true, + "tool_call": false, + "reasoning": { + "supported": false + }, + "attachment": false, + "open_weights": true, + "knowledge": "2025-04", + "release_date": "2025-07-28", + "last_updated": "2025-07-28", + "type": "chat" + }, + { + "id": "z-ai/GLM-4.6-turbo", + "name": "GLM 4.6 Turbo", + "display_name": "GLM 4.6 Turbo", + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 204800, + "output": 131072 + }, + "tool_call": false, + "reasoning": { + "supported": false + }, + "attachment": false, + "open_weights": true, + "release_date": "2025-10-02", + "last_updated": "2025-10-02", + "type": "chat" + }, + { + "id": "z-ai/glm-latest", + "name": "GLM Latest", + "display_name": "GLM Latest", + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1048576, + "output": 131072 + }, "tool_call": true, "reasoning": { "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "effort_options": [ - "low", - "medium", - "high" - ] - } - }, "attachment": false, "open_weights": true, - "knowledge": "2024-06", - "release_date": "2025-08-05", - "last_updated": "2025-08-05", + "release_date": "2026-05-03", + "last_updated": "2026-05-03", "type": "chat" }, { - "id": "gpt-5-3-codex", - "name": "GPT-5.3 Codex", - "display_name": "GPT-5.3 Codex", + "id": "abliteration-ai/abliterated-model-large-flash", + "name": "Abliterated Large Flash", + "display_name": "Abliterated Large Flash", "modalities": { "input": [ "text", @@ -61321,10 +62190,9 @@ ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 1048576, + "output": 262144 }, - "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -61332,51 +62200,39 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", "effort_options": [ + "none", + "minimal", "low", "medium", "high", - "xhigh" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + "xhigh", + "max" + ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-01-31", - "release_date": "2026-02-05", - "last_updated": "2026-02-05", + "release_date": "2026-10-23", + "last_updated": "2026-10-23", "type": "chat" }, { - "id": "grok-4-3", - "name": "Grok 4.3", - "display_name": "Grok 4.3", + "id": "abliteration-ai/abliterated-model-large-v2", + "name": "Abliterated Model Large V2", + "display_name": "Abliterated Model Large V2", "modalities": { "input": [ - "text", - "image", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 1048576, + "output": 999990 }, - "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -61386,40 +62242,35 @@ "reasoning": { "effort_options": [ "low", - "medium", - "high" + "high", + "max" ] } }, - "attachment": true, + "attachment": false, "open_weights": false, - "knowledge": "2024-11", - "release_date": "2026-04-17", - "last_updated": "2026-04-17", + "release_date": "2026-08-31", + "last_updated": "2026-08-31", "type": "chat" }, { - "id": "gemini-3-1-pro-preview", - "name": "Gemini 3.1 Pro Preview", - "display_name": "Gemini 3.1 Pro Preview", + "id": "abliteration-ai/abliterated-model", + "name": "Abliterated Model", + "display_name": "Abliterated Model", "modalities": { "input": [ "text", - "image", - "video", - "audio", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 64000 + "context": 262144, + "output": 262134 }, - "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { "supported": true, "default": true @@ -61427,134 +62278,103 @@ "extra_capabilities": { "reasoning": { "effort_options": [ + "none", + "minimal", "low", "medium", - "high" + "high", + "xhigh", + "max" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-01", - "release_date": "2026-02-19", - "last_updated": "2026-02-19", + "release_date": "2026-08-27", + "last_updated": "2026-08-27", "type": "chat" }, { - "id": "claude-opus-4-6", - "name": "Claude Opus 4.6", - "display_name": "Claude Opus 4.6", + "id": "abliteration-ai/abliterated-model-large", + "name": "Abliterated Model Large", + "display_name": "Abliterated Model Large", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 128000 + "context": 1000000, + "output": 999990 }, - "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "mixed", - "budget": { - "min": 1024, - "unit": "tokens" - }, - "effort": "high", "effort_options": [ + "none", + "minimal", "low", "medium", "high", + "xhigh", "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Anthropic recommends adaptive thinking with effort for Claude 4.6; budget_tokens remains a deprecated compatibility path.", - "Anthropic API defaults effort to high; lower effort levels should be chosen per workload." ] } }, - "attachment": true, + "attachment": false, "open_weights": false, - "knowledge": "2025-05-31", - "release_date": "2026-02-05", - "last_updated": "2026-03-13", + "release_date": "2026-08-27", + "last_updated": "2026-08-27", "type": "chat" }, { - "id": "claude-haiku-4-5", - "name": "Claude Haiku 4.5", - "display_name": "Claude Haiku 4.5", + "id": "heabsy/cyberheabsy", + "name": "CyberHeabsy", + "display_name": "CyberHeabsy", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 64000 + "context": 1048576, + "output": 65536 }, - "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "budget", - "budget": { - "min": 1024, - "unit": "tokens" - }, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", - "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." + "effort_options": [ + "none", + "low", + "medium", + "high" ] } }, - "attachment": true, + "attachment": false, "open_weights": false, - "knowledge": "2025-02-28", - "release_date": "2025-10-15", - "last_updated": "2025-10-15", + "release_date": "2026-09-27", + "last_updated": "2026-09-27", "type": "chat" }, { - "id": "gpt-5-5", - "name": "GPT-5.5", - "display_name": "GPT-5.5", + "id": "heabsy/qwen3.8-27b-uncensored-fast", + "name": "Qwen 3.8 27B Uncensored Fast by Heabsy", + "display_name": "Qwen 3.8 27B Uncensored Fast by Heabsy", "modalities": { "input": [ "text", @@ -61565,10 +62385,9 @@ ] }, "limit": { - "context": 272000, - "output": 128000 + "context": 524288, + "output": 235929 }, - "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -61576,36 +62395,22 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", "effort_options": [ - "low", - "medium", - "high", - "xhigh" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", + "none", "high" - ], - "visibility": "hidden" + ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-04-23", - "last_updated": "2026-04-23", + "release_date": "2026-10-16", + "last_updated": "2026-10-16", "type": "chat" }, { - "id": "minimax-m2-7", - "name": "MiniMax-M2.7", - "display_name": "MiniMax-M2.7", + "id": "stepfun-ai/step-3.5-flash-2603", + "name": "Step 3.5 Flash 2603", + "display_name": "Step 3.5 Flash 2603", "modalities": { "input": [ "text" @@ -61615,56 +62420,73 @@ ] }, "limit": { - "context": 192000, - "output": 8192 + "context": 262144, + "output": 65536 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "low", + "medium", + "high" + ] + } }, "attachment": false, "open_weights": true, - "knowledge": "2024-09", - "release_date": "2026-03-18", - "last_updated": "2026-03-18", + "knowledge": "2025-01", + "release_date": "2026-04-02", + "last_updated": "2026-04-02", "type": "chat" }, { - "id": "qwen-3-6-plus", - "name": "Qwen 3.6 Plus", - "display_name": "Qwen 3.6 Plus", + "id": "stepfun-ai/step-3.5-flash", + "name": "Step 3.5 Flash", + "display_name": "Step 3.5 Flash", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 64000 + "context": 262144, + "output": 65536 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { "supported": true, "default": true }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-04", - "release_date": "2026-04-02", - "last_updated": "2026-04-02", + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "low", + "medium", + "high" + ] + } + }, + "attachment": false, + "open_weights": true, + "knowledge": "2025-01", + "release_date": "2026-01-29", + "last_updated": "2026-02-13", "type": "chat" }, { - "id": "zai-glm-5-1", - "name": "Z.AI GLM-5.1", - "display_name": "Z.AI GLM-5.1", + "id": "inception/mercury-3", + "name": "Mercury 3", + "display_name": "Mercury 3", "modalities": { "input": [ "text" @@ -61674,77 +62496,73 @@ ] }, "limit": { - "context": 198000, - "output": 8192 + "context": 1000000, + "output": 131072 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "none", + "low", + "medium", + "high" + ] + } }, "attachment": false, - "open_weights": true, - "knowledge": "2024-10", - "release_date": "2026-04-07", - "last_updated": "2026-04-07", + "open_weights": false, + "release_date": "2026-10-16", + "last_updated": "2026-10-20", "type": "chat" }, { - "id": "gpt-5-4-mini", - "name": "GPT-5.4 Mini", - "display_name": "GPT-5.4 Mini", + "id": "inception/mercury-2.5-preview", + "name": "Mercury 2.5 Preview", + "display_name": "Mercury 2.5 Preview", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 260000, + "output": 65536 }, - "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "none", "effort_options": [ "none", - "low", - "medium", - "high", - "xhigh" - ], - "verbosity": "medium", - "verbosity_options": [ "low", "medium", "high" - ], - "visibility": "hidden" + ] } }, - "attachment": true, + "attachment": false, "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-03-17", - "last_updated": "2026-03-17", + "release_date": "2026-09-01", + "last_updated": "2026-09-01", "type": "chat" }, { - "id": "kimi-k2-6", - "name": "Kimi-K2.6", - "display_name": "Kimi-K2.6", + "id": "TEE/qwen2.5-vl-72b-instruct", + "name": "Qwen2.5 VL 72B TEE", + "display_name": "Qwen2.5 VL 72B TEE", "modalities": { "input": [ "text", @@ -61755,26 +62573,25 @@ ] }, "limit": { - "context": 256000, - "output": 128000 + "context": 65536, + "output": 8192 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true + "supported": false }, "attachment": true, "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-04-20", - "last_updated": "2026-04-20", + "knowledge": "2024-04", + "release_date": "2025-01-26", + "last_updated": "2025-01-26", "type": "chat" }, { - "id": "gpt-5-4-nano", - "name": "GPT-5.4 Nano", - "display_name": "GPT-5.4 Nano", + "id": "TEE/kimi-k3", + "name": "Kimi K3 TEE", + "display_name": "Kimi K3 TEE", "modalities": { "input": [ "text", @@ -61785,85 +62602,64 @@ ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 1048576, + "output": 65535 }, "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "none", "effort_options": [ - "none", "low", - "medium", "high", - "xhigh" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + "max" + ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2024-05-30", - "release_date": "2026-03-17", - "last_updated": "2026-03-17", + "open_weights": true, + "release_date": "2026-07-16", + "last_updated": "2026-07-16", "type": "chat" - } - ] - }, - "ai21": { - "id": "ai21", - "name": "AI21 Labs", - "display_name": "AI21 Labs", - "api": "https://api.ai21.com/studio/v1", - "doc": "https://docs.ai21.com/docs/jamba-foundation-models", - "models": [ + }, { - "id": "jamba-large", - "name": "Jamba Large", - "display_name": "Jamba Large", + "id": "TEE/gemma4-31b", + "name": "Gemma 4 31B", + "display_name": "Gemma 4 31B", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 256000, - "output": 4096 + "context": 262144, + "output": 131072 }, "temperature": true, "tool_call": true, "reasoning": { "supported": false }, - "attachment": false, + "attachment": true, "open_weights": true, - "knowledge": "2024-08-22", - "release_date": "2025-07-03", - "last_updated": "2025-07-03", + "knowledge": "2025-01", + "release_date": "2026-04-02", + "last_updated": "2026-04-02", "type": "chat" }, { - "id": "jamba-mini", - "name": "Jamba Mini", - "display_name": "Jamba Mini", + "id": "TEE/nemotron-3.5-lightning", + "name": "Nvidia Nemotron 3.5 Lightning TEE", + "display_name": "Nvidia Nemotron 3.5 Lightning TEE", "modalities": { "input": [ "text" @@ -61873,34 +62669,37 @@ ] }, "limit": { - "context": 256000, - "output": 4096 + "context": 262144, + "output": 65536 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "effort_options": [ + "none", + "low", + "medium", + "high" + ] + } }, "attachment": false, "open_weights": true, - "knowledge": "2024-08-22", - "release_date": "2026-01-08", - "last_updated": "2026-01-08", + "knowledge": "2025-09", + "release_date": "2026-08-11", + "last_updated": "2026-08-11", "type": "chat" - } - ] - }, - "tinfoil": { - "id": "tinfoil", - "name": "Tinfoil", - "display_name": "Tinfoil", - "api": "https://inference.tinfoil.sh/v1", - "doc": "https://docs.tinfoil.sh", - "models": [ + }, { - "id": "kimi-k3", - "name": "Kimi K3", - "display_name": "Kimi K3", + "id": "TEE/qwen3.5-27b", + "name": "Qwen3.5 27B TEE", + "display_name": "Qwen3.5 27B TEE", "modalities": { "input": [ "text", @@ -61913,34 +62712,34 @@ }, "limit": { "context": 262144, - "output": 131072 + "output": 65536 }, - "temperature": false, - "tool_call": true, + "temperature": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true + "supported": true }, "extra_capabilities": { "reasoning": { "supported": true, - "effort_options": [ - "low", - "high", - "max" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, "attachment": true, "open_weights": true, - "release_date": "2026-07-16", - "last_updated": "2026-07-16", + "release_date": "2026-02-23", + "last_updated": "2026-02-23", "type": "chat" }, { - "id": "deepseek-v4-1-flash", - "name": "DeepSeek V4.1 Flash", - "display_name": "DeepSeek V4.1 Flash", + "id": "TEE/kimi-k2.7-code", + "name": "Kimi K2.7 Code TEE", + "display_name": "Kimi K2.7 Code TEE", "modalities": { "input": [ "text", @@ -61951,10 +62750,10 @@ ] }, "limit": { - "context": 1048576, - "output": 384000 + "context": 262144, + "output": 65536 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -61962,28 +62761,20 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "none", - "minimal", - "low", - "medium", - "high", - "xhigh", - "max" - ] + "supported": true } }, "attachment": true, "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-09-10", - "last_updated": "2026-09-10", + "knowledge": "2025-01", + "release_date": "2026-06-12", + "last_updated": "2026-06-12", "type": "chat" }, { - "id": "glm-5-3", - "name": "GLM-5.3", - "display_name": "GLM-5.3", + "id": "TEE/qwen3.6-35b-a3b", + "name": "Qwen3.6 35B A3B TEE", + "display_name": "Qwen3.6 35B A3B TEE", "modalities": { "input": [ "text" @@ -61992,6 +62783,45 @@ "text" ] }, + "limit": { + "context": 262144, + "output": 262144 + }, + "temperature": true, + "tool_call": false, + "reasoning": { + "supported": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } + }, + "attachment": false, + "open_weights": true, + "release_date": "2026-04-17", + "last_updated": "2026-04-17", + "type": "chat" + }, + { + "id": "TEE/glm-5.3-flash", + "name": "GLM 5.3 Flash TEE", + "display_name": "GLM 5.3 Flash TEE", + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, "limit": { "context": 1048576, "output": 131072 @@ -62004,6 +62834,7 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, "effort_options": [ "low", "high", @@ -62011,16 +62842,16 @@ ] } }, - "attachment": false, + "attachment": true, "open_weights": true, - "release_date": "2026-08-14", - "last_updated": "2026-08-14", + "release_date": "2026-08-26", + "last_updated": "2026-08-26", "type": "chat" }, { - "id": "gpt-oss-120b", - "name": "gpt-oss-120b", - "display_name": "gpt-oss-120b", + "id": "TEE/deepseek-v3.2", + "name": "DeepSeek V3.2 TEE", + "display_name": "DeepSeek V3.2 TEE", "modalities": { "input": [ "text" @@ -62030,36 +62861,30 @@ ] }, "limit": { - "context": 131072, - "output": 32768 + "context": 164000, + "output": 65536 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true + "supported": true }, "extra_capabilities": { "reasoning": { - "supported": true, - "effort_options": [ - "low", - "medium", - "high" - ] + "supported": true } }, "attachment": false, "open_weights": true, - "knowledge": "2024-06", - "release_date": "2025-08-05", - "last_updated": "2025-08-05", + "knowledge": "2024-07", + "release_date": "2025-12-01", + "last_updated": "2025-12-01", "type": "chat" }, { - "id": "gemma4-31b", - "name": "Gemma 4 31B IT", - "display_name": "Gemma 4 31B IT", + "id": "TEE/deepseek-v4.1-flash", + "name": "DeepSeek V4.1 Flash TEE", + "display_name": "DeepSeek V4.1 Flash TEE", "modalities": { "input": [ "text", @@ -62070,8 +62895,8 @@ ] }, "limit": { - "context": 262144, - "output": 32768 + "context": 1048576, + "output": 384000 }, "temperature": true, "tool_call": true, @@ -62081,28 +62906,27 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, "effort_options": [ "none", - "minimal", "low", "medium", "high", - "xhigh", "max" ] } }, "attachment": true, "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-04-02", - "last_updated": "2026-04-02", + "knowledge": "2025-05", + "release_date": "2026-09-10", + "last_updated": "2026-09-10", "type": "chat" }, { - "id": "nomic-embed-text", - "name": "Nomic Embed Text v1.5", - "display_name": "Nomic Embed Text v1.5", + "id": "TEE/gpt-oss-120b", + "name": "GPT-OSS 120B TEE", + "display_name": "GPT-OSS 120B TEE", "modalities": { "input": [ "text" @@ -62112,52 +62936,60 @@ ] }, "limit": { - "context": 8192, - "output": 768 + "context": 131072, + "output": 16384 }, - "temperature": false, + "temperature": true, "tool_call": false, "reasoning": { - "supported": false + "supported": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true + } }, "attachment": false, "open_weights": true, - "release_date": "2024-02", - "last_updated": "2024-02", + "knowledge": "2024-06", + "release_date": "2025-08-05", + "last_updated": "2025-08-05", "type": "chat" }, { - "id": "gpt-oss-safeguard-120b", - "name": "gpt-oss-safeguard-120b", - "display_name": "gpt-oss-safeguard-120b", + "id": "TEE/qwen3.8-27b", + "name": "Qwen3.8 27B TEE", + "display_name": "Qwen3.8 27B TEE", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 32768 + "context": 262144, + "output": 262144 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, + "attachment": true, "open_weights": true, - "knowledge": "2024-06", - "release_date": "2025-10-29", - "last_updated": "2025-10-29", + "release_date": "2026-08-14", + "last_updated": "2026-08-14", "type": "chat" }, { - "id": "llama3-3-70b", - "name": "Llama-3.3-70B-Instruct", - "display_name": "Llama-3.3-70B-Instruct", + "id": "TEE/glm-5.2:thinking", + "name": "GLM 5.2 Thinking TEE", + "display_name": "GLM 5.2 Thinking TEE", "modalities": { "input": [ "text" @@ -62167,34 +62999,36 @@ ] }, "limit": { - "context": 131072, - "output": 4096 + "context": 1048576, + "output": 131072 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" + ] + } }, "attachment": false, "open_weights": true, - "knowledge": "2023-12", - "release_date": "2024-12-06", - "last_updated": "2024-12-06", + "release_date": "2026-06-13", + "last_updated": "2026-06-13", "type": "chat" - } - ] - }, - "merge-gateway": { - "id": "merge-gateway", - "name": "Merge Gateway", - "display_name": "Merge Gateway", - "api": "https://api-gateway.merge.dev/v1/ai-sdk", - "doc": "https://docs.merge.dev/merge-gateway", - "models": [ + }, { - "id": "bytedance/dola-seed-2.0-lite", - "name": "Seed 2.0 Lite", - "display_name": "Seed 2.0 Lite", + "id": "TEE/qwen3.6-27b", + "name": "Qwen3.6 27B TEE", + "display_name": "Qwen3.6 27B TEE", "modalities": { "input": [ "text", @@ -62205,52 +63039,72 @@ ] }, "limit": { - "context": 256000, - "output": 128000 + "context": 262144, + "output": 65536 }, "temperature": true, "tool_call": false, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } }, "attachment": true, - "open_weights": false, - "release_date": "2026-02-14", - "last_updated": "2026-02-14", + "open_weights": true, + "release_date": "2026-04-22", + "last_updated": "2026-04-22", "type": "chat" }, { - "id": "bytedance/dola-seed-2.0-code", - "name": "Seed 2.0 Code", - "display_name": "Seed 2.0 Code", + "id": "TEE/glm-5.1-thinking", + "name": "GLM 5.1 Thinking TEE", + "display_name": "GLM 5.1 Thinking TEE", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 256000, - "output": 128000 + "context": 202752, + "output": 65535 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": true, - "open_weights": false, - "release_date": "2026-02-14", - "last_updated": "2026-02-14", + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "none", + "high" + ] + } + }, + "attachment": false, + "open_weights": true, + "release_date": "2026-04-07", + "last_updated": "2026-04-07", "type": "chat" }, { - "id": "bytedance/dola-seed-2.0-code-preview", - "name": "Dola Seed 2.0 Code (preview)", - "display_name": "Dola Seed 2.0 Code (preview)", + "id": "TEE/glm-5.3", + "name": "GLM 5.3 TEE", + "display_name": "GLM 5.3 TEE", "modalities": { "input": [ "text" @@ -62260,24 +63114,35 @@ ] }, "limit": { - "context": 131072, - "output": 32768 + "context": 1048576, + "output": 131072 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "effort_options": [ + "low", + "high", + "max" + ] + } }, "attachment": false, - "open_weights": false, - "release_date": "2026-03-28", - "last_updated": "2026-03-28", + "open_weights": true, + "release_date": "2026-08-14", + "last_updated": "2026-08-14", "type": "chat" }, { - "id": "bytedance/dola-seed-2.0-pro", - "name": "Seed 2.0 Pro", - "display_name": "Seed 2.0 Pro", + "id": "TEE/gemma4-31b:thinking", + "name": "Gemma 4 31B Thinking TEE", + "display_name": "Gemma 4 31B Thinking TEE", "modalities": { "input": [ "text", @@ -62288,53 +63153,54 @@ ] }, "limit": { - "context": 256000, - "output": 128000 + "context": 262144, + "output": 131072 }, - "temperature": false, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { "supported": true, "default": true }, "attachment": true, - "open_weights": false, - "release_date": "2026-02-14", - "last_updated": "2026-02-14", + "open_weights": true, + "knowledge": "2025-01", + "release_date": "2026-04-02", + "last_updated": "2026-04-02", "type": "chat" }, { - "id": "bytedance/dola-seed-2.0-mini", - "name": "Seed 2.0 Mini", - "display_name": "Seed 2.0 Mini", + "id": "TEE/llama3-3-70b", + "name": "Llama 3.3 70B", + "display_name": "Llama 3.3 70B", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 256000, - "output": 128000 + "context": 128000, + "output": 16384 }, "temperature": true, "tool_call": false, "reasoning": { "supported": false }, - "attachment": true, - "open_weights": false, - "release_date": "2026-02-14", - "last_updated": "2026-02-14", + "attachment": false, + "open_weights": true, + "knowledge": "2023-12", + "release_date": "2024-12-06", + "last_updated": "2024-12-06", "type": "chat" }, { - "id": "minimax/minimax-m2.1", - "name": "MiniMax M2.1", - "display_name": "MiniMax M2.1", + "id": "TEE/glm-5.1", + "name": "GLM 5.1 TEE", + "display_name": "GLM 5.1 TEE", "modalities": { "input": [ "text" @@ -62344,8 +63210,8 @@ ] }, "limit": { - "context": 204800, - "output": 8192 + "context": 202752, + "output": 65535 }, "temperature": true, "tool_call": true, @@ -62355,65 +63221,34 @@ }, "extra_capabilities": { "reasoning": { - "supported": true + "supported": true, + "effort_options": [ + "none", + "high" + ] } }, "attachment": false, "open_weights": true, - "release_date": "2025-12-23", - "last_updated": "2025-12-23", + "release_date": "2026-04-07", + "last_updated": "2026-04-07", "type": "chat" }, { - "id": "minimax/minimax-m3", - "name": "MiniMax M3", - "display_name": "MiniMax M3", + "id": "TEE/gemma-4-31b-it", + "name": "Gemma 4 31B IT TEE", + "display_name": "Gemma 4 31B IT TEE", "modalities": { "input": [ - "text", - "image" - ], - "output": [ "text" - ] - }, - "limit": { - "context": 1000000, - "output": 128000 - }, - "temperature": true, - "tool_call": true, - "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true - } - }, - "attachment": true, - "open_weights": true, - "release_date": "2026-06-01", - "last_updated": "2026-06-01", - "type": "chat" - }, - { - "id": "minimax/minimax-m3.1-flash", - "name": "MiniMax M3.1 Flash", - "display_name": "MiniMax M3.1 Flash", - "modalities": { - "input": [ - "text", - "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 262144, + "output": 262144 }, "temperature": true, "tool_call": true, @@ -62423,53 +63258,20 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" - ] + "supported": true } }, - "attachment": true, - "open_weights": false, - "release_date": "2026-10-19", - "last_updated": "2026-10-19", - "type": "chat" - }, - { - "id": "minimax/minimax-m2", - "name": "MiniMax M2", - "display_name": "MiniMax M2", - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 204800, - "output": 8192 - }, - "temperature": true, - "tool_call": true, - "reasoning": { - "supported": true, - "default": true - }, "attachment": false, "open_weights": true, - "release_date": "2025-10-27", - "last_updated": "2025-10-27", + "knowledge": "2025-01", + "release_date": "2026-04-02", + "last_updated": "2026-04-02", "type": "chat" }, { - "id": "minimax/minimax-m2.5", - "name": "MiniMax M2.5", - "display_name": "MiniMax M2.5", + "id": "TEE/glm-5.2", + "name": "GLM 5.2 TEE", + "display_name": "GLM 5.2 TEE", "modalities": { "input": [ "text" @@ -62479,8 +63281,8 @@ ] }, "limit": { - "context": 204800, - "output": 8192 + "context": 1048576, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -62495,14 +63297,14 @@ }, "attachment": false, "open_weights": true, - "release_date": "2026-02-12", - "last_updated": "2026-02-12", + "release_date": "2026-06-13", + "last_updated": "2026-06-13", "type": "chat" }, { - "id": "minimax/minimax-m2.7", - "name": "MiniMax M2.7", - "display_name": "MiniMax M2.7", + "id": "TEE/qwen3.5-397b-a17b", + "name": "Qwen3.5 397B A17B TEE", + "display_name": "Qwen3.5 397B A17B TEE", "modalities": { "input": [ "text" @@ -62512,14 +63314,13 @@ ] }, "limit": { - "context": 204800, - "output": 8192 + "context": 262144, + "output": 65536 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true + "supported": true }, "extra_capabilities": { "reasoning": { @@ -62534,14 +63335,14 @@ }, "attachment": false, "open_weights": true, - "release_date": "2026-03-18", - "last_updated": "2026-03-18", + "release_date": "2026-02-15", + "last_updated": "2026-02-15", "type": "chat" }, { - "id": "minimax/minimax-m2.7-highspeed", - "name": "MiniMax M2.7 Highspeed", - "display_name": "MiniMax M2.7 Highspeed", + "id": "TEE/gemma-4-26b-a4b-uncensored", + "name": "Gemma 4 26B A4B Uncensored TEE", + "display_name": "Gemma 4 26B A4B Uncensored TEE", "modalities": { "input": [ "text" @@ -62551,69 +63352,57 @@ ] }, "limit": { - "context": 204800, - "output": 8192 + "context": 65536, + "output": 65536 }, - "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } + "supported": false }, "attachment": false, "open_weights": true, - "release_date": "2026-03-18", - "last_updated": "2026-03-18", + "release_date": "2026-05-23", + "last_updated": "2026-05-23", "type": "chat" }, { - "id": "minimax/minimax-m2.5-highspeed", - "name": "MiniMax M2.5 Highspeed", - "display_name": "MiniMax M2.5 Highspeed", + "id": "TEE/kimi-k2.6", + "name": "Kimi K2.6 TEE", + "display_name": "Kimi K2.6 TEE", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 204800, - "output": 8192 + "context": 262144, + "output": 65536 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true + "supported": true }, "extra_capabilities": { "reasoning": { "supported": true } }, - "attachment": false, + "attachment": true, "open_weights": true, - "release_date": "2026-02-13", - "last_updated": "2026-02-13", + "knowledge": "2025-01", + "release_date": "2026-04-20", + "last_updated": "2026-04-20", "type": "chat" }, { - "id": "writer/palmyra-x4", - "name": "Palmyra X4", - "display_name": "Palmyra X4", + "id": "meta-llama/llama-3.1-8b-instruct", + "name": "Llama 3.1 8b Instruct", + "display_name": "Llama 3.1 8b Instruct", "modalities": { "input": [ "text" @@ -62623,8 +63412,8 @@ ] }, "limit": { - "context": 128000, - "output": 32000 + "context": 131072, + "output": 16384 }, "temperature": true, "tool_call": true, @@ -62632,42 +63421,45 @@ "supported": false }, "attachment": false, - "open_weights": false, - "release_date": "2024-10-09", - "last_updated": "2025-04-28", + "open_weights": true, + "knowledge": "2023-12", + "release_date": "2024-07-23", + "last_updated": "2024-07-23", "type": "chat" }, { - "id": "writer/palmyra-x5", - "name": "Palmyra X5", - "display_name": "Palmyra X5", + "id": "meta-llama/llama-4-scout", + "name": "Llama 4 Scout", + "display_name": "Llama 4 Scout", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 250000 + "context": 328000, + "output": 65536 }, "temperature": true, "tool_call": true, "reasoning": { "supported": false }, - "attachment": false, - "open_weights": false, - "release_date": "2025-04-28", - "last_updated": "2025-04-28", + "attachment": true, + "open_weights": true, + "knowledge": "2024-08", + "release_date": "2025-04-05", + "last_updated": "2025-04-05", "type": "chat" }, { - "id": "zai/glm-4.7-flashx", - "name": "GLM-4.7 FlashX", - "display_name": "GLM-4.7 FlashX", + "id": "meta-llama/llama-3.3-70b-instruct", + "name": "Llama 3.3 70b Instruct", + "display_name": "Llama 3.3 70b Instruct", "modalities": { "input": [ "text" @@ -62677,26 +63469,25 @@ ] }, "limit": { - "context": 200000, - "output": 131072 + "context": 131072, + "output": 16384 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true + "supported": false }, "attachment": false, "open_weights": true, - "knowledge": "2025-04", - "release_date": "2026-01-19", - "last_updated": "2026-01-19", + "knowledge": "2023-12", + "release_date": "2024-12-06", + "last_updated": "2024-12-06", "type": "chat" }, { - "id": "zai/glm-4.5v", - "name": "Glm 4.5V", - "display_name": "Glm 4.5V", + "id": "meta-llama/llama-4-maverick", + "name": "Llama 4 Maverick", + "display_name": "Llama 4 Maverick", "modalities": { "input": [ "text", @@ -62707,31 +63498,28 @@ ] }, "limit": { - "context": 128000, - "output": 32000 + "context": 1048576, + "output": 65536 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true + "supported": false }, "attachment": true, "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-08-11", - "last_updated": "2025-08-11", + "knowledge": "2024-08", + "release_date": "2025-04-05", + "last_updated": "2025-04-05", "type": "chat" }, { - "id": "zai/glm-5.3-flash", - "name": "GLM-5.3 Flash", - "display_name": "GLM-5.3 Flash", + "id": "meta-llama/llama-3.2-3b-instruct", + "name": "Llama 3.2 3b Instruct", + "display_name": "Llama 3.2 3b Instruct", "modalities": { "input": [ "text", - "image", - "video", "pdf" ], "output": [ @@ -62739,35 +63527,25 @@ ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 131072, + "output": 8192 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "effort_options": [ - "low", - "high", - "max" - ] - } + "supported": false }, "attachment": true, "open_weights": true, - "release_date": "2026-08-26", - "last_updated": "2026-08-26", + "knowledge": "2023-12", + "release_date": "2024-09-25", + "last_updated": "2024-09-25", "type": "chat" }, { - "id": "zai/glm-5-turbo", - "name": "GLM-5 Turbo", - "display_name": "GLM-5 Turbo", + "id": "cohere/command-r-plus-08-2024", + "name": "Cohere: Command R+", + "display_name": "Cohere: Command R+", "modalities": { "input": [ "text" @@ -62777,36 +63555,25 @@ ] }, "limit": { - "context": 200000, - "output": 131072 + "context": 128000, + "output": 4096 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } + "supported": false }, "attachment": false, - "open_weights": false, - "release_date": "2026-03-16", - "last_updated": "2026-03-16", + "open_weights": true, + "knowledge": "2024-06-01", + "release_date": "2024-08-30", + "last_updated": "2024-08-30", "type": "chat" }, { - "id": "zai/glm-4.7", - "name": "GLM-4.7", - "display_name": "GLM-4.7", + "id": "LatitudeGames/Wayfarer-Large-70B-Llama-3.3", + "name": "Llama 3.3 70B Wayfarer", + "display_name": "Llama 3.3 70B Wayfarer", "modalities": { "input": [ "text" @@ -62816,50 +63583,36 @@ ] }, "limit": { - "context": 200000, - "output": 131072 + "context": 32768, + "output": 16384 }, - "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } + "supported": false }, "attachment": false, "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-12-22", - "last_updated": "2025-12-22", + "release_date": "2025-02-20", + "last_updated": "2025-02-20", "type": "chat" }, { - "id": "zai/glm-4.6", - "name": "GLM-4.6", - "display_name": "GLM-4.6", + "id": "pqhaz/DeepSeek-V4.1-Flash-1017-heretic:thinking", + "name": "DeepSeek V4.1 Flash Heretic Thinking", + "display_name": "DeepSeek V4.1 Flash Heretic Thinking", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 131072 + "context": 512000, + "output": 128000 }, - "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -62867,49 +63620,68 @@ }, "extra_capabilities": { "reasoning": { - "supported": true + "effort_options": [ + "none", + "minimal", + "low", + "medium", + "high", + "xhigh", + "max" + ] } }, - "attachment": false, + "attachment": true, "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-09-30", - "last_updated": "2025-09-30", + "release_date": "2026-10-31", + "last_updated": "2026-10-31", "type": "chat" }, { - "id": "zai/glm-4.5-air", - "name": "GLM-4.5 Air", - "display_name": "GLM-4.5 Air", + "id": "pqhaz/DeepSeek-V4.1-Flash-1017-heretic", + "name": "DeepSeek V4.1 Flash Heretic", + "display_name": "DeepSeek V4.1 Flash Heretic", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 98304 + "context": 512000, + "output": 128000 }, - "temperature": true, "tool_call": true, "reasoning": { "supported": true, "default": true }, - "attachment": false, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "none", + "minimal", + "low", + "medium", + "high", + "xhigh", + "max" + ] + } + }, + "attachment": true, "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-07-28", - "last_updated": "2025-07-28", + "release_date": "2026-10-31", + "last_updated": "2026-10-31", "type": "chat" }, { - "id": "zai/glm-5.1", - "name": "GLM-5.1", - "display_name": "GLM-5.1", + "id": "ReadyArt/MS3.2-The-Omega-Directive-24B-Unslop-v2.0", + "name": "Omega Directive 24B Unslop v2.0", + "display_name": "Omega Directive 24B Unslop v2.0", "modalities": { "input": [ "text" @@ -62919,36 +63691,23 @@ ] }, "limit": { - "context": 200000, - "output": 131072 + "context": 32768, + "output": 32768 }, - "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } + "supported": false }, "attachment": false, "open_weights": true, - "release_date": "2026-04-07", - "last_updated": "2026-04-07", + "release_date": "2025-12-08", + "last_updated": "2025-12-08", "type": "chat" }, { - "id": "zai/glm-5", - "name": "GLM-5", - "display_name": "GLM-5", + "id": "Doctor-Shotgun/MS3.2-24B-Magnum-Diamond", + "name": "MS3.2 24B Magnum Diamond", + "display_name": "MS3.2 24B Magnum Diamond", "modalities": { "input": [ "text" @@ -62958,36 +63717,23 @@ ] }, "limit": { - "context": 200000, - "output": 131072 + "context": 32768, + "output": 32768 }, - "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } + "supported": false }, "attachment": false, "open_weights": true, - "release_date": "2026-02-12", - "last_updated": "2026-02-12", + "release_date": "2025-11-24", + "last_updated": "2025-11-24", "type": "chat" }, { - "id": "zai/glm-5.3", - "name": "GLM-5.3", - "display_name": "GLM-5.3", + "id": "minimax/minimax-m2.5", + "name": "MiniMax M2.5", + "display_name": "MiniMax M2.5", "modalities": { "input": [ "text" @@ -62997,7 +63743,7 @@ ] }, "limit": { - "context": 1000000, + "context": 204800, "output": 131072 }, "temperature": true, @@ -63008,64 +63754,59 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "effort_options": [ - "low", - "high", - "max" - ] + "supported": true } }, "attachment": false, "open_weights": true, - "release_date": "2026-08-14", - "last_updated": "2026-08-14", + "release_date": "2026-02-12", + "last_updated": "2026-02-12", "type": "chat" }, { - "id": "zai/glm-4.5", - "name": "GLM-4.5", - "display_name": "GLM-4.5", + "id": "minimax/minimax-latest", + "name": "MiniMax Latest", + "display_name": "MiniMax Latest", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 98304 + "context": 512000, + "output": 80000 }, - "temperature": true, "tool_call": true, "reasoning": { "supported": true, "default": true }, - "attachment": false, + "attachment": true, "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-07-28", - "last_updated": "2025-07-28", + "release_date": "2026-05-03", + "last_updated": "2026-05-03", "type": "chat" }, { - "id": "zai/glm-4.7-flash", - "name": "GLM 4.7 Flash", - "display_name": "GLM 4.7 Flash", + "id": "minimax/minimax-m3:thinking", + "name": "MiniMax M3 Thinking", + "display_name": "MiniMax M3 Thinking", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 128000 + "context": 512000, + "output": 80000 }, "temperature": true, "tool_call": true, @@ -63073,61 +63814,43 @@ "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "supported": true - } - }, - "attachment": false, + "attachment": true, "open_weights": true, - "knowledge": "2025-04", - "release_date": "2026-01-19", - "last_updated": "2026-01-19", + "release_date": "2026-06-01", + "last_updated": "2026-06-01", "type": "chat" }, { - "id": "zai/glm-5.2", - "name": "GLM-5.2", - "display_name": "GLM-5.2", + "id": "minimax/minimax-01", + "name": "MiniMax 01", + "display_name": "MiniMax 01", "modalities": { "input": [ - "text" + "text", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 1000192, + "output": 16384 }, - "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } + "supported": false }, - "attachment": false, - "open_weights": true, - "release_date": "2026-06-13", - "last_updated": "2026-06-13", + "attachment": true, + "open_weights": false, + "release_date": "2024-01-01", + "last_updated": "2025-01-15", "type": "chat" }, { - "id": "cohere/command-a-03-2025", - "name": "Command A 03-2025", - "display_name": "Command A 03-2025", + "id": "minimax/minimax-m2", + "name": "MiniMax M2", + "display_name": "MiniMax M2", "modalities": { "input": [ "text" @@ -63137,25 +63860,25 @@ ] }, "limit": { - "context": 256000, - "output": 8000 + "context": 200000, + "output": 131072 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": false + "supported": true, + "default": true }, "attachment": false, "open_weights": true, - "knowledge": "2024-06-01", - "release_date": "2025-03-13", - "last_updated": "2025-03-13", + "release_date": "2025-10-27", + "last_updated": "2025-10-27", "type": "chat" }, { - "id": "cohere/command-r-08-2024", - "name": "Command R 08-2024", - "display_name": "Command R 08-2024", + "id": "minimax/minimax-m2.7-turbo", + "name": "MiniMax M2.7 Turbo", + "display_name": "MiniMax M2.7 Turbo", "modalities": { "input": [ "text" @@ -63165,53 +63888,57 @@ ] }, "limit": { - "context": 128000, - "output": 4000 + "context": 204800, + "output": 131072 }, - "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, "attachment": false, "open_weights": true, - "knowledge": "2024-06-01", - "release_date": "2024-08-30", - "last_updated": "2024-08-30", + "release_date": "2026-03-18", + "last_updated": "2026-03-18", "type": "chat" }, { - "id": "cohere/command-r7b-12-2024", - "name": "Command R7B 12-2024", - "display_name": "Command R7B 12-2024", + "id": "minimax/minimax-m3", + "name": "MiniMax M3", + "display_name": "MiniMax M3", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 4000 + "context": 512000, + "output": 80000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true }, - "attachment": false, + "extra_capabilities": { + "reasoning": { + "supported": true + } + }, + "attachment": true, "open_weights": true, - "knowledge": "2024-06-01", - "release_date": "2024-12-13", - "last_updated": "2024-12-13", + "release_date": "2026-06-01", + "last_updated": "2026-06-01", "type": "chat" }, { - "id": "cohere/command-r-plus-08-2024", - "name": "Command R+ 08-2024", - "display_name": "Command R+ 08-2024", + "id": "minimax/minimax-m2.1", + "name": "MiniMax M2.1", + "display_name": "MiniMax M2.1", "modalities": { "input": [ "text" @@ -63221,37 +63948,41 @@ ] }, "limit": { - "context": 128000, - "output": 4000 + "context": 200000, + "output": 131072 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true + } }, "attachment": false, "open_weights": true, - "knowledge": "2024-06-01", - "release_date": "2024-08-30", - "last_updated": "2024-08-30", + "release_date": "2025-12-23", + "last_updated": "2025-12-23", "type": "chat" }, { - "id": "xai/grok-4.20-0309-reasoning", - "name": "Grok 4.20", - "display_name": "Grok 4.20", + "id": "minimax/minimax-m2.7", + "name": "MiniMax M2.7", + "display_name": "MiniMax M2.7", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 1000000 + "context": 204800, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -63259,28 +63990,40 @@ "supported": true, "default": true }, - "attachment": true, - "open_weights": false, - "release_date": "2026-03-09", - "last_updated": "2026-03-09", + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } + }, + "attachment": false, + "open_weights": true, + "release_date": "2026-03-18", + "last_updated": "2026-03-18", "type": "chat" }, { - "id": "xai/grok-4.5", - "name": "Grok 4.5", - "display_name": "Grok 4.5", + "id": "minimax/minimax-m3.1-flash", + "name": "MiniMax M3.1 Flash", + "display_name": "MiniMax M3.1 Flash", "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 500000, - "output": 500000 + "context": 1048576, + "output": 524288 }, "temperature": true, "tool_call": true, @@ -63290,70 +64033,95 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, "effort_options": [ "low", "medium", - "high" + "high", + "xhigh", + "max" ] } }, "attachment": true, "open_weights": false, - "release_date": "2026-07-08", - "last_updated": "2026-07-08", + "release_date": "2026-10-19", + "last_updated": "2026-10-19", "type": "chat" }, { - "id": "xai/grok-build-0.1", - "name": "Grok Build 0.1", - "display_name": "Grok Build 0.1", + "id": "abacusai/Dracarys-72B-Instruct", + "name": "Llama 3.1 70B Dracarys 2", + "display_name": "Llama 3.1 70B Dracarys 2", + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 32768, + "output": 8192 + }, + "tool_call": false, + "reasoning": { + "supported": false + }, + "attachment": false, + "open_weights": true, + "release_date": "2025-08-02", + "last_updated": "2025-08-02", + "type": "chat" + }, + { + "id": "perceptron/perceptron-mk1", + "name": "Perceptron Mk1", + "display_name": "Perceptron Mk1", "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 256000, - "output": 256000 + "context": 32768, + "output": 8192 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "supported": true - } - }, "attachment": true, "open_weights": false, - "release_date": "2026-04-16", - "last_updated": "2026-04-16", + "release_date": "2026-05-12", + "last_updated": "2026-05-12", "type": "chat" }, { - "id": "xai/grok-4.7", - "name": "Grok 4.7", - "display_name": "Grok 4.7", + "id": "perceptron/perceptron-mk1.5", + "name": "Perceptron Mk1.5", + "display_name": "Perceptron Mk1.5", "modalities": { "input": [ "text", - "image" + "image", + "video", + "audio" ], "output": [ "text" ] }, "limit": { - "context": 500000, - "output": 500000 + "context": 36864, + "output": 8192 }, "temperature": true, "tool_call": true, @@ -63364,25 +64132,24 @@ "extra_capabilities": { "reasoning": { "effort_options": [ + "none", "minimal", "low", "medium", - "high", - "xhigh" + "high" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-05", - "release_date": "2026-09-21", - "last_updated": "2026-09-21", + "release_date": "2026-09-25", + "last_updated": "2026-09-25", "type": "chat" }, { - "id": "xai/grok-4.3", - "name": "Grok 4.3", - "display_name": "Grok 4.3", + "id": "bytedance/doubao-seed-character", + "name": "Doubao Seed Character", + "display_name": "Doubao Seed Character", "modalities": { "input": [ "text", @@ -63393,111 +64160,100 @@ ] }, "limit": { - "context": 1000000, - "output": 1000000 + "context": 128000, + "output": 32768 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { "supported": true, "default": true }, "extra_capabilities": { "reasoning": { - "supported": true + "effort_options": [ + "none", + "high" + ] } }, "attachment": true, "open_weights": false, - "release_date": "2026-04-17", - "last_updated": "2026-04-17", + "release_date": "2026-06-23", + "last_updated": "2026-06-23", "type": "chat" }, { - "id": "xai/grok-4.20-0309-non-reasoning", - "name": "Grok 4.20 Non-Reasoning", - "display_name": "Grok 4.20 Non-Reasoning", + "id": "bytedance/doubao-seed-2.1-pro", + "name": "Doubao Seed 2.1 Pro", + "display_name": "Doubao Seed 2.1 Pro", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 1000000 + "context": 256000, + "output": 128000 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { "supported": false }, - "attachment": true, + "attachment": false, "open_weights": false, - "release_date": "2026-03-09", - "last_updated": "2026-03-09", + "release_date": "2026-06-23", + "last_updated": "2026-06-23", "type": "chat" }, { - "id": "xai/grok-4.6", - "name": "Grok 4.6", - "display_name": "Grok 4.6", + "id": "bytedance/doubao-seed-2.1-turbo", + "name": "Doubao Seed 2.1 Turbo", + "display_name": "Doubao Seed 2.1 Turbo", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 500000, - "output": 500000 + "context": 256000, + "output": 128000 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "effort_options": [ - "low", - "medium", - "high", - "xhigh" - ] - } + "supported": false }, - "attachment": true, + "attachment": false, "open_weights": false, - "knowledge": "2026-02-01", - "release_date": "2026-08-12", - "last_updated": "2026-08-12", + "release_date": "2026-06-23", + "last_updated": "2026-06-23", "type": "chat" }, { - "id": "sakana/fugu-max", - "name": "Fugu Max", - "display_name": "Fugu Max", + "id": "moonshotai/kimi-k3", + "name": "Kimi K3", + "display_name": "Kimi K3", "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 1048576, + "output": 943718 }, "temperature": false, "tool_call": true, @@ -63507,66 +64263,65 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, "effort_options": [ + "low", "high", - "xhigh", "max" ] } }, "attachment": true, - "open_weights": false, - "release_date": "2026-09-11", - "last_updated": "2026-09-11", + "open_weights": true, + "release_date": "2026-07-16", + "last_updated": "2026-07-16", "type": "chat" }, { - "id": "sakana/sakana-namazu", - "name": "Sakana Namazu", - "display_name": "Sakana Namazu", + "id": "moonshotai/kimi-k2-instruct-0711", + "name": "Kimi K2 0711", + "display_name": "Kimi K2 0711", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 65536 + "context": 128000, + "output": 8192 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true + "supported": false }, - "attachment": true, - "open_weights": false, - "release_date": "2026-08-03", - "last_updated": "2026-08-03", + "attachment": false, + "open_weights": true, + "release_date": "2025-07-11", + "last_updated": "2025-07-11", "type": "chat" }, { - "id": "sakana/fugu-ultra", - "name": "Fugu Ultra", - "display_name": "Fugu Ultra", + "id": "moonshotai/kimi-latest", + "name": "Kimi Latest", + "display_name": "Kimi Latest", "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 1048576, + "output": 943718 }, - "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -63575,51 +64330,57 @@ "extra_capabilities": { "reasoning": { "effort_options": [ + "low", "high", - "xhigh", "max" ] } }, "attachment": true, "open_weights": false, - "release_date": "2026-06-15", - "last_updated": "2026-06-15", + "release_date": "2026-05-03", + "last_updated": "2026-05-03", "type": "chat" }, { - "id": "nvidia/nemotron-nano-9b-v2", - "name": "Nemotron Nano 9B", - "display_name": "Nemotron Nano 9B", + "id": "moonshotai/kimi-k2.7-code", + "name": "Kimi K2.7 Code", + "display_name": "Kimi K2.7 Code", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 8192 + "context": 262144, + "output": 65536 }, - "temperature": true, - "tool_call": false, + "temperature": false, + "tool_call": true, "reasoning": { "supported": true, "default": true }, - "attachment": false, + "extra_capabilities": { + "reasoning": { + "supported": true + } + }, + "attachment": true, "open_weights": true, - "knowledge": "2024-09", - "release_date": "2025-08-18", - "last_updated": "2025-08-18", + "knowledge": "2025-01", + "release_date": "2026-06-12", + "last_updated": "2026-06-12", "type": "chat" }, { - "id": "nvidia/nemotron-3-nano-30b-a3b", - "name": "Nemotron 3 Nano 30B A3B", - "display_name": "Nemotron 3 Nano 30B A3B", + "id": "moonshotai/kimi-k2-thinking", + "name": "Kimi K2 Thinking", + "display_name": "Kimi K2 Thinking", "modalities": { "input": [ "text" @@ -63629,39 +64390,51 @@ ] }, "limit": { - "context": 256000, - "output": 32000 + "context": 262144, + "output": 98304 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { "supported": true, "default": true }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } + }, "attachment": false, "open_weights": true, - "knowledge": "2025-06-25", - "release_date": "2025-12-15", - "last_updated": "2025-12-15", + "knowledge": "2024-08", + "release_date": "2025-11-06", + "last_updated": "2025-11-06", "type": "chat" }, { - "id": "nvidia/nemotron-3.5-lightning-30b-a3b", - "name": "Nemotron 3.5 Lightning 30B A3B", - "display_name": "Nemotron 3.5 Lightning 30B A3B", + "id": "moonshotai/kimi-k2.7-code-highspeed", + "name": "Kimi K2.7 Code High-Speed", + "display_name": "Kimi K2.7 Code High-Speed", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 262144 + "context": 262144, + "output": 65536 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -63669,69 +64442,70 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "none", - "low", - "medium", - "high" - ] + "supported": true } }, - "attachment": false, + "attachment": true, "open_weights": true, - "knowledge": "2025-09", - "release_date": "2026-08-11", - "last_updated": "2026-08-11", + "knowledge": "2025-01", + "release_date": "2026-06-12", + "last_updated": "2026-06-12", "type": "chat" }, { - "id": "anthropic/claude-sonnet-5-5", - "name": "Claude Sonnet 5.5", - "display_name": "Claude Sonnet 5.5", + "id": "moonshotai/kimi-k3.1-pro-highspeed", + "name": "Kimi K3.1 Pro Highspeed", + "display_name": "Kimi K3.1 Pro Highspeed", "modalities": { "input": [ "text", "image", - "pdf" + "video" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 1048576, + "output": 1048576 }, - "temperature": false, "tool_call": true, "reasoning": { "supported": true, "default": true }, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "low", + "high", + "max" + ] + } + }, "attachment": true, "open_weights": false, - "knowledge": "2026-06", - "release_date": "2026-09-28", - "last_updated": "2026-09-28", + "release_date": "2026-10-24", + "last_updated": "2026-10-24", "type": "chat" }, { - "id": "anthropic/claude-haiku-5-5", - "name": "Claude Haiku 5.5", - "display_name": "Claude Haiku 5.5", + "id": "moonshotai/kimi-k2.5:thinking", + "name": "Kimi K2.5 Thinking", + "display_name": "Kimi K2.5 Thinking", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 262144, + "output": 65536 }, "temperature": false, "tool_call": true, @@ -63740,364 +64514,256 @@ "default": true }, "attachment": true, - "open_weights": false, - "knowledge": "2026-06", - "release_date": "2026-10-07", - "last_updated": "2026-10-07", + "open_weights": true, + "knowledge": "2025-01", + "release_date": "2026-01-27", + "last_updated": "2026-01-27", "type": "chat" }, { - "id": "anthropic/claude-opus-5", - "name": "Claude Opus 5", - "display_name": "Claude Opus 5", + "id": "moonshotai/kimi-k2-instruct", + "name": "Kimi K2 Instruct", + "display_name": "Kimi K2 Instruct", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 256000, + "output": 8192 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" - ] - } + "supported": false }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-05", - "release_date": "2026-07-24", - "last_updated": "2026-07-24", + "attachment": false, + "open_weights": true, + "release_date": "2025-07-11", + "last_updated": "2025-07-11", "type": "chat" }, { - "id": "anthropic/claude-opus-4-7", - "name": "Claude Opus 4.7", - "display_name": "Claude Opus 4.7", + "id": "moonshotai/kimi-k3.1-pro", + "name": "Kimi K3.1 Pro", + "display_name": "Kimi K3.1 Pro", "modalities": { "input": [ "text", "image", - "pdf" + "video" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 1048576, + "output": 1048576 }, "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "high", "effort_options": [ "low", - "medium", "high", - "xhigh", "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "omitted", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Claude Opus 4.7 and newer Opus models require thinking.type = \"adaptive\" to enable thinking explicitly.", - "Manual budget_tokens requests return 400 on Claude Opus 4.7 and newer adaptive-only Opus models.", - "task_budget is separate from thinking control and should not be treated as a thinking budget." ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-01-31", - "release_date": "2026-04-16", - "last_updated": "2026-04-16", + "release_date": "2026-10-23", + "last_updated": "2026-10-23", "type": "chat" }, { - "id": "anthropic/claude-opus-5-5", - "name": "Claude Opus 5.5", - "display_name": "Claude Opus 5.5", + "id": "moonshotai/Kimi-K2-Instruct-0905", + "name": "Kimi K2 0905", + "display_name": "Kimi K2 0905", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 262144, + "output": 100352 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true + "supported": false }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-06", - "release_date": "2026-09-22", - "last_updated": "2026-09-22", + "attachment": false, + "open_weights": true, + "release_date": "2025-09-05", + "last_updated": "2025-09-05", "type": "chat" }, { - "id": "anthropic/claude-sonnet-5", - "name": "Claude Sonnet 5", - "display_name": "Claude Sonnet 5", + "id": "moonshotai/kimi-k2.6:thinking", + "name": "Kimi K2.6 Thinking", + "display_name": "Kimi K2.6 Thinking", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 262144, + "output": 65536 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" - ] - } - }, "attachment": true, - "open_weights": false, - "knowledge": "2026-01-31", - "release_date": "2026-06-30", - "last_updated": "2026-06-30", + "open_weights": true, + "knowledge": "2025-01", + "release_date": "2026-04-20", + "last_updated": "2026-04-20", "type": "chat" }, { - "id": "anthropic/claude-sonnet-4-6", - "name": "Claude Sonnet 4.6", - "display_name": "Claude Sonnet 4.6", + "id": "moonshotai/kimi-k2.5", + "name": "Kimi K2.5", + "display_name": "Kimi K2.5", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 262144, + "output": 65536 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { - "supported": true, - "default": false + "supported": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "mixed", - "budget": { - "min": 1024, - "unit": "tokens" - }, - "effort": "high", - "effort_options": [ - "low", - "medium", - "high", - "max" - ], "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ "thinking_blocks" - ], - "notes": [ - "Anthropic recommends adaptive thinking with effort for Claude 4.6; budget_tokens remains a deprecated compatibility path.", - "Anthropic API defaults effort to high; lower effort levels should be chosen per workload." ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-02-17", - "last_updated": "2026-03-13", + "open_weights": true, + "knowledge": "2025-01", + "release_date": "2026-01-27", + "last_updated": "2026-01-27", "type": "chat" }, { - "id": "anthropic/claude-opus-4-6", - "name": "Claude Opus 4.6", - "display_name": "Claude Opus 4.6", + "id": "moonshotai/kimi-k2.6", + "name": "Kimi K2.6", + "display_name": "Kimi K2.6", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 262144, + "output": 65536 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": false + "supported": true }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "mixed", - "budget": { - "min": 1024, - "unit": "tokens" - }, - "effort": "high", - "effort_options": [ - "low", - "medium", - "high", - "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Anthropic recommends adaptive thinking with effort for Claude 4.6; budget_tokens remains a deprecated compatibility path.", - "Anthropic API defaults effort to high; lower effort levels should be chosen per workload." - ] + "supported": true } }, "attachment": true, - "open_weights": false, - "knowledge": "2025-05-31", - "release_date": "2026-02-05", - "last_updated": "2026-03-13", + "open_weights": true, + "knowledge": "2025-01", + "release_date": "2026-04-20", + "last_updated": "2026-04-20", "type": "chat" }, { - "id": "anthropic/claude-opus-4-1-20250805", - "name": "Claude Opus 4.1 (20250805)", - "display_name": "Claude Opus 4.1 (20250805)", + "id": "reflection/beam-1.0", + "name": "Beam 1.0 501B A23B", + "display_name": "Beam 1.0 501B A23B", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 32000 + "context": 1048576, + "output": 131072 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "budget", - "budget": { - "min": 1024, - "unit": "tokens" - }, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", - "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-03-31", - "release_date": "2025-08-05", - "last_updated": "2025-08-05", + "attachment": false, + "open_weights": true, + "knowledge": "2026-06-30", + "release_date": "2026-10-05", + "last_updated": "2026-10-16", "type": "chat" }, { - "id": "anthropic/claude-sonnet-4-5-20250929", - "name": "Claude Sonnet 4.5 (20250929)", - "display_name": "Claude Sonnet 4.5 (20250929)", + "id": "unsloth/gemma-3-12b-it", + "name": "Gemma 3 12B IT", + "display_name": "Gemma 3 12B IT", "modalities": { "input": [ "text", - "image", "pdf" ], "output": [ @@ -64105,51 +64771,28 @@ ] }, "limit": { - "context": 200000, - "output": 64000 + "context": 131072, + "output": 16384 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": false - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "budget", - "budget": { - "min": 1024, - "unit": "tokens" - }, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", - "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." - ] - } + "supported": false }, "attachment": true, - "open_weights": false, - "knowledge": "2025-07-31", - "release_date": "2025-09-29", - "last_updated": "2025-09-29", + "open_weights": true, + "knowledge": "2024-08", + "release_date": "2025-03-12", + "last_updated": "2025-03-12", "type": "chat" }, { - "id": "anthropic/claude-haiku-4-5-20251001", - "name": "Claude Haiku 4.5 (20251001)", - "display_name": "Claude Haiku 4.5 (20251001)", + "id": "unsloth/gemma-3-27b-it", + "name": "Gemma 3 27B IT", + "display_name": "Gemma 3 27B IT", "modalities": { "input": [ "text", - "image", "pdf" ], "output": [ @@ -64157,51 +64800,28 @@ ] }, "limit": { - "context": 200000, - "output": 64000 + "context": 128000, + "output": 96000 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": false - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "budget", - "budget": { - "min": 1024, - "unit": "tokens" - }, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", - "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." - ] - } + "supported": false }, "attachment": true, - "open_weights": false, - "knowledge": "2025-02-28", - "release_date": "2025-10-15", - "last_updated": "2025-10-15", + "open_weights": true, + "knowledge": "2024-08", + "release_date": "2025-03-12", + "last_updated": "2025-03-12", "type": "chat" }, { - "id": "anthropic/claude-opus-4-8", - "name": "Claude Opus 4.8", - "display_name": "Claude Opus 4.8", + "id": "unsloth/gemma-3-4b-it", + "name": "Gemma 3 4B IT", + "display_name": "Gemma 3 4B IT", "modalities": { "input": [ "text", - "image", "pdf" ], "output": [ @@ -64209,125 +64829,79 @@ ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 128000, + "output": 8192 }, - "temperature": false, - "tool_call": true, + "temperature": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": false - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "high", - "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "omitted", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Claude Opus 4.7 and newer Opus models require thinking.type = \"adaptive\" to enable thinking explicitly.", - "Manual budget_tokens requests return 400 on Claude Opus 4.7 and newer adaptive-only Opus models.", - "task_budget is separate from thinking control and should not be treated as a thinking budget." - ] - } + "supported": false }, "attachment": true, - "open_weights": false, - "knowledge": "2026-01", - "release_date": "2026-05-28", - "last_updated": "2026-05-28", + "open_weights": true, + "knowledge": "2024-08", + "release_date": "2025-03-12", + "last_updated": "2025-03-12", "type": "chat" }, { - "id": "anthropic/claude-opus-4-5-20251101", - "name": "Claude Opus 4.5 (20251101)", - "display_name": "Claude Opus 4.5 (20251101)", + "id": "stepfun/step-5-preview", + "name": "Step 5 Preview", + "display_name": "Step 5 Preview", "modalities": { "input": [ "text", "image", - "pdf" + "video" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 64000 + "context": 1000000, + "output": 65536 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "mixed", - "budget": { - "min": 1024, - "unit": "tokens" - }, - "effort": "high", "effort_options": [ "low", "medium", "high" - ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Claude Opus 4.5 uses manual thinking.type = \"enabled\" with budget_tokens; effort can be used alongside the thinking budget.", - "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header." ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2025-05", - "release_date": "2025-11-24", - "last_updated": "2025-11-24", + "open_weights": true, + "release_date": "2026-09-16", + "last_updated": "2026-09-20", "type": "chat" }, { - "id": "anthropic/claude-fable-5", - "name": "Claude Fable 5", - "display_name": "Claude Fable 5", + "id": "stepfun/step-3.7-flash:thinking", + "name": "Step 3.7 Flash Thinking", + "display_name": "Step 3.7 Flash Thinking", "modalities": { "input": [ "text", "image", - "pdf" + "video" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 262144, + "output": 256000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -64335,98 +64909,51 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "high", "effort_options": [ + "none", "low", "medium", - "high", - "xhigh", - "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "omitted", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Adaptive thinking is always on for Claude Fable 5 and Claude Mythos 5; thinking.type = \"disabled\" is rejected.", - "Manual budget_tokens requests return 400 on Claude Fable 5 and Claude Mythos 5.", - "thinking.display defaults to omitted; set display to summarized to receive readable thinking summaries." + "high" ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2026-01-31", - "release_date": "2026-06-09", - "last_updated": "2026-06-09", + "open_weights": true, + "knowledge": "2026-03-01", + "release_date": "2026-05-29", + "last_updated": "2026-05-29", "type": "chat" }, { - "id": "anthropic/claude-fable-5-1", - "name": "Claude Fable 5.1", - "display_name": "Claude Fable 5.1", + "id": "Steelskull/L3.3-Nevoria-R1-70b", + "name": "Steelskull Nevoria R1 70b", + "display_name": "Steelskull Nevoria R1 70b", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 32768, + "output": 16384 }, - "temperature": false, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "high", - "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "omitted", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Adaptive thinking is always on for Claude Fable 5 and Claude Mythos 5; thinking.type = \"disabled\" is rejected.", - "Manual budget_tokens requests return 400 on Claude Fable 5 and Claude Mythos 5.", - "thinking.display defaults to omitted; set display to summarized to receive readable thinking summaries." - ] - } + "supported": false }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-06", - "release_date": "2026-09-01", - "last_updated": "2026-09-01", + "attachment": false, + "open_weights": true, + "release_date": "2024-01-01", + "last_updated": "2024-12-06", "type": "chat" }, { - "id": "deepseek/deepseek-v4-flash-0731-fast", - "name": "DeepSeek V4 Flash 0731", - "display_name": "DeepSeek V4 Flash 0731", + "id": "Steelskull/L3.3-Electra-R1-70b", + "name": "Steelskull Electra R1 70b", + "display_name": "Steelskull Electra R1 70b", "modalities": { "input": [ "text" @@ -64436,31 +64963,23 @@ ] }, "limit": { - "context": 1000000, - "output": 384000 + "context": 32768, + "output": 16384 }, - "temperature": true, "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true - } + "supported": false }, "attachment": false, "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-07-31", - "last_updated": "2026-07-31", + "release_date": "2024-01-01", + "last_updated": "2024-12-06", "type": "chat" }, { - "id": "deepseek/deepseek-v4-flash", - "name": "DeepSeek V4 Flash", - "display_name": "DeepSeek V4 Flash", + "id": "Steelskull/L3.3-MS-Nevoria-70b", + "name": "Steelskull Nevoria 70b", + "display_name": "Steelskull Nevoria 70b", "modalities": { "input": [ "text" @@ -64470,91 +64989,49 @@ ] }, "limit": { - "context": 1000000, - "output": 384000 + "context": 32768, + "output": 16384 }, - "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "high", - "effort_options": [ - "low", - "high", - "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." - ] - } + "supported": false }, "attachment": false, "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-04-24", - "last_updated": "2026-04-24", + "release_date": "2024-01-01", + "last_updated": "2024-12-06", "type": "chat" }, { - "id": "deepseek/deepseek-v4.1-flash-1017", - "name": "DeepSeek V4.1 Flash 1017", - "display_name": "DeepSeek V4.1 Flash 1017", + "id": "Steelskull/L3.3-Cu-Mai-R1-70b", + "name": "Llama 3.3 70B Cu Mai", + "display_name": "Llama 3.3 70B Cu Mai", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 384000 + "context": 32768, + "output": 16384 }, - "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "none", - "minimal", - "low", - "medium", - "high", - "xhigh", - "max" - ] - } + "supported": false }, - "attachment": true, + "attachment": false, "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-10-17", - "last_updated": "2026-10-17", + "release_date": "2024-01-01", + "last_updated": "2024-12-06", "type": "chat" }, { - "id": "deepseek/deepseek-v4-pro", - "name": "DeepSeek V4 Pro", - "display_name": "DeepSeek V4 Pro", + "id": "shisa-ai/shisa-v2-llama3.3-70b", + "name": "Shisa V2 Llama 3.3 70B", + "display_name": "Shisa V2 Llama 3.3 70B", "modalities": { "input": [ "text" @@ -64564,48 +65041,23 @@ ] }, "limit": { - "context": 1000000, - "output": 384000 + "context": 128000, + "output": 16384 }, - "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "high", - "effort_options": [ - "low", - "high", - "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." - ] - } + "supported": false }, "attachment": false, "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-04-24", - "last_updated": "2026-04-24", + "release_date": "2025-07-26", + "last_updated": "2025-07-26", "type": "chat" }, { - "id": "deepseek/deepseek-v3.1", - "name": "DeepSeek V3.1", - "display_name": "DeepSeek V3.1", + "id": "shisa-ai/shisa-v2.1-llama3.3-70b", + "name": "Shisa V2.1 Llama 3.3 70B", + "display_name": "Shisa V2.1 Llama 3.3 70B", "modalities": { "input": [ "text" @@ -64615,25 +65067,23 @@ ] }, "limit": { - "context": 131072, - "output": 32768 + "context": 32768, + "output": 4096 }, - "temperature": true, "tool_call": false, "reasoning": { - "supported": true, - "default": true + "supported": false }, "attachment": false, "open_weights": true, - "release_date": "2025-08-21", - "last_updated": "2025-08-21", + "release_date": "2024-01-01", + "last_updated": "2024-12-06", "type": "chat" }, { - "id": "deepseek/deepseek-r1-0528", - "name": "DeepSeek R1 (0528)", - "display_name": "DeepSeek R1 (0528)", + "id": "tencent/hy3", + "name": "Tencent Hy3", + "display_name": "Tencent Hy3", "modalities": { "input": [ "text" @@ -64643,11 +65093,11 @@ ] }, "limit": { - "context": 65536, - "output": 32768 + "context": 262144, + "output": 128000 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { "supported": true, "default": true @@ -64655,36 +65105,34 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "none", + "low", + "high" ] } }, "attachment": false, "open_weights": true, - "release_date": "2025-05-28", - "last_updated": "2025-05-28", + "release_date": "2026-07-06", + "last_updated": "2026-07-06", "type": "chat" }, { - "id": "deepseek/deepseek-v4.1-flash", - "name": "DeepSeek V4.1 Flash", - "display_name": "DeepSeek V4.1 Flash", + "id": "tencent/hy4-preview", + "name": "Tencent Hy4 Preview", + "display_name": "Tencent Hy4 Preview", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 384000 + "context": 1048576, + "output": 64000 }, "temperature": true, "tool_call": true, @@ -64697,60 +65145,50 @@ "supported": true, "effort_options": [ "none", - "minimal", "low", - "medium", - "high", - "xhigh", - "max" + "high" ] } }, - "attachment": true, + "attachment": false, "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-09-10", - "last_updated": "2026-09-10", + "release_date": "2026-08-28", + "last_updated": "2026-08-28", "type": "chat" }, { - "id": "deepseek/deepseek-v3.2", - "name": "DeepSeek V3.2", - "display_name": "DeepSeek V3.2", + "id": "anthracite-org/magnum-v4-72b", + "name": "Magnum v4 72B", + "display_name": "Magnum v4 72B", "modalities": { "input": [ - "text" + "text", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 32768 + "context": 16384, + "output": 8192 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true - } + "supported": false }, - "attachment": false, + "attachment": true, "open_weights": true, - "knowledge": "2024-07", - "release_date": "2025-12-01", - "last_updated": "2025-12-01", + "knowledge": "2024-06-30", + "release_date": "2024-10-22", + "last_updated": "2024-10-22", "type": "chat" }, { - "id": "deepseek/deepseek-v4-flash-0731", - "name": "DeepSeek V4 Flash 0731", - "display_name": "DeepSeek V4 Flash 0731", + "id": "anthracite-org/magnum-v2-72b", + "name": "Magnum V2 72B", + "display_name": "Magnum V2 72B", "modalities": { "input": [ "text" @@ -64760,38 +65198,23 @@ ] }, "limit": { - "context": 1048576, - "output": 384000 + "context": 16384, + "output": 8192 }, - "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "effort_options": [ - "none", - "low", - "medium", - "high", - "max" - ] - } + "supported": false }, "attachment": false, "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-07-31", - "last_updated": "2026-07-31", + "release_date": "2024-01-01", + "last_updated": "2024-07-01", "type": "chat" }, { - "id": "deepseek/deepseek-v4-pro-0813", - "name": "DeepSeek V4 Pro 0813", - "display_name": "DeepSeek V4 Pro 0813", + "id": "inflatebot/MN-12B-Mag-Mell-R1", + "name": "Mag Mell R1", + "display_name": "Mag Mell R1", "modalities": { "input": [ "text" @@ -64801,39 +65224,23 @@ ] }, "limit": { - "context": 1000000, - "output": 384000 + "context": 16384, + "output": 8192 }, - "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "effort_options": [ - "none", - "minimal", - "low", - "medium", - "high", - "xhigh", - "max" - ] - } + "supported": false }, "attachment": false, "open_weights": true, - "release_date": "2026-08-13", - "last_updated": "2026-08-22", + "release_date": "2024-01-01", + "last_updated": "2024-07-01", "type": "chat" }, { - "id": "deepseek/deepseek-v4-pro-0423", - "name": "DeepSeek V4 Pro 0423", - "display_name": "DeepSeek V4 Pro 0423", + "id": "pamanseau/OpenReasoning-Nemotron-32B", + "name": "OpenReasoning Nemotron 32B", + "display_name": "OpenReasoning Nemotron 32B", "modalities": { "input": [ "text" @@ -64843,37 +65250,24 @@ ] }, "limit": { - "context": 1000000, - "output": 393216 + "context": 32768, + "output": 65536 }, - "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "none", - "low", - "medium", - "high", - "max" - ] - } - }, "attachment": false, "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-04-23", - "last_updated": "2026-04-23", + "release_date": "2025-08-21", + "last_updated": "2025-08-21", "type": "chat" }, { - "id": "deepseek/deepseek-v3", - "name": "DeepSeek V3", - "display_name": "DeepSeek V3", + "id": "Salesforce/Llama-xLAM-2-70b-fc-r", + "name": "Llama-xLAM-2 70B fc-r", + "display_name": "Llama-xLAM-2 70B fc-r", "modalities": { "input": [ "text" @@ -64883,24 +65277,23 @@ ] }, "limit": { - "context": 163840, - "output": 81920 + "context": 128000, + "output": 16384 }, - "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { "supported": false }, "attachment": false, "open_weights": true, - "release_date": "2024-12-26", - "last_updated": "2024-12-26", + "release_date": "2025-04-13", + "last_updated": "2025-04-13", "type": "chat" }, { - "id": "deepseek/deepseek-r1", - "name": "DeepSeek R1", - "display_name": "DeepSeek R1", + "id": "aleph-alpha/kolibri-1", + "name": "Kolibri 1", + "display_name": "Kolibri 1", "modalities": { "input": [ "text" @@ -64910,51 +65303,51 @@ ] }, "limit": { - "context": 163840, - "output": 40960 + "context": 1048576, + "output": 943718 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { "supported": true, "default": true }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "none", + "low", + "medium", + "high" ] } }, "attachment": false, "open_weights": true, - "knowledge": "2024-07", - "release_date": "2025-01-20", - "last_updated": "2025-05-29", + "knowledge": "2026-06-18", + "release_date": "2026-10-03", + "last_updated": "2026-10-03", "type": "chat" }, { - "id": "deepseek/deepseek-v4-flash-0423", - "name": "DeepSeek V4 Flash 0423", - "display_name": "DeepSeek V4 Flash 0423", + "id": "fireworks/ember-1", + "name": "Ember 1", + "display_name": "Ember 1", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 32768 + "context": 1048576, + "output": 131072 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { "supported": true, "default": true @@ -64963,30 +65356,28 @@ "reasoning": { "effort_options": [ "none", - "minimal", "low", - "medium", "high", - "xhigh", "max" ] } }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-04-23", - "last_updated": "2026-04-23", + "attachment": true, + "open_weights": false, + "release_date": "2026-09-22", + "last_updated": "2026-09-22", "type": "chat" }, { - "id": "google/gemini-3-flash-preview", - "name": "Gemini 3 Flash Preview", - "display_name": "Gemini 3 Flash Preview", + "id": "google/gemini-3.5-flash", + "name": "Gemini 3.5 Flash", + "display_name": "Gemini 3.5 Flash", "modalities": { "input": [ "text", - "image" + "image", + "video", + "audio" ], "output": [ "text" @@ -65024,20 +65415,20 @@ "attachment": true, "open_weights": false, "knowledge": "2025-01", - "release_date": "2025-12-17", - "last_updated": "2025-12-17", + "release_date": "2026-05-19", + "last_updated": "2026-05-19", "type": "chat" }, { - "id": "google/gemini-2.5-pro", - "name": "Gemini 2.5 Pro", - "display_name": "Gemini 2.5 Pro", + "id": "google/gemini-3.5-flash-thinking", + "name": "Gemini 3.5 Flash Thinking", + "display_name": "Gemini 3.5 Flash Thinking", "modalities": { "input": [ "text", "image", - "audio", - "pdf" + "video", + "audio" ], "output": [ "text" @@ -65048,7 +65439,7 @@ "output": 65536 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { "supported": true, "default": true @@ -65057,14 +65448,14 @@ "reasoning": { "supported": true, "default_enabled": true, - "mode": "budget", - "budget": { - "default": -1, - "min": 128, - "max": 32768, - "auto": -1, - "unit": "tokens" - }, + "mode": "level", + "level": "high", + "level_options": [ + "minimal", + "low", + "medium", + "high" + ], "summaries": true, "visibility": "summary", "continuation": [ @@ -65075,43 +65466,49 @@ "attachment": true, "open_weights": false, "knowledge": "2025-01", - "release_date": "2025-06-17", - "last_updated": "2025-06-17", + "release_date": "2026-05-19", + "last_updated": "2026-05-19", "type": "chat" }, { - "id": "google/gemini-2.5-flash-image", - "name": "Gemini 2.5 Flash Image", - "display_name": "Gemini 2.5 Flash Image", + "id": "google/gemma-4-26b-a4b-it", + "name": "Gemma 4 26B A4B", + "display_name": "Gemma 4 26B A4B", "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ - "text", - "image" + "text" ] }, "limit": { - "context": 32768, - "output": 32768 + "context": 262144, + "output": 131072 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true + } }, "attachment": true, - "open_weights": false, - "knowledge": "2024-06", - "release_date": "2025-08-26", - "last_updated": "2025-08-26" + "open_weights": true, + "release_date": "2026-04-02", + "last_updated": "2026-04-02", + "type": "chat" }, { - "id": "google/gemini-3.5-flash-lite", - "name": "Gemini 3.5 Flash-Lite", - "display_name": "Gemini 3.5 Flash-Lite", + "id": "google/gemini-4-argon", + "name": "Gemini 4 Argon", + "display_name": "Gemini 4 Argon", "modalities": { "input": [ "text", @@ -65128,7 +65525,7 @@ "context": 1048576, "output": 65536 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -65136,58 +65533,72 @@ }, "extra_capabilities": { "reasoning": { - "supported": true + "effort_options": [ + "low", + "medium", + "high" + ] } }, "attachment": true, "open_weights": false, "knowledge": "2026-03", - "release_date": "2026-07-21", - "last_updated": "2026-07-21", + "release_date": "2026-10-13", + "last_updated": "2026-10-13", "type": "chat" }, { - "id": "google/gemini-3.1-flash-image", - "name": "Gemini 3.1 Flash Image", - "display_name": "Gemini 3.1 Flash Image", + "id": "google/gemini-flash-latest", + "name": "Gemini Flash Latest", + "display_name": "Gemini Flash Latest", "modalities": { "input": [ "text", - "image" + "image", + "video", + "audio", + "pdf" ], "output": [ - "text", - "image" + "text" ] }, "limit": { - "context": 32768, - "output": 32768 + "context": 1048576, + "output": 65536 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { "supported": true, "default": true }, "extra_capabilities": { "reasoning": { - "supported": true + "effort_options": [ + "minimal", + "low", + "medium", + "high" + ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-01", - "release_date": "2026-05-28", - "last_updated": "2026-05-28" + "knowledge": "2026-03", + "release_date": "2026-08-13", + "last_updated": "2026-08-13", + "type": "chat" }, { - "id": "google/gemma-4-31b-it", - "name": "Gemma 4 31B It", - "display_name": "Gemma 4 31B It", + "id": "google/gemma-4-26b-a4b-it:thinking", + "name": "Gemma 4 26B A4B Thinking", + "display_name": "Gemma 4 26B A4B Thinking", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" @@ -65195,36 +65606,29 @@ }, "limit": { "context": 262144, - "output": 65536 + "output": 131072 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "supported": true - } - }, - "attachment": false, + "attachment": true, "open_weights": true, - "knowledge": "2025-01", "release_date": "2026-04-02", "last_updated": "2026-04-02", "type": "chat" }, { - "id": "google/gemini-2.5-flash", - "name": "Gemini 2.5 Flash", - "display_name": "Gemini 2.5 Flash", + "id": "google/gemini-3.1-pro-preview-low", + "name": "Gemini 3.1 Pro (Preview Low)", + "display_name": "Gemini 3.1 Pro (Preview Low)", "modalities": { "input": [ "text", "image", - "audio", - "pdf" + "audio" ], "output": [ "text" @@ -65244,15 +65648,12 @@ "reasoning": { "supported": true, "default_enabled": true, - "mode": "budget", - "budget": { - "default": -1, - "min": 0, - "max": 24576, - "auto": -1, - "off": 0, - "unit": "tokens" - }, + "mode": "level", + "level": "high", + "level_options": [ + "low", + "high" + ], "summaries": true, "visibility": "summary", "continuation": [ @@ -65263,18 +65664,19 @@ "attachment": true, "open_weights": false, "knowledge": "2025-01", - "release_date": "2025-06-17", - "last_updated": "2025-06-17", + "release_date": "2026-02-19", + "last_updated": "2026-02-19", "type": "chat" }, { - "id": "google/gemini-3.1-flash-lite", - "name": "Gemini 3.1 Flash-Lite", - "display_name": "Gemini 3.1 Flash-Lite", + "id": "google/gemini-3.6-flash", + "name": "Gemini 3.6 Flash", + "display_name": "Gemini 3.6 Flash", "modalities": { "input": [ "text", "image", + "video", "audio", "pdf" ], @@ -65295,55 +65697,33 @@ "extra_capabilities": { "reasoning": { "supported": true, - "effort_options": [ + "default_enabled": true, + "mode": "level", + "level": "high", + "level_options": [ "minimal", "low", "medium", "high" + ], + "summaries": true, + "visibility": "summary", + "continuation": [ + "thought_signatures" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-01", - "release_date": "2026-05-07", - "last_updated": "2026-05-07", - "type": "chat" - }, - { - "id": "google/gemini-2.5-computer-use-preview-10-2025", - "name": "Gemini 2.5 Computer Use Preview (10-2025)", - "display_name": "Gemini 2.5 Computer Use Preview (10-2025)", - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 64000 - }, - "temperature": true, - "tool_call": true, - "reasoning": { - "supported": true, - "default": true - }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-01", - "release_date": "2025-10-07", - "last_updated": "2025-10-07", + "knowledge": "2026-03", + "release_date": "2026-07-21", + "last_updated": "2026-07-21", "type": "chat" }, { - "id": "google/gemini-3.7-flash", - "name": "Gemini 3.7 Flash", - "display_name": "Gemini 3.7 Flash", + "id": "google/gemini-3.8-flash", + "name": "Gemini 3.8 Flash", + "display_name": "Gemini 3.8 Flash", "modalities": { "input": [ "text", @@ -65387,15 +65767,14 @@ }, "attachment": true, "open_weights": false, - "knowledge": "2026-03", - "release_date": "2026-08-13", - "last_updated": "2026-08-13", + "release_date": "2026-09-02", + "last_updated": "2026-09-02", "type": "chat" }, { - "id": "google/gemma-3-12b-it", - "name": "Gemma 3 12B", - "display_name": "Gemma 3 12B", + "id": "google/gemma-4-26b-a4b-it-cybersecurity", + "name": "Gemma 4 26B A4B Cybersecurity", + "display_name": "Gemma 4 26B A4B Cybersecurity", "modalities": { "input": [ "text", @@ -65406,72 +65785,67 @@ ] }, "limit": { - "context": 128000, - "output": 8192 + "context": 262144, + "output": 32768 }, - "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, "attachment": true, "open_weights": true, - "knowledge": "2024-08", - "release_date": "2025-03-12", - "last_updated": "2025-03-12", + "release_date": "2026-09-19", + "last_updated": "2026-09-19", "type": "chat" }, { - "id": "google/gemini-3-pro-image", - "name": "Gemini 3 Pro Image", - "display_name": "Gemini 3 Pro Image", + "id": "google/gemini-flash-lite-latest", + "name": "Gemini Flash Lite Latest", + "display_name": "Gemini Flash Lite Latest", "modalities": { "input": [ "text", - "image" + "image", + "video", + "audio", + "pdf" ], "output": [ - "text", - "image" + "text" ] }, "limit": { - "context": 65536, + "context": 1048576, "output": 65536 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { "supported": true, "default": true }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "level", - "level": "high", - "level_options": [ + "effort_options": [ + "minimal", "low", + "medium", "high" - ], - "summaries": true, - "visibility": "summary", - "continuation": [ - "thought_signatures" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-01", - "release_date": "2026-05-28", - "last_updated": "2026-05-28" + "knowledge": "2026-03", + "release_date": "2026-07-21", + "last_updated": "2026-07-21", + "type": "chat" }, { - "id": "google/gemma-4-26b-a4b-it", - "name": "Gemma 4 26B A4B", - "display_name": "Gemma 4 26B A4B", + "id": "google/gemma4-31b-splituntied", + "name": "Gemma 4 31B Split-Untied", + "display_name": "Gemma 4 31B Split-Untied", "modalities": { "input": [ "text" @@ -65482,63 +65856,59 @@ }, "limit": { "context": 262144, - "output": 65536 + "output": 32768 }, - "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true - } + "supported": false }, "attachment": false, "open_weights": true, - "release_date": "2026-04-02", - "last_updated": "2026-04-02", + "release_date": "2026-09-17", + "last_updated": "2026-09-17", "type": "chat" }, { - "id": "google/gemini-embedding-2", - "name": "Gemini Embedding 2", - "display_name": "Gemini Embedding 2", + "id": "google/gemma-4-31b-it:thinking", + "name": "Gemma 4 31B Thinking", + "display_name": "Gemma 4 31B Thinking", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 8192, - "output": 8192 + "context": 262144, + "output": 131072 }, - "temperature": false, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, - "open_weights": false, - "knowledge": "2025-11", - "release_date": "2026-04-22", - "last_updated": "2026-04-22", - "type": "embedding" + "attachment": true, + "open_weights": true, + "knowledge": "2025-01", + "release_date": "2026-04-02", + "last_updated": "2026-04-02", + "type": "chat" }, { - "id": "google/gemini-3.1-pro-preview-customtools", - "name": "Gemini 3.1 Pro Preview Custom Tools", - "display_name": "Gemini 3.1 Pro Preview Custom Tools", + "id": "google/gemini-pro-latest", + "name": "Gemini Pro Latest", + "display_name": "Gemini Pro Latest", "modalities": { "input": [ "text", "image", - "audio", - "pdf" + "video", + "audio" ], "output": [ "text" @@ -65548,7 +65918,6 @@ "context": 1048576, "output": 65536 }, - "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -65556,39 +65925,29 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "level", - "level": "high", - "level_options": [ + "effort_options": [ "low", + "medium", "high" - ], - "summaries": true, - "visibility": "summary", - "continuation": [ - "thought_signatures" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-01", - "release_date": "2026-02-19", - "last_updated": "2026-02-19", + "release_date": "2026-03-29", + "last_updated": "2026-03-29", "type": "chat" }, { - "id": "google/gemini-flash-latest", - "name": "Gemini Flash Latest", - "display_name": "Gemini Flash Latest", + "id": "google/gemini-3.1-pro-preview", + "name": "Gemini 3.1 Pro (Preview)", + "display_name": "Gemini 3.1 Pro (Preview)", "modalities": { "input": [ "text", "image", "video", - "audio", - "pdf" + "audio" ], "output": [ "text" @@ -65606,32 +65965,38 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "minimal", + "supported": true, + "default_enabled": true, + "mode": "level", + "level": "high", + "level_options": [ "low", - "medium", "high" + ], + "summaries": true, + "visibility": "summary", + "continuation": [ + "thought_signatures" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-03", - "release_date": "2026-08-13", - "last_updated": "2026-08-13", + "knowledge": "2025-01", + "release_date": "2026-02-19", + "last_updated": "2026-02-19", "type": "chat" }, { - "id": "google/gemini-3.1-pro-preview", - "name": "Gemini 3.1 Pro Preview", - "display_name": "Gemini 3.1 Pro Preview", + "id": "google/gemini-3-flash-preview-thinking", + "name": "Gemini 3 Flash Thinking", + "display_name": "Gemini 3 Flash Thinking", "modalities": { "input": [ "text", "image", "video", - "audio", - "pdf" + "audio" ], "output": [ "text" @@ -65642,7 +66007,7 @@ "output": 65536 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { "supported": true, "default": true @@ -65654,7 +66019,9 @@ "mode": "level", "level": "high", "level_options": [ + "minimal", "low", + "medium", "high" ], "summaries": true, @@ -65667,18 +66034,19 @@ "attachment": true, "open_weights": false, "knowledge": "2025-01", - "release_date": "2026-02-19", - "last_updated": "2026-02-19", + "release_date": "2025-12-17", + "last_updated": "2025-12-17", "type": "chat" }, { - "id": "google/gemini-2.5-flash-lite", - "name": "Gemini 2.5 Flash-Lite", - "display_name": "Gemini 2.5 Flash-Lite", + "id": "google/gemini-3.7-flash", + "name": "Gemini 3.7 Flash", + "display_name": "Gemini 3.7 Flash", "modalities": { "input": [ "text", "image", + "video", "audio", "pdf" ], @@ -65687,27 +66055,27 @@ ] }, "limit": { - "context": 1000000, + "context": 1048576, "output": 65536 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "budget", - "budget": { - "default": -1, - "min": 512, - "max": 24576, - "auto": -1, - "unit": "tokens" - }, + "default_enabled": true, + "mode": "level", + "level": "high", + "level_options": [ + "minimal", + "low", + "medium", + "high" + ], "summaries": true, "visibility": "summary", "continuation": [ @@ -65717,51 +66085,57 @@ }, "attachment": true, "open_weights": false, - "knowledge": "2025-01", - "release_date": "2025-06-17", - "last_updated": "2025-06-17", + "knowledge": "2026-03", + "release_date": "2026-08-13", + "last_updated": "2026-08-13", "type": "chat" }, { - "id": "google/gemma-3-4b-it", - "name": "Gemma 3 4B", - "display_name": "Gemma 3 4B", + "id": "google/gemma-4-31b-it", + "name": "Gemma 4 31B", + "display_name": "Gemma 4 31B", "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 8192 + "context": 262144, + "output": 131072 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true + } }, "attachment": true, "open_weights": true, - "knowledge": "2024-08", - "release_date": "2025-03-12", - "last_updated": "2025-03-12", + "knowledge": "2025-01", + "release_date": "2026-04-02", + "last_updated": "2026-04-02", "type": "chat" }, { - "id": "google/gemini-3.8-flash", - "name": "Gemini 3.8 Flash", - "display_name": "Gemini 3.8 Flash", + "id": "google/gemini-3.1-pro-preview-high", + "name": "Gemini 3.1 Pro (Preview High)", + "display_name": "Gemini 3.1 Pro (Preview High)", "modalities": { "input": [ "text", "image", "video", - "audio", - "pdf" + "audio" ], "output": [ "text" @@ -65784,9 +66158,7 @@ "mode": "level", "level": "high", "level_options": [ - "minimal", "low", - "medium", "high" ], "summaries": true, @@ -65798,14 +66170,15 @@ }, "attachment": true, "open_weights": false, - "release_date": "2026-09-02", - "last_updated": "2026-09-02", + "knowledge": "2025-01", + "release_date": "2026-02-19", + "last_updated": "2026-02-19", "type": "chat" }, { - "id": "google/gemini-4-argon", - "name": "Gemini 4 Argon", - "display_name": "Gemini 4 Argon", + "id": "google/gemini-3.1-flash-lite", + "name": "Gemini 3.1 Flash Lite", + "display_name": "Gemini 3.1 Flash Lite", "modalities": { "input": [ "text", @@ -65822,30 +66195,40 @@ "context": 1048576, "output": 65536 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, "default": true }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "effort_options": [ + "minimal", + "low", + "medium", + "high" + ] + } + }, "attachment": true, "open_weights": false, - "knowledge": "2026-03", - "release_date": "2026-10-13", - "last_updated": "2026-10-13", + "knowledge": "2025-01", + "release_date": "2026-05-07", + "last_updated": "2026-05-07", "type": "chat" }, { - "id": "google/gemini-3.6-flash", - "name": "Gemini 3.6 Flash", - "display_name": "Gemini 3.6 Flash", + "id": "google/gemini-3.1-pro-preview-customtools", + "name": "Gemini 3.1 Pro (Preview Custom Tools)", + "display_name": "Gemini 3.1 Pro (Preview Custom Tools)", "modalities": { "input": [ "text", "image", "video", - "audio", - "pdf" + "audio" ], "output": [ "text" @@ -65868,9 +66251,7 @@ "mode": "level", "level": "high", "level_options": [ - "minimal", "low", - "medium", "high" ], "summaries": true, @@ -65882,49 +66263,21 @@ }, "attachment": true, "open_weights": false, - "knowledge": "2026-03", - "release_date": "2026-07-21", - "last_updated": "2026-07-21", + "knowledge": "2025-01", + "release_date": "2026-02-19", + "last_updated": "2026-02-19", "type": "chat" }, { - "id": "google/gemini-embedding-001", - "name": "Gemini Embedding 001", - "display_name": "Gemini Embedding 001", - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 2048, - "output": 4096 - }, - "temperature": false, - "tool_call": false, - "reasoning": { - "supported": false - }, - "attachment": false, - "open_weights": false, - "knowledge": "2025-05", - "release_date": "2025-05-20", - "last_updated": "2025-05-20", - "type": "embedding" - }, - { - "id": "google/gemini-3.5-flash", - "name": "Gemini 3.5 Flash", - "display_name": "Gemini 3.5 Flash", + "id": "google/gemini-3-flash-preview", + "name": "Gemini 3 Flash (Preview)", + "display_name": "Gemini 3 Flash (Preview)", "modalities": { "input": [ "text", "image", - "audio", - "pdf" + "video", + "audio" ], "output": [ "text" @@ -65962,14 +66315,14 @@ "attachment": true, "open_weights": false, "knowledge": "2025-01", - "release_date": "2026-05-19", - "last_updated": "2026-05-19", + "release_date": "2025-12-17", + "last_updated": "2025-12-17", "type": "chat" }, { - "id": "google/gemma-3-27b-it", - "name": "Gemma 3 27B IT", - "display_name": "Gemma 3 27B IT", + "id": "google/diffusiongemma", + "name": "DiffusionGemma", + "display_name": "DiffusionGemma", "modalities": { "input": [ "text" @@ -65979,25 +66332,34 @@ ] }, "limit": { - "context": 131072, + "context": 262144, "output": 32768 }, "temperature": true, "tool_call": false, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "none", + "xhigh" + ] + } }, "attachment": false, "open_weights": true, - "knowledge": "2024-08", - "release_date": "2025-03-12", - "last_updated": "2025-03-12", + "knowledge": "2025-01", + "release_date": "2026-06-09", + "last_updated": "2026-07-15", "type": "chat" }, { - "id": "google/gemini-flash-lite-latest", - "name": "Gemini Flash-Lite Latest", - "display_name": "Gemini Flash-Lite Latest", + "id": "google/gemini-3.5-flash-lite", + "name": "Gemini 3.5 Flash Lite", + "display_name": "Gemini 3.5 Flash Lite", "modalities": { "input": [ "text", @@ -66022,6 +66384,7 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, "effort_options": [ "minimal", "low", @@ -66038,9 +66401,9 @@ "type": "chat" }, { - "id": "thinkingmachines/inkling", - "name": "Inkling", - "display_name": "Inkling", + "id": "Gryphe/MythoMax-L2-13b", + "name": "MythoMax 13B", + "display_name": "MythoMax 13B", "modalities": { "input": [ "text" @@ -66050,263 +66413,175 @@ ] }, "limit": { - "context": 1048000, - "output": 32000 + "context": 4096, + "output": 3686 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true + "supported": false }, "attachment": false, "open_weights": true, - "release_date": "2026-07-15", - "last_updated": "2026-07-15", + "release_date": "2023-08-10", + "last_updated": "2023-08-10", "type": "chat" }, { - "id": "openai/gpt-5-chat-latest", - "name": "GPT-5 Chat Latest", - "display_name": "GPT-5 Chat Latest", + "id": "mistralai/mistral-medium-3.1", + "name": "Mistral Medium 3.1", + "display_name": "Mistral Medium 3.1", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 16384 + "context": 131072, + "output": 32768 }, - "temperature": false, + "temperature": true, "tool_call": false, "reasoning": { "supported": false }, - "attachment": true, + "attachment": false, "open_weights": false, - "knowledge": "2024-09-30", - "release_date": "2025-08-07", - "last_updated": "2025-08-07", + "knowledge": "2025-06-30", + "release_date": "2025-08-12", + "last_updated": "2025-08-12", "type": "chat" }, { - "id": "openai/gpt-5-search-api", - "name": "GPT-5 Search API", - "display_name": "GPT-5 Search API", + "id": "mistralai/ministral-14b-instruct-2512", + "name": "Ministral 3 14B", + "display_name": "Ministral 3 14B", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 272000, - "output": 128000 + "context": 262144, + "output": 32768 }, - "temperature": false, - "tool_call": true, + "temperature": true, + "tool_call": false, "reasoning": { "supported": false }, "attachment": true, - "open_weights": false, - "release_date": "2025-10-14", - "last_updated": "2025-10-14", + "open_weights": true, + "release_date": "2025-12-02", + "last_updated": "2025-12-02", "type": "chat" }, { - "id": "openai/gpt-5", - "name": "GPT-5", - "display_name": "GPT-5", + "id": "mistralai/mixtral-8x22b-instruct-v0.1", + "name": "Mixtral 8x22B", + "display_name": "Mixtral 8x22B", "modalities": { "input": [ - "text", - "image", - "pdf" - ], - "output": [ "text" - ] - }, - "limit": { - "context": 400000, - "output": 128000 - }, - "temperature": false, - "tool_call": true, - "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", - "effort_options": [ - "minimal", - "low", - "medium", - "high" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" - } - }, - "attachment": true, - "open_weights": false, - "knowledge": "2024-09-30", - "release_date": "2025-08-07", - "last_updated": "2025-08-07", - "type": "chat" - }, - { - "id": "openai/gpt-4o-mini", - "name": "GPT-4o Mini", - "display_name": "GPT-4o Mini", - "modalities": { - "input": [ - "text", - "image", - "pdf" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 16384 + "context": 65536, + "output": 52428 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { "supported": false }, - "attachment": true, - "open_weights": false, - "knowledge": "2023-09", - "release_date": "2024-07-18", - "last_updated": "2024-07-18", + "attachment": false, + "open_weights": true, + "knowledge": "2024-01-31", + "release_date": "2024-04-17", + "last_updated": "2024-04-17", "type": "chat" }, { - "id": "openai/gpt-5.5", - "name": "GPT-5.5", - "display_name": "GPT-5.5", + "id": "mistralai/devstral-small-2505", + "name": "Mistral Devstral Small 2505", + "display_name": "Mistral Devstral Small 2505", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 32768, + "output": 8192 }, - "temperature": false, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", - "effort_options": [ - "low", - "medium", - "high", - "xhigh" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" - } + "supported": false }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-12-01", - "release_date": "2026-04-23", - "last_updated": "2026-04-23", + "attachment": false, + "open_weights": true, + "release_date": "2025-08-02", + "last_updated": "2025-08-02", "type": "chat" }, { - "id": "openai/gpt-4.1-nano", - "name": "GPT-4.1 Nano", - "display_name": "GPT-4.1 Nano", + "id": "mistralai/ministral-14b-2512", + "name": "Ministral 14B", + "display_name": "Ministral 14B", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1047576, + "context": 262144, "output": 32768 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { "supported": false }, - "attachment": true, - "open_weights": false, - "knowledge": "2024-04", - "release_date": "2025-04-14", - "last_updated": "2025-04-14", + "attachment": false, + "open_weights": true, + "release_date": "2025-12-02", + "last_updated": "2025-12-02", "type": "chat" }, { - "id": "openai/o1", - "name": "o1", - "display_name": "o1", + "id": "mistralai/mistral-small-4-119b-2603:thinking", + "name": "Mistral Small 4 119B Thinking", + "display_name": "Mistral Small 4 119B Thinking", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 100000 + "context": 262144, + "output": 16384 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -66314,29 +66589,23 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", "effort_options": [ - "low", - "medium", + "none", "high" - ], - "visibility": "hidden" + ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2023-09", - "release_date": "2024-12-05", - "last_updated": "2024-12-05", + "open_weights": true, + "knowledge": "2025-06", + "release_date": "2026-03-16", + "last_updated": "2026-03-16", "type": "chat" }, { - "id": "openai/gpt-6-astra", - "name": "GPT-6 Astra", - "display_name": "GPT-6 Astra", + "id": "mistralai/mistral-medium-3.5", + "name": "Mistral Medium 3.5", + "display_name": "Mistral Medium 3.5", "modalities": { "input": [ "text", @@ -66347,10 +66616,9 @@ ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 256000, + "output": 32768 }, - "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -66358,139 +66626,132 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" + "none", + "high" ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2026-04-30", - "release_date": "2026-09-04", - "last_updated": "2026-09-04", + "open_weights": true, + "release_date": "2026-04-29", + "last_updated": "2026-04-29", "type": "chat" }, { - "id": "openai/gpt-5.4", - "name": "GPT-5.4", - "display_name": "GPT-5.4", + "id": "mistralai/mistral-nemo-instruct-2407", + "name": "Mistral Nemo", + "display_name": "Mistral Nemo", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 16384, + "output": 8192 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": false + "supported": false }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "none", - "effort_options": [ - "none", - "low", - "medium", - "high", - "xhigh" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" - } + "attachment": false, + "open_weights": true, + "knowledge": "2024-07", + "release_date": "2024-07-18", + "last_updated": "2024-07-18", + "type": "chat" + }, + { + "id": "mistralai/codestral-2508", + "name": "Codestral 2508", + "display_name": "Codestral 2508", + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] }, - "attachment": true, + "limit": { + "context": 256000, + "output": 32768 + }, + "temperature": true, + "tool_call": false, + "reasoning": { + "supported": false + }, + "attachment": false, "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-03-05", - "last_updated": "2026-03-05", + "release_date": "2025-07-30", + "last_updated": "2025-07-30", "type": "chat" }, { - "id": "openai/gpt-5.1-chat-latest", - "name": "GPT-5.1 Chat Latest", - "display_name": "GPT-5.1 Chat Latest", + "id": "mistralai/mistral-saba", + "name": "Mistral Saba", + "display_name": "Mistral Saba", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 16384 + "context": 32768, + "output": 26214 }, - "temperature": false, - "tool_call": true, + "temperature": true, + "tool_call": false, "reasoning": { "supported": false }, - "attachment": true, + "attachment": false, "open_weights": false, "knowledge": "2024-09-30", - "release_date": "2025-11-13", - "last_updated": "2025-11-13", + "release_date": "2025-02-17", + "last_updated": "2025-02-17", "type": "chat" }, { - "id": "openai/gpt-audio", - "name": "GPT Audio", - "display_name": "GPT Audio", + "id": "mistralai/ministral-8b-2512", + "name": "Ministral 8B", + "display_name": "Ministral 8B", "modalities": { "input": [ - "text", - "audio" + "text" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 16384 + "context": 262144, + "output": 32768 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { "supported": false }, - "attachment": true, - "open_weights": false, - "knowledge": "2023-10-01", - "release_date": "2025-08-28", - "last_updated": "2025-08-28", + "attachment": false, + "open_weights": true, + "release_date": "2025-12-02", + "last_updated": "2025-12-02", "type": "chat" }, { - "id": "openai/gpt-oss-20b", - "name": "GPT-OSS 20B", - "display_name": "GPT-OSS 20B", + "id": "mistralai/ministral-3b-2512", + "name": "Ministral 3B", + "display_name": "Ministral 3B", "modalities": { "input": [ "text" @@ -66500,106 +66761,94 @@ ] }, "limit": { - "context": 128000, - "output": 32000 + "context": 131072, + "output": 32768 }, "temperature": true, "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true - } + "supported": false }, "attachment": false, "open_weights": true, - "knowledge": "2024-06", - "release_date": "2025-08-05", - "last_updated": "2025-08-05", + "release_date": "2025-12-02", + "last_updated": "2025-12-02", "type": "chat" }, { - "id": "openai/gpt-4-turbo", - "name": "GPT-4 Turbo", - "display_name": "GPT-4 Turbo", + "id": "mistralai/devstral-2-123b-instruct-2512", + "name": "Devstral 2 123B", + "display_name": "Devstral 2 123B", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 4096 + "context": 262144, + "output": 65536 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { "supported": false }, - "attachment": true, - "open_weights": false, - "knowledge": "2023-12", - "release_date": "2023-11-06", - "last_updated": "2024-04-09", + "attachment": false, + "open_weights": true, + "knowledge": "2025-12", + "release_date": "2025-12-09", + "last_updated": "2025-12-09", "type": "chat" }, { - "id": "openai/gpt-4o", - "name": "GPT-4o", - "display_name": "GPT-4o", + "id": "mistralai/mistral-medium-3", + "name": "Mistral Medium 3", + "display_name": "Mistral Medium 3", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 16384 + "context": 131072, + "output": 32768 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { "supported": false }, "attachment": true, "open_weights": false, - "knowledge": "2023-09", - "release_date": "2024-05-13", - "last_updated": "2024-08-06", + "knowledge": "2025-03-31", + "release_date": "2025-05-07", + "last_updated": "2025-05-07", "type": "chat" }, { - "id": "openai/o3", - "name": "o3", - "display_name": "o3", + "id": "mistralai/mistral-medium-3.5:thinking", + "name": "Mistral Medium 3.5 Thinking", + "display_name": "Mistral Medium 3.5 Thinking", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 100000 + "context": 256000, + "output": 32768 }, - "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -66607,34 +66856,26 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", "effort_options": [ - "low", - "medium", + "none", "high" - ], - "visibility": "hidden" + ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2024-05", - "release_date": "2025-04-16", - "last_updated": "2025-04-16", + "open_weights": true, + "release_date": "2026-04-30", + "last_updated": "2026-04-30", "type": "chat" }, { - "id": "openai/gpt-5.2-chat-latest", - "name": "GPT-5.2 Chat Latest", - "display_name": "GPT-5.2 Chat Latest", + "id": "mistralai/mistral-small-3.1-24b-instruct", + "name": "Mistral Small 3.1 24B (2503)", + "display_name": "Mistral Small 3.1 24B (2503)", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" @@ -66642,24 +66883,24 @@ }, "limit": { "context": 128000, - "output": 16384 + "output": 102400 }, - "temperature": false, - "tool_call": true, + "temperature": true, + "tool_call": false, "reasoning": { "supported": false }, "attachment": true, - "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2025-12-11", - "last_updated": "2025-12-11", + "open_weights": true, + "knowledge": "2023-10-31", + "release_date": "2025-03-17", + "last_updated": "2025-03-17", "type": "chat" }, { - "id": "openai/gpt-6-sol", - "name": "GPT-6 Sol", - "display_name": "GPT-6 Sol", + "id": "mistralai/mistral-large-4", + "name": "Mistral Large 4", + "display_name": "Mistral Large 4", "modalities": { "input": [ "text", @@ -66670,10 +66911,10 @@ ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 524288, + "output": 262144 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -66683,169 +66924,147 @@ "reasoning": { "effort_options": [ "none", - "low", - "medium", - "high", - "xhigh", - "max" + "high" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-04-20", - "release_date": "2026-09-22", - "last_updated": "2026-09-22", + "release_date": "2026-10-06", + "last_updated": "2026-10-06", "type": "chat" }, { - "id": "openai/o4-mini", - "name": "o4 Mini", - "display_name": "o4 Mini", + "id": "mistralai/mistral-large", + "name": "Mistral Large 2411", + "display_name": "Mistral Large 2411", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 100000 + "context": 128000, + "output": 102400 }, - "temperature": false, - "tool_call": true, + "temperature": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", - "effort_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" - } + "supported": false }, - "attachment": true, + "attachment": false, "open_weights": false, - "knowledge": "2024-05", - "release_date": "2025-04-16", - "last_updated": "2025-04-16", + "knowledge": "2024-11-30", + "release_date": "2024-02-26", + "last_updated": "2024-02-26", "type": "chat" }, { - "id": "openai/gpt-5.6-sol", - "name": "GPT-5.6 Sol", - "display_name": "GPT-5.6 Sol", + "id": "mistralai/mistral-small-3.2-24b-instruct", + "name": "Mistral Small 3.2 24B (2506)", + "display_name": "Mistral Small 3.2 24B (2506)", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 128000, + "output": 16384 }, - "temperature": false, - "tool_call": true, + "temperature": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true + "supported": false }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", - "effort_options": [ - "none", - "low", - "medium", - "high", - "xhigh", - "max" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" - } + "attachment": false, + "open_weights": true, + "knowledge": "2023-10-31", + "release_date": "2025-06-20", + "last_updated": "2025-06-20", + "type": "chat" + }, + { + "id": "mistralai/mistral-small-24b-instruct-2501", + "name": "Mistral Small 24B", + "display_name": "Mistral Small 24B", + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-02-16", - "release_date": "2026-07-09", - "last_updated": "2026-07-09", + "limit": { + "context": 32768, + "output": 8192 + }, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": false + }, + "attachment": false, + "open_weights": true, + "knowledge": "2023-10-31", + "release_date": "2025-01-30", + "last_updated": "2025-01-30", "type": "chat" }, { - "id": "openai/gpt-4o-2024-11-20", - "name": "GPT-4o (2024-11-20)", - "display_name": "GPT-4o (2024-11-20)", + "id": "mistralai/mistral-small-4-119b-2603", + "name": "Mistral Small 4 119B", + "display_name": "Mistral Small 4 119B", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 128000, + "context": 262144, "output": 16384 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, "attachment": true, - "open_weights": false, - "knowledge": "2023-09", - "release_date": "2024-11-20", - "last_updated": "2024-11-20", + "open_weights": true, + "knowledge": "2025-06", + "release_date": "2026-03-16", + "last_updated": "2026-03-16", "type": "chat" }, { - "id": "openai/gpt-5.6-terra", - "name": "GPT-5.6 Terra", - "display_name": "GPT-5.6 Terra", + "id": "particle-ai/particle-1", + "name": "Particle-1", + "display_name": "Particle-1", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 1048576, + "output": 131072 }, - "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -66853,38 +67072,27 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", "effort_options": [ "none", + "minimal", "low", "medium", "high", "xhigh", "max" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-02-16", - "release_date": "2026-07-09", - "last_updated": "2026-07-09", + "release_date": "2026-10-29", + "last_updated": "2026-10-29", "type": "chat" }, { - "id": "openai/gpt-oss-120b", - "name": "GPT-OSS 120B", - "display_name": "GPT-OSS 120B", + "id": "NousResearch/hermes-4-405b:thinking", + "name": "Hermes 4 Large (Thinking)", + "display_name": "Hermes 4 Large (Thinking)", "modalities": { "input": [ "text" @@ -66895,154 +67103,106 @@ }, "limit": { "context": 128000, - "output": 32000 + "output": 8192 }, "temperature": true, "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true - } + "supported": false }, "attachment": false, "open_weights": true, - "knowledge": "2024-06", - "release_date": "2025-08-05", - "last_updated": "2025-08-05", + "knowledge": "2024-08-31", + "release_date": "2025-08-26", + "last_updated": "2025-08-26", "type": "chat" }, { - "id": "openai/gpt-6-luna", - "name": "GPT-6 Luna", - "display_name": "GPT-6 Luna", + "id": "NousResearch/hermes-3-llama-3.1-70b", + "name": "Hermes 3 70B", + "display_name": "Hermes 3 70B", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 65536, + "output": 8192 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "none", - "low", - "medium", - "high", - "xhigh", - "max" - ] - } + "supported": false }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-05-18", - "release_date": "2026-09-22", - "last_updated": "2026-09-22", + "attachment": false, + "open_weights": true, + "knowledge": "2023-12-31", + "release_date": "2024-08-18", + "last_updated": "2024-08-18", "type": "chat" }, { - "id": "openai/gpt-5-mini", - "name": "GPT-5 Mini", - "display_name": "GPT-5 Mini", + "id": "NousResearch/hermes-4-405b", + "name": "Hermes 4 Large", + "display_name": "Hermes 4 Large", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 128000, + "output": 8192 }, - "temperature": false, - "tool_call": true, + "temperature": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", - "effort_options": [ - "minimal", - "low", - "medium", - "high" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" - } + "supported": false }, - "attachment": true, - "open_weights": false, - "knowledge": "2024-05-30", - "release_date": "2025-08-07", - "last_updated": "2025-08-07", + "attachment": false, + "open_weights": true, + "knowledge": "2024-08-31", + "release_date": "2025-08-26", + "last_updated": "2025-08-26", "type": "chat" }, { - "id": "openai/gpt-4o-2024-08-06", - "name": "GPT-4o (2024-08-06)", - "display_name": "GPT-4o (2024-08-06)", + "id": "featherless-ai/Qwerky-72B", + "name": "Qwerky 72B", + "display_name": "Qwerky 72B", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 16384 + "context": 32000, + "output": 8192 }, - "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { "supported": false }, - "attachment": true, - "open_weights": false, - "knowledge": "2023-09", - "release_date": "2024-08-06", - "last_updated": "2024-08-06", + "attachment": false, + "open_weights": true, + "release_date": "2024-01-01", + "last_updated": "2025-03-20", "type": "chat" }, { - "id": "openai/gpt-4", - "name": "GPT-4", - "display_name": "GPT-4", + "id": "NeverSleep/Lumimaid-v0.2-70B", + "name": "Lumimaid v0.2", + "display_name": "Lumimaid v0.2", "modalities": { "input": [ "text" @@ -67052,25 +67212,23 @@ ] }, "limit": { - "context": 8192, + "context": 16384, "output": 8192 }, - "temperature": true, "tool_call": false, "reasoning": { "supported": false }, "attachment": false, - "open_weights": false, - "knowledge": "2023-11", - "release_date": "2023-11-06", - "last_updated": "2024-04-09", + "open_weights": true, + "release_date": "2024-01-01", + "last_updated": "2024-07-01", "type": "chat" }, { - "id": "openai/o3-mini", - "name": "o3 Mini", - "display_name": "o3 Mini", + "id": "nanogpt/coding-router:low", + "name": "Coding Router Low", + "display_name": "Coding Router Low", "modalities": { "input": [ "text" @@ -67080,266 +67238,245 @@ ] }, "limit": { - "context": 200000, - "output": 100000 + "context": 1000000, + "output": 128000 }, - "temperature": false, "tool_call": true, "reasoning": { "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", - "effort_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" - } - }, "attachment": false, "open_weights": false, - "knowledge": "2024-05", - "release_date": "2024-12-20", - "last_updated": "2025-01-29", + "release_date": "2026-05-12", + "last_updated": "2026-05-12", "type": "chat" }, { - "id": "openai/gpt-5.1", - "name": "GPT-5.1", - "display_name": "GPT-5.1", + "id": "nanogpt/coding-router", + "name": "Coding Router", + "display_name": "Coding Router", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 400000, + "context": 1000000, "output": 128000 }, - "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "none", - "effort_options": [ - "none", - "low", - "medium", - "high" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" - } + "default": true }, - "attachment": true, + "attachment": false, "open_weights": false, - "knowledge": "2024-09-30", - "release_date": "2025-11-13", - "last_updated": "2025-11-13", + "release_date": "2026-05-12", + "last_updated": "2026-05-12", "type": "chat" }, { - "id": "openai/whisper-1", - "name": "Whisper", - "display_name": "Whisper", + "id": "nanogpt/coding-router:medium", + "name": "Coding Router Medium", + "display_name": "Coding Router Medium", "modalities": { "input": [ - "audio" + "text" ], "output": [ "text" ] }, "limit": { - "context": 448000, - "output": 4096 + "context": 1000000, + "output": 128000 }, - "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": true, + "attachment": false, "open_weights": false, - "release_date": "2023-03-01", - "last_updated": "2023-03-01" + "release_date": "2026-05-12", + "last_updated": "2026-05-12", + "type": "chat" }, { - "id": "openai/gpt-5.6-luna", - "name": "GPT-5.6 Luna", - "display_name": "GPT-5.6 Luna", + "id": "nanogpt/coding-router:high", + "name": "Coding Router High", + "display_name": "Coding Router High", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1050000, + "context": 1000000, "output": 128000 }, - "temperature": false, "tool_call": true, "reasoning": { "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", - "effort_options": [ - "none", - "low", - "medium", - "high", - "xhigh", - "max" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" - } - }, - "attachment": true, + "attachment": false, "open_weights": false, - "knowledge": "2026-02-16", - "release_date": "2026-07-09", - "last_updated": "2026-07-09", + "release_date": "2026-05-12", + "last_updated": "2026-05-12", "type": "chat" }, { - "id": "openai/gpt-audio-mini", - "name": "GPT Audio Mini", - "display_name": "GPT Audio Mini", + "id": "nanogpt/coding-router:max", + "name": "Coding Router Max", + "display_name": "Coding Router Max", "modalities": { "input": [ - "text", - "audio" + "text" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 16384 + "context": 1000000, + "output": 128000 }, - "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": true, + "attachment": false, "open_weights": false, - "knowledge": "2023-10-01", - "release_date": "2025-10-06", - "last_updated": "2025-10-06", + "release_date": "2026-05-12", + "last_updated": "2026-05-12", "type": "chat" }, { - "id": "openai/gpt-5.4-mini", - "name": "GPT-5.4 Mini", - "display_name": "GPT-5.4 Mini", + "id": "xingchen-agi/xing4.0-29b-a4b", + "name": "Xing 4.0 29B A4B", + "display_name": "Xing 4.0 29B A4B", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 262144, + "output": 262144 }, - "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "none", "effort_options": [ "none", + "minimal", "low", "medium", "high", - "xhigh" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", + "xhigh", + "max" + ] + } + }, + "attachment": false, + "open_weights": true, + "release_date": "2026-10-24", + "last_updated": "2026-10-24", + "type": "chat" + }, + { + "id": "prism-ml/ternary-bonsai-2-27b", + "name": "Ternary Bonsai 2 27B", + "display_name": "Ternary Bonsai 2 27B", + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 262144, + "output": 32768 + }, + "tool_call": true, + "reasoning": { + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "effort_options": [ "medium", - "high" - ], - "visibility": "hidden" + "xhigh" + ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-03-17", - "last_updated": "2026-03-17", + "open_weights": true, + "release_date": "2026-09-18", + "last_updated": "2026-09-18", "type": "chat" }, { - "id": "openai/gpt-4.1-mini", - "name": "GPT-4.1 Mini", - "display_name": "GPT-4.1 Mini", + "id": "prism-ml/ternary-bonsai-2-9b", + "name": "Ternary Bonsai2 9B", + "display_name": "Ternary Bonsai2 9B", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1047576, + "context": 262144, + "output": 65536 + }, + "tool_call": true, + "reasoning": { + "supported": true, + "default": true + }, + "attachment": false, + "open_weights": true, + "release_date": "2026-10-23", + "last_updated": "2026-10-23", + "type": "chat" + }, + { + "id": "aion-labs/aion-2.0", + "name": "AionLabs: Aion-2.0", + "display_name": "AionLabs: Aion-2.0", + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, "output": 32768 }, "temperature": true, @@ -67347,70 +67484,44 @@ "reasoning": { "supported": false }, - "attachment": true, + "attachment": false, "open_weights": false, - "knowledge": "2024-04", - "release_date": "2025-04-14", - "last_updated": "2025-04-14", + "release_date": "2025-12-21", + "last_updated": "2025-12-21", "type": "chat" }, { - "id": "openai/gpt-5.4-nano", - "name": "GPT-5.4 Nano", - "display_name": "GPT-5.4 Nano", + "id": "aion-labs/aion-3.0", + "name": "AionLabs: Aion 3.0", + "display_name": "AionLabs: Aion 3.0", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 131072, + "output": 32768 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "none", - "effort_options": [ - "none", - "low", - "medium", - "high", - "xhigh" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" - } + "default": true }, - "attachment": true, + "attachment": false, "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-03-17", - "last_updated": "2026-03-17", + "release_date": "2026-05-05", + "last_updated": "2026-05-05", "type": "chat" }, { - "id": "openai/gpt-oss-safeguard-20b", - "name": "GPT OSS Safeguard 20B", - "display_name": "GPT OSS Safeguard 20B", + "id": "aion-labs/aion-3.5-mini", + "name": "AionLabs: Aion 3.5 Mini", + "display_name": "AionLabs: Aion 3.5 Mini", "modalities": { "input": [ "text" @@ -67420,55 +67531,52 @@ ] }, "limit": { - "context": 4096, - "output": 4096 + "context": 262144, + "output": 32768 }, - "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { "supported": true, "default": true }, "attachment": false, - "open_weights": true, - "release_date": "2025-10-29", - "last_updated": "2025-10-29", + "open_weights": false, + "release_date": "2026-09-23", + "last_updated": "2026-09-23", "type": "chat" }, { - "id": "openai/gpt-4.1", - "name": "GPT-4.1", - "display_name": "GPT-4.1", + "id": "aion-labs/aion-3.0-mini", + "name": "AionLabs: Aion 3.0 Mini", + "display_name": "AionLabs: Aion 3.0 Mini", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1047576, + "context": 131072, "output": 32768 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": true, + "attachment": false, "open_weights": false, - "knowledge": "2024-04", - "release_date": "2025-04-14", - "last_updated": "2025-04-14", + "release_date": "2026-05-14", + "last_updated": "2026-05-14", "type": "chat" }, { - "id": "openai/gpt-3.5-turbo", - "name": "GPT-3.5 Turbo", - "display_name": "GPT-3.5 Turbo", + "id": "aion-labs/aion-3.5", + "name": "AionLabs: Aion 3.5", + "display_name": "AionLabs: Aion 3.5", "modalities": { "input": [ "text" @@ -67478,37 +67586,34 @@ ] }, "limit": { - "context": 16385, - "output": 4096 + "context": 262144, + "output": 32768 }, - "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, "attachment": false, "open_weights": false, - "knowledge": "2021-09-01", - "release_date": "2023-03-01", - "last_updated": "2023-11-06", + "release_date": "2026-09-23", + "last_updated": "2026-09-23", "type": "chat" }, { - "id": "openai/gpt-5.3-chat-latest", - "name": "GPT-5.3 Chat Latest", - "display_name": "GPT-5.3 Chat Latest", + "id": "aion-labs/aion-rp-llama-3.1-8b", + "name": "Llama 3.1 8b (uncensored)", + "display_name": "Llama 3.1 8b (uncensored)", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 128000, + "context": 32768, "output": 16384 }, "temperature": true, @@ -67516,17 +67621,17 @@ "reasoning": { "supported": false }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-03-03", - "last_updated": "2026-03-03", + "attachment": false, + "open_weights": true, + "knowledge": "2023-12-31", + "release_date": "2025-02-04", + "last_updated": "2025-02-04", "type": "chat" }, { - "id": "openai/gpt-oss-safeguard-120b", - "name": "GPT OSS Safeguard 120B", - "display_name": "GPT OSS Safeguard 120B", + "id": "meganova-ai/manta-pro-1.0", + "name": "Manta Pro 1.0", + "display_name": "Manta Pro 1.0", "modalities": { "input": [ "text" @@ -67536,272 +67641,197 @@ ] }, "limit": { - "context": 4096, - "output": 4096 + "context": 65536, + "output": 32768 }, - "temperature": true, "tool_call": false, "reasoning": { - "supported": true, - "default": true + "supported": false }, "attachment": false, - "open_weights": true, - "release_date": "2025-10-29", - "last_updated": "2025-10-29", + "open_weights": false, + "release_date": "2025-09-20", + "last_updated": "2025-12-20", "type": "chat" }, { - "id": "openai/gpt-6.1-terra", - "name": "GPT-6.1 Terra", - "display_name": "GPT-6.1 Terra", + "id": "meganova-ai/manta-mini-1.0", + "name": "Manta Mini 1.0", + "display_name": "Manta Mini 1.0", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 8192, + "output": 8192 }, - "temperature": false, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "none", - "low", - "medium", - "high", - "xhigh", - "max" - ] - } + "supported": false }, - "attachment": true, + "attachment": false, "open_weights": false, - "knowledge": "2026-06-01", - "release_date": "2026-10-26", - "last_updated": "2026-10-26", + "release_date": "2025-09-20", + "last_updated": "2025-12-20", "type": "chat" }, { - "id": "openai/gpt-4o-2024-05-13", - "name": "GPT-4o (2024-05-13)", - "display_name": "GPT-4o (2024-05-13)", + "id": "meganova-ai/manta-flash-1.0", + "name": "Manta Flash 1.0", + "display_name": "Manta Flash 1.0", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 4096 + "context": 16384, + "output": 16384 }, - "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { "supported": false }, - "attachment": true, + "attachment": false, "open_weights": false, - "knowledge": "2023-09", - "release_date": "2024-05-13", - "last_updated": "2024-05-13", + "release_date": "2025-09-20", + "last_updated": "2025-12-20", "type": "chat" }, { - "id": "openai/gpt-5.2", - "name": "GPT-5.2", - "display_name": "GPT-5.2", + "id": "liquid/lfm-2.5-2.6b", + "name": "LFM2.5 2.6B", + "display_name": "LFM2.5 2.6B", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 128000, + "output": 32768 }, - "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "none", "effort_options": [ - "none", + "minimal", "low", "medium", "high", "xhigh" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2025-12-11", - "last_updated": "2025-12-11", + "attachment": false, + "open_weights": true, + "release_date": "2026-08-12", + "last_updated": "2026-08-12", "type": "chat" }, { - "id": "openai/gpt-6.1-sol", - "name": "GPT-6.1 Sol", - "display_name": "GPT-6.1 Sol", + "id": "liquid/lfm-2.5-24b-a2b", + "name": "LFM2.5 24B A2B", + "display_name": "LFM2.5 24B A2B", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 128000, + "output": 8192 }, - "temperature": false, "tool_call": true, "reasoning": { "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" - ] - } - }, - "attachment": true, + "attachment": false, "open_weights": false, - "knowledge": "2026-04-30", - "release_date": "2026-09-29", - "last_updated": "2026-09-29", + "release_date": "2026-10-14", + "last_updated": "2026-10-14", "type": "chat" }, { - "id": "openai/gpt-5-nano", - "name": "GPT-5 Nano", - "display_name": "GPT-5 Nano", + "id": "moondream/moondream3.1-9b-a2b", + "name": "Moondream 3.1 9B A2B", + "display_name": "Moondream 3.1 9B A2B", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 32000, + "output": 4096 }, - "temperature": false, - "tool_call": true, + "tool_call": false, "reasoning": { "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", - "effort_options": [ - "minimal", - "low", - "medium", - "high" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" - } - }, "attachment": true, "open_weights": false, - "knowledge": "2024-05-30", - "release_date": "2025-08-07", - "last_updated": "2025-08-07", + "release_date": "2026-10-16", + "last_updated": "2026-10-16", "type": "chat" }, { - "id": "mistral/magistral-medium-latest", - "name": "Magistral Medium (latest)", - "display_name": "Magistral Medium (latest)", + "id": "moondream/moondream3-preview", + "name": "Moondream 3 Preview", + "display_name": "Moondream 3 Preview", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 16384 + "context": 32000, + "output": 4096 }, - "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { "supported": true, "default": true }, - "attachment": false, + "attachment": true, "open_weights": false, - "knowledge": "2025-06", - "release_date": "2025-03-17", - "last_updated": "2025-03-20", + "release_date": "2026-10-16", + "last_updated": "2026-10-16", "type": "chat" }, { - "id": "mistral/devstral-2512", - "name": "Devstral 2", - "display_name": "Devstral 2", + "id": "TheDrummer/Rocinante-12B-v1.1", + "name": "Rocinante 12b", + "display_name": "Rocinante 12b", "modalities": { "input": [ "text" @@ -67811,25 +67841,23 @@ ] }, "limit": { - "context": 256000, - "output": 256000 + "context": 16384, + "output": 8192 }, - "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { "supported": false }, "attachment": false, "open_weights": true, - "knowledge": "2025-12", - "release_date": "2025-12-09", - "last_updated": "2025-12-09", + "release_date": "2024-01-01", + "last_updated": "2024-01-01", "type": "chat" }, { - "id": "mistral/mistral-medium-latest", - "name": "Mistral Medium (latest)", - "display_name": "Mistral Medium (latest)", + "id": "TheDrummer/Artemis-v1.1", + "name": "TheDrummer/Artemis v1.1", + "display_name": "TheDrummer/Artemis v1.1", "modalities": { "input": [ "text", @@ -67841,9 +67869,8 @@ }, "limit": { "context": 262144, - "output": 262144 + "output": 32768 }, - "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -67851,72 +67878,123 @@ }, "attachment": true, "open_weights": true, - "release_date": "2026-04-29", - "last_updated": "2026-04-29", + "release_date": "2026-09-06", + "last_updated": "2026-09-06", "type": "chat" }, { - "id": "mistral/mistral-medium-2505", - "name": "Mistral Medium 3", - "display_name": "Mistral Medium 3", + "id": "TheDrummer/Cydonia-24B-v2", + "name": "The Drummer Cydonia 24B v2", + "display_name": "The Drummer Cydonia 24B v2", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 128000 + "context": 32768, + "output": 32768 + }, + "tool_call": false, + "reasoning": { + "supported": false + }, + "attachment": false, + "open_weights": true, + "release_date": "2025-02-17", + "last_updated": "2025-02-17", + "type": "chat" + }, + { + "id": "TheDrummer/Cydonia-24B-v4.1", + "name": "The Drummer Cydonia 24B v4.1", + "display_name": "The Drummer Cydonia 24B v4.1", + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "output": 117964 }, "temperature": true, "tool_call": true, "reasoning": { "supported": false }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-05", - "release_date": "2025-05-07", - "last_updated": "2025-05-07", + "attachment": false, + "open_weights": true, + "knowledge": "2024-04-30", + "release_date": "2025-09-27", + "last_updated": "2025-09-27", "type": "chat" }, { - "id": "mistral/mistral-large-2512", - "name": "Mistral Large 3", - "display_name": "Mistral Large 3", + "id": "TheDrummer/Cydonia-24B-v4.3", + "name": "The Drummer Cydonia 24B v4.3", + "display_name": "The Drummer Cydonia 24B v4.3", + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "output": 32768 + }, + "tool_call": false, + "reasoning": { + "supported": false + }, + "attachment": false, + "open_weights": true, + "release_date": "2025-12-25", + "last_updated": "2025-12-25", + "type": "chat" + }, + { + "id": "TheDrummer/skyfall-36b-v2", + "name": "TheDrummer Skyfall 36B V2", + "display_name": "TheDrummer Skyfall 36B V2", "modalities": { "input": [ "text", - "image" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 256000, - "output": 256000 + "context": 32768, + "output": 29491 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { "supported": false }, "attachment": true, "open_weights": true, - "knowledge": "2024-11", - "release_date": "2025-12-02", - "last_updated": "2025-12-02", + "knowledge": "2024-06-30", + "release_date": "2025-03-10", + "last_updated": "2025-03-10", "type": "chat" }, { - "id": "mistral/codestral-latest", - "name": "Codestral (latest)", - "display_name": "Codestral (latest)", + "id": "TheDrummer/Anubis-70B-v1.1", + "name": "Anubis 70B v1.1", + "display_name": "Anubis 70B v1.1", "modalities": { "input": [ "text" @@ -67926,25 +68004,23 @@ ] }, "limit": { - "context": 256000, - "output": 4096 + "context": 32000, + "output": 16384 }, - "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { "supported": false }, "attachment": false, "open_weights": true, - "knowledge": "2024-10", - "release_date": "2024-05-29", - "last_updated": "2025-01-04", + "release_date": "2024-01-01", + "last_updated": "2024-01-01", "type": "chat" }, { - "id": "mistral/devstral-medium-latest", - "name": "Devstral 2 (latest)", - "display_name": "Devstral 2 (latest)", + "id": "TheDrummer/Cydonia-24B-v4", + "name": "The Drummer Cydonia 24B v4", + "display_name": "The Drummer Cydonia 24B v4", "modalities": { "input": [ "text" @@ -67954,168 +68030,143 @@ ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 32768, + "output": 32768 }, - "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { "supported": false }, "attachment": false, "open_weights": true, - "knowledge": "2025-12", - "release_date": "2025-12-02", - "last_updated": "2025-12-02", + "release_date": "2025-07-22", + "last_updated": "2025-07-22", "type": "chat" }, { - "id": "mistral/pixtral-large-latest", - "name": "Pixtral Large (latest)", - "display_name": "Pixtral Large (latest)", + "id": "TheDrummer/UnslopNemo-12B-v4.1", + "name": "UnslopNemo 12b v4", + "display_name": "UnslopNemo 12b v4", "modalities": { "input": [ "text", - "image" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 128000 + "context": 8192, + "output": 26214 }, - "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { "supported": false }, "attachment": true, "open_weights": true, - "knowledge": "2024-11", - "release_date": "2024-11-01", - "last_updated": "2024-11-04", + "release_date": "2024-01-01", + "last_updated": "2024-01-01", "type": "chat" }, { - "id": "mistral/mistral-large-latest", - "name": "Mistral Large (latest)", - "display_name": "Mistral Large (latest)", + "id": "TheDrummer/Anubis-70B-v1", + "name": "Anubis 70B v1", + "display_name": "Anubis 70B v1", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 65536, + "output": 16384 }, - "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { "supported": false }, - "attachment": true, + "attachment": false, "open_weights": true, - "knowledge": "2024-11", - "release_date": "2024-11-01", - "last_updated": "2025-12-02", + "release_date": "2024-01-01", + "last_updated": "2024-01-01", "type": "chat" }, { - "id": "mistral/mistral-small-latest", - "name": "Mistral Small (latest)", - "display_name": "Mistral Small (latest)", + "id": "TheDrummer/Magidonia-24B-v4.3", + "name": "The Drummer Magidonia 24B v4.3", + "display_name": "The Drummer Magidonia 24B v4.3", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 256000, - "output": 256000 + "context": 32768, + "output": 32768 }, - "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true + "supported": false }, - "attachment": true, + "attachment": false, "open_weights": true, - "knowledge": "2025-06", - "release_date": "2026-03-16", - "last_updated": "2026-03-16", + "release_date": "2025-12-25", + "last_updated": "2025-12-25", "type": "chat" }, { - "id": "moonshot/kimi-k3", - "name": "Kimi K3", - "display_name": "Kimi K3", + "id": "microsoft/wizardlm-2-8x22b", + "name": "WizardLM-2 8x22B", + "display_name": "WizardLM-2 8x22B", "modalities": { "input": [ "text", - "image" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 1048576 + "context": 65536, + "output": 8192 }, - "temperature": false, - "tool_call": true, + "temperature": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "effort_options": [ - "none", - "minimal", - "low", - "medium", - "high", - "xhigh", - "max" - ] - } + "supported": false }, "attachment": true, "open_weights": true, - "release_date": "2026-07-16", - "last_updated": "2026-07-16", + "release_date": "2024-04-15", + "last_updated": "2024-04-15", "type": "chat" }, { - "id": "moonshot/kimi-k2.6", - "name": "Kimi K2.6", - "display_name": "Kimi K2.6", + "id": "anthropic/claude-opus-4.1:thinking:32768", + "name": "Claude 4.1 Opus Thinking (32K)", + "display_name": "Claude 4.1 Opus Thinking (32K)", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 200000, + "output": 32000 }, "temperature": true, "tool_call": true, @@ -68126,78 +68177,100 @@ "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": true, + "mode": "budget", + "budget": { + "min": 1024, + "unit": "tokens" + }, "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ "thinking_blocks" + ], + "notes": [ + "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", + "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." ] } }, "attachment": true, - "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-04-20", - "last_updated": "2026-04-20", + "open_weights": false, + "knowledge": "2025-03-31", + "release_date": "2025-08-05", + "last_updated": "2025-08-05", "type": "chat" }, { - "id": "moonshot/kimi-k2.5", - "name": "Kimi K2.5", - "display_name": "Kimi K2.5", + "id": "anthropic/claude-haiku-4.5", + "name": "Claude Haiku 4.5", + "display_name": "Claude Haiku 4.5", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 200000, + "output": 64000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": false, + "mode": "budget", + "budget": { + "min": 1024, + "unit": "tokens" + }, "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ "thinking_blocks" + ], + "notes": [ + "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", + "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." ] } }, "attachment": true, - "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-01-27", - "last_updated": "2026-01-27", + "open_weights": false, + "knowledge": "2025-02-28", + "release_date": "2025-10-15", + "last_updated": "2025-10-15", "type": "chat" }, { - "id": "moonshot/kimi-k3.1-pro", - "name": "Kimi K3.1 Pro", - "display_name": "Kimi K3.1 Pro", + "id": "anthropic/claude-fable-5.1", + "name": "Claude Fable 5.1", + "display_name": "Claude Fable 5.1", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 1048576 + "context": 1000000, + "output": 128000 }, "temperature": false, "tool_call": true, @@ -68207,37 +68280,56 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "high", "effort_options": [ "low", + "medium", "high", + "xhigh", "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "omitted", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Adaptive thinking is always on for Claude Fable 5 and Claude Mythos 5; thinking.type = \"disabled\" is rejected.", + "Manual budget_tokens requests return 400 on Claude Fable 5 and Claude Mythos 5.", + "thinking.display defaults to omitted; set display to summarized to receive readable thinking summaries." ] } }, "attachment": true, "open_weights": false, - "release_date": "2026-10-23", - "last_updated": "2026-10-23", + "knowledge": "2026-06", + "release_date": "2026-09-01", + "last_updated": "2026-09-01", "type": "chat" }, { - "id": "moonshot/kimi-k2.7-code", - "name": "Kimi K2.7 Code", - "display_name": "Kimi K2.7 Code", + "id": "anthropic/claude-opus-4.6:thinking:medium", + "name": "Claude 4.6 Opus Thinking Medium", + "display_name": "Claude 4.6 Opus Thinking Medium", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 32768 + "context": 1000000, + "output": 128000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -68246,69 +68338,99 @@ "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": true, + "mode": "mixed", + "budget": { + "min": 1024, + "unit": "tokens" + }, + "effort": "high", + "effort_options": [ + "low", + "medium", + "high", + "max" + ], "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ "thinking_blocks" + ], + "notes": [ + "Anthropic recommends adaptive thinking with effort for Claude 4.6; budget_tokens remains a deprecated compatibility path.", + "Anthropic API defaults effort to high; lower effort levels should be chosen per workload." ] } }, "attachment": true, - "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-06-12", - "last_updated": "2026-06-12", + "open_weights": false, + "knowledge": "2025-05-31", + "release_date": "2026-02-05", + "last_updated": "2026-03-13", "type": "chat" }, { - "id": "moonshot/kimi-k2.7-code-highspeed", - "name": "Kimi K2.7 Code Highspeed", - "display_name": "Kimi K2.7 Code Highspeed", + "id": "anthropic/claude-sonnet-4.5", + "name": "Claude Sonnet 4.5", + "display_name": "Claude Sonnet 4.5", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 32768 + "context": 200000, + "output": 64000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": false, + "mode": "budget", + "budget": { + "min": 1024, + "unit": "tokens" + }, "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ "thinking_blocks" + ], + "notes": [ + "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", + "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." ] } }, "attachment": true, - "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-06-12", - "last_updated": "2026-06-12", + "open_weights": false, + "knowledge": "2025-07-31", + "release_date": "2025-09-29", + "last_updated": "2025-09-29", "type": "chat" }, { - "id": "qwen/qwen3.7-max", - "name": "Qwen3.7 Max", - "display_name": "Qwen3.7 Max", + "id": "anthropic/claude-sonnet-5", + "name": "Claude Sonnet 5", + "display_name": "Claude Sonnet 5", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" @@ -68316,82 +68438,83 @@ }, "limit": { "context": 1000000, - "output": 250000 + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { - "supported": true, - "default": true + "supported": true }, "extra_capabilities": { "reasoning": { "supported": true } }, - "attachment": false, + "attachment": true, "open_weights": false, - "release_date": "2026-05-21", - "last_updated": "2026-05-21", + "knowledge": "2026-01-31", + "release_date": "2026-06-30", + "last_updated": "2026-06-30", "type": "chat" }, { - "id": "qwen/qwen3-max", - "name": "Qwen3 Max", - "display_name": "Qwen3 Max", + "id": "anthropic/claude-opus-latest", + "name": "Claude Opus Latest", + "display_name": "Claude Opus Latest", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 65536 + "context": 1000000, + "output": 128000 }, - "temperature": true, "tool_call": true, "reasoning": { - "supported": true + "supported": true, + "default": true }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" ] } }, - "attachment": false, + "attachment": true, "open_weights": false, - "knowledge": "2025-04", - "release_date": "2025-09-23", - "last_updated": "2025-09-23", + "release_date": "2026-03-29", + "last_updated": "2026-03-29", "type": "chat" }, { - "id": "qwen/qwen3.5-9b", - "name": "Qwen3.5 9B", - "display_name": "Qwen3.5 9B", + "id": "anthropic/claude-haiku-latest", + "name": "Claude Haiku Latest", + "display_name": "Claude Haiku Latest", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 32768 + "context": 1000000, + "output": 128000 }, - "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -68399,135 +68522,191 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-02-23", - "last_updated": "2026-02-23", + "open_weights": false, + "release_date": "2026-03-29", + "last_updated": "2026-03-29", "type": "chat" }, { - "id": "qwen/qwen3-max-preview", - "name": "Qwen3 Max Preview", - "display_name": "Qwen3 Max Preview", + "id": "anthropic/claude-sonnet-4.5:thinking", + "name": "Claude Sonnet 4.5 Thinking", + "display_name": "Claude Sonnet 4.5 Thinking", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 256000, - "output": 65536 + "context": 200000, + "output": 64000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true + "supported": true, + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": true, + "mode": "budget", + "budget": { + "min": 1024, + "unit": "tokens" + }, "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ "thinking_blocks" + ], + "notes": [ + "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", + "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." ] } }, - "attachment": false, + "attachment": true, "open_weights": false, - "knowledge": "2025-04", - "release_date": "2025-09-05", - "last_updated": "2025-09-23", + "knowledge": "2025-07-31", + "release_date": "2025-09-29", + "last_updated": "2025-09-29", "type": "chat" }, { - "id": "qwen/qwen3.8-27b", - "name": "Qwen3.8 27B", - "display_name": "Qwen3.8 27B", + "id": "anthropic/claude-fable-5", + "name": "Claude Fable 5", + "display_name": "Claude Fable 5", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 65536 + "context": 1000000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, "default": true }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "high", + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "omitted", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Adaptive thinking is always on for Claude Fable 5 and Claude Mythos 5; thinking.type = \"disabled\" is rejected.", + "Manual budget_tokens requests return 400 on Claude Fable 5 and Claude Mythos 5.", + "thinking.display defaults to omitted; set display to summarized to receive readable thinking summaries." + ] + } + }, "attachment": true, - "open_weights": true, - "release_date": "2026-08-14", - "last_updated": "2026-08-14", + "open_weights": false, + "knowledge": "2026-01-31", + "release_date": "2026-06-09", + "last_updated": "2026-06-09", "type": "chat" }, { - "id": "qwen/qwen3-vl-30b-a3b-instruct", - "name": "Qwen3-VL 30B-A3B Instruct", - "display_name": "Qwen3-VL 30B-A3B Instruct", + "id": "anthropic/claude-haiku-5.5", + "name": "Claude Haiku 5.5", + "display_name": "Claude Haiku 5.5", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 32768 + "context": 1000000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" + ] + } }, "attachment": true, - "open_weights": true, - "release_date": "2025-10-04", - "last_updated": "2025-10-04", + "open_weights": false, + "knowledge": "2026-06", + "release_date": "2026-10-07", + "last_updated": "2026-10-07", "type": "chat" }, { - "id": "qwen/qwen3-vl-plus", - "name": "Qwen3-VL Plus", - "display_name": "Qwen3-VL Plus", + "id": "anthropic/claude-sonnet-5.5:thinking", + "name": "Claude Sonnet 5.5 Thinking", + "display_name": "Claude Sonnet 5.5 Thinking", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 65536 + "context": 1000000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -68536,57 +68715,84 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "default_enabled": true, + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-04", - "release_date": "2025-09-23", - "last_updated": "2025-09-23", + "knowledge": "2026-06", + "release_date": "2026-09-28", + "last_updated": "2026-09-28", "type": "chat" }, { - "id": "qwen/qwen3-vl-235b-a22b-instruct", - "name": "Qwen3-VL 235B A22B Instruct", - "display_name": "Qwen3-VL 235B A22B Instruct", + "id": "anthropic/claude-opus-4.1:thinking:1024", + "name": "Claude 4.1 Opus Thinking (1K)", + "display_name": "Claude 4.1 Opus Thinking (1K)", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 32768 + "context": 200000, + "output": 32000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "budget", + "budget": { + "min": 1024, + "unit": "tokens" + }, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", + "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." + ] + } }, "attachment": true, - "open_weights": true, + "open_weights": false, "knowledge": "2025-03-31", - "release_date": "2025-09-23", - "last_updated": "2025-09-23", + "release_date": "2025-08-05", + "last_updated": "2025-08-05", "type": "chat" }, { - "id": "qwen/qwen3.6-flash", - "name": "Qwen3.6 Flash", - "display_name": "Qwen3.6 Flash", + "id": "anthropic/claude-opus-4.6:thinking", + "name": "Claude 4.6 Opus Thinking", + "display_name": "Claude 4.6 Opus Thinking", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" @@ -68594,7 +68800,7 @@ }, "limit": { "context": 1000000, - "output": 250000 + "output": 128000 }, "temperature": true, "tool_call": true, @@ -68605,166 +68811,170 @@ "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": true, + "mode": "mixed", + "budget": { + "min": 1024, + "unit": "tokens" + }, + "effort": "high", + "effort_options": [ + "low", + "medium", + "high", + "max" + ], "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ "thinking_blocks" + ], + "notes": [ + "Anthropic recommends adaptive thinking with effort for Claude 4.6; budget_tokens remains a deprecated compatibility path.", + "Anthropic API defaults effort to high; lower effort levels should be chosen per workload." ] } }, - "attachment": false, + "attachment": true, "open_weights": false, - "release_date": "2026-04-27", - "last_updated": "2026-04-27", + "knowledge": "2025-05-31", + "release_date": "2026-02-05", + "last_updated": "2026-03-13", "type": "chat" }, { - "id": "qwen/qwen-flash", - "name": "Qwen Flash", - "display_name": "Qwen Flash", + "id": "anthropic/claude-opus-4.5", + "name": "Claude 4.5 Opus", + "display_name": "Claude 4.5 Opus", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 250000 + "context": 200000, + "output": 64000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": false, + "mode": "mixed", + "budget": { + "min": 1024, + "unit": "tokens" + }, + "effort": "high", + "effort_options": [ + "low", + "medium", + "high" + ], "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ "thinking_blocks" + ], + "notes": [ + "Claude Opus 4.5 uses manual thinking.type = \"enabled\" with budget_tokens; effort can be used alongside the thinking budget.", + "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header." ] } }, - "attachment": false, + "attachment": true, "open_weights": false, - "knowledge": "2024-04", - "release_date": "2025-07-28", - "last_updated": "2025-07-28", + "knowledge": "2025-05", + "release_date": "2025-11-24", + "last_updated": "2025-11-24", "type": "chat" }, { - "id": "qwen/qwen3.8-2.4t-a95b", - "name": "Qwen3.8 2.4T A95B", - "display_name": "Qwen3.8 2.4T A95B", + "id": "anthropic/claude-fable-latest", + "name": "Claude Fable Latest", + "display_name": "Claude Fable Latest", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 1010000 + "context": 1000000, + "output": 128000 }, - "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { "supported": true, "default": true }, "extra_capabilities": { "reasoning": { - "supported": true + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "high", + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "omitted", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Adaptive thinking is always on for Claude Fable 5 and Claude Mythos 5; thinking.type = \"disabled\" is rejected.", + "Manual budget_tokens requests return 400 on Claude Fable 5 and Claude Mythos 5.", + "thinking.display defaults to omitted; set display to summarized to receive readable thinking summaries." + ] } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-08-12", - "last_updated": "2026-08-12", - "type": "chat" - }, - { - "id": "qwen/qwen3-coder-480b-a35b-instruct", - "name": "Qwen3 Coder 480B A35B Instruct", - "display_name": "Qwen3 Coder 480B A35B Instruct", - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 32768 - }, - "temperature": true, - "tool_call": false, - "reasoning": { - "supported": false - }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-07-22", - "last_updated": "2025-07-22", - "type": "chat" - }, - { - "id": "qwen/qwen3-coder-next", - "name": "Qwen3 Coder Next", - "display_name": "Qwen3 Coder Next", - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 262144, - "output": 65536 - }, - "temperature": true, - "tool_call": false, - "reasoning": { - "supported": false - }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-09", - "release_date": "2026-02-03", - "last_updated": "2026-02-03", + "attachment": true, + "open_weights": false, + "release_date": "2026-06-09", + "last_updated": "2026-06-09", "type": "chat" }, { - "id": "qwen/qwen3.5-35b-a3b", - "name": "Qwen3.5 35B A3B", - "display_name": "Qwen3.5 35B A3B", + "id": "anthropic/claude-sonnet-5:thinking", + "name": "Claude Sonnet 5 Thinking", + "display_name": "Claude Sonnet 5 Thinking", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 32768 + "context": 1000000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -68773,27 +68983,32 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "default_enabled": true, + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" ] } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-02-23", - "last_updated": "2026-02-23", + "attachment": true, + "open_weights": false, + "knowledge": "2026-01-31", + "release_date": "2026-06-30", + "last_updated": "2026-06-30", "type": "chat" }, { - "id": "qwen/qwen3.5-plus", - "name": "Qwen3.5 Plus", - "display_name": "Qwen3.5 Plus", + "id": "anthropic/claude-opus-4.6", + "name": "Claude 4.6 Opus", + "display_name": "Claude 4.6 Opus", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" @@ -68801,39 +69016,58 @@ }, "limit": { "context": 1000000, - "output": 250000 + "output": 128000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": false, + "mode": "mixed", + "budget": { + "min": 1024, + "unit": "tokens" + }, + "effort": "high", + "effort_options": [ + "low", + "medium", + "high", + "max" + ], "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ "thinking_blocks" + ], + "notes": [ + "Anthropic recommends adaptive thinking with effort for Claude 4.6; budget_tokens remains a deprecated compatibility path.", + "Anthropic API defaults effort to high; lower effort levels should be chosen per workload." ] } }, - "attachment": false, + "attachment": true, "open_weights": false, - "knowledge": "2025-04", - "release_date": "2026-02-16", - "last_updated": "2026-02-16", + "knowledge": "2025-05-31", + "release_date": "2026-02-05", + "last_updated": "2026-03-13", "type": "chat" }, { - "id": "qwen/qwen3.5-flash", - "name": "Qwen3.5 Flash", - "display_name": "Qwen3.5 Flash", + "id": "anthropic/claude-sonnet-5.5", + "name": "Claude Sonnet 5.5", + "display_name": "Claude Sonnet 5.5", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" @@ -68841,9 +69075,9 @@ }, "limit": { "context": 1000000, - "output": 250000 + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -68851,56 +69085,69 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "low", + "medium", + "high" ] } }, - "attachment": false, + "attachment": true, "open_weights": false, - "release_date": "2026-02-23", - "last_updated": "2026-02-23", + "knowledge": "2026-06", + "release_date": "2026-09-28", + "last_updated": "2026-09-28", "type": "chat" }, { - "id": "qwen/qwen3-next-80b-a3b-instruct", - "name": "Qwen3-Next 80B-A3B Instruct", - "display_name": "Qwen3-Next 80B-A3B Instruct", + "id": "anthropic/claude-sonnet-latest", + "name": "Claude Sonnet Latest", + "display_name": "Claude Sonnet Latest", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 32768 + "context": 1000000, + "output": 128000 }, - "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-09", - "last_updated": "2025-09", + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" + ] + } + }, + "attachment": true, + "open_weights": false, + "release_date": "2026-03-01", + "last_updated": "2026-03-01", "type": "chat" }, { - "id": "qwen/qwen-plus", - "name": "Qwen Plus", - "display_name": "Qwen Plus", + "id": "anthropic/claude-opus-4.7:thinking", + "name": "Claude 4.7 Opus Thinking", + "display_name": "Claude 4.7 Opus Thinking", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" @@ -68908,9 +69155,9 @@ }, "limit": { "context": 1000000, - "output": 250000 + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -68919,116 +69166,167 @@ "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "high", + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" + ], "interleaved": true, "summaries": true, - "visibility": "summary", + "visibility": "omitted", "continuation": [ "thinking_blocks" + ], + "notes": [ + "Claude Opus 4.7 and newer Opus models require thinking.type = \"adaptive\" to enable thinking explicitly.", + "Manual budget_tokens requests return 400 on Claude Opus 4.7 and newer adaptive-only Opus models.", + "task_budget is separate from thinking control and should not be treated as a thinking budget." ] } }, - "attachment": false, + "attachment": true, "open_weights": false, - "knowledge": "2024-04", - "release_date": "2024-01-25", - "last_updated": "2025-09-11", + "knowledge": "2026-01-31", + "release_date": "2026-04-16", + "last_updated": "2026-04-16", "type": "chat" }, { - "id": "qwen/qwen3-235b-a22b", - "name": "Qwen3 235B A22B", - "display_name": "Qwen3 235B A22B", + "id": "anthropic/claude-opus-4.7", + "name": "Claude 4.7 Opus", + "display_name": "Claude 4.7 Opus", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 32768 + "context": 1000000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": false, + "mode": "effort", + "effort": "high", + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" + ], "interleaved": true, "summaries": true, - "visibility": "summary", + "visibility": "omitted", "continuation": [ "thinking_blocks" + ], + "notes": [ + "Claude Opus 4.7 and newer Opus models require thinking.type = \"adaptive\" to enable thinking explicitly.", + "Manual budget_tokens requests return 400 on Claude Opus 4.7 and newer adaptive-only Opus models.", + "task_budget is separate from thinking control and should not be treated as a thinking budget." ] } }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-04-28", - "last_updated": "2025-04-28", + "attachment": true, + "open_weights": false, + "knowledge": "2026-01-31", + "release_date": "2026-04-16", + "last_updated": "2026-04-16", "type": "chat" }, { - "id": "qwen/qwen3-vl-flash", - "name": "Qwen3-VL Flash", - "display_name": "Qwen3-VL Flash", + "id": "anthropic/claude-opus-4.8", + "name": "Claude Opus 4.8", + "display_name": "Claude Opus 4.8", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 65536 + "context": 1000000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { - "supported": true + "supported": true, + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": false, + "mode": "effort", + "effort": "high", + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" + ], "interleaved": true, "summaries": true, - "visibility": "summary", + "visibility": "omitted", "continuation": [ "thinking_blocks" + ], + "notes": [ + "Claude Opus 4.7 and newer Opus models require thinking.type = \"adaptive\" to enable thinking explicitly.", + "Manual budget_tokens requests return 400 on Claude Opus 4.7 and newer adaptive-only Opus models.", + "task_budget is separate from thinking control and should not be treated as a thinking budget." ] } }, "attachment": true, "open_weights": false, - "release_date": "2025-10-15", - "last_updated": "2025-10-15", + "knowledge": "2026-01", + "release_date": "2026-05-28", + "last_updated": "2026-05-28", "type": "chat" }, { - "id": "qwen/qwen3.8-max", - "name": "Qwen3.8 Max", - "display_name": "Qwen3.8 Max", + "id": "anthropic/claude-opus-4.5:thinking", + "name": "Claude 4.5 Opus Thinking", + "display_name": "Claude 4.5 Opus Thinking", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 200000, + "output": 64000 }, "temperature": true, "tool_call": true, @@ -69038,101 +69336,141 @@ }, "extra_capabilities": { "reasoning": { - "supported": true + "supported": true, + "default_enabled": true, + "mode": "mixed", + "budget": { + "min": 1024, + "unit": "tokens" + }, + "effort": "high", + "effort_options": [ + "low", + "medium", + "high" + ], + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Claude Opus 4.5 uses manual thinking.type = \"enabled\" with budget_tokens; effort can be used alongside the thinking budget.", + "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header." + ] } }, "attachment": true, "open_weights": false, - "release_date": "2026-08-03", - "last_updated": "2026-08-03", + "knowledge": "2025-05", + "release_date": "2025-11-24", + "last_updated": "2025-11-24", "type": "chat" }, { - "id": "qwen/qwen3.5-122b-a10b", - "name": "Qwen3.5 122B A10B", - "display_name": "Qwen3.5 122B A10B", + "id": "anthropic/claude-opus-4.1", + "name": "Claude 4.1 Opus", + "display_name": "Claude 4.1 Opus", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 32768 + "context": 200000, + "output": 32000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": false, + "mode": "budget", + "budget": { + "min": 1024, + "unit": "tokens" + }, "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ "thinking_blocks" + ], + "notes": [ + "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", + "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." ] } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-02-23", - "last_updated": "2026-02-23", + "attachment": true, + "open_weights": false, + "knowledge": "2025-03-31", + "release_date": "2025-08-05", + "last_updated": "2025-08-05", "type": "chat" }, { - "id": "qwen/qwen3-next-80b-a3b-thinking", - "name": "Qwen3-Next 80B-A3B (Thinking)", - "display_name": "Qwen3-Next 80B-A3B (Thinking)", + "id": "anthropic/claude-opus-5.5", + "name": "Claude Opus 5.5", + "display_name": "Claude Opus 5.5", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 32768 + "context": 1000000, + "output": 128000 }, - "temperature": true, - "tool_call": false, + "temperature": false, + "tool_call": true, "reasoning": { "supported": true, "default": true }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" ] } }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-09", - "last_updated": "2025-09", + "attachment": true, + "open_weights": false, + "knowledge": "2026-06", + "release_date": "2026-09-22", + "last_updated": "2026-09-22", "type": "chat" }, { - "id": "qwen/qwen3.6-plus", - "name": "Qwen3.6 Plus", - "display_name": "Qwen3.6 Plus", + "id": "anthropic/claude-sonnet-4.6:thinking", + "name": "Claude Sonnet 4.6 Thinking", + "display_name": "Claude Sonnet 4.6 Thinking", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" @@ -69140,7 +69478,7 @@ }, "limit": { "context": 1000000, - "output": 250000 + "output": 128000 }, "temperature": true, "tool_call": true, @@ -69151,36 +69489,55 @@ "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": true, + "mode": "mixed", + "budget": { + "min": 1024, + "unit": "tokens" + }, + "effort": "high", + "effort_options": [ + "low", + "medium", + "high", + "max" + ], "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ "thinking_blocks" + ], + "notes": [ + "Anthropic recommends adaptive thinking with effort for Claude 4.6; budget_tokens remains a deprecated compatibility path.", + "Anthropic API defaults effort to high; lower effort levels should be chosen per workload." ] } }, - "attachment": false, + "attachment": true, "open_weights": false, - "knowledge": "2025-04", - "release_date": "2026-04-02", - "last_updated": "2026-04-02", + "knowledge": "2025-08-31", + "release_date": "2026-02-17", + "last_updated": "2026-03-13", "type": "chat" }, { - "id": "qwen/qwen3-32b", - "name": "Qwen3 32B", - "display_name": "Qwen3 32B", + "id": "anthropic/claude-opus-4.1:thinking", + "name": "Claude 4.1 Opus Thinking", + "display_name": "Claude 4.1 Opus Thinking", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 32768 + "context": 200000, + "output": 32000 }, "temperature": true, "tool_call": true, @@ -69191,63 +69548,48 @@ "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": true, + "mode": "budget", + "budget": { + "min": 1024, + "unit": "tokens" + }, "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ "thinking_blocks" + ], + "notes": [ + "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", + "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." ] } }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-04-28", - "last_updated": "2025-04-28", - "type": "chat" - }, - { - "id": "qwen/qwen3-235b-a22b-instruct-2507", - "name": "Qwen3 235B A22B Instruct 2507", - "display_name": "Qwen3 235B A22B Instruct 2507", - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 32768 - }, - "temperature": true, - "tool_call": true, - "reasoning": { - "supported": false - }, - "attachment": false, - "open_weights": true, - "release_date": "2025-07-21", - "last_updated": "2025-07-21", + "attachment": true, + "open_weights": false, + "knowledge": "2025-03-31", + "release_date": "2025-08-05", + "last_updated": "2025-08-05", "type": "chat" }, { - "id": "qwen/qwen3.6-35b-a3b", - "name": "Qwen3.6 35B A3B", - "display_name": "Qwen3.6 35B A3B", + "id": "anthropic/claude-haiku-4.5:thinking", + "name": "Claude Haiku 4.5 Thinking", + "display_name": "Claude Haiku 4.5 Thinking", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 65536 + "context": 200000, + "output": 64000 }, "temperature": true, "tool_call": true, @@ -69258,35 +69600,48 @@ "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": true, + "mode": "budget", + "budget": { + "min": 1024, + "unit": "tokens" + }, "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ "thinking_blocks" + ], + "notes": [ + "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", + "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." ] } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-04-17", - "last_updated": "2026-04-17", + "attachment": true, + "open_weights": false, + "knowledge": "2025-02-28", + "release_date": "2025-10-15", + "last_updated": "2025-10-15", "type": "chat" }, { - "id": "qwen/qwen3-30b-a3b", - "name": "Qwen3 30B A3B", - "display_name": "Qwen3 30B A3B", + "id": "anthropic/claude-opus-4.6:thinking:max", + "name": "Claude 4.6 Opus Thinking Max", + "display_name": "Claude 4.6 Opus Thinking Max", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 32768 + "context": 1000000, + "output": 128000 }, "temperature": true, "tool_call": true, @@ -69297,34 +69652,54 @@ "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": true, + "mode": "mixed", + "budget": { + "min": 1024, + "unit": "tokens" + }, + "effort": "high", + "effort_options": [ + "low", + "medium", + "high", + "max" + ], "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ "thinking_blocks" + ], + "notes": [ + "Anthropic recommends adaptive thinking with effort for Claude 4.6; budget_tokens remains a deprecated compatibility path.", + "Anthropic API defaults effort to high; lower effort levels should be chosen per workload." ] } }, - "attachment": false, - "open_weights": true, - "release_date": "2025-04-28", - "last_updated": "2025-04-28", + "attachment": true, + "open_weights": false, + "knowledge": "2025-05-31", + "release_date": "2026-02-05", + "last_updated": "2026-03-13", "type": "chat" }, { - "id": "qwen/qwen3.8-flash", - "name": "Qwen3.8 Flash", - "display_name": "Qwen3.8 Flash", + "id": "anthropic/claude-opus-4.6:thinking:low", + "name": "Claude 4.6 Opus Thinking Low", + "display_name": "Claude 4.6 Opus Thinking Low", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 977000, + "context": 1000000, "output": 128000 }, "temperature": true, @@ -69335,73 +69710,117 @@ }, "extra_capabilities": { "reasoning": { - "supported": true + "supported": true, + "default_enabled": true, + "mode": "mixed", + "budget": { + "min": 1024, + "unit": "tokens" + }, + "effort": "high", + "effort_options": [ + "low", + "medium", + "high", + "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Anthropic recommends adaptive thinking with effort for Claude 4.6; budget_tokens remains a deprecated compatibility path.", + "Anthropic API defaults effort to high; lower effort levels should be chosen per workload." + ] } }, - "attachment": false, + "attachment": true, "open_weights": false, - "release_date": "2026-08-26", - "last_updated": "2026-08-26", + "knowledge": "2025-05-31", + "release_date": "2026-02-05", + "last_updated": "2026-03-13", "type": "chat" }, { - "id": "qwen/qwen3.6-max-preview", - "name": "Qwen3.6 Max Preview", - "display_name": "Qwen3.6 Max Preview", + "id": "anthropic/claude-sonnet-4.6", + "name": "Claude Sonnet 4.6", + "display_name": "Claude Sonnet 4.6", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 256000, - "output": 65536 + "context": 1000000, + "output": 128000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": false, + "mode": "mixed", + "budget": { + "min": 1024, + "unit": "tokens" + }, + "effort": "high", + "effort_options": [ + "low", + "medium", + "high", + "max" + ], "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ "thinking_blocks" + ], + "notes": [ + "Anthropic recommends adaptive thinking with effort for Claude 4.6; budget_tokens remains a deprecated compatibility path.", + "Anthropic API defaults effort to high; lower effort levels should be chosen per workload." ] } }, - "attachment": false, + "attachment": true, "open_weights": false, - "knowledge": "2025-04", - "release_date": "2026-04-20", - "last_updated": "2026-04-20", + "knowledge": "2025-08-31", + "release_date": "2026-02-17", + "last_updated": "2026-03-13", "type": "chat" }, { - "id": "qwen/qwen3.5-4b", - "name": "Qwen3.5 4B", - "display_name": "Qwen3.5 4B", + "id": "anthropic/claude-opus-5", + "name": "Claude Opus 5", + "display_name": "Claude Opus 5", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 32768 + "context": 1000000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -69410,36 +69829,39 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-03-02", - "last_updated": "2026-03-02", + "open_weights": false, + "knowledge": "2026-05", + "release_date": "2026-07-24", + "last_updated": "2026-07-24", "type": "chat" }, { - "id": "qwen/qwen3.6-27b", - "name": "Qwen3.6 27B", - "display_name": "Qwen3.6 27B", + "id": "anthropic/claude-opus-4.1:thinking:8192", + "name": "Claude 4.1 Opus Thinking (8K)", + "display_name": "Claude 4.1 Opus Thinking (8K)", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 256000, - "output": 64000 + "context": 200000, + "output": 32000 }, "temperature": true, "tool_call": true, @@ -69450,37 +69872,50 @@ "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": true, + "mode": "budget", + "budget": { + "min": 1024, + "unit": "tokens" + }, "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ "thinking_blocks" + ], + "notes": [ + "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", + "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-04-22", - "last_updated": "2026-04-22", + "open_weights": false, + "knowledge": "2025-03-31", + "release_date": "2025-08-05", + "last_updated": "2025-08-05", "type": "chat" }, { - "id": "qwen/qwen3.5-397b-a17b", - "name": "Qwen3.5 397B A17B", - "display_name": "Qwen3.5 397B A17B", + "id": "anthropic/claude-opus-4.8:thinking", + "name": "Claude Opus 4.8 Thinking", + "display_name": "Claude Opus 4.8 Thinking", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 32768 + "context": 1000000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -69489,65 +69924,68 @@ "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "high", + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" + ], "interleaved": true, "summaries": true, - "visibility": "summary", + "visibility": "omitted", "continuation": [ "thinking_blocks" + ], + "notes": [ + "Claude Opus 4.7 and newer Opus models require thinking.type = \"adaptive\" to enable thinking explicitly.", + "Manual budget_tokens requests return 400 on Claude Opus 4.7 and newer adaptive-only Opus models.", + "task_budget is separate from thinking control and should not be treated as a thinking budget." ] } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-02-15", - "last_updated": "2026-02-15", + "attachment": true, + "open_weights": false, + "knowledge": "2026-01", + "release_date": "2026-05-28", + "last_updated": "2026-05-28", "type": "chat" }, { - "id": "qwen/qwen3-vl-235b-a22b-thinking", - "name": "Qwen3-VL 235B A22B Thinking", - "display_name": "Qwen3-VL 235B A22B Thinking", + "id": "amazon/nova-lite-v1", + "name": "Amazon Nova Lite 1.0", + "display_name": "Amazon Nova Lite 1.0", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 32768 + "context": 300000, + "output": 5120 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } + "supported": false }, - "attachment": true, - "open_weights": true, - "knowledge": "2025-03-31", - "release_date": "2025-09-23", - "last_updated": "2025-09-23", + "attachment": false, + "open_weights": false, + "knowledge": "2024-10-31", + "release_date": "2024-12-05", + "last_updated": "2024-12-05", "type": "chat" }, { - "id": "qwen/qwen3-coder-plus", - "name": "Qwen3 Coder Plus", - "display_name": "Qwen3 Coder Plus", + "id": "amazon/nova-2-lite-v1", + "name": "Amazon Nova 2 Lite", + "display_name": "Amazon Nova 2 Lite", "modalities": { "input": [ "text" @@ -69558,24 +69996,24 @@ }, "limit": { "context": 1000000, - "output": 250000 + "output": 65535 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { "supported": false }, "attachment": false, "open_weights": false, - "knowledge": "2025-04", - "release_date": "2025-07-23", - "last_updated": "2025-07-23", + "knowledge": "2025-10", + "release_date": "2025-12-02", + "last_updated": "2025-12-02", "type": "chat" }, { - "id": "qwen/qwen3.5-27b", - "name": "Qwen3.5 27B", - "display_name": "Qwen3.5 27B", + "id": "amazon/nova-pro-v1", + "name": "Amazon Nova Pro 1.0", + "display_name": "Amazon Nova Pro 1.0", "modalities": { "input": [ "text" @@ -69585,10 +70023,38 @@ ] }, "limit": { - "context": 131072, - "output": 32768 + "context": 300000, + "output": 32000 }, "temperature": true, + "tool_call": false, + "reasoning": { + "supported": false + }, + "attachment": false, + "open_weights": false, + "knowledge": "2024-10-31", + "release_date": "2024-12-05", + "last_updated": "2024-12-05", + "type": "chat" + }, + { + "id": "orca/orcacyber-qwen3.8-flash-next", + "name": "OrcaCyber Qwen3.8 Flash Next Uncensored", + "display_name": "OrcaCyber Qwen3.8 Flash Next Uncensored", + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 261632, + "output": 131072 + }, "tool_call": true, "reasoning": { "supported": true, @@ -69596,39 +70062,36 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "none", + "low", + "medium", + "xhigh" ] } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-02-23", - "last_updated": "2026-02-23", + "attachment": true, + "open_weights": false, + "release_date": "2026-11-02", + "last_updated": "2026-11-02", "type": "chat" }, { - "id": "qwen/qwen3.7-plus", - "name": "Qwen3.7 Plus", - "display_name": "Qwen3.7 Plus", + "id": "orca/orcacyber-zero-1.5", + "name": "OrcaCyber Zero 1.5", + "display_name": "OrcaCyber Zero 1.5", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 65536 + "context": 999488, + "output": 128000 }, - "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -69636,20 +70099,24 @@ }, "extra_capabilities": { "reasoning": { - "supported": true + "effort_options": [ + "none", + "low", + "medium", + "high" + ] } }, - "attachment": true, + "attachment": false, "open_weights": false, - "knowledge": "2025-04", - "release_date": "2026-06-02", - "last_updated": "2026-06-02", + "release_date": "2026-10-30", + "last_updated": "2026-10-30", "type": "chat" }, { - "id": "qwen/qwen3-coder-flash", - "name": "Qwen3 Coder Flash", - "display_name": "Qwen3 Coder Flash", + "id": "orca/orcacyber-minimax-m3.1-flash", + "name": "OrcaCyber MiniMax M3.1 Flash Uncensored", + "display_name": "OrcaCyber MiniMax M3.1 Flash Uncensored", "modalities": { "input": [ "text" @@ -69659,25 +70126,35 @@ ] }, "limit": { - "context": 1000000, - "output": 250000 + "context": 261632, + "output": 131072 }, - "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" + ] + } }, "attachment": false, "open_weights": false, - "knowledge": "2025-04", - "release_date": "2025-07-28", - "last_updated": "2025-07-28", + "release_date": "2026-11-02", + "last_updated": "2026-11-02", "type": "chat" }, { - "id": "meta/llama-3.1-70b-instruct", - "name": "Llama 3.1 70B", - "display_name": "Llama 3.1 70B", + "id": "EVA-UNIT-01/EVA-LLaMA-3.33-70B-v0.0", + "name": "EVA Llama 3.33 70B", + "display_name": "EVA Llama 3.33 70B", "modalities": { "input": [ "text" @@ -69687,65 +70164,49 @@ ] }, "limit": { - "context": 128000, - "output": 2048 + "context": 32768, + "output": 16384 }, - "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { "supported": false }, "attachment": false, "open_weights": true, - "knowledge": "2023-12", - "release_date": "2024-07-23", - "last_updated": "2024-07-23", + "release_date": "2025-07-26", + "last_updated": "2025-07-26", "type": "chat" }, { - "id": "meta/muse-spark-open", - "name": "Muse Spark Open", - "display_name": "Muse Spark Open", + "id": "EVA-UNIT-01/EVA-Qwen2.5-32B-v0.2", + "name": "EVA-Qwen2.5-32B-v0.2", + "display_name": "EVA-Qwen2.5-32B-v0.2", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 262144 + "context": 16384, + "output": 8192 }, - "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "low", - "medium", - "high", - "xhigh" - ] - } + "supported": false }, - "attachment": true, + "attachment": false, "open_weights": true, - "knowledge": "2026-01-04", - "release_date": "2026-10-15", - "last_updated": "2026-10-15", + "release_date": "2025-07-26", + "last_updated": "2025-07-26", "type": "chat" }, { - "id": "meta/llama-3.3-70b-instruct", - "name": "Llama 3.3 70B Instruct", - "display_name": "Llama 3.3 70B Instruct", + "id": "EVA-UNIT-01/EVA-LLaMA-3.33-70B-v0.1", + "name": "EVA-LLaMA-3.33-70B-v0.1", + "display_name": "EVA-LLaMA-3.33-70B-v0.1", "modalities": { "input": [ "text" @@ -69755,25 +70216,23 @@ ] }, "limit": { - "context": 131072, - "output": 32768 + "context": 32768, + "output": 16384 }, - "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { "supported": false }, "attachment": false, "open_weights": true, - "knowledge": "2023-12", - "release_date": "2024-12-06", - "last_updated": "2024-12-06", + "release_date": "2025-09-25", + "last_updated": "2025-09-25", "type": "chat" }, { - "id": "meta/llama-3.1-8b-instruct", - "name": "Llama 3.1 8B", - "display_name": "Llama 3.1 8B", + "id": "EVA-UNIT-01/EVA-Qwen2.5-72B-v0.2", + "name": "EVA-Qwen2.5-72B-v0.2", + "display_name": "EVA-Qwen2.5-72B-v0.2", "modalities": { "input": [ "text" @@ -69783,135 +70242,91 @@ ] }, "limit": { - "context": 128000, - "output": 2048 + "context": 16384, + "output": 8192 }, - "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { "supported": false }, "attachment": false, "open_weights": true, - "knowledge": "2023-12", - "release_date": "2024-07-23", - "last_updated": "2024-07-23", + "release_date": "2025-09-25", + "last_updated": "2025-09-25", "type": "chat" - } - ] - }, - "modal": { - "id": "modal", - "name": "Modal", - "display_name": "Modal", - "api": "https://inference.us-west.modal.direct/v1", - "doc": "https://modal.com/docs/guide/endpoints", - "models": [ + }, { - "id": "zai-org/GLM-5.3-Flash", - "name": "GLM 5.3 Flash", - "display_name": "GLM 5.3 Flash", + "id": "LLM360/K2-Think", + "name": "K2-Think", + "display_name": "K2-Think", "modalities": { "input": [ - "text", - "image", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 128000, + "output": 32768 }, - "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "effort_options": [ - "low", - "high", - "max" - ] - } + "supported": false }, - "attachment": true, + "attachment": false, "open_weights": true, - "release_date": "2026-08-26", - "last_updated": "2026-08-26", + "release_date": "2025-07-26", + "last_updated": "2025-07-26", "type": "chat" }, { - "id": "zai-org/GLM-5.3", - "name": "GLM-5.3", - "display_name": "GLM-5.3", + "id": "bytedance-seed/seed-2.0-lite", + "name": "ByteDance Seed 2.0 Lite", + "display_name": "ByteDance Seed 2.0 Lite", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 1000000, + "context": 262144, "output": 131072 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "effort_options": [ - "low", - "high", - "max" - ] - } + "supported": false }, - "attachment": false, - "open_weights": true, - "release_date": "2026-08-14", - "last_updated": "2026-08-14", + "attachment": true, + "open_weights": false, + "release_date": "2026-02-14", + "last_updated": "2026-02-14", "type": "chat" }, { - "id": "Qwen/Qwen3.8-2.4T-A95B", - "name": "Qwen3.8-Max", - "display_name": "Qwen3.8-Max", + "id": "bytedance-seed/seed-2-1-turbo", + "name": "ByteDance Seed 2.1 Turbo", + "display_name": "ByteDance Seed 2.1 Turbo", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 1010000, - "output": 131072 + "context": 262144, + "output": 235929 }, "temperature": true, "tool_call": true, @@ -69919,45 +70334,28 @@ "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "effort_options": [ - "low", - "medium", - "xhigh" - ] - } - }, - "attachment": false, - "open_weights": true, - "release_date": "2026-08-12", - "last_updated": "2026-08-12", + "attachment": true, + "open_weights": false, + "release_date": "2026-06-23", + "last_updated": "2026-06-23", "type": "chat" }, { - "id": "meta-models/Muse-Spark-Open-816B-A42B", - "name": "Muse Spark Open", - "display_name": "Muse Spark Open", + "id": "bytedance-seed/seed-2.0-code", + "name": "ByteDance Seed 2.0 Code", + "display_name": "ByteDance Seed 2.0 Code", "modalities": { "input": [ "text", "image", - "video", - "audio" + "video" ], "output": [ "text" ] }, "limit": { - "context": 1048576, + "context": 262144, "output": 131072 }, "temperature": true, @@ -69968,45 +70366,35 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], "effort_options": [ + "none", "low", "medium", - "high", - "xhigh" + "high" ] } }, "attachment": true, - "open_weights": true, - "knowledge": "2026-01-04", - "release_date": "2026-10-15", - "last_updated": "2026-10-15", + "open_weights": false, + "release_date": "2026-02-14", + "last_updated": "2026-02-14", "type": "chat" }, { - "id": "nvidia/GLM-5.3-Flash-NVFP4", - "name": "GLM-5.3-Flash", - "display_name": "GLM-5.3-Flash", + "id": "arcee-ai/trinity-large-thinking", + "name": "Trinity Large Thinking", + "display_name": "Trinity Large Thinking", "modalities": { "input": [ - "text", - "image", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 262144, + "output": 80000 }, "temperature": true, "tool_call": true, @@ -70014,73 +70402,45 @@ "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "effort_options": [ - "low", - "high", - "max" - ] - } - }, - "attachment": true, + "attachment": false, "open_weights": true, - "release_date": "2026-08-26", - "last_updated": "2026-08-26", + "release_date": "2026-04-01", + "last_updated": "2026-05-28", "type": "chat" }, { - "id": "thinkingmachines/Inkling-NVFP4", - "name": "Inkling", - "display_name": "Inkling", + "id": "qwen/qwen3-vl-235b-a22b-instruct", + "name": "Qwen3 VL 235B A22B Instruct", + "display_name": "Qwen3 VL 235B A22B Instruct", "modalities": { "input": [ "text", - "image", - "audio" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 262144 + "context": 131072, + "output": 32768 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } + "supported": false }, "attachment": true, "open_weights": true, - "release_date": "2026-07-15", - "last_updated": "2026-07-15", + "knowledge": "2025-03-31", + "release_date": "2025-09-23", + "last_updated": "2025-09-23", "type": "chat" }, { - "id": "moonshotai/Kimi-K3", - "name": "Kimi K3", - "display_name": "Kimi K3", + "id": "qwen/qwen2.5-vl-72b-instruct", + "name": "Qwen25 VL 72b", + "display_name": "Qwen25 VL 72b", "modalities": { "input": [ "text", @@ -70091,36 +70451,23 @@ ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 32000, + "output": 32000 }, - "temperature": false, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } + "supported": false }, "attachment": true, "open_weights": true, - "release_date": "2026-07-16", - "last_updated": "2026-07-16", + "release_date": "2025-05-10", + "last_updated": "2025-05-10", "type": "chat" }, { - "id": "deepseek-ai/DeepSeek-V4.1-Flash", - "name": "DeepSeek V4.1 Flash", - "display_name": "DeepSeek V4.1 Flash", + "id": "qwen/qwen3.8-27b-kimiko", + "name": "Qwen 3.8 27B Kimiko", + "display_name": "Qwen 3.8 27B Kimiko", "modalities": { "input": [ "text", @@ -70131,64 +70478,40 @@ ] }, "limit": { - "context": 1000000, - "output": 384000 + "context": 262144, + "output": 32768 }, - "temperature": true, "tool_call": true, "reasoning": { "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "effort_options": [ - "low", - "high", - "max" - ] - } - }, "attachment": true, "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-09-10", - "last_updated": "2026-09-10", + "release_date": "2026-10-05", + "last_updated": "2026-10-05", "type": "chat" - } - ] - }, - "poe": { - "id": "poe", - "name": "Poe", - "display_name": "Poe", - "api": "https://api.poe.com/v1", - "doc": "https://creator.poe.com/docs/external-applications/openai-compatible-api", - "models": [ + }, { - "id": "poetools/claude-code", - "name": "claude-code", - "display_name": "claude-code", + "id": "qwen/qwen3.8-omni-flash", + "name": "Qwen3.8 Omni Flash", + "display_name": "Qwen3.8 Omni Flash", "modalities": { "input": [ - "text" + "text", + "image", + "audio", + "video" ], "output": [ "text" ] }, "limit": { - "context": 8192, - "output": 8192 + "context": 991808, + "output": 131072 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -70198,93 +70521,102 @@ "reasoning": { "effort_options": [ "none", - "low", - "medium", - "high", - "xhigh", - "max" + "high" ] } }, - "attachment": false, + "attachment": true, "open_weights": false, - "release_date": "2025-11-27", - "last_updated": "2025-11-27", + "release_date": "2026-09-17", + "last_updated": "2026-09-17", "type": "chat" }, { - "id": "trytako/tako", - "name": "Tako", - "display_name": "Tako", + "id": "qwen/qwen3.7-flash:thinking", + "name": "Qwen3.7 Flash Thinking", + "display_name": "Qwen3.7 Flash Thinking", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 2048, - "output": 8192 + "context": 983616, + "output": 65536 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "none", + "high" + ] + } + }, + "attachment": true, "open_weights": false, - "release_date": "2024-08-15", - "last_updated": "2024-08-15", + "release_date": "2026-07-15", + "last_updated": "2026-07-15", "type": "chat" }, { - "id": "cerebras/qwen3-32b-cs", - "name": "qwen3-32b-cs", - "display_name": "qwen3-32b-cs", + "id": "qwen/qwen3.8-27b-cybersecurity", + "name": "Qwen 3.8 27B Cybersecurity", + "display_name": "Qwen 3.8 27B Cybersecurity", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 8192, - "output": 8192 + "context": 262144, + "output": 32768 }, - "temperature": false, "tool_call": true, "reasoning": { "supported": true, "default": true }, - "attachment": false, + "attachment": true, "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-04-28", - "last_updated": "2025-04-28", + "release_date": "2026-09-19", + "last_updated": "2026-09-19", "type": "chat" }, { - "id": "cerebras/gpt-oss-120b-cs", - "name": "GPT-OSS-120B-CS", - "display_name": "GPT-OSS-120B-CS", + "id": "qwen/qwen3.8-max:thinking", + "name": "Qwen3.8 Max Thinking", + "display_name": "Qwen3.8 Max Thinking", "modalities": { "input": [ - "text" + "text", + "image", + "video", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 8192 + "context": 991000, + "output": 131072 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -70295,77 +70627,77 @@ "effort_options": [ "low", "medium", - "high" + "high", + "max" ] } }, - "attachment": false, - "open_weights": true, - "knowledge": "2024-06", - "release_date": "2025-08-05", - "last_updated": "2025-08-05", + "attachment": true, + "open_weights": false, + "release_date": "2026-08-03", + "last_updated": "2026-08-03", "type": "chat" }, { - "id": "cerebras/qwen3-235b-2507-cs", - "name": "qwen3-235b-2507-cs", - "display_name": "qwen3-235b-2507-cs", + "id": "qwen/qwen3.8-27b-hemmingway", + "name": "Qwen 3.8 27B Hemingway", + "display_name": "Qwen 3.8 27B Hemingway", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 8192, - "output": 8192 + "context": 262144, + "output": 32768 }, - "temperature": false, "tool_call": true, "reasoning": { "supported": true, "default": true }, - "attachment": false, + "attachment": true, "open_weights": true, - "release_date": "2025-07-21", - "last_updated": "2025-07-21", + "release_date": "2026-09-21", + "last_updated": "2026-09-21", "type": "chat" }, { - "id": "cerebras/llama-3.1-8b-cs", - "name": "Llama-3.1-8B-CS", - "display_name": "Llama-3.1-8B-CS", + "id": "qwen/qwen3.8-27b-uncensored:thinking", + "name": "Qwen 3.8 27B Uncensored Thinking", + "display_name": "Qwen 3.8 27B Uncensored Thinking", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 8192 + "context": 1048576, + "output": 65536 }, - "temperature": false, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, + "attachment": true, "open_weights": true, - "knowledge": "2023-12", - "release_date": "2024-07-23", - "last_updated": "2024-07-23", + "release_date": "2026-07-29", + "last_updated": "2026-08-25", "type": "chat" }, { - "id": "cerebras/llama-3.3-70b-cs", - "name": "llama-3.3-70b-cs", - "display_name": "llama-3.3-70b-cs", + "id": "qwen/qwen3.8-27b-synthia", + "name": "Qwen 3.8 27B Synthia", + "display_name": "Qwen 3.8 27B Synthia", "modalities": { "input": [ "text" @@ -70375,25 +70707,23 @@ ] }, "limit": { - "context": 8192, - "output": 8192 + "context": 262144, + "output": 32768 }, - "temperature": false, "tool_call": false, "reasoning": { "supported": false }, "attachment": false, "open_weights": true, - "knowledge": "2023-12", - "release_date": "2024-12-06", - "last_updated": "2024-12-06", + "release_date": "2026-10-05", + "last_updated": "2026-10-05", "type": "chat" }, { - "id": "empiriolabs/deepseek-v4-flash-el", - "name": "DeepSeek-V4-Flash-EL", - "display_name": "DeepSeek-V4-Flash-EL", + "id": "qwen/qwen3-next-80b-a3b-instruct", + "name": "Qwen3 Next 80B A3B (Instruct)", + "display_name": "Qwen3 Next 80B A3B (Instruct)", "modalities": { "input": [ "text" @@ -70403,55 +70733,65 @@ ] }, "limit": { - "context": 1000000, - "output": 384000 + "context": 262144, + "output": 32768 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true + "supported": false }, "attachment": false, "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-04-24", - "last_updated": "2026-04-24", + "knowledge": "2025-04", + "release_date": "2025-09", + "last_updated": "2025-09", "type": "chat" }, { - "id": "empiriolabs/deepseek-v4-pro-el", - "name": "DeepSeek-V4-Pro-EL", - "display_name": "DeepSeek-V4-Pro-EL", + "id": "qwen/qwen3.5-27b", + "name": "Qwen3.5 27B", + "display_name": "Qwen3.5 27B", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 384000 + "context": 260096, + "output": 65536 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true + "supported": true }, - "attachment": false, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } + }, + "attachment": true, "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-04-24", - "last_updated": "2026-04-24", + "release_date": "2026-02-23", + "last_updated": "2026-02-23", "type": "chat" }, { - "id": "xai/grok-4.1-fast-non-reasoning", - "name": "Grok-4.1-Fast-Non-Reasoning", - "display_name": "Grok-4.1-Fast-Non-Reasoning", + "id": "qwen/qwen3.8-27b-queen", + "name": "Qwen 3.8 27B Queen", + "display_name": "Qwen 3.8 27B Queen", "modalities": { "input": [ "text", @@ -70462,38 +70802,37 @@ ] }, "limit": { - "context": 2000000, - "output": 30000 + "context": 524288, + "output": 32768 }, - "temperature": false, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, "attachment": true, - "open_weights": false, - "release_date": "2025-11-19", - "last_updated": "2025-11-19", + "open_weights": true, + "release_date": "2026-09-09", + "last_updated": "2026-09-09", "type": "chat" }, { - "id": "xai/grok-4.20-multi-agent", - "name": "Grok-4.20-Multi-Agent", - "display_name": "Grok-4.20-Multi-Agent", + "id": "qwen/qwen3.5-122b-a10b:thinking", + "name": "Qwen3.5 122B A10B Thinking", + "display_name": "Qwen3.5 122B A10B Thinking", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 8192 + "context": 131072, + "output": 32768 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -70501,22 +70840,25 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "medium", - "high" + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, - "attachment": true, - "open_weights": false, - "release_date": "2026-03-13", - "last_updated": "2026-03-13", + "attachment": false, + "open_weights": true, + "release_date": "2026-02-23", + "last_updated": "2026-02-23", "type": "chat" }, { - "id": "xai/grok-3", - "name": "Grok 3", - "display_name": "Grok 3", + "id": "qwen/qwen-2.5-72b-instruct", + "name": "Qwen2.5 72B", + "display_name": "Qwen2.5 72B", "modalities": { "input": [ "text" @@ -70529,50 +70871,57 @@ "context": 131072, "output": 8192 }, - "temperature": false, "tool_call": true, "reasoning": { "supported": false }, "attachment": false, - "open_weights": false, - "release_date": "2025-04-11", - "last_updated": "2025-04-11", + "open_weights": true, + "release_date": "2025-07-03", + "last_updated": "2025-07-03", "type": "chat" }, { - "id": "xai/grok-4.1-fast-reasoning", - "name": "Grok-4.1-Fast-Reasoning", - "display_name": "Grok-4.1-Fast-Reasoning", + "id": "qwen/qwen3.7-max", + "name": "Qwen3.7 Max", + "display_name": "Qwen3.7 Max", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 2000000, - "output": 30000 + "context": 1000000, + "output": 65536 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, "default": true }, - "attachment": true, + "extra_capabilities": { + "reasoning": { + "supported": true, + "effort_options": [ + "none", + "high" + ] + } + }, + "attachment": false, "open_weights": false, - "release_date": "2025-11-19", - "last_updated": "2025-11-19", + "release_date": "2026-05-21", + "last_updated": "2026-05-21", "type": "chat" }, { - "id": "xai/grok-3-mini", - "name": "Grok 3 Mini", - "display_name": "Grok 3 Mini", + "id": "qwen/qwen3.7-max:thinking", + "name": "Qwen3.7 Max Thinking", + "display_name": "Qwen3.7 Max Thinking", "modalities": { "input": [ "text" @@ -70582,10 +70931,10 @@ ] }, "limit": { - "context": 131072, - "output": 8192 + "context": 1000000, + "output": 65536 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -70594,22 +70943,61 @@ "extra_capabilities": { "reasoning": { "effort_options": [ - "low", - "medium", + "none", "high" ] } }, "attachment": false, "open_weights": false, - "release_date": "2025-04-11", - "last_updated": "2025-04-11", + "release_date": "2026-05-21", + "last_updated": "2026-05-21", "type": "chat" }, { - "id": "xai/grok-code-fast-1", - "name": "Grok Code Fast 1", - "display_name": "Grok Code Fast 1", + "id": "qwen/qwen3.6-flash", + "name": "Qwen3.6 Flash", + "display_name": "Qwen3.6 Flash", + "modalities": { + "input": [ + "text", + "image", + "video" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 991808, + "output": 65536 + }, + "temperature": true, + "tool_call": false, + "reasoning": { + "supported": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } + }, + "attachment": true, + "open_weights": false, + "release_date": "2026-04-27", + "last_updated": "2026-04-27", + "type": "chat" + }, + { + "id": "qwen/qwen3-30b-a3b-instruct-2507", + "name": "Qwen3 30B A3B Instruct 2507", + "display_name": "Qwen3 30B A3B Instruct 2507", "modalities": { "input": [ "text" @@ -70620,120 +71008,221 @@ }, "limit": { "context": 256000, - "output": 128000 + "output": 32768 }, - "temperature": false, - "tool_call": true, + "temperature": true, + "tool_call": false, + "reasoning": { + "supported": false + }, + "attachment": false, + "open_weights": true, + "release_date": "2025-07-28", + "last_updated": "2025-07-28", + "type": "chat" + }, + { + "id": "qwen/qwen-plus", + "name": "Qwen Plus", + "display_name": "Qwen Plus", + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 995904, + "output": 32768 + }, + "temperature": true, + "tool_call": false, "reasoning": { "supported": true, "default": true }, "extra_capabilities": { "reasoning": { - "supported": true + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] } }, "attachment": false, "open_weights": false, - "release_date": "2025-09-02", - "last_updated": "2025-09-02", + "knowledge": "2024-04", + "release_date": "2024-01-25", + "last_updated": "2025-09-11", "type": "chat" }, { - "id": "xai/grok-4-fast-reasoning", - "name": "Grok-4-Fast-Reasoning", - "display_name": "Grok-4-Fast-Reasoning", + "id": "qwen/qwen3.8-flash", + "name": "Qwen3.8 Flash", + "display_name": "Qwen3.8 Flash", "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 2000000, - "output": 128000 + "context": 1000000, + "output": 131072 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, "default": true }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "effort_options": [ + "none", + "high" + ] + } + }, "attachment": true, "open_weights": false, - "release_date": "2025-12-18", - "last_updated": "2025-12-18", + "release_date": "2026-08-26", + "last_updated": "2026-08-26", "type": "chat" }, { - "id": "xai/grok-4", - "name": "Grok-4", - "display_name": "Grok-4", + "id": "qwen/qwen3.6-35b-a3b", + "name": "Qwen3.6 35B A3B", + "display_name": "Qwen3.6 35B A3B", "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 256000, - "output": 128000 + "context": 1048576, + "output": 65536 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true + "supported": true }, "extra_capabilities": { "reasoning": { - "supported": true + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] } }, "attachment": true, - "open_weights": false, - "release_date": "2025-07-10", - "last_updated": "2025-07-10", + "open_weights": true, + "release_date": "2026-04-17", + "last_updated": "2026-04-17", "type": "chat" }, { - "id": "xai/grok-4-fast-non-reasoning", - "name": "Grok-4-Fast-Non-Reasoning", - "display_name": "Grok-4-Fast-Non-Reasoning", + "id": "qwen/qwen3-30b-a3b", + "name": "Qwen3 30B A3B", + "display_name": "Qwen3 30B A3B", + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 41000, + "output": 32768 + }, + "temperature": true, + "tool_call": false, + "reasoning": { + "supported": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } + }, + "attachment": false, + "open_weights": true, + "release_date": "2025-04-28", + "last_updated": "2025-04-28", + "type": "chat" + }, + { + "id": "qwen/qwen3.5-plus", + "name": "Qwen3.5 Plus", + "display_name": "Qwen3.5 Plus", "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 2000000, - "output": 128000 + "context": 983616, + "output": 65536 }, - "temperature": false, - "tool_call": true, + "temperature": true, + "tool_call": false, "reasoning": { - "supported": false + "supported": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } }, "attachment": true, "open_weights": false, - "release_date": "2025-12-18", - "last_updated": "2025-12-18", + "knowledge": "2025-04", + "release_date": "2026-02-16", + "last_updated": "2026-02-16", "type": "chat" }, { - "id": "fireworks-ai/kimi-k2.5-fw", - "name": "Kimi-K2.5-FW", - "display_name": "Kimi-K2.5-FW", + "id": "qwen/qwen3.8-27b-darkidol", + "name": "Qwen 3.8 27B DarkIdol", + "display_name": "Qwen 3.8 27B DarkIdol", "modalities": { "input": [ "text", @@ -70745,310 +71234,283 @@ }, "limit": { "context": 262144, - "output": 16384 + "output": 32768 }, - "temperature": false, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, "attachment": true, "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-01-27", - "last_updated": "2026-01-27", + "release_date": "2026-10-05", + "last_updated": "2026-10-05", "type": "chat" }, { - "id": "anthropic/claude-sonnet-3.5", - "name": "Claude-Sonnet-3.5", - "display_name": "Claude-Sonnet-3.5", + "id": "qwen/qwen3.8-27b-swift", + "name": "Qwen 3.8 27B Swift", + "display_name": "Qwen 3.8 27B Swift", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 189096, - "output": 8192 + "context": 262144, + "output": 32768 }, - "temperature": false, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, "attachment": true, - "open_weights": false, - "knowledge": "2024-04-30", - "release_date": "2024-10-22", - "last_updated": "2024-10-22", + "open_weights": true, + "release_date": "2026-10-05", + "last_updated": "2026-10-05", "type": "chat" }, { - "id": "anthropic/claude-haiku-3.5", - "name": "Claude-Haiku-3.5", - "display_name": "Claude-Haiku-3.5", + "id": "qwen/qwen3.5-flash", + "name": "Qwen3.5 Flash", + "display_name": "Qwen3.5 Flash", "modalities": { "input": [ "text", "image", - "pdf" + "video" ], "output": [ "text" ] }, "limit": { - "context": 189096, - "output": 8192 + "context": 991808, + "output": 65536 }, - "temperature": false, - "tool_call": true, + "temperature": true, + "tool_call": false, "reasoning": { - "supported": false + "supported": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } }, "attachment": true, "open_weights": false, - "knowledge": "2024-07-31", - "release_date": "2024-10-22", - "last_updated": "2024-10-22", + "release_date": "2026-02-23", + "last_updated": "2026-02-23", "type": "chat" }, { - "id": "anthropic/claude-opus-4", - "name": "Claude-Opus-4", - "display_name": "Claude-Opus-4", + "id": "qwen/qwen3.7-flash", + "name": "Qwen3.7 Flash", + "display_name": "Qwen3.7 Flash", "modalities": { "input": [ "text", "image", - "pdf" + "video" ], "output": [ "text" ] }, "limit": { - "context": 192512, - "output": 28672 + "context": 991808, + "output": 65536 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "budget", - "budget": { - "min": 1024, - "unit": "tokens" - }, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", - "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." + "effort_options": [ + "none", + "high" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-03-31", - "release_date": "2025-05-22", - "last_updated": "2025-05-22", + "release_date": "2026-07-15", + "last_updated": "2026-07-15", "type": "chat" }, { - "id": "anthropic/claude-haiku-3", - "name": "Claude-Haiku-3", - "display_name": "Claude-Haiku-3", + "id": "qwen/qwen3-coder-next", + "name": "Qwen3 Coder Next", + "display_name": "Qwen3 Coder Next", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 189096, - "output": 8192 + "context": 262144, + "output": 65536 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": false }, - "attachment": true, - "open_weights": false, - "knowledge": "2023-08-31", - "release_date": "2024-03-13", - "last_updated": "2024-03-13", + "attachment": false, + "open_weights": true, + "knowledge": "2025-09", + "release_date": "2026-02-03", + "last_updated": "2026-02-03", "type": "chat" }, { - "id": "anthropic/claude-opus-4.1", - "name": "Claude-Opus-4.1", - "display_name": "Claude-Opus-4.1", + "id": "qwen/qwen3.5-397b-a17b:thinking", + "name": "Qwen3.5 397B A17B Thinking", + "display_name": "Qwen3.5 397B A17B Thinking", "modalities": { "input": [ "text", "image", - "pdf" + "video" ], "output": [ "text" ] }, "limit": { - "context": 196608, - "output": 32000 + "context": 262144, + "output": 65536 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "budget", - "budget": { - "min": 1024, - "unit": "tokens" - }, "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ "thinking_blocks" - ], - "notes": [ - "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", - "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2025-03-31", - "release_date": "2025-08-05", - "last_updated": "2025-08-05", + "open_weights": true, + "release_date": "2026-02-15", + "last_updated": "2026-02-15", "type": "chat" }, { - "id": "anthropic/claude-sonnet-3.7", - "name": "Claude-Sonnet-3.7", - "display_name": "Claude-Sonnet-3.7", + "id": "qwen/qwen3-8b", + "name": "Qwen 3 8B", + "display_name": "Qwen 3 8B", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 196608, - "output": 128000 + "context": 41000, + "output": 32768 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true + "supported": true }, - "attachment": true, - "open_weights": false, - "knowledge": "2024-10-31", - "release_date": "2025-02-19", - "last_updated": "2025-02-19", + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } + }, + "attachment": false, + "open_weights": true, + "release_date": "2025-04-29", + "last_updated": "2025-04-29", "type": "chat" }, { - "id": "anthropic/claude-opus-4.8", - "name": "Claude-Opus-4.8", - "display_name": "Claude-Opus-4.8", + "id": "qwen/qwen3.5-35b-a3b:thinking", + "name": "Qwen3.5 35B A3B Thinking", + "display_name": "Qwen3.5 35B A3B Thinking", "modalities": { "input": [ "text", "image", - "pdf" + "video" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 128000 + "context": 260096, + "output": 65536 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "high", - "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" - ], "interleaved": true, "summaries": true, - "visibility": "omitted", + "visibility": "summary", "continuation": [ "thinking_blocks" - ], - "notes": [ - "Claude Opus 4.7 and newer Opus models require thinking.type = \"adaptive\" to enable thinking explicitly.", - "Manual budget_tokens requests return 400 on Claude Opus 4.7 and newer adaptive-only Opus models.", - "task_budget is separate from thinking control and should not be treated as a thinking budget." ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2026-01", - "release_date": "2026-05-28", - "last_updated": "2026-05-28", + "open_weights": true, + "release_date": "2026-02-23", + "last_updated": "2026-02-23", "type": "chat" }, { - "id": "anthropic/claude-opus-4.6", - "name": "Claude-Opus-4.6", - "display_name": "Claude-Opus-4.6", + "id": "qwen/qwen3.8-max-0902", + "name": "Qwen3.8 Max 0902", + "display_name": "Qwen3.8 Max 0902", "modalities": { "input": [ "text", "image", + "video", "pdf" ], "output": [ @@ -71056,116 +71518,78 @@ ] }, "limit": { - "context": 983040, - "output": 128000 + "context": 991808, + "output": 131072 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "mixed", - "budget": { - "min": 1024, - "unit": "tokens" - }, - "effort": "high", "effort_options": [ + "none", "low", "medium", "high", "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Anthropic recommends adaptive thinking with effort for Claude 4.6; budget_tokens remains a deprecated compatibility path.", - "Anthropic API defaults effort to high; lower effort levels should be chosen per workload." ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-05-31", - "release_date": "2026-02-05", - "last_updated": "2026-03-13", + "release_date": "2026-09-02", + "last_updated": "2026-09-02", "type": "chat" }, { - "id": "anthropic/claude-opus-4.5", - "name": "Claude-Opus-4.5", - "display_name": "Claude-Opus-4.5", + "id": "qwen/qwen-turbo", + "name": "Qwen Turbo", + "display_name": "Qwen Turbo", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 196608, - "output": 64000 + "context": 1000000, + "output": 8192 }, - "temperature": false, - "tool_call": true, + "temperature": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": false + "supported": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "mixed", - "budget": { - "min": 1024, - "unit": "tokens" - }, - "effort": "high", - "effort_options": [ - "low", - "medium", - "high" - ], "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ "thinking_blocks" - ], - "notes": [ - "Claude Opus 4.5 uses manual thinking.type = \"enabled\" with budget_tokens; effort can be used alongside the thinking budget.", - "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header." ] } }, - "attachment": true, + "attachment": false, "open_weights": false, - "knowledge": "2025-05", - "release_date": "2025-11-24", - "last_updated": "2025-11-24", + "knowledge": "2024-04", + "release_date": "2024-11-01", + "last_updated": "2025-04-28", "type": "chat" }, { - "id": "anthropic/claude-sonnet-4.6", - "name": "Claude-Sonnet-4.6", - "display_name": "Claude-Sonnet-4.6", + "id": "qwen/qwen3-235b-a22b", + "name": "Qwen 3 235b A22B", + "display_name": "Qwen 3 235b A22B", "modalities": { "input": [ "text", - "image", "pdf" ], "output": [ @@ -71173,422 +71597,327 @@ ] }, "limit": { - "context": 983040, - "output": 128000 + "context": 262144, + "output": 16384 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": false + "supported": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "mixed", - "budget": { - "min": 1024, - "unit": "tokens" - }, - "effort": "high", - "effort_options": [ - "low", - "medium", - "high", - "max" - ], "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ "thinking_blocks" - ], - "notes": [ - "Anthropic recommends adaptive thinking with effort for Claude 4.6; budget_tokens remains a deprecated compatibility path.", - "Anthropic API defaults effort to high; lower effort levels should be chosen per workload." ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-02-17", - "last_updated": "2026-03-13", + "open_weights": true, + "knowledge": "2025-04", + "release_date": "2025-04-28", + "last_updated": "2025-04-28", "type": "chat" }, { - "id": "anthropic/claude-sonnet-3.5-june", - "name": "Claude-Sonnet-3.5-June", - "display_name": "Claude-Sonnet-3.5-June", + "id": "qwen/qwen3.5-omni-plus", + "name": "Qwen3.5 Omni Plus", + "display_name": "Qwen3.5 Omni Plus", "modalities": { "input": [ "text", "image", - "pdf" + "video", + "audio" ], "output": [ "text" ] }, "limit": { - "context": 189096, - "output": 8192 + "context": 983616, + "output": 65536 }, - "temperature": false, - "tool_call": true, + "tool_call": false, "reasoning": { "supported": false }, "attachment": true, "open_weights": false, - "release_date": "2024-11-18", - "last_updated": "2024-11-18", + "release_date": "2026-03-30", + "last_updated": "2026-03-30", "type": "chat" }, { - "id": "anthropic/claude-haiku-4.5", - "name": "Claude-Haiku-4.5", - "display_name": "Claude-Haiku-4.5", + "id": "qwen/qwen3.5-27b:thinking", + "name": "Qwen3.5 27B Thinking", + "display_name": "Qwen3.5 27B Thinking", "modalities": { "input": [ "text", "image", - "pdf" + "video" ], "output": [ "text" ] }, "limit": { - "context": 192000, - "output": 64000 + "context": 260096, + "output": 65536 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "budget", - "budget": { - "min": 1024, - "unit": "tokens" - }, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", - "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." - ] - } + "default": true }, "attachment": true, - "open_weights": false, - "knowledge": "2025-02-28", - "release_date": "2025-10-15", - "last_updated": "2025-10-15", + "open_weights": true, + "release_date": "2026-02-23", + "last_updated": "2026-02-23", "type": "chat" }, { - "id": "anthropic/claude-opus-4.7", - "name": "Claude-Opus-4.7", - "display_name": "Claude-Opus-4.7", + "id": "qwen/qwen3.8-max-prime", + "name": "Qwen3.8 Max Prime", + "display_name": "Qwen3.8 Max Prime", "modalities": { "input": [ "text", "image", - "pdf" + "video" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 128000 + "context": 1000000, + "output": 131072 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "high", "effort_options": [ + "minimal", "low", "medium", "high", - "xhigh", - "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "omitted", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Claude Opus 4.7 and newer Opus models require thinking.type = \"adaptive\" to enable thinking explicitly.", - "Manual budget_tokens requests return 400 on Claude Opus 4.7 and newer adaptive-only Opus models.", - "task_budget is separate from thinking control and should not be treated as a thinking budget." + "xhigh" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-01-31", - "release_date": "2026-04-16", - "last_updated": "2026-04-16", + "release_date": "2026-09-23", + "last_updated": "2026-09-23", "type": "chat" }, { - "id": "anthropic/claude-sonnet-4", - "name": "Claude-Sonnet-4", - "display_name": "Claude-Sonnet-4", + "id": "qwen/qwen3-next-80b-a3b-thinking", + "name": "Qwen3 Next 80B A3B (Thinking)", + "display_name": "Qwen3 Next 80B A3B (Thinking)", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 983040, - "output": 64000 + "context": 256000, + "output": 32768 }, - "temperature": false, - "tool_call": true, + "temperature": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": false + "supported": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "budget", - "budget": { - "min": 1024, - "unit": "tokens" - }, "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ "thinking_blocks" - ], - "notes": [ - "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", - "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-03-31", - "release_date": "2025-05-22", - "last_updated": "2025-05-22", + "attachment": false, + "open_weights": true, + "knowledge": "2025-04", + "release_date": "2025-09", + "last_updated": "2025-09", "type": "chat" }, { - "id": "anthropic/claude-sonnet-4.5", - "name": "Claude-Sonnet-4.5", - "display_name": "Claude-Sonnet-4.5", + "id": "qwen/qwen3.5-plus:thinking", + "name": "Qwen3.5 Plus Thinking", + "display_name": "Qwen3.5 Plus Thinking", "modalities": { "input": [ "text", "image", - "pdf" + "video" ], "output": [ "text" ] }, "limit": { - "context": 983040, - "output": 32768 + "context": 983616, + "output": 65536 }, - "temperature": false, - "tool_call": true, + "temperature": true, + "tool_call": false, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "budget", - "budget": { - "min": 1024, - "unit": "tokens" - }, "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ "thinking_blocks" - ], - "notes": [ - "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", - "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-07-31", - "release_date": "2025-09-29", - "last_updated": "2025-09-29", + "knowledge": "2025-04", + "release_date": "2026-02-16", + "last_updated": "2026-02-16", "type": "chat" }, { - "id": "lumalabs/ray2", - "name": "Ray2", - "display_name": "Ray2", + "id": "qwen/qwen3.8-27b", + "name": "Qwen3.8 27B", + "display_name": "Qwen3.8 27B", "modalities": { "input": [ "text", - "image" - ], - "output": [ + "image", "video" - ] - }, - "limit": { - "context": 5000, - "output": 8192 - }, - "temperature": false, - "tool_call": true, - "reasoning": { - "supported": false - }, - "attachment": true, - "open_weights": false, - "release_date": "2025-02-20", - "last_updated": "2025-02-20" - }, - { - "id": "topazlabs-co/topazlabs", - "name": "TopazLabs", - "display_name": "TopazLabs", - "modalities": { - "input": [ - "text", - "image" ], "output": [ - "image" + "text" ] }, "limit": { - "context": 204, - "output": 8192 + "context": 1048576, + "output": 65536 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, "attachment": true, - "open_weights": false, - "release_date": "2024-12-03", - "last_updated": "2024-12-03" + "open_weights": true, + "release_date": "2026-08-14", + "last_updated": "2026-08-14", + "type": "chat" }, { - "id": "google/veo-3-fast", - "name": "Veo-3-Fast", - "display_name": "Veo-3-Fast", + "id": "qwen/qwen3-14b", + "name": "Qwen 3 14b", + "display_name": "Qwen 3 14b", "modalities": { "input": [ "text" ], "output": [ - "video" + "text" ] }, "limit": { - "context": 480, - "output": 8192 + "context": 41000, + "output": 32768 }, - "temperature": false, "tool_call": true, "reasoning": { - "supported": false + "supported": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } }, "attachment": false, - "open_weights": false, - "release_date": "2025-10-13", - "last_updated": "2025-10-13" + "open_weights": true, + "release_date": "2024-01-01", + "last_updated": "2024-01-01", + "type": "chat" }, { - "id": "google/gemma-4-31b", - "name": "Gemma-4-31B", - "display_name": "Gemma-4-31B", + "id": "qwen/qwen3-coder-flash", + "name": "Qwen3 Coder Flash", + "display_name": "Qwen3 Coder Flash", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 8192 + "context": 128000, + "output": 65536 }, - "temperature": false, - "tool_call": true, + "temperature": true, + "tool_call": false, "reasoning": { "supported": false }, - "attachment": true, - "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-04-02", - "last_updated": "2026-04-02", + "attachment": false, + "open_weights": false, + "knowledge": "2025-04", + "release_date": "2025-07-28", + "last_updated": "2025-07-28", "type": "chat" }, { - "id": "google/gemini-2.5-pro", - "name": "Gemini-2.5-Pro", - "display_name": "Gemini-2.5-Pro", + "id": "qwen/qwen3-vl-235b-a22b-thinking", + "name": "Qwen3 VL 235B A22B Thinking", + "display_name": "Qwen3 VL 235B A22B Thinking", "modalities": { "input": [ "text", - "image", - "video", - "audio" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1065535, - "output": 65535 + "context": 131072, + "output": 32768 }, - "temperature": false, - "tool_call": true, + "temperature": true, + "tool_call": false, "reasoning": { "supported": true, "default": true @@ -71596,133 +71925,92 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "budget", - "budget": { - "default": -1, - "min": 128, - "max": 32768, - "auto": -1, - "unit": "tokens" - }, + "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ - "thought_signatures" + "thinking_blocks" ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2025-01", - "release_date": "2025-06-17", - "last_updated": "2025-06-17", + "open_weights": true, + "knowledge": "2025-03-31", + "release_date": "2025-09-23", + "last_updated": "2025-09-23", "type": "chat" }, { - "id": "google/gemini-3.5-flash-lite", - "name": "Gemini-3.5-Flash-Lite", - "display_name": "Gemini-3.5-Flash-Lite", + "id": "qwen/qwen3.6-27b", + "name": "Qwen3.6 27B", + "display_name": "Qwen3.6 27B", "modalities": { "input": [ "text", "image", - "video", - "audio" + "video" ], "output": [ "text" ] }, "limit": { - "context": 1048576, + "context": 262144, "output": 65536 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true + "supported": true }, "extra_capabilities": { "reasoning": { "supported": true, - "effort_options": [ - "minimal", - "low", - "medium", - "high" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2026-03", - "release_date": "2026-07-21", - "last_updated": "2026-07-21", + "open_weights": true, + "release_date": "2026-04-22", + "last_updated": "2026-04-22", "type": "chat" }, { - "id": "google/imagen-4-ultra", - "name": "Imagen-4-Ultra", - "display_name": "Imagen-4-Ultra", + "id": "qwen/qwen2.5-coder-32b-instruct", + "name": "Qwen 2.5 Coder 32b", + "display_name": "Qwen 2.5 Coder 32b", "modalities": { "input": [ "text" ], "output": [ - "image" + "text" ] }, "limit": { - "context": 480, + "context": 32000, "output": 8192 }, - "temperature": false, - "tool_call": true, + "temperature": true, + "tool_call": false, "reasoning": { "supported": false }, "attachment": false, - "open_weights": false, - "release_date": "2025-05-24", - "last_updated": "2025-05-24" - }, - { - "id": "google/gemini-2.0-flash-lite", - "name": "Gemini-2.0-Flash-Lite", - "display_name": "Gemini-2.0-Flash-Lite", - "modalities": { - "input": [ - "text", - "image", - "video", - "audio" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 990000, - "output": 8192 - }, - "temperature": false, - "tool_call": true, - "reasoning": { - "supported": false - }, - "attachment": true, - "open_weights": false, - "knowledge": "2024-06", - "release_date": "2024-12-11", - "last_updated": "2024-12-11", + "open_weights": true, + "release_date": "2024-11-12", + "last_updated": "2024-11-12", "type": "chat" }, { - "id": "google/gemini-deep-research", - "name": "gemini-deep-research", - "display_name": "gemini-deep-research", + "id": "qwen/qwen3.5-9b", + "name": "Qwen3.5 9B", + "display_name": "Qwen3.5 9B", "modalities": { "input": [ "text", @@ -71734,473 +72022,420 @@ ] }, "limit": { - "context": 1048576, - "output": 8192 + "context": 262144, + "output": 65536 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, "default": true }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } + }, "attachment": true, - "open_weights": false, - "release_date": "2025-12-11", - "last_updated": "2025-12-11", + "open_weights": true, + "release_date": "2026-02-23", + "last_updated": "2026-02-23", "type": "chat" }, { - "id": "google/gemini-2.5-flash", - "name": "Gemini-2.5-Flash", - "display_name": "Gemini-2.5-Flash", + "id": "qwen/qwen3.8-27b-fimi", + "name": "Qwen 3.8 27B Fimi", + "display_name": "Qwen 3.8 27B Fimi", "modalities": { "input": [ "text", - "image", - "video", - "audio" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1065535, - "output": 65535 + "context": 262144, + "output": 32768 }, - "temperature": false, "tool_call": true, "reasoning": { "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "budget", - "budget": { - "default": -1, - "min": 0, - "max": 24576, - "auto": -1, - "off": 0, - "unit": "tokens" - }, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thought_signatures" - ] - } - }, "attachment": true, - "open_weights": false, - "knowledge": "2025-01", - "release_date": "2025-06-17", - "last_updated": "2025-06-17", + "open_weights": true, + "release_date": "2026-10-05", + "last_updated": "2026-10-05", "type": "chat" }, { - "id": "google/veo-3", - "name": "Veo-3", - "display_name": "Veo-3", + "id": "qwen/qwen3-coder-30b-a3b-instruct", + "name": "Qwen3 Coder 30B A3B Instruct", + "display_name": "Qwen3 Coder 30B A3B Instruct", "modalities": { "input": [ "text" ], "output": [ - "video" + "text" ] }, "limit": { - "context": 480, - "output": 8192 + "context": 128000, + "output": 65536 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": false }, "attachment": false, - "open_weights": false, - "release_date": "2025-05-21", - "last_updated": "2025-05-21" + "open_weights": true, + "knowledge": "2025-04", + "release_date": "2025-07-31", + "last_updated": "2025-07-31", + "type": "chat" }, { - "id": "google/gemini-3.1-flash-lite", - "name": "Gemini-3.1-Flash-Lite", - "display_name": "Gemini-3.1-Flash-Lite", + "id": "qwen/qwen3.6-plus", + "name": "Qwen 3.6 Plus", + "display_name": "Qwen 3.6 Plus", "modalities": { "input": [ "text", "image", - "video", - "audio" + "video" ], "output": [ "text" ] }, "limit": { - "context": 1048576, + "context": 991808, "output": 65536 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true + "supported": true }, "extra_capabilities": { "reasoning": { "supported": true, - "effort_options": [ - "minimal", - "low", - "high" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-01", - "release_date": "2026-05-07", - "last_updated": "2026-05-07", + "knowledge": "2025-04", + "release_date": "2026-04-02", + "last_updated": "2026-04-02", "type": "chat" }, { - "id": "google/gemini-3.7-flash", - "name": "Gemini-3.7-Flash", - "display_name": "Gemini-3.7-Flash", + "id": "qwen/qwen3.6-max-preview", + "name": "Qwen3.6 Max Preview", + "display_name": "Qwen3.6 Max Preview", "modalities": { "input": [ - "text", - "image", - "video", - "audio" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, + "context": 245760, "output": 65536 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true + "supported": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "level", - "level": "high", - "level_options": [ - "minimal", - "low", - "medium", - "high" - ], + "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ - "thought_signatures" + "thinking_blocks" ] } }, - "attachment": true, + "attachment": false, "open_weights": false, - "knowledge": "2026-03", - "release_date": "2026-08-13", - "last_updated": "2026-08-13", + "knowledge": "2025-04", + "release_date": "2026-04-20", + "last_updated": "2026-04-20", "type": "chat" }, { - "id": "google/lyria", - "name": "Lyria", - "display_name": "Lyria", + "id": "qwen/qwen3.8-27b-novelist", + "name": "Qwen 3.8 27B Novelist", + "display_name": "Qwen 3.8 27B Novelist", + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 262144, + "output": 32768 + }, + "tool_call": true, + "reasoning": { + "supported": true, + "default": true + }, + "attachment": true, + "open_weights": true, + "release_date": "2026-10-05", + "last_updated": "2026-10-05", + "type": "chat" + }, + { + "id": "qwen/qwen3-coder", + "name": "Qwen 3 Coder 480B", + "display_name": "Qwen 3 Coder 480B", "modalities": { "input": [ "text" ], "output": [ - "audio" + "text" ] }, "limit": { - "context": 8192, - "output": 8192 + "context": 262000, + "output": 65536 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": false }, "attachment": false, - "open_weights": false, - "release_date": "2025-06-04", - "last_updated": "2025-06-04", + "open_weights": true, + "knowledge": "2025-04", + "release_date": "2025-07-22", + "last_updated": "2025-07-22", "type": "chat" }, { - "id": "google/veo-2", - "name": "Veo-2", - "display_name": "Veo-2", + "id": "qwen/qwen3-235b-a22b-instruct-2507", + "name": "Qwen 3 235b A22B 2507", + "display_name": "Qwen 3 235b A22B 2507", "modalities": { "input": [ "text" ], "output": [ - "video" + "text" ] }, "limit": { - "context": 480, - "output": 8192 + "context": 262144, + "output": 16384 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": false }, "attachment": false, - "open_weights": false, - "release_date": "2024-12-02", - "last_updated": "2024-12-02" + "open_weights": true, + "release_date": "2025-07-21", + "last_updated": "2025-07-21", + "type": "chat" }, { - "id": "google/gemini-3.1-pro", - "name": "Gemini-3.1-Pro", - "display_name": "Gemini-3.1-Pro", + "id": "qwen/qwen3.5-35b-a3b", + "name": "Qwen3.5 35B A3B", + "display_name": "Qwen3.5 35B A3B", "modalities": { "input": [ "text", "image", - "video", - "audio" + "video" ], "output": [ "text" ] }, "limit": { - "context": 1048576, + "context": 260096, "output": 65536 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true + "supported": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "level", - "level": "high", - "level_options": [ - "low", - "high" - ], + "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ - "thought_signatures" + "thinking_blocks" ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2025-01", - "release_date": "2026-02-19", - "last_updated": "2026-02-19", + "open_weights": true, + "release_date": "2026-02-23", + "last_updated": "2026-02-23", "type": "chat" }, { - "id": "google/gemini-2.0-flash", - "name": "Gemini-2.0-Flash", - "display_name": "Gemini-2.0-Flash", + "id": "qwen/qwen3.8-max", + "name": "Qwen3.8 Max", + "display_name": "Qwen3.8 Max", "modalities": { "input": [ "text", "image", "video", - "audio" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 990000, - "output": 8192 + "context": 991000, + "output": 65536 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true + } }, "attachment": true, "open_weights": false, - "knowledge": "2024-06", - "release_date": "2024-12-11", - "last_updated": "2024-12-11", + "release_date": "2026-08-03", + "last_updated": "2026-08-03", "type": "chat" }, { - "id": "google/gemini-3-flash", - "name": "Gemini-3-Flash", - "display_name": "Gemini-3-Flash", + "id": "qwen/qwen3-235b-a22b-thinking-2507", + "name": "Qwen 3 235b A22B 2507 Thinking", + "display_name": "Qwen 3 235b A22B 2507 Thinking", "modalities": { "input": [ - "text", - "image", - "video", - "audio" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 131072, + "output": 81920 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true + "supported": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "level", - "level": "high", - "level_options": [ - "minimal", - "low", - "medium", - "high" - ], + "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ - "thought_signatures" + "thinking_blocks" ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-01", - "release_date": "2025-12-17", - "last_updated": "2025-12-17", + "attachment": false, + "open_weights": true, + "release_date": "2025-07-25", + "last_updated": "2025-07-25", "type": "chat" }, { - "id": "google/nano-banana", - "name": "Nano-Banana", - "display_name": "Nano-Banana", - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text", - "image" - ] - }, - "limit": { - "context": 65536, - "output": 8192 - }, - "temperature": false, - "tool_call": true, - "reasoning": { - "supported": false - }, - "attachment": true, - "open_weights": false, - "knowledge": "2024-06", - "release_date": "2025-08-26", - "last_updated": "2025-08-26" - }, - { - "id": "google/gemini-2.5-flash-lite", - "name": "Gemini-2.5-Flash-Lite", - "display_name": "Gemini-2.5-Flash-Lite", + "id": "qwen/qwen3.5-397b-a17b", + "name": "Qwen3.5 397B A17B", + "display_name": "Qwen3.5 397B A17B", "modalities": { "input": [ "text", "image", - "video", - "audio" + "video" ], "output": [ "text" ] }, "limit": { - "context": 1024000, - "output": 64000 + "context": 262144, + "output": 65536 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": false + "supported": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "budget", - "budget": { - "default": -1, - "min": 512, - "max": 24576, - "auto": -1, - "unit": "tokens" - }, + "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ - "thought_signatures" + "thinking_blocks" ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2025-01", - "release_date": "2025-06-17", - "last_updated": "2025-06-17", + "open_weights": true, + "release_date": "2026-02-15", + "last_updated": "2026-02-15", "type": "chat" }, { - "id": "google/gemini-3.8-flash", - "name": "Gemini-3.8-Flash", - "display_name": "Gemini-3.8-Flash", + "id": "qwen/qwen3.7-plus", + "name": "Qwen3.7 Plus", + "display_name": "Qwen3.7 Plus", "modalities": { "input": [ "text", "image", - "video", - "audio" + "video" ], "output": [ "text" ] }, "limit": { - "context": 1048576, + "context": 991808, "output": 65536 }, "temperature": true, @@ -72212,136 +72447,158 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "level", - "level": "high", - "level_options": [ - "minimal", - "low", - "medium", + "effort_options": [ + "none", "high" - ], - "summaries": true, - "visibility": "summary", - "continuation": [ - "thought_signatures" ] } }, "attachment": true, "open_weights": false, - "release_date": "2026-09-02", - "last_updated": "2026-09-02", + "knowledge": "2025-04", + "release_date": "2026-06-02", + "last_updated": "2026-06-02", "type": "chat" }, { - "id": "google/veo-3.1-fast", - "name": "Veo-3.1-Fast", - "display_name": "Veo-3.1-Fast", + "id": "qwen/qwen3.8-27b-fable", + "name": "Qwen 3.8 27B Fable", + "display_name": "Qwen 3.8 27B Fable", "modalities": { "input": [ "text", "image" ], "output": [ - "video" + "text" ] }, "limit": { - "context": 480, - "output": 8192 + "context": 524288, + "output": 32768 }, - "temperature": false, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, "attachment": true, - "open_weights": false, - "release_date": "2025-10-15", - "last_updated": "2025-10-15" + "open_weights": true, + "release_date": "2026-07-29", + "last_updated": "2026-08-28", + "type": "chat" }, { - "id": "google/gemini-3.6-flash", - "name": "Gemini-3.6-Flash", - "display_name": "Gemini-3.6-Flash", + "id": "qwen/qwen3.5-122b-a10b", + "name": "Qwen3.5 122B A10B", + "display_name": "Qwen3.5 122B A10B", + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "output": 32768 + }, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } + }, + "attachment": false, + "open_weights": true, + "release_date": "2026-02-23", + "last_updated": "2026-02-23", + "type": "chat" + }, + { + "id": "qwen/qwen3-32b", + "name": "Qwen 3 32b", + "display_name": "Qwen 3 32b", "modalities": { "input": [ "text", - "image", - "video", - "audio" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 41000, + "output": 32768 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true + "supported": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "level", - "level": "high", - "level_options": [ - "minimal", - "low", - "medium", - "high" - ], + "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ - "thought_signatures" + "thinking_blocks" ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2026-03", - "release_date": "2026-07-21", - "last_updated": "2026-07-21", + "open_weights": true, + "knowledge": "2025-04", + "release_date": "2025-04-28", + "last_updated": "2025-04-28", "type": "chat" }, { - "id": "google/imagen-4", - "name": "Imagen-4", - "display_name": "Imagen-4", + "id": "qwen/qwen3.5-omni-flash", + "name": "Qwen3.5 Omni Flash", + "display_name": "Qwen3.5 Omni Flash", "modalities": { "input": [ - "text" + "text", + "image", + "video", + "audio" ], "output": [ - "image" + "text" ] }, "limit": { - "context": 480, - "output": 8192 + "context": 49152, + "output": 16384 }, - "temperature": false, - "tool_call": true, + "tool_call": false, "reasoning": { "supported": false }, - "attachment": false, + "attachment": true, "open_weights": false, - "release_date": "2025-05-22", - "last_updated": "2025-05-22" + "release_date": "2026-03-30", + "last_updated": "2026-03-30", + "type": "chat" }, { - "id": "google/gemini-3.5-flash", - "name": "Gemini-3.5-Flash", - "display_name": "Gemini-3.5-Flash", + "id": "qwen/qwen3.8-27b:thinking", + "name": "Qwen3.8 27B Thinking", + "display_name": "Qwen3.8 27B Thinking", "modalities": { "input": [ "text", @@ -72362,90 +72619,72 @@ "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "level", - "level": "high", - "level_options": [ - "minimal", - "low", - "medium", - "high" - ], - "summaries": true, - "visibility": "summary", - "continuation": [ - "thought_signatures" - ] - } - }, "attachment": true, - "open_weights": false, - "knowledge": "2025-01", - "release_date": "2026-05-19", - "last_updated": "2026-05-19", + "open_weights": true, + "release_date": "2026-08-14", + "last_updated": "2026-08-14", "type": "chat" }, { - "id": "google/veo-3.1", - "name": "Veo-3.1", - "display_name": "Veo-3.1", + "id": "qwen/qwen-max", + "name": "Qwen 2.5 Max", + "display_name": "Qwen 2.5 Max", "modalities": { "input": [ "text" ], "output": [ - "video" + "text" ] }, "limit": { - "context": 480, + "context": 32000, "output": 8192 }, - "temperature": false, - "tool_call": true, + "temperature": true, + "tool_call": false, "reasoning": { "supported": false }, "attachment": false, "open_weights": false, - "release_date": "2025-10-15", - "last_updated": "2025-10-15" + "knowledge": "2024-04", + "release_date": "2024-04-03", + "last_updated": "2025-01-25", + "type": "chat" }, { - "id": "google/nano-banana-pro", - "name": "Nano-Banana-Pro", - "display_name": "Nano-Banana-Pro", + "id": "qwen/qwen3.8-27b-uncensored", + "name": "Qwen 3.8 27B Uncensored", + "display_name": "Qwen 3.8 27B Uncensored", "modalities": { "input": [ "text", "image" ], "output": [ - "image" + "text" ] }, "limit": { - "context": 65536, - "output": 8192 + "context": 1048576, + "output": 65536 }, - "temperature": false, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, "attachment": true, - "open_weights": false, - "knowledge": "2025-01", - "release_date": "2025-11-20", - "last_updated": "2025-11-20" + "open_weights": true, + "release_date": "2026-07-29", + "last_updated": "2026-08-21", + "type": "chat" }, { - "id": "google/gemini-omni-1.1-flash", - "name": "Gemini-Omni-1.1-Flash", - "display_name": "Gemini-Omni-1.1-Flash", + "id": "qwen/qwen3.6-35b-a3b:thinking", + "name": "Qwen3.6 35B A3B Thinking", + "display_name": "Qwen3.6 35B A3B Thinking", "modalities": { "input": [ "text", @@ -72453,212 +72692,270 @@ "video" ], "output": [ - "video" + "text" ] }, "limit": { - "context": 131072, - "output": 57920 + "context": 1048576, + "output": 65536 }, - "temperature": false, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { "supported": true, "default": true }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } + }, "attachment": true, - "open_weights": false, - "release_date": "2026-08-27", - "last_updated": "2026-08-27" + "open_weights": true, + "release_date": "2026-04-17", + "last_updated": "2026-04-17", + "type": "chat" }, { - "id": "google/imagen-4-fast", - "name": "Imagen-4-Fast", - "display_name": "Imagen-4-Fast", + "id": "qwen/qwen3.5-flash:thinking", + "name": "Qwen3.5 Flash Thinking", + "display_name": "Qwen3.5 Flash Thinking", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ - "image" + "text" ] }, "limit": { - "context": 480, - "output": 8192 + "context": 991808, + "output": 65536 }, - "temperature": false, - "tool_call": true, + "temperature": true, + "tool_call": false, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } + }, + "attachment": true, "open_weights": false, - "release_date": "2025-06-25", - "last_updated": "2025-06-25" + "release_date": "2026-02-23", + "last_updated": "2026-02-23", + "type": "chat" }, { - "id": "google/imagen-3-fast", - "name": "Imagen-3-Fast", - "display_name": "Imagen-3-Fast", + "id": "qwen/qwen-long", + "name": "Qwen Long 10M", + "display_name": "Qwen Long 10M", "modalities": { "input": [ "text" ], "output": [ - "image" + "text" ] }, "limit": { - "context": 480, + "context": 10000000, "output": 8192 }, - "temperature": false, - "tool_call": true, + "tool_call": false, "reasoning": { "supported": false }, "attachment": false, "open_weights": false, - "release_date": "2024-10-17", - "last_updated": "2024-10-17" + "release_date": "2024-08-01", + "last_updated": "2024-08-01", + "type": "chat" }, { - "id": "google/imagen-3", - "name": "Imagen-3", - "display_name": "Imagen-3", + "id": "qwen/qwen3.7-plus:thinking", + "name": "Qwen3.7 Plus Thinking", + "display_name": "Qwen3.7 Plus Thinking", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ - "image" + "text" ] }, "limit": { - "context": 480, - "output": 8192 + "context": 983616, + "output": 65536 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "none", + "high" + ] + } + }, + "attachment": true, "open_weights": false, - "release_date": "2024-10-15", - "last_updated": "2024-10-15" + "knowledge": "2025-04", + "release_date": "2026-06-02", + "last_updated": "2026-06-02", + "type": "chat" }, { - "id": "ideogramai/ideogram", - "name": "Ideogram", - "display_name": "Ideogram", + "id": "qwen/qwen3.6-27b:thinking", + "name": "Qwen3.6 27B Thinking", + "display_name": "Qwen3.6 27B Thinking", "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ - "image" + "text" ] }, "limit": { - "context": 150, - "output": 8192 + "context": 262144, + "output": 65536 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } }, "attachment": true, - "open_weights": false, - "release_date": "2024-04-03", - "last_updated": "2024-04-03" + "open_weights": true, + "release_date": "2026-04-22", + "last_updated": "2026-04-22", + "type": "chat" }, { - "id": "ideogramai/ideogram-v2a-turbo", - "name": "Ideogram-v2a-Turbo", - "display_name": "Ideogram-v2a-Turbo", + "id": "qwen/qwen3.8-27b-antislop", + "name": "Qwen 3.8 27B Antislop", + "display_name": "Qwen 3.8 27B Antislop", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ - "image" + "text" ] }, "limit": { - "context": 150, - "output": 8192 + "context": 262144, + "output": 32768 }, - "temperature": false, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, - "open_weights": false, - "release_date": "2025-02-27", - "last_updated": "2025-02-27" + "attachment": true, + "open_weights": true, + "release_date": "2026-10-05", + "last_updated": "2026-10-05", + "type": "chat" }, { - "id": "ideogramai/ideogram-v2", - "name": "Ideogram-v2", - "display_name": "Ideogram-v2", + "id": "qwen/qwen3.8-27b-kiwen", + "name": "Qwen 3.8 27B Kiwen", + "display_name": "Qwen 3.8 27B Kiwen", "modalities": { "input": [ "text", "image" ], "output": [ - "image" + "text" ] }, "limit": { - "context": 150, - "output": 8192 + "context": 262144, + "output": 32768 }, - "temperature": false, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, "attachment": true, - "open_weights": false, - "release_date": "2024-08-21", - "last_updated": "2024-08-21" + "open_weights": true, + "release_date": "2026-10-05", + "last_updated": "2026-10-05", + "type": "chat" }, { - "id": "ideogramai/ideogram-v2a", - "name": "Ideogram-v2a", - "display_name": "Ideogram-v2a", + "id": "inference-net/schematron-v2-turbo", + "name": "Schematron V2 Turbo", + "display_name": "Schematron V2 Turbo", "modalities": { "input": [ "text" ], "output": [ - "image" + "text" ] }, "limit": { - "context": 150, + "context": 128000, "output": 8192 }, - "temperature": false, - "tool_call": true, + "temperature": true, + "tool_call": false, "reasoning": { "supported": false }, "attachment": false, "open_weights": false, - "release_date": "2025-02-27", - "last_updated": "2025-02-27" + "release_date": "2026-04-16", + "last_updated": "2026-04-16", + "type": "chat" }, { - "id": "openai/gpt-4o-mini-search", - "name": "GPT-4o-mini-Search", - "display_name": "GPT-4o-mini-Search", + "id": "inference-net/schematron-v2-small", + "name": "Schematron V2 Small", + "display_name": "Schematron V2 Small", "modalities": { "input": [ "text" @@ -72669,262 +72966,181 @@ }, "limit": { "context": 128000, - "output": 8192 + "output": 4096 }, - "temperature": false, - "tool_call": true, + "temperature": true, + "tool_call": false, "reasoning": { "supported": false }, "attachment": false, "open_weights": false, - "release_date": "2025-03-11", - "last_updated": "2025-03-11", + "release_date": "2026-04-16", + "last_updated": "2026-04-16", "type": "chat" }, { - "id": "openai/gpt-5.1-codex-max", - "name": "GPT-5.1-Codex-Max", - "display_name": "GPT-5.1-Codex-Max", + "id": "baseten/Kimi-K2-Instruct-FP4", + "name": "Kimi K2 0711 Instruct FP4", + "display_name": "Kimi K2 0711 Instruct FP4", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 131072, + "output": 131072 }, - "temperature": false, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": false - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "none", - "effort_options": [ - "none", - "low", - "medium", - "high" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" - } + "supported": false }, - "attachment": true, - "open_weights": false, - "knowledge": "2024-09-30", - "release_date": "2025-11-13", - "last_updated": "2025-11-13", + "attachment": false, + "open_weights": true, + "release_date": "2024-01-01", + "last_updated": "2025-07-11", "type": "chat" }, { - "id": "openai/gpt-5", - "name": "GPT-5", - "display_name": "GPT-5", + "id": "huihui-ai/DeepSeek-R1-Distill-Qwen-32B-abliterated", + "name": "DeepSeek R1 Qwen Abliterated", + "display_name": "DeepSeek R1 Qwen Abliterated", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 16384, + "output": 8192 }, - "temperature": false, - "tool_call": true, + "tool_call": false, "reasoning": { "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", - "effort_options": [ - "minimal", - "low", - "medium", - "high" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" - } - }, - "attachment": true, - "open_weights": false, - "knowledge": "2024-09-30", - "release_date": "2025-08-07", - "last_updated": "2025-08-07", + "attachment": false, + "open_weights": true, + "release_date": "2024-01-01", + "last_updated": "2025-01-20", "type": "chat" }, { - "id": "openai/gpt-4o-mini", - "name": "GPT-4o-mini", - "display_name": "GPT-4o-mini", + "id": "huihui-ai/Llama-3.3-70B-Instruct-abliterated", + "name": "Llama 3.3 70B Instruct abliterated", + "display_name": "Llama 3.3 70B Instruct abliterated", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 124096, - "output": 4096 + "context": 32768, + "output": 16384 }, - "temperature": false, - "tool_call": true, + "tool_call": false, "reasoning": { "supported": false }, - "attachment": true, - "open_weights": false, - "knowledge": "2023-09", - "release_date": "2024-07-18", - "last_updated": "2024-07-18", + "attachment": false, + "open_weights": true, + "release_date": "2025-08-08", + "last_updated": "2025-08-08", "type": "chat" }, { - "id": "openai/gpt-image-1-mini", - "name": "GPT-Image-1-Mini", - "display_name": "GPT-Image-1-Mini", + "id": "huihui-ai/Qwen2.5-32B-Instruct-abliterated", + "name": "Qwen 2.5 32B Abliterated", + "display_name": "Qwen 2.5 32B Abliterated", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ - "image" + "text" ] }, "limit": { - "context": 8192, + "context": 32768, "output": 8192 }, - "temperature": false, - "tool_call": true, + "tool_call": false, "reasoning": { "supported": false }, - "attachment": true, - "open_weights": false, - "release_date": "2025-10-06", - "last_updated": "2025-10-06" + "attachment": false, + "open_weights": true, + "release_date": "2025-01-06", + "last_updated": "2025-01-06", + "type": "chat" }, { - "id": "openai/gpt-5.5", - "name": "GPT-5.5", - "display_name": "GPT-5.5", + "id": "huihui-ai/DeepSeek-R1-Distill-Llama-70B-abliterated", + "name": "DeepSeek R1 Llama 70B Abliterated", + "display_name": "DeepSeek R1 Llama 70B Abliterated", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ - "text", - "image" + "text" ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 16384, + "output": 8192 }, - "temperature": false, - "tool_call": true, + "tool_call": false, "reasoning": { "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", - "effort_options": [ - "low", - "medium", - "high", - "xhigh" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" - } - }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-12-01", - "release_date": "2026-04-23", - "last_updated": "2026-04-23", + "attachment": false, + "open_weights": true, + "release_date": "2024-01-01", + "last_updated": "2025-01-20", "type": "chat" }, { - "id": "openai/gpt-4.1-nano", - "name": "GPT-4.1-nano", - "display_name": "GPT-4.1-nano", + "id": "failspy/Meta-Llama-3-70B-Instruct-abliterated-v3.5", + "name": "Llama 3 70B abliterated", + "display_name": "Llama 3 70B abliterated", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1047576, - "output": 32768 + "context": 8192, + "output": 8192 }, - "temperature": false, - "tool_call": true, + "tool_call": false, "reasoning": { "supported": false }, - "attachment": true, - "open_weights": false, - "knowledge": "2024-04", - "release_date": "2025-04-14", - "last_updated": "2025-04-14", + "attachment": false, + "open_weights": true, + "release_date": "2025-07-26", + "last_updated": "2025-07-26", "type": "chat" }, { - "id": "openai/o1", - "name": "o1", - "display_name": "o1", + "id": "lightonai/LightOnOCR-2-1B", + "name": "LightOnOCR 2", + "display_name": "LightOnOCR 2", "modalities": { "input": [ "text", @@ -72935,147 +73151,113 @@ ] }, "limit": { - "context": 200000, - "output": 100000 + "context": 32768, + "output": 8192 }, - "temperature": false, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", - "effort_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" - } + "supported": false }, "attachment": true, - "open_weights": false, - "knowledge": "2023-09", - "release_date": "2024-12-05", - "last_updated": "2024-12-05", + "open_weights": true, + "release_date": "2026-09-10", + "last_updated": "2026-09-10", "type": "chat" }, { - "id": "openai/gpt-image-2", - "name": "GPT-Image-2", - "display_name": "GPT-Image-2", + "id": "MarinaraSpaghetti/NemoMix-Unleashed-12B", + "name": "NemoMix 12B Unleashed", + "display_name": "NemoMix 12B Unleashed", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ - "image" + "text" ] }, "limit": { - "context": 8192, + "context": 32768, "output": 8192 }, - "temperature": false, "tool_call": false, "reasoning": { "supported": false }, - "attachment": true, - "open_weights": false, - "release_date": "2026-04-21", - "last_updated": "2026-04-21" + "attachment": false, + "open_weights": true, + "release_date": "2024-01-01", + "last_updated": "2024-07-01", + "type": "chat" }, { - "id": "openai/gpt-5.4", - "name": "GPT-5.4", - "display_name": "GPT-5.4", + "id": "GalrionSoftworks/MN-LooseCannon-12B-v1", + "name": "MN-LooseCannon-12B-v1", + "display_name": "MN-LooseCannon-12B-v1", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ - "text", - "image" + "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 16384, + "output": 8192 }, - "temperature": false, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": false - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "none", - "effort_options": [ - "none", - "low", - "medium", - "high", - "xhigh" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" - } + "supported": false }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-03-05", - "last_updated": "2026-03-05", + "attachment": false, + "open_weights": true, + "release_date": "2024-01-01", + "last_updated": "2024-07-01", "type": "chat" }, { - "id": "openai/dall-e-3", - "name": "DALL-E-3", - "display_name": "DALL-E-3", + "id": "deepseek/deepseek-v4.1-flash-0910-uncensored", + "name": "DeepSeek V4.1 Flash 0910 Uncensored", + "display_name": "DeepSeek V4.1 Flash 0910 Uncensored", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ - "image" + "text" ] }, "limit": { - "context": 800, - "output": 8192 + "context": 524288, + "output": 32768 }, - "temperature": false, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, - "open_weights": false, - "release_date": "2023-11-06", - "last_updated": "2023-11-06" + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "none", + "low", + "high", + "max" + ] + } + }, + "attachment": true, + "open_weights": true, + "release_date": "2026-10-27", + "last_updated": "2026-10-27", + "type": "chat" }, { - "id": "openai/gpt-3.5-turbo-instruct", - "name": "GPT-3.5-Turbo-Instruct", - "display_name": "GPT-3.5-Turbo-Instruct", + "id": "deepseek/deepseek-v4.1-flash-1017:thinking", + "name": "DeepSeek V4.1 Flash 1017 Thinking", + "display_name": "DeepSeek V4.1 Flash 1017 Thinking", "modalities": { "input": [ "text", @@ -73086,82 +73268,112 @@ ] }, "limit": { - "context": 3500, - "output": 1024 + "context": 1048576, + "output": 393216 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "none", + "low", + "high", + "max" + ] + } }, "attachment": true, - "open_weights": false, - "release_date": "2023-09-20", - "last_updated": "2023-09-20", + "open_weights": true, + "knowledge": "2025-05", + "release_date": "2026-10-17", + "last_updated": "2026-10-17", "type": "chat" }, { - "id": "openai/gpt-5.3-instant", - "name": "GPT-5.3-Instant", - "display_name": "GPT-5.3-Instant", + "id": "deepseek/deepseek-latest", + "name": "DeepSeek Latest", + "display_name": "DeepSeek Latest", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 16384 + "context": 1048576, + "output": 384000 }, - "temperature": false, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-03-03", - "last_updated": "2026-03-03", + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "none", + "low", + "high", + "max" + ] + } + }, + "attachment": false, + "open_weights": true, + "release_date": "2026-05-03", + "last_updated": "2026-05-03", "type": "chat" }, { - "id": "openai/gpt-5-chat", - "name": "GPT-5-Chat", - "display_name": "GPT-5-Chat", + "id": "deepseek/deepseek-v4-flash-0423", + "name": "DeepSeek V4 Flash 0423", + "display_name": "DeepSeek V4 Flash 0423", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 16384 + "context": 1048576, + "output": 384000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": true, - "open_weights": false, - "knowledge": "2024-09-30", - "release_date": "2025-08-07", - "last_updated": "2025-08-07", + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "none", + "low", + "high", + "max" + ] + } + }, + "attachment": false, + "open_weights": true, + "knowledge": "2025-05", + "release_date": "2026-04-23", + "last_updated": "2026-04-23", "type": "chat" }, { - "id": "openai/gpt-4o-aug", - "name": "GPT-4o-Aug", - "display_name": "GPT-4o-Aug", + "id": "deepseek/deepseek-v4.1-flash-1017-uncensored", + "name": "DeepSeek V4.1 Flash 1017 Uncensored", + "display_name": "DeepSeek V4.1 Flash 1017 Uncensored", "modalities": { "input": [ "text", @@ -73172,25 +73384,34 @@ ] }, "limit": { - "context": 128000, - "output": 8192 + "context": 1048576, + "output": 393216 }, - "temperature": false, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "none", + "low", + "high", + "max" + ] + } }, "attachment": true, - "open_weights": false, - "knowledge": "2023-09", - "release_date": "2024-08-06", - "last_updated": "2024-08-06", + "open_weights": true, + "release_date": "2026-10-27", + "last_updated": "2026-10-27", "type": "chat" }, { - "id": "openai/chatgpt-4o-latest", - "name": "ChatGPT-4o-Latest", - "display_name": "ChatGPT-4o-Latest", + "id": "deepseek/deepseek-v4.1-flash-1017-uncensored:thinking", + "name": "DeepSeek V4.1 Flash 1017 Uncensored Thinking", + "display_name": "DeepSeek V4.1 Flash 1017 Uncensored Thinking", "modalities": { "input": [ "text", @@ -73201,24 +73422,34 @@ ] }, "limit": { - "context": 128000, - "output": 8192 + "context": 1048576, + "output": 393216 }, - "temperature": false, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "none", + "low", + "high", + "max" + ] + } }, "attachment": true, - "open_weights": false, - "release_date": "2024-08-14", - "last_updated": "2024-08-14", + "open_weights": true, + "release_date": "2026-10-27", + "last_updated": "2026-10-27", "type": "chat" }, { - "id": "openai/gpt-4-turbo", - "name": "GPT-4-Turbo", - "display_name": "GPT-4-Turbo", + "id": "deepseek/deepseek-v4.1-flash-1017", + "name": "DeepSeek V4.1 Flash 1017", + "display_name": "DeepSeek V4.1 Flash 1017", "modalities": { "input": [ "text", @@ -73229,25 +73460,36 @@ ] }, "limit": { - "context": 128000, - "output": 4096 + "context": 1048576, + "output": 393216 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "none", + "low", + "high", + "max" + ] + } }, "attachment": true, - "open_weights": false, - "knowledge": "2023-12", - "release_date": "2023-11-06", - "last_updated": "2024-04-09", + "open_weights": true, + "knowledge": "2025-05", + "release_date": "2026-10-17", + "last_updated": "2026-10-17", "type": "chat" }, { - "id": "openai/gpt-5.3-codex-spark", - "name": "GPT-5.3-Codex-Spark", - "display_name": "GPT-5.3-Codex-Spark", + "id": "deepseek/deepseek-v4-flash-0423:thinking", + "name": "DeepSeek V4 Flash 0423 (Thinking)", + "display_name": "DeepSeek V4 Flash 0423 (Thinking)", "modalities": { "input": [ "text" @@ -73257,10 +73499,10 @@ ] }, "limit": { - "context": 128000, - "output": 16384 + "context": 1048576, + "output": 384000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -73268,36 +73510,25 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", "effort_options": [ + "none", "low", - "medium", "high", - "xhigh" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + "max" + ] } }, "attachment": false, - "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-02-05", - "last_updated": "2026-02-05", + "open_weights": true, + "knowledge": "2025-05", + "release_date": "2026-04-23", + "last_updated": "2026-04-23", "type": "chat" }, { - "id": "openai/gpt-4o", - "name": "GPT-4o", - "display_name": "GPT-4o", + "id": "deepseek/deepseek-v4.1-flash:thinking", + "name": "DeepSeek V4.1 Flash Thinking", + "display_name": "DeepSeek V4.1 Flash Thinking", "modalities": { "input": [ "text", @@ -73308,39 +73539,49 @@ ] }, "limit": { - "context": 128000, - "output": 8192 + "context": 1000000, + "output": 384000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "none", + "low", + "high", + "max" + ] + } }, "attachment": true, - "open_weights": false, - "knowledge": "2023-09", - "release_date": "2024-05-13", - "last_updated": "2024-08-06", + "open_weights": true, + "knowledge": "2025-05", + "release_date": "2026-09-10", + "last_updated": "2026-09-10", "type": "chat" }, { - "id": "openai/gpt-5-pro", - "name": "GPT-5-Pro", - "display_name": "GPT-5-Pro", + "id": "deepseek/deepseek-v4-pro:thinking", + "name": "DeepSeek V4 Pro (Thinking)", + "display_name": "DeepSeek V4 Pro (Thinking)", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 1048576, + "output": 384000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -73348,71 +73589,67 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "fixed", - "effort": "high", - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + "effort_options": [ + "none", + "high", + "max" + ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2024-09-30", - "release_date": "2025-10-06", - "last_updated": "2025-10-06", + "attachment": false, + "open_weights": true, + "knowledge": "2025-05", + "release_date": "2026-04-24", + "last_updated": "2026-04-24", "type": "chat" }, { - "id": "openai/gpt-image-1", - "name": "GPT-Image-1", - "display_name": "GPT-Image-1", + "id": "deepseek/deepseek-v3.2:thinking", + "name": "DeepSeek V3.2 Thinking", + "display_name": "DeepSeek V3.2 Thinking", "modalities": { "input": [ "text", - "image" + "pdf" ], "output": [ - "image" + "text" ] }, "limit": { - "context": 128000, - "output": 8192 + "context": 163000, + "output": 65536 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, "attachment": true, - "open_weights": false, - "release_date": "2025-04-24", - "last_updated": "2025-04-24" + "open_weights": true, + "knowledge": "2024-07", + "release_date": "2025-12-01", + "last_updated": "2025-12-01", + "type": "chat" }, { - "id": "openai/o3", - "name": "o3", - "display_name": "o3", + "id": "deepseek/deepseek-v4-flash:thinking", + "name": "DeepSeek V4 Flash (Thinking)", + "display_name": "DeepSeek V4 Flash (Thinking)", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 100000 + "context": 1048576, + "output": 384000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -73420,205 +73657,214 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", "effort_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + "none", + "high", + "max" + ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2024-05", - "release_date": "2025-04-16", - "last_updated": "2025-04-16", + "attachment": false, + "open_weights": true, + "knowledge": "2025-05", + "release_date": "2026-04-24", + "last_updated": "2026-04-24", "type": "chat" }, { - "id": "openai/o4-mini", - "name": "o4-mini", - "display_name": "o4-mini", + "id": "deepseek/deepseek-v3.2", + "name": "DeepSeek V3.2", + "display_name": "DeepSeek V3.2", "modalities": { "input": [ "text", - "image" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 100000 + "context": 163000, + "output": 65536 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true + "supported": true }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", - "effort_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + "supported": true } }, "attachment": true, - "open_weights": false, - "knowledge": "2024-05", - "release_date": "2025-04-16", - "last_updated": "2025-04-16", + "open_weights": true, + "knowledge": "2024-07", + "release_date": "2025-12-01", + "last_updated": "2025-12-01", "type": "chat" }, { - "id": "openai/gpt-4o-search", - "name": "GPT-4o-Search", - "display_name": "GPT-4o-Search", + "id": "deepseek/deepseek-v4-flash-latest", + "name": "DeepSeek V4 Flash Latest", + "display_name": "DeepSeek V4 Flash Latest", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 8192 + "context": 1048576, + "output": 393216 }, - "temperature": false, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, - "open_weights": false, - "release_date": "2025-03-11", - "last_updated": "2025-03-11", + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "none", + "low", + "high", + "max" + ] + } + }, + "attachment": true, + "open_weights": true, + "release_date": "2026-08-02", + "last_updated": "2026-08-02", "type": "chat" }, { - "id": "openai/gpt-5.1-codex", - "name": "GPT-5.1-Codex", - "display_name": "GPT-5.1-Codex", + "id": "deepseek/deepseek-v4-flash-0731:thinking", + "name": "DeepSeek V4 Flash 0731 (Thinking)", + "display_name": "DeepSeek V4 Flash 0731 (Thinking)", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 1048576, + "output": 131072 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "none", "effort_options": [ "none", "low", - "medium", - "high" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + "high", + "max" + ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2024-09-30", - "release_date": "2025-11-13", - "last_updated": "2025-11-13", + "attachment": false, + "open_weights": true, + "knowledge": "2025-05", + "release_date": "2026-07-31", + "last_updated": "2026-07-31", "type": "chat" }, { - "id": "openai/sora-2-pro", - "name": "Sora-2-Pro", - "display_name": "Sora-2-Pro", + "id": "deepseek/deepseek-v4.1-flash", + "name": "DeepSeek V4.1 Flash", + "display_name": "DeepSeek V4.1 Flash", "modalities": { "input": [ "text", "image" ], "output": [ - "video" + "text" ] }, "limit": { - "context": 8192, - "output": 8192 + "context": 1000000, + "output": 384000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "effort_options": [ + "none", + "low", + "high", + "max" + ] + } }, "attachment": true, - "open_weights": false, - "release_date": "2025-10-06", - "last_updated": "2025-10-06" + "open_weights": true, + "knowledge": "2025-05", + "release_date": "2026-09-10", + "last_updated": "2026-09-10", + "type": "chat" }, { - "id": "openai/sora-2", - "name": "Sora-2", - "display_name": "Sora-2", + "id": "deepseek/deepseek-v4.1-flash-uncensored", + "name": "DeepSeek V4.1 Flash Uncensored", + "display_name": "DeepSeek V4.1 Flash Uncensored", "modalities": { "input": [ "text", "image" ], "output": [ - "video" + "text" ] }, "limit": { - "context": 8192, - "output": 8192 + "context": 1048576, + "output": 393216 }, - "temperature": false, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "none", + "low", + "medium", + "high" + ] + } }, "attachment": true, - "open_weights": false, - "release_date": "2025-10-06", - "last_updated": "2025-10-06" + "open_weights": true, + "release_date": "2026-10-01", + "last_updated": "2026-10-01", + "type": "chat" }, { - "id": "openai/gpt-5-mini", - "name": "GPT-5-mini", - "display_name": "GPT-5-mini", + "id": "deepseek/deepseek-v4-flash-vision-exp", + "name": "DeepSeek V4 Flash Vision Exp", + "display_name": "DeepSeek V4 Flash Vision Exp", "modalities": { "input": [ "text", @@ -73629,10 +73875,10 @@ ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 1048576, + "output": 384000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -73643,33 +73889,33 @@ "supported": true, "default_enabled": true, "mode": "effort", - "effort": "medium", + "effort": "high", "effort_options": [ - "minimal", "low", - "medium", - "high" + "high", + "max" ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ], - "visibility": "hidden" + "notes": [ + "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." + ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2024-05-30", - "release_date": "2025-08-07", - "last_updated": "2025-08-07", + "open_weights": true, + "release_date": "2026-08-21", + "last_updated": "2026-09-01", "type": "chat" }, { - "id": "openai/gpt-5-codex", - "name": "GPT-5-Codex", - "display_name": "GPT-5-Codex", + "id": "deepseek/deepseek-v4.1-flash-uncensored:thinking", + "name": "DeepSeek V4.1 Flash Uncensored Thinking", + "display_name": "DeepSeek V4.1 Flash Uncensored Thinking", "modalities": { "input": [ "text", @@ -73680,10 +73926,9 @@ ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 1048576, + "output": 393216 }, - "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -73691,51 +73936,37 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", "effort_options": [ - "minimal", - "low", - "medium", - "high" - ], - "verbosity": "medium", - "verbosity_options": [ + "none", "low", "medium", "high" - ], - "visibility": "hidden" + ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2024-09-30", - "release_date": "2025-09-15", - "last_updated": "2025-09-15", + "open_weights": true, + "release_date": "2026-10-01", + "last_updated": "2026-10-01", "type": "chat" }, { - "id": "openai/gpt-5.4-pro", - "name": "GPT-5.4-Pro", - "display_name": "GPT-5.4-Pro", + "id": "deepseek/deepseek-v4.1-flash-0910-uncensored:thinking", + "name": "DeepSeek V4.1 Flash 0910 Uncensored Thinking", + "display_name": "DeepSeek V4.1 Flash 0910 Uncensored Thinking", "modalities": { "input": [ "text", "image" ], "output": [ - "text", - "image" + "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 524288, + "output": 32768 }, - "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -73743,35 +73974,24 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "high", "effort_options": [ - "medium", - "high", - "xhigh" - ], - "verbosity": "medium", - "verbosity_options": [ + "none", "low", - "medium", - "high" - ], - "visibility": "hidden" + "high", + "max" + ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-03-05", - "last_updated": "2026-03-05", + "open_weights": true, + "release_date": "2026-10-27", + "last_updated": "2026-10-27", "type": "chat" }, { - "id": "openai/o4-mini-deep-research", - "name": "o4-mini-deep-research", - "display_name": "o4-mini-deep-research", + "id": "deepseek/deepseek-v4-pro", + "name": "DeepSeek V4 Pro", + "display_name": "DeepSeek V4 Pro", "modalities": { "input": [ "text" @@ -73781,10 +74001,10 @@ ] }, "limit": { - "context": 200000, - "output": 100000 + "context": 1048576, + "output": 384000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -73795,26 +74015,34 @@ "supported": true, "default_enabled": true, "mode": "effort", - "effort": "medium", + "effort": "high", "effort_options": [ "low", - "medium", - "high" + "high", + "max" ], - "visibility": "hidden" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." + ] } }, "attachment": false, - "open_weights": false, - "knowledge": "2024-05", - "release_date": "2025-06-26", - "last_updated": "2025-06-26", + "open_weights": true, + "knowledge": "2025-05", + "release_date": "2026-04-24", + "last_updated": "2026-04-24", "type": "chat" }, { - "id": "openai/gpt-5.1-codex-mini", - "name": "GPT-5.1-Codex-Mini", - "display_name": "GPT-5.1-Codex-Mini", + "id": "deepseek/deepseek-v4-flash", + "name": "DeepSeek V4 Flash", + "display_name": "DeepSeek V4 Flash", "modalities": { "input": [ "text" @@ -73824,61 +74052,61 @@ ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 1048576, + "output": 384000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, + "default_enabled": true, "mode": "effort", - "effort": "none", + "effort": "high", "effort_options": [ - "none", "low", - "medium", - "high" + "high", + "max" ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ], - "visibility": "hidden" + "notes": [ + "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." + ] } }, "attachment": false, - "open_weights": false, - "knowledge": "2024-09-30", - "release_date": "2025-11-13", - "last_updated": "2025-11-13", + "open_weights": true, + "knowledge": "2025-05", + "release_date": "2026-04-24", + "last_updated": "2026-04-24", "type": "chat" }, { - "id": "openai/o3-mini", - "name": "o3-mini", - "display_name": "o3-mini", + "id": "deepseek/deepseek-v4-pro-0813", + "name": "DeepSeek V4 Pro 0813", + "display_name": "DeepSeek V4 Pro 0813", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 100000 + "context": 1048576, + "output": 384000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -73887,292 +74115,226 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", "effort_options": [ + "none", "low", - "medium", - "high" - ], - "visibility": "hidden" + "high", + "max" + ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2024-05", - "release_date": "2024-12-20", - "last_updated": "2025-01-29", + "attachment": false, + "open_weights": true, + "release_date": "2026-08-13", + "last_updated": "2026-08-22", "type": "chat" }, { - "id": "openai/gpt-5.1", - "name": "GPT-5.1", - "display_name": "GPT-5.1", + "id": "deepseek/deepseek-v4-flash-0731", + "name": "DeepSeek V4 Flash 0731", + "display_name": "DeepSeek V4 Flash 0731", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 1048576, + "output": 131072 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "none", "effort_options": [ "none", "low", - "medium", - "high" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + "high", + "max" + ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2024-09-30", - "release_date": "2025-11-13", - "last_updated": "2025-11-13", + "attachment": false, + "open_weights": true, + "knowledge": "2025-05", + "release_date": "2026-07-31", + "last_updated": "2026-07-31", "type": "chat" }, { - "id": "openai/gpt-image-1.5", - "name": "gpt-image-1.5", - "display_name": "gpt-image-1.5", + "id": "deepseek/deepseek-v4-pro-0813:thinking", + "name": "DeepSeek V4 Pro 0813 Thinking", + "display_name": "DeepSeek V4 Pro 0813 Thinking", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ - "image" + "text" ] }, "limit": { - "context": 128000, - "output": 8192 + "context": 1048576, + "output": 384000 }, - "temperature": false, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": true, - "open_weights": false, - "release_date": "2025-11-25", - "last_updated": "2025-11-25" + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "none", + "low", + "high", + "max" + ] + } + }, + "attachment": false, + "open_weights": true, + "release_date": "2026-08-13", + "last_updated": "2026-08-22", + "type": "chat" }, { - "id": "openai/gpt-5.4-mini", - "name": "GPT-5.4-Mini", - "display_name": "GPT-5.4-Mini", + "id": "undi95/remm-slerp-l2-13b", + "name": "ReMM SLERP 13B", + "display_name": "ReMM SLERP 13B", "modalities": { "input": [ "text", - "image" + "pdf" ], "output": [ - "text", - "image" + "text" ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 6144, + "output": 4096 }, - "temperature": false, - "tool_call": true, + "temperature": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": false - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "none", - "effort_options": [ - "none", - "low", - "medium", - "high", - "xhigh" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" - } + "supported": false }, "attachment": true, - "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-03-17", - "last_updated": "2026-03-17", + "open_weights": true, + "knowledge": "2023-06-30", + "release_date": "2023-07-22", + "last_updated": "2023-07-22", "type": "chat" }, { - "id": "openai/gpt-5.3-codex", - "name": "GPT-5.3-Codex", - "display_name": "GPT-5.3-Codex", + "id": "elorian/glimpse", + "name": "Elorian Glimpse", + "display_name": "Elorian Glimpse", "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 262144, + "output": 256000 }, - "temperature": false, "tool_call": true, "reasoning": { "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", - "effort_options": [ - "low", - "medium", - "high", - "xhigh" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" - } - }, "attachment": true, "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-02-05", - "last_updated": "2026-02-05", + "release_date": "2026-10-17", + "last_updated": "2026-10-17", "type": "chat" }, { - "id": "openai/gpt-4.1-mini", - "name": "GPT-4.1-mini", - "display_name": "GPT-4.1-mini", + "id": "MiniMaxAI/MiniMax-M1-80k", + "name": "MiniMax M1 80K", + "display_name": "MiniMax M1 80K", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1047576, - "output": 32768 + "context": 1000000, + "output": 131072 }, - "temperature": false, - "tool_call": true, + "temperature": true, + "tool_call": false, "reasoning": { "supported": false }, - "attachment": true, - "open_weights": false, - "knowledge": "2024-04", - "release_date": "2025-04-14", - "last_updated": "2025-04-14", + "attachment": false, + "open_weights": true, + "release_date": "2025-06-17", + "last_updated": "2025-06-17", "type": "chat" }, { - "id": "openai/gpt-5.4-nano", - "name": "GPT-5.4-Nano", - "display_name": "GPT-5.4-Nano", + "id": "x-ai/grok-build-0.1", + "name": "Grok Build 0.1", + "display_name": "Grok Build 0.1", "modalities": { "input": [ "text", "image" ], "output": [ - "text", - "image" + "text" ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 256000, + "output": 230400 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "none", "effort_options": [ - "none", - "low", - "medium", - "high", - "xhigh" - ], - "verbosity": "medium", - "verbosity_options": [ "low", "medium", "high" - ], - "visibility": "hidden" + ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-03-17", - "last_updated": "2026-03-17", + "release_date": "2026-04-16", + "last_updated": "2026-04-16", "type": "chat" }, { - "id": "openai/o3-pro", - "name": "o3-pro", - "display_name": "o3-pro", + "id": "x-ai/grok-latest", + "name": "Grok Latest", + "display_name": "Grok Latest", "modalities": { "input": [ "text", @@ -74183,10 +74345,9 @@ ] }, "limit": { - "context": 200000, - "output": 100000 + "context": 500000, + "output": 450000 }, - "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -74194,44 +74355,38 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", "effort_options": [ "low", "medium", - "high" - ], - "visibility": "hidden" + "high", + "xhigh" + ] } }, "attachment": true, "open_weights": false, - "knowledge": "2024-05", - "release_date": "2025-06-10", - "last_updated": "2025-06-10", + "release_date": "2026-05-03", + "last_updated": "2026-05-03", "type": "chat" }, { - "id": "openai/gpt-5.5-pro", - "name": "GPT-5.5-Pro", - "display_name": "GPT-5.5-Pro", + "id": "x-ai/grok-4.7", + "name": "Grok 4.7", + "display_name": "Grok 4.7", "modalities": { "input": [ "text", "image" ], "output": [ - "text", - "image" + "text" ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 500000, + "output": 500000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -74239,8 +74394,8 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, "effort_options": [ + "low", "medium", "high", "xhigh" @@ -74249,15 +74404,15 @@ }, "attachment": true, "open_weights": false, - "knowledge": "2025-12-01", - "release_date": "2026-04-23", - "last_updated": "2026-04-23", + "knowledge": "2026-05", + "release_date": "2026-09-21", + "last_updated": "2026-09-21", "type": "chat" }, { - "id": "openai/gpt-5.2-pro", - "name": "GPT-5.2-Pro", - "display_name": "GPT-5.2-Pro", + "id": "x-ai/grok-4.5", + "name": "Grok 4.5", + "display_name": "Grok 4.5", "modalities": { "input": [ "text", @@ -74268,10 +74423,10 @@ ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 500000, + "output": 450000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -74280,34 +74435,23 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "high", "effort_options": [ - "medium", - "high", - "xhigh" - ], - "verbosity": "medium", - "verbosity_options": [ "low", "medium", "high" - ], - "visibility": "hidden" + ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2025-12-11", - "last_updated": "2025-12-11", + "release_date": "2026-07-08", + "last_updated": "2026-07-08", "type": "chat" }, { - "id": "openai/gpt-4.1", - "name": "GPT-4.1", - "display_name": "GPT-4.1", + "id": "x-ai/grok-4.3", + "name": "Grok 4.3", + "display_name": "Grok 4.3", "modalities": { "input": [ "text", @@ -74318,25 +74462,35 @@ ] }, "limit": { - "context": 1047576, - "output": 32768 + "context": 1000000, + "output": 900000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "effort_options": [ + "low", + "medium", + "high" + ] + } }, "attachment": true, "open_weights": false, - "knowledge": "2024-04", - "release_date": "2025-04-14", - "last_updated": "2025-04-14", + "release_date": "2026-04-17", + "last_updated": "2026-04-17", "type": "chat" }, { - "id": "openai/gpt-3.5-turbo", - "name": "GPT-3.5-Turbo", - "display_name": "GPT-3.5-Turbo", + "id": "x-ai/grok-4.20", + "name": "Grok 4.20", + "display_name": "Grok 4.20", "modalities": { "input": [ "text", @@ -74347,25 +74501,33 @@ ] }, "limit": { - "context": 16384, - "output": 2048 + "context": 2000000, + "output": 131072 }, - "temperature": false, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "low", + "medium", + "high" + ] + } }, "attachment": true, "open_weights": false, - "knowledge": "2021-09-01", - "release_date": "2023-03-01", - "last_updated": "2023-11-06", + "release_date": "2026-03-31", + "last_updated": "2026-03-31", "type": "chat" }, { - "id": "openai/gpt-5.2-codex", - "name": "GPT-5.2-Codex", - "display_name": "GPT-5.2-Codex", + "id": "x-ai/grok-4.6", + "name": "Grok 4.6", + "display_name": "Grok 4.6", "modalities": { "input": [ "text", @@ -74376,10 +74538,10 @@ ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 500000, + "output": 450000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -74388,35 +74550,25 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", "effort_options": [ "low", "medium", "high", "xhigh" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2025-12-11", - "last_updated": "2025-12-11", + "knowledge": "2026-02-01", + "release_date": "2026-08-12", + "last_updated": "2026-08-12", "type": "chat" }, { - "id": "openai/o1-pro", - "name": "o1-pro", - "display_name": "o1-pro", + "id": "x-ai/grok-4.20-multi-agent", + "name": "Grok 4.20 Multi-Agent", + "display_name": "Grok 4.20 Multi-Agent", "modalities": { "input": [ "text", @@ -74427,10 +74579,9 @@ ] }, "limit": { - "context": 200000, - "output": 100000 + "context": 2000000, + "output": 131072 }, - "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -74438,308 +74589,181 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", "effort_options": [ "low", "medium", - "high" - ], - "visibility": "hidden" + "high", + "xhigh" + ] } }, "attachment": true, "open_weights": false, - "knowledge": "2023-09", - "release_date": "2025-03-19", - "last_updated": "2025-03-19", + "release_date": "2026-03-31", + "last_updated": "2026-03-31", "type": "chat" }, { - "id": "openai/gpt-3.5-turbo-raw", - "name": "GPT-3.5-Turbo-Raw", - "display_name": "GPT-3.5-Turbo-Raw", + "id": "soob3123/amoral-gemma3-27B-v2", + "name": "Amoral Gemma3 27B v2", + "display_name": "Amoral Gemma3 27B v2", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 4524, - "output": 2048 + "context": 32768, + "output": 8192 }, - "temperature": false, - "tool_call": true, + "tool_call": false, "reasoning": { "supported": false }, - "attachment": true, - "open_weights": false, - "knowledge": "2021-09-01", - "release_date": "2023-03-01", - "last_updated": "2023-11-06", + "attachment": false, + "open_weights": true, + "release_date": "2025-05-23", + "last_updated": "2025-05-23", "type": "chat" }, { - "id": "openai/gpt-5.2-instant", - "name": "GPT-5.2-Instant", - "display_name": "GPT-5.2-Instant", + "id": "soob3123/Veiled-Calla-12B", + "name": "Veiled Calla 12B", + "display_name": "Veiled Calla 12B", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 16384 + "context": 32768, + "output": 8192 }, - "temperature": false, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": false - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "none", - "effort_options": [ - "none", - "low", - "medium", - "high", - "xhigh" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" - } + "supported": false }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2025-12-11", - "last_updated": "2025-12-11", + "attachment": false, + "open_weights": true, + "release_date": "2025-04-13", + "last_updated": "2025-04-13", "type": "chat" }, { - "id": "openai/o3-mini-high", - "name": "o3-mini-high", - "display_name": "o3-mini-high", + "id": "soob3123/GrayLine-Qwen3-8B", + "name": "Grayline Qwen3 8B", + "display_name": "Grayline Qwen3 8B", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 100000 + "context": 32768, + "output": 32768 }, - "temperature": false, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", - "effort_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" - } + "supported": false }, - "attachment": true, - "open_weights": false, - "knowledge": "2024-05", - "release_date": "2024-12-20", - "last_updated": "2025-01-29", + "attachment": false, + "open_weights": true, + "release_date": "2025-09-25", + "last_updated": "2025-09-25", "type": "chat" }, { - "id": "openai/gpt-5.2", - "name": "GPT-5.2", - "display_name": "GPT-5.2", + "id": "aisingapore/gemma-sea-lion-v4-27b-it", + "name": "Gemma SEA-LION v4 27B", + "display_name": "Gemma SEA-LION v4 27B", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 400000, + "context": 131072, "output": 128000 }, - "temperature": false, - "tool_call": true, + "temperature": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": false - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "none", - "effort_options": [ - "none", - "low", - "medium", - "high", - "xhigh" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" - } + "supported": false }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2025-12-11", - "last_updated": "2025-12-11", + "attachment": false, + "open_weights": true, + "release_date": "2025-09-23", + "last_updated": "2025-09-23", "type": "chat" }, { - "id": "openai/gpt-5.1-instant", - "name": "GPT-5.1-Instant", - "display_name": "GPT-5.1-Instant", + "id": "aisingapore/qwen-sea-lion-v4-32b-it", + "name": "Qwen SEA-LION v4 32B", + "display_name": "Qwen SEA-LION v4 32B", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 16384 + "context": 131072, + "output": 131072 }, - "temperature": false, "tool_call": true, "reasoning": { - "supported": true, - "default": false - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "none", - "effort_options": [ - "none", - "low", - "medium", - "high" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" - } + "supported": false }, - "attachment": true, - "open_weights": false, - "knowledge": "2024-09-30", - "release_date": "2025-11-13", - "last_updated": "2025-11-13", + "attachment": false, + "open_weights": true, + "release_date": "2026-10-15", + "last_updated": "2026-10-15", "type": "chat" }, { - "id": "openai/gpt-5-nano", - "name": "GPT-5-nano", - "display_name": "GPT-5-nano", + "id": "nothingiisreal/L3.1-70B-Celeste-V0.1-BF16", + "name": "Llama 3.1 70B Celeste v0.1", + "display_name": "Llama 3.1 70B Celeste v0.1", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 32768, + "output": 16384 }, - "temperature": false, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", - "effort_options": [ - "minimal", - "low", - "medium", - "high" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" - } + "supported": false }, - "attachment": true, - "open_weights": false, - "knowledge": "2024-05-30", - "release_date": "2025-08-07", - "last_updated": "2025-08-07", + "attachment": false, + "open_weights": true, + "release_date": "2024-01-01", + "last_updated": "2024-07-23", "type": "chat" }, { - "id": "openai/o3-deep-research", - "name": "o3-deep-research", - "display_name": "o3-deep-research", + "id": "upstage/solar-pro4:thinking", + "name": "Solar Pro 4 Thinking", + "display_name": "Solar Pro 4 Thinking", "modalities": { "input": [ "text" @@ -74749,10 +74773,10 @@ ] }, "limit": { - "context": 200000, - "output": 100000 + "context": 524288, + "output": 131072 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -74760,108 +74784,120 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", "effort_options": [ + "none", "low", "medium", "high" - ], - "visibility": "hidden" + ] } }, "attachment": false, "open_weights": false, - "knowledge": "2024-05", - "release_date": "2025-06-26", - "last_updated": "2025-06-26", + "knowledge": "2026-02", + "release_date": "2026-08-06", + "last_updated": "2026-08-06", "type": "chat" }, { - "id": "elevenlabs/elevenlabs-v3", - "name": "ElevenLabs-v3", - "display_name": "ElevenLabs-v3", + "id": "upstage/solar-pro-3", + "name": "Solar Pro 3", + "display_name": "Solar Pro 3", "modalities": { "input": [ "text" ], "output": [ - "audio" + "text" ] }, "limit": { - "context": 128000, - "output": 8192 + "context": 131072, + "output": 117964 }, - "temperature": false, - "tool_call": true, + "temperature": true, + "tool_call": false, "reasoning": { "supported": false }, "attachment": false, "open_weights": false, - "release_date": "2025-08-20", - "last_updated": "2025-08-20" + "knowledge": "2025-07", + "release_date": "2026-01-26", + "last_updated": "2026-03-23", + "type": "chat" }, { - "id": "elevenlabs/elevenlabs-v2.5-turbo", - "name": "ElevenLabs-v2.5-Turbo", - "display_name": "ElevenLabs-v2.5-Turbo", + "id": "upstage/solar-mini4:thinking", + "name": "Solar Mini 4 Thinking", + "display_name": "Solar Mini 4 Thinking", "modalities": { "input": [ "text" ], "output": [ - "audio" + "text" ] }, "limit": { - "context": 128000, - "output": 8192 + "context": 524288, + "output": 131072 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "none", + "low", + "medium", + "high" + ] + } }, "attachment": false, "open_weights": false, - "release_date": "2024-07-19", - "last_updated": "2024-07-19" + "knowledge": "2026-02", + "release_date": "2026-09-22", + "last_updated": "2026-09-22", + "type": "chat" }, { - "id": "elevenlabs/elevenlabs-music", - "name": "ElevenLabs-Music", - "display_name": "ElevenLabs-Music", + "id": "upstage/solar-pro4", + "name": "Solar Pro 4", + "display_name": "Solar Pro 4", "modalities": { "input": [ "text" ], "output": [ - "audio" + "text" ] }, "limit": { - "context": 2000, - "output": 8192 + "context": 524288, + "output": 131072 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": false }, "attachment": false, "open_weights": false, - "release_date": "2025-08-29", - "last_updated": "2025-08-29", + "knowledge": "2026-02", + "release_date": "2026-08-06", + "last_updated": "2026-08-06", "type": "chat" }, { - "id": "novita/glm-4.7-n", - "name": "glm-4.7-n", - "display_name": "glm-4.7-n", + "id": "upstage/solar-mini4", + "name": "Solar Mini 4", + "display_name": "Solar Mini 4", "modalities": { "input": [ "text" @@ -74871,59 +74907,52 @@ ] }, "limit": { - "context": 205000, + "context": 524288, "output": 131072 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true + "supported": false }, "attachment": false, - "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-12-22", - "last_updated": "2025-12-22", + "open_weights": false, + "knowledge": "2026-02", + "release_date": "2026-09-22", + "last_updated": "2026-09-22", "type": "chat" }, { - "id": "novita/minimax-m2.1", - "name": "minimax-m2.1", - "display_name": "minimax-m2.1", + "id": "unbiased/pareto-26.10", + "name": "Pareto 26.10", + "display_name": "Pareto 26.10", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 205000, + "context": 1048576, "output": 131072 }, - "temperature": false, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true - } + "supported": false }, - "attachment": false, - "open_weights": true, - "release_date": "2025-12-23", - "last_updated": "2025-12-23", + "attachment": true, + "open_weights": false, + "release_date": "2026-10-01", + "last_updated": "2026-10-01", "type": "chat" }, { - "id": "novita/glm-4.6v", - "name": "glm-4.6v", - "display_name": "glm-4.6v", + "id": "unbiased/pareto-26.9", + "name": "Pareto 26.9", + "display_name": "Pareto 26.9", "modalities": { "input": [ "text", @@ -74934,26 +74963,23 @@ ] }, "limit": { - "context": 131000, - "output": 32768 + "context": 262144, + "output": 131072 }, - "temperature": false, "tool_call": true, "reasoning": { - "supported": true, - "default": true + "supported": false }, "attachment": true, - "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-12-08", - "last_updated": "2025-12-08", + "open_weights": false, + "release_date": "2026-09-18", + "last_updated": "2026-09-18", "type": "chat" }, { - "id": "novita/glm-4.7", - "name": "glm-4.7", - "display_name": "glm-4.7", + "id": "ibm-granite/granite-4.2-8b", + "name": "Granite 4.2 8B", + "display_name": "Granite 4.2 8B", "modalities": { "input": [ "text" @@ -74963,8 +74989,8 @@ ] }, "limit": { - "context": 205000, - "output": 131072 + "context": 131072, + "output": 117964 }, "temperature": true, "tool_call": true, @@ -74974,77 +75000,82 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "none", + "low", + "high" ] } }, "attachment": false, "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-12-22", - "last_updated": "2025-12-22", + "release_date": "2026-08-25", + "last_updated": "2026-08-25", "type": "chat" }, { - "id": "novita/kimi-k2.6", - "name": "Kimi-K2.6", - "display_name": "Kimi-K2.6", + "id": "meta/muse-spark-1.1", + "name": "Muse Spark 1.1", + "display_name": "Muse Spark 1.1", "modalities": { "input": [ "text", "image", - "video" + "video", + "audio" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 1000000, + "output": 65536 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { "supported": true, "default": true }, "extra_capabilities": { "reasoning": { - "supported": true + "supported": true, + "effort_options": [ + "minimal", + "low", + "medium", + "high", + "xhigh" + ] } }, "attachment": true, - "open_weights": true, - "knowledge": "2025-04", - "release_date": "2026-04-20", - "last_updated": "2026-04-20", + "open_weights": false, + "release_date": "2026-04-08", + "last_updated": "2026-07-09", "type": "chat" }, { - "id": "novita/kimi-k2.5", - "name": "Kimi-K2.5", - "display_name": "Kimi-K2.5", + "id": "meta/muse-spark-1.3-contributor", + "name": "Muse Spark 1.3 Contributor", + "display_name": "Muse Spark 1.3 Contributor", "modalities": { "input": [ "text", "image", - "video" + "video", + "audio", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 262144 + "context": 1048576, + "output": 943718 }, - "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -75052,39 +75083,39 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "minimal", + "low", + "medium", + "high", + "xhigh" ] } }, "attachment": true, - "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-01-27", - "last_updated": "2026-01-27", + "open_weights": false, + "release_date": "2026-09-02", + "last_updated": "2026-09-02", "type": "chat" }, { - "id": "novita/glm-4.6", - "name": "GLM-4.6", - "display_name": "GLM-4.6", + "id": "meta/muse-spark-open", + "name": "Muse Spark Open", + "display_name": "Muse Spark Open", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 8192, - "output": 8192 + "context": 1048576, + "output": 131072 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -75092,33 +75123,41 @@ }, "extra_capabilities": { "reasoning": { - "supported": true + "effort_options": [ + "low", + "medium", + "high", + "xhigh" + ] } }, - "attachment": false, + "attachment": true, "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-09-30", - "last_updated": "2025-09-30", + "knowledge": "2026-01-04", + "release_date": "2026-10-15", + "last_updated": "2026-10-15", "type": "chat" }, { - "id": "novita/deepseek-v3.2", - "name": "DeepSeek-V3.2", - "display_name": "DeepSeek-V3.2", + "id": "meta/muse-wonder", + "name": "Muse Wonder", + "display_name": "Muse Wonder", "modalities": { "input": [ - "text" + "text", + "image", + "video", + "audio", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 8192 + "context": 1048576, + "output": 1048576 }, - "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -75126,33 +75165,43 @@ }, "extra_capabilities": { "reasoning": { - "supported": true - } - }, - "attachment": false, - "open_weights": true, - "knowledge": "2024-07", - "release_date": "2025-12-01", - "last_updated": "2025-12-01", + "effort_options": [ + "minimal", + "low", + "medium", + "high", + "xhigh", + "max" + ] + } + }, + "attachment": true, + "open_weights": false, + "release_date": "2026-10-12", + "last_updated": "2026-10-12", "type": "chat" }, { - "id": "novita/kimi-k2-thinking", - "name": "kimi-k2-thinking", - "display_name": "kimi-k2-thinking", + "id": "meta/muse-spark-1.2", + "name": "Muse Spark 1.2", + "display_name": "Muse Spark 1.2", "modalities": { "input": [ - "text" + "text", + "image", + "video", + "pdf", + "audio" ], "output": [ "text" ] }, "limit": { - "context": 256000, - "output": 8192 + "context": 1000000, + "output": 65536 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -75161,38 +75210,41 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "minimal", + "low", + "medium", + "high", + "xhigh" ] } }, - "attachment": false, - "open_weights": true, - "knowledge": "2024-08", - "release_date": "2025-11-06", - "last_updated": "2025-11-06", + "attachment": true, + "open_weights": false, + "release_date": "2026-08-05", + "last_updated": "2026-08-05", "type": "chat" }, { - "id": "novita/glm-5", - "name": "GLM-5", - "display_name": "GLM-5", + "id": "meta/muse-spark-1.2-contributor", + "name": "Muse Spark 1.2 Contributor (Data Used for Training)", + "display_name": "Muse Spark 1.2 Contributor (Data Used for Training)", "modalities": { "input": [ - "text" + "text", + "image", + "video", + "audio", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 205000, - "output": 131072 + "context": 1000000, + "output": 65536 }, - "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -75200,38 +75252,42 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "minimal", + "low", + "medium", + "high", + "xhigh" ] } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-02-12", - "last_updated": "2026-02-12", + "attachment": true, + "open_weights": false, + "release_date": "2026-08-05", + "last_updated": "2026-08-05", "type": "chat" }, { - "id": "novita/glm-4.7-flash", - "name": "glm-4.7-flash", - "display_name": "glm-4.7-flash", + "id": "meta/muse-spark-1.3", + "name": "Muse Spark 1.3", + "display_name": "Muse Spark 1.3", "modalities": { "input": [ - "text" + "text", + "image", + "video", + "pdf", + "audio" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 65500 + "context": 1048576, + "output": 943718 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -75239,138 +75295,185 @@ }, "extra_capabilities": { "reasoning": { - "supported": true + "supported": true, + "effort_options": [ + "minimal", + "low", + "medium", + "high", + "xhigh" + ] } }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-04", - "release_date": "2026-01-19", - "last_updated": "2026-01-19", + "attachment": true, + "open_weights": false, + "release_date": "2026-09-02", + "last_updated": "2026-09-02", "type": "chat" }, { - "id": "runwayml/runway", - "name": "Runway", - "display_name": "Runway", + "id": "meta/muse-glimmer-30b", + "name": "Muse Glimmer 30B", + "display_name": "Muse Glimmer 30B", "modalities": { "input": [ "text", "image" ], "output": [ - "video" + "text" ] }, "limit": { - "context": 256, - "output": 8192 + "context": 131072, + "output": 117964 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "minimal", + "low", + "medium", + "high", + "xhigh" + ] + } }, "attachment": true, - "open_weights": false, - "release_date": "2024-10-11", - "last_updated": "2024-10-11" + "open_weights": true, + "knowledge": "2026-01-04", + "release_date": "2026-08-10", + "last_updated": "2026-08-10", + "type": "chat" }, { - "id": "runwayml/runway-gen-4-turbo", - "name": "Runway-Gen-4-Turbo", - "display_name": "Runway-Gen-4-Turbo", + "id": "inclusionai/ling-3.1-flash", + "name": "Ling 3.1 Flash", + "display_name": "Ling 3.1 Flash", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ - "video" + "text" ] }, "limit": { - "context": 256, - "output": 8192 + "context": 262144, + "output": 32768 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": true, - "open_weights": false, - "release_date": "2025-05-09", - "last_updated": "2025-05-09" + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "none", + "low", + "medium", + "high" + ] + } + }, + "attachment": false, + "open_weights": true, + "release_date": "2026-09-29", + "last_updated": "2026-09-29", + "type": "chat" }, { - "id": "stabilityai/stablediffusionxl", - "name": "StableDiffusionXL", - "display_name": "StableDiffusionXL", + "id": "inclusionai/ling-3.0-flash:thinking", + "name": "Ling 3.0 Flash Thinking", + "display_name": "Ling 3.0 Flash Thinking", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ - "image" + "text" ] }, "limit": { - "context": 200, - "output": 8192 + "context": 262144, + "output": 32768 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": true, - "open_weights": false, - "release_date": "2023-07-09", - "last_updated": "2023-07-09" - } - ] - }, - "azure-cognitive-services": { - "id": "azure-cognitive-services", - "name": "Azure Cognitive Services", - "display_name": "Azure Cognitive Services", - "doc": "https://learn.microsoft.com/en-us/azure/ai-services/openai/concepts/models", - "models": [ + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "none", + "minimal", + "low", + "medium", + "high", + "xhigh" + ] + } + }, + "attachment": false, + "open_weights": true, + "release_date": "2026-07-23", + "last_updated": "2026-07-23", + "type": "chat" + }, { - "id": "phi-4-reasoning", - "name": "Phi-4-reasoning", - "display_name": "Phi-4-reasoning", + "id": "inclusionai/ling-3.0-flash-vl", + "name": "Ling 3.0 Flash VL", + "display_name": "Ling 3.0 Flash VL", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 32000, - "output": 4096 + "context": 262144, + "output": 32768 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { "supported": true, "default": true }, - "attachment": false, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "none", + "low", + "medium", + "high" + ] + } + }, + "attachment": true, "open_weights": true, - "knowledge": "2023-10", - "release_date": "2024-12-11", - "last_updated": "2024-12-11", + "release_date": "2026-09-10", + "last_updated": "2026-09-10", "type": "chat" }, { - "id": "text-embedding-ada-002", - "name": "text-embedding-ada-002", - "display_name": "text-embedding-ada-002", + "id": "inclusionai/ling-3.0-flash", + "name": "Ling 3.0 Flash", + "display_name": "Ling 3.0 Flash", "modalities": { "input": [ "text" @@ -75380,35 +75483,35 @@ ] }, "limit": { - "context": 8192, - "output": 1536 + "context": 262144, + "output": 32768 }, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { "supported": false }, "attachment": false, - "open_weights": false, - "release_date": "2022-12-15", - "last_updated": "2022-12-15", - "type": "embedding" + "open_weights": true, + "release_date": "2026-07-23", + "last_updated": "2026-07-23", + "type": "chat" }, { - "id": "grok-4-1-fast-reasoning", - "name": "Grok 4.1 Fast (Reasoning)", - "display_name": "Grok 4.1 Fast (Reasoning)", + "id": "deepseek-ai/DeepSeek-R1-0528", + "name": "DeepSeek R1 0528", + "display_name": "DeepSeek R1 0528", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 8192 + "context": 163840, + "output": 32768 }, "temperature": true, "tool_call": true, @@ -75418,128 +75521,80 @@ }, "extra_capabilities": { "reasoning": { - "supported": true + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] } }, - "attachment": true, - "open_weights": false, - "release_date": "2025-06-27", - "last_updated": "2025-06-27", + "attachment": false, + "open_weights": true, + "release_date": "2025-05-28", + "last_updated": "2025-05-28", "type": "chat" }, { - "id": "gpt-5.1-codex-max", - "name": "GPT-5.1 Codex Max", - "display_name": "GPT-5.1 Codex Max", + "id": "deepseek-ai/DeepSeek-V3.1-Terminus", + "name": "DeepSeek V3.1 Terminus", + "display_name": "DeepSeek V3.1 Terminus", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 128000, + "output": 65536 }, - "temperature": false, "tool_call": true, "reasoning": { - "supported": true, - "default": false - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "none", - "effort_options": [ - "none", - "low", - "medium", - "high" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" - } + "supported": false }, - "attachment": true, - "open_weights": false, - "knowledge": "2024-09-30", - "release_date": "2025-11-13", - "last_updated": "2025-11-13", + "attachment": false, + "open_weights": true, + "release_date": "2025-08-02", + "last_updated": "2025-08-02", "type": "chat" }, { - "id": "gpt-5", - "name": "GPT-5", - "display_name": "GPT-5", + "id": "deepseek-ai/deepseek-v3.2-exp", + "name": "DeepSeek V3.2 Exp", + "display_name": "DeepSeek V3.2 Exp", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 163840, + "output": 65536 }, - "temperature": false, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", - "effort_options": [ - "minimal", - "low", - "medium", - "high" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" - } + "supported": false }, - "attachment": true, - "open_weights": false, - "knowledge": "2024-09-30", - "release_date": "2025-08-07", - "last_updated": "2025-08-07", + "attachment": false, + "open_weights": true, + "release_date": "2024-01-01", + "last_updated": "2025-09-29", "type": "chat" }, { - "id": "gpt-4o-mini", - "name": "GPT-4o mini", - "display_name": "GPT-4o mini", + "id": "deepseek-ai/DeepSeek-V3.1:thinking", + "name": "DeepSeek V3.1 Thinking", + "display_name": "DeepSeek V3.1 Thinking", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" @@ -75547,28 +75602,26 @@ }, "limit": { "context": 128000, - "output": 16384 + "output": 65536 }, "temperature": true, "tool_call": true, "reasoning": { "supported": false }, - "attachment": true, - "open_weights": false, - "knowledge": "2023-09", - "release_date": "2024-07-18", - "last_updated": "2024-07-18", + "attachment": false, + "open_weights": true, + "release_date": "2025-08-21", + "last_updated": "2025-08-21", "type": "chat" }, { - "id": "gpt-5.5", - "name": "GPT-5.5", - "display_name": "GPT-5.5", + "id": "deepseek-ai/DeepSeek-V3.1", + "name": "DeepSeek V3.1", + "display_name": "DeepSeek V3.1", "modalities": { "input": [ "text", - "image", "pdf" ], "output": [ @@ -75576,125 +75629,81 @@ ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 128000, + "output": 65536 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", - "effort_options": [ - "low", - "medium", - "high", - "xhigh" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" - } + "supported": false }, "attachment": true, - "open_weights": false, - "knowledge": "2025-12-01", - "release_date": "2026-04-24", - "last_updated": "2026-04-24", + "open_weights": true, + "release_date": "2025-08-21", + "last_updated": "2025-08-21", "type": "chat" }, { - "id": "gpt-4.1-nano", - "name": "GPT-4.1 nano", - "display_name": "GPT-4.1 nano", + "id": "deepseek-ai/DeepSeek-V3.1-Terminus:thinking", + "name": "DeepSeek V3.1 Terminus (Thinking)", + "display_name": "DeepSeek V3.1 Terminus (Thinking)", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1047576, - "output": 32768 + "context": 128000, + "output": 65536 }, - "temperature": true, "tool_call": true, "reasoning": { "supported": false }, - "attachment": true, - "open_weights": false, - "knowledge": "2024-04", - "release_date": "2025-04-14", - "last_updated": "2025-04-14", + "attachment": false, + "open_weights": true, + "release_date": "2024-01-01", + "last_updated": "2025-09-22", "type": "chat" }, { - "id": "o1", - "name": "o1", - "display_name": "o1", + "id": "deepseek-ai/deepseek-v3.2-exp-thinking", + "name": "DeepSeek V3.2 Exp Thinking", + "display_name": "DeepSeek V3.2 Exp Thinking", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 100000 + "context": 163840, + "output": 65536 }, - "temperature": false, - "tool_call": true, + "tool_call": false, "reasoning": { "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", - "effort_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" - } - }, - "attachment": true, - "open_weights": false, - "knowledge": "2023-09", - "release_date": "2024-12-05", - "last_updated": "2024-12-05", + "attachment": false, + "open_weights": true, + "release_date": "2024-01-01", + "last_updated": "2025-09-29", "type": "chat" }, { - "id": "gpt-3.5-turbo-1106", - "name": "GPT-3.5 Turbo 1106", - "display_name": "GPT-3.5 Turbo 1106", + "id": "reka/reka-edge-2603", + "name": "Reka Edge 2603", + "display_name": "Reka Edge 2603", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" @@ -75702,66 +75711,68 @@ }, "limit": { "context": 16384, - "output": 4096 + "output": 16384 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { "supported": false }, - "attachment": false, - "open_weights": false, - "knowledge": "2021-08", - "release_date": "2023-11-06", - "last_updated": "2023-11-06", + "attachment": true, + "open_weights": true, + "release_date": "2026-03-11", + "last_updated": "2026-03-11", "type": "chat" }, { - "id": "gpt-image-2", - "name": "GPT-Image-2", - "display_name": "GPT-Image-2", + "id": "reka/reka-flash-3", + "name": "Reka Flash 3", + "display_name": "Reka Flash 3", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ - "image" + "text" ] }, "limit": { - "context": 8192, - "output": 8192 + "context": 65536, + "output": 65536 }, - "temperature": false, + "temperature": true, "tool_call": false, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": true, - "open_weights": false, - "release_date": "2026-04-21", - "last_updated": "2026-04-21" + "attachment": false, + "open_weights": true, + "knowledge": "2025-01-31", + "release_date": "2025-03-10", + "last_updated": "2025-03-10", + "type": "chat" }, { - "id": "gpt-6-astra", - "name": "GPT-6 Astra", - "display_name": "GPT-6 Astra", + "id": "xiaomi/mimo-v2.6-pro", + "name": "MiMo V2.6 Pro", + "display_name": "MiMo V2.6 Pro", "modalities": { "input": [ "text", "image", - "pdf" + "audio", + "video" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 1048576, + "output": 131072 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -75769,178 +75780,147 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, "effort_options": [ "none", - "low", - "medium", - "high", - "xhigh", - "max" + "high" ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2026-04-30", - "release_date": "2026-09-04", - "last_updated": "2026-09-04", + "open_weights": true, + "release_date": "2026-09-22", + "last_updated": "2026-09-22", "type": "chat" }, { - "id": "claude-opus-4-5", - "name": "Claude Opus 4.5", - "display_name": "Claude Opus 4.5", + "id": "xiaomi/mimo-v2.5-pro", + "name": "MiMo V2.5 Pro", + "display_name": "MiMo V2.5 Pro", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 64000 + "context": 1048576, + "output": 131072 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "mixed", - "budget": { - "min": 1024, - "unit": "tokens" - }, - "effort": "high", "effort_options": [ - "low", - "medium", + "none", "high" - ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Claude Opus 4.5 uses manual thinking.type = \"enabled\" with budget_tokens; effort can be used alongside the thinking budget.", - "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header." ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-03-31", - "release_date": "2025-11-24", - "last_updated": "2025-08-01", + "attachment": false, + "open_weights": true, + "knowledge": "2024-12", + "release_date": "2026-04-22", + "last_updated": "2026-04-22", "type": "chat" }, { - "id": "gpt-5.4", - "name": "GPT-5.4", - "display_name": "GPT-5.4", + "id": "xiaomi/mimo-v2.6-flash", + "name": "MiMo V2.6 Flash", + "display_name": "MiMo V2.6 Flash", "modalities": { "input": [ "text", "image", - "pdf" + "audio", + "video" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 1048576, + "output": 131072 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "none", "effort_options": [ "none", - "low", - "medium", - "high", - "xhigh" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", "high" - ], - "visibility": "hidden" + ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-03-05", - "last_updated": "2026-03-05", + "open_weights": true, + "release_date": "2026-09-22", + "last_updated": "2026-09-22", "type": "chat" }, { - "id": "gpt-3.5-turbo-instruct", - "name": "GPT-3.5 Turbo Instruct", - "display_name": "GPT-3.5 Turbo Instruct", + "id": "xiaomi/mimo-v2.6-flash-uncensored:thinking", + "name": "MiMo V2.6 Flash Uncensored Thinking", + "display_name": "MiMo V2.6 Flash Uncensored Thinking", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 4096, - "output": 4096 + "context": 1048576, + "output": 65536 }, - "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, - "open_weights": false, - "knowledge": "2021-08", - "release_date": "2023-09-21", - "last_updated": "2023-09-21", + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "max" + ] + } + }, + "attachment": true, + "open_weights": true, + "release_date": "2026-09-26", + "last_updated": "2026-09-26", "type": "chat" }, { - "id": "deepseek-v4-flash", - "name": "DeepSeek-V4-Flash", - "display_name": "DeepSeek-V4-Flash", + "id": "xiaomi/mimo-v2.5", + "name": "MiMo V2.5", + "display_name": "MiMo V2.5", "modalities": { "input": [ - "text" + "text", + "image", + "audio", + "video" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 384000 + "context": 1048576, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -75950,52 +75930,39 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "high", "effort_options": [ - "low", - "high", - "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." + "none", + "high" ] } }, - "attachment": false, + "attachment": true, "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-04-24", - "last_updated": "2026-04-24", + "knowledge": "2024-12", + "release_date": "2026-04-22", + "last_updated": "2026-04-22", "type": "chat" }, { - "id": "claude-sonnet-5-5", - "name": "Claude Sonnet 5.5", - "display_name": "Claude Sonnet 5.5", + "id": "xiaomi/mimo-v2.6-pro-ultraspeed", + "name": "MiMo V2.6 Pro UltraSpeed", + "display_name": "MiMo V2.6 Pro UltraSpeed", "modalities": { "input": [ "text", "image", - "pdf" + "audio", + "video" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 1048576, + "output": 131072 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -76004,69 +75971,71 @@ "extra_capabilities": { "reasoning": { "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" + "none", + "high" ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2026-06", - "release_date": "2026-09-28", - "last_updated": "2026-09-28", + "open_weights": true, + "release_date": "2026-09-21", + "last_updated": "2026-09-21", "type": "chat" }, { - "id": "mistral-medium-2505", - "name": "Mistral Medium 3", - "display_name": "Mistral Medium 3", + "id": "xiaomi/mimo-v2.5-pro:thinking", + "name": "MiMo V2.5 Pro Thinking", + "display_name": "MiMo V2.5 Pro Thinking", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 128000 + "context": 1048576, + "output": 131072 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-05", - "release_date": "2025-05-07", - "last_updated": "2025-05-07", + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "none", + "high" + ] + } + }, + "attachment": false, + "open_weights": true, + "knowledge": "2024-12", + "release_date": "2026-04-22", + "last_updated": "2026-04-22", "type": "chat" }, { - "id": "claude-haiku-5-5", - "name": "Claude Haiku 5.5", - "display_name": "Claude Haiku 5.5", + "id": "xiaomi/mimo-v2.6-flash-abliterated", + "name": "MiMo V2.6 Flash Abliterated", + "display_name": "MiMo V2.6 Flash Abliterated", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 1048576, + "output": 65536 }, - "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -76075,65 +76044,75 @@ "extra_capabilities": { "reasoning": { "effort_options": [ + "none", "low", "medium", - "high", - "xhigh", - "max" + "high" ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2026-06", - "release_date": "2026-10-07", - "last_updated": "2026-10-07", + "open_weights": true, + "release_date": "2026-09-25", + "last_updated": "2026-09-25", "type": "chat" }, { - "id": "phi-4-mini-reasoning", - "name": "Phi-4-mini-reasoning", - "display_name": "Phi-4-mini-reasoning", + "id": "xiaomi/mimo-v2.6-flash-uncensored", + "name": "MiMo V2.6 Flash Uncensored", + "display_name": "MiMo V2.6 Flash Uncensored", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 4096 + "context": 1048576, + "output": 65536 }, - "temperature": true, "tool_call": true, "reasoning": { "supported": true, "default": true }, - "attachment": false, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "none", + "low", + "medium", + "high" + ] + } + }, + "attachment": true, "open_weights": true, - "knowledge": "2023-10", - "release_date": "2024-12-11", - "last_updated": "2024-12-11", + "release_date": "2026-09-25", + "last_updated": "2026-09-25", "type": "chat" }, { - "id": "deepseek-v4-pro", - "name": "DeepSeek-V4-Pro", - "display_name": "DeepSeek-V4-Pro", + "id": "xiaomi/mimo-v2.5:thinking", + "name": "MiMo V2.5 Thinking", + "display_name": "MiMo V2.5 Thinking", "modalities": { "input": [ - "text" + "text", + "image", + "audio", + "video" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 384000 + "context": 1048576, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -76143,41 +76122,38 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "high", "effort_options": [ - "low", - "high", - "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." + "none", + "high" ] } }, - "attachment": false, + "attachment": true, "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-04-24", - "last_updated": "2026-04-24", + "knowledge": "2024-12", + "release_date": "2026-04-22", + "last_updated": "2026-04-22", "type": "chat" - }, + } + ] + }, + "opper": { + "id": "opper", + "name": "Opper", + "display_name": "Opper", + "api": "https://api.opper.ai/v3/compat", + "doc": "https://opper.ai/models", + "models": [ { - "id": "claude-opus-5", - "name": "Claude Opus 5", - "display_name": "Claude Opus 5", + "id": "gemini-3.5-flash", + "name": "Gemini 3.5 Flash", + "display_name": "Gemini 3.5 Flash", "modalities": { "input": [ "text", "image", + "video", + "audio", "pdf" ], "output": [ @@ -76185,10 +76161,10 @@ ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 1048576, + "output": 65536 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -76197,26 +76173,34 @@ "extra_capabilities": { "reasoning": { "supported": true, - "effort_options": [ + "default_enabled": true, + "mode": "level", + "level": "high", + "level_options": [ + "minimal", "low", "medium", - "high", - "xhigh", - "max" + "high" + ], + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-05", - "release_date": "2026-07-24", - "last_updated": "2026-07-24", + "knowledge": "2025-01", + "release_date": "2026-05-19", + "last_updated": "2026-05-19", "type": "chat" }, { - "id": "gpt-4-turbo", - "name": "GPT-4 Turbo", - "display_name": "GPT-4 Turbo", + "id": "gpt-5.4-nano", + "name": "GPT-5.4 nano", + "display_name": "GPT-5.4 nano", "modalities": { "input": [ "text", @@ -76227,25 +76211,53 @@ ] }, "limit": { - "context": 128000, - "output": 4096 + "context": 400000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": false + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": false, + "mode": "effort", + "effort": "none", + "effort_options": [ + "none", + "low", + "medium", + "high", + "xhigh" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } }, "attachment": true, "open_weights": false, - "knowledge": "2023-12", - "release_date": "2023-11-06", - "last_updated": "2024-04-09", + "knowledge": "2025-08-31", + "release_date": "2026-03-17", + "last_updated": "2026-03-17", "type": "chat" }, { - "id": "phi-4-mini", - "name": "Phi-4-mini", - "display_name": "Phi-4-mini", + "id": "kimi-k3", + "name": "Kimi K3", + "display_name": "Kimi K3", "modalities": { "input": [ "text" @@ -76255,25 +76267,36 @@ ] }, "limit": { - "context": 128000, - "output": 4096 + "context": 1048576, + "output": 131072 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } }, "attachment": false, "open_weights": true, - "knowledge": "2023-10", - "release_date": "2024-12-11", - "last_updated": "2024-12-11", + "release_date": "2026-07-16", + "last_updated": "2026-07-16", "type": "chat" }, { - "id": "claude-mythos-5", - "name": "Claude Mythos 5", - "display_name": "Claude Mythos 5", + "id": "gpt-5.4", + "name": "GPT-5.4", + "display_name": "GPT-5.4", "modalities": { "input": [ "text", @@ -76285,79 +76308,53 @@ ] }, "limit": { - "context": 1000000, + "context": 1050000, "output": 128000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, + "default_enabled": false, "mode": "effort", - "effort": "high", + "effort": "none", "effort_options": [ + "none", "low", "medium", "high", - "xhigh", - "max" + "xhigh" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" ], "interleaved": true, "summaries": true, - "visibility": "omitted", + "visibility": "summary", "continuation": [ "thinking_blocks" - ], - "notes": [ - "Adaptive thinking is always on for Claude Fable 5 and Claude Mythos 5; thinking.type = \"disabled\" is rejected.", - "Manual budget_tokens requests return 400 on Claude Fable 5 and Claude Mythos 5.", - "thinking.display defaults to omitted; set display to summarized to receive readable thinking summaries." ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-01-31", - "release_date": "2026-06-09", - "last_updated": "2026-06-09", + "knowledge": "2025-08-31", + "release_date": "2026-03-05", + "last_updated": "2026-03-05", "type": "chat" }, { - "id": "cohere-embed-v3-multilingual", - "name": "Embed v3 Multilingual", - "display_name": "Embed v3 Multilingual", - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 512, - "output": 1024 - }, - "temperature": false, - "tool_call": false, - "reasoning": { - "supported": false - }, - "attachment": false, - "open_weights": true, - "release_date": "2023-11-07", - "last_updated": "2023-11-07", - "type": "embedding" - }, - { - "id": "llama-4-scout-17b-16e-instruct", - "name": "Llama 4 Scout 17B 16E Instruct", - "display_name": "Llama 4 Scout 17B 16E Instruct", + "id": "gpt-5.4-mini", + "name": "GPT-5.4 mini", + "display_name": "GPT-5.4 mini", "modalities": { "input": [ "text", @@ -76368,40 +76365,10 @@ ] }, "limit": { - "context": 128000, - "output": 8192 - }, - "temperature": true, - "tool_call": true, - "reasoning": { - "supported": false - }, - "attachment": true, - "open_weights": true, - "knowledge": "2024-08", - "release_date": "2025-04-05", - "last_updated": "2025-04-05", - "type": "chat" - }, - { - "id": "claude-opus-4-7", - "name": "Claude Opus 4.7", - "display_name": "Claude Opus 4.7", - "modalities": { - "input": [ - "text", - "image", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 1000000, + "context": 400000, "output": 128000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -76412,51 +76379,51 @@ "supported": true, "default_enabled": false, "mode": "effort", - "effort": "high", + "effort": "none", "effort_options": [ + "none", "low", "medium", "high", - "xhigh", - "max" + "xhigh" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" ], "interleaved": true, "summaries": true, - "visibility": "omitted", + "visibility": "summary", "continuation": [ "thinking_blocks" - ], - "notes": [ - "Claude Opus 4.7 and newer Opus models require thinking.type = \"adaptive\" to enable thinking explicitly.", - "Manual budget_tokens requests return 400 on Claude Opus 4.7 and newer adaptive-only Opus models.", - "task_budget is separate from thinking control and should not be treated as a thinking budget." ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-01-31", - "release_date": "2026-04-16", - "last_updated": "2026-04-16", + "knowledge": "2025-08-31", + "release_date": "2026-03-17", + "last_updated": "2026-03-17", "type": "chat" }, { - "id": "gpt-4o", - "name": "GPT-4o", - "display_name": "GPT-4o", + "id": "mistral-large-2512", + "name": "Mistral Large 3", + "display_name": "Mistral Large 3", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 16384 + "context": 256000, + "output": 8192 }, "temperature": true, "tool_call": true, @@ -76464,78 +76431,119 @@ "supported": false }, "attachment": true, - "open_weights": false, - "knowledge": "2023-09", - "release_date": "2024-05-13", - "last_updated": "2024-08-06", + "open_weights": true, + "knowledge": "2024-11", + "release_date": "2025-12-02", + "last_updated": "2025-12-02", "type": "chat" }, { - "id": "mistral-small-2503", - "name": "Mistral Small 3.1", - "display_name": "Mistral Small 3.1", + "id": "grok-build-0.1", + "name": "Grok Build 0.1", + "display_name": "Grok Build 0.1", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 32768 + "context": 256000, + "output": 256000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } }, "attachment": true, "open_weights": false, - "knowledge": "2024-09", - "release_date": "2025-03-01", - "last_updated": "2025-03-01", + "release_date": "2026-04-16", + "last_updated": "2026-04-16", "type": "chat" }, { - "id": "gpt-4-turbo-vision", - "name": "GPT-4 Turbo Vision", - "display_name": "GPT-4 Turbo Vision", + "id": "gpt-5.6-terra", + "name": "GPT-5.6 Terra", + "display_name": "GPT-5.6 Terra", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 4096 + "context": 1000000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", + "effort_options": [ + "none", + "low", + "medium", + "high", + "xhigh", + "max" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } }, "attachment": true, "open_weights": false, - "knowledge": "2023-11", - "release_date": "2023-11-06", - "last_updated": "2024-04-09", + "knowledge": "2026-02-16", + "release_date": "2026-07-09", + "last_updated": "2026-07-09", "type": "chat" }, { - "id": "gpt-5-pro", - "name": "GPT-5 Pro", - "display_name": "GPT-5 Pro", + "id": "gpt-5.3-codex", + "name": "GPT-5.3 Codex", + "display_name": "GPT-5.3 Codex", "modalities": { "input": [ "text", - "image", "pdf" ], "output": [ @@ -76556,28 +76564,39 @@ "reasoning": { "supported": true, "default_enabled": true, - "mode": "fixed", - "effort": "high", + "mode": "effort", + "effort": "medium", + "effort_options": [ + "low", + "medium", + "high", + "xhigh" + ], "verbosity": "medium", "verbosity_options": [ "low", "medium", "high" ], - "visibility": "hidden" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] } }, "attachment": true, "open_weights": false, - "knowledge": "2024-09-30", - "release_date": "2025-10-06", - "last_updated": "2025-10-06", + "knowledge": "2025-08-31", + "release_date": "2026-02-05", + "last_updated": "2026-02-05", "type": "chat" }, { - "id": "kimi-k2.6", - "name": "Kimi K2.6", - "display_name": "Kimi K2.6", + "id": "gpt-6-astra", + "name": "GPT-6 Astra", + "display_name": "GPT-6 Astra", "modalities": { "input": [ "text", @@ -76588,10 +76607,10 @@ ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 1050000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -76600,24 +76619,35 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" + ] } }, "attachment": true, - "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-04-20", - "last_updated": "2026-04-20", + "open_weights": false, + "knowledge": "2026-04-30", + "release_date": "2026-09-04", + "last_updated": "2026-09-04", "type": "chat" }, { - "id": "grok-4-1-fast-non-reasoning", - "name": "Grok 4.1 Fast (Non-Reasoning)", - "display_name": "Grok 4.1 Fast (Non-Reasoning)", + "id": "sonar", + "name": "Sonar", + "display_name": "Sonar", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" @@ -76625,54 +76655,27 @@ }, "limit": { "context": 128000, - "output": 8192 + "output": 4096 }, "temperature": true, - "tool_call": true, - "reasoning": { - "supported": false - }, - "attachment": true, - "open_weights": false, - "release_date": "2025-06-27", - "last_updated": "2025-06-27", - "type": "chat" - }, - { - "id": "gpt-image-1", - "name": "GPT-Image-1", - "display_name": "GPT-Image-1", - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "image" - ] - }, - "limit": { - "context": 8192, - "output": 8192 - }, - "temperature": false, "tool_call": false, "reasoning": { "supported": false }, - "attachment": true, + "attachment": false, "open_weights": false, - "release_date": "2025-04-24", - "last_updated": "2025-04-24" + "knowledge": "2025-09-01", + "release_date": "2024-01-01", + "last_updated": "2025-09-01", + "type": "chat" }, { - "id": "kimi-k2.5", - "name": "Kimi K2.5", - "display_name": "Kimi K2.5", + "id": "qwen3.8-2.4t-a95b", + "name": "Qwen3.8 2.4T A95B", + "display_name": "Qwen3.8 2.4T A95B", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" @@ -76680,7 +76683,7 @@ }, "limit": { "context": 262144, - "output": 262144 + "output": 131072 }, "temperature": true, "tool_call": true, @@ -76699,29 +76702,27 @@ ] } }, - "attachment": true, + "attachment": false, "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-01-27", - "last_updated": "2026-01-27", + "release_date": "2026-08-12", + "last_updated": "2026-08-12", "type": "chat" }, { - "id": "claude-opus-5-5", - "name": "Claude Opus 5.5", - "display_name": "Claude Opus 5.5", + "id": "gpt-5.4-pro", + "name": "GPT-5.4 Pro", + "display_name": "GPT-5.4 Pro", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, + "context": 1050000, "output": 128000 }, "temperature": false, @@ -76732,26 +76733,40 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "high", "effort_options": [ - "low", "medium", "high", - "xhigh", - "max" + "xhigh" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-06", - "release_date": "2026-09-22", - "last_updated": "2026-09-22", + "knowledge": "2025-08-31", + "release_date": "2026-03-05", + "last_updated": "2026-03-05", "type": "chat" }, { - "id": "o3", - "name": "o3", - "display_name": "o3", + "id": "claude-opus-4-7", + "name": "Claude Opus 4.7", + "display_name": "Claude Opus 4.7", "modalities": { "input": [ "text", @@ -76763,73 +76778,97 @@ ] }, "limit": { - "context": 200000, - "output": 100000 + "context": 1000000, + "output": 64000 }, "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, + "default_enabled": false, "mode": "effort", - "effort": "medium", + "effort": "high", "effort_options": [ "low", "medium", - "high" + "high", + "xhigh", + "max" ], - "visibility": "hidden" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Claude Opus 4.7 and newer Opus models require thinking.type = \"adaptive\" to enable thinking explicitly.", + "Manual budget_tokens requests return 400 on Claude Opus 4.7 and newer adaptive-only Opus models.", + "task_budget is separate from thinking control and should not be treated as a thinking budget." + ] } }, "attachment": true, "open_weights": false, - "knowledge": "2024-05", - "release_date": "2025-04-16", - "last_updated": "2025-04-16", + "knowledge": "2026-01-31", + "release_date": "2026-04-16", + "last_updated": "2026-04-16", "type": "chat" }, { - "id": "gpt-chat-latest", - "name": "GPT Chat Latest", - "display_name": "GPT Chat Latest", + "id": "claude-haiku-4-5", + "name": "Claude Haiku 4.5 (latest)", + "display_name": "Claude Haiku 4.5 (latest)", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 200000, + "output": 64000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, - "effort_options": [ - "medium" + "default_enabled": false, + "mode": "budget", + "budget": { + "min": 1024, + "unit": "tokens" + }, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", + "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-12-01", - "release_date": "2026-05-05", - "last_updated": "2026-05-28", + "knowledge": "2025-02-28", + "release_date": "2025-10-15", + "last_updated": "2025-10-15", "type": "chat" }, { @@ -76848,7 +76887,7 @@ }, "limit": { "context": 1000000, - "output": 128000 + "output": 64000 }, "temperature": false, "tool_call": true, @@ -76859,12 +76898,11 @@ "extra_capabilities": { "reasoning": { "supported": true, - "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, @@ -76876,62 +76914,89 @@ "type": "chat" }, { - "id": "text-embedding-3-small", - "name": "text-embedding-3-small", - "display_name": "text-embedding-3-small", + "id": "qwen3.6-35b-a3b", + "name": "Qwen3.6 35B-A3B", + "display_name": "Qwen3.6 35B-A3B", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 8192, - "output": 1536 + "context": 262144, + "output": 65536 }, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, - "open_weights": false, - "release_date": "2024-01-25", - "last_updated": "2024-01-25", - "type": "embedding" + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } + }, + "attachment": true, + "open_weights": true, + "release_date": "2026-04-17", + "last_updated": "2026-04-17", + "type": "chat" }, { - "id": "gpt-image-2.5-flare", - "name": "GPT Image 2.5 Flare", - "display_name": "GPT Image 2.5 Flare", + "id": "grok-4.5", + "name": "Grok 4.5", + "display_name": "Grok 4.5", "modalities": { "input": [ "text", "image" ], "output": [ - "image" + "text" ] }, "limit": { - "context": 8192, - "output": 8192 + "context": 500000, + "output": 500000 }, - "temperature": false, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } }, "attachment": true, "open_weights": false, - "release_date": "2026-09-08", - "last_updated": "2026-09-08" + "release_date": "2026-07-08", + "last_updated": "2026-07-08", + "type": "chat" }, { - "id": "gpt-6-sol", - "name": "GPT-6 Sol", - "display_name": "GPT-6 Sol", + "id": "claude-fable-5", + "name": "Claude Fable 5", + "display_name": "Claude Fable 5", "modalities": { "input": [ "text", @@ -76943,7 +77008,7 @@ ] }, "limit": { - "context": 1050000, + "context": 1000000, "output": 128000 }, "temperature": false, @@ -76954,27 +77019,69 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "high", "effort_options": [ - "none", "low", "medium", "high", "xhigh", "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Adaptive thinking is always on for Claude Fable 5 and Claude Mythos 5; thinking.type = \"disabled\" is rejected.", + "Manual budget_tokens requests return 400 on Claude Fable 5 and Claude Mythos 5.", + "thinking.display defaults to omitted; set display to summarized to receive readable thinking summaries." ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-04-20", - "release_date": "2026-09-22", - "last_updated": "2026-09-22", + "knowledge": "2026-01-31", + "release_date": "2026-06-09", + "last_updated": "2026-06-09", "type": "chat" }, { - "id": "o4-mini", - "name": "o4-mini", - "display_name": "o4-mini", + "id": "qwen3-coder-next", + "name": "Qwen3 Coder Next", + "display_name": "Qwen3 Coder Next", + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 262144, + "output": 65536 + }, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": false + }, + "attachment": false, + "open_weights": true, + "knowledge": "2025-09", + "release_date": "2026-02-03", + "last_updated": "2026-02-03", + "type": "chat" + }, + { + "id": "claude-sonnet-4-5", + "name": "Claude Sonnet 4.5 (latest)", + "display_name": "Claude Sonnet 4.5 (latest)", "modalities": { "input": [ "text", @@ -76987,44 +77094,89 @@ }, "limit": { "context": 200000, - "output": 100000 + "output": 64000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", - "effort_options": [ - "low", - "medium", - "high" + "default_enabled": false, + "mode": "budget", + "budget": { + "min": 1024, + "unit": "tokens" + }, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ], - "visibility": "hidden" + "notes": [ + "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", + "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." + ] } }, "attachment": true, "open_weights": false, - "knowledge": "2024-05", - "release_date": "2025-04-16", - "last_updated": "2025-04-16", + "knowledge": "2025-07-31", + "release_date": "2025-09-29", + "last_updated": "2025-09-29", "type": "chat" }, { - "id": "gpt-5.6-sol", - "name": "GPT-5.6 Sol", - "display_name": "GPT-5.6 Sol", + "id": "glm-5.3-flash", + "name": "GLM-5.3-Flash", + "display_name": "GLM-5.3-Flash", + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1000000, + "output": 128000 + }, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } + }, + "attachment": false, + "open_weights": true, + "release_date": "2026-08-26", + "last_updated": "2026-08-26", + "type": "chat" + }, + { + "id": "gpt-5.5-pro", + "name": "GPT-5.5 Pro", + "display_name": "GPT-5.5 Pro", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" @@ -77043,41 +77195,36 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ - "none", - "low", "medium", "high", - "xhigh", - "max" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + "xhigh" + ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-02-16", - "release_date": "2026-07-09", - "last_updated": "2026-07-09", + "knowledge": "2025-12-01", + "release_date": "2026-04-23", + "last_updated": "2026-04-23", "type": "chat" }, { - "id": "claude-sonnet-4-6", - "name": "Claude Sonnet 4.6", - "display_name": "Claude Sonnet 4.6", + "id": "gemini-3.8-flash", + "name": "Gemini 3.8 Flash", + "display_name": "Gemini 3.8 Flash", "modalities": { "input": [ "text", "image", + "video", + "audio", "pdf" ], "output": [ @@ -77085,54 +77232,45 @@ ] }, "limit": { - "context": 1000000, - "output": 64000 + "context": 1048576, + "output": 65536 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "mixed", - "budget": { - "min": 1024, - "unit": "tokens" - }, - "effort": "high", - "effort_options": [ + "default_enabled": true, + "mode": "level", + "level": "high", + "level_options": [ + "minimal", "low", "medium", - "high", - "max" + "high" ], "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ "thinking_blocks" - ], - "notes": [ - "Anthropic recommends adaptive thinking with effort for Claude 4.6; budget_tokens remains a deprecated compatibility path.", - "Anthropic API defaults effort to high; lower effort levels should be chosen per workload." ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-02-17", - "last_updated": "2026-03-13", + "release_date": "2026-09-02", + "last_updated": "2026-09-02", "type": "chat" }, { - "id": "gpt-5.6-terra", - "name": "GPT-5.6 Terra", - "display_name": "GPT-5.6 Terra", + "id": "gpt-5.6-luna", + "name": "GPT-5.6 Luna", + "display_name": "GPT-5.6 Luna", "modalities": { "input": [ "text", @@ -77144,7 +77282,7 @@ ] }, "limit": { - "context": 1050000, + "context": 1000000, "output": 128000 }, "temperature": false, @@ -77173,7 +77311,12 @@ "medium", "high" ], - "visibility": "hidden" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] } }, "attachment": true, @@ -77184,35 +77327,9 @@ "type": "chat" }, { - "id": "text-embedding-3-large", - "name": "text-embedding-3-large", - "display_name": "text-embedding-3-large", - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 8192, - "output": 3072 - }, - "tool_call": false, - "reasoning": { - "supported": false - }, - "attachment": false, - "open_weights": false, - "release_date": "2024-01-25", - "last_updated": "2024-01-25", - "type": "embedding" - }, - { - "id": "gpt-5.1-codex", - "name": "GPT-5.1 Codex", - "display_name": "GPT-5.1 Codex", + "id": "claude-opus-4-5", + "name": "Claude Opus 4.5 (latest)", + "display_name": "Claude Opus 4.5 (latest)", "modalities": { "input": [ "text", @@ -77224,10 +77341,10 @@ ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 200000, + "output": 64000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -77237,49 +77354,53 @@ "reasoning": { "supported": true, "default_enabled": false, - "mode": "effort", - "effort": "none", + "mode": "mixed", + "budget": { + "min": 1024, + "unit": "tokens" + }, + "effort": "high", "effort_options": [ - "none", "low", "medium", "high" ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ], - "visibility": "hidden" + "notes": [ + "Claude Opus 4.5 uses manual thinking.type = \"enabled\" with budget_tokens; effort can be used alongside the thinking budget.", + "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header." + ] } }, "attachment": true, "open_weights": false, - "knowledge": "2024-09-30", - "release_date": "2025-11-14", - "last_updated": "2025-11-14", + "knowledge": "2025-05", + "release_date": "2025-11-24", + "last_updated": "2025-11-24", "type": "chat" }, { - "id": "gpt-6-luna", - "name": "GPT-6 Luna", - "display_name": "GPT-6 Luna", + "id": "gpt-oss-120b", + "name": "GPT OSS 120B", + "display_name": "GPT OSS 120B", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 128000, + "output": 8192 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -77287,39 +77408,37 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "none", - "low", - "medium", - "high", - "xhigh", - "max" + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-05-18", - "release_date": "2026-09-22", - "last_updated": "2026-09-22", + "attachment": false, + "open_weights": true, + "knowledge": "2024-06", + "release_date": "2025-08-05", + "last_updated": "2025-08-05", "type": "chat" }, { - "id": "deepseek-v4.1-flash", - "name": "DeepSeek-V4.1-Flash", - "display_name": "DeepSeek-V4.1-Flash", + "id": "qwen3.8-27b", + "name": "Qwen3.8 27B", + "display_name": "Qwen3.8 27B", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 262144, + "output": 32768 }, "temperature": true, "tool_call": true, @@ -77337,39 +77456,35 @@ "thinking_blocks" ], "effort_options": [ - "none", "low", - "high", - "max" + "medium", + "xhigh" ] } }, - "attachment": true, + "attachment": false, "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-09-10", - "last_updated": "2026-09-10", + "release_date": "2026-08-14", + "last_updated": "2026-08-14", "type": "chat" }, { - "id": "gpt-5-mini", - "name": "GPT-5 Mini", - "display_name": "GPT-5 Mini", + "id": "deepseek-v4-pro", + "name": "DeepSeek V4 Pro", + "display_name": "DeepSeek V4 Pro", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 1000000, + "output": 65536 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -77380,112 +77495,34 @@ "supported": true, "default_enabled": true, "mode": "effort", - "effort": "medium", + "effort": "high", "effort_options": [ - "minimal", "low", - "medium", - "high" + "high", + "max" ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" - } - }, - "attachment": true, - "open_weights": false, - "knowledge": "2024-05-30", - "release_date": "2025-08-07", - "last_updated": "2025-08-07", - "type": "chat" - }, - { - "id": "gpt-5-codex", - "name": "GPT-5-Codex", - "display_name": "GPT-5-Codex", - "modalities": { - "input": [ - "text", - "image", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 400000, - "output": 128000 - }, - "temperature": false, - "tool_call": true, - "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", - "effort_options": [ - "minimal", - "low", - "medium", - "high" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ], - "visibility": "hidden" + "notes": [ + "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." + ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2024-09-30", - "release_date": "2025-09-15", - "last_updated": "2025-09-15", - "type": "chat" - }, - { - "id": "model-router", - "name": "Model Router", - "display_name": "Model Router", - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 200000, - "output": 16384 - }, - "tool_call": true, - "reasoning": { - "supported": false - }, - "attachment": true, - "open_weights": false, - "release_date": "2025-05-19", - "last_updated": "2025-11-18", + "attachment": false, + "open_weights": true, + "knowledge": "2025-05", + "release_date": "2026-04-24", + "last_updated": "2026-04-24", "type": "chat" }, { - "id": "deepseek-v3.2", - "name": "DeepSeek-V3.2", - "display_name": "DeepSeek-V3.2", + "id": "glm-5.3", + "name": "GLM-5.3", + "display_name": "GLM-5.3", "modalities": { "input": [ "text" @@ -77495,8 +77532,8 @@ ] }, "limit": { - "context": 128000, - "output": 128000 + "context": 1000000, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -77506,49 +77543,25 @@ }, "extra_capabilities": { "reasoning": { - "supported": true + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] } }, "attachment": false, "open_weights": true, - "knowledge": "2024-07", - "release_date": "2025-12-01", - "last_updated": "2025-12-01", - "type": "chat" - }, - { - "id": "cohere-command-a", - "name": "Command A", - "display_name": "Command A", - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 8192 - }, - "temperature": true, - "tool_call": true, - "reasoning": { - "supported": true, - "default": true - }, - "attachment": false, - "open_weights": true, - "knowledge": "2024-06-01", - "release_date": "2025-03-13", - "last_updated": "2025-03-13", + "release_date": "2026-08-14", + "last_updated": "2026-08-14", "type": "chat" }, { - "id": "grok-4.7", - "name": "Grok 4.7", - "display_name": "Grok 4.7", + "id": "gpt-5.6-sol", + "name": "GPT-5.6 Sol", + "display_name": "GPT-5.6 Sol", "modalities": { "input": [ "text", @@ -77560,10 +77573,10 @@ ] }, "limit": { - "context": 500000, - "output": 500000 + "context": 1000000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -77571,25 +77584,43 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", "effort_options": [ + "none", "low", "medium", "high", - "xhigh" + "xhigh", + "max" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-05", - "release_date": "2026-09-21", - "last_updated": "2026-09-21", + "knowledge": "2026-02-16", + "release_date": "2026-07-09", + "last_updated": "2026-07-09", "type": "chat" }, { - "id": "gpt-5.4-pro", - "name": "GPT-5.4 Pro", - "display_name": "GPT-5.4 Pro", + "id": "claude-sonnet-4-6", + "name": "Claude Sonnet 4.6", + "display_name": "Claude Sonnet 4.6", "modalities": { "input": [ "text", @@ -77601,74 +77632,94 @@ ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 1000000, + "output": 64000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", + "default_enabled": false, + "mode": "mixed", + "budget": { + "min": 1024, + "unit": "tokens" + }, "effort": "high", "effort_options": [ + "low", "medium", "high", - "xhigh" + "max" ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ], - "visibility": "hidden" + "notes": [ + "Anthropic recommends adaptive thinking with effort for Claude 4.6; budget_tokens remains a deprecated compatibility path.", + "Anthropic API defaults effort to high; lower effort levels should be chosen per workload." + ] } }, "attachment": true, "open_weights": false, "knowledge": "2025-08-31", - "release_date": "2026-03-05", - "last_updated": "2026-03-05", + "release_date": "2026-02-17", + "last_updated": "2026-03-13", "type": "chat" }, { - "id": "codestral-2501", - "name": "Codestral 25.01", - "display_name": "Codestral 25.01", + "id": "grok-4.3", + "name": "Grok 4.3", + "display_name": "Grok 4.3", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 256000, - "output": 256000 + "context": 1000000, + "output": 30000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } + }, + "attachment": true, "open_weights": false, - "knowledge": "2024-03", - "release_date": "2025-01-01", - "last_updated": "2025-01-01", + "release_date": "2026-04-17", + "last_updated": "2026-04-17", "type": "chat" }, { - "id": "gpt-5.1-codex-mini", - "name": "GPT-5.1 Codex Mini", - "display_name": "GPT-5.1 Codex Mini", + "id": "claude-opus-4-8", + "name": "Claude Opus 4.8", + "display_name": "Claude Opus 4.8", "modalities": { "input": [ "text", @@ -77680,7 +77731,7 @@ ] }, "limit": { - "context": 400000, + "context": 1000000, "output": 128000 }, "temperature": false, @@ -77694,61 +77745,89 @@ "supported": true, "default_enabled": false, "mode": "effort", - "effort": "none", + "effort": "high", "effort_options": [ - "none", "low", "medium", - "high" + "high", + "xhigh", + "max" ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ], - "visibility": "hidden" + "notes": [ + "Claude Opus 4.7 and newer Opus models require thinking.type = \"adaptive\" to enable thinking explicitly.", + "Manual budget_tokens requests return 400 on Claude Opus 4.7 and newer adaptive-only Opus models.", + "task_budget is separate from thinking control and should not be treated as a thinking budget." + ] } }, "attachment": true, "open_weights": false, - "knowledge": "2024-09-30", - "release_date": "2025-11-14", - "last_updated": "2025-11-14", + "knowledge": "2026-01", + "release_date": "2026-05-28", + "last_updated": "2026-05-28", "type": "chat" }, { - "id": "llama-3.3-70b-instruct", - "name": "Llama-3.3-70B-Instruct", - "display_name": "Llama-3.3-70B-Instruct", + "id": "gemini-3.1-pro-preview", + "name": "Gemini 3.1 Pro Preview", + "display_name": "Gemini 3.1 Pro Preview", "modalities": { "input": [ - "text" + "text", + "image", + "video", + "audio", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 32768 + "context": 1048576, + "output": 65536 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, - "open_weights": true, - "knowledge": "2023-12", - "release_date": "2024-12-06", - "last_updated": "2024-12-06", + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "level", + "level": "high", + "level_options": [ + "low", + "high" + ], + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } + }, + "attachment": true, + "open_weights": false, + "knowledge": "2025-01", + "release_date": "2026-02-19", + "last_updated": "2026-02-19", "type": "chat" }, { - "id": "o3-mini", - "name": "o3-mini", - "display_name": "o3-mini", + "id": "deepseek-v4-flash", + "name": "DeepSeek V4 Flash", + "display_name": "DeepSeek V4 Flash", "modalities": { "input": [ "text" @@ -77758,10 +77837,10 @@ ] }, "limit": { - "context": 200000, - "output": 100000 + "context": 1000000, + "output": 384000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -77772,26 +77851,34 @@ "supported": true, "default_enabled": true, "mode": "effort", - "effort": "medium", + "effort": "high", "effort_options": [ "low", - "medium", - "high" + "high", + "max" ], - "visibility": "hidden" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." + ] } }, "attachment": false, - "open_weights": false, - "knowledge": "2024-05", - "release_date": "2024-12-20", - "last_updated": "2025-01-29", + "open_weights": true, + "knowledge": "2025-05", + "release_date": "2026-04-24", + "last_updated": "2026-04-24", "type": "chat" }, { - "id": "gpt-5.1", - "name": "GPT-5.1", - "display_name": "GPT-5.1", + "id": "gpt-5.5", + "name": "GPT-5.5", + "display_name": "GPT-5.5", "modalities": { "input": [ "text", @@ -77803,26 +77890,26 @@ ] }, "limit": { - "context": 400000, + "context": 1050000, "output": 128000 }, "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, + "default_enabled": true, "mode": "effort", - "effort": "none", + "effort": "medium", "effort_options": [ - "none", "low", "medium", - "high" + "high", + "xhigh" ], "verbosity": "medium", "verbosity_options": [ @@ -77830,14 +77917,19 @@ "medium", "high" ], - "visibility": "hidden" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] } }, "attachment": true, "open_weights": false, - "knowledge": "2024-09-30", - "release_date": "2025-11-14", - "last_updated": "2025-11-14", + "knowledge": "2025-12-01", + "release_date": "2026-04-23", + "last_updated": "2026-04-23", "type": "chat" }, { @@ -77896,25 +77988,26 @@ "open_weights": false, "knowledge": "2025-05-31", "release_date": "2026-02-05", - "last_updated": "2026-07-17", + "last_updated": "2026-03-13", "type": "chat" }, { - "id": "kimi-k2.7-code", - "name": "Kimi K2.7 Code", - "display_name": "Kimi K2.7 Code", + "id": "claude-fable-5-1", + "name": "Claude Fable 5.1", + "display_name": "Claude Fable 5.1", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 1000000, + "output": 128000 }, "temperature": false, "tool_call": true, @@ -77925,68 +78018,107 @@ "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "high", + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" + ], "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ "thinking_blocks" + ], + "notes": [ + "Adaptive thinking is always on for Claude Fable 5 and Claude Mythos 5; thinking.type = \"disabled\" is rejected.", + "Manual budget_tokens requests return 400 on Claude Fable 5 and Claude Mythos 5.", + "thinking.display defaults to omitted; set display to summarized to receive readable thinking summaries." ] } }, "attachment": true, - "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-06-12", - "last_updated": "2026-06-12", + "open_weights": false, + "knowledge": "2026-06", + "release_date": "2026-09-01", + "last_updated": "2026-09-01", "type": "chat" }, { - "id": "gpt-image-1.5", - "name": "GPT-Image-1.5", - "display_name": "GPT-Image-1.5", + "id": "gemini-3.7-flash", + "name": "Gemini 3.7 Flash", + "display_name": "Gemini 3.7 Flash", "modalities": { "input": [ "text", - "image" + "image", + "video", + "audio", + "pdf" ], "output": [ - "text", - "image" + "text" ] }, "limit": { - "context": 8192, - "output": 8192 + "context": 1048576, + "output": 65536 }, - "temperature": false, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "level", + "level": "high", + "level_options": [ + "minimal", + "low", + "medium", + "high" + ], + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } }, "attachment": true, "open_weights": false, - "release_date": "2025-11-25", - "last_updated": "2025-11-25" + "knowledge": "2026-03", + "release_date": "2026-08-13", + "last_updated": "2026-08-13", + "type": "chat" }, { - "id": "gpt-5.6-luna", - "name": "GPT-5.6 Luna", - "display_name": "GPT-5.6 Luna", + "id": "gemma-4-31b-it", + "name": "Gemma 4 31B IT", + "display_name": "Gemma 4 31B IT", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 256000, + "output": 8192 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -77995,41 +78127,28 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", - "effort_options": [ - "none", - "low", - "medium", - "high", - "xhigh", - "max" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2026-02-16", - "release_date": "2026-07-09", - "last_updated": "2026-07-09", + "open_weights": true, + "knowledge": "2025-01", + "release_date": "2026-04-02", + "last_updated": "2026-04-02", "type": "chat" }, { - "id": "llama-4-maverick-17b-128e-instruct-fp8", - "name": "Llama 4 Maverick 17B 128E Instruct FP8", - "display_name": "Llama 4 Maverick 17B 128E Instruct FP8", + "id": "glm-5.2", + "name": "GLM-5.2", + "display_name": "GLM-5.2", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" @@ -78037,24 +78156,35 @@ }, "limit": { "context": 1000000, - "output": 16384 + "output": 131072 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": true, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } + }, + "attachment": false, "open_weights": true, - "knowledge": "2024-08", - "release_date": "2025-04-05", - "last_updated": "2025-04-05", + "release_date": "2026-06-13", + "last_updated": "2026-06-13", "type": "chat" }, { - "id": "gpt-5.4-mini", - "name": "GPT-5.4 Mini", - "display_name": "GPT-5.4 Mini", + "id": "claude-opus-5", + "name": "Claude Opus 5", + "display_name": "Claude Opus 5", "modalities": { "input": [ "text", @@ -78066,48 +78196,37 @@ ] }, "limit": { - "context": 400000, + "context": 1000000, "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "none", - "effort_options": [ - "none", - "low", - "medium", - "high", - "xhigh" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-03-17", - "last_updated": "2026-03-17", + "knowledge": "2026-05", + "release_date": "2026-07-24", + "last_updated": "2026-07-24", "type": "chat" }, { - "id": "phi-4-reasoning-plus", - "name": "Phi-4-reasoning-plus", - "display_name": "Phi-4-reasoning-plus", + "id": "sonar-pro", + "name": "Sonar Pro", + "display_name": "Sonar Pro", "modalities": { "input": [ "text" @@ -78117,41 +78236,39 @@ ] }, "limit": { - "context": 32000, - "output": 4096 + "context": 200000, + "output": 8000 }, "temperature": true, "tool_call": false, "reasoning": { - "supported": true, - "default": true + "supported": false }, "attachment": false, - "open_weights": true, - "knowledge": "2023-10", - "release_date": "2024-12-11", - "last_updated": "2024-12-11", + "open_weights": false, + "knowledge": "2025-09-01", + "release_date": "2024-01-01", + "last_updated": "2025-09-01", "type": "chat" }, { - "id": "gpt-5.3-codex", - "name": "GPT-5.3 Codex", - "display_name": "GPT-5.3 Codex", + "id": "qwen3.8-max", + "name": "Qwen3.8 Max", + "display_name": "Qwen3.8 Max", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 983616, + "output": 131072 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -78160,145 +78277,155 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", - "effort_options": [ - "low", - "medium", - "high", - "xhigh" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-02-24", - "last_updated": "2026-02-24", + "release_date": "2026-08-03", + "last_updated": "2026-08-03", "type": "chat" }, { - "id": "gpt-4.1-mini", - "name": "GPT-4.1 mini", - "display_name": "GPT-4.1 mini", + "id": "grok-4.6", + "name": "Grok 4.6", + "display_name": "Grok 4.6", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1047576, - "output": 32768 + "context": 500000, + "output": 500000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } }, "attachment": true, "open_weights": false, - "knowledge": "2024-04", - "release_date": "2025-04-14", - "last_updated": "2025-04-14", + "knowledge": "2026-02-01", + "release_date": "2026-08-12", + "last_updated": "2026-08-12", "type": "chat" }, { - "id": "claude-haiku-4-5", - "name": "Claude Haiku 4.5", - "display_name": "Claude Haiku 4.5", + "id": "muse-spark-1.2", + "name": "Muse Spark 1.2", + "display_name": "Muse Spark 1.2", "modalities": { "input": [ "text", "image", - "pdf" + "video", + "pdf", + "audio" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 64000 + "context": 1048576, + "output": 131072 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "budget", - "budget": { - "min": 1024, - "unit": "tokens" - }, "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ "thinking_blocks" ], - "notes": [ - "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", - "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." + "effort_options": [ + "minimal", + "low", + "medium", + "high", + "xhigh" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-02-28", - "release_date": "2025-11-18", - "last_updated": "2025-11-18", + "release_date": "2026-08-05", + "last_updated": "2026-08-05", "type": "chat" }, { - "id": "phi-4", - "name": "Phi-4", - "display_name": "Phi-4", + "id": "minimax-m3", + "name": "MiniMax-M3", + "display_name": "MiniMax-M3", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 4096 + "context": 1000000, + "output": 131072 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } + }, + "attachment": true, "open_weights": true, - "knowledge": "2023-10", - "release_date": "2024-12-11", - "last_updated": "2024-12-11", + "release_date": "2026-06-01", + "last_updated": "2026-06-01", "type": "chat" }, { - "id": "deepseek-v3.2-speciale", - "name": "DeepSeek-V3.2-Speciale", - "display_name": "DeepSeek-V3.2-Speciale", + "id": "sonar-reasoning-pro", + "name": "Sonar Reasoning Pro", + "display_name": "Sonar Reasoning Pro", "modalities": { "input": [ "text" @@ -78309,7 +78436,7 @@ }, "limit": { "context": 128000, - "output": 128000 + "output": 4096 }, "temperature": true, "tool_call": false, @@ -78317,131 +78444,126 @@ "supported": true, "default": true }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } + }, "attachment": false, - "open_weights": true, - "knowledge": "2024-07", - "release_date": "2025-12-01", - "last_updated": "2025-12-01", + "open_weights": false, + "knowledge": "2025-09-01", + "release_date": "2024-01-01", + "last_updated": "2025-09-01", "type": "chat" }, { - "id": "gpt-5.4-nano", - "name": "GPT-5.4 Nano", - "display_name": "GPT-5.4 Nano", + "id": "muse-spark-1.3", + "name": "Muse Spark 1.3", + "display_name": "Muse Spark 1.3", "modalities": { "input": [ "text", "image", - "pdf" + "video", + "pdf", + "audio" ], "output": [ "text" ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 1048576, + "output": 131072 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "none", + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ - "none", + "minimal", "low", "medium", "high", - "xhigh" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + "xhigh", + "max" + ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-03-17", - "last_updated": "2026-03-17", + "release_date": "2026-09-02", + "last_updated": "2026-09-02", "type": "chat" }, { - "id": "claude-opus-4-8", - "name": "Claude Opus 4.8", - "display_name": "Claude Opus 4.8", + "id": "mistral-small-2603", + "name": "Mistral Small 4", + "display_name": "Mistral Small 4", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 256000, + "output": 8192 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "high", - "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" - ], "interleaved": true, "summaries": true, - "visibility": "omitted", + "visibility": "summary", "continuation": [ "thinking_blocks" - ], - "notes": [ - "Claude Opus 4.7 and newer Opus models require thinking.type = \"adaptive\" to enable thinking explicitly.", - "Manual budget_tokens requests return 400 on Claude Opus 4.7 and newer adaptive-only Opus models.", - "task_budget is separate from thinking control and should not be treated as a thinking budget." ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2025-12-31", - "release_date": "2026-05-28", - "last_updated": "2026-05-28", + "open_weights": true, + "knowledge": "2025-06", + "release_date": "2026-03-16", + "last_updated": "2026-03-16", "type": "chat" }, { - "id": "gpt-4.1", - "name": "GPT-4.1", - "display_name": "GPT-4.1", + "id": "gemini-3-flash-preview", + "name": "Gemini 3 Flash Preview", + "display_name": "Gemini 3 Flash Preview", "modalities": { "input": [ "text", "image", + "video", + "audio", "pdf" ], "output": [ @@ -78449,53 +78571,46 @@ ] }, "limit": { - "context": 1047576, - "output": 32768 + "context": 1048576, + "output": 65536 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false - }, - "attachment": true, - "open_weights": false, - "knowledge": "2024-04", - "release_date": "2025-04-14", - "last_updated": "2025-04-14", - "type": "chat" - }, - { - "id": "gpt-3.5-turbo-0125", - "name": "GPT-3.5 Turbo 0125", - "display_name": "GPT-3.5 Turbo 0125", - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 16384, - "output": 4096 + "supported": true, + "default": true }, - "temperature": true, - "tool_call": false, - "reasoning": { - "supported": false + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "level", + "level": "high", + "level_options": [ + "minimal", + "low", + "medium", + "high" + ], + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } }, - "attachment": false, + "attachment": true, "open_weights": false, - "knowledge": "2021-08", - "release_date": "2024-01-25", - "last_updated": "2024-01-25", + "knowledge": "2025-01", + "release_date": "2025-12-17", + "last_updated": "2025-12-17", "type": "chat" }, { - "id": "ministral-3b", - "name": "Ministral 3B", - "display_name": "Ministral 3B", + "id": "minimax-m2.7", + "name": "MiniMax-M2.7", + "display_name": "MiniMax-M2.7", "modalities": { "input": [ "text" @@ -78505,29 +78620,42 @@ ] }, "limit": { - "context": 128000, - "output": 8192 + "context": 196608, + "output": 131072 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } }, "attachment": false, "open_weights": true, - "knowledge": "2024-03", - "release_date": "2024-10-22", - "last_updated": "2024-10-22", + "release_date": "2026-03-18", + "last_updated": "2026-03-18", "type": "chat" }, { - "id": "gpt-5.2-codex", - "name": "GPT-5.2 Codex", - "display_name": "GPT-5.2 Codex", + "id": "gemini-3.5-flash-lite", + "name": "Gemini 3.5 Flash Lite", + "display_name": "Gemini 3.5 Flash Lite", "modalities": { "input": [ "text", "image", + "video", + "audio", "pdf" ], "output": [ @@ -78535,10 +78663,10 @@ ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 1048576, + "output": 65536 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -78547,119 +78675,123 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", - "effort_options": [ - "low", - "medium", - "high", - "xhigh" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-01-14", - "last_updated": "2026-01-14", + "knowledge": "2026-03", + "release_date": "2026-07-21", + "last_updated": "2026-07-21", "type": "chat" }, { - "id": "claude-sonnet-4-5", - "name": "Claude Sonnet 4.5", - "display_name": "Claude Sonnet 4.5", + "id": "gpt-oss-20b", + "name": "GPT OSS 20B", + "display_name": "GPT OSS 20B", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 64000 + "context": 128000, + "output": 8192 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "budget", - "budget": { - "min": 1024, - "unit": "tokens" - }, "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ "thinking_blocks" - ], - "notes": [ - "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", - "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-07-31", - "release_date": "2025-11-18", - "last_updated": "2025-11-18", + "attachment": false, + "open_weights": true, + "knowledge": "2024-06", + "release_date": "2025-08-05", + "last_updated": "2025-08-05", "type": "chat" - }, + } + ] + }, + "modal": { + "id": "modal", + "name": "Modal", + "display_name": "Modal", + "api": "https://inference.us-west.modal.direct/v1", + "doc": "https://modal.com/docs/guide/endpoints", + "models": [ { - "id": "gpt-image-2.5-sunburst", - "name": "GPT Image 2.5 Sunburst", - "display_name": "GPT Image 2.5 Sunburst", + "id": "Qwen/Qwen3.8-2.4T-A95B", + "name": "Qwen3.8-Max", + "display_name": "Qwen3.8-Max", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ - "image" + "text" ] }, "limit": { - "context": 8192, - "output": 8192 + "context": 1010000, + "output": 131072 }, - "temperature": false, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": true, - "open_weights": false, - "release_date": "2026-09-08", - "last_updated": "2026-09-08" + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "effort_options": [ + "low", + "medium", + "xhigh" + ] + } + }, + "attachment": false, + "open_weights": true, + "release_date": "2026-08-12", + "last_updated": "2026-08-12", + "type": "chat" }, { - "id": "claude-fable-5", - "name": "Claude Fable 5", - "display_name": "Claude Fable 5", + "id": "nvidia/GLM-5.3-Flash-NVFP4", + "name": "GLM-5.3-Flash", + "display_name": "GLM-5.3-Flash", "modalities": { "input": [ "text", "image", - "pdf" + "video" ], "output": [ "text" @@ -78667,9 +78799,9 @@ }, "limit": { "context": 1000000, - "output": 128000 + "output": 131072 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -78678,53 +78810,82 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "high", + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ "low", - "medium", "high", - "xhigh", "max" - ], + ] + } + }, + "attachment": true, + "open_weights": true, + "release_date": "2026-08-26", + "last_updated": "2026-08-26", + "type": "chat" + }, + { + "id": "thinkingmachines/Inkling-NVFP4", + "name": "Inkling", + "display_name": "Inkling", + "modalities": { + "input": [ + "text", + "image", + "audio" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1048576, + "output": 262144 + }, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, "interleaved": true, "summaries": true, - "visibility": "omitted", + "visibility": "summary", "continuation": [ "thinking_blocks" - ], - "notes": [ - "Adaptive thinking is always on for Claude Fable 5 and Claude Mythos 5; thinking.type = \"disabled\" is rejected.", - "Manual budget_tokens requests return 400 on Claude Fable 5 and Claude Mythos 5.", - "thinking.display defaults to omitted; set display to summarized to receive readable thinking summaries." ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2026-01-31", - "release_date": "2026-06-09", - "last_updated": "2026-06-09", + "open_weights": true, + "release_date": "2026-07-15", + "last_updated": "2026-07-15", "type": "chat" }, { - "id": "gpt-6.1-terra", - "name": "GPT-6.1 Terra", - "display_name": "GPT-6.1 Terra", + "id": "moonshotai/Kimi-K3", + "name": "Kimi K3", + "display_name": "Kimi K3", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 1048576, + "output": 131072 }, "temperature": false, "tool_call": true, @@ -78734,38 +78895,38 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "none", - "low", - "medium", - "high", - "xhigh", - "max" + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2026-06-01", - "release_date": "2026-10-26", - "last_updated": "2026-10-26", + "open_weights": true, + "release_date": "2026-07-16", + "last_updated": "2026-07-16", "type": "chat" }, { - "id": "grok-4-20-reasoning", - "name": "Grok 4.20 (Reasoning)", - "display_name": "Grok 4.20 (Reasoning)", + "id": "zai-org/GLM-5.3-Flash", + "name": "GLM 5.3 Flash", + "display_name": "GLM 5.3 Flash", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 262000, - "output": 8192 + "context": 1000000, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -78775,20 +78936,30 @@ }, "extra_capabilities": { "reasoning": { - "supported": true + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "effort_options": [ + "low", + "high", + "max" + ] } }, - "attachment": false, - "open_weights": false, - "knowledge": "2025-09", - "release_date": "2026-04-08", - "last_updated": "2026-04-08", + "attachment": true, + "open_weights": true, + "release_date": "2026-08-26", + "last_updated": "2026-08-26", "type": "chat" }, { - "id": "grok-4-20-non-reasoning", - "name": "Grok 4.20 (Non-Reasoning)", - "display_name": "Grok 4.20 (Non-Reasoning)", + "id": "zai-org/GLM-5.3", + "name": "GLM-5.3", + "display_name": "GLM-5.3", "modalities": { "input": [ "text" @@ -78798,45 +78969,57 @@ ] }, "limit": { - "context": 262000, - "output": 8192 + "context": 1000000, + "output": 131072 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true + "supported": true, + "default": true }, "extra_capabilities": { "reasoning": { - "supported": true + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "effort_options": [ + "low", + "high", + "max" + ] } }, "attachment": false, - "open_weights": false, - "knowledge": "2025-09", - "release_date": "2026-04-08", - "last_updated": "2026-04-08", + "open_weights": true, + "release_date": "2026-08-14", + "last_updated": "2026-08-14", "type": "chat" }, { - "id": "claude-fable-5-1", - "name": "Claude Fable 5.1", - "display_name": "Claude Fable 5.1", + "id": "meta-models/Muse-Spark-Open-816B-A42B", + "name": "Muse Spark Open", + "display_name": "Muse Spark Open", "modalities": { "input": [ "text", "image", - "pdf" + "video", + "audio" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 1048576, + "output": 131072 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -78845,75 +79028,90 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "high", - "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" - ], "interleaved": true, "summaries": true, - "visibility": "omitted", + "visibility": "summary", "continuation": [ "thinking_blocks" ], - "notes": [ - "Adaptive thinking is always on for Claude Fable 5 and Claude Mythos 5; thinking.type = \"disabled\" is rejected.", - "Manual budget_tokens requests return 400 on Claude Fable 5 and Claude Mythos 5.", - "thinking.display defaults to omitted; set display to summarized to receive readable thinking summaries." + "effort_options": [ + "low", + "medium", + "high", + "xhigh" ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2026-06", - "release_date": "2026-09-01", - "last_updated": "2026-09-01", + "open_weights": true, + "knowledge": "2026-01-04", + "release_date": "2026-10-15", + "last_updated": "2026-10-15", "type": "chat" }, { - "id": "phi-4-multimodal", - "name": "Phi-4-multimodal", - "display_name": "Phi-4-multimodal", + "id": "deepseek-ai/DeepSeek-V4.1-Flash", + "name": "DeepSeek V4.1 Flash", + "display_name": "DeepSeek V4.1 Flash", "modalities": { "input": [ "text", - "image", - "audio" + "image" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 4096 + "context": 1000000, + "output": 384000 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "effort_options": [ + "low", + "high", + "max" + ] + } }, "attachment": true, "open_weights": true, - "knowledge": "2023-10", - "release_date": "2024-12-11", - "last_updated": "2024-12-11", + "knowledge": "2025-05", + "release_date": "2026-09-10", + "last_updated": "2026-09-10", "type": "chat" - }, + } + ] + }, + "crossmodel": { + "id": "crossmodel", + "name": "CrossModel", + "display_name": "CrossModel", + "api": "https://api.crossmodel.ai/v1", + "doc": "https://www.crossmodel.ai/docs", + "models": [ { - "id": "gpt-5.2", - "name": "GPT-5.2", - "display_name": "GPT-5.2", + "id": "openai/gpt-5.4-nano", + "name": "GPT-5.4 nano", + "display_name": "GPT-5.4 nano", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" @@ -78923,7 +79121,7 @@ "context": 400000, "output": 128000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -78954,19 +79152,18 @@ "attachment": true, "open_weights": false, "knowledge": "2025-08-31", - "release_date": "2025-12-11", - "last_updated": "2025-12-11", + "release_date": "2026-03-17", + "last_updated": "2026-03-17", "type": "chat" }, { - "id": "gpt-6.1-sol", - "name": "GPT-6.1 Sol", - "display_name": "GPT-6.1 Sol", + "id": "openai/gpt-5.4", + "name": "GPT-5.4", + "display_name": "GPT-5.4", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" @@ -78976,61 +79173,45 @@ "context": 1050000, "output": 128000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": false, + "mode": "effort", + "effort": "none", "effort_options": [ + "none", "low", "medium", "high", - "xhigh", - "max" - ] + "xhigh" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, "attachment": true, "open_weights": false, - "knowledge": "2026-04-30", - "release_date": "2026-09-29", - "last_updated": "2026-09-29", + "knowledge": "2025-08-31", + "release_date": "2026-03-05", + "last_updated": "2026-03-05", "type": "chat" }, { - "id": "cohere-embed-v3-english", - "name": "Embed v3 English", - "display_name": "Embed v3 English", - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 512, - "output": 1024 - }, - "temperature": false, - "tool_call": false, - "reasoning": { - "supported": false - }, - "attachment": false, - "open_weights": true, - "release_date": "2023-11-07", - "last_updated": "2023-11-07", - "type": "embedding" - }, - { - "id": "cohere-embed-v-4-0", - "name": "Embed v4", - "display_name": "Embed v4", + "id": "openai/gpt-5.4-mini", + "name": "GPT-5.4 mini", + "display_name": "GPT-5.4 mini", "modalities": { "input": [ "text", @@ -79040,56 +79221,28 @@ "text" ] }, - "limit": { - "context": 128000, - "output": 1536 - }, - "temperature": false, - "tool_call": false, - "reasoning": { - "supported": false - }, - "attachment": true, - "open_weights": true, - "release_date": "2025-04-15", - "last_updated": "2025-04-15", - "type": "embedding" - }, - { - "id": "gpt-5-nano", - "name": "GPT-5 Nano", - "display_name": "GPT-5 Nano", - "modalities": { - "input": [ - "text", - "image", - "pdf" - ], - "output": [ - "text" - ] - }, "limit": { "context": 400000, "output": 128000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, + "default_enabled": false, "mode": "effort", - "effort": "medium", + "effort": "none", "effort_options": [ - "minimal", + "none", "low", "medium", - "high" + "high", + "xhigh" ], "verbosity": "medium", "verbosity_options": [ @@ -79102,28 +79255,27 @@ }, "attachment": true, "open_weights": false, - "knowledge": "2024-05-30", - "release_date": "2025-08-07", - "last_updated": "2025-08-07", + "knowledge": "2025-08-31", + "release_date": "2026-03-17", + "last_updated": "2026-03-17", "type": "chat" }, { - "id": "codex-mini", - "name": "Codex Mini", - "display_name": "Codex Mini", + "id": "openai/gpt-6-sol", + "name": "GPT-6 Sol", + "display_name": "GPT-6 Sol", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 100000 + "context": 1050000, + "output": 128000 }, "temperature": false, "tool_call": true, @@ -79134,23 +79286,26 @@ "extra_capabilities": { "reasoning": { "effort_options": [ + "none", "low", "medium", - "high" + "high", + "xhigh", + "max" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2024-04", - "release_date": "2025-05-16", - "last_updated": "2025-05-16", + "knowledge": "2026-04-20", + "release_date": "2026-09-22", + "last_updated": "2026-09-22", "type": "chat" }, { - "id": "grok-4.6", - "name": "Grok 4.6", - "display_name": "Grok 4.6", + "id": "openai/gpt-5.6-terra", + "name": "GPT-5.6 Terra", + "display_name": "GPT-5.6 Terra", "modalities": { "input": [ "text", @@ -79161,10 +79316,10 @@ ] }, "limit": { - "context": 200000, + "context": 1050000, "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -79173,34 +79328,37 @@ "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", "effort_options": [ + "none", "low", "medium", "high", - "xhigh" - ] + "xhigh", + "max" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, "attachment": true, "open_weights": false, - "knowledge": "2026-02-01", - "release_date": "2026-08-12", - "last_updated": "2026-08-12", + "knowledge": "2026-02-16", + "release_date": "2026-07-09", + "last_updated": "2026-07-09", "type": "chat" - } - ] - }, - "snowflake-cortex": { - "id": "snowflake-cortex", - "name": "Snowflake Cortex", - "display_name": "Snowflake Cortex", - "api": "https://${SNOWFLAKE_ACCOUNT}.snowflakecomputing.com/api/v2/cortex/v1", - "doc": "https://docs.snowflake.com/en/user-guide/snowflake-cortex/cortex-rest-api", - "models": [ + }, { - "id": "claude-opus-4-5", - "name": "Claude Opus 4.5 (latest)", - "display_name": "Claude Opus 4.5 (latest)", + "id": "openai/gpt-6-astra", + "name": "GPT-6 Astra", + "display_name": "GPT-6 Astra", "modalities": { "input": [ "text", @@ -79211,53 +79369,38 @@ ] }, "limit": { - "context": 200000, - "output": 64000 + "context": 1050000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "mixed", - "budget": { - "min": 1024, - "unit": "tokens" - }, - "effort": "high", "effort_options": [ "low", "medium", - "high" - ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Claude Opus 4.5 uses manual thinking.type = \"enabled\" with budget_tokens; effort can be used alongside the thinking budget.", - "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header." + "high", + "xhigh", + "max" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-05", - "release_date": "2025-11-24", - "last_updated": "2025-11-24", + "knowledge": "2026-04-30", + "release_date": "2026-09-04", + "last_updated": "2026-09-04", "type": "chat" }, { - "id": "claude-opus-5", - "name": "Claude Opus 5", - "display_name": "Claude Opus 5", + "id": "openai/gpt-6.1-sol", + "name": "GPT-6.1 Sol", + "display_name": "GPT-6.1 Sol", "modalities": { "input": [ "text", @@ -79268,7 +79411,7 @@ ] }, "limit": { - "context": 1000000, + "context": 1050000, "output": 128000 }, "temperature": false, @@ -79279,20 +79422,26 @@ }, "extra_capabilities": { "reasoning": { - "supported": true + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" + ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-05", - "release_date": "2026-07-24", - "last_updated": "2026-07-24", + "knowledge": "2026-04-30", + "release_date": "2026-09-29", + "last_updated": "2026-09-29", "type": "chat" }, { - "id": "openai-gpt-5.1", - "name": "GPT-5.1", - "display_name": "GPT-5.1", + "id": "openai/gpt-5.5-pro", + "name": "GPT-5.5 Pro", + "display_name": "GPT-5.5 Pro", "modalities": { "input": [ "text", @@ -79303,10 +79452,10 @@ ] }, "limit": { - "context": 272000, - "output": 8192 + "context": 1050000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -79314,26 +79463,25 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, "effort_options": [ - "none", - "minimal", - "low", "medium", - "high" + "high", + "xhigh" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2024-09-30", - "release_date": "2025-11-13", - "last_updated": "2025-11-13", + "knowledge": "2025-12-01", + "release_date": "2026-04-23", + "last_updated": "2026-04-23", "type": "chat" }, { - "id": "claude-opus-4-7", - "name": "Claude Opus 4.7", - "display_name": "Claude Opus 4.7", + "id": "openai/gpt-5.6-luna", + "name": "GPT-5.6 Luna", + "display_name": "GPT-5.6 Luna", "modalities": { "input": [ "text", @@ -79344,52 +79492,49 @@ ] }, "limit": { - "context": 1000000, + "context": 1050000, "output": 128000 }, "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, + "default_enabled": true, "mode": "effort", - "effort": "high", + "effort": "medium", "effort_options": [ + "none", "low", "medium", "high", "xhigh", "max" ], - "interleaved": true, - "summaries": true, - "visibility": "omitted", - "continuation": [ - "thinking_blocks" + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" ], - "notes": [ - "Claude Opus 4.7 and newer Opus models require thinking.type = \"adaptive\" to enable thinking explicitly.", - "Manual budget_tokens requests return 400 on Claude Opus 4.7 and newer adaptive-only Opus models.", - "task_budget is separate from thinking control and should not be treated as a thinking budget." - ] + "visibility": "hidden" } }, "attachment": true, "open_weights": false, - "knowledge": "2026-01-31", - "release_date": "2026-04-16", - "last_updated": "2026-04-16", + "knowledge": "2026-02-16", + "release_date": "2026-07-09", + "last_updated": "2026-07-09", "type": "chat" }, { - "id": "openai-gpt-4.1", - "name": "GPT-4.1", - "display_name": "GPT-4.1", + "id": "openai/gpt-4o-mini", + "name": "GPT-4o mini", + "display_name": "GPT-4o mini", "modalities": { "input": [ "text", @@ -79401,7 +79546,7 @@ }, "limit": { "context": 128000, - "output": 32000 + "output": 16384 }, "temperature": true, "tool_call": true, @@ -79410,15 +79555,15 @@ }, "attachment": true, "open_weights": false, - "knowledge": "2024-04", - "release_date": "2025-04-14", - "last_updated": "2025-04-14", + "knowledge": "2023-09", + "release_date": "2024-07-18", + "last_updated": "2024-07-18", "type": "chat" }, { - "id": "openai-gpt-5.5", - "name": "GPT-5.5", - "display_name": "GPT-5.5", + "id": "openai/gpt-5.6-sol", + "name": "GPT-5.6 Sol", + "display_name": "GPT-5.6 Sol", "modalities": { "input": [ "text", @@ -79440,26 +79585,38 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", "effort_options": [ "none", - "minimal", + "low", + "medium", + "high", + "xhigh", + "max" + ], + "verbosity": "medium", + "verbosity_options": [ "low", "medium", "high" - ] + ], + "visibility": "hidden" } }, "attachment": true, "open_weights": false, - "knowledge": "2025-12-01", - "release_date": "2026-04-23", - "last_updated": "2026-04-23", + "knowledge": "2026-02-16", + "release_date": "2026-07-09", + "last_updated": "2026-07-09", "type": "chat" }, { - "id": "claude-sonnet-5", - "name": "Claude Sonnet 5", - "display_name": "Claude Sonnet 5", + "id": "openai/gpt-5.5", + "name": "GPT-5.5", + "display_name": "GPT-5.5", "modalities": { "input": [ "text", @@ -79470,8 +79627,8 @@ ] }, "limit": { - "context": 1000000, - "output": 64000 + "context": 1050000, + "output": 128000 }, "temperature": false, "tool_call": true, @@ -79481,20 +79638,36 @@ }, "extra_capabilities": { "reasoning": { - "supported": true + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", + "effort_options": [ + "low", + "medium", + "high", + "xhigh" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, "attachment": true, "open_weights": false, - "knowledge": "2026-01-31", - "release_date": "2026-06-30", - "last_updated": "2026-06-30", + "knowledge": "2025-12-01", + "release_date": "2026-04-23", + "last_updated": "2026-04-23", "type": "chat" }, { - "id": "claude-sonnet-4-6", - "name": "Claude Sonnet 4.6", - "display_name": "Claude Sonnet 4.6", + "id": "openai/gpt-6-luna", + "name": "GPT-6 Luna", + "display_name": "GPT-6 Luna", "modalities": { "input": [ "text", @@ -79505,69 +79678,52 @@ ] }, "limit": { - "context": 1000000, - "output": 64000 + "context": 1050000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "mixed", - "budget": { - "min": 1024, - "unit": "tokens" - }, - "effort": "high", "effort_options": [ + "none", "low", "medium", "high", + "xhigh", "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Anthropic recommends adaptive thinking with effort for Claude 4.6; budget_tokens remains a deprecated compatibility path.", - "Anthropic API defaults effort to high; lower effort levels should be chosen per workload." ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-02-17", - "last_updated": "2026-03-13", + "knowledge": "2026-05-18", + "release_date": "2026-09-22", + "last_updated": "2026-09-22", "type": "chat" }, { - "id": "gemini-3.1-pro", - "name": "Gemini 3.1 Pro Preview", - "display_name": "Gemini 3.1 Pro Preview", + "id": "gemini/gemini-3.5-flash", + "name": "Gemini 3.5 Flash", + "display_name": "Gemini 3.5 Flash", "modalities": { "input": [ "text", "image", - "video", "audio", - "pdf" + "video" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 64000 + "context": 1048576, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -79582,7 +79738,9 @@ "mode": "level", "level": "high", "level_options": [ + "minimal", "low", + "medium", "high" ], "summaries": true, @@ -79595,142 +79753,124 @@ "attachment": true, "open_weights": false, "knowledge": "2025-01", - "release_date": "2026-02-19", - "last_updated": "2026-02-19", + "release_date": "2026-05-19", + "last_updated": "2026-05-19", "type": "chat" }, { - "id": "snowflake-llama3.3-70b", - "name": "Llama-3.3-70B-Instruct", - "display_name": "Llama-3.3-70B-Instruct", + "id": "gemini/gemini-4-argon", + "name": "Gemini 4 Argon", + "display_name": "Gemini 4 Argon", "modalities": { "input": [ - "text" + "text", + "image", + "audio", + "video" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 4096 + "context": 1048576, + "output": 65536 }, - "temperature": true, - "tool_call": false, + "temperature": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, - "open_weights": true, - "knowledge": "2023-12", - "release_date": "2024-12-06", - "last_updated": "2024-12-06", + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "none", + "minimal", + "low", + "medium", + "high" + ] + } + }, + "attachment": true, + "open_weights": false, + "knowledge": "2026-03", + "release_date": "2026-10-13", + "last_updated": "2026-10-13", "type": "chat" }, { - "id": "claude-opus-4-6", - "name": "Claude Opus 4.6", - "display_name": "Claude Opus 4.6", + "id": "gemini/gemini-3.6-flash", + "name": "Gemini 3.6 Flash", + "display_name": "Gemini 3.6 Flash", "modalities": { "input": [ "text", - "image" + "image", + "audio", + "video" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 1048576, + "output": 65536 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "mixed", - "budget": { - "min": 1024, - "unit": "tokens" - }, - "effort": "high", - "effort_options": [ + "default_enabled": true, + "mode": "level", + "level": "high", + "level_options": [ + "minimal", "low", "medium", - "high", - "max" + "high" ], - "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Anthropic recommends adaptive thinking with effort for Claude 4.6; budget_tokens remains a deprecated compatibility path.", - "Anthropic API defaults effort to high; lower effort levels should be chosen per workload." + "thought_signatures" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-05-31", - "release_date": "2026-02-05", - "last_updated": "2026-03-13", - "type": "chat" - }, - { - "id": "mistral-large2", - "name": "Mistral Large 2407", - "display_name": "Mistral Large 2407", - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 8192 - }, - "temperature": true, - "tool_call": false, - "reasoning": { - "supported": false - }, - "attachment": false, - "open_weights": true, - "knowledge": "2024-03-31", - "release_date": "2024-07-24", - "last_updated": "2024-07-24", + "knowledge": "2026-03", + "release_date": "2026-07-21", + "last_updated": "2026-07-21", "type": "chat" }, { - "id": "openai-gpt-5-nano", - "name": "GPT-5 Nano", - "display_name": "GPT-5 Nano", + "id": "gemini/gemini-3.8-flash", + "name": "Gemini 3.8 Flash", + "display_name": "Gemini 3.8 Flash", "modalities": { "input": [ "text", - "image" + "image", + "audio", + "video" ], "output": [ "text" ] }, "limit": { - "context": 272000, - "output": 8192 + "context": 1048576, + "output": 65536 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -79738,40 +79878,49 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "none", + "supported": true, + "default_enabled": true, + "mode": "level", + "level": "high", + "level_options": [ "minimal", "low", "medium", "high" + ], + "summaries": true, + "visibility": "summary", + "continuation": [ + "thought_signatures" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2024-05-30", - "release_date": "2025-08-07", - "last_updated": "2025-08-07", + "release_date": "2026-09-02", + "last_updated": "2026-09-02", "type": "chat" }, { - "id": "openai-gpt-5.6-luna", - "name": "GPT-5.6 Luna", - "display_name": "GPT-5.6 Luna", + "id": "gemini/gemini-2.5-pro", + "name": "Gemini 2.5 Pro", + "display_name": "Gemini 2.5 Pro", "modalities": { "input": [ "text", - "image" + "image", + "audio", + "video" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 1048576, + "output": 65536 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -79779,38 +79928,48 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "none", - "minimal", - "low", - "medium", - "high" - ] - } - }, - "attachment": true, + "supported": true, + "default_enabled": true, + "mode": "budget", + "budget": { + "default": -1, + "min": 128, + "max": 32768, + "auto": -1, + "unit": "tokens" + }, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thought_signatures" + ] + } + }, + "attachment": true, "open_weights": false, - "knowledge": "2026-02-16", - "release_date": "2026-07-09", - "last_updated": "2026-07-09", + "knowledge": "2025-01", + "release_date": "2025-06-17", + "last_updated": "2025-06-17", "type": "chat" }, { - "id": "claude-haiku-4-5", - "name": "Claude Haiku 4.5 (latest)", - "display_name": "Claude Haiku 4.5 (latest)", + "id": "gemini/gemini-2.5-flash-lite", + "name": "Gemini 2.5 Flash-Lite", + "display_name": "Gemini 2.5 Flash-Lite", "modalities": { "input": [ "text", - "image" + "image", + "audio", + "video" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 64000 + "context": 1048576, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -79824,44 +79983,44 @@ "default_enabled": false, "mode": "budget", "budget": { - "min": 1024, + "default": -1, + "min": 512, + "max": 24576, + "auto": -1, "unit": "tokens" }, - "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", - "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." + "thought_signatures" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-02-28", - "release_date": "2025-10-15", - "last_updated": "2025-10-15", + "knowledge": "2025-01", + "release_date": "2025-06-17", + "last_updated": "2025-06-17", "type": "chat" }, { - "id": "openai-gpt-5.4", - "name": "GPT-5.4", - "display_name": "GPT-5.4", + "id": "gemini/gemini-2.5-flash", + "name": "Gemini 2.5 Flash", + "display_name": "Gemini 2.5 Flash", "modalities": { "input": [ "text", - "image" + "image", + "video", + "audio" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 1048576, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -79871,96 +80030,100 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "none", - "minimal", - "low", - "medium", - "high" + "supported": true, + "default_enabled": true, + "mode": "budget", + "budget": { + "default": -1, + "min": 0, + "max": 24576, + "auto": -1, + "off": 0, + "unit": "tokens" + }, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thought_signatures" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-03-05", - "last_updated": "2026-03-05", + "knowledge": "2025-01", + "release_date": "2025-06-17", + "last_updated": "2025-06-17", "type": "chat" }, { - "id": "claude-opus-4-8", - "name": "Claude Opus 4.8", - "display_name": "Claude Opus 4.8", + "id": "gemini/gemini-3.1-pro-preview", + "name": "Gemini 3.1 Pro Preview", + "display_name": "Gemini 3.1 Pro Preview", "modalities": { "input": [ "text", - "image" + "image", + "audio", + "video" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 1048576, + "output": 65536 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "high", - "effort_options": [ + "default_enabled": true, + "mode": "level", + "level": "high", + "level_options": [ "low", - "medium", - "high", - "xhigh", - "max" + "high" ], - "interleaved": true, "summaries": true, - "visibility": "omitted", + "visibility": "summary", "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Claude Opus 4.7 and newer Opus models require thinking.type = \"adaptive\" to enable thinking explicitly.", - "Manual budget_tokens requests return 400 on Claude Opus 4.7 and newer adaptive-only Opus models.", - "task_budget is separate from thinking control and should not be treated as a thinking budget." + "thought_signatures" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-01", - "release_date": "2026-05-28", - "last_updated": "2026-05-28", + "knowledge": "2025-01", + "release_date": "2026-02-19", + "last_updated": "2026-02-19", "type": "chat" }, { - "id": "openai-gpt-5.6-sol", - "name": "GPT-5.6 Sol", - "display_name": "GPT-5.6 Sol", + "id": "gemini/gemini-3.7-flash", + "name": "Gemini 3.7 Flash", + "display_name": "Gemini 3.7 Flash", "modalities": { "input": [ "text", - "image" + "image", + "audio", + "video" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 1048576, + "output": 65536 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -79968,80 +80131,50 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "none", + "supported": true, + "default_enabled": true, + "mode": "level", + "level": "high", + "level_options": [ "minimal", "low", "medium", "high" - ] - } - }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-02-16", - "release_date": "2026-07-09", - "last_updated": "2026-07-09", - "type": "chat" - }, - { - "id": "deepseek-r1", - "name": "DeepSeek-R1", - "display_name": "DeepSeek-R1", - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 32768 - }, - "temperature": true, - "tool_call": false, - "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, + ], "summaries": true, "visibility": "summary", "continuation": [ - "thinking_blocks" + "thought_signatures" ] } }, - "attachment": false, - "open_weights": true, - "knowledge": "2024-07", - "release_date": "2025-01-20", - "last_updated": "2025-05-29", + "attachment": true, + "open_weights": false, + "knowledge": "2026-03", + "release_date": "2026-08-13", + "last_updated": "2026-08-13", "type": "chat" }, { - "id": "openai-gpt-5", - "name": "GPT-5", - "display_name": "GPT-5", + "id": "gemini/gemini-3-flash-preview", + "name": "Gemini 3 Flash Preview", + "display_name": "Gemini 3 Flash Preview", "modalities": { "input": [ "text", - "image" + "image", + "video", + "audio" ], "output": [ "text" ] }, "limit": { - "context": 272000, - "output": 8192 + "context": 1048576, + "output": 65536 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -80049,91 +80182,50 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "none", + "supported": true, + "default_enabled": true, + "mode": "level", + "level": "high", + "level_options": [ "minimal", "low", "medium", "high" - ] - } - }, - "attachment": true, - "open_weights": false, - "knowledge": "2024-09-30", - "release_date": "2025-08-07", - "last_updated": "2025-08-07", - "type": "chat" - }, - { - "id": "claude-sonnet-4-5", - "name": "Claude Sonnet 4.5 (latest)", - "display_name": "Claude Sonnet 4.5 (latest)", - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 200000, - "output": 64000 - }, - "temperature": true, - "tool_call": true, - "reasoning": { - "supported": true, - "default": false - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "budget", - "budget": { - "min": 1024, - "unit": "tokens" - }, - "interleaved": true, + ], "summaries": true, "visibility": "summary", "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", - "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." + "thought_signatures" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-07-31", - "release_date": "2025-09-29", - "last_updated": "2025-09-29", + "knowledge": "2025-01", + "release_date": "2025-12-17", + "last_updated": "2025-12-17", "type": "chat" }, { - "id": "openai-gpt-5-mini", - "name": "GPT-5 Mini", - "display_name": "GPT-5 Mini", + "id": "gemini/gemini-3.5-flash-lite", + "name": "Gemini 3.5 Flash Lite", + "display_name": "Gemini 3.5 Flash Lite", "modalities": { "input": [ "text", - "image" + "image", + "audio", + "video" ], "output": [ "text" ] }, "limit": { - "context": 272000, - "output": 8192 + "context": 1048576, + "output": 65536 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -80141,8 +80233,8 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, "effort_options": [ - "none", "minimal", "low", "medium", @@ -80152,19 +80244,20 @@ }, "attachment": true, "open_weights": false, - "knowledge": "2024-05-30", - "release_date": "2025-08-07", - "last_updated": "2025-08-07", + "knowledge": "2026-03", + "release_date": "2026-07-21", + "last_updated": "2026-07-21", "type": "chat" }, { - "id": "claude-fable-5", - "name": "Claude Fable 5", - "display_name": "Claude Fable 5", + "id": "z-ai/glm-5.3-flash", + "name": "GLM-5.3-Flash", + "display_name": "GLM-5.3-Flash", "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ "text" @@ -80174,7 +80267,7 @@ "context": 1000000, "output": 128000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -80183,51 +80276,33 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "high", "effort_options": [ "low", - "medium", "high", - "xhigh", "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "omitted", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Adaptive thinking is always on for Claude Fable 5 and Claude Mythos 5; thinking.type = \"disabled\" is rejected.", - "Manual budget_tokens requests return 400 on Claude Fable 5 and Claude Mythos 5.", - "thinking.display defaults to omitted; set display to summarized to receive readable thinking summaries." ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2026-01-31", - "release_date": "2026-06-09", - "last_updated": "2026-06-09", + "open_weights": true, + "release_date": "2026-08-26", + "last_updated": "2026-08-26", "type": "chat" }, { - "id": "openai-gpt-5.2", - "name": "GPT-5.2", - "display_name": "GPT-5.2", + "id": "z-ai/glm-4.7", + "name": "GLM-4.7", + "display_name": "GLM-4.7", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 400000, + "context": 200000, "output": 128000 }, "temperature": true, @@ -80238,40 +80313,39 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "none", - "minimal", - "low", - "medium", - "high" + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2025-12-11", - "last_updated": "2025-12-11", + "attachment": false, + "open_weights": true, + "knowledge": "2025-04", + "release_date": "2025-12-22", + "last_updated": "2025-12-22", "type": "chat" }, { - "id": "openai-gpt-5.6-terra", - "name": "GPT-5.6 Terra", - "display_name": "GPT-5.6 Terra", + "id": "z-ai/glm-5.3", + "name": "GLM-5.3", + "display_name": "GLM-5.3", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1050000, + "context": 1000000, "output": 128000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -80279,35 +80353,24 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, "effort_options": [ - "none", - "minimal", "low", - "medium", - "high" + "high", + "max" ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-02-16", - "release_date": "2026-07-09", - "last_updated": "2026-07-09", + "attachment": false, + "open_weights": true, + "release_date": "2026-08-14", + "last_updated": "2026-08-14", "type": "chat" - } - ] - }, - "kuae-cloud-coding-plan": { - "id": "kuae-cloud-coding-plan", - "name": "KUAE Cloud Coding Plan", - "display_name": "KUAE Cloud Coding Plan", - "api": "https://coding-plan-endpoint.kuaecloud.net/v1", - "doc": "https://docs.mthreads.com/kuaecloud/kuaecloud-doc-online/coding_plan/", - "models": [ + }, { - "id": "GLM-4.7", - "name": "GLM-4.7", - "display_name": "GLM-4.7", + "id": "z-ai/glm-5", + "name": "GLM-5", + "display_name": "GLM-5", "modalities": { "input": [ "text" @@ -80317,8 +80380,8 @@ ] }, "limit": { - "context": 204800, - "output": 131072 + "context": 200000, + "output": 128000 }, "temperature": true, "tool_call": true, @@ -80339,52 +80402,47 @@ }, "attachment": false, "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-12-22", - "last_updated": "2025-12-22", + "release_date": "2026-02-12", + "last_updated": "2026-02-12", "type": "chat" - } - ] - }, - "inferx": { - "id": "inferx", - "name": "InferX", - "display_name": "InferX", - "api": "https://model.inferx.net/endpoints/v1", - "doc": "https://model.inferx.net/endpoints", - "models": [ + }, { - "id": "Agents-A1", - "name": "Agents-A1", - "display_name": "Agents-A1", + "id": "z-ai/glm-5.1", + "name": "GLM-5.1", + "display_name": "GLM-5.1", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 262000, - "output": 100000 + "context": 200000, + "output": 128000 }, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, "default": true }, - "attachment": true, + "extra_capabilities": { + "reasoning": { + "supported": true + } + }, + "attachment": false, "open_weights": true, - "release_date": "2026-06-26", - "last_updated": "2026-06-26", + "release_date": "2026-04-07", + "last_updated": "2026-04-07", "type": "chat" }, { - "id": "deepseek-v4-flash", - "name": "deepseek-v4-flash", - "display_name": "deepseek-v4-flash", + "id": "z-ai/glm-5.2", + "name": "GLM-5.2", + "display_name": "GLM-5.2", "modalities": { "input": [ "text" @@ -80395,7 +80453,7 @@ }, "limit": { "context": 1000000, - "output": 100000 + "output": 128000 }, "temperature": true, "tool_call": true, @@ -80405,49 +80463,30 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "high", - "effort_options": [ - "low", - "high", - "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." - ] + "supported": true } }, "attachment": false, "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-04-24", - "last_updated": "2026-04-24", + "release_date": "2026-06-13", + "last_updated": "2026-06-13", "type": "chat" }, { - "id": "gemma-4-31B-it-fp8", - "name": "Gemma 4 31B IT FP8", - "display_name": "Gemma 4 31B IT FP8", + "id": "z-ai/glm-5-turbo", + "name": "GLM-5-Turbo", + "display_name": "GLM-5-Turbo", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 32768 + "context": 200000, + "output": 128000 }, "temperature": true, "tool_call": true, @@ -80455,45 +80494,56 @@ "supported": true, "default": true }, - "attachment": true, - "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-04-02", - "last_updated": "2026-04-02", + "extra_capabilities": { + "reasoning": { + "supported": true + } + }, + "attachment": false, + "open_weights": false, + "release_date": "2026-03-16", + "last_updated": "2026-03-16", "type": "chat" }, { - "id": "Qwen3-Coder-Next-FP8-no-thinking", - "name": "Qwen3-Coder-Next-FP8-no-thinking", - "display_name": "Qwen3-Coder-Next-FP8-no-thinking", + "id": "minimax/minimax-m3", + "name": "MiniMax-M3", + "display_name": "MiniMax-M3", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 260000, - "output": 65536 + "context": 1024000, + "output": 512000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, + "extra_capabilities": { + "reasoning": { + "supported": true + } + }, + "attachment": true, "open_weights": true, - "knowledge": "2025-04", - "release_date": "2026-02-03", - "last_updated": "2026-02-03", + "release_date": "2026-06-01", + "last_updated": "2026-06-01", "type": "chat" }, { - "id": "Devstral-2-123B-Instruct-2512-int4-AutoRound", - "name": "Devstral-2-123B-Instruct-2512-int4-AutoRound", - "display_name": "Devstral-2-123B-Instruct-2512-int4-AutoRound", + "id": "minimax/minimax-m2.7", + "name": "MiniMax-M2.7", + "display_name": "MiniMax-M2.7", "modalities": { "input": [ "text" @@ -80503,25 +80553,36 @@ ] }, "limit": { - "context": 128000, - "output": 128000 + "context": 204800, + "output": 131072 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } }, "attachment": false, "open_weights": true, - "knowledge": "2025-12", - "release_date": "2025-12-09", - "last_updated": "2025-12-09", + "release_date": "2026-03-18", + "last_updated": "2026-03-18", "type": "chat" }, { - "id": "Qwen3-Coder-Next-FP8", - "name": "Qwen3 Coder Next FP8", - "display_name": "Qwen3 Coder Next FP8", + "id": "tencent/hy3", + "name": "Hy3", + "display_name": "Hy3", "modalities": { "input": [ "text" @@ -80531,37 +80592,45 @@ ] }, "limit": { - "context": 256144, - "output": 65536 + "context": 262144, + "output": 131072 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "effort_options": [ + "none", + "low", + "high" + ] + } }, "attachment": false, "open_weights": true, - "knowledge": "2025-04", - "release_date": "2026-02-03", - "last_updated": "2026-02-03", + "release_date": "2026-07-06", + "last_updated": "2026-07-06", "type": "chat" }, { - "id": "Qwen3.6-35B-A3B-FP8", - "name": "Qwen3.6 35B A3B FP8", - "display_name": "Qwen3.6 35B A3B FP8", + "id": "tencent/hy4-preview", + "name": "Hy4 preview", + "display_name": "Hy4 preview", "modalities": { "input": [ - "text", - "image", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 262000, + "context": 1048576, "output": 65536 }, "temperature": true, @@ -80573,30 +80642,26 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "none", + "high" ] } }, - "attachment": true, + "attachment": false, "open_weights": true, - "release_date": "2026-04-17", - "last_updated": "2026-04-17", + "release_date": "2026-08-28", + "last_updated": "2026-08-28", "type": "chat" }, { - "id": "mimo-v25", - "name": "mimo-v25", - "display_name": "mimo-v25", + "id": "anthropic/claude-haiku-5-5", + "name": "Claude Haiku 5.5", + "display_name": "Claude Haiku 5.5", "modalities": { "input": [ "text", - "image", - "audio", - "video" + "image" ], "output": [ "text" @@ -80604,65 +80669,92 @@ }, "limit": { "context": 1000000, - "output": 100000 + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, "default": true }, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" + ] + } + }, "attachment": true, - "open_weights": true, - "knowledge": "2024-12", - "release_date": "2026-04-22", - "last_updated": "2026-04-22", + "open_weights": false, + "knowledge": "2026-06", + "release_date": "2026-10-07", + "last_updated": "2026-10-07", "type": "chat" }, { - "id": "Qwen3.6-35B-A3B-fp8-no-thinking", - "name": "Qwen3.6-35B-A3B-fp8-no-thinking", - "display_name": "Qwen3.6-35B-A3B-fp8-no-thinking", + "id": "anthropic/claude-opus-4-7", + "name": "Claude Opus 4.7", + "display_name": "Claude Opus 4.7", "modalities": { "input": [ "text", - "image", - "video" + "image" ], "output": [ "text" ] }, "limit": { - "context": 262000, - "output": 65536 + "context": 1000000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { - "supported": true + "supported": true, + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": false, + "mode": "effort", + "effort": "high", + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" + ], "interleaved": true, "summaries": true, - "visibility": "summary", + "visibility": "omitted", "continuation": [ "thinking_blocks" + ], + "notes": [ + "Claude Opus 4.7 and newer Opus models require thinking.type = \"adaptive\" to enable thinking explicitly.", + "Manual budget_tokens requests return 400 on Claude Opus 4.7 and newer adaptive-only Opus models.", + "task_budget is separate from thinking control and should not be treated as a thinking budget." ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-04-17", - "last_updated": "2026-04-17", + "open_weights": false, + "knowledge": "2026-01-31", + "release_date": "2026-04-16", + "last_updated": "2026-04-16", "type": "chat" }, { - "id": "Ornith-1.0-35B-FP8", - "name": "Ornith-1.0-35B-FP8", - "display_name": "Ornith-1.0-35B-FP8", + "id": "anthropic/claude-haiku-4-5", + "name": "Claude Haiku 4.5 (latest)", + "display_name": "Claude Haiku 4.5 (latest)", "modalities": { "input": [ "text", @@ -80673,67 +80765,103 @@ ] }, "limit": { - "context": 262000, - "output": 100000 + "context": 200000, + "output": 64000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": false, + "mode": "budget", + "budget": { + "min": 1024, + "unit": "tokens" + }, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", + "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." + ] + } }, "attachment": true, - "open_weights": true, - "release_date": "2026-06-25", - "last_updated": "2026-06-25", + "open_weights": false, + "knowledge": "2025-02-28", + "release_date": "2025-10-15", + "last_updated": "2025-10-15", "type": "chat" }, { - "id": "Qwen3-Embedding-8B", - "name": "Qwen3-Embedding-8B", - "display_name": "Qwen3-Embedding-8B", + "id": "anthropic/claude-sonnet-5", + "name": "Claude Sonnet 5", + "display_name": "Claude Sonnet 5", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 32768, - "output": 8192 + "context": 1000000, + "output": 128000 }, "temperature": false, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, - "open_weights": true, - "release_date": "2025-06-05", - "last_updated": "2025-06-05", - "type": "embedding" + "extra_capabilities": { + "reasoning": { + "supported": true, + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" + ] + } + }, + "attachment": true, + "open_weights": false, + "knowledge": "2026-01-31", + "release_date": "2026-06-30", + "last_updated": "2026-06-30", + "type": "chat" }, { - "id": "Qwen3.6-27B-FP8", - "name": "Qwen3.6 27B FP8", - "display_name": "Qwen3.6 27B FP8", + "id": "anthropic/claude-fable-5", + "name": "Claude Fable 5", + "display_name": "Claude Fable 5", "modalities": { "input": [ "text", - "image", - "video" + "image" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 65536 + "context": 1000000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -80742,33 +80870,40 @@ "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "high", + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" + ], "interleaved": true, "summaries": true, - "visibility": "summary", + "visibility": "omitted", "continuation": [ "thinking_blocks" + ], + "notes": [ + "Adaptive thinking is always on for Claude Fable 5 and Claude Mythos 5; thinking.type = \"disabled\" is rejected.", + "Manual budget_tokens requests return 400 on Claude Fable 5 and Claude Mythos 5.", + "thinking.display defaults to omitted; set display to summarized to receive readable thinking summaries." ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-04-22", - "last_updated": "2026-04-22", + "open_weights": false, + "knowledge": "2026-01-31", + "release_date": "2026-06-09", + "last_updated": "2026-06-09", "type": "chat" - } - ] - }, - "wandb": { - "id": "wandb", - "name": "CoreWeave", - "display_name": "CoreWeave", - "api": "https://api.inference.wandb.ai/v1", - "doc": "https://docs.wandb.ai/inference", - "models": [ + }, { - "id": "zai-org/GLM-5.3-Flash", - "name": "GLM 5.3 Flash", - "display_name": "GLM 5.3 Flash", + "id": "anthropic/claude-opus-5-5", + "name": "Claude Opus 5.5", + "display_name": "Claude Opus 5.5", "modalities": { "input": [ "text", @@ -80779,10 +80914,10 @@ ] }, "limit": { - "context": 1048576, - "output": 1048576 + "context": 1000000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -80790,32 +80925,40 @@ }, "extra_capabilities": { "reasoning": { - "supported": true + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" + ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-08-26", - "last_updated": "2026-08-26", + "open_weights": false, + "knowledge": "2026-06", + "release_date": "2026-09-22", + "last_updated": "2026-09-22", "type": "chat" }, { - "id": "zai-org/GLM-5.2", - "name": "GLM 5.2", - "display_name": "GLM 5.2", + "id": "anthropic/claude-sonnet-5-5", + "name": "Claude Sonnet 5.5", + "display_name": "Claude Sonnet 5.5", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 1048576 + "context": 1000000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -80823,57 +80966,84 @@ }, "extra_capabilities": { "reasoning": { - "supported": true + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" + ] } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-06-13", - "last_updated": "2026-06-13", + "attachment": true, + "open_weights": false, + "knowledge": "2026-06", + "release_date": "2026-09-28", + "last_updated": "2026-09-28", "type": "chat" }, { - "id": "zai-org/GLM-5.3", - "name": "GLM 5.3", - "display_name": "GLM 5.3", + "id": "anthropic/claude-sonnet-4-6", + "name": "Claude Sonnet 4.6", + "display_name": "Claude Sonnet 4.6", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 1048576 + "context": 1000000, + "output": 128000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": false, + "mode": "mixed", + "budget": { + "min": 1024, + "unit": "tokens" + }, + "effort": "high", "effort_options": [ "low", + "medium", "high", "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Anthropic recommends adaptive thinking with effort for Claude 4.6; budget_tokens remains a deprecated compatibility path.", + "Anthropic API defaults effort to high; lower effort levels should be chosen per workload." ] } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-08-14", - "last_updated": "2026-08-14", + "attachment": true, + "open_weights": false, + "knowledge": "2025-08-31", + "release_date": "2026-02-17", + "last_updated": "2026-03-13", "type": "chat" }, { - "id": "Qwen/Qwen3.6-35B-A3B", - "name": "Qwen3.6 35B A3B", - "display_name": "Qwen3.6 35B A3B", + "id": "anthropic/claude-opus-4-8", + "name": "Claude Opus 4.8", + "display_name": "Claude Opus 4.8", "modalities": { "input": [ "text", @@ -80884,36 +81054,52 @@ ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 1000000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": false, + "mode": "effort", + "effort": "high", + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" + ], "interleaved": true, "summaries": true, - "visibility": "summary", + "visibility": "omitted", "continuation": [ "thinking_blocks" + ], + "notes": [ + "Claude Opus 4.7 and newer Opus models require thinking.type = \"adaptive\" to enable thinking explicitly.", + "Manual budget_tokens requests return 400 on Claude Opus 4.7 and newer adaptive-only Opus models.", + "task_budget is separate from thinking control and should not be treated as a thinking budget." ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-04-17", - "last_updated": "2026-04-17", + "open_weights": false, + "knowledge": "2026-01", + "release_date": "2026-05-28", + "last_updated": "2026-05-28", "type": "chat" }, { - "id": "Qwen/Qwen3.8-27B", - "name": "Qwen3.8 27B", - "display_name": "Qwen3.8 27B", + "id": "anthropic/claude-fable-5-1", + "name": "Claude Fable 5.1", + "display_name": "Claude Fable 5.1", "modalities": { "input": [ "text", @@ -80924,54 +81110,52 @@ ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 1000000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, "default": true }, - "attachment": true, - "open_weights": true, - "release_date": "2026-08-14", - "last_updated": "2026-08-14", - "type": "chat" - }, - { - "id": "nvidia/NVIDIA-Nemotron-3-Ultra-550B-A55B", - "name": "Nemotron 3 Ultra", - "display_name": "Nemotron 3 Ultra", - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 262144, - "output": 262144 - }, - "temperature": true, - "tool_call": true, - "reasoning": { - "supported": true, - "default": true + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "high", + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "omitted", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Adaptive thinking is always on for Claude Fable 5 and Claude Mythos 5; thinking.type = \"disabled\" is rejected.", + "Manual budget_tokens requests return 400 on Claude Fable 5 and Claude Mythos 5.", + "thinking.display defaults to omitted; set display to summarized to receive readable thinking summaries." + ] + } }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-09", - "release_date": "2026-06-04", - "last_updated": "2026-06-04", + "attachment": true, + "open_weights": false, + "knowledge": "2026-06", + "release_date": "2026-09-01", + "last_updated": "2026-09-01", "type": "chat" }, { - "id": "nvidia/NVIDIA-Nemotron-3.5-Super-VL-120B-A12B", - "name": "Nemotron 3.5 Super VL", - "display_name": "Nemotron 3.5 Super VL", + "id": "anthropic/claude-opus-5", + "name": "Claude Opus 5", + "display_name": "Claude Opus 5", "modalities": { "input": [ "text", @@ -80982,36 +81166,52 @@ ] }, "limit": { - "context": 1048576, - "output": 1048576 + "context": 1000000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, "default": true }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" + ] + } + }, "attachment": true, - "open_weights": true, - "release_date": "2026-10-15", - "last_updated": "2026-10-15", + "open_weights": false, + "knowledge": "2026-05", + "release_date": "2026-07-24", + "last_updated": "2026-07-24", "type": "chat" }, { - "id": "nvidia/NVIDIA-Nemotron-3.5-Lightning-30B-A3B", - "name": "Nemotron 3.5 Lightning", - "display_name": "Nemotron 3.5 Lightning", + "id": "qwen/qwen3.8-omni-flash", + "name": "Qwen3.8 Omni Flash", + "display_name": "Qwen3.8 Omni Flash", "modalities": { "input": [ - "text" + "text", + "image", + "audio", + "video" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 1000000, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -81019,17 +81219,25 @@ "supported": true, "default": true }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-09", - "release_date": "2026-08-11", - "last_updated": "2026-08-11", + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "low", + "medium", + "xhigh" + ] + } + }, + "attachment": true, + "open_weights": false, + "release_date": "2026-09-17", + "last_updated": "2026-09-17", "type": "chat" }, { - "id": "ibm-granite/granite-4.2-8b", - "name": "Granite 4.2 8B", - "display_name": "Granite 4.2 8B", + "id": "qwen/qwen3.7-max", + "name": "Qwen3.7 Max", + "display_name": "Qwen3.7 Max", "modalities": { "input": [ "text" @@ -81039,8 +81247,8 @@ ] }, "limit": { - "context": 131072, - "output": 131072 + "context": 1000000, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -81048,28 +81256,34 @@ "supported": true, "default": true }, + "extra_capabilities": { + "reasoning": { + "supported": true + } + }, "attachment": false, - "open_weights": true, - "release_date": "2026-08-25", - "last_updated": "2026-08-25", + "open_weights": false, + "release_date": "2026-05-21", + "last_updated": "2026-05-21", "type": "chat" }, { - "id": "google/gemma-4-31B-it", - "name": "Gemma 4 31B", - "display_name": "Gemma 4 31B", + "id": "qwen/qwen3.6-flash", + "name": "Qwen3.6 Flash", + "display_name": "Qwen3.6 Flash", "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 1000000, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -81079,32 +81293,38 @@ }, "extra_capabilities": { "reasoning": { - "supported": true + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] } }, "attachment": true, - "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-04-02", - "last_updated": "2026-04-02", + "open_weights": false, + "release_date": "2026-04-27", + "last_updated": "2026-04-27", "type": "chat" }, { - "id": "google/gemma-4-26B-A4B-it", - "name": "Gemma 4 26B A4B", - "display_name": "Gemma 4 26B A4B", + "id": "qwen/qwen3.8-flash", + "name": "Qwen3.8 Flash", + "display_name": "Qwen3.8 Flash", "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 1000000, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -81114,30 +81334,37 @@ }, "extra_capabilities": { "reasoning": { - "supported": true + "supported": true, + "effort_options": [ + "low", + "medium", + "xhigh" + ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-04-02", - "last_updated": "2026-04-02", + "open_weights": false, + "release_date": "2026-08-26", + "last_updated": "2026-08-26", "type": "chat" }, { - "id": "openai/gpt-oss-20b", - "name": "gpt-oss-20b", - "display_name": "gpt-oss-20b", + "id": "qwen/qwen3.7-flash", + "name": "Qwen3.7 Flash", + "display_name": "Qwen3.7 Flash", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 131072 + "context": 1000000, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -81150,28 +81377,29 @@ "supported": true } }, - "attachment": false, - "open_weights": true, - "knowledge": "2024-06", - "release_date": "2025-08-05", - "last_updated": "2025-08-05", + "attachment": true, + "open_weights": false, + "release_date": "2026-07-15", + "last_updated": "2026-07-15", "type": "chat" }, { - "id": "openai/gpt-oss-120b", - "name": "gpt-oss-120b", - "display_name": "gpt-oss-120b", + "id": "qwen/qwen3.6-plus", + "name": "Qwen3.6 Plus", + "display_name": "Qwen3.6 Plus", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 131072 + "context": 1000000, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -81181,32 +81409,39 @@ }, "extra_capabilities": { "reasoning": { - "supported": true + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] } }, - "attachment": false, - "open_weights": true, - "knowledge": "2024-06", - "release_date": "2025-08-05", - "last_updated": "2025-08-05", + "attachment": true, + "open_weights": false, + "knowledge": "2025-04", + "release_date": "2026-04-02", + "last_updated": "2026-04-02", "type": "chat" }, { - "id": "MiniMaxAI/MiniMax-M3", - "name": "MiniMax M3", - "display_name": "MiniMax M3", + "id": "qwen/qwen3.8-max", + "name": "Qwen3.8 Max", + "display_name": "Qwen3.8 Max", "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 1000000, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -81216,30 +81451,37 @@ }, "extra_capabilities": { "reasoning": { - "supported": true + "supported": true, + "effort_options": [ + "low", + "medium", + "xhigh" + ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-06-01", - "last_updated": "2026-06-01", + "open_weights": false, + "release_date": "2026-08-03", + "last_updated": "2026-08-03", "type": "chat" }, { - "id": "reflection/Beam-1.0-501B-A23B", - "name": "Beam 1.0 501B A23B", - "display_name": "Beam 1.0 501B A23B", + "id": "qwen/qwen3.7-plus", + "name": "Qwen3.7 Plus", + "display_name": "Qwen3.7 Plus", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 1048576 + "context": 1000000, + "output": 64000 }, "temperature": true, "tool_call": true, @@ -81249,27 +81491,20 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "minimal", - "low", - "medium", - "high", - "xhigh", - "max" - ] + "supported": true } }, - "attachment": false, - "open_weights": true, - "knowledge": "2026-06-30", - "release_date": "2026-10-05", - "last_updated": "2026-10-16", + "attachment": true, + "open_weights": false, + "knowledge": "2025-04", + "release_date": "2026-06-02", + "last_updated": "2026-06-02", "type": "chat" }, { - "id": "moonshotai/Kimi-K2.7-Code", - "name": "Kimi K2.7 Code", - "display_name": "Kimi K2.7 Code", + "id": "deepseek/deepseek-v4.1-flash", + "name": "DeepSeek V4.1 Flash", + "display_name": "DeepSeek V4.1 Flash", "modalities": { "input": [ "text", @@ -81280,8 +81515,8 @@ ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 1000000, + "output": 384000 }, "temperature": true, "tool_call": true, @@ -81291,20 +81526,27 @@ }, "extra_capabilities": { "reasoning": { - "supported": true + "supported": true, + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" + ] } }, "attachment": true, "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-06-12", - "last_updated": "2026-06-12", + "knowledge": "2025-05", + "release_date": "2026-09-10", + "last_updated": "2026-09-10", "type": "chat" }, { - "id": "moonshotai/Kimi-K2.6", - "name": "Kimi K2.6", - "display_name": "Kimi K2.6", + "id": "deepseek/deepseek-v4-flash-vision-exp", + "name": "DeepSeek V4 Flash Vision Exp", + "display_name": "DeepSeek V4 Flash Vision Exp", "modalities": { "input": [ "text", @@ -81315,8 +81557,8 @@ ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 1000000, + "output": 384000 }, "temperature": true, "tool_call": true, @@ -81326,20 +81568,36 @@ }, "extra_capabilities": { "reasoning": { - "supported": true + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "high", + "effort_options": [ + "low", + "high", + "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." + ] } }, "attachment": true, "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-04-20", - "last_updated": "2026-04-20", + "release_date": "2026-08-21", + "last_updated": "2026-09-01", "type": "chat" }, { - "id": "meta-llama/Llama-3.3-70B-Instruct", - "name": "Llama 3.3 70B", - "display_name": "Llama 3.3 70B", + "id": "deepseek/deepseek-v4-pro", + "name": "DeepSeek V4 Pro", + "display_name": "DeepSeek V4 Pro", "modalities": { "input": [ "text" @@ -81349,25 +81607,48 @@ ] }, "limit": { - "context": 128000, - "output": 128000 + "context": 1000000, + "output": 384000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "high", + "effort_options": [ + "low", + "high", + "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." + ] + } }, "attachment": false, "open_weights": true, - "knowledge": "2023-12", - "release_date": "2024-12-06", - "last_updated": "2024-12-06", + "knowledge": "2025-05", + "release_date": "2026-04-24", + "last_updated": "2026-04-24", "type": "chat" }, { - "id": "meta-llama/Llama-3.1-8B-Instruct", - "name": "Llama 3.1 8B", - "display_name": "Llama 3.1 8B", + "id": "deepseek/deepseek-v4-flash", + "name": "DeepSeek V4 Flash", + "display_name": "DeepSeek V4 Flash", "modalities": { "input": [ "text" @@ -81377,29 +81658,53 @@ ] }, "limit": { - "context": 131072, - "output": 131072 + "context": 1000000, + "output": 384000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "high", + "effort_options": [ + "low", + "high", + "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." + ] + } }, "attachment": false, "open_weights": true, - "knowledge": "2023-12", - "release_date": "2024-07-23", - "last_updated": "2024-07-23", + "knowledge": "2025-05", + "release_date": "2026-04-24", + "last_updated": "2026-04-24", "type": "chat" }, { - "id": "deepseek-ai/DeepSeek-V4.1-Flash", - "name": "DeepSeek V4.1 Flash", - "display_name": "DeepSeek V4.1 Flash", + "id": "moonshot/kimi-k3", + "name": "Kimi K3", + "display_name": "Kimi K3", "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ "text" @@ -81409,7 +81714,7 @@ "context": 1048576, "output": 1048576 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -81417,33 +81722,39 @@ }, "extra_capabilities": { "reasoning": { - "supported": true + "supported": true, + "effort_options": [ + "low", + "high", + "max" + ] } }, "attachment": true, "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-09-10", - "last_updated": "2026-09-10", + "release_date": "2026-07-16", + "last_updated": "2026-07-16", "type": "chat" }, { - "id": "deepseek-ai/DeepSeek-V4-Flash-0731", - "name": "DeepSeek V4 Flash 0731", - "display_name": "DeepSeek V4 Flash 0731", + "id": "moonshot/kimi-k2.7-code", + "name": "Kimi K2.7 Code", + "display_name": "Kimi K2.7 Code", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 262000, + "output": 262000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -81454,28 +81765,30 @@ "supported": true } }, - "attachment": false, + "attachment": true, "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-07-31", - "last_updated": "2026-07-31", + "knowledge": "2025-01", + "release_date": "2026-06-12", + "last_updated": "2026-06-12", "type": "chat" }, { - "id": "deepseek-ai/DeepSeek-V4-Pro-0813", - "name": "DeepSeek V4 Pro 0813", - "display_name": "DeepSeek V4 Pro 0813", + "id": "moonshot/kimi-k2.6", + "name": "Kimi K2.6", + "display_name": "Kimi K2.6", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 1048576 + "context": 262000, + "output": 262000 }, "temperature": true, "tool_call": true, @@ -81488,78 +81801,51 @@ "supported": true } }, - "attachment": false, + "attachment": true, "open_weights": true, - "release_date": "2026-08-13", - "last_updated": "2026-08-22", + "knowledge": "2025-01", + "release_date": "2026-04-20", + "last_updated": "2026-04-20", "type": "chat" }, { - "id": "deepseek-ai/DeepSeek-V3.1", - "name": "DeepSeek V3.1", - "display_name": "DeepSeek V3.1", + "id": "x-ai/grok-build-0.1", + "name": "Grok Build 0.1", + "display_name": "Grok Build 0.1", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 161000, - "output": 161000 + "context": 256000, + "output": 256000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false - }, - "attachment": false, - "open_weights": true, - "release_date": "2025-08-21", - "last_updated": "2025-08-21", - "type": "chat" - } - ] - }, - "regolo-ai": { - "id": "regolo-ai", - "name": "Regolo AI", - "display_name": "Regolo AI", - "api": "https://api.regolo.ai/v1", - "doc": "https://docs.regolo.ai/", - "models": [ - { - "id": "qwen-image", - "name": "Qwen-Image", - "display_name": "Qwen-Image", - "modalities": { - "input": [ - "text" - ], - "output": [ - "image" - ] - }, - "limit": { - "context": 8192, - "output": 4096 + "supported": true, + "default": true }, - "temperature": true, - "tool_call": false, - "reasoning": { - "supported": false + "extra_capabilities": { + "reasoning": { + "supported": true + } }, - "attachment": false, + "attachment": true, "open_weights": false, - "release_date": "2026-03-01", - "last_updated": "2026-03-01" + "release_date": "2026-04-16", + "last_updated": "2026-04-16", + "type": "chat" }, { - "id": "qwen3.5-9b", - "name": "Qwen3.5-9B", - "display_name": "Qwen3.5-9B", + "id": "x-ai/grok-4.7", + "name": "Grok 4.7", + "display_name": "Grok 4.7", "modalities": { "input": [ "text", @@ -81570,8 +81856,8 @@ ] }, "limit": { - "context": 262144, - "output": 8192 + "context": 500000, + "output": 500000 }, "temperature": true, "tool_call": true, @@ -81581,38 +81867,37 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "low", + "medium", + "high", + "xhigh" ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-02-23", - "last_updated": "2026-02-23", + "open_weights": false, + "knowledge": "2026-05", + "release_date": "2026-09-21", + "last_updated": "2026-09-21", "type": "chat" }, { - "id": "qwen3.8-27b", - "name": "Qwen3.8 27B", - "display_name": "Qwen3.8 27B", + "id": "x-ai/grok-4.5", + "name": "Grok 4.5", + "display_name": "Grok 4.5", "modalities": { "input": [ "text", - "image", - "video" + "image" ], "output": [ "text" ] }, "limit": { - "context": 120000, - "output": 120000 + "context": 500000, + "output": 500000 }, "temperature": true, "tool_call": true, @@ -81620,27 +81905,38 @@ "supported": true, "default": true }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "effort_options": [ + "low", + "medium", + "high" + ] + } + }, "attachment": true, - "open_weights": true, - "release_date": "2026-08-14", - "last_updated": "2026-08-14", + "open_weights": false, + "release_date": "2026-07-08", + "last_updated": "2026-07-08", "type": "chat" }, { - "id": "gpt-oss-20b", - "name": "GPT-OSS-20B", - "display_name": "GPT-OSS-20B", + "id": "x-ai/grok-4.3", + "name": "Grok 4.3", + "display_name": "Grok 4.3", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 16384 + "context": 1000000, + "output": 1000000 }, "temperature": true, "tool_call": true, @@ -81652,23 +81948,23 @@ "reasoning": { "supported": true, "effort_options": [ + "none", "low", "medium", "high" ] } }, - "attachment": false, - "open_weights": true, - "knowledge": "2024-06", - "release_date": "2025-08-05", - "last_updated": "2025-08-05", + "attachment": true, + "open_weights": false, + "release_date": "2026-04-17", + "last_updated": "2026-04-17", "type": "chat" }, { - "id": "brick-v1-beta", - "name": "Brick v1 Beta", - "display_name": "Brick v1 Beta", + "id": "x-ai/grok-4.6", + "name": "Grok 4.6", + "display_name": "Grok 4.6", "modalities": { "input": [ "text", @@ -81679,63 +81975,92 @@ ] }, "limit": { - "context": 100000, - "output": 15000 + "context": 500000, + "output": 500000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "effort_options": [ + "low", + "medium", + "high", + "xhigh" + ] + } }, "attachment": true, "open_weights": false, - "release_date": "2026-02-06", - "last_updated": "2026-02-06", + "knowledge": "2026-02-01", + "release_date": "2026-08-12", + "last_updated": "2026-08-12", "type": "chat" }, { - "id": "deepseek-ocr-2", - "name": "DeepSeek OCR 2", - "display_name": "DeepSeek OCR 2", + "id": "xiaomi/mimo-v2.6-pro", + "name": "MiMo-V2.6-Pro", + "display_name": "MiMo-V2.6-Pro", "modalities": { "input": [ "text", - "image" + "image", + "audio", + "video" ], "output": [ "text" ] }, "limit": { - "context": 4000, - "output": 4000 + "context": 1048576, + "output": 131072 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "none", + "low", + "medium", + "high" + ] + } }, "attachment": true, "open_weights": true, - "release_date": "2026-01-27", - "last_updated": "2026-01-27", + "release_date": "2026-09-22", + "last_updated": "2026-09-22", "type": "chat" }, { - "id": "qwen3-coder-next", - "name": "Qwen3-Coder-Next", - "display_name": "Qwen3-Coder-Next", + "id": "xiaomi/mimo-v2.6-flash", + "name": "MiMo-V2.6-Flash", + "display_name": "MiMo-V2.6-Flash", "modalities": { "input": [ - "text" + "text", + "image", + "audio", + "video" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 16384 + "context": 1048576, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -81743,17 +82068,35 @@ "supported": true, "default": true }, - "attachment": false, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "none", + "low", + "medium", + "high" + ] + } + }, + "attachment": true, "open_weights": true, - "knowledge": "2025-09", - "release_date": "2026-02-03", - "last_updated": "2026-02-03", + "release_date": "2026-09-22", + "last_updated": "2026-09-22", "type": "chat" - }, + } + ] + }, + "agentrouter": { + "id": "agentrouter", + "name": "AgentRouter", + "display_name": "AgentRouter", + "api": "https://agentrouter.org/v1", + "doc": "https://agentrouter.org/docs/opencode.html", + "models": [ { - "id": "glm5.2", - "name": "GLM-5.2", - "display_name": "GLM-5.2", + "id": "glm-5.3", + "name": "GLM-5.3", + "display_name": "GLM-5.3", "modalities": { "input": [ "text" @@ -81763,8 +82106,8 @@ ] }, "limit": { - "context": 96000, - "output": 96000 + "context": 1000000, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -81774,7 +82117,9 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, "effort_options": [ + "low", "high", "max" ] @@ -81782,79 +82127,125 @@ }, "attachment": false, "open_weights": true, - "release_date": "2026-06-13", - "last_updated": "2026-06-13", + "release_date": "2026-08-14", + "last_updated": "2026-08-14", "type": "chat" }, { - "id": "faster-whisper-large-v3", - "name": "Faster Whisper Large v3", - "display_name": "Faster Whisper Large v3", + "id": "gpt-5.6-sol", + "name": "GPT-5.6 Sol", + "display_name": "GPT-5.6 Sol", "modalities": { "input": [ - "audio" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 448, - "output": 4096 + "context": 1050000, + "output": 128000 }, - "temperature": true, - "tool_call": false, + "temperature": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", + "effort_options": [ + "none", + "low", + "medium", + "high", + "xhigh", + "max" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" + } }, "attachment": true, - "open_weights": true, - "release_date": "2023-11-06", - "last_updated": "2023-11-06" + "open_weights": false, + "knowledge": "2026-02-16", + "release_date": "2026-07-09", + "last_updated": "2026-07-09", + "type": "chat" }, { - "id": "gpt-oss-120b", - "name": "GPT-OSS-120B", - "display_name": "GPT-OSS-120B", + "id": "claude-opus-4-8", + "name": "Claude Opus 4.8", + "display_name": "Claude Opus 4.8", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 16384 + "context": 1000000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": false, + "mode": "effort", + "effort": "high", "effort_options": [ "low", "medium", - "high" + "high", + "xhigh", + "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "omitted", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Claude Opus 4.7 and newer Opus models require thinking.type = \"adaptive\" to enable thinking explicitly.", + "Manual budget_tokens requests return 400 on Claude Opus 4.7 and newer adaptive-only Opus models.", + "task_budget is separate from thinking control and should not be treated as a thinking budget." ] } }, - "attachment": false, - "open_weights": true, - "knowledge": "2024-06", - "release_date": "2025-08-05", - "last_updated": "2025-08-05", + "attachment": true, + "open_weights": false, + "knowledge": "2026-01", + "release_date": "2026-05-28", + "last_updated": "2026-05-28", "type": "chat" }, { - "id": "apertus-70b", - "name": "Apertus 70B", - "display_name": "Apertus 70B", + "id": "deepseek-v4-flash", + "name": "DeepSeek V4 Flash", + "display_name": "DeepSeek V4 Flash", "modalities": { "input": [ "text" @@ -81864,83 +82255,100 @@ ] }, "limit": { - "context": 30000, - "output": 30000 + "context": 1000000, + "output": 384000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "high", + "effort_options": [ + "low", + "high", + "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." + ] + } }, "attachment": false, "open_weights": true, - "knowledge": "2025-09", - "release_date": "2025-09-02", - "last_updated": "2025-09-02", + "knowledge": "2025-05", + "release_date": "2026-04-24", + "last_updated": "2026-04-24", "type": "chat" }, { - "id": "mistral-small-4-119b", - "name": "Mistral Small 4 119B", - "display_name": "Mistral Small 4 119B", + "id": "claude-opus-5", + "name": "Claude Opus 5", + "display_name": "Claude Opus 5", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 256000, - "output": 16384 + "context": 1000000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, "default": true }, - "attachment": true, - "open_weights": true, - "knowledge": "2025-06", - "release_date": "2026-03-16", - "last_updated": "2026-03-16", - "type": "chat" - }, - { - "id": "llama-3.3-70b-instruct", - "name": "Llama 3.3 70B Instruct", - "display_name": "Llama 3.3 70B Instruct", - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 16384 - }, - "temperature": true, - "tool_call": true, - "reasoning": { - "supported": false + "extra_capabilities": { + "reasoning": { + "supported": true, + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" + ] + } }, - "attachment": false, - "open_weights": true, - "knowledge": "2023-12", - "release_date": "2024-12-06", - "last_updated": "2024-12-06", + "attachment": true, + "open_weights": false, + "knowledge": "2026-05", + "release_date": "2026-07-24", + "last_updated": "2026-07-24", "type": "chat" - }, + } + ] + }, + "the-grid-ai": { + "id": "the-grid-ai", + "name": "The Grid AI", + "display_name": "The Grid AI", + "api": "https://api.thegrid.ai/v1", + "doc": "https://thegrid.ai/docs", + "models": [ { - "id": "qwen3.5-122b", - "name": "Qwen3.5-122B", - "display_name": "Qwen3.5-122B", + "id": "code-max", + "name": "Code Max", + "display_name": "Code Max", "modalities": { "input": [ "text", @@ -81951,10 +82359,10 @@ ] }, "limit": { - "context": 262144, - "output": 16384 + "context": 1000000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -81968,19 +82376,26 @@ "visibility": "summary", "continuation": [ "thinking_blocks" + ], + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-02-23", - "last_updated": "2026-02-23", + "open_weights": false, + "release_date": "2026-05-04", + "last_updated": "2026-07-30", "type": "chat" }, { - "id": "gemma4-31b", - "name": "Gemma 4 31B IT", - "display_name": "Gemma 4 31B IT", + "id": "kimi-latest", + "name": "Kimi Latest", + "display_name": "Kimi Latest", "modalities": { "input": [ "text", @@ -81991,131 +82406,10 @@ ] }, "limit": { - "context": 100000, - "output": 100000 + "context": 1048576, + "output": 943718 }, - "temperature": true, - "tool_call": true, - "reasoning": { - "supported": true, - "default": true - }, - "attachment": true, - "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-04-02", - "last_updated": "2026-04-02", - "type": "chat" - }, - { - "id": "brick-complexity-pro", - "name": "Brick Complexity Pro", - "display_name": "Brick Complexity Pro", - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 100000, - "output": 15000 - }, - "temperature": true, - "tool_call": true, - "reasoning": { - "supported": false - }, - "attachment": true, - "open_weights": false, - "release_date": "2026-02-06", - "last_updated": "2026-02-06", - "type": "chat" - }, - { - "id": "qwen3-embedding-8b", - "name": "Qwen3-Embedding-8B", - "display_name": "Qwen3-Embedding-8B", - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 32768, - "output": 8192 - }, - "temperature": false, - "tool_call": false, - "reasoning": { - "supported": false - }, - "attachment": false, - "open_weights": true, - "release_date": "2025-06-05", - "last_updated": "2025-06-05", - "type": "embedding" - }, - { - "id": "qwen3-reranker-4b", - "name": "Qwen3-Reranker-4B", - "display_name": "Qwen3-Reranker-4B", - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 32768, - "output": 8192 - }, - "temperature": false, - "tool_call": false, - "reasoning": { - "supported": false - }, - "attachment": false, - "open_weights": true, - "release_date": "2026-02-01", - "last_updated": "2026-02-01", - "type": "rerank" - } - ] - }, - "hopper": { - "id": "hopper", - "name": "Hopper", - "display_name": "Hopper", - "api": "https://api.withhopper.com/v1", - "doc": "https://withhopper.com/agents/deepseek-v4.1-flash-1017", - "models": [ - { - "id": "deepseek-v4.1-flash-1017", - "name": "DeepSeek V4.1 Flash 1017", - "display_name": "DeepSeek V4.1 Flash 1017", - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 1048576, - "output": 1048576 - }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -82138,28 +82432,26 @@ } }, "attachment": true, - "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-10-17", - "last_updated": "2026-10-17", + "open_weights": false, + "release_date": "2026-07-06", + "last_updated": "2026-07-06", "type": "chat" }, { - "id": "deepseek-v4.1-flash-1017-abliterated", - "name": "DeepSeek V4.1 Flash 1017 Abliterated", - "display_name": "DeepSeek V4.1 Flash 1017 Abliterated", + "id": "text-standard", + "name": "Text Standard", + "display_name": "Text Standard", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 1048576 + "context": 128000, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -82169,33 +82461,30 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ "low", - "high", - "max" + "medium", + "high" ] } }, - "attachment": true, - "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-10-17", - "last_updated": "2026-10-17", + "attachment": false, + "open_weights": false, + "release_date": "2026-02-26", + "last_updated": "2026-10-06", "type": "chat" - } - ] - }, - "poolside": { - "id": "poolside", - "name": "Poolside", - "display_name": "Poolside", - "api": "https://inference.poolside.ai/v1", - "doc": "https://platform.poolside.ai", - "models": [ + }, { - "id": "poolside/laguna-s-2.1", - "name": "Laguna S 2.1", - "display_name": "Laguna S 2.1", + "id": "deepseek-pro-latest", + "name": "DeepSeek Pro Latest", + "display_name": "DeepSeek Pro Latest", "modalities": { "input": [ "text" @@ -82205,10 +82494,10 @@ ] }, "limit": { - "context": 1048576, - "output": 32768 + "context": 1000000, + "output": 384000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -82222,32 +82511,38 @@ "visibility": "summary", "continuation": [ "thinking_blocks" + ], + "effort_options": [ + "low", + "medium", + "high" ] } }, "attachment": false, - "open_weights": true, - "release_date": "2026-07-21", - "last_updated": "2026-07-21", + "open_weights": false, + "release_date": "2026-07-06", + "last_updated": "2026-07-06", "type": "chat" }, { - "id": "poolside/laguna-m.1", - "name": "Laguna M.1", - "display_name": "Laguna M.1", + "id": "claude-opus-latest", + "name": "Claude Opus Latest", + "display_name": "Claude Opus Latest", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 32768 + "context": 1000000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -82261,19 +82556,26 @@ "visibility": "summary", "continuation": [ "thinking_blocks" + ], + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" ] } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-04-28", - "last_updated": "2026-06-13", + "attachment": true, + "open_weights": false, + "release_date": "2026-07-06", + "last_updated": "2026-07-30", "type": "chat" }, { - "id": "poolside/laguna-xs-2.1", - "name": "Laguna XS 2.1", - "display_name": "Laguna XS 2.1", + "id": "agent-standard", + "name": "Agent Standard", + "display_name": "Agent Standard", "modalities": { "input": [ "text" @@ -82283,8 +82585,8 @@ ] }, "limit": { - "context": 262144, - "output": 32768 + "context": 128000, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -82300,28 +82602,24 @@ "visibility": "summary", "continuation": [ "thinking_blocks" + ], + "effort_options": [ + "low", + "medium", + "high" ] } }, "attachment": false, - "open_weights": true, - "release_date": "2026-07-02", - "last_updated": "2026-07-02", + "open_weights": false, + "release_date": "2026-05-04", + "last_updated": "2026-10-06", "type": "chat" - } - ] - }, - "routing-run": { - "id": "routing-run", - "name": "routing.run", - "display_name": "routing.run", - "api": "https://api.routing.run/v1", - "doc": "https://docs.routing.run/api-reference/models", - "models": [ + }, { - "id": "glm-5.2-nitro", - "name": "GLM 5.2 Nitro", - "display_name": "GLM 5.2 Nitro", + "id": "minimax-latest", + "name": "MiniMax Latest", + "display_name": "MiniMax Latest", "modalities": { "input": [ "text" @@ -82331,10 +82629,10 @@ ] }, "limit": { - "context": 200000, - "output": 32000 + "context": 1000000, + "output": 512000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -82348,34 +82646,38 @@ "visibility": "summary", "continuation": [ "thinking_blocks" + ], + "effort_options": [ + "low", + "medium", + "high" ] } }, "attachment": false, - "open_weights": true, - "release_date": "2026-06-13", - "last_updated": "2026-06-13", + "open_weights": false, + "release_date": "2026-07-06", + "last_updated": "2026-07-06", "type": "chat" }, { - "id": "kimi-k2.6-nitro", - "name": "Kimi K2.6 Nitro", - "display_name": "Kimi K2.6 Nitro", + "id": "agent-max", + "name": "Agent Max", + "display_name": "Agent Max", "modalities": { "input": [ "text", - "image", - "video" + "image" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 32000 + "context": 1000000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -82389,35 +82691,40 @@ "visibility": "summary", "continuation": [ "thinking_blocks" + ], + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" ] } }, "attachment": true, - "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-04-20", - "last_updated": "2026-04-20", + "open_weights": false, + "release_date": "2026-05-04", + "last_updated": "2026-07-30", "type": "chat" }, { - "id": "qwen3.5-9b", - "name": "Qwen3.5 9B", - "display_name": "Qwen3.5 9B", + "id": "text-max", + "name": "Text Max", + "display_name": "Text Max", "modalities": { "input": [ "text", - "image", - "video" + "image" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 32000 + "context": 1000000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -82431,32 +82738,37 @@ "visibility": "summary", "continuation": [ "thinking_blocks" + ], + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-02-23", - "last_updated": "2026-02-23", + "open_weights": false, + "release_date": "2026-02-26", + "last_updated": "2026-07-30", "type": "chat" }, { - "id": "kimi-k2.7-code-nitro", - "name": "Kimi K2.7 Code Nitro", - "display_name": "Kimi K2.7 Code Nitro", + "id": "bytedance-pro-latest", + "name": "ByteDance Pro Latest", + "display_name": "ByteDance Pro Latest", "modalities": { "input": [ - "text", - "image", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 32000 + "context": 262144, + "output": 131072 }, "temperature": false, "tool_call": true, @@ -82472,20 +82784,24 @@ "visibility": "summary", "continuation": [ "thinking_blocks" + ], + "effort_options": [ + "low", + "medium", + "high" ] } }, - "attachment": true, - "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-06-12", - "last_updated": "2026-06-12", + "attachment": false, + "open_weights": false, + "release_date": "2026-07-06", + "last_updated": "2026-07-06", "type": "chat" }, { - "id": "deepseek-v4-flash", - "name": "DeepSeek V4 Flash", - "display_name": "DeepSeek V4 Flash", + "id": "text-prime", + "name": "Text Prime", + "display_name": "Text Prime", "modalities": { "input": [ "text" @@ -82495,8 +82811,8 @@ ] }, "limit": { - "context": 1000000, - "output": 64000 + "context": 196608, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -82507,36 +82823,29 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "high", - "effort_options": [ - "low", - "high", - "max" - ], "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ "thinking_blocks" ], - "notes": [ - "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." + "effort_options": [ + "low", + "medium", + "high" ] } }, "attachment": false, - "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-04-24", - "last_updated": "2026-04-24", + "open_weights": false, + "release_date": "2026-02-26", + "last_updated": "2026-07-15", "type": "chat" }, { - "id": "deepseek-v4-pro", - "name": "DeepSeek V4 Pro", - "display_name": "DeepSeek V4 Pro", + "id": "agent-prime", + "name": "Agent Prime", + "display_name": "Agent Prime", "modalities": { "input": [ "text" @@ -82546,8 +82855,8 @@ ] }, "limit": { - "context": 1000000, - "output": 64000 + "context": 196608, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -82558,51 +82867,43 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "high", - "effort_options": [ - "low", - "high", - "max" - ], "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ "thinking_blocks" ], - "notes": [ - "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." + "effort_options": [ + "low", + "medium", + "high" ] } }, "attachment": false, - "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-04-24", - "last_updated": "2026-04-24", + "open_weights": false, + "release_date": "2026-05-04", + "last_updated": "2026-07-15", "type": "chat" }, { - "id": "kimi-k2.6", - "name": "Kimi K2.6", - "display_name": "Kimi K2.6", + "id": "gemini-pro-latest", + "name": "Gemini Pro Latest", + "display_name": "Gemini Pro Latest", "modalities": { "input": [ "text", - "image", - "video" + "image" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 32000 + "context": 1000000, + "output": 65536 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -82616,35 +82917,37 @@ "visibility": "summary", "continuation": [ "thinking_blocks" + ], + "effort_options": [ + "low", + "medium", + "high" ] } }, "attachment": true, - "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-04-20", - "last_updated": "2026-04-20", + "open_weights": false, + "release_date": "2026-07-06", + "last_updated": "2026-07-06", "type": "chat" }, { - "id": "gpt-5.6-sol", - "name": "GPT-5.6 Sol", - "display_name": "GPT-5.6 Sol", + "id": "code-prime", + "name": "Code Prime", + "display_name": "Code Prime", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 196608, + "output": 131072 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -82653,47 +82956,33 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", - "effort_options": [ - "none", - "low", - "medium", - "high", - "xhigh", - "max" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ "thinking_blocks" + ], + "effort_options": [ + "low", + "medium", + "high" ] } }, - "attachment": true, + "attachment": false, "open_weights": false, - "knowledge": "2026-02-16", - "release_date": "2026-07-09", - "last_updated": "2026-07-09", + "release_date": "2026-05-04", + "last_updated": "2026-07-15", "type": "chat" }, { - "id": "claude-sonnet-4-6", - "name": "Claude Sonnet 4.6", - "display_name": "Claude Sonnet 4.6", + "id": "gpt-sol-latest", + "name": "GPT Sol Latest", + "display_name": "GPT Sol Latest", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" @@ -82701,68 +82990,53 @@ }, "limit": { "context": 1000000, - "output": 64000 + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "mixed", - "budget": { - "min": 1024, - "unit": "tokens" - }, - "effort": "high", - "effort_options": [ - "low", - "medium", - "high", - "max" - ], "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ "thinking_blocks" ], - "notes": [ - "Anthropic recommends adaptive thinking with effort for Claude 4.6; budget_tokens remains a deprecated compatibility path.", - "Anthropic API defaults effort to high; lower effort levels should be chosen per workload." + "effort_options": [ + "low", + "medium", + "high" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-02-17", - "last_updated": "2026-03-13", + "release_date": "2026-07-06", + "last_updated": "2026-07-06", "type": "chat" }, { - "id": "gpt-5.6-terra", - "name": "GPT-5.6 Terra", - "display_name": "GPT-5.6 Terra", + "id": "code-standard", + "name": "Code Standard", + "display_name": "Code Standard", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 128000, + "output": 65536 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -82771,55 +83045,40 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", - "effort_options": [ - "none", - "low", - "medium", - "high", - "xhigh", - "max" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ "thinking_blocks" + ], + "effort_options": [ + "low", + "medium", + "high" ] } }, - "attachment": true, + "attachment": false, "open_weights": false, - "knowledge": "2026-02-16", - "release_date": "2026-07-09", - "last_updated": "2026-07-09", + "release_date": "2026-05-04", + "last_updated": "2026-10-06", "type": "chat" }, { - "id": "kimi-k2.7-code", - "name": "Kimi K2.7 Code", - "display_name": "Kimi K2.7 Code", + "id": "glm-latest", + "name": "GLM Latest", + "display_name": "GLM Latest", "modalities": { "input": [ - "text", - "image", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 32000 + "context": 1048576, + "output": 131072 }, "temperature": false, "tool_call": true, @@ -82835,35 +83094,46 @@ "visibility": "summary", "continuation": [ "thinking_blocks" + ], + "effort_options": [ + "low", + "high", + "max" ] } }, - "attachment": true, - "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-06-12", - "last_updated": "2026-06-12", + "attachment": false, + "open_weights": false, + "release_date": "2026-07-06", + "last_updated": "2026-08-18", "type": "chat" - }, + } + ] + }, + "amd": { + "id": "amd", + "name": "AMD", + "display_name": "AMD", + "api": "https://developer.amd.com.cn/radeon/api/v1", + "doc": "https://developer.amd.com.cn/radeon/tokenfactory", + "models": [ { - "id": "gpt-5.6-luna", - "name": "GPT-5.6 Luna", - "display_name": "GPT-5.6 Luna", + "id": "GLM-5.3-Flash", + "name": "GLM-5.3-Flash", + "display_name": "GLM-5.3-Flash", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 262144, + "output": 131072 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -82872,53 +83142,35 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", "effort_options": [ - "none", "low", - "medium", "high", - "xhigh", "max" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-02-16", - "release_date": "2026-07-09", - "last_updated": "2026-07-09", + "attachment": false, + "open_weights": true, + "release_date": "2026-08-26", + "last_updated": "2026-08-26", "type": "chat" }, { - "id": "nemotron-3-ultra", - "name": "Nemotron 3 Ultra 550B A55B", - "display_name": "Nemotron 3 Ultra 550B A55B", + "id": "Gemma-4-31B-IT", + "name": "Gemma 4 31B IT", + "display_name": "Gemma 4 31B IT", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 32000 + "context": 262144, + "output": 32768 }, "temperature": true, "tool_call": true, @@ -82928,94 +83180,60 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] + "supported": true } }, - "attachment": false, + "attachment": true, "open_weights": true, - "knowledge": "2025-09", - "release_date": "2026-06-04", - "last_updated": "2026-06-04", + "knowledge": "2025-01", + "release_date": "2026-04-02", + "last_updated": "2026-04-02", "type": "chat" }, { - "id": "claude-opus-4-8", - "name": "Claude Opus 4.8", - "display_name": "Claude Opus 4.8", + "id": "MiniCPM5-2B", + "name": "MiniCPM5-2B", + "display_name": "MiniCPM5-2B", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 32000 + "context": 131072, + "output": 131072 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "high", - "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Claude Opus 4.7 and newer Opus models require thinking.type = \"adaptive\" to enable thinking explicitly.", - "Manual budget_tokens requests return 400 on Claude Opus 4.7 and newer adaptive-only Opus models.", - "task_budget is separate from thinking control and should not be treated as a thinking budget." - ] - } + "default": true }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-01", - "release_date": "2026-05-28", - "last_updated": "2026-05-28", + "attachment": false, + "open_weights": true, + "release_date": "2026-09-06", + "last_updated": "2026-09-12", "type": "chat" }, { - "id": "glm-5.2", - "name": "GLM-5.2", - "display_name": "GLM-5.2", + "id": "MiniCPM-V-4.7-35B-A3B", + "name": "MiniCPM-V 4.7 35B-A3B", + "display_name": "MiniCPM-V 4.7 35B-A3B", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 32000 + "context": 262144, + "output": 262144 }, "temperature": true, "tool_call": true, @@ -83023,78 +83241,49 @@ "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } - }, - "attachment": false, + "attachment": true, "open_weights": true, - "release_date": "2026-06-13", - "last_updated": "2026-06-13", + "release_date": "2026-10-07", + "last_updated": "2026-10-07", "type": "chat" - } - ] - }, - "lynkr": { - "id": "lynkr", - "name": "Lynkr", - "display_name": "Lynkr", - "api": "http://127.0.0.1:8081/v1", - "doc": "https://github.com/Fast-Editor/Lynkr", - "models": [ + }, { - "id": "lynkr-auto", - "name": "Lynkr Auto (complexity routing)", - "display_name": "Lynkr Auto (complexity routing)", + "id": "MiniCPM-V-4.7-1B", + "name": "MiniCPM-V 4.7 1B", + "display_name": "MiniCPM-V 4.7 1B", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 8192 + "context": 262144, + "output": 262144 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, - "open_weights": false, - "release_date": "2025-12-03", - "last_updated": "2026-07-11", + "attachment": true, + "open_weights": true, + "release_date": "2026-10-06", + "last_updated": "2026-10-06", "type": "chat" - } - ] - }, - "google-vertex": { - "id": "google-vertex", - "name": "Vertex", - "display_name": "Vertex", - "doc": "https://cloud.google.com/vertex-ai/generative-ai/docs/models", - "models": [ + }, { - "id": "gemini-3-flash-preview", - "name": "Gemini 3 Flash Preview", - "display_name": "Gemini 3 Flash Preview", + "id": "DeepSeek-V4.1-Flash", + "name": "DeepSeek V4.1 Flash", + "display_name": "DeepSeek V4.1 Flash", "modalities": { "input": [ "text", - "image", - "video", - "audio", - "pdf" + "image" ], "output": [ "text" @@ -83102,7 +83291,7 @@ }, "limit": { "context": 1048576, - "output": 65536 + "output": 384000 }, "temperature": true, "tool_call": true, @@ -83113,48 +83302,40 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "level", - "level": "high", - "level_options": [ + "effort_options": [ + "none", "minimal", "low", "medium", - "high" - ], - "summaries": true, - "visibility": "summary", - "continuation": [ - "thought_signatures" + "high", + "xhigh", + "max" ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2025-01", - "release_date": "2025-12-17", - "last_updated": "2025-12-17", + "open_weights": true, + "knowledge": "2025-05", + "release_date": "2026-10-17", + "last_updated": "2026-10-17", "type": "chat" }, { - "id": "gemini-2.5-pro", - "name": "Gemini 2.5 Pro", - "display_name": "Gemini 2.5 Pro", + "id": "DeepSeek-V4-Flash-Vision-Exp", + "name": "DeepSeek V4 Flash Vision Exp", + "display_name": "DeepSeek V4 Flash Vision Exp", "modalities": { "input": [ "text", - "image", - "audio", - "video", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 1000000, + "output": 384000 }, "temperature": true, "tool_call": true, @@ -83166,77 +83347,84 @@ "reasoning": { "supported": true, "default_enabled": true, - "mode": "budget", - "budget": { - "default": -1, - "min": 128, - "max": 32768, - "auto": -1, - "unit": "tokens" - }, + "mode": "effort", + "effort": "high", + "effort_options": [ + "low", + "high", + "max" + ], + "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ - "thought_signatures" + "thinking_blocks" + ], + "notes": [ + "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2025-01", - "release_date": "2025-06-17", - "last_updated": "2025-06-17", + "open_weights": true, + "release_date": "2026-08-21", + "last_updated": "2026-09-01", "type": "chat" }, { - "id": "gemini-2.5-flash-image", - "name": "Nano Banana", - "display_name": "Nano Banana", + "id": "Qwen3.8-Flash-Next", + "name": "Qwen3.8 Flash Next", + "display_name": "Qwen3.8 Flash Next", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ - "text", - "image" + "text" ] }, "limit": { - "context": 32768, - "output": 32768 + "context": 262144, + "output": 131072 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "low", + "medium", + "xhigh" + ] + } }, "attachment": true, - "open_weights": false, - "knowledge": "2024-06", - "release_date": "2025-08-26", - "last_updated": "2025-08-26" + "open_weights": true, + "release_date": "2026-08-27", + "last_updated": "2026-08-27", + "type": "chat" }, { - "id": "gemini-3.5-flash-lite", - "name": "Gemini 3.5 Flash Lite", - "display_name": "Gemini 3.5 Flash Lite", + "id": "Qwen3.8-27B", + "name": "Qwen3.8 27B", + "display_name": "Qwen3.8 27B", "modalities": { "input": [ "text", - "image", - "video", - "audio", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 262144, + "output": 32768 }, "temperature": true, "tool_call": true, @@ -83247,25 +83435,23 @@ "extra_capabilities": { "reasoning": { "supported": true, + "interleaved": true, "effort_options": [ - "minimal", "low", - "medium", - "high" + "medium" ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2026-03", - "release_date": "2026-07-21", - "last_updated": "2026-07-21", + "open_weights": true, + "release_date": "2026-08-14", + "last_updated": "2026-08-14", "type": "chat" }, { - "id": "mistral-medium-3", - "name": "Mistral Medium 3", - "display_name": "Mistral Medium 3", + "id": "MiMo-V2.6-Flash", + "name": "MiMo-V2.6-Flash", + "display_name": "MiMo-V2.6-Flash", "modalities": { "input": [ "text", @@ -83276,25 +83462,25 @@ ] }, "limit": { - "context": 131072, + "context": 1048576, "output": 131072 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, "attachment": true, - "open_weights": false, - "knowledge": "2025-05", - "release_date": "2025-05-07", - "last_updated": "2025-05-07", + "open_weights": true, + "release_date": "2026-09-22", + "last_updated": "2026-09-22", "type": "chat" }, { - "id": "codestral-2", - "name": "Codestral 2", - "display_name": "Codestral 2", + "id": "DeepSeek-V4-Flash", + "name": "DeepSeek V4 Flash 0731", + "display_name": "DeepSeek V4 Flash 0731", "modalities": { "input": [ "text" @@ -83304,42 +83490,11 @@ ] }, "limit": { - "context": 128000, - "output": 8192 + "context": 1048576, + "output": 384000 }, "temperature": true, "tool_call": true, - "reasoning": { - "supported": false - }, - "attachment": false, - "open_weights": false, - "release_date": "2025-07-30", - "last_updated": "2025-07-30", - "type": "chat" - }, - { - "id": "gemini-3.1-flash-image", - "name": "Nano Banana 2", - "display_name": "Nano Banana 2", - "modalities": { - "input": [ - "text", - "image", - "video", - "pdf" - ], - "output": [ - "text", - "image" - ] - }, - "limit": { - "context": 131072, - "output": 32768 - }, - "temperature": true, - "tool_call": false, "reasoning": { "supported": true, "default": true @@ -83347,22 +83502,45 @@ "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "high", "effort_options": [ - "minimal", - "high" + "low", + "high", + "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-01", - "release_date": "2026-05-28", - "last_updated": "2026-05-28" - }, + "attachment": false, + "open_weights": true, + "knowledge": "2025-05", + "release_date": "2026-07-31", + "last_updated": "2026-07-31", + "type": "chat" + } + ] + }, + "engy": { + "id": "engy", + "name": "engy", + "display_name": "engy", + "api": "https://api.engy.ai/v1", + "doc": "https://engy.ai/pricing", + "models": [ { - "id": "mistral-small-2503", - "name": "Mistral Small 3.1 24B", - "display_name": "Mistral Small 3.1 24B", + "id": "kimi-k3", + "name": "Kimi K3", + "display_name": "Kimi K3", "modalities": { "input": [ "text", @@ -83373,67 +83551,47 @@ ] }, "limit": { - "context": 128000, - "output": 16384 + "context": 1113088, + "output": 65536 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } }, "attachment": true, "open_weights": true, - "knowledge": "2024-06", - "release_date": "2025-03-17", - "last_updated": "2025-03-17", + "release_date": "2026-07-16", + "last_updated": "2026-07-16", "type": "chat" }, { - "id": "gemini-2.5-flash-tts", - "name": "Gemini 2.5 Flash TTS", - "display_name": "Gemini 2.5 Flash TTS", + "id": "qwen3.6-35b-a3b", + "name": "Qwen3.6 35B-A3B", + "display_name": "Qwen3.6 35B-A3B", "modalities": { "input": [ "text" ], - "output": [ - "audio" - ] - }, - "limit": { - "context": 32768, - "output": 16384 - }, - "temperature": false, - "tool_call": false, - "reasoning": { - "supported": false - }, - "attachment": false, - "open_weights": false, - "knowledge": "2025-01", - "release_date": "2025-09-30", - "last_updated": "2025-12-10" - }, - { - "id": "gemini-2.5-flash", - "name": "Gemini 2.5 Flash", - "display_name": "Gemini 2.5 Flash", - "modalities": { - "input": [ - "text", - "image", - "audio", - "video", - "pdf" - ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 208192, + "output": 8192 }, "temperature": true, "tool_call": true, @@ -83444,41 +83602,28 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "budget", - "budget": { - "default": -1, - "min": 0, - "max": 24576, - "auto": -1, - "off": 0, - "unit": "tokens" - }, + "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ - "thought_signatures" + "thinking_blocks" ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-01", - "release_date": "2025-06-17", - "last_updated": "2025-06-17", + "attachment": false, + "open_weights": true, + "release_date": "2026-04-17", + "last_updated": "2026-04-17", "type": "chat" }, { - "id": "gemini-3.1-flash-lite", - "name": "Gemini 3.1 Flash Lite", - "display_name": "Gemini 3.1 Flash Lite", + "id": "deepseek-v4.1-flash-1017", + "name": "DeepSeek V4.1 Flash 1017", + "display_name": "DeepSeek V4.1 Flash 1017", "modalities": { "input": [ "text", - "image", - "video", - "audio", - "pdf" + "image" ], "output": [ "text" @@ -83497,40 +83642,42 @@ "extra_capabilities": { "reasoning": { "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ - "minimal", "low", - "medium", - "high" + "high", + "max" ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2025-01", - "release_date": "2026-05-07", - "last_updated": "2026-05-07", + "open_weights": true, + "knowledge": "2025-05", + "release_date": "2026-10-17", + "last_updated": "2026-10-17", "type": "chat" }, { - "id": "gemini-3.7-flash", - "name": "Gemini 3.7 Flash", - "display_name": "Gemini 3.7 Flash", + "id": "glm-5.3-flash", + "name": "GLM-5.3-Flash", + "display_name": "GLM-5.3-Flash", "modalities": { "input": [ "text", - "image", - "video", - "audio", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 327680, + "output": 32768 }, "temperature": true, "tool_call": true, @@ -83541,50 +83688,44 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "level", - "level": "high", - "level_options": [ - "minimal", - "low", - "medium", - "high" - ], + "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ - "thought_signatures" + "thinking_blocks" + ], + "effort_options": [ + "low", + "high", + "max" ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2026-03", - "release_date": "2026-08-13", - "last_updated": "2026-08-13", + "open_weights": true, + "release_date": "2026-08-26", + "last_updated": "2026-08-26", "type": "chat" }, { - "id": "gemini-3-pro-image", - "name": "Nano Banana Pro", - "display_name": "Nano Banana Pro", + "id": "deepseek-v4.1-flash", + "name": "DeepSeek V4.1 Flash", + "display_name": "DeepSeek V4.1 Flash", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ - "text", - "image" + "text" ] }, "limit": { - "context": 65536, - "output": 32768 + "context": 1048576, + "output": 65536 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { "supported": true, "default": true @@ -83592,44 +83733,41 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "level", - "level": "high", - "level_options": [ - "low", - "high" - ], + "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ - "thought_signatures" + "thinking_blocks" + ], + "effort_options": [ + "low", + "high", + "max" ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2025-01", - "release_date": "2026-05-28", - "last_updated": "2026-05-28" + "open_weights": true, + "knowledge": "2025-05", + "release_date": "2026-09-10", + "last_updated": "2026-09-10", + "type": "chat" }, { - "id": "gemini-3.1-pro-preview-customtools", - "name": "Gemini 3.1 Pro Preview Custom Tools", - "display_name": "Gemini 3.1 Pro Preview Custom Tools", + "id": "qwen3.8-27b", + "name": "Qwen3.8 27B", + "display_name": "Qwen3.8 27B", "modalities": { "input": [ "text", - "image", - "video", - "audio", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1048576, + "context": 1001536, "output": 65536 }, "temperature": true, @@ -83641,46 +83779,40 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "level", - "level": "high", - "level_options": [ - "low", - "high" - ], + "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ - "thought_signatures" + "thinking_blocks" + ], + "effort_options": [ + "low", + "medium", + "xhigh" ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2025-01", - "release_date": "2026-02-19", - "last_updated": "2026-02-19", + "open_weights": true, + "release_date": "2026-08-14", + "last_updated": "2026-08-14", "type": "chat" }, { - "id": "gemini-3.1-flash-lite-image", - "name": "Nano Banana 2 Lite", - "display_name": "Nano Banana 2 Lite", + "id": "glm-5.3", + "name": "GLM-5.3", + "display_name": "GLM-5.3", "modalities": { "input": [ - "text", - "image", - "video", - "pdf" + "text" ], "output": [ - "text", - "image" + "text" ] }, "limit": { - "context": 65536, - "output": 4096 + "context": 327680, + "output": 32768 }, "temperature": true, "tool_call": true, @@ -83691,37 +83823,40 @@ "extra_capabilities": { "reasoning": { "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ - "minimal", - "high" + "low", + "high", + "max" ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-01", - "release_date": "2026-06-30", - "last_updated": "2026-06-30" + "attachment": false, + "open_weights": true, + "release_date": "2026-08-14", + "last_updated": "2026-08-14", + "type": "chat" }, { - "id": "gemini-3.1-pro-preview", - "name": "Gemini 3.1 Pro Preview", - "display_name": "Gemini 3.1 Pro Preview", + "id": "glm-5.2", + "name": "GLM-5.2", + "display_name": "GLM-5.2", "modalities": { "input": [ - "text", - "image", - "video", - "audio", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 262144, + "output": 32768 }, "temperature": true, "tool_call": true, @@ -83732,38 +83867,31 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "level", - "level": "high", - "level_options": [ - "low", - "high" - ], + "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ - "thought_signatures" + "thinking_blocks" + ], + "effort_options": [ + "high", + "max" ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-01", - "release_date": "2026-02-19", - "last_updated": "2026-02-19", + "attachment": false, + "open_weights": true, + "release_date": "2026-06-13", + "last_updated": "2026-06-13", "type": "chat" }, { - "id": "gemini-2.5-flash-lite", - "name": "Gemini 2.5 Flash-Lite", - "display_name": "Gemini 2.5 Flash-Lite", + "id": "deepseek-v4-flash-0731", + "name": "DeepSeek V4 Flash 0731", + "display_name": "DeepSeek V4 Flash 0731", "modalities": { "input": [ - "text", - "image", - "audio", - "video", - "pdf" + "text" ], "output": [ "text" @@ -83771,59 +83899,62 @@ }, "limit": { "context": 1048576, - "output": 65536 + "output": 128000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "budget", - "budget": { - "default": -1, - "min": 512, - "max": 24576, - "auto": -1, - "unit": "tokens" - }, + "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ - "thought_signatures" + "thinking_blocks" + ], + "effort_options": [ + "low", + "high", + "max" ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-01", - "release_date": "2025-06-17", - "last_updated": "2025-06-17", + "attachment": false, + "open_weights": true, + "knowledge": "2025-05", + "release_date": "2026-07-31", + "last_updated": "2026-07-31", "type": "chat" - }, + } + ] + }, + "neosmith": { + "id": "neosmith", + "name": "NeoSmith", + "display_name": "NeoSmith", + "api": "https://router.neosmith.ai/v1", + "doc": "https://neosmith.ai/docs", + "models": [ { - "id": "gemini-3.8-flash", - "name": "Gemini 3.8 Flash", - "display_name": "Gemini 3.8 Flash", + "id": "neosmith.intelligent-pro", + "name": "NeoSmith Pro", + "display_name": "NeoSmith Pro", "modalities": { "input": [ "text", - "image", - "video", - "audio", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 1000000, + "output": 128000 }, "temperature": true, "tool_call": true, @@ -83831,92 +83962,57 @@ "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "level", - "level": "high", - "level_options": [ - "minimal", - "low", - "medium", - "high" - ], - "summaries": true, - "visibility": "summary", - "continuation": [ - "thought_signatures" - ] - } - }, "attachment": true, "open_weights": false, - "release_date": "2026-09-02", - "last_updated": "2026-09-02", + "release_date": "2026-04-26", + "last_updated": "2026-07-18", "type": "chat" }, { - "id": "gemini-4-argon", - "name": "Gemini 4 Argon", - "display_name": "Gemini 4 Argon", + "id": "neosmith.neolite", + "name": "NeoSmith NeoLite", + "display_name": "NeoSmith NeoLite", "modalities": { "input": [ "text", - "image", - "video", - "audio", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 512000, + "output": 64000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "low", - "medium", - "high" - ] - } - }, "attachment": true, "open_weights": false, - "knowledge": "2026-03", - "release_date": "2026-10-13", - "last_updated": "2026-10-13", + "release_date": "2026-06-20", + "last_updated": "2026-08-03", "type": "chat" }, { - "id": "gemini-3.6-flash", - "name": "Gemini 3.6 Flash", - "display_name": "Gemini 3.6 Flash", + "id": "neosmith.intelligent-basic", + "name": "NeoSmith Basic", + "display_name": "NeoSmith Basic", "modalities": { "input": [ "text", - "image", - "video", - "audio", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 1000000, + "output": 128000 }, "temperature": true, "tool_call": true, @@ -83924,79 +84020,65 @@ "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "level", - "level": "high", - "level_options": [ - "minimal", - "low", - "medium", - "high" - ], - "summaries": true, - "visibility": "summary", - "continuation": [ - "thought_signatures" - ] - } - }, "attachment": true, "open_weights": false, - "knowledge": "2026-03", - "release_date": "2026-07-21", - "last_updated": "2026-07-21", + "release_date": "2026-04-26", + "last_updated": "2026-08-03", "type": "chat" }, { - "id": "gemini-embedding-001", - "name": "Gemini Embedding 001", - "display_name": "Gemini Embedding 001", + "id": "neosmith.intelligent-maestro", + "name": "NeoSmith Maestro", + "display_name": "NeoSmith Maestro", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 2048, - "output": 1 + "context": 1000000, + "output": 128000 }, - "temperature": false, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, + "attachment": true, "open_weights": false, - "knowledge": "2025-05", - "release_date": "2025-05-20", - "last_updated": "2025-05-20", - "type": "embedding" - }, + "release_date": "2026-08-03", + "last_updated": "2026-08-08", + "type": "chat" + } + ] + }, + "ebcloud": { + "id": "ebcloud", + "name": "EBCloud", + "display_name": "EBCloud", + "api": "https://maas-api.ebcloud.com/v1", + "doc": "https://docs.ebtech.com/ai/model-api.html", + "models": [ { - "id": "gemini-3.5-flash", - "name": "Gemini 3.5 Flash", - "display_name": "Gemini 3.5 Flash", + "id": "DeepSeek-V4-Pro", + "name": "DeepSeek V4 Pro", + "display_name": "DeepSeek V4 Pro", "modalities": { "input": [ - "text", - "image", - "video", - "audio", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 1000000, + "output": 384000 }, "temperature": true, "tool_call": true, @@ -84008,46 +84090,49 @@ "reasoning": { "supported": true, "default_enabled": true, - "mode": "level", - "level": "high", - "level_options": [ - "minimal", + "mode": "effort", + "effort": "high", + "effort_options": [ "low", - "medium", - "high" + "high", + "max" ], + "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ - "thought_signatures" + "thinking_blocks" + ], + "notes": [ + "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-01", - "release_date": "2026-05-19", - "last_updated": "2026-05-19", + "attachment": false, + "open_weights": true, + "knowledge": "2025-05", + "release_date": "2026-04-24", + "last_updated": "2026-04-24", "type": "chat" }, { - "id": "gemini-2.5-pro-tts", - "name": "Gemini 2.5 Pro TTS", - "display_name": "Gemini 2.5 Pro TTS", + "id": "GLM-5.1", + "name": "GLM-5.1", + "display_name": "GLM-5.1", "modalities": { "input": [ "text" ], "output": [ - "audio" + "text" ] }, "limit": { - "context": 32768, - "output": 16384 + "context": 200000, + "output": 131072 }, - "temperature": false, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { "supported": true, "default": true @@ -84055,72 +84140,64 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "budget", - "budget": { - "default": -1, - "min": 128, - "max": 32768, - "auto": -1, - "unit": "tokens" - }, + "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ - "thought_signatures" + "thinking_blocks" ] } }, "attachment": false, - "open_weights": false, - "knowledge": "2025-01", - "release_date": "2025-09-30", - "last_updated": "2025-12-10" + "open_weights": true, + "release_date": "2026-04-07", + "last_updated": "2026-04-07", + "type": "chat" }, { - "id": "gemini-nano-banana-2.1", - "name": "Nano Banana 2.1", - "display_name": "Nano Banana 2.1", + "id": "Kimi-K2.6", + "name": "Kimi K2.6", + "display_name": "Kimi K2.6", "modalities": { "input": [ - "text", - "image", - "video", - "pdf" + "text" ], "output": [ - "text", - "image" + "text" ] }, "limit": { - "context": 131072, - "output": 32768 + "context": 262144, + "output": 262144 }, - "temperature": false, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { "supported": true, "default": true }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "minimal", - "medium", - "high" + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, - "attachment": true, - "open_weights": false, - "release_date": "2026-10-06", - "last_updated": "2026-10-06" + "attachment": false, + "open_weights": true, + "knowledge": "2025-01", + "release_date": "2026-04-20", + "last_updated": "2026-04-20", + "type": "chat" }, { - "id": "zai-org/glm-4.7-maas", - "name": "GLM-4.7", - "display_name": "GLM-4.7", + "id": "DeepSeek-V4-Flash", + "name": "DeepSeek V4 Flash", + "display_name": "DeepSeek V4 Flash", "modalities": { "input": [ "text" @@ -84130,8 +84207,8 @@ ] }, "limit": { - "context": 200000, - "output": 128000 + "context": 1000000, + "output": 384000 }, "temperature": true, "tool_call": true, @@ -84142,36 +84219,57 @@ "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "high", + "effort_options": [ + "low", + "high", + "max" + ], "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ "thinking_blocks" + ], + "notes": [ + "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." ] } }, "attachment": false, "open_weights": true, - "knowledge": "2025-04", - "release_date": "2026-01-06", - "last_updated": "2026-01-06", + "knowledge": "2025-05", + "release_date": "2026-04-24", + "last_updated": "2026-04-24", "type": "chat" - }, + } + ] + }, + "inferx": { + "id": "inferx", + "name": "InferX", + "display_name": "InferX", + "api": "https://model.inferx.net/endpoints/v1", + "doc": "https://model.inferx.net/endpoints", + "models": [ { - "id": "zai-org/glm-5.2-maas", - "name": "GLM-5.2", - "display_name": "GLM-5.2", + "id": "gemma-4-31B-it-fp8", + "name": "Gemma 4 31B IT FP8", + "display_name": "Gemma 4 31B IT FP8", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 64000 + "context": 262144, + "output": 32768 }, "temperature": true, "tool_call": true, @@ -84179,44 +84277,35 @@ "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } - }, - "attachment": false, + "attachment": true, "open_weights": true, - "release_date": "2026-06-13", - "last_updated": "2026-06-13", + "knowledge": "2025-01", + "release_date": "2026-04-02", + "last_updated": "2026-04-02", "type": "chat" }, { - "id": "zai-org/glm-5-maas", - "name": "GLM-5", - "display_name": "GLM-5", + "id": "Qwen3.6-35B-A3B-fp8-no-thinking", + "name": "Qwen3.6-35B-A3B-fp8-no-thinking", + "display_name": "Qwen3.6-35B-A3B-fp8-no-thinking", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 128000 + "context": 262000, + "output": 65536 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true + "supported": true }, "extra_capabilities": { "reasoning": { @@ -84229,16 +84318,16 @@ ] } }, - "attachment": false, + "attachment": true, "open_weights": true, - "release_date": "2026-02-12", - "last_updated": "2026-02-12", + "release_date": "2026-04-17", + "last_updated": "2026-04-17", "type": "chat" }, { - "id": "xai/grok-4.1-fast-non-reasoning", - "name": "Grok 4.1 Fast", - "display_name": "Grok 4.1 Fast", + "id": "Ornith-1.0-35B-FP8", + "name": "Ornith-1.0-35B-FP8", + "display_name": "Ornith-1.0-35B-FP8", "modalities": { "input": [ "text", @@ -84249,36 +84338,38 @@ ] }, "limit": { - "context": 128000, - "output": 30000 + "context": 262000, + "output": 100000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, "attachment": true, - "open_weights": false, - "release_date": "2025-11-19", - "last_updated": "2025-11-19", + "open_weights": true, + "release_date": "2026-06-25", + "last_updated": "2026-06-25", "type": "chat" }, { - "id": "xai/grok-4.20-reasoning", - "name": "Grok 4.20 (Reasoning)", - "display_name": "Grok 4.20 (Reasoning)", + "id": "Qwen3.6-27B-FP8", + "name": "Qwen3.6 27B FP8", + "display_name": "Qwen3.6 27B FP8", "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 2000000, - "output": 30000 + "context": 262144, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -84286,44 +84377,55 @@ "supported": true, "default": true }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } + }, "attachment": true, - "open_weights": false, - "release_date": "2026-03-09", - "last_updated": "2026-03-09", + "open_weights": true, + "release_date": "2026-04-22", + "last_updated": "2026-04-22", "type": "chat" }, { - "id": "xai/grok-4.20-non-reasoning", - "name": "Grok 4.20 (Non-Reasoning)", - "display_name": "Grok 4.20 (Non-Reasoning)", + "id": "Devstral-2-123B-Instruct-2512-int4-AutoRound", + "name": "Devstral-2-123B-Instruct-2512-int4-AutoRound", + "display_name": "Devstral-2-123B-Instruct-2512-int4-AutoRound", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 2000000, - "output": 30000 + "context": 128000, + "output": 128000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": false }, - "attachment": true, - "open_weights": false, - "release_date": "2026-03-09", - "last_updated": "2026-03-09", + "attachment": false, + "open_weights": true, + "knowledge": "2025-12", + "release_date": "2025-12-09", + "last_updated": "2025-12-09", "type": "chat" }, { - "id": "xai/grok-4.1-fast-reasoning", - "name": "Grok 4.1 Fast (Reasoning)", - "display_name": "Grok 4.1 Fast (Reasoning)", + "id": "Agents-A1", + "name": "Agents-A1", + "display_name": "Agents-A1", "modalities": { "input": [ "text", @@ -84334,76 +84436,66 @@ ] }, "limit": { - "context": 128000, - "output": 30000 + "context": 262000, + "output": 100000 }, - "temperature": true, "tool_call": true, "reasoning": { "supported": true, "default": true }, "attachment": true, - "open_weights": false, - "release_date": "2025-11-19", - "last_updated": "2025-11-19", + "open_weights": true, + "release_date": "2026-06-26", + "last_updated": "2026-06-26", "type": "chat" }, { - "id": "xai/grok-4.7", - "name": "Grok 4.7", - "display_name": "Grok 4.7", + "id": "Qwen3-Coder-Next-FP8-no-thinking", + "name": "Qwen3-Coder-Next-FP8-no-thinking", + "display_name": "Qwen3-Coder-Next-FP8-no-thinking", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 524288, - "output": 500000 + "context": 260000, + "output": 65536 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "low", - "medium", - "high" - ] - } + "supported": false }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-05", - "release_date": "2026-09-21", - "last_updated": "2026-09-21", + "attachment": false, + "open_weights": true, + "knowledge": "2025-04", + "release_date": "2026-02-03", + "last_updated": "2026-02-03", "type": "chat" }, { - "id": "xai/grok-4.3", - "name": "Grok 4.3", - "display_name": "Grok 4.3", + "id": "mimo-v25", + "name": "mimo-v25", + "display_name": "mimo-v25", "modalities": { "input": [ "text", - "image" + "image", + "audio", + "video" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 30000 + "context": 1000000, + "output": 100000 }, "temperature": true, "tool_call": true, @@ -84411,73 +84503,58 @@ "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "supported": true - } - }, "attachment": true, - "open_weights": false, - "release_date": "2026-04-17", - "last_updated": "2026-04-17", + "open_weights": true, + "knowledge": "2024-12", + "release_date": "2026-04-22", + "last_updated": "2026-04-22", "type": "chat" }, { - "id": "xai/grok-4.6", - "name": "Grok 4.6", - "display_name": "Grok 4.6", + "id": "Qwen3-Coder-Next-FP8", + "name": "Qwen3 Coder Next FP8", + "display_name": "Qwen3 Coder Next FP8", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 524288, - "output": 500000 + "context": 256144, + "output": 65536 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "effort_options": [ - "low", - "medium", - "high" - ] - } + "supported": false }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-02-01", - "release_date": "2026-08-12", - "last_updated": "2026-08-12", + "attachment": false, + "open_weights": true, + "knowledge": "2025-04", + "release_date": "2026-02-03", + "last_updated": "2026-02-03", "type": "chat" }, { - "id": "google/gemma-4-26b-a4b-it-maas", - "name": "Gemma 4 26B A4B IT", - "display_name": "Gemma 4 26B A4B IT", + "id": "Qwen3.6-35B-A3B-FP8", + "name": "Qwen3.6 35B A3B FP8", + "display_name": "Qwen3.6 35B A3B FP8", "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 128000 + "context": 262000, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -84498,14 +84575,14 @@ }, "attachment": true, "open_weights": true, - "release_date": "2026-04-02", - "last_updated": "2026-04-02", + "release_date": "2026-04-17", + "last_updated": "2026-04-17", "type": "chat" }, { - "id": "openai/gpt-oss-20b-maas", - "name": "GPT OSS 20B", - "display_name": "GPT OSS 20B", + "id": "deepseek-v4-flash", + "name": "deepseek-v4-flash", + "display_name": "deepseek-v4-flash", "modalities": { "input": [ "text" @@ -84515,8 +84592,8 @@ ] }, "limit": { - "context": 131072, - "output": 32768 + "context": 1000000, + "output": 100000 }, "temperature": true, "tool_call": true, @@ -84526,23 +84603,37 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "high", "effort_options": [ "low", - "medium", - "high" + "high", + "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." ] } }, "attachment": false, "open_weights": true, - "release_date": "2025-08-05", - "last_updated": "2025-08-05", + "knowledge": "2025-05", + "release_date": "2026-04-24", + "last_updated": "2026-04-24", "type": "chat" }, { - "id": "openai/gpt-oss-120b-maas", - "name": "GPT OSS 120B", - "display_name": "GPT OSS 120B", + "id": "Qwen3-Embedding-8B", + "name": "Qwen3-Embedding-8B", + "display_name": "Qwen3-Embedding-8B", "modalities": { "input": [ "text" @@ -84552,35 +84643,32 @@ ] }, "limit": { - "context": 131072, - "output": 131072 + "context": 32768, + "output": 8192 }, - "temperature": true, - "tool_call": true, + "temperature": false, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "low", - "medium", - "high" - ] - } + "supported": false }, "attachment": false, "open_weights": true, - "knowledge": "2024-06", - "release_date": "2025-08-05", - "last_updated": "2025-08-05", - "type": "chat" - }, + "release_date": "2025-06-05", + "last_updated": "2025-06-05", + "type": "embedding" + } + ] + }, + "deepinfra": { + "id": "deepinfra", + "name": "Deep Infra", + "display_name": "Deep Infra", + "doc": "https://deepinfra.com/models", + "models": [ { - "id": "moonshotai/kimi-k2-thinking-maas", - "name": "Kimi K2 Thinking", - "display_name": "Kimi K2 Thinking", + "id": "openai/gpt-oss-120b", + "name": "GPT OSS 120B", + "display_name": "GPT OSS 120B", "modalities": { "input": [ "text" @@ -84590,8 +84678,8 @@ ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 131072, + "output": 16384 }, "temperature": true, "tool_call": true, @@ -84602,25 +84690,24 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "low", + "medium", + "high" ] } }, "attachment": false, "open_weights": true, - "knowledge": "2024-08", - "release_date": "2025-11-13", - "last_updated": "2025-11-13", + "knowledge": "2024-06", + "release_date": "2025-08-05", + "last_updated": "2025-08-05", "type": "chat" }, { - "id": "qwen/qwen3-235b-a22b-instruct-2507-maas", - "name": "Qwen3 235B A22B Instruct", - "display_name": "Qwen3 235B A22B Instruct", + "id": "openai/gpt-oss-20b", + "name": "GPT OSS 20B", + "display_name": "GPT OSS 20B", "modalities": { "input": [ "text" @@ -84630,82 +84717,105 @@ ] }, "limit": { - "context": 262144, + "context": 131072, "output": 16384 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "effort_options": [ + "low", + "medium", + "high" + ] + } }, "attachment": false, "open_weights": true, - "release_date": "2025-08-13", - "last_updated": "2025-08-13", + "knowledge": "2024-06", + "release_date": "2025-08-05", + "last_updated": "2025-08-05", "type": "chat" }, { - "id": "meta/llama-4-scout-17b-16e-instruct-maas", - "name": "Llama 4 Scout 17B Instruct", - "display_name": "Llama 4 Scout 17B Instruct", + "id": "Qwen/Qwen3.5-122B-A10B", + "name": "Qwen3.5 122B-A10B", + "display_name": "Qwen3.5 122B-A10B", "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 1310720, - "output": 8192 + "context": 262144, + "output": 65536 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } }, "attachment": true, "open_weights": true, - "knowledge": "2024-08", - "release_date": "2025-04-05", - "last_updated": "2025-04-05", + "release_date": "2026-02-23", + "last_updated": "2026-02-23", "type": "chat" }, { - "id": "meta/llama-4-maverick-17b-128e-instruct-maas", - "name": "Llama 4 Maverick 17B 128E Instruct", - "display_name": "Llama 4 Maverick 17B 128E Instruct", + "id": "Qwen/Qwen3-Next-80B-A3B-Instruct", + "name": "Qwen3-Next 80B-A3B Instruct", + "display_name": "Qwen3-Next 80B-A3B Instruct", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 524288, - "output": 8192 + "context": 262144, + "output": 32768 }, "temperature": true, "tool_call": true, "reasoning": { "supported": false }, - "attachment": true, + "attachment": false, "open_weights": true, - "knowledge": "2024-08", - "release_date": "2025-04-29", - "last_updated": "2025-04-29", + "knowledge": "2025-04", + "release_date": "2025-09", + "last_updated": "2025-09", "type": "chat" }, { - "id": "meta/llama-3.3-70b-instruct-maas", - "name": "Llama 3.3 70B Instruct", - "display_name": "Llama 3.3 70B Instruct", + "id": "Qwen/Qwen3.7-Max", + "name": "Qwen3.7 Max", + "display_name": "Qwen3.7 Max", "modalities": { "input": [ "text" @@ -84715,35 +84825,41 @@ ] }, "limit": { - "context": 128000, - "output": 8192 + "context": 256000, + "output": 131072 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true + } }, "attachment": false, - "open_weights": true, - "knowledge": "2023-12", - "release_date": "2025-04-29", - "last_updated": "2025-04-29", + "open_weights": false, + "release_date": "2026-05-21", + "last_updated": "2026-05-21", "type": "chat" }, { - "id": "deepseek-ai/deepseek-v3.2-maas", - "name": "DeepSeek V3.2", - "display_name": "DeepSeek V3.2", + "id": "Qwen/Qwen3.5-27B", + "name": "Qwen3.5 27B", + "display_name": "Qwen3.5 27B", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 163840, + "context": 262144, "output": 65536 }, "temperature": true, @@ -84763,27 +84879,29 @@ ] } }, - "attachment": false, + "attachment": true, "open_weights": true, - "release_date": "2025-12-17", - "last_updated": "2026-04-04", + "release_date": "2026-02-23", + "last_updated": "2026-02-23", "type": "chat" }, { - "id": "deepseek-ai/deepseek-v3.1-maas", - "name": "DeepSeek V3.1", - "display_name": "DeepSeek V3.1", + "id": "Qwen/Qwen3.5-9B", + "name": "Qwen3.5 9B", + "display_name": "Qwen3.5 9B", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 163840, - "output": 32768 + "context": 262144, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -84791,30 +84909,30 @@ "supported": true, "default": true }, - "attachment": false, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } + }, + "attachment": true, "open_weights": true, - "release_date": "2025-08-28", - "last_updated": "2025-08-28", + "release_date": "2026-02-23", + "last_updated": "2026-02-23", "type": "chat" - } - ] - }, - "kilo": { - "id": "kilo", - "name": "Kilo Gateway", - "display_name": "Kilo Gateway", - "api": "https://api.kilo.ai/api/gateway", - "doc": "https://kilo.ai", - "models": [ + }, { - "id": "bytedance-seed/seed-1.6-flash", - "name": "ByteDance Seed: Seed 1.6 Flash (retires Nov 11)", - "display_name": "ByteDance Seed: Seed 1.6 Flash (retires Nov 11)", + "id": "Qwen/Qwen3.8-2.4T-A95B", + "name": "Qwen3.8 2.4T A95B", + "display_name": "Qwen3.8 2.4T A95B", "modalities": { "input": [ - "text", - "image", - "video" + "text" ], "output": [ "text" @@ -84822,7 +84940,7 @@ }, "limit": { "context": 262144, - "output": 32768 + "output": 131072 }, "temperature": true, "tool_call": true, @@ -84832,27 +84950,28 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, "effort_options": [ - "none", - "high" + "low", + "medium", + "xhigh" ] } }, - "attachment": true, - "open_weights": false, - "release_date": "2025-08-28", - "last_updated": "2025-08-28", + "attachment": false, + "open_weights": true, + "release_date": "2026-08-12", + "last_updated": "2026-08-12", "type": "chat" }, { - "id": "bytedance-seed/seed-2.0-mini", - "name": "Seed 2.0 Mini", - "display_name": "Seed 2.0 Mini", + "id": "Qwen/Qwen3-VL-30B-A3B-Instruct", + "name": "Qwen3 VL 30B A3B Instruct", + "display_name": "Qwen3 VL 30B A3B Instruct", "modalities": { "input": [ "text", - "image", - "video" + "image" ], "output": [ "text" @@ -84860,48 +84979,34 @@ }, "limit": { "context": 262144, - "output": 131072 + "output": 32768 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "none", - "minimal", - "low", - "medium", - "high" - ] - } + "supported": false }, "attachment": true, - "open_weights": false, - "release_date": "2026-02-14", - "last_updated": "2026-02-14", + "open_weights": true, + "release_date": "2025-10-04", + "last_updated": "2025-10-04", "type": "chat" }, { - "id": "bytedance-seed/seed-2.0-code", - "name": "Seed 2.0 Code", - "display_name": "Seed 2.0 Code", + "id": "Qwen/Qwen3-30B-A3B", + "name": "Qwen3 30B A3B", + "display_name": "Qwen3 30B A3B", "modalities": { "input": [ - "text", - "image", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 131072 + "context": 40960, + "output": 16384 }, "temperature": true, "tool_call": true, @@ -84911,24 +85016,25 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "none", - "low", - "medium", - "high" + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, - "attachment": true, - "open_weights": false, - "release_date": "2026-02-14", - "last_updated": "2026-02-14", + "attachment": false, + "open_weights": true, + "release_date": "2025-04-28", + "last_updated": "2025-04-28", "type": "chat" }, { - "id": "bytedance-seed/seed-2-1-turbo", - "name": "ByteDance Seed: Seed 2.1 Turbo", - "display_name": "ByteDance Seed: Seed 2.1 Turbo", + "id": "Qwen/Qwen3.6-27B", + "name": "Qwen3.6 27B", + "display_name": "Qwen3.6 27B", "modalities": { "input": [ "text", @@ -84941,7 +85047,7 @@ }, "limit": { "context": 262144, - "output": 235929 + "output": 65536 }, "temperature": true, "tool_call": true, @@ -84951,60 +85057,60 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "none", - "high" + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, "attachment": true, - "open_weights": false, - "release_date": "2026-06-23", - "last_updated": "2026-06-23", + "open_weights": true, + "release_date": "2026-04-22", + "last_updated": "2026-04-22", "type": "chat" }, { - "id": "bytedance-seed/seed-1.6", - "name": "ByteDance Seed: Seed 1.6 (retires Nov 11)", - "display_name": "ByteDance Seed: Seed 1.6 (retires Nov 11)", + "id": "Qwen/Qwen3.8-Max", + "name": "Qwen3.8 Max", + "display_name": "Qwen3.8 Max", "modalities": { "input": [ "text", "image", - "video" + "video", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 32768 + "context": 256000, + "output": 131072 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true + "supported": true }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "none", - "high" - ] + "supported": true } }, "attachment": true, "open_weights": false, - "release_date": "2025-10-15", - "last_updated": "2025-10-15", + "release_date": "2026-08-03", + "last_updated": "2026-08-03", "type": "chat" }, { - "id": "bytedance-seed/seed-2.0-lite", - "name": "Seed 2.0 Lite", - "display_name": "Seed 2.0 Lite", + "id": "Qwen/Qwen3.5-397B-A17B", + "name": "Qwen 3.5 397B A17B", + "display_name": "Qwen 3.5 397B A17B", "modalities": { "input": [ "text", @@ -85017,7 +85123,7 @@ }, "limit": { "context": 262144, - "output": 131072 + "output": 81920 }, "temperature": true, "tool_call": true, @@ -85027,63 +85133,65 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "none", - "minimal", - "low", - "medium", - "high" + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, "attachment": true, - "open_weights": false, - "release_date": "2026-02-14", - "last_updated": "2026-02-14", + "open_weights": true, + "knowledge": "2025-01", + "release_date": "2026-02-15", + "last_updated": "2026-02-15", "type": "chat" }, { - "id": "nex-agi/nex-n2.5-pro", - "name": "Nex AGI: Nex-N2.5-Pro", - "display_name": "Nex AGI: Nex-N2.5-Pro", + "id": "Qwen/Qwen3-Max", + "name": "Qwen3 Max", + "display_name": "Qwen3 Max", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 235929 + "context": 256000, + "output": 65536 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true + "supported": true }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "none", - "medium", - "high" + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, - "attachment": true, + "attachment": false, "open_weights": false, - "release_date": "2026-09-08", - "last_updated": "2026-09-08", + "knowledge": "2025-04", + "release_date": "2025-09-23", + "last_updated": "2025-09-23", "type": "chat" }, { - "id": "nex-agi/nex-n2.5-mini", - "name": "Nex AGI: Nex-N2.5-Mini", - "display_name": "Nex AGI: Nex-N2.5-Mini", + "id": "Qwen/Qwen3-VL-235B-A22B-Instruct", + "name": "Qwen3 VL 235B A22B Instruct", + "display_name": "Qwen3 VL 235B A22B Instruct", "modalities": { "input": [ "text", @@ -85095,44 +85203,37 @@ }, "limit": { "context": 262144, - "output": 235929 + "output": 32768 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "none", - "medium", - "high" - ] - } + "supported": false }, "attachment": true, - "open_weights": false, - "release_date": "2026-09-08", - "last_updated": "2026-09-08", + "open_weights": true, + "knowledge": "2025-03-31", + "release_date": "2025-09-23", + "last_updated": "2025-09-23", "type": "chat" }, { - "id": "meituan/longcat-2.0", - "name": "Meituan: LongCat 2.0", - "display_name": "Meituan: LongCat 2.0", + "id": "Qwen/Qwen3.8-27B", + "name": "Qwen3.8 27B", + "display_name": "Qwen3.8 27B", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 1048756, - "output": 262144 + "context": 262144, + "output": 32768 }, "temperature": true, "tool_call": true, @@ -85142,23 +85243,23 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, "effort_options": [ - "none", - "high" + "low", + "medium", + "xhigh" ] } }, - "attachment": false, + "attachment": true, "open_weights": true, - "release_date": "2026-06-30", - "last_updated": "2026-06-30", + "release_date": "2026-08-14", + "last_updated": "2026-08-14", "type": "chat" }, { - "id": "undi95/remm-slerp-l2-13b", - "name": "ReMM SLERP 13B", - "display_name": "ReMM SLERP 13B", + "id": "Qwen/Qwen3-235B-A22B-Instruct-2507", + "name": "Qwen3 235B-A22B Instruct 2507", + "display_name": "Qwen3 235B-A22B Instruct 2507", "modalities": { "input": [ "text" @@ -85168,52 +85269,58 @@ ] }, "limit": { - "context": 6144, - "output": 5529 + "context": 262144, + "output": 16384 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { "supported": false }, "attachment": false, "open_weights": true, - "knowledge": "2023-06-30", - "release_date": "2023-07-22", - "last_updated": "2023-07-22", + "release_date": "2025-07-21", + "last_updated": "2025-07-21", "type": "chat" }, { - "id": "inference-net/schematron-v2-small", - "name": "Inference.net: Schematron V2 Small", - "display_name": "Inference.net: Schematron V2 Small", + "id": "Qwen/Qwen3.8-Flash", + "name": "Qwen3.8 Flash", + "display_name": "Qwen3.8 Flash", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 4096 + "context": 1000000, + "output": 131072 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true }, - "attachment": false, + "extra_capabilities": { + "reasoning": { + "supported": true + } + }, + "attachment": true, "open_weights": false, - "release_date": "2026-04-16", - "last_updated": "2026-04-16", + "release_date": "2026-08-26", + "last_updated": "2026-08-26", "type": "chat" }, { - "id": "inference-net/schematron-v2-turbo", - "name": "Inference.net: Schematron V2 Turbo", - "display_name": "Inference.net: Schematron V2 Turbo", + "id": "Qwen/Qwen3-32B", + "name": "Qwen3 32B", + "display_name": "Qwen3 32B", "modalities": { "input": [ "text" @@ -85223,52 +85330,78 @@ ] }, "limit": { - "context": 128000, - "output": 8192 + "context": 40960, + "output": 16384 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } }, "attachment": false, - "open_weights": false, - "release_date": "2026-04-16", - "last_updated": "2026-04-16", + "open_weights": true, + "knowledge": "2025-04", + "release_date": "2025-04-28", + "last_updated": "2025-04-28", "type": "chat" }, { - "id": "bytedance/ui-tars-1.5-7b", - "name": "ByteDance: UI-TARS 7B ", - "display_name": "ByteDance: UI-TARS 7B ", + "id": "Qwen/Qwen3.6-35B-A3B", + "name": "Qwen3.6 35B A3B", + "display_name": "Qwen3.6 35B A3B", "modalities": { "input": [ + "text", "image", - "text" + "video" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 2048 + "context": 262144, + "output": 81920 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } }, "attachment": true, - "open_weights": false, - "release_date": "2025-07-22", - "last_updated": "2025-07-22", + "open_weights": true, + "release_date": "2026-04-17", + "last_updated": "2026-04-17", "type": "chat" }, { - "id": "minimax/minimax-m2-her", - "name": "MiniMax-M2 Her", - "display_name": "MiniMax-M2 Her", + "id": "Qwen/Qwen3-Coder-480B-A35B-Instruct-Turbo", + "name": "Qwen3 Coder 480B A35B Instruct Turbo", + "display_name": "Qwen3 Coder 480B A35B Instruct Turbo", "modalities": { "input": [ "text" @@ -85278,35 +85411,38 @@ ] }, "limit": { - "context": 65536, - "output": 2048 + "context": 262144, + "output": 66536 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { "supported": false }, "attachment": false, - "open_weights": false, - "release_date": "2026-01-23", - "last_updated": "2026-01-23", + "open_weights": true, + "knowledge": "2025-04", + "release_date": "2025-07-22", + "last_updated": "2025-07-22", "type": "chat" }, { - "id": "minimax/minimax-m2.1", - "name": "MiniMax-M2.1", - "display_name": "MiniMax-M2.1", + "id": "Qwen/Qwen3.5-35B-A3B", + "name": "Qwen 3.5 35B A3B", + "display_name": "Qwen 3.5 35B A3B", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 204800, - "output": 131072 + "context": 262144, + "output": 81920 }, "temperature": true, "tool_call": true, @@ -85317,60 +85453,39 @@ "extra_capabilities": { "reasoning": { "supported": true, - "effort_options": [ - "high" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, - "attachment": false, + "attachment": true, "open_weights": true, - "release_date": "2025-12-23", - "last_updated": "2025-12-23", + "knowledge": "2025-01", + "release_date": "2026-02-23", + "last_updated": "2026-02-23", "type": "chat" }, { - "id": "minimax/minimax-01", - "name": "MiniMax: MiniMax-01", - "display_name": "MiniMax: MiniMax-01", + "id": "XiaomiMiMo/MiMo-V2.5", + "name": "MiMo-V2.5", + "display_name": "MiMo-V2.5", "modalities": { "input": [ "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 1000192, - "output": 900172 - }, - "temperature": true, - "tool_call": false, - "reasoning": { - "supported": false - }, - "attachment": true, - "open_weights": false, - "release_date": "2025-01-15", - "last_updated": "2025-01-15", - "type": "chat" - }, - { - "id": "minimax/minimax-m1", - "name": "MiniMax: MiniMax M1", - "display_name": "MiniMax: MiniMax M1", - "modalities": { - "input": [ - "text" + "image", + "audio", + "video" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 40000 + "context": 262144, + "output": 16384 }, "temperature": true, "tool_call": true, @@ -85380,35 +85495,38 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "none", - "high" + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, - "attachment": false, - "open_weights": false, - "release_date": "2025-06-17", - "last_updated": "2025-06-17", + "attachment": true, + "open_weights": true, + "knowledge": "2024-12", + "release_date": "2026-04-22", + "last_updated": "2026-04-22", "type": "chat" }, { - "id": "minimax/minimax-m3", - "name": "MiniMax-M3", - "display_name": "MiniMax-M3", + "id": "XiaomiMiMo/MiMo-V2.5-Pro", + "name": "MiMo-V2.5-Pro", + "display_name": "MiMo-V2.5-Pro", "modalities": { "input": [ "text", - "image", - "video" + "audio" ], "output": [ "text" ] }, "limit": { - "context": 524288, - "output": 512000 + "context": 1048576, + "output": 16384 }, "temperature": true, "tool_call": true, @@ -85419,26 +85537,30 @@ "extra_capabilities": { "reasoning": { "supported": true, - "effort_options": [ - "none", - "high" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, "attachment": true, "open_weights": true, - "release_date": "2026-06-01", - "last_updated": "2026-06-01", + "knowledge": "2024-12", + "release_date": "2026-04-22", + "last_updated": "2026-04-22", "type": "chat" }, { - "id": "minimax/minimax-m3.1-flash", - "name": "MiniMax: MiniMax M3.1 Flash", - "display_name": "MiniMax: MiniMax M3.1 Flash", + "id": "XiaomiMiMo/MiMo-V2.6-Pro", + "name": "MiMo-V2.6-Pro", + "display_name": "MiMo-V2.6-Pro", "modalities": { "input": [ "text", "image", + "audio", "video" ], "output": [ @@ -85447,7 +85569,7 @@ }, "limit": { "context": 1048576, - "output": 524288 + "output": 131072 }, "temperature": true, "tool_call": true, @@ -85455,37 +85577,29 @@ "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" - ] - } - }, "attachment": true, - "open_weights": false, - "release_date": "2026-10-19", - "last_updated": "2026-10-19", + "open_weights": true, + "release_date": "2026-09-22", + "last_updated": "2026-09-22", "type": "chat" }, { - "id": "minimax/minimax-m2", - "name": "MiniMax-M2", - "display_name": "MiniMax-M2", + "id": "XiaomiMiMo/MiMo-V2.6-Flash", + "name": "MiMo-V2.6-Flash", + "display_name": "MiMo-V2.6-Flash", "modalities": { "input": [ - "text" + "text", + "image", + "audio", + "video" ], "output": [ "text" ] }, "limit": { - "context": 204800, + "context": 1048576, "output": 131072 }, "temperature": true, @@ -85494,34 +85608,30 @@ "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "high" - ] - } - }, - "attachment": false, + "attachment": true, "open_weights": true, - "release_date": "2025-10-27", - "last_updated": "2025-10-27", + "release_date": "2026-09-22", + "last_updated": "2026-09-22", "type": "chat" }, { - "id": "minimax/minimax-m2.5", - "name": "MiniMax-M2.5", - "display_name": "MiniMax-M2.5", + "id": "nvidia/Nemotron-3-Nano-Omni-30B-A3B-Reasoning", + "name": "Nemotron 3 Nano Omni 30B A3B Reasoning", + "display_name": "Nemotron 3 Nano Omni 30B A3B Reasoning", "modalities": { "input": [ - "text" + "text", + "image", + "video", + "audio" ], "output": [ "text" ] }, "limit": { - "context": 198000, - "output": 32768 + "context": 262144, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -85531,22 +85641,19 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "effort_options": [ - "high" - ] + "supported": true } }, - "attachment": false, + "attachment": true, "open_weights": true, - "release_date": "2026-02-12", - "last_updated": "2026-02-12", + "release_date": "2026-04-28", + "last_updated": "2026-04-28", "type": "chat" }, { - "id": "minimax/minimax-m2.7", - "name": "MiniMax-M2.7", - "display_name": "MiniMax-M2.7", + "id": "nvidia/Llama-3.3-Nemotron-Super-49B-v1.5", + "name": "Llama 3.3 Nemotron Super 49B v1.5", + "display_name": "Llama 3.3 Nemotron Super 49B v1.5", "modalities": { "input": [ "text" @@ -85556,7 +85663,7 @@ ] }, "limit": { - "context": 204800, + "context": 131072, "output": 131072 }, "temperature": true, @@ -85565,30 +85672,16 @@ "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "effort_options": [ - "high" - ] - } - }, "attachment": false, "open_weights": true, - "release_date": "2026-03-18", - "last_updated": "2026-03-18", + "release_date": "2025-07-25", + "last_updated": "2025-07-25", "type": "chat" }, { - "id": "gryphe/mythomax-l2-13b", - "name": "MythoMax 13B", - "display_name": "MythoMax 13B", + "id": "nvidia/Nemotron-3-Nano-30B-A3B", + "name": "Nemotron 3 Nano 30B A3B", + "display_name": "Nemotron 3 Nano 30B A3B", "modalities": { "input": [ "text" @@ -85598,123 +85691,133 @@ ] }, "limit": { - "context": 4096, - "output": 3686 + "context": 262144, + "output": 262144 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, "attachment": false, "open_weights": true, - "release_date": "2023-08-10", - "last_updated": "2023-08-10", + "knowledge": "2025-06-25", + "release_date": "2025-12-15", + "last_updated": "2025-12-15", "type": "chat" }, { - "id": "unbiased/pareto-26.9", - "name": "Pareto 26.9", - "display_name": "Pareto 26.9", + "id": "thinkingmachines/Inkling-Small", + "name": "Inkling Small", + "display_name": "Inkling Small", "modalities": { "input": [ "text", - "image" + "image", + "audio" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 131072 + "context": 524288, + "output": 1048576 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, "attachment": true, - "open_weights": false, - "release_date": "2026-10-13", - "last_updated": "2026-10-13", + "open_weights": true, + "release_date": "2026-07-30", + "last_updated": "2026-07-30", "type": "chat" }, { - "id": "unbiased/pareto-26.10", - "name": "Pareto 26.10", - "display_name": "Pareto 26.10", + "id": "thinkingmachines/Inkling", + "name": "Inkling", + "display_name": "Inkling", "modalities": { "input": [ "text", - "image" + "image", + "audio" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 524288, + "output": 1048576 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, "attachment": true, - "open_weights": false, - "release_date": "2026-10-15", - "last_updated": "2026-10-15", + "open_weights": true, + "release_date": "2026-07-15", + "last_updated": "2026-07-15", "type": "chat" }, { - "id": "writer/palmyra-x5", - "name": "Writer: Palmyra X5", - "display_name": "Writer: Palmyra X5", + "id": "stepfun-ai/Step-3.7-Flash", + "name": "Step 3.7 Flash", + "display_name": "Step 3.7 Flash", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 1040000, - "output": 8192 + "context": 262144, + "output": 256000 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, - "open_weights": false, - "release_date": "2025-04-28", - "last_updated": "2025-04-28", + "attachment": true, + "open_weights": true, + "knowledge": "2026-03-01", + "release_date": "2026-05-29", + "last_updated": "2026-05-29", "type": "chat" }, { - "id": "x-ai/grok-4.20-multi-agent", - "name": "SpaceXAI: Grok 4.20 Multi-Agent", - "display_name": "SpaceXAI: Grok 4.20 Multi-Agent", + "id": "stepfun-ai/Step-5-Preview", + "name": "Step 5 Preview", + "display_name": "Step 5 Preview", "modalities": { "input": [ "text", "image", - "pdf" + "video" ], "output": [ "text" ] }, "limit": { - "context": 2000000, - "output": 1800000 + "context": 1048576, + "output": 1000000 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { "supported": true, "default": true @@ -85724,150 +85827,147 @@ "effort_options": [ "low", "medium", - "high", - "xhigh" + "high" ] } }, "attachment": true, - "open_weights": false, - "release_date": "2026-03-31", - "last_updated": "2026-03-31", + "open_weights": true, + "release_date": "2026-09-16", + "last_updated": "2026-09-20", "type": "chat" }, { - "id": "x-ai/grok-4.5", - "name": "Grok 4.5", - "display_name": "Grok 4.5", + "id": "meta-llama/Llama-4-Scout-17B-16E-Instruct", + "name": "Llama 4 Scout 17B", + "display_name": "Llama 4 Scout 17B", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 500000, - "output": 450000 + "context": 327680, + "output": 16384 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "effort_options": [ - "low", - "medium", - "high" - ] - } + "supported": false }, "attachment": true, - "open_weights": false, - "release_date": "2026-07-08", - "last_updated": "2026-07-08", + "open_weights": true, + "knowledge": "2024-08", + "release_date": "2025-04-05", + "last_updated": "2025-04-05", "type": "chat" }, { - "id": "x-ai/grok-4.20", - "name": "SpaceXAI: Grok 4.20", - "display_name": "SpaceXAI: Grok 4.20", + "id": "meta-llama/Llama-Guard-4-12B", + "name": "Llama Guard 4 12B", + "display_name": "Llama Guard 4 12B", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 2000000, - "output": 1800000 + "context": 163840, + "output": 163840 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "none", - "high" - ] - } + "supported": false }, "attachment": true, - "open_weights": false, - "release_date": "2026-03-31", - "last_updated": "2026-03-31", + "open_weights": true, + "release_date": "2025-04-29", + "last_updated": "2025-04-29", "type": "chat" }, { - "id": "x-ai/grok-build-0.1", - "name": "Grok Build 0.1", - "display_name": "Grok Build 0.1", + "id": "meta-llama/Llama-4-Maverick-17B-128E-Instruct-FP8", + "name": "Llama 4 Maverick 17B FP8", + "display_name": "Llama 4 Maverick 17B FP8", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 256000, - "output": 230400 + "context": 1048576, + "output": 16384 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "effort_options": [ - "high" - ] - } + "supported": false }, "attachment": true, - "open_weights": false, - "release_date": "2026-04-16", - "last_updated": "2026-04-16", + "open_weights": true, + "knowledge": "2024-08", + "release_date": "2025-04-05", + "last_updated": "2025-04-05", "type": "chat" }, { - "id": "x-ai/grok-4.7", - "name": "Grok 4.7", - "display_name": "Grok 4.7", + "id": "meta-llama/Llama-3.3-70B-Instruct-Turbo", + "name": "Llama 3.3 70B Turbo", + "display_name": "Llama 3.3 70B Turbo", + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "output": 16384 + }, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": false + }, + "attachment": false, + "open_weights": true, + "knowledge": "2023-12", + "release_date": "2024-12-06", + "last_updated": "2024-12-06", + "type": "chat" + }, + { + "id": "moonshotai/Kimi-K2.5", + "name": "Kimi K2.5", + "display_name": "Kimi K2.5", "modalities": { "input": [ "text", "image", - "pdf" + "video" ], "output": [ "text" ] }, "limit": { - "context": 500000, - "output": 450000 + "context": 262144, + "output": 32768 }, "temperature": true, "tool_call": true, @@ -85877,40 +85977,41 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "low", - "medium", - "high", - "xhigh" + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2026-05", - "release_date": "2026-09-21", - "last_updated": "2026-09-21", + "open_weights": true, + "knowledge": "2025-01", + "release_date": "2026-01-27", + "last_updated": "2026-01-27", "type": "chat" }, { - "id": "x-ai/grok-4.3", - "name": "Grok 4.3", - "display_name": "Grok 4.3", + "id": "moonshotai/Kimi-K2.7-Code", + "name": "Kimi K2.7 Code", + "display_name": "Kimi K2.7 Code", "modalities": { "input": [ "text", "image", - "pdf" + "video" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 900000 + "context": 262144, + "output": 262144 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -85919,37 +86020,38 @@ "extra_capabilities": { "reasoning": { "supported": true, - "effort_options": [ - "none", - "low", - "medium", - "high" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, "attachment": true, - "open_weights": false, - "release_date": "2026-04-17", - "last_updated": "2026-04-17", + "open_weights": true, + "knowledge": "2025-01", + "release_date": "2026-06-12", + "last_updated": "2026-06-12", "type": "chat" }, { - "id": "x-ai/grok-4.6", - "name": "Grok 4.6", - "display_name": "Grok 4.6", + "id": "moonshotai/Kimi-K2.6", + "name": "Kimi K2.6", + "display_name": "Kimi K2.6", "modalities": { "input": [ "text", "image", - "pdf" + "video" ], "output": [ "text" ] }, "limit": { - "context": 500000, - "output": 450000 + "context": 262144, + "output": 16384 }, "temperature": true, "tool_call": true, @@ -85957,40 +86059,81 @@ "supported": true, "default": true }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } + }, + "attachment": true, + "open_weights": true, + "knowledge": "2025-01", + "release_date": "2026-04-20", + "last_updated": "2026-04-20", + "type": "chat" + }, + { + "id": "moonshotai/Kimi-K3", + "name": "Kimi K3", + "display_name": "Kimi K3", + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1048576, + "output": 131072 + }, + "temperature": false, + "tool_call": true, + "reasoning": { + "supported": true, + "default": true + }, "extra_capabilities": { "reasoning": { "supported": true, "effort_options": [ "low", - "medium", "high", - "xhigh" + "max" ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2026-02-01", - "release_date": "2026-08-12", - "last_updated": "2026-08-12", + "open_weights": true, + "release_date": "2026-07-16", + "last_updated": "2026-07-16", "type": "chat" }, { - "id": "dots-studio/dots-3-note-preview:free", - "name": "Dots Studio: Dots3-Note Preview (free)", - "display_name": "Dots Studio: Dots3-Note Preview (free)", + "id": "zai-org/GLM-5.3-Flash", + "name": "GLM-5.3-Flash", + "display_name": "GLM-5.3-Flash", "modalities": { "input": [ "text", - "image" + "image", + "video", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 512000, - "output": 460800 + "context": 1048576, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -86000,22 +86143,24 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, "effort_options": [ - "none", - "high" + "low", + "high", + "max" ] } }, "attachment": true, - "open_weights": false, - "release_date": "2026-08-14", - "last_updated": "2026-08-14", + "open_weights": true, + "release_date": "2026-08-26", + "last_updated": "2026-08-26", "type": "chat" }, { - "id": "poolside/laguna-xs-2.1:free", - "name": "Poolside: Laguna XS 2.1 (free)", - "display_name": "Poolside: Laguna XS 2.1 (free)", + "id": "zai-org/GLM-5", + "name": "GLM-5", + "display_name": "GLM-5", "modalities": { "input": [ "text" @@ -86025,8 +86170,8 @@ ] }, "limit": { - "context": 262144, - "output": 32768 + "context": 202752, + "output": 16384 }, "temperature": true, "tool_call": true, @@ -86036,22 +86181,26 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "none", - "high" + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, "attachment": false, "open_weights": true, - "release_date": "2026-07-02", - "last_updated": "2026-07-02", + "knowledge": "2025-12", + "release_date": "2026-02-12", + "last_updated": "2026-02-12", "type": "chat" }, { - "id": "poolside/laguna-s-2.1:free", - "name": "Poolside: Laguna S 2.1 (free)", - "display_name": "Poolside: Laguna S 2.1 (free)", + "id": "zai-org/GLM-4.6", + "name": "GLM-4.6", + "display_name": "GLM-4.6", "modalities": { "input": [ "text" @@ -86061,8 +86210,8 @@ ] }, "limit": { - "context": 262144, - "output": 32768 + "context": 202752, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -86072,22 +86221,26 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "none", - "high" + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, "attachment": false, "open_weights": true, - "release_date": "2026-07-21", - "last_updated": "2026-07-21", + "knowledge": "2025-04", + "release_date": "2025-09-30", + "last_updated": "2025-09-30", "type": "chat" }, { - "id": "poolside/laguna-s-2.1", - "name": "Poolside: Laguna S 2.1 (retires Oct 31)", - "display_name": "Poolside: Laguna S 2.1 (retires Oct 31)", + "id": "zai-org/GLM-5.1", + "name": "GLM-5.1", + "display_name": "GLM-5.1", "modalities": { "input": [ "text" @@ -86097,8 +86250,8 @@ ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 202752, + "output": 16384 }, "temperature": true, "tool_call": true, @@ -86109,22 +86262,25 @@ "extra_capabilities": { "reasoning": { "supported": true, - "effort_options": [ - "none", - "high" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, "attachment": false, "open_weights": true, - "release_date": "2026-07-21", - "last_updated": "2026-07-21", + "knowledge": "2025-04", + "release_date": "2026-04-07", + "last_updated": "2026-04-07", "type": "chat" }, { - "id": "poolside/laguna-xs-2.1", - "name": "Poolside: Laguna XS 2.1 (retires Oct 31)", - "display_name": "Poolside: Laguna XS 2.1 (retires Oct 31)", + "id": "zai-org/GLM-5.2", + "name": "GLM-5.2", + "display_name": "GLM-5.2", "modalities": { "input": [ "text" @@ -86134,7 +86290,7 @@ ] }, "limit": { - "context": 262144, + "context": 1048576, "output": 32768 }, "temperature": true, @@ -86146,26 +86302,31 @@ "extra_capabilities": { "reasoning": { "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ - "none", - "high" + "high", + "max" ] } }, "attachment": false, "open_weights": true, - "release_date": "2026-07-02", - "last_updated": "2026-07-02", + "release_date": "2026-06-13", + "last_updated": "2026-06-13", "type": "chat" }, { - "id": "fireworks/ember-1", - "name": "Fireworks: Ember-1", - "display_name": "Fireworks: Ember-1", + "id": "zai-org/GLM-5.3", + "name": "GLM-5.3", + "display_name": "GLM-5.3", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" @@ -86173,7 +86334,7 @@ }, "limit": { "context": 1048576, - "output": 943718 + "output": 131072 }, "temperature": true, "tool_call": true, @@ -86183,24 +86344,24 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, "effort_options": [ - "none", "low", "high", "max" ] } }, - "attachment": true, - "open_weights": false, - "release_date": "2026-09-22", - "last_updated": "2026-09-22", + "attachment": false, + "open_weights": true, + "release_date": "2026-08-14", + "last_updated": "2026-08-14", "type": "chat" }, { - "id": "anthracite-org/magnum-v4-72b", - "name": "Magnum v4 72B", - "display_name": "Magnum v4 72B", + "id": "zai-org/GLM-4.7-Flash", + "name": "GLM-4.7-Flash", + "display_name": "GLM-4.7-Flash", "modalities": { "input": [ "text" @@ -86210,38 +86371,48 @@ ] }, "limit": { - "context": 32768, - "output": 4096 + "context": 202752, + "output": 16384 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } }, "attachment": false, "open_weights": true, - "knowledge": "2024-06-30", - "release_date": "2024-10-22", - "last_updated": "2024-10-22", + "knowledge": "2025-04", + "release_date": "2026-01-19", + "last_updated": "2026-01-19", "type": "chat" }, { - "id": "elorian/glimpse", - "name": "Elorian: Glimpse", - "display_name": "Elorian: Glimpse", + "id": "zai-org/GLM-4.7", + "name": "GLM-4.7", + "display_name": "GLM-4.7", "modalities": { "input": [ - "text", - "image", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 256000 + "context": 202752, + "output": 16384 }, "temperature": true, "tool_call": true, @@ -86251,34 +86422,37 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "none", - "high" + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, - "attachment": true, - "open_weights": false, - "release_date": "2026-10-17", - "last_updated": "2026-10-17", + "attachment": false, + "open_weights": true, + "knowledge": "2025-04", + "release_date": "2025-12-22", + "last_updated": "2025-12-22", "type": "chat" }, { - "id": "cohere/command-a-plus", - "name": "Cohere: Command A+", - "display_name": "Cohere: Command A+", + "id": "tencent/Hy3", + "name": "Hy3", + "display_name": "Hy3", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 192000, - "output": 64000 + "context": 262144, + "output": 128000 }, "temperature": true, "tool_call": true, @@ -86288,23 +86462,19 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "none", - "high" - ] + "supported": true } }, - "attachment": true, + "attachment": false, "open_weights": true, - "knowledge": "2025-04-01", - "release_date": "2026-05-20", - "last_updated": "2026-06-09", + "release_date": "2026-07-06", + "last_updated": "2026-07-06", "type": "chat" }, { - "id": "cohere/north-mini-code:free", - "name": "Cohere: North Mini Code (free)", - "display_name": "Cohere: North Mini Code (free)", + "id": "tencent/Hy4-preview", + "name": "Hy4 preview", + "display_name": "Hy4 preview", "modalities": { "input": [ "text" @@ -86314,7 +86484,7 @@ ] }, "limit": { - "context": 256000, + "context": 1048576, "output": 64000 }, "temperature": true, @@ -86325,6 +86495,7 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, "effort_options": [ "none", "high" @@ -86333,142 +86504,150 @@ }, "attachment": false, "open_weights": true, - "knowledge": "2025-09-23", - "release_date": "2026-06-09", - "last_updated": "2026-06-09", + "release_date": "2026-08-28", + "last_updated": "2026-08-28", "type": "chat" }, { - "id": "cohere/command-r-08-2024", - "name": "Command R", - "display_name": "Command R", + "id": "google/gemma-4-26B-A4B-it", + "name": "Gemma 4 26B A4B IT", + "display_name": "Gemma 4 26B A4B IT", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 4000 + "context": 262144, + "output": 32768 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, + "extra_capabilities": { + "reasoning": { + "supported": true + } + }, + "attachment": true, "open_weights": true, - "knowledge": "2024-06-01", - "release_date": "2024-08-30", - "last_updated": "2024-08-30", + "release_date": "2026-04-02", + "last_updated": "2026-04-02", "type": "chat" }, { - "id": "cohere/command-a", - "name": "Command A", - "display_name": "Command A", + "id": "google/gemma-3-12b-it", + "name": "Gemma 3 12B IT", + "display_name": "Gemma 3 12B IT", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 256000, - "output": 8192 + "context": 131072, + "output": 131072 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { "supported": false }, - "attachment": false, + "attachment": true, "open_weights": true, - "knowledge": "2024-08-31", - "release_date": "2025-03-13", - "last_updated": "2025-03-13", + "knowledge": "2024-08", + "release_date": "2025-03-12", + "last_updated": "2025-03-12", "type": "chat" }, { - "id": "cohere/command-r7b-12-2024", - "name": "Command R7B", - "display_name": "Command R7B", + "id": "google/gemma-3-27b-it", + "name": "Gemma 3 27B IT", + "display_name": "Gemma 3 27B IT", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 4000 + "context": 131072, + "output": 131072 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { "supported": false }, - "attachment": false, + "attachment": true, "open_weights": true, - "knowledge": "2024-06-01", - "release_date": "2024-12-13", - "last_updated": "2024-12-13", + "knowledge": "2024-08", + "release_date": "2025-03-12", + "last_updated": "2025-03-12", "type": "chat" }, { - "id": "cohere/command-r-plus-08-2024", - "name": "Command R+", - "display_name": "Command R+", + "id": "google/gemma-3-4b-it", + "name": "Gemma 3 4B IT", + "display_name": "Gemma 3 4B IT", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 4000 + "context": 131072, + "output": 131072 }, "temperature": true, "tool_call": true, "reasoning": { "supported": false }, - "attachment": false, + "attachment": true, "open_weights": true, - "knowledge": "2024-06-01", - "release_date": "2024-08-30", - "last_updated": "2024-08-30", + "knowledge": "2024-08", + "release_date": "2025-03-12", + "last_updated": "2025-03-12", "type": "chat" }, { - "id": "sakana/fugu-ultra-v2", - "name": "Fugu Ultra v2", - "display_name": "Fugu Ultra v2", + "id": "google/gemma-4-31B-it", + "name": "Gemma 4 31B IT", + "display_name": "Gemma 4 31B IT", "modalities": { "input": [ "text", "image", - "pdf" + "video" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 262144, + "output": 32768 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -86476,24 +86655,78 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "high", - "xhigh", - "max" - ] + "supported": true } }, "attachment": true, - "open_weights": false, - "knowledge": "2026-08-28", - "release_date": "2026-09-11", - "last_updated": "2026-09-11", + "open_weights": true, + "knowledge": "2025-01", + "release_date": "2026-04-02", + "last_updated": "2026-04-02", "type": "chat" }, { - "id": "sakana/fugu-max", - "name": "Fugu Max", - "display_name": "Fugu Max", + "id": "google/gemma-4-E4B-it", + "name": "Gemma 4 E4B IT", + "display_name": "Gemma 4 E4B IT", + "modalities": { + "input": [ + "text", + "image", + "audio" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "output": 8192 + }, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": true, + "default": true + }, + "attachment": true, + "open_weights": true, + "release_date": "2026-04-02", + "last_updated": "2026-04-02", + "type": "chat" + }, + { + "id": "mistralai/Mistral-Small-24B-Instruct-2501", + "name": "Mistral Small 3", + "display_name": "Mistral Small 3", + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 32768, + "output": 16384 + }, + "temperature": true, + "tool_call": false, + "reasoning": { + "supported": false + }, + "attachment": false, + "open_weights": true, + "knowledge": "2023-10-31", + "release_date": "2025-01-30", + "last_updated": "2025-01-30", + "type": "chat" + }, + { + "id": "anthropic/claude-fable-5", + "name": "Claude Fable 5", + "display_name": "Claude Fable 5", "modalities": { "input": [ "text", @@ -86516,61 +86749,70 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "high", "effort_options": [ + "low", + "medium", "high", "xhigh", "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "omitted", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Adaptive thinking is always on for Claude Fable 5 and Claude Mythos 5; thinking.type = \"disabled\" is rejected.", + "Manual budget_tokens requests return 400 on Claude Fable 5 and Claude Mythos 5.", + "thinking.display defaults to omitted; set display to summarized to receive readable thinking summaries." ] } }, "attachment": true, "open_weights": false, - "release_date": "2026-09-11", - "last_updated": "2026-09-11", + "knowledge": "2026-01-31", + "release_date": "2026-06-09", + "last_updated": "2026-06-09", "type": "chat" }, { - "id": "sakana/sakana-namazu", - "name": "Sakana Namazu", - "display_name": "Sakana Namazu", + "id": "ByteDance/Seed-2.0-mini", + "name": "Seed 2.0 Mini", + "display_name": "Seed 2.0 Mini", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 65536 + "context": 256000, + "output": 32000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "none", - "high" - ] - } - }, "attachment": true, "open_weights": false, - "release_date": "2026-08-03", - "last_updated": "2026-08-03", + "release_date": "2026-02-14", + "last_updated": "2026-02-14", "type": "chat" }, { - "id": "sakana/fugu-ultra", - "name": "Fugu Ultra", - "display_name": "Fugu Ultra", + "id": "ByteDance/Seed-2.0-code", + "name": "Seed 2.0 Code", + "display_name": "Seed 2.0 Code", "modalities": { "input": [ "text", @@ -86581,10 +86823,10 @@ ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 256000, + "output": 131072 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -86593,71 +86835,62 @@ "extra_capabilities": { "reasoning": { "effort_options": [ - "high", - "xhigh", - "max" + "none", + "low", + "medium", + "high" ] } }, "attachment": true, "open_weights": false, - "release_date": "2026-06-15", - "last_updated": "2026-06-15", + "release_date": "2026-02-14", + "last_updated": "2026-02-14", "type": "chat" }, { - "id": "xingchen-agi/xing4.0-29b-a4b:free", - "name": "XingChen: Xing4.0 29B A4B (free)", - "display_name": "XingChen: Xing4.0 29B A4B (free)", + "id": "ByteDance/Seed-2.0-pro", + "name": "Seed 2.0 Pro", + "display_name": "Seed 2.0 Pro", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 235929 + "context": 256000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "none", - "high" - ] - } - }, - "attachment": false, + "attachment": true, "open_weights": false, - "release_date": "2026-10-16", - "last_updated": "2026-10-16", + "release_date": "2026-02-14", + "last_updated": "2026-02-14", "type": "chat" }, { - "id": "xiaomi/mimo-v2.6-pro", - "name": "MiMo-V2.6-Pro", - "display_name": "MiMo-V2.6-Pro", + "id": "MiniMaxAI/MiniMax-M2.5", + "name": "MiniMax M2.5", + "display_name": "MiniMax M2.5", "modalities": { "input": [ - "text", - "image", - "audio", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, + "context": 196608, "output": 131072 }, "temperature": true, @@ -86668,35 +86901,36 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "none", - "high" + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, - "attachment": true, + "attachment": false, "open_weights": true, - "release_date": "2026-09-22", - "last_updated": "2026-09-22", + "knowledge": "2025-06", + "release_date": "2026-02-12", + "last_updated": "2026-02-12", "type": "chat" }, { - "id": "xiaomi/mimo-v2.6-flash", - "name": "MiMo-V2.6-Flash", - "display_name": "MiMo-V2.6-Flash", + "id": "MiniMaxAI/MiniMax-M2.7", + "name": "MiniMax-M2.7", + "display_name": "MiniMax-M2.7", "modalities": { "input": [ - "text", - "image", - "audio", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, + "context": 196608, "output": 131072 }, "temperature": true, @@ -86707,27 +86941,29 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "none", - "high" + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, - "attachment": true, + "attachment": false, "open_weights": true, - "release_date": "2026-09-22", - "last_updated": "2026-09-22", + "release_date": "2026-03-18", + "last_updated": "2026-03-18", "type": "chat" }, { - "id": "xiaomi/mimo-v2.6-pro-ultraspeed", - "name": "MiMo-V2.6-Pro-UltraSpeed", - "display_name": "MiMo-V2.6-Pro-UltraSpeed", + "id": "MiniMaxAI/MiniMax-M3", + "name": "MiniMax-M3", + "display_name": "MiniMax-M3", "modalities": { "input": [ "text", "image", - "audio", "video" ], "output": [ @@ -86735,8 +86971,8 @@ ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 524288, + "output": 512000 }, "temperature": true, "tool_call": true, @@ -86746,36 +86982,30 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "none", - "high" - ] + "supported": true } }, "attachment": true, "open_weights": true, - "release_date": "2026-09-21", - "last_updated": "2026-09-21", + "release_date": "2026-06-01", + "last_updated": "2026-06-01", "type": "chat" }, { - "id": "xiaomi/mimo-v2.5", - "name": "MiMo-V2.5", - "display_name": "MiMo-V2.5", + "id": "deepseek-ai/DeepSeek-R1-0528", + "name": "DeepSeek-R1-0528", + "display_name": "DeepSeek-R1-0528", "modalities": { "input": [ - "text", - "image", - "audio", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 65536 + "context": 163840, + "output": 64000 }, "temperature": true, "tool_call": true, @@ -86791,24 +87021,20 @@ "visibility": "summary", "continuation": [ "thinking_blocks" - ], - "effort_options": [ - "none", - "high" ] } }, - "attachment": true, + "attachment": false, "open_weights": true, - "knowledge": "2024-12", - "release_date": "2026-04-22", - "last_updated": "2026-04-22", + "knowledge": "2024-07", + "release_date": "2025-05-28", + "last_updated": "2025-05-28", "type": "chat" }, { - "id": "xiaomi/mimo-v2.5-pro", - "name": "MiMo-V2.5-Pro", - "display_name": "MiMo-V2.5-Pro", + "id": "deepseek-ai/DeepSeek-V4-Pro", + "name": "DeepSeek V4 Pro", + "display_name": "DeepSeek V4 Pro", "modalities": { "input": [ "text" @@ -86818,8 +87044,8 @@ ] }, "limit": { - "context": 262144, - "output": 235929 + "context": 1048576, + "output": 16384 }, "temperature": true, "tool_call": true, @@ -86830,43 +87056,75 @@ "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "high", + "effort_options": [ + "low", + "high", + "max" + ], "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ "thinking_blocks" ], - "effort_options": [ - "none", - "high" + "notes": [ + "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." ] } }, "attachment": false, "open_weights": true, - "knowledge": "2024-12", - "release_date": "2026-04-22", - "last_updated": "2026-04-22", + "knowledge": "2025-05", + "release_date": "2026-04-24", + "last_updated": "2026-04-24", "type": "chat" }, { - "id": "nvidia/nemotron-3-nano-omni-30b-a3b-reasoning:free", - "name": "NVIDIA: Nemotron 3 Nano Omni (free)", - "display_name": "NVIDIA: Nemotron 3 Nano Omni (free)", + "id": "deepseek-ai/DeepSeek-V3", + "name": "DeepSeek-V3", + "display_name": "DeepSeek-V3", + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 163840, + "output": 8192 + }, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": false + }, + "attachment": false, + "open_weights": true, + "release_date": "2024-12-26", + "last_updated": "2024-12-26", + "type": "chat" + }, + { + "id": "deepseek-ai/DeepSeek-V4.1-Flash-1017", + "name": "DeepSeek V4.1 Flash 1017", + "display_name": "DeepSeek V4.1 Flash 1017", "modalities": { "input": [ "text", - "image", - "video", - "audio" + "image" ], "output": [ "text" ] }, "limit": { - "context": 256000, - "output": 65536 + "context": 1048576, + "output": 384000 }, "temperature": true, "tool_call": true, @@ -86878,20 +87136,23 @@ "reasoning": { "effort_options": [ "none", - "high" + "low", + "high", + "max" ] } }, "attachment": true, "open_weights": true, - "release_date": "2026-04-28", - "last_updated": "2026-04-28", + "knowledge": "2025-05", + "release_date": "2026-10-17", + "last_updated": "2026-10-17", "type": "chat" }, { - "id": "nvidia/nemotron-3.5-content-safety", - "name": "Nemotron 3.5 Content Safety", - "display_name": "Nemotron 3.5 Content Safety", + "id": "deepseek-ai/DeepSeek-V4.1-Flash", + "name": "DeepSeek V4.1 Flash", + "display_name": "DeepSeek V4.1 Flash", "modalities": { "input": [ "text", @@ -86902,11 +87163,11 @@ ] }, "limit": { - "context": 131072, - "output": 117964 + "context": 1048576, + "output": 384000 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { "supported": true, "default": true @@ -86916,31 +87177,36 @@ "supported": true, "effort_options": [ "none", - "high" + "low", + "high", + "xhigh", + "max" ] } }, "attachment": true, "open_weights": true, - "release_date": "2026-06-02", - "last_updated": "2026-06-02", + "knowledge": "2025-05", + "release_date": "2026-09-10", + "last_updated": "2026-09-10", "type": "chat" }, { - "id": "nvidia/nemotron-3-ultra-550b-a55b", - "name": "Nemotron 3 Ultra 550B A55B", - "display_name": "Nemotron 3 Ultra 550B A55B", + "id": "deepseek-ai/DeepSeek-V4-Flash-Vision-Exp", + "name": "DeepSeek V4 Flash Vision Exp", + "display_name": "DeepSeek V4 Flash Vision Exp", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 202800, - "output": 182520 + "context": 1048576, + "output": 384000 }, "temperature": true, "tool_call": true, @@ -86951,24 +87217,35 @@ "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "high", "effort_options": [ - "none", - "medium", - "high" + "low", + "high", + "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." ] } }, - "attachment": false, + "attachment": true, "open_weights": true, - "knowledge": "2025-09", - "release_date": "2026-06-04", - "last_updated": "2026-06-04", + "release_date": "2026-08-21", + "last_updated": "2026-09-01", "type": "chat" }, { - "id": "nvidia/nemotron-3.5-lightning", - "name": "Nemotron 3.5 Lightning 30B A3B", - "display_name": "Nemotron 3.5 Lightning 30B A3B", + "id": "deepseek-ai/DeepSeek-V3-0324", + "name": "DeepSeek V3 0324", + "display_name": "DeepSeek V3 0324", "modalities": { "input": [ "text" @@ -86978,35 +87255,24 @@ ] }, "limit": { - "context": 262144, - "output": 235929 + "context": 163840, + "output": 163840 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "effort_options": [ - "none", - "high" - ] - } + "supported": false }, "attachment": false, "open_weights": true, - "knowledge": "2025-09", - "release_date": "2026-08-11", - "last_updated": "2026-08-11", + "release_date": "2025-03-24", + "last_updated": "2025-03-24", "type": "chat" }, { - "id": "nvidia/nemotron-3-ultra-550b-a55b:free", - "name": "NVIDIA: Nemotron 3 Ultra (free)", - "display_name": "NVIDIA: Nemotron 3 Ultra (free)", + "id": "deepseek-ai/DeepSeek-V3.1", + "name": "DeepSeek-V3.1", + "display_name": "DeepSeek-V3.1", "modalities": { "input": [ "text" @@ -87016,8 +87282,8 @@ ] }, "limit": { - "context": 1000000, - "output": 65536 + "context": 163840, + "output": 8192 }, "temperature": true, "tool_call": true, @@ -87025,26 +87291,16 @@ "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "none", - "medium", - "high" - ] - } - }, "attachment": false, "open_weights": true, - "knowledge": "2025-09", - "release_date": "2026-06-04", - "last_updated": "2026-06-04", + "release_date": "2025-08-21", + "last_updated": "2025-08-21", "type": "chat" }, { - "id": "nvidia/nemotron-3-super-120b-a12b", - "name": "Nemotron 3 Super 120B A12B", - "display_name": "Nemotron 3 Super 120B A12B", + "id": "deepseek-ai/DeepSeek-V4-Flash-0731", + "name": "DeepSeek V4 Flash 0731", + "display_name": "DeepSeek V4 Flash 0731", "modalities": { "input": [ "text" @@ -87055,7 +87311,7 @@ }, "limit": { "context": 1048576, - "output": 262144 + "output": 384000 }, "temperature": true, "tool_call": true, @@ -87065,24 +87321,25 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, "effort_options": [ - "none", "low", - "medium" + "high", + "max" ] } }, "attachment": false, "open_weights": true, - "knowledge": "2025-06", - "release_date": "2026-03-11", - "last_updated": "2026-03-11", + "knowledge": "2025-05", + "release_date": "2026-07-31", + "last_updated": "2026-07-31", "type": "chat" }, { - "id": "nvidia/nemotron-3.5-lightning:free", - "name": "NVIDIA: Nemotron 3.5 Lightning (free)", - "display_name": "NVIDIA: Nemotron 3.5 Lightning (free)", + "id": "deepseek-ai/DeepSeek-V4-Pro-0813", + "name": "DeepSeek V4 Pro 0813", + "display_name": "DeepSeek V4 Pro 0813", "modalities": { "input": [ "text" @@ -87092,8 +87349,8 @@ ] }, "limit": { - "context": 1000000, - "output": 65536 + "context": 1048576, + "output": 384000 }, "temperature": true, "tool_call": true, @@ -87103,23 +87360,24 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, "effort_options": [ - "none", - "high" + "low", + "high", + "max" ] } }, "attachment": false, "open_weights": true, - "knowledge": "2025-09", - "release_date": "2026-08-11", - "last_updated": "2026-08-11", + "release_date": "2026-08-13", + "last_updated": "2026-08-22", "type": "chat" }, { - "id": "nvidia/nemotron-3-nano-30b-a3b", - "name": "Nemotron 3 Nano 30B A3B", - "display_name": "Nemotron 3 Nano 30B A3B", + "id": "deepseek-ai/DeepSeek-V4-Flash", + "name": "DeepSeek V4 Flash", + "display_name": "DeepSeek V4 Flash", "modalities": { "input": [ "text" @@ -87129,8 +87387,8 @@ ] }, "limit": { - "context": 262144, - "output": 235929 + "context": 1048576, + "output": 16384 }, "temperature": true, "tool_call": true, @@ -87140,36 +87398,48 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "high", "effort_options": [ - "none", - "high" - ] + "low", + "high", + "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." + ] } }, "attachment": false, "open_weights": true, - "knowledge": "2025-06-25", - "release_date": "2025-12-15", - "last_updated": "2025-12-15", + "knowledge": "2025-05", + "release_date": "2026-04-24", + "last_updated": "2026-04-24", "type": "chat" }, { - "id": "nvidia/nemotron-3.5-super-vl", - "name": "NVIDIA: Nemotron 3.5 Super VL", - "display_name": "NVIDIA: Nemotron 3.5 Super VL", + "id": "deepseek-ai/DeepSeek-V3.2", + "name": "DeepSeek-V3.2", + "display_name": "DeepSeek-V3.2", "modalities": { "input": [ - "text", - "image", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 235929 + "context": 163840, + "output": 64000 }, "temperature": true, "tool_call": true, @@ -87179,221 +87449,165 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "none", - "high" + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, - "attachment": true, + "attachment": false, "open_weights": true, - "release_date": "2026-10-15", - "last_updated": "2026-10-15", + "knowledge": "2024-07", + "release_date": "2025-12-01", + "last_updated": "2025-12-01", "type": "chat" - }, + } + ] + }, + "klokintegration": { + "id": "klokintegration", + "name": "klokintegration.se", + "display_name": "klokintegration.se", + "api": "https://api-gw.klok.ipaas.se/proxy/kloker-key/v1", + "doc": "https://klokintegration.se/docs/ai-api", + "models": [ { - "id": "nvidia/nemotron-3.5-content-safety:free", - "name": "NVIDIA: Nemotron 3.5 Content Safety (free)", - "display_name": "NVIDIA: Nemotron 3.5 Content Safety (free)", + "id": "Kloker-Integration-Architect", + "name": "Kloker Integration Architect", + "display_name": "Kloker Integration Architect", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 8192 + "context": 200000, + "output": 50000 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "none", - "high" - ] - } + "supported": false }, - "attachment": true, - "open_weights": true, - "release_date": "2026-06-02", - "last_updated": "2026-06-02", + "attachment": false, + "open_weights": false, + "release_date": "2026-08-29", + "last_updated": "2026-08-29", "type": "chat" }, { - "id": "nvidia/nemotron-3.5-super-vl:free", - "name": "NVIDIA: Nemotron 3.5 Super VL (free)", - "display_name": "NVIDIA: Nemotron 3.5 Super VL (free)", + "id": "Kloker-Integration-Developer", + "name": "Kloker Integration Developer", + "display_name": "Kloker Integration Developer", "modalities": { "input": [ - "text", - "image", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 200000, + "output": 50000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "none", - "high" - ] - } + "supported": false }, - "attachment": true, - "open_weights": true, - "release_date": "2026-10-15", - "last_updated": "2026-10-15", + "attachment": false, + "open_weights": false, + "release_date": "2026-08-29", + "last_updated": "2026-08-29", "type": "chat" }, { - "id": "anthropic/claude-fable-5.1", - "name": "Claude Fable 5.1", - "display_name": "Claude Fable 5.1", + "id": "Kloker", + "name": "Kloker", + "display_name": "Kloker", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 200000, + "output": 50000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "high", - "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "omitted", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Adaptive thinking is always on for Claude Fable 5 and Claude Mythos 5; thinking.type = \"disabled\" is rejected.", - "Manual budget_tokens requests return 400 on Claude Fable 5 and Claude Mythos 5.", - "thinking.display defaults to omitted; set display to summarized to receive readable thinking summaries." - ] - } + "supported": false }, - "attachment": true, + "attachment": false, "open_weights": false, - "knowledge": "2026-06", - "release_date": "2026-09-01", - "last_updated": "2026-09-01", + "release_date": "2026-08-29", + "last_updated": "2026-08-29", "type": "chat" - }, + } + ] + }, + "nvidia": { + "id": "nvidia", + "name": "Nvidia", + "display_name": "Nvidia", + "api": "https://integrate.api.nvidia.com/v1", + "doc": "https://docs.api.nvidia.com/nim/", + "models": [ { - "id": "anthropic/claude-opus-4.1", - "name": "Claude Opus 4.1 (latest)", - "display_name": "Claude Opus 4.1 (latest)", + "id": "openai/whisper-large-v3", + "name": "Whisper Large v3", + "display_name": "Whisper Large v3", "modalities": { "input": [ - "text", - "image", - "pdf" + "audio" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 32000 + "context": 448, + "output": 4096 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": false - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "budget", - "budget": { - "min": 1024, - "unit": "tokens" - }, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", - "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." - ] - } + "supported": false }, "attachment": true, - "open_weights": false, - "knowledge": "2025-03-31", - "release_date": "2025-08-05", - "last_updated": "2025-08-05", - "type": "chat" + "open_weights": true, + "knowledge": "2023-09", + "release_date": "2023-11-06", + "last_updated": "2023-11-06" }, { - "id": "anthropic/claude-opus-5", - "name": "Claude Opus 5", - "display_name": "Claude Opus 5", + "id": "openai/gpt-oss-20b", + "name": "GPT OSS 20B", + "display_name": "GPT OSS 20B", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 131072, + "output": 32768 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -87403,249 +87617,136 @@ "reasoning": { "supported": true, "effort_options": [ - "none", "low", "medium", - "high", - "xhigh", - "max" + "high" ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-05", - "release_date": "2026-07-24", - "last_updated": "2026-07-24", + "attachment": false, + "open_weights": true, + "knowledge": "2024-06", + "release_date": "2025-08-05", + "last_updated": "2025-08-05", "type": "chat" }, { - "id": "anthropic/claude-opus-4.8", - "name": "Claude Opus 4.8", - "display_name": "Claude Opus 4.8", + "id": "nvidia/nemotron-3.5-lightning-30b-a3b", + "name": "Nemotron 3.5 Lightning 30B A3B", + "display_name": "Nemotron 3.5 Lightning 30B A3B", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 262144, + "output": 262144 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "high", - "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "omitted", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Claude Opus 4.7 and newer Opus models require thinking.type = \"adaptive\" to enable thinking explicitly.", - "Manual budget_tokens requests return 400 on Claude Opus 4.7 and newer adaptive-only Opus models.", - "task_budget is separate from thinking control and should not be treated as a thinking budget." - ] - } + "default": true }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-01", - "release_date": "2026-05-28", - "last_updated": "2026-05-28", + "attachment": false, + "open_weights": true, + "knowledge": "2025-09", + "release_date": "2026-08-11", + "last_updated": "2026-08-11", "type": "chat" }, { - "id": "anthropic/claude-sonnet-5", - "name": "Claude Sonnet 5", - "display_name": "Claude Sonnet 5", + "id": "nvidia/llama-3.1-nemotron-70b-instruct", + "name": "Llama 3.1 Nemotron 70B Instruct", + "display_name": "Llama 3.1 Nemotron 70B Instruct", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 128000, + "output": 8192 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "effort_options": [ - "none", - "low", - "medium", - "high", - "xhigh", - "max" - ] - } + "supported": false }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-01-31", - "release_date": "2026-06-30", - "last_updated": "2026-06-30", + "attachment": false, + "open_weights": true, + "release_date": "2025-04-15", + "last_updated": "2025-04-15", "type": "chat" }, { - "id": "anthropic/claude-opus-4.6", - "name": "Claude Opus 4.6", - "display_name": "Claude Opus 4.6", + "id": "nvidia/usdvalidate", + "name": "usdvalidate", + "display_name": "usdvalidate", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 8192, + "output": 4096 }, - "temperature": true, - "tool_call": true, + "temperature": false, + "tool_call": false, "reasoning": { - "supported": true, - "default": false - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "mixed", - "budget": { - "min": 1024, - "unit": "tokens" - }, - "effort": "high", - "effort_options": [ - "low", - "medium", - "high", - "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Anthropic recommends adaptive thinking with effort for Claude 4.6; budget_tokens remains a deprecated compatibility path.", - "Anthropic API defaults effort to high; lower effort levels should be chosen per workload." - ] - } + "supported": false }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-05-31", - "release_date": "2026-02-05", - "last_updated": "2026-03-13", + "attachment": false, + "open_weights": true, + "release_date": "2024-07-24", + "last_updated": "2025-01-08", "type": "chat" }, { - "id": "anthropic/claude-opus-4.5", - "name": "Claude Opus 4.5 (latest)", - "display_name": "Claude Opus 4.5 (latest)", + "id": "nvidia/usdcode", + "name": "usdcode", + "display_name": "usdcode", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 64000 + "context": 128000, + "output": 4096 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": false - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "mixed", - "budget": { - "min": 1024, - "unit": "tokens" - }, - "effort": "high", - "effort_options": [ - "low", - "medium", - "high" - ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Claude Opus 4.5 uses manual thinking.type = \"enabled\" with budget_tokens; effort can be used alongside the thinking budget.", - "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header." - ] - } + "supported": false }, - "attachment": true, + "attachment": false, "open_weights": false, - "knowledge": "2025-05", - "release_date": "2025-11-24", - "last_updated": "2025-11-24", + "release_date": "2026-01-01", + "last_updated": "2026-01-01", "type": "chat" }, { - "id": "anthropic/claude-sonnet-4.6", - "name": "Claude Sonnet 4.6", - "display_name": "Claude Sonnet 4.6", + "id": "nvidia/nemotron-3-ultra-550b-a55b", + "name": "Nemotron 3 Ultra 550B A55B", + "display_name": "Nemotron 3 Ultra 550B A55B", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" @@ -87653,220 +87754,160 @@ }, "limit": { "context": 1000000, - "output": 128000 + "output": 65536 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "mixed", - "budget": { - "min": 1024, - "unit": "tokens" - }, - "effort": "high", "effort_options": [ - "low", + "none", "medium", - "high", - "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Anthropic recommends adaptive thinking with effort for Claude 4.6; budget_tokens remains a deprecated compatibility path.", - "Anthropic API defaults effort to high; lower effort levels should be chosen per workload." + "high" ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-02-17", - "last_updated": "2026-03-13", + "attachment": false, + "open_weights": true, + "knowledge": "2025-09", + "release_date": "2026-06-04", + "last_updated": "2026-06-04", "type": "chat" }, { - "id": "anthropic/claude-haiku-4.5", - "name": "Claude Haiku 4.5 (latest)", - "display_name": "Claude Haiku 4.5 (latest)", + "id": "nvidia/synthetic-video-detector", + "name": "synthetic-video-detector", + "display_name": "synthetic-video-detector", "modalities": { "input": [ - "text", - "image", - "pdf" + "video" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 64000 + "context": 8192, + "output": 4096 }, - "temperature": true, - "tool_call": true, + "temperature": false, + "tool_call": false, "reasoning": { - "supported": true, - "default": false - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "budget", - "budget": { - "min": 1024, - "unit": "tokens" - }, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", - "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." - ] - } + "supported": false }, "attachment": true, - "open_weights": false, - "knowledge": "2025-02-28", - "release_date": "2025-10-15", - "last_updated": "2025-10-15", + "open_weights": true, + "release_date": "2026-04-16", + "last_updated": "2026-04-16", "type": "chat" }, { - "id": "anthropic/claude-opus-4.7", - "name": "Claude Opus 4.7", - "display_name": "Claude Opus 4.7", + "id": "nvidia/cosmos-transfer2_5-2b", + "name": "cosmos-transfer2.5-2b", + "display_name": "cosmos-transfer2.5-2b", "modalities": { "input": [ "text", "image", - "pdf" + "video" ], "output": [ - "text" + "video" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 8192, + "output": 4096 }, "temperature": false, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": false + "supported": false }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "high", - "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "omitted", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Claude Opus 4.7 and newer Opus models require thinking.type = \"adaptive\" to enable thinking explicitly.", - "Manual budget_tokens requests return 400 on Claude Opus 4.7 and newer adaptive-only Opus models.", - "task_budget is separate from thinking control and should not be treated as a thinking budget." - ] - } + "attachment": true, + "open_weights": true, + "release_date": "2026-02-26", + "last_updated": "2026-02-26" + }, + { + "id": "nvidia/sparsedrive", + "name": "sparsedrive", + "display_name": "sparsedrive", + "modalities": { + "input": [ + "video" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 8192 + }, + "temperature": false, + "tool_call": false, + "reasoning": { + "supported": false }, "attachment": true, - "open_weights": false, - "knowledge": "2026-01-31", - "release_date": "2026-04-16", - "last_updated": "2026-04-16", + "open_weights": true, + "release_date": "2025-03-18", + "last_updated": "2025-07-20", "type": "chat" }, { - "id": "anthropic/claude-sonnet-5.5", - "name": "Claude Sonnet 5.5", - "display_name": "Claude Sonnet 5.5", + "id": "nvidia/llama-nemotron-embed-vl-1b-v2", + "name": "llama-nemotron-embed-vl-1b-v2", + "display_name": "llama-nemotron-embed-vl-1b-v2", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 8192, + "output": 2048 }, "temperature": false, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" - ] - } + "supported": false }, "attachment": true, - "open_weights": false, - "knowledge": "2026-06", - "release_date": "2026-09-28", - "last_updated": "2026-09-28", - "type": "chat" + "open_weights": true, + "release_date": "2025-12-18", + "last_updated": "2025-12-18", + "type": "embedding" }, { - "id": "anthropic/claude-fable-5", - "name": "Claude Fable 5", - "display_name": "Claude Fable 5", + "id": "nvidia/nemotron-3.5-content-safety", + "name": "Nemotron 3.5 Content Safety", + "display_name": "Nemotron 3.5 Content Safety", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 128000, + "output": 8192 }, - "temperature": false, - "tool_call": true, + "temperature": true, + "tool_call": false, "reasoning": { "supported": true, "default": true @@ -87874,180 +87915,125 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "high", - "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" - ], "interleaved": true, "summaries": true, - "visibility": "omitted", + "visibility": "summary", "continuation": [ "thinking_blocks" - ], - "notes": [ - "Adaptive thinking is always on for Claude Fable 5 and Claude Mythos 5; thinking.type = \"disabled\" is rejected.", - "Manual budget_tokens requests return 400 on Claude Fable 5 and Claude Mythos 5.", - "thinking.display defaults to omitted; set display to summarized to receive readable thinking summaries." ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2026-01-31", - "release_date": "2026-06-09", - "last_updated": "2026-06-09", + "open_weights": true, + "release_date": "2026-06-02", + "last_updated": "2026-06-02", "type": "chat" }, { - "id": "anthropic/claude-sonnet-4.5", - "name": "Claude Sonnet 4.5 (latest)", - "display_name": "Claude Sonnet 4.5 (latest)", + "id": "nvidia/nemotron-3.5-super-vl-120b-a12b", + "name": "Nemotron 3.5 Super VL 120B A12B", + "display_name": "Nemotron 3.5 Super VL 120B A12B", "modalities": { "input": [ "text", "image", - "pdf" + "video" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 64000 + "context": 1048576, + "output": 8192 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "budget", - "budget": { - "min": 1024, - "unit": "tokens" - }, "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ "thinking_blocks" - ], - "notes": [ - "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", - "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2025-07-31", - "release_date": "2025-09-29", - "last_updated": "2025-09-29", + "open_weights": true, + "release_date": "2026-10-15", + "last_updated": "2026-10-15", "type": "chat" }, { - "id": "anthropic/claude-haiku-5.5", - "name": "Claude Haiku 5.5", - "display_name": "Claude Haiku 5.5", + "id": "nvidia/riva-translate-4b-instruct-v1.1", + "name": "riva-translate-4b-instruct-v1_1", + "display_name": "riva-translate-4b-instruct-v1_1", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 128000, + "output": 4096 }, "temperature": false, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "none", - "low", - "medium", - "high", - "xhigh", - "max" - ] - } + "supported": false }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-06", - "release_date": "2026-10-07", - "last_updated": "2026-10-07", + "attachment": false, + "open_weights": true, + "release_date": "2025-12-12", + "last_updated": "2025-12-12", "type": "chat" }, { - "id": "anthropic/claude-opus-5.5", - "name": "Claude Opus 5.5", - "display_name": "Claude Opus 5.5", + "id": "nvidia/llama-nemotron-rerank-vl-1b-v2", + "name": "llama-nemotron-rerank-vl-1b-v2", + "display_name": "llama-nemotron-rerank-vl-1b-v2", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 10240, + "output": 4096 }, "temperature": false, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" - ] - } + "supported": false }, "attachment": true, - "open_weights": false, - "knowledge": "2026-06", - "release_date": "2026-09-22", - "last_updated": "2026-09-22", - "type": "chat" + "open_weights": true, + "release_date": "2025-12-18", + "last_updated": "2025-12-18" }, { - "id": "stealth/glyph-cluster", - "name": "Stealth: Glyph Cluster (free)", - "display_name": "Stealth: Glyph Cluster (free)", + "id": "nvidia/nemotron-3-nano-omni-30b-a3b-reasoning", + "name": "Nemotron 3 Nano Omni", + "display_name": "Nemotron 3 Nano Omni", "modalities": { "input": [ - "text" + "text", + "image", + "video", + "audio" ], "output": [ "text" @@ -88055,7 +88041,7 @@ }, "limit": { "context": 256000, - "output": 256000 + "output": 65536 }, "temperature": true, "tool_call": true, @@ -88065,80 +88051,76 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "low", - "medium", - "high", - "xhigh" - ] + "supported": true } }, - "attachment": false, - "open_weights": false, - "release_date": "2025-08-26", - "last_updated": "2025-08-26", + "attachment": true, + "open_weights": true, + "release_date": "2026-04-28", + "last_updated": "2026-04-28", "type": "chat" }, { - "id": "thedrummer/skyfall-36b-v2", - "name": "TheDrummer: Skyfall 36B V2", - "display_name": "TheDrummer: Skyfall 36B V2", + "id": "nvidia/cosmos-reason2-8b", + "name": "Cosmos Reason2 8B", + "display_name": "Cosmos Reason2 8B", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 32768, - "output": 29491 + "context": 131072, + "output": 16384 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, + "attachment": true, "open_weights": true, - "knowledge": "2024-06-30", - "release_date": "2025-03-10", - "last_updated": "2025-03-10", + "release_date": "2025-12-01", + "last_updated": "2025-12-01", "type": "chat" }, { - "id": "thedrummer/cydonia-24b-v4.1", - "name": "TheDrummer: Cydonia 24B V4.1", - "display_name": "TheDrummer: Cydonia 24B V4.1", + "id": "nvidia/magpie-tts-zeroshot", + "name": "magpie-tts-zeroshot", + "display_name": "magpie-tts-zeroshot", "modalities": { "input": [ - "text" + "text", + "audio" ], "output": [ - "text" + "audio" ] }, "limit": { - "context": 131072, - "output": 117964 + "context": 8192, + "output": 4096 }, - "temperature": true, + "temperature": false, "tool_call": false, "reasoning": { "supported": false }, - "attachment": false, + "attachment": true, "open_weights": true, - "knowledge": "2024-04-30", - "release_date": "2025-09-27", - "last_updated": "2025-09-27", - "type": "chat" + "release_date": "2025-05-22", + "last_updated": "2025-06-12" }, { - "id": "thedrummer/unslopnemo-12b", - "name": "TheDrummer: UnslopNemo 12B", - "display_name": "TheDrummer: UnslopNemo 12B", + "id": "nvidia/rerank-qa-mistral-4b", + "name": "rerank-qa-mistral-4b", + "display_name": "rerank-qa-mistral-4b", "modalities": { "input": [ "text" @@ -88148,94 +88130,77 @@ ] }, "limit": { - "context": 1024000, - "output": 819200 + "context": 128000, + "output": 4096 }, - "temperature": true, + "temperature": false, "tool_call": false, "reasoning": { "supported": false }, "attachment": false, "open_weights": true, - "knowledge": "2024-04-30", - "release_date": "2024-11-08", - "last_updated": "2024-11-08", - "type": "chat" + "release_date": "2024-03-17", + "last_updated": "2025-01-17" }, { - "id": "particle-ai/particle-1", - "name": "Particle AI: Particle-1", - "display_name": "Particle AI: Particle-1", + "id": "nvidia/bevformer", + "name": "bevformer", + "display_name": "bevformer", "modalities": { "input": [ - "text", - "image" + "video" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 128000, + "output": 8192 }, - "temperature": true, - "tool_call": true, + "temperature": false, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "none", - "minimal", - "low", - "medium", - "high", - "xhigh", - "max" - ] - } + "supported": false }, "attachment": true, - "open_weights": false, - "release_date": "2026-10-28", - "last_updated": "2026-10-28", + "open_weights": true, + "release_date": "2025-03-18", + "last_updated": "2025-07-20", "type": "chat" }, { - "id": "morph/morph-v3-fast", - "name": "Morph: Morph V3 Fast", - "display_name": "Morph: Morph V3 Fast", + "id": "nvidia/streampetr", + "name": "streampetr", + "display_name": "streampetr", "modalities": { "input": [ - "text" + "video" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 131072 + "context": 128000, + "output": 8192 }, - "temperature": true, + "temperature": false, "tool_call": false, "reasoning": { "supported": false }, - "attachment": false, - "open_weights": false, - "release_date": "2025-07-07", - "last_updated": "2025-07-07", + "attachment": true, + "open_weights": true, + "release_date": "2025-11-13", + "last_updated": "2025-11-13", "type": "chat" }, { - "id": "morph/morph-v3-large", - "name": "Morph: Morph V3 Large", - "display_name": "Morph: Morph V3 Large", + "id": "nvidia/llama-3.1-nemotron-ultra-253b-v1", + "name": "Llama 3.1 Nemotron Ultra 253B", + "display_name": "Llama 3.1 Nemotron Ultra 253B", "modalities": { "input": [ "text" @@ -88245,141 +88210,145 @@ ] }, "limit": { - "context": 262144, - "output": 131072 + "context": 128000, + "output": 16384 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, "attachment": false, - "open_weights": false, - "release_date": "2025-07-07", - "last_updated": "2025-07-07", + "open_weights": true, + "release_date": "2025-04-07", + "last_updated": "2025-04-07", "type": "chat" }, { - "id": "microsoft/phi-4", - "name": "Microsoft: Phi 4", - "display_name": "Microsoft: Phi 4", + "id": "nvidia/active-speaker-detection", + "name": "Active Speaker Detection", + "display_name": "Active Speaker Detection", "modalities": { "input": [ - "text" + "video" ], "output": [ "text" ] }, "limit": { - "context": 16384, - "output": 14745 - }, - "temperature": true, + "context": 8192, + "output": 4096 + }, + "temperature": false, "tool_call": false, "reasoning": { "supported": false }, - "attachment": false, - "open_weights": false, - "release_date": "2025-01-10", - "last_updated": "2025-01-10", + "attachment": true, + "open_weights": true, + "release_date": "2026-04-16", + "last_updated": "2026-04-16", "type": "chat" }, { - "id": "z-ai/glm-4.5v", - "name": "GLM-4.5V", - "display_name": "GLM-4.5V", + "id": "nvidia/studiovoice", + "name": "studiovoice", + "display_name": "studiovoice", "modalities": { "input": [ - "text", - "image" + "audio" ], "output": [ - "text" + "audio" ] }, "limit": { - "context": 65536, - "output": 16384 + "context": 8192, + "output": 8192 }, - "temperature": true, - "tool_call": true, + "temperature": false, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "none", - "high" - ] - } + "supported": false }, "attachment": true, "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-08-11", - "last_updated": "2025-08-11", + "release_date": "2024-10-03", + "last_updated": "2025-06-13", "type": "chat" }, { - "id": "z-ai/glm-5.3-flash", - "name": "GLM-5.3-Flash", - "display_name": "GLM-5.3-Flash", + "id": "nvidia/nemotron-voicechat", + "name": "nemotron-voicechat", + "display_name": "nemotron-voicechat", "modalities": { "input": [ "text", - "image", - "video" + "audio" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 128000, + "output": 8192 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "effort_options": [ - "low", - "high", - "max" - ] - } + "supported": false }, "attachment": true, "open_weights": true, - "release_date": "2026-08-26", - "last_updated": "2026-08-26", + "release_date": "2026-03-16", + "last_updated": "2026-03-16", "type": "chat" }, { - "id": "z-ai/glm-5v-turbo", - "name": "GLM-5V-Turbo", - "display_name": "GLM-5V-Turbo", + "id": "nvidia/llama-3.1-nemotron-safety-guard-8b-v3", + "name": "llama-3.1-nemotron-safety-guard-8b-v3", + "display_name": "llama-3.1-nemotron-safety-guard-8b-v3", + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 4096 + }, + "temperature": false, + "tool_call": false, + "reasoning": { + "supported": false + }, + "attachment": false, + "open_weights": true, + "release_date": "2025-10-28", + "last_updated": "2025-10-28", + "type": "chat" + }, + { + "id": "z-ai/glm-5.3-flash", + "name": "GLM-5.3-Flash", + "display_name": "GLM-5.3-Flash", "modalities": { "input": [ - "image", "text", - "video" + "image" ], "output": [ "text" ] }, "limit": { - "context": 202752, + "context": 1000000, "output": 131072 }, "temperature": true, @@ -88390,22 +88359,30 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ - "none", - "high" + "low", + "high", + "max" ] } }, "attachment": true, - "open_weights": false, - "release_date": "2026-04-01", - "last_updated": "2026-04-01", + "open_weights": true, + "release_date": "2026-08-26", + "last_updated": "2026-08-26", "type": "chat" }, { - "id": "z-ai/glm-5-turbo", - "name": "GLM-5-Turbo", - "display_name": "GLM-5-Turbo", + "id": "z-ai/glm-5.3", + "name": "GLM-5.3", + "display_name": "GLM-5.3", "modalities": { "input": [ "text" @@ -88415,7 +88392,7 @@ ] }, "limit": { - "context": 202752, + "context": 1000000, "output": 131072 }, "temperature": true, @@ -88427,35 +88404,41 @@ "extra_capabilities": { "reasoning": { "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ - "none", - "high" + "low", + "high", + "max" ] } }, "attachment": false, - "open_weights": false, - "release_date": "2026-03-16", - "last_updated": "2026-03-16", + "open_weights": true, + "release_date": "2026-08-14", + "last_updated": "2026-08-14", "type": "chat" }, { - "id": "z-ai/glm-4.6v", - "name": "GLM-4.6V", - "display_name": "GLM-4.6V", + "id": "stepfun-ai/step-5-preview", + "name": "Step 5 Preview", + "display_name": "Step 5 Preview", "modalities": { "input": [ "text", - "image", - "video" + "image" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 32768 + "context": 1048576, + "output": 1048576 }, "temperature": true, "tool_call": true, @@ -88465,36 +88448,49 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ "none", - "high" + "minimal", + "low", + "medium", + "high", + "xhigh", + "max" ] } }, "attachment": true, "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-12-08", - "last_updated": "2025-12-08", + "release_date": "2026-09-16", + "last_updated": "2026-09-20", "type": "chat" }, { - "id": "z-ai/glm-4.7", - "name": "GLM-4.7", - "display_name": "GLM-4.7", + "id": "moonshotai/kimi-k3", + "name": "Kimi K3", + "display_name": "Kimi K3", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 204800, + "context": 1048576, "output": 131072 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -88510,33 +88506,35 @@ "thinking_blocks" ], "effort_options": [ - "none", - "high" + "low", + "high", + "max" ] } }, - "attachment": false, + "attachment": true, "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-12-22", - "last_updated": "2025-12-22", + "release_date": "2026-07-16", + "last_updated": "2026-07-16", "type": "chat" }, { - "id": "z-ai/glm-5.3-prime", - "name": "Z.ai: GLM 5.3 Prime", - "display_name": "Z.ai: GLM 5.3 Prime", + "id": "moonshotai/kimi-k2.6", + "name": "Kimi K2.6", + "display_name": "Kimi K2.6", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 262144, + "output": 262144 }, "temperature": true, "tool_call": true, @@ -88546,98 +88544,92 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ + "none", + "minimal", "low", + "medium", "high", + "xhigh", "max" ] } }, - "attachment": false, - "open_weights": false, - "release_date": "2026-09-23", - "last_updated": "2026-09-23", + "attachment": true, + "open_weights": true, + "knowledge": "2025-01", + "release_date": "2026-04-20", + "last_updated": "2026-04-20", "type": "chat" }, { - "id": "z-ai/glm-4.6", - "name": "GLM-4.6", - "display_name": "GLM-4.6", + "id": "google/gemma-3-12b-it", + "name": "Gemma 3 12B IT", + "display_name": "Gemma 3 12B IT", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 202752, - "output": 131072 + "context": 131072, + "output": 16384 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "effort_options": [ - "none", - "high" - ] - } + "supported": false }, - "attachment": false, + "attachment": true, "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-09-30", - "last_updated": "2025-09-30", + "knowledge": "2024-08", + "release_date": "2025-03-12", + "last_updated": "2025-03-12", "type": "chat" }, { - "id": "z-ai/glm-4.5-air", - "name": "GLM-4.5-Air", - "display_name": "GLM-4.5-Air", + "id": "google/google-paligemma", + "name": "paligemma", + "display_name": "paligemma", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 98304 + "context": 128000, + "output": 8192 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "none", - "high" - ] - } + "supported": false }, - "attachment": false, + "attachment": true, "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-07-28", - "last_updated": "2025-07-28", + "release_date": "2024-05-14", + "last_updated": "2024-08-26", "type": "chat" }, { - "id": "z-ai/glm-5.3-flashx", - "name": "Z.ai: GLM 5.3 FlashX", - "display_name": "Z.ai: GLM 5.3 FlashX", + "id": "google/diffusiongemma-26b-a4b-it", + "name": "DiffusionGemma 26B A4B IT", + "display_name": "DiffusionGemma 26B A4B IT", "modalities": { "input": [ "text", @@ -88649,8 +88641,8 @@ ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 250000, + "output": 32768 }, "temperature": true, "tool_call": true, @@ -88661,70 +88653,69 @@ "extra_capabilities": { "reasoning": { "effort_options": [ + "none", + "minimal", "low", + "medium", "high", + "xhigh", "max" ] } }, "attachment": true, - "open_weights": false, - "release_date": "2026-09-18", - "last_updated": "2026-09-18", + "open_weights": true, + "knowledge": "2025-01", + "release_date": "2026-06-09", + "last_updated": "2026-07-15", "type": "chat" }, { - "id": "z-ai/glm-5.1", - "name": "GLM-5.1", - "display_name": "GLM-5.1", + "id": "google/gemma-3-4b-it", + "name": "Gemma 3 4B IT", + "display_name": "Gemma 3 4B IT", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 202752, - "output": 131072 + "context": 131072, + "output": 16384 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "effort_options": [ - "none", - "high" - ] - } + "supported": false }, - "attachment": false, + "attachment": true, "open_weights": true, - "release_date": "2026-04-07", - "last_updated": "2026-04-07", + "knowledge": "2024-08", + "release_date": "2025-03-12", + "last_updated": "2025-03-12", "type": "chat" }, { - "id": "z-ai/glm-5", - "name": "GLM-5", - "display_name": "GLM-5", + "id": "google/gemma-4-31b-it", + "name": "Gemma-4-31B-IT", + "display_name": "Gemma-4-31B-IT", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 202752, - "output": 131072 + "context": 256000, + "output": 16384 }, "temperature": true, "tool_call": true, @@ -88734,29 +88725,20 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "effort_options": [ - "none", - "high" - ] + "supported": true } }, - "attachment": false, + "attachment": true, "open_weights": true, - "release_date": "2026-02-12", - "last_updated": "2026-02-12", + "knowledge": "2025-01", + "release_date": "2026-04-02", + "last_updated": "2026-04-02", "type": "chat" }, { - "id": "z-ai/glm-5.3", - "name": "GLM-5.3", - "display_name": "GLM-5.3", + "id": "mistralai/mistral-7b-instruct-v0.3", + "name": "Mistral-7B-Instruct-v0.3", + "display_name": "Mistral-7B-Instruct-v0.3", "modalities": { "input": [ "text" @@ -88766,35 +88748,24 @@ ] }, "limit": { - "context": 1048576, - "output": 943718 + "context": 65536, + "output": 65536 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "effort_options": [ - "low", - "high", - "max" - ] - } + "supported": false }, "attachment": false, "open_weights": true, - "release_date": "2026-08-14", - "last_updated": "2026-08-14", + "release_date": "2024-05-22", + "last_updated": "2024-05-22", "type": "chat" }, { - "id": "z-ai/glm-4.5", - "name": "GLM-4.5", - "display_name": "GLM-4.5", + "id": "mistralai/magistral-small-2506", + "name": "Magistral Small 2506", + "display_name": "Magistral Small 2506", "modalities": { "input": [ "text" @@ -88804,37 +88775,28 @@ ] }, "limit": { - "context": 131072, - "output": 98304 + "context": 32768, + "output": 32768 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "none", - "high" - ] - } + "supported": false }, "attachment": false, "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-07-28", - "last_updated": "2025-07-28", + "release_date": "2025-06-10", + "last_updated": "2025-06-10", "type": "chat" }, { - "id": "z-ai/glm-4.7-flash", - "name": "GLM-4.7-Flash", - "display_name": "GLM-4.7-Flash", + "id": "mistralai/mistral-medium-3-instruct", + "name": "Mistral Medium 3", + "display_name": "Mistral Medium 3", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" @@ -88842,193 +88804,133 @@ }, "limit": { "context": 131072, - "output": 117964 + "output": 32768 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "effort_options": [ - "none", - "high" - ] - } + "supported": false }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-04", - "release_date": "2026-01-19", - "last_updated": "2026-01-19", + "attachment": true, + "open_weights": false, + "knowledge": "2025-03-31", + "release_date": "2025-05-07", + "last_updated": "2025-05-07", "type": "chat" }, { - "id": "z-ai/glm-5.2", - "name": "GLM-5.2", - "display_name": "GLM-5.2", + "id": "black-forest-labs/flux_2-klein-4b", + "name": "FLUX.2 Klein 4B", + "display_name": "FLUX.2 Klein 4B", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ - "text" + "image" ] }, "limit": { - "context": 1048576, - "output": 943718 + "context": 8192, + "output": 8192 }, - "temperature": true, - "tool_call": true, + "temperature": false, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "effort_options": [ - "none", - "high", - "xhigh" - ] - } + "supported": false }, - "attachment": false, + "attachment": true, "open_weights": true, - "release_date": "2026-06-13", - "last_updated": "2026-06-13", - "type": "chat" + "knowledge": "2025-06", + "release_date": "2026-01-15", + "last_updated": "2026-01-15" }, { - "id": "ibm-granite/granite-4.2-8b", - "name": "IBM: Granite 4.2 8B", - "display_name": "IBM: Granite 4.2 8B", + "id": "black-forest-labs/flux.1-dev", + "name": "FLUX.1-dev", + "display_name": "FLUX.1-dev", "modalities": { "input": [ "text" ], "output": [ - "text" + "image" ] }, "limit": { - "context": 131072, - "output": 117964 + "context": 8192, + "output": 8192 }, - "temperature": true, - "tool_call": true, + "temperature": false, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "none", - "low", - "high" - ] - } + "supported": false }, "attachment": false, "open_weights": true, - "release_date": "2026-08-25", - "last_updated": "2026-08-25", - "type": "chat" + "knowledge": "2024-08", + "release_date": "2024-08-01", + "last_updated": "2024-08-01" }, { - "id": "upstage/solar-mini4", - "name": "Upstage: Solar Mini 4", - "display_name": "Upstage: Solar Mini 4", + "id": "black-forest-labs/flux_1-schnell", + "name": "FLUX.1-schnell", + "display_name": "FLUX.1-schnell", "modalities": { "input": [ "text" ], "output": [ - "text" + "image" ] }, "limit": { - "context": 524288, - "output": 131072 + "context": 8192, + "output": 8192 }, - "temperature": true, - "tool_call": true, + "temperature": false, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "none", - "minimal", - "low", - "medium", - "high", - "xhigh", - "max" - ] - } + "supported": false }, "attachment": false, - "open_weights": false, - "knowledge": "2026-02", - "release_date": "2026-09-22", - "last_updated": "2026-09-22", - "type": "chat" + "open_weights": true, + "knowledge": "2024-07", + "release_date": "2024-08-01", + "last_updated": "2024-08-01" }, { - "id": "upstage/solar-pro4", - "name": "Upstage: Solar Pro 4", - "display_name": "Upstage: Solar Pro 4", + "id": "black-forest-labs/flux_1-kontext-dev", + "name": "FLUX.1-Kontext-dev", + "display_name": "FLUX.1-Kontext-dev", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ - "text" + "image" ] }, "limit": { - "context": 524288, - "output": 131072 + "context": 8192, + "output": 8192 }, - "temperature": true, - "tool_call": true, + "temperature": false, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "none", - "minimal", - "low", - "medium", - "high", - "xhigh", - "max" - ] - } + "supported": false }, - "attachment": false, - "open_weights": false, - "knowledge": "2026-02", - "release_date": "2026-08-06", - "last_updated": "2026-08-06", - "type": "chat" + "attachment": true, + "open_weights": true, + "release_date": "2025-05-29", + "last_updated": "2025-05-29" }, { - "id": "inclusionai/ling-3.0-flash", - "name": "inclusionAI: Ling 3.0 Flash", - "display_name": "inclusionAI: Ling 3.0 Flash", + "id": "poolside/laguna-xs-2.1", + "name": "Laguna XS 2.1", + "display_name": "Laguna XS 2.1", "modalities": { "input": [ "text" @@ -89039,7 +88941,7 @@ }, "limit": { "context": 262144, - "output": 32768 + "output": 16384 }, "temperature": true, "tool_call": true, @@ -89047,108 +88949,81 @@ "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "none", - "high" - ] - } - }, "attachment": false, "open_weights": true, - "release_date": "2026-07-23", - "last_updated": "2026-07-23", + "release_date": "2026-07-02", + "last_updated": "2026-07-02", "type": "chat" }, { - "id": "inclusionai/ling-3.1-flash", - "name": "inclusionAI: Ling 3.1 Flash", - "display_name": "inclusionAI: Ling 3.1 Flash", + "id": "qwen/qwen-image", + "name": "Qwen Image", + "display_name": "Qwen Image", "modalities": { "input": [ "text" ], "output": [ - "text" + "image" ] }, "limit": { - "context": 1000000, - "output": 262144 + "context": 8192, + "output": 8192 }, - "temperature": true, - "tool_call": true, + "temperature": false, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "none", - "high" - ] - } + "supported": false }, "attachment": false, - "open_weights": true, - "release_date": "2026-09-29", - "last_updated": "2026-09-29", - "type": "chat" + "open_weights": false, + "release_date": "2025-08-07", + "last_updated": "2025-08-07" }, { - "id": "inclusionai/ling-3.0-flash-vl", - "name": "inclusionAI: Ling 3.0 Flash VL", - "display_name": "inclusionAI: Ling 3.0 Flash VL", + "id": "qwen/qwen-image-edit", + "name": "Qwen Image Edit", + "display_name": "Qwen Image Edit", "modalities": { "input": [ "text", - "image", - "video" + "image" ], "output": [ - "text" + "image" ] }, "limit": { - "context": 131072, - "output": 32768 + "context": 8192, + "output": 8192 }, - "temperature": true, - "tool_call": true, + "temperature": false, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "none", - "high" - ] - } + "supported": false }, "attachment": true, - "open_weights": true, - "release_date": "2026-09-10", - "last_updated": "2026-09-10", - "type": "chat" + "open_weights": false, + "release_date": "2025-08-19", + "last_updated": "2025-08-19" }, { - "id": "inclusionai/ling-3.1-flash-1019-free", - "name": "inclusionAI: Ling 3.1 Flash 1019 (free)", - "display_name": "inclusionAI: Ling 3.1 Flash 1019 (free)", + "id": "minimaxai/minimax-m3.1-flash", + "name": "MiniMax-M3.1-Flash", + "display_name": "MiniMax-M3.1-Flash", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 1000000, + "context": 262144, "output": 262144 }, "temperature": true, @@ -89159,25 +89034,36 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ - "none", - "high" + "low", + "medium", + "high", + "xhigh", + "max" ] } }, - "attachment": false, + "attachment": true, "open_weights": false, - "release_date": "2025-08-26", - "last_updated": "2025-08-26", + "release_date": "2026-10-19", + "last_updated": "2026-10-19", "type": "chat" }, { - "id": "deepseek/deepseek-chat-v3-0324", - "name": "DeepSeek V3 0324", - "display_name": "DeepSeek V3 0324", + "id": "meta/llama-3.2-11b-vision-instruct", + "name": "Llama 3.2 11b Vision Instruct", + "display_name": "Llama 3.2 11b Vision Instruct", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" @@ -89185,75 +89071,53 @@ }, "limit": { "context": 128000, - "output": 115200 + "output": 4096 }, "temperature": true, "tool_call": true, "reasoning": { "supported": false }, - "attachment": false, + "attachment": true, "open_weights": true, - "knowledge": "2024-07-31", - "release_date": "2025-03-24", - "last_updated": "2025-03-24", + "knowledge": "2023-12", + "release_date": "2024-09-25", + "last_updated": "2024-09-25", "type": "chat" }, { - "id": "deepseek/deepseek-v4-flash", - "name": "DeepSeek V4 Flash", - "display_name": "DeepSeek V4 Flash", + "id": "meta/llama-3.2-90b-vision-instruct", + "name": "Llama-3.2-90B-Vision-Instruct", + "display_name": "Llama-3.2-90B-Vision-Instruct", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 1048575, - "output": 943717 + "context": 128000, + "output": 8192 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "high", - "effort_options": [ - "low", - "high", - "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." - ] - } + "supported": false }, - "attachment": false, + "attachment": true, "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-04-24", - "last_updated": "2026-04-24", + "knowledge": "2023-12", + "release_date": "2024-09-25", + "last_updated": "2024-09-25", "type": "chat" }, { - "id": "deepseek/deepseek-v4.1-flash-1017", - "name": "DeepSeek V4.1 Flash 1017", - "display_name": "DeepSeek V4.1 Flash 1017", + "id": "meta/llama-guard-4-12b", + "name": "Llama Guard 4 12B", + "display_name": "Llama Guard 4 12B", "modalities": { "input": [ "text", @@ -89264,47 +89128,36 @@ ] }, "limit": { - "context": 1048576, - "output": 393216 + "context": 163840, + "output": 16384 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "none", - "low", - "high", - "max" - ] - } + "supported": false }, "attachment": true, "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-10-17", - "last_updated": "2026-10-17", + "release_date": "2025-04-29", + "last_updated": "2025-04-29", "type": "chat" }, { - "id": "deepseek/deepseek-v4-pro", - "name": "DeepSeek V4 Pro", - "display_name": "DeepSeek V4 Pro", + "id": "meta/muse-glimmer-30b", + "name": "Muse Glimmer 30B", + "display_name": "Muse Glimmer 30B", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 393216 + "context": 131072, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -89315,36 +89168,33 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "high", - "effort_options": [ - "low", - "high", - "max" - ], "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ "thinking_blocks" ], - "notes": [ - "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." + "effort_options": [ + "none", + "minimal", + "low", + "medium", + "high", + "max" ] } }, - "attachment": false, + "attachment": true, "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-04-24", - "last_updated": "2026-04-24", + "knowledge": "2026-01-04", + "release_date": "2026-08-10", + "last_updated": "2026-08-10", "type": "chat" }, { - "id": "deepseek/deepseek-v4-flash-vision-exp", - "name": "DeepSeek V4 Flash Vision Exp", - "display_name": "DeepSeek V4 Flash Vision Exp", + "id": "deepseek-ai/deepseek-v4.1-flash", + "name": "DeepSeek V4.1 Flash", + "display_name": "DeepSeek V4.1 Flash", "modalities": { "input": [ "text", @@ -89355,8 +89205,8 @@ ] }, "limit": { - "context": 1048576, - "output": 262144 + "context": 1000000, + "output": 384000 }, "temperature": true, "tool_call": true, @@ -89367,45 +89217,51 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "high", - "effort_options": [ - "low", - "high", - "max" - ], "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ "thinking_blocks" ], - "notes": [ - "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." + "effort_options": [ + "none", + "low", + "high", + "max" ] } }, "attachment": true, "open_weights": true, - "release_date": "2026-08-21", - "last_updated": "2026-09-01", + "knowledge": "2025-05", + "release_date": "2026-09-10", + "last_updated": "2026-09-10", "type": "chat" - }, + } + ] + }, + "bothub": { + "id": "bothub", + "name": "Bothub", + "display_name": "Bothub", + "api": "https://openai.bothub.ru/v1", + "doc": "https://bothub.ru/models", + "models": [ { - "id": "deepseek/deepseek-r1-0528", - "name": "DeepSeek R1 0528", - "display_name": "DeepSeek R1 0528", + "id": "gemma-4-31b-it:free", + "name": "Gemma 4 31B IT (free)", + "display_name": "Gemma 4 31B IT (free)", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 163840, + "context": 262144, "output": 32768 }, "temperature": true, @@ -89417,31 +89273,27 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "effort_options": [ - "high" - ] + "interleaved": true } }, - "attachment": false, + "attachment": true, "open_weights": true, - "release_date": "2025-05-28", - "last_updated": "2025-05-28", + "knowledge": "2025-01", + "release_date": "2026-04-02", + "last_updated": "2026-04-02", "type": "chat" }, { - "id": "deepseek/deepseek-v4.1-flash", - "name": "DeepSeek V4.1 Flash", - "display_name": "DeepSeek V4.1 Flash", + "id": "muse-spark-1.3-contributor", + "name": "Muse Spark 1.3 Contributor", + "display_name": "Muse Spark 1.3 Contributor", "modalities": { "input": [ "text", - "image" + "image", + "video", + "pdf", + "audio" ], "output": [ "text" @@ -89460,36 +89312,40 @@ "extra_capabilities": { "reasoning": { "supported": true, + "interleaved": true, "effort_options": [ - "none", + "minimal", "low", + "medium", "high", - "max" + "xhigh" ] } }, "attachment": true, - "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-09-10", - "last_updated": "2026-09-10", + "open_weights": false, + "release_date": "2026-09-02", + "last_updated": "2026-09-02", "type": "chat" }, { - "id": "deepseek/deepseek-v3.2", - "name": "DeepSeek V3.2", - "display_name": "DeepSeek V3.2", + "id": "glm-5.3-flash", + "name": "GLM-5.3-Flash", + "display_name": "GLM-5.3-Flash", "modalities": { "input": [ - "text" + "text", + "image", + "video", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 163840, - "output": 147456 + "context": 1000000, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -89500,36 +89356,39 @@ "extra_capabilities": { "reasoning": { "supported": true, + "interleaved": true, "effort_options": [ - "none", - "high" + "low", + "high", + "max" ] } }, - "attachment": false, + "attachment": true, "open_weights": true, - "knowledge": "2024-07", - "release_date": "2025-12-01", - "last_updated": "2025-12-01", + "release_date": "2026-08-26", + "last_updated": "2026-08-26", "type": "chat" }, { - "id": "deepseek/deepseek-chat-v3.1", - "name": "DeepSeek V3.1", - "display_name": "DeepSeek V3.1", + "id": "gpt-5.6-luna", + "name": "GPT-5.6 Luna", + "display_name": "GPT-5.6 Luna", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 163840, - "output": 147456 + "context": 1050000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -89537,23 +89396,78 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", "effort_options": [ "none", + "low", + "medium", + "high", + "xhigh", + "max" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", "high" + ], + "interleaved": true, + "visibility": "hidden" + } + }, + "attachment": true, + "open_weights": false, + "knowledge": "2026-02-16", + "release_date": "2026-07-09", + "last_updated": "2026-07-09", + "type": "chat" + }, + { + "id": "glm-5.3", + "name": "GLM-5.3", + "display_name": "GLM-5.3", + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1000000, + "output": 131072 + }, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "effort_options": [ + "low", + "high", + "max" ] } }, "attachment": false, "open_weights": true, - "knowledge": "2025-03-31", - "release_date": "2025-08-21", - "last_updated": "2025-08-21", + "release_date": "2026-08-14", + "last_updated": "2026-08-14", "type": "chat" }, { - "id": "deepseek/deepseek-v4-flash-0731", - "name": "DeepSeek V4 Flash 0731", - "display_name": "DeepSeek V4 Flash 0731", + "id": "nemotron-3-ultra-550b-a55b:free", + "name": "Nemotron 3 Ultra (free)", + "display_name": "Nemotron 3 Ultra (free)", "modalities": { "input": [ "text" @@ -89563,8 +89477,8 @@ ] }, "limit": { - "context": 1048576, - "output": 943718 + "context": 1000000, + "output": 128000 }, "temperature": true, "tool_call": true, @@ -89575,23 +89489,23 @@ "extra_capabilities": { "reasoning": { "supported": true, + "interleaved": true, "effort_options": [ "none", - "low", - "high", - "max" + "medium", + "high" ] } }, "attachment": false, "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-07-31", - "last_updated": "2026-07-31", + "knowledge": "2025-09", + "release_date": "2026-06-04", + "last_updated": "2026-06-04", "type": "chat" }, { - "id": "deepseek/deepseek-v4-pro-0813", + "id": "deepseek-v4-pro-0813", "name": "DeepSeek V4 Pro 0813", "display_name": "DeepSeek V4 Pro 0813", "modalities": { @@ -89603,8 +89517,8 @@ ] }, "limit": { - "context": 1048576, - "output": 393216 + "context": 1000000, + "output": 384000 }, "temperature": true, "tool_call": true, @@ -89615,9 +89529,9 @@ "extra_capabilities": { "reasoning": { "supported": true, + "interleaved": true, "effort_options": [ "none", - "low", "high", "max" ] @@ -89630,9 +89544,9 @@ "type": "chat" }, { - "id": "deepseek/deepseek-v3.2-exp", - "name": "DeepSeek: DeepSeek V3.2 Exp", - "display_name": "DeepSeek: DeepSeek V3.2 Exp", + "id": "deepseek-v4-flash-0731", + "name": "DeepSeek V4 Flash 0731", + "display_name": "DeepSeek V4 Flash 0731", "modalities": { "input": [ "text" @@ -89642,8 +89556,8 @@ ] }, "limit": { - "context": 163840, - "output": 147456 + "context": 1000000, + "output": 384000 }, "temperature": true, "tool_call": true, @@ -89653,22 +89567,36 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "interleaved": true, "effort_options": [ "none", - "high" + "low", + "high", + "max" ] } }, "attachment": false, - "open_weights": false, - "release_date": "2025-09-29", - "last_updated": "2025-09-29", + "open_weights": true, + "knowledge": "2025-05", + "release_date": "2026-07-31", + "last_updated": "2026-07-31", "type": "chat" - }, + } + ] + }, + "ambient": { + "id": "ambient", + "name": "Ambient", + "display_name": "Ambient", + "api": "https://api.ambient.xyz/v1", + "doc": "https://ambient.xyz", + "models": [ { - "id": "deepseek/deepseek-v3.1-terminus", - "name": "DeepSeek: DeepSeek V3.1 Terminus", - "display_name": "DeepSeek: DeepSeek V3.1 Terminus", + "id": "ambient/large", + "name": "Ambient Large", + "display_name": "Ambient Large", "modalities": { "input": [ "text" @@ -89678,8 +89606,8 @@ ] }, "limit": { - "context": 163840, - "output": 147456 + "context": 202752, + "output": 202752 }, "temperature": true, "tool_call": true, @@ -89690,21 +89618,21 @@ "extra_capabilities": { "reasoning": { "effort_options": [ - "none", - "high" + "high", + "max" ] } }, "attachment": false, - "open_weights": false, - "release_date": "2025-09-22", - "last_updated": "2025-09-22", + "open_weights": true, + "release_date": "2026-06-13", + "last_updated": "2026-06-13", "type": "chat" }, { - "id": "deepseek/deepseek-chat", - "name": "DeepSeek Chat", - "display_name": "DeepSeek Chat", + "id": "ambient/small", + "name": "Ambient Small", + "display_name": "Ambient Small", "modalities": { "input": [ "text" @@ -89714,8 +89642,8 @@ ] }, "limit": { - "context": 128000, - "output": 16000 + "context": 101072, + "output": 101072 }, "temperature": true, "tool_call": true, @@ -89724,30 +89652,25 @@ }, "attachment": false, "open_weights": true, - "knowledge": "2025-09", - "release_date": "2025-12-01", - "last_updated": "2026-02-28", + "release_date": "2026-10-13", + "last_updated": "2026-10-13", "type": "chat" }, { - "id": "google/gemini-3-flash-preview", - "name": "Gemini 3 Flash Preview", - "display_name": "Gemini 3 Flash Preview", + "id": "z-ai/glm-5.2", + "name": "GLM-5.2", + "display_name": "GLM-5.2", "modalities": { "input": [ - "text", - "image", - "video", - "audio", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 202752, + "output": 202752 }, "temperature": true, "tool_call": true, @@ -89758,48 +89681,33 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "level", - "level": "high", - "level_options": [ - "minimal", - "low", - "medium", - "high" - ], - "summaries": true, - "visibility": "summary", - "continuation": [ - "thought_signatures" + "effort_options": [ + "high", + "max" ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-01", - "release_date": "2025-12-17", - "last_updated": "2025-12-17", + "attachment": false, + "open_weights": true, + "release_date": "2026-06-13", + "last_updated": "2026-06-13", "type": "chat" }, { - "id": "google/gemini-2.5-pro", - "name": "Gemini 2.5 Pro", - "display_name": "Gemini 2.5 Pro", + "id": "minimax/minimax-m3", + "name": "MiniMax-M3", + "display_name": "MiniMax-M3", "modalities": { "input": [ - "text", - "image", - "audio", - "video", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 131072, + "output": 131702 }, "temperature": true, "tool_call": true, @@ -89809,108 +89717,82 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "budget", - "budget": { - "default": -1, - "min": 128, - "max": 32768, - "auto": -1, - "unit": "tokens" - }, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thought_signatures" - ] + "supported": true } }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-01", - "release_date": "2025-06-17", - "last_updated": "2025-06-17", + "attachment": false, + "open_weights": true, + "release_date": "2026-06-01", + "last_updated": "2026-06-01", "type": "chat" }, { - "id": "google/gemini-2.5-flash-image", - "name": "Nano Banana", - "display_name": "Nano Banana", + "id": "zai-org/GLM-5.2-FP8", + "name": "GLM-5.2", + "display_name": "GLM-5.2", "modalities": { "input": [ - "image", - "text", - "pdf" + "text" ], "output": [ - "text", - "image" + "text" ] }, "limit": { - "context": 32768, - "output": 8192 + "context": 202752, + "output": 202752 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": true, - "open_weights": false, - "knowledge": "2024-06", - "release_date": "2025-08-26", - "last_updated": "2025-08-26" + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "high", + "max" + ] + } + }, + "attachment": false, + "open_weights": true, + "release_date": "2026-06-13", + "last_updated": "2026-06-13", + "type": "chat" }, { - "id": "google/gemini-3.5-flash-lite", - "name": "Gemini 3.5 Flash Lite", - "display_name": "Gemini 3.5 Flash Lite", + "id": "unsloth/qwen3.8-27b-gguf", + "name": "qwen3.8-27b-4q", + "display_name": "qwen3.8-27b-4q", "modalities": { "input": [ - "text", - "image", - "video", - "audio", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 101072, + "output": 101072 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "effort_options": [ - "minimal", - "low", - "medium", - "high" - ] - } + "supported": false }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-03", - "release_date": "2026-07-21", - "last_updated": "2026-07-21", + "attachment": false, + "open_weights": true, + "release_date": "2026-10-13", + "last_updated": "2026-10-13", "type": "chat" }, { - "id": "google/gemma-2-27b-it", - "name": "Gemma 2 27B", - "display_name": "Gemma 2 27B", + "id": "qwen/qwen3-30b-a3b-instruct-2507", + "name": "Qwen3 30B-A3B Instruct 2507", + "display_name": "Qwen3 30B-A3B Instruct 2507", "modalities": { "input": [ "text" @@ -89920,42 +89802,38 @@ ] }, "limit": { - "context": 8192, - "output": 2048 + "context": 131072, + "output": 32768 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { "supported": false }, "attachment": false, "open_weights": true, - "knowledge": "2024-06-30", - "release_date": "2024-07-13", - "last_updated": "2024-07-13", + "release_date": "2025-07-28", + "last_updated": "2025-07-28", "type": "chat" }, { - "id": "google/gemini-3.1-flash-image", - "name": "Nano Banana 2", - "display_name": "Nano Banana 2", + "id": "qwen/qwen3.6-35b-a3b", + "name": "Qwen3.6 35B-A3B", + "display_name": "Qwen3.6 35B-A3B", "modalities": { "input": [ - "image", - "text", - "pdf" + "text" ], "output": [ - "text", - "image" + "text" ] }, "limit": { - "context": 131072, - "output": 32768 + "context": 262144, + "output": 262144 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { "supported": true, "default": true @@ -89963,36 +89841,36 @@ "extra_capabilities": { "reasoning": { "supported": true, - "effort_options": [ - "none", - "minimal", - "high" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-01", - "release_date": "2026-05-28", - "last_updated": "2026-05-28" + "attachment": false, + "open_weights": true, + "release_date": "2026-04-17", + "last_updated": "2026-04-17", + "type": "chat" }, { - "id": "google/gemma-4-31b-it", - "name": "Gemma 4 31B IT", - "display_name": "Gemma 4 31B IT", + "id": "qwen/qwen3.8-27b", + "name": "Qwen3.8 27B", + "display_name": "Qwen3.8 27B", "modalities": { "input": [ - "image", "text", - "video" + "image" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 16384 + "context": 32768, + "output": 8192 }, "temperature": true, "tool_call": true, @@ -90002,41 +89880,37 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, "effort_options": [ - "none", - "high" + "low", + "medium", + "xhigh" ] } }, "attachment": true, "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-04-02", - "last_updated": "2026-04-02", + "release_date": "2026-08-14", + "last_updated": "2026-08-14", "type": "chat" }, { - "id": "google/gemini-3-pro-image-preview", - "name": "Nano Banana Pro Preview", - "display_name": "Nano Banana Pro Preview", + "id": "qwen/qwen3.6-27b", + "name": "Qwen3.6 27B", + "display_name": "Qwen3.6 27B", "modalities": { "input": [ - "image", - "text", - "pdf" + "text" ], "output": [ - "text", - "image" + "text" ] }, "limit": { - "context": 65536, - "output": 32768 + "context": 32768, + "output": 8192 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { "supported": true, "default": true @@ -90044,36 +89918,37 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "level", - "level": "high", - "level_options": [ - "low", - "high" - ], + "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ - "thought_signatures" + "thinking_blocks" ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-01", - "release_date": "2025-11-20", - "last_updated": "2025-11-20" - }, + "attachment": false, + "open_weights": true, + "release_date": "2026-04-22", + "last_updated": "2026-04-22", + "type": "chat" + } + ] + }, + "pioneer": { + "id": "pioneer", + "name": "Pioneer", + "display_name": "Pioneer", + "api": "https://api.fastino.ai/v1", + "doc": "https://agent.pioneer.ai/llms.txt", + "models": [ { - "id": "google/gemini-2.5-flash", - "name": "Gemini 2.5 Flash", - "display_name": "Gemini 2.5 Flash", + "id": "gpt-5.6-terra", + "name": "GPT-5.6 Terra", + "display_name": "GPT-5.6 Terra", "modalities": { "input": [ "text", "image", - "audio", - "video", "pdf" ], "output": [ @@ -90081,10 +89956,10 @@ ] }, "limit": { - "context": 1048576, - "output": 65535 + "context": 1050000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -90094,39 +89969,45 @@ "reasoning": { "supported": true, "default_enabled": true, - "mode": "budget", - "budget": { - "default": -1, - "min": 0, - "max": 24576, - "auto": -1, - "off": 0, - "unit": "tokens" - }, + "mode": "effort", + "effort": "medium", + "effort_options": [ + "none", + "low", + "medium", + "high", + "xhigh", + "max" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ - "thought_signatures" + "thinking_blocks" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-01", - "release_date": "2025-06-17", - "last_updated": "2025-06-17", + "knowledge": "2026-02-16", + "release_date": "2026-07-09", + "last_updated": "2026-07-09", "type": "chat" }, { - "id": "google/gemini-3.1-flash-lite", - "name": "Gemini 3.1 Flash Lite", - "display_name": "Gemini 3.1 Flash Lite", + "id": "claude-haiku-4-5", + "name": "Claude Haiku 4.5 (latest)", + "display_name": "Claude Haiku 4.5 (latest)", "modalities": { "input": [ "text", "image", - "video", - "audio", "pdf" ], "output": [ @@ -90134,44 +90015,51 @@ ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 200000, + "output": 64000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, - "effort_options": [ - "none", - "minimal", - "low", - "medium", - "high" + "default_enabled": false, + "mode": "budget", + "budget": { + "min": 1024, + "unit": "tokens" + }, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", + "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-01", - "release_date": "2026-05-07", - "last_updated": "2026-05-07", + "knowledge": "2025-02-28", + "release_date": "2025-10-15", + "last_updated": "2025-10-15", "type": "chat" }, { - "id": "google/gemini-3.7-flash", - "name": "Gemini 3.7 Flash", - "display_name": "Gemini 3.7 Flash", + "id": "claude-sonnet-5", + "name": "Claude Sonnet 5", + "display_name": "Claude Sonnet 5", "modalities": { "input": [ "text", "image", - "video", - "audio", "pdf" ], "output": [ @@ -90179,10 +90067,10 @@ ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 1000000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -90191,78 +90079,96 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "level", - "level": "high", - "level_options": [ - "minimal", - "low", - "medium", - "high" - ], + "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ - "thought_signatures" + "thinking_blocks" + ], + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-03", - "release_date": "2026-08-13", - "last_updated": "2026-08-13", + "knowledge": "2026-01-31", + "release_date": "2026-06-30", + "last_updated": "2026-06-30", "type": "chat" }, { - "id": "google/gemma-3-12b-it", - "name": "Gemma 3 12B IT", - "display_name": "Gemma 3 12B IT", + "id": "claude-opus-5-fast", + "name": "Claude Opus 5", + "display_name": "Claude Opus 5", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 16384 + "context": 1000000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" + ] + } }, "attachment": true, - "open_weights": true, - "knowledge": "2024-08", - "release_date": "2025-03-12", - "last_updated": "2025-03-12", + "open_weights": false, + "knowledge": "2026-05", + "release_date": "2026-07-24", + "last_updated": "2026-07-24", "type": "chat" }, { - "id": "google/gemini-3-pro-image", - "name": "Nano Banana Pro", - "display_name": "Nano Banana Pro", + "id": "claude-fable-5", + "name": "Claude Fable 5", + "display_name": "Claude Fable 5", "modalities": { "input": [ - "image", "text", + "image", "pdf" ], "output": [ - "text", - "image" + "text" ] }, "limit": { - "context": 65536, - "output": 32768 + "context": 1000000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -90272,35 +90178,43 @@ "reasoning": { "supported": true, "default_enabled": true, - "mode": "level", - "level": "high", - "level_options": [ + "mode": "effort", + "effort": "high", + "effort_options": [ "low", - "high" + "medium", + "high", + "xhigh", + "max" ], + "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ - "thought_signatures" + "thinking_blocks" + ], + "notes": [ + "Adaptive thinking is always on for Claude Fable 5 and Claude Mythos 5; thinking.type = \"disabled\" is rejected.", + "Manual budget_tokens requests return 400 on Claude Fable 5 and Claude Mythos 5.", + "thinking.display defaults to omitted; set display to summarized to receive readable thinking summaries." ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-01", - "release_date": "2026-05-28", - "last_updated": "2026-05-28" + "knowledge": "2026-01-31", + "release_date": "2026-06-09", + "last_updated": "2026-06-09", + "type": "chat" }, { - "id": "google/gemini-3.1-flash-lite-preview", - "name": "Gemini 3.1 Flash Lite Preview", - "display_name": "Gemini 3.1 Flash Lite Preview", + "id": "gpt-5.6-luna", + "name": "GPT-5.6 Luna", + "display_name": "GPT-5.6 Luna", "modalities": { "input": [ "text", "image", - "video", - "audio", "pdf" ], "output": [ @@ -90308,10 +90222,10 @@ ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 1050000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -90319,41 +90233,58 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", "effort_options": [ "none", - "minimal", + "low", + "medium", + "high", + "xhigh", + "max" + ], + "verbosity": "medium", + "verbosity_options": [ "low", "medium", "high" + ], + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-01", - "release_date": "2026-03-03", - "last_updated": "2026-03-03", + "knowledge": "2026-02-16", + "release_date": "2026-07-09", + "last_updated": "2026-07-09", "type": "chat" }, { - "id": "google/gemma-4-26b-a4b-it", - "name": "Gemma 4 26B A4B IT", - "display_name": "Gemma 4 26B A4B IT", + "id": "gpt-5.6-sol", + "name": "GPT-5.6 Sol", + "display_name": "GPT-5.6 Sol", "modalities": { "input": [ - "image", "text", - "video" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 235929 + "context": 1050000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -90362,28 +90293,46 @@ "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", "effort_options": [ "none", + "low", + "medium", + "high", + "xhigh", + "max" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", "high" + ], + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-04-02", - "last_updated": "2026-04-02", + "open_weights": false, + "knowledge": "2026-02-16", + "release_date": "2026-07-09", + "last_updated": "2026-07-09", "type": "chat" }, { - "id": "google/gemini-3.1-pro-preview-customtools", - "name": "Gemini 3.1 Pro Preview Custom Tools", - "display_name": "Gemini 3.1 Pro Preview Custom Tools", + "id": "gpt-5.5", + "name": "GPT-5.5", + "display_name": "GPT-5.5", "modalities": { "input": [ "text", "image", - "video", - "audio", "pdf" ], "output": [ @@ -90391,10 +90340,10 @@ ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 1050000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -90404,30 +90353,39 @@ "reasoning": { "supported": true, "default_enabled": true, - "mode": "level", - "level": "high", - "level_options": [ + "mode": "effort", + "effort": "medium", + "effort_options": [ + "low", + "medium", + "high", + "xhigh" + ], + "verbosity": "medium", + "verbosity_options": [ "low", + "medium", "high" ], + "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ - "thought_signatures" + "thinking_blocks" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-01", - "release_date": "2026-02-19", - "last_updated": "2026-02-19", + "knowledge": "2025-12-01", + "release_date": "2026-04-23", + "last_updated": "2026-04-23", "type": "chat" }, { - "id": "google/gemini-3.1-flash-image-preview", - "name": "Nano Banana 2 Preview", - "display_name": "Nano Banana 2 Preview", + "id": "claude-opus-5", + "name": "Claude Opus 5", + "display_name": "Claude Opus 5", "modalities": { "input": [ "text", @@ -90435,16 +90393,15 @@ "pdf" ], "output": [ - "text", - "image" + "text" ] }, "limit": { - "context": 65536, - "output": 58982 + "context": 1000000, + "output": 128000 }, - "temperature": true, - "tool_call": false, + "temperature": false, + "tool_call": true, "reasoning": { "supported": true, "default": true @@ -90452,40 +90409,48 @@ "extra_capabilities": { "reasoning": { "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ - "none", - "minimal", - "high" + "low", + "medium", + "high", + "xhigh", + "max" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-01", - "release_date": "2026-02-26", - "last_updated": "2026-02-26" + "knowledge": "2026-05", + "release_date": "2026-07-24", + "last_updated": "2026-07-24", + "type": "chat" }, { - "id": "google/gemini-3.1-flash-lite-image", - "name": "Nano Banana 2 Lite", - "display_name": "Nano Banana 2 Lite", + "id": "nvidia/NVIDIA-Nemotron-3.5-Super-VL-120B-A12B-NVFP4", + "name": "Nemotron 3.5 Super VL 120B A12B", + "display_name": "Nemotron 3.5 Super VL 120B A12B", "modalities": { "input": [ - "image", "text", - "pdf" + "image", + "video" ], "output": [ - "text", - "image" + "text" ] }, "limit": { - "context": 65536, - "output": 58982 + "context": 262144, + "output": 4096 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { "supported": true, "default": true @@ -90493,38 +90458,41 @@ "extra_capabilities": { "reasoning": { "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ - "none", - "minimal", - "high" + "low", + "medium", + "high", + "xhigh" ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2025-01", - "release_date": "2026-06-30", - "last_updated": "2026-06-30" + "open_weights": true, + "release_date": "2026-10-15", + "last_updated": "2026-10-15", + "type": "chat" }, { - "id": "google/gemini-3.1-pro-preview", - "name": "Gemini 3.1 Pro Preview", - "display_name": "Gemini 3.1 Pro Preview", + "id": "nvidia/NVIDIA-Nemotron-3.5-Lightning-30B-A3B-BF16", + "name": "Nemotron 3.5 Lightning 30B A3B", + "display_name": "Nemotron 3.5 Lightning 30B A3B", "modalities": { "input": [ - "text", - "image", - "video", - "audio", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 65536, + "output": 4096 }, "temperature": true, "tool_call": true, @@ -90535,127 +90503,134 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "level", - "level": "high", - "level_options": [ - "low", - "high" - ], + "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ - "thought_signatures" + "thinking_blocks" + ], + "effort_options": [ + "low", + "medium", + "high", + "xhigh" ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-01", - "release_date": "2026-02-19", - "last_updated": "2026-02-19", + "attachment": false, + "open_weights": true, + "knowledge": "2025-09", + "release_date": "2026-08-11", + "last_updated": "2026-08-11", "type": "chat" }, { - "id": "google/gemini-2.5-flash-lite", - "name": "Gemini 2.5 Flash-Lite", - "display_name": "Gemini 2.5 Flash-Lite", + "id": "moonshotai/Kimi-K3-Fast", + "name": "Kimi K3", + "display_name": "Kimi K3", "modalities": { "input": [ "text", "image", - "audio", - "video", - "pdf" + "video" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 65535 + "context": 1000000, + "output": 131072 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "budget", - "budget": { - "default": -1, - "min": 512, - "max": 24576, - "auto": -1, - "unit": "tokens" - }, + "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ - "thought_signatures" + "thinking_blocks" + ], + "effort_options": [ + "low", + "high", + "max" ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2025-01", - "release_date": "2025-06-17", - "last_updated": "2025-06-17", + "open_weights": true, + "release_date": "2026-07-16", + "last_updated": "2026-07-16", "type": "chat" }, { - "id": "google/gemma-3-4b-it", - "name": "Gemma 3 4B IT", - "display_name": "Gemma 3 4B IT", + "id": "moonshotai/Kimi-K3", + "name": "Kimi K3", + "display_name": "Kimi K3", "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 16384 + "context": 1000000, + "output": 131072 }, - "temperature": true, - "tool_call": false, + "temperature": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "effort_options": [ + "low", + "high", + "max" + ] + } }, "attachment": true, "open_weights": true, - "knowledge": "2024-08", - "release_date": "2025-03-12", - "last_updated": "2025-03-12", + "release_date": "2026-07-16", + "last_updated": "2026-07-16", "type": "chat" }, { - "id": "google/gemini-3.8-flash", - "name": "Gemini 3.8 Flash", - "display_name": "Gemini 3.8 Flash", + "id": "zai-org/GLM-5.2", + "name": "GLM-5.2", + "display_name": "GLM-5.2", "modalities": { "input": [ - "text", - "image", - "video", - "audio", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 1040000, + "output": 128000 }, "temperature": true, "tool_call": true, @@ -90666,355 +90641,244 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "level", - "level": "high", - "level_options": [ - "minimal", - "low", - "medium", - "high" - ], + "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ - "thought_signatures" + "thinking_blocks" + ], + "effort_options": [ + "high", + "max" ] } }, - "attachment": true, - "open_weights": false, - "release_date": "2026-09-02", - "last_updated": "2026-09-02", + "attachment": false, + "open_weights": true, + "release_date": "2026-06-13", + "last_updated": "2026-06-13", "type": "chat" }, { - "id": "google/gemini-4-argon", - "name": "Gemini 4 Argon", - "display_name": "Gemini 4 Argon", + "id": "fastino/gliguard-LLMGuardrails-300M", + "name": "GLiGuard LLM Guardrails 300M", + "display_name": "GLiGuard LLM Guardrails 300M", "modalities": { "input": [ - "text", - "image", - "video", - "audio", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 943718 + "context": 8192, + "output": 8192 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "low", - "medium", - "high" - ] - } + "supported": false }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-03", - "release_date": "2026-10-13", - "last_updated": "2026-10-13", + "attachment": false, + "open_weights": true, + "release_date": "2026-04-30", + "last_updated": "2026-04-30", "type": "chat" }, { - "id": "google/gemini-2.5-pro-preview", - "name": "Gemini 2.5 Pro Preview 06-05", - "display_name": "Gemini 2.5 Pro Preview 06-05", + "id": "fastino/gliner2.5-base-v1", + "name": "GLiNER 2.5 Base", + "display_name": "GLiNER 2.5 Base", "modalities": { "input": [ - "pdf", - "image", - "text", - "audio" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 4096, + "output": 4096 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "budget", - "budget": { - "default": -1, - "min": 128, - "max": 32768, - "auto": -1, - "unit": "tokens" - }, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thought_signatures" - ] - } + "supported": false }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-01-31", - "release_date": "2025-06-05", - "last_updated": "2025-06-05", + "attachment": false, + "open_weights": true, + "release_date": "2026-08-24", + "last_updated": "2026-08-24", "type": "chat" }, { - "id": "google/gemini-3.6-flash", - "name": "Gemini 3.6 Flash", - "display_name": "Gemini 3.6 Flash", + "id": "fastino/gliner2-base-v1", + "name": "GLiNER2 Base", + "display_name": "GLiNER2 Base", "modalities": { "input": [ - "text", - "image", - "video", - "audio", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 2048, + "output": 2048 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "level", - "level": "high", - "level_options": [ - "minimal", - "low", - "medium", - "high" - ], - "summaries": true, - "visibility": "summary", - "continuation": [ - "thought_signatures" - ] - } + "supported": false }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-03", - "release_date": "2026-07-21", - "last_updated": "2026-07-21", + "attachment": false, + "open_weights": true, + "release_date": "2025-06-30", + "last_updated": "2025-06-30", "type": "chat" }, { - "id": "google/lyria-3-clip-preview", - "name": "Lyria 3 Clip Preview", - "display_name": "Lyria 3 Clip Preview", + "id": "fastino/gliner2.5-multi-v1", + "name": "GLiNER 2.5 Multi", + "display_name": "GLiNER 2.5 Multi", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ - "text", - "audio" + "text" ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 4096, + "output": 4096 }, "temperature": true, "tool_call": false, "reasoning": { "supported": false }, - "attachment": true, - "open_weights": false, - "release_date": "2026-03-25", - "last_updated": "2026-03-25", + "attachment": false, + "open_weights": true, + "release_date": "2026-08-24", + "last_updated": "2026-08-24", "type": "chat" }, { - "id": "google/gemini-3.5-flash", - "name": "Gemini 3.5 Flash", - "display_name": "Gemini 3.5 Flash", + "id": "fastino/gliguard-PII-multi", + "name": "GLiNER2-Guardrails-PII-Multi", + "display_name": "GLiNER2-Guardrails-PII-Multi", "modalities": { "input": [ - "text", - "image", - "video", - "audio", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 8192, + "output": 8192 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "level", - "level": "high", - "level_options": [ - "minimal", - "low", - "medium", - "high" - ], - "summaries": true, - "visibility": "summary", - "continuation": [ - "thought_signatures" - ] - } + "supported": false }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-01", - "release_date": "2026-05-19", - "last_updated": "2026-05-19", + "attachment": false, + "open_weights": true, + "release_date": "2026-07-08", + "last_updated": "2026-07-08", "type": "chat" }, { - "id": "google/gemini-nano-banana-2.1", - "name": "Nano Banana 2.1", - "display_name": "Nano Banana 2.1", + "id": "fastino/gliner2-multi-large-v1", + "name": "GLiNER2 Multi Large", + "display_name": "GLiNER2 Multi Large", "modalities": { "input": [ - "image", - "text", - "pdf" + "text" ], "output": [ - "text", - "image" + "text" ] }, "limit": { - "context": 65536, - "output": 58982 + "context": 8192, + "output": 8192 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "none", - "minimal", - "high" - ] - } + "supported": false }, - "attachment": true, - "open_weights": false, - "release_date": "2026-10-06", - "last_updated": "2026-10-06" + "attachment": false, + "open_weights": true, + "release_date": "2025-11-30", + "last_updated": "2025-11-30", + "type": "chat" }, { - "id": "google/gemma-3-27b-it", - "name": "Gemma 3 27B IT", - "display_name": "Gemma 3 27B IT", + "id": "fastino/gliner2-privacy-filter-PII-multi", + "name": "GLiNER2 Privacy Filter PII (Multi)", + "display_name": "GLiNER2 Privacy Filter PII (Multi)", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 117964 + "context": 8192, + "output": 8192 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { "supported": false }, - "attachment": true, + "attachment": false, "open_weights": true, - "knowledge": "2024-08", - "release_date": "2025-03-12", - "last_updated": "2025-03-12", + "release_date": "2026-04-30", + "last_updated": "2026-04-30", "type": "chat" }, { - "id": "google/lyria-3-pro-preview", - "name": "Lyria 3 Pro Preview", - "display_name": "Lyria 3 Pro Preview", + "id": "fastino/gliner2-multi-v1", + "name": "GLiNER2 Multi", + "display_name": "GLiNER2 Multi", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ - "text", - "audio" + "text" ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 8192, + "output": 8192 }, "temperature": true, "tool_call": false, "reasoning": { "supported": false }, - "attachment": true, - "open_weights": false, - "release_date": "2026-03-25", - "last_updated": "2026-03-25", + "attachment": false, + "open_weights": true, + "release_date": "2025-11-30", + "last_updated": "2025-11-30", "type": "chat" }, { - "id": "openrouter/pareto-code", - "name": "Pareto Code Router", - "display_name": "Pareto Code Router", + "id": "fastino/GLiNER-2.5-Decide", + "name": "GLiNER-2.5-Decide", + "display_name": "GLiNER-2.5-Decide", "modalities": { "input": [ "text" @@ -91024,24 +90888,24 @@ ] }, "limit": { - "context": 200000, - "output": 65536 + "context": 8192, + "output": 8192 }, - "temperature": false, + "temperature": true, "tool_call": false, "reasoning": { "supported": false }, "attachment": false, - "open_weights": false, - "release_date": "2026-04-21", - "last_updated": "2026-05-01", + "open_weights": true, + "release_date": "2026-09-24", + "last_updated": "2026-09-24", "type": "chat" }, { - "id": "openrouter/bodybuilder", - "name": "Body Builder (beta)", - "display_name": "Body Builder (beta)", + "id": "fastino/gliner2-large-v1", + "name": "GLiNER2 Large", + "display_name": "GLiNER2 Large", "modalities": { "input": [ "text" @@ -91051,35 +90915,35 @@ ] }, "limit": { - "context": 128000, - "output": 32768 + "context": 8192, + "output": 8192 }, + "temperature": true, "tool_call": false, "reasoning": { "supported": false }, "attachment": false, - "open_weights": false, - "release_date": "2026-03-15", - "last_updated": "2026-03-15", + "open_weights": true, + "release_date": "2025-06-30", + "last_updated": "2025-06-30", "type": "chat" }, { - "id": "openrouter/free", - "name": "OpenRouter Free Models Router", - "display_name": "OpenRouter Free Models Router", + "id": "deepseek-ai/DeepSeek-V4-Flash", + "name": "DeepSeek V4 Flash", + "display_name": "DeepSeek V4 Flash", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 32768 + "context": 1000000, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -91087,32 +90951,58 @@ "supported": true, "default": true }, - "attachment": true, - "open_weights": false, - "release_date": "2026-02-01", - "last_updated": "2026-02-01", + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "high", + "effort_options": [ + "low", + "high", + "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." + ] + } + }, + "attachment": false, + "open_weights": true, + "knowledge": "2025-05", + "release_date": "2026-04-24", + "last_updated": "2026-04-24", "type": "chat" - }, + } + ] + }, + "togetherai": { + "id": "togetherai", + "name": "Together AI", + "display_name": "Together AI", + "doc": "https://docs.together.ai/docs/serverless-models", + "models": [ { - "id": "openrouter/auto", - "name": "Auto Router", - "display_name": "Auto Router", + "id": "openai/gpt-oss-120b", + "name": "GPT OSS 120B", + "display_name": "GPT OSS 120B", "modalities": { "input": [ - "audio", - "image", - "pdf", - "text", - "video" + "text" ], "output": [ - "image", "text" ] }, "limit": { - "context": 2000000, - "output": 32768 + "context": 131072, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -91122,19 +91012,25 @@ }, "extra_capabilities": { "reasoning": { - "supported": true + "supported": true, + "effort_options": [ + "low", + "medium", + "high" + ] } }, - "attachment": true, - "open_weights": false, - "release_date": "2026-03-15", - "last_updated": "2026-03-15", + "attachment": false, + "open_weights": true, + "knowledge": "2024-06", + "release_date": "2025-08-05", + "last_updated": "2025-08-05", "type": "chat" }, { - "id": "nousresearch/hermes-3-llama-3.1-405b", - "name": "Nous: Hermes 3 405B Instruct (retires Nov 2)", - "display_name": "Nous: Hermes 3 405B Instruct (retires Nov 2)", + "id": "Qwen/Qwen3-235B-A22B-Instruct-2507-tput", + "name": "Qwen3 235B A22B Instruct 2507 FP8", + "display_name": "Qwen3 235B A22B Instruct 2507 FP8", "modalities": { "input": [ "text" @@ -91144,25 +91040,24 @@ ] }, "limit": { - "context": 131072, - "output": 16384 + "context": 262144, + "output": 262144 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { "supported": false }, "attachment": false, "open_weights": true, - "knowledge": "2023-12-31", - "release_date": "2024-08-16", - "last_updated": "2024-08-16", + "release_date": "2025-07-21", + "last_updated": "2025-07-21", "type": "chat" }, { - "id": "nousresearch/hermes-4-405b", - "name": "Nous: Hermes 4 405B", - "display_name": "Nous: Hermes 4 405B", + "id": "Qwen/Qwen3.7-Max", + "name": "Qwen3.7 Max", + "display_name": "Qwen3.7 Max", "modalities": { "input": [ "text" @@ -91172,213 +91067,180 @@ ] }, "limit": { - "context": 131072, - "output": 117964 + "context": 1000000, + "output": 131072 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { "supported": true, "default": true }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "none", - "high" - ] + "supported": true } }, "attachment": false, - "open_weights": true, - "knowledge": "2024-08-31", - "release_date": "2025-08-26", - "last_updated": "2025-08-26", + "open_weights": false, + "release_date": "2026-05-21", + "last_updated": "2026-05-21", "type": "chat" }, { - "id": "nousresearch/hermes-3-llama-3.1-70b", - "name": "Nous: Hermes 3 70B Instruct (retires Nov 2)", - "display_name": "Nous: Hermes 3 70B Instruct (retires Nov 2)", + "id": "Qwen/Qwen3.5-9B", + "name": "Qwen3.5 9B", + "display_name": "Qwen3.5 9B", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 16384 + "context": 262144, + "output": 65536 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } + }, + "attachment": true, "open_weights": true, - "knowledge": "2023-12-31", - "release_date": "2024-08-18", - "last_updated": "2024-08-18", + "release_date": "2026-02-23", + "last_updated": "2026-02-23", "type": "chat" }, { - "id": "thinkingmachines/inkling", - "name": "Inkling", - "display_name": "Inkling", + "id": "Qwen/Qwen2.5-7B-Instruct-Turbo", + "name": "Qwen 2.5 7B Instruct Turbo", + "display_name": "Qwen 2.5 7B Instruct Turbo", "modalities": { "input": [ - "text", - "image", - "audio" + "text" ], "output": [ "text" ] }, "limit": { - "context": 524288, - "output": 471859 + "context": 32768, + "output": 32768 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "none", - "minimal", - "low", - "medium", - "high", - "max" - ] - } + "supported": false }, - "attachment": true, + "attachment": false, "open_weights": true, - "release_date": "2026-07-15", - "last_updated": "2026-07-15", + "release_date": "2024-09-19", + "last_updated": "2024-09-19", "type": "chat" }, { - "id": "thinkingmachines/inkling-small", - "name": "Inkling Small", - "display_name": "Inkling Small", + "id": "Qwen/Qwen3-Coder-Next-FP8", + "name": "Qwen3 Coder Next FP8", + "display_name": "Qwen3 Coder Next FP8", "modalities": { "input": [ - "text", - "image", - "audio" + "text" ], "output": [ "text" ] }, "limit": { - "context": 524288, + "context": 262144, "output": 262144 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "none", - "minimal", - "low", - "medium", - "high", - "max" - ] - } + "supported": false }, - "attachment": true, + "attachment": false, "open_weights": true, - "release_date": "2026-07-30", - "last_updated": "2026-07-30", + "knowledge": "2025-09", + "release_date": "2026-02-03", + "last_updated": "2026-02-03", "type": "chat" }, { - "id": "openai/gpt-5.1-codex-max", - "name": "GPT-5.1 Codex Max", - "display_name": "GPT-5.1 Codex Max", + "id": "Qwen/Qwen3.6-Plus", + "name": "Qwen3.6 Plus", + "display_name": "Qwen3.6 Plus", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 1000000, + "output": 65536 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "none", - "effort_options": [ - "none", - "low", - "medium", - "high" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] } }, - "attachment": true, + "attachment": false, "open_weights": false, - "knowledge": "2024-09-30", - "release_date": "2025-11-13", - "last_updated": "2025-11-13", + "knowledge": "2025-04", + "release_date": "2026-04-02", + "last_updated": "2026-04-02", "type": "chat" }, { - "id": "openai/gpt-5", - "name": "GPT-5", - "display_name": "GPT-5", + "id": "Qwen/Qwen3.5-397B-A17B", + "name": "Qwen3.5 397B A17B", + "display_name": "Qwen3.5 397B A17B", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 262144, + "output": 130000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -91387,80 +91249,65 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", - "effort_options": [ - "minimal", - "low", - "medium", - "high" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2024-09-30", - "release_date": "2025-08-07", - "last_updated": "2025-08-07", + "open_weights": true, + "release_date": "2026-02-15", + "last_updated": "2026-02-15", "type": "chat" }, { - "id": "openai/gpt-4o-mini", - "name": "GPT-4o mini", - "display_name": "GPT-4o mini", + "id": "Qwen/Qwen3-Coder-480B-A35B-Instruct-FP8", + "name": "Qwen3 Coder 480B A35B Instruct", + "display_name": "Qwen3 Coder 480B A35B Instruct", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 16384 + "context": 262144, + "output": 262144 }, "temperature": true, "tool_call": true, "reasoning": { "supported": false }, - "attachment": true, - "open_weights": false, - "knowledge": "2023-09", - "release_date": "2024-07-18", - "last_updated": "2024-07-18", + "attachment": false, + "open_weights": true, + "knowledge": "2025-04", + "release_date": "2025-07-22", + "last_updated": "2025-07-22", "type": "chat" }, { - "id": "openai/gpt-5.5", - "name": "GPT-5.5", - "display_name": "GPT-5.5", + "id": "nvidia/nemotron-3-ultra-550b-a55b", + "name": "Nemotron 3 Ultra 550B A55B", + "display_name": "Nemotron 3 Ultra 550B A55B", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 512300, + "output": 512300 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -91468,81 +91315,76 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", - "effort_options": [ - "low", - "medium", - "high", - "xhigh" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + "supported": true } }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-12-01", - "release_date": "2026-04-23", - "last_updated": "2026-04-23", + "attachment": false, + "open_weights": true, + "knowledge": "2025-09", + "release_date": "2026-06-04", + "last_updated": "2026-06-04", "type": "chat" }, { - "id": "openai/gpt-4.1-nano", - "name": "GPT-4.1 nano", - "display_name": "GPT-4.1 nano", + "id": "nvidia/NVIDIA-Nemotron-3.5-Super-VL", + "name": "Nemotron 3.5 Super VL 120B A12B", + "display_name": "Nemotron 3.5 Super VL 120B A12B", "modalities": { "input": [ - "image", "text", - "pdf" + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 1047576, - "output": 942818 + "context": 262144, + "output": 262144 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } }, "attachment": true, - "open_weights": false, - "knowledge": "2024-04", - "release_date": "2025-04-14", - "last_updated": "2025-04-14", + "open_weights": true, + "release_date": "2026-10-15", + "last_updated": "2026-10-15", "type": "chat" }, { - "id": "openai/gpt-6.1-terra-pro", - "name": "OpenAI: GPT-6.1 Terra Pro", - "display_name": "OpenAI: GPT-6.1 Terra Pro", + "id": "thinkingmachines/Inkling", + "name": "Inkling", + "display_name": "Inkling", "modalities": { "input": [ - "pdf", + "text", "image", - "text" + "audio" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 524288, + "output": 131072 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -91551,6 +91393,8 @@ "extra_capabilities": { "reasoning": { "effort_options": [ + "none", + "minimal", "low", "medium", "high", @@ -91560,217 +91404,207 @@ } }, "attachment": true, - "open_weights": false, - "release_date": "2026-10-26", - "last_updated": "2026-10-26", + "open_weights": true, + "release_date": "2026-07-15", + "last_updated": "2026-07-15", "type": "chat" }, { - "id": "openai/gpt-6-astra", - "name": "GPT-6 Astra", - "display_name": "GPT-6 Astra", + "id": "meta-llama/Llama-3.3-70B-Instruct-Turbo", + "name": "Llama 3.3 70B", + "display_name": "Llama 3.3 70B", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 131072, + "output": 131072 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" - ] - } + "supported": false }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-04-30", - "release_date": "2026-09-04", - "last_updated": "2026-09-04", + "attachment": false, + "open_weights": true, + "knowledge": "2023-12", + "release_date": "2024-12-06", + "last_updated": "2024-12-06", "type": "chat" }, { - "id": "openai/gpt-5.4", - "name": "GPT-5.4", - "display_name": "GPT-5.4", + "id": "moonshotai/Kimi-K3", + "name": "Kimi K3", + "display_name": "Kimi K3", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 1048576, + "output": 131072 }, "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "none", + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ "none", "low", - "medium", "high", - "xhigh" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + "max" + ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-03-05", - "last_updated": "2026-03-05", + "open_weights": true, + "release_date": "2026-07-16", + "last_updated": "2026-07-16", "type": "chat" }, { - "id": "openai/gpt-5-image", - "name": "GPT-5 Image", - "display_name": "GPT-5 Image", + "id": "zai-org/GLM-5.3-Flash", + "name": "GLM-5.3-Flash", + "display_name": "GLM-5.3-Flash", "modalities": { "input": [ - "image", "text", - "pdf" + "image", + "video" ], "output": [ - "image", "text" ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 1048575, + "output": 400000 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { "supported": true, "default": true }, "extra_capabilities": { "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ - "high" + "low", + "high", + "max" ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2024-10-01", - "release_date": "2025-10-14", - "last_updated": "2025-10-14" + "open_weights": true, + "release_date": "2026-08-26", + "last_updated": "2026-08-26", + "type": "chat" }, { - "id": "openai/gpt-5-image-mini", - "name": "GPT-5 Image Mini", - "display_name": "GPT-5 Image Mini", + "id": "zai-org/GLM-5", + "name": "GLM-5", + "display_name": "GLM-5", "modalities": { "input": [ - "pdf", - "image", "text" ], "output": [ - "image", "text" ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 202752, + "output": 131072 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { "supported": true, "default": true }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "high" + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, - "attachment": true, - "open_weights": false, - "release_date": "2025-10-16", - "last_updated": "2025-10-16" + "attachment": false, + "open_weights": true, + "release_date": "2026-02-12", + "last_updated": "2026-02-12", + "type": "chat" }, { - "id": "openai/gpt-audio", - "name": "GPT-Audio", - "display_name": "GPT-Audio", + "id": "zai-org/GLM-5.1", + "name": "GLM-5.1", + "display_name": "GLM-5.1", "modalities": { "input": [ - "text", - "audio", - "pdf" + "text" ], "output": [ - "text", - "audio" + "text" ] }, "limit": { - "context": 128000, - "output": 16384 + "context": 202752, + "output": 131072 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": true, - "open_weights": false, - "knowledge": "2023-10-01", - "release_date": "2025-08-28", - "last_updated": "2025-08-28", + "extra_capabilities": { + "reasoning": { + "supported": true + } + }, + "attachment": false, + "open_weights": true, + "release_date": "2026-04-07", + "last_updated": "2026-04-07", "type": "chat" }, { - "id": "openai/gpt-oss-20b", - "name": "GPT OSS 20B", - "display_name": "GPT OSS 20B", + "id": "zai-org/GLM-5.2", + "name": "GLM-5.2", + "display_name": "GLM-5.2", "modalities": { "input": [ "text" @@ -91780,8 +91614,8 @@ ] }, "limit": { - "context": 131072, - "output": 32768 + "context": 1048575, + "output": 164000 }, "temperature": true, "tool_call": true, @@ -91792,95 +91626,135 @@ "extra_capabilities": { "reasoning": { "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ - "low", - "medium", - "high" + "high", + "max" ] } }, "attachment": false, "open_weights": true, - "knowledge": "2024-06", - "release_date": "2025-08-05", - "last_updated": "2025-08-05", + "release_date": "2026-06-13", + "last_updated": "2026-06-13", "type": "chat" }, { - "id": "openai/gpt-3.5-turbo-16k", - "name": "OpenAI: GPT-3.5 Turbo 16k", - "display_name": "OpenAI: GPT-3.5 Turbo 16k", + "id": "zai-org/GLM-5.3", + "name": "GLM-5.3", + "display_name": "GLM-5.3", "modalities": { "input": [ - "text", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 16385, - "output": 4096 + "context": 1048575, + "output": 262144 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": true, - "open_weights": false, - "release_date": "2023-08-28", - "last_updated": "2023-08-28", + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "effort_options": [ + "low", + "high", + "max" + ] + } + }, + "attachment": false, + "open_weights": true, + "release_date": "2026-08-14", + "last_updated": "2026-08-14", "type": "chat" }, { - "id": "openai/gpt-3.5-turbo-0613", - "name": "OpenAI: GPT-3.5 Turbo (older v0613)", - "display_name": "OpenAI: GPT-3.5 Turbo (older v0613)", + "id": "reflection/Beam-1.0-501B-A23B", + "name": "Beam 1.0 501B A23B", + "display_name": "Beam 1.0 501B A23B", "modalities": { "input": [ - "text", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 4095, - "output": 3685 + "context": 1048576, + "output": 131072 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": true, - "open_weights": false, - "release_date": "2024-01-25", - "last_updated": "2024-01-25", + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "effort_options": [ + "minimal", + "low", + "medium", + "high", + "xhigh", + "max" + ] + } + }, + "attachment": false, + "open_weights": true, + "knowledge": "2026-06-30", + "release_date": "2026-10-05", + "last_updated": "2026-10-16", "type": "chat" }, { - "id": "openai/gpt-6-astra-pro", - "name": "OpenAI: GPT-6 Astra Pro ($$$$)", - "display_name": "OpenAI: GPT-6 Astra Pro ($$$$)", + "id": "meta-models/Muse-Spark-Open-816B-A42B", + "name": "Muse Spark Open", + "display_name": "Muse Spark Open", "modalities": { "input": [ - "pdf", + "text", "image", - "text" + "video" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 1048576, + "output": 131072 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -91888,152 +91762,174 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ + "none", "low", "medium", "high", - "xhigh", - "max" + "xhigh" ] } }, "attachment": true, - "open_weights": false, - "release_date": "2026-09-04", - "last_updated": "2026-09-04", + "open_weights": true, + "knowledge": "2026-01-04", + "release_date": "2026-10-15", + "last_updated": "2026-10-15", "type": "chat" }, { - "id": "openai/gpt-4o", - "name": "GPT-4o", - "display_name": "GPT-4o", + "id": "LiquidAI/LFM2-24B-A2B", + "name": "LFM2-24B-A2B", + "display_name": "LFM2-24B-A2B", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 16384 + "context": 32768, + "output": 32768 + }, + "temperature": true, + "tool_call": false, + "reasoning": { + "supported": false + }, + "attachment": false, + "open_weights": true, + "release_date": "2026-02-24", + "last_updated": "2026-02-24", + "type": "chat" + }, + { + "id": "essentialai/Rnj-1-Instruct", + "name": "Rnj-1 Instruct", + "display_name": "Rnj-1 Instruct", + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 32768, + "output": 32768 }, "temperature": true, "tool_call": true, "reasoning": { "supported": false }, - "attachment": true, - "open_weights": false, - "knowledge": "2023-09", - "release_date": "2024-05-13", - "last_updated": "2024-08-06", + "attachment": false, + "open_weights": true, + "release_date": "2025-12-04", + "last_updated": "2025-12-04", "type": "chat" }, { - "id": "openai/gpt-5.4-image-2", - "name": "GPT-5.4 Image 2", - "display_name": "GPT-5.4 Image 2", + "id": "MiniMaxAI/MiniMax-M2.5", + "name": "MiniMax-M2.5", + "display_name": "MiniMax-M2.5", "modalities": { "input": [ - "image", - "text", - "pdf" + "text" ], "output": [ - "image", "text" ] }, "limit": { - "context": 272000, - "output": 128000 + "context": 204800, + "output": 131072 }, - "temperature": false, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "none", - "effort_options": [ - "none", - "low", - "medium", - "high", - "xhigh" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + "supported": true } }, - "attachment": true, - "open_weights": false, - "release_date": "2026-04-21", - "last_updated": "2026-04-21" + "attachment": false, + "open_weights": true, + "release_date": "2026-02-12", + "last_updated": "2026-02-12", + "type": "chat" }, { - "id": "openai/gpt-4o-mini-2024-07-18", - "name": "OpenAI: GPT-4o-mini (2024-07-18)", - "display_name": "OpenAI: GPT-4o-mini (2024-07-18)", + "id": "MiniMaxAI/MiniMax-M2.7", + "name": "MiniMax-M2.7", + "display_name": "MiniMax-M2.7", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 16384 + "context": 196608, + "output": 131072 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": true, - "open_weights": false, - "knowledge": "2023-09", - "release_date": "2024-07-18", - "last_updated": "2024-07-18", + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } + }, + "attachment": false, + "open_weights": true, + "release_date": "2026-03-18", + "last_updated": "2026-03-18", "type": "chat" }, { - "id": "openai/gpt-5-pro", - "name": "GPT-5 Pro", - "display_name": "GPT-5 Pro", + "id": "MiniMaxAI/MiniMax-M3", + "name": "MiniMax-M3", + "display_name": "MiniMax-M3", "modalities": { "input": [ - "image", "text", - "pdf" + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 524288, + "output": 524288 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -92041,45 +91937,88 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "fixed", - "effort": "high", - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + "supported": true } }, "attachment": true, - "open_weights": false, - "knowledge": "2024-09-30", - "release_date": "2025-10-06", - "last_updated": "2025-10-06", + "open_weights": true, + "release_date": "2026-06-01", + "last_updated": "2026-06-01", "type": "chat" }, { - "id": "openai/o3", - "name": "o3", - "display_name": "o3", + "id": "deepseek-ai/DeepSeek-V3-1", + "name": "DeepSeek V3.1", + "display_name": "DeepSeek V3.1", + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "output": 131072 + }, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": true, + "default": true + }, + "attachment": false, + "open_weights": true, + "release_date": "2025-08-21", + "last_updated": "2025-08-21", + "type": "chat" + }, + { + "id": "deepseek-ai/DeepSeek-V3", + "name": "DeepSeek-V3", + "display_name": "DeepSeek-V3", + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "output": 131072 + }, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": false + }, + "attachment": false, + "open_weights": true, + "release_date": "2024-12-26", + "last_updated": "2024-12-26", + "type": "chat" + }, + { + "id": "deepseek-ai/DeepSeek-V4.1-Flash", + "name": "DeepSeek V4.1 Flash", + "display_name": "DeepSeek V4.1 Flash", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 100000 + "context": 1048576, + "output": 384000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -92088,77 +92027,84 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ + "none", "low", - "medium", - "high" - ], - "visibility": "hidden" + "high", + "max" + ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2024-05", - "release_date": "2025-04-16", - "last_updated": "2025-04-16", + "open_weights": true, + "knowledge": "2025-05", + "release_date": "2026-09-10", + "last_updated": "2026-09-10", "type": "chat" }, { - "id": "openai/gpt-chat-latest", - "name": "OpenAI: GPT Chat Latest", - "display_name": "OpenAI: GPT Chat Latest", + "id": "deepseek-ai/DeepSeek-R1", + "name": "DeepSeek-R1", + "display_name": "DeepSeek-R1", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 163839, + "output": 163839 }, - "temperature": false, - "tool_call": true, + "temperature": true, + "tool_call": false, "reasoning": { - "supported": true + "supported": true, + "default": true }, "extra_capabilities": { "reasoning": { - "supported": true + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] } }, - "attachment": true, - "open_weights": false, - "release_date": "2026-05-05", - "last_updated": "2026-05-05", + "attachment": false, + "open_weights": true, + "knowledge": "2024-07", + "release_date": "2025-01-20", + "last_updated": "2025-05-29", "type": "chat" }, { - "id": "openai/gpt-6-sol", - "name": "GPT-6 Sol", - "display_name": "GPT-6 Sol", + "id": "deepseek-ai/DeepSeek-V4-Flash-0731", + "name": "DeepSeek V4 Flash 0731", + "display_name": "DeepSeek V4 Flash 0731", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 1048576, + "output": 384000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -92166,42 +92112,43 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ - "none", - "low", - "medium", "high", - "xhigh", "max" ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-04-20", - "release_date": "2026-09-22", - "last_updated": "2026-09-22", + "attachment": false, + "open_weights": true, + "knowledge": "2025-05", + "release_date": "2026-07-31", + "last_updated": "2026-07-31", "type": "chat" }, { - "id": "openai/gpt-5.6-sol", - "name": "GPT-5.6 Sol", - "display_name": "GPT-5.6 Sol", + "id": "deepseek-ai/DeepSeek-V4-Pro-0813", + "name": "DeepSeek V4 Pro 0813", + "display_name": "DeepSeek V4 Pro 0813", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 1048576, + "output": 384000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -92210,50 +92157,76 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ - "none", - "low", - "medium", "high", - "xhigh", "max" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-02-16", - "release_date": "2026-07-09", - "last_updated": "2026-07-09", + "attachment": false, + "open_weights": true, + "release_date": "2026-08-13", + "last_updated": "2026-08-22", + "type": "chat" + } + ] + }, + "atomic-chat": { + "id": "atomic-chat", + "name": "Atomic Chat", + "display_name": "Atomic Chat", + "api": "http://127.0.0.1:1337/v1", + "doc": "https://atomic.chat", + "models": [ + { + "id": "gemma-4-E4B-it-IQ4_XS", + "name": "Gemma 4 E4B Instruct (IQ4_XS)", + "display_name": "Gemma 4 E4B Instruct (IQ4_XS)", + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 32768, + "output": 8192 + }, + "temperature": true, + "tool_call": false, + "reasoning": { + "supported": false + }, + "attachment": false, + "open_weights": true, + "release_date": "2026-04-02", + "last_updated": "2026-04-02", "type": "chat" }, { - "id": "openai/gpt-4o-2024-11-20", - "name": "GPT-4o (2024-11-20)", - "display_name": "GPT-4o (2024-11-20)", + "id": "Qwen3_5-9B-Q4_K_M", + "name": "Qwen 3.5 9B (Q4_K_M)", + "display_name": "Qwen 3.5 9B (Q4_K_M)", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 16384 + "context": 32768, + "output": 8192 }, "temperature": true, "tool_call": true, @@ -92261,66 +92234,103 @@ "supported": false }, "attachment": true, - "open_weights": false, - "knowledge": "2023-09", - "release_date": "2024-11-20", - "last_updated": "2024-11-20", + "open_weights": true, + "release_date": "2026-02-23", + "last_updated": "2026-02-23", "type": "chat" }, { - "id": "openai/gpt-5.6-terra", - "name": "GPT-5.6 Terra", - "display_name": "GPT-5.6 Terra", + "id": "gemma-4-E4B-it-MLX-4bit", + "name": "Gemma 4 E4B Instruct (MLX 4-bit)", + "display_name": "Gemma 4 E4B Instruct (MLX 4-bit)", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 32768, + "output": 8192 }, - "temperature": false, + "temperature": true, + "tool_call": false, + "reasoning": { + "supported": false + }, + "attachment": false, + "open_weights": true, + "release_date": "2026-04-02", + "last_updated": "2026-04-02", + "type": "chat" + }, + { + "id": "Meta-Llama-3_1-8B-Instruct-GGUF", + "name": "Meta Llama 3.1 8B Instruct (GGUF)", + "display_name": "Meta Llama 3.1 8B Instruct (GGUF)", + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "output": 4096 + }, + "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true + "supported": false }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", - "effort_options": [ - "none", - "low", - "medium", - "high", - "xhigh", - "max" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" - } + "attachment": false, + "open_weights": true, + "knowledge": "2023-12", + "release_date": "2024-07-23", + "last_updated": "2024-07-23", + "type": "chat" + }, + { + "id": "Qwen3_5-9B-MLX-4bit", + "name": "Qwen 3.5 9B (MLX 4-bit)", + "display_name": "Qwen 3.5 9B (MLX 4-bit)", + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 32768, + "output": 8192 + }, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": false }, "attachment": true, - "open_weights": false, - "knowledge": "2026-02-16", - "release_date": "2026-07-09", - "last_updated": "2026-07-09", + "open_weights": true, + "release_date": "2026-02-23", + "last_updated": "2026-02-23", "type": "chat" - }, + } + ] + }, + "stackit": { + "id": "stackit", + "name": "STACKIT", + "display_name": "STACKIT", + "api": "https://api.openai-compat.model-serving.eu01.onstackit.cloud/v1", + "doc": "https://docs.stackit.cloud/products/data-and-ai/ai-model-serving/basics/available-shared-models", + "models": [ { "id": "openai/gpt-oss-120b", "name": "GPT OSS 120B", @@ -92334,8 +92344,8 @@ ] }, "limit": { - "context": 131072, - "output": 65536 + "context": 131000, + "output": 8192 }, "temperature": true, "tool_call": true, @@ -92361,161 +92371,141 @@ "type": "chat" }, { - "id": "openai/gpt-5.1-codex", - "name": "GPT-5.1 Codex", - "display_name": "GPT-5.1 Codex", + "id": "openai/gpt-oss-20b", + "name": "GPT OSS 20B", + "display_name": "GPT OSS 20B", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 131072, + "output": 8192 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "none", - "effort_options": [ - "none", - "low", - "medium", - "high" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + "supported": true } }, - "attachment": true, - "open_weights": false, - "knowledge": "2024-09-30", - "release_date": "2025-11-13", - "last_updated": "2025-11-13", + "attachment": false, + "open_weights": true, + "knowledge": "2024-06", + "release_date": "2025-08-05", + "last_updated": "2025-08-05", "type": "chat" }, { - "id": "openai/gpt-6-luna", - "name": "GPT-6 Luna", - "display_name": "GPT-6 Luna", + "id": "Qwen/Qwen3-VL-235B-A22B-Instruct-FP8", + "name": "Qwen3-VL 235B", + "display_name": "Qwen3-VL 235B", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 218000, + "output": 16384 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "none", - "low", - "medium", - "high", - "xhigh", - "max" - ] - } + "supported": false }, "attachment": true, - "open_weights": false, - "knowledge": "2026-05-18", - "release_date": "2026-09-22", - "last_updated": "2026-09-22", + "open_weights": true, + "knowledge": "2025-03-31", + "release_date": "2025-09-23", + "last_updated": "2025-09-23", "type": "chat" }, { - "id": "openai/gpt-5-mini", - "name": "GPT-5 Mini", - "display_name": "GPT-5 Mini", + "id": "Qwen/Qwen3.6-27B", + "name": "Qwen3.6 27B", + "display_name": "Qwen3.6 27B", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 262144, + "output": 16384 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true + "supported": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", - "effort_options": [ - "minimal", - "low", - "medium", - "high" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2024-05-30", - "release_date": "2025-08-07", - "last_updated": "2025-08-07", + "attachment": false, + "open_weights": true, + "release_date": "2026-04-22", + "last_updated": "2026-04-22", "type": "chat" }, { - "id": "openai/gpt-4o-2024-08-06", - "name": "GPT-4o (2024-08-06)", - "display_name": "GPT-4o (2024-08-06)", + "id": "Qwen/Qwen3-VL-Embedding-8B", + "name": "Qwen3-VL Embedding 8B", + "display_name": "Qwen3-VL Embedding 8B", "modalities": { "input": [ "text", - "image", - "pdf" + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 32000, + "output": 4096 + }, + "temperature": false, + "tool_call": false, + "reasoning": { + "supported": false + }, + "attachment": true, + "open_weights": true, + "release_date": "2026-02-05", + "last_updated": "2026-02-05", + "type": "embedding" + }, + { + "id": "cortecs/Llama-3.3-70B-Instruct-FP8-Dynamic", + "name": "Llama 3.3 70B", + "display_name": "Llama 3.3 70B", + "modalities": { + "input": [ + "text" ], "output": [ "text" @@ -92523,24 +92513,89 @@ }, "limit": { "context": 128000, - "output": 16384 + "output": 4096 }, "temperature": true, "tool_call": true, "reasoning": { "supported": false }, + "attachment": false, + "open_weights": true, + "knowledge": "2023-12", + "release_date": "2024-12-06", + "last_updated": "2024-12-06", + "type": "chat" + }, + { + "id": "google/gemma-3-27b-it", + "name": "Gemma 3 27B", + "display_name": "Gemma 3 27B", + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 37000, + "output": 4096 + }, + "temperature": true, + "tool_call": false, + "reasoning": { + "supported": false + }, "attachment": true, - "open_weights": false, - "knowledge": "2023-09", - "release_date": "2024-08-06", - "last_updated": "2024-08-06", + "open_weights": true, + "knowledge": "2024-08", + "release_date": "2025-03-12", + "last_updated": "2025-03-12", "type": "chat" }, { - "id": "openai/gpt-5.4-pro", - "name": "GPT-5.4 Pro", - "display_name": "GPT-5.4 Pro", + "id": "intfloat/e5-mistral-7b-instruct", + "name": "E5 Mistral 7B", + "display_name": "E5 Mistral 7B", + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 4096, + "output": 4096 + }, + "temperature": false, + "tool_call": false, + "reasoning": { + "supported": false + }, + "attachment": false, + "open_weights": true, + "release_date": "2023-12-11", + "last_updated": "2023-12-11", + "type": "embedding" + } + ] + }, + "ai-router": { + "id": "ai-router", + "name": "AI-ROUTER", + "display_name": "AI-ROUTER", + "api": "https://api.ai-router.dev/v1", + "doc": "https://ai-router.dev/openai-compatible-api-gateway/", + "models": [ + { + "id": "gpt-5.4", + "name": "GPT-5.4", + "display_name": "GPT-5.4", "modalities": { "input": [ "text", @@ -92555,19 +92610,21 @@ "context": 1050000, "output": 128000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, + "default_enabled": false, "mode": "effort", - "effort": "high", + "effort": "none", "effort_options": [ + "none", + "low", "medium", "high", "xhigh" @@ -92589,13 +92646,13 @@ "type": "chat" }, { - "id": "openai/gpt-5.1-codex-mini", - "name": "GPT-5.1 Codex mini", - "display_name": "GPT-5.1 Codex mini", + "id": "gpt-5.6-terra", + "name": "GPT-5.6 Terra", + "display_name": "GPT-5.6 Terra", "modalities": { "input": [ - "image", "text", + "image", "pdf" ], "output": [ @@ -92603,26 +92660,28 @@ ] }, "limit": { - "context": 400000, + "context": 1050000, "output": 128000 }, "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, + "default_enabled": true, "mode": "effort", - "effort": "none", + "effort": "medium", "effort_options": [ "none", "low", "medium", - "high" + "high", + "xhigh", + "max" ], "verbosity": "medium", "verbosity_options": [ @@ -92635,48 +92694,19 @@ }, "attachment": true, "open_weights": false, - "knowledge": "2024-09-30", - "release_date": "2025-11-13", - "last_updated": "2025-11-13", + "knowledge": "2026-02-16", + "release_date": "2026-07-09", + "last_updated": "2026-07-09", "type": "chat" }, { - "id": "openai/gpt-4", - "name": "GPT-4", - "display_name": "GPT-4", + "id": "gpt-5.6-luna", + "name": "GPT-5.6 Luna", + "display_name": "GPT-5.6 Luna", "modalities": { "input": [ "text", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 8191, - "output": 4096 - }, - "temperature": true, - "tool_call": true, - "reasoning": { - "supported": false - }, - "attachment": true, - "open_weights": false, - "knowledge": "2023-11", - "release_date": "2023-11-06", - "last_updated": "2024-04-09", - "type": "chat" - }, - { - "id": "openai/gpt-5.1", - "name": "GPT-5.1", - "display_name": "GPT-5.1", - "modalities": { - "input": [ "image", - "text", "pdf" ], "output": [ @@ -92684,26 +92714,28 @@ ] }, "limit": { - "context": 400000, + "context": 1050000, "output": 128000 }, "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, + "default_enabled": true, "mode": "effort", - "effort": "none", + "effort": "medium", "effort_options": [ "none", "low", "medium", - "high" + "high", + "xhigh", + "max" ], "verbosity": "medium", "verbosity_options": [ @@ -92716,15 +92748,15 @@ }, "attachment": true, "open_weights": false, - "knowledge": "2024-09-30", - "release_date": "2025-11-13", - "last_updated": "2025-11-13", + "knowledge": "2026-02-16", + "release_date": "2026-07-09", + "last_updated": "2026-07-09", "type": "chat" }, { - "id": "openai/gpt-5.6-luna", - "name": "GPT-5.6 Luna", - "display_name": "GPT-5.6 Luna", + "id": "gpt-5.6-sol", + "name": "GPT-5.6 Sol", + "display_name": "GPT-5.6 Sol", "modalities": { "input": [ "text", @@ -92776,68 +92808,36 @@ "type": "chat" }, { - "id": "openai/gpt-audio-mini", - "name": "GPT-Audio Mini", - "display_name": "GPT-Audio Mini", + "id": "gpt-5.5", + "name": "GPT-5.5", + "display_name": "GPT-5.5", "modalities": { "input": [ "text", - "audio", - "pdf" - ], - "output": [ - "text", - "audio" - ] - }, - "limit": { - "context": 128000, - "output": 16384 - }, - "temperature": true, - "tool_call": true, - "reasoning": { - "supported": false - }, - "attachment": true, - "open_weights": false, - "knowledge": "2023-10-01", - "release_date": "2025-10-06", - "last_updated": "2025-10-06", - "type": "chat" - }, - { - "id": "openai/gpt-5.4-mini", - "name": "GPT-5.4 mini", - "display_name": "GPT-5.4 mini", - "modalities": { - "input": [ - "pdf", "image", - "text" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 400000, + "context": 1050000, "output": 128000 }, "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, + "default_enabled": true, "mode": "effort", - "effort": "none", + "effort": "medium", "effort_options": [ - "none", "low", "medium", "high", @@ -92854,30 +92854,37 @@ }, "attachment": true, "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-03-17", - "last_updated": "2026-03-17", + "knowledge": "2025-12-01", + "release_date": "2026-04-23", + "last_updated": "2026-04-23", "type": "chat" - }, + } + ] + }, + "aiand": { + "id": "aiand", + "name": "ai&", + "display_name": "ai&", + "api": "https://api.aiand.com/v1", + "doc": "https://docs.aiand.com/", + "models": [ { - "id": "openai/gpt-5.3-codex", - "name": "GPT-5.3 Codex", - "display_name": "GPT-5.3 Codex", + "id": "openai/gpt-oss-120b", + "name": "GPT OSS 120B", + "display_name": "GPT OSS 120B", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 131072, + "output": 32768 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -92886,50 +92893,45 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", - "effort_options": [ - "low", - "medium", - "high", - "xhigh" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ], - "verbosity": "medium", - "verbosity_options": [ + "effort_options": [ "low", "medium", "high" - ], - "visibility": "hidden" + ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-02-05", - "last_updated": "2026-02-05", + "attachment": false, + "open_weights": true, + "knowledge": "2024-06", + "release_date": "2025-08-05", + "last_updated": "2025-08-05", "type": "chat" }, { - "id": "openai/gpt-6-sol-pro", - "name": "OpenAI: GPT-6 Sol Pro", - "display_name": "OpenAI: GPT-6 Sol Pro", + "id": "moonshotai/kimi-k3", + "name": "Kimi K3", + "display_name": "Kimi K3", "modalities": { "input": [ - "pdf", + "text", "image", - "text" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 1048576, + "output": 131072 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -92937,30 +92939,35 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ - "none", "low", - "medium", "high", - "xhigh", "max" ] } }, "attachment": true, - "open_weights": false, - "release_date": "2026-09-22", - "last_updated": "2026-09-22", + "open_weights": true, + "release_date": "2026-07-16", + "last_updated": "2026-07-16", "type": "chat" }, { - "id": "openai/gpt-5.6-terra-pro", - "name": "GPT-5.6 Terra", - "display_name": "GPT-5.6 Terra", + "id": "zai-org/glm-5.3-flash", + "name": "GLM-5.3-Flash", + "display_name": "GLM-5.3-Flash", "modalities": { "input": [ "text", "image", + "video", "pdf" ], "output": [ @@ -92968,10 +92975,10 @@ ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 1048550, + "output": 131072 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -92979,42 +92986,43 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ - "none", "low", - "medium", "high", - "xhigh", "max" ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2026-02-16", - "release_date": "2026-07-09", - "last_updated": "2026-07-09", + "open_weights": true, + "release_date": "2026-08-26", + "last_updated": "2026-08-26", "type": "chat" }, { - "id": "openai/gpt-5.6-sol-pro", - "name": "GPT-5.6 Sol", - "display_name": "GPT-5.6 Sol", + "id": "zai-org/glm-5.3", + "name": "GLM-5.3", + "display_name": "GLM-5.3", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 1048576, + "output": 131072 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -93022,31 +93030,35 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ - "none", "low", - "medium", "high", - "xhigh", "max" ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-02-16", - "release_date": "2026-07-09", - "last_updated": "2026-07-09", + "attachment": false, + "open_weights": true, + "release_date": "2026-08-14", + "last_updated": "2026-08-14", "type": "chat" }, { - "id": "openai/gpt-4.1-mini", - "name": "GPT-4.1 mini", - "display_name": "GPT-4.1 mini", + "id": "google/gemma-4-31b-it", + "name": "Gemma 4 31B IT", + "display_name": "Gemma 4 31B IT", "modalities": { "input": [ "text", "image", + "video", "pdf" ], "output": [ @@ -93054,93 +93066,105 @@ ] }, "limit": { - "context": 1047576, + "context": 262144, "output": 32768 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "effort_options": [ + "none", + "high" + ] + } }, "attachment": true, - "open_weights": false, - "knowledge": "2024-04", - "release_date": "2025-04-14", - "last_updated": "2025-04-14", + "open_weights": true, + "knowledge": "2025-01", + "release_date": "2026-04-02", + "last_updated": "2026-04-02", "type": "chat" }, { - "id": "openai/gpt-5.4-nano", - "name": "GPT-5.4 nano", - "display_name": "GPT-5.4 nano", + "id": "qwen/qwen3.8-27b", + "name": "Qwen3.8 27B", + "display_name": "Qwen3.8 27B", "modalities": { "input": [ - "pdf", + "text", "image", - "text" + "video", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 262144, + "output": 32768 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "none", + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ "none", "low", "medium", - "high", "xhigh" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-03-17", - "last_updated": "2026-03-17", + "open_weights": true, + "release_date": "2026-08-14", + "last_updated": "2026-08-14", "type": "chat" }, { - "id": "openai/o3-pro", - "name": "o3-pro", - "display_name": "o3-pro", + "id": "qwen/qwen3.6-27b", + "name": "Qwen3.6 27B", + "display_name": "Qwen3.6 27B", "modalities": { "input": [ "text", - "pdf", - "image" + "image", + "video", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 100000 + "context": 262144, + "output": 65536 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -93149,43 +93173,41 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ - "low", - "medium", + "none", "high" - ], - "visibility": "hidden" + ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2024-05", - "release_date": "2025-06-10", - "last_updated": "2025-06-10", + "open_weights": true, + "release_date": "2026-04-22", + "last_updated": "2026-04-22", "type": "chat" }, { - "id": "openai/gpt-5.5-pro", - "name": "GPT-5.5 Pro", - "display_name": "GPT-5.5 Pro", + "id": "motif-technologies/motif-3", + "name": "Motif 3", + "display_name": "Motif 3", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 262144, + "output": 262144 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -93194,39 +93216,43 @@ "extra_capabilities": { "reasoning": { "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ - "medium", - "high", - "xhigh" + "none", + "high" ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-12-01", - "release_date": "2026-04-23", - "last_updated": "2026-04-23", + "attachment": false, + "open_weights": true, + "release_date": "2026-08-07", + "last_updated": "2026-08-07", "type": "chat" }, { - "id": "openai/gpt-5.2-pro", - "name": "GPT-5.2 Pro", - "display_name": "GPT-5.2 Pro", + "id": "meta/muse-spark-open", + "name": "Muse Spark Open", + "display_name": "Muse Spark Open", "modalities": { "input": [ - "image", "text", - "pdf" + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 1048576, + "output": 943718 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -93235,45 +93261,44 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "high", + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ + "low", "medium", "high", "xhigh" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2025-12-11", - "last_updated": "2025-12-11", + "open_weights": true, + "knowledge": "2026-01-04", + "release_date": "2026-10-15", + "last_updated": "2026-10-15", "type": "chat" }, { - "id": "openai/gpt-oss-safeguard-20b", - "name": "GPT OSS Safeguard 20B", - "display_name": "GPT OSS Safeguard 20B", + "id": "deepseek-ai/deepseek-v4.1-flash-1017", + "name": "DeepSeek V4.1 Flash 1017", + "display_name": "DeepSeek V4.1 Flash 1017", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 65536 + "context": 1048576, + "output": 384000 }, "temperature": true, "tool_call": true, @@ -93283,21 +93308,33 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ - "high" + "none", + "low", + "high", + "xhigh", + "max" ] } }, - "attachment": false, + "attachment": true, "open_weights": true, - "release_date": "2025-10-29", - "last_updated": "2025-10-29", + "knowledge": "2025-05", + "release_date": "2026-10-17", + "last_updated": "2026-10-17", "type": "chat" }, { - "id": "openai/gpt-4.1", - "name": "GPT-4.1", - "display_name": "GPT-4.1", + "id": "deepseek-ai/deepseek-v4.1-flash", + "name": "DeepSeek V4.1 Flash", + "display_name": "DeepSeek V4.1 Flash", "modalities": { "input": [ "text", @@ -93309,29 +93346,46 @@ ] }, "limit": { - "context": 1047576, - "output": 32768 + "context": 1048576, + "output": 384000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "effort_options": [ + "none", + "low", + "high", + "xhigh", + "max" + ] + } }, "attachment": true, - "open_weights": false, - "knowledge": "2024-04", - "release_date": "2025-04-14", - "last_updated": "2025-04-14", + "open_weights": true, + "knowledge": "2025-05", + "release_date": "2026-09-10", + "last_updated": "2026-09-10", "type": "chat" }, { - "id": "openai/gpt-6-luna-pro", - "name": "OpenAI: GPT-6 Luna Pro", - "display_name": "OpenAI: GPT-6 Luna Pro", + "id": "deepseek-ai/deepseek-v4-pro", + "name": "DeepSeek V4 Pro", + "display_name": "DeepSeek V4 Pro", "modalities": { "input": [ - "pdf", - "image", "text" ], "output": [ @@ -93339,10 +93393,10 @@ ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 1048576, + "output": 384000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -93350,31 +93404,50 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "high", "effort_options": [ - "none", "low", - "medium", "high", - "xhigh", "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." ] } }, - "attachment": true, - "open_weights": false, - "release_date": "2026-09-22", - "last_updated": "2026-09-22", + "attachment": false, + "open_weights": true, + "knowledge": "2025-05", + "release_date": "2026-04-24", + "last_updated": "2026-04-24", "type": "chat" - }, + } + ] + }, + "ofox": { + "id": "ofox", + "name": "Ofox", + "display_name": "Ofox", + "api": "https://api.ofox.ai/v1", + "doc": "https://ofox.ai/docs", + "models": [ { - "id": "openai/gpt-5.2-codex", - "name": "GPT-5.2 Codex", - "display_name": "GPT-5.2 Codex", + "id": "openai/gpt-5.4-nano", + "name": "GPT-5.4 nano", + "display_name": "GPT-5.4 nano", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" @@ -93384,19 +93457,20 @@ "context": 400000, "output": 128000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, + "default_enabled": false, "mode": "effort", - "effort": "medium", + "effort": "none", "effort_options": [ + "none", "low", "medium", "high", @@ -93414,14 +93488,14 @@ "attachment": true, "open_weights": false, "knowledge": "2025-08-31", - "release_date": "2025-12-11", - "last_updated": "2025-12-11", + "release_date": "2026-03-17", + "last_updated": "2026-03-17", "type": "chat" }, { "id": "openai/gpt-6.1-terra", - "name": "OpenAI: GPT-6.1 Terra", - "display_name": "OpenAI: GPT-6.1 Terra", + "name": "GPT-6.1 Terra", + "display_name": "GPT-6.1 Terra", "modalities": { "input": [ "text", @@ -93462,9 +93536,9 @@ "type": "chat" }, { - "id": "openai/gpt-4o-2024-05-13", - "name": "GPT-4o (2024-05-13)", - "display_name": "GPT-4o (2024-05-13)", + "id": "openai/gpt-5.4", + "name": "GPT-5.4", + "display_name": "GPT-5.4", "modalities": { "input": [ "text", @@ -93476,80 +93550,111 @@ ] }, "limit": { - "context": 128000, - "output": 4096 + "context": 1050000, + "output": 128000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": false + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": false, + "mode": "effort", + "effort": "none", + "effort_options": [ + "none", + "low", + "medium", + "high", + "xhigh" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" + } }, "attachment": true, "open_weights": false, - "knowledge": "2023-09", - "release_date": "2024-05-13", - "last_updated": "2024-05-13", + "knowledge": "2025-08-31", + "release_date": "2026-03-05", + "last_updated": "2026-03-05", "type": "chat" }, { - "id": "openai/gpt-5.6-luna-pro", - "name": "GPT-5.6 Luna", - "display_name": "GPT-5.6 Luna", + "id": "openai/gpt-5.4-mini", + "name": "GPT-5.4 mini", + "display_name": "GPT-5.4 mini", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1050000, + "context": 400000, "output": 128000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": false, + "mode": "effort", + "effort": "none", "effort_options": [ "none", "low", "medium", "high", - "xhigh", - "max" - ] + "xhigh" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, "attachment": true, "open_weights": false, - "knowledge": "2026-02-16", - "release_date": "2026-07-09", - "last_updated": "2026-07-09", + "knowledge": "2025-08-31", + "release_date": "2026-03-17", + "last_updated": "2026-03-17", "type": "chat" }, { - "id": "openai/gpt-5.2", - "name": "GPT-5.2", - "display_name": "GPT-5.2", + "id": "openai/gpt-5.1-codex-max", + "name": "GPT-5.1 Codex Max", + "display_name": "GPT-5.1 Codex Max", "modalities": { "input": [ - "pdf", - "image", - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 400000, + "context": 256000, "output": 128000 }, "temperature": false, @@ -93568,8 +93673,7 @@ "none", "low", "medium", - "high", - "xhigh" + "high" ], "verbosity": "medium", "verbosity_options": [ @@ -93582,15 +93686,15 @@ }, "attachment": true, "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2025-12-11", - "last_updated": "2025-12-11", + "knowledge": "2024-09-30", + "release_date": "2025-11-13", + "last_updated": "2025-11-13", "type": "chat" }, { - "id": "openai/gpt-6.1-sol", - "name": "GPT-6.1 Sol", - "display_name": "GPT-6.1 Sol", + "id": "openai/gpt-6-sol", + "name": "GPT-6 Sol", + "display_name": "GPT-6 Sol", "modalities": { "input": [ "text", @@ -93614,6 +93718,7 @@ "extra_capabilities": { "reasoning": { "effort_options": [ + "none", "low", "medium", "high", @@ -93624,20 +93729,20 @@ }, "attachment": true, "open_weights": false, - "knowledge": "2026-04-30", - "release_date": "2026-09-29", - "last_updated": "2026-09-29", + "knowledge": "2026-04-20", + "release_date": "2026-09-22", + "last_updated": "2026-09-22", "type": "chat" }, { - "id": "openai/gpt-6.1-sol-pro", - "name": "OpenAI: GPT-6.1 Sol Pro", - "display_name": "OpenAI: GPT-6.1 Sol Pro", + "id": "openai/gpt-5.6-terra", + "name": "GPT-5.6 Terra", + "display_name": "GPT-5.6 Terra", "modalities": { "input": [ - "pdf", + "text", "image", - "text" + "pdf" ], "output": [ "text" @@ -93655,25 +93760,38 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", "effort_options": [ + "none", "low", "medium", "high", "xhigh", "max" - ] + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, "attachment": true, "open_weights": false, - "release_date": "2026-09-29", - "last_updated": "2026-09-29", + "knowledge": "2026-02-16", + "release_date": "2026-07-09", + "last_updated": "2026-07-09", "type": "chat" }, { - "id": "openai/gpt-5-nano", - "name": "GPT-5 Nano", - "display_name": "GPT-5 Nano", + "id": "openai/gpt-5.3-codex", + "name": "GPT-5.3 Codex", + "display_name": "GPT-5.3 Codex", "modalities": { "input": [ "text", @@ -93688,7 +93806,7 @@ "context": 400000, "output": 128000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -93701,10 +93819,10 @@ "mode": "effort", "effort": "medium", "effort_options": [ - "minimal", "low", "medium", - "high" + "high", + "xhigh" ], "verbosity": "medium", "verbosity_options": [ @@ -93717,28 +93835,30 @@ }, "attachment": true, "open_weights": false, - "knowledge": "2024-05-30", - "release_date": "2025-08-07", - "last_updated": "2025-08-07", + "knowledge": "2025-08-31", + "release_date": "2026-02-05", + "last_updated": "2026-02-05", "type": "chat" }, { - "id": "tencent/hy4-preview", - "name": "Hy4 preview", - "display_name": "Hy4 preview", + "id": "openai/gpt-6-astra", + "name": "GPT-6 Astra", + "display_name": "GPT-6 Astra", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 64000 + "context": 1050000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -93748,98 +93868,143 @@ "reasoning": { "supported": true, "effort_options": [ - "none", "low", - "high" + "medium", + "high", + "xhigh", + "max" ] } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-08-28", - "last_updated": "2026-08-28", + "attachment": true, + "open_weights": false, + "knowledge": "2026-04-30", + "release_date": "2026-09-04", + "last_updated": "2026-09-04", "type": "chat" }, { - "id": "tencent/hy-mt2-30b-a3b", - "name": "Hy-MT2-30B-A3B", - "display_name": "Hy-MT2-30B-A3B", + "id": "openai/gpt-5.2-codex", + "name": "GPT-5.2 Codex", + "display_name": "GPT-5.2 Codex", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 8192, - "output": 4096 + "context": 400000, + "output": 128000 }, - "temperature": true, - "tool_call": false, + "temperature": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, - "open_weights": true, - "release_date": "2026-05-21", - "last_updated": "2026-05-21", + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", + "effort_options": [ + "low", + "medium", + "high", + "xhigh" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" + } + }, + "attachment": true, + "open_weights": false, + "knowledge": "2025-08-31", + "release_date": "2025-12-11", + "last_updated": "2025-12-11", "type": "chat" }, { - "id": "tencent/hunyuan-a13b-instruct", - "name": "Tencent: Hunyuan A13B Instruct", - "display_name": "Tencent: Hunyuan A13B Instruct", + "id": "openai/gpt-5.2", + "name": "GPT-5.2", + "display_name": "GPT-5.2", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 117964 + "context": 400000, + "output": 128000 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": false, + "mode": "effort", + "effort": "none", "effort_options": [ "none", + "low", + "medium", + "high", + "xhigh" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", "high" - ] + ], + "visibility": "hidden" } }, - "attachment": false, + "attachment": true, "open_weights": false, - "release_date": "2025-07-08", - "last_updated": "2025-07-08", + "knowledge": "2025-08-31", + "release_date": "2025-12-11", + "last_updated": "2025-12-11", "type": "chat" }, { - "id": "tencent/hy3-preview", - "name": "Hy3 preview", - "display_name": "Hy3 preview", + "id": "openai/gpt-5.4-pro", + "name": "GPT-5.4 Pro", + "display_name": "GPT-5.4 Pro", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 235929 + "context": 1050000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -93847,36 +94012,50 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "high", "effort_options": [ - "none", + "medium", + "high", + "xhigh" + ], + "verbosity": "medium", + "verbosity_options": [ "low", + "medium", "high" - ] + ], + "visibility": "hidden" } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-04-20", - "last_updated": "2026-04-20", + "attachment": true, + "open_weights": false, + "knowledge": "2025-08-31", + "release_date": "2026-03-05", + "last_updated": "2026-03-05", "type": "chat" }, { - "id": "tencent/hy3", - "name": "Hy3", - "display_name": "Hy3", + "id": "openai/gpt-6.1-sol", + "name": "GPT-6.1 Sol", + "display_name": "GPT-6.1 Sol", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 262144, + "context": 1050000, "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -93884,151 +94063,196 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, "effort_options": [ - "none", "low", - "high" + "medium", + "high", + "xhigh", + "max" ] } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-07-06", - "last_updated": "2026-07-06", + "attachment": true, + "open_weights": false, + "knowledge": "2026-04-30", + "release_date": "2026-09-29", + "last_updated": "2026-09-29", "type": "chat" }, { - "id": "tencent/hy-mt2-7b", - "name": "Hy-MT2-7B", - "display_name": "Hy-MT2-7B", + "id": "openai/gpt-5-nano", + "name": "GPT-5 Nano", + "display_name": "GPT-5 Nano", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 8192, - "output": 4096 + "context": 400000, + "output": 128000 }, - "temperature": true, - "tool_call": false, + "temperature": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, - "open_weights": true, - "release_date": "2026-05-21", - "last_updated": "2026-05-21", + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", + "effort_options": [ + "minimal", + "low", + "medium", + "high" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" + } + }, + "attachment": true, + "open_weights": false, + "knowledge": "2024-05-30", + "release_date": "2025-08-07", + "last_updated": "2025-08-07", "type": "chat" }, { - "id": "tencent/hy-mt2-1.8b", - "name": "Hy-MT2-1.8B", - "display_name": "Hy-MT2-1.8B", + "id": "openai/gpt-5.6-luna", + "name": "GPT-5.6 Luna", + "display_name": "GPT-5.6 Luna", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 8192, - "output": 4096 + "context": 1050000, + "output": 128000 }, - "temperature": true, - "tool_call": false, + "temperature": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, - "open_weights": true, - "release_date": "2026-05-21", - "last_updated": "2026-05-21", + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", + "effort_options": [ + "none", + "low", + "medium", + "high", + "xhigh", + "max" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" + } + }, + "attachment": true, + "open_weights": false, + "knowledge": "2026-02-16", + "release_date": "2026-07-09", + "last_updated": "2026-07-09", "type": "chat" }, { - "id": "amazon/nova-micro-v1", - "name": "Amazon: Nova Micro 1.0", - "display_name": "Amazon: Nova Micro 1.0", + "id": "openai/gpt-5", + "name": "GPT-5", + "display_name": "GPT-5", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 5120 + "context": 400000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, - "open_weights": false, - "knowledge": "2024-10-31", - "release_date": "2024-12-05", - "last_updated": "2024-12-05", - "type": "chat" - }, - { - "id": "amazon/nova-pro-v1", - "name": "Amazon: Nova Pro 1.0", - "display_name": "Amazon: Nova Pro 1.0", - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 300000, - "output": 5120 - }, - "temperature": true, - "tool_call": true, - "reasoning": { - "supported": false + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", + "effort_options": [ + "minimal", + "low", + "medium", + "high" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" + } }, "attachment": true, "open_weights": false, - "knowledge": "2024-10-31", - "release_date": "2024-12-05", - "last_updated": "2024-12-05", + "knowledge": "2024-09-30", + "release_date": "2025-08-07", + "last_updated": "2025-08-07", "type": "chat" }, { - "id": "amazon/nova-2-lite-v1", - "name": "Amazon: Nova 2 Lite", - "display_name": "Amazon: Nova 2 Lite", + "id": "openai/gpt-5-mini", + "name": "GPT-5 Mini", + "display_name": "GPT-5 Mini", "modalities": { "input": [ "text", - "image", - "video", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 65535 + "context": 256000, + "output": 32768 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -94036,35 +94260,49 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", "effort_options": [ - "none", + "minimal", + "low", + "medium", "high" - ] + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, "attachment": true, "open_weights": false, - "knowledge": "2025-10", - "release_date": "2025-12-02", - "last_updated": "2025-12-02", + "knowledge": "2024-05-30", + "release_date": "2025-08-07", + "last_updated": "2025-08-07", "type": "chat" }, { - "id": "amazon/nova-lite-v1", - "name": "Amazon: Nova Lite 1.0", - "display_name": "Amazon: Nova Lite 1.0", + "id": "openai/gpt-4o-mini", + "name": "GPT-4o mini", + "display_name": "GPT-4o mini", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 300000, - "output": 5120 + "context": 128000, + "output": 16384 }, "temperature": true, "tool_call": true, @@ -94073,27 +94311,28 @@ }, "attachment": true, "open_weights": false, - "knowledge": "2024-10-31", - "release_date": "2024-12-05", - "last_updated": "2024-12-05", + "knowledge": "2023-09", + "release_date": "2024-07-18", + "last_updated": "2024-07-18", "type": "chat" }, { - "id": "amazon/nova-premier-v1", - "name": "Amazon: Nova Premier 1.0", - "display_name": "Amazon: Nova Premier 1.0", + "id": "openai/gpt-4o", + "name": "GPT-4o", + "display_name": "GPT-4o", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 32000 + "context": 128000, + "output": 16384 }, "temperature": true, "tool_call": true, @@ -94102,28 +94341,30 @@ }, "attachment": true, "open_weights": false, - "knowledge": "2024-10", - "release_date": "2025-04-30", - "last_updated": "2025-04-30", + "knowledge": "2023-09", + "release_date": "2024-05-13", + "last_updated": "2024-08-06", "type": "chat" }, { - "id": "reflection/beam-free", - "name": "Reflection: Beam 1.0 (free)", - "display_name": "Reflection: Beam 1.0 (free)", + "id": "openai/gpt-5.6-sol", + "name": "GPT-5.6 Sol", + "display_name": "GPT-5.6 Sol", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 1050000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -94131,78 +94372,104 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", "effort_options": [ + "none", "low", "medium", "high", "xhigh", "max" - ] + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, - "attachment": false, + "attachment": true, "open_weights": false, - "release_date": "2025-08-26", - "last_updated": "2025-08-26", + "knowledge": "2026-02-16", + "release_date": "2026-07-09", + "last_updated": "2026-07-09", "type": "chat" }, { - "id": "reflection/beam-1.0-501b-a23b", - "name": "Reflection: Beam 1.0 501B A23B", - "display_name": "Reflection: Beam 1.0 501B A23B", + "id": "openai/gpt-5.1", + "name": "GPT-5.1", + "display_name": "GPT-5.1", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 400000, + "output": 128000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": false, + "mode": "effort", + "effort": "none", "effort_options": [ + "none", "low", "medium", - "high", - "xhigh", - "max" - ] + "high" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, - "attachment": false, - "open_weights": true, - "knowledge": "2026-06-30", - "release_date": "2026-10-05", - "last_updated": "2026-10-16", + "attachment": true, + "open_weights": false, + "knowledge": "2024-09-30", + "release_date": "2025-11-13", + "last_updated": "2025-11-13", "type": "chat" }, { - "id": "liquid/lfm-2.5-24b-a2b", - "name": "LiquidAI: LFM2.5-24B-A2B", - "display_name": "LiquidAI: LFM2.5-24B-A2B", + "id": "openai/gpt-5.5", + "name": "GPT-5.5", + "display_name": "GPT-5.5", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 8192 + "context": 1050000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -94210,92 +94477,117 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", "effort_options": [ + "low", + "medium", + "high", + "xhigh" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", "high" - ] + ], + "visibility": "hidden" } }, - "attachment": false, + "attachment": true, "open_weights": false, - "release_date": "2026-10-13", - "last_updated": "2026-10-13", + "knowledge": "2025-12-01", + "release_date": "2026-04-23", + "last_updated": "2026-04-23", "type": "chat" }, { - "id": "liquid/lfm-2.5-2.6b", - "name": "LiquidAI: LFM2.5-2.6B", - "display_name": "LiquidAI: LFM2.5-2.6B", + "id": "openai/gpt-5.1-codex-mini", + "name": "GPT-5.1 Codex mini", + "display_name": "GPT-5.1 Codex mini", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 8192 + "context": 256000, + "output": 65536 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": false, + "mode": "effort", + "effort": "none", "effort_options": [ + "none", + "low", + "medium", "high" - ] + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, - "attachment": false, + "attachment": true, "open_weights": false, - "release_date": "2026-08-11", - "last_updated": "2026-08-11", + "knowledge": "2024-09-30", + "release_date": "2025-11-13", + "last_updated": "2025-11-13", "type": "chat" }, { - "id": "liquid/lfm-2.5-2.6b:free", - "name": "LiquidAI: LFM2.5-2.6B (free)", - "display_name": "LiquidAI: LFM2.5-2.6B (free)", + "id": "openai/gpt-4.1-mini", + "name": "GPT-4.1 mini", + "display_name": "GPT-4.1 mini", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 65536, - "output": 8192 + "context": 1047576, + "output": 32768 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "high" - ] - } + "supported": false }, - "attachment": false, + "attachment": true, "open_weights": false, - "release_date": "2026-08-11", - "last_updated": "2026-08-11", + "knowledge": "2024-04", + "release_date": "2025-04-14", + "last_updated": "2025-04-14", "type": "chat" }, { - "id": "scaledown/extract", - "name": "ScaleDown: Extract", - "display_name": "ScaleDown: Extract", + "id": "openai/gpt-4.1", + "name": "GPT-4.1", + "display_name": "GPT-4.1", "modalities": { "input": [ "text", @@ -94307,24 +94599,25 @@ ] }, "limit": { - "context": 1000000, - "output": 900000 + "context": 1047576, + "output": 32768 }, - "temperature": false, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { "supported": false }, "attachment": true, "open_weights": false, - "release_date": "2026-10-13", - "last_updated": "2026-10-13", + "knowledge": "2024-04", + "release_date": "2025-04-14", + "last_updated": "2025-04-14", "type": "chat" }, { - "id": "scaledown/summarize", - "name": "ScaleDown: Summarize", - "display_name": "ScaleDown: Summarize", + "id": "openai/gpt-6-luna", + "name": "GPT-6 Luna", + "display_name": "GPT-6 Luna", "modalities": { "input": [ "text", @@ -94336,101 +94629,130 @@ ] }, "limit": { - "context": 1000000, - "output": 4000 + "context": 1050000, + "output": 128000 }, "temperature": false, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "none", + "low", + "medium", + "high", + "xhigh", + "max" + ] + } }, "attachment": true, "open_weights": false, - "release_date": "2026-10-13", - "last_updated": "2026-10-13", + "knowledge": "2026-05-18", + "release_date": "2026-09-22", + "last_updated": "2026-09-22", "type": "chat" }, { - "id": "rekaai/reka-edge", - "name": "Reka Edge", - "display_name": "Reka Edge", + "id": "z-ai/glm-5.3-flashx", + "name": "GLM-5.3-FlashX", + "display_name": "GLM-5.3-FlashX", "modalities": { "input": [ - "image", "text", - "video" + "image", + "video", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 16384, - "output": 14745 + "context": 1000000, + "output": 131072 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "low", + "high", + "max" + ] + } }, "attachment": true, "open_weights": true, - "release_date": "2026-03-11", - "last_updated": "2026-03-11", + "release_date": "2026-08-26", + "last_updated": "2026-08-26", "type": "chat" }, { - "id": "rekaai/reka-flash-3", - "name": "Reka Flash 3", - "display_name": "Reka Flash 3", + "id": "z-ai/glm-5.3-flash", + "name": "GLM-5.3-Flash", + "display_name": "GLM-5.3-Flash", "modalities": { "input": [ - "text" + "text", + "image", + "video", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 65536, - "output": 58982 + "context": 1000000, + "output": 131072 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { "supported": true, "default": true }, "extra_capabilities": { "reasoning": { + "supported": true, "effort_options": [ - "high" + "low", + "high", + "max" ] } }, - "attachment": false, + "attachment": true, "open_weights": true, - "knowledge": "2025-01-31", - "release_date": "2025-03-10", - "last_updated": "2025-03-10", + "release_date": "2026-08-26", + "last_updated": "2026-08-26", "type": "chat" }, { - "id": "kilo-auto/small", - "name": "Auto Small", - "display_name": "Auto Small", + "id": "z-ai/glm-4.7", + "name": "GLM-4.7", + "display_name": "GLM-4.7", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 32768 + "context": 204800, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -94438,16 +94760,28 @@ "supported": true, "default": true }, - "attachment": true, - "open_weights": false, - "release_date": "1970-01-01", - "last_updated": "1970-01-01", + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } + }, + "attachment": false, + "open_weights": true, + "knowledge": "2025-04", + "release_date": "2025-12-22", + "last_updated": "2025-12-22", "type": "chat" }, { - "id": "kilo-auto/free", - "name": "Auto Free", - "display_name": "Auto Free", + "id": "z-ai/glm-4.6", + "name": "GLM-4.6", + "display_name": "GLM-4.6", "modalities": { "input": [ "text" @@ -94457,8 +94791,8 @@ ] }, "limit": { - "context": 256000, - "output": 32768 + "context": 204800, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -94466,20 +94800,25 @@ "supported": true, "default": true }, + "extra_capabilities": { + "reasoning": { + "supported": true + } + }, "attachment": false, - "open_weights": false, - "release_date": "1970-01-01", - "last_updated": "1970-01-01", + "open_weights": true, + "knowledge": "2025-04", + "release_date": "2025-09-30", + "last_updated": "2025-09-30", "type": "chat" }, { - "id": "kilo-auto/efficient", - "name": "Auto Efficient", - "display_name": "Auto Efficient", + "id": "z-ai/glm-5.3", + "name": "GLM-5.3", + "display_name": "GLM-5.3", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" @@ -94487,7 +94826,7 @@ }, "limit": { "context": 1000000, - "output": 65536 + "output": 131072 }, "temperature": true, "tool_call": true, @@ -94495,29 +94834,37 @@ "supported": true, "default": true }, - "attachment": true, - "open_weights": false, - "release_date": "1970-01-01", - "last_updated": "1970-01-01", + "extra_capabilities": { + "reasoning": { + "supported": true, + "effort_options": [ + "low", + "high", + "max" + ] + } + }, + "attachment": false, + "open_weights": true, + "release_date": "2026-08-14", + "last_updated": "2026-08-14", "type": "chat" }, { - "id": "kilo-auto/frontier", - "name": "Auto Frontier", - "display_name": "Auto Frontier", + "id": "z-ai/glm-5", + "name": "GLM-5", + "display_name": "GLM-5", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 204800, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -94525,28 +94872,38 @@ "supported": true, "default": true }, - "attachment": true, - "open_weights": false, - "release_date": "1970-01-01", - "last_updated": "1970-01-01", + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } + }, + "attachment": false, + "open_weights": true, + "release_date": "2026-02-12", + "last_updated": "2026-02-12", "type": "chat" }, { - "id": "kilo-auto/balanced", - "name": "Auto Balanced", - "display_name": "Auto Balanced", + "id": "z-ai/glm-4.7-flashx", + "name": "GLM-4.7-FlashX", + "display_name": "GLM-4.7-FlashX", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 65536 + "context": 200000, + "output": 128000 }, "temperature": true, "tool_call": true, @@ -94554,16 +94911,17 @@ "supported": true, "default": true }, - "attachment": true, - "open_weights": false, - "release_date": "1970-01-01", - "last_updated": "1970-01-01", + "attachment": false, + "open_weights": true, + "knowledge": "2025-04", + "release_date": "2026-01-19", + "last_updated": "2026-01-19", "type": "chat" }, { - "id": "mancer/weaver", - "name": "Mancer: Weaver (alpha)", - "display_name": "Mancer: Weaver (alpha)", + "id": "z-ai/glm-5.1", + "name": "GLM-5.1", + "display_name": "GLM-5.1", "modalities": { "input": [ "text" @@ -94573,25 +94931,61 @@ ] }, "limit": { - "context": 8000, - "output": 6000 + "context": 200000, + "output": 131072 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true + } }, "attachment": false, + "open_weights": true, + "release_date": "2026-04-07", + "last_updated": "2026-04-07", + "type": "chat" + }, + { + "id": "z-ai/glm-5v-turbo", + "name": "GLM-5V-Turbo", + "display_name": "GLM-5V-Turbo", + "modalities": { + "input": [ + "text", + "image", + "video", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 200000, + "output": 131072 + }, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": true, + "default": true + }, + "attachment": true, "open_weights": false, - "knowledge": "2023-06-30", - "release_date": "2023-08-02", - "last_updated": "2023-08-02", + "release_date": "2026-04-01", + "last_updated": "2026-04-01", "type": "chat" }, { - "id": "recursive/flint-v1.0:free", - "name": "Recursive: Flint (free)", - "display_name": "Recursive: Flint (free)", + "id": "z-ai/glm-5.2", + "name": "GLM-5.2", + "display_name": "GLM-5.2", "modalities": { "input": [ "text" @@ -94601,8 +94995,8 @@ ] }, "limit": { - "context": 262144, - "output": 235929 + "context": 1000000, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -94612,21 +95006,23 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, "effort_options": [ - "high" + "high", + "max" ] } }, "attachment": false, - "open_weights": false, - "release_date": "2026-10-21", - "last_updated": "2026-10-21", + "open_weights": true, + "release_date": "2026-06-13", + "last_updated": "2026-06-13", "type": "chat" }, { - "id": "aion-labs/aion-rp-llama-3.1-8b", - "name": "AionLabs: Aion-RP 1.0 (8B)", - "display_name": "AionLabs: Aion-RP 1.0 (8B)", + "id": "z-ai/glm-5-turbo", + "name": "GLM-5-Turbo", + "display_name": "GLM-5-Turbo", "modalities": { "input": [ "text" @@ -94636,25 +95032,30 @@ ] }, "limit": { - "context": 32768, - "output": 29491 + "context": 200000, + "output": 131072 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true + } }, "attachment": false, - "open_weights": true, - "knowledge": "2023-12-31", - "release_date": "2025-02-04", - "last_updated": "2025-02-04", + "open_weights": false, + "release_date": "2026-03-16", + "last_updated": "2026-03-16", "type": "chat" }, { - "id": "aion-labs/aion-3.5-mini", - "name": "AionLabs: Aion 3.5 Mini", - "display_name": "AionLabs: Aion 3.5 Mini", + "id": "minimax/minimax-m2.5", + "name": "MiniMax-M2.5", + "display_name": "MiniMax-M2.5", "modalities": { "input": [ "text" @@ -94664,8 +95065,8 @@ ] }, "limit": { - "context": 262144, - "output": 32768 + "context": 204800, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -94675,23 +95076,19 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "low", - "high", - "max" - ] + "supported": true } }, "attachment": false, - "open_weights": false, - "release_date": "2026-09-23", - "last_updated": "2026-09-23", + "open_weights": true, + "release_date": "2026-02-12", + "last_updated": "2026-02-12", "type": "chat" }, { - "id": "aion-labs/aion-3.0-mini", - "name": "AionLabs: Aion-3.0-Mini", - "display_name": "AionLabs: Aion-3.0-Mini", + "id": "minimax/m2-her", + "name": "MiniMax-M2 Her", + "display_name": "MiniMax-M2 Her", "modalities": { "input": [ "text" @@ -94701,8 +95098,8 @@ ] }, "limit": { - "context": 131072, - "output": 32768 + "context": 65536, + "output": 2048 }, "temperature": true, "tool_call": true, @@ -94710,23 +95107,16 @@ "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "high" - ] - } - }, "attachment": false, "open_weights": false, - "release_date": "2026-05-14", - "last_updated": "2026-05-14", + "release_date": "2026-01-23", + "last_updated": "2026-01-23", "type": "chat" }, { - "id": "aion-labs/aion-3.0", - "name": "AionLabs: Aion-3.0", - "display_name": "AionLabs: Aion-3.0", + "id": "minimax/minimax-m2.5-lightning", + "name": "MiniMax-M2.5 Lightning", + "display_name": "MiniMax-M2.5 Lightning", "modalities": { "input": [ "text" @@ -94736,8 +95126,8 @@ ] }, "limit": { - "context": 131072, - "output": 32768 + "context": 204800, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -94745,23 +95135,16 @@ "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "high" - ] - } - }, "attachment": false, - "open_weights": false, - "release_date": "2026-05-05", - "last_updated": "2026-05-05", + "open_weights": true, + "release_date": "2026-02-13", + "last_updated": "2026-02-13", "type": "chat" }, { - "id": "aion-labs/aion-3.5", - "name": "AionLabs: Aion 3.5", - "display_name": "AionLabs: Aion 3.5", + "id": "minimax/minimax-m2.1-lightning", + "name": "MiniMax-M2.1 Lightning", + "display_name": "MiniMax-M2.1 Lightning", "modalities": { "input": [ "text" @@ -94771,8 +95154,8 @@ ] }, "limit": { - "context": 262144, - "output": 32768 + "context": 204800, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -94780,25 +95163,16 @@ "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "low", - "high", - "max" - ] - } - }, "attachment": false, - "open_weights": false, - "release_date": "2026-09-23", - "last_updated": "2026-09-23", + "open_weights": true, + "release_date": "2025-12-23", + "last_updated": "2025-12-23", "type": "chat" }, { - "id": "aion-labs/aion-2.0", - "name": "AionLabs: Aion-2.0", - "display_name": "AionLabs: Aion-2.0", + "id": "minimax/minimax-m2.7-highspeed", + "name": "MiniMax-M2.7-highspeed", + "display_name": "MiniMax-M2.7-highspeed", "modalities": { "input": [ "text" @@ -94808,8 +95182,8 @@ ] }, "limit": { - "context": 131072, - "output": 32768 + "context": 204800, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -94819,107 +95193,99 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "high" + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, "attachment": false, - "open_weights": false, - "release_date": "2025-12-21", - "last_updated": "2025-12-21", + "open_weights": true, + "release_date": "2026-03-18", + "last_updated": "2026-03-18", "type": "chat" }, { - "id": "moondream/moondream3.1-9b-a2b", - "name": "Moondream: Moondream 3.1", - "display_name": "Moondream: Moondream 3.1", + "id": "minimax/minimax-m2", + "name": "MiniMax-M2", + "display_name": "MiniMax-M2", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 32000, - "output": 4096 + "context": 204800, + "output": 131072 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "none", - "high" - ] - } - }, - "attachment": true, - "open_weights": false, - "release_date": "2026-10-15", - "last_updated": "2026-10-15", + "attachment": false, + "open_weights": true, + "release_date": "2025-10-27", + "last_updated": "2025-10-27", "type": "chat" }, { - "id": "moondream/moondream3-preview", - "name": "Moondream: Moondream 3 Preview", - "display_name": "Moondream: Moondream 3 Preview", + "id": "minimax/minimax-m3", + "name": "MiniMax-M3", + "display_name": "MiniMax-M3", "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 32000, - "output": 4096 + "context": 1048576, + "output": 512000 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { "supported": true, "default": true }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "none", - "high" - ] + "supported": true } }, "attachment": true, - "open_weights": false, - "release_date": "2026-10-15", - "last_updated": "2026-10-15", + "open_weights": true, + "release_date": "2026-06-01", + "last_updated": "2026-06-01", "type": "chat" }, { - "id": "prism-ml/ternary-bonsai-2-27b", - "name": "PrismML: Ternary Bonsai 2 27B", - "display_name": "PrismML: Ternary Bonsai 2 27B", + "id": "minimax/minimax-m2.1", + "name": "MiniMax-M2.1", + "display_name": "MiniMax-M2.1", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 32768 + "context": 204800, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -94929,35 +95295,30 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "none", - "medium", - "xhigh" - ] + "supported": true } }, - "attachment": true, - "open_weights": false, - "release_date": "2026-09-18", - "last_updated": "2026-09-18", + "attachment": false, + "open_weights": true, + "release_date": "2025-12-23", + "last_updated": "2025-12-23", "type": "chat" }, { - "id": "prism-ml/ternary-bonsai-2-9b", - "name": "PrismML: Ternary Bonsai 2 9B", - "display_name": "PrismML: Ternary Bonsai 2 9B", + "id": "minimax/minimax-m2.7", + "name": "MiniMax-M2.7", + "display_name": "MiniMax-M2.7", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 65536 + "context": 204800, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -94967,16 +95328,19 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "none", - "high" + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, - "attachment": true, - "open_weights": false, - "release_date": "2026-10-16", - "last_updated": "2026-10-16", + "attachment": false, + "open_weights": true, + "release_date": "2026-03-18", + "last_updated": "2026-03-18", "type": "chat" }, { @@ -94995,9 +95359,9 @@ }, "limit": { "context": 1048576, - "output": 943718 + "output": 131072 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -95005,13 +95369,7 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "effort_options": [ - "none", - "low", - "high", - "max" - ] + "supported": true } }, "attachment": true, @@ -95021,13 +95379,14 @@ "type": "chat" }, { - "id": "moonshotai/kimi-k2.6", - "name": "Kimi K2.6", - "display_name": "Kimi K2.6", + "id": "moonshotai/kimi-k2.7-code", + "name": "Kimi K2.7 Code", + "display_name": "Kimi K2.7 Code", "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ "text" @@ -95035,9 +95394,9 @@ }, "limit": { "context": 262144, - "output": 235929 + "output": 262144 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -95045,28 +95404,25 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "effort_options": [ - "none", - "high" - ] + "supported": true } }, "attachment": true, "open_weights": true, "knowledge": "2025-01", - "release_date": "2026-04-20", - "last_updated": "2026-04-20", + "release_date": "2026-06-12", + "last_updated": "2026-06-12", "type": "chat" }, { - "id": "moonshotai/kimi-k2.5", - "name": "Kimi K2.5", - "display_name": "Kimi K2.5", + "id": "moonshotai/kimi-k2.7-code-highspeed", + "name": "Kimi K2.7 Code Highspeed", + "display_name": "Kimi K2.7 Code Highspeed", "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ "text" @@ -95074,9 +95430,9 @@ }, "limit": { "context": 262144, - "output": 235929 + "output": 262144 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -95084,35 +95440,24 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "effort_options": [ - "none", - "high" - ] + "supported": true } }, "attachment": true, "open_weights": true, "knowledge": "2025-01", - "release_date": "2026-01-27", - "last_updated": "2026-01-27", + "release_date": "2026-06-12", + "last_updated": "2026-06-12", "type": "chat" }, { "id": "moonshotai/kimi-k3.1-pro", - "name": "MoonshotAI: Kimi K3.1 Pro", - "display_name": "MoonshotAI: Kimi K3.1 Pro", + "name": "Kimi K3.1 Pro", + "display_name": "Kimi K3.1 Pro", "modalities": { "input": [ "text", - "image", - "video" + "image" ], "output": [ "text" @@ -95120,7 +95465,7 @@ }, "limit": { "context": 1048576, - "output": 943718 + "output": 131072 }, "temperature": false, "tool_call": true, @@ -95131,7 +95476,6 @@ "extra_capabilities": { "reasoning": { "effort_options": [ - "none", "low", "high", "max" @@ -95145,12 +95489,14 @@ "type": "chat" }, { - "id": "moonshotai/kimi-k2-0905", - "name": "Kimi K2 Instruct 0905", - "display_name": "Kimi K2 Instruct 0905", + "id": "moonshotai/kimi-k2.6", + "name": "Kimi K2.6", + "display_name": "Kimi K2.6", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" @@ -95158,108 +95504,99 @@ }, "limit": { "context": 262144, - "output": 98304 + "output": 262144 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, + "extra_capabilities": { + "reasoning": { + "supported": true + } + }, + "attachment": true, "open_weights": true, - "release_date": "2025-09-05", - "last_updated": "2025-09-05", + "knowledge": "2025-01", + "release_date": "2026-04-20", + "last_updated": "2026-04-20", "type": "chat" }, { - "id": "moonshotai/kimi-k2", - "name": "Kimi K2 0711", - "display_name": "Kimi K2 0711", + "id": "google/gemini-3.5-flash", + "name": "Gemini 3.5 Flash", + "display_name": "Gemini 3.5 Flash", "modalities": { "input": [ - "text" + "text", + "image", + "video", + "audio", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 98304 + "context": 1048576, + "output": 65536 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false - }, - "attachment": false, - "open_weights": true, - "knowledge": "2024-12-31", - "release_date": "2025-07-11", - "last_updated": "2025-07-11", - "type": "chat" - }, - { - "id": "moonshotai/kimi-k2-thinking", - "name": "Kimi K2 Thinking", - "display_name": "Kimi K2 Thinking", - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 262144, - "output": 98304 - }, - "temperature": true, - "tool_call": true, - "reasoning": { - "supported": true, - "default": true + "supported": true, + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, + "default_enabled": true, + "mode": "level", + "level": "high", + "level_options": [ + "minimal", + "low", + "medium", + "high" + ], "summaries": true, "visibility": "summary", "continuation": [ - "thinking_blocks" - ], - "effort_options": [ - "high" + "thought_signatures" ] } }, - "attachment": false, - "open_weights": true, - "knowledge": "2024-08", - "release_date": "2025-11-06", - "last_updated": "2025-11-06", + "attachment": true, + "open_weights": false, + "knowledge": "2025-01", + "release_date": "2026-05-19", + "last_updated": "2026-05-19", "type": "chat" }, { - "id": "moonshotai/kimi-k2.7-code", - "name": "Kimi K2.7 Code", - "display_name": "Kimi K2.7 Code", + "id": "google/gemini-4-argon", + "name": "Gemini 4 Argon", + "display_name": "Gemini 4 Argon", "modalities": { "input": [ "text", - "image" + "image", + "video", + "audio", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 235929 + "context": 1048576, + "output": 65536 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -95267,91 +95604,39 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, "effort_options": [ + "low", + "medium", "high" ] } }, "attachment": true, - "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-06-12", - "last_updated": "2026-06-12", - "type": "chat" - }, - { - "id": "sao10k/l3-lunaris-8b", - "name": "Sao10K: Llama 3 8B Lunaris", - "display_name": "Sao10K: Llama 3 8B Lunaris", - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 8192, - "output": 7372 - }, - "temperature": true, - "tool_call": false, - "reasoning": { - "supported": false - }, - "attachment": false, - "open_weights": true, - "knowledge": "2023-12-31", - "release_date": "2024-08-13", - "last_updated": "2024-08-13", - "type": "chat" - }, - { - "id": "sao10k/l3.1-euryale-70b", - "name": "Sao10K: Llama 3.1 Euryale 70B v2.2", - "display_name": "Sao10K: Llama 3.1 Euryale 70B v2.2", - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 16384 - }, - "temperature": true, - "tool_call": false, - "reasoning": { - "supported": false - }, - "attachment": false, - "open_weights": true, - "knowledge": "2023-12-31", - "release_date": "2024-08-28", - "last_updated": "2024-08-28", + "open_weights": false, + "knowledge": "2026-03", + "release_date": "2026-10-13", + "last_updated": "2026-10-13", "type": "chat" }, { - "id": "qwen/qwen3-vl-30b-a3b-thinking", - "name": "Qwen: Qwen3 VL 30B A3B Thinking", - "display_name": "Qwen: Qwen3 VL 30B A3B Thinking", + "id": "google/gemini-3.6-flash", + "name": "Gemini 3.6 Flash", + "display_name": "Gemini 3.6 Flash", "modalities": { "input": [ "text", - "image" + "image", + "video", + "audio", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 235929 + "context": 1048576, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -95362,30 +95647,39 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, + "default_enabled": true, + "mode": "level", + "level": "high", + "level_options": [ + "minimal", + "low", + "medium", + "high" + ], "summaries": true, "visibility": "summary", "continuation": [ - "thinking_blocks" - ], - "effort_options": [ - "high" + "thought_signatures" ] } }, "attachment": true, "open_weights": false, - "release_date": "2025-10-06", - "last_updated": "2025-10-06", + "knowledge": "2026-03", + "release_date": "2026-07-21", + "last_updated": "2026-07-21", "type": "chat" }, { - "id": "qwen/qwen3.7-max", - "name": "Qwen3.7 Max", - "display_name": "Qwen3.7 Max", + "id": "google/gemini-3.8-flash", + "name": "Gemini 3.8 Flash", + "display_name": "Gemini 3.8 Flash", "modalities": { "input": [ - "text" + "text", + "image", + "audio", + "pdf" ], "output": [ "text" @@ -95393,7 +95687,7 @@ }, "limit": { "context": 1000000, - "output": 131072 + "output": 65536 }, "temperature": true, "tool_call": true, @@ -95404,35 +95698,47 @@ "extra_capabilities": { "reasoning": { "supported": true, - "effort_options": [ - "none", + "default_enabled": true, + "mode": "level", + "level": "high", + "level_options": [ + "minimal", + "low", + "medium", "high" + ], + "summaries": true, + "visibility": "summary", + "continuation": [ + "thought_signatures" ] } }, - "attachment": false, + "attachment": true, "open_weights": false, - "release_date": "2026-05-21", - "last_updated": "2026-05-21", + "release_date": "2026-09-02", + "last_updated": "2026-09-02", "type": "chat" }, { - "id": "qwen/qwen3.8-max-prime", - "name": "Qwen 3.8 Max Prime", - "display_name": "Qwen 3.8 Max Prime", + "id": "google/gemini-2.5-pro", + "name": "Gemini 2.5 Pro", + "display_name": "Gemini 2.5 Pro", "modalities": { "input": [ "text", "image", - "video" + "audio", + "video", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 1048576, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -95442,106 +95748,153 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "minimal", - "low", - "medium", - "high", - "xhigh" + "supported": true, + "default_enabled": true, + "mode": "budget", + "budget": { + "default": -1, + "min": 128, + "max": 32768, + "auto": -1, + "unit": "tokens" + }, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thought_signatures" ] } }, "attachment": true, "open_weights": false, - "release_date": "2026-09-23", - "last_updated": "2026-09-23", + "knowledge": "2025-01", + "release_date": "2025-06-17", + "last_updated": "2025-06-17", "type": "chat" }, { - "id": "qwen/qwen3-235b-a22b-thinking-2507", - "name": "Qwen: Qwen3 235B A22B Thinking 2507", - "display_name": "Qwen: Qwen3 235B A22B Thinking 2507", + "id": "google/gemini-2.5-flash-lite", + "name": "Gemini 2.5 Flash-Lite", + "display_name": "Gemini 2.5 Flash-Lite", "modalities": { "input": [ - "text" + "text", + "image", + "audio", + "video", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 16384 + "context": 1048576, + "output": 65536 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, + "default_enabled": false, + "mode": "budget", + "budget": { + "default": -1, + "min": 512, + "max": 24576, + "auto": -1, + "unit": "tokens" + }, "summaries": true, "visibility": "summary", "continuation": [ - "thinking_blocks" - ], - "effort_options": [ - "high" + "thought_signatures" ] } }, - "attachment": false, - "open_weights": true, - "release_date": "2025-07-25", - "last_updated": "2025-07-25", + "attachment": true, + "open_weights": false, + "knowledge": "2025-01", + "release_date": "2025-06-17", + "last_updated": "2025-06-17", "type": "chat" }, { - "id": "qwen/qwen3-30b-a3b-instruct-2507", - "name": "Qwen: Qwen3 30B A3B Instruct 2507", - "display_name": "Qwen: Qwen3 30B A3B Instruct 2507", + "id": "google/gemini-2.5-flash", + "name": "Gemini 2.5 Flash", + "display_name": "Gemini 2.5 Flash", "modalities": { "input": [ - "text" + "text", + "image", + "audio", + "video", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 235929 + "context": 1048576, + "output": 65536 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, - "open_weights": true, - "release_date": "2025-07-28", - "last_updated": "2025-07-28", + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "budget", + "budget": { + "default": -1, + "min": 0, + "max": 24576, + "auto": -1, + "off": 0, + "unit": "tokens" + }, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thought_signatures" + ] + } + }, + "attachment": true, + "open_weights": false, + "knowledge": "2025-01", + "release_date": "2025-06-17", + "last_updated": "2025-06-17", "type": "chat" }, { - "id": "qwen/qwen3.5-plus-20260420", - "name": "Qwen: Qwen3.5 Plus 2026-04-20", - "display_name": "Qwen: Qwen3.5 Plus 2026-04-20", + "id": "google/gemini-3.1-pro-preview", + "name": "Gemini 3.1 Pro Preview", + "display_name": "Gemini 3.1 Pro Preview", "modalities": { "input": [ "text", "image", - "video" + "video", + "audio", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1000000, + "context": 1048576, "output": 65536 }, "temperature": true, @@ -95553,97 +95906,142 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, + "default_enabled": true, + "mode": "level", + "level": "high", + "level_options": [ + "low", + "high" + ], "summaries": true, "visibility": "summary", "continuation": [ - "thinking_blocks" - ], - "effort_options": [ - "none", - "high" + "thought_signatures" ] } }, "attachment": true, "open_weights": false, - "release_date": "2026-04-27", - "last_updated": "2026-04-27", + "knowledge": "2025-01", + "release_date": "2026-02-19", + "last_updated": "2026-02-19", "type": "chat" }, { - "id": "qwen/qwen2.5-vl-72b-instruct", - "name": "Qwen: Qwen2.5 VL 72B Instruct", - "display_name": "Qwen: Qwen2.5 VL 72B Instruct", + "id": "google/gemini-3.7-flash", + "name": "Gemini 3.7 Flash", + "display_name": "Gemini 3.7 Flash", "modalities": { "input": [ "text", - "image" + "image", + "video", + "audio", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 115200 + "context": 1048576, + "output": 65536 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "level", + "level": "high", + "level_options": [ + "minimal", + "low", + "medium", + "high" + ], + "summaries": true, + "visibility": "summary", + "continuation": [ + "thought_signatures" + ] + } }, "attachment": true, - "open_weights": true, - "knowledge": "2024-04", - "release_date": "2025-01-26", - "last_updated": "2025-01-26", + "open_weights": false, + "knowledge": "2026-03", + "release_date": "2026-08-13", + "last_updated": "2026-08-13", "type": "chat" }, { - "id": "qwen/qwen-2.5-7b-instruct", - "name": "Qwen: Qwen2.5 7B Instruct", - "display_name": "Qwen: Qwen2.5 7B Instruct", + "id": "google/gemini-3.1-flash-lite", + "name": "Gemini 3.1 Flash Lite", + "display_name": "Gemini 3.1 Flash Lite", "modalities": { "input": [ - "text" + "text", + "image", + "video", + "audio", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 32768, - "output": 29491 + "context": 1048576, + "output": 65536 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, + "extra_capabilities": { + "reasoning": { + "supported": true, + "effort_options": [ + "minimal", + "low", + "medium", + "high" + ] + } + }, + "attachment": true, "open_weights": false, - "release_date": "2024-10-16", - "last_updated": "2024-10-16", + "knowledge": "2025-01", + "release_date": "2026-05-07", + "last_updated": "2026-05-07", "type": "chat" }, { - "id": "qwen/qwen3.5-9b", - "name": "Qwen3.5 9B", - "display_name": "Qwen3.5 9B", + "id": "google/gemini-3-flash-preview", + "name": "Gemini 3 Flash Preview", + "display_name": "Gemini 3 Flash Preview", "modalities": { "input": [ "text", "image", - "video" + "video", + "audio", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 235929 + "context": 1048576, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -95654,41 +96052,48 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, + "default_enabled": true, + "mode": "level", + "level": "high", + "level_options": [ + "minimal", + "low", + "medium", + "high" + ], "summaries": true, "visibility": "summary", "continuation": [ - "thinking_blocks" - ], - "effort_options": [ - "none", - "high" + "thought_signatures" ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-02-23", - "last_updated": "2026-02-23", + "open_weights": false, + "knowledge": "2025-01", + "release_date": "2025-12-17", + "last_updated": "2025-12-17", "type": "chat" }, { - "id": "qwen/qwen3.8-max-0902", - "name": "Qwen3.8 Max 0902", - "display_name": "Qwen3.8 Max 0902", + "id": "google/gemini-3.5-flash-lite", + "name": "Gemini 3.5 Flash Lite", + "display_name": "Gemini 3.5 Flash Lite", "modalities": { "input": [ "text", "image", - "video" + "video", + "audio", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 1048576, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -95698,53 +96103,56 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, "effort_options": [ "minimal", "low", "medium", - "high", - "xhigh" + "high" ] } }, "attachment": true, "open_weights": false, - "release_date": "2026-09-02", - "last_updated": "2026-09-02", + "knowledge": "2026-03", + "release_date": "2026-07-21", + "last_updated": "2026-07-21", "type": "chat" }, { - "id": "qwen/qwen3-coder-30b-a3b-instruct", - "name": "Qwen3-Coder 30B-A3B Instruct", - "display_name": "Qwen3-Coder 30B-A3B Instruct", + "id": "volcengine/doubao-seed-character", + "name": "Seed Character", + "display_name": "Seed Character", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 235929 + "context": 256000, + "output": 256000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-07-31", - "last_updated": "2025-07-31", + "attachment": true, + "open_weights": false, + "release_date": "2026-06-23", + "last_updated": "2026-06-23", "type": "chat" }, { - "id": "qwen/qwen3.8-27b", - "name": "Qwen3.8 27B", - "display_name": "Qwen3.8 27B", + "id": "volcengine/doubao-seed-2.0-lite", + "name": "Seed 2.0 Lite", + "display_name": "Seed 2.0 Lite", "modalities": { "input": [ "text", @@ -95756,8 +96164,8 @@ ] }, "limit": { - "context": 262144, - "output": 235929 + "context": 256000, + "output": 32000 }, "temperature": true, "tool_call": true, @@ -95765,83 +96173,76 @@ "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "none", - "low", - "medium", - "xhigh" - ] - } - }, "attachment": true, - "open_weights": true, - "release_date": "2026-08-14", - "last_updated": "2026-08-14", + "open_weights": false, + "release_date": "2026-02-14", + "last_updated": "2026-02-14", "type": "chat" }, { - "id": "qwen/qwen3-vl-30b-a3b-instruct", - "name": "Qwen3 VL 30B A3B Instruct", - "display_name": "Qwen3 VL 30B A3B Instruct", + "id": "volcengine/doubao-seed-2.0-mini", + "name": "Seed 2.0 Mini", + "display_name": "Seed 2.0 Mini", "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 16384 + "context": 256000, + "output": 32000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, "attachment": true, - "open_weights": true, - "release_date": "2025-10-04", - "last_updated": "2025-10-04", + "open_weights": false, + "release_date": "2026-02-14", + "last_updated": "2026-02-14", "type": "chat" }, { - "id": "qwen/qwen3-vl-235b-a22b-instruct", - "name": "Qwen3 VL 235B A22B Instruct", - "display_name": "Qwen3 VL 235B A22B Instruct", + "id": "volcengine/doubao-seed-2.1-pro", + "name": "Seed 2.1 Pro", + "display_name": "Seed 2.1 Pro", "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 32768 + "context": 256000, + "output": 256000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, "attachment": true, - "open_weights": true, - "knowledge": "2025-03-31", - "release_date": "2025-09-23", - "last_updated": "2025-09-23", + "open_weights": false, + "release_date": "2026-06-23", + "last_updated": "2026-06-23", "type": "chat" }, { - "id": "qwen/qwen3.6-flash", - "name": "Qwen3.6 Flash", - "display_name": "Qwen3.6 Flash", + "id": "volcengine/doubao-seed-2.0-pro", + "name": "Seed 2.0 Pro", + "display_name": "Seed 2.0 Pro", "modalities": { "input": [ "text", @@ -95853,45 +96254,29 @@ ] }, "limit": { - "context": 1000000, - "output": 65536 + "context": 256000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "effort_options": [ - "none", - "high" - ] - } - }, "attachment": true, "open_weights": false, - "release_date": "2026-04-27", - "last_updated": "2026-04-27", + "release_date": "2026-02-14", + "last_updated": "2026-02-14", "type": "chat" }, { - "id": "qwen/qwen3.8-omni-flash", - "name": "Qwen3.8 Omni Flash", - "display_name": "Qwen3.8 Omni Flash", + "id": "volcengine/doubao-seed-2.1-turbo", + "name": "Seed 2.1 Turbo", + "display_name": "Seed 2.1 Turbo", "modalities": { "input": [ "text", "image", - "audio", "video" ], "output": [ @@ -95899,8 +96284,8 @@ ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 256000, + "output": 256000 }, "temperature": true, "tool_call": true, @@ -95908,35 +96293,29 @@ "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "none", - "high" - ] - } - }, "attachment": true, "open_weights": false, - "release_date": "2026-09-17", - "last_updated": "2026-09-17", + "release_date": "2026-06-23", + "last_updated": "2026-06-23", "type": "chat" }, { - "id": "qwen/qwen3.8-2.4t-a95b", - "name": "Qwen3.8 2.4T A95B", - "display_name": "Qwen3.8 2.4T A95B", + "id": "volcengine/doubao-seed-evolving", + "name": "Seed Evolving", + "display_name": "Seed Evolving", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 256000, + "output": 256000 }, "temperature": true, "tool_call": true, @@ -95944,159 +96323,103 @@ "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "effort_options": [ - "low", - "medium", - "xhigh" - ] - } - }, - "attachment": false, - "open_weights": true, - "release_date": "2026-08-12", - "last_updated": "2026-08-12", + "attachment": true, + "open_weights": false, + "release_date": "2026-06-23", + "last_updated": "2026-06-23", "type": "chat" }, { - "id": "qwen/qwen3-coder-next", - "name": "Qwen3 Coder Next", - "display_name": "Qwen3 Coder Next", + "id": "volcengine/doubao-seed-2.0-code", + "name": "Seed 2.0 Code", + "display_name": "Seed 2.0 Code", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 235929 + "context": 256000, + "output": 128000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-09", - "release_date": "2026-02-03", - "last_updated": "2026-02-03", + "attachment": true, + "open_weights": false, + "release_date": "2026-02-14", + "last_updated": "2026-02-14", "type": "chat" }, { - "id": "qwen/qwen3.5-35b-a3b", - "name": "Qwen3.5 35B-A3B", - "display_name": "Qwen3.5 35B-A3B", + "id": "anthropic/claude-haiku-4.5", + "name": "Claude Haiku 4.5 (latest)", + "display_name": "Claude Haiku 4.5 (latest)", "modalities": { "input": [ "text", "image", - "video" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 81920 + "context": 200000, + "output": 64000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": false, + "mode": "budget", + "budget": { + "min": 1024, + "unit": "tokens" + }, "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ "thinking_blocks" ], - "effort_options": [ - "none", - "high" + "notes": [ + "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", + "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-02-23", - "last_updated": "2026-02-23", - "type": "chat" - }, - { - "id": "qwen/qwen-2.5-72b-instruct", - "name": "Qwen2.5 72B Instruct", - "display_name": "Qwen2.5 72B Instruct", - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 32768, - "output": 16384 - }, - "temperature": true, - "tool_call": true, - "reasoning": { - "supported": false - }, - "attachment": false, "open_weights": false, - "release_date": "2024-09-19", - "last_updated": "2024-09-19", - "type": "chat" - }, - { - "id": "qwen/qwen3-next-80b-a3b-instruct", - "name": "Qwen3-Next 80B-A3B Instruct", - "display_name": "Qwen3-Next 80B-A3B Instruct", - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 262144, - "output": 16384 - }, - "temperature": true, - "tool_call": true, - "reasoning": { - "supported": false - }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-09", - "last_updated": "2025-09", + "knowledge": "2025-02-28", + "release_date": "2025-10-15", + "last_updated": "2025-10-15", "type": "chat" }, { - "id": "qwen/qwen3.5-plus-02-15", - "name": "Qwen: Qwen3.5 Plus 2026-02-15", - "display_name": "Qwen: Qwen3.5 Plus 2026-02-15", + "id": "anthropic/claude-fable-5.1", + "name": "Claude Fable 5.1", + "display_name": "Claude Fable 5.1", "modalities": { "input": [ "text", "image", - "video" + "pdf" ], "output": [ "text" @@ -96104,9 +96427,9 @@ }, "limit": { "context": 1000000, - "output": 65536 + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -96115,80 +96438,107 @@ "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "high", + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" + ], "interleaved": true, "summaries": true, - "visibility": "summary", + "visibility": "omitted", "continuation": [ "thinking_blocks" ], - "effort_options": [ - "none", - "high" + "notes": [ + "Adaptive thinking is always on for Claude Fable 5 and Claude Mythos 5; thinking.type = \"disabled\" is rejected.", + "Manual budget_tokens requests return 400 on Claude Fable 5 and Claude Mythos 5.", + "thinking.display defaults to omitted; set display to summarized to receive readable thinking summaries." ] } }, "attachment": true, "open_weights": false, - "release_date": "2026-02-16", - "last_updated": "2026-02-16", + "knowledge": "2026-06", + "release_date": "2026-09-01", + "last_updated": "2026-09-01", "type": "chat" }, { - "id": "qwen/qwen-plus", - "name": "Qwen Plus", - "display_name": "Qwen Plus", + "id": "anthropic/claude-sonnet-4.5", + "name": "Claude Sonnet 4.5 (latest)", + "display_name": "Claude Sonnet 4.5 (latest)", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 32768 + "context": 200000, + "output": 64000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true + "supported": true, + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": false, + "mode": "budget", + "budget": { + "min": 1024, + "unit": "tokens" + }, "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ "thinking_blocks" + ], + "notes": [ + "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", + "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." ] } }, - "attachment": false, + "attachment": true, "open_weights": false, - "knowledge": "2024-04", - "release_date": "2024-01-25", - "last_updated": "2025-09-11", + "knowledge": "2025-07-31", + "release_date": "2025-09-29", + "last_updated": "2025-09-29", "type": "chat" }, { - "id": "qwen/qwen3-14b", - "name": "Qwen: Qwen3 14B", - "display_name": "Qwen: Qwen3 14B", + "id": "anthropic/claude-sonnet-5", + "name": "Claude Sonnet 5", + "display_name": "Claude Sonnet 5", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 40960, - "output": 16384 + "context": 1000000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -96197,43 +96547,41 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], "effort_options": [ - "none", - "high" + "low", + "medium", + "high", + "xhigh", + "max" ] } }, - "attachment": false, + "attachment": true, "open_weights": false, - "release_date": "2025-04-28", - "last_updated": "2025-04-28", + "knowledge": "2026-01-31", + "release_date": "2026-06-30", + "last_updated": "2026-06-30", "type": "chat" }, { - "id": "qwen/qwen3.5-122b-a10b", - "name": "Qwen3.5 122B-A10B", - "display_name": "Qwen3.5 122B-A10B", + "id": "anthropic/claude-fable-5", + "name": "Claude Fable 5", + "display_name": "Claude Fable 5", "modalities": { "input": [ "text", "image", - "video" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 65536 + "context": 1000000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -96242,60 +96590,45 @@ "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "high", + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" + ], "interleaved": true, "summaries": true, - "visibility": "summary", + "visibility": "omitted", "continuation": [ "thinking_blocks" ], - "effort_options": [ - "none", - "high" + "notes": [ + "Adaptive thinking is always on for Claude Fable 5 and Claude Mythos 5; thinking.type = \"disabled\" is rejected.", + "Manual budget_tokens requests return 400 on Claude Fable 5 and Claude Mythos 5.", + "thinking.display defaults to omitted; set display to summarized to receive readable thinking summaries." ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-02-23", - "last_updated": "2026-02-23", - "type": "chat" - }, - { - "id": "qwen/qwen-2.5-coder-32b-instruct", - "name": "Qwen2.5 Coder 32B Instruct", - "display_name": "Qwen2.5 Coder 32B Instruct", - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 32768, - "output": 29491 - }, - "temperature": true, - "tool_call": false, - "reasoning": { - "supported": false - }, - "attachment": false, - "open_weights": true, - "release_date": "2024-11-12", - "last_updated": "2024-11-12", + "open_weights": false, + "knowledge": "2026-01-31", + "release_date": "2026-06-09", + "last_updated": "2026-06-09", "type": "chat" }, { - "id": "qwen/qwen3.5-flash-02-23", - "name": "Qwen: Qwen3.5-Flash", - "display_name": "Qwen: Qwen3.5-Flash", + "id": "anthropic/claude-haiku-5.5", + "name": "Claude Haiku 5.5", + "display_name": "Claude Haiku 5.5", "modalities": { "input": [ "text", "image", - "video" + "pdf" ], "output": [ "text" @@ -96303,9 +96636,9 @@ }, "limit": { "context": 1000000, - "output": 65536 + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -96313,240 +96646,304 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], "effort_options": [ - "none", - "high" + "low", + "medium", + "high", + "xhigh", + "max" ] } }, "attachment": true, "open_weights": false, - "release_date": "2026-02-25", - "last_updated": "2026-02-25", + "knowledge": "2026-06", + "release_date": "2026-10-07", + "last_updated": "2026-10-07", "type": "chat" }, { - "id": "qwen/qwen3.6-plus", - "name": "Qwen3.6 Plus", - "display_name": "Qwen3.6 Plus", + "id": "anthropic/claude-opus-4.5", + "name": "Claude Opus 4.5 (latest)", + "display_name": "Claude Opus 4.5 (latest)", "modalities": { "input": [ "text", "image", - "video" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 65536 + "context": 200000, + "output": 64000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "effort_options": [ - "none", + "default_enabled": false, + "mode": "mixed", + "budget": { + "min": 1024, + "unit": "tokens" + }, + "effort": "high", + "effort_options": [ + "low", + "medium", "high" + ], + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Claude Opus 4.5 uses manual thinking.type = \"enabled\" with budget_tokens; effort can be used alongside the thinking budget.", + "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header." ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-04", - "release_date": "2026-04-02", - "last_updated": "2026-04-02", + "knowledge": "2025-05", + "release_date": "2025-11-24", + "last_updated": "2025-11-24", "type": "chat" }, { - "id": "qwen/qwen3-32b", - "name": "Qwen3 32B", - "display_name": "Qwen3 32B", + "id": "anthropic/claude-opus-4.6", + "name": "Claude Opus 4.6", + "display_name": "Claude Opus 4.6", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 40960, - "output": 16384 + "context": 1000000, + "output": 128000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": false, + "mode": "mixed", + "budget": { + "min": 1024, + "unit": "tokens" + }, + "effort": "high", + "effort_options": [ + "low", + "medium", + "high", + "max" + ], "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ "thinking_blocks" ], - "effort_options": [ - "none", - "high" + "notes": [ + "Anthropic recommends adaptive thinking with effort for Claude 4.6; budget_tokens remains a deprecated compatibility path.", + "Anthropic API defaults effort to high; lower effort levels should be chosen per workload." ] } }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-04-28", - "last_updated": "2025-04-28", + "attachment": true, + "open_weights": false, + "knowledge": "2025-05-31", + "release_date": "2026-02-05", + "last_updated": "2026-03-13", "type": "chat" }, { - "id": "qwen/qwen3-coder", - "name": "Qwen: Qwen3 Coder 480B A35B", - "display_name": "Qwen: Qwen3 Coder 480B A35B", + "id": "anthropic/claude-sonnet-5.5", + "name": "Claude Sonnet 5.5", + "display_name": "Claude Sonnet 5.5", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 65536 + "context": 1000000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-07-22", - "last_updated": "2025-07-22", + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" + ] + } + }, + "attachment": true, + "open_weights": false, + "knowledge": "2026-06", + "release_date": "2026-09-28", + "last_updated": "2026-09-28", "type": "chat" }, { - "id": "qwen/qwen3.6-35b-a3b", - "name": "Qwen3.6 35B-A3B", - "display_name": "Qwen3.6 35B-A3B", + "id": "anthropic/claude-opus-4.7", + "name": "Claude Opus 4.7", + "display_name": "Claude Opus 4.7", "modalities": { "input": [ "text", "image", - "video" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 235929 + "context": 1000000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": false, + "mode": "effort", + "effort": "high", + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" + ], "interleaved": true, "summaries": true, - "visibility": "summary", + "visibility": "omitted", "continuation": [ "thinking_blocks" ], - "effort_options": [ - "none", - "high" + "notes": [ + "Claude Opus 4.7 and newer Opus models require thinking.type = \"adaptive\" to enable thinking explicitly.", + "Manual budget_tokens requests return 400 on Claude Opus 4.7 and newer adaptive-only Opus models.", + "task_budget is separate from thinking control and should not be treated as a thinking budget." ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-04-17", - "last_updated": "2026-04-17", + "open_weights": false, + "knowledge": "2026-01-31", + "release_date": "2026-04-16", + "last_updated": "2026-04-16", "type": "chat" }, { - "id": "qwen/qwen3-30b-a3b", - "name": "Qwen3 30B A3B", - "display_name": "Qwen3 30B A3B", + "id": "anthropic/claude-opus-4.8", + "name": "Claude Opus 4.8", + "display_name": "Claude Opus 4.8", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 40960, - "output": 16384 + "context": 1000000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": false, + "mode": "effort", + "effort": "high", + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" + ], "interleaved": true, "summaries": true, - "visibility": "summary", + "visibility": "omitted", "continuation": [ "thinking_blocks" ], - "effort_options": [ - "none", - "high" + "notes": [ + "Claude Opus 4.7 and newer Opus models require thinking.type = \"adaptive\" to enable thinking explicitly.", + "Manual budget_tokens requests return 400 on Claude Opus 4.7 and newer adaptive-only Opus models.", + "task_budget is separate from thinking control and should not be treated as a thinking budget." ] } }, - "attachment": false, - "open_weights": true, - "release_date": "2025-04-28", - "last_updated": "2025-04-28", + "attachment": true, + "open_weights": false, + "knowledge": "2026-01", + "release_date": "2026-05-28", + "last_updated": "2026-05-28", "type": "chat" }, { - "id": "qwen/qwen3.8-flash", - "name": "Qwen3.8 Flash", - "display_name": "Qwen3.8 Flash", + "id": "anthropic/claude-opus-5.5", + "name": "Claude Opus 5.5", + "display_name": "Claude Opus 5.5", "modalities": { "input": [ "text", "image", - "video" + "pdf" ], "output": [ "text" @@ -96554,9 +96951,9 @@ }, "limit": { "context": 1000000, - "output": 131072 + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -96564,28 +96961,31 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, "effort_options": [ - "none", - "high" + "low", + "medium", + "high", + "xhigh", + "max" ] } }, "attachment": true, "open_weights": false, - "release_date": "2026-08-26", - "last_updated": "2026-08-26", + "knowledge": "2026-06", + "release_date": "2026-09-22", + "last_updated": "2026-09-22", "type": "chat" }, { - "id": "qwen/qwen3.7-flash", - "name": "Qwen3.7 Flash", - "display_name": "Qwen3.7 Flash", + "id": "anthropic/claude-sonnet-4.6", + "name": "Claude Sonnet 4.6", + "display_name": "Claude Sonnet 4.6", "modalities": { "input": [ "text", "image", - "video" + "pdf" ], "output": [ "text" @@ -96593,74 +96993,107 @@ }, "limit": { "context": 1000000, - "output": 65536 + "output": 128000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": false, + "mode": "mixed", + "budget": { + "min": 1024, + "unit": "tokens" + }, + "effort": "high", "effort_options": [ - "none", - "high" + "low", + "medium", + "high", + "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Anthropic recommends adaptive thinking with effort for Claude 4.6; budget_tokens remains a deprecated compatibility path.", + "Anthropic API defaults effort to high; lower effort levels should be chosen per workload." ] } }, "attachment": true, "open_weights": false, - "release_date": "2026-07-15", - "last_updated": "2026-07-15", + "knowledge": "2025-08-31", + "release_date": "2026-02-17", + "last_updated": "2026-03-13", "type": "chat" }, { - "id": "qwen/qwen3-vl-8b-instruct", - "name": "Qwen: Qwen3 VL 8B Instruct", - "display_name": "Qwen: Qwen3 VL 8B Instruct", + "id": "anthropic/claude-opus-5", + "name": "Claude Opus 5", + "display_name": "Claude Opus 5", "modalities": { "input": [ + "text", "image", - "text" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 235929 + "context": 1000000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" + ] + } }, "attachment": true, "open_weights": false, - "release_date": "2025-10-14", - "last_updated": "2025-10-14", + "knowledge": "2026-05", + "release_date": "2026-07-24", + "last_updated": "2026-07-24", "type": "chat" }, { - "id": "qwen/qwen3.6-27b", - "name": "Qwen3.6 27B", - "display_name": "Qwen3.6 27B", + "id": "qwen/qwen3-max", + "name": "Qwen3 Max", + "display_name": "Qwen3 Max", "modalities": { "input": [ - "text", - "image", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 65536 + "context": 256000, + "output": 64000 }, "temperature": true, "tool_call": true, @@ -96676,36 +97109,61 @@ "visibility": "summary", "continuation": [ "thinking_blocks" - ], - "effort_options": [ - "none", - "high" ] } }, + "attachment": false, + "open_weights": false, + "knowledge": "2025-04", + "release_date": "2025-09-23", + "last_updated": "2025-09-23", + "type": "chat" + }, + { + "id": "qwen/qwen-vl-max", + "name": "Qwen-VL Max", + "display_name": "Qwen-VL Max", + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 8000 + }, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": false + }, "attachment": true, - "open_weights": true, - "release_date": "2026-04-22", - "last_updated": "2026-04-22", + "open_weights": false, + "knowledge": "2024-04", + "release_date": "2024-04-08", + "last_updated": "2025-08-13", "type": "chat" }, { - "id": "qwen/qwen3.5-397b-a17b", - "name": "Qwen3.5 397B-A17B", - "display_name": "Qwen3.5 397B-A17B", + "id": "qwen/qwen3.5-27b", + "name": "Qwen3.5 27B", + "display_name": "Qwen3.5 27B", "modalities": { "input": [ "text", - "image", - "video" + "image" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 235929 + "context": 256000, + "output": 64000 }, "temperature": true, "tool_call": true, @@ -96721,23 +97179,19 @@ "visibility": "summary", "continuation": [ "thinking_blocks" - ], - "effort_options": [ - "none", - "high" ] } }, "attachment": true, "open_weights": true, - "release_date": "2026-02-15", - "last_updated": "2026-02-15", + "release_date": "2026-02-23", + "last_updated": "2026-02-23", "type": "chat" }, { - "id": "qwen/qwen3-235b-a22b-2507", - "name": "Qwen: Qwen3 235B A22B Instruct 2507", - "display_name": "Qwen: Qwen3 235B A22B Instruct 2507", + "id": "qwen/qwen3.7-max", + "name": "Qwen3.7 Max", + "display_name": "Qwen3.7 Max", "modalities": { "input": [ "text" @@ -96747,24 +97201,30 @@ ] }, "limit": { - "context": 262144, - "output": 16384 + "context": 1064000, + "output": 64000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true + } }, "attachment": false, - "open_weights": true, - "release_date": "2025-07-21", - "last_updated": "2025-07-21", + "open_weights": false, + "release_date": "2026-05-21", + "last_updated": "2026-05-21", "type": "chat" }, { - "id": "qwen/qwen3.5-27b", - "name": "Qwen3.5 27B", - "display_name": "Qwen3.5 27B", + "id": "qwen/qwen3.6-flash", + "name": "Qwen3.6 Flash", + "display_name": "Qwen3.6 Flash", "modalities": { "input": [ "text", @@ -96776,8 +97236,8 @@ ] }, "limit": { - "context": 262144, - "output": 81920 + "context": 1000000, + "output": 64000 }, "temperature": true, "tool_call": true, @@ -96793,27 +97253,22 @@ "visibility": "summary", "continuation": [ "thinking_blocks" - ], - "effort_options": [ - "none", - "high" ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-02-23", - "last_updated": "2026-02-23", + "open_weights": false, + "release_date": "2026-04-27", + "last_updated": "2026-04-27", "type": "chat" }, { - "id": "qwen/qwen3.7-plus", - "name": "Qwen3.7 Plus", - "display_name": "Qwen3.7 Plus", + "id": "qwen/qwen-plus", + "name": "Qwen Plus", + "display_name": "Qwen Plus", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" @@ -96821,37 +97276,40 @@ }, "limit": { "context": 1000000, - "output": 131072 + "output": 32000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true + "supported": true }, "extra_capabilities": { "reasoning": { "supported": true, - "effort_options": [ - "none", - "high" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, - "attachment": true, + "attachment": false, "open_weights": false, - "knowledge": "2025-04", - "release_date": "2026-06-02", - "last_updated": "2026-06-02", + "knowledge": "2024-04", + "release_date": "2024-01-25", + "last_updated": "2025-09-11", "type": "chat" }, { - "id": "qwen/qwen3-coder-flash", - "name": "Qwen3 Coder Flash", - "display_name": "Qwen3 Coder Flash", + "id": "qwen/qwen3.8-flash", + "name": "Qwen3.8 Flash", + "display_name": "Qwen3.8 Flash", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" @@ -96859,38 +97317,42 @@ }, "limit": { "context": 1000000, - "output": 65536 + "output": 131072 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, + "extra_capabilities": { + "reasoning": { + "supported": true + } + }, + "attachment": true, "open_weights": false, - "knowledge": "2025-04", - "release_date": "2025-07-28", - "last_updated": "2025-07-28", + "release_date": "2026-08-26", + "last_updated": "2026-08-26", "type": "chat" }, { - "id": "meta/muse-spark-1.3-contributor", - "name": "Meta: Muse Spark 1.3 Contributor", - "display_name": "Meta: Muse Spark 1.3 Contributor", + "id": "qwen/qwen3.5-plus", + "name": "Qwen3.5 Plus", + "display_name": "Qwen3.5 Plus", "modalities": { "input": [ "text", "image", - "video", - "pdf" + "video" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 943718 + "context": 1000000, + "output": 64000 }, "temperature": true, "tool_call": true, @@ -96900,41 +97362,39 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "minimal", - "low", - "medium", - "high", - "xhigh", - "max" + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, "attachment": true, "open_weights": false, - "release_date": "2026-09-02", - "last_updated": "2026-09-02", + "knowledge": "2025-04", + "release_date": "2026-02-16", + "last_updated": "2026-02-16", "type": "chat" }, { - "id": "meta/muse-spark-open", - "name": "Muse Spark Open", - "display_name": "Muse Spark Open", + "id": "qwen/qwen3.5-flash", + "name": "Qwen3.5 Flash", + "display_name": "Qwen3.5 Flash", "modalities": { "input": [ "text", "image", - "video", - "pdf", - "audio" + "video" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 1000000, + "output": 64000 }, "temperature": true, "tool_call": true, @@ -96944,83 +97404,67 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "low", - "medium", - "high", - "xhigh" + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, "attachment": true, - "open_weights": true, - "knowledge": "2026-01-04", - "release_date": "2026-10-15", - "last_updated": "2026-10-15", + "open_weights": false, + "release_date": "2026-02-23", + "last_updated": "2026-02-23", "type": "chat" }, { - "id": "meta/muse-spark-1.3", - "name": "Muse Spark 1.3", - "display_name": "Muse Spark 1.3", + "id": "qwen/qwen3-coder-next", + "name": "Qwen3 Coder Next", + "display_name": "Qwen3 Coder Next", "modalities": { "input": [ - "text", - "image", - "video", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 943718 + "context": 256000, + "output": 64000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "effort_options": [ - "minimal", - "low", - "medium", - "high", - "xhigh", - "max" - ] - } + "supported": false }, - "attachment": true, - "open_weights": false, - "release_date": "2026-09-02", - "last_updated": "2026-09-02", + "attachment": false, + "open_weights": true, + "knowledge": "2025-09", + "release_date": "2026-02-03", + "last_updated": "2026-02-03", "type": "chat" }, { - "id": "meta/muse-spark-1.1", - "name": "Muse Spark 1.1", - "display_name": "Muse Spark 1.1", + "id": "qwen/qwen3.8-max-0902", + "name": "Qwen3.8 Max 0902", + "display_name": "Qwen3.8 Max 0902", "modalities": { "input": [ "text", "image", - "pdf", - "video" + "video", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 943718 + "context": 1000000, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -97030,84 +97474,75 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, "effort_options": [ - "minimal", "low", "medium", - "high", "xhigh" ] } }, "attachment": true, "open_weights": false, - "release_date": "2026-04-08", - "last_updated": "2026-07-09", + "release_date": "2026-09-02", + "last_updated": "2026-09-02", "type": "chat" }, { - "id": "meta/muse-wonder", - "name": "Meta: Muse Wonder", - "display_name": "Meta: Muse Wonder", + "id": "qwen/qwen-turbo", + "name": "Qwen Turbo", + "display_name": "Qwen Turbo", "modalities": { "input": [ - "text", - "image", - "video", - "pdf", - "audio" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 943718 + "context": 128000, + "output": 16000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true + "supported": true }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "minimal", - "low", - "medium", - "high", - "xhigh", - "max" + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, - "attachment": true, + "attachment": false, "open_weights": false, - "release_date": "2026-10-12", - "last_updated": "2026-10-12", + "knowledge": "2024-04", + "release_date": "2024-11-01", + "last_updated": "2025-04-28", "type": "chat" }, { - "id": "meta/muse-spark-1.2", - "name": "Muse Spark 1.2", - "display_name": "Muse Spark 1.2", + "id": "qwen/qwen3.8-27b", + "name": "Qwen3.8 27B", + "display_name": "Qwen3.8 27B", "modalities": { "input": [ "text", "image", - "video", - "pdf" + "video" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 943718 + "context": 1131072, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -97115,83 +97550,57 @@ "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "effort_options": [ - "minimal", - "low", - "medium", - "high", - "xhigh" - ] - } - }, "attachment": true, - "open_weights": false, - "release_date": "2026-08-05", - "last_updated": "2026-08-05", + "open_weights": true, + "release_date": "2026-08-14", + "last_updated": "2026-08-14", "type": "chat" }, { - "id": "meta/muse-spark-1.2-contributor", - "name": "Meta: Muse Spark 1.2 Contributor", - "display_name": "Meta: Muse Spark 1.2 Contributor", + "id": "qwen/qwen3-coder-flash", + "name": "Qwen3 Coder Flash", + "display_name": "Qwen3 Coder Flash", "modalities": { "input": [ - "text", - "image", - "video", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 943718 + "context": 1000000, + "output": 64000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "minimal", - "low", - "medium", - "high", - "xhigh" - ] - } + "supported": false }, - "attachment": true, + "attachment": false, "open_weights": false, - "release_date": "2026-08-21", - "last_updated": "2026-08-21", + "knowledge": "2025-04", + "release_date": "2025-07-28", + "last_updated": "2025-07-28", "type": "chat" }, { - "id": "meta/muse-glimmer-30b", - "name": "Muse Glimmer 30B", - "display_name": "Muse Glimmer 30B", + "id": "qwen/qwen3.6-27b", + "name": "Qwen3.6 27B", + "display_name": "Qwen3.6 27B", "modalities": { "input": [ "text", "image", - "pdf" + "video" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 16384 + "context": 256000, + "output": 64000 }, "temperature": true, "tool_call": true, @@ -97201,38 +97610,36 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "low", - "medium", - "high", - "xhigh" + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, "attachment": true, "open_weights": true, - "knowledge": "2026-01-04", - "release_date": "2026-08-10", - "last_updated": "2026-08-10", + "release_date": "2026-04-22", + "last_updated": "2026-04-22", "type": "chat" }, { - "id": "stepfun/step-3.7-flash", - "name": "Step 3.7 Flash", - "display_name": "Step 3.7 Flash", + "id": "qwen/qwen-flash", + "name": "Qwen Flash", + "display_name": "Qwen Flash", "modalities": { "input": [ - "text", - "image", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 256000, - "output": 230400 + "context": 1000000, + "output": 32000 }, "temperature": true, "tool_call": true, @@ -97242,24 +97649,26 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "low", - "medium", - "high" + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, - "attachment": true, - "open_weights": true, - "knowledge": "2026-03-01", - "release_date": "2026-05-29", - "last_updated": "2026-05-29", + "attachment": false, + "open_weights": false, + "knowledge": "2024-04", + "release_date": "2025-07-28", + "last_updated": "2025-07-28", "type": "chat" }, { - "id": "stepfun/step-5-preview", - "name": "Step 5 Preview", - "display_name": "Step 5 Preview", + "id": "qwen/qwen3.6-plus", + "name": "Qwen3.6 Plus", + "display_name": "Qwen3.6 Plus", "modalities": { "input": [ "text", @@ -97282,23 +97691,26 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "low", - "medium", - "high" + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-09-16", - "last_updated": "2026-09-20", + "open_weights": false, + "knowledge": "2025-04", + "release_date": "2026-04-02", + "last_updated": "2026-04-02", "type": "chat" }, { - "id": "stepfun/step-3.5-flash", - "name": "Step 3.5 Flash", - "display_name": "Step 3.5 Flash", + "id": "qwen/qwen3.6-max-preview", + "name": "Qwen3.6 Max Preview", + "display_name": "Qwen3.6 Max Preview", "modalities": { "input": [ "text" @@ -97308,8 +97720,8 @@ ] }, "limit": { - "context": 262144, - "output": 65536 + "context": 256000, + "output": 64000 }, "temperature": true, "tool_call": true, @@ -97319,121 +97731,135 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "high" + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, "attachment": false, - "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-01-29", - "last_updated": "2026-02-13", + "open_weights": false, + "knowledge": "2025-04", + "release_date": "2026-04-20", + "last_updated": "2026-04-20", "type": "chat" }, { - "id": "perplexity/sonar-pro", - "name": "Perplexity: Sonar Pro", - "display_name": "Perplexity: Sonar Pro", + "id": "qwen/qwen3-coder-plus", + "name": "Qwen3 Coder Plus", + "display_name": "Qwen3 Coder Plus", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 8000 + "context": 1000000, + "output": 64000 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { "supported": false }, - "attachment": true, + "attachment": false, "open_weights": false, - "release_date": "2025-03-07", - "last_updated": "2025-03-07", + "knowledge": "2025-04", + "release_date": "2025-07-23", + "last_updated": "2025-07-23", "type": "chat" }, { - "id": "perplexity/sonar-deep-research", - "name": "Perplexity: Sonar Deep Research", - "display_name": "Perplexity: Sonar Deep Research", + "id": "qwen/qwen3.5-35b-a3b", + "name": "Qwen3.5 35B-A3B", + "display_name": "Qwen3.5 35B-A3B", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 115200 + "context": 256000, + "output": 64000 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { "supported": true, "default": true }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "high" + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, - "attachment": false, - "open_weights": false, - "release_date": "2025-03-07", - "last_updated": "2025-03-07", + "attachment": true, + "open_weights": true, + "release_date": "2026-02-23", + "last_updated": "2026-02-23", "type": "chat" }, { - "id": "perplexity/sonar-pro-search", - "name": "Perplexity: Sonar Pro Search", - "display_name": "Perplexity: Sonar Pro Search", + "id": "qwen/qwen3.8-max", + "name": "Qwen3.8 Max", + "display_name": "Qwen3.8 Max", "modalities": { "input": [ "text", - "image" + "image", + "video", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 8000 + "context": 1000000, + "output": 131072 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { "supported": true, "default": true }, "extra_capabilities": { "reasoning": { + "supported": true, "effort_options": [ - "high" + "low", + "medium", + "xhigh" ] } }, "attachment": true, "open_weights": false, - "release_date": "2025-10-30", - "last_updated": "2025-10-30", + "release_date": "2026-08-03", + "last_updated": "2026-08-03", "type": "chat" }, { - "id": "perplexity/sonar", - "name": "Perplexity: Sonar", - "display_name": "Perplexity: Sonar", + "id": "qwen/qwen3.5-397b-a17b", + "name": "Qwen3.5 397B-A17B", + "display_name": "Qwen3.5 397B-A17B", "modalities": { "input": [ "text", @@ -97444,63 +97870,76 @@ ] }, "limit": { - "context": 127072, - "output": 114364 + "context": 256000, + "output": 64000 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } }, "attachment": true, - "open_weights": false, - "release_date": "2025-01-27", - "last_updated": "2025-01-27", + "open_weights": true, + "release_date": "2026-02-15", + "last_updated": "2026-02-15", "type": "chat" }, { - "id": "perplexity/sonar-reasoning-pro", - "name": "Perplexity: Sonar Reasoning Pro", - "display_name": "Perplexity: Sonar Reasoning Pro", + "id": "qwen/qwen3.7-plus", + "name": "Qwen3.7 Plus", + "display_name": "Qwen3.7 Plus", "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 115200 + "context": 1064000, + "output": 64000 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { "supported": true, "default": true }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "high" - ] + "supported": true } }, "attachment": true, "open_weights": false, - "release_date": "2025-03-07", - "last_updated": "2025-03-07", + "knowledge": "2025-04", + "release_date": "2026-06-02", + "last_updated": "2026-06-02", "type": "chat" }, { - "id": "relace/relace-apply-3", - "name": "Relace: Relace Apply 3", - "display_name": "Relace: Relace Apply 3", + "id": "qwen/qwen3.5-122b-a10b", + "name": "Qwen3.5 122B-A10B", + "display_name": "Qwen3.5 122B-A10B", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" @@ -97508,23 +97947,35 @@ }, "limit": { "context": 256000, - "output": 128000 + "output": 64000 }, - "temperature": false, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, - "open_weights": false, - "release_date": "2025-09-26", - "last_updated": "2025-09-26", + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } + }, + "attachment": true, + "open_weights": true, + "release_date": "2026-02-23", + "last_updated": "2026-02-23", "type": "chat" }, { - "id": "relace/relace-search", - "name": "Relace: Relace Search", - "display_name": "Relace: Relace Search", + "id": "qwen/qwen-max", + "name": "Qwen Max", + "display_name": "Qwen Max", "modalities": { "input": [ "text" @@ -97534,8 +97985,8 @@ ] }, "limit": { - "context": 256000, - "output": 128000 + "context": 32000, + "output": 8000 }, "temperature": true, "tool_call": true, @@ -97544,46 +97995,55 @@ }, "attachment": false, "open_weights": false, - "release_date": "2025-12-08", - "last_updated": "2025-12-08", + "knowledge": "2024-04", + "release_date": "2024-04-03", + "last_updated": "2025-01-25", "type": "chat" }, { - "id": "mistralai/mistral-large-2407", - "name": "Mistral Large 2407", - "display_name": "Mistral Large 2407", + "id": "deepseek/deepseek-v4-flash-0423", + "name": "DeepSeek V4 Flash 0423", + "display_name": "DeepSeek V4 Flash 0423", "modalities": { "input": [ - "text", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 104857 + "context": 1000000, + "output": 384000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": true, - "open_weights": true, - "knowledge": "2024-03-31", - "release_date": "2024-07-24", - "last_updated": "2024-07-24", + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "low", + "high", + "max" + ] + } + }, + "attachment": false, + "open_weights": true, + "knowledge": "2025-05", + "release_date": "2026-04-23", + "last_updated": "2026-04-23", "type": "chat" }, { - "id": "mistralai/mistral-small-3.2-24b-instruct", - "name": "Mistral Small 3.2 24B", - "display_name": "Mistral Small 3.2 24B", + "id": "deepseek/deepseek-v4-pro-0423", + "name": "DeepSeek V4 Pro 0423", + "display_name": "DeepSeek V4 Pro 0423", "modalities": { "input": [ - "image", "text" ], "output": [ @@ -97591,25 +98051,34 @@ ] }, "limit": { - "context": 128000, - "output": 16384 + "context": 1000000, + "output": 384000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": true, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "high", + "max" + ] + } + }, + "attachment": false, "open_weights": true, - "knowledge": "2023-10-31", - "release_date": "2025-06-20", - "last_updated": "2025-06-20", + "knowledge": "2025-05", + "release_date": "2026-04-23", + "last_updated": "2026-04-23", "type": "chat" }, { - "id": "mistralai/ministral-14b-2512", - "name": "Ministral 3 14B 2512", - "display_name": "Ministral 3 14B 2512", + "id": "deepseek/deepseek-v4.1-flash-1017", + "name": "DeepSeek V4.1 Flash 1017", + "display_name": "DeepSeek V4.1 Flash 1017", "modalities": { "input": [ "text", @@ -97620,228 +98089,328 @@ ] }, "limit": { - "context": 262144, - "output": 209715 + "context": 1000000, + "output": 384000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "low", + "high", + "max" + ] + } }, "attachment": true, "open_weights": true, - "release_date": "2025-12-02", - "last_updated": "2025-12-02", + "knowledge": "2025-05", + "release_date": "2026-10-17", + "last_updated": "2026-10-17", "type": "chat" }, { - "id": "mistralai/mistral-medium-3", - "name": "Mistral Medium 3", - "display_name": "Mistral Medium 3", + "id": "deepseek/deepseek-v3.2", + "name": "DeepSeek V3.2", + "display_name": "DeepSeek V3.2", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 104857 + "context": 128000, + "output": 32000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-03-31", - "release_date": "2025-05-07", - "last_updated": "2025-05-07", + "extra_capabilities": { + "reasoning": { + "supported": true + } + }, + "attachment": false, + "open_weights": true, + "knowledge": "2024-07", + "release_date": "2025-12-01", + "last_updated": "2025-12-01", "type": "chat" }, { - "id": "mistralai/devstral-2512", - "name": "Devstral 2", - "display_name": "Devstral 2", + "id": "deepseek/deepseek-v4.1-flash", + "name": "DeepSeek V4.1 Flash", + "display_name": "DeepSeek V4.1 Flash", "modalities": { "input": [ "text", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 209715 + "context": 1000000, + "output": 384000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "effort_options": [ + "low", + "high", + "max" + ] + } }, "attachment": true, "open_weights": true, - "knowledge": "2025-12", - "release_date": "2025-12-09", - "last_updated": "2025-12-09", + "knowledge": "2025-05", + "release_date": "2026-09-10", + "last_updated": "2026-09-10", "type": "chat" }, { - "id": "mistralai/mistral-large-2512", - "name": "Mistral Large 3", - "display_name": "Mistral Large 3", + "id": "deepseek/deepseek-v4-pro", + "name": "DeepSeek V4 Pro", + "display_name": "DeepSeek V4 Pro", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 209715 + "context": 1000000, + "output": 384000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": true, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "high", + "effort_options": [ + "low", + "high", + "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." + ] + } + }, + "attachment": false, "open_weights": true, - "knowledge": "2024-11", - "release_date": "2025-12-02", - "last_updated": "2025-12-02", + "knowledge": "2025-05", + "release_date": "2026-04-24", + "last_updated": "2026-04-24", "type": "chat" }, { - "id": "mistralai/voxtral-small-24b-2507", - "name": "Voxtral Small 24B 2507", - "display_name": "Voxtral Small 24B 2507", + "id": "deepseek/deepseek-v4-flash", + "name": "DeepSeek V4 Flash", + "display_name": "DeepSeek V4 Flash", "modalities": { "input": [ - "text", - "audio", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 32768, - "output": 26214 + "context": 1000000, + "output": 384000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": true, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "high", + "effort_options": [ + "low", + "high", + "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." + ] + } + }, + "attachment": false, "open_weights": true, - "release_date": "2025-07-15", - "last_updated": "2025-07-15", + "knowledge": "2025-05", + "release_date": "2026-04-24", + "last_updated": "2026-04-24", "type": "chat" }, { - "id": "mistralai/mistral-saba", - "name": "Saba", - "display_name": "Saba", + "id": "deepseek/deepseek-v4-pro-0813", + "name": "DeepSeek V4 Pro 0813", + "display_name": "DeepSeek V4 Pro 0813", "modalities": { "input": [ - "text", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 32768, - "output": 26214 + "context": 1000000, + "output": 384000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": true, - "open_weights": false, - "knowledge": "2024-09-30", - "release_date": "2025-02-17", - "last_updated": "2025-02-17", + "extra_capabilities": { + "reasoning": { + "supported": true, + "effort_options": [ + "high", + "max" + ] + } + }, + "attachment": false, + "open_weights": true, + "release_date": "2026-08-13", + "last_updated": "2026-08-22", "type": "chat" }, { - "id": "mistralai/mixtral-8x22b-instruct", - "name": "Mixtral 8x22B Instruct", - "display_name": "Mixtral 8x22B Instruct", + "id": "deepseek/deepseek-v4-flash-0731", + "name": "DeepSeek V4 Flash 0731", + "display_name": "DeepSeek V4 Flash 0731", "modalities": { "input": [ - "text", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 65536, - "output": 52428 + "context": 1000000, + "output": 384000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": true, + "extra_capabilities": { + "reasoning": { + "supported": true, + "effort_options": [ + "low", + "high", + "max" + ] + } + }, + "attachment": false, "open_weights": true, - "knowledge": "2024-01-31", - "release_date": "2024-04-17", - "last_updated": "2024-04-17", + "knowledge": "2025-05", + "release_date": "2026-07-31", + "last_updated": "2026-07-31", "type": "chat" }, { - "id": "mistralai/mistral-small-24b-instruct-2501", - "name": "Mistral Small 3", - "display_name": "Mistral Small 3", + "id": "x-ai/grok-4.7", + "name": "Grok 4.7", + "display_name": "Grok 4.7", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 32768, - "output": 16384 + "context": 500000, + "output": 65536 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, - "open_weights": true, - "knowledge": "2023-10-31", - "release_date": "2025-01-30", - "last_updated": "2025-01-30", + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "low", + "medium", + "high", + "xhigh" + ] + } + }, + "attachment": true, + "open_weights": false, + "knowledge": "2026-05", + "release_date": "2026-09-21", + "last_updated": "2026-09-21", "type": "chat" }, { - "id": "mistralai/ministral-3b-2512", - "name": "Ministral 3 3B 2512", - "display_name": "Ministral 3 3B 2512", + "id": "x-ai/grok-4.5", + "name": "Grok 4.5", + "display_name": "Grok 4.5", "modalities": { "input": [ "text", @@ -97852,36 +98421,48 @@ ] }, "limit": { - "context": 131072, - "output": 104857 + "context": 500000, + "output": 65536 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "effort_options": [ + "low", + "medium", + "high" + ] + } }, "attachment": true, - "open_weights": true, - "release_date": "2025-12-02", - "last_updated": "2025-12-02", + "open_weights": false, + "release_date": "2026-07-08", + "last_updated": "2026-07-08", "type": "chat" }, { - "id": "mistralai/mistral-small-2603", - "name": "Mistral Small 4", - "display_name": "Mistral Small 4", + "id": "x-ai/grok-4.3", + "name": "Grok 4.3", + "display_name": "Grok 4.3", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 209715 + "context": 1000000, + "output": 30000 }, "temperature": true, "tool_call": true, @@ -97891,23 +98472,25 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, "effort_options": [ "none", + "low", + "medium", "high" ] } }, "attachment": true, - "open_weights": true, - "knowledge": "2025-06", - "release_date": "2026-03-16", - "last_updated": "2026-03-16", + "open_weights": false, + "release_date": "2026-04-17", + "last_updated": "2026-04-17", "type": "chat" }, { - "id": "mistralai/mistral-medium-3.1", - "name": "Mistral Medium 3.1", - "display_name": "Mistral Medium 3.1", + "id": "x-ai/grok-4.20", + "name": "Grok 4.20 (Reasoning)", + "display_name": "Grok 4.20 (Reasoning)", "modalities": { "input": [ "text", @@ -97919,25 +98502,25 @@ ] }, "limit": { - "context": 131072, - "output": 104857 + "context": 2000000, + "output": 128000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, "attachment": true, "open_weights": false, - "knowledge": "2025-06-30", - "release_date": "2025-08-12", - "last_updated": "2025-08-12", + "release_date": "2026-03-09", + "last_updated": "2026-03-09", "type": "chat" }, { - "id": "mistralai/ministral-8b-2512", - "name": "Ministral 3 8B 2512", - "display_name": "Ministral 3 8B 2512", + "id": "x-ai/grok-4.1-fast", + "name": "Grok 4.1 Fast", + "display_name": "Grok 4.1 Fast", "modalities": { "input": [ "text", @@ -97948,8 +98531,8 @@ ] }, "limit": { - "context": 262144, - "output": 209715 + "context": 2000000, + "output": 30000 }, "temperature": true, "tool_call": true, @@ -97957,15 +98540,15 @@ "supported": false }, "attachment": true, - "open_weights": true, - "release_date": "2025-12-02", - "last_updated": "2025-12-02", + "open_weights": false, + "release_date": "2025-11-19", + "last_updated": "2025-11-19", "type": "chat" }, { - "id": "mistralai/mistral-small-3.1-24b-instruct", - "name": "Mistral Small 3.1 24B", - "display_name": "Mistral Small 3.1 24B", + "id": "x-ai/grok-4.6", + "name": "Grok 4.6", + "display_name": "Grok 4.6", "modalities": { "input": [ "text", @@ -97976,25 +98559,37 @@ ] }, "limit": { - "context": 128000, - "output": 102400 + "context": 500000, + "output": 65536 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "effort_options": [ + "low", + "medium", + "high", + "xhigh" + ] + } }, "attachment": true, - "open_weights": true, - "knowledge": "2023-10-31", - "release_date": "2025-03-17", - "last_updated": "2025-03-17", + "open_weights": false, + "knowledge": "2026-02-01", + "release_date": "2026-08-12", + "last_updated": "2026-08-12", "type": "chat" }, { - "id": "mistralai/mistral-nemo", - "name": "Mistral Nemo", - "display_name": "Mistral Nemo", + "id": "bailian/qwen3-max", + "name": "Qwen3 Max", + "display_name": "Qwen3 Max", "modalities": { "input": [ "text" @@ -98004,25 +98599,37 @@ ] }, "limit": { - "context": 131072, - "output": 16384 + "context": 262144, + "output": 65536 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } }, "attachment": false, - "open_weights": true, - "knowledge": "2024-07", - "release_date": "2024-07-18", - "last_updated": "2024-07-18", + "open_weights": false, + "knowledge": "2025-04", + "release_date": "2025-09-23", + "last_updated": "2025-09-23", "type": "chat" }, { - "id": "mistralai/mistral-large-4-0", - "name": "Mistral: Mistral Large 4", - "display_name": "Mistral: Mistral Large 4", + "id": "bailian/qwen-vl-max", + "name": "Qwen-VL Max", + "display_name": "Qwen-VL Max", "modalities": { "input": [ "text", @@ -98033,75 +98640,77 @@ ] }, "limit": { - "context": 1048576, - "output": 262144 + "context": 131072, + "output": 8192 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "none", - "high" - ] - } + "supported": false }, "attachment": true, "open_weights": false, - "release_date": "2026-10-06", - "last_updated": "2026-10-06", + "knowledge": "2024-04", + "release_date": "2024-04-08", + "last_updated": "2025-08-13", "type": "chat" }, { - "id": "mistralai/mistral-large", - "name": "Mistral Large", - "display_name": "Mistral Large", + "id": "bailian/qwen3.5-27b", + "name": "Qwen3.5 27B", + "display_name": "Qwen3.5 27B", "modalities": { "input": [ "text", - "pdf" + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 102400 + "context": 262144, + "output": 65536 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } }, "attachment": true, - "open_weights": false, - "knowledge": "2024-11-30", - "release_date": "2024-02-26", - "last_updated": "2024-02-26", + "open_weights": true, + "release_date": "2026-02-23", + "last_updated": "2026-02-23", "type": "chat" }, { - "id": "mistralai/mistral-medium-3-5", - "name": "Mistral Medium 3.5", - "display_name": "Mistral Medium 3.5", + "id": "bailian/qwen3.7-max", + "name": "Qwen3.7 Max", + "display_name": "Qwen3.7 Max", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 209715 + "context": 1000000, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -98111,50 +98720,60 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "none", - "high" - ] + "supported": true } }, - "attachment": true, + "attachment": false, "open_weights": false, - "release_date": "2026-04-30", - "last_updated": "2026-04-30", + "release_date": "2026-05-21", + "last_updated": "2026-05-21", "type": "chat" }, { - "id": "mistralai/codestral-2508", - "name": "Codestral 25.08", - "display_name": "Codestral 25.08", + "id": "bailian/qwen3.6-flash", + "name": "Qwen3.6 Flash", + "display_name": "Qwen3.6 Flash", "modalities": { "input": [ "text", - "pdf" + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 256000, - "output": 204800 + "context": 1000000, + "output": 65536 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } }, "attachment": true, "open_weights": false, - "release_date": "2025-07-30", - "last_updated": "2025-07-30", + "release_date": "2026-04-27", + "last_updated": "2026-04-27", "type": "chat" }, { - "id": "cognitivecomputations/dolphin-mistral-24b-venice-edition", - "name": "Venice: Uncensored", - "display_name": "Venice: Uncensored", + "id": "bailian/qwen-plus", + "name": "Qwen Plus", + "display_name": "Qwen Plus", "modalities": { "input": [ "text" @@ -98164,109 +98783,154 @@ ] }, "limit": { - "context": 128000, - "output": 8192 + "context": 1000000, + "output": 32768 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } }, "attachment": false, - "open_weights": true, - "knowledge": "2024-04-30", - "release_date": "2025-07-09", - "last_updated": "2025-07-09", + "open_weights": false, + "knowledge": "2024-04", + "release_date": "2024-01-25", + "last_updated": "2025-09-11", "type": "chat" }, { - "id": "meta-llama/llama-3.1-70b-instruct", - "name": "Llama-3.1-70B-Instruct", - "display_name": "Llama-3.1-70B-Instruct", + "id": "bailian/qwen3.8-flash", + "name": "Qwen3.8 Flash", + "display_name": "Qwen3.8 Flash", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 16384 + "context": 1000000, + "output": 131072 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, - "open_weights": true, - "knowledge": "2023-12", - "release_date": "2024-07-23", - "last_updated": "2024-07-23", + "extra_capabilities": { + "reasoning": { + "supported": true + } + }, + "attachment": true, + "open_weights": false, + "release_date": "2026-08-26", + "last_updated": "2026-08-26", "type": "chat" }, { - "id": "meta-llama/llama-3.2-1b-instruct", - "name": "Llama 3.2 1B Instruct", - "display_name": "Llama 3.2 1B Instruct", + "id": "bailian/qwen3.5-plus", + "name": "Qwen3.5 Plus", + "display_name": "Qwen3.5 Plus", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 60000, - "output": 54000 + "context": 1000000, + "output": 65536 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, - "open_weights": true, - "knowledge": "2023-12", - "release_date": "2024-09-25", - "last_updated": "2024-09-25", + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } + }, + "attachment": true, + "open_weights": false, + "knowledge": "2025-04", + "release_date": "2026-02-16", + "last_updated": "2026-02-16", "type": "chat" }, { - "id": "meta-llama/llama-guard-4-12b", - "name": "Meta: Llama Guard 4 12B", - "display_name": "Meta: Llama Guard 4 12B", + "id": "bailian/qwen3.5-flash", + "name": "Qwen3.5 Flash", + "display_name": "Qwen3.5 Flash", "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 163840, - "output": 16384 + "context": 1000000, + "output": 65536 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } }, "attachment": true, - "open_weights": true, - "release_date": "2025-04-29", - "last_updated": "2025-04-29", + "open_weights": false, + "release_date": "2026-02-23", + "last_updated": "2026-02-23", "type": "chat" }, { - "id": "meta-llama/llama-3.3-70b-instruct", - "name": "Llama-3.3-70B-Instruct", - "display_name": "Llama-3.3-70B-Instruct", + "id": "bailian/qwen3-coder-next", + "name": "Qwen3 Coder Next", + "display_name": "Qwen3 Coder Next", "modalities": { "input": [ "text" @@ -98276,8 +98940,8 @@ ] }, "limit": { - "context": 131072, - "output": 16384 + "context": 262144, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -98286,47 +98950,58 @@ }, "attachment": false, "open_weights": true, - "knowledge": "2023-12", - "release_date": "2024-12-06", - "last_updated": "2024-12-06", + "knowledge": "2025-09", + "release_date": "2026-02-03", + "last_updated": "2026-02-03", "type": "chat" }, { - "id": "meta-llama/llama-3.2-3b-instruct", - "name": "Meta: Llama 3.2 3B Instruct", - "display_name": "Meta: Llama 3.2 3B Instruct", + "id": "bailian/qwen3.8-max-0902", + "name": "Qwen3.8 Max 0902", + "display_name": "Qwen3.8 Max 0902", "modalities": { "input": [ - "text" + "text", + "image", + "video", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 117964 + "context": 1000000, + "output": 131072 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, - "open_weights": true, - "knowledge": "2023-12", - "release_date": "2024-09-25", - "last_updated": "2024-09-25", + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "low", + "medium", + "xhigh" + ] + } + }, + "attachment": true, + "open_weights": false, + "release_date": "2026-09-02", + "last_updated": "2026-09-02", "type": "chat" }, { - "id": "meta-llama/llama-4-maverick", - "name": "Meta: Llama 4 Maverick", - "display_name": "Meta: Llama 4 Maverick", + "id": "bailian/qwen-turbo", + "name": "Qwen Turbo", + "display_name": "Qwen Turbo", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" @@ -98339,48 +99014,60 @@ "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true }, - "attachment": true, - "open_weights": true, - "knowledge": "2024-08", - "release_date": "2025-04-05", - "last_updated": "2025-04-05", + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } + }, + "attachment": false, + "open_weights": false, + "knowledge": "2024-04", + "release_date": "2024-11-01", + "last_updated": "2025-04-28", "type": "chat" }, { - "id": "meta-llama/llama-4-scout", - "name": "Meta: Llama 4 Scout", - "display_name": "Meta: Llama 4 Scout", + "id": "bailian/qwen3.8-27b", + "name": "Qwen3.8 27B", + "display_name": "Qwen3.8 27B", "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 327680, - "output": 16384 + "context": 1131072, + "output": 131072 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, "attachment": true, "open_weights": true, - "knowledge": "2024-08", - "release_date": "2025-04-05", - "last_updated": "2025-04-05", + "release_date": "2026-08-14", + "last_updated": "2026-08-14", "type": "chat" }, { - "id": "meta-llama/llama-3.1-8b-instruct", - "name": "Llama-3.1-8B-Instruct", - "display_name": "Llama-3.1-8B-Instruct", + "id": "bailian/qwen3-coder-flash", + "name": "Qwen3 Coder Flash", + "display_name": "Qwen3 Coder Flash", "modalities": { "input": [ "text" @@ -98390,8 +99077,8 @@ ] }, "limit": { - "context": 131072, - "output": 117964 + "context": 1000000, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -98399,16 +99086,16 @@ "supported": false }, "attachment": false, - "open_weights": true, - "knowledge": "2023-12", - "release_date": "2024-07-23", - "last_updated": "2024-07-23", + "open_weights": false, + "knowledge": "2025-04", + "release_date": "2025-07-28", + "last_updated": "2025-07-28", "type": "chat" }, { - "id": "perceptron/perceptron-mk1", - "name": "Perceptron: Perceptron Mk1", - "display_name": "Perceptron: Perceptron Mk1", + "id": "bailian/qwen3.6-27b", + "name": "Qwen3.6 27B", + "display_name": "Qwen3.6 27B", "modalities": { "input": [ "text", @@ -98420,47 +99107,47 @@ ] }, "limit": { - "context": 32768, - "output": 8192 + "context": 256000, + "output": 64000 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { "supported": true, "default": true }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "none", - "high" + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, "attachment": true, - "open_weights": false, - "release_date": "2026-05-12", - "last_updated": "2026-05-12", + "open_weights": true, + "release_date": "2026-04-22", + "last_updated": "2026-04-22", "type": "chat" }, { - "id": "perceptron/perceptron-mk1.5", - "name": "Perceptron: Perceptron Mk1.5", - "display_name": "Perceptron: Perceptron Mk1.5", + "id": "bailian/qwen-flash", + "name": "Qwen Flash", + "display_name": "Qwen Flash", "modalities": { "input": [ - "text", - "image", - "video", - "audio" + "text" ], "output": [ "text" ] }, "limit": { - "context": 36864, - "output": 8192 + "context": 1000000, + "output": 32768 }, "temperature": true, "tool_call": true, @@ -98470,36 +99157,39 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "none", - "minimal", - "low", - "medium", - "high" + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, - "attachment": true, + "attachment": false, "open_weights": false, - "release_date": "2026-09-25", - "last_updated": "2026-09-25", + "knowledge": "2024-04", + "release_date": "2025-07-28", + "last_updated": "2025-07-28", "type": "chat" }, { - "id": "inception/mercury-2", - "name": "Inception: Mercury 2", - "display_name": "Inception: Mercury 2", + "id": "bailian/qwen3.6-plus", + "name": "Qwen3.6 Plus", + "display_name": "Qwen3.6 Plus", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 50000 + "context": 1000000, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -98509,24 +99199,26 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "none", - "low", - "medium", - "high" + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, - "attachment": false, + "attachment": true, "open_weights": false, - "release_date": "2026-02-24", - "last_updated": "2026-02-24", + "knowledge": "2025-04", + "release_date": "2026-04-02", + "last_updated": "2026-04-02", "type": "chat" }, { - "id": "inception/mercury-2.5", - "name": "Inception: Mercury 2.5", - "display_name": "Inception: Mercury 2.5", + "id": "bailian/qwen3.6-max-preview", + "name": "Qwen3.6 Max Preview", + "display_name": "Qwen3.6 Max Preview", "modalities": { "input": [ "text" @@ -98536,7 +99228,7 @@ ] }, "limit": { - "context": 260000, + "context": 262144, "output": 65536 }, "temperature": true, @@ -98547,24 +99239,26 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "none", - "low", - "medium", - "high" + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, "attachment": false, "open_weights": false, - "release_date": "2026-09-08", - "last_updated": "2026-09-08", + "knowledge": "2025-04", + "release_date": "2026-04-20", + "last_updated": "2026-04-20", "type": "chat" }, { - "id": "inception/mercury-3", - "name": "Inception: Mercury 3", - "display_name": "Inception: Mercury 3", + "id": "bailian/qwen3-coder-plus", + "name": "Qwen3 Coder Plus", + "display_name": "Qwen3 Coder Plus", "modalities": { "input": [ "text" @@ -98574,58 +99268,38 @@ ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 1048576, + "output": 65536 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "none", - "low", - "medium", - "high" - ] - } + "supported": false }, "attachment": false, "open_weights": false, - "release_date": "2026-10-16", - "last_updated": "2026-10-16", + "knowledge": "2025-04", + "release_date": "2025-07-23", + "last_updated": "2025-07-23", "type": "chat" - } - ] - }, - "aiand": { - "id": "aiand", - "name": "ai&", - "display_name": "ai&", - "api": "https://api.aiand.com/v1", - "doc": "https://docs.aiand.com/", - "models": [ + }, { - "id": "zai-org/glm-5.3-flash", - "name": "GLM-5.3-Flash", - "display_name": "GLM-5.3-Flash", - "modalities": { + "id": "bailian/qwen3.5-35b-a3b", + "name": "Qwen3.5 35B-A3B", + "display_name": "Qwen3.5 35B-A3B", + "modalities": { "input": [ "text", "image", - "video", - "pdf" + "video" ], "output": [ "text" ] }, "limit": { - "context": 1048550, - "output": 131072 + "context": 262144, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -98641,34 +99315,32 @@ "visibility": "summary", "continuation": [ "thinking_blocks" - ], - "effort_options": [ - "low", - "high", - "max" ] } }, "attachment": true, "open_weights": true, - "release_date": "2026-08-26", - "last_updated": "2026-08-26", + "release_date": "2026-02-23", + "last_updated": "2026-02-23", "type": "chat" }, { - "id": "zai-org/glm-5.3", - "name": "GLM-5.3", - "display_name": "GLM-5.3", + "id": "bailian/qwen3.8-max", + "name": "Qwen3.8 Max", + "display_name": "Qwen3.8 Max", "modalities": { "input": [ - "text" + "text", + "image", + "video", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1048576, + "context": 1000000, "output": 131072 }, "temperature": true, @@ -98680,43 +99352,35 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], "effort_options": [ "low", - "high", - "max" + "medium", + "xhigh" ] } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-08-14", - "last_updated": "2026-08-14", + "attachment": true, + "open_weights": false, + "release_date": "2026-08-03", + "last_updated": "2026-08-03", "type": "chat" }, { - "id": "google/gemma-4-31b-it", - "name": "Gemma 4 31B IT", - "display_name": "Gemma 4 31B IT", + "id": "bailian/qwen3.5-397b-a17b", + "name": "Qwen3.5 397B-A17B", + "display_name": "Qwen3.5 397B-A17B", "modalities": { "input": [ "text", - "image", - "video", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 32768 + "context": 256000, + "output": 64000 }, "temperature": true, "tool_call": true, @@ -98732,35 +99396,32 @@ "visibility": "summary", "continuation": [ "thinking_blocks" - ], - "effort_options": [ - "none", - "high" ] } }, "attachment": true, "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-04-02", - "last_updated": "2026-04-02", + "release_date": "2026-02-15", + "last_updated": "2026-02-15", "type": "chat" }, { - "id": "openai/gpt-oss-120b", - "name": "GPT OSS 120B", - "display_name": "GPT OSS 120B", + "id": "bailian/qwen3.7-plus", + "name": "Qwen3.7 Plus", + "display_name": "Qwen3.7 Plus", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 32768 + "context": 1000000, + "output": 64000 }, "temperature": true, "tool_call": true, @@ -98770,42 +99431,32 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "effort_options": [ - "low", - "medium", - "high" - ] + "supported": true } }, - "attachment": false, - "open_weights": true, - "knowledge": "2024-06", - "release_date": "2025-08-05", - "last_updated": "2025-08-05", + "attachment": true, + "open_weights": false, + "knowledge": "2025-04", + "release_date": "2026-06-02", + "last_updated": "2026-06-02", "type": "chat" }, { - "id": "motif-technologies/motif-3", - "name": "Motif 3", - "display_name": "Motif 3", + "id": "bailian/qwen3.5-122b-a10b", + "name": "Qwen3.5 122B-A10B", + "display_name": "Qwen3.5 122B-A10B", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 256000, + "output": 64000 }, "temperature": true, "tool_call": true, @@ -98821,28 +99472,62 @@ "visibility": "summary", "continuation": [ "thinking_blocks" - ], - "effort_options": [ - "none", - "high" ] } }, - "attachment": false, + "attachment": true, "open_weights": true, - "release_date": "2026-08-07", - "last_updated": "2026-08-07", + "release_date": "2026-02-23", + "last_updated": "2026-02-23", "type": "chat" }, { - "id": "moonshotai/kimi-k3", - "name": "Kimi K3", - "display_name": "Kimi K3", + "id": "bailian/qwen-max", + "name": "Qwen Max", + "display_name": "Qwen Max", + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 32768, + "output": 8192 + }, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": false + }, + "attachment": false, + "open_weights": false, + "knowledge": "2024-04", + "release_date": "2024-04-03", + "last_updated": "2025-01-25", + "type": "chat" + } + ] + }, + "xiaomi-token-plan-ams": { + "id": "xiaomi-token-plan-ams", + "name": "Xiaomi Token Plan (Europe)", + "display_name": "Xiaomi Token Plan (Europe)", + "api": "https://token-plan-ams.xiaomimimo.com/v1", + "doc": "https://platform.xiaomimimo.com/#/docs", + "models": [ + { + "id": "mimo-v2.6-pro", + "name": "MiMo-V2.6-Pro", + "display_name": "MiMo-V2.6-Pro", "modalities": { "input": [ "text", "image", - "pdf" + "audio", + "video" ], "output": [ "text" @@ -98866,38 +99551,33 @@ "visibility": "summary", "continuation": [ "thinking_blocks" - ], - "effort_options": [ - "low", - "high", - "max" ] } }, "attachment": true, "open_weights": true, - "release_date": "2026-07-16", - "last_updated": "2026-07-16", + "release_date": "2026-09-22", + "last_updated": "2026-09-22", "type": "chat" }, { - "id": "qwen/qwen3.8-27b", - "name": "Qwen3.8 27B", - "display_name": "Qwen3.8 27B", + "id": "mimo-v2.6-flash", + "name": "MiMo-V2.6-Flash", + "display_name": "MiMo-V2.6-Flash", "modalities": { "input": [ "text", "image", - "video", - "pdf" + "audio", + "video" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 32768 + "context": 1048576, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -98913,39 +99593,85 @@ "visibility": "summary", "continuation": [ "thinking_blocks" - ], - "effort_options": [ - "none", - "low", - "medium", - "xhigh" ] } }, "attachment": true, "open_weights": true, - "release_date": "2026-08-14", - "last_updated": "2026-08-14", + "release_date": "2026-09-22", + "last_updated": "2026-09-22", "type": "chat" }, { - "id": "qwen/qwen3.6-27b", - "name": "Qwen3.6 27B", - "display_name": "Qwen3.6 27B", + "id": "mimo-v2.5-tts", + "name": "MiMo-V2.5-TTS", + "display_name": "MiMo-V2.5-TTS", + "modalities": { + "input": [ + "text" + ], + "output": [ + "audio" + ] + }, + "limit": { + "context": 8192, + "output": 8192 + }, + "temperature": false, + "tool_call": false, + "reasoning": { + "supported": false + }, + "attachment": false, + "open_weights": false, + "release_date": "2026-04-22", + "last_updated": "2026-04-22" + }, + { + "id": "mimo-v2.5-tts-voiceclone", + "name": "MiMo-V2.5-TTS-VoiceClone", + "display_name": "MiMo-V2.5-TTS-VoiceClone", + "modalities": { + "input": [ + "text" + ], + "output": [ + "audio" + ] + }, + "limit": { + "context": 8192, + "output": 8192 + }, + "temperature": false, + "tool_call": false, + "reasoning": { + "supported": false + }, + "attachment": false, + "open_weights": false, + "release_date": "2026-04-22", + "last_updated": "2026-04-22" + }, + { + "id": "mimo-v2.5", + "name": "MiMo-V2.5", + "display_name": "MiMo-V2.5", "modalities": { "input": [ "text", "image", - "video", - "pdf" + "audio", + "video" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 65536 + "context": 1048576, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -98961,28 +99687,49 @@ "visibility": "summary", "continuation": [ "thinking_blocks" - ], - "effort_options": [ - "none", - "high" ] } }, "attachment": true, "open_weights": true, + "knowledge": "2024-12", "release_date": "2026-04-22", "last_updated": "2026-04-22", "type": "chat" }, { - "id": "meta/muse-spark-open", - "name": "Muse Spark Open", - "display_name": "Muse Spark Open", + "id": "mimo-v2.5-tts-voicedesign", + "name": "MiMo-V2.5-TTS-VoiceDesign", + "display_name": "MiMo-V2.5-TTS-VoiceDesign", "modalities": { "input": [ - "text", - "image", - "video" + "text" + ], + "output": [ + "audio" + ] + }, + "limit": { + "context": 8192, + "output": 8192 + }, + "temperature": false, + "tool_call": false, + "reasoning": { + "supported": false + }, + "attachment": false, + "open_weights": false, + "release_date": "2026-04-22", + "last_updated": "2026-04-22" + }, + { + "id": "mimo-v2.5-pro", + "name": "MiMo-V2.5-Pro", + "display_name": "MiMo-V2.5-Pro", + "modalities": { + "input": [ + "text" ], "output": [ "text" @@ -98990,7 +99737,7 @@ }, "limit": { "context": 1048576, - "output": 943718 + "output": 131072 }, "temperature": true, "tool_call": true, @@ -99006,39 +99753,40 @@ "visibility": "summary", "continuation": [ "thinking_blocks" - ], - "effort_options": [ - "low", - "medium", - "high", - "xhigh" ] } }, - "attachment": true, + "attachment": false, "open_weights": true, - "knowledge": "2026-01-04", - "release_date": "2026-10-15", - "last_updated": "2026-10-15", + "knowledge": "2024-12", + "release_date": "2026-04-22", + "last_updated": "2026-04-22", "type": "chat" - }, + } + ] + }, + "openreason": { + "id": "openreason", + "name": "OpenReason", + "display_name": "OpenReason", + "api": "https://api.openreason.app/v1", + "doc": "https://openreason.app/docs", + "models": [ { - "id": "deepseek-ai/deepseek-v4.1-flash-1017", - "name": "DeepSeek V4.1 Flash 1017", - "display_name": "DeepSeek V4.1 Flash 1017", + "id": "openai/gpt-oss-120b", + "name": "GPT OSS 120B", + "display_name": "GPT OSS 120B", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 384000 + "context": 131072, + "output": 32768 }, "temperature": true, "tool_call": true, @@ -99056,34 +99804,35 @@ "thinking_blocks" ], "effort_options": [ - "none", - "high", - "max" + "low", + "medium", + "high" ] } }, - "attachment": true, + "attachment": false, "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-10-17", - "last_updated": "2026-10-17", + "knowledge": "2024-06", + "release_date": "2025-08-05", + "last_updated": "2025-08-05", "type": "chat" }, { - "id": "deepseek-ai/deepseek-v4-pro", - "name": "DeepSeek V4 Pro", - "display_name": "DeepSeek V4 Pro", + "id": "moonshotai/kimi-k2.7-code", + "name": "Kimi K2.7 Code", + "display_name": "Kimi K2.7 Code", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 384000 + "context": 262144, + "output": 262144 }, "temperature": true, "tool_call": true, @@ -99094,48 +99843,35 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "high", - "effort_options": [ - "low", - "high", - "max" - ], "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ "thinking_blocks" - ], - "notes": [ - "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." ] } }, - "attachment": false, + "attachment": true, "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-04-24", - "last_updated": "2026-04-24", + "knowledge": "2025-01", + "release_date": "2026-06-12", + "last_updated": "2026-06-12", "type": "chat" }, { - "id": "deepseek-ai/deepseek-v4.1-flash", - "name": "DeepSeek V4.1 Flash", - "display_name": "DeepSeek V4.1 Flash", + "id": "deepseek-ai/deepseek-v4-flash-0731", + "name": "DeepSeek V4 Flash 0731", + "display_name": "DeepSeek V4 Flash 0731", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, + "context": 1000000, "output": 384000 }, "temperature": true, @@ -99155,72 +99891,72 @@ ], "effort_options": [ "none", + "minimal", + "low", + "medium", "high", + "xhigh", "max" ] } }, - "attachment": true, + "attachment": false, "open_weights": true, "knowledge": "2025-05", - "release_date": "2026-09-10", - "last_updated": "2026-09-10", + "release_date": "2026-07-31", + "last_updated": "2026-07-31", "type": "chat" } ] }, - "llmgateway-providers": { - "id": "llmgateway-providers", - "name": "LLM Gateway", - "display_name": "LLM Gateway", - "api": "https://api.llmgateway.io/v1", - "doc": "https://llmgateway.io/docs", + "evroc": { + "id": "evroc", + "name": "evroc", + "display_name": "evroc", + "api": "https://models.think.evroc.com/v1", + "doc": "https://docs.evroc.com/products/think/overview.html", "models": [ { - "id": "scx-ai-gp/glm-5.3-fast", - "name": "GLM 5.3 Fast (SCX.ai)", - "display_name": "GLM 5.3 Fast (SCX.ai)", + "id": "openai/whisper-large-v3", + "name": "Whisper 3 Large", + "display_name": "Whisper 3 Large", "modalities": { "input": [ - "text" + "audio" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 448, + "output": 4096 }, "temperature": true, "tool_call": false, "reasoning": { "supported": false }, - "attachment": false, + "attachment": true, "open_weights": true, - "release_date": "2026-08-14", - "last_updated": "2026-08-14", - "type": "chat" + "release_date": "2023-11-06", + "last_updated": "2023-11-06" }, { - "id": "scx-ai-gp/glm-5.3-flash", - "name": "GLM-5.3 Flash (SCX.ai)", - "display_name": "GLM-5.3 Flash (SCX.ai)", + "id": "openai/gpt-oss-120b", + "name": "GPT OSS 120B", + "display_name": "GPT OSS 120B", "modalities": { "input": [ - "text", - "image", - "video", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 65536, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -99233,65 +99969,48 @@ "supported": true, "effort_options": [ "low", - "high", - "max" + "medium", + "high" ] } }, - "attachment": true, + "attachment": false, "open_weights": true, - "release_date": "2026-08-26", - "last_updated": "2026-08-26", + "knowledge": "2024-06", + "release_date": "2025-08-05", + "last_updated": "2025-08-05", "type": "chat" }, { - "id": "scx-ai-gp/kimi-k3", - "name": "Kimi K3 (SCX.ai)", - "display_name": "Kimi K3 (SCX.ai)", + "id": "openai/whisper-large-v3-turbo", + "name": "Whisper Large v3 Turbo", + "display_name": "Whisper Large v3 Turbo", "modalities": { "input": [ - "text", - "image", - "video" + "audio" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 1048576 + "context": 448, + "output": 448 }, - "temperature": false, - "tool_call": true, + "temperature": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "effort_options": [ - "none", - "minimal", - "low", - "medium", - "high", - "xhigh", - "max" - ] - } + "supported": false }, "attachment": true, "open_weights": true, - "release_date": "2026-07-16", - "last_updated": "2026-07-16", - "type": "chat" + "release_date": "2024-10-01", + "last_updated": "2024-10-01" }, { - "id": "scx-ai-gp/glm-5.2-fast", - "name": "GLM-5.2 Turbo (SCX.ai)", - "display_name": "GLM-5.2 Turbo (SCX.ai)", + "id": "Qwen/Qwen3-Reranker-4B", + "name": "Qwen3 Reranker 4B", + "display_name": "Qwen3 Reranker 4B", "modalities": { "input": [ "text" @@ -99301,39 +100020,63 @@ ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 32000, + "output": 4096 }, - "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true + "supported": false }, "attachment": false, "open_weights": true, - "release_date": "2026-06-13", - "last_updated": "2026-06-13", - "type": "chat" + "release_date": "2025-07-30", + "last_updated": "2025-07-30", + "type": "rerank" }, { - "id": "scx-ai-gp/qwen3.8-max", - "name": "Qwen3.8 Max (SCX.ai)", - "display_name": "Qwen3.8 Max (SCX.ai)", + "id": "Qwen/Qwen3-Embedding-8B", + "name": "Qwen3 Embedding 8B", + "display_name": "Qwen3 Embedding 8B", + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 40960, + "output": 4096 + }, + "temperature": false, + "tool_call": false, + "reasoning": { + "supported": false + }, + "attachment": false, + "open_weights": true, + "release_date": "2025-06-05", + "last_updated": "2025-06-05", + "type": "embedding" + }, + { + "id": "Qwen/Qwen3.8-27B", + "name": "Qwen3.8-27B", + "display_name": "Qwen3.8-27B", "modalities": { "input": [ "text", "image", - "video", - "pdf" + "video" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 262144, + "output": 262144 }, "temperature": true, "tool_call": true, @@ -99343,7 +100086,6 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, "effort_options": [ "low", "medium", @@ -99352,48 +100094,84 @@ } }, "attachment": true, - "open_weights": false, - "release_date": "2026-08-03", - "last_updated": "2026-08-03", + "open_weights": true, + "release_date": "2026-08-14", + "last_updated": "2026-08-14", "type": "chat" }, { - "id": "scx-ai-gp/deepseek-v4.1-flash", - "name": "DeepSeek V4.1 Flash (SCX.ai)", - "display_name": "DeepSeek V4.1 Flash (SCX.ai)", + "id": "Qwen/Qwen3.6-35B-A3B", + "name": "Qwen3.6 35B-A3B", + "display_name": "Qwen3.6 35B-A3B", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 384000 + "context": 262144, + "output": 65536 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": true + "supported": true, + "default": true }, "extra_capabilities": { "reasoning": { - "supported": true + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] } }, + "attachment": true, + "open_weights": true, + "release_date": "2026-04-17", + "last_updated": "2026-04-17", + "type": "chat" + }, + { + "id": "nvidia/Llama-3.3-70B-Instruct-FP8", + "name": "Llama-3.3-70B-Instruct", + "display_name": "Llama-3.3-70B-Instruct", + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 4096 + }, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": false + }, "attachment": false, "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-09-10", - "last_updated": "2026-09-10", + "knowledge": "2023-12", + "release_date": "2024-12-06", + "last_updated": "2024-12-06", "type": "chat" }, { - "id": "scx-ai-gp/kimi-k2.7-code", - "name": "Kimi K2.7 Code (SCX.ai)", - "display_name": "Kimi K2.7 Code (SCX.ai)", + "id": "evroc/roc", + "name": "roc", + "display_name": "roc", "modalities": { "input": [ "text", @@ -99408,7 +100186,7 @@ "context": 262144, "output": 262144 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -99422,39 +100200,33 @@ "visibility": "summary", "continuation": [ "thinking_blocks" - ], - "effort_options": [ - "none", - "minimal", - "low", - "medium", - "high", - "xhigh" ] } }, "attachment": true, - "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-06-12", - "last_updated": "2026-06-12", + "open_weights": false, + "knowledge": "2026-01", + "release_date": "2026-06-06", + "last_updated": "2026-06-06", "type": "chat" }, { - "id": "scx-ai-gp/glm-5.3", - "name": "GLM-5.3 (SCX.ai)", - "display_name": "GLM-5.3 (SCX.ai)", + "id": "moonshotai/Kimi-K2.6", + "name": "Kimi K2.6", + "display_name": "Kimi K2.6", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 262144, + "output": 262144 }, "temperature": true, "tool_call": true, @@ -99464,24 +100236,20 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "effort_options": [ - "low", - "high", - "max" - ] + "supported": true } }, - "attachment": false, + "attachment": true, "open_weights": true, - "release_date": "2026-08-14", - "last_updated": "2026-08-14", + "knowledge": "2025-01", + "release_date": "2026-04-20", + "last_updated": "2026-04-20", "type": "chat" }, { - "id": "scx-ai-gp/glm-5.2", - "name": "GLM-5.2 (SCX.ai)", - "display_name": "GLM-5.2 (SCX.ai)", + "id": "zai-org/GLM-5.2", + "name": "GLM-5.2", + "display_name": "GLM-5.2", "modalities": { "input": [ "text" @@ -99491,7 +100259,7 @@ ] }, "limit": { - "context": 1000000, + "context": 524288, "output": 131072 }, "temperature": true, @@ -99503,19 +100271,8 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], "effort_options": [ - "none", - "minimal", - "low", - "medium", "high", - "xhigh", "max" ] } @@ -99527,20 +100284,21 @@ "type": "chat" }, { - "id": "scx-ai/minimax-m2.7", - "name": "MiniMax M2.7 (SCX.ai (Turbo))", - "display_name": "MiniMax M2.7 (SCX.ai (Turbo))", + "id": "google/gemma-4-26B-A4B-it", + "name": "Gemma 4 26B A4B IT", + "display_name": "Gemma 4 26B A4B IT", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 196608, - "output": 196608 + "context": 262144, + "output": 32768 }, "temperature": true, "tool_call": true, @@ -99550,43 +100308,31 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" - ] + "supported": true } }, - "attachment": false, + "attachment": true, "open_weights": true, - "release_date": "2026-03-18", - "last_updated": "2026-03-18", + "release_date": "2026-04-02", + "last_updated": "2026-04-02", "type": "chat" }, { - "id": "scx-ai/gpt-oss-120b", - "name": "GPT OSS 120B (SCX.ai (Turbo))", - "display_name": "GPT OSS 120B (SCX.ai (Turbo))", + "id": "mistralai/Mistral-Medium-3.5-128B", + "name": "Mistral Medium 3.5", + "display_name": "Mistral Medium 3.5", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 32768 + "context": 262144, + "output": 262144 }, "temperature": true, "tool_call": true, @@ -99596,27 +100342,25 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, "effort_options": [ - "low", - "medium", + "none", "high" ] } }, - "attachment": false, + "attachment": true, "open_weights": true, - "knowledge": "2024-06", - "release_date": "2025-08-05", - "last_updated": "2025-08-05", + "release_date": "2026-04-29", + "last_updated": "2026-04-29", "type": "chat" }, { - "id": "scx-ai/qwen3-32b", - "name": "Qwen3 32B (SCX.ai (Turbo))", - "display_name": "Qwen3 32B (SCX.ai (Turbo))", + "id": "mistralai/Voxtral-Small-24B-2507", + "name": "Voxtral Small 24B", + "display_name": "Voxtral Small 24B", "modalities": { "input": [ + "audio", "text" ], "output": [ @@ -99624,42 +100368,89 @@ ] }, "limit": { - "context": 32768, - "output": 8192 + "context": 32000, + "output": 32000 }, - "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true + "supported": false }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } + "attachment": true, + "open_weights": true, + "release_date": "2025-03-01", + "last_updated": "2025-03-01" + }, + { + "id": "KBLab/kb-whisper-large", + "name": "KB Whisper", + "display_name": "KB Whisper", + "modalities": { + "input": [ + "audio" + ], + "output": [ + "text" + ] }, - "attachment": false, + "limit": { + "context": 448, + "output": 448 + }, + "tool_call": false, + "reasoning": { + "supported": false + }, + "attachment": true, "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-04-28", - "last_updated": "2025-04-28", - "type": "chat" + "release_date": "2024-10-01", + "last_updated": "2024-10-01" }, { - "id": "nebius/minimax-m3.1-flash", - "name": "MiniMax M3.1 Flash (Nebius AI)", - "display_name": "MiniMax M3.1 Flash (Nebius AI)", + "id": "intfloat/multilingual-e5-large-instruct", + "name": "E5 Multi-Lingual Large Embeddings 0.6B", + "display_name": "E5 Multi-Lingual Large Embeddings 0.6B", + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 512, + "output": 512 + }, + "tool_call": false, + "reasoning": { + "supported": false + }, + "attachment": false, + "open_weights": true, + "release_date": "2024-06-01", + "last_updated": "2024-06-01", + "type": "embedding" + } + ] + }, + "opencode": { + "id": "opencode", + "name": "OpenCode", + "display_name": "OpenCode", + "api": "https://opencode.ai/inference/openai/v1", + "doc": "https://opencode.ai/v2/docs/console/models/", + "models": [ + { + "id": "gemini-3.5-flash", + "name": "Gemini 3.5 Flash", + "display_name": "Gemini 3.5 Flash", "modalities": { "input": [ "text", "image", - "video" + "video", + "audio", + "pdf" ], "output": [ "text" @@ -99667,7 +100458,7 @@ }, "limit": { "context": 1048576, - "output": 1048576 + "output": 65536 }, "temperature": true, "tool_call": true, @@ -99677,39 +100468,45 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "none", + "supported": true, + "default_enabled": true, + "mode": "level", + "level": "high", + "level_options": [ "minimal", "low", "medium", - "high", - "xhigh", - "max" + "high" + ], + "summaries": true, + "visibility": "summary", + "continuation": [ + "thought_signatures" ] } }, "attachment": true, "open_weights": false, - "release_date": "2026-10-19", - "last_updated": "2026-10-19", + "knowledge": "2025-01", + "release_date": "2026-05-19", + "last_updated": "2026-05-19", "type": "chat" }, { - "id": "luminal/deepseek-v4.1-flash", - "name": "DeepSeek V4.1 Flash (luminal)", - "display_name": "DeepSeek V4.1 Flash (luminal)", + "id": "nemotron-3.5-lightning-free", + "name": "Nemotron 3.5 Lightning Free", + "display_name": "Nemotron 3.5 Lightning Free", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 384000 + "context": 262144, + "output": 262144 }, "temperature": true, "tool_call": true, @@ -99720,78 +100517,90 @@ "extra_capabilities": { "reasoning": { "supported": true, - "effort_options": [ - "none", - "minimal", - "low", - "medium", - "high", - "xhigh", - "max" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, - "attachment": true, + "attachment": false, "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-09-10", - "last_updated": "2026-09-10", + "knowledge": "2025-09", + "release_date": "2026-08-11", + "last_updated": "2026-08-11", "type": "chat" }, { - "id": "bytedance/seed-1-8-251228", - "name": "Seed 1.8 (251228) (ByteDance)", - "display_name": "Seed 1.8 (251228) (ByteDance)", + "id": "gpt-5.4-nano", + "name": "GPT-5.4 Nano", + "display_name": "GPT-5.4 Nano", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 256000, - "output": 256000 + "context": 400000, + "output": 128000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": false, + "mode": "effort", + "effort": "none", "effort_options": [ - "minimal", + "none", + "low", + "medium", + "high", + "xhigh" + ], + "verbosity": "medium", + "verbosity_options": [ "low", "medium", "high" - ] + ], + "visibility": "hidden" } }, "attachment": true, "open_weights": false, - "release_date": "2025-12-18", - "last_updated": "2025-12-18", + "knowledge": "2025-08-31", + "release_date": "2026-03-17", + "last_updated": "2026-03-17", "type": "chat" }, { - "id": "bytedance/seed-2-0-lite-260428", - "name": "Seed 2.0 Lite (260428) (ByteDance)", - "display_name": "Seed 2.0 Lite (260428) (ByteDance)", + "id": "x-preview-f-free", + "name": "Ox Alpha Free (Unlimited)", + "display_name": "Ox Alpha Free (Unlimited)", "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 262144, + "context": 1000000, "output": 131072 }, "temperature": true, @@ -99802,24 +100611,30 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ - "minimal", "low", - "medium", - "high" + "high", + "max" ] } }, "attachment": true, "open_weights": false, - "release_date": "2026-04-28", - "last_updated": "2026-04-28", + "release_date": "2026-08-21", + "last_updated": "2026-08-21", "type": "chat" }, { - "id": "bytedance/deepseek-v4-flash", - "name": "DeepSeek V4 Flash (ByteDance)", - "display_name": "DeepSeek V4 Flash (ByteDance)", + "id": "mimo-v2-flash-free", + "name": "MiMo V2 Flash Free", + "display_name": "MiMo V2 Flash Free", "modalities": { "input": [ "text" @@ -99829,8 +100644,8 @@ ] }, "limit": { - "context": 1048576, - "output": 393216 + "context": 262144, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -99841,50 +100656,40 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "high", - "effort_options": [ - "low", - "high", - "max" - ], "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ "thinking_blocks" - ], - "notes": [ - "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." ] } }, "attachment": false, "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-04-24", - "last_updated": "2026-04-24", + "knowledge": "2024-12-01", + "release_date": "2025-12-16", + "last_updated": "2026-02-04", "type": "chat" }, { - "id": "bytedance/seed-1-6-250915", - "name": "Seed 1.6 (250915) (ByteDance)", - "display_name": "Seed 1.6 (250915) (ByteDance)", + "id": "kimi-k3", + "name": "Kimi K3", + "display_name": "Kimi K3", "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 256000, - "output": 256000 + "context": 1048576, + "output": 131072 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -99892,24 +100697,30 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ - "minimal", "low", - "medium", - "high" + "high", + "max" ] } }, "attachment": true, - "open_weights": false, - "release_date": "2025-09-15", - "last_updated": "2025-09-15", + "open_weights": true, + "release_date": "2026-07-16", + "last_updated": "2026-07-16", "type": "chat" }, { - "id": "bytedance/deepseek-v4-pro", - "name": "DeepSeek V4 Pro (ByteDance)", - "display_name": "DeepSeek V4 Pro (ByteDance)", + "id": "hy3-preview-free", + "name": "Hy3 preview Free", + "display_name": "Hy3 preview Free", "modalities": { "input": [ "text" @@ -99919,8 +100730,8 @@ ] }, "limit": { - "context": 1048576, - "output": 393216 + "context": 256000, + "output": 64000 }, "temperature": true, "tool_call": true, @@ -99928,42 +100739,23 @@ "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "high", - "effort_options": [ - "low", - "high", - "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." - ] - } - }, "attachment": false, "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-04-24", - "last_updated": "2026-04-24", + "knowledge": "2025-06", + "release_date": "2026-04-20", + "last_updated": "2026-04-20", "type": "chat" }, { - "id": "bytedance/glm-4.7", - "name": "GLM-4.7 (ByteDance)", - "display_name": "GLM-4.7 (ByteDance)", + "id": "mimo-v2.6-flash-free", + "name": "MiMo-V2.6-Flash Free", + "display_name": "MiMo-V2.6-Flash Free", "modalities": { "input": [ - "text" + "text", + "image", + "audio", + "video" ], "output": [ "text" @@ -99971,7 +100763,7 @@ }, "limit": { "context": 200000, - "output": 128000 + "output": 32000 }, "temperature": true, "tool_call": true, @@ -99990,17 +100782,16 @@ ] } }, - "attachment": false, + "attachment": true, "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-12-22", - "last_updated": "2025-12-22", + "release_date": "2026-09-22", + "last_updated": "2026-09-22", "type": "chat" }, { - "id": "bytedance/gpt-oss-120b", - "name": "GPT OSS 120B (ByteDance)", - "display_name": "GPT OSS 120B (ByteDance)", + "id": "minimax-m2.5", + "name": "MiniMax-M2.5", + "display_name": "MiniMax-M2.5", "modalities": { "input": [ "text" @@ -100010,8 +100801,8 @@ ] }, "limit": { - "context": 128000, - "output": 32000 + "context": 204800, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -100022,109 +100813,142 @@ "extra_capabilities": { "reasoning": { "supported": true, - "effort_options": [ - "low", - "medium", - "high" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, "attachment": false, "open_weights": true, - "knowledge": "2024-06", - "release_date": "2025-08-05", - "last_updated": "2025-08-05", + "knowledge": "2025-01", + "release_date": "2026-02-12", + "last_updated": "2026-02-12", "type": "chat" }, { - "id": "bytedance/seed-1-6-flash-250715", - "name": "Seed 1.6 Flash (250715) (ByteDance)", - "display_name": "Seed 1.6 Flash (250715) (ByteDance)", + "id": "gpt-5.4", + "name": "GPT-5.4", + "display_name": "GPT-5.4", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 256000, - "output": 256000 + "context": 1050000, + "output": 128000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": false, + "mode": "effort", + "effort": "none", "effort_options": [ - "minimal", + "none", + "low", + "medium", + "high", + "xhigh" + ], + "verbosity": "medium", + "verbosity_options": [ "low", "medium", "high" - ] + ], + "visibility": "hidden" } }, "attachment": true, "open_weights": false, - "release_date": "2025-07-26", - "last_updated": "2025-07-26", + "knowledge": "2025-08-31", + "release_date": "2026-03-05", + "last_updated": "2026-03-05", "type": "chat" }, { - "id": "bytedance/deepseek-v3.2", - "name": "DeepSeek V3.2 (ByteDance)", - "display_name": "DeepSeek V3.2 (ByteDance)", + "id": "gpt-5.4-mini", + "name": "GPT-5.4 Mini", + "display_name": "GPT-5.4 Mini", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 32768 + "context": 400000, + "output": 128000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { - "supported": true + "supported": true, + "default_enabled": false, + "mode": "effort", + "effort": "none", + "effort_options": [ + "none", + "low", + "medium", + "high", + "xhigh" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, - "attachment": false, - "open_weights": true, - "knowledge": "2024-07", - "release_date": "2025-12-01", - "last_updated": "2025-12-01", + "attachment": true, + "open_weights": false, + "knowledge": "2025-08-31", + "release_date": "2026-03-17", + "last_updated": "2026-03-17", "type": "chat" }, { - "id": "bytedance/seed-2-0-mini-260428", - "name": "Seed 2.0 Mini (260428) (ByteDance)", - "display_name": "Seed 2.0 Mini (260428) (ByteDance)", + "id": "hy3-free", + "name": "Hy3 Free", + "display_name": "Hy3 Free", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 131072 + "context": 190000, + "output": 64000 }, "temperature": true, "tool_call": true, @@ -100134,24 +100958,30 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ - "minimal", "low", "medium", "high" ] } }, - "attachment": true, - "open_weights": false, - "release_date": "2026-04-28", - "last_updated": "2026-04-28", + "attachment": false, + "open_weights": true, + "release_date": "2026-07-06", + "last_updated": "2026-07-06", "type": "chat" }, { - "id": "bytedance/seed-2-0-code-preview-260328", - "name": "Seed 2.0 Code Preview (260328) (ByteDance)", - "display_name": "Seed 2.0 Code Preview (260328) (ByteDance)", + "id": "gpt-5.1-codex-max", + "name": "GPT-5.1 Codex Max", + "display_name": "GPT-5.1 Codex Max", "modalities": { "input": [ "text", @@ -100162,39 +100992,52 @@ ] }, "limit": { - "context": 262144, - "output": 131072 + "context": 400000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": false, + "mode": "effort", + "effort": "none", "effort_options": [ - "minimal", + "none", "low", "medium", "high" - ] + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, "attachment": true, "open_weights": false, - "release_date": "2026-03-28", - "last_updated": "2026-03-28", + "knowledge": "2024-09-30", + "release_date": "2025-11-13", + "last_updated": "2025-11-13", "type": "chat" }, { - "id": "bytedance/seed-1-6-250615", - "name": "Seed 1.6 (250615) (ByteDance)", - "display_name": "Seed 1.6 (250615) (ByteDance)", + "id": "grok-build-0.1", + "name": "Grok Build 0.1", + "display_name": "Grok Build 0.1", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" @@ -100212,38 +101055,34 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "minimal", - "low", - "medium", - "high" - ] + "supported": true } }, "attachment": true, "open_weights": false, - "release_date": "2025-06-25", - "last_updated": "2025-06-25", + "release_date": "2026-04-16", + "last_updated": "2026-04-16", "type": "chat" }, { - "id": "bytedance/seed-2-0-pro-260328", - "name": "Seed 2.0 Pro (260328) (ByteDance)", - "display_name": "Seed 2.0 Pro (260328) (ByteDance)", + "id": "gpt-6-sol", + "name": "GPT-6 Sol", + "display_name": "GPT-6 Sol", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 131072 + "context": 1050000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -100252,36 +101091,41 @@ "extra_capabilities": { "reasoning": { "effort_options": [ - "minimal", + "none", "low", "medium", - "high" + "high", + "xhigh", + "max" ] } }, "attachment": true, "open_weights": false, - "release_date": "2026-03-28", - "last_updated": "2026-03-28", + "knowledge": "2026-04-20", + "release_date": "2026-09-22", + "last_updated": "2026-09-22", "type": "chat" }, { - "id": "bytedance/glm-5.2", - "name": "GLM-5.2 (ByteDance)", - "display_name": "GLM-5.2 (ByteDance)", + "id": "gpt-5.6-terra", + "name": "GPT-5.6 Terra", + "display_name": "GPT-5.6 Terra", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1024000, + "context": 1050000, "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -100290,44 +101134,50 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], + "default_enabled": true, + "mode": "effort", + "effort": "medium", "effort_options": [ "none", - "minimal", "low", "medium", "high", "xhigh", "max" - ] + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-06-13", - "last_updated": "2026-06-13", + "attachment": true, + "open_weights": false, + "knowledge": "2026-02-16", + "release_date": "2026-07-09", + "last_updated": "2026-07-09", "type": "chat" }, { - "id": "minimax/minimax-text-01", - "name": "MiniMax Text 01 (MiniMax)", - "display_name": "MiniMax Text 01 (MiniMax)", + "id": "gpt-5.3-codex", + "name": "GPT-5.3 Codex", + "display_name": "GPT-5.3 Codex", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 400000, + "output": 128000 }, "temperature": true, "tool_call": true, @@ -100335,29 +101185,53 @@ "supported": true, "default": true }, - "attachment": false, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", + "effort_options": [ + "low", + "medium", + "high", + "xhigh" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" + } + }, + "attachment": true, "open_weights": false, - "release_date": "2025-01-15", - "last_updated": "2025-01-15", + "knowledge": "2025-08-31", + "release_date": "2026-02-05", + "last_updated": "2026-02-05", "type": "chat" }, { - "id": "minimax/minimax-m2.1", - "name": "MiniMax M2.1 (MiniMax)", - "display_name": "MiniMax M2.1 (MiniMax)", + "id": "gpt-6-astra", + "name": "GPT-6 Astra", + "display_name": "GPT-6 Astra", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 196608, - "output": 131072 + "context": 1050000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -100375,16 +101249,17 @@ ] } }, - "attachment": false, - "open_weights": true, - "release_date": "2025-12-23", - "last_updated": "2025-12-23", + "attachment": true, + "open_weights": false, + "knowledge": "2026-04-30", + "release_date": "2026-09-04", + "last_updated": "2026-09-04", "type": "chat" }, { - "id": "minimax/minimax-m2.1-lightning", - "name": "MiniMax M2.1 Lightning (MiniMax)", - "display_name": "MiniMax M2.1 Lightning (MiniMax)", + "id": "north-mini-code-free", + "name": "North Mini Code Free", + "display_name": "North Mini Code Free", "modalities": { "input": [ "text" @@ -100394,8 +101269,8 @@ ] }, "limit": { - "context": 196608, - "output": 131072 + "context": 256000, + "output": 64000 }, "temperature": true, "tool_call": true, @@ -100405,84 +101280,96 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" + "none", + "high" ] } }, "attachment": false, - "open_weights": false, - "release_date": "2025-12-23", - "last_updated": "2025-12-23", + "open_weights": true, + "knowledge": "2025-09-23", + "release_date": "2026-06-09", + "last_updated": "2026-06-09", "type": "chat" }, { - "id": "minimax/minimax-m3", - "name": "MiniMax M3 (MiniMax)", - "display_name": "MiniMax M3 (MiniMax)", + "id": "gpt-5.1-codex", + "name": "GPT-5.1 Codex", + "display_name": "GPT-5.1 Codex", "modalities": { "input": [ "text", - "image", - "video" + "image" ], "output": [ "text" ] }, "limit": { - "context": 512000, - "output": 131072 + "context": 400000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": false, + "mode": "effort", + "effort": "none", "effort_options": [ "none", - "minimal", "low", "medium", - "high", - "xhigh", - "max" - ] + "high" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, "attachment": true, - "open_weights": true, - "release_date": "2026-06-01", - "last_updated": "2026-06-01", + "open_weights": false, + "knowledge": "2024-09-30", + "release_date": "2025-11-13", + "last_updated": "2025-11-13", "type": "chat" }, { - "id": "minimax/minimax-m3.1-flash", - "name": "MiniMax M3.1 Flash (MiniMax)", - "display_name": "MiniMax M3.1 Flash (MiniMax)", + "id": "claude-haiku-5-5", + "name": "Claude Haiku 5.5", + "display_name": "Claude Haiku 5.5", "modalities": { "input": [ "text", "image", - "video" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 524288 + "context": 1000000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -100501,25 +101388,27 @@ }, "attachment": true, "open_weights": false, - "release_date": "2026-10-19", - "last_updated": "2026-10-19", + "knowledge": "2026-06", + "release_date": "2026-10-07", + "last_updated": "2026-10-07", "type": "chat" }, { - "id": "minimax/minimax-m2", - "name": "MiniMax M2 (MiniMax)", - "display_name": "MiniMax M2 (MiniMax)", + "id": "deepseek-v4.1-flash-free", + "name": "DeepSeek V4.1 Flash Free", + "display_name": "DeepSeek V4.1 Flash Free", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 196608, - "output": 131072 + "context": 200000, + "output": 32000 }, "temperature": true, "tool_call": true, @@ -100529,25 +101418,31 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ "low", - "medium", "high", - "xhigh", "max" ] } }, - "attachment": false, + "attachment": true, "open_weights": true, - "release_date": "2025-10-27", - "last_updated": "2025-10-27", + "knowledge": "2025-05", + "release_date": "2026-09-10", + "last_updated": "2026-09-10", "type": "chat" }, { - "id": "minimax/minimax-m2.5", - "name": "MiniMax M2.5 (MiniMax)", - "display_name": "MiniMax M2.5 (MiniMax)", + "id": "ling-3.0-flash-free", + "name": "Ling-3.0-flash Free", + "display_name": "Ling-3.0-flash Free", "modalities": { "input": [ "text" @@ -100557,8 +101452,8 @@ ] }, "limit": { - "context": 204800, - "output": 131100 + "context": 262144, + "output": 32768 }, "temperature": true, "tool_call": true, @@ -100568,26 +101463,23 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, "effort_options": [ "low", "medium", - "high", - "xhigh", - "max" + "high" ] } }, "attachment": false, "open_weights": true, - "release_date": "2026-02-12", - "last_updated": "2026-02-12", + "release_date": "2026-07-23", + "last_updated": "2026-07-23", "type": "chat" }, { - "id": "minimax/minimax-m2.7", - "name": "MiniMax M2.7 (MiniMax)", - "display_name": "MiniMax M2.7 (MiniMax)", + "id": "ling-3.0-flash-fin-free", + "name": "Ling 3.0 Flash Fin Free", + "display_name": "Ling 3.0 Flash Fin Free", "modalities": { "input": [ "text" @@ -100597,8 +101489,8 @@ ] }, "limit": { - "context": 204800, - "output": 131100 + "context": 262144, + "output": 32768 }, "temperature": true, "tool_call": true, @@ -100606,47 +101498,30 @@ "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" - ] - } - }, "attachment": false, "open_weights": true, - "release_date": "2026-03-18", - "last_updated": "2026-03-18", + "release_date": "2026-08-27", + "last_updated": "2026-09-03", "type": "chat" }, { - "id": "minimax/minimax-m2.7-highspeed", - "name": "MiniMax M2.7 Highspeed (MiniMax)", - "display_name": "MiniMax M2.7 Highspeed (MiniMax)", + "id": "space-bunny-free", + "name": "Space Bunny Free", + "display_name": "Space Bunny Free", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 204800, - "output": 131100 + "context": 1048576, + "output": 524288 }, - "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -100670,27 +101545,30 @@ ] } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-03-18", - "last_updated": "2026-03-18", + "attachment": true, + "open_weights": false, + "release_date": "2026-09-23", + "last_updated": "2026-09-23", "type": "chat" }, { - "id": "minimax/minimax-m2.5-highspeed", - "name": "MiniMax M2.5 Highspeed (MiniMax)", - "display_name": "MiniMax M2.5 Highspeed (MiniMax)", + "id": "mimo-v2-omni-free", + "name": "MiMo V2 Omni Free", + "display_name": "MiMo V2 Omni Free", "modalities": { "input": [ - "text" + "text", + "image", + "audio", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 204800, - "output": 131100 + "context": 262144, + "output": 64000 }, "temperature": true, "tool_call": true, @@ -100701,31 +101579,29 @@ "extra_capabilities": { "reasoning": { "supported": true, - "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-02-13", - "last_updated": "2026-02-13", + "attachment": true, + "open_weights": false, + "knowledge": "2024-12", + "release_date": "2026-03-18", + "last_updated": "2026-03-18", "type": "chat" }, { - "id": "quartz/gemini-3.1-pro-preview", - "name": "Gemini 3.1 Pro (Preview) (Quartz)", - "display_name": "Gemini 3.1 Pro (Preview) (Quartz)", + "id": "gpt-5.2-codex", + "name": "GPT-5.2 Codex", + "display_name": "GPT-5.2 Codex", "modalities": { "input": [ "text", "image", - "video", - "audio", "pdf" ], "output": [ @@ -100733,10 +101609,10 @@ ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 400000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -100746,89 +101622,101 @@ "reasoning": { "supported": true, "default_enabled": true, - "mode": "level", - "level": "high", - "level_options": [ + "mode": "effort", + "effort": "medium", + "effort_options": [ + "low", + "medium", + "high", + "xhigh" + ], + "verbosity": "medium", + "verbosity_options": [ "low", + "medium", "high" ], - "summaries": true, - "visibility": "summary", - "continuation": [ - "thought_signatures" - ] + "visibility": "hidden" } }, "attachment": true, "open_weights": false, - "knowledge": "2025-01", - "release_date": "2026-02-19", - "last_updated": "2026-02-19", + "knowledge": "2025-08-31", + "release_date": "2025-12-11", + "last_updated": "2025-12-11", "type": "chat" }, { - "id": "reka-ai/glm-5.3-flash", - "name": "GLM-5.3 Flash (reka-ai)", - "display_name": "GLM-5.3 Flash (reka-ai)", + "id": "gpt-5.2", + "name": "GPT-5.2", + "display_name": "GPT-5.2", "modalities": { "input": [ "text", - "image", - "video", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 262144 + "context": 400000, + "output": 128000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": false, + "mode": "effort", + "effort": "none", "effort_options": [ "none", - "minimal", "low", "medium", "high", - "xhigh", - "max" - ] + "xhigh" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, "attachment": true, - "open_weights": true, - "release_date": "2026-08-26", - "last_updated": "2026-08-26", + "open_weights": false, + "knowledge": "2025-08-31", + "release_date": "2025-12-11", + "last_updated": "2025-12-11", "type": "chat" }, { - "id": "reka-ai/deepseek-v4.1-flash-1017", - "name": "DeepSeek V4.1 Flash 1017 (reka-ai)", - "display_name": "DeepSeek V4.1 Flash 1017 (reka-ai)", + "id": "gpt-5.4-pro", + "name": "GPT-5.4 Pro", + "display_name": "GPT-5.4 Pro", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 393216 + "context": 1050000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -100836,42 +101724,50 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "high", "effort_options": [ - "none", - "minimal", - "low", "medium", "high", - "xhigh", - "max" - ] + "xhigh" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, "attachment": true, - "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-10-17", - "last_updated": "2026-10-17", + "open_weights": false, + "knowledge": "2025-08-31", + "release_date": "2026-03-05", + "last_updated": "2026-03-05", "type": "chat" }, { - "id": "reka-ai/deepseek-v4.1-flash", - "name": "DeepSeek V4.1 Flash (reka-ai)", - "display_name": "DeepSeek V4.1 Flash (reka-ai)", + "id": "kimi-k2.7-code", + "name": "Kimi K2.7 Code", + "display_name": "Kimi K2.7 Code", "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 393216 + "context": 262144, + "output": 262144 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -100880,28 +101776,25 @@ "extra_capabilities": { "reasoning": { "supported": true, - "effort_options": [ - "none", - "minimal", - "low", - "medium", - "high", - "xhigh", - "max" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, "attachment": true, "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-09-10", - "last_updated": "2026-09-10", + "knowledge": "2025-01", + "release_date": "2026-06-12", + "last_updated": "2026-06-12", "type": "chat" }, { - "id": "reka-ai/glm-5.3", - "name": "GLM-5.3 (reka-ai)", - "display_name": "GLM-5.3 (reka-ai)", + "id": "cat-jam-free", + "name": "Cat Jam Free", + "display_name": "Cat Jam Free", "modalities": { "input": [ "text" @@ -100912,7 +101805,7 @@ }, "limit": { "context": 1048576, - "output": 262144 + "output": 131072 }, "temperature": true, "tool_call": true, @@ -100923,9 +101816,13 @@ "extra_capabilities": { "reasoning": { "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ - "none", - "minimal", "low", "medium", "high", @@ -100935,43 +101832,57 @@ } }, "attachment": false, - "open_weights": true, - "release_date": "2026-08-14", - "last_updated": "2026-08-14", + "open_weights": false, + "release_date": "2026-10-12", + "last_updated": "2026-10-12", "type": "chat" }, { - "id": "reka-ai/reka-edge-2603", - "name": "Reka Edge 2603 (reka-ai)", - "display_name": "Reka Edge 2603 (reka-ai)", + "id": "gpt-6.1-sol", + "name": "GPT-6.1 Sol", + "display_name": "GPT-6.1 Sol", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 16384, - "output": 16384 + "context": 1050000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" + ] + } }, "attachment": true, "open_weights": false, - "release_date": "2026-10-09", - "last_updated": "2026-10-09", + "knowledge": "2026-04-30", + "release_date": "2026-09-29", + "last_updated": "2026-09-29", "type": "chat" }, { - "id": "aws-bedrock/llama-3.1-70b-instruct", - "name": "Llama 3.1 70B Instruct (AWS Bedrock)", - "display_name": "Llama 3.1 70B Instruct (AWS Bedrock)", + "id": "kimi-k2-thinking", + "name": "Kimi K2 Thinking", + "display_name": "Kimi K2 Thinking", "modalities": { "input": [ "text" @@ -100981,39 +101892,54 @@ ] }, "limit": { - "context": 128000, - "output": 2048 + "context": 262144, + "output": 262144 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } }, "attachment": false, "open_weights": true, - "knowledge": "2023-12", - "release_date": "2024-07-23", - "last_updated": "2024-07-23", + "knowledge": "2024-08", + "release_date": "2025-11-06", + "last_updated": "2025-11-06", "type": "chat" }, { - "id": "aws-bedrock/grok-4-6", - "name": "Grok 4.6 (AWS Bedrock)", - "display_name": "Grok 4.6 (AWS Bedrock)", + "id": "gemini-4-argon", + "name": "Gemini 4 Argon", + "display_name": "Gemini 4 Argon", "modalities": { "input": [ "text", - "image" + "image", + "video", + "audio", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 500000, - "output": 500000 + "context": 1048576, + "output": 65536 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -101024,22 +101950,21 @@ "effort_options": [ "low", "medium", - "high", - "xhigh" + "high" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-02-01", - "release_date": "2026-08-12", - "last_updated": "2026-08-12", + "knowledge": "2026-03", + "release_date": "2026-10-13", + "last_updated": "2026-10-13", "type": "chat" }, { - "id": "aws-bedrock/claude-sonnet-5-5", - "name": "Claude Sonnet 5.5 (AWS Bedrock)", - "display_name": "Claude Sonnet 5.5 (AWS Bedrock)", + "id": "claude-opus-4-7", + "name": "Claude Opus 4.7", + "display_name": "Claude Opus 4.7", "modalities": { "input": [ "text", @@ -101058,34 +101983,51 @@ "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": false, + "mode": "effort", + "effort": "high", "effort_options": [ "low", "medium", "high", "xhigh", "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "omitted", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Claude Opus 4.7 and newer Opus models require thinking.type = \"adaptive\" to enable thinking explicitly.", + "Manual budget_tokens requests return 400 on Claude Opus 4.7 and newer adaptive-only Opus models.", + "task_budget is separate from thinking control and should not be treated as a thinking budget." ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-06", - "release_date": "2026-09-28", - "last_updated": "2026-09-28", + "knowledge": "2026-01-31", + "release_date": "2026-04-16", + "last_updated": "2026-04-16", "type": "chat" }, { - "id": "aws-bedrock/claude-haiku-5-5", - "name": "Claude Haiku 5.5 (AWS Bedrock)", - "display_name": "Claude Haiku 5.5 (AWS Bedrock)", + "id": "gemini-3.1-pro", + "name": "Gemini 3.1 Pro Preview", + "display_name": "Gemini 3.1 Pro Preview", "modalities": { "input": [ "text", "image", + "video", + "audio", "pdf" ], "output": [ @@ -101093,10 +102035,10 @@ ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 1048576, + "output": 65536 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -101104,40 +102046,44 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "none", + "supported": true, + "default_enabled": true, + "mode": "level", + "level": "high", + "level_options": [ "low", - "medium", - "high", - "xhigh", - "max" + "high" + ], + "summaries": true, + "visibility": "summary", + "continuation": [ + "thought_signatures" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-06", - "release_date": "2026-10-07", - "last_updated": "2026-10-07", + "knowledge": "2025-01", + "release_date": "2026-02-19", + "last_updated": "2026-02-19", "type": "chat" }, { - "id": "aws-bedrock/kimi-k3", - "name": "Kimi K3 (AWS Bedrock)", - "display_name": "Kimi K3 (AWS Bedrock)", + "id": "gpt-5-codex", + "name": "GPT-5 Codex", + "display_name": "GPT-5 Codex", "modalities": { "input": [ "text", - "image", - "video" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 1048576 + "context": 400000, + "output": 128000 }, "temperature": false, "tool_call": true, @@ -101148,41 +102094,48 @@ "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", "effort_options": [ - "none", + "minimal", "low", "medium", - "high", - "xhigh", - "max" - ] + "high" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, "attachment": true, - "open_weights": true, - "release_date": "2026-07-16", - "last_updated": "2026-07-16", + "open_weights": false, + "knowledge": "2024-09-30", + "release_date": "2025-09-15", + "last_updated": "2025-09-15", "type": "chat" }, { - "id": "aws-bedrock/claude-opus-5", - "name": "Claude Opus 5 (AWS Bedrock)", - "display_name": "Claude Opus 5 (AWS Bedrock)", + "id": "flint-40b-free", + "name": "Flint 40B Free", + "display_name": "Flint 40B Free", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 262144, + "output": 65536 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -101191,8 +102144,13 @@ "extra_capabilities": { "reasoning": { "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ - "none", "low", "medium", "high", @@ -101201,17 +102159,67 @@ ] } }, + "attachment": false, + "open_weights": true, + "knowledge": "2026-06", + "release_date": "2026-10-21", + "last_updated": "2026-10-21", + "type": "chat" + }, + { + "id": "gemini-3-pro", + "name": "Gemini 3 Pro", + "display_name": "Gemini 3 Pro", + "modalities": { + "input": [ + "text", + "image", + "video", + "audio", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1048576, + "output": 65536 + }, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "level", + "level": "high", + "level_options": [ + "low", + "high" + ], + "summaries": true, + "visibility": "summary", + "continuation": [ + "thought_signatures" + ] + } + }, "attachment": true, "open_weights": false, - "knowledge": "2026-05", - "release_date": "2026-07-24", - "last_updated": "2026-07-24", + "knowledge": "2025-01", + "release_date": "2025-11-18", + "last_updated": "2025-11-18", "type": "chat" }, { - "id": "aws-bedrock/claude-opus-4-7", - "name": "Claude Opus 4.7 (AWS Bedrock)", - "display_name": "Claude Opus 4.7 (AWS Bedrock)", + "id": "claude-haiku-4-5", + "name": "Claude Haiku 4.5", + "display_name": "Claude Haiku 4.5", "modalities": { "input": [ "text", @@ -101223,10 +102231,10 @@ ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 200000, + "output": 64000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -101236,39 +102244,34 @@ "reasoning": { "supported": true, "default_enabled": false, - "mode": "effort", - "effort": "high", - "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" - ], + "mode": "budget", + "budget": { + "min": 1024, + "unit": "tokens" + }, "interleaved": true, "summaries": true, - "visibility": "omitted", + "visibility": "summary", "continuation": [ "thinking_blocks" ], "notes": [ - "Claude Opus 4.7 and newer Opus models require thinking.type = \"adaptive\" to enable thinking explicitly.", - "Manual budget_tokens requests return 400 on Claude Opus 4.7 and newer adaptive-only Opus models.", - "task_budget is separate from thinking control and should not be treated as a thinking budget." + "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", + "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-01-31", - "release_date": "2026-04-16", - "last_updated": "2026-04-16", + "knowledge": "2025-02-28", + "release_date": "2025-10-15", + "last_updated": "2025-10-15", "type": "chat" }, { - "id": "aws-bedrock/claude-opus-5-5", - "name": "Claude Opus 5.5 (AWS Bedrock)", - "display_name": "Claude Opus 5.5 (AWS Bedrock)", + "id": "grok-4.7", + "name": "Grok 4.7", + "display_name": "Grok 4.7", "modalities": { "input": [ "text", @@ -101280,10 +102283,10 @@ ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 500000, + "output": 500000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -101295,22 +102298,21 @@ "low", "medium", "high", - "xhigh", - "max" + "xhigh" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-06", - "release_date": "2026-09-22", - "last_updated": "2026-09-22", + "knowledge": "2026-05", + "release_date": "2026-09-21", + "last_updated": "2026-09-21", "type": "chat" }, { - "id": "aws-bedrock/claude-sonnet-5", - "name": "Claude Sonnet 5 (AWS Bedrock)", - "display_name": "Claude Sonnet 5 (AWS Bedrock)", + "id": "claude-sonnet-5", + "name": "Claude Sonnet 5", + "display_name": "Claude Sonnet 5", "modalities": { "input": [ "text", @@ -101335,7 +102337,6 @@ "reasoning": { "supported": true, "effort_options": [ - "none", "low", "medium", "high", @@ -101352,14 +102353,14 @@ "type": "chat" }, { - "id": "aws-bedrock/claude-sonnet-4-6", - "name": "Claude Sonnet 4.6 (AWS Bedrock)", - "display_name": "Claude Sonnet 4.6 (AWS Bedrock)", + "id": "qwen3.8-flash", + "name": "Qwen3.8 Flash", + "display_name": "Qwen3.8 Flash", "modalities": { "input": [ "text", "image", - "pdf" + "video" ], "output": [ "text" @@ -101367,68 +102368,89 @@ }, "limit": { "context": 1000000, - "output": 64000 + "output": 131072 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "mixed", - "budget": { - "min": 1024, - "unit": "tokens" - }, - "effort": "high", "effort_options": [ "low", "medium", - "high", - "max" - ], + "xhigh" + ] + } + }, + "attachment": true, + "open_weights": false, + "release_date": "2026-08-26", + "last_updated": "2026-08-26", + "type": "chat" + }, + { + "id": "qwen3.5-plus", + "name": "Qwen3.5 Plus", + "display_name": "Qwen3.5 Plus", + "modalities": { + "input": [ + "text", + "image", + "video" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 262144, + "output": 65536 + }, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ "thinking_blocks" - ], - "notes": [ - "Anthropic recommends adaptive thinking with effort for Claude 4.6; budget_tokens remains a deprecated compatibility path.", - "Anthropic API defaults effort to high; lower effort levels should be chosen per workload." ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-02-17", - "last_updated": "2026-03-13", + "knowledge": "2025-04", + "release_date": "2026-02-16", + "last_updated": "2026-02-16", "type": "chat" }, { - "id": "aws-bedrock/grok-4-7", - "name": "Grok 4.7 (AWS Bedrock)", - "display_name": "Grok 4.7 (AWS Bedrock)", + "id": "gpt-5.3-codex-spark", + "name": "GPT-5.3 Codex Spark", + "display_name": "GPT-5.3 Codex Spark", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 524288, - "output": 524288 + "context": 128000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -101436,38 +102458,48 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", "effort_options": [ "low", "medium", "high", "xhigh" - ] + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, - "attachment": true, + "attachment": false, "open_weights": false, - "knowledge": "2026-05", - "release_date": "2026-09-21", - "last_updated": "2026-09-21", + "knowledge": "2025-08-31", + "release_date": "2026-02-05", + "last_updated": "2026-02-05", "type": "chat" }, { - "id": "aws-bedrock/grok-4-3", - "name": "Grok 4.3 (AWS Bedrock)", - "display_name": "Grok 4.3 (AWS Bedrock)", + "id": "grok-4.5", + "name": "Grok 4.5", + "display_name": "Grok 4.5", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 500000, + "output": 500000 }, "temperature": true, "tool_call": true, @@ -101477,8 +102509,8 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, "effort_options": [ - "none", "low", "medium", "high" @@ -101487,73 +102519,48 @@ }, "attachment": true, "open_weights": false, - "release_date": "2026-04-17", - "last_updated": "2026-04-17", + "release_date": "2026-07-08", + "last_updated": "2026-07-08", "type": "chat" }, { - "id": "aws-bedrock/claude-opus-4-6", - "name": "Claude Opus 4.6 (AWS Bedrock)", - "display_name": "Claude Opus 4.6 (AWS Bedrock)", + "id": "minimax-m2.5-free", + "name": "MiniMax-M2.5 Free", + "display_name": "MiniMax-M2.5 Free", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 204800, + "output": 131072 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "mixed", - "budget": { - "min": 1024, - "unit": "tokens" - }, - "effort": "high", - "effort_options": [ - "low", - "medium", - "high", - "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Anthropic recommends adaptive thinking with effort for Claude 4.6; budget_tokens remains a deprecated compatibility path.", - "Anthropic API defaults effort to high; lower effort levels should be chosen per workload." - ] + "supported": true } }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-05-31", - "release_date": "2026-02-05", - "last_updated": "2026-03-13", + "attachment": false, + "open_weights": true, + "knowledge": "2025-01", + "release_date": "2026-02-12", + "last_updated": "2026-02-12", "type": "chat" }, { - "id": "aws-bedrock/claude-sonnet-4-5-20250929", - "name": "Claude Sonnet 4.5 (2025-09-29) (AWS Bedrock)", - "display_name": "Claude Sonnet 4.5 (2025-09-29) (AWS Bedrock)", + "id": "claude-fable-5", + "name": "Claude Fable 5", + "display_name": "Claude Fable 5", "modalities": { "input": [ "text", @@ -101565,47 +102572,52 @@ ] }, "limit": { - "context": 200000, - "output": 8192 + "context": 1000000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "budget", - "budget": { - "min": 1024, - "unit": "tokens" - }, + "default_enabled": true, + "mode": "effort", + "effort": "high", + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" + ], "interleaved": true, "summaries": true, - "visibility": "summary", + "visibility": "omitted", "continuation": [ "thinking_blocks" ], "notes": [ - "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", - "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." + "Adaptive thinking is always on for Claude Fable 5 and Claude Mythos 5; thinking.type = \"disabled\" is rejected.", + "Manual budget_tokens requests return 400 on Claude Fable 5 and Claude Mythos 5.", + "thinking.display defaults to omitted; set display to summarized to receive readable thinking summaries." ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-07-31", - "release_date": "2025-09-29", - "last_updated": "2025-09-29", + "knowledge": "2026-01-31", + "release_date": "2026-06-09", + "last_updated": "2026-06-09", "type": "chat" }, { - "id": "aws-bedrock/llama-4-maverick-17b-instruct", - "name": "Llama 4 Maverick 17B Instruct (AWS Bedrock)", - "display_name": "Llama 4 Maverick 17B Instruct (AWS Bedrock)", + "id": "pulse-alpha-free", + "name": "Pulse Alpha Free", + "display_name": "Pulse Alpha Free", "modalities": { "input": [ "text", @@ -101616,238 +102628,200 @@ ] }, "limit": { - "context": 8192, - "output": 2048 + "context": 1048576, + "output": 131072 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "low", + "high", + "max" + ] + } }, "attachment": true, - "open_weights": true, - "knowledge": "2024-08", - "release_date": "2025-04-05", - "last_updated": "2025-04-05", + "open_weights": false, + "release_date": "2026-11-02", + "last_updated": "2026-11-02", "type": "chat" }, { - "id": "aws-bedrock/claude-haiku-4-5", - "name": "Claude Haiku 4.5 (AWS Bedrock)", - "display_name": "Claude Haiku 4.5 (AWS Bedrock)", + "id": "glm-5-free", + "name": "GLM-5 Free", + "display_name": "GLM-5 Free", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 64000 + "context": 204800, + "output": 131072 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "budget", - "budget": { - "min": 1024, - "unit": "tokens" - }, "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ "thinking_blocks" - ], - "notes": [ - "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", - "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-02-28", - "release_date": "2025-10-15", - "last_updated": "2025-10-15", + "attachment": false, + "open_weights": true, + "knowledge": "2025-04", + "release_date": "2026-02-12", + "last_updated": "2026-02-12", "type": "chat" }, { - "id": "aws-bedrock/claude-haiku-4-5-20251001", - "name": "Claude Haiku 4.5 (2025-10-01) (AWS Bedrock)", - "display_name": "Claude Haiku 4.5 (2025-10-01) (AWS Bedrock)", + "id": "grok-code", + "name": "Grok Code Fast 1", + "display_name": "Grok Code Fast 1", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 64000 + "context": 256000, + "output": 256000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "budget", - "budget": { - "min": 1024, - "unit": "tokens" - }, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", - "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." - ] - } + "default": true }, - "attachment": true, + "attachment": false, "open_weights": false, - "knowledge": "2025-02-28", - "release_date": "2025-10-15", - "last_updated": "2025-10-15", + "release_date": "2025-09-02", + "last_updated": "2025-09-02", "type": "chat" }, { - "id": "aws-bedrock/claude-opus-4-8", - "name": "Claude Opus 4.8 (AWS Bedrock)", - "display_name": "Claude Opus 4.8 (AWS Bedrock)", + "id": "gpt-5-nano", + "name": "GPT-5 Nano", + "display_name": "GPT-5 Nano", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, + "context": 400000, "output": 128000 }, "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, + "default_enabled": true, "mode": "effort", - "effort": "high", + "effort": "medium", "effort_options": [ + "minimal", "low", "medium", - "high", - "xhigh", - "max" + "high" ], - "interleaved": true, - "summaries": true, - "visibility": "omitted", - "continuation": [ - "thinking_blocks" + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" ], - "notes": [ - "Claude Opus 4.7 and newer Opus models require thinking.type = \"adaptive\" to enable thinking explicitly.", - "Manual budget_tokens requests return 400 on Claude Opus 4.7 and newer adaptive-only Opus models.", - "task_budget is separate from thinking control and should not be treated as a thinking budget." - ] + "visibility": "hidden" } }, "attachment": true, "open_weights": false, - "knowledge": "2026-01", - "release_date": "2026-05-28", - "last_updated": "2026-05-28", + "knowledge": "2024-05-30", + "release_date": "2025-08-07", + "last_updated": "2025-08-07", "type": "chat" }, { - "id": "aws-bedrock/claude-sonnet-4-5", - "name": "Claude Sonnet 4.5 (AWS Bedrock)", - "display_name": "Claude Sonnet 4.5 (AWS Bedrock)", + "id": "beam-free", + "name": "Beam Free", + "display_name": "Beam Free", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 8192 + "context": 1048576, + "output": 131072 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "budget", - "budget": { - "min": 1024, - "unit": "tokens" - }, "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ "thinking_blocks" ], - "notes": [ - "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", - "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-07-31", - "release_date": "2025-09-29", - "last_updated": "2025-09-29", + "attachment": false, + "open_weights": true, + "knowledge": "2026-06-30", + "release_date": "2026-10-05", + "last_updated": "2026-10-16", "type": "chat" }, { - "id": "aws-bedrock/claude-opus-4-5-20251101", - "name": "Claude Opus 4.5 (AWS Bedrock)", - "display_name": "Claude Opus 4.5 (AWS Bedrock)", + "id": "claude-opus-4-1", + "name": "Claude Opus 4.1", + "display_name": "Claude Opus 4.1", "modalities": { "input": [ "text", @@ -101872,17 +102846,11 @@ "reasoning": { "supported": true, "default_enabled": false, - "mode": "mixed", + "mode": "budget", "budget": { "min": 1024, "unit": "tokens" }, - "effort": "high", - "effort_options": [ - "low", - "medium", - "high" - ], "interleaved": true, "summaries": true, "visibility": "summary", @@ -101890,22 +102858,22 @@ "thinking_blocks" ], "notes": [ - "Claude Opus 4.5 uses manual thinking.type = \"enabled\" with budget_tokens; effort can be used alongside the thinking budget.", - "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header." + "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", + "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-05", - "release_date": "2025-11-24", - "last_updated": "2025-11-24", + "knowledge": "2025-03-31", + "release_date": "2025-08-05", + "last_updated": "2025-08-05", "type": "chat" }, { - "id": "aws-bedrock/claude-fable-5", - "name": "Claude Fable 5 (AWS Bedrock)", - "display_name": "Claude Fable 5 (AWS Bedrock)", + "id": "claude-sonnet-4-5", + "name": "Claude Sonnet 4.5", + "display_name": "Claude Sonnet 4.5", "modalities": { "input": [ "text", @@ -101918,56 +102886,51 @@ }, "limit": { "context": 1000000, - "output": 128000 + "output": 64000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "high", - "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" - ], + "default_enabled": false, + "mode": "budget", + "budget": { + "min": 1024, + "unit": "tokens" + }, "interleaved": true, "summaries": true, - "visibility": "omitted", + "visibility": "summary", "continuation": [ "thinking_blocks" ], "notes": [ - "Adaptive thinking is always on for Claude Fable 5 and Claude Mythos 5; thinking.type = \"disabled\" is rejected.", - "Manual budget_tokens requests return 400 on Claude Fable 5 and Claude Mythos 5.", - "thinking.display defaults to omitted; set display to summarized to receive readable thinking summaries." + "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", + "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-01-31", - "release_date": "2026-06-09", - "last_updated": "2026-06-09", + "knowledge": "2025-07-31", + "release_date": "2025-09-29", + "last_updated": "2025-09-29", "type": "chat" }, { - "id": "aws-bedrock/claude-fable-5-1", - "name": "Claude Fable 5.1 (AWS Bedrock)", - "display_name": "Claude Fable 5.1 (AWS Bedrock)", + "id": "glm-5.3-flash", + "name": "GLM-5.3-Flash", + "display_name": "GLM-5.3-Flash", "modalities": { "input": [ "text", "image", - "pdf" + "video" ], "output": [ "text" @@ -101975,9 +102938,9 @@ }, "limit": { "context": 1000000, - "output": 128000 + "output": 131072 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -101986,74 +102949,75 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "high", - "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" - ], "interleaved": true, "summaries": true, - "visibility": "omitted", + "visibility": "summary", "continuation": [ "thinking_blocks" ], - "notes": [ - "Adaptive thinking is always on for Claude Fable 5 and Claude Mythos 5; thinking.type = \"disabled\" is rejected.", - "Manual budget_tokens requests return 400 on Claude Fable 5 and Claude Mythos 5.", - "thinking.display defaults to omitted; set display to summarized to receive readable thinking summaries." + "effort_options": [ + "low", + "high", + "max" ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2026-06", - "release_date": "2026-09-01", - "last_updated": "2026-09-01", + "open_weights": true, + "release_date": "2026-08-26", + "last_updated": "2026-08-26", "type": "chat" }, { - "id": "aws-bedrock/llama-4-scout-17b-instruct", - "name": "Llama 4 Scout 17B Instruct (AWS Bedrock)", - "display_name": "Llama 4 Scout 17B Instruct (AWS Bedrock)", + "id": "glm-4.7", + "name": "GLM-4.7", + "display_name": "GLM-4.7", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 8192, - "output": 2048 + "context": 204800, + "output": 131072 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": true, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } + }, + "attachment": false, "open_weights": true, - "knowledge": "2024-08", - "release_date": "2025-04-05", - "last_updated": "2025-04-05", + "knowledge": "2025-04", + "release_date": "2025-12-22", + "last_updated": "2025-12-22", "type": "chat" }, { - "id": "consensusprotocol/glm-5.3-flash", - "name": "GLM-5.3 Flash (Consensus Protocol)", - "display_name": "GLM-5.3 Flash (Consensus Protocol)", + "id": "gemini-3.6-flash", + "name": "Gemini 3.6 Flash", + "display_name": "Gemini 3.6 Flash", "modalities": { "input": [ "text", "image", "video", + "audio", "pdf" ], "output": [ @@ -102062,7 +103026,7 @@ }, "limit": { "context": 1048576, - "output": 131072 + "output": 65536 }, "temperature": true, "tool_call": true, @@ -102073,23 +103037,33 @@ "extra_capabilities": { "reasoning": { "supported": true, - "effort_options": [ + "default_enabled": true, + "mode": "level", + "level": "high", + "level_options": [ + "minimal", "low", - "high", - "max" + "medium", + "high" + ], + "summaries": true, + "visibility": "summary", + "continuation": [ + "thought_signatures" ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-08-26", - "last_updated": "2026-08-26", + "open_weights": false, + "knowledge": "2026-03", + "release_date": "2026-07-21", + "last_updated": "2026-07-21", "type": "chat" }, { - "id": "consensusprotocol/deepseek-v4-flash", - "name": "DeepSeek V4 Flash (Consensus Protocol)", - "display_name": "DeepSeek V4 Flash (Consensus Protocol)", + "id": "glm-4.6", + "name": "GLM-4.6", + "display_name": "GLM-4.6", "modalities": { "input": [ "text" @@ -102099,8 +103073,8 @@ ] }, "limit": { - "context": 1000000, - "output": 384000 + "context": 204800, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -102110,48 +103084,35 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "high", - "effort_options": [ - "low", - "high", - "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." - ] + "supported": true } }, "attachment": false, "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-04-24", - "last_updated": "2026-04-24", + "knowledge": "2025-04", + "release_date": "2025-09-30", + "last_updated": "2025-09-30", "type": "chat" }, { - "id": "consensusprotocol/gpt-oss-20b", - "name": "GPT OSS 20B (Consensus Protocol)", - "display_name": "GPT OSS 20B (Consensus Protocol)", + "id": "gemini-3-flash", + "name": "Gemini 3 Flash", + "display_name": "Gemini 3 Flash", "modalities": { "input": [ - "text" + "text", + "image", + "video", + "audio", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 32768 + "context": 1048576, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -102162,37 +103123,48 @@ "extra_capabilities": { "reasoning": { "supported": true, - "effort_options": [ + "default_enabled": true, + "mode": "level", + "level": "high", + "level_options": [ + "minimal", "low", "medium", "high" + ], + "summaries": true, + "visibility": "summary", + "continuation": [ + "thought_signatures" ] } }, - "attachment": false, - "open_weights": true, - "knowledge": "2024-06", - "release_date": "2025-08-05", - "last_updated": "2025-08-05", + "attachment": true, + "open_weights": false, + "knowledge": "2025-01", + "release_date": "2025-12-17", + "last_updated": "2025-12-17", "type": "chat" }, { - "id": "consensusprotocol/mimo-v2.6-flash", - "name": "MiMo V2.6 Flash (Consensus Protocol)", - "display_name": "MiMo V2.6 Flash (Consensus Protocol)", + "id": "gpt-5.5-pro", + "name": "GPT-5.5 Pro", + "display_name": "GPT-5.5 Pro", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 1050000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -102200,27 +103172,25 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, "effort_options": [ - "none", - "minimal", - "low", "medium", "high", - "xhigh", - "max" + "xhigh" ] } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-09-22", - "last_updated": "2026-09-22", + "attachment": true, + "open_weights": false, + "knowledge": "2025-12-01", + "release_date": "2026-04-23", + "last_updated": "2026-04-23", "type": "chat" }, { - "id": "consensusprotocol/gemma-4-31b-it", - "name": "Gemma 4 31B IT (Consensus Protocol)", - "display_name": "Gemma 4 31B IT (Consensus Protocol)", + "id": "glm-4.7-free", + "name": "GLM-4.7 Free", + "display_name": "GLM-4.7 Free", "modalities": { "input": [ "text" @@ -102230,8 +103200,8 @@ ] }, "limit": { - "context": 262144, - "output": 32768 + "context": 204800, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -102242,28 +103212,32 @@ "extra_capabilities": { "reasoning": { "supported": true, - "effort_options": [ - "none", - "high", - "xhigh", - "max" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, "attachment": false, "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-04-02", - "last_updated": "2026-04-02", + "knowledge": "2025-04", + "release_date": "2025-12-22", + "last_updated": "2025-12-22", "type": "chat" }, { - "id": "consensusprotocol/minimax-m3", - "name": "MiniMax M3 (Consensus Protocol)", - "display_name": "MiniMax M3 (Consensus Protocol)", + "id": "gemini-3.8-flash", + "name": "Gemini 3.8 Flash", + "display_name": "Gemini 3.8 Flash", "modalities": { "input": [ - "text" + "text", + "image", + "video", + "audio", + "pdf" ], "output": [ "text" @@ -102271,7 +103245,7 @@ }, "limit": { "context": 1048576, - "output": 512000 + "output": 65536 }, "temperature": true, "tool_call": true, @@ -102282,41 +103256,47 @@ "extra_capabilities": { "reasoning": { "supported": true, - "effort_options": [ - "none", + "default_enabled": true, + "mode": "level", + "level": "high", + "level_options": [ "minimal", "low", "medium", - "high", - "xhigh", - "max" + "high" + ], + "summaries": true, + "visibility": "summary", + "continuation": [ + "thought_signatures" ] } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-06-01", - "last_updated": "2026-06-01", + "attachment": true, + "open_weights": false, + "release_date": "2026-09-02", + "last_updated": "2026-09-02", "type": "chat" }, { - "id": "consensusprotocol/deepseek-v4.1-flash", - "name": "DeepSeek V4.1 Flash (Consensus Protocol)", - "display_name": "DeepSeek V4.1 Flash (Consensus Protocol)", + "id": "claude-opus-5-5", + "name": "Claude Opus 5.5", + "display_name": "Claude Opus 5.5", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 384000 + "context": 1000000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -102324,9 +103304,9 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, "effort_options": [ "low", + "medium", "high", "xhigh", "max" @@ -102334,29 +103314,27 @@ } }, "attachment": true, - "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-09-10", - "last_updated": "2026-09-10", + "open_weights": false, + "knowledge": "2026-06", + "release_date": "2026-09-22", + "last_updated": "2026-09-22", "type": "chat" }, { - "id": "consensusprotocol/Qwen3.8-27B", - "name": "Qwen3.8 27B (Consensus Protocol)", - "display_name": "Qwen3.8 27B (Consensus Protocol)", + "id": "big-pickle", + "name": "Big Pickle", + "display_name": "Big Pickle", "modalities": { "input": [ - "text", - "image", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 262000, - "output": 32768 + "context": 200000, + "output": 32000 }, "temperature": true, "tool_call": true, @@ -102366,28 +103344,30 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "low", - "medium", - "xhigh" + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, - "attachment": true, - "open_weights": true, - "release_date": "2026-08-14", - "last_updated": "2026-08-14", + "attachment": false, + "open_weights": false, + "knowledge": "2025-01", + "release_date": "2025-10-17", + "last_updated": "2025-10-17", "type": "chat" }, { - "id": "cloudflare/glm-5.3-flash", - "name": "GLM-5.3 Flash (Cloudflare)", - "display_name": "GLM-5.3 Flash (Cloudflare)", + "id": "gpt-5.6-luna", + "name": "GPT-5.6 Luna", + "display_name": "GPT-5.6 Luna", "modalities": { "input": [ "text", "image", - "video", "pdf" ], "output": [ @@ -102395,10 +103375,10 @@ ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 1050000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -102407,51 +103387,71 @@ "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", "effort_options": [ + "none", "low", + "medium", "high", + "xhigh", "max" - ] + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, "attachment": true, - "open_weights": true, - "release_date": "2026-08-26", - "last_updated": "2026-08-26", + "open_weights": false, + "knowledge": "2026-02-16", + "release_date": "2026-07-09", + "last_updated": "2026-07-09", "type": "chat" }, { - "id": "cloudflare/deepseek-v4-flash", - "name": "DeepSeek V4 Flash (Cloudflare)", - "display_name": "DeepSeek V4 Flash (Cloudflare)", + "id": "claude-opus-4-5", + "name": "Claude Opus 4.5", + "display_name": "Claude Opus 4.5", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 393216 + "context": 200000, + "output": 64000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", + "default_enabled": false, + "mode": "mixed", + "budget": { + "min": 1024, + "unit": "tokens" + }, "effort": "high", "effort_options": [ "low", - "high", - "max" + "medium", + "high" ], "interleaved": true, "summaries": true, @@ -102460,75 +103460,115 @@ "thinking_blocks" ], "notes": [ - "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." + "Claude Opus 4.5 uses manual thinking.type = \"enabled\" with budget_tokens; effort can be used alongside the thinking budget.", + "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header." ] } }, - "attachment": false, - "open_weights": true, + "attachment": true, + "open_weights": false, "knowledge": "2025-05", - "release_date": "2026-04-24", - "last_updated": "2026-04-24", + "release_date": "2025-11-24", + "last_updated": "2025-11-24", "type": "chat" }, { - "id": "cloudflare/deepseek-v4.1-flash-1017", - "name": "DeepSeek V4.1 Flash 1017 (Cloudflare)", - "display_name": "DeepSeek V4.1 Flash 1017 (Cloudflare)", + "id": "claude-3-5-haiku", + "name": "Claude Haiku 3.5", + "display_name": "Claude Haiku 3.5", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 393216 + "context": 200000, + "output": 8192 }, "temperature": true, "tool_call": true, + "reasoning": { + "supported": false + }, + "attachment": true, + "open_weights": false, + "knowledge": "2024-07-31", + "release_date": "2024-10-22", + "last_updated": "2024-10-22", + "type": "chat" + }, + { + "id": "gpt-5", + "name": "GPT-5", + "display_name": "GPT-5", + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 400000, + "output": 128000 + }, + "temperature": false, + "tool_call": true, "reasoning": { "supported": true, "default": true }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", "effort_options": [ - "none", "minimal", "low", "medium", - "high", - "xhigh", - "max" - ] + "high" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, "attachment": true, - "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-10-17", - "last_updated": "2026-10-17", + "open_weights": false, + "knowledge": "2024-09-30", + "release_date": "2025-08-07", + "last_updated": "2025-08-07", "type": "chat" }, { - "id": "cloudflare/deepseek-v4-pro", - "name": "DeepSeek V4 Pro (Cloudflare)", - "display_name": "DeepSeek V4 Pro (Cloudflare)", + "id": "deepseek-v4.1-flash", + "name": "DeepSeek V4.1 Flash", + "display_name": "DeepSeek V4.1 Flash", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 393216 + "context": 1000000, + "output": 384000 }, "temperature": true, "tool_call": true, @@ -102539,36 +103579,30 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "high", - "effort_options": [ - "low", - "high", - "max" - ], "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ "thinking_blocks" ], - "notes": [ - "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." + "effort_options": [ + "low", + "high", + "max" ] } }, - "attachment": false, + "attachment": true, "open_weights": true, "knowledge": "2025-05", - "release_date": "2026-04-24", - "last_updated": "2026-04-24", + "release_date": "2026-09-10", + "last_updated": "2026-09-10", "type": "chat" }, { - "id": "cloudflare/glm-5.3", - "name": "GLM-5.3 (Cloudflare)", - "display_name": "GLM-5.3 (Cloudflare)", + "id": "mimo-v2-pro-free", + "name": "MiMo V2 Pro Free", + "display_name": "MiMo V2 Pro Free", "modalities": { "input": [ "text" @@ -102579,7 +103613,7 @@ }, "limit": { "context": 1048576, - "output": 131072 + "output": 64000 }, "temperature": true, "tool_call": true, @@ -102590,38 +103624,37 @@ "extra_capabilities": { "reasoning": { "supported": true, - "effort_options": [ - "low", - "high", - "max" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, "attachment": false, - "open_weights": true, - "release_date": "2026-08-14", - "last_updated": "2026-08-14", + "open_weights": false, + "knowledge": "2024-12", + "release_date": "2026-03-18", + "last_updated": "2026-03-18", "type": "chat" }, { - "id": "google-vertex/gemini-3-flash-preview", - "name": "Gemini 3 Flash (Preview) (Google Vertex AI)", - "display_name": "Gemini 3 Flash (Preview) (Google Vertex AI)", + "id": "deepseek-v4-flash-vision-exp", + "name": "DeepSeek V4 Flash Vision Exp", + "display_name": "DeepSeek V4 Flash Vision Exp", "modalities": { "input": [ "text", - "image", - "video", - "audio", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 65535 + "context": 1000000, + "output": 384000 }, "temperature": true, "tool_call": true, @@ -102633,47 +103666,47 @@ "reasoning": { "supported": true, "default_enabled": true, - "mode": "level", - "level": "high", - "level_options": [ - "minimal", + "mode": "effort", + "effort": "high", + "effort_options": [ "low", - "medium", - "high" + "high", + "max" ], + "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ - "thought_signatures" + "thinking_blocks" + ], + "notes": [ + "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2025-01", - "release_date": "2025-12-17", - "last_updated": "2025-12-17", + "open_weights": true, + "release_date": "2026-08-21", + "last_updated": "2026-09-01", "type": "chat" }, { - "id": "google-vertex/gemini-3.5-flash-lite", - "name": "Gemini 3.5 Flash Lite (Google Vertex AI)", - "display_name": "Gemini 3.5 Flash Lite (Google Vertex AI)", + "id": "minimax-m3-free", + "name": "MiniMax-M3 Free", + "display_name": "MiniMax-M3 Free", "modalities": { "input": [ "text", "image", - "video", - "audio", - "pdf" + "video" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 200000, + "output": 32000 }, "temperature": true, "tool_call": true, @@ -102683,41 +103716,60 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "effort_options": [ - "minimal", - "low", - "medium", - "high" - ] + "supported": true } }, "attachment": true, + "open_weights": true, + "knowledge": "2025-01", + "release_date": "2026-06-01", + "last_updated": "2026-06-01", + "type": "chat" + }, + { + "id": "ling-3.0-tiny-free", + "name": "Ling-3.0-tiny Free", + "display_name": "Ling-3.0-tiny Free", + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 262144, + "output": 32768 + }, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": true, + "default": true + }, + "attachment": false, "open_weights": false, - "knowledge": "2026-03", - "release_date": "2026-07-21", - "last_updated": "2026-07-21", + "release_date": "2026-08-06", + "last_updated": "2026-08-06", "type": "chat" }, { - "id": "google-vertex/gemini-3.1-flash-lite", - "name": "Gemini 3.1 Flash Lite (Google Vertex AI)", - "display_name": "Gemini 3.1 Flash Lite (Google Vertex AI)", + "id": "mistral-large-4", + "name": "Mistral Large 4", + "display_name": "Mistral Large 4", "modalities": { "input": [ "text", - "image", - "video", - "audio", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 524288, + "output": 262144 }, "temperature": true, "tool_call": true, @@ -102727,41 +103779,35 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, "effort_options": [ - "minimal", - "low", - "medium", + "none", "high" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-01", - "release_date": "2026-05-07", - "last_updated": "2026-05-07", + "release_date": "2026-10-06", + "last_updated": "2026-10-06", "type": "chat" }, { - "id": "google-vertex/gemini-3.7-flash", - "name": "Gemini 3.7 Flash (Google Vertex AI)", - "display_name": "Gemini 3.7 Flash (Google Vertex AI)", + "id": "minimax-m3.1-flash-free", + "name": "MiniMax M3.1 Flash Free", + "display_name": "MiniMax M3.1 Flash Free", "modalities": { "input": [ "text", "image", - "video", - "audio", - "pdf" + "video" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 1000000, + "output": 512000 }, "temperature": true, "tool_call": true, @@ -102772,48 +103818,42 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "level", - "level": "high", - "level_options": [ - "minimal", - "low", - "medium", - "high" - ], + "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ - "thought_signatures" + "thinking_blocks" + ], + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-03", - "release_date": "2026-08-13", - "last_updated": "2026-08-13", + "release_date": "2026-10-19", + "last_updated": "2026-10-19", "type": "chat" }, { - "id": "google-vertex/gemini-3.1-pro-preview", - "name": "Gemini 3.1 Pro (Preview) (Google Vertex AI)", - "display_name": "Gemini 3.1 Pro (Preview) (Google Vertex AI)", + "id": "longcat-2.0-free", + "name": "LongCat-2.0 Free", + "display_name": "LongCat-2.0 Free", "modalities": { "input": [ - "text", - "image", - "video", - "audio", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 1000000, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -102824,46 +103864,35 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "level", - "level": "high", - "level_options": [ - "low", - "high" - ], + "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ - "thought_signatures" + "thinking_blocks" ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-01", - "release_date": "2026-02-19", - "last_updated": "2026-02-19", + "attachment": false, + "open_weights": true, + "release_date": "2026-06-30", + "last_updated": "2026-06-30", "type": "chat" }, { - "id": "google-vertex/gemini-3.8-flash", - "name": "Gemini 3.8 Flash (Google Vertex AI)", - "display_name": "Gemini 3.8 Flash (Google Vertex AI)", + "id": "deepseek-v4-pro", + "name": "DeepSeek V4 Pro", + "display_name": "DeepSeek V4 Pro", "modalities": { "input": [ - "text", - "image", - "video", - "audio", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 1000000, + "output": 384000 }, "temperature": true, "tool_call": true, @@ -102875,37 +103904,39 @@ "reasoning": { "supported": true, "default_enabled": true, - "mode": "level", - "level": "high", - "level_options": [ - "minimal", + "mode": "effort", + "effort": "high", + "effort_options": [ "low", - "medium", - "high" + "high", + "max" ], + "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ - "thought_signatures" + "thinking_blocks" + ], + "notes": [ + "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." ] } }, - "attachment": true, - "open_weights": false, - "release_date": "2026-09-02", - "last_updated": "2026-09-02", + "attachment": false, + "open_weights": true, + "knowledge": "2025-05", + "release_date": "2026-04-24", + "last_updated": "2026-04-24", "type": "chat" }, { - "id": "google-vertex/gemini-4-argon", - "name": "Gemini 4 Argon (Google Vertex AI)", - "display_name": "Gemini 4 Argon (Google Vertex AI)", + "id": "claude-sonnet-5-5", + "name": "Claude Sonnet 5.5", + "display_name": "Claude Sonnet 5.5", "modalities": { "input": [ "text", "image", - "video", - "audio", "pdf" ], "output": [ @@ -102913,8 +103944,8 @@ ] }, "limit": { - "context": 1048576, - "output": 1048576 + "context": 1000000, + "output": 128000 }, "temperature": false, "tool_call": true, @@ -102925,36 +103956,36 @@ "extra_capabilities": { "reasoning": { "effort_options": [ - "high" + "low", + "medium", + "high", + "xhigh", + "max" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-03", - "release_date": "2026-10-13", - "last_updated": "2026-10-13", + "knowledge": "2026-06", + "release_date": "2026-09-28", + "last_updated": "2026-09-28", "type": "chat" }, { - "id": "google-vertex/gemini-3.6-flash", - "name": "Gemini 3.6 Flash (Google Vertex AI)", - "display_name": "Gemini 3.6 Flash (Google Vertex AI)", + "id": "glm-5.3", + "name": "GLM-5.3", + "display_name": "GLM-5.3", "modalities": { "input": [ - "text", - "image", - "video", - "audio", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 1000000, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -102965,48 +103996,40 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "level", - "level": "high", - "level_options": [ - "minimal", - "low", - "medium", - "high" - ], + "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ - "thought_signatures" + "thinking_blocks" + ], + "effort_options": [ + "low", + "high", + "max" ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-03", - "release_date": "2026-07-21", - "last_updated": "2026-07-21", + "attachment": false, + "open_weights": true, + "release_date": "2026-08-14", + "last_updated": "2026-08-14", "type": "chat" }, { - "id": "google-vertex/gemini-3.5-flash", - "name": "Gemini 3.5 Flash (Google Vertex AI)", - "display_name": "Gemini 3.5 Flash (Google Vertex AI)", + "id": "minimax-m2.1-free", + "name": "MiniMax-M2.1 Free", + "display_name": "MiniMax-M2.1 Free", "modalities": { "input": [ - "text", - "image", - "video", - "audio", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 204800, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -103016,39 +104039,24 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "level", - "level": "high", - "level_options": [ - "minimal", - "low", - "medium", - "high" - ], - "summaries": true, - "visibility": "summary", - "continuation": [ - "thought_signatures" - ] + "supported": true } }, - "attachment": true, - "open_weights": false, + "attachment": false, + "open_weights": true, "knowledge": "2025-01", - "release_date": "2026-05-19", - "last_updated": "2026-05-19", + "release_date": "2025-12-23", + "last_updated": "2025-12-23", "type": "chat" }, { - "id": "fireworks/glm-5.3-flash", - "name": "GLM-5.3 Flash (Fireworks AI)", - "display_name": "GLM-5.3 Flash (Fireworks AI)", + "id": "gpt-5.6-sol", + "name": "GPT-5.6 Sol", + "display_name": "GPT-5.6 Sol", "modalities": { "input": [ "text", "image", - "video", "pdf" ], "output": [ @@ -103056,10 +104064,10 @@ ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 1050000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -103068,80 +104076,109 @@ "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", "effort_options": [ + "none", + "low", + "medium", + "high", "xhigh", "max" - ] + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, "attachment": true, - "open_weights": true, - "release_date": "2026-08-26", - "last_updated": "2026-08-26", + "open_weights": false, + "knowledge": "2026-02-16", + "release_date": "2026-07-09", + "last_updated": "2026-07-09", "type": "chat" }, { - "id": "fireworks/kimi-k3", - "name": "Kimi K3 (Fireworks AI)", - "display_name": "Kimi K3 (Fireworks AI)", + "id": "claude-sonnet-4-6", + "name": "Claude Sonnet 4.6", + "display_name": "Claude Sonnet 4.6", "modalities": { "input": [ "text", "image", - "video" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1040384, - "output": 1040384 + "context": 1000000, + "output": 64000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": false, + "mode": "mixed", + "budget": { + "min": 1024, + "unit": "tokens" + }, + "effort": "high", "effort_options": [ - "none", "low", "medium", "high", - "xhigh", "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Anthropic recommends adaptive thinking with effort for Claude 4.6; budget_tokens remains a deprecated compatibility path.", + "Anthropic API defaults effort to high; lower effort levels should be chosen per workload." ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-07-16", - "last_updated": "2026-07-16", + "open_weights": false, + "knowledge": "2025-08-31", + "release_date": "2026-02-17", + "last_updated": "2026-03-13", "type": "chat" }, { - "id": "fireworks/kimi-k3-fast", - "name": "Kimi K3 Fast (Fireworks AI)", - "display_name": "Kimi K3 Fast (Fireworks AI)", + "id": "nemotron-3-ultra-free", + "name": "Nemotron 3 Ultra Free", + "display_name": "Nemotron 3 Ultra Free", "modalities": { "input": [ - "text", - "image", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1040384, - "output": 1040384 + "context": 1000000, + "output": 128000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -103149,26 +104186,26 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "none", - "low", - "medium", - "high", - "xhigh", - "max" + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, - "attachment": true, + "attachment": false, "open_weights": true, - "release_date": "2026-07-16", - "last_updated": "2026-07-16", + "knowledge": "2025-09", + "release_date": "2026-06-04", + "last_updated": "2026-06-04", "type": "chat" }, { - "id": "fireworks/deepseek-v4.1-flash", - "name": "DeepSeek V4.1 Flash (Fireworks AI)", - "display_name": "DeepSeek V4.1 Flash (Fireworks AI)", + "id": "gpt-5.1", + "name": "GPT-5.1", + "display_name": "GPT-5.1", "modalities": { "input": [ "text", @@ -103179,39 +104216,47 @@ ] }, "limit": { - "context": 1048576, - "output": 393216 + "context": 400000, + "output": 128000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": false, + "mode": "effort", + "effort": "none", "effort_options": [ "none", "low", "medium", - "high", - "xhigh", - "max" - ] + "high" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, "attachment": true, - "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-09-10", - "last_updated": "2026-09-10", + "open_weights": false, + "knowledge": "2024-09-30", + "release_date": "2025-11-13", + "last_updated": "2025-11-13", "type": "chat" }, { - "id": "fireworks/glm-5.3", - "name": "GLM-5.3 (Fireworks AI)", - "display_name": "GLM-5.3 (Fireworks AI)", + "id": "glm-5", + "name": "GLM-5", + "display_name": "GLM-5", "modalities": { "input": [ "text" @@ -103221,7 +104266,7 @@ ] }, "limit": { - "context": 1048576, + "context": 204800, "output": 131072 }, "temperature": true, @@ -103233,37 +104278,42 @@ "extra_capabilities": { "reasoning": { "supported": true, - "effort_options": [ - "xhigh", - "max" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, "attachment": false, "open_weights": true, - "release_date": "2026-08-14", - "last_updated": "2026-08-14", + "knowledge": "2025-04", + "release_date": "2026-02-12", + "last_updated": "2026-02-12", "type": "chat" }, { - "id": "azure-anthropic/claude-sonnet-5-5", - "name": "Claude Sonnet 5.5 (Azure Anthropic)", - "display_name": "Claude Sonnet 5.5 (Azure Anthropic)", + "id": "muse-wonder-1.0", + "name": "Muse Wonder", + "display_name": "Muse Wonder", "modalities": { "input": [ "text", "image", - "pdf" + "video", + "pdf", + "audio" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 1048576, + "output": 131072 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -103272,6 +104322,7 @@ "extra_capabilities": { "reasoning": { "effort_options": [ + "minimal", "low", "medium", "high", @@ -103282,15 +104333,14 @@ }, "attachment": true, "open_weights": false, - "knowledge": "2026-06", - "release_date": "2026-09-28", - "last_updated": "2026-09-28", + "release_date": "2026-10-12", + "last_updated": "2026-10-12", "type": "chat" }, { - "id": "azure-anthropic/claude-opus-5", - "name": "Claude Opus 5 (Azure Anthropic)", - "display_name": "Claude Opus 5 (Azure Anthropic)", + "id": "claude-opus-4-8", + "name": "Claude Opus 4.8", + "display_name": "Claude Opus 4.8", "modalities": { "input": [ "text", @@ -103309,94 +104359,91 @@ "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": false, + "mode": "effort", + "effort": "high", "effort_options": [ - "none", "low", "medium", "high", "xhigh", "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "omitted", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Claude Opus 4.7 and newer Opus models require thinking.type = \"adaptive\" to enable thinking explicitly.", + "Manual budget_tokens requests return 400 on Claude Opus 4.7 and newer adaptive-only Opus models.", + "task_budget is separate from thinking control and should not be treated as a thinking budget." ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-05", - "release_date": "2026-07-24", - "last_updated": "2026-07-24", + "knowledge": "2026-01", + "release_date": "2026-05-28", + "last_updated": "2026-05-28", "type": "chat" }, { - "id": "azure-anthropic/claude-opus-4-7", - "name": "Claude Opus 4.7 (Azure Anthropic)", - "display_name": "Claude Opus 4.7 (Azure Anthropic)", + "id": "kimi-k2.5-free", + "name": "Kimi K2.5 Free", + "display_name": "Kimi K2.5 Free", "modalities": { "input": [ "text", "image", - "pdf" + "video" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 262144, + "output": 262144 }, "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "high", - "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" - ], "interleaved": true, "summaries": true, - "visibility": "omitted", + "visibility": "summary", "continuation": [ "thinking_blocks" - ], - "notes": [ - "Claude Opus 4.7 and newer Opus models require thinking.type = \"adaptive\" to enable thinking explicitly.", - "Manual budget_tokens requests return 400 on Claude Opus 4.7 and newer adaptive-only Opus models.", - "task_budget is separate from thinking control and should not be treated as a thinking budget." ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2026-01-31", - "release_date": "2026-04-16", - "last_updated": "2026-04-16", + "open_weights": true, + "knowledge": "2025-01", + "release_date": "2026-01-27", + "last_updated": "2026-01-27", "type": "chat" }, { - "id": "azure-anthropic/claude-opus-5-5", - "name": "Claude Opus 5.5 (Azure Anthropic)", - "display_name": "Claude Opus 5.5 (Azure Anthropic)", + "id": "longcat-2.5-preview-free", + "name": "LongCat 2.5 Preview Free", + "display_name": "LongCat 2.5 Preview Free", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" @@ -103404,7 +104451,7 @@ }, "limit": { "context": 1000000, - "output": 128000 + "output": 131072 }, "temperature": false, "tool_call": true, @@ -103414,26 +104461,76 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } + }, + "attachment": true, + "open_weights": false, + "release_date": "2026-09-25", + "last_updated": "2026-09-25", + "type": "chat" + }, + { + "id": "deepseek-v4-flash", + "name": "DeepSeek V4 Flash", + "display_name": "DeepSeek V4 Flash", + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1000000, + "output": 384000 + }, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "high", "effort_options": [ "low", - "medium", "high", - "xhigh", "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-06", - "release_date": "2026-09-22", - "last_updated": "2026-09-22", + "attachment": false, + "open_weights": true, + "knowledge": "2025-05", + "release_date": "2026-07-31", + "last_updated": "2026-07-31", "type": "chat" }, { - "id": "azure-anthropic/claude-sonnet-5", - "name": "Claude Sonnet 5 (Azure Anthropic)", - "display_name": "Claude Sonnet 5 (Azure Anthropic)", + "id": "gpt-5.5", + "name": "GPT-5.5", + "display_name": "GPT-5.5", "modalities": { "input": [ "text", @@ -103445,7 +104542,7 @@ ] }, "limit": { - "context": 1000000, + "context": 1050000, "output": 128000 }, "temperature": false, @@ -103457,27 +104554,35 @@ "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", "effort_options": [ - "none", "low", "medium", "high", - "xhigh", - "max" - ] + "xhigh" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, "attachment": true, "open_weights": false, - "knowledge": "2026-01-31", - "release_date": "2026-06-30", - "last_updated": "2026-06-30", + "knowledge": "2025-12-01", + "release_date": "2026-04-23", + "last_updated": "2026-04-23", "type": "chat" }, { - "id": "azure-anthropic/claude-opus-4-6", - "name": "Claude Opus 4.6 (Azure Anthropic)", - "display_name": "Claude Opus 4.6 (Azure Anthropic)", + "id": "claude-opus-4-6", + "name": "Claude Opus 4.6", + "display_name": "Claude Opus 4.6", "modalities": { "input": [ "text", @@ -103534,21 +104639,20 @@ "type": "chat" }, { - "id": "azure-anthropic/claude-opus-4-8", - "name": "Claude Opus 4.8 (Azure Anthropic)", - "display_name": "Claude Opus 4.8 (Azure Anthropic)", + "id": "gpt-5.1-codex-mini", + "name": "GPT-5.1 Codex Mini", + "display_name": "GPT-5.1 Codex Mini", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, + "context": 400000, "output": 128000 }, "temperature": false, @@ -103562,38 +104666,33 @@ "supported": true, "default_enabled": false, "mode": "effort", - "effort": "high", + "effort": "none", "effort_options": [ + "none", "low", "medium", - "high", - "xhigh", - "max" + "high" ], - "interleaved": true, - "summaries": true, - "visibility": "omitted", - "continuation": [ - "thinking_blocks" + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" ], - "notes": [ - "Claude Opus 4.7 and newer Opus models require thinking.type = \"adaptive\" to enable thinking explicitly.", - "Manual budget_tokens requests return 400 on Claude Opus 4.7 and newer adaptive-only Opus models.", - "task_budget is separate from thinking control and should not be treated as a thinking budget." - ] + "visibility": "hidden" } }, "attachment": true, "open_weights": false, - "knowledge": "2026-01", - "release_date": "2026-05-28", - "last_updated": "2026-05-28", + "knowledge": "2024-09-30", + "release_date": "2025-11-13", + "last_updated": "2025-11-13", "type": "chat" }, { - "id": "azure-anthropic/claude-fable-5", - "name": "Claude Fable 5 (Azure Anthropic)", - "display_name": "Claude Fable 5 (Azure Anthropic)", + "id": "claude-fable-5-1", + "name": "Claude Fable 5.1", + "display_name": "Claude Fable 5.1", "modalities": { "input": [ "text", @@ -103642,30 +104741,30 @@ }, "attachment": true, "open_weights": false, - "knowledge": "2026-01-31", - "release_date": "2026-06-09", - "last_updated": "2026-06-09", + "knowledge": "2026-06", + "release_date": "2026-09-01", + "last_updated": "2026-09-01", "type": "chat" }, { - "id": "azure-anthropic/claude-fable-5-1", - "name": "Claude Fable 5.1 (Azure Anthropic)", - "display_name": "Claude Fable 5.1 (Azure Anthropic)", + "id": "qwen3.6-plus", + "name": "Qwen3.6 Plus", + "display_name": "Qwen3.6 Plus", "modalities": { "input": [ "text", "image", - "pdf" + "video" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 262144, + "output": 65536 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -103674,52 +104773,36 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "high", - "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" - ], "interleaved": true, "summaries": true, - "visibility": "omitted", + "visibility": "summary", "continuation": [ "thinking_blocks" - ], - "notes": [ - "Adaptive thinking is always on for Claude Fable 5 and Claude Mythos 5; thinking.type = \"disabled\" is rejected.", - "Manual budget_tokens requests return 400 on Claude Fable 5 and Claude Mythos 5.", - "thinking.display defaults to omitted; set display to summarized to receive readable thinking summaries." ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-06", - "release_date": "2026-09-01", - "last_updated": "2026-09-01", + "knowledge": "2025-04", + "release_date": "2026-04-02", + "last_updated": "2026-04-02", "type": "chat" }, { - "id": "sciforium/deepseek-v4.1-flash-1017", - "name": "DeepSeek V4.1 Flash 1017 (sciforium)", - "display_name": "DeepSeek V4.1 Flash 1017 (sciforium)", + "id": "glm-5.1", + "name": "GLM-5.1", + "display_name": "GLM-5.1", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 384000 + "context": 204800, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -103729,26 +104812,26 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "none", - "low", - "high", - "xhigh", - "max" + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, - "attachment": true, + "attachment": false, "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-10-17", - "last_updated": "2026-10-17", + "knowledge": "2025-04", + "release_date": "2026-04-07", + "last_updated": "2026-04-07", "type": "chat" }, { - "id": "cerebras/gpt-oss-120b", - "name": "GPT OSS 120B (Cerebras)", - "display_name": "GPT OSS 120B (Cerebras)", + "id": "laguna-s-2.1-free", + "name": "Laguna S 2.1 Free", + "display_name": "Laguna S 2.1 Free", "modalities": { "input": [ "text" @@ -103758,8 +104841,8 @@ ] }, "limit": { - "context": 131072, - "output": 32768 + "context": 256000, + "output": 32000 }, "temperature": true, "tool_call": true, @@ -103769,7 +104852,6 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, "effort_options": [ "low", "medium", @@ -103779,27 +104861,25 @@ }, "attachment": false, "open_weights": true, - "knowledge": "2024-06", - "release_date": "2025-08-05", - "last_updated": "2025-08-05", + "release_date": "2026-07-21", + "last_updated": "2026-07-21", "type": "chat" }, { - "id": "azure/grok-4-1-fast-reasoning", - "name": "Grok 4.1 Fast Reasoning (Azure)", - "display_name": "Grok 4.1 Fast Reasoning (Azure)", + "id": "ll-alpha-free", + "name": "LL Alpha Free", + "display_name": "LL Alpha Free", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 128000 + "context": 262144, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -103810,37 +104890,49 @@ "extra_capabilities": { "reasoning": { "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ "low", "medium", - "high" + "high", + "xhigh", + "max" ] } }, - "attachment": true, - "open_weights": false, - "release_date": "2025-11-19", - "last_updated": "2025-11-19", + "attachment": false, + "open_weights": true, + "knowledge": "2026-06", + "release_date": "2026-10-15", + "last_updated": "2026-10-15", "type": "chat" }, { - "id": "azure/gpt-5", - "name": "GPT-5 (Azure)", - "display_name": "GPT-5 (Azure)", + "id": "gemini-3.7-flash", + "name": "Gemini 3.7 Flash", + "display_name": "Gemini 3.7 Flash", "modalities": { "input": [ "text", - "image" - ], - "output": [ + "image", + "video", + "audio", + "pdf" + ], + "output": [ "text" ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 1048576, + "output": 65536 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -103850,101 +104942,101 @@ "reasoning": { "supported": true, "default_enabled": true, - "mode": "effort", - "effort": "medium", - "effort_options": [ + "mode": "level", + "level": "high", + "level_options": [ "minimal", "low", "medium", "high" ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + "summaries": true, + "visibility": "summary", + "continuation": [ + "thought_signatures" + ] } }, "attachment": true, "open_weights": false, - "knowledge": "2024-09-30", - "release_date": "2025-08-07", - "last_updated": "2025-08-07", + "knowledge": "2026-03", + "release_date": "2026-08-13", + "last_updated": "2026-08-13", "type": "chat" }, { - "id": "azure/gpt-5.5", - "name": "GPT-5.5 (Azure)", - "display_name": "GPT-5.5 (Azure)", + "id": "qwen3-coder", + "name": "Qwen3 Coder", + "display_name": "Qwen3 Coder", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 262144, + "output": 65536 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true + "supported": false }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", - "effort_options": [ - "low", - "medium", - "high", - "xhigh" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" - } + "attachment": false, + "open_weights": true, + "knowledge": "2025-04", + "release_date": "2025-07-22", + "last_updated": "2025-07-22", + "type": "chat" + }, + { + "id": "kimi-k2", + "name": "Kimi K2", + "display_name": "Kimi K2", + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-12-01", - "release_date": "2026-04-23", - "last_updated": "2026-04-23", + "limit": { + "context": 262144, + "output": 262144 + }, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": false + }, + "attachment": false, + "open_weights": true, + "knowledge": "2024-10", + "release_date": "2025-09-05", + "last_updated": "2025-09-05", "type": "chat" }, { - "id": "azure/gpt-6-astra", - "name": "GPT-6 Astra (Azure)", - "display_name": "GPT-6 Astra (Azure)", + "id": "glm-5.2", + "name": "GLM-5.2", + "display_name": "GLM-5.2", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 1000000, + "output": 131072 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -103953,79 +105045,56 @@ "extra_capabilities": { "reasoning": { "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ - "low", - "medium", "high", - "xhigh", "max" ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-04-30", - "release_date": "2026-09-04", - "last_updated": "2026-09-04", + "attachment": false, + "open_weights": true, + "release_date": "2026-06-13", + "last_updated": "2026-06-13", "type": "chat" }, { - "id": "azure/gpt-5.4", - "name": "GPT-5.4 (Azure)", - "display_name": "GPT-5.4 (Azure)", + "id": "ling-2.6-flash-free", + "name": "Ling 2.6 Flash Free", + "display_name": "Ling 2.6 Flash Free", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 262100, + "output": 32800 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": false - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "none", - "effort_options": [ - "none", - "low", - "medium", - "high", - "xhigh" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" - } + "supported": false }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-03-05", - "last_updated": "2026-03-05", + "attachment": false, + "open_weights": true, + "knowledge": "2025-06", + "release_date": "2026-04-21", + "last_updated": "2026-04-21", "type": "chat" }, { - "id": "azure/gpt-4o", - "name": "GPT-4o (Azure)", - "display_name": "GPT-4o (Azure)", + "id": "claude-opus-5", + "name": "Claude Opus 5", + "display_name": "Claude Opus 5", "modalities": { "input": [ "text", @@ -104037,25 +105106,38 @@ ] }, "limit": { - "context": 128000, - "output": 16384 + "context": 1000000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" + ] + } }, "attachment": true, "open_weights": false, - "knowledge": "2023-09", - "release_date": "2024-05-13", - "last_updated": "2024-08-06", + "knowledge": "2026-05", + "release_date": "2026-07-24", + "last_updated": "2026-07-24", "type": "chat" }, { - "id": "azure/gpt-5-pro", - "name": "GPT-5 Pro (Azure)", - "display_name": "GPT-5 Pro (Azure)", + "id": "qwen3.8-max", + "name": "Qwen3.8 Max", + "display_name": "Qwen3.8 Max", "modalities": { "input": [ "text", @@ -104066,10 +105148,10 @@ ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 262144, + "output": 131072 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -104078,44 +105160,37 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "fixed", - "effort": "high", - "verbosity": "medium", - "verbosity_options": [ + "effort_options": [ "low", "medium", - "high" - ], - "visibility": "hidden" + "xhigh" + ] } }, "attachment": true, "open_weights": false, - "knowledge": "2024-09-30", - "release_date": "2025-10-06", - "last_updated": "2025-10-06", + "release_date": "2026-08-03", + "last_updated": "2026-08-03", "type": "chat" }, { - "id": "azure/gpt-6-sol", - "name": "GPT-6 Sol (Azure)", - "display_name": "GPT-6 Sol (Azure)", + "id": "grok-4.6", + "name": "Grok 4.6", + "display_name": "Grok 4.6", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 500000, + "output": 500000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -104123,27 +105198,26 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, "effort_options": [ - "none", "low", "medium", "high", - "xhigh", - "max" + "xhigh" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-04-20", - "release_date": "2026-09-22", - "last_updated": "2026-09-22", + "knowledge": "2026-02-01", + "release_date": "2026-08-12", + "last_updated": "2026-08-12", "type": "chat" }, { - "id": "azure/gpt-5.6-sol", - "name": "GPT-5.6 Sol (Azure)", - "display_name": "GPT-5.6 Sol (Azure)", + "id": "claude-sonnet-4", + "name": "Claude Sonnet 4", + "display_name": "Claude Sonnet 4", "modalities": { "input": [ "text", @@ -104155,64 +105229,64 @@ ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 1000000, + "output": 64000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", - "effort_options": [ - "none", - "low", - "medium", - "high", - "xhigh", - "max" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" + "default_enabled": false, + "mode": "budget", + "budget": { + "min": 1024, + "unit": "tokens" + }, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ], - "visibility": "hidden" + "notes": [ + "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", + "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." + ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-02-16", - "release_date": "2026-07-09", - "last_updated": "2026-07-09", + "knowledge": "2025-03-31", + "release_date": "2025-05-22", + "last_updated": "2025-05-22", "type": "chat" }, { - "id": "azure/gpt-5.6-terra", - "name": "GPT-5.6 Terra (Azure)", - "display_name": "GPT-5.6 Terra (Azure)", + "id": "muse-spark-1.2", + "name": "Muse Spark 1.2", + "display_name": "Muse Spark 1.2", "modalities": { "input": [ "text", "image", - "pdf" + "video", + "pdf", + "audio" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 1048576, + "output": 131072 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -104221,37 +105295,25 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", "effort_options": [ - "none", + "minimal", "low", "medium", "high", - "xhigh", - "max" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + "xhigh" + ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-02-16", - "release_date": "2026-07-09", - "last_updated": "2026-07-09", + "release_date": "2026-08-05", + "last_updated": "2026-08-05", "type": "chat" }, { - "id": "azure/gpt-oss-120b", - "name": "GPT OSS 120B (Azure)", - "display_name": "GPT OSS 120B (Azure)", + "id": "ring-2.6-1t-free", + "name": "Ring 2.6 1T Free", + "display_name": "Ring 2.6 1T Free", "modalities": { "input": [ "text" @@ -104261,8 +105323,8 @@ ] }, "limit": { - "context": 131072, - "output": 32768 + "context": 262000, + "output": 66000 }, "temperature": true, "tool_call": true, @@ -104273,90 +105335,83 @@ "extra_capabilities": { "reasoning": { "supported": true, - "effort_options": [ - "low", - "medium", - "high" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, "attachment": false, "open_weights": true, - "knowledge": "2024-06", - "release_date": "2025-08-05", - "last_updated": "2025-08-05", + "knowledge": "2025-06", + "release_date": "2026-05-08", + "last_updated": "2026-05-08", "type": "chat" }, { - "id": "azure/gpt-5.1-codex", - "name": "GPT-5.1 Codex (Azure)", - "display_name": "GPT-5.1 Codex (Azure)", + "id": "minimax-m3", + "name": "MiniMax-M3", + "display_name": "MiniMax-M3", "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 400000, - "output": 272000 + "context": 512000, + "output": 128000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "none", - "effort_options": [ - "none", - "low", - "medium", - "high" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2024-09-30", - "release_date": "2025-11-13", - "last_updated": "2025-11-13", + "open_weights": true, + "release_date": "2026-06-01", + "last_updated": "2026-06-01", "type": "chat" }, { - "id": "azure/gpt-6-luna", - "name": "GPT-6 Luna (Azure)", - "display_name": "GPT-6 Luna (Azure)", + "id": "muse-spark-1.2-contributor-free", + "name": "Muse Spark 1.2 Free", + "display_name": "Muse Spark 1.2 Free", "modalities": { "input": [ "text", "image", - "pdf" + "video", + "pdf", + "audio" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 1048576, + "output": 131072 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -104365,40 +105420,37 @@ "extra_capabilities": { "reasoning": { "effort_options": [ - "none", + "minimal", "low", "medium", "high", - "xhigh", - "max" + "xhigh" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-05-18", - "release_date": "2026-09-22", - "last_updated": "2026-09-22", + "release_date": "2026-08-05", + "last_updated": "2026-08-05", "type": "chat" }, { - "id": "azure/gpt-5-mini", - "name": "GPT-5 Mini (Azure)", - "display_name": "GPT-5 Mini (Azure)", + "id": "nemotron-3-super-free", + "name": "Nemotron 3 Super Free", + "display_name": "Nemotron 3 Super Free", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 400000, + "context": 204800, "output": 128000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -104407,46 +105459,40 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", - "effort_options": [ - "minimal", - "low", - "medium", - "high" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2024-05-30", - "release_date": "2025-08-07", - "last_updated": "2025-08-07", + "attachment": false, + "open_weights": true, + "knowledge": "2025-06", + "release_date": "2026-03-11", + "last_updated": "2026-03-11", "type": "chat" }, { - "id": "azure/grok-4-3", - "name": "Grok 4.3 (Azure)", - "display_name": "Grok 4.3 (Azure)", + "id": "muse-spark-1.3", + "name": "Muse Spark 1.3", + "display_name": "Muse Spark 1.3", "modalities": { "input": [ - "text" + "text", + "image", + "video", + "pdf", + "audio" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 8192 + "context": 1048576, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -104456,24 +105502,26 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, "effort_options": [ - "none", + "minimal", "low", "medium", - "high" + "high", + "xhigh" ] } }, - "attachment": false, + "attachment": true, "open_weights": false, - "release_date": "2026-04-30", - "last_updated": "2026-04-30", + "release_date": "2026-09-02", + "last_updated": "2026-09-02", "type": "chat" }, { - "id": "azure/gpt-5.4-pro", - "name": "GPT-5.4 Pro (Azure)", - "display_name": "GPT-5.4 Pro (Azure)", + "id": "nemotron-3.5-super-vl-free", + "name": "Nemotron 3.5 Super VL Free", + "display_name": "Nemotron 3.5 Super VL Free", "modalities": { "input": [ "text", @@ -104484,10 +105532,10 @@ ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 1048576, + "output": 131072 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -104496,136 +105544,160 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "high", + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ + "none", + "minimal", + "low", "medium", "high", "xhigh" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-03-05", - "last_updated": "2026-03-05", + "open_weights": true, + "release_date": "2026-10-15", + "last_updated": "2026-10-15", "type": "chat" }, { - "id": "azure/gpt-5.1-codex-mini", - "name": "GPT-5.1 Codex mini (Azure)", - "display_name": "GPT-5.1 Codex mini (Azure)", + "id": "deepseek-v4-flash-free", + "name": "DeepSeek V4 Flash Free", + "display_name": "DeepSeek V4 Flash Free", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 400000, + "context": 200000, "output": 128000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "none", - "effort_options": [ - "none", - "low", - "medium", - "high" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ], - "verbosity": "medium", - "verbosity_options": [ + "effort_options": [ "low", - "medium", - "high" - ], - "visibility": "hidden" + "high", + "max" + ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2024-09-30", - "release_date": "2025-11-13", - "last_updated": "2025-11-13", + "attachment": false, + "open_weights": true, + "knowledge": "2025-05", + "release_date": "2026-07-31", + "last_updated": "2026-07-31", "type": "chat" }, { - "id": "azure/gpt-5.1", - "name": "GPT-5.1 (Azure)", - "display_name": "GPT-5.1 (Azure)", + "id": "mimo-v2.5-free", + "name": "MiMo V2.5 Free", + "display_name": "MiMo V2.5 Free", "modalities": { "input": [ "text", - "image" + "image", + "audio", + "video" ], "output": [ "text" ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 200000, + "output": 32000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "none", - "effort_options": [ - "none", - "low", - "medium", - "high" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2024-09-30", - "release_date": "2025-11-13", - "last_updated": "2025-11-13", + "open_weights": true, + "knowledge": "2024-12", + "release_date": "2026-04-22", + "last_updated": "2026-04-22", "type": "chat" }, { - "id": "azure/gpt-5.6-luna", - "name": "GPT-5.6 Luna (Azure)", - "display_name": "GPT-5.6 Luna (Azure)", + "id": "minimax-m2.1", + "name": "MiniMax-M2.1", + "display_name": "MiniMax-M2.1", + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 204800, + "output": 131072 + }, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } + }, + "attachment": false, + "open_weights": true, + "knowledge": "2025-01", + "release_date": "2025-12-23", + "last_updated": "2025-12-23", + "type": "chat" + }, + { + "id": "gpt-6-luna", + "name": "GPT-6 Luna", + "display_name": "GPT-6 Luna", "modalities": { "input": [ "text", @@ -104648,10 +105720,6 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", "effort_options": [ "none", "low", @@ -104659,94 +105727,80 @@ "high", "xhigh", "max" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-02-16", - "release_date": "2026-07-09", - "last_updated": "2026-07-09", + "knowledge": "2026-05-18", + "release_date": "2026-09-22", + "last_updated": "2026-09-22", "type": "chat" }, { - "id": "azure/gpt-5.4-mini", - "name": "GPT-5.4 Mini (Azure)", - "display_name": "GPT-5.4 Mini (Azure)", + "id": "glm-5.3-flash-free", + "name": "GLM-5.3-Flash Free", + "display_name": "GLM-5.3-Flash Free", "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 200000, + "output": 32000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "none", + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ - "none", "low", - "medium", "high", - "xhigh" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + "max" + ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-03-17", - "last_updated": "2026-03-17", + "open_weights": true, + "release_date": "2026-08-26", + "last_updated": "2026-08-26", "type": "chat" }, { - "id": "azure/gpt-5.3-codex", - "name": "GPT-5.3 Codex (Azure)", - "display_name": "GPT-5.3 Codex (Azure)", + "id": "exo-free", + "name": "Exo Free", + "display_name": "Exo Free", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 1048576, + "output": 131072 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -104754,163 +105808,146 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", "effort_options": [ - "low", - "medium", - "high", - "xhigh" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", "high" - ], - "visibility": "hidden" + ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-02-05", - "last_updated": "2026-02-05", + "release_date": "2026-10-06", + "last_updated": "2026-10-06", "type": "chat" }, { - "id": "azure/gpt-4.1-mini", - "name": "GPT-4.1 Mini (Azure)", - "display_name": "GPT-4.1 Mini (Azure)", + "id": "qwen3.6-plus-free", + "name": "Qwen3.6 Plus Free", + "display_name": "Qwen3.6 Plus Free", "modalities": { "input": [ "text", "image", - "pdf" + "video" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 32768 + "context": 262144, + "output": 65536 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } }, "attachment": true, "open_weights": false, - "knowledge": "2024-04", - "release_date": "2025-04-14", - "last_updated": "2025-04-14", + "knowledge": "2025-04", + "release_date": "2026-04-02", + "last_updated": "2026-04-02", "type": "chat" }, { - "id": "azure/gpt-5.4-nano", - "name": "GPT-5.4 Nano (Azure)", - "display_name": "GPT-5.4 Nano (Azure)", + "id": "minimax-m2.7", + "name": "MiniMax-M2.7", + "display_name": "MiniMax-M2.7", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 204800, + "output": 131072 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "none", - "effort_options": [ - "none", - "low", - "medium", - "high", - "xhigh" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-03-17", - "last_updated": "2026-03-17", + "attachment": false, + "open_weights": true, + "knowledge": "2025-01", + "release_date": "2026-03-18", + "last_updated": "2026-03-18", "type": "chat" }, { - "id": "azure/gpt-4.1", - "name": "GPT-4.1 (Azure)", - "display_name": "GPT-4.1 (Azure)", + "id": "ling-3.1-flash-free", + "name": "Ling 3.1 Flash Free", + "display_name": "Ling 3.1 Flash Free", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, + "context": 262144, "output": 32768 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": true, - "open_weights": false, - "knowledge": "2024-04", - "release_date": "2025-04-14", - "last_updated": "2025-04-14", + "attachment": false, + "open_weights": true, + "release_date": "2026-09-29", + "last_updated": "2026-09-29", "type": "chat" }, { - "id": "azure/gpt-5.2-codex", - "name": "GPT-5.2 Codex (Azure)", - "display_name": "GPT-5.2 Codex (Azure)", + "id": "step-5-preview-free", + "name": "Step 5 Preview Free", + "display_name": "Step 5 Preview Free", "modalities": { "input": [ "text", "image", - "pdf" + "video" ], "output": [ "text" ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 1000000, + "output": 65536 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -104919,48 +105956,42 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", - "effort_options": [ - "low", - "medium", - "high", - "xhigh" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ], - "verbosity": "medium", - "verbosity_options": [ + "effort_options": [ "low", "medium", "high" - ], - "visibility": "hidden" + ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2025-12-11", - "last_updated": "2025-12-11", + "open_weights": true, + "release_date": "2026-09-16", + "last_updated": "2026-09-20", "type": "chat" }, { - "id": "azure/gpt-6.1-terra", - "name": "GPT-6.1 Terra (Azure)", - "display_name": "GPT-6.1 Terra (Azure)", + "id": "kimi-k2.5", + "name": "Kimi K2.5", + "display_name": "Kimi K2.5", "modalities": { "input": [ "text", "image", - "pdf" + "video" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 262144, + "output": 65536 }, "temperature": false, "tool_call": true, @@ -104970,94 +106001,84 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "none", - "low", - "medium", - "high", - "xhigh", - "max" + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2026-06-01", - "release_date": "2026-10-26", - "last_updated": "2026-10-26", + "open_weights": true, + "knowledge": "2025-01", + "release_date": "2026-01-27", + "last_updated": "2026-01-27", "type": "chat" }, { - "id": "azure/gpt-5.2", - "name": "GPT-5.2 (Azure)", - "display_name": "GPT-5.2 (Azure)", + "id": "muse-spark-1.3-contributor-free", + "name": "Muse Spark 1.3 Free", + "display_name": "Muse Spark 1.3 Free", "modalities": { "input": [ "text", - "image" + "image", + "video", + "pdf", + "audio" ], "output": [ "text" ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 1048576, + "output": 131072 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "none", "effort_options": [ - "none", + "minimal", "low", "medium", "high", "xhigh" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2025-12-11", - "last_updated": "2025-12-11", + "release_date": "2026-09-02", + "last_updated": "2026-09-02", "type": "chat" }, { - "id": "azure/gpt-6.1-sol", - "name": "GPT-6.1 Sol (Azure)", - "display_name": "GPT-6.1 Sol (Azure)", + "id": "kimi-k2.6", + "name": "Kimi K2.6", + "display_name": "Kimi K2.6", "modalities": { "input": [ "text", "image", - "pdf" + "video" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 262144, + "output": 65536 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -105065,26 +106086,26 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2026-04-30", - "release_date": "2026-09-29", - "last_updated": "2026-09-29", + "open_weights": true, + "knowledge": "2025-01", + "release_date": "2026-04-20", + "last_updated": "2026-04-20", "type": "chat" }, { - "id": "azure/gpt-5-nano", - "name": "GPT-5 Nano (Azure)", - "display_name": "GPT-5 Nano (Azure)", + "id": "trinity-large-preview-free", + "name": "Trinity Large Preview", + "display_name": "Trinity Large Preview", "modalities": { "input": [ "text" @@ -105094,57 +106115,39 @@ ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 131072, + "output": 131072 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", - "effort_options": [ - "minimal", - "low", - "medium", - "high" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" - } + "supported": false }, "attachment": false, - "open_weights": false, - "knowledge": "2024-05-30", - "release_date": "2025-08-07", - "last_updated": "2025-08-07", + "open_weights": true, + "knowledge": "2025-06", + "release_date": "2026-01-27", + "last_updated": "2026-05-28", "type": "chat" }, { - "id": "alibaba/qwen3.7-max", - "name": "Qwen3.7 Max (Alibaba Cloud)", - "display_name": "Qwen3.7 Max (Alibaba Cloud)", + "id": "gemini-3.5-flash-lite", + "name": "Gemini 3.5 Flash Lite", + "display_name": "Gemini 3.5 Flash Lite", "modalities": { "input": [ - "text" + "text", + "image", + "video", + "audio", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 983616, + "context": 1048576, "output": 65536 }, "temperature": true, @@ -105157,37 +106160,36 @@ "reasoning": { "supported": true, "effort_options": [ - "none", "minimal", "low", "medium", - "high", - "xhigh", - "max" + "high" ] } }, - "attachment": false, + "attachment": true, "open_weights": false, - "release_date": "2026-05-21", - "last_updated": "2026-05-21", + "knowledge": "2026-03", + "release_date": "2026-07-21", + "last_updated": "2026-07-21", "type": "chat" }, { - "id": "alibaba/deepseek-v4-flash", - "name": "DeepSeek V4 Flash (Alibaba Cloud)", - "display_name": "DeepSeek V4 Flash (Alibaba Cloud)", + "id": "fledge-alpha-free", + "name": "Fledge Alpha Free", + "display_name": "Fledge Alpha Free", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 393216 + "context": 1048576, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -105197,49 +106199,45 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "high", "effort_options": [ "low", "high", "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." ] } }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-04-24", - "last_updated": "2026-04-24", + "attachment": true, + "open_weights": false, + "release_date": "2026-10-01", + "last_updated": "2026-10-01", "type": "chat" - }, + } + ] + }, + "cortecs": { + "id": "cortecs", + "name": "Cortecs", + "display_name": "Cortecs", + "api": "https://api.cortecs.ai/v1", + "doc": "https://api.cortecs.ai/v1/models", + "models": [ { - "id": "alibaba/deepseek-v4.1-flash-1017", - "name": "DeepSeek V4.1 Flash 1017 (Alibaba Cloud)", - "display_name": "DeepSeek V4.1 Flash 1017 (Alibaba Cloud)", + "id": "gemini-3.5-flash", + "name": "Gemini 3.5 Flash", + "display_name": "Gemini 3.5 Flash", "modalities": { "input": [ "text", - "image" + "image", + "audio" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 393216 + "context": 1048576, + "output": 65535 }, "temperature": true, "tool_call": true, @@ -105249,72 +106247,63 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "none", + "supported": true, + "default_enabled": true, + "mode": "level", + "level": "high", + "level_options": [ "minimal", "low", "medium", - "high", - "xhigh", - "max" + "high" + ], + "summaries": true, + "visibility": "summary", + "continuation": [ + "thought_signatures" ] } }, "attachment": true, - "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-10-17", - "last_updated": "2026-10-17", + "open_weights": false, + "knowledge": "2025-01", + "release_date": "2026-05-19", + "last_updated": "2026-05-19", "type": "chat" }, { - "id": "alibaba/kimi-k3", - "name": "Kimi K3 (Alibaba Cloud)", - "display_name": "Kimi K3 (Alibaba Cloud)", + "id": "nova-micro-v1", + "name": "Nova Micro 1.0", + "display_name": "Nova Micro 1.0", "modalities": { "input": [ "text", - "image", - "video" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 1048576 + "context": 128000, + "output": 10000 }, "temperature": false, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "effort_options": [ - "none", - "minimal", - "low", - "medium", - "high", - "xhigh", - "max" - ] - } + "supported": false }, "attachment": true, - "open_weights": true, - "release_date": "2026-07-16", - "last_updated": "2026-07-16", + "open_weights": false, + "knowledge": "2024-10-31", + "release_date": "2024-12-05", + "last_updated": "2024-12-05", "type": "chat" }, { - "id": "alibaba/qwen3-vl-plus", - "name": "Qwen3 VL Plus (Alibaba Cloud)", - "display_name": "Qwen3 VL Plus (Alibaba Cloud)", + "id": "qwen2.5-vl-72b-instruct", + "name": "qwen2.5-vl-72b-instruct", + "display_name": "qwen2.5-vl-72b-instruct", "modalities": { "input": [ "text", @@ -105325,39 +106314,30 @@ ] }, "limit": { - "context": 262144, - "output": 32768 + "context": 32000, + "output": 32000 }, - "temperature": true, + "temperature": false, "tool_call": false, "reasoning": { - "supported": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } + "supported": false }, "attachment": true, - "open_weights": false, - "knowledge": "2025-04", - "release_date": "2025-09-23", - "last_updated": "2025-09-23", + "open_weights": true, + "knowledge": "2024-04", + "release_date": "2025-01-26", + "last_updated": "2025-01-26", "type": "chat" }, { - "id": "alibaba/deepseek-v4-pro", - "name": "DeepSeek V4 Pro (Alibaba Cloud)", - "display_name": "DeepSeek V4 Pro (Alibaba Cloud)", + "id": "claude-opus4-6", + "name": "Claude Opus 4.6", + "display_name": "Claude Opus 4.6", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" @@ -105365,7 +106345,7 @@ }, "limit": { "context": 1000000, - "output": 393216 + "output": 128000 }, "temperature": true, "tool_call": true, @@ -105375,52 +106355,38 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "high", "effort_options": [ "low", - "high", - "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." + "medium", + "high" ] } }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-04-24", - "last_updated": "2026-04-24", + "attachment": true, + "open_weights": false, + "knowledge": "2025-05-31", + "release_date": "2026-02-05", + "last_updated": "2026-03-13", "type": "chat" }, { - "id": "alibaba/qwen3.6-flash", - "name": "Qwen3.6 Flash (Alibaba Cloud)", - "display_name": "Qwen3.6 Flash (Alibaba Cloud)", + "id": "kimi-k3", + "name": "Kimi K3", + "display_name": "Kimi K3", "modalities": { "input": [ "text", - "image", - "video" + "image" ], "output": [ "text" ] }, "limit": { - "context": 983616, - "output": 65536 + "context": 1048576, + "output": 1048576 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -105436,81 +106402,37 @@ "thinking_blocks" ], "effort_options": [ - "none", - "minimal", "low", - "medium", "high", - "xhigh", "max" ] } }, "attachment": true, - "open_weights": false, - "release_date": "2026-04-27", - "last_updated": "2026-04-27", - "type": "chat" - }, - { - "id": "alibaba/qwen-flash", - "name": "Qwen Flash (Alibaba Cloud)", - "display_name": "Qwen Flash (Alibaba Cloud)", - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 1000000, - "output": 32000 - }, - "temperature": true, - "tool_call": true, - "reasoning": { - "supported": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } - }, - "attachment": false, - "open_weights": false, - "knowledge": "2024-04", - "release_date": "2025-07-28", - "last_updated": "2025-07-28", + "open_weights": true, + "release_date": "2026-07-16", + "last_updated": "2026-07-16", "type": "chat" }, { - "id": "alibaba/qwen3.8-omni-flash", - "name": "Qwen3.8 Omni Flash (Alibaba Cloud)", - "display_name": "Qwen3.8 Omni Flash (Alibaba Cloud)", + "id": "gpt-6.1-terra", + "name": "GPT-6.1 Terra", + "display_name": "GPT-6.1 Terra", "modalities": { "input": [ "text", "image", - "audio", - "video" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 983616, - "output": 131072 + "context": 1050000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -105519,39 +106441,34 @@ "extra_capabilities": { "reasoning": { "effort_options": [ - "none", - "minimal", "low", "medium", - "high", - "xhigh", - "max" + "high" ] } }, "attachment": true, "open_weights": false, - "release_date": "2026-09-17", - "last_updated": "2026-09-17", + "knowledge": "2026-06-01", + "release_date": "2026-10-26", + "last_updated": "2026-10-26", "type": "chat" }, { - "id": "alibaba/qwen3.5-35b-a3b", - "name": "Qwen3.5 35B A3B (Alibaba Cloud)", - "display_name": "Qwen3.5 35B A3B (Alibaba Cloud)", + "id": "minimax-m2.5", + "name": "MiniMax-M2.5", + "display_name": "MiniMax-M2.5", "modalities": { "input": [ - "text", - "image", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 65536 + "context": 196000, + "output": 196000 }, "temperature": true, "tool_call": true, @@ -105567,94 +106484,87 @@ "visibility": "summary", "continuation": [ "thinking_blocks" - ], - "effort_options": [ - "none", - "minimal", - "low", - "medium", - "high", - "xhigh", - "max" ] } }, - "attachment": true, + "attachment": false, "open_weights": true, - "release_date": "2026-02-23", - "last_updated": "2026-02-23", + "release_date": "2026-02-12", + "last_updated": "2026-02-12", "type": "chat" }, { - "id": "alibaba/kimi-k2.5", - "name": "Kimi K2.5 (Alibaba Cloud)", - "display_name": "Kimi K2.5 (Alibaba Cloud)", + "id": "gpt-5.4", + "name": "GPT-5.4", + "display_name": "GPT-5.4", "modalities": { "input": [ "text", "image", - "video" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 98304 + "context": 1050000, + "output": 128000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], + "default_enabled": false, + "mode": "effort", + "effort": "none", "effort_options": [ "none", - "minimal", "low", "medium", "high", - "xhigh", - "max" - ] + "xhigh" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, "attachment": true, - "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-01-27", - "last_updated": "2026-01-27", + "open_weights": false, + "knowledge": "2025-08-31", + "release_date": "2026-03-05", + "last_updated": "2026-03-05", "type": "chat" }, { - "id": "alibaba/qwen35-397b-a17b", - "name": "Qwen3.5 397B A17B (Alibaba Cloud)", - "display_name": "Qwen3.5 397B A17B (Alibaba Cloud)", + "id": "claude-opus4-8", + "name": "Claude Opus 4.8", + "display_name": "Claude Opus 4.8", "modalities": { "input": [ "text", "image", - "video" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 65536 + "context": 1000000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -105663,70 +106573,56 @@ "extra_capabilities": { "reasoning": { "effort_options": [ - "none", - "minimal", "low", "medium", - "high", - "xhigh", - "max" + "high" ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-02-15", - "last_updated": "2026-02-15", + "open_weights": false, + "knowledge": "2026-01", + "release_date": "2026-05-28", + "last_updated": "2026-05-28", "type": "chat" }, { - "id": "alibaba/qwen-plus", - "name": "Qwen Plus (Alibaba Cloud)", - "display_name": "Qwen Plus (Alibaba Cloud)", + "id": "mistral-large-2512", + "name": "Mistral Large 3", + "display_name": "Mistral Large 3", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 32000 + "context": 256000, + "output": 256000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } + "supported": false }, - "attachment": false, - "open_weights": false, - "knowledge": "2024-04", - "release_date": "2024-01-25", - "last_updated": "2025-09-11", + "attachment": true, + "open_weights": true, + "knowledge": "2024-11", + "release_date": "2025-12-02", + "last_updated": "2025-12-02", "type": "chat" }, { - "id": "alibaba/qwen3.8-max", - "name": "Qwen3.8 Max (Alibaba Cloud)", - "display_name": "Qwen3.8 Max (Alibaba Cloud)", + "id": "gpt-6-sol", + "name": "GPT-6 Sol", + "display_name": "GPT-6 Sol", "modalities": { "input": [ "text", "image", - "video", "pdf" ], "output": [ @@ -105734,10 +106630,10 @@ ] }, "limit": { - "context": 983616, - "output": 131072 + "context": 1050000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -105745,39 +106641,39 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, "effort_options": [ "low", "medium", - "xhigh" + "high" ] } }, "attachment": true, "open_weights": false, - "release_date": "2026-08-03", - "last_updated": "2026-08-03", + "knowledge": "2026-04-20", + "release_date": "2026-09-22", + "last_updated": "2026-09-22", "type": "chat" }, { - "id": "alibaba/qwen3.5-122b-a10b", - "name": "Qwen3.5 122B A10B (Alibaba Cloud)", - "display_name": "Qwen3.5 122B A10B (Alibaba Cloud)", + "id": "gpt-5.6-terra", + "name": "GPT-5.6 Terra", + "display_name": "GPT-5.6 Terra", "modalities": { "input": [ "text", "image", - "video" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 65536 + "context": 1050000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -105786,33 +106682,37 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], + "default_enabled": true, + "mode": "effort", + "effort": "medium", "effort_options": [ "none", - "minimal", "low", "medium", "high", "xhigh", "max" - ] + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, "attachment": true, - "open_weights": true, - "release_date": "2026-02-23", - "last_updated": "2026-02-23", + "open_weights": false, + "knowledge": "2026-02-16", + "release_date": "2026-07-09", + "last_updated": "2026-07-09", "type": "chat" }, { - "id": "alibaba/deepseek-v4.1-flash", - "name": "DeepSeek V4.1 Flash (Alibaba Cloud)", - "display_name": "DeepSeek V4.1 Flash (Alibaba Cloud)", + "id": "nemotron-nano-v2-12b", + "name": "nemotron-nano-v2-12b", + "display_name": "nemotron-nano-v2-12b", "modalities": { "input": [ "text", @@ -105823,55 +106723,40 @@ ] }, "limit": { - "context": 1000000, - "output": 393216 + "context": 128000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "effort_options": [ - "none", - "minimal", - "low", - "medium", - "high", - "xhigh", - "max" - ] - } - }, "attachment": true, "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-09-10", - "last_updated": "2026-09-10", + "release_date": "2025-10-28", + "last_updated": "2025-10-28", "type": "chat" }, { - "id": "alibaba/qwen3.6-plus", - "name": "Qwen3.6 Plus (Alibaba Cloud)", - "display_name": "Qwen3.6 Plus (Alibaba Cloud)", + "id": "gpt-6-astra", + "name": "GPT-6 Astra", + "display_name": "GPT-6 Astra", "modalities": { "input": [ "text", "image", - "video" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 983616, - "output": 65536 + "context": 1050000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -105880,47 +106765,35 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], "effort_options": [ - "none", - "minimal", "low", "medium", - "high", - "xhigh", - "max" + "high" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-04", - "release_date": "2026-04-02", - "last_updated": "2026-04-02", + "knowledge": "2026-04-30", + "release_date": "2026-09-04", + "last_updated": "2026-09-04", "type": "chat" }, { - "id": "alibaba/qwen3.6-35b-a3b", - "name": "Qwen3.6 35B A3B (Alibaba Cloud)", - "display_name": "Qwen3.6 35B A3B (Alibaba Cloud)", + "id": "gpt-oss-safeguard-120b", + "name": "GPT OSS Safeguard 120B", + "display_name": "GPT OSS Safeguard 120B", "modalities": { "input": [ - "text", - "image", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 65536 + "context": 128000, + "output": 128000 }, "temperature": true, "tool_call": true, @@ -105930,34 +106803,23 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], "effort_options": [ - "none", - "minimal", "low", "medium", - "high", - "xhigh", - "max" + "high" ] } }, - "attachment": true, + "attachment": false, "open_weights": true, - "release_date": "2026-04-17", - "last_updated": "2026-04-17", + "release_date": "2025-10-29", + "last_updated": "2025-10-29", "type": "chat" }, { - "id": "alibaba/qwen-plus-latest", - "name": "Qwen Plus Latest (Alibaba Cloud)", - "display_name": "Qwen Plus Latest (Alibaba Cloud)", + "id": "qwen3.8-2.4t-a95b", + "name": "Qwen3.8 2.4T A95B", + "display_name": "Qwen3.8 2.4T A95B", "modalities": { "input": [ "text" @@ -105967,92 +106829,76 @@ ] }, "limit": { - "context": 1000000, - "output": 32000 + "context": 262144, + "output": 262144 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true + "supported": true, + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "low", + "medium", + "xhigh" ] } }, "attachment": false, - "open_weights": false, - "release_date": "2024-09-09", - "last_updated": "2024-09-09", + "open_weights": true, + "release_date": "2026-08-12", + "last_updated": "2026-08-12", "type": "chat" }, { - "id": "alibaba/qwen3.8-flash", - "name": "Qwen3.8 Flash (Alibaba Cloud)", - "display_name": "Qwen3.8 Flash (Alibaba Cloud)", + "id": "nvidia-nemotron-3-nano-30b-a3b", + "name": "nvidia-nemotron-3-nano-30b-a3b", + "display_name": "nvidia-nemotron-3-nano-30b-a3b", "modalities": { "input": [ - "text", - "image", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 983616, - "output": 131072 + "context": 256000, + "output": 256000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "effort_options": [ - "none", - "minimal", - "low", - "medium", - "high", - "xhigh", - "max" - ] - } - }, - "attachment": true, - "open_weights": false, - "release_date": "2026-08-26", - "last_updated": "2026-08-26", + "attachment": false, + "open_weights": true, + "knowledge": "2025-06-25", + "release_date": "2025-12-15", + "last_updated": "2025-12-15", "type": "chat" }, { - "id": "alibaba/qwen3.7-flash", - "name": "Qwen3.7 Flash (Alibaba Cloud)", - "display_name": "Qwen3.7 Flash (Alibaba Cloud)", + "id": "gemma-4-26b-a4b-it", + "name": "Gemma 4 26B A4B IT", + "display_name": "Gemma 4 26B A4B IT", "modalities": { "input": [ "text", - "image", - "video" + "image" ], "output": [ "text" ] }, "limit": { - "context": 983616, - "output": 65536 + "context": 262000, + "output": 81920 }, "temperature": true, "tool_call": true, @@ -106062,41 +106908,34 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "effort_options": [ - "none", - "minimal", - "low", - "medium", - "high", - "xhigh", - "max" - ] + "supported": true } }, "attachment": true, - "open_weights": false, - "release_date": "2026-07-15", - "last_updated": "2026-07-15", + "open_weights": true, + "release_date": "2026-04-02", + "last_updated": "2026-04-02", "type": "chat" }, { - "id": "alibaba/glm-5", - "name": "GLM-5 (Alibaba Cloud)", - "display_name": "GLM-5 (Alibaba Cloud)", + "id": "kimi-k2.7-code", + "name": "Kimi K2.7 Code", + "display_name": "Kimi K2.7 Code", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 202752, - "output": 16384 + "context": 262144, + "output": 262144 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -106110,40 +106949,35 @@ "visibility": "summary", "continuation": [ "thinking_blocks" - ], - "effort_options": [ - "none", - "minimal", - "low", - "medium", - "high", - "xhigh" ] } }, - "attachment": false, + "attachment": true, "open_weights": true, - "release_date": "2026-02-12", - "last_updated": "2026-02-12", + "knowledge": "2025-01", + "release_date": "2026-06-12", + "last_updated": "2026-06-12", "type": "chat" }, { - "id": "alibaba/glm-5.3", - "name": "GLM-5.3 (Alibaba Cloud)", - "display_name": "GLM-5.3 (Alibaba Cloud)", + "id": "gpt-6.1-sol", + "name": "GPT-6.1 Sol", + "display_name": "GPT-6.1 Sol", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 1050000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -106151,65 +106985,36 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, "effort_options": [ "low", - "high", - "max" + "medium", + "high" ] } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-08-14", - "last_updated": "2026-08-14", - "type": "chat" - }, - { - "id": "alibaba/qwen-max", - "name": "Qwen Max (Alibaba Cloud)", - "display_name": "Qwen Max (Alibaba Cloud)", - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 32768, - "output": 8192 - }, - "temperature": true, - "tool_call": true, - "reasoning": { - "supported": false - }, - "attachment": false, + "attachment": true, "open_weights": false, - "knowledge": "2024-04", - "release_date": "2024-04-03", - "last_updated": "2025-01-25", + "knowledge": "2026-04-30", + "release_date": "2026-09-29", + "last_updated": "2026-09-29", "type": "chat" }, { - "id": "alibaba/qwen3.5-27b", - "name": "Qwen3.5 27B (Alibaba Cloud)", - "display_name": "Qwen3.5 27B (Alibaba Cloud)", + "id": "mistral-large-4-0", + "name": "Mistral Large 4", + "display_name": "Mistral Large 4", "modalities": { "input": [ "text", - "image", - "video" + "image" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 65536 + "context": 1000000, + "output": 262144 }, "temperature": true, "tool_call": true, @@ -106219,49 +107024,38 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], "effort_options": [ "none", - "minimal", - "low", - "medium", - "high", - "xhigh", - "max" + "high" ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-02-23", - "last_updated": "2026-02-23", + "open_weights": false, + "release_date": "2026-10-06", + "last_updated": "2026-10-06", "type": "chat" }, { - "id": "alibaba/qwen3.7-plus", - "name": "Qwen3.7 Plus (Alibaba Cloud)", - "display_name": "Qwen3.7 Plus (Alibaba Cloud)", + "id": "gemini-4-argon", + "name": "Gemini 4 Argon", + "display_name": "Gemini 4 Argon", "modalities": { "input": [ "text", "image", - "video" + "audio", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 983616, - "output": 65536 + "context": 1048576, + "output": 65535 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -106269,68 +107063,65 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, "effort_options": [ - "none", - "minimal", "low", "medium", - "high", - "xhigh", - "max" + "high" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-04", - "release_date": "2026-06-02", - "last_updated": "2026-06-02", + "knowledge": "2026-03", + "release_date": "2026-10-13", + "last_updated": "2026-10-13", "type": "chat" }, { - "id": "alibaba/qwen3-coder-flash", - "name": "Qwen3 Coder Flash (Alibaba Cloud)", - "display_name": "Qwen3 Coder Flash (Alibaba Cloud)", + "id": "ministral-14b-2512", + "name": "Ministral 3 14B 2512", + "display_name": "Ministral 3 14B 2512", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 997952, - "output": 65536 + "context": 256000, + "output": 256000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": false }, - "attachment": false, - "open_weights": false, - "knowledge": "2025-04", - "release_date": "2025-07-28", - "last_updated": "2025-07-28", + "attachment": true, + "open_weights": true, + "release_date": "2025-12-02", + "last_updated": "2025-12-02", "type": "chat" }, { - "id": "alibaba/glm-5.2", - "name": "GLM-5.2 (Alibaba Cloud)", - "display_name": "GLM-5.2 (Alibaba Cloud)", + "id": "claude-opus4-5", + "name": "Claude Opus 4.5 (latest)", + "display_name": "Claude Opus 4.5 (latest)", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 200000, + "output": 64000 }, "temperature": true, "tool_call": true, @@ -106340,37 +107131,29 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], "effort_options": [ - "none", - "minimal", "low", "medium", - "high", - "xhigh", - "max" + "high" ] } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-06-13", - "last_updated": "2026-06-13", + "attachment": true, + "open_weights": false, + "knowledge": "2025-05", + "release_date": "2025-11-24", + "last_updated": "2025-11-24", "type": "chat" }, { - "id": "zai/glm-4.7-flashx", - "name": "GLM-4.7 FlashX (Z AI)", - "display_name": "GLM-4.7 FlashX (Z AI)", + "id": "claude-haiku-4-5", + "name": "Claude Haiku 4.5 (latest)", + "display_name": "Claude Haiku 4.5 (latest)", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" @@ -106378,88 +107161,78 @@ }, "limit": { "context": 200000, - "output": 128000 + "output": 64000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-04", - "release_date": "2026-01-19", - "last_updated": "2026-01-19", - "type": "chat" - }, - { - "id": "zai/glm-4.5-airx", - "name": "GLM-4.5 AirX (Z AI)", - "display_name": "GLM-4.5 AirX (Z AI)", - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 128000 - }, - "temperature": true, - "tool_call": true, - "reasoning": { - "supported": false + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": false, + "mode": "budget", + "budget": { + "min": 1024, + "unit": "tokens" + }, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", + "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." + ] + } }, - "attachment": false, + "attachment": true, "open_weights": false, - "release_date": "2025-07-28", - "last_updated": "2025-07-28", + "knowledge": "2025-02-28", + "release_date": "2025-10-15", + "last_updated": "2025-10-15", "type": "chat" }, { - "id": "zai/glm-4.5v", - "name": "GLM-4.5V (Z AI)", - "display_name": "GLM-4.5V (Z AI)", + "id": "qwen3-30b-a3b-instruct-2507", + "name": "qwen3-30b-a3b-instruct-2507", + "display_name": "qwen3-30b-a3b-instruct-2507", "modalities": { "input": [ - "text", - "image", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 16000 + "context": 262000, + "output": 262000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, "default": true }, - "attachment": true, + "attachment": false, "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-08-11", - "last_updated": "2025-08-11", + "release_date": "2025-07-28", + "last_updated": "2025-07-28", "type": "chat" }, { - "id": "zai/glm-5.3-flash", - "name": "GLM-5.3 Flash (Z AI)", - "display_name": "GLM-5.3 Flash (Z AI)", + "id": "claude-sonnet-5", + "name": "Claude Sonnet 5", + "display_name": "Claude Sonnet 5", "modalities": { "input": [ "text", "image", - "video", "pdf" ], "output": [ @@ -106467,10 +107240,10 @@ ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 1000000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -106481,90 +107254,91 @@ "supported": true, "effort_options": [ "low", - "high", - "max" + "medium", + "high" ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-08-26", - "last_updated": "2026-08-26", + "open_weights": false, + "knowledge": "2026-01-31", + "release_date": "2026-06-30", + "last_updated": "2026-06-30", "type": "chat" }, { - "id": "zai/glm-4-32b-0414-128k", - "name": "GLM-4 32B (0414-128k) (Z AI)", - "display_name": "GLM-4 32B (0414-128k) (Z AI)", + "id": "mistral-medium-3.5", + "name": "mistral-medium-3.5", + "display_name": "mistral-medium-3.5", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 128000 + "context": 256000, + "output": 256000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, + "attachment": true, "open_weights": false, - "release_date": "2025-04-14", - "last_updated": "2025-04-14", + "release_date": "2026-04-30", + "last_updated": "2026-04-30", "type": "chat" }, { - "id": "zai/glm-4.6v", - "name": "GLM-4.6V (Z AI)", - "display_name": "GLM-4.6V (Z AI)", + "id": "mistral-nemo-instruct-2407", + "name": "mistral-nemo-instruct-2407", + "display_name": "mistral-nemo-instruct-2407", "modalities": { "input": [ - "text", - "image", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 16000 + "context": 118000, + "output": 118000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { - "supported": true, - "default": true + "supported": false }, - "attachment": true, + "attachment": false, "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-12-08", - "last_updated": "2025-12-08", + "knowledge": "2024-07", + "release_date": "2024-07-18", + "last_updated": "2024-07-18", "type": "chat" }, { - "id": "zai/glm-4.7", - "name": "GLM-4.7 (Z AI)", - "display_name": "GLM-4.7 (Z AI)", + "id": "qwen3.6-35b-a3b", + "name": "Qwen3.6 35B-A3B", + "display_name": "Qwen3.6 35B-A3B", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 128000 + "context": 262000, + "output": 32768 }, "temperature": true, "tool_call": true, @@ -106583,56 +107357,56 @@ ] } }, - "attachment": false, + "attachment": true, "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-12-22", - "last_updated": "2025-12-22", + "release_date": "2026-04-17", + "last_updated": "2026-04-17", "type": "chat" }, { - "id": "zai/glm-4.5-x", - "name": "GLM-4.5 X (Z AI)", - "display_name": "GLM-4.5 X (Z AI)", + "id": "voxtral-small-2507", + "name": "voxtral-small-2507", + "display_name": "voxtral-small-2507", "modalities": { "input": [ - "text" + "text", + "audio" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 128000 + "context": 32000, + "output": 32000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { - "supported": true, - "default": true + "supported": false }, - "attachment": false, - "open_weights": false, - "release_date": "2025-07-28", - "last_updated": "2025-07-28", + "attachment": true, + "open_weights": true, + "release_date": "2025-07-15", + "last_updated": "2025-07-15", "type": "chat" }, { - "id": "zai/glm-4.6", - "name": "GLM-4.6 (Z AI)", - "display_name": "GLM-4.6 (Z AI)", + "id": "deepseek-v4.1-flash-1017", + "name": "DeepSeek V4.1 Flash 1017", + "display_name": "DeepSeek V4.1 Flash 1017", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 131072 + "context": 1048576, + "output": 1048576 }, "temperature": true, "tool_call": true, @@ -106642,20 +107416,24 @@ }, "extra_capabilities": { "reasoning": { - "supported": true + "effort_options": [ + "low", + "medium", + "high" + ] } }, - "attachment": false, + "attachment": true, "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-09-30", - "last_updated": "2025-09-30", + "knowledge": "2025-05", + "release_date": "2026-10-17", + "last_updated": "2026-10-17", "type": "chat" }, { - "id": "zai/glm-4.5-air", - "name": "GLM-4.5 Air (Z AI)", - "display_name": "GLM-4.5 Air (Z AI)", + "id": "llama-3.3-70b-instruct", + "name": "Llama-3.3-70B-Instruct", + "display_name": "Llama-3.3-70B-Instruct", "modalities": { "input": [ "text" @@ -106665,8 +107443,8 @@ ] }, "limit": { - "context": 128000, - "output": 98304 + "context": 131000, + "output": 131000 }, "temperature": true, "tool_call": true, @@ -106675,15 +107453,15 @@ }, "attachment": false, "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-07-28", - "last_updated": "2025-07-28", + "knowledge": "2023-12", + "release_date": "2024-12-06", + "last_updated": "2024-12-06", "type": "chat" }, { - "id": "zai/glm-5.1", - "name": "GLM-5.1 (Z AI)", - "display_name": "GLM-5.1 (Z AI)", + "id": "codestral-2508", + "name": "Codestral 25.08", + "display_name": "Codestral 25.08", "modalities": { "input": [ "text" @@ -106693,45 +107471,24 @@ ] }, "limit": { - "context": 200000, - "output": 128000 + "context": 256000, + "output": 256000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "effort_options": [ - "none", - "minimal", - "low", - "medium", - "high", - "xhigh", - "max" - ] - } + "supported": false }, "attachment": false, - "open_weights": true, - "release_date": "2026-04-07", - "last_updated": "2026-04-07", + "open_weights": false, + "release_date": "2025-07-30", + "last_updated": "2025-07-30", "type": "chat" }, { - "id": "zai/glm-5", - "name": "GLM-5 (Z AI)", - "display_name": "GLM-5 (Z AI)", + "id": "qwen3-coder-next", + "name": "Qwen3 Coder Next", + "display_name": "Qwen3 Coder Next", "modalities": { "input": [ "text" @@ -106741,126 +107498,110 @@ ] }, "limit": { - "context": 202800, - "output": 131100 + "context": 256000, + "output": 256000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "effort_options": [ - "none", - "minimal", - "low", - "medium", - "high", - "xhigh", - "max" - ] - } + "supported": false }, "attachment": false, "open_weights": true, - "release_date": "2026-02-12", - "last_updated": "2026-02-12", + "knowledge": "2025-09", + "release_date": "2026-02-03", + "last_updated": "2026-02-03", "type": "chat" }, { - "id": "zai/glm-5.3", - "name": "GLM-5.3 (Z AI)", - "display_name": "GLM-5.3 (Z AI)", + "id": "gpt-4.1-nano", + "name": "GPT-4.1 nano", + "display_name": "GPT-4.1 nano", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 1047576, + "output": 32768 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "effort_options": [ - "low", - "high", - "max" - ] - } + "supported": false }, - "attachment": false, - "open_weights": true, - "release_date": "2026-08-14", - "last_updated": "2026-08-14", + "attachment": true, + "open_weights": false, + "knowledge": "2024-04", + "release_date": "2025-04-14", + "last_updated": "2025-04-14", "type": "chat" }, { - "id": "zai/glm-4.5", - "name": "GLM-4.5 (Z AI)", - "display_name": "GLM-4.5 (Z AI)", + "id": "claude-haiku-5.5", + "name": "Claude Haiku 5.5", + "display_name": "Claude Haiku 5.5", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 98304 + "context": 1000000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, "default": true }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-07-28", - "last_updated": "2025-07-28", + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "low", + "medium", + "high" + ] + } + }, + "attachment": true, + "open_weights": false, + "knowledge": "2026-06", + "release_date": "2026-10-07", + "last_updated": "2026-10-07", "type": "chat" }, { - "id": "zai/glm-4.6v-flashx", - "name": "GLM-4.6V FlashX (Z AI)", - "display_name": "GLM-4.6V FlashX (Z AI)", + "id": "nova-2-lite", + "name": "Nova 2 Lite", + "display_name": "Nova 2 Lite", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 16000 + "context": 1000000, + "output": 65535 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -106868,27 +107609,29 @@ }, "attachment": true, "open_weights": false, - "release_date": "2025-12-08", - "last_updated": "2025-12-08", + "knowledge": "2025-10", + "release_date": "2025-12-02", + "last_updated": "2025-12-01", "type": "chat" }, { - "id": "zai/glm-5.2", - "name": "GLM-5.2 (Z AI)", - "display_name": "GLM-5.2 (Z AI)", + "id": "gpt-5-nano", + "name": "GPT-5 Nano", + "display_name": "GPT-5 Nano", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, + "context": 400000, "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -106897,69 +107640,64 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], + "default_enabled": true, + "mode": "effort", + "effort": "medium", "effort_options": [ - "none", - "high", - "max" - ] + "minimal", + "low", + "medium", + "high" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-06-13", - "last_updated": "2026-06-13", + "attachment": true, + "open_weights": false, + "knowledge": "2024-05-30", + "release_date": "2025-08-07", + "last_updated": "2025-08-07", "type": "chat" }, { - "id": "xai/grok-4-6", - "name": "Grok 4.6 (xAI)", - "display_name": "Grok 4.6 (xAI)", + "id": "mistral-small-3.2-24b-instruct-2506", + "name": "mistral-small-3.2-24b-instruct-2506", + "display_name": "mistral-small-3.2-24b-instruct-2506", "modalities": { "input": [ - "text", - "image" + "image", + "text" ], "output": [ "text" ] }, "limit": { - "context": 500000, - "output": 500000 + "context": 131000, + "output": 131000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "low", - "medium", - "high", - "xhigh" - ] - } + "supported": false }, "attachment": true, - "open_weights": false, - "knowledge": "2026-02-01", - "release_date": "2026-08-12", - "last_updated": "2026-08-12", + "open_weights": true, + "knowledge": "2023-10-31", + "release_date": "2025-06-20", + "last_updated": "2025-06-20", "type": "chat" }, { - "id": "xai/grok-4-20-beta-0309-non-reasoning", - "name": "Grok 4.20 Beta Non-Reasoning (0309) (xAI)", - "display_name": "Grok 4.20 Beta Non-Reasoning (0309) (xAI)", + "id": "nova-lite-v1", + "name": "Nova Lite 1.0", + "display_name": "Nova Lite 1.0", "modalities": { "input": [ "text", @@ -106971,37 +107709,37 @@ ] }, "limit": { - "context": 2000000, - "output": 30000 + "context": 300000, + "output": 10000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": false }, "attachment": true, "open_weights": false, - "release_date": "2026-03-09", - "last_updated": "2026-03-09", + "knowledge": "2024-10-31", + "release_date": "2024-12-05", + "last_updated": "2024-12-05", "type": "chat" }, { - "id": "xai/grok-4-7", - "name": "Grok 4.7 (xAI)", - "display_name": "Grok 4.7 (xAI)", + "id": "glm-5.3-flash", + "name": "GLM-5.3-Flash", + "display_name": "GLM-5.3-Flash", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 500000, - "output": 500000 + "context": 1048576, + "output": 1048576 }, "temperature": true, "tool_call": true, @@ -107011,29 +107749,27 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, "effort_options": [ "low", - "medium", "high", - "xhigh" + "max" ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2026-05", - "release_date": "2026-09-21", - "last_updated": "2026-09-21", + "open_weights": true, + "release_date": "2026-08-26", + "last_updated": "2026-08-26", "type": "chat" }, { - "id": "xai/grok-4-3", - "name": "Grok 4.3 (xAI)", - "display_name": "Grok 4.3 (xAI)", + "id": "mistral-7b-instruct-v0.2", + "name": "mistral-7b-instruct-v0.2", + "display_name": "mistral-7b-instruct-v0.2", "modalities": { "input": [ "text", - "image", "pdf" ], "output": [ @@ -107041,48 +107777,37 @@ ] }, "limit": { - "context": 1000000, - "output": 1000000 + "context": 32000, + "output": 8192 }, - "temperature": true, - "tool_call": true, + "temperature": false, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "none", - "low", - "medium", - "high" - ] - } + "supported": false }, "attachment": true, "open_weights": false, - "release_date": "2026-04-17", - "last_updated": "2026-04-17", + "release_date": "2025-05-26", + "last_updated": "2025-05-26", "type": "chat" }, { - "id": "xai/grok-build-0-1", - "name": "Grok Build 0.1 (xAI)", - "display_name": "Grok Build 0.1 (xAI)", + "id": "gemini-3.6-flash", + "name": "Gemini 3.6 Flash", + "display_name": "Gemini 3.6 Flash", "modalities": { "input": [ "text", "image", - "pdf" + "audio" ], "output": [ "text" ] }, "limit": { - "context": 256000, - "output": 256000 + "context": 1048576, + "output": 65535 }, "temperature": true, "tool_call": true, @@ -107092,23 +107817,34 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ + "supported": true, + "default_enabled": true, + "mode": "level", + "level": "high", + "level_options": [ + "minimal", "low", "medium", "high" + ], + "summaries": true, + "visibility": "summary", + "continuation": [ + "thought_signatures" ] } }, "attachment": true, "open_weights": false, - "release_date": "2026-04-16", - "last_updated": "2026-04-16", + "knowledge": "2026-03", + "release_date": "2026-07-21", + "last_updated": "2026-07-21", "type": "chat" }, { - "id": "xai/grok-4", - "name": "Grok 4 (xAI)", - "display_name": "Grok 4 (xAI)", + "id": "ministral-8b-2512", + "name": "Ministral 3 8B 2512", + "display_name": "Ministral 3 8B 2512", "modalities": { "input": [ "text", @@ -107122,78 +107858,62 @@ "context": 256000, "output": 256000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true - } + "supported": false }, "attachment": true, - "open_weights": false, - "release_date": "2025-07-09", - "last_updated": "2025-07-09", + "open_weights": true, + "release_date": "2025-12-02", + "last_updated": "2025-12-02", "type": "chat" }, { - "id": "xai/grok-4-20-beta-0309-reasoning", - "name": "Grok 4.20 Beta Reasoning (0309) (xAI)", - "display_name": "Grok 4.20 Beta Reasoning (0309) (xAI)", + "id": "gemma-3-27b-it", + "name": "Gemma 3 27B IT", + "display_name": "Gemma 3 27B IT", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 2000000, - "output": 30000 + "context": 131000, + "output": 110000 }, - "temperature": true, - "tool_call": true, + "temperature": false, + "tool_call": false, "reasoning": { "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "low", - "medium", - "high" - ] - } - }, "attachment": true, - "open_weights": false, - "release_date": "2026-03-09", - "last_updated": "2026-03-09", + "open_weights": true, + "knowledge": "2024-08", + "release_date": "2025-03-12", + "last_updated": "2025-03-12", "type": "chat" }, { - "id": "xai/grok-4-5", - "name": "Grok 4.5 (xAI)", - "display_name": "Grok 4.5 (xAI)", + "id": "deepseek-v3.2", + "name": "DeepSeek V3.2", + "display_name": "DeepSeek V3.2", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 500000, - "output": 500000 + "context": 163840, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -107203,6 +107923,7 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, "effort_options": [ "low", "medium", @@ -107210,70 +107931,58 @@ ] } }, - "attachment": true, - "open_weights": false, - "release_date": "2026-07-08", - "last_updated": "2026-07-08", + "attachment": false, + "open_weights": true, + "knowledge": "2024-07", + "release_date": "2025-12-01", + "last_updated": "2025-12-01", "type": "chat" }, { - "id": "sakana/fugu-max", - "name": "Fugu Max (Sakana AI)", - "display_name": "Fugu Max (Sakana AI)", + "id": "ministral-3b-2512", + "name": "Ministral 3 3B 2512", + "display_name": "Ministral 3 3B 2512", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 1000000 + "context": 256000, + "output": 256000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "high", - "xhigh", - "max" - ] - } + "supported": false }, "attachment": true, - "open_weights": false, - "release_date": "2026-09-11", - "last_updated": "2026-09-11", + "open_weights": true, + "release_date": "2025-12-02", + "last_updated": "2025-12-02", "type": "chat" }, { - "id": "sakana/fugu-ultra-v2.0", - "name": "Fugu Ultra v2.0 (Sakana AI)", - "display_name": "Fugu Ultra v2.0 (Sakana AI)", + "id": "kolibri-1", + "name": "kolibri-1", + "display_name": "kolibri-1", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 1000000 + "context": 262144, + "output": 81920 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -107282,37 +107991,40 @@ "extra_capabilities": { "reasoning": { "effort_options": [ - "high", - "xhigh", - "max" + "none", + "low", + "medium", + "high" ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-08-28", - "release_date": "2026-09-11", - "last_updated": "2026-09-11", + "attachment": false, + "open_weights": true, + "knowledge": "2026-06-18", + "release_date": "2026-10-03", + "last_updated": "2026-10-03", "type": "chat" }, { - "id": "sakana/fugu-ultra", - "name": "Fugu Ultra (Sakana AI)", - "display_name": "Fugu Ultra (Sakana AI)", + "id": "gemini-3.8-flash", + "name": "Gemini 3.8 Flash", + "display_name": "Gemini 3.8 Flash", "modalities": { "input": [ "text", - "image" + "image", + "audio", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 1000000 + "context": 1048576, + "output": 65535 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -107320,28 +108032,37 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "high", - "xhigh", - "max" + "supported": true, + "default_enabled": true, + "mode": "level", + "level": "high", + "level_options": [ + "minimal", + "low", + "medium", + "high" + ], + "summaries": true, + "visibility": "summary", + "continuation": [ + "thought_signatures" ] } }, "attachment": true, "open_weights": false, - "release_date": "2026-06-15", - "last_updated": "2026-06-15", + "release_date": "2026-09-02", + "last_updated": "2026-09-02", "type": "chat" }, { - "id": "entrim/glm-5.3-flash", - "name": "GLM-5.3 Flash (entrim)", - "display_name": "GLM-5.3 Flash (entrim)", + "id": "gpt-5.6-luna", + "name": "GPT-5.6 Luna", + "display_name": "GPT-5.6 Luna", "modalities": { "input": [ "text", "image", - "video", "pdf" ], "output": [ @@ -107349,10 +108070,10 @@ ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 1050000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -107361,79 +108082,81 @@ "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", "effort_options": [ + "none", "low", "medium", "high", "xhigh", "max" - ] + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, "attachment": true, - "open_weights": true, - "release_date": "2026-08-26", - "last_updated": "2026-08-26", + "open_weights": false, + "knowledge": "2026-02-16", + "release_date": "2026-07-09", + "last_updated": "2026-07-09", "type": "chat" }, { - "id": "entrim/deepseek-v4.1-flash-1017", - "name": "DeepSeek V4.1 Flash 1017 (entrim)", - "display_name": "DeepSeek V4.1 Flash 1017 (entrim)", + "id": "nova-pro-v1", + "name": "Nova Pro 1.0", + "display_name": "Nova Pro 1.0", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 384000 + "context": 300000, + "output": 10000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" - ] - } + "supported": false }, "attachment": true, - "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-10-17", - "last_updated": "2026-10-17", + "open_weights": false, + "knowledge": "2024-10-31", + "release_date": "2024-12-05", + "last_updated": "2024-12-05", "type": "chat" }, { - "id": "entrim/glm-5.3", - "name": "GLM-5.3 (entrim)", - "display_name": "GLM-5.3 (entrim)", + "id": "gpt-5", + "name": "GPT-5", + "display_name": "GPT-5", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 400000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -107442,30 +108165,39 @@ "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", "effort_options": [ + "minimal", "low", "medium", - "high", - "xhigh", - "max" - ] + "high" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-08-14", - "last_updated": "2026-08-14", + "attachment": true, + "open_weights": false, + "knowledge": "2024-09-30", + "release_date": "2025-08-07", + "last_updated": "2025-08-07", "type": "chat" }, { - "id": "inference.net/glm-5.3-flash", - "name": "GLM-5.3 Flash (Inference.net)", - "display_name": "GLM-5.3 Flash (Inference.net)", + "id": "claude-sonnet-5.5", + "name": "Claude Sonnet 5.5", + "display_name": "Claude Sonnet 5.5", "modalities": { "input": [ "text", "image", - "video", "pdf" ], "output": [ @@ -107473,10 +108205,10 @@ ] }, "limit": { - "context": 1048576, + "context": 1000000, "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -107484,36 +108216,37 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, "effort_options": [ "low", - "high", - "max" + "medium", + "high" ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-08-26", - "last_updated": "2026-08-26", + "open_weights": false, + "knowledge": "2026-06", + "release_date": "2026-09-28", + "last_updated": "2026-09-28", "type": "chat" }, { - "id": "inference.net/deepseek-v4.1-flash-1017", - "name": "DeepSeek V4.1 Flash 1017 (Inference.net)", - "display_name": "DeepSeek V4.1 Flash 1017 (Inference.net)", + "id": "gemini-2.5-pro", + "name": "Gemini 2.5 Pro", + "display_name": "Gemini 2.5 Pro", "modalities": { "input": [ "text", - "image" + "image", + "audio" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 131072 + "context": 1048576, + "output": 65535 }, "temperature": true, "tool_call": true, @@ -107523,41 +108256,48 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "none", - "low", - "high", - "xhigh", - "max" + "supported": true, + "default_enabled": true, + "mode": "budget", + "budget": { + "default": -1, + "min": 128, + "max": 32768, + "auto": -1, + "unit": "tokens" + }, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thought_signatures" ] } }, "attachment": true, - "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-10-17", - "last_updated": "2026-10-17", + "open_weights": false, + "knowledge": "2025-01", + "release_date": "2025-06-17", + "last_updated": "2025-06-17", "type": "chat" }, { - "id": "inference.net/minimax-m3.1-flash", - "name": "MiniMax M3.1 Flash (Inference.net)", - "display_name": "MiniMax M3.1 Flash (Inference.net)", + "id": "gpt-5-mini", + "name": "GPT-5 Mini", + "display_name": "GPT-5 Mini", "modalities": { "input": [ "text", - "image", - "video" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 524288 + "context": 400000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -107565,103 +108305,105 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", "effort_options": [ + "minimal", "low", "medium", - "high", - "xhigh", - "max" - ] + "high" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, "attachment": true, "open_weights": false, - "release_date": "2026-10-19", - "last_updated": "2026-10-19", + "knowledge": "2024-05-30", + "release_date": "2025-08-07", + "last_updated": "2025-08-07", "type": "chat" }, { - "id": "inference.net/deepseek-v4.1-flash-cyber", - "name": "DeepSeek V4.1 Flash Cyber (Inference.net)", - "display_name": "DeepSeek V4.1 Flash Cyber (Inference.net)", + "id": "mistral-large-2402", + "name": "mistral-large-2402", + "display_name": "mistral-large-2402", "modalities": { "input": [ "text", - "image" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1040000, - "output": 393216 + "context": 32000, + "output": 8192 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "none", - "low", - "high", - "xhigh", - "max" - ] - } - }, "attachment": true, "open_weights": false, - "release_date": "2026-10-29", - "last_updated": "2026-10-29", + "release_date": "2025-05-26", + "last_updated": "2025-05-26", "type": "chat" }, { - "id": "inference.net/glm-5.3", - "name": "GLM-5.3 (Inference.net)", - "display_name": "GLM-5.3 (Inference.net)", + "id": "qwen3-vl-235b-a22b", + "name": "qwen3-vl-235b-a22b", + "display_name": "qwen3-vl-235b-a22b", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 256000, + "output": 256000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { - "supported": true, - "default": true + "supported": true }, "extra_capabilities": { "reasoning": { "supported": true, - "effort_options": [ - "low", - "high", - "max" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, - "attachment": false, + "attachment": true, "open_weights": true, - "release_date": "2026-08-14", - "last_updated": "2026-08-14", + "knowledge": "2025-03-31", + "release_date": "2025-09-23", + "last_updated": "2025-09-23", "type": "chat" }, { - "id": "inference.net/llama-3.2-11b-instruct", - "name": "Llama 3.2 11B Instruct (Inference.net)", - "display_name": "Llama 3.2 11B Instruct (Inference.net)", + "id": "hermes-4-405b", + "name": "Hermes 4 405B", + "display_name": "Hermes 4 405B", "modalities": { "input": [ "text" @@ -107674,35 +108416,34 @@ "context": 128000, "output": 128000 }, - "temperature": true, - "tool_call": false, + "temperature": false, + "tool_call": true, "reasoning": { "supported": false }, "attachment": false, - "open_weights": false, - "release_date": "2024-09-25", - "last_updated": "2024-09-25", + "open_weights": true, + "knowledge": "2024-08-31", + "release_date": "2025-08-26", + "last_updated": "2025-08-26", "type": "chat" }, { - "id": "xiaomi/mimo-v2.6-pro", - "name": "MiMo V2.6 Pro (Xiaomi)", - "display_name": "MiMo V2.6 Pro (Xiaomi)", + "id": "deepseek-v4.1-flash", + "name": "DeepSeek V4.1 Flash", + "display_name": "DeepSeek V4.1 Flash", "modalities": { "input": [ "text", - "image", - "audio", - "video" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 1048576, + "output": 393216 }, "temperature": true, "tool_call": true, @@ -107712,8 +108453,8 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, "effort_options": [ - "none", "low", "medium", "high" @@ -107722,68 +108463,56 @@ }, "attachment": true, "open_weights": true, - "release_date": "2026-09-22", - "last_updated": "2026-09-22", + "knowledge": "2025-05", + "release_date": "2026-09-10", + "last_updated": "2026-09-10", "type": "chat" }, { - "id": "xiaomi/mimo-v2.6-flash", - "name": "MiMo V2.6 Flash (Xiaomi)", - "display_name": "MiMo V2.6 Flash (Xiaomi)", + "id": "gpt-4o-mini", + "name": "GPT-4o mini", + "display_name": "GPT-4o mini", "modalities": { "input": [ "text", "image", - "audio", - "video" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 128000, + "output": 16000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "none", - "low", - "medium", - "high" - ] - } + "supported": false }, "attachment": true, - "open_weights": true, - "release_date": "2026-09-22", - "last_updated": "2026-09-22", + "open_weights": false, + "knowledge": "2023-09", + "release_date": "2024-07-18", + "last_updated": "2024-07-18", "type": "chat" }, { - "id": "google-ai-studio/gemini-pro-latest", - "name": "Gemini Pro Latest (Google AI Studio)", - "display_name": "Gemini Pro Latest (Google AI Studio)", + "id": "gpt-oss-120b", + "name": "GPT OSS 120B", + "display_name": "GPT OSS 120B", "modalities": { "input": [ - "text", - "image", - "audio" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 131000, + "output": 131000 }, "temperature": true, "tool_call": true, @@ -107793,6 +108522,7 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, "effort_options": [ "low", "medium", @@ -107800,31 +108530,29 @@ ] } }, - "attachment": true, - "open_weights": false, - "release_date": "2026-02-27", - "last_updated": "2026-02-27", + "attachment": false, + "open_weights": true, + "knowledge": "2024-06", + "release_date": "2025-08-05", + "last_updated": "2025-08-05", "type": "chat" }, { - "id": "google-ai-studio/gemini-3-flash-preview", - "name": "Gemini 3 Flash (Preview) (Google AI Studio)", - "display_name": "Gemini 3 Flash (Preview) (Google AI Studio)", + "id": "qwen3.8-27b", + "name": "Qwen3.8 27B", + "display_name": "Qwen3.8 27B", "modalities": { "input": [ "text", - "image", - "video", - "audio", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 65535 + "context": 1000000, + "output": 500000 }, "temperature": true, "tool_call": true, @@ -107834,40 +108562,27 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "level", - "level": "high", - "level_options": [ - "minimal", + "effort_options": [ "low", "medium", - "high" - ], - "summaries": true, - "visibility": "summary", - "continuation": [ - "thought_signatures" + "xhigh" ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2025-01", - "release_date": "2025-12-17", - "last_updated": "2025-12-17", + "open_weights": true, + "release_date": "2026-08-14", + "last_updated": "2026-08-14", "type": "chat" }, { - "id": "google-ai-studio/gemini-3.5-flash-lite", - "name": "Gemini 3.5 Flash Lite (Google AI Studio)", - "display_name": "Gemini 3.5 Flash Lite (Google AI Studio)", + "id": "gpt-4o", + "name": "GPT-4o", + "display_name": "GPT-4o", "modalities": { "input": [ "text", "image", - "video", - "audio", "pdf" ], "output": [ @@ -107875,52 +108590,37 @@ ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 128000, + "output": 16000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "effort_options": [ - "minimal", - "low", - "medium", - "high" - ] - } + "supported": false }, "attachment": true, "open_weights": false, - "knowledge": "2026-03", - "release_date": "2026-07-21", - "last_updated": "2026-07-21", + "knowledge": "2023-09", + "release_date": "2024-05-13", + "last_updated": "2024-08-06", "type": "chat" }, { - "id": "google-ai-studio/gemini-3.1-flash-lite", - "name": "Gemini 3.1 Flash Lite (Google AI Studio)", - "display_name": "Gemini 3.1 Flash Lite (Google AI Studio)", + "id": "qwen3.8-flash-next", + "name": "Qwen3.8 Flash Next", + "display_name": "Qwen3.8 Flash Next", "modalities": { "input": [ "text", - "image", - "video", - "audio", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 262144, + "output": 64000 }, "temperature": true, "tool_call": true, @@ -107930,41 +108630,62 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, "effort_options": [ - "minimal", "low", "medium", - "high" + "xhigh" ] } }, "attachment": true, + "open_weights": true, + "release_date": "2026-08-27", + "last_updated": "2026-08-27", + "type": "chat" + }, + { + "id": "minicpm-v-4.5", + "name": "minicpm-v-4.5", + "display_name": "minicpm-v-4.5", + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 32000, + "output": 32000 + }, + "temperature": false, + "tool_call": true, + "reasoning": { + "supported": false + }, + "attachment": false, "open_weights": false, - "knowledge": "2025-01", - "release_date": "2026-05-07", - "last_updated": "2026-05-07", + "release_date": "2026-06-02", + "last_updated": "2026-06-02", "type": "chat" }, { - "id": "google-ai-studio/gemini-3.1-pro-preview", - "name": "Gemini 3.1 Pro (Preview) (Google AI Studio)", - "display_name": "Gemini 3.1 Pro (Preview) (Google AI Studio)", + "id": "qwen3.6-27b", + "name": "Qwen3.6 27B", + "display_name": "Qwen3.6 27B", "modalities": { "input": [ "text", - "image", - "video", - "audio", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 262000, + "output": 262000 }, "temperature": true, "tool_call": true, @@ -107975,46 +108696,35 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "level", - "level": "high", - "level_options": [ - "low", - "high" - ], + "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ - "thought_signatures" + "thinking_blocks" ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2025-01", - "release_date": "2026-02-19", - "last_updated": "2026-02-19", + "open_weights": true, + "release_date": "2026-04-22", + "last_updated": "2026-04-22", "type": "chat" }, { - "id": "google-ai-studio/gemini-3.8-flash", - "name": "Gemini 3.8 Flash (Google AI Studio)", - "display_name": "Gemini 3.8 Flash (Google AI Studio)", + "id": "deepseek-v4-pro", + "name": "DeepSeek V4 Pro", + "display_name": "DeepSeek V4 Pro", "modalities": { "input": [ - "text", - "image", - "video", - "audio", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 1000000, + "output": 384000 }, "temperature": true, "tool_call": true, @@ -108026,38 +108736,38 @@ "reasoning": { "supported": true, "default_enabled": true, - "mode": "level", - "level": "high", - "level_options": [ - "minimal", + "mode": "effort", + "effort": "high", + "effort_options": [ "low", - "medium", - "high" + "high", + "max" ], + "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ - "thought_signatures" + "thinking_blocks" + ], + "notes": [ + "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." ] } }, - "attachment": true, - "open_weights": false, - "release_date": "2026-09-02", - "last_updated": "2026-09-02", + "attachment": false, + "open_weights": true, + "knowledge": "2025-05", + "release_date": "2026-04-24", + "last_updated": "2026-04-24", "type": "chat" }, { - "id": "google-ai-studio/gemini-4-argon", - "name": "Gemini 4 Argon (Google AI Studio)", - "display_name": "Gemini 4 Argon (Google AI Studio)", + "id": "glm-5.3", + "name": "GLM-5.3", + "display_name": "GLM-5.3", "modalities": { "input": [ - "text", - "image", - "video", - "audio", - "pdf" + "text" ], "output": [ "text" @@ -108067,7 +108777,7 @@ "context": 1048576, "output": 1048576 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -108075,28 +108785,28 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, "effort_options": [ - "high" + "low", + "high", + "max" ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-03", - "release_date": "2026-10-13", - "last_updated": "2026-10-13", + "attachment": false, + "open_weights": true, + "release_date": "2026-08-14", + "last_updated": "2026-08-14", "type": "chat" }, { - "id": "google-ai-studio/gemini-3.6-flash", - "name": "Gemini 3.6 Flash (Google AI Studio)", - "display_name": "Gemini 3.6 Flash (Google AI Studio)", + "id": "gpt-5.6-sol", + "name": "GPT-5.6 Sol", + "display_name": "GPT-5.6 Sol", "modalities": { "input": [ "text", "image", - "video", - "audio", "pdf" ], "output": [ @@ -108104,10 +108814,10 @@ ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 1050000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -108117,89 +108827,78 @@ "reasoning": { "supported": true, "default_enabled": true, - "mode": "level", - "level": "high", - "level_options": [ - "minimal", + "mode": "effort", + "effort": "medium", + "effort_options": [ + "none", + "low", + "medium", + "high", + "xhigh", + "max" + ], + "verbosity": "medium", + "verbosity_options": [ "low", "medium", "high" ], - "summaries": true, - "visibility": "summary", - "continuation": [ - "thought_signatures" - ] + "visibility": "hidden" } }, "attachment": true, "open_weights": false, - "knowledge": "2026-03", - "release_date": "2026-07-21", - "last_updated": "2026-07-21", + "knowledge": "2026-02-16", + "release_date": "2026-07-09", + "last_updated": "2026-07-09", "type": "chat" }, { - "id": "anthropic/claude-sonnet-5-5", - "name": "Claude Sonnet 5.5 (Anthropic)", - "display_name": "Claude Sonnet 5.5 (Anthropic)", + "id": "apertus-70b", + "name": "Apertus 70B", + "display_name": "Apertus 70B", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 65536, + "output": 16384 }, "temperature": false, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" - ] - } + "supported": false }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-06", - "release_date": "2026-09-28", - "last_updated": "2026-09-28", + "attachment": false, + "open_weights": true, + "knowledge": "2025-09", + "release_date": "2025-09-02", + "last_updated": "2025-09-02", "type": "chat" }, { - "id": "anthropic/claude-haiku-5-5", - "name": "Claude Haiku 5.5 (Anthropic)", - "display_name": "Claude Haiku 5.5 (Anthropic)", + "id": "qwen3.5-9b", + "name": "Qwen3.5 9B", + "display_name": "Qwen3.5 9B", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 262000, + "output": 262000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -108207,27 +108906,25 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "none", - "low", - "medium", - "high", - "xhigh", - "max" + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2026-06", - "release_date": "2026-10-07", - "last_updated": "2026-10-07", + "open_weights": true, + "release_date": "2026-02-23", + "last_updated": "2026-02-23", "type": "chat" }, { - "id": "anthropic/claude-opus-5", - "name": "Claude Opus 5 (Anthropic)", - "display_name": "Claude Opus 5 (Anthropic)", + "id": "claude-opus-5.5", + "name": "Claude Opus 5.5", + "display_name": "Claude Opus 5.5", "modalities": { "input": [ "text", @@ -108250,28 +108947,24 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, "effort_options": [ - "none", "low", "medium", - "high", - "xhigh", - "max" + "high" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-05", - "release_date": "2026-07-24", - "last_updated": "2026-07-24", + "knowledge": "2026-06", + "release_date": "2026-09-22", + "last_updated": "2026-09-22", "type": "chat" }, { - "id": "anthropic/claude-opus-4-7", - "name": "Claude Opus 4.7 (Anthropic)", - "display_name": "Claude Opus 4.7 (Anthropic)", + "id": "gpt-5.1", + "name": "GPT-5.1", + "display_name": "GPT-5.1", "modalities": { "input": [ "text", @@ -108283,10 +108976,10 @@ ] }, "limit": { - "context": 1000000, + "context": 400000, "output": 128000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -108297,42 +108990,38 @@ "supported": true, "default_enabled": false, "mode": "effort", - "effort": "high", + "effort": "none", "effort_options": [ + "none", "low", "medium", - "high", - "xhigh", - "max" + "high" ], - "interleaved": true, - "summaries": true, - "visibility": "omitted", - "continuation": [ - "thinking_blocks" + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" ], - "notes": [ - "Claude Opus 4.7 and newer Opus models require thinking.type = \"adaptive\" to enable thinking explicitly.", - "Manual budget_tokens requests return 400 on Claude Opus 4.7 and newer adaptive-only Opus models.", - "task_budget is separate from thinking control and should not be treated as a thinking budget." - ] + "visibility": "hidden" } }, "attachment": true, "open_weights": false, - "knowledge": "2026-01-31", - "release_date": "2026-04-16", - "last_updated": "2026-04-16", + "knowledge": "2024-09-30", + "release_date": "2025-11-13", + "last_updated": "2025-11-13", "type": "chat" }, { - "id": "anthropic/claude-opus-5-5", - "name": "Claude Opus 5.5 (Anthropic)", - "display_name": "Claude Opus 5.5 (Anthropic)", + "id": "gemini-2.5-flash", + "name": "Gemini 2.5 Flash", + "display_name": "Gemini 2.5 Flash", "modalities": { "input": [ "text", "image", + "audio", "pdf" ], "output": [ @@ -108340,10 +109029,10 @@ ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 1048576, + "output": 65535 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -108351,41 +109040,48 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" + "supported": true, + "default_enabled": true, + "mode": "budget", + "budget": { + "default": -1, + "min": 0, + "max": 24576, + "auto": -1, + "off": 0, + "unit": "tokens" + }, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thought_signatures" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-06", - "release_date": "2026-09-22", - "last_updated": "2026-09-22", + "knowledge": "2025-01", + "release_date": "2025-06-17", + "last_updated": "2025-06-17", "type": "chat" }, { - "id": "anthropic/claude-sonnet-5", - "name": "Claude Sonnet 5 (Anthropic)", - "display_name": "Claude Sonnet 5 (Anthropic)", + "id": "minimax-m2", + "name": "MiniMax-M2", + "display_name": "MiniMax-M2", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 400000, + "output": 196000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -108394,27 +109090,52 @@ "extra_capabilities": { "reasoning": { "supported": true, - "effort_options": [ - "none", - "low", - "medium", - "high", - "xhigh", - "max" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-01-31", - "release_date": "2026-06-30", - "last_updated": "2026-06-30", + "attachment": false, + "open_weights": true, + "release_date": "2025-10-27", + "last_updated": "2025-10-27", "type": "chat" }, { - "id": "anthropic/claude-sonnet-4-6", - "name": "Claude Sonnet 4.6 (Anthropic)", - "display_name": "Claude Sonnet 4.6 (Anthropic)", + "id": "qwen3-coder-30b-a3b-instruct", + "name": "Qwen3-Coder 30B-A3B Instruct", + "display_name": "Qwen3-Coder 30B-A3B Instruct", + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 262144, + "output": 262144 + }, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": false + }, + "attachment": false, + "open_weights": true, + "knowledge": "2025-04", + "release_date": "2025-07-31", + "last_updated": "2025-07-31", + "type": "chat" + }, + { + "id": "claude-4-6-sonnet", + "name": "Claude Sonnet 4.6", + "display_name": "Claude Sonnet 4.6", "modalities": { "input": [ "text", @@ -108427,39 +109148,20 @@ }, "limit": { "context": 1000000, - "output": 64000 + "output": 128000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "mixed", - "budget": { - "min": 1024, - "unit": "tokens" - }, - "effort": "high", "effort_options": [ "low", "medium", - "high", - "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Anthropic recommends adaptive thinking with effort for Claude 4.6; budget_tokens remains a deprecated compatibility path.", - "Anthropic API defaults effort to high; lower effort levels should be chosen per workload." + "high" ] } }, @@ -108471,176 +109173,148 @@ "type": "chat" }, { - "id": "anthropic/claude-opus-4-6", - "name": "Claude Opus 4.6 (Anthropic)", - "display_name": "Claude Opus 4.6 (Anthropic)", + "id": "qwen3guard-gen-0.6b", + "name": "qwen3guard-gen-0.6b", + "display_name": "qwen3guard-gen-0.6b", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 32000, + "output": 32000 + }, + "temperature": false, + "tool_call": false, + "reasoning": { + "supported": false + }, + "attachment": false, + "open_weights": false, + "release_date": "2026-02-04", + "last_updated": "2026-02-04", + "type": "chat" + }, + { + "id": "glm-4.7-flash", + "name": "GLM-4.7-Flash", + "display_name": "GLM-4.7-Flash", + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 203000, + "output": 203000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "mixed", - "budget": { - "min": 1024, - "unit": "tokens" - }, - "effort": "high", - "effort_options": [ - "low", - "medium", - "high", - "max" - ], "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ "thinking_blocks" - ], - "notes": [ - "Anthropic recommends adaptive thinking with effort for Claude 4.6; budget_tokens remains a deprecated compatibility path.", - "Anthropic API defaults effort to high; lower effort levels should be chosen per workload." ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-05-31", - "release_date": "2026-02-05", - "last_updated": "2026-03-13", + "attachment": false, + "open_weights": true, + "knowledge": "2025-04", + "release_date": "2026-01-19", + "last_updated": "2026-01-19", "type": "chat" }, { - "id": "anthropic/claude-sonnet-4-5-20250929", - "name": "Claude Sonnet 4.5 (2025-09-29) (Anthropic)", - "display_name": "Claude Sonnet 4.5 (2025-09-29) (Anthropic)", + "id": "glm-5.1", + "name": "GLM-5.1", + "display_name": "GLM-5.1", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 64000 + "context": 202752, + "output": 202752 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "budget", - "budget": { - "min": 1024, - "unit": "tokens" - }, "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ "thinking_blocks" - ], - "notes": [ - "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", - "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-07-31", - "release_date": "2025-09-29", - "last_updated": "2025-09-29", + "attachment": false, + "open_weights": true, + "release_date": "2026-04-07", + "last_updated": "2026-04-07", "type": "chat" }, { - "id": "anthropic/claude-haiku-4-5", - "name": "Claude Haiku 4.5 (Anthropic)", - "display_name": "Claude Haiku 4.5 (Anthropic)", + "id": "devstral-2512", + "name": "Devstral 2", + "display_name": "Devstral 2", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 64000 + "context": 256000, + "output": 256000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": false - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "budget", - "budget": { - "min": 1024, - "unit": "tokens" - }, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", - "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." - ] - } + "supported": false }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-02-28", - "release_date": "2025-10-15", - "last_updated": "2025-10-15", + "attachment": false, + "open_weights": true, + "knowledge": "2025-12", + "release_date": "2025-12-09", + "last_updated": "2025-12-09", "type": "chat" }, { - "id": "anthropic/claude-haiku-4-5-20251001", - "name": "Claude Haiku 4.5 (2025-10-01) (Anthropic)", - "display_name": "Claude Haiku 4.5 (2025-10-01) (Anthropic)", + "id": "gemini-3.7-flash", + "name": "Gemini 3.7 Flash", + "display_name": "Gemini 3.7 Flash", "modalities": { "input": [ "text", "image", + "audio", "pdf" ], "output": [ @@ -108648,156 +109322,150 @@ ] }, "limit": { - "context": 200000, - "output": 64000 + "context": 1048576, + "output": 65535 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "budget", - "budget": { - "min": 1024, - "unit": "tokens" - }, - "interleaved": true, + "default_enabled": true, + "mode": "level", + "level": "high", + "level_options": [ + "minimal", + "low", + "medium", + "high" + ], "summaries": true, "visibility": "summary", "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", - "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." + "thought_signatures" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-02-28", - "release_date": "2025-10-15", - "last_updated": "2025-10-15", + "knowledge": "2026-03", + "release_date": "2026-08-13", + "last_updated": "2026-08-13", "type": "chat" }, { - "id": "anthropic/claude-opus-4-8", - "name": "Claude Opus 4.8 (Anthropic)", - "display_name": "Claude Opus 4.8 (Anthropic)", + "id": "qwen3-235b-a22b-instruct-2507", + "name": "Qwen3 235B-A22B Instruct 2507", + "display_name": "Qwen3 235B-A22B Instruct 2507", + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 262000, + "output": 262000 + }, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": false + }, + "attachment": false, + "open_weights": true, + "release_date": "2025-07-21", + "last_updated": "2025-07-21", + "type": "chat" + }, + { + "id": "gemma-4-31b-it", + "name": "Gemma 4 31B IT", + "display_name": "Gemma 4 31B IT", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 262000, + "output": 262000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "high", - "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "omitted", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Claude Opus 4.7 and newer Opus models require thinking.type = \"adaptive\" to enable thinking explicitly.", - "Manual budget_tokens requests return 400 on Claude Opus 4.7 and newer adaptive-only Opus models.", - "task_budget is separate from thinking control and should not be treated as a thinking budget." - ] + "supported": true } }, "attachment": true, - "open_weights": false, - "knowledge": "2026-01", - "release_date": "2026-05-28", - "last_updated": "2026-05-28", + "open_weights": true, + "knowledge": "2025-01", + "release_date": "2026-04-02", + "last_updated": "2026-04-02", "type": "chat" }, { - "id": "anthropic/claude-sonnet-4-5", - "name": "Claude Sonnet 4.5 (Anthropic)", - "display_name": "Claude Sonnet 4.5 (Anthropic)", + "id": "glm-5.2", + "name": "GLM-5.2", + "display_name": "GLM-5.2", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 64000 + "context": 1048576, + "output": 1000000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "budget", - "budget": { - "min": 1024, - "unit": "tokens" - }, "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ "thinking_blocks" ], - "notes": [ - "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", - "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." + "effort_options": [ + "high", + "max" ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-07-31", - "release_date": "2025-09-29", - "last_updated": "2025-09-29", + "attachment": false, + "open_weights": true, + "release_date": "2026-06-13", + "last_updated": "2026-06-13", "type": "chat" }, { - "id": "anthropic/claude-opus-4-5-20251101", - "name": "Claude Opus 4.5 (Anthropic)", - "display_name": "Claude Opus 4.5 (Anthropic)", + "id": "claude-4-5-sonnet", + "name": "Claude Sonnet 4.5 (latest)", + "display_name": "Claude Sonnet 4.5 (latest)", "modalities": { "input": [ "text", @@ -108810,52 +109478,34 @@ }, "limit": { "context": 200000, - "output": 32000 + "output": 64000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "mixed", - "budget": { - "min": 1024, - "unit": "tokens" - }, - "effort": "high", "effort_options": [ "low", "medium", "high" - ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Claude Opus 4.5 uses manual thinking.type = \"enabled\" with budget_tokens; effort can be used alongside the thinking budget.", - "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header." ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-05", - "release_date": "2025-11-24", - "last_updated": "2025-11-24", + "knowledge": "2025-07-31", + "release_date": "2025-09-29", + "last_updated": "2025-09-29", "type": "chat" }, { - "id": "anthropic/claude-fable-5", - "name": "Claude Fable 5 (Anthropic)", - "display_name": "Claude Fable 5 (Anthropic)", + "id": "claude-opus-5", + "name": "Claude Opus 5", + "display_name": "Claude Opus 5", "modalities": { "input": [ "text", @@ -108879,55 +109529,38 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "high", "effort_options": [ "low", "medium", - "high", - "xhigh", - "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "omitted", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Adaptive thinking is always on for Claude Fable 5 and Claude Mythos 5; thinking.type = \"disabled\" is rejected.", - "Manual budget_tokens requests return 400 on Claude Fable 5 and Claude Mythos 5.", - "thinking.display defaults to omitted; set display to summarized to receive readable thinking summaries." + "high" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-01-31", - "release_date": "2026-06-09", - "last_updated": "2026-06-09", + "knowledge": "2026-05", + "release_date": "2026-07-24", + "last_updated": "2026-07-24", "type": "chat" }, { - "id": "anthropic/claude-fable-5-1", - "name": "Claude Fable 5.1 (Anthropic)", - "display_name": "Claude Fable 5.1 (Anthropic)", + "id": "minimax-m3", + "name": "MiniMax-M3", + "display_name": "MiniMax-M3", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 1048576, + "output": 1048576 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -108935,41 +109568,19 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "high", - "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "omitted", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Adaptive thinking is always on for Claude Fable 5 and Claude Mythos 5; thinking.type = \"disabled\" is rejected.", - "Manual budget_tokens requests return 400 on Claude Fable 5 and Claude Mythos 5.", - "thinking.display defaults to omitted; set display to summarized to receive readable thinking summaries." - ] + "supported": true } }, "attachment": true, - "open_weights": false, - "knowledge": "2026-06", - "release_date": "2026-09-01", - "last_updated": "2026-09-01", + "open_weights": true, + "release_date": "2026-06-01", + "last_updated": "2026-06-01", "type": "chat" }, { - "id": "groq/gpt-oss-20b", - "name": "GPT OSS 20B (Groq)", - "display_name": "GPT OSS 20B (Groq)", + "id": "deepseek-v4-pro-0813", + "name": "DeepSeek V4 Pro 0813", + "display_name": "DeepSeek V4 Pro 0813", "modalities": { "input": [ "text" @@ -108979,8 +109590,8 @@ ] }, "limit": { - "context": 131072, - "output": 32766 + "context": 1048576, + "output": 64000 }, "temperature": true, "tool_call": true, @@ -109000,15 +109611,14 @@ }, "attachment": false, "open_weights": true, - "knowledge": "2024-06", - "release_date": "2025-08-05", - "last_updated": "2025-08-05", + "release_date": "2026-08-13", + "last_updated": "2026-08-22", "type": "chat" }, { - "id": "groq/gpt-oss-120b", - "name": "GPT OSS 120B (Groq)", - "display_name": "GPT OSS 120B (Groq)", + "id": "qwen3.5-397b-a17b", + "name": "Qwen3.5 397B-A17B", + "display_name": "Qwen3.5 397B-A17B", "modalities": { "input": [ "text" @@ -109018,8 +109628,8 @@ ] }, "limit": { - "context": 131072, - "output": 32766 + "context": 262000, + "output": 262000 }, "temperature": true, "tool_call": true, @@ -109030,75 +109640,96 @@ "extra_capabilities": { "reasoning": { "supported": true, - "effort_options": [ - "low", - "medium", - "high" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, "attachment": false, "open_weights": true, - "knowledge": "2024-06", - "release_date": "2025-08-05", - "last_updated": "2025-08-05", + "release_date": "2026-02-15", + "last_updated": "2026-02-15", "type": "chat" }, { - "id": "deepseek/deepseek-v4.1-flash-1017", - "name": "DeepSeek V4.1 Flash 1017 (DeepSeek)", - "display_name": "DeepSeek V4.1 Flash 1017 (DeepSeek)", + "id": "mixtral-8x7B-instruct-v0.1", + "name": "Mixtral 8x7B Instruct v0.1", + "display_name": "Mixtral 8x7B Instruct v0.1", "modalities": { "input": [ "text", - "image" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 393216 + "context": 32000, + "output": 4096 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "none", - "low", - "high", - "max" - ] - } - }, "attachment": true, "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-10-17", - "last_updated": "2026-10-17", + "release_date": "2023-12-11", + "last_updated": "2023-12-11", "type": "chat" }, { - "id": "deepseek/deepseek-v4-pro", - "name": "DeepSeek V4 Pro (DeepSeek)", - "display_name": "DeepSeek V4 Pro (DeepSeek)", + "id": "mistral-small-2503", + "name": "mistral-small-2503", + "display_name": "mistral-small-2503", "modalities": { "input": [ + "text", + "image" + ], + "output": [ "text" + ] + }, + "limit": { + "context": 128000, + "output": 128000 + }, + "temperature": false, + "tool_call": true, + "reasoning": { + "supported": false + }, + "attachment": true, + "open_weights": true, + "knowledge": "2024-06", + "release_date": "2025-03-17", + "last_updated": "2025-03-17", + "type": "chat" + }, + { + "id": "gemini-3.1-flash-lite", + "name": "Gemini 3.1 Flash Lite", + "display_name": "Gemini 3.1 Flash Lite", + "modalities": { + "input": [ + "text", + "image", + "audio", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 393216 + "context": 1048576, + "output": 65535 }, "temperature": true, "tool_call": true, @@ -109109,36 +109740,24 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "high", "effort_options": [ "low", - "high", - "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." + "medium", + "high" ] } }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-04-24", - "last_updated": "2026-04-24", + "attachment": true, + "open_weights": false, + "knowledge": "2025-01", + "release_date": "2026-05-07", + "last_updated": "2026-05-07", "type": "chat" }, { - "id": "ranoai/deepseek-v4-flash", - "name": "DeepSeek V4 Flash (RanoAI)", - "display_name": "DeepSeek V4 Flash (RanoAI)", + "id": "qwen3-32b", + "name": "Qwen3 32B", + "display_name": "Qwen3 32B", "modalities": { "input": [ "text" @@ -109148,8 +109767,8 @@ ] }, "limit": { - "context": 1000000, - "output": 393216 + "context": 16384, + "output": 16384 }, "temperature": true, "tool_call": true, @@ -109160,41 +109779,29 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "high", - "effort_options": [ - "low", - "high", - "max" - ], "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ "thinking_blocks" - ], - "notes": [ - "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." ] } }, "attachment": false, "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-04-24", - "last_updated": "2026-04-24", + "knowledge": "2025-04", + "release_date": "2025-04-28", + "last_updated": "2025-04-28", "type": "chat" }, { - "id": "greenference/glm-5.3-flash", - "name": "GLM-5.3 Flash (greenference)", - "display_name": "GLM-5.3 Flash (greenference)", + "id": "gpt-4.1-mini", + "name": "GPT-4.1 mini", + "display_name": "GPT-4.1 mini", "modalities": { "input": [ "text", "image", - "video", "pdf" ], "output": [ @@ -109202,73 +109809,55 @@ ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 1047576, + "output": 32768 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "effort_options": [ - "low", - "medium", - "high" - ] - } + "supported": false }, "attachment": true, - "open_weights": true, - "release_date": "2026-08-26", - "last_updated": "2026-08-26", + "open_weights": false, + "knowledge": "2024-04", + "release_date": "2025-04-14", + "last_updated": "2025-04-14", "type": "chat" }, { - "id": "greenference/qwen3-14b", - "name": "Qwen3 14B (greenference)", - "display_name": "Qwen3 14B (greenference)", + "id": "mistral-small-2603", + "name": "Mistral Small 4", + "display_name": "Mistral Small 4", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 16384, - "output": 8192 + "context": 262144, + "output": 256000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } + "supported": true, + "default": true }, - "attachment": false, - "open_weights": false, - "release_date": "2025-04-28", - "last_updated": "2025-04-28", + "attachment": true, + "open_weights": true, + "knowledge": "2025-06", + "release_date": "2026-03-16", + "last_updated": "2026-03-16", "type": "chat" }, { - "id": "greenference/qwen3-30b-a3b", - "name": "Qwen3 30B A3B (greenference)", - "display_name": "Qwen3 30B A3B (greenference)", + "id": "deepseek-v4-flash-0731", + "name": "DeepSeek V4 Flash 0731", + "display_name": "DeepSeek V4 Flash 0731", "modalities": { "input": [ "text" @@ -109278,13 +109867,14 @@ ] }, "limit": { - "context": 8192, - "output": 4096 + "context": 1048576, + "output": 1048576 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": true + "supported": true, + "default": true }, "extra_capabilities": { "reasoning": { @@ -109294,72 +109884,68 @@ "visibility": "summary", "continuation": [ "thinking_blocks" + ], + "effort_options": [ + "low", + "medium", + "high" ] } }, "attachment": false, "open_weights": true, - "release_date": "2025-04-28", - "last_updated": "2025-04-28", + "knowledge": "2025-05", + "release_date": "2026-07-31", + "last_updated": "2026-07-31", "type": "chat" }, { - "id": "greenference/qwen3.6-27b", - "name": "Qwen3.6 27B (greenference)", - "display_name": "Qwen3.6 27B (greenference)", + "id": "gpt-4.1", + "name": "GPT-4.1", + "display_name": "GPT-4.1", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 16384, - "output": 8192 + "context": 1047576, + "output": 32768 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } + "supported": false }, - "attachment": false, - "open_weights": true, - "release_date": "2026-04-22", - "last_updated": "2026-04-22", + "attachment": true, + "open_weights": false, + "knowledge": "2024-04", + "release_date": "2025-04-14", + "last_updated": "2025-04-14", "type": "chat" }, { - "id": "runpod/kimi-k3", - "name": "Kimi K3 (Runpod)", - "display_name": "Kimi K3 (Runpod)", + "id": "minimax-m2.1", + "name": "MiniMax-M2.1", + "display_name": "MiniMax-M2.1", "modalities": { "input": [ - "text", - "image", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 1048576 + "context": 196000, + "output": 196000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -109368,41 +109954,39 @@ "extra_capabilities": { "reasoning": { "supported": true, - "effort_options": [ - "none", - "minimal", - "low", - "medium", - "high", - "xhigh", - "max" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, - "attachment": true, + "attachment": false, "open_weights": true, - "release_date": "2026-07-16", - "last_updated": "2026-07-16", + "release_date": "2025-12-23", + "last_updated": "2025-12-23", "type": "chat" }, { - "id": "vertex-openai/grok-4-6", - "name": "Grok 4.6 (Vertex AI (OpenAI-compatible))", - "display_name": "Grok 4.6 (Vertex AI (OpenAI-compatible))", + "id": "claude-opus4-7", + "name": "Claude Opus 4.7", + "display_name": "Claude Opus 4.7", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 500000, - "output": 500000 + "context": 1000000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -109411,7 +109995,6 @@ "extra_capabilities": { "reasoning": { "effort_options": [ - "minimal", "low", "medium", "high" @@ -109420,15 +110003,15 @@ }, "attachment": true, "open_weights": false, - "knowledge": "2026-02-01", - "release_date": "2026-08-12", - "last_updated": "2026-08-12", + "knowledge": "2026-01-31", + "release_date": "2026-04-16", + "last_updated": "2026-04-16", "type": "chat" }, { - "id": "vertex-openai/grok-4-7", - "name": "Grok 4.7 (Vertex AI (OpenAI-compatible))", - "display_name": "Grok 4.7 (Vertex AI (OpenAI-compatible))", + "id": "gpt-6-luna", + "name": "GPT-6 Luna", + "display_name": "GPT-6 Luna", "modalities": { "input": [ "text", @@ -109440,10 +110023,10 @@ ] }, "limit": { - "context": 524288, - "output": 524288 + "context": 1050000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -109452,7 +110035,6 @@ "extra_capabilities": { "reasoning": { "effort_options": [ - "minimal", "low", "medium", "high" @@ -109461,28 +110043,27 @@ }, "attachment": true, "open_weights": false, - "knowledge": "2026-05", - "release_date": "2026-09-21", - "last_updated": "2026-09-21", + "knowledge": "2026-05-18", + "release_date": "2026-09-22", + "last_updated": "2026-09-22", "type": "chat" }, { - "id": "vertex-openai/grok-4-20-reasoning", - "name": "Grok 4.20 Reasoning (Vertex AI (OpenAI-compatible))", - "display_name": "Grok 4.20 Reasoning (Vertex AI (OpenAI-compatible))", + "id": "minimax-m3.1-flash", + "name": "MiniMax-M3.1-Flash", + "display_name": "MiniMax-M3.1-Flash", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 2000000, - "output": 30000 + "context": 1048576, + "output": 64000 }, "temperature": true, "tool_call": true, @@ -109492,24 +110073,25 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, "effort_options": [ "low", "medium", - "high" + "high", + "xhigh", + "max" ] } }, "attachment": true, "open_weights": false, - "release_date": "2026-03-09", - "last_updated": "2026-03-09", + "release_date": "2026-10-19", + "last_updated": "2026-10-19", "type": "chat" }, { - "id": "vertex-openai/grok-4-20-non-reasoning", - "name": "Grok 4.20 Non-Reasoning (Vertex AI (OpenAI-compatible))", - "display_name": "Grok 4.20 Non-Reasoning (Vertex AI (OpenAI-compatible))", + "id": "kimi-k2.6", + "name": "Kimi K2.6", + "display_name": "Kimi K2.6", "modalities": { "input": [ "text", @@ -109521,43 +110103,53 @@ ] }, "limit": { - "context": 2000000, - "output": 30000 + "context": 256000, + "output": 256000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true + "supported": true, + "default": true }, "extra_capabilities": { "reasoning": { - "supported": true + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] } }, "attachment": true, - "open_weights": false, - "release_date": "2026-03-09", - "last_updated": "2026-03-09", + "open_weights": true, + "knowledge": "2025-01", + "release_date": "2026-04-20", + "last_updated": "2026-04-20", "type": "chat" }, { - "id": "openai/gpt-5", - "name": "GPT-5 (OpenAI)", - "display_name": "GPT-5 (OpenAI)", + "id": "gemini-3.5-flash-lite", + "name": "Gemini 3.5 Flash Lite", + "display_name": "Gemini 3.5 Flash Lite", "modalities": { "input": [ "text", - "image" + "image", + "audio", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 1048576, + "output": 65535 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -109566,80 +110158,64 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", "effort_options": [ - "minimal", - "low", - "medium", - "high" - ], - "verbosity": "medium", - "verbosity_options": [ "low", "medium", "high" - ], - "visibility": "hidden" + ] } }, "attachment": true, "open_weights": false, - "knowledge": "2024-09-30", - "release_date": "2025-08-07", - "last_updated": "2025-08-07", + "knowledge": "2026-03", + "release_date": "2026-07-21", + "last_updated": "2026-07-21", "type": "chat" }, { - "id": "openai/gpt-4o-mini", - "name": "GPT-4o Mini (OpenAI)", - "display_name": "GPT-4o Mini (OpenAI)", + "id": "qwen3guard-gen-8b", + "name": "qwen3guard-gen-8b", + "display_name": "qwen3guard-gen-8b", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 16384 + "context": 32000, + "output": 32000 }, - "temperature": true, - "tool_call": true, + "temperature": false, + "tool_call": false, "reasoning": { "supported": false }, - "attachment": true, + "attachment": false, "open_weights": false, - "knowledge": "2023-09", - "release_date": "2024-07-18", - "last_updated": "2024-07-18", + "release_date": "2026-02-04", + "last_updated": "2026-02-04", "type": "chat" }, { - "id": "openai/gpt-5.5", - "name": "GPT-5.5 (OpenAI)", - "display_name": "GPT-5.5 (OpenAI)", + "id": "gpt-oss-20b", + "name": "GPT OSS 20B", + "display_name": "GPT OSS 20B", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 131000, + "output": 131000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -109648,35 +110224,24 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", "effort_options": [ - "low", - "medium", - "high", - "xhigh" - ], - "verbosity": "medium", - "verbosity_options": [ "low", "medium", "high" - ], - "visibility": "hidden" + ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-12-01", - "release_date": "2026-04-23", - "last_updated": "2026-04-23", + "attachment": false, + "open_weights": true, + "knowledge": "2024-06", + "release_date": "2025-08-05", + "last_updated": "2025-08-05", "type": "chat" }, { - "id": "openai/gpt-6-astra", - "name": "GPT-6 Astra (OpenAI)", - "display_name": "GPT-6 Astra (OpenAI)", + "id": "pixtral-large-2502", + "name": "Pixtral Large (25.02)", + "display_name": "Pixtral Large (25.02)", "modalities": { "input": [ "text", @@ -109688,7 +110253,7 @@ ] }, "limit": { - "context": 1050000, + "context": 128000, "output": 128000 }, "temperature": false, @@ -109697,126 +110262,101 @@ "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" - ] - } - }, "attachment": true, "open_weights": false, - "knowledge": "2026-04-30", - "release_date": "2026-09-04", - "last_updated": "2026-09-04", + "release_date": "2025-04-08", + "last_updated": "2025-04-08", "type": "chat" - }, + } + ] + }, + "thinkingmachines": { + "id": "thinkingmachines", + "name": "Thinking Machines", + "display_name": "Thinking Machines", + "api": "https://tinker.thinkingmachines.dev/services/tinker-prod/anthropic/api/v1", + "doc": "https://tinker-docs.thinkingmachines.ai/tinker/compatible-apis/anthropic/", + "models": [ { - "id": "openai/gpt-5.4", - "name": "GPT-5.4 (OpenAI)", - "display_name": "GPT-5.4 (OpenAI)", + "id": "openai/gpt-oss-120b:peft:131072", + "name": "GPT OSS 120B", + "display_name": "GPT OSS 120B", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 131072, + "output": 131072 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "none", - "effort_options": [ - "none", - "low", - "medium", - "high", - "xhigh" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" - } + "default": true }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-03-05", - "last_updated": "2026-03-05", + "attachment": false, + "open_weights": true, + "knowledge": "2024-06", + "release_date": "2025-08-05", + "last_updated": "2025-08-05", "type": "chat" }, { - "id": "openai/gpt-4o", - "name": "GPT-4o (OpenAI)", - "display_name": "GPT-4o (OpenAI)", + "id": "openai/gpt-oss-120b", + "name": "GPT OSS 120B", + "display_name": "GPT OSS 120B", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 16384 + "context": 32768, + "output": 32768 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": true, - "open_weights": false, - "knowledge": "2023-09", - "release_date": "2024-05-13", - "last_updated": "2024-08-06", + "extra_capabilities": { + "reasoning": { + "supported": true + } + }, + "attachment": false, + "open_weights": true, + "knowledge": "2024-06", + "release_date": "2025-08-05", + "last_updated": "2025-08-05", "type": "chat" }, { - "id": "openai/gpt-5-pro", - "name": "GPT-5 Pro (OpenAI)", - "display_name": "GPT-5 Pro (OpenAI)", + "id": "openai/gpt-oss-20b", + "name": "GPT OSS 20B", + "display_name": "GPT OSS 20B", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 400000, - "output": 272000 + "context": 32768, + "output": 32768 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -109824,46 +110364,35 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "fixed", - "effort": "high", - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + "supported": true } }, - "attachment": true, - "open_weights": false, - "knowledge": "2024-09-30", - "release_date": "2025-10-06", - "last_updated": "2025-10-06", + "attachment": false, + "open_weights": true, + "knowledge": "2024-06", + "release_date": "2025-08-05", + "last_updated": "2025-08-05", "type": "chat" }, { - "id": "openai/o3", - "name": "o3 (OpenAI)", - "display_name": "o3 (OpenAI)", + "id": "Qwen/Qwen3.6-35B-A3B:peft:262144", + "name": "Qwen3.6 35B-A3B", + "display_name": "Qwen3.6 35B-A3B", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 100000 + "context": 262144, + "output": 262144 }, - "temperature": false, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { "supported": true, "default": true @@ -109871,43 +110400,38 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", - "effort_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2024-05", - "release_date": "2025-04-16", - "last_updated": "2025-04-16", + "open_weights": true, + "release_date": "2026-04-17", + "last_updated": "2026-04-17", "type": "chat" }, { - "id": "openai/gpt-6-sol", - "name": "GPT-6 Sol (OpenAI)", - "display_name": "GPT-6 Sol (OpenAI)", + "id": "Qwen/Qwen3.5-4B", + "name": "Qwen3.5 4B", + "display_name": "Qwen3.5 4B", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 65536, + "output": 65536 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -109915,42 +110439,38 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "none", - "low", - "medium", - "high", - "xhigh", - "max" + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2026-04-20", - "release_date": "2026-09-22", - "last_updated": "2026-09-22", + "open_weights": true, + "release_date": "2026-03-02", + "last_updated": "2026-03-02", "type": "chat" }, { - "id": "openai/gpt-5.6-sol", - "name": "GPT-5.6 Sol (OpenAI)", - "display_name": "GPT-5.6 Sol (OpenAI)", + "id": "Qwen/Qwen3-8B", + "name": "Qwen3 8B", + "display_name": "Qwen3 8B", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 32768, + "output": 32768 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -109959,52 +110479,38 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", - "effort_options": [ - "none", - "low", - "medium", - "high", - "xhigh", - "max" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-02-16", - "release_date": "2026-07-09", - "last_updated": "2026-07-09", + "attachment": false, + "open_weights": true, + "release_date": "2025-04-29", + "last_updated": "2025-04-29", "type": "chat" }, { - "id": "openai/gpt-5.6-terra", - "name": "GPT-5.6 Terra (OpenAI)", - "display_name": "GPT-5.6 Terra (OpenAI)", + "id": "Qwen/Qwen3.5-9B", + "name": "Qwen3.5 9B", + "display_name": "Qwen3.5 9B", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 65536, + "output": 65536 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -110013,52 +110519,38 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", - "effort_options": [ - "none", - "low", - "medium", - "high", - "xhigh", - "max" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2026-02-16", - "release_date": "2026-07-09", - "last_updated": "2026-07-09", + "open_weights": true, + "release_date": "2026-02-23", + "last_updated": "2026-02-23", "type": "chat" }, { - "id": "openai/gpt-6-luna", - "name": "GPT-6 Luna (OpenAI)", - "display_name": "GPT-6 Luna (OpenAI)", + "id": "Qwen/Qwen3.6-27B", + "name": "Qwen3.6 27B", + "display_name": "Qwen3.6 27B", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 65536, + "output": 65536 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -110066,27 +110558,25 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "none", - "low", - "medium", - "high", - "xhigh", - "max" + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2026-05-18", - "release_date": "2026-09-22", - "last_updated": "2026-09-22", + "open_weights": true, + "release_date": "2026-04-22", + "last_updated": "2026-04-22", "type": "chat" }, { - "id": "openai/gpt-5-mini", - "name": "GPT-5 Mini (OpenAI)", - "display_name": "GPT-5 Mini (OpenAI)", + "id": "Qwen/Qwen3.5-397B-A17B:peft:262144", + "name": "Qwen3.5 397B-A17B", + "display_name": "Qwen3.5 397B-A17B", "modalities": { "input": [ "text", @@ -110097,10 +110587,10 @@ ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 262144, + "output": 262144 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -110109,178 +110599,143 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", - "effort_options": [ - "minimal", - "low", - "medium", - "high" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2024-05-30", - "release_date": "2025-08-07", - "last_updated": "2025-08-07", + "open_weights": true, + "release_date": "2026-02-15", + "last_updated": "2026-02-15", "type": "chat" }, { - "id": "openai/gpt-live-transcribe", - "name": "GPT Live Transcribe (OpenAI)", - "display_name": "GPT Live Transcribe (OpenAI)", + "id": "Qwen/Qwen3.5-4B:peft:262144", + "name": "Qwen3.5 4B", + "display_name": "Qwen3.5 4B", "modalities": { "input": [ "text", - "audio" + "image" ], "output": [ "text" ] }, "limit": { - "context": 8192, - "output": 8192 + "context": 262144, + "output": 262144 }, - "temperature": false, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, "attachment": true, - "open_weights": false, - "release_date": "2026-07-28", - "last_updated": "2026-07-28" + "open_weights": true, + "release_date": "2026-03-02", + "last_updated": "2026-03-02", + "type": "chat" }, { - "id": "openai/gpt-5.4-pro", - "name": "GPT-5.4 Pro (OpenAI)", - "display_name": "GPT-5.4 Pro (OpenAI)", + "id": "Qwen/Qwen3.5-9B-Base", + "name": "Qwen3.5 9B Base", + "display_name": "Qwen3.5 9B Base", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 65536, + "output": 65536 }, - "temperature": false, - "tool_call": true, + "temperature": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true + "supported": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "high", - "effort_options": [ - "medium", - "high", - "xhigh" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-03-05", - "last_updated": "2026-03-05", + "attachment": false, + "open_weights": true, + "release_date": "2026-02-26", + "last_updated": "2026-02-26", "type": "chat" }, { - "id": "openai/gpt-5.1", - "name": "GPT-5.1 (OpenAI)", - "display_name": "GPT-5.1 (OpenAI)", - "modalities": { - "input": [ - "text", - "image" + "id": "Qwen/Qwen3.5-35B-A3B-Base", + "name": "Qwen3.5 35B-A3B Base", + "display_name": "Qwen3.5 35B-A3B Base", + "modalities": { + "input": [ + "text" ], "output": [ "text" ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 65536, + "output": 65536 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": false + "supported": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "none", - "effort_options": [ - "none", - "low", - "medium", - "high" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2024-09-30", - "release_date": "2025-11-13", - "last_updated": "2025-11-13", + "attachment": false, + "open_weights": true, + "release_date": "2026-02-24", + "last_updated": "2026-02-24", "type": "chat" }, { - "id": "openai/gpt-5.6-luna", - "name": "GPT-5.6 Luna (OpenAI)", - "display_name": "GPT-5.6 Luna (OpenAI)", + "id": "Qwen/Qwen3.5-397B-A17B", + "name": "Qwen3.5 397B-A17B", + "display_name": "Qwen3.5 397B-A17B", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 65536, + "output": 65536 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -110289,37 +110744,24 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", - "effort_options": [ - "none", - "low", - "medium", - "high", - "xhigh", - "max" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2026-02-16", - "release_date": "2026-07-09", - "last_updated": "2026-07-09", + "open_weights": true, + "release_date": "2026-02-15", + "last_updated": "2026-02-15", "type": "chat" }, { - "id": "openai/gpt-5.4-mini", - "name": "GPT-5.4 Mini (OpenAI)", - "display_name": "GPT-5.4 Mini (OpenAI)", + "id": "Qwen/Qwen3.8-27B", + "name": "Qwen3.8 27B", + "display_name": "Qwen3.8 27B", "modalities": { "input": [ "text", @@ -110330,89 +110772,77 @@ ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 65536, + "output": 65536 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "none", - "effort_options": [ - "none", - "low", - "medium", - "high", - "xhigh" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" - } + "default": true }, "attachment": true, - "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-03-17", - "last_updated": "2026-03-17", + "open_weights": true, + "release_date": "2026-08-14", + "last_updated": "2026-08-14", "type": "chat" }, { - "id": "openai/gpt-4o-mini-transcribe", - "name": "GPT-4o Mini Transcribe (OpenAI)", - "display_name": "GPT-4o Mini Transcribe (OpenAI)", + "id": "Qwen/Qwen3.6-35B-A3B", + "name": "Qwen3.6 35B-A3B", + "display_name": "Qwen3.6 35B-A3B", "modalities": { "input": [ "text", - "audio" + "image" ], "output": [ "text" ] }, "limit": { - "context": 16000, - "output": 2000 + "context": 65536, + "output": 65536 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } }, "attachment": true, - "open_weights": false, - "release_date": "2025-03-20", - "last_updated": "2025-03-20", + "open_weights": true, + "release_date": "2026-04-17", + "last_updated": "2026-04-17", "type": "chat" }, { - "id": "openai/gpt-5.3-codex", - "name": "GPT-5.3 Codex (OpenAI)", - "display_name": "GPT-5.3 Codex (OpenAI)", + "id": "Qwen/Qwen3.8-27B:peft:262144", + "name": "Qwen3.8 27B", + "display_name": "Qwen3.8 27B", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 262144, + "output": 262144 }, "temperature": true, "tool_call": true, @@ -110420,216 +110850,161 @@ "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", - "effort_options": [ - "low", - "medium", - "high", - "xhigh" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" - } - }, "attachment": true, - "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-02-05", - "last_updated": "2026-02-05", + "open_weights": true, + "release_date": "2026-08-14", + "last_updated": "2026-08-14", "type": "chat" }, { - "id": "openai/gpt-4.1-mini", - "name": "GPT-4.1 Mini (OpenAI)", - "display_name": "GPT-4.1 Mini (OpenAI)", + "id": "nvidia/NVIDIA-Nemotron-3.5-Super-VL-120B-A12B-BF16:peft:262144", + "name": "Nemotron 3.5 Super VL 120B A12B", + "display_name": "Nemotron 3.5 Super VL 120B A12B", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 32768 + "context": 262144, + "output": 262144 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, "attachment": true, - "open_weights": false, - "knowledge": "2024-04", - "release_date": "2025-04-14", - "last_updated": "2025-04-14", + "open_weights": true, + "release_date": "2026-10-15", + "last_updated": "2026-10-15", "type": "chat" }, { - "id": "openai/gpt-4o-transcribe", - "name": "GPT-4o Transcribe (OpenAI)", - "display_name": "GPT-4o Transcribe (OpenAI)", + "id": "nvidia/NVIDIA-Nemotron-3-Ultra-550B-A55B-BF16", + "name": "Nemotron 3 Ultra 550B A55B", + "display_name": "Nemotron 3 Ultra 550B A55B", "modalities": { "input": [ - "text", - "audio" + "text" ], "output": [ "text" ] }, "limit": { - "context": 16000, - "output": 2000 + "context": 65536, + "output": 65536 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": true, - "open_weights": false, - "release_date": "2025-03-20", - "last_updated": "2025-03-20", + "attachment": false, + "open_weights": true, + "knowledge": "2025-09", + "release_date": "2026-06-04", + "last_updated": "2026-06-04", "type": "chat" }, { - "id": "openai/gpt-transcribe", - "name": "GPT Transcribe (OpenAI)", - "display_name": "GPT Transcribe (OpenAI)", + "id": "nvidia/NVIDIA-Nemotron-3.5-Lightning-30B-A3B-BF16:peft:262144", + "name": "Nemotron 3.5 Lightning 30B A3B", + "display_name": "Nemotron 3.5 Lightning 30B A3B", "modalities": { "input": [ - "text", - "audio" + "text" ], "output": [ "text" ] }, "limit": { - "context": 8192, - "output": 8192 + "context": 262144, + "output": 262144 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": true, - "open_weights": false, - "release_date": "2026-07-28", - "last_updated": "2026-07-28" + "attachment": false, + "open_weights": true, + "knowledge": "2025-09", + "release_date": "2026-08-11", + "last_updated": "2026-08-11", + "type": "chat" }, { - "id": "openai/gpt-5.4-nano", - "name": "GPT-5.4 Nano (OpenAI)", - "display_name": "GPT-5.4 Nano (OpenAI)", + "id": "nvidia/NVIDIA-Nemotron-3-Ultra-550B-A55B-BF16:peft:262144", + "name": "Nemotron 3 Ultra 550B A55B", + "display_name": "Nemotron 3 Ultra 550B A55B", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 262144, + "output": 262144 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "none", - "effort_options": [ - "none", - "low", - "medium", - "high", - "xhigh" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" - } + "default": true }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-03-17", - "last_updated": "2026-03-17", + "attachment": false, + "open_weights": true, + "knowledge": "2025-09", + "release_date": "2026-06-04", + "last_updated": "2026-06-04", "type": "chat" }, { - "id": "openai/gpt-5.5-pro", - "name": "GPT-5.5 Pro (OpenAI)", - "display_name": "GPT-5.5 Pro (OpenAI)", + "id": "nvidia/NVIDIA-Nemotron-3-Super-120B-A12B-BF16", + "name": "Nemotron 3 Super 120B A12B", + "display_name": "Nemotron 3 Super 120B A12B", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 65536, + "output": 65536 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "effort_options": [ - "medium", - "high", - "xhigh" - ] - } - }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-12-01", - "release_date": "2026-04-23", - "last_updated": "2026-04-23", + "attachment": false, + "open_weights": true, + "knowledge": "2025-06", + "release_date": "2026-03-11", + "last_updated": "2026-03-11", "type": "chat" }, { - "id": "openai/gpt-5.2-pro", - "name": "GPT-5.2 Pro (OpenAI)", - "display_name": "GPT-5.2 Pro (OpenAI)", + "id": "nvidia/NVIDIA-Nemotron-3.5-Super-VL-120B-A12B-BF16", + "name": "Nemotron 3.5 Super VL 120B A12B", + "display_name": "Nemotron 3.5 Super VL 120B A12B", "modalities": { "input": [ "text", @@ -110640,186 +111015,126 @@ ] }, "limit": { - "context": 400000, - "output": 272000 + "context": 65536, + "output": 65536 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "high", - "effort_options": [ - "medium", - "high", - "xhigh" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" - } - }, "attachment": true, - "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2025-12-11", - "last_updated": "2025-12-11", + "open_weights": true, + "release_date": "2026-10-15", + "last_updated": "2026-10-15", "type": "chat" }, { - "id": "openai/gpt-4.1", - "name": "GPT-4.1 (OpenAI)", - "display_name": "GPT-4.1 (OpenAI)", + "id": "nvidia/NVIDIA-Nemotron-3-Super-120B-A12B-BF16:peft:262144", + "name": "Nemotron 3 Super 120B A12B", + "display_name": "Nemotron 3 Super 120B A12B", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 32768 + "context": 262144, + "output": 262144 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": true, - "open_weights": false, - "knowledge": "2024-04", - "release_date": "2025-04-14", - "last_updated": "2025-04-14", + "attachment": false, + "open_weights": true, + "knowledge": "2025-06", + "release_date": "2026-03-11", + "last_updated": "2026-03-11", "type": "chat" }, { - "id": "openai/gpt-6.1-terra", - "name": "GPT-6.1 Terra (OpenAI)", - "display_name": "GPT-6.1 Terra (OpenAI)", + "id": "nvidia/NVIDIA-Nemotron-3-Nano-30B-A3B-BF16", + "name": "Nemotron 3 Nano 30B A3B", + "display_name": "Nemotron 3 Nano 30B A3B", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 65536, + "output": 65536 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "none", - "low", - "medium", - "high", - "xhigh", - "max" - ] - } - }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-06-01", - "release_date": "2026-10-26", - "last_updated": "2026-10-26", + "attachment": false, + "open_weights": true, + "knowledge": "2025-06-25", + "release_date": "2025-12-15", + "last_updated": "2025-12-15", "type": "chat" }, { - "id": "openai/gpt-5.2", - "name": "GPT-5.2 (OpenAI)", - "display_name": "GPT-5.2 (OpenAI)", + "id": "nvidia/NVIDIA-Nemotron-3.5-Lightning-30B-A3B-BF16", + "name": "Nemotron 3.5 Lightning 30B A3B", + "display_name": "Nemotron 3.5 Lightning 30B A3B", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 65536, + "output": 65536 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "none", - "effort_options": [ - "none", - "low", - "medium", - "high", - "xhigh" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" - } + "default": true }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2025-12-11", - "last_updated": "2025-12-11", + "attachment": false, + "open_weights": true, + "knowledge": "2025-09", + "release_date": "2026-08-11", + "last_updated": "2026-08-11", "type": "chat" }, { - "id": "openai/gpt-6.1-sol", - "name": "GPT-6.1 Sol (OpenAI)", - "display_name": "GPT-6.1 Sol (OpenAI)", + "id": "thinkingmachines/Inkling-Small:peft:262144:sampling-nvfp4", + "name": "Inkling Small", + "display_name": "Inkling Small", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 262144, + "output": 262144 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -110837,29 +111152,29 @@ } }, "attachment": true, - "open_weights": false, - "knowledge": "2026-04-30", - "release_date": "2026-09-29", - "last_updated": "2026-09-29", + "open_weights": true, + "release_date": "2026-07-30", + "last_updated": "2026-07-30", "type": "chat" }, { - "id": "openai/gpt-5-nano", - "name": "GPT-5 Nano (OpenAI)", - "display_name": "GPT-5 Nano (OpenAI)", + "id": "thinkingmachines/Inkling:peft:262144:sampling-nvfp4", + "name": "Inkling", + "display_name": "Inkling", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 262144, + "output": 262144 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -110867,46 +111182,36 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", "effort_options": [ - "minimal", - "low", - "medium", - "high" - ], - "verbosity": "medium", - "verbosity_options": [ "low", "medium", - "high" - ], - "visibility": "hidden" + "high", + "xhigh", + "max" + ] } }, - "attachment": false, - "open_weights": false, - "knowledge": "2024-05-30", - "release_date": "2025-08-07", - "last_updated": "2025-08-07", + "attachment": true, + "open_weights": true, + "release_date": "2026-07-15", + "last_updated": "2026-07-15", "type": "chat" }, { - "id": "tencent/hy4-preview", - "name": "Hy4 Preview (Tencent Cloud)", - "display_name": "Hy4 Preview (Tencent Cloud)", + "id": "thinkingmachines/Inkling-Small", + "name": "Inkling Small", + "display_name": "Inkling Small", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, + "context": 65536, "output": 65536 }, "temperature": true, @@ -110917,10 +111222,7 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, "effort_options": [ - "none", - "minimal", "low", "medium", "high", @@ -110929,31 +111231,30 @@ ] } }, - "attachment": false, + "attachment": true, "open_weights": true, - "release_date": "2026-08-28", - "last_updated": "2026-08-28", + "release_date": "2026-07-30", + "last_updated": "2026-07-30", "type": "chat" }, { - "id": "tencent/kimi-k2.8-preview", - "name": "Kimi K2.8 Preview (Tencent Cloud)", - "display_name": "Kimi K2.8 Preview (Tencent Cloud)", + "id": "thinkingmachines/Inkling:peft:262144", + "name": "Inkling (256K)", + "display_name": "Inkling (256K)", "modalities": { "input": [ "text", - "image", - "video" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 1000000 + "context": 262144, + "output": 262144 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -110962,8 +111263,6 @@ "extra_capabilities": { "reasoning": { "effort_options": [ - "none", - "minimal", "low", "medium", "high", @@ -110973,29 +111272,27 @@ } }, "attachment": true, - "open_weights": false, - "release_date": "2026-09-11", - "last_updated": "2026-09-11", + "open_weights": true, + "release_date": "2026-07-15", + "last_updated": "2026-07-15", "type": "chat" }, { - "id": "tencent/glm-5.3-flash", - "name": "GLM-5.3 Flash (Tencent Cloud)", - "display_name": "GLM-5.3 Flash (Tencent Cloud)", + "id": "thinkingmachines/Inkling", + "name": "Inkling", + "display_name": "Inkling", "modalities": { "input": [ "text", - "image", - "video", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 65536, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -111005,8 +111302,10 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, "effort_options": [ + "low", + "medium", + "high", "xhigh", "max" ] @@ -111014,25 +111313,26 @@ }, "attachment": true, "open_weights": true, - "release_date": "2026-08-26", - "last_updated": "2026-08-26", + "release_date": "2026-07-15", + "last_updated": "2026-07-15", "type": "chat" }, { - "id": "tencent/deepseek-v4-flash", - "name": "DeepSeek V4 Flash (Tencent Cloud)", - "display_name": "DeepSeek V4 Flash (Tencent Cloud)", + "id": "thinkingmachines/Inkling-Small:peft:262144", + "name": "Inkling Small", + "display_name": "Inkling Small", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 393216 + "context": 262144, + "output": 262144 }, "temperature": true, "tool_call": true, @@ -111042,50 +111342,36 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "high", "effort_options": [ "low", + "medium", "high", + "xhigh", "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." ] } }, - "attachment": false, + "attachment": true, "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-04-24", - "last_updated": "2026-04-24", + "release_date": "2026-07-30", + "last_updated": "2026-07-30", "type": "chat" }, { - "id": "tencent/mimo-v2.6-pro", - "name": "MiMo V2.6 Pro (Tencent Cloud)", - "display_name": "MiMo V2.6 Pro (Tencent Cloud)", + "id": "moonshotai/Kimi-K2.6:peft:131072", + "name": "Kimi K2.6", + "display_name": "Kimi K2.6", "modalities": { "input": [ "text", - "image", - "audio", - "video" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, + "context": 131072, "output": 131072 }, "temperature": true, @@ -111094,41 +111380,29 @@ "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "none", - "low", - "medium", - "high", - "max" - ] - } - }, "attachment": true, "open_weights": true, - "release_date": "2026-09-22", - "last_updated": "2026-09-22", + "knowledge": "2025-01", + "release_date": "2026-04-20", + "last_updated": "2026-04-20", "type": "chat" }, { - "id": "tencent/glm-5v-turbo", - "name": "GLM-5V Turbo (Tencent Cloud)", - "display_name": "GLM-5V Turbo (Tencent Cloud)", + "id": "moonshotai/Kimi-K2.6", + "name": "Kimi K2.6", + "display_name": "Kimi K2.6", "modalities": { "input": [ "text", - "image", - "video", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 128000 + "context": 32768, + "output": 32768 }, "temperature": true, "tool_call": true, @@ -111138,25 +111412,20 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" - ] + "supported": true } }, "attachment": true, - "open_weights": false, - "release_date": "2026-04-01", - "last_updated": "2026-04-01", + "open_weights": true, + "knowledge": "2025-01", + "release_date": "2026-04-20", + "last_updated": "2026-04-20", "type": "chat" }, { - "id": "tencent/glm-5-turbo", - "name": "GLM-5 Turbo (Tencent Cloud)", - "display_name": "GLM-5 Turbo (Tencent Cloud)", + "id": "zai-org/GLM-5.3-Flash:peft:262144", + "name": "GLM-5.3-Flash", + "display_name": "GLM-5.3-Flash", "modalities": { "input": [ "text" @@ -111166,8 +111435,8 @@ ] }, "limit": { - "context": 200000, - "output": 128000 + "context": 262144, + "output": 262144 }, "temperature": true, "tool_call": true, @@ -111175,40 +111444,27 @@ "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" - ] - } - }, "attachment": false, - "open_weights": false, - "release_date": "2026-03-16", - "last_updated": "2026-03-16", + "open_weights": true, + "release_date": "2026-08-26", + "last_updated": "2026-08-26", "type": "chat" }, { - "id": "tencent/deepseek-v4.1-flash-1017", - "name": "DeepSeek V4.1 Flash 1017 (Tencent Cloud)", - "display_name": "DeepSeek V4.1 Flash 1017 (Tencent Cloud)", + "id": "zai-org/GLM-5.3:peft:262144", + "name": "GLM-5.3", + "display_name": "GLM-5.3", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 393216 + "context": 262144, + "output": 262144 }, "temperature": true, "tool_call": true, @@ -111216,20 +111472,35 @@ "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "none", - "minimal", - "low", - "medium", - "high", - "xhigh", - "max" - ] - } + "attachment": false, + "open_weights": true, + "release_date": "2026-08-14", + "last_updated": "2026-08-14", + "type": "chat" + }, + { + "id": "deepseek-ai/DeepSeek-V4.1-Flash-1017:peft:262144", + "name": "DeepSeek V4.1 Flash 1017", + "display_name": "DeepSeek V4.1 Flash 1017", + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] }, - "attachment": true, + "limit": { + "context": 262144, + "output": 262144 + }, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": true, + "default": true + }, + "attachment": false, "open_weights": true, "knowledge": "2025-05", "release_date": "2026-10-17", @@ -111237,53 +111508,37 @@ "type": "chat" }, { - "id": "tencent/kimi-k3", - "name": "Kimi K3 (Tencent Cloud)", - "display_name": "Kimi K3 (Tencent Cloud)", + "id": "deepseek-ai/DeepSeek-V3.1", + "name": "DeepSeek-V3.1", + "display_name": "DeepSeek-V3.1", "modalities": { "input": [ - "text", - "image", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 1048576 + "context": 32768, + "output": 32768 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "effort_options": [ - "none", - "minimal", - "low", - "medium", - "high", - "xhigh", - "max" - ] - } - }, - "attachment": true, + "attachment": false, "open_weights": true, - "release_date": "2026-07-16", - "last_updated": "2026-07-16", + "release_date": "2025-08-21", + "last_updated": "2025-08-21", "type": "chat" }, { - "id": "tencent/deepseek-v4-pro", - "name": "DeepSeek V4 Pro (Tencent Cloud)", - "display_name": "DeepSeek V4 Pro (Tencent Cloud)", + "id": "deepseek-ai/DeepSeek-V4.1-Flash:peft:262144", + "name": "DeepSeek V4.1 Flash", + "display_name": "DeepSeek V4.1 Flash", "modalities": { "input": [ "text" @@ -111293,8 +111548,8 @@ ] }, "limit": { - "context": 1000000, - "output": 393216 + "context": 262144, + "output": 262144 }, "temperature": true, "tool_call": true, @@ -111302,45 +111557,29 @@ "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "high", - "effort_options": [ - "low", - "high", - "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." - ] - } - }, "attachment": false, "open_weights": true, "knowledge": "2025-05", - "release_date": "2026-04-24", - "last_updated": "2026-04-24", + "release_date": "2026-09-10", + "last_updated": "2026-09-10", "type": "chat" - }, + } + ] + }, + "abliteration-ai": { + "id": "abliteration-ai", + "name": "abliteration.ai", + "display_name": "abliteration.ai", + "api": "https://api.abliteration.ai/v1", + "doc": "https://docs.abliteration.ai/models", + "models": [ { - "id": "tencent/mimo-v2.6-flash", - "name": "MiMo V2.6 Flash (Tencent Cloud)", - "display_name": "MiMo V2.6 Flash (Tencent Cloud)", + "id": "abliterated-model-large-v2", + "name": "Abliterated Model Large V2", + "display_name": "Abliterated Model Large V2", "modalities": { "input": [ - "text", - "image", - "audio", - "video" + "text" ], "output": [ "text" @@ -111348,7 +111587,7 @@ }, "limit": { "context": 1000000, - "output": 131072 + "output": 999990 }, "temperature": true, "tool_call": true, @@ -111359,37 +111598,34 @@ "extra_capabilities": { "reasoning": { "effort_options": [ - "none", "low", - "medium", "high", "max" ] } }, - "attachment": true, - "open_weights": true, - "release_date": "2026-09-22", - "last_updated": "2026-09-22", + "attachment": false, + "open_weights": false, + "release_date": "2026-08-29", + "last_updated": "2026-08-31", "type": "chat" }, { - "id": "tencent/kimi-k2.6", - "name": "Kimi K2.6 (Tencent Cloud)", - "display_name": "Kimi K2.6 (Tencent Cloud)", + "id": "abliterated-model", + "name": "Abliterated Model", + "display_name": "Abliterated Model", "modalities": { "input": [ "text", - "image", - "video" + "image" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 150000, + "output": 8192 }, "temperature": true, "tool_call": true, @@ -111399,13 +111635,6 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], "effort_options": [ "none", "minimal", @@ -111419,20 +111648,17 @@ }, "attachment": true, "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-04-20", - "last_updated": "2026-04-20", + "release_date": "2026-01-06", + "last_updated": "2026-07-28", "type": "chat" }, { - "id": "tencent/minimax-m3", - "name": "MiniMax M3 (Tencent Cloud)", - "display_name": "MiniMax M3 (Tencent Cloud)", + "id": "abliterated-model-large", + "name": "Abliterated Model Large", + "display_name": "Abliterated Model Large", "modalities": { "input": [ - "text", - "image", - "video" + "text" ], "output": [ "text" @@ -111440,7 +111666,7 @@ }, "limit": { "context": 1000000, - "output": 131072 + "output": 999990 }, "temperature": true, "tool_call": true, @@ -111450,37 +111676,44 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, "effort_options": [ - "low", - "medium", + "none", "high", - "xhigh", "max" ] } }, - "attachment": true, - "open_weights": true, - "release_date": "2026-06-01", - "last_updated": "2026-06-01", + "attachment": false, + "open_weights": false, + "release_date": "2026-07-25", + "last_updated": "2026-07-28", "type": "chat" - }, + } + ] + }, + "fireworks-ai": { + "id": "fireworks-ai", + "name": "Fireworks AI", + "display_name": "Fireworks AI", + "api": "https://api.fireworks.ai/inference/v1/", + "doc": "https://fireworks.ai/docs/", + "models": [ { - "id": "tencent/minimax-m2.7", - "name": "MiniMax M2.7 (Tencent Cloud)", - "display_name": "MiniMax M2.7 (Tencent Cloud)", + "id": "accounts/fireworks/models/glm-5p3-flash", + "name": "GLM 5.3 Flash", + "display_name": "GLM 5.3 Flash", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 128000 + "context": 1048573, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -111499,23 +111732,21 @@ ], "effort_options": [ "low", - "medium", "high", - "xhigh", "max" ] } }, - "attachment": false, + "attachment": true, "open_weights": true, - "release_date": "2026-03-18", - "last_updated": "2026-03-18", + "release_date": "2026-08-26", + "last_updated": "2026-08-26", "type": "chat" }, { - "id": "tencent/deepseek-v4.1-flash", - "name": "DeepSeek V4.1 Flash (Tencent Cloud)", - "display_name": "DeepSeek V4.1 Flash (Tencent Cloud)", + "id": "accounts/fireworks/models/kimi-k3", + "name": "Kimi K3", + "display_name": "Kimi K3", "modalities": { "input": [ "text", @@ -111526,10 +111757,10 @@ ] }, "limit": { - "context": 1000000, - "output": 393216 + "context": 1048576, + "output": 131072 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -111538,28 +111769,30 @@ "extra_capabilities": { "reasoning": { "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ - "none", - "minimal", "low", "medium", "high", - "xhigh", "max" ] } }, "attachment": true, "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-09-10", - "last_updated": "2026-09-10", + "release_date": "2026-07-16", + "last_updated": "2026-07-16", "type": "chat" }, { - "id": "tencent/hy3", - "name": "Hy3 (Tencent Cloud)", - "display_name": "Hy3 (Tencent Cloud)", + "id": "accounts/fireworks/models/nemotron-3-ultra-nvfp4", + "name": "Nemotron 3 Ultra 550B A55B", + "display_name": "Nemotron 3 Ultra 550B A55B", "modalities": { "input": [ "text" @@ -111570,7 +111803,7 @@ }, "limit": { "context": 262144, - "output": 131072 + "output": 128000 }, "temperature": true, "tool_call": true, @@ -111578,44 +111811,31 @@ "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "effort_options": [ - "none", - "minimal", - "low", - "medium", - "high", - "xhigh" - ] - } - }, "attachment": false, "open_weights": true, - "release_date": "2026-07-06", - "last_updated": "2026-07-06", + "knowledge": "2025-09", + "release_date": "2026-06-04", + "last_updated": "2026-06-04", "type": "chat" }, { - "id": "tencent/kimi-k2.7-code", - "name": "Kimi K2.7 Code (Tencent Cloud)", - "display_name": "Kimi K2.7 Code (Tencent Cloud)", + "id": "accounts/fireworks/models/minimax-m3p1-flash", + "name": "MiniMax-M3.1-Flash", + "display_name": "MiniMax-M3.1-Flash", "modalities": { "input": [ "text", - "image", - "video" + "image" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 1000000, + "output": 512000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -111631,7 +111851,6 @@ "thinking_blocks" ], "effort_options": [ - "minimal", "low", "medium", "high", @@ -111641,28 +111860,26 @@ } }, "attachment": true, - "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-06-12", - "last_updated": "2026-06-12", + "open_weights": false, + "release_date": "2026-10-19", + "last_updated": "2026-10-19", "type": "chat" }, { - "id": "tencent/glm-5.3-flashx", - "name": "GLM-5.3 FlashX (Tencent Cloud)", - "display_name": "GLM-5.3 FlashX (Tencent Cloud)", + "id": "accounts/fireworks/models/nemotron-lightning-3p5-30b-a3b", + "name": "Nemotron 3.5 Lightning 30B A3B", + "display_name": "Nemotron 3.5 Lightning 30B A3B", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 262144, + "output": 262144 }, "temperature": true, "tool_call": true, @@ -111670,35 +111887,29 @@ "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "xhigh", - "max" - ] - } - }, - "attachment": true, - "open_weights": false, - "release_date": "2026-09-18", - "last_updated": "2026-09-18", + "attachment": false, + "open_weights": true, + "knowledge": "2025-09", + "release_date": "2026-08-11", + "last_updated": "2026-08-11", "type": "chat" }, { - "id": "tencent/glm-5.1", - "name": "GLM-5.1 (Tencent Cloud)", - "display_name": "GLM-5.1 (Tencent Cloud)", + "id": "accounts/fireworks/models/muse-spark-open", + "name": "Muse Spark Open", + "display_name": "Muse Spark Open", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 128000 + "context": 1048576, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -111708,70 +111919,37 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], "effort_options": [ "low", "medium", "high", - "xhigh", - "max" + "xhigh" ] } }, - "attachment": false, + "attachment": true, "open_weights": true, - "release_date": "2026-04-07", - "last_updated": "2026-04-07", - "type": "chat" - }, - { - "id": "tencent/hy-mt2-plus", - "name": "Hy-MT2 Plus (Tencent Cloud)", - "display_name": "Hy-MT2 Plus (Tencent Cloud)", - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 8192, - "output": 4096 - }, - "temperature": true, - "tool_call": false, - "reasoning": { - "supported": false - }, - "attachment": false, - "open_weights": false, - "release_date": "2026-05-21", - "last_updated": "2026-05-21", + "knowledge": "2026-01-04", + "release_date": "2026-10-15", + "last_updated": "2026-10-15", "type": "chat" }, { - "id": "tencent/glm-5", - "name": "GLM-5 (Tencent Cloud)", - "display_name": "GLM-5 (Tencent Cloud)", + "id": "accounts/fireworks/models/deepseek-v4p1-flash-1017", + "name": "DeepSeek V4.1 Flash 1017", + "display_name": "DeepSeek V4.1 Flash 1017", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 128000 + "context": 1000000, + "output": 384000 }, "temperature": true, "tool_call": true, @@ -111789,29 +111967,27 @@ "thinking_blocks" ], "effort_options": [ - "none", - "minimal", "low", - "medium", "high", - "xhigh", "max" ] } }, - "attachment": false, + "attachment": true, "open_weights": true, - "release_date": "2026-02-12", - "last_updated": "2026-02-12", + "knowledge": "2025-05", + "release_date": "2026-10-17", + "last_updated": "2026-10-17", "type": "chat" }, { - "id": "tencent/glm-5.3", - "name": "GLM-5.3 (Tencent Cloud)", - "display_name": "GLM-5.3 (Tencent Cloud)", + "id": "accounts/fireworks/models/deepseek-v4p1-flash", + "name": "DeepSeek V4.1 Flash", + "display_name": "DeepSeek V4.1 Flash", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" @@ -111819,7 +111995,7 @@ }, "limit": { "context": 1000000, - "output": 128000 + "output": 384000 }, "temperature": true, "tool_call": true, @@ -111830,70 +112006,43 @@ "extra_capabilities": { "reasoning": { "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ "low", - "medium", "high", - "xhigh", "max" ] } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-08-14", - "last_updated": "2026-08-14", - "type": "chat" - }, - { - "id": "tencent/step-5-preview", - "name": "Step 5 Preview (Tencent Cloud)", - "display_name": "Step 5 Preview (Tencent Cloud)", - "modalities": { - "input": [ - "text", - "image", - "video" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 1000000, - "output": 65536 - }, - "temperature": true, - "tool_call": true, - "reasoning": { - "supported": true, - "default": true - }, "attachment": true, "open_weights": true, - "release_date": "2026-09-16", - "last_updated": "2026-09-20", + "knowledge": "2025-05", + "release_date": "2026-09-10", + "last_updated": "2026-09-10", "type": "chat" }, { - "id": "tencent/kimi-k2.7-code-highspeed", - "name": "Kimi K2.7 Code Highspeed (Tencent Cloud)", - "display_name": "Kimi K2.7 Code Highspeed (Tencent Cloud)", + "id": "accounts/fireworks/models/gpt-oss-120b", + "name": "GPT OSS 120B", + "display_name": "GPT OSS 120B", "modalities": { "input": [ - "text", - "image", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 131072, + "output": 32768 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -111902,33 +112051,24 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], "effort_options": [ - "minimal", "low", "medium", - "high", - "xhigh", - "max" + "high" ] } }, - "attachment": true, + "attachment": false, "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-06-12", - "last_updated": "2026-06-12", + "knowledge": "2024-06", + "release_date": "2025-08-05", + "last_updated": "2025-08-05", "type": "chat" }, { - "id": "tencent/glm-5.2", - "name": "GLM-5.2 (Tencent Cloud)", - "display_name": "GLM-5.2 (Tencent Cloud)", + "id": "accounts/fireworks/models/qwen3p8-2p4t-a95b", + "name": "Qwen3.8 2.4T A95B", + "display_name": "Qwen3.8 2.4T A95B", "modalities": { "input": [ "text" @@ -111938,8 +112078,8 @@ ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 262144, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -111958,31 +112098,26 @@ ], "effort_options": [ "none", - "minimal", "low", "medium", - "high", - "xhigh", - "max" + "xhigh" ] } }, "attachment": false, "open_weights": true, - "release_date": "2026-06-13", - "last_updated": "2026-06-13", + "release_date": "2026-08-12", + "last_updated": "2026-08-12", "type": "chat" }, { - "id": "baidu/glm-5.3-flash", - "name": "GLM-5.3 Flash (Baidu)", - "display_name": "GLM-5.3 Flash (Baidu)", + "id": "accounts/fireworks/models/inkling", + "name": "Inkling", + "display_name": "Inkling", "modalities": { "input": [ "text", - "image", - "video", - "pdf" + "image" ], "output": [ "text" @@ -111990,7 +112125,7 @@ }, "limit": { "context": 1048576, - "output": 131072 + "output": 1048576 }, "temperature": true, "tool_call": true, @@ -111998,25 +112133,16 @@ "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "effort_options": [ - "xhigh", - "max" - ] - } - }, "attachment": true, "open_weights": true, - "release_date": "2026-08-26", - "last_updated": "2026-08-26", + "release_date": "2026-07-15", + "last_updated": "2026-07-15", "type": "chat" }, { - "id": "baidu/deepseek-v4-flash", - "name": "DeepSeek V4 Flash (Baidu)", - "display_name": "DeepSeek V4 Flash (Baidu)", + "id": "accounts/fireworks/models/glm-5p3", + "name": "GLM 5.3", + "display_name": "GLM 5.3", "modalities": { "input": [ "text" @@ -112026,8 +112152,8 @@ ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 1048573, + "output": 262144 }, "temperature": true, "tool_call": true, @@ -112038,36 +112164,29 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "high", - "effort_options": [ - "low", - "high", - "max" - ], "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ "thinking_blocks" ], - "notes": [ - "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." + "effort_options": [ + "low", + "high", + "max" ] } }, "attachment": false, "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-04-24", - "last_updated": "2026-04-24", + "release_date": "2026-08-14", + "last_updated": "2026-08-14", "type": "chat" }, { - "id": "baidu/deepseek-v4.1-flash-1017", - "name": "DeepSeek V4.1 Flash 1017 (Baidu)", - "display_name": "DeepSeek V4.1 Flash 1017 (Baidu)", + "id": "accounts/fireworks/models/qwen3p8-max", + "name": "Qwen3.8 Max", + "display_name": "Qwen3.8 Max", "modalities": { "input": [ "text", @@ -112078,8 +112197,8 @@ ] }, "limit": { - "context": 1048576, - "output": 393216 + "context": 262144, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -112091,26 +112210,22 @@ "reasoning": { "effort_options": [ "none", - "minimal", "low", "medium", - "high", - "xhigh", - "max" + "xhigh" ] } }, "attachment": true, - "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-10-17", - "last_updated": "2026-10-17", + "open_weights": false, + "release_date": "2026-08-03", + "last_updated": "2026-08-03", "type": "chat" }, { - "id": "baidu/deepseek-v4-pro", - "name": "DeepSeek V4 Pro (Baidu)", - "display_name": "DeepSeek V4 Pro (Baidu)", + "id": "accounts/fireworks/models/minimax-m3", + "name": "MiniMax-M3", + "display_name": "MiniMax-M3", "modalities": { "input": [ "text" @@ -112120,8 +112235,8 @@ ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 512000, + "output": 512000 }, "temperature": true, "tool_call": true, @@ -112132,36 +112247,29 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "high", - "effort_options": [ - "low", - "high", - "max" - ], "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ "thinking_blocks" ], - "notes": [ - "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." + "effort_options": [ + "low", + "medium", + "high" ] } }, "attachment": false, "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-04-24", - "last_updated": "2026-04-24", + "release_date": "2026-06-01", + "last_updated": "2026-06-01", "type": "chat" }, { - "id": "baidu/deepseek-v4.1-flash", - "name": "DeepSeek V4.1 Flash (Baidu)", - "display_name": "DeepSeek V4.1 Flash (Baidu)", + "id": "accounts/fireworks/models/ember-1", + "name": "Ember-1", + "display_name": "Ember-1", "modalities": { "input": [ "text", @@ -112173,7 +112281,7 @@ }, "limit": { "context": 1048576, - "output": 393216 + "output": 131072 }, "temperature": true, "tool_call": true, @@ -112184,28 +112292,30 @@ "extra_capabilities": { "reasoning": { "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ - "none", - "minimal", "low", "medium", "high", - "xhigh", "max" ] } }, "attachment": true, - "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-09-10", - "last_updated": "2026-09-10", + "open_weights": false, + "release_date": "2026-09-22", + "last_updated": "2026-09-22", "type": "chat" }, { - "id": "baidu/glm-5.1", - "name": "GLM-5.1 (Baidu)", - "display_name": "GLM-5.1 (Baidu)", + "id": "accounts/fireworks/routers/glm-5p3-fast", + "name": "GLM 5.3 Fast", + "display_name": "GLM 5.3 Fast", "modalities": { "input": [ "text" @@ -112215,8 +112325,8 @@ ] }, "limit": { - "context": 202752, - "output": 131072 + "context": 1048572, + "output": 262144 }, "temperature": true, "tool_call": true, @@ -112234,29 +112344,26 @@ "thinking_blocks" ], "effort_options": [ - "none", - "minimal", "low", - "medium", "high", - "xhigh", "max" ] } }, "attachment": false, "open_weights": true, - "release_date": "2026-04-07", - "last_updated": "2026-04-07", + "release_date": "2026-08-14", + "last_updated": "2026-08-14", "type": "chat" }, { - "id": "baidu/glm-5.3", - "name": "GLM-5.3 (Baidu)", - "display_name": "GLM-5.3 (Baidu)", + "id": "accounts/fireworks/routers/kimi-fast-latest", + "name": "Kimi Fast Latest", + "display_name": "Kimi Fast Latest", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" @@ -112266,7 +112373,7 @@ "context": 1048576, "output": 131072 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -112275,26 +112382,34 @@ "extra_capabilities": { "reasoning": { "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ "low", + "medium", "high", "max" ] } }, - "attachment": false, + "attachment": true, "open_weights": true, - "release_date": "2026-08-14", - "last_updated": "2026-08-14", + "release_date": "2026-07-16", + "last_updated": "2026-07-16", "type": "chat" }, { - "id": "baidu/glm-5.2", - "name": "GLM-5.2 (Baidu)", - "display_name": "GLM-5.2 (Baidu)", + "id": "accounts/fireworks/routers/kimi-latest", + "name": "Kimi Latest", + "display_name": "Kimi Latest", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" @@ -112304,7 +112419,7 @@ "context": 1048576, "output": 131072 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -112320,26 +112435,23 @@ "thinking_blocks" ], "effort_options": [ - "none", - "minimal", "low", "medium", "high", - "xhigh", "max" ] } }, - "attachment": false, + "attachment": true, "open_weights": true, - "release_date": "2026-06-13", - "last_updated": "2026-06-13", + "release_date": "2026-07-16", + "last_updated": "2026-07-16", "type": "chat" }, { - "id": "mistral/mistral-small-2506", - "name": "Mistral Small 3.2 (Mistral AI)", - "display_name": "Mistral Small 3.2 (Mistral AI)", + "id": "accounts/fireworks/routers/deepseek-flash-latest", + "name": "DeepSeek Flash Latest", + "display_name": "DeepSeek Flash Latest", "modalities": { "input": [ "text", @@ -112350,25 +112462,42 @@ ] }, "limit": { - "context": 128000, - "output": 16384 + "context": 1000000, + "output": 384000 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "effort_options": [ + "low", + "high", + "max" + ] + } }, "attachment": true, "open_weights": true, - "knowledge": "2025-03", - "release_date": "2025-06-20", - "last_updated": "2025-06-20", + "knowledge": "2025-05", + "release_date": "2026-10-17", + "last_updated": "2026-10-17", "type": "chat" }, { - "id": "mistral/ministral-14b-2512", - "name": "Ministral 14B (Mistral AI)", - "display_name": "Ministral 14B (Mistral AI)", + "id": "accounts/fireworks/routers/kimi-k3-fast", + "name": "Kimi K3 Fast", + "display_name": "Kimi K3 Fast", "modalities": { "input": [ "text", @@ -112379,24 +112508,42 @@ ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 1048576, + "output": 131072 }, - "temperature": true, - "tool_call": false, + "temperature": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "effort_options": [ + "low", + "medium", + "high", + "max" + ] + } }, "attachment": true, "open_weights": true, - "release_date": "2025-12-02", - "last_updated": "2025-12-02", + "release_date": "2026-07-16", + "last_updated": "2026-07-16", "type": "chat" }, { - "id": "mistral/devstral-2512", - "name": "Devstral 2 (Mistral AI)", - "display_name": "Devstral 2 (Mistral AI)", + "id": "accounts/fireworks/routers/minimax-latest", + "name": "MiniMax Latest", + "display_name": "MiniMax Latest", "modalities": { "input": [ "text" @@ -112406,25 +112553,41 @@ ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 512000, + "output": 512000 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "effort_options": [ + "low", + "medium", + "high" + ] + } }, "attachment": false, "open_weights": true, - "knowledge": "2025-12", - "release_date": "2025-12-09", - "last_updated": "2025-12-09", + "release_date": "2026-06-01", + "last_updated": "2026-06-01", "type": "chat" }, { - "id": "mistral/mistral-large-2512", - "name": "Mistral Large 3 (Mistral AI)", - "display_name": "Mistral Large 3 (Mistral AI)", + "id": "accounts/fireworks/routers/deepseek-v4p1-flash-spot", + "name": "DeepSeek V4.1 Flash Spot", + "display_name": "DeepSeek V4.1 Flash Spot", "modalities": { "input": [ "text", @@ -112435,25 +112598,42 @@ ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 1000000, + "output": 384000 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "effort_options": [ + "low", + "high", + "max" + ] + } }, "attachment": true, "open_weights": true, - "knowledge": "2024-11", - "release_date": "2025-12-02", - "last_updated": "2025-12-02", + "knowledge": "2025-05", + "release_date": "2026-09-10", + "last_updated": "2026-09-10", "type": "chat" }, { - "id": "mistral/ministral-3b-2512", - "name": "Ministral 3B (Mistral AI)", - "display_name": "Ministral 3B (Mistral AI)", + "id": "accounts/fireworks/routers/deepseek-v4p1-flash-1017-spot", + "name": "DeepSeek V4.1 Flash 1017 Spot", + "display_name": "DeepSeek V4.1 Flash 1017 Spot", "modalities": { "input": [ "text", @@ -112464,24 +112644,42 @@ ] }, "limit": { - "context": 131072, - "output": 131072 + "context": 1000000, + "output": 384000 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "effort_options": [ + "low", + "high", + "max" + ] + } }, "attachment": true, "open_weights": true, - "release_date": "2025-12-02", - "last_updated": "2025-12-02", + "knowledge": "2025-05", + "release_date": "2026-10-17", + "last_updated": "2026-10-17", "type": "chat" }, { - "id": "mistral/mistral-large-4", - "name": "Mistral Large 4 (Mistral AI)", - "display_name": "Mistral Large 4 (Mistral AI)", + "id": "accounts/fireworks/routers/glm-flash-latest", + "name": "GLM Flash Latest (GLM 5.3 Flash)", + "display_name": "GLM Flash Latest (GLM 5.3 Flash)", "modalities": { "input": [ "text", @@ -112492,24 +112690,41 @@ ] }, "limit": { - "context": 1000000, - "output": 262144 + "context": 1048573, + "output": 131072 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "effort_options": [ + "low", + "high", + "max" + ] + } }, "attachment": true, - "open_weights": false, - "release_date": "2026-10-06", - "last_updated": "2026-10-06", + "open_weights": true, + "release_date": "2026-08-26", + "last_updated": "2026-08-26", "type": "chat" }, { - "id": "mistral/mistral-small-2603", - "name": "Mistral Small 4 (Mistral AI)", - "display_name": "Mistral Small 4 (Mistral AI)", + "id": "accounts/fireworks/routers/qwen-max-latest", + "name": "Qwen Max Latest (Qwen3.8 Max)", + "display_name": "Qwen Max Latest (Qwen3.8 Max)", "modalities": { "input": [ "text", @@ -112521,24 +112736,34 @@ }, "limit": { "context": 262144, - "output": 256000 + "output": 131072 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "none", + "low", + "medium", + "xhigh" + ] + } }, "attachment": true, - "open_weights": true, - "knowledge": "2025-06", - "release_date": "2026-03-16", - "last_updated": "2026-03-16", + "open_weights": false, + "release_date": "2026-08-03", + "last_updated": "2026-08-03", "type": "chat" }, { - "id": "mistral/ministral-8b-2512", - "name": "Ministral 8B (Mistral AI)", - "display_name": "Ministral 8B (Mistral AI)", + "id": "accounts/fireworks/routers/kimi-k3-spot", + "name": "Kimi K3 Spot", + "display_name": "Kimi K3 Spot", "modalities": { "input": [ "text", @@ -112549,24 +112774,42 @@ ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 1048576, + "output": 131072 }, - "temperature": true, - "tool_call": false, + "temperature": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "effort_options": [ + "low", + "medium", + "high", + "max" + ] + } }, "attachment": true, "open_weights": true, - "release_date": "2025-12-02", - "last_updated": "2025-12-02", + "release_date": "2026-07-16", + "last_updated": "2026-07-16", "type": "chat" }, { - "id": "mistral/glm-5.3", - "name": "GLM-5.3 (Mistral AI)", - "display_name": "GLM-5.3 (Mistral AI)", + "id": "accounts/fireworks/routers/glm-5p3-spot", + "name": "GLM 5.3 Spot", + "display_name": "GLM 5.3 Spot", "modalities": { "input": [ "text" @@ -112576,8 +112819,8 @@ ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 1048573, + "output": 262144 }, "temperature": true, "tool_call": true, @@ -112588,6 +112831,12 @@ "extra_capabilities": { "reasoning": { "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ "low", "high", @@ -112602,9 +112851,9 @@ "type": "chat" }, { - "id": "mistral/mistral-large-latest", - "name": "Mistral Large Latest (Mistral AI)", - "display_name": "Mistral Large Latest (Mistral AI)", + "id": "accounts/fireworks/routers/glm-fast-latest", + "name": "GLM 5.3 Fast (Latest)", + "display_name": "GLM 5.3 Fast (Latest)", "modalities": { "input": [ "text" @@ -112614,96 +112863,161 @@ ] }, "limit": { - "context": 128000, + "context": 1048572, "output": 262144 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "effort_options": [ + "low", + "high", + "max" + ] + } }, "attachment": false, "open_weights": true, - "knowledge": "2024-11", - "release_date": "2024-11-01", - "last_updated": "2025-12-02", + "release_date": "2026-08-14", + "last_updated": "2026-08-14", "type": "chat" }, { - "id": "mistral/mistral-medium-3-5", - "name": "Mistral Medium 3.5 (Mistral AI)", - "display_name": "Mistral Medium 3.5 (Mistral AI)", + "id": "accounts/fireworks/routers/glm-latest", + "name": "GLM Latest", + "display_name": "GLM Latest", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 262144, + "context": 1048573, "output": 262144 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": true, - "open_weights": false, - "release_date": "2026-04-30", - "last_updated": "2026-04-30", + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "effort_options": [ + "low", + "high", + "max" + ] + } + }, + "attachment": false, + "open_weights": true, + "release_date": "2026-08-14", + "last_updated": "2026-08-14", "type": "chat" - }, + } + ] + }, + "perplexity-agent": { + "id": "perplexity-agent", + "name": "Perplexity Agent", + "display_name": "Perplexity Agent", + "api": "https://api.perplexity.ai/v1", + "doc": "https://docs.perplexity.ai/docs/agent-api/models", + "models": [ { - "id": "mistral/codestral-2508", - "name": "Codestral (Mistral AI)", - "display_name": "Codestral (Mistral AI)", + "id": "openai/gpt-5.4", + "name": "GPT-5.4", + "display_name": "GPT-5.4", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 256000, - "output": 256000 + "context": 1050000, + "output": 128000 }, - "temperature": true, - "tool_call": false, + "temperature": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": false }, - "attachment": false, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": false, + "mode": "effort", + "effort": "none", + "effort_options": [ + "none", + "low", + "medium", + "high", + "xhigh" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" + } + }, + "attachment": true, "open_weights": false, - "release_date": "2025-07-30", - "last_updated": "2025-07-30", + "knowledge": "2025-08-31", + "release_date": "2026-03-05", + "last_updated": "2026-03-05", "type": "chat" }, { - "id": "deepinfra/qwen3.5-9b", - "name": "Qwen3.5 9B (DeepInfra)", - "display_name": "Qwen3.5 9B (DeepInfra)", + "id": "openai/gpt-6-sol", + "name": "GPT-6 Sol", + "display_name": "GPT-6 Sol", "modalities": { "input": [ "text", "image", - "video" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 32768 + "context": 1050000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -112711,38 +113025,41 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-02-23", - "last_updated": "2026-02-23", + "open_weights": false, + "knowledge": "2026-04-20", + "release_date": "2026-09-22", + "last_updated": "2026-09-22", "type": "chat" }, { - "id": "deepinfra/deepseek-v4-flash", - "name": "DeepSeek V4 Flash (DeepInfra)", - "display_name": "DeepSeek V4 Flash (DeepInfra)", + "id": "openai/gpt-5.6-terra", + "name": "GPT-5.6 Terra", + "display_name": "GPT-5.6 Terra", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 393216 + "context": 1050000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -112753,50 +113070,50 @@ "supported": true, "default_enabled": true, "mode": "effort", - "effort": "high", + "effort": "medium", "effort_options": [ + "none", "low", + "medium", "high", + "xhigh", "max" ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" ], - "notes": [ - "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." - ] + "visibility": "hidden" } }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-04-24", - "last_updated": "2026-04-24", + "attachment": true, + "open_weights": false, + "knowledge": "2026-02-16", + "release_date": "2026-07-09", + "last_updated": "2026-07-09", "type": "chat" }, { - "id": "deepinfra/mimo-v2.6-pro", - "name": "MiMo V2.6 Pro (DeepInfra)", - "display_name": "MiMo V2.6 Pro (DeepInfra)", + "id": "openai/gpt-6-astra", + "name": "GPT-6 Astra", + "display_name": "GPT-6 Astra", "modalities": { "input": [ "text", "image", - "audio", - "video" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 1050000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -112804,9 +113121,8 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, "effort_options": [ - "none", - "minimal", "low", "medium", "high", @@ -112816,58 +113132,83 @@ } }, "attachment": true, - "open_weights": true, - "release_date": "2026-09-22", - "last_updated": "2026-09-22", + "open_weights": false, + "knowledge": "2026-04-30", + "release_date": "2026-09-04", + "last_updated": "2026-09-04", "type": "chat" }, { - "id": "deepinfra/qwen3-vl-30b-a3b-instruct", - "name": "Qwen3 VL 30B A3B Instruct (DeepInfra)", - "display_name": "Qwen3 VL 30B A3B Instruct (DeepInfra)", + "id": "openai/gpt-5.2", + "name": "GPT-5.2", + "display_name": "GPT-5.2", "modalities": { "input": [ "text", - "image", - "video" + "image" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 32768 + "context": 400000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": false + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": false, + "mode": "effort", + "effort": "none", + "effort_options": [ + "none", + "low", + "medium", + "high", + "xhigh" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" + } }, "attachment": true, - "open_weights": true, - "release_date": "2025-10-04", - "last_updated": "2025-10-04", + "open_weights": false, + "knowledge": "2025-08-31", + "release_date": "2025-12-11", + "last_updated": "2025-12-11", "type": "chat" }, { - "id": "deepinfra/deepseek-v4.1-flash-1017", - "name": "DeepSeek V4.1 Flash 1017 (DeepInfra)", - "display_name": "DeepSeek V4.1 Flash 1017 (DeepInfra)", + "id": "openai/gpt-6.1-sol", + "name": "GPT-6.1 Sol", + "display_name": "GPT-6.1 Sol", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 393216 + "context": 1050000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -112876,8 +113217,6 @@ "extra_capabilities": { "reasoning": { "effort_options": [ - "none", - "minimal", "low", "medium", "high", @@ -112887,29 +113226,31 @@ } }, "attachment": true, - "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-10-17", - "last_updated": "2026-10-17", + "open_weights": false, + "knowledge": "2026-04-30", + "release_date": "2026-09-29", + "last_updated": "2026-09-29", "type": "chat" }, { - "id": "deepinfra/deepseek-v4-pro", - "name": "DeepSeek V4 Pro (DeepInfra)", - "display_name": "DeepSeek V4 Pro (DeepInfra)", + "id": "openai/gpt-5.6-luna", + "name": "GPT-5.6 Luna", + "display_name": "GPT-5.6 Luna", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 64000 + "context": 1050000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -112920,34 +113261,35 @@ "supported": true, "default_enabled": true, "mode": "effort", - "effort": "high", + "effort": "medium", "effort_options": [ + "none", "low", + "medium", "high", + "xhigh", "max" ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" ], - "notes": [ - "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." - ] + "visibility": "hidden" } }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-04-24", - "last_updated": "2026-04-24", + "attachment": true, + "open_weights": false, + "knowledge": "2026-02-16", + "release_date": "2026-07-09", + "last_updated": "2026-07-09", "type": "chat" }, { - "id": "deepinfra/qwen3-vl-235b-a22b-instruct", - "name": "Qwen3 VL 235B A22B Instruct (DeepInfra)", - "display_name": "Qwen3 VL 235B A22B Instruct (DeepInfra)", + "id": "openai/gpt-5-mini", + "name": "GPT-5 Mini", + "display_name": "GPT-5 Mini", "modalities": { "input": [ "text", @@ -112958,41 +113300,62 @@ ] }, "limit": { - "context": 262144, - "output": 32768 + "context": 400000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", + "effort_options": [ + "minimal", + "low", + "medium", + "high" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" + } }, "attachment": true, - "open_weights": true, - "knowledge": "2025-03-31", - "release_date": "2025-09-23", - "last_updated": "2025-09-23", + "open_weights": false, + "knowledge": "2024-05-30", + "release_date": "2025-08-07", + "last_updated": "2025-08-07", "type": "chat" }, { - "id": "deepinfra/mimo-v2.6-flash", - "name": "MiMo V2.6 Flash (DeepInfra)", - "display_name": "MiMo V2.6 Flash (DeepInfra)", + "id": "openai/gpt-5.6-sol", + "name": "GPT-5.6 Sol", + "display_name": "GPT-5.6 Sol", "modalities": { "input": [ "text", "image", - "audio", - "video" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 1050000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -113000,112 +113363,104 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", "effort_options": [ "none", - "minimal", "low", "medium", "high", "xhigh", "max" - ] + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, "attachment": true, - "open_weights": true, - "release_date": "2026-09-22", - "last_updated": "2026-09-22", + "open_weights": false, + "knowledge": "2026-02-16", + "release_date": "2026-07-09", + "last_updated": "2026-07-09", "type": "chat" }, { - "id": "deepinfra/gemma-4-31b-it", - "name": "Gemma 4 31B IT (DeepInfra)", - "display_name": "Gemma 4 31B IT (DeepInfra)", + "id": "openai/gpt-5.1", + "name": "GPT-5.1", + "display_name": "GPT-5.1", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 32768 + "context": 400000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": false, + "mode": "effort", + "effort": "none", "effort_options": [ "none", - "minimal", "low", "medium", - "high", - "xhigh", - "max" - ] + "high" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-04-02", - "last_updated": "2026-04-02", - "type": "chat" - }, - { - "id": "deepinfra/ling-3.0-flash", - "name": "InclusionAI Ling 3.0 Flash (DeepInfra)", - "display_name": "InclusionAI Ling 3.0 Flash (DeepInfra)", - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 262144, - "output": 32768 - }, - "temperature": true, - "tool_call": true, - "reasoning": { - "supported": true, - "default": true - }, - "attachment": false, - "open_weights": true, - "release_date": "2026-07-23", - "last_updated": "2026-07-23", + "attachment": true, + "open_weights": false, + "knowledge": "2024-09-30", + "release_date": "2025-11-13", + "last_updated": "2025-11-13", "type": "chat" }, { - "id": "deepinfra/deepseek-v4.1-flash", - "name": "DeepSeek V4.1 Flash (DeepInfra)", - "display_name": "DeepSeek V4.1 Flash (DeepInfra)", + "id": "openai/gpt-5.5", + "name": "GPT-5.5", + "display_name": "GPT-5.5", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 393216 + "context": 1050000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -113114,61 +113469,77 @@ "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", "effort_options": [ - "none", - "minimal", "low", "medium", "high", - "xhigh", - "max" - ] + "xhigh" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, "attachment": true, - "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-09-10", - "last_updated": "2026-09-10", + "open_weights": false, + "knowledge": "2025-12-01", + "release_date": "2026-04-23", + "last_updated": "2026-04-23", "type": "chat" }, { - "id": "deepinfra/deepseek-v3.2", - "name": "DeepSeek V3.2 (DeepInfra)", - "display_name": "DeepSeek V3.2 (DeepInfra)", + "id": "openai/gpt-6-luna", + "name": "GPT-6 Luna", + "display_name": "GPT-6 Luna", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 160000, - "output": 65536 + "context": 1050000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { - "supported": true + "supported": true, + "default": true }, "extra_capabilities": { "reasoning": { - "supported": true + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" + ] } }, - "attachment": false, - "open_weights": true, - "knowledge": "2024-07", - "release_date": "2025-12-01", - "last_updated": "2025-12-01", + "attachment": true, + "open_weights": false, + "knowledge": "2026-05-18", + "release_date": "2026-09-22", + "last_updated": "2026-09-22", "type": "chat" }, { - "id": "deepinfra/gemma-4-26b-a4b-it", - "name": "Gemma 4 26B A4B IT (DeepInfra)", - "display_name": "Gemma 4 26B A4B IT (DeepInfra)", + "id": "nvidia/nemotron-3-super-120b-a12b", + "name": "Nemotron 3 Super 120B", + "display_name": "Nemotron 3 Super 120B", "modalities": { "input": [ "text" @@ -113178,8 +113549,8 @@ ] }, "limit": { - "context": 262144, - "output": 32768 + "context": 1000000, + "output": 32000 }, "temperature": true, "tool_call": true, @@ -113189,32 +113560,39 @@ }, "extra_capabilities": { "reasoning": { - "supported": true + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" + ] } }, "attachment": false, "open_weights": true, - "release_date": "2026-04-02", - "last_updated": "2026-04-02", + "knowledge": "2026-02", + "release_date": "2026-03-11", + "last_updated": "2026-03-11", "type": "chat" }, { - "id": "deepinfra/ling-3.0-flash-vl", - "name": "Ling 3.0 Flash VL (DeepInfra)", - "display_name": "Ling 3.0 Flash VL (DeepInfra)", + "id": "xai/grok-4.7", + "name": "Grok 4.7", + "display_name": "Grok 4.7", "modalities": { "input": [ "text", "image", - "video" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 32768 + "context": 500000, + "output": 500000 }, "temperature": true, "tool_call": true, @@ -113225,34 +113603,36 @@ "extra_capabilities": { "reasoning": { "effort_options": [ - "none", "low", "medium", - "high" + "high", + "xhigh" ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-09-10", - "last_updated": "2026-09-10", + "open_weights": false, + "knowledge": "2026-05", + "release_date": "2026-09-21", + "last_updated": "2026-09-21", "type": "chat" }, { - "id": "deepinfra/granite-4.2-8b", - "name": "Granite 4.2 8B (DeepInfra)", - "display_name": "Granite 4.2 8B (DeepInfra)", + "id": "xai/grok-4.5", + "name": "Grok 4.5", + "display_name": "Grok 4.5", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 117964 + "context": 500000, + "output": 500000 }, "temperature": true, "tool_call": true, @@ -113262,24 +113642,25 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, "effort_options": [ - "none", "low", "medium", - "high" + "high", + "xhigh" ] } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-08-25", - "last_updated": "2026-08-25", + "attachment": true, + "open_weights": false, + "release_date": "2026-07-08", + "last_updated": "2026-07-08", "type": "chat" }, { - "id": "deepinfra/nemotron-3-ultra-550b", - "name": "Nemotron 3 Ultra 550B (DeepInfra)", - "display_name": "Nemotron 3 Ultra 550B (DeepInfra)", + "id": "xai/grok-4-1-fast-non-reasoning", + "name": "Grok 4.1 Fast (Non-Reasoning)", + "display_name": "Grok 4.1 Fast (Non-Reasoning)", "modalities": { "input": [ "text", @@ -113290,8 +113671,8 @@ ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 2000000, + "output": 30000 }, "temperature": true, "tool_call": true, @@ -113300,14 +113681,15 @@ }, "attachment": true, "open_weights": false, - "release_date": "2026-06-01", - "last_updated": "2026-06-01", + "knowledge": "2025-07", + "release_date": "2025-11-19", + "last_updated": "2025-11-19", "type": "chat" }, { - "id": "deepinfra/muse-glimmer-30b", - "name": "Muse Glimmer 30B (DeepInfra)", - "display_name": "Muse Glimmer 30B (DeepInfra)", + "id": "xai/grok-4.6", + "name": "Grok 4.6", + "display_name": "Grok 4.6", "modalities": { "input": [ "text", @@ -113318,8 +113700,8 @@ ] }, "limit": { - "context": 131072, - "output": 16384 + "context": 500000, + "output": 500000 }, "temperature": true, "tool_call": true, @@ -113329,41 +113711,41 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, "effort_options": [ - "minimal", "low", "medium", "high", - "xhigh", - "max" + "xhigh" ] } }, "attachment": true, - "open_weights": true, - "knowledge": "2026-01-04", - "release_date": "2026-08-10", - "last_updated": "2026-08-10", + "open_weights": false, + "knowledge": "2026-02-01", + "release_date": "2026-08-12", + "last_updated": "2026-08-12", "type": "chat" }, { - "id": "azure-ai-foundry/grok-4-1-fast-reasoning", - "name": "Grok 4.1 Fast Reasoning (Azure AI Foundry)", - "display_name": "Grok 4.1 Fast Reasoning (Azure AI Foundry)", + "id": "moonshot-ai/kimi-k3", + "name": "Kimi K3", + "display_name": "Kimi K3", "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 30000 + "context": 1048576, + "output": 131072 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -113373,35 +113755,40 @@ "reasoning": { "supported": true, "effort_options": [ + "minimal", "low", "medium", - "high" + "high", + "xhigh", + "max" ] } }, "attachment": true, - "open_weights": false, - "release_date": "2025-11-19", - "last_updated": "2025-11-19", + "open_weights": true, + "release_date": "2026-07-16", + "last_updated": "2026-07-16", "type": "chat" }, { - "id": "azure-ai-foundry/grok-4-3", - "name": "Grok 4.3 (Azure AI Foundry)", - "display_name": "Grok 4.3 (Azure AI Foundry)", + "id": "moonshot-ai/kimi-k2.7-code", + "name": "Kimi K2.7 Code", + "display_name": "Kimi K2.7 Code", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 20000, - "output": 8192 + "context": 262144, + "output": 262144 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -113409,37 +113796,35 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "none", - "low", - "medium", - "high" - ] + "supported": true } }, - "attachment": false, - "open_weights": false, - "release_date": "2026-04-17", - "last_updated": "2026-04-17", + "attachment": true, + "open_weights": true, + "knowledge": "2025-01", + "release_date": "2026-06-12", + "last_updated": "2026-06-12", "type": "chat" }, { - "id": "novita/qwen3.7-max", - "name": "Qwen3.7 Max (NovitaAI)", - "display_name": "Qwen3.7 Max (NovitaAI)", + "id": "google/gemini-4-argon", + "name": "Gemini 4 Argon", + "display_name": "Gemini 4 Argon", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1000000, + "context": 1048576, "output": 65536 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -113447,41 +113832,37 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, "effort_options": [ - "none", - "minimal", "low", "medium", - "high", - "xhigh", - "max" + "high" ] } }, - "attachment": false, + "attachment": true, "open_weights": false, - "release_date": "2026-05-21", - "last_updated": "2026-05-21", + "knowledge": "2026-03", + "release_date": "2026-10-13", + "last_updated": "2026-10-13", "type": "chat" }, { - "id": "novita/glm-4.5v", - "name": "GLM-4.5V (NovitaAI)", - "display_name": "GLM-4.5V (NovitaAI)", + "id": "google/gemini-3.6-flash", + "name": "Gemini 3.6 Flash", + "display_name": "Gemini 3.6 Flash", "modalities": { "input": [ "text", "image", - "video" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 65536, - "output": 16000 + "context": 1048576, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -113489,22 +113870,40 @@ "supported": true, "default": true }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "level", + "level": "high", + "level_options": [ + "minimal", + "low", + "medium", + "high" + ], + "summaries": true, + "visibility": "summary", + "continuation": [ + "thought_signatures" + ] + } + }, "attachment": true, - "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-08-11", - "last_updated": "2025-08-11", + "open_weights": false, + "knowledge": "2026-03", + "release_date": "2026-07-21", + "last_updated": "2026-07-21", "type": "chat" }, { - "id": "novita/glm-5.3-flash", - "name": "GLM-5.3 Flash (NovitaAI)", - "display_name": "GLM-5.3 Flash (NovitaAI)", + "id": "google/gemini-3.8-flash", + "name": "Gemini 3.8 Flash", + "display_name": "Gemini 3.8 Flash", "modalities": { "input": [ "text", "image", - "video", "pdf" ], "output": [ @@ -113513,7 +113912,7 @@ }, "limit": { "context": 1048576, - "output": 131072 + "output": 65536 }, "temperature": true, "tool_call": true, @@ -113524,62 +113923,47 @@ "extra_capabilities": { "reasoning": { "supported": true, - "effort_options": [ + "default_enabled": true, + "mode": "level", + "level": "high", + "level_options": [ + "minimal", "low", - "high", - "max" + "medium", + "high" + ], + "summaries": true, + "visibility": "summary", + "continuation": [ + "thought_signatures" ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-08-26", - "last_updated": "2026-08-26", - "type": "chat" - }, - { - "id": "novita/qwen3.8-27b", - "name": "Qwen3.8 27B (NovitaAI)", - "display_name": "Qwen3.8 27B (NovitaAI)", - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 1000000, - "output": 131072 - }, - "temperature": true, - "tool_call": true, - "reasoning": { - "supported": true, - "default": true - }, - "attachment": false, - "open_weights": true, - "release_date": "2026-08-14", - "last_updated": "2026-08-14", + "open_weights": false, + "release_date": "2026-09-02", + "last_updated": "2026-09-02", "type": "chat" }, { - "id": "novita/deepseek-v4-flash", - "name": "DeepSeek V4 Flash (NovitaAI)", - "display_name": "DeepSeek V4 Flash (NovitaAI)", + "id": "google/gemini-3.1-pro-preview", + "name": "Gemini 3.1 Pro Preview", + "display_name": "Gemini 3.1 Pro Preview", "modalities": { "input": [ - "text" + "text", + "image", + "video", + "audio", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 393216 + "context": 1048576, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -113591,77 +113975,95 @@ "reasoning": { "supported": true, "default_enabled": true, - "mode": "effort", - "effort": "high", - "effort_options": [ + "mode": "level", + "level": "high", + "level_options": [ "low", - "high", - "max" + "high" ], - "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ - "thinking_blocks" - ], - "notes": [ - "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." + "thought_signatures" ] } }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-04-24", - "last_updated": "2026-04-24", + "attachment": true, + "open_weights": false, + "knowledge": "2025-01", + "release_date": "2026-02-19", + "last_updated": "2026-02-19", "type": "chat" }, { - "id": "novita/qwen3-vl-30b-a3b-instruct", - "name": "Qwen3 VL 30B A3B Instruct (NovitaAI)", - "display_name": "Qwen3 VL 30B A3B Instruct (NovitaAI)", + "id": "google/gemini-3.7-flash", + "name": "Gemini 3.7 Flash", + "display_name": "Gemini 3.7 Flash", "modalities": { "input": [ "text", "image", - "video" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 32768 + "context": 1048576, + "output": 65536 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "level", + "level": "high", + "level_options": [ + "minimal", + "low", + "medium", + "high" + ], + "summaries": true, + "visibility": "summary", + "continuation": [ + "thought_signatures" + ] + } }, "attachment": true, - "open_weights": true, - "release_date": "2025-10-04", - "last_updated": "2025-10-04", + "open_weights": false, + "knowledge": "2026-03", + "release_date": "2026-08-13", + "last_updated": "2026-08-13", "type": "chat" }, { - "id": "novita/step-3.7-flash", - "name": "Step 3.7 Flash (NovitaAI)", - "display_name": "Step 3.7 Flash (NovitaAI)", + "id": "google/gemini-3-flash-preview", + "name": "Gemini 3 Flash Preview", + "display_name": "Gemini 3 Flash Preview", "modalities": { "input": [ "text", "image", - "video" + "video", + "audio", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 256000 + "context": 1048576, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -113671,41 +114073,49 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ + "supported": true, + "default_enabled": true, + "mode": "level", + "level": "high", + "level_options": [ "minimal", "low", "medium", - "high", - "xhigh", - "max" + "high" + ], + "summaries": true, + "visibility": "summary", + "continuation": [ + "thought_signatures" ] } }, "attachment": true, - "open_weights": true, - "knowledge": "2026-03-01", - "release_date": "2026-05-29", - "last_updated": "2026-05-29", + "open_weights": false, + "knowledge": "2025-01", + "release_date": "2025-12-17", + "last_updated": "2025-12-17", "type": "chat" }, { - "id": "novita/deepseek-v4.1-flash-1017", - "name": "DeepSeek V4.1 Flash 1017 (NovitaAI)", - "display_name": "DeepSeek V4.1 Flash 1017 (NovitaAI)", + "id": "anthropic/claude-haiku-5-5", + "name": "Claude Haiku 5.5", + "display_name": "Claude Haiku 5.5", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 393216 + "context": 1000000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -113714,8 +114124,6 @@ "extra_capabilities": { "reasoning": { "effort_options": [ - "none", - "minimal", "low", "medium", "high", @@ -113725,134 +114133,130 @@ } }, "attachment": true, - "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-10-17", - "last_updated": "2026-10-17", + "open_weights": false, + "knowledge": "2026-06", + "release_date": "2026-10-07", + "last_updated": "2026-10-07", "type": "chat" }, { - "id": "novita/kimi-k3", - "name": "Kimi K3 (NovitaAI)", - "display_name": "Kimi K3 (NovitaAI)", + "id": "anthropic/claude-opus-4-7", + "name": "Claude Opus 4.7", + "display_name": "Claude Opus 4.7", "modalities": { "input": [ "text", "image", - "video" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 1048576 + "context": 1000000, + "output": 128000 }, "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": false, + "mode": "effort", + "effort": "high", "effort_options": [ - "none", - "minimal", "low", "medium", "high", "xhigh", "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "omitted", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Claude Opus 4.7 and newer Opus models require thinking.type = \"adaptive\" to enable thinking explicitly.", + "Manual budget_tokens requests return 400 on Claude Opus 4.7 and newer adaptive-only Opus models.", + "task_budget is separate from thinking control and should not be treated as a thinking budget." ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-07-16", - "last_updated": "2026-07-16", + "open_weights": false, + "knowledge": "2026-01-31", + "release_date": "2026-04-16", + "last_updated": "2026-04-16", "type": "chat" }, { - "id": "novita/glm-4.6v", - "name": "GLM-4.6V (NovitaAI)", - "display_name": "GLM-4.6V (NovitaAI)", + "id": "anthropic/claude-haiku-4-5", + "name": "Claude Haiku 4.5", + "display_name": "Claude Haiku 4.5", "modalities": { "input": [ "text", "image", - "video" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 16000 - }, - "temperature": true, - "tool_call": true, - "reasoning": { - "supported": true, - "default": true - }, - "attachment": true, - "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-12-08", - "last_updated": "2025-12-08", - "type": "chat" - }, - { - "id": "novita/glm-4.7", - "name": "GLM-4.7 (NovitaAI)", - "display_name": "GLM-4.7 (NovitaAI)", - "modalities": { - "input": [ - "text" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 204800, - "output": 128000 + "context": 200000, + "output": 64000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": false, + "mode": "budget", + "budget": { + "min": 1024, + "unit": "tokens" + }, "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ "thinking_blocks" + ], + "notes": [ + "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", + "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." ] } }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-12-22", - "last_updated": "2025-12-22", + "attachment": true, + "open_weights": false, + "knowledge": "2025-02-28", + "release_date": "2025-10-15", + "last_updated": "2025-10-15", "type": "chat" }, { - "id": "novita/qwen3.8-2.4t-a95b", - "name": "Qwen3.8 2.4T A95B (NovitaAI)", - "display_name": "Qwen3.8 2.4T A95B (NovitaAI)", + "id": "anthropic/claude-sonnet-5", + "name": "Claude Sonnet 5", + "display_name": "Claude Sonnet 5", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" @@ -113860,9 +114264,9 @@ }, "limit": { "context": 1000000, - "output": 131072 + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -113872,8 +114276,6 @@ "reasoning": { "supported": true, "effort_options": [ - "none", - "minimal", "low", "medium", "high", @@ -113882,105 +114284,131 @@ ] } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-08-12", - "last_updated": "2026-08-12", + "attachment": true, + "open_weights": false, + "knowledge": "2026-01-31", + "release_date": "2026-06-30", + "last_updated": "2026-06-30", "type": "chat" }, { - "id": "novita/gemma-4-31b-it", - "name": "Gemma 4 31B IT (NovitaAI)", - "display_name": "Gemma 4 31B IT (NovitaAI)", + "id": "anthropic/claude-fable-5", + "name": "Claude Fable 5", + "display_name": "Claude Fable 5", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 32768 + "context": 1000000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { - "supported": true + "supported": true, + "default": true }, "extra_capabilities": { "reasoning": { - "supported": true + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "high", + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "omitted", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Adaptive thinking is always on for Claude Fable 5 and Claude Mythos 5; thinking.type = \"disabled\" is rejected.", + "Manual budget_tokens requests return 400 on Claude Fable 5 and Claude Mythos 5.", + "thinking.display defaults to omitted; set display to summarized to receive readable thinking summaries." + ] } }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-04-02", - "last_updated": "2026-04-02", + "attachment": true, + "open_weights": false, + "knowledge": "2026-01-31", + "release_date": "2026-06-09", + "last_updated": "2026-06-09", "type": "chat" }, { - "id": "novita/kimi-k2.6", - "name": "Kimi K2.6 (NovitaAI)", - "display_name": "Kimi K2.6 (NovitaAI)", + "id": "anthropic/claude-sonnet-4-5", + "name": "Claude Sonnet 4.5", + "display_name": "Claude Sonnet 4.5", "modalities": { "input": [ "text", "image", - "video" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 200000, + "output": 64000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": false, + "mode": "budget", + "budget": { + "min": 1024, + "unit": "tokens" + }, "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ "thinking_blocks" ], - "effort_options": [ - "none", - "minimal", - "low", - "medium", - "high", - "xhigh", - "max" + "notes": [ + "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", + "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." ] } }, "attachment": true, - "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-04-20", - "last_updated": "2026-04-20", + "open_weights": false, + "knowledge": "2025-07-31", + "release_date": "2025-09-29", + "last_updated": "2025-09-29", "type": "chat" }, { - "id": "novita/minimax-m3.1-flash", - "name": "MiniMax M3.1 Flash (NovitaAI)", - "display_name": "MiniMax M3.1 Flash (NovitaAI)", + "id": "anthropic/claude-opus-5-5", + "name": "Claude Opus 5.5", + "display_name": "Claude Opus 5.5", "modalities": { "input": [ "text", "image", - "video" + "pdf" ], "output": [ "text" @@ -113988,9 +114416,9 @@ }, "limit": { "context": 1000000, - "output": 524288 + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -114009,64 +114437,88 @@ }, "attachment": true, "open_weights": false, - "release_date": "2026-10-19", - "last_updated": "2026-10-19", + "knowledge": "2026-06", + "release_date": "2026-09-22", + "last_updated": "2026-09-22", "type": "chat" }, { - "id": "novita/ling-3.0-flash", - "name": "InclusionAI Ling 3.0 Flash (NovitaAI)", - "display_name": "InclusionAI Ling 3.0 Flash (NovitaAI)", + "id": "anthropic/claude-opus-4-5", + "name": "Claude Opus 4.5", + "display_name": "Claude Opus 4.5", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 32768 + "context": 200000, + "output": 64000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "none" - ] - } - }, - "attachment": false, - "open_weights": true, - "release_date": "2026-07-23", - "last_updated": "2026-07-23", + "supported": true, + "default_enabled": false, + "mode": "mixed", + "budget": { + "min": 1024, + "unit": "tokens" + }, + "effort": "high", + "effort_options": [ + "low", + "medium", + "high" + ], + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Claude Opus 4.5 uses manual thinking.type = \"enabled\" with budget_tokens; effort can be used alongside the thinking budget.", + "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header." + ] + } + }, + "attachment": true, + "open_weights": false, + "knowledge": "2025-03-31", + "release_date": "2025-11-24", + "last_updated": "2025-11-24", "type": "chat" }, { - "id": "novita/qwen35-397b-a17b", - "name": "Qwen3.5 397B A17B (NovitaAI)", - "display_name": "Qwen3.5 397B A17B (NovitaAI)", + "id": "anthropic/claude-sonnet-5-5", + "name": "Claude Sonnet 5.5", + "display_name": "Claude Sonnet 5.5", "modalities": { "input": [ "text", "image", - "video" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 64000 + "context": 1000000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -114075,8 +114527,6 @@ "extra_capabilities": { "reasoning": { "effort_options": [ - "none", - "minimal", "low", "medium", "high", @@ -114086,106 +114536,138 @@ } }, "attachment": true, - "open_weights": true, - "release_date": "2026-02-15", - "last_updated": "2026-02-15", + "open_weights": false, + "knowledge": "2026-06", + "release_date": "2026-09-28", + "last_updated": "2026-09-28", "type": "chat" }, { - "id": "novita/minimax-m2.5", - "name": "MiniMax M2.5 (NovitaAI)", - "display_name": "MiniMax M2.5 (NovitaAI)", + "id": "anthropic/claude-sonnet-4-6", + "name": "Claude Sonnet 4.6", + "display_name": "Claude Sonnet 4.6", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 204800, - "output": 131100 + "context": 200000, + "output": 64000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": false, + "mode": "mixed", + "budget": { + "min": 1024, + "unit": "tokens" + }, + "effort": "high", "effort_options": [ "low", "medium", "high", - "xhigh", "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Anthropic recommends adaptive thinking with effort for Claude 4.6; budget_tokens remains a deprecated compatibility path.", + "Anthropic API defaults effort to high; lower effort levels should be chosen per workload." ] } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-02-12", - "last_updated": "2026-02-12", + "attachment": true, + "open_weights": false, + "knowledge": "2025-08-31", + "release_date": "2026-02-17", + "last_updated": "2026-03-13", "type": "chat" }, { - "id": "novita/minimax-m2.7", - "name": "MiniMax M2.7 (NovitaAI)", - "display_name": "MiniMax M2.7 (NovitaAI)", + "id": "anthropic/claude-opus-4-6", + "name": "Claude Opus 4.6", + "display_name": "Claude Opus 4.6", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 204800, - "output": 131100 + "context": 200000, + "output": 128000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": false, + "mode": "mixed", + "budget": { + "min": 1024, + "unit": "tokens" + }, + "effort": "high", + "effort_options": [ + "low", + "medium", + "high", + "max" + ], "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ "thinking_blocks" ], - "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" + "notes": [ + "Anthropic recommends adaptive thinking with effort for Claude 4.6; budget_tokens remains a deprecated compatibility path.", + "Anthropic API defaults effort to high; lower effort levels should be chosen per workload." ] } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-03-18", - "last_updated": "2026-03-18", + "attachment": true, + "open_weights": false, + "knowledge": "2025-05-31", + "release_date": "2026-02-05", + "last_updated": "2026-03-13", "type": "chat" }, { - "id": "novita/qwen3.8-max", - "name": "Qwen3.8 Max (NovitaAI)", - "display_name": "Qwen3.8 Max (NovitaAI)", + "id": "anthropic/claude-fable-5-1", + "name": "Claude Fable 5.1", + "display_name": "Claude Fable 5.1", "modalities": { "input": [ "text", "image", - "video", "pdf" ], "output": [ @@ -114194,9 +114676,9 @@ }, "limit": { "context": 1000000, - "output": 131072 + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -114205,72 +114687,55 @@ "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "high", "effort_options": [ "low", "medium", - "xhigh" + "high", + "xhigh", + "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "omitted", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Adaptive thinking is always on for Claude Fable 5 and Claude Mythos 5; thinking.type = \"disabled\" is rejected.", + "Manual budget_tokens requests return 400 on Claude Fable 5 and Claude Mythos 5.", + "thinking.display defaults to omitted; set display to summarized to receive readable thinking summaries." ] } }, "attachment": true, "open_weights": false, - "release_date": "2026-08-03", - "last_updated": "2026-08-03", - "type": "chat" - }, - { - "id": "novita/glm-4.6", - "name": "GLM-4.6 (NovitaAI)", - "display_name": "GLM-4.6 (NovitaAI)", - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 204800, - "output": 131072 - }, - "temperature": true, - "tool_call": true, - "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true - } - }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-09-30", - "last_updated": "2025-09-30", + "knowledge": "2026-06", + "release_date": "2026-09-01", + "last_updated": "2026-09-01", "type": "chat" }, { - "id": "novita/qwen3.5-122b-a10b", - "name": "Qwen3.5 122B A10B (NovitaAI)", - "display_name": "Qwen3.5 122B A10B (NovitaAI)", + "id": "anthropic/claude-opus-5", + "name": "Claude Opus 5", + "display_name": "Claude Opus 5", "modalities": { "input": [ "text", "image", - "video" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 65536 + "context": 1000000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -114279,15 +114744,7 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], "effort_options": [ - "none", - "minimal", "low", "medium", "high", @@ -114297,15 +114754,16 @@ } }, "attachment": true, - "open_weights": true, - "release_date": "2026-02-23", - "last_updated": "2026-02-23", + "open_weights": false, + "knowledge": "2026-05", + "release_date": "2026-07-24", + "last_updated": "2026-07-24", "type": "chat" }, { - "id": "novita/deepseek-v4.1-flash", - "name": "DeepSeek V4.1 Flash (NovitaAI)", - "display_name": "DeepSeek V4.1 Flash (NovitaAI)", + "id": "perplexity/kimi-k3", + "name": "Kimi K3", + "display_name": "Kimi K3", "modalities": { "input": [ "text", @@ -114317,9 +114775,9 @@ }, "limit": { "context": 1048576, - "output": 393216 + "output": 131072 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -114329,7 +114787,6 @@ "reasoning": { "supported": true, "effort_options": [ - "none", "minimal", "low", "medium", @@ -114341,15 +114798,14 @@ }, "attachment": true, "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-09-10", - "last_updated": "2026-09-10", + "release_date": "2026-07-16", + "last_updated": "2026-07-16", "type": "chat" }, { - "id": "novita/kimi-k2", - "name": "Kimi K2 (NovitaAI)", - "display_name": "Kimi K2 (NovitaAI)", + "id": "perplexity/sonar", + "name": "Sonar", + "display_name": "Sonar", "modalities": { "input": [ "text" @@ -114359,8 +114815,8 @@ ] }, "limit": { - "context": 131072, - "output": 131072 + "context": 128000, + "output": 8192 }, "temperature": true, "tool_call": true, @@ -114369,80 +114825,15 @@ }, "attachment": false, "open_weights": false, - "release_date": "2025-07-11", - "last_updated": "2025-07-11", - "type": "chat" - }, - { - "id": "novita/llama-3.3-70b-instruct", - "name": "Llama 3.3 70B Instruct (NovitaAI)", - "display_name": "Llama 3.3 70B Instruct (NovitaAI)", - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 120000 - }, - "temperature": true, - "tool_call": true, - "reasoning": { - "supported": false - }, - "attachment": false, - "open_weights": true, - "knowledge": "2023-12", - "release_date": "2024-12-06", - "last_updated": "2024-12-06", - "type": "chat" - }, - { - "id": "novita/hy3", - "name": "Hy3 (NovitaAI)", - "display_name": "Hy3 (NovitaAI)", - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 262144, - "output": 262144 - }, - "temperature": true, - "tool_call": true, - "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "effort_options": [ - "none", - "low", - "high" - ] - } - }, - "attachment": false, - "open_weights": true, - "release_date": "2026-07-06", - "last_updated": "2026-07-06", + "knowledge": "2025-09-01", + "release_date": "2024-01-01", + "last_updated": "2025-09-01", "type": "chat" }, { - "id": "novita/gemma-4-26b-a4b-it", - "name": "Gemma 4 26B A4B IT (NovitaAI)", - "display_name": "Gemma 4 26B A4B IT (NovitaAI)", + "id": "perplexity/nemotron-3.5-super-120b-a12b", + "name": "Nemotron 3.5 Super 120B A12B", + "display_name": "Nemotron 3.5 Super 120B A12B", "modalities": { "input": [ "text" @@ -114452,58 +114843,17 @@ ] }, "limit": { - "context": 262144, - "output": 32768 + "context": 1048576, + "output": 131072 }, "temperature": true, "tool_call": true, - "reasoning": { - "supported": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true - } - }, - "attachment": false, - "open_weights": true, - "release_date": "2026-04-02", - "last_updated": "2026-04-02", - "type": "chat" - }, - { - "id": "novita/kimi-k2.7-code", - "name": "Kimi K2.7 Code (NovitaAI)", - "display_name": "Kimi K2.7 Code (NovitaAI)", - "modalities": { - "input": [ - "text", - "image", - "video" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 262144, - "output": 262144 - }, - "temperature": false, - "tool_call": true, "reasoning": { "supported": true, "default": true }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], "effort_options": [ "low", "medium", @@ -114513,30 +114863,29 @@ ] } }, - "attachment": true, + "attachment": false, "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-06-12", - "last_updated": "2026-06-12", + "release_date": "2026-10-15", + "last_updated": "2026-10-15", "type": "chat" }, { - "id": "novita/qwen3.6-35b-a3b", - "name": "Qwen3.6 35B A3B (NovitaAI)", - "display_name": "Qwen3.6 35B A3B (NovitaAI)", + "id": "perplexity/glm-5.3-flash", + "name": "GLM-5.3-Flash", + "display_name": "GLM-5.3-Flash", "modalities": { "input": [ "text", "image", - "video" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 64000 + "context": 1000000, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -114547,33 +114896,23 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], "effort_options": [ - "none", - "minimal", "low", - "medium", "high", - "xhigh", "max" ] } }, "attachment": true, "open_weights": true, - "release_date": "2026-04-17", - "last_updated": "2026-04-17", + "release_date": "2026-08-26", + "last_updated": "2026-08-26", "type": "chat" }, { - "id": "novita/glm-5.1", - "name": "GLM-5.1 (NovitaAI)", - "display_name": "GLM-5.1 (NovitaAI)", + "id": "perplexity/glm-5.3", + "name": "GLM-5.3", + "display_name": "GLM-5.3", "modalities": { "input": [ "text" @@ -114583,7 +114922,7 @@ ] }, "limit": { - "context": 204800, + "context": 1000000, "output": 131072 }, "temperature": true, @@ -114595,33 +114934,23 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], "effort_options": [ - "none", - "minimal", "low", - "medium", "high", - "xhigh", "max" ] } }, "attachment": false, "open_weights": true, - "release_date": "2026-04-07", - "last_updated": "2026-04-07", + "release_date": "2026-08-14", + "last_updated": "2026-08-14", "type": "chat" }, { - "id": "novita/qwen3.8-flash", - "name": "Qwen3.8 Flash (NovitaAI)", - "display_name": "Qwen3.8 Flash (NovitaAI)", + "id": "perplexity/deepseek-v4-pro-0813", + "name": "DeepSeek V4 Pro 0813", + "display_name": "DeepSeek V4 Pro 0813", "modalities": { "input": [ "text" @@ -114632,7 +114961,7 @@ }, "limit": { "context": 1000000, - "output": 131072 + "output": 384000 }, "temperature": true, "tool_call": true, @@ -114644,55 +114973,22 @@ "reasoning": { "supported": true, "effort_options": [ - "none", - "minimal", "low", - "medium", "high", - "xhigh", "max" ] } }, "attachment": false, - "open_weights": false, - "release_date": "2026-08-26", - "last_updated": "2026-08-26", - "type": "chat" - }, - { - "id": "novita/llama-4-maverick-17b-instruct", - "name": "Llama 4 Maverick 17B Instruct (NovitaAI)", - "display_name": "Llama 4 Maverick 17B Instruct (NovitaAI)", - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 1048576, - "output": 8192 - }, - "temperature": true, - "tool_call": false, - "reasoning": { - "supported": false - }, - "attachment": true, "open_weights": true, - "knowledge": "2024-08", - "release_date": "2025-04-05", - "last_updated": "2025-04-05", + "release_date": "2026-08-13", + "last_updated": "2026-08-22", "type": "chat" }, { - "id": "novita/glm-5", - "name": "GLM-5 (NovitaAI)", - "display_name": "GLM-5 (NovitaAI)", + "id": "deepseek/deepseek-v4-flash-0731", + "name": "DeepSeek V4 Flash 0731", + "display_name": "DeepSeek V4 Flash 0731", "modalities": { "input": [ "text" @@ -114702,8 +114998,8 @@ ] }, "limit": { - "context": 202800, - "output": 131072 + "context": 1000000, + "output": 384000 }, "temperature": true, "tool_call": true, @@ -114714,44 +115010,45 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], "effort_options": [ - "none", - "minimal", "low", - "medium", "high", - "xhigh", "max" ] } }, "attachment": false, "open_weights": true, - "release_date": "2026-02-12", - "last_updated": "2026-02-12", + "knowledge": "2025-05", + "release_date": "2026-07-31", + "last_updated": "2026-07-31", "type": "chat" - }, + } + ] + }, + "xai": { + "id": "xai", + "name": "xai", + "display_name": "xai", + "doc": "https://docs.x.ai/docs/models", + "models": [ { - "id": "novita/glm-5.3", - "name": "GLM-5.3 (NovitaAI)", - "display_name": "GLM-5.3 (NovitaAI)", + "id": "grok-build-0.1", + "name": "Grok Build 0.1", + "display_name": "Grok Build 0.1", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 256000, + "output": 256000 }, "temperature": true, "tool_call": true, @@ -114761,64 +115058,60 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "effort_options": [ - "low", - "high", - "max" - ] + "supported": true } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-08-14", - "last_updated": "2026-08-14", + "attachment": true, + "open_weights": false, + "release_date": "2026-04-16", + "last_updated": "2026-04-16", "type": "chat" }, { - "id": "novita/llama-4-scout-17b-instruct", - "name": "Llama 4 Scout 17B Instruct (NovitaAI)", - "display_name": "Llama 4 Scout 17B Instruct (NovitaAI)", + "id": "grok-imagine-video-1.5", + "name": "Grok Imagine Video 1.5", + "display_name": "Grok Imagine Video 1.5", "modalities": { "input": [ "text", - "image" + "image", + "audio" ], "output": [ - "text" + "video" ] }, "limit": { - "context": 131072, - "output": 131072 + "context": 1024, + "output": 8192 }, - "temperature": true, + "temperature": false, "tool_call": false, "reasoning": { "supported": false }, "attachment": true, - "open_weights": true, - "knowledge": "2024-08", - "release_date": "2025-04-05", - "last_updated": "2025-04-05", - "type": "chat" + "open_weights": false, + "release_date": "2026-05-30", + "last_updated": "2026-05-30" }, { - "id": "novita/glm-5.2", - "name": "GLM-5.2 (NovitaAI)", - "display_name": "GLM-5.2 (NovitaAI)", + "id": "grok-4.7", + "name": "Grok 4.7", + "display_name": "Grok 4.7", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 500000, + "output": 500000 }, "temperature": true, "tool_call": true, @@ -114828,49 +115121,40 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], "effort_options": [ - "none", - "minimal", "low", "medium", "high", - "xhigh", - "max" + "xhigh" ] } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-06-13", - "last_updated": "2026-06-13", + "attachment": true, + "open_weights": false, + "knowledge": "2026-05", + "release_date": "2026-09-21", + "last_updated": "2026-09-21", "type": "chat" }, { - "id": "moonshot/kimi-k3", - "name": "Kimi K3 (Moonshot AI)", - "display_name": "Kimi K3 (Moonshot AI)", + "id": "grok-4.5", + "name": "Grok 4.5", + "display_name": "Grok 4.5", "modalities": { "input": [ "text", "image", - "video" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 1048576 + "context": 500000, + "output": 500000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -114881,37 +115165,38 @@ "supported": true, "effort_options": [ "low", + "medium", "high", - "max" + "xhigh" ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-07-16", - "last_updated": "2026-07-16", + "open_weights": false, + "release_date": "2026-07-08", + "last_updated": "2026-07-08", "type": "chat" }, { - "id": "moonshot/kimi-k2.6", - "name": "Kimi K2.6 (Moonshot AI)", - "display_name": "Kimi K2.6 (Moonshot AI)", + "id": "grok-4.20-multi-agent-0309", + "name": "Grok 4.20 Multi-Agent", + "display_name": "Grok 4.20 Multi-Agent", "modalities": { "input": [ "text", "image", - "video" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 1000000, + "output": 30000 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { "supported": true, "default": true @@ -114919,34 +115204,24 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], "effort_options": [ - "none", - "minimal", "low", "medium", "high", - "xhigh", - "max" + "xhigh" ] } }, "attachment": true, - "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-04-20", - "last_updated": "2026-04-20", + "open_weights": false, + "release_date": "2026-03-09", + "last_updated": "2026-03-09", "type": "chat" }, { - "id": "moonshot/kimi-k2.5", - "name": "Kimi K2.5 (Moonshot AI)", - "display_name": "Kimi K2.5 (Moonshot AI)", + "id": "grok-imagine-video", + "name": "Grok Imagine Video", + "display_name": "Grok Imagine Video", "modalities": { "input": [ "text", @@ -114954,142 +115229,99 @@ "video" ], "output": [ - "text" + "video" ] }, "limit": { - "context": 262144, - "output": 32768 + "context": 1024, + "output": 8192 }, "temperature": false, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "effort_options": [ - "none", - "minimal", - "low", - "medium", - "high", - "xhigh", - "max" - ] - } + "supported": false }, "attachment": true, - "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-01-27", - "last_updated": "2026-01-27", - "type": "chat" + "open_weights": false, + "release_date": "2026-01-28", + "last_updated": "2026-01-28" }, { - "id": "moonshot/kimi-k3.1-pro-highspeed", - "name": "Kimi K3.1 Pro Highspeed (Moonshot AI)", - "display_name": "Kimi K3.1 Pro Highspeed (Moonshot AI)", + "id": "grok-imagine-video-1.5-lite", + "name": "Grok Imagine Video 1.5 Lite", + "display_name": "Grok Imagine Video 1.5 Lite", "modalities": { "input": [ "text", "image" ], "output": [ - "text" + "video" ] }, "limit": { - "context": 1048576, - "output": 1048576 + "context": 1024, + "output": 8192 }, "temperature": false, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "low", - "high", - "max" - ] - } + "supported": false }, "attachment": true, "open_weights": false, - "release_date": "2026-10-23", - "last_updated": "2026-10-23", - "type": "chat" + "release_date": "2026-10-01", + "last_updated": "2026-10-01" }, { - "id": "moonshot/kimi-k3.1-pro", - "name": "Kimi K3.1 Pro (Moonshot AI)", - "display_name": "Kimi K3.1 Pro (Moonshot AI)", + "id": "grok-4.20-0309-reasoning", + "name": "Grok 4.20 (Reasoning)", + "display_name": "Grok 4.20 (Reasoning)", "modalities": { "input": [ "text", "image", - "video" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 1048576 + "context": 1000000, + "output": 30000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "low", - "high", - "max" - ] - } - }, "attachment": true, "open_weights": false, - "release_date": "2026-10-23", - "last_updated": "2026-10-23", + "release_date": "2026-03-09", + "last_updated": "2026-03-09", "type": "chat" }, { - "id": "moonshot/kimi-k2.7-code", - "name": "Kimi K2.7 Code (Moonshot AI)", - "display_name": "Kimi K2.7 Code (Moonshot AI)", + "id": "grok-4.3", + "name": "Grok 4.3", + "display_name": "Grok 4.3", "modalities": { "input": [ "text", "image", - "video" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 1000000, + "output": 30000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -115098,86 +115330,58 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], "effort_options": [ + "none", "low", "medium", "high", - "xhigh", - "max" + "xhigh" ] } }, "attachment": true, - "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-06-12", - "last_updated": "2026-06-12", + "open_weights": false, + "release_date": "2026-04-17", + "last_updated": "2026-04-17", "type": "chat" }, { - "id": "moonshot/kimi-k2.7-code-highspeed", - "name": "Kimi K2.7 Code Highspeed (Moonshot AI)", - "display_name": "Kimi K2.7 Code Highspeed (Moonshot AI)", + "id": "grok-4.20-0309-non-reasoning", + "name": "Grok 4.20 (Non-Reasoning)", + "display_name": "Grok 4.20 (Non-Reasoning)", "modalities": { "input": [ "text", "image", - "video" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 1000000, + "output": 30000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" - ] - } + "supported": false }, "attachment": true, - "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-06-12", - "last_updated": "2026-06-12", + "open_weights": false, + "release_date": "2026-03-09", + "last_updated": "2026-03-09", "type": "chat" }, { - "id": "together-ai/glm-5.3-flash", - "name": "GLM-5.3 Flash (Together AI)", - "display_name": "GLM-5.3 Flash (Together AI)", + "id": "grok-4.6", + "name": "Grok 4.6", + "display_name": "Grok 4.6", "modalities": { "input": [ "text", "image", - "video", "pdf" ], "output": [ @@ -115185,8 +115389,8 @@ ] }, "limit": { - "context": 1048576, - "output": 943717 + "context": 500000, + "output": 500000 }, "temperature": true, "tool_call": true, @@ -115199,97 +115403,79 @@ "supported": true, "effort_options": [ "low", + "medium", "high", - "max" + "xhigh" ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-08-26", - "last_updated": "2026-08-26", + "open_weights": false, + "knowledge": "2026-02-01", + "release_date": "2026-08-12", + "last_updated": "2026-08-12", "type": "chat" }, { - "id": "together-ai/deepseek-v4.1-flash-1017", - "name": "DeepSeek V4.1 Flash 1017 (Together AI)", - "display_name": "DeepSeek V4.1 Flash 1017 (Together AI)", + "id": "grok-imagine-image", + "name": "Grok Imagine Image", + "display_name": "Grok Imagine Image", "modalities": { "input": [ "text", "image" ], "output": [ - "text" + "image" ] }, "limit": { - "context": 1048576, - "output": 393216 + "context": 16000, + "output": 8192 }, - "temperature": true, - "tool_call": true, + "temperature": false, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "none", - "low", - "high", - "xhigh", - "max" - ] - } + "supported": false }, "attachment": true, - "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-10-17", - "last_updated": "2026-10-17", - "type": "chat" + "open_weights": false, + "release_date": "2026-01-28", + "last_updated": "2026-01-28" }, { - "id": "together-ai/kimi-k3", - "name": "Kimi K3 (Together AI)", - "display_name": "Kimi K3 (Together AI)", + "id": "grok-3-mini-fast-beta", + "name": "Grok 3 Mini Fast Beta", + "display_name": "Grok 3 Mini Fast Beta", "modalities": { "input": [ - "text", - "image", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 1000000 + "context": 120000, + "output": 100000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": true, + "effort": "low" }, - "extra_capabilities": { - "reasoning": { - "supported": true - } + "search": { + "supported": false }, - "attachment": true, - "open_weights": true, - "release_date": "2026-07-16", - "last_updated": "2026-07-16", + "attachment": false, "type": "chat" }, { - "id": "together-ai/deepseek-v4-pro", - "name": "DeepSeek V4 Pro (Together AI)", - "display_name": "DeepSeek V4 Pro (Together AI)", + "id": "grok-3-mini-beta", + "name": "Grok 3 Mini Beta", + "display_name": "Grok 3 Mini Beta", "modalities": { "input": [ "text" @@ -115299,48 +115485,26 @@ ] }, "limit": { - "context": 1048576, - "output": 163840 + "context": 120000, + "output": 100000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": true, + "effort": "low" }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "high", - "effort_options": [ - "low", - "high", - "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." - ] - } + "search": { + "supported": false }, "attachment": false, - "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-04-24", - "last_updated": "2026-04-24", "type": "chat" }, { - "id": "together-ai/glm-4.7", - "name": "GLM-4.7 (Together AI)", - "display_name": "GLM-4.7 (Together AI)", + "id": "grok-3-fast-beta", + "name": "Grok 3 Fast Beta", + "display_name": "Grok 3 Fast Beta", "modalities": { "input": [ "text" @@ -115350,139 +115514,107 @@ ] }, "limit": { - "context": 202752, - "output": 128000 + "context": 120000, + "output": 100000 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": true, - "default": true + "supported": false }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } + "search": { + "supported": false }, "attachment": false, - "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-12-22", - "last_updated": "2025-12-22", "type": "chat" }, { - "id": "together-ai/qwen3.8-2.4t-a95b", - "name": "Qwen3.8 2.4T A95B (Together AI)", - "display_name": "Qwen3.8 2.4T A95B (Together AI)", + "id": "grok-2-vision-1212", + "name": "Grok 2 Vision 1212", + "display_name": "Grok 2 Vision 1212", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 1010000, - "output": 909000 + "context": 32000, + "output": 32000 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true + "supported": false }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "effort_options": [ - "none", - "low", - "medium", - "xhigh" - ] - } + "search": { + "supported": false }, "attachment": false, - "open_weights": true, - "release_date": "2026-08-12", - "last_updated": "2026-08-12", "type": "chat" }, { - "id": "together-ai/minimax-m3", - "name": "MiniMax M3 (Together AI)", - "display_name": "MiniMax M3 (Together AI)", + "id": "grok-2-image-1212", + "name": "Grok 2 Image 1212", + "display_name": "Grok 2 Image 1212", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 524288, - "output": 131072 + "context": 130000, + "output": 100000 }, "temperature": true, "tool_call": false, "reasoning": { - "supported": true, - "default": true + "supported": false }, - "extra_capabilities": { - "reasoning": { - "supported": true - } + "search": { + "supported": false }, "attachment": false, - "open_weights": true, - "release_date": "2026-06-01", - "last_updated": "2026-06-01", "type": "chat" }, { - "id": "together-ai/minimax-m3.1-flash", - "name": "MiniMax M3.1 Flash (Together AI)", - "display_name": "MiniMax M3.1 Flash (Together AI)", + "id": "grok-3-beta", + "name": "Grok 3 Beta", + "display_name": "Grok 3 Beta", "modalities": { "input": [ - "text", - "image", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 1048576 + "context": 120000, + "output": 100000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true + "supported": false }, - "attachment": true, - "open_weights": false, - "release_date": "2026-10-19", - "last_updated": "2026-10-19", + "search": { + "supported": false + }, + "attachment": false, "type": "chat" }, { - "id": "together-ai/gpt-oss-120b", - "name": "GPT OSS 120B (Together AI)", - "display_name": "GPT OSS 120B (Together AI)", + "id": "grok-2-1212", + "name": "Grok 2 1212", + "display_name": "Grok 2 1212", "modalities": { "input": [ "text" @@ -115492,36 +115624,33 @@ ] }, "limit": { - "context": 131072, - "output": 32768 + "context": 120000, + "output": 100000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true + "supported": false }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "effort_options": [ - "low", - "medium", - "high" - ] - } + "search": { + "supported": false }, "attachment": false, - "open_weights": true, - "knowledge": "2024-06", - "release_date": "2025-08-05", - "last_updated": "2025-08-05", "type": "chat" - }, + } + ] + }, + "agnes": { + "id": "agnes", + "name": "Agnes AI", + "display_name": "Agnes AI", + "api": "https://apihub.agnes-ai.com/v1", + "doc": "https://agnes-ai.com/doc", + "models": [ { - "id": "together-ai/deepseek-v4.1-flash", - "name": "DeepSeek V4.1 Flash (Together AI)", - "display_name": "DeepSeek V4.1 Flash (Together AI)", + "id": "agnes-2.0-flash", + "name": "Agnes 2.0 Flash", + "display_name": "Agnes 2.0 Flash", "modalities": { "input": [ "text", @@ -115532,8 +115661,8 @@ ] }, "limit": { - "context": 1048576, - "output": 393216 + "context": 512000, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -115544,37 +115673,36 @@ "extra_capabilities": { "reasoning": { "supported": true, - "effort_options": [ - "none", - "low", - "high", - "xhigh", - "max" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, "attachment": true, - "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-09-10", - "last_updated": "2026-09-10", + "open_weights": false, + "release_date": "2026-05-25", + "last_updated": "2026-05-25", "type": "chat" }, { - "id": "together-ai/glm-5.3", - "name": "GLM-5.3 (Together AI)", - "display_name": "GLM-5.3 (Together AI)", + "id": "agnes-3.0-flash", + "name": "Agnes 3.0 Flash", + "display_name": "Agnes 3.0 Flash", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 943717 + "context": 512000, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -115585,23 +115713,24 @@ "extra_capabilities": { "reasoning": { "supported": true, - "effort_options": [ - "low", - "high", - "max" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-08-14", - "last_updated": "2026-08-14", + "attachment": true, + "open_weights": false, + "release_date": "2026-09", + "last_updated": "2026-09", "type": "chat" }, { - "id": "together-ai/muse-glimmer-30b", - "name": "Muse Glimmer 30B (Together AI)", - "display_name": "Muse Glimmer 30B (Together AI)", + "id": "agnes-2.5-pro-alpha", + "name": "Agnes 2.5 Pro Alpha", + "display_name": "Agnes 2.5 Pro Alpha", "modalities": { "input": [ "text", @@ -115612,8 +115741,8 @@ ] }, "limit": { - "context": 131072, - "output": 117964 + "context": 1000000, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -115623,42 +115752,39 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "minimal", - "low", - "medium", - "high", - "xhigh", - "max" + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, "attachment": true, "open_weights": true, - "knowledge": "2026-01-04", - "release_date": "2026-08-10", - "last_updated": "2026-08-10", + "release_date": "2026-07-24", + "last_updated": "2026-07-24", "type": "chat" }, { - "id": "aws-mantle/gpt-6-astra", - "name": "GPT-6 Astra (AWS Mantle)", - "display_name": "GPT-6 Astra (AWS Mantle)", + "id": "agnes-2.5-flash", + "name": "Agnes 2.5 Flash", + "display_name": "Agnes 2.5 Flash", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 512000, + "output": 65536 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -115667,41 +115793,46 @@ "extra_capabilities": { "reasoning": { "supported": true, - "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-04-30", - "release_date": "2026-09-04", - "last_updated": "2026-09-04", + "release_date": "2026-07", + "last_updated": "2026-07", "type": "chat" - }, + } + ] + }, + "tencent-token-plan": { + "id": "tencent-token-plan", + "name": "Tencent Token Plan", + "display_name": "Tencent Token Plan", + "api": "https://api.lkeap.cloud.tencent.com/plan/v3", + "doc": "https://cloud.tencent.com/document/product/1823/130060", + "models": [ { - "id": "aws-mantle/gpt-5.6-sol", - "name": "GPT-5.6 Sol (AWS Mantle)", - "display_name": "GPT-5.6 Sol (AWS Mantle)", + "id": "hy3", + "name": "Hy3", + "display_name": "Hy3", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 921600, + "context": 256000, "output": 128000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -115710,52 +115841,35 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", "effort_options": [ "none", - "low", - "medium", - "high", - "xhigh", - "max" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", "high" - ], - "visibility": "hidden" + ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-02-16", - "release_date": "2026-07-09", - "last_updated": "2026-07-09", + "attachment": false, + "open_weights": true, + "release_date": "2026-07-06", + "last_updated": "2026-07-06", "type": "chat" }, { - "id": "aws-mantle/gpt-5.6-terra", - "name": "GPT-5.6 Terra (AWS Mantle)", - "display_name": "GPT-5.6 Terra (AWS Mantle)", + "id": "hy4-preview", + "name": "Hy4 preview", + "display_name": "Hy4 preview", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 921600, - "output": 128000 + "context": 1024000, + "output": 64000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -115764,50 +115878,43 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", "effort_options": [ "none", - "low", - "medium", - "high", - "xhigh", - "max" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", "high" - ], - "visibility": "hidden" + ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-02-16", - "release_date": "2026-07-09", - "last_updated": "2026-07-09", + "attachment": false, + "open_weights": true, + "release_date": "2026-08-28", + "last_updated": "2026-08-28", "type": "chat" - }, + } + ] + }, + "morph": { + "id": "morph", + "name": "Morph", + "display_name": "Morph", + "api": "https://api.morphllm.com/v1", + "doc": "https://docs.morphllm.com/api-reference/introduction", + "models": [ { - "id": "aws-mantle/gpt-5.6-luna", - "name": "GPT-5.6 Luna (AWS Mantle)", - "display_name": "GPT-5.6 Luna (AWS Mantle)", + "id": "morph-kimik3", + "name": "Kimi K3", + "display_name": "Kimi K3", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 921600, - "output": 128000 + "context": 1048576, + "output": 1048576 }, "temperature": false, "tool_call": true, @@ -115818,425 +115925,351 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ "none", "low", - "medium", "high", - "xhigh", "max" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2026-02-16", - "release_date": "2026-07-09", - "last_updated": "2026-07-09", + "open_weights": true, + "release_date": "2026-07-16", + "last_updated": "2026-07-16", "type": "chat" }, { - "id": "vertex-anthropic/claude-opus-4-7", - "name": "Claude Opus 4.7 (Vertex AI (Anthropic))", - "display_name": "Claude Opus 4.7 (Vertex AI (Anthropic))", + "id": "morph-dsv41flash", + "name": "DeepSeek V4.1 Flash", + "display_name": "DeepSeek V4.1 Flash", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 1048576, + "output": 1048576 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "high", - "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" - ], "interleaved": true, "summaries": true, - "visibility": "omitted", + "visibility": "summary", "continuation": [ "thinking_blocks" ], - "notes": [ - "Claude Opus 4.7 and newer Opus models require thinking.type = \"adaptive\" to enable thinking explicitly.", - "Manual budget_tokens requests return 400 on Claude Opus 4.7 and newer adaptive-only Opus models.", - "task_budget is separate from thinking control and should not be treated as a thinking budget." + "effort_options": [ + "none", + "low", + "high", + "xhigh", + "max" ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2026-01-31", - "release_date": "2026-04-16", - "last_updated": "2026-04-16", + "open_weights": true, + "knowledge": "2025-05", + "release_date": "2026-09-10", + "last_updated": "2026-09-10", "type": "chat" }, { - "id": "vertex-anthropic/claude-sonnet-5", - "name": "Claude Sonnet 5 (Vertex AI (Anthropic))", - "display_name": "Claude Sonnet 5 (Vertex AI (Anthropic))", + "id": "auto", + "name": "Auto", + "display_name": "Auto", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 32000, + "output": 32000 }, "temperature": false, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true + "supported": true }, "extra_capabilities": { "reasoning": { - "supported": true, - "effort_options": [ - "none", - "low", - "medium", - "high", - "xhigh", - "max" - ] + "supported": true } }, - "attachment": true, + "attachment": false, "open_weights": false, - "knowledge": "2026-01-31", - "release_date": "2026-06-30", - "last_updated": "2026-06-30", + "release_date": "2024-06-01", + "last_updated": "2024-06-01", "type": "chat" }, { - "id": "vertex-anthropic/claude-sonnet-4-6", - "name": "Claude Sonnet 4.6 (Vertex AI (Anthropic))", - "display_name": "Claude Sonnet 4.6 (Vertex AI (Anthropic))", + "id": "morph-v3-fast", + "name": "Morph V3 Fast", + "display_name": "Morph V3 Fast", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 64000 + "context": 262144, + "output": 131072 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": false - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "mixed", - "budget": { - "min": 1024, - "unit": "tokens" - }, - "effort": "high", - "effort_options": [ - "low", - "medium", - "high", - "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Anthropic recommends adaptive thinking with effort for Claude 4.6; budget_tokens remains a deprecated compatibility path.", - "Anthropic API defaults effort to high; lower effort levels should be chosen per workload." - ] - } + "supported": false }, - "attachment": true, + "attachment": false, "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-02-17", - "last_updated": "2026-03-13", + "release_date": "2025-07-07", + "last_updated": "2025-07-07", "type": "chat" }, { - "id": "vertex-anthropic/claude-opus-4-6", - "name": "Claude Opus 4.6 (Vertex AI (Anthropic))", - "display_name": "Claude Opus 4.6 (Vertex AI (Anthropic))", + "id": "morph-glm53flash", + "name": "GLM-5.3-Flash", + "display_name": "GLM-5.3-Flash", "modalities": { "input": [ "text", "image", - "pdf" + "video" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 1048576, + "output": 1048576 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "mixed", - "budget": { - "min": 1024, - "unit": "tokens" - }, - "effort": "high", - "effort_options": [ - "low", - "medium", - "high", - "max" - ], "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ "thinking_blocks" ], - "notes": [ - "Anthropic recommends adaptive thinking with effort for Claude 4.6; budget_tokens remains a deprecated compatibility path.", - "Anthropic API defaults effort to high; lower effort levels should be chosen per workload." + "effort_options": [ + "none", + "low", + "high", + "max" ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2025-05-31", - "release_date": "2026-02-05", - "last_updated": "2026-03-13", + "open_weights": true, + "release_date": "2026-08-26", + "last_updated": "2026-08-26", "type": "chat" }, { - "id": "vertex-anthropic/claude-haiku-4-5", - "name": "Claude Haiku 4.5 (Vertex AI (Anthropic))", - "display_name": "Claude Haiku 4.5 (Vertex AI (Anthropic))", + "id": "morph-glm53-744b", + "name": "GLM-5.3", + "display_name": "GLM-5.3", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 64000 + "context": 1048576, + "output": 1048576 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "budget", - "budget": { - "min": 1024, - "unit": "tokens" - }, "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ "thinking_blocks" ], - "notes": [ - "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", - "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." + "effort_options": [ + "none", + "low", + "high", + "max" ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-02-28", - "release_date": "2025-10-15", - "last_updated": "2025-10-15", + "attachment": false, + "open_weights": true, + "release_date": "2026-08-14", + "last_updated": "2026-08-14", "type": "chat" }, { - "id": "vertex-anthropic/claude-sonnet-4-5", - "name": "Claude Sonnet 4.5 (Vertex AI (Anthropic))", - "display_name": "Claude Sonnet 4.5 (Vertex AI (Anthropic))", + "id": "morph-kimik3-fast", + "name": "Kimi K3 Fast", + "display_name": "Kimi K3 Fast", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 64000 + "context": 1048576, + "output": 1048576 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "budget", - "budget": { - "min": 1024, - "unit": "tokens" - }, "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ "thinking_blocks" ], - "notes": [ - "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", - "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." + "effort_options": [ + "none", + "low", + "high" ] } }, "attachment": true, + "open_weights": true, + "release_date": "2026-07-16", + "last_updated": "2026-07-16", + "type": "chat" + }, + { + "id": "morph-v3-large", + "name": "Morph V3 Large", + "display_name": "Morph V3 Large", + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 262144, + "output": 131072 + }, + "temperature": true, + "tool_call": false, + "reasoning": { + "supported": false + }, + "attachment": false, "open_weights": false, - "knowledge": "2025-07-31", - "release_date": "2025-09-29", - "last_updated": "2025-09-29", + "release_date": "2025-07-07", + "last_updated": "2025-07-07", "type": "chat" }, { - "id": "vertex-anthropic/claude-opus-4-5-20251101", - "name": "Claude Opus 4.5 (Vertex AI (Anthropic))", - "display_name": "Claude Opus 4.5 (Vertex AI (Anthropic))", + "id": "morph-dsv4flash", + "name": "DeepSeek V4 Flash 0731", + "display_name": "DeepSeek V4 Flash 0731", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 32000 + "context": 1048576, + "output": 1048576 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "mixed", - "budget": { - "min": 1024, - "unit": "tokens" - }, - "effort": "high", - "effort_options": [ - "low", - "medium", - "high" - ], "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ "thinking_blocks" ], - "notes": [ - "Claude Opus 4.5 uses manual thinking.type = \"enabled\" with budget_tokens; effort can be used alongside the thinking budget.", - "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header." + "effort_options": [ + "none", + "low", + "high", + "max" ] } }, - "attachment": true, - "open_weights": false, + "attachment": false, + "open_weights": true, "knowledge": "2025-05", - "release_date": "2025-11-24", - "last_updated": "2025-11-24", + "release_date": "2026-07-31", + "last_updated": "2026-07-31", "type": "chat" }, { - "id": "meta/muse-wonder-1.0", - "name": "Muse Wonder 1.0 (Meta)", - "display_name": "Muse Wonder 1.0 (Meta)", + "id": "morph-dsv41flash-1017", + "name": "DeepSeek V4.1 Flash 1017", + "display_name": "DeepSeek V4.1 Flash 1017", "modalities": { "input": [ "text", - "image", - "video", - "pdf", - "audio" + "image" ], "output": [ "text" @@ -116254,32 +116287,36 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ - "minimal", + "none", "low", - "medium", "high", - "xhigh" + "xhigh", + "max" ] } }, "attachment": true, - "open_weights": false, - "release_date": "2026-10-12", - "last_updated": "2026-10-12", + "open_weights": true, + "knowledge": "2025-05", + "release_date": "2026-10-17", + "last_updated": "2026-10-17", "type": "chat" }, { - "id": "meta/muse-spark-1.3", - "name": "Muse Spark 1.3 (Meta)", - "display_name": "Muse Spark 1.3 (Meta)", + "id": "morph-glm52-744b", + "name": "GLM-5.2", + "display_name": "GLM-5.2", "modalities": { "input": [ - "text", - "image", - "video", - "pdf", - "audio" + "text" ], "output": [ "text" @@ -116298,30 +116335,42 @@ "extra_capabilities": { "reasoning": { "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ - "minimal", - "low", - "medium", + "none", "high", - "xhigh" + "max" ] } }, - "attachment": true, - "open_weights": false, - "release_date": "2026-09-02", - "last_updated": "2026-09-02", + "attachment": false, + "open_weights": true, + "release_date": "2026-06-13", + "last_updated": "2026-06-13", "type": "chat" - }, + } + ] + }, + "stepfun-ai": { + "id": "stepfun-ai", + "name": "StepFun (Global)", + "display_name": "StepFun (Global)", + "api": "https://api.stepfun.ai/v1", + "doc": "https://platform.stepfun.ai/docs/en/overview/concept", + "models": [ { - "id": "meta/muse-spark-1.1", - "name": "Muse Spark 1.1 (Meta)", - "display_name": "Muse Spark 1.1 (Meta)", + "id": "step-5-preview", + "name": "Step 5 Preview", + "display_name": "Step 5 Preview", "modalities": { "input": [ "text", "image", - "pdf", "video" ], "output": [ @@ -116329,8 +116378,8 @@ ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 1000000, + "output": 384000 }, "temperature": true, "tool_call": true, @@ -116341,31 +116390,33 @@ "extra_capabilities": { "reasoning": { "supported": true, + "mode": "effort", + "effort": "medium", "effort_options": [ - "minimal", "low", "medium", - "high", - "xhigh" + "high" + ], + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, "attachment": true, - "open_weights": false, - "release_date": "2026-04-08", - "last_updated": "2026-07-09", + "open_weights": true, + "release_date": "2026-09-16", + "last_updated": "2026-09-20", "type": "chat" }, { - "id": "meta/muse-spark-1.2", - "name": "Muse Spark 1.2 (Meta)", - "display_name": "Muse Spark 1.2 (Meta)", + "id": "stepaudio-2.5-asr", + "name": "StepAudio 2.5 ASR", + "display_name": "StepAudio 2.5 ASR", "modalities": { "input": [ - "text", - "image", - "video", - "pdf", "audio" ], "output": [ @@ -116373,37 +116424,23 @@ ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 8192, + "output": 8192 }, - "temperature": true, - "tool_call": true, + "temperature": false, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "effort_options": [ - "minimal", - "low", - "medium", - "high", - "xhigh" - ] - } + "supported": false }, "attachment": true, "open_weights": false, - "release_date": "2026-08-05", - "last_updated": "2026-08-05", - "type": "chat" + "release_date": "2026-04-24", + "last_updated": "2026-07-02" }, { - "id": "canopywave/deepseek-v4-flash", - "name": "DeepSeek V4 Flash (CanopyWave)", - "display_name": "DeepSeek V4 Flash (CanopyWave)", + "id": "step-3.5-flash-2603", + "name": "Step 3.5 Flash 2603", + "display_name": "Step 3.5 Flash 2603", "modalities": { "input": [ "text" @@ -116413,8 +116450,8 @@ ] }, "limit": { - "context": 1000000, - "output": 393216 + "context": 256000, + "output": 256000 }, "temperature": true, "tool_call": true, @@ -116425,51 +116462,42 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "high", - "effort_options": [ - "low", - "high", - "max" - ], "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ "thinking_blocks" ], - "notes": [ - "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." + "effort_options": [ + "low", + "high" ] } }, "attachment": false, "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-04-24", - "last_updated": "2026-04-24", + "knowledge": "2025-01", + "release_date": "2026-04-02", + "last_updated": "2026-04-02", "type": "chat" }, { - "id": "canopywave/kimi-k3", - "name": "Kimi K3 (CanopyWave)", - "display_name": "Kimi K3 (CanopyWave)", + "id": "step-3.5-flash", + "name": "Step 3.5 Flash", + "display_name": "Step 3.5 Flash", "modalities": { "input": [ - "text", - "image", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 1048576 + "context": 256000, + "output": 256000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -116478,78 +116506,81 @@ "extra_capabilities": { "reasoning": { "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ - "none", - "minimal", "low", - "medium", - "high", - "xhigh", - "max" + "high" ] } }, - "attachment": true, + "attachment": false, "open_weights": true, - "release_date": "2026-07-16", - "last_updated": "2026-07-16", + "knowledge": "2025-01", + "release_date": "2026-01-29", + "last_updated": "2026-02-13", "type": "chat" }, { - "id": "canopywave/deepseek-v4-pro", - "name": "DeepSeek V4 Pro (CanopyWave)", - "display_name": "DeepSeek V4 Pro (CanopyWave)", + "id": "step-tts-2", + "name": "Step TTS 2", + "display_name": "Step TTS 2", "modalities": { "input": [ "text" ], "output": [ - "text" + "audio" ] }, "limit": { - "context": 1000000, - "output": 393216 + "context": 8192, + "output": 8192 }, - "temperature": true, - "tool_call": true, + "temperature": false, + "tool_call": false, "reasoning": { - "supported": true, - "default": true + "supported": false }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "high", - "effort_options": [ - "low", - "high", - "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." - ] - } + "attachment": false, + "open_weights": false, + "release_date": "2026-03-01", + "last_updated": "2026-07-02" + }, + { + "id": "stepaudio-2.5-tts", + "name": "StepAudio 2.5 TTS", + "display_name": "StepAudio 2.5 TTS", + "modalities": { + "input": [ + "text" + ], + "output": [ + "audio" + ] + }, + "limit": { + "context": 8192, + "output": 8192 + }, + "temperature": false, + "tool_call": false, + "reasoning": { + "supported": false }, "attachment": false, - "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-04-24", - "last_updated": "2026-04-24", - "type": "chat" + "open_weights": false, + "release_date": "2026-04-16", + "last_updated": "2026-07-02" }, { - "id": "canopywave/kimi-k2.6", - "name": "Kimi K2.6 (CanopyWave)", - "display_name": "Kimi K2.6 (CanopyWave)", + "id": "step-3.7-flash", + "name": "Step 3.7 Flash", + "display_name": "Step 3.7 Flash", "modalities": { "input": [ "text", @@ -116561,8 +116592,8 @@ ] }, "limit": { - "context": 262144, - "output": 32768 + "context": 256000, + "output": 256000 }, "temperature": true, "tool_call": true, @@ -116580,40 +116611,46 @@ "thinking_blocks" ], "effort_options": [ - "none", - "minimal", "low", "medium", - "high", - "xhigh", - "max" + "high" ] } }, "attachment": true, "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-04-20", - "last_updated": "2026-04-20", + "knowledge": "2026-03-01", + "release_date": "2026-05-29", + "last_updated": "2026-05-29", "type": "chat" - }, + } + ] + }, + "sensenova": { + "id": "sensenova", + "name": "SenseNova (China)", + "display_name": "SenseNova (China)", + "api": "https://token.sensenova.cn/v1", + "doc": "https://platform.sensenova.cn/docs", + "models": [ { - "id": "canopywave/glm-5.2", - "name": "GLM-5.2 (CanopyWave)", - "display_name": "GLM-5.2 (CanopyWave)", + "id": "kimi-k3", + "name": "Kimi K3", + "display_name": "Kimi K3", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 32768 + "context": 1048576, + "output": 65536 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -116629,68 +116666,34 @@ "thinking_blocks" ], "effort_options": [ - "none", - "minimal", "low", - "medium", "high", - "xhigh", "max" ] } }, - "attachment": false, + "attachment": true, "open_weights": true, - "release_date": "2026-06-13", - "last_updated": "2026-06-13", - "type": "chat" - }, - { - "id": "perplexity/sonar", - "name": "Sonar (Perplexity)", - "display_name": "Sonar (Perplexity)", - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 130000, - "output": 4096 - }, - "temperature": true, - "tool_call": false, - "reasoning": { - "supported": false - }, - "attachment": false, - "open_weights": false, - "knowledge": "2025-09-01", - "release_date": "2024-01-01", - "last_updated": "2025-09-01", + "release_date": "2026-07-16", + "last_updated": "2026-07-16", "type": "chat" }, { - "id": "runware/glm-5.3-flash", - "name": "GLM-5.3 Flash (Runware)", - "display_name": "GLM-5.3 Flash (Runware)", + "id": "deepseek-v4.1-flash", + "name": "DeepSeek V4.1 Flash", + "display_name": "DeepSeek V4.1 Flash", "modalities": { "input": [ "text", - "image", - "video", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 1048576, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -116701,27 +116704,31 @@ "extra_capabilities": { "reasoning": { "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ "none", - "minimal", "low", - "medium", "high", - "xhigh", "max" ] } }, "attachment": true, "open_weights": true, - "release_date": "2026-08-26", - "last_updated": "2026-08-26", + "knowledge": "2025-05", + "release_date": "2026-09-10", + "last_updated": "2026-09-10", "type": "chat" }, { - "id": "runware/deepseek-v4-flash", - "name": "DeepSeek V4 Flash (Runware)", - "display_name": "DeepSeek V4 Flash (Runware)", + "id": "deepseek-v4-pro", + "name": "DeepSeek V4 Pro", + "display_name": "DeepSeek V4 Pro", "modalities": { "input": [ "text" @@ -116732,7 +116739,7 @@ }, "limit": { "context": 1048576, - "output": 384000 + "output": 65536 }, "temperature": true, "tool_call": true, @@ -116764,97 +116771,14 @@ }, "attachment": false, "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-04-24", - "last_updated": "2026-04-24", - "type": "chat" - }, - { - "id": "runware/deepseek-v4.1-flash-1017", - "name": "DeepSeek V4.1 Flash 1017 (runware)", - "display_name": "DeepSeek V4.1 Flash 1017 (runware)", - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 1048576, - "output": 1048576 - }, - "temperature": true, - "tool_call": true, - "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "none", - "low", - "high", - "xhigh", - "max" - ] - } - }, - "attachment": true, - "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-10-17", - "last_updated": "2026-10-17", - "type": "chat" - }, - { - "id": "runware/kimi-k3", - "name": "Kimi K3 (Runware)", - "display_name": "Kimi K3 (Runware)", - "modalities": { - "input": [ - "text", - "image", - "video" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 1048576, - "output": 1048576 - }, - "temperature": false, - "tool_call": true, - "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "effort_options": [ - "none", - "low", - "high", - "max" - ] - } - }, - "attachment": true, - "open_weights": true, - "release_date": "2026-07-16", - "last_updated": "2026-07-16", + "release_date": "2026-08-13", + "last_updated": "2026-08-22", "type": "chat" }, { - "id": "runware/deepseek-v4-pro", - "name": "DeepSeek V4 Pro (Runware)", - "display_name": "DeepSeek V4 Pro (Runware)", + "id": "deepseek-v4-flash", + "name": "DeepSeek V4 Flash", + "display_name": "DeepSeek V4 Flash", "modalities": { "input": [ "text" @@ -116864,8 +116788,8 @@ ] }, "limit": { - "context": 1048576, - "output": 384000 + "context": 1000000, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -116903,21 +116827,20 @@ "type": "chat" }, { - "id": "runware/gemma-4-31b-it", - "name": "Gemma 4 31B IT (Runware)", - "display_name": "Gemma 4 31B IT (Runware)", + "id": "glm-5.2", + "name": "GLM-5.2", + "display_name": "GLM-5.2", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 65536 + "context": 1000000, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -116928,25 +116851,28 @@ "extra_capabilities": { "reasoning": { "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ "none", - "high", - "xhigh", - "max" + "high" ] } }, - "attachment": true, + "attachment": false, "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-04-02", - "last_updated": "2026-04-02", + "release_date": "2026-06-13", + "last_updated": "2026-06-13", "type": "chat" }, { - "id": "runware/deepseek-v4.1-flash", - "name": "DeepSeek V4.1 Flash (Runware)", - "display_name": "DeepSeek V4.1 Flash (Runware)", + "id": "sensenova-6.8-flash-lite", + "name": "SenseNova 6.8 Flash Lite", + "display_name": "SenseNova 6.8 Flash Lite", "modalities": { "input": [ "text", @@ -116957,8 +116883,8 @@ ] }, "limit": { - "context": 1048576, - "output": 384000 + "context": 262144, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -116968,27 +116894,33 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, "effort_options": [ "none", "low", - "high", - "xhigh", - "max" + "medium", + "high" ] } }, "attachment": true, - "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-09-10", - "last_updated": "2026-09-10", + "open_weights": false, + "release_date": "2026-08-11", + "last_updated": "2026-08-28", "type": "chat" - }, + } + ] + }, + "inception": { + "id": "inception", + "name": "Inception", + "display_name": "Inception", + "api": "https://api.inceptionlabs.ai/v1/", + "doc": "https://docs.inceptionlabs.ai/get-started/models", + "models": [ { - "id": "runware/glm-5.3", - "name": "GLM-5.3 (Runware)", - "display_name": "GLM-5.3 (Runware)", + "id": "mercury-2", + "name": "Mercury 2", + "display_name": "Mercury 2", "modalities": { "input": [ "text" @@ -116998,8 +116930,8 @@ ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 128000, + "output": 50000 }, "temperature": true, "tool_call": true, @@ -117009,24 +116941,24 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, "effort_options": [ "low", - "high", - "max" + "medium", + "high" ] } }, "attachment": false, - "open_weights": true, - "release_date": "2026-08-14", - "last_updated": "2026-08-14", + "open_weights": false, + "knowledge": "2025-01-01", + "release_date": "2026-02-24", + "last_updated": "2026-02-24", "type": "chat" }, { - "id": "runware/glm-5.2", - "name": "GLM-5.2 (Runware)", - "display_name": "GLM-5.2 (Runware)", + "id": "mercury-3", + "name": "Mercury 3", + "display_name": "Mercury 3", "modalities": { "input": [ "text" @@ -117036,8 +116968,8 @@ ] }, "limit": { - "context": 1024000, - "output": 128000 + "context": 1000000, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -117047,86 +116979,61 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], "effort_options": [ - "none", - "minimal", "low", "medium", - "high", - "xhigh", - "max" + "high" ] } }, "attachment": false, - "open_weights": true, - "release_date": "2026-06-13", - "last_updated": "2026-06-13", + "open_weights": false, + "release_date": "2026-10-16", + "last_updated": "2026-10-20", "type": "chat" }, { - "id": "meta-contributor/muse-spark-1.3-contributor", - "name": "Muse Spark 1.3 Contributor (Meta Contributor)", - "display_name": "Muse Spark 1.3 Contributor (Meta Contributor)", + "id": "mercury-edit-2", + "name": "Mercury Edit 2", + "display_name": "Mercury Edit 2", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 1048576 + "context": 32000, + "output": 8192 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "minimal", - "low", - "medium", - "high", - "xhigh" - ] - } + "supported": false }, - "attachment": true, + "attachment": false, "open_weights": false, - "release_date": "2026-09-02", - "last_updated": "2026-09-02", + "release_date": "2026-03-30", + "last_updated": "2026-03-30", "type": "chat" }, { - "id": "meta-contributor/muse-spark-1.2-contributor", - "name": "Muse Spark 1.2 Contributor (Meta Contributor)", - "display_name": "Muse Spark 1.2 Contributor (Meta Contributor)", + "id": "mercury-2.5", + "name": "Mercury 2.5", + "display_name": "Mercury 2.5", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 260000, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -117137,254 +117044,313 @@ "extra_capabilities": { "reasoning": { "effort_options": [ - "minimal", "low", "medium", - "high", - "xhigh" + "high" ] } }, - "attachment": true, + "attachment": false, "open_weights": false, - "release_date": "2026-08-06", - "last_updated": "2026-08-06", + "knowledge": "2025-11-01", + "release_date": "2026-09-08", + "last_updated": "2026-09-08", "type": "chat" } ] }, - "siliconflow": { - "id": "siliconflow", - "name": "SiliconFlow", - "display_name": "SiliconFlow", - "api": "https://api.siliconflow.cn/v1", - "doc": "https://cloud.siliconflow.com/models", + "freemodel": { + "id": "freemodel", + "name": "FreeModel", + "display_name": "FreeModel", + "api": "https://cc.freemodel.dev/v1", + "doc": "https://freemodel.dev", "models": [ { - "id": "zai-org/GLM-4.5-Air", - "name": "zai-org/GLM-4.5-Air", - "display_name": "zai-org/GLM-4.5-Air", + "id": "gpt-5.4", + "name": "GPT-5.4", + "display_name": "GPT-5.4", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 131000, - "output": 98304 + "context": 1050000, + "output": 128000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": false }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-07-28", - "last_updated": "2025-07-28", + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": false, + "mode": "effort", + "effort": "none", + "effort_options": [ + "none", + "low", + "medium", + "high", + "xhigh" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" + } + }, + "attachment": true, + "open_weights": false, + "knowledge": "2025-08-31", + "release_date": "2026-03-05", + "last_updated": "2026-03-05", "type": "chat" }, { - "id": "zai-org/GLM-5.2", - "name": "GLM-5.2", - "display_name": "GLM-5.2", + "id": "gpt-5.4-mini", + "name": "GPT-5.4 mini", + "display_name": "GPT-5.4 mini", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 1049000, - "output": 262000 + "context": 400000, + "output": 128000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], + "default_enabled": false, + "mode": "effort", + "effort": "none", "effort_options": [ + "none", + "low", + "medium", "high", - "max" - ] + "xhigh" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-06-13", - "last_updated": "2026-06-13", + "attachment": true, + "open_weights": false, + "knowledge": "2025-08-31", + "release_date": "2026-03-17", + "last_updated": "2026-03-17", "type": "chat" }, { - "id": "Qwen/Qwen3-VL-30B-A3B-Instruct", - "name": "Qwen/Qwen3-VL-30B-A3B-Instruct", - "display_name": "Qwen/Qwen3-VL-30B-A3B-Instruct", + "id": "gpt-5.3-codex", + "name": "GPT-5.3 Codex", + "display_name": "GPT-5.3 Codex", "modalities": { "input": [ "text", "image", - "video" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 262000, - "output": 32768 + "context": 400000, + "output": 128000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false - }, - "attachment": true, - "open_weights": true, - "release_date": "2025-10-04", - "last_updated": "2025-10-04", - "type": "chat" - }, - { - "id": "Qwen/Qwen2.5-7B-Instruct", - "name": "Qwen/Qwen2.5-7B-Instruct", - "display_name": "Qwen/Qwen2.5-7B-Instruct", - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 33000, - "output": 4000 + "supported": true, + "default": true }, - "temperature": true, - "tool_call": true, - "reasoning": { - "supported": false + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", + "effort_options": [ + "low", + "medium", + "high", + "xhigh" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" + } }, - "attachment": false, - "open_weights": true, - "release_date": "2024-09-19", - "last_updated": "2024-09-19", + "attachment": true, + "open_weights": false, + "knowledge": "2025-08-31", + "release_date": "2026-02-05", + "last_updated": "2026-02-05", "type": "chat" }, { - "id": "Qwen/Qwen3-8B", - "name": "Qwen/Qwen3-8B", - "display_name": "Qwen/Qwen3-8B", + "id": "claude-haiku-4-5-20251001", + "name": "Claude Haiku 4.5", + "display_name": "Claude Haiku 4.5", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 131000, - "output": 32768 + "context": 200000, + "output": 64000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true + "supported": true, + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": false, + "mode": "budget", + "budget": { + "min": 1024, + "unit": "tokens" + }, "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ "thinking_blocks" + ], + "notes": [ + "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", + "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." ] } }, - "attachment": false, - "open_weights": true, - "release_date": "2025-04-29", - "last_updated": "2025-04-29", + "attachment": true, + "open_weights": false, + "knowledge": "2025-02-28", + "release_date": "2025-10-15", + "last_updated": "2025-10-15", "type": "chat" }, { - "id": "Qwen/Qwen3.6-35B-A3B", - "name": "Qwen/Qwen3.6-35B-A3B", - "display_name": "Qwen/Qwen3.6-35B-A3B", + "id": "claude-opus-4-7", + "name": "Claude Opus 4.7", + "display_name": "Claude Opus 4.7", "modalities": { "input": [ "text", "image", - "video" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 65536 + "context": 1000000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { - "supported": true + "supported": true, + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": false, + "mode": "effort", + "effort": "high", + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" + ], "interleaved": true, "summaries": true, - "visibility": "summary", + "visibility": "omitted", "continuation": [ "thinking_blocks" + ], + "notes": [ + "Claude Opus 4.7 and newer Opus models require thinking.type = \"adaptive\" to enable thinking explicitly.", + "Manual budget_tokens requests return 400 on Claude Opus 4.7 and newer adaptive-only Opus models.", + "task_budget is separate from thinking control and should not be treated as a thinking budget." ] } }, "attachment": true, - "open_weights": true, - "knowledge": "2025-04", - "release_date": "2026-04-17", - "last_updated": "2026-04-17", + "open_weights": false, + "knowledge": "2026-01-31", + "release_date": "2026-04-16", + "last_updated": "2026-04-16", "type": "chat" }, { - "id": "Qwen/Qwen3-VL-32B-Thinking", - "name": "Qwen/Qwen3-VL-32B-Thinking", - "display_name": "Qwen/Qwen3-VL-32B-Thinking", + "id": "claude-fable-5", + "name": "Claude Fable 5", + "display_name": "Claude Fable 5", "modalities": { "input": [ "text", "image", - "video" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 262000, - "output": 32768 + "context": 1000000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -117393,271 +117359,275 @@ "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "high", + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" + ], "interleaved": true, "summaries": true, - "visibility": "summary", + "visibility": "omitted", "continuation": [ "thinking_blocks" + ], + "notes": [ + "Adaptive thinking is always on for Claude Fable 5 and Claude Mythos 5; thinking.type = \"disabled\" is rejected.", + "Manual budget_tokens requests return 400 on Claude Fable 5 and Claude Mythos 5.", + "thinking.display defaults to omitted; set display to summarized to receive readable thinking summaries." ] } }, "attachment": true, - "open_weights": true, - "release_date": "2025-10-21", - "last_updated": "2025-11-25", + "open_weights": false, + "knowledge": "2026-01-31", + "release_date": "2026-06-09", + "last_updated": "2026-06-09", "type": "chat" }, { - "id": "Qwen/Qwen3-Coder-30B-A3B-Instruct", - "name": "Qwen/Qwen3-Coder-30B-A3B-Instruct", - "display_name": "Qwen/Qwen3-Coder-30B-A3B-Instruct", - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 262000, - "output": 65536 - }, - "temperature": true, - "tool_call": true, - "reasoning": { - "supported": false - }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-07-31", - "last_updated": "2025-07-31" - }, - { - "id": "Qwen/Qwen3-VL-30B-A3B-Thinking", - "name": "Qwen/Qwen3-VL-30B-A3B-Thinking", - "display_name": "Qwen/Qwen3-VL-30B-A3B-Thinking", + "id": "claude-sonnet-4-6", + "name": "Claude Sonnet 4.6", + "display_name": "Claude Sonnet 4.6", "modalities": { "input": [ "text", "image", - "video" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 262000, - "output": 32768 + "context": 1000000, + "output": 64000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": false, + "mode": "mixed", + "budget": { + "min": 1024, + "unit": "tokens" + }, + "effort": "high", + "effort_options": [ + "low", + "medium", + "high", + "max" + ], "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ "thinking_blocks" + ], + "notes": [ + "Anthropic recommends adaptive thinking with effort for Claude 4.6; budget_tokens remains a deprecated compatibility path.", + "Anthropic API defaults effort to high; lower effort levels should be chosen per workload." ] } }, "attachment": true, - "open_weights": true, - "release_date": "2025-10-11", - "last_updated": "2025-11-25", + "open_weights": false, + "knowledge": "2025-08-31", + "release_date": "2026-02-17", + "last_updated": "2026-03-13", "type": "chat" }, { - "id": "Qwen/Qwen3.5-27B", - "name": "Qwen/Qwen3.5-27B", - "display_name": "Qwen/Qwen3.5-27B", + "id": "claude-opus-4-8", + "name": "Claude Opus 4.8", + "display_name": "Claude Opus 4.8", "modalities": { "input": [ "text", "image", - "video" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 65536 + "context": 1000000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": false, + "mode": "effort", + "effort": "high", + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" + ], "interleaved": true, "summaries": true, - "visibility": "summary", + "visibility": "omitted", "continuation": [ "thinking_blocks" + ], + "notes": [ + "Claude Opus 4.7 and newer Opus models require thinking.type = \"adaptive\" to enable thinking explicitly.", + "Manual budget_tokens requests return 400 on Claude Opus 4.7 and newer adaptive-only Opus models.", + "task_budget is separate from thinking control and should not be treated as a thinking budget." ] } }, "attachment": true, - "open_weights": true, - "knowledge": "2025-04", - "release_date": "2026-02-23", - "last_updated": "2026-02-23", - "type": "chat" - }, - { - "id": "Qwen/Qwen3-30B-A3B-Instruct-2507", - "name": "Qwen/Qwen3-30B-A3B-Instruct-2507", - "display_name": "Qwen/Qwen3-30B-A3B-Instruct-2507", - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 262000, - "output": 32768 - }, - "temperature": true, - "tool_call": true, - "reasoning": { - "supported": false - }, - "attachment": false, - "open_weights": true, - "release_date": "2025-07-28", - "last_updated": "2025-07-28", + "open_weights": false, + "knowledge": "2026-01", + "release_date": "2026-05-28", + "last_updated": "2026-05-28", "type": "chat" }, { - "id": "Qwen/Qwen3-VL-32B-Instruct", - "name": "Qwen/Qwen3-VL-32B-Instruct", - "display_name": "Qwen/Qwen3-VL-32B-Instruct", + "id": "gpt-5.5", + "name": "GPT-5.5", + "display_name": "GPT-5.5", "modalities": { "input": [ "text", "image", - "video" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 262000, - "output": 32768 - }, - "temperature": true, - "tool_call": true, - "reasoning": { - "supported": false - }, - "attachment": true, - "open_weights": true, - "release_date": "2025-10-21", - "last_updated": "2025-11-25", - "type": "chat" - }, - { - "id": "Qwen/Qwen3-32B", - "name": "Qwen/Qwen3-32B", - "display_name": "Qwen/Qwen3-32B", - "modalities": { - "input": [ - "text" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 131000, - "output": 32768 + "context": 1050000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { - "supported": true + "supported": true, + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] + "default_enabled": true, + "mode": "effort", + "effort": "medium", + "effort_options": [ + "low", + "medium", + "high", + "xhigh" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-04-28", - "last_updated": "2025-04-28", + "attachment": true, + "open_weights": false, + "knowledge": "2025-12-01", + "release_date": "2026-04-23", + "last_updated": "2026-04-23", "type": "chat" }, { - "id": "Qwen/Qwen3.5-122B-A10B", - "name": "Qwen/Qwen3.5-122B-A10B", - "display_name": "Qwen/Qwen3.5-122B-A10B", + "id": "claude-opus-4-6", + "name": "Claude Opus 4.6", + "display_name": "Claude Opus 4.6", "modalities": { "input": [ "text", "image", - "video" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 65536 + "context": 1000000, + "output": 128000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": false, + "mode": "mixed", + "budget": { + "min": 1024, + "unit": "tokens" + }, + "effort": "high", + "effort_options": [ + "low", + "medium", + "high", + "max" + ], "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ "thinking_blocks" + ], + "notes": [ + "Anthropic recommends adaptive thinking with effort for Claude 4.6; budget_tokens remains a deprecated compatibility path.", + "Anthropic API defaults effort to high; lower effort levels should be chosen per workload." ] } }, "attachment": true, - "open_weights": true, - "knowledge": "2025-04", - "release_date": "2026-02-23", - "last_updated": "2026-02-23", + "open_weights": false, + "knowledge": "2025-05-31", + "release_date": "2026-02-05", + "last_updated": "2026-03-13", "type": "chat" - }, + } + ] + }, + "cloudflare-ai-gateway": { + "id": "cloudflare-ai-gateway", + "name": "Cloudflare AI Gateway", + "display_name": "Cloudflare AI Gateway", + "doc": "https://developers.cloudflare.com/ai-gateway/", + "models": [ { - "id": "Qwen/Qwen2.5-72B-Instruct", - "name": "Qwen/Qwen2.5-72B-Instruct", - "display_name": "Qwen/Qwen2.5-72B-Instruct", + "id": "openai/o3-mini", + "name": "o3-mini", + "display_name": "o3-mini", "modalities": { "input": [ "text" @@ -117667,190 +117637,255 @@ ] }, "limit": { - "context": 33000, - "output": 4000 + "context": 200000, + "output": 100000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", + "effort_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" + } }, "attachment": false, - "open_weights": true, - "release_date": "2024-09-18", - "last_updated": "2025-11-25", + "open_weights": false, + "knowledge": "2024-05", + "release_date": "2024-12-20", + "last_updated": "2025-01-29", "type": "chat" }, { - "id": "Qwen/Qwen3.5-4B", - "name": "Qwen/Qwen3.5-4B", - "display_name": "Qwen/Qwen3.5-4B", + "id": "openai/gpt-5.4-nano", + "name": "GPT-5.4 nano", + "display_name": "GPT-5.4 nano", "modalities": { "input": [ "text", - "image", - "video" + "image" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 65536 + "context": 128000, + "output": 128000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] + "default_enabled": false, + "mode": "effort", + "effort": "none", + "effort_options": [ + "none", + "low", + "medium", + "high", + "xhigh" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, "attachment": true, - "open_weights": true, - "knowledge": "2025-04", - "release_date": "2026-03-02", - "last_updated": "2026-03-02", + "open_weights": false, + "knowledge": "2025-08-31", + "release_date": "2026-03-17", + "last_updated": "2026-03-17", "type": "chat" }, { - "id": "Qwen/Qwen3-VL-8B-Instruct", - "name": "Qwen/Qwen3-VL-8B-Instruct", - "display_name": "Qwen/Qwen3-VL-8B-Instruct", + "id": "openai/gpt-6.1-terra", + "name": "GPT-6.1 Terra", + "display_name": "GPT-6.1 Terra", "modalities": { "input": [ "text", "image", - "video" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 262000, - "output": 32768 + "context": 1050000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "none", + "low", + "medium", + "high", + "xhigh", + "max" + ] + } }, "attachment": true, - "open_weights": true, - "release_date": "2025-10-15", - "last_updated": "2025-11-25", + "open_weights": false, + "knowledge": "2026-06-01", + "release_date": "2026-10-26", + "last_updated": "2026-10-26", "type": "chat" }, { - "id": "Qwen/Qwen3.5-35B-A3B", - "name": "Qwen/Qwen3.5-35B-A3B", - "display_name": "Qwen/Qwen3.5-35B-A3B", + "id": "openai/gpt-5.4", + "name": "GPT-5.4", + "display_name": "GPT-5.4", "modalities": { "input": [ "text", "image", - "video" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 65536 + "context": 1000000, + "output": 128000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] + "default_enabled": false, + "mode": "effort", + "effort": "none", + "effort_options": [ + "none", + "low", + "medium", + "high", + "xhigh" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, "attachment": true, - "open_weights": true, - "knowledge": "2025-04", - "release_date": "2026-02-23", - "last_updated": "2026-02-23", + "open_weights": false, + "knowledge": "2025-08-31", + "release_date": "2026-03-05", + "last_updated": "2026-03-05", "type": "chat" }, { - "id": "Qwen/Qwen3-14B", - "name": "Qwen/Qwen3-14B", - "display_name": "Qwen/Qwen3-14B", + "id": "openai/gpt-5.4-mini", + "name": "GPT-5.4 mini", + "display_name": "GPT-5.4 mini", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 131000, - "output": 32768 + "context": 128000, + "output": 128000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true + "supported": true, + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] + "default_enabled": false, + "mode": "effort", + "effort": "none", + "effort_options": [ + "none", + "low", + "medium", + "high", + "xhigh" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, - "attachment": false, - "open_weights": true, - "release_date": "2025-04-30", - "last_updated": "2025-11-25", + "attachment": true, + "open_weights": false, + "knowledge": "2025-08-31", + "release_date": "2026-03-17", + "last_updated": "2026-03-17", "type": "chat" }, { - "id": "Qwen/Qwen3.5-9B", - "name": "Qwen/Qwen3.5-9B", - "display_name": "Qwen/Qwen3.5-9B", + "id": "openai/gpt-6-sol", + "name": "GPT-6 Sol", + "display_name": "GPT-6 Sol", "modalities": { "input": [ "text", "image", - "video" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 65536 + "context": 1050000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -117858,67 +117893,42 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "none", + "low", + "medium", + "high", + "xhigh", + "max" ] } }, "attachment": true, - "open_weights": true, - "knowledge": "2025-04", - "release_date": "2026-02-23", - "last_updated": "2026-02-23", + "open_weights": false, + "knowledge": "2026-04-20", + "release_date": "2026-09-22", + "last_updated": "2026-09-22", "type": "chat" }, { - "id": "PaddlePaddle/PaddleOCR-VL-1.5", - "name": "PaddlePaddle/PaddleOCR-VL-1.5", - "display_name": "PaddlePaddle/PaddleOCR-VL-1.5", + "id": "openai/gpt-5.6-terra", + "name": "GPT-5.6 Terra", + "display_name": "GPT-5.6 Terra", "modalities": { "input": [ "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 16384, - "output": 6226 - }, - "temperature": true, - "tool_call": false, - "reasoning": { - "supported": false - }, - "attachment": true, - "open_weights": true, - "release_date": "2026-01-29", - "last_updated": "2026-01-29", - "type": "chat" - }, - { - "id": "Pro/zai-org/GLM-5.1", - "name": "Pro/zai-org/GLM-5.1", - "display_name": "Pro/zai-org/GLM-5.1", - "modalities": { - "input": [ - "text" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 205000, - "output": 131072 + "context": 1050000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -117927,37 +117937,52 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] + "default_enabled": true, + "mode": "effort", + "effort": "medium", + "effort_options": [ + "none", + "low", + "medium", + "high", + "xhigh", + "max" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-04-07", - "last_updated": "2026-04-07", + "attachment": true, + "open_weights": false, + "knowledge": "2026-02-16", + "release_date": "2026-07-09", + "last_updated": "2026-07-09", "type": "chat" }, { - "id": "Pro/zai-org/GLM-5", - "name": "Pro/zai-org/GLM-5", - "display_name": "Pro/zai-org/GLM-5", + "id": "openai/gpt-6-astra", + "name": "GPT-6 Astra", + "display_name": "GPT-6 Astra", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 205000, - "output": 131072 + "context": 1050000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -117966,24 +117991,24 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "low", + "medium", + "high" ] } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-02-12", - "last_updated": "2026-02-12", + "attachment": true, + "open_weights": false, + "knowledge": "2026-04-30", + "release_date": "2026-09-04", + "last_updated": "2026-09-04", "type": "chat" }, { - "id": "Pro/moonshotai/Kimi-K2.5", - "name": "Pro/moonshotai/Kimi-K2.5", - "display_name": "Pro/moonshotai/Kimi-K2.5", + "id": "openai/gpt-5.4-pro", + "name": "GPT-5.4 Pro", + "display_name": "GPT-5.4 Pro", "modalities": { "input": [ "text", @@ -117994,10 +118019,10 @@ ] }, "limit": { - "context": 262000, - "output": 99560 + "context": 1000000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -118006,39 +118031,49 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] + "default_enabled": true, + "mode": "effort", + "effort": "high", + "effort_options": [ + "medium", + "high", + "xhigh" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, "attachment": true, - "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-01-27", - "last_updated": "2026-01-27", + "open_weights": false, + "knowledge": "2025-08-31", + "release_date": "2026-03-05", + "last_updated": "2026-03-05", "type": "chat" }, { - "id": "Pro/moonshotai/Kimi-K2.6", - "name": "Pro/moonshotai/Kimi-K2.6", - "display_name": "Pro/moonshotai/Kimi-K2.6", + "id": "openai/gpt-6.1-sol", + "name": "GPT-6.1 Sol", + "display_name": "GPT-6.1 Sol", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 262000, - "output": 99560 + "context": 1050000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -118046,67 +118081,70 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "none", + "low", + "medium", + "high", + "xhigh", + "max" ] } }, "attachment": true, - "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-04-20", - "last_updated": "2026-04-20", + "open_weights": false, + "knowledge": "2026-04-30", + "release_date": "2026-09-29", + "last_updated": "2026-09-29", "type": "chat" }, { - "id": "Pro/deepseek-ai/DeepSeek-V3.1-Terminus", - "name": "Pro/deepseek-ai/DeepSeek-V3.1-Terminus", - "display_name": "Pro/deepseek-ai/DeepSeek-V3.1-Terminus", + "id": "openai/gpt-4.1-nano", + "name": "GPT-4.1 nano", + "display_name": "GPT-4.1 nano", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 164000, - "output": 8192 + "context": 1000000, + "output": 32768 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true + "supported": false }, - "attachment": false, - "open_weights": true, - "release_date": "2025-09-29", - "last_updated": "2025-11-25", + "attachment": true, + "open_weights": false, + "knowledge": "2024-04", + "release_date": "2025-04-14", + "last_updated": "2025-04-14", "type": "chat" }, { - "id": "Pro/deepseek-ai/DeepSeek-R1", - "name": "Pro/deepseek-ai/DeepSeek-R1", - "display_name": "Pro/deepseek-ai/DeepSeek-R1", + "id": "openai/gpt-5-nano", + "name": "GPT-5 Nano", + "display_name": "GPT-5 Nano", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 164000, - "output": 8192 + "context": 128000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -118115,37 +118153,50 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] + "default_enabled": true, + "mode": "effort", + "effort": "medium", + "effort_options": [ + "minimal", + "low", + "medium", + "high" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, - "attachment": false, - "open_weights": true, - "release_date": "2025-05-28", - "last_updated": "2025-05-28", + "attachment": true, + "open_weights": false, + "knowledge": "2024-05-30", + "release_date": "2025-08-07", + "last_updated": "2025-08-07", "type": "chat" }, { - "id": "Pro/deepseek-ai/DeepSeek-V3.2", - "name": "Pro/deepseek-ai/DeepSeek-V3.2", - "display_name": "Pro/deepseek-ai/DeepSeek-V3.2", + "id": "openai/gpt-5.5-pro", + "name": "GPT-5.5 Pro", + "display_name": "GPT-5.5 Pro", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 164000, - "output": 65536 + "context": 1000000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -118153,190 +118204,225 @@ }, "extra_capabilities": { "reasoning": { - "supported": true + "supported": true, + "effort_options": [ + "medium", + "high", + "xhigh" + ] } }, - "attachment": false, - "open_weights": true, - "knowledge": "2024-07", - "release_date": "2025-12-01", - "last_updated": "2025-12-01", - "type": "chat" - }, - { - "id": "Pro/deepseek-ai/DeepSeek-V3", - "name": "Pro/deepseek-ai/DeepSeek-V3", - "display_name": "Pro/deepseek-ai/DeepSeek-V3", - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 164000, - "output": 8192 - }, - "temperature": true, - "tool_call": true, - "reasoning": { - "supported": false - }, - "attachment": false, - "open_weights": true, - "release_date": "2025-03-24", - "last_updated": "2025-03-24", + "attachment": true, + "open_weights": false, + "knowledge": "2025-12-01", + "release_date": "2026-04-23", + "last_updated": "2026-04-23", "type": "chat" }, { - "id": "ByteDance-Seed/Seed-OSS-36B-Instruct", - "name": "ByteDance-Seed/Seed-OSS-36B-Instruct", - "display_name": "ByteDance-Seed/Seed-OSS-36B-Instruct", + "id": "openai/gpt-5.6-luna", + "name": "GPT-5.6 Luna", + "display_name": "GPT-5.6 Luna", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 262000, - "output": 99560 + "context": 1050000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { - "supported": true + "supported": true, + "default": true }, "extra_capabilities": { "reasoning": { - "supported": true + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", + "effort_options": [ + "none", + "low", + "medium", + "high", + "xhigh", + "max" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, - "attachment": false, - "open_weights": true, - "release_date": "2025-09-04", - "last_updated": "2025-11-25", + "attachment": true, + "open_weights": false, + "knowledge": "2026-02-16", + "release_date": "2026-07-09", + "last_updated": "2026-07-09", "type": "chat" }, { - "id": "tencent/Hunyuan-A13B-Instruct", - "name": "tencent/Hunyuan-A13B-Instruct", - "display_name": "tencent/Hunyuan-A13B-Instruct", + "id": "openai/o4-mini", + "name": "o4-mini", + "display_name": "o4-mini", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 131000, - "output": 117964 + "context": 200000, + "output": 100000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { - "supported": false - }, - "attachment": false, - "open_weights": true, - "release_date": "2025-06-30", - "last_updated": "2025-11-25", - "type": "chat" - }, - { - "id": "inclusionAI/Ling-flash-2.0", - "name": "inclusionAI/Ling-flash-2.0", - "display_name": "inclusionAI/Ling-flash-2.0", - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131000, - "output": 49780 + "supported": true, + "default": true }, - "temperature": true, - "tool_call": true, - "reasoning": { - "supported": false + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", + "effort_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" + } }, - "attachment": false, - "open_weights": true, - "release_date": "2025-09-18", - "last_updated": "2025-11-25", + "attachment": true, + "open_weights": false, + "knowledge": "2024-05", + "release_date": "2025-04-16", + "last_updated": "2025-04-16", "type": "chat" }, { - "id": "stepfun-ai/Step-3.5-Flash", - "name": "stepfun-ai/Step-3.5-Flash", - "display_name": "stepfun-ai/Step-3.5-Flash", + "id": "openai/gpt-5", + "name": "GPT-5", + "display_name": "GPT-5", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 262000, - "output": 256000 + "context": 128000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, "default": true }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-01-29", - "last_updated": "2026-02-13", + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", + "effort_options": [ + "minimal", + "low", + "medium", + "high" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" + } + }, + "attachment": true, + "open_weights": false, + "knowledge": "2024-09-30", + "release_date": "2025-08-07", + "last_updated": "2025-08-07", "type": "chat" }, { - "id": "deepseek-ai/DeepSeek-V3.1-Terminus", - "name": "deepseek-ai/DeepSeek-V3.1-Terminus", - "display_name": "deepseek-ai/DeepSeek-V3.1-Terminus", + "id": "openai/gpt-5-mini", + "name": "GPT-5 Mini", + "display_name": "GPT-5 Mini", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 164000, - "output": 8192 + "context": 128000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, "default": true }, - "attachment": false, - "open_weights": true, - "release_date": "2025-09-29", - "last_updated": "2025-11-25", + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", + "effort_options": [ + "minimal", + "low", + "medium", + "high" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" + } + }, + "attachment": true, + "open_weights": false, + "knowledge": "2024-05-30", + "release_date": "2025-08-07", + "last_updated": "2025-08-07", "type": "chat" }, { - "id": "deepseek-ai/DeepSeek-OCR", - "name": "deepseek-ai/DeepSeek-OCR", - "display_name": "deepseek-ai/DeepSeek-OCR", + "id": "openai/gpt-4o-mini", + "name": "GPT-4o mini", + "display_name": "GPT-4o mini", "modalities": { "input": [ "text", @@ -118348,75 +118434,70 @@ ] }, "limit": { - "context": 8192, - "output": 3113 + "context": 128000, + "output": 16384 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { "supported": false }, "attachment": true, - "open_weights": true, - "release_date": "2025-10-20", - "last_updated": "2025-10-20" + "open_weights": false, + "knowledge": "2023-09", + "release_date": "2024-07-18", + "last_updated": "2024-07-18", + "type": "chat" }, { - "id": "deepseek-ai/DeepSeek-R1", - "name": "deepseek-ai/DeepSeek-R1", - "display_name": "deepseek-ai/DeepSeek-R1", + "id": "openai/gpt-4o", + "name": "GPT-4o", + "display_name": "GPT-4o", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 164000, - "output": 8192 + "context": 128000, + "output": 16384 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } + "supported": false }, - "attachment": false, - "open_weights": true, - "release_date": "2025-05-28", - "last_updated": "2025-05-28", + "attachment": true, + "open_weights": false, + "knowledge": "2023-09", + "release_date": "2024-05-13", + "last_updated": "2024-08-06", "type": "chat" }, { - "id": "deepseek-ai/DeepSeek-V4-Pro", - "name": "deepseek-ai/DeepSeek-V4-Pro", - "display_name": "deepseek-ai/DeepSeek-V4-Pro", + "id": "openai/gpt-5.6-sol", + "name": "GPT-5.6 Sol", + "display_name": "GPT-5.6 Sol", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1049000, - "output": 393000 + "context": 1050000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -118427,71 +118508,91 @@ "supported": true, "default_enabled": true, "mode": "effort", - "effort": "high", + "effort": "medium", "effort_options": [ + "none", "low", + "medium", "high", + "xhigh", "max" ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" ], - "notes": [ - "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." - ] + "visibility": "hidden" } }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-04-24", - "last_updated": "2026-04-24", + "attachment": true, + "open_weights": false, + "knowledge": "2026-02-16", + "release_date": "2026-07-09", + "last_updated": "2026-07-09", "type": "chat" }, { - "id": "deepseek-ai/DeepSeek-V3.2", - "name": "deepseek-ai/DeepSeek-V3.2", - "display_name": "deepseek-ai/DeepSeek-V3.2", + "id": "openai/gpt-5.1", + "name": "GPT-5.1", + "display_name": "GPT-5.1", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 164000, - "output": 65536 + "context": 128000, + "output": 128000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { - "supported": true + "supported": true, + "default_enabled": false, + "mode": "effort", + "effort": "none", + "effort_options": [ + "none", + "low", + "medium", + "high" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, - "attachment": false, - "open_weights": true, - "knowledge": "2024-07", - "release_date": "2025-12-01", - "last_updated": "2025-12-01", + "attachment": true, + "open_weights": false, + "knowledge": "2024-09-30", + "release_date": "2025-11-13", + "last_updated": "2025-11-13", "type": "chat" }, { - "id": "deepseek-ai/DeepSeek-V4-Flash", - "name": "DeepSeek V4 Flash", - "display_name": "DeepSeek V4 Flash", + "id": "openai/gpt-5.5", + "name": "GPT-5.5", + "display_name": "GPT-5.5", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" @@ -118499,9 +118600,9 @@ }, "limit": { "context": 1000000, - "output": 384000 + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -118512,117 +118613,151 @@ "supported": true, "default_enabled": true, "mode": "effort", - "effort": "high", + "effort": "medium", "effort_options": [ "low", + "medium", "high", - "max" + "xhigh" ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" ], - "notes": [ - "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." - ] + "visibility": "hidden" } }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-04-24", - "last_updated": "2026-04-24", + "attachment": true, + "open_weights": false, + "knowledge": "2025-12-01", + "release_date": "2026-04-23", + "last_updated": "2026-04-23", "type": "chat" }, { - "id": "deepseek-ai/DeepSeek-V3", - "name": "deepseek-ai/DeepSeek-V3", - "display_name": "deepseek-ai/DeepSeek-V3", + "id": "openai/gpt-4.1-mini", + "name": "GPT-4.1 mini", + "display_name": "GPT-4.1 mini", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 164000, - "output": 8192 + "context": 1047576, + "output": 32768 }, "temperature": true, "tool_call": true, "reasoning": { "supported": false }, - "attachment": false, - "open_weights": true, - "release_date": "2025-03-24", - "last_updated": "2025-03-24", + "attachment": true, + "open_weights": false, + "knowledge": "2024-04", + "release_date": "2025-04-14", + "last_updated": "2025-04-14", "type": "chat" }, { - "id": "deepseek-ai/DeepSeek-V3.2-Exp", - "name": "deepseek-ai/DeepSeek-V3.2-Exp", - "display_name": "deepseek-ai/DeepSeek-V3.2-Exp", + "id": "openai/gpt-4.1", + "name": "GPT-4.1", + "display_name": "GPT-4.1", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 8192 + "context": 1047576, + "output": 32768 }, + "temperature": true, "tool_call": true, "reasoning": { "supported": false }, + "attachment": true, + "open_weights": false, + "knowledge": "2024-04", + "release_date": "2025-04-14", + "last_updated": "2025-04-14", "type": "chat" }, { - "id": "Pro/deepseek-ai/DeepSeek-V3.2-Exp", - "name": "Pro/deepseek-ai/DeepSeek-V3.2-Exp", - "display_name": "Pro/deepseek-ai/DeepSeek-V3.2-Exp", + "id": "openai/gpt-6-luna", + "name": "GPT-6 Luna", + "display_name": "GPT-6 Luna", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 8192 + "context": 1050000, + "output": 128000 }, + "temperature": false, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "none", + "low", + "medium", + "high", + "xhigh", + "max" + ] + } }, + "attachment": true, + "open_weights": false, + "knowledge": "2026-05-18", + "release_date": "2026-09-22", + "last_updated": "2026-09-22", "type": "chat" }, { - "id": "inclusionAI/Ring-1T", - "name": "inclusionAI/Ring-1T", - "display_name": "inclusionAI/Ring-1T", + "id": "openai/o3", + "name": "o3", + "display_name": "o3", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 8192 + "context": 200000, + "output": 100000 }, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -118630,74 +118765,84 @@ }, "extra_capabilities": { "reasoning": { - "supported": true + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", + "effort_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, + "attachment": true, + "open_weights": false, + "knowledge": "2024-05", + "release_date": "2025-04-16", + "last_updated": "2025-04-16", "type": "chat" }, { - "id": "inclusionAI/Ling-1T", - "name": "inclusionAI/Ling-1T", - "display_name": "inclusionAI/Ling-1T", + "id": "xai/grok-4.7", + "name": "Grok 4.7", + "display_name": "Grok 4.7", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 8192 + "context": 500000, + "output": 500000 }, + "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "low", + "medium", + "high", + "xhigh" + ] + } }, + "attachment": true, + "open_weights": false, + "knowledge": "2026-05", + "release_date": "2026-09-21", + "last_updated": "2026-09-21", "type": "chat" }, { - "id": "zai-org/GLM-4.6", - "name": "zai-org/GLM-4.6", - "display_name": "zai-org/GLM-4.6", + "id": "xai/grok-4.5", + "name": "Grok 4.5", + "display_name": "Grok 4.5", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 8192 - }, - "tool_call": true, - "reasoning": { - "supported": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true - } - }, - "type": "chat" - }, - { - "id": "Qwen/Qwen3-VL-235B-A22B-Instruct", - "name": "Qwen/Qwen3-VL-235B-A22B-Instruct", - "display_name": "Qwen/Qwen3-VL-235B-A22B-Instruct", - "tool_call": true, - "reasoning": { - "supported": false + "context": 500000, + "output": 500000 }, - "type": "chat" - }, - { - "id": "Qwen/Qwen3-VL-235B-A22B-Thinking", - "name": "Qwen/Qwen3-VL-235B-A22B-Thinking", - "display_name": "Qwen/Qwen3-VL-235B-A22B-Thinking", + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -118706,54 +118851,68 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "low", + "medium", + "high" ] } }, + "attachment": true, + "open_weights": false, + "release_date": "2026-07-08", + "last_updated": "2026-07-08", "type": "chat" }, { - "id": "Qwen/Qwen3-Omni-30B-A3B-Instruct", - "name": "Qwen/Qwen3-Omni-30B-A3B-Instruct", - "display_name": "Qwen/Qwen3-Omni-30B-A3B-Instruct", + "id": "xai/grok-4.20-0309-reasoning", + "name": "Grok 4.20 (Reasoning)", + "display_name": "Grok 4.20 (Reasoning)", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 8192 + "context": 2000000, + "output": 30000 }, + "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, + "attachment": true, + "open_weights": false, + "release_date": "2026-03-09", + "last_updated": "2026-03-09", "type": "chat" }, { - "id": "Qwen/Qwen3-Omni-30B-A3B-Thinking", - "name": "Qwen/Qwen3-Omni-30B-A3B-Thinking", - "display_name": "Qwen/Qwen3-Omni-30B-A3B-Thinking", + "id": "xai/grok-4.3", + "name": "Grok 4.3", + "display_name": "Grok 4.3", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 8192 + "context": 1000000, + "output": 30000 }, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -118762,108 +118921,175 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "none", + "low", + "medium", + "high" ] } }, + "attachment": true, + "open_weights": false, + "release_date": "2026-04-17", + "last_updated": "2026-04-17", "type": "chat" }, { - "id": "Qwen/Qwen3-Omni-30B-A3B-Captioner", - "name": "Qwen/Qwen3-Omni-30B-A3B-Captioner", - "display_name": "Qwen/Qwen3-Omni-30B-A3B-Captioner", + "id": "xai/grok-4.20-0309-non-reasoning", + "name": "Grok 4.20 (Non-Reasoning)", + "display_name": "Grok 4.20 (Non-Reasoning)", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 8192 + "context": 2000000, + "output": 30000 }, + "temperature": true, "tool_call": true, "reasoning": { "supported": false }, + "attachment": true, + "open_weights": false, + "release_date": "2026-03-09", + "last_updated": "2026-03-09", "type": "chat" }, { - "id": "moonshotai/Kimi-K2-Instruct-0905", - "name": "moonshotai/Kimi-K2-Instruct-0905", - "display_name": "moonshotai/Kimi-K2-Instruct-0905", + "id": "xai/grok-4.6", + "name": "Grok 4.6", + "display_name": "Grok 4.6", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 8192 + "context": 500000, + "output": 500000 }, + "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "effort_options": [ + "low", + "medium", + "high" + ] + } }, + "attachment": true, + "open_weights": false, + "knowledge": "2026-02-01", + "release_date": "2026-08-12", + "last_updated": "2026-08-12", "type": "chat" }, { - "id": "Pro/moonshotai/Kimi-K2-Instruct-0905", - "name": "Pro/moonshotai/Kimi-K2-Instruct-0905", - "display_name": "Pro/moonshotai/Kimi-K2-Instruct-0905", + "id": "minimax/m3.1-flash", + "name": "MiniMax-M3.1-Flash", + "display_name": "MiniMax-M3.1-Flash", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 8192 + "context": 1000000, + "output": 512000 }, + "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" + ] + } }, + "attachment": true, + "open_weights": false, + "release_date": "2026-10-19", + "last_updated": "2026-10-19", "type": "chat" }, { - "id": "Qwen/Qwen3-Next-80B-A3B-Instruct", - "name": "Qwen/Qwen3-Next-80B-A3B-Instruct", - "display_name": "Qwen/Qwen3-Next-80B-A3B-Instruct", + "id": "moonshotai/kimi-k3", + "name": "Kimi K3", + "display_name": "Kimi K3", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 8192 + "context": 1048576, + "output": 131072 }, + "temperature": false, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "effort_options": [ + "none", + "low", + "high", + "max" + ] + } }, + "attachment": true, + "open_weights": true, + "release_date": "2026-07-16", + "last_updated": "2026-07-16", "type": "chat" }, { - "id": "Qwen/Qwen3-Next-80B-A3B-Thinking", - "name": "Qwen/Qwen3-Next-80B-A3B-Thinking", - "display_name": "Qwen/Qwen3-Next-80B-A3B-Thinking", + "id": "reflection-ai/beam-1.0-501b-a23b", + "name": "Beam 1.0 501B A23B", + "display_name": "Beam 1.0 501B A23B", "modalities": { "input": [ "text" @@ -118873,9 +119099,10 @@ ] }, "limit": { - "context": 131072, - "output": 8192 + "context": 262144, + "output": 131072 }, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -118889,27 +119116,41 @@ "visibility": "summary", "continuation": [ "thinking_blocks" + ], + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" ] } }, + "attachment": false, + "open_weights": true, + "knowledge": "2026-06-30", + "release_date": "2026-10-05", + "last_updated": "2026-10-16", "type": "chat" }, { - "id": "inclusionAI/Ring-flash-2.0", - "name": "inclusionAI/Ring-flash-2.0", - "display_name": "inclusionAI/Ring-flash-2.0", + "id": "fireworks/ember-1", + "name": "Ember-1", + "display_name": "Ember-1", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 8192 + "context": 1040000, + "output": 131072 }, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -118917,289 +119158,451 @@ }, "extra_capabilities": { "reasoning": { - "supported": true + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "effort_options": [ + "none", + "low", + "high", + "max" + ] } }, + "attachment": true, + "open_weights": false, + "release_date": "2026-09-22", + "last_updated": "2026-09-22", "type": "chat" }, { - "id": "inclusionAI/Ling-mini-2.0", - "name": "inclusionAI/Ling-mini-2.0", - "display_name": "inclusionAI/Ling-mini-2.0", + "id": "google/gemini-3.5-flash", + "name": "Gemini 3.5 Flash", + "display_name": "Gemini 3.5 Flash", "modalities": { "input": [ - "text" + "text", + "image", + "video", + "audio", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 8192 + "context": 1048576, + "output": 65536 }, + "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "level", + "level": "high", + "level_options": [ + "minimal", + "low", + "medium", + "high" + ], + "summaries": true, + "visibility": "summary", + "continuation": [ + "thought_signatures" + ] + } }, + "attachment": true, + "open_weights": false, + "knowledge": "2025-01", + "release_date": "2026-05-19", + "last_updated": "2026-05-19", "type": "chat" }, { - "id": "Qwen/Qwen-Image-Edit-2509", - "name": "Qwen/Qwen-Image-Edit-2509", - "display_name": "Qwen/Qwen-Image-Edit-2509", + "id": "google/gemini-4-argon", + "name": "Gemini 4 Argon", + "display_name": "Gemini 4 Argon", "modalities": { "input": [ + "text", "image", - "text" + "video", + "audio", + "pdf" ], "output": [ - "image" - ] - }, - "tool_call": false, - "reasoning": { - "supported": false - } - }, - { - "id": "Qwen/Qwen-Image-Edit", - "name": "Qwen/Qwen-Image-Edit", - "display_name": "Qwen/Qwen-Image-Edit", - "modalities": { - "input": [ - "image", "text" - ], - "output": [ - "image" ] }, - "tool_call": false, - "reasoning": { - "supported": false - } - }, - { - "id": "Qwen/Qwen-Image", - "name": "Qwen/Qwen-Image", - "display_name": "Qwen/Qwen-Image", - "modalities": { - "input": [ - "text" - ], - "output": [ - "image" - ] + "limit": { + "context": 1048576, + "output": 65536 }, - "tool_call": false, + "temperature": false, + "tool_call": true, "reasoning": { - "supported": false - } + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "low", + "medium", + "high" + ] + } + }, + "attachment": true, + "open_weights": false, + "knowledge": "2026-03", + "release_date": "2026-10-13", + "last_updated": "2026-10-13", + "type": "chat" }, { - "id": "tencent/Hunyuan-MT-7B", - "name": "tencent/Hunyuan-MT-7B", - "display_name": "tencent/Hunyuan-MT-7B", + "id": "google/gemini-3.1-pro", + "name": "Gemini 3.1 Pro Preview", + "display_name": "Gemini 3.1 Pro Preview", "modalities": { "input": [ - "text" + "text", + "image", + "video", + "audio", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 8192 + "context": 1000000, + "output": 65536 }, + "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "level", + "level": "high", + "level_options": [ + "low", + "high" + ], + "summaries": true, + "visibility": "summary", + "continuation": [ + "thought_signatures" + ] + } }, + "attachment": true, + "open_weights": false, + "knowledge": "2025-01", + "release_date": "2026-02-19", + "last_updated": "2026-02-19", "type": "chat" }, { - "id": "Wan-AI/Wan2.2-I2V-A14B", - "name": "Wan-AI/Wan2.2-I2V-A14B", - "display_name": "Wan-AI/Wan2.2-I2V-A14B", + "id": "google/gemini-3.6-flash", + "name": "Gemini 3.6 Flash", + "display_name": "Gemini 3.6 Flash", "modalities": { "input": [ + "text", "image", - "text" - ], - "output": [ - "video" - ] - }, - "tool_call": false, - "reasoning": { - "supported": false - } - }, - { - "id": "Wan-AI/Wan2.2-T2V-A14B", - "name": "Wan-AI/Wan2.2-T2V-A14B", - "display_name": "Wan-AI/Wan2.2-T2V-A14B", - "modalities": { - "input": [ - "text" - ], - "output": [ - "video" - ] - }, - "tool_call": false, - "reasoning": { - "supported": false - } - }, - { - "id": "zai-org/GLM-4.5V", - "name": "zai-org/GLM-4.5V", - "display_name": "zai-org/GLM-4.5V", - "modalities": { - "input": [ - "text" + "video", + "audio", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 8192 + "context": 1048576, + "output": 65536 }, + "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "level", + "level": "high", + "level_options": [ + "minimal", + "low", + "medium", + "high" + ], + "summaries": true, + "visibility": "summary", + "continuation": [ + "thought_signatures" + ] + } }, + "attachment": true, + "open_weights": false, + "knowledge": "2026-03", + "release_date": "2026-07-21", + "last_updated": "2026-07-21", "type": "chat" }, { - "id": "zai-org/GLM-4.5", - "name": "zai-org/GLM-4.5", - "display_name": "zai-org/GLM-4.5", + "id": "google/gemini-3-flash", + "name": "Gemini 3 Flash Preview", + "display_name": "Gemini 3 Flash Preview", "modalities": { "input": [ - "text" + "text", + "image", + "video", + "audio", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 8192 + "context": 1000000, + "output": 65536 }, + "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "level", + "level": "high", + "level_options": [ + "minimal", + "low", + "medium", + "high" + ], + "summaries": true, + "visibility": "summary", + "continuation": [ + "thought_signatures" + ] + } }, + "attachment": true, + "open_weights": false, + "knowledge": "2025-01", + "release_date": "2025-12-17", + "last_updated": "2025-12-17", "type": "chat" }, { - "id": "stepfun-ai/step3", - "name": "stepfun-ai/step3", - "display_name": "stepfun-ai/step3", + "id": "google/gemini-3.8-flash", + "name": "Gemini 3.8 Flash", + "display_name": "Gemini 3.8 Flash", "modalities": { "input": [ - "text" + "text", + "image", + "video", + "audio", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 8192 + "context": 1048576, + "output": 65536 }, + "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "level", + "level": "high", + "level_options": [ + "minimal", + "low", + "medium", + "high" + ], + "summaries": true, + "visibility": "summary", + "continuation": [ + "thought_signatures" + ] + } }, + "attachment": true, + "open_weights": false, + "release_date": "2026-09-02", + "last_updated": "2026-09-02", "type": "chat" }, { - "id": "TeleAI/TeleSpeechASR", - "name": "TeleAI/TeleSpeechASR", - "display_name": "TeleAI/TeleSpeechASR", + "id": "google/gemini-2.5-pro", + "name": "Gemini 2.5 Pro", + "display_name": "Gemini 2.5 Pro", "modalities": { "input": [ - "audio" + "text", + "image", + "audio", + "video", + "pdf" ], "output": [ "text" ] }, - "tool_call": false, - "reasoning": { - "supported": false - } - }, - { - "id": "Qwen/Qwen3-Coder-480B-A35B-Instruct", - "name": "Qwen/Qwen3-Coder-480B-A35B-Instruct", - "display_name": "Qwen/Qwen3-Coder-480B-A35B-Instruct", - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] + "limit": { + "context": 1000000, + "output": 65536 }, + "temperature": true, "tool_call": true, "reasoning": { - "supported": false - } + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "budget", + "budget": { + "default": -1, + "min": 128, + "max": 32768, + "auto": -1, + "unit": "tokens" + }, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thought_signatures" + ] + } + }, + "attachment": true, + "open_weights": false, + "knowledge": "2025-01", + "release_date": "2025-06-17", + "last_updated": "2025-06-17", + "type": "chat" }, { - "id": "Qwen/Qwen3-30B-A3B-Thinking-2507", - "name": "Qwen/Qwen3-30B-A3B-Thinking-2507", - "display_name": "Qwen/Qwen3-30B-A3B-Thinking-2507", + "id": "google/gemini-2.5-flash-lite", + "name": "Gemini 2.5 Flash-Lite", + "display_name": "Gemini 2.5 Flash-Lite", "modalities": { "input": [ - "text" + "text", + "image", + "audio", + "video", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 8192 + "context": 1000000, + "output": 65536 }, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, + "default_enabled": false, + "mode": "budget", + "budget": { + "default": -1, + "min": 512, + "max": 24576, + "auto": -1, + "unit": "tokens" + }, "summaries": true, "visibility": "summary", "continuation": [ - "thinking_blocks" + "thought_signatures" ] } }, + "attachment": true, + "open_weights": false, + "knowledge": "2025-01", + "release_date": "2025-06-17", + "last_updated": "2025-06-17", "type": "chat" }, { - "id": "Qwen/Qwen3-235B-A22B-Thinking-2507", - "name": "Qwen/Qwen3-235B-A22B-Thinking-2507", - "display_name": "Qwen/Qwen3-235B-A22B-Thinking-2507", + "id": "google/gemini-2.5-flash", + "name": "Gemini 2.5 Flash", + "display_name": "Gemini 2.5 Flash", "modalities": { "input": [ - "text" + "text", + "image", + "audio", + "video", + "pdf" ], "output": [ "text" ] }, + "limit": { + "context": 1000000, + "output": 65536 + }, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -119208,88 +119611,174 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, + "default_enabled": true, + "mode": "budget", + "budget": { + "default": -1, + "min": 0, + "max": 24576, + "auto": -1, + "off": 0, + "unit": "tokens" + }, "summaries": true, "visibility": "summary", "continuation": [ - "thinking_blocks" + "thought_signatures" ] } }, + "attachment": true, + "open_weights": false, + "knowledge": "2025-01", + "release_date": "2025-06-17", + "last_updated": "2025-06-17", "type": "chat" }, { - "id": "Qwen/Qwen3-235B-A22B-Instruct-2507", - "name": "Qwen/Qwen3-235B-A22B-Instruct-2507", - "display_name": "Qwen/Qwen3-235B-A22B-Instruct-2507", + "id": "google/gemini-3.7-flash", + "name": "Gemini 3.7 Flash", + "display_name": "Gemini 3.7 Flash", "modalities": { "input": [ - "text" + "text", + "image", + "video", + "audio", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 8192 + "context": 1048576, + "output": 65536 }, + "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "level", + "level": "high", + "level_options": [ + "minimal", + "low", + "medium", + "high" + ], + "summaries": true, + "visibility": "summary", + "continuation": [ + "thought_signatures" + ] + } }, + "attachment": true, + "open_weights": false, + "knowledge": "2026-03", + "release_date": "2026-08-13", + "last_updated": "2026-08-13", "type": "chat" }, { - "id": "THUDM/GLM-4.1V-9B-Thinking", - "name": "THUDM/GLM-4.1V-9B-Thinking", - "display_name": "THUDM/GLM-4.1V-9B-Thinking", + "id": "google/gemini-3.1-flash-lite", + "name": "Gemini 3.1 Flash Lite", + "display_name": "Gemini 3.1 Flash Lite", "modalities": { "input": [ - "text" + "text", + "image", + "video", + "audio", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 8192 + "context": 1000000, + "output": 65536 }, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, "default": true }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "effort_options": [ + "minimal", + "low", + "medium", + "high" + ] + } + }, + "attachment": true, + "open_weights": false, + "knowledge": "2025-01", + "release_date": "2026-05-07", + "last_updated": "2026-05-07", "type": "chat" }, { - "id": "Pro/THUDM/GLM-4.1V-9B-Thinking", - "name": "Pro/THUDM/GLM-4.1V-9B-Thinking", - "display_name": "Pro/THUDM/GLM-4.1V-9B-Thinking", + "id": "google/gemini-3.5-flash-lite", + "name": "Gemini 3.5 Flash Lite", + "display_name": "Gemini 3.5 Flash Lite", "modalities": { "input": [ - "text" + "text", + "image", + "video", + "audio", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 8192 + "context": 1048576, + "output": 65536 }, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, "default": true }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "effort_options": [ + "minimal", + "low", + "medium", + "high" + ] + } + }, + "attachment": true, + "open_weights": false, + "knowledge": "2026-03", + "release_date": "2026-07-21", + "last_updated": "2026-07-21", "type": "chat" }, { - "id": "baidu/ERNIE-4.5-300B-A47B", - "name": "baidu/ERNIE-4.5-300B-A47B", - "display_name": "baidu/ERNIE-4.5-300B-A47B", + "id": "alibaba/qwen3-max", + "name": "Qwen3 Max", + "display_name": "Qwen3 Max", "modalities": { "input": [ "text" @@ -119298,16 +119787,37 @@ "text" ] }, + "limit": { + "context": 262144, + "output": 65536 + }, + "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } }, + "attachment": false, + "open_weights": false, + "knowledge": "2025-04", + "release_date": "2025-09-23", + "last_updated": "2025-09-23", "type": "chat" }, { - "id": "moonshotai/Kimi-Dev-72B", - "name": "moonshotai/Kimi-Dev-72B", - "display_name": "moonshotai/Kimi-Dev-72B", + "id": "alibaba/qwen3.7-max", + "name": "Qwen3.7 Max", + "display_name": "Qwen3.7 Max", "modalities": { "input": [ "text" @@ -119317,1548 +119827,373 @@ ] }, "limit": { - "context": 131072, - "output": 8192 + "context": 1000000, + "output": 131072 }, + "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true + } }, + "attachment": false, + "open_weights": false, + "release_date": "2026-05-21", + "last_updated": "2026-05-21", "type": "chat" }, { - "id": "MiniMaxAI/MiniMax-M1-80k", - "name": "MiniMaxAI/MiniMax-M1-80k", - "display_name": "MiniMaxAI/MiniMax-M1-80k", + "id": "alibaba/qwen3.8-max", + "name": "Qwen3.8 Max", + "display_name": "Qwen3.8 Max", "modalities": { "input": [ - "text" + "text", + "image", + "video", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 8192 + "context": 1000000, + "output": 131072 }, + "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "effort_options": [ + "low", + "medium", + "xhigh" + ] + } }, + "attachment": true, + "open_weights": false, + "release_date": "2026-08-03", + "last_updated": "2026-08-03", "type": "chat" }, { - "id": "Tongyi-Zhiwen/QwenLong-L1-32B", - "name": "Tongyi-Zhiwen/QwenLong-L1-32B", - "display_name": "Tongyi-Zhiwen/QwenLong-L1-32B", + "id": "alibaba/qwen3.5-397b-a17b", + "name": "Qwen3.5 397B-A17B", + "display_name": "Qwen3.5 397B-A17B", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 8192 + "context": 262144, + "output": 65536 }, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, "default": true }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } + }, + "attachment": true, + "open_weights": true, + "release_date": "2026-02-15", + "last_updated": "2026-02-15", "type": "chat" }, { - "id": "deepseek-ai/DeepSeek-R1-0528-Qwen3-8B", - "name": "deepseek-ai/DeepSeek-R1-0528-Qwen3-8B", - "display_name": "deepseek-ai/DeepSeek-R1-0528-Qwen3-8B", + "id": "alibaba/qwen3.7-plus", + "name": "Qwen3.7 Plus", + "display_name": "Qwen3.7 Plus", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 8192 + "context": 1000000, + "output": 64000 }, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, "default": true }, + "extra_capabilities": { + "reasoning": { + "supported": true + } + }, + "attachment": true, + "open_weights": false, + "knowledge": "2025-04", + "release_date": "2026-06-02", + "last_updated": "2026-06-02", "type": "chat" }, { - "id": "Qwen/Qwen3-30B-A3B", - "name": "Qwen/Qwen3-30B-A3B", - "display_name": "Qwen/Qwen3-30B-A3B", + "id": "anthropic/claude-haiku-4.5", + "name": "Claude Haiku 4.5 (latest)", + "display_name": "Claude Haiku 4.5 (latest)", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 8192 + "context": 200000, + "output": 64000 }, + "temperature": true, "tool_call": true, "reasoning": { - "supported": true + "supported": true, + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": false, + "mode": "budget", + "budget": { + "min": 1024, + "unit": "tokens" + }, "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ "thinking_blocks" + ], + "notes": [ + "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", + "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." ] } }, + "attachment": true, + "open_weights": false, + "knowledge": "2025-02-28", + "release_date": "2025-10-15", + "last_updated": "2025-10-15", "type": "chat" }, { - "id": "Qwen/Qwen3-Reranker-8B", - "name": "Qwen/Qwen3-Reranker-8B", - "display_name": "Qwen/Qwen3-Reranker-8B", + "id": "anthropic/claude-fable-5.1", + "name": "Claude Fable 5.1", + "display_name": "Claude Fable 5.1", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ - "score" + "text" ] }, "limit": { - "context": 8192, - "output": 2048 + "context": 1000000, + "output": 128000 }, - "tool_call": false, + "temperature": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "type": "rerank" + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "high", + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "omitted", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Adaptive thinking is always on for Claude Fable 5 and Claude Mythos 5; thinking.type = \"disabled\" is rejected.", + "Manual budget_tokens requests return 400 on Claude Fable 5 and Claude Mythos 5.", + "thinking.display defaults to omitted; set display to summarized to receive readable thinking summaries." + ] + } + }, + "attachment": true, + "open_weights": false, + "knowledge": "2026-06", + "release_date": "2026-09-01", + "last_updated": "2026-09-01", + "type": "chat" }, { - "id": "Qwen/Qwen3-Embedding-8B", - "name": "Qwen/Qwen3-Embedding-8B", - "display_name": "Qwen/Qwen3-Embedding-8B", + "id": "anthropic/claude-sonnet-4.5", + "name": "Claude Sonnet 4.5 (latest)", + "display_name": "Claude Sonnet 4.5 (latest)", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ - "embedding" + "text" ] }, "limit": { - "context": 8192, - "output": 2048 - }, - "tool_call": false, - "reasoning": { - "supported": false - }, - "type": "embedding" - }, - { - "id": "Qwen/Qwen3-Reranker-4B", - "name": "Qwen/Qwen3-Reranker-4B", - "display_name": "Qwen/Qwen3-Reranker-4B", - "modalities": { - "input": [ - "text" - ], - "output": [ - "score" - ] - }, - "limit": { - "context": 8192, - "output": 2048 - }, - "tool_call": false, - "reasoning": { - "supported": false - }, - "type": "rerank" - }, - { - "id": "Qwen/Qwen3-Embedding-4B", - "name": "Qwen/Qwen3-Embedding-4B", - "display_name": "Qwen/Qwen3-Embedding-4B", - "modalities": { - "input": [ - "text" - ], - "output": [ - "embedding" - ] - }, - "limit": { - "context": 8192, - "output": 2048 - }, - "tool_call": false, - "reasoning": { - "supported": false - }, - "type": "embedding" - }, - { - "id": "Qwen/Qwen3-Reranker-0.6B", - "name": "Qwen/Qwen3-Reranker-0.6B", - "display_name": "Qwen/Qwen3-Reranker-0.6B", - "modalities": { - "input": [ - "text" - ], - "output": [ - "score" - ] - }, - "limit": { - "context": 8192, - "output": 2048 - }, - "tool_call": false, - "reasoning": { - "supported": false - }, - "type": "rerank" - }, - { - "id": "Qwen/Qwen3-Embedding-0.6B", - "name": "Qwen/Qwen3-Embedding-0.6B", - "display_name": "Qwen/Qwen3-Embedding-0.6B", - "modalities": { - "input": [ - "text" - ], - "output": [ - "embedding" - ] - }, - "limit": { - "context": 8192, - "output": 2048 - }, - "tool_call": false, - "reasoning": { - "supported": false - }, - "type": "embedding" - }, - { - "id": "ascend-tribe/pangu-pro-moe", - "name": "ascend-tribe/pangu-pro-moe", - "display_name": "ascend-tribe/pangu-pro-moe", - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 8192 - }, - "tool_call": true, - "reasoning": { - "supported": false - }, - "type": "chat" - }, - { - "id": "THUDM/GLM-Z1-32B-0414", - "name": "THUDM/GLM-Z1-32B-0414", - "display_name": "THUDM/GLM-Z1-32B-0414", - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 8192 - }, - "tool_call": true, - "reasoning": { - "supported": false - }, - "type": "chat" - }, - { - "id": "THUDM/GLM-4-32B-0414", - "name": "THUDM/GLM-4-32B-0414", - "display_name": "THUDM/GLM-4-32B-0414", - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 8192 - }, - "tool_call": true, - "reasoning": { - "supported": false - }, - "type": "chat" - }, - { - "id": "THUDM/GLM-Z1-9B-0414", - "name": "THUDM/GLM-Z1-9B-0414", - "display_name": "THUDM/GLM-Z1-9B-0414", - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 8192 - }, - "tool_call": true, - "reasoning": { - "supported": false - }, - "type": "chat" - }, - { - "id": "THUDM/GLM-4-9B-0414", - "name": "THUDM/GLM-4-9B-0414", - "display_name": "THUDM/GLM-4-9B-0414", - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 8192 - }, - "tool_call": true, - "reasoning": { - "supported": false - }, - "type": "chat" - }, - { - "id": "Qwen/Qwen2.5-VL-32B-Instruct", - "name": "Qwen/Qwen2.5-VL-32B-Instruct", - "display_name": "Qwen/Qwen2.5-VL-32B-Instruct", - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 8192 - }, - "tool_call": true, - "reasoning": { - "supported": false - }, - "type": "chat" - }, - { - "id": "Qwen/Qwen3-235B-A22B", - "name": "Qwen/Qwen3-235B-A22B", - "display_name": "Qwen/Qwen3-235B-A22B", - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 8192 + "context": 200000, + "output": 64000 }, + "temperature": true, "tool_call": true, "reasoning": { - "supported": true + "supported": true, + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": false, + "mode": "budget", + "budget": { + "min": 1024, + "unit": "tokens" + }, "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ "thinking_blocks" + ], + "notes": [ + "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", + "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." ] } }, + "attachment": true, + "open_weights": false, + "knowledge": "2025-07-31", + "release_date": "2025-09-29", + "last_updated": "2025-09-29", "type": "chat" }, { - "id": "Qwen/QwQ-32B", - "name": "Qwen/QwQ-32B", - "display_name": "Qwen/QwQ-32B", + "id": "anthropic/claude-sonnet-5", + "name": "Claude Sonnet 5", + "display_name": "Claude Sonnet 5", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 8192 + "context": 1000000, + "output": 128000 }, + "temperature": false, "tool_call": true, "reasoning": { - "supported": true + "supported": true, + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" ] } }, - "type": "chat" - }, - { - "id": "Qwen/Qwen2.5-VL-72B-Instruct", - "name": "Qwen/Qwen2.5-VL-72B-Instruct", - "display_name": "Qwen/Qwen2.5-VL-72B-Instruct", - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 8192 - }, - "tool_call": true, - "reasoning": { - "supported": false - }, - "type": "chat" - }, - { - "id": "Pro/Qwen/Qwen2.5-VL-7B-Instruct", - "name": "Pro/Qwen/Qwen2.5-VL-7B-Instruct", - "display_name": "Pro/Qwen/Qwen2.5-VL-7B-Instruct", - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 8192 - }, - "tool_call": true, - "reasoning": { - "supported": false - }, - "type": "chat" - }, - { - "id": "deepseek-ai/DeepSeek-R1-Distill-Qwen-32B", - "name": "deepseek-ai/DeepSeek-R1-Distill-Qwen-32B", - "display_name": "deepseek-ai/DeepSeek-R1-Distill-Qwen-32B", - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 8192 - }, - "tool_call": true, - "reasoning": { - "supported": true, - "default": true - }, - "type": "chat" - }, - { - "id": "deepseek-ai/DeepSeek-R1-Distill-Qwen-14B", - "name": "deepseek-ai/DeepSeek-R1-Distill-Qwen-14B", - "display_name": "deepseek-ai/DeepSeek-R1-Distill-Qwen-14B", - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 8192 - }, - "tool_call": true, - "reasoning": { - "supported": true, - "default": true - }, - "type": "chat" - }, - { - "id": "deepseek-ai/DeepSeek-R1-Distill-Qwen-7B", - "name": "deepseek-ai/DeepSeek-R1-Distill-Qwen-7B", - "display_name": "deepseek-ai/DeepSeek-R1-Distill-Qwen-7B", - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 8192 - }, - "tool_call": true, - "reasoning": { - "supported": true, - "default": true - }, - "type": "chat" - }, - { - "id": "Qwen/QVQ-72B-Preview", - "name": "Qwen/QVQ-72B-Preview", - "display_name": "Qwen/QVQ-72B-Preview", - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 8192 - }, - "tool_call": true, - "reasoning": { - "supported": true, - "default": true - }, - "type": "chat" - }, - { - "id": "deepseek-ai/DeepSeek-V2.5", - "name": "deepseek-ai/DeepSeek-V2.5", - "display_name": "deepseek-ai/DeepSeek-V2.5", - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 8192 - }, - "tool_call": true, - "reasoning": { - "supported": false - }, - "type": "chat" - }, - { - "id": "fnlp/MOSS-TTSD-v0.5", - "name": "fnlp/MOSS-TTSD-v0.5", - "display_name": "fnlp/MOSS-TTSD-v0.5", - "modalities": { - "input": [ - "text" - ], - "output": [ - "audio" - ] - }, - "tool_call": false, - "reasoning": { - "supported": false - } - }, - { - "id": "FunAudioLLM/CosyVoice2-0.5B", - "name": "FunAudioLLM/CosyVoice2-0.5B", - "display_name": "FunAudioLLM/CosyVoice2-0.5B", - "modalities": { - "input": [ - "text" - ], - "output": [ - "audio" - ] - }, - "tool_call": false, - "reasoning": { - "supported": false - } - }, - { - "id": "FunAudioLLM/SenseVoiceSmall", - "name": "FunAudioLLM/SenseVoiceSmall", - "display_name": "FunAudioLLM/SenseVoiceSmall", - "modalities": { - "input": [ - "audio" - ], - "output": [ - "text" - ] - }, - "tool_call": false, - "reasoning": { - "supported": false - } - }, - { - "id": "IndexTeam/IndexTTS-2", - "name": "IndexTeam/IndexTTS-2", - "display_name": "IndexTeam/IndexTTS-2", - "modalities": { - "input": [ - "text" - ], - "output": [ - "audio" - ] - }, - "tool_call": false, - "reasoning": { - "supported": false - } - }, - { - "id": "BAAI/bge-m3", - "name": "BAAI/bge-m3", - "display_name": "BAAI/bge-m3", - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 8192 - }, - "tool_call": true, - "reasoning": { - "supported": false - }, - "type": "embedding" - }, - { - "id": "BAAI/bge-reranker-v2-m3", - "name": "BAAI/bge-reranker-v2-m3", - "display_name": "BAAI/bge-reranker-v2-m3", - "modalities": { - "input": [ - "text" - ], - "output": [ - "score" - ] - }, - "limit": { - "context": 8192, - "output": 2048 - }, - "tool_call": false, - "reasoning": { - "supported": false - }, - "type": "rerank" - }, - { - "id": "netease-youdao/bce-embedding-base_v1", - "name": "netease-youdao/bce-embedding-base_v1", - "display_name": "netease-youdao/bce-embedding-base_v1", - "modalities": { - "input": [ - "text" - ], - "output": [ - "embedding" - ] - }, - "limit": { - "context": 8192, - "output": 2048 - }, - "tool_call": false, - "reasoning": { - "supported": false - }, - "type": "embedding" - }, - { - "id": "netease-youdao/bce-reranker-base_v1", - "name": "netease-youdao/bce-reranker-base_v1", - "display_name": "netease-youdao/bce-reranker-base_v1", - "modalities": { - "input": [ - "text" - ], - "output": [ - "score" - ] - }, - "limit": { - "context": 8192, - "output": 2048 - }, - "tool_call": false, - "reasoning": { - "supported": false - } - }, - { - "id": "Qwen/Qwen2.5-Coder-32B-Instruct", - "name": "Qwen/Qwen2.5-Coder-32B-Instruct", - "display_name": "Qwen/Qwen2.5-Coder-32B-Instruct", - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 8192 - }, - "tool_call": true, - "reasoning": { - "supported": false - } - }, - { - "id": "Kwai-Kolors/Kolors", - "name": "Kwai-Kolors/Kolors", - "display_name": "Kwai-Kolors/Kolors", - "modalities": { - "input": [ - "text" - ], - "output": [ - "image" - ] - }, - "tool_call": false, - "reasoning": { - "supported": false - } - }, - { - "id": "Qwen/Qwen2-VL-72B-Instruct", - "name": "Qwen/Qwen2-VL-72B-Instruct", - "display_name": "Qwen/Qwen2-VL-72B-Instruct", - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 8192 - }, - "tool_call": true, - "reasoning": { - "supported": false - }, - "type": "chat" - }, - { - "id": "Qwen/Qwen2.5-72B-Instruct-128K", - "name": "Qwen/Qwen2.5-72B-Instruct-128K", - "display_name": "Qwen/Qwen2.5-72B-Instruct-128K", - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 8192 - }, - "tool_call": true, - "reasoning": { - "supported": false - }, - "type": "chat" - }, - { - "id": "deepseek-ai/deepseek-vl2", - "name": "deepseek-ai/deepseek-vl2", - "display_name": "deepseek-ai/deepseek-vl2", - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 8192 - }, - "tool_call": true, - "reasoning": { - "supported": false - }, - "type": "chat" - }, - { - "id": "Qwen/Qwen2.5-32B-Instruct", - "name": "Qwen/Qwen2.5-32B-Instruct", - "display_name": "Qwen/Qwen2.5-32B-Instruct", - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 8192 - }, - "tool_call": true, - "reasoning": { - "supported": false - }, - "type": "chat" - }, - { - "id": "Qwen/Qwen2.5-14B-Instruct", - "name": "Qwen/Qwen2.5-14B-Instruct", - "display_name": "Qwen/Qwen2.5-14B-Instruct", - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 8192 - }, - "tool_call": true, - "reasoning": { - "supported": false - }, - "type": "chat" - }, - { - "id": "Qwen/Qwen2.5-Coder-7B-Instruct", - "name": "Qwen/Qwen2.5-Coder-7B-Instruct", - "display_name": "Qwen/Qwen2.5-Coder-7B-Instruct", - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 8192 - }, - "tool_call": true, - "reasoning": { - "supported": false - } - }, - { - "id": "internlm/internlm2_5-7b-chat", - "name": "internlm/internlm2_5-7b-chat", - "display_name": "internlm/internlm2_5-7b-chat", - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 8192 - }, - "tool_call": true, - "reasoning": { - "supported": false - }, - "type": "chat" - }, - { - "id": "Qwen/Qwen2-7B-Instruct", - "name": "Qwen/Qwen2-7B-Instruct", - "display_name": "Qwen/Qwen2-7B-Instruct", - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 8192 - }, - "tool_call": true, - "reasoning": { - "supported": false - }, - "type": "chat" - }, - { - "id": "THUDM/glm-4-9b-chat", - "name": "THUDM/glm-4-9b-chat", - "display_name": "THUDM/glm-4-9b-chat", - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 8192 - }, - "tool_call": true, - "reasoning": { - "supported": false - }, - "type": "chat" - }, - { - "id": "BAAI/bge-large-en-v1.5", - "name": "BAAI/bge-large-en-v1.5", - "display_name": "BAAI/bge-large-en-v1.5", - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 8192 - }, - "tool_call": true, - "reasoning": { - "supported": false - }, - "type": "embedding" - }, - { - "id": "BAAI/bge-large-zh-v1.5", - "name": "BAAI/bge-large-zh-v1.5", - "display_name": "BAAI/bge-large-zh-v1.5", - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 8192 - }, - "tool_call": true, - "reasoning": { - "supported": false - }, - "type": "embedding" - }, - { - "id": "LoRA/Qwen/Qwen2.5-32B-Instruct", - "name": "LoRA/Qwen/Qwen2.5-32B-Instruct", - "display_name": "LoRA/Qwen/Qwen2.5-32B-Instruct", - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 8192 - }, - "tool_call": true, - "reasoning": { - "supported": false - }, - "type": "chat" - }, - { - "id": "LoRA/Qwen/Qwen2.5-14B-Instruct", - "name": "LoRA/Qwen/Qwen2.5-14B-Instruct", - "display_name": "LoRA/Qwen/Qwen2.5-14B-Instruct", - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 8192 - }, - "tool_call": true, - "reasoning": { - "supported": false - }, - "type": "chat" - }, - { - "id": "Pro/deepseek-ai/DeepSeek-R1-Distill-Qwen-7B", - "name": "Pro/deepseek-ai/DeepSeek-R1-Distill-Qwen-7B", - "display_name": "Pro/deepseek-ai/DeepSeek-R1-Distill-Qwen-7B", - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 8192 - }, - "tool_call": true, - "reasoning": { - "supported": true, - "default": true - }, - "type": "chat" - }, - { - "id": "Pro/Qwen/Qwen2.5-Coder-7B-Instruct", - "name": "Pro/Qwen/Qwen2.5-Coder-7B-Instruct", - "display_name": "Pro/Qwen/Qwen2.5-Coder-7B-Instruct", - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 8192 - }, - "tool_call": true, - "reasoning": { - "supported": false - } - }, - { - "id": "Pro/BAAI/bge-m3", - "name": "Pro/BAAI/bge-m3", - "display_name": "Pro/BAAI/bge-m3", - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 8192 - }, - "tool_call": true, - "reasoning": { - "supported": false - }, - "type": "embedding" - }, - { - "id": "Pro/Qwen/Qwen2.5-7B-Instruct", - "name": "Pro/Qwen/Qwen2.5-7B-Instruct", - "display_name": "Pro/Qwen/Qwen2.5-7B-Instruct", - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 8192 - }, - "tool_call": true, - "reasoning": { - "supported": false - }, - "type": "chat" - }, - { - "id": "Pro/BAAI/bge-reranker-v2-m3", - "name": "Pro/BAAI/bge-reranker-v2-m3", - "display_name": "Pro/BAAI/bge-reranker-v2-m3", - "modalities": { - "input": [ - "text" - ], - "output": [ - "score" - ] - }, - "limit": { - "context": 8192, - "output": 2048 - }, - "tool_call": false, - "reasoning": { - "supported": false - }, - "type": "rerank" - }, - { - "id": "LoRA/Qwen/Qwen2.5-72B-Instruct", - "name": "LoRA/Qwen/Qwen2.5-72B-Instruct", - "display_name": "LoRA/Qwen/Qwen2.5-72B-Instruct", - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 8192 - }, - "tool_call": true, - "reasoning": { - "supported": false - }, - "type": "chat" - }, - { - "id": "Pro/Qwen/Qwen2-7B-Instruct", - "name": "Pro/Qwen/Qwen2-7B-Instruct", - "display_name": "Pro/Qwen/Qwen2-7B-Instruct", - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 8192 - }, - "tool_call": true, - "reasoning": { - "supported": false - }, - "type": "chat" - }, - { - "id": "LoRA/Qwen/Qwen2.5-7B-Instruct", - "name": "LoRA/Qwen/Qwen2.5-7B-Instruct", - "display_name": "LoRA/Qwen/Qwen2.5-7B-Instruct", - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 8192 - }, - "tool_call": true, - "reasoning": { - "supported": false - }, - "type": "chat" - }, - { - "id": "Pro/THUDM/glm-4-9b-chat", - "name": "Pro/THUDM/glm-4-9b-chat", - "display_name": "Pro/THUDM/glm-4-9b-chat", - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 8192 - }, - "tool_call": true, - "reasoning": { - "supported": false - }, - "type": "chat" - }, - { - "id": "THUDM/GLM-Z1-Rumination-32B-0414", - "name": "THUDM/GLM-Z1-Rumination-32B-0414", - "display_name": "THUDM/GLM-Z1-Rumination-32B-0414", - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 8192 - }, - "tool_call": true, - "reasoning": { - "supported": true, - "default": true - }, - "type": "chat" - }, - { - "id": "stabilityai/stable-diffusion-xl-base-1.0", - "name": "stabilityai/stable-diffusion-xl-base-1.0", - "display_name": "stabilityai/stable-diffusion-xl-base-1.0", - "modalities": { - "input": [ - "text" - ], - "output": [ - "image" - ] - }, - "tool_call": false, - "reasoning": { - "supported": false - } - }, - { - "id": "black-forest-labs/FLUX.1-schnell", - "name": "black-forest-labs/FLUX.1-schnell", - "display_name": "black-forest-labs/FLUX.1-schnell", - "modalities": { - "input": [ - "text" - ], - "output": [ - "image" - ] - }, - "tool_call": false, - "reasoning": { - "supported": false - } - }, - { - "id": "black-forest-labs/FLUX.1-dev", - "name": "black-forest-labs/FLUX.1-dev", - "display_name": "black-forest-labs/FLUX.1-dev", - "modalities": { - "input": [ - "text" - ], - "output": [ - "image" - ] - }, - "tool_call": false, - "reasoning": { - "supported": false - } - }, - { - "id": "Pro/black-forest-labs/FLUX.1-schnell", - "name": "Pro/black-forest-labs/FLUX.1-schnell", - "display_name": "Pro/black-forest-labs/FLUX.1-schnell", - "modalities": { - "input": [ - "text" - ], - "output": [ - "image" - ] - }, - "tool_call": false, - "reasoning": { - "supported": false - } - }, - { - "id": "stabilityai/stable-diffusion-3-5-large", - "name": "stabilityai/stable-diffusion-3-5-large", - "display_name": "stabilityai/stable-diffusion-3-5-large", - "modalities": { - "input": [ - "text" - ], - "output": [ - "image" - ] - }, - "tool_call": false, - "reasoning": { - "supported": false - } - }, - { - "id": "fishaudio/fish-speech-1.4", - "name": "fishaudio/fish-speech-1.4", - "display_name": "fishaudio/fish-speech-1.4", - "modalities": { - "input": [ - "text" - ], - "output": [ - "audio" - ] - }, - "tool_call": false, - "reasoning": { - "supported": false - } - }, - { - "id": "RVC-Boss/GPT-SoVITS", - "name": "RVC-Boss/GPT-SoVITS", - "display_name": "RVC-Boss/GPT-SoVITS", - "modalities": { - "input": [ - "text" - ], - "output": [ - "audio" - ] - }, - "tool_call": false, - "reasoning": { - "supported": false - } - }, - { - "id": "fishaudio/fish-speech-1.5", - "name": "fishaudio/fish-speech-1.5", - "display_name": "fishaudio/fish-speech-1.5", - "modalities": { - "input": [ - "text" - ], - "output": [ - "audio" - ] - }, - "tool_call": false, - "reasoning": { - "supported": false - } - }, - { - "id": "black-forest-labs/FLUX.1-pro", - "name": "black-forest-labs/FLUX.1-pro", - "display_name": "black-forest-labs/FLUX.1-pro", - "modalities": { - "input": [ - "text" - ], - "output": [ - "image" - ] - }, - "tool_call": false, - "reasoning": { - "supported": false - } - }, - { - "id": "LoRA/black-forest-labs/FLUX.1-dev", - "name": "LoRA/black-forest-labs/FLUX.1-dev", - "display_name": "LoRA/black-forest-labs/FLUX.1-dev", - "modalities": { - "input": [ - "text" - ], - "output": [ - "image" - ] - }, - "tool_call": false, - "reasoning": { - "supported": false - } - }, - { - "id": "SeedLLM/Seed-Rice-7B", - "name": "SeedLLM/Seed-Rice-7B", - "display_name": "SeedLLM/Seed-Rice-7B", - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 8192 - }, - "tool_call": true, - "reasoning": { - "supported": false - }, - "type": "chat" - } - ] - }, - "fastrouter": { - "id": "fastrouter", - "name": "FastRouter", - "display_name": "FastRouter", - "api": "https://go.fastrouter.ai/api/v1", - "doc": "https://fastrouter.ai/models", - "models": [ - { - "id": "bytedance/seedance-2", - "name": "Seedance 2", - "display_name": "Seedance 2", - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "video" - ] - }, - "limit": { - "context": 4096, - "output": 8192 - }, - "temperature": false, - "tool_call": false, - "reasoning": { - "supported": false - }, "attachment": true, "open_weights": false, - "release_date": "2026-02-12", - "last_updated": "2026-10-04" + "knowledge": "2026-01-31", + "release_date": "2026-06-30", + "last_updated": "2026-06-30", + "type": "chat" }, { - "id": "minimax/minimax-m2.7", - "name": "MiniMax-M2.7", - "display_name": "MiniMax-M2.7", + "id": "anthropic/claude-fable-5", + "name": "Claude Fable 5", + "display_name": "Claude Fable 5", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 204800, - "output": 131072 + "context": 1000000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -120867,37 +120202,55 @@ "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "high", + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" + ], "interleaved": true, "summaries": true, - "visibility": "summary", + "visibility": "omitted", "continuation": [ "thinking_blocks" + ], + "notes": [ + "Adaptive thinking is always on for Claude Fable 5 and Claude Mythos 5; thinking.type = \"disabled\" is rejected.", + "Manual budget_tokens requests return 400 on Claude Fable 5 and Claude Mythos 5.", + "thinking.display defaults to omitted; set display to summarized to receive readable thinking summaries." ] } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-03-18", - "last_updated": "2026-03-18", + "attachment": true, + "open_weights": false, + "knowledge": "2026-01-31", + "release_date": "2026-06-09", + "last_updated": "2026-06-09", "type": "chat" }, { - "id": "minimax/minimax-m2.7-highspeed", - "name": "MiniMax-M2.7-highspeed", - "display_name": "MiniMax-M2.7-highspeed", + "id": "anthropic/claude-haiku-5.5", + "name": "Claude Haiku 5.5", + "display_name": "Claude Haiku 5.5", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 204800, - "output": 131072 + "context": 1000000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -120905,25 +120258,26 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" ] } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-03-18", - "last_updated": "2026-03-18", + "attachment": true, + "open_weights": false, + "knowledge": "2026-06", + "release_date": "2026-10-07", + "last_updated": "2026-10-07", "type": "chat" }, { - "id": "x-ai/grok-build-0.1", - "name": "Grok Build 0.1", - "display_name": "Grok Build 0.1", + "id": "anthropic/claude-opus-4.5", + "name": "Claude Opus 4.5 (latest)", + "display_name": "Claude Opus 4.5 (latest)", "modalities": { "input": [ "text", @@ -120935,69 +120289,112 @@ ] }, "limit": { - "context": 256000, - "output": 256000 + "context": 200000, + "output": 64000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": false, + "mode": "mixed", + "budget": { + "min": 1024, + "unit": "tokens" + }, + "effort": "high", "effort_options": [ "low", "medium", "high" + ], + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Claude Opus 4.5 uses manual thinking.type = \"enabled\" with budget_tokens; effort can be used alongside the thinking budget.", + "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header." ] } }, "attachment": true, "open_weights": false, - "release_date": "2026-04-16", - "last_updated": "2026-04-16", + "knowledge": "2025-05", + "release_date": "2025-11-24", + "last_updated": "2025-11-24", "type": "chat" }, { - "id": "x-ai/grok-4", - "name": "Grok 4", - "display_name": "Grok 4", + "id": "anthropic/claude-opus-4.6", + "name": "Claude Opus 4.6", + "display_name": "Claude Opus 4.6", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 256000, - "output": 64000 + "context": 1000000, + "output": 128000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { - "supported": true + "supported": true, + "default_enabled": false, + "mode": "mixed", + "budget": { + "min": 1024, + "unit": "tokens" + }, + "effort": "high", + "effort_options": [ + "low", + "medium", + "high", + "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Anthropic recommends adaptive thinking with effort for Claude 4.6; budget_tokens remains a deprecated compatibility path.", + "Anthropic API defaults effort to high; lower effort levels should be chosen per workload." + ] } }, - "attachment": false, + "attachment": true, "open_weights": false, - "knowledge": "2025-07", - "release_date": "2025-07-09", - "last_updated": "2025-07-09", + "knowledge": "2025-05-31", + "release_date": "2026-02-05", + "last_updated": "2026-03-13", "type": "chat" }, { - "id": "x-ai/grok-4.3", - "name": "Grok 4.3", - "display_name": "Grok 4.3", + "id": "anthropic/claude-sonnet-5.5", + "name": "Claude Sonnet 5.5", + "display_name": "Claude Sonnet 5.5", "modalities": { "input": [ "text", @@ -121010,9 +120407,9 @@ }, "limit": { "context": 1000000, - "output": 30000 + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -121020,76 +120417,26 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, "effort_options": [ "low", "medium", - "high" + "high", + "xhigh", + "max" ] } }, "attachment": true, "open_weights": false, - "release_date": "2026-04-17", - "last_updated": "2026-04-17", + "knowledge": "2026-06", + "release_date": "2026-09-28", + "last_updated": "2026-09-28", "type": "chat" }, { - "id": "leonardo-ai/lucid-realism", - "name": "Lucid Realism", - "display_name": "Lucid Realism", - "modalities": { - "input": [ - "text" - ], - "output": [ - "image" - ] - }, - "limit": { - "context": 4096, - "output": 8192 - }, - "temperature": false, - "tool_call": false, - "reasoning": { - "supported": false - }, - "attachment": false, - "open_weights": false, - "release_date": "2025-06-01", - "last_updated": "2025-06-01" - }, - { - "id": "leonardo-ai/lucid-origin", - "name": "Lucid Origin", - "display_name": "Lucid Origin", - "modalities": { - "input": [ - "text" - ], - "output": [ - "image" - ] - }, - "limit": { - "context": 4096, - "output": 8192 - }, - "temperature": false, - "tool_call": false, - "reasoning": { - "supported": false - }, - "attachment": false, - "open_weights": false, - "release_date": "2025-06-01", - "last_updated": "2025-06-01" - }, - { - "id": "anthropic/claude-opus-4.1", - "name": "Claude Opus 4.1", - "display_name": "Claude Opus 4.1", + "id": "anthropic/claude-opus-4.7", + "name": "Claude Opus 4.7", + "display_name": "Claude Opus 4.7", "modalities": { "input": [ "text", @@ -121101,10 +120448,10 @@ ] }, "limit": { - "context": 200000, - "output": 32000 + "context": 1000000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -121114,28 +120461,33 @@ "reasoning": { "supported": true, "default_enabled": false, - "mode": "budget", - "budget": { - "min": 1024, - "unit": "tokens" - }, + "mode": "effort", + "effort": "high", + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" + ], "interleaved": true, "summaries": true, - "visibility": "summary", + "visibility": "omitted", "continuation": [ "thinking_blocks" ], "notes": [ - "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", - "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." + "Claude Opus 4.7 and newer Opus models require thinking.type = \"adaptive\" to enable thinking explicitly.", + "Manual budget_tokens requests return 400 on Claude Opus 4.7 and newer adaptive-only Opus models.", + "task_budget is separate from thinking control and should not be treated as a thinking budget." ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-03-31", - "release_date": "2025-08-05", - "last_updated": "2025-08-05", + "knowledge": "2026-01-31", + "release_date": "2026-04-16", + "last_updated": "2026-04-16", "type": "chat" }, { @@ -121195,6 +120547,48 @@ "last_updated": "2026-05-28", "type": "chat" }, + { + "id": "anthropic/claude-opus-5.5", + "name": "Claude Opus 5.5", + "display_name": "Claude Opus 5.5", + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1000000, + "output": 128000 + }, + "temperature": false, + "tool_call": true, + "reasoning": { + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" + ] + } + }, + "attachment": true, + "open_weights": false, + "knowledge": "2026-06", + "release_date": "2026-09-22", + "last_updated": "2026-09-22", + "type": "chat" + }, { "id": "anthropic/claude-sonnet-4.6", "name": "Claude Sonnet 4.6", @@ -121211,7 +120605,7 @@ }, "limit": { "context": 1000000, - "output": 64000 + "output": 128000 }, "temperature": true, "tool_call": true, @@ -121255,55 +120649,24 @@ "type": "chat" }, { - "id": "z-ai/glm-5.1", - "name": "GLM-5.1", - "display_name": "GLM-5.1", - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 200000, - "output": 131072 - }, - "temperature": true, - "tool_call": true, - "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true - } - }, - "attachment": false, - "open_weights": true, - "release_date": "2026-04-07", - "last_updated": "2026-04-07", - "type": "chat" - }, - { - "id": "z-ai/glm-5", - "name": "GLM-5", - "display_name": "GLM-5", + "id": "anthropic/claude-opus-5", + "name": "Claude Opus 5", + "display_name": "Claude Opus 5", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 204800, - "output": 131072 + "context": 1000000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -121312,18 +120675,20 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" ] } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-02-12", - "last_updated": "2026-02-12", + "attachment": true, + "open_weights": false, + "knowledge": "2026-05", + "release_date": "2026-07-24", + "last_updated": "2026-07-24", "type": "chat" }, { @@ -121339,7 +120704,7 @@ ] }, "limit": { - "context": 1000000, + "context": 131072, "output": 384000 }, "temperature": true, @@ -121378,86 +120743,9 @@ "type": "chat" }, { - "id": "google/veo3.1-fast", - "name": "Veo 3.1 Fast", - "display_name": "Veo 3.1 Fast", - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "video" - ] - }, - "limit": { - "context": 400000, - "output": 8192 - }, - "temperature": false, - "tool_call": false, - "reasoning": { - "supported": false - }, - "attachment": true, - "open_weights": false, - "release_date": "2025-10-15", - "last_updated": "2026-01-01" - }, - { - "id": "google/gemini-2.5-pro", - "name": "Gemini 2.5 Pro", - "display_name": "Gemini 2.5 Pro", - "modalities": { - "input": [ - "text", - "image", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 1048576, - "output": 65536 - }, - "temperature": true, - "tool_call": true, - "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "budget", - "budget": { - "default": -1, - "min": 128, - "max": 32768, - "auto": -1, - "unit": "tokens" - }, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thought_signatures" - ] - } - }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-01", - "release_date": "2025-06-17", - "last_updated": "2025-06-17", - "type": "chat" - }, - { - "id": "google/gemma-4-31b-it", - "name": "Gemma 4 31B IT", - "display_name": "Gemma 4 31B IT", + "id": "unbiased/pareto", + "name": "Pareto", + "display_name": "Pareto", "modalities": { "input": [ "text", @@ -121469,173 +120757,40 @@ }, "limit": { "context": 262144, - "output": 32768 + "output": 131072 }, "temperature": true, "tool_call": true, - "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true - } - }, - "attachment": true, - "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-04-02", - "last_updated": "2026-04-02", - "type": "chat" - }, - { - "id": "google/veo3.1-lite", - "name": "Veo 3.1 Lite", - "display_name": "Veo 3.1 Lite", - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "video" - ] - }, - "limit": { - "context": 400000, - "output": 8192 - }, - "temperature": false, - "tool_call": false, "reasoning": { "supported": false }, "attachment": true, "open_weights": false, - "release_date": "2026-03-31", - "last_updated": "2026-03-31" - }, - { - "id": "google/gemini-3-pro-image-preview", - "name": "Nano Banana Pro Preview", - "display_name": "Nano Banana Pro Preview", - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text", - "image" - ] - }, - "limit": { - "context": 65536, - "output": 32768 - }, - "temperature": true, - "tool_call": false, - "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "level", - "level": "high", - "level_options": [ - "low", - "high" - ], - "summaries": true, - "visibility": "summary", - "continuation": [ - "thought_signatures" - ] - } - }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-01", - "release_date": "2025-11-20", - "last_updated": "2025-11-20" - }, - { - "id": "google/gemini-2.5-flash", - "name": "Gemini 2.5 Flash", - "display_name": "Gemini 2.5 Flash", - "modalities": { - "input": [ - "text", - "image", - "audio", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 1048576, - "output": 65536 - }, - "temperature": true, - "tool_call": true, - "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "budget", - "budget": { - "default": -1, - "min": 0, - "max": 24576, - "auto": -1, - "off": 0, - "unit": "tokens" - }, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thought_signatures" - ] - } - }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-01", - "release_date": "2025-06-17", - "last_updated": "2025-06-17", + "release_date": "2026-09-17", + "last_updated": "2026-09-17", "type": "chat" }, { - "id": "google/gemini-3.1-flash-image-preview", - "name": "Nano Banana 2 Preview", - "display_name": "Nano Banana 2 Preview", + "id": "meta/muse-spark-1.1", + "name": "Muse Spark 1.1", + "display_name": "Muse Spark 1.1", "modalities": { "input": [ "text", "image", - "pdf" + "pdf", + "video" ], "output": [ - "text", - "image" + "text" ] }, "limit": { - "context": 65536, - "output": 65536 + "context": 1048576, + "output": 131072 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { "supported": true, "default": true @@ -121644,37 +120799,38 @@ "reasoning": { "supported": true, "effort_options": [ + "minimal", "low", "medium", - "high" + "high", + "xhigh" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-01", - "release_date": "2026-02-26", - "last_updated": "2026-02-26" + "release_date": "2026-04-08", + "last_updated": "2026-07-09", + "type": "chat" }, { - "id": "google/gemini-3.1-pro-preview", - "name": "Gemini 3.1 Pro Preview", - "display_name": "Gemini 3.1 Pro Preview", + "id": "meta/muse-spark-open", + "name": "Muse Spark Open", + "display_name": "Muse Spark Open", "modalities": { "input": [ "text", "image", "video", - "audio", - "pdf" + "audio" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 1040000, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -121684,66 +120840,76 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "level", - "level": "high", - "level_options": [ + "effort_options": [ "low", - "high" - ], - "summaries": true, - "visibility": "summary", - "continuation": [ - "thought_signatures" + "medium", + "high", + "xhigh" ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2025-01", - "release_date": "2026-02-19", - "last_updated": "2026-02-19", + "open_weights": true, + "knowledge": "2026-01-04", + "release_date": "2026-10-15", + "last_updated": "2026-10-15", "type": "chat" }, { - "id": "google/veo3.1", - "name": "Veo 3.1", - "display_name": "Veo 3.1", + "id": "meta/muse-wonder-1.0", + "name": "Muse Wonder", + "display_name": "Muse Wonder", "modalities": { "input": [ "text", - "image" + "image", + "video", + "pdf", + "audio" ], "output": [ - "video" + "text" ] }, "limit": { - "context": 400000, - "output": 8192 + "context": 1048576, + "output": 131072 }, - "temperature": false, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "minimal", + "low", + "medium", + "high", + "xhigh", + "max" + ] + } }, "attachment": true, "open_weights": false, - "release_date": "2025-10-15", - "last_updated": "2026-01" + "release_date": "2026-10-12", + "last_updated": "2026-10-12", + "type": "chat" }, { - "id": "google/gemini-3.5-flash", - "name": "Gemini 3.5 Flash", - "display_name": "Gemini 3.5 Flash", + "id": "meta/muse-spark-1.2", + "name": "Muse Spark 1.2", + "display_name": "Muse Spark 1.2", "modalities": { "input": [ "text", "image", "video", - "audio", - "pdf" + "pdf", + "audio" ], "output": [ "text" @@ -121751,7 +120917,7 @@ }, "limit": { "context": 1048576, - "output": 65536 + "output": 131072 }, "temperature": true, "tool_call": true, @@ -121762,74 +120928,42 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "level", - "level": "high", - "level_options": [ + "effort_options": [ "minimal", "low", "medium", - "high" - ], - "summaries": true, - "visibility": "summary", - "continuation": [ - "thought_signatures" + "high", + "xhigh" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-01", - "release_date": "2026-05-19", - "last_updated": "2026-05-19", + "release_date": "2026-08-05", + "last_updated": "2026-08-05", "type": "chat" }, { - "id": "wanx/wan-v2-6", - "name": "Wan 2.6", - "display_name": "Wan 2.6", - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "video" - ] - }, - "limit": { - "context": 400000, - "output": 8192 - }, - "temperature": false, - "tool_call": false, - "reasoning": { - "supported": false - }, - "attachment": true, - "open_weights": false, - "release_date": "2025-12-03", - "last_updated": "2025-12-03" - }, - { - "id": "openai/gpt-5", - "name": "GPT-5", - "display_name": "GPT-5", + "id": "meta/muse-spark-1.3", + "name": "Muse Spark 1.3", + "display_name": "Muse Spark 1.3", "modalities": { "input": [ "text", - "image" + "image", + "video", + "pdf", + "audio" ], "output": [ "text" ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 1048576, + "output": 131072 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -121838,50 +120972,48 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", "effort_options": [ "minimal", "low", "medium", - "high" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + "high", + "xhigh", + "max" + ] } }, "attachment": true, "open_weights": false, - "knowledge": "2024-10-01", - "release_date": "2025-08-07", - "last_updated": "2025-08-07", + "release_date": "2026-09-02", + "last_updated": "2026-09-02", "type": "chat" - }, + } + ] + }, + "aki-io": { + "id": "aki-io", + "name": "AKI.IO", + "display_name": "AKI.IO", + "api": "https://aki.io/v1", + "doc": "https://aki.io/docs/", + "models": [ { - "id": "openai/gpt-5.5", - "name": "GPT-5.5", - "display_name": "GPT-5.5", + "id": "mistral4-119b", + "name": "Mistral Small 4", + "display_name": "Mistral Small 4", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 262144, + "output": 81920 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -121889,63 +121021,60 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", "effort_options": [ - "low", - "medium", - "high", - "xhigh" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", + "none", "high" - ], - "visibility": "hidden" + ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-12-01", - "release_date": "2026-04-23", - "last_updated": "2026-04-23", + "attachment": false, + "open_weights": true, + "knowledge": "2025-06", + "release_date": "2026-03-16", + "last_updated": "2026-03-16", "type": "chat" }, { - "id": "openai/gpt-image-2", - "name": "GPT Image 2", - "display_name": "GPT Image 2", + "id": "glm5.3-754b", + "name": "GLM-5.3", + "display_name": "GLM-5.3", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ - "image" + "text" ] }, "limit": { - "context": 128000, - "output": 8192 + "context": 524288, + "output": 81920 }, - "temperature": false, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": true, - "open_weights": false, - "release_date": "2026-04-21", - "last_updated": "2026-04-21" + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "low", + "high", + "max" + ] + } + }, + "attachment": false, + "open_weights": true, + "release_date": "2026-08-14", + "last_updated": "2026-08-14", + "type": "chat" }, { - "id": "openai/gpt-oss-20b", - "name": "GPT OSS 20B", - "display_name": "GPT OSS 20B", + "id": "gpt-oss-120b", + "name": "GPT OSS 120B", + "display_name": "GPT OSS 120B", "modalities": { "input": [ "text" @@ -121955,7 +121084,7 @@ ] }, "limit": { - "context": 131072, + "context": 128000, "output": 32768 }, "temperature": true, @@ -121982,9 +121111,9 @@ "type": "chat" }, { - "id": "openai/gpt-oss-120b", - "name": "GPT OSS 120B", - "display_name": "GPT OSS 120B", + "id": "qwen3.8-27b", + "name": "Qwen3.8 27B", + "display_name": "Qwen3.8 27B", "modalities": { "input": [ "text" @@ -121994,7 +121123,7 @@ ] }, "limit": { - "context": 131072, + "context": 262144, "output": 32768 }, "temperature": true, @@ -122005,39 +121134,37 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, "effort_options": [ + "none", "low", "medium", - "high" + "xhigh" ] } }, "attachment": false, "open_weights": true, - "knowledge": "2024-06", - "release_date": "2025-08-05", - "last_updated": "2025-08-05", + "release_date": "2026-08-14", + "last_updated": "2026-08-14", "type": "chat" }, { - "id": "openai/gpt-5-mini", - "name": "GPT-5 Mini", - "display_name": "GPT-5 Mini", + "id": "gemma4-26b", + "name": "Gemma 4 26B A4B IT", + "display_name": "Gemma 4 26B A4B IT", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 256000, + "output": 32768 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -122045,101 +121172,73 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", "effort_options": [ - "minimal", - "low", - "medium", - "high" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", + "none", "high" - ], - "visibility": "hidden" + ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2024-10-01", - "release_date": "2025-08-07", - "last_updated": "2025-08-07", + "attachment": false, + "open_weights": true, + "release_date": "2026-04-02", + "last_updated": "2026-04-02", "type": "chat" }, { - "id": "openai/gpt-5.4-mini", - "name": "GPT-5.4 mini", - "display_name": "GPT-5.4 mini", + "id": "deepseek-v4.1-flash-552b", + "name": "DeepSeek V4.1 Flash", + "display_name": "DeepSeek V4.1 Flash", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 1048576, + "output": 81920 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "none", "effort_options": [ "none", "low", "medium", "high", - "xhigh" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + "max" + ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-03-17", - "last_updated": "2026-03-17", + "attachment": false, + "open_weights": true, + "knowledge": "2025-05", + "release_date": "2026-09-10", + "last_updated": "2026-09-10", "type": "chat" }, { - "id": "openai/gpt-5.3-codex", - "name": "GPT-5.3 Codex", - "display_name": "GPT-5.3 Codex", + "id": "qwen3.6-35b", + "name": "Qwen3.6 35B-A3B", + "display_name": "Qwen3.6 35B-A3B", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 256000, + "output": 32768 }, "temperature": true, "tool_call": true, @@ -122150,35 +121249,37 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", - "effort_options": [ - "low", - "medium", - "high", - "xhigh" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", + "effort_options": [ + "none", "high" - ], - "visibility": "hidden" + ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-02-05", - "last_updated": "2026-02-05", + "attachment": false, + "open_weights": true, + "release_date": "2026-04-17", + "last_updated": "2026-04-17", "type": "chat" - }, + } + ] + }, + "wally": { + "id": "wally", + "name": "Wally", + "display_name": "Wally", + "api": "https://inference.runanywhere.ai/v1", + "doc": "https://docs.runanywhere.ai/wally-cloud/api", + "models": [ { - "id": "openai/gpt-5.4-nano", - "name": "GPT-5.4 nano", - "display_name": "GPT-5.4 nano", + "id": "kimi-k3", + "name": "Kimi K3", + "display_name": "Kimi K3", "modalities": { "input": [ "text", @@ -122189,63 +121290,55 @@ ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 1048576, + "output": 65536 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "none", + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ "none", "low", - "medium", "high", - "xhigh" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + "max" + ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-03-17", - "last_updated": "2026-03-17", + "open_weights": true, + "release_date": "2026-07-16", + "last_updated": "2026-07-16", "type": "chat" }, { - "id": "openai/gpt-5.5-pro", - "name": "GPT-5.5 Pro", - "display_name": "GPT-5.5 Pro", + "id": "glm-5.3-flash", + "name": "GLM-5.3-Flash", + "display_name": "GLM-5.3-Flash", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 1048576, + "output": 131072 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -122254,24 +121347,29 @@ "extra_capabilities": { "reasoning": { "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ "low", - "medium", - "high" + "high", + "max" ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-12-01", - "release_date": "2026-04-23", - "last_updated": "2026-04-23", + "attachment": false, + "open_weights": true, + "release_date": "2026-08-26", + "last_updated": "2026-08-26", "type": "chat" }, { - "id": "openai/gpt-4.1", - "name": "GPT-4.1", - "display_name": "GPT-4.1", + "id": "deepseek-v4.1-flash-fast", + "name": "DeepSeek V4.1 Flash Fast", + "display_name": "DeepSeek V4.1 Flash Fast", "modalities": { "input": [ "text", @@ -122282,25 +121380,43 @@ ] }, "limit": { - "context": 1047576, - "output": 32768 + "context": 1048576, + "output": 65536 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "effort_options": [ + "none", + "low", + "high", + "max" + ] + } }, "attachment": true, - "open_weights": false, - "knowledge": "2024-04", - "release_date": "2025-04-14", - "last_updated": "2025-04-14", + "open_weights": true, + "knowledge": "2025-05", + "release_date": "2026-09-10", + "last_updated": "2026-09-10", "type": "chat" }, { - "id": "openai/gpt-5-nano", - "name": "GPT-5 Nano", - "display_name": "GPT-5 Nano", + "id": "deepseek-v4.1-flash", + "name": "DeepSeek V4.1 Flash", + "display_name": "DeepSeek V4.1 Flash", "modalities": { "input": [ "text", @@ -122311,10 +121427,10 @@ ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 1048576, + "output": 65536 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -122323,35 +121439,31 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", - "effort_options": [ - "minimal", - "low", - "medium", - "high" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ], - "verbosity": "medium", - "verbosity_options": [ + "effort_options": [ + "none", "low", - "medium", - "high" - ], - "visibility": "hidden" + "high", + "max" + ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2024-10-01", - "release_date": "2025-08-07", - "last_updated": "2025-08-07", + "open_weights": true, + "knowledge": "2025-05", + "release_date": "2026-09-10", + "last_updated": "2026-09-10", "type": "chat" }, { - "id": "sarvam/sarvam-105b", - "name": "Sarvam 105B", - "display_name": "Sarvam 105B", + "id": "glm-5.3-fast", + "name": "GLM-5.3 Fast", + "display_name": "GLM-5.3 Fast", "modalities": { "input": [ "text" @@ -122361,7 +121473,7 @@ ] }, "limit": { - "context": 131072, + "context": 1048576, "output": 131072 }, "temperature": true, @@ -122370,29 +121482,43 @@ "supported": true, "default": true }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "effort_options": [ + "low", + "high", + "max" + ] + } + }, "attachment": false, "open_weights": true, - "release_date": "2026-02-18", - "last_updated": "2026-02-18", + "release_date": "2026-08-14", + "last_updated": "2026-08-14", "type": "chat" }, { - "id": "moonshotai/kimi-k2.6", - "name": "Kimi K2.6", - "display_name": "Kimi K2.6", + "id": "minimax-m3", + "name": "MiniMax-M3", + "display_name": "MiniMax-M3", "modalities": { "input": [ - "text", - "image", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 524287, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -122402,34 +121528,28 @@ }, "extra_capabilities": { "reasoning": { - "supported": true + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] } }, - "attachment": true, + "attachment": false, "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-04-20", - "last_updated": "2026-04-20", + "release_date": "2026-06-01", + "last_updated": "2026-06-01", "type": "chat" - } - ] - }, - "munito-eu": { - "id": "munito-eu", - "name": "Munito AI (EU)", - "display_name": "Munito AI (EU)", - "api": "https://api.eu.munito.ai/inference/v1", - "doc": "https://docs.munito.ai/regions", - "models": [ + }, { - "id": "zai-org/glm-5.3-flash", - "name": "GLM-5.3-Flash", - "display_name": "GLM-5.3-Flash", + "id": "deepseek-v4-flash-0731", + "name": "DeepSeek V4 Flash 0731", + "display_name": "DeepSeek V4 Flash 0731", "modalities": { "input": [ - "text", - "image", - "video" + "text" ], "output": [ "text" @@ -122437,7 +121557,7 @@ }, "limit": { "context": 1048576, - "output": 943718 + "output": 65536 }, "temperature": true, "tool_call": true, @@ -122455,22 +121575,24 @@ "thinking_blocks" ], "effort_options": [ + "none", "low", "high", "max" ] } }, - "attachment": true, + "attachment": false, "open_weights": true, - "release_date": "2026-08-26", - "last_updated": "2026-08-26", + "knowledge": "2025-05", + "release_date": "2026-07-31", + "last_updated": "2026-07-31", "type": "chat" }, { - "id": "aleph-alpha/kolibri-1", - "name": "Kolibri 1", - "display_name": "Kolibri 1", + "id": "deepseek-v4-pro-fast", + "name": "DeepSeek V4 Pro Fast", + "display_name": "DeepSeek V4 Pro Fast", "modalities": { "input": [ "text" @@ -122481,7 +121603,7 @@ }, "limit": { "context": 1048576, - "output": 943718 + "output": 65536 }, "temperature": true, "tool_call": true, @@ -122500,28 +121622,68 @@ ], "effort_options": [ "none", - "low", - "medium", - "high" + "high", + "max" ] } }, "attachment": false, "open_weights": true, - "knowledge": "2026-06-18", - "release_date": "2026-10-03", - "last_updated": "2026-10-03", + "knowledge": "2025-05", + "release_date": "2026-04-24", + "last_updated": "2026-04-24", + "type": "chat" + } + ] + }, + "submodel": { + "id": "submodel", + "name": "submodel", + "display_name": "submodel", + "api": "https://llm.submodel.ai/v1", + "doc": "https://submodel.gitbook.io", + "models": [ + { + "id": "openai/gpt-oss-120b", + "name": "GPT OSS 120B", + "display_name": "GPT OSS 120B", + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "output": 32768 + }, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true + } + }, + "attachment": false, + "open_weights": true, + "knowledge": "2024-06", + "release_date": "2025-08-05", + "last_updated": "2025-08-05", "type": "chat" }, { - "id": "qwen/qwen3.8-27b", - "name": "Qwen3.8 27B", - "display_name": "Qwen3.8 27B", + "id": "Qwen/Qwen3-235B-A22B-Instruct-2507", + "name": "Qwen3 235B A22B Instruct 2507", + "display_name": "Qwen3 235B A22B Instruct 2507", "modalities": { "input": [ - "text", - "image", - "video" + "text" ], "output": [ "text" @@ -122529,7 +121691,62 @@ }, "limit": { "context": 262144, - "output": 235929 + "output": 131072 + }, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": false + }, + "attachment": false, + "open_weights": true, + "release_date": "2025-07-21", + "last_updated": "2025-07-21", + "type": "chat" + }, + { + "id": "Qwen/Qwen3-Coder-480B-A35B-Instruct-FP8", + "name": "Qwen3 Coder 480B A35B Instruct", + "display_name": "Qwen3 Coder 480B A35B Instruct", + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 262144, + "output": 262144 + }, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": false + }, + "attachment": false, + "open_weights": true, + "knowledge": "2025-04", + "release_date": "2025-07-22", + "last_updated": "2025-07-22", + "type": "chat" + }, + { + "id": "Qwen/Qwen3-235B-A22B-Thinking-2507", + "name": "Qwen3 235B A22B Thinking 2507", + "display_name": "Qwen3 235B A22B Thinking 2507", + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 262144, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -122545,25 +121762,19 @@ "visibility": "summary", "continuation": [ "thinking_blocks" - ], - "effort_options": [ - "none", - "low", - "medium", - "xhigh" ] } }, - "attachment": true, + "attachment": false, "open_weights": true, - "release_date": "2026-08-14", - "last_updated": "2026-08-14", + "release_date": "2025-07-25", + "last_updated": "2025-07-25", "type": "chat" }, { - "id": "deepseek-ai/deepseek-v4-flash", - "name": "DeepSeek V4 Flash 0731", - "display_name": "DeepSeek V4 Flash 0731", + "id": "zai-org/GLM-4.5-Air", + "name": "GLM 4.5 Air", + "display_name": "GLM 4.5 Air", "modalities": { "input": [ "text" @@ -122573,8 +121784,65 @@ ] }, "limit": { - "context": 1048576, - "output": 943718 + "context": 131072, + "output": 131072 + }, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": false + }, + "attachment": false, + "open_weights": true, + "knowledge": "2025-04", + "release_date": "2025-07-28", + "last_updated": "2025-07-28", + "type": "chat" + }, + { + "id": "zai-org/GLM-4.5-FP8", + "name": "GLM 4.5 FP8", + "display_name": "GLM 4.5 FP8", + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "output": 131072 + }, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": true, + "default": true + }, + "attachment": false, + "open_weights": true, + "knowledge": "2025-04", + "release_date": "2025-07-28", + "last_updated": "2025-07-28", + "type": "chat" + }, + { + "id": "deepseek-ai/DeepSeek-R1-0528", + "name": "DeepSeek R1 0528", + "display_name": "DeepSeek R1 0528", + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 75000, + "output": 163840 }, "temperature": true, "tool_call": true, @@ -122585,45 +121853,87 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "high", - "effort_options": [ - "low", - "high", - "max" - ], "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ "thinking_blocks" - ], - "notes": [ - "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." ] } }, "attachment": false, "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-07-31", - "last_updated": "2026-07-31", + "release_date": "2025-05-28", + "last_updated": "2025-05-28", + "type": "chat" + }, + { + "id": "deepseek-ai/DeepSeek-V3-0324", + "name": "DeepSeek V3 0324", + "display_name": "DeepSeek V3 0324", + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 75000, + "output": 163840 + }, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": false + }, + "attachment": false, + "open_weights": true, + "release_date": "2025-03-24", + "last_updated": "2025-03-24", + "type": "chat" + }, + { + "id": "deepseek-ai/DeepSeek-V3.1", + "name": "DeepSeek V3.1", + "display_name": "DeepSeek V3.1", + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 75000, + "output": 163840 + }, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": true, + "default": true + }, + "attachment": false, + "open_weights": true, + "release_date": "2025-08-21", + "last_updated": "2025-08-21", "type": "chat" } ] }, - "opencode-go": { - "id": "opencode-go", - "name": "OpenCode Go", - "display_name": "OpenCode Go", - "api": "https://opencode.ai/inference/go/openai/v1", - "doc": "https://opencode.ai/v2/docs/console/go/", + "cohere": { + "id": "cohere", + "name": "Cohere", + "display_name": "Cohere", + "doc": "https://docs.cohere.com/docs/models", "models": [ { - "id": "hy4-preview", - "name": "Hy4 preview", - "display_name": "Hy4 preview", + "id": "command-r-08-2024", + "name": "Command R", + "display_name": "Command R", "modalities": { "input": [ "text" @@ -122633,34 +121943,25 @@ ] }, "limit": { - "context": 1024000, - "output": 64000 + "context": 128000, + "output": 4000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "effort_options": [ - "none", - "high" - ] - } + "supported": false }, "attachment": false, "open_weights": true, - "release_date": "2026-08-28", - "last_updated": "2026-08-28", + "knowledge": "2024-06-01", + "release_date": "2024-08-30", + "last_updated": "2024-08-30", "type": "chat" - }, - { - "id": "longcat-2.0", - "name": "LongCat-2.0", - "display_name": "LongCat-2.0", + }, + { + "id": "north-mini-code-1-0", + "name": "North Mini Code", + "display_name": "North Mini Code", "modalities": { "input": [ "text" @@ -122670,8 +121971,8 @@ ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 256000, + "output": 64000 }, "temperature": true, "tool_call": true, @@ -122687,282 +121988,189 @@ "visibility": "summary", "continuation": [ "thinking_blocks" + ], + "effort_options": [ + "none", + "high" ] } }, "attachment": false, "open_weights": true, - "release_date": "2026-06-30", - "last_updated": "2026-06-30", + "knowledge": "2025-09-23", + "release_date": "2026-06-09", + "last_updated": "2026-06-09", "type": "chat" }, { - "id": "muse-spark-1.3-contributor", - "name": "Muse Spark 1.3 Contributor", - "display_name": "Muse Spark 1.3 Contributor", + "id": "command-r-plus-08-2024", + "name": "Command R+", + "display_name": "Command R+", "modalities": { "input": [ - "text", - "image", - "video", - "pdf", - "audio" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 128000, + "output": 4000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "minimal", - "low", - "medium", - "high", - "xhigh" - ] - } + "supported": false }, - "attachment": true, - "open_weights": false, - "release_date": "2026-09-02", - "last_updated": "2026-09-02", + "attachment": false, + "open_weights": true, + "knowledge": "2024-06-01", + "release_date": "2024-08-30", + "last_updated": "2024-08-30", "type": "chat" }, { - "id": "glm-5.3-flash", - "name": "GLM-5.3-Flash", - "display_name": "GLM-5.3-Flash", + "id": "command-r7b-12-2024", + "name": "Command R7B", + "display_name": "Command R7B", "modalities": { "input": [ - "text", - "image", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 128000, + "output": 4000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "effort_options": [ - "low", - "high", - "max" - ] - } + "supported": false }, - "attachment": true, + "attachment": false, "open_weights": true, - "release_date": "2026-08-26", - "last_updated": "2026-08-26", + "knowledge": "2024-06-01", + "release_date": "2024-12-13", + "last_updated": "2024-12-13", "type": "chat" }, { - "id": "mimo-v2.6-pro", - "name": "MiMo-V2.6-Pro", - "display_name": "MiMo-V2.6-Pro", + "id": "c4ai-aya-expanse-32b", + "name": "Aya Expanse 32B", + "display_name": "Aya Expanse 32B", "modalities": { "input": [ - "text", - "image", - "audio", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 128000, + "output": 4000 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } + "supported": false }, - "attachment": true, + "attachment": false, "open_weights": true, - "release_date": "2026-09-22", - "last_updated": "2026-09-22", + "release_date": "2024-10-24", + "last_updated": "2024-10-24", "type": "chat" }, { - "id": "claude-haiku-5-5", - "name": "Claude Haiku 5.5", - "display_name": "Claude Haiku 5.5", + "id": "north-small-translate-09-2026", + "name": "North Small Translate", + "display_name": "North Small Translate", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 32768, + "output": 16000 }, - "temperature": false, - "tool_call": true, + "temperature": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" - ] - } + "supported": false }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-06", - "release_date": "2026-10-07", - "last_updated": "2026-10-07", + "attachment": false, + "open_weights": true, + "release_date": "2026-09-09", + "last_updated": "2026-09-09", "type": "chat" }, { - "id": "kimi-k3", - "name": "Kimi K3", - "display_name": "Kimi K3", + "id": "tiny-aya-global", + "name": "Tiny Aya Global", + "display_name": "Tiny Aya Global", "modalities": { "input": [ - "text", - "image", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 8000, + "output": 8000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "effort_options": [ - "max" - ] - } + "supported": false }, - "attachment": true, + "attachment": false, "open_weights": true, - "release_date": "2026-07-16", - "last_updated": "2026-07-16", + "release_date": "2026-02-17", + "last_updated": "2026-02-17", "type": "chat" }, { - "id": "step-5-preview-free", - "name": "Step 5 Preview Free", - "display_name": "Step 5 Preview Free", + "id": "command-a-03-2025", + "name": "Command A", + "display_name": "Command A", "modalities": { "input": [ - "text", - "image", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 65536 + "context": 256000, + "output": 8000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "effort_options": [ - "low", - "medium", - "high" - ] - } + "supported": false }, - "attachment": true, + "attachment": false, "open_weights": true, - "release_date": "2026-09-16", - "last_updated": "2026-09-20", + "knowledge": "2024-06-01", + "release_date": "2025-03-13", + "last_updated": "2025-03-13", "type": "chat" }, { - "id": "deepseek-v4-pro", - "name": "DeepSeek V4 Pro (New)", - "display_name": "DeepSeek V4 Pro (New)", + "id": "tiny-aya-fire", + "name": "Tiny Aya Fire", + "display_name": "Tiny Aya Fire", "modalities": { "input": [ "text" @@ -122972,102 +122180,65 @@ ] }, "limit": { - "context": 1000000, - "output": 384000 + "context": 8000, + "output": 8000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "high", - "effort_options": [ - "low", - "high", - "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." - ] - } + "supported": false }, "attachment": false, "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-04-24", - "last_updated": "2026-04-24", + "release_date": "2026-02-17", + "last_updated": "2026-02-17", "type": "chat" }, { - "id": "longcat-2.5-preview-free", - "name": "LongCat 2.5 Preview Free", - "display_name": "LongCat 2.5 Preview Free", + "id": "command-a-reasoning-08-2025", + "name": "Command A Reasoning", + "display_name": "Command A Reasoning", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 256000, + "output": 32000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } - }, - "attachment": true, - "open_weights": false, - "release_date": "2026-09-25", - "last_updated": "2026-09-25", + "attachment": false, + "open_weights": true, + "knowledge": "2024-06-01", + "release_date": "2025-08-21", + "last_updated": "2025-08-21", "type": "chat" }, { - "id": "mimo-v2.6-flash", - "name": "MiMo-V2.6-Flash", - "display_name": "MiMo-V2.6-Flash", + "id": "command-a-plus-05-2026", + "name": "Command A Plus", + "display_name": "Command A Plus", "modalities": { "input": [ "text", - "image", - "audio", - "video" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 128000, + "output": 64000 }, "temperature": true, "tool_call": true, @@ -123077,25 +122248,20 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] + "supported": true } }, "attachment": true, "open_weights": true, - "release_date": "2026-09-22", - "last_updated": "2026-09-22", + "knowledge": "2025-04-01", + "release_date": "2026-05-20", + "last_updated": "2026-06-09", "type": "chat" }, { - "id": "beam-free", - "name": "Beam Free", - "display_name": "Beam Free", + "id": "tiny-aya-water", + "name": "Tiny Aya Water", + "display_name": "Tiny Aya Water", "modalities": { "input": [ "text" @@ -123105,174 +122271,109 @@ ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 8000, + "output": 8000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" - ] - } + "supported": false }, "attachment": false, "open_weights": true, - "knowledge": "2026-06-30", - "release_date": "2026-10-05", - "last_updated": "2026-10-16", + "release_date": "2026-02-17", + "last_updated": "2026-02-17", "type": "chat" }, { - "id": "minimax-m3", - "name": "MiniMax-M3", - "display_name": "MiniMax-M3", + "id": "c4ai-aya-vision-32b", + "name": "Aya Vision 32B", + "display_name": "Aya Vision 32B", "modalities": { "input": [ "text", - "image", - "video" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 16000, + "output": 4000 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true - } + "supported": false }, "attachment": true, "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-06-01", - "last_updated": "2026-06-01", + "release_date": "2025-03-04", + "last_updated": "2025-05-14", "type": "chat" }, { - "id": "kimi-k3.1-pro", - "name": "Kimi K3.1 Pro", - "display_name": "Kimi K3.1 Pro", + "id": "command-a-translate-08-2025", + "name": "Command A Translate", + "display_name": "Command A Translate", "modalities": { "input": [ - "text", - "image", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 8000, + "output": 8000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "effort_options": [ - "low", - "high", - "max" - ] - } + "supported": false }, - "attachment": true, - "open_weights": false, - "release_date": "2026-10-23", - "last_updated": "2026-10-23", + "attachment": false, + "open_weights": true, + "knowledge": "2024-06-01", + "release_date": "2025-08-28", + "last_updated": "2025-08-28", "type": "chat" }, { - "id": "minimax-m3.1-flash", - "name": "MiniMax M3.1 Flash", - "display_name": "MiniMax M3.1 Flash", + "id": "command-a-vision-07-2025", + "name": "Command A Vision", + "display_name": "Command A Vision", "modalities": { "input": [ "text", - "image", - "video" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 512000 + "context": 128000, + "output": 8000 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" - ] - } + "supported": false }, "attachment": true, - "open_weights": false, - "release_date": "2026-10-19", - "last_updated": "2026-10-19", + "open_weights": true, + "knowledge": "2024-06-01", + "release_date": "2025-07-31", + "last_updated": "2025-07-31", "type": "chat" }, { - "id": "minimax-m2.7", - "name": "MiniMax-M2.7", - "display_name": "MiniMax-M2.7", + "id": "tiny-aya-earth", + "name": "Tiny Aya Earth", + "display_name": "Tiny Aya Earth", "modalities": { "input": [ "text" @@ -123282,93 +122383,75 @@ ] }, "limit": { - "context": 204800, - "output": 131072 + "context": 8000, + "output": 8000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } + "supported": false }, "attachment": false, "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-03-18", - "last_updated": "2026-03-18", + "release_date": "2026-02-17", + "last_updated": "2026-02-17", "type": "chat" }, { - "id": "qwen3.8-max", - "name": "Qwen3.8 Max", - "display_name": "Qwen3.8 Max", + "id": "command-r7b-arabic-02-2025", + "name": "Command R7B Arabic", + "display_name": "Command R7B Arabic", "modalities": { "input": [ - "text", - "image", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 128000, + "output": 4000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "effort_options": [ - "low", - "medium", - "xhigh" - ] - } + "supported": false }, - "attachment": true, - "open_weights": false, - "release_date": "2026-08-03", - "last_updated": "2026-08-03", + "attachment": false, + "open_weights": true, + "knowledge": "2024-06-01", + "release_date": "2025-02-27", + "last_updated": "2025-02-27", "type": "chat" - }, + } + ] + }, + "boundless": { + "id": "boundless", + "name": "Boundless", + "display_name": "Boundless", + "api": "https://api.inference.boundless.network/v1", + "doc": "https://inference.boundless.network/models", + "models": [ { - "id": "mimo-v2.5", - "name": "MiMo V2.5", - "display_name": "MiMo V2.5", + "id": "kimi-k3", + "name": "Kimi K3", + "display_name": "Kimi K3", "modalities": { "input": [ "text", - "image", - "audio", - "video" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 1048576, + "output": 131072 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -123377,30 +122460,27 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "none", + "low", + "high" ] } }, "attachment": true, "open_weights": true, - "knowledge": "2024-12", - "release_date": "2026-04-22", - "last_updated": "2026-04-22", + "release_date": "2026-07-16", + "last_updated": "2026-07-16", "type": "chat" }, { - "id": "gpt-6-luna", - "name": "GPT-6 Luna", - "display_name": "GPT-6 Luna", + "id": "gpt-6-sol", + "name": "GPT-6 Sol", + "display_name": "GPT-6 Sol", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" @@ -123422,23 +122502,21 @@ "none", "low", "medium", - "high", - "xhigh", - "max" + "high" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-05-18", + "knowledge": "2026-04-20", "release_date": "2026-09-22", "last_updated": "2026-09-22", "type": "chat" }, { - "id": "deepseek-v4.1-flash", - "name": "DeepSeek V4.1 Flash", - "display_name": "DeepSeek V4.1 Flash", + "id": "qwen3.6", + "name": "Qwen3.6 35B-A3B", + "display_name": "Qwen3.6 35B-A3B", "modalities": { "input": [ "text", @@ -123449,8 +122527,8 @@ ] }, "limit": { - "context": 1000000, - "output": 384000 + "context": 262144, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -123458,37 +122536,21 @@ "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "effort_options": [ - "low", - "high", - "max" - ] - } - }, "attachment": true, "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-09-10", - "last_updated": "2026-09-10", + "release_date": "2026-04-17", + "last_updated": "2026-04-17", "type": "chat" }, { - "id": "minimax-m3.1-flash-free", - "name": "MiniMax M3.1 Flash Free", - "display_name": "MiniMax M3.1 Flash Free", + "id": "mimo-v2.6-pro", + "name": "MiMo-V2.6-Pro", + "display_name": "MiMo-V2.6-Pro", "modalities": { "input": [ "text", "image", + "audio", "video" ], "output": [ @@ -123496,8 +122558,8 @@ ] }, "limit": { - "context": 1000000, - "output": 512000 + "context": 1048576, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -123505,34 +122567,16 @@ "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" - ] - } - }, "attachment": true, - "open_weights": false, - "release_date": "2026-10-19", - "last_updated": "2026-10-19", + "open_weights": true, + "release_date": "2026-09-22", + "last_updated": "2026-09-22", "type": "chat" }, { - "id": "mimo-v2.5-pro", - "name": "MiMo V2.5 Pro", - "display_name": "MiMo V2.5 Pro", + "id": "claude-fable-5.1", + "name": "Claude Fable 5.1", + "display_name": "Claude Fable 5.1", "modalities": { "input": [ "text" @@ -123542,10 +122586,10 @@ ] }, "limit": { - "context": 1048576, + "context": 262144, "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -123554,38 +122598,52 @@ "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "high", + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" + ], "interleaved": true, "summaries": true, - "visibility": "summary", + "visibility": "omitted", "continuation": [ "thinking_blocks" + ], + "notes": [ + "Adaptive thinking is always on for Claude Fable 5 and Claude Mythos 5; thinking.type = \"disabled\" is rejected.", + "Manual budget_tokens requests return 400 on Claude Fable 5 and Claude Mythos 5.", + "thinking.display defaults to omitted; set display to summarized to receive readable thinking summaries." ] } }, "attachment": false, - "open_weights": true, - "knowledge": "2024-12", - "release_date": "2026-04-22", - "last_updated": "2026-04-22", + "open_weights": false, + "knowledge": "2026-06", + "release_date": "2026-09-01", + "last_updated": "2026-09-01", "type": "chat" }, { - "id": "grok-4.7", - "name": "Grok 4.7", - "display_name": "Grok 4.7", + "id": "qwen3.8-flash", + "name": "Qwen3.8 Flash", + "display_name": "Qwen3.8 Flash", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 500000, - "output": 500000 + "context": 1000000, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -123595,25 +122653,23 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, "effort_options": [ "low", - "medium", - "high", - "xhigh" + "medium" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-05", - "release_date": "2026-09-21", - "last_updated": "2026-09-21", + "release_date": "2026-08-26", + "last_updated": "2026-08-26", "type": "chat" }, { - "id": "hy3", - "name": "Hy3", - "display_name": "Hy3", + "id": "nemotron3-super", + "name": "Nemotron 3 Super 120B A12B", + "display_name": "Nemotron 3 Super 120B A12B", "modalities": { "input": [ "text" @@ -123623,8 +122679,8 @@ ] }, "limit": { - "context": 256000, - "output": 128000 + "context": 262144, + "output": 262144 }, "temperature": true, "tool_call": true, @@ -123634,24 +122690,24 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, "effort_options": [ "none", "low", - "high" + "medium" ] } }, "attachment": false, "open_weights": true, - "release_date": "2026-07-06", - "last_updated": "2026-07-06", + "knowledge": "2025-06", + "release_date": "2026-03-11", + "last_updated": "2026-03-11", "type": "chat" }, { - "id": "kimi-k2.7-code", - "name": "Kimi K2.7 Code", - "display_name": "Kimi K2.7 Code", + "id": "glm-5.3-flash", + "name": "GLM-5.3-Flash", + "display_name": "GLM-5.3-Flash", "modalities": { "input": [ "text", @@ -123663,10 +122719,10 @@ ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 1048576, + "output": 131072 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -123675,19 +122731,47 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "low", + "high" ] } }, "attachment": true, "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-06-12", - "last_updated": "2026-06-12", + "release_date": "2026-08-26", + "last_updated": "2026-08-26", + "type": "chat" + }, + { + "id": "mimo-v2.6-flash", + "name": "MiMo-V2.6-Flash", + "display_name": "MiMo-V2.6-Flash", + "modalities": { + "input": [ + "text", + "image", + "audio", + "video" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1048576, + "output": 131072 + }, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": true, + "default": true + }, + "attachment": true, + "open_weights": true, + "release_date": "2026-09-22", + "last_updated": "2026-09-22", "type": "chat" }, { @@ -123696,16 +122780,14 @@ "display_name": "GPT-5.6 Luna", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1050000, + "context": 262144, "output": 128000 }, "temperature": false, @@ -123737,7 +122819,7 @@ "visibility": "hidden" } }, - "attachment": true, + "attachment": false, "open_weights": false, "knowledge": "2026-02-16", "release_date": "2026-07-09", @@ -123745,22 +122827,21 @@ "type": "chat" }, { - "id": "qwen3.8-flash", - "name": "Qwen3.8 Flash", - "display_name": "Qwen3.8 Flash", + "id": "deepseek-v4.1-flash", + "name": "DeepSeek V4.1 Flash", + "display_name": "DeepSeek V4.1 Flash", "modalities": { "input": [ "text", - "image", - "video" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 1048576, + "output": 384000 }, "temperature": true, "tool_call": true, @@ -123772,16 +122853,17 @@ "reasoning": { "supported": true, "effort_options": [ + "none", "low", - "medium", - "xhigh" + "high" ] } }, "attachment": true, - "open_weights": false, - "release_date": "2026-08-26", - "last_updated": "2026-08-26", + "open_weights": true, + "knowledge": "2025-05", + "release_date": "2026-09-10", + "last_updated": "2026-09-10", "type": "chat" }, { @@ -123797,7 +122879,7 @@ ] }, "limit": { - "context": 1000000, + "context": 1048576, "output": 131072 }, "temperature": true, @@ -123809,16 +122891,9 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], "effort_options": [ "low", - "high", - "max" + "high" ] } }, @@ -123829,111 +122904,22 @@ "type": "chat" }, { - "id": "gemini-4-argon", - "name": "Gemini 4 Argon", - "display_name": "Gemini 4 Argon", - "modalities": { - "input": [ - "text", - "image", - "video", - "audio", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 1048576, - "output": 65536 - }, - "temperature": false, - "tool_call": true, - "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "low", - "medium", - "high" - ] - } - }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-03", - "release_date": "2026-10-13", - "last_updated": "2026-10-13", - "type": "chat" - }, - { - "id": "step-5-preview", - "name": "Step 5 Preview", - "display_name": "Step 5 Preview", + "id": "gpt-5.6-sol", + "name": "GPT-5.6 Sol", + "display_name": "GPT-5.6 Sol", "modalities": { "input": [ - "text", - "image", - "video" - ], - "output": [ "text" - ] - }, - "limit": { - "context": 1000000, - "output": 65536 - }, - "temperature": true, - "tool_call": true, - "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "effort_options": [ - "low", - "medium", - "high" - ] - } - }, - "attachment": true, - "open_weights": true, - "release_date": "2026-09-16", - "last_updated": "2026-09-20", - "type": "chat" - }, - { - "id": "space-bunny", - "name": "Space Bunny", - "display_name": "Space Bunny", - "modalities": { - "input": [ - "text", - "image", - "video" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 524288 + "context": 262144, + "output": 128000 }, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -123942,89 +122928,49 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" - ] - } - }, - "attachment": true, - "open_weights": false, - "release_date": "2026-09-23", - "last_updated": "2026-09-23", - "type": "chat" - }, - { - "id": "muse-spark-1.2-contributor", - "name": "Muse Spark 1.2 Contributor", - "display_name": "Muse Spark 1.2 Contributor", - "modalities": { - "input": [ - "text", - "image", - "video", - "pdf", - "audio" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 1048576, - "output": 131072 - }, - "temperature": true, - "tool_call": true, - "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { + "default_enabled": true, + "mode": "effort", + "effort": "medium", "effort_options": [ - "minimal", + "none", "low", "medium", "high", - "xhigh" - ] + "xhigh", + "max" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, - "attachment": true, + "attachment": false, "open_weights": false, - "release_date": "2026-08-05", - "last_updated": "2026-08-05", + "knowledge": "2026-02-16", + "release_date": "2026-07-09", + "last_updated": "2026-07-09", "type": "chat" }, { - "id": "gemini-4-argon-contributor", - "name": "Gemini 4 Argon Contributor", - "display_name": "Gemini 4 Argon Contributor", + "id": "claude-opus-5.5", + "name": "Claude Opus 5.5", + "display_name": "Claude Opus 5.5", "modalities": { "input": [ "text", - "image", - "video", - "audio", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 1000000, + "output": 128000 }, "temperature": false, "tool_call": true, @@ -124043,15 +122989,15 @@ }, "attachment": true, "open_weights": false, - "knowledge": "2026-03", - "release_date": "2026-10-13", - "last_updated": "2026-10-13", + "knowledge": "2026-06", + "release_date": "2026-09-22", + "last_updated": "2026-09-22", "type": "chat" }, { - "id": "iceman-contributor-free", - "name": "Iceman Contributor Free", - "display_name": "Iceman Contributor Free", + "id": "glm-5.2", + "name": "GLM-5.2", + "display_name": "GLM-5.2", "modalities": { "input": [ "text" @@ -124061,24 +123007,34 @@ ] }, "limit": { - "context": 262144, + "context": 1048576, "output": 131072 }, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, "default": true }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "effort_options": [ + "none", + "high" + ] + } + }, "attachment": false, - "open_weights": false, - "release_date": "2026-10-24", - "last_updated": "2026-10-24", + "open_weights": true, + "release_date": "2026-06-13", + "last_updated": "2026-06-13", "type": "chat" }, { - "id": "cat-jam-free", - "name": "Cat Jam Free", - "display_name": "Cat Jam Free", + "id": "claude-opus-5", + "name": "Claude Opus 5", + "display_name": "Claude Opus 5", "modalities": { "input": [ "text" @@ -124088,10 +123044,10 @@ ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 262144, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -124100,31 +123056,25 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], "effort_options": [ + "none", "low", "medium", - "high", - "xhigh", - "max" + "high" ] } }, "attachment": false, "open_weights": false, - "release_date": "2026-10-12", - "last_updated": "2026-10-12", + "knowledge": "2026-05", + "release_date": "2026-07-24", + "last_updated": "2026-07-24", "type": "chat" }, { - "id": "qwen3.7-plus", - "name": "Qwen3.7 Plus", - "display_name": "Qwen3.7 Plus", + "id": "minimax-m3", + "name": "MiniMax-M3", + "display_name": "MiniMax-M3", "modalities": { "input": [ "text", @@ -124136,8 +123086,8 @@ ] }, "limit": { - "context": 1000000, - "output": 65536 + "context": 1048576, + "output": 512000 }, "temperature": true, "tool_call": true, @@ -124151,16 +123101,15 @@ } }, "attachment": true, - "open_weights": false, - "knowledge": "2025-04", - "release_date": "2026-06-02", - "last_updated": "2026-06-02", + "open_weights": true, + "release_date": "2026-06-01", + "last_updated": "2026-06-01", "type": "chat" }, { - "id": "glm-5.2", - "name": "GLM-5.2", - "display_name": "GLM-5.2", + "id": "deepseek-v4-pro-0813", + "name": "DeepSeek V4 Pro 0813", + "display_name": "DeepSeek V4 Pro 0813", "modalities": { "input": [ "text" @@ -124170,8 +123119,8 @@ ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 1048576, + "output": 384000 }, "temperature": true, "tool_call": true, @@ -124182,48 +123131,34 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], "effort_options": [ - "high", - "max" + "none", + "low", + "high" ] } }, "attachment": false, "open_weights": true, - "release_date": "2026-06-13", - "last_updated": "2026-06-13", + "release_date": "2026-08-13", + "last_updated": "2026-08-22", "type": "chat" - } - ] - }, - "cerebras": { - "id": "cerebras", - "name": "Cerebras", - "display_name": "Cerebras", - "doc": "https://inference-docs.cerebras.ai/models/overview", - "models": [ + }, { - "id": "qwen-3.8-27b", - "name": "Qwen3.8 27B", - "display_name": "Qwen3.8 27B", + "id": "dsv4", + "name": "DeepSeek V4 Flash 0731", + "display_name": "DeepSeek V4 Flash 0731", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 40960 + "context": 1048576, + "output": 384000 }, "temperature": true, "tool_call": true, @@ -124236,34 +123171,35 @@ "effort_options": [ "none", "low", - "medium", "high" ] } }, - "attachment": true, + "attachment": false, "open_weights": true, - "release_date": "2026-08-14", - "last_updated": "2026-08-14", + "knowledge": "2025-05", + "release_date": "2026-07-31", + "last_updated": "2026-07-31", "type": "chat" }, { - "id": "gpt-oss-120b", - "name": "GPT OSS 120B", - "display_name": "GPT OSS 120B", + "id": "gpt-6-luna", + "name": "GPT-6 Luna", + "display_name": "GPT-6 Luna", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 40960 + "context": 1050000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -124271,39 +123207,40 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, "effort_options": [ + "none", "low", "medium", "high" ] } }, - "attachment": false, - "open_weights": true, - "knowledge": "2024-06", - "release_date": "2025-08-05", - "last_updated": "2025-08-05", + "attachment": true, + "open_weights": false, + "knowledge": "2026-05-18", + "release_date": "2026-09-22", + "last_updated": "2026-09-22", "type": "chat" } ] }, - "volcengine-coding-plan": { - "id": "volcengine-coding-plan", - "name": "Volcengine Ark Coding Plan", - "display_name": "Volcengine Ark Coding Plan", - "api": "https://ark.cn-beijing.volces.com/api/coding/v3", - "doc": "https://www.volcengine.com/docs/82379/1928261", + "302ai": { + "id": "302ai", + "name": "302.AI", + "display_name": "302.AI", + "api": "https://api.302.ai/v1", + "doc": "https://doc.302.ai", "models": [ { - "id": "glm-5.3-flash", - "name": "GLM-5.3-Flash", - "display_name": "GLM-5.3-Flash", + "id": "gemini-3.5-flash", + "name": "Gemini 3.5 Flash", + "display_name": "Gemini 3.5 Flash", "modalities": { "input": [ "text", "image", "video", + "audio", "pdf" ], "output": [ @@ -124311,8 +123248,8 @@ ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 1048576, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -124323,42 +123260,47 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, + "default_enabled": true, + "mode": "level", + "level": "high", + "level_options": [ + "minimal", + "low", + "medium", + "high" + ], "summaries": true, "visibility": "summary", "continuation": [ - "thinking_blocks" - ], - "effort_options": [ - "low", - "high", - "max" + "thought_signatures" ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-08-26", - "last_updated": "2026-08-26", + "open_weights": false, + "knowledge": "2025-01", + "release_date": "2026-05-19", + "last_updated": "2026-05-19", "type": "chat" }, { - "id": "deepseek-v4-flash", - "name": "DeepSeek V4 Flash", - "display_name": "DeepSeek V4 Flash", + "id": "gpt-5-pro", + "name": "gpt-5-pro", + "display_name": "gpt-5-pro", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 384000 + "context": 400000, + "output": 272000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -124368,40 +123310,35 @@ "reasoning": { "supported": true, "default_enabled": true, - "mode": "effort", + "mode": "fixed", "effort": "high", - "effort_options": [ + "verbosity": "medium", + "verbosity_options": [ "low", - "high", - "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "medium", + "high" ], - "notes": [ - "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." - ] + "visibility": "hidden" } }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-04-24", - "last_updated": "2026-04-24", + "attachment": true, + "open_weights": false, + "knowledge": "2024-09-30", + "release_date": "2025-10-06", + "last_updated": "2025-10-06", "type": "chat" }, { - "id": "kimi-k3", - "name": "Kimi K3", - "display_name": "Kimi K3", + "id": "gemini-3.5-flash-thinking", + "name": "gemini-3.5-flash-thinking", + "display_name": "gemini-3.5-flash-thinking", "modalities": { "input": [ "text", "image", - "video" + "video", + "audio", + "pdf" ], "output": [ "text" @@ -124409,9 +123346,9 @@ }, "limit": { "context": 1048576, - "output": 131072 + "output": 65536 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -124420,80 +123357,85 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, + "default_enabled": true, + "mode": "level", + "level": "high", + "level_options": [ + "minimal", + "low", + "medium", + "high" + ], "summaries": true, "visibility": "summary", "continuation": [ - "thinking_blocks" - ], - "effort_options": [ - "low", - "high", - "max" + "thought_signatures" ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-07-16", - "last_updated": "2026-07-16", + "open_weights": false, + "knowledge": "2025-01", + "release_date": "2026-05-19", + "last_updated": "2026-05-19", "type": "chat" }, { - "id": "deepseek-v4-pro", - "name": "DeepSeek V4 Pro", - "display_name": "DeepSeek V4 Pro", + "id": "gpt-5.4-nano", + "name": "gpt-5.4-nano", + "display_name": "gpt-5.4-nano", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 384000 + "context": 400000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, + "default_enabled": false, "mode": "effort", - "effort": "high", + "effort": "none", "effort_options": [ + "none", "low", + "medium", "high", - "max" + "xhigh" ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" ], - "notes": [ - "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." - ] + "visibility": "hidden" } }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-04-24", - "last_updated": "2026-04-24", + "attachment": true, + "open_weights": false, + "knowledge": "2025-08-31", + "release_date": "2026-03-17", + "last_updated": "2026-03-17", "type": "chat" }, { - "id": "minimax-m3", - "name": "MiniMax-M3", - "display_name": "MiniMax-M3", + "id": "kimi-k3", + "name": "Kimi K3", + "display_name": "Kimi K3", "modalities": { "input": [ "text", @@ -124506,9 +123448,9 @@ }, "limit": { "context": 1048576, - "output": 512000 + "output": 131072 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -124522,34 +123464,37 @@ "visibility": "summary", "continuation": [ "thinking_blocks" + ], + "effort_options": [ + "low", + "high", + "max" ] } }, "attachment": true, "open_weights": true, - "release_date": "2026-06-01", - "last_updated": "2026-06-01", + "release_date": "2026-07-16", + "last_updated": "2026-07-16", "type": "chat" }, { - "id": "kimi-k2.7-code", - "name": "Kimi K2.7 Code", - "display_name": "Kimi K2.7 Code", + "id": "MiniMax-M2.5", + "name": "MiniMax-M2.5", + "display_name": "MiniMax-M2.5", "modalities": { "input": [ - "text", - "image", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 204800, + "output": 131072 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -124557,26 +123502,19 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] + "supported": true } }, - "attachment": true, + "attachment": false, "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-06-12", - "last_updated": "2026-06-12", + "release_date": "2026-02-12", + "last_updated": "2026-02-12", "type": "chat" }, { - "id": "glm-5.3", - "name": "GLM-5.3", - "display_name": "GLM-5.3", + "id": "qwen3.7-max-2026-06-08", + "name": "qwen3.7-max-2026-06-08", + "display_name": "qwen3.7-max-2026-06-08", "modalities": { "input": [ "text" @@ -124595,137 +123533,133 @@ "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "effort_options": [ - "low", - "high", - "max" - ] - } - }, "attachment": false, - "open_weights": true, - "release_date": "2026-08-14", - "last_updated": "2026-08-14", + "open_weights": false, + "release_date": "2026-05-21", + "last_updated": "2026-05-21", "type": "chat" }, { - "id": "doubao-seed-2.1-turbo", - "name": "Seed 2.1 Turbo", - "display_name": "Seed 2.1 Turbo", + "id": "gpt-5.4", + "name": "gpt-5.4", + "display_name": "gpt-5.4", "modalities": { "input": [ "text", "image", - "video" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 256000, - "output": 256000 + "context": 1050000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], + "default_enabled": false, + "mode": "effort", + "effort": "none", "effort_options": [ "none", "low", + "medium", + "high", + "xhigh" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", "high" - ] + ], + "visibility": "hidden" } }, "attachment": true, "open_weights": false, - "release_date": "2026-06-23", - "last_updated": "2026-06-23", + "knowledge": "2025-08-31", + "release_date": "2026-03-05", + "last_updated": "2026-03-05", "type": "chat" }, { - "id": "doubao-seed-evolving", - "name": "Seed Evolving", - "display_name": "Seed Evolving", + "id": "gpt-5.4-mini", + "name": "gpt-5.4-mini", + "display_name": "gpt-5.4-mini", "modalities": { "input": [ "text", - "image", - "video" + "image" ], "output": [ "text" ] }, "limit": { - "context": 256000, - "output": 256000 + "context": 400000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], + "default_enabled": false, + "mode": "effort", + "effort": "none", "effort_options": [ "none", "low", + "medium", + "high", + "xhigh" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", "high" - ] + ], + "visibility": "hidden" } }, "attachment": true, "open_weights": false, - "release_date": "2026-06-23", - "last_updated": "2026-06-23", + "knowledge": "2025-08-31", + "release_date": "2026-03-17", + "last_updated": "2026-03-17", "type": "chat" }, { - "id": "doubao-seed-2.0-lite", - "name": "Seed 2.0 Lite", - "display_name": "Seed 2.0 Lite", + "id": "deepseek-flash", + "name": "DeepSeek V4.1 Flash", + "display_name": "DeepSeek V4.1 Flash", "modalities": { "input": [ "text", - "image", - "video" + "image" ], "output": [ "text" ] }, "limit": { - "context": 256000, - "output": 32000 + "context": 1000000, + "output": 384000 }, "temperature": true, "tool_call": true, @@ -124743,93 +123677,67 @@ "thinking_blocks" ], "effort_options": [ - "none", "low", - "high" + "high", + "max" ] } }, "attachment": true, - "open_weights": false, - "release_date": "2026-02-14", - "last_updated": "2026-02-14", + "open_weights": true, + "knowledge": "2025-05", + "release_date": "2026-09-10", + "last_updated": "2026-09-10", "type": "chat" - } - ] - }, - "zhipuai-coding-plan": { - "id": "zhipuai-coding-plan", - "name": "Zhipu AI Coding Plan", - "display_name": "Zhipu AI Coding Plan", - "api": "https://open.bigmodel.cn/api/coding/paas/v4", - "doc": "https://docs.bigmodel.cn/cn/coding-plan/overview", - "models": [ + }, { - "id": "glm-5.3-flash", - "name": "GLM-5.3-Flash", - "display_name": "GLM-5.3-Flash", + "id": "mistral-large-2512", + "name": "mistral-large-2512", + "display_name": "mistral-large-2512", "modalities": { "input": [ "text", - "image", - "video", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 128000, + "output": 262144 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "effort_options": [ - "low", - "high", - "max" - ] - } + "supported": false }, "attachment": true, "open_weights": true, - "release_date": "2026-08-26", - "last_updated": "2026-08-26", + "knowledge": "2024-12", + "release_date": "2025-12-02", + "last_updated": "2025-12-02", "type": "chat" }, { - "id": "glm-4.6v", - "name": "GLM-4.6V", - "display_name": "GLM-4.6V", + "id": "gpt-6-sol", + "name": "GPT-6 Sol", + "display_name": "GPT-6 Sol", "modalities": { "input": [ "text", "image", - "video" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 32768 + "context": 1050000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -124837,39 +123745,42 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "none", + "low", + "medium", + "high", + "xhigh", + "max" ] } }, "attachment": true, - "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-12-08", - "last_updated": "2025-12-08", + "open_weights": false, + "knowledge": "2026-04-20", + "release_date": "2026-09-22", + "last_updated": "2026-09-22", "type": "chat" }, { - "id": "glm-5.3", - "name": "GLM-5.3", - "display_name": "GLM-5.3", + "id": "gpt-5.6-terra", + "name": "GPT-5.6 Terra", + "display_name": "GPT-5.6 Terra", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 1050000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -124878,42 +123789,52 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], + "default_enabled": true, + "mode": "effort", + "effort": "medium", "effort_options": [ + "none", "low", + "medium", "high", + "xhigh", "max" - ] + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-08-14", - "last_updated": "2026-08-14", + "attachment": true, + "open_weights": false, + "knowledge": "2026-02-16", + "release_date": "2026-07-09", + "last_updated": "2026-07-09", "type": "chat" }, { - "id": "glm-5.3-highspeed", - "name": "GLM-5.3 Highspeed", - "display_name": "GLM-5.3 Highspeed", + "id": "gpt-6-astra", + "name": "GPT-6 Astra", + "display_name": "GPT-6 Astra", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 1050000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -124922,106 +123843,122 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], "effort_options": [ "low", + "medium", "high", + "xhigh", "max" ] } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-08-14", - "last_updated": "2026-08-14", + "attachment": true, + "open_weights": false, + "knowledge": "2026-04-30", + "release_date": "2026-09-04", + "last_updated": "2026-09-04", "type": "chat" - } - ] - }, - "azure": { - "id": "azure", - "name": "Azure", - "display_name": "Azure", - "doc": "https://learn.microsoft.com/en-us/azure/ai-services/openai/concepts/models", - "models": [ + }, { - "id": "phi-4-reasoning", - "name": "Phi-4-reasoning", - "display_name": "Phi-4-reasoning", + "id": "claude-haiku-4-5-20251001", + "name": "claude-haiku-4-5-20251001", + "display_name": "claude-haiku-4-5-20251001", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 32000, - "output": 4096 + "context": 200000, + "output": 64000 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, - "attachment": false, - "open_weights": true, - "knowledge": "2023-10", - "release_date": "2024-12-11", - "last_updated": "2024-12-11", + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": false, + "mode": "budget", + "budget": { + "min": 1024, + "unit": "tokens" + }, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", + "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." + ] + } + }, + "attachment": true, + "open_weights": false, + "knowledge": "2025-02-28", + "release_date": "2025-10-15", + "last_updated": "2025-10-15", "type": "chat" }, { - "id": "text-embedding-ada-002", - "name": "text-embedding-ada-002", - "display_name": "text-embedding-ada-002", + "id": "gpt-5.3-chat-latest", + "name": "GPT-5.3 Chat (latest)", + "display_name": "GPT-5.3 Chat (latest)", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 8192, - "output": 1536 + "context": 128000, + "output": 16384 }, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { "supported": false }, - "attachment": false, + "attachment": true, "open_weights": false, - "release_date": "2022-12-15", - "last_updated": "2022-12-15", - "type": "embedding" + "knowledge": "2025-08-31", + "release_date": "2026-03-03", + "last_updated": "2026-03-03", + "type": "chat" }, { - "id": "grok-4-1-fast-reasoning", - "name": "Grok 4.1 Fast (Reasoning)", - "display_name": "Grok 4.1 Fast (Reasoning)", + "id": "claude-haiku-5-5", + "name": "Claude Haiku 5.5", + "display_name": "Claude Haiku 5.5", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 8192 + "context": 1000000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -125029,24 +123966,30 @@ }, "extra_capabilities": { "reasoning": { - "supported": true + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" + ] } }, "attachment": true, "open_weights": false, - "release_date": "2025-06-27", - "last_updated": "2025-06-27", + "knowledge": "2026-06", + "release_date": "2026-10-07", + "last_updated": "2026-10-07", "type": "chat" }, { - "id": "gpt-5.1-codex-max", - "name": "GPT-5.1 Codex Max", - "display_name": "GPT-5.1 Codex Max", + "id": "gpt-5.2", + "name": "gpt-5.2", + "display_name": "gpt-5.2", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" @@ -125072,7 +124015,8 @@ "none", "low", "medium", - "high" + "high", + "xhigh" ], "verbosity": "medium", "verbosity_options": [ @@ -125085,15 +124029,15 @@ }, "attachment": true, "open_weights": false, - "knowledge": "2024-09-30", - "release_date": "2025-11-13", - "last_updated": "2025-11-13", + "knowledge": "2025-08-31", + "release_date": "2025-12-11", + "last_updated": "2025-12-11", "type": "chat" }, { - "id": "gpt-5", - "name": "GPT-5", - "display_name": "GPT-5", + "id": "claude-sonnet-4-5-20250929", + "name": "claude-sonnet-4-5-20250929", + "display_name": "claude-sonnet-4-5-20250929", "modalities": { "input": [ "text", @@ -125105,60 +124049,59 @@ ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 200000, + "output": 64000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", - "effort_options": [ - "minimal", - "low", - "medium", - "high" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" + "default_enabled": false, + "mode": "budget", + "budget": { + "min": 1024, + "unit": "tokens" + }, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ], - "visibility": "hidden" + "notes": [ + "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", + "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." + ] } }, "attachment": true, "open_weights": false, - "knowledge": "2024-09-30", - "release_date": "2025-08-07", - "last_updated": "2025-08-07", + "knowledge": "2025-07-31", + "release_date": "2025-09-29", + "last_updated": "2025-09-29", "type": "chat" }, { - "id": "gpt-4o-mini", - "name": "GPT-4o mini", - "display_name": "GPT-4o mini", + "id": "doubao-seed-1-8-251215", + "name": "doubao-seed-1-8-251215", + "display_name": "doubao-seed-1-8-251215", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 16384 + "context": 224000, + "output": 64000 }, "temperature": true, "tool_call": true, @@ -125167,15 +124110,14 @@ }, "attachment": true, "open_weights": false, - "knowledge": "2023-09", - "release_date": "2024-07-18", - "last_updated": "2024-07-18", + "release_date": "2025-12-28", + "last_updated": "2025-12-28", "type": "chat" }, { - "id": "gpt-5.5", - "name": "GPT-5.5", - "display_name": "GPT-5.5", + "id": "claude-sonnet-4-5-20250929-thinking", + "name": "claude-sonnet-4-5-20250929-thinking", + "display_name": "claude-sonnet-4-5-20250929-thinking", "modalities": { "input": [ "text", @@ -125187,10 +124129,10 @@ ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 200000, + "output": 64000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -125200,64 +124142,105 @@ "reasoning": { "supported": true, "default_enabled": true, - "mode": "effort", - "effort": "medium", - "effort_options": [ - "low", - "medium", - "high", - "xhigh" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" + "mode": "budget", + "budget": { + "min": 1024, + "unit": "tokens" + }, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ], - "visibility": "hidden" + "notes": [ + "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", + "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." + ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-12-01", - "release_date": "2026-04-24", - "last_updated": "2026-04-24", + "knowledge": "2025-03", + "release_date": "2025-09-29", + "last_updated": "2025-09-29", "type": "chat" }, { - "id": "gpt-4.1-nano", - "name": "GPT-4.1 nano", - "display_name": "GPT-4.1 nano", + "id": "kimi-k2.7-code", + "name": "Kimi K2.7 Code", + "display_name": "Kimi K2.7 Code", "modalities": { "input": [ "text", "image", - "pdf" + "video" ], "output": [ "text" ] }, "limit": { - "context": 1047576, - "output": 32768 + "context": 262144, + "output": 262144 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } }, "attachment": true, - "open_weights": false, - "knowledge": "2024-04", - "release_date": "2025-04-14", - "last_updated": "2025-04-14", + "open_weights": true, + "knowledge": "2025-01", + "release_date": "2026-06-12", + "last_updated": "2026-06-12", + "type": "chat" + }, + { + "id": "deepseek-v3.2-thinking", + "name": "DeepSeek-V3.2-Thinking", + "display_name": "DeepSeek-V3.2-Thinking", + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 128000 + }, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": true, + "default": true + }, + "attachment": false, + "open_weights": true, + "knowledge": "2024-12", + "release_date": "2025-12-01", + "last_updated": "2025-12-01", "type": "chat" }, { - "id": "o1", - "name": "o1", - "display_name": "o1", + "id": "gpt-6.1-sol", + "name": "GPT-6.1 Sol", + "display_name": "GPT-6.1 Sol", "modalities": { "input": [ "text", @@ -125269,8 +124252,8 @@ ] }, "limit": { - "context": 200000, - "output": 100000 + "context": 1050000, + "output": 128000 }, "temperature": false, "tool_call": true, @@ -125280,29 +124263,26 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", "effort_options": [ "low", "medium", - "high" - ], - "visibility": "hidden" + "high", + "xhigh", + "max" + ] } }, "attachment": true, "open_weights": false, - "knowledge": "2023-09", - "release_date": "2024-12-05", - "last_updated": "2024-12-05", + "knowledge": "2026-04-30", + "release_date": "2026-09-29", + "last_updated": "2026-09-29", "type": "chat" }, { - "id": "gpt-3.5-turbo-1106", - "name": "GPT-3.5 Turbo 1106", - "display_name": "GPT-3.5 Turbo 1106", + "id": "kimi-k2-thinking", + "name": "kimi-k2-thinking", + "display_name": "kimi-k2-thinking", "modalities": { "input": [ "text" @@ -125312,96 +124292,108 @@ ] }, "limit": { - "context": 16384, - "output": 4096 + "context": 262144, + "output": 262144 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } }, "attachment": false, - "open_weights": false, - "knowledge": "2021-08", - "release_date": "2023-11-06", - "last_updated": "2023-11-06", + "open_weights": true, + "knowledge": "2025-06", + "release_date": "2025-11-06", + "last_updated": "2025-11-06", "type": "chat" }, { - "id": "gpt-image-2", - "name": "GPT-Image-2", - "display_name": "GPT-Image-2", + "id": "gemini-4-argon", + "name": "Gemini 4 Argon", + "display_name": "Gemini 4 Argon", "modalities": { "input": [ "text", - "image" + "image", + "video", + "audio", + "pdf" ], "output": [ - "image" + "text" ] }, "limit": { - "context": 8192, - "output": 8192 + "context": 1048576, + "output": 65536 }, "temperature": false, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "low", + "medium", + "high" + ] + } }, "attachment": true, "open_weights": false, - "release_date": "2026-04-21", - "last_updated": "2026-04-21" + "knowledge": "2026-03", + "release_date": "2026-10-13", + "last_updated": "2026-10-13", + "type": "chat" }, { - "id": "gpt-6-astra", - "name": "GPT-6 Astra", - "display_name": "GPT-6 Astra", + "id": "ministral-14b-2512", + "name": "ministral-14b-2512", + "display_name": "ministral-14b-2512", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1050000, + "context": 128000, "output": 128000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "effort_options": [ - "none", - "low", - "medium", - "high", - "xhigh", - "max" - ] - } + "supported": false }, "attachment": true, - "open_weights": false, - "knowledge": "2026-04-30", - "release_date": "2026-09-04", - "last_updated": "2026-09-04", + "open_weights": true, + "knowledge": "2024-12", + "release_date": "2025-12-02", + "last_updated": "2025-12-02", "type": "chat" }, { - "id": "claude-opus-4-5", - "name": "Claude Opus 4.5", - "display_name": "Claude Opus 4.5", + "id": "claude-sonnet-4-6-thinking", + "name": "claude-sonnet-4-6-thinking", + "display_name": "claude-sonnet-4-6-thinking", "modalities": { "input": [ "text", @@ -125413,19 +124405,19 @@ ] }, "limit": { - "context": 200000, + "context": 1000000, "output": 64000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, + "default_enabled": true, "mode": "mixed", "budget": { "min": 1024, @@ -125435,7 +124427,8 @@ "effort_options": [ "low", "medium", - "high" + "high", + "max" ], "interleaved": true, "summaries": true, @@ -125444,22 +124437,22 @@ "thinking_blocks" ], "notes": [ - "Claude Opus 4.5 uses manual thinking.type = \"enabled\" with budget_tokens; effort can be used alongside the thinking budget.", - "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header." + "Anthropic recommends adaptive thinking with effort for Claude 4.6; budget_tokens remains a deprecated compatibility path.", + "Anthropic API defaults effort to high; lower effort levels should be chosen per workload." ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-03-31", - "release_date": "2025-11-24", - "last_updated": "2025-08-01", + "knowledge": "2025-08-31", + "release_date": "2026-02-17", + "last_updated": "2026-03-13", "type": "chat" }, { - "id": "gpt-5.4", - "name": "GPT-5.4", - "display_name": "GPT-5.4", + "id": "claude-opus-4-7", + "name": "claude-opus-4-7", + "display_name": "claude-opus-4-7", "modalities": { "input": [ "text", @@ -125471,10 +124464,10 @@ ] }, "limit": { - "context": 1050000, + "context": 1000000, "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -125485,34 +124478,38 @@ "supported": true, "default_enabled": false, "mode": "effort", - "effort": "none", + "effort": "high", "effort_options": [ - "none", "low", "medium", "high", - "xhigh" + "xhigh", + "max" ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" + "interleaved": true, + "summaries": true, + "visibility": "omitted", + "continuation": [ + "thinking_blocks" ], - "visibility": "hidden" + "notes": [ + "Claude Opus 4.7 and newer Opus models require thinking.type = \"adaptive\" to enable thinking explicitly.", + "Manual budget_tokens requests return 400 on Claude Opus 4.7 and newer adaptive-only Opus models.", + "task_budget is separate from thinking control and should not be treated as a thinking budget." + ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-03-05", - "last_updated": "2026-03-05", + "knowledge": "2026-01-31", + "release_date": "2026-04-16", + "last_updated": "2026-04-16", "type": "chat" }, { - "id": "gpt-3.5-turbo-instruct", - "name": "GPT-3.5 Turbo Instruct", - "display_name": "GPT-3.5 Turbo Instruct", + "id": "qwen3.7-max", + "name": "Qwen3.7 Max", + "display_name": "Qwen3.7 Max", "modalities": { "input": [ "text" @@ -125522,28 +124519,35 @@ ] }, "limit": { - "context": 4096, - "output": 4096 + "context": 1000000, + "output": 131072 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true + } }, "attachment": false, "open_weights": false, - "knowledge": "2021-08", - "release_date": "2023-09-21", - "last_updated": "2023-09-21", + "release_date": "2026-05-21", + "last_updated": "2026-05-21", "type": "chat" }, { - "id": "deepseek-v4-flash", - "name": "DeepSeek-V4-Flash", - "display_name": "DeepSeek-V4-Flash", + "id": "qwen3.6-flash", + "name": "Qwen3.6 Flash", + "display_name": "Qwen3.6 Flash", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" @@ -125551,7 +124555,7 @@ }, "limit": { "context": 1000000, - "output": 384000 + "output": 65536 }, "temperature": true, "tool_call": true, @@ -125562,36 +124566,24 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "high", - "effort_options": [ - "low", - "high", - "max" - ], "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ "thinking_blocks" - ], - "notes": [ - "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." ] } }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-04-24", - "last_updated": "2026-04-24", + "attachment": true, + "open_weights": false, + "release_date": "2026-04-27", + "last_updated": "2026-04-27", "type": "chat" }, { - "id": "claude-sonnet-5-5", - "name": "Claude Sonnet 5.5", - "display_name": "Claude Sonnet 5.5", + "id": "claude-haiku-4-5", + "name": "claude-haiku-4-5", + "display_name": "claude-haiku-4-5", "modalities": { "input": [ "text", @@ -125603,66 +124595,95 @@ ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 200000, + "output": 64000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" + "supported": true, + "default_enabled": false, + "mode": "budget", + "budget": { + "min": 1024, + "unit": "tokens" + }, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", + "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-06", - "release_date": "2026-09-28", - "last_updated": "2026-09-28", + "knowledge": "2025-02-28", + "release_date": "2025-10-15", + "last_updated": "2025-10-15", "type": "chat" }, { - "id": "mistral-medium-2505", - "name": "Mistral Medium 3", - "display_name": "Mistral Medium 3", + "id": "grok-4.7", + "name": "Grok 4.7", + "display_name": "Grok 4.7", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 128000 + "context": 500000, + "output": 500000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "effort_options": [ + "low", + "medium", + "high", + "xhigh" + ] + } }, "attachment": true, "open_weights": false, - "knowledge": "2025-05", - "release_date": "2025-05-07", - "last_updated": "2025-05-07", + "knowledge": "2026-05", + "release_date": "2026-09-21", + "last_updated": "2026-09-21", "type": "chat" }, { - "id": "claude-haiku-5-5", - "name": "Claude Haiku 5.5", - "display_name": "Claude Haiku 5.5", + "id": "claude-sonnet-5", + "name": "Claude Sonnet 5", + "display_name": "Claude Sonnet 5", "modalities": { "input": [ "text", @@ -125685,6 +124706,7 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, "effort_options": [ "low", "medium", @@ -125696,26 +124718,28 @@ }, "attachment": true, "open_weights": false, - "knowledge": "2026-06", - "release_date": "2026-10-07", - "last_updated": "2026-10-07", + "knowledge": "2026-01-31", + "release_date": "2026-06-30", + "last_updated": "2026-06-30", "type": "chat" }, { - "id": "phi-4-mini-reasoning", - "name": "Phi-4-mini-reasoning", - "display_name": "Phi-4-mini-reasoning", + "id": "qwen3.8-flash", + "name": "Qwen3.8 Flash", + "display_name": "Qwen3.8 Flash", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 4096 + "context": 1000000, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -125723,28 +124747,45 @@ "supported": true, "default": true }, - "attachment": false, - "open_weights": true, - "knowledge": "2023-10", - "release_date": "2024-12-11", - "last_updated": "2024-12-11", + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "effort_options": [ + "low", + "medium", + "xhigh" + ] + } + }, + "attachment": true, + "open_weights": false, + "release_date": "2026-08-26", + "last_updated": "2026-08-26", "type": "chat" }, { - "id": "deepseek-v4-pro", - "name": "DeepSeek-V4-Pro", - "display_name": "DeepSeek-V4-Pro", + "id": "qwen3.6-35b-a3b", + "name": "Qwen3.6 35B-A3B", + "display_name": "Qwen3.6 35B-A3B", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 384000 + "context": 262144, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -125755,79 +124796,63 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "high", - "effort_options": [ - "low", - "high", - "max" - ], "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ "thinking_blocks" - ], - "notes": [ - "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." ] } }, - "attachment": false, + "attachment": true, "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-04-24", - "last_updated": "2026-04-24", + "release_date": "2026-04-17", + "last_updated": "2026-04-17", "type": "chat" }, { - "id": "claude-opus-5", - "name": "Claude Opus 5", - "display_name": "Claude Opus 5", + "id": "qwen3-30b-a3b", + "name": "Qwen3-30B-A3B", + "display_name": "Qwen3-30B-A3B", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 128000, + "output": 8192 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true + "supported": true }, "extra_capabilities": { "reasoning": { "supported": true, - "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-05", - "release_date": "2026-07-24", - "last_updated": "2026-07-24", + "attachment": false, + "open_weights": true, + "knowledge": "2025-04", + "release_date": "2025-04-28", + "last_updated": "2025-04-28", "type": "chat" }, { - "id": "gpt-4-turbo", - "name": "GPT-4 Turbo", - "display_name": "GPT-4 Turbo", + "id": "gemini-2.0-flash-lite", + "name": "gemini-2.0-flash-lite", + "display_name": "gemini-2.0-flash-lite", "modalities": { "input": [ "text", @@ -125838,58 +124863,71 @@ ] }, "limit": { - "context": 128000, - "output": 4096 + "context": 2000000, + "output": 8192 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { "supported": false }, "attachment": true, "open_weights": false, - "knowledge": "2023-12", - "release_date": "2023-11-06", - "last_updated": "2024-04-09", + "knowledge": "2024-11", + "release_date": "2024-12-11", + "last_updated": "2024-12-11", "type": "chat" }, { - "id": "phi-4-mini", - "name": "Phi-4-mini", - "display_name": "Phi-4-mini", + "id": "qwen3.5-plus", + "name": "Qwen3.5 Plus", + "display_name": "Qwen3.5 Plus", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 4096 + "context": 1000000, + "output": 65536 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, - "open_weights": true, - "knowledge": "2023-10", - "release_date": "2024-12-11", - "last_updated": "2024-12-11", + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } + }, + "attachment": true, + "open_weights": false, + "knowledge": "2025-04", + "release_date": "2026-02-16", + "last_updated": "2026-02-16", "type": "chat" }, { - "id": "claude-mythos-5", - "name": "Claude Mythos 5", - "display_name": "Claude Mythos 5", + "id": "gemini-2.5-flash-preview-09-2025", + "name": "gemini-2.5-flash-preview-09-2025", + "display_name": "gemini-2.5-flash-preview-09-2025", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" @@ -125897,9 +124935,9 @@ }, "limit": { "context": 1000000, - "output": 128000 + "output": 65536 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -125909,66 +124947,82 @@ "reasoning": { "supported": true, "default_enabled": true, - "mode": "effort", - "effort": "high", - "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" - ], - "interleaved": true, + "mode": "budget", + "budget": { + "default": -1, + "min": 0, + "max": 24576, + "auto": -1, + "off": 0, + "unit": "tokens" + }, "summaries": true, - "visibility": "omitted", + "visibility": "summary", "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Adaptive thinking is always on for Claude Fable 5 and Claude Mythos 5; thinking.type = \"disabled\" is rejected.", - "Manual budget_tokens requests return 400 on Claude Fable 5 and Claude Mythos 5.", - "thinking.display defaults to omitted; set display to summarized to receive readable thinking summaries." + "thought_signatures" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-01-31", - "release_date": "2026-06-09", - "last_updated": "2026-06-09", + "knowledge": "2025-01", + "release_date": "2025-09-26", + "last_updated": "2025-09-26", "type": "chat" }, { - "id": "cohere-embed-v3-multilingual", - "name": "Embed v3 Multilingual", - "display_name": "Embed v3 Multilingual", + "id": "gemini-2.5-flash-lite-preview-09-2025", + "name": "gemini-2.5-flash-lite-preview-09-2025", + "display_name": "gemini-2.5-flash-lite-preview-09-2025", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 512, - "output": 1024 + "context": 1000000, + "output": 65536 }, - "temperature": false, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": false }, - "attachment": false, - "open_weights": true, - "release_date": "2023-11-07", - "last_updated": "2023-11-07", - "type": "embedding" + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": false, + "mode": "budget", + "budget": { + "default": -1, + "min": 512, + "max": 24576, + "auto": -1, + "unit": "tokens" + }, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thought_signatures" + ] + } + }, + "attachment": true, + "open_weights": false, + "knowledge": "2025-01", + "release_date": "2025-09-26", + "last_updated": "2025-09-26", + "type": "chat" }, { - "id": "llama-4-scout-17b-16e-instruct", - "name": "Llama 4 Scout 17B 16E Instruct", - "display_name": "Llama 4 Scout 17B 16E Instruct", + "id": "grok-4.5", + "name": "Grok 4.5", + "display_name": "Grok 4.5", "modalities": { "input": [ "text", @@ -125979,25 +125033,35 @@ ] }, "limit": { - "context": 128000, - "output": 8192 + "context": 500000, + "output": 500000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "effort_options": [ + "low", + "medium", + "high" + ] + } }, "attachment": true, - "open_weights": true, - "knowledge": "2024-08", - "release_date": "2025-04-05", - "last_updated": "2025-04-05", + "open_weights": false, + "release_date": "2026-07-08", + "last_updated": "2026-07-08", "type": "chat" }, { - "id": "claude-opus-4-7", - "name": "Claude Opus 4.7", - "display_name": "Claude Opus 4.7", + "id": "claude-fable-5", + "name": "Claude Fable 5", + "display_name": "Claude Fable 5", "modalities": { "input": [ "text", @@ -126016,12 +125080,12 @@ "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, + "default_enabled": true, "mode": "effort", "effort": "high", "effort_options": [ @@ -126038,28 +125102,28 @@ "thinking_blocks" ], "notes": [ - "Claude Opus 4.7 and newer Opus models require thinking.type = \"adaptive\" to enable thinking explicitly.", - "Manual budget_tokens requests return 400 on Claude Opus 4.7 and newer adaptive-only Opus models.", - "task_budget is separate from thinking control and should not be treated as a thinking budget." + "Adaptive thinking is always on for Claude Fable 5 and Claude Mythos 5; thinking.type = \"disabled\" is rejected.", + "Manual budget_tokens requests return 400 on Claude Fable 5 and Claude Mythos 5.", + "thinking.display defaults to omitted; set display to summarized to receive readable thinking summaries." ] } }, "attachment": true, "open_weights": false, "knowledge": "2026-01-31", - "release_date": "2026-04-16", - "last_updated": "2026-04-16", + "release_date": "2026-06-09", + "last_updated": "2026-06-09", "type": "chat" }, { - "id": "gpt-4o", - "name": "GPT-4o", - "display_name": "GPT-4o", + "id": "glm-4.6v", + "name": "GLM-4.6V", + "display_name": "GLM-4.6V", "modalities": { "input": [ "text", "image", - "pdf" + "video" ], "output": [ "text" @@ -126067,24 +125131,25 @@ }, "limit": { "context": 128000, - "output": 16384 + "output": 32768 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, "attachment": true, - "open_weights": false, - "knowledge": "2023-09", - "release_date": "2024-05-13", - "last_updated": "2024-08-06", + "open_weights": true, + "knowledge": "2025-04", + "release_date": "2025-12-08", + "last_updated": "2025-12-08", "type": "chat" }, { - "id": "mistral-small-2503", - "name": "Mistral Small 3.1", - "display_name": "Mistral Small 3.1", + "id": "gpt-4.1-nano", + "name": "gpt-4.1-nano", + "display_name": "gpt-4.1-nano", "modalities": { "input": [ "text", @@ -126095,7 +125160,7 @@ ] }, "limit": { - "context": 128000, + "context": 1047576, "output": 32768 }, "temperature": true, @@ -126105,15 +125170,15 @@ }, "attachment": true, "open_weights": false, - "knowledge": "2024-09", - "release_date": "2025-03-01", - "last_updated": "2025-03-01", + "knowledge": "2024-04", + "release_date": "2025-04-14", + "last_updated": "2025-04-14", "type": "chat" }, { - "id": "gpt-4-turbo-vision", - "name": "GPT-4 Turbo Vision", - "display_name": "GPT-4 Turbo Vision", + "id": "doubao-seed-1-6-thinking-250715", + "name": "doubao-seed-1-6-thinking-250715", + "display_name": "doubao-seed-1-6-thinking-250715", "modalities": { "input": [ "text", @@ -126124,25 +125189,25 @@ ] }, "limit": { - "context": 128000, - "output": 4096 + "context": 256000, + "output": 16000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, "attachment": true, "open_weights": false, - "knowledge": "2023-11", - "release_date": "2023-11-06", - "last_updated": "2024-04-09", + "release_date": "2025-07-15", + "last_updated": "2025-07-15", "type": "chat" }, { - "id": "gpt-5-pro", - "name": "GPT-5 Pro", - "display_name": "GPT-5 Pro", + "id": "claude-opus-5-thinking", + "name": "claude-opus-5-thinking", + "display_name": "claude-opus-5-thinking", "modalities": { "input": [ "text", @@ -126154,7 +125219,7 @@ ] }, "limit": { - "context": 400000, + "context": 1000000, "output": 128000 }, "temperature": false, @@ -126166,29 +125231,20 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "fixed", - "effort": "high", - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + "default_enabled": true } }, "attachment": true, "open_weights": false, - "knowledge": "2024-09-30", - "release_date": "2025-10-06", - "last_updated": "2025-10-06", + "knowledge": "2026-05", + "release_date": "2026-07-24", + "last_updated": "2026-07-24", "type": "chat" }, { - "id": "kimi-k2.6", - "name": "Kimi K2.6", - "display_name": "Kimi K2.6", + "id": "grok-4.1", + "name": "grok-4.1", + "display_name": "grok-4.1", "modalities": { "input": [ "text", @@ -126199,36 +125255,28 @@ ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 200000, + "output": 64000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true - } + "supported": false }, "attachment": true, - "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-04-20", - "last_updated": "2026-04-20", + "open_weights": false, + "knowledge": "2025-06", + "release_date": "2025-11-18", + "last_updated": "2025-11-18", "type": "chat" }, { - "id": "grok-4-1-fast-non-reasoning", - "name": "Grok 4.1 Fast (Non-Reasoning)", - "display_name": "Grok 4.1 Fast (Non-Reasoning)", + "id": "qwen3-235b-a22b", + "name": "Qwen3-235B-A22B", + "display_name": "Qwen3-235B-A22B", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" @@ -126236,62 +125284,49 @@ }, "limit": { "context": 128000, - "output": 8192 + "output": 16384 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false - }, - "attachment": true, - "open_weights": false, - "release_date": "2025-06-27", - "last_updated": "2025-06-27", - "type": "chat" - }, - { - "id": "gpt-image-1", - "name": "GPT-Image-1", - "display_name": "GPT-Image-1", - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "image" - ] - }, - "limit": { - "context": 8192, - "output": 8192 + "supported": true }, - "temperature": false, - "tool_call": false, - "reasoning": { - "supported": false + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } }, - "attachment": true, - "open_weights": false, - "release_date": "2025-04-24", - "last_updated": "2025-04-24" + "attachment": false, + "open_weights": true, + "knowledge": "2025-04", + "release_date": "2025-04-28", + "last_updated": "2025-04-28", + "type": "chat" }, { - "id": "kimi-k2.5", - "name": "Kimi K2.5", - "display_name": "Kimi K2.5", + "id": "glm-5.3-flash", + "name": "GLM-5.3-Flash", + "display_name": "GLM-5.3-Flash", "modalities": { "input": [ "text", - "image" + "image", + "video", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 1000000, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -126307,35 +125342,37 @@ "visibility": "summary", "continuation": [ "thinking_blocks" + ], + "effort_options": [ + "low", + "high", + "max" ] } }, "attachment": true, "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-01-27", - "last_updated": "2026-01-27", + "release_date": "2026-08-26", + "last_updated": "2026-08-26", "type": "chat" }, { - "id": "claude-opus-5-5", - "name": "Claude Opus 5.5", - "display_name": "Claude Opus 5.5", + "id": "glm-4.7", + "name": "glm-4.7", + "display_name": "glm-4.7", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 204800, + "output": 131072 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -126343,30 +125380,32 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-06", - "release_date": "2026-09-22", - "last_updated": "2026-09-22", + "attachment": false, + "open_weights": true, + "knowledge": "2025-04", + "release_date": "2025-12-22", + "last_updated": "2025-12-22", "type": "chat" }, { - "id": "o3", - "name": "o3", - "display_name": "o3", + "id": "gemini-3.6-flash", + "name": "Gemini 3.6 Flash", + "display_name": "Gemini 3.6 Flash", "modalities": { "input": [ "text", "image", + "video", + "audio", "pdf" ], "output": [ @@ -126374,10 +125413,10 @@ ] }, "limit": { - "context": 200000, - "output": 100000 + "context": 1048576, + "output": 65536 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -126387,42 +125426,45 @@ "reasoning": { "supported": true, "default_enabled": true, - "mode": "effort", - "effort": "medium", - "effort_options": [ + "mode": "level", + "level": "high", + "level_options": [ + "minimal", "low", "medium", "high" ], - "visibility": "hidden" + "summaries": true, + "visibility": "summary", + "continuation": [ + "thought_signatures" + ] } }, "attachment": true, "open_weights": false, - "knowledge": "2024-05", - "release_date": "2025-04-16", - "last_updated": "2025-04-16", + "knowledge": "2026-03", + "release_date": "2026-07-21", + "last_updated": "2026-07-21", "type": "chat" }, { - "id": "gpt-chat-latest", - "name": "GPT Chat Latest", - "display_name": "GPT Chat Latest", + "id": "glm-4.6", + "name": "glm-4.6", + "display_name": "glm-4.6", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 204800, + "output": 131072 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -126430,66 +125472,53 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "effort_options": [ - "medium" - ] + "supported": true } }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-12-01", - "release_date": "2026-05-05", - "last_updated": "2026-05-28", + "attachment": false, + "open_weights": true, + "knowledge": "2025-04", + "release_date": "2025-09-30", + "last_updated": "2025-09-30", "type": "chat" }, { - "id": "claude-sonnet-5", - "name": "Claude Sonnet 5", - "display_name": "Claude Sonnet 5", + "id": "deepseek-v3.2", + "name": "deepseek-v3.2", + "display_name": "deepseek-v3.2", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 128000, + "output": 8192 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true + "supported": true }, "extra_capabilities": { "reasoning": { - "supported": true, - "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" - ] + "supported": true } }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-01-31", - "release_date": "2026-06-30", - "last_updated": "2026-06-30", + "attachment": false, + "open_weights": true, + "knowledge": "2024-12", + "release_date": "2025-12-01", + "last_updated": "2025-12-01", "type": "chat" }, { - "id": "text-embedding-3-small", - "name": "text-embedding-3-small", - "display_name": "text-embedding-3-small", + "id": "qwen3-coder-480b-a35b-instruct", + "name": "qwen3-coder-480b-a35b-instruct", + "display_name": "qwen3-coder-480b-a35b-instruct", "modalities": { "input": [ "text" @@ -126499,50 +125528,76 @@ ] }, "limit": { - "context": 8192, - "output": 1536 + "context": 262144, + "output": 65536 }, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { "supported": false }, "attachment": false, - "open_weights": false, - "release_date": "2024-01-25", - "last_updated": "2024-01-25", - "type": "embedding" + "open_weights": true, + "knowledge": "2025-04", + "release_date": "2025-07-22", + "last_updated": "2025-07-22", + "type": "chat" }, { - "id": "gpt-image-2.5-flare", - "name": "GPT Image 2.5 Flare", - "display_name": "GPT Image 2.5 Flare", + "id": "gemini-3.8-flash", + "name": "Gemini 3.8 Flash", + "display_name": "Gemini 3.8 Flash", "modalities": { "input": [ "text", - "image" + "image", + "video", + "audio", + "pdf" ], "output": [ - "image" + "text" ] }, "limit": { - "context": 8192, - "output": 8192 + "context": 1048576, + "output": 65536 }, - "temperature": false, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "level", + "level": "high", + "level_options": [ + "minimal", + "low", + "medium", + "high" + ], + "summaries": true, + "visibility": "summary", + "continuation": [ + "thought_signatures" + ] + } }, "attachment": true, "open_weights": false, - "release_date": "2026-09-08", - "last_updated": "2026-09-08" + "release_date": "2026-09-02", + "last_updated": "2026-09-02", + "type": "chat" }, { - "id": "gpt-6-sol", - "name": "GPT-6 Sol", - "display_name": "GPT-6 Sol", + "id": "claude-opus-5-5", + "name": "Claude Opus 5.5", + "display_name": "Claude Opus 5.5", "modalities": { "input": [ "text", @@ -126554,7 +125609,7 @@ ] }, "limit": { - "context": 1050000, + "context": 1000000, "output": 128000 }, "temperature": false, @@ -126566,7 +125621,6 @@ "extra_capabilities": { "reasoning": { "effort_options": [ - "none", "low", "medium", "high", @@ -126577,19 +125631,21 @@ }, "attachment": true, "open_weights": false, - "knowledge": "2026-04-20", + "knowledge": "2026-06", "release_date": "2026-09-22", "last_updated": "2026-09-22", "type": "chat" }, { - "id": "o4-mini", - "name": "o4-mini", - "display_name": "o4-mini", + "id": "gemini-3.1-flash-lite-preview", + "name": "Gemini 3.1 Flash Lite Preview", + "display_name": "Gemini 3.1 Flash Lite Preview", "modalities": { "input": [ "text", "image", + "video", + "audio", "pdf" ], "output": [ @@ -126597,10 +125653,10 @@ ] }, "limit": { - "context": 200000, - "output": 100000 + "context": 1048576, + "output": 65536 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -126608,29 +125664,25 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", "effort_options": [ + "minimal", "low", "medium", "high" - ], - "visibility": "hidden" + ] } }, "attachment": true, "open_weights": false, - "knowledge": "2024-05", - "release_date": "2025-04-16", - "last_updated": "2025-04-16", + "knowledge": "2025-01", + "release_date": "2026-03-03", + "last_updated": "2026-03-03", "type": "chat" }, { - "id": "gpt-5.6-sol", - "name": "GPT-5.6 Sol", - "display_name": "GPT-5.6 Sol", + "id": "gpt-5.6-luna", + "name": "GPT-5.6 Luna", + "display_name": "GPT-5.6 Luna", "modalities": { "input": [ "text", @@ -126682,45 +125734,37 @@ "type": "chat" }, { - "id": "claude-sonnet-4-6", - "name": "Claude Sonnet 4.6", - "display_name": "Claude Sonnet 4.6", + "id": "claude-opus-4-1-20250805-thinking", + "name": "claude-opus-4-1-20250805-thinking", + "display_name": "claude-opus-4-1-20250805-thinking", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 64000 + "context": 200000, + "output": 32000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "mixed", + "default_enabled": true, + "mode": "budget", "budget": { "min": 1024, "unit": "tokens" }, - "effort": "high", - "effort_options": [ - "low", - "medium", - "high", - "max" - ], "interleaved": true, "summaries": true, "visibility": "summary", @@ -126728,34 +125772,33 @@ "thinking_blocks" ], "notes": [ - "Anthropic recommends adaptive thinking with effort for Claude 4.6; budget_tokens remains a deprecated compatibility path.", - "Anthropic API defaults effort to high; lower effort levels should be chosen per workload." + "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", + "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-02-17", - "last_updated": "2026-03-13", + "knowledge": "2025-03-31", + "release_date": "2025-08-05", + "last_updated": "2025-08-05", "type": "chat" }, { - "id": "gpt-5.6-terra", - "name": "GPT-5.6 Terra", - "display_name": "GPT-5.6 Terra", + "id": "gpt-5", + "name": "gpt-5", + "display_name": "gpt-5", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1050000, + "context": 400000, "output": 128000 }, "temperature": false, @@ -126771,12 +125814,10 @@ "mode": "effort", "effort": "medium", "effort_options": [ - "none", + "minimal", "low", "medium", - "high", - "xhigh", - "max" + "high" ], "verbosity": "medium", "verbosity_options": [ @@ -126789,46 +125830,68 @@ }, "attachment": true, "open_weights": false, - "knowledge": "2026-02-16", - "release_date": "2026-07-09", - "last_updated": "2026-07-09", + "knowledge": "2024-09-30", + "release_date": "2025-08-07", + "last_updated": "2025-08-07", "type": "chat" }, { - "id": "text-embedding-3-large", - "name": "text-embedding-3-large", - "display_name": "text-embedding-3-large", + "id": "gemini-2.5-pro", + "name": "gemini-2.5-pro", + "display_name": "gemini-2.5-pro", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 8192, - "output": 3072 + "context": 1000000, + "output": 65536 }, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "budget", + "budget": { + "default": -1, + "min": 128, + "max": 32768, + "auto": -1, + "unit": "tokens" + }, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thought_signatures" + ] + } + }, + "attachment": true, "open_weights": false, - "release_date": "2024-01-25", - "last_updated": "2024-01-25", - "type": "embedding" + "knowledge": "2025-01", + "release_date": "2025-06-17", + "last_updated": "2025-06-17", + "type": "chat" }, { - "id": "gpt-5.1-codex", - "name": "GPT-5.1 Codex", - "display_name": "GPT-5.1 Codex", + "id": "gpt-5-mini", + "name": "gpt-5-mini", + "display_name": "gpt-5-mini", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" @@ -126842,16 +125905,16 @@ "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, + "default_enabled": true, "mode": "effort", - "effort": "none", + "effort": "medium", "effort_options": [ - "none", + "minimal", "low", "medium", "high" @@ -126867,58 +125930,45 @@ }, "attachment": true, "open_weights": false, - "knowledge": "2024-09-30", - "release_date": "2025-11-14", - "last_updated": "2025-11-14", + "knowledge": "2024-05-30", + "release_date": "2025-08-07", + "last_updated": "2025-08-07", "type": "chat" }, { - "id": "gpt-6-luna", - "name": "GPT-6 Luna", - "display_name": "GPT-6 Luna", + "id": "gpt-5-thinking", + "name": "gpt-5-thinking", + "display_name": "gpt-5-thinking", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1050000, + "context": 400000, "output": 128000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "none", - "low", - "medium", - "high", - "xhigh", - "max" - ] - } - }, "attachment": true, "open_weights": false, - "knowledge": "2026-05-18", - "release_date": "2026-09-22", - "last_updated": "2026-09-22", + "knowledge": "2024-10", + "release_date": "2025-08-07", + "last_updated": "2025-08-07", "type": "chat" }, { - "id": "deepseek-v4.1-flash", - "name": "DeepSeek-V4.1-Flash", - "display_name": "DeepSeek-V4.1-Flash", + "id": "grok-4-fast-non-reasoning", + "name": "grok-4-fast-non-reasoning", + "display_name": "grok-4-fast-non-reasoning", "modalities": { "input": [ "text", @@ -126929,58 +125979,39 @@ ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 2000000, + "output": 30000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "effort_options": [ - "none", - "low", - "high", - "max" - ] - } + "supported": false }, "attachment": true, - "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-09-10", - "last_updated": "2026-09-10", + "open_weights": false, + "knowledge": "2025-06", + "release_date": "2025-12-18", + "last_updated": "2025-12-18", "type": "chat" }, { - "id": "gpt-5-mini", - "name": "GPT-5 Mini", - "display_name": "GPT-5 Mini", + "id": "grok-4.20-beta-0309-reasoning", + "name": "grok-4.20-beta-0309-reasoning", + "display_name": "grok-4.20-beta-0309-reasoning", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 2000000, + "output": 30000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -126988,51 +126019,32 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", - "effort_options": [ - "minimal", - "low", - "medium", - "high" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + "supported": true } }, "attachment": true, "open_weights": false, - "knowledge": "2024-05-30", - "release_date": "2025-08-07", - "last_updated": "2025-08-07", + "release_date": "2026-03-09", + "last_updated": "2026-03-09", "type": "chat" }, { - "id": "gpt-5-codex", - "name": "GPT-5-Codex", - "display_name": "GPT-5-Codex", + "id": "MiniMax-M2.1", + "name": "MiniMax-M2.1", + "display_name": "MiniMax-M2.1", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 204800, + "output": 131072 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -127040,76 +126052,64 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", - "effort_options": [ - "minimal", - "low", - "medium", - "high" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + "supported": true } }, - "attachment": true, - "open_weights": false, - "knowledge": "2024-09-30", - "release_date": "2025-09-15", - "last_updated": "2025-09-15", + "attachment": false, + "open_weights": true, + "release_date": "2025-12-23", + "last_updated": "2025-12-23", "type": "chat" }, { - "id": "model-router", - "name": "Model Router", - "display_name": "Model Router", + "id": "gpt-4o", + "name": "gpt-4o", + "display_name": "gpt-4o", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 200000, + "context": 128000, "output": 16384 }, + "temperature": true, "tool_call": true, "reasoning": { "supported": false }, "attachment": true, "open_weights": false, - "release_date": "2025-05-19", - "last_updated": "2025-11-18", + "knowledge": "2023-09", + "release_date": "2024-05-13", + "last_updated": "2024-08-06", "type": "chat" }, { - "id": "deepseek-v3.2", - "name": "DeepSeek-V3.2", - "display_name": "DeepSeek-V3.2", + "id": "kimi-k3.1-pro", + "name": "Kimi K3.1 Pro", + "display_name": "Kimi K3.1 Pro", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 128000 + "context": 1048576, + "output": 131072 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -127117,31 +126117,43 @@ }, "extra_capabilities": { "reasoning": { - "supported": true + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "effort_options": [ + "none", + "low", + "high", + "max" + ] } }, - "attachment": false, - "open_weights": true, - "knowledge": "2024-07", - "release_date": "2025-12-01", - "last_updated": "2025-12-01", + "attachment": true, + "open_weights": false, + "release_date": "2026-10-23", + "last_updated": "2026-10-23", "type": "chat" }, { - "id": "cohere-command-a", - "name": "Command A", - "display_name": "Command A", + "id": "gemini-2.5-flash-nothink", + "name": "gemini-2.5-flash-nothink", + "display_name": "gemini-2.5-flash-nothink", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 8192 + "context": 1000000, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -127149,17 +126161,37 @@ "supported": true, "default": true }, - "attachment": false, - "open_weights": true, - "knowledge": "2024-06-01", - "release_date": "2025-03-13", - "last_updated": "2025-03-13", + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "budget", + "budget": { + "default": -1, + "min": 0, + "max": 24576, + "auto": -1, + "off": 0, + "unit": "tokens" + }, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thought_signatures" + ] + } + }, + "attachment": true, + "open_weights": false, + "knowledge": "2025-01", + "release_date": "2025-06-17", + "last_updated": "2025-06-17", "type": "chat" }, { - "id": "grok-4.7", - "name": "Grok 4.7", - "display_name": "Grok 4.7", + "id": "claude-sonnet-5-5", + "name": "Claude Sonnet 5.5", + "display_name": "Claude Sonnet 5.5", "modalities": { "input": [ "text", @@ -127171,10 +126203,10 @@ ] }, "limit": { - "context": 500000, - "output": 500000 + "context": 1000000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -127186,36 +126218,35 @@ "low", "medium", "high", - "xhigh" + "xhigh", + "max" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-05", - "release_date": "2026-09-21", - "last_updated": "2026-09-21", + "knowledge": "2026-06", + "release_date": "2026-09-28", + "last_updated": "2026-09-28", "type": "chat" }, { - "id": "gpt-5.4-pro", - "name": "GPT-5.4 Pro", - "display_name": "GPT-5.4 Pro", + "id": "glm-5.3", + "name": "GLM-5.3", + "display_name": "GLM-5.3", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 1000000, + "output": 131072 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -127224,62 +126255,72 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "high", - "effort_options": [ - "medium", - "high", - "xhigh" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ], - "verbosity": "medium", - "verbosity_options": [ + "effort_options": [ "low", - "medium", - "high" - ], - "visibility": "hidden" + "high", + "max" + ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-03-05", - "last_updated": "2026-03-05", + "attachment": false, + "open_weights": true, + "release_date": "2026-08-14", + "last_updated": "2026-08-14", "type": "chat" }, { - "id": "codestral-2501", - "name": "Codestral 25.01", - "display_name": "Codestral 25.01", + "id": "gpt-5.6-terra-pro", + "name": "gpt-5.6-terra-pro", + "display_name": "gpt-5.6-terra-pro", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 256000, - "output": 256000 + "context": 1050000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "none", + "low", + "medium", + "high", + "xhigh", + "max" + ] + } + }, + "attachment": true, "open_weights": false, - "knowledge": "2024-03", - "release_date": "2025-01-01", - "last_updated": "2025-01-01", + "knowledge": "2026-02-16", + "release_date": "2026-07-09", + "last_updated": "2026-07-09", "type": "chat" }, { - "id": "gpt-5.1-codex-mini", - "name": "GPT-5.1 Codex Mini", - "display_name": "GPT-5.1 Codex Mini", + "id": "gpt-5.6-luna-pro", + "name": "gpt-5.6-luna-pro", + "display_name": "gpt-5.6-luna-pro", "modalities": { "input": [ "text", @@ -127291,47 +126332,38 @@ ] }, "limit": { - "context": 400000, + "context": 1050000, "output": 128000 }, "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "none", "effort_options": [ "none", "low", "medium", - "high" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + "high", + "xhigh", + "max" + ] } }, "attachment": true, "open_weights": false, - "knowledge": "2024-09-30", - "release_date": "2025-11-14", - "last_updated": "2025-11-14", + "knowledge": "2026-02-16", + "release_date": "2026-07-09", + "last_updated": "2026-07-09", "type": "chat" }, { - "id": "llama-3.3-70b-instruct", - "name": "Llama-3.3-70B-Instruct", - "display_name": "Llama-3.3-70B-Instruct", + "id": "MiniMax-M1", + "name": "MiniMax-M1", + "display_name": "MiniMax-M1", "modalities": { "input": [ "text" @@ -127341,8 +126373,8 @@ ] }, "limit": { - "context": 128000, - "output": 32768 + "context": 1000000, + "output": 128000 }, "temperature": true, "tool_call": true, @@ -127350,59 +126382,15 @@ "supported": false }, "attachment": false, - "open_weights": true, - "knowledge": "2023-12", - "release_date": "2024-12-06", - "last_updated": "2024-12-06", - "type": "chat" - }, - { - "id": "o3-mini", - "name": "o3-mini", - "display_name": "o3-mini", - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 200000, - "output": 100000 - }, - "temperature": false, - "tool_call": true, - "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", - "effort_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" - } - }, - "attachment": false, "open_weights": false, - "knowledge": "2024-05", - "release_date": "2024-12-20", - "last_updated": "2025-01-29", + "release_date": "2025-06-16", + "last_updated": "2025-06-16", "type": "chat" }, { - "id": "gpt-5.1", - "name": "GPT-5.1", - "display_name": "GPT-5.1", + "id": "gpt-5.6-sol", + "name": "GPT-5.6 Sol", + "display_name": "GPT-5.6 Sol", "modalities": { "input": [ "text", @@ -127414,26 +126402,28 @@ ] }, "limit": { - "context": 400000, + "context": 1050000, "output": 128000 }, "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, + "default_enabled": true, "mode": "effort", - "effort": "none", + "effort": "medium", "effort_options": [ "none", "low", "medium", - "high" + "high", + "xhigh", + "max" ], "verbosity": "medium", "verbosity_options": [ @@ -127446,15 +126436,15 @@ }, "attachment": true, "open_weights": false, - "knowledge": "2024-09-30", - "release_date": "2025-11-14", - "last_updated": "2025-11-14", + "knowledge": "2026-02-16", + "release_date": "2026-07-09", + "last_updated": "2026-07-09", "type": "chat" }, { - "id": "claude-opus-4-6", - "name": "Claude Opus 4.6", - "display_name": "Claude Opus 4.6", + "id": "claude-sonnet-4-6", + "name": "claude-sonnet-4-6", + "display_name": "claude-sonnet-4-6", "modalities": { "input": [ "text", @@ -127467,7 +126457,7 @@ }, "limit": { "context": 1000000, - "output": 128000 + "output": 64000 }, "temperature": true, "tool_call": true, @@ -127505,84 +126495,46 @@ }, "attachment": true, "open_weights": false, - "knowledge": "2025-05-31", - "release_date": "2026-02-05", - "last_updated": "2026-07-17", + "knowledge": "2025-08-31", + "release_date": "2026-02-17", + "last_updated": "2026-03-13", "type": "chat" }, { - "id": "kimi-k2.7-code", - "name": "Kimi K2.7 Code", - "display_name": "Kimi K2.7 Code", + "id": "glm-4.5v", + "name": "GLM-4.5V", + "display_name": "GLM-4.5V", "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 64000, + "output": 16384 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } - }, "attachment": true, "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-06-12", - "last_updated": "2026-06-12", + "knowledge": "2025-04", + "release_date": "2025-08-11", + "last_updated": "2025-08-11", "type": "chat" }, { - "id": "gpt-image-1.5", - "name": "GPT-Image-1.5", - "display_name": "GPT-Image-1.5", - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text", - "image" - ] - }, - "limit": { - "context": 8192, - "output": 8192 - }, - "temperature": false, - "tool_call": false, - "reasoning": { - "supported": false - }, - "attachment": true, - "open_weights": false, - "release_date": "2025-11-25", - "last_updated": "2025-11-25" - }, - { - "id": "gpt-5.6-luna", - "name": "GPT-5.6 Luna", - "display_name": "GPT-5.6 Luna", + "id": "grok-4.3", + "name": "Grok 4.3", + "display_name": "Grok 4.3", "modalities": { "input": [ "text", @@ -127594,10 +126546,10 @@ ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 1000000, + "output": 30000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -127606,37 +126558,24 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", "effort_options": [ "none", - "low", - "medium", - "high", - "xhigh", - "max" - ], - "verbosity": "medium", - "verbosity_options": [ "low", "medium", "high" - ], - "visibility": "hidden" + ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-02-16", - "release_date": "2026-07-09", - "last_updated": "2026-07-09", + "release_date": "2026-04-17", + "last_updated": "2026-04-17", "type": "chat" }, { - "id": "llama-4-maverick-17b-128e-instruct-fp8", - "name": "Llama 4 Maverick 17B 128E Instruct FP8", - "display_name": "Llama 4 Maverick 17B 128E Instruct FP8", + "id": "gpt-5.1", + "name": "gpt-5.1", + "display_name": "gpt-5.1", "modalities": { "input": [ "text", @@ -127646,41 +126585,11 @@ "text" ] }, - "limit": { - "context": 1000000, - "output": 16384 - }, - "temperature": true, - "tool_call": true, - "reasoning": { - "supported": false - }, - "attachment": true, - "open_weights": true, - "knowledge": "2024-08", - "release_date": "2025-04-05", - "last_updated": "2025-04-05", - "type": "chat" - }, - { - "id": "gpt-5.4-mini", - "name": "GPT-5.4 Mini", - "display_name": "GPT-5.4 Mini", - "modalities": { - "input": [ - "text", - "image", - "pdf" - ], - "output": [ - "text" - ] - }, "limit": { "context": 400000, "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -127696,8 +126605,7 @@ "none", "low", "medium", - "high", - "xhigh" + "high" ], "verbosity": "medium", "verbosity_options": [ @@ -127710,59 +126618,29 @@ }, "attachment": true, "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-03-17", - "last_updated": "2026-03-17", - "type": "chat" - }, - { - "id": "phi-4-reasoning-plus", - "name": "Phi-4-reasoning-plus", - "display_name": "Phi-4-reasoning-plus", - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 32000, - "output": 4096 - }, - "temperature": true, - "tool_call": false, - "reasoning": { - "supported": true, - "default": true - }, - "attachment": false, - "open_weights": true, - "knowledge": "2023-10", - "release_date": "2024-12-11", - "last_updated": "2024-12-11", + "knowledge": "2024-09-30", + "release_date": "2025-11-13", + "last_updated": "2025-11-13", "type": "chat" }, { - "id": "gpt-5.3-codex", - "name": "GPT-5.3 Codex", - "display_name": "GPT-5.3 Codex", + "id": "gemini-2.5-flash", + "name": "gemini-2.5-flash", + "display_name": "gemini-2.5-flash", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 1000000, + "output": 65536 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -127772,47 +126650,45 @@ "reasoning": { "supported": true, "default_enabled": true, - "mode": "effort", - "effort": "medium", - "effort_options": [ - "low", - "medium", - "high", - "xhigh" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + "mode": "budget", + "budget": { + "default": -1, + "min": 0, + "max": 24576, + "auto": -1, + "off": 0, + "unit": "tokens" + }, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thought_signatures" + ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-02-24", - "last_updated": "2026-02-24", + "knowledge": "2025-01", + "release_date": "2025-06-17", + "last_updated": "2025-06-17", "type": "chat" }, { - "id": "gpt-4.1-mini", - "name": "GPT-4.1 mini", - "display_name": "GPT-4.1 mini", + "id": "doubao-seed-1-6-vision-250815", + "name": "doubao-seed-1-6-vision-250815", + "display_name": "doubao-seed-1-6-vision-250815", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1047576, - "output": 32768 + "context": 256000, + "output": 32000 }, "temperature": true, "tool_call": true, @@ -127821,171 +126697,129 @@ }, "attachment": true, "open_weights": false, - "knowledge": "2024-04", - "release_date": "2025-04-14", - "last_updated": "2025-04-14", + "release_date": "2025-08-15", + "last_updated": "2025-08-15", "type": "chat" }, { - "id": "claude-haiku-4-5", - "name": "Claude Haiku 4.5", - "display_name": "Claude Haiku 4.5", + "id": "gemini-3-pro-preview", + "name": "gemini-3-pro-preview", + "display_name": "gemini-3-pro-preview", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 200000, + "context": 1000000, "output": 64000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "budget", - "budget": { - "min": 1024, - "unit": "tokens" - }, - "interleaved": true, + "default_enabled": true, + "mode": "level", + "level": "high", + "level_options": [ + "low", + "high" + ], "summaries": true, "visibility": "summary", "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", - "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." + "thought_signatures" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-02-28", + "knowledge": "2025-06", "release_date": "2025-11-18", "last_updated": "2025-11-18", "type": "chat" }, { - "id": "phi-4", - "name": "Phi-4", - "display_name": "Phi-4", - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 4096 - }, - "temperature": true, - "tool_call": false, - "reasoning": { - "supported": false - }, - "attachment": false, - "open_weights": true, - "knowledge": "2023-10", - "release_date": "2024-12-11", - "last_updated": "2024-12-11", - "type": "chat" - }, - { - "id": "deepseek-v3.2-speciale", - "name": "DeepSeek-V3.2-Speciale", - "display_name": "DeepSeek-V3.2-Speciale", + "id": "grok-4-1-fast-reasoning", + "name": "grok-4-1-fast-reasoning", + "display_name": "grok-4-1-fast-reasoning", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 128000 + "context": 2000000, + "output": 30000 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { "supported": true, "default": true }, - "attachment": false, - "open_weights": true, - "knowledge": "2024-07", - "release_date": "2025-12-01", - "last_updated": "2025-12-01", + "extra_capabilities": { + "reasoning": { + "supported": true + } + }, + "attachment": true, + "open_weights": false, + "knowledge": "2025-06", + "release_date": "2025-11-19", + "last_updated": "2025-11-19", "type": "chat" }, { - "id": "gpt-5.4-nano", - "name": "GPT-5.4 Nano", - "display_name": "GPT-5.4 Nano", + "id": "glm-5", + "name": "glm-5", + "display_name": "glm-5", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 204800, + "output": 131072 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "none", - "effort_options": [ - "none", - "low", - "medium", - "high", - "xhigh" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-03-17", - "last_updated": "2026-03-17", + "attachment": false, + "open_weights": true, + "release_date": "2026-02-12", + "last_updated": "2026-02-12", "type": "chat" }, { @@ -128040,45 +126874,42 @@ }, "attachment": true, "open_weights": false, - "knowledge": "2025-12-31", + "knowledge": "2026-01", "release_date": "2026-05-28", "last_updated": "2026-05-28", "type": "chat" }, { - "id": "gpt-4.1", - "name": "GPT-4.1", - "display_name": "GPT-4.1", + "id": "MiniMax-M2", + "name": "MiniMax-M2", + "display_name": "MiniMax-M2", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1047576, - "output": 32768 + "context": 1000000, + "output": 128000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": false }, - "attachment": true, - "open_weights": false, - "knowledge": "2024-04", - "release_date": "2025-04-14", - "last_updated": "2025-04-14", + "attachment": false, + "open_weights": true, + "release_date": "2025-10-27", + "last_updated": "2025-10-27", "type": "chat" }, { - "id": "gpt-3.5-turbo-0125", - "name": "GPT-3.5 Turbo 0125", - "display_name": "GPT-3.5 Turbo 0125", + "id": "MiniMax-M2.7", + "name": "MiniMax-M2.7", + "display_name": "MiniMax-M2.7", "modalities": { "input": [ "text" @@ -128088,53 +126919,86 @@ ] }, "limit": { - "context": 16384, - "output": 4096 + "context": 204800, + "output": 131072 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } }, "attachment": false, - "open_weights": false, - "knowledge": "2021-08", - "release_date": "2024-01-25", - "last_updated": "2024-01-25", + "open_weights": true, + "release_date": "2026-03-18", + "last_updated": "2026-03-18", "type": "chat" }, { - "id": "ministral-3b", - "name": "Ministral 3B", - "display_name": "Ministral 3B", + "id": "gemini-3.1-pro-preview", + "name": "Gemini 3.1 Pro Preview", + "display_name": "Gemini 3.1 Pro Preview", "modalities": { "input": [ - "text" + "text", + "image", + "video", + "audio", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 8192 + "context": 1048576, + "output": 65536 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, - "open_weights": true, - "knowledge": "2024-03", - "release_date": "2024-10-22", - "last_updated": "2024-10-22", + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "level", + "level": "high", + "level_options": [ + "low", + "high" + ], + "summaries": true, + "visibility": "summary", + "continuation": [ + "thought_signatures" + ] + } + }, + "attachment": true, + "open_weights": false, + "knowledge": "2025-01", + "release_date": "2026-02-19", + "last_updated": "2026-02-19", "type": "chat" }, { - "id": "gpt-5.2-codex", - "name": "GPT-5.2 Codex", - "display_name": "GPT-5.2 Codex", + "id": "gpt-5.5", + "name": "GPT-5.5", + "display_name": "GPT-5.5", "modalities": { "input": [ "text", @@ -128146,7 +127010,7 @@ ] }, "limit": { - "context": 400000, + "context": 1050000, "output": 128000 }, "temperature": false, @@ -128178,15 +127042,15 @@ }, "attachment": true, "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-01-14", - "last_updated": "2026-01-14", + "knowledge": "2025-12-01", + "release_date": "2026-04-23", + "last_updated": "2026-04-23", "type": "chat" }, { - "id": "claude-sonnet-4-5", - "name": "Claude Sonnet 4.5", - "display_name": "Claude Sonnet 4.5", + "id": "claude-fable-5-1", + "name": "Claude Fable 5.1", + "display_name": "Claude Fable 5.1", "modalities": { "input": [ "text", @@ -128198,74 +127062,94 @@ ] }, "limit": { - "context": 200000, - "output": 64000 + "context": 1000000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "budget", - "budget": { - "min": 1024, - "unit": "tokens" - }, + "default_enabled": true, + "mode": "effort", + "effort": "high", + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" + ], "interleaved": true, "summaries": true, - "visibility": "summary", + "visibility": "omitted", "continuation": [ "thinking_blocks" ], "notes": [ - "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", - "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." + "Adaptive thinking is always on for Claude Fable 5 and Claude Mythos 5; thinking.type = \"disabled\" is rejected.", + "Manual budget_tokens requests return 400 on Claude Fable 5 and Claude Mythos 5.", + "thinking.display defaults to omitted; set display to summarized to receive readable thinking summaries." ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-07-31", - "release_date": "2025-11-18", - "last_updated": "2025-11-18", + "knowledge": "2026-06", + "release_date": "2026-09-01", + "last_updated": "2026-09-01", "type": "chat" }, { - "id": "gpt-image-2.5-sunburst", - "name": "GPT Image 2.5 Sunburst", - "display_name": "GPT Image 2.5 Sunburst", + "id": "qwen3.6-plus", + "name": "Qwen3.6 Plus", + "display_name": "Qwen3.6 Plus", "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ - "image" + "text" ] }, "limit": { - "context": 8192, - "output": 8192 + "context": 1000000, + "output": 65536 }, - "temperature": false, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } }, "attachment": true, "open_weights": false, - "release_date": "2026-09-08", - "last_updated": "2026-09-08" + "knowledge": "2025-04", + "release_date": "2026-04-02", + "last_updated": "2026-04-02", + "type": "chat" }, { - "id": "claude-fable-5", - "name": "Claude Fable 5", - "display_name": "Claude Fable 5", + "id": "claude-opus-4-7-thinking", + "name": "claude-opus-4-7-thinking", + "display_name": "claude-opus-4-7-thinking", "modalities": { "input": [ "text", @@ -128306,66 +127190,53 @@ "thinking_blocks" ], "notes": [ - "Adaptive thinking is always on for Claude Fable 5 and Claude Mythos 5; thinking.type = \"disabled\" is rejected.", - "Manual budget_tokens requests return 400 on Claude Fable 5 and Claude Mythos 5.", - "thinking.display defaults to omitted; set display to summarized to receive readable thinking summaries." + "Claude Opus 4.7 and newer Opus models require thinking.type = \"adaptive\" to enable thinking explicitly.", + "Manual budget_tokens requests return 400 on Claude Opus 4.7 and newer adaptive-only Opus models.", + "task_budget is separate from thinking control and should not be treated as a thinking budget." ] } }, "attachment": true, "open_weights": false, "knowledge": "2026-01-31", - "release_date": "2026-06-09", - "last_updated": "2026-06-09", + "release_date": "2026-04-16", + "last_updated": "2026-04-16", "type": "chat" }, { - "id": "gpt-6.1-terra", - "name": "GPT-6.1 Terra", - "display_name": "GPT-6.1 Terra", + "id": "grok-4-fast-reasoning", + "name": "grok-4-fast-reasoning", + "display_name": "grok-4-fast-reasoning", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 2000000, + "output": 30000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "none", - "low", - "medium", - "high", - "xhigh", - "max" - ] - } - }, "attachment": true, "open_weights": false, - "knowledge": "2026-06-01", - "release_date": "2026-10-26", - "last_updated": "2026-10-26", + "knowledge": "2025-06", + "release_date": "2025-12-18", + "last_updated": "2025-12-18", "type": "chat" }, { - "id": "grok-4-20-reasoning", - "name": "Grok 4.20 (Reasoning)", - "display_name": "Grok 4.20 (Reasoning)", + "id": "glm-5.1", + "name": "GLM-5.1", + "display_name": "GLM-5.1", "modalities": { "input": [ "text" @@ -128375,8 +127246,8 @@ ] }, "limit": { - "context": 262000, - "output": 8192 + "context": 200000, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -128386,57 +127257,83 @@ }, "extra_capabilities": { "reasoning": { - "supported": true + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] } }, "attachment": false, - "open_weights": false, - "knowledge": "2025-09", - "release_date": "2026-04-08", - "last_updated": "2026-04-08", + "open_weights": true, + "release_date": "2026-04-07", + "last_updated": "2026-04-07", "type": "chat" }, { - "id": "grok-4-20-non-reasoning", - "name": "Grok 4.20 (Non-Reasoning)", - "display_name": "Grok 4.20 (Non-Reasoning)", + "id": "gemini-3.7-flash", + "name": "Gemini 3.7 Flash", + "display_name": "Gemini 3.7 Flash", "modalities": { "input": [ - "text" + "text", + "image", + "video", + "audio", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 262000, - "output": 8192 + "context": 1048576, + "output": 65536 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true + "supported": true, + "default": true }, "extra_capabilities": { "reasoning": { - "supported": true + "supported": true, + "default_enabled": true, + "mode": "level", + "level": "high", + "level_options": [ + "minimal", + "low", + "medium", + "high" + ], + "summaries": true, + "visibility": "summary", + "continuation": [ + "thought_signatures" + ] } }, - "attachment": false, + "attachment": true, "open_weights": false, - "knowledge": "2025-09", - "release_date": "2026-04-08", - "last_updated": "2026-04-08", + "knowledge": "2026-03", + "release_date": "2026-08-13", + "last_updated": "2026-08-13", "type": "chat" }, { - "id": "claude-fable-5-1", - "name": "Claude Fable 5.1", - "display_name": "Claude Fable 5.1", + "id": "glm-5v-turbo", + "name": "GLM-5V-Turbo", + "display_name": "GLM-5V-Turbo", "modalities": { "input": [ "text", "image", + "video", + "audio", "pdf" ], "output": [ @@ -128444,10 +127341,10 @@ ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 200000, + "output": 131072 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -128456,45 +127353,27 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "high", - "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" - ], "interleaved": true, "summaries": true, - "visibility": "omitted", + "visibility": "summary", "continuation": [ "thinking_blocks" - ], - "notes": [ - "Adaptive thinking is always on for Claude Fable 5 and Claude Mythos 5; thinking.type = \"disabled\" is rejected.", - "Manual budget_tokens requests return 400 on Claude Fable 5 and Claude Mythos 5.", - "thinking.display defaults to omitted; set display to summarized to receive readable thinking summaries." ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-06", - "release_date": "2026-09-01", - "last_updated": "2026-09-01", + "release_date": "2026-04-01", + "last_updated": "2026-04-01", "type": "chat" }, { - "id": "phi-4-multimodal", - "name": "Phi-4-multimodal", - "display_name": "Phi-4-multimodal", + "id": "qwen3-235b-a22b-instruct-2507", + "name": "qwen3-235b-a22b-instruct-2507", + "display_name": "qwen3-235b-a22b-instruct-2507", "modalities": { "input": [ - "text", - "image", - "audio" + "text" ], "output": [ "text" @@ -128502,92 +127381,80 @@ }, "limit": { "context": 128000, - "output": 4096 + "output": 65536 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { "supported": false }, - "attachment": true, + "attachment": false, "open_weights": true, - "knowledge": "2023-10", - "release_date": "2024-12-11", - "last_updated": "2024-12-11", + "knowledge": "2025-04", + "release_date": "2025-07-21", + "last_updated": "2025-07-21", "type": "chat" }, { - "id": "gpt-5.2", - "name": "GPT-5.2", - "display_name": "GPT-5.2", + "id": "glm-5.2", + "name": "GLM-5.2", + "display_name": "GLM-5.2", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 1000000, + "output": 131072 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "none", + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ - "none", - "low", - "medium", "high", - "xhigh" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + "max" + ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2025-12-11", - "last_updated": "2025-12-11", + "attachment": false, + "open_weights": true, + "release_date": "2026-06-13", + "last_updated": "2026-06-13", "type": "chat" }, { - "id": "gpt-6.1-sol", - "name": "GPT-6.1 Sol", - "display_name": "GPT-6.1 Sol", + "id": "glm-5-turbo", + "name": "glm-5-turbo", + "display_name": "glm-5-turbo", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 200000, + "output": 131072 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -128595,53 +127462,25 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, - "attachment": true, + "attachment": false, "open_weights": false, - "knowledge": "2026-04-30", - "release_date": "2026-09-29", - "last_updated": "2026-09-29", + "release_date": "2026-03-16", + "last_updated": "2026-03-16", "type": "chat" }, { - "id": "cohere-embed-v3-english", - "name": "Embed v3 English", - "display_name": "Embed v3 English", - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 512, - "output": 1024 - }, - "temperature": false, - "tool_call": false, - "reasoning": { - "supported": false - }, - "attachment": false, - "open_weights": true, - "release_date": "2023-11-07", - "last_updated": "2023-11-07", - "type": "embedding" - }, - { - "id": "cohere-embed-v-4-0", - "name": "Embed v4", - "display_name": "Embed v4", + "id": "gpt-5.2-chat-latest", + "name": "gpt-5.2-chat-latest", + "display_name": "gpt-5.2-chat-latest", "modalities": { "input": [ "text", @@ -128653,36 +127492,7 @@ }, "limit": { "context": 128000, - "output": 1536 - }, - "temperature": false, - "tool_call": false, - "reasoning": { - "supported": false - }, - "attachment": true, - "open_weights": true, - "release_date": "2025-04-15", - "last_updated": "2025-04-15", - "type": "embedding" - }, - { - "id": "gpt-5-nano", - "name": "GPT-5 Nano", - "display_name": "GPT-5 Nano", - "modalities": { - "input": [ - "text", - "image", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 400000, - "output": 128000 + "output": 16384 }, "temperature": false, "tool_call": true, @@ -128692,36 +127502,22 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", "effort_options": [ - "minimal", - "low", - "medium", - "high" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + "medium" + ] } }, "attachment": true, "open_weights": false, - "knowledge": "2024-05-30", - "release_date": "2025-08-07", - "last_updated": "2025-08-07", + "knowledge": "2025-08-31", + "release_date": "2025-12-11", + "last_updated": "2025-12-11", "type": "chat" }, { - "id": "codex-mini", - "name": "Codex Mini", - "display_name": "Codex Mini", + "id": "claude-opus-5", + "name": "Claude Opus 5", + "display_name": "Claude Opus 5", "modalities": { "input": [ "text", @@ -128733,8 +127529,8 @@ ] }, "limit": { - "context": 200000, - "output": 100000 + "context": 1000000, + "output": 128000 }, "temperature": false, "tool_call": true, @@ -128744,36 +127540,40 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, "effort_options": [ "low", "medium", - "high" + "high", + "xhigh", + "max" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2024-04", - "release_date": "2025-05-16", - "last_updated": "2025-05-16", + "knowledge": "2026-05", + "release_date": "2026-07-24", + "last_updated": "2026-07-24", "type": "chat" }, { - "id": "grok-4.6", - "name": "Grok 4.6", - "display_name": "Grok 4.6", + "id": "qwen3.5-35b-a3b", + "name": "Qwen3.5 35B-A3B", + "display_name": "Qwen3.5 35B-A3B", "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 128000 + "context": 262144, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -128784,66 +127584,29 @@ "extra_capabilities": { "reasoning": { "supported": true, - "effort_options": [ - "low", - "medium", - "high", - "xhigh" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2026-02-01", - "release_date": "2026-08-12", - "last_updated": "2026-08-12", - "type": "chat" - } - ] - }, - "anyapi": { - "id": "anyapi", - "name": "AnyAPI", - "display_name": "AnyAPI", - "api": "https://api.anyapi.ai/v1", - "doc": "https://docs.anyapi.ai", - "models": [ - { - "id": "cohere/command-r-plus-08-2024", - "name": "Command R+", - "display_name": "Command R+", - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 4000 - }, - "temperature": true, - "tool_call": true, - "reasoning": { - "supported": false - }, - "attachment": false, "open_weights": true, - "knowledge": "2024-06-01", - "release_date": "2024-08-30", - "last_updated": "2024-08-30", + "release_date": "2026-02-23", + "last_updated": "2026-02-23", "type": "chat" }, { - "id": "xai/grok-4.3", - "name": "Grok 4.3", - "display_name": "Grok 4.3", + "id": "qwen3.8-max", + "name": "Qwen3.8 Max", + "display_name": "Qwen3.8 Max", "modalities": { "input": [ "text", "image", + "video", "pdf" ], "output": [ @@ -128852,7 +127615,7 @@ }, "limit": { "context": 1000000, - "output": 30000 + "output": 131072 }, "temperature": true, "tool_call": true, @@ -128862,19 +127625,30 @@ }, "extra_capabilities": { "reasoning": { - "supported": true + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "effort_options": [ + "low", + "medium", + "xhigh" + ] } }, "attachment": true, "open_weights": false, - "release_date": "2026-04-17", - "last_updated": "2026-04-17", + "release_date": "2026-08-03", + "last_updated": "2026-08-03", "type": "chat" }, { - "id": "anthropic/claude-opus-4-7", - "name": "Claude Opus 4.7", - "display_name": "Claude Opus 4.7", + "id": "claude-opus-4-1-20250805", + "name": "claude-opus-4-1-20250805", + "display_name": "claude-opus-4-1-20250805", "modalities": { "input": [ "text", @@ -128886,10 +127660,10 @@ ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 200000, + "output": 32000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -128899,98 +127673,75 @@ "reasoning": { "supported": true, "default_enabled": false, - "mode": "effort", - "effort": "high", - "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" - ], + "mode": "budget", + "budget": { + "min": 1024, + "unit": "tokens" + }, "interleaved": true, "summaries": true, - "visibility": "omitted", + "visibility": "summary", "continuation": [ "thinking_blocks" ], "notes": [ - "Claude Opus 4.7 and newer Opus models require thinking.type = \"adaptive\" to enable thinking explicitly.", - "Manual budget_tokens requests return 400 on Claude Opus 4.7 and newer adaptive-only Opus models.", - "task_budget is separate from thinking control and should not be treated as a thinking budget." + "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", + "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-01-31", - "release_date": "2026-04-16", - "last_updated": "2026-04-16", + "knowledge": "2025-03-31", + "release_date": "2025-08-05", + "last_updated": "2025-08-05", "type": "chat" }, { - "id": "anthropic/claude-sonnet-4-6", - "name": "Claude Sonnet 4.6", - "display_name": "Claude Sonnet 4.6", + "id": "grok-4.6", + "name": "Grok 4.6", + "display_name": "Grok 4.6", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 64000 + "context": 500000, + "output": 500000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "mixed", - "budget": { - "min": 1024, - "unit": "tokens" - }, - "effort": "high", "effort_options": [ "low", "medium", "high", - "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Anthropic recommends adaptive thinking with effort for Claude 4.6; budget_tokens remains a deprecated compatibility path.", - "Anthropic API defaults effort to high; lower effort levels should be chosen per workload." + "xhigh" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-02-17", - "last_updated": "2026-03-13", + "knowledge": "2026-02-01", + "release_date": "2026-08-12", + "last_updated": "2026-08-12", "type": "chat" }, { - "id": "anthropic/claude-opus-4-6", - "name": "Claude Opus 4.6", - "display_name": "Claude Opus 4.6", + "id": "gpt-5.6-sol-pro", + "name": "gpt-5.6-sol-pro", + "display_name": "gpt-5.6-sol-pro", "modalities": { "input": [ "text", @@ -129002,110 +127753,109 @@ ] }, "limit": { - "context": 1000000, + "context": 1050000, "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "mixed", - "budget": { - "min": 1024, - "unit": "tokens" - }, - "effort": "high", "effort_options": [ + "none", "low", "medium", "high", + "xhigh", "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Anthropic recommends adaptive thinking with effort for Claude 4.6; budget_tokens remains a deprecated compatibility path.", - "Anthropic API defaults effort to high; lower effort levels should be chosen per workload." ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-05-31", - "release_date": "2026-02-05", - "last_updated": "2026-03-13", + "knowledge": "2026-02-16", + "release_date": "2026-07-09", + "last_updated": "2026-07-09", "type": "chat" }, { - "id": "anthropic/claude-haiku-4-5", - "name": "Claude Haiku 4.5 (latest)", - "display_name": "Claude Haiku 4.5 (latest)", + "id": "glm-4.5", + "name": "GLM-4.5", + "display_name": "GLM-4.5", + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "output": 98304 + }, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": true, + "default": true + }, + "attachment": false, + "open_weights": true, + "knowledge": "2025-04", + "release_date": "2025-07-28", + "last_updated": "2025-07-28", + "type": "chat" + }, + { + "id": "qwen3.7-plus", + "name": "Qwen3.7 Plus", + "display_name": "Qwen3.7 Plus", "modalities": { "input": [ "text", "image", - "pdf" + "video" ], "output": [ "text" ] }, "limit": { - "context": 200000, + "context": 1000000, "output": 64000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "budget", - "budget": { - "min": 1024, - "unit": "tokens" - }, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", - "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." - ] + "supported": true } }, "attachment": true, "open_weights": false, - "knowledge": "2025-02-28", - "release_date": "2025-10-15", - "last_updated": "2025-10-15", + "knowledge": "2025-04", + "release_date": "2026-06-02", + "last_updated": "2026-06-02", "type": "chat" }, { - "id": "anthropic/claude-sonnet-4-5", - "name": "Claude Sonnet 4.5 (latest)", - "display_name": "Claude Sonnet 4.5 (latest)", + "id": "gemini-3.1-flash-lite", + "name": "Gemini 3.1 Flash Lite", + "display_name": "Gemini 3.1 Flash Lite", "modalities": { "input": [ "text", "image", + "video", + "audio", "pdf" ], "output": [ @@ -129113,98 +127863,67 @@ ] }, "limit": { - "context": 200000, - "output": 64000 + "context": 1048576, + "output": 65536 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "budget", - "budget": { - "min": 1024, - "unit": "tokens" - }, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", - "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." + "effort_options": [ + "minimal", + "low", + "medium", + "high" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-07-31", - "release_date": "2025-09-29", - "last_updated": "2025-09-29", + "knowledge": "2025-01", + "release_date": "2026-05-07", + "last_updated": "2026-05-07", "type": "chat" }, { - "id": "deepseek/deepseek-v4-flash", - "name": "DeepSeek V4 Flash", - "display_name": "DeepSeek V4 Flash", + "id": "gpt-4.1-mini", + "name": "gpt-4.1-mini", + "display_name": "gpt-4.1-mini", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 384000 + "context": 1047576, + "output": 32768 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "high", - "effort_options": [ - "low", - "high", - "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." - ] - } + "supported": false }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-04-24", - "last_updated": "2026-04-24", + "attachment": true, + "open_weights": false, + "knowledge": "2024-04", + "release_date": "2025-04-14", + "last_updated": "2025-04-14", "type": "chat" }, { - "id": "deepseek/deepseek-v4-pro", - "name": "DeepSeek V4 Pro", - "display_name": "DeepSeek V4 Pro", + "id": "kimi-k2-0905-preview", + "name": "kimi-k2-0905-preview", + "display_name": "kimi-k2-0905-preview", "modalities": { "input": [ "text" @@ -129214,88 +127933,55 @@ ] }, "limit": { - "context": 1000000, - "output": 384000 + "context": 262144, + "output": 262144 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "high", - "effort_options": [ - "low", - "high", - "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." - ] - } + "supported": false }, "attachment": false, "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-04-24", - "last_updated": "2026-04-24", + "knowledge": "2025-06", + "release_date": "2025-09-05", + "last_updated": "2025-09-05", "type": "chat" }, { - "id": "deepseek/deepseek-r1", - "name": "DeepSeek Reasoner", - "display_name": "DeepSeek Reasoner", + "id": "gpt-4.1", + "name": "gpt-4.1", + "display_name": "gpt-4.1", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 384000 + "context": 1047576, + "output": 32768 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } + "supported": false }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-09", - "release_date": "2025-12-01", - "last_updated": "2026-02-28", + "attachment": true, + "open_weights": false, + "knowledge": "2024-04", + "release_date": "2025-04-14", + "last_updated": "2025-04-14", "type": "chat" }, { - "id": "deepseek/deepseek-chat", - "name": "DeepSeek Chat", - "display_name": "DeepSeek Chat", + "id": "qwen3-max-2025-09-23", + "name": "qwen3-max-2025-09-23", + "display_name": "qwen3-max-2025-09-23", "modalities": { "input": [ "text" @@ -129305,8 +127991,8 @@ ] }, "limit": { - "context": 1000000, - "output": 384000 + "context": 258048, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -129314,30 +128000,27 @@ "supported": false }, "attachment": false, - "open_weights": true, - "knowledge": "2025-09", - "release_date": "2025-12-01", - "last_updated": "2026-02-28", + "open_weights": false, + "knowledge": "2025-04", + "release_date": "2025-09-23", + "last_updated": "2025-09-23", "type": "chat" }, { - "id": "google/gemini-3-flash-preview", - "name": "Gemini 3 Flash Preview", - "display_name": "Gemini 3 Flash Preview", + "id": "gemini-3-flash-preview", + "name": "gemini-3-flash-preview", + "display_name": "gemini-3-flash-preview", "modalities": { "input": [ "text", - "image", - "video", - "audio", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1048576, + "context": 1000000, "output": 65536 }, "temperature": true, @@ -129367,73 +128050,48 @@ }, "attachment": true, "open_weights": false, - "knowledge": "2025-01", + "knowledge": "2025-06", "release_date": "2025-12-17", "last_updated": "2025-12-17", "type": "chat" }, { - "id": "google/gemini-2.5-pro", - "name": "Gemini 2.5 Pro", - "display_name": "Gemini 2.5 Pro", + "id": "gemini-2.5-flash-image", + "name": "gemini-2.5-flash-image", + "display_name": "gemini-2.5-flash-image", "modalities": { "input": [ "text", - "image", - "audio", - "video", - "pdf" + "image" ], "output": [ - "text" + "text", + "image" ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 32768, + "output": 32768 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "budget", - "budget": { - "default": -1, - "min": 128, - "max": 32768, - "auto": -1, - "unit": "tokens" - }, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thought_signatures" - ] - } + "supported": false }, "attachment": true, "open_weights": false, "knowledge": "2025-01", - "release_date": "2025-06-17", - "last_updated": "2025-06-17", - "type": "chat" + "release_date": "2025-08-26", + "last_updated": "2025-08-26" }, { - "id": "google/gemini-2.5-flash", - "name": "Gemini 2.5 Flash", - "display_name": "Gemini 2.5 Flash", + "id": "gpt-6-luna", + "name": "GPT-6 Luna", + "display_name": "GPT-6 Luna", "modalities": { "input": [ "text", "image", - "audio", - "video", "pdf" ], "output": [ @@ -129441,10 +128099,10 @@ ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 1050000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -129452,52 +128110,41 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "budget", - "budget": { - "default": -1, - "min": 0, - "max": 24576, - "auto": -1, - "off": 0, - "unit": "tokens" - }, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thought_signatures" + "effort_options": [ + "none", + "low", + "medium", + "high", + "xhigh", + "max" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-01", - "release_date": "2025-06-17", - "last_updated": "2025-06-17", + "knowledge": "2026-05-18", + "release_date": "2026-09-22", + "last_updated": "2026-09-22", "type": "chat" }, { - "id": "google/gemini-3-pro-preview", - "name": "Gemini 3 Pro Preview", - "display_name": "Gemini 3 Pro Preview", + "id": "gpt-5.1-chat-latest", + "name": "gpt-5.1-chat-latest", + "display_name": "gpt-5.1-chat-latest", "modalities": { "input": [ "text", - "image", - "video", - "audio", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 128000, + "output": 16384 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -129505,38 +128152,26 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "level", - "level": "high", - "level_options": [ - "low", - "high" - ], - "summaries": true, - "visibility": "summary", - "continuation": [ - "thought_signatures" + "effort_options": [ + "medium" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-01", - "release_date": "2025-11-18", - "last_updated": "2025-11-18", + "knowledge": "2024-09-30", + "release_date": "2025-11-13", + "last_updated": "2025-11-13", "type": "chat" }, { - "id": "google/gemini-2.5-flash-lite", - "name": "Gemini 2.5 Flash-Lite", - "display_name": "Gemini 2.5 Flash-Lite", + "id": "o3", + "name": "o3", + "display_name": "o3", "modalities": { "input": [ "text", "image", - "audio", - "video", "pdf" ], "output": [ @@ -129544,59 +128179,55 @@ ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 200000, + "output": 100000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "budget", - "budget": { - "default": -1, - "min": 512, - "max": 24576, - "auto": -1, - "unit": "tokens" - }, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thought_signatures" - ] + "default_enabled": true, + "mode": "effort", + "effort": "medium", + "effort_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, "attachment": true, "open_weights": false, - "knowledge": "2025-01", - "release_date": "2025-06-17", - "last_updated": "2025-06-17", + "knowledge": "2024-05", + "release_date": "2025-04-16", + "last_updated": "2025-04-16", "type": "chat" }, { - "id": "openai/gpt-5", - "name": "GPT-5", - "display_name": "GPT-5", + "id": "MiniMax-M3", + "name": "MiniMax-M3", + "display_name": "MiniMax-M3", "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 1048576, + "output": 512000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -129604,36 +128235,19 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", - "effort_options": [ - "minimal", - "low", - "medium", - "high" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + "supported": true } }, "attachment": true, - "open_weights": false, - "knowledge": "2024-09-30", - "release_date": "2025-08-07", - "last_updated": "2025-08-07", + "open_weights": true, + "release_date": "2026-06-01", + "last_updated": "2026-06-01", "type": "chat" }, { - "id": "openai/gpt-5.4", - "name": "GPT-5.4", - "display_name": "GPT-5.4", + "id": "gemini-3.1-flash-image-preview", + "name": "gemini-3.1-flash-image-preview", + "display_name": "gemini-3.1-flash-image-preview", "modalities": { "input": [ "text", @@ -129641,68 +128255,50 @@ "pdf" ], "output": [ - "text" + "text", + "image" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 131072, + "output": 32768 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": false + "supported": true }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "none", - "effort_options": [ - "none", - "low", - "medium", - "high", - "xhigh" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + "supported": true } }, "attachment": true, "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-03-05", - "last_updated": "2026-03-05", - "type": "chat" + "knowledge": "2025-01", + "release_date": "2026-02-26", + "last_updated": "2026-02-26" }, { - "id": "openai/o3", - "name": "o3", - "display_name": "o3", + "id": "gemini-3-pro-image-preview", + "name": "gemini-3-pro-image-preview", + "display_name": "gemini-3-pro-image-preview", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ - "text" + "text", + "image" ] }, "limit": { - "context": 200000, - "output": 100000 + "context": 32768, + "output": 64000 }, - "temperature": false, - "tool_call": true, + "temperature": true, + "tool_call": false, "reasoning": { "supported": true, "default": true @@ -129711,31 +128307,34 @@ "reasoning": { "supported": true, "default_enabled": true, - "mode": "effort", - "effort": "medium", - "effort_options": [ + "mode": "level", + "level": "high", + "level_options": [ "low", - "medium", "high" ], - "visibility": "hidden" + "summaries": true, + "visibility": "summary", + "continuation": [ + "thought_signatures" + ] } }, "attachment": true, "open_weights": false, - "knowledge": "2024-05", - "release_date": "2025-04-16", - "last_updated": "2025-04-16", - "type": "chat" + "knowledge": "2025-06", + "release_date": "2025-11-20", + "last_updated": "2025-11-20" }, { - "id": "openai/o4-mini", - "name": "o4-mini", - "display_name": "o4-mini", + "id": "claude-opus-4-5-20251101", + "name": "claude-opus-4-5-20251101", + "display_name": "claude-opus-4-5-20251101", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" @@ -129743,51 +128342,65 @@ }, "limit": { "context": 200000, - "output": 100000 + "output": 64000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", + "default_enabled": false, + "mode": "mixed", + "budget": { + "min": 1024, + "unit": "tokens" + }, + "effort": "high", "effort_options": [ "low", "medium", "high" ], - "visibility": "hidden" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Claude Opus 4.5 uses manual thinking.type = \"enabled\" with budget_tokens; effort can be used alongside the thinking budget.", + "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header." + ] } }, "attachment": true, "open_weights": false, - "knowledge": "2024-05", - "release_date": "2025-04-16", - "last_updated": "2025-04-16", + "knowledge": "2025-03-31", + "release_date": "2025-11-24", + "last_updated": "2025-11-24", "type": "chat" }, { - "id": "openai/gpt-5-mini", - "name": "GPT-5 Mini", - "display_name": "GPT-5 Mini", + "id": "kimi-k2.5", + "name": "Kimi K2.5", + "display_name": "Kimi K2.5", "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 262144, + "output": 262144 }, "temperature": false, "tool_call": true, @@ -129798,48 +128411,40 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", - "effort_options": [ - "minimal", - "low", - "medium", - "high" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2024-05-30", - "release_date": "2025-08-07", - "last_updated": "2025-08-07", + "open_weights": true, + "knowledge": "2025-01", + "release_date": "2026-01-27", + "last_updated": "2026-01-27", "type": "chat" }, { - "id": "openai/o3-mini", - "name": "o3-mini", - "display_name": "o3-mini", + "id": "kimi-k2.6", + "name": "Kimi K2.6", + "display_name": "Kimi K2.6", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 100000 + "context": 262144, + "output": 262144 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -129848,250 +128453,259 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", - "effort_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] } }, - "attachment": false, - "open_weights": false, - "knowledge": "2024-05", - "release_date": "2024-12-20", - "last_updated": "2025-01-29", + "attachment": true, + "open_weights": true, + "knowledge": "2025-01", + "release_date": "2026-04-20", + "last_updated": "2026-04-20", "type": "chat" }, { - "id": "openai/gpt-5.1", - "name": "GPT-5.1", - "display_name": "GPT-5.1", + "id": "gemini-3.5-flash-lite", + "name": "Gemini 3.5 Flash Lite", + "display_name": "Gemini 3.5 Flash Lite", "modalities": { "input": [ "text", - "image" + "image", + "video", + "audio", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 1048576, + "output": 65536 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "none", "effort_options": [ - "none", - "low", - "medium", - "high" - ], - "verbosity": "medium", - "verbosity_options": [ + "minimal", "low", "medium", "high" - ], - "visibility": "hidden" + ] } }, "attachment": true, "open_weights": false, - "knowledge": "2024-09-30", - "release_date": "2025-11-13", - "last_updated": "2025-11-13", + "knowledge": "2026-03", + "release_date": "2026-07-21", + "last_updated": "2026-07-21", "type": "chat" - }, + } + ] + }, + "empiriolabs": { + "id": "empiriolabs", + "name": "EmpirioLabs AI", + "display_name": "EmpirioLabs AI", + "api": "https://api.empiriolabs.ai/v1", + "doc": "https://docs.empiriolabs.ai", + "models": [ { - "id": "openai/gpt-4.1-mini", - "name": "GPT-4.1 mini", - "display_name": "GPT-4.1 mini", + "id": "gemma-3-27b", + "name": "Gemma 3 27B", + "display_name": "Gemma 3 27B", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1047576, - "output": 32768 + "context": 131072, + "output": 16384 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { "supported": false }, "attachment": true, - "open_weights": false, - "knowledge": "2024-04", - "release_date": "2025-04-14", - "last_updated": "2025-04-14", + "open_weights": true, + "knowledge": "2024-08", + "release_date": "2025-03-12", + "last_updated": "2025-03-12", "type": "chat" }, { - "id": "openai/gpt-4.1", - "name": "GPT-4.1", - "display_name": "GPT-4.1", + "id": "glm-4-7-flashx", + "name": "GLM 4.7 FlashX", + "display_name": "GLM 4.7 FlashX", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1047576, - "output": 32768 + "context": 202752, + "output": 131072 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": true, - "open_weights": false, - "knowledge": "2024-04", - "release_date": "2025-04-14", - "last_updated": "2025-04-14", + "attachment": false, + "open_weights": true, + "knowledge": "2025-04", + "release_date": "2026-01-19", + "last_updated": "2026-01-19", "type": "chat" }, { - "id": "openai/gpt-5.2", - "name": "GPT-5.2", - "display_name": "GPT-5.2", + "id": "kimi-k3", + "name": "Kimi K3", + "display_name": "Kimi K3", "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 1048576, + "output": 131072 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "none", "effort_options": [ - "none", "low", - "medium", "high", - "xhigh" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + "max" + ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2025-12-11", - "last_updated": "2025-12-11", + "open_weights": true, + "release_date": "2026-07-16", + "last_updated": "2026-07-16", "type": "chat" }, { - "id": "perplexity/sonar-pro", - "name": "Sonar Pro", - "display_name": "Sonar Pro", + "id": "muse-spark-1-2", + "name": "Muse Spark 1.2", + "display_name": "Muse Spark 1.2", "modalities": { "input": [ "text", - "image" + "image", + "video", + "audio" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 8192 + "context": 1048576, + "output": 131072 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "minimal", + "low", + "medium", + "high", + "xhigh" + ] + } }, "attachment": true, "open_weights": false, - "knowledge": "2025-09-01", - "release_date": "2024-01-01", - "last_updated": "2025-09-01", + "release_date": "2026-08-05", + "last_updated": "2026-08-05", "type": "chat" }, { - "id": "perplexity/sonar-reasoning-pro", - "name": "Sonar Reasoning Pro", - "display_name": "Sonar Reasoning Pro", + "id": "qwen3-5-4b", + "name": "Qwen3.5 4B", + "display_name": "Qwen3.5 4B", "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 4096 + "context": 262144, + "output": 32768 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { "supported": true, "default": true }, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "none", + "low", + "medium", + "high", + "max" + ] + } + }, "attachment": true, - "open_weights": false, - "knowledge": "2025-09-01", - "release_date": "2024-01-01", - "last_updated": "2025-09-01", + "open_weights": true, + "release_date": "2026-03-02", + "last_updated": "2026-03-02", "type": "chat" }, { - "id": "mistralai/devstral-2512", - "name": "Devstral 2", - "display_name": "Devstral 2", + "id": "minimax-m2-7", + "name": "MiniMax M2.7", + "display_name": "MiniMax M2.7", "modalities": { "input": [ "text" @@ -130101,75 +128715,79 @@ ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 204800, + "output": 131072 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, "attachment": false, "open_weights": true, - "knowledge": "2025-12", - "release_date": "2025-12-09", - "last_updated": "2025-12-09", + "release_date": "2026-03-18", + "last_updated": "2026-03-18", "type": "chat" }, { - "id": "mistralai/mistral-large-2512", - "name": "Mistral Large 3", - "display_name": "Mistral Large 3", + "id": "qwen3-8-flash", + "name": "Qwen3.8 Flash", + "display_name": "Qwen3.8 Flash", "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 983616, + "output": 131072 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "none", + "low", + "medium", + "high", + "max" + ] + } }, "attachment": true, - "open_weights": true, - "knowledge": "2024-11", - "release_date": "2025-12-02", - "last_updated": "2025-12-02", + "open_weights": false, + "release_date": "2026-08-26", + "last_updated": "2026-08-26", "type": "chat" - } - ] - }, - "prime": { - "id": "prime", - "name": "Prime Inference", - "display_name": "Prime Inference", - "api": "https://api.pinference.ai/api/v1", - "doc": "https://docs.primeintellect.ai/inference/overview", - "models": [ + }, { - "id": "x-ai/grok-4.7", - "name": "Grok 4.7", - "display_name": "Grok 4.7", + "id": "step-5-preview", + "name": "Step 5 Preview", + "display_name": "Step 5 Preview", "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 500000, - "output": 450000 + "context": 1024000, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -130182,34 +128800,31 @@ "effort_options": [ "low", "medium", - "high", - "xhigh" + "high" ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2026-05", - "release_date": "2026-09-21", - "last_updated": "2026-09-21", + "open_weights": true, + "release_date": "2026-09-16", + "last_updated": "2026-09-20", "type": "chat" }, { - "id": "x-ai/grok-4.6", - "name": "Grok 4.6", - "display_name": "Grok 4.6", + "id": "glm-5-2", + "name": "GLM 5.2", + "display_name": "GLM 5.2", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 500000, - "output": 450000 + "context": 1048576, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -130219,40 +128834,42 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, "effort_options": [ + "none", + "minimal", "low", "medium", "high", - "xhigh" + "xhigh", + "max" ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-02-01", - "release_date": "2026-08-12", - "last_updated": "2026-08-12", + "attachment": false, + "open_weights": true, + "release_date": "2026-06-13", + "last_updated": "2026-06-13", "type": "chat" }, { - "id": "anthropic/claude-fable-5.1", - "name": "Claude Fable 5.1", - "display_name": "Claude Fable 5.1", + "id": "minimax-m3-1-flash", + "name": "MiniMax M3.1 Flash", + "display_name": "MiniMax M3.1 Flash", "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 1048576, + "output": 524288 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -130260,52 +128877,37 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "high", "effort_options": [ "low", "medium", "high", "xhigh", "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "omitted", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Adaptive thinking is always on for Claude Fable 5 and Claude Mythos 5; thinking.type = \"disabled\" is rejected.", - "Manual budget_tokens requests return 400 on Claude Fable 5 and Claude Mythos 5.", - "thinking.display defaults to omitted; set display to summarized to receive readable thinking summaries." ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-06", - "release_date": "2026-09-01", - "last_updated": "2026-09-01", + "release_date": "2026-10-19", + "last_updated": "2026-10-19", "type": "chat" }, { - "id": "anthropic/claude-opus-5", - "name": "Claude Opus 5", - "display_name": "Claude Opus 5", + "id": "seed-2-0-pro", + "name": "Seed 2.0 Pro", + "display_name": "Seed 2.0 Pro", "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 1000000, + "context": 229096, "output": 128000 }, "temperature": false, @@ -130316,98 +128918,69 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, "effort_options": [ - "none", "low", "medium", - "high", - "xhigh", - "max" + "high" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-05", - "release_date": "2026-07-24", - "last_updated": "2026-07-24", + "release_date": "2026-02-14", + "last_updated": "2026-02-14", "type": "chat" }, { - "id": "anthropic/claude-opus-4.8", - "name": "Claude Opus 4.8", - "display_name": "Claude Opus 4.8", + "id": "mimo-v2-6-pro", + "name": "MiMo V2.6 Pro", + "display_name": "MiMo V2.6 Pro", "modalities": { "input": [ "text", - "image" + "image", + "audio", + "video" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 1048576, + "output": 131072 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "high", - "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "omitted", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Claude Opus 4.7 and newer Opus models require thinking.type = \"adaptive\" to enable thinking explicitly.", - "Manual budget_tokens requests return 400 on Claude Opus 4.7 and newer adaptive-only Opus models.", - "task_budget is separate from thinking control and should not be treated as a thinking budget." - ] - } + "default": true }, "attachment": true, - "open_weights": false, - "knowledge": "2026-01", - "release_date": "2026-05-28", - "last_updated": "2026-05-28", + "open_weights": true, + "release_date": "2026-09-22", + "last_updated": "2026-09-22", "type": "chat" }, { - "id": "anthropic/claude-sonnet-5", - "name": "Claude Sonnet 5", - "display_name": "Claude Sonnet 5", + "id": "gemma-4-26b-a4b", + "name": "Gemma 4 26B-A4B", + "display_name": "Gemma 4 26B-A4B", "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 262144, + "output": 32768 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -130415,28 +128988,25 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, "effort_options": [ "none", "low", "medium", "high", - "xhigh", "max" ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2026-01-31", - "release_date": "2026-06-30", - "last_updated": "2026-06-30", + "open_weights": true, + "release_date": "2026-04-02", + "last_updated": "2026-04-02", "type": "chat" }, { - "id": "anthropic/claude-opus-4.6", - "name": "Claude Opus 4.6", - "display_name": "Claude Opus 4.6", + "id": "fugu-max", + "name": "Fugu Max", + "display_name": "Fugu Max", "modalities": { "input": [ "text", @@ -130448,181 +129018,126 @@ }, "limit": { "context": 1000000, - "output": 128000 + "output": 131072 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "mixed", - "budget": { - "min": 1024, - "unit": "tokens" - }, - "effort": "high", "effort_options": [ - "low", - "medium", "high", + "xhigh", "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Anthropic recommends adaptive thinking with effort for Claude 4.6; budget_tokens remains a deprecated compatibility path.", - "Anthropic API defaults effort to high; lower effort levels should be chosen per workload." ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-05-31", - "release_date": "2026-02-05", - "last_updated": "2026-03-13", + "release_date": "2026-09-11", + "last_updated": "2026-09-11", "type": "chat" }, { - "id": "anthropic/claude-sonnet-4.6", - "name": "Claude Sonnet 4.6", - "display_name": "Claude Sonnet 4.6", + "id": "seed-2-0-lite", + "name": "Seed 2.0 Lite", + "display_name": "Seed 2.0 Lite", "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 1000000, + "context": 229096, "output": 128000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "mixed", - "budget": { - "min": 1024, - "unit": "tokens" - }, - "effort": "high", "effort_options": [ "low", "medium", - "high", - "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Anthropic recommends adaptive thinking with effort for Claude 4.6; budget_tokens remains a deprecated compatibility path.", - "Anthropic API defaults effort to high; lower effort levels should be chosen per workload." + "high" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-02-17", - "last_updated": "2026-03-13", + "release_date": "2026-02-14", + "last_updated": "2026-02-14", "type": "chat" }, { - "id": "anthropic/claude-opus-4.7", - "name": "Claude Opus 4.7", - "display_name": "Claude Opus 4.7", + "id": "seed-2-0-code", + "name": "Seed 2.0 Code", + "display_name": "Seed 2.0 Code", "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 1000000, + "context": 229096, "output": 128000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "high", "effort_options": [ "low", "medium", - "high", - "xhigh", - "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "omitted", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Claude Opus 4.7 and newer Opus models require thinking.type = \"adaptive\" to enable thinking explicitly.", - "Manual budget_tokens requests return 400 on Claude Opus 4.7 and newer adaptive-only Opus models.", - "task_budget is separate from thinking control and should not be treated as a thinking budget." + "high" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-01-31", - "release_date": "2026-04-16", - "last_updated": "2026-04-16", + "release_date": "2026-02-14", + "last_updated": "2026-02-14", "type": "chat" }, { - "id": "anthropic/claude-sonnet-5.5", - "name": "Claude Sonnet 5.5", - "display_name": "Claude Sonnet 5.5", + "id": "qwen3-6-plus", + "name": "Qwen3.6 Plus", + "display_name": "Qwen3.6 Plus", "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 983616, + "output": 65536 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -130631,39 +129146,40 @@ "extra_capabilities": { "reasoning": { "effort_options": [ + "none", "low", "medium", "high", - "xhigh", "max" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-06", - "release_date": "2026-09-28", - "last_updated": "2026-09-28", + "knowledge": "2025-04", + "release_date": "2026-04-02", + "last_updated": "2026-04-02", "type": "chat" }, { - "id": "anthropic/claude-fable-5", - "name": "Claude Fable 5", - "display_name": "Claude Fable 5", + "id": "qwen3-5-35b-a3b", + "name": "Qwen3.5 35B-A3B", + "display_name": "Qwen3.5 35B-A3B", "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 258048, + "output": 64000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -130671,55 +129187,41 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "high", "effort_options": [ + "none", "low", "medium", "high", - "xhigh", "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "omitted", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Adaptive thinking is always on for Claude Fable 5 and Claude Mythos 5; thinking.type = \"disabled\" is rejected.", - "Manual budget_tokens requests return 400 on Claude Fable 5 and Claude Mythos 5.", - "thinking.display defaults to omitted; set display to summarized to receive readable thinking summaries." ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2026-01-31", - "release_date": "2026-06-09", - "last_updated": "2026-06-09", + "open_weights": true, + "release_date": "2026-02-23", + "last_updated": "2026-02-23", "type": "chat" }, { - "id": "anthropic/claude-opus-5.5", - "name": "Claude Opus 5.5", - "display_name": "Claude Opus 5.5", + "id": "qwen3-8-omni-flash", + "name": "Qwen3.8 Omni Flash", + "display_name": "Qwen3.8 Omni Flash", "modalities": { "input": [ "text", - "image" + "image", + "audio", + "video" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 1006592, + "output": 131072 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -130728,25 +129230,24 @@ "extra_capabilities": { "reasoning": { "effort_options": [ + "none", "low", "medium", "high", - "xhigh", "max" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-06", - "release_date": "2026-09-22", - "last_updated": "2026-09-22", + "release_date": "2026-09-17", + "last_updated": "2026-09-17", "type": "chat" }, { - "id": "z-ai/glm-5.3-flash", - "name": "GLM 5.3 Flash", - "display_name": "GLM 5.3 Flash", + "id": "qwen3-5-397b-a17b", + "name": "Qwen3.5 397B-A17B", + "display_name": "Qwen3.5 397B-A17B", "modalities": { "input": [ "text", @@ -130758,8 +129259,8 @@ ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 258048, + "output": 64000 }, "temperature": true, "tool_call": true, @@ -130769,9 +129270,10 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, "effort_options": [ + "none", "low", + "medium", "high", "max" ] @@ -130779,25 +129281,27 @@ }, "attachment": true, "open_weights": true, - "release_date": "2026-08-26", - "last_updated": "2026-08-26", + "release_date": "2026-02-15", + "last_updated": "2026-02-15", "type": "chat" }, { - "id": "z-ai/glm-5.3", - "name": "GLM 5.3", - "display_name": "GLM 5.3", + "id": "kimi-k2-6", + "name": "Kimi K2.6", + "display_name": "Kimi K2.6", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 262144, + "output": 16000 }, "temperature": true, "tool_call": true, @@ -130807,24 +129311,26 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, "effort_options": [ + "none", "low", + "medium", "high", "max" ] } }, - "attachment": false, + "attachment": true, "open_weights": true, - "release_date": "2026-08-14", - "last_updated": "2026-08-14", + "knowledge": "2025-01", + "release_date": "2026-04-20", + "last_updated": "2026-04-20", "type": "chat" }, { - "id": "z-ai/glm-5.2", - "name": "GLM 5.2", - "display_name": "GLM 5.2", + "id": "glm-4-7-flash", + "name": "GLM 4.7 Flash", + "display_name": "GLM 4.7 Flash", "modalities": { "input": [ "text" @@ -130834,8 +129340,8 @@ ] }, "limit": { - "context": 1048576, - "output": 943718 + "context": 202752, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -130843,29 +129349,23 @@ "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "effort_options": [ - "none", - "high", - "xhigh" - ] - } - }, "attachment": false, "open_weights": true, - "release_date": "2026-06-13", - "last_updated": "2026-06-13", + "knowledge": "2025-04", + "release_date": "2026-01-19", + "last_updated": "2026-01-19", "type": "chat" }, { - "id": "deepseek/deepseek-v4-flash", - "name": "DeepSeek V4 Flash 0423", - "display_name": "DeepSeek V4 Flash 0423", + "id": "glm-5-3-flash", + "name": "GLM 5.3 Flash", + "display_name": "GLM 5.3 Flash", "modalities": { "input": [ - "text" + "text", + "image", + "video", + "pdf" ], "output": [ "text" @@ -130873,7 +129373,7 @@ }, "limit": { "context": 1048576, - "output": 943717 + "output": 131072 }, "temperature": true, "tool_call": true, @@ -130883,77 +129383,54 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "high", "effort_options": [ "low", "high", "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." ] } }, - "attachment": false, + "attachment": true, "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-04-24", - "last_updated": "2026-04-24", + "release_date": "2026-08-26", + "last_updated": "2026-08-26", "type": "chat" }, { - "id": "deepseek/deepseek-v4.1-flash-1017", - "name": "DeepSeek V4.1 Flash 1017", - "display_name": "DeepSeek V4.1 Flash 1017", + "id": "kimi-k2-7-code", + "name": "Kimi K2.7 Code", + "display_name": "Kimi K2.7 Code", "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 1048576, + "context": 262144, "output": 131072 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "none", - "low", - "high", - "max" - ] - } - }, "attachment": true, "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-10-17", - "last_updated": "2026-10-17", + "knowledge": "2025-01", + "release_date": "2026-06-12", + "last_updated": "2026-06-12", "type": "chat" }, { - "id": "deepseek/deepseek-v4-pro", - "name": "DeepSeek V4 Pro 0423", - "display_name": "DeepSeek V4 Pro 0423", + "id": "minimax-m2-7-highspeed", + "name": "MiniMax M2.7 Highspeed", + "display_name": "MiniMax M2.7 Highspeed", "modalities": { "input": [ "text" @@ -130963,8 +129440,8 @@ ] }, "limit": { - "context": 1048576, - "output": 393216 + "context": 204800, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -130972,43 +129449,22 @@ "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "high", - "effort_options": [ - "low", - "high", - "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." - ] - } - }, "attachment": false, "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-04-24", - "last_updated": "2026-04-24", + "release_date": "2026-03-18", + "last_updated": "2026-03-18", "type": "chat" }, { - "id": "deepseek/deepseek-v4.1-flash", - "name": "DeepSeek V4.1 Flash", - "display_name": "DeepSeek V4.1 Flash", + "id": "mimo-v2-6-flash", + "name": "MiMo V2.6 Flash", + "display_name": "MiMo V2.6 Flash", "modalities": { "input": [ "text", - "image" + "image", + "audio", + "video" ], "output": [ "text" @@ -131024,39 +129480,29 @@ "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "effort_options": [ - "none", - "low", - "high", - "max" - ] - } - }, "attachment": true, "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-09-10", - "last_updated": "2026-09-10", + "release_date": "2026-09-22", + "last_updated": "2026-09-22", "type": "chat" }, { - "id": "deepseek/deepseek-v4-flash-0731", - "name": "DeepSeek V4 Flash 0731", - "display_name": "DeepSeek V4 Flash 0731", + "id": "qwen3-8-27b", + "name": "Qwen3.8 27B", + "display_name": "Qwen3.8 27B", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 943718 + "context": 262144, + "output": 32768 }, "temperature": true, "tool_call": true, @@ -131066,32 +129512,30 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, "effort_options": [ "none", "low", - "high", - "max" + "medium", + "xhigh" ] } }, - "attachment": false, + "attachment": true, "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-07-31", - "last_updated": "2026-07-31", + "release_date": "2026-08-14", + "last_updated": "2026-08-14", "type": "chat" }, { - "id": "google/gemini-3-flash-preview", - "name": "Gemini 3 Flash Preview", - "display_name": "Gemini 3 Flash Preview", + "id": "muse-spark-1-1", + "name": "Muse Spark 1.1", + "display_name": "Muse Spark 1.1", "modalities": { "input": [ "text", "image", - "audio", - "video" + "video", + "audio" ], "output": [ "text" @@ -131099,7 +129543,7 @@ }, "limit": { "context": 1048576, - "output": 65536 + "output": 131072 }, "temperature": true, "tool_call": true, @@ -131109,48 +129553,38 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "level", - "level": "high", - "level_options": [ + "effort_options": [ "minimal", "low", "medium", - "high" - ], - "summaries": true, - "visibility": "summary", - "continuation": [ - "thought_signatures" + "high", + "xhigh" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-01", - "release_date": "2025-12-17", - "last_updated": "2025-12-17", + "release_date": "2026-04-08", + "last_updated": "2026-07-09", "type": "chat" }, { - "id": "google/gemini-3.7-flash", - "name": "Gemini 3.7 Flash", - "display_name": "Gemini 3.7 Flash", + "id": "qwen3-8-max-0902", + "name": "Qwen3.8 Max 0902", + "display_name": "Qwen3.8 Max 0902", "modalities": { "input": [ "text", "image", - "video", - "audio" + "video" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 983616, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -131160,50 +129594,39 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "level", - "level": "high", - "level_options": [ - "minimal", + "effort_options": [ + "none", "low", "medium", - "high" - ], - "summaries": true, - "visibility": "summary", - "continuation": [ - "thought_signatures" + "high", + "max" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-03", - "release_date": "2026-08-13", - "last_updated": "2026-08-13", + "release_date": "2026-09-02", + "last_updated": "2026-09-02", "type": "chat" }, { - "id": "google/gemini-3.1-pro-preview", - "name": "Gemini 3.1 Pro Preview", - "display_name": "Gemini 3.1 Pro Preview", + "id": "fugu-ultra-v1-0", + "name": "Fugu Ultra v1.0", + "display_name": "Fugu Ultra v1.0", "modalities": { "input": [ - "audio", - "image", "text", - "video" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 1000000, + "output": 131072 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -131211,48 +129634,37 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "level", - "level": "high", - "level_options": [ - "low", - "high" - ], - "summaries": true, - "visibility": "summary", - "continuation": [ - "thought_signatures" + "effort_options": [ + "high", + "xhigh", + "max" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-01", - "release_date": "2026-02-19", - "last_updated": "2026-02-19", + "release_date": "2026-06-15", + "last_updated": "2026-06-15", "type": "chat" }, { - "id": "google/gemini-3.8-flash", - "name": "Gemini 3.8 Flash", - "display_name": "Gemini 3.8 Flash", + "id": "fugu-ultra-v2-0", + "name": "Fugu Ultra v2.0", + "display_name": "Fugu Ultra v2.0", "modalities": { "input": [ "text", - "image", - "video", - "audio" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 1000000, + "output": 131072 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -131260,38 +129672,28 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "level", - "level": "high", - "level_options": [ - "minimal", - "low", - "medium", - "high" - ], - "summaries": true, - "visibility": "summary", - "continuation": [ - "thought_signatures" + "effort_options": [ + "high", + "xhigh", + "max" ] } }, "attachment": true, "open_weights": false, - "release_date": "2026-09-02", - "last_updated": "2026-09-02", + "knowledge": "2026-08-28", + "release_date": "2026-09-11", + "last_updated": "2026-09-11", "type": "chat" }, { - "id": "google/gemini-4-argon", - "name": "Gemini 4 Argon", - "display_name": "Gemini 4 Argon", + "id": "qwen3-5-122b-a10b", + "name": "Qwen3.5 122B-A10B", + "display_name": "Qwen3.5 122B-A10B", "modalities": { "input": [ - "audio", - "image", "text", + "image", "video" ], "output": [ @@ -131299,8 +129701,8 @@ ] }, "limit": { - "context": 1048576, - "output": 943718 + "context": 258048, + "output": 64000 }, "temperature": true, "tool_call": true, @@ -131311,40 +129713,67 @@ "extra_capabilities": { "reasoning": { "effort_options": [ + "none", "low", "medium", - "high" + "high", + "max" ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2026-03", - "release_date": "2026-10-13", - "last_updated": "2026-10-13", + "open_weights": true, + "release_date": "2026-02-23", + "last_updated": "2026-02-23", "type": "chat" }, { - "id": "google/gemini-3.6-flash", - "name": "Gemini 3.6 Flash", - "display_name": "Gemini 3.6 Flash", + "id": "mistral-small-4", + "name": "Mistral Small 4", + "display_name": "Mistral Small 4", "modalities": { "input": [ "text", - "image", - "video", - "audio" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1048576, + "context": 262144, "output": 65536 }, "temperature": true, "tool_call": true, + "reasoning": { + "supported": false + }, + "attachment": true, + "open_weights": true, + "knowledge": "2025-06", + "release_date": "2026-03-16", + "last_updated": "2026-03-16", + "type": "chat" + }, + { + "id": "deepseek-v4-pro", + "name": "DeepSeek V4 Pro", + "display_name": "DeepSeek V4 Pro", + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1000000, + "output": 393216 + }, + "temperature": true, + "tool_call": true, "reasoning": { "supported": true, "default": true @@ -131353,38 +129782,41 @@ "reasoning": { "supported": true, "default_enabled": true, - "mode": "level", - "level": "high", - "level_options": [ - "minimal", + "mode": "effort", + "effort": "high", + "effort_options": [ "low", - "medium", - "high" + "high", + "max" ], + "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ - "thought_signatures" + "thinking_blocks" + ], + "notes": [ + "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-03", - "release_date": "2026-07-21", - "last_updated": "2026-07-21", + "attachment": false, + "open_weights": true, + "knowledge": "2025-05", + "release_date": "2026-04-24", + "last_updated": "2026-04-24", "type": "chat" }, { - "id": "google/gemini-3.5-flash", - "name": "Gemini 3.5 Flash", - "display_name": "Gemini 3.5 Flash", + "id": "mimo-v2-6-pro-ultraspeed", + "name": "MiMo V2.6 Pro UltraSpeed", + "display_name": "MiMo V2.6 Pro UltraSpeed", "modalities": { "input": [ "text", "image", - "video", - "audio" + "audio", + "video" ], "output": [ "text" @@ -131392,7 +129824,35 @@ }, "limit": { "context": 1048576, - "output": 65536 + "output": 131072 + }, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": true, + "default": true + }, + "attachment": true, + "open_weights": true, + "release_date": "2026-09-21", + "last_updated": "2026-09-21", + "type": "chat" + }, + { + "id": "glm-5-3", + "name": "GLM 5.3", + "display_name": "GLM 5.3", + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1048576, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -131402,38 +129862,29 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "level", - "level": "high", - "level_options": [ - "minimal", + "effort_options": [ "low", - "medium", - "high" - ], - "summaries": true, - "visibility": "summary", - "continuation": [ - "thought_signatures" + "high", + "max" ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-01", - "release_date": "2026-05-19", - "last_updated": "2026-05-19", + "attachment": false, + "open_weights": true, + "release_date": "2026-08-14", + "last_updated": "2026-08-14", "type": "chat" }, { - "id": "google/gemma-3-27b-it", - "name": "Gemma 3 27B", - "display_name": "Gemma 3 27B", + "id": "glm-4-6v-flash", + "name": "GLM 4.6V Flash", + "display_name": "GLM 4.6V Flash", "modalities": { "input": [ "text", - "image" + "image", + "video", + "pdf" ], "output": [ "text" @@ -131441,38 +129892,39 @@ }, "limit": { "context": 131072, - "output": 117964 + "output": 32768 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, "attachment": true, "open_weights": true, - "knowledge": "2024-08", - "release_date": "2025-03-12", - "last_updated": "2025-03-12", + "release_date": "2025-12-08", + "last_updated": "2025-12-08", "type": "chat" }, { - "id": "openai/gpt-5", - "name": "GPT-5", - "display_name": "GPT-5", + "id": "seed-2-1-turbo", + "name": "Seed 2.1 Turbo", + "display_name": "Seed 2.1 Turbo", "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 229093, + "output": 65536 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -131480,68 +129932,66 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", "effort_options": [ - "minimal", - "low", - "medium", - "high" - ], - "verbosity": "medium", - "verbosity_options": [ "low", "medium", "high" - ], - "visibility": "hidden" + ] } }, "attachment": true, "open_weights": false, - "knowledge": "2024-09-30", - "release_date": "2025-08-07", - "last_updated": "2025-08-07", + "release_date": "2026-06-23", + "last_updated": "2026-06-23", "type": "chat" }, { - "id": "openai/gpt-4o-mini", - "name": "GPT-4o-mini", - "display_name": "GPT-4o-mini", + "id": "qwen3-5-9b", + "name": "Qwen3.5 9B", + "display_name": "Qwen3.5 9B", "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 16384 + "context": 262144, + "output": 32768 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "none", + "low", + "medium", + "high", + "max" + ] + } }, "attachment": true, - "open_weights": false, - "knowledge": "2023-09", - "release_date": "2024-07-18", - "last_updated": "2024-07-18", + "open_weights": true, + "release_date": "2026-02-23", + "last_updated": "2026-02-23", "type": "chat" }, { - "id": "openai/gpt-5.5", - "name": "GPT-5.5", - "display_name": "GPT-5.5", + "id": "qwen3-6-35b-a3b", + "name": "Qwen3.6 35B A3B", + "display_name": "Qwen3.6 35B A3B", "modalities": { "input": [ - "image", "text" ], "output": [ @@ -131549,10 +129999,10 @@ ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 131072, + "output": 16384 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -131560,79 +130010,81 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", "effort_options": [ + "none", "low", "medium", "high", - "xhigh" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + "max" + ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-12-01", - "release_date": "2026-04-23", - "last_updated": "2026-04-23", + "attachment": false, + "open_weights": true, + "release_date": "2026-04-17", + "last_updated": "2026-04-17", "type": "chat" }, { - "id": "openai/gpt-4.1-nano", - "name": "GPT-4.1 Nano", - "display_name": "GPT-4.1 Nano", + "id": "qwen3-6-flash", + "name": "Qwen3.6 Flash", + "display_name": "Qwen3.6 Flash", "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 1047576, - "output": 942818 + "context": 983616, + "output": 65536 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "none", + "low", + "medium", + "high", + "max" + ] + } }, "attachment": true, "open_weights": false, - "knowledge": "2024-04", - "release_date": "2025-04-14", - "last_updated": "2025-04-14", + "release_date": "2026-04-27", + "last_updated": "2026-04-27", "type": "chat" }, { - "id": "openai/gpt-6-astra", - "name": "GPT-6 Astra", - "display_name": "GPT-6 Astra", + "id": "qwen3-8-max", + "name": "Qwen3.8 Max", + "display_name": "Qwen3.8 Max", "modalities": { "input": [ + "text", "image", - "text" + "video" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 983616, + "output": 131072 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -131640,90 +130092,79 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, "effort_options": [ + "none", "low", "medium", "high", - "xhigh", "max" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-04-30", - "release_date": "2026-09-04", - "last_updated": "2026-09-04", + "release_date": "2026-08-03", + "last_updated": "2026-08-03", "type": "chat" }, { - "id": "openai/gpt-5.4", - "name": "GPT-5.4", - "display_name": "GPT-5.4", + "id": "qwen3-6-27b", + "name": "Qwen3.6 27B", + "display_name": "Qwen3.6 27B", "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 229376, + "output": 64000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "none", "effort_options": [ "none", "low", "medium", "high", - "xhigh" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + "max" + ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-03-05", - "last_updated": "2026-03-05", + "open_weights": true, + "release_date": "2026-04-22", + "last_updated": "2026-04-22", "type": "chat" }, { - "id": "openai/gpt-oss-20b", - "name": "gpt-oss-20b", - "display_name": "gpt-oss-20b", + "id": "qwen3-5-27b", + "name": "Qwen3.5 27B", + "display_name": "Qwen3.5 27B", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 32768 + "context": 258048, + "output": 64000 }, "temperature": true, "tool_call": true, @@ -131733,57 +130174,57 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, "effort_options": [ + "none", "low", "medium", - "high" + "high", + "max" ] } }, - "attachment": false, + "attachment": true, "open_weights": true, - "knowledge": "2024-06", - "release_date": "2025-08-05", - "last_updated": "2025-08-05", + "release_date": "2026-02-23", + "last_updated": "2026-02-23", "type": "chat" }, { - "id": "openai/gpt-4o", - "name": "GPT-4o", - "display_name": "GPT-4o", + "id": "qwen3-5-flash", + "name": "Qwen3.5 Flash", + "display_name": "Qwen3.5 Flash", "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 16384 + "context": 995904, + "output": 32768 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, "attachment": true, "open_weights": false, - "knowledge": "2023-09", - "release_date": "2024-05-13", - "last_updated": "2024-08-06", + "release_date": "2026-02-23", + "last_updated": "2026-02-23", "type": "chat" }, { - "id": "openai/gpt-6-sol", - "name": "GPT-6 Sol", - "display_name": "GPT-6 Sol", + "id": "qwen3-7-max", + "name": "Qwen3.7 Max", + "display_name": "Qwen3.7 Max", "modalities": { "input": [ - "image", "text" ], "output": [ @@ -131791,10 +130232,10 @@ ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 983616, + "output": 65536 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -131807,34 +130248,33 @@ "low", "medium", "high", - "xhigh", "max" ] } }, - "attachment": true, + "attachment": false, "open_weights": false, - "knowledge": "2026-04-20", - "release_date": "2026-09-22", - "last_updated": "2026-09-22", + "release_date": "2026-05-21", + "last_updated": "2026-05-21", "type": "chat" }, { - "id": "openai/gpt-5.6-sol", - "name": "GPT-5.6 Sol", - "display_name": "GPT-5.6 Sol", + "id": "kimi-k2-7-code-highspeed", + "name": "Kimi K2.7 Code Highspeed", + "display_name": "Kimi K2.7 Code Highspeed", "modalities": { "input": [ + "text", "image", - "text" + "video" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 262144, + "output": 131072 }, "temperature": false, "tool_call": true, @@ -131842,54 +130282,73 @@ "supported": true, "default": true }, + "attachment": true, + "open_weights": true, + "knowledge": "2025-01", + "release_date": "2026-06-12", + "last_updated": "2026-06-12", + "type": "chat" + }, + { + "id": "muse-spark-1-3", + "name": "Muse Spark 1.3", + "display_name": "Muse Spark 1.3", + "modalities": { + "input": [ + "text", + "image", + "video" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1048576, + "output": 131072 + }, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": true, + "default": true + }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", "effort_options": [ - "none", + "minimal", "low", "medium", "high", - "xhigh", - "max" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + "xhigh" + ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-02-16", - "release_date": "2026-07-09", - "last_updated": "2026-07-09", + "release_date": "2026-09-02", + "last_updated": "2026-09-02", "type": "chat" }, { - "id": "openai/gpt-5.6-terra", - "name": "GPT-5.6 Terra", - "display_name": "GPT-5.6 Terra", + "id": "step-3-7-flash", + "name": "Step 3.7 Flash", + "display_name": "Step 3.7 Flash", "modalities": { "input": [ + "text", "image", - "text" + "video" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 262144, + "output": 131072 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -131897,38 +130356,24 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", "effort_options": [ - "none", - "low", - "medium", - "high", - "xhigh", - "max" - ], - "verbosity": "medium", - "verbosity_options": [ "low", "medium", "high" - ], - "visibility": "hidden" + ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2026-02-16", - "release_date": "2026-07-09", - "last_updated": "2026-07-09", + "open_weights": true, + "knowledge": "2026-03-01", + "release_date": "2026-05-29", + "last_updated": "2026-05-29", "type": "chat" }, { - "id": "openai/gpt-oss-120b", - "name": "gpt-oss-120b", - "display_name": "gpt-oss-120b", + "id": "glm-5-1", + "name": "GLM 5.1", + "display_name": "GLM 5.1", "modalities": { "input": [ "text" @@ -131938,8 +130383,8 @@ ] }, "limit": { - "context": 131072, - "output": 65536 + "context": 202745, + "output": 128000 }, "temperature": true, "tool_call": true, @@ -131949,39 +130394,40 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, "effort_options": [ + "none", "low", "medium", - "high" + "high", + "max" ] } }, "attachment": false, "open_weights": true, - "knowledge": "2024-06", - "release_date": "2025-08-05", - "last_updated": "2025-08-05", + "release_date": "2026-04-07", + "last_updated": "2026-04-07", "type": "chat" }, { - "id": "openai/gpt-6-luna", - "name": "GPT-6 Luna", - "display_name": "GPT-6 Luna", + "id": "seed-2-0-mini", + "name": "Seed 2.0 Mini", + "display_name": "Seed 2.0 Mini", "modalities": { "input": [ + "text", "image", - "text" + "video" ], "output": [ "text" ] }, "limit": { - "context": 1050000, + "context": 229156, "output": 128000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -131990,40 +130436,37 @@ "extra_capabilities": { "reasoning": { "effort_options": [ - "none", "low", "medium", - "high", - "xhigh", - "max" + "high" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-05-18", - "release_date": "2026-09-22", - "last_updated": "2026-09-22", + "release_date": "2026-02-14", + "last_updated": "2026-02-14", "type": "chat" }, { - "id": "openai/gpt-5-mini", - "name": "GPT-5 Mini", - "display_name": "GPT-5 Mini", + "id": "qwen3-7-flash", + "name": "Qwen3.7 Flash", + "display_name": "Qwen3.7 Flash", "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 983616, + "output": 65536 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -132031,50 +130474,40 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", "effort_options": [ - "minimal", - "low", - "medium", - "high" - ], - "verbosity": "medium", - "verbosity_options": [ + "none", "low", "medium", - "high" - ], - "visibility": "hidden" + "high", + "max" + ] } }, "attachment": true, "open_weights": false, - "knowledge": "2024-05-30", - "release_date": "2025-08-07", - "last_updated": "2025-08-07", + "release_date": "2026-07-15", + "last_updated": "2026-07-15", "type": "chat" }, { - "id": "openai/gpt-5.4-pro", - "name": "GPT-5.4 Pro", - "display_name": "GPT-5.4 Pro", + "id": "minimax-m3", + "name": "MiniMax M3", + "display_name": "MiniMax M3", "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 1048576, + "output": 524288 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -132082,98 +130515,71 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "high", - "effort_options": [ - "medium", - "high", - "xhigh" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + "supported": true } }, "attachment": true, - "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-03-05", - "last_updated": "2026-03-05", + "open_weights": true, + "release_date": "2026-06-01", + "last_updated": "2026-06-01", "type": "chat" }, { - "id": "openai/gpt-5.1", - "name": "GPT-5.1", - "display_name": "GPT-5.1", + "id": "deepseek-v4-pro-0813", + "name": "DeepSeek V4 Pro 0813", + "display_name": "DeepSeek V4 Pro 0813", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 1000000, + "output": 393216 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "none", "effort_options": [ "none", "low", "medium", - "high" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + "high", + "max" + ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2024-09-30", - "release_date": "2025-11-13", - "last_updated": "2025-11-13", + "attachment": false, + "open_weights": true, + "release_date": "2026-08-13", + "last_updated": "2026-08-22", "type": "chat" }, { - "id": "openai/gpt-5.6-luna", - "name": "GPT-5.6 Luna", - "display_name": "GPT-5.6 Luna", + "id": "fugu-ultra-v1-1", + "name": "Fugu Ultra v1.1", + "display_name": "Fugu Ultra v1.1", "modalities": { "input": [ - "image", - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 1000000, + "output": 131072 }, "temperature": false, "tool_call": true, @@ -132183,38 +130589,23 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", "effort_options": [ - "none", - "low", - "medium", "high", "xhigh", "max" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-02-16", - "release_date": "2026-07-09", - "last_updated": "2026-07-09", + "release_date": "2026-06-15", + "last_updated": "2026-06-15", "type": "chat" }, { - "id": "openai/gpt-5.4-mini", - "name": "GPT-5.4 Mini", - "display_name": "GPT-5.4 Mini", + "id": "deepseek-v4-1-flash-1017", + "name": "DeepSeek V4.1 Flash 1017", + "display_name": "DeepSeek V4.1 Flash 1017", "modalities": { "input": [ "text", @@ -132225,62 +130616,50 @@ ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 1000000, + "output": 393216 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "none", "effort_options": [ "none", "low", "medium", "high", - "xhigh" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + "max" + ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-03-17", - "last_updated": "2026-03-17", + "open_weights": true, + "knowledge": "2025-05", + "release_date": "2026-10-17", + "last_updated": "2026-10-17", "type": "chat" }, { - "id": "openai/gpt-5.3-codex", - "name": "GPT-5.3-Codex", - "display_name": "GPT-5.3-Codex", + "id": "deepseek-v4-flash-0731", + "name": "DeepSeek V4 Flash 0731", + "display_name": "DeepSeek V4 Flash 0731", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 1000000, + "output": 393216 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -132289,49 +130668,40 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", "effort_options": [ + "none", "low", "medium", "high", - "xhigh" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + "max" + ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-02-05", - "last_updated": "2026-02-05", + "attachment": false, + "open_weights": true, + "knowledge": "2025-05", + "release_date": "2026-07-31", + "last_updated": "2026-07-31", "type": "chat" }, { - "id": "openai/gpt-5.6-terra-pro", - "name": "GPT-5.6 Terra Pro", - "display_name": "GPT-5.6 Terra Pro", + "id": "muse-glimmer-30b", + "name": "Muse Glimmer 30B", + "display_name": "Muse Glimmer 30B", "modalities": { "input": [ - "image", - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 131072, + "output": 32768 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -132344,25 +130714,23 @@ "low", "medium", "high", - "xhigh", "max" ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2026-02-16", - "release_date": "2026-07-09", - "last_updated": "2026-07-09", + "open_weights": true, + "knowledge": "2026-01-04", + "release_date": "2026-08-10", + "last_updated": "2026-08-10", "type": "chat" }, { - "id": "openai/gpt-5.6-sol-pro", - "name": "GPT-5.6 Sol Pro", - "display_name": "GPT-5.6 Sol Pro", + "id": "step-3-5-flash-2603", + "name": "Step 3.5 Flash 2603", + "display_name": "Step 3.5 Flash 2603", "modalities": { "input": [ - "image", "text" ], "output": [ @@ -132370,10 +130738,10 @@ ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 262144, + "output": 131072 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -132382,29 +130750,24 @@ "extra_capabilities": { "reasoning": { "effort_options": [ - "none", "low", - "medium", - "high", - "xhigh", - "max" + "high" ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-02-16", - "release_date": "2026-07-09", - "last_updated": "2026-07-09", + "attachment": false, + "open_weights": true, + "knowledge": "2025-01", + "release_date": "2026-04-02", + "last_updated": "2026-04-02", "type": "chat" }, { - "id": "openai/gpt-4.1-mini", - "name": "GPT-4.1 Mini", - "display_name": "GPT-4.1 Mini", + "id": "glm-4-5-flash", + "name": "GLM 4.5 Flash", + "display_name": "GLM 4.5 Flash", "modalities": { "input": [ - "image", "text" ], "output": [ @@ -132412,109 +130775,101 @@ ] }, "limit": { - "context": 1047576, - "output": 32768 + "context": 131072, + "output": 98304 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": true, + "attachment": false, "open_weights": false, - "knowledge": "2024-04", - "release_date": "2025-04-14", - "last_updated": "2025-04-14", + "knowledge": "2025-04", + "release_date": "2025-07-28", + "last_updated": "2025-07-28", "type": "chat" }, { - "id": "openai/gpt-5.4-nano", - "name": "GPT-5.4 Nano", - "display_name": "GPT-5.4 Nano", + "id": "qwen3-7-plus", + "name": "Qwen3.7 Plus", + "display_name": "Qwen3.7 Plus", "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 983616, + "output": 65536 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "none", "effort_options": [ "none", "low", "medium", "high", - "xhigh" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + "max" + ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-03-17", - "last_updated": "2026-03-17", + "knowledge": "2025-04", + "release_date": "2026-06-02", + "last_updated": "2026-06-02", "type": "chat" }, { - "id": "openai/gpt-4.1", - "name": "GPT-4.1", - "display_name": "GPT-4.1", + "id": "qwen3-5-plus", + "name": "Qwen3.5 Plus", + "display_name": "Qwen3.5 Plus", "modalities": { "input": [ + "text", "image", - "text" + "video" ], "output": [ "text" ] }, "limit": { - "context": 1047576, - "output": 32768 + "context": 983616, + "output": 65536 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, "attachment": true, "open_weights": false, - "knowledge": "2024-04", - "release_date": "2025-04-14", - "last_updated": "2025-04-14", + "knowledge": "2025-04", + "release_date": "2026-02-16", + "last_updated": "2026-02-16", "type": "chat" }, { - "id": "openai/gpt-5.6-luna-pro", - "name": "GPT-5.6 Luna Pro", - "display_name": "GPT-5.6 Luna Pro", + "id": "step-3-5-flash", + "name": "Step 3.5 Flash", + "display_name": "Step 3.5 Flash", "modalities": { "input": [ - "image", "text" ], "output": [ @@ -132522,101 +130877,89 @@ ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 262144, + "output": 131072 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "none", - "low", - "medium", - "high", - "xhigh", - "max" - ] - } - }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-02-16", - "release_date": "2026-07-09", - "last_updated": "2026-07-09", + "attachment": false, + "open_weights": true, + "knowledge": "2025-01", + "release_date": "2026-01-29", + "last_updated": "2026-02-13", "type": "chat" - }, + } + ] + }, + "routing-run": { + "id": "routing-run", + "name": "routing.run", + "display_name": "routing.run", + "api": "https://api.routing.run/v1", + "doc": "https://docs.routing.run/api-reference/models", + "models": [ { - "id": "openai/gpt-5.2", - "name": "GPT-5.2", - "display_name": "GPT-5.2", + "id": "kimi-k2.7-code-nitro", + "name": "Kimi K2.7 Code Nitro", + "display_name": "Kimi K2.7 Code Nitro", "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 200000, + "output": 32000 }, "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "none", - "effort_options": [ - "none", - "low", - "medium", - "high", - "xhigh" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2025-12-11", - "last_updated": "2025-12-11", + "open_weights": true, + "knowledge": "2025-01", + "release_date": "2026-06-12", + "last_updated": "2026-06-12", "type": "chat" }, { - "id": "openai/gpt-6.1-sol", - "name": "GPT-6.1 Sol", - "display_name": "GPT-6.1 Sol", + "id": "gpt-5.6-terra", + "name": "GPT-5.6 Terra", + "display_name": "GPT-5.6 Terra", "modalities": { "input": [ + "text", "image", - "text" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1050000, + "context": 1000000, "output": 128000 }, "temperature": false, @@ -132627,38 +130970,56 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", "effort_options": [ + "none", "low", "medium", "high", "xhigh", "max" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-04-30", - "release_date": "2026-09-29", - "last_updated": "2026-09-29", + "knowledge": "2026-02-16", + "release_date": "2026-07-09", + "last_updated": "2026-07-09", "type": "chat" }, { - "id": "openai/gpt-5-nano", - "name": "GPT-5 Nano", - "display_name": "GPT-5 Nano", + "id": "kimi-k2.7-code", + "name": "Kimi K2.7 Code", + "display_name": "Kimi K2.7 Code", "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 200000, + "output": 32000 }, "temperature": false, "tool_call": true, @@ -132669,48 +131030,36 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", - "effort_options": [ - "minimal", - "low", - "medium", - "high" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2024-05-30", - "release_date": "2025-08-07", - "last_updated": "2025-08-07", + "open_weights": true, + "knowledge": "2025-01", + "release_date": "2026-06-12", + "last_updated": "2026-06-12", "type": "chat" }, { - "id": "moonshotai/kimi-k3", - "name": "Kimi K3", - "display_name": "Kimi K3", + "id": "nemotron-3-ultra", + "name": "Nemotron 3 Ultra 550B A55B", + "display_name": "Nemotron 3 Ultra 550B A55B", "modalities": { "input": [ - "text", - "image", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 943718 + "context": 131072, + "output": 32000 }, "temperature": true, "tool_call": true, @@ -132721,37 +131070,38 @@ "extra_capabilities": { "reasoning": { "supported": true, - "effort_options": [ - "none", - "low", - "high", - "max" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, - "attachment": true, + "attachment": false, "open_weights": true, - "release_date": "2026-07-16", - "last_updated": "2026-07-16", + "knowledge": "2025-09", + "release_date": "2026-06-04", + "last_updated": "2026-06-04", "type": "chat" }, { - "id": "moonshotai/kimi-k3.1-pro", - "name": "Kimi K3.1 Pro", - "display_name": "Kimi K3.1 Pro", + "id": "gpt-5.6-luna", + "name": "GPT-5.6 Luna", + "display_name": "GPT-5.6 Luna", "modalities": { "input": [ "text", "image", - "video" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 943718 + "context": 1000000, + "output": 128000 }, "temperature": false, "tool_call": true, @@ -132761,24 +131111,43 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", "effort_options": [ "none", "low", + "medium", "high", + "xhigh", "max" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, "attachment": true, "open_weights": false, - "release_date": "2026-10-23", - "last_updated": "2026-10-23", + "knowledge": "2026-02-16", + "release_date": "2026-07-09", + "last_updated": "2026-07-09", "type": "chat" }, { - "id": "moonshotai/kimi-k2-0905", - "name": "Kimi K2 0905", - "display_name": "Kimi K2 0905", + "id": "deepseek-v4-pro", + "name": "DeepSeek V4 Pro", + "display_name": "DeepSeek V4 Pro", "modalities": { "input": [ "text" @@ -132788,56 +131157,53 @@ ] }, "limit": { - "context": 262144, - "output": 98304 + "context": 1000000, + "output": 64000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false - }, - "attachment": false, - "open_weights": true, - "release_date": "2025-09-05", - "last_updated": "2025-09-05", - "type": "chat" - }, - { - "id": "qwen/qwen3-30b-a3b-instruct-2507", - "name": "Qwen3 30B A3B Instruct 2507", - "display_name": "Qwen3 30B A3B Instruct 2507", - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 262144, - "output": 235929 + "supported": true, + "default": true }, - "temperature": true, - "tool_call": true, - "reasoning": { - "supported": false + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "high", + "effort_options": [ + "low", + "high", + "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." + ] + } }, "attachment": false, "open_weights": true, - "release_date": "2025-07-28", - "last_updated": "2025-07-28", + "knowledge": "2025-05", + "release_date": "2026-04-24", + "last_updated": "2026-04-24", "type": "chat" }, { - "id": "qwen/qwen3.8-27b", - "name": "Qwen3.8 27B", - "display_name": "Qwen3.8 27B", + "id": "gpt-5.6-sol", + "name": "GPT-5.6 Sol", + "display_name": "GPT-5.6 Sol", "modalities": { "input": [ "text", "image", - "video" + "pdf" ], "output": [ "text" @@ -132845,9 +131211,9 @@ }, "limit": { "context": 1000000, - "output": 235929 + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -132855,56 +131221,107 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", "effort_options": [ "none", "low", "medium", - "xhigh" + "high", + "xhigh", + "max" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-08-14", - "last_updated": "2026-08-14", + "open_weights": false, + "knowledge": "2026-02-16", + "release_date": "2026-07-09", + "last_updated": "2026-07-09", "type": "chat" }, { - "id": "qwen/qwen3-vl-30b-a3b-instruct", - "name": "Qwen3 VL 30B A3B Instruct", - "display_name": "Qwen3 VL 30B A3B Instruct", + "id": "claude-sonnet-4-6", + "name": "Claude Sonnet 4.6", + "display_name": "Claude Sonnet 4.6", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 16384 + "context": 1000000, + "output": 64000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": false + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": false, + "mode": "mixed", + "budget": { + "min": 1024, + "unit": "tokens" + }, + "effort": "high", + "effort_options": [ + "low", + "medium", + "high", + "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Anthropic recommends adaptive thinking with effort for Claude 4.6; budget_tokens remains a deprecated compatibility path.", + "Anthropic API defaults effort to high; lower effort levels should be chosen per workload." + ] + } }, "attachment": true, - "open_weights": true, - "release_date": "2025-10-04", - "last_updated": "2025-10-04", + "open_weights": false, + "knowledge": "2025-08-31", + "release_date": "2026-02-17", + "last_updated": "2026-03-13", "type": "chat" }, { - "id": "qwen/qwen3-vl-235b-a22b-instruct", - "name": "Qwen3 VL 235B A22B Instruct", - "display_name": "Qwen3 VL 235B A22B Instruct", + "id": "qwen3.5-9b", + "name": "Qwen3.5 9B", + "display_name": "Qwen3.5 9B", "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ "text" @@ -132912,123 +131329,143 @@ }, "limit": { "context": 262144, - "output": 32768 + "output": 32000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false - }, - "attachment": true, - "open_weights": true, - "knowledge": "2025-03-31", - "release_date": "2025-09-23", - "last_updated": "2025-09-23", - "type": "chat" - }, - { - "id": "qwen/qwen3-coder-next", - "name": "Qwen3 Coder Next", - "display_name": "Qwen3 Coder Next", - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 262144, - "output": 235929 + "supported": true, + "default": true }, - "temperature": true, - "tool_call": true, - "reasoning": { - "supported": false + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } }, - "attachment": false, + "attachment": true, "open_weights": true, - "knowledge": "2025-09", - "release_date": "2026-02-03", - "last_updated": "2026-02-03", + "release_date": "2026-02-23", + "last_updated": "2026-02-23", "type": "chat" }, { - "id": "meta/muse-spark-open", - "name": "Muse Spark Open", - "display_name": "Muse Spark Open", + "id": "claude-opus-4-8", + "name": "Claude Opus 4.8", + "display_name": "Claude Opus 4.8", "modalities": { "input": [ "text", "image", - "video", - "audio" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 1000000, + "output": 32000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": false, + "mode": "effort", + "effort": "high", "effort_options": [ "low", "medium", "high", - "xhigh" + "xhigh", + "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Claude Opus 4.7 and newer Opus models require thinking.type = \"adaptive\" to enable thinking explicitly.", + "Manual budget_tokens requests return 400 on Claude Opus 4.7 and newer adaptive-only Opus models.", + "task_budget is separate from thinking control and should not be treated as a thinking budget." ] } }, "attachment": true, - "open_weights": true, - "knowledge": "2026-01-04", - "release_date": "2026-10-15", - "last_updated": "2026-10-15", + "open_weights": false, + "knowledge": "2026-01", + "release_date": "2026-05-28", + "last_updated": "2026-05-28", "type": "chat" }, { - "id": "mistralai/mistral-large-2512", - "name": "Mistral Large 3 2512", - "display_name": "Mistral Large 3 2512", + "id": "deepseek-v4-flash", + "name": "DeepSeek V4 Flash", + "display_name": "DeepSeek V4 Flash", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 209715 + "context": 1000000, + "output": 64000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": true, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "high", + "effort_options": [ + "low", + "high", + "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." + ] + } + }, + "attachment": false, "open_weights": true, - "knowledge": "2024-11", - "release_date": "2025-12-02", - "last_updated": "2025-12-02", + "knowledge": "2025-05", + "release_date": "2026-04-24", + "last_updated": "2026-04-24", "type": "chat" }, { - "id": "mistralai/mixtral-8x22b-instruct", - "name": "Mixtral 8x22B Instruct", - "display_name": "Mixtral 8x22B Instruct", + "id": "glm-5.2-nitro", + "name": "GLM 5.2 Nitro", + "display_name": "GLM 5.2 Nitro", "modalities": { "input": [ "text" @@ -133038,37 +131475,47 @@ ] }, "limit": { - "context": 65536, - "output": 52428 + "context": 200000, + "output": 32000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } }, "attachment": false, "open_weights": true, - "knowledge": "2024-01-31", - "release_date": "2024-04-17", - "last_updated": "2024-04-17", + "release_date": "2026-06-13", + "last_updated": "2026-06-13", "type": "chat" }, { - "id": "mistralai/mistral-small-2603", - "name": "Mistral Small 4", - "display_name": "Mistral Small 4", + "id": "glm-5.2", + "name": "GLM-5.2", + "display_name": "GLM-5.2", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 209715 + "context": 200000, + "output": 32000 }, "temperature": true, "tool_call": true, @@ -133078,88 +131525,118 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "none", - "high" + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, - "attachment": true, + "attachment": false, "open_weights": true, - "knowledge": "2025-06", - "release_date": "2026-03-16", - "last_updated": "2026-03-16", + "release_date": "2026-06-13", + "last_updated": "2026-06-13", "type": "chat" }, { - "id": "mistralai/mistral-nemo", - "name": "Mistral Nemo", - "display_name": "Mistral Nemo", + "id": "kimi-k2.6-nitro", + "name": "Kimi K2.6 Nitro", + "display_name": "Kimi K2.6 Nitro", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 16384 + "context": 200000, + "output": 32000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } }, - "attachment": false, + "attachment": true, "open_weights": true, - "knowledge": "2024-07", - "release_date": "2024-07-18", - "last_updated": "2024-07-18", + "knowledge": "2025-01", + "release_date": "2026-04-20", + "last_updated": "2026-04-20", "type": "chat" }, { - "id": "meta-llama/llama-3.3-70b-instruct", - "name": "Llama 3.3 70B Instruct", - "display_name": "Llama 3.3 70B Instruct", + "id": "kimi-k2.6", + "name": "Kimi K2.6", + "display_name": "Kimi K2.6", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 16384 + "context": 200000, + "output": 32000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } + }, + "attachment": true, "open_weights": true, - "knowledge": "2023-12", - "release_date": "2024-12-06", - "last_updated": "2024-12-06", + "knowledge": "2025-01", + "release_date": "2026-04-20", + "last_updated": "2026-04-20", "type": "chat" } ] }, - "abliteration-ai": { - "id": "abliteration-ai", - "name": "abliteration.ai", - "display_name": "abliteration.ai", - "api": "https://api.abliteration.ai/v1", - "doc": "https://docs.abliteration.ai/models", + "subconscious": { + "id": "subconscious", + "name": "Subconscious", + "display_name": "Subconscious", + "api": "https://api.subconscious.dev/v1", + "doc": "https://docs.subconscious.dev", "models": [ { - "id": "abliterated-model-large-v2", - "name": "Abliterated Model Large V2", - "display_name": "Abliterated Model Large V2", + "id": "subconscious/glm-5.3-marathon", + "name": "GLM-5.3 Marathon", + "display_name": "GLM-5.3 Marathon", "modalities": { "input": [ "text" @@ -133169,8 +131646,8 @@ ] }, "limit": { - "context": 1000000, - "output": 999990 + "context": 5000000, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -133188,15 +131665,15 @@ } }, "attachment": false, - "open_weights": false, - "release_date": "2026-08-29", - "last_updated": "2026-08-31", + "open_weights": true, + "release_date": "2026-08-14", + "last_updated": "2026-08-14", "type": "chat" }, { - "id": "abliterated-model", - "name": "Abliterated Model", - "display_name": "Abliterated Model", + "id": "subconscious/deepseek-v4.1-flash-marathon", + "name": "DeepSeek V4.1 Flash Marathon", + "display_name": "DeepSeek V4.1 Flash Marathon", "modalities": { "input": [ "text", @@ -133207,8 +131684,8 @@ ] }, "limit": { - "context": 150000, - "output": 8192 + "context": 5000000, + "output": 384000 }, "temperature": true, "tool_call": true, @@ -133219,26 +131696,32 @@ "extra_capabilities": { "reasoning": { "effort_options": [ - "none", - "minimal", "low", - "medium", "high", - "xhigh", "max" ] } }, "attachment": true, "open_weights": true, - "release_date": "2026-01-06", - "last_updated": "2026-07-28", + "knowledge": "2025-05", + "release_date": "2026-09-10", + "last_updated": "2026-09-10", "type": "chat" - }, + } + ] + }, + "anyapi": { + "id": "anyapi", + "name": "AnyAPI", + "display_name": "AnyAPI", + "api": "https://api.anyapi.ai/v1", + "doc": "https://docs.anyapi.ai", + "models": [ { - "id": "abliterated-model-large", - "name": "Abliterated Model Large", - "display_name": "Abliterated Model Large", + "id": "openai/o3-mini", + "name": "o3-mini", + "display_name": "o3-mini", "modalities": { "input": [ "text" @@ -133248,10 +131731,10 @@ ] }, "limit": { - "context": 1000000, - "output": 999990 + "context": 200000, + "output": 100000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -133259,125 +131742,138 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", "effort_options": [ - "none", - "high", - "max" - ] + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, "attachment": false, "open_weights": false, - "release_date": "2026-07-25", - "last_updated": "2026-07-28", + "knowledge": "2024-05", + "release_date": "2024-12-20", + "last_updated": "2025-01-29", "type": "chat" - } - ] - }, - "zai-coding-plan": { - "id": "zai-coding-plan", - "name": "Z.AI Coding Plan", - "display_name": "Z.AI Coding Plan", - "api": "https://api.z.ai/api/coding/paas/v4", - "doc": "https://docs.z.ai/devpack/overview", - "models": [ + }, { - "id": "glm-5.2-highspeed", - "name": "GLM-5.2 Highspeed", - "display_name": "GLM-5.2 Highspeed", + "id": "openai/gpt-5.4", + "name": "GPT-5.4", + "display_name": "GPT-5.4", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 1050000, + "output": 128000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], + "default_enabled": false, + "mode": "effort", + "effort": "none", "effort_options": [ + "none", + "low", + "medium", "high", - "max" - ] + "xhigh" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-06-13", - "last_updated": "2026-06-13", + "attachment": true, + "open_weights": false, + "knowledge": "2025-08-31", + "release_date": "2026-03-05", + "last_updated": "2026-03-05", "type": "chat" }, { - "id": "glm-5.3-flash", - "name": "GLM-5.3-Flash", - "display_name": "GLM-5.3-Flash", + "id": "openai/gpt-5.2", + "name": "GPT-5.2", + "display_name": "GPT-5.2", "modalities": { "input": [ "text", - "image", - "video", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 400000, + "output": 128000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], + "default_enabled": false, + "mode": "effort", + "effort": "none", "effort_options": [ + "none", "low", + "medium", "high", - "max" - ] + "xhigh" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, "attachment": true, - "open_weights": true, - "release_date": "2026-08-26", - "last_updated": "2026-08-26", + "open_weights": false, + "knowledge": "2025-08-31", + "release_date": "2025-12-11", + "last_updated": "2025-12-11", "type": "chat" }, { - "id": "glm-5-turbo", - "name": "GLM-5-Turbo", - "display_name": "GLM-5-Turbo", + "id": "openai/o4-mini", + "name": "o4-mini", + "display_name": "o4-mini", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" @@ -133385,9 +131881,9 @@ }, "limit": { "context": 200000, - "output": 131072 + "output": 100000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -133396,37 +131892,42 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] + "default_enabled": true, + "mode": "effort", + "effort": "medium", + "effort_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, - "attachment": false, + "attachment": true, "open_weights": false, - "release_date": "2026-03-16", - "last_updated": "2026-03-16", + "knowledge": "2024-05", + "release_date": "2025-04-16", + "last_updated": "2025-04-16", "type": "chat" }, { - "id": "glm-4.7", - "name": "GLM-4.7", - "display_name": "GLM-4.7", + "id": "openai/gpt-5", + "name": "GPT-5", + "display_name": "GPT-5", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 204800, - "output": 131072 + "context": 400000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -133435,38 +131936,49 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] + "default_enabled": true, + "mode": "effort", + "effort": "medium", + "effort_options": [ + "minimal", + "low", + "medium", + "high" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-12-22", - "last_updated": "2025-12-22", + "attachment": true, + "open_weights": false, + "knowledge": "2024-09-30", + "release_date": "2025-08-07", + "last_updated": "2025-08-07", "type": "chat" }, { - "id": "glm-5.3", - "name": "GLM-5.3", - "display_name": "GLM-5.3", + "id": "openai/gpt-5-mini", + "name": "GPT-5 Mini", + "display_name": "GPT-5 Mini", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 400000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -133475,177 +131987,204 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], + "default_enabled": true, + "mode": "effort", + "effort": "medium", "effort_options": [ + "minimal", "low", - "high", - "max" - ] + "medium", + "high" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-08-14", - "last_updated": "2026-08-14", + "attachment": true, + "open_weights": false, + "knowledge": "2024-05-30", + "release_date": "2025-08-07", + "last_updated": "2025-08-07", "type": "chat" }, { - "id": "glm-5.3-highspeed", - "name": "GLM-5.3 Highspeed", - "display_name": "GLM-5.3 Highspeed", + "id": "openai/gpt-5.1", + "name": "GPT-5.1", + "display_name": "GPT-5.1", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 400000, + "output": 128000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], + "default_enabled": false, + "mode": "effort", + "effort": "none", "effort_options": [ + "none", "low", - "high", - "max" - ] + "medium", + "high" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-08-14", - "last_updated": "2026-08-14", + "attachment": true, + "open_weights": false, + "knowledge": "2024-09-30", + "release_date": "2025-11-13", + "last_updated": "2025-11-13", "type": "chat" }, { - "id": "glm-5.2", - "name": "GLM-5.2", - "display_name": "GLM-5.2", + "id": "openai/gpt-4.1-mini", + "name": "GPT-4.1 mini", + "display_name": "GPT-4.1 mini", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 1047576, + "output": 32768 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "effort_options": [ - "high", - "max" - ] - } + "supported": false }, - "attachment": false, - "open_weights": true, - "release_date": "2026-06-13", - "last_updated": "2026-06-13", + "attachment": true, + "open_weights": false, + "knowledge": "2024-04", + "release_date": "2025-04-14", + "last_updated": "2025-04-14", "type": "chat" - } - ] - }, - "minimax-coding-plan": { - "id": "minimax-coding-plan", - "name": "MiniMax Token Plan (minimax.io)", - "display_name": "MiniMax Token Plan (minimax.io)", - "api": "https://api.minimax.io/anthropic/v1", - "doc": "https://platform.minimax.io/docs/token-plan/intro", - "models": [ + }, { - "id": "MiniMax-M3.1-Flash", - "name": "MiniMax-M3.1-Flash", - "display_name": "MiniMax-M3.1-Flash", + "id": "openai/gpt-4.1", + "name": "GPT-4.1", + "display_name": "GPT-4.1", "modalities": { "input": [ "text", "image", - "video" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 524288 + "context": 1047576, + "output": 32768 }, "temperature": true, "tool_call": true, + "reasoning": { + "supported": false + }, + "attachment": true, + "open_weights": false, + "knowledge": "2024-04", + "release_date": "2025-04-14", + "last_updated": "2025-04-14", + "type": "chat" + }, + { + "id": "openai/o3", + "name": "o3", + "display_name": "o3", + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 200000, + "output": 100000 + }, + "temperature": false, + "tool_call": true, "reasoning": { "supported": true, "default": true }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", "effort_options": [ "low", "medium", - "high", - "xhigh", - "max" - ] + "high" + ], + "visibility": "hidden" } }, "attachment": true, "open_weights": false, - "release_date": "2026-10-19", - "last_updated": "2026-10-19", + "knowledge": "2024-05", + "release_date": "2025-04-16", + "last_updated": "2025-04-16", "type": "chat" }, { - "id": "MiniMax-M2.5-highspeed", - "name": "MiniMax-M2.5-highspeed", - "display_name": "MiniMax-M2.5-highspeed", + "id": "xai/grok-4.3", + "name": "Grok 4.3", + "display_name": "Grok 4.3", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 204800, - "output": 131072 + "context": 1000000, + "output": 30000 }, "temperature": true, "tool_call": true, @@ -133658,16 +132197,16 @@ "supported": true } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-02-13", - "last_updated": "2026-02-13", + "attachment": true, + "open_weights": false, + "release_date": "2026-04-17", + "last_updated": "2026-04-17", "type": "chat" }, { - "id": "MiniMax-M2.7-highspeed", - "name": "MiniMax-M2.7-highspeed", - "display_name": "MiniMax-M2.7-highspeed", + "id": "cohere/command-r-plus-08-2024", + "name": "Command R+", + "display_name": "Command R+", "modalities": { "input": [ "text" @@ -133677,49 +132216,40 @@ ] }, "limit": { - "context": 204800, - "output": 131072 + "context": 128000, + "output": 4000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } + "supported": false }, "attachment": false, "open_weights": true, - "release_date": "2026-03-18", - "last_updated": "2026-03-18", + "knowledge": "2024-06-01", + "release_date": "2024-08-30", + "last_updated": "2024-08-30", "type": "chat" }, { - "id": "MiniMax-M3.1-Flash-Preview", - "name": "MiniMax-M3.1-Flash-Preview", - "display_name": "MiniMax-M3.1-Flash-Preview", + "id": "google/gemini-2.5-pro", + "name": "Gemini 2.5 Pro", + "display_name": "Gemini 2.5 Pro", "modalities": { "input": [ "text", "image", - "video" + "audio", + "video", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 512000 + "context": 1048576, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -133729,66 +132259,101 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" + "supported": true, + "default_enabled": true, + "mode": "budget", + "budget": { + "default": -1, + "min": 128, + "max": 32768, + "auto": -1, + "unit": "tokens" + }, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thought_signatures" ] } }, "attachment": true, "open_weights": false, - "release_date": "2026-09-27", - "last_updated": "2026-09-27", + "knowledge": "2025-01", + "release_date": "2025-06-17", + "last_updated": "2025-06-17", "type": "chat" }, { - "id": "MiniMax-M2", - "name": "MiniMax-M2", - "display_name": "MiniMax-M2", + "id": "google/gemini-2.5-flash-lite", + "name": "Gemini 2.5 Flash-Lite", + "display_name": "Gemini 2.5 Flash-Lite", "modalities": { "input": [ - "text" + "text", + "image", + "audio", + "video", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 204800, - "output": 131072 + "context": 1048576, + "output": 65536 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, - "attachment": false, - "open_weights": true, - "release_date": "2025-10-27", - "last_updated": "2025-10-27", + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": false, + "mode": "budget", + "budget": { + "default": -1, + "min": 512, + "max": 24576, + "auto": -1, + "unit": "tokens" + }, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thought_signatures" + ] + } + }, + "attachment": true, + "open_weights": false, + "knowledge": "2025-01", + "release_date": "2025-06-17", + "last_updated": "2025-06-17", "type": "chat" }, { - "id": "MiniMax-M3", - "name": "MiniMax-M3", - "display_name": "MiniMax-M3", + "id": "google/gemini-2.5-flash", + "name": "Gemini 2.5 Flash", + "display_name": "Gemini 2.5 Flash", "modalities": { "input": [ "text", "image", - "video" + "audio", + "video", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 524288 + "context": 1048576, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -133798,30 +132363,50 @@ }, "extra_capabilities": { "reasoning": { - "supported": true + "supported": true, + "default_enabled": true, + "mode": "budget", + "budget": { + "default": -1, + "min": 0, + "max": 24576, + "auto": -1, + "off": 0, + "unit": "tokens" + }, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thought_signatures" + ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-06-01", - "last_updated": "2026-06-25", + "open_weights": false, + "knowledge": "2025-01", + "release_date": "2025-06-17", + "last_updated": "2025-06-17", "type": "chat" }, { - "id": "MiniMax-M2.1", - "name": "MiniMax-M2.1", - "display_name": "MiniMax-M2.1", + "id": "google/gemini-3-pro-preview", + "name": "Gemini 3 Pro Preview", + "display_name": "Gemini 3 Pro Preview", "modalities": { "input": [ - "text" + "text", + "image", + "video", + "audio", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 204800, - "output": 131072 + "context": 1048576, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -133831,30 +132416,47 @@ }, "extra_capabilities": { "reasoning": { - "supported": true + "supported": true, + "default_enabled": true, + "mode": "level", + "level": "high", + "level_options": [ + "low", + "high" + ], + "summaries": true, + "visibility": "summary", + "continuation": [ + "thought_signatures" + ] } }, - "attachment": false, - "open_weights": true, - "release_date": "2025-12-23", - "last_updated": "2025-12-23", + "attachment": true, + "open_weights": false, + "knowledge": "2025-01", + "release_date": "2025-11-18", + "last_updated": "2025-11-18", "type": "chat" }, { - "id": "MiniMax-M2.7", - "name": "MiniMax-M2.7", - "display_name": "MiniMax-M2.7", + "id": "google/gemini-3-flash-preview", + "name": "Gemini 3 Flash Preview", + "display_name": "Gemini 3 Flash Preview", "modalities": { "input": [ - "text" + "text", + "image", + "video", + "audio", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 204800, - "output": 131072 + "context": 1048576, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -133865,24 +132467,62 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, + "default_enabled": true, + "mode": "level", + "level": "high", + "level_options": [ + "minimal", + "low", + "medium", + "high" + ], "summaries": true, "visibility": "summary", "continuation": [ - "thinking_blocks" + "thought_signatures" ] } }, - "attachment": false, + "attachment": true, + "open_weights": false, + "knowledge": "2025-01", + "release_date": "2025-12-17", + "last_updated": "2025-12-17", + "type": "chat" + }, + { + "id": "mistralai/mistral-large-2512", + "name": "Mistral Large 3", + "display_name": "Mistral Large 3", + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 262144, + "output": 262144 + }, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": false + }, + "attachment": true, "open_weights": true, - "release_date": "2026-03-18", - "last_updated": "2026-03-18", + "knowledge": "2024-11", + "release_date": "2025-12-02", + "last_updated": "2025-12-02", "type": "chat" }, { - "id": "MiniMax-M2.5", - "name": "MiniMax-M2.5", - "display_name": "MiniMax-M2.5", + "id": "mistralai/devstral-2512", + "name": "Devstral 2", + "display_name": "Devstral 2", "modalities": { "input": [ "text" @@ -133892,42 +132532,30 @@ ] }, "limit": { - "context": 204800, - "output": 131072 + "context": 262144, + "output": 262144 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true - } + "supported": false }, "attachment": false, "open_weights": true, - "release_date": "2026-02-12", - "last_updated": "2026-02-12", + "knowledge": "2025-12", + "release_date": "2025-12-09", + "last_updated": "2025-12-09", "type": "chat" - } - ] - }, - "scnet-token-plan": { - "id": "scnet-token-plan", - "name": "SCNet Token Plan", - "display_name": "SCNet Token Plan", - "api": "https://api.scnet.cn/api/llm/v1", - "doc": "https://www.scnet.cn/ac/openapi/doc/2.0/moduleapi/plans/token-plan.html", - "models": [ + }, { - "id": "DeepSeek-V4.1-Flash", - "name": "DeepSeek V4.1 Flash", - "display_name": "DeepSeek V4.1 Flash", + "id": "anthropic/claude-opus-4-7", + "name": "Claude Opus 4.7", + "display_name": "Claude Opus 4.7", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" @@ -133935,89 +132563,107 @@ }, "limit": { "context": 1000000, - "output": 384000 + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": false, + "mode": "effort", + "effort": "high", + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" + ], "interleaved": true, "summaries": true, - "visibility": "summary", + "visibility": "omitted", "continuation": [ "thinking_blocks" ], - "effort_options": [ - "high", - "max" + "notes": [ + "Claude Opus 4.7 and newer Opus models require thinking.type = \"adaptive\" to enable thinking explicitly.", + "Manual budget_tokens requests return 400 on Claude Opus 4.7 and newer adaptive-only Opus models.", + "task_budget is separate from thinking control and should not be treated as a thinking budget." ] } }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-09-10", - "last_updated": "2026-09-10", + "attachment": true, + "open_weights": false, + "knowledge": "2026-01-31", + "release_date": "2026-04-16", + "last_updated": "2026-04-16", "type": "chat" }, { - "id": "DeepSeek-V4-Flash-0731", - "name": "DeepSeek V4 Flash 0731", - "display_name": "DeepSeek V4 Flash 0731", + "id": "anthropic/claude-haiku-4-5", + "name": "Claude Haiku 4.5 (latest)", + "display_name": "Claude Haiku 4.5 (latest)", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 384000 + "context": 200000, + "output": 64000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": false, + "mode": "budget", + "budget": { + "min": 1024, + "unit": "tokens" + }, "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ "thinking_blocks" ], - "effort_options": [ - "high", - "max" + "notes": [ + "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", + "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." ] } }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-07-31", - "last_updated": "2026-07-31", + "attachment": true, + "open_weights": false, + "knowledge": "2025-02-28", + "release_date": "2025-10-15", + "last_updated": "2025-10-15", "type": "chat" }, { - "id": "Qwen3.8-Max", - "name": "Qwen3.8 Max", - "display_name": "Qwen3.8 Max", + "id": "anthropic/claude-sonnet-4-5", + "name": "Claude Sonnet 4.5 (latest)", + "display_name": "Claude Sonnet 4.5 (latest)", "modalities": { "input": [ "text", "image", - "video", "pdf" ], "output": [ @@ -134025,39 +132671,52 @@ ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 200000, + "output": 64000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": false, + "mode": "budget", + "budget": { + "min": 1024, + "unit": "tokens" + }, "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ "thinking_blocks" + ], + "notes": [ + "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", + "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." ] } }, "attachment": true, "open_weights": false, - "release_date": "2026-08-03", - "last_updated": "2026-08-03", + "knowledge": "2025-07-31", + "release_date": "2025-09-29", + "last_updated": "2025-09-29", "type": "chat" }, { - "id": "DeepSeek-V4-Pro-0813", - "name": "DeepSeek V4 Pro 0813", - "display_name": "DeepSeek V4 Pro 0813", + "id": "anthropic/claude-sonnet-4-6", + "name": "Claude Sonnet 4.6", + "display_name": "Claude Sonnet 4.6", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" @@ -134065,83 +132724,116 @@ }, "limit": { "context": 1000000, - "output": 384000 + "output": 64000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": false, + "mode": "mixed", + "budget": { + "min": 1024, + "unit": "tokens" + }, + "effort": "high", + "effort_options": [ + "low", + "medium", + "high", + "max" + ], "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ "thinking_blocks" ], - "effort_options": [ - "high", - "max" + "notes": [ + "Anthropic recommends adaptive thinking with effort for Claude 4.6; budget_tokens remains a deprecated compatibility path.", + "Anthropic API defaults effort to high; lower effort levels should be chosen per workload." ] } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-08-13", - "last_updated": "2026-08-22", + "attachment": true, + "open_weights": false, + "knowledge": "2025-08-31", + "release_date": "2026-02-17", + "last_updated": "2026-03-13", "type": "chat" }, { - "id": "Kimi-K2.5", - "name": "Kimi K2.5", - "display_name": "Kimi K2.5", + "id": "anthropic/claude-opus-4-6", + "name": "Claude Opus 4.6", + "display_name": "Claude Opus 4.6", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 1000000, + "output": 128000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": false, + "mode": "mixed", + "budget": { + "min": 1024, + "unit": "tokens" + }, + "effort": "high", + "effort_options": [ + "low", + "medium", + "high", + "max" + ], "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ "thinking_blocks" + ], + "notes": [ + "Anthropic recommends adaptive thinking with effort for Claude 4.6; budget_tokens remains a deprecated compatibility path.", + "Anthropic API defaults effort to high; lower effort levels should be chosen per workload." ] } }, "attachment": true, - "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-01-27", - "last_updated": "2026-01-27", + "open_weights": false, + "knowledge": "2025-05-31", + "release_date": "2026-02-05", + "last_updated": "2026-03-13", "type": "chat" }, { - "id": "GLM-5.1", - "name": "GLM-5.1", - "display_name": "GLM-5.1", + "id": "perplexity/sonar-pro", + "name": "Sonar Pro", + "display_name": "Sonar Pro", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" @@ -134149,122 +132841,85 @@ }, "limit": { "context": 200000, - "output": 131072 + "output": 8192 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } + "supported": false }, - "attachment": false, - "open_weights": true, - "release_date": "2026-04-07", - "last_updated": "2026-04-07", + "attachment": true, + "open_weights": false, + "knowledge": "2025-09-01", + "release_date": "2024-01-01", + "last_updated": "2025-09-01", "type": "chat" }, { - "id": "Kimi-K2.7-Code", - "name": "Kimi K2.7 Code", - "display_name": "Kimi K2.7 Code", + "id": "perplexity/sonar-reasoning-pro", + "name": "Sonar Reasoning Pro", + "display_name": "Sonar Reasoning Pro", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 128000, + "output": 4096 }, - "temperature": false, - "tool_call": true, + "temperature": true, + "tool_call": false, "reasoning": { "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } - }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-06-12", - "last_updated": "2026-06-12", + "attachment": true, + "open_weights": false, + "knowledge": "2025-09-01", + "release_date": "2024-01-01", + "last_updated": "2025-09-01", "type": "chat" }, { - "id": "Kimi-K2.6", - "name": "Kimi K2.6", - "display_name": "Kimi K2.6", + "id": "deepseek/deepseek-chat", + "name": "DeepSeek Chat", + "display_name": "DeepSeek Chat", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 1000000, + "output": 384000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } + "supported": false }, - "attachment": true, + "attachment": false, "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-04-20", - "last_updated": "2026-04-20", + "knowledge": "2025-09", + "release_date": "2025-12-01", + "last_updated": "2026-02-28", "type": "chat" }, { - "id": "GLM-5.3-Flash", - "name": "GLM-5.3-Flash", - "display_name": "GLM-5.3-Flash", + "id": "deepseek/deepseek-v4-pro", + "name": "DeepSeek V4 Pro", + "display_name": "DeepSeek V4 Pro", "modalities": { "input": [ - "text", - "image", - "video", - "pdf" + "text" ], "output": [ "text" @@ -134272,7 +132927,7 @@ }, "limit": { "context": 1000000, - "output": 131072 + "output": 384000 }, "temperature": true, "tool_call": true, @@ -134283,24 +132938,36 @@ "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "high", + "effort_options": [ + "low", + "high", + "max" + ], "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ "thinking_blocks" + ], + "notes": [ + "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." ] } }, - "attachment": true, + "attachment": false, "open_weights": true, - "release_date": "2026-08-26", - "last_updated": "2026-08-26", + "knowledge": "2025-05", + "release_date": "2026-04-24", + "last_updated": "2026-04-24", "type": "chat" }, { - "id": "MiniMax-M3", - "name": "MiniMax-M3", - "display_name": "MiniMax-M3", + "id": "deepseek/deepseek-r1", + "name": "DeepSeek Reasoner", + "display_name": "DeepSeek Reasoner", "modalities": { "input": [ "text" @@ -134310,8 +132977,8 @@ ] }, "limit": { - "context": 1048576, - "output": 512000 + "context": 1000000, + "output": 384000 }, "temperature": true, "tool_call": true, @@ -134332,14 +132999,15 @@ }, "attachment": false, "open_weights": true, - "release_date": "2026-06-01", - "last_updated": "2026-06-01", + "knowledge": "2025-09", + "release_date": "2025-12-01", + "last_updated": "2026-02-28", "type": "chat" }, { - "id": "DeepSeek-V4-Pro", - "name": "DeepSeek V4 Pro", - "display_name": "DeepSeek V4 Pro", + "id": "deepseek/deepseek-v4-flash", + "name": "DeepSeek V4 Flash", + "display_name": "DeepSeek V4 Flash", "modalities": { "input": [ "text" @@ -134386,11 +133054,20 @@ "release_date": "2026-04-24", "last_updated": "2026-04-24", "type": "chat" - }, + } + ] + }, + "minimax": { + "id": "minimax", + "name": "MiniMax (minimax.io)", + "display_name": "MiniMax (minimax.io)", + "api": "https://api.minimax.io/anthropic/v1", + "doc": "https://platform.minimax.io/docs/guides/quickstart", + "models": [ { - "id": "GLM-5", - "name": "GLM-5", - "display_name": "GLM-5", + "id": "MiniMax-M2.5", + "name": "MiniMax-M2.5", + "display_name": "MiniMax-M2.5", "modalities": { "input": [ "text" @@ -134411,13 +133088,7 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] + "supported": true } }, "attachment": false, @@ -134427,22 +133098,24 @@ "type": "chat" }, { - "id": "Kimi-K3", - "name": "Kimi K3", - "display_name": "Kimi K3", + "id": "MiniMax-M3.1-Flash", + "name": "MiniMax-M3.1-Flash", + "display_name": "MiniMax-M3.1-Flash", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 1000000, + "output": 524288 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -134450,25 +133123,25 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" ] } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-07-16", - "last_updated": "2026-07-16", + "attachment": true, + "open_weights": false, + "release_date": "2026-10-19", + "last_updated": "2026-10-19", "type": "chat" }, { - "id": "MiniMax-M2.7", - "name": "MiniMax-M2.7", - "display_name": "MiniMax-M2.7", + "id": "MiniMax-M2.7-highspeed", + "name": "MiniMax-M2.7-highspeed", + "display_name": "MiniMax-M2.7-highspeed", "modalities": { "input": [ "text" @@ -134505,9 +133178,9 @@ "type": "chat" }, { - "id": "DeepSeek-V4-Flash", - "name": "DeepSeek V4 Flash", - "display_name": "DeepSeek V4 Flash", + "id": "MiniMax-M2.1", + "name": "MiniMax-M2.1", + "display_name": "MiniMax-M2.1", "modalities": { "input": [ "text" @@ -134517,8 +133190,8 @@ ] }, "limit": { - "context": 1000000, - "output": 384000 + "context": 204800, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -134528,37 +133201,47 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "high", - "effort_options": [ - "low", - "high", - "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." - ] + "supported": true } }, "attachment": false, "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-04-24", - "last_updated": "2026-04-24", + "release_date": "2025-12-23", + "last_updated": "2025-12-23", "type": "chat" }, { - "id": "MiniMax-M2.5", - "name": "MiniMax-M2.5", - "display_name": "MiniMax-M2.5", + "id": "MiniMax-M2", + "name": "MiniMax-M2", + "display_name": "MiniMax-M2", + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 204800, + "output": 131072 + }, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": true, + "default": true + }, + "attachment": false, + "open_weights": true, + "release_date": "2025-10-27", + "last_updated": "2025-10-27", + "type": "chat" + }, + { + "id": "MiniMax-M2.7", + "name": "MiniMax-M2.7", + "display_name": "MiniMax-M2.7", "modalities": { "input": [ "text" @@ -134590,14 +133273,14 @@ }, "attachment": false, "open_weights": true, - "release_date": "2026-02-12", - "last_updated": "2026-02-12", + "release_date": "2026-03-18", + "last_updated": "2026-03-18", "type": "chat" }, { - "id": "GLM-5.2", - "name": "GLM-5.2", - "display_name": "GLM-5.2", + "id": "MiniMax-M2.5-highspeed", + "name": "MiniMax-M2.5-highspeed", + "display_name": "MiniMax-M2.5-highspeed", "modalities": { "input": [ "text" @@ -134607,7 +133290,7 @@ ] }, "limit": { - "context": 1000000, + "context": 204800, "output": 131072 }, "temperature": true, @@ -134618,25 +133301,19 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] + "supported": true } }, "attachment": false, "open_weights": true, - "release_date": "2026-06-13", - "last_updated": "2026-06-13", + "release_date": "2026-02-13", + "last_updated": "2026-02-13", "type": "chat" }, { - "id": "Qwen3.8-Flash", - "name": "Qwen3.8 Flash", - "display_name": "Qwen3.8 Flash", + "id": "MiniMax-M3", + "name": "MiniMax-M3", + "display_name": "MiniMax-M3", "modalities": { "input": [ "text", @@ -134649,7 +133326,7 @@ }, "limit": { "context": 1000000, - "output": 131072 + "output": 524288 }, "temperature": true, "tool_call": true, @@ -134659,36 +133336,40 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] + "supported": true } }, "attachment": true, - "open_weights": false, - "release_date": "2026-08-26", - "last_updated": "2026-08-26", + "open_weights": true, + "release_date": "2026-06-01", + "last_updated": "2026-06-25", "type": "chat" - }, + } + ] + }, + "hopper": { + "id": "hopper", + "name": "Hopper", + "display_name": "Hopper", + "api": "https://api.withhopper.com/v1", + "doc": "https://withhopper.com/agents/deepseek-v4.1-flash-1017", + "models": [ { - "id": "GLM-5.3", - "name": "GLM-5.3", - "display_name": "GLM-5.3", + "id": "deepseek-v4.1-flash-1017-abliterated", + "name": "DeepSeek V4.1 Flash 1017 Abliterated", + "display_name": "DeepSeek V4.1 Flash 1017 Abliterated", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 1048576, + "output": 1048576 }, "temperature": true, "tool_call": true, @@ -134698,46 +133379,36 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "low", + "high", + "max" ] } }, - "attachment": false, + "attachment": true, "open_weights": true, - "release_date": "2026-08-14", - "last_updated": "2026-08-14", + "knowledge": "2025-05", + "release_date": "2026-10-17", + "last_updated": "2026-10-17", "type": "chat" - } - ] - }, - "qvac": { - "id": "qvac", - "name": "QVAC", - "display_name": "QVAC", - "doc": "https://www.npmjs.com/package/@qvac/ai-sdk-provider", - "models": [ + }, { - "id": "qwen3.5-9b", - "name": "Qwen3.5 9B", - "display_name": "Qwen3.5 9B", + "id": "deepseek-v4.1-flash-1017", + "name": "DeepSeek V4.1 Flash 1017", + "display_name": "DeepSeek V4.1 Flash 1017", "modalities": { "input": [ "text", - "image", - "video" + "image" ], "output": [ "text" ] }, "limit": { - "context": 32768, - "output": 8192 + "context": 1048576, + "output": 1048576 }, "temperature": true, "tool_call": true, @@ -134755,32 +133426,46 @@ "thinking_blocks" ], "effort_options": [ - "none", + "low", + "high", "max" ] } }, "attachment": true, "open_weights": true, - "release_date": "2026-02-23", - "last_updated": "2026-02-23", + "knowledge": "2025-05", + "release_date": "2026-10-17", + "last_updated": "2026-10-17", "type": "chat" - }, + } + ] + }, + "tensorx": { + "id": "tensorx", + "name": "TensorX", + "display_name": "TensorX", + "api": "https://api.tensorx.ai/v1", + "doc": "https://docs.tensorx.ai/", + "models": [ { - "id": "gpt-oss-20b", - "name": "GPT OSS 20B", - "display_name": "GPT OSS 20B", + "id": "z-ai/glm-5.3-flash", + "name": "GLM-5.3-Flash", + "display_name": "GLM-5.3-Flash", "modalities": { "input": [ - "text" + "text", + "image", + "video", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 32768 + "context": 1048576, + "output": 64000 }, "temperature": true, "tool_call": true, @@ -134790,20 +133475,24 @@ }, "extra_capabilities": { "reasoning": { - "supported": true + "supported": true, + "effort_options": [ + "low", + "high", + "max" + ] } }, - "attachment": false, + "attachment": true, "open_weights": true, - "knowledge": "2024-06", - "release_date": "2025-08-05", - "last_updated": "2025-08-05", + "release_date": "2026-08-26", + "last_updated": "2026-08-26", "type": "chat" }, { - "id": "qwen3.5-0.8b", - "name": "Qwen3.5 0.8B", - "display_name": "Qwen3.5 0.8B", + "id": "z-ai/glm-5.3", + "name": "GLM-5.3", + "display_name": "GLM-5.3", "modalities": { "input": [ "text" @@ -134813,24 +133502,35 @@ ] }, "limit": { - "context": 32768, - "output": 8192 + "context": 1048576, + "output": 64000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "effort_options": [ + "low", + "high", + "max" + ] + } }, "attachment": false, "open_weights": true, - "release_date": "2026-03-02", - "last_updated": "2026-03-02", + "release_date": "2026-08-14", + "last_updated": "2026-08-14", "type": "chat" }, { - "id": "qwen3.5-2b", - "name": "Qwen3.5 2B", - "display_name": "Qwen3.5 2B", + "id": "z-ai/glm-5", + "name": "GLM-5", + "display_name": "GLM-5", "modalities": { "input": [ "text" @@ -134840,13 +133540,14 @@ ] }, "limit": { - "context": 32768, - "output": 8192 + "context": 202752, + "output": 202752 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true + "supported": true, + "default": true }, "extra_capabilities": { "reasoning": { @@ -134856,19 +133557,28 @@ "visibility": "summary", "continuation": [ "thinking_blocks" + ], + "effort_options": [ + "none", + "minimal", + "low", + "medium", + "high", + "xhigh", + "max" ] } }, "attachment": false, "open_weights": true, - "release_date": "2026-03-02", - "last_updated": "2026-03-02", + "release_date": "2026-02-12", + "last_updated": "2026-02-12", "type": "chat" }, { - "id": "gpt-oss-120b", - "name": "GPT OSS 120B", - "display_name": "GPT OSS 120B", + "id": "z-ai/glm-5.1", + "name": "GLM-5.1", + "display_name": "GLM-5.1", "modalities": { "input": [ "text" @@ -134878,8 +133588,8 @@ ] }, "limit": { - "context": 131072, - "output": 32768 + "context": 202752, + "output": 202752 }, "temperature": true, "tool_call": true, @@ -134894,27 +133604,28 @@ }, "attachment": false, "open_weights": true, - "knowledge": "2024-06", - "release_date": "2025-08-05", - "last_updated": "2025-08-05", + "release_date": "2026-04-07", + "last_updated": "2026-04-07", "type": "chat" }, { - "id": "gemma4-31b", - "name": "Gemma 4 31B IT", - "display_name": "Gemma 4 31B IT", + "id": "z-ai/glm-5v-turbo", + "name": "GLM-5V-Turbo", + "display_name": "GLM-5V-Turbo", "modalities": { "input": [ "text", - "image" + "image", + "video", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 32768 + "context": 202752, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -134922,30 +133633,40 @@ "supported": true, "default": true }, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "none", + "minimal", + "low", + "medium", + "high", + "xhigh", + "max" + ] + } + }, "attachment": true, - "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-04-02", - "last_updated": "2026-04-02", + "open_weights": false, + "release_date": "2026-04-01", + "last_updated": "2026-04-01", "type": "chat" }, { - "id": "qwen3.6-35b-a3b", - "name": "Qwen3.6 35B-A3B", - "display_name": "Qwen3.6 35B-A3B", + "id": "z-ai/glm-5.2", + "name": "GLM-5.2", + "display_name": "GLM-5.2", "modalities": { "input": [ - "text", - "image", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 65536 + "context": 1048576, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -134956,24 +133677,22 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "high", + "max" ] } }, - "attachment": true, + "attachment": false, "open_weights": true, - "release_date": "2026-04-17", - "last_updated": "2026-04-17", + "release_date": "2026-06-13", + "last_updated": "2026-06-13", "type": "chat" }, { - "id": "qwen3.5-4b", - "name": "Qwen3.5 4B", - "display_name": "Qwen3.5 4B", + "id": "z-ai/glm-5-turbo", + "name": "GLM-5-Turbo", + "display_name": "GLM-5-Turbo", "modalities": { "input": [ "text" @@ -134983,35 +133702,71 @@ ] }, "limit": { - "context": 32768, - "output": 8192 + "context": 202752, + "output": 131072 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true + } + }, + "attachment": false, + "open_weights": false, + "release_date": "2026-03-16", + "last_updated": "2026-03-16", + "type": "chat" + }, + { + "id": "minimax/minimax-m2.5", + "name": "MiniMax-M2.5", + "display_name": "MiniMax-M2.5", + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 196608, + "output": 65536 + }, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": true, + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "minimal", + "low", + "medium", + "high", + "xhigh", + "max" ] } }, "attachment": false, "open_weights": true, - "release_date": "2026-03-02", - "last_updated": "2026-03-02", + "release_date": "2026-02-12", + "last_updated": "2026-02-12", "type": "chat" }, { - "id": "qwen3.6-27b", - "name": "Qwen3.6 27B", - "display_name": "Qwen3.6 27B", + "id": "minimax/minimax-m3", + "name": "MiniMax-M3", + "display_name": "MiniMax-M3", "modalities": { "input": [ "text", @@ -135023,8 +133778,8 @@ ] }, "limit": { - "context": 262144, - "output": 65536 + "context": 1048576, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -135034,47 +133789,34 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] + "supported": true } }, "attachment": true, "open_weights": true, - "release_date": "2026-04-22", - "last_updated": "2026-04-22", + "release_date": "2026-06-01", + "last_updated": "2026-06-01", "type": "chat" - } - ] - }, - "lilac": { - "id": "lilac", - "name": "Lilac", - "display_name": "Lilac", - "api": "https://api.getlilac.com/v1", - "doc": "https://docs.getlilac.com/inference/models", - "models": [ + }, { - "id": "zai-org/glm-5.2", - "name": "GLM 5.2", - "display_name": "GLM 5.2", + "id": "moonshotai/kimi-k3", + "name": "Kimi K3", + "display_name": "Kimi K3", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 524288, - "output": 524288 + "context": 1048576, + "output": 131072 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -135082,29 +133824,19 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "effort_options": [ - "high", - "max" - ] + "supported": true } }, - "attachment": false, + "attachment": true, "open_weights": true, - "release_date": "2026-06-13", - "last_updated": "2026-06-13", + "release_date": "2026-07-16", + "last_updated": "2026-07-16", "type": "chat" }, { - "id": "minimaxai/minimax-m3", - "name": "MiniMax M3", - "display_name": "MiniMax M3", + "id": "moonshotai/kimi-k2.7-code", + "name": "Kimi K2.7 Code", + "display_name": "Kimi K2.7 Code", "modalities": { "input": [ "text", @@ -135116,10 +133848,10 @@ ] }, "limit": { - "context": 1048576, - "output": 1048576 + "context": 262144, + "output": 262144 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -135132,14 +133864,15 @@ }, "attachment": true, "open_weights": true, - "release_date": "2026-06-01", - "last_updated": "2026-06-01", + "knowledge": "2025-01", + "release_date": "2026-06-12", + "last_updated": "2026-06-12", "type": "chat" }, { - "id": "google/gemma-4-31b-it", - "name": "Gemma 4 31B IT", - "display_name": "Gemma 4 31B IT", + "id": "moonshotai/kimi-k2.5", + "name": "Kimi K2.5", + "display_name": "Kimi K2.5", "modalities": { "input": [ "text", @@ -135151,10 +133884,10 @@ ] }, "limit": { - "context": 262100, - "output": 262100 + "context": 262144, + "output": 262144 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -135174,8 +133907,8 @@ "attachment": true, "open_weights": true, "knowledge": "2025-01", - "release_date": "2026-04-02", - "last_updated": "2026-04-02", + "release_date": "2026-01-27", + "last_updated": "2026-01-27", "type": "chat" }, { @@ -135185,7 +133918,8 @@ "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ "text" @@ -135203,13 +133937,7 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] + "supported": true } }, "attachment": true, @@ -135218,20 +133946,11 @@ "release_date": "2026-04-20", "last_updated": "2026-04-20", "type": "chat" - } - ] - }, - "sambanova": { - "id": "sambanova", - "name": "SambaNova", - "display_name": "SambaNova", - "api": "https://api.sambanova.ai/v1", - "doc": "https://docs.sambanova.ai/cloud/docs/get-started/supported-models", - "models": [ + }, { - "id": "MiniMax-M3", - "name": "MiniMax-M3", - "display_name": "MiniMax-M3", + "id": "qwen/qwen3.8-2.4t-a95b", + "name": "Qwen3.8 2.4T A95B", + "display_name": "Qwen3.8 2.4T A95B", "modalities": { "input": [ "text" @@ -135241,8 +133960,8 @@ ] }, "limit": { - "context": 1048576, - "output": 1048576 + "context": 262144, + "output": 64000 }, "temperature": true, "tool_call": true, @@ -135252,30 +133971,37 @@ }, "extra_capabilities": { "reasoning": { - "supported": true + "supported": true, + "effort_options": [ + "low", + "medium", + "xhigh" + ] } }, "attachment": false, "open_weights": true, - "release_date": "2026-06-01", - "last_updated": "2026-06-01", + "release_date": "2026-08-12", + "last_updated": "2026-08-12", "type": "chat" }, { - "id": "gpt-oss-120b", - "name": "GPT OSS 120B", - "display_name": "GPT OSS 120B", + "id": "qwen/qwen3.8-27b", + "name": "Qwen3.8 27B", + "display_name": "Qwen3.8 27B", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 131072 + "context": 262144, + "output": 32768 }, "temperature": true, "tool_call": true, @@ -135285,36 +134011,36 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, "effort_options": [ "low", "medium", - "high" + "xhigh" ] } }, - "attachment": false, + "attachment": true, "open_weights": true, - "knowledge": "2024-06", - "release_date": "2025-08-05", - "last_updated": "2025-08-05", + "release_date": "2026-08-14", + "last_updated": "2026-08-14", "type": "chat" }, { - "id": "DeepSeek-V3.2", - "name": "DeepSeek V3.2", - "display_name": "DeepSeek V3.2", + "id": "qwen/qwen3.8-flash-next", + "name": "Qwen3.8 Flash Next", + "display_name": "Qwen3.8 Flash Next", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 32768, - "output": 7168 + "context": 262144, + "output": 64000 }, "temperature": true, "tool_call": true, @@ -135324,20 +134050,23 @@ }, "extra_capabilities": { "reasoning": { - "supported": true + "effort_options": [ + "low", + "medium", + "xhigh" + ] } }, - "attachment": false, + "attachment": true, "open_weights": true, - "knowledge": "2024-07", - "release_date": "2025-12-01", - "last_updated": "2025-12-01", + "release_date": "2026-08-27", + "last_updated": "2026-08-27", "type": "chat" }, { - "id": "gemma-4-31B-it", - "name": "Gemma 4 31B IT", - "display_name": "Gemma 4 31B IT", + "id": "qwen/qwen3.5-9b", + "name": "Qwen3.5 9B", + "display_name": "Qwen3.5 9B", "modalities": { "input": [ "text", @@ -135349,8 +134078,8 @@ ] }, "limit": { - "context": 131072, - "output": 131072 + "context": 262144, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -135360,31 +134089,47 @@ }, "extra_capabilities": { "reasoning": { - "supported": true + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "effort_options": [ + "none", + "minimal", + "low", + "medium", + "high", + "xhigh", + "max" + ] } }, "attachment": true, "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-04-02", - "last_updated": "2026-04-02", + "release_date": "2026-02-23", + "last_updated": "2026-02-23", "type": "chat" }, { - "id": "DeepSeek-V3.1", - "name": "DeepSeek-V3.1", - "display_name": "DeepSeek-V3.1", + "id": "qwen/qwen3.5-122b-a10b", + "name": "Qwen3.5 122B-A10B", + "display_name": "Qwen3.5 122B-A10B", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 7168 + "context": 262144, + "output": 262144 }, "temperature": true, "tool_call": true, @@ -135392,16 +134137,36 @@ "supported": true, "default": true }, - "attachment": false, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "effort_options": [ + "none", + "minimal", + "low", + "medium", + "high", + "xhigh", + "max" + ] + } + }, + "attachment": true, "open_weights": true, - "release_date": "2025-08-21", - "last_updated": "2025-08-21", + "release_date": "2026-02-23", + "last_updated": "2026-02-23", "type": "chat" }, { - "id": "Meta-Llama-3.3-70B-Instruct", - "name": "Llama-3.3-70B-Instruct", - "display_name": "Llama-3.3-70B-Instruct", + "id": "qwen/qwen3-235b-a22b-2507", + "name": "Qwen3 235B-A22B-2507", + "display_name": "Qwen3 235B-A22B-2507", "modalities": { "input": [ "text" @@ -135411,8 +134176,8 @@ ] }, "limit": { - "context": 131072, - "output": 3072 + "context": 131000, + "output": 262144 }, "temperature": true, "tool_call": true, @@ -135421,39 +134186,28 @@ }, "attachment": false, "open_weights": true, - "knowledge": "2023-12", - "release_date": "2024-12-06", - "last_updated": "2024-12-06", + "knowledge": "2025-06-30", + "release_date": "2025-07-21", + "last_updated": "2025-07-21", "type": "chat" - } - ] - }, - "kimi-code-plan-global": { - "id": "kimi-code-plan-global", - "name": "Kimi For Coding (kimi.ai)", - "display_name": "Kimi For Coding (kimi.ai)", - "api": "https://api.kimi.ai/coding/v1", - "doc": "https://www.kimi.ai/code/docs/en/kimi-code/models.html", - "models": [ + }, { - "id": "k3", - "name": "Kimi K3", - "display_name": "Kimi K3", + "id": "deepseek/deepseek-r1-0528", + "name": "DeepSeek R1-0528", + "display_name": "DeepSeek R1-0528", "modalities": { "input": [ - "text", - "image", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 164000, + "output": 8192 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -135469,38 +134223,39 @@ "thinking_blocks" ], "effort_options": [ - "none", + "minimal", "low", + "medium", "high", + "xhigh", "max" ] } }, - "attachment": true, + "attachment": false, "open_weights": true, - "release_date": "2026-07-16", - "last_updated": "2026-07-16", + "knowledge": "2024-07", + "release_date": "2025-05-28", + "last_updated": "2025-05-28", "type": "chat" }, { - "id": "kimi-for-coding-highspeed", - "name": "Kimi For Coding HighSpeed", - "display_name": "Kimi For Coding HighSpeed", + "id": "deepseek/deepseek-v3.2", + "name": "DeepSeek V3.2", + "display_name": "DeepSeek V3.2", "modalities": { "input": [ - "text", - "image", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 32768 + "context": 163840, + "output": 163840 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -135509,30 +134264,32 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "none", + "minimal", + "low", + "medium", + "high", + "xhigh", + "max" ] } }, - "attachment": true, + "attachment": false, "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-06-12", - "last_updated": "2026-06-12", + "knowledge": "2025-05", + "release_date": "2025-12-01", + "last_updated": "2025-12-01", "type": "chat" }, { - "id": "kimi-for-coding", - "name": "kimi-for-coding", - "display_name": "kimi-for-coding", + "id": "deepseek/deepseek-v4.1-flash", + "name": "DeepSeek V4.1 Flash", + "display_name": "DeepSeek V4.1 Flash", "modalities": { "input": [ - "text", - "image", - "video" + "text", + "image" ], "output": [ "text" @@ -135540,9 +134297,9 @@ }, "limit": { "context": 1048576, - "output": 32768 + "output": 384000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -135551,35 +134308,26 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], "effort_options": [ - "none", - "low", "high", "max" ] } }, "attachment": true, - "open_weights": false, - "release_date": "2026-09-11", - "last_updated": "2026-09-11", + "open_weights": true, + "knowledge": "2025-05", + "release_date": "2026-09-10", + "last_updated": "2026-09-10", "type": "chat" }, { - "id": "k3.1-pro-highspeed", - "name": "Kimi K3.1 Pro Highspeed", - "display_name": "Kimi K3.1 Pro Highspeed", + "id": "deepseek/deepseek-v4-pro", + "name": "DeepSeek V4 Pro", + "display_name": "DeepSeek V4 Pro", "modalities": { "input": [ - "text", - "image", - "video" + "text" ], "output": [ "text" @@ -135587,9 +134335,9 @@ }, "limit": { "context": 1048576, - "output": 131072 + "output": 384000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -135598,43 +134346,49 @@ "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "high", + "effort_options": [ + "low", + "high", + "max" + ], "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ "thinking_blocks" ], - "effort_options": [ - "low", - "high", - "max" + "notes": [ + "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." ] } }, - "attachment": true, - "open_weights": false, - "release_date": "2026-10-23", - "last_updated": "2026-10-23", + "attachment": false, + "open_weights": true, + "knowledge": "2025-05", + "release_date": "2026-04-24", + "last_updated": "2026-04-24", "type": "chat" }, { - "id": "k3-256k", - "name": "Kimi K3-256K", - "display_name": "Kimi K3-256K", + "id": "deepseek/deepseek-v4-pro-0813", + "name": "DeepSeek V4 Pro 0813", + "display_name": "DeepSeek V4 Pro 0813", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 131072 + "context": 1048576, + "output": 64000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -135643,35 +134397,25 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], "effort_options": [ - "none", - "low", "high", "max" ] } }, - "attachment": true, + "attachment": false, "open_weights": true, - "release_date": "2026-07-16", - "last_updated": "2026-07-16", + "release_date": "2026-08-13", + "last_updated": "2026-08-22", "type": "chat" }, { - "id": "k3.1-pro", - "name": "Kimi K3.1 Pro", - "display_name": "Kimi K3.1 Pro", + "id": "deepseek/deepseek-v4-flash-0731", + "name": "DeepSeek V4 Flash 0731", + "display_name": "DeepSeek V4 Flash 0731", "modalities": { "input": [ - "text", - "image", - "video" + "text" ], "output": [ "text" @@ -135679,9 +134423,9 @@ }, "limit": { "context": 1048576, - "output": 131072 + "output": 384000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -135690,49 +134434,45 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], "effort_options": [ - "low", "high", "max" ] } }, - "attachment": true, - "open_weights": false, - "release_date": "2026-10-23", - "last_updated": "2026-10-23", + "attachment": false, + "open_weights": true, + "knowledge": "2025-05", + "release_date": "2026-07-31", + "last_updated": "2026-07-31", "type": "chat" } ] }, - "salad-cloud": { - "id": "salad-cloud", - "name": "SaladCloud AI Gateway", - "display_name": "SaladCloud AI Gateway", - "doc": "https://docs.salad.com/ai-gateway/explanation/overview", + "meganova": { + "id": "meganova", + "name": "Meganova", + "display_name": "Meganova", + "api": "https://api.meganova.ai/v1", + "doc": "https://docs.meganova.ai", "models": [ { - "id": "qwen3.6-35b-a3b", - "name": "Qwen3.6 35B-A3B", - "display_name": "Qwen3.6 35B-A3B", + "id": "Qwen/Qwen3.5-Plus", + "name": "Qwen3.5 Plus", + "display_name": "Qwen3.5 Plus", "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 1000000, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -135752,57 +134492,44 @@ } }, "attachment": true, - "open_weights": true, - "release_date": "2026-04-17", - "last_updated": "2026-04-17", + "open_weights": false, + "knowledge": "2025-04", + "release_date": "2026-02-16", + "last_updated": "2026-02-16", "type": "chat" - } - ] - }, - "alibaba": { - "id": "alibaba", - "name": "alibaba", - "display_name": "alibaba", - "api": "https://dashscope-intl.aliyuncs.com/compatible-mode/v1", - "doc": "https://www.alibabacloud.com/help/en/model-studio/models", - "models": [ + }, { - "id": "qwen3.7-max", - "name": "Qwen3.7 Max", - "display_name": "Qwen3.7 Max", + "id": "Qwen/Qwen2.5-VL-32B-Instruct", + "name": "Qwen2.5 VL 32B Instruct", + "display_name": "Qwen2.5 VL 32B Instruct", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 16384, + "output": 16384 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true - } + "supported": false }, - "attachment": false, - "open_weights": false, - "release_date": "2026-05-21", - "last_updated": "2026-05-21", + "attachment": true, + "open_weights": true, + "release_date": "2025-03-24", + "last_updated": "2025-03-24", "type": "chat" }, { - "id": "qwen3-max", - "name": "Qwen3 Max", - "display_name": "Qwen3 Max", + "id": "Qwen/Qwen3-235B-A22B-Instruct-2507", + "name": "Qwen3 235B A22B Instruct 2507", + "display_name": "Qwen3 235B A22B Instruct 2507", "modalities": { "input": [ "text" @@ -135812,36 +134539,24 @@ ] }, "limit": { - "context": 262144, - "output": 65536 + "context": 262000, + "output": 262000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } + "supported": false }, "attachment": false, - "open_weights": false, - "knowledge": "2025-04", - "release_date": "2025-09-23", - "last_updated": "2025-09-23", + "open_weights": true, + "release_date": "2025-07-21", + "last_updated": "2025-07-21", "type": "chat" }, { - "id": "qwen-mt-turbo", - "name": "Qwen Mt Turbo", - "display_name": "Qwen Mt Turbo", + "id": "XiaomiMiMo/MiMo-V2-Flash", + "name": "MiMo V2 Flash", + "display_name": "MiMo V2 Flash", "modalities": { "input": [ "text" @@ -135851,25 +134566,26 @@ ] }, "limit": { - "context": 16384, - "output": 8192 + "context": 262144, + "output": 32000 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, "attachment": false, - "open_weights": false, - "knowledge": "2024-04", - "release_date": "2025-01", - "last_updated": "2025-01", + "open_weights": true, + "knowledge": "2024-12-01", + "release_date": "2025-12-16", + "last_updated": "2026-02-04", "type": "chat" }, { - "id": "qwen3-coder-30b-a3b-instruct", - "name": "Qwen3-Coder 30B-A3B Instruct", - "display_name": "Qwen3-Coder 30B-A3B Instruct", + "id": "meta-llama/Llama-3.3-70B-Instruct", + "name": "Llama 3.3 70B Instruct", + "display_name": "Llama 3.3 70B Instruct", "modalities": { "input": [ "text" @@ -135879,8 +134595,8 @@ ] }, "limit": { - "context": 262144, - "output": 65536 + "context": 131072, + "output": 16384 }, "temperature": true, "tool_call": true, @@ -135889,15 +134605,15 @@ }, "attachment": false, "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-04", - "last_updated": "2025-04", + "knowledge": "2023-12", + "release_date": "2024-12-06", + "last_updated": "2024-12-06", "type": "chat" }, { - "id": "kimi-k3", - "name": "Kimi K3", - "display_name": "Kimi K3", + "id": "moonshotai/Kimi-K2.5", + "name": "Kimi K2.5", + "display_name": "Kimi K2.5", "modalities": { "input": [ "text", @@ -135908,10 +134624,10 @@ ] }, "limit": { - "context": 1048576, - "output": 1048576 + "context": 262144, + "output": 262144 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -135925,28 +134641,23 @@ "visibility": "summary", "continuation": [ "thinking_blocks" - ], - "effort_options": [ - "low", - "high", - "max" ] } }, "attachment": true, "open_weights": true, - "release_date": "2026-07-16", - "last_updated": "2026-07-16", + "knowledge": "2026-01", + "release_date": "2026-01-27", + "last_updated": "2026-01-27", "type": "chat" }, { - "id": "qwen3-vl-plus", - "name": "Qwen3 VL Plus", - "display_name": "Qwen3 VL Plus", + "id": "moonshotai/Kimi-K2-Thinking", + "name": "Kimi K2 Thinking", + "display_name": "Kimi K2 Thinking", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" @@ -135954,18 +134665,13 @@ }, "limit": { "context": 262144, - "output": 32768 + "output": 262144 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false, - "budget": { - "default": 81920, - "min": 0, - "max": 81920 - } + "default": true }, "extra_capabilities": { "reasoning": { @@ -135979,16 +134685,16 @@ } }, "attachment": false, - "open_weights": false, - "knowledge": "2025-04", - "release_date": "2025-09-23", - "last_updated": "2025-09-23", + "open_weights": true, + "knowledge": "2024-08", + "release_date": "2025-11-06", + "last_updated": "2025-11-06", "type": "chat" }, { - "id": "qwen-turbo", - "name": "Qwen Turbo", - "display_name": "Qwen Turbo", + "id": "zai-org/GLM-5", + "name": "GLM-5", + "display_name": "GLM-5", "modalities": { "input": [ "text" @@ -135998,19 +134704,14 @@ ] }, "limit": { - "context": 131072, - "output": 16384 + "context": 202752, + "output": 131072 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false, - "budget": { - "default": 38912, - "min": 0, - "max": 38912 - } + "default": true }, "extra_capabilities": { "reasoning": { @@ -136023,94 +134724,27 @@ ] } }, - "search": { - "supported": true, - "default": false, - "forced_search": false, - "search_strategy": "turbo" - }, "attachment": false, - "open_weights": false, - "knowledge": "2024-04", - "release_date": "2024-11-01", - "last_updated": "2025-04-28", + "open_weights": true, + "release_date": "2026-02-12", + "last_updated": "2026-02-12", "type": "chat" }, { - "id": "qwen3-asr-flash", - "name": "Qwen3-ASR Flash", - "display_name": "Qwen3-ASR Flash", + "id": "zai-org/GLM-4.6", + "name": "GLM-4.6", + "display_name": "GLM-4.6", "modalities": { "input": [ - "audio" - ], - "output": [ "text" - ] - }, - "limit": { - "context": 53248, - "output": 4096 - }, - "temperature": false, - "tool_call": false, - "reasoning": { - "supported": false - }, - "attachment": true, - "open_weights": false, - "knowledge": "2024-04", - "release_date": "2025-09-08", - "last_updated": "2025-09-08" - }, - { - "id": "qwen3-livetranslate-flash-realtime", - "name": "Qwen3-LiveTranslate Flash Realtime", - "display_name": "Qwen3-LiveTranslate Flash Realtime", - "modalities": { - "input": [ - "text", - "image", - "audio", - "video" - ], - "output": [ - "text", - "audio" - ] - }, - "limit": { - "context": 53248, - "output": 4096 - }, - "temperature": true, - "tool_call": false, - "reasoning": { - "supported": false - }, - "attachment": true, - "open_weights": false, - "knowledge": "2024-04", - "release_date": "2025-09-22", - "last_updated": "2025-09-22" - }, - { - "id": "qwen3.6-flash", - "name": "Qwen3.6 Flash", - "display_name": "Qwen3.6 Flash", - "modalities": { - "input": [ - "text", - "image", - "video" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 65536 + "context": 202752, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -136129,16 +134763,17 @@ ] } }, - "attachment": true, - "open_weights": false, - "release_date": "2026-04-27", - "last_updated": "2026-04-27", + "attachment": false, + "open_weights": true, + "knowledge": "2025-04", + "release_date": "2025-09-30", + "last_updated": "2025-09-30", "type": "chat" }, { - "id": "qwen-flash", - "name": "Qwen Flash", - "display_name": "Qwen Flash", + "id": "zai-org/GLM-4.7", + "name": "GLM-4.7", + "display_name": "GLM-4.7", "modalities": { "input": [ "text" @@ -136148,19 +134783,14 @@ ] }, "limit": { - "context": 1000000, - "output": 32768 + "context": 202752, + "output": 131072 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false, - "budget": { - "default": 81920, - "min": 0, - "max": 81920 - } + "default": true }, "extra_capabilities": { "reasoning": { @@ -136173,74 +134803,46 @@ ] } }, - "search": { - "supported": true, - "default": false, - "forced_search": false, - "search_strategy": "turbo" - }, "attachment": false, - "open_weights": false, - "knowledge": "2024-04", - "release_date": "2025-07-28", - "last_updated": "2025-07-28", + "open_weights": true, + "knowledge": "2025-04", + "release_date": "2025-12-22", + "last_updated": "2025-12-22", "type": "chat" }, { - "id": "qwen3.8-omni-flash", - "name": "Qwen3.8 Omni Flash", - "display_name": "Qwen3.8 Omni Flash", + "id": "mistralai/Mistral-Small-3.2-24B-Instruct-2506", + "name": "Mistral Small 3.2 24B Instruct", + "display_name": "Mistral Small 3.2 24B Instruct", "modalities": { "input": [ "text", - "image", - "audio", - "video" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 32768, + "output": 8192 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "xhigh", - "effort_options": [ - "none", - "low", - "medium", - "xhigh" - ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } + "supported": false }, "attachment": true, - "open_weights": false, - "release_date": "2026-09-17", - "last_updated": "2026-09-17", + "open_weights": true, + "knowledge": "2024-10", + "release_date": "2025-06-20", + "last_updated": "2025-06-20", "type": "chat" }, { - "id": "qwen3-coder-480b-a35b-instruct", - "name": "Qwen3-Coder 480B-A35B Instruct", - "display_name": "Qwen3-Coder 480B-A35B Instruct", + "id": "mistralai/Mistral-Nemo-Instruct-2407", + "name": "Mistral Nemo Instruct 2407", + "display_name": "Mistral Nemo Instruct 2407", "modalities": { "input": [ "text" @@ -136250,7 +134852,7 @@ ] }, "limit": { - "context": 262144, + "context": 131072, "output": 65536 }, "temperature": true, @@ -136260,15 +134862,15 @@ }, "attachment": false, "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-04", - "last_updated": "2025-04", + "knowledge": "2024-07", + "release_date": "2024-07-18", + "last_updated": "2024-07-18", "type": "chat" }, { - "id": "qwq-plus", - "name": "QwQ Plus", - "display_name": "QwQ Plus", + "id": "MiniMaxAI/MiniMax-M2.5", + "name": "MiniMax M2.5", + "display_name": "MiniMax M2.5", "modalities": { "input": [ "text" @@ -136278,19 +134880,14 @@ ] }, "limit": { - "context": 131072, - "output": 8192 + "context": 204800, + "output": 131072 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { "supported": true, - "default": true, - "budget": { - "default": 32768, - "min": 0, - "max": 32768 - } + "default": true }, "extra_capabilities": { "reasoning": { @@ -136303,36 +134900,27 @@ ] } }, - "search": { - "supported": true, - "default": false, - "forced_search": false, - "search_strategy": "turbo" - }, "attachment": false, - "open_weights": false, - "knowledge": "2024-04", - "release_date": "2025-03-05", - "last_updated": "2025-03-05", + "open_weights": true, + "release_date": "2026-02-12", + "last_updated": "2026-02-12", "type": "chat" }, { - "id": "qwen3.5-35b-a3b", - "name": "Qwen3.5 35B-A3B", - "display_name": "Qwen3.5 35B-A3B", + "id": "MiniMaxAI/MiniMax-M2.1", + "name": "MiniMax M2.1", + "display_name": "MiniMax M2.1", "modalities": { "input": [ - "text", - "image", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 65536 + "context": 196608, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -136351,64 +134939,30 @@ ] } }, - "attachment": true, - "open_weights": true, - "release_date": "2026-02-23", - "last_updated": "2026-02-23", - "type": "chat" - }, - { - "id": "qwen2-5-omni-7b", - "name": "Qwen2.5-Omni 7B", - "display_name": "Qwen2.5-Omni 7B", - "modalities": { - "input": [ - "text", - "image", - "audio", - "video" - ], - "output": [ - "text", - "audio" - ] - }, - "limit": { - "context": 32768, - "output": 2048 - }, - "temperature": true, - "tool_call": true, - "reasoning": { - "supported": false - }, - "attachment": true, + "attachment": false, "open_weights": true, - "knowledge": "2024-04", - "release_date": "2024-12", - "last_updated": "2024-12", + "release_date": "2025-12-23", + "last_updated": "2025-12-23", "type": "chat" }, { - "id": "qwen3.5-plus", - "name": "Qwen3.5 Plus", - "display_name": "Qwen3.5 Plus", + "id": "deepseek-ai/DeepSeek-R1-0528", + "name": "DeepSeek R1 0528", + "display_name": "DeepSeek R1 0528", "modalities": { "input": [ - "text", - "image", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 65536 + "context": 163840, + "output": 64000 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { "supported": true, "default": true @@ -136424,99 +134978,71 @@ ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-04", - "release_date": "2026-02-16", - "last_updated": "2026-02-16", + "attachment": false, + "open_weights": true, + "knowledge": "2024-07", + "release_date": "2025-05-28", + "last_updated": "2025-05-28", "type": "chat" }, { - "id": "qwen3-vl-235b-a22b", - "name": "Qwen3-VL 235B-A22B", - "display_name": "Qwen3-VL 235B-A22B", + "id": "deepseek-ai/DeepSeek-V3.2-Exp", + "name": "DeepSeek V3.2 Exp", + "display_name": "DeepSeek V3.2 Exp", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 32768 + "context": 164000, + "output": 164000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } + "supported": false }, - "attachment": true, + "attachment": false, "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-04", - "last_updated": "2025-04", + "release_date": "2025-10-10", + "last_updated": "2025-10-10", "type": "chat" }, { - "id": "qwen3.5-flash", - "name": "Qwen3.5 Flash", - "display_name": "Qwen3.5 Flash", + "id": "deepseek-ai/DeepSeek-V3-0324", + "name": "DeepSeek V3 0324", + "display_name": "DeepSeek V3 0324", "modalities": { "input": [ - "text", - "image", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 65536 + "context": 163840, + "output": 163840 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } + "supported": false }, - "attachment": true, - "open_weights": false, - "release_date": "2026-02-23", - "last_updated": "2026-02-23", + "attachment": false, + "open_weights": true, + "release_date": "2025-03-24", + "last_updated": "2025-03-24", "type": "chat" }, { - "id": "qwen3-next-80b-a3b-instruct", - "name": "Qwen3 Next 80B A3B Instruct", - "display_name": "Qwen3 Next 80B A3B Instruct", + "id": "deepseek-ai/DeepSeek-V3.1", + "name": "DeepSeek V3.1", + "display_name": "DeepSeek V3.1", "modalities": { "input": [ "text" @@ -136526,8 +135052,8 @@ ] }, "limit": { - "context": 131072, - "output": 32768 + "context": 164000, + "output": 164000 }, "temperature": true, "tool_call": true, @@ -136536,15 +135062,14 @@ }, "attachment": false, "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-09", - "last_updated": "2025-09", + "release_date": "2025-08-21", + "last_updated": "2025-08-21", "type": "chat" }, { - "id": "qwen-plus", - "name": "Qwen Plus", - "display_name": "Qwen Plus", + "id": "deepseek-ai/DeepSeek-V3.2", + "name": "DeepSeek V3.2", + "display_name": "DeepSeek V3.2", "modalities": { "input": [ "text" @@ -136554,99 +135079,58 @@ ] }, "limit": { - "context": 1000000, - "output": 32768 + "context": 164000, + "output": 164000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": false, - "budget": { - "default": 81920, - "min": 0, - "max": 81920 - } + "supported": true }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] + "supported": true } }, - "search": { - "supported": true, - "default": false, - "forced_search": false, - "search_strategy": "turbo" - }, "attachment": false, - "open_weights": false, - "knowledge": "2024-04", - "release_date": "2024-01-25", - "last_updated": "2025-09-11", + "open_weights": true, + "knowledge": "2024-07", + "release_date": "2025-12-01", + "last_updated": "2025-12-01", "type": "chat" - }, + } + ] + }, + "moonshot-ai": { + "id": "moonshot-ai", + "name": "Moonshot AI", + "display_name": "Moonshot AI", + "api": "https://api.moonshot.ai/v1", + "doc": "https://platform.moonshot.ai/docs/api/chat", + "models": [ { - "id": "qwen-vl-ocr", - "name": "Qwen Vl Ocr", - "display_name": "Qwen Vl Ocr", + "id": "kimi-k3", + "name": "Kimi K3", + "display_name": "Kimi K3", "modalities": { "input": [ "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 34096, - "output": 4096 - }, - "temperature": true, - "tool_call": false, - "reasoning": { - "supported": false - }, - "attachment": false, - "open_weights": false, - "knowledge": "2024-04", - "release_date": "2024-10-28", - "last_updated": "2025-04-13", - "type": "chat" - }, - { - "id": "qwen3-235b-a22b", - "name": "Qwen3 235B A22B", - "display_name": "Qwen3 235B A22B", - "modalities": { - "input": [ - "text" + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 32768 + "context": 1048576, + "output": 1048576 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": false, - "budget": { - "default": 81920, - "min": 0, - "max": 81920 - } + "default": true }, "extra_capabilities": { "reasoning": { @@ -136656,34 +135140,37 @@ "visibility": "summary", "continuation": [ "thinking_blocks" + ], + "effort_options": [ + "low", + "high", + "max" ] } }, - "attachment": false, + "attachment": true, "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-04", - "last_updated": "2025-04", + "release_date": "2026-07-16", + "last_updated": "2026-07-16", "type": "chat" }, { - "id": "qwen3.8-max", - "name": "Qwen3.8 Max", - "display_name": "Qwen3.8 Max", + "id": "kimi-k2.7-code", + "name": "Kimi K2.7 Code", + "display_name": "Kimi K2.7 Code", "modalities": { "input": [ "text", "image", - "video", - "pdf" + "video" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 262144, + "output": 262144 }, "temperature": true, "tool_call": true, @@ -136695,68 +135182,51 @@ "reasoning": { "supported": true, "default_enabled": true, - "mode": "effort", - "budget": { - "min": 0, - "max": 262144, - "unit": "tokens" - }, - "effort": "xhigh", - "effort_options": [ - "none", - "low", - "medium", - "xhigh" - ], + "mode": "fixed", "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ "thinking_blocks" - ], - "notes": [ - "Prefer reasoning_effort; thinking_budget is an alternative, not an additional control.", - "Omitting both controls differs from explicitly selecting xhigh, which maps to 262144 budget tokens." - ], - "budget_exclusive_with_effort": true + ] } }, "attachment": true, - "open_weights": false, - "release_date": "2026-08-03", - "last_updated": "2026-08-03", + "open_weights": true, + "knowledge": "2025-01", + "release_date": "2026-04-21", + "last_updated": "2026-04-21", "type": "chat" }, { - "id": "qwen3-14b", - "name": "Qwen3 14B", - "display_name": "Qwen3 14B", + "id": "kimi-k2.7-code-highspeed", + "name": "Kimi K2.7 Code Highspeed", + "display_name": "Kimi K2.7 Code Highspeed", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 8192 + "context": 262144, + "output": 262144 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true, - "budget": { - "default": 38912, - "min": 0, - "max": 38912 - } + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": true, + "mode": "fixed", "interleaved": true, "summaries": true, "visibility": "summary", @@ -136765,17 +135235,17 @@ ] } }, - "attachment": false, + "attachment": true, "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-04", - "last_updated": "2025-04", + "knowledge": "2025-01", + "release_date": "2026-04-21", + "last_updated": "2026-04-21", "type": "chat" }, { - "id": "qwen3.5-122b-a10b", - "name": "Qwen3.5 122B-A10B", - "display_name": "Qwen3.5 122B-A10B", + "id": "kimi-k3.1-pro-highspeed", + "name": "Kimi K3.1 Pro Highspeed", + "display_name": "Kimi K3.1 Pro Highspeed", "modalities": { "input": [ "text", @@ -136787,10 +135257,10 @@ ] }, "limit": { - "context": 262144, - "output": 65536 + "context": 1048576, + "output": 1048576 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -136804,70 +135274,89 @@ "visibility": "summary", "continuation": [ "thinking_blocks" + ], + "effort_options": [ + "low", + "high", + "max" ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-02-23", - "last_updated": "2026-02-23", + "open_weights": false, + "release_date": "2026-10-23", + "last_updated": "2026-10-23", "type": "chat" }, { - "id": "qwen-vl-plus", - "name": "Qwen-VL Plus", - "display_name": "Qwen-VL Plus", + "id": "kimi-k3.1-pro", + "name": "Kimi K3.1 Pro", + "display_name": "Kimi K3.1 Pro", "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 8192 + "context": 1048576, + "output": 1048576 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "effort_options": [ + "low", + "high", + "max" + ] + } }, "attachment": true, "open_weights": false, - "knowledge": "2024-04", - "release_date": "2024-01-25", - "last_updated": "2025-08-15", + "release_date": "2026-10-23", + "last_updated": "2026-10-23", "type": "chat" }, { - "id": "qwen3-next-80b-a3b-thinking", - "name": "Qwen3 Next 80B A3B Thinking", - "display_name": "Qwen3 Next 80B A3B Thinking", + "id": "kimi-k2.6", + "name": "Kimi K2.6", + "display_name": "Kimi K2.6", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 32768 + "context": 262144, + "output": 262144 }, - "temperature": true, - "tool_call": false, + "temperature": false, + "tool_call": true, "reasoning": { "supported": true, - "default": true, - "budget": { - "default": 81920, - "min": 0, - "max": 81920 - } + "default": true }, "extra_capabilities": { "reasoning": { @@ -136880,17 +135369,26 @@ ] } }, - "attachment": false, + "attachment": true, "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-09", - "last_updated": "2025-09", + "knowledge": "2025-01", + "release_date": "2026-04-20", + "last_updated": "2026-04-20", "type": "chat" - }, + } + ] + }, + "inco": { + "id": "inco", + "name": "Inco", + "display_name": "Inco", + "api": "https://api.inco.ai/v1", + "doc": "https://platform.inco.ai/docs", + "models": [ { - "id": "qwen3.6-plus", - "name": "Qwen3.6 Plus", - "display_name": "Qwen3.6 Plus", + "id": "kimi-k3:fast", + "name": "Kimi K3", + "display_name": "Kimi K3", "modalities": { "input": [ "text", @@ -136902,10 +135400,10 @@ ] }, "limit": { - "context": 1000000, - "output": 65536 + "context": 1048576, + "output": 131072 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -136923,19 +135421,19 @@ } }, "attachment": true, - "open_weights": false, - "knowledge": "2025-04", - "release_date": "2026-04-02", - "last_updated": "2026-04-02", + "open_weights": true, + "release_date": "2026-07-16", + "last_updated": "2026-07-16", "type": "chat" }, { - "id": "deepseek-v4-flash-0731", - "name": "DeepSeek V4 Flash 0731", - "display_name": "DeepSeek V4 Flash 0731", + "id": "deepseek-v4.1-flash:fast", + "name": "DeepSeek V4.1 Flash", + "display_name": "DeepSeek V4.1 Flash", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" @@ -136959,54 +135457,62 @@ "visibility": "summary", "continuation": [ "thinking_blocks" - ], - "effort_options": [ - "low", - "high", - "max" ] } }, - "attachment": false, + "attachment": true, "open_weights": true, "knowledge": "2025-05", - "release_date": "2026-07-31", - "last_updated": "2026-07-31", + "release_date": "2026-09-10", + "last_updated": "2026-09-10", "type": "chat" }, { - "id": "qwen-vl-max", - "name": "Qwen-VL Max", - "display_name": "Qwen-VL Max", + "id": "glm-5.3-flash:fast", + "name": "GLM-5.3-Flash", + "display_name": "GLM-5.3-Flash", "modalities": { "input": [ "text", - "image" + "image", + "video", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 8192 + "context": 1000000, + "output": 131072 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } }, "attachment": true, - "open_weights": false, - "knowledge": "2024-04", - "release_date": "2024-04-08", - "last_updated": "2025-08-13", + "open_weights": true, + "release_date": "2026-08-26", + "last_updated": "2026-08-26", "type": "chat" }, { - "id": "qwen3-32b", - "name": "Qwen3 32B", - "display_name": "Qwen3 32B", + "id": "glm-5.3", + "name": "GLM-5.3", + "display_name": "GLM-5.3", "modalities": { "input": [ "text" @@ -137016,19 +135522,14 @@ ] }, "limit": { - "context": 131072, - "output": 16384 + "context": 1000000, + "output": 131072 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true, - "budget": { - "default": 38912, - "min": 0, - "max": 38912 - } + "default": true }, "extra_capabilities": { "reasoning": { @@ -137043,15 +135544,14 @@ }, "attachment": false, "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-04", - "last_updated": "2025-04", + "release_date": "2026-08-14", + "last_updated": "2026-08-14", "type": "chat" }, { - "id": "qwen-plus-character-ja", - "name": "Qwen Plus Character (Japanese)", - "display_name": "Qwen Plus Character (Japanese)", + "id": "glm-5.3:fast", + "name": "GLM-5.3 Fast", + "display_name": "GLM-5.3 Fast", "modalities": { "input": [ "text" @@ -137061,25 +135561,36 @@ ] }, "limit": { - "context": 8192, - "output": 512 + "context": 1000000, + "output": 131072 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } }, "attachment": false, - "open_weights": false, - "knowledge": "2024-04", - "release_date": "2024-01", - "last_updated": "2024-01", + "open_weights": true, + "release_date": "2026-08-14", + "last_updated": "2026-08-14", "type": "chat" }, { - "id": "qwen3.6-35b-a3b", - "name": "Qwen3.6 35B-A3B", - "display_name": "Qwen3.6 35B-A3B", + "id": "minimax-m3", + "name": "MiniMax-M3", + "display_name": "MiniMax-M3", "modalities": { "input": [ "text", @@ -137091,8 +135602,8 @@ ] }, "limit": { - "context": 262144, - "output": 65536 + "context": 1048576, + "output": 512000 }, "temperature": true, "tool_call": true, @@ -137113,46 +135624,64 @@ }, "attachment": true, "open_weights": true, - "release_date": "2026-04-17", - "last_updated": "2026-04-17", + "release_date": "2026-06-01", + "last_updated": "2026-06-01", "type": "chat" }, { - "id": "qwen-omni-turbo", - "name": "Qwen-Omni Turbo", - "display_name": "Qwen-Omni Turbo", + "id": "minimax-m3:fast", + "name": "MiniMax M3 Fast", + "display_name": "MiniMax M3 Fast", "modalities": { "input": [ "text", "image", - "audio", "video" ], "output": [ - "text", - "audio" + "text" ] }, "limit": { - "context": 32768, - "output": 2048 + "context": 1048576, + "output": 512000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } }, "attachment": true, - "open_weights": false, - "knowledge": "2024-04", - "release_date": "2025-01-19", - "last_updated": "2025-03-26", + "open_weights": true, + "release_date": "2026-06-01", + "last_updated": "2026-06-01", "type": "chat" - }, + } + ] + }, + "kimi-code-plan-global": { + "id": "kimi-code-plan-global", + "name": "Kimi For Coding (kimi.ai)", + "display_name": "Kimi For Coding (kimi.ai)", + "api": "https://api.kimi.ai/coding/v1", + "doc": "https://www.kimi.ai/code/docs/en/kimi-code/models.html", + "models": [ { - "id": "qwen3.8-flash", - "name": "Qwen3.8 Flash", - "display_name": "Qwen3.8 Flash", + "id": "kimi-for-coding-highspeed", + "name": "Kimi For Coding HighSpeed", + "display_name": "Kimi For Coding HighSpeed", "modalities": { "input": [ "text", @@ -137164,10 +135693,10 @@ ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 262144, + "output": 32768 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -137176,87 +135705,86 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "budget": { - "min": 0, - "max": 262144, - "unit": "tokens" - }, - "effort": "xhigh", - "effort_options": [ - "none", - "low", - "medium", - "xhigh" - ], "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ "thinking_blocks" - ], - "notes": [ - "Prefer reasoning_effort; thinking_budget is an alternative, not an additional control.", - "Omitting both controls differs from explicitly selecting xhigh, which maps to 262144 budget tokens." - ], - "budget_exclusive_with_effort": true + ] } }, "attachment": true, - "open_weights": false, - "release_date": "2026-08-26", - "last_updated": "2026-08-26", + "open_weights": true, + "knowledge": "2025-01", + "release_date": "2026-06-12", + "last_updated": "2026-06-12", "type": "chat" }, { - "id": "qwen3-omni-flash-realtime", - "name": "Qwen3-Omni Flash Realtime", - "display_name": "Qwen3-Omni Flash Realtime", + "id": "k3.1-pro", + "name": "Kimi K3.1 Pro", + "display_name": "Kimi K3.1 Pro", "modalities": { "input": [ "text", "image", - "audio", "video" ], "output": [ - "text", - "audio" + "text" ] }, "limit": { - "context": 65536, - "output": 16384 + "context": 1048576, + "output": 131072 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "effort_options": [ + "low", + "high", + "max" + ] + } }, "attachment": true, "open_weights": false, - "knowledge": "2024-04", - "release_date": "2025-09-15", - "last_updated": "2025-09-15" + "release_date": "2026-10-23", + "last_updated": "2026-10-23", + "type": "chat" }, { - "id": "qwen3.6-max-preview", - "name": "Qwen3.6 Max Preview", - "display_name": "Qwen3.6 Max Preview", + "id": "k3", + "name": "Kimi K3", + "display_name": "Kimi K3", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 65536 + "context": 1048576, + "output": 131072 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -137270,20 +135798,25 @@ "visibility": "summary", "continuation": [ "thinking_blocks" + ], + "effort_options": [ + "none", + "low", + "high", + "max" ] } }, - "attachment": false, - "open_weights": false, - "knowledge": "2025-04", - "release_date": "2026-04-20", - "last_updated": "2026-04-20", + "attachment": true, + "open_weights": true, + "release_date": "2026-07-16", + "last_updated": "2026-07-16", "type": "chat" }, { - "id": "qwen3.7-flash", - "name": "Qwen3.7 Flash", - "display_name": "Qwen3.7 Flash", + "id": "k3.1-pro-highspeed", + "name": "Kimi K3.1 Pro Highspeed", + "display_name": "Kimi K3.1 Pro Highspeed", "modalities": { "input": [ "text", @@ -137295,10 +135828,10 @@ ] }, "limit": { - "context": 1000000, + "context": 1048576, "output": 131072 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -137312,19 +135845,24 @@ "visibility": "summary", "continuation": [ "thinking_blocks" + ], + "effort_options": [ + "low", + "high", + "max" ] } }, "attachment": true, "open_weights": false, - "release_date": "2026-07-15", - "last_updated": "2026-07-15", + "release_date": "2026-10-23", + "last_updated": "2026-10-23", "type": "chat" }, { - "id": "qwen3-vl-30b-a3b", - "name": "Qwen3-VL 30B-A3B", - "display_name": "Qwen3-VL 30B-A3B", + "id": "k3-256k", + "name": "Kimi K3-256K", + "display_name": "Kimi K3-256K", "modalities": { "input": [ "text", @@ -137335,10 +135873,10 @@ ] }, "limit": { - "context": 131072, - "output": 32768 + "context": 262144, + "output": 131072 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -137352,20 +135890,25 @@ "visibility": "summary", "continuation": [ "thinking_blocks" + ], + "effort_options": [ + "none", + "low", + "high", + "max" ] } }, "attachment": true, "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-04", - "last_updated": "2025-04", + "release_date": "2026-07-16", + "last_updated": "2026-07-16", "type": "chat" }, { - "id": "qwen3.6-27b", - "name": "Qwen3.6 27B", - "display_name": "Qwen3.6 27B", + "id": "kimi-for-coding", + "name": "kimi-for-coding", + "display_name": "kimi-for-coding", "modalities": { "input": [ "text", @@ -137377,10 +135920,10 @@ ] }, "limit": { - "context": 262144, - "output": 65536 + "context": 1048576, + "output": 32768 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -137394,32 +135937,45 @@ "visibility": "summary", "continuation": [ "thinking_blocks" + ], + "effort_options": [ + "none", + "low", + "high", + "max" ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-04-22", - "last_updated": "2026-04-22", + "open_weights": false, + "release_date": "2026-09-11", + "last_updated": "2026-09-11", "type": "chat" - }, + } + ] + }, + "reflection": { + "id": "reflection", + "name": "Reflection", + "display_name": "Reflection", + "api": "https://api.reflection.ai/openai/v1", + "doc": "https://developers.reflection.ai/models", + "models": [ { - "id": "qwen3.5-397b-a17b", - "name": "Qwen3.5 397B-A17B", - "display_name": "Qwen3.5 397B-A17B", + "id": "Beam-1.0-501B-A23B", + "name": "Beam 1.0 501B A23B", + "display_name": "Beam 1.0 501B A23B", "modalities": { "input": [ - "text", - "image", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 65536 + "context": 1048576, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -137435,19 +135991,36 @@ "visibility": "summary", "continuation": [ "thinking_blocks" + ], + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" ] } }, - "attachment": true, + "attachment": false, "open_weights": true, - "release_date": "2026-02-15", - "last_updated": "2026-02-15", + "knowledge": "2026-06-30", + "release_date": "2026-10-05", + "last_updated": "2026-10-16", "type": "chat" - }, + } + ] + }, + "cloudferro-sherlock": { + "id": "cloudferro-sherlock", + "name": "CloudFerro Sherlock", + "display_name": "CloudFerro Sherlock", + "api": "https://api-sherlock.cloudferro.com/openai/v1/", + "doc": "https://docs.sherlock.cloudferro.com/", + "models": [ { - "id": "qwen3-8b", - "name": "Qwen3 8B", - "display_name": "Qwen3 8B", + "id": "openai/gpt-oss-120b", + "name": "OpenAI GPT OSS 120B", + "display_name": "OpenAI GPT OSS 120B", "modalities": { "input": [ "text" @@ -137457,42 +136030,36 @@ ] }, "limit": { - "context": 131072, - "output": 8192 + "context": 131000, + "output": 131000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true, - "budget": { - "default": 38912, - "min": 0, - "max": 38912 - } + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "low", + "medium", + "high" ] } }, "attachment": false, "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-04", - "last_updated": "2025-04", + "knowledge": "2024-06", + "release_date": "2025-08-05", + "last_updated": "2025-08-05", "type": "chat" }, { - "id": "qwen3-coder-plus", - "name": "Qwen3 Coder Plus", - "display_name": "Qwen3 Coder Plus", + "id": "speakleash/Bielik-11B-v3.0-Instruct", + "name": "Bielik 11B v3.0 Instruct", + "display_name": "Bielik 11B v3.0 Instruct", "modalities": { "input": [ "text" @@ -137502,8 +136069,8 @@ ] }, "limit": { - "context": 1000000, - "output": 65536 + "context": 32000, + "output": 32000 }, "temperature": true, "tool_call": true, @@ -137512,75 +136079,80 @@ }, "attachment": false, "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-07-23", - "last_updated": "2025-07-23", + "knowledge": "2025-03", + "release_date": "2025-03-13", + "last_updated": "2025-03-13", "type": "chat" }, { - "id": "qvq-max", - "name": "QVQ Max", - "display_name": "QVQ Max", + "id": "meta-llama/Llama-3.3-70B-Instruct", + "name": "Llama 3.3 70B Instruct", + "display_name": "Llama 3.3 70B Instruct", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 8192 + "context": 70000, + "output": 70000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true + "supported": false }, - "attachment": true, - "open_weights": false, - "knowledge": "2024-04", - "release_date": "2025-03-25", - "last_updated": "2025-03-25", + "attachment": false, + "open_weights": true, + "knowledge": "2024-10-09", + "release_date": "2024-12-06", + "last_updated": "2024-12-06", "type": "chat" }, { - "id": "qwen-omni-turbo-realtime", - "name": "Qwen-Omni Turbo Realtime", - "display_name": "Qwen-Omni Turbo Realtime", + "id": "zai-org/GLM-5.3", + "name": "GLM-5.3", + "display_name": "GLM-5.3", "modalities": { "input": [ - "text", - "image", - "audio" + "text" ], "output": [ - "text", - "audio" + "text" ] }, "limit": { - "context": 32768, - "output": 2048 + "context": 262144, + "output": 131072 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": true, - "open_weights": false, - "knowledge": "2024-04", - "release_date": "2025-05-08", - "last_updated": "2025-05-08" + "extra_capabilities": { + "reasoning": { + "supported": true, + "effort_options": [ + "low", + "high" + ] + } + }, + "attachment": false, + "open_weights": true, + "release_date": "2026-08-14", + "last_updated": "2026-08-14", + "type": "chat" }, { - "id": "qwen-max", - "name": "Qwen Max", - "display_name": "Qwen Max", + "id": "MiniMaxAI/MiniMax-M2.5", + "name": "MiniMax-M2.5", + "display_name": "MiniMax-M2.5", "modalities": { "input": [ "text" @@ -137590,44 +136162,51 @@ ] }, "limit": { - "context": 32768, - "output": 8192 + "context": 196000, + "output": 16000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false - }, - "search": { "supported": true, - "default": false, - "forced_search": false, - "search_strategy": "turbo" + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true + } }, "attachment": false, - "open_weights": false, - "knowledge": "2024-04", - "release_date": "2024-04-03", - "last_updated": "2025-01-25", + "open_weights": true, + "knowledge": "2026-01", + "release_date": "2026-02-12", + "last_updated": "2026-02-12", "type": "chat" - }, + } + ] + }, + "alibaba-coding-plan-cn": { + "id": "alibaba-coding-plan-cn", + "name": "Alibaba Coding Plan (China)", + "display_name": "Alibaba Coding Plan (China)", + "api": "https://coding.dashscope.aliyuncs.com/v1", + "doc": "https://help.aliyun.com/zh/model-studio/coding-plan", + "models": [ { - "id": "qwen3.5-27b", - "name": "Qwen3.5 27B", - "display_name": "Qwen3.5 27B", + "id": "MiniMax-M2.5", + "name": "MiniMax-M2.5", + "display_name": "MiniMax-M2.5", "modalities": { "input": [ - "text", - "image", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 65536 + "context": 196608, + "output": 24576 }, "temperature": true, "tool_call": true, @@ -137646,21 +136225,20 @@ ] } }, - "attachment": true, + "attachment": false, "open_weights": true, - "release_date": "2026-02-23", - "last_updated": "2026-02-23", + "release_date": "2026-02-12", + "last_updated": "2026-02-12", "type": "chat" }, { - "id": "qwen3.7-plus", - "name": "Qwen3.7 Plus", - "display_name": "Qwen3.7 Plus", + "id": "qwen3.5-plus", + "name": "Qwen3.5 Plus", + "display_name": "Qwen3.5 Plus", "modalities": { "input": [ "text", - "image", - "video" + "image" ], "output": [ "text" @@ -137668,7 +136246,7 @@ }, "limit": { "context": 1000000, - "output": 131072 + "output": 65536 }, "temperature": true, "tool_call": true, @@ -137690,14 +136268,14 @@ "attachment": true, "open_weights": false, "knowledge": "2025-04", - "release_date": "2026-06-02", - "last_updated": "2026-06-04", + "release_date": "2026-02-16", + "last_updated": "2026-02-16", "type": "chat" }, { - "id": "qwen3-coder-flash", - "name": "Qwen3 Coder Flash", - "display_name": "Qwen3 Coder Flash", + "id": "qwen3-coder-next", + "name": "Qwen3 Coder Next", + "display_name": "Qwen3 Coder Next", "modalities": { "input": [ "text" @@ -137707,7 +136285,7 @@ ] }, "limit": { - "context": 1000000, + "context": 262144, "output": 65536 }, "temperature": true, @@ -137716,30 +136294,25 @@ "supported": false }, "attachment": false, - "open_weights": false, - "knowledge": "2025-04", - "release_date": "2025-07-28", - "last_updated": "2025-07-28", + "open_weights": true, + "release_date": "2026-02-03", + "last_updated": "2026-02-03", "type": "chat" }, { - "id": "qwen3-omni-flash", - "name": "Qwen3-Omni Flash", - "display_name": "Qwen3-Omni Flash", + "id": "glm-4.7", + "name": "GLM-4.7", + "display_name": "GLM-4.7", "modalities": { "input": [ - "text", - "image", - "audio", - "video" + "text" ], "output": [ - "text", - "audio" + "text" ] }, "limit": { - "context": 65536, + "context": 202752, "output": 16384 }, "temperature": true, @@ -137759,17 +136332,17 @@ ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2024-04", - "release_date": "2025-09-15", - "last_updated": "2025-09-15", + "attachment": false, + "open_weights": true, + "knowledge": "2025-04", + "release_date": "2025-12-22", + "last_updated": "2025-12-22", "type": "chat" }, { - "id": "glm-5.2", - "name": "GLM-5.2", - "display_name": "GLM-5.2", + "id": "qwen3-max-2026-01-23", + "name": "Qwen3 Max", + "display_name": "Qwen3 Max", "modalities": { "input": [ "text" @@ -137779,14 +136352,13 @@ ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 262144, + "output": 32768 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true + "supported": true }, "extra_capabilities": { "reasoning": { @@ -137796,23 +136368,20 @@ "visibility": "summary", "continuation": [ "thinking_blocks" - ], - "effort_options": [ - "high", - "max" ] } }, "attachment": false, - "open_weights": true, - "release_date": "2026-06-13", - "last_updated": "2026-06-13", + "open_weights": false, + "knowledge": "2025-04", + "release_date": "2026-01-23", + "last_updated": "2026-01-23", "type": "chat" }, { - "id": "qwen-mt-plus", - "name": "Qwen Mt Plus", - "display_name": "Qwen Mt Plus", + "id": "glm-5", + "name": "GLM-5", + "display_name": "GLM-5", "modalities": { "input": [ "text" @@ -137822,28 +136391,41 @@ ] }, "limit": { - "context": 16384, - "output": 8192 + "context": 202752, + "output": 16384 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } }, "attachment": false, "open_weights": false, - "knowledge": "2024-04", - "release_date": "2025-01", - "last_updated": "2025-01", + "release_date": "2026-02-11", + "last_updated": "2026-02-11", "type": "chat" }, { - "id": "qwen3-coder-plus-2025-09-23", - "name": "Qwen3 Coder Plus 2025 09 23", - "display_name": "Qwen3 Coder Plus 2025 09 23", + "id": "qwen3.6-plus", + "name": "Qwen3.6 Plus", + "display_name": "Qwen3.6 Plus", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" @@ -137856,15 +136438,31 @@ "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } + }, + "attachment": true, + "open_weights": false, + "knowledge": "2025-04", + "release_date": "2026-04-02", + "last_updated": "2026-04-02", "type": "chat" }, { - "id": "qwen3-coder-plus-2025-07-22", - "name": "Qwen3 Coder Plus 2025 07 22", - "display_name": "Qwen3 Coder Plus 2025 07 22", + "id": "qwen3-coder-plus", + "name": "Qwen3 Coder Plus", + "display_name": "Qwen3 Coder Plus", "modalities": { "input": [ "text" @@ -137883,114 +136481,70 @@ "supported": false }, "attachment": false, + "open_weights": true, + "knowledge": "2025-04", + "release_date": "2025-07-23", + "last_updated": "2025-07-23", "type": "chat" }, { - "id": "qwen-vl-ocr-latest", - "name": "Qwen Vl Ocr Latest", - "display_name": "Qwen Vl Ocr Latest", - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 34096, - "output": 4096 - }, - "temperature": true, - "tool_call": false, - "reasoning": { - "supported": false - }, - "attachment": false, - "type": "chat" - }, - { - "id": "qvq-max-2025-05-15", - "name": "Qvq Max 2025 05 15", - "display_name": "Qvq Max 2025 05 15", + "id": "qwen3.7-plus", + "name": "Qwen3.7 Plus", + "display_name": "Qwen3.7 Plus", "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 8192 + "context": 1000000, + "output": 64000 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { "supported": true, - "default": false, - "budget": { - "default": 16384, - "min": 0, - "max": 16384 - } - }, - "attachment": false, - "type": "chat" - }, - { - "id": "qwen-long", - "name": "Qwen Long", - "display_name": "Qwen Long", - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 1000000, - "output": 8192 + "default": true }, - "temperature": true, - "tool_call": false, - "reasoning": { - "supported": false + "extra_capabilities": { + "reasoning": { + "supported": true + } }, - "attachment": false, + "attachment": true, + "open_weights": false, + "knowledge": "2025-04", + "release_date": "2026-06-02", + "last_updated": "2026-06-02", "type": "chat" }, { - "id": "qwen-turbo-latest", - "name": "Qwen Turbo Latest", - "display_name": "Qwen Turbo Latest", + "id": "kimi-k2.5", + "name": "Kimi K2.5", + "display_name": "Kimi K2.5", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 16384 + "context": 262144, + "output": 32768 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false, - "budget": { - "default": 38912, - "min": 0, - "max": 38912 - } + "default": true }, "extra_capabilities": { "reasoning": { @@ -138003,65 +136557,46 @@ ] } }, - "search": { - "supported": true, - "default": false, - "forced_search": false, - "search_strategy": "turbo" - }, - "attachment": false, - "type": "chat" - }, - { - "id": "qwen-turbo-2024-09-19", - "name": "Qwen Turbo 2024 09 19", - "display_name": "Qwen Turbo 2024 09 19", - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 8192 - }, - "temperature": true, - "tool_call": true, - "reasoning": { - "supported": false - }, - "attachment": false, + "attachment": true, + "open_weights": true, + "knowledge": "2025-01", + "release_date": "2026-01-27", + "last_updated": "2026-01-27", "type": "chat" - }, + } + ] + }, + "xiaomi-token-plan-sgp": { + "id": "xiaomi-token-plan-sgp", + "name": "Xiaomi Token Plan (Singapore)", + "display_name": "Xiaomi Token Plan (Singapore)", + "api": "https://token-plan-sgp.xiaomimimo.com/v1", + "doc": "https://platform.xiaomimimo.com/#/docs", + "models": [ { - "id": "qwen-flash-2025-07-28", - "name": "Qwen Flash 2025 07 28", - "display_name": "Qwen Flash 2025 07 28", + "id": "mimo-v2.6-pro", + "name": "MiMo-V2.6-Pro", + "display_name": "MiMo-V2.6-Pro", "modalities": { "input": [ - "text" + "text", + "image", + "audio", + "video" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 32768 + "context": 1048576, + "output": 131072 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false, - "budget": { - "default": 81920, - "min": 0, - "max": 81920 - } + "default": true }, "extra_capabilities": { "reasoning": { @@ -138074,41 +136609,36 @@ ] } }, - "search": { - "supported": true, - "default": false, - "forced_search": false, - "search_strategy": "turbo" - }, - "attachment": false, + "attachment": true, + "open_weights": true, + "release_date": "2026-09-22", + "last_updated": "2026-09-22", "type": "chat" }, { - "id": "qwen-plus-latest", - "name": "Qwen Plus Latest", - "display_name": "Qwen Plus Latest", + "id": "mimo-v2.6-flash", + "name": "MiMo-V2.6-Flash", + "display_name": "MiMo-V2.6-Flash", "modalities": { "input": [ - "text" + "text", + "image", + "audio", + "video" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 32768 + "context": 1048576, + "output": 131072 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false, - "budget": { - "default": 81920, - "min": 0, - "max": 81920 - } + "default": true }, "extra_capabilities": { "reasoning": { @@ -138121,112 +136651,88 @@ ] } }, - "search": { - "supported": true, - "default": false, - "forced_search": false, - "search_strategy": "turbo" - }, - "attachment": false, + "attachment": true, + "open_weights": true, + "release_date": "2026-09-22", + "last_updated": "2026-09-22", "type": "chat" }, { - "id": "qwen-plus-2024-09-19", - "name": "Qwen Plus 2024 09 19", - "display_name": "Qwen Plus 2024 09 19", + "id": "mimo-v2.5-tts", + "name": "MiMo-V2.5-TTS", + "display_name": "MiMo-V2.5-TTS", "modalities": { "input": [ "text" ], "output": [ - "text" + "audio" ] }, "limit": { - "context": 131072, + "context": 8192, "output": 8192 }, - "temperature": true, - "tool_call": true, + "temperature": false, + "tool_call": false, "reasoning": { "supported": false }, "attachment": false, - "type": "chat" + "open_weights": false, + "release_date": "2026-04-22", + "last_updated": "2026-04-22" }, { - "id": "qwen-plus-2025-07-14", - "name": "Qwen Plus 2025 07 14", - "display_name": "Qwen Plus 2025 07 14", + "id": "mimo-v2.5-tts-voiceclone", + "name": "MiMo-V2.5-TTS-VoiceClone", + "display_name": "MiMo-V2.5-TTS-VoiceClone", "modalities": { "input": [ "text" ], "output": [ - "text" + "audio" ] }, "limit": { - "context": 131072, - "output": 16384 + "context": 8192, + "output": 8192 }, - "temperature": true, - "tool_call": true, + "temperature": false, + "tool_call": false, "reasoning": { - "supported": true, - "default": false, - "budget": { - "default": 38912, - "min": 0, - "max": 38912 - } - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } - }, - "search": { - "supported": true, - "default": false, - "forced_search": false, - "search_strategy": "turbo" + "supported": false }, "attachment": false, - "type": "chat" + "open_weights": false, + "release_date": "2026-04-22", + "last_updated": "2026-04-22" }, { - "id": "qwen-plus-2025-09-11", - "name": "Qwen Plus 2025 09 11", - "display_name": "Qwen Plus 2025 09 11", + "id": "mimo-v2.5", + "name": "MiMo-V2.5", + "display_name": "MiMo-V2.5", "modalities": { "input": [ - "text" + "text", + "image", + "audio", + "video" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 32768 + "context": 1048576, + "output": 131072 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false, - "budget": { - "default": 81920, - "min": 0, - "max": 81920 - } + "default": true }, "extra_capabilities": { "reasoning": { @@ -138239,49 +136745,43 @@ ] } }, - "search": { - "supported": true, - "default": false, - "forced_search": false, - "search_strategy": "turbo" - }, - "attachment": false, + "attachment": true, + "open_weights": true, + "knowledge": "2024-12", + "release_date": "2026-04-22", + "last_updated": "2026-04-22", "type": "chat" }, { - "id": "qwen-max-latest", - "name": "Qwen Max Latest", - "display_name": "Qwen Max Latest", + "id": "mimo-v2.5-tts-voicedesign", + "name": "MiMo-V2.5-TTS-VoiceDesign", + "display_name": "MiMo-V2.5-TTS-VoiceDesign", "modalities": { "input": [ "text" ], "output": [ - "text" + "audio" ] }, "limit": { - "context": 131072, + "context": 8192, "output": 8192 }, - "temperature": true, - "tool_call": true, + "temperature": false, + "tool_call": false, "reasoning": { "supported": false }, - "search": { - "supported": true, - "default": false, - "forced_search": false, - "search_strategy": "turbo" - }, "attachment": false, - "type": "chat" + "open_weights": false, + "release_date": "2026-04-22", + "last_updated": "2026-04-22" }, { - "id": "qwen-max-2024-09-19", - "name": "Qwen Max 2024 09 19", - "display_name": "Qwen Max 2024 09 19", + "id": "mimo-v2.5-pro", + "name": "MiMo-V2.5-Pro", + "display_name": "MiMo-V2.5-Pro", "modalities": { "input": [ "text" @@ -138291,51 +136791,89 @@ ] }, "limit": { - "context": 32768, - "output": 8192 + "context": 1048576, + "output": 131072 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false - }, - "search": { "supported": true, - "default": false, - "forced_search": false, - "search_strategy": "turbo" + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } }, "attachment": false, + "open_weights": true, + "knowledge": "2024-12", + "release_date": "2026-04-22", + "last_updated": "2026-04-22", "type": "chat" - }, + } + ] + }, + "neon": { + "id": "neon", + "name": "Neon", + "display_name": "Neon", + "api": "${NEON_AI_GATEWAY_BASE_URL}/v1", + "doc": "https://neon.com/docs", + "models": [ { - "id": "qwen-max-2024-04-28", - "name": "Qwen Max 2024 04 28", - "display_name": "Qwen Max 2024 04 28", + "id": "kimi-k3", + "name": "Kimi K3", + "display_name": "Kimi K3", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 8000, - "output": 2000 + "context": 1048576, + "output": 65536 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, + "extra_capabilities": { + "reasoning": { + "supported": true, + "effort_options": [ + "none", + "minimal", + "low", + "medium", + "high", + "max" + ] + } + }, + "attachment": true, + "open_weights": true, + "release_date": "2026-07-16", + "last_updated": "2026-07-16", "type": "chat" }, { - "id": "qwen-max-2024-04-03", - "name": "Qwen Max 2024 04 03", - "display_name": "Qwen Max 2024 04 03", + "id": "qwen35-122b-a10b", + "name": "Qwen3.5 122B-A10B", + "display_name": "Qwen3.5 122B-A10B", "modalities": { "input": [ "text" @@ -138345,187 +136883,258 @@ ] }, "limit": { - "context": 8000, - "output": 2000 + "context": 262144, + "output": 25000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "none", + "low", + "medium", + "high" + ] + } }, "attachment": false, + "open_weights": true, + "release_date": "2026-02-23", + "last_updated": "2026-02-23", "type": "chat" }, { - "id": "qwen-max-2025-01-25", - "name": "Qwen Max 2025 01 25", - "display_name": "Qwen Max 2025 01 25", + "id": "gpt-5-6-luna", + "name": "GPT-5.6 Luna", + "display_name": "GPT-5.6 Luna", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 8192 + "context": 1050000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { - "supported": false - }, - "search": { "supported": true, - "default": false, - "forced_search": false, - "search_strategy": "turbo" + "default": true }, - "attachment": false, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", + "effort_options": [ + "none", + "low", + "medium", + "high", + "xhigh", + "max" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" + } + }, + "attachment": true, + "open_weights": false, + "knowledge": "2026-02-16", + "release_date": "2026-07-09", + "last_updated": "2026-07-09", "type": "chat" }, { - "id": "qwen3-max-2025-09-23", - "name": "Qwen3 Max 20250923", - "display_name": "Qwen3 Max 20250923", + "id": "gpt-5-4", + "name": "GPT-5.4", + "display_name": "GPT-5.4", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 65536 + "context": 1050000, + "output": 128000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false - }, - "search": { "supported": true, - "default": false, - "forced_search": false, - "search_strategy": "turbo" + "default": false }, - "attachment": false, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": false, + "mode": "effort", + "effort": "none", + "effort_options": [ + "none", + "low", + "medium", + "high", + "xhigh" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" + } + }, + "attachment": true, + "open_weights": false, + "knowledge": "2025-08-31", + "release_date": "2026-03-05", + "last_updated": "2026-03-05", "type": "chat" }, { - "id": "qwen3-max-preview", - "name": "Qwen3 Max Preview", - "display_name": "Qwen3 Max Preview", + "id": "gpt-6-sol", + "name": "GPT-6 Sol", + "display_name": "GPT-6 Sol", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 65536 + "context": 1050000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { - "supported": true + "supported": true, + "default": true }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "none", + "low", + "medium", + "high", + "xhigh" ] } }, - "search": { - "supported": true, - "default": false, - "forced_search": false, - "search_strategy": "turbo" - }, - "attachment": false, + "attachment": true, + "open_weights": false, + "knowledge": "2026-04-20", + "release_date": "2026-09-22", + "last_updated": "2026-09-22", "type": "chat" }, { - "id": "qwen3-235b-a22b-thinking-2507", - "name": "Qwen3 235B A22B Thinking 2507", - "display_name": "Qwen3 235B A22B Thinking 2507", + "id": "gpt-6-astra", + "name": "GPT-6 Astra", + "display_name": "GPT-6 Astra", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 32768 + "context": 1050000, + "output": 128000 }, - "temperature": true, - "tool_call": false, + "temperature": false, + "tool_call": true, "reasoning": { "supported": true, - "default": true, - "budget": { - "default": 81920, - "min": 0, - "max": 81920 - } + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" ] } }, - "attachment": false, + "attachment": true, + "open_weights": false, + "knowledge": "2026-04-30", + "release_date": "2026-09-04", + "last_updated": "2026-09-04", "type": "chat" }, { - "id": "qwen3-235b-a22b-instruct-2507", - "name": "Qwen3 235B A22B Instruct 2507", - "display_name": "Qwen3 235B A22B Instruct 2507", + "id": "claude-haiku-5-5", + "name": "Claude Haiku 5.5", + "display_name": "Claude Haiku 5.5", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 32768 + "context": 1000000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, + "attachment": true, + "open_weights": false, + "knowledge": "2026-06", + "release_date": "2026-10-07", + "last_updated": "2026-10-07", "type": "chat" }, { - "id": "qwen3-30b-a3b-instruct-2507", - "name": "Qwen3 30B A3B Instruct 2507", - "display_name": "Qwen3 30B A3B Instruct 2507", + "id": "glm-5-2", + "name": "GLM-5.2", + "display_name": "GLM-5.2", "modalities": { "input": [ "text" @@ -138535,62 +137144,89 @@ ] }, "limit": { - "context": 131072, - "output": 32768 + "context": 1000000, + "output": 65536 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "none", + "minimal", + "low", + "medium", + "high", + "max" + ] + } }, "attachment": false, + "open_weights": true, + "release_date": "2026-06-13", + "last_updated": "2026-06-13", "type": "chat" }, { - "id": "qwen3-30b-a3b-thinking-2507", - "name": "Qwen3 30B A3B Thinking 2507", - "display_name": "Qwen3 30B A3B Thinking 2507", + "id": "gpt-5-4-mini", + "name": "GPT-5.4 mini", + "display_name": "GPT-5.4 mini", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 32768 + "context": 400000, + "output": 128000 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { "supported": true, - "default": true, - "budget": { - "default": 81920, - "min": 0, - "max": 81920 - } + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] + "default_enabled": false, + "mode": "effort", + "effort": "none", + "effort_options": [ + "none", + "low", + "medium", + "high", + "xhigh" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, - "attachment": false, + "attachment": true, + "open_weights": false, + "knowledge": "2025-08-31", + "release_date": "2026-03-17", + "last_updated": "2026-03-17", "type": "chat" }, { - "id": "qwen3-30b-a3b", - "name": "Qwen3 30B A3B", - "display_name": "Qwen3 30B A3B", + "id": "qwen3-next-80b-a3b-instruct", + "name": "Qwen3-Next 80B-A3B Instruct", + "display_name": "Qwen3-Next 80B-A3B Instruct", "modalities": { "input": [ "text" @@ -138601,205 +137237,233 @@ }, "limit": { "context": 131072, - "output": 32768 + "output": 10000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": false, - "budget": { - "default": 81920, - "min": 0, - "max": 81920 - } - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } + "supported": false }, "attachment": false, + "open_weights": true, + "knowledge": "2025-04", + "release_date": "2025-09", + "last_updated": "2025-09", "type": "chat" }, { - "id": "qwen3-4b", - "name": "Qwen3 4B", - "display_name": "Qwen3 4B", + "id": "gpt-5-6-sol", + "name": "GPT-5.6 Sol", + "display_name": "GPT-5.6 Sol", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 8192 + "context": 1050000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": true, - "budget": { - "default": 38912, - "min": 0, - "max": 38912 - } + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] + "default_enabled": true, + "mode": "effort", + "effort": "medium", + "effort_options": [ + "none", + "low", + "medium", + "high", + "xhigh", + "max" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, - "attachment": false, + "attachment": true, + "open_weights": false, + "knowledge": "2026-02-16", + "release_date": "2026-07-09", + "last_updated": "2026-07-09", "type": "chat" }, { - "id": "qwen3-1.7b", - "name": "Qwen3 1.7B", - "display_name": "Qwen3 1.7B", + "id": "gemini-4-argon", + "name": "Gemini 4 Argon", + "display_name": "Gemini 4 Argon", "modalities": { "input": [ - "text" + "text", + "image", + "video", + "audio", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 32768, - "output": 8192 + "context": 1048576, + "output": 65536 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": true, - "budget": { - "default": 30720, - "min": 0, - "max": 30720 - } + "default": true }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "low", + "medium", + "high" ] } }, - "attachment": false, + "attachment": true, + "open_weights": false, + "knowledge": "2026-03", + "release_date": "2026-10-13", + "last_updated": "2026-10-13", "type": "chat" }, { - "id": "qwen3-0.6b", - "name": "Qwen3 0.6B", - "display_name": "Qwen3 0.6B", + "id": "claude-opus-4-7", + "name": "Claude Opus 4.7", + "display_name": "Claude Opus 4.7", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 32768, - "output": 8192 + "context": 1000000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": true, - "budget": { - "default": 30720, - "min": 0, - "max": 30720 - } + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": false, + "mode": "effort", + "effort": "high", + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" + ], "interleaved": true, "summaries": true, - "visibility": "summary", + "visibility": "omitted", "continuation": [ "thinking_blocks" + ], + "notes": [ + "Claude Opus 4.7 and newer Opus models require thinking.type = \"adaptive\" to enable thinking explicitly.", + "Manual budget_tokens requests return 400 on Claude Opus 4.7 and newer adaptive-only Opus models.", + "task_budget is separate from thinking control and should not be treated as a thinking budget." ] } }, - "attachment": false, + "attachment": true, + "open_weights": false, + "knowledge": "2026-01-31", + "release_date": "2026-04-16", + "last_updated": "2026-04-16", "type": "chat" }, { - "id": "qwen3-vl-plus-2025-09-23", - "name": "Qwen3 VL Plus 2025 09 23", - "display_name": "Qwen3 VL Plus 2025 09 23", + "id": "claude-haiku-4-5", + "name": "Claude Haiku 4.5 (latest)", + "display_name": "Claude Haiku 4.5 (latest)", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 32768 + "context": 200000, + "output": 64000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false, - "budget": { - "default": 81920, - "min": 0, - "max": 81920 - } + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": false, + "mode": "budget", + "budget": { + "min": 1024, + "unit": "tokens" + }, "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ "thinking_blocks" + ], + "notes": [ + "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", + "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." ] } }, - "attachment": false, + "attachment": true, + "open_weights": false, + "knowledge": "2025-02-28", + "release_date": "2025-10-15", + "last_updated": "2025-10-15", "type": "chat" }, { - "id": "qwq-plus-latest", - "name": "QwQ Plus Latest", - "display_name": "QwQ Plus Latest", + "id": "gemma-3-12b", + "name": "Gemma 3 12B", + "display_name": "Gemma 3 12B", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" @@ -138810,59 +137474,36 @@ "output": 8192 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": true, - "default": true, - "budget": { - "default": 32768, - "min": 0, - "max": 32768 - } - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } - }, - "search": { "supported": false }, - "attachment": false, + "attachment": true, + "open_weights": true, + "knowledge": "2024-08-31", + "release_date": "2025-03-12", + "last_updated": "2025-03-12", "type": "chat" - } - ] - }, - "ambient": { - "id": "ambient", - "name": "Ambient", - "display_name": "Ambient", - "api": "https://api.ambient.xyz/v1", - "doc": "https://ambient.xyz", - "models": [ + }, { - "id": "zai-org/GLM-5.2-FP8", - "name": "GLM-5.2", - "display_name": "GLM-5.2", + "id": "claude-sonnet-5", + "name": "Claude Sonnet 5", + "display_name": "Claude Sonnet 5", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 202752, - "output": 202752 + "context": 1000000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -138870,33 +137511,42 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, "effort_options": [ + "low", + "medium", "high", + "xhigh", "max" ] } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-06-13", - "last_updated": "2026-06-13", + "attachment": true, + "open_weights": false, + "knowledge": "2026-01-31", + "release_date": "2026-06-30", + "last_updated": "2026-06-30", "type": "chat" }, { - "id": "minimax/minimax-m3", - "name": "MiniMax-M3", - "display_name": "MiniMax-M3", + "id": "gemini-3-1-flash-lite", + "name": "Gemini 3.1 Flash Lite Preview", + "display_name": "Gemini 3.1 Flash Lite Preview", "modalities": { "input": [ - "text" + "text", + "image", + "video", + "audio", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 131702 + "context": 1048576, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -138906,19 +137556,24 @@ }, "extra_capabilities": { "reasoning": { - "supported": true + "effort_options": [ + "low", + "medium", + "high" + ] } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-06-01", - "last_updated": "2026-06-01", + "attachment": true, + "open_weights": false, + "knowledge": "2025-01", + "release_date": "2026-03-03", + "last_updated": "2026-03-03", "type": "chat" }, { - "id": "ambient/small", - "name": "Ambient Small", - "display_name": "Ambient Small", + "id": "meta-llama-3-1-8b-instruct", + "name": "Llama 3.1 8B Instruct", + "display_name": "Llama 3.1 8B Instruct", "modalities": { "input": [ "text" @@ -138928,8 +137583,8 @@ ] }, "limit": { - "context": 101072, - "output": 101072 + "context": 131072, + "output": 8192 }, "temperature": true, "tool_call": true, @@ -138938,25 +137593,27 @@ }, "attachment": false, "open_weights": true, - "release_date": "2026-10-13", - "last_updated": "2026-10-13", + "knowledge": "2023-12-31", + "release_date": "2024-07-23", + "last_updated": "2024-07-23", "type": "chat" }, { - "id": "ambient/large", - "name": "Ambient Large", - "display_name": "Ambient Large", + "id": "glm-5-3-flash", + "name": "GLM-5.3 Flash", + "display_name": "GLM-5.3 Flash", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 202752, - "output": 202752 + "context": 1048576, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -138967,34 +137624,37 @@ "extra_capabilities": { "reasoning": { "effort_options": [ + "low", "high", "max" ] } }, - "attachment": false, + "attachment": true, "open_weights": true, - "release_date": "2026-06-13", - "last_updated": "2026-06-13", + "release_date": "2026-08-26", + "last_updated": "2026-08-26", "type": "chat" }, { - "id": "z-ai/glm-5.2", - "name": "GLM-5.2", - "display_name": "GLM-5.2", + "id": "claude-fable-5", + "name": "Claude Fable 5", + "display_name": "Claude Fable 5", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 202752, - "output": 202752 + "context": 1000000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -139003,22 +137663,40 @@ "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "high", "effort_options": [ + "low", + "medium", "high", + "xhigh", "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "omitted", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Adaptive thinking is always on for Claude Fable 5 and Claude Mythos 5; thinking.type = \"disabled\" is rejected.", + "Manual budget_tokens requests return 400 on Claude Fable 5 and Claude Mythos 5.", + "thinking.display defaults to omitted; set display to summarized to receive readable thinking summaries." ] } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-06-13", - "last_updated": "2026-06-13", + "attachment": true, + "open_weights": false, + "knowledge": "2026-01-31", + "release_date": "2026-06-09", + "last_updated": "2026-06-09", "type": "chat" }, { - "id": "qwen/qwen3-30b-a3b-instruct-2507", - "name": "Qwen3 30B-A3B Instruct 2507", - "display_name": "Qwen3 30B-A3B Instruct 2507", + "id": "grok-4-7", + "name": "Grok 4.7", + "display_name": "Grok 4.7", "modalities": { "input": [ "text" @@ -139028,24 +137706,36 @@ ] }, "limit": { - "context": 131072, - "output": 32768 + "context": 500000, + "output": 500000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "low", + "medium", + "high", + "xhigh" + ] + } }, "attachment": false, - "open_weights": true, - "release_date": "2025-07-28", - "last_updated": "2025-07-28", + "open_weights": false, + "knowledge": "2026-05", + "release_date": "2026-09-21", + "last_updated": "2026-09-21", "type": "chat" }, { - "id": "qwen/qwen3.8-27b", - "name": "Qwen3.8 27B", - "display_name": "Qwen3.8 27B", + "id": "gpt-5-nano", + "name": "GPT-5 Nano", + "display_name": "GPT-5 Nano", "modalities": { "input": [ "text", @@ -139056,10 +137746,10 @@ ] }, "limit": { - "context": 32768, - "output": 8192 + "context": 400000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -139067,153 +137757,259 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", "effort_options": [ + "minimal", "low", "medium", - "xhigh" - ] + "high" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, "attachment": true, - "open_weights": true, - "release_date": "2026-08-14", - "last_updated": "2026-08-14", + "open_weights": false, + "knowledge": "2024-05-30", + "release_date": "2025-08-07", + "last_updated": "2025-08-07", "type": "chat" }, { - "id": "qwen/qwen3.6-35b-a3b", - "name": "Qwen3.6 35B-A3B", - "display_name": "Qwen3.6 35B-A3B", + "id": "claude-opus-4-1", + "name": "Claude Opus 4.1 (latest)", + "display_name": "Claude Opus 4.1 (latest)", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 200000, + "output": 32000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": false, + "mode": "budget", + "budget": { + "min": 1024, + "unit": "tokens" + }, "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ "thinking_blocks" + ], + "notes": [ + "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", + "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." ] } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-04-17", - "last_updated": "2026-04-17", + "attachment": true, + "open_weights": false, + "knowledge": "2025-03-31", + "release_date": "2025-08-05", + "last_updated": "2025-08-05", "type": "chat" }, { - "id": "qwen/qwen3.6-27b", - "name": "Qwen3.6 27B", - "display_name": "Qwen3.6 27B", + "id": "claude-sonnet-4-5", + "name": "Claude Sonnet 4.5 (latest)", + "display_name": "Claude Sonnet 4.5 (latest)", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 32768, - "output": 8192 + "context": 200000, + "output": 64000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": false, + "mode": "budget", + "budget": { + "min": 1024, + "unit": "tokens" + }, "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ "thinking_blocks" + ], + "notes": [ + "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", + "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." ] } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-04-22", - "last_updated": "2026-04-22", + "attachment": true, + "open_weights": false, + "knowledge": "2025-07-31", + "release_date": "2025-09-29", + "last_updated": "2025-09-29", "type": "chat" }, { - "id": "unsloth/qwen3.8-27b-gguf", - "name": "qwen3.8-27b-4q", - "display_name": "qwen3.8-27b-4q", + "id": "gpt-5-4-nano", + "name": "GPT-5.4 nano", + "display_name": "GPT-5.4 nano", "modalities": { "input": [ + "text", + "image" + ], + "output": [ "text" + ] + }, + "limit": { + "context": 400000, + "output": 128000 + }, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": true, + "default": false + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": false, + "mode": "effort", + "effort": "none", + "effort_options": [ + "none", + "low", + "medium", + "high", + "xhigh" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" + } + }, + "attachment": true, + "open_weights": false, + "knowledge": "2025-08-31", + "release_date": "2026-03-17", + "last_updated": "2026-03-17", + "type": "chat" + }, + { + "id": "gemini-3-flash", + "name": "Gemini 3 Flash Preview", + "display_name": "Gemini 3 Flash Preview", + "modalities": { + "input": [ + "text", + "image", + "video", + "audio", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 101072, - "output": 101072 + "context": 1048576, + "output": 65536 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "level", + "level": "high", + "level_options": [ + "minimal", + "low", + "medium", + "high" + ], + "summaries": true, + "visibility": "summary", + "continuation": [ + "thought_signatures" + ] + } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-10-13", - "last_updated": "2026-10-13", + "attachment": true, + "open_weights": false, + "knowledge": "2025-01", + "release_date": "2025-12-17", + "last_updated": "2025-12-17", "type": "chat" - } - ] - }, - "vultr": { - "id": "vultr", - "name": "Vultr", - "display_name": "Vultr", - "api": "https://api.vultrinference.com/v1", - "doc": "https://api.vultrinference.com/", - "models": [ + }, { - "id": "nemotron-3-nano-omni-30b-a3b-reasoning", - "name": "Nemotron 3 Nano Omni 30B A3B Reasoning", - "display_name": "Nemotron 3 Nano Omni 30B A3B Reasoning", + "id": "claude-opus-5-5", + "name": "Claude Opus 5.5", + "display_name": "Claude Opus 5.5", "modalities": { "input": [ "text", "image", - "video", - "audio" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 65536 + "context": 1000000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -139221,74 +138017,98 @@ }, "extra_capabilities": { "reasoning": { - "supported": true + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" + ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-04-28", - "last_updated": "2026-04-28", + "open_weights": false, + "knowledge": "2026-06", + "release_date": "2026-09-22", + "last_updated": "2026-09-22", "type": "chat" }, { - "id": "glm-5.3-flash", - "name": "GLM-5.3-Flash", - "display_name": "GLM-5.3-Flash", + "id": "claude-opus-4-5", + "name": "Claude Opus 4.5 (latest)", + "display_name": "Claude Opus 4.5 (latest)", "modalities": { "input": [ "text", "image", - "video" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 200000, + "output": 64000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": false, + "mode": "mixed", + "budget": { + "min": 1024, + "unit": "tokens" + }, + "effort": "high", "effort_options": [ "low", - "high", - "max" + "medium", + "high" + ], + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Claude Opus 4.5 uses manual thinking.type = \"enabled\" with budget_tokens; effort can be used alongside the thinking budget.", + "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header." ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-08-26", - "last_updated": "2026-08-26", + "open_weights": false, + "knowledge": "2025-05", + "release_date": "2025-11-24", + "last_updated": "2025-11-24", "type": "chat" }, { - "id": "qwen3.8-27b", - "name": "Qwen3.8 27B", - "display_name": "Qwen3.8 27B", + "id": "gpt-5", + "name": "GPT-5", + "display_name": "GPT-5", "modalities": { "input": [ "text", - "image", - "video" + "image" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 32768 + "context": 400000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -139296,69 +138116,102 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", "effort_options": [ + "minimal", "low", "medium", - "xhigh" - ] + "high" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, "attachment": true, - "open_weights": true, - "release_date": "2026-08-14", - "last_updated": "2026-08-14", + "open_weights": false, + "knowledge": "2024-09-30", + "release_date": "2025-08-07", + "last_updated": "2025-08-07", "type": "chat" }, { - "id": "mimo-v2.6-pro-rl", - "name": "MiMo-V2.6-Pro", - "display_name": "MiMo-V2.6-Pro", + "id": "gpt-5-5", + "name": "GPT-5.5", + "display_name": "GPT-5.5", "modalities": { "input": [ "text", "image", - "audio", - "video" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 1050000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, "default": true }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", + "effort_options": [ + "low", + "medium", + "high", + "xhigh" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" + } + }, "attachment": true, - "open_weights": true, - "release_date": "2026-09-22", - "last_updated": "2026-09-22", + "open_weights": false, + "knowledge": "2025-12-01", + "release_date": "2026-04-23", + "last_updated": "2026-04-23", "type": "chat" }, { - "id": "minimax-m3", - "name": "MiniMax-M3", - "display_name": "MiniMax-M3", + "id": "gpt-5-mini", + "name": "GPT-5 Mini", + "display_name": "GPT-5 Mini", "modalities": { "input": [ "text", - "image", - "video" + "image" ], "output": [ "text" ] }, "limit": { - "context": 524288, - "output": 512000 + "context": 400000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -139366,25 +138219,43 @@ }, "extra_capabilities": { "reasoning": { - "supported": true + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", + "effort_options": [ + "minimal", + "low", + "medium", + "high" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, "attachment": true, - "open_weights": true, - "release_date": "2026-06-01", - "last_updated": "2026-06-01", + "open_weights": false, + "knowledge": "2024-05-30", + "release_date": "2025-08-07", + "last_updated": "2025-08-07", "type": "chat" }, { - "id": "mimo-v2.6-flash-rl", - "name": "MiMo-V2.6-Flash", - "display_name": "MiMo-V2.6-Flash", + "id": "gemini-3-5-flash-lite", + "name": "Gemini 3.5 Flash Lite", + "display_name": "Gemini 3.5 Flash Lite", "modalities": { "input": [ "text", "image", + "video", "audio", - "video" + "pdf" ], "output": [ "text" @@ -139392,7 +138263,7 @@ }, "limit": { "context": 1048576, - "output": 131072 + "output": 65536 }, "temperature": true, "tool_call": true, @@ -139400,30 +138271,42 @@ "supported": true, "default": true }, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "minimal", + "low", + "medium", + "high" + ] + } + }, "attachment": true, - "open_weights": true, - "release_date": "2026-09-22", - "last_updated": "2026-09-22", + "open_weights": false, + "knowledge": "2026-03", + "release_date": "2026-07-21", + "last_updated": "2026-07-21", "type": "chat" }, { - "id": "deepseek-v4.1-flash", - "name": "DeepSeek V4.1 Flash", - "display_name": "DeepSeek V4.1 Flash", + "id": "gpt-5-5-pro", + "name": "GPT-5.5 Pro", + "display_name": "GPT-5.5 Pro", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 384000 + "context": 1050000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -139431,25 +138314,24 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, "effort_options": [ - "low", + "medium", "high", - "max" + "xhigh" ] } }, "attachment": true, - "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-09-10", - "last_updated": "2026-09-10", + "open_weights": false, + "knowledge": "2025-12-01", + "release_date": "2026-04-23", + "last_updated": "2026-04-23", "type": "chat" }, { - "id": "nemotron-3.5-content-safety", - "name": "Nemotron 3.5 Content Safety", - "display_name": "Nemotron 3.5 Content Safety", + "id": "gpt-5-1", + "name": "GPT-5.1", + "display_name": "GPT-5.1", "modalities": { "input": [ "text", @@ -139460,30 +138342,47 @@ ] }, "limit": { - "context": 131072, - "output": 8192 + "context": 400000, + "output": 128000 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { - "supported": true + "supported": true, + "default_enabled": false, + "mode": "effort", + "effort": "none", + "effort_options": [ + "none", + "low", + "medium", + "high" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, "attachment": true, - "open_weights": true, - "release_date": "2026-06-02", - "last_updated": "2026-06-02", + "open_weights": false, + "knowledge": "2024-09-30", + "release_date": "2025-11-13", + "last_updated": "2025-11-13", "type": "chat" }, { - "id": "deepseek-v4-flash-0731", - "name": "DeepSeek V4 Flash 0731", - "display_name": "DeepSeek V4 Flash 0731", + "id": "gpt-oss-120b", + "name": "GPT OSS 120B", + "display_name": "GPT OSS 120B", "modalities": { "input": [ "text" @@ -139493,8 +138392,8 @@ ] }, "limit": { - "context": 1048576, - "output": 384000 + "context": 131072, + "output": 25000 }, "temperature": true, "tool_call": true, @@ -139507,35 +138406,37 @@ "supported": true, "effort_options": [ "low", - "high", - "max" + "medium", + "high" ] } }, "attachment": false, "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-07-31", - "last_updated": "2026-07-31", + "knowledge": "2024-06", + "release_date": "2025-08-05", + "last_updated": "2025-08-05", "type": "chat" }, { - "id": "laguna-s-2.1", - "name": "Laguna S 2.1", - "display_name": "Laguna S 2.1", + "id": "claude-sonnet-5-5", + "name": "Claude Sonnet 5.5", + "display_name": "Claude Sonnet 5.5", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 32768 + "context": 1000000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -139543,56 +138444,83 @@ }, "extra_capabilities": { "reasoning": { - "supported": true + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" + ] } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-07-21", - "last_updated": "2026-07-21", + "attachment": true, + "open_weights": false, + "knowledge": "2026-06", + "release_date": "2026-09-28", + "last_updated": "2026-09-28", "type": "chat" }, { - "id": "qwen3.8-flash-next", - "name": "Qwen3.8 Flash Next", - "display_name": "Qwen3.8 Flash Next", + "id": "claude-sonnet-4-6", + "name": "Claude Sonnet 4.6", + "display_name": "Claude Sonnet 4.6", "modalities": { "input": [ "text", "image", - "video" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 131072 + "context": 1000000, + "output": 64000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": false, + "mode": "mixed", + "budget": { + "min": 1024, + "unit": "tokens" + }, + "effort": "high", "effort_options": [ "low", "medium", - "xhigh" + "high", + "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Anthropic recommends adaptive thinking with effort for Claude 4.6; budget_tokens remains a deprecated compatibility path.", + "Anthropic API defaults effort to high; lower effort levels should be chosen per workload." ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-08-27", - "last_updated": "2026-08-27", + "open_weights": false, + "knowledge": "2025-08-31", + "release_date": "2026-02-17", + "last_updated": "2026-03-13", "type": "chat" }, { - "id": "glm-5.3", + "id": "glm-5-3", "name": "GLM-5.3", "display_name": "GLM-5.3", "modalities": { @@ -139615,7 +138543,6 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, "effort_options": [ "low", "high", @@ -139630,89 +138557,149 @@ "type": "chat" }, { - "id": "glm-5.x-menthol", - "name": "GLM-5", - "display_name": "GLM-5", + "id": "gemini-3-7-flash", + "name": "Gemini 3.7 Flash", + "display_name": "Gemini 3.7 Flash", "modalities": { "input": [ - "text" + "text", + "image", + "video", + "audio", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 202752, - "output": 131072 + "context": 1048576, + "output": 65536 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, "default": true }, - "attachment": false, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "low", + "medium", + "high" + ] + } + }, + "attachment": true, + "open_weights": false, + "knowledge": "2026-03", + "release_date": "2026-08-13", + "last_updated": "2026-08-13", + "type": "chat" + }, + { + "id": "llama-4-maverick", + "name": "Llama 4 Maverick 17B Instruct", + "display_name": "Llama 4 Maverick 17B Instruct", + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1000000, + "output": 8192 + }, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": false + }, + "attachment": true, "open_weights": true, - "release_date": "2026-02-12", - "last_updated": "2026-02-12", + "knowledge": "2024-08", + "release_date": "2025-04-05", + "last_updated": "2025-04-05", "type": "chat" }, { - "id": "muse-glimmer-30b", - "name": "Muse Glimmer 30B", - "display_name": "Muse Glimmer 30B", + "id": "claude-opus-4-8", + "name": "Claude Opus 4.8", + "display_name": "Claude Opus 4.8", "modalities": { "input": [ "text", "image", - "video" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 131072 + "context": 1000000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": false, + "mode": "effort", + "effort": "high", "effort_options": [ "low", "medium", "high", - "xhigh" + "xhigh", + "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "omitted", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Claude Opus 4.7 and newer Opus models require thinking.type = \"adaptive\" to enable thinking explicitly.", + "Manual budget_tokens requests return 400 on Claude Opus 4.7 and newer adaptive-only Opus models.", + "task_budget is separate from thinking control and should not be treated as a thinking budget." ] } }, "attachment": true, - "open_weights": true, - "knowledge": "2026-01-04", - "release_date": "2026-08-10", - "last_updated": "2026-08-10", + "open_weights": false, + "knowledge": "2026-01", + "release_date": "2026-05-28", + "last_updated": "2026-05-28", "type": "chat" }, { - "id": "glm-5.2", - "name": "GLM-5.2", - "display_name": "GLM-5.2", + "id": "grok-4-6", + "name": "Grok 4.6", + "display_name": "Grok 4.6", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 500000, + "output": 524288 }, "temperature": true, "tool_call": true, @@ -139722,68 +138709,77 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, "effort_options": [ - "none", + "low", + "medium", "high", - "max" + "xhigh" ] } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-06-13", - "last_updated": "2026-06-13", + "attachment": true, + "open_weights": false, + "knowledge": "2026-02-01", + "release_date": "2026-08-12", + "last_updated": "2026-08-12", "type": "chat" - } - ] - }, - "crossmodel": { - "id": "crossmodel", - "name": "CrossModel", - "display_name": "CrossModel", - "api": "https://api.crossmodel.ai/v1", - "doc": "https://www.crossmodel.ai/docs", - "models": [ + }, { - "id": "minimax/minimax-m3", - "name": "MiniMax-M3", - "display_name": "MiniMax-M3", + "id": "gpt-5-2", + "name": "GPT-5.2", + "display_name": "GPT-5.2", "modalities": { "input": [ "text", - "image", - "video" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1024000, - "output": 512000 + "context": 400000, + "output": 128000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { - "supported": true + "supported": true, + "default_enabled": false, + "mode": "effort", + "effort": "none", + "effort_options": [ + "none", + "low", + "medium", + "high", + "xhigh" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, "attachment": true, - "open_weights": true, - "release_date": "2026-06-01", - "last_updated": "2026-06-01", + "open_weights": false, + "knowledge": "2025-08-31", + "release_date": "2025-12-11", + "last_updated": "2025-12-11", "type": "chat" }, { - "id": "minimax/minimax-m2.7", - "name": "MiniMax-M2.7", - "display_name": "MiniMax-M2.7", + "id": "meta-llama-3-3-70b-instruct", + "name": "Llama-3.3-70B-Instruct", + "display_name": "Llama-3.3-70B-Instruct", "modalities": { "input": [ "text" @@ -139793,50 +138789,40 @@ ] }, "limit": { - "context": 204800, - "output": 131072 + "context": 128000, + "output": 8192 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } + "supported": false }, "attachment": false, "open_weights": true, - "release_date": "2026-03-18", - "last_updated": "2026-03-18", + "knowledge": "2023-12", + "release_date": "2024-12-06", + "last_updated": "2024-12-06", "type": "chat" }, { - "id": "x-ai/grok-4.5", - "name": "Grok 4.5", - "display_name": "Grok 4.5", + "id": "gpt-6-1-sol", + "name": "GPT-6.1 Sol", + "display_name": "GPT-6.1 Sol", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 500000, - "output": 500000 + "context": 1050000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -139844,70 +138830,97 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, "effort_options": [ "low", "medium", - "high" + "high", + "xhigh" ] } }, "attachment": true, "open_weights": false, - "release_date": "2026-07-08", - "last_updated": "2026-07-08", + "knowledge": "2026-04-30", + "release_date": "2026-09-29", + "last_updated": "2026-09-29", "type": "chat" }, { - "id": "x-ai/grok-build-0.1", - "name": "Grok Build 0.1", - "display_name": "Grok Build 0.1", + "id": "claude-opus-4-6", + "name": "Claude Opus 4.6", + "display_name": "Claude Opus 4.6", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 256000, - "output": 256000 + "context": 1000000, + "output": 128000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { - "supported": true + "supported": true, + "default_enabled": false, + "mode": "mixed", + "budget": { + "min": 1024, + "unit": "tokens" + }, + "effort": "high", + "effort_options": [ + "low", + "medium", + "high", + "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Anthropic recommends adaptive thinking with effort for Claude 4.6; budget_tokens remains a deprecated compatibility path.", + "Anthropic API defaults effort to high; lower effort levels should be chosen per workload." + ] } }, "attachment": true, "open_weights": false, - "release_date": "2026-04-16", - "last_updated": "2026-04-16", + "knowledge": "2025-05-31", + "release_date": "2026-02-05", + "last_updated": "2026-03-13", "type": "chat" }, { - "id": "x-ai/grok-4.7", - "name": "Grok 4.7", - "display_name": "Grok 4.7", + "id": "inkling", + "name": "Inkling", + "display_name": "Inkling", "modalities": { "input": [ "text", - "image" + "image", + "audio" ], "output": [ "text" ] }, "limit": { - "context": 500000, - "output": 500000 + "context": 1048576, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -139918,28 +138931,30 @@ "extra_capabilities": { "reasoning": { "effort_options": [ + "none", + "minimal", "low", "medium", "high", - "xhigh" + "max" ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2026-05", - "release_date": "2026-09-21", - "last_updated": "2026-09-21", + "open_weights": true, + "release_date": "2026-07-15", + "last_updated": "2026-07-15", "type": "chat" }, { - "id": "x-ai/grok-4.3", - "name": "Grok 4.3", - "display_name": "Grok 4.3", + "id": "claude-fable-5-1", + "name": "Claude Fable 5.1", + "display_name": "Claude Fable 5.1", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" @@ -139947,9 +138962,9 @@ }, "limit": { "context": 1000000, - "output": 1000000 + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -139958,24 +138973,40 @@ "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "high", "effort_options": [ - "none", "low", "medium", - "high" + "high", + "xhigh", + "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "omitted", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Adaptive thinking is always on for Claude Fable 5 and Claude Mythos 5; thinking.type = \"disabled\" is rejected.", + "Manual budget_tokens requests return 400 on Claude Fable 5 and Claude Mythos 5.", + "thinking.display defaults to omitted; set display to summarized to receive readable thinking summaries." ] } }, "attachment": true, "open_weights": false, - "release_date": "2026-04-17", - "last_updated": "2026-04-17", + "knowledge": "2026-06", + "release_date": "2026-09-01", + "last_updated": "2026-09-01", "type": "chat" }, { - "id": "x-ai/grok-4.6", - "name": "Grok 4.6", - "display_name": "Grok 4.6", + "id": "deepseek-v4-1-flash", + "name": "DeepSeek V4.1 Flash", + "display_name": "DeepSeek V4.1 Flash", "modalities": { "input": [ "text", @@ -139986,8 +139017,8 @@ ] }, "limit": { - "context": 500000, - "output": 500000 + "context": 1000000, + "output": 384000 }, "temperature": true, "tool_call": true, @@ -139997,32 +139028,31 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, "effort_options": [ "low", - "medium", "high", - "xhigh" + "max" ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2026-02-01", - "release_date": "2026-08-12", - "last_updated": "2026-08-12", + "open_weights": true, + "knowledge": "2025-05", + "release_date": "2026-09-10", + "last_updated": "2026-09-10", "type": "chat" }, { - "id": "gemini/gemini-3-flash-preview", - "name": "Gemini 3 Flash Preview", - "display_name": "Gemini 3 Flash Preview", + "id": "gemini-3-5-flash", + "name": "Gemini 3.5 Flash", + "display_name": "Gemini 3.5 Flash", "modalities": { "input": [ "text", "image", "video", - "audio" + "audio", + "pdf" ], "output": [ "text" @@ -140040,50 +139070,40 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "level", - "level": "high", - "level_options": [ + "effort_options": [ "minimal", "low", "medium", "high" - ], - "summaries": true, - "visibility": "summary", - "continuation": [ - "thought_signatures" ] } }, "attachment": true, "open_weights": false, "knowledge": "2025-01", - "release_date": "2025-12-17", - "last_updated": "2025-12-17", + "release_date": "2026-05-19", + "last_updated": "2026-05-19", "type": "chat" }, { - "id": "gemini/gemini-2.5-pro", - "name": "Gemini 2.5 Pro", - "display_name": "Gemini 2.5 Pro", + "id": "claude-opus-5", + "name": "Claude Opus 5", + "display_name": "Claude Opus 5", "modalities": { "input": [ "text", "image", - "audio", - "video" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 1000000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -140092,39 +139112,33 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "budget", - "budget": { - "default": -1, - "min": 128, - "max": 32768, - "auto": -1, - "unit": "tokens" - }, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thought_signatures" + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-01", - "release_date": "2025-06-17", - "last_updated": "2025-06-17", + "knowledge": "2026-05", + "release_date": "2026-07-24", + "last_updated": "2026-07-24", "type": "chat" }, { - "id": "gemini/gemini-3.5-flash-lite", - "name": "Gemini 3.5 Flash Lite", - "display_name": "Gemini 3.5 Flash Lite", + "id": "gemini-3-6-flash", + "name": "Gemini 3.6 Flash", + "display_name": "Gemini 3.6 Flash", "modalities": { "input": [ "text", "image", + "video", "audio", - "video" + "pdf" ], "output": [ "text" @@ -140134,7 +139148,7 @@ "context": 1048576, "output": 65536 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -140142,7 +139156,6 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, "effort_options": [ "minimal", "low", @@ -140159,15 +139172,16 @@ "type": "chat" }, { - "id": "gemini/gemini-2.5-flash", - "name": "Gemini 2.5 Flash", - "display_name": "Gemini 2.5 Flash", + "id": "gemini-3-1-pro", + "name": "Gemini 3.1 Pro Preview Custom Tools", + "display_name": "Gemini 3.1 Pro Preview Custom Tools", "modalities": { "input": [ "text", "image", "video", - "audio" + "audio", + "pdf" ], "output": [ "text" @@ -140185,51 +139199,39 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "budget", - "budget": { - "default": -1, - "min": 0, - "max": 24576, - "auto": -1, - "off": 0, - "unit": "tokens" - }, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thought_signatures" + "effort_options": [ + "low", + "medium", + "high" ] } }, "attachment": true, "open_weights": false, "knowledge": "2025-01", - "release_date": "2025-06-17", - "last_updated": "2025-06-17", + "release_date": "2026-02-19", + "last_updated": "2026-02-19", "type": "chat" }, { - "id": "gemini/gemini-3.7-flash", - "name": "Gemini 3.7 Flash", - "display_name": "Gemini 3.7 Flash", + "id": "gpt-6-luna", + "name": "GPT-6 Luna", + "display_name": "GPT-6 Luna", "modalities": { "input": [ "text", "image", - "audio", - "video" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 1050000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -140237,48 +139239,39 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "level", - "level": "high", - "level_options": [ - "minimal", + "effort_options": [ + "none", "low", "medium", - "high" - ], - "summaries": true, - "visibility": "summary", - "continuation": [ - "thought_signatures" + "high", + "xhigh" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-03", - "release_date": "2026-08-13", - "last_updated": "2026-08-13", + "knowledge": "2026-05-18", + "release_date": "2026-09-22", + "last_updated": "2026-09-22", "type": "chat" }, { - "id": "gemini/gemini-3.1-pro-preview", - "name": "Gemini 3.1 Pro Preview", - "display_name": "Gemini 3.1 Pro Preview", + "id": "gpt-5-3-codex", + "name": "GPT-5.3 Codex", + "display_name": "GPT-5.3 Codex", "modalities": { "input": [ "text", "image", - "audio", - "video" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 400000, + "output": 128000 }, "temperature": true, "tool_call": true, @@ -140290,95 +139283,99 @@ "reasoning": { "supported": true, "default_enabled": true, - "mode": "level", - "level": "high", - "level_options": [ + "mode": "effort", + "effort": "medium", + "effort_options": [ + "low", + "medium", + "high", + "xhigh" + ], + "verbosity": "medium", + "verbosity_options": [ "low", + "medium", "high" ], - "summaries": true, - "visibility": "summary", - "continuation": [ - "thought_signatures" - ] + "visibility": "hidden" } }, "attachment": true, "open_weights": false, - "knowledge": "2025-01", - "release_date": "2026-02-19", - "last_updated": "2026-02-19", + "knowledge": "2025-08-31", + "release_date": "2026-02-05", + "last_updated": "2026-02-05", "type": "chat" }, { - "id": "gemini/gemini-2.5-flash-lite", - "name": "Gemini 2.5 Flash-Lite", - "display_name": "Gemini 2.5 Flash-Lite", + "id": "gpt-5-6-terra", + "name": "GPT-5.6 Terra", + "display_name": "GPT-5.6 Terra", "modalities": { "input": [ "text", "image", - "audio", - "video" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 1050000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "budget", - "budget": { - "default": -1, - "min": 512, - "max": 24576, - "auto": -1, - "unit": "tokens" - }, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thought_signatures" - ] + "default_enabled": true, + "mode": "effort", + "effort": "medium", + "effort_options": [ + "none", + "low", + "medium", + "high", + "xhigh", + "max" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, "attachment": true, "open_weights": false, - "knowledge": "2025-01", - "release_date": "2025-06-17", - "last_updated": "2025-06-17", + "knowledge": "2026-02-16", + "release_date": "2026-07-09", + "last_updated": "2026-07-09", "type": "chat" }, { - "id": "gemini/gemini-3.8-flash", - "name": "Gemini 3.8 Flash", - "display_name": "Gemini 3.8 Flash", - "modalities": { - "input": [ - "text", - "image", - "audio", - "video" + "id": "gpt-oss-20b", + "name": "GPT OSS 20B", + "display_name": "GPT OSS 20B", + "modalities": { + "input": [ + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 131072, + "output": 25000 }, "temperature": true, "tool_call": true, @@ -140389,38 +139386,31 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "level", - "level": "high", - "level_options": [ - "minimal", + "effort_options": [ "low", "medium", "high" - ], - "summaries": true, - "visibility": "summary", - "continuation": [ - "thought_signatures" ] } }, - "attachment": true, - "open_weights": false, - "release_date": "2026-09-02", - "last_updated": "2026-09-02", + "attachment": false, + "open_weights": true, + "knowledge": "2024-06", + "release_date": "2025-08-05", + "last_updated": "2025-08-05", "type": "chat" }, { - "id": "gemini/gemini-4-argon", - "name": "Gemini 4 Argon", - "display_name": "Gemini 4 Argon", + "id": "gemini-3-8-flash", + "name": "Gemini 3.8 Flash", + "display_name": "Gemini 3.8 Flash", "modalities": { "input": [ "text", "image", + "video", "audio", - "video" + "pdf" ], "output": [ "text" @@ -140439,8 +139429,6 @@ "extra_capabilities": { "reasoning": { "effort_options": [ - "none", - "minimal", "low", "medium", "high" @@ -140449,20 +139437,27 @@ }, "attachment": true, "open_weights": false, - "knowledge": "2026-03", - "release_date": "2026-10-13", - "last_updated": "2026-10-13", + "release_date": "2026-09-02", + "last_updated": "2026-09-02", "type": "chat" - }, + } + ] + }, + "stepfun": { + "id": "stepfun", + "name": "StepFun (China)", + "display_name": "StepFun (China)", + "api": "https://api.stepfun.com/v1", + "doc": "https://platform.stepfun.com/docs/zh/overview/concept", + "models": [ { - "id": "gemini/gemini-3.6-flash", - "name": "Gemini 3.6 Flash", - "display_name": "Gemini 3.6 Flash", + "id": "step-5-preview", + "name": "Step 5 Preview", + "display_name": "Step 5 Preview", "modalities": { "input": [ "text", "image", - "audio", "video" ], "output": [ @@ -140470,8 +139465,8 @@ ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 1000000, + "output": 384000 }, "temperature": true, "tool_call": true, @@ -140482,47 +139477,68 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "level", - "level": "high", - "level_options": [ - "minimal", + "mode": "effort", + "effort": "medium", + "effort_options": [ "low", "medium", "high" ], + "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ - "thought_signatures" + "thinking_blocks" ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2026-03", - "release_date": "2026-07-21", - "last_updated": "2026-07-21", + "open_weights": true, + "release_date": "2026-09-16", + "last_updated": "2026-09-20", "type": "chat" }, { - "id": "gemini/gemini-3.5-flash", - "name": "Gemini 3.5 Flash", - "display_name": "Gemini 3.5 Flash", + "id": "stepaudio-2.5-asr", + "name": "StepAudio 2.5 ASR", + "display_name": "StepAudio 2.5 ASR", "modalities": { "input": [ - "text", - "image", - "audio", - "video" + "audio" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 8192, + "output": 8192 + }, + "temperature": false, + "tool_call": false, + "reasoning": { + "supported": false + }, + "attachment": true, + "open_weights": false, + "release_date": "2026-04-24", + "last_updated": "2026-07-02" + }, + { + "id": "step-3.5-flash-2603", + "name": "Step 3.5 Flash 2603", + "display_name": "Step 3.5 Flash 2603", + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 256000, + "output": 256000 }, "temperature": true, "tool_call": true, @@ -140533,47 +139549,40 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "level", - "level": "high", - "level_options": [ - "minimal", - "low", - "medium", - "high" - ], + "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ - "thought_signatures" + "thinking_blocks" + ], + "effort_options": [ + "low", + "high" ] } }, - "attachment": true, - "open_weights": false, + "attachment": false, + "open_weights": true, "knowledge": "2025-01", - "release_date": "2026-05-19", - "last_updated": "2026-05-19", + "release_date": "2026-04-02", + "last_updated": "2026-04-02", "type": "chat" }, { - "id": "xiaomi/mimo-v2.6-pro", - "name": "MiMo-V2.6-Pro", - "display_name": "MiMo-V2.6-Pro", + "id": "step-3.5-flash", + "name": "Step 3.5 Flash", + "display_name": "Step 3.5 Flash", "modalities": { "input": [ - "text", - "image", - "audio", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 256000, + "output": 256000 }, "temperature": true, "tool_call": true, @@ -140583,29 +139592,86 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ - "none", "low", - "medium", "high" ] } }, - "attachment": true, + "attachment": false, "open_weights": true, - "release_date": "2026-09-22", - "last_updated": "2026-09-22", + "knowledge": "2025-01", + "release_date": "2026-01-29", + "last_updated": "2026-02-13", "type": "chat" }, { - "id": "xiaomi/mimo-v2.6-flash", - "name": "MiMo-V2.6-Flash", - "display_name": "MiMo-V2.6-Flash", + "id": "step-tts-2", + "name": "Step TTS 2", + "display_name": "Step TTS 2", + "modalities": { + "input": [ + "text" + ], + "output": [ + "audio" + ] + }, + "limit": { + "context": 8192, + "output": 8192 + }, + "temperature": false, + "tool_call": false, + "reasoning": { + "supported": false + }, + "attachment": false, + "open_weights": false, + "release_date": "2026-03-01", + "last_updated": "2026-07-02" + }, + { + "id": "stepaudio-2.5-tts", + "name": "StepAudio 2.5 TTS", + "display_name": "StepAudio 2.5 TTS", + "modalities": { + "input": [ + "text" + ], + "output": [ + "audio" + ] + }, + "limit": { + "context": 8192, + "output": 8192 + }, + "temperature": false, + "tool_call": false, + "reasoning": { + "supported": false + }, + "attachment": false, + "open_weights": false, + "release_date": "2026-04-16", + "last_updated": "2026-07-02" + }, + { + "id": "step-3.7-flash", + "name": "Step 3.7 Flash", + "display_name": "Step 3.7 Flash", "modalities": { "input": [ "text", "image", - "audio", "video" ], "output": [ @@ -140613,8 +139679,8 @@ ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 256000, + "output": 256000 }, "temperature": true, "tool_call": true, @@ -140624,8 +139690,14 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ - "none", "low", "medium", "high" @@ -140634,69 +139706,136 @@ }, "attachment": true, "open_weights": true, - "release_date": "2026-09-22", - "last_updated": "2026-09-22", + "knowledge": "2026-03-01", + "release_date": "2026-05-29", + "last_updated": "2026-05-29", "type": "chat" - }, + } + ] + }, + "bailing": { + "id": "bailing", + "name": "Bailing", + "display_name": "Bailing", + "api": "https://api.tbox.cn/api/llm/v1", + "doc": "https://alipaytbox.yuque.com/sxs0ba/ling/intro", + "models": [ { - "id": "anthropic/claude-sonnet-5-5", - "name": "Claude Sonnet 5.5", - "display_name": "Claude Sonnet 5.5", + "id": "Ling-1T", + "name": "Ling-1T", + "display_name": "Ling-1T", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 128000, + "output": 32000 }, - "temperature": false, + "temperature": true, "tool_call": true, + "reasoning": { + "supported": false + }, + "attachment": false, + "open_weights": true, + "knowledge": "2024-06", + "release_date": "2025-10", + "last_updated": "2025-10", + "type": "chat" + }, + { + "id": "Ring-1T", + "name": "Ring-1T", + "display_name": "Ring-1T", + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 32000 + }, + "temperature": true, + "tool_call": false, "reasoning": { "supported": true, "default": true }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" - ] + "supported": true } }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-06", - "release_date": "2026-09-28", - "last_updated": "2026-09-28", + "attachment": false, + "open_weights": true, + "knowledge": "2024-06", + "release_date": "2025-10", + "last_updated": "2025-10", + "type": "chat" + } + ] + }, + "drun": { + "id": "drun", + "name": "D.Run (China)", + "display_name": "D.Run (China)", + "api": "https://chat.d.run/v1", + "doc": "https://www.d.run", + "models": [ + { + "id": "public/deepseek-v3", + "name": "DeepSeek V3", + "display_name": "DeepSeek V3", + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "output": 8192 + }, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": false + }, + "attachment": false, + "open_weights": true, + "knowledge": "2024-07", + "release_date": "2024-12-26", + "last_updated": "2024-12-26", "type": "chat" }, { - "id": "anthropic/claude-haiku-5-5", - "name": "Claude Haiku 5.5", - "display_name": "Claude Haiku 5.5", + "id": "public/minimax-m25", + "name": "MiniMax M2.5", + "display_name": "MiniMax M2.5", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 204800, + "output": 131072 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -140704,40 +139843,38 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-06", - "release_date": "2026-10-07", - "last_updated": "2026-10-07", + "attachment": false, + "open_weights": true, + "release_date": "2026-02-12", + "last_updated": "2026-02-12", "type": "chat" }, { - "id": "anthropic/claude-opus-5", - "name": "Claude Opus 5", - "display_name": "Claude Opus 5", + "id": "public/deepseek-r1", + "name": "DeepSeek R1", + "display_name": "DeepSeek R1", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 131072, + "output": 32000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -140746,30 +139883,67 @@ "extra_capabilities": { "reasoning": { "supported": true, - "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-05", - "release_date": "2026-07-24", - "last_updated": "2026-07-24", + "attachment": false, + "open_weights": true, + "knowledge": "2024-12", + "release_date": "2025-01-20", + "last_updated": "2025-05-29", + "type": "chat" + } + ] + }, + "auriko": { + "id": "auriko", + "name": "Auriko", + "display_name": "Auriko", + "api": "https://api.auriko.ai/v1", + "doc": "https://docs.auriko.ai", + "models": [ + { + "id": "minimax-m2-7", + "name": "MiniMax-M2.7", + "display_name": "MiniMax-M2.7", + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 204800, + "output": 131072 + }, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": true, + "default": true + }, + "attachment": false, + "open_weights": true, + "release_date": "2026-03-18", + "last_updated": "2026-03-18", "type": "chat" }, { - "id": "anthropic/claude-opus-4-7", + "id": "claude-opus-4-7", "name": "Claude Opus 4.7", "display_name": "Claude Opus 4.7", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" @@ -140819,23 +139993,54 @@ "type": "chat" }, { - "id": "anthropic/claude-opus-5-5", - "name": "Claude Opus 5.5", - "display_name": "Claude Opus 5.5", + "id": "minimax-m2-7-highspeed", + "name": "MiniMax-M2.7-highspeed", + "display_name": "MiniMax-M2.7-highspeed", + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 204800, + "output": 131072 + }, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": true, + "default": true + }, + "attachment": false, + "open_weights": true, + "release_date": "2026-03-18", + "last_updated": "2026-03-18", + "type": "chat" + }, + { + "id": "gemini-2.5-pro", + "name": "Gemini 2.5 Pro", + "display_name": "Gemini 2.5 Pro", "modalities": { "input": [ "text", - "image" + "image", + "audio", + "video", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 1048576, + "output": 65536 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -140843,30 +140048,39 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" + "supported": true, + "default_enabled": true, + "mode": "budget", + "budget": { + "default": -1, + "min": 128, + "max": 32768, + "auto": -1, + "unit": "tokens" + }, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thought_signatures" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-06", - "release_date": "2026-09-22", - "last_updated": "2026-09-22", + "knowledge": "2025-01", + "release_date": "2025-06-17", + "last_updated": "2025-06-17", "type": "chat" }, { - "id": "anthropic/claude-sonnet-5", - "name": "Claude Sonnet 5", - "display_name": "Claude Sonnet 5", + "id": "qwen-3.6-plus", + "name": "Qwen3.6 Plus", + "display_name": "Qwen3.6 Plus", "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ "text" @@ -140874,9 +140088,38 @@ }, "limit": { "context": 1000000, - "output": 128000 + "output": 65536 }, - "temperature": false, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": true, + "default": true + }, + "attachment": true, + "open_weights": false, + "knowledge": "2025-04", + "release_date": "2026-04-02", + "last_updated": "2026-04-02", + "type": "chat" + }, + { + "id": "deepseek-v4-pro", + "name": "DeepSeek V4 Pro", + "display_name": "DeepSeek V4 Pro", + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1000000, + "output": 384000 + }, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -140885,30 +140128,41 @@ "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "high", "effort_options": [ "low", - "medium", "high", - "xhigh", "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-01-31", - "release_date": "2026-06-30", - "last_updated": "2026-06-30", + "attachment": false, + "open_weights": true, + "knowledge": "2025-05", + "release_date": "2026-04-24", + "last_updated": "2026-04-24", "type": "chat" }, { - "id": "anthropic/claude-sonnet-4-6", + "id": "claude-sonnet-4-6", "name": "Claude Sonnet 4.6", "display_name": "Claude Sonnet 4.6", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" @@ -140916,7 +140170,7 @@ }, "limit": { "context": 1000000, - "output": 128000 + "output": 64000 }, "temperature": true, "tool_call": true, @@ -140960,130 +140214,119 @@ "type": "chat" }, { - "id": "anthropic/claude-haiku-4-5", - "name": "Claude Haiku 4.5 (latest)", - "display_name": "Claude Haiku 4.5 (latest)", + "id": "grok-4.3", + "name": "Grok 4.3", + "display_name": "Grok 4.3", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 64000 + "context": 1000000, + "output": 30000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "budget", - "budget": { - "min": 1024, - "unit": "tokens" - }, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", - "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." + "effort_options": [ + "low", + "medium", + "high" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-02-28", - "release_date": "2025-10-15", - "last_updated": "2025-10-15", + "release_date": "2026-04-17", + "last_updated": "2026-04-17", "type": "chat" }, { - "id": "anthropic/claude-opus-4-8", - "name": "Claude Opus 4.8", - "display_name": "Claude Opus 4.8", + "id": "gemini-2.5-flash", + "name": "Gemini 2.5 Flash", + "display_name": "Gemini 2.5 Flash", "modalities": { "input": [ "text", - "image" + "image", + "audio", + "video", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 1048576, + "output": 65536 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "high", - "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" - ], - "interleaved": true, + "default_enabled": true, + "mode": "budget", + "budget": { + "default": -1, + "min": 0, + "max": 24576, + "auto": -1, + "off": 0, + "unit": "tokens" + }, "summaries": true, - "visibility": "omitted", + "visibility": "summary", "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Claude Opus 4.7 and newer Opus models require thinking.type = \"adaptive\" to enable thinking explicitly.", - "Manual budget_tokens requests return 400 on Claude Opus 4.7 and newer adaptive-only Opus models.", - "task_budget is separate from thinking control and should not be treated as a thinking budget." + "thought_signatures" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-01", - "release_date": "2026-05-28", - "last_updated": "2026-05-28", + "knowledge": "2025-01", + "release_date": "2025-06-17", + "last_updated": "2025-06-17", "type": "chat" }, { - "id": "anthropic/claude-fable-5", - "name": "Claude Fable 5", - "display_name": "Claude Fable 5", + "id": "gemini-3.1-pro-preview", + "name": "Gemini 3.1 Pro Preview", + "display_name": "Gemini 3.1 Pro Preview", "modalities": { "input": [ "text", - "image" + "image", + "video", + "audio", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 1048576, + "output": 65536 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -141093,43 +140336,33 @@ "reasoning": { "supported": true, "default_enabled": true, - "mode": "effort", - "effort": "high", - "effort_options": [ + "mode": "level", + "level": "high", + "level_options": [ "low", - "medium", - "high", - "xhigh", - "max" + "high" ], - "interleaved": true, "summaries": true, - "visibility": "omitted", + "visibility": "summary", "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Adaptive thinking is always on for Claude Fable 5 and Claude Mythos 5; thinking.type = \"disabled\" is rejected.", - "Manual budget_tokens requests return 400 on Claude Fable 5 and Claude Mythos 5.", - "thinking.display defaults to omitted; set display to summarized to receive readable thinking summaries." + "thought_signatures" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-01-31", - "release_date": "2026-06-09", - "last_updated": "2026-06-09", + "knowledge": "2025-01", + "release_date": "2026-02-19", + "last_updated": "2026-02-19", "type": "chat" }, { - "id": "anthropic/claude-fable-5-1", - "name": "Claude Fable 5.1", - "display_name": "Claude Fable 5.1", + "id": "deepseek-v4-flash", + "name": "DeepSeek V4 Flash", + "display_name": "DeepSeek V4 Flash", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" @@ -141137,9 +140370,9 @@ }, "limit": { "context": 1000000, - "output": 128000 + "output": 384000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -141153,40 +140386,36 @@ "effort": "high", "effort_options": [ "low", - "medium", "high", - "xhigh", "max" ], "interleaved": true, "summaries": true, - "visibility": "omitted", + "visibility": "summary", "continuation": [ "thinking_blocks" ], "notes": [ - "Adaptive thinking is always on for Claude Fable 5 and Claude Mythos 5; thinking.type = \"disabled\" is rejected.", - "Manual budget_tokens requests return 400 on Claude Fable 5 and Claude Mythos 5.", - "thinking.display defaults to omitted; set display to summarized to receive readable thinking summaries." + "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-06", - "release_date": "2026-09-01", - "last_updated": "2026-09-01", + "attachment": false, + "open_weights": true, + "knowledge": "2025-05", + "release_date": "2026-04-24", + "last_updated": "2026-04-24", "type": "chat" }, { - "id": "z-ai/glm-5.3-flash", - "name": "GLM-5.3-Flash", - "display_name": "GLM-5.3-Flash", + "id": "claude-opus-4-6", + "name": "Claude Opus 4.6", + "display_name": "Claude Opus 4.6", "modalities": { "input": [ "text", "image", - "video" + "pdf" ], "output": [ "text" @@ -141200,28 +140429,47 @@ "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": false, + "mode": "mixed", + "budget": { + "min": 1024, + "unit": "tokens" + }, + "effort": "high", "effort_options": [ "low", + "medium", "high", "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Anthropic recommends adaptive thinking with effort for Claude 4.6; budget_tokens remains a deprecated compatibility path.", + "Anthropic API defaults effort to high; lower effort levels should be chosen per workload." ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-08-26", - "last_updated": "2026-08-26", + "open_weights": false, + "knowledge": "2025-05-31", + "release_date": "2026-02-05", + "last_updated": "2026-03-13", "type": "chat" }, { - "id": "z-ai/glm-5-turbo", - "name": "GLM-5-Turbo", - "display_name": "GLM-5-Turbo", + "id": "glm-5.1", + "name": "GLM-5.1", + "display_name": "GLM-5.1", "modalities": { "input": [ "text" @@ -141232,7 +140480,7 @@ }, "limit": { "context": 200000, - "output": 128000 + "output": 131072 }, "temperature": true, "tool_call": true, @@ -141242,32 +140490,40 @@ }, "extra_capabilities": { "reasoning": { - "supported": true + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] } }, "attachment": false, - "open_weights": false, - "release_date": "2026-03-16", - "last_updated": "2026-03-16", + "open_weights": true, + "release_date": "2026-04-07", + "last_updated": "2026-04-07", "type": "chat" }, { - "id": "z-ai/glm-4.7", - "name": "GLM-4.7", - "display_name": "GLM-4.7", + "id": "kimi-k2.5", + "name": "Kimi K2.5", + "display_name": "Kimi K2.5", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 128000 + "context": 262144, + "output": 262144 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -141281,31 +140537,40 @@ "visibility": "summary", "continuation": [ "thinking_blocks" + ], + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" ] } }, - "attachment": false, + "attachment": true, "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-12-22", - "last_updated": "2025-12-22", + "knowledge": "2025-01", + "release_date": "2026-01-27", + "last_updated": "2026-01-27", "type": "chat" }, { - "id": "z-ai/glm-5.1", - "name": "GLM-5.1", - "display_name": "GLM-5.1", + "id": "kimi-k2.6", + "name": "Kimi K2.6", + "display_name": "Kimi K2.6", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 128000 + "context": 262144, + "output": 262144 }, "temperature": true, "tool_call": true, @@ -141315,19 +140580,42 @@ }, "extra_capabilities": { "reasoning": { - "supported": true + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" + ] } }, - "attachment": false, + "attachment": true, "open_weights": true, - "release_date": "2026-04-07", - "last_updated": "2026-04-07", + "knowledge": "2025-01", + "release_date": "2026-04-20", + "last_updated": "2026-04-20", "type": "chat" - }, + } + ] + }, + "tencent-coding-plan": { + "id": "tencent-coding-plan", + "name": "Tencent Coding Plan (China)", + "display_name": "Tencent Coding Plan (China)", + "api": "https://api.lkeap.cloud.tencent.com/coding/v3", + "doc": "https://cloud.tencent.com/document/product/1772/128947", + "models": [ { - "id": "z-ai/glm-5", - "name": "GLM-5", - "display_name": "GLM-5", + "id": "minimax-m2.5", + "name": "MiniMax-M2.5", + "display_name": "MiniMax-M2.5", "modalities": { "input": [ "text" @@ -141337,8 +140625,8 @@ ] }, "limit": { - "context": 200000, - "output": 128000 + "context": 204800, + "output": 32768 }, "temperature": true, "tool_call": true, @@ -141364,9 +140652,9 @@ "type": "chat" }, { - "id": "z-ai/glm-5.3", - "name": "GLM-5.3", - "display_name": "GLM-5.3", + "id": "hunyuan-turbos", + "name": "Hunyuan-TurboS", + "display_name": "Hunyuan-TurboS", "modalities": { "input": [ "text" @@ -141376,35 +140664,24 @@ ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 131072, + "output": 16384 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "effort_options": [ - "low", - "high", - "max" - ] - } + "supported": false }, "attachment": false, - "open_weights": true, - "release_date": "2026-08-14", - "last_updated": "2026-08-14", + "open_weights": false, + "release_date": "2026-03-08", + "last_updated": "2026-03-08", "type": "chat" }, { - "id": "z-ai/glm-5.2", - "name": "GLM-5.2", - "display_name": "GLM-5.2", + "id": "hunyuan-2.0-instruct", + "name": "Tencent HY 2.0 Instruct", + "display_name": "Tencent HY 2.0 Instruct", "modalities": { "input": [ "text" @@ -141414,30 +140691,24 @@ ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 131072, + "output": 16384 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true - } + "supported": false }, "attachment": false, - "open_weights": true, - "release_date": "2026-06-13", - "last_updated": "2026-06-13", + "open_weights": false, + "release_date": "2026-03-08", + "last_updated": "2026-03-08", "type": "chat" }, { - "id": "deepseek/deepseek-v4-flash", - "name": "DeepSeek V4 Flash", - "display_name": "DeepSeek V4 Flash", + "id": "tc-code-latest", + "name": "Auto", + "display_name": "Auto", "modalities": { "input": [ "text" @@ -141447,48 +140718,24 @@ ] }, "limit": { - "context": 1000000, - "output": 384000 + "context": 131072, + "output": 16384 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "high", - "effort_options": [ - "low", - "high", - "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." - ] - } + "supported": false }, "attachment": false, - "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-04-24", - "last_updated": "2026-04-24", + "open_weights": false, + "release_date": "2026-03-08", + "last_updated": "2026-03-08", "type": "chat" }, { - "id": "deepseek/deepseek-v4-pro", - "name": "DeepSeek V4 Pro", - "display_name": "DeepSeek V4 Pro", + "id": "hunyuan-t1", + "name": "Hunyuan-T1", + "display_name": "Hunyuan-T1", "modalities": { "input": [ "text" @@ -141498,8 +140745,8 @@ ] }, "limit": { - "context": 1000000, - "output": 384000 + "context": 131072, + "output": 16384 }, "temperature": true, "tool_call": true, @@ -141510,48 +140757,35 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "high", - "effort_options": [ - "low", - "high", - "max" - ], "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ "thinking_blocks" - ], - "notes": [ - "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." ] } }, "attachment": false, - "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-04-24", - "last_updated": "2026-04-24", + "open_weights": false, + "release_date": "2026-03-08", + "last_updated": "2026-03-08", "type": "chat" }, { - "id": "deepseek/deepseek-v4-flash-vision-exp", - "name": "DeepSeek V4 Flash Vision Exp", - "display_name": "DeepSeek V4 Flash Vision Exp", + "id": "glm-5", + "name": "GLM-5", + "display_name": "GLM-5", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 384000 + "context": 202752, + "output": 16384 }, "temperature": true, "tool_call": true, @@ -141562,47 +140796,35 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "high", - "effort_options": [ - "low", - "high", - "max" - ], "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ "thinking_blocks" - ], - "notes": [ - "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." ] } }, - "attachment": true, + "attachment": false, "open_weights": true, - "release_date": "2026-08-21", - "last_updated": "2026-09-01", + "release_date": "2026-02-12", + "last_updated": "2026-02-12", "type": "chat" }, { - "id": "deepseek/deepseek-v4.1-flash", - "name": "DeepSeek V4.1 Flash", - "display_name": "DeepSeek V4.1 Flash", + "id": "hunyuan-2.0-thinking", + "name": "Tencent HY 2.0 Think", + "display_name": "Tencent HY 2.0 Think", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 384000 + "context": 131072, + "output": 16384 }, "temperature": true, "tool_call": true, @@ -141613,55 +140835,75 @@ "extra_capabilities": { "reasoning": { "supported": true, - "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, - "attachment": true, - "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-09-10", - "last_updated": "2026-09-10", + "attachment": false, + "open_weights": false, + "release_date": "2026-03-08", + "last_updated": "2026-03-08", "type": "chat" }, { - "id": "openai/gpt-4o-mini", - "name": "GPT-4o mini", - "display_name": "GPT-4o mini", + "id": "kimi-k2.5", + "name": "Kimi-K2.5", + "display_name": "Kimi-K2.5", "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 16384 + "context": 262144, + "output": 32768 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } }, "attachment": true, - "open_weights": false, - "knowledge": "2023-09", - "release_date": "2024-07-18", - "last_updated": "2024-07-18", + "open_weights": true, + "knowledge": "2025-01", + "release_date": "2026-01-27", + "last_updated": "2026-01-27", "type": "chat" - }, + } + ] + }, + "impossibl": { + "id": "impossibl", + "name": "Impossibl", + "display_name": "Impossibl", + "api": "https://api.impossibl.com/v1", + "doc": "https://impossibl.com/docs/models", + "models": [ { - "id": "openai/gpt-5.5", - "name": "GPT-5.5", - "display_name": "GPT-5.5", + "id": "openai/gpt-5-pro", + "name": "GPT-5 Pro", + "display_name": "GPT-5 Pro", "modalities": { "input": [ "text", @@ -141672,8 +140914,8 @@ ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 400000, + "output": 272000 }, "temperature": false, "tool_call": true, @@ -141685,14 +140927,8 @@ "reasoning": { "supported": true, "default_enabled": true, - "mode": "effort", - "effort": "medium", - "effort_options": [ - "low", - "medium", - "high", - "xhigh" - ], + "mode": "fixed", + "effort": "high", "verbosity": "medium", "verbosity_options": [ "low", @@ -141704,27 +140940,26 @@ }, "attachment": true, "open_weights": false, - "knowledge": "2025-12-01", - "release_date": "2026-04-23", - "last_updated": "2026-04-23", + "knowledge": "2024-09-30", + "release_date": "2025-10-06", + "last_updated": "2025-10-06", "type": "chat" }, { - "id": "openai/gpt-6-astra", - "name": "GPT-6 Astra", - "display_name": "GPT-6 Astra", + "id": "openai/o3-mini", + "name": "o3-mini", + "display_name": "o3-mini", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 200000, + "output": 100000 }, "temperature": false, "tool_call": true, @@ -141735,26 +140970,28 @@ "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", "effort_options": [ "low", "medium", - "high", - "xhigh", - "max" - ] + "high" + ], + "visibility": "hidden" } }, - "attachment": true, + "attachment": false, "open_weights": false, - "knowledge": "2026-04-30", - "release_date": "2026-09-04", - "last_updated": "2026-09-04", + "knowledge": "2024-05", + "release_date": "2024-12-20", + "last_updated": "2025-01-29", "type": "chat" }, { - "id": "openai/gpt-5.4", - "name": "GPT-5.4", - "display_name": "GPT-5.4", + "id": "openai/gpt-5.4-nano", + "name": "GPT-5.4 nano", + "display_name": "GPT-5.4 nano", "modalities": { "input": [ "text", @@ -141765,7 +141002,7 @@ ] }, "limit": { - "context": 1050000, + "context": 400000, "output": 128000 }, "temperature": true, @@ -141799,18 +141036,19 @@ "attachment": true, "open_weights": false, "knowledge": "2025-08-31", - "release_date": "2026-03-05", - "last_updated": "2026-03-05", + "release_date": "2026-03-17", + "last_updated": "2026-03-17", "type": "chat" }, { - "id": "openai/gpt-6-sol", - "name": "GPT-6 Sol", - "display_name": "GPT-6 Sol", + "id": "openai/gpt-5.4", + "name": "GPT-5.4", + "display_name": "GPT-5.4", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" @@ -141820,35 +141058,45 @@ "context": 1050000, "output": 128000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": false, + "mode": "effort", + "effort": "none", "effort_options": [ "none", "low", "medium", "high", - "xhigh", - "max" - ] + "xhigh" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, "attachment": true, "open_weights": false, - "knowledge": "2026-04-20", - "release_date": "2026-09-22", - "last_updated": "2026-09-22", + "knowledge": "2025-08-31", + "release_date": "2026-03-05", + "last_updated": "2026-03-05", "type": "chat" }, { - "id": "openai/gpt-5.6-sol", - "name": "GPT-5.6 Sol", - "display_name": "GPT-5.6 Sol", + "id": "openai/gpt-5.4-mini", + "name": "GPT-5.4 mini", + "display_name": "GPT-5.4 mini", "modalities": { "input": [ "text", @@ -141859,28 +141107,27 @@ ] }, "limit": { - "context": 1050000, + "context": 400000, "output": 128000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, + "default_enabled": false, "mode": "effort", - "effort": "medium", + "effort": "none", "effort_options": [ "none", "low", "medium", "high", - "xhigh", - "max" + "xhigh" ], "verbosity": "medium", "verbosity_options": [ @@ -141893,9 +141140,9 @@ }, "attachment": true, "open_weights": false, - "knowledge": "2026-02-16", - "release_date": "2026-07-09", - "last_updated": "2026-07-09", + "knowledge": "2025-08-31", + "release_date": "2026-03-17", + "last_updated": "2026-03-17", "type": "chat" }, { @@ -141905,7 +141152,8 @@ "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" @@ -141952,23 +141200,24 @@ "type": "chat" }, { - "id": "openai/gpt-6-luna", - "name": "GPT-6 Luna", - "display_name": "GPT-6 Luna", + "id": "openai/gpt-5.3-codex", + "name": "GPT-5.3 Codex", + "display_name": "GPT-5.3 Codex", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1050000, + "context": 400000, "output": 128000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -141976,27 +141225,36 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", "effort_options": [ - "none", "low", "medium", "high", - "xhigh", - "max" - ] + "xhigh" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, "attachment": true, "open_weights": false, - "knowledge": "2026-05-18", - "release_date": "2026-09-22", - "last_updated": "2026-09-22", + "knowledge": "2025-08-31", + "release_date": "2026-02-05", + "last_updated": "2026-02-05", "type": "chat" }, { - "id": "openai/gpt-5.6-luna", - "name": "GPT-5.6 Luna", - "display_name": "GPT-5.6 Luna", + "id": "openai/gpt-5.1-codex", + "name": "GPT-5.1 Codex", + "display_name": "GPT-5.1 Codex", "modalities": { "input": [ "text", @@ -142007,28 +141265,26 @@ ] }, "limit": { - "context": 1050000, + "context": 400000, "output": 128000 }, "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, + "default_enabled": false, "mode": "effort", - "effort": "medium", + "effort": "none", "effort_options": [ "none", "low", "medium", - "high", - "xhigh", - "max" + "high" ], "verbosity": "medium", "verbosity_options": [ @@ -142041,19 +141297,20 @@ }, "attachment": true, "open_weights": false, - "knowledge": "2026-02-16", - "release_date": "2026-07-09", - "last_updated": "2026-07-09", + "knowledge": "2024-09-30", + "release_date": "2025-11-13", + "last_updated": "2025-11-13", "type": "chat" }, { - "id": "openai/gpt-5.4-mini", - "name": "GPT-5.4 mini", - "display_name": "GPT-5.4 mini", + "id": "openai/gpt-5.2-codex", + "name": "GPT-5.2 Codex", + "display_name": "GPT-5.2 Codex", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" @@ -142063,20 +141320,19 @@ "context": 400000, "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, + "default_enabled": true, "mode": "effort", - "effort": "none", + "effort": "medium", "effort_options": [ - "none", "low", "medium", "high", @@ -142094,14 +141350,14 @@ "attachment": true, "open_weights": false, "knowledge": "2025-08-31", - "release_date": "2026-03-17", - "last_updated": "2026-03-17", + "release_date": "2025-12-11", + "last_updated": "2025-12-11", "type": "chat" }, { - "id": "openai/gpt-5.4-nano", - "name": "GPT-5.4 nano", - "display_name": "GPT-5.4 nano", + "id": "openai/gpt-5.2", + "name": "GPT-5.2", + "display_name": "GPT-5.2", "modalities": { "input": [ "text", @@ -142146,14 +141402,14 @@ "attachment": true, "open_weights": false, "knowledge": "2025-08-31", - "release_date": "2026-03-17", - "last_updated": "2026-03-17", + "release_date": "2025-12-11", + "last_updated": "2025-12-11", "type": "chat" }, { - "id": "openai/gpt-5.5-pro", - "name": "GPT-5.5 Pro", - "display_name": "GPT-5.5 Pro", + "id": "openai/gpt-5.4-pro", + "name": "GPT-5.4 Pro", + "display_name": "GPT-5.4 Pro", "modalities": { "input": [ "text", @@ -142176,24 +141432,34 @@ "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "high", "effort_options": [ "medium", "high", "xhigh" - ] + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, "attachment": true, "open_weights": false, - "knowledge": "2025-12-01", - "release_date": "2026-04-23", - "last_updated": "2026-04-23", + "knowledge": "2025-08-31", + "release_date": "2026-03-05", + "last_updated": "2026-03-05", "type": "chat" }, { - "id": "openai/gpt-6.1-sol", - "name": "GPT-6.1 Sol", - "display_name": "GPT-6.1 Sol", + "id": "openai/gpt-5-codex", + "name": "GPT-5-Codex", + "display_name": "GPT-5-Codex", "modalities": { "input": [ "text", @@ -142204,7 +141470,7 @@ ] }, "limit": { - "context": 1050000, + "context": 400000, "output": 128000 }, "temperature": false, @@ -142215,76 +141481,79 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", "effort_options": [ + "minimal", "low", "medium", - "high", - "xhigh", - "max" - ] + "high" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, "attachment": true, "open_weights": false, - "knowledge": "2026-04-30", - "release_date": "2026-09-29", - "last_updated": "2026-09-29", + "knowledge": "2024-09-30", + "release_date": "2025-09-15", + "last_updated": "2025-09-15", "type": "chat" }, { - "id": "tencent/hy4-preview", - "name": "Hy4 preview", - "display_name": "Hy4 preview", + "id": "openai/gpt-4.1-nano", + "name": "GPT-4.1 nano", + "display_name": "GPT-4.1 nano", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 1047576, + "output": 32768 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "effort_options": [ - "none", - "high" - ] - } + "supported": false }, - "attachment": false, - "open_weights": true, - "release_date": "2026-08-28", - "last_updated": "2026-08-28", + "attachment": true, + "open_weights": false, + "knowledge": "2024-04", + "release_date": "2025-04-14", + "last_updated": "2025-04-14", "type": "chat" }, { - "id": "tencent/hy3", - "name": "Hy3", - "display_name": "Hy3", + "id": "openai/gpt-5-nano", + "name": "GPT-5 Nano", + "display_name": "GPT-5 Nano", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 131072 + "context": 400000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -142293,36 +141562,48 @@ "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", "effort_options": [ - "none", + "minimal", + "low", + "medium", + "high" + ], + "verbosity": "medium", + "verbosity_options": [ "low", + "medium", "high" - ] + ], + "visibility": "hidden" } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-07-06", - "last_updated": "2026-07-06", + "attachment": true, + "open_weights": false, + "knowledge": "2024-05-30", + "release_date": "2025-08-07", + "last_updated": "2025-08-07", "type": "chat" }, { - "id": "moonshot/kimi-k3", - "name": "Kimi K3", - "display_name": "Kimi K3", + "id": "openai/gpt-5.5-pro", + "name": "GPT-5.5 Pro", + "display_name": "GPT-5.5 Pro", "modalities": { "input": [ "text", "image", - "video" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 1048576 + "context": 1050000, + "output": 128000 }, "temperature": false, "tool_call": true, @@ -142334,71 +141615,36 @@ "reasoning": { "supported": true, "effort_options": [ - "low", + "medium", "high", - "max" + "xhigh" ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-07-16", - "last_updated": "2026-07-16", - "type": "chat" - }, - { - "id": "moonshot/kimi-k2.6", - "name": "Kimi K2.6", - "display_name": "Kimi K2.6", - "modalities": { - "input": [ - "text", - "image", - "video" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 262000, - "output": 262000 - }, - "temperature": true, - "tool_call": true, - "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true - } - }, - "attachment": true, - "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-04-20", - "last_updated": "2026-04-20", + "open_weights": false, + "knowledge": "2025-12-01", + "release_date": "2026-04-23", + "last_updated": "2026-04-23", "type": "chat" }, { - "id": "moonshot/kimi-k2.7-code", - "name": "Kimi K2.7 Code", - "display_name": "Kimi K2.7 Code", + "id": "openai/gpt-5.6-luna", + "name": "GPT-5.6 Luna", + "display_name": "GPT-5.6 Luna", "modalities": { "input": [ "text", "image", - "video" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 262000, - "output": 262000 + "context": 1050000, + "output": 128000 }, "temperature": false, "tool_call": true, @@ -142408,68 +141654,52 @@ }, "extra_capabilities": { "reasoning": { - "supported": true + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", + "effort_options": [ + "none", + "low", + "medium", + "high", + "xhigh", + "max" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, "attachment": true, - "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-06-12", - "last_updated": "2026-06-12", - "type": "chat" - }, - { - "id": "qwen/qwen3.7-max", - "name": "Qwen3.7 Max", - "display_name": "Qwen3.7 Max", - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 1000000, - "output": 65536 - }, - "temperature": true, - "tool_call": true, - "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true - } - }, - "attachment": false, "open_weights": false, - "release_date": "2026-05-21", - "last_updated": "2026-05-21", + "knowledge": "2026-02-16", + "release_date": "2026-07-09", + "last_updated": "2026-07-09", "type": "chat" }, { - "id": "qwen/qwen3.6-flash", - "name": "Qwen3.6 Flash", - "display_name": "Qwen3.6 Flash", + "id": "openai/o4-mini", + "name": "o4-mini", + "display_name": "o4-mini", "modalities": { "input": [ "text", - "image", - "video" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 65536 + "context": 200000, + "output": 100000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -142478,40 +141708,42 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] + "default_enabled": true, + "mode": "effort", + "effort": "medium", + "effort_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, "attachment": true, "open_weights": false, - "release_date": "2026-04-27", - "last_updated": "2026-04-27", + "knowledge": "2024-05", + "release_date": "2025-04-16", + "last_updated": "2025-04-16", "type": "chat" }, { - "id": "qwen/qwen3.8-omni-flash", - "name": "Qwen3.8 Omni Flash", - "display_name": "Qwen3.8 Omni Flash", + "id": "openai/gpt-5", + "name": "GPT-5", + "display_name": "GPT-5", "modalities": { "input": [ "text", - "image", - "audio", - "video" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 400000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -142519,38 +141751,50 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", "effort_options": [ + "minimal", "low", "medium", - "xhigh" - ] + "high" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, "attachment": true, "open_weights": false, - "release_date": "2026-09-17", - "last_updated": "2026-09-17", + "knowledge": "2024-09-30", + "release_date": "2025-08-07", + "last_updated": "2025-08-07", "type": "chat" }, { - "id": "qwen/qwen3.8-max", - "name": "Qwen3.8 Max", - "display_name": "Qwen3.8 Max", + "id": "openai/gpt-5-mini", + "name": "GPT-5 Mini", + "display_name": "GPT-5 Mini", "modalities": { "input": [ "text", - "image", - "video" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 400000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -142559,80 +141803,80 @@ "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", "effort_options": [ + "minimal", "low", "medium", - "xhigh" - ] + "high" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, "attachment": true, "open_weights": false, - "release_date": "2026-08-03", - "last_updated": "2026-08-03", + "knowledge": "2024-05-30", + "release_date": "2025-08-07", + "last_updated": "2025-08-07", "type": "chat" }, { - "id": "qwen/qwen3.6-plus", - "name": "Qwen3.6 Plus", - "display_name": "Qwen3.6 Plus", + "id": "openai/gpt-4o-mini", + "name": "GPT-4o mini", + "display_name": "GPT-4o mini", "modalities": { "input": [ "text", "image", - "video" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 65536 + "context": 128000, + "output": 16384 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } + "supported": false }, "attachment": true, "open_weights": false, - "knowledge": "2025-04", - "release_date": "2026-04-02", - "last_updated": "2026-04-02", + "knowledge": "2023-09", + "release_date": "2024-07-18", + "last_updated": "2024-07-18", "type": "chat" }, { - "id": "qwen/qwen3.8-flash", - "name": "Qwen3.8 Flash", - "display_name": "Qwen3.8 Flash", + "id": "openai/o1", + "name": "o1", + "display_name": "o1", "modalities": { "input": [ "text", "image", - "video" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 200000, + "output": 100000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -142641,103 +141885,58 @@ "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", "effort_options": [ "low", "medium", - "xhigh" - ] + "high" + ], + "visibility": "hidden" } }, "attachment": true, "open_weights": false, - "release_date": "2026-08-26", - "last_updated": "2026-08-26", + "knowledge": "2023-09", + "release_date": "2024-12-05", + "last_updated": "2024-12-05", "type": "chat" }, { - "id": "qwen/qwen3.7-flash", - "name": "Qwen3.7 Flash", - "display_name": "Qwen3.7 Flash", + "id": "openai/gpt-4o", + "name": "GPT-4o", + "display_name": "GPT-4o", "modalities": { "input": [ "text", "image", - "video" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 65536 + "context": 128000, + "output": 16384 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true - } + "supported": false }, "attachment": true, "open_weights": false, - "release_date": "2026-07-15", - "last_updated": "2026-07-15", + "knowledge": "2023-09", + "release_date": "2024-05-13", + "last_updated": "2024-08-06", "type": "chat" }, { - "id": "qwen/qwen3.7-plus", - "name": "Qwen3.7 Plus", - "display_name": "Qwen3.7 Plus", - "modalities": { - "input": [ - "text", - "image", - "video" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 1000000, - "output": 64000 - }, - "temperature": true, - "tool_call": true, - "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true - } - }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-04", - "release_date": "2026-06-02", - "last_updated": "2026-06-02", - "type": "chat" - } - ] - }, - "klokintegration": { - "id": "klokintegration", - "name": "klokintegration.se", - "display_name": "klokintegration.se", - "api": "https://api-gw.klok.ipaas.se/proxy/kloker-key/v1", - "doc": "https://klokintegration.se/docs/ai-api", - "models": [ - { - "id": "Kloker-Integration-Architect", - "name": "Kloker Integration Architect", - "display_name": "Kloker Integration Architect", + "id": "openai/gpt-3.5-turbo", + "name": "GPT-3.5-turbo", + "display_name": "GPT-3.5-turbo", "modalities": { "input": [ "text" @@ -142747,87 +141946,79 @@ ] }, "limit": { - "context": 200000, - "output": 50000 + "context": 16385, + "output": 4096 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { "supported": false }, "attachment": false, "open_weights": false, - "release_date": "2026-08-29", - "last_updated": "2026-08-29", + "knowledge": "2021-09-01", + "release_date": "2023-03-01", + "last_updated": "2023-11-06", "type": "chat" }, { - "id": "Kloker", - "name": "Kloker", - "display_name": "Kloker", + "id": "openai/gpt-5.6-sol", + "name": "GPT-5.6 Sol", + "display_name": "GPT-5.6 Sol", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 50000 + "context": 1050000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { - "supported": false - }, - "attachment": false, - "open_weights": false, - "release_date": "2026-08-29", - "last_updated": "2026-08-29", - "type": "chat" - }, - { - "id": "Kloker-Integration-Developer", - "name": "Kloker Integration Developer", - "display_name": "Kloker Integration Developer", - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 200000, - "output": 50000 + "supported": true, + "default": true }, - "temperature": true, - "tool_call": true, - "reasoning": { - "supported": false + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", + "effort_options": [ + "none", + "low", + "medium", + "high", + "xhigh", + "max" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" + } }, - "attachment": false, + "attachment": true, "open_weights": false, - "release_date": "2026-08-29", - "last_updated": "2026-08-29", + "knowledge": "2026-02-16", + "release_date": "2026-07-09", + "last_updated": "2026-07-09", "type": "chat" - } - ] - }, - "coralbricks": { - "id": "coralbricks", - "name": "CoralBricks", - "display_name": "CoralBricks", - "api": "https://inference.coralbricks.ai/v1", - "doc": "https://www.coralbricks.ai/docs", - "models": [ + }, { - "id": "deepseek-v4.1-flash-fast", - "name": "DeepSeek V4.1 Flash", - "display_name": "DeepSeek V4.1 Flash", + "id": "openai/gpt-5.1", + "name": "GPT-5.1", + "display_name": "GPT-5.1", "modalities": { "input": [ "text", @@ -142838,46 +142029,47 @@ ] }, "limit": { - "context": 1048576, - "output": 393216 + "context": 400000, + "output": 128000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, + "default_enabled": false, + "mode": "effort", + "effort": "none", "effort_options": [ "none", "low", - "high", - "max" - ] + "medium", + "high" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, "attachment": true, - "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-09-10", - "last_updated": "2026-09-10", + "open_weights": false, + "knowledge": "2024-09-30", + "release_date": "2025-11-13", + "last_updated": "2025-11-13", "type": "chat" - } - ] - }, - "sap-ai-core": { - "id": "sap-ai-core", - "name": "SAP AI Core", - "display_name": "SAP AI Core", - "doc": "https://help.sap.com/docs/sap-ai-core", - "models": [ + }, { - "id": "anthropic--claude-4.5-opus", - "name": "anthropic--claude-4.5-opus", - "display_name": "anthropic--claude-4.5-opus", + "id": "openai/gpt-5.5", + "name": "GPT-5.5", + "display_name": "GPT-5.5", "modalities": { "input": [ "text", @@ -142889,10 +142081,10 @@ ] }, "limit": { - "context": 200000, - "output": 64000 + "context": 1050000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -142900,24 +142092,36 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", "effort_options": [ + "low", + "medium", + "high", + "xhigh" + ], + "verbosity": "medium", + "verbosity_options": [ "low", "medium", "high" - ] + ], + "visibility": "hidden" } }, "attachment": true, "open_weights": false, - "knowledge": "2025-05", - "release_date": "2025-11-24", - "last_updated": "2025-11-24", + "knowledge": "2025-12-01", + "release_date": "2026-04-23", + "last_updated": "2026-04-23", "type": "chat" }, { - "id": "gpt-5", - "name": "gpt-5", - "display_name": "gpt-5", + "id": "openai/gpt-5.1-codex-mini", + "name": "GPT-5.1 Codex mini", + "display_name": "GPT-5.1 Codex mini", "modalities": { "input": [ "text", @@ -142935,16 +142139,16 @@ "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, + "default_enabled": false, "mode": "effort", - "effort": "medium", + "effort": "none", "effort_options": [ - "minimal", + "none", "low", "medium", "high" @@ -142961,24 +142165,25 @@ "attachment": true, "open_weights": false, "knowledge": "2024-09-30", - "release_date": "2025-08-07", - "last_updated": "2025-08-07", + "release_date": "2025-11-13", + "last_updated": "2025-11-13", "type": "chat" }, { - "id": "sap-abap-1", - "name": "sap-abap-1", - "display_name": "sap-abap-1", + "id": "openai/gpt-4-turbo", + "name": "GPT-4 Turbo", + "display_name": "GPT-4 Turbo", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 32768, + "context": 128000, "output": 4096 }, "temperature": true, @@ -142986,22 +142191,21 @@ "reasoning": { "supported": false }, - "attachment": false, + "attachment": true, "open_weights": false, - "release_date": "2025-11-26", - "last_updated": "2025-11-26", + "knowledge": "2023-12", + "release_date": "2023-11-06", + "last_updated": "2024-04-09", "type": "chat" }, { - "id": "gemini-2.5-pro", - "name": "gemini-2.5-pro", - "display_name": "gemini-2.5-pro", + "id": "openai/gpt-4.1-mini", + "name": "GPT-4.1 mini", + "display_name": "GPT-4.1 mini", "modalities": { "input": [ "text", "image", - "audio", - "video", "pdf" ], "output": [ @@ -143009,45 +142213,55 @@ ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 1047576, + "output": 32768 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true + "supported": false }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "budget", - "budget": { - "default": -1, - "min": 128, - "max": 32768, - "auto": -1, - "unit": "tokens" - }, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thought_signatures" - ] - } + "attachment": true, + "open_weights": false, + "knowledge": "2024-04", + "release_date": "2025-04-14", + "last_updated": "2025-04-14", + "type": "chat" + }, + { + "id": "openai/gpt-4.1", + "name": "GPT-4.1", + "display_name": "GPT-4.1", + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1047576, + "output": 32768 + }, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": false }, "attachment": true, "open_weights": false, - "knowledge": "2025-01", - "release_date": "2025-06-17", - "last_updated": "2025-06-17", + "knowledge": "2024-04", + "release_date": "2025-04-14", + "last_updated": "2025-04-14", "type": "chat" }, { - "id": "gpt-5.5", - "name": "gpt-5.5", - "display_name": "gpt-5.5", + "id": "openai/o3", + "name": "o3", + "display_name": "o3", "modalities": { "input": [ "text", @@ -143059,8 +142273,8 @@ ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 200000, + "output": 100000 }, "temperature": false, "tool_call": true, @@ -143075,13 +142289,6 @@ "mode": "effort", "effort": "medium", "effort_options": [ - "low", - "medium", - "high", - "xhigh" - ], - "verbosity": "medium", - "verbosity_options": [ "low", "medium", "high" @@ -143091,15 +142298,15 @@ }, "attachment": true, "open_weights": false, - "knowledge": "2025-12-01", - "release_date": "2026-04-23", - "last_updated": "2026-04-23", + "knowledge": "2024-05", + "release_date": "2025-04-16", + "last_updated": "2025-04-16", "type": "chat" }, { - "id": "gpt-4.1-nano", - "name": "gpt-4.1-nano", - "display_name": "gpt-4.1-nano", + "id": "thinkingmachines/inkling", + "name": "Inkling", + "display_name": "Inkling", "modalities": { "input": [ "text", @@ -143110,69 +142317,86 @@ ] }, "limit": { - "context": 1047576, - "output": 32768 + "context": 65536, + "output": 65536 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "none", + "minimal", + "low", + "medium", + "high", + "xhigh" + ] + } }, "attachment": true, - "open_weights": false, - "knowledge": "2024-04", - "release_date": "2025-04-14", - "last_updated": "2025-04-14", + "open_weights": true, + "release_date": "2026-07-15", + "last_updated": "2026-07-15", "type": "chat" }, { - "id": "sonar-pro", - "name": "sonar-pro", - "display_name": "sonar-pro", + "id": "xai/grok-build-0.1", + "name": "Grok Build 0.1", + "display_name": "Grok Build 0.1", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 8192 + "context": 256000, + "output": 256000 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true + } }, "attachment": true, "open_weights": false, - "knowledge": "2025-09-01", - "release_date": "2024-01-01", - "last_updated": "2025-09-01", + "release_date": "2026-04-16", + "last_updated": "2026-04-16", "type": "chat" }, { - "id": "gpt-6-astra", - "name": "GPT-6 Astra", - "display_name": "GPT-6 Astra", + "id": "xai/grok-4.5", + "name": "Grok 4.5", + "display_name": "Grok 4.5", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 500000, + "output": 500000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -143184,23 +142408,20 @@ "effort_options": [ "low", "medium", - "high", - "xhigh", - "max" + "high" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-04-30", - "release_date": "2026-09-04", - "last_updated": "2026-09-04", + "release_date": "2026-07-08", + "last_updated": "2026-07-08", "type": "chat" }, { - "id": "gpt-5.4", - "name": "gpt-5.4", - "display_name": "gpt-5.4", + "id": "xai/grok-4.20-0309-reasoning", + "name": "Grok 4.20 (Reasoning)", + "display_name": "Grok 4.20 (Reasoning)", "modalities": { "input": [ "text", @@ -143212,54 +142433,29 @@ ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 1000000, + "output": 30000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "none", - "effort_options": [ - "none", - "low", - "medium", - "high", - "xhigh" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" - } + "default": true }, "attachment": true, "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-03-05", - "last_updated": "2026-03-05", + "release_date": "2026-03-09", + "last_updated": "2026-03-09", "type": "chat" }, { - "id": "gemini-3.5-flash-lite", - "name": "Gemini 3.5 Flash Lite", - "display_name": "Gemini 3.5 Flash Lite", + "id": "xai/grok-4.3", + "name": "Grok 4.3", + "display_name": "Grok 4.3", "modalities": { "input": [ "text", "image", - "video", - "audio", "pdf" ], "output": [ @@ -143267,8 +142463,8 @@ ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 1000000, + "output": 30000 }, "temperature": true, "tool_call": true, @@ -143280,7 +142476,7 @@ "reasoning": { "supported": true, "effort_options": [ - "minimal", + "none", "low", "medium", "high" @@ -143289,43 +142485,78 @@ }, "attachment": true, "open_weights": false, - "knowledge": "2026-03", - "release_date": "2026-07-21", - "last_updated": "2026-07-21", + "release_date": "2026-04-17", + "last_updated": "2026-04-17", "type": "chat" }, { - "id": "gemini-embedding", - "name": "Gemini Embedding 001", - "display_name": "Gemini Embedding 001", + "id": "xai/grok-4.20-0309-non-reasoning", + "name": "Grok 4.20 (Non-Reasoning)", + "display_name": "Grok 4.20 (Non-Reasoning)", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 2048, - "output": 1 + "context": 1000000, + "output": 30000 }, - "temperature": false, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { "supported": false }, - "attachment": false, + "attachment": true, "open_weights": false, - "knowledge": "2025-05", - "release_date": "2025-05-20", - "last_updated": "2025-05-20", - "type": "embedding" + "release_date": "2026-03-09", + "last_updated": "2026-03-09", + "type": "chat" }, { - "id": "sonar-deep-research", - "name": "sonar-deep-research", - "display_name": "sonar-deep-research", + "id": "moonshotai/kimi-k3", + "name": "Kimi K3", + "display_name": "Kimi K3", + "modalities": { + "input": [ + "text", + "image", + "video" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1048576, + "output": 131072 + }, + "temperature": false, + "tool_call": true, + "reasoning": { + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true + } + }, + "attachment": true, + "open_weights": true, + "release_date": "2026-07-16", + "last_updated": "2026-07-16", + "type": "chat" + }, + { + "id": "fireworks/gpt-oss-120b", + "name": "GPT OSS 120B", + "display_name": "GPT OSS 120B", "modalities": { "input": [ "text" @@ -143335,19 +142566,19 @@ ] }, "limit": { - "context": 128000, + "context": 131072, "output": 32768 }, - "temperature": false, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { "supported": true, "default": true }, "extra_capabilities": { "reasoning": { + "supported": true, "effort_options": [ - "minimal", "low", "medium", "high" @@ -143355,16 +142586,16 @@ } }, "attachment": false, - "open_weights": false, - "knowledge": "2025-01", - "release_date": "2025-02-01", - "last_updated": "2025-09-01", + "open_weights": true, + "knowledge": "2024-06", + "release_date": "2025-08-05", + "last_updated": "2025-08-05", "type": "chat" }, { - "id": "nvidia--llama-3.2-nv-embedqa-1b", - "name": "nvidia--llama-3.2-nv-embedqa-1b", - "display_name": "nvidia--llama-3.2-nv-embedqa-1b", + "id": "fireworks/glm-5.2", + "name": "GLM-5.2", + "display_name": "GLM-5.2", "modalities": { "input": [ "text" @@ -143374,37 +142605,41 @@ ] }, "limit": { - "context": 8192, - "output": 4096 + "context": 1000000, + "output": 131072 }, - "temperature": false, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true + } }, "attachment": false, - "open_weights": false, - "release_date": "2024-09-25", - "last_updated": "2024-09-25", + "open_weights": true, + "release_date": "2026-06-13", + "last_updated": "2026-06-13", "type": "chat" }, { - "id": "anthropic--claude-4.6-opus", - "name": "anthropic--claude-4.6-opus", - "display_name": "anthropic--claude-4.6-opus", + "id": "fireworks/gpt-oss-20b", + "name": "GPT OSS 20B", + "display_name": "GPT OSS 20B", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 131072, + "output": 32768 }, "temperature": true, "tool_call": true, @@ -143414,59 +142649,83 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, "effort_options": [ "low", "medium", - "high", - "max" + "high" ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-05-31", - "release_date": "2026-02-05", - "last_updated": "2026-03-13", + "attachment": false, + "open_weights": true, + "knowledge": "2024-06", + "release_date": "2025-08-05", + "last_updated": "2025-08-05", "type": "chat" }, { - "id": "amazon--nova-pro", - "name": "amazon--nova-pro", - "display_name": "amazon--nova-pro", + "id": "google/gemini-3.5-flash", + "name": "Gemini 3.5 Flash", + "display_name": "Gemini 3.5 Flash", "modalities": { "input": [ "text", "image", - "video" + "video", + "audio", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 300000, - "output": 8192 + "context": 1048576, + "output": 65536 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "level", + "level": "high", + "level_options": [ + "minimal", + "low", + "medium", + "high" + ], + "summaries": true, + "visibility": "summary", + "continuation": [ + "thought_signatures" + ] + } }, "attachment": true, "open_weights": false, - "knowledge": "2024-10", - "release_date": "2024-12-03", - "last_updated": "2024-12-03", + "knowledge": "2025-01", + "release_date": "2026-05-19", + "last_updated": "2026-05-19", "type": "chat" }, { - "id": "anthropic--claude-4.7-opus", - "name": "anthropic--claude-4.7-opus", - "display_name": "anthropic--claude-4.7-opus", + "id": "google/gemini-3.6-flash", + "name": "Gemini 3.6 Flash", + "display_name": "Gemini 3.6 Flash", "modalities": { "input": [ "text", "image", + "video", + "audio", "pdf" ], "output": [ @@ -143474,10 +142733,10 @@ ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 1048576, + "output": 65536 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -143485,39 +142744,49 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ + "supported": true, + "default_enabled": true, + "mode": "level", + "level": "high", + "level_options": [ + "minimal", "low", "medium", - "high", - "xhigh", - "max" + "high" + ], + "summaries": true, + "visibility": "summary", + "continuation": [ + "thought_signatures" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-01-31", - "release_date": "2026-04-16", - "last_updated": "2026-04-16", + "knowledge": "2026-03", + "release_date": "2026-07-21", + "last_updated": "2026-07-21", "type": "chat" }, { - "id": "amazon--nova-lite", - "name": "amazon--nova-lite", - "display_name": "amazon--nova-lite", + "id": "google/gemini-2.5-pro", + "name": "Gemini 2.5 Pro", + "display_name": "Gemini 2.5 Pro", "modalities": { "input": [ "text", "image", - "video" + "audio", + "video", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 64000 + "context": 1048576, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -143527,24 +142796,34 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "low", - "medium", - "high" + "supported": true, + "default_enabled": true, + "mode": "budget", + "budget": { + "default": -1, + "min": 128, + "max": 32768, + "auto": -1, + "unit": "tokens" + }, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thought_signatures" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-10", - "release_date": "2025-12-02", - "last_updated": "2025-12-01", + "knowledge": "2025-01", + "release_date": "2025-06-17", + "last_updated": "2025-06-17", "type": "chat" }, { - "id": "gemini-2.5-flash", - "name": "gemini-2.5-flash", - "display_name": "gemini-2.5-flash", + "id": "google/gemini-2.5-flash-lite", + "name": "Gemini 2.5 Flash-Lite", + "display_name": "Gemini 2.5 Flash-Lite", "modalities": { "input": [ "text", @@ -143565,19 +142844,18 @@ "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, + "default_enabled": false, "mode": "budget", "budget": { "default": -1, - "min": 0, + "min": 512, "max": 24576, "auto": -1, - "off": 0, "unit": "tokens" }, "summaries": true, @@ -143595,36 +142873,62 @@ "type": "chat" }, { - "id": "text-embedding-3-small", - "name": "text-embedding-3-small", - "display_name": "text-embedding-3-small", + "id": "google/gemini-2.5-flash", + "name": "Gemini 2.5 Flash", + "display_name": "Gemini 2.5 Flash", "modalities": { "input": [ - "text" + "text", + "image", + "audio", + "video", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 8191, - "output": 1536 + "context": 1048576, + "output": 65536 }, - "temperature": false, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "budget", + "budget": { + "default": -1, + "min": 0, + "max": 24576, + "auto": -1, + "off": 0, + "unit": "tokens" + }, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thought_signatures" + ] + } + }, + "attachment": true, "open_weights": false, - "release_date": "2024-01-25", - "last_updated": "2024-01-25", - "type": "embedding" + "knowledge": "2025-01", + "release_date": "2025-06-17", + "last_updated": "2025-06-17", + "type": "chat" }, { - "id": "gemini-3.1-flash-lite", - "name": "gemini-3.1-flash-lite", - "display_name": "gemini-3.1-flash-lite", + "id": "google/gemini-3.1-pro-preview", + "name": "Gemini 3.1 Pro Preview", + "display_name": "Gemini 3.1 Pro Preview", "modalities": { "input": [ "text", @@ -143650,29 +142954,37 @@ "extra_capabilities": { "reasoning": { "supported": true, - "effort_options": [ - "minimal", + "default_enabled": true, + "mode": "level", + "level": "high", + "level_options": [ "low", - "medium", "high" + ], + "summaries": true, + "visibility": "summary", + "continuation": [ + "thought_signatures" ] } }, "attachment": true, "open_weights": false, "knowledge": "2025-01", - "release_date": "2026-05-07", - "last_updated": "2026-05-07", + "release_date": "2026-02-19", + "last_updated": "2026-02-19", "type": "chat" }, { - "id": "anthropic--claude-4.6-sonnet", - "name": "anthropic--claude-4.6-sonnet", - "display_name": "anthropic--claude-4.6-sonnet", + "id": "google/gemini-3.1-flash-lite", + "name": "Gemini 3.1 Flash Lite", + "display_name": "Gemini 3.1 Flash Lite", "modalities": { "input": [ "text", "image", + "video", + "audio", "pdf" ], "output": [ @@ -143680,8 +142992,8 @@ ] }, "limit": { - "context": 1000000, - "output": 64000 + "context": 1048576, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -143691,29 +143003,32 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, "effort_options": [ + "minimal", "low", "medium", - "high", - "max" + "high" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-02-17", - "last_updated": "2026-03-13", + "knowledge": "2025-01", + "release_date": "2026-05-07", + "last_updated": "2026-05-07", "type": "chat" }, { - "id": "gpt-5.6-sol", - "name": "gpt-5.6-sol", - "display_name": "gpt-5.6-sol", + "id": "google/gemini-3.5-flash-lite", + "name": "Gemini 3.5 Flash Lite", + "display_name": "Gemini 3.5 Flash Lite", "modalities": { "input": [ "text", "image", + "video", + "audio", "pdf" ], "output": [ @@ -143721,10 +143036,10 @@ ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 1048576, + "output": 65536 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -143733,52 +143048,38 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", "effort_options": [ - "none", - "low", - "medium", - "high", - "xhigh", - "max" - ], - "verbosity": "medium", - "verbosity_options": [ + "minimal", "low", "medium", "high" - ], - "visibility": "hidden" + ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-02-16", - "release_date": "2026-07-09", - "last_updated": "2026-07-09", + "knowledge": "2026-03", + "release_date": "2026-07-21", + "last_updated": "2026-07-21", "type": "chat" }, { - "id": "gpt-5.6-terra", - "name": "gpt-5.6-terra", - "display_name": "gpt-5.6-terra", + "id": "groq/gpt-oss-120b", + "name": "GPT OSS 120B", + "display_name": "GPT OSS 120B", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 131072, + "output": 32768 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -143787,37 +143088,24 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", "effort_options": [ - "none", - "low", - "medium", - "high", - "xhigh", - "max" - ], - "verbosity": "medium", - "verbosity_options": [ "low", "medium", "high" - ], - "visibility": "hidden" + ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-02-16", - "release_date": "2026-07-09", - "last_updated": "2026-07-09", + "attachment": false, + "open_weights": true, + "knowledge": "2024-06", + "release_date": "2025-08-05", + "last_updated": "2025-08-05", "type": "chat" }, { - "id": "amazon--titan-embed-text", - "name": "amazon--titan-embed-text", - "display_name": "amazon--titan-embed-text", + "id": "groq/gpt-oss-20b", + "name": "GPT OSS 20B", + "display_name": "GPT OSS 20B", "modalities": { "input": [ "text" @@ -143827,51 +143115,93 @@ ] }, "limit": { - "context": 8192, - "output": 1536 + "context": 131072, + "output": 32768 }, - "temperature": false, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "effort_options": [ + "low", + "medium", + "high" + ] + } }, "attachment": false, - "open_weights": false, - "release_date": "2024-04-30", - "last_updated": "2024-04-30", + "open_weights": true, + "knowledge": "2024-06", + "release_date": "2025-08-05", + "last_updated": "2025-08-05", "type": "chat" }, { - "id": "text-embedding-3-large", - "name": "text-embedding-3-large", - "display_name": "text-embedding-3-large", + "id": "anthropic/claude-opus-4-7", + "name": "Claude Opus 4.7", + "display_name": "Claude Opus 4.7", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 8191, - "output": 3072 + "context": 1000000, + "output": 128000 }, "temperature": false, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": false }, - "attachment": false, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": false, + "mode": "effort", + "effort": "high", + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "omitted", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Claude Opus 4.7 and newer Opus models require thinking.type = \"adaptive\" to enable thinking explicitly.", + "Manual budget_tokens requests return 400 on Claude Opus 4.7 and newer adaptive-only Opus models.", + "task_budget is separate from thinking control and should not be treated as a thinking budget." + ] + } + }, + "attachment": true, "open_weights": false, - "release_date": "2024-01-25", - "last_updated": "2024-01-25", - "type": "embedding" + "knowledge": "2026-01-31", + "release_date": "2026-04-16", + "last_updated": "2026-04-16", + "type": "chat" }, { - "id": "anthropic--claude-3-haiku", - "name": "anthropic--claude-3-haiku", - "display_name": "anthropic--claude-3-haiku", + "id": "anthropic/claude-haiku-4-5", + "name": "Claude Haiku 4.5 (latest)", + "display_name": "Claude Haiku 4.5 (latest)", "modalities": { "input": [ "text", @@ -143884,24 +143214,46 @@ }, "limit": { "context": 200000, - "output": 4096 + "output": 64000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": false + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": false, + "mode": "budget", + "budget": { + "min": 1024, + "unit": "tokens" + }, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", + "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." + ] + } }, "attachment": true, "open_weights": false, - "knowledge": "2023-08-31", - "release_date": "2024-03-13", - "last_updated": "2024-03-13", + "knowledge": "2025-02-28", + "release_date": "2025-10-15", + "last_updated": "2025-10-15", "type": "chat" }, { - "id": "gpt-6-luna", - "name": "GPT-6 Luna", - "display_name": "GPT-6 Luna", + "id": "anthropic/claude-sonnet-5", + "name": "Claude Sonnet 5", + "display_name": "Claude Sonnet 5", "modalities": { "input": [ "text", @@ -143913,7 +143265,7 @@ ] }, "limit": { - "context": 1050000, + "context": 1000000, "output": 128000 }, "temperature": false, @@ -143924,8 +143276,8 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, "effort_options": [ - "none", "low", "medium", "high", @@ -143936,15 +143288,15 @@ }, "attachment": true, "open_weights": false, - "knowledge": "2026-05-18", - "release_date": "2026-09-22", - "last_updated": "2026-09-22", + "knowledge": "2026-01-31", + "release_date": "2026-06-30", + "last_updated": "2026-06-30", "type": "chat" }, { - "id": "anthropic--claude-4.5-haiku", - "name": "anthropic--claude-4.5-haiku", - "display_name": "anthropic--claude-4.5-haiku", + "id": "anthropic/claude-fable-5", + "name": "Claude Fable 5", + "display_name": "Claude Fable 5", "modalities": { "input": [ "text", @@ -143956,83 +143308,108 @@ ] }, "limit": { - "context": 200000, - "output": 64000 + "context": 1000000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, "default": true }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "high", + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "omitted", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Adaptive thinking is always on for Claude Fable 5 and Claude Mythos 5; thinking.type = \"disabled\" is rejected.", + "Manual budget_tokens requests return 400 on Claude Fable 5 and Claude Mythos 5.", + "thinking.display defaults to omitted; set display to summarized to receive readable thinking summaries." + ] + } + }, "attachment": true, "open_weights": false, - "knowledge": "2025-02-28", - "release_date": "2025-10-15", - "last_updated": "2025-10-15", + "knowledge": "2026-01-31", + "release_date": "2026-06-09", + "last_updated": "2026-06-09", "type": "chat" }, { - "id": "gpt-5-mini", - "name": "gpt-5-mini", - "display_name": "gpt-5-mini", + "id": "anthropic/claude-sonnet-4-5", + "name": "Claude Sonnet 4.5 (latest)", + "display_name": "Claude Sonnet 4.5 (latest)", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 200000, + "output": 64000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", - "effort_options": [ - "minimal", - "low", - "medium", - "high" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" + "default_enabled": false, + "mode": "budget", + "budget": { + "min": 1024, + "unit": "tokens" + }, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ], - "visibility": "hidden" + "notes": [ + "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", + "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." + ] } }, "attachment": true, "open_weights": false, - "knowledge": "2024-05-30", - "release_date": "2025-08-07", - "last_updated": "2025-08-07", + "knowledge": "2025-07-31", + "release_date": "2025-09-29", + "last_updated": "2025-09-29", "type": "chat" }, { - "id": "gemini-embedding-2", - "name": "Gemini Embedding 2", - "display_name": "Gemini Embedding 2", + "id": "anthropic/claude-opus-4-5", + "name": "Claude Opus 4.5 (latest)", + "display_name": "Claude Opus 4.5 (latest)", "modalities": { "input": [ "text", "image", - "audio", - "video", "pdf" ], "output": [ @@ -144040,25 +143417,53 @@ ] }, "limit": { - "context": 8192, - "output": 3072 + "context": 200000, + "output": 64000 }, - "temperature": false, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": false + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": false, + "mode": "mixed", + "budget": { + "min": 1024, + "unit": "tokens" + }, + "effort": "high", + "effort_options": [ + "low", + "medium", + "high" + ], + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Claude Opus 4.5 uses manual thinking.type = \"enabled\" with budget_tokens; effort can be used alongside the thinking budget.", + "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header." + ] + } }, "attachment": true, "open_weights": false, - "knowledge": "2025-11", - "release_date": "2026-04-22", - "last_updated": "2026-04-22", - "type": "embedding" + "knowledge": "2025-05", + "release_date": "2025-11-24", + "last_updated": "2025-11-24", + "type": "chat" }, { - "id": "gpt-5.6-luna", - "name": "gpt-5.6-luna", - "display_name": "gpt-5.6-luna", + "id": "anthropic/claude-sonnet-4-6", + "name": "Claude Sonnet 4.6", + "display_name": "Claude Sonnet 4.6", "modalities": { "input": [ "text", @@ -144070,49 +143475,54 @@ ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 1000000, + "output": 64000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", + "default_enabled": false, + "mode": "mixed", + "budget": { + "min": 1024, + "unit": "tokens" + }, + "effort": "high", "effort_options": [ - "none", "low", "medium", "high", - "xhigh", "max" ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ], - "visibility": "hidden" + "notes": [ + "Anthropic recommends adaptive thinking with effort for Claude 4.6; budget_tokens remains a deprecated compatibility path.", + "Anthropic API defaults effort to high; lower effort levels should be chosen per workload." + ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-02-16", - "release_date": "2026-07-09", - "last_updated": "2026-07-09", + "knowledge": "2025-08-31", + "release_date": "2026-02-17", + "last_updated": "2026-03-13", "type": "chat" }, { - "id": "anthropic--claude-4-opus", - "name": "anthropic--claude-4-opus", - "display_name": "anthropic--claude-4-opus", + "id": "anthropic/claude-opus-4-8", + "name": "Claude Opus 4.8", + "display_name": "Claude Opus 4.8", "modalities": { "input": [ "text", @@ -144124,54 +143534,111 @@ ] }, "limit": { - "context": 200000, - "output": 32000 + "context": 1000000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": false, + "mode": "effort", + "effort": "high", + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "omitted", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Claude Opus 4.7 and newer Opus models require thinking.type = \"adaptive\" to enable thinking explicitly.", + "Manual budget_tokens requests return 400 on Claude Opus 4.7 and newer adaptive-only Opus models.", + "task_budget is separate from thinking control and should not be treated as a thinking budget." + ] + } }, "attachment": true, "open_weights": false, - "knowledge": "2025-03-31", - "release_date": "2025-05-22", - "last_updated": "2025-05-22", + "knowledge": "2026-01", + "release_date": "2026-05-28", + "last_updated": "2026-05-28", "type": "chat" }, { - "id": "sonar", - "name": "sonar", - "display_name": "sonar", + "id": "anthropic/claude-opus-4-6", + "name": "Claude Opus 4.6", + "display_name": "Claude Opus 4.6", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 4096 + "context": 1000000, + "output": 128000 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": false }, - "attachment": false, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": false, + "mode": "mixed", + "budget": { + "min": 1024, + "unit": "tokens" + }, + "effort": "high", + "effort_options": [ + "low", + "medium", + "high", + "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Anthropic recommends adaptive thinking with effort for Claude 4.6; budget_tokens remains a deprecated compatibility path.", + "Anthropic API defaults effort to high; lower effort levels should be chosen per workload." + ] + } + }, + "attachment": true, "open_weights": false, - "knowledge": "2025-09-01", - "release_date": "2024-01-01", - "last_updated": "2025-09-01", + "knowledge": "2025-05-31", + "release_date": "2026-02-05", + "last_updated": "2026-03-13", "type": "chat" }, { - "id": "nvidia--llama-nemotron-embed-1b", - "name": "Llama Nemotron Embed 1B v2", - "display_name": "Llama Nemotron Embed 1B v2", + "id": "cerebras/gpt-oss-120b", + "name": "GPT OSS 120B", + "display_name": "GPT OSS 120B", "modalities": { "input": [ "text" @@ -144181,106 +143648,75 @@ ] }, "limit": { - "context": 8192, - "output": 2048 - }, - "temperature": false, - "tool_call": false, - "reasoning": { - "supported": false - }, - "attachment": false, - "open_weights": true, - "release_date": "2025-10-16", - "last_updated": "2025-10-16", - "type": "embedding" - }, - { - "id": "gemini-2.5-flash-lite", - "name": "gemini-2.5-flash-lite", - "display_name": "gemini-2.5-flash-lite", - "modalities": { - "input": [ - "text", - "image", - "audio", - "video", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 1048576, - "output": 65536 + "context": 131072, + "output": 32768 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "budget", - "budget": { - "default": -1, - "min": 512, - "max": 24576, - "auto": -1, - "unit": "tokens" - }, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thought_signatures" + "effort_options": [ + "low", + "medium", + "high" ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-01", - "release_date": "2025-06-17", - "last_updated": "2025-06-17", + "attachment": false, + "open_weights": true, + "knowledge": "2024-06", + "release_date": "2025-08-05", + "last_updated": "2025-08-05", "type": "chat" }, { - "id": "gpt-4.1-mini", - "name": "gpt-4.1-mini", - "display_name": "gpt-4.1-mini", + "id": "qwen/qwen3.8-max-preview", + "name": "Qwen3.8 Max Preview", + "display_name": "Qwen3.8 Max Preview", "modalities": { "input": [ "text", "image", - "pdf" + "video" ], "output": [ "text" ] }, "limit": { - "context": 1047576, - "output": 32768 + "context": 1000000, + "output": 131072 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "low", + "medium", + "xhigh" + ] + } }, "attachment": true, "open_weights": false, - "knowledge": "2024-04", - "release_date": "2025-04-14", - "last_updated": "2025-04-14", + "release_date": "2026-07-19", + "last_updated": "2026-07-19", "type": "chat" }, { - "id": "cohere--command-a-reasoning", - "name": "cohere--command-a-reasoning", - "display_name": "cohere--command-a-reasoning", + "id": "qwen/qwen3.7-max", + "name": "Qwen3.7 Max", + "display_name": "Qwen3.7 Max", "modalities": { "input": [ "text" @@ -144290,8 +143726,8 @@ ] }, "limit": { - "context": 256000, - "output": 32000 + "context": 1000000, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -144301,40 +143737,31 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "none", - "minimal", - "low", - "medium", - "high" - ] + "supported": true } }, "attachment": false, - "open_weights": true, - "knowledge": "2024-06-01", - "release_date": "2025-08-21", - "last_updated": "2025-08-21", + "open_weights": false, + "release_date": "2026-05-21", + "last_updated": "2026-05-21", "type": "chat" }, { - "id": "gemini-3.8-flash", - "name": "Gemini 3.8 Flash", - "display_name": "Gemini 3.8 Flash", + "id": "qwen/qwen3.6-flash", + "name": "Qwen3.6 Flash", + "display_name": "Qwen3.6 Flash", "modalities": { "input": [ "text", "image", - "video", - "audio", - "pdf" + "video" ], "output": [ "text" ] }, "limit": { - "context": 1048576, + "context": 1000000, "output": 65536 }, "temperature": true, @@ -144346,44 +143773,36 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "level", - "level": "high", - "level_options": [ - "minimal", - "low", - "medium", - "high" - ], + "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ - "thought_signatures" + "thinking_blocks" ] } }, "attachment": true, "open_weights": false, - "release_date": "2026-09-02", - "last_updated": "2026-09-02", + "release_date": "2026-04-27", + "last_updated": "2026-04-27", "type": "chat" }, { - "id": "anthropic--claude-4.5-sonnet", - "name": "anthropic--claude-4.5-sonnet", - "display_name": "anthropic--claude-4.5-sonnet", + "id": "qwen/qwen3.7-plus", + "name": "Qwen3.7 Plus", + "display_name": "Qwen3.7 Plus", "modalities": { "input": [ "text", "image", - "pdf" + "video" ], "output": [ "text" ] }, "limit": { - "context": 200000, + "context": 1000000, "output": 64000 }, "temperature": true, @@ -144392,22 +143811,25 @@ "supported": true, "default": true }, + "extra_capabilities": { + "reasoning": { + "supported": true + } + }, "attachment": true, "open_weights": false, - "knowledge": "2025-07-31", - "release_date": "2025-09-29", - "last_updated": "2025-09-29", + "knowledge": "2025-04", + "release_date": "2026-06-02", + "last_updated": "2026-06-02", "type": "chat" }, { - "id": "anthropic--claude-4.8-opus", - "name": "anthropic--claude-4.8-opus", - "display_name": "anthropic--claude-4.8-opus", + "id": "deepseek/deepseek-v4-pro", + "name": "DeepSeek V4 Pro", + "display_name": "DeepSeek V4 Pro", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" @@ -144415,9 +143837,9 @@ }, "limit": { "context": 1000000, - "output": 128000 + "output": 384000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -144425,41 +143847,48 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "high", "effort_options": [ "low", - "medium", "high", - "xhigh", "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-01", - "release_date": "2026-05-28", - "last_updated": "2026-05-28", + "attachment": false, + "open_weights": true, + "knowledge": "2025-05", + "release_date": "2026-04-24", + "last_updated": "2026-04-24", "type": "chat" }, { - "id": "gemini-3.6-flash", - "name": "Gemini 3.6 Flash", - "display_name": "Gemini 3.6 Flash", + "id": "deepseek/deepseek-v4-flash", + "name": "DeepSeek V4 Flash", + "display_name": "DeepSeek V4 Flash", "modalities": { "input": [ - "text", - "image", - "video", - "audio", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 1000000, + "output": 384000 }, "temperature": true, "tool_call": true, @@ -144471,77 +143900,46 @@ "reasoning": { "supported": true, "default_enabled": true, - "mode": "level", - "level": "high", - "level_options": [ - "minimal", + "mode": "effort", + "effort": "high", + "effort_options": [ "low", - "medium", - "high" + "high", + "max" ], + "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ - "thought_signatures" + "thinking_blocks" + ], + "notes": [ + "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-03", - "release_date": "2026-07-21", - "last_updated": "2026-07-21", + "attachment": false, + "open_weights": true, + "knowledge": "2025-05", + "release_date": "2026-04-24", + "last_updated": "2026-04-24", "type": "chat" }, { - "id": "gpt-4.1", - "name": "gpt-4.1", - "display_name": "gpt-4.1", + "id": "zai/glm-4.7", + "name": "GLM-4.7", + "display_name": "GLM-4.7", "modalities": { "input": [ - "text", - "image", - "pdf" - ], - "output": [ "text" - ] - }, - "limit": { - "context": 1047576, - "output": 32768 - }, - "temperature": true, - "tool_call": true, - "reasoning": { - "supported": false - }, - "attachment": true, - "open_weights": false, - "knowledge": "2024-04", - "release_date": "2025-04-14", - "last_updated": "2025-04-14", - "type": "chat" - }, - { - "id": "gemini-3.5-flash", - "name": "gemini-3.5-flash", - "display_name": "gemini-3.5-flash", - "modalities": { - "input": [ - "text", - "image", - "video", - "audio", - "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 204800, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -144552,45 +143950,36 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "level", - "level": "high", - "level_options": [ - "minimal", - "low", - "medium", - "high" - ], + "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ - "thought_signatures" + "thinking_blocks" ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-01", - "release_date": "2026-05-19", - "last_updated": "2026-05-19", + "attachment": false, + "open_weights": true, + "knowledge": "2025-04", + "release_date": "2025-12-22", + "last_updated": "2025-12-22", "type": "chat" }, { - "id": "mistralai--mistral-medium", - "name": "Mistral Medium 3.5", - "display_name": "Mistral Medium 3.5", + "id": "zai/glm-4.6", + "name": "GLM-4.6", + "display_name": "GLM-4.6", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 204800, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -144600,118 +143989,101 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "none", - "high" - ] + "supported": true } }, - "attachment": true, + "attachment": false, "open_weights": true, - "release_date": "2026-04-29", - "last_updated": "2026-04-29", + "knowledge": "2025-04", + "release_date": "2025-09-30", + "last_updated": "2025-09-30", "type": "chat" }, { - "id": "mistralai--mistral-medium-instruct", - "name": "mistralai--mistral-medium-instruct", - "display_name": "mistralai--mistral-medium-instruct", + "id": "zai/glm-4.5-air", + "name": "GLM-4.5-Air", + "display_name": "GLM-4.5-Air", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 128000 + "context": 131072, + "output": 98304 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-05", - "release_date": "2025-05-07", - "last_updated": "2025-05-07", + "attachment": false, + "open_weights": true, + "knowledge": "2025-04", + "release_date": "2025-07-28", + "last_updated": "2025-07-28", "type": "chat" }, { - "id": "gpt-5.2", - "name": "gpt-5.2", - "display_name": "gpt-5.2", + "id": "zai/glm-5", + "name": "GLM-5", + "display_name": "GLM-5", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 204800, + "output": 131072 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "none", - "effort_options": [ - "none", - "low", - "medium", - "high", - "xhigh" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2025-12-11", - "last_updated": "2025-12-11", + "attachment": false, + "open_weights": true, + "release_date": "2026-02-12", + "last_updated": "2026-02-12", "type": "chat" }, { - "id": "gpt-6.1-sol", - "name": "GPT-6.1 Sol", - "display_name": "GPT-6.1 Sol", + "id": "zai/glm-5.1", + "name": "GLM-5.1", + "display_name": "GLM-5.1", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 200000, + "output": 131072 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -144719,39 +144091,30 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" - ] + "supported": true } }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-04-30", - "release_date": "2026-09-29", - "last_updated": "2026-09-29", + "attachment": false, + "open_weights": true, + "release_date": "2026-04-07", + "last_updated": "2026-04-07", "type": "chat" }, { - "id": "anthropic--claude-4-sonnet", - "name": "anthropic--claude-4-sonnet", - "display_name": "anthropic--claude-4-sonnet", + "id": "zai/glm-5.2", + "name": "GLM-5.2", + "display_name": "GLM-5.2", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 64000 + "context": 1000000, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -144759,31 +144122,34 @@ "supported": true, "default": true }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-03-31", - "release_date": "2025-05-22", - "last_updated": "2025-05-22", + "extra_capabilities": { + "reasoning": { + "supported": true + } + }, + "attachment": false, + "open_weights": true, + "release_date": "2026-06-13", + "last_updated": "2026-06-13", "type": "chat" }, { - "id": "gpt-5-nano", - "name": "gpt-5-nano", - "display_name": "gpt-5-nano", + "id": "zai/glm-5-turbo", + "name": "GLM-5-Turbo", + "display_name": "GLM-5-Turbo", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 200000, + "output": 131072 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -144791,48 +144157,30 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", - "effort_options": [ - "minimal", - "low", - "medium", - "high" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + "supported": true } }, - "attachment": true, + "attachment": false, "open_weights": false, - "knowledge": "2024-05-30", - "release_date": "2025-08-07", - "last_updated": "2025-08-07", + "release_date": "2026-03-16", + "last_updated": "2026-03-16", "type": "chat" }, { - "id": "mistralai--mistral-small", - "name": "mistralai--mistral-small", - "display_name": "mistralai--mistral-small", + "id": "zai/glm-4.5", + "name": "GLM-4.5", + "display_name": "GLM-4.5", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 128000 + "context": 131072, + "output": 98304 }, "temperature": true, "tool_call": true, @@ -144840,74 +144188,71 @@ "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "none", - "high" - ] - } - }, - "attachment": true, + "attachment": false, "open_weights": true, - "knowledge": "2025-06", - "release_date": "2026-03-16", - "last_updated": "2026-03-16", + "knowledge": "2025-04", + "release_date": "2025-07-28", + "last_updated": "2025-07-28", "type": "chat" }, { - "id": "amazon--nova-micro", - "name": "amazon--nova-micro", - "display_name": "amazon--nova-micro", + "id": "xiaomi/mimo-v2.5", + "name": "MiMo-V2.5", + "display_name": "MiMo-V2.5", "modalities": { "input": [ - "text" + "text", + "image", + "audio", + "video" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 8192 + "context": 1048576, + "output": 131072 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, - "open_weights": false, - "knowledge": "2024-10", - "release_date": "2024-12-03", - "last_updated": "2024-12-03", + "attachment": true, + "open_weights": true, + "knowledge": "2024-12", + "release_date": "2026-04-22", + "last_updated": "2026-04-22", "type": "chat" } ] }, - "cortecs": { - "id": "cortecs", - "name": "Cortecs", - "display_name": "Cortecs", - "api": "https://api.cortecs.ai/v1", - "doc": "https://api.cortecs.ai/v1/models", + "wallaby": { + "id": "wallaby", + "name": "Wallaby", + "display_name": "Wallaby", + "api": "https://api.wallabytoken.com/v1", + "doc": "https://wallabytoken.com/docs", "models": [ { - "id": "gpt-5", - "name": "GPT-5", - "display_name": "GPT-5", + "id": "moonshotai/kimi-k3", + "name": "Kimi K3", + "display_name": "Kimi K3", "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 1048576, + "output": 131072 }, "temperature": false, "tool_call": true, @@ -144918,80 +144263,107 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", - "effort_options": [ - "minimal", - "low", - "medium", - "high" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ], - "verbosity": "medium", - "verbosity_options": [ + "effort_options": [ "low", "medium", - "high" - ], - "visibility": "hidden" + "high", + "max" + ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2024-09-30", - "release_date": "2025-08-07", - "last_updated": "2025-08-07", + "open_weights": true, + "release_date": "2026-07-16", + "last_updated": "2026-07-16", "type": "chat" - }, + } + ] + }, + "tokenrouter": { + "id": "tokenrouter", + "name": "TokenRouter", + "display_name": "TokenRouter", + "api": "https://api.tokenrouter.com/v1", + "doc": "https://www.tokenrouter.com/docs/tokenrouter-feature-guide/", + "models": [ { - "id": "gpt-4o-mini", - "name": "GPT-4o mini", - "display_name": "GPT-4o mini", + "id": "z-ai/glm-5.3-free", + "name": "GLM-5.3 (free)", + "display_name": "GLM-5.3 (free)", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 16000 + "context": 1000000, + "output": 131072 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": true, - "open_weights": false, - "knowledge": "2023-09", - "release_date": "2024-07-18", - "last_updated": "2024-07-18", + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "effort_options": [ + "low", + "high", + "max" + ] + } + }, + "attachment": false, + "open_weights": true, + "release_date": "2026-08-14", + "last_updated": "2026-08-14", "type": "chat" - }, + } + ] + }, + "kenari": { + "id": "kenari", + "name": "Kenari", + "display_name": "Kenari", + "api": "https://kenari.id/v1", + "doc": "https://kenari.id/docs", + "models": [ { - "id": "claude-opus4-5", - "name": "Claude Opus 4.5 (latest)", - "display_name": "Claude Opus 4.5 (latest)", + "id": "kimi-k3", + "name": "Kimi K3", + "display_name": "Kimi K3", "modalities": { "input": [ "text", "image", - "pdf" + "video" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 64000 + "context": 1048576, + "output": 131072 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -144999,24 +144371,24 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, "effort_options": [ "low", - "medium", - "high" + "high", + "max" ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2025-05", - "release_date": "2025-11-24", - "last_updated": "2025-11-24", + "open_weights": true, + "release_date": "2026-07-16", + "last_updated": "2026-07-16", "type": "chat" }, { - "id": "nova-2-lite", - "name": "Nova 2 Lite", - "display_name": "Nova 2 Lite", + "id": "grok-build-0-1", + "name": "Grok Build 0.1", + "display_name": "Grok Build 0.1", "modalities": { "input": [ "text", @@ -145028,10 +144400,10 @@ ] }, "limit": { - "context": 1000000, - "output": 65535 + "context": 256000, + "output": 256000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -145039,28 +144411,25 @@ }, "attachment": true, "open_weights": false, - "knowledge": "2025-10", - "release_date": "2025-12-02", - "last_updated": "2025-12-01", + "release_date": "2026-04-16", + "last_updated": "2026-04-16", "type": "chat" }, { - "id": "gemini-2.5-pro", - "name": "Gemini 2.5 Pro", - "display_name": "Gemini 2.5 Pro", + "id": "minimax-m2-7", + "name": "MiniMax-M2.7", + "display_name": "MiniMax-M2.7", "modalities": { "input": [ - "text", - "image", - "audio" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 65535 + "context": 204800, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -145068,47 +144437,29 @@ "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "budget", - "budget": { - "default": -1, - "min": 128, - "max": 32768, - "auto": -1, - "unit": "tokens" - }, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thought_signatures" - ] - } - }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-01", - "release_date": "2025-06-17", - "last_updated": "2025-06-17", + "attachment": false, + "open_weights": true, + "release_date": "2026-03-18", + "last_updated": "2026-03-18", "type": "chat" }, { - "id": "qwen3-30b-a3b-instruct-2507", - "name": "qwen3-30b-a3b-instruct-2507", - "display_name": "qwen3-30b-a3b-instruct-2507", + "id": "gpt-5-6-luna", + "name": "GPT-5.6 Luna", + "display_name": "GPT-5.6 Luna", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 262000, - "output": 262000 + "context": 1050000, + "output": 128000 }, "temperature": false, "tool_call": true, @@ -145116,90 +144467,123 @@ "supported": true, "default": true }, - "attachment": false, - "open_weights": true, - "release_date": "2025-07-28", - "last_updated": "2025-07-28", + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", + "effort_options": [ + "none", + "low", + "medium", + "high", + "xhigh", + "max" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" + } + }, + "attachment": true, + "open_weights": false, + "knowledge": "2026-02-16", + "release_date": "2026-07-09", + "last_updated": "2026-07-09", "type": "chat" }, { - "id": "gpt-4.1-nano", - "name": "GPT-4.1 nano", - "display_name": "GPT-4.1 nano", + "id": "kimi-k2-6:free", + "name": "Kimi K2.6 (Free)", + "display_name": "Kimi K2.6 (Free)", "modalities": { "input": [ "text", "image", - "pdf" + "video" ], "output": [ "text" ] }, "limit": { - "context": 1047576, - "output": 32768 + "context": 262144, + "output": 262144 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, "attachment": true, - "open_weights": false, - "knowledge": "2024-04", - "release_date": "2025-04-14", - "last_updated": "2025-04-14", + "open_weights": true, + "knowledge": "2025-01", + "release_date": "2026-04-20", + "last_updated": "2026-04-20", "type": "chat" }, { - "id": "nova-micro-v1", - "name": "Nova Micro 1.0", - "display_name": "Nova Micro 1.0", + "id": "glm-5-2", + "name": "GLM-5.2", + "display_name": "GLM-5.2", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 10000 + "context": 1000000, + "output": 131072 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "none", + "high", + "xhigh" + ] + } }, - "attachment": true, - "open_weights": false, - "knowledge": "2024-10-31", - "release_date": "2024-12-05", - "last_updated": "2024-12-05", + "attachment": false, + "open_weights": true, + "release_date": "2026-06-13", + "last_updated": "2026-06-13", "type": "chat" }, { - "id": "claude-opus4-8", - "name": "Claude Opus 4.8", - "display_name": "Claude Opus 4.8", + "id": "nano-banana-2-lite", + "name": "Nano Banana 2 Lite", + "display_name": "Nano Banana 2 Lite", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ - "text" + "text", + "image" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 65536, + "output": 4096 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -145208,23 +144592,21 @@ "extra_capabilities": { "reasoning": { "effort_options": [ - "low", - "medium", + "minimal", "high" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-01", - "release_date": "2026-05-28", - "last_updated": "2026-05-28", - "type": "chat" + "knowledge": "2025-01", + "release_date": "2026-06-30", + "last_updated": "2026-06-30" }, { - "id": "qwen2.5-vl-72b-instruct", - "name": "qwen2.5-vl-72b-instruct", - "display_name": "qwen2.5-vl-72b-instruct", + "id": "gpt-5-4-mini", + "name": "GPT-5.4 mini", + "display_name": "GPT-5.4 mini", "modalities": { "input": [ "text", @@ -145235,66 +144617,86 @@ ] }, "limit": { - "context": 32000, - "output": 32000 + "context": 400000, + "output": 128000 }, - "temperature": false, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": false + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": false, + "mode": "effort", + "effort": "none", + "effort_options": [ + "none", + "low", + "medium", + "high", + "xhigh" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" + } }, "attachment": true, - "open_weights": true, - "knowledge": "2024-04", - "release_date": "2025-01-26", - "last_updated": "2025-01-26", + "open_weights": false, + "knowledge": "2025-08-31", + "release_date": "2026-03-17", + "last_updated": "2026-03-17", "type": "chat" }, { - "id": "gpt-6-astra", - "name": "GPT-6 Astra", - "display_name": "GPT-6 Astra", + "id": "nano-banana-pro", + "name": "Nano Banana Pro", + "display_name": "Nano Banana Pro", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ - "text" + "text", + "image" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 65536, + "output": 32768 }, - "temperature": false, - "tool_call": true, + "temperature": true, + "tool_call": false, "reasoning": { "supported": true, "default": true }, "extra_capabilities": { "reasoning": { - "supported": true, "effort_options": [ "low", - "medium", "high" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-04-30", - "release_date": "2026-09-04", - "last_updated": "2026-09-04", - "type": "chat" + "knowledge": "2025-01", + "release_date": "2026-05-28", + "last_updated": "2026-05-28" }, { - "id": "gpt-5.4", - "name": "GPT-5.4", - "display_name": "GPT-5.4", + "id": "gpt-5-6-sol", + "name": "GPT-5.6 Sol", + "display_name": "GPT-5.6 Sol", "modalities": { "input": [ "text", @@ -145309,24 +144711,25 @@ "context": 1050000, "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, + "default_enabled": true, "mode": "effort", - "effort": "none", + "effort": "medium", "effort_options": [ "none", "low", "medium", "high", - "xhigh" + "xhigh", + "max" ], "verbosity": "medium", "verbosity_options": [ @@ -145339,29 +144742,26 @@ }, "attachment": true, "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-03-05", - "last_updated": "2026-03-05", + "knowledge": "2026-02-16", + "release_date": "2026-07-09", + "last_updated": "2026-07-09", "type": "chat" }, { - "id": "gemini-3.5-flash-lite", - "name": "Gemini 3.5 Flash Lite", - "display_name": "Gemini 3.5 Flash Lite", + "id": "nemotron-3-ultra-550b-a55b", + "name": "Nemotron 3 Ultra 550B A55B", + "display_name": "Nemotron 3 Ultra 550B A55B", "modalities": { "input": [ - "text", - "image", - "audio", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 65535 + "context": 1000000, + "output": 128000 }, "temperature": true, "tool_call": true, @@ -145371,37 +144771,31 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "effort_options": [ - "low", - "medium", - "high" - ] + "supported": true } }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-03", - "release_date": "2026-07-21", - "last_updated": "2026-07-21", + "attachment": false, + "open_weights": true, + "knowledge": "2025-09", + "release_date": "2026-06-04", + "last_updated": "2026-06-04", "type": "chat" }, { - "id": "qwen3.5-9b", - "name": "Qwen3.5 9B", - "display_name": "Qwen3.5 9B", + "id": "nemotron-3-super-120b-a12b:free", + "name": "Nemotron 3 Super 120B A12B (Free)", + "display_name": "Nemotron 3 Super 120B A12B (Free)", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 262000, - "output": 262000 + "context": 262144, + "output": 262144 }, "temperature": true, "tool_call": true, @@ -145411,66 +144805,96 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "none", + "low", + "medium" ] } }, - "attachment": true, + "attachment": false, "open_weights": true, - "release_date": "2026-02-23", - "last_updated": "2026-02-23", + "knowledge": "2025-06", + "release_date": "2026-03-11", + "last_updated": "2026-03-11", "type": "chat" }, { - "id": "ministral-14b-2512", - "name": "Ministral 3 14B 2512", - "display_name": "Ministral 3 14B 2512", + "id": "claude-opus-4-7", + "name": "Claude Opus 4.7", + "display_name": "Claude Opus 4.7", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 256000, - "output": 256000 + "context": 1000000, + "output": 128000 }, "temperature": false, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": false + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": false, + "mode": "effort", + "effort": "high", + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "omitted", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Claude Opus 4.7 and newer Opus models require thinking.type = \"adaptive\" to enable thinking explicitly.", + "Manual budget_tokens requests return 400 on Claude Opus 4.7 and newer adaptive-only Opus models.", + "task_budget is separate from thinking control and should not be treated as a thinking budget." + ] + } }, "attachment": true, - "open_weights": true, - "release_date": "2025-12-02", - "last_updated": "2025-12-02", + "open_weights": false, + "knowledge": "2026-01-31", + "release_date": "2026-04-16", + "last_updated": "2026-04-16", "type": "chat" }, { - "id": "minimax-m2.1", - "name": "MiniMax-M2.1", - "display_name": "MiniMax-M2.1", + "id": "claude-sonnet-5", + "name": "Claude Sonnet 5", + "display_name": "Claude Sonnet 5", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 196000, - "output": 196000 + "context": 1000000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -145479,28 +144903,32 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" ] } }, - "attachment": false, - "open_weights": true, - "release_date": "2025-12-23", - "last_updated": "2025-12-23", + "attachment": true, + "open_weights": false, + "knowledge": "2026-01-31", + "release_date": "2026-06-30", + "last_updated": "2026-06-30", "type": "chat" }, { - "id": "claude-4-5-sonnet", - "name": "Claude Sonnet 4.5 (latest)", - "display_name": "Claude Sonnet 4.5 (latest)", + "id": "gemini-3-1-flash-lite", + "name": "Gemini 3.1 Flash Lite", + "display_name": "Gemini 3.1 Flash Lite", "modalities": { "input": [ "text", "image", + "video", + "audio", "pdf" ], "output": [ @@ -145508,8 +144936,8 @@ ] }, "limit": { - "context": 200000, - "output": 64000 + "context": 1048576, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -145520,6 +144948,7 @@ "extra_capabilities": { "reasoning": { "effort_options": [ + "minimal", "low", "medium", "high" @@ -145528,18 +144957,20 @@ }, "attachment": true, "open_weights": false, - "knowledge": "2025-07-31", - "release_date": "2025-09-29", - "last_updated": "2025-09-29", + "knowledge": "2025-01", + "release_date": "2026-05-07", + "last_updated": "2026-05-07", "type": "chat" }, { - "id": "qwen3-coder-30b-a3b-instruct", - "name": "Qwen3-Coder 30B-A3B Instruct", - "display_name": "Qwen3-Coder 30B-A3B Instruct", + "id": "kimi-k2-6", + "name": "Kimi K2.6", + "display_name": "Kimi K2.6", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" @@ -145552,59 +144983,66 @@ "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, + "attachment": true, "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-07-31", - "last_updated": "2025-07-31", + "knowledge": "2025-01", + "release_date": "2026-04-20", + "last_updated": "2026-04-20", "type": "chat" }, { - "id": "devstral-2512", - "name": "Devstral 2", - "display_name": "Devstral 2", + "id": "mimo-v2-5:free", + "name": "MiMo-V2.5 (Free)", + "display_name": "MiMo-V2.5 (Free)", "modalities": { "input": [ - "text" + "text", + "image", + "audio", + "video" ], "output": [ "text" ] }, "limit": { - "context": 256000, - "output": 256000 + "context": 1048576, + "output": 131072 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, + "attachment": true, "open_weights": true, - "knowledge": "2025-12", - "release_date": "2025-12-09", - "last_updated": "2025-12-09", + "knowledge": "2024-12", + "release_date": "2026-04-22", + "last_updated": "2026-04-22", "type": "chat" }, { - "id": "glm-5.3-flash", + "id": "glm-5-3-flash", "name": "GLM-5.3-Flash", "display_name": "GLM-5.3-Flash", "modalities": { "input": [ "text", - "image" + "image", + "video", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 1048576 + "context": 1000000, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -145614,7 +145052,6 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, "effort_options": [ "low", "high", @@ -145629,13 +145066,14 @@ "type": "chat" }, { - "id": "qwen3.8-27b", - "name": "Qwen3.8 27B", - "display_name": "Qwen3.8 27B", + "id": "claude-fable-5", + "name": "Claude Fable 5", + "display_name": "Claude Fable 5", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" @@ -145643,9 +145081,9 @@ }, "limit": { "context": 1000000, - "output": 500000 + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -145653,75 +145091,85 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "high", "effort_options": [ "low", "medium", - "xhigh" + "high", + "xhigh", + "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "omitted", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Adaptive thinking is always on for Claude Fable 5 and Claude Mythos 5; thinking.type = \"disabled\" is rejected.", + "Manual budget_tokens requests return 400 on Claude Fable 5 and Claude Mythos 5.", + "thinking.display defaults to omitted; set display to summarized to receive readable thinking summaries." ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-08-14", - "last_updated": "2026-08-14", + "open_weights": false, + "knowledge": "2026-01-31", + "release_date": "2026-06-09", + "last_updated": "2026-06-09", "type": "chat" }, { - "id": "gpt-oss-20b", - "name": "GPT OSS 20B", - "display_name": "GPT OSS 20B", + "id": "kimi-k2-7-code", + "name": "Kimi K2.7 Code", + "display_name": "Kimi K2.7 Code", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 131000, - "output": 131000 + "context": 262144, + "output": 262144 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "effort_options": [ - "low", - "medium", - "high" - ] - } - }, - "attachment": false, + "attachment": true, "open_weights": true, - "knowledge": "2024-06", - "release_date": "2025-08-05", - "last_updated": "2025-08-05", + "knowledge": "2025-01", + "release_date": "2026-06-12", + "last_updated": "2026-06-12", "type": "chat" }, { - "id": "pixtral-large-2502", - "name": "Pixtral Large (25.02)", - "display_name": "Pixtral Large (25.02)", + "id": "kimi-k2-7-code:free", + "name": "Kimi K2.7 Code (Free)", + "display_name": "Kimi K2.7 Code (Free)", "modalities": { "input": [ "text", "image", - "pdf" + "video" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 128000 + "context": 262144, + "output": 262144 }, "temperature": false, "tool_call": true, @@ -145730,27 +145178,27 @@ "default": true }, "attachment": true, - "open_weights": false, - "release_date": "2025-04-08", - "last_updated": "2025-04-08", + "open_weights": true, + "knowledge": "2025-01", + "release_date": "2026-06-12", + "last_updated": "2026-06-12", "type": "chat" }, { - "id": "deepseek-v4.1-flash-1017", - "name": "DeepSeek V4.1 Flash 1017", - "display_name": "DeepSeek V4.1 Flash 1017", + "id": "minimax-m2-7-highspeed", + "name": "MiniMax-M2.7-highspeed", + "display_name": "MiniMax-M2.7-highspeed", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 1048576 + "context": 204800, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -145758,30 +145206,19 @@ "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "low", - "medium", - "high" - ] - } - }, - "attachment": true, + "attachment": false, "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-10-17", - "last_updated": "2026-10-17", + "release_date": "2026-03-18", + "last_updated": "2026-03-18", "type": "chat" }, { - "id": "mistral-large-2512", - "name": "Mistral Large 3", - "display_name": "Mistral Large 3", + "id": "hy3:free", + "name": "Hy3 (Free)", + "display_name": "Hy3 (Free)", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" @@ -145789,24 +145226,33 @@ }, "limit": { "context": 256000, - "output": 256000 + "output": 128000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": true, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "none", + "low", + "high" + ] + } + }, + "attachment": false, "open_weights": true, - "knowledge": "2024-11", - "release_date": "2025-12-02", - "last_updated": "2025-12-02", + "release_date": "2026-07-06", + "last_updated": "2026-07-06", "type": "chat" }, { - "id": "kimi-k3", - "name": "Kimi K3", - "display_name": "Kimi K3", + "id": "grok-4-5", + "name": "Grok 4.5", + "display_name": "Grok 4.5", "modalities": { "input": [ "text", @@ -145817,10 +145263,10 @@ ] }, "limit": { - "context": 1048576, - "output": 1048576 + "context": 500000, + "output": 500000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -145828,30 +145274,23 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], "effort_options": [ "low", - "high", - "max" + "medium", + "high" ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-07-16", - "last_updated": "2026-07-16", + "open_weights": false, + "release_date": "2026-07-08", + "last_updated": "2026-07-08", "type": "chat" }, { - "id": "deepseek-v4-pro", - "name": "DeepSeek V4 Pro", - "display_name": "DeepSeek V4 Pro", + "id": "glm-4-7-flash:free", + "name": "GLM-4.7-Flash (Free)", + "display_name": "GLM-4.7-Flash (Free)", "modalities": { "input": [ "text" @@ -145861,8 +145300,8 @@ ] }, "limit": { - "context": 1000000, - "output": 384000 + "context": 200000, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -145870,39 +145309,44 @@ "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "high", - "effort_options": [ - "low", - "high", - "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." - ] - } - }, "attachment": false, "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-04-24", - "last_updated": "2026-04-24", + "knowledge": "2025-04", + "release_date": "2026-01-19", + "last_updated": "2026-01-19", "type": "chat" }, { - "id": "claude-opus-5", - "name": "Claude Opus 5", - "display_name": "Claude Opus 5", + "id": "gemini-3-1-flash-tts", + "name": "Gemini 3.1 Flash TTS Preview", + "display_name": "Gemini 3.1 Flash TTS Preview", + "modalities": { + "input": [ + "text" + ], + "output": [ + "audio" + ] + }, + "limit": { + "context": 8192, + "output": 16384 + }, + "temperature": true, + "tool_call": false, + "reasoning": { + "supported": false + }, + "attachment": false, + "open_weights": false, + "knowledge": "2025-01", + "release_date": "2026-04-15", + "last_updated": "2026-04-15" + }, + { + "id": "gpt-5-5", + "name": "GPT-5.5", + "display_name": "GPT-5.5", "modalities": { "input": [ "text", @@ -145914,7 +145358,7 @@ ] }, "limit": { - "context": 1000000, + "context": 1050000, "output": 128000 }, "temperature": false, @@ -145926,52 +145370,73 @@ "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", "effort_options": [ + "low", + "medium", + "high", + "xhigh" + ], + "verbosity": "medium", + "verbosity_options": [ "low", "medium", "high" - ] + ], + "visibility": "hidden" } }, "attachment": true, "open_weights": false, - "knowledge": "2026-05", - "release_date": "2026-07-24", - "last_updated": "2026-07-24", + "knowledge": "2025-12-01", + "release_date": "2026-04-23", + "last_updated": "2026-04-23", "type": "chat" }, { - "id": "voxtral-small-2507", - "name": "voxtral-small-2507", - "display_name": "voxtral-small-2507", + "id": "hy3", + "name": "Hy3", + "display_name": "Hy3", "modalities": { "input": [ - "text", - "audio" + "text" ], "output": [ "text" ] }, "limit": { - "context": 32000, - "output": 32000 + "context": 256000, + "output": 128000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": true, + "extra_capabilities": { + "reasoning": { + "supported": true, + "effort_options": [ + "none", + "low", + "high" + ] + } + }, + "attachment": false, "open_weights": true, - "release_date": "2025-07-15", - "last_updated": "2025-07-15", + "release_date": "2026-07-06", + "last_updated": "2026-07-06", "type": "chat" }, { - "id": "nvidia-nemotron-3-nano-30b-a3b", - "name": "nvidia-nemotron-3-nano-30b-a3b", - "display_name": "nvidia-nemotron-3-nano-30b-a3b", + "id": "nemotron-3-nano-30b-a3b", + "name": "Nemotron 3 Nano 30B A3B", + "display_name": "Nemotron 3 Nano 30B A3B", "modalities": { "input": [ "text" @@ -145981,10 +145446,10 @@ ] }, "limit": { - "context": 256000, - "output": 256000 + "context": 262144, + "output": 262144 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -145998,9 +145463,9 @@ "type": "chat" }, { - "id": "qwen3.8-2.4t-a95b", - "name": "Qwen3.8 2.4T A95B", - "display_name": "Qwen3.8 2.4T A95B", + "id": "gpt-oss-120b", + "name": "GPT OSS 120B", + "display_name": "GPT OSS 120B", "modalities": { "input": [ "text" @@ -146010,8 +145475,8 @@ ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 131072, + "output": 32768 }, "temperature": true, "tool_call": true, @@ -146025,32 +145490,35 @@ "effort_options": [ "low", "medium", - "xhigh" + "high" ] } }, "attachment": false, "open_weights": true, - "release_date": "2026-08-12", - "last_updated": "2026-08-12", + "knowledge": "2024-06", + "release_date": "2025-08-05", + "last_updated": "2025-08-05", "type": "chat" }, { - "id": "gemma-4-31b-it", - "name": "Gemma 4 31B IT", - "display_name": "Gemma 4 31B IT", + "id": "mimo-v2-5", + "name": "MiMo-V2.5", + "display_name": "MiMo-V2.5", "modalities": { "input": [ "text", - "image" + "image", + "audio", + "video" ], "output": [ "text" ] }, "limit": { - "context": 262000, - "output": 262000 + "context": 1048576, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -146058,22 +145526,17 @@ "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "supported": true - } - }, "attachment": true, "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-04-02", - "last_updated": "2026-04-02", + "knowledge": "2024-12", + "release_date": "2026-04-22", + "last_updated": "2026-04-22", "type": "chat" }, { - "id": "qwen3-coder-next", - "name": "Qwen3 Coder Next", - "display_name": "Qwen3 Coder Next", + "id": "nemotron-3-super-120b-a12b", + "name": "Nemotron 3 Super 120B A12B", + "display_name": "Nemotron 3 Super 120B A12B", "modalities": { "input": [ "text" @@ -146083,84 +145546,75 @@ ] }, "limit": { - "context": 256000, - "output": 256000 + "context": 262144, + "output": 262144 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "none", + "low", + "medium" + ] + } }, "attachment": false, "open_weights": true, - "knowledge": "2025-09", - "release_date": "2026-02-03", - "last_updated": "2026-02-03", + "knowledge": "2025-06", + "release_date": "2026-03-11", + "last_updated": "2026-03-11", "type": "chat" }, { - "id": "gpt-4o", - "name": "GPT-4o", - "display_name": "GPT-4o", + "id": "step-3-7-flash:free", + "name": "Step 3.7 Flash (Free)", + "display_name": "Step 3.7 Flash (Free)", "modalities": { "input": [ "text", "image", - "pdf" + "video" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 16000 + "context": 256000, + "output": 256000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false - }, - "attachment": true, - "open_weights": false, - "knowledge": "2023-09", - "release_date": "2024-05-13", - "last_updated": "2024-08-06", - "type": "chat" - }, - { - "id": "mistral-small-2503", - "name": "mistral-small-2503", - "display_name": "mistral-small-2503", - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 128000 + "supported": true, + "default": true }, - "temperature": false, - "tool_call": true, - "reasoning": { - "supported": false + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "low", + "medium", + "high" + ] + } }, "attachment": true, "open_weights": true, - "knowledge": "2024-06", - "release_date": "2025-03-17", - "last_updated": "2025-03-17", + "knowledge": "2026-03-01", + "release_date": "2026-05-29", + "last_updated": "2026-05-29", "type": "chat" }, { - "id": "kolibri-1", - "name": "kolibri-1", - "display_name": "kolibri-1", + "id": "deepseek-v4-pro", + "name": "DeepSeek V4 Pro", + "display_name": "DeepSeek V4 Pro", "modalities": { "input": [ "text" @@ -146170,10 +145624,10 @@ ] }, "limit": { - "context": 262144, - "output": 81920 + "context": 1000000, + "output": 384000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -146181,25 +145635,37 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "high", "effort_options": [ - "none", "low", - "medium", - "high" + "high", + "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." ] } }, "attachment": false, "open_weights": true, - "knowledge": "2026-06-18", - "release_date": "2026-10-03", - "last_updated": "2026-10-03", + "knowledge": "2025-05", + "release_date": "2026-04-24", + "last_updated": "2026-04-24", "type": "chat" }, { - "id": "kimi-k2.6", - "name": "Kimi K2.6", - "display_name": "Kimi K2.6", + "id": "claude-sonnet-4-6", + "name": "Claude Sonnet 4.6", + "display_name": "Claude Sonnet 4.6", "modalities": { "input": [ "text", @@ -146211,51 +145677,65 @@ ] }, "limit": { - "context": 256000, - "output": 256000 + "context": 1000000, + "output": 64000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": false, + "mode": "mixed", + "budget": { + "min": 1024, + "unit": "tokens" + }, + "effort": "high", + "effort_options": [ + "low", + "medium", + "high", + "max" + ], "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ "thinking_blocks" + ], + "notes": [ + "Anthropic recommends adaptive thinking with effort for Claude 4.6; budget_tokens remains a deprecated compatibility path.", + "Anthropic API defaults effort to high; lower effort levels should be chosen per workload." ] } }, "attachment": true, - "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-04-20", - "last_updated": "2026-04-20", + "open_weights": false, + "knowledge": "2025-08-31", + "release_date": "2026-02-17", + "last_updated": "2026-03-13", "type": "chat" }, { - "id": "gemini-2.5-flash", - "name": "Gemini 2.5 Flash", - "display_name": "Gemini 2.5 Flash", + "id": "glm-5-3", + "name": "GLM-5.3", + "display_name": "GLM-5.3", "modalities": { "input": [ - "text", - "image", - "audio", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 65535 + "context": 1000000, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -146265,35 +145745,23 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "budget", - "budget": { - "default": -1, - "min": 0, - "max": 24576, - "auto": -1, - "off": 0, - "unit": "tokens" - }, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thought_signatures" + "effort_options": [ + "low", + "high", + "max" ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-01", - "release_date": "2025-06-17", - "last_updated": "2025-06-17", + "attachment": false, + "open_weights": true, + "release_date": "2026-08-14", + "last_updated": "2026-08-14", "type": "chat" }, { - "id": "minimax-m3", - "name": "MiniMax-M3", - "display_name": "MiniMax-M3", + "id": "mistral-medium-3-5:free", + "name": "Mistral Medium 3.5 (Free)", + "display_name": "Mistral Medium 3.5 (Free)", "modalities": { "input": [ "text", @@ -146304,8 +145772,8 @@ ] }, "limit": { - "context": 1048576, - "output": 1048576 + "context": 262144, + "output": 262144 }, "temperature": true, "tool_call": true, @@ -146315,23 +145783,29 @@ }, "extra_capabilities": { "reasoning": { - "supported": true + "effort_options": [ + "none", + "high" + ] } }, "attachment": true, "open_weights": true, - "release_date": "2026-06-01", - "last_updated": "2026-06-01", + "release_date": "2026-04-29", + "last_updated": "2026-04-29", "type": "chat" }, { - "id": "minimax-m3.1-flash", - "name": "MiniMax-M3.1-Flash", - "display_name": "MiniMax-M3.1-Flash", + "id": "gemini-3-7-flash", + "name": "Gemini 3.7 Flash", + "display_name": "Gemini 3.7 Flash", "modalities": { "input": [ "text", - "image" + "image", + "video", + "audio", + "pdf" ], "output": [ "text" @@ -146339,7 +145813,7 @@ }, "limit": { "context": 1048576, - "output": 64000 + "output": 65536 }, "temperature": true, "tool_call": true, @@ -146352,51 +145826,61 @@ "effort_options": [ "low", "medium", - "high", - "xhigh", - "max" + "high" ] } }, "attachment": true, "open_weights": false, - "release_date": "2026-10-19", - "last_updated": "2026-10-19", + "knowledge": "2026-03", + "release_date": "2026-08-13", + "last_updated": "2026-08-13", "type": "chat" }, { - "id": "mistral-small-3.2-24b-instruct-2506", - "name": "mistral-small-3.2-24b-instruct-2506", - "display_name": "mistral-small-3.2-24b-instruct-2506", + "id": "qwen3-8-max", + "name": "Qwen3.8 Max", + "display_name": "Qwen3.8 Max", "modalities": { "input": [ + "text", "image", - "text" + "video", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 131000, - "output": 131000 + "context": 1000000, + "output": 131072 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "low", + "medium", + "xhigh" + ] + } }, "attachment": true, - "open_weights": true, - "knowledge": "2023-10-31", - "release_date": "2025-06-20", - "last_updated": "2025-06-20", + "open_weights": false, + "release_date": "2026-08-03", + "last_updated": "2026-08-03", "type": "chat" }, { - "id": "claude-sonnet-5", - "name": "Claude Sonnet 5", - "display_name": "Claude Sonnet 5", + "id": "claude-opus-4-8", + "name": "Claude Opus 4.8", + "display_name": "Claude Opus 4.8", "modalities": { "input": [ "text", @@ -146415,80 +145899,95 @@ "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": false, + "mode": "effort", + "effort": "high", "effort_options": [ "low", "medium", - "high" + "high", + "xhigh", + "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "omitted", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Claude Opus 4.7 and newer Opus models require thinking.type = \"adaptive\" to enable thinking explicitly.", + "Manual budget_tokens requests return 400 on Claude Opus 4.7 and newer adaptive-only Opus models.", + "task_budget is separate from thinking control and should not be treated as a thinking budget." ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-01-31", - "release_date": "2026-06-30", - "last_updated": "2026-06-30", + "knowledge": "2026-01", + "release_date": "2026-05-28", + "last_updated": "2026-05-28", "type": "chat" }, { - "id": "qwen3-vl-235b-a22b", - "name": "qwen3-vl-235b-a22b", - "display_name": "qwen3-vl-235b-a22b", + "id": "deepseek-v4-flash:free", + "name": "DeepSeek V4 Flash (Free)", + "display_name": "DeepSeek V4 Flash (Free)", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 256000, - "output": 256000 + "context": 1000000, + "output": 384000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { - "supported": true + "supported": true, + "default": true }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "low", + "high", + "max" ] } }, - "attachment": true, + "attachment": false, "open_weights": true, - "knowledge": "2025-03-31", - "release_date": "2025-09-23", - "last_updated": "2025-09-23", + "knowledge": "2025-05", + "release_date": "2026-04-24", + "last_updated": "2026-04-24", "type": "chat" }, { - "id": "minimax-m2", - "name": "MiniMax-M2", - "display_name": "MiniMax-M2", + "id": "grok-4-6", + "name": "Grok 4.6", + "display_name": "Grok 4.6", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 400000, - "output": 196000 + "context": 500000, + "output": 500000 }, "temperature": true, "tool_call": true, @@ -146498,30 +145997,31 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "low", + "medium", + "high", + "xhigh" ] } }, - "attachment": false, - "open_weights": true, - "release_date": "2025-10-27", - "last_updated": "2025-10-27", + "attachment": true, + "open_weights": false, + "knowledge": "2026-02-01", + "release_date": "2026-08-12", + "last_updated": "2026-08-12", "type": "chat" }, { - "id": "gemini-3.1-flash-lite", - "name": "Gemini 3.1 Flash Lite", - "display_name": "Gemini 3.1 Flash Lite", + "id": "gemini-2-5-flash-lite", + "name": "Gemini 2.5 Flash-Lite", + "display_name": "Gemini 2.5 Flash-Lite", "modalities": { "input": [ "text", "image", "audio", + "video", "pdf" ], "output": [ @@ -146530,7 +146030,7 @@ }, "limit": { "context": 1048576, - "output": 65535 + "output": 65536 }, "temperature": true, "tool_call": true, @@ -146538,60 +146038,50 @@ "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "effort_options": [ - "low", - "medium", - "high" - ] - } - }, "attachment": true, "open_weights": false, "knowledge": "2025-01", - "release_date": "2026-05-07", - "last_updated": "2026-05-07", + "release_date": "2025-06-17", + "last_updated": "2025-06-17", "type": "chat" }, { - "id": "nemotron-nano-v2-12b", - "name": "nemotron-nano-v2-12b", - "display_name": "nemotron-nano-v2-12b", + "id": "grok-imagine-image-2-0", + "name": "Grok Imagine Image 2.0", + "display_name": "Grok Imagine Image 2.0", "modalities": { "input": [ "text", "image" ], "output": [ - "text" + "image" ] }, "limit": { - "context": 128000, - "output": 128000 + "context": 64000, + "output": 8192 }, "temperature": false, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true + "supported": false }, "attachment": true, - "open_weights": true, - "release_date": "2025-10-28", - "last_updated": "2025-10-28", - "type": "chat" + "open_weights": false, + "release_date": "2026-08-07", + "last_updated": "2026-08-07" }, { - "id": "nova-pro-v1", - "name": "Nova Pro 1.0", - "display_name": "Nova Pro 1.0", + "id": "gemini-2-5-flash", + "name": "Gemini 2.5 Flash", + "display_name": "Gemini 2.5 Flash", "modalities": { "input": [ "text", "image", + "audio", + "video", "pdf" ], "output": [ @@ -146599,25 +146089,26 @@ ] }, "limit": { - "context": 300000, - "output": 10000 + "context": 1048576, + "output": 65536 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, "attachment": true, "open_weights": false, - "knowledge": "2024-10-31", - "release_date": "2024-12-05", - "last_updated": "2024-12-05", + "knowledge": "2025-01", + "release_date": "2025-06-17", + "last_updated": "2025-06-17", "type": "chat" }, { - "id": "minimax-m2.5", - "name": "MiniMax-M2.5", - "display_name": "MiniMax-M2.5", + "id": "deepseek-v4-flash", + "name": "DeepSeek V4 Flash", + "display_name": "DeepSeek V4 Flash", "modalities": { "input": [ "text" @@ -146627,8 +146118,8 @@ ] }, "limit": { - "context": 196000, - "output": 196000 + "context": 1000000, + "output": 384000 }, "temperature": true, "tool_call": true, @@ -146639,119 +146130,131 @@ "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "high", + "effort_options": [ + "low", + "high", + "max" + ], "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ "thinking_blocks" + ], + "notes": [ + "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." ] } }, "attachment": false, "open_weights": true, - "release_date": "2026-02-12", - "last_updated": "2026-02-12", + "knowledge": "2025-05", + "release_date": "2026-04-24", + "last_updated": "2026-04-24", "type": "chat" }, { - "id": "mixtral-8x7B-instruct-v0.1", - "name": "Mixtral 8x7B Instruct v0.1", - "display_name": "Mixtral 8x7B Instruct v0.1", + "id": "deepseek-v4-1-flash", + "name": "DeepSeek V4.1 Flash", + "display_name": "DeepSeek V4.1 Flash", "modalities": { "input": [ "text", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 32000, - "output": 4096 + "context": 1000000, + "output": 384000 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { "supported": true, "default": true }, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "none", + "low", + "high", + "max" + ] + } + }, "attachment": true, "open_weights": true, - "release_date": "2023-12-11", - "last_updated": "2023-12-11", + "knowledge": "2025-05", + "release_date": "2026-09-10", + "last_updated": "2026-09-10", "type": "chat" }, { - "id": "gemini-3.7-flash", - "name": "Gemini 3.7 Flash", - "display_name": "Gemini 3.7 Flash", + "id": "nano-banana-2", + "name": "Nano Banana 2", + "display_name": "Nano Banana 2", "modalities": { "input": [ "text", "image", - "audio", + "video", "pdf" ], "output": [ - "text" + "text", + "image" ] }, "limit": { - "context": 1048576, - "output": 65535 + "context": 131072, + "output": 32768 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { "supported": true, "default": true }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "level", - "level": "high", - "level_options": [ + "effort_options": [ "minimal", - "low", - "medium", "high" - ], - "summaries": true, - "visibility": "summary", - "continuation": [ - "thought_signatures" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-03", - "release_date": "2026-08-13", - "last_updated": "2026-08-13", - "type": "chat" + "knowledge": "2025-01", + "release_date": "2026-05-28", + "last_updated": "2026-05-28" }, { - "id": "gpt-6-sol", - "name": "GPT-6 Sol", - "display_name": "GPT-6 Sol", + "id": "step-3-7-flash", + "name": "Step 3.7 Flash", + "display_name": "Step 3.7 Flash", "modalities": { "input": [ "text", "image", - "pdf" + "video" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 256000, + "output": 256000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -146767,20 +146270,22 @@ } }, "attachment": true, - "open_weights": false, - "knowledge": "2026-04-20", - "release_date": "2026-09-22", - "last_updated": "2026-09-22", + "open_weights": true, + "knowledge": "2026-03-01", + "release_date": "2026-05-29", + "last_updated": "2026-05-29", "type": "chat" }, { - "id": "gpt-5.6-sol", - "name": "GPT-5.6 Sol", - "display_name": "GPT-5.6 Sol", + "id": "gemini-3-5-flash", + "name": "Gemini 3.5 Flash", + "display_name": "Gemini 3.5 Flash", "modalities": { "input": [ "text", "image", + "video", + "audio", "pdf" ], "output": [ @@ -146788,10 +146293,10 @@ ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 1048576, + "output": 65536 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -146799,53 +146304,39 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", "effort_options": [ - "none", - "low", - "medium", - "high", - "xhigh", - "max" - ], - "verbosity": "medium", - "verbosity_options": [ + "minimal", "low", "medium", "high" - ], - "visibility": "hidden" + ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-02-16", - "release_date": "2026-07-09", - "last_updated": "2026-07-09", + "knowledge": "2025-01", + "release_date": "2026-05-19", + "last_updated": "2026-05-19", "type": "chat" }, { - "id": "gpt-5.6-terra", - "name": "GPT-5.6 Terra", - "display_name": "GPT-5.6 Terra", + "id": "gemma-4-31b-it", + "name": "Gemma 4 31B IT", + "display_name": "Gemma 4 31B IT", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 262144, + "output": 32768 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -146853,38 +146344,20 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", - "effort_options": [ - "none", - "low", - "medium", - "high", - "xhigh", - "max" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + "supported": true } }, "attachment": true, - "open_weights": false, - "knowledge": "2026-02-16", - "release_date": "2026-07-09", - "last_updated": "2026-07-09", + "open_weights": true, + "knowledge": "2025-01", + "release_date": "2026-04-02", + "last_updated": "2026-04-02", "type": "chat" }, { - "id": "gpt-oss-120b", - "name": "GPT OSS 120B", - "display_name": "GPT OSS 120B", + "id": "glm-5-1", + "name": "GLM-5.1", + "display_name": "GLM-5.1", "modalities": { "input": [ "text" @@ -146894,8 +146367,8 @@ ] }, "limit": { - "context": 131000, - "output": 131000 + "context": 200000, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -146903,27 +146376,16 @@ "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "effort_options": [ - "low", - "medium", - "high" - ] - } - }, "attachment": false, "open_weights": true, - "knowledge": "2024-06", - "release_date": "2025-08-05", - "last_updated": "2025-08-05", + "release_date": "2026-04-07", + "last_updated": "2026-04-07", "type": "chat" }, { - "id": "gpt-6-luna", - "name": "GPT-6 Luna", - "display_name": "GPT-6 Luna", + "id": "claude-opus-5", + "name": "Claude Opus 5", + "display_name": "Claude Opus 5", "modalities": { "input": [ "text", @@ -146935,7 +146397,7 @@ ] }, "limit": { - "context": 1050000, + "context": 1000000, "output": 128000 }, "temperature": false, @@ -146946,56 +146408,69 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, "effort_options": [ "low", "medium", - "high" + "high", + "xhigh", + "max" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-05-18", - "release_date": "2026-09-22", - "last_updated": "2026-09-22", + "knowledge": "2026-05", + "release_date": "2026-07-24", + "last_updated": "2026-07-24", "type": "chat" }, { - "id": "hermes-4-405b", - "name": "Hermes 4 405B", - "display_name": "Hermes 4 405B", + "id": "minimax-m3", + "name": "MiniMax-M3", + "display_name": "MiniMax-M3", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 128000 + "context": 1048576, + "output": 512000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, + "extra_capabilities": { + "reasoning": { + "supported": true + } + }, + "attachment": true, "open_weights": true, - "knowledge": "2024-08-31", - "release_date": "2025-08-26", - "last_updated": "2025-08-26", + "release_date": "2026-06-01", + "last_updated": "2026-06-01", "type": "chat" }, { - "id": "deepseek-v4.1-flash", - "name": "DeepSeek V4.1 Flash", - "display_name": "DeepSeek V4.1 Flash", + "id": "gemini-3-6-flash", + "name": "Gemini 3.6 Flash", + "display_name": "Gemini 3.6 Flash", "modalities": { "input": [ "text", - "image" + "image", + "video", + "audio", + "pdf" ], "output": [ "text" @@ -147003,7 +146478,7 @@ }, "limit": { "context": 1048576, - "output": 393216 + "output": 65536 }, "temperature": true, "tool_call": true, @@ -147013,8 +146488,8 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, "effort_options": [ + "minimal", "low", "medium", "high" @@ -147022,30 +146497,33 @@ } }, "attachment": true, - "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-09-10", - "last_updated": "2026-09-10", + "open_weights": false, + "knowledge": "2026-03", + "release_date": "2026-07-21", + "last_updated": "2026-07-21", "type": "chat" }, { - "id": "gpt-5-mini", - "name": "GPT-5 Mini", - "display_name": "GPT-5 Mini", + "id": "gemini-3-1-pro", + "name": "Gemini 3.1 Pro Preview", + "display_name": "Gemini 3.1 Pro Preview", "modalities": { "input": [ "text", - "image" + "image", + "video", + "audio", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 1048576, + "output": 65536 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -147053,99 +146531,112 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", "effort_options": [ - "minimal", - "low", - "medium", - "high" - ], - "verbosity": "medium", - "verbosity_options": [ "low", "medium", "high" - ], - "visibility": "hidden" + ] } }, "attachment": true, "open_weights": false, - "knowledge": "2024-05-30", - "release_date": "2025-08-07", - "last_updated": "2025-08-07", + "knowledge": "2025-01", + "release_date": "2026-02-19", + "last_updated": "2026-02-19", "type": "chat" }, { - "id": "apertus-70b", - "name": "Apertus 70B", - "display_name": "Apertus 70B", + "id": "grok-imagine-image", + "name": "Grok Imagine Image", + "display_name": "Grok Imagine Image", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ - "text" + "image" ] }, "limit": { - "context": 65536, - "output": 16384 + "context": 16000, + "output": 8192 }, "temperature": false, + "tool_call": false, + "reasoning": { + "supported": false + }, + "attachment": true, + "open_weights": false, + "release_date": "2026-01-28", + "last_updated": "2026-01-28" + }, + { + "id": "mistral-large:free", + "name": "Mistral Large (Free)", + "display_name": "Mistral Large (Free)", + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 262144, + "output": 262144 + }, + "temperature": true, "tool_call": true, "reasoning": { "supported": false }, - "attachment": false, + "attachment": true, "open_weights": true, - "knowledge": "2025-09", - "release_date": "2025-09-02", - "last_updated": "2025-09-02", + "knowledge": "2024-11", + "release_date": "2024-11-01", + "last_updated": "2025-12-02", "type": "chat" }, { - "id": "nova-lite-v1", - "name": "Nova Lite 1.0", - "display_name": "Nova Lite 1.0", + "id": "gpt-image-2", + "name": "GPT-Image-2", + "display_name": "GPT-Image-2", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ - "text" + "image" ] }, "limit": { - "context": 300000, - "output": 10000 + "context": 272000, + "output": 16384 }, "temperature": false, - "tool_call": true, + "tool_call": false, "reasoning": { "supported": false }, "attachment": true, "open_weights": false, - "knowledge": "2024-10-31", - "release_date": "2024-12-05", - "last_updated": "2024-12-05", - "type": "chat" + "release_date": "2026-04-21", + "last_updated": "2026-04-21" }, { - "id": "claude-opus4-6", - "name": "Claude Opus 4.6", - "display_name": "Claude Opus 4.6", + "id": "qwen3-7-plus", + "name": "Qwen3.7 Plus", + "display_name": "Qwen3.7 Plus", "modalities": { "input": [ "text", "image", - "pdf" + "video" ], "output": [ "text" @@ -147153,7 +146644,7 @@ }, "limit": { "context": 1000000, - "output": 128000 + "output": 64000 }, "temperature": true, "tool_call": true, @@ -147161,26 +146652,17 @@ "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "low", - "medium", - "high" - ] - } - }, "attachment": true, "open_weights": false, - "knowledge": "2025-05-31", - "release_date": "2026-02-05", - "last_updated": "2026-03-13", + "knowledge": "2025-04", + "release_date": "2026-06-02", + "last_updated": "2026-06-02", "type": "chat" }, { - "id": "deepseek-v3.2", - "name": "DeepSeek V3.2", - "display_name": "DeepSeek V3.2", + "id": "mimo-v2-5-pro", + "name": "MiMo-V2.5-Pro", + "display_name": "MiMo-V2.5-Pro", "modalities": { "input": [ "text" @@ -147190,8 +146672,8 @@ ] }, "limit": { - "context": 163840, - "output": 65536 + "context": 1048576, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -147199,27 +146681,71 @@ "supported": true, "default": true }, + "attachment": false, + "open_weights": true, + "knowledge": "2024-12", + "release_date": "2026-04-22", + "last_updated": "2026-04-22", + "type": "chat" + }, + { + "id": "gpt-5-6-terra", + "name": "GPT-5.6 Terra", + "display_name": "GPT-5.6 Terra", + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1050000, + "output": 128000 + }, + "temperature": false, + "tool_call": true, + "reasoning": { + "supported": true, + "default": true + }, "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", "effort_options": [ + "none", + "low", + "medium", + "high", + "xhigh", + "max" + ], + "verbosity": "medium", + "verbosity_options": [ "low", "medium", "high" - ] + ], + "visibility": "hidden" } }, - "attachment": false, - "open_weights": true, - "knowledge": "2024-07", - "release_date": "2025-12-01", - "last_updated": "2025-12-01", + "attachment": true, + "open_weights": false, + "knowledge": "2026-02-16", + "release_date": "2026-07-09", + "last_updated": "2026-07-09", "type": "chat" }, { - "id": "deepseek-v4-flash-0731", - "name": "DeepSeek V4 Flash 0731", - "display_name": "DeepSeek V4 Flash 0731", + "id": "gpt-oss-20b", + "name": "GPT OSS 20B", + "display_name": "GPT OSS 20B", "modalities": { "input": [ "text" @@ -147229,8 +146755,8 @@ ] }, "limit": { - "context": 1048576, - "output": 1048576 + "context": 131072, + "output": 32768 }, "temperature": true, "tool_call": true, @@ -147241,12 +146767,6 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], "effort_options": [ "low", "medium", @@ -147256,46 +146776,54 @@ }, "attachment": false, "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-07-31", - "last_updated": "2026-07-31", + "knowledge": "2024-06", + "release_date": "2025-08-05", + "last_updated": "2025-08-05", "type": "chat" }, { - "id": "llama-3.3-70b-instruct", - "name": "Llama-3.3-70B-Instruct", - "display_name": "Llama-3.3-70B-Instruct", + "id": "whisper-large-v3-turbo", + "name": "Whisper Large v3 Turbo", + "display_name": "Whisper Large v3 Turbo", "modalities": { "input": [ - "text" + "audio" ], "output": [ "text" ] }, "limit": { - "context": 131000, - "output": 131000 + "context": 448, + "output": 448 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { "supported": false }, - "attachment": false, + "attachment": true, "open_weights": true, - "knowledge": "2023-12", - "release_date": "2024-12-06", - "last_updated": "2024-12-06", - "type": "chat" - }, + "release_date": "2024-10-01", + "last_updated": "2024-10-01" + } + ] + }, + "coralbricks": { + "id": "coralbricks", + "name": "CoralBricks", + "display_name": "CoralBricks", + "api": "https://inference.coralbricks.ai/v1", + "doc": "https://www.coralbricks.ai/docs", + "models": [ { - "id": "deepseek-v4-pro-0813", - "name": "DeepSeek V4 Pro 0813", - "display_name": "DeepSeek V4 Pro 0813", + "id": "deepseek-v4.1-flash-fast", + "name": "DeepSeek V4.1 Flash", + "display_name": "DeepSeek V4.1 Flash", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" @@ -147303,7 +146831,7 @@ }, "limit": { "context": 1048576, - "output": 64000 + "output": 393216 }, "temperature": true, "tool_call": true, @@ -147314,35 +146842,50 @@ "extra_capabilities": { "reasoning": { "supported": true, + "interleaved": true, "effort_options": [ + "none", "low", - "medium", - "high" + "high", + "max" ] } }, - "attachment": false, + "attachment": true, "open_weights": true, - "release_date": "2026-08-13", - "last_updated": "2026-08-22", + "knowledge": "2025-05", + "release_date": "2026-09-10", + "last_updated": "2026-09-10", "type": "chat" - }, + } + ] + }, + "unorouter": { + "id": "unorouter", + "name": "UnoRouter", + "display_name": "UnoRouter", + "api": "https://api.unorouter.com/v1", + "doc": "https://unorouter.com/models", + "models": [ { - "id": "gemma-4-26b-a4b-it", - "name": "Gemma 4 26B A4B IT", - "display_name": "Gemma 4 26B A4B IT", + "id": "gemini-3.5-flash", + "name": "Gemini 3.5 Flash", + "display_name": "Gemini 3.5 Flash", "modalities": { "input": [ "text", - "image" + "image", + "video", + "audio", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 262000, - "output": 81920 + "context": 1048576, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -147352,19 +146895,34 @@ }, "extra_capabilities": { "reasoning": { - "supported": true + "supported": true, + "default_enabled": true, + "mode": "level", + "level": "high", + "level_options": [ + "minimal", + "low", + "medium", + "high" + ], + "summaries": true, + "visibility": "summary", + "continuation": [ + "thought_signatures" + ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-04-02", - "last_updated": "2026-04-02", + "open_weights": false, + "knowledge": "2025-01", + "release_date": "2026-05-19", + "last_updated": "2026-05-19", "type": "chat" }, { - "id": "gpt-5.1", - "name": "GPT-5.1", - "display_name": "GPT-5.1", + "id": "gpt-5.4", + "name": "GPT-5.4", + "display_name": "GPT-5.4", "modalities": { "input": [ "text", @@ -147376,7 +146934,7 @@ ] }, "limit": { - "context": 400000, + "context": 1050000, "output": 128000 }, "temperature": true, @@ -147395,7 +146953,8 @@ "none", "low", "medium", - "high" + "high", + "xhigh" ], "verbosity": "medium", "verbosity_options": [ @@ -147408,70 +146967,15 @@ }, "attachment": true, "open_weights": false, - "knowledge": "2024-09-30", - "release_date": "2025-11-13", - "last_updated": "2025-11-13", - "type": "chat" - }, - { - "id": "ministral-3b-2512", - "name": "Ministral 3 3B 2512", - "display_name": "Ministral 3 3B 2512", - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 256000, - "output": 256000 - }, - "temperature": false, - "tool_call": true, - "reasoning": { - "supported": false - }, - "attachment": true, - "open_weights": true, - "release_date": "2025-12-02", - "last_updated": "2025-12-02", - "type": "chat" - }, - { - "id": "qwen3guard-gen-0.6b", - "name": "qwen3guard-gen-0.6b", - "display_name": "qwen3guard-gen-0.6b", - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 32000, - "output": 32000 - }, - "temperature": false, - "tool_call": false, - "reasoning": { - "supported": false - }, - "attachment": false, - "open_weights": false, - "release_date": "2026-02-04", - "last_updated": "2026-02-04", + "knowledge": "2025-08-31", + "release_date": "2026-03-05", + "last_updated": "2026-03-05", "type": "chat" }, { - "id": "kimi-k2.7-code", - "name": "Kimi K2.7 Code", - "display_name": "Kimi K2.7 Code", + "id": "claude-haiku-4-5-20251001", + "name": "Claude Haiku 4.5", + "display_name": "Claude Haiku 4.5", "modalities": { "input": [ "text", @@ -147483,98 +146987,78 @@ ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 200000, + "output": 64000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": false, + "mode": "budget", + "budget": { + "min": 1024, + "unit": "tokens" + }, "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ "thinking_blocks" + ], + "notes": [ + "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", + "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." ] } }, "attachment": true, - "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-06-12", - "last_updated": "2026-06-12", - "type": "chat" - }, - { - "id": "mistral-nemo-instruct-2407", - "name": "mistral-nemo-instruct-2407", - "display_name": "mistral-nemo-instruct-2407", - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 118000, - "output": 118000 - }, - "temperature": false, - "tool_call": true, - "reasoning": { - "supported": false - }, - "attachment": false, - "open_weights": true, - "knowledge": "2024-07", - "release_date": "2024-07-18", - "last_updated": "2024-07-18", + "open_weights": false, + "knowledge": "2025-02-28", + "release_date": "2025-10-15", + "last_updated": "2025-10-15", "type": "chat" }, { - "id": "gpt-5.6-luna", - "name": "GPT-5.6 Luna", - "display_name": "GPT-5.6 Luna", + "id": "gpt-5.2", + "name": "GPT-5.2", + "display_name": "GPT-5.2", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1050000, + "context": 400000, "output": 128000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, + "default_enabled": false, "mode": "effort", - "effort": "medium", + "effort": "none", "effort_options": [ "none", "low", "medium", "high", - "xhigh", - "max" + "xhigh" ], "verbosity": "medium", "verbosity_options": [ @@ -147587,68 +147071,28 @@ }, "attachment": true, "open_weights": false, - "knowledge": "2026-02-16", - "release_date": "2026-07-09", - "last_updated": "2026-07-09", - "type": "chat" - }, - { - "id": "qwen3-32b", - "name": "Qwen3 32B", - "display_name": "Qwen3 32B", - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 16384, - "output": 16384 - }, - "temperature": true, - "tool_call": true, - "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } - }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-04-28", - "last_updated": "2025-04-28", + "knowledge": "2025-08-31", + "release_date": "2025-12-11", + "last_updated": "2025-12-11", "type": "chat" }, { - "id": "claude-4-6-sonnet", - "name": "Claude Sonnet 4.6", - "display_name": "Claude Sonnet 4.6", + "id": "step-3.7-flash:free", + "name": "Step 3.7 Flash", + "display_name": "Step 3.7 Flash", "modalities": { "input": [ "text", "image", - "pdf" + "video" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 256000, + "output": 256000 }, "temperature": true, "tool_call": true, @@ -147656,26 +147100,17 @@ "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "low", - "medium", - "high" - ] - } - }, "attachment": true, - "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-02-17", - "last_updated": "2026-03-13", + "open_weights": true, + "knowledge": "2026-03-01", + "release_date": "2026-05-29", + "last_updated": "2026-05-29", "type": "chat" }, { - "id": "qwen3-235b-a22b-instruct-2507", - "name": "Qwen3 235B-A22B Instruct 2507", - "display_name": "Qwen3 235B-A22B Instruct 2507", + "id": "deepseek-v4-pro:free", + "name": "DeepSeek V4 Pro", + "display_name": "DeepSeek V4 Pro", "modalities": { "input": [ "text" @@ -147685,24 +147120,26 @@ ] }, "limit": { - "context": 262000, - "output": 262000 + "context": 1000000, + "output": 384000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, "attachment": false, "open_weights": true, - "release_date": "2025-07-21", - "last_updated": "2025-07-21", + "knowledge": "2025-05", + "release_date": "2026-04-24", + "last_updated": "2026-04-24", "type": "chat" }, { - "id": "mistral-small-2603", - "name": "Mistral Small 4", - "display_name": "Mistral Small 4", + "id": "gemma-4-31b-it:free", + "name": "Gemma 4 31B IT", + "display_name": "Gemma 4 31B IT", "modalities": { "input": [ "text", @@ -147714,7 +147151,7 @@ }, "limit": { "context": 262144, - "output": 256000 + "output": 32768 }, "temperature": true, "tool_call": true, @@ -147723,30 +147160,31 @@ "default": true }, "attachment": true, - "open_weights": true, - "knowledge": "2025-06", - "release_date": "2026-03-16", - "last_updated": "2026-03-16", + "open_weights": true, + "knowledge": "2025-01", + "release_date": "2026-04-02", + "last_updated": "2026-04-02", "type": "chat" }, { - "id": "qwen3.6-35b-a3b", - "name": "Qwen3.6 35B-A3B", - "display_name": "Qwen3.6 35B-A3B", + "id": "claude-sonnet-5", + "name": "Claude Sonnet 5", + "display_name": "Claude Sonnet 5", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 262000, - "output": 32768 + "context": 1000000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -147754,29 +147192,25 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] + "supported": true } }, "attachment": true, - "open_weights": true, - "release_date": "2026-04-17", - "last_updated": "2026-04-17", + "open_weights": false, + "knowledge": "2026-01-31", + "release_date": "2026-06-30", + "last_updated": "2026-06-30", "type": "chat" }, { - "id": "qwen3.8-flash-next", - "name": "Qwen3.8 Flash Next", - "display_name": "Qwen3.8 Flash Next", + "id": "qwen3.5-397b-a17b:free", + "name": "Qwen3.5 397B-A17B", + "display_name": "Qwen3.5 397B-A17B", "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ "text" @@ -147784,7 +147218,7 @@ }, "limit": { "context": 262144, - "output": 64000 + "output": 65536 }, "temperature": true, "tool_call": true, @@ -147794,23 +147228,25 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "low", - "medium", - "xhigh" + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, "attachment": true, "open_weights": true, - "release_date": "2026-08-27", - "last_updated": "2026-08-27", + "release_date": "2026-02-15", + "last_updated": "2026-02-15", "type": "chat" }, { - "id": "glm-5.1", - "name": "GLM-5.1", - "display_name": "GLM-5.1", + "id": "deepseek-v4-pro", + "name": "DeepSeek V4 Pro", + "display_name": "DeepSeek V4 Pro", "modalities": { "input": [ "text" @@ -147820,8 +147256,8 @@ ] }, "limit": { - "context": 202752, - "output": 202752 + "context": 1000000, + "output": 384000 }, "temperature": true, "tool_call": true, @@ -147832,24 +147268,36 @@ "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "high", + "effort_options": [ + "low", + "high", + "max" + ], "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ "thinking_blocks" + ], + "notes": [ + "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." ] } }, "attachment": false, "open_weights": true, - "release_date": "2026-04-07", - "last_updated": "2026-04-07", + "knowledge": "2025-05", + "release_date": "2026-04-24", + "last_updated": "2026-04-24", "type": "chat" }, { - "id": "qwen3guard-gen-8b", - "name": "qwen3guard-gen-8b", - "display_name": "qwen3guard-gen-8b", + "id": "glm-4.5-flash:free", + "name": "GLM-4.5-Flash", + "display_name": "GLM-4.5-Flash", "modalities": { "input": [ "text" @@ -147859,82 +147307,54 @@ ] }, "limit": { - "context": 32000, - "output": 32000 - }, - "temperature": false, - "tool_call": false, - "reasoning": { - "supported": false - }, - "attachment": false, - "open_weights": false, - "release_date": "2026-02-04", - "last_updated": "2026-02-04", - "type": "chat" - }, - { - "id": "gpt-4.1-mini", - "name": "GPT-4.1 mini", - "display_name": "GPT-4.1 mini", - "modalities": { - "input": [ - "text", - "image", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 1047576, - "output": 32768 + "context": 131072, + "output": 98304 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": true, + "attachment": false, "open_weights": false, - "knowledge": "2024-04", - "release_date": "2025-04-14", - "last_updated": "2025-04-14", + "knowledge": "2025-04", + "release_date": "2025-07-28", + "last_updated": "2025-07-28", "type": "chat" }, { - "id": "ministral-8b-2512", - "name": "Ministral 3 8B 2512", - "display_name": "Ministral 3 8B 2512", + "id": "glm-5.2:free", + "name": "GLM-5.2", + "display_name": "GLM-5.2", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 256000, - "output": 256000 + "context": 1000000, + "output": 131072 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": true, + "attachment": false, "open_weights": true, - "release_date": "2025-12-02", - "last_updated": "2025-12-02", + "release_date": "2026-06-13", + "last_updated": "2026-06-13", "type": "chat" }, { - "id": "claude-haiku-4-5", - "name": "Claude Haiku 4.5 (latest)", - "display_name": "Claude Haiku 4.5 (latest)", + "id": "claude-opus-4-8", + "name": "Claude Opus 4.8", + "display_name": "Claude Opus 4.8", "modalities": { "input": [ "text", @@ -147946,10 +147366,10 @@ ] }, "limit": { - "context": 200000, - "output": 64000 + "context": 1000000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -147959,48 +147379,50 @@ "reasoning": { "supported": true, "default_enabled": false, - "mode": "budget", - "budget": { - "min": 1024, - "unit": "tokens" - }, + "mode": "effort", + "effort": "high", + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" + ], "interleaved": true, "summaries": true, - "visibility": "summary", + "visibility": "omitted", "continuation": [ "thinking_blocks" ], "notes": [ - "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", - "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." + "Claude Opus 4.7 and newer Opus models require thinking.type = \"adaptive\" to enable thinking explicitly.", + "Manual budget_tokens requests return 400 on Claude Opus 4.7 and newer adaptive-only Opus models.", + "task_budget is separate from thinking control and should not be treated as a thinking budget." ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-02-28", - "release_date": "2025-10-15", - "last_updated": "2025-10-15", + "knowledge": "2026-01", + "release_date": "2026-05-28", + "last_updated": "2026-05-28", "type": "chat" }, { - "id": "gemini-3.8-flash", - "name": "Gemini 3.8 Flash", - "display_name": "Gemini 3.8 Flash", + "id": "nemotron-3-ultra-550b-a55b:free", + "name": "Nemotron 3 Ultra 550B A55B", + "display_name": "Nemotron 3 Ultra 550B A55B", "modalities": { "input": [ - "text", - "image", - "audio", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 65535 + "context": 1000000, + "output": 128000 }, "temperature": true, "tool_call": true, @@ -148008,64 +147430,46 @@ "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "level", - "level": "high", - "level_options": [ - "minimal", - "low", - "medium", - "high" - ], - "summaries": true, - "visibility": "summary", - "continuation": [ - "thought_signatures" - ] - } - }, - "attachment": true, - "open_weights": false, - "release_date": "2026-09-02", - "last_updated": "2026-09-02", + "attachment": false, + "open_weights": true, + "knowledge": "2025-09", + "release_date": "2026-06-04", + "last_updated": "2026-06-04", "type": "chat" }, { - "id": "mistral-medium-3.5", - "name": "mistral-medium-3.5", - "display_name": "mistral-medium-3.5", + "id": "deepseek-v4-flash:free", + "name": "DeepSeek V4 Flash", + "display_name": "DeepSeek V4 Flash", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 256000, - "output": 256000 + "context": 1000000, + "output": 384000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, "default": true }, - "attachment": true, - "open_weights": false, - "release_date": "2026-04-30", - "last_updated": "2026-04-30", + "attachment": false, + "open_weights": true, + "knowledge": "2025-05", + "release_date": "2026-04-24", + "last_updated": "2026-04-24", "type": "chat" }, { - "id": "glm-5.3", - "name": "GLM-5.3", - "display_name": "GLM-5.3", + "id": "minimax-m2.7:free", + "name": "MiniMax-M2.7", + "display_name": "MiniMax-M2.7", "modalities": { "input": [ "text" @@ -148075,8 +147479,8 @@ ] }, "limit": { - "context": 1048576, - "output": 1048576 + "context": 204800, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -148084,31 +147488,20 @@ "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "effort_options": [ - "low", - "high", - "max" - ] - } - }, "attachment": false, "open_weights": true, - "release_date": "2026-08-14", - "last_updated": "2026-08-14", + "release_date": "2026-03-18", + "last_updated": "2026-03-18", "type": "chat" }, { - "id": "gemini-4-argon", - "name": "Gemini 4 Argon", - "display_name": "Gemini 4 Argon", + "id": "gpt-5.5:free", + "name": "GPT-5.5", + "display_name": "GPT-5.5", "modalities": { "input": [ "text", "image", - "audio", "pdf" ], "output": [ @@ -148116,8 +147509,8 @@ ] }, "limit": { - "context": 1048576, - "output": 65535 + "context": 1050000, + "output": 128000 }, "temperature": false, "tool_call": true, @@ -148125,31 +147518,20 @@ "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "low", - "medium", - "high" - ] - } - }, "attachment": true, "open_weights": false, - "knowledge": "2026-03", - "release_date": "2026-10-13", - "last_updated": "2026-10-13", + "knowledge": "2025-12-01", + "release_date": "2026-04-23", + "last_updated": "2026-04-23", "type": "chat" }, { - "id": "claude-sonnet-5.5", - "name": "Claude Sonnet 5.5", - "display_name": "Claude Sonnet 5.5", + "id": "deepseek-v4-flash", + "name": "DeepSeek V4 Flash", + "display_name": "DeepSeek V4 Flash", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" @@ -148157,9 +147539,9 @@ }, "limit": { "context": 1000000, - "output": 128000 + "output": 384000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -148167,39 +147549,52 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "high", "effort_options": [ "low", - "medium", - "high" + "high", + "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-06", - "release_date": "2026-09-28", - "last_updated": "2026-09-28", + "attachment": false, + "open_weights": true, + "knowledge": "2025-05", + "release_date": "2026-04-24", + "last_updated": "2026-04-24", "type": "chat" }, { - "id": "gemini-3.6-flash", - "name": "Gemini 3.6 Flash", - "display_name": "Gemini 3.6 Flash", + "id": "gpt-5.5", + "name": "GPT-5.5", + "display_name": "GPT-5.5", "modalities": { "input": [ "text", "image", - "audio" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 65535 + "context": 1050000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -148209,74 +147604,78 @@ "reasoning": { "supported": true, "default_enabled": true, - "mode": "level", - "level": "high", - "level_options": [ - "minimal", + "mode": "effort", + "effort": "medium", + "effort_options": [ + "low", + "medium", + "high", + "xhigh" + ], + "verbosity": "medium", + "verbosity_options": [ "low", "medium", "high" ], - "summaries": true, - "visibility": "summary", - "continuation": [ - "thought_signatures" - ] + "visibility": "hidden" } }, "attachment": true, "open_weights": false, - "knowledge": "2026-03", - "release_date": "2026-07-21", - "last_updated": "2026-07-21", + "knowledge": "2025-12-01", + "release_date": "2026-04-23", + "last_updated": "2026-04-23", "type": "chat" }, { - "id": "gpt-4.1", - "name": "GPT-4.1", - "display_name": "GPT-4.1", + "id": "glm-5.2", + "name": "GLM-5.2", + "display_name": "GLM-5.2", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1047576, - "output": 32768 + "context": 1000000, + "output": 131072 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": true, - "open_weights": false, - "knowledge": "2024-04", - "release_date": "2025-04-14", - "last_updated": "2025-04-14", + "extra_capabilities": { + "reasoning": { + "supported": true + } + }, + "attachment": false, + "open_weights": true, + "release_date": "2026-06-13", + "last_updated": "2026-06-13", "type": "chat" }, { - "id": "qwen3.6-27b", - "name": "Qwen3.6 27B", - "display_name": "Qwen3.6 27B", + "id": "minimax-m2.7", + "name": "MiniMax-M2.7", + "display_name": "MiniMax-M2.7", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 262000, - "output": 262000 + "context": 204800, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -148295,27 +147694,29 @@ ] } }, - "attachment": true, + "attachment": false, "open_weights": true, - "release_date": "2026-04-22", - "last_updated": "2026-04-22", + "release_date": "2026-03-18", + "last_updated": "2026-03-18", "type": "chat" }, { - "id": "qwen3.5-397b-a17b", - "name": "Qwen3.5 397B-A17B", - "display_name": "Qwen3.5 397B-A17B", + "id": "gpt-5.4:free", + "name": "GPT-5.4", + "display_name": "GPT-5.4", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 262000, - "output": 262000 + "context": 1050000, + "output": 128000 }, "temperature": true, "tool_call": true, @@ -148323,38 +147724,29 @@ "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } - }, - "attachment": false, - "open_weights": true, - "release_date": "2026-02-15", - "last_updated": "2026-02-15", + "attachment": true, + "open_weights": false, + "knowledge": "2025-08-31", + "release_date": "2026-03-05", + "last_updated": "2026-03-05", "type": "chat" }, { - "id": "mistral-large-4-0", - "name": "Mistral Large 4", - "display_name": "Mistral Large 4", + "id": "kimi-k2.6", + "name": "Kimi K2.6", + "display_name": "Kimi K2.6", "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 1000000, + "context": 262144, "output": 262144 }, "temperature": true, @@ -148365,18 +147757,24 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "none", - "high" - ] + "supported": true } }, "attachment": true, - "open_weights": false, - "release_date": "2026-10-06", - "last_updated": "2026-10-06", + "open_weights": true, + "knowledge": "2025-01", + "release_date": "2026-04-20", + "last_updated": "2026-04-20", "type": "chat" - }, + } + ] + }, + "google-vertex": { + "id": "google-vertex", + "name": "Vertex", + "display_name": "Vertex", + "doc": "https://cloud.google.com/vertex-ai/generative-ai/docs/models", + "models": [ { "id": "gemini-3.5-flash", "name": "Gemini 3.5 Flash", @@ -148385,7 +147783,9 @@ "input": [ "text", "image", - "audio" + "video", + "audio", + "pdf" ], "output": [ "text" @@ -148393,7 +147793,7 @@ }, "limit": { "context": 1048576, - "output": 65535 + "output": 65536 }, "temperature": true, "tool_call": true, @@ -148428,51 +147828,27 @@ "type": "chat" }, { - "id": "mistral-7b-instruct-v0.2", - "name": "mistral-7b-instruct-v0.2", - "display_name": "mistral-7b-instruct-v0.2", + "id": "gemini-nano-banana-2.1", + "name": "Nano Banana 2.1", + "display_name": "Nano Banana 2.1", "modalities": { "input": [ "text", + "image", + "video", "pdf" ], "output": [ - "text" + "text", + "image" ] }, "limit": { - "context": 32000, - "output": 8192 + "context": 131072, + "output": 32768 }, "temperature": false, "tool_call": false, - "reasoning": { - "supported": false - }, - "attachment": true, - "open_weights": false, - "release_date": "2025-05-26", - "last_updated": "2025-05-26", - "type": "chat" - }, - { - "id": "gpt-oss-safeguard-120b", - "name": "GPT OSS Safeguard 120B", - "display_name": "GPT OSS Safeguard 120B", - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 128000 - }, - "temperature": true, - "tool_call": true, "reasoning": { "supported": true, "default": true @@ -148480,93 +147856,68 @@ "extra_capabilities": { "reasoning": { "effort_options": [ - "low", + "minimal", "medium", "high" ] } }, - "attachment": false, - "open_weights": true, - "release_date": "2025-10-29", - "last_updated": "2025-10-29", - "type": "chat" + "attachment": true, + "open_weights": false, + "release_date": "2026-10-06", + "last_updated": "2026-10-06" }, { - "id": "claude-opus4-7", - "name": "Claude Opus 4.7", - "display_name": "Claude Opus 4.7", + "id": "gemini-3.1-flash-image", + "name": "Nano Banana 2", + "display_name": "Nano Banana 2", "modalities": { "input": [ "text", "image", + "video", "pdf" ], "output": [ - "text" + "text", + "image" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 131072, + "output": 32768 }, - "temperature": false, - "tool_call": true, + "temperature": true, + "tool_call": false, "reasoning": { "supported": true, "default": true }, "extra_capabilities": { "reasoning": { + "supported": true, "effort_options": [ - "low", - "medium", + "minimal", "high" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-01-31", - "release_date": "2026-04-16", - "last_updated": "2026-04-16", - "type": "chat" - }, - { - "id": "minicpm-v-4.5", - "name": "minicpm-v-4.5", - "display_name": "minicpm-v-4.5", - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 32000, - "output": 32000 - }, - "temperature": false, - "tool_call": true, - "reasoning": { - "supported": false - }, - "attachment": false, - "open_weights": false, - "release_date": "2026-06-02", - "last_updated": "2026-06-02", - "type": "chat" + "knowledge": "2025-01", + "release_date": "2026-05-28", + "last_updated": "2026-05-28" }, { - "id": "gpt-6.1-terra", - "name": "GPT-6.1 Terra", - "display_name": "GPT-6.1 Terra", + "id": "gemini-4-argon", + "name": "Gemini 4 Argon", + "display_name": "Gemini 4 Argon", "modalities": { "input": [ "text", "image", + "video", + "audio", "pdf" ], "output": [ @@ -148574,8 +147925,8 @@ ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 1048576, + "output": 65536 }, "temperature": false, "tool_call": true, @@ -148594,118 +147945,69 @@ }, "attachment": true, "open_weights": false, - "knowledge": "2026-06-01", - "release_date": "2026-10-26", - "last_updated": "2026-10-26", + "knowledge": "2026-03", + "release_date": "2026-10-13", + "last_updated": "2026-10-13", "type": "chat" }, { - "id": "mistral-large-2402", - "name": "mistral-large-2402", - "display_name": "mistral-large-2402", + "id": "gemini-3-pro-image", + "name": "Nano Banana Pro", + "display_name": "Nano Banana Pro", "modalities": { "input": [ "text", + "image", "pdf" ], "output": [ - "text" - ] - }, - "limit": { - "context": 32000, - "output": 8192 - }, - "temperature": false, - "tool_call": true, - "reasoning": { - "supported": true, - "default": true - }, - "attachment": true, - "open_weights": false, - "release_date": "2025-05-26", - "last_updated": "2025-05-26", - "type": "chat" - }, - { - "id": "gemma-3-27b-it", - "name": "Gemma 3 27B IT", - "display_name": "Gemma 3 27B IT", - "modalities": { - "input": [ "text", "image" - ], - "output": [ - "text" ] }, "limit": { - "context": 131000, - "output": 110000 + "context": 65536, + "output": 32768 }, - "temperature": false, + "temperature": true, "tool_call": false, "reasoning": { "supported": true, "default": true }, - "attachment": true, - "open_weights": true, - "knowledge": "2024-08", - "release_date": "2025-03-12", - "last_updated": "2025-03-12", - "type": "chat" - }, - { - "id": "gpt-6.1-sol", - "name": "GPT-6.1 Sol", - "display_name": "GPT-6.1 Sol", - "modalities": { - "input": [ - "text", - "image", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 1050000, - "output": 128000 - }, - "temperature": false, - "tool_call": true, - "reasoning": { - "supported": true, - "default": true - }, "extra_capabilities": { "reasoning": { - "effort_options": [ + "supported": true, + "default_enabled": true, + "mode": "level", + "level": "high", + "level_options": [ "low", - "medium", "high" + ], + "summaries": true, + "visibility": "summary", + "continuation": [ + "thought_signatures" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-04-30", - "release_date": "2026-09-29", - "last_updated": "2026-09-29", - "type": "chat" + "knowledge": "2025-01", + "release_date": "2026-05-28", + "last_updated": "2026-05-28" }, { - "id": "claude-haiku-5.5", - "name": "Claude Haiku 5.5", - "display_name": "Claude Haiku 5.5", + "id": "gemini-3.6-flash", + "name": "Gemini 3.6 Flash", + "display_name": "Gemini 3.6 Flash", "modalities": { "input": [ "text", "image", + "video", + "audio", "pdf" ], "output": [ @@ -148713,10 +148015,10 @@ ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 1048576, + "output": 65536 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -148724,78 +148026,80 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ + "supported": true, + "default_enabled": true, + "mode": "level", + "level": "high", + "level_options": [ + "minimal", "low", "medium", "high" + ], + "summaries": true, + "visibility": "summary", + "continuation": [ + "thought_signatures" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-06", - "release_date": "2026-10-07", - "last_updated": "2026-10-07", + "knowledge": "2026-03", + "release_date": "2026-07-21", + "last_updated": "2026-07-21", "type": "chat" }, { - "id": "glm-4.7-flash", - "name": "GLM-4.7-Flash", - "display_name": "GLM-4.7-Flash", + "id": "mistral-medium-3", + "name": "Mistral Medium 3", + "display_name": "Mistral Medium 3", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 203000, - "output": 203000 + "context": 131072, + "output": 131072 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } + "supported": false }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-04", - "release_date": "2026-01-19", - "last_updated": "2026-01-19", + "attachment": true, + "open_weights": false, + "knowledge": "2025-05", + "release_date": "2025-05-07", + "last_updated": "2025-05-07", "type": "chat" }, { - "id": "gpt-5-nano", - "name": "GPT-5 Nano", - "display_name": "GPT-5 Nano", + "id": "gemini-3.8-flash", + "name": "Gemini 3.8 Flash", + "display_name": "Gemini 3.8 Flash", "modalities": { "input": [ "text", - "image" + "image", + "video", + "audio", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 1048576, + "output": 65536 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -148805,49 +148109,48 @@ "reasoning": { "supported": true, "default_enabled": true, - "mode": "effort", - "effort": "medium", - "effort_options": [ + "mode": "level", + "level": "high", + "level_options": [ "minimal", "low", "medium", "high" ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + "summaries": true, + "visibility": "summary", + "continuation": [ + "thought_signatures" + ] } }, "attachment": true, "open_weights": false, - "knowledge": "2024-05-30", - "release_date": "2025-08-07", - "last_updated": "2025-08-07", + "release_date": "2026-09-02", + "last_updated": "2026-09-02", "type": "chat" }, { - "id": "claude-opus-5.5", - "name": "Claude Opus 5.5", - "display_name": "Claude Opus 5.5", + "id": "gemini-3.1-flash-lite-image", + "name": "Nano Banana 2 Lite", + "display_name": "Nano Banana 2 Lite", "modalities": { "input": [ "text", "image", + "video", "pdf" ], "output": [ - "text" + "text", + "image" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 65536, + "output": 4096 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -148855,27 +148158,30 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, "effort_options": [ - "low", - "medium", + "minimal", "high" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-06", - "release_date": "2026-09-22", - "last_updated": "2026-09-22", - "type": "chat" + "knowledge": "2025-01", + "release_date": "2026-06-30", + "last_updated": "2026-06-30" }, { - "id": "glm-5.2", - "name": "GLM-5.2", - "display_name": "GLM-5.2", + "id": "gemini-2.5-pro", + "name": "Gemini 2.5 Pro", + "display_name": "Gemini 2.5 Pro", "modalities": { "input": [ - "text" + "text", + "image", + "audio", + "video", + "pdf" ], "output": [ "text" @@ -148883,7 +148189,7 @@ }, "limit": { "context": 1048576, - "output": 1000000 + "output": 65536 }, "temperature": true, "tool_call": true, @@ -148894,131 +148200,100 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, + "default_enabled": true, + "mode": "budget", + "budget": { + "default": -1, + "min": 128, + "max": 32768, + "auto": -1, + "unit": "tokens" + }, "summaries": true, "visibility": "summary", "continuation": [ - "thinking_blocks" - ], - "effort_options": [ - "high", - "max" + "thought_signatures" ] } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-06-13", - "last_updated": "2026-06-13", + "attachment": true, + "open_weights": false, + "knowledge": "2025-01", + "release_date": "2025-06-17", + "last_updated": "2025-06-17", "type": "chat" }, { - "id": "codestral-2508", - "name": "Codestral 25.08", - "display_name": "Codestral 25.08", + "id": "gemini-2.5-flash-lite", + "name": "Gemini 2.5 Flash-Lite", + "display_name": "Gemini 2.5 Flash-Lite", "modalities": { "input": [ - "text" + "text", + "image", + "audio", + "video", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 256000, - "output": 256000 + "context": 1048576, + "output": 65536 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false - }, - "attachment": false, - "open_weights": false, - "release_date": "2025-07-30", - "last_updated": "2025-07-30", - "type": "chat" - } - ] - }, - "scaleway": { - "id": "scaleway", - "name": "Scaleway", - "display_name": "Scaleway", - "api": "https://api.scaleway.ai/v1", - "doc": "https://www.scaleway.com/en/docs/generative-apis/", - "models": [ - { - "id": "lightonocr-3-0.8b", - "name": "LightOnOCR-3 0.8B", - "display_name": "LightOnOCR-3 0.8B", - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 262144, - "output": 16384 + "supported": true, + "default": false }, - "temperature": true, - "tool_call": false, - "reasoning": { - "supported": false + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": false, + "mode": "budget", + "budget": { + "default": -1, + "min": 512, + "max": 24576, + "auto": -1, + "unit": "tokens" + }, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thought_signatures" + ] + } }, "attachment": true, - "open_weights": true, - "release_date": "2026-10-08", - "last_updated": "2026-10-14", + "open_weights": false, + "knowledge": "2025-01", + "release_date": "2025-06-17", + "last_updated": "2025-06-17", "type": "chat" }, { - "id": "bge-multilingual-gemma2", - "name": "BGE Multilingual Gemma2", - "display_name": "BGE Multilingual Gemma2", - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 8191, - "output": 8192 - }, - "temperature": false, - "tool_call": false, - "reasoning": { - "supported": false - }, - "attachment": false, - "open_weights": true, - "release_date": "2024-07-25", - "last_updated": "2024-07-25", - "type": "embedding" - }, - { - "id": "mistral-medium-3.5-128b", - "name": "Mistral Medium 3.5 128B", - "display_name": "Mistral Medium 3.5 128B", + "id": "gemini-2.5-flash", + "name": "Gemini 2.5 Flash", + "display_name": "Gemini 2.5 Flash", "modalities": { "input": [ "text", - "image" + "image", + "audio", + "video", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 256000, - "output": 16384 + "context": 1048576, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -149028,35 +148303,50 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "none", - "high" + "supported": true, + "default_enabled": true, + "mode": "budget", + "budget": { + "default": -1, + "min": 0, + "max": 24576, + "auto": -1, + "off": 0, + "unit": "tokens" + }, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thought_signatures" ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-04-29", - "last_updated": "2026-04-29", + "open_weights": false, + "knowledge": "2025-01", + "release_date": "2025-06-17", + "last_updated": "2025-06-17", "type": "chat" }, { - "id": "qwen3.8-27b", - "name": "Qwen3.8 27B", - "display_name": "Qwen3.8 27B", + "id": "gemini-3.1-pro-preview", + "name": "Gemini 3.1 Pro Preview", + "display_name": "Gemini 3.1 Pro Preview", "modalities": { "input": [ "text", "image", - "video" + "video", + "audio", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 32768 + "context": 1048576, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -149066,36 +148356,75 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "none", + "supported": true, + "default_enabled": true, + "mode": "level", + "level": "high", + "level_options": [ "low", - "medium", - "xhigh" + "high" + ], + "summaries": true, + "visibility": "summary", + "continuation": [ + "thought_signatures" ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-08-14", - "last_updated": "2026-08-14", + "open_weights": false, + "knowledge": "2025-01", + "release_date": "2026-02-19", + "last_updated": "2026-02-19", "type": "chat" }, { - "id": "deepseek-v4.1-flash-1017", - "name": "DeepSeek V4.1 Flash 1017", - "display_name": "DeepSeek V4.1 Flash 1017", + "id": "gemini-embedding-001", + "name": "Gemini Embedding 001", + "display_name": "Gemini Embedding 001", + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 2048, + "output": 1 + }, + "temperature": false, + "tool_call": false, + "reasoning": { + "supported": false + }, + "attachment": false, + "open_weights": false, + "knowledge": "2025-05", + "release_date": "2025-05-20", + "last_updated": "2025-05-20", + "type": "embedding" + }, + { + "id": "gemini-3.7-flash", + "name": "Gemini 3.7 Flash", + "display_name": "Gemini 3.7 Flash", "modalities": { "input": [ "text", - "image" + "image", + "video", + "audio", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 256000, - "output": 32768 + "context": 1048576, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -149106,53 +148435,87 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "effort_options": [ - "none", + "default_enabled": true, + "mode": "level", + "level": "high", + "level_options": [ + "minimal", "low", - "high", - "max" + "medium", + "high" + ], + "summaries": true, + "visibility": "summary", + "continuation": [ + "thought_signatures" ] } }, "attachment": true, - "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-10-17", - "last_updated": "2026-10-17", + "open_weights": false, + "knowledge": "2026-03", + "release_date": "2026-08-13", + "last_updated": "2026-08-13", "type": "chat" }, { - "id": "whisper-large-v3", - "name": "Whisper Large v3", - "display_name": "Whisper Large v3", + "id": "codestral-2", + "name": "Codestral 2", + "display_name": "Codestral 2", "modalities": { "input": [ - "audio" + "text" ], "output": [ "text" ] }, "limit": { - "context": 8192, + "context": 128000, "output": 8192 }, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": false + }, + "attachment": false, + "open_weights": false, + "release_date": "2025-07-30", + "last_updated": "2025-07-30", + "type": "chat" + }, + { + "id": "gemini-2.5-flash-tts", + "name": "Gemini 2.5 Flash TTS", + "display_name": "Gemini 2.5 Flash TTS", + "modalities": { + "input": [ + "text" + ], + "output": [ + "audio" + ] + }, + "limit": { + "context": 32768, + "output": 16384 + }, "temperature": false, "tool_call": false, "reasoning": { "supported": false }, - "attachment": true, - "open_weights": true, - "knowledge": "2023-09", - "release_date": "2023-09-01", - "last_updated": "2026-03-17" + "attachment": false, + "open_weights": false, + "knowledge": "2025-01", + "release_date": "2025-09-30", + "last_updated": "2025-12-10" }, { - "id": "mistral-small-3.2-24b-instruct-2506", - "name": "Mistral Small 3.2 24B Instruct (2506)", - "display_name": "Mistral Small 3.2 24B Instruct (2506)", + "id": "mistral-small-2503", + "name": "Mistral Small 3.1 24B", + "display_name": "Mistral Small 3.1 24B", "modalities": { "input": [ "text", @@ -149164,7 +148527,7 @@ }, "limit": { "context": 128000, - "output": 32768 + "output": 16384 }, "temperature": true, "tool_call": true, @@ -149173,26 +148536,30 @@ }, "attachment": true, "open_weights": true, - "knowledge": "2025-03", - "release_date": "2025-06-20", - "last_updated": "2025-06-20", + "knowledge": "2024-06", + "release_date": "2025-03-17", + "last_updated": "2025-03-17", "type": "chat" }, { - "id": "gpt-oss-120b", - "name": "GPT-OSS 120B", - "display_name": "GPT-OSS 120B", + "id": "gemini-3.1-flash-lite", + "name": "Gemini 3.1 Flash Lite", + "display_name": "Gemini 3.1 Flash Lite", "modalities": { "input": [ - "text" + "text", + "image", + "video", + "audio", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 32768 + "context": 1048576, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -149204,34 +148571,39 @@ "reasoning": { "supported": true, "effort_options": [ + "minimal", "low", "medium", "high" ] } }, - "attachment": false, - "open_weights": true, - "knowledge": "2024-06", - "release_date": "2025-08-05", - "last_updated": "2025-08-05", + "attachment": true, + "open_weights": false, + "knowledge": "2025-01", + "release_date": "2026-05-07", + "last_updated": "2026-05-07", "type": "chat" }, { - "id": "deepseek-v4-flash-0731", - "name": "DeepSeek V4 Flash 0731", - "display_name": "DeepSeek V4 Flash 0731", + "id": "gemini-3.1-pro-preview-customtools", + "name": "Gemini 3.1 Pro Preview Custom Tools", + "display_name": "Gemini 3.1 Pro Preview Custom Tools", "modalities": { "input": [ - "text" + "text", + "image", + "video", + "audio", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 256000, - "output": 32768 + "context": 1048576, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -149242,66 +148614,46 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "effort_options": [ - "none", + "default_enabled": true, + "mode": "level", + "level": "high", + "level_options": [ "low", - "high", - "max" + "high" + ], + "summaries": true, + "visibility": "summary", + "continuation": [ + "thought_signatures" ] } }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-07-31", - "last_updated": "2026-07-31", - "type": "chat" - }, - { - "id": "llama-3.3-70b-instruct", - "name": "Llama-3.3-70B-Instruct", - "display_name": "Llama-3.3-70B-Instruct", - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 100000, - "output": 16384 - }, - "temperature": true, - "tool_call": true, - "reasoning": { - "supported": false - }, - "attachment": false, - "open_weights": true, - "knowledge": "2023-12", - "release_date": "2024-12-06", - "last_updated": "2024-12-06", + "attachment": true, + "open_weights": false, + "knowledge": "2025-01", + "release_date": "2026-02-19", + "last_updated": "2026-02-19", "type": "chat" }, { - "id": "gemma-4-26b-a4b-it", - "name": "Gemma 4 26B A4B IT", - "display_name": "Gemma 4 26B A4B IT", + "id": "gemini-3-flash-preview", + "name": "Gemini 3 Flash Preview", + "display_name": "Gemini 3 Flash Preview", "modalities": { "input": [ "text", - "image" + "image", + "video", + "audio", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 256000, - "output": 16384 + "context": 1048576, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -149312,68 +148664,77 @@ "extra_capabilities": { "reasoning": { "supported": true, - "effort_options": [ - "none", + "default_enabled": true, + "mode": "level", + "level": "high", + "level_options": [ + "minimal", "low", "medium", "high" + ], + "summaries": true, + "visibility": "summary", + "continuation": [ + "thought_signatures" ] } }, "attachment": true, - "open_weights": true, - "knowledge": "2025-04", - "release_date": "2026-04-02", - "last_updated": "2026-04-02", + "open_weights": false, + "knowledge": "2025-01", + "release_date": "2025-12-17", + "last_updated": "2025-12-17", "type": "chat" }, { - "id": "qwen3-235b-a22b-instruct-2507", - "name": "Qwen3 235B A22B Instruct 2507", - "display_name": "Qwen3 235B A22B Instruct 2507", + "id": "gemini-2.5-flash-image", + "name": "Nano Banana", + "display_name": "Nano Banana", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ - "text" + "text", + "image" ] }, "limit": { - "context": 260000, - "output": 16384 + "context": 32768, + "output": 32768 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { "supported": false }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-07-21", - "last_updated": "2025-07-21", - "type": "chat" + "attachment": true, + "open_weights": false, + "knowledge": "2024-06", + "release_date": "2025-08-26", + "last_updated": "2025-08-26" }, { - "id": "qwen3.6-35b-a3b", - "name": "Qwen3.6 35B A3B", - "display_name": "Qwen3.6 35B A3B", + "id": "gemini-2.5-pro-tts", + "name": "Gemini 2.5 Pro TTS", + "display_name": "Gemini 2.5 Pro TTS", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ - "text" + "audio" ] }, "limit": { - "context": 128000, + "context": 32768, "output": 16384 }, - "temperature": true, - "tool_call": true, + "temperature": false, + "tool_call": false, "reasoning": { "supported": true, "default": true @@ -149381,42 +148742,47 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, + "default_enabled": true, + "mode": "budget", + "budget": { + "default": -1, + "min": 128, + "max": 32768, + "auto": -1, + "unit": "tokens" + }, "summaries": true, "visibility": "summary", "continuation": [ - "thinking_blocks" - ], - "effort_options": [ - "none", - "low", - "medium", - "high" + "thought_signatures" ] } }, - "attachment": true, - "open_weights": true, - "knowledge": "2025-04", - "release_date": "2026-04-17", - "last_updated": "2026-04-17", - "type": "chat" + "attachment": false, + "open_weights": false, + "knowledge": "2025-01", + "release_date": "2025-09-30", + "last_updated": "2025-12-10" }, { - "id": "glm-5.3", - "name": "GLM-5.3", - "display_name": "GLM-5.3", + "id": "gemini-3.5-flash-lite", + "name": "Gemini 3.5 Flash Lite", + "display_name": "Gemini 3.5 Flash Lite", "modalities": { "input": [ - "text" + "text", + "image", + "video", + "audio", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 256000, - "output": 16384 + "context": 1048576, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -149428,22 +148794,24 @@ "reasoning": { "supported": true, "effort_options": [ + "minimal", "low", - "high", - "max" + "medium", + "high" ] } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-08-14", - "last_updated": "2026-08-14", + "attachment": true, + "open_weights": false, + "knowledge": "2026-03", + "release_date": "2026-07-21", + "last_updated": "2026-07-21", "type": "chat" }, { - "id": "qwen3-embedding-8b", - "name": "Qwen3 Embedding 8B", - "display_name": "Qwen3 Embedding 8B", + "id": "openai/gpt-oss-120b-maas", + "name": "GPT OSS 120B", + "display_name": "GPT OSS 120B", "modalities": { "input": [ "text" @@ -149453,37 +148821,8 @@ ] }, "limit": { - "context": 32768, - "output": 8192 - }, - "temperature": false, - "tool_call": false, - "reasoning": { - "supported": false - }, - "attachment": false, - "open_weights": true, - "release_date": "2025-06-05", - "last_updated": "2025-06-05", - "type": "embedding" - }, - { - "id": "qwen3.5-397b-a17b", - "name": "Qwen3.5 397B A17B", - "display_name": "Qwen3.5 397B A17B", - "modalities": { - "input": [ - "text", - "image", - "video" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 256000, - "output": 16384 + "context": 131072, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -149493,32 +148832,24 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], "effort_options": [ - "none", "low", "medium", "high" ] } }, - "attachment": true, + "attachment": false, "open_weights": true, - "knowledge": "2025-04", - "release_date": "2026-02-15", - "last_updated": "2026-02-15", + "knowledge": "2024-06", + "release_date": "2025-08-05", + "last_updated": "2025-08-05", "type": "chat" }, { - "id": "glm-5.2", - "name": "GLM-5.2", - "display_name": "GLM-5.2", + "id": "openai/gpt-oss-20b-maas", + "name": "GPT OSS 20B", + "display_name": "GPT OSS 20B", "modalities": { "input": [ "text" @@ -149528,8 +148859,8 @@ ] }, "limit": { - "context": 256000, - "output": 16384 + "context": 131072, + "output": 32768 }, "temperature": true, "tool_call": true, @@ -149539,9 +148870,7 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, "effort_options": [ - "none", "low", "medium", "high" @@ -149550,38 +148879,26 @@ }, "attachment": false, "open_weights": true, - "release_date": "2026-06-13", - "last_updated": "2026-06-13", + "release_date": "2025-08-05", + "last_updated": "2025-08-05", "type": "chat" - } - ] - }, - "bothub": { - "id": "bothub", - "name": "Bothub", - "display_name": "Bothub", - "api": "https://openai.bothub.ru/v1", - "doc": "https://bothub.ru/models", - "models": [ + }, { - "id": "muse-spark-1.3-contributor", - "name": "Muse Spark 1.3 Contributor", - "display_name": "Muse Spark 1.3 Contributor", + "id": "xai/grok-4.7", + "name": "Grok 4.7", + "display_name": "Grok 4.7", "modalities": { "input": [ "text", - "image", - "video", - "pdf", - "audio" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 943718 + "context": 524288, + "output": 500000 }, "temperature": true, "tool_call": true, @@ -149591,69 +148908,52 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, "effort_options": [ - "minimal", "low", "medium", - "high", - "xhigh" + "high" ] } }, "attachment": true, "open_weights": false, - "release_date": "2026-09-02", - "last_updated": "2026-09-02", + "knowledge": "2026-05", + "release_date": "2026-09-21", + "last_updated": "2026-09-21", "type": "chat" }, { - "id": "glm-5.3-flash", - "name": "GLM-5.3-Flash", - "display_name": "GLM-5.3-Flash", + "id": "xai/grok-4.1-fast-non-reasoning", + "name": "Grok 4.1 Fast", + "display_name": "Grok 4.1 Fast", "modalities": { "input": [ "text", - "image", - "video", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 128000, + "output": 30000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "effort_options": [ - "low", - "high", - "max" - ] - } + "supported": false }, "attachment": true, - "open_weights": true, - "release_date": "2026-08-26", - "last_updated": "2026-08-26", + "open_weights": false, + "release_date": "2025-11-19", + "last_updated": "2025-11-19", "type": "chat" }, { - "id": "gemma-4-31b-it:free", - "name": "Gemma 4 31B IT (free)", - "display_name": "Gemma 4 31B IT (free)", + "id": "xai/grok-4.1-fast-reasoning", + "name": "Grok 4.1 Fast (Reasoning)", + "display_name": "Grok 4.1 Fast (Reasoning)", "modalities": { "input": [ "text", @@ -149664,8 +148964,8 @@ ] }, "limit": { - "context": 262144, - "output": 32768 + "context": 128000, + "output": 30000 }, "temperature": true, "tool_call": true, @@ -149673,34 +148973,28 @@ "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true - } - }, "attachment": true, - "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-04-02", - "last_updated": "2026-04-02", + "open_weights": false, + "release_date": "2025-11-19", + "last_updated": "2025-11-19", "type": "chat" }, { - "id": "deepseek-v4-flash-0731", - "name": "DeepSeek V4 Flash 0731", - "display_name": "DeepSeek V4 Flash 0731", + "id": "xai/grok-4.3", + "name": "Grok 4.3", + "display_name": "Grok 4.3", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 384000 + "context": 200000, + "output": 30000 }, "temperature": true, "tool_call": true, @@ -149710,81 +149004,61 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "effort_options": [ - "none", - "low", - "high", - "max" - ] + "supported": true } }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-07-31", - "last_updated": "2026-07-31", + "attachment": true, + "open_weights": false, + "release_date": "2026-04-17", + "last_updated": "2026-04-17", "type": "chat" }, { - "id": "deepseek-v4-pro-0813", - "name": "DeepSeek V4 Pro 0813", - "display_name": "DeepSeek V4 Pro 0813", + "id": "xai/grok-4.20-non-reasoning", + "name": "Grok 4.20 (Non-Reasoning)", + "display_name": "Grok 4.20 (Non-Reasoning)", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 384000 + "context": 2000000, + "output": 30000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "effort_options": [ - "none", - "high", - "max" - ] - } + "supported": false }, - "attachment": false, - "open_weights": true, - "release_date": "2026-08-13", - "last_updated": "2026-08-22", + "attachment": true, + "open_weights": false, + "release_date": "2026-03-09", + "last_updated": "2026-03-09", "type": "chat" }, { - "id": "gpt-5.6-luna", - "name": "GPT-5.6 Luna", - "display_name": "GPT-5.6 Luna", + "id": "xai/grok-4.6", + "name": "Grok 4.6", + "display_name": "Grok 4.6", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 524288, + "output": 500000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -149793,49 +149067,36 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", "effort_options": [ - "none", - "low", - "medium", - "high", - "xhigh", - "max" - ], - "verbosity": "medium", - "verbosity_options": [ "low", "medium", "high" - ], - "interleaved": true, - "visibility": "hidden" + ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-02-16", - "release_date": "2026-07-09", - "last_updated": "2026-07-09", + "knowledge": "2026-02-01", + "release_date": "2026-08-12", + "last_updated": "2026-08-12", "type": "chat" }, { - "id": "nemotron-3-ultra-550b-a55b:free", - "name": "Nemotron 3 Ultra (free)", - "display_name": "Nemotron 3 Ultra (free)", + "id": "xai/grok-4.20-reasoning", + "name": "Grok 4.20 (Reasoning)", + "display_name": "Grok 4.20 (Reasoning)", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 2000000, + "output": 30000 }, "temperature": true, "tool_call": true, @@ -149843,28 +149104,16 @@ "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "effort_options": [ - "none", - "medium", - "high" - ] - } - }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-09", - "release_date": "2026-06-04", - "last_updated": "2026-06-04", + "attachment": true, + "open_weights": false, + "release_date": "2026-03-09", + "last_updated": "2026-03-09", "type": "chat" }, { - "id": "glm-5.3", - "name": "GLM-5.3", - "display_name": "GLM-5.3", + "id": "moonshotai/kimi-k2-thinking-maas", + "name": "Kimi K2 Thinking", + "display_name": "Kimi K2 Thinking", "modalities": { "input": [ "text" @@ -149874,8 +149123,8 @@ ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 262144, + "output": 262144 }, "temperature": true, "tool_call": true, @@ -149887,45 +149136,35 @@ "reasoning": { "supported": true, "interleaved": true, - "effort_options": [ - "low", - "high", - "max" + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, "attachment": false, "open_weights": true, - "release_date": "2026-08-14", - "last_updated": "2026-08-14", + "knowledge": "2024-08", + "release_date": "2025-11-13", + "last_updated": "2025-11-13", "type": "chat" - } - ] - }, - "zai": { - "id": "zai", - "name": "Z.AI", - "display_name": "Z.AI", - "api": "https://api.z.ai/api/paas/v4", - "doc": "https://docs.z.ai/guides/overview/pricing", - "models": [ + }, { - "id": "glm-4.6v-flash", - "name": "GLM-4.6V-Flash", - "display_name": "GLM-4.6V-Flash", + "id": "zai-org/glm-5.2-maas", + "name": "GLM-5.2", + "display_name": "GLM-5.2", "modalities": { "input": [ - "text", - "image", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 32768 + "context": 1000000, + "output": 64000 }, "temperature": true, "tool_call": true, @@ -149944,16 +149183,16 @@ ] } }, - "attachment": true, + "attachment": false, "open_weights": true, - "release_date": "2025-12-08", - "last_updated": "2025-12-08", + "release_date": "2026-06-13", + "last_updated": "2026-06-13", "type": "chat" }, { - "id": "glm-4.7-flashx", - "name": "GLM-4.7-FlashX", - "display_name": "GLM-4.7-FlashX", + "id": "zai-org/glm-4.7-maas", + "name": "GLM-4.7", + "display_name": "GLM-4.7", "modalities": { "input": [ "text" @@ -149964,7 +149203,7 @@ }, "limit": { "context": 200000, - "output": 131072 + "output": 128000 }, "temperature": true, "tool_call": true, @@ -149986,27 +149225,25 @@ "attachment": false, "open_weights": true, "knowledge": "2025-04", - "release_date": "2026-01-19", - "last_updated": "2026-01-19", + "release_date": "2026-01-06", + "last_updated": "2026-01-06", "type": "chat" }, { - "id": "glm-4.5v", - "name": "GLM-4.5V", - "display_name": "GLM-4.5V", + "id": "zai-org/glm-5-maas", + "name": "GLM-5", + "display_name": "GLM-5", "modalities": { "input": [ - "text", - "image", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 64000, - "output": 16384 + "context": 200000, + "output": 128000 }, "temperature": true, "tool_call": true, @@ -150025,31 +149262,28 @@ ] } }, - "attachment": true, + "attachment": false, "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-08-11", - "last_updated": "2025-08-11", + "release_date": "2026-02-12", + "last_updated": "2026-02-12", "type": "chat" }, { - "id": "glm-5.3-flash", - "name": "GLM-5.3-Flash", - "display_name": "GLM-5.3-Flash", + "id": "google/gemma-4-26b-a4b-it-maas", + "name": "Gemma 4 26B A4B IT", + "display_name": "Gemma 4 26B A4B IT", "modalities": { "input": [ "text", - "image", - "video", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 262144, + "output": 128000 }, "temperature": true, "tool_call": true, @@ -150065,66 +149299,46 @@ "visibility": "summary", "continuation": [ "thinking_blocks" - ], - "effort_options": [ - "low", - "high", - "max" ] } }, "attachment": true, "open_weights": true, - "release_date": "2026-08-26", - "last_updated": "2026-08-26", + "release_date": "2026-04-02", + "last_updated": "2026-04-02", "type": "chat" }, { - "id": "glm-5v-turbo", - "name": "GLM-5V-Turbo", - "display_name": "GLM-5V-Turbo", + "id": "qwen/qwen3-235b-a22b-instruct-2507-maas", + "name": "Qwen3 235B A22B Instruct", + "display_name": "Qwen3 235B A22B Instruct", "modalities": { "input": [ - "text", - "image", - "video", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 131072 + "context": 262144, + "output": 16384 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } + "supported": false }, - "attachment": true, - "open_weights": false, - "release_date": "2026-04-01", - "last_updated": "2026-04-01", + "attachment": false, + "open_weights": true, + "release_date": "2025-08-13", + "last_updated": "2025-08-13", "type": "chat" }, { - "id": "glm-5-turbo", - "name": "GLM-5-Turbo", - "display_name": "GLM-5-Turbo", + "id": "meta/llama-3.3-70b-instruct-maas", + "name": "Llama 3.3 70B Instruct", + "display_name": "Llama 3.3 70B Instruct", "modalities": { "input": [ "text" @@ -150134,78 +149348,83 @@ ] }, "limit": { - "context": 200000, - "output": 131072 + "context": 128000, + "output": 8192 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } + "supported": false }, "attachment": false, - "open_weights": false, - "release_date": "2026-03-16", - "last_updated": "2026-03-16", + "open_weights": true, + "knowledge": "2023-12", + "release_date": "2025-04-29", + "last_updated": "2025-04-29", "type": "chat" }, { - "id": "glm-4.6v", - "name": "GLM-4.6V", - "display_name": "GLM-4.6V", + "id": "meta/llama-4-maverick-17b-128e-instruct-maas", + "name": "Llama 4 Maverick 17B 128E Instruct", + "display_name": "Llama 4 Maverick 17B 128E Instruct", "modalities": { "input": [ "text", - "image", - "video" + "image" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 32768 + "context": 524288, + "output": 8192 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true + "supported": false }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } + "attachment": true, + "open_weights": true, + "knowledge": "2024-08", + "release_date": "2025-04-29", + "last_updated": "2025-04-29", + "type": "chat" + }, + { + "id": "meta/llama-4-scout-17b-16e-instruct-maas", + "name": "Llama 4 Scout 17B Instruct", + "display_name": "Llama 4 Scout 17B Instruct", + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1310720, + "output": 8192 + }, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": false }, "attachment": true, "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-12-08", - "last_updated": "2025-12-08", + "knowledge": "2024-08", + "release_date": "2025-04-05", + "last_updated": "2025-04-05", "type": "chat" }, { - "id": "glm-4.7", - "name": "GLM-4.7", - "display_name": "GLM-4.7", + "id": "deepseek-ai/deepseek-v3.1-maas", + "name": "DeepSeek V3.1", + "display_name": "DeepSeek V3.1", "modalities": { "input": [ "text" @@ -150215,8 +149434,8 @@ ] }, "limit": { - "context": 204800, - "output": 131072 + "context": 163840, + "output": 32768 }, "temperature": true, "tool_call": true, @@ -150224,28 +149443,16 @@ "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } - }, "attachment": false, "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-12-22", - "last_updated": "2025-12-22", + "release_date": "2025-08-28", + "last_updated": "2025-08-28", "type": "chat" }, { - "id": "glm-4.6", - "name": "GLM-4.6", - "display_name": "GLM-4.6", + "id": "deepseek-ai/deepseek-v3.2-maas", + "name": "DeepSeek V3.2", + "display_name": "DeepSeek V3.2", "modalities": { "input": [ "text" @@ -150255,8 +149462,8 @@ ] }, "limit": { - "context": 204800, - "output": 131072 + "context": 163840, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -150277,60 +149484,80 @@ }, "attachment": false, "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-09-30", - "last_updated": "2025-09-30", + "release_date": "2025-12-17", + "last_updated": "2026-04-04", "type": "chat" - }, + } + ] + }, + "daoxe": { + "id": "daoxe", + "name": "DaoXE", + "display_name": "DaoXE", + "api": "https://daoxe.com/v1", + "doc": "https://daoxe.com/pricing", + "models": [ { - "id": "glm-4.5-air", - "name": "GLM-4.5-Air", - "display_name": "GLM-4.5-Air", + "id": "gpt-5.4", + "name": "GPT-5.4", + "display_name": "GPT-5.4", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 98304 + "context": 1050000, + "output": 128000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] + "default_enabled": false, + "mode": "effort", + "effort": "none", + "effort_options": [ + "none", + "low", + "medium", + "high", + "xhigh" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-07-28", - "last_updated": "2025-07-28", + "attachment": true, + "open_weights": false, + "knowledge": "2025-08-31", + "release_date": "2026-03-05", + "last_updated": "2026-03-05", "type": "chat" }, { - "id": "glm-5.3-flashx", - "name": "GLM-5.3-FlashX", - "display_name": "GLM-5.3-FlashX", + "id": "claude-haiku-4-5-20251001", + "name": "Claude Haiku 4.5", + "display_name": "Claude Haiku 4.5", "modalities": { "input": [ "text", "image", - "video", "pdf" ], "output": [ @@ -150338,52 +149565,59 @@ ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 200000, + "output": 64000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": false, + "mode": "budget", + "budget": { + "min": 1024, + "unit": "tokens" + }, "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ "thinking_blocks" ], - "effort_options": [ - "low", - "high", - "max" + "notes": [ + "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", + "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-09-18", - "last_updated": "2026-09-18", + "open_weights": false, + "knowledge": "2025-02-28", + "release_date": "2025-10-15", + "last_updated": "2025-10-15", "type": "chat" }, { - "id": "glm-5.1", - "name": "GLM-5.1", - "display_name": "GLM-5.1", + "id": "grok-4.5", + "name": "Grok 4.5", + "display_name": "Grok 4.5", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 131072 + "context": 500000, + "output": 500000 }, "temperature": true, "tool_call": true, @@ -150394,66 +149628,87 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "low", + "medium", + "high" ] } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-04-07", - "last_updated": "2026-04-07", + "attachment": true, + "open_weights": false, + "release_date": "2026-07-08", + "last_updated": "2026-07-08", "type": "chat" }, { - "id": "glm-5", - "name": "GLM-5", - "display_name": "GLM-5", + "id": "claude-sonnet-4-6", + "name": "Claude Sonnet 4.6", + "display_name": "Claude Sonnet 4.6", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 204800, - "output": 131072 + "context": 1000000, + "output": 64000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": false, + "mode": "mixed", + "budget": { + "min": 1024, + "unit": "tokens" + }, + "effort": "high", + "effort_options": [ + "low", + "medium", + "high", + "max" + ], "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ "thinking_blocks" + ], + "notes": [ + "Anthropic recommends adaptive thinking with effort for Claude 4.6; budget_tokens remains a deprecated compatibility path.", + "Anthropic API defaults effort to high; lower effort levels should be chosen per workload." ] } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-02-12", - "last_updated": "2026-02-12", + "attachment": true, + "open_weights": false, + "knowledge": "2025-08-31", + "release_date": "2026-02-17", + "last_updated": "2026-03-13", "type": "chat" }, { - "id": "glm-5.3", - "name": "GLM-5.3", - "display_name": "GLM-5.3", + "id": "grok-4.3", + "name": "Grok 4.3", + "display_name": "Grok 4.3", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" @@ -150461,7 +149716,7 @@ }, "limit": { "context": 1000000, - "output": 131072 + "output": 30000 }, "temperature": true, "tool_call": true, @@ -150472,80 +149727,95 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], "effort_options": [ "low", - "high", - "max" + "medium", + "high" ] } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-08-14", - "last_updated": "2026-08-14", + "attachment": true, + "open_weights": false, + "release_date": "2026-04-17", + "last_updated": "2026-04-17", "type": "chat" }, { - "id": "glm-4.5-flash", - "name": "GLM-4.5-Flash", - "display_name": "GLM-4.5-Flash", + "id": "claude-opus-4-8", + "name": "Claude Opus 4.8", + "display_name": "Claude Opus 4.8", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 98304 + "context": 1000000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": false, + "mode": "effort", + "effort": "high", + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" + ], "interleaved": true, "summaries": true, - "visibility": "summary", + "visibility": "omitted", "continuation": [ "thinking_blocks" + ], + "notes": [ + "Claude Opus 4.7 and newer Opus models require thinking.type = \"adaptive\" to enable thinking explicitly.", + "Manual budget_tokens requests return 400 on Claude Opus 4.7 and newer adaptive-only Opus models.", + "task_budget is separate from thinking control and should not be treated as a thinking budget." ] } }, - "attachment": false, + "attachment": true, "open_weights": false, - "knowledge": "2025-04", - "release_date": "2025-07-28", - "last_updated": "2025-07-28", + "knowledge": "2026-01", + "release_date": "2026-05-28", + "last_updated": "2026-05-28", "type": "chat" }, { - "id": "glm-4.5", - "name": "GLM-4.5", - "display_name": "GLM-4.5", + "id": "gemini-3.1-pro-preview", + "name": "Gemini 3.1 Pro Preview", + "display_name": "Gemini 3.1 Pro Preview", "modalities": { "input": [ - "text" + "text", + "image", + "video", + "audio", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 98304 + "context": 1048576, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -150556,38 +149826,46 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, + "default_enabled": true, + "mode": "level", + "level": "high", + "level_options": [ + "low", + "high" + ], "summaries": true, "visibility": "summary", "continuation": [ - "thinking_blocks" + "thought_signatures" ] } }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-07-28", - "last_updated": "2025-07-28", + "attachment": true, + "open_weights": false, + "knowledge": "2025-01", + "release_date": "2026-02-19", + "last_updated": "2026-02-19", "type": "chat" }, { - "id": "glm-4.7-flash", - "name": "GLM-4.7-Flash", - "display_name": "GLM-4.7-Flash", + "id": "gpt-5.5", + "name": "GPT-5.5", + "display_name": "GPT-5.5", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 131072 + "context": 1050000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -150596,38 +149874,50 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] + "default_enabled": true, + "mode": "effort", + "effort": "medium", + "effort_options": [ + "low", + "medium", + "high", + "xhigh" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-04", - "release_date": "2026-01-19", - "last_updated": "2026-01-19", + "attachment": true, + "open_weights": false, + "knowledge": "2025-12-01", + "release_date": "2026-04-23", + "last_updated": "2026-04-23", "type": "chat" }, { - "id": "glm-5.2", - "name": "GLM-5.2", - "display_name": "GLM-5.2", + "id": "kimi-k2.5", + "name": "Kimi K2.5", + "display_name": "Kimi K2.5", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 262144, + "output": 262144 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -150643,114 +149933,110 @@ "thinking_blocks" ], "effort_options": [ - "high", - "max" + "low", + "medium", + "high" ] } }, - "attachment": false, + "attachment": true, "open_weights": true, - "release_date": "2026-06-13", - "last_updated": "2026-06-13", + "knowledge": "2025-01", + "release_date": "2026-01-27", + "last_updated": "2026-01-27", "type": "chat" } ] }, - "perplexity-agent": { - "id": "perplexity-agent", - "name": "Perplexity Agent", - "display_name": "Perplexity Agent", - "api": "https://api.perplexity.ai/v1", - "doc": "https://docs.perplexity.ai/docs/agent-api/models", + "ai21": { + "id": "ai21", + "name": "AI21 Labs", + "display_name": "AI21 Labs", + "api": "https://api.ai21.com/studio/v1", + "doc": "https://docs.ai21.com/docs/jamba-foundation-models", "models": [ { - "id": "xai/grok-4.5", - "name": "Grok 4.5", - "display_name": "Grok 4.5", + "id": "jamba-mini", + "name": "Jamba Mini", + "display_name": "Jamba Mini", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 500000, - "output": 500000 + "context": 256000, + "output": 4096 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "effort_options": [ - "low", - "medium", - "high", - "xhigh" - ] - } + "supported": false }, - "attachment": true, - "open_weights": false, - "release_date": "2026-07-08", - "last_updated": "2026-07-08", + "attachment": false, + "open_weights": true, + "knowledge": "2024-08-22", + "release_date": "2026-01-08", + "last_updated": "2026-01-08", "type": "chat" }, { - "id": "xai/grok-4-1-fast-non-reasoning", - "name": "Grok 4.1 Fast (Non-Reasoning)", - "display_name": "Grok 4.1 Fast (Non-Reasoning)", + "id": "jamba-large", + "name": "Jamba Large", + "display_name": "Jamba Large", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 2000000, - "output": 30000 + "context": 256000, + "output": 4096 }, "temperature": true, "tool_call": true, "reasoning": { "supported": false }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-07", - "release_date": "2025-11-19", - "last_updated": "2025-11-19", + "attachment": false, + "open_weights": true, + "knowledge": "2024-08-22", + "release_date": "2025-07-03", + "last_updated": "2025-07-03", "type": "chat" - }, + } + ] + }, + "nearai": { + "id": "nearai", + "name": "NEAR AI Cloud", + "display_name": "NEAR AI Cloud", + "api": "https://cloud-api.near.ai/v1", + "doc": "https://docs.near.ai/", + "models": [ { - "id": "xai/grok-4.7", - "name": "Grok 4.7", - "display_name": "Grok 4.7", + "id": "openai/o3-mini", + "name": "o3-mini", + "display_name": "o3-mini", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 500000, - "output": 500000 + "context": 200000, + "output": 100000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -150758,25 +150044,29 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", "effort_options": [ "low", "medium", - "high", - "xhigh" - ] + "high" + ], + "visibility": "hidden" } }, - "attachment": true, + "attachment": false, "open_weights": false, - "knowledge": "2026-05", - "release_date": "2026-09-21", - "last_updated": "2026-09-21", + "knowledge": "2024-05", + "release_date": "2024-12-20", + "last_updated": "2025-01-29", "type": "chat" }, { - "id": "xai/grok-4.6", - "name": "Grok 4.6", - "display_name": "Grok 4.6", + "id": "openai/gpt-5.4-nano", + "name": "GPT-5.4 nano", + "display_name": "GPT-5.4 nano", "modalities": { "input": [ "text", @@ -150787,90 +150077,113 @@ ] }, "limit": { - "context": 500000, - "output": 500000 + "context": 400000, + "output": 128000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": false, + "mode": "effort", + "effort": "none", "effort_options": [ + "none", "low", "medium", "high", "xhigh" - ] + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, "attachment": true, "open_weights": false, - "knowledge": "2026-02-01", - "release_date": "2026-08-12", - "last_updated": "2026-08-12", + "knowledge": "2025-08-31", + "release_date": "2026-03-17", + "last_updated": "2026-03-17", "type": "chat" }, { - "id": "nvidia/nemotron-3-super-120b-a12b", - "name": "Nemotron 3 Super 120B", - "display_name": "Nemotron 3 Super 120B", + "id": "openai/gpt-5.4", + "name": "GPT-5.4", + "display_name": "GPT-5.4", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 32000 + "context": 1050000, + "output": 128000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": false, + "mode": "effort", + "effort": "none", "effort_options": [ + "none", "low", "medium", "high", - "xhigh", - "max" - ] + "xhigh" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, - "attachment": false, - "open_weights": true, - "knowledge": "2026-02", - "release_date": "2026-03-11", - "last_updated": "2026-03-11", + "attachment": true, + "open_weights": false, + "knowledge": "2025-08-31", + "release_date": "2026-03-05", + "last_updated": "2026-03-05", "type": "chat" }, { - "id": "anthropic/claude-opus-4-5", - "name": "Claude Opus 4.5", - "display_name": "Claude Opus 4.5", + "id": "openai/gpt-5.4-mini", + "name": "GPT-5.4 mini", + "display_name": "GPT-5.4 mini", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 64000 + "context": 400000, + "output": 128000 }, "temperature": true, "tool_call": true, @@ -150882,52 +150195,46 @@ "reasoning": { "supported": true, "default_enabled": false, - "mode": "mixed", - "budget": { - "min": 1024, - "unit": "tokens" - }, - "effort": "high", + "mode": "effort", + "effort": "none", "effort_options": [ + "none", "low", "medium", - "high" + "high", + "xhigh" ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" ], - "notes": [ - "Claude Opus 4.5 uses manual thinking.type = \"enabled\" with budget_tokens; effort can be used alongside the thinking budget.", - "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header." - ] + "visibility": "hidden" } }, "attachment": true, "open_weights": false, - "knowledge": "2025-03-31", - "release_date": "2025-11-24", - "last_updated": "2025-11-24", + "knowledge": "2025-08-31", + "release_date": "2026-03-17", + "last_updated": "2026-03-17", "type": "chat" }, { - "id": "anthropic/claude-sonnet-5-5", - "name": "Claude Sonnet 5.5", - "display_name": "Claude Sonnet 5.5", + "id": "openai/gpt-6-sol", + "name": "GPT-6 Sol", + "display_name": "GPT-6 Sol", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, + "context": 1050000, "output": 128000 }, "temperature": false, @@ -150939,6 +150246,7 @@ "extra_capabilities": { "reasoning": { "effort_options": [ + "none", "low", "medium", "high", @@ -150949,27 +150257,26 @@ }, "attachment": true, "open_weights": false, - "knowledge": "2026-06", - "release_date": "2026-09-28", - "last_updated": "2026-09-28", + "knowledge": "2026-04-20", + "release_date": "2026-09-22", + "last_updated": "2026-09-22", "type": "chat" }, { - "id": "anthropic/claude-haiku-5-5", - "name": "Claude Haiku 5.5", - "display_name": "Claude Haiku 5.5", + "id": "openai/gpt-6-astra", + "name": "GPT-6 Astra", + "display_name": "GPT-6 Astra", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, + "context": 1050000, "output": 128000 }, "temperature": false, @@ -150980,6 +150287,7 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, "effort_options": [ "low", "medium", @@ -150991,73 +150299,55 @@ }, "attachment": true, "open_weights": false, - "knowledge": "2026-06", - "release_date": "2026-10-07", - "last_updated": "2026-10-07", + "knowledge": "2026-04-30", + "release_date": "2026-09-04", + "last_updated": "2026-09-04", "type": "chat" }, { - "id": "anthropic/claude-opus-5", - "name": "Claude Opus 5", - "display_name": "Claude Opus 5", + "id": "openai/whisper-large-v3", + "name": "Whisper Large v3", + "display_name": "Whisper Large v3", "modalities": { "input": [ - "text", - "image", - "pdf" + "audio" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 448, + "output": 448 }, - "temperature": false, - "tool_call": true, + "temperature": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" - ] - } + "supported": false }, "attachment": true, - "open_weights": false, - "knowledge": "2026-05", - "release_date": "2026-07-24", - "last_updated": "2026-07-24", - "type": "chat" + "open_weights": true, + "release_date": "2023-11-06", + "last_updated": "2023-11-06" }, { - "id": "anthropic/claude-opus-4-7", - "name": "Claude Opus 4.7", - "display_name": "Claude Opus 4.7", + "id": "openai/gpt-5.2", + "name": "GPT-5.2", + "display_name": "GPT-5.2", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, + "context": 400000, "output": 128000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -151068,50 +150358,45 @@ "supported": true, "default_enabled": false, "mode": "effort", - "effort": "high", + "effort": "none", "effort_options": [ + "none", "low", "medium", "high", - "xhigh", - "max" + "xhigh" ], - "interleaved": true, - "summaries": true, - "visibility": "omitted", - "continuation": [ - "thinking_blocks" + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" ], - "notes": [ - "Claude Opus 4.7 and newer Opus models require thinking.type = \"adaptive\" to enable thinking explicitly.", - "Manual budget_tokens requests return 400 on Claude Opus 4.7 and newer adaptive-only Opus models.", - "task_budget is separate from thinking control and should not be treated as a thinking budget." - ] + "visibility": "hidden" } }, "attachment": true, "open_weights": false, - "knowledge": "2026-01-31", - "release_date": "2026-04-16", - "last_updated": "2026-04-16", + "knowledge": "2025-08-31", + "release_date": "2025-12-11", + "last_updated": "2025-12-11", "type": "chat" }, { - "id": "anthropic/claude-opus-5-5", - "name": "Claude Opus 5.5", - "display_name": "Claude Opus 5.5", + "id": "openai/gpt-6.1-sol", + "name": "GPT-6.1 Sol", + "display_name": "GPT-6.1 Sol", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, + "context": 1050000, "output": 128000 }, "temperature": false, @@ -151133,27 +150418,26 @@ }, "attachment": true, "open_weights": false, - "knowledge": "2026-06", - "release_date": "2026-09-22", - "last_updated": "2026-09-22", + "knowledge": "2026-04-30", + "release_date": "2026-09-29", + "last_updated": "2026-09-29", "type": "chat" }, { - "id": "anthropic/claude-sonnet-5", - "name": "Claude Sonnet 5", - "display_name": "Claude Sonnet 5", + "id": "openai/gpt-5-nano", + "name": "GPT-5 Nano", + "display_name": "GPT-5 Nano", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, + "context": 400000, "output": 128000 }, "temperature": false, @@ -151165,90 +150449,92 @@ "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", "effort_options": [ + "minimal", "low", "medium", - "high", - "xhigh", - "max" - ] + "high" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, "attachment": true, "open_weights": false, - "knowledge": "2026-01-31", - "release_date": "2026-06-30", - "last_updated": "2026-06-30", + "knowledge": "2024-05-30", + "release_date": "2025-08-07", + "last_updated": "2025-08-07", "type": "chat" }, { - "id": "anthropic/claude-sonnet-4-6", - "name": "Claude Sonnet 4.6", - "display_name": "Claude Sonnet 4.6", + "id": "openai/gpt-5.6-luna", + "name": "GPT-5.6 Luna", + "display_name": "GPT-5.6 Luna", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 64000 + "context": 1050000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "mixed", - "budget": { - "min": 1024, - "unit": "tokens" - }, - "effort": "high", + "default_enabled": true, + "mode": "effort", + "effort": "medium", "effort_options": [ + "none", "low", "medium", "high", + "xhigh", "max" ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" ], - "notes": [ - "Anthropic recommends adaptive thinking with effort for Claude 4.6; budget_tokens remains a deprecated compatibility path.", - "Anthropic API defaults effort to high; lower effort levels should be chosen per workload." - ] + "visibility": "hidden" } }, "attachment": true, "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-02-17", - "last_updated": "2026-03-13", + "knowledge": "2026-02-16", + "release_date": "2026-07-09", + "last_updated": "2026-07-09", "type": "chat" }, { - "id": "anthropic/claude-opus-4-6", - "name": "Claude Opus 4.6", - "display_name": "Claude Opus 4.6", + "id": "openai/o4-mini", + "name": "o4-mini", + "display_name": "o4-mini", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" @@ -151256,169 +150542,152 @@ }, "limit": { "context": 200000, - "output": 128000 + "output": 100000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "mixed", - "budget": { - "min": 1024, - "unit": "tokens" - }, - "effort": "high", + "default_enabled": true, + "mode": "effort", + "effort": "medium", "effort_options": [ "low", "medium", - "high", - "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "high" ], - "notes": [ - "Anthropic recommends adaptive thinking with effort for Claude 4.6; budget_tokens remains a deprecated compatibility path.", - "Anthropic API defaults effort to high; lower effort levels should be chosen per workload." - ] + "visibility": "hidden" } }, "attachment": true, "open_weights": false, - "knowledge": "2025-05-31", - "release_date": "2026-02-05", - "last_updated": "2026-03-13", + "knowledge": "2024-05", + "release_date": "2025-04-16", + "last_updated": "2025-04-16", "type": "chat" }, { - "id": "anthropic/claude-haiku-4-5", - "name": "Claude Haiku 4.5", - "display_name": "Claude Haiku 4.5", + "id": "openai/gpt-5", + "name": "GPT-5", + "display_name": "GPT-5", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 64000 + "context": 400000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "budget", - "budget": { - "min": 1024, - "unit": "tokens" - }, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "default_enabled": true, + "mode": "effort", + "effort": "medium", + "effort_options": [ + "minimal", + "low", + "medium", + "high" ], - "notes": [ - "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", - "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." - ] + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, "attachment": true, "open_weights": false, - "knowledge": "2025-02-28", - "release_date": "2025-10-15", - "last_updated": "2025-10-15", + "knowledge": "2024-09-30", + "release_date": "2025-08-07", + "last_updated": "2025-08-07", "type": "chat" }, { - "id": "anthropic/claude-sonnet-4-5", - "name": "Claude Sonnet 4.5", - "display_name": "Claude Sonnet 4.5", + "id": "openai/gpt-5-mini", + "name": "GPT-5 Mini", + "display_name": "GPT-5 Mini", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 64000 + "context": 400000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "budget", - "budget": { - "min": 1024, - "unit": "tokens" - }, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "default_enabled": true, + "mode": "effort", + "effort": "medium", + "effort_options": [ + "minimal", + "low", + "medium", + "high" ], - "notes": [ - "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", - "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." - ] + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, "attachment": true, "open_weights": false, - "knowledge": "2025-07-31", - "release_date": "2025-09-29", - "last_updated": "2025-09-29", + "knowledge": "2024-05-30", + "release_date": "2025-08-07", + "last_updated": "2025-08-07", "type": "chat" }, { - "id": "anthropic/claude-fable-5", - "name": "Claude Fable 5", - "display_name": "Claude Fable 5", + "id": "openai/gpt-5.6-sol", + "name": "GPT-5.6 Sol", + "display_name": "GPT-5.6 Sol", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, + "context": 1050000, "output": 128000 }, "temperature": false, @@ -151432,108 +150701,101 @@ "supported": true, "default_enabled": true, "mode": "effort", - "effort": "high", + "effort": "medium", "effort_options": [ + "none", "low", "medium", "high", "xhigh", "max" ], - "interleaved": true, - "summaries": true, - "visibility": "omitted", - "continuation": [ - "thinking_blocks" + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" ], - "notes": [ - "Adaptive thinking is always on for Claude Fable 5 and Claude Mythos 5; thinking.type = \"disabled\" is rejected.", - "Manual budget_tokens requests return 400 on Claude Fable 5 and Claude Mythos 5.", - "thinking.display defaults to omitted; set display to summarized to receive readable thinking summaries." - ] + "visibility": "hidden" } }, "attachment": true, "open_weights": false, - "knowledge": "2026-01-31", - "release_date": "2026-06-09", - "last_updated": "2026-06-09", + "knowledge": "2026-02-16", + "release_date": "2026-07-09", + "last_updated": "2026-07-09", "type": "chat" }, { - "id": "anthropic/claude-fable-5-1", - "name": "Claude Fable 5.1", - "display_name": "Claude Fable 5.1", + "id": "openai/gpt-5.1", + "name": "GPT-5.1", + "display_name": "GPT-5.1", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, + "context": 400000, "output": 128000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, + "default_enabled": false, "mode": "effort", - "effort": "high", + "effort": "none", "effort_options": [ + "none", "low", "medium", - "high", - "xhigh", - "max" + "high" ], - "interleaved": true, - "summaries": true, - "visibility": "omitted", - "continuation": [ - "thinking_blocks" + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" ], - "notes": [ - "Adaptive thinking is always on for Claude Fable 5 and Claude Mythos 5; thinking.type = \"disabled\" is rejected.", - "Manual budget_tokens requests return 400 on Claude Fable 5 and Claude Mythos 5.", - "thinking.display defaults to omitted; set display to summarized to receive readable thinking summaries." - ] + "visibility": "hidden" } }, "attachment": true, "open_weights": false, - "knowledge": "2026-06", - "release_date": "2026-09-01", - "last_updated": "2026-09-01", + "knowledge": "2024-09-30", + "release_date": "2025-11-13", + "last_updated": "2025-11-13", "type": "chat" }, { - "id": "deepseek/deepseek-v4-flash-0731", - "name": "DeepSeek V4 Flash 0731", - "display_name": "DeepSeek V4 Flash 0731", + "id": "openai/gpt-5.5", + "name": "GPT-5.5", + "display_name": "GPT-5.5", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 384000 + "context": 1050000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -151542,30 +150804,39 @@ "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", "effort_options": [ "low", + "medium", "high", - "max" - ] + "xhigh" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-07-31", - "last_updated": "2026-07-31", + "attachment": true, + "open_weights": false, + "knowledge": "2025-12-01", + "release_date": "2026-04-23", + "last_updated": "2026-04-23", "type": "chat" }, { - "id": "google/gemini-3-flash-preview", - "name": "Gemini 3 Flash Preview", - "display_name": "Gemini 3 Flash Preview", + "id": "openai/gpt-4.1-mini", + "name": "GPT-4.1 mini", + "display_name": "GPT-4.1 mini", "modalities": { "input": [ "text", "image", - "video", - "audio", "pdf" ], "output": [ @@ -151573,45 +150844,25 @@ ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 1047576, + "output": 32768 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "level", - "level": "high", - "level_options": [ - "minimal", - "low", - "medium", - "high" - ], - "summaries": true, - "visibility": "summary", - "continuation": [ - "thought_signatures" - ] - } + "supported": false }, "attachment": true, "open_weights": false, - "knowledge": "2025-01", - "release_date": "2025-12-17", - "last_updated": "2025-12-17", + "knowledge": "2024-04", + "release_date": "2025-04-14", + "last_updated": "2025-04-14", "type": "chat" }, { - "id": "google/gemini-3.7-flash", - "name": "Gemini 3.7 Flash", - "display_name": "Gemini 3.7 Flash", + "id": "openai/gpt-4.1", + "name": "GPT-4.1", + "display_name": "GPT-4.1", "modalities": { "input": [ "text", @@ -151623,62 +150874,39 @@ ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 1047576, + "output": 32768 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "level", - "level": "high", - "level_options": [ - "minimal", - "low", - "medium", - "high" - ], - "summaries": true, - "visibility": "summary", - "continuation": [ - "thought_signatures" - ] - } + "supported": false }, "attachment": true, "open_weights": false, - "knowledge": "2026-03", - "release_date": "2026-08-13", - "last_updated": "2026-08-13", + "knowledge": "2024-04", + "release_date": "2025-04-14", + "last_updated": "2025-04-14", "type": "chat" }, { - "id": "google/gemini-3.1-pro-preview", - "name": "Gemini 3.1 Pro Preview", - "display_name": "Gemini 3.1 Pro Preview", + "id": "openai/gpt-6-luna", + "name": "GPT-6 Luna", + "display_name": "GPT-6 Luna", "modalities": { "input": [ "text", - "image", - "video", - "audio", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 1050000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -151686,32 +150914,27 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "level", - "level": "high", - "level_options": [ + "effort_options": [ + "none", "low", - "high" - ], - "summaries": true, - "visibility": "summary", - "continuation": [ - "thought_signatures" + "medium", + "high", + "xhigh", + "max" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-01", - "release_date": "2026-02-19", - "last_updated": "2026-02-19", + "knowledge": "2026-05-18", + "release_date": "2026-09-22", + "last_updated": "2026-09-22", "type": "chat" }, { - "id": "google/gemini-3.8-flash", - "name": "Gemini 3.8 Flash", - "display_name": "Gemini 3.8 Flash", + "id": "openai/o3", + "name": "o3", + "display_name": "o3", "modalities": { "input": [ "text", @@ -151723,10 +150946,10 @@ ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 200000, + "output": 100000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -151736,84 +150959,94 @@ "reasoning": { "supported": true, "default_enabled": true, - "mode": "level", - "level": "high", - "level_options": [ - "minimal", + "mode": "effort", + "effort": "medium", + "effort_options": [ "low", "medium", "high" ], - "summaries": true, - "visibility": "summary", - "continuation": [ - "thought_signatures" - ] + "visibility": "hidden" } }, "attachment": true, "open_weights": false, - "release_date": "2026-09-02", - "last_updated": "2026-09-02", + "knowledge": "2024-05", + "release_date": "2025-04-16", + "last_updated": "2025-04-16", "type": "chat" }, { - "id": "google/gemini-4-argon", - "name": "Gemini 4 Argon", - "display_name": "Gemini 4 Argon", + "id": "Qwen/Qwen3-Reranker-0.6B", + "name": "Qwen3 Reranker 0.6B", + "display_name": "Qwen3 Reranker 0.6B", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 40960, + "output": 1024 }, "temperature": false, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true + "supported": false }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "low", - "medium", - "high" - ] - } + "attachment": false, + "open_weights": true, + "release_date": "2025-06-05", + "last_updated": "2025-06-05", + "type": "rerank" + }, + { + "id": "Qwen/Qwen3-VL-30B-A3B-Instruct", + "name": "Qwen3-VL 30B-A3B Instruct", + "display_name": "Qwen3-VL 30B-A3B Instruct", + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 16384, + "output": 8192 + }, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": false }, "attachment": true, - "open_weights": false, - "knowledge": "2026-03", - "release_date": "2026-10-13", - "last_updated": "2026-10-13", + "open_weights": true, + "release_date": "2025-10-04", + "last_updated": "2025-10-04", "type": "chat" }, { - "id": "google/gemini-3.6-flash", - "name": "Gemini 3.6 Flash", - "display_name": "Gemini 3.6 Flash", + "id": "Qwen/Qwen3.8-27B-abliterated", + "name": "Qwen3.8 27B Abliterated", + "display_name": "Qwen3.8 27B Abliterated", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 262144, + "output": 32768 }, "temperature": true, "tool_call": true, @@ -151824,48 +151057,64 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "level", - "level": "high", - "level_options": [ - "minimal", - "low", - "medium", - "high" - ], + "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ - "thought_signatures" + "thinking_blocks" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-03", - "release_date": "2026-07-21", - "last_updated": "2026-07-21", + "release_date": "2026-10-29", + "last_updated": "2026-10-29", "type": "chat" }, { - "id": "moonshot-ai/kimi-k3", - "name": "Kimi K3", - "display_name": "Kimi K3", + "id": "Qwen/Qwen3-Embedding-0.6B", + "name": "Qwen3 Embedding 0.6B", + "display_name": "Qwen3 Embedding 0.6B", "modalities": { "input": [ - "text", - "image", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 32768, + "output": 1024 }, "temperature": false, + "tool_call": false, + "reasoning": { + "supported": false + }, + "attachment": false, + "open_weights": true, + "release_date": "2025-06-05", + "last_updated": "2025-06-05", + "type": "embedding" + }, + { + "id": "Qwen/Qwen3.6-35B-A3B-FP8", + "name": "Qwen 3.6 35B A3B FP8", + "display_name": "Qwen 3.6 35B A3B FP8", + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 262144, + "output": 8192 + }, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -151874,31 +151123,28 @@ "extra_capabilities": { "reasoning": { "supported": true, - "effort_options": [ - "minimal", - "low", - "medium", - "high", - "xhigh", - "max" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, - "attachment": true, + "attachment": false, "open_weights": true, - "release_date": "2026-07-16", - "last_updated": "2026-07-16", + "release_date": "2026-04-17", + "last_updated": "2026-04-17", "type": "chat" }, { - "id": "moonshot-ai/kimi-k2.7-code", - "name": "Kimi K2.7 Code", - "display_name": "Kimi K2.7 Code", + "id": "Qwen/Qwen3.8-27B", + "name": "Qwen3.8 27B", + "display_name": "Qwen3.8 27B", "modalities": { "input": [ "text", - "image", - "video" + "image" ], "output": [ "text" @@ -151906,9 +151152,9 @@ }, "limit": { "context": 262144, - "output": 262144 + "output": 32768 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -151916,35 +151162,39 @@ }, "extra_capabilities": { "reasoning": { - "supported": true + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] } }, "attachment": true, "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-06-12", - "last_updated": "2026-06-12", + "release_date": "2026-08-14", + "last_updated": "2026-08-14", "type": "chat" }, { - "id": "openai/gpt-5.5", - "name": "GPT-5.5", - "display_name": "GPT-5.5", + "id": "z-ai/glm-5.3-flash", + "name": "GLM-5.3-Flash", + "display_name": "GLM-5.3-Flash", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 1000000, + "output": 131072 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -151953,50 +151203,42 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ "low", - "medium", "high", - "xhigh" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + "max" + ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2025-12-01", - "release_date": "2026-04-23", - "last_updated": "2026-04-23", + "open_weights": true, + "release_date": "2026-08-26", + "last_updated": "2026-08-26", "type": "chat" }, { - "id": "openai/gpt-6-astra", - "name": "GPT-6 Astra", - "display_name": "GPT-6 Astra", + "id": "z-ai/glm-5.3", + "name": "GLM-5.3", + "display_name": "GLM-5.3", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 1000000, + "output": 131072 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -152005,91 +151247,85 @@ "extra_capabilities": { "reasoning": { "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ "low", - "medium", "high", - "xhigh", "max" ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-04-30", - "release_date": "2026-09-04", - "last_updated": "2026-09-04", + "attachment": false, + "open_weights": true, + "release_date": "2026-08-14", + "last_updated": "2026-08-14", "type": "chat" }, { - "id": "openai/gpt-5.4", - "name": "GPT-5.4", - "display_name": "GPT-5.4", + "id": "moonshotai/kimi-k3", + "name": "Kimi K3", + "display_name": "Kimi K3", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 1048576, + "output": 131072 }, "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "none", + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ - "none", "low", - "medium", "high", - "xhigh" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + "max" + ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-03-05", - "last_updated": "2026-03-05", + "attachment": false, + "open_weights": true, + "release_date": "2026-07-16", + "last_updated": "2026-07-16", "type": "chat" }, { - "id": "openai/gpt-6-sol", - "name": "GPT-6 Sol", - "display_name": "GPT-6 Sol", + "id": "moonshotai/kimi-k3.1-pro", + "name": "Kimi K3.1 Pro", + "display_name": "Kimi K3.1 Pro", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 1048576, + "output": 131072 }, "temperature": false, "tool_call": true, @@ -152099,41 +151335,43 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ "low", - "medium", "high", - "xhigh", "max" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-04-20", - "release_date": "2026-09-22", - "last_updated": "2026-09-22", + "release_date": "2026-10-23", + "last_updated": "2026-10-23", "type": "chat" }, { - "id": "openai/gpt-5.6-sol", - "name": "GPT-5.6 Sol", - "display_name": "GPT-5.6 Sol", + "id": "moonshotai/kimi-k2.6", + "name": "Kimi K2.6", + "display_name": "Kimi K2.6", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 262144, + "output": 262144 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -152142,41 +151380,31 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", - "effort_options": [ - "none", - "low", - "medium", - "high", - "xhigh", - "max" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-02-16", - "release_date": "2026-07-09", - "last_updated": "2026-07-09", + "attachment": false, + "open_weights": true, + "knowledge": "2025-01", + "release_date": "2026-04-20", + "last_updated": "2026-04-20", "type": "chat" }, { - "id": "openai/gpt-5.6-terra", - "name": "GPT-5.6 Terra", - "display_name": "GPT-5.6 Terra", + "id": "google/gemini-3.5-flash", + "name": "Gemini 3.5 Flash", + "display_name": "Gemini 3.5 Flash", "modalities": { "input": [ "text", "image", + "video", + "audio", "pdf" ], "output": [ @@ -152184,10 +151412,10 @@ ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 1048576, + "output": 65536 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -152197,51 +151425,46 @@ "reasoning": { "supported": true, "default_enabled": true, - "mode": "effort", - "effort": "medium", - "effort_options": [ - "none", - "low", - "medium", - "high", - "xhigh", - "max" - ], - "verbosity": "medium", - "verbosity_options": [ + "mode": "level", + "level": "high", + "level_options": [ + "minimal", "low", "medium", "high" ], - "visibility": "hidden" + "summaries": true, + "visibility": "summary", + "continuation": [ + "thought_signatures" + ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-02-16", - "release_date": "2026-07-09", - "last_updated": "2026-07-09", + "knowledge": "2025-01", + "release_date": "2026-05-19", + "last_updated": "2026-05-19", "type": "chat" }, { - "id": "openai/gpt-6-luna", - "name": "GPT-6 Luna", - "display_name": "GPT-6 Luna", + "id": "google/gemini-3.8-flash", + "name": "Gemini 3.8 Flash", + "display_name": "Gemini 3.8 Flash", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 1048576, + "output": 65536 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -152249,40 +151472,50 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ + "supported": true, + "default_enabled": true, + "mode": "level", + "level": "high", + "level_options": [ + "minimal", "low", "medium", - "high", - "xhigh", - "max" + "high" + ], + "summaries": true, + "visibility": "summary", + "continuation": [ + "thought_signatures" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-05-18", - "release_date": "2026-09-22", - "last_updated": "2026-09-22", + "release_date": "2026-09-02", + "last_updated": "2026-09-02", "type": "chat" }, { - "id": "openai/gpt-5-mini", - "name": "GPT-5 Mini", - "display_name": "GPT-5 Mini", + "id": "google/gemini-2.5-pro", + "name": "Gemini 2.5 Pro", + "display_name": "Gemini 2.5 Pro", "modalities": { "input": [ "text", - "image" + "image", + "audio", + "video", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 1048576, + "output": 65536 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -152292,48 +151525,49 @@ "reasoning": { "supported": true, "default_enabled": true, - "mode": "effort", - "effort": "medium", - "effort_options": [ - "minimal", - "low", - "medium", - "high" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + "mode": "budget", + "budget": { + "default": -1, + "min": 128, + "max": 32768, + "auto": -1, + "unit": "tokens" + }, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thought_signatures" + ] } }, "attachment": true, "open_weights": false, - "knowledge": "2024-05-30", - "release_date": "2025-08-07", - "last_updated": "2025-08-07", + "knowledge": "2025-01", + "release_date": "2025-06-17", + "last_updated": "2025-06-17", "type": "chat" }, { - "id": "openai/gpt-5.1", - "name": "GPT-5.1", - "display_name": "GPT-5.1", + "id": "google/gemini-2.5-flash-lite", + "name": "Gemini 2.5 Flash-Lite", + "display_name": "Gemini 2.5 Flash-Lite", "modalities": { "input": [ "text", - "image" + "image", + "audio", + "video", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 1048576, + "output": 65536 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -152343,38 +151577,38 @@ "reasoning": { "supported": true, "default_enabled": false, - "mode": "effort", - "effort": "none", - "effort_options": [ - "none", - "low", - "medium", - "high" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + "mode": "budget", + "budget": { + "default": -1, + "min": 512, + "max": 24576, + "auto": -1, + "unit": "tokens" + }, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thought_signatures" + ] } }, "attachment": true, "open_weights": false, - "knowledge": "2024-09-30", - "release_date": "2025-11-13", - "last_updated": "2025-11-13", + "knowledge": "2025-01", + "release_date": "2025-06-17", + "last_updated": "2025-06-17", "type": "chat" }, { - "id": "openai/gpt-5.6-luna", - "name": "GPT-5.6 Luna", - "display_name": "GPT-5.6 Luna", + "id": "google/gemini-2.5-flash", + "name": "Gemini 2.5 Flash", + "display_name": "Gemini 2.5 Flash", "modalities": { "input": [ "text", "image", + "audio", + "video", "pdf" ], "output": [ @@ -152382,10 +151616,10 @@ ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 1048576, + "output": 65536 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -152395,135 +151629,102 @@ "reasoning": { "supported": true, "default_enabled": true, - "mode": "effort", - "effort": "medium", - "effort_options": [ - "none", - "low", - "medium", - "high", - "xhigh", - "max" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + "mode": "budget", + "budget": { + "default": -1, + "min": 0, + "max": 24576, + "auto": -1, + "off": 0, + "unit": "tokens" + }, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thought_signatures" + ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-02-16", - "release_date": "2026-07-09", - "last_updated": "2026-07-09", + "knowledge": "2025-01", + "release_date": "2025-06-17", + "last_updated": "2025-06-17", "type": "chat" }, { - "id": "openai/gpt-5.2", - "name": "GPT-5.2", - "display_name": "GPT-5.2", + "id": "google/gemini-3.1-flash-lite", + "name": "Gemini 3.1 Flash Lite", + "display_name": "Gemini 3.1 Flash Lite", "modalities": { "input": [ "text", - "image" + "image", + "video", + "audio", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 1048576, + "output": 65536 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "none", - "effort_options": [ - "none", - "low", - "medium", - "high", - "xhigh" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + "supported": true } }, "attachment": true, "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2025-12-11", - "last_updated": "2025-12-11", + "knowledge": "2025-01", + "release_date": "2026-05-07", + "last_updated": "2026-05-07", "type": "chat" }, { - "id": "openai/gpt-6.1-sol", - "name": "GPT-6.1 Sol", - "display_name": "GPT-6.1 Sol", + "id": "black-forest-labs/FLUX.2-klein-4B", + "name": "FLUX.2 Klein 4B", + "display_name": "FLUX.2 Klein 4B", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ - "text" + "image" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 8192, + "output": 8192 }, "temperature": false, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" - ] - } + "supported": false }, "attachment": true, - "open_weights": false, - "knowledge": "2026-04-30", - "release_date": "2026-09-29", - "last_updated": "2026-09-29", - "type": "chat" + "open_weights": true, + "release_date": "2026-01-15", + "last_updated": "2026-01-15" }, { - "id": "perplexity/glm-5.3-flash", - "name": "GLM-5.3-Flash", - "display_name": "GLM-5.3-Flash", + "id": "anthropic/claude-haiku-5-5", + "name": "Claude Haiku 5.5", + "display_name": "Claude Haiku 5.5", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" @@ -152531,9 +151732,9 @@ }, "limit": { "context": 1000000, - "output": 131072 + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -152541,145 +151742,184 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, "effort_options": [ "low", + "medium", "high", + "xhigh", "max" ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-08-26", - "last_updated": "2026-08-26", + "open_weights": false, + "knowledge": "2026-06", + "release_date": "2026-10-07", + "last_updated": "2026-10-07", "type": "chat" }, { - "id": "perplexity/kimi-k3", - "name": "Kimi K3", - "display_name": "Kimi K3", + "id": "anthropic/claude-opus-4-7", + "name": "Claude Opus 4.7", + "display_name": "Claude Opus 4.7", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 1000000, + "output": 128000 }, "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": false, + "mode": "effort", + "effort": "high", "effort_options": [ - "minimal", "low", "medium", "high", "xhigh", "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "omitted", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Claude Opus 4.7 and newer Opus models require thinking.type = \"adaptive\" to enable thinking explicitly.", + "Manual budget_tokens requests return 400 on Claude Opus 4.7 and newer adaptive-only Opus models.", + "task_budget is separate from thinking control and should not be treated as a thinking budget." ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-07-16", - "last_updated": "2026-07-16", + "open_weights": false, + "knowledge": "2026-01-31", + "release_date": "2026-04-16", + "last_updated": "2026-04-16", "type": "chat" }, { - "id": "perplexity/deepseek-v4-pro-0813", - "name": "DeepSeek V4 Pro 0813", - "display_name": "DeepSeek V4 Pro 0813", + "id": "anthropic/claude-haiku-4-5", + "name": "Claude Haiku 4.5 (latest)", + "display_name": "Claude Haiku 4.5 (latest)", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 384000 + "context": 200000, + "output": 64000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, - "effort_options": [ - "low", - "high", - "max" + "default_enabled": false, + "mode": "budget", + "budget": { + "min": 1024, + "unit": "tokens" + }, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", + "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." ] } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-08-13", - "last_updated": "2026-08-22", + "attachment": true, + "open_weights": false, + "knowledge": "2025-02-28", + "release_date": "2025-10-15", + "last_updated": "2025-10-15", "type": "chat" }, { - "id": "perplexity/sonar", - "name": "Sonar", - "display_name": "Sonar", + "id": "anthropic/claude-sonnet-5", + "name": "Claude Sonnet 5", + "display_name": "Claude Sonnet 5", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 8192 + "context": 1000000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, + "extra_capabilities": { + "reasoning": { + "supported": true + } + }, + "attachment": true, "open_weights": false, - "knowledge": "2025-09-01", - "release_date": "2024-01-01", - "last_updated": "2025-09-01", + "knowledge": "2026-01-31", + "release_date": "2026-06-30", + "last_updated": "2026-06-30", "type": "chat" }, { - "id": "perplexity/nemotron-3.5-super-120b-a12b", - "name": "Nemotron 3.5 Super 120B A12B", - "display_name": "Nemotron 3.5 Super 120B A12B", + "id": "anthropic/claude-fable-5", + "name": "Claude Fable 5", + "display_name": "Claude Fable 5", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 1000000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -152687,87 +151927,108 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "high", "effort_options": [ "low", "medium", "high", "xhigh", "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "omitted", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Adaptive thinking is always on for Claude Fable 5 and Claude Mythos 5; thinking.type = \"disabled\" is rejected.", + "Manual budget_tokens requests return 400 on Claude Fable 5 and Claude Mythos 5.", + "thinking.display defaults to omitted; set display to summarized to receive readable thinking summaries." ] } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-10-15", - "last_updated": "2026-10-15", + "attachment": true, + "open_weights": false, + "knowledge": "2026-01-31", + "release_date": "2026-06-09", + "last_updated": "2026-06-09", "type": "chat" }, { - "id": "perplexity/glm-5.3", - "name": "GLM-5.3", - "display_name": "GLM-5.3", + "id": "anthropic/claude-sonnet-4-5", + "name": "Claude Sonnet 4.5 (latest)", + "display_name": "Claude Sonnet 4.5 (latest)", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 200000, + "output": 64000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, - "effort_options": [ - "low", - "high", - "max" + "default_enabled": false, + "mode": "budget", + "budget": { + "min": 1024, + "unit": "tokens" + }, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", + "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." ] } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-08-14", - "last_updated": "2026-08-14", + "attachment": true, + "open_weights": false, + "knowledge": "2025-07-31", + "release_date": "2025-09-29", + "last_updated": "2025-09-29", "type": "chat" - } - ] - }, - "stepfun-step-plan": { - "id": "stepfun-step-plan", - "name": "StepFun Step Plan (China)", - "display_name": "StepFun Step Plan (China)", - "api": "https://api.stepfun.com/step_plan/v1", - "doc": "https://platform.stepfun.com/docs/zh/step-plan/integrations/reasoning-api", - "models": [ + }, { - "id": "step-3.7-flash", - "name": "Step 3.7 Flash", - "display_name": "Step 3.7 Flash", + "id": "anthropic/claude-opus-5-5", + "name": "Claude Opus 5.5", + "display_name": "Claude Opus 5.5", "modalities": { "input": [ "text", "image", - "video" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 256000, - "output": 256000 + "context": 1000000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -152775,108 +152036,131 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], "effort_options": [ "low", "medium", - "high" + "high", + "xhigh", + "max" ] } }, "attachment": true, - "open_weights": true, - "knowledge": "2026-03-01", - "release_date": "2026-05-29", - "last_updated": "2026-05-29", + "open_weights": false, + "knowledge": "2026-06", + "release_date": "2026-09-22", + "last_updated": "2026-09-22", "type": "chat" }, { - "id": "step-router-v1", - "name": "Step Router v1", - "display_name": "Step Router v1", + "id": "anthropic/claude-sonnet-5-5", + "name": "Claude Sonnet 5.5", + "display_name": "Claude Sonnet 5.5", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 256000, - "output": 256000 + "context": 1000000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" + ] + } + }, + "attachment": true, "open_weights": false, - "knowledge": "2025-01", - "release_date": "2026-05-29", - "last_updated": "2026-05-29", + "knowledge": "2026-06", + "release_date": "2026-09-28", + "last_updated": "2026-09-28", "type": "chat" }, { - "id": "step-3.5-flash-2603", - "name": "Step 3.5 Flash 2603", - "display_name": "Step 3.5 Flash 2603", + "id": "anthropic/claude-sonnet-4-6", + "name": "Claude Sonnet 4.6", + "display_name": "Claude Sonnet 4.6", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 256000, - "output": 256000 + "context": 1000000, + "output": 64000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": false, + "mode": "mixed", + "budget": { + "min": 1024, + "unit": "tokens" + }, + "effort": "high", + "effort_options": [ + "low", + "medium", + "high", + "max" + ], "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ "thinking_blocks" ], - "effort_options": [ - "low", - "high" + "notes": [ + "Anthropic recommends adaptive thinking with effort for Claude 4.6; budget_tokens remains a deprecated compatibility path.", + "Anthropic API defaults effort to high; lower effort levels should be chosen per workload." ] } }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-04-02", - "last_updated": "2026-04-02", + "attachment": true, + "open_weights": false, + "knowledge": "2025-08-31", + "release_date": "2026-02-17", + "last_updated": "2026-03-13", "type": "chat" }, { - "id": "step-5-preview", - "name": "Step 5 Preview", - "display_name": "Step 5 Preview", + "id": "anthropic/claude-opus-4-8", + "name": "Claude Opus 4.8", + "display_name": "Claude Opus 4.8", "modalities": { "input": [ "text", - "image", - "video" + "image" ], "output": [ "text" @@ -152884,100 +152168,114 @@ }, "limit": { "context": 1000000, - "output": 384000 + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": false, "mode": "effort", - "effort": "medium", + "effort": "high", "effort_options": [ "low", "medium", - "high" + "high", + "xhigh", + "max" ], "interleaved": true, "summaries": true, - "visibility": "summary", + "visibility": "omitted", "continuation": [ "thinking_blocks" + ], + "notes": [ + "Claude Opus 4.7 and newer Opus models require thinking.type = \"adaptive\" to enable thinking explicitly.", + "Manual budget_tokens requests return 400 on Claude Opus 4.7 and newer adaptive-only Opus models.", + "task_budget is separate from thinking control and should not be treated as a thinking budget." ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-09-16", - "last_updated": "2026-09-20", + "open_weights": false, + "knowledge": "2026-01", + "release_date": "2026-05-28", + "last_updated": "2026-05-28", "type": "chat" }, { - "id": "step-3.5-flash", - "name": "Step 3.5 Flash", - "display_name": "Step 3.5 Flash", + "id": "anthropic/claude-opus-4-6", + "name": "Claude Opus 4.6", + "display_name": "Claude Opus 4.6", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 256000, - "output": 256000 + "context": 200000, + "output": 128000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": false, + "mode": "mixed", + "budget": { + "min": 1024, + "unit": "tokens" + }, + "effort": "high", + "effort_options": [ + "low", + "medium", + "high", + "max" + ], "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ "thinking_blocks" ], - "effort_options": [ - "low", - "high" + "notes": [ + "Anthropic recommends adaptive thinking with effort for Claude 4.6; budget_tokens remains a deprecated compatibility path.", + "Anthropic API defaults effort to high; lower effort levels should be chosen per workload." ] } }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-01-29", - "last_updated": "2026-02-13", + "attachment": true, + "open_weights": false, + "knowledge": "2025-05-31", + "release_date": "2026-02-05", + "last_updated": "2026-03-13", "type": "chat" - } - ] - }, - "empiriolabs": { - "id": "empiriolabs", - "name": "EmpirioLabs AI", - "display_name": "EmpirioLabs AI", - "api": "https://api.empiriolabs.ai/v1", - "doc": "https://docs.empiriolabs.ai", - "models": [ + }, { - "id": "minimax-m3-1-flash", - "name": "MiniMax M3.1 Flash", - "display_name": "MiniMax M3.1 Flash", + "id": "anthropic/claude-fable-5-1", + "name": "Claude Fable 5.1", + "display_name": "Claude Fable 5.1", "modalities": { "input": [ "text", - "image", - "video" + "image" ], "output": [ "text" @@ -152985,9 +152283,9 @@ }, "limit": { "context": 1000000, - "output": 524288 + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -152995,37 +152293,52 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "high", "effort_options": [ "low", "medium", "high", "xhigh", "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "omitted", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Adaptive thinking is always on for Claude Fable 5 and Claude Mythos 5; thinking.type = \"disabled\" is rejected.", + "Manual budget_tokens requests return 400 on Claude Fable 5 and Claude Mythos 5.", + "thinking.display defaults to omitted; set display to summarized to receive readable thinking summaries." ] } }, "attachment": true, "open_weights": false, - "release_date": "2026-10-19", - "last_updated": "2026-10-19", + "knowledge": "2026-06", + "release_date": "2026-09-01", + "last_updated": "2026-09-01", "type": "chat" }, { - "id": "seed-2-0-pro", - "name": "Seed 2.0 Pro", - "display_name": "Seed 2.0 Pro", + "id": "anthropic/claude-opus-5", + "name": "Claude Opus 5", + "display_name": "Claude Opus 5", "modalities": { "input": [ "text", - "image", - "video" + "image" ], "output": [ "text" ] }, "limit": { - "context": 256000, + "context": 1000000, "output": 128000 }, "temperature": false, @@ -153036,36 +152349,31 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "low", - "medium", - "high" - ] + "supported": true } }, "attachment": true, "open_weights": false, - "release_date": "2026-02-14", - "last_updated": "2026-02-14", + "knowledge": "2026-05", + "release_date": "2026-07-24", + "last_updated": "2026-07-24", "type": "chat" }, { - "id": "qwen3-6-plus", - "name": "Qwen3.6 Plus", - "display_name": "Qwen3.6 Plus", + "id": "qwen/qwen3.7-max", + "name": "Qwen3.7 Max", + "display_name": "Qwen3.7 Max", "modalities": { "input": [ - "text", - "image", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 983616, - "output": 65536 + "context": 1000000, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -153075,26 +152383,19 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "none", - "low", - "medium", - "high", - "max" - ] + "supported": true } }, - "attachment": true, + "attachment": false, "open_weights": false, - "knowledge": "2025-04", - "release_date": "2026-04-02", - "last_updated": "2026-04-02", + "release_date": "2026-05-21", + "last_updated": "2026-05-21", "type": "chat" }, { - "id": "mistral-small-4", - "name": "Mistral Small 4", - "display_name": "Mistral Small 4", + "id": "qwen/qwen3.5-397b-a17b", + "name": "Qwen3.5 397B-A17B", + "display_name": "Qwen3.5 397B-A17B", "modalities": { "input": [ "text", @@ -153111,19 +152412,30 @@ "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } }, "attachment": true, "open_weights": true, - "knowledge": "2025-06", - "release_date": "2026-03-16", - "last_updated": "2026-03-16", + "release_date": "2026-02-15", + "last_updated": "2026-02-15", "type": "chat" }, { - "id": "fugu-ultra-v1-1", - "name": "Fugu Ultra v1.1", - "display_name": "Fugu Ultra v1.1", + "id": "deepseek/deepseek-v4.1-flash-1017", + "name": "DeepSeek V4.1 Flash 1017", + "display_name": "DeepSeek V4.1 Flash 1017", "modalities": { "input": [ "text", @@ -153135,9 +152447,9 @@ }, "limit": { "context": 1000000, - "output": 131072 + "output": 384000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -153146,22 +152458,23 @@ "extra_capabilities": { "reasoning": { "effort_options": [ + "low", "high", - "xhigh", "max" ] } }, "attachment": true, - "open_weights": false, - "release_date": "2026-06-15", - "last_updated": "2026-06-15", + "open_weights": true, + "knowledge": "2025-05", + "release_date": "2026-10-17", + "last_updated": "2026-10-17", "type": "chat" }, { - "id": "glm-5-2", - "name": "GLM 5.2", - "display_name": "GLM 5.2", + "id": "deepseek/deepseek-v3.2", + "name": "DeepSeek V3.2", + "display_name": "DeepSeek V3.2", "modalities": { "input": [ "text" @@ -153171,8 +152484,8 @@ ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 128000, + "output": 64000 }, "temperature": true, "tool_call": true, @@ -153182,41 +152495,41 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "none", - "minimal", - "low", - "medium", - "high", - "xhigh", - "max" + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, "attachment": false, "open_weights": true, - "release_date": "2026-06-13", - "last_updated": "2026-06-13", + "knowledge": "2024-07", + "release_date": "2025-12-01", + "last_updated": "2025-12-01", "type": "chat" }, { - "id": "fugu-ultra-v1-0", - "name": "Fugu Ultra v1.0", - "display_name": "Fugu Ultra v1.0", + "id": "x-ai/grok-4.7", + "name": "Grok 4.7", + "display_name": "Grok 4.7", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 500000, + "output": 500000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -153225,22 +152538,24 @@ "extra_capabilities": { "reasoning": { "effort_options": [ + "low", + "medium", "high", - "xhigh", - "max" + "xhigh" ] } }, "attachment": true, "open_weights": false, - "release_date": "2026-06-15", - "last_updated": "2026-06-15", + "knowledge": "2026-05", + "release_date": "2026-09-21", + "last_updated": "2026-09-21", "type": "chat" }, { - "id": "qwen3-7-max", - "name": "Qwen3.7 Max", - "display_name": "Qwen3.7 Max", + "id": "x-ai/grok-4.6", + "name": "Grok 4.6", + "display_name": "Grok 4.6", "modalities": { "input": [ "text" @@ -153250,8 +152565,8 @@ ] }, "limit": { - "context": 983616, - "output": 65536 + "context": 500000, + "output": 500000 }, "temperature": true, "tool_call": true, @@ -153261,69 +152576,81 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, "effort_options": [ - "none", "low", "medium", "high", - "max" + "xhigh" ] } }, "attachment": false, "open_weights": false, - "release_date": "2026-05-21", - "last_updated": "2026-05-21", + "knowledge": "2026-02-01", + "release_date": "2026-08-12", + "last_updated": "2026-08-12", "type": "chat" }, { - "id": "kimi-k2-7-code-highspeed", - "name": "Kimi K2.7 Code Highspeed", - "display_name": "Kimi K2.7 Code Highspeed", + "id": "deepseek-ai/DeepSeek-V4.1-Flash-1017-abliterated", + "name": "DeepSeek V4.1 Flash 1017 Abliterated", + "display_name": "DeepSeek V4.1 Flash 1017 Abliterated", "modalities": { "input": [ - "text", - "image", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 131072 + "context": 1048576, + "output": 384000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, "default": true }, - "attachment": true, - "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-06-12", - "last_updated": "2026-06-12", + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "effort_options": [ + "low", + "high", + "max" + ] + } + }, + "attachment": false, + "open_weights": false, + "release_date": "2026-10-29", + "last_updated": "2026-10-29", "type": "chat" }, { - "id": "qwen3-8-max", - "name": "Qwen3.8 Max", - "display_name": "Qwen3.8 Max", + "id": "deepseek-ai/DeepSeek-V4.1-Flash-1017", + "name": "DeepSeek V4.1 Flash 1017", + "display_name": "DeepSeek V4.1 Flash 1017", "modalities": { "input": [ - "text", - "image", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 983616, - "output": 131072 + "context": 1000000, + "output": 384000 }, "temperature": true, "tool_call": true, @@ -153333,25 +152660,73 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ - "none", "low", - "medium", "high", "max" ] } }, - "attachment": true, - "open_weights": false, - "release_date": "2026-08-03", - "last_updated": "2026-08-03", + "attachment": false, + "open_weights": true, + "knowledge": "2025-05", + "release_date": "2026-10-17", + "last_updated": "2026-10-17", + "type": "chat" + } + ] + }, + "minimax-cn-coding-plan": { + "id": "minimax-cn-coding-plan", + "name": "MiniMax Token Plan (minimax.cn)", + "display_name": "MiniMax Token Plan (minimax.cn)", + "api": "https://api.minimax.cn/anthropic/v1", + "doc": "https://platform.minimaxi.com/docs/token-plan/intro", + "models": [ + { + "id": "MiniMax-M2.5", + "name": "MiniMax-M2.5", + "display_name": "MiniMax-M2.5", + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 204800, + "output": 131072 + }, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true + } + }, + "attachment": false, + "open_weights": true, + "release_date": "2026-02-12", + "last_updated": "2026-02-12", "type": "chat" }, { - "id": "qwen3-7-plus", - "name": "Qwen3.7 Plus", - "display_name": "Qwen3.7 Plus", + "id": "MiniMax-M3.1-Flash", + "name": "MiniMax-M3.1-Flash", + "display_name": "MiniMax-M3.1-Flash", "modalities": { "input": [ "text", @@ -153363,8 +152738,8 @@ ] }, "limit": { - "context": 983616, - "output": 65536 + "context": 1000000, + "output": 524288 }, "temperature": true, "tool_call": true, @@ -153375,40 +152750,37 @@ "extra_capabilities": { "reasoning": { "effort_options": [ - "none", "low", "medium", "high", + "xhigh", "max" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-04", - "release_date": "2026-06-02", - "last_updated": "2026-06-02", + "release_date": "2026-10-19", + "last_updated": "2026-10-19", "type": "chat" }, { - "id": "kimi-k3", - "name": "Kimi K3", - "display_name": "Kimi K3", + "id": "MiniMax-M2.7-highspeed", + "name": "MiniMax-M2.7-highspeed", + "display_name": "MiniMax-M2.7-highspeed", "modalities": { "input": [ - "text", - "image", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, + "context": 204800, "output": 131072 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -153417,23 +152789,24 @@ "extra_capabilities": { "reasoning": { "supported": true, - "effort_options": [ - "low", - "high", - "max" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, - "attachment": true, + "attachment": false, "open_weights": true, - "release_date": "2026-07-16", - "last_updated": "2026-07-16", + "release_date": "2026-03-18", + "last_updated": "2026-03-18", "type": "chat" }, { - "id": "qwen3-6-35b-a3b", - "name": "Qwen3.6 35B A3B", - "display_name": "Qwen3.6 35B A3B", + "id": "MiniMax-M2.1", + "name": "MiniMax-M2.1", + "display_name": "MiniMax-M2.1", "modalities": { "input": [ "text" @@ -153443,8 +152816,8 @@ ] }, "limit": { - "context": 131072, - "output": 16384 + "context": 204800, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -153454,38 +152827,30 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "none", - "low", - "medium", - "high", - "max" - ] + "supported": true } }, "attachment": false, "open_weights": true, - "release_date": "2026-04-17", - "last_updated": "2026-04-17", + "release_date": "2025-12-23", + "last_updated": "2025-12-23", "type": "chat" }, { - "id": "seed-2-0-lite", - "name": "Seed 2.0 Lite", - "display_name": "Seed 2.0 Lite", + "id": "MiniMax-M2", + "name": "MiniMax-M2", + "display_name": "MiniMax-M2", "modalities": { "input": [ - "text", - "image", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 256000, - "output": 128000 + "context": 204800, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -153493,25 +152858,16 @@ "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "low", - "medium", - "high" - ] - } - }, - "attachment": true, - "open_weights": false, - "release_date": "2026-02-14", - "last_updated": "2026-02-14", + "attachment": false, + "open_weights": true, + "release_date": "2025-10-27", + "last_updated": "2025-10-27", "type": "chat" }, { - "id": "deepseek-v4-pro", - "name": "DeepSeek V4 Pro", - "display_name": "DeepSeek V4 Pro", + "id": "MiniMax-M2.7", + "name": "MiniMax-M2.7", + "display_name": "MiniMax-M2.7", "modalities": { "input": [ "text" @@ -153521,8 +152877,8 @@ ] }, "limit": { - "context": 1000000, - "output": 393216 + "context": 204800, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -153533,49 +152889,35 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "high", - "effort_options": [ - "low", - "high", - "max" - ], "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ "thinking_blocks" - ], - "notes": [ - "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." ] } }, "attachment": false, "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-04-24", - "last_updated": "2026-04-24", + "release_date": "2026-03-18", + "last_updated": "2026-03-18", "type": "chat" }, { - "id": "qwen3-5-122b-a10b", - "name": "Qwen3.5 122B-A10B", - "display_name": "Qwen3.5 122B-A10B", + "id": "MiniMax-M2.5-highspeed", + "name": "MiniMax-M2.5-highspeed", + "display_name": "MiniMax-M2.5-highspeed", "modalities": { "input": [ - "text", - "image", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 258048, - "output": 64000 + "context": 204800, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -153585,25 +152927,19 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "none", - "low", - "medium", - "high", - "max" - ] + "supported": true } }, - "attachment": true, + "attachment": false, "open_weights": true, - "release_date": "2026-02-23", - "last_updated": "2026-02-23", + "release_date": "2026-02-13", + "last_updated": "2026-02-13", "type": "chat" }, { - "id": "gemma-4-26b-a4b", - "name": "Gemma 4 26B-A4B", - "display_name": "Gemma 4 26B-A4B", + "id": "MiniMax-M3.1-Flash-Preview", + "name": "MiniMax-M3.1-Flash-Preview", + "display_name": "MiniMax-M3.1-Flash-Preview", "modalities": { "input": [ "text", @@ -153615,8 +152951,8 @@ ] }, "limit": { - "context": 262144, - "output": 32768 + "context": 1000000, + "output": 512000 }, "temperature": true, "tool_call": true, @@ -153627,29 +152963,28 @@ "extra_capabilities": { "reasoning": { "effort_options": [ - "none", "low", "medium", "high", + "xhigh", "max" ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-04-02", - "last_updated": "2026-04-02", + "open_weights": false, + "release_date": "2026-09-27", + "last_updated": "2026-09-27", "type": "chat" }, { - "id": "mimo-v2-6-pro", - "name": "MiMo V2.6 Pro", - "display_name": "MiMo V2.6 Pro", + "id": "MiniMax-M3", + "name": "MiniMax-M3", + "display_name": "MiniMax-M3", "modalities": { "input": [ "text", "image", - "audio", "video" ], "output": [ @@ -153657,8 +152992,8 @@ ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 1000000, + "output": 524288 }, "temperature": true, "tool_call": true, @@ -153666,32 +153001,44 @@ "supported": true, "default": true }, + "extra_capabilities": { + "reasoning": { + "supported": true + } + }, "attachment": true, "open_weights": true, - "release_date": "2026-09-22", - "last_updated": "2026-09-22", + "release_date": "2026-06-01", + "last_updated": "2026-06-25", "type": "chat" - }, + } + ] + }, + "neuralwatt": { + "id": "neuralwatt", + "name": "Neuralwatt", + "display_name": "Neuralwatt", + "api": "https://api.neuralwatt.com/v1", + "doc": "https://portal.neuralwatt.com/docs", + "models": [ { - "id": "glm-5-3-flash", - "name": "GLM 5.3 Flash", - "display_name": "GLM 5.3 Flash", + "id": "kimi-k3", + "name": "Kimi K3", + "display_name": "Kimi K3", "modalities": { "input": [ "text", - "image", - "video", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 1048560, + "output": 1048560 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -153699,7 +153046,10 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "interleaved": true, "effort_options": [ + "none", "low", "high", "max" @@ -153708,58 +153058,103 @@ }, "attachment": true, "open_weights": true, - "release_date": "2026-08-26", - "last_updated": "2026-08-26", + "release_date": "2026-07-16", + "last_updated": "2026-07-16", "type": "chat" }, { - "id": "qwen3-5-plus", - "name": "Qwen3.5 Plus", - "display_name": "Qwen3.5 Plus", + "id": "kimi-k2.7-code", + "name": "Kimi K2.7 Code", + "display_name": "Kimi K2.7 Code", "modalities": { "input": [ "text", - "image", - "video" + "image" ], "output": [ "text" ] }, "limit": { - "context": 983616, - "output": 65536 + "context": 262128, + "output": 262128 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, "default": true }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true + } + }, "attachment": true, - "open_weights": false, - "knowledge": "2025-04", - "release_date": "2026-02-16", - "last_updated": "2026-02-16", + "open_weights": true, + "knowledge": "2025-01", + "release_date": "2026-06-12", + "last_updated": "2026-06-12", "type": "chat" }, { - "id": "minimax-m3", - "name": "MiniMax M3", - "display_name": "MiniMax M3", + "id": "deepseek-v4-flash-speed", + "name": "DeepSeek V4 Flash (Speed)", + "display_name": "DeepSeek V4 Flash (Speed)", + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1048560, + "output": 393216 + }, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "effort_options": [ + "none", + "high", + "max" + ] + } + }, + "attachment": false, + "open_weights": true, + "knowledge": "2025-05", + "release_date": "2026-07-31", + "last_updated": "2026-07-31", + "type": "chat" + }, + { + "id": "mimo-v2.6-pro", + "name": "MiMo-V2.6-Pro", + "display_name": "MiMo-V2.6-Pro", "modalities": { "input": [ "text", "image", - "video" + "audio" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 524288 + "context": 1048560, + "output": 393216 }, "temperature": true, "tool_call": true, @@ -153769,32 +153164,36 @@ }, "extra_capabilities": { "reasoning": { - "supported": true + "supported": true, + "interleaved": true, + "effort_options": [ + "none", + "high" + ] } }, "attachment": true, "open_weights": true, - "release_date": "2026-06-01", - "last_updated": "2026-06-01", + "release_date": "2026-09-22", + "last_updated": "2026-09-22", "type": "chat" }, { - "id": "seed-2-0-code", - "name": "Seed 2.0 Code", - "display_name": "Seed 2.0 Code", + "id": "deepseek-v4.1-flash-flex", + "name": "DeepSeek V4.1 Flash Flex", + "display_name": "DeepSeek V4.1 Flash Flex", "modalities": { "input": [ "text", - "image", - "video" + "image" ], "output": [ "text" ] }, "limit": { - "context": 256000, - "output": 128000 + "context": 1048560, + "output": 393216 }, "temperature": true, "tool_call": true, @@ -153804,34 +153203,40 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "interleaved": true, "effort_options": [ + "none", "low", - "medium", - "high" + "high", + "xhigh", + "max" ] } }, "attachment": true, - "open_weights": false, - "release_date": "2026-02-14", - "last_updated": "2026-02-14", + "open_weights": true, + "knowledge": "2025-05", + "release_date": "2026-10-17", + "last_updated": "2026-10-17", "type": "chat" }, { - "id": "glm-4-7-flash", - "name": "GLM 4.7 Flash", - "display_name": "GLM 4.7 Flash", + "id": "qwen3.6-35b-flex", + "name": "Qwen3.6 35B Flex", + "display_name": "Qwen3.6 35B Flex", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 131072 + "context": 131056, + "output": 131056 }, "temperature": true, "tool_call": true, @@ -153839,28 +153244,43 @@ "supported": true, "default": true }, - "attachment": false, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "effort_options": [ + "none", + "high" + ] + } + }, + "attachment": true, "open_weights": true, - "knowledge": "2025-04", - "release_date": "2026-01-19", - "last_updated": "2026-01-19", + "release_date": "2026-04-17", + "last_updated": "2026-04-17", "type": "chat" }, { - "id": "glm-5-3", - "name": "GLM 5.3", - "display_name": "GLM 5.3", + "id": "glm-5.3-flash", + "name": "GLM-5.3 Flash", + "display_name": "GLM-5.3 Flash", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 1048560, + "output": 1048560 }, "temperature": true, "tool_call": true, @@ -153870,6 +153290,8 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "interleaved": true, "effort_options": [ "low", "high", @@ -153877,27 +153299,26 @@ ] } }, - "attachment": false, + "attachment": true, "open_weights": true, - "release_date": "2026-08-14", - "last_updated": "2026-08-14", + "release_date": "2026-08-26", + "last_updated": "2026-08-26", "type": "chat" }, { - "id": "deepseek-v4-1-flash-1017", - "name": "DeepSeek V4.1 Flash 1017", - "display_name": "DeepSeek V4.1 Flash 1017", + "id": "deepseek-v4-flash-flex", + "name": "DeepSeek V4 Flash Flex", + "display_name": "DeepSeek V4 Flash Flex", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, + "context": 1048560, "output": 393216 }, "temperature": true, @@ -153908,95 +153329,93 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "interleaved": true, "effort_options": [ "none", - "low", - "medium", "high", "max" ] } }, - "attachment": true, + "attachment": false, "open_weights": true, "knowledge": "2025-05", - "release_date": "2026-10-17", - "last_updated": "2026-10-17", + "release_date": "2026-04-24", + "last_updated": "2026-04-24", "type": "chat" }, { - "id": "minimax-m2-7-highspeed", - "name": "MiniMax M2.7 Highspeed", - "display_name": "MiniMax M2.7 Highspeed", + "id": "kimi-k3-fast", + "name": "Kimi K3 Fast", + "display_name": "Kimi K3 Fast", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 204800, - "output": 131072 + "context": 1048560, + "output": 1048560 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { - "supported": true, - "default": true + "supported": false }, - "attachment": false, + "attachment": true, "open_weights": true, - "release_date": "2026-03-18", - "last_updated": "2026-03-18", + "release_date": "2026-07-16", + "last_updated": "2026-07-16", "type": "chat" }, { - "id": "qwen3-8-flash", - "name": "Qwen3.8 Flash", - "display_name": "Qwen3.8 Flash", + "id": "qwen3.6-35b-fast", + "name": "Qwen3.6 35B Fast", + "display_name": "Qwen3.6 35B Fast", "modalities": { "input": [ "text", - "image", - "video" + "image" ], "output": [ "text" ] }, "limit": { - "context": 983616, - "output": 131072 + "context": 131056, + "output": 131056 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true + "supported": true }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "none", - "low", - "medium", - "high", - "max" + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, "attachment": true, - "open_weights": false, - "release_date": "2026-08-26", - "last_updated": "2026-08-26", + "open_weights": true, + "release_date": "2026-04-17", + "last_updated": "2026-04-17", "type": "chat" }, { - "id": "gemma-3-27b", - "name": "Gemma 3 27B", - "display_name": "Gemma 3 27B", + "id": "deepseek-v4.1-flash", + "name": "DeepSeek V4.1 Flash", + "display_name": "DeepSeek V4.1 Flash", "modalities": { "input": [ "text", @@ -154007,38 +153426,50 @@ ] }, "limit": { - "context": 131072, - "output": 16384 + "context": 1048560, + "output": 393216 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "effort_options": [ + "none", + "low", + "high", + "xhigh", + "max" + ] + } }, "attachment": true, "open_weights": true, - "knowledge": "2024-08", - "release_date": "2025-03-12", - "last_updated": "2025-03-12", + "knowledge": "2025-05", + "release_date": "2026-10-17", + "last_updated": "2026-10-17", "type": "chat" }, { - "id": "seed-2-0-mini", - "name": "Seed 2.0 Mini", - "display_name": "Seed 2.0 Mini", + "id": "glm-5.3", + "name": "GLM 5.3", + "display_name": "GLM 5.3", "modalities": { "input": [ - "text", - "image", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 256000, - "output": 128000 + "context": 1048560, + "output": 1048560 }, "temperature": true, "tool_call": true, @@ -154048,94 +153479,114 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "interleaved": true, "effort_options": [ "low", - "medium", - "high" + "high", + "max" ] } }, - "attachment": true, - "open_weights": false, - "release_date": "2026-02-14", - "last_updated": "2026-02-14", + "attachment": false, + "open_weights": true, + "release_date": "2026-08-14", + "last_updated": "2026-08-14", "type": "chat" }, { - "id": "mimo-v2-6-flash", - "name": "MiMo V2.6 Flash", - "display_name": "MiMo V2.6 Flash", + "id": "kimi-k2.7-code-flex", + "name": "Kimi K2.7 Code Flex", + "display_name": "Kimi K2.7 Code Flex", "modalities": { "input": [ "text", - "image", - "audio", - "video" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 262128, + "output": 262128 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, "default": true }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true + } + }, "attachment": true, "open_weights": true, - "release_date": "2026-09-22", - "last_updated": "2026-09-22", + "knowledge": "2025-01", + "release_date": "2026-06-12", + "last_updated": "2026-06-12", "type": "chat" }, { - "id": "glm-4-5-flash", - "name": "GLM 4.5 Flash", - "display_name": "GLM 4.5 Flash", + "id": "kimi-k3-flex", + "name": "Kimi K3 Flex", + "display_name": "Kimi K3 Flex", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 98304 + "context": 1048560, + "output": 1048560 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, "default": true }, - "attachment": false, - "open_weights": false, - "knowledge": "2025-04", - "release_date": "2025-07-28", - "last_updated": "2025-07-28", + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "effort_options": [ + "none", + "low", + "high", + "max" + ] + } + }, + "attachment": true, + "open_weights": true, + "release_date": "2026-07-16", + "last_updated": "2026-07-16", "type": "chat" }, { - "id": "deepseek-v4-flash-0731", - "name": "DeepSeek V4 Flash 0731", - "display_name": "DeepSeek V4 Flash 0731", + "id": "qwen-3.8-27b-flex", + "name": "Qwen3.8 27B Flex", + "display_name": "Qwen3.8 27B Flex", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 393216 + "context": 262128, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -154146,37 +153597,37 @@ "extra_capabilities": { "reasoning": { "supported": true, + "interleaved": true, "effort_options": [ "none", "low", "medium", - "high", - "max" + "xhigh" ] } }, - "attachment": false, + "attachment": true, "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-07-31", - "last_updated": "2026-07-31", + "release_date": "2026-08-14", + "last_updated": "2026-08-14", "type": "chat" }, { - "id": "glm-5-1", - "name": "GLM 5.1", - "display_name": "GLM 5.1", + "id": "qwen-3.8-27b", + "name": "Qwen3.8 27B", + "display_name": "Qwen3.8 27B", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 202745, - "output": 128000 + "context": 262128, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -154186,25 +153637,26 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "interleaved": true, "effort_options": [ "none", "low", "medium", - "high", - "max" + "xhigh" ] } }, - "attachment": false, + "attachment": true, "open_weights": true, - "release_date": "2026-04-07", - "last_updated": "2026-04-07", + "release_date": "2026-08-14", + "last_updated": "2026-08-14", "type": "chat" }, { - "id": "deepseek-v4-pro-0813", - "name": "DeepSeek V4 Pro 0813", - "display_name": "DeepSeek V4 Pro 0813", + "id": "deepseek-v4-flash", + "name": "DeepSeek V4 Flash", + "display_name": "DeepSeek V4 Flash", "modalities": { "input": [ "text" @@ -154214,7 +153666,7 @@ ] }, "limit": { - "context": 1000000, + "context": 1048560, "output": 393216 }, "temperature": true, @@ -154226,36 +153678,48 @@ "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "high", "effort_options": [ - "none", "low", - "medium", "high", "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." ] } }, "attachment": false, "open_weights": true, - "release_date": "2026-08-13", - "last_updated": "2026-08-22", + "knowledge": "2025-05", + "release_date": "2026-04-24", + "last_updated": "2026-04-24", "type": "chat" }, { - "id": "step-3-5-flash", - "name": "Step 3.5 Flash", - "display_name": "Step 3.5 Flash", + "id": "glm-5.3-flash-flex", + "name": "GLM-5.3 Flash Flex", + "display_name": "GLM-5.3 Flash Flex", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 131072 + "context": 1048560, + "output": 1048560 }, "temperature": true, "tool_call": true, @@ -154263,17 +153727,27 @@ "supported": true, "default": true }, - "attachment": false, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "effort_options": [ + "low", + "high", + "max" + ] + } + }, + "attachment": true, "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-01-29", - "last_updated": "2026-02-13", + "release_date": "2026-08-26", + "last_updated": "2026-08-26", "type": "chat" }, { - "id": "fugu-max", - "name": "Fugu Max", - "display_name": "Fugu Max", + "id": "kimi-k2.7-code-fast", + "name": "Kimi K2.7 Code Fast", + "display_name": "Kimi K2.7 Code Fast", "modalities": { "input": [ "text", @@ -154284,8 +153758,8 @@ ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 262128, + "output": 262128 }, "temperature": false, "tool_call": true, @@ -154295,36 +153769,33 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "high", - "xhigh", - "max" - ] + "supported": true, + "interleaved": true } }, "attachment": true, - "open_weights": false, - "release_date": "2026-09-11", - "last_updated": "2026-09-11", + "open_weights": true, + "knowledge": "2025-01", + "release_date": "2026-06-12", + "last_updated": "2026-06-12", "type": "chat" }, { - "id": "qwen3-5-27b", - "name": "Qwen3.5 27B", - "display_name": "Qwen3.5 27B", + "id": "gemma-4-31b", + "name": "Gemma 4 31B", + "display_name": "Gemma 4 31B", "modalities": { "input": [ "text", - "image", - "video" + "image" ], "output": [ "text" ] }, "limit": { - "context": 258048, - "output": 64000 + "context": 262128, + "output": 16384 }, "temperature": true, "tool_call": true, @@ -154334,38 +153805,37 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "interleaved": true, "effort_options": [ "none", - "low", - "medium", - "high", "max" ] } }, "attachment": true, "open_weights": true, - "release_date": "2026-02-23", - "last_updated": "2026-02-23", + "knowledge": "2025-01", + "release_date": "2026-04-02", + "last_updated": "2026-04-02", "type": "chat" }, { - "id": "qwen3-5-9b", - "name": "Qwen3.5 9B", - "display_name": "Qwen3.5 9B", + "id": "qwen3.6-35b", + "name": "Qwen3.6 35B", + "display_name": "Qwen3.6 35B", "modalities": { "input": [ "text", - "image", - "video" + "image" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 32768 + "context": 131056, + "output": 131056 }, "temperature": true, "tool_call": true, @@ -154375,38 +153845,42 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ "none", - "low", - "medium", - "high", - "max" + "high" ] } }, "attachment": true, "open_weights": true, - "release_date": "2026-02-23", - "last_updated": "2026-02-23", + "release_date": "2026-04-17", + "last_updated": "2026-04-17", "type": "chat" }, { - "id": "qwen3-6-flash", - "name": "Qwen3.6 Flash", - "display_name": "Qwen3.6 Flash", + "id": "mimo-v2.6-pro-flex", + "name": "MiMo-V2.6-Pro Flex", + "display_name": "MiMo-V2.6-Pro Flex", "modalities": { "input": [ "text", "image", - "video" + "audio" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 65536 + "context": 1048560, + "output": 393216 }, "temperature": true, "tool_call": true, @@ -154416,38 +153890,35 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "interleaved": true, "effort_options": [ "none", - "low", - "medium", - "high", - "max" + "high" ] } }, "attachment": true, - "open_weights": false, - "release_date": "2026-04-27", - "last_updated": "2026-04-27", + "open_weights": true, + "release_date": "2026-09-22", + "last_updated": "2026-09-22", "type": "chat" }, { - "id": "qwen3-7-flash", - "name": "Qwen3.7 Flash", - "display_name": "Qwen3.7 Flash", + "id": "glm-5.3-flex", + "name": "GLM 5.3 Flex", + "display_name": "GLM 5.3 Flex", "modalities": { "input": [ - "text", - "image", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 983616, - "output": 65536 + "context": 1048560, + "output": 1048560 }, "temperature": true, "tool_call": true, @@ -154457,38 +153928,45 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "interleaved": true, "effort_options": [ - "none", "low", - "medium", "high", "max" ] } }, - "attachment": true, - "open_weights": false, - "release_date": "2026-07-15", - "last_updated": "2026-07-15", + "attachment": false, + "open_weights": true, + "release_date": "2026-08-14", + "last_updated": "2026-08-14", "type": "chat" - }, + } + ] + }, + "dinference": { + "id": "dinference", + "name": "DInference", + "display_name": "DInference", + "api": "https://api.dinference.com/v1", + "doc": "https://dinference.com", + "models": [ { - "id": "qwen3-5-397b-a17b", - "name": "Qwen3.5 397B-A17B", - "display_name": "Qwen3.5 397B-A17B", + "id": "minimax-m2.5", + "name": "MiniMax-M2.5", + "display_name": "MiniMax-M2.5", "modalities": { "input": [ - "text", - "image", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 258048, - "output": 64000 + "context": 200000, + "output": 32000 }, "temperature": true, "tool_call": true, @@ -154498,39 +153976,30 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "none", - "low", - "medium", - "high", - "max" - ] + "supported": true } }, - "attachment": true, + "attachment": false, "open_weights": true, - "release_date": "2026-02-15", - "last_updated": "2026-02-15", + "release_date": "2026-02-12", + "last_updated": "2026-02-12", "type": "chat" }, { - "id": "muse-spark-1-2", - "name": "Muse Spark 1.2", - "display_name": "Muse Spark 1.2", + "id": "glm-4.7", + "name": "GLM-4.7", + "display_name": "GLM-4.7", "modalities": { "input": [ - "text", - "image", - "video", - "audio" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 200000, + "output": 128000 }, "temperature": true, "tool_call": true, @@ -154540,79 +154009,70 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "minimal", - "low", - "medium", - "high", - "xhigh" + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, - "attachment": true, - "open_weights": false, - "release_date": "2026-08-05", - "last_updated": "2026-08-05", + "attachment": false, + "open_weights": true, + "knowledge": "2025-04", + "release_date": "2025-12-22", + "last_updated": "2025-12-22", "type": "chat" }, { - "id": "qwen3-8-max-0902", - "name": "Qwen3.8 Max 0902", - "display_name": "Qwen3.8 Max 0902", + "id": "gpt-oss-120b", + "name": "GPT OSS 120B", + "display_name": "GPT OSS 120B", "modalities": { "input": [ - "text", - "image", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 983616, - "output": 131072 + "context": 131072, + "output": 32768 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true + "supported": true }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "none", - "low", - "medium", - "high", - "max" - ] + "supported": true } }, - "attachment": true, - "open_weights": false, - "release_date": "2026-09-02", - "last_updated": "2026-09-02", + "attachment": false, + "open_weights": true, + "knowledge": "2024-06", + "release_date": "2025-08-05", + "last_updated": "2025-08-05", "type": "chat" }, { - "id": "qwen3-5-4b", - "name": "Qwen3.5 4B", - "display_name": "Qwen3.5 4B", + "id": "glm-5", + "name": "GLM-5", + "display_name": "GLM-5", "modalities": { "input": [ - "text", - "image", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 32768 + "context": 200000, + "output": 128000 }, "temperature": true, "tool_call": true, @@ -154622,38 +154082,36 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "none", - "low", - "medium", - "high", - "max" + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, - "attachment": true, + "attachment": false, "open_weights": true, - "release_date": "2026-03-02", - "last_updated": "2026-03-02", + "release_date": "2026-02-12", + "last_updated": "2026-02-12", "type": "chat" }, { - "id": "step-3-7-flash", - "name": "Step 3.7 Flash", - "display_name": "Step 3.7 Flash", + "id": "glm-5.1", + "name": "GLM-5.1", + "display_name": "GLM-5.1", "modalities": { "input": [ - "text", - "image", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 131072 + "context": 200000, + "output": 128000 }, "temperature": true, "tool_call": true, @@ -154663,38 +154121,36 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "low", - "medium", - "high" + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, - "attachment": true, + "attachment": false, "open_weights": true, - "knowledge": "2026-03-01", - "release_date": "2026-05-29", - "last_updated": "2026-05-29", + "release_date": "2026-04-07", + "last_updated": "2026-04-07", "type": "chat" }, { - "id": "muse-spark-1-1", - "name": "Muse Spark 1.1", - "display_name": "Muse Spark 1.1", + "id": "glm-5.2", + "name": "GLM-5.2", + "display_name": "GLM-5.2", "modalities": { "input": [ - "text", - "image", - "video", - "audio" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 1000000, + "output": 128000 }, "temperature": true, "tool_call": true, @@ -154704,36 +154160,48 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "minimal", - "low", - "medium", - "high", - "xhigh" + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, - "attachment": true, - "open_weights": false, - "release_date": "2026-04-08", - "last_updated": "2026-07-09", + "attachment": false, + "open_weights": true, + "release_date": "2026-06-13", + "last_updated": "2026-06-13", "type": "chat" - }, + } + ] + }, + "google": { + "id": "google", + "name": "google", + "display_name": "google", + "doc": "https://ai.google.dev/gemini-api/docs/models", + "models": [ { - "id": "glm-4-7-flashx", - "name": "GLM 4.7 FlashX", - "display_name": "GLM 4.7 FlashX", + "id": "gemini-3.5-flash", + "name": "Gemini 3.5 Flash", + "display_name": "Gemini 3.5 Flash", "modalities": { "input": [ - "text" + "text", + "image", + "video", + "audio", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 131072 + "context": 1048576, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -154741,90 +154209,133 @@ "supported": true, "default": true }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-04", - "release_date": "2026-01-19", - "last_updated": "2026-01-19", + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "level", + "level": "high", + "level_options": [ + "minimal", + "low", + "medium", + "high" + ], + "summaries": true, + "visibility": "summary", + "continuation": [ + "thought_signatures" + ] + } + }, + "attachment": true, + "open_weights": false, + "knowledge": "2025-01", + "release_date": "2026-05-19", + "last_updated": "2026-05-19", "type": "chat" }, { - "id": "kimi-k2-7-code", - "name": "Kimi K2.7 Code", - "display_name": "Kimi K2.7 Code", + "id": "deep-research-preview-04-2026", + "name": "Deep Research Preview (Apr-21-2026)", + "display_name": "Deep Research Preview (Apr-21-2026)", "modalities": { "input": [ "text", "image", - "video" + "video", + "audio", + "pdf" ], "output": [ - "text" + "text", + "image" ] }, "limit": { - "context": 262144, - "output": 131072 + "context": 131072, + "output": 65536 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, "default": true }, "attachment": true, - "open_weights": true, + "open_weights": false, "knowledge": "2025-01", - "release_date": "2026-06-12", - "last_updated": "2026-06-12", + "release_date": "2026-04-21", + "last_updated": "2026-04-21", "type": "chat" }, { - "id": "minimax-m2-7", - "name": "MiniMax M2.7", - "display_name": "MiniMax M2.7", + "id": "gemini-2.5-pro-preview-tts", + "name": "Gemini 2.5 Pro Preview TTS", + "display_name": "Gemini 2.5 Pro Preview TTS", "modalities": { "input": [ "text" ], "output": [ - "text" + "audio" ] }, "limit": { - "context": 204800, - "output": 131072 + "context": 8192, + "output": 16384 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { "supported": true, "default": true }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "budget", + "budget": { + "default": -1, + "min": 128, + "max": 32768, + "auto": -1, + "unit": "tokens" + }, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thought_signatures" + ] + } + }, "attachment": false, - "open_weights": true, - "release_date": "2026-03-18", - "last_updated": "2026-03-18", - "type": "chat" + "open_weights": false, + "knowledge": "2025-01", + "release_date": "2025-05-01", + "last_updated": "2025-05-01" }, { - "id": "mimo-v2-6-pro-ultraspeed", - "name": "MiMo V2.6 Pro UltraSpeed", - "display_name": "MiMo V2.6 Pro UltraSpeed", + "id": "deep-research-max-preview-04-2026", + "name": "Deep Research Max Preview (Apr-21-2026)", + "display_name": "Deep Research Max Preview (Apr-21-2026)", "modalities": { "input": [ "text", "image", + "video", "audio", - "video" + "pdf" ], "output": [ - "text" + "text", + "image" ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 131072, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -154833,67 +154344,69 @@ "default": true }, "attachment": true, - "open_weights": true, - "release_date": "2026-09-21", - "last_updated": "2026-09-21", + "open_weights": false, + "knowledge": "2025-01", + "release_date": "2026-04-21", + "last_updated": "2026-04-21", "type": "chat" }, { - "id": "step-5-preview", - "name": "Step 5 Preview", - "display_name": "Step 5 Preview", + "id": "gemini-3.1-flash-image", + "name": "Nano Banana 2", + "display_name": "Nano Banana 2", "modalities": { "input": [ "text", "image", - "video" + "video", + "pdf" ], "output": [ - "text" + "text", + "image" ] }, "limit": { - "context": 1024000, - "output": 131072 + "context": 131072, + "output": 32768 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { "supported": true, "default": true }, "extra_capabilities": { "reasoning": { + "supported": true, "effort_options": [ - "low", - "medium", + "minimal", "high" ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-09-16", - "last_updated": "2026-09-20", - "type": "chat" + "open_weights": false, + "knowledge": "2025-01", + "release_date": "2026-05-28", + "last_updated": "2026-05-28" }, { - "id": "seed-2-1-turbo", - "name": "Seed 2.1 Turbo", - "display_name": "Seed 2.1 Turbo", + "id": "gemma-4-26b-a4b-it", + "name": "Gemma 4 26B A4B IT", + "display_name": "Gemma 4 26B A4B IT", "modalities": { "input": [ "text", - "image", - "video" + "image" ], "output": [ "text" ] }, "limit": { - "context": 256000, - "output": 65536 + "context": 262144, + "output": 32768 }, "temperature": true, "tool_call": true, @@ -154903,75 +154416,60 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "low", - "medium", - "high" - ] + "supported": true } }, "attachment": true, - "open_weights": false, - "release_date": "2026-06-23", - "last_updated": "2026-06-23", + "open_weights": true, + "release_date": "2026-04-02", + "last_updated": "2026-04-02", "type": "chat" }, { - "id": "qwen3-5-35b-a3b", - "name": "Qwen3.5 35B-A3B", - "display_name": "Qwen3.5 35B-A3B", + "id": "lyria-3-pro-preview", + "name": "Lyria 3 Pro Preview", + "display_name": "Lyria 3 Pro Preview", "modalities": { "input": [ "text", - "image", - "video" + "image" ], "output": [ - "text" + "text", + "audio" ] }, "limit": { - "context": 258048, - "output": 64000 + "context": 1048576, + "output": 65536 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "none", - "low", - "medium", - "high", - "max" - ] - } + "supported": false }, "attachment": true, - "open_weights": true, - "release_date": "2026-02-23", - "last_updated": "2026-02-23", + "open_weights": false, + "release_date": "2026-03-25", + "last_updated": "2026-03-25", "type": "chat" }, { - "id": "step-3-5-flash-2603", - "name": "Step 3.5 Flash 2603", - "display_name": "Step 3.5 Flash 2603", + "id": "gemini-2.5-computer-use-preview-10-2025", + "name": "Gemini 2.5 Computer Use Preview 10-2025", + "display_name": "Gemini 2.5 Computer Use Preview 10-2025", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 131072 + "context": 128000, + "output": 64000 }, "temperature": true, "tool_call": true, @@ -154979,37 +154477,32 @@ "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "low", - "high" - ] - } - }, - "attachment": false, - "open_weights": true, + "attachment": true, + "open_weights": false, "knowledge": "2025-01", - "release_date": "2026-04-02", - "last_updated": "2026-04-02", + "release_date": "2025-10-07", + "last_updated": "2025-10-07", "type": "chat" }, { - "id": "fugu-ultra-v2-0", - "name": "Fugu Ultra v2.0", - "display_name": "Fugu Ultra v2.0", + "id": "gemini-4-argon", + "name": "Gemini 4 Argon", + "display_name": "Gemini 4 Argon", "modalities": { "input": [ "text", - "image" + "image", + "video", + "audio", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 1048576, + "output": 32768 }, "temperature": false, "tool_call": true, @@ -155020,99 +154513,106 @@ "extra_capabilities": { "reasoning": { "effort_options": [ - "high", - "xhigh", - "max" + "low", + "medium", + "high" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-08-28", - "release_date": "2026-09-11", - "last_updated": "2026-09-11", + "knowledge": "2026-03", + "release_date": "2026-10-13", + "last_updated": "2026-10-13", "type": "chat" }, { - "id": "qwen3-8-27b", - "name": "Qwen3.8 27B", - "display_name": "Qwen3.8 27B", + "id": "gemini-3-pro-image", + "name": "Nano Banana Pro", + "display_name": "Nano Banana Pro", "modalities": { "input": [ "text", - "image", - "video" + "image" ], "output": [ - "text" + "text", + "image" ] }, "limit": { - "context": 262144, + "context": 65536, "output": 32768 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { "supported": true, "default": true }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "none", + "supported": true, + "default_enabled": true, + "mode": "level", + "level": "high", + "level_options": [ "low", - "medium", - "xhigh" + "high" + ], + "summaries": true, + "visibility": "summary", + "continuation": [ + "thought_signatures" ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-08-14", - "last_updated": "2026-08-14", - "type": "chat" + "open_weights": false, + "knowledge": "2025-01", + "release_date": "2026-05-28", + "last_updated": "2026-05-28" }, { - "id": "glm-4-6v-flash", - "name": "GLM 4.6V Flash", - "display_name": "GLM 4.6V Flash", + "id": "lyria-3-clip-preview", + "name": "Lyria 3 Clip Preview", + "display_name": "Lyria 3 Clip Preview", "modalities": { "input": [ "text", - "image", - "video", - "pdf" + "image" ], "output": [ - "text" + "text", + "audio" ] }, "limit": { - "context": 131072, - "output": 32768 + "context": 1048576, + "output": 65536 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true + "supported": false }, "attachment": true, - "open_weights": true, - "release_date": "2025-12-08", - "last_updated": "2025-12-08", + "open_weights": false, + "release_date": "2026-03-25", + "last_updated": "2026-03-25", "type": "chat" }, { - "id": "muse-spark-1-3", - "name": "Muse Spark 1.3", - "display_name": "Muse Spark 1.3", + "id": "gemini-flash-latest", + "name": "Gemini Flash Latest", + "display_name": "Gemini Flash Latest", "modalities": { "input": [ "text", "image", - "video" + "video", + "audio", + "pdf" ], "output": [ "text" @@ -155120,7 +154620,7 @@ }, "limit": { "context": 1048576, - "output": 131072 + "output": 65536 }, "temperature": true, "tool_call": true, @@ -155131,37 +154631,38 @@ "extra_capabilities": { "reasoning": { "effort_options": [ - "minimal", "low", "medium", - "high", - "xhigh" + "high" ] } }, "attachment": true, "open_weights": false, - "release_date": "2026-09-02", - "last_updated": "2026-09-02", + "knowledge": "2026-03", + "release_date": "2026-08-13", + "last_updated": "2026-08-13", "type": "chat" }, { - "id": "qwen3-5-flash", - "name": "Qwen3.5 Flash", - "display_name": "Qwen3.5 Flash", + "id": "gemini-3.6-flash", + "name": "Gemini 3.6 Flash", + "display_name": "Gemini 3.6 Flash", "modalities": { "input": [ "text", "image", - "video" + "video", + "audio", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 995904, - "output": 32768 + "context": 1048576, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -155169,28 +154670,51 @@ "supported": true, "default": true }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "level", + "level": "high", + "level_options": [ + "minimal", + "low", + "medium", + "high" + ], + "summaries": true, + "visibility": "summary", + "continuation": [ + "thought_signatures" + ] + } + }, "attachment": true, "open_weights": false, - "release_date": "2026-02-23", - "last_updated": "2026-02-23", + "knowledge": "2026-03", + "release_date": "2026-07-21", + "last_updated": "2026-07-21", "type": "chat" }, { - "id": "muse-glimmer-30b", - "name": "Muse Glimmer 30B", - "display_name": "Muse Glimmer 30B", + "id": "gemini-3.8-flash", + "name": "Gemini 3.8 Flash", + "display_name": "Gemini 3.8 Flash", "modalities": { "input": [ "text", - "image" + "image", + "video", + "audio", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 32768 + "context": 1048576, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -155200,40 +154724,48 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "none", + "supported": true, + "default_enabled": true, + "mode": "level", + "level": "high", + "level_options": [ + "minimal", "low", "medium", - "high", - "max" + "high" + ], + "summaries": true, + "visibility": "summary", + "continuation": [ + "thought_signatures" ] } }, "attachment": true, - "open_weights": true, - "knowledge": "2026-01-04", - "release_date": "2026-08-10", - "last_updated": "2026-08-10", + "open_weights": false, + "release_date": "2026-09-02", + "last_updated": "2026-09-02", "type": "chat" }, { - "id": "qwen3-8-omni-flash", - "name": "Qwen3.8 Omni Flash", - "display_name": "Qwen3.8 Omni Flash", + "id": "gemini-3.1-flash-lite-preview", + "name": "Gemini 3.1 Flash Lite Preview", + "display_name": "Gemini 3.1 Flash Lite Preview", "modalities": { "input": [ "text", "image", + "video", "audio", - "video" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1006592, - "output": 131072 + "context": 1048576, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -155244,37 +154776,37 @@ "extra_capabilities": { "reasoning": { "effort_options": [ - "none", + "minimal", "low", "medium", - "high", - "max" + "high" ] } }, "attachment": true, "open_weights": false, - "release_date": "2026-09-17", - "last_updated": "2026-09-17", + "knowledge": "2025-01", + "release_date": "2026-03-03", + "last_updated": "2026-03-03", "type": "chat" }, { - "id": "qwen3-6-27b", - "name": "Qwen3.6 27B", - "display_name": "Qwen3.6 27B", + "id": "gemini-3.1-flash-lite-image", + "name": "Nano Banana 2 Lite", + "display_name": "Nano Banana 2 Lite", "modalities": { "input": [ "text", - "image", - "video" + "image" ], "output": [ - "text" + "text", + "image" ] }, "limit": { - "context": 229376, - "output": 64000 + "context": 65536, + "output": 4096 }, "temperature": true, "tool_call": true, @@ -155284,87 +154816,99 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, "effort_options": [ - "none", - "low", - "medium", - "high", - "max" + "minimal", + "high" ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-04-22", - "last_updated": "2026-04-22", - "type": "chat" + "open_weights": false, + "knowledge": "2025-01", + "release_date": "2026-06-30", + "last_updated": "2026-06-30" }, { - "id": "kimi-k2-6", - "name": "Kimi K2.6", - "display_name": "Kimi K2.6", + "id": "gemini-2.5-pro", + "name": "Gemini 2.5 Pro", + "display_name": "Gemini 2.5 Pro", "modalities": { "input": [ "text", "image", - "video" + "audio", + "video", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 16000 + "context": 1048576, + "output": 65536 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": true, + "budget": { + "default": -1, + "min": 128, + "max": 32768 + } }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "none", - "low", - "medium", - "high", - "max" + "supported": true, + "default_enabled": true, + "mode": "budget", + "budget": { + "default": -1, + "min": 128, + "max": 32768, + "auto": -1, + "unit": "tokens" + }, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thought_signatures" ] } }, + "search": { + "supported": true, + "default": false + }, "attachment": true, - "open_weights": true, + "open_weights": false, "knowledge": "2025-01", - "release_date": "2026-04-20", - "last_updated": "2026-04-20", + "release_date": "2025-06-17", + "last_updated": "2025-06-17", "type": "chat" - } - ] - }, - "cline-pass": { - "id": "cline-pass", - "name": "ClinePass", - "display_name": "ClinePass", - "api": "https://api.cline.bot/api/v1", - "doc": "https://docs.cline.bot/getting-started/clinepass", - "models": [ + }, { - "id": "cline-pass/qwen3.7-max", - "name": "Qwen3.7 Max", - "display_name": "Qwen3.7 Max", + "id": "gemini-flash-lite-latest", + "name": "Gemini Flash-Lite Latest", + "display_name": "Gemini Flash-Lite Latest", "modalities": { "input": [ - "text" + "text", + "image", + "video", + "audio", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 1048576, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -155374,34 +154918,40 @@ }, "extra_capabilities": { "reasoning": { - "supported": true + "effort_options": [ + "minimal", + "low", + "medium", + "high" + ] } }, - "attachment": false, + "attachment": true, "open_weights": false, - "release_date": "2026-05-21", - "last_updated": "2026-05-21", + "knowledge": "2026-03", + "release_date": "2026-07-21", + "last_updated": "2026-07-21", "type": "chat" }, { - "id": "cline-pass/muse-spark-1.3-contributor", - "name": "Muse Spark 1.3 Contributor", - "display_name": "Muse Spark 1.3 Contributor", + "id": "gemini-3.1-flash-live-preview", + "name": "Gemini 3.1 Flash Live Preview", + "display_name": "Gemini 3.1 Flash Live Preview", "modalities": { "input": [ "text", "image", "video", - "pdf", "audio" ], "output": [ - "text" + "text", + "audio" ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 131072, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -155411,29 +154961,38 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ + "supported": true, + "default_enabled": true, + "mode": "level", + "level": "high", + "level_options": [ "minimal", "low", "medium", - "high", - "xhigh" + "high" + ], + "summaries": true, + "visibility": "summary", + "continuation": [ + "thought_signatures" ] } }, "attachment": true, "open_weights": false, - "release_date": "2026-09-02", - "last_updated": "2026-09-02", - "type": "chat" + "knowledge": "2025-01", + "release_date": "2026-03-26", + "last_updated": "2026-03-26" }, { - "id": "cline-pass/glm-5.3-flash", - "name": "cline-pass/glm-5.3-flash", - "display_name": "cline-pass/glm-5.3-flash", + "id": "gemini-2.5-flash-lite", + "name": "Gemini 2.5 Flash Lite", + "display_name": "Gemini 2.5 Flash Lite", "modalities": { "input": [ "text", "image", + "audio", "video", "pdf" ], @@ -155442,98 +155001,123 @@ ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 1048576, + "output": 65536 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false, + "budget": { + "default": -1, + "min": 512, + "max": 24576 + } }, "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, + "default_enabled": false, + "mode": "budget", + "budget": { + "default": -1, + "min": 512, + "max": 24576, + "auto": -1, + "unit": "tokens" + }, "summaries": true, "visibility": "summary", "continuation": [ - "thinking_blocks" - ], - "effort_options": [ - "low", - "high", - "max" + "thought_signatures" ] } }, + "search": { + "supported": true, + "default": false + }, "attachment": true, - "open_weights": true, - "release_date": "2026-08-26", - "last_updated": "2026-08-26", + "open_weights": false, + "knowledge": "2025-01", + "release_date": "2025-06-17", + "last_updated": "2025-06-17", "type": "chat" }, { - "id": "cline-pass/deepseek-v4-flash", - "name": "DeepSeek V4 Flash", - "display_name": "DeepSeek V4 Flash", + "id": "gemini-2.5-flash", + "name": "Gemini 2.5 Flash", + "display_name": "Gemini 2.5 Flash", "modalities": { "input": [ - "text" + "text", + "image", + "audio", + "video", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 384000 + "context": 1048576, + "output": 65536 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": true, + "budget": { + "default": -1, + "min": 0, + "max": 24576 + } }, "extra_capabilities": { "reasoning": { "supported": true, "default_enabled": true, - "mode": "effort", - "effort": "high", - "effort_options": [ - "low", - "high", - "max" - ], - "interleaved": true, + "mode": "budget", + "budget": { + "default": -1, + "min": 0, + "max": 24576, + "auto": -1, + "off": 0, + "unit": "tokens" + }, "summaries": true, "visibility": "summary", "continuation": [ - "thinking_blocks" - ], - "notes": [ - "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." + "thought_signatures" ] } }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-04-24", - "last_updated": "2026-04-24", + "search": { + "supported": true, + "default": false + }, + "attachment": true, + "open_weights": false, + "knowledge": "2025-01", + "release_date": "2025-06-17", + "last_updated": "2025-06-17", "type": "chat" }, { - "id": "cline-pass/mimo-v2.6-pro", - "name": "MiMo-V2.6-Pro", - "display_name": "MiMo-V2.6-Pro", + "id": "gemini-pro-latest", + "name": "Gemini Pro Latest", + "display_name": "Gemini Pro Latest", "modalities": { "input": [ "text", "image", + "video", "audio", - "video" + "pdf" ], "output": [ "text" @@ -155541,7 +155125,7 @@ }, "limit": { "context": 1048576, - "output": 131072 + "output": 65536 }, "temperature": true, "tool_call": true, @@ -155552,29 +155136,30 @@ "extra_capabilities": { "reasoning": { "effort_options": [ - "none", "low", "medium", - "high", - "xhigh" + "high" ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-09-22", - "last_updated": "2026-09-22", + "open_weights": false, + "knowledge": "2025-01", + "release_date": "2026-02-19", + "last_updated": "2026-02-19", "type": "chat" }, { - "id": "cline-pass/kimi-k3", - "name": "Kimi K3", - "display_name": "Kimi K3", + "id": "gemini-3.1-pro-preview", + "name": "Gemini 3.1 Pro Preview", + "display_name": "Gemini 3.1 Pro Preview", "modalities": { "input": [ "text", "image", - "video" + "video", + "audio", + "pdf" ], "output": [ "text" @@ -155582,9 +155167,9 @@ }, "limit": { "context": 1048576, - "output": 131072 + "output": 65536 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -155593,81 +155178,98 @@ "extra_capabilities": { "reasoning": { "supported": true, - "effort_options": [ - "none", + "default_enabled": true, + "mode": "level", + "level": "high", + "level_options": [ "low", - "high", - "max" + "high" + ], + "summaries": true, + "visibility": "summary", + "continuation": [ + "thought_signatures" ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-07-16", - "last_updated": "2026-07-16", + "open_weights": false, + "knowledge": "2025-01", + "release_date": "2026-02-19", + "last_updated": "2026-02-19", "type": "chat" }, { - "id": "cline-pass/deepseek-v4-pro", - "name": "DeepSeek V4 Pro", - "display_name": "DeepSeek V4 Pro", + "id": "gemini-embedding-2", + "name": "Gemini Embedding 2", + "display_name": "Gemini Embedding 2", "modalities": { "input": [ - "text" + "text", + "image", + "audio", + "video", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 384000 + "context": 8192, + "output": 1 }, - "temperature": true, - "tool_call": true, + "temperature": false, + "tool_call": false, "reasoning": { - "supported": true, - "default": true + "supported": false }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "high", - "effort_options": [ - "low", - "high", - "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." - ] - } + "attachment": true, + "open_weights": false, + "knowledge": "2025-11", + "release_date": "2026-04-22", + "last_updated": "2026-04-22", + "type": "embedding" + }, + { + "id": "gemini-embedding-001", + "name": "Gemini Embedding 001", + "display_name": "Gemini Embedding 001", + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 2048, + "output": 1 + }, + "temperature": false, + "tool_call": false, + "reasoning": { + "supported": false }, "attachment": false, - "open_weights": true, + "open_weights": false, "knowledge": "2025-05", - "release_date": "2026-04-24", - "last_updated": "2026-04-24", - "type": "chat" + "release_date": "2025-05-20", + "last_updated": "2025-05-20", + "type": "embedding" }, { - "id": "cline-pass/mimo-v2.6-flash", - "name": "MiMo-V2.6-Flash", - "display_name": "MiMo-V2.6-Flash", + "id": "gemini-3.7-flash", + "name": "Gemini 3.7 Flash", + "display_name": "Gemini 3.7 Flash", "modalities": { "input": [ "text", "image", + "video", "audio", - "video" + "pdf" ], "output": [ "text" @@ -155675,7 +155277,7 @@ }, "limit": { "context": 1048576, - "output": 131072 + "output": 65536 }, "temperature": true, "tool_call": true, @@ -155685,30 +155287,38 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "none", + "supported": true, + "default_enabled": true, + "mode": "level", + "level": "high", + "level_options": [ + "minimal", "low", "medium", - "high", - "xhigh" + "high" + ], + "summaries": true, + "visibility": "summary", + "continuation": [ + "thought_signatures" ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-09-22", - "last_updated": "2026-09-22", + "open_weights": false, + "knowledge": "2026-03", + "release_date": "2026-08-13", + "last_updated": "2026-08-13", "type": "chat" }, { - "id": "cline-pass/kimi-k2.6", - "name": "Kimi K2.6", - "display_name": "Kimi K2.6", + "id": "gemma-4-31b-it", + "name": "Gemma 4 31B IT", + "display_name": "Gemma 4 31B IT", "modalities": { "input": [ "text", - "image", - "video" + "image" ], "output": [ "text" @@ -155716,7 +155326,7 @@ }, "limit": { "context": 262144, - "output": 262144 + "output": 32768 }, "temperature": true, "tool_call": true, @@ -155731,15 +155341,14 @@ }, "attachment": true, "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-04-20", - "last_updated": "2026-04-20", + "release_date": "2026-04-02", + "last_updated": "2026-04-02", "type": "chat" }, { - "id": "cline-pass/minimax-m3", - "name": "MiniMax-M3", - "display_name": "MiniMax-M3", + "id": "gemini-omni-flash-preview", + "name": "Gemini Omni Flash Preview", + "display_name": "Gemini Omni Flash Preview", "modalities": { "input": [ "text", @@ -155747,120 +155356,99 @@ "video" ], "output": [ - "text" + "video" ] }, "limit": { - "context": 1048576, - "output": 512000 + "context": 131072, + "output": 65536 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "supported": true - } - }, "attachment": true, - "open_weights": true, - "release_date": "2026-06-01", - "last_updated": "2026-06-01", - "type": "chat" + "open_weights": false, + "release_date": "2026-06-30", + "last_updated": "2026-06-30" }, { - "id": "cline-pass/qwen3.8-max", - "name": "Qwen3.8 Max", - "display_name": "Qwen3.8 Max", + "id": "gemini-3.1-flash-tts-preview", + "name": "Gemini 3.1 Flash TTS Preview", + "display_name": "Gemini 3.1 Flash TTS Preview", "modalities": { "input": [ - "text", - "image", - "video", - "pdf" + "text" ], "output": [ - "text" + "audio" ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 8192, + "output": 16384 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "effort_options": [ - "none", - "low", - "medium", - "xhigh" - ] - } - }, - "attachment": true, + "attachment": false, "open_weights": false, - "release_date": "2026-08-03", - "last_updated": "2026-08-03", - "type": "chat" + "knowledge": "2025-01", + "release_date": "2026-04-15", + "last_updated": "2026-04-15" }, { - "id": "cline-pass/mimo-v2.5", - "name": "MiMo-V2.5", - "display_name": "MiMo-V2.5", + "id": "gemini-3.5-live-translate-preview", + "name": "Gemini 3.5 Live Translate Preview", + "display_name": "Gemini 3.5 Live Translate Preview", "modalities": { "input": [ - "text", - "image", - "audio", - "video" + "audio" ], "output": [ + "audio", "text" ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 16384, + "output": 32768 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true + "supported": false }, "attachment": true, - "open_weights": true, - "knowledge": "2024-12", - "release_date": "2026-04-22", - "last_updated": "2026-04-22", - "type": "chat" + "open_weights": false, + "knowledge": "2025-01", + "release_date": "2026-06-09", + "last_updated": "2026-06-09" }, { - "id": "cline-pass/deepseek-v4.1-flash", - "name": "DeepSeek V4.1 Flash", - "display_name": "DeepSeek V4.1 Flash", + "id": "gemini-3.1-flash-lite", + "name": "Gemini 3.1 Flash Lite", + "display_name": "Gemini 3.1 Flash Lite", "modalities": { "input": [ "text", - "image" + "image", + "video", + "audio", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 384000 + "context": 1048576, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -155872,27 +155460,31 @@ "reasoning": { "supported": true, "effort_options": [ - "none", + "minimal", "low", - "high", - "max" + "medium", + "high" ] } }, "attachment": true, - "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-09-10", - "last_updated": "2026-09-10", + "open_weights": false, + "knowledge": "2025-01", + "release_date": "2026-05-07", + "last_updated": "2026-05-07", "type": "chat" }, { - "id": "cline-pass/mimo-v2.5-pro", - "name": "MiMo-V2.5-Pro", - "display_name": "MiMo-V2.5-Pro", + "id": "gemini-3.1-pro-preview-customtools", + "name": "Gemini 3.1 Pro Preview Custom Tools", + "display_name": "Gemini 3.1 Pro Preview Custom Tools", "modalities": { "input": [ - "text" + "text", + "image", + "video", + "audio", + "pdf" ], "output": [ "text" @@ -155900,7 +155492,7 @@ }, "limit": { "context": 1048576, - "output": 131072 + "output": 65536 }, "temperature": true, "tool_call": true, @@ -155908,64 +155500,76 @@ "supported": true, "default": true }, - "attachment": false, - "open_weights": true, - "knowledge": "2024-12", - "release_date": "2026-04-22", - "last_updated": "2026-04-22", + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "level", + "level": "high", + "level_options": [ + "low", + "high" + ], + "summaries": true, + "visibility": "summary", + "continuation": [ + "thought_signatures" + ] + } + }, + "attachment": true, + "open_weights": false, + "knowledge": "2025-01", + "release_date": "2026-02-19", + "last_updated": "2026-02-19", "type": "chat" }, { - "id": "cline-pass/kimi-k2.7-code", - "name": "Kimi K2.7 Code", - "display_name": "Kimi K2.7 Code", + "id": "gemini-2.5-flash-preview-tts", + "name": "Gemini 2.5 Flash Preview TTS", + "display_name": "Gemini 2.5 Flash Preview TTS", "modalities": { "input": [ - "text", - "image", - "video" + "text" ], "output": [ - "text" + "audio" ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 8192, + "output": 16384 }, - "temperature": false, - "tool_call": true, + "temperature": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true - } + "supported": false }, - "attachment": true, - "open_weights": true, + "attachment": false, + "open_weights": false, "knowledge": "2025-01", - "release_date": "2026-06-12", - "last_updated": "2026-06-12", - "type": "chat" + "release_date": "2025-05-01", + "last_updated": "2025-05-01" }, { - "id": "cline-pass/glm-5.3", - "name": "GLM-5.3", - "display_name": "GLM-5.3", + "id": "gemini-3-flash-preview", + "name": "Gemini 3 Flash Preview", + "display_name": "Gemini 3 Flash Preview", "modalities": { "input": [ - "text" + "text", + "image", + "video", + "audio", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 1048576, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -155976,76 +155580,77 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, + "default_enabled": true, + "mode": "level", + "level": "high", + "level_options": [ + "minimal", + "low", + "medium", + "high" + ], "summaries": true, "visibility": "summary", "continuation": [ - "thinking_blocks" - ], - "effort_options": [ - "low", - "high", - "max" + "thought_signatures" ] } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-08-14", - "last_updated": "2026-08-14", + "attachment": true, + "open_weights": false, + "knowledge": "2025-01", + "release_date": "2025-12-17", + "last_updated": "2025-12-17", "type": "chat" }, { - "id": "cline-pass/qwen3.7-plus", - "name": "Qwen3.7 Plus", - "display_name": "Qwen3.7 Plus", + "id": "gemini-2.5-flash-image", + "name": "Gemini 2.5 Flash Image", + "display_name": "Gemini 2.5 Flash Image", "modalities": { "input": [ "text", - "image", - "video" + "image" ], "output": [ - "text" + "text", + "image" ] }, "limit": { - "context": 1000000, - "output": 64000 + "context": 32768, + "output": 32768 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true - } + "supported": false }, - "attachment": true, + "attachment": false, "open_weights": false, - "knowledge": "2025-04", - "release_date": "2026-06-02", - "last_updated": "2026-06-02", - "type": "chat" + "knowledge": "2024-06", + "release_date": "2025-08-26", + "last_updated": "2025-08-26" }, { - "id": "cline-pass/glm-5.2", - "name": "GLM-5.2", - "display_name": "GLM-5.2", + "id": "gemini-3.5-flash-lite", + "name": "Gemini 3.5 Flash Lite", + "display_name": "Gemini 3.5 Flash Lite", "modalities": { "input": [ - "text" + "text", + "image", + "video", + "audio", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 1048576, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -156057,31 +155662,24 @@ "reasoning": { "supported": true, "effort_options": [ - "none", - "high", - "max" + "minimal", + "low", + "medium", + "high" ] } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-06-13", - "last_updated": "2026-06-13", + "attachment": true, + "open_weights": false, + "knowledge": "2026-03", + "release_date": "2026-07-21", + "last_updated": "2026-07-21", "type": "chat" - } - ] - }, - "nan": { - "id": "nan", - "name": "NaN", - "display_name": "NaN", - "api": "https://api.nan.builders/v1", - "doc": "https://nan.builders/docs/models", - "models": [ + }, { - "id": "glm5.3-flash", - "name": "GLM-5.3-Flash", - "display_name": "GLM-5.3-Flash", + "id": "gemini-2.5-flash-preview-09-2025", + "name": "Gemini 2.5 Flash Preview 09 2025", + "display_name": "Gemini 2.5 Flash Preview 09 2025", "modalities": { "input": [ "text", @@ -156092,63 +155690,77 @@ ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 1048576, + "output": 65536 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": true, + "budget": { + "default": -1, + "min": 0, + "max": 24576 + } }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "minimal", - "low", - "medium", - "high", - "xhigh", - "max" + "supported": true, + "default_enabled": true, + "mode": "budget", + "budget": { + "default": -1, + "min": 0, + "max": 24576, + "auto": -1, + "off": 0, + "unit": "tokens" + }, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thought_signatures" ] } }, + "search": { + "supported": true, + "default": false + }, "attachment": true, - "open_weights": true, - "release_date": "2026-08-26", - "last_updated": "2026-08-26", "type": "chat" }, { - "id": "qwen-image-2.1", - "name": "Qwen-Image-2.1", - "display_name": "Qwen-Image-2.1", + "id": "gemini-2.5-flash-image-preview", + "name": "Gemini 2.5 Flash Image Preview", + "display_name": "Gemini 2.5 Flash Image Preview", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ + "text", "image" ] }, "limit": { - "context": 8192, - "output": 8192 + "context": 32768, + "output": 32768 }, - "temperature": false, + "temperature": true, "tool_call": false, "reasoning": { "supported": false }, "attachment": false, - "open_weights": true, - "release_date": "2026-09-14", - "last_updated": "2026-09-21" + "type": "imageGeneration" }, { - "id": "deepseek-v4-flash", - "name": "DeepSeek V4.1 Flash", - "display_name": "DeepSeek V4.1 Flash", + "id": "gemini-2.5-flash-lite-preview-09-2025", + "name": "Gemini 2.5 Flash Lite Preview 09 2025", + "display_name": "Gemini 2.5 Flash Lite Preview 09 2025", "modalities": { "input": [ "text", @@ -156159,53 +155771,113 @@ ] }, "limit": { - "context": 1000000, - "output": 384000 + "context": 1048576, + "output": 65536 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false, + "budget": { + "default": -1, + "min": 512, + "max": 24576 + } }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "high", - "effort_options": [ - "low", - "high", - "max" - ], - "interleaved": true, + "default_enabled": false, + "mode": "budget", + "budget": { + "default": -1, + "min": 512, + "max": 24576, + "auto": -1, + "unit": "tokens" + }, "summaries": true, "visibility": "summary", "continuation": [ - "thinking_blocks" - ], - "notes": [ - "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." + "thought_signatures" ] } }, + "search": { + "supported": true, + "default": false + }, + "attachment": true, + "type": "chat" + }, + { + "id": "gemini-2.0-flash", + "name": "Gemini 2.0 Flash", + "display_name": "Gemini 2.0 Flash", + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1048576, + "output": 8192 + }, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": true, + "default": false + }, + "search": { + "supported": true, + "default": false + }, + "attachment": true, + "type": "chat" + }, + { + "id": "gemini-2.0-flash-preview-image-generation", + "name": "Gemini 2.0 Flash Preview Image Generation", + "display_name": "Gemini 2.0 Flash Preview Image Generation", + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text", + "image" + ] + }, + "limit": { + "context": 32000, + "output": 8192 + }, + "temperature": true, + "tool_call": false, + "reasoning": { + "supported": false + }, + "search": { + "supported": false + }, "attachment": true, - "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-09-10", - "last_updated": "2026-09-10", "type": "chat" }, { - "id": "mimo-v2.6-flash", - "name": "MiMo-V2.6-Flash", - "display_name": "MiMo-V2.6-Flash", + "id": "gemini-2.0-flash-lite", + "name": "Gemini 2.0 Flash Lite", + "display_name": "Gemini 2.0 Flash Lite", "modalities": { "input": [ "text", - "image", - "audio" + "image" ], "output": [ "text" @@ -156213,35 +155885,32 @@ }, "limit": { "context": 1048576, - "output": 131072 + "output": 8192 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true + "supported": false }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "none", - "low", - "medium", - "high", - "max" - ] - } + "search": { + "supported": false }, "attachment": true, - "open_weights": true, - "release_date": "2026-09-22", - "last_updated": "2026-09-22", "type": "chat" - }, + } + ] + }, + "synthetic": { + "id": "synthetic", + "name": "Synthetic", + "display_name": "Synthetic", + "api": "https://api.synthetic.new/openai/v1", + "doc": "https://synthetic.new/pricing", + "models": [ { - "id": "qwen3.8-flash", - "name": "Qwen3.8 Flash", - "display_name": "Qwen3.8 Flash", + "id": "hf:moonshotai/Kimi-K2.7-Code", + "name": "Kimi K2.7 Code", + "display_name": "Kimi K2.7 Code", "modalities": { "input": [ "text", @@ -156252,10 +155921,10 @@ ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 262144, + "output": 65536 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -156264,27 +155933,30 @@ "extra_capabilities": { "reasoning": { "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ - "none", - "minimal", "low", "medium", - "high", - "xhigh", - "max" + "high" ] } }, "attachment": true, - "open_weights": false, - "release_date": "2026-08-26", - "last_updated": "2026-08-26", + "open_weights": true, + "knowledge": "2025-01", + "release_date": "2026-06-12", + "last_updated": "2026-06-12", "type": "chat" }, { - "id": "qwen3.6", - "name": "Qwen3.6 35B-A3B", - "display_name": "Qwen3.6 35B-A3B", + "id": "hf:moonshotai/Kimi-K3", + "name": "Kimi K3", + "display_name": "Kimi K3", "modalities": { "input": [ "text", @@ -156295,7 +155967,7 @@ ] }, "limit": { - "context": 262144, + "context": 524288, "output": 65536 }, "temperature": true, @@ -156306,10 +155978,15 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ - "none", "low", - "medium", "high", "max" ] @@ -156317,25 +155994,26 @@ }, "attachment": true, "open_weights": true, - "release_date": "2026-04-17", - "last_updated": "2026-04-17", + "release_date": "2026-07-16", + "last_updated": "2026-07-16", "type": "chat" }, { - "id": "glm5.3", - "name": "GLM-5.3", - "display_name": "GLM-5.3", + "id": "hf:zai-org/GLM-5.3-Flash", + "name": "GLM-5.3-Flash", + "display_name": "GLM-5.3-Flash", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 524288, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -156345,38 +156023,41 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ - "minimal", "low", - "medium", "high", - "xhigh", "max" ] } }, - "attachment": false, + "attachment": true, "open_weights": true, - "release_date": "2026-08-14", - "last_updated": "2026-08-14", + "release_date": "2026-08-26", + "last_updated": "2026-08-26", "type": "chat" }, { - "id": "gemma4", - "name": "Gemma 4 26B A4B IT", - "display_name": "Gemma 4 26B A4B IT", + "id": "hf:zai-org/GLM-5.2", + "name": "GLM-5.2", + "display_name": "GLM-5.2", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 32768 + "context": 524288, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -156386,34 +156067,30 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ "none", - "low", - "medium", "high", - "max" + "xhigh" ] } }, - "attachment": true, + "attachment": false, "open_weights": true, - "release_date": "2026-04-02", - "last_updated": "2026-04-02", + "release_date": "2026-06-13", + "last_updated": "2026-06-13", "type": "chat" - } - ] - }, - "echo": { - "id": "echo", - "name": "Echo", - "display_name": "Echo", - "api": "https://echo.tracerml.ai/v1", - "doc": "https://echo.tracerml.ai/docs/api", - "models": [ + }, { - "id": "echo", - "name": "Echo", - "display_name": "Echo", + "id": "hf:zai-org/GLM-5.3", + "name": "GLM-5.3", + "display_name": "GLM-5.3", "modalities": { "input": [ "text" @@ -156423,7 +156100,7 @@ ] }, "limit": { - "context": 262144, + "context": 524288, "output": 65536 }, "temperature": true, @@ -156434,47 +156111,43 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ - "none", "low", - "medium", - "high" + "high", + "max" ] } }, "attachment": false, - "open_weights": false, - "release_date": "2026-07-19", - "last_updated": "2026-08-16", + "open_weights": true, + "release_date": "2026-08-14", + "last_updated": "2026-08-14", "type": "chat" - } - ] - }, - "model-oracle-ai": { - "id": "model-oracle-ai", - "name": "Model Oracle AI", - "display_name": "Model Oracle AI", - "api": "https://api.modeloracle.com/api/v1", - "doc": "https://modeloracle.com/setup/", - "models": [ + }, { - "id": "gpt-5", - "name": "GPT-5", - "display_name": "GPT-5", + "id": "hf:zai-org/GLM-4.7-Flash", + "name": "GLM-4.7-Flash", + "display_name": "GLM-4.7-Flash", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 196608, + "output": 65536 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -156483,50 +156156,45 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", - "effort_options": [ - "minimal", - "low", - "medium", - "high" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ], - "verbosity": "medium", - "verbosity_options": [ + "effort_options": [ + "none", "low", "medium", "high" - ], - "visibility": "hidden" + ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2024-09-30", - "release_date": "2025-08-07", - "last_updated": "2025-08-07", + "attachment": false, + "open_weights": true, + "knowledge": "2025-04", + "release_date": "2026-01-19", + "last_updated": "2026-01-19", "type": "chat" }, { - "id": "gpt-5.5", - "name": "GPT-5.5", - "display_name": "GPT-5.5", + "id": "hf:MiniMaxAI/MiniMax-M3", + "name": "MiniMax-M3", + "display_name": "MiniMax-M3", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 524288, + "output": 65536 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -156535,99 +156203,87 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", - "effort_options": [ - "low", - "medium", - "high", - "xhigh" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ], - "verbosity": "medium", - "verbosity_options": [ + "effort_options": [ "low", "medium", "high" - ], - "visibility": "hidden" + ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2025-12-01", - "release_date": "2026-04-23", - "last_updated": "2026-04-23", + "open_weights": true, + "release_date": "2026-06-01", + "last_updated": "2026-06-01", "type": "chat" }, { - "id": "gpt-5.4", - "name": "GPT-5.4", - "display_name": "GPT-5.4", + "id": "hf:Qwen/Qwen3.6-27B", + "name": "Qwen3.6 27B", + "display_name": "Qwen3.6 27B", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 262144, + "output": 65536 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "none", + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ "none", - "low", - "medium", - "high", - "xhigh" - ], - "verbosity": "medium", - "verbosity_options": [ "low", "medium", "high" - ], - "visibility": "hidden" + ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-03-05", - "last_updated": "2026-03-05", + "open_weights": true, + "release_date": "2026-04-22", + "last_updated": "2026-04-22", "type": "chat" }, { - "id": "deepseek-v4-pro", - "name": "DeepSeek V4 Pro", - "display_name": "DeepSeek V4 Pro", + "id": "hf:deepseek-ai/DeepSeek-V4.1-Flash-1017", + "name": "DeepSeek V4.1 Flash 1017", + "display_name": "DeepSeek V4.1 Flash 1017", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 384000 + "context": 524288, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -156638,108 +156294,83 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "high", + "interleaved": true, "effort_options": [ + "none", "low", "high", + "xhigh", "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." ] } }, - "attachment": false, + "attachment": true, "open_weights": true, "knowledge": "2025-05", - "release_date": "2026-04-24", - "last_updated": "2026-04-24", + "release_date": "2026-10-17", + "last_updated": "2026-10-17", "type": "chat" }, { - "id": "claude-opus-4.8", - "name": "Claude Opus 4.8", - "display_name": "Claude Opus 4.8", + "id": "hf:deepseek-ai/DeepSeek-V4.1-Flash", + "name": "DeepSeek V4.1 Flash", + "display_name": "DeepSeek V4.1 Flash", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 524288, + "output": 65536 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "high", + "interleaved": true, "effort_options": [ + "none", "low", - "medium", "high", "xhigh", "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "omitted", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Claude Opus 4.7 and newer Opus models require thinking.type = \"adaptive\" to enable thinking explicitly.", - "Manual budget_tokens requests return 400 on Claude Opus 4.7 and newer adaptive-only Opus models.", - "task_budget is separate from thinking control and should not be treated as a thinking budget." ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2026-01", - "release_date": "2026-05-28", - "last_updated": "2026-05-28", + "open_weights": true, + "knowledge": "2025-05", + "release_date": "2026-09-10", + "last_updated": "2026-09-10", "type": "chat" }, { - "id": "claude-sonnet-5", - "name": "Claude Sonnet 5", - "display_name": "Claude Sonnet 5", + "id": "hf:nvidia/NVIDIA-Nemotron-3-Super-120B-A12B-NVFP4", + "name": "Nemotron 3 Super 120B A12B", + "display_name": "Nemotron 3 Super 120B A12B", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 262144, + "output": 65536 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -156748,40 +156379,44 @@ "extra_capabilities": { "reasoning": { "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ + "none", "low", "medium", - "high", - "xhigh", - "max" + "high" ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-01-31", - "release_date": "2026-06-30", - "last_updated": "2026-06-30", + "attachment": false, + "open_weights": true, + "knowledge": "2025-06", + "release_date": "2026-03-11", + "last_updated": "2026-03-11", "type": "chat" }, { - "id": "o4-mini", - "name": "o4-mini", - "display_name": "o4-mini", + "id": "hf:openai/gpt-oss-120b", + "name": "GPT OSS 120B", + "display_name": "GPT OSS 120B", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 100000 + "context": 131072, + "output": 65536 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -156790,32 +156425,39 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", "effort_options": [ + "none", "low", "medium", "high" - ], - "visibility": "hidden" + ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2024-05", - "release_date": "2025-04-16", - "last_updated": "2025-04-16", + "attachment": false, + "open_weights": true, + "knowledge": "2024-06", + "release_date": "2025-08-05", + "last_updated": "2025-08-05", "type": "chat" - }, + } + ] + }, + "zai-coding-plan": { + "id": "zai-coding-plan", + "name": "Z.AI Coding Plan", + "display_name": "Z.AI Coding Plan", + "api": "https://api.z.ai/api/coding/paas/v4", + "doc": "https://docs.z.ai/devpack/overview", + "models": [ { - "id": "auto", - "name": "Auto", - "display_name": "Auto", + "id": "glm-5.3-flash", + "name": "GLM-5.3-Flash", + "display_name": "GLM-5.3-Flash", "modalities": { "input": [ "text", "image", + "video", "pdf" ], "output": [ @@ -156824,7 +156466,7 @@ }, "limit": { "context": 1000000, - "output": 128000 + "output": 131072 }, "temperature": true, "tool_call": true, @@ -156835,248 +156477,160 @@ "extra_capabilities": { "reasoning": { "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ - "none", - "minimal", "low", - "medium", "high", - "xhigh", "max" ] } }, "attachment": true, - "open_weights": false, - "release_date": "2026-06-29", - "last_updated": "2026-07-07", + "open_weights": true, + "release_date": "2026-08-26", + "last_updated": "2026-08-26", "type": "chat" }, { - "id": "gpt-5.4-mini", - "name": "GPT-5.4 mini", - "display_name": "GPT-5.4 mini", + "id": "glm-5.3-highspeed", + "name": "GLM-5.3 Highspeed", + "display_name": "GLM-5.3 Highspeed", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 1000000, + "output": 131072 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "none", + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ - "none", "low", - "medium", "high", - "xhigh" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + "max" + ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-03-17", - "last_updated": "2026-03-17", + "attachment": false, + "open_weights": true, + "release_date": "2026-08-14", + "last_updated": "2026-08-14", "type": "chat" }, { - "id": "claude-haiku-4.5", - "name": "Claude Haiku 4.5 (latest)", - "display_name": "Claude Haiku 4.5 (latest)", + "id": "glm-4.7", + "name": "GLM-4.7", + "display_name": "GLM-4.7", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 64000 + "context": 204800, + "output": 131072 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "budget", - "budget": { - "min": 1024, - "unit": "tokens" - }, "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ "thinking_blocks" - ], - "notes": [ - "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", - "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-02-28", - "release_date": "2025-10-15", - "last_updated": "2025-10-15", + "attachment": false, + "open_weights": true, + "knowledge": "2025-04", + "release_date": "2025-12-22", + "last_updated": "2025-12-22", "type": "chat" }, { - "id": "gpt-4.1-mini", - "name": "GPT-4.1 mini", - "display_name": "GPT-4.1 mini", + "id": "glm-5.3", + "name": "GLM-5.3", + "display_name": "GLM-5.3", "modalities": { "input": [ - "text", - "image", - "pdf" - ], - "output": [ "text" - ] - }, - "limit": { - "context": 1047576, - "output": 32768 - }, - "temperature": true, - "tool_call": true, - "reasoning": { - "supported": false - }, - "attachment": true, - "open_weights": false, - "knowledge": "2024-04", - "release_date": "2025-04-14", - "last_updated": "2025-04-14", - "type": "chat" - }, - { - "id": "gpt-5.4-nano", - "name": "GPT-5.4 nano", - "display_name": "GPT-5.4 nano", - "modalities": { - "input": [ - "text", - "image" ], "output": [ "text" ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 1000000, + "output": 131072 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "none", + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ - "none", "low", - "medium", "high", - "xhigh" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + "max" + ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-03-17", - "last_updated": "2026-03-17", + "attachment": false, + "open_weights": true, + "release_date": "2026-08-14", + "last_updated": "2026-08-14", "type": "chat" }, { - "id": "gpt-4.1", - "name": "GPT-4.1", - "display_name": "GPT-4.1", + "id": "glm-5.2-highspeed", + "name": "GLM-5.2 Highspeed", + "display_name": "GLM-5.2 Highspeed", "modalities": { "input": [ - "text", - "image", - "pdf" - ], - "output": [ "text" - ] - }, - "limit": { - "context": 1047576, - "output": 32768 - }, - "temperature": true, - "tool_call": true, - "reasoning": { - "supported": false - }, - "attachment": true, - "open_weights": false, - "knowledge": "2024-04", - "release_date": "2025-04-14", - "last_updated": "2025-04-14", - "type": "chat" - }, - { - "id": "claude-fable-5", - "name": "Claude Fable 5", - "display_name": "Claude Fable 5", - "modalities": { - "input": [ - "text", - "image", - "pdf" ], "output": [ "text" @@ -157084,9 +156638,9 @@ }, "limit": { "context": 1000000, - "output": 128000 + "output": 131072 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -157095,34 +156649,22 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "high", - "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" - ], "interleaved": true, "summaries": true, - "visibility": "omitted", + "visibility": "summary", "continuation": [ "thinking_blocks" ], - "notes": [ - "Adaptive thinking is always on for Claude Fable 5 and Claude Mythos 5; thinking.type = \"disabled\" is rejected.", - "Manual budget_tokens requests return 400 on Claude Fable 5 and Claude Mythos 5.", - "thinking.display defaults to omitted; set display to summarized to receive readable thinking summaries." + "effort_options": [ + "high", + "max" ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-01-31", - "release_date": "2026-06-09", - "last_updated": "2026-06-09", + "attachment": false, + "open_weights": true, + "release_date": "2026-06-13", + "last_updated": "2026-06-13", "type": "chat" }, { @@ -157157,9 +156699,8 @@ "thinking_blocks" ], "effort_options": [ - "none", "high", - "xhigh" + "max" ] } }, @@ -157168,32 +156709,22 @@ "release_date": "2026-06-13", "last_updated": "2026-06-13", "type": "chat" - } - ] - }, - "kosmik": { - "id": "kosmik", - "name": "Kosmik Compute", - "display_name": "Kosmik Compute", - "api": "https://api.koscompute.com/v1", - "doc": "https://api.koscompute.com/docs/", - "models": [ + }, { - "id": "qwen/qwen3.8-27b", - "name": "Qwen3.8 27B", - "display_name": "Qwen3.8 27B", + "id": "glm-5-turbo", + "name": "GLM-5-Turbo", + "display_name": "GLM-5-Turbo", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 32768 + "context": 200000, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -157209,75 +156740,41 @@ "visibility": "summary", "continuation": [ "thinking_blocks" - ], - "effort_options": [ - "none", - "low", - "medium", - "xhigh" ] } }, - "attachment": true, - "open_weights": true, - "release_date": "2026-08-14", - "last_updated": "2026-08-14", + "attachment": false, + "open_weights": false, + "release_date": "2026-03-16", + "last_updated": "2026-03-16", "type": "chat" } ] }, - "helicone": { - "id": "helicone", - "name": "Helicone", - "display_name": "Helicone", - "api": "https://ai-gateway.helicone.ai/v1", - "doc": "https://helicone.ai/models", + "standardcompute": { + "id": "standardcompute", + "name": "Standard Compute", + "display_name": "Standard Compute", + "api": "https://api.stdcmpt.com/v1", + "doc": "https://docs.standardcompute.com/models", "models": [ { - "id": "gpt-5-chat-latest", - "name": "OpenAI GPT-5 Chat Latest", - "display_name": "OpenAI GPT-5 Chat Latest", - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 16384 - }, - "temperature": false, - "tool_call": true, - "reasoning": { - "supported": false - }, - "attachment": true, - "open_weights": false, - "knowledge": "2024-09", - "release_date": "2025-08-07", - "last_updated": "2025-08-07", - "type": "chat" - }, - { - "id": "grok-4-1-fast-reasoning", - "name": "xAI Grok 4.1 Fast Reasoning", - "display_name": "xAI Grok 4.1 Fast Reasoning", + "id": "standardcompute", + "name": "Standard Compute", + "display_name": "Standard Compute", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 2000000, - "output": 2000000 + "context": 1000000, + "output": 50000 }, "temperature": true, "tool_call": true, @@ -157287,32 +156784,38 @@ }, "extra_capabilities": { "reasoning": { - "supported": true + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-11", - "release_date": "2025-11-19", - "last_updated": "2025-11-19", + "release_date": "2026-03-01", + "last_updated": "2026-10-31", "type": "chat" }, { - "id": "gpt-5", - "name": "OpenAI GPT-5", - "display_name": "OpenAI GPT-5", + "id": "openai/gpt-5.6-terra", + "name": "GPT-5.6 Terra", + "display_name": "GPT-5.6 Terra", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 1050000, + "output": 50000 }, "temperature": false, "tool_call": true, @@ -157327,10 +156830,12 @@ "mode": "effort", "effort": "medium", "effort_options": [ - "minimal", + "none", "low", "medium", - "high" + "high", + "xhigh", + "max" ], "verbosity": "medium", "verbosity_options": [ @@ -157338,63 +156843,89 @@ "medium", "high" ], - "visibility": "hidden" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-01", - "release_date": "2025-08-07", - "last_updated": "2025-08-07", + "knowledge": "2026-02-16", + "release_date": "2026-07-09", + "last_updated": "2026-07-09", "type": "chat" }, { - "id": "gpt-4o-mini", - "name": "OpenAI GPT-4o-mini", - "display_name": "OpenAI GPT-4o-mini", + "id": "openai/gpt-6-astra", + "name": "GPT-6 Astra", + "display_name": "GPT-6 Astra", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 16384 + "context": 1050000, + "output": 50000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" + ] + } }, "attachment": true, "open_weights": false, - "knowledge": "2024-07", - "release_date": "2024-07-18", - "last_updated": "2024-07-18", + "knowledge": "2026-04-30", + "release_date": "2026-09-04", + "last_updated": "2026-09-04", "type": "chat" }, { - "id": "gemini-2.5-pro", - "name": "Google Gemini 2.5 Pro", - "display_name": "Google Gemini 2.5 Pro", + "id": "openai/gpt-5.6-luna", + "name": "GPT-5.6 Luna", + "display_name": "GPT-5.6 Luna", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 1050000, + "output": 50000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -157404,75 +156935,116 @@ "reasoning": { "supported": true, "default_enabled": true, - "mode": "budget", - "budget": { - "default": -1, - "min": 128, - "max": 32768, - "auto": -1, - "unit": "tokens" - }, + "mode": "effort", + "effort": "medium", + "effort_options": [ + "none", + "low", + "medium", + "high", + "xhigh", + "max" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ - "thought_signatures" + "thinking_blocks" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-06", - "release_date": "2025-06-17", - "last_updated": "2025-06-17", + "knowledge": "2026-02-16", + "release_date": "2026-07-09", + "last_updated": "2026-07-09", "type": "chat" }, { - "id": "gpt-4.1-nano", - "name": "OpenAI GPT-4.1 Nano", - "display_name": "OpenAI GPT-4.1 Nano", + "id": "openai/gpt-5.6-sol", + "name": "GPT-5.6 Sol", + "display_name": "GPT-5.6 Sol", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1047576, - "output": 32768 + "context": 1050000, + "output": 50000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", + "effort_options": [ + "none", + "low", + "medium", + "high", + "xhigh", + "max" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } }, "attachment": true, "open_weights": false, - "knowledge": "2025-04", - "release_date": "2025-04-14", - "last_updated": "2025-04-14", + "knowledge": "2026-02-16", + "release_date": "2026-07-09", + "last_updated": "2026-07-09", "type": "chat" }, { - "id": "o1", - "name": "OpenAI: o1", - "display_name": "OpenAI: o1", + "id": "openai/gpt-6-luna", + "name": "GPT-6 Luna", + "display_name": "GPT-6 Luna", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 100000 + "context": 1050000, + "output": 50000 }, "temperature": false, - "tool_call": false, + "tool_call": true, "reasoning": { "supported": true, "default": true @@ -157480,85 +157052,80 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ + "none", "low", "medium", - "high" - ], - "visibility": "hidden" + "high", + "xhigh", + "max" + ] } }, - "attachment": false, - "open_weights": false, - "knowledge": "2025-01", - "release_date": "2024-12-05", - "last_updated": "2024-12-05", - "type": "chat" - }, - { - "id": "sonar-pro", - "name": "Perplexity Sonar Pro", - "display_name": "Perplexity Sonar Pro", - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 200000, - "output": 4096 - }, - "temperature": true, - "tool_call": false, - "reasoning": { - "supported": false - }, - "attachment": false, + "attachment": true, "open_weights": false, - "knowledge": "2025-01", - "release_date": "2024-01-01", - "last_updated": "2025-09-01", + "knowledge": "2026-05-18", + "release_date": "2026-09-22", + "last_updated": "2026-09-22", "type": "chat" }, { - "id": "gpt-5.1-chat-latest", - "name": "OpenAI GPT-5.1 Chat", - "display_name": "OpenAI GPT-5.1 Chat", + "id": "z-ai/glm-5.3-flash", + "name": "GLM-5.3-Flash", + "display_name": "GLM-5.3-Flash", "modalities": { "input": [ "text", - "image" + "image", + "video", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 16384 + "context": 1000000, + "output": 50000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "effort_options": [ + "low", + "high", + "max" + ] + } }, "attachment": true, - "open_weights": false, - "knowledge": "2025-01", - "release_date": "2025-11-13", - "last_updated": "2025-11-13", + "open_weights": true, + "release_date": "2026-08-26", + "last_updated": "2026-08-26", "type": "chat" }, { - "id": "sonar-deep-research", - "name": "Perplexity Sonar Deep Research", - "display_name": "Perplexity Sonar Deep Research", + "id": "z-ai/glm-5.3", + "name": "GLM-5.3", + "display_name": "GLM-5.3", "modalities": { "input": [ "text" @@ -157568,36 +157135,41 @@ ] }, "limit": { - "context": 127000, - "output": 4096 + "context": 1000000, + "output": 50000 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { "supported": true, "default": true }, "extra_capabilities": { "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ - "minimal", "low", - "medium", - "high" + "high", + "max" ] } }, "attachment": false, - "open_weights": false, - "knowledge": "2025-01", - "release_date": "2025-02-01", - "last_updated": "2025-09-01", + "open_weights": true, + "release_date": "2026-08-14", + "last_updated": "2026-08-14", "type": "chat" }, { - "id": "qwen3-coder-30b-a3b-instruct", - "name": "Qwen3 Coder 30B A3B Instruct", - "display_name": "Qwen3 Coder 30B A3B Instruct", + "id": "z-ai/glm-5.2", + "name": "GLM-5.2", + "display_name": "GLM-5.2", "modalities": { "input": [ "text" @@ -157607,39 +157179,57 @@ ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 1000000, + "output": 50000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "effort_options": [ + "none", + "high", + "xhigh" + ] + } }, "attachment": false, "open_weights": true, - "knowledge": "2025-07", - "release_date": "2025-07-31", - "last_updated": "2025-07-31", + "release_date": "2026-06-13", + "last_updated": "2026-06-13", "type": "chat" }, { - "id": "deepseek-reasoner", - "name": "DeepSeek Reasoner", - "display_name": "DeepSeek Reasoner", + "id": "minimax/minimax-m3", + "name": "MiniMax-M3", + "display_name": "MiniMax-M3", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 64000 + "context": 1048576, + "output": 50000 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { "supported": true, "default": true @@ -157655,30 +157245,31 @@ ] } }, - "attachment": false, + "attachment": true, "open_weights": true, - "knowledge": "2025-01", - "release_date": "2025-12-01", - "last_updated": "2026-02-28", + "release_date": "2026-06-01", + "last_updated": "2026-06-01", "type": "chat" }, { - "id": "gpt-oss-20b", - "name": "OpenAI GPT-OSS 20b", - "display_name": "OpenAI GPT-OSS 20b", + "id": "moonshotai/kimi-k3", + "name": "Kimi K3", + "display_name": "Kimi K3", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 131072 + "context": 1048576, + "output": 50000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -157687,376 +157278,612 @@ "extra_capabilities": { "reasoning": { "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ + "none", "low", - "medium", - "high" + "high", + "max" ] } }, - "attachment": false, + "attachment": true, "open_weights": true, - "knowledge": "2024-06", - "release_date": "2025-08-05", - "last_updated": "2025-08-05", + "release_date": "2026-07-16", + "last_updated": "2026-07-16", "type": "chat" }, { - "id": "claude-opus-4", - "name": "Anthropic: Claude Opus 4", - "display_name": "Anthropic: Claude Opus 4", + "id": "google/gemini-3.8-flash", + "name": "Gemini 3.8 Flash", + "display_name": "Gemini 3.8 Flash", "modalities": { "input": [ "text", - "image" + "image", + "video", + "audio", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 32000 + "context": 1048576, + "output": 50000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "budget", - "budget": { - "min": 1024, - "unit": "tokens" - }, + "default_enabled": true, + "mode": "level", + "level": "high", + "level_options": [ + "minimal", + "low", + "medium", + "high" + ], "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ "thinking_blocks" - ], - "notes": [ - "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", - "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-05", - "release_date": "2025-05-22", - "last_updated": "2025-05-22", + "release_date": "2026-09-02", + "last_updated": "2026-09-02", "type": "chat" }, { - "id": "deepseek-tng-r1t2-chimera", - "name": "DeepSeek TNG R1T2 Chimera", - "display_name": "DeepSeek TNG R1T2 Chimera", + "id": "google/gemini-2.5-pro", + "name": "Gemini 2.5 Pro", + "display_name": "Gemini 2.5 Pro", "modalities": { "input": [ - "text" + "text", + "image", + "audio", + "video", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 130000, - "output": 163840 + "context": 1048576, + "output": 50000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "budget", + "budget": { + "default": -1, + "min": 128, + "max": 32768, + "auto": -1, + "unit": "tokens" + }, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } + }, + "attachment": true, "open_weights": false, - "knowledge": "2025-07", - "release_date": "2025-07-02", - "last_updated": "2025-07-02", + "knowledge": "2025-01", + "release_date": "2025-06-17", + "last_updated": "2025-06-17", "type": "chat" }, { - "id": "llama-3.1-8b-instant", - "name": "Meta Llama 3.1 8B Instant", - "display_name": "Meta Llama 3.1 8B Instant", + "id": "google/gemini-2.5-flash", + "name": "Gemini 2.5 Flash", + "display_name": "Gemini 2.5 Flash", "modalities": { "input": [ - "text" + "text", + "image", + "audio", + "video", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 32678 + "context": 1048576, + "output": 50000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, - "open_weights": true, - "knowledge": "2024-07", - "release_date": "2024-07-23", - "last_updated": "2024-07-23", + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "budget", + "budget": { + "default": -1, + "min": 0, + "max": 24576, + "auto": -1, + "off": 0, + "unit": "tokens" + }, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } + }, + "attachment": true, + "open_weights": false, + "knowledge": "2025-01", + "release_date": "2025-06-17", + "last_updated": "2025-06-17", "type": "chat" }, { - "id": "sonar-reasoning", - "name": "Perplexity Sonar Reasoning", - "display_name": "Perplexity Sonar Reasoning", + "id": "google/gemini-3.1-pro-preview", + "name": "Gemini 3.1 Pro Preview", + "display_name": "Gemini 3.1 Pro Preview", "modalities": { "input": [ - "text" + "text", + "image", + "video", + "audio", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 127000, - "output": 4096 + "context": 1048576, + "output": 50000 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { "supported": true, "default": true }, - "attachment": false, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "level", + "level": "high", + "level_options": [ + "low", + "high" + ], + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } + }, + "attachment": true, "open_weights": false, "knowledge": "2025-01", - "release_date": "2025-01-27", - "last_updated": "2025-01-27", + "release_date": "2026-02-19", + "last_updated": "2026-02-19", "type": "chat" }, { - "id": "chatgpt-4o-latest", - "name": "OpenAI ChatGPT-4o", - "display_name": "OpenAI ChatGPT-4o", + "id": "google/gemini-3.5-flash-lite", + "name": "Gemini 3.5 Flash Lite", + "display_name": "Gemini 3.5 Flash Lite", "modalities": { "input": [ "text", - "image" + "image", + "video", + "audio", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 16384 + "context": 1048576, + "output": 50000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "effort_options": [ + "minimal", + "low", + "medium", + "high" + ] + } }, "attachment": true, "open_weights": false, - "knowledge": "2024-08", - "release_date": "2024-08-14", - "last_updated": "2024-08-14", + "knowledge": "2026-03", + "release_date": "2026-07-21", + "last_updated": "2026-07-21", "type": "chat" }, { - "id": "ernie-4.5-21b-a3b-thinking", - "name": "Baidu Ernie 4.5 21B A3B Thinking", - "display_name": "Baidu Ernie 4.5 21B A3B Thinking", + "id": "mistralai/mistral-large-4-0", + "name": "Mistral Large 4", + "display_name": "Mistral Large 4", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 8000 + "context": 1048576, + "output": 50000 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { "supported": true, "default": true }, - "attachment": false, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "effort_options": [ + "none", + "high" + ] + } + }, + "attachment": true, "open_weights": false, - "knowledge": "2025-03", - "release_date": "2025-03-16", - "last_updated": "2025-03-16", + "release_date": "2026-10-06", + "last_updated": "2026-10-06", "type": "chat" }, { - "id": "grok-3", - "name": "xAI Grok 3", - "display_name": "xAI Grok 3", + "id": "anthropic/claude-fable-5.1", + "name": "Claude Fable 5.1", + "display_name": "Claude Fable 5.1", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 131072 + "context": 1000000, + "output": 50000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "high", + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Adaptive thinking is always on for Claude Fable 5 and Claude Mythos 5; thinking.type = \"disabled\" is rejected.", + "Manual budget_tokens requests return 400 on Claude Fable 5 and Claude Mythos 5.", + "thinking.display defaults to omitted; set display to summarized to receive readable thinking summaries." + ] + } + }, + "attachment": true, "open_weights": false, - "knowledge": "2024-06", - "release_date": "2024-06-01", - "last_updated": "2024-06-01", + "knowledge": "2026-06", + "release_date": "2026-09-01", + "last_updated": "2026-09-01", "type": "chat" }, { - "id": "qwen3-vl-235b-a22b-instruct", - "name": "Qwen3 VL 235B A22B Instruct", - "display_name": "Qwen3 VL 235B A22B Instruct", + "id": "anthropic/claude-sonnet-5", + "name": "Claude Sonnet 5", + "display_name": "Claude Sonnet 5", "modalities": { "input": [ "text", "image", - "video" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 256000, - "output": 16384 + "context": 1000000, + "output": 50000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "effort_options": [ + "none", + "low", + "medium", + "high", + "xhigh", + "max" + ] + } }, "attachment": true, - "open_weights": true, - "knowledge": "2025-09", - "release_date": "2025-09-23", - "last_updated": "2025-09-23", + "open_weights": false, + "knowledge": "2026-01-31", + "release_date": "2026-06-30", + "last_updated": "2026-06-30", "type": "chat" }, { - "id": "claude-4.5-sonnet", - "name": "Anthropic: Claude Sonnet 4.5", - "display_name": "Anthropic: Claude Sonnet 4.5", + "id": "anthropic/claude-fable-5", + "name": "Claude Fable 5", + "display_name": "Claude Fable 5", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 64000 + "context": 1000000, + "output": 50000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, "default": true }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "high", + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Adaptive thinking is always on for Claude Fable 5 and Claude Mythos 5; thinking.type = \"disabled\" is rejected.", + "Manual budget_tokens requests return 400 on Claude Fable 5 and Claude Mythos 5.", + "thinking.display defaults to omitted; set display to summarized to receive readable thinking summaries." + ] + } + }, "attachment": true, "open_weights": false, - "knowledge": "2025-09", - "release_date": "2025-09-29", - "last_updated": "2025-09-29", + "knowledge": "2026-01-31", + "release_date": "2026-06-09", + "last_updated": "2026-06-09", "type": "chat" }, { - "id": "gpt-4.1-mini-2025-04-14", - "name": "OpenAI GPT-4.1 Mini", - "display_name": "OpenAI GPT-4.1 Mini", + "id": "anthropic/claude-haiku-5.5", + "name": "Claude Haiku 5.5", + "display_name": "Claude Haiku 5.5", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1047576, - "output": 32768 + "context": 1000000, + "output": 50000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "effort_options": [ + "none", + "low", + "medium", + "high", + "xhigh", + "max" + ] + } }, "attachment": true, "open_weights": false, - "knowledge": "2025-04", - "release_date": "2025-04-14", - "last_updated": "2025-04-14", + "knowledge": "2026-06", + "release_date": "2026-10-07", + "last_updated": "2026-10-07", "type": "chat" }, { - "id": "llama-prompt-guard-2-22m", - "name": "Meta Llama Prompt Guard 2 22M", - "display_name": "Meta Llama Prompt Guard 2 22M", + "id": "anthropic/claude-sonnet-5.5", + "name": "Claude Sonnet 5.5", + "display_name": "Claude Sonnet 5.5", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 512, - "output": 2 + "context": 1000000, + "output": 50000 }, - "temperature": true, - "tool_call": false, + "temperature": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, - "open_weights": true, - "knowledge": "2024-10", - "release_date": "2025-04-29", - "last_updated": "2025-04-29", + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" + ] + } + }, + "attachment": true, + "open_weights": false, + "knowledge": "2026-06", + "release_date": "2026-09-28", + "last_updated": "2026-09-28", "type": "chat" }, { - "id": "deepseek-r1-distill-llama-70b", - "name": "DeepSeek R1 Distill Llama 70B", - "display_name": "DeepSeek R1 Distill Llama 70B", + "id": "anthropic/claude-opus-5.5", + "name": "Claude Opus 5.5", + "display_name": "Claude Opus 5.5", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 4096 + "context": 1000000, + "output": 50000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -158064,49 +157891,83 @@ }, "extra_capabilities": { "reasoning": { - "supported": true + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" + ] } }, - "attachment": false, + "attachment": true, "open_weights": false, - "knowledge": "2025-01", - "release_date": "2025-01-20", - "last_updated": "2025-01-20", + "knowledge": "2026-06", + "release_date": "2026-09-22", + "last_updated": "2026-09-22", "type": "chat" }, { - "id": "gpt-4o", - "name": "OpenAI GPT-4o", - "display_name": "OpenAI GPT-4o", + "id": "anthropic/claude-opus-5", + "name": "Claude Opus 5", + "display_name": "Claude Opus 5", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 16384 + "context": 1000000, + "output": 50000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "effort_options": [ + "none", + "low", + "medium", + "high", + "xhigh", + "max" + ] + } }, "attachment": true, "open_weights": false, - "knowledge": "2024-05", - "release_date": "2024-05-13", - "last_updated": "2024-08-06", + "knowledge": "2026-05", + "release_date": "2026-07-24", + "last_updated": "2026-07-24", "type": "chat" }, { - "id": "gpt-5-pro", - "name": "OpenAI: GPT-5 Pro", - "display_name": "OpenAI: GPT-5 Pro", + "id": "qwen/qwen3.7-max", + "name": "Qwen3.7 Max", + "display_name": "Qwen3.7 Max", "modalities": { "input": [ "text" @@ -158116,10 +157977,10 @@ ] }, "limit": { - "context": 128000, - "output": 32768 + "context": 1000000, + "output": 50000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -158128,72 +157989,85 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "fixed", - "effort": "high", - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] } }, "attachment": false, "open_weights": false, - "knowledge": "2025-01", - "release_date": "2025-10-06", - "last_updated": "2025-10-06", + "release_date": "2026-05-21", + "last_updated": "2026-05-21", "type": "chat" }, { - "id": "grok-4-1-fast-non-reasoning", - "name": "xAI Grok 4.1 Fast Non-Reasoning", - "display_name": "xAI Grok 4.1 Fast Non-Reasoning", + "id": "qwen/qwen3.8-flash", + "name": "Qwen3.8 Flash", + "display_name": "Qwen3.8 Flash", "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 2000000, - "output": 30000 + "context": 1000000, + "output": 50000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "effort_options": [ + "low", + "medium", + "xhigh" + ] + } }, "attachment": true, "open_weights": false, - "knowledge": "2025-11", - "release_date": "2025-11-19", - "last_updated": "2025-11-19", + "release_date": "2026-08-26", + "last_updated": "2026-08-26", "type": "chat" }, { - "id": "o3", - "name": "OpenAI o3", - "display_name": "OpenAI o3", + "id": "qwen/qwen3.7-flash", + "name": "Qwen3.7 Flash", + "display_name": "Qwen3.7 Flash", "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 100000 + "context": 1000000, + "output": 50000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -158202,40 +158076,38 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", - "effort_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] } }, "attachment": true, "open_weights": false, - "knowledge": "2024-06", - "release_date": "2025-04-16", - "last_updated": "2025-04-16", + "release_date": "2026-07-15", + "last_updated": "2026-07-15", "type": "chat" }, { - "id": "gemini-2.5-flash", - "name": "Google Gemini 2.5 Flash", - "display_name": "Google Gemini 2.5 Flash", + "id": "qwen/qwen3.8-max-0902", + "name": "Qwen3.8 Max 0902", + "display_name": "Qwen3.8 Max 0902", "modalities": { "input": [ "text", - "image" + "image", + "video", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 65535 + "context": 1000000, + "output": 50000 }, "temperature": true, "tool_call": true, @@ -158246,119 +158118,167 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "budget", - "budget": { - "default": -1, - "min": 0, - "max": 24576, - "auto": -1, - "off": 0, - "unit": "tokens" - }, + "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ - "thought_signatures" + "thinking_blocks" + ], + "effort_options": [ + "minimal", + "low", + "medium", + "high", + "xhigh" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-06", - "release_date": "2025-06-17", - "last_updated": "2025-06-17", + "release_date": "2026-09-02", + "last_updated": "2026-09-02", "type": "chat" }, { - "id": "llama-guard-4", - "name": "Meta Llama Guard 4 12B", - "display_name": "Meta Llama Guard 4 12B", + "id": "qwen/qwen3.8-27b", + "name": "Qwen3.8 27B", + "display_name": "Qwen3.8 27B", "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 1024 + "context": 262144, + "output": 32768 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "effort_options": [ + "none", + "low", + "medium", + "xhigh" + ] + } }, "attachment": true, - "open_weights": false, - "knowledge": "2025-01", - "release_date": "2025-01-01", - "last_updated": "2025-01-01", + "open_weights": true, + "release_date": "2026-08-14", + "last_updated": "2026-08-14", "type": "chat" }, { - "id": "grok-3-mini", - "name": "xAI Grok 3 Mini", - "display_name": "xAI Grok 3 Mini", + "id": "qwen/qwen3.7-plus", + "name": "Qwen3.7 Plus", + "display_name": "Qwen3.7 Plus", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 131072 + "context": 1000000, + "output": 50000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } }, - "attachment": false, + "attachment": true, "open_weights": false, - "knowledge": "2024-06", - "release_date": "2024-06-01", - "last_updated": "2024-06-01", + "knowledge": "2025-04", + "release_date": "2026-06-02", + "last_updated": "2026-06-02", "type": "chat" }, { - "id": "kimi-k2-0905", - "name": "Kimi K2 (09/05)", - "display_name": "Kimi K2 (09/05)", + "id": "deepseek/deepseek-v4.1-flash", + "name": "DeepSeek V4.1 Flash", + "display_name": "DeepSeek V4.1 Flash", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 16384 + "context": 1000000, + "output": 50000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "effort_options": [ + "none", + "low", + "high", + "max" + ] + } + }, + "attachment": true, "open_weights": true, - "knowledge": "2025-09", - "release_date": "2025-09-05", - "last_updated": "2025-09-05", + "knowledge": "2025-05", + "release_date": "2026-09-10", + "last_updated": "2026-09-10", "type": "chat" }, { - "id": "qwen3-next-80b-a3b-instruct", - "name": "Qwen3 Next 80B A3B Instruct", - "display_name": "Qwen3 Next 80B A3B Instruct", + "id": "deepseek/deepseek-v4-pro", + "name": "DeepSeek V4 Pro", + "display_name": "DeepSeek V4 Pro", "modalities": { "input": [ "text" @@ -158368,39 +158288,59 @@ ] }, "limit": { - "context": 262000, - "output": 16384 + "context": 1000000, + "output": 50000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "high", + "effort_options": [ + "low", + "high", + "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." + ] + } }, "attachment": false, "open_weights": true, - "knowledge": "2025-01", - "release_date": "2025-09", - "last_updated": "2025-09", + "knowledge": "2025-05", + "release_date": "2026-04-24", + "last_updated": "2026-04-24", "type": "chat" }, { - "id": "gemini-3-pro-preview", - "name": "Google Gemini 3 Pro Preview", - "display_name": "Google Gemini 3 Pro Preview", + "id": "deepseek/deepseek-v4-flash", + "name": "DeepSeek V4 Flash", + "display_name": "DeepSeek V4 Flash", "modalities": { "input": [ - "text", - "image", - "audio", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 1000000, + "output": 50000 }, "temperature": true, "tool_call": true, @@ -158412,59 +158352,80 @@ "reasoning": { "supported": true, "default_enabled": true, - "mode": "level", - "level": "high", - "level_options": [ + "mode": "effort", + "effort": "high", + "effort_options": [ "low", - "high" + "high", + "max" ], + "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ - "thought_signatures" + "thinking_blocks" + ], + "notes": [ + "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-11", - "release_date": "2025-11-18", - "last_updated": "2025-11-18", + "attachment": false, + "open_weights": true, + "knowledge": "2025-05", + "release_date": "2026-04-24", + "last_updated": "2026-04-24", "type": "chat" }, { - "id": "mistral-small", - "name": "Mistral Small 3.2", - "display_name": "Mistral Small 3.2", + "id": "deepseek/deepseek-v4-pro-0813", + "name": "DeepSeek V4 Pro 0813", + "display_name": "DeepSeek V4 Pro 0813", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 16384 + "context": 1000000, + "output": 50000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": true, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "effort_options": [ + "none", + "low", + "high", + "max" + ] + } + }, + "attachment": false, "open_weights": true, - "knowledge": "2025-03", - "release_date": "2025-06-20", - "last_updated": "2025-06-20", + "release_date": "2026-08-13", + "last_updated": "2026-08-22", "type": "chat" }, { - "id": "grok-code-fast-1", - "name": "xAI Grok Code Fast 1", - "display_name": "xAI Grok Code Fast 1", + "id": "deepseek/deepseek-v4-flash-0731", + "name": "DeepSeek V4 Flash 0731", + "display_name": "DeepSeek V4 Flash 0731", "modalities": { "input": [ "text" @@ -158474,8 +158435,8 @@ ] }, "limit": { - "context": 256000, - "output": 10000 + "context": 1000000, + "output": 50000 }, "temperature": true, "tool_call": true, @@ -158485,34 +158446,47 @@ }, "extra_capabilities": { "reasoning": { - "supported": true + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "effort_options": [ + "none", + "low", + "high", + "max" + ] } }, "attachment": false, - "open_weights": false, - "knowledge": "2024-08", - "release_date": "2025-09-02", - "last_updated": "2025-09-02", + "open_weights": true, + "knowledge": "2025-05", + "release_date": "2026-07-31", + "last_updated": "2026-07-31", "type": "chat" }, { - "id": "o4-mini", - "name": "OpenAI o4 Mini", - "display_name": "OpenAI o4 Mini", + "id": "x-ai/grok-4.3", + "name": "Grok 4.3", + "display_name": "Grok 4.3", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 100000 + "context": 1000000, + "output": 30000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -158521,57 +158495,72 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ + "none", "low", "medium", "high" - ], - "visibility": "hidden" + ] } }, "attachment": true, "open_weights": false, - "knowledge": "2024-06", - "release_date": "2025-04-16", - "last_updated": "2025-04-16", + "release_date": "2026-04-17", + "last_updated": "2026-04-17", "type": "chat" }, { - "id": "gemma-3-12b-it", - "name": "Google Gemma 3 12B", - "display_name": "Google Gemma 3 12B", + "id": "xiaomi/mimo-v2.6-pro", + "name": "MiMo-V2.6-Pro", + "display_name": "MiMo-V2.6-Pro", "modalities": { "input": [ "text", - "image" + "image", + "audio", + "video" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 8192 + "context": 1048576, + "output": 50000 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } }, "attachment": true, "open_weights": true, - "knowledge": "2024-12", - "release_date": "2025-03-12", - "last_updated": "2025-03-12", + "release_date": "2026-09-22", + "last_updated": "2026-09-22", "type": "chat" }, { - "id": "gpt-oss-120b", - "name": "OpenAI GPT-OSS 120b", - "display_name": "OpenAI GPT-OSS 120b", + "id": "xiaomi/mimo-v2.5-pro", + "name": "MiMo-V2.5-Pro", + "display_name": "MiMo-V2.5-Pro", "modalities": { "input": [ "text" @@ -158581,8 +158570,8 @@ ] }, "limit": { - "context": 131072, - "output": 131072 + "context": 1048576, + "output": 50000 }, "temperature": true, "tool_call": true, @@ -158593,35 +158582,39 @@ "extra_capabilities": { "reasoning": { "supported": true, - "effort_options": [ - "low", - "medium", - "high" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, "attachment": false, "open_weights": true, - "knowledge": "2024-06", - "release_date": "2025-08-05", - "last_updated": "2025-08-05", + "knowledge": "2024-12", + "release_date": "2026-04-22", + "last_updated": "2026-04-22", "type": "chat" }, { - "id": "glm-4.6", - "name": "Zai GLM-4.6", - "display_name": "Zai GLM-4.6", + "id": "xiaomi/mimo-v2.6-flash", + "name": "MiMo-V2.6-Flash", + "display_name": "MiMo-V2.6-Flash", "modalities": { "input": [ - "text" + "text", + "image", + "audio", + "video" ], "output": [ "text" ] }, "limit": { - "context": 204800, - "output": 131072 + "context": 1048576, + "output": 50000 }, "temperature": true, "tool_call": true, @@ -158631,85 +158624,118 @@ }, "extra_capabilities": { "reasoning": { - "supported": true + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] } }, - "attachment": false, + "attachment": true, "open_weights": true, - "knowledge": "2024-07", - "release_date": "2025-09-30", - "last_updated": "2025-09-30", + "release_date": "2026-09-22", + "last_updated": "2026-09-22", "type": "chat" }, { - "id": "gpt-5.1-codex", - "name": "OpenAI: GPT-5.1 Codex", - "display_name": "OpenAI: GPT-5.1 Codex", + "id": "xiaomi/mimo-v2.5", + "name": "MiMo-V2.5", + "display_name": "MiMo-V2.5", "modalities": { "input": [ "text", - "image" + "image", + "audio", + "video" ], "output": [ "text" ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 1048576, + "output": 50000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "none", - "effort_options": [ - "none", - "low", - "medium", - "high" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2025-01", - "release_date": "2025-11-13", - "last_updated": "2025-11-13", + "open_weights": true, + "knowledge": "2024-12", + "release_date": "2026-04-22", + "last_updated": "2026-04-22", + "type": "chat" + } + ] + }, + "pendra": { + "id": "pendra", + "name": "Pendra", + "display_name": "Pendra", + "api": "https://api.pendra.ai/api/v1", + "doc": "https://pendra.ai/docs/integrations/opencode", + "models": [ + { + "id": "qwen3-coder:30b", + "name": "Qwen3-Coder 30B-A3B Instruct", + "display_name": "Qwen3-Coder 30B-A3B Instruct", + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 262144, + "output": 65536 + }, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": false + }, + "attachment": false, + "open_weights": true, + "knowledge": "2025-04", + "release_date": "2025-07-31", + "last_updated": "2025-07-31", "type": "chat" }, { - "id": "gpt-5-mini", - "name": "OpenAI GPT-5 Mini", - "display_name": "OpenAI GPT-5 Mini", + "id": "deepseek-v4-flash", + "name": "DeepSeek V4 Flash", + "display_name": "DeepSeek V4 Flash", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 1000000, + "output": 384000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -158720,33 +158746,34 @@ "supported": true, "default_enabled": true, "mode": "effort", - "effort": "medium", + "effort": "high", "effort_options": [ - "minimal", "low", - "medium", - "high" + "high", + "max" ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ], - "visibility": "hidden" + "notes": [ + "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." + ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-01", - "release_date": "2025-08-07", - "last_updated": "2025-08-07", + "attachment": false, + "open_weights": true, + "knowledge": "2025-05", + "release_date": "2026-04-24", + "last_updated": "2026-04-24", "type": "chat" }, { - "id": "gpt-5-codex", - "name": "OpenAI: GPT-5 Codex", - "display_name": "OpenAI: GPT-5 Codex", + "id": "gpt-oss:120b", + "name": "GPT OSS 120B", + "display_name": "GPT OSS 120B", "modalities": { "input": [ "text" @@ -158756,10 +158783,10 @@ ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 131072, + "output": 32768 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -158767,36 +158794,24 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", "effort_options": [ - "minimal", - "low", - "medium", - "high" - ], - "verbosity": "medium", - "verbosity_options": [ "low", "medium", "high" - ], - "visibility": "hidden" + ] } }, "attachment": false, - "open_weights": false, - "knowledge": "2025-01", - "release_date": "2025-09-15", - "last_updated": "2025-09-15", + "open_weights": true, + "knowledge": "2024-06", + "release_date": "2025-08-05", + "last_updated": "2025-08-05", "type": "chat" }, { - "id": "deepseek-v3.2", - "name": "DeepSeek V3.2", - "display_name": "DeepSeek V3.2", + "id": "glm-4.7-flash", + "name": "GLM-4.7-Flash", + "display_name": "GLM-4.7-Flash", "modalities": { "input": [ "text" @@ -158806,13 +158821,14 @@ ] }, "limit": { - "context": 163840, - "output": 65536 + "context": 200000, + "output": 131072 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true + "supported": true, + "default": true }, "extra_capabilities": { "reasoning": { @@ -158821,55 +158837,55 @@ }, "attachment": false, "open_weights": true, - "knowledge": "2025-09", - "release_date": "2025-12-01", - "last_updated": "2025-12-01", + "knowledge": "2025-04", + "release_date": "2026-01-19", + "last_updated": "2026-01-19", "type": "chat" }, { - "id": "claude-3.5-haiku", - "name": "Anthropic: Claude 3.5 Haiku", - "display_name": "Anthropic: Claude 3.5 Haiku", + "id": "llama3.3:70b", + "name": "Llama-3.3-70B-Instruct", + "display_name": "Llama-3.3-70B-Instruct", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 8192 + "context": 128000, + "output": 4096 }, "temperature": true, "tool_call": true, "reasoning": { "supported": false }, - "attachment": true, - "open_weights": false, - "knowledge": "2024-10", - "release_date": "2024-10-22", - "last_updated": "2024-10-22", + "attachment": false, + "open_weights": true, + "knowledge": "2023-12", + "release_date": "2024-12-06", + "last_updated": "2024-12-06", "type": "chat" }, { - "id": "kimi-k2-thinking", - "name": "Kimi K2 Thinking", - "display_name": "Kimi K2 Thinking", + "id": "qwen3.6:27b", + "name": "Qwen3.6 27B", + "display_name": "Qwen3.6 27B", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 256000, - "output": 262144 + "context": 262144, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -158877,28 +158893,24 @@ "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } - }, - "attachment": false, + "attachment": true, "open_weights": true, - "knowledge": "2025-11", - "release_date": "2025-11-06", - "last_updated": "2025-11-06", + "release_date": "2026-04-22", + "last_updated": "2026-04-22", "type": "chat" - }, + } + ] + }, + "mistral": { + "id": "mistral", + "name": "Mistral", + "display_name": "Mistral", + "doc": "https://docs.mistral.ai/getting-started/models/", + "models": [ { - "id": "gemma2-9b-it", - "name": "Google Gemma 2", - "display_name": "Google Gemma 2", + "id": "devstral-medium-latest", + "name": "Devstral 2 (latest)", + "display_name": "Devstral 2 (latest)", "modalities": { "input": [ "text" @@ -158908,104 +158920,84 @@ ] }, "limit": { - "context": 8192, - "output": 8192 + "context": 262144, + "output": 262144 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { "supported": false }, "attachment": false, - "open_weights": false, - "knowledge": "2024-06", - "release_date": "2024-06-25", - "last_updated": "2024-06-25", + "open_weights": true, + "knowledge": "2025-12", + "release_date": "2025-12-02", + "last_updated": "2025-12-02", "type": "chat" }, { - "id": "gpt-5.1-codex-mini", - "name": "OpenAI: GPT-5.1 Codex Mini", - "display_name": "OpenAI: GPT-5.1 Codex Mini", + "id": "codestral-latest", + "name": "Codestral (latest)", + "display_name": "Codestral (latest)", "modalities": { "input": [ "text", - "image" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 256000, + "output": 4096 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": false - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "none", - "effort_options": [ - "none", - "low", - "medium", - "high" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" - } + "supported": false }, "attachment": true, - "open_weights": false, - "knowledge": "2025-01", - "release_date": "2025-11-13", - "last_updated": "2025-11-13", + "open_weights": true, + "knowledge": "2024-10", + "release_date": "2024-05-29", + "last_updated": "2025-01-04", "type": "chat" }, { - "id": "llama-3.3-70b-instruct", - "name": "Meta Llama 3.3 70B Instruct", - "display_name": "Meta Llama 3.3 70B Instruct", + "id": "mistral-large-2512", + "name": "Mistral Large 3", + "display_name": "Mistral Large 3", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 16400 + "context": 262144, + "output": 262144 }, "temperature": true, "tool_call": true, "reasoning": { "supported": false }, - "attachment": false, + "attachment": true, "open_weights": true, - "knowledge": "2024-12", - "release_date": "2024-12-06", - "last_updated": "2024-12-06", + "knowledge": "2024-11", + "release_date": "2024-11-01", + "last_updated": "2025-12-02", "type": "chat" }, { - "id": "o3-mini", - "name": "OpenAI o3 Mini", - "display_name": "OpenAI o3 Mini", + "id": "glm-5-2", + "name": "GLM-5.2", + "display_name": "GLM-5.2", "modalities": { "input": [ "text" @@ -159015,10 +159007,10 @@ ] }, "limit": { - "context": 200000, - "output": 100000 + "context": 1048576, + "output": 131072 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -159026,131 +159018,78 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", "effort_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + "none", + "high", + "max" + ] } }, "attachment": false, - "open_weights": false, - "knowledge": "2023-10", - "release_date": "2024-12-20", - "last_updated": "2025-01-29", + "open_weights": true, + "release_date": "2026-06-13", + "last_updated": "2026-06-13", "type": "chat" }, { - "id": "gpt-5.1", - "name": "OpenAI GPT-5.1", - "display_name": "OpenAI GPT-5.1", + "id": "ministral-14b-2512", + "name": "Ministral 3 14B", + "display_name": "Ministral 3 14B", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 262144, + "output": 262144 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": false - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "none", - "effort_options": [ - "none", - "low", - "medium", - "high" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" - } + "supported": false }, "attachment": true, - "open_weights": false, - "knowledge": "2025-01", - "release_date": "2025-11-13", - "last_updated": "2025-11-13", + "open_weights": true, + "release_date": "2025-12-02", + "last_updated": "2025-12-02", "type": "chat" }, { - "id": "claude-opus-4-1", - "name": "Anthropic: Claude Opus 4.1", - "display_name": "Anthropic: Claude Opus 4.1", + "id": "voxtral-mini-latest", + "name": "Voxtral Mini (latest)", + "display_name": "Voxtral Mini (latest)", "modalities": { "input": [ - "text", - "image" + "audio" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 32000 + "context": 8192, + "output": 8192 }, - "temperature": true, - "tool_call": true, + "temperature": false, + "tool_call": false, "reasoning": { - "supported": true, - "default": false - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "budget", - "budget": { - "min": 1024, - "unit": "tokens" - }, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", - "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." - ] - } + "supported": false }, "attachment": true, "open_weights": false, - "knowledge": "2025-08", - "release_date": "2025-08-05", - "last_updated": "2025-08-05", - "type": "chat" + "release_date": "2026-02-04", + "last_updated": "2026-02-04" }, { - "id": "kimi-k2-0711", - "name": "Kimi K2 (07/11)", - "display_name": "Kimi K2 (07/11)", + "id": "devstral-latest", + "name": "Devstral 2", + "display_name": "Devstral 2", "modalities": { "input": [ "text" @@ -159160,8 +159099,8 @@ ] }, "limit": { - "context": 131072, - "output": 16384 + "context": 262144, + "output": 262144 }, "temperature": true, "tool_call": true, @@ -159170,15 +159109,15 @@ }, "attachment": false, "open_weights": true, - "knowledge": "2025-01", - "release_date": "2025-07-11", - "last_updated": "2025-07-11", + "knowledge": "2025-12", + "release_date": "2025-12-09", + "last_updated": "2025-12-09", "type": "chat" }, { - "id": "grok-4-fast-reasoning", - "name": "xAI: Grok 4 Fast Reasoning", - "display_name": "xAI: Grok 4 Fast Reasoning", + "id": "mistral-medium-2505", + "name": "Mistral Medium 3", + "display_name": "Mistral Medium 3", "modalities": { "input": [ "text", @@ -159189,37 +159128,67 @@ ] }, "limit": { - "context": 2000000, - "output": 2000000 + "context": 131072, + "output": 131072 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true + "supported": false }, "attachment": true, "open_weights": false, - "knowledge": "2025-09", - "release_date": "2025-12-18", - "last_updated": "2025-12-18", + "knowledge": "2025-05", + "release_date": "2025-05-07", + "last_updated": "2025-05-07", "type": "chat" }, { - "id": "qwen3-32b", - "name": "Qwen3 32B", - "display_name": "Qwen3 32B", + "id": "voxtral-small-2507", + "name": "Voxtral Small", + "display_name": "Voxtral Small", "modalities": { "input": [ + "text", + "audio", + "pdf" + ], + "output": [ "text" + ] + }, + "limit": { + "context": 32768, + "output": 32000 + }, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": false + }, + "attachment": true, + "open_weights": true, + "release_date": "2025-07-15", + "last_updated": "2025-07-15", + "type": "chat" + }, + { + "id": "mistral-medium-latest", + "name": "Mistral Medium (latest)", + "display_name": "Mistral Medium (latest)", + "modalities": { + "input": [ + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 40960 + "context": 262144, + "output": 262144 }, "temperature": true, "tool_call": true, @@ -159229,29 +159198,26 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "none", + "high" ] } }, - "attachment": false, + "attachment": true, "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-04-28", - "last_updated": "2025-04-28", + "release_date": "2026-04-29", + "last_updated": "2026-04-29", "type": "chat" }, { - "id": "grok-4", - "name": "xAI Grok 4", - "display_name": "xAI Grok 4", + "id": "codestral-2508", + "name": "Codestral 25.08", + "display_name": "Codestral 25.08", "modalities": { "input": [ - "text" + "text", + "pdf" ], "output": [ "text" @@ -159259,32 +159225,57 @@ }, "limit": { "context": 256000, - "output": 256000 + "output": 8192 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true - } + "supported": false }, - "attachment": false, + "attachment": true, "open_weights": false, - "knowledge": "2024-07", - "release_date": "2024-07-09", - "last_updated": "2024-07-09", + "release_date": "2025-07-30", + "last_updated": "2025-07-30", "type": "chat" }, { - "id": "qwen3-coder", - "name": "Qwen3 Coder 480B A35B Instruct Turbo", - "display_name": "Qwen3 Coder 480B A35B Instruct Turbo", + "id": "pixtral-12b", + "name": "Pixtral 12B", + "display_name": "Pixtral 12B", "modalities": { "input": [ + "text", + "image" + ], + "output": [ "text" + ] + }, + "limit": { + "context": 128000, + "output": 128000 + }, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": false + }, + "attachment": true, + "open_weights": true, + "knowledge": "2024-09", + "release_date": "2024-09-01", + "last_updated": "2024-09-01", + "type": "chat" + }, + { + "id": "ministral-8b-2512", + "name": "Ministral 3 8B", + "display_name": "Ministral 3 8B", + "modalities": { + "input": [ + "text", + "image", + "pdf" ], "output": [ "text" @@ -159292,223 +159283,196 @@ }, "limit": { "context": 262144, - "output": 16384 + "output": 262144 }, "temperature": true, "tool_call": true, "reasoning": { "supported": false }, - "attachment": false, + "attachment": true, "open_weights": true, - "knowledge": "2025-07", - "release_date": "2025-07-22", - "last_updated": "2025-07-22", + "release_date": "2025-12-02", + "last_updated": "2025-12-02", "type": "chat" }, { - "id": "claude-opus-4-1-20250805", - "name": "Anthropic: Claude Opus 4.1 (20250805)", - "display_name": "Anthropic: Claude Opus 4.1 (20250805)", + "id": "ministral-3b-2512", + "name": "Ministral 3 3B", + "display_name": "Ministral 3 3B", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 32000 + "context": 131072, + "output": 262144 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": false - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "budget", - "budget": { - "min": 1024, - "unit": "tokens" - }, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", - "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." - ] - } + "supported": false }, "attachment": true, - "open_weights": false, - "knowledge": "2025-08", - "release_date": "2025-08-05", - "last_updated": "2025-08-05", + "open_weights": true, + "release_date": "2025-12-02", + "last_updated": "2025-12-02", "type": "chat" }, { - "id": "claude-sonnet-4-5-20250929", - "name": "Anthropic: Claude Sonnet 4.5 (20250929)", - "display_name": "Anthropic: Claude Sonnet 4.5 (20250929)", + "id": "mistral-small-latest", + "name": "Mistral Small (latest)", + "display_name": "Mistral Small (latest)", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 64000 + "context": 262144, + "output": 256000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "budget", - "budget": { - "min": 1024, - "unit": "tokens" - }, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", - "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." + "effort_options": [ + "none", + "high" ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2025-09", - "release_date": "2025-09-29", - "last_updated": "2025-09-29", + "open_weights": true, + "knowledge": "2025-06", + "release_date": "2026-03-16", + "last_updated": "2026-03-16", "type": "chat" }, { - "id": "claude-4.5-haiku", - "name": "Anthropic: Claude 4.5 Haiku", - "display_name": "Anthropic: Claude 4.5 Haiku", + "id": "open-mistral-nemo", + "name": "Open Mistral Nemo", + "display_name": "Open Mistral Nemo", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 8192 + "context": 128000, + "output": 128000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true + "supported": false }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-10", - "release_date": "2025-10-15", - "last_updated": "2025-10-15", + "attachment": false, + "open_weights": true, + "knowledge": "2024-07", + "release_date": "2024-07-01", + "last_updated": "2024-07-01", "type": "chat" }, { - "id": "sonar", - "name": "Perplexity Sonar", - "display_name": "Perplexity Sonar", + "id": "mistral-large-4-0-8", + "name": "Mistral Large 4.0.8", + "display_name": "Mistral Large 4.0.8", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 127000, - "output": 4096 + "context": 1048576, + "output": 262144 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "low", + "medium", + "high" + ] + } + }, + "attachment": true, "open_weights": false, - "knowledge": "2025-01", - "release_date": "2024-01-01", - "last_updated": "2025-09-01", + "release_date": "2026-10-06", + "last_updated": "2026-10-06", "type": "chat" }, { - "id": "qwen3-30b-a3b", - "name": "Qwen3 30B A3B", - "display_name": "Qwen3 30B A3B", + "id": "mistral-medium-2604", + "name": "Mistral Medium 3.5", + "display_name": "Mistral Medium 3.5", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 41000, - "output": 41000 + "context": 262144, + "output": 262144 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true + "supported": true, + "default": true }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "none", + "high" ] } }, - "attachment": false, + "attachment": true, "open_weights": true, - "knowledge": "2025-06", - "release_date": "2025-04-28", - "last_updated": "2025-04-28", + "release_date": "2026-04-29", + "last_updated": "2026-04-29", "type": "chat" }, { - "id": "gemini-2.5-flash-lite", - "name": "Google Gemini 2.5 Flash Lite", - "display_name": "Google Gemini 2.5 Flash Lite", + "id": "magistral-medium-latest", + "name": "Magistral Medium (latest)", + "display_name": "Magistral Medium (latest)", "modalities": { "input": [ "text", @@ -159519,57 +159483,47 @@ ] }, "limit": { - "context": 1048576, - "output": 65535 + "context": 262144, + "output": 16384 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "budget", - "budget": { - "default": -1, - "min": 512, - "max": 24576, - "auto": -1, - "unit": "tokens" - }, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thought_signatures" + "effort_options": [ + "none", + "high" ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2025-07", - "release_date": "2025-06-17", - "last_updated": "2025-06-17", + "open_weights": true, + "knowledge": "2025-06", + "release_date": "2025-03-17", + "last_updated": "2025-03-20", "type": "chat" }, { - "id": "gpt-4.1-mini", - "name": "OpenAI GPT-4.1 Mini", - "display_name": "OpenAI GPT-4.1 Mini", + "id": "ministral-3b-latest", + "name": "Ministral 3B (latest)", + "display_name": "Ministral 3B (latest)", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1047576, - "output": 32768 + "context": 131072, + "output": 128000 }, "temperature": true, "tool_call": true, @@ -159577,16 +159531,16 @@ "supported": false }, "attachment": true, - "open_weights": false, - "knowledge": "2025-04", - "release_date": "2025-04-14", - "last_updated": "2025-04-14", + "open_weights": true, + "knowledge": "2024-10", + "release_date": "2024-10-01", + "last_updated": "2024-10-04", "type": "chat" }, { - "id": "claude-haiku-4-5-20251001", - "name": "Anthropic: Claude 4.5 Haiku (20251001)", - "display_name": "Anthropic: Claude 4.5 Haiku (20251001)", + "id": "labs-devstral-small-2512", + "name": "Devstral Small 2", + "display_name": "Devstral Small 2", "modalities": { "input": [ "text", @@ -159597,61 +159551,40 @@ ] }, "limit": { - "context": 200000, - "output": 8192 + "context": 256000, + "output": 256000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": false - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "budget", - "budget": { - "min": 1024, - "unit": "tokens" - }, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", - "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." - ] - } + "supported": false }, "attachment": true, - "open_weights": false, - "knowledge": "2025-10", - "release_date": "2025-10-15", - "last_updated": "2025-10-15", + "open_weights": true, + "knowledge": "2025-12", + "release_date": "2025-12-09", + "last_updated": "2025-12-09", "type": "chat" }, { - "id": "o3-pro", - "name": "OpenAI o3 Pro", - "display_name": "OpenAI o3 Pro", + "id": "mistral-large-4", + "name": "Mistral Large 4", + "display_name": "Mistral Large 4", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 100000 + "context": 1048576, + "output": 262144 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -159659,57 +159592,52 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", "effort_options": [ "low", "medium", "high" - ], - "visibility": "hidden" + ] } }, "attachment": true, "open_weights": false, - "knowledge": "2024-06", - "release_date": "2025-06-10", - "last_updated": "2025-06-10", + "release_date": "2026-10-06", + "last_updated": "2026-10-06", "type": "chat" }, { - "id": "deepseek-v3", - "name": "DeepSeek V3", - "display_name": "DeepSeek V3", + "id": "mistral-medium-2508", + "name": "Mistral Medium 3.1", + "display_name": "Mistral Medium 3.1", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 8192 + "context": 262144, + "output": 262144 }, "temperature": true, "tool_call": true, "reasoning": { "supported": false }, - "attachment": false, + "attachment": true, "open_weights": false, - "knowledge": "2024-12", - "release_date": "2024-12-26", - "last_updated": "2024-12-26", + "knowledge": "2025-05", + "release_date": "2025-08-12", + "last_updated": "2025-08-12", "type": "chat" }, { - "id": "claude-4.5-opus", - "name": "Anthropic: Claude Opus 4.5", - "display_name": "Anthropic: Claude Opus 4.5", + "id": "labs-leanstral-1-5-1", + "name": "Leanstral 1.5", + "display_name": "Leanstral 1.5", "modalities": { "input": [ "text", @@ -159720,8 +159648,8 @@ ] }, "limit": { - "context": 200000, - "output": 64000 + "context": 262144, + "output": 128000 }, "temperature": true, "tool_call": true, @@ -159729,17 +159657,24 @@ "supported": true, "default": true }, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "none", + "high" + ] + } + }, "attachment": true, - "open_weights": false, - "knowledge": "2025-11", - "release_date": "2025-11-24", - "last_updated": "2025-11-24", + "open_weights": true, + "release_date": "2026-06-30", + "last_updated": "2026-06-30", "type": "chat" }, { - "id": "mistral-nemo", - "name": "Mistral Nemo", - "display_name": "Mistral Nemo", + "id": "zai-glm-5-2", + "name": "GLM-5.2", + "display_name": "GLM-5.2", "modalities": { "input": [ "text" @@ -159749,25 +159684,34 @@ ] }, "limit": { - "context": 128000, - "output": 16400 + "context": 1048576, + "output": 131072 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "none", + "high", + "max" + ] + } }, "attachment": false, "open_weights": true, - "knowledge": "2024-07", - "release_date": "2024-07-18", - "last_updated": "2024-07-18", + "release_date": "2026-06-13", + "last_updated": "2026-06-13", "type": "chat" }, { - "id": "qwen3-235b-a22b-thinking", - "name": "Qwen3 235B A22B Thinking", - "display_name": "Qwen3 235B A22B Thinking", + "id": "devstral-2512", + "name": "Devstral 2", + "display_name": "Devstral 2", "modalities": { "input": [ "text" @@ -159778,128 +159722,104 @@ }, "limit": { "context": 262144, - "output": 81920 + "output": 262144 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } + "supported": false }, "attachment": false, "open_weights": true, - "knowledge": "2025-07", - "release_date": "2025-07-25", - "last_updated": "2025-07-25", + "knowledge": "2025-12", + "release_date": "2025-12-09", + "last_updated": "2025-12-09", "type": "chat" }, { - "id": "claude-3.7-sonnet", - "name": "Anthropic: Claude 3.7 Sonnet", - "display_name": "Anthropic: Claude 3.7 Sonnet", + "id": "zai-glm-5-3-flash", + "name": "GLM-5.3-Flash", + "display_name": "GLM-5.3-Flash", "modalities": { "input": [ "text", - "image" + "image", + "video", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 64000 + "context": 1048576, + "output": 131072 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "budget", - "budget": { - "min": 1024, - "unit": "tokens" - }, - "interleaved": false, - "summaries": false, - "visibility": "full", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Anthropic uses thinking budget tokens" + "effort_options": [ + "low", + "high", + "max" ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2025-02", - "release_date": "2025-02-19", - "last_updated": "2025-02-19", + "open_weights": true, + "release_date": "2026-08-26", + "last_updated": "2026-08-26", "type": "chat" }, { - "id": "gpt-4.1", - "name": "OpenAI GPT-4.1", - "display_name": "OpenAI GPT-4.1", + "id": "mistral-embed", + "name": "Mistral Embed", + "display_name": "Mistral Embed", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1047576, - "output": 32768 + "context": 8000, + "output": 3072 }, - "temperature": true, - "tool_call": true, + "temperature": false, + "tool_call": false, "reasoning": { "supported": false }, - "attachment": true, + "attachment": false, "open_weights": false, - "knowledge": "2025-04", - "release_date": "2025-04-14", - "last_updated": "2025-04-14", - "type": "chat" + "release_date": "2023-12-11", + "last_updated": "2023-12-11", + "type": "embedding" }, { - "id": "grok-4-fast-non-reasoning", - "name": "xAI Grok 4 Fast Non-Reasoning", - "display_name": "xAI Grok 4 Fast Non-Reasoning", + "id": "voxtral-small-latest", + "name": "Voxtral Small (latest)", + "display_name": "Voxtral Small (latest)", "modalities": { "input": [ "text", - "image", - "audio" + "audio", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 2000000, - "output": 2000000 + "context": 32768, + "output": 32000 }, "temperature": true, "tool_call": true, @@ -159907,44 +159827,45 @@ "supported": false }, "attachment": true, - "open_weights": false, - "knowledge": "2025-09", - "release_date": "2025-12-18", - "last_updated": "2025-12-18", + "open_weights": true, + "release_date": "2025-07-15", + "last_updated": "2025-07-15", "type": "chat" }, { - "id": "qwen2.5-coder-7b-fast", - "name": "Qwen2.5 Coder 7B fast", - "display_name": "Qwen2.5 Coder 7B fast", + "id": "ministral-8b-latest", + "name": "Ministral 8B (latest)", + "display_name": "Ministral 8B (latest)", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 32000, - "output": 8192 + "context": 262144, + "output": 128000 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { "supported": false }, - "attachment": false, - "open_weights": false, - "knowledge": "2024-09", - "release_date": "2024-09-15", - "last_updated": "2024-09-15", + "attachment": true, + "open_weights": true, + "knowledge": "2024-10", + "release_date": "2024-10-01", + "last_updated": "2024-10-04", "type": "chat" }, { - "id": "o1-mini", - "name": "OpenAI: o1-mini", - "display_name": "OpenAI: o1-mini", + "id": "deepseek-v4-1-flash-1017", + "name": "DeepSeek V4.1 Flash 1017", + "display_name": "DeepSeek V4.1 Flash 1017", "modalities": { "input": [ "text" @@ -159954,51 +159875,49 @@ ] }, "limit": { - "context": 128000, - "output": 65536 + "context": 1048576, + "output": 128000 }, - "temperature": false, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { "supported": true, "default": true }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", "effort_options": [ + "none", "low", - "medium", - "high" - ], - "visibility": "hidden" + "high", + "max" + ] } }, "attachment": false, - "open_weights": false, - "knowledge": "2025-01", - "release_date": "2025-01-01", - "last_updated": "2025-01-01", + "open_weights": true, + "knowledge": "2025-05", + "release_date": "2026-10-17", + "last_updated": "2026-10-17", "type": "chat" }, { - "id": "deepseek-v3.1-terminus", - "name": "DeepSeek V3.1 Terminus", - "display_name": "DeepSeek V3.1 Terminus", + "id": "mistral-small-2603", + "name": "Mistral Small 4", + "display_name": "Mistral Small 4", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 16384 + "context": 262144, + "output": 256000 }, "temperature": true, "tool_call": true, @@ -160006,214 +159925,211 @@ "supported": true, "default": true }, - "attachment": false, - "open_weights": false, - "knowledge": "2025-09", - "release_date": "2025-09-22", - "last_updated": "2025-09-22", + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "none", + "high" + ] + } + }, + "attachment": true, + "open_weights": true, + "knowledge": "2025-06", + "release_date": "2026-03-16", + "last_updated": "2026-03-16", "type": "chat" }, { - "id": "llama-3.3-70b-versatile", - "name": "Meta Llama 3.3 70B Versatile", - "display_name": "Meta Llama 3.3 70B Versatile", + "id": "voxtral-mini-tts-latest", + "name": "Voxtral Mini TTS (latest)", + "display_name": "Voxtral Mini TTS (latest)", "modalities": { "input": [ "text" ], "output": [ - "text" + "audio" ] }, "limit": { - "context": 131072, - "output": 32678 + "context": 8192, + "output": 8192 }, - "temperature": true, - "tool_call": true, + "temperature": false, + "tool_call": false, "reasoning": { "supported": false }, "attachment": false, "open_weights": true, - "knowledge": "2024-12", - "release_date": "2024-12-06", - "last_updated": "2024-12-06", - "type": "chat" + "release_date": "2026-03-23", + "last_updated": "2026-03-23" }, { - "id": "claude-3.5-sonnet-v2", - "name": "Anthropic: Claude 3.5 Sonnet v2", - "display_name": "Anthropic: Claude 3.5 Sonnet v2", + "id": "zai-glm-5-3", + "name": "GLM-5.3", + "display_name": "GLM-5.3", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 8192 + "context": 1048576, + "output": 131072 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": true, - "open_weights": false, - "knowledge": "2024-10", - "release_date": "2024-10-22", - "last_updated": "2024-10-22", + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "low", + "high", + "max" + ] + } + }, + "attachment": false, + "open_weights": true, + "release_date": "2026-08-14", + "last_updated": "2026-08-14", "type": "chat" }, { - "id": "hermes-2-pro-llama-3-8b", - "name": "Hermes 2 Pro Llama 3 8B", - "display_name": "Hermes 2 Pro Llama 3 8B", + "id": "mistral-small-2506", + "name": "Mistral Small 3.2", + "display_name": "Mistral Small 3.2", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 131072 + "context": 128000, + "output": 16384 }, "temperature": true, "tool_call": true, "reasoning": { "supported": false }, - "attachment": false, + "attachment": true, "open_weights": true, - "knowledge": "2024-05", - "release_date": "2024-04-30", - "last_updated": "2024-04-30", + "knowledge": "2025-03", + "release_date": "2025-06-20", + "last_updated": "2025-06-20", "type": "chat" }, { - "id": "claude-sonnet-4", - "name": "Anthropic: Claude Sonnet 4", - "display_name": "Anthropic: Claude Sonnet 4", + "id": "mistral-large-latest", + "name": "Mistral Large (latest)", + "display_name": "Mistral Large (latest)", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 64000 + "context": 262144, + "output": 262144 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": false - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "budget", - "budget": { - "min": 1024, - "unit": "tokens" - }, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", - "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." - ] - } + "supported": false }, "attachment": true, - "open_weights": false, - "knowledge": "2025-05", - "release_date": "2025-05-22", - "last_updated": "2025-05-22", + "open_weights": true, + "knowledge": "2024-11", + "release_date": "2024-11-01", + "last_updated": "2025-12-02", "type": "chat" - }, + } + ] + }, + "groq": { + "id": "groq", + "name": "Groq", + "display_name": "Groq", + "doc": "https://console.groq.com/docs/models", + "models": [ { - "id": "llama-prompt-guard-2-86m", - "name": "Meta Llama Prompt Guard 2 86M", - "display_name": "Meta Llama Prompt Guard 2 86M", + "id": "whisper-large-v3", + "name": "Whisper", + "display_name": "Whisper", "modalities": { "input": [ - "text" + "audio" ], "output": [ "text" ] }, "limit": { - "context": 512, - "output": 2 + "context": 448, + "output": 448 }, "temperature": true, "tool_call": false, "reasoning": { "supported": false }, - "attachment": false, + "attachment": true, "open_weights": true, - "knowledge": "2024-10", - "release_date": "2025-04-29", - "last_updated": "2025-04-29", - "type": "chat" + "release_date": "2023-11-06", + "last_updated": "2023-11-06" }, { - "id": "claude-3-haiku-20240307", - "name": "Anthropic: Claude 3 Haiku", - "display_name": "Anthropic: Claude 3 Haiku", + "id": "allam-2-7b", + "name": "ALLaM-2-7b", + "display_name": "ALLaM-2-7b", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 200000, + "context": 4096, "output": 4096 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { "supported": false }, - "attachment": true, - "open_weights": false, - "knowledge": "2024-03", - "release_date": "2024-03-13", - "last_updated": "2024-03-13", + "attachment": false, + "open_weights": true, + "release_date": "2025-01-23", + "last_updated": "2025-01-23", "type": "chat" }, { - "id": "llama-4-maverick", - "name": "Meta Llama 4 Maverick 17B 128E", - "display_name": "Meta Llama 4 Maverick 17B 128E", + "id": "llama-3.3-70b-versatile", + "name": "Llama 3.3 70B", + "display_name": "Llama 3.3 70B", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" @@ -160221,24 +160137,24 @@ }, "limit": { "context": 131072, - "output": 8192 + "output": 32768 }, "temperature": true, "tool_call": true, "reasoning": { "supported": false }, - "attachment": true, + "attachment": false, "open_weights": true, - "knowledge": "2025-01", - "release_date": "2025-04-05", - "last_updated": "2025-04-05", + "knowledge": "2023-12", + "release_date": "2024-12-06", + "last_updated": "2024-12-06", "type": "chat" }, { - "id": "llama-3.1-8b-instruct-turbo", - "name": "Meta Llama 3.1 8B Instruct Turbo", - "display_name": "Meta Llama 3.1 8B Instruct Turbo", + "id": "llama-3.1-8b-instant", + "name": "Llama 3.1 8B", + "display_name": "Llama 3.1 8B", "modalities": { "input": [ "text" @@ -160248,8 +160164,8 @@ ] }, "limit": { - "context": 128000, - "output": 128000 + "context": 131072, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -160258,66 +160174,41 @@ }, "attachment": false, "open_weights": true, - "knowledge": "2024-07", + "knowledge": "2023-12", "release_date": "2024-07-23", "last_updated": "2024-07-23", "type": "chat" }, { - "id": "gpt-5-nano", - "name": "OpenAI GPT-5 Nano", - "display_name": "OpenAI GPT-5 Nano", + "id": "whisper-large-v3-turbo", + "name": "Whisper Large V3 Turbo", + "display_name": "Whisper Large V3 Turbo", "modalities": { "input": [ - "text", - "image" + "audio" ], "output": [ "text" ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 448, + "output": 448 }, - "temperature": false, - "tool_call": true, + "temperature": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", - "effort_options": [ - "minimal", - "low", - "medium", - "high" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" - } + "supported": false }, "attachment": true, - "open_weights": false, - "knowledge": "2025-01", - "release_date": "2025-08-07", - "last_updated": "2025-08-07", - "type": "chat" + "open_weights": true, + "release_date": "2024-10-01", + "last_updated": "2024-10-01" }, { - "id": "mistral-large-2411", - "name": "Mistral-Large", - "display_name": "Mistral-Large", + "id": "openai/gpt-oss-safeguard-20b", + "name": "Safety GPT OSS 20B", + "display_name": "Safety GPT OSS 20B", "modalities": { "input": [ "text" @@ -160327,29 +160218,37 @@ ] }, "limit": { - "context": 128000, - "output": 32768 + "context": 131072, + "output": 65536 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "low", + "medium", + "high" + ] + } }, "attachment": false, "open_weights": true, - "knowledge": "2024-07", - "release_date": "2024-11-18", - "last_updated": "2024-11-18", + "release_date": "2025-10-29", + "last_updated": "2025-10-29", "type": "chat" }, { - "id": "llama-4-scout", - "name": "Meta Llama 4 Scout 17B 16E", - "display_name": "Meta Llama 4 Scout 17B 16E", + "id": "openai/gpt-oss-120b", + "name": "GPT OSS 120B", + "display_name": "GPT OSS 120B", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" @@ -160357,24 +160256,35 @@ }, "limit": { "context": 131072, - "output": 8192 + "output": 65536 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": true, + "extra_capabilities": { + "reasoning": { + "supported": true, + "effort_options": [ + "low", + "medium", + "high" + ] + } + }, + "attachment": false, "open_weights": true, - "knowledge": "2025-01", - "release_date": "2025-04-05", - "last_updated": "2025-04-05", + "knowledge": "2024-06", + "release_date": "2025-08-05", + "last_updated": "2025-08-05", "type": "chat" }, { - "id": "sonar-reasoning-pro", - "name": "Perplexity Sonar Reasoning Pro", - "display_name": "Perplexity Sonar Reasoning Pro", + "id": "openai/gpt-oss-20b", + "name": "GPT OSS 20B", + "display_name": "GPT OSS 20B", "modalities": { "input": [ "text" @@ -160384,19 +160294,19 @@ ] }, "limit": { - "context": 127000, - "output": 4096 + "context": 131072, + "output": 65536 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { "supported": true, "default": true }, "extra_capabilities": { "reasoning": { + "supported": true, "effort_options": [ - "minimal", "low", "medium", "high" @@ -160404,16 +160314,16 @@ } }, "attachment": false, - "open_weights": false, - "knowledge": "2025-01", - "release_date": "2024-01-01", - "last_updated": "2025-09-01", + "open_weights": true, + "knowledge": "2024-06", + "release_date": "2025-08-05", + "last_updated": "2025-08-05", "type": "chat" }, { - "id": "llama-3.1-8b-instruct", - "name": "Meta Llama 3.1 8B Instruct", - "display_name": "Meta Llama 3.1 8B Instruct", + "id": "meta-llama/llama-prompt-guard-2-86m", + "name": "Prompt Guard 2 86M", + "display_name": "Prompt Guard 2 86M", "modalities": { "input": [ "text" @@ -160423,33 +160333,24 @@ ] }, "limit": { - "context": 16384, - "output": 16384 + "context": 512, + "output": 512 }, - "temperature": true, - "tool_call": true, + "temperature": false, + "tool_call": false, "reasoning": { "supported": false }, "attachment": false, "open_weights": true, - "knowledge": "2024-07", - "release_date": "2024-07-23", - "last_updated": "2024-07-23", + "release_date": "2025-04-29", + "last_updated": "2025-04-29", "type": "chat" - } - ] - }, - "cohere": { - "id": "cohere", - "name": "Cohere", - "display_name": "Cohere", - "doc": "https://docs.cohere.com/docs/models", - "models": [ + }, { - "id": "command-a-reasoning-08-2025", - "name": "Command A Reasoning", - "display_name": "Command A Reasoning", + "id": "meta-llama/llama-prompt-guard-2-22m", + "name": "Llama Prompt Guard 2 22M", + "display_name": "Llama Prompt Guard 2 22M", "modalities": { "input": [ "text" @@ -160459,81 +160360,115 @@ ] }, "limit": { - "context": 256000, - "output": 32000 + "context": 512, + "output": 512 }, - "temperature": true, - "tool_call": true, + "temperature": false, + "tool_call": false, "reasoning": { - "supported": true, - "default": true + "supported": false }, "attachment": false, "open_weights": true, - "knowledge": "2024-06-01", - "release_date": "2025-08-21", - "last_updated": "2025-08-21", + "release_date": "2025-04-29", + "last_updated": "2025-04-29", "type": "chat" }, { - "id": "command-a-03-2025", - "name": "Command A", - "display_name": "Command A", + "id": "canopylabs/orpheus-arabic-saudi", + "name": "Canopy Labs Orpheus Arabic Saudi", + "display_name": "Canopy Labs Orpheus Arabic Saudi", "modalities": { "input": [ "text" ], "output": [ - "text" + "audio" ] }, "limit": { - "context": 256000, - "output": 8000 + "context": 4000, + "output": 50000 }, - "temperature": true, - "tool_call": true, + "temperature": false, + "tool_call": false, "reasoning": { "supported": false }, "attachment": false, - "open_weights": true, - "knowledge": "2024-06-01", - "release_date": "2025-03-13", - "last_updated": "2025-03-13", - "type": "chat" + "open_weights": false, + "release_date": "2025-12-16", + "last_updated": "2025-12-16" }, { - "id": "tiny-aya-earth", - "name": "Tiny Aya Earth", - "display_name": "Tiny Aya Earth", + "id": "canopylabs/orpheus-v1-english", + "name": "Canopy Labs Orpheus V1 English", + "display_name": "Canopy Labs Orpheus V1 English", "modalities": { "input": [ "text" ], + "output": [ + "audio" + ] + }, + "limit": { + "context": 4000, + "output": 50000 + }, + "temperature": false, + "tool_call": false, + "reasoning": { + "supported": false + }, + "attachment": false, + "open_weights": false, + "release_date": "2025-12-19", + "last_updated": "2025-12-19" + }, + { + "id": "qwen/qwen3.8-27b", + "name": "Qwen3.8 27B", + "display_name": "Qwen3.8 27B", + "modalities": { + "input": [ + "text", + "image" + ], "output": [ "text" ] }, "limit": { - "context": 8000, - "output": 8000 + "context": 131072, + "output": 16384 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "none", + "low", + "medium", + "high" + ] + } + }, + "attachment": true, "open_weights": true, - "release_date": "2026-02-17", - "last_updated": "2026-02-17", + "release_date": "2026-08-14", + "last_updated": "2026-08-14", "type": "chat" }, { - "id": "c4ai-aya-vision-32b", - "name": "Aya Vision 32B", - "display_name": "Aya Vision 32B", + "id": "qwen/qwen3.6-27b", + "name": "Qwen3.6 27B", + "display_name": "Qwen3.6 27B", "modalities": { "input": [ "text", @@ -160544,35 +160479,62 @@ ] }, "limit": { - "context": 16000, - "output": 4000 + "context": 131072, + "output": 16384 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "effort_options": [ + "none" + ] + } }, "attachment": true, "open_weights": true, - "release_date": "2025-03-04", - "last_updated": "2025-05-14", + "release_date": "2026-04-22", + "last_updated": "2026-04-22", "type": "chat" - }, + } + ] + }, + "alibaba": { + "id": "alibaba", + "name": "alibaba", + "display_name": "alibaba", + "api": "https://dashscope-intl.aliyuncs.com/compatible-mode/v1", + "doc": "https://www.alibabacloud.com/help/en/model-studio/models", + "models": [ { - "id": "north-mini-code-1-0", - "name": "North Mini Code", - "display_name": "North Mini Code", + "id": "qwen3.8-omni-flash", + "name": "Qwen3.8 Omni Flash", + "display_name": "Qwen3.8 Omni Flash", "modalities": { "input": [ - "text" + "text", + "image", + "audio", + "video" ], "output": [ "text" ] }, "limit": { - "context": 256000, - "output": 64000 + "context": 1000000, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -160583,56 +160545,78 @@ "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "xhigh", + "effort_options": [ + "none", + "low", + "medium", + "xhigh" + ], "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ "thinking_blocks" - ], - "effort_options": [ - "none", - "high" ] } }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-09-23", - "release_date": "2026-06-09", - "last_updated": "2026-06-09", + "attachment": true, + "open_weights": false, + "release_date": "2026-09-17", + "last_updated": "2026-09-17", "type": "chat" }, { - "id": "north-small-translate-09-2026", - "name": "North Small Translate", - "display_name": "North Small Translate", + "id": "kimi-k3", + "name": "Kimi K3", + "display_name": "Kimi K3", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 32768, - "output": 16000 + "context": 1048576, + "output": 1048576 }, - "temperature": true, - "tool_call": false, + "temperature": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "effort_options": [ + "low", + "high", + "max" + ] + } + }, + "attachment": true, "open_weights": true, - "release_date": "2026-09-09", - "last_updated": "2026-09-09", + "release_date": "2026-07-16", + "last_updated": "2026-07-16", "type": "chat" }, { - "id": "tiny-aya-fire", - "name": "Tiny Aya Fire", - "display_name": "Tiny Aya Fire", + "id": "qwen3-max", + "name": "Qwen3 Max", + "display_name": "Qwen3 Max", "modalities": { "input": [ "text" @@ -160642,51 +160626,65 @@ ] }, "limit": { - "context": 8000, - "output": 8000 + "context": 262144, + "output": 65536 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } }, "attachment": false, - "open_weights": true, - "release_date": "2026-02-17", - "last_updated": "2026-02-17", + "open_weights": false, + "knowledge": "2025-04", + "release_date": "2025-09-23", + "last_updated": "2025-09-23", "type": "chat" }, { - "id": "c4ai-aya-expanse-32b", - "name": "Aya Expanse 32B", - "display_name": "Aya Expanse 32B", + "id": "qwen-vl-max", + "name": "Qwen-VL Max", + "display_name": "Qwen-VL Max", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 4000 + "context": 131072, + "output": 8192 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { "supported": false }, - "attachment": false, - "open_weights": true, - "release_date": "2024-10-24", - "last_updated": "2024-10-24", + "attachment": true, + "open_weights": false, + "knowledge": "2024-04", + "release_date": "2024-04-08", + "last_updated": "2025-08-13", "type": "chat" }, { - "id": "command-r7b-arabic-02-2025", - "name": "Command R7B Arabic", - "display_name": "Command R7B Arabic", + "id": "qwen3-next-80b-a3b-instruct", + "name": "Qwen3 Next 80B A3B Instruct", + "display_name": "Qwen3 Next 80B A3B Instruct", "modalities": { "input": [ "text" @@ -160696,8 +160694,8 @@ ] }, "limit": { - "context": 128000, - "output": 4000 + "context": 131072, + "output": 32768 }, "temperature": true, "tool_call": true, @@ -160706,98 +160704,114 @@ }, "attachment": false, "open_weights": true, - "knowledge": "2024-06-01", - "release_date": "2025-02-27", - "last_updated": "2025-02-27", + "knowledge": "2025-04", + "release_date": "2025-09", + "last_updated": "2025-09", "type": "chat" }, { - "id": "command-r-08-2024", - "name": "Command R", - "display_name": "Command R", + "id": "qwen3.5-27b", + "name": "Qwen3.5 27B", + "display_name": "Qwen3.5 27B", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 4000 + "context": 262144, + "output": 65536 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } + }, + "attachment": true, "open_weights": true, - "knowledge": "2024-06-01", - "release_date": "2024-08-30", - "last_updated": "2024-08-30", + "release_date": "2026-02-23", + "last_updated": "2026-02-23", "type": "chat" }, { - "id": "command-a-translate-08-2025", - "name": "Command A Translate", - "display_name": "Command A Translate", + "id": "qwen3-omni-flash-realtime", + "name": "Qwen3-Omni Flash Realtime", + "display_name": "Qwen3-Omni Flash Realtime", "modalities": { "input": [ - "text" + "text", + "image", + "audio", + "video" ], "output": [ - "text" + "text", + "audio" ] }, "limit": { - "context": 8000, - "output": 8000 + "context": 65536, + "output": 16384 }, "temperature": true, "tool_call": true, "reasoning": { "supported": false }, - "attachment": false, - "open_weights": true, - "knowledge": "2024-06-01", - "release_date": "2025-08-28", - "last_updated": "2025-08-28", - "type": "chat" + "attachment": true, + "open_weights": false, + "knowledge": "2024-04", + "release_date": "2025-09-15", + "last_updated": "2025-09-15" }, { - "id": "tiny-aya-water", - "name": "Tiny Aya Water", - "display_name": "Tiny Aya Water", + "id": "qwen3-asr-flash", + "name": "Qwen3-ASR Flash", + "display_name": "Qwen3-ASR Flash", "modalities": { "input": [ - "text" + "audio" ], "output": [ "text" ] }, "limit": { - "context": 8000, - "output": 8000 + "context": 53248, + "output": 4096 }, - "temperature": true, - "tool_call": true, + "temperature": false, + "tool_call": false, "reasoning": { "supported": false }, - "attachment": false, - "open_weights": true, - "release_date": "2026-02-17", - "last_updated": "2026-02-17", - "type": "chat" + "attachment": true, + "open_weights": false, + "knowledge": "2024-04", + "release_date": "2025-09-08", + "last_updated": "2025-09-08" }, { - "id": "command-r7b-12-2024", - "name": "Command R7B", - "display_name": "Command R7B", + "id": "qwen3.7-max", + "name": "Qwen3.7 Max", + "display_name": "Qwen3.7 Max", "modalities": { "input": [ "text" @@ -160807,66 +160821,43 @@ ] }, "limit": { - "context": 128000, - "output": 4000 + "context": 1000000, + "output": 131072 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false - }, - "attachment": false, - "open_weights": true, - "knowledge": "2024-06-01", - "release_date": "2024-12-13", - "last_updated": "2024-12-13", - "type": "chat" - }, - { - "id": "command-a-vision-07-2025", - "name": "Command A Vision", - "display_name": "Command A Vision", - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 8000 + "supported": true, + "default": true }, - "temperature": true, - "tool_call": false, - "reasoning": { - "supported": false + "extra_capabilities": { + "reasoning": { + "supported": true + } }, - "attachment": true, - "open_weights": true, - "knowledge": "2024-06-01", - "release_date": "2025-07-31", - "last_updated": "2025-07-31", + "attachment": false, + "open_weights": false, + "release_date": "2026-05-21", + "last_updated": "2026-05-21", "type": "chat" }, { - "id": "command-a-plus-05-2026", - "name": "Command A Plus", - "display_name": "Command A Plus", + "id": "qwen3.6-flash", + "name": "Qwen3.6 Flash", + "display_name": "Qwen3.6 Flash", "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 64000 + "context": 1000000, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -160876,20 +160867,25 @@ }, "extra_capabilities": { "reasoning": { - "supported": true + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] } }, "attachment": true, - "open_weights": true, - "knowledge": "2025-04-01", - "release_date": "2026-05-20", - "last_updated": "2026-06-09", + "open_weights": false, + "release_date": "2026-04-27", + "last_updated": "2026-04-27", "type": "chat" }, { - "id": "command-r-plus-08-2024", - "name": "Command R+", - "display_name": "Command R+", + "id": "qwen-plus", + "name": "Qwen Plus", + "display_name": "Qwen Plus", "modalities": { "input": [ "text" @@ -160899,72 +160895,121 @@ ] }, "limit": { - "context": 128000, - "output": 4000 + "context": 1000000, + "output": 32768 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": false, + "budget": { + "default": 81920, + "min": 0, + "max": 81920 + } + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } + }, + "search": { + "supported": true, + "default": false, + "forced_search": false, + "search_strategy": "turbo" }, "attachment": false, - "open_weights": true, - "knowledge": "2024-06-01", - "release_date": "2024-08-30", - "last_updated": "2024-08-30", + "open_weights": false, + "knowledge": "2024-04", + "release_date": "2024-01-25", + "last_updated": "2025-09-11", "type": "chat" }, { - "id": "tiny-aya-global", - "name": "Tiny Aya Global", - "display_name": "Tiny Aya Global", + "id": "qwen3.8-flash", + "name": "Qwen3.8 Flash", + "display_name": "Qwen3.8 Flash", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 8000, - "output": 8000 + "context": 1000000, + "output": 131072 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, - "open_weights": true, - "release_date": "2026-02-17", - "last_updated": "2026-02-17", + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "budget": { + "min": 0, + "max": 262144, + "unit": "tokens" + }, + "effort": "xhigh", + "effort_options": [ + "none", + "low", + "medium", + "xhigh" + ], + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Prefer reasoning_effort; thinking_budget is an alternative, not an additional control.", + "Omitting both controls differs from explicitly selecting xhigh, which maps to 262144 budget tokens." + ], + "budget_exclusive_with_effort": true + } + }, + "attachment": true, + "open_weights": false, + "release_date": "2026-08-26", + "last_updated": "2026-08-26", "type": "chat" - } - ] - }, - "agentrouter": { - "id": "agentrouter", - "name": "AgentRouter", - "display_name": "AgentRouter", - "api": "https://agentrouter.org/v1", - "doc": "https://agentrouter.org/docs/opencode.html", - "models": [ + }, { - "id": "deepseek-v4-flash", - "name": "DeepSeek V4 Flash", - "display_name": "DeepSeek V4 Flash", + "id": "qwen3.6-35b-a3b", + "name": "Qwen3.6 35B-A3B", + "display_name": "Qwen3.6 35B-A3B", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 384000 + "context": 262144, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -160975,41 +161020,29 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "high", - "effort_options": [ - "low", - "high", - "max" - ], "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ "thinking_blocks" - ], - "notes": [ - "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." ] } }, - "attachment": false, + "attachment": true, "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-04-24", - "last_updated": "2026-04-24", + "release_date": "2026-04-17", + "last_updated": "2026-04-17", "type": "chat" }, { - "id": "claude-opus-5", - "name": "Claude Opus 5", - "display_name": "Claude Opus 5", + "id": "qwen3.5-plus", + "name": "Qwen3.5 Plus", + "display_name": "Qwen3.5 Plus", "modalities": { "input": [ "text", "image", - "pdf" + "video" ], "output": [ "text" @@ -161017,9 +161050,9 @@ }, "limit": { "context": 1000000, - "output": 128000 + "output": 65536 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -161028,41 +161061,40 @@ "extra_capabilities": { "reasoning": { "supported": true, - "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-05", - "release_date": "2026-07-24", - "last_updated": "2026-07-24", + "knowledge": "2025-04", + "release_date": "2026-02-16", + "last_updated": "2026-02-16", "type": "chat" }, { - "id": "gpt-5.6-sol", - "name": "GPT-5.6 Sol", - "display_name": "GPT-5.6 Sol", + "id": "qwen3.5-flash", + "name": "Qwen3.5 Flash", + "display_name": "Qwen3.5 Flash", "modalities": { "input": [ "text", "image", - "pdf" + "video" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 1000000, + "output": 65536 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -161071,80 +161103,58 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", - "effort_options": [ - "none", - "low", - "medium", - "high", - "xhigh", - "max" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-02-16", - "release_date": "2026-07-09", - "last_updated": "2026-07-09", + "release_date": "2026-02-23", + "last_updated": "2026-02-23", "type": "chat" }, { - "id": "glm-5.3", - "name": "GLM-5.3", - "display_name": "GLM-5.3", + "id": "qwen-vl-plus", + "name": "Qwen-VL Plus", + "display_name": "Qwen-VL Plus", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 131072, + "output": 8192 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "effort_options": [ - "low", - "high", - "max" - ] - } + "supported": false }, - "attachment": false, - "open_weights": true, - "release_date": "2026-08-14", - "last_updated": "2026-08-14", + "attachment": true, + "open_weights": false, + "knowledge": "2024-04", + "release_date": "2024-01-25", + "last_updated": "2025-08-15", "type": "chat" }, { - "id": "claude-opus-4-8", - "name": "Claude Opus 4.8", - "display_name": "Claude Opus 4.8", + "id": "qwen3.7-flash", + "name": "Qwen3.7 Flash", + "display_name": "Qwen3.7 Flash", "modalities": { "input": [ "text", "image", - "pdf" + "video" ], "output": [ "text" @@ -161152,293 +161162,353 @@ }, "limit": { "context": 1000000, - "output": 128000 + "output": 131072 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "high", - "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" - ], "interleaved": true, "summaries": true, - "visibility": "omitted", + "visibility": "summary", "continuation": [ "thinking_blocks" - ], - "notes": [ - "Claude Opus 4.7 and newer Opus models require thinking.type = \"adaptive\" to enable thinking explicitly.", - "Manual budget_tokens requests return 400 on Claude Opus 4.7 and newer adaptive-only Opus models.", - "task_budget is separate from thinking control and should not be treated as a thinking budget." ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-01", - "release_date": "2026-05-28", - "last_updated": "2026-05-28", + "release_date": "2026-07-15", + "last_updated": "2026-07-15", "type": "chat" - } - ] - }, - "xai": { - "id": "xai", - "name": "xai", - "display_name": "xai", - "doc": "https://docs.x.ai/docs/models", - "models": [ + }, { - "id": "grok-4.20-0309-reasoning", - "name": "Grok 4.20 (Reasoning)", - "display_name": "Grok 4.20 (Reasoning)", + "id": "qwen3-8b", + "name": "Qwen3 8B", + "display_name": "Qwen3 8B", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 30000 + "context": 131072, + "output": 8192 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": true, + "budget": { + "default": 38912, + "min": 0, + "max": 38912 + } }, - "attachment": true, - "open_weights": false, - "release_date": "2026-03-09", - "last_updated": "2026-03-09", + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } + }, + "attachment": false, + "open_weights": true, + "knowledge": "2025-04", + "release_date": "2025-04", + "last_updated": "2025-04", "type": "chat" }, { - "id": "grok-imagine-image-quality", - "name": "Grok Imagine Image Quality", - "display_name": "Grok Imagine Image Quality", + "id": "qwen3-vl-30b-a3b", + "name": "Qwen3-VL 30B-A3B", + "display_name": "Qwen3-VL 30B-A3B", "modalities": { "input": [ "text", "image" ], "output": [ - "image" + "text" ] }, "limit": { - "context": 16000, - "output": 8192 + "context": 131072, + "output": 32768 }, - "temperature": false, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } }, "attachment": true, - "open_weights": false, - "release_date": "2026-04-03", - "last_updated": "2026-04-03" + "open_weights": true, + "knowledge": "2025-04", + "release_date": "2025-04", + "last_updated": "2025-04", + "type": "chat" }, { - "id": "grok-imagine-image", - "name": "Grok Imagine Image", - "display_name": "Grok Imagine Image", + "id": "qwen-turbo", + "name": "Qwen Turbo", + "display_name": "Qwen Turbo", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ - "image" + "text" ] }, "limit": { - "context": 16000, - "output": 8192 + "context": 131072, + "output": 16384 }, - "temperature": false, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": false, + "budget": { + "default": 38912, + "min": 0, + "max": 38912 + } }, - "attachment": true, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } + }, + "search": { + "supported": true, + "default": false, + "forced_search": false, + "search_strategy": "turbo" + }, + "attachment": false, "open_weights": false, - "release_date": "2026-01-28", - "last_updated": "2026-01-28" + "knowledge": "2024-04", + "release_date": "2024-11-01", + "last_updated": "2025-04-28", + "type": "chat" }, { - "id": "grok-4.5", - "name": "Grok 4.5", - "display_name": "Grok 4.5", + "id": "qwen3-235b-a22b", + "name": "Qwen3 235B A22B", + "display_name": "Qwen3 235B A22B", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 500000, - "output": 500000 + "context": 131072, + "output": 32768 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false, + "budget": { + "default": 81920, + "min": 0, + "max": 81920 + } }, "extra_capabilities": { "reasoning": { "supported": true, - "effort_options": [ - "low", - "medium", - "high", - "xhigh" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, - "attachment": true, - "open_weights": false, - "release_date": "2026-07-08", - "last_updated": "2026-07-08", + "attachment": false, + "open_weights": true, + "knowledge": "2025-04", + "release_date": "2025-04", + "last_updated": "2025-04", "type": "chat" }, { - "id": "grok-4.20-multi-agent-0309", - "name": "Grok 4.20 Multi-Agent", - "display_name": "Grok 4.20 Multi-Agent", + "id": "qwq-plus", + "name": "QwQ Plus", + "display_name": "QwQ Plus", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 30000 + "context": 131072, + "output": 8192 }, "temperature": true, "tool_call": false, "reasoning": { "supported": true, - "default": true + "default": true, + "budget": { + "default": 32768, + "min": 0, + "max": 32768 + } }, "extra_capabilities": { "reasoning": { "supported": true, - "effort_options": [ - "low", - "medium", - "high", - "xhigh" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, - "attachment": true, + "search": { + "supported": true, + "default": false, + "forced_search": false, + "search_strategy": "turbo" + }, + "attachment": false, "open_weights": false, - "release_date": "2026-03-09", - "last_updated": "2026-03-09", + "knowledge": "2024-04", + "release_date": "2025-03-05", + "last_updated": "2025-03-05", "type": "chat" }, { - "id": "grok-imagine-video-1.5-lite", - "name": "Grok Imagine Video 1.5 Lite", - "display_name": "Grok Imagine Video 1.5 Lite", + "id": "qwen3-coder-480b-a35b-instruct", + "name": "Qwen3-Coder 480B-A35B Instruct", + "display_name": "Qwen3-Coder 480B-A35B Instruct", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ - "video" + "text" ] }, "limit": { - "context": 1024, - "output": 8192 + "context": 262144, + "output": 65536 }, - "temperature": false, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { "supported": false }, - "attachment": true, - "open_weights": false, - "release_date": "2026-10-01", - "last_updated": "2026-10-01" + "attachment": false, + "open_weights": true, + "knowledge": "2025-04", + "release_date": "2025-04", + "last_updated": "2025-04", + "type": "chat" }, { - "id": "grok-imagine-video", - "name": "Grok Imagine Video", - "display_name": "Grok Imagine Video", + "id": "qwen3-next-80b-a3b-thinking", + "name": "Qwen3 Next 80B A3B Thinking", + "display_name": "Qwen3 Next 80B A3B Thinking", "modalities": { "input": [ - "text", - "image", - "video" + "text" ], "output": [ - "video" + "text" ] }, "limit": { - "context": 1024, - "output": 8192 + "context": 131072, + "output": 32768 }, - "temperature": false, + "temperature": true, "tool_call": false, "reasoning": { - "supported": false + "supported": true, + "default": true, + "budget": { + "default": 81920, + "min": 0, + "max": 81920 + } }, - "attachment": true, - "open_weights": false, - "release_date": "2026-01-28", - "last_updated": "2026-01-28" + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } + }, + "attachment": false, + "open_weights": true, + "knowledge": "2025-04", + "release_date": "2025-09", + "last_updated": "2025-09", + "type": "chat" }, { - "id": "grok-build-0.1", - "name": "Grok Build 0.1", - "display_name": "Grok Build 0.1", + "id": "qwen3-vl-235b-a22b", + "name": "Qwen3-VL 235B-A22B", + "display_name": "Qwen3-VL 235B-A22B", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 256000, - "output": 256000 + "context": 131072, + "output": 32768 }, "temperature": true, "tool_call": true, @@ -161448,101 +161518,142 @@ }, "extra_capabilities": { "reasoning": { - "supported": true + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] } }, "attachment": true, - "open_weights": false, - "release_date": "2026-04-16", - "last_updated": "2026-04-16", + "open_weights": true, + "knowledge": "2025-04", + "release_date": "2025-04", + "last_updated": "2025-04", "type": "chat" }, { - "id": "grok-imagine-video-1.5", - "name": "Grok Imagine Video 1.5", - "display_name": "Grok Imagine Video 1.5", + "id": "qvq-max", + "name": "QVQ Max", + "display_name": "QVQ Max", "modalities": { "input": [ "text", - "image", - "audio" + "image" ], "output": [ - "video" + "text" ] }, "limit": { - "context": 1024, + "context": 131072, "output": 8192 }, - "temperature": false, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, "attachment": true, "open_weights": false, - "release_date": "2026-05-30", - "last_updated": "2026-05-30" + "knowledge": "2024-04", + "release_date": "2025-03-25", + "last_updated": "2025-03-25", + "type": "chat" }, { - "id": "grok-4.7", - "name": "Grok 4.7", - "display_name": "Grok 4.7", + "id": "qwen3-14b", + "name": "Qwen3 14B", + "display_name": "Qwen3 14B", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 500000, - "output": 500000 + "context": 131072, + "output": 8192 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": true, + "budget": { + "default": 38912, + "min": 0, + "max": 38912 + } }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "low", - "medium", - "high", - "xhigh" + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, - "attachment": true, + "attachment": false, + "open_weights": true, + "knowledge": "2025-04", + "release_date": "2025-04", + "last_updated": "2025-04", + "type": "chat" + }, + { + "id": "qwen3-coder-flash", + "name": "Qwen3 Coder Flash", + "display_name": "Qwen3 Coder Flash", + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1000000, + "output": 65536 + }, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": false + }, + "attachment": false, "open_weights": false, - "knowledge": "2026-05", - "release_date": "2026-09-21", - "last_updated": "2026-09-21", + "knowledge": "2025-04", + "release_date": "2025-07-28", + "last_updated": "2025-07-28", "type": "chat" }, { - "id": "grok-4.3", - "name": "Grok 4.3", - "display_name": "Grok 4.3", + "id": "qwen3.6-27b", + "name": "Qwen3.6 27B", + "display_name": "Qwen3.6 27B", "modalities": { "input": [ "text", "image", - "pdf" + "video" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 30000 + "context": 262144, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -161553,125 +161664,127 @@ "extra_capabilities": { "reasoning": { "supported": true, - "effort_options": [ - "none", - "low", - "medium", - "high", - "xhigh" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, "attachment": true, - "open_weights": false, - "release_date": "2026-04-17", - "last_updated": "2026-04-17", + "open_weights": true, + "release_date": "2026-04-22", + "last_updated": "2026-04-22", "type": "chat" }, { - "id": "grok-4.20-0309-non-reasoning", - "name": "Grok 4.20 (Non-Reasoning)", - "display_name": "Grok 4.20 (Non-Reasoning)", + "id": "qwen3-livetranslate-flash-realtime", + "name": "Qwen3-LiveTranslate Flash Realtime", + "display_name": "Qwen3-LiveTranslate Flash Realtime", "modalities": { "input": [ "text", "image", - "pdf" + "audio", + "video" ], "output": [ - "text" + "text", + "audio" ] }, "limit": { - "context": 1000000, - "output": 30000 + "context": 53248, + "output": 4096 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { "supported": false }, "attachment": true, "open_weights": false, - "release_date": "2026-03-09", - "last_updated": "2026-03-09", - "type": "chat" + "knowledge": "2024-04", + "release_date": "2025-09-22", + "last_updated": "2025-09-22" }, { - "id": "grok-4.6", - "name": "Grok 4.6", - "display_name": "Grok 4.6", + "id": "qwen-mt-turbo", + "name": "Qwen Mt Turbo", + "display_name": "Qwen Mt Turbo", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 500000, - "output": 500000 + "context": 16384, + "output": 8192 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "effort_options": [ - "low", - "medium", - "high", - "xhigh" - ] - } + "supported": false }, - "attachment": true, + "attachment": false, "open_weights": false, - "knowledge": "2026-02-01", - "release_date": "2026-08-12", - "last_updated": "2026-08-12", + "knowledge": "2024-04", + "release_date": "2025-01", + "last_updated": "2025-01", "type": "chat" }, { - "id": "grok-3-mini-fast-beta", - "name": "Grok 3 Mini Fast Beta", - "display_name": "Grok 3 Mini Fast Beta", + "id": "qwen3-omni-flash", + "name": "Qwen3-Omni Flash", + "display_name": "Qwen3-Omni Flash", "modalities": { "input": [ - "text" + "text", + "image", + "audio", + "video" ], "output": [ - "text" + "text", + "audio" ] }, "limit": { - "context": 120000, - "output": 100000 + "context": 65536, + "output": 16384 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true, - "effort": "low" + "default": true }, - "search": { - "supported": false + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } }, - "attachment": false, + "attachment": true, + "open_weights": false, + "knowledge": "2024-04", + "release_date": "2025-09-15", + "last_updated": "2025-09-15", "type": "chat" }, { - "id": "grok-3-mini-beta", - "name": "Grok 3 Mini Beta", - "display_name": "Grok 3 Mini Beta", + "id": "qwen3-coder-30b-a3b-instruct", + "name": "Qwen3-Coder 30B-A3B Instruct", + "display_name": "Qwen3-Coder 30B-A3B Instruct", "modalities": { "input": [ "text" @@ -161681,26 +161794,25 @@ ] }, "limit": { - "context": 120000, - "output": 100000 + "context": 262144, + "output": 65536 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true, - "effort": "low" - }, - "search": { "supported": false }, "attachment": false, + "open_weights": true, + "knowledge": "2025-04", + "release_date": "2025-04", + "last_updated": "2025-04", "type": "chat" }, { - "id": "grok-3-fast-beta", - "name": "Grok 3 Fast Beta", - "display_name": "Grok 3 Fast Beta", + "id": "qwen-plus-character-ja", + "name": "Qwen Plus Character (Japanese)", + "display_name": "Qwen Plus Character (Japanese)", "modalities": { "input": [ "text" @@ -161710,80 +161822,118 @@ ] }, "limit": { - "context": 120000, - "output": 100000 + "context": 8192, + "output": 512 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { "supported": false }, - "search": { - "supported": false - }, "attachment": false, + "open_weights": false, + "knowledge": "2024-04", + "release_date": "2024-01", + "last_updated": "2024-01", "type": "chat" }, { - "id": "grok-2-vision-1212", - "name": "Grok 2 Vision 1212", - "display_name": "Grok 2 Vision 1212", + "id": "qwen-flash", + "name": "Qwen Flash", + "display_name": "Qwen Flash", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 32000, - "output": 32000 + "context": 1000000, + "output": 32768 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": false, + "budget": { + "default": 81920, + "min": 0, + "max": 81920 + } + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } }, "search": { - "supported": false + "supported": true, + "default": false, + "forced_search": false, + "search_strategy": "turbo" }, "attachment": false, + "open_weights": false, + "knowledge": "2024-04", + "release_date": "2025-07-28", + "last_updated": "2025-07-28", "type": "chat" }, { - "id": "grok-2-image-1212", - "name": "Grok 2 Image 1212", - "display_name": "Grok 2 Image 1212", + "id": "qwen3.6-plus", + "name": "Qwen3.6 Plus", + "display_name": "Qwen3.6 Plus", "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 130000, - "output": 100000 + "context": 1000000, + "output": 65536 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "search": { - "supported": false + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } }, - "attachment": false, + "attachment": true, + "open_weights": false, + "knowledge": "2025-04", + "release_date": "2026-04-02", + "last_updated": "2026-04-02", "type": "chat" }, { - "id": "grok-3-beta", - "name": "Grok 3 Beta", - "display_name": "Grok 3 Beta", + "id": "qwen3.6-max-preview", + "name": "Qwen3.6 Max Preview", + "display_name": "Qwen3.6 Max Preview", "modalities": { "input": [ "text" @@ -161793,24 +161943,37 @@ ] }, "limit": { - "context": 120000, - "output": 100000 + "context": 262144, + "output": 65536 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "search": { - "supported": false + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } }, "attachment": false, + "open_weights": false, + "knowledge": "2025-04", + "release_date": "2026-04-20", + "last_updated": "2026-04-20", "type": "chat" }, { - "id": "grok-2-1212", - "name": "Grok 2 1212", - "display_name": "Grok 2 1212", + "id": "qwen3-coder-plus", + "name": "Qwen3 Coder Plus", + "display_name": "Qwen3 Coder Plus", "modalities": { "input": [ "text" @@ -161820,33 +161983,25 @@ ] }, "limit": { - "context": 120000, - "output": 100000 + "context": 1000000, + "output": 65536 }, "temperature": true, "tool_call": true, "reasoning": { "supported": false }, - "search": { - "supported": false - }, "attachment": false, + "open_weights": true, + "knowledge": "2025-04", + "release_date": "2025-07-23", + "last_updated": "2025-07-23", "type": "chat" - } - ] - }, - "io-net": { - "id": "io-net", - "name": "IO.NET", - "display_name": "IO.NET", - "api": "https://api.intelligence.io.solutions/api/v1", - "doc": "https://io.net/docs/guides/intelligence/io-intelligence", - "models": [ + }, { - "id": "zai-org/GLM-4.6", - "name": "GLM 4.6", - "display_name": "GLM 4.6", + "id": "glm-5.2", + "name": "GLM-5.2", + "display_name": "GLM-5.2", "modalities": { "input": [ "text" @@ -161856,8 +162011,8 @@ ] }, "limit": { - "context": 200000, - "output": 4096 + "context": 1000000, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -161868,93 +162023,113 @@ "extra_capabilities": { "reasoning": { "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ - "none", - "low", - "medium", - "high" + "high", + "max" ] } }, "attachment": false, "open_weights": true, - "knowledge": "2024-10", - "release_date": "2025-09-30", - "last_updated": "2025-09-30", + "release_date": "2026-06-13", + "last_updated": "2026-06-13", "type": "chat" }, { - "id": "Qwen/Qwen3-Next-80B-A3B-Instruct", - "name": "Qwen 3 Next 80B Instruct", - "display_name": "Qwen 3 Next 80B Instruct", + "id": "qwen-omni-turbo-realtime", + "name": "Qwen-Omni Turbo Realtime", + "display_name": "Qwen-Omni Turbo Realtime", "modalities": { "input": [ - "text" + "text", + "image", + "audio" ], "output": [ - "text" + "text", + "audio" ] }, "limit": { - "context": 262144, - "output": 4096 + "context": 32768, + "output": 2048 }, "temperature": true, "tool_call": true, "reasoning": { "supported": false }, - "attachment": false, - "open_weights": true, - "knowledge": "2024-12", - "release_date": "2025-09", - "last_updated": "2025-09", - "type": "chat" + "attachment": true, + "open_weights": false, + "knowledge": "2024-04", + "release_date": "2025-05-08", + "last_updated": "2025-05-08" }, { - "id": "Qwen/Qwen2.5-VL-32B-Instruct", - "name": "Qwen 2.5 VL 32B Instruct", - "display_name": "Qwen 2.5 VL 32B Instruct", + "id": "qwen3.5-35b-a3b", + "name": "Qwen3.5 35B-A3B", + "display_name": "Qwen3.5 35B-A3B", "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 32000, - "output": 4096 + "context": 262144, + "output": 65536 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } }, "attachment": true, "open_weights": true, - "knowledge": "2024-09", - "release_date": "2024-11-01", - "last_updated": "2024-11-01", + "release_date": "2026-02-23", + "last_updated": "2026-02-23", "type": "chat" }, { - "id": "Qwen/Qwen3-235B-A22B-Thinking-2507", - "name": "Qwen 3 235B Thinking", - "display_name": "Qwen 3 235B Thinking", + "id": "qwen3.8-max", + "name": "Qwen3.8 Max", + "display_name": "Qwen3.8 Max", "modalities": { "input": [ - "text" + "text", + "image", + "video", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 4096 + "context": 1000000, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -161965,64 +162140,88 @@ "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": true, + "mode": "effort", + "budget": { + "min": 0, + "max": 262144, + "unit": "tokens" + }, + "effort": "xhigh", + "effort_options": [ + "none", + "low", + "medium", + "xhigh" + ], "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ "thinking_blocks" - ] + ], + "notes": [ + "Prefer reasoning_effort; thinking_budget is an alternative, not an additional control.", + "Omitting both controls differs from explicitly selecting xhigh, which maps to 262144 budget tokens." + ], + "budget_exclusive_with_effort": true } }, - "attachment": false, - "open_weights": true, - "knowledge": "2024-12", - "release_date": "2025-07-25", - "last_updated": "2025-07-25", + "attachment": true, + "open_weights": false, + "release_date": "2026-08-03", + "last_updated": "2026-08-03", "type": "chat" }, { - "id": "Intel/Qwen3-Coder-480B-A35B-Instruct-int4-mixed-ar", - "name": "Qwen 3 Coder 480B", - "display_name": "Qwen 3 Coder 480B", + "id": "qwen2-5-omni-7b", + "name": "Qwen2.5-Omni 7B", + "display_name": "Qwen2.5-Omni 7B", "modalities": { "input": [ - "text" + "text", + "image", + "audio", + "video" ], "output": [ - "text" + "text", + "audio" ] }, "limit": { - "context": 106000, - "output": 4096 + "context": 32768, + "output": 2048 }, "temperature": true, "tool_call": true, "reasoning": { "supported": false }, - "attachment": false, + "attachment": true, "open_weights": true, - "knowledge": "2024-12", - "release_date": "2025-07-22", - "last_updated": "2025-07-22", + "knowledge": "2024-04", + "release_date": "2024-12", + "last_updated": "2024-12", "type": "chat" }, { - "id": "openai/gpt-oss-20b", - "name": "GPT-OSS 20B", - "display_name": "GPT-OSS 20B", + "id": "qwen3.5-397b-a17b", + "name": "Qwen3.5 397B-A17B", + "display_name": "Qwen3.5 397B-A17B", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 64000, - "output": 4096 + "context": 262144, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -162033,35 +162232,37 @@ "extra_capabilities": { "reasoning": { "supported": true, - "effort_options": [ - "low", - "medium", - "high" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, - "attachment": false, + "attachment": true, "open_weights": true, - "knowledge": "2024-10", - "release_date": "2025-08-05", - "last_updated": "2025-08-05", + "release_date": "2026-02-15", + "last_updated": "2026-02-15", "type": "chat" }, { - "id": "openai/gpt-oss-120b", - "name": "GPT-OSS 120B", - "display_name": "GPT-OSS 120B", + "id": "qwen3.7-plus", + "name": "Qwen3.7 Plus", + "display_name": "Qwen3.7 Plus", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 4096 + "context": 1000000, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -162072,52 +162273,66 @@ "extra_capabilities": { "reasoning": { "supported": true, - "effort_options": [ - "low", - "medium", - "high" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, - "attachment": false, - "open_weights": true, - "knowledge": "2024-10", - "release_date": "2025-08-05", - "last_updated": "2025-08-05", + "attachment": true, + "open_weights": false, + "knowledge": "2025-04", + "release_date": "2026-06-02", + "last_updated": "2026-06-04", "type": "chat" }, { - "id": "moonshotai/Kimi-K2-Instruct-0905", - "name": "Kimi K2 Instruct", - "display_name": "Kimi K2 Instruct", + "id": "qwen3.5-122b-a10b", + "name": "Qwen3.5 122B-A10B", + "display_name": "Qwen3.5 122B-A10B", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 32768, - "output": 4096 + "context": 262144, + "output": 65536 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } + }, + "attachment": true, "open_weights": true, - "knowledge": "2024-08", - "release_date": "2025-09-05", - "last_updated": "2025-09-05", + "release_date": "2026-02-23", + "last_updated": "2026-02-23", "type": "chat" }, { - "id": "moonshotai/Kimi-K2-Thinking", - "name": "Kimi K2 Thinking", - "display_name": "Kimi K2 Thinking", + "id": "qwen3-32b", + "name": "Qwen3 32B", + "display_name": "Qwen3 32B", "modalities": { "input": [ "text" @@ -162127,14 +162342,19 @@ ] }, "limit": { - "context": 32768, - "output": 4096 + "context": 131072, + "output": 16384 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": true, + "budget": { + "default": 38912, + "min": 0, + "max": 38912 + } }, "extra_capabilities": { "reasoning": { @@ -162149,15 +162369,15 @@ }, "attachment": false, "open_weights": true, - "knowledge": "2024-08", - "release_date": "2025-11-06", - "last_updated": "2025-11-06", + "knowledge": "2025-04", + "release_date": "2025-04", + "last_updated": "2025-04", "type": "chat" }, { - "id": "mistralai/Mistral-Nemo-Instruct-2407", - "name": "Mistral Nemo Instruct 2407", - "display_name": "Mistral Nemo Instruct 2407", + "id": "qwen-max", + "name": "Qwen Max", + "display_name": "Qwen Max", "modalities": { "input": [ "text" @@ -162167,81 +162387,92 @@ ] }, "limit": { - "context": 128000, - "output": 4096 + "context": 32768, + "output": 8192 }, "temperature": true, "tool_call": true, "reasoning": { "supported": false }, + "search": { + "supported": true, + "default": false, + "forced_search": false, + "search_strategy": "turbo" + }, "attachment": false, - "open_weights": true, - "knowledge": "2024-05", - "release_date": "2024-07-18", - "last_updated": "2024-07-18", + "open_weights": false, + "knowledge": "2024-04", + "release_date": "2024-04-03", + "last_updated": "2025-01-25", "type": "chat" }, { - "id": "mistralai/Devstral-Small-2505", - "name": "Devstral Small 2505", - "display_name": "Devstral Small 2505", + "id": "qwen-omni-turbo", + "name": "Qwen-Omni Turbo", + "display_name": "Qwen-Omni Turbo", "modalities": { "input": [ - "text" + "text", + "image", + "audio", + "video" ], "output": [ - "text" + "text", + "audio" ] }, "limit": { - "context": 128000, - "output": 4096 + "context": 32768, + "output": 2048 }, "temperature": true, "tool_call": true, "reasoning": { "supported": false }, - "attachment": false, + "attachment": true, "open_weights": false, - "knowledge": "2024-12", - "release_date": "2025-05-01", - "last_updated": "2025-05-01", + "knowledge": "2024-04", + "release_date": "2025-01-19", + "last_updated": "2025-03-26", "type": "chat" }, { - "id": "mistralai/Mistral-Large-Instruct-2411", - "name": "Mistral Large Instruct 2411", - "display_name": "Mistral Large Instruct 2411", + "id": "qwen-vl-ocr", + "name": "Qwen Vl Ocr", + "display_name": "Qwen Vl Ocr", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 128000, + "context": 34096, "output": 4096 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { "supported": false }, "attachment": false, - "open_weights": true, - "knowledge": "2024-10", - "release_date": "2024-11-18", - "last_updated": "2024-11-18", + "open_weights": false, + "knowledge": "2024-04", + "release_date": "2024-10-28", + "last_updated": "2025-04-13", "type": "chat" }, { - "id": "mistralai/Magistral-Small-2506", - "name": "Magistral Small 2506", - "display_name": "Magistral Small 2506", + "id": "deepseek-v4-flash-0731", + "name": "DeepSeek V4 Flash 0731", + "display_name": "DeepSeek V4 Flash 0731", "modalities": { "input": [ "text" @@ -162251,25 +162482,42 @@ ] }, "limit": { - "context": 128000, - "output": 4096 + "context": 1000000, + "output": 384000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "effort_options": [ + "low", + "high", + "max" + ] + } }, "attachment": false, "open_weights": true, - "knowledge": "2025-01", - "release_date": "2025-06-10", - "last_updated": "2025-06-10", + "knowledge": "2025-05", + "release_date": "2026-07-31", + "last_updated": "2026-07-31", "type": "chat" }, { - "id": "meta-llama/Llama-4-Maverick-17B-128E-Instruct-FP8", - "name": "Llama 4 Maverick 17B 128E Instruct", - "display_name": "Llama 4 Maverick 17B 128E Instruct", + "id": "qwen3-vl-plus", + "name": "Qwen3 VL Plus", + "display_name": "Qwen3 VL Plus", "modalities": { "input": [ "text", @@ -162280,54 +162528,70 @@ ] }, "limit": { - "context": 430000, - "output": 4096 + "context": 262144, + "output": 32768 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": false, + "budget": { + "default": 81920, + "min": 0, + "max": 81920 + } }, - "attachment": true, - "open_weights": true, - "knowledge": "2024-12", - "release_date": "2025-04-05", - "last_updated": "2025-04-05", + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } + }, + "attachment": false, + "open_weights": false, + "knowledge": "2025-04", + "release_date": "2025-09-23", + "last_updated": "2025-09-23", "type": "chat" }, { - "id": "meta-llama/Llama-3.2-90B-Vision-Instruct", - "name": "Llama 3.2 90B Vision Instruct", - "display_name": "Llama 3.2 90B Vision Instruct", + "id": "qwen-mt-plus", + "name": "Qwen Mt Plus", + "display_name": "Qwen Mt Plus", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 16000, - "output": 4096 + "context": 16384, + "output": 8192 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { "supported": false }, - "attachment": true, - "open_weights": true, - "knowledge": "2023-12", - "release_date": "2024-09-25", - "last_updated": "2024-09-25", + "attachment": false, + "open_weights": false, + "knowledge": "2024-04", + "release_date": "2025-01", + "last_updated": "2025-01", "type": "chat" }, { - "id": "meta-llama/Llama-3.3-70B-Instruct", - "name": "Llama 3.3 70B Instruct", - "display_name": "Llama 3.3 70B Instruct", + "id": "qwen3-coder-plus-2025-09-23", + "name": "Qwen3 Coder Plus 2025 09 23", + "display_name": "Qwen3 Coder Plus 2025 09 23", "modalities": { "input": [ "text" @@ -162337,8 +162601,8 @@ ] }, "limit": { - "context": 128000, - "output": 4096 + "context": 1000000, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -162346,16 +162610,12 @@ "supported": false }, "attachment": false, - "open_weights": true, - "knowledge": "2023-12", - "release_date": "2024-12-06", - "last_updated": "2024-12-06", "type": "chat" }, { - "id": "deepseek-ai/DeepSeek-R1-0528", - "name": "DeepSeek R1", - "display_name": "DeepSeek R1", + "id": "qwen3-coder-plus-2025-07-22", + "name": "Qwen3 Coder Plus 2025 07 22", + "display_name": "Qwen3 Coder Plus 2025 07 22", "modalities": { "input": [ "text" @@ -162365,46 +162625,21 @@ ] }, "limit": { - "context": 128000, - "output": 4096 + "context": 1000000, + "output": 65536 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } + "supported": false }, "attachment": false, - "open_weights": true, - "knowledge": "2024-07", - "release_date": "2025-05-28", - "last_updated": "2025-05-28", "type": "chat" - } - ] - }, - "sakana": { - "id": "sakana", - "name": "Sakana AI", - "display_name": "Sakana AI", - "api": "https://api.sakana.ai/v1", - "doc": "https://console.sakana.ai/models", - "models": [ + }, { - "id": "fugu-ultra-20260615", - "name": "Fugu Ultra", - "display_name": "Fugu Ultra", + "id": "qwen-vl-ocr-latest", + "name": "Qwen Vl Ocr Latest", + "display_name": "Qwen Vl Ocr Latest", "modalities": { "input": [ "text", @@ -162415,33 +162650,21 @@ ] }, "limit": { - "context": 1000000, - "output": 1000000 + "context": 34096, + "output": 4096 }, - "temperature": false, - "tool_call": true, + "temperature": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "high", - "xhigh" - ] - } + "supported": false }, - "attachment": true, - "open_weights": false, - "release_date": "2026-06-15", - "last_updated": "2026-06-15", + "attachment": false, "type": "chat" }, { - "id": "fugu", - "name": "Fugu", - "display_name": "Fugu", + "id": "qvq-max-2025-05-15", + "name": "Qvq Max 2025 05 15", + "display_name": "Qvq Max 2025 05 15", "modalities": { "input": [ "text", @@ -162452,272 +162675,262 @@ ] }, "limit": { - "context": 1000000, - "output": 1000000 + "context": 131072, + "output": 8192 }, - "temperature": false, - "tool_call": true, + "temperature": true, + "tool_call": false, "reasoning": { "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "high", - "xhigh" - ] + "default": false, + "budget": { + "default": 16384, + "min": 0, + "max": 16384 } }, - "attachment": true, - "open_weights": false, - "release_date": "2026-06-15", - "last_updated": "2026-06-15", + "attachment": false, "type": "chat" }, { - "id": "sakana-namazu", - "name": "Sakana Namazu", - "display_name": "Sakana Namazu", + "id": "qwen-long", + "name": "Qwen Long", + "display_name": "Qwen Long", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 65536 + "context": 1000000, + "output": 8192 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true + "supported": false }, - "attachment": true, - "open_weights": false, - "release_date": "2026-08-03", - "last_updated": "2026-08-03", + "attachment": false, "type": "chat" }, { - "id": "fugu-ultra", - "name": "Fugu Ultra", - "display_name": "Fugu Ultra", + "id": "qwen-turbo-latest", + "name": "Qwen Turbo Latest", + "display_name": "Qwen Turbo Latest", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 1000000 + "context": 131072, + "output": 16384 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false, + "budget": { + "default": 38912, + "min": 0, + "max": 38912 + } }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "high", - "xhigh" + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, - "attachment": true, - "open_weights": false, - "release_date": "2026-06-15", - "last_updated": "2026-06-15", + "search": { + "supported": true, + "default": false, + "forced_search": false, + "search_strategy": "turbo" + }, + "attachment": false, "type": "chat" - } - ] - }, - "daoxe": { - "id": "daoxe", - "name": "DaoXE", - "display_name": "DaoXE", - "api": "https://daoxe.com/v1", - "doc": "https://daoxe.com/pricing", - "models": [ + }, { - "id": "gpt-5.5", - "name": "GPT-5.5", - "display_name": "GPT-5.5", + "id": "qwen-turbo-2024-09-19", + "name": "Qwen Turbo 2024 09 19", + "display_name": "Qwen Turbo 2024 09 19", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 131072, + "output": 8192 }, - "temperature": false, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": false + }, + "attachment": false, + "type": "chat" + }, + { + "id": "qwen-flash-2025-07-28", + "name": "Qwen Flash 2025 07 28", + "display_name": "Qwen Flash 2025 07 28", + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1000000, + "output": 32768 + }, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false, + "budget": { + "default": 81920, + "min": 0, + "max": 81920 + } }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", - "effort_options": [ - "low", - "medium", - "high", - "xhigh" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-12-01", - "release_date": "2026-04-23", - "last_updated": "2026-04-23", + "search": { + "supported": true, + "default": false, + "forced_search": false, + "search_strategy": "turbo" + }, + "attachment": false, "type": "chat" }, { - "id": "gpt-5.4", - "name": "GPT-5.4", - "display_name": "GPT-5.4", + "id": "qwen-plus-latest", + "name": "Qwen Plus Latest", + "display_name": "Qwen Plus Latest", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 1000000, + "output": 32768 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": false, + "budget": { + "default": 81920, + "min": 0, + "max": 81920 + } }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "none", - "effort_options": [ - "none", - "low", - "medium", - "high", - "xhigh" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-03-05", - "last_updated": "2026-03-05", + "search": { + "supported": true, + "default": false, + "forced_search": false, + "search_strategy": "turbo" + }, + "attachment": false, "type": "chat" }, { - "id": "grok-4.5", - "name": "Grok 4.5", - "display_name": "Grok 4.5", + "id": "qwen-plus-2024-09-19", + "name": "Qwen Plus 2024 09 19", + "display_name": "Qwen Plus 2024 09 19", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 500000, - "output": 500000 + "context": 131072, + "output": 8192 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "effort_options": [ - "low", - "medium", - "high" - ] - } + "supported": false }, - "attachment": true, - "open_weights": false, - "release_date": "2026-07-08", - "last_updated": "2026-07-08", + "attachment": false, "type": "chat" }, { - "id": "kimi-k2.5", - "name": "Kimi K2.5", - "display_name": "Kimi K2.5", + "id": "qwen-plus-2025-07-14", + "name": "Qwen Plus 2025 07 14", + "display_name": "Qwen Plus 2025 07 14", "modalities": { "input": [ - "text", - "image", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 131072, + "output": 16384 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false, + "budget": { + "default": 38912, + "min": 0, + "max": 38912 + } }, "extra_capabilities": { "reasoning": { @@ -162727,30 +162940,25 @@ "visibility": "summary", "continuation": [ "thinking_blocks" - ], - "effort_options": [ - "low", - "medium", - "high" ] } }, - "attachment": true, - "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-01-27", - "last_updated": "2026-01-27", + "search": { + "supported": true, + "default": false, + "forced_search": false, + "search_strategy": "turbo" + }, + "attachment": false, "type": "chat" }, { - "id": "claude-sonnet-4-6", - "name": "Claude Sonnet 4.6", - "display_name": "Claude Sonnet 4.6", + "id": "qwen-plus-2025-09-11", + "name": "Qwen Plus 2025 09 11", + "display_name": "Qwen Plus 2025 09 11", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" @@ -162758,261 +162966,151 @@ }, "limit": { "context": 1000000, - "output": 64000 + "output": 32768 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": false, + "budget": { + "default": 81920, + "min": 0, + "max": 81920 + } }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "mixed", - "budget": { - "min": 1024, - "unit": "tokens" - }, - "effort": "high", - "effort_options": [ - "low", - "medium", - "high", - "max" - ], "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ "thinking_blocks" - ], - "notes": [ - "Anthropic recommends adaptive thinking with effort for Claude 4.6; budget_tokens remains a deprecated compatibility path.", - "Anthropic API defaults effort to high; lower effort levels should be chosen per workload." ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-02-17", - "last_updated": "2026-03-13", + "search": { + "supported": true, + "default": false, + "forced_search": false, + "search_strategy": "turbo" + }, + "attachment": false, "type": "chat" }, { - "id": "gemini-3.1-pro-preview", - "name": "Gemini 3.1 Pro Preview", - "display_name": "Gemini 3.1 Pro Preview", + "id": "qwen-max-latest", + "name": "Qwen Max Latest", + "display_name": "Qwen Max Latest", "modalities": { "input": [ - "text", - "image", - "video", - "audio", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 131072, + "output": 8192 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true + "supported": false }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "level", - "level": "high", - "level_options": [ - "low", - "high" - ], - "summaries": true, - "visibility": "summary", - "continuation": [ - "thought_signatures" - ] - } + "search": { + "supported": true, + "default": false, + "forced_search": false, + "search_strategy": "turbo" }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-01", - "release_date": "2026-02-19", - "last_updated": "2026-02-19", + "attachment": false, "type": "chat" }, { - "id": "claude-haiku-4-5-20251001", - "name": "Claude Haiku 4.5", - "display_name": "Claude Haiku 4.5", + "id": "qwen-max-2024-09-19", + "name": "Qwen Max 2024 09 19", + "display_name": "Qwen Max 2024 09 19", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 64000 + "context": 32768, + "output": 8192 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": false + "supported": false }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "budget", - "budget": { - "min": 1024, - "unit": "tokens" - }, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", - "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." - ] - } + "search": { + "supported": true, + "default": false, + "forced_search": false, + "search_strategy": "turbo" }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-02-28", - "release_date": "2025-10-15", - "last_updated": "2025-10-15", + "attachment": false, "type": "chat" }, { - "id": "claude-opus-4-8", - "name": "Claude Opus 4.8", - "display_name": "Claude Opus 4.8", + "id": "qwen-max-2024-04-28", + "name": "Qwen Max 2024 04 28", + "display_name": "Qwen Max 2024 04 28", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 8000, + "output": 2000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": false - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "high", - "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "omitted", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Claude Opus 4.7 and newer Opus models require thinking.type = \"adaptive\" to enable thinking explicitly.", - "Manual budget_tokens requests return 400 on Claude Opus 4.7 and newer adaptive-only Opus models.", - "task_budget is separate from thinking control and should not be treated as a thinking budget." - ] - } + "supported": false }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-01", - "release_date": "2026-05-28", - "last_updated": "2026-05-28", + "attachment": false, "type": "chat" }, { - "id": "grok-4.3", - "name": "Grok 4.3", - "display_name": "Grok 4.3", + "id": "qwen-max-2024-04-03", + "name": "Qwen Max 2024 04 03", + "display_name": "Qwen Max 2024 04 03", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 30000 + "context": 8000, + "output": 2000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "effort_options": [ - "low", - "medium", - "high" - ] - } + "supported": false }, - "attachment": true, - "open_weights": false, - "release_date": "2026-04-17", - "last_updated": "2026-04-17", + "attachment": false, "type": "chat" - } - ] - }, - "fireworks-ai": { - "id": "fireworks-ai", - "name": "Fireworks AI", - "display_name": "Fireworks AI", - "api": "https://api.fireworks.ai/inference/v1/", - "doc": "https://fireworks.ai/docs/", - "models": [ + }, { - "id": "accounts/fireworks/models/qwen3p8-2p4t-a95b", - "name": "Qwen3.8 2.4T A95B", - "display_name": "Qwen3.8 2.4T A95B", + "id": "qwen-max-2025-01-25", + "name": "Qwen Max 2025 01 25", + "display_name": "Qwen Max 2025 01 25", "modalities": { "input": [ "text" @@ -163022,42 +163120,27 @@ ] }, "limit": { - "context": 262144, - "output": 131072 + "context": 131072, + "output": 8192 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true + "supported": false }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "effort_options": [ - "none", - "low", - "medium", - "xhigh" - ] - } + "search": { + "supported": true, + "default": false, + "forced_search": false, + "search_strategy": "turbo" }, "attachment": false, - "open_weights": true, - "release_date": "2026-08-12", - "last_updated": "2026-08-12", "type": "chat" }, { - "id": "accounts/fireworks/models/nemotron-3-ultra-nvfp4", - "name": "Nemotron 3 Ultra 550B A55B", - "display_name": "Nemotron 3 Ultra 550B A55B", + "id": "qwen3-max-2025-09-23", + "name": "Qwen3 Max 20250923", + "display_name": "Qwen3 Max 20250923", "modalities": { "input": [ "text" @@ -163068,43 +163151,42 @@ }, "limit": { "context": 262144, - "output": 128000 + "output": 65536 }, "temperature": true, "tool_call": true, "reasoning": { + "supported": false + }, + "search": { "supported": true, - "default": true + "default": false, + "forced_search": false, + "search_strategy": "turbo" }, "attachment": false, - "open_weights": true, - "knowledge": "2025-09", - "release_date": "2026-06-04", - "last_updated": "2026-06-04", "type": "chat" }, { - "id": "accounts/fireworks/models/deepseek-v4p1-flash", - "name": "DeepSeek V4.1 Flash", - "display_name": "DeepSeek V4.1 Flash", + "id": "qwen3-max-preview", + "name": "Qwen3 Max Preview", + "display_name": "Qwen3 Max Preview", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 384000 + "context": 262144, + "output": 65536 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true + "supported": true }, "extra_capabilities": { "reasoning": { @@ -163114,43 +163196,44 @@ "visibility": "summary", "continuation": [ "thinking_blocks" - ], - "effort_options": [ - "low", - "high", - "max" ] } }, - "attachment": true, - "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-09-10", - "last_updated": "2026-09-10", + "search": { + "supported": true, + "default": false, + "forced_search": false, + "search_strategy": "turbo" + }, + "attachment": false, "type": "chat" }, { - "id": "accounts/fireworks/models/kimi-k3", - "name": "Kimi K3", - "display_name": "Kimi K3", + "id": "qwen3-235b-a22b-thinking-2507", + "name": "Qwen3 235B A22B Thinking 2507", + "display_name": "Qwen3 235B A22B Thinking 2507", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 131072, + "output": 32768 }, - "temperature": false, - "tool_call": true, + "temperature": true, + "tool_call": false, "reasoning": { "supported": true, - "default": true + "default": true, + "budget": { + "default": 81920, + "min": 0, + "max": 81920 + } }, "extra_capabilities": { "reasoning": { @@ -163160,43 +163243,86 @@ "visibility": "summary", "continuation": [ "thinking_blocks" - ], - "effort_options": [ - "low", - "medium", - "high", - "max" ] } }, - "attachment": true, - "open_weights": true, - "release_date": "2026-07-16", - "last_updated": "2026-07-16", + "attachment": false, "type": "chat" }, { - "id": "accounts/fireworks/models/ember-1", - "name": "Ember-1", - "display_name": "Ember-1", + "id": "qwen3-235b-a22b-instruct-2507", + "name": "Qwen3 235B A22B Instruct 2507", + "display_name": "Qwen3 235B A22B Instruct 2507", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 131072, + "output": 32768 + }, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": false + }, + "attachment": false, + "type": "chat" + }, + { + "id": "qwen3-30b-a3b-instruct-2507", + "name": "Qwen3 30B A3B Instruct 2507", + "display_name": "Qwen3 30B A3B Instruct 2507", + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "output": 32768 }, "temperature": true, "tool_call": true, + "reasoning": { + "supported": false + }, + "attachment": false, + "type": "chat" + }, + { + "id": "qwen3-30b-a3b-thinking-2507", + "name": "Qwen3 30B A3B Thinking 2507", + "display_name": "Qwen3 30B A3B Thinking 2507", + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "output": 32768 + }, + "temperature": true, + "tool_call": false, "reasoning": { "supported": true, - "default": true + "default": true, + "budget": { + "default": 81920, + "min": 0, + "max": 81920 + } }, "extra_capabilities": { "reasoning": { @@ -163206,65 +163332,57 @@ "visibility": "summary", "continuation": [ "thinking_blocks" - ], - "effort_options": [ - "low", - "medium", - "high", - "max" ] } }, - "attachment": true, - "open_weights": false, - "release_date": "2026-09-22", - "last_updated": "2026-09-22", + "attachment": false, "type": "chat" }, { - "id": "accounts/fireworks/models/muse-spark-open", - "name": "Muse Spark Open", - "display_name": "Muse Spark Open", + "id": "qwen3-30b-a3b", + "name": "Qwen3 30B A3B", + "display_name": "Qwen3 30B A3B", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 131072, + "output": 32768 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false, + "budget": { + "default": 81920, + "min": 0, + "max": 81920 + } }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "low", - "medium", - "high", - "xhigh" + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, - "attachment": true, - "open_weights": true, - "knowledge": "2026-01-04", - "release_date": "2026-10-15", - "last_updated": "2026-10-15", + "attachment": false, "type": "chat" }, { - "id": "accounts/fireworks/models/minimax-m3", - "name": "MiniMax-M3", - "display_name": "MiniMax-M3", + "id": "qwen3-4b", + "name": "Qwen3 4B", + "display_name": "Qwen3 4B", "modalities": { "input": [ "text" @@ -163274,14 +163392,19 @@ ] }, "limit": { - "context": 512000, - "output": 512000 + "context": 131072, + "output": 8192 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": true, + "budget": { + "default": 38912, + "min": 0, + "max": 38912 + } }, "extra_capabilities": { "reasoning": { @@ -163291,63 +163414,57 @@ "visibility": "summary", "continuation": [ "thinking_blocks" - ], - "effort_options": [ - "low", - "medium", - "high" ] } }, "attachment": false, - "open_weights": true, - "release_date": "2026-06-01", - "last_updated": "2026-06-01", "type": "chat" }, { - "id": "accounts/fireworks/models/qwen3p8-max", - "name": "Qwen3.8 Max", - "display_name": "Qwen3.8 Max", + "id": "qwen3-1.7b", + "name": "Qwen3 1.7B", + "display_name": "Qwen3 1.7B", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 131072 + "context": 32768, + "output": 8192 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": true, + "budget": { + "default": 30720, + "min": 0, + "max": 30720 + } }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "none", - "low", - "medium", - "xhigh" + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, - "attachment": true, - "open_weights": false, - "release_date": "2026-08-03", - "last_updated": "2026-08-03", + "attachment": false, "type": "chat" }, { - "id": "accounts/fireworks/models/gpt-oss-120b", - "name": "GPT OSS 120B", - "display_name": "GPT OSS 120B", + "id": "qwen3-0.6b", + "name": "Qwen3 0.6B", + "display_name": "Qwen3 0.6B", "modalities": { "input": [ "text" @@ -163357,36 +163474,38 @@ ] }, "limit": { - "context": 131072, - "output": 32768 + "context": 32768, + "output": 8192 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": true, + "budget": { + "default": 30720, + "min": 0, + "max": 30720 + } }, "extra_capabilities": { "reasoning": { "supported": true, - "effort_options": [ - "low", - "medium", - "high" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, "attachment": false, - "open_weights": true, - "knowledge": "2024-06", - "release_date": "2025-08-05", - "last_updated": "2025-08-05", "type": "chat" }, { - "id": "accounts/fireworks/models/deepseek-v4p1-flash-1017", - "name": "DeepSeek V4.1 Flash 1017", - "display_name": "DeepSeek V4.1 Flash 1017", + "id": "qwen3-vl-plus-2025-09-23", + "name": "Qwen3 VL Plus 2025 09 23", + "display_name": "Qwen3 VL Plus 2025 09 23", "modalities": { "input": [ "text", @@ -163397,14 +163516,19 @@ ] }, "limit": { - "context": 1000000, - "output": 384000 + "context": 262144, + "output": 32768 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false, + "budget": { + "default": 81920, + "min": 0, + "max": 81920 + } }, "extra_capabilities": { "reasoning": { @@ -163414,65 +163538,82 @@ "visibility": "summary", "continuation": [ "thinking_blocks" - ], - "effort_options": [ - "low", - "high", - "max" ] } }, - "attachment": true, - "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-10-17", - "last_updated": "2026-10-17", + "attachment": false, "type": "chat" }, { - "id": "accounts/fireworks/models/inkling", - "name": "Inkling", - "display_name": "Inkling", + "id": "qwq-plus-latest", + "name": "QwQ Plus Latest", + "display_name": "QwQ Plus Latest", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 1048576 + "context": 131072, + "output": 8192 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { "supported": true, - "default": true + "default": true, + "budget": { + "default": 32768, + "min": 0, + "max": 32768 + } }, - "attachment": true, - "open_weights": true, - "release_date": "2026-07-15", - "last_updated": "2026-07-15", + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } + }, + "search": { + "supported": false + }, + "attachment": false, "type": "chat" - }, + } + ] + }, + "tempr": { + "id": "tempr", + "name": "Tempr Gateway", + "display_name": "Tempr Gateway", + "api": "https://api.temprhq.io/v1", + "doc": "https://temprhq.io/docs/gateway-reference.html", + "models": [ { - "id": "accounts/fireworks/models/glm-5p3", - "name": "GLM 5.3", - "display_name": "GLM 5.3", + "id": "xai/grok-build-0.1", + "name": "Grok Build 0.1", + "display_name": "Grok Build 0.1", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1048573, - "output": 262144 + "context": 256000, + "output": 256000 }, "temperature": true, "tool_call": true, @@ -163482,42 +163623,32 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "effort_options": [ - "low", - "high", - "max" - ] + "supported": true } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-08-14", - "last_updated": "2026-08-14", + "attachment": true, + "open_weights": false, + "release_date": "2026-04-16", + "last_updated": "2026-04-16", "type": "chat" }, { - "id": "accounts/fireworks/models/glm-5p3-flash", - "name": "GLM 5.3 Flash", - "display_name": "GLM 5.3 Flash", + "id": "xai/grok-4.7", + "name": "Grok 4.7", + "display_name": "Grok 4.7", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1048573, - "output": 131072 + "context": 500000, + "output": 500000 }, "temperature": true, "tool_call": true, @@ -163527,42 +163658,38 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], "effort_options": [ "low", + "medium", "high", - "max" + "xhigh" ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-08-26", - "last_updated": "2026-08-26", + "open_weights": false, + "knowledge": "2026-05", + "release_date": "2026-09-21", + "last_updated": "2026-09-21", "type": "chat" }, { - "id": "accounts/fireworks/models/minimax-m3p1-flash", - "name": "MiniMax-M3.1-Flash", - "display_name": "MiniMax-M3.1-Flash", + "id": "xai/grok-4.5", + "name": "Grok 4.5", + "display_name": "Grok 4.5", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 512000 + "context": 500000, + "output": 500000 }, "temperature": true, "tool_call": true, @@ -163573,42 +163700,37 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], "effort_options": [ "low", "medium", "high", - "xhigh", - "max" + "xhigh" ] } }, "attachment": true, "open_weights": false, - "release_date": "2026-10-19", - "last_updated": "2026-10-19", + "release_date": "2026-07-08", + "last_updated": "2026-07-08", "type": "chat" }, { - "id": "accounts/fireworks/models/nemotron-lightning-3p5-30b-a3b", - "name": "Nemotron 3.5 Lightning 30B A3B", - "display_name": "Nemotron 3.5 Lightning 30B A3B", + "id": "xai/grok-4.20-0309-reasoning", + "name": "Grok 4.20 (Reasoning)", + "display_name": "Grok 4.20 (Reasoning)", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 1000000, + "output": 30000 }, "temperature": true, "tool_call": true, @@ -163616,31 +163738,31 @@ "supported": true, "default": true }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-09", - "release_date": "2026-08-11", - "last_updated": "2026-08-11", + "attachment": true, + "open_weights": false, + "release_date": "2026-03-09", + "last_updated": "2026-03-09", "type": "chat" }, { - "id": "accounts/fireworks/routers/kimi-latest", - "name": "Kimi Latest", - "display_name": "Kimi Latest", + "id": "xai/grok-4.3", + "name": "Grok 4.3", + "display_name": "Grok 4.3", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 1000000, + "output": 30000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -163649,34 +163771,30 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], "effort_options": [ + "none", "low", "medium", "high", - "max" + "xhigh" ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-07-16", - "last_updated": "2026-07-16", + "open_weights": false, + "release_date": "2026-04-17", + "last_updated": "2026-04-17", "type": "chat" }, { - "id": "accounts/fireworks/routers/deepseek-v4p1-flash-1017-spot", - "name": "DeepSeek V4.1 Flash 1017 Spot", - "display_name": "DeepSeek V4.1 Flash 1017 Spot", + "id": "xai/grok-4.20-0309-non-reasoning", + "name": "Grok 4.20 (Non-Reasoning)", + "display_name": "Grok 4.20 (Non-Reasoning)", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" @@ -163684,7 +163802,36 @@ }, "limit": { "context": 1000000, - "output": 384000 + "output": 30000 + }, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": false + }, + "attachment": true, + "open_weights": false, + "release_date": "2026-03-09", + "last_updated": "2026-03-09", + "type": "chat" + }, + { + "id": "xai/grok-4.6", + "name": "Grok 4.6", + "display_name": "Grok 4.6", + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 500000, + "output": 500000 }, "temperature": true, "tool_call": true, @@ -163695,42 +163842,36 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], "effort_options": [ "low", + "medium", "high", - "max" + "xhigh" ] } }, "attachment": true, - "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-10-17", - "last_updated": "2026-10-17", + "open_weights": false, + "knowledge": "2026-02-01", + "release_date": "2026-08-12", + "last_updated": "2026-08-12", "type": "chat" }, { - "id": "accounts/fireworks/routers/deepseek-v4p1-flash-spot", - "name": "DeepSeek V4.1 Flash Spot", - "display_name": "DeepSeek V4.1 Flash Spot", + "id": "minimax/MiniMax-M2.7-highspeed", + "name": "MiniMax-M2.7-highspeed", + "display_name": "MiniMax-M2.7-highspeed", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 384000 + "context": 204800, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -163746,39 +163887,32 @@ "visibility": "summary", "continuation": [ "thinking_blocks" - ], - "effort_options": [ - "low", - "high", - "max" ] } }, - "attachment": true, + "attachment": false, "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-09-10", - "last_updated": "2026-09-10", + "release_date": "2026-03-18", + "last_updated": "2026-03-18", "type": "chat" }, { - "id": "accounts/fireworks/routers/kimi-k3-fast", - "name": "Kimi K3 Fast", - "display_name": "Kimi K3 Fast", + "id": "minimax/MiniMax-M2.7", + "name": "MiniMax-M2.7", + "display_name": "MiniMax-M2.7", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, + "context": 204800, "output": 131072 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -163792,35 +163926,31 @@ "visibility": "summary", "continuation": [ "thinking_blocks" - ], - "effort_options": [ - "low", - "medium", - "high", - "max" ] } }, - "attachment": true, + "attachment": false, "open_weights": true, - "release_date": "2026-07-16", - "last_updated": "2026-07-16", + "release_date": "2026-03-18", + "last_updated": "2026-03-18", "type": "chat" }, { - "id": "accounts/fireworks/routers/minimax-latest", - "name": "MiniMax Latest", - "display_name": "MiniMax Latest", + "id": "minimax/MiniMax-M3", + "name": "MiniMax-M3", + "display_name": "MiniMax-M3", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 512000, + "context": 1048576, "output": 512000 }, "temperature": true, @@ -163831,34 +163961,26 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "effort_options": [ - "low", - "medium", - "high" - ] + "supported": true } }, - "attachment": false, + "attachment": true, "open_weights": true, "release_date": "2026-06-01", "last_updated": "2026-06-01", "type": "chat" }, { - "id": "accounts/fireworks/routers/kimi-fast-latest", - "name": "Kimi Fast Latest", - "display_name": "Kimi Fast Latest", + "id": "google/gemini-3.5-flash", + "name": "Gemini 3.5 Flash", + "display_name": "Gemini 3.5 Flash", "modalities": { "input": [ "text", - "image" + "image", + "video", + "audio", + "pdf" ], "output": [ "text" @@ -163866,9 +163988,9 @@ }, "limit": { "context": 1048576, - "output": 131072 + "output": 65536 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -163877,30 +163999,33 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, + "default_enabled": true, + "mode": "level", + "level": "high", + "level_options": [ + "minimal", + "low", + "medium", + "high" + ], "summaries": true, "visibility": "summary", "continuation": [ - "thinking_blocks" - ], - "effort_options": [ - "low", - "medium", - "high", - "max" + "thought_signatures" ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-07-16", - "last_updated": "2026-07-16", + "open_weights": false, + "knowledge": "2025-01", + "release_date": "2026-05-19", + "last_updated": "2026-05-19", "type": "chat" }, { - "id": "accounts/fireworks/routers/glm-flash-latest", - "name": "GLM Flash Latest (GLM 5.3 Flash)", - "display_name": "GLM Flash Latest (GLM 5.3 Flash)", + "id": "google/gemma-4-26b-a4b-it", + "name": "Gemma 4 26B A4B IT", + "display_name": "Gemma 4 26B A4B IT", "modalities": { "input": [ "text", @@ -163911,8 +164036,8 @@ ] }, "limit": { - "context": 1048573, - "output": 131072 + "context": 262144, + "output": 32768 }, "temperature": true, "tool_call": true, @@ -163922,34 +164047,26 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "effort_options": [ - "low", - "high", - "max" - ] + "supported": true } }, "attachment": true, "open_weights": true, - "release_date": "2026-08-26", - "last_updated": "2026-08-26", + "release_date": "2026-04-02", + "last_updated": "2026-04-02", "type": "chat" }, { - "id": "accounts/fireworks/routers/kimi-k3-spot", - "name": "Kimi K3 Spot", - "display_name": "Kimi K3 Spot", + "id": "google/gemini-flash-latest", + "name": "Gemini Flash Latest", + "display_name": "Gemini Flash Latest", "modalities": { "input": [ "text", - "image" + "image", + "video", + "audio", + "pdf" ], "output": [ "text" @@ -163957,9 +164074,9 @@ }, "limit": { "context": 1048576, - "output": 131072 + "output": 65536 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -163967,43 +164084,39 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], "effort_options": [ "low", "medium", - "high", - "max" + "high" ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-07-16", - "last_updated": "2026-07-16", + "open_weights": false, + "knowledge": "2026-03", + "release_date": "2026-08-13", + "last_updated": "2026-08-13", "type": "chat" }, { - "id": "accounts/fireworks/routers/deepseek-flash-latest", - "name": "DeepSeek Flash Latest", - "display_name": "DeepSeek Flash Latest", + "id": "google/gemini-3.6-flash", + "name": "Gemini 3.6 Flash", + "display_name": "Gemini 3.6 Flash", "modalities": { "input": [ "text", - "image" + "image", + "video", + "audio", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 384000 + "context": 1048576, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -164014,41 +164127,48 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, + "default_enabled": true, + "mode": "level", + "level": "high", + "level_options": [ + "minimal", + "low", + "medium", + "high" + ], "summaries": true, "visibility": "summary", "continuation": [ - "thinking_blocks" - ], - "effort_options": [ - "low", - "high", - "max" + "thought_signatures" ] } }, "attachment": true, - "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-10-17", - "last_updated": "2026-10-17", + "open_weights": false, + "knowledge": "2026-03", + "release_date": "2026-07-21", + "last_updated": "2026-07-21", "type": "chat" }, { - "id": "accounts/fireworks/routers/glm-5p3-fast", - "name": "GLM 5.3 Fast", - "display_name": "GLM 5.3 Fast", + "id": "google/gemini-3.8-flash", + "name": "Gemini 3.8 Flash", + "display_name": "Gemini 3.8 Flash", "modalities": { "input": [ - "text" + "text", + "image", + "video", + "audio", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1048572, - "output": 262144 + "context": 1048576, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -164059,41 +164179,47 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, + "default_enabled": true, + "mode": "level", + "level": "high", + "level_options": [ + "minimal", + "low", + "medium", + "high" + ], "summaries": true, "visibility": "summary", "continuation": [ - "thinking_blocks" - ], - "effort_options": [ - "low", - "high", - "max" + "thought_signatures" ] } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-08-14", - "last_updated": "2026-08-14", + "attachment": true, + "open_weights": false, + "release_date": "2026-09-02", + "last_updated": "2026-09-02", "type": "chat" }, { - "id": "accounts/fireworks/routers/qwen-max-latest", - "name": "Qwen Max Latest (Qwen3.8 Max)", - "display_name": "Qwen Max Latest (Qwen3.8 Max)", + "id": "google/gemini-flash-lite-latest", + "name": "Gemini Flash-Lite Latest", + "display_name": "Gemini Flash-Lite Latest", "modalities": { "input": [ "text", - "image" + "image", + "video", + "audio", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 131072 + "context": 1048576, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -164104,34 +164230,39 @@ "extra_capabilities": { "reasoning": { "effort_options": [ - "none", + "minimal", "low", "medium", - "xhigh" + "high" ] } }, "attachment": true, "open_weights": false, - "release_date": "2026-08-03", - "last_updated": "2026-08-03", + "knowledge": "2026-03", + "release_date": "2026-07-21", + "last_updated": "2026-07-21", "type": "chat" }, { - "id": "accounts/fireworks/routers/glm-latest", - "name": "GLM Latest", - "display_name": "GLM Latest", + "id": "google/gemini-3.1-pro-preview", + "name": "Gemini 3.1 Pro Preview", + "display_name": "Gemini 3.1 Pro Preview", "modalities": { "input": [ - "text" + "text", + "image", + "video", + "audio", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1048573, - "output": 262144 + "context": 1048576, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -164142,29 +164273,31 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, + "default_enabled": true, + "mode": "level", + "level": "high", + "level_options": [ + "low", + "high" + ], "summaries": true, "visibility": "summary", "continuation": [ - "thinking_blocks" - ], - "effort_options": [ - "low", - "high", - "max" + "thought_signatures" ] } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-08-14", - "last_updated": "2026-08-14", + "attachment": true, + "open_weights": false, + "knowledge": "2025-01", + "release_date": "2026-02-19", + "last_updated": "2026-02-19", "type": "chat" }, { - "id": "accounts/fireworks/routers/glm-fast-latest", - "name": "GLM 5.3 Fast (Latest)", - "display_name": "GLM 5.3 Fast (Latest)", + "id": "google/gemini-embedding-2", + "name": "Gemini Embedding 2", + "display_name": "Gemini Embedding 2", "modalities": { "input": [ "text" @@ -164174,41 +164307,25 @@ ] }, "limit": { - "context": 1048572, - "output": 262144 + "context": 8192, + "output": 1 }, - "temperature": true, - "tool_call": true, + "temperature": false, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "effort_options": [ - "low", - "high", - "max" - ] - } + "supported": false }, "attachment": false, - "open_weights": true, - "release_date": "2026-08-14", - "last_updated": "2026-08-14", - "type": "chat" + "open_weights": false, + "knowledge": "2025-11", + "release_date": "2026-04-22", + "last_updated": "2026-04-22", + "type": "embedding" }, { - "id": "accounts/fireworks/routers/glm-5p3-spot", - "name": "GLM 5.3 Spot", - "display_name": "GLM 5.3 Spot", + "id": "google/gemini-embedding-001", + "name": "Gemini Embedding 001", + "display_name": "Gemini Embedding 001", "modalities": { "input": [ "text" @@ -164218,63 +164335,40 @@ ] }, "limit": { - "context": 1048573, - "output": 262144 + "context": 2048, + "output": 1 }, - "temperature": true, - "tool_call": true, + "temperature": false, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "effort_options": [ - "low", - "high", - "max" - ] - } + "supported": false }, "attachment": false, - "open_weights": true, - "release_date": "2026-08-14", - "last_updated": "2026-08-14", - "type": "chat" - } - ] - }, - "melious": { - "id": "melious", - "name": "Melious", - "display_name": "Melious", - "api": "https://api.melious.ai/v1", - "doc": "https://melious.ai/docs/reference/models", - "models": [ + "open_weights": false, + "knowledge": "2025-05", + "release_date": "2025-05-20", + "last_updated": "2025-05-20", + "type": "embedding" + }, { - "id": "glm-5.3-flash", - "name": "GLM-5.3-Flash", - "display_name": "GLM-5.3-Flash", + "id": "google/gemini-3.7-flash", + "name": "Gemini 3.7 Flash", + "display_name": "Gemini 3.7 Flash", "modalities": { "input": [ "text", "image", - "video" + "video", + "audio", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 1048576, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -164285,29 +164379,33 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, + "default_enabled": true, + "mode": "level", + "level": "high", + "level_options": [ + "minimal", + "low", + "medium", + "high" + ], "summaries": true, "visibility": "summary", "continuation": [ - "thinking_blocks" - ], - "effort_options": [ - "low", - "high", - "max" + "thought_signatures" ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-08-26", - "last_updated": "2026-08-26", + "open_weights": false, + "knowledge": "2026-03", + "release_date": "2026-08-13", + "last_updated": "2026-08-13", "type": "chat" }, { - "id": "deepseek-v4.1-flash-1017", - "name": "DeepSeek V4.1 Flash 1017", - "display_name": "DeepSeek V4.1 Flash 1017", + "id": "google/gemma-4-31b-it", + "name": "Gemma 4 31B IT", + "display_name": "Gemma 4 31B IT", "modalities": { "input": [ "text", @@ -164318,8 +164416,8 @@ ] }, "limit": { - "context": 1000000, - "output": 384000 + "context": 262144, + "output": 32768 }, "temperature": true, "tool_call": true, @@ -164329,47 +164427,37 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "effort_options": [ - "none", - "low", - "high", - "max" - ] + "supported": true } }, "attachment": true, "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-10-17", - "last_updated": "2026-10-17", + "knowledge": "2025-01", + "release_date": "2026-04-02", + "last_updated": "2026-04-02", "type": "chat" }, { - "id": "kimi-k3", - "name": "Kimi K3", - "display_name": "Kimi K3", + "id": "google/gemini-3.1-flash-lite", + "name": "Gemini 3.1 Flash Lite", + "display_name": "Gemini 3.1 Flash Lite", "modalities": { "input": [ "text", "image", - "video" + "video", + "audio", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 1048576, + "output": 65536 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -164378,43 +164466,40 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], "effort_options": [ - "none", + "minimal", "low", - "high", - "max" + "medium", + "high" ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-07-16", - "last_updated": "2026-07-16", + "open_weights": false, + "knowledge": "2025-01", + "release_date": "2026-05-07", + "last_updated": "2026-05-07", "type": "chat" }, { - "id": "kimi-k2.6", - "name": "Kimi K2.6", - "display_name": "Kimi K2.6", + "id": "google/gemini-3.1-pro-preview-customtools", + "name": "Gemini 3.1 Pro Preview Custom Tools", + "display_name": "Gemini 3.1 Pro Preview Custom Tools", "modalities": { "input": [ "text", "image", - "video" + "video", + "audio", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 256000, - "output": 256000 + "context": 1048576, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -164425,37 +164510,46 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, + "default_enabled": true, + "mode": "level", + "level": "high", + "level_options": [ + "low", + "high" + ], "summaries": true, "visibility": "summary", "continuation": [ - "thinking_blocks" + "thought_signatures" ] } }, "attachment": true, - "open_weights": true, + "open_weights": false, "knowledge": "2025-01", - "release_date": "2026-04-20", - "last_updated": "2026-04-20", + "release_date": "2026-02-19", + "last_updated": "2026-02-19", "type": "chat" }, { - "id": "deepseek-v4.1-flash", - "name": "DeepSeek V4.1 Flash", - "display_name": "DeepSeek V4.1 Flash", + "id": "google/gemini-3-flash-preview", + "name": "Gemini 3 Flash Preview", + "display_name": "Gemini 3 Flash Preview", "modalities": { "input": [ "text", - "image" + "image", + "video", + "audio", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 384000 + "context": 1048576, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -164466,42 +164560,48 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, + "default_enabled": true, + "mode": "level", + "level": "high", + "level_options": [ + "minimal", + "low", + "medium", + "high" + ], "summaries": true, "visibility": "summary", "continuation": [ - "thinking_blocks" - ], - "effort_options": [ - "none", - "low", - "high", - "max" + "thought_signatures" ] } }, "attachment": true, - "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-09-10", - "last_updated": "2026-09-10", + "open_weights": false, + "knowledge": "2025-01", + "release_date": "2025-12-17", + "last_updated": "2025-12-17", "type": "chat" }, { - "id": "deepseek-v4-flash-0731", - "name": "DeepSeek V4 Flash 0731", - "display_name": "DeepSeek V4 Flash 0731", + "id": "google/gemini-3.5-flash-lite", + "name": "Gemini 3.5 Flash Lite", + "display_name": "Gemini 3.5 Flash Lite", "modalities": { "input": [ - "text" + "text", + "image", + "video", + "audio", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 384000 + "context": 1048576, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -164512,35 +164612,30 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], "effort_options": [ - "none", + "minimal", "low", - "high", - "max" + "medium", + "high" ] } }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-07-31", - "last_updated": "2026-07-31", + "attachment": true, + "open_weights": false, + "knowledge": "2026-03", + "release_date": "2026-07-21", + "last_updated": "2026-07-21", "type": "chat" }, { - "id": "kimi-k2.7-code", - "name": "Kimi K2.7 Code", - "display_name": "Kimi K2.7 Code", + "id": "xiaomi-mimo/mimo-v2.6-pro", + "name": "MiMo-V2.6-Pro", + "display_name": "MiMo-V2.6-Pro", "modalities": { "input": [ "text", "image", + "audio", "video" ], "output": [ @@ -164548,47 +164643,38 @@ ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 1048576, + "output": 131072 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } - }, "attachment": true, "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-06-12", - "last_updated": "2026-06-12", + "release_date": "2026-09-22", + "last_updated": "2026-09-22", "type": "chat" }, { - "id": "glm-5.3", - "name": "GLM-5.3", - "display_name": "GLM-5.3", + "id": "xiaomi-mimo/mimo-v2.6-flash", + "name": "MiMo-V2.6-Flash", + "display_name": "MiMo-V2.6-Flash", "modalities": { "input": [ - "text" + "text", + "image", + "audio", + "video" ], "output": [ "text" ] }, "limit": { - "context": 1000000, + "context": 1048576, "output": 131072 }, "temperature": true, @@ -164597,52 +164683,30 @@ "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "effort_options": [ - "low", - "high", - "max" - ] - } - }, - "attachment": false, + "attachment": true, "open_weights": true, - "release_date": "2026-08-14", - "last_updated": "2026-08-14", + "release_date": "2026-09-22", + "last_updated": "2026-09-22", "type": "chat" - } - ] - }, - "dinference": { - "id": "dinference", - "name": "DInference", - "display_name": "DInference", - "api": "https://api.dinference.com/v1", - "doc": "https://dinference.com", - "models": [ + }, { - "id": "glm-4.7", - "name": "GLM-4.7", - "display_name": "GLM-4.7", + "id": "xiaomi-mimo/mimo-v2.6-pro-ultraspeed", + "name": "MiMo-V2.6-Pro-UltraSpeed", + "display_name": "MiMo-V2.6-Pro-UltraSpeed", "modalities": { "input": [ - "text" + "text", + "image", + "audio", + "video" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 128000 + "context": 1048576, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -164650,105 +164714,94 @@ "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } - }, - "attachment": false, + "attachment": true, "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-12-22", - "last_updated": "2025-12-22", + "release_date": "2026-09-21", + "last_updated": "2026-09-21", "type": "chat" }, { - "id": "minimax-m2.5", - "name": "MiniMax-M2.5", - "display_name": "MiniMax-M2.5", + "id": "mistral/mistral-large-2512", + "name": "Mistral Large 3", + "display_name": "Mistral Large 3", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 32000 + "context": 262144, + "output": 262144 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true - } + "supported": false }, - "attachment": false, + "attachment": true, "open_weights": true, - "release_date": "2026-02-12", - "last_updated": "2026-02-12", + "knowledge": "2024-11", + "release_date": "2025-12-02", + "last_updated": "2025-12-02", "type": "chat" }, { - "id": "gpt-oss-120b", - "name": "GPT OSS 120B", - "display_name": "GPT OSS 120B", + "id": "mistral/mistral-medium-latest", + "name": "Mistral Medium (latest)", + "display_name": "Mistral Medium (latest)", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 32768 + "context": 262144, + "output": 262144 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true + "supported": true, + "default": true }, "extra_capabilities": { "reasoning": { - "supported": true + "effort_options": [ + "none", + "high" + ] } }, - "attachment": false, + "attachment": true, "open_weights": true, - "knowledge": "2024-06", - "release_date": "2025-08-05", - "last_updated": "2025-08-05", + "release_date": "2026-04-29", + "last_updated": "2026-04-29", "type": "chat" }, { - "id": "glm-5.1", - "name": "GLM-5.1", - "display_name": "GLM-5.1", + "id": "mistral/mistral-small-latest", + "name": "Mistral Small (latest)", + "display_name": "Mistral Small (latest)", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 128000 + "context": 256000, + "output": 256000 }, "temperature": true, "tool_call": true, @@ -164758,36 +164811,35 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "none", + "high" ] } }, - "attachment": false, + "attachment": true, "open_weights": true, - "release_date": "2026-04-07", - "last_updated": "2026-04-07", + "knowledge": "2025-06", + "release_date": "2026-03-16", + "last_updated": "2026-03-16", "type": "chat" }, { - "id": "glm-5", - "name": "GLM-5", - "display_name": "GLM-5", + "id": "mistral/mistral-medium-2604", + "name": "Mistral Medium 3.5", + "display_name": "Mistral Medium 3.5", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 128000 + "context": 262144, + "output": 262144 }, "temperature": true, "tool_call": true, @@ -164797,23 +164849,20 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "none", + "high" ] } }, - "attachment": false, + "attachment": true, "open_weights": true, - "release_date": "2026-02-12", - "last_updated": "2026-02-12", + "release_date": "2026-04-29", + "last_updated": "2026-04-29", "type": "chat" }, { - "id": "glm-5.2", + "id": "mistral/zai-glm-5-2", "name": "GLM-5.2", "display_name": "GLM-5.2", "modalities": { @@ -164826,7 +164875,7 @@ }, "limit": { "context": 1000000, - "output": 128000 + "output": 131072 }, "temperature": true, "tool_call": true, @@ -164836,12 +164885,10 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "none", + "high", + "max" ] } }, @@ -164850,73 +164897,78 @@ "release_date": "2026-06-13", "last_updated": "2026-06-13", "type": "chat" - } - ] - }, - "xiaomi": { - "id": "xiaomi", - "name": "Xiaomi", - "display_name": "Xiaomi", - "api": "https://api.xiaomimimo.com/v1", - "doc": "https://platform.xiaomimimo.com/#/docs", - "models": [ + }, { - "id": "mimo-v2.6-pro", - "name": "MiMo-V2.6-Pro", - "display_name": "MiMo-V2.6-Pro", + "id": "mistral/mistral-embed", + "name": "Mistral Embed", + "display_name": "Mistral Embed", + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 8000, + "output": 3072 + }, + "temperature": false, + "tool_call": false, + "reasoning": { + "supported": false + }, + "attachment": false, + "open_weights": false, + "release_date": "2023-12-11", + "last_updated": "2023-12-11", + "type": "embedding" + }, + { + "id": "mistral/voxtral-small-latest", + "name": "Voxtral Small (latest)", + "display_name": "Voxtral Small (latest)", "modalities": { "input": [ "text", - "image", - "audio", - "video" + "audio" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 32000, + "output": 32000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } + "supported": false }, "attachment": true, "open_weights": true, - "release_date": "2026-09-22", - "last_updated": "2026-09-22", + "release_date": "2025-07-15", + "last_updated": "2025-07-15", "type": "chat" }, { - "id": "mimo-v2.5-pro-ultraspeed", - "name": "MiMo-V2.5-Pro-UltraSpeed", - "display_name": "MiMo-V2.5-Pro-UltraSpeed", + "id": "mistral/mistral-small-2603", + "name": "Mistral Small 4", + "display_name": "Mistral Small 4", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 256000, + "output": 256000 }, "temperature": true, "tool_call": true, @@ -164926,39 +164978,33 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "none", + "high" ] } }, - "attachment": false, + "attachment": true, "open_weights": true, - "knowledge": "2024-12", - "release_date": "2026-06-08", - "last_updated": "2026-06-09", + "knowledge": "2025-06", + "release_date": "2026-03-16", + "last_updated": "2026-03-16", "type": "chat" }, { - "id": "mimo-v2.6-flash", - "name": "MiMo-V2.6-Flash", - "display_name": "MiMo-V2.6-Flash", + "id": "mistral/zai-glm-5-3", + "name": "GLM-5.3", + "display_name": "GLM-5.3", "modalities": { "input": [ - "text", - "image", - "audio", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, + "context": 1000000, "output": 131072 }, "temperature": true, @@ -164969,110 +165015,79 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "low", + "high", + "max" ] } }, - "attachment": true, + "attachment": false, "open_weights": true, - "release_date": "2026-09-22", - "last_updated": "2026-09-22", + "release_date": "2026-08-14", + "last_updated": "2026-08-14", "type": "chat" }, { - "id": "mimo-v2.6-pro-ultraspeed", - "name": "MiMo-V2.6-Pro-UltraSpeed", - "display_name": "MiMo-V2.6-Pro-UltraSpeed", + "id": "mistral/mistral-large-latest", + "name": "Mistral Large (latest)", + "display_name": "Mistral Large (latest)", "modalities": { "input": [ "text", - "image", - "audio", - "video" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 262144, + "output": 262144 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } + "supported": false }, "attachment": true, "open_weights": true, - "release_date": "2026-09-21", - "last_updated": "2026-09-21", + "knowledge": "2024-11", + "release_date": "2024-11-01", + "last_updated": "2025-12-02", "type": "chat" }, { - "id": "mimo-v2.5", - "name": "MiMo-V2.5", - "display_name": "MiMo-V2.5", + "id": "groq/allam-2-7b", + "name": "ALLaM-2-7b", + "display_name": "ALLaM-2-7b", "modalities": { "input": [ - "text", - "image", - "audio", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 4096, + "output": 4096 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } + "supported": false }, - "attachment": true, + "attachment": false, "open_weights": true, - "knowledge": "2024-12", - "release_date": "2026-04-22", - "last_updated": "2026-04-22", + "release_date": "2025-01-23", + "last_updated": "2025-01-23", "type": "chat" }, { - "id": "mimo-v2.5-pro", - "name": "MiMo-V2.5-Pro", - "display_name": "MiMo-V2.5-Pro", + "id": "groq/openai/gpt-oss-120b", + "name": "GPT OSS 120B", + "display_name": "GPT OSS 120B", "modalities": { "input": [ "text" @@ -165082,8 +165097,8 @@ ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 131072, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -165094,34 +165109,24 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "low", + "medium", + "high" ] } }, "attachment": false, "open_weights": true, - "knowledge": "2024-12", - "release_date": "2026-04-22", - "last_updated": "2026-04-22", + "knowledge": "2024-06", + "release_date": "2025-08-05", + "last_updated": "2025-08-05", "type": "chat" - } - ] - }, - "hyper": { - "id": "hyper", - "name": "Charm Hyper", - "display_name": "Charm Hyper", - "api": "https://hyper.charm.land/v1", - "doc": "https://hyper.charm.land", - "models": [ + }, { - "id": "qwen3.7-max", - "name": "Qwen3.7 Max", - "display_name": "Qwen3.7 Max", + "id": "groq/openai/gpt-oss-20b", + "name": "GPT OSS 20B", + "display_name": "GPT OSS 20B", "modalities": { "input": [ "text" @@ -165131,8 +165136,8 @@ ] }, "limit": { - "context": 1000000, - "output": 64000 + "context": 131072, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -165144,8 +165149,6 @@ "reasoning": { "supported": true, "effort_options": [ - "none", - "minimal", "low", "medium", "high" @@ -165153,15 +165156,16 @@ } }, "attachment": false, - "open_weights": false, - "release_date": "2026-05-21", - "last_updated": "2026-05-21", + "open_weights": true, + "knowledge": "2024-06", + "release_date": "2025-08-05", + "last_updated": "2025-08-05", "type": "chat" }, { - "id": "glm-5.3-flash", - "name": "GLM-5.3-Flash", - "display_name": "GLM-5.3-Flash", + "id": "groq/qwen/qwen3.8-27b", + "name": "Qwen3.8 27B", + "display_name": "Qwen3.8 27B", "modalities": { "input": [ "text", @@ -165172,8 +165176,8 @@ ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 131042, + "output": 16384 }, "temperature": true, "tool_call": true, @@ -165183,195 +165187,252 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, "effort_options": [ + "none", "low", - "high", - "max" + "medium", + "high" ] } }, "attachment": true, "open_weights": true, - "release_date": "2026-08-26", - "last_updated": "2026-08-26", + "release_date": "2026-08-14", + "last_updated": "2026-08-14", "type": "chat" }, { - "id": "qwen3.8-27b", - "name": "Qwen3.8 27B", - "display_name": "Qwen3.8 27B", + "id": "anthropic/claude-haiku-4-5-20251001", + "name": "Claude Haiku 4.5", + "display_name": "Claude Haiku 4.5", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 200000, + "output": 64000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "none", - "minimal", - "low", - "medium", - "high" + "supported": true, + "default_enabled": false, + "mode": "budget", + "budget": { + "min": 1024, + "unit": "tokens" + }, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", + "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-08-14", - "last_updated": "2026-08-14", + "open_weights": false, + "knowledge": "2025-02-28", + "release_date": "2025-10-15", + "last_updated": "2025-10-15", "type": "chat" }, { - "id": "deepseek-v4.1-flash-1017", - "name": "DeepSeek V4.1 Flash 1017", - "display_name": "DeepSeek V4.1 Flash 1017", + "id": "anthropic/claude-sonnet-4-5-20250929", + "name": "Claude Sonnet 4.5", + "display_name": "Claude Sonnet 4.5", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 262144 + "context": 200000, + "output": 64000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "low", - "high", - "max" + "supported": true, + "default_enabled": false, + "mode": "budget", + "budget": { + "min": 1024, + "unit": "tokens" + }, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", + "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." ] } }, "attachment": true, - "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-10-17", - "last_updated": "2026-10-17", + "open_weights": false, + "knowledge": "2025-07-31", + "release_date": "2025-09-29", + "last_updated": "2025-09-29", "type": "chat" }, { - "id": "kimi-k3", - "name": "Kimi K3", - "display_name": "Kimi K3", + "id": "anthropic/claude-opus-4-7", + "name": "Claude Opus 4.7", + "display_name": "Claude Opus 4.7", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 16000 + "context": 1000000, + "output": 128000 }, "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": false, + "mode": "effort", + "effort": "high", "effort_options": [ "low", + "medium", "high", + "xhigh", "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "omitted", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Claude Opus 4.7 and newer Opus models require thinking.type = \"adaptive\" to enable thinking explicitly.", + "Manual budget_tokens requests return 400 on Claude Opus 4.7 and newer adaptive-only Opus models.", + "task_budget is separate from thinking control and should not be treated as a thinking budget." ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-07-16", - "last_updated": "2026-07-16", + "open_weights": false, + "knowledge": "2026-01-31", + "release_date": "2026-04-16", + "last_updated": "2026-04-16", "type": "chat" }, { - "id": "qwen3.8-2.4t-a95b", - "name": "Qwen3.8 2.4T A95B", - "display_name": "Qwen3.8 2.4T A95B", + "id": "anthropic/claude-haiku-4-5", + "name": "Claude Haiku 4.5 (latest)", + "display_name": "Claude Haiku 4.5 (latest)", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 200000, + "output": 64000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, - "effort_options": [ - "minimal", - "low", - "medium", - "high" + "default_enabled": false, + "mode": "budget", + "budget": { + "min": 1024, + "unit": "tokens" + }, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", + "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." ] } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-08-12", - "last_updated": "2026-08-12", + "attachment": true, + "open_weights": false, + "knowledge": "2025-02-28", + "release_date": "2025-10-15", + "last_updated": "2025-10-15", "type": "chat" }, { - "id": "minimax-m3", - "name": "MiniMax-M3", - "display_name": "MiniMax-M3", + "id": "anthropic/claude-sonnet-5", + "name": "Claude Sonnet 5", + "display_name": "Claude Sonnet 5", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 512000, - "output": 512000 + "context": 1000000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -165383,33 +165444,38 @@ "effort_options": [ "low", "medium", - "high" + "high", + "xhigh", + "max" ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-06-01", - "last_updated": "2026-06-01", + "open_weights": false, + "knowledge": "2026-01-31", + "release_date": "2026-06-30", + "last_updated": "2026-06-30", "type": "chat" }, { - "id": "minimax-m2.7", - "name": "MiniMax-M2.7", - "display_name": "MiniMax-M2.7", + "id": "anthropic/claude-fable-5", + "name": "Claude Fable 5", + "display_name": "Claude Fable 5", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 262100, - "output": 6553 + "context": 1000000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -165418,178 +165484,271 @@ "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "high", + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" + ], "interleaved": true, "summaries": true, - "visibility": "summary", + "visibility": "omitted", "continuation": [ "thinking_blocks" + ], + "notes": [ + "Adaptive thinking is always on for Claude Fable 5 and Claude Mythos 5; thinking.type = \"disabled\" is rejected.", + "Manual budget_tokens requests return 400 on Claude Fable 5 and Claude Mythos 5.", + "thinking.display defaults to omitted; set display to summarized to receive readable thinking summaries." ] } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-03-18", - "last_updated": "2026-03-18", + "attachment": true, + "open_weights": false, + "knowledge": "2026-01-31", + "release_date": "2026-06-09", + "last_updated": "2026-06-09", "type": "chat" }, { - "id": "qwen3.8-max", - "name": "Qwen3.8 Max Preview", - "display_name": "Qwen3.8 Max Preview", + "id": "anthropic/claude-sonnet-4-5", + "name": "Claude Sonnet 4.5 (latest)", + "display_name": "Claude Sonnet 4.5 (latest)", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 65536 + "context": 200000, + "output": 64000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { - "supported": true + "supported": true, + "default_enabled": false, + "mode": "budget", + "budget": { + "min": 1024, + "unit": "tokens" + }, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", + "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." + ] } }, "attachment": true, "open_weights": false, - "release_date": "2026-07-19", - "last_updated": "2026-07-19", + "knowledge": "2025-07-31", + "release_date": "2025-09-29", + "last_updated": "2025-09-29", "type": "chat" }, { - "id": "gpt-oss-120b", - "name": "GPT OSS 120B", - "display_name": "GPT OSS 120B", + "id": "anthropic/claude-opus-4-5", + "name": "Claude Opus 4.5 (latest)", + "display_name": "Claude Opus 4.5 (latest)", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 13107 + "context": 200000, + "output": 64000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": false, + "mode": "mixed", + "budget": { + "min": 1024, + "unit": "tokens" + }, + "effort": "high", "effort_options": [ "low", "medium", "high" + ], + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Claude Opus 4.5 uses manual thinking.type = \"enabled\" with budget_tokens; effort can be used alongside the thinking budget.", + "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header." ] } }, - "attachment": false, - "open_weights": true, - "knowledge": "2024-06", - "release_date": "2025-08-05", - "last_updated": "2025-08-05", + "attachment": true, + "open_weights": false, + "knowledge": "2025-05", + "release_date": "2025-11-24", + "last_updated": "2025-11-24", "type": "chat" }, { - "id": "deepseek-v4.1-flash", - "name": "DeepSeek V4.1 Flash", - "display_name": "DeepSeek V4.1 Flash", + "id": "anthropic/claude-sonnet-4-6", + "name": "Claude Sonnet 4.6", + "display_name": "Claude Sonnet 4.6", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 262144 + "context": 1000000, + "output": 64000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": false, + "mode": "mixed", + "budget": { + "min": 1024, + "unit": "tokens" + }, + "effort": "high", "effort_options": [ "low", + "medium", "high", "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Anthropic recommends adaptive thinking with effort for Claude 4.6; budget_tokens remains a deprecated compatibility path.", + "Anthropic API defaults effort to high; lower effort levels should be chosen per workload." ] } }, "attachment": true, - "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-09-10", - "last_updated": "2026-09-10", + "open_weights": false, + "knowledge": "2025-08-31", + "release_date": "2026-02-17", + "last_updated": "2026-03-13", "type": "chat" }, { - "id": "inkling", - "name": "Inkling", - "display_name": "Inkling", + "id": "anthropic/claude-opus-4-8", + "name": "Claude Opus 4.8", + "display_name": "Claude Opus 4.8", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 1000000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": false, + "mode": "effort", + "effort": "high", "effort_options": [ "low", "medium", - "high" + "high", + "xhigh", + "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "omitted", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Claude Opus 4.7 and newer Opus models require thinking.type = \"adaptive\" to enable thinking explicitly.", + "Manual budget_tokens requests return 400 on Claude Opus 4.7 and newer adaptive-only Opus models.", + "task_budget is separate from thinking control and should not be treated as a thinking budget." ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-07-15", - "last_updated": "2026-07-15", + "open_weights": false, + "knowledge": "2026-01", + "release_date": "2026-05-28", + "last_updated": "2026-05-28", "type": "chat" }, { - "id": "deepseek-v4-flash-0731", - "name": "DeepSeek V4 Flash 0731", - "display_name": "DeepSeek V4 Flash 0731", + "id": "anthropic/claude-opus-4-6", + "name": "Claude Opus 4.6", + "display_name": "Claude Opus 4.6", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" @@ -165597,48 +165756,68 @@ }, "limit": { "context": 1000000, - "output": 384000 + "output": 128000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": false, + "mode": "mixed", + "budget": { + "min": 1024, + "unit": "tokens" + }, + "effort": "high", "effort_options": [ "low", + "medium", "high", "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Anthropic recommends adaptive thinking with effort for Claude 4.6; budget_tokens remains a deprecated compatibility path.", + "Anthropic API defaults effort to high; lower effort levels should be chosen per workload." ] } }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-07-31", - "last_updated": "2026-07-31", + "attachment": true, + "open_weights": false, + "knowledge": "2025-05-31", + "release_date": "2026-02-05", + "last_updated": "2026-03-13", "type": "chat" }, { - "id": "kimi-k2-thinking", - "name": "Kimi K2 Thinking", - "display_name": "Kimi K2 Thinking", + "id": "anthropic/claude-fable-5-1", + "name": "Claude Fable 5.1", + "display_name": "Claude Fable 5.1", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 26214 + "context": 1000000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -165647,28 +165826,45 @@ "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "high", + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" + ], "interleaved": true, "summaries": true, - "visibility": "summary", + "visibility": "omitted", "continuation": [ "thinking_blocks" + ], + "notes": [ + "Adaptive thinking is always on for Claude Fable 5 and Claude Mythos 5; thinking.type = \"disabled\" is rejected.", + "Manual budget_tokens requests return 400 on Claude Fable 5 and Claude Mythos 5.", + "thinking.display defaults to omitted; set display to summarized to receive readable thinking summaries." ] } }, - "attachment": false, - "open_weights": true, - "knowledge": "2024-08", - "release_date": "2025-11-06", - "last_updated": "2025-11-06", + "attachment": true, + "open_weights": false, + "knowledge": "2026-06", + "release_date": "2026-09-01", + "last_updated": "2026-09-01", "type": "chat" }, { - "id": "deepseek-v4-pro-0813", - "name": "DeepSeek V4 Pro 0813", - "display_name": "DeepSeek V4 Pro 0813", + "id": "anthropic/claude-opus-5", + "name": "Claude Opus 5", + "display_name": "Claude Opus 5", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" @@ -165676,9 +165872,9 @@ }, "limit": { "context": 1000000, - "output": 262144 + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -165689,68 +165885,95 @@ "supported": true, "effort_options": [ "low", + "medium", "high", + "xhigh", "max" ] } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-08-13", - "last_updated": "2026-08-22", + "attachment": true, + "open_weights": false, + "knowledge": "2026-05", + "release_date": "2026-07-24", + "last_updated": "2026-07-24", "type": "chat" }, { - "id": "gemma-4-26b-a4b-it", - "name": "Gemma 4 26B A4B IT", - "display_name": "Gemma 4 26B A4B IT", + "id": "anthropic/claude-opus-4-5-20251101", + "name": "Claude Opus 4.5", + "display_name": "Claude Opus 4.5", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 256000, - "output": 25600 + "context": 200000, + "output": 64000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { - "supported": true + "supported": true, + "default_enabled": false, + "mode": "mixed", + "budget": { + "min": 1024, + "unit": "tokens" + }, + "effort": "high", + "effort_options": [ + "low", + "medium", + "high" + ], + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Claude Opus 4.5 uses manual thinking.type = \"enabled\" with budget_tokens; effort can be used alongside the thinking budget.", + "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header." + ] } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-04-02", - "last_updated": "2026-04-02", + "attachment": true, + "open_weights": false, + "knowledge": "2025-05", + "release_date": "2025-11-24", + "last_updated": "2025-11-24", "type": "chat" }, { - "id": "kimi-k2.7-code", - "name": "Kimi K2.7 Code", - "display_name": "Kimi K2.7 Code", + "id": "cerebras/gpt-oss-120b", + "name": "GPT OSS 120B", + "display_name": "GPT OSS 120B", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 256000, - "output": 16000 + "context": 131072, + "output": 40960 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -165758,20 +165981,25 @@ }, "extra_capabilities": { "reasoning": { - "supported": true + "supported": true, + "effort_options": [ + "low", + "medium", + "high" + ] } }, - "attachment": true, + "attachment": false, "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-06-12", - "last_updated": "2026-06-12", + "knowledge": "2024-06", + "release_date": "2025-08-05", + "last_updated": "2025-08-05", "type": "chat" }, { - "id": "qwen3.8-flash", - "name": "Qwen3.8 Flash", - "display_name": "Qwen3.8 Flash", + "id": "cerebras/qwen-3.8-27b", + "name": "Qwen3.8 27B", + "display_name": "Qwen3.8 27B", "modalities": { "input": [ "text", @@ -165782,8 +166010,8 @@ ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 131072, + "output": 40960 }, "temperature": true, "tool_call": true, @@ -165793,10 +166021,8 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, "effort_options": [ "none", - "minimal", "low", "medium", "high" @@ -165804,15 +166030,15 @@ } }, "attachment": true, - "open_weights": false, - "release_date": "2026-08-26", - "last_updated": "2026-08-26", + "open_weights": true, + "release_date": "2026-08-14", + "last_updated": "2026-08-14", "type": "chat" }, { - "id": "qwen3.7-flash", - "name": "Qwen3.7 Flash", - "display_name": "Qwen3.7 Flash", + "id": "deepseek/deepseek-flash", + "name": "DeepSeek V4.1 Flash", + "display_name": "DeepSeek V4.1 Flash", "modalities": { "input": [ "text", @@ -165824,7 +166050,7 @@ }, "limit": { "context": 1000000, - "output": 64000 + "output": 384000 }, "temperature": true, "tool_call": true, @@ -165834,26 +166060,24 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, "effort_options": [ - "none", - "minimal", "low", - "medium", - "high" + "high", + "max" ] } }, "attachment": true, - "open_weights": false, - "release_date": "2026-07-15", - "last_updated": "2026-07-15", + "open_weights": true, + "knowledge": "2025-05", + "release_date": "2026-09-10", + "last_updated": "2026-09-10", "type": "chat" }, { - "id": "glm-5.3", - "name": "GLM-5.3", - "display_name": "GLM-5.3", + "id": "deepseek/deepseek-v4-pro", + "name": "DeepSeek V4 Pro", + "display_name": "DeepSeek V4 Pro", "modalities": { "input": [ "text" @@ -165864,7 +166088,7 @@ }, "limit": { "context": 1000000, - "output": 128000 + "output": 384000 }, "temperature": true, "tool_call": true, @@ -165875,64 +166099,50 @@ "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "high", "effort_options": [ "low", "high", "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." ] } }, "attachment": false, "open_weights": true, - "release_date": "2026-08-14", - "last_updated": "2026-08-14", - "type": "chat" - }, - { - "id": "prism", - "name": "Prism", - "display_name": "Prism", - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 1000000, - "output": 16000 - }, - "tool_call": true, - "reasoning": { - "supported": false - }, - "attachment": true, - "open_weights": false, - "release_date": "1970-01-01", - "last_updated": "2026-10-15", + "release_date": "2026-08-13", + "last_updated": "2026-08-22", "type": "chat" }, { - "id": "qwen3.7-plus", - "name": "Qwen3.7 Plus", - "display_name": "Qwen3.7 Plus", + "id": "moonshot/kimi-k3", + "name": "Kimi K3", + "display_name": "Kimi K3", "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 64000 + "context": 1048576, + "output": 131072 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -165942,38 +166152,37 @@ "reasoning": { "supported": true, "effort_options": [ - "none", - "minimal", "low", - "medium", - "high" + "high", + "max" ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2025-04", - "release_date": "2026-06-02", - "last_updated": "2026-06-02", + "open_weights": true, + "release_date": "2026-07-16", + "last_updated": "2026-07-16", "type": "chat" }, { - "id": "glm-5.2", - "name": "GLM-5.2", - "display_name": "GLM-5.2", + "id": "moonshot/kimi-k2.7-code", + "name": "Kimi K2.7 Code", + "display_name": "Kimi K2.7 Code", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 32768 + "context": 262144, + "output": 262144 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -165981,35 +166190,25 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "effort_options": [ - "high", - "xhigh" - ] + "supported": true } }, - "attachment": false, + "attachment": true, "open_weights": true, - "release_date": "2026-06-13", - "last_updated": "2026-06-13", + "knowledge": "2025-01", + "release_date": "2026-06-12", + "last_updated": "2026-06-12", "type": "chat" - } - ] - }, - "stackit": { - "id": "stackit", - "name": "STACKIT", - "display_name": "STACKIT", - "api": "https://api.openai-compat.model-serving.eu01.onstackit.cloud/v1", - "doc": "https://docs.stackit.cloud/products/data-and-ai/ai-model-serving/basics/available-shared-models", - "models": [ + }, { - "id": "Qwen/Qwen3.6-27B", - "name": "Qwen3.6 27B", - "display_name": "Qwen3.6 27B", + "id": "moonshot/kimi-k2.7-code-highspeed", + "name": "Kimi K2.7 Code Highspeed", + "display_name": "Kimi K2.7 Code Highspeed", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" @@ -166017,94 +166216,111 @@ }, "limit": { "context": 262144, - "output": 16384 + "output": 262144 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { - "supported": true + "supported": true, + "default": true }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] + "supported": true } }, - "attachment": false, + "attachment": true, "open_weights": true, - "release_date": "2026-04-22", - "last_updated": "2026-04-22", + "knowledge": "2025-01", + "release_date": "2026-06-12", + "last_updated": "2026-06-12", "type": "chat" }, { - "id": "Qwen/Qwen3-VL-Embedding-8B", - "name": "Qwen3-VL Embedding 8B", - "display_name": "Qwen3-VL Embedding 8B", + "id": "moonshot/kimi-k2.6", + "name": "Kimi K2.6", + "display_name": "Kimi K2.6", "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 32000, - "output": 4096 + "context": 262144, + "output": 262144 }, "temperature": false, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true + } }, "attachment": true, "open_weights": true, - "release_date": "2026-02-05", - "last_updated": "2026-02-05", - "type": "embedding" + "knowledge": "2025-01", + "release_date": "2026-04-20", + "last_updated": "2026-04-20", + "type": "chat" }, { - "id": "Qwen/Qwen3-VL-235B-A22B-Instruct-FP8", - "name": "Qwen3-VL 235B", - "display_name": "Qwen3-VL 235B", + "id": "zai/glm-5.3-flashx", + "name": "GLM-5.3-FlashX", + "display_name": "GLM-5.3-FlashX", "modalities": { "input": [ "text", - "image" + "image", + "video", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 218000, - "output": 16384 + "context": 1000000, + "output": 131072 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "low", + "high", + "max" + ] + } }, "attachment": true, "open_weights": true, - "knowledge": "2025-03-31", - "release_date": "2025-09-23", - "last_updated": "2025-09-23", + "release_date": "2026-09-18", + "last_updated": "2026-09-18", "type": "chat" }, { - "id": "cortecs/Llama-3.3-70B-Instruct-FP8-Dynamic", - "name": "Llama 3.3 70B", - "display_name": "Llama 3.3 70B", + "id": "zai/glm-4.6v", + "name": "GLM-4.6V", + "display_name": "GLM-4.6V", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" @@ -166112,91 +166328,39 @@ }, "limit": { "context": 128000, - "output": 4096 + "output": 32768 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false - }, - "attachment": false, - "open_weights": true, - "knowledge": "2023-12", - "release_date": "2024-12-06", - "last_updated": "2024-12-06", - "type": "chat" - }, - { - "id": "google/gemma-3-27b-it", - "name": "Gemma 3 27B", - "display_name": "Gemma 3 27B", - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 37000, - "output": 4096 - }, - "temperature": true, - "tool_call": false, - "reasoning": { - "supported": false + "supported": true, + "default": true }, "attachment": true, "open_weights": true, - "knowledge": "2024-08", - "release_date": "2025-03-12", - "last_updated": "2025-03-12", + "knowledge": "2025-04", + "release_date": "2025-12-08", + "last_updated": "2025-12-08", "type": "chat" }, { - "id": "intfloat/e5-mistral-7b-instruct", - "name": "E5 Mistral 7B", - "display_name": "E5 Mistral 7B", - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 4096, - "output": 4096 - }, - "temperature": false, - "tool_call": false, - "reasoning": { - "supported": false - }, - "attachment": false, - "open_weights": true, - "release_date": "2023-12-11", - "last_updated": "2023-12-11", - "type": "embedding" - }, - { - "id": "openai/gpt-oss-20b", - "name": "GPT OSS 20B", - "display_name": "GPT OSS 20B", + "id": "zai/glm-5.3-flash", + "name": "GLM-5.3-Flash", + "display_name": "GLM-5.3-Flash", "modalities": { "input": [ - "text" + "text", + "image", + "video", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 8192 + "context": 1000000, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -166206,20 +166370,24 @@ }, "extra_capabilities": { "reasoning": { - "supported": true + "supported": true, + "effort_options": [ + "low", + "high", + "max" + ] } }, - "attachment": false, + "attachment": true, "open_weights": true, - "knowledge": "2024-06", - "release_date": "2025-08-05", - "last_updated": "2025-08-05", + "release_date": "2026-08-26", + "last_updated": "2026-08-26", "type": "chat" }, { - "id": "openai/gpt-oss-120b", - "name": "GPT OSS 120B", - "display_name": "GPT OSS 120B", + "id": "zai/glm-4.7", + "name": "GLM-4.7", + "display_name": "GLM-4.7", "modalities": { "input": [ "text" @@ -166229,8 +166397,8 @@ ] }, "limit": { - "context": 131000, - "output": 8192 + "context": 204800, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -166241,46 +166409,36 @@ "extra_capabilities": { "reasoning": { "supported": true, - "effort_options": [ - "low", - "medium", - "high" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, "attachment": false, "open_weights": true, - "knowledge": "2024-06", - "release_date": "2025-08-05", - "last_updated": "2025-08-05", + "knowledge": "2025-04", + "release_date": "2025-12-22", + "last_updated": "2025-12-22", "type": "chat" - } - ] - }, - "nvidia": { - "id": "nvidia", - "name": "Nvidia", - "display_name": "Nvidia", - "api": "https://integrate.api.nvidia.com/v1", - "doc": "https://docs.api.nvidia.com/nim/", - "models": [ + }, { - "id": "minimaxai/minimax-m3.1-flash", - "name": "MiniMax-M3.1-Flash", - "display_name": "MiniMax-M3.1-Flash", + "id": "zai/glm-4.6", + "name": "GLM-4.6", + "display_name": "GLM-4.6", "modalities": { "input": [ - "text", - "image", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 204800, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -166290,32 +166448,20 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" - ] + "supported": true } }, - "attachment": true, - "open_weights": false, - "release_date": "2026-10-19", - "last_updated": "2026-10-19", + "attachment": false, + "open_weights": true, + "knowledge": "2025-04", + "release_date": "2025-09-30", + "last_updated": "2025-09-30", "type": "chat" }, { - "id": "poolside/laguna-xs-2.1", - "name": "Laguna XS 2.1", - "display_name": "Laguna XS 2.1", + "id": "zai/glm-4.5-air", + "name": "GLM-4.5-Air", + "display_name": "GLM-4.5-Air", "modalities": { "input": [ "text" @@ -166325,8 +166471,8 @@ ] }, "limit": { - "context": 262144, - "output": 16384 + "context": 131072, + "output": 98304 }, "temperature": true, "tool_call": true, @@ -166334,187 +166480,222 @@ "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "supported": true - } - }, "attachment": false, "open_weights": true, - "release_date": "2026-07-02", - "last_updated": "2026-07-02", + "knowledge": "2025-04", + "release_date": "2025-07-28", + "last_updated": "2025-07-28", "type": "chat" }, { - "id": "black-forest-labs/flux_1-schnell", - "name": "FLUX.1-schnell", - "display_name": "FLUX.1-schnell", + "id": "zai/glm-4.5-flash", + "name": "GLM-4.5-Flash", + "display_name": "GLM-4.5-Flash", "modalities": { "input": [ "text" ], "output": [ - "image" + "text" ] }, "limit": { - "context": 8192, - "output": 8192 + "context": 131072, + "output": 98304 }, - "temperature": false, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, "attachment": false, - "open_weights": true, - "knowledge": "2024-07", - "release_date": "2024-08-01", - "last_updated": "2024-08-01" + "open_weights": false, + "knowledge": "2025-04", + "release_date": "2025-07-28", + "last_updated": "2025-07-28", + "type": "chat" }, { - "id": "black-forest-labs/flux.1-dev", - "name": "FLUX.1-dev", - "display_name": "FLUX.1-dev", + "id": "zai/glm-5.3", + "name": "GLM-5.3", + "display_name": "GLM-5.3", "modalities": { "input": [ "text" ], "output": [ - "image" + "text" ] }, "limit": { - "context": 8192, - "output": 8192 + "context": 1000000, + "output": 131072 }, - "temperature": false, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "effort_options": [ + "low", + "high", + "max" + ] + } }, "attachment": false, "open_weights": true, - "knowledge": "2024-08", - "release_date": "2024-08-01", - "last_updated": "2024-08-01" + "release_date": "2026-08-14", + "last_updated": "2026-08-14", + "type": "chat" }, { - "id": "black-forest-labs/flux_2-klein-4b", - "name": "FLUX.2 Klein 4B", - "display_name": "FLUX.2 Klein 4B", + "id": "zai/glm-4.5v", + "name": "GLM-4.5V", + "display_name": "GLM-4.5V", "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ - "image" + "text" ] }, "limit": { - "context": 8192, - "output": 8192 + "context": 64000, + "output": 16384 }, - "temperature": false, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, "attachment": true, "open_weights": true, - "knowledge": "2025-06", - "release_date": "2026-01-15", - "last_updated": "2026-01-15" + "knowledge": "2025-04", + "release_date": "2025-08-11", + "last_updated": "2025-08-11", + "type": "chat" }, { - "id": "black-forest-labs/flux_1-kontext-dev", - "name": "FLUX.1-Kontext-dev", - "display_name": "FLUX.1-Kontext-dev", + "id": "zai/glm-5", + "name": "GLM-5", + "display_name": "GLM-5", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ - "image" + "text" ] }, "limit": { - "context": 8192, - "output": 8192 + "context": 204800, + "output": 131072 }, - "temperature": false, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": true, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } + }, + "attachment": false, "open_weights": true, - "release_date": "2025-05-29", - "last_updated": "2025-05-29" + "release_date": "2026-02-12", + "last_updated": "2026-02-12", + "type": "chat" }, { - "id": "nvidia/streampetr", - "name": "streampetr", - "display_name": "streampetr", + "id": "zai/glm-4.7-flashx", + "name": "GLM-4.7-FlashX", + "display_name": "GLM-4.7-FlashX", "modalities": { "input": [ - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 8192 + "context": 200000, + "output": 131072 }, - "temperature": false, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": true, + "attachment": false, "open_weights": true, - "release_date": "2025-11-13", - "last_updated": "2025-11-13", + "knowledge": "2025-04", + "release_date": "2026-01-19", + "last_updated": "2026-01-19", "type": "chat" }, { - "id": "nvidia/magpie-tts-zeroshot", - "name": "magpie-tts-zeroshot", - "display_name": "magpie-tts-zeroshot", + "id": "zai/glm-4.7-flash", + "name": "GLM-4.7-Flash", + "display_name": "GLM-4.7-Flash", "modalities": { "input": [ - "text", - "audio" + "text" ], "output": [ - "audio" + "text" ] }, "limit": { - "context": 8192, - "output": 4096 + "context": 200000, + "output": 131072 }, - "temperature": false, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": true, + "extra_capabilities": { + "reasoning": { + "supported": true + } + }, + "attachment": false, "open_weights": true, - "release_date": "2025-05-22", - "last_updated": "2025-06-12" + "knowledge": "2025-04", + "release_date": "2026-01-19", + "last_updated": "2026-01-19", + "type": "chat" }, { - "id": "nvidia/llama-3.1-nemotron-70b-instruct", - "name": "Llama 3.1 Nemotron 70B Instruct", - "display_name": "Llama 3.1 Nemotron 70B Instruct", + "id": "zai/glm-4.6v-flash", + "name": "GLM-4.6V-Flash", + "display_name": "GLM-4.6V-Flash", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" @@ -166522,23 +166703,24 @@ }, "limit": { "context": 128000, - "output": 8192 + "output": 32768 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, + "attachment": true, "open_weights": true, - "release_date": "2025-04-15", - "last_updated": "2025-04-15", + "release_date": "2025-12-08", + "last_updated": "2025-12-08", "type": "chat" }, { - "id": "nvidia/usdvalidate", - "name": "usdvalidate", - "display_name": "usdvalidate", + "id": "zai/glm-5.1", + "name": "GLM-5.1", + "display_name": "GLM-5.1", "modalities": { "input": [ "text" @@ -166548,51 +166730,61 @@ ] }, "limit": { - "context": 8192, - "output": 4096 + "context": 200000, + "output": 131072 }, - "temperature": false, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true + } }, "attachment": false, "open_weights": true, - "release_date": "2024-07-24", - "last_updated": "2025-01-08", + "release_date": "2026-04-07", + "last_updated": "2026-04-07", "type": "chat" }, { - "id": "nvidia/llama-3.1-nemotron-safety-guard-8b-v3", - "name": "llama-3.1-nemotron-safety-guard-8b-v3", - "display_name": "llama-3.1-nemotron-safety-guard-8b-v3", + "id": "zai/glm-5v-turbo", + "name": "GLM-5V-Turbo", + "display_name": "GLM-5V-Turbo", "modalities": { "input": [ - "text" + "text", + "image", + "video", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 4096 + "context": 200000, + "output": 131072 }, - "temperature": false, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, - "open_weights": true, - "release_date": "2025-10-28", - "last_updated": "2025-10-28", + "attachment": true, + "open_weights": false, + "release_date": "2026-04-01", + "last_updated": "2026-04-01", "type": "chat" }, { - "id": "nvidia/riva-translate-4b-instruct-v1.1", - "name": "riva-translate-4b-instruct-v1_1", - "display_name": "riva-translate-4b-instruct-v1_1", + "id": "zai/glm-5.2", + "name": "GLM-5.2", + "display_name": "GLM-5.2", "modalities": { "input": [ "text" @@ -166602,91 +166794,120 @@ ] }, "limit": { - "context": 128000, - "output": 4096 + "context": 1000000, + "output": 131072 }, - "temperature": false, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "effort_options": [ + "none", + "high", + "max" + ] + } }, "attachment": false, "open_weights": true, - "release_date": "2025-12-12", - "last_updated": "2025-12-12", + "release_date": "2026-06-13", + "last_updated": "2026-06-13", "type": "chat" }, { - "id": "nvidia/llama-nemotron-rerank-vl-1b-v2", - "name": "llama-nemotron-rerank-vl-1b-v2", - "display_name": "llama-nemotron-rerank-vl-1b-v2", + "id": "zai/glm-5-turbo", + "name": "GLM-5-Turbo", + "display_name": "GLM-5-Turbo", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 10240, - "output": 4096 + "context": 200000, + "output": 131072 }, - "temperature": false, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": true, - "open_weights": true, - "release_date": "2025-12-18", - "last_updated": "2025-12-18" + "extra_capabilities": { + "reasoning": { + "supported": true + } + }, + "attachment": false, + "open_weights": false, + "release_date": "2026-03-16", + "last_updated": "2026-03-16", + "type": "chat" }, { - "id": "nvidia/bevformer", - "name": "bevformer", - "display_name": "bevformer", + "id": "zai/glm-4.5", + "name": "GLM-4.5", + "display_name": "GLM-4.5", "modalities": { "input": [ - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 8192 + "context": 131072, + "output": 98304 }, - "temperature": false, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": true, + "attachment": false, "open_weights": true, - "release_date": "2025-03-18", - "last_updated": "2025-07-20", + "knowledge": "2025-04", + "release_date": "2025-07-28", + "last_updated": "2025-07-28", "type": "chat" - }, + } + ] + }, + "llmgateway": { + "id": "llmgateway", + "name": "DevPass (LLM Gateway)", + "display_name": "DevPass (LLM Gateway)", + "api": "https://api.llmgateway.io/v1", + "doc": "https://llmgateway.io/docs", + "models": [ { - "id": "nvidia/nemotron-3-nano-omni-30b-a3b-reasoning", - "name": "Nemotron 3 Nano Omni", - "display_name": "Nemotron 3 Nano Omni", + "id": "gemini-3.5-flash", + "name": "Gemini 3.5 Flash", + "display_name": "Gemini 3.5 Flash", "modalities": { "input": [ "text", "image", "video", - "audio" + "audio", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 256000, + "context": 1048576, "output": 65536 }, "temperature": true, @@ -166697,173 +166918,270 @@ }, "extra_capabilities": { "reasoning": { - "supported": true + "supported": true, + "default_enabled": true, + "mode": "level", + "level": "high", + "level_options": [ + "minimal", + "low", + "medium", + "high" + ], + "summaries": true, + "visibility": "summary", + "continuation": [ + "thought_signatures" + ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-04-28", - "last_updated": "2026-04-28", + "open_weights": false, + "knowledge": "2025-01", + "release_date": "2026-05-19", + "last_updated": "2026-05-19", "type": "chat" }, { - "id": "nvidia/usdcode", - "name": "usdcode", - "display_name": "usdcode", + "id": "gpt-5.2-pro", + "name": "GPT-5.2 Pro", + "display_name": "GPT-5.2 Pro", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 4096 + "context": 400000, + "output": 128000 }, - "temperature": true, - "tool_call": false, + "temperature": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "high", + "effort_options": [ + "medium", + "high", + "xhigh" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" + } + }, + "attachment": true, "open_weights": false, - "release_date": "2026-01-01", - "last_updated": "2026-01-01", + "knowledge": "2025-08-31", + "release_date": "2025-12-11", + "last_updated": "2025-12-11", "type": "chat" }, { - "id": "nvidia/cosmos-reason2-8b", - "name": "Cosmos Reason2 8B", - "display_name": "Cosmos Reason2 8B", + "id": "gpt-5-pro", + "name": "GPT-5 Pro", + "display_name": "GPT-5 Pro", "modalities": { "input": [ "text", - "image", - "video" + "image" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 16384 + "context": 400000, + "output": 272000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, "default": true }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "fixed", + "effort": "high", + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" + } + }, "attachment": true, - "open_weights": true, - "release_date": "2025-12-01", - "last_updated": "2025-12-01", + "open_weights": false, + "knowledge": "2024-09-30", + "release_date": "2025-10-06", + "last_updated": "2025-10-06", "type": "chat" }, { - "id": "nvidia/studiovoice", - "name": "studiovoice", - "display_name": "studiovoice", + "id": "qwen3-vl-235b-a22b-instruct", + "name": "Qwen3 VL 235B A22B Instruct", + "display_name": "Qwen3 VL 235B A22B Instruct", "modalities": { "input": [ - "audio" + "text", + "image" ], "output": [ - "audio" + "text" ] }, "limit": { - "context": 8192, + "context": 262144, "output": 8192 }, - "temperature": false, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { "supported": false }, "attachment": true, "open_weights": true, - "release_date": "2024-10-03", - "last_updated": "2025-06-13", + "knowledge": "2025-03-31", + "release_date": "2025-09-23", + "last_updated": "2025-09-23", "type": "chat" }, { - "id": "nvidia/sparsedrive", - "name": "sparsedrive", - "display_name": "sparsedrive", + "id": "gpt-5.4-nano", + "name": "GPT-5.4 nano", + "display_name": "GPT-5.4 nano", "modalities": { "input": [ - "video" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 8192 + "context": 400000, + "output": 128000 }, - "temperature": false, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": false + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": false, + "mode": "effort", + "effort": "none", + "effort_options": [ + "none", + "low", + "medium", + "high", + "xhigh" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" + } }, "attachment": true, - "open_weights": true, - "release_date": "2025-03-18", - "last_updated": "2025-07-20", + "open_weights": false, + "knowledge": "2025-08-31", + "release_date": "2026-03-17", + "last_updated": "2026-03-17", "type": "chat" }, { - "id": "nvidia/cosmos-transfer2_5-2b", - "name": "cosmos-transfer2.5-2b", - "display_name": "cosmos-transfer2.5-2b", + "id": "qwen3.8-omni-flash", + "name": "Qwen3.8 Omni Flash", + "display_name": "Qwen3.8 Omni Flash", "modalities": { "input": [ "text", "image", + "audio", "video" ], "output": [ - "video" + "text" ] }, "limit": { - "context": 8192, - "output": 4096 + "context": 983616, + "output": 131072 }, - "temperature": false, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "none", + "minimal", + "low", + "medium", + "high", + "xhigh", + "max" + ] + } }, "attachment": true, - "open_weights": true, - "release_date": "2026-02-26", - "last_updated": "2026-02-26" + "open_weights": false, + "release_date": "2026-09-17", + "last_updated": "2026-09-17", + "type": "chat" }, { - "id": "nvidia/nemotron-3.5-content-safety", - "name": "Nemotron 3.5 Content Safety", - "display_name": "Nemotron 3.5 Content Safety", + "id": "kimi-k3", + "name": "Kimi K3", + "display_name": "Kimi K3", "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 8192 + "context": 1048576, + "output": 131072 }, - "temperature": true, - "tool_call": false, + "temperature": false, + "tool_call": true, "reasoning": { "supported": true, "default": true @@ -166871,35 +167189,40 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "none", + "minimal", + "low", + "medium", + "high", + "xhigh", + "max" ] } }, "attachment": true, "open_weights": true, - "release_date": "2026-06-02", - "last_updated": "2026-06-02", + "release_date": "2026-07-16", + "last_updated": "2026-07-16", "type": "chat" }, { - "id": "nvidia/nemotron-3-ultra-550b-a55b", - "name": "Nemotron 3 Ultra 550B A55B", - "display_name": "Nemotron 3 Ultra 550B A55B", + "id": "grok-build-0-1", + "name": "Grok Build 0.1", + "display_name": "Grok Build 0.1", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 65536 + "context": 256000, + "output": 256000 }, "temperature": true, "tool_call": true, @@ -166909,118 +167232,171 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, "effort_options": [ - "none", + "low", "medium", "high" ] } }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-09", - "release_date": "2026-06-04", - "last_updated": "2026-06-04", + "attachment": true, + "open_weights": false, + "release_date": "2026-04-16", + "last_updated": "2026-04-16", "type": "chat" }, { - "id": "nvidia/rerank-qa-mistral-4b", - "name": "rerank-qa-mistral-4b", - "display_name": "rerank-qa-mistral-4b", + "id": "gpt-6.1-terra", + "name": "GPT-6.1 Terra", + "display_name": "GPT-6.1 Terra", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 4096 + "context": 1050000, + "output": 128000 }, "temperature": false, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, - "open_weights": true, - "release_date": "2024-03-17", - "last_updated": "2025-01-17" + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "none", + "low", + "medium", + "high", + "xhigh", + "max" + ] + } + }, + "attachment": true, + "open_weights": false, + "knowledge": "2026-06-01", + "release_date": "2026-10-26", + "last_updated": "2026-10-26", + "type": "chat" }, { - "id": "nvidia/synthetic-video-detector", - "name": "synthetic-video-detector", - "display_name": "synthetic-video-detector", + "id": "minimax-m2.5", + "name": "MiniMax-M2.5", + "display_name": "MiniMax-M2.5", "modalities": { "input": [ - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 8192, - "output": 4096 + "context": 228700, + "output": 131072 }, - "temperature": false, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": true, + "extra_capabilities": { + "reasoning": { + "supported": true, + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" + ] + } + }, + "attachment": false, "open_weights": true, - "release_date": "2026-04-16", - "last_updated": "2026-04-16", + "release_date": "2026-02-12", + "last_updated": "2026-02-12", "type": "chat" }, { - "id": "nvidia/active-speaker-detection", - "name": "Active Speaker Detection", - "display_name": "Active Speaker Detection", + "id": "gpt-5.4", + "name": "GPT-5.4", + "display_name": "GPT-5.4", "modalities": { "input": [ - "video" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 8192, - "output": 4096 + "context": 1050000, + "output": 128000 }, - "temperature": false, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": false + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": false, + "mode": "effort", + "effort": "none", + "effort_options": [ + "none", + "low", + "medium", + "high", + "xhigh" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" + } }, "attachment": true, - "open_weights": true, - "release_date": "2026-04-16", - "last_updated": "2026-04-16", + "open_weights": false, + "knowledge": "2025-08-31", + "release_date": "2026-03-05", + "last_updated": "2026-03-05", "type": "chat" }, { - "id": "nvidia/nemotron-3.5-super-vl-120b-a12b", - "name": "Nemotron 3.5 Super VL 120B A12B", - "display_name": "Nemotron 3.5 Super VL 120B A12B", + "id": "glm-5.3-flashx", + "name": "GLM-5.3 FlashX", + "display_name": "GLM-5.3 FlashX", "modalities": { "input": [ "text", - "image", - "video" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 8192 + "context": 1000000, + "output": 1000000 }, "temperature": true, "tool_call": true, @@ -167030,25 +167406,22 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "xhigh", + "max" ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-10-15", - "last_updated": "2026-10-15", + "open_weights": false, + "release_date": "2026-09-18", + "last_updated": "2026-09-18", "type": "chat" }, { - "id": "nvidia/llama-nemotron-embed-vl-1b-v2", - "name": "llama-nemotron-embed-vl-1b-v2", - "display_name": "llama-nemotron-embed-vl-1b-v2", + "id": "deepseek-v4.1-flash-cyber", + "name": "DeepSeek V4.1 Flash Cyber", + "display_name": "DeepSeek V4.1 Flash Cyber", "modalities": { "input": [ "text", @@ -167059,63 +167432,101 @@ ] }, "limit": { - "context": 8192, - "output": 2048 + "context": 1040000, + "output": 1040000 }, - "temperature": false, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "none", + "low", + "high", + "xhigh", + "max" + ] + } }, "attachment": true, - "open_weights": true, - "release_date": "2025-12-18", - "last_updated": "2025-12-18", - "type": "embedding" + "open_weights": false, + "release_date": "2026-10-29", + "last_updated": "2026-10-29", + "type": "chat" }, { - "id": "nvidia/nemotron-voicechat", - "name": "nemotron-voicechat", - "display_name": "nemotron-voicechat", + "id": "gpt-5.4-mini", + "name": "GPT-5.4 mini", + "display_name": "GPT-5.4 mini", "modalities": { "input": [ "text", - "audio" + "image" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 8192 + "context": 400000, + "output": 128000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": false + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": false, + "mode": "effort", + "effort": "none", + "effort_options": [ + "none", + "low", + "medium", + "high", + "xhigh" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" + } }, "attachment": true, - "open_weights": true, - "release_date": "2026-03-16", - "last_updated": "2026-03-16", + "open_weights": false, + "knowledge": "2025-08-31", + "release_date": "2026-03-17", + "last_updated": "2026-03-17", "type": "chat" }, { - "id": "nvidia/llama-3.1-nemotron-ultra-253b-v1", - "name": "Llama 3.1 Nemotron Ultra 253B", - "display_name": "Llama 3.1 Nemotron Ultra 253B", + "id": "qwen35-397b-a17b", + "name": "Qwen3.5 397B-A17B", + "display_name": "Qwen3.5 397B-A17B", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 16384 + "context": 262144, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -167123,19 +167534,33 @@ "supported": true, "default": true }, - "attachment": false, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "none", + "minimal", + "low", + "medium", + "high", + "xhigh", + "max" + ] + } + }, + "attachment": true, "open_weights": true, - "release_date": "2025-04-07", - "last_updated": "2025-04-07", + "release_date": "2026-02-15", + "last_updated": "2026-02-15", "type": "chat" }, - { - "id": "nvidia/nemotron-3.5-lightning-30b-a3b", - "name": "Nemotron 3.5 Lightning 30B A3B", - "display_name": "Nemotron 3.5 Lightning 30B A3B", + { + "id": "mistral-large-2512", + "name": "Mistral Large 3", + "display_name": "Mistral Large 3", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" @@ -167148,31 +167573,29 @@ "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true + "supported": false }, - "attachment": false, + "attachment": true, "open_weights": true, - "knowledge": "2025-09", - "release_date": "2026-08-11", - "last_updated": "2026-08-11", + "knowledge": "2024-11", + "release_date": "2025-12-02", + "last_updated": "2025-12-02", "type": "chat" }, { - "id": "z-ai/glm-5.3-flash", - "name": "GLM-5.3-Flash", - "display_name": "GLM-5.3-Flash", + "id": "minimax-m2.5-highspeed", + "name": "MiniMax-M2.5-highspeed", + "display_name": "MiniMax-M2.5-highspeed", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, + "context": 204800, "output": 131072 }, "temperature": true, @@ -167184,42 +167607,40 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], "effort_options": [ "low", + "medium", "high", + "xhigh", "max" ] } }, - "attachment": true, + "attachment": false, "open_weights": true, - "release_date": "2026-08-26", - "last_updated": "2026-08-26", + "release_date": "2026-02-13", + "last_updated": "2026-02-13", "type": "chat" }, { - "id": "z-ai/glm-5.3", - "name": "GLM-5.3", - "display_name": "GLM-5.3", + "id": "gpt-6-sol", + "name": "GPT-6 Sol", + "display_name": "GPT-6 Sol", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 1050000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -167227,71 +167648,94 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], "effort_options": [ + "none", "low", + "medium", "high", + "xhigh", "max" ] } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-08-14", - "last_updated": "2026-08-14", + "attachment": true, + "open_weights": false, + "knowledge": "2026-04-20", + "release_date": "2026-09-22", + "last_updated": "2026-09-22", "type": "chat" }, { - "id": "google/google-paligemma", - "name": "paligemma", - "display_name": "paligemma", + "id": "gpt-5.6-terra", + "name": "GPT-5.6 Terra", + "display_name": "GPT-5.6 Terra", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 8192 + "context": 1050000, + "output": 128000 }, - "temperature": true, - "tool_call": false, + "temperature": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", + "effort_options": [ + "none", + "low", + "medium", + "high", + "xhigh", + "max" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" + } }, "attachment": true, - "open_weights": true, - "release_date": "2024-05-14", - "last_updated": "2024-08-26", + "open_weights": false, + "knowledge": "2026-02-16", + "release_date": "2026-07-09", + "last_updated": "2026-07-09", "type": "chat" }, { - "id": "google/gemma-4-31b-it", - "name": "Gemma-4-31B-IT", - "display_name": "Gemma-4-31B-IT", + "id": "gpt-5.3-codex", + "name": "GPT-5.3 Codex", + "display_name": "GPT-5.3 Codex", "modalities": { "input": [ "text", "image", - "video" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 256000, - "output": 16384 + "context": 400000, + "output": 128000 }, "temperature": true, "tool_call": true, @@ -167301,20 +167745,36 @@ }, "extra_capabilities": { "reasoning": { - "supported": true + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", + "effort_options": [ + "low", + "medium", + "high", + "xhigh" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, "attachment": true, - "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-04-02", - "last_updated": "2026-04-02", + "open_weights": false, + "knowledge": "2025-08-31", + "release_date": "2026-02-05", + "last_updated": "2026-02-05", "type": "chat" }, { - "id": "google/gemma-3-12b-it", - "name": "Gemma 3 12B IT", - "display_name": "Gemma 3 12B IT", + "id": "gpt-6-astra", + "name": "GPT-6 Astra", + "display_name": "GPT-6 Astra", "modalities": { "input": [ "text", @@ -167325,54 +167785,90 @@ ] }, "limit": { - "context": 131072, - "output": 16384 + "context": 1050000, + "output": 1050000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" + ] + } }, "attachment": true, - "open_weights": true, - "knowledge": "2024-08", - "release_date": "2025-03-12", - "last_updated": "2025-03-12", + "open_weights": false, + "knowledge": "2026-04-30", + "release_date": "2026-09-04", + "last_updated": "2026-09-04", "type": "chat" }, { - "id": "google/gemma-3-4b-it", - "name": "Gemma 3 4B IT", - "display_name": "Gemma 3 4B IT", + "id": "claude-haiku-4-5-20251001", + "name": "Claude Haiku 4.5", + "display_name": "Claude Haiku 4.5", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 16384 + "context": 200000, + "output": 64000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": false + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": false, + "mode": "budget", + "budget": { + "min": 1024, + "unit": "tokens" + }, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", + "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." + ] + } }, "attachment": true, - "open_weights": true, - "knowledge": "2024-08", - "release_date": "2025-03-12", - "last_updated": "2025-03-12", + "open_weights": false, + "knowledge": "2025-02-28", + "release_date": "2025-10-15", + "last_updated": "2025-10-15", "type": "chat" }, { - "id": "google/diffusiongemma-26b-a4b-it", - "name": "DiffusionGemma 26B A4B IT", - "display_name": "DiffusionGemma 26B A4B IT", + "id": "step-5-preview", + "name": "Step 5 Preview", + "display_name": "Step 5 Preview", "modalities": { "input": [ "text", @@ -167384,8 +167880,8 @@ ] }, "limit": { - "context": 250000, - "output": 32768 + "context": 1000000, + "output": 1000000 }, "temperature": true, "tool_call": true, @@ -167393,109 +167889,108 @@ "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "none", - "minimal", - "low", - "medium", - "high", - "xhigh", - "max" - ] - } - }, "attachment": true, "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-06-09", - "last_updated": "2026-07-15", + "release_date": "2026-09-16", + "last_updated": "2026-09-20", "type": "chat" }, { - "id": "openai/gpt-oss-20b", - "name": "GPT OSS 20B", - "display_name": "GPT OSS 20B", + "id": "gpt-5.1-codex", + "name": "GPT-5.1 Codex", + "display_name": "GPT-5.1 Codex", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 32768 + "context": 400000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": false, + "mode": "effort", + "effort": "none", "effort_options": [ + "none", "low", "medium", "high" - ] + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, - "attachment": false, - "open_weights": true, - "knowledge": "2024-06", - "release_date": "2025-08-05", - "last_updated": "2025-08-05", + "attachment": true, + "open_weights": false, + "knowledge": "2024-09-30", + "release_date": "2025-11-13", + "last_updated": "2025-11-13", "type": "chat" }, { - "id": "openai/whisper-large-v3", - "name": "Whisper Large v3", - "display_name": "Whisper Large v3", + "id": "qwen3-vl-30b-a3b-instruct", + "name": "Qwen3 VL 30B A3B Instruct", + "display_name": "Qwen3 VL 30B A3B Instruct", "modalities": { "input": [ - "audio" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 448, - "output": 4096 + "context": 262144, + "output": 8192 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { "supported": false }, "attachment": true, "open_weights": true, - "knowledge": "2023-09", - "release_date": "2023-11-06", - "last_updated": "2023-11-06" + "release_date": "2025-10-04", + "last_updated": "2025-10-04", + "type": "chat" }, { - "id": "moonshotai/kimi-k3", - "name": "Kimi K3", - "display_name": "Kimi K3", + "id": "claude-haiku-5-5", + "name": "Claude Haiku 5.5", + "display_name": "Claude Haiku 5.5", "modalities": { "input": [ "text", "image", - "video" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 1000000, + "output": 128000 }, "temperature": false, "tool_call": true, @@ -167505,220 +168000,245 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], "effort_options": [ + "none", "low", + "medium", "high", + "xhigh", "max" ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-07-16", - "last_updated": "2026-07-16", + "open_weights": false, + "knowledge": "2026-06", + "release_date": "2026-10-07", + "last_updated": "2026-10-07", "type": "chat" }, { - "id": "moonshotai/kimi-k2.6", - "name": "Kimi K2.6", - "display_name": "Kimi K2.6", + "id": "llama-4-maverick-17b-instruct", + "name": "Llama 4 Maverick 17B Instruct", + "display_name": "Llama 4 Maverick 17B Instruct", "modalities": { "input": [ "text", - "image", - "video" + "image" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 1048576, + "output": 2048 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "effort_options": [ - "none", - "minimal", - "low", - "medium", - "high", - "xhigh", - "max" - ] - } + "supported": false }, "attachment": true, "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-04-20", - "last_updated": "2026-04-20", + "knowledge": "2024-08", + "release_date": "2025-04-05", + "last_updated": "2025-04-05", "type": "chat" }, { - "id": "qwen/qwen-image", - "name": "Qwen Image", - "display_name": "Qwen Image", + "id": "sonar", + "name": "Sonar", + "display_name": "Sonar", "modalities": { "input": [ "text" ], "output": [ - "image" + "text" ] }, "limit": { - "context": 8192, - "output": 8192 + "context": 130000, + "output": 4096 }, - "temperature": false, + "temperature": true, "tool_call": false, "reasoning": { "supported": false }, "attachment": false, "open_weights": false, - "release_date": "2025-08-07", - "last_updated": "2025-08-07" + "knowledge": "2025-09-01", + "release_date": "2024-01-01", + "last_updated": "2025-09-01", + "type": "chat" }, { - "id": "qwen/qwen-image-edit", - "name": "Qwen Image Edit", - "display_name": "Qwen Image Edit", + "id": "auto", + "name": "Auto Route", + "display_name": "Auto Route", "modalities": { "input": [ "text", "image" ], "output": [ - "image" + "text" ] }, "limit": { - "context": 8192, - "output": 8192 + "context": 128000, + "output": 16384 }, - "temperature": false, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { - "supported": false + "supported": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true + } }, "attachment": true, "open_weights": false, - "release_date": "2025-08-19", - "last_updated": "2025-08-19" + "release_date": "2024-01-01", + "last_updated": "2024-01-01", + "type": "chat" }, { - "id": "meta/llama-3.2-11b-vision-instruct", - "name": "Llama 3.2 11b Vision Instruct", - "display_name": "Llama 3.2 11b Vision Instruct", + "id": "grok-4-20-non-reasoning", + "name": "Grok 4.20 (Non-Reasoning)", + "display_name": "Grok 4.20 (Non-Reasoning)", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 4096 + "context": 2000000, + "output": 30000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true + } }, "attachment": true, - "open_weights": true, - "knowledge": "2023-12", - "release_date": "2024-09-25", - "last_updated": "2024-09-25", + "open_weights": false, + "release_date": "2026-03-09", + "last_updated": "2026-03-09", "type": "chat" }, { - "id": "meta/llama-3.2-90b-vision-instruct", - "name": "Llama-3.2-90B-Vision-Instruct", - "display_name": "Llama-3.2-90B-Vision-Instruct", + "id": "qwen3.8-2.4t-a95b", + "name": "Qwen3.8 2.4T A95B", + "display_name": "Qwen3.8 2.4T A95B", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 8192 + "context": 1010000, + "output": 131072 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": true, + "extra_capabilities": { + "reasoning": { + "supported": true, + "effort_options": [ + "none", + "minimal", + "low", + "medium", + "high", + "xhigh", + "max" + ] + } + }, + "attachment": false, "open_weights": true, - "knowledge": "2023-12", - "release_date": "2024-09-25", - "last_updated": "2024-09-25", + "release_date": "2026-08-12", + "last_updated": "2026-08-12", "type": "chat" }, { - "id": "meta/llama-guard-4-12b", - "name": "Llama Guard 4 12B", - "display_name": "Llama Guard 4 12B", + "id": "gpt-5.2-codex", + "name": "GPT-5.2 Codex", + "display_name": "GPT-5.2 Codex", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 163840, - "output": 16384 + "context": 400000, + "output": 128000 }, - "temperature": true, - "tool_call": false, + "temperature": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", + "effort_options": [ + "low", + "medium", + "high", + "xhigh" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" + } }, "attachment": true, - "open_weights": true, - "release_date": "2025-04-29", - "last_updated": "2025-04-29", + "open_weights": false, + "knowledge": "2025-08-31", + "release_date": "2025-12-11", + "last_updated": "2025-12-11", "type": "chat" }, { - "id": "meta/muse-glimmer-30b", - "name": "Muse Glimmer 30B", - "display_name": "Muse Glimmer 30B", + "id": "gpt-5.2", + "name": "GPT-5.2", + "display_name": "GPT-5.2", "modalities": { "input": [ "text", @@ -167729,121 +168249,150 @@ ] }, "limit": { - "context": 131072, - "output": 131072 + "context": 400000, + "output": 128000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], + "default_enabled": false, + "mode": "effort", + "effort": "none", "effort_options": [ "none", - "minimal", "low", "medium", "high", - "max" - ] + "xhigh" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, "attachment": true, - "open_weights": true, - "knowledge": "2026-01-04", - "release_date": "2026-08-10", - "last_updated": "2026-08-10", + "open_weights": false, + "knowledge": "2025-08-31", + "release_date": "2025-12-11", + "last_updated": "2025-12-11", "type": "chat" }, { - "id": "stepfun-ai/step-5-preview", - "name": "Step 5 Preview", - "display_name": "Step 5 Preview", + "id": "claude-sonnet-4-5-20250929", + "name": "Claude Sonnet 4.5", + "display_name": "Claude Sonnet 4.5", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 1048576 + "context": 200000, + "output": 64000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": false, + "mode": "budget", + "budget": { + "min": 1024, + "unit": "tokens" + }, "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ "thinking_blocks" ], - "effort_options": [ - "none", - "minimal", - "low", - "medium", - "high", - "xhigh", - "max" + "notes": [ + "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", + "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-09-16", - "last_updated": "2026-09-20", + "open_weights": false, + "knowledge": "2025-07-31", + "release_date": "2025-09-29", + "last_updated": "2025-09-29", "type": "chat" }, { - "id": "mistralai/magistral-small-2506", - "name": "Magistral Small 2506", - "display_name": "Magistral Small 2506", + "id": "gpt-5.4-pro", + "name": "GPT-5.4 Pro", + "display_name": "GPT-5.4 Pro", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 32768, - "output": 32768 + "context": 1050000, + "output": 128000 }, - "temperature": true, - "tool_call": false, + "temperature": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, - "open_weights": true, - "release_date": "2025-06-10", - "last_updated": "2025-06-10", + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "high", + "effort_options": [ + "medium", + "high", + "xhigh" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" + } + }, + "attachment": true, + "open_weights": false, + "knowledge": "2025-08-31", + "release_date": "2026-03-05", + "last_updated": "2026-03-05", "type": "chat" }, { - "id": "mistralai/mistral-medium-3-instruct", - "name": "Mistral Medium 3", - "display_name": "Mistral Medium 3", + "id": "seed-1-6-250915", + "name": "Seed 1.6 (250915)", + "display_name": "Seed 1.6 (250915)", "modalities": { "input": [ "text", @@ -167854,52 +168403,75 @@ ] }, "limit": { - "context": 131072, - "output": 32768 + "context": 256000, + "output": 8192 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "minimal", + "low", + "medium", + "high" + ] + } }, "attachment": true, - "open_weights": false, - "knowledge": "2025-03-31", - "release_date": "2025-05-07", - "last_updated": "2025-05-07", + "open_weights": true, + "release_date": "2025-09-15", + "last_updated": "2025-09-15", "type": "chat" }, { - "id": "mistralai/mistral-7b-instruct-v0.3", - "name": "Mistral-7B-Instruct-v0.3", - "display_name": "Mistral-7B-Instruct-v0.3", + "id": "grok-4-3", + "name": "Grok 4.3", + "display_name": "Grok 4.3", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 65536, - "output": 65536 + "context": 1000000, + "output": 30000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, - "open_weights": true, - "release_date": "2024-05-22", - "last_updated": "2024-05-22", + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "none", + "low", + "medium", + "high" + ] + } + }, + "attachment": true, + "open_weights": false, + "release_date": "2026-04-17", + "last_updated": "2026-04-17", "type": "chat" }, { - "id": "deepseek-ai/deepseek-v4.1-flash", - "name": "DeepSeek V4.1 Flash", - "display_name": "DeepSeek V4.1 Flash", + "id": "seed-1-8-251228", + "name": "Seed 1.8 (251228)", + "display_name": "Seed 1.8 (251228)", "modalities": { "input": [ "text", @@ -167910,8 +168482,8 @@ ] }, "limit": { - "context": 1000000, - "output": 384000 + "context": 256000, + "output": 8192 }, "temperature": true, "tool_call": true, @@ -167921,48 +168493,30 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], "effort_options": [ - "none", + "minimal", "low", - "high", - "max" + "medium", + "high" ] } }, "attachment": true, "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-09-10", - "last_updated": "2026-09-10", + "release_date": "2025-12-18", + "last_updated": "2025-12-18", "type": "chat" - } - ] - }, - "qihang-ai": { - "id": "qihang-ai", - "name": "QiHang", - "display_name": "QiHang", - "api": "https://api.qhaigc.net/v1", - "doc": "https://www.qhaigc.net/docs", - "models": [ + }, { - "id": "gemini-3-flash-preview", - "name": "Gemini 3 Flash Preview", - "display_name": "Gemini 3 Flash Preview", + "id": "muse-spark-1.1", + "name": "Muse Spark 1.1", + "display_name": "Muse Spark 1.1", "modalities": { "input": [ "text", "image", - "video", - "audio", - "pdf" + "pdf", + "video" ], "output": [ "text" @@ -167970,7 +168524,7 @@ }, "limit": { "context": 1048576, - "output": 65536 + "output": 131072 }, "temperature": true, "tool_call": true, @@ -167981,47 +168535,37 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "level", - "level": "high", - "level_options": [ + "effort_options": [ "minimal", "low", "medium", - "high" - ], - "summaries": true, - "visibility": "summary", - "continuation": [ - "thought_signatures" + "high", + "xhigh" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-01", - "release_date": "2025-12-17", - "last_updated": "2025-12-17", + "release_date": "2026-04-08", + "last_updated": "2026-07-09", "type": "chat" }, { - "id": "gemini-2.5-flash", - "name": "Gemini 2.5 Flash", - "display_name": "Gemini 2.5 Flash", + "id": "qwen3.5-27b", + "name": "Qwen3.5 27B", + "display_name": "Qwen3.5 27B", "modalities": { "input": [ "text", "image", - "audio", - "video", - "pdf" + "video" ], "output": [ "text" ] }, "limit": { - "context": 1048576, + "context": 262144, "output": 65536 }, "temperature": true, @@ -168033,40 +168577,37 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "budget", - "budget": { - "default": -1, - "min": 0, - "max": 24576, - "auto": -1, - "off": 0, - "unit": "tokens" - }, + "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ - "thought_signatures" + "thinking_blocks" + ], + "effort_options": [ + "none", + "minimal", + "low", + "medium", + "high", + "xhigh", + "max" ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2025-01", - "release_date": "2025-06-17", - "last_updated": "2025-06-17", + "open_weights": true, + "release_date": "2026-02-23", + "last_updated": "2026-02-23", "type": "chat" }, { - "id": "gemini-3-pro-preview", - "name": "Gemini 3 Pro Preview", - "display_name": "Gemini 3 Pro Preview", + "id": "fugu-max", + "name": "Fugu Max", + "display_name": "Fugu Max", "modalities": { "input": [ "text", - "image", - "audio", - "video" + "image" ], "output": [ "text" @@ -168074,7 +168615,7 @@ }, "limit": { "context": 1000000, - "output": 65000 + "output": 1000000 }, "temperature": true, "tool_call": true, @@ -168084,32 +168625,23 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "level", - "level": "high", - "level_options": [ - "low", - "high" - ], - "summaries": true, - "visibility": "summary", - "continuation": [ - "thought_signatures" + "effort_options": [ + "high", + "xhigh", + "max" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-11", - "release_date": "2025-11-18", - "last_updated": "2025-11-18", + "release_date": "2026-09-11", + "last_updated": "2026-09-11", "type": "chat" }, { - "id": "gpt-5-mini", - "name": "GPT-5-Mini", - "display_name": "GPT-5-Mini", + "id": "gemma-4-26b-a4b-it", + "name": "Gemma 4 26B A4B IT", + "display_name": "Gemma 4 26B A4B IT", "modalities": { "input": [ "text", @@ -168120,8 +168652,70 @@ ] }, "limit": { - "context": 200000, - "output": 64000 + "context": 262144, + "output": 32768 + }, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true + } + }, + "attachment": true, + "open_weights": true, + "release_date": "2026-04-02", + "last_updated": "2026-04-02", + "type": "chat" + }, + { + "id": "glm-4-32b-0414-128k", + "name": "GLM-4 32B (0414-128k)", + "display_name": "GLM-4 32B (0414-128k)", + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 16384 + }, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": false + }, + "attachment": false, + "open_weights": false, + "release_date": "2025-04-14", + "last_updated": "2025-04-14", + "type": "chat" + }, + { + "id": "kimi-k2.7-code", + "name": "Kimi K2.7 Code", + "display_name": "Kimi K2.7 Code", + "modalities": { + "input": [ + "text", + "image", + "video" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 262144, + "output": 262144 }, "temperature": false, "tool_call": true, @@ -168132,35 +168726,62 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ + "none", "minimal", "low", "medium", - "high" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + "high", + "xhigh", + "max" + ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2024-09-30", - "release_date": "2025-08-07", - "last_updated": "2025-08-07", + "attachment": true, + "open_weights": true, + "knowledge": "2025-01", + "release_date": "2026-06-12", + "last_updated": "2026-06-12", + "type": "chat" + }, + { + "id": "minimax-text-01", + "name": "MiniMax Text 01", + "display_name": "MiniMax Text 01", + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1000000, + "output": 131072 + }, + "temperature": true, + "tool_call": false, + "reasoning": { + "supported": true, + "default": true + }, + "attachment": false, + "open_weights": true, + "release_date": "2025-01-15", + "last_updated": "2025-01-15", "type": "chat" }, { - "id": "claude-sonnet-4-5-20250929", - "name": "Claude Sonnet 4.5", - "display_name": "Claude Sonnet 4.5", + "id": "gpt-6.1-sol", + "name": "GPT-6.1 Sol", + "display_name": "GPT-6.1 Sol", "modalities": { "input": [ "text", @@ -168172,47 +168793,37 @@ ] }, "limit": { - "context": 200000, - "output": 64000 + "context": 1050000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "budget", - "budget": { - "min": 1024, - "unit": "tokens" - }, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", - "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-07-31", - "release_date": "2025-09-29", - "last_updated": "2025-09-29", + "knowledge": "2026-04-30", + "release_date": "2026-09-29", + "last_updated": "2026-09-29", "type": "chat" }, { - "id": "claude-haiku-4-5-20251001", - "name": "Claude Haiku 4.5", - "display_name": "Claude Haiku 4.5", + "id": "grok-4-20-reasoning", + "name": "Grok 4.20 (Reasoning)", + "display_name": "Grok 4.20 (Reasoning)", "modalities": { "input": [ "text", @@ -168224,61 +168835,51 @@ ] }, "limit": { - "context": 200000, - "output": 64000 + "context": 2000000, + "output": 30000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "budget", - "budget": { - "min": 1024, - "unit": "tokens" - }, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", - "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." + "effort_options": [ + "low", + "medium", + "high" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-07-31", - "release_date": "2025-10-15", - "last_updated": "2025-10-15", + "release_date": "2026-03-09", + "last_updated": "2026-03-09", "type": "chat" }, { - "id": "gpt-5.2-codex", - "name": "GPT-5.2 Codex", - "display_name": "GPT-5.2 Codex", + "id": "mimo-v2.6-pro", + "name": "MiMo-V2.6-Pro", + "display_name": "MiMo-V2.6-Pro", "modalities": { "input": [ "text", - "image" + "image", + "audio", + "video" ], "output": [ "text" ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 1048576, + "output": 131072 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -168286,93 +168887,65 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", "effort_options": [ + "none", + "minimal", "low", "medium", "high", - "xhigh" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + "xhigh", + "max" + ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2025-12-11", - "last_updated": "2025-12-11", + "open_weights": true, + "release_date": "2026-09-22", + "last_updated": "2026-09-22", "type": "chat" }, { - "id": "claude-opus-4-5-20251101", - "name": "Claude Opus 4.5", - "display_name": "Claude Opus 4.5", + "id": "gemini-4-argon", + "name": "Gemini 4 Argon", + "display_name": "Gemini 4 Argon", "modalities": { "input": [ "text", - "image" + "image", + "audio" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 32000 + "context": 1048576, + "output": 1048576 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "mixed", - "budget": { - "min": 1024, - "unit": "tokens" - }, - "effort": "high", "effort_options": [ - "low", - "medium", "high" - ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Claude Opus 4.5 uses manual thinking.type = \"enabled\" with budget_tokens; effort can be used alongside the thinking budget.", - "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header." ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-03", - "release_date": "2025-11-24", - "last_updated": "2025-11-24", + "knowledge": "2026-03", + "release_date": "2026-10-13", + "last_updated": "2026-10-13", "type": "chat" }, { - "id": "gpt-5.2", - "name": "GPT-5.2", - "display_name": "GPT-5.2", + "id": "muse-spark-1.3-contributor", + "name": "Muse Spark 1.3 Contributor", + "display_name": "Muse Spark 1.3 Contributor", "modalities": { "input": [ "text", @@ -168383,60 +168956,40 @@ ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 1048576, + "output": 1048576 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "none", "effort_options": [ - "none", + "minimal", "low", "medium", "high", "xhigh" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2025-12-11", - "last_updated": "2025-12-11", + "release_date": "2026-09-02", + "last_updated": "2026-09-02", "type": "chat" - } - ] - }, - "crusoe": { - "id": "crusoe", - "name": "Crusoe", - "display_name": "Crusoe", - "api": "https://api.inference.crusoecloud.com/v1", - "doc": "https://docs.crusoecloud.com/managed-inference/overview", - "models": [ + }, { - "id": "Qwen/Qwen3-235B-A22B-Instruct-2507", - "name": "Qwen3 235B-A22B Instruct 2507", - "display_name": "Qwen3 235B-A22B Instruct 2507", + "id": "ministral-14b-2512", + "name": "Ministral 14B", + "display_name": "Ministral 14B", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" @@ -168444,60 +168997,67 @@ }, "limit": { "context": 262144, - "output": 16384 + "output": 8192 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { "supported": false }, - "attachment": false, + "attachment": true, "open_weights": true, - "release_date": "2025-07-21", - "last_updated": "2025-07-21", + "release_date": "2025-12-02", + "last_updated": "2025-12-02", "type": "chat" }, { - "id": "zai/GLM-5.1", - "name": "GLM-5.1", - "display_name": "GLM-5.1", + "id": "qwen-plus-latest", + "name": "Qwen Plus Latest", + "display_name": "Qwen Plus Latest", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 131072 + "context": 1000000, + "output": 8192 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true + "supported": true }, "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-04-07", - "last_updated": "2026-04-07", + "attachment": true, + "open_weights": false, + "release_date": "2025-01-25", + "last_updated": "2025-01-25", "type": "chat" }, { - "id": "zai/GLM-5.2", - "name": "GLM-5.2", - "display_name": "GLM-5.2", + "id": "claude-opus-4-7", + "name": "Claude Opus 4.7", + "display_name": "Claude Opus 4.7", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" @@ -168505,30 +169065,51 @@ }, "limit": { "context": 1000000, - "output": 131072 + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true + "default_enabled": false, + "mode": "effort", + "effort": "high", + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "omitted", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Claude Opus 4.7 and newer Opus models require thinking.type = \"adaptive\" to enable thinking explicitly.", + "Manual budget_tokens requests return 400 on Claude Opus 4.7 and newer adaptive-only Opus models.", + "task_budget is separate from thinking control and should not be treated as a thinking budget." + ] } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-06-13", - "last_updated": "2026-06-13", + "attachment": true, + "open_weights": false, + "knowledge": "2026-01-31", + "release_date": "2026-04-16", + "last_updated": "2026-04-16", "type": "chat" }, { - "id": "nvidia/NVIDIA-Nemotron-3-Nano-30B-A3B", - "name": "Nemotron 3 Nano 30B A3B", - "display_name": "Nemotron 3 Nano 30B A3B", + "id": "qwen3.7-max", + "name": "Qwen3.7 Max", + "display_name": "Qwen3.7 Max", "modalities": { "input": [ "text" @@ -168538,8 +169119,8 @@ ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 1000000, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -168547,64 +169128,70 @@ "supported": true, "default": true }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "effort_options": [ + "none", + "minimal", + "low", + "medium", + "high", + "xhigh", + "max" + ] + } + }, "attachment": false, - "open_weights": true, - "knowledge": "2025-06-25", - "release_date": "2025-12-15", - "last_updated": "2025-12-15", + "open_weights": false, + "release_date": "2026-05-21", + "last_updated": "2026-05-21", "type": "chat" }, { - "id": "nvidia/NVIDIA-Nemotron-3-Super-120B-A12B", - "name": "Nemotron 3 Super 120B A12B", - "display_name": "Nemotron 3 Super 120B A12B", + "id": "gpt-4o-mini-transcribe", + "name": "GPT-4o Mini Transcribe", + "display_name": "GPT-4o Mini Transcribe", "modalities": { "input": [ - "text" + "text", + "audio" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 16000, + "output": 16000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true - } + "supported": false }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-06", - "release_date": "2026-03-11", - "last_updated": "2026-03-11", + "attachment": true, + "open_weights": false, + "release_date": "2025-03-20", + "last_updated": "2025-03-20", "type": "chat" }, { - "id": "nvidia/Nemotron-3-Nano-Omni-Reasoning-30B-A3B", - "name": "Nemotron 3 Nano Omni 30B A3B Reasoning", - "display_name": "Nemotron 3 Nano Omni 30B A3B Reasoning", + "id": "qwen3.6-flash", + "name": "Qwen3.6 Flash", + "display_name": "Qwen3.6 Flash", "modalities": { "input": [ "text", "image", - "video", - "audio" + "video" ], "output": [ "text" ] }, "limit": { - "context": 256000, + "context": 983616, "output": 65536 }, "temperature": true, @@ -168616,54 +169203,85 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "effort_options": [ + "none", + "minimal", + "low", + "medium", + "high", + "xhigh", + "max" + ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-04-28", - "last_updated": "2026-04-28", + "open_weights": false, + "release_date": "2026-04-27", + "last_updated": "2026-04-27", "type": "chat" }, { - "id": "google/gemma-4-31b-it", - "name": "Gemma 4 31B IT", - "display_name": "Gemma 4 31B IT", + "id": "claude-haiku-4-5", + "name": "Claude Haiku 4.5 (latest)", + "display_name": "Claude Haiku 4.5 (latest)", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 32768 + "context": 200000, + "output": 64000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { - "supported": true + "supported": true, + "default_enabled": false, + "mode": "budget", + "budget": { + "min": 1024, + "unit": "tokens" + }, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", + "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." + ] } }, "attachment": true, - "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-04-02", - "last_updated": "2026-04-02", + "open_weights": false, + "knowledge": "2025-02-28", + "release_date": "2025-10-15", + "last_updated": "2025-10-15", "type": "chat" }, { - "id": "openai/gpt-oss-120b", - "name": "GPT OSS 120B", - "display_name": "GPT OSS 120B", + "id": "qwen-plus", + "name": "Qwen Plus", + "display_name": "Qwen Plus", "modalities": { "input": [ "text" @@ -168686,29 +169304,28 @@ "reasoning": { "supported": true, "interleaved": true, - "effort_options": [ - "low", - "medium", - "high" + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, "attachment": false, - "open_weights": true, - "knowledge": "2024-06", - "release_date": "2025-08-05", - "last_updated": "2025-08-05", + "open_weights": false, + "knowledge": "2024-04", + "release_date": "2024-01-25", + "last_updated": "2025-09-11", "type": "chat" }, { - "id": "moonshotai/Kimi-K2.6", - "name": "Kimi K2.6", - "display_name": "Kimi K2.6", + "id": "seed-2-0-pro-260328", + "name": "Seed 2.0 Pro (260328)", + "display_name": "Seed 2.0 Pro (260328)", "modalities": { "input": [ "text", - "image", - "video" + "image" ], "output": [ "text" @@ -168726,85 +169343,24 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true + "effort_options": [ + "minimal", + "low", + "medium", + "high" + ] } }, "attachment": true, - "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-04-20", - "last_updated": "2026-04-20", - "type": "chat" - }, - { - "id": "meta-llama/Llama-3.3-70B-Instruct", - "name": "Llama-3.3-70B-Instruct", - "display_name": "Llama-3.3-70B-Instruct", - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 4096 - }, - "temperature": true, - "tool_call": true, - "reasoning": { - "supported": false - }, - "attachment": false, - "open_weights": true, - "knowledge": "2023-12", - "release_date": "2024-12-06", - "last_updated": "2024-12-06", + "open_weights": false, + "release_date": "2026-03-28", + "last_updated": "2026-03-28", "type": "chat" }, { - "id": "deepseek-ai/DeepSeek-V3-0324", - "name": "DeepSeek V3 0324", - "display_name": "DeepSeek V3 0324", - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 163840, - "output": 163840 - }, - "temperature": true, - "tool_call": true, - "reasoning": { - "supported": false - }, - "attachment": false, - "open_weights": true, - "release_date": "2025-03-24", - "last_updated": "2025-03-24", - "type": "chat" - } - ] - }, - "ainetcafe": { - "id": "ainetcafe", - "name": "ainetcafe", - "display_name": "ainetcafe", - "api": "https://microquickjs.com/v1", - "doc": "https://ainetcafe.com/k3/guides/", - "models": [ - { - "id": "Kimi-K3", - "name": "Kimi K3", - "display_name": "Kimi K3", + "id": "claude-sonnet-5", + "name": "Claude Sonnet 5", + "display_name": "Claude Sonnet 5", "modalities": { "input": [ "text", @@ -168815,11 +169371,11 @@ ] }, "limit": { - "context": 262144, - "output": 32768 + "context": 1000000, + "output": 1000000 }, "temperature": false, - "tool_call": true, + "tool_call": false, "reasoning": { "supported": true, "default": true @@ -168827,49 +169383,40 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], "effort_options": [ + "none", "low", + "medium", "high", + "xhigh", "max" ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-07-16", - "last_updated": "2026-07-16", + "open_weights": false, + "knowledge": "2026-01-31", + "release_date": "2026-06-30", + "last_updated": "2026-06-30", "type": "chat" - } - ] - }, - "blueclaw": { - "id": "blueclaw", - "name": "Blue Claw", - "display_name": "Blue Claw", - "api": "https://openai.blueclaw.network/v1", - "doc": "https://blueclaw.network", - "models": [ + }, { - "id": "Qwen3.8-27B", - "name": "Qwen3.8 27B", - "display_name": "Qwen3.8 27B", + "id": "qwen3.8-flash", + "name": "Qwen3.8 Flash", + "display_name": "Qwen3.8 Flash", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 32768 + "context": 1000000, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -168879,43 +169426,41 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, "effort_options": [ + "none", + "minimal", "low", "medium", - "xhigh" + "high", + "xhigh", + "max" ] } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-08-14", - "last_updated": "2026-08-14", + "attachment": true, + "open_weights": false, + "release_date": "2026-08-26", + "last_updated": "2026-08-26", "type": "chat" - } - ] - }, - "ebcloud": { - "id": "ebcloud", - "name": "EBCloud", - "display_name": "EBCloud", - "api": "https://maas-api.ebcloud.com/v1", - "doc": "https://docs.ebtech.com/ai/model-api.html", - "models": [ + }, { - "id": "GLM-5.1", - "name": "GLM-5.1", - "display_name": "GLM-5.1", + "id": "qwen3.6-35b-a3b", + "name": "Qwen3.6 35B-A3B", + "display_name": "Qwen3.6 35B-A3B", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 131072 + "context": 262144, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -168931,19 +169476,28 @@ "visibility": "summary", "continuation": [ "thinking_blocks" + ], + "effort_options": [ + "none", + "minimal", + "low", + "medium", + "high", + "xhigh", + "max" ] } }, - "attachment": false, + "attachment": true, "open_weights": true, - "release_date": "2026-04-07", - "last_updated": "2026-04-07", + "release_date": "2026-04-17", + "last_updated": "2026-04-17", "type": "chat" }, { - "id": "Kimi-K2.6", - "name": "Kimi K2.6", - "display_name": "Kimi K2.6", + "id": "qwen3-30b-a3b", + "name": "Qwen3 30B A3B", + "display_name": "Qwen3 30B A3B", "modalities": { "input": [ "text" @@ -168953,8 +169507,8 @@ ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 32768, + "output": 16384 }, "temperature": true, "tool_call": true, @@ -168975,26 +169529,26 @@ }, "attachment": false, "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-04-20", - "last_updated": "2026-04-20", + "release_date": "2025-04-28", + "last_updated": "2025-04-28", "type": "chat" }, { - "id": "DeepSeek-V4-Pro", - "name": "DeepSeek V4 Pro", - "display_name": "DeepSeek V4 Pro", + "id": "deepseek-v4.1-flash-1017", + "name": "DeepSeek V4.1 Flash 1017", + "display_name": "DeepSeek V4.1 Flash 1017", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 384000 + "context": 1050000, + "output": 1050000 }, "temperature": true, "tool_call": true, @@ -169004,48 +169558,36 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "high", "effort_options": [ "low", "high", "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." ] } }, - "attachment": false, + "attachment": true, "open_weights": true, "knowledge": "2025-05", - "release_date": "2026-04-24", - "last_updated": "2026-04-24", + "release_date": "2026-10-17", + "last_updated": "2026-10-17", "type": "chat" }, { - "id": "DeepSeek-V4-Flash", - "name": "DeepSeek V4 Flash", - "display_name": "DeepSeek V4 Flash", + "id": "qwen3.7-flash", + "name": "Qwen3.7 Flash", + "display_name": "Qwen3.7 Flash", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 384000 + "context": 983616, + "output": 1000000 }, "temperature": true, "tool_call": true, @@ -169056,45 +169598,27 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "high", "effort_options": [ + "none", + "minimal", "low", + "medium", "high", + "xhigh", "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." ] } }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-04-24", - "last_updated": "2026-04-24", + "attachment": true, + "open_weights": false, + "release_date": "2026-07-15", + "last_updated": "2026-07-15", "type": "chat" - } - ] - }, - "abacus": { - "id": "abacus", - "name": "Abacus", - "display_name": "Abacus", - "api": "https://routellm.abacus.ai/v1", - "doc": "https://abacus.ai/help/api", - "models": [ + }, { - "id": "mimo-v2-pro", - "name": "MiMo-V2-Pro", - "display_name": "MiMo-V2-Pro", + "id": "glm-4.5-x", + "name": "GLM-4.5 X", + "display_name": "GLM-4.5 X", "modalities": { "input": [ "text" @@ -169104,8 +169628,8 @@ ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 128000, + "output": 16384 }, "temperature": true, "tool_call": true, @@ -169115,15 +169639,64 @@ }, "attachment": false, "open_weights": false, - "knowledge": "2024-12", - "release_date": "2026-03-18", - "last_updated": "2026-03-18", + "release_date": "2025-07-28", + "last_updated": "2025-07-28", "type": "chat" }, { - "id": "kimi-k2-turbo-preview", - "name": "Kimi K2 Turbo Preview", - "display_name": "Kimi K2 Turbo Preview", + "id": "kimi-k2.7-code-highspeed", + "name": "Kimi K2.7 Code Highspeed", + "display_name": "Kimi K2.7 Code Highspeed", + "modalities": { + "input": [ + "text", + "image", + "video" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 262144, + "output": 262144 + }, + "temperature": false, + "tool_call": true, + "reasoning": { + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "effort_options": [ + "minimal", + "low", + "medium", + "high", + "xhigh", + "max" + ] + } + }, + "attachment": true, + "open_weights": true, + "knowledge": "2025-01", + "release_date": "2026-06-12", + "last_updated": "2026-06-12", + "type": "chat" + }, + { + "id": "llama-3.3-70b-instruct", + "name": "Llama-3.3-70B-Instruct", + "display_name": "Llama-3.3-70B-Instruct", "modalities": { "input": [ "text" @@ -169133,8 +169706,8 @@ ] }, "limit": { - "context": 256000, - "output": 8192 + "context": 131072, + "output": 4096 }, "temperature": true, "tool_call": true, @@ -169142,28 +169715,28 @@ "supported": false }, "attachment": false, - "open_weights": false, - "release_date": "2025-07-08", - "last_updated": "2025-07-08", + "open_weights": true, + "knowledge": "2023-12", + "release_date": "2024-12-06", + "last_updated": "2024-12-06", "type": "chat" }, { - "id": "gemini-3-flash-preview", - "name": "Gemini 3 Flash Preview", - "display_name": "Gemini 3 Flash Preview", + "id": "seed-2-0-code-preview-260328", + "name": "Seed 2.0 Code Preview (260328)", + "display_name": "Seed 2.0 Code Preview (260328)", "modalities": { "input": [ "text", - "image", - "audio" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 262144, + "output": 262144 }, "temperature": true, "tool_call": true, @@ -169173,96 +169746,63 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "level", - "level": "high", - "level_options": [ + "effort_options": [ "minimal", "low", "medium", "high" - ], - "summaries": true, - "visibility": "summary", - "continuation": [ - "thought_signatures" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-01", - "release_date": "2025-12-17", - "last_updated": "2025-12-17", + "release_date": "2026-03-28", + "last_updated": "2026-03-28", "type": "chat" }, { - "id": "gpt-5.1-codex-max", - "name": "GPT-5.1 Codex Max", - "display_name": "GPT-5.1 Codex Max", + "id": "codestral-2508", + "name": "Codestral", + "display_name": "Codestral", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 256000, + "output": 16384 }, - "temperature": false, - "tool_call": true, + "temperature": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": false - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "none", - "effort_options": [ - "none", - "low", - "medium", - "high" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" - } + "supported": false }, - "attachment": true, + "attachment": false, "open_weights": false, - "knowledge": "2024-09-30", - "release_date": "2025-11-13", - "last_updated": "2025-11-13", + "release_date": "2025-07-30", + "last_updated": "2025-07-30", "type": "chat" }, { - "id": "gpt-5", - "name": "GPT-5", - "display_name": "GPT-5", + "id": "claude-fable-5", + "name": "Claude Fable 5", + "display_name": "Claude Fable 5", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 400000, + "context": 1000000, "output": 128000 }, "temperature": false, @@ -169276,33 +169816,38 @@ "supported": true, "default_enabled": true, "mode": "effort", - "effort": "medium", + "effort": "high", "effort_options": [ - "minimal", "low", "medium", - "high" + "high", + "xhigh", + "max" ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" + "interleaved": true, + "summaries": true, + "visibility": "omitted", + "continuation": [ + "thinking_blocks" ], - "visibility": "hidden" + "notes": [ + "Adaptive thinking is always on for Claude Fable 5 and Claude Mythos 5; thinking.type = \"disabled\" is rejected.", + "Manual budget_tokens requests return 400 on Claude Fable 5 and Claude Mythos 5.", + "thinking.display defaults to omitted; set display to summarized to receive readable thinking summaries." + ] } }, "attachment": true, "open_weights": false, - "knowledge": "2024-09-30", - "release_date": "2025-08-07", - "last_updated": "2025-08-07", + "knowledge": "2026-01-31", + "release_date": "2026-06-09", + "last_updated": "2026-06-09", "type": "chat" }, { - "id": "qwen3.7-max", - "name": "Qwen3.7 Max", - "display_name": "Qwen3.7 Max", + "id": "nemotron-3-ultra-550b", + "name": "Nemotron 3 Ultra 550B A55B", + "display_name": "Nemotron 3 Ultra 550B A55B", "modalities": { "input": [ "text" @@ -169312,8 +169857,8 @@ ] }, "limit": { - "context": 1000000, - "output": 64000 + "context": 1048576, + "output": 128000 }, "temperature": true, "tool_call": true, @@ -169321,32 +169866,60 @@ "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "supported": true - } - }, "attachment": false, - "open_weights": false, - "release_date": "2026-05-21", - "last_updated": "2026-05-21", + "open_weights": true, + "knowledge": "2025-09", + "release_date": "2026-06-04", + "last_updated": "2026-06-04", "type": "chat" }, { - "id": "qwen3-max", - "name": "Qwen3 Max", - "display_name": "Qwen3 Max", + "id": "glm-4.6v", + "name": "GLM-4.6V", + "display_name": "GLM-4.6V", + "modalities": { + "input": [ + "text", + "image", + "video" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "output": 32768 + }, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": true, + "default": true + }, + "attachment": true, + "open_weights": true, + "knowledge": "2025-04", + "release_date": "2025-12-08", + "last_updated": "2025-12-08", + "type": "chat" + }, + { + "id": "grok-4-7", + "name": "Grok 4.7", + "display_name": "Grok 4.7", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 16384 + "context": 524288, + "output": 500000 }, "temperature": true, "tool_call": true, @@ -169356,26 +169929,26 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "minimal", + "low", + "medium", + "high", + "xhigh" ] } }, - "attachment": false, + "attachment": true, "open_weights": false, - "knowledge": "2025-04", - "release_date": "2025-09-23", - "last_updated": "2025-09-23", + "knowledge": "2026-05", + "release_date": "2026-09-21", + "last_updated": "2026-09-21", "type": "chat" }, { - "id": "route-llm", - "name": "RouteLLM", - "display_name": "RouteLLM", + "id": "reka-edge-2603", + "name": "Reka Edge 2603", + "display_name": "Reka Edge 2603", "modalities": { "input": [ "text", @@ -169386,8 +169959,8 @@ ] }, "limit": { - "context": 128000, - "output": 64000 + "context": 16384, + "output": 16384 }, "temperature": true, "tool_call": true, @@ -169396,14 +169969,14 @@ }, "attachment": true, "open_weights": false, - "release_date": "2024-01-01", - "last_updated": "2026-07-10", + "release_date": "2026-10-09", + "last_updated": "2026-10-09", "type": "chat" }, { - "id": "gpt-4o-mini", - "name": "GPT-4o mini", - "display_name": "GPT-4o mini", + "id": "gpt-5-nano", + "name": "GPT-5 Nano", + "display_name": "GPT-5 Nano", "modalities": { "input": [ "text", @@ -169414,75 +169987,99 @@ ] }, "limit": { - "context": 128000, - "output": 16384 + "context": 400000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", + "effort_options": [ + "minimal", + "low", + "medium", + "high" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" + } }, "attachment": true, "open_weights": false, - "knowledge": "2023-09", - "release_date": "2024-07-18", - "last_updated": "2024-07-18", + "knowledge": "2024-05-30", + "release_date": "2025-08-07", + "last_updated": "2025-08-07", "type": "chat" }, { - "id": "gemini-2.5-pro", - "name": "Gemini 2.5 Pro", - "display_name": "Gemini 2.5 Pro", + "id": "claude-sonnet-4-5", + "name": "Claude Sonnet 4.5 (latest)", + "display_name": "Claude Sonnet 4.5 (latest)", "modalities": { "input": [ "text", "image", - "audio" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 200000, + "output": 64000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, + "default_enabled": false, "mode": "budget", "budget": { - "default": -1, - "min": 128, - "max": 32768, - "auto": -1, + "min": 1024, "unit": "tokens" }, + "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ - "thought_signatures" + "thinking_blocks" + ], + "notes": [ + "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", + "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-01", - "release_date": "2025-06-17", - "last_updated": "2025-06-17", + "knowledge": "2025-07-31", + "release_date": "2025-09-29", + "last_updated": "2025-09-29", "type": "chat" }, { - "id": "gpt-5.5", - "name": "GPT-5.5", - "display_name": "GPT-5.5", + "id": "seed-1-6-250615", + "name": "Seed 1.6 (250615)", + "display_name": "Seed 1.6 (250615)", "modalities": { "input": [ "text", @@ -169493,10 +170090,10 @@ ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 256000, + "output": 8192 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -169504,36 +170101,24 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", "effort_options": [ - "low", - "medium", - "high", - "xhigh" - ], - "verbosity": "medium", - "verbosity_options": [ + "minimal", "low", "medium", "high" - ], - "visibility": "hidden" + ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2025-12-01", - "release_date": "2026-04-23", - "last_updated": "2026-04-23", + "open_weights": true, + "release_date": "2025-06-25", + "last_updated": "2025-06-25", "type": "chat" }, { - "id": "gpt-4.1-nano", - "name": "GPT-4.1 nano", - "display_name": "GPT-4.1 nano", + "id": "grok-4-5", + "name": "Grok 4.5", + "display_name": "Grok 4.5", "modalities": { "input": [ "text", @@ -169544,119 +170129,90 @@ ] }, "limit": { - "context": 1047576, - "output": 32768 + "context": 500000, + "output": 500000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false - }, - "attachment": true, - "open_weights": false, - "knowledge": "2024-04", - "release_date": "2025-04-14", - "last_updated": "2025-04-14", - "type": "chat" - }, - { - "id": "gemini-2.5-flash-image", - "name": "Nano Banana", - "display_name": "Nano Banana", - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text", - "image" - ] - }, - "limit": { - "context": 32768, - "output": 32768 + "supported": true, + "default": true }, - "temperature": true, - "tool_call": false, - "reasoning": { - "supported": false + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "low", + "medium", + "high" + ] + } }, "attachment": true, "open_weights": false, - "knowledge": "2024-06", - "release_date": "2025-08-26", - "last_updated": "2025-08-26" + "release_date": "2026-07-08", + "last_updated": "2026-07-08", + "type": "chat" }, { - "id": "gpt-5.4", - "name": "GPT-5.4", - "display_name": "GPT-5.4", + "id": "glm-5.3-flash", + "name": "GLM-5.3-Flash", + "display_name": "GLM-5.3-Flash", "modalities": { "input": [ "text", - "image" + "image", + "video", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 1048576, + "output": 131072 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "none", "effort_options": [ "none", + "minimal", "low", "medium", "high", - "xhigh" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + "xhigh", + "max" + ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-03-05", - "last_updated": "2026-03-05", + "open_weights": true, + "release_date": "2026-08-26", + "last_updated": "2026-08-26", "type": "chat" }, { - "id": "gemini-3.5-flash-lite", - "name": "Gemini 3.5 Flash Lite", - "display_name": "Gemini 3.5 Flash Lite", + "id": "glm-4.7", + "name": "GLM-4.7", + "display_name": "GLM-4.7", "modalities": { "input": [ - "text", - "image", - "audio" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 204800, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -169666,54 +170222,76 @@ }, "extra_capabilities": { "reasoning": { - "supported": true + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-03", - "release_date": "2026-07-21", - "last_updated": "2026-07-21", + "attachment": false, + "open_weights": true, + "knowledge": "2025-04", + "release_date": "2025-12-22", + "last_updated": "2025-12-22", "type": "chat" }, { - "id": "gpt-5.1-chat-latest", - "name": "GPT-5.1 Chat Latest", - "display_name": "GPT-5.1 Chat Latest", + "id": "mimo-v2.6-flash", + "name": "MiMo-V2.6-Flash", + "display_name": "MiMo-V2.6-Flash", "modalities": { "input": [ "text", - "image" + "image", + "audio", + "video" ], "output": [ "text" ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 1048576, + "output": 131072 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, "default": true }, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "none", + "minimal", + "low", + "medium", + "high", + "xhigh", + "max" + ] + } + }, "attachment": true, - "open_weights": false, - "knowledge": "2024-09-30", - "release_date": "2025-11-13", - "last_updated": "2025-11-13", + "open_weights": true, + "release_date": "2026-09-22", + "last_updated": "2026-09-22", "type": "chat" }, { - "id": "claude-sonnet-4-20250514", - "name": "Claude Sonnet 4", - "display_name": "Claude Sonnet 4", + "id": "gemini-3.6-flash", + "name": "Gemini 3.6 Flash", + "display_name": "Gemini 3.6 Flash", "modalities": { "input": [ "text", "image", + "video", + "audio", "pdf" ], "output": [ @@ -169721,96 +170299,86 @@ ] }, "limit": { - "context": 200000, - "output": 64000 + "context": 1048576, + "output": 65536 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "budget", - "budget": { - "min": 1024, - "unit": "tokens" - }, - "interleaved": true, + "default_enabled": true, + "mode": "level", + "level": "high", + "level_options": [ + "minimal", + "low", + "medium", + "high" + ], "summaries": true, "visibility": "summary", "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", - "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." + "thought_signatures" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-03-31", - "release_date": "2025-05-22", - "last_updated": "2025-05-22", + "knowledge": "2026-03", + "release_date": "2026-07-21", + "last_updated": "2026-07-21", "type": "chat" }, { - "id": "gemini-3.1-flash-image", - "name": "Nano Banana 2", - "display_name": "Nano Banana 2", + "id": "ministral-8b-2512", + "name": "Ministral 8B", + "display_name": "Ministral 8B", "modalities": { "input": [ "text", "image" ], "output": [ - "text", - "image" + "text" ] }, "limit": { - "context": 1048576, - "output": 32768 + "context": 262144, + "output": 8192 }, "temperature": true, "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true - } + "supported": false }, "attachment": true, - "open_weights": false, - "knowledge": "2025-01", - "release_date": "2026-05-28", - "last_updated": "2026-05-28" + "open_weights": true, + "release_date": "2025-12-02", + "last_updated": "2025-12-02", + "type": "chat" }, { - "id": "claude-opus-5", - "name": "Claude Opus 5", - "display_name": "Claude Opus 5", + "id": "glm-4.6", + "name": "GLM-4.6", + "display_name": "GLM-4.6", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 204800, + "output": 131072 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -169821,17 +170389,17 @@ "supported": true } }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-05", - "release_date": "2026-07-24", - "last_updated": "2026-07-24", + "attachment": false, + "open_weights": true, + "knowledge": "2025-04", + "release_date": "2025-09-30", + "last_updated": "2025-09-30", "type": "chat" }, { - "id": "claude-opus-4-7", - "name": "Claude Opus 4.7", - "display_name": "Claude Opus 4.7", + "id": "deepseek-v3.2", + "name": "DeepSeek V3.2", + "display_name": "DeepSeek V3.2", "modalities": { "input": [ "text", @@ -169842,56 +170410,34 @@ ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 163840, + "output": 16384 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "high", - "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "omitted", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Claude Opus 4.7 and newer Opus models require thinking.type = \"adaptive\" to enable thinking explicitly.", - "Manual budget_tokens requests return 400 on Claude Opus 4.7 and newer adaptive-only Opus models.", - "task_budget is separate from thinking control and should not be treated as a thinking budget." - ] + "supported": true } }, "attachment": true, - "open_weights": false, - "knowledge": "2026-01-31", - "release_date": "2026-04-16", - "last_updated": "2026-04-16", + "open_weights": true, + "knowledge": "2024-07", + "release_date": "2025-12-01", + "last_updated": "2025-12-01", "type": "chat" }, { - "id": "gpt-4o", - "name": "GPT-4o", - "display_name": "GPT-4o", + "id": "llama-3.1-70b-instruct", + "name": "Llama 3.1 70B Instruct", + "display_name": "Llama 3.1 70B Instruct", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" @@ -169899,24 +170445,24 @@ }, "limit": { "context": 128000, - "output": 16384 + "output": 2048 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { "supported": false }, - "attachment": true, - "open_weights": false, - "knowledge": "2023-09", - "release_date": "2024-05-13", - "last_updated": "2024-08-06", + "attachment": false, + "open_weights": true, + "knowledge": "2023-12", + "release_date": "2024-07-23", + "last_updated": "2024-07-23", "type": "chat" }, { - "id": "grok-4.5", - "name": "Grok 4.5", - "display_name": "Grok 4.5", + "id": "ministral-3b-2512", + "name": "Ministral 3B", + "display_name": "Ministral 3B", "modalities": { "input": [ "text", @@ -169927,10 +170473,39 @@ ] }, "limit": { - "context": 500000, - "output": 32768 + "context": 131072, + "output": 8192 }, "temperature": true, + "tool_call": false, + "reasoning": { + "supported": false + }, + "attachment": true, + "open_weights": true, + "release_date": "2025-12-02", + "last_updated": "2025-12-02", + "type": "chat" + }, + { + "id": "gpt-5.5-pro", + "name": "GPT-5.5 Pro", + "display_name": "GPT-5.5 Pro", + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1050000, + "output": 128000 + }, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -169938,19 +170513,25 @@ }, "extra_capabilities": { "reasoning": { - "supported": true + "supported": true, + "effort_options": [ + "medium", + "high", + "xhigh" + ] } }, "attachment": true, "open_weights": false, - "release_date": "2026-07-08", - "last_updated": "2026-07-08", + "knowledge": "2025-12-01", + "release_date": "2026-04-23", + "last_updated": "2026-04-23", "type": "chat" }, { - "id": "grok-4-1-fast-non-reasoning", - "name": "Grok 4.1 Fast (Non-Reasoning)", - "display_name": "Grok 4.1 Fast (Non-Reasoning)", + "id": "kimi-k3.1-pro-highspeed", + "name": "Kimi K3.1 Pro Highspeed", + "display_name": "Kimi K3.1 Pro Highspeed", "modalities": { "input": [ "text", @@ -169961,37 +170542,51 @@ ] }, "limit": { - "context": 2000000, - "output": 16384 + "context": 1048576, + "output": 1048576 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "none", + "minimal", + "low", + "medium", + "high", + "xhigh", + "max" + ] + } }, "attachment": true, "open_weights": false, - "release_date": "2025-11-17", - "last_updated": "2025-11-17", + "release_date": "2026-10-23", + "last_updated": "2026-10-23", "type": "chat" }, { - "id": "kimi-k2.5", - "name": "Kimi K2.5", - "display_name": "Kimi K2.5", + "id": "gemini-3.8-flash", + "name": "Gemini 3.8 Flash", + "display_name": "Gemini 3.8 Flash", "modalities": { "input": [ "text", "image", - "video" + "audio" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 32768 + "context": 1048576, + "output": 1048576 }, "temperature": true, "tool_call": true, @@ -170002,72 +170597,74 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, + "default_enabled": true, + "mode": "level", + "level": "high", + "level_options": [ + "minimal", + "low", + "medium", + "high" + ], "summaries": true, "visibility": "summary", "continuation": [ - "thinking_blocks" + "thought_signatures" ] } }, "attachment": true, - "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-01-27", - "last_updated": "2026-01-27", + "open_weights": false, + "release_date": "2026-09-02", + "last_updated": "2026-09-02", "type": "chat" }, { - "id": "gemini-3-pro-image-preview", - "name": "Nano Banana Pro Preview", - "display_name": "Nano Banana Pro Preview", + "id": "claude-opus-5-5", + "name": "Claude Opus 5.5", + "display_name": "Claude Opus 5.5", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ - "text", - "image" + "text" ] }, "limit": { - "context": 65536, - "output": 32768 + "context": 1000000, + "output": 128000 }, - "temperature": true, - "tool_call": false, + "temperature": false, + "tool_call": true, "reasoning": { "supported": true, "default": true }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "level", - "level": "high", - "level_options": [ + "effort_options": [ "low", - "high" - ], - "summaries": true, - "visibility": "summary", - "continuation": [ - "thought_signatures" + "medium", + "high", + "xhigh", + "max" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-01", - "release_date": "2025-11-20", - "last_updated": "2025-11-20" + "knowledge": "2026-06", + "release_date": "2026-09-22", + "last_updated": "2026-09-22", + "type": "chat" }, { - "id": "o3", - "name": "o3", - "display_name": "o3", + "id": "seed-2-0-mini-260428", + "name": "Seed 2.0 Mini (260428)", + "display_name": "Seed 2.0 Mini (260428)", "modalities": { "input": [ "text", @@ -170078,10 +170675,10 @@ ] }, "limit": { - "context": 200000, - "output": 100000 + "context": 262144, + "output": 262144 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -170089,44 +170686,39 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", "effort_options": [ + "minimal", "low", "medium", "high" - ], - "visibility": "hidden" + ] } }, "attachment": true, "open_weights": false, - "knowledge": "2024-05", - "release_date": "2025-04-16", - "last_updated": "2025-04-16", + "release_date": "2026-04-28", + "last_updated": "2026-04-28", "type": "chat" }, { - "id": "gemini-2.5-flash", - "name": "Gemini 2.5 Flash", - "display_name": "Gemini 2.5 Flash", + "id": "gpt-5.6-luna", + "name": "GPT-5.6 Luna", + "display_name": "GPT-5.6 Luna", "modalities": { "input": [ "text", "image", - "audio" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 1050000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -170136,33 +170728,36 @@ "reasoning": { "supported": true, "default_enabled": true, - "mode": "budget", - "budget": { - "default": -1, - "min": 0, - "max": 24576, - "auto": -1, - "off": 0, - "unit": "tokens" - }, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thought_signatures" - ] + "mode": "effort", + "effort": "medium", + "effort_options": [ + "none", + "low", + "medium", + "high", + "xhigh", + "max" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, "attachment": true, "open_weights": false, - "knowledge": "2025-01", - "release_date": "2025-06-17", - "last_updated": "2025-06-17", + "knowledge": "2026-02-16", + "release_date": "2026-07-09", + "last_updated": "2026-07-09", "type": "chat" }, { - "id": "claude-sonnet-5", - "name": "Claude Sonnet 5", - "display_name": "Claude Sonnet 5", + "id": "fugu-ultra-v2.0", + "name": "Fugu Ultra v2.0", + "display_name": "Fugu Ultra v2.0", "modalities": { "input": [ "text", @@ -170174,9 +170769,9 @@ }, "limit": { "context": 1000000, - "output": 64000 + "output": 1000000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -170184,63 +170779,77 @@ }, "extra_capabilities": { "reasoning": { - "supported": true + "effort_options": [ + "high", + "xhigh", + "max" + ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-01-31", - "release_date": "2026-06-30", - "last_updated": "2026-06-30", + "release_date": "2026-09-11", + "last_updated": "2026-09-11", "type": "chat" }, { - "id": "gpt-5.2-chat-latest", - "name": "GPT-5.2 Chat Latest", - "display_name": "GPT-5.2 Chat Latest", + "id": "kimi-k3-fast", + "name": "Kimi K3", + "display_name": "Kimi K3", "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 1040384, + "output": 131072 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, "default": true }, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "none", + "low", + "medium", + "high", + "xhigh", + "max" + ] + } + }, "attachment": true, - "open_weights": false, - "knowledge": "2024-09-30", - "release_date": "2025-12-11", - "last_updated": "2025-12-11", + "open_weights": true, + "release_date": "2026-07-16", + "last_updated": "2026-07-16", "type": "chat" }, { - "id": "gemini-3.1-flash-lite", - "name": "Gemini 3.1 Flash Lite", - "display_name": "Gemini 3.1 Flash Lite", + "id": "seed-2-0-lite-260428", + "name": "Seed 2.0 Lite (260428)", + "display_name": "Seed 2.0 Lite (260428)", "modalities": { "input": [ "text", - "image", - "audio" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 262144, + "output": 262144 }, "temperature": true, "tool_call": true, @@ -170250,20 +170859,24 @@ }, "extra_capabilities": { "reasoning": { - "supported": true + "effort_options": [ + "minimal", + "low", + "medium", + "high" + ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-01", - "release_date": "2026-05-07", - "last_updated": "2026-05-07", + "release_date": "2026-04-28", + "last_updated": "2026-04-28", "type": "chat" }, { - "id": "grok-code-fast-1", - "name": "Grok Code Fast 1", - "display_name": "Grok Code Fast 1", + "id": "glm-4.5-air", + "name": "GLM-4.5-Air", + "display_name": "GLM-4.5-Air", "modalities": { "input": [ "text" @@ -170273,45 +170886,40 @@ ] }, "limit": { - "context": 256000, - "output": 16384 + "context": 128000, + "output": 98304 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true - } + "supported": true, + "default": true }, "attachment": false, - "open_weights": false, - "release_date": "2025-09-02", - "last_updated": "2025-09-02", + "open_weights": true, + "knowledge": "2025-04", + "release_date": "2025-07-28", + "last_updated": "2025-07-28", "type": "chat" }, { - "id": "gemini-3.7-flash", - "name": "Gemini 3.7 Flash", - "display_name": "Gemini 3.7 Flash", + "id": "gpt-5", + "name": "GPT-5", + "display_name": "GPT-5", "modalities": { "input": [ "text", - "image", - "audio", - "video" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 400000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -170321,46 +170929,47 @@ "reasoning": { "supported": true, "default_enabled": true, - "mode": "level", - "level": "high", - "level_options": [ + "mode": "effort", + "effort": "medium", + "effort_options": [ "minimal", "low", "medium", "high" ], - "summaries": true, - "visibility": "summary", - "continuation": [ - "thought_signatures" - ] + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, "attachment": true, "open_weights": false, - "knowledge": "2026-03", - "release_date": "2026-08-13", - "last_updated": "2026-08-13", + "knowledge": "2024-09-30", + "release_date": "2025-08-07", + "last_updated": "2025-08-07", "type": "chat" }, { - "id": "o4-mini", - "name": "o4-mini", - "display_name": "o4-mini", + "id": "hy3", + "name": "Hy3", + "display_name": "Hy3", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 100000 + "context": 262144, + "output": 128000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -170369,28 +170978,26 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", "effort_options": [ + "none", + "minimal", "low", "medium", - "high" - ], - "visibility": "hidden" + "high", + "xhigh" + ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2024-05", - "release_date": "2025-04-16", - "last_updated": "2025-04-16", + "attachment": false, + "open_weights": true, + "release_date": "2026-07-06", + "last_updated": "2026-07-06", "type": "chat" }, { - "id": "gpt-5.6-sol", - "name": "GPT-5.6 Sol", - "display_name": "GPT-5.6 Sol", + "id": "gpt-5-mini", + "name": "GPT-5 Mini", + "display_name": "GPT-5 Mini", "modalities": { "input": [ "text", @@ -170401,7 +171008,7 @@ ] }, "limit": { - "context": 1000000, + "context": 400000, "output": 128000 }, "temperature": false, @@ -170417,12 +171024,10 @@ "mode": "effort", "effort": "medium", "effort_options": [ - "none", + "minimal", "low", "medium", - "high", - "xhigh", - "max" + "high" ], "verbosity": "medium", "verbosity_options": [ @@ -170435,15 +171040,15 @@ }, "attachment": true, "open_weights": false, - "knowledge": "2026-02-16", - "release_date": "2026-07-09", - "last_updated": "2026-07-09", + "knowledge": "2024-05-30", + "release_date": "2025-08-07", + "last_updated": "2025-08-07", "type": "chat" }, { - "id": "claude-sonnet-4-6", - "name": "Claude Sonnet 4.6", - "display_name": "Claude Sonnet 4.6", + "id": "deepseek-v4.1-flash", + "name": "DeepSeek V4.1 Flash", + "display_name": "DeepSeek V4.1 Flash", "modalities": { "input": [ "text", @@ -170454,58 +171059,41 @@ ] }, "limit": { - "context": 1000000, - "output": 64000 + "context": 1050000, + "output": 384000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "mixed", - "budget": { - "min": 1024, - "unit": "tokens" - }, - "effort": "high", "effort_options": [ "low", - "medium", "high", "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Anthropic recommends adaptive thinking with effort for Claude 4.6; budget_tokens remains a deprecated compatibility path.", - "Anthropic API defaults effort to high; lower effort levels should be chosen per workload." ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-02-17", - "last_updated": "2026-03-13", + "open_weights": true, + "knowledge": "2025-05", + "release_date": "2026-09-10", + "last_updated": "2026-09-10", "type": "chat" }, { - "id": "gpt-4o-2024-11-20", - "name": "GPT-4o (2024-11-20)", - "display_name": "GPT-4o (2024-11-20)", + "id": "gpt-4o-mini", + "name": "GPT-4o mini", + "display_name": "GPT-4o mini", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" @@ -170522,15 +171110,53 @@ }, "attachment": true, "open_weights": false, - "knowledge": "2024-10", - "release_date": "2024-11-20", - "last_updated": "2024-11-20", + "knowledge": "2023-09", + "release_date": "2024-07-18", + "last_updated": "2024-07-18", "type": "chat" }, { - "id": "qwen-2.5-coder-32b", - "name": "Qwen 2.5 Coder 32B", - "display_name": "Qwen 2.5 Coder 32B", + "id": "fugu-ultra", + "name": "Fugu Ultra", + "display_name": "Fugu Ultra", + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1000000, + "output": 1000000 + }, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "high", + "xhigh", + "max" + ] + } + }, + "attachment": true, + "open_weights": false, + "release_date": "2026-06-15", + "last_updated": "2026-06-15", + "type": "chat" + }, + { + "id": "gpt-oss-120b", + "name": "GPT OSS 120B", + "display_name": "GPT OSS 120B", "modalities": { "input": [ "text" @@ -170540,63 +171166,68 @@ ] }, "limit": { - "context": 128000, - "output": 8192 + "context": 131072, + "output": 32766 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "effort_options": [ + "low", + "medium", + "high" + ] + } }, "attachment": false, "open_weights": true, - "release_date": "2024-11-12", - "last_updated": "2024-11-12", + "knowledge": "2024-06", + "release_date": "2025-08-05", + "last_updated": "2025-08-05", "type": "chat" }, { - "id": "qwen3.8-max", - "name": "Qwen3.8 Max", - "display_name": "Qwen3.8 Max", + "id": "grok-4-20-beta-0309-non-reasoning", + "name": "Grok 4.20 (Non-Reasoning)", + "display_name": "Grok 4.20 (Non-Reasoning)", "modalities": { "input": [ "text", "image", - "video" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 2000000, + "output": 30000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true - } + "supported": false }, "attachment": true, "open_weights": false, - "release_date": "2026-08-03", - "last_updated": "2026-08-03", + "release_date": "2026-03-09", + "last_updated": "2026-03-09", "type": "chat" }, { - "id": "gpt-5.6-terra", - "name": "GPT-5.6 Terra", - "display_name": "GPT-5.6 Terra", + "id": "hy4-preview", + "name": "Hy4 preview", + "display_name": "Hy4 preview", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" @@ -170604,9 +171235,9 @@ }, "limit": { "context": 1000000, - "output": 128000 + "output": 64000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -170615,139 +171246,122 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", "effort_options": [ "none", + "minimal", "low", "medium", "high", "xhigh", "max" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + ] } }, - "attachment": true, + "attachment": false, + "open_weights": true, + "release_date": "2026-08-28", + "last_updated": "2026-08-28", + "type": "chat" + }, + { + "id": "hy-mt2-plus", + "name": "Hy-MT2 Plus", + "display_name": "Hy-MT2 Plus", + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 8192, + "output": 8192 + }, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": false + }, + "attachment": false, "open_weights": false, - "knowledge": "2026-02-16", - "release_date": "2026-07-09", - "last_updated": "2026-07-09", + "release_date": "2026-05-21", + "last_updated": "2026-05-21", "type": "chat" }, { - "id": "gpt-5.1-codex", - "name": "GPT-5.1 Codex", - "display_name": "GPT-5.1 Codex", + "id": "qwen3-14b", + "name": "Qwen3 14B", + "display_name": "Qwen3 14B", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 32768, + "output": 32768 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": false + "supported": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "none", - "effort_options": [ - "none", - "low", - "medium", - "high" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] } }, - "attachment": true, + "attachment": false, "open_weights": false, - "knowledge": "2024-09-30", - "release_date": "2025-11-13", - "last_updated": "2025-11-13", + "release_date": "2025-04-28", + "last_updated": "2025-04-28", "type": "chat" }, { - "id": "gpt-5-mini", - "name": "GPT-5 Mini", - "display_name": "GPT-5 Mini", + "id": "gpt-4o", + "name": "GPT-4o", + "display_name": "GPT-4o", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 128000, + "output": 16384 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", - "effort_options": [ - "minimal", - "low", - "medium", - "high" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" - } + "supported": false }, "attachment": true, "open_weights": false, - "knowledge": "2024-05-30", - "release_date": "2025-08-07", - "last_updated": "2025-08-07", + "knowledge": "2023-09", + "release_date": "2024-05-13", + "last_updated": "2024-08-06", "type": "chat" }, { - "id": "gpt-5-codex", - "name": "GPT-5-Codex", - "display_name": "GPT-5-Codex", + "id": "mistral-large-4", + "name": "Mistral Large 4", + "display_name": "Mistral Large 4", "modalities": { "input": [ "text", @@ -170758,101 +171372,58 @@ ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 1000000, + "output": 262144 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", - "effort_options": [ - "minimal", - "low", - "medium", - "high" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" - } - }, "attachment": true, "open_weights": false, - "knowledge": "2024-09-30", - "release_date": "2025-09-15", - "last_updated": "2025-09-15", + "release_date": "2026-10-06", + "last_updated": "2026-10-06", "type": "chat" }, { - "id": "gemini-3-pro-image", - "name": "Nano Banana Pro", - "display_name": "Nano Banana Pro", + "id": "qwen3-coder-flash", + "name": "Qwen3 Coder Flash", + "display_name": "Qwen3 Coder Flash", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ - "text", - "image" + "text" ] }, "limit": { - "context": 65536, - "output": 32768 + "context": 997952, + "output": 65536 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "level", - "level": "high", - "level_options": [ - "low", - "high" - ], - "summaries": true, - "visibility": "summary", - "continuation": [ - "thought_signatures" - ] - } + "supported": false }, - "attachment": true, + "attachment": false, "open_weights": false, - "knowledge": "2025-01", - "release_date": "2026-05-28", - "last_updated": "2026-05-28" + "knowledge": "2025-04", + "release_date": "2025-07-28", + "last_updated": "2025-07-28", + "type": "chat" }, { - "id": "gemini-3.1-flash-lite-preview", - "name": "Gemini 3.1 Flash Lite Preview", - "display_name": "Gemini 3.1 Flash Lite Preview", + "id": "kimi-k3.1-pro", + "name": "Kimi K3.1 Pro", + "display_name": "Kimi K3.1 Pro", "modalities": { "input": [ "text", "image", - "video", - "audio", - "pdf" + "video" ], "output": [ "text" @@ -170860,25 +171431,37 @@ }, "limit": { "context": 1048576, - "output": 65536 + "output": 131072 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, "default": true }, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "none", + "minimal", + "low", + "medium", + "high", + "xhigh", + "max" + ] + } + }, "attachment": true, "open_weights": false, - "knowledge": "2025-01", - "release_date": "2026-03-03", - "last_updated": "2026-03-03", + "release_date": "2026-10-23", + "last_updated": "2026-10-23", "type": "chat" }, { - "id": "o3-mini", - "name": "o3-mini", - "display_name": "o3-mini", + "id": "minimax-m2.1-lightning", + "name": "MiniMax M2.1 Lightning", + "display_name": "MiniMax M2.1 Lightning", "modalities": { "input": [ "text" @@ -170888,123 +171471,103 @@ ] }, "limit": { - "context": 200000, - "output": 100000 + "context": 196608, + "output": 131072 }, - "temperature": false, - "tool_call": true, + "temperature": true, + "tool_call": false, "reasoning": { "supported": true, "default": true }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", "effort_options": [ "low", "medium", - "high" - ], - "visibility": "hidden" + "high", + "xhigh", + "max" + ] } }, "attachment": false, - "open_weights": false, - "knowledge": "2024-05", - "release_date": "2024-12-20", - "last_updated": "2025-01-29", + "open_weights": true, + "release_date": "2025-12-23", + "last_updated": "2025-12-23", "type": "chat" }, { - "id": "gpt-5.1", - "name": "GPT-5.1", - "display_name": "GPT-5.1", + "id": "qwen3.6-27b", + "name": "Qwen3.6 27B", + "display_name": "Qwen3.6 27B", "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 16384, + "output": 65536 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "none", - "effort_options": [ - "none", - "low", - "medium", - "high" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2024-09-30", - "release_date": "2025-11-13", - "last_updated": "2025-11-13", + "open_weights": true, + "release_date": "2026-04-22", + "last_updated": "2026-04-22", "type": "chat" }, { - "id": "claude-opus-4-6", - "name": "Claude Opus 4.6", - "display_name": "Claude Opus 4.6", + "id": "deepseek-v4-pro", + "name": "DeepSeek V4 Pro", + "display_name": "DeepSeek V4 Pro", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 1050000, + "output": 384000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "mixed", - "budget": { - "min": 1024, - "unit": "tokens" - }, + "default_enabled": true, + "mode": "effort", "effort": "high", "effort_options": [ "low", - "medium", "high", "max" ], @@ -171015,26 +171578,26 @@ "thinking_blocks" ], "notes": [ - "Anthropic recommends adaptive thinking with effort for Claude 4.6; budget_tokens remains a deprecated compatibility path.", - "Anthropic API defaults effort to high; lower effort levels should be chosen per workload." + "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-05-31", - "release_date": "2026-02-05", - "last_updated": "2026-03-13", + "attachment": false, + "open_weights": true, + "knowledge": "2025-05", + "release_date": "2026-04-24", + "last_updated": "2026-04-24", "type": "chat" }, { - "id": "gpt-5.6-luna", - "name": "GPT-5.6 Luna", - "display_name": "GPT-5.6 Luna", + "id": "claude-sonnet-5-5", + "name": "Claude Sonnet 5.5", + "display_name": "Claude Sonnet 5.5", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" @@ -171052,172 +171615,139 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", "effort_options": [ - "none", "low", "medium", "high", "xhigh", "max" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-02-16", - "release_date": "2026-07-09", - "last_updated": "2026-07-09", + "knowledge": "2026-06", + "release_date": "2026-09-28", + "last_updated": "2026-09-28", "type": "chat" }, { - "id": "claude-opus-4-1-20250805", - "name": "Claude Opus 4.1", - "display_name": "Claude Opus 4.1", + "id": "glm-5.3", + "name": "GLM-5.3", + "display_name": "GLM-5.3", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 32000 + "context": 1048576, + "output": 131072 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "budget", - "budget": { - "min": 1024, - "unit": "tokens" - }, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", - "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." + "effort_options": [ + "none", + "minimal", + "low", + "medium", + "high", + "xhigh", + "max" ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-03-31", - "release_date": "2025-08-05", - "last_updated": "2025-08-05", + "attachment": false, + "open_weights": true, + "release_date": "2026-08-14", + "last_updated": "2026-08-14", "type": "chat" }, { - "id": "claude-sonnet-4-5-20250929", - "name": "Claude Sonnet 4.5", - "display_name": "Claude Sonnet 4.5", + "id": "granite-4.2-8b", + "name": "Granite 4.2 8B", + "display_name": "Granite 4.2 8B", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 64000 + "context": 131072, + "output": 131072 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "budget", - "budget": { - "min": 1024, - "unit": "tokens" - }, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", - "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." + "effort_options": [ + "none", + "low", + "medium", + "high" ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-07-31", - "release_date": "2025-09-29", - "last_updated": "2025-09-29", + "attachment": false, + "open_weights": true, + "release_date": "2026-08-25", + "last_updated": "2026-08-25", "type": "chat" }, { - "id": "gpt-5.4-mini", - "name": "GPT-5.4 mini", - "display_name": "GPT-5.4 mini", + "id": "gpt-5.6-sol", + "name": "GPT-5.6 Sol", + "display_name": "GPT-5.6 Sol", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 400000, + "context": 1050000, "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, + "default_enabled": true, "mode": "effort", - "effort": "none", + "effort": "medium", "effort_options": [ "none", "low", "medium", "high", - "xhigh" + "xhigh", + "max" ], "verbosity": "medium", "verbosity_options": [ @@ -171230,15 +171760,15 @@ }, "attachment": true, "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-03-17", - "last_updated": "2026-03-17", + "knowledge": "2026-02-16", + "release_date": "2026-07-09", + "last_updated": "2026-07-09", "type": "chat" }, { - "id": "claude-opus-4-20250514", - "name": "Claude Opus 4", - "display_name": "Claude Opus 4", + "id": "claude-sonnet-4-6", + "name": "Claude Sonnet 4.6", + "display_name": "Claude Sonnet 4.6", "modalities": { "input": [ "text", @@ -171250,8 +171780,8 @@ ] }, "limit": { - "context": 200000, - "output": 32000 + "context": 1000000, + "output": 64000 }, "temperature": true, "tool_call": true, @@ -171263,11 +171793,18 @@ "reasoning": { "supported": true, "default_enabled": false, - "mode": "budget", + "mode": "mixed", "budget": { "min": 1024, "unit": "tokens" }, + "effort": "high", + "effort_options": [ + "low", + "medium", + "high", + "max" + ], "interleaved": true, "summaries": true, "visibility": "summary", @@ -171275,34 +171812,35 @@ "thinking_blocks" ], "notes": [ - "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", - "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." + "Anthropic recommends adaptive thinking with effort for Claude 4.6; budget_tokens remains a deprecated compatibility path.", + "Anthropic API defaults effort to high; lower effort levels should be chosen per workload." ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-03-31", - "release_date": "2025-05-22", - "last_updated": "2025-05-22", + "knowledge": "2025-08-31", + "release_date": "2026-02-17", + "last_updated": "2026-03-13", "type": "chat" }, { - "id": "gpt-5.3-codex", - "name": "GPT-5.3 Codex", - "display_name": "GPT-5.3 Codex", + "id": "glm-4.5v", + "name": "GLM-4.5V", + "display_name": "GLM-4.5V", "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 128000, + "output": 16384 }, "temperature": true, "tool_call": true, @@ -171310,85 +171848,56 @@ "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", - "effort_options": [ - "low", - "medium", - "high", - "xhigh" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" - } - }, "attachment": true, - "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-02-05", - "last_updated": "2026-02-05", + "open_weights": true, + "knowledge": "2025-04", + "release_date": "2025-08-11", + "last_updated": "2025-08-11", "type": "chat" }, { - "id": "gemini-3.1-flash-image-preview", - "name": "Nano Banana 2 Preview", - "display_name": "Nano Banana 2 Preview", + "id": "glm-4.5-airx", + "name": "GLM-4.5 AirX", + "display_name": "GLM-4.5 AirX", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ - "text", - "image" + "text" ] }, "limit": { - "context": 1048576, - "output": 32768 + "context": 128000, + "output": 16384 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true - } + "supported": false }, - "attachment": true, + "attachment": false, "open_weights": false, - "knowledge": "2025-01", - "release_date": "2026-02-26", - "last_updated": "2026-02-26" + "release_date": "2025-07-28", + "last_updated": "2025-07-28", + "type": "chat" }, { - "id": "gemini-3.1-pro-preview", - "name": "Gemini 3.1 Pro Preview", - "display_name": "Gemini 3.1 Pro Preview", + "id": "qwen3.5-9b", + "name": "Qwen3.5 9B", + "display_name": "Qwen3.5 9B", "modalities": { "input": [ "text", "image", - "audio" + "video" ], "output": [ "text" ] }, "limit": { - "context": 1048576, + "context": 262144, "output": 65536 }, "temperature": true, @@ -171400,60 +171909,24 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "level", - "level": "high", - "level_options": [ - "low", - "high" - ], + "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ - "thought_signatures" + "thinking_blocks" ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2025-01", - "release_date": "2026-02-19", - "last_updated": "2026-02-19", - "type": "chat" - }, - { - "id": "gpt-4.1-mini", - "name": "GPT-4.1 mini", - "display_name": "GPT-4.1 mini", - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 1047576, - "output": 32768 - }, - "temperature": true, - "tool_call": true, - "reasoning": { - "supported": false - }, - "attachment": true, - "open_weights": false, - "knowledge": "2024-04", - "release_date": "2025-04-14", - "last_updated": "2025-04-14", + "open_weights": true, + "release_date": "2026-02-23", + "last_updated": "2026-02-23", "type": "chat" }, { - "id": "claude-haiku-4-5-20251001", - "name": "Claude Haiku 4.5", - "display_name": "Claude Haiku 4.5", + "id": "gpt-5.1", + "name": "GPT-5.1", + "display_name": "GPT-5.1", "modalities": { "input": [ "text", @@ -171464,8 +171937,8 @@ ] }, "limit": { - "context": 200000, - "output": 64000 + "context": 400000, + "output": 128000 }, "temperature": true, "tool_call": true, @@ -171477,86 +171950,80 @@ "reasoning": { "supported": true, "default_enabled": false, - "mode": "budget", - "budget": { - "min": 1024, - "unit": "tokens" - }, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "mode": "effort", + "effort": "none", + "effort_options": [ + "none", + "low", + "medium", + "high" ], - "notes": [ - "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", - "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." - ] + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, "attachment": true, "open_weights": false, - "knowledge": "2025-02-28", - "release_date": "2025-10-15", - "last_updated": "2025-10-15", + "knowledge": "2024-09-30", + "release_date": "2025-11-13", + "last_updated": "2025-11-13", "type": "chat" }, { - "id": "gpt-5.4-nano", - "name": "GPT-5.4 nano", - "display_name": "GPT-5.4 nano", + "id": "minimax-m2.7-highspeed", + "name": "MiniMax-M2.7-highspeed", + "display_name": "MiniMax-M2.7-highspeed", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 204800, + "output": 131072 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "none", + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ - "none", "low", "medium", "high", - "xhigh" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + "xhigh", + "max" + ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-03-17", - "last_updated": "2026-03-17", + "attachment": false, + "open_weights": true, + "release_date": "2026-03-18", + "last_updated": "2026-03-18", "type": "chat" }, { - "id": "o3-pro", - "name": "o3-pro", - "display_name": "o3-pro", + "id": "seed-1-6-flash-250715", + "name": "Seed 1.6 Flash (250715)", + "display_name": "Seed 1.6 Flash (250715)", "modalities": { "input": [ "text", @@ -171567,10 +172034,10 @@ ] }, "limit": { - "context": 200000, - "output": 100000 + "context": 256000, + "output": 8192 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -171578,29 +172045,24 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", "effort_options": [ + "minimal", "low", "medium", "high" - ], - "visibility": "hidden" + ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2024-05", - "release_date": "2025-06-10", - "last_updated": "2025-06-10", + "open_weights": true, + "release_date": "2025-07-26", + "last_updated": "2025-07-26", "type": "chat" }, { - "id": "claude-opus-4-8", - "name": "Claude Opus 4.8", - "display_name": "Claude Opus 4.8", + "id": "grok-4-1-fast-reasoning", + "name": "Grok 4.1 Fast Reasoning", + "display_name": "Grok 4.1 Fast Reasoning", "modalities": { "input": [ "text", @@ -171611,67 +172073,48 @@ ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 2000000, + "output": 30000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "high", "effort_options": [ "low", "medium", - "high", - "xhigh", - "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "omitted", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Claude Opus 4.7 and newer Opus models require thinking.type = \"adaptive\" to enable thinking explicitly.", - "Manual budget_tokens requests return 400 on Claude Opus 4.7 and newer adaptive-only Opus models.", - "task_budget is separate from thinking control and should not be treated as a thinking budget." + "high" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-01", - "release_date": "2026-05-28", - "last_updated": "2026-05-28", + "release_date": "2025-11-19", + "last_updated": "2025-11-19", "type": "chat" }, { - "id": "gpt-5.3-codex-xhigh", - "name": "GPT-5.3 Codex XHigh", - "display_name": "GPT-5.3 Codex XHigh", + "id": "glm-5", + "name": "GLM-5", + "display_name": "GLM-5", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 202800, + "output": 131072 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -171680,48 +172123,44 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ + "none", + "minimal", "low", "medium", "high", - "xhigh" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + "xhigh", + "max" + ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-02-05", - "last_updated": "2026-02-05", + "attachment": false, + "open_weights": true, + "release_date": "2026-02-12", + "last_updated": "2026-02-12", "type": "chat" }, { - "id": "muse-spark-1.1", - "name": "Muse Spark 1.1", - "display_name": "Muse Spark 1.1", + "id": "minimax-m2", + "name": "MiniMax-M2", + "display_name": "MiniMax-M2", "modalities": { "input": [ - "text", - "image", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 32000 + "context": 196608, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -171731,23 +172170,31 @@ }, "extra_capabilities": { "reasoning": { - "supported": true + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" + ] } }, - "attachment": true, - "open_weights": false, - "release_date": "2026-04-08", - "last_updated": "2026-07-09", + "attachment": false, + "open_weights": true, + "release_date": "2025-10-27", + "last_updated": "2025-10-27", "type": "chat" }, { - "id": "gemini-3.6-flash", - "name": "Gemini 3.6 Flash", - "display_name": "Gemini 3.6 Flash", + "id": "muse-wonder-1.0", + "name": "Muse Wonder", + "display_name": "Muse Wonder", "modalities": { "input": [ "text", "image", + "video", + "pdf", "audio" ], "output": [ @@ -171756,7 +172203,7 @@ }, "limit": { "context": 1048576, - "output": 65536 + "output": 131072 }, "temperature": true, "tool_call": true, @@ -171766,63 +172213,53 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "level", - "level": "high", - "level_options": [ + "effort_options": [ "minimal", "low", "medium", - "high" - ], - "summaries": true, - "visibility": "summary", - "continuation": [ - "thought_signatures" + "high", + "xhigh" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-03", - "release_date": "2026-07-21", - "last_updated": "2026-07-21", + "release_date": "2026-10-12", + "last_updated": "2026-10-12", "type": "chat" }, { - "id": "gpt-4.1", - "name": "GPT-4.1", - "display_name": "GPT-4.1", + "id": "glm-5.3-fast", + "name": "GLM-5.3", + "display_name": "GLM-5.3", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1047576, - "output": 32768 + "context": 1000000, + "output": 131072 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": true, - "open_weights": false, - "knowledge": "2024-04", - "release_date": "2025-04-14", - "last_updated": "2025-04-14", + "attachment": false, + "open_weights": true, + "release_date": "2026-08-14", + "last_updated": "2026-08-14", "type": "chat" }, { - "id": "grok-4-fast-non-reasoning", - "name": "Grok 4 Fast (Non-Reasoning)", - "display_name": "Grok 4 Fast (Non-Reasoning)", + "id": "gemini-pro-latest", + "name": "Gemini Pro Latest", + "display_name": "Gemini Pro Latest", "modalities": { "input": [ "text", @@ -171833,75 +172270,91 @@ ] }, "limit": { - "context": 2000000, - "output": 16384 + "context": 1048576, + "output": 65536 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "low", + "medium", + "high" + ] + } }, "attachment": true, "open_weights": false, - "release_date": "2025-12-18", - "last_updated": "2025-12-18", + "release_date": "2026-02-27", + "last_updated": "2026-02-27", "type": "chat" }, { - "id": "gpt-5.2-codex", - "name": "GPT-5.2 Codex", - "display_name": "GPT-5.2 Codex", + "id": "claude-opus-4-8", + "name": "Claude Opus 4.8", + "display_name": "Claude Opus 4.8", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 400000, + "context": 1000000, "output": 128000 }, "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, + "default_enabled": false, "mode": "effort", - "effort": "medium", + "effort": "high", "effort_options": [ "low", "medium", "high", - "xhigh" + "xhigh", + "max" ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" + "interleaved": true, + "summaries": true, + "visibility": "omitted", + "continuation": [ + "thinking_blocks" ], - "visibility": "hidden" + "notes": [ + "Claude Opus 4.7 and newer Opus models require thinking.type = \"adaptive\" to enable thinking explicitly.", + "Manual budget_tokens requests return 400 on Claude Opus 4.7 and newer adaptive-only Opus models.", + "task_budget is separate from thinking control and should not be treated as a thinking budget." + ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2025-12-11", - "last_updated": "2025-12-11", + "knowledge": "2026-01", + "release_date": "2026-05-28", + "last_updated": "2026-05-28", "type": "chat" }, { - "id": "llama-3.3-70b-versatile", - "name": "Llama 3.3 70B Versatile", - "display_name": "Llama 3.3 70B Versatile", + "id": "glm-4.7-flashx", + "name": "GLM-4.7-FlashX", + "display_name": "GLM-4.7-FlashX", "modalities": { "input": [ "text" @@ -171911,25 +172364,26 @@ ] }, "limit": { - "context": 128000, - "output": 32768 + "context": 200000, + "output": 131072 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, "attachment": false, "open_weights": true, - "knowledge": "2023-12", - "release_date": "2024-12-06", - "last_updated": "2024-12-06", + "knowledge": "2025-04", + "release_date": "2026-01-19", + "last_updated": "2026-01-19", "type": "chat" }, { - "id": "gpt-5.3-chat-latest", - "name": "GPT-5.3 Chat Latest", - "display_name": "GPT-5.3 Chat Latest", + "id": "grok-4-6", + "name": "Grok 4.6", + "display_name": "Grok 4.6", "modalities": { "input": [ "text", @@ -171940,38 +172394,50 @@ ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 500000, + "output": 500000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "minimal", + "low", + "medium", + "high", + "xhigh" + ] + } }, "attachment": true, "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-03-03", - "last_updated": "2026-03-03", + "knowledge": "2026-02-01", + "release_date": "2026-08-12", + "last_updated": "2026-08-12", "type": "chat" }, { - "id": "gemini-3.5-flash", - "name": "Gemini 3.5 Flash", - "display_name": "Gemini 3.5 Flash", + "id": "grok-4-20-beta-0309-reasoning", + "name": "Grok 4.20 (Reasoning)", + "display_name": "Grok 4.20 (Reasoning)", "modalities": { "input": [ "text", "image", - "audio" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 2000000, + "output": 30000 }, "temperature": true, "tool_call": true, @@ -171981,48 +172447,34 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "level", - "level": "high", - "level_options": [ - "minimal", + "effort_options": [ "low", "medium", "high" - ], - "summaries": true, - "visibility": "summary", - "continuation": [ - "thought_signatures" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-01", - "release_date": "2026-05-19", - "last_updated": "2026-05-19", + "release_date": "2026-03-09", + "last_updated": "2026-03-09", "type": "chat" }, { - "id": "muse-spark-1.2", - "name": "Muse Spark 1.2", - "display_name": "Muse Spark 1.2", + "id": "qwen-flash", + "name": "Qwen Flash", + "display_name": "Qwen Flash", "modalities": { "input": [ - "text", - "image", - "audio", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 1000000, + "output": 32768 }, "temperature": true, "tool_call": true, @@ -172032,90 +172484,89 @@ }, "extra_capabilities": { "reasoning": { - "supported": true + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] } }, - "attachment": true, + "attachment": false, "open_weights": false, - "release_date": "2026-08-05", - "last_updated": "2026-08-05", + "knowledge": "2024-04", + "release_date": "2025-07-28", + "last_updated": "2025-07-28", "type": "chat" }, { - "id": "claude-opus-4-5-20251101", - "name": "Claude Opus 4.5", - "display_name": "Claude Opus 4.5", + "id": "gemini-3.1-pro-preview", + "name": "Gemini 3.1 Pro Preview", + "display_name": "Gemini 3.1 Pro Preview", "modalities": { "input": [ "text", - "image" + "image", + "video", + "audio", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 64000 + "context": 1048576, + "output": 65536 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "mixed", - "budget": { - "min": 1024, - "unit": "tokens" - }, - "effort": "high", - "effort_options": [ + "default_enabled": true, + "mode": "level", + "level": "high", + "level_options": [ "low", - "medium", "high" ], - "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Claude Opus 4.5 uses manual thinking.type = \"enabled\" with budget_tokens; effort can be used alongside the thinking budget.", - "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header." + "thought_signatures" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-05", - "release_date": "2025-11-24", - "last_updated": "2025-11-24", + "knowledge": "2025-01", + "release_date": "2026-02-19", + "last_updated": "2026-02-19", "type": "chat" }, { - "id": "claude-fable-5", - "name": "Claude Fable 5", - "display_name": "Claude Fable 5", + "id": "deepseek-v4-flash", + "name": "DeepSeek V4 Flash", + "display_name": "DeepSeek V4 Flash", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 1050000, + "output": 384000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -172129,96 +172580,58 @@ "effort": "high", "effort_options": [ "low", - "medium", "high", - "xhigh", "max" ], "interleaved": true, "summaries": true, - "visibility": "omitted", + "visibility": "summary", "continuation": [ "thinking_blocks" ], "notes": [ - "Adaptive thinking is always on for Claude Fable 5 and Claude Mythos 5; thinking.type = \"disabled\" is rejected.", - "Manual budget_tokens requests return 400 on Claude Fable 5 and Claude Mythos 5.", - "thinking.display defaults to omitted; set display to summarized to receive readable thinking summaries." + "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-01-31", - "release_date": "2026-06-09", - "last_updated": "2026-06-09", - "type": "chat" - }, - { - "id": "grok-4.3", - "name": "Grok 4.3", - "display_name": "Grok 4.3", - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 1000000, - "output": 32768 - }, - "temperature": true, - "tool_call": true, - "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true - } - }, - "attachment": true, - "open_weights": false, - "release_date": "2026-04-17", - "last_updated": "2026-04-17", + "attachment": false, + "open_weights": true, + "knowledge": "2025-05", + "release_date": "2026-04-24", + "last_updated": "2026-04-24", "type": "chat" }, { - "id": "gpt-5.2", - "name": "GPT-5.2", - "display_name": "GPT-5.2", + "id": "gpt-5.5", + "name": "GPT-5.5", + "display_name": "GPT-5.5", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 400000, + "context": 1050000, "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, + "default_enabled": true, "mode": "effort", - "effort": "none", + "effort": "medium", "effort_options": [ - "none", "low", "medium", "high", @@ -172235,44 +172648,74 @@ }, "attachment": true, "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2025-12-11", - "last_updated": "2025-12-11", + "knowledge": "2025-12-01", + "release_date": "2026-04-23", + "last_updated": "2026-04-23", "type": "chat" }, { - "id": "grok-4-0709", - "name": "Grok 4", - "display_name": "Grok 4", + "id": "claude-opus-4-6", + "name": "Claude Opus 4.6", + "display_name": "Claude Opus 4.6", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 256000, - "output": 16384 + "context": 1000000, + "output": 128000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": false, + "mode": "mixed", + "budget": { + "min": 1024, + "unit": "tokens" + }, + "effort": "high", + "effort_options": [ + "low", + "medium", + "high", + "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Anthropic recommends adaptive thinking with effort for Claude 4.6; budget_tokens remains a deprecated compatibility path.", + "Anthropic API defaults effort to high; lower effort levels should be chosen per workload." + ] + } }, "attachment": true, "open_weights": false, - "release_date": "2025-07-09", - "last_updated": "2025-07-09", + "knowledge": "2025-05-31", + "release_date": "2026-02-05", + "last_updated": "2026-03-13", "type": "chat" }, { - "id": "gpt-5-nano", - "name": "GPT-5 Nano", - "display_name": "GPT-5 Nano", + "id": "gpt-5.1-codex-mini", + "name": "GPT-5.1 Codex mini", + "display_name": "GPT-5.1 Codex mini", "modalities": { "input": [ "text", @@ -172290,16 +172733,16 @@ "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, + "default_enabled": false, "mode": "effort", - "effort": "medium", + "effort": "none", "effort_options": [ - "minimal", + "none", "low", "medium", "high" @@ -172315,15 +172758,15 @@ }, "attachment": true, "open_weights": false, - "knowledge": "2024-05-30", - "release_date": "2025-08-07", - "last_updated": "2025-08-07", + "knowledge": "2024-09-30", + "release_date": "2025-11-13", + "last_updated": "2025-11-13", "type": "chat" }, { - "id": "claude-3-7-sonnet-20250219", - "name": "Claude Sonnet 3.7", - "display_name": "Claude Sonnet 3.7", + "id": "claude-fable-5-1", + "name": "Claude Fable 5.1", + "display_name": "Claude Fable 5.1", "modalities": { "input": [ "text", @@ -172335,58 +172778,65 @@ ] }, "limit": { - "context": 200000, - "output": 64000 + "context": 1000000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "budget", - "budget": { - "min": 1024, - "unit": "tokens" - }, - "interleaved": false, - "summaries": false, - "visibility": "full", + "default_enabled": true, + "mode": "effort", + "effort": "high", + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "omitted", "continuation": [ "thinking_blocks" ], "notes": [ - "Anthropic uses thinking budget tokens" + "Adaptive thinking is always on for Claude Fable 5 and Claude Mythos 5; thinking.type = \"disabled\" is rejected.", + "Manual budget_tokens requests return 400 on Claude Fable 5 and Claude Mythos 5.", + "thinking.display defaults to omitted; set display to summarized to receive readable thinking summaries." ] } }, "attachment": true, "open_weights": false, - "knowledge": "2024-10-31", - "release_date": "2025-02-19", - "last_updated": "2025-02-19", + "knowledge": "2026-06", + "release_date": "2026-09-01", + "last_updated": "2026-09-01", "type": "chat" }, { - "id": "grok-4.6", - "name": "Grok 4.6", - "display_name": "Grok 4.6", + "id": "qwen3.6-plus", + "name": "Qwen3.6 Plus", + "display_name": "Qwen3.6 Plus", "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 500000, - "output": 32768 + "context": 983616, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -172396,30 +172846,46 @@ }, "extra_capabilities": { "reasoning": { - "supported": true + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "effort_options": [ + "none", + "minimal", + "low", + "medium", + "high", + "xhigh", + "max" + ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-02-01", - "release_date": "2026-08-12", - "last_updated": "2026-08-12", + "knowledge": "2025-04", + "release_date": "2026-04-02", + "last_updated": "2026-04-02", "type": "chat" }, { - "id": "zai-org/GLM-4.7", - "name": "GLM-4.7", - "display_name": "GLM-4.7", + "id": "ling-3.0-flash-vl", + "name": "Ling 3.0 Flash VL", + "display_name": "Ling 3.0 Flash VL", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 204800, + "context": 131072, "output": 131072 }, "temperature": true, @@ -172430,59 +172896,79 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "none", + "low", + "medium", + "high" ] } }, - "attachment": false, + "attachment": true, "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-12-22", - "last_updated": "2025-12-22", + "release_date": "2026-09-10", + "last_updated": "2026-09-10", "type": "chat" }, { - "id": "zai-org/GLM-5.1", - "name": "GLM-5.1", - "display_name": "GLM-5.1", + "id": "gpt-live-transcribe", + "name": "GPT-Live-Transcribe", + "display_name": "GPT-Live-Transcribe", "modalities": { "input": [ + "text", + "audio" + ], + "output": [ "text" + ] + }, + "limit": { + "context": 8192, + "output": 8192 + }, + "temperature": false, + "tool_call": false, + "reasoning": { + "supported": false + }, + "attachment": true, + "open_weights": false, + "release_date": "2026-07-28", + "last_updated": "2026-07-28" + }, + { + "id": "gpt-4o-transcribe", + "name": "GPT-4o Transcribe", + "display_name": "GPT-4o Transcribe", + "modalities": { + "input": [ + "text", + "audio" ], "output": [ "text" ] }, "limit": { - "context": 204800, - "output": 131072 + "context": 16000, + "output": 16000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true - } + "supported": false }, - "attachment": false, - "open_weights": true, - "release_date": "2026-04-07", - "last_updated": "2026-04-07", + "attachment": true, + "open_weights": false, + "release_date": "2025-03-20", + "last_updated": "2025-03-20", "type": "chat" }, { - "id": "zai-org/GLM-5", - "name": "GLM-5", - "display_name": "GLM-5", + "id": "glm-5.1", + "name": "GLM-5.1", + "display_name": "GLM-5.1", "modalities": { "input": [ "text" @@ -172509,52 +172995,55 @@ "visibility": "summary", "continuation": [ "thinking_blocks" + ], + "effort_options": [ + "none", + "minimal", + "low", + "medium", + "high", + "xhigh", + "max" ] } }, "attachment": false, "open_weights": true, - "release_date": "2026-02-12", - "last_updated": "2026-02-12", + "release_date": "2026-04-07", + "last_updated": "2026-04-07", "type": "chat" }, { - "id": "zai-org/GLM-5.2", - "name": "GLM-5.2", - "display_name": "GLM-5.2", + "id": "gpt-transcribe", + "name": "GPT-Transcribe", + "display_name": "GPT-Transcribe", "modalities": { "input": [ - "text" + "text", + "audio" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 8192, + "output": 8192 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true - } + "supported": false }, - "attachment": false, - "open_weights": true, - "release_date": "2026-06-13", - "last_updated": "2026-06-13", - "type": "chat" + "attachment": true, + "open_weights": false, + "release_date": "2026-07-28", + "last_updated": "2026-07-28" }, { - "id": "zai-org/GLM-4.5", - "name": "GLM-4.5", - "display_name": "GLM-4.5", + "id": "devstral-2512", + "name": "Devstral 2", + "display_name": "Devstral 2", "modalities": { "input": [ "text" @@ -172564,37 +173053,40 @@ ] }, "limit": { - "context": 131072, - "output": 96000 + "context": 262144, + "output": 262144 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true + "supported": false }, "attachment": false, "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-07-28", - "last_updated": "2025-07-28", + "knowledge": "2025-12", + "release_date": "2025-12-09", + "last_updated": "2025-12-09", "type": "chat" }, { - "id": "zai-org/GLM-4.6", - "name": "GLM-4.6", - "display_name": "GLM-4.6", + "id": "gemini-3.7-flash", + "name": "Gemini 3.7 Flash", + "display_name": "Gemini 3.7 Flash", "modalities": { "input": [ - "text" + "text", + "image", + "video", + "audio", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 202752, - "output": 131072 + "context": 1048576, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -172604,31 +173096,48 @@ }, "extra_capabilities": { "reasoning": { - "supported": true + "supported": true, + "default_enabled": true, + "mode": "level", + "level": "high", + "level_options": [ + "minimal", + "low", + "medium", + "high" + ], + "summaries": true, + "visibility": "summary", + "continuation": [ + "thought_signatures" + ] } }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-09-30", - "last_updated": "2025-09-30", + "attachment": true, + "open_weights": false, + "knowledge": "2026-03", + "release_date": "2026-08-13", + "last_updated": "2026-08-13", "type": "chat" }, { - "id": "Qwen/Qwen3.6-27B", - "name": "Qwen3.6 27B", - "display_name": "Qwen3.6 27B", + "id": "glm-5v-turbo", + "name": "GLM-5V-Turbo", + "display_name": "GLM-5V-Turbo", "modalities": { "input": [ - "text" + "text", + "image", + "video", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 8192 + "context": 200000, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -172638,25 +173147,25 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" ] } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-04-22", - "last_updated": "2026-04-22", + "attachment": true, + "open_weights": false, + "release_date": "2026-04-01", + "last_updated": "2026-04-01", "type": "chat" }, { - "id": "Qwen/Qwen3-32B", - "name": "Qwen3 32B", - "display_name": "Qwen3 32B", + "id": "Qwen3.8-27B", + "name": "Qwen3.8 27B", + "display_name": "Qwen3.8 27B", "modalities": { "input": [ "text" @@ -172666,8 +173175,8 @@ ] }, "limit": { - "context": 131072, - "output": 8192 + "context": 262000, + "output": 32768 }, "temperature": true, "tool_call": true, @@ -172677,53 +173186,87 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "low", + "medium", + "xhigh" ] } }, "attachment": false, "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-04-28", - "last_updated": "2025-04-28", + "release_date": "2026-08-14", + "last_updated": "2026-08-14", "type": "chat" }, { - "id": "Qwen/Qwen2.5-72B-Instruct", - "name": "Qwen 2.5 72B Instruct", - "display_name": "Qwen 2.5 72B Instruct", + "id": "llama-4-scout-17b-instruct", + "name": "Llama 4 Scout 17B Instruct", + "display_name": "Llama 4 Scout 17B Instruct", "modalities": { "input": [ + "text", + "image" + ], + "output": [ "text" + ] + }, + "limit": { + "context": 131072, + "output": 2048 + }, + "temperature": true, + "tool_call": false, + "reasoning": { + "supported": false + }, + "attachment": true, + "open_weights": true, + "knowledge": "2024-08", + "release_date": "2025-04-05", + "last_updated": "2025-04-05", + "type": "chat" + }, + { + "id": "gemma-4-31b-it", + "name": "Gemma 4 31B IT", + "display_name": "Gemma 4 31B IT", + "modalities": { + "input": [ + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 8192 + "context": 262144, + "output": 32768 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, + "extra_capabilities": { + "reasoning": { + "supported": true + } + }, + "attachment": true, "open_weights": true, - "release_date": "2024-09-19", - "last_updated": "2024-09-19", + "knowledge": "2025-01", + "release_date": "2026-04-02", + "last_updated": "2026-04-02", "type": "chat" }, { - "id": "Qwen/Qwen3-Coder-480B-A35B-Instruct", - "name": "Qwen3-Coder 480B-A35B Instruct", - "display_name": "Qwen3-Coder 480B-A35B Instruct", + "id": "kimi-k2", + "name": "Kimi K2", + "display_name": "Kimi K2", "modalities": { "input": [ "text" @@ -172733,7 +173276,7 @@ ] }, "limit": { - "context": 262144, + "context": 256000, "output": 16384 }, "temperature": true, @@ -172743,15 +173286,14 @@ }, "attachment": false, "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-07-22", - "last_updated": "2025-07-22", + "release_date": "2025-07-11", + "last_updated": "2025-07-11", "type": "chat" }, { - "id": "Qwen/QwQ-32B", - "name": "QwQ 32B", - "display_name": "QwQ 32B", + "id": "glm-5.2", + "name": "GLM-5.2", + "display_name": "GLM-5.2", "modalities": { "input": [ "text" @@ -172761,8 +173303,8 @@ ] }, "limit": { - "context": 32768, - "output": 32768 + "context": 1048576, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -172778,47 +173320,71 @@ "visibility": "summary", "continuation": [ "thinking_blocks" + ], + "effort_options": [ + "none", + "minimal", + "low", + "medium", + "high", + "xhigh", + "max" ] } }, "attachment": false, "open_weights": true, - "knowledge": "2024-04", - "release_date": "2025-03-05", - "last_updated": "2025-03-05", + "release_date": "2026-06-13", + "last_updated": "2026-06-13", "type": "chat" }, { - "id": "Qwen/Qwen3-235B-A22B-Instruct-2507", - "name": "Qwen3 235B A22B Instruct", - "display_name": "Qwen3 235B A22B Instruct", + "id": "kimi-k2.8-preview", + "name": "Kimi K2.8 Preview", + "display_name": "Kimi K2.8 Preview", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 8192 + "context": 1000000, + "output": 1000000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, - "open_weights": true, - "release_date": "2025-07-21", - "last_updated": "2025-07-21", + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "none", + "minimal", + "low", + "medium", + "high", + "xhigh", + "max" + ] + } + }, + "attachment": true, + "open_weights": false, + "release_date": "2026-09-11", + "last_updated": "2026-09-11", "type": "chat" }, { - "id": "deepseek/deepseek-v3.1", - "name": "DeepSeek V3.1", - "display_name": "DeepSeek V3.1", + "id": "glm-5-turbo", + "name": "GLM-5-Turbo", + "display_name": "GLM-5-Turbo", "modalities": { "input": [ "text" @@ -172828,8 +173394,8 @@ ] }, "limit": { - "context": 128000, - "output": 8192 + "context": 200000, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -172837,16 +173403,28 @@ "supported": true, "default": true }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" + ] + } + }, "attachment": false, - "open_weights": true, - "release_date": "2025-08-21", - "last_updated": "2025-08-21", + "open_weights": false, + "release_date": "2026-03-16", + "last_updated": "2026-03-16", "type": "chat" }, { - "id": "google/gemma-4-31b-it", - "name": "Gemma 4 31B IT", - "display_name": "Gemma 4 31B IT", + "id": "mistral-medium-3-5", + "name": "Mistral Medium 3.5", + "display_name": "Mistral Medium 3.5", "modalities": { "input": [ "text", @@ -172858,59 +173436,67 @@ }, "limit": { "context": 262144, - "output": 131072 + "output": 262144 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true - } + "supported": false }, "attachment": true, - "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-04-02", - "last_updated": "2026-04-02", + "open_weights": false, + "release_date": "2026-04-30", + "last_updated": "2026-04-30", "type": "chat" }, { - "id": "thinkingmachines/Inkling", - "name": "Inkling", - "display_name": "Inkling", + "id": "claude-opus-5", + "name": "Claude Opus 5", + "display_name": "Claude Opus 5", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 131072 + "context": 1000000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, "default": true }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "effort_options": [ + "none", + "low", + "medium", + "high", + "xhigh", + "max" + ] + } + }, "attachment": true, - "open_weights": true, - "release_date": "2026-07-15", - "last_updated": "2026-07-15", + "open_weights": false, + "knowledge": "2026-05", + "release_date": "2026-07-24", + "last_updated": "2026-07-24", "type": "chat" }, { - "id": "openai/gpt-oss-120b", - "name": "GPT OSS 120B", - "display_name": "GPT OSS 120B", + "id": "llama-3.2-11b-instruct", + "name": "Llama 3.2 11B Instruct", + "display_name": "Llama 3.2 11B Instruct", "modalities": { "input": [ "text" @@ -172921,30 +173507,23 @@ }, "limit": { "context": 128000, - "output": 16384 + "output": 8192 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true - } + "supported": false }, "attachment": false, "open_weights": true, - "knowledge": "2024-06", - "release_date": "2025-08-05", - "last_updated": "2025-08-05", + "release_date": "2024-09-25", + "last_updated": "2024-09-25", "type": "chat" }, { - "id": "MiniMaxAI/MiniMax-M3", - "name": "MiniMax-M3", - "display_name": "MiniMax-M3", + "id": "qwen3.5-35b-a3b", + "name": "Qwen3.5 35B-A3B", + "display_name": "Qwen3.5 35B-A3B", "modalities": { "input": [ "text", @@ -172956,8 +173535,8 @@ ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 262144, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -172967,30 +173546,46 @@ }, "extra_capabilities": { "reasoning": { - "supported": true + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "effort_options": [ + "none", + "minimal", + "low", + "medium", + "high", + "xhigh", + "max" + ] } }, "attachment": true, "open_weights": true, - "release_date": "2026-06-01", - "last_updated": "2026-06-01", + "release_date": "2026-02-23", + "last_updated": "2026-02-23", "type": "chat" }, { - "id": "MiniMaxAI/MiniMax-M2.7", - "name": "MiniMax-M2.7", - "display_name": "MiniMax-M2.7", + "id": "qwen3.8-max", + "name": "Qwen3.8 Max Preview", + "display_name": "Qwen3.8 Max Preview", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 204800, - "output": 131072 + "context": 1000000, + "output": 1000000 }, "temperature": true, "tool_call": true, @@ -173001,43 +173596,40 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "low", + "medium", + "xhigh" ] } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-03-18", - "last_updated": "2026-03-18", + "attachment": true, + "open_weights": false, + "release_date": "2026-07-19", + "last_updated": "2026-07-19", "type": "chat" }, { - "id": "moonshotai/Kimi-K2.7-Code", - "name": "Kimi K2.7 Code", - "display_name": "Kimi K2.7 Code", + "id": "grok-4", + "name": "Grok 4", + "display_name": "Grok 4", "modalities": { "input": [ "text", - "image", - "video" + "image" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 256000, + "output": 256000 }, - "temperature": false, + "temperature": true, "tool_call": true, - "reasoning": { - "supported": true, - "default": true + "reasoning": { + "supported": true }, "extra_capabilities": { "reasoning": { @@ -173045,29 +173637,30 @@ } }, "attachment": true, - "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-06-12", - "last_updated": "2026-06-12", + "open_weights": false, + "release_date": "2025-07-09", + "last_updated": "2025-07-09", "type": "chat" }, { - "id": "moonshotai/Kimi-K2.6", - "name": "Kimi K2.6", - "display_name": "Kimi K2.6", + "id": "muse-spark-1.2", + "name": "Muse Spark 1.2", + "display_name": "Muse Spark 1.2", "modalities": { "input": [ "text", "image", - "video" + "video", + "pdf", + "audio" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 1048576, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -173077,20 +173670,26 @@ }, "extra_capabilities": { "reasoning": { - "supported": true + "supported": true, + "effort_options": [ + "minimal", + "low", + "medium", + "high", + "xhigh" + ] } }, "attachment": true, - "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-04-20", - "last_updated": "2026-04-20", + "open_weights": false, + "release_date": "2026-08-05", + "last_updated": "2026-08-05", "type": "chat" }, { - "id": "moonshotai/Kimi-K3", - "name": "Kimi K3", - "display_name": "Kimi K3", + "id": "minimax-m3", + "name": "MiniMax-M3", + "display_name": "MiniMax-M3", "modalities": { "input": [ "text", @@ -173103,9 +173702,9 @@ }, "limit": { "context": 1048576, - "output": 131072 + "output": 512000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -173113,142 +173712,210 @@ }, "extra_capabilities": { "reasoning": { - "supported": true + "supported": true, + "effort_options": [ + "none", + "minimal", + "low", + "medium", + "high", + "xhigh", + "max" + ] } }, "attachment": true, "open_weights": true, - "release_date": "2026-07-16", - "last_updated": "2026-07-16", + "release_date": "2026-06-01", + "last_updated": "2026-06-01", "type": "chat" }, { - "id": "meta-llama/Llama-4-Maverick-17B-128E-Instruct-FP8", - "name": "Llama 4 Maverick 17B Instruct", - "display_name": "Llama 4 Maverick 17B Instruct", + "id": "glm-4.5", + "name": "GLM-4.5", + "display_name": "GLM-4.5", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 8192 + "context": 128000, + "output": 98304 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": true, + "attachment": false, "open_weights": true, - "knowledge": "2024-08", - "release_date": "2025-04-05", - "last_updated": "2025-04-05", + "knowledge": "2025-04", + "release_date": "2025-07-28", + "last_updated": "2025-07-28", "type": "chat" }, { - "id": "meta-llama/Meta-Llama-3.3-70B-Instruct", - "name": "Llama-3.3-70B-Instruct", - "display_name": "Llama-3.3-70B-Instruct", + "id": "qwen3.7-plus", + "name": "Qwen3.7 Plus", + "display_name": "Qwen3.7 Plus", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 8192 + "context": 983616, + "output": 64000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, - "open_weights": true, - "knowledge": "2023-12", - "release_date": "2024-12-06", - "last_updated": "2024-12-06", + "extra_capabilities": { + "reasoning": { + "supported": true, + "effort_options": [ + "none", + "minimal", + "low", + "medium", + "high", + "xhigh", + "max" + ] + } + }, + "attachment": true, + "open_weights": false, + "knowledge": "2025-04", + "release_date": "2026-06-02", + "last_updated": "2026-06-02", "type": "chat" }, { - "id": "meta-llama/Meta-Llama-3.1-405B-Instruct-Turbo", - "name": "Llama 3.1 405B Instruct Turbo", - "display_name": "Llama 3.1 405B Instruct Turbo", + "id": "qwen3.5-122b-a10b", + "name": "Qwen3.5 122B-A10B", + "display_name": "Qwen3.5 122B-A10B", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 4096 + "context": 262144, + "output": 65536 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "effort_options": [ + "none", + "minimal", + "low", + "medium", + "high", + "xhigh", + "max" + ] + } + }, + "attachment": true, "open_weights": true, - "release_date": "2024-07-23", - "last_updated": "2024-07-23", + "release_date": "2026-02-23", + "last_updated": "2026-02-23", "type": "chat" }, { - "id": "meta-llama/Meta-Llama-3.1-8B-Instruct", - "name": "Llama 3.1 8B Instruct", - "display_name": "Llama 3.1 8B Instruct", + "id": "step-3.7-flash", + "name": "Step 3.7 Flash", + "display_name": "Step 3.7 Flash", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 4096 + "context": 262144, + "output": 256000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "minimal", + "low", + "medium", + "high", + "xhigh", + "max" + ] + } + }, + "attachment": true, "open_weights": true, - "knowledge": "2023-12", - "release_date": "2024-07-23", - "last_updated": "2024-07-23", + "knowledge": "2026-03-01", + "release_date": "2026-05-29", + "last_updated": "2026-05-29", "type": "chat" }, { - "id": "deepseek-ai/DeepSeek-V3.1-Terminus", - "name": "DeepSeek V3.1 Terminus", - "display_name": "DeepSeek V3.1 Terminus", + "id": "gemini-3.1-flash-lite", + "name": "Gemini 3.1 Flash Lite", + "display_name": "Gemini 3.1 Flash Lite", "modalities": { "input": [ - "text" + "text", + "image", + "video", + "audio", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 8192 + "context": 1048576, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -173256,16 +173923,28 @@ "supported": true, "default": true }, - "attachment": false, - "open_weights": true, - "release_date": "2025-06-01", - "last_updated": "2025-06-01", + "extra_capabilities": { + "reasoning": { + "supported": true, + "effort_options": [ + "minimal", + "low", + "medium", + "high" + ] + } + }, + "attachment": true, + "open_weights": false, + "knowledge": "2025-01", + "release_date": "2026-05-07", + "last_updated": "2026-05-07", "type": "chat" }, { - "id": "deepseek-ai/DeepSeek-R1", - "name": "DeepSeek R1", - "display_name": "DeepSeek R1", + "id": "qwen3-32b", + "name": "Qwen3 32B", + "display_name": "Qwen3 32B", "modalities": { "input": [ "text" @@ -173275,8 +173954,8 @@ ] }, "limit": { - "context": 128000, - "output": 8192 + "context": 40960, + "output": 16384 }, "temperature": true, "tool_call": true, @@ -173297,26 +173976,27 @@ }, "attachment": false, "open_weights": true, - "knowledge": "2024-07", - "release_date": "2025-01-20", - "last_updated": "2025-05-29", + "knowledge": "2025-04", + "release_date": "2025-04-28", + "last_updated": "2025-04-28", "type": "chat" }, { - "id": "deepseek-ai/DeepSeek-V4-Pro", - "name": "DeepSeek V4 Pro", - "display_name": "DeepSeek V4 Pro", + "id": "muse-spark-1.2-contributor", + "name": "Muse Spark 1.2 Contributor", + "display_name": "Muse Spark 1.2 Contributor", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 32768 + "context": 1048576, + "output": 1048576 }, "temperature": true, "tool_call": true, @@ -173326,82 +174006,70 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "high", "effort_options": [ + "minimal", "low", + "medium", "high", - "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." + "xhigh" ] } }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-04-24", - "last_updated": "2026-04-24", + "attachment": true, + "open_weights": false, + "release_date": "2026-08-06", + "last_updated": "2026-08-06", "type": "chat" }, { - "id": "deepseek-ai/DeepSeek-V3.2", - "name": "DeepSeek V3.2", - "display_name": "DeepSeek V3.2", + "id": "gpt-4.1-mini", + "name": "GPT-4.1 mini", + "display_name": "GPT-4.1 mini", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 8192 + "context": 1000000, + "output": 32768 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true - } + "supported": false }, - "attachment": false, - "open_weights": true, - "knowledge": "2024-07", - "release_date": "2025-12-01", - "last_updated": "2025-12-01", + "attachment": true, + "open_weights": false, + "knowledge": "2024-04", + "release_date": "2025-04-14", + "last_updated": "2025-04-14", "type": "chat" }, { - "id": "deepseek-ai/DeepSeek-V4-Flash", - "name": "DeepSeek V4 Flash", - "display_name": "DeepSeek V4 Flash", + "id": "muse-spark-1.3", + "name": "Muse Spark 1.3", + "display_name": "Muse Spark 1.3", "modalities": { "input": [ - "text" + "text", + "image", + "video", + "pdf", + "audio" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 32768 + "context": 1048576, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -173412,45 +174080,25 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "high", "effort_options": [ + "minimal", "low", + "medium", "high", - "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." + "xhigh" ] } }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-04-24", - "last_updated": "2026-04-24", + "attachment": true, + "open_weights": false, + "release_date": "2026-09-02", + "last_updated": "2026-09-02", "type": "chat" - } - ] - }, - "neuralwatt": { - "id": "neuralwatt", - "name": "Neuralwatt", - "display_name": "Neuralwatt", - "api": "https://api.neuralwatt.com/v1", - "doc": "https://portal.neuralwatt.com/docs", - "models": [ + }, { - "id": "glm-5.3-flex", - "name": "GLM 5.3 Flex", - "display_name": "GLM 5.3 Flex", + "id": "qwen-max", + "name": "Qwen Max", + "display_name": "Qwen Max", "modalities": { "input": [ "text" @@ -173460,48 +174108,36 @@ ] }, "limit": { - "context": 1048560, - "output": 1048560 + "context": 32768, + "output": 8192 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "effort_options": [ - "low", - "high", - "max" - ] - } + "supported": false }, "attachment": false, - "open_weights": true, - "release_date": "2026-08-14", - "last_updated": "2026-08-14", + "open_weights": false, + "knowledge": "2024-04", + "release_date": "2024-04-03", + "last_updated": "2025-01-25", "type": "chat" }, { - "id": "gemma-4-31b", - "name": "Gemma 4 31B", - "display_name": "Gemma 4 31B", + "id": "glm-5.2-fast", + "name": "GLM-5.2", + "display_name": "GLM-5.2", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 262128, - "output": 16384 + "context": 1000000, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -173509,27 +174145,16 @@ "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "effort_options": [ - "none", - "max" - ] - } - }, - "attachment": true, + "attachment": false, "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-04-02", - "last_updated": "2026-04-02", + "release_date": "2026-06-13", + "last_updated": "2026-06-13", "type": "chat" }, { - "id": "deepseek-v4.1-flash-flex", - "name": "DeepSeek V4.1 Flash Flex", - "display_name": "DeepSeek V4.1 Flash Flex", + "id": "mistral-small-2603", + "name": "Mistral Small 4", + "display_name": "Mistral Small 4", "modalities": { "input": [ "text", @@ -173540,8 +174165,8 @@ ] }, "limit": { - "context": 1048560, - "output": 393216 + "context": 262144, + "output": 256000 }, "temperature": true, "tool_call": true, @@ -173549,30 +174174,17 @@ "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "effort_options": [ - "none", - "low", - "high", - "xhigh", - "max" - ] - } - }, "attachment": true, "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-10-17", - "last_updated": "2026-10-17", + "knowledge": "2025-06", + "release_date": "2026-03-16", + "last_updated": "2026-03-16", "type": "chat" }, { - "id": "kimi-k3-flex", - "name": "Kimi K3 Flex", - "display_name": "Kimi K3 Flex", + "id": "muse-glimmer-30b", + "name": "Muse Glimmer 30B", + "display_name": "Muse Glimmer 30B", "modalities": { "input": [ "text", @@ -173583,10 +174195,10 @@ ] }, "limit": { - "context": 1048560, - "output": 1048560 + "context": 131072, + "output": 131072 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -173594,66 +174206,57 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, "effort_options": [ - "none", + "minimal", "low", + "medium", "high", + "xhigh", "max" ] } }, "attachment": true, "open_weights": true, - "release_date": "2026-07-16", - "last_updated": "2026-07-16", + "knowledge": "2026-01-04", + "release_date": "2026-08-10", + "last_updated": "2026-08-10", "type": "chat" }, { - "id": "glm-5.3-flash", - "name": "GLM-5.3 Flash", - "display_name": "GLM-5.3 Flash", + "id": "gpt-4.1", + "name": "GPT-4.1", + "display_name": "GPT-4.1", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1048560, - "output": 1048560 + "context": 1000000, + "output": 32768 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "effort_options": [ - "low", - "high", - "max" - ] - } + "supported": false }, "attachment": true, - "open_weights": true, - "release_date": "2026-08-26", - "last_updated": "2026-08-26", + "open_weights": false, + "knowledge": "2024-04", + "release_date": "2025-04-14", + "last_updated": "2025-04-14", "type": "chat" }, { - "id": "deepseek-v4-flash", - "name": "DeepSeek V4 Flash", - "display_name": "DeepSeek V4 Flash", + "id": "minimax-m2.1", + "name": "MiniMax-M2.1", + "display_name": "MiniMax-M2.1", "modalities": { "input": [ "text" @@ -173663,8 +174266,8 @@ ] }, "limit": { - "context": 1048560, - "output": 393216 + "context": 204800, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -173675,49 +174278,40 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "high", "effort_options": [ "low", + "medium", "high", + "xhigh", "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." ] } }, "attachment": false, "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-04-24", - "last_updated": "2026-04-24", + "release_date": "2025-12-23", + "last_updated": "2025-12-23", "type": "chat" }, { - "id": "mimo-v2.6-pro", - "name": "MiMo-V2.6-Pro", - "display_name": "MiMo-V2.6-Pro", + "id": "gemini-3-flash-preview", + "name": "Gemini 3 Flash Preview", + "display_name": "Gemini 3 Flash Preview", "modalities": { "input": [ "text", "image", - "audio" + "video", + "audio", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1048560, - "output": 393216 + "context": 1048576, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -173728,35 +174322,46 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "effort_options": [ - "none", + "default_enabled": true, + "mode": "level", + "level": "high", + "level_options": [ + "minimal", + "low", + "medium", "high" + ], + "summaries": true, + "visibility": "summary", + "continuation": [ + "thought_signatures" ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-09-22", - "last_updated": "2026-09-22", + "open_weights": false, + "knowledge": "2025-01", + "release_date": "2025-12-17", + "last_updated": "2025-12-17", "type": "chat" }, { - "id": "kimi-k3", - "name": "Kimi K3", - "display_name": "Kimi K3", + "id": "gpt-6-luna", + "name": "GPT-6 Luna", + "display_name": "GPT-6 Luna", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1048560, - "output": 1048560 + "context": 1050000, + "output": 128000 }, "temperature": false, "tool_call": true, @@ -173766,38 +174371,40 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, "effort_options": [ "none", "low", + "medium", "high", + "xhigh", "max" ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-07-16", - "last_updated": "2026-07-16", + "open_weights": false, + "knowledge": "2026-05-18", + "release_date": "2026-09-22", + "last_updated": "2026-09-22", "type": "chat" }, { - "id": "kimi-k2.7-code-flex", - "name": "Kimi K2.7 Code Flex", - "display_name": "Kimi K2.7 Code Flex", + "id": "o3", + "name": "o3", + "display_name": "o3", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 262128, - "output": 262128 + "context": 200000, + "output": 100000 }, "temperature": false, "tool_call": true, @@ -173808,20 +174415,28 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true + "default_enabled": true, + "mode": "effort", + "effort": "medium", + "effort_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, "attachment": true, - "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-06-12", - "last_updated": "2026-06-12", + "open_weights": false, + "knowledge": "2024-05", + "release_date": "2025-04-16", + "last_updated": "2025-04-16", "type": "chat" }, { - "id": "qwen3.6-35b-fast", - "name": "Qwen3.6 35B Fast", - "display_name": "Qwen3.6 35B Fast", + "id": "qwen3-vl-plus", + "name": "Qwen3-VL Plus", + "display_name": "Qwen3-VL Plus", "modalities": { "input": [ "text", @@ -173832,13 +174447,14 @@ ] }, "limit": { - "context": 131056, - "output": 131056 + "context": 262144, + "output": 32768 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true + "supported": true, + "default": true }, "extra_capabilities": { "reasoning": { @@ -173852,27 +174468,27 @@ } }, "attachment": true, - "open_weights": true, - "release_date": "2026-04-17", - "last_updated": "2026-04-17", + "open_weights": false, + "knowledge": "2025-04", + "release_date": "2025-09-23", + "last_updated": "2025-09-23", "type": "chat" }, { - "id": "glm-5.3-flash-flex", - "name": "GLM-5.3 Flash Flex", - "display_name": "GLM-5.3 Flash Flex", + "id": "minimax-m2.7", + "name": "MiniMax-M2.7", + "display_name": "MiniMax-M2.7", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048560, - "output": 1048560 + "context": 204800, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -173884,35 +174500,43 @@ "reasoning": { "supported": true, "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ "low", + "medium", "high", + "xhigh", "max" ] } }, - "attachment": true, + "attachment": false, "open_weights": true, - "release_date": "2026-08-26", - "last_updated": "2026-08-26", + "release_date": "2026-03-18", + "last_updated": "2026-03-18", "type": "chat" }, { - "id": "qwen-3.8-27b", - "name": "Qwen3.8 27B", - "display_name": "Qwen3.8 27B", + "id": "minimax-m3.1-flash", + "name": "MiniMax-M3.1-Flash", + "display_name": "MiniMax-M3.1-Flash", "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 262128, - "output": 131072 + "context": 1048576, + "output": 512000 }, "temperature": true, "tool_call": true, @@ -173922,77 +174546,68 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, "effort_options": [ "none", + "minimal", "low", "medium", - "xhigh" + "high", + "xhigh", + "max" ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-08-14", - "last_updated": "2026-08-14", + "open_weights": false, + "release_date": "2026-10-19", + "last_updated": "2026-10-19", "type": "chat" }, { - "id": "mimo-v2.6-pro-flex", - "name": "MiMo-V2.6-Pro Flex", - "display_name": "MiMo-V2.6-Pro Flex", + "id": "mistral-small-2506", + "name": "Mistral Small 3.2", + "display_name": "Mistral Small 3.2", "modalities": { "input": [ "text", - "image", - "audio" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1048560, - "output": 393216 + "context": 128000, + "output": 16384 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "effort_options": [ - "none", - "high" - ] - } + "supported": false }, "attachment": true, "open_weights": true, - "release_date": "2026-09-22", - "last_updated": "2026-09-22", + "knowledge": "2025-03", + "release_date": "2025-06-20", + "last_updated": "2025-06-20", "type": "chat" }, { - "id": "deepseek-v4-flash-flex", - "name": "DeepSeek V4 Flash Flex", - "display_name": "DeepSeek V4 Flash Flex", + "id": "glm-4.6v-flashx", + "name": "GLM-4.6V FlashX", + "display_name": "GLM-4.6V FlashX", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 1048560, - "output": 393216 + "context": 128000, + "output": 16000 }, "temperature": true, "tool_call": true, @@ -174000,28 +174615,16 @@ "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "effort_options": [ - "none", - "high", - "max" - ] - } - }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-04-24", - "last_updated": "2026-04-24", + "attachment": true, + "open_weights": false, + "release_date": "2025-12-08", + "last_updated": "2025-12-08", "type": "chat" }, { - "id": "kimi-k3-fast", - "name": "Kimi K3 Fast", - "display_name": "Kimi K3 Fast", + "id": "custom", + "name": "Custom Model", + "display_name": "Custom Model", "modalities": { "input": [ "text", @@ -174032,80 +174635,95 @@ ] }, "limit": { - "context": 1048560, - "output": 1048560 + "context": 128000, + "output": 16384 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": false }, "attachment": true, - "open_weights": true, - "release_date": "2026-07-16", - "last_updated": "2026-07-16", + "open_weights": false, + "release_date": "2024-01-01", + "last_updated": "2024-01-01", "type": "chat" }, { - "id": "qwen3.6-35b-flex", - "name": "Qwen3.6 35B Flex", - "display_name": "Qwen3.6 35B Flex", + "id": "claude-opus-4-5-20251101", + "name": "Claude Opus 4.5", + "display_name": "Claude Opus 4.5", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 131056, - "output": 131056 + "context": 200000, + "output": 64000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": false, + "mode": "mixed", + "budget": { + "min": 1024, + "unit": "tokens" + }, + "effort": "high", + "effort_options": [ + "low", + "medium", + "high" + ], "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ "thinking_blocks" ], - "effort_options": [ - "none", - "high" + "notes": [ + "Claude Opus 4.5 uses manual thinking.type = \"enabled\" with budget_tokens; effort can be used alongside the thinking budget.", + "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header." ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-04-17", - "last_updated": "2026-04-17", + "open_weights": false, + "knowledge": "2025-05", + "release_date": "2025-11-24", + "last_updated": "2025-11-24", "type": "chat" }, { - "id": "kimi-k2.7-code-fast", - "name": "Kimi K2.7 Code Fast", - "display_name": "Kimi K2.7 Code Fast", + "id": "kimi-k2.5", + "name": "Kimi K2.5", + "display_name": "Kimi K2.5", "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 262128, - "output": 262128 + "context": 262144, + "output": 262144 }, "temperature": false, "tool_call": true, @@ -174116,32 +174734,45 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "effort_options": [ + "none", + "minimal", + "low", + "medium", + "high", + "xhigh", + "max" + ] } }, "attachment": true, "open_weights": true, "knowledge": "2025-01", - "release_date": "2026-06-12", - "last_updated": "2026-06-12", + "release_date": "2026-01-27", + "last_updated": "2026-01-27", "type": "chat" }, { - "id": "deepseek-v4.1-flash", - "name": "DeepSeek V4.1 Flash", - "display_name": "DeepSeek V4.1 Flash", + "id": "ling-3.0-flash", + "name": "InclusionAI Ling 3.0 Flash", + "display_name": "InclusionAI Ling 3.0 Flash", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048560, - "output": 393216 + "context": 262144, + "output": 262144 }, "temperature": true, "tool_call": true, @@ -174151,40 +174782,34 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, "effort_options": [ - "none", - "low", - "high", - "xhigh", - "max" + "none" ] } }, - "attachment": true, + "attachment": false, "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-10-17", - "last_updated": "2026-10-17", + "release_date": "2026-07-23", + "last_updated": "2026-07-23", "type": "chat" }, { - "id": "qwen-3.8-27b-flex", - "name": "Qwen3.8 27B Flex", - "display_name": "Qwen3.8 27B Flex", + "id": "kimi-k2.6", + "name": "Kimi K2.6", + "display_name": "Kimi K2.6", "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 262128, - "output": 131072 + "context": 262144, + "output": 262144 }, "temperature": true, "tool_call": true, @@ -174196,24 +174821,33 @@ "reasoning": { "supported": true, "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ "none", + "minimal", "low", "medium", - "xhigh" + "high", + "xhigh", + "max" ] } }, "attachment": true, "open_weights": true, - "release_date": "2026-08-14", - "last_updated": "2026-08-14", + "knowledge": "2025-01", + "release_date": "2026-04-20", + "last_updated": "2026-04-20", "type": "chat" }, { - "id": "kimi-k2.7-code", - "name": "Kimi K2.7 Code", - "display_name": "Kimi K2.7 Code", + "id": "mistral-large-latest", + "name": "Mistral Large (latest)", + "display_name": "Mistral Large (latest)", "modalities": { "input": [ "text", @@ -174224,10 +174858,42 @@ ] }, "limit": { - "context": 262128, - "output": 262128 + "context": 128000, + "output": 262144 }, - "temperature": false, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": false + }, + "attachment": true, + "open_weights": true, + "knowledge": "2024-11", + "release_date": "2024-11-01", + "last_updated": "2025-12-02", + "type": "chat" + }, + { + "id": "gemini-3.5-flash-lite", + "name": "Gemini 3.5 Flash Lite", + "display_name": "Gemini 3.5 Flash Lite", + "modalities": { + "input": [ + "text", + "image", + "video", + "audio", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1048576, + "output": 65536 + }, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -174236,20 +174902,25 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true + "effort_options": [ + "minimal", + "low", + "medium", + "high" + ] } }, "attachment": true, - "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-06-12", - "last_updated": "2026-06-12", + "open_weights": false, + "knowledge": "2026-03", + "release_date": "2026-07-21", + "last_updated": "2026-07-21", "type": "chat" }, { - "id": "glm-5.3", - "name": "GLM 5.3", - "display_name": "GLM 5.3", + "id": "gpt-oss-20b", + "name": "GPT OSS 20B", + "display_name": "GPT OSS 20B", "modalities": { "input": [ "text" @@ -174259,8 +174930,8 @@ ] }, "limit": { - "context": 1048560, - "output": 1048560 + "context": 131072, + "output": 32766 }, "temperature": true, "tool_call": true, @@ -174271,24 +174942,33 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, "effort_options": [ "low", - "high", - "max" + "medium", + "high" ] } }, "attachment": false, "open_weights": true, - "release_date": "2026-08-14", - "last_updated": "2026-08-14", + "knowledge": "2024-06", + "release_date": "2025-08-05", + "last_updated": "2025-08-05", "type": "chat" - }, + } + ] + }, + "umans-ai-coding-plan": { + "id": "umans-ai-coding-plan", + "name": "Umans AI Coding Plan", + "display_name": "Umans AI Coding Plan", + "api": "https://api.code.umans.ai/v1", + "doc": "https://app.umans.ai/offers/code/docs", + "models": [ { - "id": "qwen3.6-35b", - "name": "Qwen3.6 35B", - "display_name": "Qwen3.6 35B", + "id": "umans-qwen3.6-35b-a3b", + "name": "Qwen3.6 35B A3B", + "display_name": "Qwen3.6 35B A3B", "modalities": { "input": [ "text", @@ -174299,10 +174979,10 @@ ] }, "limit": { - "context": 131056, - "output": 131056 + "context": 262144, + "output": 262144 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -174318,7 +174998,8 @@ "thinking_blocks" ], "effort_options": [ - "none", + "low", + "medium", "high" ] } @@ -174330,9 +175011,9 @@ "type": "chat" }, { - "id": "deepseek-v4-flash-speed", - "name": "DeepSeek V4 Flash (Speed)", - "display_name": "DeepSeek V4 Flash (Speed)", + "id": "umans-deepseek-v4-flash-0731", + "name": "DeepSeek V4 Flash", + "display_name": "DeepSeek V4 Flash", "modalities": { "input": [ "text" @@ -174342,8 +175023,8 @@ ] }, "limit": { - "context": 1048560, - "output": 393216 + "context": 1048576, + "output": 393215 }, "temperature": true, "tool_call": true, @@ -174355,8 +175036,13 @@ "reasoning": { "supported": true, "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ - "none", + "low", "high", "max" ] @@ -174368,23 +175054,16 @@ "release_date": "2026-07-31", "last_updated": "2026-07-31", "type": "chat" - } - ] - }, - "inceptron": { - "id": "inceptron", - "name": "Inceptron", - "display_name": "Inceptron", - "api": "https://api.inceptron.io/v1", - "doc": "https://docs.inceptron.io", - "models": [ + }, { - "id": "zai-org/GLM-5.3-Flash", + "id": "umans-glm-5.3-flash", "name": "GLM 5.3 Flash", "display_name": "GLM 5.3 Flash", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" @@ -174392,7 +175071,7 @@ }, "limit": { "context": 1048576, - "output": 1048576 + "output": 131071 }, "temperature": true, "tool_call": true, @@ -174416,19 +175095,21 @@ ] } }, - "attachment": false, + "attachment": true, "open_weights": true, "release_date": "2026-08-26", "last_updated": "2026-08-26", "type": "chat" }, { - "id": "zai-org/GLM-5.2", - "name": "GLM 5.2", - "display_name": "GLM 5.2", + "id": "umans-coder", + "name": "Umans Coder", + "display_name": "Umans Coder", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" @@ -174436,9 +175117,9 @@ }, "limit": { "context": 1048576, - "output": 1048576 + "output": 131071 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -174454,24 +175135,26 @@ "thinking_blocks" ], "effort_options": [ + "low", "high", "max" ] } }, - "attachment": false, + "attachment": true, "open_weights": true, - "release_date": "2026-06-13", - "last_updated": "2026-06-13", + "release_date": "2026-08-26", + "last_updated": "2026-08-26", "type": "chat" }, { - "id": "zai-org/GLM-5.3", - "name": "GLM 5.3", - "display_name": "GLM 5.3", + "id": "umans-deepseek-v4.1-flash-1017", + "name": "DeepSeek V4.1 Flash 1017", + "display_name": "DeepSeek V4.1 Flash 1017", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" @@ -174479,7 +175162,7 @@ }, "limit": { "context": 1048576, - "output": 1048576 + "output": 393215 }, "temperature": true, "tool_call": true, @@ -174503,16 +175186,17 @@ ] } }, - "attachment": false, + "attachment": true, "open_weights": true, - "release_date": "2026-08-14", - "last_updated": "2026-08-14", + "knowledge": "2025-05", + "release_date": "2026-10-17", + "last_updated": "2026-10-17", "type": "chat" }, { - "id": "moonshotai/Kimi-K2.7-Code", - "name": "Kimi K2.7 Code", - "display_name": "Kimi K2.7 Code", + "id": "umans-deepseek-v4.1-flash", + "name": "DeepSeek V4.1 Flash", + "display_name": "DeepSeek V4.1 Flash", "modalities": { "input": [ "text", @@ -174523,8 +175207,8 @@ ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 1048576, + "output": 393215 }, "temperature": true, "tool_call": true, @@ -174540,32 +175224,36 @@ "visibility": "summary", "continuation": [ "thinking_blocks" + ], + "effort_options": [ + "low", + "high", + "max" ] } }, "attachment": true, "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-06-12", - "last_updated": "2026-06-12", + "knowledge": "2025-05", + "release_date": "2026-10-17", + "last_updated": "2026-10-17", "type": "chat" }, { - "id": "moonshotai/Kimi-K2.6", - "name": "Kimi K2.6", - "display_name": "Kimi K2.6", + "id": "umans-glm-5.3", + "name": "GLM 5.3", + "display_name": "GLM 5.3", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 1048576, + "output": 131071 }, "temperature": true, "tool_call": true, @@ -174581,23 +175269,28 @@ "visibility": "summary", "continuation": [ "thinking_blocks" + ], + "effort_options": [ + "low", + "high", + "max" ] } }, - "attachment": true, + "attachment": false, "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-04-20", - "last_updated": "2026-04-20", + "release_date": "2026-08-14", + "last_updated": "2026-08-14", "type": "chat" }, { - "id": "deepseek-ai/DeepSeek-V4-Flash-0731", - "name": "DeepSeek V4 Flash 0731", - "display_name": "DeepSeek V4 Flash 0731", + "id": "umans-kimi-k3", + "name": "Kimi K3", + "display_name": "Kimi K3", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" @@ -174605,9 +175298,9 @@ }, "limit": { "context": 1048576, - "output": 1048576 + "output": 131072 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -174623,100 +175316,87 @@ "thinking_blocks" ], "effort_options": [ + "low", "high", "max" ] } }, - "attachment": false, + "attachment": true, "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-07-31", - "last_updated": "2026-07-31", + "release_date": "2026-07-16", + "last_updated": "2026-07-16", "type": "chat" - } - ] - }, - "anthropic": { - "id": "anthropic", - "name": "Anthropic", - "display_name": "Anthropic", - "doc": "https://docs.anthropic.com/en/docs/about-claude/models", - "models": [ + }, { - "id": "claude-opus-4-5", - "name": "Claude Opus 4.5 (latest)", - "display_name": "Claude Opus 4.5 (latest)", + "id": "umans-flash", + "name": "Umans Flash", + "display_name": "Umans Flash", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 64000 + "context": 262144, + "output": 262144 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "mixed", - "budget": { - "min": 1024, - "unit": "tokens" - }, - "effort": "high", - "effort_options": [ - "low", - "medium", - "high" - ], "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ "thinking_blocks" ], - "notes": [ - "Claude Opus 4.5 uses manual thinking.type = \"enabled\" with budget_tokens; effort can be used alongside the thinking budget.", - "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header." + "effort_options": [ + "low", + "medium", + "high" ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2025-05", - "release_date": "2025-11-24", - "last_updated": "2025-11-24", + "open_weights": true, + "release_date": "2026-04-17", + "last_updated": "2026-04-17", "type": "chat" - }, + } + ] + }, + "orcarouter": { + "id": "orcarouter", + "name": "OrcaRouter", + "display_name": "OrcaRouter", + "api": "https://api.orcarouter.ai/v1", + "doc": "https://docs.orcarouter.ai", + "models": [ { - "id": "claude-sonnet-5-5", - "name": "Claude Sonnet 5.5", - "display_name": "Claude Sonnet 5.5", + "id": "openai/gpt-5.2-pro", + "name": "GPT-5.2 Pro", + "display_name": "GPT-5.2 Pro", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, + "context": 400000, "output": 128000 }, "temperature": false, @@ -174727,39 +175407,47 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "high", "effort_options": [ - "low", "medium", "high", - "xhigh", - "max" - ] + "xhigh" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, "attachment": true, "open_weights": false, - "knowledge": "2026-06", - "release_date": "2026-09-28", - "last_updated": "2026-09-28", + "knowledge": "2025-08-31", + "release_date": "2025-12-11", + "last_updated": "2025-12-11", "type": "chat" }, { - "id": "claude-haiku-5-5", - "name": "Claude Haiku 5.5", - "display_name": "Claude Haiku 5.5", + "id": "openai/gpt-5-pro", + "name": "GPT-5 Pro", + "display_name": "GPT-5 Pro", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 400000, + "output": 272000 }, "temperature": false, "tool_call": true, @@ -174769,69 +175457,82 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ + "supported": true, + "default_enabled": true, + "mode": "fixed", + "effort": "high", + "verbosity": "medium", + "verbosity_options": [ "low", "medium", - "high", - "xhigh", - "max" - ] + "high" + ], + "visibility": "hidden" } }, "attachment": true, "open_weights": false, - "knowledge": "2026-06", - "release_date": "2026-10-07", - "last_updated": "2026-10-07", + "knowledge": "2024-09-30", + "release_date": "2025-10-06", + "last_updated": "2025-10-06", "type": "chat" }, { - "id": "claude-opus-5", - "name": "Claude Opus 5", - "display_name": "Claude Opus 5", + "id": "openai/gpt-5.4-nano", + "name": "GPT-5.4 nano", + "display_name": "GPT-5.4 nano", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, + "context": 400000, "output": 128000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": false, + "mode": "effort", + "effort": "none", "effort_options": [ + "none", "low", "medium", "high", - "xhigh", - "max" - ] + "xhigh" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, "attachment": true, "open_weights": false, - "knowledge": "2026-05", - "release_date": "2026-07-24", - "last_updated": "2026-07-24", + "knowledge": "2025-08-31", + "release_date": "2026-03-17", + "last_updated": "2026-03-17", "type": "chat" }, { - "id": "claude-opus-4-7", - "name": "Claude Opus 4.7", - "display_name": "Claude Opus 4.7", + "id": "openai/gpt-5.4", + "name": "GPT-5.4", + "display_name": "GPT-5.4", "modalities": { "input": [ "text", @@ -174843,10 +175544,10 @@ ] }, "limit": { - "context": 1000000, + "context": 1050000, "output": 128000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -174857,80 +175558,86 @@ "supported": true, "default_enabled": false, "mode": "effort", - "effort": "high", + "effort": "none", "effort_options": [ + "none", "low", "medium", "high", - "xhigh", - "max" + "xhigh" ], - "interleaved": true, - "summaries": true, - "visibility": "omitted", - "continuation": [ - "thinking_blocks" + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" ], - "notes": [ - "Claude Opus 4.7 and newer Opus models require thinking.type = \"adaptive\" to enable thinking explicitly.", - "Manual budget_tokens requests return 400 on Claude Opus 4.7 and newer adaptive-only Opus models.", - "task_budget is separate from thinking control and should not be treated as a thinking budget." - ] + "visibility": "hidden" } }, "attachment": true, "open_weights": false, - "knowledge": "2026-01-31", - "release_date": "2026-04-14", - "last_updated": "2026-04-16", + "knowledge": "2025-08-31", + "release_date": "2026-03-05", + "last_updated": "2026-03-05", "type": "chat" }, { - "id": "claude-opus-5-5", - "name": "Claude Opus 5.5", - "display_name": "Claude Opus 5.5", + "id": "openai/gpt-5.4-mini", + "name": "GPT-5.4 mini", + "display_name": "GPT-5.4 mini", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, + "context": 400000, "output": 128000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": false, + "mode": "effort", + "effort": "none", "effort_options": [ + "none", "low", "medium", "high", - "xhigh", - "max" - ] + "xhigh" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, "attachment": true, "open_weights": false, - "knowledge": "2026-06", - "release_date": "2026-09-22", - "last_updated": "2026-09-22", + "knowledge": "2025-08-31", + "release_date": "2026-03-17", + "last_updated": "2026-03-17", "type": "chat" }, { - "id": "claude-sonnet-5", - "name": "Claude Sonnet 5", - "display_name": "Claude Sonnet 5", + "id": "openai/gpt-5.6-terra", + "name": "GPT-5.6 Terra", + "display_name": "GPT-5.6 Terra", "modalities": { "input": [ "text", @@ -174942,7 +175649,7 @@ ] }, "limit": { - "context": 1000000, + "context": 1050000, "output": 128000 }, "temperature": false, @@ -174954,26 +175661,37 @@ "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", "effort_options": [ + "none", "low", "medium", "high", "xhigh", "max" - ] + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, "attachment": true, "open_weights": false, - "knowledge": "2026-01-31", - "release_date": "2026-06-29", - "last_updated": "2026-06-30", + "knowledge": "2026-02-16", + "release_date": "2026-07-09", + "last_updated": "2026-07-09", "type": "chat" }, { - "id": "claude-sonnet-4-6", - "name": "Claude Sonnet 4.6", - "display_name": "Claude Sonnet 4.6", + "id": "openai/gpt-5.3-codex", + "name": "GPT-5.3 Codex", + "display_name": "GPT-5.3 Codex", "modalities": { "input": [ "text", @@ -174985,69 +175703,61 @@ ] }, "limit": { - "context": 1000000, + "context": 400000, "output": 128000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "mixed", - "budget": { - "min": 1024, - "unit": "tokens" - }, - "effort": "high", + "default_enabled": true, + "mode": "effort", + "effort": "medium", "effort_options": [ "low", "medium", "high", - "max" + "xhigh" ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" ], - "notes": [ - "Anthropic recommends adaptive thinking with effort for Claude 4.6; budget_tokens remains a deprecated compatibility path.", - "Anthropic API defaults effort to high; lower effort levels should be chosen per workload." - ] + "visibility": "hidden" } }, "attachment": true, "open_weights": false, "knowledge": "2025-08-31", - "release_date": "2026-02-17", - "last_updated": "2026-03-13", + "release_date": "2026-02-05", + "last_updated": "2026-02-05", "type": "chat" }, { - "id": "claude-opus-4-6", - "name": "Claude Opus 4.6", - "display_name": "Claude Opus 4.6", + "id": "openai/gpt-5.1-codex", + "name": "GPT-5.1 Codex", + "display_name": "GPT-5.1 Codex", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, + "context": 400000, "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -175057,93 +175767,63 @@ "reasoning": { "supported": true, "default_enabled": false, - "mode": "mixed", - "budget": { - "min": 1024, - "unit": "tokens" - }, - "effort": "high", + "mode": "effort", + "effort": "none", "effort_options": [ + "none", "low", "medium", - "high", - "max" + "high" ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" ], - "notes": [ - "Anthropic recommends adaptive thinking with effort for Claude 4.6; budget_tokens remains a deprecated compatibility path.", - "Anthropic API defaults effort to high; lower effort levels should be chosen per workload." - ] + "visibility": "hidden" } }, "attachment": true, "open_weights": false, - "knowledge": "2025-05-31", - "release_date": "2026-02-04", - "last_updated": "2026-03-13", + "knowledge": "2024-09-30", + "release_date": "2025-11-13", + "last_updated": "2025-11-13", "type": "chat" }, { - "id": "claude-sonnet-4-5-20250929", - "name": "Claude Sonnet 4.5", - "display_name": "Claude Sonnet 4.5", + "id": "openai/gpt-4o-2024-08-06", + "name": "GPT-4o (2024-08-06)", + "display_name": "GPT-4o (2024-08-06)", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 64000 + "context": 128000, + "output": 16384 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": false - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "budget", - "budget": { - "min": 1024, - "unit": "tokens" - }, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", - "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." - ] - } + "supported": false }, "attachment": true, "open_weights": false, - "knowledge": "2025-07-31", - "release_date": "2025-09-29", - "last_updated": "2025-09-29", + "knowledge": "2023-09", + "release_date": "2024-08-06", + "last_updated": "2024-08-06", "type": "chat" }, { - "id": "claude-haiku-4-5", - "name": "Claude Haiku 4.5 (latest)", - "display_name": "Claude Haiku 4.5 (latest)", + "id": "openai/gpt-5.2-codex", + "name": "GPT-5.2 Codex", + "display_name": "GPT-5.2 Codex", "modalities": { "input": [ "text", @@ -175155,60 +175835,59 @@ ] }, "limit": { - "context": 200000, - "output": 64000 + "context": 400000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "budget", - "budget": { - "min": 1024, - "unit": "tokens" - }, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "default_enabled": true, + "mode": "effort", + "effort": "medium", + "effort_options": [ + "low", + "medium", + "high", + "xhigh" ], - "notes": [ - "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", - "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." - ] + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, "attachment": true, "open_weights": false, - "knowledge": "2025-02-28", - "release_date": "2025-10-15", - "last_updated": "2025-10-15", + "knowledge": "2025-08-31", + "release_date": "2025-12-11", + "last_updated": "2025-12-11", "type": "chat" }, { - "id": "claude-haiku-4-5-20251001", - "name": "Claude Haiku 4.5", - "display_name": "Claude Haiku 4.5", + "id": "openai/gpt-5.2", + "name": "GPT-5.2", + "display_name": "GPT-5.2", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 64000 + "context": 400000, + "output": 128000 }, "temperature": true, "tool_call": true, @@ -175220,143 +175899,223 @@ "reasoning": { "supported": true, "default_enabled": false, - "mode": "budget", - "budget": { - "min": 1024, - "unit": "tokens" - }, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "mode": "effort", + "effort": "none", + "effort_options": [ + "none", + "low", + "medium", + "high", + "xhigh" ], - "notes": [ - "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", - "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." - ] + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, "attachment": true, "open_weights": false, - "knowledge": "2025-02-28", - "release_date": "2025-10-15", - "last_updated": "2025-10-15", + "knowledge": "2025-08-31", + "release_date": "2025-12-11", + "last_updated": "2025-12-11", "type": "chat" }, { - "id": "claude-opus-4-8", - "name": "Claude Opus 4.8", - "display_name": "Claude Opus 4.8", + "id": "openai/gpt-5.4-pro", + "name": "GPT-5.4 Pro", + "display_name": "GPT-5.4 Pro", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, + "context": 1050000, "output": 128000 }, "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, + "default_enabled": true, "mode": "effort", "effort": "high", "effort_options": [ - "low", "medium", "high", - "xhigh", - "max" + "xhigh" ], - "interleaved": true, - "summaries": true, - "visibility": "omitted", - "continuation": [ - "thinking_blocks" + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" ], - "notes": [ - "Claude Opus 4.7 and newer Opus models require thinking.type = \"adaptive\" to enable thinking explicitly.", - "Manual budget_tokens requests return 400 on Claude Opus 4.7 and newer adaptive-only Opus models.", - "task_budget is separate from thinking control and should not be treated as a thinking budget." - ] + "visibility": "hidden" } }, "attachment": true, "open_weights": false, - "knowledge": "2026-01", - "release_date": "2026-05-28", - "last_updated": "2026-05-28", + "knowledge": "2025-08-31", + "release_date": "2026-03-05", + "last_updated": "2026-03-05", "type": "chat" }, { - "id": "claude-sonnet-4-5", - "name": "Claude Sonnet 4.5 (latest)", - "display_name": "Claude Sonnet 4.5 (latest)", + "id": "openai/gpt-4o-2024-11-20", + "name": "GPT-4o (2024-11-20)", + "display_name": "GPT-4o (2024-11-20)", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 64000 + "context": 128000, + "output": 16384 + }, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": false + }, + "attachment": true, + "open_weights": false, + "knowledge": "2023-09", + "release_date": "2024-11-20", + "last_updated": "2024-11-20", + "type": "chat" + }, + { + "id": "openai/gpt-4o-2024-05-13", + "name": "GPT-4o (2024-05-13)", + "display_name": "GPT-4o (2024-05-13)", + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 4096 + }, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": false + }, + "attachment": true, + "open_weights": false, + "knowledge": "2023-09", + "release_date": "2024-05-13", + "last_updated": "2024-05-13", + "type": "chat" + }, + { + "id": "openai/gpt-4.1-nano", + "name": "GPT-4.1 nano", + "display_name": "GPT-4.1 nano", + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1047576, + "output": 32768 }, "temperature": true, "tool_call": true, + "reasoning": { + "supported": false + }, + "attachment": true, + "open_weights": false, + "knowledge": "2024-04", + "release_date": "2025-04-14", + "last_updated": "2025-04-14", + "type": "chat" + }, + { + "id": "openai/gpt-5-nano", + "name": "GPT-5 Nano", + "display_name": "GPT-5 Nano", + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 400000, + "output": 128000 + }, + "temperature": false, + "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "budget", - "budget": { - "min": 1024, - "unit": "tokens" - }, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "default_enabled": true, + "mode": "effort", + "effort": "medium", + "effort_options": [ + "minimal", + "low", + "medium", + "high" ], - "notes": [ - "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", - "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." - ] + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, "attachment": true, "open_weights": false, - "knowledge": "2025-07-31", - "release_date": "2025-09-29", - "last_updated": "2025-09-29", + "knowledge": "2024-05-30", + "release_date": "2025-08-07", + "last_updated": "2025-08-07", "type": "chat" }, { - "id": "claude-opus-4-5-20251101", - "name": "Claude Opus 4.5", - "display_name": "Claude Opus 4.5", + "id": "openai/gpt-5.5-pro", + "name": "GPT-5.5 Pro", + "display_name": "GPT-5.5 Pro", "modalities": { "input": [ "text", @@ -175368,53 +176127,66 @@ ] }, "limit": { - "context": 200000, - "output": 64000 + "context": 1050000, + "output": 100000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "mixed", - "budget": { - "min": 1024, - "unit": "tokens" - }, - "effort": "high", "effort_options": [ - "low", "medium", - "high" - ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Claude Opus 4.5 uses manual thinking.type = \"enabled\" with budget_tokens; effort can be used alongside the thinking budget.", - "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header." + "high", + "xhigh" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-05", - "release_date": "2025-11-24", - "last_updated": "2025-11-01", + "knowledge": "2025-12-01", + "release_date": "2026-04-23", + "last_updated": "2026-04-23", "type": "chat" }, { - "id": "claude-fable-5", - "name": "Claude Fable 5", - "display_name": "Claude Fable 5", + "id": "openai/gpt-5-chat-latest", + "name": "GPT-5 Chat (latest)", + "display_name": "GPT-5 Chat (latest)", + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 400000, + "output": 100000 + }, + "temperature": false, + "tool_call": false, + "reasoning": { + "supported": true, + "default": true + }, + "attachment": true, + "open_weights": false, + "knowledge": "2024-09-30", + "release_date": "2025-08-07", + "last_updated": "2025-08-07", + "type": "chat" + }, + { + "id": "openai/gpt-5.6-luna", + "name": "GPT-5.6 Luna", + "display_name": "GPT-5.6 Luna", "modalities": { "input": [ "text", @@ -175426,7 +176198,7 @@ ] }, "limit": { - "context": 1000000, + "context": 1050000, "output": 128000 }, "temperature": false, @@ -175440,49 +176212,46 @@ "supported": true, "default_enabled": true, "mode": "effort", - "effort": "high", + "effort": "medium", "effort_options": [ + "none", "low", "medium", "high", "xhigh", "max" ], - "interleaved": true, - "summaries": true, - "visibility": "omitted", - "continuation": [ - "thinking_blocks" + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" ], - "notes": [ - "Adaptive thinking is always on for Claude Fable 5 and Claude Mythos 5; thinking.type = \"disabled\" is rejected.", - "Manual budget_tokens requests return 400 on Claude Fable 5 and Claude Mythos 5.", - "thinking.display defaults to omitted; set display to summarized to receive readable thinking summaries." - ] + "visibility": "hidden" } }, "attachment": true, "open_weights": false, - "release_date": "2026-06-07", - "last_updated": "2026-06-09", + "knowledge": "2026-02-16", + "release_date": "2026-07-09", + "last_updated": "2026-07-09", "type": "chat" }, { - "id": "claude-fable-5-1", - "name": "Claude Fable 5.1", - "display_name": "Claude Fable 5.1", + "id": "openai/gpt-5", + "name": "GPT-5", + "display_name": "GPT-5", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, + "context": 400000, "output": 128000 }, "temperature": false, @@ -175496,47 +176265,33 @@ "supported": true, "default_enabled": true, "mode": "effort", - "effort": "high", + "effort": "medium", "effort_options": [ + "minimal", "low", "medium", - "high", - "xhigh", - "max" + "high" ], - "interleaved": true, - "summaries": true, - "visibility": "omitted", - "continuation": [ - "thinking_blocks" + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" ], - "notes": [ - "Adaptive thinking is always on for Claude Fable 5 and Claude Mythos 5; thinking.type = \"disabled\" is rejected.", - "Manual budget_tokens requests return 400 on Claude Fable 5 and Claude Mythos 5.", - "thinking.display defaults to omitted; set display to summarized to receive readable thinking summaries." - ] + "visibility": "hidden" } }, "attachment": true, "open_weights": false, - "knowledge": "2026-06", - "release_date": "2026-09-01", - "last_updated": "2026-09-01", + "knowledge": "2024-09-30", + "release_date": "2025-08-07", + "last_updated": "2025-08-07", "type": "chat" - } - ] - }, - "umans-ai": { - "id": "umans-ai", - "name": "Umans AI", - "display_name": "Umans AI", - "api": "https://api.code.umans.ai/v1", - "doc": "https://app.umans.ai/offers/code/docs/orgs", - "models": [ + }, { - "id": "umans-deepseek-v4.1-flash-1017", - "name": "DeepSeek V4.1 Flash 1017", - "display_name": "DeepSeek V4.1 Flash 1017", + "id": "openai/gpt-5-mini", + "name": "GPT-5 Mini", + "display_name": "GPT-5 Mini", "modalities": { "input": [ "text", @@ -175547,10 +176302,10 @@ ] }, "limit": { - "context": 1048576, - "output": 393215 + "context": 400000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -175559,76 +176314,65 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], + "default_enabled": true, + "mode": "effort", + "effort": "medium", "effort_options": [ + "minimal", "low", - "high", - "max" - ] + "medium", + "high" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, "attachment": true, - "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-10-17", - "last_updated": "2026-10-17", + "open_weights": false, + "knowledge": "2024-05-30", + "release_date": "2025-08-07", + "last_updated": "2025-08-07", "type": "chat" }, { - "id": "umans-coder", - "name": "Umans Coder", - "display_name": "Umans Coder", + "id": "openai/gpt-4o-mini", + "name": "GPT-4o mini", + "display_name": "GPT-4o mini", "modalities": { "input": [ "text", "image", - "video" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 131071 + "context": 128000, + "output": 16384 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "effort_options": [ - "low", - "high", - "max" - ] - } + "supported": false }, "attachment": true, - "open_weights": true, - "release_date": "2026-08-26", - "last_updated": "2026-08-26", + "open_weights": false, + "knowledge": "2023-09", + "release_date": "2024-07-18", + "last_updated": "2024-07-18", "type": "chat" }, { - "id": "umans-glm-5.3", - "name": "GLM 5.3", - "display_name": "GLM 5.3", + "id": "openai/gpt-oss-120b", + "name": "GPT OSS 120B", + "display_name": "GPT OSS 120B", "modalities": { "input": [ "text" @@ -175638,8 +176382,8 @@ ] }, "limit": { - "context": 1048576, - "output": 131071 + "context": 131072, + "output": 32768 }, "temperature": true, "tool_call": true, @@ -175650,180 +176394,125 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], "effort_options": [ "low", - "high", - "max" + "medium", + "high" ] } }, "attachment": false, "open_weights": true, - "release_date": "2026-08-14", - "last_updated": "2026-08-14", + "knowledge": "2024-06", + "release_date": "2025-08-05", + "last_updated": "2025-08-05", "type": "chat" }, { - "id": "umans-kimi-k3", - "name": "Kimi K3", - "display_name": "Kimi K3", + "id": "openai/gpt-4", + "name": "GPT-4", + "display_name": "GPT-4", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 8192, + "output": 8192 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "effort_options": [ - "low", - "high", - "max" - ] - } + "supported": false }, - "attachment": true, - "open_weights": true, - "release_date": "2026-07-16", - "last_updated": "2026-07-16", + "attachment": false, + "open_weights": false, + "knowledge": "2023-11", + "release_date": "2023-11-06", + "last_updated": "2024-04-09", "type": "chat" }, { - "id": "umans-deepseek-v4.1-flash", - "name": "DeepSeek V4.1 Flash", - "display_name": "DeepSeek V4.1 Flash", + "id": "openai/gpt-4o", + "name": "GPT-4o", + "display_name": "GPT-4o", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 393215 + "context": 128000, + "output": 16384 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "effort_options": [ - "low", - "high", - "max" - ] - } + "supported": false }, "attachment": true, - "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-10-17", - "last_updated": "2026-10-17", + "open_weights": false, + "knowledge": "2023-09", + "release_date": "2024-05-13", + "last_updated": "2024-08-06", "type": "chat" }, { - "id": "umans-flash", - "name": "Umans Flash", - "display_name": "Umans Flash", + "id": "openai/gpt-3.5-turbo", + "name": "GPT-3.5-turbo", + "display_name": "GPT-3.5-turbo", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 32768 + "context": 16385, + "output": 4096 }, - "temperature": false, - "tool_call": true, + "temperature": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "effort_options": [ - "low", - "medium", - "high" - ] - } + "supported": false }, - "attachment": true, - "open_weights": true, - "release_date": "2026-04-17", - "last_updated": "2026-04-17", + "attachment": false, + "open_weights": false, + "knowledge": "2021-09-01", + "release_date": "2023-03-01", + "last_updated": "2023-11-06", "type": "chat" }, { - "id": "umans-glm-5.3-flash", - "name": "GLM 5.3 Flash", - "display_name": "GLM 5.3 Flash", + "id": "openai/gpt-5.6-sol", + "name": "GPT-5.6 Sol", + "display_name": "GPT-5.6 Sol", "modalities": { "input": [ "text", "image", - "video" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 131071 + "context": 1050000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -175832,96 +176521,103 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], + "default_enabled": true, + "mode": "effort", + "effort": "medium", "effort_options": [ + "none", "low", + "medium", "high", + "xhigh", "max" - ] + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, "attachment": true, - "open_weights": true, - "release_date": "2026-08-26", - "last_updated": "2026-08-26", + "open_weights": false, + "knowledge": "2026-02-16", + "release_date": "2026-07-09", + "last_updated": "2026-07-09", "type": "chat" }, { - "id": "umans-deepseek-v4-flash-0731", - "name": "DeepSeek V4 Flash", - "display_name": "DeepSeek V4 Flash", + "id": "openai/gpt-5.1", + "name": "GPT-5.1", + "display_name": "GPT-5.1", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 393215 + "context": 400000, + "output": 128000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], + "default_enabled": false, + "mode": "effort", + "effort": "none", "effort_options": [ + "none", "low", - "high", - "max" - ] + "medium", + "high" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-07-31", - "last_updated": "2026-07-31", + "attachment": true, + "open_weights": false, + "knowledge": "2024-09-30", + "release_date": "2025-11-13", + "last_updated": "2025-11-13", "type": "chat" - } - ] - }, - "evroc": { - "id": "evroc", - "name": "evroc", - "display_name": "evroc", - "api": "https://models.think.evroc.com/v1", - "doc": "https://docs.evroc.com/products/think/overview.html", - "models": [ + }, { - "id": "zai-org/GLM-5.2", - "name": "GLM-5.2", - "display_name": "GLM-5.2", + "id": "openai/gpt-5.5", + "name": "GPT-5.5", + "display_name": "GPT-5.5", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 524288, - "output": 131072 + "context": 1050000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -175930,104 +176626,129 @@ "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", "effort_options": [ + "low", + "medium", "high", - "max" - ] + "xhigh" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-06-13", - "last_updated": "2026-06-13", + "attachment": true, + "open_weights": false, + "knowledge": "2025-12-01", + "release_date": "2026-04-23", + "last_updated": "2026-04-23", "type": "chat" }, { - "id": "Qwen/Qwen3.6-35B-A3B", - "name": "Qwen3.6 35B-A3B", - "display_name": "Qwen3.6 35B-A3B", + "id": "openai/gpt-5.1-codex-mini", + "name": "GPT-5.1 Codex mini", + "display_name": "GPT-5.1 Codex mini", "modalities": { "input": [ "text", - "image", - "video" + "image" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 65536 + "context": 400000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] + "default_enabled": false, + "mode": "effort", + "effort": "none", + "effort_options": [ + "none", + "low", + "medium", + "high" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, "attachment": true, - "open_weights": true, - "release_date": "2026-04-17", - "last_updated": "2026-04-17", + "open_weights": false, + "knowledge": "2024-09-30", + "release_date": "2025-11-13", + "last_updated": "2025-11-13", "type": "chat" }, { - "id": "Qwen/Qwen3-Reranker-4B", - "name": "Qwen3 Reranker 4B", - "display_name": "Qwen3 Reranker 4B", + "id": "openai/gpt-4-turbo", + "name": "GPT-4 Turbo", + "display_name": "GPT-4 Turbo", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 32000, + "context": 128000, "output": 4096 }, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { "supported": false }, - "attachment": false, - "open_weights": true, - "release_date": "2025-07-30", - "last_updated": "2025-07-30", - "type": "rerank" + "attachment": true, + "open_weights": false, + "knowledge": "2023-12", + "release_date": "2023-11-06", + "last_updated": "2024-04-09", + "type": "chat" }, { - "id": "Qwen/Qwen3.8-27B", - "name": "Qwen3.8-27B", - "display_name": "Qwen3.8-27B", + "id": "openai/gpt-5.2-chat-latest", + "name": "GPT-5.2 Chat", + "display_name": "GPT-5.2 Chat", "modalities": { "input": [ "text", - "image", - "video" + "image" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 128000, + "output": 16384 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -176036,131 +176757,125 @@ "extra_capabilities": { "reasoning": { "effort_options": [ - "low", - "medium", - "xhigh" + "medium" ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-08-14", - "last_updated": "2026-08-14", + "open_weights": false, + "knowledge": "2025-08-31", + "release_date": "2025-12-11", + "last_updated": "2025-12-11", "type": "chat" }, { - "id": "Qwen/Qwen3-Embedding-8B", - "name": "Qwen3 Embedding 8B", - "display_name": "Qwen3 Embedding 8B", + "id": "openai/gpt-4.1-mini", + "name": "GPT-4.1 mini", + "display_name": "GPT-4.1 mini", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 40960, - "output": 4096 + "context": 1047576, + "output": 32768 }, - "temperature": false, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { "supported": false }, - "attachment": false, - "open_weights": true, - "release_date": "2025-06-05", - "last_updated": "2025-06-05", - "type": "embedding" + "attachment": true, + "open_weights": false, + "knowledge": "2024-04", + "release_date": "2025-04-14", + "last_updated": "2025-04-14", + "type": "chat" }, { - "id": "nvidia/Llama-3.3-70B-Instruct-FP8", - "name": "Llama-3.3-70B-Instruct", - "display_name": "Llama-3.3-70B-Instruct", + "id": "openai/gpt-4.1", + "name": "GPT-4.1", + "display_name": "GPT-4.1", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 4096 + "context": 1047576, + "output": 32768 }, "temperature": true, "tool_call": true, "reasoning": { "supported": false }, - "attachment": false, - "open_weights": true, - "knowledge": "2023-12", - "release_date": "2024-12-06", - "last_updated": "2024-12-06", + "attachment": true, + "open_weights": false, + "knowledge": "2024-04", + "release_date": "2025-04-14", + "last_updated": "2025-04-14", "type": "chat" }, { - "id": "evroc/roc", - "name": "roc", - "display_name": "roc", + "id": "openai/gpt-5.1-chat-latest", + "name": "GPT-5.1 Chat", + "display_name": "GPT-5.1 Chat", "modalities": { "input": [ "text", - "image", - "video" + "image" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 128000, + "output": 16384 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } - }, "attachment": true, "open_weights": false, - "knowledge": "2026-01", - "release_date": "2026-06-06", - "last_updated": "2026-06-06", + "knowledge": "2024-09-30", + "release_date": "2025-11-13", + "last_updated": "2025-11-13", "type": "chat" }, { - "id": "google/gemma-4-26B-A4B-it", - "name": "Gemma 4 26B A4B IT", - "display_name": "Gemma 4 26B A4B IT", + "id": "z-ai/glm-5.3-flash", + "name": "GLM-5.3-Flash", + "display_name": "GLM-5.3-Flash", "modalities": { "input": [ "text", - "image" + "image", + "video", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 32768 + "context": 1000000, + "output": 128000 }, "temperature": true, "tool_call": true, @@ -176170,19 +176885,30 @@ }, "extra_capabilities": { "reasoning": { - "supported": true + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "effort_options": [ + "low", + "high", + "max" + ] } }, "attachment": true, "open_weights": true, - "release_date": "2026-04-02", - "last_updated": "2026-04-02", + "release_date": "2026-08-26", + "last_updated": "2026-08-26", "type": "chat" }, { - "id": "intfloat/multilingual-e5-large-instruct", - "name": "E5 Multi-Lingual Large Embeddings 0.6B", - "display_name": "E5 Multi-Lingual Large Embeddings 0.6B", + "id": "z-ai/glm-4.7", + "name": "GLM-4.7", + "display_name": "GLM-4.7", "modalities": { "input": [ "text" @@ -176192,75 +176918,100 @@ ] }, "limit": { - "context": 512, - "output": 512 + "context": 204800, + "output": 131072 }, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } }, "attachment": false, "open_weights": true, - "release_date": "2024-06-01", - "last_updated": "2024-06-01", - "type": "embedding" + "knowledge": "2025-04", + "release_date": "2025-12-22", + "last_updated": "2025-12-22", + "type": "chat" }, { - "id": "openai/whisper-large-v3-turbo", - "name": "Whisper Large v3 Turbo", - "display_name": "Whisper Large v3 Turbo", + "id": "z-ai/glm-4.6", + "name": "GLM-4.6", + "display_name": "GLM-4.6", "modalities": { "input": [ - "audio" + "text" ], "output": [ "text" ] }, "limit": { - "context": 448, - "output": 448 + "context": 204800, + "output": 131072 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": true, + "extra_capabilities": { + "reasoning": { + "supported": true + } + }, + "attachment": false, "open_weights": true, - "release_date": "2024-10-01", - "last_updated": "2024-10-01" + "knowledge": "2025-04", + "release_date": "2025-09-30", + "last_updated": "2025-09-30", + "type": "chat" }, { - "id": "openai/whisper-large-v3", - "name": "Whisper 3 Large", - "display_name": "Whisper 3 Large", + "id": "z-ai/glm-4.5-air", + "name": "GLM-4.5-Air", + "display_name": "GLM-4.5-Air", "modalities": { "input": [ - "audio" + "text" ], "output": [ "text" ] }, "limit": { - "context": 448, - "output": 4096 + "context": 131072, + "output": 98304 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": true, + "attachment": false, "open_weights": true, - "release_date": "2023-11-06", - "last_updated": "2023-11-06" + "knowledge": "2025-04", + "release_date": "2025-07-28", + "last_updated": "2025-07-28", + "type": "chat" }, { - "id": "openai/gpt-oss-120b", - "name": "GPT OSS 120B", - "display_name": "GPT OSS 120B", + "id": "z-ai/glm-5.3", + "name": "GLM-5.3", + "display_name": "GLM-5.3", "modalities": { "input": [ "text" @@ -176270,8 +177021,8 @@ ] }, "limit": { - "context": 65536, - "output": 65536 + "context": 1000000, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -176282,62 +177033,79 @@ "extra_capabilities": { "reasoning": { "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ "low", - "medium", - "high" + "high", + "max" ] } }, "attachment": false, "open_weights": true, - "knowledge": "2024-06", - "release_date": "2025-08-05", - "last_updated": "2025-08-05", + "release_date": "2026-08-14", + "last_updated": "2026-08-14", "type": "chat" }, { - "id": "KBLab/kb-whisper-large", - "name": "KB Whisper", - "display_name": "KB Whisper", + "id": "z-ai/glm-5", + "name": "GLM-5", + "display_name": "GLM-5", "modalities": { "input": [ - "audio" + "text" ], "output": [ "text" ] }, "limit": { - "context": 448, - "output": 448 + "context": 204800, + "output": 131072 }, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": true, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } + }, + "attachment": false, "open_weights": true, - "release_date": "2024-10-01", - "last_updated": "2024-10-01" + "release_date": "2026-02-12", + "last_updated": "2026-02-12", + "type": "chat" }, { - "id": "moonshotai/Kimi-K2.6", - "name": "Kimi K2.6", - "display_name": "Kimi K2.6", + "id": "z-ai/glm-5.1", + "name": "GLM-5.1", + "display_name": "GLM-5.1", "modalities": { "input": [ - "text", - "image", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 200000, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -176347,32 +177115,36 @@ }, "extra_capabilities": { "reasoning": { - "supported": true + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] } }, - "attachment": true, + "attachment": false, "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-04-20", - "last_updated": "2026-04-20", + "release_date": "2026-04-07", + "last_updated": "2026-04-07", "type": "chat" }, { - "id": "mistralai/Mistral-Medium-3.5-128B", - "name": "Mistral Medium 3.5", - "display_name": "Mistral Medium 3.5", + "id": "z-ai/glm-5.2", + "name": "GLM-5.2", + "display_name": "GLM-5.2", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 1000000, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -176382,25 +177154,31 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ - "none", - "high" + "high", + "max" ] } }, - "attachment": true, + "attachment": false, "open_weights": true, - "release_date": "2026-04-29", - "last_updated": "2026-04-29", + "release_date": "2026-06-13", + "last_updated": "2026-06-13", "type": "chat" }, { - "id": "mistralai/Voxtral-Small-24B-2507", - "name": "Voxtral Small 24B", - "display_name": "Voxtral Small 24B", + "id": "z-ai/glm-4.5", + "name": "GLM-4.5", + "display_name": "GLM-4.5", "modalities": { "input": [ - "audio", "text" ], "output": [ @@ -176408,102 +177186,112 @@ ] }, "limit": { - "context": 32000, - "output": 32000 + "context": 131072, + "output": 98304 }, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": true, + "attachment": false, "open_weights": true, - "release_date": "2025-03-01", - "last_updated": "2025-03-01" - } - ] - }, - "bailing": { - "id": "bailing", - "name": "Bailing", - "display_name": "Bailing", - "api": "https://api.tbox.cn/api/llm/v1", - "doc": "https://alipaytbox.yuque.com/sxs0ba/ling/intro", - "models": [ + "knowledge": "2025-04", + "release_date": "2025-07-28", + "last_updated": "2025-07-28", + "type": "chat" + }, { - "id": "Ling-1T", - "name": "Ling-1T", - "display_name": "Ling-1T", + "id": "z-ai/glm-5.3-flash-free", + "name": "GLM-5.3-Flash (free)", + "display_name": "GLM-5.3-Flash (free)", "modalities": { "input": [ - "text" + "text", + "image", + "video", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 32000 + "context": 1000000, + "output": 128000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "effort_options": [ + "low", + "high", + "max" + ] + } + }, + "attachment": true, "open_weights": true, - "knowledge": "2024-06", - "release_date": "2025-10", - "last_updated": "2025-10", + "release_date": "2026-08-26", + "last_updated": "2026-08-26", "type": "chat" }, { - "id": "Ring-1T", - "name": "Ring-1T", - "display_name": "Ring-1T", + "id": "grok/grok-4.5", + "name": "Grok 4.5", + "display_name": "Grok 4.5", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 32000 + "context": 500000, + "output": 500000 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { "supported": true, "default": true }, "extra_capabilities": { "reasoning": { - "supported": true + "supported": true, + "effort_options": [ + "low", + "medium", + "high" + ] } }, - "attachment": false, - "open_weights": true, - "knowledge": "2024-06", - "release_date": "2025-10", - "last_updated": "2025-10", + "attachment": true, + "open_weights": false, + "release_date": "2026-07-08", + "last_updated": "2026-07-08", "type": "chat" - } - ] - }, - "github-copilot": { - "id": "github-copilot", - "name": "GitHub Copilot", - "display_name": "GitHub Copilot", - "api": "https://api.githubcopilot.com", - "doc": "https://docs.github.com/en/copilot", - "models": [ + }, { - "id": "gpt-5.5", - "name": "GPT-5.5", - "display_name": "GPT-5.5", + "id": "grok/grok-4.3", + "name": "Grok 4.3", + "display_name": "Grok 4.3", "modalities": { "input": [ "text", @@ -176515,10 +177303,10 @@ ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 1000000, + "output": 30000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -176527,50 +177315,38 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", "effort_options": [ - "low", - "medium", - "high", - "xhigh" - ], - "verbosity": "medium", - "verbosity_options": [ + "none", "low", "medium", "high" - ], - "visibility": "hidden" + ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-12-01", - "release_date": "2026-04-23", - "last_updated": "2026-04-23", + "release_date": "2026-04-17", + "last_updated": "2026-04-17", "type": "chat" }, { - "id": "gpt-6-astra", - "name": "GPT-6 Astra", - "display_name": "GPT-6 Astra", + "id": "grok/grok-4.6", + "name": "Grok 4.6", + "display_name": "Grok 4.6", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 500000, + "output": 500000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -176583,90 +177359,67 @@ "low", "medium", "high", - "xhigh", - "max" + "xhigh" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-04-30", - "release_date": "2026-09-04", - "last_updated": "2026-09-04", + "knowledge": "2026-02-01", + "release_date": "2026-08-12", + "last_updated": "2026-08-12", "type": "chat" }, { - "id": "gpt-5.4", - "name": "GPT-5.4", - "display_name": "GPT-5.4", + "id": "minimax/minimax-m2.5", + "name": "MiniMax-M2.5", + "display_name": "MiniMax-M2.5", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 204800, + "output": 131072 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "none", - "effort_options": [ - "none", - "low", - "medium", - "high", - "xhigh" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + "supported": true } }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-03-05", - "last_updated": "2026-03-05", + "attachment": false, + "open_weights": true, + "release_date": "2026-02-12", + "last_updated": "2026-02-12", "type": "chat" }, { - "id": "claude-fable-5.1", - "name": "Claude Fable 5.1", - "display_name": "Claude Fable 5.1", + "id": "minimax/minimax-m2.5-highspeed", + "name": "MiniMax-M2.5-highspeed", + "display_name": "MiniMax-M2.5-highspeed", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 204800, + "output": 131072 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -176674,55 +177427,32 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "high", - "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "omitted", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Adaptive thinking is always on for Claude Fable 5 and Claude Mythos 5; thinking.type = \"disabled\" is rejected.", - "Manual budget_tokens requests return 400 on Claude Fable 5 and Claude Mythos 5.", - "thinking.display defaults to omitted; set display to summarized to receive readable thinking summaries." - ] + "supported": true } }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-06", - "release_date": "2026-09-01", - "last_updated": "2026-09-01", + "attachment": false, + "open_weights": true, + "release_date": "2026-02-13", + "last_updated": "2026-02-13", "type": "chat" }, { - "id": "kimi-k3", - "name": "Kimi K3", - "display_name": "Kimi K3", + "id": "minimax/minimax-m2.7-highspeed", + "name": "MiniMax-M2.7-highspeed", + "display_name": "MiniMax-M2.7-highspeed", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, + "context": 204800, "output": 131072 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -176731,38 +177461,39 @@ "extra_capabilities": { "reasoning": { "supported": true, - "effort_options": [ - "low", - "high", - "max" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, - "attachment": true, + "attachment": false, "open_weights": true, - "release_date": "2026-07-16", - "last_updated": "2026-07-16", + "release_date": "2026-03-18", + "last_updated": "2026-03-18", "type": "chat" }, { - "id": "claude-opus-5", - "name": "Claude Opus 5", - "display_name": "Claude Opus 5", + "id": "minimax/minimax-m3", + "name": "MiniMax-M3", + "display_name": "MiniMax-M3", "modalities": { "input": [ "text", "image", - "pdf" + "video" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 64000 + "context": 1048576, + "output": 512000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -176770,39 +177501,30 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" - ] + "supported": true } }, "attachment": true, - "open_weights": false, - "knowledge": "2026-05", - "release_date": "2026-07-24", - "last_updated": "2026-07-24", + "open_weights": true, + "release_date": "2026-06-01", + "last_updated": "2026-06-01", "type": "chat" }, { - "id": "grok-4.5", - "name": "Grok 4.5", - "display_name": "Grok 4.5", + "id": "minimax/minimax-m2.7", + "name": "MiniMax-M2.7", + "display_name": "MiniMax-M2.7", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 500000, - "output": 128000 + "context": 204800, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -176813,95 +177535,75 @@ "extra_capabilities": { "reasoning": { "supported": true, - "effort_options": [ - "low", - "medium", - "high" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, - "attachment": true, - "open_weights": false, - "release_date": "2026-07-08", - "last_updated": "2026-07-08", + "attachment": false, + "open_weights": true, + "release_date": "2026-03-18", + "last_updated": "2026-03-18", "type": "chat" }, { - "id": "claude-opus-4.8", - "name": "Claude Opus 4.8", - "display_name": "Claude Opus 4.8", + "id": "tencent/hy3-free", + "name": "Hy3 (free)", + "display_name": "Hy3 (free)", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 64000 + "context": 256000, + "output": 128000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "high", "effort_options": [ + "none", "low", - "medium", - "high", - "xhigh", - "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "omitted", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Claude Opus 4.7 and newer Opus models require thinking.type = \"adaptive\" to enable thinking explicitly.", - "Manual budget_tokens requests return 400 on Claude Opus 4.7 and newer adaptive-only Opus models.", - "task_budget is separate from thinking control and should not be treated as a thinking budget." + "high" ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-01", - "release_date": "2026-05-28", - "last_updated": "2026-05-28", + "attachment": false, + "open_weights": true, + "release_date": "2026-07-06", + "last_updated": "2026-07-06", "type": "chat" }, { - "id": "claude-sonnet-5", - "name": "Claude Sonnet 5", - "display_name": "Claude Sonnet 5", + "id": "tencent/hy3", + "name": "Hy3", + "display_name": "Hy3", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, + "context": 256000, "output": 128000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -176911,37 +177613,37 @@ "reasoning": { "supported": true, "effort_options": [ + "none", "low", - "medium", - "high", - "xhigh", - "max" + "high" ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-01-31", - "release_date": "2026-06-30", - "last_updated": "2026-06-30", + "attachment": false, + "open_weights": true, + "release_date": "2026-07-06", + "last_updated": "2026-07-06", "type": "chat" }, { - "id": "gemini-3.7-flash", - "name": "Gemini 3.7 Flash", - "display_name": "Gemini 3.7 Flash", + "id": "google/gemini-3.5-flash", + "name": "Gemini 3.5 Flash", + "display_name": "Gemini 3.5 Flash", "modalities": { "input": [ "text", - "image" + "image", + "video", + "audio", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 64000 + "context": 1048576, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -176970,30 +177672,61 @@ }, "attachment": true, "open_weights": false, - "knowledge": "2026-03", - "release_date": "2026-08-13", - "last_updated": "2026-08-13", + "knowledge": "2025-01", + "release_date": "2026-05-19", + "last_updated": "2026-05-19", "type": "chat" }, { - "id": "gpt-6-sol", - "name": "GPT-6 Sol", - "display_name": "GPT-6 Sol", + "id": "google/gemini-robotics-er-1.6-preview", + "name": "Gemini Robotics-ER 1.6 Preview", + "display_name": "Gemini Robotics-ER 1.6 Preview", "modalities": { "input": [ "text", "image", - "pdf" + "video", + "audio" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 131072, + "output": 65536 }, - "temperature": false, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": true, + "default": true + }, + "attachment": true, + "open_weights": false, + "knowledge": "2025-01", + "release_date": "2026-04-14", + "last_updated": "2026-04-14", + "type": "chat" + }, + { + "id": "google/gemma-4-26b-a4b-it", + "name": "Gemma 4 26B A4B IT", + "display_name": "Gemma 4 26B A4B IT", + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 262144, + "output": 32768 + }, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -177001,31 +177734,25 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "none", - "low", - "medium", - "high", - "xhigh", - "max" - ] + "supported": true } }, "attachment": true, - "open_weights": false, - "knowledge": "2026-04-20", - "release_date": "2026-09-22", - "last_updated": "2026-09-22", + "open_weights": true, + "release_date": "2026-04-02", + "last_updated": "2026-04-02", "type": "chat" }, { - "id": "gpt-5.6-sol", - "name": "GPT-5.6 Sol", - "display_name": "GPT-5.6 Sol", + "id": "google/gemini-flash-latest", + "name": "Gemini Flash Latest", + "display_name": "Gemini Flash Latest", "modalities": { "input": [ "text", "image", + "video", + "audio", "pdf" ], "output": [ @@ -177033,10 +177760,10 @@ ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 1048576, + "output": 65536 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -177044,42 +177771,30 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", "effort_options": [ - "none", - "low", - "medium", - "high", - "xhigh", - "max" - ], - "verbosity": "medium", - "verbosity_options": [ "low", "medium", "high" - ], - "visibility": "hidden" + ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-02-16", - "release_date": "2026-07-09", - "last_updated": "2026-07-09", + "knowledge": "2026-03", + "release_date": "2026-08-13", + "last_updated": "2026-08-13", "type": "chat" }, { - "id": "gpt-5.6-terra", - "name": "GPT-5.6 Terra", - "display_name": "GPT-5.6 Terra", + "id": "google/gemini-3.6-flash", + "name": "Gemini 3.6 Flash", + "display_name": "Gemini 3.6 Flash", "modalities": { "input": [ "text", "image", + "video", + "audio", "pdf" ], "output": [ @@ -177087,10 +177802,10 @@ ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 1048576, + "output": 65536 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -177100,40 +177815,38 @@ "reasoning": { "supported": true, "default_enabled": true, - "mode": "effort", - "effort": "medium", - "effort_options": [ - "none", - "low", - "medium", - "high", - "xhigh", - "max" - ], - "verbosity": "medium", - "verbosity_options": [ + "mode": "level", + "level": "high", + "level_options": [ + "minimal", "low", "medium", "high" ], - "visibility": "hidden" + "summaries": true, + "visibility": "summary", + "continuation": [ + "thought_signatures" + ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-02-16", - "release_date": "2026-07-09", - "last_updated": "2026-07-09", + "knowledge": "2026-03", + "release_date": "2026-07-21", + "last_updated": "2026-07-21", "type": "chat" }, { - "id": "gpt-6-luna", - "name": "GPT-6 Luna", - "display_name": "GPT-6 Luna", + "id": "google/gemini-3.1-flash-lite-preview", + "name": "Gemini 3.1 Flash Lite Preview", + "display_name": "Gemini 3.1 Flash Lite Preview", "modalities": { "input": [ "text", "image", + "video", + "audio", "pdf" ], "output": [ @@ -177141,10 +177854,10 @@ ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 1048576, + "output": 65536 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -177153,40 +177866,41 @@ "extra_capabilities": { "reasoning": { "effort_options": [ - "none", + "minimal", "low", "medium", - "high", - "xhigh", - "max" + "high" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-05-18", - "release_date": "2026-09-22", - "last_updated": "2026-09-22", + "knowledge": "2025-01", + "release_date": "2026-03-03", + "last_updated": "2026-03-03", "type": "chat" }, { - "id": "gpt-5-mini", - "name": "GPT-5 Mini", - "display_name": "GPT-5 Mini", + "id": "google/gemini-2.5-pro", + "name": "Gemini 2.5 Pro", + "display_name": "Gemini 2.5 Pro", "modalities": { "input": [ "text", - "image" + "image", + "audio", + "video", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 264000, - "output": 64000 + "context": 1048576, + "output": 65536 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -177196,46 +177910,47 @@ "reasoning": { "supported": true, "default_enabled": true, - "mode": "effort", - "effort": "medium", - "effort_options": [ - "minimal", - "low", - "medium", - "high" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + "mode": "budget", + "budget": { + "default": -1, + "min": 128, + "max": 32768, + "auto": -1, + "unit": "tokens" + }, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thought_signatures" + ] } }, "attachment": true, "open_weights": false, - "knowledge": "2024-05-30", - "release_date": "2025-08-07", - "last_updated": "2025-08-07", + "knowledge": "2025-01", + "release_date": "2025-06-17", + "last_updated": "2025-06-17", "type": "chat" }, { - "id": "grok-4.7", - "name": "Grok 4.7", - "display_name": "Grok 4.7", + "id": "google/gemini-flash-lite-latest", + "name": "Gemini Flash-Lite Latest", + "display_name": "Gemini Flash-Lite Latest", "modalities": { "input": [ "text", - "image" + "image", + "video", + "audio", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 500000, - "output": 128000 + "context": 1048576, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -177246,28 +177961,30 @@ "extra_capabilities": { "reasoning": { "effort_options": [ + "minimal", "low", "medium", - "high", - "xhigh" + "high" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-05", - "release_date": "2026-09-21", - "last_updated": "2026-09-21", + "knowledge": "2026-03", + "release_date": "2026-07-21", + "last_updated": "2026-07-21", "type": "chat" }, { - "id": "claude-sonnet-4.6", - "name": "Claude Sonnet 4.6", - "display_name": "Claude Sonnet 4.6", + "id": "google/gemini-2.5-flash-lite", + "name": "Gemini 2.5 Flash-Lite", + "display_name": "Gemini 2.5 Flash-Lite", "modalities": { "input": [ "text", "image", + "audio", + "video", "pdf" ], "output": [ @@ -177275,8 +177992,8 @@ ] }, "limit": { - "context": 200000, - "output": 32000 + "context": 1048576, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -177288,55 +178005,49 @@ "reasoning": { "supported": true, "default_enabled": false, - "mode": "mixed", + "mode": "budget", "budget": { - "min": 1024, + "default": -1, + "min": 512, + "max": 24576, + "auto": -1, "unit": "tokens" }, - "effort": "high", - "effort_options": [ - "low", - "medium", - "high", - "max" - ], - "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Anthropic recommends adaptive thinking with effort for Claude 4.6; budget_tokens remains a deprecated compatibility path.", - "Anthropic API defaults effort to high; lower effort levels should be chosen per workload." + "thought_signatures" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-02-17", - "last_updated": "2026-03-13", + "knowledge": "2025-01", + "release_date": "2025-06-17", + "last_updated": "2025-06-17", "type": "chat" }, { - "id": "kimi-k2.7-code", - "name": "Kimi K2.7 Code", - "display_name": "Kimi K2.7 Code", + "id": "google/gemini-2.5-flash", + "name": "Gemini 2.5 Flash", + "display_name": "Gemini 2.5 Flash", "modalities": { "input": [ "text", - "image" + "image", + "audio", + "video", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 256000, - "output": 32000 + "context": 1048576, + "output": 65536 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -177344,24 +178055,41 @@ }, "extra_capabilities": { "reasoning": { - "supported": true + "supported": true, + "default_enabled": true, + "mode": "budget", + "budget": { + "default": -1, + "min": 0, + "max": 24576, + "auto": -1, + "off": 0, + "unit": "tokens" + }, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thought_signatures" + ] } }, "attachment": true, - "open_weights": true, + "open_weights": false, "knowledge": "2025-01", - "release_date": "2026-06-12", - "last_updated": "2026-06-12", + "release_date": "2025-06-17", + "last_updated": "2025-06-17", "type": "chat" }, { - "id": "gpt-5.6-luna", - "name": "GPT-5.6 Luna", - "display_name": "GPT-5.6 Luna", + "id": "google/gemini-3.1-pro-preview", + "name": "Gemini 3.1 Pro Preview", + "display_name": "Gemini 3.1 Pro Preview", "modalities": { "input": [ "text", "image", + "video", + "audio", "pdf" ], "output": [ @@ -177369,10 +178097,10 @@ ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 1048576, + "output": 65536 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -177382,36 +178110,30 @@ "reasoning": { "supported": true, "default_enabled": true, - "mode": "effort", - "effort": "medium", - "effort_options": [ - "none", - "low", - "medium", - "high", - "xhigh", - "max" - ], - "verbosity": "medium", - "verbosity_options": [ + "mode": "level", + "level": "high", + "level_options": [ "low", - "medium", "high" ], - "visibility": "hidden" + "summaries": true, + "visibility": "summary", + "continuation": [ + "thought_signatures" + ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-02-16", - "release_date": "2026-07-09", - "last_updated": "2026-07-09", + "knowledge": "2025-01", + "release_date": "2026-02-19", + "last_updated": "2026-02-19", "type": "chat" }, { - "id": "gpt-5.4-mini", - "name": "GPT-5.4 mini", - "display_name": "GPT-5.4 mini", + "id": "google/gemma-4-31b-it", + "name": "Gemma 4 31B IT", + "display_name": "Gemma 4 31B IT", "modalities": { "input": [ "text", @@ -177422,52 +178144,37 @@ ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 262144, + "output": 32768 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "none", - "effort_options": [ - "none", - "low", - "medium", - "high", - "xhigh" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + "supported": true } }, "attachment": true, - "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-03-17", - "last_updated": "2026-03-17", + "open_weights": true, + "knowledge": "2025-01", + "release_date": "2026-04-02", + "last_updated": "2026-04-02", "type": "chat" }, { - "id": "claude-haiku-4.5", - "name": "Claude Haiku 4.5 (latest)", - "display_name": "Claude Haiku 4.5 (latest)", + "id": "google/gemini-3.1-flash-lite", + "name": "Gemini 3.1 Flash Lite", + "display_name": "Gemini 3.1 Flash Lite", "modalities": { "input": [ "text", "image", + "video", + "audio", "pdf" ], "output": [ @@ -177475,51 +178182,43 @@ ] }, "limit": { - "context": 200000, - "output": 64000 + "context": 1048576, + "output": 65536 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "budget", - "budget": { - "min": 1024, - "unit": "tokens" - }, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", - "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." + "effort_options": [ + "minimal", + "low", + "medium", + "high" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-02-28", - "release_date": "2025-10-15", - "last_updated": "2025-10-15", + "knowledge": "2025-01", + "release_date": "2026-05-07", + "last_updated": "2026-05-07", "type": "chat" }, { - "id": "gpt-5.3-codex", - "name": "GPT-5.3 Codex", - "display_name": "GPT-5.3 Codex", + "id": "google/gemini-3.1-pro-preview-customtools", + "name": "Gemini 3.1 Pro Preview Custom Tools", + "display_name": "Gemini 3.1 Pro Preview Custom Tools", "modalities": { "input": [ "text", "image", + "video", + "audio", "pdf" ], "output": [ @@ -177527,8 +178226,8 @@ ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 1048576, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -177540,38 +178239,36 @@ "reasoning": { "supported": true, "default_enabled": true, - "mode": "effort", - "effort": "medium", - "effort_options": [ - "low", - "medium", - "high", - "xhigh" - ], - "verbosity": "medium", - "verbosity_options": [ + "mode": "level", + "level": "high", + "level_options": [ "low", - "medium", "high" ], - "visibility": "hidden" + "summaries": true, + "visibility": "summary", + "continuation": [ + "thought_signatures" + ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-02-05", - "last_updated": "2026-02-05", + "knowledge": "2025-01", + "release_date": "2026-02-19", + "last_updated": "2026-02-19", "type": "chat" }, { - "id": "gemini-3.8-flash", - "name": "Gemini 3.8 Flash", - "display_name": "Gemini 3.8 Flash", + "id": "google/gemini-3-flash-preview", + "name": "Gemini 3 Flash Preview", + "display_name": "Gemini 3 Flash Preview", "modalities": { "input": [ "text", "image", + "video", + "audio", "pdf" ], "output": [ @@ -177579,8 +178276,8 @@ ] }, "limit": { - "context": 1000000, - "output": 64000 + "context": 1048576, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -177609,66 +178306,120 @@ }, "attachment": true, "open_weights": false, - "release_date": "2026-09-02", - "last_updated": "2026-09-02", + "knowledge": "2025-01", + "release_date": "2025-12-17", + "last_updated": "2025-12-17", "type": "chat" }, { - "id": "gpt-5.4-nano", - "name": "GPT-5.4 nano", - "display_name": "GPT-5.4 nano", + "id": "google/gemini-3.5-flash-lite", + "name": "Gemini 3.5 Flash Lite", + "display_name": "Gemini 3.5 Flash Lite", "modalities": { "input": [ "text", - "image" + "image", + "video", + "audio", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 1048576, + "output": 65536 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "none", "effort_options": [ - "none", - "low", - "medium", - "high", - "xhigh" - ], - "verbosity": "medium", - "verbosity_options": [ + "minimal", "low", "medium", "high" - ], - "visibility": "hidden" + ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-03-17", - "last_updated": "2026-03-17", + "knowledge": "2026-03", + "release_date": "2026-07-21", + "last_updated": "2026-07-21", "type": "chat" }, { - "id": "claude-opus-4.7", - "name": "Claude Opus 4.7", - "display_name": "Claude Opus 4.7", + "id": "orcarouter/auto", + "name": "OrcaRouter Auto", + "display_name": "OrcaRouter Auto", + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 16384 + }, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true + } + }, + "attachment": true, + "open_weights": false, + "release_date": "2025-01-01", + "last_updated": "2026-05-14", + "type": "chat" + }, + { + "id": "orcarouter/free", + "name": "OrcaRouter Free", + "display_name": "OrcaRouter Free", + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 65536, + "output": 32768 + }, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": true, + "default": true + }, + "attachment": false, + "open_weights": false, + "release_date": "2026-08-27", + "last_updated": "2026-08-27", + "type": "chat" + }, + { + "id": "orcarouter/fusion-mini", + "name": "OrcaRouter Fusion Mini", + "display_name": "OrcaRouter Fusion Mini", "modalities": { "input": [ "text", @@ -177680,52 +178431,34 @@ ] }, "limit": { - "context": 200000, - "output": 32000 + "context": 1000000, + "output": 128000 }, "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "high", "effort_options": [ "low", "medium", - "high", - "xhigh", - "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "omitted", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Claude Opus 4.7 and newer Opus models require thinking.type = \"adaptive\" to enable thinking explicitly.", - "Manual budget_tokens requests return 400 on Claude Opus 4.7 and newer adaptive-only Opus models.", - "task_budget is separate from thinking control and should not be treated as a thinking budget." + "high" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-01-31", - "release_date": "2026-04-16", - "last_updated": "2026-04-16", + "release_date": "2026-06-15", + "last_updated": "2026-06-15", "type": "chat" }, { - "id": "gemini-4-argon", - "name": "Gemini 4 Argon", - "display_name": "Gemini 4 Argon", + "id": "orcarouter/fusion", + "name": "OrcaRouter Fusion", + "display_name": "OrcaRouter Fusion", "modalities": { "input": [ "text", @@ -177737,8 +178470,8 @@ ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 1000000, + "output": 128000 }, "temperature": false, "tool_call": true, @@ -177757,30 +178490,27 @@ }, "attachment": true, "open_weights": false, - "knowledge": "2026-03", - "release_date": "2026-10-13", - "last_updated": "2026-10-13", + "release_date": "2026-06-15", + "last_updated": "2026-06-15", "type": "chat" }, { - "id": "claude-sonnet-5.5", - "name": "Claude Sonnet 5.5", - "display_name": "Claude Sonnet 5.5", + "id": "orcarouter/fusion-flash", + "name": "OrcaRouter Fusion Flash", + "display_name": "OrcaRouter Fusion Flash", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, + "context": 200000, "output": 128000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -177791,23 +178521,20 @@ "effort_options": [ "low", "medium", - "high", - "xhigh", - "max" + "high" ] } }, - "attachment": true, + "attachment": false, "open_weights": false, - "knowledge": "2026-06", - "release_date": "2026-09-28", - "last_updated": "2026-09-28", + "release_date": "2026-06-15", + "last_updated": "2026-06-15", "type": "chat" }, { - "id": "gemini-3.6-flash", - "name": "Gemini 3.6 Flash", - "display_name": "Gemini 3.6 Flash", + "id": "anthropic/claude-haiku-4.5", + "name": "Claude Haiku 4.5 (latest)", + "display_name": "Claude Haiku 4.5 (latest)", "modalities": { "input": [ "text", @@ -177819,51 +178546,51 @@ ] }, "limit": { - "context": 1000000, + "context": 200000, "output": 64000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "level", - "level": "high", - "level_options": [ - "minimal", - "low", - "medium", - "high" - ], + "default_enabled": false, + "mode": "budget", + "budget": { + "min": 1024, + "unit": "tokens" + }, + "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ - "thought_signatures" + "thinking_blocks" + ], + "notes": [ + "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", + "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-03", - "release_date": "2026-07-21", - "last_updated": "2026-07-21", + "knowledge": "2025-02-28", + "release_date": "2025-10-15", + "last_updated": "2025-10-15", "type": "chat" }, { - "id": "gemini-3.5-flash", - "name": "Gemini 3.5 Flash", - "display_name": "Gemini 3.5 Flash", + "id": "anthropic/claude-sonnet-4.5", + "name": "Claude Sonnet 4.5 (latest)", + "display_name": "Claude Sonnet 4.5 (latest)", "modalities": { "input": [ "text", "image", - "video", - "audio", "pdf" ], "output": [ @@ -177871,45 +178598,47 @@ ] }, "limit": { - "context": 200000, + "context": 1000000, "output": 64000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "level", - "level": "high", - "level_options": [ - "minimal", - "low", - "medium", - "high" - ], + "default_enabled": false, + "mode": "budget", + "budget": { + "min": 1024, + "unit": "tokens" + }, + "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ - "thought_signatures" + "thinking_blocks" + ], + "notes": [ + "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", + "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-01", - "release_date": "2026-05-19", - "last_updated": "2026-05-19", + "knowledge": "2025-07-31", + "release_date": "2025-09-29", + "last_updated": "2025-09-29", "type": "chat" }, { - "id": "claude-fable-5", - "name": "Claude Fable 5", - "display_name": "Claude Fable 5", + "id": "anthropic/claude-sonnet-5", + "name": "Claude Sonnet 5", + "display_name": "Claude Sonnet 5", "modalities": { "input": [ "text", @@ -177933,40 +178662,24 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "high", "effort_options": [ "low", "medium", - "high", - "xhigh", - "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "omitted", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Adaptive thinking is always on for Claude Fable 5 and Claude Mythos 5; thinking.type = \"disabled\" is rejected.", - "Manual budget_tokens requests return 400 on Claude Fable 5 and Claude Mythos 5.", - "thinking.display defaults to omitted; set display to summarized to receive readable thinking summaries." + "high" ] } }, "attachment": true, "open_weights": false, "knowledge": "2026-01-31", - "release_date": "2026-06-09", - "last_updated": "2026-06-09", + "release_date": "2026-06-30", + "last_updated": "2026-06-30", "type": "chat" }, { - "id": "gpt-6.1-terra", - "name": "GPT-6.1 Terra", - "display_name": "GPT-6.1 Terra", + "id": "anthropic/claude-fable-5", + "name": "Claude Fable 5", + "display_name": "Claude Fable 5", "modalities": { "input": [ "text", @@ -177978,7 +178691,7 @@ ] }, "limit": { - "context": 1050000, + "context": 1000000, "output": 128000 }, "temperature": false, @@ -177989,27 +178702,41 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "high", "effort_options": [ - "none", "low", "medium", "high", "xhigh", "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "omitted", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Adaptive thinking is always on for Claude Fable 5 and Claude Mythos 5; thinking.type = \"disabled\" is rejected.", + "Manual budget_tokens requests return 400 on Claude Fable 5 and Claude Mythos 5.", + "thinking.display defaults to omitted; set display to summarized to receive readable thinking summaries." ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-06-01", - "release_date": "2026-10-26", - "last_updated": "2026-10-26", + "knowledge": "2026-01-31", + "release_date": "2026-06-09", + "last_updated": "2026-06-09", "type": "chat" }, { - "id": "mai-code-1.1-flash", - "name": "MAI-Code-1.1-Flash", - "display_name": "MAI-Code-1.1-Flash", + "id": "anthropic/claude-opus-4.5", + "name": "Claude Opus 4.5 (latest)", + "display_name": "Claude Opus 4.5 (latest)", "modalities": { "input": [ "text", @@ -178021,34 +178748,53 @@ ] }, "limit": { - "context": 256000, - "output": 128000 + "context": 200000, + "output": 64000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": false, + "mode": "mixed", + "budget": { + "min": 1024, + "unit": "tokens" + }, + "effort": "high", "effort_options": [ "low", "medium", "high" + ], + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Claude Opus 4.5 uses manual thinking.type = \"enabled\" with budget_tokens; effort can be used alongside the thinking budget.", + "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header." ] } }, "attachment": true, "open_weights": false, - "release_date": "2026-08-11", - "last_updated": "2026-08-11", + "knowledge": "2025-05", + "release_date": "2025-11-24", + "last_updated": "2025-11-24", "type": "chat" }, { - "id": "gpt-6.1-sol", - "name": "GPT-6.1 Sol", - "display_name": "GPT-6.1 Sol", + "id": "anthropic/claude-opus-4.6", + "name": "Claude Opus 4.6", + "display_name": "Claude Opus 4.6", "modalities": { "input": [ "text", @@ -178060,37 +178806,54 @@ ] }, "limit": { - "context": 1050000, + "context": 1000000, "output": 128000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": false, + "mode": "mixed", + "budget": { + "min": 1024, + "unit": "tokens" + }, + "effort": "high", "effort_options": [ "low", "medium", "high", - "xhigh", "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Anthropic recommends adaptive thinking with effort for Claude 4.6; budget_tokens remains a deprecated compatibility path.", + "Anthropic API defaults effort to high; lower effort levels should be chosen per workload." ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-04-30", - "release_date": "2026-09-29", - "last_updated": "2026-09-29", + "knowledge": "2025-05-31", + "release_date": "2026-02-05", + "last_updated": "2026-03-13", "type": "chat" }, { - "id": "claude-haiku-5.5", - "name": "Claude Haiku 5.5", - "display_name": "Claude Haiku 5.5", + "id": "anthropic/claude-opus-4.7", + "name": "Claude Opus 4.7", + "display_name": "Claude Opus 4.7", "modalities": { "input": [ "text", @@ -178109,30 +178872,45 @@ "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": false, + "mode": "effort", + "effort": "high", "effort_options": [ "low", "medium", "high", "xhigh", "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "omitted", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Claude Opus 4.7 and newer Opus models require thinking.type = \"adaptive\" to enable thinking explicitly.", + "Manual budget_tokens requests return 400 on Claude Opus 4.7 and newer adaptive-only Opus models.", + "task_budget is separate from thinking control and should not be treated as a thinking budget." ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-06", - "release_date": "2026-10-07", - "last_updated": "2026-10-07", + "knowledge": "2026-01-31", + "release_date": "2026-04-16", + "last_updated": "2026-04-16", "type": "chat" }, { - "id": "claude-opus-5.5", - "name": "Claude Opus 5.5", - "display_name": "Claude Opus 5.5", + "id": "anthropic/claude-opus-4.8", + "name": "Claude Opus 4.8", + "display_name": "Claude Opus 4.8", "modalities": { "input": [ "text", @@ -178151,83 +178929,109 @@ "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": false, + "mode": "effort", + "effort": "high", "effort_options": [ "low", "medium", "high", "xhigh", "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "omitted", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Claude Opus 4.7 and newer Opus models require thinking.type = \"adaptive\" to enable thinking explicitly.", + "Manual budget_tokens requests return 400 on Claude Opus 4.7 and newer adaptive-only Opus models.", + "task_budget is separate from thinking control and should not be treated as a thinking budget." ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-06", - "release_date": "2026-09-22", - "last_updated": "2026-09-22", + "knowledge": "2026-01", + "release_date": "2026-05-28", + "last_updated": "2026-05-28", "type": "chat" }, { - "id": "grok-4.6", - "name": "Grok 4.6", - "display_name": "Grok 4.6", + "id": "anthropic/claude-sonnet-4.6", + "name": "Claude Sonnet 4.6", + "display_name": "Claude Sonnet 4.6", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 500000, - "output": 128000 + "context": 1000000, + "output": 64000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": false, + "mode": "mixed", + "budget": { + "min": 1024, + "unit": "tokens" + }, + "effort": "high", "effort_options": [ "low", "medium", "high", - "xhigh" + "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Anthropic recommends adaptive thinking with effort for Claude 4.6; budget_tokens remains a deprecated compatibility path.", + "Anthropic API defaults effort to high; lower effort levels should be chosen per workload." ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-02-01", - "release_date": "2026-08-12", - "last_updated": "2026-08-12", + "knowledge": "2025-08-31", + "release_date": "2026-02-17", + "last_updated": "2026-03-13", "type": "chat" - } - ] - }, - "greenpt": { - "id": "greenpt", - "name": "GreenPT", - "display_name": "GreenPT", - "api": "https://api.greenpt.ai/v1", - "doc": "https://docs.greenpt.ai", - "models": [ + }, { - "id": "glm-5.2-caveman-ultra", - "name": "GLM-5.2 Caveman Ultra", - "display_name": "GLM-5.2 Caveman Ultra", + "id": "anthropic/claude-opus-5", + "name": "Claude Opus 5", + "display_name": "Claude Opus 5", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" @@ -178235,9 +179039,9 @@ }, "limit": { "context": 1000000, - "output": 131072 + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -178245,64 +179049,54 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, "effort_options": [ - "none", - "minimal", "low", "medium", "high" ] } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-06-13", - "last_updated": "2026-06-13", + "attachment": true, + "open_weights": false, + "knowledge": "2026-05", + "release_date": "2026-07-24", + "last_updated": "2026-07-24", "type": "chat" }, { - "id": "glm-5.2-ponytail-ultra", - "name": "GLM-5.2 Ponytail Ultra", - "display_name": "GLM-5.2 Ponytail Ultra", + "id": "qwen/qwen3-vl-235b-a22b-instruct", + "name": "Qwen3 VL 235B A22B Instruct", + "display_name": "Qwen3 VL 235B A22B Instruct", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 131072, + "output": 32768 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "none", - "minimal", - "low", - "medium", - "high" - ] - } + "supported": false }, - "attachment": false, + "attachment": true, "open_weights": true, - "release_date": "2026-06-13", - "last_updated": "2026-06-13", + "knowledge": "2025-03-31", + "release_date": "2025-09-23", + "last_updated": "2025-09-23", "type": "chat" }, { - "id": "glm-5.2-ponytail-lite", - "name": "GLM-5.2 Ponytail Lite", - "display_name": "GLM-5.2 Ponytail Lite", + "id": "qwen/qwen3-max", + "name": "Qwen3 Max", + "display_name": "Qwen3 Max", "modalities": { "input": [ "text" @@ -178312,40 +179106,41 @@ ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 262144, + "output": 65536 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true + "supported": true }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "none", - "minimal", - "low", - "medium", - "high" + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, "attachment": false, - "open_weights": true, - "release_date": "2026-06-13", - "last_updated": "2026-06-13", + "open_weights": false, + "knowledge": "2025-04", + "release_date": "2025-09-23", + "last_updated": "2025-09-23", "type": "chat" }, { - "id": "mistral-medium-3.5-128b", - "name": "Mistral Medium 3.5", - "display_name": "Mistral Medium 3.5", + "id": "qwen/qwen3.5-27b", + "name": "Qwen3.5 27B", + "display_name": "Qwen3.5 27B", "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ "text" @@ -178353,7 +179148,7 @@ }, "limit": { "context": 262144, - "output": 16384 + "output": 65536 }, "temperature": true, "tool_call": true, @@ -178363,51 +179158,25 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "none", - "high" + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, "attachment": true, "open_weights": true, - "release_date": "2026-04-29", - "last_updated": "2026-04-29", - "type": "chat" - }, - { - "id": "pixtral-12b-2409", - "name": "Pixtral 12B", - "display_name": "Pixtral 12B", - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 4096 - }, - "temperature": true, - "tool_call": true, - "reasoning": { - "supported": false - }, - "attachment": true, - "open_weights": true, - "knowledge": "2024-09", - "release_date": "2024-09-11", - "last_updated": "2024-09-11", + "release_date": "2026-02-23", + "last_updated": "2026-02-23", "type": "chat" }, { - "id": "qwen3-coder-30b-a3b-instruct", - "name": "Qwen3-Coder 30B-A3B Instruct", - "display_name": "Qwen3-Coder 30B-A3B Instruct", + "id": "qwen/qwen3.7-max", + "name": "Qwen3.7 Max", + "display_name": "Qwen3.7 Max", "modalities": { "input": [ "text" @@ -178417,28 +179186,35 @@ ] }, "limit": { - "context": 128000, - "output": 32768 + "context": 1000000, + "output": 131072 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true + } }, "attachment": false, - "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-07-31", - "last_updated": "2025-07-31", + "open_weights": false, + "release_date": "2026-05-21", + "last_updated": "2026-05-21", "type": "chat" }, { - "id": "glm-5.3-flash", - "name": "GLM-5.3-Flash", - "display_name": "GLM-5.3-Flash", + "id": "qwen/qwen3.6-flash", + "name": "Qwen3.6 Flash", + "display_name": "Qwen3.6 Flash", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" @@ -178446,7 +179222,7 @@ }, "limit": { "context": 1000000, - "output": 131072 + "output": 65536 }, "temperature": true, "tool_call": true, @@ -178457,37 +179233,37 @@ "extra_capabilities": { "reasoning": { "supported": true, - "effort_options": [ - "none", - "minimal", - "low", - "medium", - "high" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-08-26", - "last_updated": "2026-08-26", + "attachment": true, + "open_weights": false, + "release_date": "2026-04-27", + "last_updated": "2026-04-27", "type": "chat" }, { - "id": "green-r-raw", - "name": "Green R Raw", - "display_name": "Green R Raw", + "id": "qwen/qwen3.6-35b-a3b", + "name": "Qwen3.6 35B-A3B", + "display_name": "Qwen3.6 35B-A3B", "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 32768 + "context": 262144, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -178497,67 +179273,82 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "low", - "medium", - "high" + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, "attachment": true, "open_weights": true, - "knowledge": "2024-06", - "release_date": "2025-08-05", - "last_updated": "2025-08-05", + "release_date": "2026-04-17", + "last_updated": "2026-04-17", "type": "chat" }, { - "id": "voxtral-small-24b-2507", - "name": "Voxtral Small 24B", - "display_name": "Voxtral Small 24B", + "id": "qwen/qwen3.5-plus", + "name": "Qwen3.5 Plus", + "display_name": "Qwen3.5 Plus", "modalities": { "input": [ "text", - "audio" + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 32768, - "output": 16384 + "context": 1000000, + "output": 65536 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } }, "attachment": true, - "open_weights": true, - "knowledge": "2025-07", - "release_date": "2025-07-15", - "last_updated": "2025-07-15", + "open_weights": false, + "knowledge": "2025-04", + "release_date": "2026-02-16", + "last_updated": "2026-02-16", "type": "chat" }, { - "id": "kimi-k3", - "name": "Kimi K3", - "display_name": "Kimi K3", + "id": "qwen/qwen3.5-flash", + "name": "Qwen3.5 Flash", + "display_name": "Qwen3.5 Flash", "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 1000000, + "output": 65536 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -178566,37 +179357,37 @@ "extra_capabilities": { "reasoning": { "supported": true, - "effort_options": [ - "none", - "minimal", - "low", - "medium", - "high" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-07-16", - "last_updated": "2026-07-16", + "open_weights": false, + "release_date": "2026-02-23", + "last_updated": "2026-02-23", "type": "chat" }, { - "id": "kimi-k2.6-fast", - "name": "Kimi K2.6 Fast", - "display_name": "Kimi K2.6 Fast", + "id": "qwen/qwen3.7-flash", + "name": "Qwen3.7 Flash", + "display_name": "Qwen3.7 Flash", "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 1000000, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -178604,28 +179395,34 @@ "supported": true, "default": true }, + "extra_capabilities": { + "reasoning": { + "supported": true + } + }, "attachment": true, - "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-04-20", - "last_updated": "2026-04-20", + "open_weights": false, + "release_date": "2026-07-15", + "last_updated": "2026-07-15", "type": "chat" }, { - "id": "glm-5.2-ponytail", - "name": "GLM-5.2 Ponytail", - "display_name": "GLM-5.2 Ponytail", + "id": "qwen/qwen3.8-27b", + "name": "Qwen3.8 27B", + "display_name": "Qwen3.8 27B", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 262144, + "output": 32768 }, "temperature": true, "tool_call": true, @@ -178637,23 +179434,22 @@ "reasoning": { "effort_options": [ "none", - "minimal", "low", "medium", - "high" + "xhigh" ] } }, - "attachment": false, + "attachment": true, "open_weights": true, - "release_date": "2026-06-13", - "last_updated": "2026-06-13", + "release_date": "2026-08-14", + "last_updated": "2026-08-14", "type": "chat" }, { - "id": "holo2-30b-a3b", - "name": "Holo2 30B A3B", - "display_name": "Holo2 30B A3B", + "id": "qwen/qwen3-vl-235b-a22b-thinking", + "name": "Qwen3 VL 235B A22B Thinking", + "display_name": "Qwen3 VL 235B A22B Thinking", "modalities": { "input": [ "text", @@ -178664,8 +179460,8 @@ ] }, "limit": { - "context": 22016, - "output": 16384 + "context": 131072, + "output": 40960 }, "temperature": true, "tool_call": true, @@ -178675,35 +179471,39 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "low", - "medium", - "high" + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, "attachment": true, "open_weights": true, - "release_date": "2025-11", - "last_updated": "2025-11", + "knowledge": "2025-03-31", + "release_date": "2025-09-23", + "last_updated": "2025-09-23", "type": "chat" }, { - "id": "kimi-k2.6", - "name": "Kimi K2.6", - "display_name": "Kimi K2.6", + "id": "qwen/qwen3.6-plus", + "name": "Qwen3.6 Plus", + "display_name": "Qwen3.6 Plus", "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 1000000, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -178714,58 +179514,72 @@ "extra_capabilities": { "reasoning": { "supported": true, - "effort_options": [ - "none", - "minimal", - "low", - "medium", - "high" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, "attachment": true, - "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-04-20", - "last_updated": "2026-04-20", + "open_weights": false, + "knowledge": "2025-04", + "release_date": "2026-04-02", + "last_updated": "2026-04-02", "type": "chat" }, { - "id": "mistral-small-3.2-24b-instruct-2506", - "name": "Mistral Small 3.2", - "display_name": "Mistral Small 3.2", + "id": "qwen/qwen3.5-35b-a3b", + "name": "Qwen3.5 35B-A3B", + "display_name": "Qwen3.5 35B-A3B", "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 32768 + "context": 262144, + "output": 65536 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } }, "attachment": true, "open_weights": true, - "knowledge": "2025-03", - "release_date": "2025-06-20", - "last_updated": "2025-06-20", + "release_date": "2026-02-23", + "last_updated": "2026-02-23", "type": "chat" }, { - "id": "glm-5.2-honey-lite", - "name": "GLM-5.2 Honey Lite", - "display_name": "GLM-5.2 Honey Lite", + "id": "qwen/qwen3.8-max", + "name": "Qwen3.8 Max", + "display_name": "Qwen3.8 Max", "modalities": { "input": [ - "text" + "text", + "image", + "video", + "pdf" ], "output": [ "text" @@ -178783,36 +179597,43 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ - "none", - "minimal", "low", "medium", - "high" + "xhigh" ] } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-06-13", - "last_updated": "2026-06-13", + "attachment": true, + "open_weights": false, + "release_date": "2026-08-03", + "last_updated": "2026-08-03", "type": "chat" }, { - "id": "minimax-m2.5", - "name": "MiniMax-M2.5", - "display_name": "MiniMax-M2.5", + "id": "qwen/qwen3.5-397b-a17b", + "name": "Qwen3.5 397B-A17B", + "display_name": "Qwen3.5 397B-A17B", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 204800, - "output": 131072 + "context": 262144, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -178823,66 +179644,37 @@ "extra_capabilities": { "reasoning": { "supported": true, - "effort_options": [ - "none", - "minimal", - "low", - "medium", - "high" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-02-12", - "last_updated": "2026-02-12", - "type": "chat" - }, - { - "id": "green-l", - "name": "Green L", - "display_name": "Green L", - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 32768 - }, - "temperature": true, - "tool_call": true, - "reasoning": { - "supported": false - }, "attachment": true, "open_weights": true, - "knowledge": "2025-03", - "release_date": "2025-06-20", - "last_updated": "2025-06-20", + "release_date": "2026-02-15", + "last_updated": "2026-02-15", "type": "chat" }, { - "id": "gpt-oss-120b", - "name": "GPT OSS 120B", - "display_name": "GPT OSS 120B", + "id": "qwen/qwen3.7-plus", + "name": "Qwen3.7 Plus", + "display_name": "Qwen3.7 Plus", "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 32768 + "context": 1000000, + "output": 64000 }, "temperature": true, "tool_call": true, @@ -178892,37 +179684,33 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "effort_options": [ - "low", - "medium", - "high" - ] + "supported": true } }, "attachment": true, - "open_weights": true, - "knowledge": "2024-06", - "release_date": "2025-08-05", - "last_updated": "2025-08-05", + "open_weights": false, + "knowledge": "2025-04", + "release_date": "2026-06-02", + "last_updated": "2026-06-02", "type": "chat" }, { - "id": "deepseek-v4.1-flash", - "name": "DeepSeek V4.1 Flash", - "display_name": "DeepSeek V4.1 Flash", + "id": "qwen/qwen3.5-122b-a10b", + "name": "Qwen3.5 122B-A10B", + "display_name": "Qwen3.5 122B-A10B", "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 384000 + "context": 262144, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -178933,39 +179721,39 @@ "extra_capabilities": { "reasoning": { "supported": true, - "effort_options": [ - "none", - "minimal", - "low", - "medium", - "high" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, "attachment": true, "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-09-10", - "last_updated": "2026-09-10", + "release_date": "2026-02-23", + "last_updated": "2026-02-23", "type": "chat" }, { - "id": "deepseek-v4-flash-0731", - "name": "DeepSeek V4 Flash 0731", - "display_name": "DeepSeek V4 Flash 0731", + "id": "kimi/kimi-k3", + "name": "Kimi K3", + "display_name": "Kimi K3", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 384000 + "context": 1048576, + "output": 131072 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -178974,57 +179762,34 @@ "extra_capabilities": { "reasoning": { "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ - "none", - "minimal", "low", - "medium", - "high" + "high", + "max" ] } }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-07-31", - "last_updated": "2026-07-31", - "type": "chat" - }, - { - "id": "llama-3.3-70b-instruct", - "name": "Llama-3.3-70B-Instruct", - "display_name": "Llama-3.3-70B-Instruct", - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 100000, - "output": 16384 - }, - "temperature": true, - "tool_call": true, - "reasoning": { - "supported": false - }, - "attachment": false, + "attachment": true, "open_weights": true, - "knowledge": "2023-12", - "release_date": "2024-12-06", - "last_updated": "2024-12-06", + "release_date": "2026-07-16", + "last_updated": "2026-07-16", "type": "chat" }, { - "id": "kimi-k2.7-code", + "id": "kimi/kimi-k2.7-code", "name": "Kimi K2.7 Code", "display_name": "Kimi K2.7 Code", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" @@ -179043,16 +179808,15 @@ "extra_capabilities": { "reasoning": { "supported": true, - "effort_options": [ - "none", - "minimal", - "low", - "medium", - "high" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, - "attachment": false, + "attachment": true, "open_weights": true, "knowledge": "2025-01", "release_date": "2026-06-12", @@ -179060,23 +179824,24 @@ "type": "chat" }, { - "id": "green-r", - "name": "Green R", - "display_name": "Green R", + "id": "kimi/kimi-k2.5", + "name": "Kimi K2.5", + "display_name": "Kimi K2.5", "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 131072, + "context": 262144, "output": 32768 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -179084,82 +179849,31 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "low", - "medium", - "high" + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, "attachment": true, "open_weights": true, - "knowledge": "2024-06", - "release_date": "2025-08-05", - "last_updated": "2025-08-05", - "type": "chat" - }, - { - "id": "green-s-pro", - "name": "Green S Pro", - "display_name": "Green S Pro", - "modalities": { - "input": [ - "audio" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 8192, - "output": 8192 - }, - "temperature": false, - "tool_call": false, - "reasoning": { - "supported": false - }, - "attachment": true, - "open_weights": false, - "release_date": "2025-02", - "last_updated": "2025-02" - }, - { - "id": "qwen3-235b-a22b-instruct-2507", - "name": "Qwen3 235B A22B Instruct 2507", - "display_name": "Qwen3 235B A22B Instruct 2507", - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 262144, - "output": 16384 - }, - "temperature": true, - "tool_call": true, - "reasoning": { - "supported": false - }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-07-21", - "last_updated": "2025-07-21", + "knowledge": "2025-01", + "release_date": "2026-01-27", + "last_updated": "2026-01-27", "type": "chat" }, { - "id": "qwen3.6-35b-a3b", - "name": "Qwen3.6 35B-A3B", - "display_name": "Qwen3.6 35B-A3B", + "id": "kimi/kimi-k2.6", + "name": "Kimi K2.6", + "display_name": "Kimi K2.6", "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ "text" @@ -179183,26 +179897,20 @@ "visibility": "summary", "continuation": [ "thinking_blocks" - ], - "effort_options": [ - "none", - "minimal", - "low", - "medium", - "high" ] } }, "attachment": true, "open_weights": true, - "release_date": "2026-04-17", - "last_updated": "2026-04-17", + "knowledge": "2025-01", + "release_date": "2026-04-20", + "last_updated": "2026-04-20", "type": "chat" }, { - "id": "glm-5.2-caveman", - "name": "GLM-5.2 Caveman", - "display_name": "GLM-5.2 Caveman", + "id": "deepseek/deepseek-chat", + "name": "DeepSeek Chat", + "display_name": "DeepSeek Chat", "modalities": { "input": [ "text" @@ -179213,35 +179921,24 @@ }, "limit": { "context": 1000000, - "output": 131072 + "output": 384000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "none", - "minimal", - "low", - "medium", - "high" - ] - } + "supported": false }, "attachment": false, "open_weights": true, - "release_date": "2026-06-13", - "last_updated": "2026-06-13", + "knowledge": "2025-09", + "release_date": "2025-12-01", + "last_updated": "2026-02-28", "type": "chat" }, { - "id": "glm-5.1", - "name": "GLM-5.1", - "display_name": "GLM-5.1", + "id": "deepseek/deepseek-reasoner", + "name": "DeepSeek Reasoner", + "display_name": "DeepSeek Reasoner", "modalities": { "input": [ "text" @@ -179251,8 +179948,8 @@ ] }, "limit": { - "context": 200000, - "output": 131072 + "context": 1000000, + "output": 384000 }, "temperature": true, "tool_call": true, @@ -179262,47 +179959,77 @@ }, "extra_capabilities": { "reasoning": { - "supported": true + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] } }, "attachment": false, "open_weights": true, - "release_date": "2026-04-07", - "last_updated": "2026-04-07", + "knowledge": "2025-09", + "release_date": "2025-12-01", + "last_updated": "2026-02-28", "type": "chat" }, { - "id": "devstral-2-123b-instruct-2512", - "name": "Devstral 2", - "display_name": "Devstral 2", + "id": "deepseek/deepseek-v4-flash-vision-exp", + "name": "DeepSeek V4 Flash Vision Exp", + "display_name": "DeepSeek V4 Flash Vision Exp", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 16384 + "context": 1000000, + "output": 384000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "high", + "effort_options": [ + "low", + "high", + "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." + ] + } + }, + "attachment": true, "open_weights": true, - "knowledge": "2025-12", - "release_date": "2025-12-09", - "last_updated": "2025-12-09", + "release_date": "2026-08-21", + "last_updated": "2026-09-01", "type": "chat" - }, - { - "id": "glm-5.3", - "name": "GLM-5.3", - "display_name": "GLM-5.3", + }, + { + "id": "deepseek/deepseek-v4-pro", + "name": "DeepSeek V4 Pro", + "display_name": "DeepSeek V4 Pro", "modalities": { "input": [ "text" @@ -179313,7 +180040,7 @@ }, "limit": { "context": 1000000, - "output": 131072 + "output": 384000 }, "temperature": true, "tool_call": true, @@ -179324,25 +180051,36 @@ "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "high", "effort_options": [ - "none", - "minimal", "low", - "medium", - "high" + "high", + "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." ] } }, "attachment": false, "open_weights": true, - "release_date": "2026-08-14", - "last_updated": "2026-08-14", + "knowledge": "2025-05", + "release_date": "2026-04-24", + "last_updated": "2026-04-24", "type": "chat" }, { - "id": "glm-5.2-honey", - "name": "GLM-5.2 Honey", - "display_name": "GLM-5.2 Honey", + "id": "deepseek/deepseek-v4-flash", + "name": "DeepSeek V4 Flash", + "display_name": "DeepSeek V4 Flash", "modalities": { "input": [ "text" @@ -179353,7 +180091,7 @@ }, "limit": { "context": 1000000, - "output": 131072 + "output": 384000 }, "temperature": true, "tool_call": true, @@ -179363,25 +180101,37 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "high", "effort_options": [ - "none", - "minimal", "low", - "medium", - "high" + "high", + "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." ] } }, "attachment": false, "open_weights": true, - "release_date": "2026-06-13", - "last_updated": "2026-06-13", + "knowledge": "2025-05", + "release_date": "2026-04-24", + "last_updated": "2026-04-24", "type": "chat" }, { - "id": "qwen3.5-397b-a17b", - "name": "Qwen3.5 397B-A17B", - "display_name": "Qwen3.5 397B-A17B", + "id": "deepseek/deepseek-v4-pro-0813", + "name": "DeepSeek V4 Pro 0813", + "display_name": "DeepSeek V4 Pro 0813", "modalities": { "input": [ "text" @@ -179391,8 +180141,8 @@ ] }, "limit": { - "context": 262144, - "output": 16384 + "context": 1000000, + "output": 384000 }, "temperature": true, "tool_call": true, @@ -179410,24 +180160,21 @@ "thinking_blocks" ], "effort_options": [ - "none", - "minimal", - "low", - "medium", - "high" + "high", + "max" ] } }, "attachment": false, "open_weights": true, - "release_date": "2026-02-15", - "last_updated": "2026-02-15", + "release_date": "2026-08-13", + "last_updated": "2026-08-22", "type": "chat" }, { - "id": "glm-5.2-caveman-lite", - "name": "GLM-5.2 Caveman Lite", - "display_name": "GLM-5.2 Caveman Lite", + "id": "deepseek/deepseek-v4-flash-free", + "name": "DeepSeek V4 Flash (free)", + "display_name": "DeepSeek V4 Flash (free)", "modalities": { "input": [ "text" @@ -179438,7 +180185,7 @@ }, "limit": { "context": 1000000, - "output": 131072 + "output": 384000 }, "temperature": true, "tool_call": true, @@ -179448,61 +180195,89 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ - "none", - "minimal", "low", - "medium", - "high" + "high", + "max" ] } }, "attachment": false, "open_weights": true, - "release_date": "2026-06-13", - "last_updated": "2026-06-13", + "knowledge": "2025-05", + "release_date": "2026-04-24", + "last_updated": "2026-04-24", "type": "chat" }, { - "id": "green-s", - "name": "Green S", - "display_name": "Green S", + "id": "deepseek/deepseek-v4-flash-0731", + "name": "DeepSeek V4 Flash 0731", + "display_name": "DeepSeek V4 Flash 0731", "modalities": { "input": [ - "audio" + "text" ], "output": [ "text" ] }, "limit": { - "context": 8192, - "output": 8192 + "context": 1000000, + "output": 384000 }, - "temperature": false, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": true, - "open_weights": false, - "release_date": "2025-01", - "last_updated": "2025-01" + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "effort_options": [ + "low", + "high", + "max" + ] + } + }, + "attachment": false, + "open_weights": true, + "knowledge": "2025-05", + "release_date": "2026-07-31", + "last_updated": "2026-07-31", + "type": "chat" }, { - "id": "glm-5.2-honey-ultra", - "name": "GLM-5.2 Honey Ultra", - "display_name": "GLM-5.2 Honey Ultra", + "id": "meta/muse-spark-1.1", + "name": "Muse Spark 1.1", + "display_name": "Muse Spark 1.1", "modalities": { "input": [ - "text" + "text", + "image", + "pdf", + "video" ], "output": [ "text" ] }, "limit": { - "context": 1000000, + "context": 1048576, "output": 131072 }, "temperature": true, @@ -179513,54 +180288,79 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, "effort_options": [ - "none", "minimal", "low", "medium", - "high" + "high", + "xhigh" ] } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-06-13", - "last_updated": "2026-06-13", + "attachment": true, + "open_weights": false, + "release_date": "2026-04-08", + "last_updated": "2026-07-09", "type": "chat" }, { - "id": "gemma-3-27b-it", - "name": "Gemma 3 27B", - "display_name": "Gemma 3 27B", + "id": "meta/muse-spark-1.2", + "name": "Muse Spark 1.2", + "display_name": "Muse Spark 1.2", "modalities": { "input": [ "text", - "image" + "image", + "video", + "pdf", + "audio" ], "output": [ "text" ] }, "limit": { - "context": 40000, - "output": 8192 + "context": 1048576, + "output": 131072 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "effort_options": [ + "minimal", + "low", + "medium", + "high", + "xhigh" + ] + } }, "attachment": true, - "open_weights": true, - "knowledge": "2024-08", - "release_date": "2025-03-12", - "last_updated": "2025-03-12", + "open_weights": false, + "release_date": "2026-08-05", + "last_updated": "2026-08-05", "type": "chat" - }, + } + ] + }, + "zeldoc": { + "id": "zeldoc", + "name": "Zeldoc", + "display_name": "Zeldoc", + "api": "https://api.zeldoc.ai/v1", + "doc": "https://docs.zeldoc.ai", + "models": [ { - "id": "green-l-raw", - "name": "Green L Raw", - "display_name": "Green L Raw", + "id": "zdev", + "name": "ZDev", + "display_name": "ZDev", "modalities": { "input": [ "text", @@ -179571,25 +180371,50 @@ ] }, "limit": { - "context": 128000, - "output": 32768 + "context": 1000000, + "output": 131072 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "effort_options": [ + "low", + "high", + "max" + ] + } }, "attachment": true, - "open_weights": true, - "knowledge": "2025-03", - "release_date": "2025-06-20", - "last_updated": "2025-06-20", + "open_weights": false, + "release_date": "2026-08-26", + "last_updated": "2026-08-26", "type": "chat" - }, + } + ] + }, + "hyper": { + "id": "hyper", + "name": "Charm Hyper", + "display_name": "Charm Hyper", + "api": "https://hyper.charm.land/v1", + "doc": "https://hyper.charm.land", + "models": [ { - "id": "gemma4", - "name": "gemma4", - "display_name": "gemma4", + "id": "kimi-k3", + "name": "Kimi K3", + "display_name": "Kimi K3", "modalities": { "input": [ "text", @@ -179600,10 +180425,10 @@ ] }, "limit": { - "context": 262144, - "output": 32768 + "context": 1048576, + "output": 16000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -179611,25 +180436,24 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, "effort_options": [ - "none", - "minimal", "low", - "medium", - "high" + "high", + "max" ] } }, "attachment": true, "open_weights": true, - "release_date": "2026-04-02", - "last_updated": "2026-04-02", + "release_date": "2026-07-16", + "last_updated": "2026-07-16", "type": "chat" }, { - "id": "glm-5.2", - "name": "GLM-5.2", - "display_name": "GLM-5.2", + "id": "qwen3.8-2.4t-a95b", + "name": "Qwen3.8 2.4T A95B", + "display_name": "Qwen3.8 2.4T A95B", "modalities": { "input": [ "text" @@ -179640,7 +180464,7 @@ }, "limit": { "context": 1000000, - "output": 131072 + "output": 128000 }, "temperature": true, "tool_call": true, @@ -179652,7 +180476,6 @@ "reasoning": { "supported": true, "effort_options": [ - "none", "minimal", "low", "medium", @@ -179662,48 +180485,14 @@ }, "attachment": false, "open_weights": true, - "release_date": "2026-06-13", - "last_updated": "2026-06-13", + "release_date": "2026-08-12", + "last_updated": "2026-08-12", "type": "chat" - } - ] - }, - "groq": { - "id": "groq", - "name": "Groq", - "display_name": "Groq", - "doc": "https://console.groq.com/docs/models", - "models": [ - { - "id": "whisper-large-v3-turbo", - "name": "Whisper Large V3 Turbo", - "display_name": "Whisper Large V3 Turbo", - "modalities": { - "input": [ - "audio" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 448, - "output": 448 - }, - "temperature": true, - "tool_call": false, - "reasoning": { - "supported": false - }, - "attachment": true, - "open_weights": true, - "release_date": "2024-10-01", - "last_updated": "2024-10-01" }, { - "id": "llama-3.1-8b-instant", - "name": "Llama 3.1 8B", - "display_name": "Llama 3.1 8B", + "id": "gemma-4-26b-a4b-it", + "name": "Gemma 4 26B A4B IT", + "display_name": "Gemma 4 26B A4B IT", "modalities": { "input": [ "text" @@ -179713,78 +180502,65 @@ ] }, "limit": { - "context": 131072, - "output": 131072 + "context": 256000, + "output": 25600 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true + } }, "attachment": false, "open_weights": true, - "knowledge": "2023-12", - "release_date": "2024-07-23", - "last_updated": "2024-07-23", + "release_date": "2026-04-02", + "last_updated": "2026-04-02", "type": "chat" }, { - "id": "whisper-large-v3", - "name": "Whisper", - "display_name": "Whisper", + "id": "kimi-k2.7-code", + "name": "Kimi K2.7 Code", + "display_name": "Kimi K2.7 Code", "modalities": { "input": [ - "audio" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 448, - "output": 448 + "context": 256000, + "output": 16000 }, - "temperature": true, - "tool_call": false, + "temperature": false, + "tool_call": true, "reasoning": { - "supported": false - }, - "attachment": true, - "open_weights": true, - "release_date": "2023-11-06", - "last_updated": "2023-11-06" - }, - { - "id": "allam-2-7b", - "name": "ALLaM-2-7b", - "display_name": "ALLaM-2-7b", - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 4096, - "output": 4096 + "supported": true, + "default": true }, - "temperature": true, - "tool_call": false, - "reasoning": { - "supported": false + "extra_capabilities": { + "reasoning": { + "supported": true + } }, - "attachment": false, + "attachment": true, "open_weights": true, - "release_date": "2025-01-23", - "last_updated": "2025-01-23", + "knowledge": "2025-01", + "release_date": "2026-06-12", + "last_updated": "2026-06-12", "type": "chat" }, { - "id": "llama-3.3-70b-versatile", - "name": "Llama 3.3 70B", - "display_name": "Llama 3.3 70B", + "id": "kimi-k2-thinking", + "name": "Kimi K2 Thinking", + "display_name": "Kimi K2 Thinking", "modalities": { "input": [ "text" @@ -179794,77 +180570,37 @@ ] }, "limit": { - "context": 131072, - "output": 32768 + "context": 262144, + "output": 26214 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } }, "attachment": false, "open_weights": true, - "knowledge": "2023-12", - "release_date": "2024-12-06", - "last_updated": "2024-12-06", + "knowledge": "2024-08", + "release_date": "2025-11-06", + "last_updated": "2025-11-06", "type": "chat" }, { - "id": "canopylabs/orpheus-v1-english", - "name": "Canopy Labs Orpheus V1 English", - "display_name": "Canopy Labs Orpheus V1 English", - "modalities": { - "input": [ - "text" - ], - "output": [ - "audio" - ] - }, - "limit": { - "context": 4000, - "output": 50000 - }, - "temperature": false, - "tool_call": false, - "reasoning": { - "supported": false - }, - "attachment": false, - "open_weights": false, - "release_date": "2025-12-19", - "last_updated": "2025-12-19" - }, - { - "id": "canopylabs/orpheus-arabic-saudi", - "name": "Canopy Labs Orpheus Arabic Saudi", - "display_name": "Canopy Labs Orpheus Arabic Saudi", - "modalities": { - "input": [ - "text" - ], - "output": [ - "audio" - ] - }, - "limit": { - "context": 4000, - "output": 50000 - }, - "temperature": false, - "tool_call": false, - "reasoning": { - "supported": false - }, - "attachment": false, - "open_weights": false, - "release_date": "2025-12-16", - "last_updated": "2025-12-16" - }, - { - "id": "openai/gpt-oss-20b", - "name": "GPT OSS 20B", - "display_name": "GPT OSS 20B", + "id": "qwen3.7-max", + "name": "Qwen3.7 Max", + "display_name": "Qwen3.7 Max", "modalities": { "input": [ "text" @@ -179874,8 +180610,8 @@ ] }, "limit": { - "context": 131072, - "output": 65536 + "context": 1000000, + "output": 64000 }, "temperature": true, "tool_call": true, @@ -179887,6 +180623,8 @@ "reasoning": { "supported": true, "effort_options": [ + "none", + "minimal", "low", "medium", "high" @@ -179894,27 +180632,27 @@ } }, "attachment": false, - "open_weights": true, - "knowledge": "2024-06", - "release_date": "2025-08-05", - "last_updated": "2025-08-05", + "open_weights": false, + "release_date": "2026-05-21", + "last_updated": "2026-05-21", "type": "chat" }, { - "id": "openai/gpt-oss-120b", - "name": "GPT OSS 120B", - "display_name": "GPT OSS 120B", + "id": "qwen3.8-flash", + "name": "Qwen3.8 Flash", + "display_name": "Qwen3.8 Flash", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 65536 + "context": 1000000, + "output": 128000 }, "temperature": true, "tool_call": true, @@ -179926,34 +180664,36 @@ "reasoning": { "supported": true, "effort_options": [ + "none", + "minimal", "low", "medium", "high" ] } }, - "attachment": false, - "open_weights": true, - "knowledge": "2024-06", - "release_date": "2025-08-05", - "last_updated": "2025-08-05", + "attachment": true, + "open_weights": false, + "release_date": "2026-08-26", + "last_updated": "2026-08-26", "type": "chat" }, { - "id": "openai/gpt-oss-safeguard-20b", - "name": "Safety GPT OSS 20B", - "display_name": "Safety GPT OSS 20B", + "id": "deepseek-v4.1-flash-1017", + "name": "DeepSeek V4.1 Flash 1017", + "display_name": "DeepSeek V4.1 Flash 1017", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 65536 + "context": 1000000, + "output": 262144 }, "temperature": true, "tool_call": true, @@ -179965,21 +180705,22 @@ "reasoning": { "effort_options": [ "low", - "medium", - "high" + "high", + "max" ] } }, - "attachment": false, + "attachment": true, "open_weights": true, - "release_date": "2025-10-29", - "last_updated": "2025-10-29", + "knowledge": "2025-05", + "release_date": "2026-10-17", + "last_updated": "2026-10-17", "type": "chat" }, { - "id": "qwen/qwen3.8-27b", - "name": "Qwen3.8 27B", - "display_name": "Qwen3.8 27B", + "id": "qwen3.7-flash", + "name": "Qwen3.7 Flash", + "display_name": "Qwen3.7 Flash", "modalities": { "input": [ "text", @@ -179990,8 +180731,8 @@ ] }, "limit": { - "context": 131072, - "output": 16384 + "context": 1000000, + "output": 64000 }, "temperature": true, "tool_call": true, @@ -180001,8 +180742,10 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, "effort_options": [ "none", + "minimal", "low", "medium", "high" @@ -180010,15 +180753,15 @@ } }, "attachment": true, - "open_weights": true, - "release_date": "2026-08-14", - "last_updated": "2026-08-14", + "open_weights": false, + "release_date": "2026-07-15", + "last_updated": "2026-07-15", "type": "chat" }, { - "id": "qwen/qwen3.6-27b", - "name": "Qwen3.6 27B", - "display_name": "Qwen3.6 27B", + "id": "glm-5.3-flash", + "name": "GLM-5.3-Flash", + "display_name": "GLM-5.3-Flash", "modalities": { "input": [ "text", @@ -180029,8 +180772,8 @@ ] }, "limit": { - "context": 131072, - "output": 16384 + "context": 1048576, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -180041,54 +180784,63 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], "effort_options": [ - "none" + "low", + "high", + "max" ] } }, "attachment": true, "open_weights": true, - "release_date": "2026-04-22", - "last_updated": "2026-04-22", + "release_date": "2026-08-26", + "last_updated": "2026-08-26", "type": "chat" }, { - "id": "meta-llama/llama-prompt-guard-2-22m", - "name": "Llama Prompt Guard 2 22M", - "display_name": "Llama Prompt Guard 2 22M", + "id": "deepseek-v4.1-flash", + "name": "DeepSeek V4.1 Flash", + "display_name": "DeepSeek V4.1 Flash", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 512, - "output": 512 + "context": 1048576, + "output": 262144 }, - "temperature": false, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, + "extra_capabilities": { + "reasoning": { + "supported": true, + "effort_options": [ + "low", + "high", + "max" + ] + } + }, + "attachment": true, "open_weights": true, - "release_date": "2025-04-29", - "last_updated": "2025-04-29", + "knowledge": "2025-05", + "release_date": "2026-09-10", + "last_updated": "2026-09-10", "type": "chat" }, { - "id": "meta-llama/llama-prompt-guard-2-86m", - "name": "Prompt Guard 2 86M", - "display_name": "Prompt Guard 2 86M", + "id": "gpt-oss-120b", + "name": "GPT OSS 120B", + "display_name": "GPT OSS 120B", "modalities": { "input": [ "text" @@ -180098,33 +180850,36 @@ ] }, "limit": { - "context": 512, - "output": 512 + "context": 131072, + "output": 13107 }, - "temperature": false, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "effort_options": [ + "low", + "medium", + "high" + ] + } }, "attachment": false, "open_weights": true, - "release_date": "2025-04-29", - "last_updated": "2025-04-29", + "knowledge": "2024-06", + "release_date": "2025-08-05", + "last_updated": "2025-08-05", "type": "chat" - } - ] - }, - "amd": { - "id": "amd", - "name": "AMD", - "display_name": "AMD", - "api": "https://developer.amd.com.cn/radeon/api/v1", - "doc": "https://developer.amd.com.cn/radeon/tokenfactory", - "models": [ + }, { - "id": "DeepSeek-V4.1-Flash", - "name": "DeepSeek V4.1 Flash", - "display_name": "DeepSeek V4.1 Flash", + "id": "qwen3.8-27b", + "name": "Qwen3.8 27B", + "display_name": "Qwen3.8 27B", "modalities": { "input": [ "text", @@ -180135,8 +180890,8 @@ ] }, "limit": { - "context": 1048576, - "output": 384000 + "context": 1000000, + "output": 128000 }, "temperature": true, "tool_call": true, @@ -180146,41 +180901,36 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, "effort_options": [ "none", "minimal", "low", "medium", - "high", - "xhigh", - "max" + "high" ] } }, "attachment": true, "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-10-17", - "last_updated": "2026-10-17", + "release_date": "2026-08-14", + "last_updated": "2026-08-14", "type": "chat" }, { - "id": "MiniCPM-V-4.7-35B-A3B", - "name": "MiniCPM-V 4.7 35B-A3B", - "display_name": "MiniCPM-V 4.7 35B-A3B", + "id": "glm-5.3", + "name": "GLM-5.3", + "display_name": "GLM-5.3", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 1000000, + "output": 128000 }, "temperature": true, "tool_call": true, @@ -180188,16 +180938,26 @@ "supported": true, "default": true }, - "attachment": true, + "extra_capabilities": { + "reasoning": { + "supported": true, + "effort_options": [ + "low", + "high", + "max" + ] + } + }, + "attachment": false, "open_weights": true, - "release_date": "2026-10-07", - "last_updated": "2026-10-07", + "release_date": "2026-08-14", + "last_updated": "2026-08-14", "type": "chat" }, { - "id": "Gemma-4-31B-IT", - "name": "Gemma 4 31B IT", - "display_name": "Gemma 4 31B IT", + "id": "inkling", + "name": "Inkling", + "display_name": "Inkling", "modalities": { "input": [ "text", @@ -180208,8 +180968,8 @@ ] }, "limit": { - "context": 262144, - "output": 32768 + "context": 1048576, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -180219,70 +180979,61 @@ }, "extra_capabilities": { "reasoning": { - "supported": true + "effort_options": [ + "low", + "medium", + "high" + ] } }, "attachment": true, "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-04-02", - "last_updated": "2026-04-02", + "release_date": "2026-07-15", + "last_updated": "2026-07-15", "type": "chat" }, { - "id": "GLM-5.3-Flash", - "name": "GLM-5.3-Flash", - "display_name": "GLM-5.3-Flash", + "id": "prism", + "name": "Prism", + "display_name": "Prism", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 131072 + "context": 1000000, + "output": 16000 }, - "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "effort_options": [ - "low", - "high", - "max" - ] - } + "supported": false }, - "attachment": false, - "open_weights": true, - "release_date": "2026-08-26", - "last_updated": "2026-08-26", + "attachment": true, + "open_weights": false, + "release_date": "1970-01-01", + "last_updated": "2026-10-15", "type": "chat" }, { - "id": "Qwen3.8-Flash-Next", - "name": "Qwen3.8 Flash Next", - "display_name": "Qwen3.8 Flash Next", + "id": "glm-5.2", + "name": "GLM-5.2", + "display_name": "GLM-5.2", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 131072 + "context": 1000000, + "output": 32768 }, "temperature": true, "tool_call": true, @@ -180292,23 +181043,23 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, "effort_options": [ - "low", - "medium", + "high", "xhigh" ] } }, - "attachment": true, + "attachment": false, "open_weights": true, - "release_date": "2026-08-27", - "last_updated": "2026-08-27", + "release_date": "2026-06-13", + "last_updated": "2026-06-13", "type": "chat" }, { - "id": "Qwen3.8-27B", - "name": "Qwen3.8 27B", - "display_name": "Qwen3.8 27B", + "id": "qwen3.8-max", + "name": "Qwen3.8 Max Preview", + "display_name": "Qwen3.8 Max Preview", "modalities": { "input": [ "text", @@ -180319,8 +181070,8 @@ ] }, "limit": { - "context": 262144, - "output": 32768 + "context": 1000000, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -180330,24 +181081,19 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "effort_options": [ - "low", - "medium" - ] + "supported": true } }, "attachment": true, - "open_weights": true, - "release_date": "2026-08-14", - "last_updated": "2026-08-14", + "open_weights": false, + "release_date": "2026-07-19", + "last_updated": "2026-07-19", "type": "chat" }, { - "id": "DeepSeek-V4-Flash-Vision-Exp", - "name": "DeepSeek V4 Flash Vision Exp", - "display_name": "DeepSeek V4 Flash Vision Exp", + "id": "minimax-m3", + "name": "MiniMax-M3", + "display_name": "MiniMax-M3", "modalities": { "input": [ "text", @@ -180358,8 +181104,8 @@ ] }, "limit": { - "context": 1000000, - "output": 384000 + "context": 512000, + "output": 512000 }, "temperature": true, "tool_call": true, @@ -180370,35 +181116,23 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "high", "effort_options": [ "low", - "high", - "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." + "medium", + "high" ] } }, "attachment": true, "open_weights": true, - "release_date": "2026-08-21", - "last_updated": "2026-09-01", + "release_date": "2026-06-01", + "last_updated": "2026-06-01", "type": "chat" }, { - "id": "DeepSeek-V4-Flash", - "name": "DeepSeek V4 Flash 0731", - "display_name": "DeepSeek V4 Flash 0731", + "id": "deepseek-v4-pro-0813", + "name": "DeepSeek V4 Pro 0813", + "display_name": "DeepSeek V4 Pro 0813", "modalities": { "input": [ "text" @@ -180408,8 +181142,8 @@ ] }, "limit": { - "context": 1048576, - "output": 384000 + "context": 1000000, + "output": 262144 }, "temperature": true, "tool_call": true, @@ -180420,36 +181154,23 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "high", "effort_options": [ "low", "high", "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." ] } }, "attachment": false, "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-07-31", - "last_updated": "2026-07-31", + "release_date": "2026-08-13", + "last_updated": "2026-08-22", "type": "chat" }, { - "id": "MiMo-V2.6-Flash", - "name": "MiMo-V2.6-Flash", - "display_name": "MiMo-V2.6-Flash", + "id": "qwen3.7-plus", + "name": "Qwen3.7 Plus", + "display_name": "Qwen3.7 Plus", "modalities": { "input": [ "text", @@ -180460,8 +181181,8 @@ ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 1000000, + "output": 64000 }, "temperature": true, "tool_call": true, @@ -180469,16 +181190,29 @@ "supported": true, "default": true }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "effort_options": [ + "none", + "minimal", + "low", + "medium", + "high" + ] + } + }, "attachment": true, - "open_weights": true, - "release_date": "2026-09-22", - "last_updated": "2026-09-22", + "open_weights": false, + "knowledge": "2025-04", + "release_date": "2026-06-02", + "last_updated": "2026-06-02", "type": "chat" }, { - "id": "MiniCPM5-2B", - "name": "MiniCPM5-2B", - "display_name": "MiniCPM5-2B", + "id": "deepseek-v4-flash-0731", + "name": "DeepSeek V4 Flash 0731", + "display_name": "DeepSeek V4 Flash 0731", "modalities": { "input": [ "text" @@ -180488,8 +181222,8 @@ ] }, "limit": { - "context": 131072, - "output": 131072 + "context": 1000000, + "output": 384000 }, "temperature": true, "tool_call": true, @@ -180497,28 +181231,38 @@ "supported": true, "default": true }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "effort_options": [ + "low", + "high", + "max" + ] + } + }, "attachment": false, "open_weights": true, - "release_date": "2026-09-06", - "last_updated": "2026-09-12", + "knowledge": "2025-05", + "release_date": "2026-07-31", + "last_updated": "2026-07-31", "type": "chat" }, { - "id": "MiniCPM-V-4.7-1B", - "name": "MiniCPM-V 4.7 1B", - "display_name": "MiniCPM-V 4.7 1B", + "id": "minimax-m2.7", + "name": "MiniMax-M2.7", + "display_name": "MiniMax-M2.7", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 262100, + "output": 6553 }, "temperature": true, "tool_call": true, @@ -180526,36 +181270,48 @@ "supported": true, "default": true }, - "attachment": true, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } + }, + "attachment": false, "open_weights": true, - "release_date": "2026-10-06", - "last_updated": "2026-10-06", + "release_date": "2026-03-18", + "last_updated": "2026-03-18", "type": "chat" } ] }, - "alibaba-coding-plan": { - "id": "alibaba-coding-plan", - "name": "Alibaba Coding Plan", - "display_name": "Alibaba Coding Plan", - "api": "https://coding-intl.dashscope.aliyuncs.com/v1", - "doc": "https://www.alibabacloud.com/help/en/model-studio/coding-plan", + "lilac": { + "id": "lilac", + "name": "Lilac", + "display_name": "Lilac", + "api": "https://api.getlilac.com/v1", + "doc": "https://docs.getlilac.com/inference/models", "models": [ { - "id": "glm-4.7", - "name": "GLM-4.7", - "display_name": "GLM-4.7", + "id": "moonshotai/kimi-k2.6", + "name": "Kimi K2.6", + "display_name": "Kimi K2.6", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 202752, - "output": 16384 + "context": 262144, + "output": 262144 }, "temperature": true, "tool_call": true, @@ -180574,17 +181330,17 @@ ] } }, - "attachment": false, + "attachment": true, "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-12-22", - "last_updated": "2025-12-22", + "knowledge": "2025-01", + "release_date": "2026-04-20", + "last_updated": "2026-04-20", "type": "chat" }, { - "id": "qwen3-coder-next", - "name": "Qwen3 Coder Next", - "display_name": "Qwen3 Coder Next", + "id": "zai-org/glm-5.2", + "name": "GLM 5.2", + "display_name": "GLM 5.2", "modalities": { "input": [ "text" @@ -180594,24 +181350,40 @@ ] }, "limit": { - "context": 262144, - "output": 65536 + "context": 524288, + "output": 524288 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "effort_options": [ + "high", + "max" + ] + } }, "attachment": false, "open_weights": true, - "release_date": "2026-02-03", - "last_updated": "2026-02-03", + "release_date": "2026-06-13", + "last_updated": "2026-06-13", "type": "chat" }, { - "id": "qwen3.5-plus", - "name": "Qwen3.5 Plus", - "display_name": "Qwen3.5 Plus", + "id": "google/gemma-4-31b-it", + "name": "Gemma 4 31B IT", + "display_name": "Gemma 4 31B IT", "modalities": { "input": [ "text", @@ -180623,8 +181395,8 @@ ] }, "limit": { - "context": 1000000, - "output": 65536 + "context": 262100, + "output": 262100 }, "temperature": true, "tool_call": true, @@ -180644,16 +181416,16 @@ } }, "attachment": true, - "open_weights": false, - "knowledge": "2025-04", - "release_date": "2026-02-16", - "last_updated": "2026-02-16", + "open_weights": true, + "knowledge": "2025-01", + "release_date": "2026-04-02", + "last_updated": "2026-04-02", "type": "chat" }, { - "id": "kimi-k2.5", - "name": "Kimi K2.5", - "display_name": "Kimi K2.5", + "id": "minimaxai/minimax-m3", + "name": "MiniMax M3", + "display_name": "MiniMax M3", "modalities": { "input": [ "text", @@ -180665,8 +181437,8 @@ ] }, "limit": { - "context": 262144, - "output": 32768 + "context": 1048576, + "output": 1048576 }, "temperature": true, "tool_call": true, @@ -180676,31 +181448,33 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] + "supported": true } }, "attachment": true, "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-01-27", - "last_updated": "2026-01-27", + "release_date": "2026-06-01", + "last_updated": "2026-06-01", "type": "chat" - }, + } + ] + }, + "trustedrouter": { + "id": "trustedrouter", + "name": "TrustedRouter", + "display_name": "TrustedRouter", + "api": "https://api.trustedrouter.com/v1", + "doc": "https://trustedrouter.com/docs", + "models": [ { - "id": "qwen3.6-plus", - "name": "Qwen3.6 Plus", - "display_name": "Qwen3.6 Plus", + "id": "trustedrouter/fast", + "name": "Fast", + "display_name": "Fast", "modalities": { "input": [ "text", "image", - "video" + "pdf" ], "output": [ "text" @@ -180708,7 +181482,7 @@ }, "limit": { "context": 1000000, - "output": 65536 + "output": 131072 }, "temperature": true, "tool_call": true, @@ -180718,37 +181492,37 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "none", + "low", + "medium", + "high" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-04", - "release_date": "2026-04-02", - "last_updated": "2026-04-02", + "release_date": "2026-06-01", + "last_updated": "2026-06-27", "type": "chat" }, { - "id": "glm-5", - "name": "GLM-5", - "display_name": "GLM-5", + "id": "trustedrouter/zdr", + "name": "Zero Data Retention", + "display_name": "Zero Data Retention", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 202752, - "output": 16384 + "context": 1000000, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -180758,36 +181532,37 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "none", + "low", + "medium", + "high" ] } }, - "attachment": false, + "attachment": true, "open_weights": false, - "release_date": "2026-02-11", - "last_updated": "2026-02-11", + "release_date": "2026-06-01", + "last_updated": "2026-06-27", "type": "chat" }, { - "id": "MiniMax-M2.5", - "name": "MiniMax-M2.5", - "display_name": "MiniMax-M2.5", + "id": "trustedrouter/auto", + "name": "Auto", + "display_name": "Auto", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 196608, - "output": 24576 + "context": 1000000, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -180798,27 +181573,29 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "none", + "low", + "medium", + "high" ] } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-02-12", - "last_updated": "2026-02-12", + "attachment": true, + "open_weights": false, + "release_date": "2026-05-01", + "last_updated": "2026-06-27", "type": "chat" }, { - "id": "qwen3-coder-plus", - "name": "Qwen3 Coder Plus", - "display_name": "Qwen3 Coder Plus", + "id": "trustedrouter/synth-code", + "name": "Synth Code", + "display_name": "Synth Code", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" @@ -180826,68 +181603,79 @@ }, "limit": { "context": 1000000, - "output": 65536 + "output": 131072 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-07-23", - "last_updated": "2025-07-23", + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "none", + "low", + "medium", + "high" + ] + } + }, + "attachment": true, + "open_weights": false, + "release_date": "2026-06-20", + "last_updated": "2026-06-27", "type": "chat" }, { - "id": "qwen3-max-2026-01-23", - "name": "Qwen3 Max", - "display_name": "Qwen3 Max", + "id": "trustedrouter/cheap", + "name": "Cheap", + "display_name": "Cheap", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 32768 + "context": 1000000, + "output": 131072 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true + "supported": true, + "default": true }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "none", + "low", + "medium", + "high" ] } }, - "attachment": false, + "attachment": true, "open_weights": false, - "knowledge": "2025-04", - "release_date": "2026-01-23", - "last_updated": "2026-01-23", + "release_date": "2026-05-01", + "last_updated": "2026-06-27", "type": "chat" }, { - "id": "qwen3.7-plus", - "name": "Qwen3.7 Plus", - "display_name": "Qwen3.7 Plus", + "id": "trustedrouter/synth", + "name": "Synth", + "display_name": "Synth", "modalities": { "input": [ "text", "image", - "video" + "pdf" ], "output": [ "text" @@ -180895,7 +181683,7 @@ }, "limit": { "context": 1000000, - "output": 64000 + "output": 131072 }, "temperature": true, "tool_call": true, @@ -180905,34 +181693,28 @@ }, "extra_capabilities": { "reasoning": { - "supported": true + "effort_options": [ + "none", + "low", + "medium", + "high" + ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-04", - "release_date": "2026-06-02", - "last_updated": "2026-06-02", + "release_date": "2026-06-20", + "last_updated": "2026-06-27", "type": "chat" - } - ] - }, - "nova": { - "id": "nova", - "name": "Nova", - "display_name": "Nova", - "api": "https://api.nova.amazon.com/v1", - "doc": "https://nova.amazon.com/dev/documentation", - "models": [ + }, { - "id": "nova-2-lite-v1", - "name": "Nova 2 Lite", - "display_name": "Nova 2 Lite", + "id": "trustedrouter/e2e", + "name": "End-to-End Encrypted", + "display_name": "End-to-End Encrypted", "modalities": { "input": [ "text", "image", - "video", "pdf" ], "output": [ @@ -180941,7 +181723,7 @@ }, "limit": { "context": 1000000, - "output": 64000 + "output": 131072 }, "temperature": true, "tool_call": true, @@ -180952,6 +181734,7 @@ "extra_capabilities": { "reasoning": { "effort_options": [ + "none", "low", "medium", "high" @@ -180960,19 +181743,28 @@ }, "attachment": true, "open_weights": false, - "knowledge": "2025-10", - "release_date": "2025-12-02", - "last_updated": "2025-12-02", + "release_date": "2026-06-01", + "last_updated": "2026-06-27", "type": "chat" - }, + } + ] + }, + "vispark": { + "id": "vispark", + "name": "Vispark", + "display_name": "Vispark", + "api": "https://api.lab.vispark.in/v1", + "doc": "https://lab.vispark.in/#vision", + "models": [ { - "id": "nova-2-pro-v1", - "name": "Nova 2 Pro", - "display_name": "Nova 2 Pro", + "id": "vispark/vision-small", + "name": "Vision Small", + "display_name": "Vision Small", "modalities": { "input": [ "text", "image", + "audio", "video", "pdf" ], @@ -180982,7 +181774,7 @@ }, "limit": { "context": 1000000, - "output": 64000 + "output": 65536 }, "temperature": true, "tool_call": true, @@ -180992,35 +181784,29 @@ }, "attachment": true, "open_weights": false, - "release_date": "2025-12-03", - "last_updated": "2026-01-03", + "release_date": "2024-05-15", + "last_updated": "2026-09", "type": "chat" - } - ] - }, - "iteracompute": { - "id": "iteracompute", - "name": "IteraCompute", - "display_name": "IteraCompute", - "api": "https://api.iteracompute.com/v1", - "doc": "https://iteracompute.com/docs.html", - "models": [ + }, { - "id": "minimax/minimax-m3", - "name": "MiniMax-M3", - "display_name": "MiniMax-M3", + "id": "vispark/vision-medium", + "name": "Vision Medium", + "display_name": "Vision Medium", "modalities": { "input": [ "text", - "image" + "image", + "audio", + "video", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 524288 + "context": 1000000, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -181028,33 +181814,31 @@ "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "supported": true - } - }, "attachment": true, - "open_weights": true, - "release_date": "2026-06-01", - "last_updated": "2026-06-01", + "open_weights": false, + "release_date": "2024-05-15", + "last_updated": "2026-09", "type": "chat" }, { - "id": "z-ai/glm-5.3-flash", - "name": "GLM-5.3-Flash", - "display_name": "GLM-5.3-Flash", + "id": "vispark/vision-large", + "name": "Vision Large", + "display_name": "Vision Large", "modalities": { "input": [ "text", - "image" + "image", + "audio", + "video", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 1000000, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -181062,39 +181846,39 @@ "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "effort_options": [ - "low", - "high", - "max" - ] - } - }, "attachment": true, - "open_weights": true, - "release_date": "2026-08-26", - "last_updated": "2026-08-26", + "open_weights": false, + "release_date": "2024-05-15", + "last_updated": "2026-09", "type": "chat" - }, + } + ] + }, + "poe": { + "id": "poe", + "name": "Poe", + "display_name": "Poe", + "api": "https://api.poe.com/v1", + "doc": "https://creator.poe.com/docs/external-applications/openai-compatible-api", + "models": [ { - "id": "z-ai/glm-5.3", - "name": "GLM-5.3", - "display_name": "GLM-5.3", + "id": "openai/gpt-5.2-pro", + "name": "GPT-5.2-Pro", + "display_name": "GPT-5.2-Pro", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 400000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -181103,36 +181887,48 @@ "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "high", "effort_options": [ - "low", + "medium", "high", - "max" - ] + "xhigh" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-08-14", - "last_updated": "2026-08-14", + "attachment": true, + "open_weights": false, + "knowledge": "2025-08-31", + "release_date": "2025-12-11", + "last_updated": "2025-12-11", "type": "chat" }, { - "id": "deepseek/deepseek-v4.1-flash", - "name": "DeepSeek V4.1 Flash", - "display_name": "DeepSeek V4.1 Flash", + "id": "openai/gpt-5-pro", + "name": "GPT-5-Pro", + "display_name": "GPT-5-Pro", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 393216 + "context": 400000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -181141,24 +181937,29 @@ "extra_capabilities": { "reasoning": { "supported": true, - "effort_options": [ + "default_enabled": true, + "mode": "fixed", + "effort": "high", + "verbosity": "medium", + "verbosity_options": [ "low", - "high", - "max" - ] + "medium", + "high" + ], + "visibility": "hidden" } }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-09-10", - "last_updated": "2026-09-10", + "attachment": true, + "open_weights": false, + "knowledge": "2024-09-30", + "release_date": "2025-10-06", + "last_updated": "2025-10-06", "type": "chat" }, { - "id": "deepseek/deepseek-v4-flash-0731", - "name": "DeepSeek V4 Flash 0731 (legacy alias)", - "display_name": "DeepSeek V4 Flash 0731 (legacy alias)", + "id": "openai/o4-mini-deep-research", + "name": "o4-mini-deep-research", + "display_name": "o4-mini-deep-research", "modalities": { "input": [ "text" @@ -181168,10 +181969,10 @@ ] }, "limit": { - "context": 1048576, - "output": 393216 + "context": 200000, + "output": 100000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -181180,37 +181981,42 @@ "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", "effort_options": [ "low", - "high", - "max" - ] + "medium", + "high" + ], + "visibility": "hidden" } }, "attachment": false, - "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-09-10", - "last_updated": "2026-09-10", + "open_weights": false, + "knowledge": "2024-05", + "release_date": "2025-06-26", + "last_updated": "2025-06-26", "type": "chat" }, { - "id": "deepseek/deepseek-v4-pro-0813", - "name": "DeepSeek V4 Pro 0813", - "display_name": "DeepSeek V4 Pro 0813", + "id": "openai/o3-mini", + "name": "o3-mini", + "display_name": "o3-mini", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 393216 + "context": 200000, + "output": 100000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -181219,221 +182025,281 @@ "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", "effort_options": [ "low", - "high", - "max" - ] + "medium", + "high" + ], + "visibility": "hidden" } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-08-13", - "last_updated": "2026-08-22", + "attachment": true, + "open_weights": false, + "knowledge": "2024-05", + "release_date": "2024-12-20", + "last_updated": "2025-01-29", "type": "chat" }, { - "id": "moonshotai/kimi-k3", - "name": "Kimi K3", - "display_name": "Kimi K3", + "id": "openai/gpt-5.4-nano", + "name": "GPT-5.4-Nano", + "display_name": "GPT-5.4-Nano", "modalities": { "input": [ "text", "image" ], "output": [ - "text" + "text", + "image" ] }, "limit": { - "context": 1048576, - "output": 999999 + "context": 400000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": false, + "mode": "effort", + "effort": "none", "effort_options": [ + "none", "low", + "medium", "high", - "max" - ] + "xhigh" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, "attachment": true, - "open_weights": true, - "release_date": "2026-07-16", - "last_updated": "2026-07-16", + "open_weights": false, + "knowledge": "2025-08-31", + "release_date": "2026-03-17", + "last_updated": "2026-03-17", "type": "chat" }, { - "id": "moonshotai/kimi-k2.7-code", - "name": "Kimi K2.7 Code", - "display_name": "Kimi K2.7 Code", + "id": "openai/chatgpt-4o-latest", + "name": "ChatGPT-4o-Latest", + "display_name": "ChatGPT-4o-Latest", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 235929 + "context": 128000, + "output": 8192 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true - } + "supported": false }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-06-12", - "last_updated": "2026-06-12", + "attachment": true, + "open_weights": false, + "release_date": "2024-08-14", + "last_updated": "2024-08-14", "type": "chat" }, { - "id": "qwen/qwen3.8-27b", - "name": "Qwen3.8 27B", - "display_name": "Qwen3.8 27B", + "id": "openai/gpt-5.4", + "name": "GPT-5.4", + "display_name": "GPT-5.4", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ - "text" + "text", + "image" ] }, "limit": { - "context": 131072, - "output": 8192 + "context": 1050000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": false, + "mode": "effort", + "effort": "none", "effort_options": [ + "none", "low", "medium", + "high", "xhigh" - ] + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, "attachment": true, - "open_weights": true, - "release_date": "2026-08-14", - "last_updated": "2026-08-14", + "open_weights": false, + "knowledge": "2025-08-31", + "release_date": "2026-03-05", + "last_updated": "2026-03-05", "type": "chat" }, { - "id": "qwen/qwen3.8-2.4t-a95b", - "name": "Qwen3.8 2.4T A95B", - "display_name": "Qwen3.8 2.4T A95B", + "id": "openai/gpt-5.4-mini", + "name": "GPT-5.4-Mini", + "display_name": "GPT-5.4-Mini", "modalities": { "input": [ "text", "image" ], "output": [ - "text" + "text", + "image" ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 400000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": false, + "mode": "effort", + "effort": "none", "effort_options": [ + "none", "low", "medium", + "high", "xhigh" - ] + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, "attachment": true, - "open_weights": true, - "release_date": "2026-08-12", - "last_updated": "2026-08-12", + "open_weights": false, + "knowledge": "2025-08-31", + "release_date": "2026-03-17", + "last_updated": "2026-03-17", "type": "chat" - } - ] - }, - "watsonx": { - "id": "watsonx", - "name": "watsonx.ai", - "display_name": "watsonx.ai", - "doc": "https://www.ibm.com/docs/en/watsonx/saas?topic=solutions-supported-foundation-models", - "models": [ + }, { - "id": "ibm/granite-4-h-small", - "name": "Granite-4.0-H-Small", - "display_name": "Granite-4.0-H-Small", + "id": "openai/gpt-5.1-codex-max", + "name": "GPT-5.1-Codex-Max", + "display_name": "GPT-5.1-Codex-Max", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 131072 + "context": 400000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": false }, - "attachment": false, - "open_weights": true, - "release_date": "2025-10-02", - "last_updated": "2025-10-02", + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": false, + "mode": "effort", + "effort": "none", + "effort_options": [ + "none", + "low", + "medium", + "high" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" + } + }, + "attachment": true, + "open_weights": false, + "knowledge": "2024-09-30", + "release_date": "2025-11-13", + "last_updated": "2025-11-13", "type": "chat" }, { - "id": "openai/gpt-oss-120b", - "name": "GPT OSS 120B", - "display_name": "GPT OSS 120B", + "id": "openai/gpt-5.3-codex", + "name": "GPT-5.3-Codex", + "display_name": "GPT-5.3-Codex", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 32768 + "context": 400000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -181442,24 +182308,35 @@ "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", "effort_options": [ + "low", + "medium", + "high", + "xhigh" + ], + "verbosity": "medium", + "verbosity_options": [ "low", "medium", "high" - ] + ], + "visibility": "hidden" } }, - "attachment": false, - "open_weights": true, - "knowledge": "2024-06", - "release_date": "2025-08-05", - "last_updated": "2025-08-05", + "attachment": true, + "open_weights": false, + "knowledge": "2025-08-31", + "release_date": "2026-02-05", + "last_updated": "2026-02-05", "type": "chat" }, { - "id": "mistralai/mistral-small-3-1-24b-instruct-2503", - "name": "Mistral Small 3.1 24B", - "display_name": "Mistral Small 3.1 24B", + "id": "openai/gpt-5.1-codex", + "name": "GPT-5.1-Codex", + "display_name": "GPT-5.1-Codex", "modalities": { "input": [ "text", @@ -181470,53 +182347,98 @@ ] }, "limit": { - "context": 131072, - "output": 16384 + "context": 400000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": false + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": false, + "mode": "effort", + "effort": "none", + "effort_options": [ + "none", + "low", + "medium", + "high" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" + } }, "attachment": true, - "open_weights": true, - "knowledge": "2024-06", - "release_date": "2025-03-17", - "last_updated": "2025-03-17", + "open_weights": false, + "knowledge": "2024-09-30", + "release_date": "2025-11-13", + "last_updated": "2025-11-13", "type": "chat" }, { - "id": "meta-llama/llama-3-3-70b-instruct", - "name": "Llama-3.3-70B-Instruct", - "display_name": "Llama-3.3-70B-Instruct", + "id": "openai/gpt-5.2-codex", + "name": "GPT-5.2-Codex", + "display_name": "GPT-5.2-Codex", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 4096 + "context": 400000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, - "open_weights": true, - "knowledge": "2023-12", - "release_date": "2024-12-06", - "last_updated": "2024-12-06", + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", + "effort_options": [ + "low", + "medium", + "high", + "xhigh" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" + } + }, + "attachment": true, + "open_weights": false, + "knowledge": "2025-08-31", + "release_date": "2025-12-11", + "last_updated": "2025-12-11", "type": "chat" }, { - "id": "meta-llama/llama-4-maverick-17b-128e-instruct-fp8", - "name": "Llama 4 Maverick 17B 128E Instruct FP8", - "display_name": "Llama 4 Maverick 17B 128E Instruct FP8", + "id": "openai/gpt-5.2", + "name": "GPT-5.2", + "display_name": "GPT-5.2", "modalities": { "input": [ "text", @@ -181527,47 +182449,63 @@ ] }, "limit": { - "context": 131072, - "output": 8192 + "context": 400000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": false + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": false, + "mode": "effort", + "effort": "none", + "effort_options": [ + "none", + "low", + "medium", + "high", + "xhigh" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" + } }, "attachment": true, - "open_weights": true, - "knowledge": "2024-08", - "release_date": "2025-04-05", - "last_updated": "2025-04-05", + "open_weights": false, + "knowledge": "2025-08-31", + "release_date": "2025-12-11", + "last_updated": "2025-12-11", "type": "chat" - } - ] - }, - "tempr": { - "id": "tempr", - "name": "Tempr Gateway", - "display_name": "Tempr Gateway", - "api": "https://api.temprhq.io/v1", - "doc": "https://temprhq.io/docs/gateway-reference.html", - "models": [ + }, { - "id": "minimax/MiniMax-M2.7-highspeed", - "name": "MiniMax-M2.7-highspeed", - "display_name": "MiniMax-M2.7-highspeed", + "id": "openai/gpt-5.4-pro", + "name": "GPT-5.4-Pro", + "display_name": "GPT-5.4-Pro", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ - "text" + "text", + "image" ] }, "limit": { - "context": 204800, - "output": 131072 + "context": 1050000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -181576,72 +182514,75 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] + "default_enabled": true, + "mode": "effort", + "effort": "high", + "effort_options": [ + "medium", + "high", + "xhigh" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-03-18", - "last_updated": "2026-03-18", + "attachment": true, + "open_weights": false, + "knowledge": "2025-08-31", + "release_date": "2026-03-05", + "last_updated": "2026-03-05", "type": "chat" }, { - "id": "minimax/MiniMax-M3", - "name": "MiniMax-M3", - "display_name": "MiniMax-M3", + "id": "openai/gpt-image-1.5", + "name": "gpt-image-1.5", + "display_name": "gpt-image-1.5", "modalities": { "input": [ "text", - "image", - "video" + "image" ], "output": [ - "text" + "image" ] }, "limit": { - "context": 1048576, - "output": 512000 + "context": 128000, + "output": 8192 }, - "temperature": true, - "tool_call": true, + "temperature": false, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true - } + "supported": false }, "attachment": true, - "open_weights": true, - "release_date": "2026-06-01", - "last_updated": "2026-06-01", - "type": "chat" + "open_weights": false, + "release_date": "2025-11-25", + "last_updated": "2025-11-25" }, { - "id": "minimax/MiniMax-M2.7", - "name": "MiniMax-M2.7", - "display_name": "MiniMax-M2.7", + "id": "openai/o1-pro", + "name": "o1-pro", + "display_name": "o1-pro", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 204800, - "output": 131072 + "context": 200000, + "output": 100000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -181650,24 +182591,28 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] + "default_enabled": true, + "mode": "effort", + "effort": "medium", + "effort_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-03-18", - "last_updated": "2026-03-18", + "attachment": true, + "open_weights": false, + "knowledge": "2023-09", + "release_date": "2025-03-19", + "last_updated": "2025-03-19", "type": "chat" }, { - "id": "cerebras/qwen-3.8-27b", - "name": "Qwen3.8 27B", - "display_name": "Qwen3.8 27B", + "id": "openai/gpt-5-codex", + "name": "GPT-5-Codex", + "display_name": "GPT-5-Codex", "modalities": { "input": [ "text", @@ -181678,10 +182623,10 @@ ] }, "limit": { - "context": 131072, - "output": 40960 + "context": 400000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -181689,24 +182634,36 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", "effort_options": [ - "none", + "minimal", "low", "medium", "high" - ] + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, "attachment": true, - "open_weights": true, - "release_date": "2026-08-14", - "last_updated": "2026-08-14", + "open_weights": false, + "knowledge": "2024-09-30", + "release_date": "2025-09-15", + "last_updated": "2025-09-15", "type": "chat" }, { - "id": "cerebras/gpt-oss-120b", - "name": "GPT OSS 120B", - "display_name": "GPT OSS 120B", + "id": "openai/gpt-5.3-codex-spark", + "name": "GPT-5.3-Codex-Spark", + "display_name": "GPT-5.3-Codex-Spark", "modalities": { "input": [ "text" @@ -181716,10 +182673,10 @@ ] }, "limit": { - "context": 131072, - "output": 40960 + "context": 128000, + "output": 16384 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -181728,223 +182685,269 @@ "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", "effort_options": [ + "low", + "medium", + "high", + "xhigh" + ], + "verbosity": "medium", + "verbosity_options": [ "low", "medium", "high" - ] + ], + "visibility": "hidden" } }, "attachment": false, - "open_weights": true, - "knowledge": "2024-06", - "release_date": "2025-08-05", - "last_updated": "2025-08-05", + "open_weights": false, + "knowledge": "2025-08-31", + "release_date": "2026-02-05", + "last_updated": "2026-02-05", "type": "chat" }, { - "id": "xiaomi-mimo/mimo-v2.6-pro", - "name": "MiMo-V2.6-Pro", - "display_name": "MiMo-V2.6-Pro", + "id": "openai/sora-2", + "name": "Sora-2", + "display_name": "Sora-2", "modalities": { "input": [ "text", - "image", - "audio", - "video" + "image" ], "output": [ - "text" + "video" ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 8192, + "output": 8192 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { - "supported": true, - "default": true + "supported": false }, "attachment": true, - "open_weights": true, - "release_date": "2026-09-22", - "last_updated": "2026-09-22", - "type": "chat" + "open_weights": false, + "release_date": "2025-10-06", + "last_updated": "2025-10-06" }, { - "id": "xiaomi-mimo/mimo-v2.6-flash", - "name": "MiMo-V2.6-Flash", - "display_name": "MiMo-V2.6-Flash", + "id": "openai/gpt-4.1-nano", + "name": "GPT-4.1-nano", + "display_name": "GPT-4.1-nano", "modalities": { "input": [ "text", - "image", - "audio", - "video" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 1047576, + "output": 32768 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { - "supported": true, - "default": true + "supported": false }, "attachment": true, - "open_weights": true, - "release_date": "2026-09-22", - "last_updated": "2026-09-22", + "open_weights": false, + "knowledge": "2024-04", + "release_date": "2025-04-14", + "last_updated": "2025-04-14", "type": "chat" }, { - "id": "xiaomi-mimo/mimo-v2.6-pro-ultraspeed", - "name": "MiMo-V2.6-Pro-UltraSpeed", - "display_name": "MiMo-V2.6-Pro-UltraSpeed", + "id": "openai/gpt-5-nano", + "name": "GPT-5-nano", + "display_name": "GPT-5-nano", "modalities": { "input": [ "text", - "image", - "audio", - "video" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 400000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, "default": true }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", + "effort_options": [ + "minimal", + "low", + "medium", + "high" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" + } + }, "attachment": true, - "open_weights": true, - "release_date": "2026-09-21", - "last_updated": "2026-09-21", + "open_weights": false, + "knowledge": "2024-05-30", + "release_date": "2025-08-07", + "last_updated": "2025-08-07", "type": "chat" }, { - "id": "zai/glm-4.6v-flash", - "name": "GLM-4.6V-Flash", - "display_name": "GLM-4.6V-Flash", + "id": "openai/o3-pro", + "name": "o3-pro", + "display_name": "o3-pro", "modalities": { "input": [ "text", - "image", - "video" + "image" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 32768 + "context": 200000, + "output": 100000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, "default": true }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", + "effort_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" + } + }, "attachment": true, - "open_weights": true, - "release_date": "2025-12-08", - "last_updated": "2025-12-08", + "open_weights": false, + "knowledge": "2024-05", + "release_date": "2025-06-10", + "last_updated": "2025-06-10", "type": "chat" }, { - "id": "zai/glm-4.7-flashx", - "name": "GLM-4.7-FlashX", - "display_name": "GLM-4.7-FlashX", + "id": "openai/gpt-5.5-pro", + "name": "GPT-5.5-Pro", + "display_name": "GPT-5.5-Pro", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ - "text" + "text", + "image" ] }, "limit": { - "context": 200000, - "output": 131072 + "context": 400000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, "default": true }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-04", - "release_date": "2026-01-19", - "last_updated": "2026-01-19", + "extra_capabilities": { + "reasoning": { + "supported": true, + "effort_options": [ + "medium", + "high", + "xhigh" + ] + } + }, + "attachment": true, + "open_weights": false, + "knowledge": "2025-12-01", + "release_date": "2026-04-23", + "last_updated": "2026-04-23", "type": "chat" }, { - "id": "zai/glm-4.5v", - "name": "GLM-4.5V", - "display_name": "GLM-4.5V", + "id": "openai/gpt-3.5-turbo-instruct", + "name": "GPT-3.5-Turbo-Instruct", + "display_name": "GPT-3.5-Turbo-Instruct", "modalities": { "input": [ "text", - "image", - "video" + "image" ], "output": [ "text" ] }, "limit": { - "context": 64000, - "output": 16384 + "context": 3500, + "output": 1024 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { - "supported": true, - "default": true + "supported": false }, "attachment": true, - "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-08-11", - "last_updated": "2025-08-11", + "open_weights": false, + "release_date": "2023-09-20", + "last_updated": "2023-09-20", "type": "chat" }, { - "id": "zai/glm-5.3-flash", - "name": "GLM-5.3-Flash", - "display_name": "GLM-5.3-Flash", + "id": "openai/o4-mini", + "name": "o4-mini", + "display_name": "o4-mini", "modalities": { "input": [ "text", - "image", - "video", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 200000, + "output": 100000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -181953,67 +182956,93 @@ "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", "effort_options": [ "low", - "high", - "max" - ] + "medium", + "high" + ], + "visibility": "hidden" } }, "attachment": true, - "open_weights": true, - "release_date": "2026-08-26", - "last_updated": "2026-08-26", + "open_weights": false, + "knowledge": "2024-05", + "release_date": "2025-04-16", + "last_updated": "2025-04-16", "type": "chat" }, { - "id": "zai/glm-5v-turbo", - "name": "GLM-5V-Turbo", - "display_name": "GLM-5V-Turbo", + "id": "openai/gpt-5", + "name": "GPT-5", + "display_name": "GPT-5", "modalities": { "input": [ "text", - "image", - "video", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 131072 + "context": 400000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, "default": true }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", + "effort_options": [ + "minimal", + "low", + "medium", + "high" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" + } + }, "attachment": true, "open_weights": false, - "release_date": "2026-04-01", - "last_updated": "2026-04-01", + "knowledge": "2024-09-30", + "release_date": "2025-08-07", + "last_updated": "2025-08-07", "type": "chat" }, { - "id": "zai/glm-5-turbo", - "name": "GLM-5-Turbo", - "display_name": "GLM-5-Turbo", + "id": "openai/gpt-5-mini", + "name": "GPT-5-mini", + "display_name": "GPT-5-mini", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 131072 + "context": 400000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -182021,103 +183050,137 @@ }, "extra_capabilities": { "reasoning": { - "supported": true + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", + "effort_options": [ + "minimal", + "low", + "medium", + "high" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, - "attachment": false, + "attachment": true, "open_weights": false, - "release_date": "2026-03-16", - "last_updated": "2026-03-16", + "knowledge": "2024-05-30", + "release_date": "2025-08-07", + "last_updated": "2025-08-07", "type": "chat" }, { - "id": "zai/glm-4.6v", - "name": "GLM-4.6V", - "display_name": "GLM-4.6V", + "id": "openai/gpt-4o-mini", + "name": "GPT-4o-mini", + "display_name": "GPT-4o-mini", "modalities": { "input": [ "text", - "image", - "video" + "image" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 32768 + "context": 124096, + "output": 4096 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { - "supported": true, - "default": true + "supported": false }, "attachment": true, - "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-12-08", - "last_updated": "2025-12-08", + "open_weights": false, + "knowledge": "2023-09", + "release_date": "2024-07-18", + "last_updated": "2024-07-18", "type": "chat" }, { - "id": "zai/glm-4.7", - "name": "GLM-4.7", - "display_name": "GLM-4.7", + "id": "openai/gpt-4o-aug", + "name": "GPT-4o-Aug", + "display_name": "GPT-4o-Aug", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 204800, - "output": 131072 + "context": 128000, + "output": 8192 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } + "supported": false }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-12-22", - "last_updated": "2025-12-22", + "attachment": true, + "open_weights": false, + "knowledge": "2023-09", + "release_date": "2024-08-06", + "last_updated": "2024-08-06", "type": "chat" }, { - "id": "zai/glm-4.6", - "name": "GLM-4.6", - "display_name": "GLM-4.6", + "id": "openai/gpt-5-chat", + "name": "GPT-5-Chat", + "display_name": "GPT-5-Chat", "modalities": { "input": [ + "text", + "image" + ], + "output": [ "text" + ] + }, + "limit": { + "context": 128000, + "output": 16384 + }, + "temperature": false, + "tool_call": true, + "reasoning": { + "supported": false + }, + "attachment": true, + "open_weights": false, + "knowledge": "2024-09-30", + "release_date": "2025-08-07", + "last_updated": "2025-08-07", + "type": "chat" + }, + { + "id": "openai/o1", + "name": "o1", + "display_name": "o1", + "modalities": { + "input": [ + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 204800, - "output": 131072 + "context": 200000, + "output": 100000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -182125,65 +183188,72 @@ }, "extra_capabilities": { "reasoning": { - "supported": true + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", + "effort_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-09-30", - "last_updated": "2025-09-30", + "attachment": true, + "open_weights": false, + "knowledge": "2023-09", + "release_date": "2024-12-05", + "last_updated": "2024-12-05", "type": "chat" }, { - "id": "zai/glm-4.5-air", - "name": "GLM-4.5-Air", - "display_name": "GLM-4.5-Air", + "id": "openai/gpt-4o", + "name": "GPT-4o", + "display_name": "GPT-4o", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 98304 + "context": 128000, + "output": 8192 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { - "supported": true, - "default": true + "supported": false }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-07-28", - "last_updated": "2025-07-28", + "attachment": true, + "open_weights": false, + "knowledge": "2023-09", + "release_date": "2024-05-13", + "last_updated": "2024-08-06", "type": "chat" }, { - "id": "zai/glm-5.3-flashx", - "name": "GLM-5.3-FlashX", - "display_name": "GLM-5.3-FlashX", + "id": "openai/o3-mini-high", + "name": "o3-mini-high", + "display_name": "o3-mini-high", "modalities": { "input": [ "text", - "image", - "video", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 200000, + "output": 100000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -182191,56 +183261,58 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", "effort_options": [ "low", - "high", - "max" - ] + "medium", + "high" + ], + "visibility": "hidden" } }, "attachment": true, - "open_weights": true, - "release_date": "2026-09-18", - "last_updated": "2026-09-18", + "open_weights": false, + "knowledge": "2024-05", + "release_date": "2024-12-20", + "last_updated": "2025-01-29", "type": "chat" }, { - "id": "zai/glm-5.1", - "name": "GLM-5.1", - "display_name": "GLM-5.1", + "id": "openai/gpt-3.5-turbo", + "name": "GPT-3.5-Turbo", + "display_name": "GPT-3.5-Turbo", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 131072 + "context": 16384, + "output": 2048 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true - } + "supported": false }, - "attachment": false, - "open_weights": true, - "release_date": "2026-04-07", - "last_updated": "2026-04-07", + "attachment": true, + "open_weights": false, + "knowledge": "2021-09-01", + "release_date": "2023-03-01", + "last_updated": "2023-11-06", "type": "chat" }, { - "id": "zai/glm-5", - "name": "GLM-5", - "display_name": "GLM-5", + "id": "openai/gpt-4o-search", + "name": "GPT-4o-Search", + "display_name": "GPT-4o-Search", "modalities": { "input": [ "text" @@ -182250,103 +183322,101 @@ ] }, "limit": { - "context": 204800, - "output": 131072 + "context": 128000, + "output": 8192 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } + "supported": false }, "attachment": false, - "open_weights": true, - "release_date": "2026-02-12", - "last_updated": "2026-02-12", + "open_weights": false, + "release_date": "2025-03-11", + "last_updated": "2025-03-11", "type": "chat" }, { - "id": "zai/glm-5.3", - "name": "GLM-5.3", - "display_name": "GLM-5.3", + "id": "openai/gpt-5.1", + "name": "GPT-5.1", + "display_name": "GPT-5.1", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 400000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": false, + "mode": "effort", + "effort": "none", "effort_options": [ + "none", "low", - "high", - "max" - ] + "medium", + "high" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-08-14", - "last_updated": "2026-08-14", + "attachment": true, + "open_weights": false, + "knowledge": "2024-09-30", + "release_date": "2025-11-13", + "last_updated": "2025-11-13", "type": "chat" }, { - "id": "zai/glm-4.5-flash", - "name": "GLM-4.5-Flash", - "display_name": "GLM-4.5-Flash", + "id": "openai/dall-e-3", + "name": "DALL-E-3", + "display_name": "DALL-E-3", "modalities": { "input": [ "text" ], "output": [ - "text" + "image" ] }, "limit": { - "context": 131072, - "output": 98304 + "context": 800, + "output": 8192 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { - "supported": true, - "default": true + "supported": false }, "attachment": false, "open_weights": false, - "knowledge": "2025-04", - "release_date": "2025-07-28", - "last_updated": "2025-07-28", - "type": "chat" + "release_date": "2023-11-06", + "last_updated": "2023-11-06" }, { - "id": "zai/glm-4.5", - "name": "GLM-4.5", - "display_name": "GLM-4.5", + "id": "openai/gpt-4o-mini-search", + "name": "GPT-4o-mini-Search", + "display_name": "GPT-4o-mini-Search", "modalities": { "input": [ "text" @@ -182356,26 +183426,24 @@ ] }, "limit": { - "context": 131072, - "output": 98304 + "context": 128000, + "output": 8192 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { - "supported": true, - "default": true + "supported": false }, "attachment": false, - "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-07-28", - "last_updated": "2025-07-28", + "open_weights": false, + "release_date": "2025-03-11", + "last_updated": "2025-03-11", "type": "chat" }, { - "id": "zai/glm-4.7-flash", - "name": "GLM-4.7-Flash", - "display_name": "GLM-4.7-Flash", + "id": "openai/o3-deep-research", + "name": "o3-deep-research", + "display_name": "o3-deep-research", "modalities": { "input": [ "text" @@ -182386,9 +183454,9 @@ }, "limit": { "context": 200000, - "output": 131072 + "output": 100000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -182396,33 +183464,44 @@ }, "extra_capabilities": { "reasoning": { - "supported": true + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", + "effort_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, "attachment": false, - "open_weights": true, - "knowledge": "2025-04", - "release_date": "2026-01-19", - "last_updated": "2026-01-19", + "open_weights": false, + "knowledge": "2024-05", + "release_date": "2025-06-26", + "last_updated": "2025-06-26", "type": "chat" }, { - "id": "zai/glm-5.2", - "name": "GLM-5.2", - "display_name": "GLM-5.2", + "id": "openai/gpt-5.5", + "name": "GPT-5.5", + "display_name": "GPT-5.5", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ - "text" + "text", + "image" ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 400000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -182431,298 +183510,348 @@ "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", "effort_options": [ - "none", + "low", + "medium", "high", - "max" - ] + "xhigh" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-06-13", - "last_updated": "2026-06-13", + "attachment": true, + "open_weights": false, + "knowledge": "2025-12-01", + "release_date": "2026-04-23", + "last_updated": "2026-04-23", "type": "chat" }, { - "id": "xai/grok-4.20-0309-reasoning", - "name": "Grok 4.20 (Reasoning)", - "display_name": "Grok 4.20 (Reasoning)", + "id": "openai/gpt-3.5-turbo-raw", + "name": "GPT-3.5-Turbo-Raw", + "display_name": "GPT-3.5-Turbo-Raw", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 30000 + "context": 4524, + "output": 2048 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { - "supported": true, - "default": true + "supported": false }, "attachment": true, "open_weights": false, - "release_date": "2026-03-09", - "last_updated": "2026-03-09", + "knowledge": "2021-09-01", + "release_date": "2023-03-01", + "last_updated": "2023-11-06", "type": "chat" }, { - "id": "xai/grok-4.5", - "name": "Grok 4.5", - "display_name": "Grok 4.5", + "id": "openai/gpt-5.1-codex-mini", + "name": "GPT-5.1-Codex-Mini", + "display_name": "GPT-5.1-Codex-Mini", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 500000, - "output": 500000 + "context": 400000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": false, + "mode": "effort", + "effort": "none", "effort_options": [ + "none", "low", "medium", - "high", - "xhigh" - ] + "high" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, - "attachment": true, + "attachment": false, "open_weights": false, - "release_date": "2026-07-08", - "last_updated": "2026-07-08", + "knowledge": "2024-09-30", + "release_date": "2025-11-13", + "last_updated": "2025-11-13", "type": "chat" }, { - "id": "xai/grok-build-0.1", - "name": "Grok Build 0.1", - "display_name": "Grok Build 0.1", + "id": "openai/gpt-image-1", + "name": "GPT-Image-1", + "display_name": "GPT-Image-1", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ - "text" + "image" ] }, "limit": { - "context": 256000, - "output": 256000 + "context": 128000, + "output": 8192 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { - "supported": true, - "default": true + "supported": false }, - "extra_capabilities": { - "reasoning": { - "supported": true - } + "attachment": true, + "open_weights": false, + "release_date": "2025-04-24", + "last_updated": "2025-04-24" + }, + { + "id": "openai/gpt-image-1-mini", + "name": "GPT-Image-1-Mini", + "display_name": "GPT-Image-1-Mini", + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "image" + ] + }, + "limit": { + "context": 8192, + "output": 8192 + }, + "temperature": false, + "tool_call": true, + "reasoning": { + "supported": false }, "attachment": true, "open_weights": false, - "release_date": "2026-04-16", - "last_updated": "2026-04-16", - "type": "chat" + "release_date": "2025-10-06", + "last_updated": "2025-10-06" }, { - "id": "xai/grok-4.7", - "name": "Grok 4.7", - "display_name": "Grok 4.7", + "id": "openai/gpt-4-turbo", + "name": "GPT-4-Turbo", + "display_name": "GPT-4-Turbo", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 500000, - "output": 500000 + "context": 128000, + "output": 4096 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "low", - "medium", - "high", - "xhigh" - ] - } + "supported": false }, "attachment": true, "open_weights": false, - "knowledge": "2026-05", - "release_date": "2026-09-21", - "last_updated": "2026-09-21", + "knowledge": "2023-12", + "release_date": "2023-11-06", + "last_updated": "2024-04-09", "type": "chat" }, { - "id": "xai/grok-4.3", - "name": "Grok 4.3", - "display_name": "Grok 4.3", + "id": "openai/sora-2-pro", + "name": "Sora-2-Pro", + "display_name": "Sora-2-Pro", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ - "text" + "video" ] }, "limit": { - "context": 1000000, - "output": 30000 + "context": 8192, + "output": 8192 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { - "supported": true, - "default": true + "supported": false }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "effort_options": [ - "none", - "low", - "medium", - "high", - "xhigh" - ] - } + "attachment": true, + "open_weights": false, + "release_date": "2025-10-06", + "last_updated": "2025-10-06" + }, + { + "id": "openai/gpt-5.3-instant", + "name": "GPT-5.3-Instant", + "display_name": "GPT-5.3-Instant", + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 16384 + }, + "temperature": false, + "tool_call": true, + "reasoning": { + "supported": false }, "attachment": true, "open_weights": false, - "release_date": "2026-04-17", - "last_updated": "2026-04-17", + "knowledge": "2025-08-31", + "release_date": "2026-03-03", + "last_updated": "2026-03-03", "type": "chat" }, { - "id": "xai/grok-4.20-0309-non-reasoning", - "name": "Grok 4.20 (Non-Reasoning)", - "display_name": "Grok 4.20 (Non-Reasoning)", + "id": "openai/gpt-4.1-mini", + "name": "GPT-4.1-mini", + "display_name": "GPT-4.1-mini", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 30000 + "context": 1047576, + "output": 32768 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": false }, "attachment": true, "open_weights": false, - "release_date": "2026-03-09", - "last_updated": "2026-03-09", + "knowledge": "2024-04", + "release_date": "2025-04-14", + "last_updated": "2025-04-14", "type": "chat" }, { - "id": "xai/grok-4.6", - "name": "Grok 4.6", - "display_name": "Grok 4.6", + "id": "openai/gpt-5.2-instant", + "name": "GPT-5.2-Instant", + "display_name": "GPT-5.2-Instant", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 500000, - "output": 500000 + "context": 128000, + "output": 16384 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": false, + "mode": "effort", + "effort": "none", "effort_options": [ + "none", "low", "medium", "high", "xhigh" - ] + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, "attachment": true, "open_weights": false, - "knowledge": "2026-02-01", - "release_date": "2026-08-12", - "last_updated": "2026-08-12", + "knowledge": "2025-08-31", + "release_date": "2025-12-11", + "last_updated": "2025-12-11", "type": "chat" }, { - "id": "anthropic/claude-opus-4-5", - "name": "Claude Opus 4.5 (latest)", - "display_name": "Claude Opus 4.5 (latest)", + "id": "openai/gpt-5.1-instant", + "name": "GPT-5.1-Instant", + "display_name": "GPT-5.1-Instant", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 64000 + "context": 128000, + "output": 16384 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -182732,636 +183861,385 @@ "reasoning": { "supported": true, "default_enabled": false, - "mode": "mixed", - "budget": { - "min": 1024, - "unit": "tokens" - }, - "effort": "high", + "mode": "effort", + "effort": "none", "effort_options": [ + "none", "low", "medium", "high" ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" ], - "notes": [ - "Claude Opus 4.5 uses manual thinking.type = \"enabled\" with budget_tokens; effort can be used alongside the thinking budget.", - "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header." - ] + "visibility": "hidden" } }, "attachment": true, "open_weights": false, - "knowledge": "2025-05", - "release_date": "2025-11-24", - "last_updated": "2025-11-24", + "knowledge": "2024-09-30", + "release_date": "2025-11-13", + "last_updated": "2025-11-13", "type": "chat" }, { - "id": "anthropic/claude-opus-5", - "name": "Claude Opus 5", - "display_name": "Claude Opus 5", + "id": "openai/gpt-4.1", + "name": "GPT-4.1", + "display_name": "GPT-4.1", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 1047576, + "output": 32768 }, "temperature": false, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" - ] - } + "supported": false }, "attachment": true, "open_weights": false, - "knowledge": "2026-05", - "release_date": "2026-07-24", - "last_updated": "2026-07-24", + "knowledge": "2024-04", + "release_date": "2025-04-14", + "last_updated": "2025-04-14", "type": "chat" }, { - "id": "anthropic/claude-opus-4-7", - "name": "Claude Opus 4.7", - "display_name": "Claude Opus 4.7", + "id": "openai/o3", + "name": "o3", + "display_name": "o3", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 200000, + "output": 100000 }, "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, + "default_enabled": true, "mode": "effort", - "effort": "high", + "effort": "medium", "effort_options": [ "low", "medium", - "high", - "xhigh", - "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "omitted", - "continuation": [ - "thinking_blocks" + "high" ], - "notes": [ - "Claude Opus 4.7 and newer Opus models require thinking.type = \"adaptive\" to enable thinking explicitly.", - "Manual budget_tokens requests return 400 on Claude Opus 4.7 and newer adaptive-only Opus models.", - "task_budget is separate from thinking control and should not be treated as a thinking budget." - ] + "visibility": "hidden" } }, "attachment": true, "open_weights": false, - "knowledge": "2026-01-31", - "release_date": "2026-04-16", - "last_updated": "2026-04-16", + "knowledge": "2024-05", + "release_date": "2025-04-16", + "last_updated": "2025-04-16", "type": "chat" }, { - "id": "anthropic/claude-sonnet-5", - "name": "Claude Sonnet 5", - "display_name": "Claude Sonnet 5", + "id": "openai/gpt-image-2", + "name": "GPT-Image-2", + "display_name": "GPT-Image-2", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ - "text" + "image" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 8192, + "output": 8192 }, "temperature": false, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" - ] - } + "supported": false }, "attachment": true, "open_weights": false, - "knowledge": "2026-01-31", - "release_date": "2026-06-30", - "last_updated": "2026-06-30", - "type": "chat" + "release_date": "2026-04-21", + "last_updated": "2026-04-21" }, { - "id": "anthropic/claude-sonnet-4-6", - "name": "Claude Sonnet 4.6", - "display_name": "Claude Sonnet 4.6", + "id": "fireworks-ai/kimi-k2.5-fw", + "name": "Kimi-K2.5-FW", + "display_name": "Kimi-K2.5-FW", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 64000 + "context": 262144, + "output": 16384 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { - "supported": true, - "default": false - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "mixed", - "budget": { - "min": 1024, - "unit": "tokens" - }, - "effort": "high", - "effort_options": [ - "low", - "medium", - "high", - "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Anthropic recommends adaptive thinking with effort for Claude 4.6; budget_tokens remains a deprecated compatibility path.", - "Anthropic API defaults effort to high; lower effort levels should be chosen per workload." - ] - } + "supported": false }, "attachment": true, - "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-02-17", - "last_updated": "2026-03-13", + "open_weights": true, + "knowledge": "2025-01", + "release_date": "2026-01-27", + "last_updated": "2026-01-27", "type": "chat" }, { - "id": "anthropic/claude-opus-4-6", - "name": "Claude Opus 4.6", - "display_name": "Claude Opus 4.6", + "id": "xai/grok-code-fast-1", + "name": "Grok Code Fast 1", + "display_name": "Grok Code Fast 1", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, + "context": 256000, "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "mixed", - "budget": { - "min": 1024, - "unit": "tokens" - }, - "effort": "high", - "effort_options": [ - "low", - "medium", - "high", - "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Anthropic recommends adaptive thinking with effort for Claude 4.6; budget_tokens remains a deprecated compatibility path.", - "Anthropic API defaults effort to high; lower effort levels should be chosen per workload." - ] + "supported": true } }, - "attachment": true, + "attachment": false, "open_weights": false, - "knowledge": "2025-05-31", - "release_date": "2026-02-05", - "last_updated": "2026-03-13", + "release_date": "2025-09-02", + "last_updated": "2025-09-02", "type": "chat" }, { - "id": "anthropic/claude-sonnet-4-5-20250929", - "name": "Claude Sonnet 4.5", - "display_name": "Claude Sonnet 4.5", + "id": "xai/grok-4.1-fast-non-reasoning", + "name": "Grok-4.1-Fast-Non-Reasoning", + "display_name": "Grok-4.1-Fast-Non-Reasoning", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 64000 + "context": 2000000, + "output": 30000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { - "supported": true, - "default": false - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "budget", - "budget": { - "min": 1024, - "unit": "tokens" - }, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", - "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." - ] - } + "supported": false }, "attachment": true, "open_weights": false, - "knowledge": "2025-07-31", - "release_date": "2025-09-29", - "last_updated": "2025-09-29", + "release_date": "2025-11-19", + "last_updated": "2025-11-19", "type": "chat" }, { - "id": "anthropic/claude-haiku-4-5", - "name": "Claude Haiku 4.5 (latest)", - "display_name": "Claude Haiku 4.5 (latest)", + "id": "xai/grok-4.1-fast-reasoning", + "name": "Grok-4.1-Fast-Reasoning", + "display_name": "Grok-4.1-Fast-Reasoning", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 64000 + "context": 2000000, + "output": 30000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": false - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "budget", - "budget": { - "min": 1024, - "unit": "tokens" - }, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", - "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." - ] - } + "default": true }, "attachment": true, "open_weights": false, - "knowledge": "2025-02-28", - "release_date": "2025-10-15", - "last_updated": "2025-10-15", + "release_date": "2025-11-19", + "last_updated": "2025-11-19", "type": "chat" }, { - "id": "anthropic/claude-haiku-4-5-20251001", - "name": "Claude Haiku 4.5", - "display_name": "Claude Haiku 4.5", + "id": "xai/grok-4-fast-non-reasoning", + "name": "Grok-4-Fast-Non-Reasoning", + "display_name": "Grok-4-Fast-Non-Reasoning", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 64000 + "context": 2000000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { - "supported": true, - "default": false - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "budget", - "budget": { - "min": 1024, - "unit": "tokens" - }, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", - "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." - ] - } + "supported": false }, "attachment": true, "open_weights": false, - "knowledge": "2025-02-28", - "release_date": "2025-10-15", - "last_updated": "2025-10-15", + "release_date": "2025-12-18", + "last_updated": "2025-12-18", "type": "chat" }, { - "id": "anthropic/claude-opus-4-8", - "name": "Claude Opus 4.8", - "display_name": "Claude Opus 4.8", + "id": "xai/grok-3-mini", + "name": "Grok 3 Mini", + "display_name": "Grok 3 Mini", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 131072, + "output": 8192 }, "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "high", "effort_options": [ "low", "medium", - "high", - "xhigh", - "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "omitted", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Claude Opus 4.7 and newer Opus models require thinking.type = \"adaptive\" to enable thinking explicitly.", - "Manual budget_tokens requests return 400 on Claude Opus 4.7 and newer adaptive-only Opus models.", - "task_budget is separate from thinking control and should not be treated as a thinking budget." + "high" ] } }, - "attachment": true, + "attachment": false, "open_weights": false, - "knowledge": "2026-01", - "release_date": "2026-05-28", - "last_updated": "2026-05-28", + "release_date": "2025-04-11", + "last_updated": "2025-04-11", "type": "chat" }, { - "id": "anthropic/claude-sonnet-4-5", - "name": "Claude Sonnet 4.5 (latest)", - "display_name": "Claude Sonnet 4.5 (latest)", + "id": "xai/grok-4-fast-reasoning", + "name": "Grok-4-Fast-Reasoning", + "display_name": "Grok-4-Fast-Reasoning", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 64000 + "context": 2000000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": false - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "budget", - "budget": { - "min": 1024, - "unit": "tokens" - }, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", - "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." - ] - } + "default": true }, "attachment": true, "open_weights": false, - "knowledge": "2025-07-31", - "release_date": "2025-09-29", - "last_updated": "2025-09-29", + "release_date": "2025-12-18", + "last_updated": "2025-12-18", "type": "chat" }, { - "id": "anthropic/claude-opus-4-5-20251101", - "name": "Claude Opus 4.5", - "display_name": "Claude Opus 4.5", + "id": "xai/grok-3", + "name": "Grok 3", + "display_name": "Grok 3", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 64000 + "context": 131072, + "output": 8192 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { - "supported": true, - "default": false - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "mixed", - "budget": { - "min": 1024, - "unit": "tokens" - }, - "effort": "high", - "effort_options": [ - "low", - "medium", - "high" - ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Claude Opus 4.5 uses manual thinking.type = \"enabled\" with budget_tokens; effort can be used alongside the thinking budget.", - "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header." - ] - } + "supported": false }, - "attachment": true, + "attachment": false, "open_weights": false, - "knowledge": "2025-05", - "release_date": "2025-11-24", - "last_updated": "2025-11-24", + "release_date": "2025-04-11", + "last_updated": "2025-04-11", "type": "chat" }, { - "id": "anthropic/claude-fable-5", - "name": "Claude Fable 5", - "display_name": "Claude Fable 5", + "id": "xai/grok-4", + "name": "Grok-4", + "display_name": "Grok-4", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, + "context": 256000, "output": 128000 }, "temperature": false, @@ -183372,54 +184250,31 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "high", - "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "omitted", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Adaptive thinking is always on for Claude Fable 5 and Claude Mythos 5; thinking.type = \"disabled\" is rejected.", - "Manual budget_tokens requests return 400 on Claude Fable 5 and Claude Mythos 5.", - "thinking.display defaults to omitted; set display to summarized to receive readable thinking summaries." - ] + "supported": true } }, "attachment": true, "open_weights": false, - "knowledge": "2026-01-31", - "release_date": "2026-06-09", - "last_updated": "2026-06-09", + "release_date": "2025-07-10", + "last_updated": "2025-07-10", "type": "chat" }, { - "id": "anthropic/claude-fable-5-1", - "name": "Claude Fable 5.1", - "display_name": "Claude Fable 5.1", + "id": "xai/grok-4.20-multi-agent", + "name": "Grok-4.20-Multi-Agent", + "display_name": "Grok-4.20-Multi-Agent", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 128000, + "output": 8192 }, "temperature": false, "tool_call": true, @@ -183429,41 +184284,22 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "high", "effort_options": [ - "low", "medium", - "high", - "xhigh", - "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "omitted", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Adaptive thinking is always on for Claude Fable 5 and Claude Mythos 5; thinking.type = \"disabled\" is rejected.", - "Manual budget_tokens requests return 400 on Claude Fable 5 and Claude Mythos 5.", - "thinking.display defaults to omitted; set display to summarized to receive readable thinking summaries." + "high" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-06", - "release_date": "2026-09-01", - "last_updated": "2026-09-01", + "release_date": "2026-03-13", + "last_updated": "2026-03-13", "type": "chat" }, { - "id": "groq/allam-2-7b", - "name": "ALLaM-2-7b", - "display_name": "ALLaM-2-7b", + "id": "empiriolabs/deepseek-v4-pro-el", + "name": "DeepSeek-V4-Pro-EL", + "display_name": "DeepSeek-V4-Pro-EL", "modalities": { "input": [ "text" @@ -183473,24 +184309,26 @@ ] }, "limit": { - "context": 4096, - "output": 4096 + "context": 1000000, + "output": 384000 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, "attachment": false, "open_weights": true, - "release_date": "2025-01-23", - "last_updated": "2025-01-23", + "knowledge": "2025-05", + "release_date": "2026-04-24", + "last_updated": "2026-04-24", "type": "chat" }, { - "id": "groq/openai/gpt-oss-20b", - "name": "GPT OSS 20B", - "display_name": "GPT OSS 20B", + "id": "empiriolabs/deepseek-v4-flash-el", + "name": "DeepSeek-V4-Flash-EL", + "display_name": "DeepSeek-V4-Flash-EL", "modalities": { "input": [ "text" @@ -183500,8 +184338,8 @@ ] }, "limit": { - "context": 131072, - "output": 65536 + "context": 1000000, + "output": 384000 }, "temperature": true, "tool_call": true, @@ -183509,376 +184347,261 @@ "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "effort_options": [ - "low", - "medium", - "high" - ] - } - }, "attachment": false, "open_weights": true, - "knowledge": "2024-06", - "release_date": "2025-08-05", - "last_updated": "2025-08-05", + "knowledge": "2025-05", + "release_date": "2026-04-24", + "last_updated": "2026-04-24", "type": "chat" }, { - "id": "groq/openai/gpt-oss-120b", - "name": "GPT OSS 120B", - "display_name": "GPT OSS 120B", + "id": "ideogramai/ideogram", + "name": "Ideogram", + "display_name": "Ideogram", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ - "text" + "image" ] }, "limit": { - "context": 131072, - "output": 65536 + "context": 150, + "output": 8192 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { - "supported": true, - "default": true + "supported": false }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "effort_options": [ - "low", - "medium", - "high" - ] - } + "attachment": true, + "open_weights": false, + "release_date": "2024-04-03", + "last_updated": "2024-04-03" + }, + { + "id": "ideogramai/ideogram-v2a", + "name": "Ideogram-v2a", + "display_name": "Ideogram-v2a", + "modalities": { + "input": [ + "text" + ], + "output": [ + "image" + ] + }, + "limit": { + "context": 150, + "output": 8192 + }, + "temperature": false, + "tool_call": true, + "reasoning": { + "supported": false }, "attachment": false, - "open_weights": true, - "knowledge": "2024-06", - "release_date": "2025-08-05", - "last_updated": "2025-08-05", - "type": "chat" + "open_weights": false, + "release_date": "2025-02-27", + "last_updated": "2025-02-27" }, { - "id": "groq/qwen/qwen3.8-27b", - "name": "Qwen3.8 27B", - "display_name": "Qwen3.8 27B", + "id": "ideogramai/ideogram-v2", + "name": "Ideogram-v2", + "display_name": "Ideogram-v2", "modalities": { "input": [ "text", "image" ], "output": [ - "text" + "image" ] }, "limit": { - "context": 131042, - "output": 16384 + "context": 150, + "output": 8192 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "none", - "low", - "medium", - "high" - ] - } + "supported": false }, "attachment": true, - "open_weights": true, - "release_date": "2026-08-14", - "last_updated": "2026-08-14", - "type": "chat" + "open_weights": false, + "release_date": "2024-08-21", + "last_updated": "2024-08-21" }, { - "id": "deepseek/deepseek-v4-pro", - "name": "DeepSeek V4 Pro", - "display_name": "DeepSeek V4 Pro", + "id": "ideogramai/ideogram-v2a-turbo", + "name": "Ideogram-v2a-Turbo", + "display_name": "Ideogram-v2a-Turbo", "modalities": { "input": [ "text" ], "output": [ - "text" + "image" ] }, "limit": { - "context": 1000000, - "output": 384000 + "context": 150, + "output": 8192 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "high", - "effort_options": [ - "low", - "high", - "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." - ] - } + "supported": false }, "attachment": false, - "open_weights": true, - "release_date": "2026-08-13", - "last_updated": "2026-08-22", - "type": "chat" + "open_weights": false, + "release_date": "2025-02-27", + "last_updated": "2025-02-27" }, { - "id": "deepseek/deepseek-flash", - "name": "DeepSeek V4.1 Flash", - "display_name": "DeepSeek V4.1 Flash", + "id": "elevenlabs/elevenlabs-v3", + "name": "ElevenLabs-v3", + "display_name": "ElevenLabs-v3", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ - "text" + "audio" ] }, "limit": { - "context": 1000000, - "output": 384000 + "context": 128000, + "output": 8192 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "low", - "high", - "max" - ] - } + "supported": false }, - "attachment": true, - "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-09-10", - "last_updated": "2026-09-10", - "type": "chat" + "attachment": false, + "open_weights": false, + "release_date": "2025-08-20", + "last_updated": "2025-08-20" }, { - "id": "google/gemini-3-flash-preview", - "name": "Gemini 3 Flash Preview", - "display_name": "Gemini 3 Flash Preview", + "id": "elevenlabs/elevenlabs-music", + "name": "ElevenLabs-Music", + "display_name": "ElevenLabs-Music", "modalities": { "input": [ - "text", - "image", - "video", - "audio", - "pdf" + "text" ], "output": [ - "text" + "audio" ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 2000, + "output": 8192 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "level", - "level": "high", - "level_options": [ - "minimal", - "low", - "medium", - "high" - ], - "summaries": true, - "visibility": "summary", - "continuation": [ - "thought_signatures" - ] - } + "supported": false }, - "attachment": true, + "attachment": false, "open_weights": false, - "knowledge": "2025-01", - "release_date": "2025-12-17", - "last_updated": "2025-12-17", + "release_date": "2025-08-29", + "last_updated": "2025-08-29", "type": "chat" }, { - "id": "google/gemini-3.5-flash-lite", - "name": "Gemini 3.5 Flash Lite", - "display_name": "Gemini 3.5 Flash Lite", + "id": "elevenlabs/elevenlabs-v2.5-turbo", + "name": "ElevenLabs-v2.5-Turbo", + "display_name": "ElevenLabs-v2.5-Turbo", "modalities": { "input": [ - "text", - "image", - "video", - "audio", - "pdf" + "text" ], "output": [ - "text" + "audio" ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 128000, + "output": 8192 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "effort_options": [ - "minimal", - "low", - "medium", - "high" - ] - } + "supported": false }, - "attachment": true, + "attachment": false, "open_weights": false, - "knowledge": "2026-03", - "release_date": "2026-07-21", - "last_updated": "2026-07-21", - "type": "chat" + "release_date": "2024-07-19", + "last_updated": "2024-07-19" }, { - "id": "google/gemma-4-31b-it", - "name": "Gemma 4 31B IT", - "display_name": "Gemma 4 31B IT", + "id": "runwayml/runway", + "name": "Runway", + "display_name": "Runway", "modalities": { "input": [ "text", "image" ], "output": [ - "text" + "video" ] }, "limit": { - "context": 262144, - "output": 32768 + "context": 256, + "output": 8192 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true - } + "supported": false }, "attachment": true, - "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-04-02", - "last_updated": "2026-04-02", - "type": "chat" + "open_weights": false, + "release_date": "2024-10-11", + "last_updated": "2024-10-11" }, - { - "id": "google/gemini-3.1-flash-lite", - "name": "Gemini 3.1 Flash Lite", - "display_name": "Gemini 3.1 Flash Lite", + { + "id": "runwayml/runway-gen-4-turbo", + "name": "Runway-Gen-4-Turbo", + "display_name": "Runway-Gen-4-Turbo", "modalities": { "input": [ "text", - "image", - "video", - "audio", - "pdf" + "image" ], "output": [ - "text" + "video" ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 256, + "output": 8192 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "effort_options": [ - "minimal", - "low", - "medium", - "high" - ] - } + "supported": false }, "attachment": true, "open_weights": false, - "knowledge": "2025-01", - "release_date": "2026-05-07", - "last_updated": "2026-05-07", - "type": "chat" + "release_date": "2025-05-09", + "last_updated": "2025-05-09" }, { - "id": "google/gemini-3.7-flash", - "name": "Gemini 3.7 Flash", - "display_name": "Gemini 3.7 Flash", + "id": "google/gemini-3.5-flash", + "name": "Gemini-3.5-Flash", + "display_name": "Gemini-3.5-Flash", "modalities": { "input": [ "text", "image", - "video", - "audio", - "pdf" + "video" ], "output": [ "text" @@ -183915,84 +184638,101 @@ }, "attachment": true, "open_weights": false, - "knowledge": "2026-03", - "release_date": "2026-08-13", - "last_updated": "2026-08-13", + "knowledge": "2025-01", + "release_date": "2026-05-19", + "last_updated": "2026-05-19", "type": "chat" }, { - "id": "google/gemma-4-26b-a4b-it", - "name": "Gemma 4 26B A4B IT", - "display_name": "Gemma 4 26B A4B IT", + "id": "google/veo-3", + "name": "Veo-3", + "display_name": "Veo-3", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ - "text" + "video" ] }, "limit": { - "context": 262144, - "output": 32768 + "context": 480, + "output": 8192 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true - } + "supported": false }, - "attachment": true, - "open_weights": true, - "release_date": "2026-04-02", - "last_updated": "2026-04-02", - "type": "chat" + "attachment": false, + "open_weights": false, + "release_date": "2025-05-21", + "last_updated": "2025-05-21" }, { - "id": "google/gemini-embedding-2", - "name": "Gemini Embedding 2", - "display_name": "Gemini Embedding 2", + "id": "google/veo-3-fast", + "name": "Veo-3-Fast", + "display_name": "Veo-3-Fast", "modalities": { "input": [ "text" ], "output": [ - "text" + "video" ] }, "limit": { - "context": 8192, - "output": 1 + "context": 480, + "output": 8192 }, "temperature": false, - "tool_call": false, + "tool_call": true, "reasoning": { "supported": false }, "attachment": false, "open_weights": false, - "knowledge": "2025-11", - "release_date": "2026-04-22", - "last_updated": "2026-04-22", - "type": "embedding" + "release_date": "2025-10-13", + "last_updated": "2025-10-13" }, { - "id": "google/gemini-3.1-pro-preview-customtools", - "name": "Gemini 3.1 Pro Preview Custom Tools", - "display_name": "Gemini 3.1 Pro Preview Custom Tools", + "id": "google/nano-banana-pro", + "name": "Nano-Banana-Pro", + "display_name": "Nano-Banana-Pro", + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "image" + ] + }, + "limit": { + "context": 65536, + "output": 8192 + }, + "temperature": false, + "tool_call": true, + "reasoning": { + "supported": false + }, + "attachment": true, + "open_weights": false, + "knowledge": "2025-01", + "release_date": "2025-11-20", + "last_updated": "2025-11-20" + }, + { + "id": "google/gemini-3.1-pro", + "name": "Gemini-3.1-Pro", + "display_name": "Gemini-3.1-Pro", "modalities": { "input": [ "text", "image", "video", - "audio", - "pdf" + "audio" ], "output": [ "text" @@ -184033,58 +184773,178 @@ "type": "chat" }, { - "id": "google/gemini-flash-latest", - "name": "Gemini Flash Latest", - "display_name": "Gemini Flash Latest", + "id": "google/imagen-3", + "name": "Imagen-3", + "display_name": "Imagen-3", + "modalities": { + "input": [ + "text" + ], + "output": [ + "image" + ] + }, + "limit": { + "context": 480, + "output": 8192 + }, + "temperature": false, + "tool_call": true, + "reasoning": { + "supported": false + }, + "attachment": false, + "open_weights": false, + "release_date": "2024-10-15", + "last_updated": "2024-10-15" + }, + { + "id": "google/imagen-3-fast", + "name": "Imagen-3-Fast", + "display_name": "Imagen-3-Fast", + "modalities": { + "input": [ + "text" + ], + "output": [ + "image" + ] + }, + "limit": { + "context": 480, + "output": 8192 + }, + "temperature": false, + "tool_call": true, + "reasoning": { + "supported": false + }, + "attachment": false, + "open_weights": false, + "release_date": "2024-10-17", + "last_updated": "2024-10-17" + }, + { + "id": "google/gemini-2.0-flash-lite", + "name": "Gemini-2.0-Flash-Lite", + "display_name": "Gemini-2.0-Flash-Lite", "modalities": { "input": [ "text", "image", "video", - "audio", - "pdf" + "audio" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 990000, + "output": 8192 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "low", - "medium", - "high" - ] - } + "supported": false }, "attachment": true, "open_weights": false, - "knowledge": "2026-03", - "release_date": "2026-08-13", - "last_updated": "2026-08-13", + "knowledge": "2024-06", + "release_date": "2024-12-11", + "last_updated": "2024-12-11", "type": "chat" }, { - "id": "google/gemini-3.1-pro-preview", - "name": "Gemini 3.1 Pro Preview", - "display_name": "Gemini 3.1 Pro Preview", + "id": "google/imagen-4-fast", + "name": "Imagen-4-Fast", + "display_name": "Imagen-4-Fast", + "modalities": { + "input": [ + "text" + ], + "output": [ + "image" + ] + }, + "limit": { + "context": 480, + "output": 8192 + }, + "temperature": false, + "tool_call": true, + "reasoning": { + "supported": false + }, + "attachment": false, + "open_weights": false, + "release_date": "2025-06-25", + "last_updated": "2025-06-25" + }, + { + "id": "google/lyria", + "name": "Lyria", + "display_name": "Lyria", + "modalities": { + "input": [ + "text" + ], + "output": [ + "audio" + ] + }, + "limit": { + "context": 8192, + "output": 8192 + }, + "temperature": false, + "tool_call": true, + "reasoning": { + "supported": false + }, + "attachment": false, + "open_weights": false, + "release_date": "2025-06-04", + "last_updated": "2025-06-04", + "type": "chat" + }, + { + "id": "google/veo-3.1-fast", + "name": "Veo-3.1-Fast", + "display_name": "Veo-3.1-Fast", + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "video" + ] + }, + "limit": { + "context": 480, + "output": 8192 + }, + "temperature": false, + "tool_call": true, + "reasoning": { + "supported": false + }, + "attachment": true, + "open_weights": false, + "release_date": "2025-10-15", + "last_updated": "2025-10-15" + }, + { + "id": "google/gemini-3.6-flash", + "name": "Gemini-3.6-Flash", + "display_name": "Gemini-3.6-Flash", "modalities": { "input": [ "text", "image", "video", - "audio", - "pdf" + "audio" ], "output": [ "text" @@ -184107,7 +184967,9 @@ "mode": "level", "level": "high", "level_options": [ + "minimal", "low", + "medium", "high" ], "summaries": true, @@ -184119,22 +184981,21 @@ }, "attachment": true, "open_weights": false, - "knowledge": "2025-01", - "release_date": "2026-02-19", - "last_updated": "2026-02-19", + "knowledge": "2026-03", + "release_date": "2026-07-21", + "last_updated": "2026-07-21", "type": "chat" }, { - "id": "google/gemini-3.8-flash", - "name": "Gemini 3.8 Flash", - "display_name": "Gemini 3.8 Flash", + "id": "google/gemini-3-flash", + "name": "Gemini-3-Flash", + "display_name": "Gemini-3-Flash", "modalities": { "input": [ "text", "image", "video", - "audio", - "pdf" + "audio" ], "output": [ "text" @@ -184171,21 +185032,21 @@ }, "attachment": true, "open_weights": false, - "release_date": "2026-09-02", - "last_updated": "2026-09-02", + "knowledge": "2025-01", + "release_date": "2025-12-17", + "last_updated": "2025-12-17", "type": "chat" }, { - "id": "google/gemini-3.6-flash", - "name": "Gemini 3.6 Flash", - "display_name": "Gemini 3.6 Flash", + "id": "google/gemini-3.8-flash", + "name": "Gemini-3.8-Flash", + "display_name": "Gemini-3.8-Flash", "modalities": { "input": [ "text", "image", "video", - "audio", - "pdf" + "audio" ], "output": [ "text" @@ -184222,60 +185083,59 @@ }, "attachment": true, "open_weights": false, - "knowledge": "2026-03", - "release_date": "2026-07-21", - "last_updated": "2026-07-21", + "release_date": "2026-09-02", + "last_updated": "2026-09-02", "type": "chat" }, { - "id": "google/gemini-embedding-001", - "name": "Gemini Embedding 001", - "display_name": "Gemini Embedding 001", + "id": "google/nano-banana", + "name": "Nano-Banana", + "display_name": "Nano-Banana", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ - "text" + "text", + "image" ] }, "limit": { - "context": 2048, - "output": 1 + "context": 65536, + "output": 8192 }, "temperature": false, - "tool_call": false, + "tool_call": true, "reasoning": { "supported": false }, - "attachment": false, + "attachment": true, "open_weights": false, - "knowledge": "2025-05", - "release_date": "2025-05-20", - "last_updated": "2025-05-20", - "type": "embedding" + "knowledge": "2024-06", + "release_date": "2025-08-26", + "last_updated": "2025-08-26" }, { - "id": "google/gemini-3.5-flash", - "name": "Gemini 3.5 Flash", - "display_name": "Gemini 3.5 Flash", + "id": "google/gemini-2.5-pro", + "name": "Gemini-2.5-Pro", + "display_name": "Gemini-2.5-Pro", "modalities": { "input": [ "text", "image", "video", - "audio", - "pdf" + "audio" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 1065535, + "output": 65535 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -184285,14 +185145,14 @@ "reasoning": { "supported": true, "default_enabled": true, - "mode": "level", - "level": "high", - "level_options": [ - "minimal", - "low", - "medium", - "high" - ], + "mode": "budget", + "budget": { + "default": -1, + "min": 128, + "max": 32768, + "auto": -1, + "unit": "tokens" + }, "summaries": true, "visibility": "summary", "continuation": [ @@ -184303,21 +185163,19 @@ "attachment": true, "open_weights": false, "knowledge": "2025-01", - "release_date": "2026-05-19", - "last_updated": "2026-05-19", + "release_date": "2025-06-17", + "last_updated": "2025-06-17", "type": "chat" }, { - "id": "google/gemini-flash-lite-latest", - "name": "Gemini Flash-Lite Latest", - "display_name": "Gemini Flash-Lite Latest", + "id": "google/gemini-deep-research", + "name": "gemini-deep-research", + "display_name": "gemini-deep-research", "modalities": { "input": [ "text", "image", - "video", - "audio", - "pdf" + "video" ], "output": [ "text" @@ -184325,179 +185183,172 @@ }, "limit": { "context": 1048576, - "output": 65536 + "output": 8192 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "minimal", - "low", - "medium", - "high" - ] - } - }, "attachment": true, "open_weights": false, - "knowledge": "2026-03", - "release_date": "2026-07-21", - "last_updated": "2026-07-21", + "release_date": "2025-12-11", + "last_updated": "2025-12-11", "type": "chat" }, { - "id": "mistral/zai-glm-5-3", - "name": "GLM-5.3", - "display_name": "GLM-5.3", + "id": "google/imagen-4", + "name": "Imagen-4", + "display_name": "Imagen-4", "modalities": { "input": [ "text" ], "output": [ - "text" + "image" ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 480, + "output": 8192 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "low", - "high", - "max" - ] - } + "supported": false }, "attachment": false, - "open_weights": true, - "release_date": "2026-08-14", - "last_updated": "2026-08-14", - "type": "chat" + "open_weights": false, + "release_date": "2025-05-22", + "last_updated": "2025-05-22" }, { - "id": "mistral/mistral-medium-latest", - "name": "Mistral Medium (latest)", - "display_name": "Mistral Medium (latest)", + "id": "google/veo-3.1", + "name": "Veo-3.1", + "display_name": "Veo-3.1", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ - "text" + "video" ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 480, + "output": 8192 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "none", - "high" - ] - } + "supported": false }, - "attachment": true, - "open_weights": true, - "release_date": "2026-04-29", - "last_updated": "2026-04-29", - "type": "chat" + "attachment": false, + "open_weights": false, + "release_date": "2025-10-15", + "last_updated": "2025-10-15" }, { - "id": "mistral/mistral-large-2512", - "name": "Mistral Large 3", - "display_name": "Mistral Large 3", + "id": "google/gemini-omni-1.1-flash", + "name": "Gemini-Omni-1.1-Flash", + "display_name": "Gemini-Omni-1.1-Flash", "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ - "text" + "video" ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 131072, + "output": 57920 }, - "temperature": true, - "tool_call": true, + "temperature": false, + "tool_call": false, "reasoning": { - "supported": false + "supported": true, + "default": true }, "attachment": true, - "open_weights": true, - "knowledge": "2024-11", - "release_date": "2025-12-02", - "last_updated": "2025-12-02", - "type": "chat" + "open_weights": false, + "release_date": "2026-08-27", + "last_updated": "2026-08-27" }, { - "id": "mistral/mistral-embed", - "name": "Mistral Embed", - "display_name": "Mistral Embed", + "id": "google/gemini-2.5-flash-lite", + "name": "Gemini-2.5-Flash-Lite", + "display_name": "Gemini-2.5-Flash-Lite", "modalities": { "input": [ - "text" + "text", + "image", + "video", + "audio" ], "output": [ "text" ] }, "limit": { - "context": 8000, - "output": 3072 + "context": 1024000, + "output": 64000 }, "temperature": false, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": false }, - "attachment": false, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": false, + "mode": "budget", + "budget": { + "default": -1, + "min": 512, + "max": 24576, + "auto": -1, + "unit": "tokens" + }, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thought_signatures" + ] + } + }, + "attachment": true, "open_weights": false, - "release_date": "2023-12-11", - "last_updated": "2023-12-11", - "type": "embedding" + "knowledge": "2025-01", + "release_date": "2025-06-17", + "last_updated": "2025-06-17", + "type": "chat" }, { - "id": "mistral/mistral-medium-2604", - "name": "Mistral Medium 3.5", - "display_name": "Mistral Medium 3.5", + "id": "google/gemini-2.5-flash", + "name": "Gemini-2.5-Flash", + "display_name": "Gemini-2.5-Flash", "modalities": { "input": [ "text", - "image" + "image", + "video", + "audio" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 1065535, + "output": 65535 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -184505,71 +185356,80 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "none", - "high" + "supported": true, + "default_enabled": true, + "mode": "budget", + "budget": { + "default": -1, + "min": 0, + "max": 24576, + "auto": -1, + "off": 0, + "unit": "tokens" + }, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thought_signatures" ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-04-29", - "last_updated": "2026-04-29", + "open_weights": false, + "knowledge": "2025-01", + "release_date": "2025-06-17", + "last_updated": "2025-06-17", "type": "chat" }, { - "id": "mistral/mistral-small-2603", - "name": "Mistral Small 4", - "display_name": "Mistral Small 4", + "id": "google/gemini-2.0-flash", + "name": "Gemini-2.0-Flash", + "display_name": "Gemini-2.0-Flash", "modalities": { "input": [ "text", - "image" + "image", + "video", + "audio" ], "output": [ "text" ] }, "limit": { - "context": 256000, - "output": 256000 + "context": 990000, + "output": 8192 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "none", - "high" - ] - } + "supported": false }, "attachment": true, - "open_weights": true, - "knowledge": "2025-06", - "release_date": "2026-03-16", - "last_updated": "2026-03-16", + "open_weights": false, + "knowledge": "2024-06", + "release_date": "2024-12-11", + "last_updated": "2024-12-11", "type": "chat" }, { - "id": "mistral/zai-glm-5-2", - "name": "GLM-5.2", - "display_name": "GLM-5.2", + "id": "google/gemini-3.7-flash", + "name": "Gemini-3.7-Flash", + "display_name": "Gemini-3.7-Flash", "modalities": { "input": [ - "text" + "text", + "image", + "video", + "audio" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 1048576, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -184579,51 +185439,60 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "none", - "high", - "max" + "supported": true, + "default_enabled": true, + "mode": "level", + "level": "high", + "level_options": [ + "minimal", + "low", + "medium", + "high" + ], + "summaries": true, + "visibility": "summary", + "continuation": [ + "thought_signatures" ] } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-06-13", - "last_updated": "2026-06-13", + "attachment": true, + "open_weights": false, + "knowledge": "2026-03", + "release_date": "2026-08-13", + "last_updated": "2026-08-13", "type": "chat" }, { - "id": "mistral/voxtral-small-latest", - "name": "Voxtral Small (latest)", - "display_name": "Voxtral Small (latest)", + "id": "google/imagen-4-ultra", + "name": "Imagen-4-Ultra", + "display_name": "Imagen-4-Ultra", "modalities": { "input": [ - "text", - "audio" + "text" ], "output": [ - "text" + "image" ] }, "limit": { - "context": 32000, - "output": 32000 + "context": 480, + "output": 8192 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": false }, - "attachment": true, - "open_weights": true, - "release_date": "2025-07-15", - "last_updated": "2025-07-15", - "type": "chat" + "attachment": false, + "open_weights": false, + "release_date": "2025-05-24", + "last_updated": "2025-05-24" }, { - "id": "mistral/mistral-large-latest", - "name": "Mistral Large (latest)", - "display_name": "Mistral Large (latest)", + "id": "google/gemma-4-31b", + "name": "Gemma-4-31B", + "display_name": "Gemma-4-31B", "modalities": { "input": [ "text", @@ -184635,36 +185504,38 @@ }, "limit": { "context": 262144, - "output": 262144 + "output": 8192 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": false }, "attachment": true, "open_weights": true, - "knowledge": "2024-11", - "release_date": "2024-11-01", - "last_updated": "2025-12-02", + "knowledge": "2025-01", + "release_date": "2026-04-02", + "last_updated": "2026-04-02", "type": "chat" }, { - "id": "mistral/mistral-small-latest", - "name": "Mistral Small (latest)", - "display_name": "Mistral Small (latest)", + "id": "google/gemini-3.1-flash-lite", + "name": "Gemini-3.1-Flash-Lite", + "display_name": "Gemini-3.1-Flash-Lite", "modalities": { "input": [ "text", - "image" + "image", + "video", + "audio" ], "output": [ "text" ] }, "limit": { - "context": 256000, - "output": 256000 + "context": 1048576, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -184674,28 +185545,57 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, "effort_options": [ - "none", + "minimal", + "low", "high" ] } }, "attachment": true, - "open_weights": true, - "knowledge": "2025-06", - "release_date": "2026-03-16", - "last_updated": "2026-03-16", + "open_weights": false, + "knowledge": "2025-01", + "release_date": "2026-05-07", + "last_updated": "2026-05-07", "type": "chat" }, { - "id": "moonshot/kimi-k3", - "name": "Kimi K3", - "display_name": "Kimi K3", + "id": "google/veo-2", + "name": "Veo-2", + "display_name": "Veo-2", + "modalities": { + "input": [ + "text" + ], + "output": [ + "video" + ] + }, + "limit": { + "context": 480, + "output": 8192 + }, + "temperature": false, + "tool_call": true, + "reasoning": { + "supported": false + }, + "attachment": false, + "open_weights": false, + "release_date": "2024-12-02", + "last_updated": "2024-12-02" + }, + { + "id": "google/gemini-3.5-flash-lite", + "name": "Gemini-3.5-Flash-Lite", + "display_name": "Gemini-3.5-Flash-Lite", "modalities": { "input": [ "text", "image", - "video" + "video", + "audio" ], "output": [ "text" @@ -184703,9 +185603,9 @@ }, "limit": { "context": 1048576, - "output": 131072 + "output": 65536 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -184715,71 +185615,62 @@ "reasoning": { "supported": true, "effort_options": [ + "minimal", "low", - "high", - "max" + "medium", + "high" ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-07-16", - "last_updated": "2026-07-16", + "open_weights": false, + "knowledge": "2026-03", + "release_date": "2026-07-21", + "last_updated": "2026-07-21", "type": "chat" }, { - "id": "moonshot/kimi-k2.6", - "name": "Kimi K2.6", - "display_name": "Kimi K2.6", + "id": "trytako/tako", + "name": "Tako", + "display_name": "Tako", "modalities": { "input": [ - "text", - "image", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 2048, + "output": 8192 }, "temperature": false, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true - } + "supported": false }, - "attachment": true, - "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-04-20", - "last_updated": "2026-04-20", + "attachment": false, + "open_weights": false, + "release_date": "2024-08-15", + "last_updated": "2024-08-15", "type": "chat" }, { - "id": "moonshot/kimi-k2.7-code", - "name": "Kimi K2.7 Code", - "display_name": "Kimi K2.7 Code", + "id": "poetools/claude-code", + "name": "claude-code", + "display_name": "claude-code", "modalities": { "input": [ - "text", - "image", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 8192, + "output": 8192 }, "temperature": false, "tool_call": true, @@ -184789,702 +185680,835 @@ }, "extra_capabilities": { "reasoning": { - "supported": true + "effort_options": [ + "none", + "low", + "medium", + "high", + "xhigh", + "max" + ] } }, - "attachment": true, - "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-06-12", - "last_updated": "2026-06-12", + "attachment": false, + "open_weights": false, + "release_date": "2025-11-27", + "last_updated": "2025-11-27", "type": "chat" }, { - "id": "moonshot/kimi-k2.7-code-highspeed", - "name": "Kimi K2.7 Code Highspeed", - "display_name": "Kimi K2.7 Code Highspeed", + "id": "anthropic/claude-haiku-4.5", + "name": "Claude-Haiku-4.5", + "display_name": "Claude-Haiku-4.5", "modalities": { "input": [ "text", "image", - "video" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 192000, + "output": 64000 }, "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { - "supported": true + "supported": true, + "default_enabled": false, + "mode": "budget", + "budget": { + "min": 1024, + "unit": "tokens" + }, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", + "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." + ] } }, "attachment": true, - "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-06-12", - "last_updated": "2026-06-12", - "type": "chat" - } - ] - }, - "inference": { - "id": "inference", - "name": "Inference", - "display_name": "Inference", - "api": "https://inference.net/v1", - "doc": "https://inference.net/models", - "models": [ - { - "id": "osmosis/osmosis-structure-0.6b", - "name": "Osmosis Structure 0.6B", - "display_name": "Osmosis Structure 0.6B", - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 4000, - "output": 2048 - }, - "temperature": true, - "tool_call": true, - "reasoning": { - "supported": false - }, - "attachment": false, - "open_weights": true, - "knowledge": "2024-12", - "release_date": "2025-01-01", - "last_updated": "2025-01-01", + "open_weights": false, + "knowledge": "2025-02-28", + "release_date": "2025-10-15", + "last_updated": "2025-10-15", "type": "chat" }, { - "id": "google/gemma-3", - "name": "Google Gemma 3", - "display_name": "Google Gemma 3", + "id": "anthropic/claude-sonnet-4.5", + "name": "Claude-Sonnet-4.5", + "display_name": "Claude-Sonnet-4.5", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 125000, - "output": 4096 + "context": 983040, + "output": 32768 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": false + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": false, + "mode": "budget", + "budget": { + "min": 1024, + "unit": "tokens" + }, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", + "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." + ] + } }, "attachment": true, - "open_weights": true, - "knowledge": "2024-12", - "release_date": "2025-01-01", - "last_updated": "2025-01-01", + "open_weights": false, + "knowledge": "2025-07-31", + "release_date": "2025-09-29", + "last_updated": "2025-09-29", "type": "chat" }, { - "id": "mistral/mistral-nemo-12b-instruct", - "name": "Mistral Nemo 12B Instruct", - "display_name": "Mistral Nemo 12B Instruct", + "id": "anthropic/claude-opus-4.5", + "name": "Claude-Opus-4.5", + "display_name": "Claude-Opus-4.5", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 16000, - "output": 4096 + "context": 196608, + "output": 64000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": false }, - "attachment": false, - "open_weights": true, - "knowledge": "2024-12", - "release_date": "2024-07-18", - "last_updated": "2024-07-18", + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": false, + "mode": "mixed", + "budget": { + "min": 1024, + "unit": "tokens" + }, + "effort": "high", + "effort_options": [ + "low", + "medium", + "high" + ], + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Claude Opus 4.5 uses manual thinking.type = \"enabled\" with budget_tokens; effort can be used alongside the thinking budget.", + "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header." + ] + } + }, + "attachment": true, + "open_weights": false, + "knowledge": "2025-05", + "release_date": "2025-11-24", + "last_updated": "2025-11-24", "type": "chat" }, { - "id": "qwen/qwen-2.5-7b-vision-instruct", - "name": "Qwen 2.5 7B Vision Instruct", - "display_name": "Qwen 2.5 7B Vision Instruct", + "id": "anthropic/claude-opus-4.6", + "name": "Claude-Opus-4.6", + "display_name": "Claude-Opus-4.6", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 125000, - "output": 4096 + "context": 983040, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": false + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": false, + "mode": "mixed", + "budget": { + "min": 1024, + "unit": "tokens" + }, + "effort": "high", + "effort_options": [ + "low", + "medium", + "high", + "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Anthropic recommends adaptive thinking with effort for Claude 4.6; budget_tokens remains a deprecated compatibility path.", + "Anthropic API defaults effort to high; lower effort levels should be chosen per workload." + ] + } }, "attachment": true, - "open_weights": true, - "knowledge": "2024-12", - "release_date": "2025-01-01", - "last_updated": "2025-01-01", + "open_weights": false, + "knowledge": "2025-05-31", + "release_date": "2026-02-05", + "last_updated": "2026-03-13", "type": "chat" }, { - "id": "qwen/qwen3-embedding-4b", - "name": "Qwen 3 Embedding 4B", - "display_name": "Qwen 3 Embedding 4B", + "id": "anthropic/claude-opus-4.7", + "name": "Claude-Opus-4.7", + "display_name": "Claude-Opus-4.7", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 32000, - "output": 2048 + "context": 1048576, + "output": 128000 }, "temperature": false, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": false }, - "attachment": false, - "open_weights": true, - "knowledge": "2024-12", - "release_date": "2025-06-05", - "last_updated": "2025-06-05", - "type": "embedding" + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": false, + "mode": "effort", + "effort": "high", + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "omitted", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Claude Opus 4.7 and newer Opus models require thinking.type = \"adaptive\" to enable thinking explicitly.", + "Manual budget_tokens requests return 400 on Claude Opus 4.7 and newer adaptive-only Opus models.", + "task_budget is separate from thinking control and should not be treated as a thinking budget." + ] + } + }, + "attachment": true, + "open_weights": false, + "knowledge": "2026-01-31", + "release_date": "2026-04-16", + "last_updated": "2026-04-16", + "type": "chat" }, { - "id": "meta/llama-3.2-11b-vision-instruct", - "name": "Llama 3.2 11B Vision Instruct", - "display_name": "Llama 3.2 11B Vision Instruct", + "id": "anthropic/claude-opus-4.8", + "name": "Claude-Opus-4.8", + "display_name": "Claude-Opus-4.8", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 16000, - "output": 4096 + "context": 1048576, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": false + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": false, + "mode": "effort", + "effort": "high", + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "omitted", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Claude Opus 4.7 and newer Opus models require thinking.type = \"adaptive\" to enable thinking explicitly.", + "Manual budget_tokens requests return 400 on Claude Opus 4.7 and newer adaptive-only Opus models.", + "task_budget is separate from thinking control and should not be treated as a thinking budget." + ] + } }, "attachment": true, - "open_weights": true, - "knowledge": "2023-12", - "release_date": "2024-09-25", - "last_updated": "2024-09-25", + "open_weights": false, + "knowledge": "2026-01", + "release_date": "2026-05-28", + "last_updated": "2026-05-28", "type": "chat" }, { - "id": "meta/llama-3.2-1b-instruct", - "name": "Llama 3.2 1B Instruct", - "display_name": "Llama 3.2 1B Instruct", + "id": "anthropic/claude-opus-4.1", + "name": "Claude-Opus-4.1", + "display_name": "Claude-Opus-4.1", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 16000, - "output": 4096 + "context": 196608, + "output": 32000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": false }, - "attachment": false, - "open_weights": true, - "knowledge": "2023-12", - "release_date": "2024-09-25", - "last_updated": "2024-09-25", + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": false, + "mode": "budget", + "budget": { + "min": 1024, + "unit": "tokens" + }, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", + "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." + ] + } + }, + "attachment": true, + "open_weights": false, + "knowledge": "2025-03-31", + "release_date": "2025-08-05", + "last_updated": "2025-08-05", "type": "chat" }, { - "id": "meta/llama-3.2-3b-instruct", - "name": "Llama 3.2 3B Instruct", - "display_name": "Llama 3.2 3B Instruct", + "id": "anthropic/claude-haiku-3.5", + "name": "Claude-Haiku-3.5", + "display_name": "Claude-Haiku-3.5", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 16000, - "output": 4096 + "context": 189096, + "output": 8192 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": false }, - "attachment": false, - "open_weights": true, - "knowledge": "2023-12", - "release_date": "2025-01-01", - "last_updated": "2025-01-01", + "attachment": true, + "open_weights": false, + "knowledge": "2024-07-31", + "release_date": "2024-10-22", + "last_updated": "2024-10-22", "type": "chat" }, { - "id": "meta/llama-3.1-8b-instruct", - "name": "Llama 3.1 8B Instruct", - "display_name": "Llama 3.1 8B Instruct", + "id": "anthropic/claude-sonnet-3.7", + "name": "Claude-Sonnet-3.7", + "display_name": "Claude-Sonnet-3.7", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 16000, - "output": 4096 + "context": 196608, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, - "open_weights": true, - "knowledge": "2023-12", - "release_date": "2024-07-23", - "last_updated": "2024-07-23", + "attachment": true, + "open_weights": false, + "knowledge": "2024-10-31", + "release_date": "2025-02-19", + "last_updated": "2025-02-19", "type": "chat" - } - ] - }, - "zenifra": { - "id": "zenifra", - "name": "Zenifra", - "display_name": "Zenifra", - "api": "https://ai.zenifra.com/v1", - "doc": "https://docs.zenifra.com", - "models": [ + }, { - "id": "alibaba/qwen3.6-35b-a3b", - "name": "Qwen3.6 35B-A3B", - "display_name": "Qwen3.6 35B-A3B", + "id": "anthropic/claude-opus-4", + "name": "Claude-Opus-4", + "display_name": "Claude-Opus-4", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 65536 + "context": 192512, + "output": 28672 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": false, + "mode": "budget", + "budget": { + "min": 1024, + "unit": "tokens" + }, "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ "thinking_blocks" + ], + "notes": [ + "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", + "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." ] } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-04-17", - "last_updated": "2026-04-17", + "attachment": true, + "open_weights": false, + "knowledge": "2025-03-31", + "release_date": "2025-05-22", + "last_updated": "2025-05-22", "type": "chat" - } - ] - }, - "stepfun-ai-step-plan": { - "id": "stepfun-ai-step-plan", - "name": "StepFun Step Plan (Global)", - "display_name": "StepFun Step Plan (Global)", - "api": "https://api.stepfun.ai/step_plan/v1", - "doc": "https://platform.stepfun.ai/docs/en/step-plan/integrations/reasoning-api", - "models": [ + }, { - "id": "step-3.7-flash", - "name": "Step 3.7 Flash", - "display_name": "Step 3.7 Flash", + "id": "anthropic/claude-sonnet-4.6", + "name": "Claude-Sonnet-4.6", + "display_name": "Claude-Sonnet-4.6", "modalities": { "input": [ "text", "image", - "video" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 256000, - "output": 256000 + "context": 983040, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": false, + "mode": "mixed", + "budget": { + "min": 1024, + "unit": "tokens" + }, + "effort": "high", + "effort_options": [ + "low", + "medium", + "high", + "max" + ], "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ "thinking_blocks" ], - "effort_options": [ - "low", - "medium", - "high" + "notes": [ + "Anthropic recommends adaptive thinking with effort for Claude 4.6; budget_tokens remains a deprecated compatibility path.", + "Anthropic API defaults effort to high; lower effort levels should be chosen per workload." ] } }, "attachment": true, - "open_weights": true, - "knowledge": "2026-03-01", - "release_date": "2026-05-29", - "last_updated": "2026-05-29", + "open_weights": false, + "knowledge": "2025-08-31", + "release_date": "2026-02-17", + "last_updated": "2026-03-13", "type": "chat" }, { - "id": "step-3.5-flash-2603", - "name": "Step 3.5 Flash 2603", - "display_name": "Step 3.5 Flash 2603", + "id": "anthropic/claude-sonnet-3.5-june", + "name": "Claude-Sonnet-3.5-June", + "display_name": "Claude-Sonnet-3.5-June", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 256000, - "output": 256000 + "context": 189096, + "output": 8192 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "effort_options": [ - "low", - "high" - ] - } + "supported": false }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-04-02", - "last_updated": "2026-04-02", + "attachment": true, + "open_weights": false, + "release_date": "2024-11-18", + "last_updated": "2024-11-18", "type": "chat" }, { - "id": "step-5-preview", - "name": "Step 5 Preview", - "display_name": "Step 5 Preview", + "id": "anthropic/claude-sonnet-4", + "name": "Claude-Sonnet-4", + "display_name": "Claude-Sonnet-4", "modalities": { "input": [ "text", "image", - "video" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 384000 + "context": 983040, + "output": 64000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, - "mode": "effort", - "effort": "medium", - "effort_options": [ - "low", - "medium", - "high" - ], + "default_enabled": false, + "mode": "budget", + "budget": { + "min": 1024, + "unit": "tokens" + }, "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ "thinking_blocks" + ], + "notes": [ + "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", + "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-09-16", - "last_updated": "2026-09-20", + "open_weights": false, + "knowledge": "2025-03-31", + "release_date": "2025-05-22", + "last_updated": "2025-05-22", "type": "chat" }, { - "id": "step-3.5-flash", - "name": "Step 3.5 Flash", - "display_name": "Step 3.5 Flash", + "id": "anthropic/claude-sonnet-3.5", + "name": "Claude-Sonnet-3.5", + "display_name": "Claude-Sonnet-3.5", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 256000, - "output": 256000 + "context": 189096, + "output": 8192 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "effort_options": [ - "low", - "high" - ] - } + "supported": false }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-01-29", - "last_updated": "2026-02-13", + "attachment": true, + "open_weights": false, + "knowledge": "2024-04-30", + "release_date": "2024-10-22", + "last_updated": "2024-10-22", "type": "chat" - } - ] - }, - "xiaomi-token-plan-cn": { - "id": "xiaomi-token-plan-cn", - "name": "Xiaomi Token Plan (China)", - "display_name": "Xiaomi Token Plan (China)", - "api": "https://token-plan-cn.xiaomimimo.com/v1", - "doc": "https://platform.xiaomimimo.com/#/docs", - "models": [ + }, { - "id": "mimo-v2.5-tts-voiceclone", - "name": "MiMo-V2.5-TTS-VoiceClone", - "display_name": "MiMo-V2.5-TTS-VoiceClone", + "id": "anthropic/claude-haiku-3", + "name": "Claude-Haiku-3", + "display_name": "Claude-Haiku-3", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ - "audio" + "text" ] }, "limit": { - "context": 8192, + "context": 189096, "output": 8192 }, "temperature": false, - "tool_call": false, + "tool_call": true, "reasoning": { "supported": false }, - "attachment": false, + "attachment": true, "open_weights": false, - "release_date": "2026-04-22", - "last_updated": "2026-04-22" + "knowledge": "2023-08-31", + "release_date": "2024-03-13", + "last_updated": "2024-03-13", + "type": "chat" }, { - "id": "mimo-v2.5-tts-voicedesign", - "name": "MiMo-V2.5-TTS-VoiceDesign", - "display_name": "MiMo-V2.5-TTS-VoiceDesign", + "id": "novita/kimi-k2-thinking", + "name": "kimi-k2-thinking", + "display_name": "kimi-k2-thinking", "modalities": { "input": [ "text" ], "output": [ - "audio" + "text" ] }, "limit": { - "context": 8192, + "context": 256000, "output": 8192 }, "temperature": false, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } }, "attachment": false, - "open_weights": false, - "release_date": "2026-04-22", - "last_updated": "2026-04-22" + "open_weights": true, + "knowledge": "2024-08", + "release_date": "2025-11-06", + "last_updated": "2025-11-06", + "type": "chat" }, { - "id": "mimo-v2.5-tts", - "name": "MiMo-V2.5-TTS", - "display_name": "MiMo-V2.5-TTS", + "id": "novita/glm-4.7-n", + "name": "glm-4.7-n", + "display_name": "glm-4.7-n", "modalities": { "input": [ "text" ], "output": [ - "audio" + "text" ] }, "limit": { - "context": 8192, - "output": 8192 + "context": 205000, + "output": 131072 }, "temperature": false, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, "attachment": false, - "open_weights": false, - "release_date": "2026-04-22", - "last_updated": "2026-04-22" + "open_weights": true, + "knowledge": "2025-04", + "release_date": "2025-12-22", + "last_updated": "2025-12-22", + "type": "chat" }, { - "id": "mimo-v2.6-pro", - "name": "MiMo-V2.6-Pro", - "display_name": "MiMo-V2.6-Pro", + "id": "novita/glm-4.6v", + "name": "glm-4.6v", + "display_name": "glm-4.6v", "modalities": { "input": [ "text", - "image", - "audio", - "video" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 131000, + "output": 32768 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } - }, "attachment": true, "open_weights": true, - "release_date": "2026-09-22", - "last_updated": "2026-09-22", + "knowledge": "2025-04", + "release_date": "2025-12-08", + "last_updated": "2025-12-08", "type": "chat" }, { - "id": "mimo-v2.6-flash", - "name": "MiMo-V2.6-Flash", - "display_name": "MiMo-V2.6-Flash", + "id": "novita/glm-4.7", + "name": "glm-4.7", + "display_name": "glm-4.7", "modalities": { "input": [ - "text", - "image", - "audio", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, + "context": 205000, "output": 131072 }, "temperature": true, @@ -185504,32 +186528,30 @@ ] } }, - "attachment": true, + "attachment": false, "open_weights": true, - "release_date": "2026-09-22", - "last_updated": "2026-09-22", + "knowledge": "2025-04", + "release_date": "2025-12-22", + "last_updated": "2025-12-22", "type": "chat" }, { - "id": "mimo-v2.5", - "name": "MiMo-V2.5", - "display_name": "MiMo-V2.5", + "id": "novita/glm-4.6", + "name": "GLM-4.6", + "display_name": "GLM-4.6", "modalities": { "input": [ - "text", - "image", - "audio", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 8192, + "output": 8192 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -185537,26 +186559,20 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] + "supported": true } }, - "attachment": true, + "attachment": false, "open_weights": true, - "knowledge": "2024-12", - "release_date": "2026-04-22", - "last_updated": "2026-04-22", + "knowledge": "2025-04", + "release_date": "2025-09-30", + "last_updated": "2025-09-30", "type": "chat" }, { - "id": "mimo-v2.5-pro", - "name": "MiMo-V2.5-Pro", - "display_name": "MiMo-V2.5-Pro", + "id": "novita/deepseek-v3.2", + "name": "DeepSeek-V3.2", + "display_name": "DeepSeek-V3.2", "modalities": { "input": [ "text" @@ -185566,8 +186582,8 @@ ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 128000, + "output": 8192 }, "temperature": true, "tool_call": true, @@ -185577,47 +186593,30 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] + "supported": true } }, "attachment": false, "open_weights": true, - "knowledge": "2024-12", - "release_date": "2026-04-22", - "last_updated": "2026-04-22", + "knowledge": "2024-07", + "release_date": "2025-12-01", + "last_updated": "2025-12-01", "type": "chat" - } - ] - }, - "trustedrouter": { - "id": "trustedrouter", - "name": "TrustedRouter", - "display_name": "TrustedRouter", - "api": "https://api.trustedrouter.com/v1", - "doc": "https://trustedrouter.com/docs", - "models": [ + }, { - "id": "trustedrouter/cheap", - "name": "Cheap", - "display_name": "Cheap", + "id": "novita/glm-5", + "name": "GLM-5", + "display_name": "GLM-5", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, + "context": 205000, "output": 131072 }, "temperature": true, @@ -185628,39 +186627,38 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "none", - "low", - "medium", - "high" + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, - "attachment": true, - "open_weights": false, - "release_date": "2026-05-01", - "last_updated": "2026-06-27", + "attachment": false, + "open_weights": true, + "release_date": "2026-02-12", + "last_updated": "2026-02-12", "type": "chat" }, { - "id": "trustedrouter/e2e", - "name": "End-to-End Encrypted", - "display_name": "End-to-End Encrypted", + "id": "novita/glm-4.7-flash", + "name": "glm-4.7-flash", + "display_name": "glm-4.7-flash", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 200000, + "output": 65500 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -185668,39 +186666,33 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "none", - "low", - "medium", - "high" - ] + "supported": true } }, - "attachment": true, - "open_weights": false, - "release_date": "2026-06-01", - "last_updated": "2026-06-27", + "attachment": false, + "open_weights": true, + "knowledge": "2025-04", + "release_date": "2026-01-19", + "last_updated": "2026-01-19", "type": "chat" }, { - "id": "trustedrouter/synth-code", - "name": "Synth Code", - "display_name": "Synth Code", + "id": "novita/minimax-m2.1", + "name": "minimax-m2.1", + "display_name": "minimax-m2.1", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, + "context": 205000, "output": 131072 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -185708,37 +186700,32 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "none", - "low", - "medium", - "high" - ] + "supported": true } }, - "attachment": true, - "open_weights": false, - "release_date": "2026-06-20", - "last_updated": "2026-06-27", + "attachment": false, + "open_weights": true, + "release_date": "2025-12-23", + "last_updated": "2025-12-23", "type": "chat" }, { - "id": "trustedrouter/auto", - "name": "Auto", - "display_name": "Auto", + "id": "novita/kimi-k2.5", + "name": "Kimi-K2.5", + "display_name": "Kimi-K2.5", "modalities": { "input": [ "text", "image", - "pdf" + "video" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 128000, + "output": 262144 }, "temperature": true, "tool_call": true, @@ -185749,37 +186736,38 @@ "extra_capabilities": { "reasoning": { "supported": true, - "effort_options": [ - "none", - "low", - "medium", - "high" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, "attachment": true, - "open_weights": false, - "release_date": "2026-05-01", - "last_updated": "2026-06-27", + "open_weights": true, + "knowledge": "2025-01", + "release_date": "2026-01-27", + "last_updated": "2026-01-27", "type": "chat" }, { - "id": "trustedrouter/synth", - "name": "Synth", - "display_name": "Synth", + "id": "novita/kimi-k2.6", + "name": "Kimi-K2.6", + "display_name": "Kimi-K2.6", "modalities": { "input": [ "text", "image", - "pdf" + "video" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 262144, + "output": 262144 }, "temperature": true, "tool_call": true, @@ -185789,154 +186777,105 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "none", - "low", - "medium", - "high" - ] + "supported": true } }, "attachment": true, - "open_weights": false, - "release_date": "2026-06-20", - "last_updated": "2026-06-27", + "open_weights": true, + "knowledge": "2025-04", + "release_date": "2026-04-20", + "last_updated": "2026-04-20", "type": "chat" }, { - "id": "trustedrouter/zdr", - "name": "Zero Data Retention", - "display_name": "Zero Data Retention", + "id": "cerebras/qwen3-235b-2507-cs", + "name": "qwen3-235b-2507-cs", + "display_name": "qwen3-235b-2507-cs", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 8192, + "output": 8192 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "none", - "low", - "medium", - "high" - ] - } - }, - "attachment": true, - "open_weights": false, - "release_date": "2026-06-01", - "last_updated": "2026-06-27", + "attachment": false, + "open_weights": true, + "release_date": "2025-07-21", + "last_updated": "2025-07-21", "type": "chat" }, { - "id": "trustedrouter/fast", - "name": "Fast", - "display_name": "Fast", + "id": "cerebras/qwen3-32b-cs", + "name": "qwen3-32b-cs", + "display_name": "qwen3-32b-cs", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 8192, + "output": 8192 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "none", - "low", - "medium", - "high" - ] - } - }, - "attachment": true, - "open_weights": false, - "release_date": "2026-06-01", - "last_updated": "2026-06-27", + "attachment": false, + "open_weights": true, + "knowledge": "2025-04", + "release_date": "2025-04-28", + "last_updated": "2025-04-28", "type": "chat" - } - ] - }, - "minimax-cn": { - "id": "minimax-cn", - "name": "MiniMax (minimax.cn)", - "display_name": "MiniMax (minimax.cn)", - "api": "https://api.minimax.cn/anthropic/v1", - "doc": "https://platform.minimaxi.com/docs/guides/quickstart", - "models": [ + }, { - "id": "MiniMax-M3.1-Flash", - "name": "MiniMax-M3.1-Flash", - "display_name": "MiniMax-M3.1-Flash", + "id": "cerebras/llama-3.3-70b-cs", + "name": "llama-3.3-70b-cs", + "display_name": "llama-3.3-70b-cs", "modalities": { "input": [ - "text", - "image", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 524288 + "context": 8192, + "output": 8192 }, - "temperature": true, - "tool_call": true, + "temperature": false, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" - ] - } + "supported": false }, - "attachment": true, - "open_weights": false, - "release_date": "2026-10-19", - "last_updated": "2026-10-19", + "attachment": false, + "open_weights": true, + "knowledge": "2023-12", + "release_date": "2024-12-06", + "last_updated": "2024-12-06", "type": "chat" }, { - "id": "MiniMax-M2.5-highspeed", - "name": "MiniMax-M2.5-highspeed", - "display_name": "MiniMax-M2.5-highspeed", + "id": "cerebras/gpt-oss-120b-cs", + "name": "GPT-OSS-120B-CS", + "display_name": "GPT-OSS-120B-CS", "modalities": { "input": [ "text" @@ -185946,10 +186885,10 @@ ] }, "limit": { - "context": 204800, - "output": 131072 + "context": 128000, + "output": 8192 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -185957,19 +186896,24 @@ }, "extra_capabilities": { "reasoning": { - "supported": true + "effort_options": [ + "low", + "medium", + "high" + ] } }, "attachment": false, "open_weights": true, - "release_date": "2026-02-13", - "last_updated": "2026-02-13", + "knowledge": "2024-06", + "release_date": "2025-08-05", + "last_updated": "2025-08-05", "type": "chat" }, { - "id": "MiniMax-M2.7-highspeed", - "name": "MiniMax-M2.7-highspeed", - "display_name": "MiniMax-M2.7-highspeed", + "id": "cerebras/llama-3.1-8b-cs", + "name": "Llama-3.1-8B-CS", + "display_name": "Llama-3.1-8B-CS", "modalities": { "input": [ "text" @@ -185979,142 +186923,126 @@ ] }, "limit": { - "context": 204800, - "output": 131072 + "context": 128000, + "output": 8192 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } + "supported": false }, "attachment": false, "open_weights": true, - "release_date": "2026-03-18", - "last_updated": "2026-03-18", + "knowledge": "2023-12", + "release_date": "2024-07-23", + "last_updated": "2024-07-23", "type": "chat" }, { - "id": "MiniMax-M2", - "name": "MiniMax-M2", - "display_name": "MiniMax-M2", + "id": "topazlabs-co/topazlabs", + "name": "TopazLabs", + "display_name": "TopazLabs", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ - "text" + "image" ] }, "limit": { - "context": 204800, - "output": 131072 + "context": 204, + "output": 8192 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { - "supported": true, - "default": true + "supported": false }, - "attachment": false, - "open_weights": true, - "release_date": "2025-10-27", - "last_updated": "2025-10-27", - "type": "chat" + "attachment": true, + "open_weights": false, + "release_date": "2024-12-03", + "last_updated": "2024-12-03" }, { - "id": "MiniMax-M3", - "name": "MiniMax-M3", - "display_name": "MiniMax-M3", + "id": "stabilityai/stablediffusionxl", + "name": "StableDiffusionXL", + "display_name": "StableDiffusionXL", "modalities": { "input": [ "text", - "image", - "video" + "image" ], "output": [ - "text" + "image" ] }, "limit": { - "context": 1000000, - "output": 524288 + "context": 200, + "output": 8192 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true - } + "supported": false }, "attachment": true, - "open_weights": true, - "release_date": "2026-06-01", - "last_updated": "2026-06-25", - "type": "chat" + "open_weights": false, + "release_date": "2023-07-09", + "last_updated": "2023-07-09" }, { - "id": "MiniMax-M2.1", - "name": "MiniMax-M2.1", - "display_name": "MiniMax-M2.1", + "id": "lumalabs/ray2", + "name": "Ray2", + "display_name": "Ray2", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ - "text" + "video" ] }, "limit": { - "context": 204800, - "output": 131072 + "context": 5000, + "output": 8192 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true - } + "supported": false }, - "attachment": false, - "open_weights": true, - "release_date": "2025-12-23", - "last_updated": "2025-12-23", - "type": "chat" - }, + "attachment": true, + "open_weights": false, + "release_date": "2025-02-20", + "last_updated": "2025-02-20" + } + ] + }, + "nan": { + "id": "nan", + "name": "NaN", + "display_name": "NaN", + "api": "https://api.nan.builders/v1", + "doc": "https://nan.builders/docs/models", + "models": [ { - "id": "MiniMax-M2.7", - "name": "MiniMax-M2.7", - "display_name": "MiniMax-M2.7", + "id": "glm5.3-flash", + "name": "GLM-5.3-Flash", + "display_name": "GLM-5.3-Flash", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 204800, + "context": 1000000, "output": 131072 }, "temperature": true, @@ -186125,36 +187053,38 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "minimal", + "low", + "medium", + "high", + "xhigh", + "max" ] } }, - "attachment": false, + "attachment": true, "open_weights": true, - "release_date": "2026-03-18", - "last_updated": "2026-03-18", + "release_date": "2026-08-26", + "last_updated": "2026-08-26", "type": "chat" }, { - "id": "MiniMax-M2.5", - "name": "MiniMax-M2.5", - "display_name": "MiniMax-M2.5", + "id": "qwen3.6", + "name": "Qwen3.6 35B-A3B", + "display_name": "Qwen3.6 35B-A3B", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 204800, - "output": 131072 + "context": 262144, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -186164,101 +187094,123 @@ }, "extra_capabilities": { "reasoning": { - "supported": true + "effort_options": [ + "none", + "low", + "medium", + "high", + "max" + ] } }, - "attachment": false, + "attachment": true, "open_weights": true, - "release_date": "2026-02-12", - "last_updated": "2026-02-12", + "release_date": "2026-04-17", + "last_updated": "2026-04-17", "type": "chat" - } - ] - }, - "amazon-bedrock": { - "id": "amazon-bedrock", - "name": "Amazon Bedrock", - "display_name": "Amazon Bedrock", - "doc": "https://docs.aws.amazon.com/bedrock/latest/userguide/models-supported.html", - "models": [ + }, { - "id": "mistral.voxtral-small-24b-2507", - "name": "Voxtral Small 24B 2507", - "display_name": "Voxtral Small 24B 2507", + "id": "qwen3.8-flash", + "name": "Qwen3.8 Flash", + "display_name": "Qwen3.8 Flash", "modalities": { "input": [ "text", - "audio" + "image" ], "output": [ "text" ] }, "limit": { - "context": 32768, - "output": 8192 + "context": 1048576, + "output": 131072 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "effort_options": [ + "none", + "minimal", + "low", + "medium", + "high", + "xhigh", + "max" + ] + } }, "attachment": true, - "open_weights": true, - "release_date": "2025-07-15", - "last_updated": "2025-07-15", + "open_weights": false, + "release_date": "2026-08-26", + "last_updated": "2026-08-26", "type": "chat" }, { - "id": "ca.amazon.nova-lite-v1:0", - "name": "Nova Lite (CA)", - "display_name": "Nova Lite (CA)", + "id": "glm5.3", + "name": "GLM-5.3", + "display_name": "GLM-5.3", "modalities": { "input": [ - "text", - "image", - "video", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 300000, - "output": 10000 + "context": 1000000, + "output": 131072 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": true, - "open_weights": false, - "knowledge": "2024-10", - "release_date": "2024-12-03", - "last_updated": "2024-12-03", + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "minimal", + "low", + "medium", + "high", + "xhigh", + "max" + ] + } + }, + "attachment": false, + "open_weights": true, + "release_date": "2026-08-14", + "last_updated": "2026-08-14", "type": "chat" }, { - "id": "anthropic.claude-fable-5-1", - "name": "Claude Fable 5.1", - "display_name": "Claude Fable 5.1", + "id": "mimo-v2.6-flash", + "name": "MiMo-V2.6-Flash", + "display_name": "MiMo-V2.6-Flash", "modalities": { "input": [ "text", "image", - "pdf" + "audio" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 1048576, + "output": 131072 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -186266,56 +187218,39 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "high", "effort_options": [ + "none", "low", "medium", "high", - "xhigh", "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "omitted", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Adaptive thinking is always on for Claude Fable 5 and Claude Mythos 5; thinking.type = \"disabled\" is rejected.", - "Manual budget_tokens requests return 400 on Claude Fable 5 and Claude Mythos 5.", - "thinking.display defaults to omitted; set display to summarized to receive readable thinking summaries." ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2026-06", - "release_date": "2026-09-01", - "last_updated": "2026-09-01", + "open_weights": true, + "release_date": "2026-09-22", + "last_updated": "2026-09-22", "type": "chat" }, { - "id": "openai.gpt-5.6-luna", - "name": "GPT-5.6 Luna", - "display_name": "GPT-5.6 Luna", + "id": "gemma4", + "name": "Gemma 4 26B A4B IT", + "display_name": "Gemma 4 26B A4B IT", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 262144, + "output": 32768 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -186328,61 +187263,58 @@ "low", "medium", "high", - "xhigh", "max" ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2026-02-16", - "release_date": "2026-07-09", - "last_updated": "2026-07-09", + "open_weights": true, + "release_date": "2026-04-02", + "last_updated": "2026-04-02", "type": "chat" }, { - "id": "qwen.qwen3-coder-480b-a35b-v1:0", - "name": "Qwen3-Coder 480B-A35B Instruct", - "display_name": "Qwen3-Coder 480B-A35B Instruct", + "id": "qwen-image-2.1", + "name": "Qwen-Image-2.1", + "display_name": "Qwen-Image-2.1", "modalities": { "input": [ "text" ], "output": [ - "text" + "image" ] }, "limit": { - "context": 131072, - "output": 65536 + "context": 8192, + "output": 8192 }, - "temperature": true, - "tool_call": true, + "temperature": false, + "tool_call": false, "reasoning": { "supported": false }, "attachment": false, "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-07-23", - "last_updated": "2025-09-18", - "type": "chat" + "release_date": "2026-09-14", + "last_updated": "2026-09-21" }, { - "id": "minimax.minimax-m2.1", - "name": "MiniMax-M2.1", - "display_name": "MiniMax-M2.1", + "id": "deepseek-v4-flash", + "name": "DeepSeek V4.1 Flash", + "display_name": "DeepSeek V4.1 Flash", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 196608, - "output": 131072 + "context": 1000000, + "output": 384000 }, "temperature": true, "tool_call": true, @@ -186393,19 +187325,44 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true + "default_enabled": true, + "mode": "effort", + "effort": "high", + "effort_options": [ + "low", + "high", + "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." + ] } }, - "attachment": false, + "attachment": true, "open_weights": true, - "release_date": "2025-12-23", - "last_updated": "2025-12-23", + "knowledge": "2025-05", + "release_date": "2026-09-10", + "last_updated": "2026-09-10", "type": "chat" - }, + } + ] + }, + "salad-cloud": { + "id": "salad-cloud", + "name": "SaladCloud AI Gateway", + "display_name": "SaladCloud AI Gateway", + "doc": "https://docs.salad.com/ai-gateway/explanation/overview", + "models": [ { - "id": "mistral.ministral-3-8b-instruct", - "name": "Ministral 3 8B", - "display_name": "Ministral 3 8B", + "id": "qwen3.6-35b-a3b", + "name": "Qwen3.6 35B-A3B", + "display_name": "Qwen3.6 35B-A3B", "modalities": { "input": [ "text", @@ -186416,55 +187373,87 @@ ] }, "limit": { - "context": 128000, - "output": 4096 + "context": 262144, + "output": 262144 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } }, "attachment": true, "open_weights": true, - "release_date": "2025-12-02", - "last_updated": "2025-12-02", + "release_date": "2026-04-17", + "last_updated": "2026-04-17", "type": "chat" - }, + } + ] + }, + "above": { + "id": "above", + "name": "above.dev", + "display_name": "above.dev", + "api": "https://api.above.dev/v1", + "doc": "https://above.dev/docs", + "models": [ { - "id": "eu.amazon.nova-pro-v1:0", - "name": "Nova Pro (EU)", - "display_name": "Nova Pro (EU)", + "id": "mimo-v2.6-pro", + "name": "MiMo V2.6 Pro", + "display_name": "MiMo V2.6 Pro", "modalities": { "input": [ "text", "image", - "video", - "pdf" + "audio", + "video" ], "output": [ "text" ] }, "limit": { - "context": 300000, - "output": 10000 + "context": 1048576, + "output": 131072 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } }, "attachment": true, - "open_weights": false, - "knowledge": "2024-10", - "release_date": "2024-12-03", - "last_updated": "2024-12-03", + "open_weights": true, + "release_date": "2026-09-22", + "last_updated": "2026-09-22", "type": "chat" }, { - "id": "mistral.magistral-small-2509", - "name": "Magistral Small 1.2", - "display_name": "Magistral Small 1.2", + "id": "glm-5.3-flash", + "name": "GLM 5.3 Flash", + "display_name": "GLM 5.3 Flash", "modalities": { "input": [ "text", @@ -186475,8 +187464,8 @@ ] }, "limit": { - "context": 128000, - "output": 40000 + "context": 1000000, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -186484,30 +187473,45 @@ "supported": true, "default": true }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "effort_options": [ + "high", + "max" + ] + } + }, "attachment": true, "open_weights": true, - "release_date": "2025-09-18", - "last_updated": "2025-09-18", + "release_date": "2026-08-26", + "last_updated": "2026-08-26", "type": "chat" }, { - "id": "us.amazon.nova-2-lite-v1:0", - "name": "Nova 2 Lite (US)", - "display_name": "Nova 2 Lite (US)", + "id": "mimo-v2.6-flash", + "name": "MiMo V2.6 Flash", + "display_name": "MiMo V2.6 Flash", "modalities": { "input": [ "text", "image", - "video", - "pdf" + "audio", + "video" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 65535 + "context": 1048576, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -186517,37 +187521,37 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "low", - "medium", - "high" + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2025-10", - "release_date": "2025-12-02", - "last_updated": "2025-12-01", + "open_weights": true, + "release_date": "2026-09-22", + "last_updated": "2026-09-22", "type": "chat" }, { - "id": "global.xai.grok-4.7", - "name": "Grok 4.7 (Global)", - "display_name": "Grok 4.7 (Global)", + "id": "deepseek-v4.1-flash", + "name": "DeepSeek V4.1 Flash", + "display_name": "DeepSeek V4.1 Flash", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 500000, - "output": 500000 + "context": 1000000, + "output": 384000 }, "temperature": true, "tool_call": true, @@ -186557,30 +187561,34 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ "low", - "medium", "high", - "xhigh" + "max" ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2026-05", - "release_date": "2026-09-21", - "last_updated": "2026-09-28", + "open_weights": true, + "knowledge": "2025-05", + "release_date": "2026-09-10", + "last_updated": "2026-09-10", "type": "chat" }, { - "id": "au.anthropic.claude-opus-5", - "name": "Claude Opus 5 (AU)", - "display_name": "Claude Opus 5 (AU)", + "id": "deepseek-v4-pro", + "name": "DeepSeek V4 Pro", + "display_name": "DeepSeek V4 Pro", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" @@ -186588,9 +187596,9 @@ }, "limit": { "context": 1000000, - "output": 128000 + "output": 384000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -186598,88 +187606,82 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "high", "effort_options": [ "low", - "medium", "high", - "xhigh", "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-05", - "release_date": "2026-07-24", - "last_updated": "2026-07-24", + "attachment": false, + "open_weights": true, + "release_date": "2026-08-13", + "last_updated": "2026-08-22", "type": "chat" }, { - "id": "us.anthropic.claude-opus-4-7", - "name": "Claude Opus 4.7 (US)", - "display_name": "Claude Opus 4.7 (US)", + "id": "mimo-v2.6-pro-ultraspeed", + "name": "MiMo V2.6 Pro UltraSpeed", + "display_name": "MiMo V2.6 Pro UltraSpeed", "modalities": { "input": [ "text", "image", - "pdf" + "audio", + "video" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 1048576, + "output": 131072 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "high", - "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" - ], "interleaved": true, "summaries": true, - "visibility": "omitted", + "visibility": "summary", "continuation": [ "thinking_blocks" - ], - "notes": [ - "Claude Opus 4.7 and newer Opus models require thinking.type = \"adaptive\" to enable thinking explicitly.", - "Manual budget_tokens requests return 400 on Claude Opus 4.7 and newer adaptive-only Opus models.", - "task_budget is separate from thinking control and should not be treated as a thinking budget." ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2026-01-31", - "release_date": "2026-04-16", - "last_updated": "2026-04-16", + "open_weights": true, + "release_date": "2026-09-21", + "last_updated": "2026-09-21", "type": "chat" }, { - "id": "jp.anthropic.claude-sonnet-5", - "name": "Claude Sonnet 5 (JP)", - "display_name": "Claude Sonnet 5 (JP)", + "id": "deepseek-v4-flash", + "name": "DeepSeek V4 Flash", + "display_name": "DeepSeek V4 Flash", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" @@ -186687,9 +187689,9 @@ }, "limit": { "context": 1000000, - "output": 128000 + "output": 384000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -186697,31 +187699,40 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "high", "effort_options": [ "low", - "medium", "high", - "xhigh", "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2026-01-31", - "release_date": "2026-06-30", - "last_updated": "2026-06-30", + "open_weights": true, + "knowledge": "2025-05", + "release_date": "2026-09-10", + "last_updated": "2026-09-10", "type": "chat" }, { - "id": "us.anthropic.claude-opus-5", - "name": "Claude Opus 5 (US)", - "display_name": "Claude Opus 5 (US)", + "id": "glm-5.2", + "name": "GLM 5.2", + "display_name": "GLM 5.2", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" @@ -186729,9 +187740,9 @@ }, "limit": { "context": 1000000, - "output": 128000 + "output": 131072 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -186739,78 +187750,70 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ - "low", - "medium", + "none", "high", - "xhigh", "max" ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-05", - "release_date": "2026-07-24", - "last_updated": "2026-07-24", + "attachment": false, + "open_weights": true, + "release_date": "2026-06-13", + "last_updated": "2026-06-13", "type": "chat" }, { - "id": "anthropic.claude-sonnet-4-5-20250929-v1:0", - "name": "Claude Sonnet 4.5", - "display_name": "Claude Sonnet 4.5", + "id": "qwen3.8-max", + "name": "Qwen 3.8 Max", + "display_name": "Qwen 3.8 Max", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 64000 + "context": 1000000, + "output": 32768 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "budget", - "budget": { - "min": 1024, - "unit": "tokens" - }, "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ "thinking_blocks" - ], - "notes": [ - "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", - "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-07-31", - "release_date": "2025-09-29", - "last_updated": "2025-09-29", + "release_date": "2026-08-03", + "last_updated": "2026-08-03", "type": "chat" }, { - "id": "zai.glm-4.7-flash", - "name": "GLM-4.7-Flash", - "display_name": "GLM-4.7-Flash", + "id": "glm-5.2-fast", + "name": "GLM 5.2 Fast", + "display_name": "GLM 5.2 Fast", "modalities": { "input": [ "text" @@ -186820,7 +187823,7 @@ ] }, "limit": { - "context": 202752, + "context": 1000000, "output": 131072 }, "temperature": true, @@ -186832,32 +187835,51 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "effort_options": [ + "none", + "high", + "max" + ] } }, "attachment": false, "open_weights": true, - "knowledge": "2025-04", - "release_date": "2026-01-19", - "last_updated": "2026-01-19", + "release_date": "2026-06-13", + "last_updated": "2026-06-13", "type": "chat" - }, + } + ] + }, + "volcengine": { + "id": "volcengine", + "name": "Volcengine Ark", + "display_name": "Volcengine Ark", + "api": "https://ark.cn-beijing.volces.com/api/v3", + "doc": "https://www.volcengine.com/docs/82379/1330310", + "models": [ { - "id": "moonshotai.kimi-k2.5", - "name": "Kimi K2.5", - "display_name": "Kimi K2.5", + "id": "doubao-seed-1-6-251015", + "name": "Seed 1.6", + "display_name": "Seed 1.6", "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 16384 + "context": 256000, + "output": 64000 }, "temperature": true, "tool_call": true, @@ -186868,25 +187890,33 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "effort_options": [ + "minimal", + "low", + "medium", + "high" + ] } }, "attachment": true, - "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-01-27", - "last_updated": "2026-02-06", + "open_weights": false, + "release_date": "2025-10-15", + "last_updated": "2025-10-15", "type": "chat" }, { - "id": "eu.anthropic.claude-sonnet-5", - "name": "Claude Sonnet 5 (EU)", - "display_name": "Claude Sonnet 5 (EU)", + "id": "glm-5-2-260617", + "name": "GLM-5.2", + "display_name": "GLM-5.2", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" @@ -186894,9 +187924,9 @@ }, "limit": { "context": 1000000, - "output": 128000 + "output": 131072 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -186904,37 +187934,44 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ + "minimal", "low", "medium", - "high", - "xhigh", - "max" + "high" ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-01-31", - "release_date": "2026-06-30", - "last_updated": "2026-06-30", + "attachment": false, + "open_weights": true, + "release_date": "2026-06-13", + "last_updated": "2026-06-13", "type": "chat" }, { - "id": "writer.palmyra-x5-v1:0", - "name": "Palmyra X5", - "display_name": "Palmyra X5", + "id": "doubao-seed-2-1-turbo-260628", + "name": "Seed 2.1 Turbo", + "display_name": "Seed 2.1 Turbo", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 1040000, - "output": 8192 + "context": 256000, + "output": 256000 }, "temperature": true, "tool_call": true, @@ -186942,16 +187979,36 @@ "supported": true, "default": true }, - "attachment": false, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "effort_options": [ + "none", + "minimal", + "low", + "medium", + "high", + "xhigh", + "max" + ] + } + }, + "attachment": true, "open_weights": false, - "release_date": "2025-04-28", - "last_updated": "2025-04-28", + "release_date": "2026-06-23", + "last_updated": "2026-06-23", "type": "chat" }, { - "id": "eu.amazon.nova-micro-v1:0", - "name": "Nova Micro (EU)", - "display_name": "Nova Micro (EU)", + "id": "deepseek-v4-flash-ga-260731", + "name": "DeepSeek V4 Flash 0731", + "display_name": "DeepSeek V4 Flash 0731", "modalities": { "input": [ "text" @@ -186961,30 +188018,47 @@ ] }, "limit": { - "context": 128000, - "output": 10000 + "context": 1000000, + "output": 384000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "effort_options": [ + "minimal", + "low", + "medium", + "high" + ] + } }, "attachment": false, - "open_weights": false, - "knowledge": "2024-10", - "release_date": "2024-12-03", - "last_updated": "2024-12-03", + "open_weights": true, + "knowledge": "2025-05", + "release_date": "2026-07-31", + "last_updated": "2026-07-31", "type": "chat" }, { - "id": "google.gemma-4-e2b", - "name": "Gemma 4 E2B IT", - "display_name": "Gemma 4 E2B IT", + "id": "doubao-seed-2-0-pro-260215", + "name": "Seed 2.0 Pro", + "display_name": "Seed 2.0 Pro", "modalities": { "input": [ "text", "image", - "audio", "video" ], "output": [ @@ -186992,10 +188066,10 @@ ] }, "limit": { - "context": 131072, - "output": 8192 + "context": 256000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -187003,91 +188077,97 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ "none", - "high" + "minimal", + "low", + "medium", + "high", + "xhigh", + "max" ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-04-02", - "last_updated": "2026-04-02", - "type": "chat" - }, - { - "id": "qwen.qwen3-coder-30b-a3b-v1:0", - "name": "Qwen3-Coder 30B-A3B Instruct", - "display_name": "Qwen3-Coder 30B-A3B Instruct", - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 262144, - "output": 131072 - }, - "temperature": true, - "tool_call": true, - "reasoning": { - "supported": false - }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-07-31", - "last_updated": "2025-09-18", + "open_weights": false, + "release_date": "2026-02-14", + "last_updated": "2026-02-14", "type": "chat" }, { - "id": "amazon.nova-lite-v1:0", - "name": "Nova Lite", - "display_name": "Nova Lite", + "id": "doubao-seed-2-0-lite-260428", + "name": "Seed 2.0 Lite", + "display_name": "Seed 2.0 Lite", "modalities": { "input": [ "text", "image", - "video", - "pdf" + "video" ], "output": [ "text" ] }, "limit": { - "context": 300000, - "output": 10000 + "context": 256000, + "output": 131072 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "effort_options": [ + "none", + "minimal", + "low", + "medium", + "high", + "xhigh", + "max" + ] + } }, "attachment": true, "open_weights": false, - "knowledge": "2024-10", - "release_date": "2024-12-03", - "last_updated": "2024-12-03", + "release_date": "2026-02-14", + "last_updated": "2026-02-14", "type": "chat" }, { - "id": "zai.glm-5", - "name": "GLM-5", - "display_name": "GLM-5", + "id": "glm-5-3-flash-260828", + "name": "GLM-5.3-Flash", + "display_name": "GLM-5.3-Flash", "modalities": { "input": [ - "text" + "text", + "image", + "video", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 202752, + "context": 1000000, "output": 131072 }, "temperature": true, @@ -187099,34 +188179,44 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "effort_options": [ + "low", + "high", + "max" + ] } }, - "attachment": false, + "attachment": true, "open_weights": true, - "release_date": "2026-02-12", - "last_updated": "2026-03-18", + "release_date": "2026-08-26", + "last_updated": "2026-08-26", "type": "chat" }, { - "id": "au.anthropic.claude-haiku-5-5", - "name": "Claude Haiku 5.5 (AU)", - "display_name": "Claude Haiku 5.5 (AU)", + "id": "doubao-seed-2-1-pro-260628", + "name": "Seed 2.1 Pro", + "display_name": "Seed 2.1 Pro", "modalities": { "input": [ "text", "image", - "pdf" + "video" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 256000, + "output": 256000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -187134,7 +188224,16 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ + "none", + "minimal", "low", "medium", "high", @@ -187145,30 +188244,29 @@ }, "attachment": true, "open_weights": false, - "knowledge": "2026-06", - "release_date": "2026-10-07", - "last_updated": "2026-10-07", + "release_date": "2026-06-23", + "last_updated": "2026-06-23", "type": "chat" }, { - "id": "global.anthropic.claude-opus-5", - "name": "Claude Opus 5 (Global)", - "display_name": "Claude Opus 5 (Global)", + "id": "doubao-seed-2-0-mini-260428", + "name": "Seed 2.0 Mini", + "display_name": "Seed 2.0 Mini", "modalities": { "input": [ "text", "image", - "pdf" + "video" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 256000, + "output": 131072 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -187176,7 +188274,16 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ + "none", + "minimal", "low", "medium", "high", @@ -187187,26 +188294,27 @@ }, "attachment": true, "open_weights": false, - "knowledge": "2026-05", - "release_date": "2026-07-24", - "last_updated": "2026-07-24", + "release_date": "2026-02-14", + "last_updated": "2026-02-14", "type": "chat" }, { - "id": "openai.gpt-oss-20b", - "name": "gpt-oss-20b", - "display_name": "gpt-oss-20b", + "id": "doubao-seed-1-6-flash-250828", + "name": "Seed 1.6 Flash", + "display_name": "Seed 1.6 Flash", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 131072 + "context": 256000, + "output": 32000 }, "temperature": true, "tool_call": true, @@ -187216,38 +188324,37 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "low", - "medium", - "high" + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, - "attachment": false, - "open_weights": true, - "knowledge": "2024-06", - "release_date": "2025-08-05", - "last_updated": "2025-08-05", + "attachment": true, + "open_weights": false, + "release_date": "2025-08-28", + "last_updated": "2025-08-28", "type": "chat" }, { - "id": "jp.amazon.nova-2-lite-v1:0", - "name": "Nova 2 Lite (JP)", - "display_name": "Nova 2 Lite (JP)", + "id": "doubao-seed-1-8-251228", + "name": "Seed 1.8", + "display_name": "Seed 1.8", "modalities": { "input": [ "text", - "image", - "video", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 65535 + "context": 256000, + "output": 64000 }, "temperature": true, "tool_call": true, @@ -187257,39 +188364,48 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ + "none", + "minimal", "low", "medium", - "high" + "high", + "xhigh", + "max" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-10", - "release_date": "2025-12-02", - "last_updated": "2025-12-01", + "release_date": "2025-12-28", + "last_updated": "2025-12-28", "type": "chat" }, { - "id": "anthropic.claude-opus-5-5", - "name": "Claude Opus 5.5", - "display_name": "Claude Opus 5.5", + "id": "doubao-seed-1-6-vision-250815", + "name": "Seed 1.6 Vision", + "display_name": "Seed 1.6 Vision", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 256000, + "output": 32000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -187297,41 +188413,40 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-06", - "release_date": "2026-09-22", - "last_updated": "2026-09-22", + "release_date": "2025-08-15", + "last_updated": "2025-08-15", "type": "chat" }, { - "id": "in.openai.gpt-5.6-luna", - "name": "GPT-5.6 Luna (India)", - "display_name": "GPT-5.6 Luna (India)", + "id": "doubao-seed-evolving", + "name": "Seed Evolving", + "display_name": "Seed Evolving", "modalities": { "input": [ "text", "image", - "pdf" + "video" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 256000, + "output": 256000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -187339,8 +188454,16 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ "none", + "minimal", "low", "medium", "high", @@ -187351,20 +188474,17 @@ }, "attachment": true, "open_weights": false, - "knowledge": "2026-02-16", - "release_date": "2026-07-09", - "last_updated": "2026-07-09", + "release_date": "2026-06-23", + "last_updated": "2026-06-23", "type": "chat" }, { - "id": "jp.anthropic.claude-opus-5", - "name": "Claude Opus 5 (JP)", - "display_name": "Claude Opus 5 (JP)", + "id": "deepseek-v4-pro-ga-260813", + "name": "DeepSeek V4 Pro 0813", + "display_name": "DeepSeek V4 Pro 0813", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" @@ -187372,9 +188492,9 @@ }, "limit": { "context": 1000000, - "output": 128000 + "output": 384000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -187382,40 +188502,46 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ + "minimal", "low", "medium", - "high", - "xhigh", - "max" + "high" ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-05", - "release_date": "2026-07-24", - "last_updated": "2026-07-24", + "attachment": false, + "open_weights": true, + "release_date": "2026-08-13", + "last_updated": "2026-08-22", "type": "chat" }, { - "id": "us.openai.gpt-6.1-terra", - "name": "GPT-6.1 Terra (US)", - "display_name": "GPT-6.1 Terra (US)", + "id": "doubao-seed-2-0-code-preview-260215", + "name": "Seed 2.0 Code", + "display_name": "Seed 2.0 Code", "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 262144, + "output": 131072 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -187423,8 +188549,16 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ "none", + "minimal", "low", "medium", "high", @@ -187435,48 +188569,74 @@ }, "attachment": true, "open_weights": false, - "knowledge": "2026-06-01", - "release_date": "2026-10-26", - "last_updated": "2026-10-26", + "release_date": "2026-02-14", + "last_updated": "2026-02-14", "type": "chat" }, { - "id": "qwen.qwen3-vl-235b-a22b", - "name": "Qwen3 VL 235B A22B Instruct", - "display_name": "Qwen3 VL 235B A22B Instruct", + "id": "doubao-seed-character-260628", + "name": "Seed Character", + "display_name": "Seed Character", "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 262000 + "context": 256000, + "output": 256000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "effort_options": [ + "minimal", + "low", + "high" + ] + } }, "attachment": true, - "open_weights": true, - "knowledge": "2025-03-31", - "release_date": "2025-09-23", - "last_updated": "2025-11-25", + "open_weights": false, + "release_date": "2026-06-23", + "last_updated": "2026-06-23", "type": "chat" - }, + } + ] + }, + "liquid": { + "id": "liquid", + "name": "Liquid Inference", + "display_name": "Liquid Inference", + "api": "https://router.inference.ai.exchange/v1", + "doc": "https://inference.ai.exchange/docs", + "models": [ { - "id": "us.anthropic.claude-haiku-5-5", - "name": "Claude Haiku 5.5 (US)", - "display_name": "Claude Haiku 5.5 (US)", + "id": "liquid.auto", + "name": "Liquid Auto", + "display_name": "Liquid Auto", "modalities": { "input": [ "text", "image", + "audio", "pdf" ], "output": [ @@ -187484,37 +188644,34 @@ ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 10000000, + "output": 1048576 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" - ] - } - }, "attachment": true, "open_weights": false, - "knowledge": "2026-06", - "release_date": "2026-10-07", - "last_updated": "2026-10-07", + "release_date": "2026-10-20", + "last_updated": "2026-10-20", "type": "chat" - }, + } + ] + }, + "blueclaw": { + "id": "blueclaw", + "name": "Blue Claw", + "display_name": "Blue Claw", + "api": "https://openai.blueclaw.network/v1", + "doc": "https://blueclaw.network", + "models": [ { - "id": "deepseek.v3-v1:0", - "name": "DeepSeek-V3.1", - "display_name": "DeepSeek-V3.1", + "id": "Qwen3.8-27B", + "name": "Qwen3.8 27B", + "display_name": "Qwen3.8 27B", "modalities": { "input": [ "text" @@ -187524,8 +188681,8 @@ ] }, "limit": { - "context": 163840, - "output": 81920 + "context": 131072, + "output": 32768 }, "temperature": true, "tool_call": true, @@ -187535,63 +188692,84 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true + "effort_options": [ + "low", + "medium", + "xhigh" + ] } }, "attachment": false, "open_weights": true, - "release_date": "2025-08-21", - "last_updated": "2025-09-18", + "release_date": "2026-08-14", + "last_updated": "2026-08-14", "type": "chat" - }, + } + ] + }, + "prime": { + "id": "prime", + "name": "Prime Inference", + "display_name": "Prime Inference", + "api": "https://api.pinference.ai/api/v1", + "doc": "https://docs.primeintellect.ai/inference/overview", + "models": [ { - "id": "in.openai.gpt-5.6-terra", - "name": "GPT-5.6 Terra (India)", - "display_name": "GPT-5.6 Terra (India)", + "id": "openai/gpt-5.4-nano", + "name": "GPT-5.4 Nano", + "display_name": "GPT-5.4 Nano", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1050000, + "context": 400000, "output": 128000 }, "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": false, + "mode": "effort", + "effort": "none", "effort_options": [ "none", "low", "medium", "high", - "xhigh", - "max" - ] + "xhigh" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, "attachment": true, "open_weights": false, - "knowledge": "2026-02-16", - "release_date": "2026-07-09", - "last_updated": "2026-07-09", + "knowledge": "2025-08-31", + "release_date": "2026-03-17", + "last_updated": "2026-03-17", "type": "chat" }, { - "id": "global.openai.gpt-5.6-sol", - "name": "GPT-5.6 Sol (Global)", - "display_name": "GPT-5.6 Sol (Global)", + "id": "openai/gpt-5.4", + "name": "GPT-5.4", + "display_name": "GPT-5.4", "modalities": { "input": [ "text", @@ -187609,64 +188787,96 @@ "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": false, + "mode": "effort", + "effort": "none", "effort_options": [ "none", "low", "medium", "high", - "xhigh", - "max" - ] + "xhigh" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, "attachment": true, "open_weights": false, - "knowledge": "2026-02-16", - "release_date": "2026-07-09", - "last_updated": "2026-07-09", + "knowledge": "2025-08-31", + "release_date": "2026-03-05", + "last_updated": "2026-03-05", "type": "chat" }, { - "id": "us.amazon.nova-pro-v1:0", - "name": "Nova Pro (US)", - "display_name": "Nova Pro (US)", + "id": "openai/gpt-5.4-mini", + "name": "GPT-5.4 Mini", + "display_name": "GPT-5.4 Mini", "modalities": { "input": [ "text", - "image", - "video", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 300000, - "output": 10000 + "context": 400000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": false + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": false, + "mode": "effort", + "effort": "none", + "effort_options": [ + "none", + "low", + "medium", + "high", + "xhigh" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" + } }, "attachment": true, "open_weights": false, - "knowledge": "2024-10", - "release_date": "2024-12-03", - "last_updated": "2024-12-03", + "knowledge": "2025-08-31", + "release_date": "2026-03-17", + "last_updated": "2026-03-17", "type": "chat" }, { - "id": "moonshot.kimi-k2-thinking", - "name": "Kimi K2 Thinking", - "display_name": "Kimi K2 Thinking", + "id": "openai/gpt-6-sol", + "name": "GPT-6 Sol", + "display_name": "GPT-6 Sol", "modalities": { "input": [ + "image", "text" ], "output": [ @@ -187674,10 +188884,10 @@ ] }, "limit": { - "context": 262144, - "output": 16000 + "context": 1050000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -187685,36 +188895,41 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true + "effort_options": [ + "none", + "low", + "medium", + "high", + "xhigh", + "max" + ] } }, - "attachment": false, - "open_weights": true, - "knowledge": "2024-08", - "release_date": "2025-11-06", - "last_updated": "2025-12-02", + "attachment": true, + "open_weights": false, + "knowledge": "2026-04-20", + "release_date": "2026-09-22", + "last_updated": "2026-09-22", "type": "chat" }, { - "id": "us.xai.grok-4.7", - "name": "Grok 4.7 (US)", - "display_name": "Grok 4.7 (US)", + "id": "openai/gpt-5.6-terra", + "name": "GPT-5.6 Terra", + "display_name": "GPT-5.6 Terra", "modalities": { "input": [ - "text", "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 500000, - "output": 500000 + "context": 1050000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -187722,25 +188937,38 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", "effort_options": [ + "none", "low", "medium", "high", - "xhigh" - ] + "xhigh", + "max" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, "attachment": true, "open_weights": false, - "knowledge": "2026-05", - "release_date": "2026-09-21", - "last_updated": "2026-09-28", + "knowledge": "2026-02-16", + "release_date": "2026-07-09", + "last_updated": "2026-07-09", "type": "chat" }, { - "id": "us.openai.gpt-6-sol", - "name": "GPT-6 Sol (US)", - "display_name": "GPT-6 Sol (US)", + "id": "openai/gpt-5.3-codex", + "name": "GPT-5.3-Codex", + "display_name": "GPT-5.3-Codex", "modalities": { "input": [ "text", @@ -187751,7 +188979,7 @@ ] }, "limit": { - "context": 1050000, + "context": 400000, "output": 128000 }, "temperature": false, @@ -187762,177 +188990,191 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", "effort_options": [ - "none", "low", "medium", "high", - "xhigh", - "max" - ] + "xhigh" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, "attachment": true, "open_weights": false, - "knowledge": "2026-04-20", - "release_date": "2026-09-22", - "last_updated": "2026-09-22", + "knowledge": "2025-08-31", + "release_date": "2026-02-05", + "last_updated": "2026-02-05", "type": "chat" }, { - "id": "global.anthropic.claude-opus-4-5-20251101-v1:0", - "name": "Claude Opus 4.5 (Global)", - "display_name": "Claude Opus 4.5 (Global)", + "id": "openai/gpt-6-astra", + "name": "GPT-6 Astra", + "display_name": "GPT-6 Astra", "modalities": { "input": [ - "text", "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 64000 + "context": 1050000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "mixed", - "budget": { - "min": 1024, - "unit": "tokens" - }, - "effort": "high", "effort_options": [ "low", "medium", - "high" - ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Claude Opus 4.5 uses manual thinking.type = \"enabled\" with budget_tokens; effort can be used alongside the thinking budget.", - "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header." + "high", + "xhigh", + "max" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-05", - "release_date": "2025-11-24", - "last_updated": "2025-11-24", + "knowledge": "2026-04-30", + "release_date": "2026-09-04", + "last_updated": "2026-09-04", "type": "chat" }, { - "id": "nvidia.nemotron-nano-9b-v2", - "name": "NVIDIA Nemotron Nano 9B v2", - "display_name": "NVIDIA Nemotron Nano 9B v2", + "id": "openai/gpt-5.2", + "name": "GPT-5.2", + "display_name": "GPT-5.2", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 8192 + "context": 400000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": false }, - "attachment": false, - "open_weights": true, - "knowledge": "2024-09", - "release_date": "2025-08-18", - "last_updated": "2025-08-18", + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": false, + "mode": "effort", + "effort": "none", + "effort_options": [ + "none", + "low", + "medium", + "high", + "xhigh" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" + } + }, + "attachment": true, + "open_weights": false, + "knowledge": "2025-08-31", + "release_date": "2025-12-11", + "last_updated": "2025-12-11", "type": "chat" }, { - "id": "jp.anthropic.claude-sonnet-4-5-20250929-v1:0", - "name": "Claude Sonnet 4.5 (JP)", - "display_name": "Claude Sonnet 4.5 (JP)", + "id": "openai/gpt-5.4-pro", + "name": "GPT-5.4 Pro", + "display_name": "GPT-5.4 Pro", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 64000 + "context": 1050000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "budget", - "budget": { - "min": 1024, - "unit": "tokens" - }, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "default_enabled": true, + "mode": "effort", + "effort": "high", + "effort_options": [ + "medium", + "high", + "xhigh" ], - "notes": [ - "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", - "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." - ] + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, "attachment": true, "open_weights": false, - "knowledge": "2025-07-31", - "release_date": "2025-09-29", - "last_updated": "2025-09-29", + "knowledge": "2025-08-31", + "release_date": "2026-03-05", + "last_updated": "2026-03-05", "type": "chat" }, { - "id": "global.anthropic.claude-sonnet-5", - "name": "Claude Sonnet 5 (Global)", - "display_name": "Claude Sonnet 5 (Global)", + "id": "openai/gpt-6.1-sol", + "name": "GPT-6.1 Sol", + "display_name": "GPT-6.1 Sol", "modalities": { "input": [ - "text", "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, + "context": 1050000, "output": 128000 }, "temperature": false, @@ -187954,27 +189196,55 @@ }, "attachment": true, "open_weights": false, - "knowledge": "2026-01-31", - "release_date": "2026-06-30", - "last_updated": "2026-06-30", + "knowledge": "2026-04-30", + "release_date": "2026-09-29", + "last_updated": "2026-09-29", "type": "chat" }, { - "id": "in.anthropic.claude-opus-5", - "name": "Claude Opus 5 (India)", - "display_name": "Claude Opus 5 (India)", + "id": "openai/gpt-4.1-nano", + "name": "GPT-4.1 Nano", + "display_name": "GPT-4.1 Nano", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, + "context": 1047576, + "output": 942818 + }, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": false + }, + "attachment": true, + "open_weights": false, + "knowledge": "2024-04", + "release_date": "2025-04-14", + "last_updated": "2025-04-14", + "type": "chat" + }, + { + "id": "openai/gpt-5-nano", + "name": "GPT-5 Nano", + "display_name": "GPT-5 Nano", + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 400000, "output": 128000 }, "temperature": false, @@ -187985,28 +189255,39 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", "effort_options": [ + "minimal", "low", "medium", - "high", - "xhigh", - "max" - ] + "high" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, "attachment": true, "open_weights": false, - "knowledge": "2026-05", - "release_date": "2026-07-24", - "last_updated": "2026-07-24", + "knowledge": "2024-05-30", + "release_date": "2025-08-07", + "last_updated": "2025-08-07", "type": "chat" }, { - "id": "global.zai.glm-5.3", - "name": "GLM-5.3 (Global)", - "display_name": "GLM-5.3 (Global)", + "id": "openai/gpt-5.6-luna", + "name": "GPT-5.6 Luna", + "display_name": "GPT-5.6 Luna", "modalities": { "input": [ + "image", "text" ], "output": [ @@ -188014,10 +189295,10 @@ ] }, "limit": { - "context": 1000000, + "context": 1050000, "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -188026,130 +189307,139 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", "effort_options": [ + "none", "low", "medium", "high", "xhigh", "max" - ] + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-08-14", - "last_updated": "2026-08-14", + "attachment": true, + "open_weights": false, + "knowledge": "2026-02-16", + "release_date": "2026-07-09", + "last_updated": "2026-07-09", "type": "chat" }, { - "id": "global.anthropic.claude-sonnet-4-20250514-v1:0", - "name": "Claude Sonnet 4 (Global)", - "display_name": "Claude Sonnet 4 (Global)", + "id": "openai/gpt-5", + "name": "GPT-5", + "display_name": "GPT-5", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 64000 + "context": 400000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "budget", - "budget": { - "min": 1024, - "unit": "tokens" - }, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "default_enabled": true, + "mode": "effort", + "effort": "medium", + "effort_options": [ + "minimal", + "low", + "medium", + "high" ], - "notes": [ - "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", - "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." - ] + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, "attachment": true, "open_weights": false, - "knowledge": "2025-03-31", - "release_date": "2025-05-22", - "last_updated": "2025-05-22", + "knowledge": "2024-09-30", + "release_date": "2025-08-07", + "last_updated": "2025-08-07", "type": "chat" }, { - "id": "global.anthropic.claude-haiku-4-5-20251001-v1:0", - "name": "Claude Haiku 4.5 (Global)", - "display_name": "Claude Haiku 4.5 (Global)", + "id": "openai/gpt-5-mini", + "name": "GPT-5 Mini", + "display_name": "GPT-5 Mini", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 64000 + "context": 400000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "budget", - "budget": { - "min": 1024, - "unit": "tokens" - }, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "default_enabled": true, + "mode": "effort", + "effort": "medium", + "effort_options": [ + "minimal", + "low", + "medium", + "high" ], - "notes": [ - "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", - "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." - ] + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, "attachment": true, "open_weights": false, - "knowledge": "2025-02-28", - "release_date": "2025-10-15", - "last_updated": "2025-10-15", + "knowledge": "2024-05-30", + "release_date": "2025-08-07", + "last_updated": "2025-08-07", "type": "chat" }, { - "id": "us.openai.gpt-6-luna", - "name": "GPT-6 Luna (US)", - "display_name": "GPT-6 Luna (US)", + "id": "openai/gpt-4o-mini", + "name": "GPT-4o-mini", + "display_name": "GPT-4o-mini", "modalities": { "input": [ "text", @@ -188160,38 +189450,25 @@ ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 128000, + "output": 16384 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "none", - "low", - "medium", - "high", - "xhigh", - "max" - ] - } + "supported": false }, "attachment": true, "open_weights": false, - "knowledge": "2026-05-18", - "release_date": "2026-09-22", - "last_updated": "2026-09-22", + "knowledge": "2023-09", + "release_date": "2024-07-18", + "last_updated": "2024-07-18", "type": "chat" }, { - "id": "meta.llama3-1-70b-instruct-v1:0", - "name": "Llama 3.1 70B Instruct", - "display_name": "Llama 3.1 70B Instruct", + "id": "openai/gpt-oss-120b", + "name": "gpt-oss-120b", + "display_name": "gpt-oss-120b", "modalities": { "input": [ "text" @@ -188201,148 +189478,118 @@ ] }, "limit": { - "context": 128000, - "output": 4096 + "context": 131072, + "output": 65536 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "effort_options": [ + "low", + "medium", + "high" + ] + } }, "attachment": false, "open_weights": true, - "knowledge": "2023-12", - "release_date": "2024-07-23", - "last_updated": "2024-07-23", + "knowledge": "2024-06", + "release_date": "2025-08-05", + "last_updated": "2025-08-05", "type": "chat" }, { - "id": "au.anthropic.claude-haiku-4-5-20251001-v1:0", - "name": "Claude Haiku 4.5 (AU)", - "display_name": "Claude Haiku 4.5 (AU)", + "id": "openai/gpt-4o", + "name": "GPT-4o", + "display_name": "GPT-4o", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 64000 + "context": 128000, + "output": 16384 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": false - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "budget", - "budget": { - "min": 1024, - "unit": "tokens" - }, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", - "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." - ] - } + "supported": false }, "attachment": true, "open_weights": false, - "knowledge": "2025-02-28", - "release_date": "2025-10-15", - "last_updated": "2025-10-15", + "knowledge": "2023-09", + "release_date": "2024-05-13", + "last_updated": "2024-08-06", "type": "chat" }, { - "id": "us.anthropic.claude-opus-4-6-v1", - "name": "Claude Opus 4.6 (US)", - "display_name": "Claude Opus 4.6 (US)", + "id": "openai/gpt-5.6-terra-pro", + "name": "GPT-5.6 Terra Pro", + "display_name": "GPT-5.6 Terra Pro", "modalities": { "input": [ - "text", "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, + "context": 1050000, "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "mixed", - "budget": { - "min": 1024, - "unit": "tokens" - }, - "effort": "high", "effort_options": [ + "none", "low", "medium", "high", + "xhigh", "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Anthropic recommends adaptive thinking with effort for Claude 4.6; budget_tokens remains a deprecated compatibility path.", - "Anthropic API defaults effort to high; lower effort levels should be chosen per workload." ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-05-31", - "release_date": "2026-02-05", - "last_updated": "2026-03-13", + "knowledge": "2026-02-16", + "release_date": "2026-07-09", + "last_updated": "2026-07-09", "type": "chat" }, { - "id": "openai.gpt-5.5", - "name": "GPT-5.5", - "display_name": "GPT-5.5", + "id": "openai/gpt-5.6-luna-pro", + "name": "GPT-5.6 Luna Pro", + "display_name": "GPT-5.6 Luna Pro", "modalities": { "input": [ - "text", "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, + "context": 1050000, "output": 128000 }, "temperature": false, @@ -188358,133 +189605,137 @@ "low", "medium", "high", - "xhigh" + "xhigh", + "max" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-12-01", - "release_date": "2026-04-23", - "last_updated": "2026-06-01", + "knowledge": "2026-02-16", + "release_date": "2026-07-09", + "last_updated": "2026-07-09", "type": "chat" }, { - "id": "eu.anthropic.claude-opus-4-5-20251101-v1:0", - "name": "Claude Opus 4.5 (EU)", - "display_name": "Claude Opus 4.5 (EU)", + "id": "openai/gpt-5.6-sol", + "name": "GPT-5.6 Sol", + "display_name": "GPT-5.6 Sol", "modalities": { "input": [ - "text", "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 64000 + "context": 1050000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "mixed", - "budget": { - "min": 1024, - "unit": "tokens" - }, - "effort": "high", + "default_enabled": true, + "mode": "effort", + "effort": "medium", "effort_options": [ + "none", "low", "medium", - "high" + "high", + "xhigh", + "max" ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" ], - "notes": [ - "Claude Opus 4.5 uses manual thinking.type = \"enabled\" with budget_tokens; effort can be used alongside the thinking budget.", - "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header." - ] + "visibility": "hidden" } }, "attachment": true, "open_weights": false, - "knowledge": "2025-05", - "release_date": "2025-11-24", - "last_updated": "2025-11-24", + "knowledge": "2026-02-16", + "release_date": "2026-07-09", + "last_updated": "2026-07-09", "type": "chat" }, { - "id": "anthropic.claude-sonnet-5-5", - "name": "Claude Sonnet 5.5", - "display_name": "Claude Sonnet 5.5", + "id": "openai/gpt-5.1", + "name": "GPT-5.1", + "display_name": "GPT-5.1", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, + "context": 400000, "output": 128000 }, "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": false, + "mode": "effort", + "effort": "none", "effort_options": [ + "none", "low", "medium", - "high", - "xhigh", - "max" - ] + "high" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, "attachment": true, "open_weights": false, - "knowledge": "2026-06", - "release_date": "2026-09-28", - "last_updated": "2026-09-28", + "knowledge": "2024-09-30", + "release_date": "2025-11-13", + "last_updated": "2025-11-13", "type": "chat" }, { - "id": "us.anthropic.claude-fable-5", - "name": "Claude Fable 5 (US)", - "display_name": "Claude Fable 5 (US)", + "id": "openai/gpt-5.5", + "name": "GPT-5.5", + "display_name": "GPT-5.5", "modalities": { "input": [ - "text", "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, + "context": 1050000, "output": 128000 }, "temperature": false, @@ -188498,68 +189749,36 @@ "supported": true, "default_enabled": true, "mode": "effort", - "effort": "high", + "effort": "medium", "effort_options": [ "low", "medium", "high", - "xhigh", - "max" + "xhigh" ], - "interleaved": true, - "summaries": true, - "visibility": "omitted", - "continuation": [ - "thinking_blocks" + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" ], - "notes": [ - "Adaptive thinking is always on for Claude Fable 5 and Claude Mythos 5; thinking.type = \"disabled\" is rejected.", - "Manual budget_tokens requests return 400 on Claude Fable 5 and Claude Mythos 5.", - "thinking.display defaults to omitted; set display to summarized to receive readable thinking summaries." - ] + "visibility": "hidden" } }, "attachment": true, "open_weights": false, - "knowledge": "2026-01-31", - "release_date": "2026-06-09", - "last_updated": "2026-06-09", - "type": "chat" - }, - { - "id": "eu.mistral.pixtral-large-2502-v1:0", - "name": "Pixtral Large (25.02) (EU)", - "display_name": "Pixtral Large (25.02) (EU)", - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 8192 - }, - "temperature": true, - "tool_call": true, - "reasoning": { - "supported": false - }, - "attachment": true, - "open_weights": false, - "release_date": "2025-04-08", - "last_updated": "2025-04-08", + "knowledge": "2025-12-01", + "release_date": "2026-04-23", + "last_updated": "2026-04-23", "type": "chat" }, { - "id": "openai.gpt-oss-safeguard-20b", - "name": "GPT OSS Safeguard 20B", - "display_name": "GPT OSS Safeguard 20B", + "id": "openai/gpt-5.6-sol-pro", + "name": "GPT-5.6 Sol Pro", + "display_name": "GPT-5.6 Sol Pro", "modalities": { "input": [ + "image", "text" ], "output": [ @@ -188567,10 +189786,10 @@ ] }, "limit": { - "context": 128000, - "output": 16384 + "context": 1050000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -188579,36 +189798,38 @@ "extra_capabilities": { "reasoning": { "effort_options": [ + "none", "low", "medium", - "high" + "high", + "xhigh", + "max" ] } }, - "attachment": false, - "open_weights": true, - "release_date": "2025-10-29", - "last_updated": "2025-10-29", + "attachment": true, + "open_weights": false, + "knowledge": "2026-02-16", + "release_date": "2026-07-09", + "last_updated": "2026-07-09", "type": "chat" }, { - "id": "us.amazon.nova-premier-v1:0", - "name": "Nova Premier (US)", - "display_name": "Nova Premier (US)", + "id": "openai/gpt-4.1-mini", + "name": "GPT-4.1 Mini", + "display_name": "GPT-4.1 Mini", "modalities": { "input": [ - "text", "image", - "video", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 10000 + "context": 1047576, + "output": 32768 }, "temperature": true, "tool_call": true, @@ -188617,27 +189838,27 @@ }, "attachment": true, "open_weights": false, - "knowledge": "2024-10", - "release_date": "2025-04-30", - "last_updated": "2025-04-30", + "knowledge": "2024-04", + "release_date": "2025-04-14", + "last_updated": "2025-04-14", "type": "chat" }, { - "id": "mistral.ministral-3-3b-instruct", - "name": "Ministral 3 3B", - "display_name": "Ministral 3 3B", + "id": "openai/gpt-4.1", + "name": "GPT-4.1", + "display_name": "GPT-4.1", "modalities": { "input": [ - "text", - "image" + "image", + "text" ], "output": [ "text" ] }, "limit": { - "context": 256000, - "output": 8192 + "context": 1047576, + "output": 32768 }, "temperature": true, "tool_call": true, @@ -188645,72 +189866,58 @@ "supported": false }, "attachment": true, - "open_weights": true, - "release_date": "2025-12-02", - "last_updated": "2025-12-02", + "open_weights": false, + "knowledge": "2024-04", + "release_date": "2025-04-14", + "last_updated": "2025-04-14", "type": "chat" }, { - "id": "anthropic.claude-opus-4-8", - "name": "Claude Opus 4.8", - "display_name": "Claude Opus 4.8", + "id": "openai/gpt-6-luna", + "name": "GPT-6 Luna", + "display_name": "GPT-6 Luna", "modalities": { "input": [ - "text", "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, + "context": 1050000, "output": 128000 }, "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "high", "effort_options": [ + "none", "low", "medium", "high", "xhigh", "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "omitted", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Claude Opus 4.7 and newer Opus models require thinking.type = \"adaptive\" to enable thinking explicitly.", - "Manual budget_tokens requests return 400 on Claude Opus 4.7 and newer adaptive-only Opus models.", - "task_budget is separate from thinking control and should not be treated as a thinking budget." ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-01", - "release_date": "2026-05-28", - "last_updated": "2026-05-28", + "knowledge": "2026-05-18", + "release_date": "2026-09-22", + "last_updated": "2026-09-22", "type": "chat" }, { - "id": "us.amazon.nova-micro-v1:0", - "name": "Nova Micro (US)", - "display_name": "Nova Micro (US)", + "id": "openai/gpt-oss-20b", + "name": "gpt-oss-20b", + "display_name": "gpt-oss-20b", "modalities": { "input": [ "text" @@ -188720,37 +189927,49 @@ ] }, "limit": { - "context": 128000, - "output": 10000 + "context": 131072, + "output": 32768 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "effort_options": [ + "low", + "medium", + "high" + ] + } }, "attachment": false, - "open_weights": false, - "knowledge": "2024-10", - "release_date": "2024-12-03", - "last_updated": "2024-12-03", + "open_weights": true, + "knowledge": "2024-06", + "release_date": "2025-08-05", + "last_updated": "2025-08-05", "type": "chat" }, { - "id": "global.xai.grok-4.6", - "name": "Grok 4.6 (Global)", - "display_name": "Grok 4.6 (Global)", + "id": "z-ai/glm-5.3-flash", + "name": "GLM 5.3 Flash", + "display_name": "GLM 5.3 Flash", "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 500000, - "output": 500000 + "context": 1048576, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -188760,39 +189979,35 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, "effort_options": [ "low", - "medium", "high", - "xhigh" + "max" ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2026-02-01", - "release_date": "2026-08-12", - "last_updated": "2026-08-18", + "open_weights": true, + "release_date": "2026-08-26", + "last_updated": "2026-08-26", "type": "chat" }, { - "id": "global.amazon.nova-2-lite-v1:0", - "name": "Nova 2 Lite (Global)", - "display_name": "Nova 2 Lite (Global)", + "id": "z-ai/glm-5.3", + "name": "GLM 5.3", + "display_name": "GLM 5.3", "modalities": { "input": [ - "text", - "image", - "video", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 65535 + "context": 1048576, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -188802,36 +190017,35 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, "effort_options": [ "low", - "medium", - "high" + "high", + "max" ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-10", - "release_date": "2025-12-02", - "last_updated": "2025-12-01", + "attachment": false, + "open_weights": true, + "release_date": "2026-08-14", + "last_updated": "2026-08-14", "type": "chat" }, { - "id": "us.xai.grok-4.6", - "name": "Grok 4.6 (US)", - "display_name": "Grok 4.6 (US)", + "id": "z-ai/glm-5.2", + "name": "GLM 5.2", + "display_name": "GLM 5.2", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 500000, - "output": 500000 + "context": 1048576, + "output": 943718 }, "temperature": true, "tool_call": true, @@ -188841,75 +190055,65 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, "effort_options": [ - "low", - "medium", + "none", "high", "xhigh" ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-02-01", - "release_date": "2026-08-12", - "last_updated": "2026-08-18", + "attachment": false, + "open_weights": true, + "release_date": "2026-06-13", + "last_updated": "2026-06-13", "type": "chat" }, { - "id": "google.gemma-4-31b", - "name": "Gemma 4 31B IT", - "display_name": "Gemma 4 31B IT", + "id": "meta-llama/llama-3.3-70b-instruct", + "name": "Llama 3.3 70B Instruct", + "display_name": "Llama 3.3 70B Instruct", "modalities": { "input": [ - "text", - "image", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 32768 + "context": 131072, + "output": 16384 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "none", - "high" - ] - } + "supported": false }, - "attachment": true, + "attachment": false, "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-04-02", - "last_updated": "2026-04-02", + "knowledge": "2023-12", + "release_date": "2024-12-06", + "last_updated": "2024-12-06", "type": "chat" }, { - "id": "zai.glm-4.7", - "name": "GLM-4.7", - "display_name": "GLM-4.7", + "id": "moonshotai/kimi-k3", + "name": "Kimi K3", + "display_name": "Kimi K3", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 202752, - "output": 131072 + "context": 1048576, + "output": 943718 }, "temperature": true, "tool_call": true, @@ -188920,120 +190124,107 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true + "effort_options": [ + "none", + "low", + "high", + "max" + ] } }, - "attachment": false, + "attachment": true, "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-12-22", - "last_updated": "2025-12-22", + "release_date": "2026-07-16", + "last_updated": "2026-07-16", "type": "chat" }, { - "id": "us.mistral.pixtral-large-2502-v1:0", - "name": "Pixtral Large (25.02) (US)", - "display_name": "Pixtral Large (25.02) (US)", + "id": "moonshotai/kimi-k2-0905", + "name": "Kimi K2 0905", + "display_name": "Kimi K2 0905", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 8192 + "context": 262144, + "output": 98304 }, "temperature": true, "tool_call": true, "reasoning": { "supported": false }, - "attachment": true, - "open_weights": false, - "release_date": "2025-04-08", - "last_updated": "2025-04-08", + "attachment": false, + "open_weights": true, + "release_date": "2025-09-05", + "last_updated": "2025-09-05", "type": "chat" }, { - "id": "au.anthropic.claude-opus-4-7", - "name": "Claude Opus 4.7 (AU)", - "display_name": "Claude Opus 4.7 (AU)", + "id": "moonshotai/kimi-k3.1-pro", + "name": "Kimi K3.1 Pro", + "display_name": "Kimi K3.1 Pro", "modalities": { "input": [ "text", "image", - "pdf" + "video" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 1048576, + "output": 943718 }, "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "high", "effort_options": [ + "none", "low", - "medium", "high", - "xhigh", "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "omitted", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Claude Opus 4.7 and newer Opus models require thinking.type = \"adaptive\" to enable thinking explicitly.", - "Manual budget_tokens requests return 400 on Claude Opus 4.7 and newer adaptive-only Opus models.", - "task_budget is separate from thinking control and should not be treated as a thinking budget." ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-01-31", - "release_date": "2026-04-16", - "last_updated": "2026-04-16", + "release_date": "2026-10-23", + "last_updated": "2026-10-23", "type": "chat" }, { - "id": "us.anthropic.claude-sonnet-5", - "name": "Claude Sonnet 5 (US)", - "display_name": "Claude Sonnet 5 (US)", + "id": "google/gemini-3.5-flash", + "name": "Gemini 3.5 Flash", + "display_name": "Gemini 3.5 Flash", "modalities": { "input": [ "text", "image", - "pdf" + "video", + "audio" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 1048576, + "output": 65536 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -189041,208 +190232,221 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ + "supported": true, + "default_enabled": true, + "mode": "level", + "level": "high", + "level_options": [ + "minimal", "low", "medium", - "high", - "xhigh", - "max" + "high" + ], + "summaries": true, + "visibility": "summary", + "continuation": [ + "thought_signatures" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-01-31", - "release_date": "2026-06-30", - "last_updated": "2026-06-30", + "knowledge": "2025-01", + "release_date": "2026-05-19", + "last_updated": "2026-05-19", "type": "chat" }, { - "id": "meta.llama3-1-8b-instruct-v1:0", - "name": "Llama 3.1 8B Instruct", - "display_name": "Llama 3.1 8B Instruct", + "id": "google/gemini-4-argon", + "name": "Gemini 4 Argon", + "display_name": "Gemini 4 Argon", "modalities": { "input": [ - "text" + "audio", + "image", + "text", + "video" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 4096 + "context": 1048576, + "output": 943718 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, - "open_weights": true, - "knowledge": "2023-12", - "release_date": "2024-07-23", - "last_updated": "2024-07-23", + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "low", + "medium", + "high" + ] + } + }, + "attachment": true, + "open_weights": false, + "knowledge": "2026-03", + "release_date": "2026-10-13", + "last_updated": "2026-10-13", "type": "chat" }, { - "id": "global.anthropic.claude-sonnet-4-6", - "name": "Claude Sonnet 4.6 (Global)", - "display_name": "Claude Sonnet 4.6 (Global)", + "id": "google/gemini-3.6-flash", + "name": "Gemini 3.6 Flash", + "display_name": "Gemini 3.6 Flash", "modalities": { "input": [ "text", "image", - "pdf" + "video", + "audio" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 1048576, + "output": 65536 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "mixed", - "budget": { - "min": 1024, - "unit": "tokens" - }, - "effort": "high", - "effort_options": [ + "default_enabled": true, + "mode": "level", + "level": "high", + "level_options": [ + "minimal", "low", "medium", - "high", - "max" + "high" ], - "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Anthropic recommends adaptive thinking with effort for Claude 4.6; budget_tokens remains a deprecated compatibility path.", - "Anthropic API defaults effort to high; lower effort levels should be chosen per workload." + "thought_signatures" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-02-17", - "last_updated": "2026-03-13", + "knowledge": "2026-03", + "release_date": "2026-07-21", + "last_updated": "2026-07-21", "type": "chat" }, { - "id": "meta.llama3-3-70b-instruct-v1:0", - "name": "Llama 3.3 70B Instruct", - "display_name": "Llama 3.3 70B Instruct", + "id": "google/gemma-3-27b-it", + "name": "Gemma 3 27B", + "display_name": "Gemma 3 27B", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 4096 + "context": 131072, + "output": 117964 }, "temperature": true, "tool_call": true, "reasoning": { "supported": false }, - "attachment": false, + "attachment": true, "open_weights": true, - "knowledge": "2023-12", - "release_date": "2024-12-06", - "last_updated": "2024-12-06", + "knowledge": "2024-08", + "release_date": "2025-03-12", + "last_updated": "2025-03-12", "type": "chat" }, { - "id": "us.anthropic.claude-opus-4-1-20250805-v1:0", - "name": "Claude Opus 4.1 (US)", - "display_name": "Claude Opus 4.1 (US)", + "id": "google/gemini-3.8-flash", + "name": "Gemini 3.8 Flash", + "display_name": "Gemini 3.8 Flash", "modalities": { "input": [ "text", "image", - "pdf" + "video", + "audio" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 32000 + "context": 1048576, + "output": 65536 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "budget", - "budget": { - "min": 1024, - "unit": "tokens" - }, - "interleaved": true, + "default_enabled": true, + "mode": "level", + "level": "high", + "level_options": [ + "minimal", + "low", + "medium", + "high" + ], "summaries": true, "visibility": "summary", "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", - "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." + "thought_signatures" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-03-31", - "release_date": "2025-08-05", - "last_updated": "2025-08-05", + "release_date": "2026-09-02", + "last_updated": "2026-09-02", "type": "chat" }, { - "id": "openai.gpt-5.6-sol", - "name": "GPT-5.6 Sol", - "display_name": "GPT-5.6 Sol", + "id": "google/gemini-3.1-pro-preview", + "name": "Gemini 3.1 Pro Preview", + "display_name": "Gemini 3.1 Pro Preview", "modalities": { "input": [ - "text", + "audio", "image", - "pdf" + "text", + "video" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 1048576, + "output": 65536 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -189250,90 +190454,97 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "none", + "supported": true, + "default_enabled": true, + "mode": "level", + "level": "high", + "level_options": [ "low", - "medium", - "high", - "xhigh", - "max" + "high" + ], + "summaries": true, + "visibility": "summary", + "continuation": [ + "thought_signatures" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-02-16", - "release_date": "2026-07-09", - "last_updated": "2026-07-09", + "knowledge": "2025-01", + "release_date": "2026-02-19", + "last_updated": "2026-02-19", "type": "chat" }, { - "id": "eu.anthropic.claude-haiku-4-5-20251001-v1:0", - "name": "Claude Haiku 4.5 (EU)", - "display_name": "Claude Haiku 4.5 (EU)", + "id": "google/gemini-3.7-flash", + "name": "Gemini 3.7 Flash", + "display_name": "Gemini 3.7 Flash", "modalities": { "input": [ "text", "image", - "pdf" + "video", + "audio" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 64000 + "context": 1048576, + "output": 65536 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "budget", - "budget": { - "min": 1024, - "unit": "tokens" - }, - "interleaved": true, + "default_enabled": true, + "mode": "level", + "level": "high", + "level_options": [ + "minimal", + "low", + "medium", + "high" + ], "summaries": true, "visibility": "summary", "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", - "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." + "thought_signatures" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-02-28", - "release_date": "2025-10-15", - "last_updated": "2025-10-15", + "knowledge": "2026-03", + "release_date": "2026-08-13", + "last_updated": "2026-08-13", "type": "chat" }, { - "id": "openai.gpt-oss-120b", - "name": "gpt-oss-120b", - "display_name": "gpt-oss-120b", + "id": "google/gemini-3-flash-preview", + "name": "Gemini 3 Flash Preview", + "display_name": "Gemini 3 Flash Preview", "modalities": { "input": [ - "text" + "text", + "image", + "audio", + "video" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 131072 + "context": 1048576, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -189343,66 +190554,63 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ + "supported": true, + "default_enabled": true, + "mode": "level", + "level": "high", + "level_options": [ + "minimal", "low", "medium", "high" + ], + "summaries": true, + "visibility": "summary", + "continuation": [ + "thought_signatures" ] } }, - "attachment": false, - "open_weights": true, - "knowledge": "2024-06", - "release_date": "2025-08-05", - "last_updated": "2025-08-05", + "attachment": true, + "open_weights": false, + "knowledge": "2025-01", + "release_date": "2025-12-17", + "last_updated": "2025-12-17", "type": "chat" }, { - "id": "global.anthropic.claude-haiku-5-5", - "name": "Claude Haiku 5.5 (Global)", - "display_name": "Claude Haiku 5.5 (Global)", + "id": "mistralai/mistral-large-2512", + "name": "Mistral Large 3 2512", + "display_name": "Mistral Large 3 2512", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 262144, + "output": 209715 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" - ] - } + "supported": false }, "attachment": true, - "open_weights": false, - "knowledge": "2026-06", - "release_date": "2026-10-07", - "last_updated": "2026-10-07", + "open_weights": true, + "knowledge": "2024-11", + "release_date": "2025-12-02", + "last_updated": "2025-12-02", "type": "chat" }, { - "id": "qwen.qwen3-next-80b-a3b", - "name": "Qwen3-Next 80B-A3B Instruct", - "display_name": "Qwen3-Next 80B-A3B Instruct", + "id": "mistralai/mixtral-8x22b-instruct", + "name": "Mixtral 8x22B Instruct", + "display_name": "Mixtral 8x22B Instruct", "modalities": { "input": [ "text" @@ -189412,8 +190620,8 @@ ] }, "limit": { - "context": 262144, - "output": 262000 + "context": 65536, + "output": 52428 }, "temperature": true, "tool_call": true, @@ -189422,15 +190630,15 @@ }, "attachment": false, "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-09-11", - "last_updated": "2025-11-25", + "knowledge": "2024-01-31", + "release_date": "2024-04-17", + "last_updated": "2024-04-17", "type": "chat" }, { - "id": "amazon.nova-micro-v1:0", - "name": "Nova Micro", - "display_name": "Nova Micro", + "id": "mistralai/mistral-nemo", + "name": "Mistral Nemo", + "display_name": "Mistral Nemo", "modalities": { "input": [ "text" @@ -189440,8 +190648,8 @@ ] }, "limit": { - "context": 128000, - "output": 10000 + "context": 131072, + "output": 16384 }, "temperature": true, "tool_call": true, @@ -189449,27 +190657,28 @@ "supported": false }, "attachment": false, - "open_weights": false, - "knowledge": "2024-10", - "release_date": "2024-12-03", - "last_updated": "2024-12-03", + "open_weights": true, + "knowledge": "2024-07", + "release_date": "2024-07-18", + "last_updated": "2024-07-18", "type": "chat" }, - { - "id": "minimax.minimax-m2", - "name": "MiniMax-M2", - "display_name": "MiniMax-M2", + { + "id": "mistralai/mistral-small-2603", + "name": "Mistral Small 4", + "display_name": "Mistral Small 4", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 204608, - "output": 128000 + "context": 262144, + "output": 209715 }, "temperature": true, "tool_call": true, @@ -189479,25 +190688,27 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true + "effort_options": [ + "none", + "high" + ] } }, - "attachment": false, + "attachment": true, "open_weights": true, - "release_date": "2025-10-27", - "last_updated": "2025-10-27", + "knowledge": "2025-06", + "release_date": "2026-03-16", + "last_updated": "2026-03-16", "type": "chat" }, { - "id": "jp.anthropic.claude-sonnet-4-6", - "name": "Claude Sonnet 4.6 (JP)", - "display_name": "Claude Sonnet 4.6 (JP)", + "id": "anthropic/claude-fable-5.1", + "name": "Claude Fable 5.1", + "display_name": "Claude Fable 5.1", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" @@ -189507,56 +190718,53 @@ "context": 1000000, "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "mixed", - "budget": { - "min": 1024, - "unit": "tokens" - }, + "default_enabled": true, + "mode": "effort", "effort": "high", "effort_options": [ "low", "medium", "high", + "xhigh", "max" ], "interleaved": true, "summaries": true, - "visibility": "summary", + "visibility": "omitted", "continuation": [ "thinking_blocks" ], "notes": [ - "Anthropic recommends adaptive thinking with effort for Claude 4.6; budget_tokens remains a deprecated compatibility path.", - "Anthropic API defaults effort to high; lower effort levels should be chosen per workload." + "Adaptive thinking is always on for Claude Fable 5 and Claude Mythos 5; thinking.type = \"disabled\" is rejected.", + "Manual budget_tokens requests return 400 on Claude Fable 5 and Claude Mythos 5.", + "thinking.display defaults to omitted; set display to summarized to receive readable thinking summaries." ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-02-17", - "last_updated": "2026-03-13", + "knowledge": "2026-06", + "release_date": "2026-09-01", + "last_updated": "2026-09-01", "type": "chat" }, { - "id": "global.anthropic.claude-opus-4-6-v1", - "name": "Claude Opus 4.6 (Global)", - "display_name": "Claude Opus 4.6 (Global)", + "id": "anthropic/claude-sonnet-5", + "name": "Claude Sonnet 5", + "display_name": "Claude Sonnet 5", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" @@ -189566,56 +190774,40 @@ "context": 1000000, "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "mixed", - "budget": { - "min": 1024, - "unit": "tokens" - }, - "effort": "high", "effort_options": [ + "none", "low", "medium", "high", + "xhigh", "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Anthropic recommends adaptive thinking with effort for Claude 4.6; budget_tokens remains a deprecated compatibility path.", - "Anthropic API defaults effort to high; lower effort levels should be chosen per workload." ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-05-31", - "release_date": "2026-02-05", - "last_updated": "2026-03-13", + "knowledge": "2026-01-31", + "release_date": "2026-06-30", + "last_updated": "2026-06-30", "type": "chat" }, { - "id": "au.anthropic.claude-opus-4-8", - "name": "Claude Opus 4.8 (AU)", - "display_name": "Claude Opus 4.8 (AU)", + "id": "anthropic/claude-fable-5", + "name": "Claude Fable 5", + "display_name": "Claude Fable 5", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" @@ -189629,12 +190821,12 @@ "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, + "default_enabled": true, "mode": "effort", "effort": "high", "effort_options": [ @@ -189651,36 +190843,35 @@ "thinking_blocks" ], "notes": [ - "Claude Opus 4.7 and newer Opus models require thinking.type = \"adaptive\" to enable thinking explicitly.", - "Manual budget_tokens requests return 400 on Claude Opus 4.7 and newer adaptive-only Opus models.", - "task_budget is separate from thinking control and should not be treated as a thinking budget." + "Adaptive thinking is always on for Claude Fable 5 and Claude Mythos 5; thinking.type = \"disabled\" is rejected.", + "Manual budget_tokens requests return 400 on Claude Fable 5 and Claude Mythos 5.", + "thinking.display defaults to omitted; set display to summarized to receive readable thinking summaries." ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-01", - "release_date": "2026-05-28", - "last_updated": "2026-05-28", + "knowledge": "2026-01-31", + "release_date": "2026-06-09", + "last_updated": "2026-06-09", "type": "chat" }, { - "id": "apac.anthropic.claude-sonnet-4-20250514-v1:0", - "name": "Claude Sonnet 4 (APAC)", - "display_name": "Claude Sonnet 4 (APAC)", + "id": "anthropic/claude-opus-4.6", + "name": "Claude Opus 4.6", + "display_name": "Claude Opus 4.6", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 64000 + "context": 1000000, + "output": 128000 }, "temperature": true, "tool_call": true, @@ -189692,11 +190883,18 @@ "reasoning": { "supported": true, "default_enabled": false, - "mode": "budget", + "mode": "mixed", "budget": { "min": 1024, "unit": "tokens" }, + "effort": "high", + "effort_options": [ + "low", + "medium", + "high", + "max" + ], "interleaved": true, "summaries": true, "visibility": "summary", @@ -189704,27 +190902,26 @@ "thinking_blocks" ], "notes": [ - "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", - "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." + "Anthropic recommends adaptive thinking with effort for Claude 4.6; budget_tokens remains a deprecated compatibility path.", + "Anthropic API defaults effort to high; lower effort levels should be chosen per workload." ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-03-31", - "release_date": "2025-05-22", - "last_updated": "2025-05-22", + "knowledge": "2025-05-31", + "release_date": "2026-02-05", + "last_updated": "2026-03-13", "type": "chat" }, { - "id": "global.anthropic.claude-opus-5-5", - "name": "Claude Opus 5.5 (Global)", - "display_name": "Claude Opus 5.5 (Global)", + "id": "anthropic/claude-sonnet-5.5", + "name": "Claude Sonnet 5.5", + "display_name": "Claude Sonnet 5.5", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" @@ -189754,14 +190951,14 @@ "attachment": true, "open_weights": false, "knowledge": "2026-06", - "release_date": "2026-09-22", - "last_updated": "2026-09-22", + "release_date": "2026-09-28", + "last_updated": "2026-09-28", "type": "chat" }, { - "id": "google.gemma-3-4b-it", - "name": "Gemma 3 4B IT", - "display_name": "Gemma 3 4B IT", + "id": "anthropic/claude-opus-4.7", + "name": "Claude Opus 4.7", + "display_name": "Claude Opus 4.7", "modalities": { "input": [ "text", @@ -189772,30 +190969,56 @@ ] }, "limit": { - "context": 131072, - "output": 4096 + "context": 1000000, + "output": 128000 }, - "temperature": true, - "tool_call": false, + "temperature": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": false + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": false, + "mode": "effort", + "effort": "high", + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "omitted", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Claude Opus 4.7 and newer Opus models require thinking.type = \"adaptive\" to enable thinking explicitly.", + "Manual budget_tokens requests return 400 on Claude Opus 4.7 and newer adaptive-only Opus models.", + "task_budget is separate from thinking control and should not be treated as a thinking budget." + ] + } }, "attachment": true, - "open_weights": true, - "knowledge": "2024-08", - "release_date": "2025-03-12", - "last_updated": "2025-03-12", + "open_weights": false, + "knowledge": "2026-01-31", + "release_date": "2026-04-16", + "last_updated": "2026-04-16", "type": "chat" }, { - "id": "global.anthropic.claude-opus-4-8", - "name": "Claude Opus 4.8 (Global)", - "display_name": "Claude Opus 4.8 (Global)", + "id": "anthropic/claude-opus-4.8", + "name": "Claude Opus 4.8", + "display_name": "Claude Opus 4.8", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" @@ -189845,14 +191068,13 @@ "type": "chat" }, { - "id": "au.anthropic.claude-opus-4-6-v1", - "name": "AU Anthropic Claude Opus 4.6", - "display_name": "AU Anthropic Claude Opus 4.6", + "id": "anthropic/claude-opus-5.5", + "name": "Claude Opus 5.5", + "display_name": "Claude Opus 5.5", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" @@ -189862,64 +191084,46 @@ "context": 1000000, "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "mixed", - "budget": { - "min": 1024, - "unit": "tokens" - }, - "effort": "high", "effort_options": [ "low", "medium", "high", + "xhigh", "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Anthropic recommends adaptive thinking with effort for Claude 4.6; budget_tokens remains a deprecated compatibility path.", - "Anthropic API defaults effort to high; lower effort levels should be chosen per workload." ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-05-31", - "release_date": "2026-02-05", - "last_updated": "2026-03-13", + "knowledge": "2026-06", + "release_date": "2026-09-22", + "last_updated": "2026-09-22", "type": "chat" }, { - "id": "eu.anthropic.claude-sonnet-4-20250514-v1:0", - "name": "Claude Sonnet 4 (EU)", - "display_name": "Claude Sonnet 4 (EU)", + "id": "anthropic/claude-sonnet-4.6", + "name": "Claude Sonnet 4.6", + "display_name": "Claude Sonnet 4.6", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 64000 + "context": 1000000, + "output": 128000 }, "temperature": true, "tool_call": true, @@ -189931,11 +191135,18 @@ "reasoning": { "supported": true, "default_enabled": false, - "mode": "budget", + "mode": "mixed", "budget": { "min": 1024, "unit": "tokens" }, + "effort": "high", + "effort_options": [ + "low", + "medium", + "high", + "max" + ], "interleaved": true, "summaries": true, "visibility": "summary", @@ -189943,22 +191154,22 @@ "thinking_blocks" ], "notes": [ - "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", - "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." + "Anthropic recommends adaptive thinking with effort for Claude 4.6; budget_tokens remains a deprecated compatibility path.", + "Anthropic API defaults effort to high; lower effort levels should be chosen per workload." ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-03-31", - "release_date": "2025-05-22", - "last_updated": "2025-05-22", + "knowledge": "2025-08-31", + "release_date": "2026-02-17", + "last_updated": "2026-03-13", "type": "chat" }, { - "id": "us.openai.gpt-5.6-terra", - "name": "GPT-5.6 Terra (US)", - "display_name": "GPT-5.6 Terra (US)", + "id": "anthropic/claude-opus-5", + "name": "Claude Opus 5", + "display_name": "Claude Opus 5", "modalities": { "input": [ "text", @@ -189969,7 +191180,7 @@ ] }, "limit": { - "context": 1050000, + "context": 1000000, "output": 128000 }, "temperature": false, @@ -189980,6 +191191,7 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, "effort_options": [ "none", "low", @@ -189992,49 +191204,132 @@ }, "attachment": true, "open_weights": false, - "knowledge": "2026-02-16", - "release_date": "2026-07-09", - "last_updated": "2026-07-09", + "knowledge": "2026-05", + "release_date": "2026-07-24", + "last_updated": "2026-07-24", "type": "chat" }, { - "id": "us.deepseek.r1-v1:0", - "name": "DeepSeek-R1 (US)", - "display_name": "DeepSeek-R1 (US)", + "id": "qwen/qwen3-vl-235b-a22b-instruct", + "name": "Qwen3 VL 235B A22B Instruct", + "display_name": "Qwen3 VL 235B A22B Instruct", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 128000, + "context": 262144, "output": 32768 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": true, - "default": true + "supported": false + }, + "attachment": true, + "open_weights": true, + "knowledge": "2025-03-31", + "release_date": "2025-09-23", + "last_updated": "2025-09-23", + "type": "chat" + }, + { + "id": "qwen/qwen3-vl-30b-a3b-instruct", + "name": "Qwen3 VL 30B A3B Instruct", + "display_name": "Qwen3 VL 30B A3B Instruct", + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 262144, + "output": 16384 + }, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": false + }, + "attachment": true, + "open_weights": true, + "release_date": "2025-10-04", + "last_updated": "2025-10-04", + "type": "chat" + }, + { + "id": "qwen/qwen3-30b-a3b-instruct-2507", + "name": "Qwen3 30B A3B Instruct 2507", + "display_name": "Qwen3 30B A3B Instruct 2507", + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 262144, + "output": 235929 + }, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": false }, "attachment": false, "open_weights": true, - "knowledge": "2024-07", - "release_date": "2025-01-20", - "last_updated": "2025-05-29", + "release_date": "2025-07-28", + "last_updated": "2025-07-28", "type": "chat" }, { - "id": "eu.anthropic.claude-opus-4-6-v1", - "name": "Claude Opus 4.6 (EU)", - "display_name": "Claude Opus 4.6 (EU)", + "id": "qwen/qwen3-coder-next", + "name": "Qwen3 Coder Next", + "display_name": "Qwen3 Coder Next", + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 262144, + "output": 235929 + }, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": false + }, + "attachment": false, + "open_weights": true, + "knowledge": "2025-09", + "release_date": "2026-02-03", + "last_updated": "2026-02-03", + "type": "chat" + }, + { + "id": "qwen/qwen3.8-27b", + "name": "Qwen3.8 27B", + "display_name": "Qwen3.8 27B", "modalities": { "input": [ "text", "image", - "pdf" + "video" ], "output": [ "text" @@ -190042,151 +191337,128 @@ }, "limit": { "context": 1000000, - "output": 128000 + "output": 235929 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "mixed", - "budget": { - "min": 1024, - "unit": "tokens" - }, - "effort": "high", "effort_options": [ + "none", "low", "medium", - "high", - "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Anthropic recommends adaptive thinking with effort for Claude 4.6; budget_tokens remains a deprecated compatibility path.", - "Anthropic API defaults effort to high; lower effort levels should be chosen per workload." + "xhigh" ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2025-05-31", - "release_date": "2026-02-05", - "last_updated": "2026-03-13", + "open_weights": true, + "release_date": "2026-08-14", + "last_updated": "2026-08-14", "type": "chat" }, { - "id": "us.amazon.nova-lite-v1:0", - "name": "Nova Lite (US)", - "display_name": "Nova Lite (US)", + "id": "deepseek/deepseek-v4.1-flash-1017", + "name": "DeepSeek V4.1 Flash 1017", + "display_name": "DeepSeek V4.1 Flash 1017", "modalities": { "input": [ "text", - "image", - "video", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 300000, - "output": 10000 + "context": 1048576, + "output": 131072 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "none", + "low", + "high", + "max" + ] + } }, "attachment": true, - "open_weights": false, - "knowledge": "2024-10", - "release_date": "2024-12-03", - "last_updated": "2024-12-03", + "open_weights": true, + "knowledge": "2025-05", + "release_date": "2026-10-17", + "last_updated": "2026-10-17", "type": "chat" }, { - "id": "jp.anthropic.claude-haiku-4-5-20251001-v1:0", - "name": "Claude Haiku 4.5 (JP)", - "display_name": "Claude Haiku 4.5 (JP)", + "id": "deepseek/deepseek-v4.1-flash", + "name": "DeepSeek V4.1 Flash", + "display_name": "DeepSeek V4.1 Flash", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 64000 + "context": 1048576, + "output": 131072 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "budget", - "budget": { - "min": 1024, - "unit": "tokens" - }, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", - "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." + "effort_options": [ + "none", + "low", + "high", + "max" ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2025-02-28", - "release_date": "2025-10-15", - "last_updated": "2025-10-15", + "open_weights": true, + "knowledge": "2025-05", + "release_date": "2026-09-10", + "last_updated": "2026-09-10", "type": "chat" }, { - "id": "au.anthropic.claude-sonnet-5-5", - "name": "Claude Sonnet 5.5 (AU)", - "display_name": "Claude Sonnet 5.5 (AU)", + "id": "deepseek/deepseek-v4-pro", + "name": "DeepSeek V4 Pro 0423", + "display_name": "DeepSeek V4 Pro 0423", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 1048576, + "output": 393216 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -190194,41 +191466,50 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "high", "effort_options": [ "low", - "medium", "high", - "xhigh", "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-06", - "release_date": "2026-09-28", - "last_updated": "2026-09-28", + "attachment": false, + "open_weights": true, + "knowledge": "2025-05", + "release_date": "2026-04-24", + "last_updated": "2026-04-24", "type": "chat" }, { - "id": "eu.anthropic.claude-fable-5", - "name": "Claude Fable 5 (EU)", - "display_name": "Claude Fable 5 (EU)", + "id": "deepseek/deepseek-v4-flash", + "name": "DeepSeek V4 Flash 0423", + "display_name": "DeepSeek V4 Flash 0423", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 1048576, + "output": 943717 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -190242,50 +191523,125 @@ "effort": "high", "effort_options": [ "low", - "medium", "high", - "xhigh", "max" ], "interleaved": true, "summaries": true, - "visibility": "omitted", + "visibility": "summary", "continuation": [ "thinking_blocks" ], "notes": [ - "Adaptive thinking is always on for Claude Fable 5 and Claude Mythos 5; thinking.type = \"disabled\" is rejected.", - "Manual budget_tokens requests return 400 on Claude Fable 5 and Claude Mythos 5.", - "thinking.display defaults to omitted; set display to summarized to receive readable thinking summaries." + "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." + ] + } + }, + "attachment": false, + "open_weights": true, + "knowledge": "2025-05", + "release_date": "2026-04-24", + "last_updated": "2026-04-24", + "type": "chat" + }, + { + "id": "deepseek/deepseek-v4-flash-0731", + "name": "DeepSeek V4 Flash 0731", + "display_name": "DeepSeek V4 Flash 0731", + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1048576, + "output": 943718 + }, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "effort_options": [ + "none", + "low", + "high", + "max" + ] + } + }, + "attachment": false, + "open_weights": true, + "knowledge": "2025-05", + "release_date": "2026-07-31", + "last_updated": "2026-07-31", + "type": "chat" + }, + { + "id": "x-ai/grok-4.7", + "name": "Grok 4.7", + "display_name": "Grok 4.7", + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 500000, + "output": 450000 + }, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "low", + "medium", + "high", + "xhigh" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-01-31", - "release_date": "2026-06-09", - "last_updated": "2026-06-09", + "knowledge": "2026-05", + "release_date": "2026-09-21", + "last_updated": "2026-09-21", "type": "chat" }, { - "id": "jp.anthropic.claude-opus-5-5", - "name": "Claude Opus 5.5 (JP)", - "display_name": "Claude Opus 5.5 (JP)", + "id": "x-ai/grok-4.6", + "name": "Grok 4.6", + "display_name": "Grok 4.6", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 500000, + "output": 450000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -190293,41 +191649,42 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, "effort_options": [ "low", "medium", "high", - "xhigh", - "max" + "xhigh" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-06", - "release_date": "2026-09-22", - "last_updated": "2026-09-22", + "knowledge": "2026-02-01", + "release_date": "2026-08-12", + "last_updated": "2026-08-12", "type": "chat" }, { - "id": "openai.gpt-5.6-terra", - "name": "GPT-5.6 Terra", - "display_name": "GPT-5.6 Terra", + "id": "meta/muse-spark-open", + "name": "Muse Spark Open", + "display_name": "Muse Spark Open", "modalities": { "input": [ "text", "image", - "pdf" + "video", + "audio" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 1048576, + "output": 943718 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -190336,26 +191693,83 @@ "extra_capabilities": { "reasoning": { "effort_options": [ - "none", "low", "medium", "high", - "xhigh", - "max" + "xhigh" + ] + } + }, + "attachment": true, + "open_weights": true, + "knowledge": "2026-01-04", + "release_date": "2026-10-15", + "last_updated": "2026-10-15", + "type": "chat" + } + ] + }, + "abacus": { + "id": "abacus", + "name": "Abacus", + "display_name": "Abacus", + "api": "https://routellm.abacus.ai/v1", + "doc": "https://abacus.ai/help/api", + "models": [ + { + "id": "gemini-3.5-flash", + "name": "Gemini 3.5 Flash", + "display_name": "Gemini 3.5 Flash", + "modalities": { + "input": [ + "text", + "image", + "audio" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1048576, + "output": 65536 + }, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "level", + "level": "high", + "level_options": [ + "minimal", + "low", + "medium", + "high" + ], + "summaries": true, + "visibility": "summary", + "continuation": [ + "thought_signatures" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-02-16", - "release_date": "2026-07-09", - "last_updated": "2026-07-09", + "knowledge": "2025-01", + "release_date": "2026-05-19", + "last_updated": "2026-05-19", "type": "chat" }, { - "id": "us.meta.llama3-1-70b-instruct-v1:0", - "name": "Llama 3.1 70B Instruct (US)", - "display_name": "Llama 3.1 70B Instruct (US)", + "id": "o3-mini", + "name": "o3-mini", + "display_name": "o3-mini", "modalities": { "input": [ "text" @@ -190365,25 +191779,40 @@ ] }, "limit": { - "context": 128000, - "output": 4096 + "context": 200000, + "output": 100000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", + "effort_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" + } }, "attachment": false, - "open_weights": true, - "knowledge": "2023-12", - "release_date": "2024-07-23", - "last_updated": "2024-07-23", + "open_weights": false, + "knowledge": "2024-05", + "release_date": "2024-12-20", + "last_updated": "2025-01-29", "type": "chat" }, { - "id": "us.meta.llama4-maverick-17b-instruct-v1:0", - "name": "Llama 4 Maverick 17B Instruct (US)", - "display_name": "Llama 4 Maverick 17B Instruct (US)", + "id": "gpt-5.4-nano", + "name": "GPT-5.4 nano", + "display_name": "GPT-5.4 nano", "modalities": { "input": [ "text", @@ -190394,109 +191823,192 @@ ] }, "limit": { - "context": 1000000, - "output": 8192 + "context": 400000, + "output": 128000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": false + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": false, + "mode": "effort", + "effort": "none", + "effort_options": [ + "none", + "low", + "medium", + "high", + "xhigh" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" + } }, "attachment": true, - "open_weights": true, - "knowledge": "2024-08", - "release_date": "2025-04-05", - "last_updated": "2025-04-05", + "open_weights": false, + "knowledge": "2025-08-31", + "release_date": "2026-03-17", + "last_updated": "2026-03-17", "type": "chat" }, { - "id": "us.anthropic.claude-opus-5-5", - "name": "Claude Opus 5.5 (US)", - "display_name": "Claude Opus 5.5 (US)", + "id": "gpt-5.4", + "name": "GPT-5.4", + "display_name": "GPT-5.4", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, + "context": 400000, "output": 128000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": false, + "mode": "effort", + "effort": "none", "effort_options": [ + "none", "low", "medium", "high", - "xhigh", - "max" - ] + "xhigh" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, "attachment": true, "open_weights": false, - "knowledge": "2026-06", - "release_date": "2026-09-22", - "last_updated": "2026-09-22", + "knowledge": "2025-08-31", + "release_date": "2026-03-05", + "last_updated": "2026-03-05", "type": "chat" }, { - "id": "au.anthropic.claude-sonnet-5", - "name": "Claude Sonnet 5 (AU)", - "display_name": "Claude Sonnet 5 (AU)", + "id": "gpt-5.4-mini", + "name": "GPT-5.4 mini", + "display_name": "GPT-5.4 mini", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, + "context": 400000, "output": 128000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": false, + "mode": "effort", + "effort": "none", "effort_options": [ + "none", "low", "medium", "high", - "xhigh", - "max" - ] + "xhigh" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, "attachment": true, "open_weights": false, - "knowledge": "2026-01-31", - "release_date": "2026-06-30", - "last_updated": "2026-06-30", + "knowledge": "2025-08-31", + "release_date": "2026-03-17", + "last_updated": "2026-03-17", "type": "chat" }, { - "id": "global.openai.gpt-5.6-terra", - "name": "GPT-5.6 Terra (Global)", - "display_name": "GPT-5.6 Terra (Global)", + "id": "qwen3-max", + "name": "Qwen3 Max", + "display_name": "Qwen3 Max", + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "output": 16384 + }, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } + }, + "attachment": false, + "open_weights": false, + "knowledge": "2025-04", + "release_date": "2025-09-23", + "last_updated": "2025-09-23", + "type": "chat" + }, + { + "id": "gpt-5.1-codex-max", + "name": "GPT-5.1 Codex Max", + "display_name": "GPT-5.1 Codex Max", "modalities": { "input": [ "text", @@ -190507,43 +192019,51 @@ ] }, "limit": { - "context": 1050000, + "context": 400000, "output": 128000 }, "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": false, + "mode": "effort", + "effort": "none", "effort_options": [ "none", "low", "medium", - "high", - "xhigh", - "max" - ] + "high" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, "attachment": true, "open_weights": false, - "knowledge": "2026-02-16", - "release_date": "2026-07-09", - "last_updated": "2026-07-09", + "knowledge": "2024-09-30", + "release_date": "2025-11-13", + "last_updated": "2025-11-13", "type": "chat" }, { - "id": "eu.anthropic.claude-opus-4-8", - "name": "Claude Opus 4.8 (EU)", - "display_name": "Claude Opus 4.8 (EU)", + "id": "gpt-5.6-terra", + "name": "GPT-5.6 Terra", + "display_name": "GPT-5.6 Terra", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" @@ -190557,105 +192077,42 @@ "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, + "default_enabled": true, "mode": "effort", - "effort": "high", + "effort": "medium", "effort_options": [ + "none", "low", "medium", "high", "xhigh", "max" ], - "interleaved": true, - "summaries": true, - "visibility": "omitted", - "continuation": [ - "thinking_blocks" + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" ], - "notes": [ - "Claude Opus 4.7 and newer Opus models require thinking.type = \"adaptive\" to enable thinking explicitly.", - "Manual budget_tokens requests return 400 on Claude Opus 4.7 and newer adaptive-only Opus models.", - "task_budget is separate from thinking control and should not be treated as a thinking budget." - ] + "visibility": "hidden" } }, "attachment": true, "open_weights": false, - "knowledge": "2026-01", - "release_date": "2026-05-28", - "last_updated": "2026-05-28", - "type": "chat" - }, - { - "id": "nvidia.nemotron-super-3-120b", - "name": "NVIDIA Nemotron 3 Super 120B A12B", - "display_name": "NVIDIA Nemotron 3 Super 120B A12B", - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 262144, - "output": 131072 - }, - "temperature": true, - "tool_call": true, - "reasoning": { - "supported": true, - "default": true - }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-06", - "release_date": "2026-03-11", - "last_updated": "2026-03-11", - "type": "chat" - }, - { - "id": "amazon.nova-pro-v1:0", - "name": "Nova Pro", - "display_name": "Nova Pro", - "modalities": { - "input": [ - "text", - "image", - "video", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 300000, - "output": 10000 - }, - "temperature": true, - "tool_call": true, - "reasoning": { - "supported": false - }, - "attachment": true, - "open_weights": false, - "knowledge": "2024-10", - "release_date": "2024-12-03", - "last_updated": "2024-12-03", + "knowledge": "2026-02-16", + "release_date": "2026-07-09", + "last_updated": "2026-07-09", "type": "chat" }, { - "id": "global.openai.gpt-5.6-luna", - "name": "GPT-5.6 Luna (Global)", - "display_name": "GPT-5.6 Luna (Global)", + "id": "gpt-5.3-codex", + "name": "GPT-5.3 Codex", + "display_name": "GPT-5.3 Codex", "modalities": { "input": [ "text", @@ -190666,10 +192123,10 @@ ] }, "limit": { - "context": 1050000, + "context": 400000, "output": 128000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -190677,27 +192134,36 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", "effort_options": [ - "none", "low", "medium", "high", - "xhigh", - "max" - ] + "xhigh" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, "attachment": true, "open_weights": false, - "knowledge": "2026-02-16", - "release_date": "2026-07-09", - "last_updated": "2026-07-09", + "knowledge": "2025-08-31", + "release_date": "2026-02-05", + "last_updated": "2026-02-05", "type": "chat" }, { - "id": "us.anthropic.claude-opus-4-5-20251101-v1:0", - "name": "Claude Opus 4.5 (US)", - "display_name": "Claude Opus 4.5 (US)", + "id": "gpt-5.3-codex-xhigh", + "name": "GPT-5.3 Codex XHigh", + "display_name": "GPT-5.3 Codex XHigh", "modalities": { "input": [ "text", @@ -190709,58 +192175,51 @@ ] }, "limit": { - "context": 200000, - "output": 64000 + "context": 400000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "mixed", - "budget": { - "min": 1024, - "unit": "tokens" - }, - "effort": "high", + "default_enabled": true, + "mode": "effort", + "effort": "medium", "effort_options": [ "low", "medium", - "high" + "high", + "xhigh" ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" ], - "notes": [ - "Claude Opus 4.5 uses manual thinking.type = \"enabled\" with budget_tokens; effort can be used alongside the thinking budget.", - "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header." - ] + "visibility": "hidden" } }, "attachment": true, "open_weights": false, - "knowledge": "2025-05", - "release_date": "2025-11-24", - "last_updated": "2025-11-24", + "knowledge": "2025-08-31", + "release_date": "2026-02-05", + "last_updated": "2026-02-05", "type": "chat" }, { - "id": "us.anthropic.claude-sonnet-4-20250514-v1:0", - "name": "Claude Sonnet 4 (US)", - "display_name": "Claude Sonnet 4 (US)", + "id": "claude-haiku-4-5-20251001", + "name": "Claude Haiku 4.5", + "display_name": "Claude Haiku 4.5", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" @@ -190799,113 +192258,109 @@ }, "attachment": true, "open_weights": false, - "knowledge": "2025-03-31", - "release_date": "2025-05-22", - "last_updated": "2025-05-22", + "knowledge": "2025-02-28", + "release_date": "2025-10-15", + "last_updated": "2025-10-15", "type": "chat" }, { - "id": "anthropic.claude-haiku-5-5", - "name": "Claude Haiku 5.5", - "display_name": "Claude Haiku 5.5", + "id": "gpt-5.3-chat-latest", + "name": "GPT-5.3 Chat Latest", + "display_name": "GPT-5.3 Chat Latest", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, + "context": 400000, "output": 128000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" - ] - } + "supported": false }, "attachment": true, "open_weights": false, - "knowledge": "2026-06", - "release_date": "2026-10-07", - "last_updated": "2026-10-07", + "knowledge": "2025-08-31", + "release_date": "2026-03-03", + "last_updated": "2026-03-03", "type": "chat" }, { - "id": "openai.gpt-6-luna", - "name": "GPT-6 Luna", - "display_name": "GPT-6 Luna", + "id": "gpt-5.1-codex", + "name": "GPT-5.1 Codex", + "display_name": "GPT-5.1 Codex", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1050000, + "context": 400000, "output": 128000 }, "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": false, + "mode": "effort", + "effort": "none", "effort_options": [ "none", "low", "medium", - "high", - "xhigh", - "max" - ] + "high" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, "attachment": true, "open_weights": false, - "knowledge": "2026-05-18", - "release_date": "2026-09-22", - "last_updated": "2026-09-22", + "knowledge": "2024-09-30", + "release_date": "2025-11-13", + "last_updated": "2025-11-13", "type": "chat" }, { - "id": "openai.gpt-oss-120b-1:0", - "name": "gpt-oss-120b", - "display_name": "gpt-oss-120b", + "id": "gpt-5.2-codex", + "name": "GPT-5.2 Codex", + "display_name": "GPT-5.2 Codex", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 131072, + "context": 400000, "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -190913,118 +192368,117 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", "effort_options": [ + "low", + "medium", + "high", + "xhigh" + ], + "verbosity": "medium", + "verbosity_options": [ "low", "medium", "high" - ] + ], + "visibility": "hidden" } }, - "attachment": false, - "open_weights": true, - "knowledge": "2024-06", - "release_date": "2025-08-05", - "last_updated": "2025-08-05", + "attachment": true, + "open_weights": false, + "knowledge": "2025-08-31", + "release_date": "2025-12-11", + "last_updated": "2025-12-11", "type": "chat" }, { - "id": "jp.anthropic.claude-opus-4-7", - "name": "Claude Opus 4.7 (JP)", - "display_name": "Claude Opus 4.7 (JP)", + "id": "grok-4-0709", + "name": "Grok 4", + "display_name": "Grok 4", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 256000, + "output": 16384 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "high", - "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "omitted", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Claude Opus 4.7 and newer Opus models require thinking.type = \"adaptive\" to enable thinking explicitly.", - "Manual budget_tokens requests return 400 on Claude Opus 4.7 and newer adaptive-only Opus models.", - "task_budget is separate from thinking control and should not be treated as a thinking budget." - ] - } + "default": true }, "attachment": true, "open_weights": false, - "knowledge": "2026-01-31", - "release_date": "2026-04-16", - "last_updated": "2026-04-16", + "release_date": "2025-07-09", + "last_updated": "2025-07-09", "type": "chat" }, { - "id": "openai.gpt-oss-safeguard-120b", - "name": "GPT OSS Safeguard 120B", - "display_name": "GPT OSS Safeguard 120B", + "id": "gpt-5.2", + "name": "GPT-5.2", + "display_name": "GPT-5.2", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 16384 + "context": 400000, + "output": 128000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": false, + "mode": "effort", + "effort": "none", "effort_options": [ + "none", + "low", + "medium", + "high", + "xhigh" + ], + "verbosity": "medium", + "verbosity_options": [ "low", "medium", "high" - ] + ], + "visibility": "hidden" } }, - "attachment": false, - "open_weights": true, - "release_date": "2025-10-29", - "last_updated": "2025-10-29", + "attachment": true, + "open_weights": false, + "knowledge": "2025-08-31", + "release_date": "2025-12-11", + "last_updated": "2025-12-11", "type": "chat" }, { - "id": "minimax.minimax-m2.5", - "name": "MiniMax-M2.5", - "display_name": "MiniMax-M2.5", + "id": "qwen-2.5-coder-32b", + "name": "Qwen 2.5 Coder 32B", + "display_name": "Qwen 2.5 Coder 32B", "modalities": { "input": [ "text" @@ -191034,36 +192488,28 @@ ] }, "limit": { - "context": 196608, - "output": 98304 + "context": 128000, + "output": 8192 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true - } + "supported": false }, "attachment": false, "open_weights": true, - "release_date": "2026-02-12", - "last_updated": "2026-03-18", + "release_date": "2024-11-12", + "last_updated": "2024-11-12", "type": "chat" }, { - "id": "anthropic.claude-haiku-4-5-20251001-v1:0", - "name": "Claude Haiku 4.5", - "display_name": "Claude Haiku 4.5", + "id": "claude-sonnet-4-5-20250929", + "name": "Claude Sonnet 4.5", + "display_name": "Claude Sonnet 4.5", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" @@ -191102,20 +192548,20 @@ }, "attachment": true, "open_weights": false, - "knowledge": "2025-02-28", - "release_date": "2025-10-15", - "last_updated": "2025-10-15", + "knowledge": "2025-07-31", + "release_date": "2025-09-29", + "last_updated": "2025-09-29", "type": "chat" }, { - "id": "au.anthropic.claude-sonnet-4-6", - "name": "AU Anthropic Claude Sonnet 4.6", - "display_name": "AU Anthropic Claude Sonnet 4.6", + "id": "muse-spark-1.1", + "name": "Muse Spark 1.1", + "display_name": "Muse Spark 1.1", "modalities": { "input": [ "text", "image", - "pdf" + "video" ], "output": [ "text" @@ -191123,185 +192569,144 @@ }, "limit": { "context": 1000000, - "output": 128000 + "output": 32000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "mixed", - "budget": { - "min": 1024, - "unit": "tokens" - }, - "effort": "high", - "effort_options": [ - "low", - "medium", - "high", - "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Anthropic recommends adaptive thinking with effort for Claude 4.6; budget_tokens remains a deprecated compatibility path.", - "Anthropic API defaults effort to high; lower effort levels should be chosen per workload." - ] + "supported": true } }, "attachment": true, "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-02-17", - "last_updated": "2026-03-13", + "release_date": "2026-04-08", + "last_updated": "2026-07-09", "type": "chat" }, { - "id": "us.writer.palmyra-x4-v1:0", - "name": "Palmyra X4 (US)", - "display_name": "Palmyra X4 (US)", + "id": "gemini-3.1-flash-image", + "name": "Nano Banana 2", + "display_name": "Nano Banana 2", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ - "text" + "text", + "image" ] }, "limit": { - "context": 122880, - "output": 8192 + "context": 1048576, + "output": 32768 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { "supported": true, "default": true }, - "attachment": false, + "extra_capabilities": { + "reasoning": { + "supported": true + } + }, + "attachment": true, "open_weights": false, - "release_date": "2024-10-09", - "last_updated": "2025-04-28", - "type": "chat" + "knowledge": "2025-01", + "release_date": "2026-05-28", + "last_updated": "2026-05-28" }, { - "id": "anthropic.claude-opus-4-7", - "name": "Claude Opus 4.7", - "display_name": "Claude Opus 4.7", + "id": "gemini-3-pro-image", + "name": "Nano Banana Pro", + "display_name": "Nano Banana Pro", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ - "text" + "text", + "image" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 65536, + "output": 32768 }, - "temperature": false, - "tool_call": true, + "temperature": true, + "tool_call": false, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "high", - "effort_options": [ + "default_enabled": true, + "mode": "level", + "level": "high", + "level_options": [ "low", - "medium", - "high", - "xhigh", - "max" + "high" ], - "interleaved": true, "summaries": true, - "visibility": "omitted", + "visibility": "summary", "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Claude Opus 4.7 and newer Opus models require thinking.type = \"adaptive\" to enable thinking explicitly.", - "Manual budget_tokens requests return 400 on Claude Opus 4.7 and newer adaptive-only Opus models.", - "task_budget is separate from thinking control and should not be treated as a thinking budget." + "thought_signatures" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-01-31", - "release_date": "2026-04-16", - "last_updated": "2026-04-16", - "type": "chat" + "knowledge": "2025-01", + "release_date": "2026-05-28", + "last_updated": "2026-05-28" }, { - "id": "openai.gpt-6-astra", - "name": "GPT-6 Astra", - "display_name": "GPT-6 Astra", + "id": "gpt-4o-2024-11-20", + "name": "GPT-4o (2024-11-20)", + "display_name": "GPT-4o (2024-11-20)", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 128000, + "output": 16384 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" - ] - } + "supported": false }, "attachment": true, "open_weights": false, - "knowledge": "2026-04-30", - "release_date": "2026-09-04", - "last_updated": "2026-09-04", + "knowledge": "2024-10", + "release_date": "2024-11-20", + "last_updated": "2024-11-20", "type": "chat" }, { - "id": "anthropic.claude-sonnet-5", - "name": "Claude Sonnet 5", - "display_name": "Claude Sonnet 5", + "id": "claude-opus-4-7", + "name": "Claude Opus 4.7", + "display_name": "Claude Opus 4.7", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" @@ -191315,30 +192720,45 @@ "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": false, + "mode": "effort", + "effort": "high", "effort_options": [ "low", "medium", "high", "xhigh", "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "omitted", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Claude Opus 4.7 and newer Opus models require thinking.type = \"adaptive\" to enable thinking explicitly.", + "Manual budget_tokens requests return 400 on Claude Opus 4.7 and newer adaptive-only Opus models.", + "task_budget is separate from thinking control and should not be treated as a thinking budget." ] } }, "attachment": true, "open_weights": false, "knowledge": "2026-01-31", - "release_date": "2026-06-30", - "last_updated": "2026-06-30", + "release_date": "2026-04-16", + "last_updated": "2026-04-16", "type": "chat" }, { - "id": "qwen.qwen3-coder-next", - "name": "Qwen3 Coder Next", - "display_name": "Qwen3 Coder Next", + "id": "qwen3.7-max", + "name": "Qwen3.7 Max", + "display_name": "Qwen3.7 Max", "modalities": { "input": [ "text" @@ -191348,37 +192768,41 @@ ] }, "limit": { - "context": 262144, - "output": 65536 + "context": 1000000, + "output": 64000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true + } }, "attachment": false, - "open_weights": true, - "knowledge": "2025-09", - "release_date": "2026-02-03", - "last_updated": "2026-02-06", + "open_weights": false, + "release_date": "2026-05-21", + "last_updated": "2026-05-21", "type": "chat" }, { - "id": "openai.gpt-6-sol", - "name": "GPT-6 Sol", - "display_name": "GPT-6 Sol", + "id": "gpt-5-codex", + "name": "GPT-5-Codex", + "display_name": "GPT-5-Codex", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1050000, + "context": 400000, "output": 128000 }, "temperature": false, @@ -191389,40 +192813,50 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", "effort_options": [ - "none", + "minimal", "low", "medium", - "high", - "xhigh", - "max" - ] + "high" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, "attachment": true, "open_weights": false, - "knowledge": "2026-04-20", - "release_date": "2026-09-22", - "last_updated": "2026-09-22", + "knowledge": "2024-09-30", + "release_date": "2025-09-15", + "last_updated": "2025-09-15", "type": "chat" }, { - "id": "us-gov.openai.gpt-oss-20b-1:0", - "name": "gpt-oss-20b (GovCloud)", - "display_name": "gpt-oss-20b (GovCloud)", + "id": "claude-sonnet-5", + "name": "Claude Sonnet 5", + "display_name": "Claude Sonnet 5", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 16384 + "context": 1000000, + "output": 64000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -191430,24 +192864,20 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "low", - "medium", - "high" - ] + "supported": true } }, - "attachment": false, - "open_weights": true, - "knowledge": "2024-06", - "release_date": "2025-08-05", - "last_updated": "2025-08-05", + "attachment": true, + "open_weights": false, + "knowledge": "2026-01-31", + "release_date": "2026-06-30", + "last_updated": "2026-06-30", "type": "chat" }, { - "id": "nvidia.nemotron-nano-12b-v2", - "name": "NVIDIA Nemotron Nano 12B v2 VL BF16", - "display_name": "NVIDIA Nemotron Nano 12B v2 VL BF16", + "id": "grok-4.5", + "name": "Grok 4.5", + "display_name": "Grok 4.5", "modalities": { "input": [ "text", @@ -191458,29 +192888,34 @@ ] }, "limit": { - "context": 131072, - "output": 8192 + "context": 500000, + "output": 32768 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true + } }, "attachment": true, - "open_weights": true, - "release_date": "2025-10-28", - "last_updated": "2025-10-28", + "open_weights": false, + "release_date": "2026-07-08", + "last_updated": "2026-07-08", "type": "chat" }, { - "id": "eu.anthropic.claude-opus-5", - "name": "Claude Opus 5 (EU)", - "display_name": "Claude Opus 5 (EU)", + "id": "claude-fable-5", + "name": "Claude Fable 5", + "display_name": "Claude Fable 5", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" @@ -191498,26 +192933,41 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "high", "effort_options": [ "low", "medium", "high", "xhigh", "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "omitted", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Adaptive thinking is always on for Claude Fable 5 and Claude Mythos 5; thinking.type = \"disabled\" is rejected.", + "Manual budget_tokens requests return 400 on Claude Fable 5 and Claude Mythos 5.", + "thinking.display defaults to omitted; set display to summarized to receive readable thinking summaries." ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-05", - "release_date": "2026-07-24", - "last_updated": "2026-07-24", + "knowledge": "2026-01-31", + "release_date": "2026-06-09", + "last_updated": "2026-06-09", "type": "chat" }, { - "id": "us.moonshotai.kimi-k3", - "name": "Kimi K3 (US)", - "display_name": "Kimi K3 (US)", + "id": "gpt-4.1-nano", + "name": "GPT-4.1 nano", + "display_name": "GPT-4.1 nano", "modalities": { "input": [ "text", @@ -191528,31 +192978,25 @@ ] }, "limit": { - "context": 1048576, - "output": 128000 + "context": 1047576, + "output": 32768 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true - } + "supported": false }, "attachment": true, - "open_weights": true, - "release_date": "2026-07-16", - "last_updated": "2026-07-16", + "open_weights": false, + "knowledge": "2024-04", + "release_date": "2025-04-14", + "last_updated": "2025-04-14", "type": "chat" }, { - "id": "global.openai.gpt-6.1-terra", - "name": "GPT-6.1 Terra (Global)", - "display_name": "GPT-6.1 Terra (Global)", + "id": "gpt-5-nano", + "name": "GPT-5 Nano", + "display_name": "GPT-5 Nano", "modalities": { "input": [ "text", @@ -191563,7 +193007,7 @@ ] }, "limit": { - "context": 1050000, + "context": 400000, "output": 128000 }, "temperature": false, @@ -191574,38 +193018,49 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", "effort_options": [ - "none", + "minimal", "low", "medium", - "high", - "xhigh", - "max" - ] + "high" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, "attachment": true, "open_weights": false, - "knowledge": "2026-06-01", - "release_date": "2026-10-26", - "last_updated": "2026-10-26", + "knowledge": "2024-05-30", + "release_date": "2025-08-07", + "last_updated": "2025-08-07", "type": "chat" }, { - "id": "us.zai.glm-5.3", - "name": "GLM-5.3 (US)", - "display_name": "GLM-5.3 (US)", + "id": "gemini-3.6-flash", + "name": "Gemini 3.6 Flash", + "display_name": "Gemini 3.6 Flash", "modalities": { "input": [ - "text" + "text", + "image", + "audio" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 1048576, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -191616,85 +193071,77 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "effort_options": [ + "default_enabled": true, + "mode": "level", + "level": "high", + "level_options": [ + "minimal", "low", "medium", - "high", - "xhigh", - "max" + "high" + ], + "summaries": true, + "visibility": "summary", + "continuation": [ + "thought_signatures" ] } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-08-14", - "last_updated": "2026-08-14", + "attachment": true, + "open_weights": false, + "knowledge": "2026-03", + "release_date": "2026-07-21", + "last_updated": "2026-07-21", "type": "chat" }, { - "id": "anthropic.claude-sonnet-4-6", - "name": "Claude Sonnet 4.6", - "display_name": "Claude Sonnet 4.6", + "id": "o3-pro", + "name": "o3-pro", + "display_name": "o3-pro", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 200000, + "output": 100000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "mixed", - "budget": { - "min": 1024, - "unit": "tokens" - }, - "effort": "high", + "default_enabled": true, + "mode": "effort", + "effort": "medium", "effort_options": [ "low", "medium", - "high", - "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "high" ], - "notes": [ - "Anthropic recommends adaptive thinking with effort for Claude 4.6; budget_tokens remains a deprecated compatibility path.", - "Anthropic API defaults effort to high; lower effort levels should be chosen per workload." - ] + "visibility": "hidden" } }, "attachment": true, "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-02-17", - "last_updated": "2026-03-13", + "knowledge": "2024-05", + "release_date": "2025-06-10", + "last_updated": "2025-06-10", "type": "chat" }, { - "id": "global.anthropic.claude-sonnet-4-5-20250929-v1:0", - "name": "Claude Sonnet 4.5 (Global)", - "display_name": "Claude Sonnet 4.5 (Global)", + "id": "claude-sonnet-4-20250514", + "name": "Claude Sonnet 4", + "display_name": "Claude Sonnet 4", "modalities": { "input": [ "text", @@ -191738,15 +193185,15 @@ }, "attachment": true, "open_weights": false, - "knowledge": "2025-07-31", - "release_date": "2025-09-29", - "last_updated": "2025-09-29", + "knowledge": "2025-03-31", + "release_date": "2025-05-22", + "last_updated": "2025-05-22", "type": "chat" }, { - "id": "us.meta.llama3-3-70b-instruct-v1:0", - "name": "Llama 3.3 70B Instruct (US)", - "display_name": "Llama 3.3 70B Instruct (US)", + "id": "grok-code-fast-1", + "name": "Grok Code Fast 1", + "display_name": "Grok Code Fast 1", "modalities": { "input": [ "text" @@ -191756,38 +193203,127 @@ ] }, "limit": { - "context": 128000, - "output": 4096 + "context": 256000, + "output": 16384 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true + } }, "attachment": false, - "open_weights": true, - "knowledge": "2023-12", - "release_date": "2024-12-06", - "last_updated": "2024-12-06", + "open_weights": false, + "release_date": "2025-09-02", + "last_updated": "2025-09-02", + "type": "chat" + }, + { + "id": "gemini-3.1-flash-lite-preview", + "name": "Gemini 3.1 Flash Lite Preview", + "display_name": "Gemini 3.1 Flash Lite Preview", + "modalities": { + "input": [ + "text", + "image", + "video", + "audio", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1048576, + "output": 65536 + }, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": true, + "default": true + }, + "attachment": true, + "open_weights": false, + "knowledge": "2025-01", + "release_date": "2026-03-03", + "last_updated": "2026-03-03", + "type": "chat" + }, + { + "id": "gpt-5.6-luna", + "name": "GPT-5.6 Luna", + "display_name": "GPT-5.6 Luna", + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1000000, + "output": 128000 + }, + "temperature": false, + "tool_call": true, + "reasoning": { + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", + "effort_options": [ + "none", + "low", + "medium", + "high", + "xhigh", + "max" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" + } + }, + "attachment": true, + "open_weights": false, + "knowledge": "2026-02-16", + "release_date": "2026-07-09", + "last_updated": "2026-07-09", "type": "chat" }, { - "id": "us.anthropic.claude-fable-5-1", - "name": "Claude Fable 5.1 (US)", - "display_name": "Claude Fable 5.1 (US)", + "id": "o4-mini", + "name": "o4-mini", + "display_name": "o4-mini", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 200000, + "output": 100000 }, "temperature": false, "tool_call": true, @@ -191800,38 +193336,26 @@ "supported": true, "default_enabled": true, "mode": "effort", - "effort": "high", + "effort": "medium", "effort_options": [ "low", "medium", - "high", - "xhigh", - "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "omitted", - "continuation": [ - "thinking_blocks" + "high" ], - "notes": [ - "Adaptive thinking is always on for Claude Fable 5 and Claude Mythos 5; thinking.type = \"disabled\" is rejected.", - "Manual budget_tokens requests return 400 on Claude Fable 5 and Claude Mythos 5.", - "thinking.display defaults to omitted; set display to summarized to receive readable thinking summaries." - ] + "visibility": "hidden" } }, "attachment": true, "open_weights": false, - "knowledge": "2026-06", - "release_date": "2026-09-01", - "last_updated": "2026-09-01", + "knowledge": "2024-05", + "release_date": "2025-04-16", + "last_updated": "2025-04-16", "type": "chat" }, { - "id": "us.openai.gpt-5.6-luna", - "name": "GPT-5.6 Luna (US)", - "display_name": "GPT-5.6 Luna (US)", + "id": "gpt-5", + "name": "GPT-5", + "display_name": "GPT-5", "modalities": { "input": [ "text", @@ -191842,7 +193366,7 @@ ] }, "limit": { - "context": 1050000, + "context": 400000, "output": 128000 }, "temperature": false, @@ -191853,42 +193377,51 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", "effort_options": [ - "none", + "minimal", "low", "medium", - "high", - "xhigh", - "max" - ] + "high" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, "attachment": true, "open_weights": false, - "knowledge": "2026-02-16", - "release_date": "2026-07-09", - "last_updated": "2026-07-09", + "knowledge": "2024-09-30", + "release_date": "2025-08-07", + "last_updated": "2025-08-07", "type": "chat" }, { - "id": "anthropic.claude-fable-5", - "name": "Claude Fable 5", - "display_name": "Claude Fable 5", + "id": "gemini-2.5-pro", + "name": "Gemini 2.5 Pro", + "display_name": "Gemini 2.5 Pro", "modalities": { "input": [ "text", "image", - "pdf" + "audio" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 1048576, + "output": 65536 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -191898,39 +193431,32 @@ "reasoning": { "supported": true, "default_enabled": true, - "mode": "effort", - "effort": "high", - "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" - ], - "interleaved": true, + "mode": "budget", + "budget": { + "default": -1, + "min": 128, + "max": 32768, + "auto": -1, + "unit": "tokens" + }, "summaries": true, - "visibility": "omitted", + "visibility": "summary", "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Adaptive thinking is always on for Claude Fable 5 and Claude Mythos 5; thinking.type = \"disabled\" is rejected.", - "Manual budget_tokens requests return 400 on Claude Fable 5 and Claude Mythos 5.", - "thinking.display defaults to omitted; set display to summarized to receive readable thinking summaries." + "thought_signatures" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-01-31", - "release_date": "2026-06-09", - "last_updated": "2026-06-09", + "knowledge": "2025-01", + "release_date": "2025-06-17", + "last_updated": "2025-06-17", "type": "chat" }, { - "id": "global.openai.gpt-6.1-sol", - "name": "GPT-6.1 Sol (Global)", - "display_name": "GPT-6.1 Sol (Global)", + "id": "gpt-5-mini", + "name": "GPT-5 Mini", + "display_name": "GPT-5 Mini", "modalities": { "input": [ "text", @@ -191941,7 +193467,7 @@ ] }, "limit": { - "context": 1050000, + "context": 400000, "output": 128000 }, "temperature": false, @@ -191952,83 +193478,65 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", "effort_options": [ + "minimal", "low", "medium", - "high", - "xhigh", - "max" - ] + "high" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, "attachment": true, "open_weights": false, - "knowledge": "2026-04-30", - "release_date": "2026-09-29", - "last_updated": "2026-09-29", + "knowledge": "2024-05-30", + "release_date": "2025-08-07", + "last_updated": "2025-08-07", "type": "chat" }, { - "id": "jp.anthropic.claude-opus-4-8", - "name": "Claude Opus 4.8 (JP)", - "display_name": "Claude Opus 4.8 (JP)", + "id": "gpt-4o-mini", + "name": "GPT-4o mini", + "display_name": "GPT-4o mini", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 128000, + "output": 16384 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": false - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "high", - "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "omitted", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Claude Opus 4.7 and newer Opus models require thinking.type = \"adaptive\" to enable thinking explicitly.", - "Manual budget_tokens requests return 400 on Claude Opus 4.7 and newer adaptive-only Opus models.", - "task_budget is separate from thinking control and should not be treated as a thinking budget." - ] - } + "supported": false }, "attachment": true, "open_weights": false, - "knowledge": "2026-01", - "release_date": "2026-05-28", - "last_updated": "2026-05-28", + "knowledge": "2023-09", + "release_date": "2024-07-18", + "last_updated": "2024-07-18", "type": "chat" }, { - "id": "anthropic.claude-opus-4-1-20250805-v1:0", - "name": "Claude Opus 4.1", - "display_name": "Claude Opus 4.1", + "id": "claude-3-7-sonnet-20250219", + "name": "Claude Sonnet 3.7", + "display_name": "Claude Sonnet 3.7", "modalities": { "input": [ "text", @@ -192041,7 +193549,7 @@ }, "limit": { "context": 200000, - "output": 32000 + "output": 64000 }, "temperature": true, "tool_call": true, @@ -192058,113 +193566,85 @@ "min": 1024, "unit": "tokens" }, - "interleaved": true, - "summaries": true, - "visibility": "summary", + "interleaved": false, + "summaries": false, + "visibility": "full", "continuation": [ "thinking_blocks" ], "notes": [ - "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", - "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." + "Anthropic uses thinking budget tokens" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-03-31", - "release_date": "2025-08-05", - "last_updated": "2025-08-05", + "knowledge": "2024-10-31", + "release_date": "2025-02-19", + "last_updated": "2025-02-19", "type": "chat" }, { - "id": "eu.anthropic.claude-haiku-5-5", - "name": "Claude Haiku 5.5 (EU)", - "display_name": "Claude Haiku 5.5 (EU)", + "id": "grok-4-fast-non-reasoning", + "name": "Grok 4 Fast (Non-Reasoning)", + "display_name": "Grok 4 Fast (Non-Reasoning)", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 2000000, + "output": 16384 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" - ] - } + "supported": false }, "attachment": true, "open_weights": false, - "knowledge": "2026-06", - "release_date": "2026-10-07", - "last_updated": "2026-10-07", + "release_date": "2025-12-18", + "last_updated": "2025-12-18", "type": "chat" }, { - "id": "openai.gpt-6.1-sol", - "name": "GPT-6.1 Sol", - "display_name": "GPT-6.1 Sol", + "id": "gpt-4o", + "name": "GPT-4o", + "display_name": "GPT-4o", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 128000, + "output": 16384 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" - ] - } + "supported": false }, "attachment": true, "open_weights": false, - "knowledge": "2026-04-30", - "release_date": "2026-09-29", - "last_updated": "2026-09-29", + "knowledge": "2023-09", + "release_date": "2024-05-13", + "last_updated": "2024-08-06", "type": "chat" }, { - "id": "global.moonshotai.kimi-k3", - "name": "Kimi K3 (Global)", - "display_name": "Kimi K3 (Global)", + "id": "route-llm", + "name": "RouteLLM", + "display_name": "RouteLLM", "modalities": { "input": [ "text", @@ -192175,31 +193655,24 @@ ] }, "limit": { - "context": 1048576, - "output": 128000 + "context": 128000, + "output": 64000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true - } + "supported": false }, "attachment": true, - "open_weights": true, - "release_date": "2026-07-16", - "last_updated": "2026-07-16", + "open_weights": false, + "release_date": "2024-01-01", + "last_updated": "2026-07-10", "type": "chat" }, { - "id": "us.anthropic.claude-sonnet-4-5-20250929-v1:0", - "name": "Claude Sonnet 4.5 (US)", - "display_name": "Claude Sonnet 4.5 (US)", + "id": "claude-opus-4-20250514", + "name": "Claude Opus 4", + "display_name": "Claude Opus 4", "modalities": { "input": [ "text", @@ -192212,7 +193685,7 @@ }, "limit": { "context": 200000, - "output": 64000 + "output": 32000 }, "temperature": true, "tool_call": true, @@ -192243,15 +193716,15 @@ }, "attachment": true, "open_weights": false, - "knowledge": "2025-07-31", - "release_date": "2025-09-29", - "last_updated": "2025-09-29", + "knowledge": "2025-03-31", + "release_date": "2025-05-22", + "last_updated": "2025-05-22", "type": "chat" }, { - "id": "mistral.ministral-3-14b-instruct", - "name": "Ministral 14B 3.0", - "display_name": "Ministral 14B 3.0", + "id": "gpt-5.6-sol", + "name": "GPT-5.6 Sol", + "display_name": "GPT-5.6 Sol", "modalities": { "input": [ "text", @@ -192262,24 +193735,49 @@ ] }, "limit": { - "context": 128000, - "output": 4096 + "context": 1000000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", + "effort_options": [ + "none", + "low", + "medium", + "high", + "xhigh", + "max" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" + } }, "attachment": true, - "open_weights": true, - "release_date": "2025-12-02", - "last_updated": "2025-12-02", + "open_weights": false, + "knowledge": "2026-02-16", + "release_date": "2026-07-09", + "last_updated": "2026-07-09", "type": "chat" }, { - "id": "google.gemma-3-12b-it", - "name": "Gemma 3 12B IT", - "display_name": "Gemma 3 12B IT", + "id": "claude-sonnet-4-6", + "name": "Claude Sonnet 4.6", + "display_name": "Claude Sonnet 4.6", "modalities": { "input": [ "text", @@ -192290,36 +193788,66 @@ ] }, "limit": { - "context": 131072, - "output": 8192 + "context": 1000000, + "output": 64000 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": false + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": false, + "mode": "mixed", + "budget": { + "min": 1024, + "unit": "tokens" + }, + "effort": "high", + "effort_options": [ + "low", + "medium", + "high", + "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Anthropic recommends adaptive thinking with effort for Claude 4.6; budget_tokens remains a deprecated compatibility path.", + "Anthropic API defaults effort to high; lower effort levels should be chosen per workload." + ] + } }, "attachment": true, - "open_weights": true, - "knowledge": "2024-08", - "release_date": "2025-03-12", - "last_updated": "2025-03-12", + "open_weights": false, + "knowledge": "2025-08-31", + "release_date": "2026-02-17", + "last_updated": "2026-03-13", "type": "chat" }, { - "id": "deepseek.v3.2", - "name": "DeepSeek V3.2", - "display_name": "DeepSeek V3.2", + "id": "grok-4.3", + "name": "Grok 4.3", + "display_name": "Grok 4.3", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 163840, - "output": 81920 + "context": 1000000, + "output": 32768 }, "temperature": true, "tool_call": true, @@ -192329,33 +193857,30 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true + "supported": true } }, - "attachment": false, - "open_weights": true, - "knowledge": "2024-07", - "release_date": "2025-12-01", - "last_updated": "2026-02-06", + "attachment": true, + "open_weights": false, + "release_date": "2026-04-17", + "last_updated": "2026-04-17", "type": "chat" }, { - "id": "us.anthropic.claude-sonnet-4-6", - "name": "Claude Sonnet 4.6 (US)", - "display_name": "Claude Sonnet 4.6 (US)", + "id": "gpt-5.1", + "name": "GPT-5.1", + "display_name": "GPT-5.1", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, + "context": 400000, "output": 128000 }, "temperature": true, @@ -192368,70 +193893,85 @@ "reasoning": { "supported": true, "default_enabled": false, - "mode": "mixed", - "budget": { - "min": 1024, - "unit": "tokens" - }, - "effort": "high", + "mode": "effort", + "effort": "none", "effort_options": [ + "none", "low", "medium", - "high", - "max" + "high" ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" ], - "notes": [ - "Anthropic recommends adaptive thinking with effort for Claude 4.6; budget_tokens remains a deprecated compatibility path.", - "Anthropic API defaults effort to high; lower effort levels should be chosen per workload." - ] + "visibility": "hidden" } }, "attachment": true, "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-02-17", - "last_updated": "2026-03-13", + "knowledge": "2024-09-30", + "release_date": "2025-11-13", + "last_updated": "2025-11-13", "type": "chat" }, { - "id": "meta.llama4-maverick-17b-instruct-v1:0", - "name": "Llama 4 Maverick 17B Instruct", - "display_name": "Llama 4 Maverick 17B Instruct", + "id": "gemini-2.5-flash", + "name": "Gemini 2.5 Flash", + "display_name": "Gemini 2.5 Flash", "modalities": { "input": [ "text", - "image" + "image", + "audio" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 8192 + "context": 1048576, + "output": 65536 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "budget", + "budget": { + "default": -1, + "min": 0, + "max": 24576, + "auto": -1, + "off": 0, + "unit": "tokens" + }, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thought_signatures" + ] + } }, "attachment": true, - "open_weights": true, - "knowledge": "2024-08", - "release_date": "2025-04-05", - "last_updated": "2025-04-05", + "open_weights": false, + "knowledge": "2025-01", + "release_date": "2025-06-17", + "last_updated": "2025-06-17", "type": "chat" }, { - "id": "us.meta.llama3-1-8b-instruct-v1:0", - "name": "Llama 3.1 8B Instruct (US)", - "display_name": "Llama 3.1 8B Instruct (US)", + "id": "llama-3.3-70b-versatile", + "name": "Llama 3.3 70B Versatile", + "display_name": "Llama 3.3 70B Versatile", "modalities": { "input": [ "text" @@ -192442,7 +193982,7 @@ }, "limit": { "context": 128000, - "output": 4096 + "output": 32768 }, "temperature": true, "tool_call": true, @@ -192452,19 +193992,18 @@ "attachment": false, "open_weights": true, "knowledge": "2023-12", - "release_date": "2024-07-23", - "last_updated": "2024-07-23", + "release_date": "2024-12-06", + "last_updated": "2024-12-06", "type": "chat" }, { - "id": "au.anthropic.claude-opus-5-5", - "name": "Claude Opus 5.5 (AU)", - "display_name": "Claude Opus 5.5 (AU)", + "id": "claude-opus-4-8", + "name": "Claude Opus 4.8", + "display_name": "Claude Opus 4.8", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" @@ -192478,44 +194017,87 @@ "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": false, + "mode": "effort", + "effort": "high", "effort_options": [ "low", "medium", "high", "xhigh", "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "omitted", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Claude Opus 4.7 and newer Opus models require thinking.type = \"adaptive\" to enable thinking explicitly.", + "Manual budget_tokens requests return 400 on Claude Opus 4.7 and newer adaptive-only Opus models.", + "task_budget is separate from thinking control and should not be treated as a thinking budget." ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-06", - "release_date": "2026-09-22", - "last_updated": "2026-09-22", + "knowledge": "2026-01", + "release_date": "2026-05-28", + "last_updated": "2026-05-28", "type": "chat" }, { - "id": "us.openai.gpt-5.6-sol", - "name": "GPT-5.6 Sol (US)", - "display_name": "GPT-5.6 Sol (US)", + "id": "kimi-k2-turbo-preview", + "name": "Kimi K2 Turbo Preview", + "display_name": "Kimi K2 Turbo Preview", + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 256000, + "output": 8192 + }, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": false + }, + "attachment": false, + "open_weights": false, + "release_date": "2025-07-08", + "last_updated": "2025-07-08", + "type": "chat" + }, + { + "id": "gemini-3.1-pro-preview", + "name": "Gemini 3.1 Pro Preview", + "display_name": "Gemini 3.1 Pro Preview", "modalities": { "input": [ "text", - "image" + "image", + "audio" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 1048576, + "output": 65536 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -192523,40 +194105,46 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "none", + "supported": true, + "default_enabled": true, + "mode": "level", + "level": "high", + "level_options": [ "low", - "medium", - "high", - "xhigh", - "max" + "high" + ], + "summaries": true, + "visibility": "summary", + "continuation": [ + "thought_signatures" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-02-16", - "release_date": "2026-07-09", - "last_updated": "2026-07-09", + "knowledge": "2025-01", + "release_date": "2026-02-19", + "last_updated": "2026-02-19", "type": "chat" }, { - "id": "openai.gpt-oss-20b-1:0", - "name": "gpt-oss-20b", - "display_name": "gpt-oss-20b", + "id": "gpt-5.5", + "name": "GPT-5.5", + "display_name": "GPT-5.5", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 131072, + "context": 1000000, "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -192564,24 +194152,36 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", "effort_options": [ + "low", + "medium", + "high", + "xhigh" + ], + "verbosity": "medium", + "verbosity_options": [ "low", "medium", "high" - ] + ], + "visibility": "hidden" } }, - "attachment": false, - "open_weights": true, - "knowledge": "2024-06", - "release_date": "2025-08-05", - "last_updated": "2025-08-05", + "attachment": true, + "open_weights": false, + "knowledge": "2025-12-01", + "release_date": "2026-04-23", + "last_updated": "2026-04-23", "type": "chat" }, { - "id": "global.openai.gpt-6-luna", - "name": "GPT-6 Luna (Global)", - "display_name": "GPT-6 Luna (Global)", + "id": "claude-opus-4-6", + "name": "Claude Opus 4.6", + "display_name": "Claude Opus 4.6", "modalities": { "input": [ "text", @@ -192592,157 +194192,167 @@ ] }, "limit": { - "context": 1050000, + "context": 1000000, "output": 128000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": false, + "mode": "mixed", + "budget": { + "min": 1024, + "unit": "tokens" + }, + "effort": "high", "effort_options": [ - "none", "low", "medium", "high", - "xhigh", "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Anthropic recommends adaptive thinking with effort for Claude 4.6; budget_tokens remains a deprecated compatibility path.", + "Anthropic API defaults effort to high; lower effort levels should be chosen per workload." ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-05-18", - "release_date": "2026-09-22", - "last_updated": "2026-09-22", + "knowledge": "2025-05-31", + "release_date": "2026-02-05", + "last_updated": "2026-03-13", "type": "chat" }, { - "id": "apac.amazon.nova-micro-v1:0", - "name": "Nova Micro (APAC)", - "display_name": "Nova Micro (APAC)", + "id": "grok-4-1-fast-non-reasoning", + "name": "Grok 4.1 Fast (Non-Reasoning)", + "display_name": "Grok 4.1 Fast (Non-Reasoning)", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 10000 + "context": 2000000, + "output": 16384 }, "temperature": true, "tool_call": true, "reasoning": { "supported": false }, - "attachment": false, + "attachment": true, "open_weights": false, - "knowledge": "2024-10", - "release_date": "2024-12-03", - "last_updated": "2024-12-03", + "release_date": "2025-11-17", + "last_updated": "2025-11-17", "type": "chat" }, { - "id": "anthropic.claude-opus-4-5-20251101-v1:0", - "name": "Claude Opus 4.5", - "display_name": "Claude Opus 4.5", + "id": "gemini-3.7-flash", + "name": "Gemini 3.7 Flash", + "display_name": "Gemini 3.7 Flash", "modalities": { "input": [ "text", "image", - "pdf" + "audio", + "video" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 64000 + "context": 1048576, + "output": 65536 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "mixed", - "budget": { - "min": 1024, - "unit": "tokens" - }, - "effort": "high", - "effort_options": [ + "default_enabled": true, + "mode": "level", + "level": "high", + "level_options": [ + "minimal", "low", "medium", "high" ], - "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Claude Opus 4.5 uses manual thinking.type = \"enabled\" with budget_tokens; effort can be used alongside the thinking budget.", - "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header." + "thought_signatures" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-05", - "release_date": "2025-11-24", - "last_updated": "2025-11-24", + "knowledge": "2026-03", + "release_date": "2026-08-13", + "last_updated": "2026-08-13", "type": "chat" }, { - "id": "mistral.devstral-2-123b", - "name": "Devstral 2 123B", - "display_name": "Devstral 2 123B", + "id": "gpt-5.2-chat-latest", + "name": "GPT-5.2 Chat Latest", + "display_name": "GPT-5.2 Chat Latest", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 8192 + "context": 400000, + "output": 128000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-12", - "release_date": "2025-12-09", - "last_updated": "2025-12-09", + "attachment": true, + "open_weights": false, + "knowledge": "2024-09-30", + "release_date": "2025-12-11", + "last_updated": "2025-12-11", "type": "chat" }, { - "id": "eu.anthropic.claude-sonnet-4-6", - "name": "Claude Sonnet 4.6 (EU)", - "display_name": "Claude Sonnet 4.6 (EU)", + "id": "claude-opus-5", + "name": "Claude Opus 5", + "display_name": "Claude Opus 5", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" @@ -192752,56 +194362,33 @@ "context": 1000000, "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "mixed", - "budget": { - "min": 1024, - "unit": "tokens" - }, - "effort": "high", - "effort_options": [ - "low", - "medium", - "high", - "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Anthropic recommends adaptive thinking with effort for Claude 4.6; budget_tokens remains a deprecated compatibility path.", - "Anthropic API defaults effort to high; lower effort levels should be chosen per workload." - ] + "supported": true } }, "attachment": true, "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-02-17", - "last_updated": "2026-03-13", + "knowledge": "2026-05", + "release_date": "2026-07-24", + "last_updated": "2026-07-24", "type": "chat" }, { - "id": "global.anthropic.claude-sonnet-5-5", - "name": "Claude Sonnet 5.5 (Global)", - "display_name": "Claude Sonnet 5.5 (Global)", + "id": "qwen3.8-max", + "name": "Qwen3.8 Max", + "display_name": "Qwen3.8 Max", "modalities": { "input": [ "text", "image", - "pdf" + "video" ], "output": [ "text" @@ -192809,9 +194396,9 @@ }, "limit": { "context": 1000000, - "output": 128000 + "output": 131072 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -192819,26 +194406,19 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" - ] + "supported": true } }, "attachment": true, "open_weights": false, - "knowledge": "2026-06", - "release_date": "2026-09-28", - "last_updated": "2026-09-28", + "release_date": "2026-08-03", + "last_updated": "2026-08-03", "type": "chat" }, { - "id": "jp.anthropic.claude-haiku-5-5", - "name": "Claude Haiku 5.5 (JP)", - "display_name": "Claude Haiku 5.5 (JP)", + "id": "claude-opus-4-1-20250805", + "name": "Claude Opus 4.1", + "display_name": "Claude Opus 4.1", "modalities": { "input": [ "text", @@ -192850,309 +194430,291 @@ ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 200000, + "output": 32000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" + "supported": true, + "default_enabled": false, + "mode": "budget", + "budget": { + "min": 1024, + "unit": "tokens" + }, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", + "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-06", - "release_date": "2026-10-07", - "last_updated": "2026-10-07", + "knowledge": "2025-03-31", + "release_date": "2025-08-05", + "last_updated": "2025-08-05", "type": "chat" }, { - "id": "eu.amazon.nova-lite-v1:0", - "name": "Nova Lite (EU)", - "display_name": "Nova Lite (EU)", + "id": "grok-4.6", + "name": "Grok 4.6", + "display_name": "Grok 4.6", "modalities": { "input": [ "text", - "image", - "video", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 300000, - "output": 10000 + "context": 500000, + "output": 32768 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true + } }, "attachment": true, "open_weights": false, - "knowledge": "2024-10", - "release_date": "2024-12-03", - "last_updated": "2024-12-03", + "knowledge": "2026-02-01", + "release_date": "2026-08-12", + "last_updated": "2026-08-12", "type": "chat" }, { - "id": "mistral.pixtral-large-2502-v1:0", - "name": "Pixtral Large (25.02)", - "display_name": "Pixtral Large (25.02)", + "id": "muse-spark-1.2", + "name": "Muse Spark 1.2", + "display_name": "Muse Spark 1.2", "modalities": { "input": [ "text", - "image" + "image", + "audio", + "video" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 8192 + "context": 1048576, + "output": 131072 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true + } }, "attachment": true, "open_weights": false, - "release_date": "2025-04-08", - "last_updated": "2025-04-08", + "release_date": "2026-08-05", + "last_updated": "2026-08-05", "type": "chat" }, { - "id": "google.gemma-3-27b-it", - "name": "Gemma 3 27B IT", - "display_name": "Gemma 3 27B IT", + "id": "gemini-3.1-flash-lite", + "name": "Gemini 3.1 Flash Lite", + "display_name": "Gemini 3.1 Flash Lite", "modalities": { "input": [ "text", - "image" + "image", + "audio" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 8192 + "context": 1048576, + "output": 65536 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true + } }, "attachment": true, - "open_weights": true, - "knowledge": "2024-08", - "release_date": "2025-03-12", - "last_updated": "2025-03-12", + "open_weights": false, + "knowledge": "2025-01", + "release_date": "2026-05-07", + "last_updated": "2026-05-07", "type": "chat" }, { - "id": "eu.anthropic.claude-opus-5-5", - "name": "Claude Opus 5.5 (EU)", - "display_name": "Claude Opus 5.5 (EU)", + "id": "gpt-4.1-mini", + "name": "GPT-4.1 mini", + "display_name": "GPT-4.1 mini", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 1047576, + "output": 32768 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" - ] - } + "supported": false }, "attachment": true, "open_weights": false, - "knowledge": "2026-06", - "release_date": "2026-09-22", - "last_updated": "2026-09-22", + "knowledge": "2024-04", + "release_date": "2025-04-14", + "last_updated": "2025-04-14", "type": "chat" }, { - "id": "writer.palmyra-x4-v1:0", - "name": "Palmyra X4", - "display_name": "Palmyra X4", + "id": "gpt-4.1", + "name": "GPT-4.1", + "display_name": "GPT-4.1", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 122880, - "output": 8192 + "context": 1047576, + "output": 32768 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true + "supported": false }, - "attachment": false, + "attachment": true, "open_weights": false, - "release_date": "2024-10-09", - "last_updated": "2025-04-28", + "knowledge": "2024-04", + "release_date": "2025-04-14", + "last_updated": "2025-04-14", "type": "chat" }, { - "id": "global.anthropic.claude-opus-4-7", - "name": "Claude Opus 4.7 (Global)", - "display_name": "Claude Opus 4.7 (Global)", + "id": "gemini-3-flash-preview", + "name": "Gemini 3 Flash Preview", + "display_name": "Gemini 3 Flash Preview", "modalities": { "input": [ "text", "image", - "pdf" + "audio" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 1048576, + "output": 65536 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "high", - "effort_options": [ + "default_enabled": true, + "mode": "level", + "level": "high", + "level_options": [ + "minimal", "low", "medium", - "high", - "xhigh", - "max" + "high" ], - "interleaved": true, "summaries": true, - "visibility": "omitted", + "visibility": "summary", "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Claude Opus 4.7 and newer Opus models require thinking.type = \"adaptive\" to enable thinking explicitly.", - "Manual budget_tokens requests return 400 on Claude Opus 4.7 and newer adaptive-only Opus models.", - "task_budget is separate from thinking control and should not be treated as a thinking budget." + "thought_signatures" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-01-31", - "release_date": "2026-04-16", - "last_updated": "2026-04-16", + "knowledge": "2025-01", + "release_date": "2025-12-17", + "last_updated": "2025-12-17", "type": "chat" }, { - "id": "eu.anthropic.claude-opus-4-7", - "name": "Claude Opus 4.7 (EU)", - "display_name": "Claude Opus 4.7 (EU)", + "id": "gemini-2.5-flash-image", + "name": "Nano Banana", + "display_name": "Nano Banana", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ - "text" + "text", + "image" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 32768, + "output": 32768 }, - "temperature": false, - "tool_call": true, + "temperature": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": false - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "high", - "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "omitted", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Claude Opus 4.7 and newer Opus models require thinking.type = \"adaptive\" to enable thinking explicitly.", - "Manual budget_tokens requests return 400 on Claude Opus 4.7 and newer adaptive-only Opus models.", - "task_budget is separate from thinking control and should not be treated as a thinking budget." - ] - } + "supported": false }, "attachment": true, "open_weights": false, - "knowledge": "2026-01-31", - "release_date": "2026-04-16", - "last_updated": "2026-04-16", - "type": "chat" + "knowledge": "2024-06", + "release_date": "2025-08-26", + "last_updated": "2025-08-26" }, { - "id": "us.openai.gpt-6.1-sol", - "name": "GPT-6.1 Sol (US)", - "display_name": "GPT-6.1 Sol (US)", + "id": "gpt-5.1-chat-latest", + "name": "GPT-5.1 Chat Latest", + "display_name": "GPT-5.1 Chat Latest", "modalities": { "input": [ "text", @@ -193163,7 +194725,7 @@ ] }, "limit": { - "context": 1050000, + "context": 400000, "output": 128000 }, "temperature": false, @@ -193172,211 +194734,213 @@ "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" - ] - } - }, "attachment": true, "open_weights": false, - "knowledge": "2026-04-30", - "release_date": "2026-09-29", - "last_updated": "2026-09-29", + "knowledge": "2024-09-30", + "release_date": "2025-11-13", + "last_updated": "2025-11-13", "type": "chat" }, { - "id": "us.anthropic.claude-opus-4-8", - "name": "Claude Opus 4.8 (US)", - "display_name": "Claude Opus 4.8 (US)", + "id": "o3", + "name": "o3", + "display_name": "o3", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 200000, + "output": 100000 }, "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, + "default_enabled": true, "mode": "effort", - "effort": "high", + "effort": "medium", "effort_options": [ "low", "medium", - "high", - "xhigh", - "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "omitted", - "continuation": [ - "thinking_blocks" + "high" ], - "notes": [ - "Claude Opus 4.7 and newer Opus models require thinking.type = \"adaptive\" to enable thinking explicitly.", - "Manual budget_tokens requests return 400 on Claude Opus 4.7 and newer adaptive-only Opus models.", - "task_budget is separate from thinking control and should not be treated as a thinking budget." - ] + "visibility": "hidden" } }, "attachment": true, "open_weights": false, - "knowledge": "2026-01", - "release_date": "2026-05-28", - "last_updated": "2026-05-28", + "knowledge": "2024-05", + "release_date": "2025-04-16", + "last_updated": "2025-04-16", "type": "chat" }, { - "id": "amazon.nova-2-lite-v1:0", - "name": "Nova 2 Lite", - "display_name": "Nova 2 Lite", + "id": "gemini-3.1-flash-image-preview", + "name": "Nano Banana 2 Preview", + "display_name": "Nano Banana 2 Preview", "modalities": { "input": [ "text", - "image", - "video", - "pdf" + "image" ], "output": [ - "text" + "text", + "image" ] }, "limit": { - "context": 1000000, - "output": 65535 + "context": 1048576, + "output": 32768 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { "supported": true, "default": true }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "low", - "medium", - "high" - ] + "supported": true } }, "attachment": true, "open_weights": false, - "knowledge": "2025-10", - "release_date": "2025-12-02", - "last_updated": "2025-12-01", - "type": "chat" + "knowledge": "2025-01", + "release_date": "2026-02-26", + "last_updated": "2026-02-26" }, { - "id": "mistral.mistral-large-3-675b-instruct", - "name": "Mistral Large 3", - "display_name": "Mistral Large 3", + "id": "gemini-3-pro-image-preview", + "name": "Nano Banana Pro Preview", + "display_name": "Nano Banana Pro Preview", "modalities": { "input": [ "text", "image" ], "output": [ - "text" + "text", + "image" ] }, "limit": { - "context": 262144, - "output": 8192 + "context": 65536, + "output": 32768 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "level", + "level": "high", + "level_options": [ + "low", + "high" + ], + "summaries": true, + "visibility": "summary", + "continuation": [ + "thought_signatures" + ] + } }, "attachment": true, - "open_weights": true, - "knowledge": "2024-11", - "release_date": "2025-12-02", - "last_updated": "2025-12-02", - "type": "chat" + "open_weights": false, + "knowledge": "2025-01", + "release_date": "2025-11-20", + "last_updated": "2025-11-20" }, { - "id": "anthropic.claude-opus-5", - "name": "Claude Opus 5", - "display_name": "Claude Opus 5", + "id": "claude-opus-4-5-20251101", + "name": "Claude Opus 4.5", + "display_name": "Claude Opus 4.5", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 200000, + "output": 64000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": false, + "mode": "mixed", + "budget": { + "min": 1024, + "unit": "tokens" + }, + "effort": "high", "effort_options": [ "low", "medium", - "high", - "xhigh", - "max" + "high" + ], + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Claude Opus 4.5 uses manual thinking.type = \"enabled\" with budget_tokens; effort can be used alongside the thinking budget.", + "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header." ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-05", - "release_date": "2026-07-24", - "last_updated": "2026-07-24", + "knowledge": "2025-05", + "release_date": "2025-11-24", + "last_updated": "2025-11-24", "type": "chat" }, { - "id": "eu.amazon.nova-2-lite-v1:0", - "name": "Nova 2 Lite (EU)", - "display_name": "Nova 2 Lite (EU)", + "id": "kimi-k2.5", + "name": "Kimi K2.5", + "display_name": "Kimi K2.5", "modalities": { "input": [ "text", "image", - "video", - "pdf" + "video" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 65535 + "context": 262144, + "output": 32768 }, "temperature": true, "tool_call": true, @@ -193386,24 +194950,26 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "low", - "medium", - "high" + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2025-10", - "release_date": "2025-12-02", - "last_updated": "2025-12-01", + "open_weights": true, + "knowledge": "2025-01", + "release_date": "2026-01-27", + "last_updated": "2026-01-27", "type": "chat" }, { - "id": "us.writer.palmyra-x5-v1:0", - "name": "Palmyra X5 (US)", - "display_name": "Palmyra X5 (US)", + "id": "mimo-v2-pro", + "name": "MiMo-V2-Pro", + "display_name": "MiMo-V2-Pro", "modalities": { "input": [ "text" @@ -193413,8 +194979,8 @@ ] }, "limit": { - "context": 1040000, - "output": 8192 + "context": 1048576, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -193424,29 +194990,30 @@ }, "attachment": false, "open_weights": false, - "release_date": "2025-04-28", - "last_updated": "2025-04-28", + "knowledge": "2024-12", + "release_date": "2026-03-18", + "last_updated": "2026-03-18", "type": "chat" }, { - "id": "eu.anthropic.claude-sonnet-5-5", - "name": "Claude Sonnet 5.5 (EU)", - "display_name": "Claude Sonnet 5.5 (EU)", + "id": "gemini-3.5-flash-lite", + "name": "Gemini 3.5 Flash Lite", + "display_name": "Gemini 3.5 Flash Lite", "modalities": { "input": [ "text", "image", - "pdf" + "audio" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 1048576, + "output": 65536 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -193454,39 +195021,31 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" - ] + "supported": true } }, "attachment": true, "open_weights": false, - "knowledge": "2026-06", - "release_date": "2026-09-28", - "last_updated": "2026-09-28", + "knowledge": "2026-03", + "release_date": "2026-07-21", + "last_updated": "2026-07-21", "type": "chat" }, { - "id": "openai.gpt-5.4", - "name": "GPT-5.4", - "display_name": "GPT-5.4", + "id": "openai/gpt-oss-120b", + "name": "GPT OSS 120B", + "display_name": "GPT OSS 120B", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 128000, + "output": 16384 }, "temperature": true, "tool_call": true, @@ -193496,105 +195055,75 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "none", - "low", - "medium", - "high", - "xhigh" - ] + "supported": true } }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-03-05", - "last_updated": "2026-06-01", + "attachment": false, + "open_weights": true, + "knowledge": "2024-06", + "release_date": "2025-08-05", + "last_updated": "2025-08-05", "type": "chat" }, { - "id": "xai.grok-4.6", - "name": "Grok 4.6", - "display_name": "Grok 4.6", + "id": "Qwen/Qwen3-Coder-480B-A35B-Instruct", + "name": "Qwen3-Coder 480B-A35B Instruct", + "display_name": "Qwen3-Coder 480B-A35B Instruct", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 500000, - "output": 500000 + "context": 262144, + "output": 16384 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "low", - "medium", - "high", - "xhigh" - ] - } + "supported": false }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-02-01", - "release_date": "2026-08-12", - "last_updated": "2026-08-18", + "attachment": false, + "open_weights": true, + "knowledge": "2025-04", + "release_date": "2025-07-22", + "last_updated": "2025-07-22", "type": "chat" }, { - "id": "xai.grok-4.3", - "name": "Grok 4.3", - "display_name": "Grok 4.3", + "id": "Qwen/Qwen2.5-72B-Instruct", + "name": "Qwen 2.5 72B Instruct", + "display_name": "Qwen 2.5 72B Instruct", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 128000, + "output": 8192 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "none", - "low", - "medium", - "high" - ] - } + "supported": false }, - "attachment": true, - "open_weights": false, - "release_date": "2026-04-17", - "last_updated": "2026-06-28", + "attachment": false, + "open_weights": true, + "release_date": "2024-09-19", + "last_updated": "2024-09-19", "type": "chat" }, { - "id": "qwen.qwen3-235b-a22b-2507-v1:0", - "name": "Qwen3 235B-A22B Instruct 2507", - "display_name": "Qwen3 235B-A22B Instruct 2507", + "id": "Qwen/Qwen3.6-27B", + "name": "Qwen3.6 27B", + "display_name": "Qwen3.6 27B", "modalities": { "input": [ "text" @@ -193605,295 +195134,226 @@ }, "limit": { "context": 262144, - "output": 131072 + "output": 8192 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } }, "attachment": false, "open_weights": true, - "release_date": "2025-07-21", - "last_updated": "2025-09-18", + "release_date": "2026-04-22", + "last_updated": "2026-04-22", "type": "chat" }, { - "id": "apac.amazon.nova-pro-v1:0", - "name": "Nova Pro (APAC)", - "display_name": "Nova Pro (APAC)", + "id": "Qwen/Qwen3-235B-A22B-Instruct-2507", + "name": "Qwen3 235B A22B Instruct", + "display_name": "Qwen3 235B A22B Instruct", "modalities": { "input": [ - "text", - "image", - "video", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 300000, - "output": 10000 + "context": 262144, + "output": 8192 }, "temperature": true, "tool_call": true, "reasoning": { "supported": false }, - "attachment": true, - "open_weights": false, - "knowledge": "2024-10", - "release_date": "2024-12-03", - "last_updated": "2024-12-03", + "attachment": false, + "open_weights": true, + "release_date": "2025-07-21", + "last_updated": "2025-07-21", "type": "chat" }, { - "id": "apac.amazon.nova-lite-v1:0", - "name": "Nova Lite (APAC)", - "display_name": "Nova Lite (APAC)", + "id": "Qwen/QwQ-32B", + "name": "QwQ 32B", + "display_name": "QwQ 32B", "modalities": { "input": [ - "text", - "image", - "video", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 300000, - "output": 10000 + "context": 32768, + "output": 32768 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": true, - "open_weights": false, - "knowledge": "2024-10", - "release_date": "2024-12-03", - "last_updated": "2024-12-03", + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } + }, + "attachment": false, + "open_weights": true, + "knowledge": "2024-04", + "release_date": "2025-03-05", + "last_updated": "2025-03-05", "type": "chat" }, { - "id": "au.anthropic.claude-sonnet-4-5-20250929-v1:0", - "name": "Claude Sonnet 4.5 (AU)", - "display_name": "Claude Sonnet 4.5 (AU)", + "id": "Qwen/Qwen3-32B", + "name": "Qwen3 32B", + "display_name": "Qwen3 32B", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 64000 + "context": 131072, + "output": 8192 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "budget", - "budget": { - "min": 1024, - "unit": "tokens" - }, "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ "thinking_blocks" - ], - "notes": [ - "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", - "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-07-31", - "release_date": "2025-09-29", - "last_updated": "2025-09-29", + "attachment": false, + "open_weights": true, + "knowledge": "2025-04", + "release_date": "2025-04-28", + "last_updated": "2025-04-28", "type": "chat" }, { - "id": "us.anthropic.claude-sonnet-5-5", - "name": "Claude Sonnet 5.5 (US)", - "display_name": "Claude Sonnet 5.5 (US)", + "id": "thinkingmachines/Inkling", + "name": "Inkling", + "display_name": "Inkling", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 262144, + "output": 131072 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" - ] - } - }, "attachment": true, - "open_weights": false, - "knowledge": "2026-06", - "release_date": "2026-09-28", - "last_updated": "2026-09-28", + "open_weights": true, + "release_date": "2026-07-15", + "last_updated": "2026-07-15", "type": "chat" }, { - "id": "anthropic.claude-opus-4-6-v1", - "name": "Claude Opus 4.6", - "display_name": "Claude Opus 4.6", + "id": "meta-llama/Meta-Llama-3.1-8B-Instruct", + "name": "Llama 3.1 8B Instruct", + "display_name": "Llama 3.1 8B Instruct", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 128000, + "output": 4096 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": false - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "mixed", - "budget": { - "min": 1024, - "unit": "tokens" - }, - "effort": "high", - "effort_options": [ - "low", - "medium", - "high", - "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Anthropic recommends adaptive thinking with effort for Claude 4.6; budget_tokens remains a deprecated compatibility path.", - "Anthropic API defaults effort to high; lower effort levels should be chosen per workload." - ] - } + "supported": false }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-05-31", - "release_date": "2026-02-05", - "last_updated": "2026-03-13", + "attachment": false, + "open_weights": true, + "knowledge": "2023-12", + "release_date": "2024-07-23", + "last_updated": "2024-07-23", "type": "chat" }, { - "id": "global.anthropic.claude-fable-5", - "name": "Claude Fable 5 (Global)", - "display_name": "Claude Fable 5 (Global)", + "id": "meta-llama/Meta-Llama-3.1-405B-Instruct-Turbo", + "name": "Llama 3.1 405B Instruct Turbo", + "display_name": "Llama 3.1 405B Instruct Turbo", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 128000, + "output": 4096 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "high", - "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "omitted", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Adaptive thinking is always on for Claude Fable 5 and Claude Mythos 5; thinking.type = \"disabled\" is rejected.", - "Manual budget_tokens requests return 400 on Claude Fable 5 and Claude Mythos 5.", - "thinking.display defaults to omitted; set display to summarized to receive readable thinking summaries." - ] - } + "supported": false }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-01-31", - "release_date": "2026-06-09", - "last_updated": "2026-06-09", + "attachment": false, + "open_weights": true, + "release_date": "2024-07-23", + "last_updated": "2024-07-23", "type": "chat" }, { - "id": "nvidia.nemotron-nano-3-30b", - "name": "NVIDIA Nemotron Nano 3 30B", - "display_name": "NVIDIA Nemotron Nano 3 30B", + "id": "meta-llama/Meta-Llama-3.3-70B-Instruct", + "name": "Llama-3.3-70B-Instruct", + "display_name": "Llama-3.3-70B-Instruct", "modalities": { "input": [ "text" @@ -193903,26 +195363,25 @@ ] }, "limit": { - "context": 262144, + "context": 131072, "output": 8192 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true + "supported": false }, "attachment": false, "open_weights": true, - "knowledge": "2025-06-25", - "release_date": "2025-12-15", - "last_updated": "2025-12-23", + "knowledge": "2023-12", + "release_date": "2024-12-06", + "last_updated": "2024-12-06", "type": "chat" }, { - "id": "meta.llama4-scout-17b-instruct-v1:0", - "name": "Llama 4 Scout 17B Instruct", - "display_name": "Llama 4 Scout 17B Instruct", + "id": "meta-llama/Llama-4-Maverick-17B-128E-Instruct-FP8", + "name": "Llama 4 Maverick 17B Instruct", + "display_name": "Llama 4 Maverick 17B Instruct", "modalities": { "input": [ "text", @@ -193933,7 +195392,7 @@ ] }, "limit": { - "context": 10000000, + "context": 1048576, "output": 8192 }, "temperature": true, @@ -193949,22 +195408,22 @@ "type": "chat" }, { - "id": "in.anthropic.claude-sonnet-5", - "name": "Claude Sonnet 5 (India)", - "display_name": "Claude Sonnet 5 (India)", + "id": "moonshotai/Kimi-K2.7-Code", + "name": "Kimi K2.7 Code", + "display_name": "Kimi K2.7 Code", "modalities": { "input": [ "text", "image", - "pdf" + "video" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 262144, + "output": 262144 }, "temperature": false, "tool_call": true, @@ -193974,37 +195433,33 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" - ] + "supported": true } }, "attachment": true, - "open_weights": false, - "knowledge": "2026-01-31", - "release_date": "2026-06-30", - "last_updated": "2026-06-30", + "open_weights": true, + "knowledge": "2025-01", + "release_date": "2026-06-12", + "last_updated": "2026-06-12", "type": "chat" }, { - "id": "us-gov.openai.gpt-oss-120b-1:0", - "name": "gpt-oss-120b (GovCloud)", - "display_name": "gpt-oss-120b (GovCloud)", + "id": "moonshotai/Kimi-K2.6", + "name": "Kimi K2.6", + "display_name": "Kimi K2.6", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 16384 + "context": 262144, + "output": 262144 }, "temperature": true, "tool_call": true, @@ -194014,36 +195469,33 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "low", - "medium", - "high" - ] + "supported": true } }, - "attachment": false, + "attachment": true, "open_weights": true, - "knowledge": "2024-06", - "release_date": "2025-08-05", - "last_updated": "2025-08-05", + "knowledge": "2025-01", + "release_date": "2026-04-20", + "last_updated": "2026-04-20", "type": "chat" }, { - "id": "global.openai.gpt-6-astra", - "name": "GPT-6 Astra (Global)", - "display_name": "GPT-6 Astra (Global)", + "id": "moonshotai/Kimi-K3", + "name": "Kimi K3", + "display_name": "Kimi K3", "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 1048576, + "output": 131072 }, "temperature": false, "tool_call": true, @@ -194053,40 +195505,61 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" - ] + "supported": true } }, "attachment": true, - "open_weights": false, - "knowledge": "2026-04-30", - "release_date": "2026-09-04", - "last_updated": "2026-09-04", + "open_weights": true, + "release_date": "2026-07-16", + "last_updated": "2026-07-16", "type": "chat" }, { - "id": "global.openai.gpt-6-sol", - "name": "GPT-6 Sol (Global)", - "display_name": "GPT-6 Sol (Global)", + "id": "zai-org/GLM-4.5", + "name": "GLM-4.5", + "display_name": "GLM-4.5", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 131072, + "output": 96000 }, - "temperature": false, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": true, + "default": true + }, + "attachment": false, + "open_weights": true, + "knowledge": "2025-04", + "release_date": "2025-07-28", + "last_updated": "2025-07-28", + "type": "chat" + }, + { + "id": "zai-org/GLM-5", + "name": "GLM-5", + "display_name": "GLM-5", + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 204800, + "output": 131072 + }, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -194094,27 +195567,25 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "none", - "low", - "medium", - "high", - "xhigh", - "max" + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-04-20", - "release_date": "2026-09-22", - "last_updated": "2026-09-22", + "attachment": false, + "open_weights": true, + "release_date": "2026-02-12", + "last_updated": "2026-02-12", "type": "chat" }, { - "id": "qwen.qwen3-32b-v1:0", - "name": "Qwen3 32B", - "display_name": "Qwen3 32B", + "id": "zai-org/GLM-4.6", + "name": "GLM-4.6", + "display_name": "GLM-4.6", "modalities": { "input": [ "text" @@ -194124,8 +195595,8 @@ ] }, "limit": { - "context": 32768, - "output": 16384 + "context": 202752, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -194135,87 +195606,66 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true + "supported": true } }, "attachment": false, "open_weights": true, "knowledge": "2025-04", - "release_date": "2025-04-28", - "last_updated": "2025-09-18", + "release_date": "2025-09-30", + "last_updated": "2025-09-30", "type": "chat" }, { - "id": "eu.anthropic.claude-sonnet-4-5-20250929-v1:0", - "name": "Claude Sonnet 4.5 (EU)", - "display_name": "Claude Sonnet 4.5 (EU)", + "id": "zai-org/GLM-5.1", + "name": "GLM-5.1", + "display_name": "GLM-5.1", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 64000 + "context": 204800, + "output": 131072 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "budget", - "budget": { - "min": 1024, - "unit": "tokens" - }, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", - "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." - ] + "supported": true } }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-07-31", - "release_date": "2025-09-29", - "last_updated": "2025-09-29", + "attachment": false, + "open_weights": true, + "release_date": "2026-04-07", + "last_updated": "2026-04-07", "type": "chat" }, { - "id": "us.openai.gpt-6-astra", - "name": "GPT-6 Astra (US)", - "display_name": "GPT-6 Astra (US)", + "id": "zai-org/GLM-5.2", + "name": "GLM-5.2", + "display_name": "GLM-5.2", "modalities": { - "input": [ - "text", - "image" + "input": [ + "text" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 1048576, + "output": 131072 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -194223,39 +195673,30 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" - ] + "supported": true } }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-04-30", - "release_date": "2026-09-04", - "last_updated": "2026-09-04", + "attachment": false, + "open_weights": true, + "release_date": "2026-06-13", + "last_updated": "2026-06-13", "type": "chat" }, { - "id": "google.gemma-4-26b-a4b", - "name": "Gemma 4 26B A4B IT", - "display_name": "Gemma 4 26B A4B IT", + "id": "zai-org/GLM-4.7", + "name": "GLM-4.7", + "display_name": "GLM-4.7", "modalities": { "input": [ - "text", - "image", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 32768 + "context": 204800, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -194265,37 +195706,40 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "none", - "high" + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, - "attachment": true, + "attachment": false, "open_weights": true, - "release_date": "2026-04-02", - "last_updated": "2026-04-02", + "knowledge": "2025-04", + "release_date": "2025-12-22", + "last_updated": "2025-12-22", "type": "chat" }, { - "id": "openai.gpt-6.1-terra", - "name": "GPT-6.1 Terra", - "display_name": "GPT-6.1 Terra", + "id": "google/gemma-4-31b-it", + "name": "Gemma 4 31B IT", + "display_name": "Gemma 4 31B IT", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 262144, + "output": 131072 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -194303,188 +195747,122 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "none", - "low", - "medium", - "high", - "xhigh", - "max" - ] + "supported": true } }, "attachment": true, - "open_weights": false, - "knowledge": "2026-06-01", - "release_date": "2026-10-26", - "last_updated": "2026-10-26", + "open_weights": true, + "knowledge": "2025-01", + "release_date": "2026-04-02", + "last_updated": "2026-04-02", "type": "chat" }, { - "id": "us.meta.llama4-scout-17b-instruct-v1:0", - "name": "Llama 4 Scout 17B Instruct (US)", - "display_name": "Llama 4 Scout 17B Instruct (US)", + "id": "deepseek/deepseek-v3.1", + "name": "DeepSeek V3.1", + "display_name": "DeepSeek V3.1", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 10000000, + "context": 128000, "output": 8192 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": true, + "attachment": false, "open_weights": true, - "knowledge": "2024-08", - "release_date": "2025-04-05", - "last_updated": "2025-04-05", + "release_date": "2025-08-21", + "last_updated": "2025-08-21", "type": "chat" }, { - "id": "us.anthropic.claude-haiku-4-5-20251001-v1:0", - "name": "Claude Haiku 4.5 (US)", - "display_name": "Claude Haiku 4.5 (US)", + "id": "MiniMaxAI/MiniMax-M2.7", + "name": "MiniMax-M2.7", + "display_name": "MiniMax-M2.7", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 64000 + "context": 204800, + "output": 131072 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "budget", - "budget": { - "min": 1024, - "unit": "tokens" - }, "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ "thinking_blocks" - ], - "notes": [ - "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", - "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-02-28", - "release_date": "2025-10-15", - "last_updated": "2025-10-15", - "type": "chat" - }, - { - "id": "mistral.voxtral-mini-3b-2507", - "name": "Voxtral Mini 3B 2507", - "display_name": "Voxtral Mini 3B 2507", - "modalities": { - "input": [ - "text", - "audio" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 32768, - "output": 4096 - }, - "temperature": true, - "tool_call": true, - "reasoning": { - "supported": false - }, - "attachment": true, + "attachment": false, "open_weights": true, - "release_date": "2025-07-15", - "last_updated": "2025-07-15", + "release_date": "2026-03-18", + "last_updated": "2026-03-18", "type": "chat" }, { - "id": "in.anthropic.claude-haiku-4-5-20251001-v1:0", - "name": "Claude Haiku 4.5 (India)", - "display_name": "Claude Haiku 4.5 (India)", + "id": "MiniMaxAI/MiniMax-M3", + "name": "MiniMax-M3", + "display_name": "MiniMax-M3", "modalities": { "input": [ "text", "image", - "pdf" + "video" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 64000 + "context": 1000000, + "output": 131072 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "budget", - "budget": { - "min": 1024, - "unit": "tokens" - }, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", - "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." - ] + "supported": true } }, "attachment": true, - "open_weights": false, - "knowledge": "2025-02-28", - "release_date": "2025-10-15", - "last_updated": "2025-10-15", + "open_weights": true, + "release_date": "2026-06-01", + "last_updated": "2026-06-01", "type": "chat" }, { - "id": "deepseek.r1-v1:0", - "name": "DeepSeek-R1", - "display_name": "DeepSeek-R1", + "id": "deepseek-ai/DeepSeek-V3.1-Terminus", + "name": "DeepSeek V3.1 Terminus", + "display_name": "DeepSeek V3.1 Terminus", "modalities": { "input": [ "text" @@ -194495,30 +195873,27 @@ }, "limit": { "context": 128000, - "output": 32768 + "output": 8192 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { "supported": true, "default": true }, "attachment": false, "open_weights": true, - "knowledge": "2024-07", - "release_date": "2025-01-20", - "last_updated": "2025-05-29", + "release_date": "2025-06-01", + "last_updated": "2025-06-01", "type": "chat" }, { - "id": "global.anthropic.claude-fable-5-1", - "name": "Claude Fable 5.1 (Global)", - "display_name": "Claude Fable 5.1 (Global)", + "id": "deepseek-ai/DeepSeek-V4-Pro", + "name": "DeepSeek V4 Pro", + "display_name": "DeepSeek V4 Pro", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" @@ -194526,9 +195901,9 @@ }, "limit": { "context": 1000000, - "output": 128000 + "output": 32768 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -194542,58 +195917,44 @@ "effort": "high", "effort_options": [ "low", - "medium", "high", - "xhigh", "max" ], "interleaved": true, "summaries": true, - "visibility": "omitted", + "visibility": "summary", "continuation": [ "thinking_blocks" ], "notes": [ - "Adaptive thinking is always on for Claude Fable 5 and Claude Mythos 5; thinking.type = \"disabled\" is rejected.", - "Manual budget_tokens requests return 400 on Claude Fable 5 and Claude Mythos 5.", - "thinking.display defaults to omitted; set display to summarized to receive readable thinking summaries." + "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-06", - "release_date": "2026-09-01", - "last_updated": "2026-09-01", + "attachment": false, + "open_weights": true, + "knowledge": "2025-05", + "release_date": "2026-04-24", + "last_updated": "2026-04-24", "type": "chat" - } - ] - }, - "umans-ai-coding-plan": { - "id": "umans-ai-coding-plan", - "name": "Umans AI Coding Plan", - "display_name": "Umans AI Coding Plan", - "api": "https://api.code.umans.ai/v1", - "doc": "https://app.umans.ai/offers/code/docs", - "models": [ + }, { - "id": "umans-qwen3.6-35b-a3b", - "name": "Qwen3.6 35B A3B", - "display_name": "Qwen3.6 35B A3B", + "id": "deepseek-ai/DeepSeek-R1", + "name": "DeepSeek R1", + "display_name": "DeepSeek R1", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 128000, + "output": 8192 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -194607,36 +195968,31 @@ "visibility": "summary", "continuation": [ "thinking_blocks" - ], - "effort_options": [ - "low", - "medium", - "high" ] } }, - "attachment": true, + "attachment": false, "open_weights": true, - "release_date": "2026-04-17", - "last_updated": "2026-04-17", + "knowledge": "2024-07", + "release_date": "2025-01-20", + "last_updated": "2025-05-29", "type": "chat" }, { - "id": "umans-deepseek-v4.1-flash-1017", - "name": "DeepSeek V4.1 Flash 1017", - "display_name": "DeepSeek V4.1 Flash 1017", + "id": "deepseek-ai/DeepSeek-V4-Flash", + "name": "DeepSeek V4 Flash", + "display_name": "DeepSeek V4 Flash", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 393215 + "context": 1000000, + "output": 32768 }, "temperature": true, "tool_call": true, @@ -194647,76 +196003,36 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], + "default_enabled": true, + "mode": "effort", + "effort": "high", "effort_options": [ "low", "high", "max" - ] - } - }, - "attachment": true, - "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-10-17", - "last_updated": "2026-10-17", - "type": "chat" - }, - { - "id": "umans-coder", - "name": "Umans Coder", - "display_name": "Umans Coder", - "modalities": { - "input": [ - "text", - "image", - "video" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 1048576, - "output": 131071 - }, - "temperature": false, - "tool_call": true, - "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, + ], "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ "thinking_blocks" ], - "effort_options": [ - "low", - "high", - "max" + "notes": [ + "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." ] } }, - "attachment": true, + "attachment": false, "open_weights": true, - "release_date": "2026-08-26", - "last_updated": "2026-08-26", + "knowledge": "2025-05", + "release_date": "2026-04-24", + "last_updated": "2026-04-24", "type": "chat" }, { - "id": "umans-glm-5.3", - "name": "GLM 5.3", - "display_name": "GLM 5.3", + "id": "deepseek-ai/DeepSeek-V3.2", + "name": "DeepSeek V3.2", + "display_name": "DeepSeek V3.2", "modalities": { "input": [ "text" @@ -194726,8 +196042,8 @@ ] }, "limit": { - "context": 1048576, - "output": 131071 + "context": 128000, + "output": 8192 }, "temperature": true, "tool_call": true, @@ -194737,30 +196053,29 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "effort_options": [ - "low", - "high", - "max" - ] + "supported": true } }, "attachment": false, "open_weights": true, - "release_date": "2026-08-14", - "last_updated": "2026-08-14", + "knowledge": "2024-07", + "release_date": "2025-12-01", + "last_updated": "2025-12-01", "type": "chat" - }, + } + ] + }, + "siliconflow": { + "id": "siliconflow", + "name": "SiliconFlow", + "display_name": "SiliconFlow", + "api": "https://api.siliconflow.cn/v1", + "doc": "https://cloud.siliconflow.com/models", + "models": [ { - "id": "umans-kimi-k3", - "name": "Kimi K3", - "display_name": "Kimi K3", + "id": "Pro/moonshotai/Kimi-K2.5", + "name": "Pro/moonshotai/Kimi-K2.5", + "display_name": "Pro/moonshotai/Kimi-K2.5", "modalities": { "input": [ "text", @@ -194771,10 +196086,10 @@ ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 262000, + "output": 99560 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -194788,24 +196103,20 @@ "visibility": "summary", "continuation": [ "thinking_blocks" - ], - "effort_options": [ - "low", - "high", - "max" ] } }, "attachment": true, "open_weights": true, - "release_date": "2026-07-16", - "last_updated": "2026-07-16", + "knowledge": "2025-01", + "release_date": "2026-01-27", + "last_updated": "2026-01-27", "type": "chat" }, { - "id": "umans-deepseek-v4.1-flash", - "name": "DeepSeek V4.1 Flash", - "display_name": "DeepSeek V4.1 Flash", + "id": "Pro/moonshotai/Kimi-K2.6", + "name": "Pro/moonshotai/Kimi-K2.6", + "display_name": "Pro/moonshotai/Kimi-K2.6", "modalities": { "input": [ "text", @@ -194816,8 +196127,8 @@ ] }, "limit": { - "context": 1048576, - "output": 393215 + "context": 262000, + "output": 99560 }, "temperature": true, "tool_call": true, @@ -194833,39 +196144,33 @@ "visibility": "summary", "continuation": [ "thinking_blocks" - ], - "effort_options": [ - "low", - "high", - "max" ] } }, "attachment": true, "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-10-17", - "last_updated": "2026-10-17", + "knowledge": "2025-01", + "release_date": "2026-04-20", + "last_updated": "2026-04-20", "type": "chat" }, { - "id": "umans-flash", - "name": "Umans Flash", - "display_name": "Umans Flash", + "id": "Pro/zai-org/GLM-5", + "name": "Pro/zai-org/GLM-5", + "display_name": "Pro/zai-org/GLM-5", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 205000, + "output": 131072 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -194879,37 +196184,30 @@ "visibility": "summary", "continuation": [ "thinking_blocks" - ], - "effort_options": [ - "low", - "medium", - "high" ] } }, - "attachment": true, + "attachment": false, "open_weights": true, - "release_date": "2026-04-17", - "last_updated": "2026-04-17", + "release_date": "2026-02-12", + "last_updated": "2026-02-12", "type": "chat" }, { - "id": "umans-glm-5.3-flash", - "name": "GLM 5.3 Flash", - "display_name": "GLM 5.3 Flash", + "id": "Pro/zai-org/GLM-5.1", + "name": "Pro/zai-org/GLM-5.1", + "display_name": "Pro/zai-org/GLM-5.1", "modalities": { "input": [ - "text", - "image", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 131071 + "context": 205000, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -194925,24 +196223,19 @@ "visibility": "summary", "continuation": [ "thinking_blocks" - ], - "effort_options": [ - "low", - "high", - "max" ] } }, - "attachment": true, + "attachment": false, "open_weights": true, - "release_date": "2026-08-26", - "last_updated": "2026-08-26", + "release_date": "2026-04-07", + "last_updated": "2026-04-07", "type": "chat" }, { - "id": "umans-deepseek-v4-flash-0731", - "name": "DeepSeek V4 Flash", - "display_name": "DeepSeek V4 Flash", + "id": "Pro/deepseek-ai/DeepSeek-V3.1-Terminus", + "name": "Pro/deepseek-ai/DeepSeek-V3.1-Terminus", + "display_name": "Pro/deepseek-ai/DeepSeek-V3.1-Terminus", "modalities": { "input": [ "text" @@ -194952,8 +196245,8 @@ ] }, "limit": { - "context": 1048576, - "output": 393215 + "context": 164000, + "output": 8192 }, "temperature": true, "tool_call": true, @@ -194961,90 +196254,43 @@ "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "effort_options": [ - "low", - "high", - "max" - ] - } - }, "attachment": false, "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-07-31", - "last_updated": "2026-07-31", + "release_date": "2025-09-29", + "last_updated": "2025-11-25", "type": "chat" - } - ] - }, - "morph": { - "id": "morph", - "name": "Morph", - "display_name": "Morph", - "api": "https://api.morphllm.com/v1", - "doc": "https://docs.morphllm.com/api-reference/introduction", - "models": [ + }, { - "id": "morph-dsv41flash-1017", - "name": "DeepSeek V4.1 Flash 1017", - "display_name": "DeepSeek V4.1 Flash 1017", + "id": "Pro/deepseek-ai/DeepSeek-V3", + "name": "Pro/deepseek-ai/DeepSeek-V3", + "display_name": "Pro/deepseek-ai/DeepSeek-V3", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 1048576 + "context": 164000, + "output": 8192 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "effort_options": [ - "none", - "low", - "high", - "xhigh", - "max" - ] - } + "supported": false }, - "attachment": true, + "attachment": false, "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-10-17", - "last_updated": "2026-10-17", + "release_date": "2025-03-24", + "last_updated": "2025-03-24", "type": "chat" }, { - "id": "morph-glm53-744b", - "name": "GLM-5.3", - "display_name": "GLM-5.3", + "id": "Pro/deepseek-ai/DeepSeek-R1", + "name": "Pro/deepseek-ai/DeepSeek-R1", + "display_name": "Pro/deepseek-ai/DeepSeek-R1", "modalities": { "input": [ "text" @@ -195054,8 +196300,8 @@ ] }, "limit": { - "context": 1048576, - "output": 1048576 + "context": 164000, + "output": 8192 }, "temperature": true, "tool_call": true, @@ -195071,25 +196317,19 @@ "visibility": "summary", "continuation": [ "thinking_blocks" - ], - "effort_options": [ - "none", - "low", - "high", - "max" ] } }, "attachment": false, "open_weights": true, - "release_date": "2026-08-14", - "last_updated": "2026-08-14", + "release_date": "2025-05-28", + "last_updated": "2025-05-28", "type": "chat" }, { - "id": "morph-v3-fast", - "name": "Morph V3 Fast", - "display_name": "Morph V3 Fast", + "id": "Pro/deepseek-ai/DeepSeek-V3.2", + "name": "Pro/deepseek-ai/DeepSeek-V3.2", + "display_name": "Pro/deepseek-ai/DeepSeek-V3.2", "modalities": { "input": [ "text" @@ -195099,24 +196339,31 @@ ] }, "limit": { - "context": 262144, - "output": 131072 + "context": 164000, + "output": 65536 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true + } }, "attachment": false, - "open_weights": false, - "release_date": "2025-07-07", - "last_updated": "2025-07-07", + "open_weights": true, + "knowledge": "2024-07", + "release_date": "2025-12-01", + "last_updated": "2025-12-01", "type": "chat" }, { - "id": "morph-glm53flash", - "name": "GLM-5.3-Flash", - "display_name": "GLM-5.3-Flash", + "id": "Qwen/Qwen3.5-122B-A10B", + "name": "Qwen/Qwen3.5-122B-A10B", + "display_name": "Qwen/Qwen3.5-122B-A10B", "modalities": { "input": [ "text", @@ -195128,8 +196375,8 @@ ] }, "limit": { - "context": 1048576, - "output": 1048576 + "context": 262144, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -195145,39 +196392,35 @@ "visibility": "summary", "continuation": [ "thinking_blocks" - ], - "effort_options": [ - "none", - "low", - "high", - "max" ] } }, "attachment": true, "open_weights": true, - "release_date": "2026-08-26", - "last_updated": "2026-08-26", + "knowledge": "2025-04", + "release_date": "2026-02-23", + "last_updated": "2026-02-23", "type": "chat" }, { - "id": "morph-kimik3", - "name": "Kimi K3", - "display_name": "Kimi K3", + "id": "Qwen/Qwen3.5-27B", + "name": "Qwen/Qwen3.5-27B", + "display_name": "Qwen/Qwen3.5-27B", "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 1048576 + "context": 262144, + "output": 65536 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -195191,69 +196434,33 @@ "visibility": "summary", "continuation": [ "thinking_blocks" - ], - "effort_options": [ - "none", - "low", - "high", - "max" ] } }, "attachment": true, "open_weights": true, - "release_date": "2026-07-16", - "last_updated": "2026-07-16", - "type": "chat" - }, - { - "id": "auto", - "name": "Auto", - "display_name": "Auto", - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 32000, - "output": 32000 - }, - "temperature": false, - "tool_call": false, - "reasoning": { - "supported": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true - } - }, - "attachment": false, - "open_weights": false, - "release_date": "2024-06-01", - "last_updated": "2024-06-01", + "knowledge": "2025-04", + "release_date": "2026-02-23", + "last_updated": "2026-02-23", "type": "chat" }, { - "id": "morph-dsv41flash", - "name": "DeepSeek V4.1 Flash", - "display_name": "DeepSeek V4.1 Flash", + "id": "Qwen/Qwen3.5-4B", + "name": "Qwen/Qwen3.5-4B", + "display_name": "Qwen/Qwen3.5-4B", "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 1048576 + "context": 262144, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -195269,27 +196476,20 @@ "visibility": "summary", "continuation": [ "thinking_blocks" - ], - "effort_options": [ - "none", - "low", - "high", - "xhigh", - "max" ] } }, "attachment": true, "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-09-10", - "last_updated": "2026-09-10", + "knowledge": "2025-04", + "release_date": "2026-03-02", + "last_updated": "2026-03-02", "type": "chat" }, { - "id": "morph-dsv4flash", - "name": "DeepSeek V4 Flash 0731", - "display_name": "DeepSeek V4 Flash 0731", + "id": "Qwen/Qwen2.5-7B-Instruct", + "name": "Qwen/Qwen2.5-7B-Instruct", + "display_name": "Qwen/Qwen2.5-7B-Instruct", "modalities": { "input": [ "text" @@ -195299,101 +196499,68 @@ ] }, "limit": { - "context": 1048576, - "output": 1048576 + "context": 33000, + "output": 4000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "effort_options": [ - "none", - "low", - "high", - "max" - ] - } + "supported": false }, "attachment": false, "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-07-31", - "last_updated": "2026-07-31", + "release_date": "2024-09-19", + "last_updated": "2024-09-19", "type": "chat" }, { - "id": "morph-glm52-744b", - "name": "GLM-5.2", - "display_name": "GLM-5.2", + "id": "Qwen/Qwen3-VL-32B-Instruct", + "name": "Qwen/Qwen3-VL-32B-Instruct", + "display_name": "Qwen/Qwen3-VL-32B-Instruct", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 1048576 + "context": 262000, + "output": 32768 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "effort_options": [ - "none", - "high", - "max" - ] - } + "supported": false }, - "attachment": false, + "attachment": true, "open_weights": true, - "release_date": "2026-06-13", - "last_updated": "2026-06-13", + "release_date": "2025-10-21", + "last_updated": "2025-11-25", "type": "chat" }, { - "id": "morph-kimik3-fast", - "name": "Kimi K3 Fast", - "display_name": "Kimi K3 Fast", + "id": "Qwen/Qwen3-VL-30B-A3B-Thinking", + "name": "Qwen/Qwen3-VL-30B-A3B-Thinking", + "display_name": "Qwen/Qwen3-VL-30B-A3B-Thinking", "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 1048576 + "context": 262000, + "output": 32768 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -195407,24 +196574,19 @@ "visibility": "summary", "continuation": [ "thinking_blocks" - ], - "effort_options": [ - "none", - "low", - "high" ] } }, "attachment": true, "open_weights": true, - "release_date": "2026-07-16", - "last_updated": "2026-07-16", + "release_date": "2025-10-11", + "last_updated": "2025-11-25", "type": "chat" }, { - "id": "morph-v3-large", - "name": "Morph V3 Large", - "display_name": "Morph V3 Large", + "id": "Qwen/Qwen2.5-72B-Instruct", + "name": "Qwen/Qwen2.5-72B-Instruct", + "display_name": "Qwen/Qwen2.5-72B-Instruct", "modalities": { "input": [ "text" @@ -195434,33 +196596,24 @@ ] }, "limit": { - "context": 262144, - "output": 131072 + "context": 33000, + "output": 4000 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { "supported": false }, "attachment": false, - "open_weights": false, - "release_date": "2025-07-07", - "last_updated": "2025-07-07", + "open_weights": true, + "release_date": "2024-09-18", + "last_updated": "2025-11-25", "type": "chat" - } - ] - }, - "alibaba-token-plan-cn": { - "id": "alibaba-token-plan-cn", - "name": "Alibaba Token Plan (China)", - "display_name": "Alibaba Token Plan (China)", - "api": "https://token-plan.cn-beijing.maas.aliyuncs.com/compatible-mode/v1", - "doc": "https://www.alibabacloud.com/help/zh/model-studio/token-plan-overview", - "models": [ + }, { - "id": "qwen3.7-max", - "name": "Qwen3.7 Max", - "display_name": "Qwen3.7 Max", + "id": "Qwen/Qwen3-14B", + "name": "Qwen/Qwen3-14B", + "display_name": "Qwen/Qwen3-14B", "modalities": { "input": [ "text" @@ -195470,14 +196623,13 @@ ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 131000, + "output": 32768 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true + "supported": true }, "extra_capabilities": { "reasoning": { @@ -195491,79 +196643,108 @@ } }, "attachment": false, - "open_weights": false, - "release_date": "2026-05-21", - "last_updated": "2026-05-21", + "open_weights": true, + "release_date": "2025-04-30", + "last_updated": "2025-11-25", "type": "chat" }, { - "id": "happyhorse-1.1-i2v", - "name": "HappyHorse 1.1 Image-to-Video", - "display_name": "HappyHorse 1.1 Image-to-Video", + "id": "Qwen/Qwen3-8B", + "name": "Qwen/Qwen3-8B", + "display_name": "Qwen/Qwen3-8B", "modalities": { "input": [ - "image", "text" ], "output": [ - "video" + "text" ] }, "limit": { - "context": 8192, - "output": 8192 + "context": 131000, + "output": 32768 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true }, - "attachment": true, - "open_weights": false, - "release_date": "2026-07-17", - "last_updated": "2026-07-17" + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } + }, + "attachment": false, + "open_weights": true, + "release_date": "2025-04-29", + "last_updated": "2025-04-29", + "type": "chat" }, { - "id": "qwen-image-2.0", - "name": "Qwen Image 2.0", - "display_name": "Qwen Image 2.0", + "id": "Qwen/Qwen3.5-9B", + "name": "Qwen/Qwen3.5-9B", + "display_name": "Qwen/Qwen3.5-9B", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ - "image" + "text" ] }, "limit": { - "context": 8192, - "output": 8192 + "context": 262144, + "output": 65536 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, - "open_weights": false, - "release_date": "2026-03-03", - "last_updated": "2026-03-03" + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } + }, + "attachment": true, + "open_weights": true, + "knowledge": "2025-04", + "release_date": "2026-02-23", + "last_updated": "2026-02-23", + "type": "chat" }, { - "id": "deepseek-v4-flash", - "name": "DeepSeek V4 Flash", - "display_name": "DeepSeek V4 Flash", + "id": "Qwen/Qwen3-VL-32B-Thinking", + "name": "Qwen/Qwen3-VL-32B-Thinking", + "display_name": "Qwen/Qwen3-VL-32B-Thinking", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 384000 + "context": 262000, + "output": 32768 }, "temperature": true, "tool_call": true, @@ -195574,36 +196755,53 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "high", - "effort_options": [ - "low", - "high", - "max" - ], "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ "thinking_blocks" - ], - "notes": [ - "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." ] } }, - "attachment": false, + "attachment": true, "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-04-24", - "last_updated": "2026-04-24", + "release_date": "2025-10-21", + "last_updated": "2025-11-25", "type": "chat" }, { - "id": "deepseek-v4.1-flash-1017", - "name": "DeepSeek V4.1 Flash 1017", - "display_name": "DeepSeek V4.1 Flash 1017", + "id": "Qwen/Qwen3-VL-30B-A3B-Instruct", + "name": "Qwen/Qwen3-VL-30B-A3B-Instruct", + "display_name": "Qwen/Qwen3-VL-30B-A3B-Instruct", + "modalities": { + "input": [ + "text", + "image", + "video" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 262000, + "output": 32768 + }, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": false + }, + "attachment": true, + "open_weights": true, + "release_date": "2025-10-04", + "last_updated": "2025-10-04", + "type": "chat" + }, + { + "id": "Qwen/Qwen3.6-27B", + "name": "Qwen3.6 27B", + "display_name": "Qwen3.6 27B", "modalities": { "input": [ "text", @@ -195614,8 +196812,8 @@ ] }, "limit": { - "context": 1000000, - "output": 384000 + "context": 262144, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -195631,51 +196829,19 @@ "visibility": "summary", "continuation": [ "thinking_blocks" - ], - "effort_options": [ - "low", - "high", - "max" ] } }, "attachment": true, "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-10-17", - "last_updated": "2026-10-17", + "release_date": "2026-04-22", + "last_updated": "2026-04-22", "type": "chat" }, { - "id": "qwen-image-2.0-pro", - "name": "Qwen Image 2.0 Pro", - "display_name": "Qwen Image 2.0 Pro", - "modalities": { - "input": [ - "text" - ], - "output": [ - "image" - ] - }, - "limit": { - "context": 8192, - "output": 8192 - }, - "temperature": true, - "tool_call": false, - "reasoning": { - "supported": false - }, - "attachment": false, - "open_weights": false, - "release_date": "2026-03-03", - "last_updated": "2026-03-03" - }, - { - "id": "deepseek-v4-pro", - "name": "DeepSeek V4 Pro", - "display_name": "DeepSeek V4 Pro", + "id": "Qwen/Qwen3-Coder-30B-A3B-Instruct", + "name": "Qwen/Qwen3-Coder-30B-A3B-Instruct", + "display_name": "Qwen/Qwen3-Coder-30B-A3B-Instruct", "modalities": { "input": [ "text" @@ -195685,48 +196851,24 @@ ] }, "limit": { - "context": 1000000, - "output": 384000 + "context": 262000, + "output": 65536 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "high", - "effort_options": [ - "low", - "high", - "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." - ] - } + "supported": false }, "attachment": false, "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-04-24", - "last_updated": "2026-04-24", - "type": "chat" + "knowledge": "2025-04", + "release_date": "2025-07-31", + "last_updated": "2025-07-31" }, { - "id": "qwen3.6-flash", - "name": "Qwen3.6 Flash", - "display_name": "Qwen3.6 Flash", + "id": "Qwen/Qwen3-VL-8B-Instruct", + "name": "Qwen/Qwen3-VL-8B-Instruct", + "display_name": "Qwen/Qwen3-VL-8B-Instruct", "modalities": { "input": [ "text", @@ -195738,68 +196880,55 @@ ] }, "limit": { - "context": 1000000, - "output": 65536 + "context": 262000, + "output": 32768 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } + "supported": false }, "attachment": true, - "open_weights": false, - "release_date": "2026-04-27", - "last_updated": "2026-04-27", + "open_weights": true, + "release_date": "2025-10-15", + "last_updated": "2025-11-25", "type": "chat" }, { - "id": "happyhorse-1.1-r2v", - "name": "HappyHorse 1.1 Reference-to-Video", - "display_name": "HappyHorse 1.1 Reference-to-Video", + "id": "Qwen/Qwen3-30B-A3B-Instruct-2507", + "name": "Qwen/Qwen3-30B-A3B-Instruct-2507", + "display_name": "Qwen/Qwen3-30B-A3B-Instruct-2507", "modalities": { "input": [ - "image", "text" ], "output": [ - "video" + "text" ] }, "limit": { - "context": 8192, - "output": 8192 + "context": 262000, + "output": 32768 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { "supported": false }, - "attachment": true, - "open_weights": false, - "release_date": "2026-07-17", - "last_updated": "2026-07-17" + "attachment": false, + "open_weights": true, + "release_date": "2025-07-28", + "last_updated": "2025-07-28", + "type": "chat" }, { - "id": "kimi-k2.6", - "name": "Kimi K2.6", - "display_name": "Kimi K2.6", + "id": "Qwen/Qwen3.8-27B", + "name": "Qwen3.8 27B", + "display_name": "Qwen3.8 27B", "modalities": { "input": [ "text", - "image", - "video" + "image" ], "output": [ "text" @@ -195807,7 +196936,7 @@ }, "limit": { "context": 262144, - "output": 262144 + "output": 32768 }, "temperature": true, "tool_call": true, @@ -195817,45 +196946,39 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "low", + "medium", + "xhigh" ] } }, "attachment": true, "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-04-20", - "last_updated": "2026-04-20", + "release_date": "2026-08-14", + "last_updated": "2026-08-14", "type": "chat" }, { - "id": "kimi-k2.5", - "name": "Kimi K2.5", - "display_name": "Kimi K2.5", + "id": "Qwen/Qwen3-32B", + "name": "Qwen/Qwen3-32B", + "display_name": "Qwen/Qwen3-32B", "modalities": { "input": [ - "text", - "image", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 98304 + "context": 131000, + "output": 32768 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true + "supported": true }, "extra_capabilities": { "reasoning": { @@ -195868,37 +196991,35 @@ ] } }, - "attachment": true, + "attachment": false, "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-01-27", - "last_updated": "2026-01-27", + "knowledge": "2025-04", + "release_date": "2025-04-28", + "last_updated": "2025-04-28", "type": "chat" }, { - "id": "qwen3.8-max", - "name": "Qwen3.8 Max", - "display_name": "Qwen3.8 Max", + "id": "Qwen/Qwen3.6-35B-A3B", + "name": "Qwen/Qwen3.6-35B-A3B", + "display_name": "Qwen/Qwen3.6-35B-A3B", "modalities": { "input": [ "text", "image", - "video", - "pdf" + "video" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 262144, + "output": 65536 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true + "supported": true }, "extra_capabilities": { "reasoning": { @@ -195908,36 +197029,33 @@ "visibility": "summary", "continuation": [ "thinking_blocks" - ], - "effort_options": [ - "low", - "medium", - "xhigh" ] } }, "attachment": true, - "open_weights": false, - "release_date": "2026-08-03", - "last_updated": "2026-08-03", + "open_weights": true, + "knowledge": "2025-04", + "release_date": "2026-04-17", + "last_updated": "2026-04-17", "type": "chat" }, { - "id": "deepseek-v4.1-flash", - "name": "DeepSeek V4.1 Flash", - "display_name": "DeepSeek V4.1 Flash", + "id": "Qwen/Qwen3.5-35B-A3B", + "name": "Qwen/Qwen3.5-35B-A3B", + "display_name": "Qwen/Qwen3.5-35B-A3B", "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 384000 + "context": 262144, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -195953,25 +197071,20 @@ "visibility": "summary", "continuation": [ "thinking_blocks" - ], - "effort_options": [ - "low", - "high", - "max" ] } }, "attachment": true, "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-09-10", - "last_updated": "2026-09-10", + "knowledge": "2025-04", + "release_date": "2026-02-23", + "last_updated": "2026-02-23", "type": "chat" }, { - "id": "deepseek-v3.2", - "name": "DeepSeek V3.2", - "display_name": "DeepSeek V3.2", + "id": "stepfun-ai/Step-3.5-Flash", + "name": "stepfun-ai/Step-3.5-Flash", + "display_name": "stepfun-ai/Step-3.5-Flash", "modalities": { "input": [ "text" @@ -195981,8 +197094,8 @@ ] }, "limit": { - "context": 131072, - "output": 65536 + "context": 262000, + "output": 256000 }, "temperature": true, "tool_call": true, @@ -195990,67 +197103,78 @@ "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "supported": true - } - }, "attachment": false, "open_weights": true, "knowledge": "2025-01", - "release_date": "2025-12-03", - "last_updated": "2025-12-05", + "release_date": "2026-01-29", + "last_updated": "2026-02-13", "type": "chat" }, { - "id": "happyhorse-1.1-t2v", - "name": "HappyHorse 1.1 Text-to-Video", - "display_name": "HappyHorse 1.1 Text-to-Video", + "id": "zai-org/GLM-5.3-Flash", + "name": "GLM-5.3-Flash", + "display_name": "GLM-5.3-Flash", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ - "video" + "text" ] }, "limit": { - "context": 8192, - "output": 8192 + "context": 1048576, + "output": 131072 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, - "open_weights": false, - "release_date": "2026-07-17", - "last_updated": "2026-07-17" + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "effort_options": [ + "low", + "high", + "max" + ] + } + }, + "attachment": true, + "open_weights": true, + "release_date": "2026-08-26", + "last_updated": "2026-08-26", + "type": "chat" }, { - "id": "qwen3.6-plus", - "name": "Qwen3.6 Plus", - "display_name": "Qwen3.6 Plus", + "id": "zai-org/GLM-4.5V", + "name": "zai-org/GLM-4.5V", + "display_name": "zai-org/GLM-4.5V", "modalities": { "input": [ - "text", - "image", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 65536 + "context": 131072, + "output": 8192 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true + "supported": true }, "extra_capabilities": { "reasoning": { @@ -196064,16 +197188,16 @@ } }, "attachment": true, - "open_weights": false, + "open_weights": true, "knowledge": "2025-04", - "release_date": "2026-04-02", - "last_updated": "2026-04-02", + "release_date": "2025-08-11", + "last_updated": "2025-08-11", "type": "chat" }, { - "id": "deepseek-v4-flash-0731", - "name": "DeepSeek V4 Flash 0731", - "display_name": "DeepSeek V4 Flash 0731", + "id": "zai-org/GLM-5.2", + "name": "GLM-5.2", + "display_name": "GLM-5.2", "modalities": { "input": [ "text" @@ -196083,8 +197207,8 @@ ] }, "limit": { - "context": 1000000, - "output": 384000 + "context": 1049000, + "output": 262000 }, "temperature": true, "tool_call": true, @@ -196109,15 +197233,14 @@ }, "attachment": false, "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-07-31", - "last_updated": "2026-07-31", + "release_date": "2026-06-13", + "last_updated": "2026-06-13", "type": "chat" }, { - "id": "deepseek-v4-pro-0813", - "name": "DeepSeek V4 Pro 0813", - "display_name": "DeepSeek V4 Pro 0813", + "id": "zai-org/GLM-5.3", + "name": "GLM-5.3", + "display_name": "GLM-5.3", "modalities": { "input": [ "text" @@ -196127,8 +197250,8 @@ ] }, "limit": { - "context": 1000000, - "output": 384000 + "context": 1048576, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -196146,6 +197269,7 @@ "thinking_blocks" ], "effort_options": [ + "low", "high", "max" ] @@ -196153,82 +197277,69 @@ }, "attachment": false, "open_weights": true, - "release_date": "2026-08-13", - "last_updated": "2026-08-22", + "release_date": "2026-08-14", + "last_updated": "2026-08-14", "type": "chat" }, { - "id": "kimi-k2.7-code", - "name": "Kimi K2.7 Code", - "display_name": "Kimi K2.7 Code", + "id": "zai-org/GLM-4.5-Air", + "name": "zai-org/GLM-4.5-Air", + "display_name": "zai-org/GLM-4.5-Air", "modalities": { "input": [ - "text", - "image", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 131000, + "output": 98304 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } + "supported": false }, - "attachment": true, + "attachment": false, "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-06-12", - "last_updated": "2026-06-12", + "knowledge": "2025-04", + "release_date": "2025-07-28", + "last_updated": "2025-07-28", "type": "chat" }, { - "id": "wan2.7-image", - "name": "Wan2.7 Image", - "display_name": "Wan2.7 Image", + "id": "tencent/Hunyuan-A13B-Instruct", + "name": "tencent/Hunyuan-A13B-Instruct", + "display_name": "tencent/Hunyuan-A13B-Instruct", "modalities": { "input": [ "text" ], "output": [ - "image" + "text" ] }, "limit": { - "context": 8192, - "output": 8192 + "context": 131000, + "output": 117964 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { "supported": false }, "attachment": false, - "open_weights": false, - "release_date": "2026-05-29", - "last_updated": "2026-05-29" + "open_weights": true, + "release_date": "2025-06-30", + "last_updated": "2025-11-25", + "type": "chat" }, { - "id": "glm-5.1", - "name": "GLM-5.1", - "display_name": "GLM-5.1", + "id": "ByteDance-Seed/Seed-OSS-36B-Instruct", + "name": "ByteDance-Seed/Seed-OSS-36B-Instruct", + "display_name": "ByteDance-Seed/Seed-OSS-36B-Instruct", "modalities": { "input": [ "text" @@ -196238,82 +197349,57 @@ ] }, "limit": { - "context": 202752, - "output": 128000 + "context": 262000, + "output": 99560 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true + "supported": true }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] + "supported": true } }, "attachment": false, "open_weights": true, - "release_date": "2026-04-07", - "last_updated": "2026-04-07", + "release_date": "2025-09-04", + "last_updated": "2025-11-25", "type": "chat" }, { - "id": "qwen3.8-flash", - "name": "Qwen3.8 Flash", - "display_name": "Qwen3.8 Flash", + "id": "PaddlePaddle/PaddleOCR-VL-1.5", + "name": "PaddlePaddle/PaddleOCR-VL-1.5", + "display_name": "PaddlePaddle/PaddleOCR-VL-1.5", "modalities": { "input": [ "text", - "image", - "video" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 16384, + "output": 6226 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "effort_options": [ - "low", - "medium", - "xhigh" - ] - } + "supported": false }, "attachment": true, - "open_weights": false, - "release_date": "2026-08-26", - "last_updated": "2026-08-26", + "open_weights": true, + "release_date": "2026-01-29", + "last_updated": "2026-01-29", "type": "chat" }, { - "id": "glm-5", - "name": "GLM-5", - "display_name": "GLM-5", + "id": "inclusionAI/Ling-flash-2.0", + "name": "inclusionAI/Ling-flash-2.0", + "display_name": "inclusionAI/Ling-flash-2.0", "modalities": { "input": [ "text" @@ -196323,36 +197409,24 @@ ] }, "limit": { - "context": 202752, - "output": 16384 + "context": 131000, + "output": 49780 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } + "supported": false }, "attachment": false, - "open_weights": false, - "release_date": "2026-02-12", - "last_updated": "2026-02-12", + "open_weights": true, + "release_date": "2025-09-18", + "last_updated": "2025-11-25", "type": "chat" }, { - "id": "glm-5.3", - "name": "GLM-5.3", - "display_name": "GLM-5.3", + "id": "deepseek-ai/DeepSeek-V3.1-Terminus", + "name": "deepseek-ai/DeepSeek-V3.1-Terminus", + "display_name": "deepseek-ai/DeepSeek-V3.1-Terminus", "modalities": { "input": [ "text" @@ -196362,8 +197436,8 @@ ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 164000, + "output": 8192 }, "temperature": true, "tool_call": true, @@ -196371,32 +197445,16 @@ "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "effort_options": [ - "low", - "high", - "max" - ] - } - }, "attachment": false, "open_weights": true, - "release_date": "2026-08-14", - "last_updated": "2026-08-14", + "release_date": "2025-09-29", + "last_updated": "2025-11-25", "type": "chat" }, { - "id": "MiniMax-M2.5", - "name": "MiniMax-M2.5", - "display_name": "MiniMax-M2.5", + "id": "deepseek-ai/DeepSeek-V4-Pro", + "name": "deepseek-ai/DeepSeek-V4-Pro", + "display_name": "deepseek-ai/DeepSeek-V4-Pro", "modalities": { "input": [ "text" @@ -196406,8 +197464,8 @@ ] }, "limit": { - "context": 196608, - "output": 32768 + "context": 1049000, + "output": 393000 }, "temperature": true, "tool_call": true, @@ -196418,109 +197476,102 @@ "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "high", + "effort_options": [ + "low", + "high", + "max" + ], "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ "thinking_blocks" + ], + "notes": [ + "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." ] } }, "attachment": false, "open_weights": true, - "release_date": "2026-02-12", - "last_updated": "2026-02-12", + "knowledge": "2025-05", + "release_date": "2026-04-24", + "last_updated": "2026-04-24", "type": "chat" }, { - "id": "qwen3.8-max-preview", - "name": "Qwen3.8 Max Preview", - "display_name": "Qwen3.8 Max Preview", + "id": "deepseek-ai/DeepSeek-V3", + "name": "deepseek-ai/DeepSeek-V3", + "display_name": "deepseek-ai/DeepSeek-V3", "modalities": { "input": [ - "text", - "image", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 164000, + "output": 8192 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "effort_options": [ - "low", - "medium", - "xhigh" - ] - } + "supported": false }, - "attachment": true, - "open_weights": false, - "release_date": "2026-07-19", - "last_updated": "2026-07-19", + "attachment": false, + "open_weights": true, + "release_date": "2025-03-24", + "last_updated": "2025-03-24", "type": "chat" }, { - "id": "wan2.7-image-pro", - "name": "Wan2.7 Image Pro", - "display_name": "Wan2.7 Image Pro", + "id": "deepseek-ai/DeepSeek-OCR", + "name": "deepseek-ai/DeepSeek-OCR", + "display_name": "deepseek-ai/DeepSeek-OCR", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ - "image" + "text" ] }, "limit": { "context": 8192, - "output": 8192 + "output": 3113 }, "temperature": true, "tool_call": false, "reasoning": { "supported": false }, - "attachment": false, - "open_weights": false, - "release_date": "2026-05-29", - "last_updated": "2026-05-29" + "attachment": true, + "open_weights": true, + "release_date": "2025-10-20", + "last_updated": "2025-10-20" }, { - "id": "qwen3.7-plus", - "name": "Qwen3.7 Plus", - "display_name": "Qwen3.7 Plus", + "id": "deepseek-ai/DeepSeek-R1", + "name": "deepseek-ai/DeepSeek-R1", + "display_name": "deepseek-ai/DeepSeek-R1", "modalities": { "input": [ - "text", - "image", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 65536 + "context": 164000, + "output": 8192 }, "temperature": true, "tool_call": true, @@ -196539,17 +197590,16 @@ ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-04", - "release_date": "2026-06-02", - "last_updated": "2026-06-02", + "attachment": false, + "open_weights": true, + "release_date": "2025-05-28", + "last_updated": "2025-05-28", "type": "chat" }, { - "id": "glm-5.2", - "name": "GLM-5.2", - "display_name": "GLM-5.2", + "id": "deepseek-ai/DeepSeek-V4-Flash", + "name": "DeepSeek V4 Flash", + "display_name": "DeepSeek V4 Flash", "modalities": { "input": [ "text" @@ -196560,7 +197610,7 @@ }, "limit": { "context": 1000000, - "output": 131072 + "output": 384000 }, "temperature": true, "tool_call": true, @@ -196571,37 +197621,36 @@ "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "high", + "effort_options": [ + "low", + "high", + "max" + ], "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ "thinking_blocks" ], - "effort_options": [ - "high", - "max" + "notes": [ + "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." ] } }, "attachment": false, "open_weights": true, - "release_date": "2026-06-13", - "last_updated": "2026-06-13", + "knowledge": "2025-05", + "release_date": "2026-04-24", + "last_updated": "2026-04-24", "type": "chat" - } - ] - }, - "upstage": { - "id": "upstage", - "name": "Upstage", - "display_name": "Upstage", - "api": "https://api.upstage.ai/v1/solar", - "doc": "https://developers.upstage.ai/docs/apis/chat", - "models": [ + }, { - "id": "solar-mini4", - "name": "Solar Mini 4", - "display_name": "Solar Mini 4", + "id": "deepseek-ai/DeepSeek-V3.2", + "name": "deepseek-ai/DeepSeek-V3.2", + "display_name": "deepseek-ai/DeepSeek-V3.2", "modalities": { "input": [ "text" @@ -196611,8 +197660,8 @@ ] }, "limit": { - "context": 524288, - "output": 131072 + "context": 164000, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -196622,28 +197671,20 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "none", - "minimal", - "low", - "medium", - "high", - "xhigh", - "max" - ] + "supported": true } }, "attachment": false, - "open_weights": false, - "knowledge": "2026-02", - "release_date": "2026-09-22", - "last_updated": "2026-09-22", + "open_weights": true, + "knowledge": "2024-07", + "release_date": "2025-12-01", + "last_updated": "2025-12-01", "type": "chat" }, { - "id": "solar-pro3", - "name": "solar-pro3", - "display_name": "solar-pro3", + "id": "deepseek-ai/DeepSeek-V3.2-Exp", + "name": "deepseek-ai/DeepSeek-V3.2-Exp", + "display_name": "deepseek-ai/DeepSeek-V3.2-Exp", "modalities": { "input": [ "text" @@ -196656,32 +197697,38 @@ "context": 131072, "output": 8192 }, - "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true + "supported": false }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "low", - "medium", - "high" - ] - } + "type": "chat" + }, + { + "id": "Pro/deepseek-ai/DeepSeek-V3.2-Exp", + "name": "Pro/deepseek-ai/DeepSeek-V3.2-Exp", + "display_name": "Pro/deepseek-ai/DeepSeek-V3.2-Exp", + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "output": 8192 + }, + "tool_call": true, + "reasoning": { + "supported": false }, - "attachment": false, - "open_weights": false, - "knowledge": "2025-03", - "release_date": "2026-01-26", - "last_updated": "2026-03-23", "type": "chat" }, { - "id": "solar-pro2", - "name": "solar-pro2", - "display_name": "solar-pro2", + "id": "inclusionAI/Ring-1T", + "name": "inclusionAI/Ring-1T", + "display_name": "inclusionAI/Ring-1T", "modalities": { "input": [ "text" @@ -196691,10 +197738,9 @@ ] }, "limit": { - "context": 65536, + "context": 131072, "output": 8192 }, - "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -196702,23 +197748,15 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "minimal", - "high" - ] + "supported": true } }, - "attachment": false, - "open_weights": false, - "knowledge": "2025-03", - "release_date": "2025-05-20", - "last_updated": "2025-05-20", "type": "chat" }, { - "id": "solar-mini", - "name": "solar-mini", - "display_name": "solar-mini", + "id": "inclusionAI/Ling-1T", + "name": "inclusionAI/Ling-1T", + "display_name": "inclusionAI/Ling-1T", "modalities": { "input": [ "text" @@ -196728,25 +197766,19 @@ ] }, "limit": { - "context": 32768, - "output": 4096 + "context": 131072, + "output": 8192 }, - "temperature": true, "tool_call": true, "reasoning": { "supported": false }, - "attachment": false, - "open_weights": false, - "knowledge": "2024-09", - "release_date": "2024-06-12", - "last_updated": "2025-04-22", "type": "chat" }, { - "id": "solar-pro4", - "name": "Solar Pro 4", - "display_name": "Solar Pro 4", + "id": "zai-org/GLM-4.6", + "name": "zai-org/GLM-4.6", + "display_name": "zai-org/GLM-4.6", "modalities": { "input": [ "text" @@ -196756,62 +197788,34 @@ ] }, "limit": { - "context": 524288, - "output": 131072 + "context": 131072, + "output": 8192 }, - "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true + "supported": true }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "none", - "minimal", - "low", - "medium", - "high", - "xhigh", - "max" - ] + "supported": true } }, - "attachment": false, - "open_weights": false, - "knowledge": "2026-02", - "release_date": "2026-08-06", - "last_updated": "2026-08-06", "type": "chat" - } - ] - }, - "deepseek": { - "id": "deepseek", - "name": "DeepSeek", - "display_name": "DeepSeek", - "api": "https://api.deepseek.com", - "doc": "https://api-docs.deepseek.com/quick_start/pricing", - "models": [ + }, { - "id": "deepseek-v4-flash", - "name": "DeepSeek V4 Flash", - "display_name": "DeepSeek V4 Flash", - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 1048576, - "output": 393216 + "id": "Qwen/Qwen3-VL-235B-A22B-Instruct", + "name": "Qwen/Qwen3-VL-235B-A22B-Instruct", + "display_name": "Qwen/Qwen3-VL-235B-A22B-Instruct", + "tool_call": true, + "reasoning": { + "supported": false }, - "temperature": true, + "type": "chat" + }, + { + "id": "Qwen/Qwen3-VL-235B-A22B-Thinking", + "name": "Qwen/Qwen3-VL-235B-A22B-Thinking", + "display_name": "Qwen/Qwen3-VL-235B-A22B-Thinking", "tool_call": true, "reasoning": { "supported": true, @@ -196820,36 +197824,20 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "high", - "effort_options": [ - "low", - "high", - "max" - ], "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ "thinking_blocks" - ], - "notes": [ - "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." ] } }, - "attachment": true, - "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-10-17", - "last_updated": "2026-10-17", "type": "chat" }, { - "id": "deepseek-v4-pro", - "name": "DeepSeek V4 Pro", - "display_name": "DeepSeek V4 Pro", + "id": "Qwen/Qwen3-Omni-30B-A3B-Instruct", + "name": "Qwen/Qwen3-Omni-30B-A3B-Instruct", + "display_name": "Qwen/Qwen3-Omni-30B-A3B-Instruct", "modalities": { "input": [ "text" @@ -196859,10 +197847,31 @@ ] }, "limit": { - "context": 1048576, - "output": 393216 + "context": 131072, + "output": 8192 + }, + "tool_call": true, + "reasoning": { + "supported": false + }, + "type": "chat" + }, + { + "id": "Qwen/Qwen3-Omni-30B-A3B-Thinking", + "name": "Qwen/Qwen3-Omni-30B-A3B-Thinking", + "display_name": "Qwen/Qwen3-Omni-30B-A3B-Thinking", + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "output": 8192 }, - "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -196871,101 +197880,120 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "high", - "effort_options": [ - "low", - "high", - "max" - ], "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ "thinking_blocks" - ], - "notes": [ - "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." ] } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-08-13", - "last_updated": "2026-08-22", "type": "chat" }, { - "id": "deepseek-v4-flash-vision-exp", - "name": "DeepSeek V4 Flash Vision Exp", - "display_name": "DeepSeek V4 Flash Vision Exp", + "id": "Qwen/Qwen3-Omni-30B-A3B-Captioner", + "name": "Qwen/Qwen3-Omni-30B-A3B-Captioner", + "display_name": "Qwen/Qwen3-Omni-30B-A3B-Captioner", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 384000 + "context": 131072, + "output": 8192 }, - "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true + "supported": false }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "high", - "effort_options": [ - "low", - "high", - "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." - ] - } + "type": "chat" + }, + { + "id": "moonshotai/Kimi-K2-Instruct-0905", + "name": "moonshotai/Kimi-K2-Instruct-0905", + "display_name": "moonshotai/Kimi-K2-Instruct-0905", + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "output": 8192 + }, + "tool_call": true, + "reasoning": { + "supported": false + }, + "type": "chat" + }, + { + "id": "Pro/moonshotai/Kimi-K2-Instruct-0905", + "name": "Pro/moonshotai/Kimi-K2-Instruct-0905", + "display_name": "Pro/moonshotai/Kimi-K2-Instruct-0905", + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "output": 8192 + }, + "tool_call": true, + "reasoning": { + "supported": false + }, + "type": "chat" + }, + { + "id": "Qwen/Qwen3-Next-80B-A3B-Instruct", + "name": "Qwen/Qwen3-Next-80B-A3B-Instruct", + "display_name": "Qwen/Qwen3-Next-80B-A3B-Instruct", + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "output": 8192 + }, + "tool_call": true, + "reasoning": { + "supported": false }, - "attachment": true, - "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-08-21", - "last_updated": "2026-08-21", "type": "chat" }, { - "id": "deepseek-flash", - "name": "DeepSeek V4.1 Flash", - "display_name": "DeepSeek V4.1 Flash", + "id": "Qwen/Qwen3-Next-80B-A3B-Thinking", + "name": "Qwen/Qwen3-Next-80B-A3B-Thinking", + "display_name": "Qwen/Qwen3-Next-80B-A3B-Thinking", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 384000 + "context": 131072, + "output": 8192 }, - "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -196979,50 +198007,27 @@ "visibility": "summary", "continuation": [ "thinking_blocks" - ], - "effort_options": [ - "low", - "high", - "max" ] } }, - "attachment": true, - "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-09-10", - "last_updated": "2026-09-10", "type": "chat" - } - ] - }, - "google": { - "id": "google", - "name": "google", - "display_name": "google", - "doc": "https://ai.google.dev/gemini-api/docs/models", - "models": [ + }, { - "id": "gemini-pro-latest", - "name": "Gemini Pro Latest", - "display_name": "Gemini Pro Latest", + "id": "inclusionAI/Ring-flash-2.0", + "name": "inclusionAI/Ring-flash-2.0", + "display_name": "inclusionAI/Ring-flash-2.0", "modalities": { "input": [ - "text", - "image", - "video", - "audio", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 131072, + "output": 8192 }, - "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -197030,426 +198035,267 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "low", - "medium", - "high" - ] + "supported": true } }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-01", - "release_date": "2026-02-19", - "last_updated": "2026-02-19", "type": "chat" }, { - "id": "gemini-3-flash-preview", - "name": "Gemini 3 Flash Preview", - "display_name": "Gemini 3 Flash Preview", + "id": "inclusionAI/Ling-mini-2.0", + "name": "inclusionAI/Ling-mini-2.0", + "display_name": "inclusionAI/Ling-mini-2.0", "modalities": { "input": [ - "text", - "image", - "video", - "audio", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 131072, + "output": 8192 }, - "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "level", - "level": "high", - "level_options": [ - "minimal", - "low", - "medium", - "high" - ], - "summaries": true, - "visibility": "summary", - "continuation": [ - "thought_signatures" - ] - } + "supported": false }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-01", - "release_date": "2025-12-17", - "last_updated": "2025-12-17", "type": "chat" }, { - "id": "gemini-2.5-pro", - "name": "Gemini 2.5 Pro", - "display_name": "Gemini 2.5 Pro", + "id": "Qwen/Qwen-Image-Edit-2509", + "name": "Qwen/Qwen-Image-Edit-2509", + "display_name": "Qwen/Qwen-Image-Edit-2509", "modalities": { "input": [ - "text", "image", - "audio", - "video", - "pdf" + "text" ], "output": [ - "text" + "image" ] }, - "limit": { - "context": 1048576, - "output": 65536 - }, - "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true, - "budget": { - "default": -1, - "min": 128, - "max": 32768 - } - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "budget", - "budget": { - "default": -1, - "min": 128, - "max": 32768, - "auto": -1, - "unit": "tokens" - }, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thought_signatures" - ] - } - }, - "search": { - "supported": true, - "default": false - }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-01", - "release_date": "2025-06-17", - "last_updated": "2025-06-17", - "type": "chat" + "supported": false + } }, { - "id": "gemini-2.5-flash-image", - "name": "Gemini 2.5 Flash Image", - "display_name": "Gemini 2.5 Flash Image", + "id": "Qwen/Qwen-Image-Edit", + "name": "Qwen/Qwen-Image-Edit", + "display_name": "Qwen/Qwen-Image-Edit", "modalities": { "input": [ - "text", - "image" + "image", + "text" ], "output": [ - "text", "image" ] }, - "limit": { - "context": 32768, - "output": 32768 - }, - "temperature": true, "tool_call": false, "reasoning": { "supported": false + } + }, + { + "id": "Qwen/Qwen-Image", + "name": "Qwen/Qwen-Image", + "display_name": "Qwen/Qwen-Image", + "modalities": { + "input": [ + "text" + ], + "output": [ + "image" + ] }, - "attachment": false, - "open_weights": false, - "knowledge": "2024-06", - "release_date": "2025-08-26", - "last_updated": "2025-08-26" + "tool_call": false, + "reasoning": { + "supported": false + } }, { - "id": "gemini-3.5-flash-lite", - "name": "Gemini 3.5 Flash Lite", - "display_name": "Gemini 3.5 Flash Lite", + "id": "tencent/Hunyuan-MT-7B", + "name": "tencent/Hunyuan-MT-7B", + "display_name": "tencent/Hunyuan-MT-7B", "modalities": { "input": [ - "text", - "image", - "video", - "audio", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 131072, + "output": 8192 }, - "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "effort_options": [ - "minimal", - "low", - "medium", - "high" - ] - } + "supported": false }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-03", - "release_date": "2026-07-21", - "last_updated": "2026-07-21", "type": "chat" }, { - "id": "deep-research-max-preview-04-2026", - "name": "Deep Research Max Preview (Apr-21-2026)", - "display_name": "Deep Research Max Preview (Apr-21-2026)", + "id": "Wan-AI/Wan2.2-I2V-A14B", + "name": "Wan-AI/Wan2.2-I2V-A14B", + "display_name": "Wan-AI/Wan2.2-I2V-A14B", "modalities": { "input": [ - "text", "image", - "video", - "audio", - "pdf" + "text" ], "output": [ - "text", - "image" + "video" + ] + }, + "tool_call": false, + "reasoning": { + "supported": false + } + }, + { + "id": "Wan-AI/Wan2.2-T2V-A14B", + "name": "Wan-AI/Wan2.2-T2V-A14B", + "display_name": "Wan-AI/Wan2.2-T2V-A14B", + "modalities": { + "input": [ + "text" + ], + "output": [ + "video" + ] + }, + "tool_call": false, + "reasoning": { + "supported": false + } + }, + { + "id": "zai-org/GLM-4.5", + "name": "zai-org/GLM-4.5", + "display_name": "zai-org/GLM-4.5", + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" ] }, "limit": { "context": 131072, - "output": 65536 + "output": 8192 }, - "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true + "supported": false }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-01", - "release_date": "2026-04-21", - "last_updated": "2026-04-21", "type": "chat" }, { - "id": "gemini-3.1-flash-tts-preview", - "name": "Gemini 3.1 Flash TTS Preview", - "display_name": "Gemini 3.1 Flash TTS Preview", + "id": "stepfun-ai/step3", + "name": "stepfun-ai/step3", + "display_name": "stepfun-ai/step3", "modalities": { "input": [ "text" ], "output": [ - "audio" + "text" ] }, "limit": { - "context": 8192, - "output": 16384 + "context": 131072, + "output": 8192 }, - "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": true, - "default": true + "supported": false }, - "attachment": false, - "open_weights": false, - "knowledge": "2025-01", - "release_date": "2026-04-15", - "last_updated": "2026-04-15" + "type": "chat" }, { - "id": "gemini-3.1-flash-image", - "name": "Nano Banana 2", - "display_name": "Nano Banana 2", + "id": "TeleAI/TeleSpeechASR", + "name": "TeleAI/TeleSpeechASR", + "display_name": "TeleAI/TeleSpeechASR", "modalities": { "input": [ - "text", - "image", - "video", - "pdf" + "audio" ], "output": [ - "text", - "image" + "text" ] }, - "limit": { - "context": 131072, - "output": 32768 - }, - "temperature": true, "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "effort_options": [ - "minimal", - "high" - ] - } - }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-01", - "release_date": "2026-05-28", - "last_updated": "2026-05-28" + "supported": false + } }, { - "id": "gemma-4-31b-it", - "name": "Gemma 4 31B IT", - "display_name": "Gemma 4 31B IT", + "id": "Qwen/Qwen3-Coder-480B-A35B-Instruct", + "name": "Qwen/Qwen3-Coder-480B-A35B-Instruct", + "display_name": "Qwen/Qwen3-Coder-480B-A35B-Instruct", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, - "limit": { - "context": 262144, - "output": 32768 - }, - "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true - } - }, - "attachment": true, - "open_weights": true, - "release_date": "2026-04-02", - "last_updated": "2026-04-02", - "type": "chat" + "supported": false + } }, { - "id": "gemini-2.5-flash", - "name": "Gemini 2.5 Flash", - "display_name": "Gemini 2.5 Flash", + "id": "Qwen/Qwen3-30B-A3B-Thinking-2507", + "name": "Qwen/Qwen3-30B-A3B-Thinking-2507", + "display_name": "Qwen/Qwen3-30B-A3B-Thinking-2507", "modalities": { "input": [ - "text", - "image", - "audio", - "video", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 131072, + "output": 8192 }, - "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true, - "budget": { - "default": -1, - "min": 0, - "max": 24576 - } + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "budget", - "budget": { - "default": -1, - "min": 0, - "max": 24576, - "auto": -1, - "off": 0, - "unit": "tokens" - }, + "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ - "thought_signatures" + "thinking_blocks" ] } }, - "search": { - "supported": true, - "default": false - }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-01", - "release_date": "2025-06-17", - "last_updated": "2025-06-17", "type": "chat" }, { - "id": "gemini-3.1-flash-lite", - "name": "Gemini 3.1 Flash Lite", - "display_name": "Gemini 3.1 Flash Lite", + "id": "Qwen/Qwen3-235B-A22B-Thinking-2507", + "name": "Qwen/Qwen3-235B-A22B-Thinking-2507", + "display_name": "Qwen/Qwen3-235B-A22B-Thinking-2507", "modalities": { "input": [ - "text", - "image", - "video", - "audio", - "pdf" + "text" ], "output": [ "text" ] }, - "limit": { - "context": 1048576, - "output": 65536 - }, - "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -197458,791 +198304,559 @@ "extra_capabilities": { "reasoning": { "supported": true, - "effort_options": [ - "minimal", - "low", - "medium", - "high" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-01", - "release_date": "2026-05-07", - "last_updated": "2026-05-07", "type": "chat" }, { - "id": "gemini-2.5-computer-use-preview-10-2025", - "name": "Gemini 2.5 Computer Use Preview 10-2025", - "display_name": "Gemini 2.5 Computer Use Preview 10-2025", + "id": "Qwen/Qwen3-235B-A22B-Instruct-2507", + "name": "Qwen/Qwen3-235B-A22B-Instruct-2507", + "display_name": "Qwen/Qwen3-235B-A22B-Instruct-2507", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 64000 + "context": 131072, + "output": 8192 }, - "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true + "supported": false }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-01", - "release_date": "2025-10-07", - "last_updated": "2025-10-07", "type": "chat" }, { - "id": "gemini-3.7-flash", - "name": "Gemini 3.7 Flash", - "display_name": "Gemini 3.7 Flash", + "id": "THUDM/GLM-4.1V-9B-Thinking", + "name": "THUDM/GLM-4.1V-9B-Thinking", + "display_name": "THUDM/GLM-4.1V-9B-Thinking", "modalities": { "input": [ - "text", - "image", - "video", - "audio", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 131072, + "output": 8192 }, - "temperature": true, "tool_call": true, "reasoning": { "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "level", - "level": "high", - "level_options": [ - "minimal", - "low", - "medium", - "high" - ], - "summaries": true, - "visibility": "summary", - "continuation": [ - "thought_signatures" - ] - } - }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-03", - "release_date": "2026-08-13", - "last_updated": "2026-08-13", "type": "chat" }, { - "id": "deep-research-preview-04-2026", - "name": "Deep Research Preview (Apr-21-2026)", - "display_name": "Deep Research Preview (Apr-21-2026)", + "id": "Pro/THUDM/GLM-4.1V-9B-Thinking", + "name": "Pro/THUDM/GLM-4.1V-9B-Thinking", + "display_name": "Pro/THUDM/GLM-4.1V-9B-Thinking", "modalities": { "input": [ - "text", - "image", - "video", - "audio", - "pdf" + "text" ], "output": [ - "text", - "image" + "text" ] }, "limit": { "context": 131072, - "output": 65536 + "output": 8192 }, - "temperature": true, "tool_call": true, "reasoning": { "supported": true, "default": true }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-01", - "release_date": "2026-04-21", - "last_updated": "2026-04-21", "type": "chat" }, { - "id": "gemini-3-pro-image", - "name": "Nano Banana Pro", - "display_name": "Nano Banana Pro", + "id": "baidu/ERNIE-4.5-300B-A47B", + "name": "baidu/ERNIE-4.5-300B-A47B", + "display_name": "baidu/ERNIE-4.5-300B-A47B", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ - "text", - "image" + "text" + ] + }, + "tool_call": true, + "reasoning": { + "supported": false + }, + "type": "chat" + }, + { + "id": "moonshotai/Kimi-Dev-72B", + "name": "moonshotai/Kimi-Dev-72B", + "display_name": "moonshotai/Kimi-Dev-72B", + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" ] }, "limit": { - "context": 65536, - "output": 32768 + "context": 131072, + "output": 8192 }, - "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "level", - "level": "high", - "level_options": [ - "low", - "high" - ], - "summaries": true, - "visibility": "summary", - "continuation": [ - "thought_signatures" - ] - } + "supported": false }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-01", - "release_date": "2026-05-28", - "last_updated": "2026-05-28" + "type": "chat" }, { - "id": "gemini-3.1-flash-lite-preview", - "name": "Gemini 3.1 Flash Lite Preview", - "display_name": "Gemini 3.1 Flash Lite Preview", + "id": "MiniMaxAI/MiniMax-M1-80k", + "name": "MiniMaxAI/MiniMax-M1-80k", + "display_name": "MiniMaxAI/MiniMax-M1-80k", "modalities": { "input": [ - "text", - "image", - "video", - "audio", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 131072, + "output": 8192 }, - "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "minimal", - "low", - "medium", - "high" - ] - } + "supported": false }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-01", - "release_date": "2026-03-03", - "last_updated": "2026-03-03", "type": "chat" }, { - "id": "gemma-4-26b-a4b-it", - "name": "Gemma 4 26B A4B IT", - "display_name": "Gemma 4 26B A4B IT", + "id": "Tongyi-Zhiwen/QwenLong-L1-32B", + "name": "Tongyi-Zhiwen/QwenLong-L1-32B", + "display_name": "Tongyi-Zhiwen/QwenLong-L1-32B", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 32768 + "context": 131072, + "output": 8192 }, - "temperature": true, "tool_call": true, "reasoning": { "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "supported": true - } - }, - "attachment": true, - "open_weights": true, - "release_date": "2026-04-02", - "last_updated": "2026-04-02", "type": "chat" }, { - "id": "gemini-embedding-2", - "name": "Gemini Embedding 2", - "display_name": "Gemini Embedding 2", + "id": "deepseek-ai/DeepSeek-R1-0528-Qwen3-8B", + "name": "deepseek-ai/DeepSeek-R1-0528-Qwen3-8B", + "display_name": "deepseek-ai/DeepSeek-R1-0528-Qwen3-8B", "modalities": { "input": [ - "text", - "image", - "audio", - "video", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 8192, - "output": 1 + "context": 131072, + "output": 8192 }, - "temperature": false, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-11", - "release_date": "2026-04-22", - "last_updated": "2026-04-22", - "type": "embedding" + "type": "chat" }, { - "id": "gemini-3.1-pro-preview-customtools", - "name": "Gemini 3.1 Pro Preview Custom Tools", - "display_name": "Gemini 3.1 Pro Preview Custom Tools", + "id": "Qwen/Qwen3-30B-A3B", + "name": "Qwen/Qwen3-30B-A3B", + "display_name": "Qwen/Qwen3-30B-A3B", "modalities": { "input": [ - "text", - "image", - "video", - "audio", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 131072, + "output": 8192 }, - "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true + "supported": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "level", - "level": "high", - "level_options": [ - "low", - "high" - ], + "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ - "thought_signatures" + "thinking_blocks" ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-01", - "release_date": "2026-02-19", - "last_updated": "2026-02-19", "type": "chat" }, { - "id": "gemini-flash-latest", - "name": "Gemini Flash Latest", - "display_name": "Gemini Flash Latest", + "id": "Qwen/Qwen3-Reranker-8B", + "name": "Qwen/Qwen3-Reranker-8B", + "display_name": "Qwen/Qwen3-Reranker-8B", "modalities": { "input": [ - "text", - "image", - "video", - "audio", - "pdf" + "text" ], "output": [ + "score" + ] + }, + "limit": { + "context": 8192, + "output": 2048 + }, + "tool_call": false, + "reasoning": { + "supported": false + }, + "type": "rerank" + }, + { + "id": "Qwen/Qwen3-Embedding-8B", + "name": "Qwen/Qwen3-Embedding-8B", + "display_name": "Qwen/Qwen3-Embedding-8B", + "modalities": { + "input": [ "text" + ], + "output": [ + "embedding" ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 8192, + "output": 2048 }, - "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true + "supported": false }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "low", - "medium", - "high" - ] - } + "type": "embedding" + }, + { + "id": "Qwen/Qwen3-Reranker-4B", + "name": "Qwen/Qwen3-Reranker-4B", + "display_name": "Qwen/Qwen3-Reranker-4B", + "modalities": { + "input": [ + "text" + ], + "output": [ + "score" + ] }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-03", - "release_date": "2026-08-13", - "last_updated": "2026-08-13", - "type": "chat" + "limit": { + "context": 8192, + "output": 2048 + }, + "tool_call": false, + "reasoning": { + "supported": false + }, + "type": "rerank" }, { - "id": "gemini-2.5-flash-preview-tts", - "name": "Gemini 2.5 Flash Preview TTS", - "display_name": "Gemini 2.5 Flash Preview TTS", + "id": "Qwen/Qwen3-Embedding-4B", + "name": "Qwen/Qwen3-Embedding-4B", + "display_name": "Qwen/Qwen3-Embedding-4B", "modalities": { "input": [ "text" ], "output": [ - "audio" + "embedding" ] }, "limit": { "context": 8192, - "output": 16384 + "output": 2048 }, - "temperature": true, "tool_call": false, "reasoning": { "supported": false }, - "attachment": false, - "open_weights": false, - "knowledge": "2025-01", - "release_date": "2025-05-01", - "last_updated": "2025-05-01" + "type": "embedding" }, { - "id": "gemini-3.1-flash-lite-image", - "name": "Nano Banana 2 Lite", - "display_name": "Nano Banana 2 Lite", + "id": "Qwen/Qwen3-Reranker-0.6B", + "name": "Qwen/Qwen3-Reranker-0.6B", + "display_name": "Qwen/Qwen3-Reranker-0.6B", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ - "text", - "image" + "score" ] }, "limit": { - "context": 65536, - "output": 4096 + "context": 8192, + "output": 2048 }, - "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true + "supported": false }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "effort_options": [ - "minimal", - "high" - ] - } + "type": "rerank" + }, + { + "id": "Qwen/Qwen3-Embedding-0.6B", + "name": "Qwen/Qwen3-Embedding-0.6B", + "display_name": "Qwen/Qwen3-Embedding-0.6B", + "modalities": { + "input": [ + "text" + ], + "output": [ + "embedding" + ] }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-01", - "release_date": "2026-06-30", - "last_updated": "2026-06-30" + "limit": { + "context": 8192, + "output": 2048 + }, + "tool_call": false, + "reasoning": { + "supported": false + }, + "type": "embedding" }, { - "id": "gemini-3.1-pro-preview", - "name": "Gemini 3.1 Pro Preview", - "display_name": "Gemini 3.1 Pro Preview", + "id": "ascend-tribe/pangu-pro-moe", + "name": "ascend-tribe/pangu-pro-moe", + "display_name": "ascend-tribe/pangu-pro-moe", "modalities": { "input": [ - "text", - "image", - "video", - "audio", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 131072, + "output": 8192 }, - "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "level", - "level": "high", - "level_options": [ - "low", - "high" - ], - "summaries": true, - "visibility": "summary", - "continuation": [ - "thought_signatures" - ] - } + "supported": false }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-01", - "release_date": "2026-02-19", - "last_updated": "2026-02-19", "type": "chat" }, { - "id": "gemini-2.5-flash-lite", - "name": "Gemini 2.5 Flash Lite", - "display_name": "Gemini 2.5 Flash Lite", + "id": "THUDM/GLM-Z1-32B-0414", + "name": "THUDM/GLM-Z1-32B-0414", + "display_name": "THUDM/GLM-Z1-32B-0414", "modalities": { "input": [ - "text", - "image", - "audio", - "video", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 131072, + "output": 8192 }, - "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": false, - "budget": { - "default": -1, - "min": 512, - "max": 24576 - } - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "budget", - "budget": { - "default": -1, - "min": 512, - "max": 24576, - "auto": -1, - "unit": "tokens" - }, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thought_signatures" - ] - } - }, - "search": { - "supported": true, - "default": false + "supported": false }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-01", - "release_date": "2025-06-17", - "last_updated": "2025-06-17", "type": "chat" }, { - "id": "gemini-omni-flash-preview", - "name": "Gemini Omni Flash Preview", - "display_name": "Gemini Omni Flash Preview", + "id": "THUDM/GLM-4-32B-0414", + "name": "THUDM/GLM-4-32B-0414", + "display_name": "THUDM/GLM-4-32B-0414", + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "output": 8192 + }, + "tool_call": true, + "reasoning": { + "supported": false + }, + "type": "chat" + }, + { + "id": "THUDM/GLM-Z1-9B-0414", + "name": "THUDM/GLM-Z1-9B-0414", + "display_name": "THUDM/GLM-Z1-9B-0414", "modalities": { "input": [ - "text", - "image", - "video" + "text" ], "output": [ - "video" + "text" ] }, "limit": { "context": 131072, - "output": 65536 + "output": 8192 }, - "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": true, - "default": true + "supported": false }, - "attachment": true, - "open_weights": false, - "release_date": "2026-06-30", - "last_updated": "2026-06-30" + "type": "chat" }, { - "id": "gemini-3.8-flash", - "name": "Gemini 3.8 Flash", - "display_name": "Gemini 3.8 Flash", + "id": "THUDM/GLM-4-9B-0414", + "name": "THUDM/GLM-4-9B-0414", + "display_name": "THUDM/GLM-4-9B-0414", "modalities": { "input": [ - "text", - "image", - "video", - "audio", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 131072, + "output": 8192 }, - "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "level", - "level": "high", - "level_options": [ - "minimal", - "low", - "medium", - "high" - ], - "summaries": true, - "visibility": "summary", - "continuation": [ - "thought_signatures" - ] - } + "supported": false }, - "attachment": true, - "open_weights": false, - "release_date": "2026-09-02", - "last_updated": "2026-09-02", "type": "chat" }, { - "id": "gemini-3.1-flash-live-preview", - "name": "Gemini 3.1 Flash Live Preview", - "display_name": "Gemini 3.1 Flash Live Preview", + "id": "Qwen/Qwen2.5-VL-32B-Instruct", + "name": "Qwen/Qwen2.5-VL-32B-Instruct", + "display_name": "Qwen/Qwen2.5-VL-32B-Instruct", "modalities": { "input": [ - "text", - "image", - "video", - "audio" + "text" ], "output": [ - "text", - "audio" + "text" ] }, "limit": { "context": 131072, - "output": 65536 + "output": 8192 }, - "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "level", - "level": "high", - "level_options": [ - "minimal", - "low", - "medium", - "high" - ], - "summaries": true, - "visibility": "summary", - "continuation": [ - "thought_signatures" - ] - } + "supported": false }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-01", - "release_date": "2026-03-26", - "last_updated": "2026-03-26" + "type": "chat" }, { - "id": "gemini-4-argon", - "name": "Gemini 4 Argon", - "display_name": "Gemini 4 Argon", + "id": "Qwen/Qwen3-235B-A22B", + "name": "Qwen/Qwen3-235B-A22B", + "display_name": "Qwen/Qwen3-235B-A22B", "modalities": { "input": [ - "text", - "image", - "video", - "audio", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 32768 + "context": 131072, + "output": 8192 }, - "temperature": false, "tool_call": true, "reasoning": { - "supported": true, - "default": true + "supported": true }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "low", - "medium", - "high" + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-03", - "release_date": "2026-10-13", - "last_updated": "2026-10-13", "type": "chat" }, { - "id": "gemini-3.6-flash", - "name": "Gemini 3.6 Flash", - "display_name": "Gemini 3.6 Flash", + "id": "Qwen/QwQ-32B", + "name": "Qwen/QwQ-32B", + "display_name": "Qwen/QwQ-32B", "modalities": { "input": [ - "text", - "image", - "video", - "audio", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 131072, + "output": 8192 }, - "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true + "supported": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "level", - "level": "high", - "level_options": [ - "minimal", - "low", - "medium", - "high" - ], + "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ - "thought_signatures" + "thinking_blocks" ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-03", - "release_date": "2026-07-21", - "last_updated": "2026-07-21", "type": "chat" }, { - "id": "gemini-embedding-001", - "name": "Gemini Embedding 001", - "display_name": "Gemini Embedding 001", + "id": "Qwen/Qwen2.5-VL-72B-Instruct", + "name": "Qwen/Qwen2.5-VL-72B-Instruct", + "display_name": "Qwen/Qwen2.5-VL-72B-Instruct", "modalities": { "input": [ "text" @@ -198252,523 +198866,310 @@ ] }, "limit": { - "context": 2048, - "output": 1 + "context": 131072, + "output": 8192 }, - "temperature": false, - "tool_call": false, + "tool_call": true, "reasoning": { "supported": false }, - "attachment": false, - "open_weights": false, - "knowledge": "2025-05", - "release_date": "2025-05-20", - "last_updated": "2025-05-20", - "type": "embedding" + "type": "chat" }, { - "id": "lyria-3-clip-preview", - "name": "Lyria 3 Clip Preview", - "display_name": "Lyria 3 Clip Preview", + "id": "Pro/Qwen/Qwen2.5-VL-7B-Instruct", + "name": "Pro/Qwen/Qwen2.5-VL-7B-Instruct", + "display_name": "Pro/Qwen/Qwen2.5-VL-7B-Instruct", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ - "text", - "audio" + "text" ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 131072, + "output": 8192 }, - "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { "supported": false }, - "attachment": true, - "open_weights": false, - "release_date": "2026-03-25", - "last_updated": "2026-03-25", "type": "chat" }, { - "id": "gemini-3.5-flash", - "name": "Gemini 3.5 Flash", - "display_name": "Gemini 3.5 Flash", + "id": "deepseek-ai/DeepSeek-R1-Distill-Qwen-32B", + "name": "deepseek-ai/DeepSeek-R1-Distill-Qwen-32B", + "display_name": "deepseek-ai/DeepSeek-R1-Distill-Qwen-32B", "modalities": { "input": [ - "text", - "image", - "video", - "audio", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 131072, + "output": 8192 }, - "temperature": true, "tool_call": true, "reasoning": { "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "level", - "level": "high", - "level_options": [ - "minimal", - "low", - "medium", - "high" - ], - "summaries": true, - "visibility": "summary", - "continuation": [ - "thought_signatures" - ] - } - }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-01", - "release_date": "2026-05-19", - "last_updated": "2026-05-19", "type": "chat" }, { - "id": "gemini-3.5-live-translate-preview", - "name": "Gemini 3.5 Live Translate Preview", - "display_name": "Gemini 3.5 Live Translate Preview", + "id": "deepseek-ai/DeepSeek-R1-Distill-Qwen-14B", + "name": "deepseek-ai/DeepSeek-R1-Distill-Qwen-14B", + "display_name": "deepseek-ai/DeepSeek-R1-Distill-Qwen-14B", "modalities": { "input": [ - "audio" + "text" ], "output": [ - "audio", "text" ] }, "limit": { - "context": 16384, - "output": 32768 + "context": 131072, + "output": 8192 }, - "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-01", - "release_date": "2026-06-09", - "last_updated": "2026-06-09" + "type": "chat" }, { - "id": "lyria-3-pro-preview", - "name": "Lyria 3 Pro Preview", - "display_name": "Lyria 3 Pro Preview", + "id": "deepseek-ai/DeepSeek-R1-Distill-Qwen-7B", + "name": "deepseek-ai/DeepSeek-R1-Distill-Qwen-7B", + "display_name": "deepseek-ai/DeepSeek-R1-Distill-Qwen-7B", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ - "text", - "audio" + "text" ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 131072, + "output": 8192 }, - "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": true, - "open_weights": false, - "release_date": "2026-03-25", - "last_updated": "2026-03-25", "type": "chat" }, { - "id": "gemini-flash-lite-latest", - "name": "Gemini Flash-Lite Latest", - "display_name": "Gemini Flash-Lite Latest", + "id": "Qwen/QVQ-72B-Preview", + "name": "Qwen/QVQ-72B-Preview", + "display_name": "Qwen/QVQ-72B-Preview", "modalities": { "input": [ - "text", - "image", - "video", - "audio", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 131072, + "output": 8192 }, - "temperature": true, "tool_call": true, "reasoning": { "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "minimal", - "low", - "medium", - "high" - ] - } - }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-03", - "release_date": "2026-07-21", - "last_updated": "2026-07-21", "type": "chat" }, { - "id": "gemini-2.5-pro-preview-tts", - "name": "Gemini 2.5 Pro Preview TTS", - "display_name": "Gemini 2.5 Pro Preview TTS", + "id": "deepseek-ai/DeepSeek-V2.5", + "name": "deepseek-ai/DeepSeek-V2.5", + "display_name": "deepseek-ai/DeepSeek-V2.5", "modalities": { "input": [ "text" ], "output": [ - "audio" + "text" ] }, "limit": { - "context": 8192, - "output": 16384 + "context": 131072, + "output": 8192 }, - "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "budget", - "budget": { - "default": -1, - "min": 128, - "max": 32768, - "auto": -1, - "unit": "tokens" - }, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thought_signatures" - ] - } + "supported": false }, - "attachment": false, - "open_weights": false, - "knowledge": "2025-01", - "release_date": "2025-05-01", - "last_updated": "2025-05-01" + "type": "chat" }, { - "id": "gemini-2.5-flash-preview-09-2025", - "name": "Gemini 2.5 Flash Preview 09 2025", - "display_name": "Gemini 2.5 Flash Preview 09 2025", + "id": "fnlp/MOSS-TTSD-v0.5", + "name": "fnlp/MOSS-TTSD-v0.5", + "display_name": "fnlp/MOSS-TTSD-v0.5", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ - "text" + "audio" ] }, - "limit": { - "context": 1048576, - "output": 65536 - }, - "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true, - "budget": { - "default": -1, - "min": 0, - "max": 24576 - } - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "budget", - "budget": { - "default": -1, - "min": 0, - "max": 24576, - "auto": -1, - "off": 0, - "unit": "tokens" - }, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thought_signatures" - ] - } - }, - "search": { - "supported": true, - "default": false - }, - "attachment": true, - "type": "chat" + "supported": false + } }, { - "id": "gemini-2.5-flash-image-preview", - "name": "Gemini 2.5 Flash Image Preview", - "display_name": "Gemini 2.5 Flash Image Preview", + "id": "FunAudioLLM/CosyVoice2-0.5B", + "name": "FunAudioLLM/CosyVoice2-0.5B", + "display_name": "FunAudioLLM/CosyVoice2-0.5B", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ - "text", - "image" + "audio" ] }, - "limit": { - "context": 32768, - "output": 32768 - }, - "temperature": true, "tool_call": false, "reasoning": { "supported": false - }, - "attachment": false, - "type": "imageGeneration" + } }, { - "id": "gemini-2.5-flash-lite-preview-09-2025", - "name": "Gemini 2.5 Flash Lite Preview 09 2025", - "display_name": "Gemini 2.5 Flash Lite Preview 09 2025", + "id": "FunAudioLLM/SenseVoiceSmall", + "name": "FunAudioLLM/SenseVoiceSmall", + "display_name": "FunAudioLLM/SenseVoiceSmall", "modalities": { "input": [ - "text", - "image" + "audio" ], "output": [ "text" ] }, - "limit": { - "context": 1048576, - "output": 65536 - }, - "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": false, - "budget": { - "default": -1, - "min": 512, - "max": 24576 - } - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "budget", - "budget": { - "default": -1, - "min": 512, - "max": 24576, - "auto": -1, - "unit": "tokens" - }, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thought_signatures" - ] - } - }, - "search": { - "supported": true, - "default": false + "supported": false + } + }, + { + "id": "IndexTeam/IndexTTS-2", + "name": "IndexTeam/IndexTTS-2", + "display_name": "IndexTeam/IndexTTS-2", + "modalities": { + "input": [ + "text" + ], + "output": [ + "audio" + ] }, - "attachment": true, - "type": "chat" + "tool_call": false, + "reasoning": { + "supported": false + } }, { - "id": "gemini-2.0-flash", - "name": "Gemini 2.0 Flash", - "display_name": "Gemini 2.0 Flash", + "id": "BAAI/bge-m3", + "name": "BAAI/bge-m3", + "display_name": "BAAI/bge-m3", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, + "context": 131072, "output": 8192 }, - "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": false - }, - "search": { - "supported": true, - "default": false + "supported": false }, - "attachment": true, - "type": "chat" + "type": "embedding" }, { - "id": "gemini-2.0-flash-preview-image-generation", - "name": "Gemini 2.0 Flash Preview Image Generation", - "display_name": "Gemini 2.0 Flash Preview Image Generation", + "id": "BAAI/bge-reranker-v2-m3", + "name": "BAAI/bge-reranker-v2-m3", + "display_name": "BAAI/bge-reranker-v2-m3", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ - "text", - "image" + "score" ] }, "limit": { - "context": 32000, - "output": 8192 + "context": 8192, + "output": 2048 }, - "temperature": true, "tool_call": false, "reasoning": { "supported": false }, - "search": { - "supported": false - }, - "attachment": true, - "type": "chat" + "type": "rerank" }, { - "id": "gemini-2.0-flash-lite", - "name": "Gemini 2.0 Flash Lite", - "display_name": "Gemini 2.0 Flash Lite", + "id": "netease-youdao/bce-embedding-base_v1", + "name": "netease-youdao/bce-embedding-base_v1", + "display_name": "netease-youdao/bce-embedding-base_v1", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ - "text" + "embedding" ] }, "limit": { - "context": 1048576, - "output": 8192 + "context": 8192, + "output": 2048 }, - "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { "supported": false }, - "search": { - "supported": false - }, - "attachment": true, - "type": "chat" - } - ] - }, - "impossibl": { - "id": "impossibl", - "name": "Impossibl", - "display_name": "Impossibl", - "api": "https://api.impossibl.com/v1", - "doc": "https://impossibl.com/docs/models", - "models": [ + "type": "embedding" + }, { - "id": "fireworks/gpt-oss-20b", - "name": "GPT OSS 20B", - "display_name": "GPT OSS 20B", + "id": "netease-youdao/bce-reranker-base_v1", + "name": "netease-youdao/bce-reranker-base_v1", + "display_name": "netease-youdao/bce-reranker-base_v1", "modalities": { "input": [ "text" ], "output": [ - "text" + "score" ] }, "limit": { - "context": 131072, - "output": 32768 + "context": 8192, + "output": 2048 }, - "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "effort_options": [ - "low", - "medium", - "high" - ] - } - }, - "attachment": false, - "open_weights": true, - "knowledge": "2024-06", - "release_date": "2025-08-05", - "last_updated": "2025-08-05", - "type": "chat" + "supported": false + } }, { - "id": "fireworks/gpt-oss-120b", - "name": "GPT OSS 120B", - "display_name": "GPT OSS 120B", + "id": "Qwen/Qwen2.5-Coder-32B-Instruct", + "name": "Qwen/Qwen2.5-Coder-32B-Instruct", + "display_name": "Qwen/Qwen2.5-Coder-32B-Instruct", "modalities": { "input": [ "text" @@ -198779,35 +199180,34 @@ }, "limit": { "context": 131072, - "output": 32768 + "output": 8192 }, - "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "effort_options": [ - "low", - "medium", - "high" - ] - } + "supported": false + } + }, + { + "id": "Kwai-Kolors/Kolors", + "name": "Kwai-Kolors/Kolors", + "display_name": "Kwai-Kolors/Kolors", + "modalities": { + "input": [ + "text" + ], + "output": [ + "image" + ] }, - "attachment": false, - "open_weights": true, - "knowledge": "2024-06", - "release_date": "2025-08-05", - "last_updated": "2025-08-05", - "type": "chat" + "tool_call": false, + "reasoning": { + "supported": false + } }, { - "id": "fireworks/glm-5.2", - "name": "GLM-5.2", - "display_name": "GLM-5.2", + "id": "Qwen/Qwen2-VL-72B-Instruct", + "name": "Qwen/Qwen2-VL-72B-Instruct", + "display_name": "Qwen/Qwen2-VL-72B-Instruct", "modalities": { "input": [ "text" @@ -198817,30 +199217,19 @@ ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 131072, + "output": 8192 }, - "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true - } + "supported": false }, - "attachment": false, - "open_weights": true, - "release_date": "2026-06-13", - "last_updated": "2026-06-13", "type": "chat" }, { - "id": "cerebras/gpt-oss-120b", - "name": "GPT OSS 120B", - "display_name": "GPT OSS 120B", + "id": "Qwen/Qwen2.5-72B-Instruct-128K", + "name": "Qwen/Qwen2.5-72B-Instruct-128K", + "display_name": "Qwen/Qwen2.5-72B-Instruct-128K", "modalities": { "input": [ "text" @@ -198851,35 +199240,18 @@ }, "limit": { "context": 131072, - "output": 32768 + "output": 8192 }, - "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "effort_options": [ - "low", - "medium", - "high" - ] - } + "supported": false }, - "attachment": false, - "open_weights": true, - "knowledge": "2024-06", - "release_date": "2025-08-05", - "last_updated": "2025-08-05", "type": "chat" }, { - "id": "zai/glm-5-turbo", - "name": "GLM-5-Turbo", - "display_name": "GLM-5-Turbo", + "id": "deepseek-ai/deepseek-vl2", + "name": "deepseek-ai/deepseek-vl2", + "display_name": "deepseek-ai/deepseek-vl2", "modalities": { "input": [ "text" @@ -198889,30 +199261,19 @@ ] }, "limit": { - "context": 200000, - "output": 131072 + "context": 131072, + "output": 8192 }, - "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true - } + "supported": false }, - "attachment": false, - "open_weights": false, - "release_date": "2026-03-16", - "last_updated": "2026-03-16", "type": "chat" }, { - "id": "zai/glm-4.7", - "name": "GLM-4.7", - "display_name": "GLM-4.7", + "id": "Qwen/Qwen2.5-32B-Instruct", + "name": "Qwen/Qwen2.5-32B-Instruct", + "display_name": "Qwen/Qwen2.5-32B-Instruct", "modalities": { "input": [ "text" @@ -198922,37 +199283,19 @@ ] }, "limit": { - "context": 204800, - "output": 131072 + "context": 131072, + "output": 8192 }, - "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } + "supported": false }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-12-22", - "last_updated": "2025-12-22", "type": "chat" }, { - "id": "zai/glm-4.6", - "name": "GLM-4.6", - "display_name": "GLM-4.6", + "id": "Qwen/Qwen2.5-14B-Instruct", + "name": "Qwen/Qwen2.5-14B-Instruct", + "display_name": "Qwen/Qwen2.5-14B-Instruct", "modalities": { "input": [ "text" @@ -198962,31 +199305,19 @@ ] }, "limit": { - "context": 204800, - "output": 131072 + "context": 131072, + "output": 8192 }, - "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true - } + "supported": false }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-09-30", - "last_updated": "2025-09-30", "type": "chat" }, { - "id": "zai/glm-4.5-air", - "name": "GLM-4.5-Air", - "display_name": "GLM-4.5-Air", + "id": "Qwen/Qwen2.5-Coder-7B-Instruct", + "name": "Qwen/Qwen2.5-Coder-7B-Instruct", + "display_name": "Qwen/Qwen2.5-Coder-7B-Instruct", "modalities": { "input": [ "text" @@ -198997,25 +199328,17 @@ }, "limit": { "context": 131072, - "output": 98304 + "output": 8192 }, - "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-07-28", - "last_updated": "2025-07-28", - "type": "chat" + "supported": false + } }, { - "id": "zai/glm-5.1", - "name": "GLM-5.1", - "display_name": "GLM-5.1", + "id": "internlm/internlm2_5-7b-chat", + "name": "internlm/internlm2_5-7b-chat", + "display_name": "internlm/internlm2_5-7b-chat", "modalities": { "input": [ "text" @@ -199025,30 +199348,19 @@ ] }, "limit": { - "context": 200000, - "output": 131072 + "context": 131072, + "output": 8192 }, - "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true - } + "supported": false }, - "attachment": false, - "open_weights": true, - "release_date": "2026-04-07", - "last_updated": "2026-04-07", "type": "chat" }, { - "id": "zai/glm-5", - "name": "GLM-5", - "display_name": "GLM-5", + "id": "Qwen/Qwen2-7B-Instruct", + "name": "Qwen/Qwen2-7B-Instruct", + "display_name": "Qwen/Qwen2-7B-Instruct", "modalities": { "input": [ "text" @@ -199058,36 +199370,19 @@ ] }, "limit": { - "context": 204800, - "output": 131072 + "context": 131072, + "output": 8192 }, - "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } + "supported": false }, - "attachment": false, - "open_weights": true, - "release_date": "2026-02-12", - "last_updated": "2026-02-12", "type": "chat" }, { - "id": "zai/glm-4.5", - "name": "GLM-4.5", - "display_name": "GLM-4.5", + "id": "THUDM/glm-4-9b-chat", + "name": "THUDM/glm-4-9b-chat", + "display_name": "THUDM/glm-4-9b-chat", "modalities": { "input": [ "text" @@ -199098,25 +199393,18 @@ }, "limit": { "context": 131072, - "output": 98304 + "output": 8192 }, - "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true + "supported": false }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-07-28", - "last_updated": "2025-07-28", "type": "chat" }, { - "id": "zai/glm-5.2", - "name": "GLM-5.2", - "display_name": "GLM-5.2", + "id": "BAAI/bge-large-en-v1.5", + "name": "BAAI/bge-large-en-v1.5", + "display_name": "BAAI/bge-large-en-v1.5", "modalities": { "input": [ "text" @@ -199126,730 +199414,476 @@ ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 131072, + "output": 8192 }, - "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true - } + "supported": false }, - "attachment": false, - "open_weights": true, - "release_date": "2026-06-13", - "last_updated": "2026-06-13", - "type": "chat" + "type": "embedding" }, { - "id": "xai/grok-4.20-0309-reasoning", - "name": "Grok 4.20 (Reasoning)", - "display_name": "Grok 4.20 (Reasoning)", + "id": "BAAI/bge-large-zh-v1.5", + "name": "BAAI/bge-large-zh-v1.5", + "display_name": "BAAI/bge-large-zh-v1.5", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 30000 + "context": 131072, + "output": 8192 }, - "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true + "supported": false }, - "attachment": true, - "open_weights": false, - "release_date": "2026-03-09", - "last_updated": "2026-03-09", - "type": "chat" + "type": "embedding" }, { - "id": "xai/grok-4.5", - "name": "Grok 4.5", - "display_name": "Grok 4.5", + "id": "LoRA/Qwen/Qwen2.5-32B-Instruct", + "name": "LoRA/Qwen/Qwen2.5-32B-Instruct", + "display_name": "LoRA/Qwen/Qwen2.5-32B-Instruct", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 500000, - "output": 500000 + "context": 131072, + "output": 8192 }, - "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "effort_options": [ - "low", - "medium", - "high" - ] - } + "supported": false }, - "attachment": true, - "open_weights": false, - "release_date": "2026-07-08", - "last_updated": "2026-07-08", "type": "chat" }, { - "id": "xai/grok-build-0.1", - "name": "Grok Build 0.1", - "display_name": "Grok Build 0.1", + "id": "LoRA/Qwen/Qwen2.5-14B-Instruct", + "name": "LoRA/Qwen/Qwen2.5-14B-Instruct", + "display_name": "LoRA/Qwen/Qwen2.5-14B-Instruct", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 256000, - "output": 256000 + "context": 131072, + "output": 8192 }, - "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true - } + "supported": false }, - "attachment": true, - "open_weights": false, - "release_date": "2026-04-16", - "last_updated": "2026-04-16", "type": "chat" }, { - "id": "xai/grok-4.3", - "name": "Grok 4.3", - "display_name": "Grok 4.3", + "id": "Pro/deepseek-ai/DeepSeek-R1-Distill-Qwen-7B", + "name": "Pro/deepseek-ai/DeepSeek-R1-Distill-Qwen-7B", + "display_name": "Pro/deepseek-ai/DeepSeek-R1-Distill-Qwen-7B", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 30000 + "context": 131072, + "output": 8192 }, - "temperature": true, "tool_call": true, "reasoning": { "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "effort_options": [ - "none", - "low", - "medium", - "high" - ] - } - }, - "attachment": true, - "open_weights": false, - "release_date": "2026-04-17", - "last_updated": "2026-04-17", "type": "chat" }, { - "id": "xai/grok-4.20-0309-non-reasoning", - "name": "Grok 4.20 (Non-Reasoning)", - "display_name": "Grok 4.20 (Non-Reasoning)", + "id": "Pro/Qwen/Qwen2.5-Coder-7B-Instruct", + "name": "Pro/Qwen/Qwen2.5-Coder-7B-Instruct", + "display_name": "Pro/Qwen/Qwen2.5-Coder-7B-Instruct", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 30000 + "context": 131072, + "output": 8192 }, - "temperature": true, "tool_call": true, "reasoning": { "supported": false - }, - "attachment": true, - "open_weights": false, - "release_date": "2026-03-09", - "last_updated": "2026-03-09", - "type": "chat" + } }, { - "id": "xiaomi/mimo-v2.5", - "name": "MiMo-V2.5", - "display_name": "MiMo-V2.5", + "id": "Pro/BAAI/bge-m3", + "name": "Pro/BAAI/bge-m3", + "display_name": "Pro/BAAI/bge-m3", "modalities": { "input": [ - "text", - "image", - "audio", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 131072, + "output": 8192 }, - "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true + "supported": false }, - "attachment": true, - "open_weights": true, - "knowledge": "2024-12", - "release_date": "2026-04-22", - "last_updated": "2026-04-22", - "type": "chat" + "type": "embedding" }, { - "id": "anthropic/claude-opus-4-5", - "name": "Claude Opus 4.5 (latest)", - "display_name": "Claude Opus 4.5 (latest)", + "id": "Pro/Qwen/Qwen2.5-7B-Instruct", + "name": "Pro/Qwen/Qwen2.5-7B-Instruct", + "display_name": "Pro/Qwen/Qwen2.5-7B-Instruct", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 64000 + "context": 131072, + "output": 8192 }, - "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": false - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "mixed", - "budget": { - "min": 1024, - "unit": "tokens" - }, - "effort": "high", - "effort_options": [ - "low", - "medium", - "high" - ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Claude Opus 4.5 uses manual thinking.type = \"enabled\" with budget_tokens; effort can be used alongside the thinking budget.", - "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header." - ] - } + "supported": false }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-05", - "release_date": "2025-11-24", - "last_updated": "2025-11-24", "type": "chat" }, { - "id": "anthropic/claude-opus-4-7", - "name": "Claude Opus 4.7", - "display_name": "Claude Opus 4.7", + "id": "Pro/BAAI/bge-reranker-v2-m3", + "name": "Pro/BAAI/bge-reranker-v2-m3", + "display_name": "Pro/BAAI/bge-reranker-v2-m3", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ - "text" + "score" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 8192, + "output": 2048 }, - "temperature": false, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": false - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "high", - "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "omitted", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Claude Opus 4.7 and newer Opus models require thinking.type = \"adaptive\" to enable thinking explicitly.", - "Manual budget_tokens requests return 400 on Claude Opus 4.7 and newer adaptive-only Opus models.", - "task_budget is separate from thinking control and should not be treated as a thinking budget." - ] - } + "supported": false }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-01-31", - "release_date": "2026-04-16", - "last_updated": "2026-04-16", - "type": "chat" + "type": "rerank" }, { - "id": "anthropic/claude-sonnet-5", - "name": "Claude Sonnet 5", - "display_name": "Claude Sonnet 5", + "id": "LoRA/Qwen/Qwen2.5-72B-Instruct", + "name": "LoRA/Qwen/Qwen2.5-72B-Instruct", + "display_name": "LoRA/Qwen/Qwen2.5-72B-Instruct", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 131072, + "output": 8192 }, - "temperature": false, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" - ] - } + "supported": false }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-01-31", - "release_date": "2026-06-30", - "last_updated": "2026-06-30", "type": "chat" }, { - "id": "anthropic/claude-sonnet-4-6", - "name": "Claude Sonnet 4.6", - "display_name": "Claude Sonnet 4.6", + "id": "Pro/Qwen/Qwen2-7B-Instruct", + "name": "Pro/Qwen/Qwen2-7B-Instruct", + "display_name": "Pro/Qwen/Qwen2-7B-Instruct", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 64000 + "context": 131072, + "output": 8192 }, - "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": false - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "mixed", - "budget": { - "min": 1024, - "unit": "tokens" - }, - "effort": "high", - "effort_options": [ - "low", - "medium", - "high", - "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Anthropic recommends adaptive thinking with effort for Claude 4.6; budget_tokens remains a deprecated compatibility path.", - "Anthropic API defaults effort to high; lower effort levels should be chosen per workload." - ] - } + "supported": false }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-02-17", - "last_updated": "2026-03-13", "type": "chat" }, { - "id": "anthropic/claude-opus-4-6", - "name": "Claude Opus 4.6", - "display_name": "Claude Opus 4.6", + "id": "LoRA/Qwen/Qwen2.5-7B-Instruct", + "name": "LoRA/Qwen/Qwen2.5-7B-Instruct", + "display_name": "LoRA/Qwen/Qwen2.5-7B-Instruct", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 131072, + "output": 8192 }, - "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": false - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "mixed", - "budget": { - "min": 1024, - "unit": "tokens" - }, - "effort": "high", - "effort_options": [ - "low", - "medium", - "high", - "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Anthropic recommends adaptive thinking with effort for Claude 4.6; budget_tokens remains a deprecated compatibility path.", - "Anthropic API defaults effort to high; lower effort levels should be chosen per workload." - ] - } + "supported": false }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-05-31", - "release_date": "2026-02-05", - "last_updated": "2026-03-13", "type": "chat" }, { - "id": "anthropic/claude-haiku-4-5", - "name": "Claude Haiku 4.5 (latest)", - "display_name": "Claude Haiku 4.5 (latest)", + "id": "Pro/THUDM/glm-4-9b-chat", + "name": "Pro/THUDM/glm-4-9b-chat", + "display_name": "Pro/THUDM/glm-4-9b-chat", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 64000 + "context": 131072, + "output": 8192 }, - "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": false - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "budget", - "budget": { - "min": 1024, - "unit": "tokens" - }, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", - "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." - ] - } + "supported": false }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-02-28", - "release_date": "2025-10-15", - "last_updated": "2025-10-15", "type": "chat" }, { - "id": "anthropic/claude-opus-4-8", - "name": "Claude Opus 4.8", - "display_name": "Claude Opus 4.8", + "id": "THUDM/GLM-Z1-Rumination-32B-0414", + "name": "THUDM/GLM-Z1-Rumination-32B-0414", + "display_name": "THUDM/GLM-Z1-Rumination-32B-0414", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 131072, + "output": 8192 }, - "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": false - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "high", - "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "omitted", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Claude Opus 4.7 and newer Opus models require thinking.type = \"adaptive\" to enable thinking explicitly.", - "Manual budget_tokens requests return 400 on Claude Opus 4.7 and newer adaptive-only Opus models.", - "task_budget is separate from thinking control and should not be treated as a thinking budget." - ] - } + "default": true }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-01", - "release_date": "2026-05-28", - "last_updated": "2026-05-28", "type": "chat" }, { - "id": "anthropic/claude-sonnet-4-5", - "name": "Claude Sonnet 4.5 (latest)", - "display_name": "Claude Sonnet 4.5 (latest)", + "id": "stabilityai/stable-diffusion-xl-base-1.0", + "name": "stabilityai/stable-diffusion-xl-base-1.0", + "display_name": "stabilityai/stable-diffusion-xl-base-1.0", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ + "image" + ] + }, + "tool_call": false, + "reasoning": { + "supported": false + } + }, + { + "id": "black-forest-labs/FLUX.1-schnell", + "name": "black-forest-labs/FLUX.1-schnell", + "display_name": "black-forest-labs/FLUX.1-schnell", + "modalities": { + "input": [ "text" + ], + "output": [ + "image" ] }, - "limit": { - "context": 200000, - "output": 64000 + "tool_call": false, + "reasoning": { + "supported": false + } + }, + { + "id": "black-forest-labs/FLUX.1-dev", + "name": "black-forest-labs/FLUX.1-dev", + "display_name": "black-forest-labs/FLUX.1-dev", + "modalities": { + "input": [ + "text" + ], + "output": [ + "image" + ] }, - "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": false + "supported": false + } + }, + { + "id": "Pro/black-forest-labs/FLUX.1-schnell", + "name": "Pro/black-forest-labs/FLUX.1-schnell", + "display_name": "Pro/black-forest-labs/FLUX.1-schnell", + "modalities": { + "input": [ + "text" + ], + "output": [ + "image" + ] }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "budget", - "budget": { - "min": 1024, - "unit": "tokens" - }, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", - "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." - ] - } + "tool_call": false, + "reasoning": { + "supported": false + } + }, + { + "id": "stabilityai/stable-diffusion-3-5-large", + "name": "stabilityai/stable-diffusion-3-5-large", + "display_name": "stabilityai/stable-diffusion-3-5-large", + "modalities": { + "input": [ + "text" + ], + "output": [ + "image" + ] }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-07-31", - "release_date": "2025-09-29", - "last_updated": "2025-09-29", - "type": "chat" + "tool_call": false, + "reasoning": { + "supported": false + } }, { - "id": "anthropic/claude-fable-5", - "name": "Claude Fable 5", - "display_name": "Claude Fable 5", + "id": "fishaudio/fish-speech-1.4", + "name": "fishaudio/fish-speech-1.4", + "display_name": "fishaudio/fish-speech-1.4", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ + "audio" + ] + }, + "tool_call": false, + "reasoning": { + "supported": false + } + }, + { + "id": "RVC-Boss/GPT-SoVITS", + "name": "RVC-Boss/GPT-SoVITS", + "display_name": "RVC-Boss/GPT-SoVITS", + "modalities": { + "input": [ "text" + ], + "output": [ + "audio" ] }, - "limit": { - "context": 1000000, - "output": 128000 + "tool_call": false, + "reasoning": { + "supported": false + } + }, + { + "id": "fishaudio/fish-speech-1.5", + "name": "fishaudio/fish-speech-1.5", + "display_name": "fishaudio/fish-speech-1.5", + "modalities": { + "input": [ + "text" + ], + "output": [ + "audio" + ] }, - "temperature": false, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true + "supported": false + } + }, + { + "id": "black-forest-labs/FLUX.1-pro", + "name": "black-forest-labs/FLUX.1-pro", + "display_name": "black-forest-labs/FLUX.1-pro", + "modalities": { + "input": [ + "text" + ], + "output": [ + "image" + ] }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "high", - "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "omitted", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Adaptive thinking is always on for Claude Fable 5 and Claude Mythos 5; thinking.type = \"disabled\" is rejected.", - "Manual budget_tokens requests return 400 on Claude Fable 5 and Claude Mythos 5.", - "thinking.display defaults to omitted; set display to summarized to receive readable thinking summaries." - ] - } + "tool_call": false, + "reasoning": { + "supported": false + } + }, + { + "id": "LoRA/black-forest-labs/FLUX.1-dev", + "name": "LoRA/black-forest-labs/FLUX.1-dev", + "display_name": "LoRA/black-forest-labs/FLUX.1-dev", + "modalities": { + "input": [ + "text" + ], + "output": [ + "image" + ] }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-01-31", - "release_date": "2026-06-09", - "last_updated": "2026-06-09", - "type": "chat" + "tool_call": false, + "reasoning": { + "supported": false + } }, { - "id": "groq/gpt-oss-20b", - "name": "GPT OSS 20B", - "display_name": "GPT OSS 20B", + "id": "SeedLLM/Seed-Rice-7B", + "name": "SeedLLM/Seed-Rice-7B", + "display_name": "SeedLLM/Seed-Rice-7B", "modalities": { "input": [ "text" @@ -199860,35 +199894,27 @@ }, "limit": { "context": 131072, - "output": 32768 + "output": 8192 }, - "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "effort_options": [ - "low", - "medium", - "high" - ] - } + "supported": false }, - "attachment": false, - "open_weights": true, - "knowledge": "2024-06", - "release_date": "2025-08-05", - "last_updated": "2025-08-05", "type": "chat" - }, + } + ] + }, + "infomaniak": { + "id": "infomaniak", + "name": "Infomaniak", + "display_name": "Infomaniak", + "api": "https://api.infomaniak.com/2/ai/${INFOMANIAK_PRODUCT_ID}/openai/v1", + "doc": "https://www.infomaniak.com/en/hosting/ai-services/open-source-models", + "models": [ { - "id": "groq/gpt-oss-120b", - "name": "GPT OSS 120B", - "display_name": "GPT OSS 120B", + "id": "bge_multilingual_gemma2", + "name": "BGE Multilingual Gemma2", + "display_name": "BGE Multilingual Gemma2", "modalities": { "input": [ "text" @@ -199898,36 +199924,24 @@ ] }, "limit": { - "context": 131072, - "output": 32768 + "context": 8000, + "output": 3584 }, - "temperature": true, - "tool_call": true, + "temperature": false, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "effort_options": [ - "low", - "medium", - "high" - ] - } + "supported": false }, "attachment": false, "open_weights": true, - "knowledge": "2024-06", - "release_date": "2025-08-05", - "last_updated": "2025-08-05", - "type": "chat" + "release_date": "2024-07-25", + "last_updated": "2026-08-01", + "type": "embedding" }, { - "id": "deepseek/deepseek-v4-flash", - "name": "DeepSeek V4 Flash", - "display_name": "DeepSeek V4 Flash", + "id": "mini_lm_l12_v2", + "name": "All-MiniLM-L12-v2", + "display_name": "All-MiniLM-L12-v2", "modalities": { "input": [ "text" @@ -199937,113 +199951,64 @@ ] }, "limit": { - "context": 1000000, - "output": 384000 + "context": 128, + "output": 384 }, - "temperature": true, - "tool_call": true, + "temperature": false, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "high", - "effort_options": [ - "low", - "high", - "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." - ] - } + "supported": false }, "attachment": false, "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-04-24", - "last_updated": "2026-04-24", - "type": "chat" + "release_date": "2021-08-30", + "last_updated": "2021-08-30", + "type": "embedding" }, { - "id": "deepseek/deepseek-v4-pro", - "name": "DeepSeek V4 Pro", - "display_name": "DeepSeek V4 Pro", + "id": "swiss-ai/Apertus-v1.5-70B", + "name": "Apertus v1.5 70B", + "display_name": "Apertus v1.5 70B", "modalities": { "input": [ - "text" + "text", + "image", + "audio" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 384000 + "context": 100000, + "output": 8192 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "high", - "effort_options": [ - "low", - "high", - "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." - ] - } + "supported": false }, - "attachment": false, + "attachment": true, "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-04-24", - "last_updated": "2026-04-24", + "release_date": "2026-07-24", + "last_updated": "2026-08-01", "type": "chat" }, { - "id": "google/gemini-2.5-pro", - "name": "Gemini 2.5 Pro", - "display_name": "Gemini 2.5 Pro", + "id": "Qwen/Qwen3.5-122B-A10B-FP8", + "name": "Qwen3.5 122B-A10B FP8", + "display_name": "Qwen3.5 122B-A10B FP8", "modalities": { "input": [ "text", - "image", - "audio", - "video", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1048576, + "context": 200000, "output": 65536 }, "temperature": true, @@ -200055,47 +200020,35 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "budget", - "budget": { - "default": -1, - "min": 128, - "max": 32768, - "auto": -1, - "unit": "tokens" - }, + "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ - "thought_signatures" + "thinking_blocks" ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2025-01", - "release_date": "2025-06-17", - "last_updated": "2025-06-17", + "open_weights": true, + "release_date": "2026-02-23", + "last_updated": "2026-02-23", "type": "chat" }, { - "id": "google/gemini-3.5-flash-lite", - "name": "Gemini 3.5 Flash Lite", - "display_name": "Gemini 3.5 Flash Lite", + "id": "Qwen/Qwen3.5-397B-A17B-FP8", + "name": "Qwen3.5 397B-A17B FP8", + "display_name": "Qwen3.5 397B-A17B FP8", "modalities": { "input": [ "text", - "image", - "video", - "audio", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1048576, + "context": 200000, "output": 65536 }, "temperature": true, @@ -200107,40 +200060,35 @@ "extra_capabilities": { "reasoning": { "supported": true, - "effort_options": [ - "minimal", - "low", - "medium", - "high" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2026-03", - "release_date": "2026-07-21", - "last_updated": "2026-07-21", + "open_weights": true, + "release_date": "2026-02-15", + "last_updated": "2026-02-15", "type": "chat" }, { - "id": "google/gemini-2.5-flash", - "name": "Gemini 2.5 Flash", - "display_name": "Gemini 2.5 Flash", + "id": "nvidia/NVIDIA-Nemotron-3-Nano-30B-A3B-FP8", + "name": "Nemotron 3 Nano 30B A3B FP8", + "display_name": "Nemotron 3 Nano 30B A3B FP8", "modalities": { "input": [ - "text", - "image", - "audio", - "video", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 1000000, + "output": 262144 }, "temperature": true, "tool_call": true, @@ -200151,49 +200099,32 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "budget", - "budget": { - "default": -1, - "min": 0, - "max": 24576, - "auto": -1, - "off": 0, - "unit": "tokens" - }, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thought_signatures" - ] + "interleaved": true } }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-01", - "release_date": "2025-06-17", - "last_updated": "2025-06-17", + "attachment": false, + "open_weights": true, + "knowledge": "2025-06-25", + "release_date": "2025-12-15", + "last_updated": "2025-12-15", "type": "chat" }, { - "id": "google/gemini-3.1-flash-lite", - "name": "Gemini 3.1 Flash Lite", - "display_name": "Gemini 3.1 Flash Lite", + "id": "moonshotai/Kimi-K2.6", + "name": "Kimi K2.6", + "display_name": "Kimi K2.6", "modalities": { "input": [ "text", - "image", - "video", - "audio", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 256000, + "output": 256000 }, "temperature": true, "tool_call": true, @@ -200204,40 +200135,31 @@ "extra_capabilities": { "reasoning": { "supported": true, - "effort_options": [ - "minimal", - "low", - "medium", - "high" - ] + "interleaved": true } }, "attachment": true, - "open_weights": false, + "open_weights": true, "knowledge": "2025-01", - "release_date": "2026-05-07", - "last_updated": "2026-05-07", + "release_date": "2026-04-20", + "last_updated": "2026-04-20", "type": "chat" }, { - "id": "google/gemini-3.1-pro-preview", - "name": "Gemini 3.1 Pro Preview", - "display_name": "Gemini 3.1 Pro Preview", + "id": "google/gemma-4-31B-it", + "name": "Gemma 4 31B IT", + "display_name": "Gemma 4 31B IT", "modalities": { "input": [ - "text", - "image", - "video", - "audio", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 100000, + "output": 32768 }, "temperature": true, "tool_call": true, @@ -200247,99 +200169,60 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "level", - "level": "high", - "level_options": [ - "low", - "high" - ], - "summaries": true, - "visibility": "summary", - "continuation": [ - "thought_signatures" - ] + "supported": true } }, - "attachment": true, - "open_weights": false, + "attachment": false, + "open_weights": true, "knowledge": "2025-01", - "release_date": "2026-02-19", - "last_updated": "2026-02-19", + "release_date": "2026-04-02", + "last_updated": "2026-04-02", "type": "chat" }, { - "id": "google/gemini-2.5-flash-lite", - "name": "Gemini 2.5 Flash-Lite", - "display_name": "Gemini 2.5 Flash-Lite", + "id": "mistralai/Ministral-3-14B-Instruct-2512", + "name": "Ministral 3 14B Instruct", + "display_name": "Ministral 3 14B Instruct", "modalities": { "input": [ "text", - "image", - "audio", - "video", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 100000, + "output": 25600 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": false - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "budget", - "budget": { - "default": -1, - "min": 512, - "max": 24576, - "auto": -1, - "unit": "tokens" - }, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thought_signatures" - ] - } + "supported": false }, "attachment": true, - "open_weights": false, - "knowledge": "2025-01", - "release_date": "2025-06-17", - "last_updated": "2025-06-17", + "open_weights": true, + "release_date": "2025-12-02", + "last_updated": "2025-12-02", "type": "chat" }, { - "id": "google/gemini-3.6-flash", - "name": "Gemini 3.6 Flash", - "display_name": "Gemini 3.6 Flash", + "id": "mistralai/Mistral-Small-4-119B-2603", + "name": "Mistral Small 4", + "display_name": "Mistral Small 4", "modalities": { - "input": [ - "text", - "image", - "video", - "audio", - "pdf" + "input": [ + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 256000, + "output": 256000 }, "temperature": true, "tool_call": true, @@ -200350,33 +200233,28 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "level", - "level": "high", - "level_options": [ - "minimal", - "low", - "medium", - "high" - ], - "summaries": true, - "visibility": "summary", - "continuation": [ - "thought_signatures" - ] + "interleaved": true } }, "attachment": true, - "open_weights": false, - "knowledge": "2026-03", - "release_date": "2026-07-21", - "last_updated": "2026-07-21", + "open_weights": true, + "knowledge": "2025-06", + "release_date": "2026-03-16", + "last_updated": "2026-03-16", "type": "chat" - }, + } + ] + }, + "sap-ai-core": { + "id": "sap-ai-core", + "name": "SAP AI Core", + "display_name": "SAP AI Core", + "doc": "https://help.sap.com/docs/sap-ai-core", + "models": [ { - "id": "google/gemini-3.5-flash", - "name": "Gemini 3.5 Flash", - "display_name": "Gemini 3.5 Flash", + "id": "gemini-3.5-flash", + "name": "gemini-3.5-flash", + "display_name": "gemini-3.5-flash", "modalities": { "input": [ "text", @@ -200426,61 +200304,49 @@ "type": "chat" }, { - "id": "thinkingmachines/inkling", - "name": "Inkling", - "display_name": "Inkling", + "id": "amazon--nova-micro", + "name": "amazon--nova-micro", + "display_name": "amazon--nova-micro", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 65536, - "output": 65536 + "context": 128000, + "output": 8192 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "none", - "minimal", - "low", - "medium", - "high", - "xhigh" - ] - } + "supported": false }, - "attachment": true, - "open_weights": true, - "release_date": "2026-07-15", - "last_updated": "2026-07-15", + "attachment": false, + "open_weights": false, + "knowledge": "2024-10", + "release_date": "2024-12-03", + "last_updated": "2024-12-03", "type": "chat" }, { - "id": "openai/gpt-5", - "name": "GPT-5", - "display_name": "GPT-5", + "id": "anthropic--claude-4.8-opus", + "name": "anthropic--claude-4.8-opus", + "display_name": "anthropic--claude-4.8-opus", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 400000, + "context": 1000000, "output": 128000 }, "temperature": false, @@ -200491,66 +200357,26 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", "effort_options": [ - "minimal", - "low", - "medium", - "high" - ], - "verbosity": "medium", - "verbosity_options": [ "low", "medium", - "high" - ], - "visibility": "hidden" + "high", + "xhigh", + "max" + ] } }, "attachment": true, "open_weights": false, - "knowledge": "2024-09-30", - "release_date": "2025-08-07", - "last_updated": "2025-08-07", - "type": "chat" - }, - { - "id": "openai/gpt-4o-mini", - "name": "GPT-4o mini", - "display_name": "GPT-4o mini", - "modalities": { - "input": [ - "text", - "image", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 16384 - }, - "temperature": true, - "tool_call": true, - "reasoning": { - "supported": false - }, - "attachment": true, - "open_weights": false, - "knowledge": "2023-09", - "release_date": "2024-07-18", - "last_updated": "2024-07-18", + "knowledge": "2026-01", + "release_date": "2026-05-28", + "last_updated": "2026-05-28", "type": "chat" }, { - "id": "openai/gpt-5.5", - "name": "GPT-5.5", - "display_name": "GPT-5.5", + "id": "gpt-5.4", + "name": "gpt-5.4", + "display_name": "gpt-5.4", "modalities": { "input": [ "text", @@ -200565,19 +200391,20 @@ "context": 1050000, "output": 128000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, + "default_enabled": false, "mode": "effort", - "effort": "medium", + "effort": "none", "effort_options": [ + "none", "low", "medium", "high", @@ -200594,44 +200421,15 @@ }, "attachment": true, "open_weights": false, - "knowledge": "2025-12-01", - "release_date": "2026-04-23", - "last_updated": "2026-04-23", - "type": "chat" - }, - { - "id": "openai/gpt-4.1-nano", - "name": "GPT-4.1 nano", - "display_name": "GPT-4.1 nano", - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 1047576, - "output": 32768 - }, - "temperature": true, - "tool_call": true, - "reasoning": { - "supported": false - }, - "attachment": true, - "open_weights": false, - "knowledge": "2024-04", - "release_date": "2025-04-14", - "last_updated": "2025-04-14", + "knowledge": "2025-08-31", + "release_date": "2026-03-05", + "last_updated": "2026-03-05", "type": "chat" }, { - "id": "openai/o1", - "name": "o1", - "display_name": "o1", + "id": "gpt-5.6-terra", + "name": "gpt-5.6-terra", + "display_name": "gpt-5.6-terra", "modalities": { "input": [ "text", @@ -200643,8 +200441,8 @@ ] }, "limit": { - "context": 200000, - "output": 100000 + "context": 1050000, + "output": 128000 }, "temperature": false, "tool_call": true, @@ -200659,6 +200457,15 @@ "mode": "effort", "effort": "medium", "effort_options": [ + "none", + "low", + "medium", + "high", + "xhigh", + "max" + ], + "verbosity": "medium", + "verbosity_options": [ "low", "medium", "high" @@ -200668,15 +200475,15 @@ }, "attachment": true, "open_weights": false, - "knowledge": "2023-09", - "release_date": "2024-12-05", - "last_updated": "2024-12-05", + "knowledge": "2026-02-16", + "release_date": "2026-07-09", + "last_updated": "2026-07-09", "type": "chat" }, { - "id": "openai/gpt-5.4", - "name": "GPT-5.4", - "display_name": "GPT-5.4", + "id": "gpt-6-astra", + "name": "GPT-6 Astra", + "display_name": "GPT-6 Astra", "modalities": { "input": [ "text", @@ -200691,79 +200498,38 @@ "context": 1050000, "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "none", "effort_options": [ - "none", "low", "medium", "high", - "xhigh" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + "xhigh", + "max" + ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-03-05", - "last_updated": "2026-03-05", + "knowledge": "2026-04-30", + "release_date": "2026-09-04", + "last_updated": "2026-09-04", "type": "chat" }, { - "id": "openai/gpt-4-turbo", - "name": "GPT-4 Turbo", - "display_name": "GPT-4 Turbo", + "id": "sonar", + "name": "sonar", + "display_name": "sonar", "modalities": { "input": [ - "text", - "image" - ], - "output": [ "text" - ] - }, - "limit": { - "context": 128000, - "output": 4096 - }, - "temperature": true, - "tool_call": true, - "reasoning": { - "supported": false - }, - "attachment": true, - "open_weights": false, - "knowledge": "2023-12", - "release_date": "2023-11-06", - "last_updated": "2024-04-09", - "type": "chat" - }, - { - "id": "openai/gpt-4o", - "name": "GPT-4o", - "display_name": "GPT-4o", - "modalities": { - "input": [ - "text", - "image", - "pdf" ], "output": [ "text" @@ -200771,24 +200537,24 @@ }, "limit": { "context": 128000, - "output": 16384 + "output": 4096 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { "supported": false }, - "attachment": true, + "attachment": false, "open_weights": false, - "knowledge": "2023-09", - "release_date": "2024-05-13", - "last_updated": "2024-08-06", + "knowledge": "2025-09-01", + "release_date": "2024-01-01", + "last_updated": "2025-09-01", "type": "chat" }, { - "id": "openai/gpt-5-pro", - "name": "GPT-5 Pro", - "display_name": "GPT-5 Pro", + "id": "gpt-5.2", + "name": "gpt-5.2", + "display_name": "gpt-5.2", "modalities": { "input": [ "text", @@ -200800,20 +200566,27 @@ }, "limit": { "context": 400000, - "output": 272000 + "output": 128000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "fixed", - "effort": "high", + "default_enabled": false, + "mode": "effort", + "effort": "none", + "effort_options": [ + "none", + "low", + "medium", + "high", + "xhigh" + ], "verbosity": "medium", "verbosity_options": [ "low", @@ -200825,15 +200598,15 @@ }, "attachment": true, "open_weights": false, - "knowledge": "2024-09-30", - "release_date": "2025-10-06", - "last_updated": "2025-10-06", + "knowledge": "2025-08-31", + "release_date": "2025-12-11", + "last_updated": "2025-12-11", "type": "chat" }, { - "id": "openai/o3", - "name": "o3", - "display_name": "o3", + "id": "anthropic--claude-4.5-opus", + "name": "anthropic--claude-4.5-opus", + "display_name": "anthropic--claude-4.5-opus", "modalities": { "input": [ "text", @@ -200846,9 +200619,9 @@ }, "limit": { "context": 200000, - "output": 100000 + "output": 64000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -200856,41 +200629,37 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", "effort_options": [ "low", "medium", "high" - ], - "visibility": "hidden" + ] } }, "attachment": true, "open_weights": false, - "knowledge": "2024-05", - "release_date": "2025-04-16", - "last_updated": "2025-04-16", + "knowledge": "2025-05", + "release_date": "2025-11-24", + "last_updated": "2025-11-24", "type": "chat" }, { - "id": "openai/o4-mini", - "name": "o4-mini", - "display_name": "o4-mini", + "id": "gpt-6.1-sol", + "name": "GPT-6.1 Sol", + "display_name": "GPT-6.1 Sol", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 100000 + "context": 1050000, + "output": 128000 }, "temperature": false, "tool_call": true, @@ -200900,29 +200669,26 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", "effort_options": [ "low", "medium", - "high" - ], - "visibility": "hidden" + "high", + "xhigh", + "max" + ] } }, "attachment": true, "open_weights": false, - "knowledge": "2024-05", - "release_date": "2025-04-16", - "last_updated": "2025-04-16", + "knowledge": "2026-04-30", + "release_date": "2026-09-29", + "last_updated": "2026-09-29", "type": "chat" }, { - "id": "openai/gpt-5.6-sol", - "name": "GPT-5.6 Sol", - "display_name": "GPT-5.6 Sol", + "id": "anthropic--claude-4.7-opus", + "name": "anthropic--claude-4.7-opus", + "display_name": "anthropic--claude-4.7-opus", "modalities": { "input": [ "text", @@ -200934,7 +200700,7 @@ ] }, "limit": { - "context": 1050000, + "context": 1000000, "output": 128000 }, "temperature": false, @@ -200945,38 +200711,53 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", "effort_options": [ - "none", "low", "medium", "high", "xhigh", "max" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-02-16", - "release_date": "2026-07-09", - "last_updated": "2026-07-09", + "knowledge": "2026-01-31", + "release_date": "2026-04-16", + "last_updated": "2026-04-16", "type": "chat" }, { - "id": "openai/gpt-5.6-terra", - "name": "GPT-5.6 Terra", - "display_name": "GPT-5.6 Terra", + "id": "nvidia--llama-nemotron-embed-1b", + "name": "Llama Nemotron Embed 1B v2", + "display_name": "Llama Nemotron Embed 1B v2", + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 8192, + "output": 2048 + }, + "temperature": false, + "tool_call": false, + "reasoning": { + "supported": false + }, + "attachment": false, + "open_weights": true, + "release_date": "2025-10-16", + "last_updated": "2025-10-16", + "type": "embedding" + }, + { + "id": "anthropic--claude-4.6-sonnet", + "name": "anthropic--claude-4.6-sonnet", + "display_name": "anthropic--claude-4.6-sonnet", "modalities": { "input": [ "text", @@ -200988,10 +200769,10 @@ ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 1000000, + "output": 64000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -200999,154 +200780,98 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", "effort_options": [ - "none", "low", "medium", "high", - "xhigh", "max" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-02-16", - "release_date": "2026-07-09", - "last_updated": "2026-07-09", + "knowledge": "2025-08-31", + "release_date": "2026-02-17", + "last_updated": "2026-03-13", "type": "chat" }, { - "id": "openai/gpt-5.1-codex", - "name": "GPT-5.1 Codex", - "display_name": "GPT-5.1 Codex", + "id": "anthropic--claude-4.5-haiku", + "name": "anthropic--claude-4.5-haiku", + "display_name": "anthropic--claude-4.5-haiku", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 200000, + "output": 64000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "none", - "effort_options": [ - "none", - "low", - "medium", - "high" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" - } + "default": true }, "attachment": true, "open_weights": false, - "knowledge": "2024-09-30", - "release_date": "2025-11-13", - "last_updated": "2025-11-13", + "knowledge": "2025-02-28", + "release_date": "2025-10-15", + "last_updated": "2025-10-15", "type": "chat" }, { - "id": "openai/gpt-5-mini", - "name": "GPT-5 Mini", - "display_name": "GPT-5 Mini", + "id": "text-embedding-3-large", + "name": "text-embedding-3-large", + "display_name": "text-embedding-3-large", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 8191, + "output": 3072 }, "temperature": false, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", - "effort_options": [ - "minimal", - "low", - "medium", - "high" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" - } + "supported": false }, - "attachment": true, + "attachment": false, "open_weights": false, - "knowledge": "2024-05-30", - "release_date": "2025-08-07", - "last_updated": "2025-08-07", - "type": "chat" + "release_date": "2024-01-25", + "last_updated": "2024-01-25", + "type": "embedding" }, { - "id": "openai/gpt-5-codex", - "name": "GPT-5-Codex", - "display_name": "GPT-5-Codex", + "id": "amazon--nova-lite", + "name": "amazon--nova-lite", + "display_name": "amazon--nova-lite", "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 1000000, + "output": 64000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -201154,86 +200879,63 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", "effort_options": [ - "minimal", - "low", - "medium", - "high" - ], - "verbosity": "medium", - "verbosity_options": [ "low", "medium", "high" - ], - "visibility": "hidden" + ] } }, "attachment": true, "open_weights": false, - "knowledge": "2024-09-30", - "release_date": "2025-09-15", - "last_updated": "2025-09-15", + "knowledge": "2025-10", + "release_date": "2025-12-02", + "last_updated": "2025-12-01", "type": "chat" }, { - "id": "openai/gpt-5.4-pro", - "name": "GPT-5.4 Pro", - "display_name": "GPT-5.4 Pro", + "id": "sonar-deep-research", + "name": "sonar-deep-research", + "display_name": "sonar-deep-research", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 128000, + "output": 32768 }, "temperature": false, - "tool_call": true, + "tool_call": false, "reasoning": { "supported": true, "default": true }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "high", "effort_options": [ - "medium", - "high", - "xhigh" - ], - "verbosity": "medium", - "verbosity_options": [ + "minimal", "low", "medium", "high" - ], - "visibility": "hidden" + ] } }, - "attachment": true, + "attachment": false, "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-03-05", - "last_updated": "2026-03-05", + "knowledge": "2025-01", + "release_date": "2025-02-01", + "last_updated": "2025-09-01", "type": "chat" }, { - "id": "openai/gpt-5.1-codex-mini", - "name": "GPT-5.1 Codex mini", - "display_name": "GPT-5.1 Codex mini", + "id": "gpt-4.1-nano", + "name": "gpt-4.1-nano", + "display_name": "gpt-4.1-nano", "modalities": { "input": [ "text", @@ -201244,47 +200946,25 @@ ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 1047576, + "output": 32768 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": false - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "none", - "effort_options": [ - "none", - "low", - "medium", - "high" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" - } + "supported": false }, "attachment": true, "open_weights": false, - "knowledge": "2024-09-30", - "release_date": "2025-11-13", - "last_updated": "2025-11-13", + "knowledge": "2024-04", + "release_date": "2025-04-14", + "last_updated": "2025-04-14", "type": "chat" }, { - "id": "openai/o3-mini", - "name": "o3-mini", - "display_name": "o3-mini", + "id": "nvidia--llama-3.2-nv-embedqa-1b", + "name": "nvidia--llama-3.2-nv-embedqa-1b", + "display_name": "nvidia--llama-3.2-nv-embedqa-1b", "modalities": { "input": [ "text" @@ -201294,40 +200974,24 @@ ] }, "limit": { - "context": 200000, - "output": 100000 + "context": 8192, + "output": 4096 }, "temperature": false, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", - "effort_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" - } + "supported": false }, "attachment": false, "open_weights": false, - "knowledge": "2024-05", - "release_date": "2024-12-20", - "last_updated": "2025-01-29", + "release_date": "2024-09-25", + "last_updated": "2024-09-25", "type": "chat" }, { - "id": "openai/gpt-5.1", - "name": "GPT-5.1", - "display_name": "GPT-5.1", + "id": "gpt-5-nano", + "name": "gpt-5-nano", + "display_name": "gpt-5-nano", "modalities": { "input": [ "text", @@ -201341,20 +201005,20 @@ "context": 400000, "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, + "default_enabled": true, "mode": "effort", - "effort": "none", + "effort": "medium", "effort_options": [ - "none", + "minimal", "low", "medium", "high" @@ -201370,30 +201034,29 @@ }, "attachment": true, "open_weights": false, - "knowledge": "2024-09-30", - "release_date": "2025-11-13", - "last_updated": "2025-11-13", + "knowledge": "2024-05-30", + "release_date": "2025-08-07", + "last_updated": "2025-08-07", "type": "chat" }, { - "id": "openai/gpt-5.6-luna", - "name": "GPT-5.6 Luna", - "display_name": "GPT-5.6 Luna", + "id": "mistralai--mistral-medium", + "name": "Mistral Medium 3.5", + "display_name": "Mistral Medium 3.5", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 262144, + "output": 262144 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -201401,94 +201064,80 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", "effort_options": [ "none", - "low", - "medium", - "high", - "xhigh", - "max" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", "high" - ], - "visibility": "hidden" + ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2026-02-16", - "release_date": "2026-07-09", - "last_updated": "2026-07-09", + "open_weights": true, + "release_date": "2026-04-29", + "last_updated": "2026-04-29", "type": "chat" }, { - "id": "openai/gpt-5.4-mini", - "name": "GPT-5.4 mini", - "display_name": "GPT-5.4 mini", + "id": "gemini-3.6-flash", + "name": "Gemini 3.6 Flash", + "display_name": "Gemini 3.6 Flash", "modalities": { "input": [ "text", - "image" + "image", + "video", + "audio", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 1048576, + "output": 65536 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "none", - "effort_options": [ - "none", - "low", - "medium", - "high", - "xhigh" - ], - "verbosity": "medium", - "verbosity_options": [ + "default_enabled": true, + "mode": "level", + "level": "high", + "level_options": [ + "minimal", "low", "medium", "high" ], - "visibility": "hidden" + "summaries": true, + "visibility": "summary", + "continuation": [ + "thought_signatures" + ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-03-17", - "last_updated": "2026-03-17", + "knowledge": "2026-03", + "release_date": "2026-07-21", + "last_updated": "2026-07-21", "type": "chat" }, { - "id": "openai/gpt-5.3-codex", - "name": "GPT-5.3 Codex", - "display_name": "GPT-5.3 Codex", + "id": "gemini-3.8-flash", + "name": "Gemini 3.8 Flash", + "display_name": "Gemini 3.8 Flash", "modalities": { "input": [ "text", "image", + "video", + "audio", "pdf" ], "output": [ @@ -201496,8 +201145,8 @@ ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 1048576, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -201509,34 +201158,31 @@ "reasoning": { "supported": true, "default_enabled": true, - "mode": "effort", - "effort": "medium", - "effort_options": [ - "low", - "medium", - "high", - "xhigh" - ], - "verbosity": "medium", - "verbosity_options": [ + "mode": "level", + "level": "high", + "level_options": [ + "minimal", "low", "medium", "high" ], - "visibility": "hidden" + "summaries": true, + "visibility": "summary", + "continuation": [ + "thought_signatures" + ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-02-05", - "last_updated": "2026-02-05", + "release_date": "2026-09-02", + "last_updated": "2026-09-02", "type": "chat" }, { - "id": "openai/gpt-4.1-mini", - "name": "GPT-4.1 mini", - "display_name": "GPT-4.1 mini", + "id": "gpt-5.6-luna", + "name": "gpt-5.6-luna", + "display_name": "gpt-5.6-luna", "modalities": { "input": [ "text", @@ -201548,56 +201194,28 @@ ] }, "limit": { - "context": 1047576, - "output": 32768 - }, - "temperature": true, - "tool_call": true, - "reasoning": { - "supported": false - }, - "attachment": true, - "open_weights": false, - "knowledge": "2024-04", - "release_date": "2025-04-14", - "last_updated": "2025-04-14", - "type": "chat" - }, - { - "id": "openai/gpt-5.4-nano", - "name": "GPT-5.4 nano", - "display_name": "GPT-5.4 nano", - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 400000, + "context": 1050000, "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, + "default_enabled": true, "mode": "effort", - "effort": "none", + "effort": "medium", "effort_options": [ "none", "low", "medium", "high", - "xhigh" + "xhigh", + "max" ], "verbosity": "medium", "verbosity_options": [ @@ -201610,86 +201228,43 @@ }, "attachment": true, "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-03-17", - "last_updated": "2026-03-17", + "knowledge": "2026-02-16", + "release_date": "2026-07-09", + "last_updated": "2026-07-09", "type": "chat" }, { - "id": "openai/gpt-5.5-pro", - "name": "GPT-5.5 Pro", - "display_name": "GPT-5.5 Pro", + "id": "gemini-embedding", + "name": "Gemini Embedding 001", + "display_name": "Gemini Embedding 001", "modalities": { "input": [ - "text", - "image", - "pdf" - ], - "output": [ "text" - ] - }, - "limit": { - "context": 1050000, - "output": 128000 - }, - "temperature": false, - "tool_call": true, - "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "effort_options": [ - "medium", - "high", - "xhigh" - ] - } - }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-12-01", - "release_date": "2026-04-23", - "last_updated": "2026-04-23", - "type": "chat" - }, - { - "id": "openai/gpt-4.1", - "name": "GPT-4.1", - "display_name": "GPT-4.1", - "modalities": { - "input": [ - "text", - "image", - "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1047576, - "output": 32768 + "context": 2048, + "output": 1 }, - "temperature": true, - "tool_call": true, + "temperature": false, + "tool_call": false, "reasoning": { "supported": false }, - "attachment": true, + "attachment": false, "open_weights": false, - "knowledge": "2024-04", - "release_date": "2025-04-14", - "last_updated": "2025-04-14", - "type": "chat" + "knowledge": "2025-05", + "release_date": "2025-05-20", + "last_updated": "2025-05-20", + "type": "embedding" }, { - "id": "openai/gpt-3.5-turbo", - "name": "GPT-3.5-turbo", - "display_name": "GPT-3.5-turbo", + "id": "amazon--titan-embed-text", + "name": "amazon--titan-embed-text", + "display_name": "amazon--titan-embed-text", "modalities": { "input": [ "text" @@ -201699,30 +201274,28 @@ ] }, "limit": { - "context": 16385, - "output": 4096 + "context": 8192, + "output": 1536 }, - "temperature": true, + "temperature": false, "tool_call": false, "reasoning": { "supported": false }, "attachment": false, "open_weights": false, - "knowledge": "2021-09-01", - "release_date": "2023-03-01", - "last_updated": "2023-11-06", + "release_date": "2024-04-30", + "last_updated": "2024-04-30", "type": "chat" }, { - "id": "openai/gpt-5.2-codex", - "name": "GPT-5.2 Codex", - "display_name": "GPT-5.2 Codex", + "id": "gpt-5", + "name": "gpt-5", + "display_name": "gpt-5", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" @@ -201745,10 +201318,10 @@ "mode": "effort", "effort": "medium", "effort_options": [ + "minimal", "low", "medium", - "high", - "xhigh" + "high" ], "verbosity": "medium", "verbosity_options": [ @@ -201761,67 +201334,67 @@ }, "attachment": true, "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2025-12-11", - "last_updated": "2025-12-11", + "knowledge": "2024-09-30", + "release_date": "2025-08-07", + "last_updated": "2025-08-07", "type": "chat" }, { - "id": "openai/gpt-5.2", - "name": "GPT-5.2", - "display_name": "GPT-5.2", + "id": "gemini-2.5-pro", + "name": "gemini-2.5-pro", + "display_name": "gemini-2.5-pro", "modalities": { "input": [ "text", - "image" + "image", + "audio", + "video", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 1048576, + "output": 65536 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "none", - "effort_options": [ - "none", - "low", - "medium", - "high", - "xhigh" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + "default_enabled": true, + "mode": "budget", + "budget": { + "default": -1, + "min": 128, + "max": 32768, + "auto": -1, + "unit": "tokens" + }, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thought_signatures" + ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2025-12-11", - "last_updated": "2025-12-11", + "knowledge": "2025-01", + "release_date": "2025-06-17", + "last_updated": "2025-06-17", "type": "chat" }, { - "id": "openai/gpt-5-nano", - "name": "GPT-5 Nano", - "display_name": "GPT-5 Nano", + "id": "gpt-5-mini", + "name": "gpt-5-mini", + "display_name": "gpt-5-mini", "modalities": { "input": [ "text", @@ -201870,44 +201443,36 @@ "type": "chat" }, { - "id": "moonshotai/kimi-k3", - "name": "Kimi K3", - "display_name": "Kimi K3", + "id": "text-embedding-3-small", + "name": "text-embedding-3-small", + "display_name": "text-embedding-3-small", "modalities": { "input": [ - "text", - "image", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 8191, + "output": 1536 }, "temperature": false, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true - } + "supported": false }, - "attachment": true, - "open_weights": true, - "release_date": "2026-07-16", - "last_updated": "2026-07-16", - "type": "chat" + "attachment": false, + "open_weights": false, + "release_date": "2024-01-25", + "last_updated": "2024-01-25", + "type": "embedding" }, { - "id": "qwen/qwen3.7-max", - "name": "Qwen3.7 Max", - "display_name": "Qwen3.7 Max", + "id": "sap-abap-1", + "name": "sap-abap-1", + "display_name": "sap-abap-1", "modalities": { "input": [ "text" @@ -201917,30 +201482,24 @@ ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 32768, + "output": 4096 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true - } + "supported": false }, "attachment": false, "open_weights": false, - "release_date": "2026-05-21", - "last_updated": "2026-05-21", + "release_date": "2025-11-26", + "last_updated": "2025-11-26", "type": "chat" }, { - "id": "qwen/qwen3.6-flash", - "name": "Qwen3.6 Flash", - "display_name": "Qwen3.6 Flash", + "id": "amazon--nova-pro", + "name": "amazon--nova-pro", + "display_name": "amazon--nova-pro", "modalities": { "input": [ "text", @@ -201952,51 +201511,40 @@ ] }, "limit": { - "context": 1000000, - "output": 65536 + "context": 300000, + "output": 8192 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } + "supported": false }, "attachment": true, "open_weights": false, - "release_date": "2026-04-27", - "last_updated": "2026-04-27", + "knowledge": "2024-10", + "release_date": "2024-12-03", + "last_updated": "2024-12-03", "type": "chat" }, { - "id": "qwen/qwen3.8-max-preview", - "name": "Qwen3.8 Max Preview", - "display_name": "Qwen3.8 Max Preview", + "id": "gpt-5.6-sol", + "name": "gpt-5.6-sol", + "display_name": "gpt-5.6-sol", "modalities": { "input": [ "text", "image", - "video" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 1050000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -202004,79 +201552,105 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", "effort_options": [ + "none", "low", "medium", - "xhigh" - ] + "high", + "xhigh", + "max" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, "attachment": true, "open_weights": false, - "release_date": "2026-07-19", - "last_updated": "2026-07-19", + "knowledge": "2026-02-16", + "release_date": "2026-07-09", + "last_updated": "2026-07-09", "type": "chat" }, { - "id": "qwen/qwen3.7-plus", - "name": "Qwen3.7 Plus", - "display_name": "Qwen3.7 Plus", + "id": "gemini-2.5-flash-lite", + "name": "gemini-2.5-flash-lite", + "display_name": "gemini-2.5-flash-lite", "modalities": { "input": [ "text", "image", - "video" + "audio", + "video", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 64000 + "context": 1048576, + "output": 65536 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { - "supported": true + "supported": true, + "default_enabled": false, + "mode": "budget", + "budget": { + "default": -1, + "min": 512, + "max": 24576, + "auto": -1, + "unit": "tokens" + }, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thought_signatures" + ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-04", - "release_date": "2026-06-02", - "last_updated": "2026-06-02", + "knowledge": "2025-01", + "release_date": "2025-06-17", + "last_updated": "2025-06-17", "type": "chat" - } - ] - }, - "ofox": { - "id": "ofox", - "name": "Ofox", - "display_name": "Ofox", - "api": "https://api.ofox.ai/v1", - "doc": "https://ofox.ai/docs", - "models": [ + }, { - "id": "minimax/minimax-m2.1", - "name": "MiniMax-M2.1", - "display_name": "MiniMax-M2.1", + "id": "gemini-2.5-flash", + "name": "gemini-2.5-flash", + "display_name": "gemini-2.5-flash", "modalities": { "input": [ - "text" + "text", + "image", + "audio", + "video", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 204800, - "output": 131072 + "context": 1048576, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -202086,19 +201660,35 @@ }, "extra_capabilities": { "reasoning": { - "supported": true + "supported": true, + "default_enabled": true, + "mode": "budget", + "budget": { + "default": -1, + "min": 0, + "max": 24576, + "auto": -1, + "off": 0, + "unit": "tokens" + }, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thought_signatures" + ] } }, - "attachment": false, - "open_weights": true, - "release_date": "2025-12-23", - "last_updated": "2025-12-23", + "attachment": true, + "open_weights": false, + "knowledge": "2025-01", + "release_date": "2025-06-17", + "last_updated": "2025-06-17", "type": "chat" }, { - "id": "minimax/minimax-m2.1-lightning", - "name": "MiniMax-M2.1 Lightning", - "display_name": "MiniMax-M2.1 Lightning", + "id": "cohere--command-a-reasoning", + "name": "cohere--command-a-reasoning", + "display_name": "cohere--command-a-reasoning", "modalities": { "input": [ "text" @@ -202108,8 +201698,8 @@ ] }, "limit": { - "context": 204800, - "output": 131072 + "context": 256000, + "output": 32000 }, "temperature": true, "tool_call": true, @@ -202117,31 +201707,43 @@ "supported": true, "default": true }, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "none", + "minimal", + "low", + "medium", + "high" + ] + } + }, "attachment": false, "open_weights": true, - "release_date": "2025-12-23", - "last_updated": "2025-12-23", + "knowledge": "2024-06-01", + "release_date": "2025-08-21", + "last_updated": "2025-08-21", "type": "chat" }, { - "id": "minimax/minimax-m3", - "name": "MiniMax-M3", - "display_name": "MiniMax-M3", + "id": "gpt-5.5", + "name": "gpt-5.5", + "display_name": "gpt-5.5", "modalities": { "input": [ "text", "image", - "video" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 512000 + "context": 1050000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -202149,86 +201751,49 @@ }, "extra_capabilities": { "reasoning": { - "supported": true + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", + "effort_options": [ + "low", + "medium", + "high", + "xhigh" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, "attachment": true, - "open_weights": true, - "release_date": "2026-06-01", - "last_updated": "2026-06-01", - "type": "chat" - }, - { - "id": "minimax/m2-her", - "name": "MiniMax-M2 Her", - "display_name": "MiniMax-M2 Her", - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 65536, - "output": 2048 - }, - "temperature": true, - "tool_call": true, - "reasoning": { - "supported": true, - "default": true - }, - "attachment": false, "open_weights": false, - "release_date": "2026-01-23", - "last_updated": "2026-01-23", - "type": "chat" - }, - { - "id": "minimax/minimax-m2", - "name": "MiniMax-M2", - "display_name": "MiniMax-M2", - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 204800, - "output": 131072 - }, - "temperature": true, - "tool_call": true, - "reasoning": { - "supported": true, - "default": true - }, - "attachment": false, - "open_weights": true, - "release_date": "2025-10-27", - "last_updated": "2025-10-27", + "knowledge": "2025-12-01", + "release_date": "2026-04-23", + "last_updated": "2026-04-23", "type": "chat" }, { - "id": "minimax/minimax-m2.5", - "name": "MiniMax-M2.5", - "display_name": "MiniMax-M2.5", + "id": "anthropic--claude-4.6-opus", + "name": "anthropic--claude-4.6-opus", + "display_name": "anthropic--claude-4.6-opus", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 204800, - "output": 131072 + "context": 1000000, + "output": 128000 }, "temperature": true, "tool_call": true, @@ -202238,69 +201803,70 @@ }, "extra_capabilities": { "reasoning": { - "supported": true + "effort_options": [ + "low", + "medium", + "high", + "max" + ] } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-02-12", - "last_updated": "2026-02-12", + "attachment": true, + "open_weights": false, + "knowledge": "2025-05-31", + "release_date": "2026-02-05", + "last_updated": "2026-03-13", "type": "chat" }, { - "id": "minimax/minimax-m2.7", - "name": "MiniMax-M2.7", - "display_name": "MiniMax-M2.7", + "id": "gemini-embedding-2", + "name": "Gemini Embedding 2", + "display_name": "Gemini Embedding 2", "modalities": { "input": [ - "text" + "text", + "image", + "audio", + "video", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 204800, - "output": 131072 + "context": 8192, + "output": 3072 }, - "temperature": true, - "tool_call": true, + "temperature": false, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } + "supported": false }, - "attachment": false, - "open_weights": true, - "release_date": "2026-03-18", - "last_updated": "2026-03-18", - "type": "chat" + "attachment": true, + "open_weights": false, + "knowledge": "2025-11", + "release_date": "2026-04-22", + "last_updated": "2026-04-22", + "type": "embedding" }, { - "id": "minimax/minimax-m2.5-lightning", - "name": "MiniMax-M2.5 Lightning", - "display_name": "MiniMax-M2.5 Lightning", + "id": "anthropic--claude-4-sonnet", + "name": "anthropic--claude-4-sonnet", + "display_name": "anthropic--claude-4-sonnet", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 204800, - "output": 131072 + "context": 200000, + "output": 64000 }, "temperature": true, "tool_call": true, @@ -202308,94 +201874,91 @@ "supported": true, "default": true }, - "attachment": false, - "open_weights": true, - "release_date": "2026-02-13", - "last_updated": "2026-02-13", + "attachment": true, + "open_weights": false, + "knowledge": "2025-03-31", + "release_date": "2025-05-22", + "last_updated": "2025-05-22", "type": "chat" }, { - "id": "minimax/minimax-m2.7-highspeed", - "name": "MiniMax-M2.7-highspeed", - "display_name": "MiniMax-M2.7-highspeed", + "id": "sonar-pro", + "name": "sonar-pro", + "display_name": "sonar-pro", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 204800, - "output": 131072 + "context": 200000, + "output": 8192 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } + "supported": false }, - "attachment": false, - "open_weights": true, - "release_date": "2026-03-18", - "last_updated": "2026-03-18", + "attachment": true, + "open_weights": false, + "knowledge": "2025-09-01", + "release_date": "2024-01-01", + "last_updated": "2025-09-01", "type": "chat" }, { - "id": "x-ai/grok-4.1-fast", - "name": "Grok 4.1 Fast", - "display_name": "Grok 4.1 Fast", + "id": "anthropic--claude-4-opus", + "name": "anthropic--claude-4-opus", + "display_name": "anthropic--claude-4-opus", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 2000000, - "output": 30000 + "context": 200000, + "output": 32000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, "attachment": true, "open_weights": false, - "release_date": "2025-11-19", - "last_updated": "2025-11-19", + "knowledge": "2025-03-31", + "release_date": "2025-05-22", + "last_updated": "2025-05-22", "type": "chat" }, { - "id": "x-ai/grok-4.5", - "name": "Grok 4.5", - "display_name": "Grok 4.5", + "id": "gemini-3.1-flash-lite", + "name": "gemini-3.1-flash-lite", + "display_name": "gemini-3.1-flash-lite", "modalities": { "input": [ "text", - "image" + "image", + "video", + "audio", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 500000, + "context": 1048576, "output": 65536 }, "temperature": true, @@ -202408,6 +201971,7 @@ "reasoning": { "supported": true, "effort_options": [ + "minimal", "low", "medium", "high" @@ -202416,14 +201980,15 @@ }, "attachment": true, "open_weights": false, - "release_date": "2026-07-08", - "last_updated": "2026-07-08", + "knowledge": "2025-01", + "release_date": "2026-05-07", + "last_updated": "2026-05-07", "type": "chat" }, { - "id": "x-ai/grok-4.20", - "name": "Grok 4.20 (Reasoning)", - "display_name": "Grok 4.20 (Reasoning)", + "id": "gpt-4.1-mini", + "name": "gpt-4.1-mini", + "display_name": "gpt-4.1-mini", "modalities": { "input": [ "text", @@ -202435,25 +202000,25 @@ ] }, "limit": { - "context": 2000000, - "output": 128000 + "context": 1047576, + "output": 32768 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true + "supported": false }, "attachment": true, "open_weights": false, - "release_date": "2026-03-09", - "last_updated": "2026-03-09", + "knowledge": "2024-04", + "release_date": "2025-04-14", + "last_updated": "2025-04-14", "type": "chat" }, { - "id": "x-ai/grok-4.7", - "name": "Grok 4.7", - "display_name": "Grok 4.7", + "id": "gpt-4.1", + "name": "gpt-4.1", + "display_name": "gpt-4.1", "modalities": { "input": [ "text", @@ -202465,91 +202030,69 @@ ] }, "limit": { - "context": 500000, - "output": 65536 + "context": 1047576, + "output": 32768 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "low", - "medium", - "high", - "xhigh" - ] - } + "supported": false }, "attachment": true, "open_weights": false, - "knowledge": "2026-05", - "release_date": "2026-09-21", - "last_updated": "2026-09-21", + "knowledge": "2024-04", + "release_date": "2025-04-14", + "last_updated": "2025-04-14", "type": "chat" }, { - "id": "x-ai/grok-4.3", - "name": "Grok 4.3", - "display_name": "Grok 4.3", + "id": "mistralai--mistral-medium-instruct", + "name": "mistralai--mistral-medium-instruct", + "display_name": "mistralai--mistral-medium-instruct", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 30000 + "context": 128000, + "output": 128000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "effort_options": [ - "none", - "low", - "medium", - "high" - ] - } + "supported": false }, "attachment": true, "open_weights": false, - "release_date": "2026-04-17", - "last_updated": "2026-04-17", + "knowledge": "2025-05", + "release_date": "2025-05-07", + "last_updated": "2025-05-07", "type": "chat" }, { - "id": "x-ai/grok-4.6", - "name": "Grok 4.6", - "display_name": "Grok 4.6", + "id": "gpt-6-luna", + "name": "GPT-6 Luna", + "display_name": "GPT-6 Luna", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 500000, - "output": 65536 + "context": 1050000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -202557,41 +202100,41 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, "effort_options": [ + "none", "low", "medium", "high", - "xhigh" + "xhigh", + "max" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-02-01", - "release_date": "2026-08-12", - "last_updated": "2026-08-12", + "knowledge": "2026-05-18", + "release_date": "2026-09-22", + "last_updated": "2026-09-22", "type": "chat" }, { - "id": "anthropic/claude-fable-5.1", - "name": "Claude Fable 5.1", - "display_name": "Claude Fable 5.1", + "id": "mistralai--mistral-small", + "name": "mistralai--mistral-small", + "display_name": "mistralai--mistral-small", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, + "context": 128000, "output": 128000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -202599,41 +202142,23 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "high", "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "omitted", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Adaptive thinking is always on for Claude Fable 5 and Claude Mythos 5; thinking.type = \"disabled\" is rejected.", - "Manual budget_tokens requests return 400 on Claude Fable 5 and Claude Mythos 5.", - "thinking.display defaults to omitted; set display to summarized to receive readable thinking summaries." + "none", + "high" ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2026-06", - "release_date": "2026-09-01", - "last_updated": "2026-09-01", + "open_weights": true, + "knowledge": "2025-06", + "release_date": "2026-03-16", + "last_updated": "2026-03-16", "type": "chat" }, { - "id": "anthropic/claude-opus-5", - "name": "Claude Opus 5", - "display_name": "Claude Opus 5", + "id": "anthropic--claude-3-haiku", + "name": "anthropic--claude-3-haiku", + "display_name": "anthropic--claude-3-haiku", "modalities": { "input": [ "text", @@ -202645,38 +202170,25 @@ ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 200000, + "output": 4096 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" - ] - } + "supported": false }, "attachment": true, "open_weights": false, - "knowledge": "2026-05", - "release_date": "2026-07-24", - "last_updated": "2026-07-24", + "knowledge": "2023-08-31", + "release_date": "2024-03-13", + "last_updated": "2024-03-13", "type": "chat" }, { - "id": "anthropic/claude-opus-4.8", - "name": "Claude Opus 4.8", - "display_name": "Claude Opus 4.8", + "id": "anthropic--claude-4.5-sonnet", + "name": "anthropic--claude-4.5-sonnet", + "display_name": "anthropic--claude-4.5-sonnet", "modalities": { "input": [ "text", @@ -202688,56 +202200,32 @@ ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 200000, + "output": 64000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "high", - "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "omitted", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Claude Opus 4.7 and newer Opus models require thinking.type = \"adaptive\" to enable thinking explicitly.", - "Manual budget_tokens requests return 400 on Claude Opus 4.7 and newer adaptive-only Opus models.", - "task_budget is separate from thinking control and should not be treated as a thinking budget." - ] - } + "default": true }, "attachment": true, "open_weights": false, - "knowledge": "2026-01", - "release_date": "2026-05-28", - "last_updated": "2026-05-28", + "knowledge": "2025-07-31", + "release_date": "2025-09-29", + "last_updated": "2025-09-29", "type": "chat" }, { - "id": "anthropic/claude-sonnet-5", - "name": "Claude Sonnet 5", - "display_name": "Claude Sonnet 5", + "id": "gemini-3.5-flash-lite", + "name": "Gemini 3.5 Flash Lite", + "display_name": "Gemini 3.5 Flash Lite", "modalities": { "input": [ "text", "image", + "video", + "audio", "pdf" ], "output": [ @@ -202745,10 +202233,10 @@ ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 1048576, + "output": 65536 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -202758,367 +202246,255 @@ "reasoning": { "supported": true, "effort_options": [ + "minimal", "low", "medium", - "high", - "xhigh", - "max" + "high" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-01-31", - "release_date": "2026-06-30", - "last_updated": "2026-06-30", + "knowledge": "2026-03", + "release_date": "2026-07-21", + "last_updated": "2026-07-21", "type": "chat" - }, + } + ] + }, + "ainetcafe": { + "id": "ainetcafe", + "name": "ainetcafe", + "display_name": "ainetcafe", + "api": "https://microquickjs.com/v1", + "doc": "https://ainetcafe.com/k3/guides/", + "models": [ { - "id": "anthropic/claude-opus-4.6", - "name": "Claude Opus 4.6", - "display_name": "Claude Opus 4.6", + "id": "Kimi-K3", + "name": "Kimi K3", + "display_name": "Kimi K3", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 262144, + "output": 32768 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "mixed", - "budget": { - "min": 1024, - "unit": "tokens" - }, - "effort": "high", - "effort_options": [ - "low", - "medium", - "high", - "max" - ], "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ "thinking_blocks" ], - "notes": [ - "Anthropic recommends adaptive thinking with effort for Claude 4.6; budget_tokens remains a deprecated compatibility path.", - "Anthropic API defaults effort to high; lower effort levels should be chosen per workload." + "effort_options": [ + "low", + "high", + "max" ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2025-05-31", - "release_date": "2026-02-05", - "last_updated": "2026-03-13", + "open_weights": true, + "release_date": "2026-07-16", + "last_updated": "2026-07-16", "type": "chat" - }, + } + ] + }, + "qiniu-ai": { + "id": "qiniu-ai", + "name": "Qiniu", + "display_name": "Qiniu", + "api": "https://api.qnaigc.com/v1", + "doc": "https://developer.qiniu.com/aitokenapi", + "models": [ { - "id": "anthropic/claude-opus-4.5", - "name": "Claude Opus 4.5 (latest)", - "display_name": "Claude Opus 4.5 (latest)", + "id": "qwen2.5-vl-72b-instruct", + "name": "Qwen 2.5 VL 72B Instruct", + "display_name": "Qwen 2.5 VL 72B Instruct", "modalities": { "input": [ "text", "image", - "pdf" + "video" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 64000 + "context": 128000, + "output": 8192 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": false - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "mixed", - "budget": { - "min": 1024, - "unit": "tokens" - }, - "effort": "high", - "effort_options": [ - "low", - "medium", - "high" - ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Claude Opus 4.5 uses manual thinking.type = \"enabled\" with budget_tokens; effort can be used alongside the thinking budget.", - "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header." - ] - } + "supported": false }, "attachment": true, - "open_weights": false, - "knowledge": "2025-05", - "release_date": "2025-11-24", - "last_updated": "2025-11-24", + "open_weights": true, + "knowledge": "2024-04", + "release_date": "2025-01-26", + "last_updated": "2025-01-26", "type": "chat" }, { - "id": "anthropic/claude-sonnet-4.6", - "name": "Claude Sonnet 4.6", - "display_name": "Claude Sonnet 4.6", + "id": "claude-3.5-sonnet", + "name": "Claude 3.5 Sonnet", + "display_name": "Claude 3.5 Sonnet", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 200000, + "output": 8192 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": false - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "mixed", - "budget": { - "min": 1024, - "unit": "tokens" - }, - "effort": "high", - "effort_options": [ - "low", - "medium", - "high", - "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Anthropic recommends adaptive thinking with effort for Claude 4.6; budget_tokens remains a deprecated compatibility path.", - "Anthropic API defaults effort to high; lower effort levels should be chosen per workload." - ] - } + "supported": false }, "attachment": true, "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-02-17", - "last_updated": "2026-03-13", + "knowledge": "2024-04-30", + "release_date": "2024-10-22", + "last_updated": "2024-10-22", "type": "chat" }, { - "id": "anthropic/claude-haiku-4.5", - "name": "Claude Haiku 4.5 (latest)", - "display_name": "Claude Haiku 4.5 (latest)", + "id": "kling-v2-6", + "name": "Kling-V2 6", + "display_name": "Kling-V2 6", "modalities": { "input": [ "text", "image", - "pdf" + "video" ], "output": [ - "text" + "video" ] }, "limit": { - "context": 200000, - "output": 64000 + "context": 99999999, + "output": 99999999 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": false - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "budget", - "budget": { - "min": 1024, - "unit": "tokens" - }, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", - "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." - ] - } + "supported": false }, "attachment": true, "open_weights": false, - "knowledge": "2025-02-28", - "release_date": "2025-10-15", - "last_updated": "2025-10-15", - "type": "chat" + "release_date": "2026-01-13", + "last_updated": "2026-01-13" }, { - "id": "anthropic/claude-opus-4.7", - "name": "Claude Opus 4.7", - "display_name": "Claude Opus 4.7", + "id": "qwen3-max", + "name": "Qwen3 Max", + "display_name": "Qwen3 Max", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 262144, + "output": 65536 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": false + "supported": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "high", - "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" - ], "interleaved": true, "summaries": true, - "visibility": "omitted", + "visibility": "summary", "continuation": [ "thinking_blocks" - ], - "notes": [ - "Claude Opus 4.7 and newer Opus models require thinking.type = \"adaptive\" to enable thinking explicitly.", - "Manual budget_tokens requests return 400 on Claude Opus 4.7 and newer adaptive-only Opus models.", - "task_budget is separate from thinking control and should not be treated as a thinking budget." ] } }, - "attachment": true, + "attachment": false, "open_weights": false, - "knowledge": "2026-01-31", - "release_date": "2026-04-16", - "last_updated": "2026-04-16", + "knowledge": "2025-04", + "release_date": "2025-09-23", + "last_updated": "2025-09-23", "type": "chat" }, { - "id": "anthropic/claude-sonnet-5.5", - "name": "Claude Sonnet 5.5", - "display_name": "Claude Sonnet 5.5", + "id": "deepseek-v3", + "name": "DeepSeek-V3", + "display_name": "DeepSeek-V3", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 128000, + "output": 16000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" - ] - } + "supported": false }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-06", - "release_date": "2026-09-28", - "last_updated": "2026-09-28", + "attachment": false, + "open_weights": true, + "release_date": "2024-12-26", + "last_updated": "2024-12-26", "type": "chat" }, { - "id": "anthropic/claude-fable-5", - "name": "Claude Fable 5", - "display_name": "Claude Fable 5", + "id": "qwen3-vl-30b-a3b-thinking", + "name": "Qwen3-Vl 30b A3b Thinking", + "display_name": "Qwen3-Vl 30b A3b Thinking", "modalities": { "input": [ "text", "image", - "pdf" + "video" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 128000, + "output": 32000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -203127,176 +202503,112 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "high", - "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" - ], "interleaved": true, "summaries": true, - "visibility": "omitted", + "visibility": "summary", "continuation": [ "thinking_blocks" - ], - "notes": [ - "Adaptive thinking is always on for Claude Fable 5 and Claude Mythos 5; thinking.type = \"disabled\" is rejected.", - "Manual budget_tokens requests return 400 on Claude Fable 5 and Claude Mythos 5.", - "thinking.display defaults to omitted; set display to summarized to receive readable thinking summaries." ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-01-31", - "release_date": "2026-06-09", - "last_updated": "2026-06-09", + "release_date": "2026-02-09", + "last_updated": "2026-02-09", "type": "chat" }, { - "id": "anthropic/claude-sonnet-4.5", - "name": "Claude Sonnet 4.5 (latest)", - "display_name": "Claude Sonnet 4.5 (latest)", + "id": "doubao-seed-2.0-lite", + "name": "Doubao Seed 2.0 Lite", + "display_name": "Doubao Seed 2.0 Lite", "modalities": { "input": [ "text", "image", - "pdf" + "video" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 64000 + "context": 256000, + "output": 32000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "budget", - "budget": { - "min": 1024, - "unit": "tokens" - }, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", - "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." - ] - } + "default": true }, "attachment": true, "open_weights": false, - "knowledge": "2025-07-31", - "release_date": "2025-09-29", - "last_updated": "2025-09-29", + "release_date": "2026-02-14", + "last_updated": "2026-02-14", "type": "chat" }, { - "id": "anthropic/claude-haiku-5.5", - "name": "Claude Haiku 5.5", - "display_name": "Claude Haiku 5.5", + "id": "qwen3-next-80b-a3b-instruct", + "name": "Qwen3 Next 80B A3B Instruct", + "display_name": "Qwen3 Next 80B A3B Instruct", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 131072, + "output": 32768 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" - ] - } + "supported": false }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-06", - "release_date": "2026-10-07", - "last_updated": "2026-10-07", + "attachment": false, + "open_weights": true, + "knowledge": "2025-04", + "release_date": "2025-09", + "last_updated": "2025-09", "type": "chat" }, { - "id": "anthropic/claude-opus-5.5", - "name": "Claude Opus 5.5", - "display_name": "Claude Opus 5.5", + "id": "doubao-seed-1.6-thinking", + "name": "Doubao-Seed 1.6 Thinking", + "display_name": "Doubao-Seed 1.6 Thinking", "modalities": { "input": [ - "text", "image", - "pdf" + "text", + "video" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 256000, + "output": 32000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" - ] - } - }, "attachment": true, "open_weights": false, - "knowledge": "2026-06", - "release_date": "2026-09-22", - "last_updated": "2026-09-22", + "release_date": "2025-08-15", + "last_updated": "2025-08-15", "type": "chat" }, { - "id": "z-ai/glm-4.7-flashx", - "name": "GLM-4.7-FlashX", - "display_name": "GLM-4.7-FlashX", + "id": "qwen3-max-preview", + "name": "Qwen3 Max Preview", + "display_name": "Qwen3 Max Preview", "modalities": { "input": [ "text" @@ -203306,40 +202618,49 @@ ] }, "limit": { - "context": 200000, - "output": 128000 + "context": 256000, + "output": 64000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true + "supported": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } }, "attachment": false, - "open_weights": true, + "open_weights": false, "knowledge": "2025-04", - "release_date": "2026-01-19", - "last_updated": "2026-01-19", + "release_date": "2025-09-05", + "last_updated": "2025-09-23", "type": "chat" }, { - "id": "z-ai/glm-5.3-flash", - "name": "GLM-5.3-Flash", - "display_name": "GLM-5.3-Flash", + "id": "doubao-seed-2.0-mini", + "name": "Doubao Seed 2.0 Mini", + "display_name": "Doubao Seed 2.0 Mini", "modalities": { "input": [ "text", "image", - "video", - "pdf" + "video" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 256000, + "output": 32000 }, "temperature": true, "tool_call": true, @@ -203347,40 +202668,27 @@ "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "effort_options": [ - "low", - "high", - "max" - ] - } - }, "attachment": true, - "open_weights": true, - "release_date": "2026-08-26", - "last_updated": "2026-08-26", + "open_weights": false, + "release_date": "2026-02-14", + "last_updated": "2026-02-14", "type": "chat" }, { - "id": "z-ai/glm-5v-turbo", - "name": "GLM-5V-Turbo", - "display_name": "GLM-5V-Turbo", + "id": "deepseek-r1-0528", + "name": "DeepSeek-R1-0528", + "display_name": "DeepSeek-R1-0528", "modalities": { "input": [ - "text", - "image", - "video", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 131072 + "context": 128000, + "output": 32000 }, "temperature": true, "tool_call": true, @@ -203388,19 +202696,31 @@ "supported": true, "default": true }, - "attachment": true, - "open_weights": false, - "release_date": "2026-04-01", - "last_updated": "2026-04-01", + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } + }, + "attachment": false, + "open_weights": true, + "release_date": "2025-05-28", + "last_updated": "2025-05-28", "type": "chat" }, { - "id": "z-ai/glm-5-turbo", - "name": "GLM-5-Turbo", - "display_name": "GLM-5-Turbo", + "id": "claude-4.5-opus", + "name": "Claude 4.5 Opus", + "display_name": "Claude 4.5 Opus", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" @@ -203408,7 +202728,7 @@ }, "limit": { "context": 200000, - "output": 131072 + "output": 200000 }, "temperature": true, "tool_call": true, @@ -203416,21 +202736,17 @@ "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "supported": true - } - }, - "attachment": false, + "attachment": true, "open_weights": false, - "release_date": "2026-03-16", - "last_updated": "2026-03-16", + "knowledge": "2025-05", + "release_date": "2025-11-24", + "last_updated": "2025-11-24", "type": "chat" }, { - "id": "z-ai/glm-4.7", - "name": "GLM-4.7", - "display_name": "GLM-4.7", + "id": "deepseek-v3.1", + "name": "DeepSeek-V3.1", + "display_name": "DeepSeek-V3.1", "modalities": { "input": [ "text" @@ -203440,8 +202756,8 @@ ] }, "limit": { - "context": 204800, - "output": 131072 + "context": 128000, + "output": 32000 }, "temperature": true, "tool_call": true, @@ -203449,28 +202765,16 @@ "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } - }, "attachment": false, "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-12-22", - "last_updated": "2025-12-22", + "release_date": "2025-08-21", + "last_updated": "2025-08-21", "type": "chat" }, { - "id": "z-ai/glm-4.6", - "name": "GLM-4.6", - "display_name": "GLM-4.6", + "id": "qwen3-30b-a3b-instruct-2507", + "name": "Qwen3 30b A3b Instruct 2507", + "display_name": "Qwen3 30b A3b Instruct 2507", "modalities": { "input": [ "text" @@ -203480,45 +202784,37 @@ ] }, "limit": { - "context": 204800, - "output": 131072 + "context": 128000, + "output": 32000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true - } + "supported": false }, "attachment": false, "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-09-30", - "last_updated": "2025-09-30", + "release_date": "2025-07-28", + "last_updated": "2025-07-28", "type": "chat" }, { - "id": "z-ai/glm-5.3-flashx", - "name": "GLM-5.3-FlashX", - "display_name": "GLM-5.3-FlashX", + "id": "doubao-seed-1.6", + "name": "Doubao-Seed 1.6", + "display_name": "Doubao-Seed 1.6", "modalities": { "input": [ "text", "image", - "video", - "pdf" + "video" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 256000, + "output": 32000 }, "temperature": true, "tool_call": true, @@ -203526,25 +202822,16 @@ "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "low", - "high", - "max" - ] - } - }, "attachment": true, - "open_weights": true, - "release_date": "2026-08-26", - "last_updated": "2026-08-26", + "open_weights": false, + "release_date": "2025-10-15", + "last_updated": "2025-10-15", "type": "chat" }, { - "id": "z-ai/glm-5.1", - "name": "GLM-5.1", - "display_name": "GLM-5.1", + "id": "qwen3-30b-a3b", + "name": "Qwen3 30B A3B", + "display_name": "Qwen3 30B A3B", "modalities": { "input": [ "text" @@ -203554,8 +202841,8 @@ ] }, "limit": { - "context": 200000, - "output": 131072 + "context": 40000, + "output": 4096 }, "temperature": true, "tool_call": true, @@ -203565,58 +202852,56 @@ }, "extra_capabilities": { "reasoning": { - "supported": true + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] } }, "attachment": false, "open_weights": true, - "release_date": "2026-04-07", - "last_updated": "2026-04-07", + "release_date": "2025-04-28", + "last_updated": "2025-04-28", "type": "chat" }, { - "id": "z-ai/glm-5", - "name": "GLM-5", - "display_name": "GLM-5", + "id": "gemini-2.0-flash-lite", + "name": "Gemini 2.0 Flash Lite", + "display_name": "Gemini 2.0 Flash Lite", "modalities": { "input": [ - "text" + "text", + "image", + "audio", + "video" ], "output": [ "text" ] }, "limit": { - "context": 204800, - "output": 131072 + "context": 1048576, + "output": 8192 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } + "supported": false }, - "attachment": false, - "open_weights": true, - "release_date": "2026-02-12", - "last_updated": "2026-02-12", + "attachment": true, + "open_weights": false, + "knowledge": "2024-06", + "release_date": "2024-12-11", + "last_updated": "2024-12-11", "type": "chat" }, { - "id": "z-ai/glm-5.3", - "name": "GLM-5.3", - "display_name": "GLM-5.3", + "id": "mimo-v2-flash", + "name": "Mimo-V2-Flash", + "display_name": "Mimo-V2-Flash", "modalities": { "input": [ "text" @@ -203626,8 +202911,8 @@ ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 256000, + "output": 256000 }, "temperature": true, "tool_call": true, @@ -203635,29 +202920,24 @@ "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "effort_options": [ - "low", - "high", - "max" - ] - } - }, "attachment": false, "open_weights": true, - "release_date": "2026-08-14", - "last_updated": "2026-08-14", + "knowledge": "2024-12-01", + "release_date": "2025-12-16", + "last_updated": "2026-02-04", "type": "chat" }, { - "id": "z-ai/glm-5.2", - "name": "GLM-5.2", - "display_name": "GLM-5.2", + "id": "gemini-3.0-flash-preview", + "name": "Gemini 3.0 Flash Preview", + "display_name": "Gemini 3.0 Flash Preview", "modalities": { "input": [ - "text" + "text", + "image", + "audio", + "video", + "pdf" ], "output": [ "text" @@ -203665,7 +202945,7 @@ }, "limit": { "context": 1000000, - "output": 131072 + "output": 64000 }, "temperature": true, "tool_call": true, @@ -203676,36 +202956,49 @@ "extra_capabilities": { "reasoning": { "supported": true, - "effort_options": [ - "high", - "max" + "default_enabled": true, + "mode": "level", + "level": "high", + "level_options": [ + "minimal", + "low", + "medium", + "high" + ], + "summaries": true, + "visibility": "summary", + "continuation": [ + "thought_signatures" ] } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-06-13", - "last_updated": "2026-06-13", + "attachment": true, + "open_weights": false, + "knowledge": "2025-01", + "release_date": "2025-12-17", + "last_updated": "2025-12-17", "type": "chat" }, { - "id": "deepseek/deepseek-v4-flash", - "name": "DeepSeek V4 Flash", - "display_name": "DeepSeek V4 Flash", + "id": "gemini-3.0-pro-image-preview", + "name": "Gemini 3.0 Pro Image Preview", + "display_name": "Gemini 3.0 Pro Image Preview", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ - "text" + "text", + "image" ] }, "limit": { - "context": 1000000, - "output": 384000 + "context": 32768, + "output": 8192 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { "supported": true, "default": true @@ -203714,39 +203007,32 @@ "reasoning": { "supported": true, "default_enabled": true, - "mode": "effort", - "effort": "high", - "effort_options": [ + "mode": "level", + "level": "high", + "level_options": [ "low", - "high", - "max" + "high" ], - "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ - "thinking_blocks" - ], - "notes": [ - "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." + "thought_signatures" ] } }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-04-24", - "last_updated": "2026-04-24", - "type": "chat" + "attachment": true, + "open_weights": false, + "knowledge": "2025-01", + "release_date": "2025-11-20", + "last_updated": "2025-11-20" }, { - "id": "deepseek/deepseek-v4.1-flash-1017", - "name": "DeepSeek V4.1 Flash 1017", - "display_name": "DeepSeek V4.1 Flash 1017", + "id": "qwen-turbo", + "name": "Qwen-Turbo", + "display_name": "Qwen-Turbo", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" @@ -203754,7 +203040,7 @@ }, "limit": { "context": 1000000, - "output": 384000 + "output": 4096 }, "temperature": true, "tool_call": true, @@ -203764,24 +203050,26 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "low", - "high", - "max" + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, - "attachment": true, - "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-10-17", - "last_updated": "2026-10-17", + "attachment": false, + "open_weights": false, + "knowledge": "2024-04", + "release_date": "2024-11-01", + "last_updated": "2025-04-28", "type": "chat" }, { - "id": "deepseek/deepseek-v4-pro", - "name": "DeepSeek V4 Pro", - "display_name": "DeepSeek V4 Pro", + "id": "qwen3-235b-a22b", + "name": "Qwen 3 235B A22B", + "display_name": "Qwen 3 235B A22B", "modalities": { "input": [ "text" @@ -203791,8 +203079,8 @@ ] }, "limit": { - "context": 1000000, - "output": 384000 + "context": 128000, + "output": 32000 }, "temperature": true, "tool_call": true, @@ -203803,48 +203091,38 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "high", - "effort_options": [ - "low", - "high", - "max" - ], "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ "thinking_blocks" - ], - "notes": [ - "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." ] } }, "attachment": false, "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-04-24", - "last_updated": "2026-04-24", + "knowledge": "2025-04", + "release_date": "2025-04-28", + "last_updated": "2025-04-28", "type": "chat" }, { - "id": "deepseek/deepseek-v4.1-flash", - "name": "DeepSeek V4.1 Flash", - "display_name": "DeepSeek V4.1 Flash", + "id": "doubao-seed-2.0-pro", + "name": "Doubao Seed 2.0 Pro", + "display_name": "Doubao Seed 2.0 Pro", "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 384000 + "context": 256000, + "output": 128000 }, "temperature": true, "tool_call": true, @@ -203852,27 +203130,45 @@ "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "effort_options": [ - "low", - "high", - "max" - ] - } + "attachment": true, + "open_weights": false, + "release_date": "2026-02-14", + "last_updated": "2026-02-14", + "type": "chat" + }, + { + "id": "claude-3.5-haiku", + "name": "Claude 3.5 Haiku", + "display_name": "Claude 3.5 Haiku", + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 200000, + "output": 8192 + }, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": false }, "attachment": true, - "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-09-10", - "last_updated": "2026-09-10", + "open_weights": false, + "knowledge": "2024-07-31", + "release_date": "2024-10-22", + "last_updated": "2024-10-22", "type": "chat" }, { - "id": "deepseek/deepseek-v3.2", - "name": "DeepSeek V3.2", - "display_name": "DeepSeek V3.2", + "id": "qwen3-coder-480b-a35b-instruct", + "name": "Qwen3 Coder 480B A35B Instruct", + "display_name": "Qwen3 Coder 480B A35B Instruct", "modalities": { "input": [ "text" @@ -203882,31 +203178,25 @@ ] }, "limit": { - "context": 128000, - "output": 32000 + "context": 262000, + "output": 4096 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true - } + "supported": false }, "attachment": false, "open_weights": true, - "knowledge": "2024-07", - "release_date": "2025-12-01", - "last_updated": "2025-12-01", + "knowledge": "2025-04", + "release_date": "2025-07-22", + "last_updated": "2025-07-22", "type": "chat" }, { - "id": "deepseek/deepseek-v4-flash-0731", - "name": "DeepSeek V4 Flash 0731", - "display_name": "DeepSeek V4 Flash 0731", + "id": "qwen3-next-80b-a3b-thinking", + "name": "Qwen3 Next 80B A3B Thinking", + "display_name": "Qwen3 Next 80B A3B Thinking", "modalities": { "input": [ "text" @@ -203916,8 +203206,8 @@ ] }, "limit": { - "context": 1000000, - "output": 384000 + "context": 131072, + "output": 32768 }, "temperature": true, "tool_call": true, @@ -203928,24 +203218,25 @@ "extra_capabilities": { "reasoning": { "supported": true, - "effort_options": [ - "low", - "high", - "max" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, "attachment": false, "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-07-31", - "last_updated": "2026-07-31", + "knowledge": "2025-04", + "release_date": "2025-09", + "last_updated": "2025-09", "type": "chat" }, { - "id": "deepseek/deepseek-v4-pro-0813", - "name": "DeepSeek V4 Pro 0813", - "display_name": "DeepSeek V4 Pro 0813", + "id": "doubao-1.5-thinking-pro", + "name": "Doubao 1.5 Thinking Pro", + "display_name": "Doubao 1.5 Thinking Pro", "modalities": { "input": [ "text" @@ -203955,8 +203246,8 @@ ] }, "limit": { - "context": 1000000, - "output": 384000 + "context": 128000, + "output": 16000 }, "temperature": true, "tool_call": true, @@ -203964,25 +203255,16 @@ "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "effort_options": [ - "high", - "max" - ] - } - }, "attachment": false, - "open_weights": true, - "release_date": "2026-08-13", - "last_updated": "2026-08-22", + "open_weights": false, + "release_date": "2025-08-05", + "last_updated": "2025-08-05", "type": "chat" }, { - "id": "deepseek/deepseek-v4-pro-0423", - "name": "DeepSeek V4 Pro 0423", - "display_name": "DeepSeek V4 Pro 0423", + "id": "glm-4.5-air", + "name": "GLM 4.5 Air", + "display_name": "GLM 4.5 Air", "modalities": { "input": [ "text" @@ -203992,8 +203274,8 @@ ] }, "limit": { - "context": 1000000, - "output": 384000 + "context": 131000, + "output": 4096 }, "temperature": true, "tool_call": true, @@ -204001,25 +203283,17 @@ "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "high", - "max" - ] - } - }, "attachment": false, "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-04-23", - "last_updated": "2026-04-23", + "knowledge": "2025-04", + "release_date": "2025-07-28", + "last_updated": "2025-07-28", "type": "chat" }, { - "id": "deepseek/deepseek-v4-flash-0423", - "name": "DeepSeek V4 Flash 0423", - "display_name": "DeepSeek V4 Flash 0423", + "id": "doubao-1.5-pro-32k", + "name": "Doubao 1.5 Pro 32k", + "display_name": "Doubao 1.5 Pro 32k", "modalities": { "input": [ "text" @@ -204029,50 +203303,37 @@ ] }, "limit": { - "context": 1000000, - "output": 384000 + "context": 128000, + "output": 12000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "low", - "high", - "max" - ] - } + "supported": false }, "attachment": false, - "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-04-23", - "last_updated": "2026-04-23", + "open_weights": false, + "release_date": "2025-08-05", + "last_updated": "2025-08-05", "type": "chat" }, { - "id": "google/gemini-3-flash-preview", - "name": "Gemini 3 Flash Preview", - "display_name": "Gemini 3 Flash Preview", + "id": "doubao-seed-1.6-flash", + "name": "Doubao-Seed 1.6 Flash", + "display_name": "Doubao-Seed 1.6 Flash", "modalities": { "input": [ "text", "image", - "video", - "audio", - "pdf" + "video" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 256000, + "output": 32000 }, "temperature": true, "tool_call": true, @@ -204080,43 +203341,22 @@ "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "level", - "level": "high", - "level_options": [ - "minimal", - "low", - "medium", - "high" - ], - "summaries": true, - "visibility": "summary", - "continuation": [ - "thought_signatures" - ] - } - }, "attachment": true, "open_weights": false, - "knowledge": "2025-01", - "release_date": "2025-12-17", - "last_updated": "2025-12-17", + "release_date": "2025-08-28", + "last_updated": "2025-08-28", "type": "chat" }, { - "id": "google/gemini-2.5-pro", + "id": "gemini-2.5-pro", "name": "Gemini 2.5 Pro", "display_name": "Gemini 2.5 Pro", "modalities": { "input": [ "text", "image", - "audio", "video", - "pdf" + "audio" ], "output": [ "text" @@ -204159,24 +203399,24 @@ "type": "chat" }, { - "id": "google/gemini-3.5-flash-lite", - "name": "Gemini 3.5 Flash Lite", - "display_name": "Gemini 3.5 Flash Lite", + "id": "gemini-3.0-pro-preview", + "name": "Gemini 3.0 Pro Preview", + "display_name": "Gemini 3.0 Pro Preview", "modalities": { "input": [ "text", "image", "video", - "audio", - "pdf" + "pdf", + "audio" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 1000000, + "output": 64000 }, "temperature": true, "tool_call": true, @@ -204187,93 +203427,73 @@ "extra_capabilities": { "reasoning": { "supported": true, - "effort_options": [ - "minimal", + "default_enabled": true, + "mode": "level", + "level": "high", + "level_options": [ "low", - "medium", "high" + ], + "summaries": true, + "visibility": "summary", + "continuation": [ + "thought_signatures" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-03", - "release_date": "2026-07-21", - "last_updated": "2026-07-21", + "knowledge": "2025-01", + "release_date": "2025-11-18", + "last_updated": "2025-11-18", "type": "chat" }, { - "id": "google/gemini-2.5-flash", - "name": "Gemini 2.5 Flash", - "display_name": "Gemini 2.5 Flash", + "id": "qwen-vl-max-2025-01-25", + "name": "Qwen VL-MAX-2025-01-25", + "display_name": "Qwen VL-MAX-2025-01-25", "modalities": { "input": [ "text", "image", "audio", - "video", - "pdf" + "video" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 128000, + "output": 4096 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "budget", - "budget": { - "default": -1, - "min": 0, - "max": 24576, - "auto": -1, - "off": 0, - "unit": "tokens" - }, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thought_signatures" - ] - } + "supported": false }, "attachment": true, "open_weights": false, - "knowledge": "2025-01", - "release_date": "2025-06-17", - "last_updated": "2025-06-17", + "release_date": "2025-08-05", + "last_updated": "2025-08-05", "type": "chat" }, { - "id": "google/gemini-3.1-flash-lite", - "name": "Gemini 3.1 Flash Lite", - "display_name": "Gemini 3.1 Flash Lite", + "id": "claude-4.5-haiku", + "name": "Claude 4.5 Haiku", + "display_name": "Claude 4.5 Haiku", "modalities": { "input": [ "text", - "image", - "video", - "audio", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 200000, + "output": 64000 }, "temperature": true, "tool_call": true, @@ -204281,137 +203501,80 @@ "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "effort_options": [ - "minimal", - "low", - "medium", - "high" - ] - } - }, "attachment": true, "open_weights": false, - "knowledge": "2025-01", - "release_date": "2026-05-07", - "last_updated": "2026-05-07", + "knowledge": "2025-02-28", + "release_date": "2025-10-15", + "last_updated": "2025-10-15", "type": "chat" }, { - "id": "google/gemini-3.7-flash", - "name": "Gemini 3.7 Flash", - "display_name": "Gemini 3.7 Flash", + "id": "MiniMax-M1", + "name": "MiniMax M1", + "display_name": "MiniMax M1", "modalities": { "input": [ - "text", - "image", - "video", - "audio", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 1000000, + "output": 80000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "level", - "level": "high", - "level_options": [ - "minimal", - "low", - "medium", - "high" - ], - "summaries": true, - "visibility": "summary", - "continuation": [ - "thought_signatures" - ] - } - }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-03", - "release_date": "2026-08-13", - "last_updated": "2026-08-13", + }, + "attachment": false, + "open_weights": true, + "release_date": "2025-06-17", + "last_updated": "2025-06-17", "type": "chat" }, { - "id": "google/gemini-3.1-pro-preview", - "name": "Gemini 3.1 Pro Preview", - "display_name": "Gemini 3.1 Pro Preview", + "id": "doubao-1.5-vision-pro", + "name": "Doubao 1.5 Vision Pro", + "display_name": "Doubao 1.5 Vision Pro", "modalities": { "input": [ "text", "image", - "video", - "audio", - "pdf" + "video" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 128000, + "output": 16000 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "level", - "level": "high", - "level_options": [ - "low", - "high" - ], - "summaries": true, - "visibility": "summary", - "continuation": [ - "thought_signatures" - ] - } + "supported": false }, "attachment": true, "open_weights": false, - "knowledge": "2025-01", - "release_date": "2026-02-19", - "last_updated": "2026-02-19", + "release_date": "2025-08-05", + "last_updated": "2025-08-05", "type": "chat" }, { - "id": "google/gemini-2.5-flash-lite", - "name": "Gemini 2.5 Flash-Lite", - "display_name": "Gemini 2.5 Flash-Lite", + "id": "gemini-2.5-flash-lite", + "name": "Gemini 2.5 Flash Lite", + "display_name": "Gemini 2.5 Flash Lite", "modalities": { "input": [ "text", "image", "audio", - "video", - "pdf" + "video" ], "output": [ "text" @@ -204419,7 +203582,7 @@ }, "limit": { "context": 1048576, - "output": 65536 + "output": 64000 }, "temperature": true, "tool_call": true, @@ -204454,23 +203617,23 @@ "type": "chat" }, { - "id": "google/gemini-3.8-flash", - "name": "Gemini 3.8 Flash", - "display_name": "Gemini 3.8 Flash", + "id": "gemini-2.5-flash", + "name": "Gemini 2.5 Flash", + "display_name": "Gemini 2.5 Flash", "modalities": { "input": [ "text", "image", "audio", - "pdf" + "video" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 65536 + "context": 1048576, + "output": 64000 }, "temperature": true, "tool_call": true, @@ -204482,14 +203645,15 @@ "reasoning": { "supported": true, "default_enabled": true, - "mode": "level", - "level": "high", - "level_options": [ - "minimal", - "low", - "medium", - "high" - ], + "mode": "budget", + "budget": { + "default": -1, + "min": 0, + "max": 24576, + "auto": -1, + "off": 0, + "unit": "tokens" + }, "summaries": true, "visibility": "summary", "continuation": [ @@ -204499,71 +203663,57 @@ }, "attachment": true, "open_weights": false, - "release_date": "2026-09-02", - "last_updated": "2026-09-02", + "knowledge": "2025-01", + "release_date": "2025-06-17", + "last_updated": "2025-06-17", "type": "chat" }, { - "id": "google/gemini-4-argon", - "name": "Gemini 4 Argon", - "display_name": "Gemini 4 Argon", + "id": "claude-4.0-sonnet", + "name": "Claude 4.0 Sonnet", + "display_name": "Claude 4.0 Sonnet", "modalities": { "input": [ "text", - "image", - "video", - "audio", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 200000, + "output": 64000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "low", - "medium", - "high" - ] - } - }, "attachment": true, "open_weights": false, - "knowledge": "2026-03", - "release_date": "2026-10-13", - "last_updated": "2026-10-13", + "knowledge": "2025-03-31", + "release_date": "2025-05-22", + "last_updated": "2025-05-22", "type": "chat" }, { - "id": "google/gemini-3.6-flash", - "name": "Gemini 3.6 Flash", - "display_name": "Gemini 3.6 Flash", + "id": "claude-4.0-opus", + "name": "Claude 4.0 Opus", + "display_name": "Claude 4.0 Opus", "modalities": { "input": [ "text", - "image", - "video", - "audio", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 200000, + "output": 32000 }, "temperature": true, "tool_call": true, @@ -204571,51 +203721,28 @@ "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "level", - "level": "high", - "level_options": [ - "minimal", - "low", - "medium", - "high" - ], - "summaries": true, - "visibility": "summary", - "continuation": [ - "thought_signatures" - ] - } - }, "attachment": true, "open_weights": false, - "knowledge": "2026-03", - "release_date": "2026-07-21", - "last_updated": "2026-07-21", + "knowledge": "2025-03-31", + "release_date": "2025-05-22", + "last_updated": "2025-05-22", "type": "chat" }, { - "id": "google/gemini-3.5-flash", - "name": "Gemini 3.5 Flash", - "display_name": "Gemini 3.5 Flash", + "id": "deepseek-r1", + "name": "DeepSeek-R1", + "display_name": "DeepSeek-R1", "modalities": { "input": [ - "text", - "image", - "video", - "audio", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 128000, + "output": 32000 }, "temperature": true, "tool_call": true, @@ -204626,33 +203753,25 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "level", - "level": "high", - "level_options": [ - "minimal", - "low", - "medium", - "high" - ], + "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ - "thought_signatures" + "thinking_blocks" ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-01", - "release_date": "2026-05-19", - "last_updated": "2026-05-19", + "attachment": false, + "open_weights": true, + "knowledge": "2024-07", + "release_date": "2025-01-20", + "last_updated": "2025-05-29", "type": "chat" }, { - "id": "openai/gpt-5.1-codex-max", - "name": "GPT-5.1 Codex Max", - "display_name": "GPT-5.1 Codex Max", + "id": "claude-3.7-sonnet", + "name": "Claude 3.7 Sonnet", + "display_name": "Claude 3.7 Sonnet", "modalities": { "input": [ "text", @@ -204663,10 +203782,10 @@ ] }, "limit": { - "context": 256000, + "context": 200000, "output": 128000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -204676,98 +203795,47 @@ "reasoning": { "supported": true, "default_enabled": false, - "mode": "effort", - "effort": "none", - "effort_options": [ - "none", - "low", - "medium", - "high" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" - } - }, - "attachment": true, - "open_weights": false, - "knowledge": "2024-09-30", - "release_date": "2025-11-13", - "last_updated": "2025-11-13", - "type": "chat" - }, - { - "id": "openai/gpt-5", - "name": "GPT-5", - "display_name": "GPT-5", - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 400000, - "output": 128000 - }, - "temperature": false, - "tool_call": true, - "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", - "effort_options": [ - "minimal", - "low", - "medium", - "high" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" + "mode": "budget", + "budget": { + "min": 1024, + "unit": "tokens" + }, + "interleaved": false, + "summaries": false, + "visibility": "full", + "continuation": [ + "thinking_blocks" ], - "visibility": "hidden" + "notes": [ + "Anthropic uses thinking budget tokens" + ] } }, "attachment": true, "open_weights": false, - "knowledge": "2024-09-30", - "release_date": "2025-08-07", - "last_updated": "2025-08-07", + "knowledge": "2024-10-31", + "release_date": "2025-02-19", + "last_updated": "2025-02-19", "type": "chat" }, { - "id": "openai/gpt-4o-mini", - "name": "GPT-4o mini", - "display_name": "GPT-4o mini", + "id": "gemini-2.0-flash", + "name": "Gemini 2.0 Flash", + "display_name": "Gemini 2.0 Flash", "modalities": { "input": [ "text", "image", - "pdf" + "audio", + "video" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 16384 + "context": 1048576, + "output": 8192 }, "temperature": true, "tool_call": true, @@ -204776,168 +203844,105 @@ }, "attachment": true, "open_weights": false, - "knowledge": "2023-09", - "release_date": "2024-07-18", - "last_updated": "2024-07-18", + "knowledge": "2024-06", + "release_date": "2024-12-11", + "last_updated": "2024-12-11", "type": "chat" }, { - "id": "openai/gpt-5.5", - "name": "GPT-5.5", - "display_name": "GPT-5.5", + "id": "claude-4.1-opus", + "name": "Claude 4.1 Opus", + "display_name": "Claude 4.1 Opus", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 200000, + "output": 32000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", - "effort_options": [ - "low", - "medium", - "high", - "xhigh" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" - } - }, "attachment": true, "open_weights": false, - "knowledge": "2025-12-01", - "release_date": "2026-04-23", - "last_updated": "2026-04-23", + "knowledge": "2025-03-31", + "release_date": "2025-08-05", + "last_updated": "2025-08-05", "type": "chat" }, { - "id": "openai/gpt-6-astra", - "name": "GPT-6 Astra", - "display_name": "GPT-6 Astra", + "id": "claude-4.5-sonnet", + "name": "Claude 4.5 Sonnet", + "display_name": "Claude 4.5 Sonnet", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 200000, + "output": 64000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" - ] - } - }, "attachment": true, "open_weights": false, - "knowledge": "2026-04-30", - "release_date": "2026-09-04", - "last_updated": "2026-09-04", + "knowledge": "2025-07-31", + "release_date": "2025-09-29", + "last_updated": "2025-09-29", "type": "chat" }, { - "id": "openai/gpt-5.4", - "name": "GPT-5.4", - "display_name": "GPT-5.4", + "id": "qwen3-235b-a22b-instruct-2507", + "name": "Qwen3 235b A22B Instruct 2507", + "display_name": "Qwen3 235b A22B Instruct 2507", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 262144, + "output": 64000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": false - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "none", - "effort_options": [ - "none", - "low", - "medium", - "high", - "xhigh" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" - } + "supported": false }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-03-05", - "last_updated": "2026-03-05", + "attachment": false, + "open_weights": true, + "release_date": "2025-07-21", + "last_updated": "2025-07-21", "type": "chat" }, { - "id": "openai/gpt-4o", - "name": "GPT-4o", - "display_name": "GPT-4o", + "id": "kimi-k2", + "name": "Kimi K2", + "display_name": "Kimi K2", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" @@ -204945,39 +203950,37 @@ }, "limit": { "context": 128000, - "output": 16384 + "output": 128000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": false }, - "attachment": true, - "open_weights": false, - "knowledge": "2023-09", - "release_date": "2024-05-13", - "last_updated": "2024-08-06", + "attachment": false, + "open_weights": true, + "knowledge": "2024-12-31", + "release_date": "2025-07-11", + "last_updated": "2025-07-11", "type": "chat" }, { - "id": "openai/gpt-6-sol", - "name": "GPT-6 Sol", - "display_name": "GPT-6 Sol", + "id": "qwen3-235b-a22b-thinking-2507", + "name": "Qwen3 235B A22B Thinking 2507", + "display_name": "Qwen3 235B A22B Thinking 2507", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 262144, + "output": 4096 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -204985,96 +203988,67 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "none", - "low", - "medium", - "high", - "xhigh", - "max" + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-04-20", - "release_date": "2026-09-22", - "last_updated": "2026-09-22", + "attachment": false, + "open_weights": true, + "release_date": "2025-07-25", + "last_updated": "2025-07-25", "type": "chat" }, { - "id": "openai/gpt-5.6-sol", - "name": "GPT-5.6 Sol", - "display_name": "GPT-5.6 Sol", + "id": "qwen-max-2025-01-25", + "name": "Qwen2.5-Max-2025-01-25", + "display_name": "Qwen2.5-Max-2025-01-25", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 128000, + "output": 4096 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", - "effort_options": [ - "none", - "low", - "medium", - "high", - "xhigh", - "max" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" - } + "supported": false }, - "attachment": true, + "attachment": false, "open_weights": false, - "knowledge": "2026-02-16", - "release_date": "2026-07-09", - "last_updated": "2026-07-09", + "knowledge": "2024-04", + "release_date": "2024-04-03", + "last_updated": "2025-01-25", "type": "chat" }, { - "id": "openai/gpt-5.6-terra", - "name": "GPT-5.6 Terra", - "display_name": "GPT-5.6 Terra", + "id": "qwen3.5-397b-a17b", + "name": "Qwen3.5 397B A17B", + "display_name": "Qwen3.5 397B A17B", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 256000, + "output": 64000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -205083,94 +204057,66 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", - "effort_options": [ - "none", - "low", - "medium", - "high", - "xhigh", - "max" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2026-02-16", - "release_date": "2026-07-09", - "last_updated": "2026-07-09", + "open_weights": true, + "release_date": "2026-02-15", + "last_updated": "2026-02-15", "type": "chat" }, { - "id": "openai/gpt-6-luna", - "name": "GPT-6 Luna", - "display_name": "GPT-6 Luna", + "id": "glm-4.5", + "name": "GLM 4.5", + "display_name": "GLM 4.5", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 131072, + "output": 98304 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "none", - "low", - "medium", - "high", - "xhigh", - "max" - ] - } - }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-05-18", - "release_date": "2026-09-22", - "last_updated": "2026-09-22", + "attachment": false, + "open_weights": true, + "knowledge": "2025-04", + "release_date": "2025-07-28", + "last_updated": "2025-07-28", "type": "chat" }, { - "id": "openai/gpt-5-mini", - "name": "GPT-5 Mini", - "display_name": "GPT-5 Mini", + "id": "qwen3-32b", + "name": "Qwen3 32B", + "display_name": "Qwen3 32B", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 256000, - "output": 32768 + "context": 40000, + "output": 4096 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -205179,49 +204125,38 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", - "effort_options": [ - "minimal", - "low", - "medium", - "high" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2024-05-30", - "release_date": "2025-08-07", - "last_updated": "2025-08-07", + "attachment": false, + "open_weights": true, + "knowledge": "2025-04", + "release_date": "2025-04-28", + "last_updated": "2025-04-28", "type": "chat" }, { - "id": "openai/gpt-5.4-pro", - "name": "GPT-5.4 Pro", - "display_name": "GPT-5.4 Pro", + "id": "qwen3-30b-a3b-thinking-2507", + "name": "Qwen3 30b A3b Thinking 2507", + "display_name": "Qwen3 30b A3b Thinking 2507", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 126000, + "output": 32000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -205230,38 +204165,29 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "high", - "effort_options": [ - "medium", - "high", - "xhigh" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] } }, - "attachment": true, + "attachment": false, "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-03-05", - "last_updated": "2026-03-05", + "release_date": "2026-02-04", + "last_updated": "2026-02-04", "type": "chat" }, { - "id": "openai/gpt-5.1-codex-mini", - "name": "GPT-5.1 Codex mini", - "display_name": "GPT-5.1 Codex mini", + "id": "doubao-seed-2.0-code", + "name": "Doubao Seed 2.0 Code", + "display_name": "Doubao Seed 2.0 Code", "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ "text" @@ -205269,112 +204195,65 @@ }, "limit": { "context": 256000, - "output": 65536 + "output": 128000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "none", - "effort_options": [ - "none", - "low", - "medium", - "high" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" - } + "default": true }, "attachment": true, "open_weights": false, - "knowledge": "2024-09-30", - "release_date": "2025-11-13", - "last_updated": "2025-11-13", + "release_date": "2026-02-14", + "last_updated": "2026-02-14", "type": "chat" }, { - "id": "openai/gpt-5.1", - "name": "GPT-5.1", - "display_name": "GPT-5.1", + "id": "gemini-2.5-flash-image", + "name": "Gemini 2.5 Flash Image", + "display_name": "Gemini 2.5 Flash Image", "modalities": { "input": [ "text", "image" ], "output": [ - "text" + "image" ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 32768, + "output": 8192 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": false - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "none", - "effort_options": [ - "none", - "low", - "medium", - "high" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" - } + "supported": false }, "attachment": true, "open_weights": false, - "knowledge": "2024-09-30", - "release_date": "2025-11-13", - "last_updated": "2025-11-13", - "type": "chat" + "knowledge": "2024-06", + "release_date": "2025-08-26", + "last_updated": "2025-08-26" }, { - "id": "openai/gpt-5.6-luna", - "name": "GPT-5.6 Luna", - "display_name": "GPT-5.6 Luna", + "id": "gpt-oss-20b", + "name": "gpt-oss-20b", + "display_name": "gpt-oss-20b", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 128000, + "output": 4096 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -205382,38 +204261,20 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", - "effort_options": [ - "none", - "low", - "medium", - "high", - "xhigh", - "max" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + "supported": true } }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-02-16", - "release_date": "2026-07-09", - "last_updated": "2026-07-09", + "attachment": false, + "open_weights": true, + "knowledge": "2024-06", + "release_date": "2025-08-05", + "last_updated": "2025-08-05", "type": "chat" }, { - "id": "openai/gpt-5.4-mini", - "name": "GPT-5.4 mini", - "display_name": "GPT-5.4 mini", + "id": "openai/gpt-5.2", + "name": "OpenAI/GPT-5.2", + "display_name": "OpenAI/GPT-5.2", "modalities": { "input": [ "text", @@ -205458,19 +204319,17 @@ "attachment": true, "open_weights": false, "knowledge": "2025-08-31", - "release_date": "2026-03-17", - "last_updated": "2026-03-17", + "release_date": "2025-12-11", + "last_updated": "2025-12-11", "type": "chat" }, { - "id": "openai/gpt-5.3-codex", - "name": "GPT-5.3 Codex", - "display_name": "GPT-5.3 Codex", + "id": "openai/gpt-5", + "name": "OpenAI/GPT-5", + "display_name": "OpenAI/GPT-5", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" @@ -205493,10 +204352,10 @@ "mode": "effort", "effort": "medium", "effort_options": [ + "minimal", "low", "medium", - "high", - "xhigh" + "high" ], "verbosity": "medium", "verbosity_options": [ @@ -205507,291 +204366,153 @@ "visibility": "hidden" } }, - "attachment": true, + "attachment": false, "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-02-05", - "last_updated": "2026-02-05", + "knowledge": "2024-09-30", + "release_date": "2025-08-07", + "last_updated": "2025-08-07", "type": "chat" }, { - "id": "openai/gpt-4.1-mini", - "name": "GPT-4.1 mini", - "display_name": "GPT-4.1 mini", + "id": "meituan/longcat-flash-chat", + "name": "Meituan/Longcat-Flash-Chat", + "display_name": "Meituan/Longcat-Flash-Chat", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1047576, - "output": 32768 + "context": 131072, + "output": 131072 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { "supported": false }, - "attachment": true, + "attachment": false, "open_weights": false, - "knowledge": "2024-04", - "release_date": "2025-04-14", - "last_updated": "2025-04-14", + "release_date": "2025-11-05", + "last_updated": "2025-11-05", "type": "chat" }, { - "id": "openai/gpt-5.4-nano", - "name": "GPT-5.4 nano", - "display_name": "GPT-5.4 nano", + "id": "meituan/longcat-flash-lite", + "name": "Meituan/Longcat-Flash-Lite", + "display_name": "Meituan/Longcat-Flash-Lite", "modalities": { "input": [ - "text", - "image" - ], - "output": [ "text" - ] - }, - "limit": { - "context": 400000, - "output": 128000 - }, - "temperature": true, - "tool_call": true, - "reasoning": { - "supported": true, - "default": false - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "none", - "effort_options": [ - "none", - "low", - "medium", - "high", - "xhigh" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" - } - }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-03-17", - "last_updated": "2026-03-17", - "type": "chat" - }, - { - "id": "openai/gpt-4.1", - "name": "GPT-4.1", - "display_name": "GPT-4.1", - "modalities": { - "input": [ - "text", - "image", - "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1047576, - "output": 32768 + "context": 256000, + "output": 320000 }, "temperature": true, "tool_call": true, - "reasoning": { - "supported": false - }, - "attachment": true, - "open_weights": false, - "knowledge": "2024-04", - "release_date": "2025-04-14", - "last_updated": "2025-04-14", - "type": "chat" - }, - { - "id": "openai/gpt-5.2-codex", - "name": "GPT-5.2 Codex", - "display_name": "GPT-5.2 Codex", - "modalities": { - "input": [ - "text", - "image", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 400000, - "output": 128000 - }, - "temperature": false, - "tool_call": true, "reasoning": { "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", - "effort_options": [ - "low", - "medium", - "high", - "xhigh" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" - } - }, - "attachment": true, + "attachment": false, "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2025-12-11", - "last_updated": "2025-12-11", + "release_date": "2026-02-06", + "last_updated": "2026-02-06", "type": "chat" }, { - "id": "openai/gpt-6.1-terra", - "name": "GPT-6.1 Terra", - "display_name": "GPT-6.1 Terra", + "id": "z-ai/autoglm-phone-9b", + "name": "Z-Ai/Autoglm Phone 9b", + "display_name": "Z-Ai/Autoglm Phone 9b", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 12800, + "output": 4096 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "none", - "low", - "medium", - "high", - "xhigh", - "max" - ] - } + "supported": false }, "attachment": true, "open_weights": false, - "knowledge": "2026-06-01", - "release_date": "2026-10-26", - "last_updated": "2026-10-26", + "release_date": "2025-12-23", + "last_updated": "2025-12-23", "type": "chat" }, { - "id": "openai/gpt-5.2", - "name": "GPT-5.2", - "display_name": "GPT-5.2", + "id": "z-ai/glm-4.7", + "name": "Z-Ai/GLM 4.7", + "display_name": "Z-Ai/GLM 4.7", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 200000, + "output": 200000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "none", - "effort_options": [ - "none", - "low", - "medium", - "high", - "xhigh" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2025-12-11", - "last_updated": "2025-12-11", + "attachment": false, + "open_weights": true, + "knowledge": "2025-04", + "release_date": "2025-12-22", + "last_updated": "2025-12-22", "type": "chat" }, { - "id": "openai/gpt-6.1-sol", - "name": "GPT-6.1 Sol", - "display_name": "GPT-6.1 Sol", + "id": "z-ai/glm-4.6", + "name": "Z-AI/GLM 4.6", + "display_name": "Z-AI/GLM 4.6", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 200000, + "output": 200000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -205799,40 +204520,33 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" - ] + "supported": true } }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-04-30", - "release_date": "2026-09-29", - "last_updated": "2026-09-29", + "attachment": false, + "open_weights": true, + "knowledge": "2025-04", + "release_date": "2025-09-30", + "last_updated": "2025-09-30", "type": "chat" }, { - "id": "openai/gpt-5-nano", - "name": "GPT-5 Nano", - "display_name": "GPT-5 Nano", + "id": "z-ai/glm-5", + "name": "Z-Ai/GLM 5", + "display_name": "Z-Ai/GLM 5", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 400000, + "context": 200000, "output": 128000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -205841,78 +204555,63 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", - "effort_options": [ - "minimal", - "low", - "medium", - "high" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2024-05-30", - "release_date": "2025-08-07", - "last_updated": "2025-08-07", + "attachment": false, + "open_weights": true, + "release_date": "2026-02-12", + "last_updated": "2026-02-12", "type": "chat" }, { - "id": "volcengine/doubao-seed-2.0-code", - "name": "Seed 2.0 Code", - "display_name": "Seed 2.0 Code", + "id": "stepfun-ai/gelab-zero-4b-preview", + "name": "Stepfun-Ai/Gelab Zero 4b Preview", + "display_name": "Stepfun-Ai/Gelab Zero 4b Preview", "modalities": { "input": [ "text", - "image", - "video" + "image" ], "output": [ "text" ] }, "limit": { - "context": 256000, - "output": 128000 + "context": 8192, + "output": 4096 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true + "supported": false }, "attachment": true, "open_weights": false, - "release_date": "2026-02-14", - "last_updated": "2026-02-14", + "release_date": "2025-12-23", + "last_updated": "2025-12-23", "type": "chat" }, { - "id": "volcengine/doubao-seed-character", - "name": "Seed Character", - "display_name": "Seed Character", + "id": "minimax/minimax-m2.5", + "name": "Minimax/Minimax-M2.5", + "display_name": "Minimax/Minimax-M2.5", "modalities": { "input": [ - "text", - "image", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 256000, - "output": 256000 + "context": 204800, + "output": 128000 }, "temperature": true, "tool_call": true, @@ -205920,29 +204619,32 @@ "supported": true, "default": true }, - "attachment": true, - "open_weights": false, - "release_date": "2026-06-23", - "last_updated": "2026-06-23", + "extra_capabilities": { + "reasoning": { + "supported": true + } + }, + "attachment": false, + "open_weights": true, + "release_date": "2026-02-12", + "last_updated": "2026-02-12", "type": "chat" }, { - "id": "volcengine/doubao-seed-2.1-pro", - "name": "Seed 2.1 Pro", - "display_name": "Seed 2.1 Pro", + "id": "minimax/minimax-m2.5-highspeed", + "name": "Minimax/Minimax-M2.5 Highspeed", + "display_name": "Minimax/Minimax-M2.5 Highspeed", "modalities": { "input": [ - "text", - "image", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 256000, - "output": 256000 + "context": 204800, + "output": 128000 }, "temperature": true, "tool_call": true, @@ -205950,59 +204652,60 @@ "supported": true, "default": true }, - "attachment": true, - "open_weights": false, - "release_date": "2026-06-23", - "last_updated": "2026-06-23", + "extra_capabilities": { + "reasoning": { + "supported": true + } + }, + "attachment": false, + "open_weights": true, + "release_date": "2026-02-13", + "last_updated": "2026-02-13", "type": "chat" }, { - "id": "volcengine/doubao-seed-2.0-pro", - "name": "Seed 2.0 Pro", - "display_name": "Seed 2.0 Pro", + "id": "minimax/minimax-m2", + "name": "Minimax/Minimax-M2", + "display_name": "Minimax/Minimax-M2", "modalities": { "input": [ - "text", - "image", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 256000, + "context": 200000, "output": 128000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, "default": true }, - "attachment": true, - "open_weights": false, - "release_date": "2026-02-14", - "last_updated": "2026-02-14", + "attachment": false, + "open_weights": true, + "release_date": "2025-10-27", + "last_updated": "2025-10-27", "type": "chat" }, { - "id": "volcengine/doubao-seed-2.0-mini", - "name": "Seed 2.0 Mini", - "display_name": "Seed 2.0 Mini", + "id": "minimax/minimax-m2.1", + "name": "Minimax/Minimax-M2.1", + "display_name": "Minimax/Minimax-M2.1", "modalities": { "input": [ - "text", - "image", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 256000, - "output": 32000 + "context": 204800, + "output": 128000 }, "temperature": true, "tool_call": true, @@ -206010,21 +204713,24 @@ "supported": true, "default": true }, - "attachment": true, - "open_weights": false, - "release_date": "2026-02-14", - "last_updated": "2026-02-14", + "extra_capabilities": { + "reasoning": { + "supported": true + } + }, + "attachment": false, + "open_weights": true, + "release_date": "2025-12-23", + "last_updated": "2025-12-23", "type": "chat" }, { - "id": "volcengine/doubao-seed-2.1-turbo", - "name": "Seed 2.1 Turbo", - "display_name": "Seed 2.1 Turbo", + "id": "moonshotai/kimi-k2-thinking", + "name": "Kimi K2 Thinking", + "display_name": "Kimi K2 Thinking", "modalities": { "input": [ - "text", - "image", - "video" + "text" ], "output": [ "text" @@ -206032,7 +204738,7 @@ }, "limit": { "context": 256000, - "output": 256000 + "output": 100000 }, "temperature": true, "tool_call": true, @@ -206040,21 +204746,31 @@ "supported": true, "default": true }, - "attachment": true, - "open_weights": false, - "release_date": "2026-06-23", - "last_updated": "2026-06-23", + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } + }, + "attachment": false, + "open_weights": true, + "knowledge": "2024-08", + "release_date": "2025-11-06", + "last_updated": "2025-11-06", "type": "chat" }, { - "id": "volcengine/doubao-seed-evolving", - "name": "Seed Evolving", - "display_name": "Seed Evolving", + "id": "moonshotai/kimi-k2-0905", + "name": "Kimi K2 0905", + "display_name": "Kimi K2 0905", "modalities": { "input": [ - "text", - "image", - "video" + "text" ], "output": [ "text" @@ -206062,24 +204778,23 @@ }, "limit": { "context": 256000, - "output": 256000 + "output": 100000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true + "supported": false }, - "attachment": true, - "open_weights": false, - "release_date": "2026-06-23", - "last_updated": "2026-06-23", + "attachment": false, + "open_weights": true, + "release_date": "2025-09-05", + "last_updated": "2025-09-05", "type": "chat" }, { - "id": "volcengine/doubao-seed-2.0-lite", - "name": "Seed 2.0 Lite", - "display_name": "Seed 2.0 Lite", + "id": "moonshotai/kimi-k2.5", + "name": "Moonshotai/Kimi-K2.5", + "display_name": "Moonshotai/Kimi-K2.5", "modalities": { "input": [ "text", @@ -206092,7 +204807,7 @@ }, "limit": { "context": 256000, - "output": 32000 + "output": 256000 }, "temperature": true, "tool_call": true, @@ -206100,64 +204815,67 @@ "supported": true, "default": true }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } + }, "attachment": true, - "open_weights": false, - "release_date": "2026-02-14", - "last_updated": "2026-02-14", + "open_weights": true, + "knowledge": "2025-01", + "release_date": "2026-01-27", + "last_updated": "2026-01-27", "type": "chat" }, { - "id": "moonshotai/kimi-k3", - "name": "Kimi K3", - "display_name": "Kimi K3", + "id": "stepfun/step-3.5-flash", + "name": "Stepfun/Step-3.5 Flash", + "display_name": "Stepfun/Step-3.5 Flash", "modalities": { "input": [ - "text", - "image", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 64000, + "output": 4096 }, - "temperature": false, - "tool_call": true, + "temperature": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true - } + "supported": false }, - "attachment": true, + "attachment": false, "open_weights": true, - "release_date": "2026-07-16", - "last_updated": "2026-07-16", + "knowledge": "2025-01", + "release_date": "2026-01-29", + "last_updated": "2026-02-13", "type": "chat" }, { - "id": "moonshotai/kimi-k2.6", - "name": "Kimi K2.6", - "display_name": "Kimi K2.6", + "id": "deepseek/deepseek-v3.2-251201", + "name": "Deepseek/DeepSeek-V3.2", + "display_name": "Deepseek/DeepSeek-V3.2", "modalities": { "input": [ - "text", - "image", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 128000, + "output": 32000 }, "temperature": true, "tool_call": true, @@ -206165,132 +204883,99 @@ "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "supported": true - } - }, - "attachment": true, + "attachment": false, "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-04-20", - "last_updated": "2026-04-20", + "knowledge": "2024-07", + "release_date": "2025-12-01", + "last_updated": "2025-12-01", "type": "chat" }, { - "id": "moonshotai/kimi-k3.1-pro", - "name": "Kimi K3.1 Pro", - "display_name": "Kimi K3.1 Pro", + "id": "deepseek/deepseek-v3.2-exp", + "name": "DeepSeek/DeepSeek-V3.2-Exp", + "display_name": "DeepSeek/DeepSeek-V3.2-Exp", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 128000, + "output": 32000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "low", - "high", - "max" - ] - } + "supported": false }, - "attachment": true, + "attachment": false, "open_weights": false, - "release_date": "2026-10-23", - "last_updated": "2026-10-23", + "release_date": "2025-09-29", + "last_updated": "2025-09-29", "type": "chat" }, { - "id": "moonshotai/kimi-k2.7-code", - "name": "Kimi K2.7 Code", - "display_name": "Kimi K2.7 Code", + "id": "deepseek/deepseek-math-v2", + "name": "Deepseek/Deepseek-Math-V2", + "display_name": "Deepseek/Deepseek-Math-V2", "modalities": { "input": [ - "text", - "image", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 160000, + "output": 160000 }, - "temperature": false, - "tool_call": true, + "temperature": true, + "tool_call": false, "reasoning": { "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "supported": true - } - }, - "attachment": true, - "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-06-12", - "last_updated": "2026-06-12", + "attachment": false, + "open_weights": false, + "release_date": "2025-12-04", + "last_updated": "2025-12-04", "type": "chat" }, { - "id": "moonshotai/kimi-k2.7-code-highspeed", - "name": "Kimi K2.7 Code Highspeed", - "display_name": "Kimi K2.7 Code Highspeed", + "id": "deepseek/deepseek-v3.1-terminus", + "name": "DeepSeek/DeepSeek-V3.1-Terminus", + "display_name": "DeepSeek/DeepSeek-V3.1-Terminus", "modalities": { "input": [ - "text", - "image", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 128000, + "output": 32000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true - } + "supported": false }, - "attachment": true, - "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-06-12", - "last_updated": "2026-06-12", + "attachment": false, + "open_weights": false, + "release_date": "2025-09-22", + "last_updated": "2025-09-22", "type": "chat" }, { - "id": "qwen/qwen3.7-max", - "name": "Qwen3.7 Max", - "display_name": "Qwen3.7 Max", + "id": "deepseek/deepseek-v3.2-exp-thinking", + "name": "DeepSeek/DeepSeek-V3.2-Exp-Thinking", + "display_name": "DeepSeek/DeepSeek-V3.2-Exp-Thinking", "modalities": { "input": [ "text" @@ -206300,8 +204985,8 @@ ] }, "limit": { - "context": 1064000, - "output": 64000 + "context": 128000, + "output": 32000 }, "temperature": true, "tool_call": true, @@ -206309,21 +204994,16 @@ "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "supported": true - } - }, "attachment": false, "open_weights": false, - "release_date": "2026-05-21", - "last_updated": "2026-05-21", + "release_date": "2025-09-29", + "last_updated": "2025-09-29", "type": "chat" }, { - "id": "qwen/qwen3-max", - "name": "Qwen3 Max", - "display_name": "Qwen3 Max", + "id": "deepseek/deepseek-v3.1-terminus-thinking", + "name": "DeepSeek/DeepSeek-V3.1-Terminus-Thinking", + "display_name": "DeepSeek/DeepSeek-V3.1-Terminus-Thinking", "modalities": { "input": [ "text" @@ -206333,8 +205013,8 @@ ] }, "limit": { - "context": 256000, - "output": 64000 + "context": 128000, + "output": 32000 }, "temperature": true, "tool_call": true, @@ -206342,42 +205022,27 @@ "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } - }, "attachment": false, "open_weights": false, - "knowledge": "2025-04", - "release_date": "2025-09-23", - "last_updated": "2025-09-23", + "release_date": "2025-09-22", + "last_updated": "2025-09-22", "type": "chat" }, { - "id": "qwen/qwen3.8-max-0902", - "name": "Qwen3.8 Max 0902", - "display_name": "Qwen3.8 Max 0902", + "id": "x-ai/grok-code-fast-1", + "name": "x-AI/Grok-Code-Fast 1", + "display_name": "x-AI/Grok-Code-Fast 1", "modalities": { "input": [ - "text", - "image", - "video", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 256000, + "output": 10000 }, "temperature": true, "tool_call": true, @@ -206387,96 +205052,81 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "low", - "medium", - "xhigh" - ] + "supported": true } }, - "attachment": true, + "attachment": false, "open_weights": false, - "release_date": "2026-09-02", - "last_updated": "2026-09-02", + "release_date": "2025-09-02", + "last_updated": "2025-09-02", "type": "chat" }, { - "id": "qwen/qwen3.8-27b", - "name": "Qwen3.8 27B", - "display_name": "Qwen3.8 27B", + "id": "x-ai/grok-4.1-fast-non-reasoning", + "name": "X-Ai/Grok 4.1 Fast Non Reasoning", + "display_name": "X-Ai/Grok 4.1 Fast Non Reasoning", "modalities": { "input": [ "text", - "image", - "video" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1131072, - "output": 131072 + "context": 2000000, + "output": 2000000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true + "supported": false }, "attachment": true, - "open_weights": true, - "release_date": "2026-08-14", - "last_updated": "2026-08-14", + "open_weights": false, + "release_date": "2025-11-19", + "last_updated": "2025-11-19", "type": "chat" }, { - "id": "qwen/qwen-turbo", - "name": "Qwen Turbo", - "display_name": "Qwen Turbo", + "id": "x-ai/grok-4.1-fast-reasoning", + "name": "X-Ai/Grok 4.1 Fast Reasoning", + "display_name": "X-Ai/Grok 4.1 Fast Reasoning", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 16000 + "context": 20000000, + "output": 2000000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } + "supported": true, + "default": true }, - "attachment": false, + "attachment": true, "open_weights": false, - "knowledge": "2024-04", - "release_date": "2024-11-01", - "last_updated": "2025-04-28", + "release_date": "2025-11-19", + "last_updated": "2025-11-19", "type": "chat" }, { - "id": "qwen/qwen3.6-flash", - "name": "Qwen3.6 Flash", - "display_name": "Qwen3.6 Flash", + "id": "x-ai/grok-4-fast-non-reasoning", + "name": "X-Ai/Grok-4-Fast-Non-Reasoning", + "display_name": "X-Ai/Grok-4-Fast-Non-Reasoning", "modalities": { "input": [ "text", "image", + "audio", "video" ], "output": [ @@ -206484,47 +205134,38 @@ ] }, "limit": { - "context": 1000000, - "output": 64000 + "context": 2000000, + "output": 2000000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } + "supported": false }, "attachment": true, "open_weights": false, - "release_date": "2026-04-27", - "last_updated": "2026-04-27", + "release_date": "2025-12-18", + "last_updated": "2025-12-18", "type": "chat" }, { - "id": "qwen/qwen-flash", - "name": "Qwen Flash", - "display_name": "Qwen Flash", + "id": "x-ai/grok-4-fast-reasoning", + "name": "X-Ai/Grok-4-Fast-Reasoning", + "display_name": "X-Ai/Grok-4-Fast-Reasoning", "modalities": { "input": [ - "text" + "text", + "image", + "audio", + "video" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 32000 + "context": 2000000, + "output": 2000000 }, "temperature": true, "tool_call": true, @@ -206532,68 +205173,58 @@ "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } - }, - "attachment": false, + "attachment": true, "open_weights": false, - "knowledge": "2024-04", - "release_date": "2025-07-28", - "last_updated": "2025-07-28", + "release_date": "2025-12-18", + "last_updated": "2025-12-18", "type": "chat" }, { - "id": "qwen/qwen3-coder-next", - "name": "Qwen3 Coder Next", - "display_name": "Qwen3 Coder Next", + "id": "x-ai/grok-4-fast", + "name": "x-AI/Grok-4-Fast", + "display_name": "x-AI/Grok-4-Fast", "modalities": { "input": [ - "text" + "text", + "image", + "audio", + "video" ], "output": [ "text" ] }, "limit": { - "context": 256000, - "output": 64000 + "context": 2000000, + "output": 2000000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-09", - "release_date": "2026-02-03", - "last_updated": "2026-02-03", + "attachment": true, + "open_weights": false, + "release_date": "2025-09-20", + "last_updated": "2025-09-20", "type": "chat" }, { - "id": "qwen/qwen3.5-35b-a3b", - "name": "Qwen3.5 35B-A3B", - "display_name": "Qwen3.5 35B-A3B", + "id": "x-ai/grok-4.1-fast", + "name": "x-AI/Grok-4.1-Fast", + "display_name": "x-AI/Grok-4.1-Fast", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 256000, - "output": 64000 + "context": 2000000, + "output": 2000000 }, "temperature": true, "tool_call": true, @@ -206601,27 +205232,25 @@ "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } - }, - "attachment": true, - "open_weights": true, - "release_date": "2026-02-23", - "last_updated": "2026-02-23", + "attachment": false, + "open_weights": false, + "release_date": "2025-11-20", + "last_updated": "2025-11-20", "type": "chat" - }, + } + ] + }, + "modular": { + "id": "modular", + "name": "Modular", + "display_name": "Modular", + "api": "https://api.modular.com/v1", + "doc": "https://docs.modular.com/models", + "models": [ { - "id": "qwen/qwen3.5-plus", - "name": "Qwen3.5 Plus", - "display_name": "Qwen3.5 Plus", + "id": "minimax/minimax-m3", + "name": "MiniMax-M3", + "display_name": "MiniMax-M3", "modalities": { "input": [ "text", @@ -206633,8 +205262,8 @@ ] }, "limit": { - "context": 1000000, - "output": 64000 + "context": 1048576, + "output": 512000 }, "temperature": true, "tool_call": true, @@ -206654,29 +205283,27 @@ } }, "attachment": true, - "open_weights": false, - "knowledge": "2025-04", - "release_date": "2026-02-16", - "last_updated": "2026-02-16", + "open_weights": true, + "release_date": "2026-06-01", + "last_updated": "2026-06-01", "type": "chat" }, { - "id": "qwen/qwen3.5-flash", - "name": "Qwen3.5 Flash", - "display_name": "Qwen3.5 Flash", + "id": "google/gemma-4-31b-it", + "name": "Gemma 4 31B IT", + "display_name": "Gemma 4 31B IT", "modalities": { "input": [ "text", - "image", - "video" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 64000 + "context": 262144, + "output": 32768 }, "temperature": true, "tool_call": true, @@ -206696,68 +205323,69 @@ } }, "attachment": true, - "open_weights": false, - "release_date": "2026-02-23", - "last_updated": "2026-02-23", + "open_weights": true, + "knowledge": "2025-01", + "release_date": "2026-04-02", + "last_updated": "2026-04-02", "type": "chat" }, { - "id": "qwen/qwen-plus", - "name": "Qwen Plus", - "display_name": "Qwen Plus", + "id": "deepseek/deepseek-v4.1-flash", + "name": "DeepSeek V4.1 Flash", + "display_name": "DeepSeek V4.1 Flash", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 32000 + "context": 1048576, + "output": 384000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true + "supported": true, + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "low", + "high", + "max" ] } }, - "attachment": false, - "open_weights": false, - "knowledge": "2024-04", - "release_date": "2024-01-25", - "last_updated": "2025-09-11", + "attachment": true, + "open_weights": true, + "knowledge": "2025-05", + "release_date": "2026-09-10", + "last_updated": "2026-09-10", "type": "chat" }, { - "id": "qwen/qwen3.8-max", - "name": "Qwen3.8 Max", - "display_name": "Qwen3.8 Max", + "id": "MiniMaxAI/MiniMax-M3.1-Flash", + "name": "MiniMax-M3.1-Flash", + "display_name": "MiniMax-M3.1-Flash", "modalities": { "input": [ "text", "image", - "video", - "pdf" + "video" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 1048576, + "output": 512000 }, "temperature": true, "tool_call": true, @@ -206768,23 +205396,40 @@ "extra_capabilities": { "reasoning": { "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ "low", "medium", - "xhigh" + "high", + "xhigh", + "max" ] } }, "attachment": true, "open_weights": false, - "release_date": "2026-08-03", - "last_updated": "2026-08-03", + "release_date": "2026-10-19", + "last_updated": "2026-10-19", "type": "chat" - }, + } + ] + }, + "lowrouter": { + "id": "lowrouter", + "name": "LowRouter", + "display_name": "LowRouter", + "api": "https://api.lowrouter.ai/v1", + "doc": "https://lowrouter.ai/docs/models/available", + "models": [ { - "id": "qwen/qwen3.5-122b-a10b", - "name": "Qwen3.5 122B-A10B", - "display_name": "Qwen3.5 122B-A10B", + "id": "auto/openai/gpt-6.1-terra", + "name": "GPT-6.1 Terra", + "display_name": "GPT-6.1 Terra", "modalities": { "input": [ "text", @@ -206795,10 +205440,10 @@ ] }, "limit": { - "context": 256000, - "output": 64000 + "context": 1050000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -206806,67 +205451,79 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "none", + "low", + "medium", + "high", + "xhigh" ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-02-23", - "last_updated": "2026-02-23", + "open_weights": false, + "knowledge": "2026-06-01", + "release_date": "2026-10-26", + "last_updated": "2026-10-26", "type": "chat" }, { - "id": "qwen/qwen3.6-plus", - "name": "Qwen3.6 Plus", - "display_name": "Qwen3.6 Plus", + "id": "auto/openai/gpt-5.4", + "name": "GPT-5.4", + "display_name": "GPT-5.4", "modalities": { "input": [ "text", "image", - "video" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 64000 + "context": 1050000, + "output": 128000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] + "default_enabled": false, + "mode": "effort", + "effort": "none", + "effort_options": [ + "none", + "low", + "medium", + "high", + "xhigh" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, "attachment": true, "open_weights": false, - "knowledge": "2025-04", - "release_date": "2026-04-02", - "last_updated": "2026-04-02", + "knowledge": "2025-08-31", + "release_date": "2026-03-05", + "last_updated": "2026-03-05", "type": "chat" }, { - "id": "qwen/qwen-vl-max", - "name": "Qwen-VL Max", - "display_name": "Qwen-VL Max", + "id": "auto/openai/gpt-5.4-mini", + "name": "GPT-5.4 mini", + "display_name": "GPT-5.4 mini", "modalities": { "input": [ "text", @@ -206877,40 +205534,63 @@ ] }, "limit": { - "context": 128000, - "output": 8000 + "context": 400000, + "output": 128000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": false + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": false, + "mode": "effort", + "effort": "none", + "effort_options": [ + "none", + "low", + "medium", + "high", + "xhigh" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" + } }, "attachment": true, "open_weights": false, - "knowledge": "2024-04", - "release_date": "2024-04-08", - "last_updated": "2025-08-13", + "knowledge": "2025-08-31", + "release_date": "2026-03-17", + "last_updated": "2026-03-17", "type": "chat" }, { - "id": "qwen/qwen3.8-flash", - "name": "Qwen3.8 Flash", - "display_name": "Qwen3.8 Flash", + "id": "auto/openai/gpt-6-sol", + "name": "GPT-6 Sol", + "display_name": "GPT-6 Sol", "modalities": { "input": [ "text", "image", - "video" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 1050000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -206918,32 +205598,41 @@ }, "extra_capabilities": { "reasoning": { - "supported": true + "effort_options": [ + "none", + "low", + "medium", + "high", + "xhigh" + ] } }, "attachment": true, "open_weights": false, - "release_date": "2026-08-26", - "last_updated": "2026-08-26", + "knowledge": "2026-04-20", + "release_date": "2026-09-22", + "last_updated": "2026-09-22", "type": "chat" }, { - "id": "qwen/qwen3.6-max-preview", - "name": "Qwen3.6 Max Preview", - "display_name": "Qwen3.6 Max Preview", + "id": "auto/openai/gpt-5.6-terra", + "name": "GPT-5.6 Terra", + "display_name": "GPT-5.6 Terra", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 256000, - "output": 64000 + "context": 1050000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -206952,40 +205641,52 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] + "default_enabled": true, + "mode": "effort", + "effort": "medium", + "effort_options": [ + "none", + "low", + "medium", + "high", + "xhigh", + "max" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, - "attachment": false, + "attachment": true, "open_weights": false, - "knowledge": "2025-04", - "release_date": "2026-04-20", - "last_updated": "2026-04-20", + "knowledge": "2026-02-16", + "release_date": "2026-07-09", + "last_updated": "2026-07-09", "type": "chat" }, { - "id": "qwen/qwen3.6-27b", - "name": "Qwen3.6 27B", - "display_name": "Qwen3.6 27B", + "id": "auto/openai/gpt-6-astra", + "name": "GPT-6 Astra", + "display_name": "GPT-6 Astra", "modalities": { "input": [ "text", "image", - "video" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 256000, - "output": 64000 + "context": 1050000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -206994,36 +205695,36 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "low", + "medium", + "high", + "xhigh" ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-04-22", - "last_updated": "2026-04-22", + "open_weights": false, + "knowledge": "2026-04-30", + "release_date": "2026-09-04", + "last_updated": "2026-09-04", "type": "chat" }, { - "id": "qwen/qwen3.5-397b-a17b", - "name": "Qwen3.5 397B-A17B", - "display_name": "Qwen3.5 397B-A17B", + "id": "auto/openai/gpt-oss-safeguard-120b", + "name": "GPT OSS Safeguard 120B", + "display_name": "GPT OSS Safeguard 120B", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 256000, - "output": 64000 + "context": 131072, + "output": 32768 }, "temperature": true, "tool_call": true, @@ -207031,83 +205732,109 @@ "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } - }, - "attachment": true, + "attachment": false, "open_weights": true, - "release_date": "2026-02-15", - "last_updated": "2026-02-15", + "release_date": "2025-10-29", + "last_updated": "2025-10-29", "type": "chat" }, { - "id": "qwen/qwen3-coder-plus", - "name": "Qwen3 Coder Plus", - "display_name": "Qwen3 Coder Plus", + "id": "auto/openai/gpt-5.2", + "name": "GPT-5.2", + "display_name": "GPT-5.2", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 64000 + "context": 400000, + "output": 128000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": false }, - "attachment": false, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": false, + "mode": "effort", + "effort": "none", + "effort_options": [ + "none", + "low", + "medium", + "high", + "xhigh" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" + } + }, + "attachment": true, "open_weights": false, - "knowledge": "2025-04", - "release_date": "2025-07-23", - "last_updated": "2025-07-23", + "knowledge": "2025-08-31", + "release_date": "2025-12-11", + "last_updated": "2025-12-11", "type": "chat" }, { - "id": "qwen/qwen-max", - "name": "Qwen Max", - "display_name": "Qwen Max", + "id": "auto/openai/gpt-6.1-sol", + "name": "GPT-6.1 Sol", + "display_name": "GPT-6.1 Sol", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 32000, - "output": 8000 + "context": 1050000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "low", + "medium", + "high", + "xhigh" + ] + } + }, + "attachment": true, "open_weights": false, - "knowledge": "2024-04", - "release_date": "2024-04-03", - "last_updated": "2025-01-25", + "knowledge": "2026-04-30", + "release_date": "2026-09-29", + "last_updated": "2026-09-29", "type": "chat" }, { - "id": "qwen/qwen3.5-27b", - "name": "Qwen3.5 27B", - "display_name": "Qwen3.5 27B", + "id": "auto/openai/gpt-5-nano", + "name": "GPT-5 Nano", + "display_name": "GPT-5 Nano", "modalities": { "input": [ "text", @@ -207118,10 +205845,10 @@ ] }, "limit": { - "context": 256000, - "output": 64000 + "context": 400000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -207130,39 +205857,50 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] + "default_enabled": true, + "mode": "effort", + "effort": "medium", + "effort_options": [ + "minimal", + "low", + "medium", + "high" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, "attachment": true, - "open_weights": true, - "release_date": "2026-02-23", - "last_updated": "2026-02-23", + "open_weights": false, + "knowledge": "2024-05-30", + "release_date": "2025-08-07", + "last_updated": "2025-08-07", "type": "chat" }, { - "id": "qwen/qwen3.7-plus", - "name": "Qwen3.7 Plus", - "display_name": "Qwen3.7 Plus", + "id": "auto/openai/gpt-5.6-luna", + "name": "GPT-5.6 Luna", + "display_name": "GPT-5.6 Luna", "modalities": { "input": [ "text", "image", - "video" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1064000, - "output": 64000 + "context": 1050000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -207170,61 +205908,103 @@ }, "extra_capabilities": { "reasoning": { - "supported": true + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", + "effort_options": [ + "none", + "low", + "medium", + "high", + "xhigh", + "max" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, "attachment": true, "open_weights": false, - "knowledge": "2025-04", - "release_date": "2026-06-02", - "last_updated": "2026-06-02", + "knowledge": "2026-02-16", + "release_date": "2026-07-09", + "last_updated": "2026-07-09", "type": "chat" }, { - "id": "qwen/qwen3-coder-flash", - "name": "Qwen3 Coder Flash", - "display_name": "Qwen3 Coder Flash", + "id": "auto/openai/gpt-5", + "name": "GPT-5", + "display_name": "GPT-5", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 64000 + "context": 400000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", + "effort_options": [ + "minimal", + "low", + "medium", + "high" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" + } + }, + "attachment": true, "open_weights": false, - "knowledge": "2025-04", - "release_date": "2025-07-28", - "last_updated": "2025-07-28", + "knowledge": "2024-09-30", + "release_date": "2025-08-07", + "last_updated": "2025-08-07", "type": "chat" }, { - "id": "bailian/qwen3.7-max", - "name": "Qwen3.7 Max", - "display_name": "Qwen3.7 Max", + "id": "auto/openai/gpt-5-mini", + "name": "GPT-5 Mini", + "display_name": "GPT-5 Mini", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 400000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -207232,19 +206012,36 @@ }, "extra_capabilities": { "reasoning": { - "supported": true + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", + "effort_options": [ + "minimal", + "low", + "medium", + "high" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, - "attachment": false, + "attachment": true, "open_weights": false, - "release_date": "2026-05-21", - "last_updated": "2026-05-21", + "knowledge": "2024-05-30", + "release_date": "2025-08-07", + "last_updated": "2025-08-07", "type": "chat" }, { - "id": "bailian/qwen3-max", - "name": "Qwen3 Max", - "display_name": "Qwen3 Max", + "id": "auto/openai/gpt-oss-safeguard-20b", + "name": "GPT OSS Safeguard 20B", + "display_name": "GPT OSS Safeguard 20B", "modalities": { "input": [ "text" @@ -207254,8 +206051,8 @@ ] }, "limit": { - "context": 262144, - "output": 65536 + "context": 131072, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -207263,33 +206060,20 @@ "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } - }, "attachment": false, - "open_weights": false, - "knowledge": "2025-04", - "release_date": "2025-09-23", - "last_updated": "2025-09-23", + "open_weights": true, + "release_date": "2025-10-29", + "last_updated": "2025-10-29", "type": "chat" }, { - "id": "bailian/qwen3.8-max-0902", - "name": "Qwen3.8 Max 0902", - "display_name": "Qwen3.8 Max 0902", + "id": "auto/openai/gpt-4o-mini", + "name": "GPT-4o mini", + "display_name": "GPT-4o mini", "modalities": { "input": [ "text", "image", - "video", "pdf" ], "output": [ @@ -207297,47 +206081,36 @@ ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 128000, + "output": 16384 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "low", - "medium", - "xhigh" - ] - } + "supported": false }, "attachment": true, "open_weights": false, - "release_date": "2026-09-02", - "last_updated": "2026-09-02", + "knowledge": "2023-09", + "release_date": "2024-07-18", + "last_updated": "2024-07-18", "type": "chat" }, { - "id": "bailian/qwen3.8-27b", - "name": "Qwen3.8 27B", - "display_name": "Qwen3.8 27B", + "id": "auto/openai/gpt-oss-120b", + "name": "GPT OSS 120B", + "display_name": "GPT OSS 120B", "modalities": { "input": [ - "text", - "image", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1131072, - "output": 131072 + "context": 131072, + "output": 32768 }, "temperature": true, "tool_call": true, @@ -207345,19 +206118,32 @@ "supported": true, "default": true }, - "attachment": true, + "extra_capabilities": { + "reasoning": { + "supported": true, + "effort_options": [ + "low", + "medium", + "high" + ] + } + }, + "attachment": false, "open_weights": true, - "release_date": "2026-08-14", - "last_updated": "2026-08-14", + "knowledge": "2024-06", + "release_date": "2025-08-05", + "last_updated": "2025-08-05", "type": "chat" }, { - "id": "bailian/qwen-turbo", - "name": "Qwen Turbo", - "display_name": "Qwen Turbo", + "id": "auto/openai/gpt-4o", + "name": "GPT-4o", + "display_name": "GPT-4o", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" @@ -207370,45 +206156,34 @@ "temperature": true, "tool_call": true, "reasoning": { - "supported": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } + "supported": false }, - "attachment": false, + "attachment": true, "open_weights": false, - "knowledge": "2024-04", - "release_date": "2024-11-01", - "last_updated": "2025-04-28", + "knowledge": "2023-09", + "release_date": "2024-05-13", + "last_updated": "2024-08-06", "type": "chat" }, { - "id": "bailian/qwen3.6-flash", - "name": "Qwen3.6 Flash", - "display_name": "Qwen3.6 Flash", + "id": "auto/openai/gpt-5.6-sol", + "name": "GPT-5.6 Sol", + "display_name": "GPT-5.6 Sol", "modalities": { "input": [ "text", "image", - "video" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 65536 + "context": 1050000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -207417,37 +206192,52 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] + "default_enabled": true, + "mode": "effort", + "effort": "medium", + "effort_options": [ + "none", + "low", + "medium", + "high", + "xhigh", + "max" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, "attachment": true, "open_weights": false, - "release_date": "2026-04-27", - "last_updated": "2026-04-27", + "knowledge": "2026-02-16", + "release_date": "2026-07-09", + "last_updated": "2026-07-09", "type": "chat" }, { - "id": "bailian/qwen-flash", - "name": "Qwen Flash", - "display_name": "Qwen Flash", + "id": "auto/openai/gpt-5.5", + "name": "GPT-5.5", + "display_name": "GPT-5.5", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 32768 + "context": 1050000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -207456,109 +206246,110 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] + "default_enabled": true, + "mode": "effort", + "effort": "medium", + "effort_options": [ + "low", + "medium", + "high", + "xhigh" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, - "attachment": false, + "attachment": true, "open_weights": false, - "knowledge": "2024-04", - "release_date": "2025-07-28", - "last_updated": "2025-07-28", + "knowledge": "2025-12-01", + "release_date": "2026-04-23", + "last_updated": "2026-04-23", "type": "chat" }, { - "id": "bailian/qwen3-coder-next", - "name": "Qwen3 Coder Next", - "display_name": "Qwen3 Coder Next", + "id": "auto/openai/gpt-4.1-mini", + "name": "GPT-4.1 mini", + "display_name": "GPT-4.1 mini", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 65536 + "context": 1047576, + "output": 32768 }, "temperature": true, "tool_call": true, "reasoning": { "supported": false }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-09", - "release_date": "2026-02-03", - "last_updated": "2026-02-03", + "attachment": true, + "open_weights": false, + "knowledge": "2024-04", + "release_date": "2025-04-14", + "last_updated": "2025-04-14", "type": "chat" }, { - "id": "bailian/qwen3.5-35b-a3b", - "name": "Qwen3.5 35B-A3B", - "display_name": "Qwen3.5 35B-A3B", + "id": "auto/openai/gpt-4.1", + "name": "GPT-4.1", + "display_name": "GPT-4.1", "modalities": { "input": [ "text", "image", - "video" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 65536 + "context": 1047576, + "output": 32768 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } + "supported": false }, "attachment": true, - "open_weights": true, - "release_date": "2026-02-23", - "last_updated": "2026-02-23", + "open_weights": false, + "knowledge": "2024-04", + "release_date": "2025-04-14", + "last_updated": "2025-04-14", "type": "chat" }, { - "id": "bailian/qwen3.5-plus", - "name": "Qwen3.5 Plus", - "display_name": "Qwen3.5 Plus", + "id": "auto/openai/gpt-6-luna", + "name": "GPT-6 Luna", + "display_name": "GPT-6 Luna", "modalities": { "input": [ "text", "image", - "video" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 65536 + "context": 1050000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -207566,41 +206357,41 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "none", + "low", + "medium", + "high", + "xhigh" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-04", - "release_date": "2026-02-16", - "last_updated": "2026-02-16", + "knowledge": "2026-05-18", + "release_date": "2026-09-22", + "last_updated": "2026-09-22", "type": "chat" }, { - "id": "bailian/qwen3.5-flash", - "name": "Qwen3.5 Flash", - "display_name": "Qwen3.5 Flash", + "id": "auto/openai/o3", + "name": "o3", + "display_name": "o3", "modalities": { "input": [ "text", "image", - "video" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 65536 + "context": 200000, + "output": 100000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -207609,24 +206400,28 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] + "default_enabled": true, + "mode": "effort", + "effort": "medium", + "effort_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, "attachment": true, "open_weights": false, - "release_date": "2026-02-23", - "last_updated": "2026-02-23", + "knowledge": "2024-05", + "release_date": "2025-04-16", + "last_updated": "2025-04-16", "type": "chat" }, { - "id": "bailian/qwen-plus", - "name": "Qwen Plus", - "display_name": "Qwen Plus", + "id": "auto/openai/gpt-oss-20b", + "name": "GPT OSS 20B", + "display_name": "GPT OSS 20B", "modalities": { "input": [ "text" @@ -207636,77 +206431,65 @@ ] }, "limit": { - "context": 1000000, + "context": 131072, "output": 32768 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true + "supported": true, + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "low", + "medium", + "high" ] } }, "attachment": false, - "open_weights": false, - "knowledge": "2024-04", - "release_date": "2024-01-25", - "last_updated": "2025-09-11", + "open_weights": true, + "knowledge": "2024-06", + "release_date": "2025-08-05", + "last_updated": "2025-08-05", "type": "chat" }, { - "id": "bailian/qwen3.8-max", - "name": "Qwen3.8 Max", - "display_name": "Qwen3.8 Max", + "id": "auto/mistralai/mistral-large-2512", + "name": "Mistral Large 3", + "display_name": "Mistral Large 3", "modalities": { "input": [ "text", - "image", - "video", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 262144, + "output": 262144 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "effort_options": [ - "low", - "medium", - "xhigh" - ] - } + "supported": false }, "attachment": true, - "open_weights": false, - "release_date": "2026-08-03", - "last_updated": "2026-08-03", + "open_weights": true, + "knowledge": "2024-11", + "release_date": "2025-12-02", + "last_updated": "2025-12-02", "type": "chat" }, { - "id": "bailian/qwen3.5-122b-a10b", - "name": "Qwen3.5 122B-A10B", - "display_name": "Qwen3.5 122B-A10B", + "id": "auto/mistralai/ministral-14b-2512", + "name": "Ministral 3 14B", + "display_name": "Ministral 3 14B", "modalities": { "input": [ "text", @@ -207717,49 +206500,36 @@ ] }, "limit": { - "context": 256000, - "output": 64000 + "context": 262144, + "output": 262144 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } + "supported": false }, "attachment": true, "open_weights": true, - "release_date": "2026-02-23", - "last_updated": "2026-02-23", + "release_date": "2025-12-02", + "last_updated": "2025-12-02", "type": "chat" }, { - "id": "bailian/qwen3.6-plus", - "name": "Qwen3.6 Plus", - "display_name": "Qwen3.6 Plus", + "id": "auto/mistralai/mistral-medium-3.5", + "name": "Mistral Medium 3.5", + "display_name": "Mistral Medium 3.5", "modalities": { "input": [ "text", - "image", - "video" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 65536 + "context": 262144, + "output": 262144 }, "temperature": true, "tool_call": true, @@ -207769,37 +206539,32 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "none", + "high" ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2025-04", - "release_date": "2026-04-02", - "last_updated": "2026-04-02", + "open_weights": true, + "release_date": "2026-04-29", + "last_updated": "2026-04-29", "type": "chat" }, { - "id": "bailian/qwen-vl-max", - "name": "Qwen-VL Max", - "display_name": "Qwen-VL Max", + "id": "auto/mistralai/codestral-2508", + "name": "Codestral 25.08", + "display_name": "Codestral 25.08", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 131072, + "context": 128000, "output": 8192 }, "temperature": true, @@ -207807,55 +206572,48 @@ "reasoning": { "supported": false }, - "attachment": true, + "attachment": false, "open_weights": false, - "knowledge": "2024-04", - "release_date": "2024-04-08", - "last_updated": "2025-08-13", + "release_date": "2025-07-30", + "last_updated": "2025-07-30", "type": "chat" }, { - "id": "bailian/qwen3.8-flash", - "name": "Qwen3.8 Flash", - "display_name": "Qwen3.8 Flash", + "id": "auto/mistralai/ministral-8b-2512", + "name": "Ministral 3 8B", + "display_name": "Ministral 3 8B", "modalities": { "input": [ "text", - "image", - "video" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 262144, + "output": 262144 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true - } + "supported": false }, "attachment": true, - "open_weights": false, - "release_date": "2026-08-26", - "last_updated": "2026-08-26", + "open_weights": true, + "release_date": "2025-12-02", + "last_updated": "2025-12-02", "type": "chat" }, { - "id": "bailian/qwen3.6-max-preview", - "name": "Qwen3.6 Max Preview", - "display_name": "Qwen3.6 Max Preview", + "id": "auto/mistralai/ministral-3b-2512", + "name": "Ministral 3 3B", + "display_name": "Ministral 3 3B", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" @@ -207863,49 +206621,35 @@ }, "limit": { "context": 262144, - "output": 65536 + "output": 262144 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } + "supported": false }, - "attachment": false, - "open_weights": false, - "knowledge": "2025-04", - "release_date": "2026-04-20", - "last_updated": "2026-04-20", + "attachment": true, + "open_weights": true, + "release_date": "2025-12-02", + "last_updated": "2025-12-02", "type": "chat" }, { - "id": "bailian/qwen3.6-27b", - "name": "Qwen3.6 27B", - "display_name": "Qwen3.6 27B", + "id": "auto/mistralai/mistral-large-4", + "name": "Mistral Large 4", + "display_name": "Mistral Large 4", "modalities": { "input": [ "text", - "image", - "video" + "image" ], "output": [ "text" ] }, "limit": { - "context": 256000, - "output": 64000 + "context": 524288, + "output": 262144 }, "temperature": true, "tool_call": true, @@ -207915,25 +206659,22 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "none", + "high" ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-04-22", - "last_updated": "2026-04-22", + "open_weights": false, + "release_date": "2026-10-06", + "last_updated": "2026-10-06", "type": "chat" }, { - "id": "bailian/qwen3.5-397b-a17b", - "name": "Qwen3.5 397B-A17B", - "display_name": "Qwen3.5 397B-A17B", + "id": "auto/mistralai/mistral-small-2603", + "name": "Mistral Small 4", + "display_name": "Mistral Small 4", "modalities": { "input": [ "text", @@ -207945,7 +206686,7 @@ }, "limit": { "context": 256000, - "output": 64000 + "output": 256000 }, "temperature": true, "tool_call": true, @@ -207955,25 +206696,32 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "none", + "high" ] } }, "attachment": true, "open_weights": true, - "release_date": "2026-02-15", - "last_updated": "2026-02-15", + "knowledge": "2025-06", + "release_date": "2026-03-16", + "last_updated": "2026-03-16", "type": "chat" - }, + } + ] + }, + "minimax-coding-plan": { + "id": "minimax-coding-plan", + "name": "MiniMax Token Plan (minimax.io)", + "display_name": "MiniMax Token Plan (minimax.io)", + "api": "https://api.minimax.io/anthropic/v1", + "doc": "https://platform.minimax.io/docs/token-plan/intro", + "models": [ { - "id": "bailian/qwen3-coder-plus", - "name": "Qwen3 Coder Plus", - "display_name": "Qwen3 Coder Plus", + "id": "MiniMax-M2.5", + "name": "MiniMax-M2.5", + "display_name": "MiniMax-M2.5", "modalities": { "input": [ "text" @@ -207983,66 +206731,82 @@ ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 204800, + "output": 131072 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true + } }, "attachment": false, - "open_weights": false, - "knowledge": "2025-04", - "release_date": "2025-07-23", - "last_updated": "2025-07-23", + "open_weights": true, + "release_date": "2026-02-12", + "last_updated": "2026-02-12", "type": "chat" }, { - "id": "bailian/qwen-max", - "name": "Qwen Max", - "display_name": "Qwen Max", + "id": "MiniMax-M3.1-Flash", + "name": "MiniMax-M3.1-Flash", + "display_name": "MiniMax-M3.1-Flash", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 32768, - "output": 8192 + "context": 1000000, + "output": 524288 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" + ] + } + }, + "attachment": true, "open_weights": false, - "knowledge": "2024-04", - "release_date": "2024-04-03", - "last_updated": "2025-01-25", + "release_date": "2026-10-19", + "last_updated": "2026-10-19", "type": "chat" }, { - "id": "bailian/qwen3.5-27b", - "name": "Qwen3.5 27B", - "display_name": "Qwen3.5 27B", + "id": "MiniMax-M2.7-highspeed", + "name": "MiniMax-M2.7-highspeed", + "display_name": "MiniMax-M2.7-highspeed", "modalities": { "input": [ - "text", - "image", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 65536 + "context": 204800, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -208061,29 +206825,27 @@ ] } }, - "attachment": true, + "attachment": false, "open_weights": true, - "release_date": "2026-02-23", - "last_updated": "2026-02-23", + "release_date": "2026-03-18", + "last_updated": "2026-03-18", "type": "chat" }, { - "id": "bailian/qwen3.7-plus", - "name": "Qwen3.7 Plus", - "display_name": "Qwen3.7 Plus", + "id": "MiniMax-M2.1", + "name": "MiniMax-M2.1", + "display_name": "MiniMax-M2.1", "modalities": { "input": [ - "text", - "image", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 64000 + "context": 204800, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -208096,17 +206858,16 @@ "supported": true } }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-04", - "release_date": "2026-06-02", - "last_updated": "2026-06-02", + "attachment": false, + "open_weights": true, + "release_date": "2025-12-23", + "last_updated": "2025-12-23", "type": "chat" }, { - "id": "bailian/qwen3-coder-flash", - "name": "Qwen3 Coder Flash", - "display_name": "Qwen3 Coder Flash", + "id": "MiniMax-M2", + "name": "MiniMax-M2", + "display_name": "MiniMax-M2", "modalities": { "input": [ "text" @@ -208116,34 +206877,25 @@ ] }, "limit": { - "context": 1000000, - "output": 65536 + "context": 204800, + "output": 131072 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, "attachment": false, - "open_weights": false, - "knowledge": "2025-04", - "release_date": "2025-07-28", - "last_updated": "2025-07-28", + "open_weights": true, + "release_date": "2025-10-27", + "last_updated": "2025-10-27", "type": "chat" - } - ] - }, - "vancine": { - "id": "vancine", - "name": "Vancine", - "display_name": "Vancine", - "api": "https://vancine.com/v1", - "doc": "https://vancine.com/docs", - "models": [ + }, { - "id": "hy4-preview", - "name": "Hy4 preview", - "display_name": "Hy4 preview", + "id": "MiniMax-M2.7", + "name": "MiniMax-M2.7", + "display_name": "MiniMax-M2.7", "modalities": { "input": [ "text" @@ -208153,8 +206905,8 @@ ] }, "limit": { - "context": 1024000, - "output": 64000 + "context": 204800, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -208165,35 +206917,34 @@ "extra_capabilities": { "reasoning": { "supported": true, - "effort_options": [ - "none", - "high" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, "attachment": false, "open_weights": true, - "release_date": "2026-08-28", - "last_updated": "2026-08-28", + "release_date": "2026-03-18", + "last_updated": "2026-03-18", "type": "chat" }, { - "id": "glm-5.3-flash", - "name": "GLM-5.3-Flash", - "display_name": "GLM-5.3-Flash", + "id": "MiniMax-M2.5-highspeed", + "name": "MiniMax-M2.5-highspeed", + "display_name": "MiniMax-M2.5-highspeed", "modalities": { "input": [ - "text", - "image", - "video", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, + "context": 204800, "output": 131072 }, "temperature": true, @@ -208204,24 +206955,19 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "effort_options": [ - "low", - "high", - "max" - ] + "supported": true } }, - "attachment": true, + "attachment": false, "open_weights": true, - "release_date": "2026-08-26", - "last_updated": "2026-08-26", + "release_date": "2026-02-13", + "last_updated": "2026-02-13", "type": "chat" }, { - "id": "kimi-k3", - "name": "Kimi K3", - "display_name": "Kimi K3", + "id": "MiniMax-M3.1-Flash-Preview", + "name": "MiniMax-M3.1-Flash-Preview", + "display_name": "MiniMax-M3.1-Flash-Preview", "modalities": { "input": [ "text", @@ -208233,10 +206979,10 @@ ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 1000000, + "output": 512000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -208244,36 +206990,30 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], "effort_options": [ "low", + "medium", "high", + "xhigh", "max" ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-07-16", - "last_updated": "2026-07-16", + "open_weights": false, + "release_date": "2026-09-27", + "last_updated": "2026-09-27", "type": "chat" }, { - "id": "qwen3.8-max", - "name": "Qwen3.8 Max", - "display_name": "Qwen3.8 Max", + "id": "MiniMax-M3", + "name": "MiniMax-M3", + "display_name": "MiniMax-M3", "modalities": { "input": [ "text", "image", - "video", - "pdf" + "video" ], "output": [ "text" @@ -208281,7 +207021,7 @@ }, "limit": { "context": 1000000, - "output": 131072 + "output": 524288 }, "temperature": true, "tool_call": true, @@ -208291,43 +207031,39 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "effort_options": [ - "low", - "medium", - "xhigh" - ] + "supported": true } }, "attachment": true, - "open_weights": false, - "release_date": "2026-08-03", - "last_updated": "2026-08-03", + "open_weights": true, + "release_date": "2026-06-01", + "last_updated": "2026-06-25", "type": "chat" - }, + } + ] + }, + "novita-ai": { + "id": "novita-ai", + "name": "Novita AI", + "display_name": "Novita AI", + "api": "https://api.novita.ai/openai", + "doc": "https://novita.ai/docs/guides/introduction", + "models": [ { - "id": "MiniMax-M3", - "name": "MiniMax-M3", - "display_name": "MiniMax-M3", + "id": "openai/gpt-oss-120b", + "name": "GPT OSS 120B", + "display_name": "GPT OSS 120B", "modalities": { "input": [ - "text", - "image", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 512000 + "context": 131072, + "output": 32768 }, "temperature": true, "tool_call": true, @@ -208343,31 +207079,90 @@ "visibility": "summary", "continuation": [ "thinking_blocks" + ], + "effort_options": [ + "low", + "medium", + "high" ] } }, - "attachment": true, + "attachment": false, "open_weights": true, - "release_date": "2026-06-01", - "last_updated": "2026-06-01", + "knowledge": "2024-06", + "release_date": "2025-08-05", + "last_updated": "2025-08-05", "type": "chat" }, { - "id": "deepseek-v4.1-flash", - "name": "DeepSeek V4.1 Flash", - "display_name": "DeepSeek V4.1 Flash", + "id": "baai/bge-reranker-v2-m3", + "name": "BGE Reranker V2 M3", + "display_name": "BGE Reranker V2 M3", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 384000 + "context": 8000, + "output": 8192 + }, + "temperature": false, + "tool_call": false, + "reasoning": { + "supported": false + }, + "attachment": false, + "open_weights": true, + "release_date": "2024-03-15", + "last_updated": "2024-03-15", + "type": "rerank" + }, + { + "id": "baai/bge-m3", + "name": "BGE M3", + "display_name": "BGE M3", + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 8192, + "output": 8192 + }, + "temperature": false, + "tool_call": false, + "reasoning": { + "supported": false + }, + "attachment": false, + "open_weights": true, + "release_date": "2024-01-30", + "last_updated": "2024-01-30", + "type": "embedding" + }, + { + "id": "nvidia/nemotron-3-nano-30b-a3b", + "name": "Nemotron 3 Nano 30B A3B", + "display_name": "Nemotron 3 Nano 30B A3B", + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 262144, + "output": 32768 }, "temperature": true, "tool_call": true, @@ -208383,29 +207178,53 @@ "visibility": "summary", "continuation": [ "thinking_blocks" - ], - "effort_options": [ - "low", - "high", - "max" ] } }, - "attachment": true, + "attachment": false, "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-09-10", - "last_updated": "2026-09-10", + "knowledge": "2025-06-25", + "release_date": "2025-12-15", + "last_updated": "2025-12-15", "type": "chat" }, { - "id": "qwen3.8-flash", - "name": "Qwen3.8 Flash", - "display_name": "Qwen3.8 Flash", + "id": "baichuan/baichuan-m2-32b", + "name": "Baichuan M2 32B", + "display_name": "Baichuan M2 32B", + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "output": 131072 + }, + "temperature": true, + "tool_call": false, + "reasoning": { + "supported": true, + "default": true + }, + "attachment": false, + "open_weights": true, + "release_date": "2025-08-13", + "last_updated": "2025-08-13", + "type": "chat" + }, + { + "id": "xiaomimimo/mimo-v2.6-pro", + "name": "MiMo-V2.6-Pro", + "display_name": "MiMo-V2.6-Pro", "modalities": { "input": [ "text", "image", + "audio", "video" ], "output": [ @@ -208413,7 +207232,7 @@ ] }, "limit": { - "context": 1000000, + "context": 1048576, "output": 131072 }, "temperature": true, @@ -208434,15 +207253,15 @@ } }, "attachment": true, - "open_weights": false, - "release_date": "2026-08-26", - "last_updated": "2026-08-26", + "open_weights": true, + "release_date": "2026-09-22", + "last_updated": "2026-09-22", "type": "chat" }, { - "id": "glm-5.3", - "name": "GLM-5.3", - "display_name": "GLM-5.3", + "id": "xiaomimimo/mimo-v2.5-pro", + "name": "MiMo-V2.5-Pro", + "display_name": "MiMo-V2.5-Pro", "modalities": { "input": [ "text" @@ -208452,7 +207271,7 @@ ] }, "limit": { - "context": 1000000, + "context": 1048576, "output": 131072 }, "temperature": true, @@ -208464,42 +207283,38 @@ "extra_capabilities": { "reasoning": { "supported": true, - "effort_options": [ - "low", - "high", - "max" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, "attachment": false, "open_weights": true, - "release_date": "2026-08-14", - "last_updated": "2026-08-14", + "knowledge": "2024-12", + "release_date": "2026-04-22", + "last_updated": "2026-04-22", "type": "chat" - } - ] - }, - "moark": { - "id": "moark", - "name": "Moark", - "display_name": "Moark", - "api": "https://moark.com/v1", - "doc": "https://moark.com/docs/openapi/v1#tag/%E6%96%87%E6%9C%AC%E7%94%9F%E6%88%90", - "models": [ + }, { - "id": "GLM-4.7", - "name": "GLM-4.7", - "display_name": "GLM-4.7", + "id": "xiaomimimo/mimo-v2.6-flash", + "name": "MiMo-V2.6-Flash", + "display_name": "MiMo-V2.6-Flash", "modalities": { "input": [ - "text" + "text", + "image", + "audio", + "video" ], "output": [ "text" ] }, "limit": { - "context": 204800, + "context": 1048576, "output": 131072 }, "temperature": true, @@ -208519,17 +207334,16 @@ ] } }, - "attachment": false, + "attachment": true, "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-12-22", - "last_updated": "2025-12-22", + "release_date": "2026-09-22", + "last_updated": "2026-09-22", "type": "chat" }, { - "id": "kimi-k3", - "name": "Kimi K3", - "display_name": "Kimi K3", + "id": "xiaomimimo/mimo-v2.5", + "name": "MiMo-V2.5", + "display_name": "MiMo-V2.5", "modalities": { "input": [ "text", @@ -208544,7 +207358,7 @@ "context": 1048576, "output": 131072 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -208552,106 +207366,83 @@ }, "extra_capabilities": { "reasoning": { - "supported": true + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] } }, "attachment": true, "open_weights": true, - "release_date": "2026-07-16", - "last_updated": "2026-07-16", + "knowledge": "2024-12", + "release_date": "2026-04-22", + "last_updated": "2026-04-22", "type": "chat" }, { - "id": "Kimi-K2.6", - "name": "Kimi K2.6", - "display_name": "Kimi K2.6", + "id": "meta-llama/llama-3.1-8b-instruct", + "name": "Llama-3.1-8B-Instruct", + "display_name": "Llama-3.1-8B-Instruct", "modalities": { "input": [ - "text", - "image", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 16384, + "output": 16384 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true - } + "supported": false }, - "attachment": true, + "attachment": false, "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-04-20", - "last_updated": "2026-04-20", + "knowledge": "2023-12", + "release_date": "2024-07-23", + "last_updated": "2024-07-23", "type": "chat" }, { - "id": "DeepSeek-V4-Pro", - "name": "DeepSeek V4 Pro", - "display_name": "DeepSeek V4 Pro", + "id": "meta-llama/llama-4-scout-17b-16e-instruct", + "name": "Llama 4 Scout 17B Instruct", + "display_name": "Llama 4 Scout 17B Instruct", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 384000 + "context": 131072, + "output": 131072 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "high", - "effort_options": [ - "low", - "high", - "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." - ] - } + "supported": false }, - "attachment": false, + "attachment": true, "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-04-24", - "last_updated": "2026-04-24", + "knowledge": "2024-08", + "release_date": "2025-04-05", + "last_updated": "2025-04-05", "type": "chat" }, { - "id": "deepseek-v4-flash-0731", - "name": "DeepSeek V4 Flash 0731", - "display_name": "DeepSeek V4 Flash 0731", + "id": "meta-llama/llama-3.3-70b-instruct", + "name": "Llama-3.3-70B-Instruct", + "display_name": "Llama-3.3-70B-Instruct", "modalities": { "input": [ "text" @@ -208661,72 +207452,54 @@ ] }, "limit": { - "context": 1000000, - "output": 384000 + "context": 12288, + "output": 12288 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true - } + "supported": false }, "attachment": false, "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-07-31", - "last_updated": "2026-07-31", + "knowledge": "2023-12", + "release_date": "2024-12-06", + "last_updated": "2024-12-06", "type": "chat" }, { - "id": "Qwen3.6-Flash", - "name": "Qwen3.6 Flash", - "display_name": "Qwen3.6 Flash", + "id": "meta-llama/llama-4-maverick-17b-128e-instruct-fp8", + "name": "Llama 4 Maverick 17B Instruct", + "display_name": "Llama 4 Maverick 17B Instruct", "modalities": { "input": [ "text", - "image", - "video" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 65536 + "context": 1048576, + "output": 8192 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } + "supported": false }, "attachment": true, - "open_weights": false, - "release_date": "2026-04-27", - "last_updated": "2026-04-27", + "open_weights": true, + "knowledge": "2024-08", + "release_date": "2025-04-05", + "last_updated": "2025-04-05", "type": "chat" }, { - "id": "MiniMax-M2.1", - "name": "MiniMax-M2.1", - "display_name": "MiniMax-M2.1", + "id": "meta-llama/llama-3.2-1b-instruct", + "name": "Llama 3.2 1B Instruct", + "display_name": "Llama 3.2 1B Instruct", "modalities": { "input": [ "text" @@ -208736,30 +207509,25 @@ ] }, "limit": { - "context": 204800, - "output": 131072 + "context": 131000, + "output": 32000 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true - } + "supported": false }, "attachment": false, "open_weights": true, - "release_date": "2025-12-23", - "last_updated": "2025-12-23", + "knowledge": "2023-12", + "release_date": "2024-09-25", + "last_updated": "2024-09-25", "type": "chat" }, { - "id": "MiniMax-M2.7", - "name": "MiniMax-M2.7", - "display_name": "MiniMax-M2.7", + "id": "baidu/ernie-4.5-21B-a3b", + "name": "ERNIE 4.5 21B A3B", + "display_name": "ERNIE 4.5 21B A3B", "modalities": { "input": [ "text" @@ -208769,36 +207537,24 @@ ] }, "limit": { - "context": 204800, - "output": 131072 + "context": 120000, + "output": 8000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } + "supported": false }, "attachment": false, "open_weights": true, - "release_date": "2026-03-18", - "last_updated": "2026-03-18", + "release_date": "2025-06-30", + "last_updated": "2025-06-30", "type": "chat" }, { - "id": "DeepSeek-V4-Flash", - "name": "DeepSeek V4 Flash", - "display_name": "DeepSeek V4 Flash", + "id": "baidu/cobuddy", + "name": "CoBuddy", + "display_name": "CoBuddy", "modalities": { "input": [ "text" @@ -208808,8 +207564,8 @@ ] }, "limit": { - "context": 1000000, - "output": 384000 + "context": 131072, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -208820,36 +207576,24 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "high", - "effort_options": [ - "low", - "high", - "max" - ], "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ "thinking_blocks" - ], - "notes": [ - "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." ] } }, "attachment": false, - "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-04-24", - "last_updated": "2026-04-24", + "open_weights": false, + "release_date": "2026-05-06", + "last_updated": "2026-05-06", "type": "chat" }, { - "id": "GLM-5.3", - "name": "GLM-5.3", - "display_name": "GLM-5.3", + "id": "minimax/minimax-m2.5", + "name": "MiniMax-M2.5", + "display_name": "MiniMax-M2.5", "modalities": { "input": [ "text" @@ -208859,8 +207603,8 @@ ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 204800, + "output": 131100 }, "temperature": true, "tool_call": true, @@ -208870,28 +207614,25 @@ }, "extra_capabilities": { "reasoning": { - "supported": true + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] } }, "attachment": false, "open_weights": true, - "release_date": "2026-08-14", - "last_updated": "2026-08-14", + "release_date": "2026-02-12", + "last_updated": "2026-02-12", "type": "chat" - } - ] - }, - "alibaba-coding-plan-cn": { - "id": "alibaba-coding-plan-cn", - "name": "Alibaba Coding Plan (China)", - "display_name": "Alibaba Coding Plan (China)", - "api": "https://coding.dashscope.aliyuncs.com/v1", - "doc": "https://help.aliyun.com/zh/model-studio/coding-plan", - "models": [ + }, { - "id": "glm-4.7", - "name": "GLM-4.7", - "display_name": "GLM-4.7", + "id": "minimax/minimax-m2.7-highspeed", + "name": "MiniMax-M2.7-highspeed", + "display_name": "MiniMax-M2.7-highspeed", "modalities": { "input": [ "text" @@ -208901,8 +207642,8 @@ ] }, "limit": { - "context": 202752, - "output": 16384 + "context": 204800, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -208923,54 +207664,66 @@ }, "attachment": false, "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-12-22", - "last_updated": "2025-12-22", + "release_date": "2026-03-18", + "last_updated": "2026-03-18", "type": "chat" }, { - "id": "qwen3-coder-next", - "name": "Qwen3 Coder Next", - "display_name": "Qwen3 Coder Next", + "id": "minimax/minimax-m3", + "name": "MiniMax-M3", + "display_name": "MiniMax-M3", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 65536 + "context": 1000000, + "output": 131072 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } + }, + "attachment": true, "open_weights": true, - "release_date": "2026-02-03", - "last_updated": "2026-02-03", + "release_date": "2026-06-01", + "last_updated": "2026-06-01", "type": "chat" }, { - "id": "qwen3.5-plus", - "name": "Qwen3.5 Plus", - "display_name": "Qwen3.5 Plus", + "id": "minimax/minimax-m2.7", + "name": "MiniMax-M2.7", + "display_name": "MiniMax-M2.7", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 65536 + "context": 204800, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -208989,29 +207742,29 @@ ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-04", - "release_date": "2026-02-16", - "last_updated": "2026-02-16", + "attachment": false, + "open_weights": true, + "release_date": "2026-03-18", + "last_updated": "2026-03-18", "type": "chat" }, { - "id": "kimi-k2.5", - "name": "Kimi K2.5", - "display_name": "Kimi K2.5", + "id": "minimax/minimax-m3.1-flash", + "name": "MiniMax-M3.1-Flash", + "display_name": "MiniMax-M3.1-Flash", "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 32768 + "context": 1000000, + "output": 524288 }, "temperature": true, "tool_call": true, @@ -209027,20 +207780,26 @@ "visibility": "summary", "continuation": [ "thinking_blocks" + ], + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" ] } }, "attachment": true, - "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-01-27", - "last_updated": "2026-01-27", + "open_weights": false, + "release_date": "2026-10-19", + "last_updated": "2026-10-19", "type": "chat" }, { - "id": "qwen3.6-plus", - "name": "Qwen3.6 Plus", - "display_name": "Qwen3.6 Plus", + "id": "moonshotai/kimi-k3", + "name": "Kimi K3", + "display_name": "Kimi K3", "modalities": { "input": [ "text", @@ -209052,10 +207811,10 @@ ] }, "limit": { - "context": 1000000, - "output": 65536 + "context": 1048576, + "output": 1048576 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -209069,33 +207828,40 @@ "visibility": "summary", "continuation": [ "thinking_blocks" + ], + "effort_options": [ + "none", + "low", + "high", + "max" ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2025-04", - "release_date": "2026-04-02", - "last_updated": "2026-04-02", + "open_weights": true, + "release_date": "2026-07-16", + "last_updated": "2026-07-16", "type": "chat" }, { - "id": "glm-5", - "name": "GLM-5", - "display_name": "GLM-5", + "id": "moonshotai/kimi-k2.7-code", + "name": "Kimi K2.7 Code", + "display_name": "Kimi K2.7 Code", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 202752, - "output": 16384 + "context": 262144, + "output": 262144 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -209112,16 +207878,17 @@ ] } }, - "attachment": false, - "open_weights": false, - "release_date": "2026-02-11", - "last_updated": "2026-02-11", + "attachment": true, + "open_weights": true, + "knowledge": "2025-01", + "release_date": "2026-06-12", + "last_updated": "2026-06-12", "type": "chat" }, { - "id": "MiniMax-M2.5", - "name": "MiniMax-M2.5", - "display_name": "MiniMax-M2.5", + "id": "moonshotai/kimi-k2-thinking", + "name": "Kimi K2 Thinking", + "display_name": "Kimi K2 Thinking", "modalities": { "input": [ "text" @@ -209131,8 +207898,8 @@ ] }, "limit": { - "context": 196608, - "output": 24576 + "context": 262144, + "output": 98304 }, "temperature": true, "tool_call": true, @@ -209153,14 +207920,15 @@ }, "attachment": false, "open_weights": true, - "release_date": "2026-02-12", - "last_updated": "2026-02-12", + "knowledge": "2024-08", + "release_date": "2025-11-06", + "last_updated": "2025-11-06", "type": "chat" }, { - "id": "qwen3-coder-plus", - "name": "Qwen3 Coder Plus", - "display_name": "Qwen3 Coder Plus", + "id": "moonshotai/kimi-k2-0905", + "name": "Kimi K2 Instruct 0905", + "display_name": "Kimi K2 Instruct 0905", "modalities": { "input": [ "text" @@ -209170,8 +207938,8 @@ ] }, "limit": { - "context": 1000000, - "output": 65536 + "context": 262144, + "output": 98304 }, "temperature": true, "tool_call": true, @@ -209180,31 +207948,33 @@ }, "attachment": false, "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-07-23", - "last_updated": "2025-07-23", + "release_date": "2025-09-05", + "last_updated": "2025-09-05", "type": "chat" }, { - "id": "qwen3-max-2026-01-23", - "name": "Qwen3 Max", - "display_name": "Qwen3 Max", + "id": "moonshotai/kimi-k3.1-pro-highspeed", + "name": "Kimi K3.1 Pro Highspeed", + "display_name": "Kimi K3.1 Pro Highspeed", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 32768 + "context": 1048576, + "output": 1048576 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { - "supported": true + "supported": true, + "default": true }, "extra_capabilities": { "reasoning": { @@ -209214,79 +207984,66 @@ "visibility": "summary", "continuation": [ "thinking_blocks" + ], + "effort_options": [ + "low", + "high", + "max" ] } }, - "attachment": false, + "attachment": true, "open_weights": false, - "knowledge": "2025-04", - "release_date": "2026-01-23", - "last_updated": "2026-01-23", + "release_date": "2026-10-23", + "last_updated": "2026-10-23", "type": "chat" }, { - "id": "qwen3.7-plus", - "name": "Qwen3.7 Plus", - "display_name": "Qwen3.7 Plus", + "id": "moonshotai/kimi-k2-instruct", + "name": "Kimi K2 Instruct", + "display_name": "Kimi K2 Instruct", "modalities": { "input": [ - "text", - "image", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 64000 + "context": 131072, + "output": 98304 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true - } + "supported": false }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-04", - "release_date": "2026-06-02", - "last_updated": "2026-06-02", + "attachment": false, + "open_weights": true, + "release_date": "2025-07-11", + "last_updated": "2025-07-11", "type": "chat" - } - ] - }, - "engy": { - "id": "engy", - "name": "engy", - "display_name": "engy", - "api": "https://api.engy.ai/v1", - "doc": "https://engy.ai/pricing", - "models": [ + }, { - "id": "glm-5.3-flash", - "name": "GLM-5.3-Flash", - "display_name": "GLM-5.3-Flash", + "id": "moonshotai/kimi-k3.1-pro", + "name": "Kimi K3.1 Pro", + "display_name": "Kimi K3.1 Pro", "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 327680, - "output": 32768 + "context": 1048576, + "output": 1048576 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -209309,29 +208066,30 @@ } }, "attachment": true, - "open_weights": true, - "release_date": "2026-08-26", - "last_updated": "2026-08-26", + "open_weights": false, + "release_date": "2026-10-23", + "last_updated": "2026-10-23", "type": "chat" }, { - "id": "qwen3.8-27b", - "name": "Qwen3.8 27B", - "display_name": "Qwen3.8 27B", + "id": "moonshotai/kimi-k2.5", + "name": "Kimi K2.5", + "display_name": "Kimi K2.5", "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 1001536, - "output": 65536 + "context": 262144, + "output": 262144 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -209345,36 +208103,33 @@ "visibility": "summary", "continuation": [ "thinking_blocks" - ], - "effort_options": [ - "low", - "medium", - "xhigh" ] } }, "attachment": true, "open_weights": true, - "release_date": "2026-08-14", - "last_updated": "2026-08-14", + "knowledge": "2025-01", + "release_date": "2026-01-27", + "last_updated": "2026-01-27", "type": "chat" }, { - "id": "deepseek-v4.1-flash-1017", - "name": "DeepSeek V4.1 Flash 1017", - "display_name": "DeepSeek V4.1 Flash 1017", + "id": "moonshotai/kimi-k2.6", + "name": "Kimi K2.6", + "display_name": "Kimi K2.6", "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 262144, + "output": 262144 }, "temperature": true, "tool_call": true, @@ -209390,37 +208145,61 @@ "visibility": "summary", "continuation": [ "thinking_blocks" - ], - "effort_options": [ - "low", - "high", - "max" ] } }, "attachment": true, "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-10-17", - "last_updated": "2026-10-17", + "knowledge": "2025-01", + "release_date": "2026-04-20", + "last_updated": "2026-04-20", "type": "chat" }, { - "id": "kimi-k3", - "name": "Kimi K3", - "display_name": "Kimi K3", + "id": "zai-org/autoglm-phone-9b-multilingual", + "name": "AutoGLM Phone 9B Multilingual", + "display_name": "AutoGLM Phone 9B Multilingual", + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 65536, + "output": 65536 + }, + "temperature": true, + "tool_call": false, + "reasoning": { + "supported": false + }, + "attachment": true, + "open_weights": true, + "release_date": "2025-12-10", + "last_updated": "2025-12-10", + "type": "chat" + }, + { + "id": "zai-org/glm-4.6v", + "name": "GLM-4.6V", + "display_name": "GLM-4.6V", "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 1113088, - "output": 65536 + "context": 131072, + "output": 32768 }, "temperature": true, "tool_call": true, @@ -209441,18 +208220,20 @@ }, "attachment": true, "open_weights": true, - "release_date": "2026-07-16", - "last_updated": "2026-07-16", + "knowledge": "2025-04", + "release_date": "2025-12-08", + "last_updated": "2025-12-08", "type": "chat" }, { - "id": "deepseek-v4.1-flash", - "name": "DeepSeek V4.1 Flash", - "display_name": "DeepSeek V4.1 Flash", + "id": "zai-org/glm-5.3-flash", + "name": "GLM-5.3-Flash", + "display_name": "GLM-5.3-Flash", "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ "text" @@ -209460,7 +208241,7 @@ }, "limit": { "context": 1048576, - "output": 65536 + "output": 131072 }, "temperature": true, "tool_call": true, @@ -209486,15 +208267,14 @@ }, "attachment": true, "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-09-10", - "last_updated": "2026-09-10", + "release_date": "2026-08-26", + "last_updated": "2026-08-26", "type": "chat" }, { - "id": "deepseek-v4-flash-0731", - "name": "DeepSeek V4 Flash 0731", - "display_name": "DeepSeek V4 Flash 0731", + "id": "zai-org/glm-4.7", + "name": "GLM-4.7", + "display_name": "GLM-4.7", "modalities": { "input": [ "text" @@ -209504,8 +208284,8 @@ ] }, "limit": { - "context": 1048576, - "output": 128000 + "context": 204800, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -209521,25 +208301,20 @@ "visibility": "summary", "continuation": [ "thinking_blocks" - ], - "effort_options": [ - "low", - "high", - "max" ] } }, "attachment": false, "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-07-31", - "last_updated": "2026-07-31", + "knowledge": "2025-04", + "release_date": "2025-12-22", + "last_updated": "2025-12-22", "type": "chat" }, { - "id": "qwen3.6-35b-a3b", - "name": "Qwen3.6 35B-A3B", - "display_name": "Qwen3.6 35B-A3B", + "id": "zai-org/glm-4.6", + "name": "GLM-4.6", + "display_name": "GLM-4.6", "modalities": { "input": [ "text" @@ -209549,8 +208324,8 @@ ] }, "limit": { - "context": 208192, - "output": 8192 + "context": 204800, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -209571,14 +208346,15 @@ }, "attachment": false, "open_weights": true, - "release_date": "2026-04-17", - "last_updated": "2026-04-17", + "knowledge": "2025-04", + "release_date": "2025-09-30", + "last_updated": "2025-09-30", "type": "chat" }, { - "id": "glm-5.3", - "name": "GLM-5.3", - "display_name": "GLM-5.3", + "id": "zai-org/glm-4.5-air", + "name": "GLM-4.5-Air", + "display_name": "GLM-4.5-Air", "modalities": { "input": [ "text" @@ -209588,8 +208364,8 @@ ] }, "limit": { - "context": 327680, - "output": 32768 + "context": 131072, + "output": 98304 }, "temperature": true, "tool_call": true, @@ -209605,24 +208381,20 @@ "visibility": "summary", "continuation": [ "thinking_blocks" - ], - "effort_options": [ - "low", - "high", - "max" ] } }, "attachment": false, "open_weights": true, - "release_date": "2026-08-14", - "last_updated": "2026-08-14", + "knowledge": "2025-04", + "release_date": "2025-07-28", + "last_updated": "2025-07-28", "type": "chat" }, { - "id": "glm-5.2", - "name": "GLM-5.2", - "display_name": "GLM-5.2", + "id": "zai-org/glm-5.3", + "name": "GLM-5.3", + "display_name": "GLM-5.3", "modalities": { "input": [ "text" @@ -209632,8 +208404,8 @@ ] }, "limit": { - "context": 262144, - "output": 32768 + "context": 1048576, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -209651,6 +208423,8 @@ "thinking_blocks" ], "effort_options": [ + "none", + "low", "high", "max" ] @@ -209658,38 +208432,29 @@ }, "attachment": false, "open_weights": true, - "release_date": "2026-06-13", - "last_updated": "2026-06-13", + "release_date": "2026-08-14", + "last_updated": "2026-08-14", "type": "chat" - } - ] - }, - "freemodel": { - "id": "freemodel", - "name": "FreeModel", - "display_name": "FreeModel", - "api": "https://cc.freemodel.dev/v1", - "doc": "https://freemodel.dev", - "models": [ + }, { - "id": "gpt-5.5", - "name": "GPT-5.5", - "display_name": "GPT-5.5", + "id": "zai-org/glm-4.5v", + "name": "GLM-4.5V", + "display_name": "GLM-4.5V", "modalities": { "input": [ "text", "image", - "pdf" + "video" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 65536, + "output": 16384 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -209698,328 +208463,246 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", - "effort_options": [ - "low", - "medium", - "high", - "xhigh" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2025-12-01", - "release_date": "2026-04-23", - "last_updated": "2026-04-23", + "open_weights": true, + "knowledge": "2025-04", + "release_date": "2025-08-11", + "last_updated": "2025-08-11", "type": "chat" }, { - "id": "gpt-5.4", - "name": "GPT-5.4", - "display_name": "GPT-5.4", + "id": "zai-org/glm-5", + "name": "GLM-5", + "display_name": "GLM-5", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 202800, + "output": 131072 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "none", - "effort_options": [ - "none", - "low", - "medium", - "high", - "xhigh" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-03-05", - "last_updated": "2026-03-05", + "attachment": false, + "open_weights": true, + "release_date": "2026-02-12", + "last_updated": "2026-02-12", "type": "chat" }, { - "id": "claude-opus-4-7", - "name": "Claude Opus 4.7", - "display_name": "Claude Opus 4.7", + "id": "zai-org/glm-4.7-flash", + "name": "GLM-4.7-Flash", + "display_name": "GLM-4.7-Flash", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, + "context": 200000, "output": 128000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "high", - "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" - ], "interleaved": true, "summaries": true, - "visibility": "omitted", + "visibility": "summary", "continuation": [ "thinking_blocks" - ], - "notes": [ - "Claude Opus 4.7 and newer Opus models require thinking.type = \"adaptive\" to enable thinking explicitly.", - "Manual budget_tokens requests return 400 on Claude Opus 4.7 and newer adaptive-only Opus models.", - "task_budget is separate from thinking control and should not be treated as a thinking budget." ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-01-31", - "release_date": "2026-04-16", - "last_updated": "2026-04-16", + "attachment": false, + "open_weights": true, + "knowledge": "2025-04", + "release_date": "2026-01-19", + "last_updated": "2026-01-19", "type": "chat" }, { - "id": "claude-sonnet-4-6", - "name": "Claude Sonnet 4.6", - "display_name": "Claude Sonnet 4.6", + "id": "zai-org/glm-5.1", + "name": "GLM-5.1", + "display_name": "GLM-5.1", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 64000 + "context": 204800, + "output": 131072 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "mixed", - "budget": { - "min": 1024, - "unit": "tokens" - }, - "effort": "high", - "effort_options": [ - "low", - "medium", - "high", - "max" - ], "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ "thinking_blocks" - ], - "notes": [ - "Anthropic recommends adaptive thinking with effort for Claude 4.6; budget_tokens remains a deprecated compatibility path.", - "Anthropic API defaults effort to high; lower effort levels should be chosen per workload." ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-02-17", - "last_updated": "2026-03-13", + "attachment": false, + "open_weights": true, + "release_date": "2026-04-07", + "last_updated": "2026-04-07", "type": "chat" }, { - "id": "claude-opus-4-6", - "name": "Claude Opus 4.6", - "display_name": "Claude Opus 4.6", + "id": "zai-org/glm-5.2", + "name": "GLM-5.2", + "display_name": "GLM-5.2", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 1048576, + "output": 131072 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "mixed", - "budget": { - "min": 1024, - "unit": "tokens" - }, - "effort": "high", - "effort_options": [ - "low", - "medium", - "high", - "max" - ], "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ "thinking_blocks" ], - "notes": [ - "Anthropic recommends adaptive thinking with effort for Claude 4.6; budget_tokens remains a deprecated compatibility path.", - "Anthropic API defaults effort to high; lower effort levels should be chosen per workload." + "effort_options": [ + "none", + "high", + "max" ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-05-31", - "release_date": "2026-02-05", - "last_updated": "2026-03-13", + "attachment": false, + "open_weights": true, + "release_date": "2026-06-13", + "last_updated": "2026-06-13", "type": "chat" }, { - "id": "gpt-5.4-mini", - "name": "GPT-5.4 mini", - "display_name": "GPT-5.4 mini", + "id": "stepfun/step-5-preview", + "name": "Step 5 Preview", + "display_name": "Step 5 Preview", "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 1000000, + "output": 64000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "none", - "effort_options": [ - "none", - "low", - "medium", - "high", - "xhigh" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ], - "verbosity": "medium", - "verbosity_options": [ + "effort_options": [ "low", "medium", "high" - ], - "visibility": "hidden" + ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-03-17", - "last_updated": "2026-03-17", + "open_weights": true, + "release_date": "2026-09-16", + "last_updated": "2026-09-20", "type": "chat" }, { - "id": "gpt-5.3-codex", - "name": "GPT-5.3 Codex", - "display_name": "GPT-5.3 Codex", + "id": "stepfun/step-3.7-flash", + "name": "Step 3.7 Flash", + "display_name": "Step 3.7 Flash", "modalities": { "input": [ "text", "image", - "pdf" + "video" ], "output": [ "text" ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 262144, + "output": 256000 }, "temperature": true, "tool_call": true, @@ -210030,92 +208713,104 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", - "effort_options": [ - "low", - "medium", - "high", - "xhigh" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ], - "verbosity": "medium", - "verbosity_options": [ + "effort_options": [ "low", "medium", "high" - ], - "visibility": "hidden" + ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-02-05", - "last_updated": "2026-02-05", + "open_weights": true, + "knowledge": "2026-03-01", + "release_date": "2026-05-29", + "last_updated": "2026-05-29", "type": "chat" }, { - "id": "claude-haiku-4-5-20251001", - "name": "Claude Haiku 4.5", - "display_name": "Claude Haiku 4.5", + "id": "paddlepaddle/paddleocr-vl", + "name": "PaddleOCR VL", + "display_name": "PaddleOCR VL", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 64000 + "context": 16384, + "output": 16384 + }, + "temperature": true, + "tool_call": false, + "reasoning": { + "supported": false + }, + "attachment": true, + "open_weights": true, + "release_date": "2025-10-16", + "last_updated": "2025-10-16", + "type": "chat" + }, + { + "id": "tencent/hy3", + "name": "Hy3", + "display_name": "Hy3", + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 262144, + "output": 262144 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "budget", - "budget": { - "min": 1024, - "unit": "tokens" - }, "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ "thinking_blocks" ], - "notes": [ - "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", - "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." + "effort_options": [ + "none", + "high" ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-02-28", - "release_date": "2025-10-15", - "last_updated": "2025-10-15", + "attachment": false, + "open_weights": true, + "release_date": "2026-07-06", + "last_updated": "2026-07-06", "type": "chat" }, { - "id": "claude-opus-4-8", - "name": "Claude Opus 4.8", - "display_name": "Claude Opus 4.8", + "id": "tencent/hy4-preview", + "name": "Hy4 preview", + "display_name": "Hy4 preview", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" @@ -210123,66 +208818,53 @@ }, "limit": { "context": 1000000, - "output": 128000 + "output": 64000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "high", - "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" - ], "interleaved": true, "summaries": true, - "visibility": "omitted", + "visibility": "summary", "continuation": [ "thinking_blocks" ], - "notes": [ - "Claude Opus 4.7 and newer Opus models require thinking.type = \"adaptive\" to enable thinking explicitly.", - "Manual budget_tokens requests return 400 on Claude Opus 4.7 and newer adaptive-only Opus models.", - "task_budget is separate from thinking control and should not be treated as a thinking budget." + "effort_options": [ + "none", + "high" ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-01", - "release_date": "2026-05-28", - "last_updated": "2026-05-28", + "attachment": false, + "open_weights": true, + "release_date": "2026-08-28", + "last_updated": "2026-08-28", "type": "chat" }, { - "id": "claude-fable-5", - "name": "Claude Fable 5", - "display_name": "Claude Fable 5", + "id": "google/gemma-4-26b-a4b-it", + "name": "Gemma 4 26B A4B IT", + "display_name": "Gemma 4 26B A4B IT", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 262144, + "output": 131072 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -210191,169 +208873,96 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "high", - "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" - ], "interleaved": true, "summaries": true, - "visibility": "omitted", + "visibility": "summary", "continuation": [ "thinking_blocks" - ], - "notes": [ - "Adaptive thinking is always on for Claude Fable 5 and Claude Mythos 5; thinking.type = \"disabled\" is rejected.", - "Manual budget_tokens requests return 400 on Claude Fable 5 and Claude Mythos 5.", - "thinking.display defaults to omitted; set display to summarized to receive readable thinking summaries." ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2026-01-31", - "release_date": "2026-06-09", - "last_updated": "2026-06-09", + "open_weights": true, + "release_date": "2026-04-02", + "last_updated": "2026-04-02", "type": "chat" - } - ] - }, - "ai-router": { - "id": "ai-router", - "name": "AI-ROUTER", - "display_name": "AI-ROUTER", - "api": "https://api.ai-router.dev/v1", - "doc": "https://ai-router.dev/openai-compatible-api-gateway/", - "models": [ + }, { - "id": "gpt-5.5", - "name": "GPT-5.5", - "display_name": "GPT-5.5", + "id": "google/gemma-3-12b-it", + "name": "Gemma 3 12B IT", + "display_name": "Gemma 3 12B IT", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 131072, + "output": 8192 }, - "temperature": false, - "tool_call": true, + "temperature": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", - "effort_options": [ - "low", - "medium", - "high", - "xhigh" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" - } + "supported": false }, "attachment": true, - "open_weights": false, - "knowledge": "2025-12-01", - "release_date": "2026-04-23", - "last_updated": "2026-04-23", + "open_weights": true, + "knowledge": "2024-08", + "release_date": "2025-03-12", + "last_updated": "2025-03-12", "type": "chat" }, { - "id": "gpt-5.4", - "name": "GPT-5.4", - "display_name": "GPT-5.4", + "id": "google/gemma-3-27b-it", + "name": "Gemma 3 27B IT", + "display_name": "Gemma 3 27B IT", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 98304, + "output": 16384 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": false - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "none", - "effort_options": [ - "none", - "low", - "medium", - "high", - "xhigh" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" - } + "supported": false }, "attachment": true, - "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-03-05", - "last_updated": "2026-03-05", + "open_weights": true, + "knowledge": "2024-08", + "release_date": "2025-03-12", + "last_updated": "2025-03-12", "type": "chat" }, { - "id": "gpt-5.6-sol", - "name": "GPT-5.6 Sol", - "display_name": "GPT-5.6 Sol", + "id": "google/gemma-4-31b-it", + "name": "Gemma 4 31B IT", + "display_name": "Gemma 4 31B IT", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 262144, + "output": 131072 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -210362,106 +208971,65 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", - "effort_options": [ - "none", - "low", - "medium", - "high", - "xhigh", - "max" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2026-02-16", - "release_date": "2026-07-09", - "last_updated": "2026-07-09", + "open_weights": true, + "knowledge": "2025-01", + "release_date": "2026-04-02", + "last_updated": "2026-04-02", "type": "chat" }, { - "id": "gpt-5.6-terra", - "name": "GPT-5.6 Terra", - "display_name": "GPT-5.6 Terra", + "id": "gryphe/mythomax-l2-13b", + "name": "MythoMax L2 13B", + "display_name": "MythoMax L2 13B", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 4096, + "output": 3200 }, - "temperature": false, - "tool_call": true, + "temperature": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", - "effort_options": [ - "none", - "low", - "medium", - "high", - "xhigh", - "max" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" - } + "supported": false }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-02-16", - "release_date": "2026-07-09", - "last_updated": "2026-07-09", + "attachment": false, + "open_weights": true, + "release_date": "2023-08-10", + "last_updated": "2023-08-10", "type": "chat" }, { - "id": "gpt-5.6-luna", - "name": "GPT-5.6 Luna", - "display_name": "GPT-5.6 Luna", + "id": "apodex/apodex-1.1-mini", + "name": "Apodex 1.1 Mini", + "display_name": "Apodex 1.1 Mini", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 262144, + "output": 262144 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -210470,46 +209038,24 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", - "effort_options": [ - "none", - "low", - "medium", - "high", - "xhigh", - "max" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-02-16", - "release_date": "2026-07-09", - "last_updated": "2026-07-09", + "attachment": false, + "open_weights": true, + "release_date": "2026-08-24", + "last_updated": "2026-08-24", "type": "chat" - } - ] - }, - "thinkingmachines": { - "id": "thinkingmachines", - "name": "Thinking Machines", - "display_name": "Thinking Machines", - "api": "https://tinker.thinkingmachines.dev/services/tinker-prod/anthropic/api/v1", - "doc": "https://tinker-docs.thinkingmachines.ai/tinker/compatible-apis/anthropic/", - "models": [ + }, { - "id": "zai-org/GLM-5.3-Flash:peft:262144", - "name": "GLM-5.3-Flash", - "display_name": "GLM-5.3-Flash", + "id": "microsoft/wizardlm-2-8x22b", + "name": "WizardLM 2 8x22B", + "display_name": "WizardLM 2 8x22B", "modalities": { "input": [ "text" @@ -210519,65 +209065,64 @@ ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 65535, + "output": 8000 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true + "supported": false }, "attachment": false, "open_weights": true, - "release_date": "2026-08-26", - "last_updated": "2026-08-26", + "release_date": "2024-04-15", + "last_updated": "2024-04-15", "type": "chat" }, { - "id": "zai-org/GLM-5.3:peft:262144", - "name": "GLM-5.3", - "display_name": "GLM-5.3", + "id": "qwen/qwen3-vl-30b-a3b-instruct", + "name": "Qwen3 VL 30B A3B Instruct", + "display_name": "Qwen3 VL 30B A3B Instruct", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 131072, + "output": 32768 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true + "supported": false }, - "attachment": false, + "attachment": true, "open_weights": true, - "release_date": "2026-08-14", - "last_updated": "2026-08-14", + "release_date": "2025-10-04", + "last_updated": "2025-10-04", "type": "chat" }, { - "id": "Qwen/Qwen3.5-397B-A17B:peft:262144", - "name": "Qwen3.5 397B-A17B", - "display_name": "Qwen3.5 397B-A17B", + "id": "qwen/qwen3.8-2.4t-a95b", + "name": "Qwen3.8 2.4T A95B", + "display_name": "Qwen3.8 2.4T A95B", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 1000000, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -210593,30 +209138,63 @@ "visibility": "summary", "continuation": [ "thinking_blocks" + ], + "effort_options": [ + "low", + "medium", + "xhigh" ] } }, - "attachment": true, + "attachment": false, "open_weights": true, - "release_date": "2026-02-15", - "last_updated": "2026-02-15", + "release_date": "2026-08-12", + "last_updated": "2026-08-12", "type": "chat" }, { - "id": "Qwen/Qwen3.5-397B-A17B", - "name": "Qwen3.5 397B-A17B", - "display_name": "Qwen3.5 397B-A17B", + "id": "qwen/qwen3-reranker-8b", + "name": "Qwen3 Reranker 8B", + "display_name": "Qwen3 Reranker 8B", + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 32768, + "output": 8192 + }, + "temperature": false, + "tool_call": false, + "reasoning": { + "supported": false + }, + "attachment": false, + "open_weights": true, + "release_date": "2025-06-05", + "last_updated": "2025-06-05", + "type": "rerank" + }, + { + "id": "qwen/qwen3.5-27b", + "name": "Qwen3.5 27B", + "display_name": "Qwen3.5 27B", "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 65536, + "context": 262144, "output": 65536 }, "temperature": true, @@ -210638,25 +209216,24 @@ }, "attachment": true, "open_weights": true, - "release_date": "2026-02-15", - "last_updated": "2026-02-15", + "release_date": "2026-02-23", + "last_updated": "2026-02-23", "type": "chat" }, { - "id": "Qwen/Qwen3.6-27B", - "name": "Qwen3.6 27B", - "display_name": "Qwen3.6 27B", + "id": "qwen/qwen3.7-max", + "name": "Qwen3.7 Max", + "display_name": "Qwen3.7 Max", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 65536, + "context": 1000000, "output": 65536 }, "temperature": true, @@ -210676,27 +209253,29 @@ ] } }, - "attachment": true, - "open_weights": true, - "release_date": "2026-04-22", - "last_updated": "2026-04-22", + "attachment": false, + "open_weights": false, + "release_date": "2026-05-21", + "last_updated": "2026-05-21", "type": "chat" }, { - "id": "Qwen/Qwen3-8B", - "name": "Qwen3 8B", - "display_name": "Qwen3 8B", + "id": "qwen/qwen3.8-flash", + "name": "Qwen3.8 Flash", + "display_name": "Qwen3.8 Flash", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 32768, - "output": 32768 + "context": 1000000, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -210712,30 +209291,36 @@ "visibility": "summary", "continuation": [ "thinking_blocks" + ], + "effort_options": [ + "low", + "medium", + "xhigh" ] } }, - "attachment": false, - "open_weights": true, - "release_date": "2025-04-29", - "last_updated": "2025-04-29", + "attachment": true, + "open_weights": false, + "release_date": "2026-08-26", + "last_updated": "2026-08-26", "type": "chat" }, { - "id": "Qwen/Qwen3.6-35B-A3B", + "id": "qwen/qwen3.6-35b-a3b", "name": "Qwen3.6 35B-A3B", "display_name": "Qwen3.6 35B-A3B", "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 65536, + "context": 262144, "output": 65536 }, "temperature": true, @@ -210762,9 +209347,9 @@ "type": "chat" }, { - "id": "Qwen/Qwen3.5-35B-A3B-Base", - "name": "Qwen3.5 35B-A3B Base", - "display_name": "Qwen3.5 35B-A3B Base", + "id": "qwen/qwen3-embedding-8b", + "name": "Qwen3 Embedding 8B", + "display_name": "Qwen3 Embedding 8B", "modalities": { "input": [ "text" @@ -210774,38 +209359,30 @@ ] }, "limit": { - "context": 65536, - "output": 65536 + "context": 32768, + "output": 8192 }, - "temperature": true, + "temperature": false, "tool_call": false, "reasoning": { - "supported": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } + "supported": false }, "attachment": false, "open_weights": true, - "release_date": "2026-02-24", - "last_updated": "2026-02-24", - "type": "chat" + "release_date": "2025-06-05", + "last_updated": "2025-06-05", + "type": "embedding" }, - { - "id": "Qwen/Qwen3.5-9B-Base", - "name": "Qwen3.5 9B Base", - "display_name": "Qwen3.5 9B Base", + { + "id": "qwen/qwen3-omni-30b-a3b-thinking", + "name": "Qwen3 Omni 30B A3B Thinking", + "display_name": "Qwen3 Omni 30B A3B Thinking", "modalities": { "input": [ - "text" + "text", + "image", + "video", + "audio" ], "output": [ "text" @@ -210813,12 +209390,13 @@ }, "limit": { "context": 65536, - "output": 65536 + "output": 16384 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": true + "supported": true, + "default": true }, "extra_capabilities": { "reasoning": { @@ -210831,28 +209409,29 @@ ] } }, - "attachment": false, + "attachment": true, "open_weights": true, - "release_date": "2026-02-26", - "last_updated": "2026-02-26", + "release_date": "2025-09-22", + "last_updated": "2025-09-22", "type": "chat" }, { - "id": "Qwen/Qwen3.8-27B:peft:262144", + "id": "qwen/qwen3.8-27b", "name": "Qwen3.8 27B", "display_name": "Qwen3.8 27B", "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 1000000, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -210860,6 +209439,22 @@ "supported": true, "default": true }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "effort_options": [ + "low", + "medium", + "xhigh" + ] + } + }, "attachment": true, "open_weights": true, "release_date": "2026-08-14", @@ -210867,20 +209462,21 @@ "type": "chat" }, { - "id": "Qwen/Qwen3.5-4B", - "name": "Qwen3.5 4B", - "display_name": "Qwen3.5 4B", + "id": "qwen/qwen3.6-27b", + "name": "Qwen3.6 27B", + "display_name": "Qwen3.6 27B", "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 65536, + "context": 262144, "output": 65536 }, "temperature": true, @@ -210902,25 +209498,26 @@ }, "attachment": true, "open_weights": true, - "release_date": "2026-03-02", - "last_updated": "2026-03-02", + "release_date": "2026-04-22", + "last_updated": "2026-04-22", "type": "chat" }, { - "id": "Qwen/Qwen3.8-27B", - "name": "Qwen3.8 27B", - "display_name": "Qwen3.8 27B", + "id": "qwen/qwen3.5-35b-a3b", + "name": "Qwen3.5 35B-A3B", + "display_name": "Qwen3.5 35B-A3B", "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 65536, + "context": 262144, "output": 65536 }, "temperature": true, @@ -210929,28 +209526,40 @@ "supported": true, "default": true }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } + }, "attachment": true, "open_weights": true, - "release_date": "2026-08-14", - "last_updated": "2026-08-14", + "release_date": "2026-02-23", + "last_updated": "2026-02-23", "type": "chat" }, { - "id": "Qwen/Qwen3.6-35B-A3B:peft:262144", - "name": "Qwen3.6 35B-A3B", - "display_name": "Qwen3.6 35B-A3B", + "id": "qwen/qwen3.8-max", + "name": "Qwen3.8 Max", + "display_name": "Qwen3.8 Max", "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 1000000, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -210966,30 +209575,36 @@ "visibility": "summary", "continuation": [ "thinking_blocks" + ], + "effort_options": [ + "low", + "medium", + "xhigh" ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-04-17", - "last_updated": "2026-04-17", + "open_weights": false, + "release_date": "2026-08-03", + "last_updated": "2026-08-03", "type": "chat" }, { - "id": "Qwen/Qwen3.5-9B", - "name": "Qwen3.5 9B", - "display_name": "Qwen3.5 9B", + "id": "qwen/qwen3.5-397b-a17b", + "name": "Qwen3.5 397B-A17B", + "display_name": "Qwen3.5 397B-A17B", "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 65536, + "context": 262144, "output": 65536 }, "temperature": true, @@ -211011,18 +209626,19 @@ }, "attachment": true, "open_weights": true, - "release_date": "2026-02-23", - "last_updated": "2026-02-23", + "release_date": "2026-02-15", + "last_updated": "2026-02-15", "type": "chat" }, { - "id": "Qwen/Qwen3.5-4B:peft:262144", - "name": "Qwen3.5 4B", - "display_name": "Qwen3.5 4B", + "id": "qwen/qwen3.5-122b-a10b", + "name": "Qwen3.5 122B-A10B", + "display_name": "Qwen3.5 122B-A10B", "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ "text" @@ -211030,7 +209646,7 @@ }, "limit": { "context": 262144, - "output": 262144 + "output": 65536 }, "temperature": true, "tool_call": true, @@ -211038,45 +209654,54 @@ "supported": true, "default": true }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } + }, "attachment": true, "open_weights": true, - "release_date": "2026-03-02", - "last_updated": "2026-03-02", + "release_date": "2026-02-23", + "last_updated": "2026-02-23", "type": "chat" }, { - "id": "nvidia/NVIDIA-Nemotron-3.5-Super-VL-120B-A12B-BF16", - "name": "Nemotron 3.5 Super VL 120B A12B", - "display_name": "Nemotron 3.5 Super VL 120B A12B", + "id": "qwen/qwen-mt-plus", + "name": "Qwen MT Plus", + "display_name": "Qwen MT Plus", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 65536, - "output": 65536 + "context": 16384, + "output": 8192 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true + "supported": false }, - "attachment": true, - "open_weights": true, - "release_date": "2026-10-15", - "last_updated": "2026-10-15", + "attachment": false, + "open_weights": false, + "release_date": "2025-07-24", + "last_updated": "2025-07-24", "type": "chat" }, { - "id": "nvidia/NVIDIA-Nemotron-3-Ultra-550B-A55B-BF16:peft:262144", - "name": "Nemotron 3 Ultra 550B A55B", - "display_name": "Nemotron 3 Ultra 550B A55B", + "id": "minimaxai/minimax-m1-80k", + "name": "MiniMax M1 80K", + "display_name": "MiniMax M1 80K", "modalities": { "input": [ "text" @@ -211086,8 +209711,8 @@ ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 1000000, + "output": 40000 }, "temperature": true, "tool_call": true, @@ -211097,15 +209722,14 @@ }, "attachment": false, "open_weights": true, - "knowledge": "2025-09", - "release_date": "2026-06-04", - "last_updated": "2026-06-04", + "release_date": "2025-06-17", + "last_updated": "2025-06-17", "type": "chat" }, { - "id": "nvidia/NVIDIA-Nemotron-3-Nano-30B-A3B-BF16", - "name": "Nemotron 3 Nano 30B A3B", - "display_name": "Nemotron 3 Nano 30B A3B", + "id": "deepseek/deepseek-r1-0528", + "name": "DeepSeek R1 0528", + "display_name": "DeepSeek R1 0528", "modalities": { "input": [ "text" @@ -211115,8 +209739,8 @@ ] }, "limit": { - "context": 65536, - "output": 65536 + "context": 163840, + "output": 32768 }, "temperature": true, "tool_call": true, @@ -211124,17 +209748,27 @@ "supported": true, "default": true }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } + }, "attachment": false, "open_weights": true, - "knowledge": "2025-06-25", - "release_date": "2025-12-15", - "last_updated": "2025-12-15", + "release_date": "2025-05-28", + "last_updated": "2025-05-28", "type": "chat" }, { - "id": "nvidia/NVIDIA-Nemotron-3.5-Super-VL-120B-A12B-BF16:peft:262144", - "name": "Nemotron 3.5 Super VL 120B A12B", - "display_name": "Nemotron 3.5 Super VL 120B A12B", + "id": "deepseek/deepseek-ocr-2", + "name": "DeepSeek OCR 2", + "display_name": "DeepSeek OCR 2", "modalities": { "input": [ "text", @@ -211145,36 +209779,36 @@ ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 8192, + "output": 8192 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true + "supported": false }, "attachment": true, "open_weights": true, - "release_date": "2026-10-15", - "last_updated": "2026-10-15", + "release_date": "2026-01-27", + "last_updated": "2026-01-27", "type": "chat" }, { - "id": "nvidia/NVIDIA-Nemotron-3.5-Lightning-30B-A3B-BF16", - "name": "Nemotron 3.5 Lightning 30B A3B", - "display_name": "Nemotron 3.5 Lightning 30B A3B", + "id": "deepseek/deepseek-v4.1-flash-1017", + "name": "DeepSeek V4.1 Flash 1017", + "display_name": "DeepSeek V4.1 Flash 1017", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 65536, - "output": 65536 + "context": 1048576, + "output": 393216 }, "temperature": true, "tool_call": true, @@ -211182,46 +209816,33 @@ "supported": true, "default": true }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-09", - "release_date": "2026-08-11", - "last_updated": "2026-08-11", - "type": "chat" - }, - { - "id": "nvidia/NVIDIA-Nemotron-3.5-Lightning-30B-A3B-BF16:peft:262144", - "name": "Nemotron 3.5 Lightning 30B A3B", - "display_name": "Nemotron 3.5 Lightning 30B A3B", - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 262144, - "output": 262144 - }, - "temperature": true, - "tool_call": true, - "reasoning": { - "supported": true, - "default": true + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "effort_options": [ + "low", + "high", + "max" + ] + } }, - "attachment": false, + "attachment": true, "open_weights": true, - "knowledge": "2025-09", - "release_date": "2026-08-11", - "last_updated": "2026-08-11", + "knowledge": "2025-05", + "release_date": "2026-10-17", + "last_updated": "2026-10-17", "type": "chat" }, { - "id": "nvidia/NVIDIA-Nemotron-3-Super-120B-A12B-BF16:peft:262144", - "name": "Nemotron 3 Super 120B A12B", - "display_name": "Nemotron 3 Super 120B A12B", + "id": "deepseek/deepseek-r1-turbo", + "name": "DeepSeek R1 (Turbo)", + "display_name": "DeepSeek R1 (Turbo)", "modalities": { "input": [ "text" @@ -211231,8 +209852,8 @@ ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 64000, + "output": 16000 }, "temperature": true, "tool_call": true, @@ -211242,26 +209863,27 @@ }, "attachment": false, "open_weights": true, - "knowledge": "2025-06", - "release_date": "2026-03-11", - "last_updated": "2026-03-11", + "knowledge": "2024-07", + "release_date": "2025-01-20", + "last_updated": "2025-05-29", "type": "chat" }, { - "id": "nvidia/NVIDIA-Nemotron-3-Ultra-550B-A55B-BF16", - "name": "Nemotron 3 Ultra 550B A55B", - "display_name": "Nemotron 3 Ultra 550B A55B", + "id": "deepseek/deepseek-v4.1-flash", + "name": "DeepSeek V4.1 Flash", + "display_name": "DeepSeek V4.1 Flash", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 65536, - "output": 65536 + "context": 1048576, + "output": 393216 }, "temperature": true, "tool_call": true, @@ -211269,17 +209891,33 @@ "supported": true, "default": true }, - "attachment": false, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "effort_options": [ + "low", + "high", + "max" + ] + } + }, + "attachment": true, "open_weights": true, - "knowledge": "2025-09", - "release_date": "2026-06-04", - "last_updated": "2026-06-04", + "knowledge": "2025-05", + "release_date": "2026-09-10", + "last_updated": "2026-09-10", "type": "chat" }, { - "id": "nvidia/NVIDIA-Nemotron-3-Super-120B-A12B-BF16", - "name": "Nemotron 3 Super 120B A12B", - "display_name": "Nemotron 3 Super 120B A12B", + "id": "deepseek/deepseek_v3", + "name": "DeepSeek-V3", + "display_name": "DeepSeek-V3", "modalities": { "input": [ "text" @@ -211289,38 +209927,35 @@ ] }, "limit": { - "context": 65536, - "output": 65536 + "context": 64000, + "output": 16000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true + "supported": false }, "attachment": false, "open_weights": true, - "knowledge": "2025-06", - "release_date": "2026-03-11", - "last_updated": "2026-03-11", + "release_date": "2024-12-26", + "last_updated": "2024-12-26", "type": "chat" }, { - "id": "thinkingmachines/Inkling-Small:peft:262144", - "name": "Inkling Small", - "display_name": "Inkling Small", + "id": "deepseek/deepseek-v4-pro", + "name": "DeepSeek V4 Pro", + "display_name": "DeepSeek V4 Pro", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 1048576, + "output": 393216 }, "temperature": true, "tool_call": true, @@ -211330,37 +209965,48 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "high", "effort_options": [ "low", - "medium", "high", - "xhigh", "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." ] } }, - "attachment": true, + "attachment": false, "open_weights": true, - "release_date": "2026-07-30", - "last_updated": "2026-07-30", + "knowledge": "2025-05", + "release_date": "2026-04-24", + "last_updated": "2026-04-24", "type": "chat" }, { - "id": "thinkingmachines/Inkling:peft:262144:sampling-nvfp4", - "name": "Inkling", - "display_name": "Inkling", + "id": "deepseek/deepseek-v4-pro-0813", + "name": "DeepSeek V4 Pro 0813", + "display_name": "DeepSeek V4 Pro 0813", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 1048576, + "output": 393216 }, "temperature": true, "tool_call": true, @@ -211370,37 +210016,41 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ "low", - "medium", "high", - "xhigh", "max" ] } }, - "attachment": true, + "attachment": false, "open_weights": true, - "release_date": "2026-07-15", - "last_updated": "2026-07-15", + "release_date": "2026-08-13", + "last_updated": "2026-08-22", "type": "chat" }, { - "id": "thinkingmachines/Inkling", - "name": "Inkling", - "display_name": "Inkling", + "id": "deepseek/deepseek-v4-flash-0731", + "name": "DeepSeek V4 Flash 0731", + "display_name": "DeepSeek V4 Flash 0731", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 65536, - "output": 65536 + "context": 1048576, + "output": 393216 }, "temperature": true, "tool_call": true, @@ -211410,77 +210060,70 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ "low", - "medium", "high", - "xhigh", "max" ] } }, - "attachment": true, + "attachment": false, "open_weights": true, - "release_date": "2026-07-15", - "last_updated": "2026-07-15", + "knowledge": "2025-05", + "release_date": "2026-07-31", + "last_updated": "2026-07-31", "type": "chat" }, { - "id": "thinkingmachines/Inkling-Small", - "name": "Inkling Small", - "display_name": "Inkling Small", + "id": "deepseek/deepseek-r1-0528-qwen3-8b", + "name": "DeepSeek R1 0528 Qwen3 8B", + "display_name": "DeepSeek R1 0528 Qwen3 8B", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 65536, - "output": 65536 + "context": 128000, + "output": 32000 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" - ] - } - }, - "attachment": true, + "attachment": false, "open_weights": true, - "release_date": "2026-07-30", - "last_updated": "2026-07-30", + "release_date": "2025-05-29", + "last_updated": "2025-05-29", "type": "chat" }, { - "id": "thinkingmachines/Inkling-Small:peft:262144:sampling-nvfp4", - "name": "Inkling Small", - "display_name": "Inkling Small", + "id": "mindai/macaron-v1-venti", + "name": "Macaron V1 Venti", + "display_name": "Macaron V1 Venti", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 1048576, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -211490,29 +210133,33 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ - "low", - "medium", + "none", "high", - "xhigh", "max" ] } }, - "attachment": true, + "attachment": false, "open_weights": true, - "release_date": "2026-07-30", - "last_updated": "2026-07-30", + "release_date": "2026-07-21", + "last_updated": "2026-07-21", "type": "chat" }, { - "id": "thinkingmachines/Inkling:peft:262144", - "name": "Inkling (256K)", - "display_name": "Inkling (256K)", + "id": "mindai/macaron-v1-tall", + "name": "Macaron V1 Tall", + "display_name": "Macaron V1 Tall", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" @@ -211520,7 +210167,7 @@ }, "limit": { "context": 262144, - "output": 262144 + "output": 32768 }, "temperature": true, "tool_call": true, @@ -211530,25 +210177,29 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" + "none", + "high" ] } }, - "attachment": true, + "attachment": false, "open_weights": true, - "release_date": "2026-07-15", - "last_updated": "2026-07-15", + "release_date": "2026-07-21", + "last_updated": "2026-07-21", "type": "chat" }, { - "id": "openai/gpt-oss-20b", - "name": "GPT OSS 20B", - "display_name": "GPT OSS 20B", + "id": "nousresearch/hermes-2-pro-llama-3-8b", + "name": "Hermes 2 Pro Llama 3 8B", + "display_name": "Hermes 2 Pro Llama 3 8B", "modalities": { "input": [ "text" @@ -211558,31 +210209,24 @@ ] }, "limit": { - "context": 32768, - "output": 32768 + "context": 8192, + "output": 8192 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true - } + "supported": false }, "attachment": false, "open_weights": true, - "knowledge": "2024-06", - "release_date": "2025-08-05", - "last_updated": "2025-08-05", + "release_date": "2024-04-30", + "last_updated": "2024-04-30", "type": "chat" }, { - "id": "openai/gpt-oss-120b:peft:131072", - "name": "GPT OSS 120B", - "display_name": "GPT OSS 120B", + "id": "inclusionai/ling-3.1-flash", + "name": "Ling 3.1 Flash", + "display_name": "Ling 3.1 Flash", "modalities": { "input": [ "text" @@ -211592,8 +210236,8 @@ ] }, "limit": { - "context": 131072, - "output": 131072 + "context": 1000000, + "output": 262144 }, "temperature": true, "tool_call": true, @@ -211601,17 +210245,27 @@ "supported": true, "default": true }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } + }, "attachment": false, "open_weights": true, - "knowledge": "2024-06", - "release_date": "2025-08-05", - "last_updated": "2025-08-05", + "release_date": "2026-09-29", + "last_updated": "2026-09-29", "type": "chat" }, { - "id": "openai/gpt-oss-120b", - "name": "GPT OSS 120B", - "display_name": "GPT OSS 120B", + "id": "inclusionai/ling-3.0-flash-fin", + "name": "Ling 3.0 Flash Fin", + "display_name": "Ling 3.0 Flash Fin", "modalities": { "input": [ "text" @@ -211621,7 +210275,7 @@ ] }, "limit": { - "context": 32768, + "context": 262144, "output": 32768 }, "temperature": true, @@ -211632,31 +210286,35 @@ }, "extra_capabilities": { "reasoning": { - "supported": true + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] } }, "attachment": false, "open_weights": true, - "knowledge": "2024-06", - "release_date": "2025-08-05", - "last_updated": "2025-08-05", + "release_date": "2026-08-27", + "last_updated": "2026-09-03", "type": "chat" }, { - "id": "moonshotai/Kimi-K2.6", - "name": "Kimi K2.6", - "display_name": "Kimi K2.6", + "id": "inclusionai/ling-3.0-flash-sante", + "name": "Ling 3.0 Flash Sante", + "display_name": "Ling 3.0 Flash Sante", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 32768, + "context": 262144, "output": 32768 }, "temperature": true, @@ -211667,32 +210325,38 @@ }, "extra_capabilities": { "reasoning": { - "supported": true + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] } }, - "attachment": true, - "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-04-20", - "last_updated": "2026-04-20", + "attachment": false, + "open_weights": false, + "release_date": "2026-09-04", + "last_updated": "2026-09-04", "type": "chat" }, { - "id": "moonshotai/Kimi-K2.6:peft:131072", - "name": "Kimi K2.6", - "display_name": "Kimi K2.6", + "id": "inclusionai/ling-3.0-flash-vl", + "name": "Ling 3.0 Flash VL", + "display_name": "Ling 3.0 Flash VL", "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 131072 + "context": 262144, + "output": 32768 }, "temperature": true, "tool_call": true, @@ -211700,17 +210364,27 @@ "supported": true, "default": true }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } + }, "attachment": true, "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-04-20", - "last_updated": "2026-04-20", + "release_date": "2026-09-10", + "last_updated": "2026-09-10", "type": "chat" }, { - "id": "deepseek-ai/DeepSeek-V4.1-Flash:peft:262144", - "name": "DeepSeek V4.1 Flash", - "display_name": "DeepSeek V4.1 Flash", + "id": "inclusionai/ling-3.0-flash", + "name": "Ling 3.0 Flash", + "display_name": "Ling 3.0 Flash", "modalities": { "input": [ "text" @@ -211721,7 +210395,7 @@ }, "limit": { "context": 262144, - "output": 262144 + "output": 32768 }, "temperature": true, "tool_call": true, @@ -211729,28 +210403,48 @@ "supported": true, "default": true }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } + }, "attachment": false, "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-09-10", - "last_updated": "2026-09-10", + "release_date": "2026-07-23", + "last_updated": "2026-07-23", "type": "chat" - }, + } + ] + }, + "oci": { + "id": "oci", + "name": "OCI Generative AI", + "display_name": "OCI Generative AI", + "api": "https://inference.generativeai.us-chicago-1.oci.oraclecloud.com/openai/v1", + "doc": "https://docs.oracle.com/en-us/iaas/Content/generative-ai/pretrained-models.htm", + "models": [ { - "id": "deepseek-ai/DeepSeek-V4.1-Flash-1017:peft:262144", - "name": "DeepSeek V4.1 Flash 1017", - "display_name": "DeepSeek V4.1 Flash 1017", + "id": "xai.grok-4.3", + "name": "Grok 4.3", + "display_name": "Grok 4.3", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 1000000, + "output": 30000 }, "temperature": true, "tool_call": true, @@ -211758,28 +210452,38 @@ "supported": true, "default": true }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-10-17", - "last_updated": "2026-10-17", + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "none", + "low", + "medium", + "high" + ] + } + }, + "attachment": true, + "open_weights": false, + "release_date": "2026-04-17", + "last_updated": "2026-04-17", "type": "chat" }, { - "id": "deepseek-ai/DeepSeek-V3.1", - "name": "DeepSeek-V3.1", - "display_name": "DeepSeek-V3.1", + "id": "xai.grok-4.20-reasoning", + "name": "Grok 4.20 (Reasoning)", + "display_name": "Grok 4.20 (Reasoning)", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 32768, - "output": 32768 + "context": 1000000, + "output": 30000 }, "temperature": true, "tool_call": true, @@ -211787,25 +210491,16 @@ "supported": true, "default": true }, - "attachment": false, - "open_weights": true, - "release_date": "2025-08-21", - "last_updated": "2025-08-21", + "attachment": true, + "open_weights": false, + "release_date": "2026-03-09", + "last_updated": "2026-03-09", "type": "chat" - } - ] - }, - "clarifai": { - "id": "clarifai", - "name": "Clarifai", - "display_name": "Clarifai", - "api": "https://api.clarifai.com/v2/ext/openai/v1", - "doc": "https://docs.clarifai.com/compute/inference/", - "models": [ + }, { - "id": "minimaxai/chat-completion/models/MiniMax-M2_5-high-throughput", - "name": "MiniMax-M2.5 High Throughput", - "display_name": "MiniMax-M2.5 High Throughput", + "id": "meta.llama-3.3-70b-instruct", + "name": "Llama-3.3-70B-Instruct", + "display_name": "Llama-3.3-70B-Instruct", "modalities": { "input": [ "text" @@ -211815,38 +210510,37 @@ ] }, "limit": { - "context": 204800, - "output": 131072 + "context": 128000, + "output": 4096 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true + "supported": false }, "attachment": false, "open_weights": true, - "release_date": "2026-02-12", - "last_updated": "2026-02-12", + "knowledge": "2023-12", + "release_date": "2024-12-06", + "last_updated": "2024-12-06", "type": "chat" }, { - "id": "clarifai/main/models/mm-poly-8b", - "name": "MM Poly 8B", - "display_name": "MM Poly 8B", + "id": "meta.llama-4-scout-17b-16e-instruct", + "name": "Llama 4 Scout 17B Instruct", + "display_name": "Llama 4 Scout 17B Instruct", "modalities": { "input": [ "text", - "image", - "video" + "image" ], "output": [ "text" ] }, "limit": { - "context": 32768, - "output": 4096 + "context": 192000, + "output": 16384 }, "temperature": true, "tool_call": false, @@ -211854,26 +210548,28 @@ "supported": false }, "attachment": true, - "open_weights": false, - "release_date": "2025-06", - "last_updated": "2026-02-25", + "open_weights": true, + "knowledge": "2024-08", + "release_date": "2025-04-05", + "last_updated": "2025-04-05", "type": "chat" }, { - "id": "openai/chat-completion/models/gpt-oss-120b-high-throughput", - "name": "GPT OSS 120B High Throughput", - "display_name": "GPT OSS 120B High Throughput", + "id": "xai.grok-4.6", + "name": "Grok 4.6", + "display_name": "Grok 4.6", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 16384 + "context": 500000, + "output": 500000 }, "temperature": true, "tool_call": true, @@ -211886,21 +210582,22 @@ "effort_options": [ "low", "medium", - "high" + "high", + "xhigh" ] } }, - "attachment": false, - "open_weights": true, - "knowledge": "2024-06", - "release_date": "2025-08-05", - "last_updated": "2025-08-05", + "attachment": true, + "open_weights": false, + "knowledge": "2026-02-01", + "release_date": "2026-08-12", + "last_updated": "2026-08-12", "type": "chat" }, { - "id": "openai/chat-completion/models/gpt-oss-20b", - "name": "GPT OSS 20B", - "display_name": "GPT OSS 20B", + "id": "openai.gpt-oss-120b", + "name": "GPT OSS 120B", + "display_name": "GPT OSS 120B", "modalities": { "input": [ "text" @@ -211910,7 +210607,7 @@ ] }, "limit": { - "context": 131072, + "context": 128000, "output": 16384 }, "temperature": true, @@ -211922,6 +210619,12 @@ "extra_capabilities": { "reasoning": { "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ "low", "medium", @@ -211937,50 +210640,48 @@ "type": "chat" }, { - "id": "arcee_ai/AFM/models/trinity-mini", - "name": "Trinity Mini", - "display_name": "Trinity Mini", + "id": "xai.grok-4.20-non-reasoning", + "name": "Grok 4.20 (Non-Reasoning)", + "display_name": "Grok 4.20 (Non-Reasoning)", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 131072 + "context": 1000000, + "output": 30000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true + "supported": false }, - "attachment": false, - "open_weights": true, - "knowledge": "2024-10", - "release_date": "2025-12-01", - "last_updated": "2026-05-28", + "attachment": true, + "open_weights": false, + "release_date": "2026-03-09", + "last_updated": "2026-03-09", "type": "chat" }, { - "id": "moonshotai/chat-completion/models/Kimi-K2_6", - "name": "Kimi K2.6", - "display_name": "Kimi K2.6", + "id": "openai.gpt-oss-20b", + "name": "GPT OSS 20B", + "display_name": "GPT OSS 20B", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 128000, + "output": 16384 }, "temperature": true, "tool_call": true, @@ -211996,48 +210697,63 @@ "visibility": "summary", "continuation": [ "thinking_blocks" + ], + "effort_options": [ + "low", + "medium", + "high" ] } }, - "attachment": true, + "attachment": false, "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-04-20", - "last_updated": "2026-04-20", + "knowledge": "2024-06", + "release_date": "2025-08-05", + "last_updated": "2025-08-05", "type": "chat" }, { - "id": "qwen/qwenCoder/models/Qwen3-Coder-30B-A3B-Instruct", - "name": "Qwen3 Coder 30B A3B Instruct", - "display_name": "Qwen3 Coder 30B A3B Instruct", + "id": "meta.llama-4-maverick-17b-128e-instruct-fp8", + "name": "Llama 4 Maverick 17B Instruct", + "display_name": "Llama 4 Maverick 17B Instruct", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 65536 + "context": 512000, + "output": 16384 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { "supported": false }, - "attachment": false, + "attachment": true, "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-07-31", - "last_updated": "2025-07-31", + "knowledge": "2024-08", + "release_date": "2025-04-05", + "last_updated": "2025-04-05", "type": "chat" - }, + } + ] + }, + "sambanova": { + "id": "sambanova", + "name": "SambaNova", + "display_name": "SambaNova", + "api": "https://api.sambanova.ai/v1", + "doc": "https://docs.sambanova.ai/cloud/docs/get-started/supported-models", + "models": [ { - "id": "qwen/qwenLM/models/Qwen3-30B-A3B-Instruct-2507", - "name": "Qwen3 30B A3B Instruct 2507", - "display_name": "Qwen3 30B A3B Instruct 2507", + "id": "gpt-oss-120b", + "name": "GPT OSS 120B", + "display_name": "GPT OSS 120B", "modalities": { "input": [ "text" @@ -212047,24 +210763,36 @@ ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 131072, + "output": 131072 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "effort_options": [ + "low", + "medium", + "high" + ] + } }, "attachment": false, "open_weights": true, - "release_date": "2025-07-28", - "last_updated": "2025-07-28", + "knowledge": "2024-06", + "release_date": "2025-08-05", + "last_updated": "2025-08-05", "type": "chat" }, { - "id": "qwen/qwenLM/models/Qwen3-30B-A3B-Thinking-2507", - "name": "Qwen3 30B A3B Thinking 2507", - "display_name": "Qwen3 30B A3B Thinking 2507", + "id": "Meta-Llama-3.3-70B-Instruct", + "name": "Llama-3.3-70B-Instruct", + "display_name": "Llama-3.3-70B-Instruct", "modalities": { "input": [ "text" @@ -212074,48 +210802,66 @@ ] }, "limit": { - "context": 262144, - "output": 131072 + "context": 131072, + "output": 3072 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true + "supported": false }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } + "attachment": false, + "open_weights": true, + "knowledge": "2023-12", + "release_date": "2024-12-06", + "last_updated": "2024-12-06", + "type": "chat" + }, + { + "id": "DeepSeek-V3.1", + "name": "DeepSeek-V3.1", + "display_name": "DeepSeek-V3.1", + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "output": 7168 + }, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": true, + "default": true }, "attachment": false, "open_weights": true, - "release_date": "2025-07-31", - "last_updated": "2026-02-25", + "release_date": "2025-08-21", + "last_updated": "2025-08-21", "type": "chat" }, { - "id": "mistralai/completion/models/Ministral-3-14B-Reasoning-2512", - "name": "Ministral 3 14B Reasoning 2512", - "display_name": "Ministral 3 14B Reasoning 2512", + "id": "gemma-4-31B-it", + "name": "Gemma 4 31B IT", + "display_name": "Gemma 4 31B IT", "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 131072, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -212123,29 +210869,33 @@ "supported": true, "default": true }, + "extra_capabilities": { + "reasoning": { + "supported": true + } + }, "attachment": true, "open_weights": true, - "knowledge": "2025-12", - "release_date": "2025-12-01", - "last_updated": "2025-12-12", + "knowledge": "2025-01", + "release_date": "2026-04-02", + "last_updated": "2026-04-02", "type": "chat" }, { - "id": "mistralai/completion/models/Ministral-3-3B-Reasoning-2512", - "name": "Ministral 3 3B Reasoning 2512", - "display_name": "Ministral 3 3B Reasoning 2512", + "id": "MiniMax-M3", + "name": "MiniMax-M3", + "display_name": "MiniMax-M3", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 1048576, + "output": 1048576 }, "temperature": true, "tool_call": true, @@ -212153,66 +210903,77 @@ "supported": true, "default": true }, - "attachment": true, + "extra_capabilities": { + "reasoning": { + "supported": true + } + }, + "attachment": false, "open_weights": true, - "release_date": "2025-12", - "last_updated": "2026-02-25", + "release_date": "2026-06-01", + "last_updated": "2026-06-01", "type": "chat" }, { - "id": "deepseek-ai/deepseek-ocr/models/DeepSeek-OCR", - "name": "DeepSeek OCR", - "display_name": "DeepSeek OCR", + "id": "DeepSeek-V3.2", + "name": "DeepSeek V3.2", + "display_name": "DeepSeek V3.2", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 8192, - "output": 8192 + "context": 32768, + "output": 7168 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": true, + "extra_capabilities": { + "reasoning": { + "supported": true + } + }, + "attachment": false, "open_weights": true, - "release_date": "2025-10-20", - "last_updated": "2026-02-25", + "knowledge": "2024-07", + "release_date": "2025-12-01", + "last_updated": "2025-12-01", "type": "chat" } ] }, - "nearai": { - "id": "nearai", - "name": "NEAR AI Cloud", - "display_name": "NEAR AI Cloud", - "api": "https://cloud-api.near.ai/v1", - "doc": "https://docs.near.ai/", + "stepfun-ai-step-plan": { + "id": "stepfun-ai-step-plan", + "name": "StepFun Step Plan (Global)", + "display_name": "StepFun Step Plan (Global)", + "api": "https://api.stepfun.ai/step_plan/v1", + "doc": "https://platform.stepfun.ai/docs/en/step-plan/integrations/reasoning-api", "models": [ { - "id": "x-ai/grok-4.7", - "name": "Grok 4.7", - "display_name": "Grok 4.7", + "id": "step-5-preview", + "name": "Step 5 Preview", + "display_name": "Step 5 Preview", "modalities": { "input": [ "text", "image", - "pdf" + "video" ], "output": [ "text" ] }, "limit": { - "context": 500000, - "output": 500000 + "context": 1000000, + "output": 384000 }, "temperature": true, "tool_call": true, @@ -212222,25 +210983,32 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "mode": "effort", + "effort": "medium", "effort_options": [ "low", "medium", - "high", - "xhigh" + "high" + ], + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2026-05", - "release_date": "2026-09-21", - "last_updated": "2026-09-21", + "open_weights": true, + "release_date": "2026-09-16", + "last_updated": "2026-09-20", "type": "chat" }, { - "id": "x-ai/grok-4.6", - "name": "Grok 4.6", - "display_name": "Grok 4.6", + "id": "step-3.5-flash-2603", + "name": "Step 3.5 Flash 2603", + "display_name": "Step 3.5 Flash 2603", "modalities": { "input": [ "text" @@ -212250,8 +211018,8 @@ ] }, "limit": { - "context": 500000, - "output": 500000 + "context": 256000, + "output": 256000 }, "temperature": true, "tool_call": true, @@ -212262,53 +211030,29 @@ "extra_capabilities": { "reasoning": { "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ "low", - "medium", - "high", - "xhigh" + "high" ] } }, "attachment": false, - "open_weights": false, - "knowledge": "2026-02-01", - "release_date": "2026-08-12", - "last_updated": "2026-08-12", - "type": "chat" - }, - { - "id": "Qwen/Qwen3-VL-30B-A3B-Instruct", - "name": "Qwen3-VL 30B-A3B Instruct", - "display_name": "Qwen3-VL 30B-A3B Instruct", - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 16384, - "output": 8192 - }, - "temperature": true, - "tool_call": true, - "reasoning": { - "supported": false - }, - "attachment": true, "open_weights": true, - "release_date": "2025-10-04", - "last_updated": "2025-10-04", + "knowledge": "2025-01", + "release_date": "2026-04-02", + "last_updated": "2026-04-02", "type": "chat" }, { - "id": "Qwen/Qwen3.6-35B-A3B-FP8", - "name": "Qwen 3.6 35B A3B FP8", - "display_name": "Qwen 3.6 35B A3B FP8", + "id": "step-3.5-flash", + "name": "Step 3.5 Flash", + "display_name": "Step 3.5 Flash", "modalities": { "input": [ "text" @@ -212318,8 +211062,8 @@ ] }, "limit": { - "context": 262144, - "output": 8192 + "context": 256000, + "output": 256000 }, "temperature": true, "tool_call": true, @@ -212335,180 +211079,225 @@ "visibility": "summary", "continuation": [ "thinking_blocks" + ], + "effort_options": [ + "low", + "high" ] } }, "attachment": false, "open_weights": true, - "release_date": "2026-04-17", - "last_updated": "2026-04-17", + "knowledge": "2025-01", + "release_date": "2026-01-29", + "last_updated": "2026-02-13", "type": "chat" }, { - "id": "Qwen/Qwen3-Embedding-0.6B", - "name": "Qwen3 Embedding 0.6B", - "display_name": "Qwen3 Embedding 0.6B", + "id": "step-3.7-flash", + "name": "Step 3.7 Flash", + "display_name": "Step 3.7 Flash", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 32768, - "output": 1024 + "context": 256000, + "output": 256000 }, - "temperature": false, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "effort_options": [ + "low", + "medium", + "high" + ] + } + }, + "attachment": true, "open_weights": true, - "release_date": "2025-06-05", - "last_updated": "2025-06-05", - "type": "embedding" - }, + "knowledge": "2026-03-01", + "release_date": "2026-05-29", + "last_updated": "2026-05-29", + "type": "chat" + } + ] + }, + "anthropic": { + "id": "anthropic", + "name": "Anthropic", + "display_name": "Anthropic", + "doc": "https://docs.anthropic.com/en/docs/about-claude/models", + "models": [ { - "id": "Qwen/Qwen3.8-27B", - "name": "Qwen3.8 27B", - "display_name": "Qwen3.8 27B", + "id": "claude-haiku-4-5-20251001", + "name": "Claude Haiku 4.5", + "display_name": "Claude Haiku 4.5", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 32768 + "context": 200000, + "output": 64000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": false, + "mode": "budget", + "budget": { + "min": 1024, + "unit": "tokens" + }, "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ "thinking_blocks" + ], + "notes": [ + "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", + "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-08-14", - "last_updated": "2026-08-14", + "open_weights": false, + "knowledge": "2025-02-28", + "release_date": "2025-10-15", + "last_updated": "2025-10-15", "type": "chat" }, { - "id": "Qwen/Qwen3-Reranker-0.6B", - "name": "Qwen3 Reranker 0.6B", - "display_name": "Qwen3 Reranker 0.6B", + "id": "claude-haiku-5-5", + "name": "Claude Haiku 5.5", + "display_name": "Claude Haiku 5.5", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 40960, - "output": 1024 + "context": 1000000, + "output": 128000 }, "temperature": false, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, - "open_weights": true, - "release_date": "2025-06-05", - "last_updated": "2025-06-05", - "type": "rerank" + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" + ] + } + }, + "attachment": true, + "open_weights": false, + "knowledge": "2026-06", + "release_date": "2026-10-07", + "last_updated": "2026-10-07", + "type": "chat" }, { - "id": "Qwen/Qwen3.8-27B-abliterated", - "name": "Qwen3.8 27B Abliterated", - "display_name": "Qwen3.8 27B Abliterated", + "id": "claude-sonnet-4-5-20250929", + "name": "Claude Sonnet 4.5", + "display_name": "Claude Sonnet 4.5", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 32768 + "context": 200000, + "output": 64000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": false, + "mode": "budget", + "budget": { + "min": 1024, + "unit": "tokens" + }, "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ "thinking_blocks" + ], + "notes": [ + "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", + "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." ] } }, "attachment": true, "open_weights": false, - "release_date": "2026-10-29", - "last_updated": "2026-10-29", + "knowledge": "2025-07-31", + "release_date": "2025-09-29", + "last_updated": "2025-09-29", "type": "chat" }, { - "id": "black-forest-labs/FLUX.2-klein-4B", - "name": "FLUX.2 Klein 4B", - "display_name": "FLUX.2 Klein 4B", - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "image" - ] - }, - "limit": { - "context": 8192, - "output": 8192 - }, - "temperature": false, - "tool_call": false, - "reasoning": { - "supported": false - }, - "attachment": true, - "open_weights": true, - "release_date": "2026-01-15", - "last_updated": "2026-01-15" - }, - { - "id": "anthropic/claude-sonnet-5-5", - "name": "Claude Sonnet 5.5", - "display_name": "Claude Sonnet 5.5", + "id": "claude-opus-4-7", + "name": "Claude Opus 4.7", + "display_name": "Claude Opus 4.7", "modalities": { "input": [ "text", @@ -212527,75 +211316,102 @@ "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": false, + "mode": "effort", + "effort": "high", "effort_options": [ "low", "medium", "high", "xhigh", "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "omitted", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Claude Opus 4.7 and newer Opus models require thinking.type = \"adaptive\" to enable thinking explicitly.", + "Manual budget_tokens requests return 400 on Claude Opus 4.7 and newer adaptive-only Opus models.", + "task_budget is separate from thinking control and should not be treated as a thinking budget." ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-06", - "release_date": "2026-09-28", - "last_updated": "2026-09-28", + "knowledge": "2026-01-31", + "release_date": "2026-04-14", + "last_updated": "2026-04-16", "type": "chat" }, { - "id": "anthropic/claude-haiku-5-5", - "name": "Claude Haiku 5.5", - "display_name": "Claude Haiku 5.5", + "id": "claude-haiku-4-5", + "name": "Claude Haiku 4.5 (latest)", + "display_name": "Claude Haiku 4.5 (latest)", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 200000, + "output": 64000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" + "supported": true, + "default_enabled": false, + "mode": "budget", + "budget": { + "min": 1024, + "unit": "tokens" + }, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", + "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-06", - "release_date": "2026-10-07", - "last_updated": "2026-10-07", + "knowledge": "2025-02-28", + "release_date": "2025-10-15", + "last_updated": "2025-10-15", "type": "chat" }, { - "id": "anthropic/claude-opus-5", - "name": "Claude Opus 5", - "display_name": "Claude Opus 5", + "id": "claude-sonnet-5", + "name": "Claude Sonnet 5", + "display_name": "Claude Sonnet 5", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" @@ -212613,20 +211429,27 @@ }, "extra_capabilities": { "reasoning": { - "supported": true + "supported": true, + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" + ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-05", - "release_date": "2026-07-24", - "last_updated": "2026-07-24", + "knowledge": "2026-01-31", + "release_date": "2026-06-29", + "last_updated": "2026-06-30", "type": "chat" }, { - "id": "anthropic/claude-opus-4-7", - "name": "Claude Opus 4.7", - "display_name": "Claude Opus 4.7", + "id": "claude-fable-5", + "name": "Claude Fable 5", + "display_name": "Claude Fable 5", "modalities": { "input": [ "text", @@ -212645,12 +211468,12 @@ "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, + "default_enabled": true, "mode": "effort", "effort": "high", "effort_options": [ @@ -212667,23 +211490,22 @@ "thinking_blocks" ], "notes": [ - "Claude Opus 4.7 and newer Opus models require thinking.type = \"adaptive\" to enable thinking explicitly.", - "Manual budget_tokens requests return 400 on Claude Opus 4.7 and newer adaptive-only Opus models.", - "task_budget is separate from thinking control and should not be treated as a thinking budget." + "Adaptive thinking is always on for Claude Fable 5 and Claude Mythos 5; thinking.type = \"disabled\" is rejected.", + "Manual budget_tokens requests return 400 on Claude Fable 5 and Claude Mythos 5.", + "thinking.display defaults to omitted; set display to summarized to receive readable thinking summaries." ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-01-31", - "release_date": "2026-04-16", - "last_updated": "2026-04-16", + "release_date": "2026-06-07", + "last_updated": "2026-06-09", "type": "chat" }, { - "id": "anthropic/claude-opus-5-5", - "name": "Claude Opus 5.5", - "display_name": "Claude Opus 5.5", + "id": "claude-sonnet-4-5", + "name": "Claude Sonnet 4.5 (latest)", + "display_name": "Claude Sonnet 4.5 (latest)", "modalities": { "input": [ "text", @@ -212695,41 +211517,52 @@ ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 200000, + "output": 64000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" + "supported": true, + "default_enabled": false, + "mode": "budget", + "budget": { + "min": 1024, + "unit": "tokens" + }, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", + "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-06", - "release_date": "2026-09-22", - "last_updated": "2026-09-22", + "knowledge": "2025-07-31", + "release_date": "2025-09-29", + "last_updated": "2025-09-29", "type": "chat" }, { - "id": "anthropic/claude-sonnet-5", - "name": "Claude Sonnet 5", - "display_name": "Claude Sonnet 5", + "id": "claude-opus-5-5", + "name": "Claude Opus 5.5", + "display_name": "Claude Opus 5.5", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" @@ -212747,20 +211580,26 @@ }, "extra_capabilities": { "reasoning": { - "supported": true + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" + ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-01-31", - "release_date": "2026-06-30", - "last_updated": "2026-06-30", + "knowledge": "2026-06", + "release_date": "2026-09-22", + "last_updated": "2026-09-22", "type": "chat" }, { - "id": "anthropic/claude-sonnet-4-6", - "name": "Claude Sonnet 4.6", - "display_name": "Claude Sonnet 4.6", + "id": "claude-opus-4-5", + "name": "Claude Opus 4.5 (latest)", + "display_name": "Claude Opus 4.5 (latest)", "modalities": { "input": [ "text", @@ -212772,7 +211611,7 @@ ] }, "limit": { - "context": 1000000, + "context": 200000, "output": 64000 }, "temperature": true, @@ -212794,8 +211633,7 @@ "effort_options": [ "low", "medium", - "high", - "max" + "high" ], "interleaved": true, "summaries": true, @@ -212804,22 +211642,22 @@ "thinking_blocks" ], "notes": [ - "Anthropic recommends adaptive thinking with effort for Claude 4.6; budget_tokens remains a deprecated compatibility path.", - "Anthropic API defaults effort to high; lower effort levels should be chosen per workload." + "Claude Opus 4.5 uses manual thinking.type = \"enabled\" with budget_tokens; effort can be used alongside the thinking budget.", + "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header." ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-02-17", - "last_updated": "2026-03-13", + "knowledge": "2025-05", + "release_date": "2025-11-24", + "last_updated": "2025-11-24", "type": "chat" }, { - "id": "anthropic/claude-opus-4-6", - "name": "Claude Opus 4.6", - "display_name": "Claude Opus 4.6", + "id": "claude-sonnet-5-5", + "name": "Claude Sonnet 5.5", + "display_name": "Claude Sonnet 5.5", "modalities": { "input": [ "text", @@ -212831,54 +211669,37 @@ ] }, "limit": { - "context": 200000, + "context": 1000000, "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "mixed", - "budget": { - "min": 1024, - "unit": "tokens" - }, - "effort": "high", "effort_options": [ "low", "medium", "high", + "xhigh", "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Anthropic recommends adaptive thinking with effort for Claude 4.6; budget_tokens remains a deprecated compatibility path.", - "Anthropic API defaults effort to high; lower effort levels should be chosen per workload." ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-05-31", - "release_date": "2026-02-05", - "last_updated": "2026-03-13", + "knowledge": "2026-06", + "release_date": "2026-09-28", + "last_updated": "2026-09-28", "type": "chat" }, { - "id": "anthropic/claude-haiku-4-5", - "name": "Claude Haiku 4.5 (latest)", - "display_name": "Claude Haiku 4.5 (latest)", + "id": "claude-sonnet-4-6", + "name": "Claude Sonnet 4.6", + "display_name": "Claude Sonnet 4.6", "modalities": { "input": [ "text", @@ -212890,8 +211711,8 @@ ] }, "limit": { - "context": 200000, - "output": 64000 + "context": 1000000, + "output": 128000 }, "temperature": true, "tool_call": true, @@ -212903,11 +211724,18 @@ "reasoning": { "supported": true, "default_enabled": false, - "mode": "budget", + "mode": "mixed", "budget": { "min": 1024, "unit": "tokens" }, + "effort": "high", + "effort_options": [ + "low", + "medium", + "high", + "max" + ], "interleaved": true, "summaries": true, "visibility": "summary", @@ -212915,26 +211743,27 @@ "thinking_blocks" ], "notes": [ - "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", - "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." + "Anthropic recommends adaptive thinking with effort for Claude 4.6; budget_tokens remains a deprecated compatibility path.", + "Anthropic API defaults effort to high; lower effort levels should be chosen per workload." ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-02-28", - "release_date": "2025-10-15", - "last_updated": "2025-10-15", + "knowledge": "2025-08-31", + "release_date": "2026-02-17", + "last_updated": "2026-03-13", "type": "chat" }, { - "id": "anthropic/claude-opus-4-8", + "id": "claude-opus-4-8", "name": "Claude Opus 4.8", "display_name": "Claude Opus 4.8", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" @@ -212984,9 +211813,9 @@ "type": "chat" }, { - "id": "anthropic/claude-sonnet-4-5", - "name": "Claude Sonnet 4.5 (latest)", - "display_name": "Claude Sonnet 4.5 (latest)", + "id": "claude-opus-4-6", + "name": "Claude Opus 4.6", + "display_name": "Claude Opus 4.6", "modalities": { "input": [ "text", @@ -212998,8 +211827,8 @@ ] }, "limit": { - "context": 200000, - "output": 64000 + "context": 1000000, + "output": 128000 }, "temperature": true, "tool_call": true, @@ -213011,11 +211840,18 @@ "reasoning": { "supported": true, "default_enabled": false, - "mode": "budget", + "mode": "mixed", "budget": { "min": 1024, "unit": "tokens" }, + "effort": "high", + "effort_options": [ + "low", + "medium", + "high", + "max" + ], "interleaved": true, "summaries": true, "visibility": "summary", @@ -213023,26 +211859,27 @@ "thinking_blocks" ], "notes": [ - "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", - "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." + "Anthropic recommends adaptive thinking with effort for Claude 4.6; budget_tokens remains a deprecated compatibility path.", + "Anthropic API defaults effort to high; lower effort levels should be chosen per workload." ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-07-31", - "release_date": "2025-09-29", - "last_updated": "2025-09-29", + "knowledge": "2025-05-31", + "release_date": "2026-02-04", + "last_updated": "2026-03-13", "type": "chat" }, { - "id": "anthropic/claude-fable-5", - "name": "Claude Fable 5", - "display_name": "Claude Fable 5", + "id": "claude-fable-5-1", + "name": "Claude Fable 5.1", + "display_name": "Claude Fable 5.1", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" @@ -213086,19 +211923,20 @@ }, "attachment": true, "open_weights": false, - "knowledge": "2026-01-31", - "release_date": "2026-06-09", - "last_updated": "2026-06-09", + "knowledge": "2026-06", + "release_date": "2026-09-01", + "last_updated": "2026-09-01", "type": "chat" }, { - "id": "anthropic/claude-fable-5-1", - "name": "Claude Fable 5.1", - "display_name": "Claude Fable 5.1", + "id": "claude-opus-5", + "name": "Claude Opus 5", + "display_name": "Claude Opus 5", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" @@ -213117,179 +211955,178 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "high", "effort_options": [ "low", "medium", "high", "xhigh", "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "omitted", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Adaptive thinking is always on for Claude Fable 5 and Claude Mythos 5; thinking.type = \"disabled\" is rejected.", - "Manual budget_tokens requests return 400 on Claude Fable 5 and Claude Mythos 5.", - "thinking.display defaults to omitted; set display to summarized to receive readable thinking summaries." ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-06", - "release_date": "2026-09-01", - "last_updated": "2026-09-01", + "knowledge": "2026-05", + "release_date": "2026-07-24", + "last_updated": "2026-07-24", "type": "chat" }, { - "id": "z-ai/glm-5.3-flash", - "name": "GLM-5.3-Flash", - "display_name": "GLM-5.3-Flash", + "id": "claude-opus-4-5-20251101", + "name": "Claude Opus 4.5", + "display_name": "Claude Opus 4.5", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 200000, + "output": 64000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": false, + "mode": "mixed", + "budget": { + "min": 1024, + "unit": "tokens" + }, + "effort": "high", + "effort_options": [ + "low", + "medium", + "high" + ], "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ "thinking_blocks" ], - "effort_options": [ - "low", - "high", - "max" + "notes": [ + "Claude Opus 4.5 uses manual thinking.type = \"enabled\" with budget_tokens; effort can be used alongside the thinking budget.", + "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header." ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-08-26", - "last_updated": "2026-08-26", + "open_weights": false, + "knowledge": "2025-05", + "release_date": "2025-11-24", + "last_updated": "2025-11-01", "type": "chat" - }, + } + ] + }, + "claudinio": { + "id": "claudinio", + "name": "Claudinio", + "display_name": "Claudinio", + "api": "https://api.claudin.io/v1", + "doc": "https://claudin.io", + "models": [ { - "id": "z-ai/glm-5.3", - "name": "GLM-5.3", - "display_name": "GLM-5.3", + "id": "claudinio", + "name": "Claudinio", + "display_name": "Claudinio", "modalities": { "input": [ - "text" + "text", + "image", + "audio", + "video" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 256000, + "output": 64000 }, - "temperature": true, "tool_call": true, "reasoning": { "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "effort_options": [ - "low", - "high", - "max" - ] - } - }, - "attachment": false, - "open_weights": true, - "release_date": "2026-08-14", - "last_updated": "2026-08-14", + "attachment": true, + "open_weights": false, + "knowledge": "2026-05", + "release_date": "2026-05-12", + "last_updated": "2026-06-02", "type": "chat" }, { - "id": "deepseek/deepseek-v4.1-flash-1017", - "name": "DeepSeek V4.1 Flash 1017", - "display_name": "DeepSeek V4.1 Flash 1017", + "id": "claudius", + "name": "Claudius", + "display_name": "Claudius", "modalities": { "input": [ "text", - "image" + "image", + "audio", + "video" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 384000 + "context": 256000, + "output": 64000 }, - "temperature": true, "tool_call": true, "reasoning": { "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "low", - "high", - "max" - ] - } - }, "attachment": true, - "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-10-17", - "last_updated": "2026-10-17", + "open_weights": false, + "knowledge": "2026-05", + "release_date": "2026-05-12", + "last_updated": "2026-05-12", "type": "chat" - }, + } + ] + }, + "alibaba-cn": { + "id": "alibaba-cn", + "name": "alibaba-cn", + "display_name": "alibaba-cn", + "api": "https://dashscope.aliyuncs.com/compatible-mode/v1", + "doc": "https://www.alibabacloud.com/help/en/model-studio/models", + "models": [ { - "id": "deepseek/deepseek-v3.2", - "name": "DeepSeek V3.2", - "display_name": "DeepSeek V3.2", + "id": "qwen3.8-omni-flash", + "name": "Qwen3.8 Omni Flash", + "display_name": "Qwen3.8 Omni Flash", "modalities": { "input": [ - "text" + "text", + "image", + "audio", + "video" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 64000 + "context": 1000000, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -213300,6 +212137,15 @@ "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "xhigh", + "effort_options": [ + "none", + "low", + "medium", + "xhigh" + ], "interleaved": true, "summaries": true, "visibility": "summary", @@ -213308,24 +212154,20 @@ ] } }, - "attachment": false, - "open_weights": true, - "knowledge": "2024-07", - "release_date": "2025-12-01", - "last_updated": "2025-12-01", + "attachment": true, + "open_weights": false, + "release_date": "2026-09-17", + "last_updated": "2026-09-17", "type": "chat" }, { - "id": "google/gemini-2.5-pro", - "name": "Gemini 2.5 Pro", - "display_name": "Gemini 2.5 Pro", + "id": "kimi-k3", + "name": "Kimi K3", + "display_name": "Kimi K3", "modalities": { "input": [ "text", - "image", - "audio", - "video", - "pdf" + "image" ], "output": [ "text" @@ -213333,9 +212175,9 @@ }, "limit": { "context": 1048576, - "output": 65536 + "output": 1048576 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -213344,48 +212186,40 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "budget", - "budget": { - "default": -1, - "min": 128, - "max": 32768, - "auto": -1, - "unit": "tokens" - }, + "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ - "thought_signatures" + "thinking_blocks" + ], + "effort_options": [ + "low", + "high", + "max" ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2025-01", - "release_date": "2025-06-17", - "last_updated": "2025-06-17", + "open_weights": true, + "release_date": "2026-07-16", + "last_updated": "2026-07-16", "type": "chat" }, { - "id": "google/gemini-2.5-flash", - "name": "Gemini 2.5 Flash", - "display_name": "Gemini 2.5 Flash", + "id": "MiniMax-M2.5", + "name": "MiniMax-M2.5", + "display_name": "MiniMax-M2.5", "modalities": { "input": [ - "text", - "image", - "audio", - "video", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 204800, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -213396,48 +212230,62 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "budget", - "budget": { - "default": -1, - "min": 0, - "max": 24576, - "auto": -1, - "off": 0, - "unit": "tokens" - }, + "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ - "thought_signatures" + "thinking_blocks" ] } }, - "attachment": true, + "attachment": false, + "open_weights": true, + "release_date": "2026-02-12", + "last_updated": "2026-02-12", + "type": "chat" + }, + { + "id": "tongyi-intent-detect-v3", + "name": "Tongyi Intent Detect V3", + "display_name": "Tongyi Intent Detect V3", + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 8192, + "output": 1024 + }, + "temperature": true, + "tool_call": false, + "reasoning": { + "supported": false + }, + "attachment": false, "open_weights": false, - "knowledge": "2025-01", - "release_date": "2025-06-17", - "last_updated": "2025-06-17", + "knowledge": "2024-04", + "release_date": "2024-01", + "last_updated": "2024-01", "type": "chat" }, { - "id": "google/gemini-3.1-flash-lite", - "name": "Gemini 3.1 Flash Lite", - "display_name": "Gemini 3.1 Flash Lite", + "id": "qwen3-max", + "name": "Qwen3 Max", + "display_name": "Qwen3 Max", "modalities": { "input": [ - "text", - "image", - "video", - "audio", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, + "context": 262144, "output": 65536 }, "temperature": true, @@ -213448,238 +212296,153 @@ }, "extra_capabilities": { "reasoning": { - "supported": true + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] } }, - "attachment": true, + "attachment": false, "open_weights": false, - "knowledge": "2025-01", - "release_date": "2026-05-07", - "last_updated": "2026-05-07", + "knowledge": "2025-04", + "release_date": "2026-01-23", + "last_updated": "2026-09-22", "type": "chat" }, { - "id": "google/gemini-2.5-flash-lite", - "name": "Gemini 2.5 Flash-Lite", - "display_name": "Gemini 2.5 Flash-Lite", + "id": "qwen-vl-max", + "name": "Qwen-VL Max", + "display_name": "Qwen-VL Max", "modalities": { "input": [ "text", - "image", - "audio", - "video", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 131072, + "output": 8192 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": false - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "budget", - "budget": { - "default": -1, - "min": 512, - "max": 24576, - "auto": -1, - "unit": "tokens" - }, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thought_signatures" - ] - } + "supported": false }, "attachment": true, "open_weights": false, - "knowledge": "2025-01", - "release_date": "2025-06-17", - "last_updated": "2025-06-17", + "knowledge": "2024-04", + "release_date": "2024-04-08", + "last_updated": "2025-08-13", "type": "chat" }, { - "id": "google/gemini-3.8-flash", - "name": "Gemini 3.8 Flash", - "display_name": "Gemini 3.8 Flash", + "id": "deepseek-v3", + "name": "DeepSeek V3", + "display_name": "DeepSeek V3", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 65536, + "output": 8192 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "level", - "level": "high", - "level_options": [ - "minimal", - "low", - "medium", - "high" - ], - "summaries": true, - "visibility": "summary", - "continuation": [ - "thought_signatures" - ] - } + "supported": false }, - "attachment": true, + "attachment": false, "open_weights": false, - "release_date": "2026-09-02", - "last_updated": "2026-09-02", + "release_date": "2024-12-01", + "last_updated": "2024-12-01", "type": "chat" }, { - "id": "google/gemini-3.5-flash", - "name": "Gemini 3.5 Flash", - "display_name": "Gemini 3.5 Flash", + "id": "qwen3-next-80b-a3b-instruct", + "name": "Qwen3 Next 80B A3B Instruct", + "display_name": "Qwen3 Next 80B A3B Instruct", "modalities": { "input": [ - "text", - "image", - "video", - "audio", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 131072, + "output": 32768 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "level", - "level": "high", - "level_options": [ - "minimal", - "low", - "medium", - "high" - ], - "summaries": true, - "visibility": "summary", - "continuation": [ - "thought_signatures" - ] - } + "supported": false }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-01", - "release_date": "2026-05-19", - "last_updated": "2026-05-19", + "attachment": false, + "open_weights": true, + "knowledge": "2025-04", + "release_date": "2025-09", + "last_updated": "2025-09", "type": "chat" }, { - "id": "openai/gpt-5", - "name": "GPT-5", - "display_name": "GPT-5", + "id": "qwen3-omni-flash-realtime", + "name": "Qwen3-Omni Flash Realtime", + "display_name": "Qwen3-Omni Flash Realtime", "modalities": { "input": [ "text", - "image" + "image", + "audio" ], "output": [ - "text" + "text", + "audio" ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 65536, + "output": 16384 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", - "effort_options": [ - "minimal", - "low", - "medium", - "high" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" - } + "supported": false }, "attachment": true, "open_weights": false, - "knowledge": "2024-09-30", - "release_date": "2025-08-07", - "last_updated": "2025-08-07", - "type": "chat" + "knowledge": "2024-04", + "release_date": "2025-09-15", + "last_updated": "2025-09-15" }, { - "id": "openai/gpt-5.5", - "name": "GPT-5.5", - "display_name": "GPT-5.5", + "id": "deepseek-r1-0528", + "name": "DeepSeek R1 0528", + "display_name": "DeepSeek R1 0528", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 131072, + "output": 16384 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -213688,130 +212451,91 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", - "effort_options": [ - "low", - "medium", - "high", - "xhigh" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] } }, - "attachment": true, + "attachment": false, "open_weights": false, - "knowledge": "2025-12-01", - "release_date": "2026-04-23", - "last_updated": "2026-04-23", + "release_date": "2025-05-28", + "last_updated": "2025-05-28", "type": "chat" }, { - "id": "openai/gpt-6-astra", - "name": "GPT-6 Astra", - "display_name": "GPT-6 Astra", + "id": "deepseek-r1-distill-qwen-14b", + "name": "DeepSeek R1 Distill Qwen 14B", + "display_name": "DeepSeek R1 Distill Qwen 14B", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 32768, + "output": 16384 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" - ] - } - }, - "attachment": true, + "attachment": false, "open_weights": false, - "knowledge": "2026-04-30", - "release_date": "2026-09-04", - "last_updated": "2026-09-04", + "release_date": "2025-01-01", + "last_updated": "2025-01-01", "type": "chat" }, { - "id": "openai/gpt-5.4", - "name": "GPT-5.4", - "display_name": "GPT-5.4", + "id": "kimi-k2-thinking", + "name": "Moonshot Kimi K2 Thinking", + "display_name": "Moonshot Kimi K2 Thinking", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 262144, + "output": 16384 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "none", - "effort_options": [ - "none", - "low", - "medium", - "high", - "xhigh" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-03-05", - "last_updated": "2026-03-05", + "attachment": false, + "open_weights": true, + "release_date": "2025-11-06", + "last_updated": "2025-11-06", "type": "chat" }, { - "id": "openai/whisper-large-v3", - "name": "Whisper Large v3", - "display_name": "Whisper Large v3", + "id": "qwen3-asr-flash", + "name": "Qwen3-ASR Flash", + "display_name": "Qwen3-ASR Flash", "modalities": { "input": [ "audio" @@ -213821,38 +212545,37 @@ ] }, "limit": { - "context": 448, - "output": 448 + "context": 53248, + "output": 4096 }, - "temperature": true, + "temperature": false, "tool_call": false, "reasoning": { "supported": false }, "attachment": true, - "open_weights": true, - "release_date": "2023-11-06", - "last_updated": "2023-11-06" + "open_weights": false, + "knowledge": "2024-04", + "release_date": "2025-09-08", + "last_updated": "2025-09-08" }, { - "id": "openai/o3", - "name": "o3", - "display_name": "o3", + "id": "qwen3.7-max", + "name": "Qwen3.7 Max", + "display_name": "Qwen3.7 Max", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 100000 + "context": 1000000, + "output": 131072 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -213860,43 +212583,34 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", - "effort_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + "supported": true } }, - "attachment": true, + "attachment": false, "open_weights": false, - "knowledge": "2024-05", - "release_date": "2025-04-16", - "last_updated": "2025-04-16", + "release_date": "2026-05-21", + "last_updated": "2026-05-21", "type": "chat" }, { - "id": "openai/gpt-6-sol", - "name": "GPT-6 Sol", - "display_name": "GPT-6 Sol", + "id": "qwen3.6-flash", + "name": "Qwen3.6 Flash", + "display_name": "Qwen3.6 Flash", "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 1000000, + "output": 65536 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -213904,85 +212618,91 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "none", - "low", - "medium", - "high", - "xhigh", - "max" + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-04-20", - "release_date": "2026-09-22", - "last_updated": "2026-09-22", + "release_date": "2026-04-27", + "last_updated": "2026-04-27", "type": "chat" }, { - "id": "openai/o4-mini", - "name": "o4-mini", - "display_name": "o4-mini", + "id": "qwen-plus", + "name": "Qwen Plus", + "display_name": "Qwen Plus", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 100000 + "context": 1000000, + "output": 32768 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false, + "budget": { + "default": 81920, + "min": 0, + "max": 81920 + } }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", - "effort_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] } }, - "attachment": true, + "search": { + "supported": true, + "default": false, + "forced_search": false, + "search_strategy": "turbo" + }, + "attachment": false, "open_weights": false, - "knowledge": "2024-05", - "release_date": "2025-04-16", - "last_updated": "2025-04-16", + "knowledge": "2024-04", + "release_date": "2024-01-25", + "last_updated": "2025-09-11", "type": "chat" }, { - "id": "openai/gpt-5.6-sol", - "name": "GPT-5.6 Sol", - "display_name": "GPT-5.6 Sol", + "id": "qwen3.8-flash", + "name": "Qwen3.8 Flash", + "display_name": "Qwen3.8 Flash", "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 1000000, + "output": 131072 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -213993,49 +212713,56 @@ "supported": true, "default_enabled": true, "mode": "effort", - "effort": "medium", + "budget": { + "min": 0, + "max": 262144, + "unit": "tokens" + }, + "effort": "xhigh", "effort_options": [ "none", "low", "medium", - "high", - "xhigh", - "max" + "xhigh" ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ], - "visibility": "hidden" + "notes": [ + "Prefer reasoning_effort; thinking_budget is an alternative, not an additional control.", + "Omitting both controls differs from explicitly selecting xhigh, which maps to 262144 budget tokens." + ], + "budget_exclusive_with_effort": true } }, "attachment": true, "open_weights": false, - "knowledge": "2026-02-16", - "release_date": "2026-07-09", - "last_updated": "2026-07-09", + "release_date": "2026-08-26", + "last_updated": "2026-08-26", "type": "chat" }, { - "id": "openai/gpt-6-luna", - "name": "GPT-6 Luna", - "display_name": "GPT-6 Luna", + "id": "qwen3.5-plus", + "name": "Qwen3.5 Plus", + "display_name": "Qwen3.5 Plus", "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 1000000, + "output": 65536 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -214043,78 +212770,54 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "none", - "low", - "medium", - "high", - "xhigh", - "max" + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-05-18", - "release_date": "2026-09-22", + "knowledge": "2025-04", + "release_date": "2026-02-16", "last_updated": "2026-09-22", "type": "chat" }, { - "id": "openai/gpt-5-mini", - "name": "GPT-5 Mini", - "display_name": "GPT-5 Mini", + "id": "qwen-math-plus", + "name": "Qwen Math Plus", + "display_name": "Qwen Math Plus", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 4096, + "output": 3072 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", - "effort_options": [ - "minimal", - "low", - "medium", - "high" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" - } + "supported": false }, - "attachment": true, + "attachment": false, "open_weights": false, - "knowledge": "2024-05-30", - "release_date": "2025-08-07", - "last_updated": "2025-08-07", + "knowledge": "2024-04", + "release_date": "2024-08-16", + "last_updated": "2024-09-19", "type": "chat" }, { - "id": "openai/o3-mini", - "name": "o3-mini", - "display_name": "o3-mini", + "id": "deepseek-r1-distill-qwen-1-5b", + "name": "DeepSeek R1 Distill Qwen 1.5B", + "display_name": "DeepSeek R1 Distill Qwen 1.5B", "modalities": { "input": [ "text" @@ -214124,91 +212827,67 @@ ] }, "limit": { - "context": 200000, - "output": 100000 + "context": 32768, + "output": 16384 }, - "temperature": false, - "tool_call": true, + "temperature": true, + "tool_call": false, "reasoning": { "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", - "effort_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" - } - }, "attachment": false, "open_weights": false, - "knowledge": "2024-05", - "release_date": "2024-12-20", - "last_updated": "2025-01-29", + "release_date": "2025-01-01", + "last_updated": "2025-01-01", "type": "chat" }, { - "id": "openai/gpt-5.1", - "name": "GPT-5.1", - "display_name": "GPT-5.1", + "id": "qwen3.5-flash", + "name": "Qwen3.5 Flash", + "display_name": "Qwen3.5 Flash", "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 1000000, + "output": 65536 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "none", - "effort_options": [ - "none", - "low", - "medium", - "high" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] } }, "attachment": true, "open_weights": false, - "knowledge": "2024-09-30", - "release_date": "2025-11-13", - "last_updated": "2025-11-13", + "knowledge": "2025-04", + "release_date": "2026-02-23", + "last_updated": "2026-09-22", "type": "chat" }, { - "id": "openai/gpt-5.6-luna", - "name": "GPT-5.6 Luna", - "display_name": "GPT-5.6 Luna", + "id": "qwen-vl-plus", + "name": "Qwen-VL Plus", + "display_name": "Qwen-VL Plus", "modalities": { "input": [ "text", @@ -214219,213 +212898,161 @@ ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 131072, + "output": 8192 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", - "effort_options": [ - "none", - "low", - "medium", - "high", - "xhigh", - "max" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" - } + "supported": false }, "attachment": true, "open_weights": false, - "knowledge": "2026-02-16", - "release_date": "2026-07-09", - "last_updated": "2026-07-09", + "knowledge": "2024-04", + "release_date": "2024-01-25", + "last_updated": "2025-08-15", "type": "chat" }, { - "id": "openai/gpt-5.4-mini", - "name": "GPT-5.4 mini", - "display_name": "GPT-5.4 mini", + "id": "qwen3.7-flash", + "name": "Qwen3.7 Flash", + "display_name": "Qwen3.7 Flash", "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 1000000, + "output": 131072 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "none", - "effort_options": [ - "none", - "low", - "medium", - "high", - "xhigh" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + "supported": true } }, "attachment": true, "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-03-17", - "last_updated": "2026-03-17", + "release_date": "2026-07-15", + "last_updated": "2026-07-15", "type": "chat" }, { - "id": "openai/gpt-4.1-mini", - "name": "GPT-4.1 mini", - "display_name": "GPT-4.1 mini", + "id": "qwen-doc-turbo", + "name": "Qwen Doc Turbo", + "display_name": "Qwen Doc Turbo", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1047576, - "output": 32768 + "context": 131072, + "output": 8192 }, "temperature": true, "tool_call": true, "reasoning": { "supported": false }, - "attachment": true, + "attachment": false, "open_weights": false, "knowledge": "2024-04", - "release_date": "2025-04-14", - "last_updated": "2025-04-14", + "release_date": "2024-01", + "last_updated": "2024-01", "type": "chat" }, { - "id": "openai/gpt-5.4-nano", - "name": "GPT-5.4 nano", - "display_name": "GPT-5.4 nano", + "id": "qwen-deep-research", + "name": "Qwen Deep Research", + "display_name": "Qwen Deep Research", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 1000000, + "output": 32768 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": false - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "none", - "effort_options": [ - "none", - "low", - "medium", - "high", - "xhigh" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" - } + "supported": false }, - "attachment": true, + "attachment": false, "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-03-17", - "last_updated": "2026-03-17", + "knowledge": "2024-04", + "release_date": "2024-01", + "last_updated": "2024-01", "type": "chat" }, { - "id": "openai/gpt-4.1", - "name": "GPT-4.1", - "display_name": "GPT-4.1", + "id": "qwen3-8b", + "name": "Qwen3 8B", + "display_name": "Qwen3 8B", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1047576, - "output": 32768 + "context": 131072, + "output": 8192 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true, + "budget": { + "default": 38912, + "min": 0, + "max": 38912 + } }, - "attachment": true, - "open_weights": false, - "knowledge": "2024-04", - "release_date": "2025-04-14", - "last_updated": "2025-04-14", + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } + }, + "attachment": false, + "open_weights": true, + "knowledge": "2025-04", + "release_date": "2025-04", + "last_updated": "2025-04", "type": "chat" }, { - "id": "openai/gpt-5.2", - "name": "GPT-5.2", - "display_name": "GPT-5.2", + "id": "qwen3-vl-30b-a3b", + "name": "Qwen3-VL 30B-A3B", + "display_name": "Qwen3-VL 30B-A3B", "modalities": { "input": [ "text", @@ -214436,140 +213063,121 @@ ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 131072, + "output": 32768 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "none", - "effort_options": [ - "none", - "low", - "medium", - "high", - "xhigh" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2025-12-11", - "last_updated": "2025-12-11", + "open_weights": true, + "knowledge": "2025-04", + "release_date": "2025-04", + "last_updated": "2025-04", "type": "chat" }, { - "id": "openai/gpt-6.1-sol", - "name": "GPT-6.1 Sol", - "display_name": "GPT-6.1 Sol", + "id": "deepseek-r1-distill-llama-70b", + "name": "DeepSeek R1 Distill Llama 70B", + "display_name": "DeepSeek R1 Distill Llama 70B", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 32768, + "output": 16384 }, - "temperature": false, - "tool_call": true, + "temperature": true, + "tool_call": false, "reasoning": { "supported": true, "default": true }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" - ] + "supported": true } }, - "attachment": true, + "attachment": false, "open_weights": false, - "knowledge": "2026-04-30", - "release_date": "2026-09-29", - "last_updated": "2026-09-29", + "release_date": "2025-01-01", + "last_updated": "2025-01-01", "type": "chat" }, { - "id": "openai/gpt-5-nano", - "name": "GPT-5 Nano", - "display_name": "GPT-5 Nano", + "id": "qwen-turbo", + "name": "Qwen Turbo", + "display_name": "Qwen Turbo", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 131072, + "output": 16384 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false, + "budget": { + "default": 38912, + "min": 0, + "max": 38912 + } }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", - "effort_options": [ - "minimal", - "low", - "medium", - "high" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] } }, - "attachment": true, + "search": { + "supported": true, + "default": false, + "forced_search": false, + "search_strategy": "turbo" + }, + "attachment": false, "open_weights": false, - "knowledge": "2024-05-30", - "release_date": "2025-08-07", - "last_updated": "2025-08-07", + "knowledge": "2024-04", + "release_date": "2024-11-01", + "last_updated": "2025-07-15", "type": "chat" }, { - "id": "moonshotai/kimi-k3", - "name": "Kimi K3", - "display_name": "Kimi K3", + "id": "deepseek-r1-distill-qwen-32b", + "name": "DeepSeek R1 Distill Qwen 32B", + "display_name": "DeepSeek R1 Distill Qwen 32B", "modalities": { "input": [ "text" @@ -214579,41 +213187,25 @@ ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 32768, + "output": 16384 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "effort_options": [ - "low", - "high", - "max" - ] - } - }, "attachment": false, - "open_weights": true, - "release_date": "2026-07-16", - "last_updated": "2026-07-16", + "open_weights": false, + "release_date": "2025-01-01", + "last_updated": "2025-01-01", "type": "chat" }, { - "id": "moonshotai/kimi-k2.6", - "name": "Kimi K2.6", - "display_name": "Kimi K2.6", + "id": "qwen3-235b-a22b", + "name": "Qwen3 235B A22B", + "display_name": "Qwen3 235B A22B", "modalities": { "input": [ "text" @@ -214623,14 +213215,19 @@ ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 131072, + "output": 32768 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false, + "budget": { + "default": 81920, + "min": 0, + "max": 81920 + } }, "extra_capabilities": { "reasoning": { @@ -214645,33 +213242,37 @@ }, "attachment": false, "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-04-20", - "last_updated": "2026-04-20", + "knowledge": "2025-04", + "release_date": "2025-04", + "last_updated": "2025-04", "type": "chat" }, { - "id": "moonshotai/kimi-k3.1-pro", - "name": "Kimi K3.1 Pro", - "display_name": "Kimi K3.1 Pro", + "id": "qwq-plus", + "name": "QwQ Plus", + "display_name": "QwQ Plus", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 131072, + "output": 8192 }, - "temperature": false, - "tool_call": true, + "temperature": true, + "tool_call": false, "reasoning": { "supported": true, - "default": true + "default": true, + "budget": { + "default": 32768, + "min": 0, + "max": 32768 + } }, "extra_capabilities": { "reasoning": { @@ -214681,24 +213282,26 @@ "visibility": "summary", "continuation": [ "thinking_blocks" - ], - "effort_options": [ - "low", - "high", - "max" ] } }, - "attachment": true, + "search": { + "supported": true, + "default": false, + "forced_search": false, + "search_strategy": "turbo" + }, + "attachment": false, "open_weights": false, - "release_date": "2026-10-23", - "last_updated": "2026-10-23", + "knowledge": "2024-04", + "release_date": "2025-03-05", + "last_updated": "2025-03-05", "type": "chat" }, { - "id": "qwen/qwen3.7-max", - "name": "Qwen3.7 Max", - "display_name": "Qwen3.7 Max", + "id": "deepseek-r1-distill-llama-8b", + "name": "DeepSeek R1 Distill Llama 8B", + "display_name": "DeepSeek R1 Distill Llama 8B", "modalities": { "input": [ "text" @@ -214708,8 +213311,8 @@ ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 32768, + "output": 16384 }, "temperature": true, "tool_call": true, @@ -214717,25 +213320,19 @@ "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "supported": true - } - }, "attachment": false, "open_weights": false, - "release_date": "2026-05-21", - "last_updated": "2026-05-21", + "release_date": "2025-01-01", + "last_updated": "2025-01-01", "type": "chat" }, { - "id": "qwen/qwen3.5-397b-a17b", - "name": "Qwen3.5 397B-A17B", - "display_name": "Qwen3.5 397B-A17B", + "id": "qwen3-coder-480b-a35b-instruct", + "name": "Qwen3-Coder 480B-A35B Instruct", + "display_name": "Qwen3-Coder 480B-A35B Instruct", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" @@ -214748,30 +213345,19 @@ "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } + "supported": false }, - "attachment": true, + "attachment": false, "open_weights": true, - "release_date": "2026-02-15", - "last_updated": "2026-02-15", + "knowledge": "2025-04", + "release_date": "2025-04", + "last_updated": "2025-04", "type": "chat" }, { - "id": "deepseek-ai/DeepSeek-V4.1-Flash-1017-abliterated", - "name": "DeepSeek V4.1 Flash 1017 Abliterated", - "display_name": "DeepSeek V4.1 Flash 1017 Abliterated", + "id": "qwen3-next-80b-a3b-thinking", + "name": "Qwen3 Next 80B A3B Thinking", + "display_name": "Qwen3 Next 80B A3B Thinking", "modalities": { "input": [ "text" @@ -214781,14 +213367,19 @@ ] }, "limit": { - "context": 1048576, - "output": 384000 + "context": 131072, + "output": 32768 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { "supported": true, - "default": true + "default": true, + "budget": { + "default": 81920, + "min": 0, + "max": 81920 + } }, "extra_capabilities": { "reasoning": { @@ -214798,35 +213389,32 @@ "visibility": "summary", "continuation": [ "thinking_blocks" - ], - "effort_options": [ - "low", - "high", - "max" ] } }, "attachment": false, - "open_weights": false, - "release_date": "2026-10-29", - "last_updated": "2026-10-29", + "open_weights": true, + "knowledge": "2025-04", + "release_date": "2025-09", + "last_updated": "2025-09", "type": "chat" }, { - "id": "deepseek-ai/DeepSeek-V4.1-Flash-1017", - "name": "DeepSeek V4.1 Flash 1017", - "display_name": "DeepSeek V4.1 Flash 1017", + "id": "qwen3-vl-235b-a22b", + "name": "Qwen3-VL 235B-A22B", + "display_name": "Qwen3-VL 235B-A22B", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 384000 + "context": 131072, + "output": 32768 }, "temperature": true, "tool_call": true, @@ -214842,47 +213430,32 @@ "visibility": "summary", "continuation": [ "thinking_blocks" - ], - "effort_options": [ - "low", - "high", - "max" ] } }, - "attachment": false, + "attachment": true, "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-10-17", - "last_updated": "2026-10-17", + "knowledge": "2025-04", + "release_date": "2025-04", + "last_updated": "2025-04", "type": "chat" - } - ] - }, - "aixy": { - "id": "aixy", - "name": "Aixy", - "display_name": "Aixy", - "api": "https://api.aixy-gateway.com/v1", - "doc": "https://docs.aixy-gateway.com/integrations/overview", - "models": [ + }, { - "id": "gemini/gemini-3.8-flash", - "name": "Gemini 3.8 Flash", - "display_name": "Gemini 3.8 Flash", + "id": "deepseek-v4.1-flash", + "name": "DeepSeek V4.1 Flash", + "display_name": "DeepSeek V4.1 Flash", "modalities": { "input": [ "text", - "image", - "audio" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 1000000, + "output": 384000 }, "temperature": true, "tool_call": true, @@ -214893,161 +213466,135 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "level", - "level": "high", - "level_options": [ - "minimal", - "low", - "medium", - "high" - ], + "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ - "thought_signatures" + "thinking_blocks" + ], + "effort_options": [ + "low", + "high", + "max" ] } }, "attachment": true, - "open_weights": false, - "release_date": "2026-09-02", - "last_updated": "2026-09-02", + "open_weights": true, + "knowledge": "2025-05", + "release_date": "2026-09-10", + "last_updated": "2026-09-10", "type": "chat" }, { - "id": "gemini/gemini-4-argon", - "name": "Gemini 4 Argon", - "display_name": "Gemini 4 Argon", + "id": "deepseek-v3-1", + "name": "DeepSeek V3.1", + "display_name": "DeepSeek V3.1", "modalities": { "input": [ - "text", - "image", - "audio" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 32768 + "context": 131072, + "output": 65536 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "low", - "medium", - "high" - ] - } + "supported": false }, - "attachment": true, + "attachment": false, "open_weights": false, - "knowledge": "2026-03", - "release_date": "2026-10-13", - "last_updated": "2026-10-13", + "release_date": "2025-01-01", + "last_updated": "2025-01-01", "type": "chat" }, { - "id": "anthropic/claude-sonnet-5-5", - "name": "Claude Sonnet 5.5", - "display_name": "Claude Sonnet 5.5", + "id": "qvq-max", + "name": "QVQ Max", + "display_name": "QVQ Max", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 131072, + "output": 8192 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" - ] - } - }, "attachment": true, "open_weights": false, - "knowledge": "2026-06", - "release_date": "2026-09-28", - "last_updated": "2026-09-28", + "knowledge": "2024-04", + "release_date": "2025-03-25", + "last_updated": "2025-03-25", "type": "chat" }, { - "id": "anthropic/claude-haiku-5-5", - "name": "Claude Haiku 5.5", - "display_name": "Claude Haiku 5.5", + "id": "qwen3-14b", + "name": "Qwen3 14B", + "display_name": "Qwen3 14B", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 131072, + "output": 8192 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": true, + "budget": { + "default": 38912, + "min": 0, + "max": 38912 + } }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-06", - "release_date": "2026-10-07", - "last_updated": "2026-10-07", + "attachment": false, + "open_weights": true, + "knowledge": "2025-04", + "release_date": "2025-04", + "last_updated": "2025-04", "type": "chat" }, { - "id": "anthropic/claude-opus-5-5", - "name": "Claude Opus 5.5", - "display_name": "Claude Opus 5.5", + "id": "qwen3-coder-flash", + "name": "Qwen3 Coder Flash", + "display_name": "Qwen3 Coder Flash", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" @@ -215055,41 +213602,27 @@ }, "limit": { "context": 1000000, - "output": 128000 + "output": 65536 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" - ] - } + "supported": false }, - "attachment": true, + "attachment": false, "open_weights": false, - "knowledge": "2026-06", - "release_date": "2026-09-22", - "last_updated": "2026-09-22", + "knowledge": "2025-04", + "release_date": "2025-07-28", + "last_updated": "2025-07-28", "type": "chat" }, { - "id": "anthropic/claude-fable-5-1", - "name": "Claude Fable 5.1", - "display_name": "Claude Fable 5.1", + "id": "deepseek-v4-pro", + "name": "DeepSeek V4 Pro", + "display_name": "DeepSeek V4 Pro", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" @@ -215097,9 +213630,9 @@ }, "limit": { "context": 1000000, - "output": 128000 + "output": 384000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -215113,50 +213646,44 @@ "effort": "high", "effort_options": [ "low", - "medium", "high", - "xhigh", "max" ], "interleaved": true, "summaries": true, - "visibility": "omitted", + "visibility": "summary", "continuation": [ "thinking_blocks" ], "notes": [ - "Adaptive thinking is always on for Claude Fable 5 and Claude Mythos 5; thinking.type = \"disabled\" is rejected.", - "Manual budget_tokens requests return 400 on Claude Fable 5 and Claude Mythos 5.", - "thinking.display defaults to omitted; set display to summarized to receive readable thinking summaries." + "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-06", - "release_date": "2026-09-01", - "last_updated": "2026-09-01", + "attachment": false, + "open_weights": true, + "knowledge": "2025-05", + "release_date": "2026-04-24", + "last_updated": "2026-04-24", "type": "chat" }, { - "id": "openai/gpt-6-astra", - "name": "GPT-6 Astra", - "display_name": "GPT-6 Astra", + "id": "glm-5.3", + "name": "GLM-5.3", + "display_name": "GLM-5.3", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 1000000, + "output": 131072 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -215165,84 +213692,42 @@ "extra_capabilities": { "reasoning": { "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ "low", - "medium", "high", - "xhigh", "max" ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-04-30", - "release_date": "2026-09-04", - "last_updated": "2026-09-04", + "attachment": false, + "open_weights": true, + "release_date": "2026-08-14", + "last_updated": "2026-08-14", "type": "chat" }, { - "id": "openai/gpt-6-sol", - "name": "GPT-6 Sol", - "display_name": "GPT-6 Sol", + "id": "glm-5", + "name": "GLM-5", + "display_name": "GLM-5", "modalities": { "input": [ - "text", - "image", - "pdf" - ], - "output": [ "text" - ] - }, - "limit": { - "context": 1050000, - "output": 128000 - }, - "temperature": false, - "tool_call": true, - "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "none", - "low", - "medium", - "high", - "xhigh", - "max" - ] - } - }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-04-20", - "release_date": "2026-09-22", - "last_updated": "2026-09-22", - "type": "chat" - }, - { - "id": "openai/gpt-5.6-sol", - "name": "GPT-5.6 Sol", - "display_name": "GPT-5.6 Sol", - "modalities": { - "input": [ - "text", - "image", - "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 202752, + "output": 16384 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -215251,106 +213736,69 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", - "effort_options": [ - "none", - "low", - "medium", - "high", - "xhigh", - "max" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] } }, - "attachment": true, + "attachment": false, "open_weights": false, - "knowledge": "2026-02-16", - "release_date": "2026-07-09", - "last_updated": "2026-07-09", + "release_date": "2026-02-11", + "last_updated": "2026-02-11", "type": "chat" }, { - "id": "openai/gpt-5.6-terra", - "name": "GPT-5.6 Terra", - "display_name": "GPT-5.6 Terra", + "id": "qwen-mt-turbo", + "name": "Qwen Mt Turbo", + "display_name": "Qwen Mt Turbo", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 16384, + "output": 8192 }, - "temperature": false, - "tool_call": true, + "temperature": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", - "effort_options": [ - "none", - "low", - "medium", - "high", - "xhigh", - "max" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" - } + "supported": false }, - "attachment": true, + "attachment": false, "open_weights": false, - "knowledge": "2026-02-16", - "release_date": "2026-07-09", - "last_updated": "2026-07-09", + "knowledge": "2024-04", + "release_date": "2025-01", + "last_updated": "2025-01", "type": "chat" }, { - "id": "openai/gpt-6-luna", - "name": "GPT-6 Luna", - "display_name": "GPT-6 Luna", + "id": "qwen3-omni-flash", + "name": "Qwen3-Omni Flash", + "display_name": "Qwen3-Omni Flash", "modalities": { "input": [ "text", "image", - "pdf" + "audio", + "video" ], "output": [ - "text" + "text", + "audio" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 65536, + "output": 16384 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -215358,215 +213806,143 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "none", - "low", - "medium", - "high", - "xhigh", - "max" + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-05-18", - "release_date": "2026-09-22", - "last_updated": "2026-09-22", + "knowledge": "2024-04", + "release_date": "2025-09-15", + "last_updated": "2025-09-15", "type": "chat" }, { - "id": "openai/gpt-5.6-luna", - "name": "GPT-5.6 Luna", - "display_name": "GPT-5.6 Luna", + "id": "qwen3-coder-30b-a3b-instruct", + "name": "Qwen3-Coder 30B-A3B Instruct", + "display_name": "Qwen3-Coder 30B-A3B Instruct", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 262144, + "output": 65536 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", - "effort_options": [ - "none", - "low", - "medium", - "high", - "xhigh", - "max" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" - } + "supported": false }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-02-16", - "release_date": "2026-07-09", - "last_updated": "2026-07-09", + "attachment": false, + "open_weights": true, + "knowledge": "2025-04", + "release_date": "2025-04", + "last_updated": "2025-04", "type": "chat" }, { - "id": "openai/gpt-6.1-sol", - "name": "GPT-6.1 Sol", - "display_name": "GPT-6.1 Sol", + "id": "qwen-flash", + "name": "Qwen Flash", + "display_name": "Qwen Flash", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 1000000, + "output": 32768 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false, + "budget": { + "default": 81920, + "min": 0, + "max": 81920 + } }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-04-30", - "release_date": "2026-09-29", - "last_updated": "2026-09-29", - "type": "chat" - } - ] - }, - "claudinio": { - "id": "claudinio", - "name": "Claudinio", - "display_name": "Claudinio", - "api": "https://api.claudin.io/v1", - "doc": "https://claudin.io", - "models": [ - { - "id": "claudinio", - "name": "Claudinio", - "display_name": "Claudinio", - "modalities": { - "input": [ - "text", - "image", - "audio", - "video" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 256000, - "output": 64000 - }, - "tool_call": true, - "reasoning": { + "search": { "supported": true, - "default": true + "default": false, + "forced_search": false, + "search_strategy": "turbo" }, - "attachment": true, + "attachment": false, "open_weights": false, - "knowledge": "2026-05", - "release_date": "2026-05-12", - "last_updated": "2026-06-02", + "knowledge": "2024-04", + "release_date": "2025-07-28", + "last_updated": "2025-07-28", "type": "chat" }, { - "id": "claudius", - "name": "Claudius", - "display_name": "Claudius", + "id": "deepseek-v3-2-exp", + "name": "DeepSeek V3.2 Exp", + "display_name": "DeepSeek V3.2 Exp", "modalities": { "input": [ - "text", - "image", - "audio", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 256000, - "output": 64000 + "context": 131072, + "output": 65536 }, + "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true + "supported": false }, - "attachment": true, + "attachment": false, "open_weights": false, - "knowledge": "2026-05", - "release_date": "2026-05-12", - "last_updated": "2026-05-12", + "release_date": "2025-01-01", + "last_updated": "2025-01-01", "type": "chat" - } - ] - }, - "privatemode-ai": { - "id": "privatemode-ai", - "name": "Privatemode AI", - "display_name": "Privatemode AI", - "api": "http://localhost:8080/v1", - "doc": "https://docs.privatemode.ai/api/overview", - "models": [ + }, { - "id": "kimi-latest", - "name": "Kimi (latest)", - "display_name": "Kimi (latest)", + "id": "deepseek-r1", + "name": "DeepSeek R1", + "display_name": "DeepSeek R1", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 256000, - "output": 262144 + "context": 131072, + "output": 16384 }, "temperature": true, "tool_call": true, @@ -215574,21 +213950,30 @@ "supported": true, "default": true }, - "attachment": true, - "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-04-20", - "last_updated": "2026-04-20", + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } + }, + "attachment": false, + "open_weights": false, + "release_date": "2025-01-01", + "last_updated": "2025-01-01", "type": "chat" }, { - "id": "glm-5.3-flash", - "name": "GLM-5.3-Flash", - "display_name": "GLM-5.3-Flash", + "id": "deepseek-v4-flash", + "name": "DeepSeek V4 Flash", + "display_name": "DeepSeek V4 Flash", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" @@ -215596,7 +213981,7 @@ }, "limit": { "context": 1000000, - "output": 131072 + "output": 384000 }, "temperature": true, "tool_call": true, @@ -215607,89 +213992,117 @@ "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "high", "effort_options": [ "low", "high", "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." ] } }, - "attachment": true, + "attachment": false, "open_weights": true, - "release_date": "2026-08-26", - "last_updated": "2026-08-26", + "knowledge": "2025-05", + "release_date": "2026-04-24", + "last_updated": "2026-04-24", "type": "chat" }, { - "id": "whisper-large-v3", - "name": "Whisper large-v3", - "display_name": "Whisper large-v3", + "id": "qwen3.6-plus", + "name": "Qwen3.6 Plus", + "display_name": "Qwen3.6 Plus", "modalities": { "input": [ - "audio" + "text", + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 448, - "output": 4096 + "context": 1000000, + "output": 65536 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } }, "attachment": true, - "open_weights": true, - "release_date": "2023-11-06", - "last_updated": "2023-11-06" + "open_weights": false, + "knowledge": "2025-04", + "release_date": "2026-04-02", + "last_updated": "2026-04-02", + "type": "chat" }, { - "id": "deepseek-ocr-2", - "name": "DeepSeek OCR 2", - "display_name": "DeepSeek OCR 2", + "id": "qwen-math-turbo", + "name": "Qwen Math Turbo", + "display_name": "Qwen Math Turbo", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 8192, - "output": 8192 + "context": 4096, + "output": 3072 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { "supported": false }, - "attachment": true, - "open_weights": true, - "release_date": "2026-01-27", - "last_updated": "2026-01-27", + "attachment": false, + "open_weights": false, + "knowledge": "2024-04", + "release_date": "2024-09-19", + "last_updated": "2024-09-19", "type": "chat" }, { - "id": "kimi-k2.6", - "name": "Kimi K2.6", - "display_name": "Kimi K2.6", + "id": "glm-5.1", + "name": "GLM-5.1", + "display_name": "GLM-5.1", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 256000, - "output": 262144 + "context": 202752, + "output": 128000 }, "temperature": true, "tool_call": true, @@ -215699,20 +214112,25 @@ }, "extra_capabilities": { "reasoning": { - "supported": true + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] } }, - "attachment": true, + "attachment": false, "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-04-20", - "last_updated": "2026-04-20", + "release_date": "2026-04-14", + "last_updated": "2026-04-14", "type": "chat" }, { - "id": "gpt-oss-120b", - "name": "gpt-oss-120b", - "display_name": "gpt-oss-120b", + "id": "qwen3.6-max-preview", + "name": "Qwen3.6 Max Preview", + "display_name": "Qwen3.6 Max Preview", "modalities": { "input": [ "text" @@ -215722,8 +214140,8 @@ ] }, "limit": { - "context": 128000, - "output": 32768 + "context": 245800, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -215734,28 +214152,27 @@ "extra_capabilities": { "reasoning": { "supported": true, - "effort_options": [ - "low", - "medium", - "high" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, "attachment": false, - "open_weights": true, - "knowledge": "2024-06", - "release_date": "2025-08-05", - "last_updated": "2025-08-05", + "open_weights": false, + "release_date": "2026-04-20", + "last_updated": "2026-04-21", "type": "chat" }, { - "id": "glm-flash-latest", - "name": "GLM Flash (latest)", - "display_name": "GLM Flash (latest)", + "id": "qwen3-coder-plus", + "name": "Qwen3 Coder Plus", + "display_name": "Qwen3 Coder Plus", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" @@ -215763,33 +214180,24 @@ }, "limit": { "context": 1000000, - "output": 131072 + "output": 65536 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "low", - "high", - "max" - ] - } + "supported": false }, - "attachment": true, + "attachment": false, "open_weights": true, - "release_date": "2026-08-26", - "last_updated": "2026-08-26", + "knowledge": "2025-04", + "release_date": "2025-07-23", + "last_updated": "2026-09-11", "type": "chat" }, { - "id": "glm-5.3", - "name": "GLM-5.3", - "display_name": "GLM-5.3", + "id": "moonshot-kimi-k2-instruct", + "name": "Moonshot Kimi K2 Instruct", + "display_name": "Moonshot Kimi K2 Instruct", "modalities": { "input": [ "text" @@ -215799,35 +214207,24 @@ ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 131072, + "output": 8192 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "effort_options": [ - "low", - "high", - "max" - ] - } + "supported": false }, "attachment": false, "open_weights": true, - "release_date": "2026-08-14", - "last_updated": "2026-08-14", + "release_date": "2025-01-01", + "last_updated": "2025-01-01", "type": "chat" }, { - "id": "glm-latest", - "name": "GLM (latest)", - "display_name": "GLM (latest)", + "id": "glm-5.2", + "name": "GLM-5.2", + "display_name": "GLM-5.2", "modalities": { "input": [ "text" @@ -215838,7 +214235,7 @@ }, "limit": { "context": 1000000, - "output": 131072 + "output": 128000 }, "temperature": true, "tool_call": true, @@ -215848,8 +214245,14 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ - "low", "high", "max" ] @@ -215857,117 +214260,150 @@ }, "attachment": false, "open_weights": true, - "release_date": "2026-08-14", - "last_updated": "2026-08-14", + "release_date": "2026-06-13", + "last_updated": "2026-06-13", "type": "chat" }, { - "id": "voxtral-mini-3b", - "name": "Voxtral Mini 3B", - "display_name": "Voxtral Mini 3B", + "id": "qwen-omni-turbo-realtime", + "name": "Qwen-Omni Turbo Realtime", + "display_name": "Qwen-Omni Turbo Realtime", "modalities": { "input": [ + "text", + "image", "audio" ], "output": [ - "text" + "text", + "audio" ] }, "limit": { - "context": 32000, - "output": 32000 + "context": 32768, + "output": 2048 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { "supported": false }, "attachment": true, - "open_weights": true, - "release_date": "2025-07-15", - "last_updated": "2025-07-15" + "open_weights": false, + "knowledge": "2024-04", + "release_date": "2025-05-08", + "last_updated": "2025-05-08" }, { - "id": "qwen3-embedding-4b", - "name": "Qwen3-Embedding 4B", - "display_name": "Qwen3-Embedding 4B", + "id": "qwen3.8-max", + "name": "Qwen3.8 Max", + "display_name": "Qwen3.8 Max", "modalities": { "input": [ - "text" + "text", + "image", + "video", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 32000, - "output": 2560 + "context": 1000000, + "output": 131072 }, - "temperature": false, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-06", - "release_date": "2025-06-05", - "last_updated": "2025-06-05", - "type": "embedding" - } - ] - }, - "oci": { - "id": "oci", - "name": "OCI Generative AI", - "display_name": "OCI Generative AI", - "api": "https://inference.generativeai.us-chicago-1.oci.oraclecloud.com/openai/v1", - "doc": "https://docs.oracle.com/en-us/iaas/Content/generative-ai/pretrained-models.htm", - "models": [ + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "budget": { + "min": 0, + "max": 262144, + "unit": "tokens" + }, + "effort": "xhigh", + "effort_options": [ + "none", + "low", + "medium", + "xhigh" + ], + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Prefer reasoning_effort; thinking_budget is an alternative, not an additional control.", + "Omitting both controls differs from explicitly selecting xhigh, which maps to 262144 budget tokens." + ], + "budget_exclusive_with_effort": true + } + }, + "attachment": true, + "open_weights": false, + "release_date": "2026-08-03", + "last_updated": "2026-08-03", + "type": "chat" + }, { - "id": "meta.llama-4-maverick-17b-128e-instruct-fp8", - "name": "Llama 4 Maverick 17B Instruct", - "display_name": "Llama 4 Maverick 17B Instruct", + "id": "qwen2-5-omni-7b", + "name": "Qwen2.5-Omni 7B", + "display_name": "Qwen2.5-Omni 7B", "modalities": { "input": [ "text", - "image" + "image", + "audio", + "video" ], "output": [ - "text" + "text", + "audio" ] }, "limit": { - "context": 512000, - "output": 16384 + "context": 32768, + "output": 2048 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { "supported": false }, "attachment": true, "open_weights": true, - "knowledge": "2024-08", - "release_date": "2025-04-05", - "last_updated": "2025-04-05", + "knowledge": "2024-04", + "release_date": "2024-12", + "last_updated": "2024-12", "type": "chat" }, { - "id": "openai.gpt-oss-20b", - "name": "GPT OSS 20B", - "display_name": "GPT OSS 20B", + "id": "qwen3.5-397b-a17b", + "name": "Qwen3.5 397B-A17B", + "display_name": "Qwen3.5 397B-A17B", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 16384 + "context": 262144, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -215983,25 +214419,56 @@ "visibility": "summary", "continuation": [ "thinking_blocks" - ], - "effort_options": [ - "low", - "medium", - "high" ] } }, - "attachment": false, + "attachment": true, "open_weights": true, - "knowledge": "2024-06", - "release_date": "2025-08-05", - "last_updated": "2025-08-05", + "knowledge": "2025-04", + "release_date": "2026-02-16", + "last_updated": "2026-02-16", "type": "chat" }, { - "id": "meta.llama-3.3-70b-instruct", - "name": "Llama-3.3-70B-Instruct", - "display_name": "Llama-3.3-70B-Instruct", + "id": "qwen3.7-plus", + "name": "Qwen3.7 Plus", + "display_name": "Qwen3.7 Plus", + "modalities": { + "input": [ + "text", + "image", + "video" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1000000, + "output": 64000 + }, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true + } + }, + "attachment": true, + "open_weights": false, + "knowledge": "2025-04", + "release_date": "2026-06-02", + "last_updated": "2026-06-02", + "type": "chat" + }, + { + "id": "qwen-plus-character", + "name": "Qwen Plus Character", + "display_name": "Qwen Plus Character", "modalities": { "input": [ "text" @@ -216011,7 +214478,7 @@ ] }, "limit": { - "context": 128000, + "context": 32768, "output": 4096 }, "temperature": true, @@ -216020,16 +214487,16 @@ "supported": false }, "attachment": false, - "open_weights": true, - "knowledge": "2023-12", - "release_date": "2024-12-06", - "last_updated": "2024-12-06", + "open_weights": false, + "knowledge": "2024-04", + "release_date": "2024-01", + "last_updated": "2024-01", "type": "chat" }, { - "id": "openai.gpt-oss-120b", - "name": "GPT OSS 120B", - "display_name": "GPT OSS 120B", + "id": "qwen3-32b", + "name": "Qwen3 32B", + "display_name": "Qwen3 32B", "modalities": { "input": [ "text" @@ -216039,14 +214506,19 @@ ] }, "limit": { - "context": 128000, + "context": 131072, "output": 16384 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": true, + "budget": { + "default": 38912, + "min": 0, + "max": 38912 + } }, "extra_capabilities": { "reasoning": { @@ -216056,83 +214528,86 @@ "visibility": "summary", "continuation": [ "thinking_blocks" - ], - "effort_options": [ - "low", - "medium", - "high" ] } }, "attachment": false, "open_weights": true, - "knowledge": "2024-06", - "release_date": "2025-08-05", - "last_updated": "2025-08-05", + "knowledge": "2025-04", + "release_date": "2025-04", + "last_updated": "2025-04", "type": "chat" }, { - "id": "xai.grok-4.20-reasoning", - "name": "Grok 4.20 (Reasoning)", - "display_name": "Grok 4.20 (Reasoning)", + "id": "qwen-max", + "name": "Qwen Max", + "display_name": "Qwen Max", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 30000 + "context": 32768, + "output": 8192 }, "temperature": true, "tool_call": true, "reasoning": { + "supported": false + }, + "search": { "supported": true, - "default": true + "default": false, + "forced_search": false, + "search_strategy": "turbo" }, - "attachment": true, + "attachment": false, "open_weights": false, - "release_date": "2026-03-09", - "last_updated": "2026-03-09", + "knowledge": "2024-04", + "release_date": "2024-04-03", + "last_updated": "2025-01-25", "type": "chat" }, { - "id": "meta.llama-4-scout-17b-16e-instruct", - "name": "Llama 4 Scout 17B Instruct", - "display_name": "Llama 4 Scout 17B Instruct", + "id": "qwen-omni-turbo", + "name": "Qwen-Omni Turbo", + "display_name": "Qwen-Omni Turbo", "modalities": { "input": [ "text", - "image" + "image", + "audio", + "video" ], "output": [ - "text" + "text", + "audio" ] }, "limit": { - "context": 192000, - "output": 16384 + "context": 32768, + "output": 2048 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { "supported": false }, "attachment": true, - "open_weights": true, - "knowledge": "2024-08", - "release_date": "2025-04-05", - "last_updated": "2025-04-05", + "open_weights": false, + "knowledge": "2024-04", + "release_date": "2025-01-19", + "last_updated": "2025-03-26", "type": "chat" }, { - "id": "xai.grok-4.20-non-reasoning", - "name": "Grok 4.20 (Non-Reasoning)", - "display_name": "Grok 4.20 (Non-Reasoning)", + "id": "qwen-vl-ocr", + "name": "Qwen Vl Ocr", + "display_name": "Qwen Vl Ocr", "modalities": { "input": [ "text", @@ -216143,24 +214618,25 @@ ] }, "limit": { - "context": 1000000, - "output": 30000 + "context": 34096, + "output": 4096 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { "supported": false }, - "attachment": true, + "attachment": false, "open_weights": false, - "release_date": "2026-03-09", - "last_updated": "2026-03-09", + "knowledge": "2024-04", + "release_date": "2024-10-28", + "last_updated": "2026-09-11", "type": "chat" }, { - "id": "xai.grok-4.6", - "name": "Grok 4.6", - "display_name": "Grok 4.6", + "id": "qwen3-vl-plus", + "name": "Qwen3 VL Plus", + "display_name": "Qwen3 VL Plus", "modalities": { "input": [ "text", @@ -216171,147 +214647,111 @@ ] }, "limit": { - "context": 500000, - "output": 500000 + "context": 262144, + "output": 32768 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false, + "budget": { + "default": 81920, + "min": 0, + "max": 81920 + } }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "low", - "medium", - "high", - "xhigh" + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, - "attachment": true, + "attachment": false, "open_weights": false, - "knowledge": "2026-02-01", - "release_date": "2026-08-12", - "last_updated": "2026-08-12", + "knowledge": "2025-04", + "release_date": "2025-09-23", + "last_updated": "2025-09-23", "type": "chat" }, { - "id": "xai.grok-4.3", - "name": "Grok 4.3", - "display_name": "Grok 4.3", + "id": "qwen-mt-plus", + "name": "Qwen Mt Plus", + "display_name": "Qwen Mt Plus", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 30000 + "context": 16384, + "output": 8192 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "none", - "low", - "medium", - "high" - ] - } + "supported": false }, - "attachment": true, + "attachment": false, "open_weights": false, - "release_date": "2026-04-17", - "last_updated": "2026-04-17", + "knowledge": "2024-04", + "release_date": "2025-01", + "last_updated": "2025-01", "type": "chat" - } - ] - }, - "auriko": { - "id": "auriko", - "name": "Auriko", - "display_name": "Auriko", - "api": "https://api.auriko.ai/v1", - "doc": "https://docs.auriko.ai", - "models": [ + }, { - "id": "gemini-2.5-pro", - "name": "Gemini 2.5 Pro", - "display_name": "Gemini 2.5 Pro", + "id": "qwen-long", + "name": "Qwen Long", + "display_name": "Qwen Long", "modalities": { "input": [ - "text", - "image", - "audio", - "video", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 1000000, + "output": 8192 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "budget", - "budget": { - "default": -1, - "min": 128, - "max": 32768, - "auto": -1, - "unit": "tokens" - }, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thought_signatures" - ] - } + "supported": false }, - "attachment": true, + "attachment": false, "open_weights": false, - "knowledge": "2025-01", - "release_date": "2025-06-17", - "last_updated": "2025-06-17", + "knowledge": "2024-04", + "release_date": "2025-01-25", + "last_updated": "2025-01-25", "type": "chat" }, { - "id": "deepseek-v4-flash", - "name": "DeepSeek V4 Flash", - "display_name": "DeepSeek V4 Flash", + "id": "kimi-k2.5", + "name": "Moonshot Kimi K2.5", + "display_name": "Moonshot Kimi K2.5", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 384000 + "context": 262144, + "output": 32768 }, "temperature": true, "tool_call": true, @@ -216322,49 +214762,36 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "high", - "effort_options": [ - "low", - "high", - "max" - ], "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ "thinking_blocks" - ], - "notes": [ - "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." ] } }, - "attachment": false, + "attachment": true, "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-04-24", - "last_updated": "2026-04-24", + "knowledge": "2025-01", + "release_date": "2026-01-27", + "last_updated": "2026-01-27", "type": "chat" }, { - "id": "qwen-3.6-plus", - "name": "Qwen3.6 Plus", - "display_name": "Qwen3.6 Plus", + "id": "deepseek-r1-distill-qwen-7b", + "name": "DeepSeek R1 Distill Qwen 7B", + "display_name": "DeepSeek R1 Distill Qwen 7B", "modalities": { "input": [ - "text", - "image", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 65536 + "context": 32768, + "output": 16384 }, "temperature": true, "tool_call": true, @@ -216372,28 +214799,29 @@ "supported": true, "default": true }, - "attachment": true, + "attachment": false, "open_weights": false, - "knowledge": "2025-04", - "release_date": "2026-04-02", - "last_updated": "2026-04-02", + "release_date": "2025-01-01", + "last_updated": "2025-01-01", "type": "chat" }, { - "id": "deepseek-v4-pro", - "name": "DeepSeek V4 Pro", - "display_name": "DeepSeek V4 Pro", + "id": "kimi-k2.6", + "name": "Moonshot Kimi K2.6", + "display_name": "Moonshot Kimi K2.6", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 384000 + "context": 262144, + "output": 16384 }, "temperature": true, "tool_call": true, @@ -216404,93 +214832,64 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "high", - "effort_options": [ - "low", - "high", - "max" - ], "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ "thinking_blocks" - ], - "notes": [ - "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." ] } }, - "attachment": false, + "attachment": true, "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-04-24", - "last_updated": "2026-04-24", + "knowledge": "2025-01", + "release_date": "2026-04-21", + "last_updated": "2026-04-21", "type": "chat" }, { - "id": "claude-opus-4-7", - "name": "Claude Opus 4.7", - "display_name": "Claude Opus 4.7", + "id": "MiniMax/MiniMax-M2.7", + "name": "MiniMax-M2.7", + "display_name": "MiniMax-M2.7", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 204800, + "output": 131072 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "high", - "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" - ], "interleaved": true, "summaries": true, - "visibility": "omitted", + "visibility": "summary", "continuation": [ "thinking_blocks" - ], - "notes": [ - "Claude Opus 4.7 and newer Opus models require thinking.type = \"adaptive\" to enable thinking explicitly.", - "Manual budget_tokens requests return 400 on Claude Opus 4.7 and newer adaptive-only Opus models.", - "task_budget is separate from thinking control and should not be treated as a thinking budget." ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-01-31", - "release_date": "2026-04-16", - "last_updated": "2026-04-16", + "attachment": false, + "open_weights": true, + "release_date": "2026-03-18", + "last_updated": "2026-03-18", "type": "chat" }, { - "id": "kimi-k2.6", - "name": "Kimi K2.6", - "display_name": "Kimi K2.6", + "id": "kimi/kimi-k2.5", + "name": "kimi/kimi-k2.5", + "display_name": "kimi/kimi-k2.5", "modalities": { "input": [ "text", @@ -216505,7 +214904,7 @@ "context": 262144, "output": 262144 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -216519,42 +214918,33 @@ "visibility": "summary", "continuation": [ "thinking_blocks" - ], - "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" ] } }, "attachment": true, "open_weights": true, "knowledge": "2025-01", - "release_date": "2026-04-20", - "last_updated": "2026-04-20", + "release_date": "2026-01-27", + "last_updated": "2026-01-27", "type": "chat" }, { - "id": "kimi-k2.5", - "name": "Kimi K2.5", - "display_name": "Kimi K2.5", + "id": "siliconflow/deepseek-r1-0528", + "name": "siliconflow/deepseek-r1-0528", + "display_name": "siliconflow/deepseek-r1-0528", "modalities": { "input": [ - "text", - "image", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 163840, + "output": 32768 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -216568,41 +214958,29 @@ "visibility": "summary", "continuation": [ "thinking_blocks" - ], - "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" ] } }, - "attachment": true, - "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-01-27", - "last_updated": "2026-01-27", + "attachment": false, + "open_weights": false, + "release_date": "2025-05-28", + "last_updated": "2025-11-25", "type": "chat" }, { - "id": "gemini-2.5-flash", - "name": "Gemini 2.5 Flash", - "display_name": "Gemini 2.5 Flash", + "id": "siliconflow/deepseek-v3.2", + "name": "siliconflow/deepseek-v3.2", + "display_name": "siliconflow/deepseek-v3.2", "modalities": { "input": [ - "text", - "image", - "audio", - "video", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, + "context": 163840, "output": 65536 }, "temperature": true, @@ -216613,94 +214991,46 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "budget", - "budget": { - "default": -1, - "min": 0, - "max": 24576, - "auto": -1, - "off": 0, - "unit": "tokens" - }, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thought_signatures" - ] + "supported": true } }, - "attachment": true, + "attachment": false, "open_weights": false, - "knowledge": "2025-01", - "release_date": "2025-06-17", - "last_updated": "2025-06-17", + "release_date": "2025-12-03", + "last_updated": "2025-12-03", "type": "chat" }, { - "id": "claude-sonnet-4-6", - "name": "Claude Sonnet 4.6", - "display_name": "Claude Sonnet 4.6", + "id": "siliconflow/deepseek-v3-0324", + "name": "siliconflow/deepseek-v3-0324", + "display_name": "siliconflow/deepseek-v3-0324", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 64000 + "context": 163840, + "output": 163840 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": false - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "mixed", - "budget": { - "min": 1024, - "unit": "tokens" - }, - "effort": "high", - "effort_options": [ - "low", - "medium", - "high", - "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Anthropic recommends adaptive thinking with effort for Claude 4.6; budget_tokens remains a deprecated compatibility path.", - "Anthropic API defaults effort to high; lower effort levels should be chosen per workload." - ] - } + "supported": false }, - "attachment": true, + "attachment": false, "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-02-17", - "last_updated": "2026-03-13", + "release_date": "2024-12-26", + "last_updated": "2025-11-25", "type": "chat" }, { - "id": "minimax-m2-7-highspeed", - "name": "MiniMax-M2.7-highspeed", - "display_name": "MiniMax-M2.7-highspeed", + "id": "siliconflow/deepseek-v3.1-terminus", + "name": "siliconflow/deepseek-v3.1-terminus", + "display_name": "siliconflow/deepseek-v3.1-terminus", "modalities": { "input": [ "text" @@ -216710,8 +215040,8 @@ ] }, "limit": { - "context": 204800, - "output": 131072 + "context": 163840, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -216720,20 +215050,18 @@ "default": true }, "attachment": false, - "open_weights": true, - "release_date": "2026-03-18", - "last_updated": "2026-03-18", + "open_weights": false, + "release_date": "2025-09-29", + "last_updated": "2025-11-25", "type": "chat" }, { - "id": "claude-opus-4-6", - "name": "Claude Opus 4.6", - "display_name": "Claude Opus 4.6", + "id": "qwen3-coder-plus-2025-09-23", + "name": "Qwen3 Coder Plus 2025 09 23", + "display_name": "Qwen3 Coder Plus 2025 09 23", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" @@ -216741,53 +215069,20 @@ }, "limit": { "context": 1000000, - "output": 128000 + "output": 65536 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": false - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "mixed", - "budget": { - "min": 1024, - "unit": "tokens" - }, - "effort": "high", - "effort_options": [ - "low", - "medium", - "high", - "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Anthropic recommends adaptive thinking with effort for Claude 4.6; budget_tokens remains a deprecated compatibility path.", - "Anthropic API defaults effort to high; lower effort levels should be chosen per workload." - ] - } + "supported": false }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-05-31", - "release_date": "2026-02-05", - "last_updated": "2026-03-13", + "attachment": false, "type": "chat" }, { - "id": "glm-5.1", - "name": "GLM-5.1", - "display_name": "GLM-5.1", + "id": "qwen3-coder-plus-2025-07-22", + "name": "Qwen3 Coder Plus 2025 07 22", + "display_name": "Qwen3 Coder Plus 2025 07 22", "modalities": { "input": [ "text" @@ -216797,210 +215092,170 @@ ] }, "limit": { - "context": 200000, - "output": 131072 + "context": 1000000, + "output": 65536 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } + "supported": false }, "attachment": false, - "open_weights": true, - "release_date": "2026-04-07", - "last_updated": "2026-04-07", "type": "chat" }, { - "id": "gemini-3.1-pro-preview", - "name": "Gemini 3.1 Pro Preview", - "display_name": "Gemini 3.1 Pro Preview", + "id": "qwen-vl-ocr-latest", + "name": "Qwen Vl Ocr Latest", + "display_name": "Qwen Vl Ocr Latest", "modalities": { "input": [ "text", - "image", - "video", - "audio", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 34096, + "output": 4096 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "level", - "level": "high", - "level_options": [ - "low", - "high" - ], - "summaries": true, - "visibility": "summary", - "continuation": [ - "thought_signatures" - ] - } + "supported": false }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-01", - "release_date": "2026-02-19", - "last_updated": "2026-02-19", + "attachment": false, "type": "chat" }, { - "id": "minimax-m2-7", - "name": "MiniMax-M2.7", - "display_name": "MiniMax-M2.7", + "id": "qvq-max-2025-05-15", + "name": "Qvq Max 2025 05 15", + "display_name": "Qvq Max 2025 05 15", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 204800, - "output": 131072 + "context": 131072, + "output": 8192 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { "supported": true, - "default": true + "default": false, + "budget": { + "default": 16384, + "min": 0, + "max": 16384 + } }, "attachment": false, - "open_weights": true, - "release_date": "2026-03-18", - "last_updated": "2026-03-18", "type": "chat" }, { - "id": "grok-4.3", - "name": "Grok 4.3", - "display_name": "Grok 4.3", + "id": "qwen-turbo-latest", + "name": "Qwen Turbo Latest", + "display_name": "Qwen Turbo Latest", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 30000 + "context": 131072, + "output": 16384 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false, + "budget": { + "default": 38912, + "min": 0, + "max": 38912 + } }, "extra_capabilities": { "reasoning": { "supported": true, - "effort_options": [ - "low", - "medium", - "high" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, - "attachment": true, - "open_weights": false, - "release_date": "2026-04-17", - "last_updated": "2026-04-17", + "search": { + "supported": true, + "default": false, + "forced_search": false, + "search_strategy": "turbo" + }, + "attachment": false, "type": "chat" - } - ] - }, - "hetzner": { - "id": "hetzner", - "name": "Hetzner", - "display_name": "Hetzner", - "api": "https://inference.hetzner.com/api/v1", - "doc": "https://experiments.hetzner.com/docs/inference", - "models": [ + }, { - "id": "Qwen3.8-27B", - "name": "Qwen3.8-27B", - "display_name": "Qwen3.8-27B", + "id": "qwen-turbo-2024-09-19", + "name": "Qwen Turbo 2024 09 19", + "display_name": "Qwen Turbo 2024 09 19", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 131072, + "output": 8192 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true + "supported": false }, - "attachment": true, - "open_weights": true, - "release_date": "2026-08-14", - "last_updated": "2026-08-14", + "attachment": false, "type": "chat" }, { - "id": "Qwen/Qwen3.6-35B-A3B-FP8", - "name": "Qwen3.6 35B A3B FP8", - "display_name": "Qwen3.6 35B A3B FP8", + "id": "qwen-flash-2025-07-28", + "name": "Qwen Flash 2025 07 28", + "display_name": "Qwen Flash 2025 07 28", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 1000000, + "output": 32768 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false, + "budget": { + "default": 81920, + "min": 0, + "max": 81920 + } }, "extra_capabilities": { "reasoning": { @@ -217013,612 +215268,458 @@ ] } }, - "attachment": true, - "open_weights": true, - "release_date": "2026-04-17", - "last_updated": "2026-04-17", - "type": "chat" - } - ] - }, - "openai": { - "id": "openai", - "name": "OpenAI", - "display_name": "OpenAI", - "doc": "https://platform.openai.com/docs/models", - "models": [ - { - "id": "text-embedding-ada-002", - "name": "text-embedding-ada-002", - "display_name": "text-embedding-ada-002", - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 8192, - "output": 1536 - }, - "temperature": false, - "tool_call": false, - "reasoning": { - "supported": false + "search": { + "supported": true, + "default": false, + "forced_search": false, + "search_strategy": "turbo" }, "attachment": false, - "open_weights": false, - "knowledge": "2022-12", - "release_date": "2022-12-15", - "last_updated": "2022-12-15", - "type": "embedding" + "type": "chat" }, { - "id": "gpt-5", - "name": "GPT-5", - "display_name": "GPT-5", + "id": "qwen-plus-latest", + "name": "Qwen Plus Latest", + "display_name": "Qwen Plus Latest", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 1000000, + "output": 32768 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false, + "budget": { + "default": 81920, + "min": 0, + "max": 81920 + } }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", - "effort_options": [ - "minimal", - "low", - "medium", - "high" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2024-09-30", - "release_date": "2025-08-07", - "last_updated": "2025-08-07", + "search": { + "supported": true, + "default": false, + "forced_search": false, + "search_strategy": "turbo" + }, + "attachment": false, "type": "chat" }, { - "id": "gpt-4o-mini", - "name": "GPT-4o mini", - "display_name": "GPT-4o mini", + "id": "qwen-plus-2024-09-19", + "name": "Qwen Plus 2024 09 19", + "display_name": "Qwen Plus 2024 09 19", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 16384 + "context": 131072, + "output": 8192 }, "temperature": true, "tool_call": true, "reasoning": { "supported": false }, - "attachment": true, - "open_weights": false, - "knowledge": "2023-09", - "release_date": "2024-07-18", - "last_updated": "2024-07-18", + "attachment": false, "type": "chat" }, { - "id": "gpt-image-1-mini", - "name": "gpt-image-1-mini", - "display_name": "gpt-image-1-mini", + "id": "qwen-plus-2025-07-14", + "name": "Qwen Plus 2025 07 14", + "display_name": "Qwen Plus 2025 07 14", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ - "text", - "image" + "text" ] }, "limit": { - "context": 8192, - "output": 8192 + "context": 131072, + "output": 16384 }, - "temperature": false, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": false, + "budget": { + "default": 38912, + "min": 0, + "max": 38912 + } }, - "attachment": true, - "open_weights": false, - "release_date": "2025-09-26", - "last_updated": "2025-09-26" + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } + }, + "search": { + "supported": true, + "default": false, + "forced_search": false, + "search_strategy": "turbo" + }, + "attachment": false, + "type": "chat" }, { - "id": "gpt-5.5", - "name": "GPT-5.5", - "display_name": "GPT-5.5", + "id": "qwen-plus-2025-09-11", + "name": "Qwen Plus 2025 09 11", + "display_name": "Qwen Plus 2025 09 11", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 1000000, + "output": 32768 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false, + "budget": { + "default": 81920, + "min": 0, + "max": 81920 + } }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", - "effort_options": [ - "low", - "medium", - "high", - "xhigh" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-12-01", - "release_date": "2026-04-23", - "last_updated": "2026-04-23", + "search": { + "supported": true, + "default": false, + "forced_search": false, + "search_strategy": "turbo" + }, + "attachment": false, "type": "chat" }, { - "id": "gpt-4.1-nano", - "name": "GPT-4.1 nano", - "display_name": "GPT-4.1 nano", + "id": "qwen-max-latest", + "name": "Qwen Max Latest", + "display_name": "Qwen Max Latest", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1047576, - "output": 32768 + "context": 131072, + "output": 8192 }, "temperature": true, "tool_call": true, "reasoning": { "supported": false }, - "attachment": true, - "open_weights": false, - "knowledge": "2024-04", - "release_date": "2025-04-14", - "last_updated": "2025-04-14", + "search": { + "supported": true, + "default": false, + "forced_search": false, + "search_strategy": "turbo" + }, + "attachment": false, "type": "chat" }, { - "id": "o1", - "name": "o1", - "display_name": "o1", + "id": "qwen-max-2024-09-19", + "name": "Qwen Max 2024 09 19", + "display_name": "Qwen Max 2024 09 19", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 100000 + "context": 32768, + "output": 8192 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true + "supported": false }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", - "effort_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" - } + "search": { + "supported": true, + "default": false, + "forced_search": false, + "search_strategy": "turbo" }, - "attachment": true, - "open_weights": false, - "knowledge": "2023-09", - "release_date": "2024-12-05", - "last_updated": "2024-12-05", + "attachment": false, "type": "chat" }, { - "id": "gpt-image-2", - "name": "gpt-image-2", - "display_name": "gpt-image-2", + "id": "qwen-max-2024-04-28", + "name": "Qwen Max 2024 04 28", + "display_name": "Qwen Max 2024 04 28", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ - "image" + "text" ] }, "limit": { - "context": 8192, - "output": 8192 + "context": 8000, + "output": 2000 }, - "temperature": false, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { "supported": false }, - "attachment": true, - "open_weights": false, - "release_date": "2026-04-21", - "last_updated": "2026-04-21", - "type": "imageGeneration" + "attachment": false, + "type": "chat" }, { - "id": "gpt-6-astra", - "name": "GPT-6 Astra", - "display_name": "GPT-6 Astra", + "id": "qwen-max-2024-04-03", + "name": "Qwen Max 2024 04 03", + "display_name": "Qwen Max 2024 04 03", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 8000, + "output": 2000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" - ] - } + "supported": false }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-04-30", - "release_date": "2026-09-04", - "last_updated": "2026-09-04", + "attachment": false, "type": "chat" }, { - "id": "gpt-5.4", - "name": "GPT-5.4", - "display_name": "GPT-5.4", + "id": "qwen-max-2025-01-25", + "name": "Qwen Max 2025 01 25", + "display_name": "Qwen Max 2025 01 25", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 131072, + "output": 8192 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": false + "supported": false }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "none", - "effort_options": [ - "none", - "low", - "medium", - "high", - "xhigh" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" - } + "search": { + "supported": true, + "default": false, + "forced_search": false, + "search_strategy": "turbo" }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-03-05", - "last_updated": "2026-03-05", + "attachment": false, "type": "chat" }, { - "id": "gpt-4-turbo", - "name": "GPT-4 Turbo", - "display_name": "GPT-4 Turbo", + "id": "qwen3-max-2025-09-23", + "name": "Qwen3 Max 20250923", + "display_name": "Qwen3 Max 20250923", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 4096 + "context": 262144, + "output": 65536 }, "temperature": true, "tool_call": true, "reasoning": { "supported": false }, - "attachment": true, - "open_weights": false, - "knowledge": "2023-12", - "release_date": "2023-11-06", - "last_updated": "2024-04-09", + "search": { + "supported": true, + "default": false, + "forced_search": false, + "search_strategy": "turbo" + }, + "attachment": false, "type": "chat" }, { - "id": "gpt-5.3-codex-spark", - "name": "GPT-5.3 Codex Spark", - "display_name": "GPT-5.3 Codex Spark", + "id": "qwen3-max-preview", + "name": "Qwen3 Max Preview", + "display_name": "Qwen3 Max Preview", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 32000 + "context": 262144, + "output": 65536 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true + "supported": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", - "effort_options": [ - "low", - "medium", - "high", - "xhigh" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-02-05", - "last_updated": "2026-02-05", - "type": "chat" - }, - { - "id": "gpt-4o", - "name": "GPT-4o", - "display_name": "GPT-4o", - "modalities": { - "input": [ - "text", - "image", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 16384 - }, - "temperature": true, - "tool_call": true, - "reasoning": { - "supported": false + "search": { + "supported": true, + "default": false, + "forced_search": false, + "search_strategy": "turbo" }, - "attachment": true, - "open_weights": false, - "knowledge": "2023-09", - "release_date": "2024-05-13", - "last_updated": "2024-08-06", + "attachment": false, "type": "chat" }, { - "id": "gpt-5-pro", - "name": "GPT-5 Pro", - "display_name": "GPT-5 Pro", + "id": "qwen3-235b-a22b-thinking-2507", + "name": "Qwen3 235B A22B Thinking 2507", + "display_name": "Qwen3 235B A22B Thinking 2507", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 400000, - "output": 272000 + "context": 131072, + "output": 32768 }, - "temperature": false, - "tool_call": true, + "temperature": true, + "tool_call": false, "reasoning": { "supported": true, - "default": true + "default": true, + "budget": { + "default": 81920, + "min": 0, + "max": 81920 + } }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "fixed", - "effort": "high", - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2024-09-30", - "release_date": "2025-10-06", - "last_updated": "2025-10-06", + "attachment": false, "type": "chat" }, { - "id": "o3", - "name": "o3", - "display_name": "o3", + "id": "qwen3-235b-a22b-instruct-2507", + "name": "Qwen3 235B A22B Instruct 2507", + "display_name": "Qwen3 235B A22B Instruct 2507", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 100000 + "context": 131072, + "output": 32768 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", - "effort_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" - } + "supported": false }, - "attachment": true, - "open_weights": false, - "knowledge": "2024-05", - "release_date": "2025-04-16", - "last_updated": "2025-04-16", + "attachment": false, "type": "chat" }, { - "id": "text-embedding-3-small", - "name": "text-embedding-3-small", - "display_name": "text-embedding-3-small", + "id": "qwen3-30b-a3b-instruct-2507", + "name": "Qwen3 30B A3B Instruct 2507", + "display_name": "Qwen3 30B A3B Instruct 2507", "modalities": { "input": [ "text" @@ -217628,251 +215729,226 @@ ] }, "limit": { - "context": 8191, - "output": 1536 + "context": 131072, + "output": 32768 }, - "temperature": false, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { "supported": false }, "attachment": false, - "open_weights": false, - "knowledge": "2024-01", - "release_date": "2024-01-25", - "last_updated": "2024-01-25", - "type": "embedding" + "type": "chat" }, { - "id": "gpt-daybreak-blue-latest", - "name": "Daybreak Blue", - "display_name": "Daybreak Blue", + "id": "qwen3-30b-a3b-thinking-2507", + "name": "Qwen3 30B A3B Thinking 2507", + "display_name": "Qwen3 30B A3B Thinking 2507", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 131072, + "output": 32768 }, - "temperature": false, - "tool_call": true, + "temperature": true, + "tool_call": false, "reasoning": { "supported": true, - "default": true + "default": true, + "budget": { + "default": 81920, + "min": 0, + "max": 81920 + } }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "none", - "low", - "medium", - "high", - "xhigh", - "max" + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-02-16", - "release_date": "2026-08-07", - "last_updated": "2026-08-07", + "attachment": false, "type": "chat" }, { - "id": "gpt-realtime-2.1", - "name": "GPT-Realtime-2.1", - "display_name": "GPT-Realtime-2.1", + "id": "qwen3-30b-a3b", + "name": "Qwen3 30B A3B", + "display_name": "Qwen3 30B A3B", "modalities": { "input": [ - "text", - "audio", - "image" + "text" ], "output": [ - "text", - "audio" + "text" ] }, "limit": { - "context": 128000, - "output": 32000 + "context": 131072, + "output": 32768 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false, + "budget": { + "default": 81920, + "min": 0, + "max": 81920 + } }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "minimal", - "low", - "medium", - "high", - "xhigh" + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2024-09-30", - "release_date": "2026-07-06", - "last_updated": "2026-07-06" + "attachment": false, + "type": "chat" }, { - "id": "gpt-6-sol", - "name": "GPT-6 Sol", - "display_name": "GPT-6 Sol", + "id": "qwen3-4b", + "name": "Qwen3 4B", + "display_name": "Qwen3 4B", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 131072, + "output": 8192 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": true, + "budget": { + "default": 38912, + "min": 0, + "max": 38912 + } }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "none", - "low", - "medium", - "high", - "xhigh", - "max" + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-04-20", - "release_date": "2026-09-22", - "last_updated": "2026-09-22", + "attachment": false, "type": "chat" }, { - "id": "o4-mini", - "name": "o4-mini", - "display_name": "o4-mini", + "id": "qwen3-1.7b", + "name": "Qwen3 1.7B", + "display_name": "Qwen3 1.7B", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 100000 + "context": 32768, + "output": 8192 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": true, + "budget": { + "default": 30720, + "min": 0, + "max": 30720 + } }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", - "effort_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2024-05", - "release_date": "2025-04-16", - "last_updated": "2025-04-16", + "attachment": false, "type": "chat" }, { - "id": "gpt-5.6-sol", - "name": "GPT-5.6 Sol", - "display_name": "GPT-5.6 Sol", + "id": "qwen3-0.6b", + "name": "Qwen3 0.6B", + "display_name": "Qwen3 0.6B", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 32768, + "output": 8192 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": true, + "budget": { + "default": 30720, + "min": 0, + "max": 30720 + } }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", - "effort_options": [ - "none", - "low", - "medium", - "high", - "xhigh", - "max" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-02-16", - "release_date": "2026-07-09", - "last_updated": "2026-07-09", + "attachment": false, "type": "chat" }, { - "id": "gpt-4o-2024-11-20", - "name": "GPT-4o (2024-11-20)", - "display_name": "GPT-4o (2024-11-20)", + "id": "qwen3-vl-plus-2025-09-23", + "name": "Qwen3 VL Plus 2025 09 23", + "display_name": "Qwen3 VL Plus 2025 09 23", "modalities": { "input": [ "text", @@ -217883,80 +215959,102 @@ ] }, "limit": { - "context": 128000, - "output": 16384 + "context": 262144, + "output": 32768 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": false, + "budget": { + "default": 81920, + "min": 0, + "max": 81920 + } }, - "attachment": true, - "open_weights": false, - "knowledge": "2023-09", - "release_date": "2024-11-20", - "last_updated": "2024-11-20", + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } + }, + "attachment": false, "type": "chat" }, { - "id": "gpt-daybreak-red-latest", - "name": "Daybreak Red", - "display_name": "Daybreak Red", + "id": "qwq-plus-latest", + "name": "QwQ Plus Latest", + "display_name": "QwQ Plus Latest", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 131072, + "output": 8192 }, - "temperature": false, - "tool_call": true, + "temperature": true, + "tool_call": false, "reasoning": { "supported": true, - "default": true + "default": true, + "budget": { + "default": 32768, + "min": 0, + "max": 32768 + } }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "none", - "low", - "medium", - "high", - "xhigh", - "max" + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-02-16", - "release_date": "2026-08-07", - "last_updated": "2026-08-07", + "search": { + "supported": false + }, + "attachment": false, "type": "chat" - }, + } + ] + }, + "helicone": { + "id": "helicone", + "name": "Helicone", + "display_name": "Helicone", + "api": "https://ai-gateway.helicone.ai/v1", + "doc": "https://helicone.ai/models", + "models": [ { - "id": "gpt-5.6-terra", - "name": "GPT-5.6 Terra", - "display_name": "GPT-5.6 Terra", + "id": "gpt-5-pro", + "name": "OpenAI: GPT-5 Pro", + "display_name": "OpenAI: GPT-5 Pro", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 128000, + "output": 32768 }, "temperature": false, "tool_call": true, @@ -217968,16 +216066,8 @@ "reasoning": { "supported": true, "default_enabled": true, - "mode": "effort", - "effort": "medium", - "effort_options": [ - "none", - "low", - "medium", - "high", - "xhigh", - "max" - ], + "mode": "fixed", + "effort": "high", "verbosity": "medium", "verbosity_options": [ "low", @@ -217987,58 +216077,58 @@ "visibility": "hidden" } }, - "attachment": true, + "attachment": false, "open_weights": false, - "knowledge": "2026-02-16", - "release_date": "2026-07-09", - "last_updated": "2026-07-09", + "knowledge": "2025-01", + "release_date": "2025-10-06", + "last_updated": "2025-10-06", "type": "chat" }, { - "id": "text-embedding-3-large", - "name": "text-embedding-3-large", - "display_name": "text-embedding-3-large", + "id": "qwen3-vl-235b-a22b-instruct", + "name": "Qwen3 VL 235B A22B Instruct", + "display_name": "Qwen3 VL 235B A22B Instruct", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 8191, - "output": 3072 + "context": 256000, + "output": 16384 }, - "temperature": false, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { "supported": false }, - "attachment": false, - "open_weights": false, - "knowledge": "2024-01", - "release_date": "2024-01-25", - "last_updated": "2024-01-25", - "type": "embedding" + "attachment": true, + "open_weights": true, + "knowledge": "2025-09", + "release_date": "2025-09-23", + "last_updated": "2025-09-23", + "type": "chat" }, { - "id": "gpt-6-luna", - "name": "GPT-6 Luna", - "display_name": "GPT-6 Luna", + "id": "o3-mini", + "name": "OpenAI o3 Mini", + "display_name": "OpenAI o3 Mini", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 200000, + "output": 100000 }, "temperature": false, "tool_call": true, @@ -218048,78 +216138,58 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", "effort_options": [ - "none", "low", "medium", - "high", - "xhigh", - "max" - ] + "high" + ], + "visibility": "hidden" } }, - "attachment": true, + "attachment": false, "open_weights": false, - "knowledge": "2026-05-18", - "release_date": "2026-09-22", - "last_updated": "2026-09-22", + "knowledge": "2023-10", + "release_date": "2024-12-20", + "last_updated": "2025-01-29", "type": "chat" }, { - "id": "gpt-5-mini", - "name": "GPT-5 Mini", - "display_name": "GPT-5 Mini", + "id": "sonar-reasoning", + "name": "Perplexity Sonar Reasoning", + "display_name": "Perplexity Sonar Reasoning", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 400000, - "output": 128000 - }, - "temperature": false, - "tool_call": true, - "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", - "effort_options": [ - "minimal", - "low", - "medium", - "high" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" - } + "context": 127000, + "output": 4096 }, - "attachment": true, + "temperature": true, + "tool_call": false, + "reasoning": { + "supported": true, + "default": true + }, + "attachment": false, "open_weights": false, - "knowledge": "2024-05-30", - "release_date": "2025-08-07", - "last_updated": "2025-08-07", + "knowledge": "2025-01", + "release_date": "2025-01-27", + "last_updated": "2025-01-27", "type": "chat" }, { - "id": "gpt-4o-2024-08-06", - "name": "GPT-4o (2024-08-06)", - "display_name": "GPT-4o (2024-08-06)", + "id": "chatgpt-4o-latest", + "name": "OpenAI ChatGPT-4o", + "display_name": "OpenAI ChatGPT-4o", "modalities": { "input": [ "text", @@ -218140,30 +216210,29 @@ }, "attachment": true, "open_weights": false, - "knowledge": "2023-09", - "release_date": "2024-08-06", - "last_updated": "2024-08-06", + "knowledge": "2024-08", + "release_date": "2024-08-14", + "last_updated": "2024-08-14", "type": "chat" }, { - "id": "gpt-5.4-pro", - "name": "GPT-5.4 Pro", - "display_name": "GPT-5.4 Pro", + "id": "qwen3-235b-a22b-thinking", + "name": "Qwen3 235B A22B Thinking", + "display_name": "Qwen3 235B A22B Thinking", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 262144, + "output": 81920 }, - "temperature": false, - "tool_call": true, + "temperature": true, + "tool_call": false, "reasoning": { "supported": true, "default": true @@ -218171,34 +216240,25 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "high", - "effort_options": [ - "medium", - "high", - "xhigh" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-03-05", - "last_updated": "2026-03-05", + "attachment": false, + "open_weights": true, + "knowledge": "2025-07", + "release_date": "2025-07-25", + "last_updated": "2025-07-25", "type": "chat" }, { - "id": "gpt-4", - "name": "GPT-4", - "display_name": "GPT-4", + "id": "llama-3.1-8b-instruct", + "name": "Meta Llama 3.1 8B Instruct", + "display_name": "Meta Llama 3.1 8B Instruct", "modalities": { "input": [ "text" @@ -218208,8 +216268,8 @@ ] }, "limit": { - "context": 8192, - "output": 8192 + "context": 16384, + "output": 16384 }, "temperature": true, "tool_call": true, @@ -218217,19 +216277,20 @@ "supported": false }, "attachment": false, - "open_weights": false, - "knowledge": "2023-11", - "release_date": "2023-11-06", - "last_updated": "2024-04-09", + "open_weights": true, + "knowledge": "2024-07", + "release_date": "2024-07-23", + "last_updated": "2024-07-23", "type": "chat" }, { - "id": "o3-mini", - "name": "o3-mini", - "display_name": "o3-mini", + "id": "claude-haiku-4-5-20251001", + "name": "Anthropic: Claude 4.5 Haiku (20251001)", + "display_name": "Anthropic: Claude 4.5 Haiku (20251001)", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" @@ -218237,39 +216298,46 @@ }, "limit": { "context": 200000, - "output": 100000 + "output": 8192 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", - "effort_options": [ - "low", - "medium", - "high" + "default_enabled": false, + "mode": "budget", + "budget": { + "min": 1024, + "unit": "tokens" + }, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ], - "visibility": "hidden" + "notes": [ + "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", + "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." + ] } }, - "attachment": false, + "attachment": true, "open_weights": false, - "knowledge": "2024-05", - "release_date": "2024-12-20", - "last_updated": "2025-01-29", + "knowledge": "2025-10", + "release_date": "2025-10-15", + "last_updated": "2025-10-15", "type": "chat" }, { - "id": "gpt-5.1", - "name": "GPT-5.1", - "display_name": "GPT-5.1", + "id": "gpt-5.1-codex", + "name": "OpenAI: GPT-5.1 Codex", + "display_name": "OpenAI: GPT-5.1 Codex", "modalities": { "input": [ "text", @@ -218283,7 +216351,7 @@ "context": 400000, "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -218312,311 +216380,246 @@ }, "attachment": true, "open_weights": false, - "knowledge": "2024-09-30", + "knowledge": "2025-01", "release_date": "2025-11-13", "last_updated": "2025-11-13", "type": "chat" }, { - "id": "gpt-image-1.5", - "name": "gpt-image-1.5", - "display_name": "gpt-image-1.5", + "id": "deepseek-v3", + "name": "DeepSeek V3", + "display_name": "DeepSeek V3", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ - "text", - "image" + "text" ] }, "limit": { - "context": 8192, + "context": 128000, "output": 8192 }, - "temperature": false, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { "supported": false }, - "attachment": true, + "attachment": false, "open_weights": false, - "release_date": "2025-11-25", - "last_updated": "2025-11-25" + "knowledge": "2024-12", + "release_date": "2024-12-26", + "last_updated": "2024-12-26", + "type": "chat" }, { - "id": "gpt-5.6-luna", - "name": "GPT-5.6 Luna", - "display_name": "GPT-5.6 Luna", + "id": "sonar", + "name": "Perplexity Sonar", + "display_name": "Perplexity Sonar", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 127000, + "output": 4096 }, - "temperature": false, - "tool_call": true, + "temperature": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", - "effort_options": [ - "none", - "low", - "medium", - "high", - "xhigh", - "max" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" - } + "supported": false }, - "attachment": true, + "attachment": false, "open_weights": false, - "knowledge": "2026-02-16", - "release_date": "2026-07-09", - "last_updated": "2026-07-09", + "knowledge": "2025-01", + "release_date": "2024-01-01", + "last_updated": "2025-09-01", "type": "chat" }, { - "id": "gpt-5.4-mini", - "name": "GPT-5.4 mini", - "display_name": "GPT-5.4 mini", + "id": "llama-3.1-8b-instruct-turbo", + "name": "Meta Llama 3.1 8B Instruct Turbo", + "display_name": "Meta Llama 3.1 8B Instruct Turbo", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 400000, + "context": 128000, "output": 128000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": false - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "none", - "effort_options": [ - "none", - "low", - "medium", - "high", - "xhigh" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" - } + "supported": false }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-03-17", - "last_updated": "2026-03-17", + "attachment": false, + "open_weights": true, + "knowledge": "2024-07", + "release_date": "2024-07-23", + "last_updated": "2024-07-23", "type": "chat" }, { - "id": "gpt-5.3-codex", - "name": "GPT-5.3 Codex", - "display_name": "GPT-5.3 Codex", + "id": "claude-sonnet-4-5-20250929", + "name": "Anthropic: Claude Sonnet 4.5 (20250929)", + "display_name": "Anthropic: Claude Sonnet 4.5 (20250929)", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 200000, + "output": 64000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", - "effort_options": [ - "low", - "medium", - "high", - "xhigh" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" + "default_enabled": false, + "mode": "budget", + "budget": { + "min": 1024, + "unit": "tokens" + }, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ], - "visibility": "hidden" + "notes": [ + "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", + "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." + ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-02-05", - "last_updated": "2026-02-05", + "knowledge": "2025-09", + "release_date": "2025-09-29", + "last_updated": "2025-09-29", "type": "chat" }, { - "id": "chatgpt-image-latest", - "name": "chatgpt-image-latest", - "display_name": "chatgpt-image-latest", + "id": "qwen3-next-80b-a3b-instruct", + "name": "Qwen3 Next 80B A3B Instruct", + "display_name": "Qwen3 Next 80B A3B Instruct", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ - "text", - "image" + "text" ] }, "limit": { - "context": 8192, - "output": 8192 + "context": 262000, + "output": 16384 }, - "temperature": false, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { "supported": false }, - "attachment": true, - "open_weights": false, - "release_date": "2025-12-16", - "last_updated": "2025-12-16" + "attachment": false, + "open_weights": true, + "knowledge": "2025-01", + "release_date": "2025-09", + "last_updated": "2025-09", + "type": "chat" }, { - "id": "gpt-4.1-mini", - "name": "GPT-4.1 mini", - "display_name": "GPT-4.1 mini", + "id": "llama-prompt-guard-2-86m", + "name": "Meta Llama Prompt Guard 2 86M", + "display_name": "Meta Llama Prompt Guard 2 86M", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1047576, - "output": 32768 + "context": 512, + "output": 2 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { "supported": false }, - "attachment": true, - "open_weights": false, - "knowledge": "2024-04", - "release_date": "2025-04-14", - "last_updated": "2025-04-14", + "attachment": false, + "open_weights": true, + "knowledge": "2024-10", + "release_date": "2025-04-29", + "last_updated": "2025-04-29", "type": "chat" }, { - "id": "gpt-5.4-nano", - "name": "GPT-5.4 nano", - "display_name": "GPT-5.4 nano", + "id": "kimi-k2-thinking", + "name": "Kimi K2 Thinking", + "display_name": "Kimi K2 Thinking", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 256000, + "output": 262144 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "none", - "effort_options": [ - "none", - "low", - "medium", - "high", - "xhigh" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-03-17", - "last_updated": "2026-03-17", + "attachment": false, + "open_weights": true, + "knowledge": "2025-11", + "release_date": "2025-11-06", + "last_updated": "2025-11-06", "type": "chat" }, { - "id": "o3-pro", - "name": "o3-pro", - "display_name": "o3-pro", + "id": "claude-4.5-opus", + "name": "Anthropic: Claude Opus 4.5", + "display_name": "Anthropic: Claude Opus 4.5", "modalities": { "input": [ "text", @@ -218628,84 +216631,57 @@ }, "limit": { "context": 200000, - "output": 100000 + "output": 64000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", - "effort_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" - } - }, "attachment": true, "open_weights": false, - "knowledge": "2024-05", - "release_date": "2025-06-10", - "last_updated": "2025-06-10", + "knowledge": "2025-11", + "release_date": "2025-11-24", + "last_updated": "2025-11-24", "type": "chat" }, { - "id": "gpt-5.5-pro", - "name": "GPT-5.5 Pro", - "display_name": "GPT-5.5 Pro", + "id": "llama-4-scout", + "name": "Meta Llama 4 Scout 17B 16E", + "display_name": "Meta Llama 4 Scout 17B 16E", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 131072, + "output": 8192 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "effort_options": [ - "medium", - "high", - "xhigh" - ] - } + "supported": false }, "attachment": true, - "open_weights": false, - "knowledge": "2025-12-01", - "release_date": "2026-04-23", - "last_updated": "2026-04-23", + "open_weights": true, + "knowledge": "2025-01", + "release_date": "2025-04-05", + "last_updated": "2025-04-05", "type": "chat" }, { - "id": "gpt-5.2-pro", - "name": "GPT-5.2 Pro", - "display_name": "GPT-5.2 Pro", + "id": "gpt-5-codex", + "name": "OpenAI: GPT-5 Codex", + "display_name": "OpenAI: GPT-5 Codex", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" @@ -218726,11 +216702,12 @@ "supported": true, "default_enabled": true, "mode": "effort", - "effort": "high", + "effort": "medium", "effort_options": [ + "minimal", + "low", "medium", - "high", - "xhigh" + "high" ], "verbosity": "medium", "verbosity_options": [ @@ -218741,47 +216718,46 @@ "visibility": "hidden" } }, - "attachment": true, + "attachment": false, "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2025-12-11", - "last_updated": "2025-12-11", + "knowledge": "2025-01", + "release_date": "2025-09-15", + "last_updated": "2025-09-15", "type": "chat" }, { - "id": "gpt-4.1", - "name": "GPT-4.1", - "display_name": "GPT-4.1", + "id": "llama-guard-4", + "name": "Meta Llama Guard 4 12B", + "display_name": "Meta Llama Guard 4 12B", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1047576, - "output": 32768 + "context": 131072, + "output": 1024 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { "supported": false }, "attachment": true, "open_weights": false, - "knowledge": "2024-04", - "release_date": "2025-04-14", - "last_updated": "2025-04-14", + "knowledge": "2025-01", + "release_date": "2025-01-01", + "last_updated": "2025-01-01", "type": "chat" }, { - "id": "gpt-3.5-turbo", - "name": "GPT-3.5-turbo", - "display_name": "GPT-3.5-turbo", + "id": "kimi-k2-0905", + "name": "Kimi K2 (09/05)", + "display_name": "Kimi K2 (09/05)", "modalities": { "input": [ "text" @@ -218791,85 +216767,78 @@ ] }, "limit": { - "context": 16385, - "output": 4096 + "context": 262144, + "output": 16384 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { "supported": false }, "attachment": false, - "open_weights": false, - "knowledge": "2021-09-01", - "release_date": "2023-03-01", - "last_updated": "2023-11-06", + "open_weights": true, + "knowledge": "2025-09", + "release_date": "2025-09-05", + "last_updated": "2025-09-05", "type": "chat" }, { - "id": "o1-pro", - "name": "o1-pro", - "display_name": "o1-pro", + "id": "qwen3-30b-a3b", + "name": "Qwen3 30B A3B", + "display_name": "Qwen3 30B A3B", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 100000 + "context": 41000, + "output": 41000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true + "supported": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", - "effort_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2023-09", - "release_date": "2025-03-19", - "last_updated": "2025-03-19", + "attachment": false, + "open_weights": true, + "knowledge": "2025-06", + "release_date": "2025-04-28", + "last_updated": "2025-04-28", "type": "chat" }, { - "id": "gpt-5.6", - "name": "GPT-5.6", - "display_name": "GPT-5.6", + "id": "o1-mini", + "name": "OpenAI: o1-mini", + "display_name": "OpenAI: o1-mini", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 128000, + "output": 65536 }, "temperature": false, - "tool_call": true, + "tool_call": false, "reasoning": { "supported": true, "default": true @@ -218881,15 +216850,6 @@ "mode": "effort", "effort": "medium", "effort_options": [ - "none", - "low", - "medium", - "high", - "xhigh", - "max" - ], - "verbosity": "medium", - "verbosity_options": [ "low", "medium", "high" @@ -218897,33 +216857,87 @@ "visibility": "hidden" } }, - "attachment": true, + "attachment": false, "open_weights": false, - "knowledge": "2026-02-16", - "release_date": "2026-07-09", - "last_updated": "2026-07-09", + "knowledge": "2025-01", + "release_date": "2025-01-01", + "last_updated": "2025-01-01", "type": "chat" }, { - "id": "gpt-6.1-terra", - "name": "GPT-6.1 Terra", - "display_name": "GPT-6.1 Terra", + "id": "llama-3.3-70b-instruct", + "name": "Meta Llama 3.3 70B Instruct", + "display_name": "Meta Llama 3.3 70B Instruct", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 128000, + "output": 16400 }, - "temperature": false, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": false + }, + "attachment": false, + "open_weights": true, + "knowledge": "2024-12", + "release_date": "2024-12-06", + "last_updated": "2024-12-06", + "type": "chat" + }, + { + "id": "mistral-large-2411", + "name": "Mistral-Large", + "display_name": "Mistral-Large", + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 32768 + }, + "temperature": true, "tool_call": true, + "reasoning": { + "supported": false + }, + "attachment": false, + "open_weights": true, + "knowledge": "2024-07", + "release_date": "2024-11-18", + "last_updated": "2024-11-18", + "type": "chat" + }, + { + "id": "sonar-deep-research", + "name": "Perplexity Sonar Deep Research", + "display_name": "Perplexity Sonar Deep Research", + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 127000, + "output": 4096 + }, + "temperature": true, + "tool_call": false, "reasoning": { "supported": true, "default": true @@ -218931,26 +216945,24 @@ "extra_capabilities": { "reasoning": { "effort_options": [ - "none", + "minimal", "low", "medium", - "high", - "xhigh", - "max" + "high" ] } }, - "attachment": true, + "attachment": false, "open_weights": false, - "knowledge": "2026-06-01", - "release_date": "2026-10-26", - "last_updated": "2026-10-26", + "knowledge": "2025-01", + "release_date": "2025-02-01", + "last_updated": "2025-09-01", "type": "chat" }, { - "id": "gpt-4o-2024-05-13", - "name": "GPT-4o (2024-05-13)", - "display_name": "GPT-4o (2024-05-13)", + "id": "gpt-4.1-nano", + "name": "OpenAI GPT-4.1 Nano", + "display_name": "OpenAI GPT-4.1 Nano", "modalities": { "input": [ "text", @@ -218961,8 +216973,8 @@ ] }, "limit": { - "context": 128000, - "output": 4096 + "context": 1047576, + "output": 32768 }, "temperature": true, "tool_call": true, @@ -218971,15 +216983,15 @@ }, "attachment": true, "open_weights": false, - "knowledge": "2023-09", - "release_date": "2024-05-13", - "last_updated": "2024-05-13", + "knowledge": "2025-04", + "release_date": "2025-04-14", + "last_updated": "2025-04-14", "type": "chat" }, { - "id": "gpt-5.2", - "name": "GPT-5.2", - "display_name": "GPT-5.2", + "id": "gpt-5-nano", + "name": "OpenAI GPT-5 Nano", + "display_name": "OpenAI GPT-5 Nano", "modalities": { "input": [ "text", @@ -218993,24 +217005,23 @@ "context": 400000, "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, + "default_enabled": true, "mode": "effort", - "effort": "none", + "effort": "medium", "effort_options": [ - "none", + "minimal", "low", "medium", - "high", - "xhigh" + "high" ], "verbosity": "medium", "verbosity_options": [ @@ -219023,57 +217034,44 @@ }, "attachment": true, "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2025-12-11", - "last_updated": "2025-12-11", + "knowledge": "2025-01", + "release_date": "2025-08-07", + "last_updated": "2025-08-07", "type": "chat" }, { - "id": "gpt-6.1-sol", - "name": "GPT-6.1 Sol", - "display_name": "GPT-6.1 Sol", + "id": "claude-3-haiku-20240307", + "name": "Anthropic: Claude 3 Haiku", + "display_name": "Anthropic: Claude 3 Haiku", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 200000, + "output": 4096 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" - ] - } + "supported": false }, "attachment": true, "open_weights": false, - "knowledge": "2026-04-30", - "release_date": "2026-09-29", - "last_updated": "2026-09-29", + "knowledge": "2024-03", + "release_date": "2024-03-13", + "last_updated": "2024-03-13", "type": "chat" }, { - "id": "gpt-5-nano", - "name": "GPT-5 Nano", - "display_name": "GPT-5 Nano", + "id": "claude-opus-4-1", + "name": "Anthropic: Claude Opus 4.1", + "display_name": "Anthropic: Claude Opus 4.1", "modalities": { "input": [ "text", @@ -219084,56 +217082,47 @@ ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 200000, + "output": 32000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", - "effort_options": [ - "minimal", - "low", - "medium", - "high" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" + "default_enabled": false, + "mode": "budget", + "budget": { + "min": 1024, + "unit": "tokens" + }, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ], - "visibility": "hidden" + "notes": [ + "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", + "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." + ] } }, "attachment": true, "open_weights": false, - "knowledge": "2024-05-30", - "release_date": "2025-08-07", - "last_updated": "2025-08-07", + "knowledge": "2025-08", + "release_date": "2025-08-05", + "last_updated": "2025-08-05", "type": "chat" - } - ] - }, - "tencent-tokenhub": { - "id": "tencent-tokenhub", - "name": "Tencent TokenHub", - "display_name": "Tencent TokenHub", - "api": "https://tokenhub.tencentmaas.com/v1", - "doc": "https://cloud.tencent.com/document/product/1823/130050", - "models": [ + }, { - "id": "hy4-preview", - "name": "Hy4 preview", - "display_name": "Hy4 preview", + "id": "deepseek-r1-distill-llama-70b", + "name": "DeepSeek R1 Distill Llama 70B", + "display_name": "DeepSeek R1 Distill Llama 70B", "modalities": { "input": [ "text" @@ -219143,8 +217132,8 @@ ] }, "limit": { - "context": 1024000, - "output": 64000 + "context": 128000, + "output": 4096 }, "temperature": true, "tool_call": true, @@ -219154,23 +217143,20 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "effort_options": [ - "none", - "high" - ] + "supported": true } }, "attachment": false, - "open_weights": true, - "release_date": "2026-08-28", - "last_updated": "2026-08-28", + "open_weights": false, + "knowledge": "2025-01", + "release_date": "2025-01-20", + "last_updated": "2025-01-20", "type": "chat" }, { - "id": "hy3-preview", - "name": "Hy3 preview", - "display_name": "Hy3 preview", + "id": "deepseek-reasoner", + "name": "DeepSeek Reasoner", + "display_name": "DeepSeek Reasoner", "modalities": { "input": [ "text" @@ -219180,47 +217166,51 @@ ] }, "limit": { - "context": 256000, + "context": 128000, "output": 64000 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { "supported": true, "default": true }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "low", - "medium", - "high" + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, "attachment": false, "open_weights": true, - "release_date": "2026-04-20", - "last_updated": "2026-04-20", + "knowledge": "2025-01", + "release_date": "2025-12-01", + "last_updated": "2026-02-28", "type": "chat" }, { - "id": "hy3", - "name": "Hy3", - "display_name": "Hy3", + "id": "o3-pro", + "name": "OpenAI o3 Pro", + "display_name": "OpenAI o3 Pro", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 256000, - "output": 128000 + "context": 200000, + "output": 100000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -219229,31 +217219,28 @@ "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", "effort_options": [ - "none", + "low", + "medium", "high" - ] + ], + "visibility": "hidden" } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-07-06", - "last_updated": "2026-07-06", + "attachment": true, + "open_weights": false, + "knowledge": "2024-06", + "release_date": "2025-06-10", + "last_updated": "2025-06-10", "type": "chat" - } - ] - }, - "atomic-chat": { - "id": "atomic-chat", - "name": "Atomic Chat", - "display_name": "Atomic Chat", - "api": "http://127.0.0.1:1337/v1", - "doc": "https://atomic.chat", - "models": [ + }, { - "id": "Qwen3_5-9B-MLX-4bit", - "name": "Qwen 3.5 9B (MLX 4-bit)", - "display_name": "Qwen 3.5 9B (MLX 4-bit)", + "id": "gemma-3-12b-it", + "name": "Google Gemma 3 12B", + "display_name": "Google Gemma 3 12B", "modalities": { "input": [ "text", @@ -219264,24 +217251,25 @@ ] }, "limit": { - "context": 32768, + "context": 131072, "output": 8192 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { "supported": false }, "attachment": true, "open_weights": true, - "release_date": "2026-02-23", - "last_updated": "2026-02-23", + "knowledge": "2024-12", + "release_date": "2025-03-12", + "last_updated": "2025-03-12", "type": "chat" }, { - "id": "gemma-4-E4B-it-MLX-4bit", - "name": "Gemma 4 E4B Instruct (MLX 4-bit)", - "display_name": "Gemma 4 E4B Instruct (MLX 4-bit)", + "id": "glm-4.6", + "name": "Zai GLM-4.6", + "display_name": "Zai GLM-4.6", "modalities": { "input": [ "text" @@ -219291,24 +217279,60 @@ ] }, "limit": { - "context": 32768, - "output": 8192 + "context": 204800, + "output": 131072 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true + } }, "attachment": false, "open_weights": true, - "release_date": "2026-04-02", - "last_updated": "2026-04-02", + "knowledge": "2024-07", + "release_date": "2025-09-30", + "last_updated": "2025-09-30", "type": "chat" }, { - "id": "Meta-Llama-3_1-8B-Instruct-GGUF", - "name": "Meta Llama 3.1 8B Instruct (GGUF)", - "display_name": "Meta Llama 3.1 8B Instruct (GGUF)", + "id": "claude-3.5-haiku", + "name": "Anthropic: Claude 3.5 Haiku", + "display_name": "Anthropic: Claude 3.5 Haiku", + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 200000, + "output": 8192 + }, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": false + }, + "attachment": true, + "open_weights": false, + "knowledge": "2024-10", + "release_date": "2024-10-22", + "last_updated": "2024-10-22", + "type": "chat" + }, + { + "id": "deepseek-v3.2", + "name": "DeepSeek V3.2", + "display_name": "DeepSeek V3.2", "modalities": { "input": [ "text" @@ -219318,25 +217342,30 @@ ] }, "limit": { - "context": 131072, - "output": 4096 + "context": 163840, + "output": 65536 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true + } }, "attachment": false, "open_weights": true, - "knowledge": "2023-12", - "release_date": "2024-07-23", - "last_updated": "2024-07-23", + "knowledge": "2025-09", + "release_date": "2025-12-01", + "last_updated": "2025-12-01", "type": "chat" }, { - "id": "gemma-4-E4B-it-IQ4_XS", - "name": "Gemma 4 E4B Instruct (IQ4_XS)", - "display_name": "Gemma 4 E4B Instruct (IQ4_XS)", + "id": "grok-code-fast-1", + "name": "xAI Grok Code Fast 1", + "display_name": "xAI Grok Code Fast 1", "modalities": { "input": [ "text" @@ -219346,24 +217375,31 @@ ] }, "limit": { - "context": 32768, - "output": 8192 + "context": 256000, + "output": 10000 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true + } }, "attachment": false, - "open_weights": true, - "release_date": "2026-04-02", - "last_updated": "2026-04-02", + "open_weights": false, + "knowledge": "2024-08", + "release_date": "2025-09-02", + "last_updated": "2025-09-02", "type": "chat" }, { - "id": "Qwen3_5-9B-Q4_K_M", - "name": "Qwen 3.5 9B (Q4_K_M)", - "display_name": "Qwen 3.5 9B (Q4_K_M)", + "id": "gpt-5-chat-latest", + "name": "OpenAI GPT-5 Chat Latest", + "display_name": "OpenAI GPT-5 Chat Latest", "modalities": { "input": [ "text", @@ -219374,46 +217410,39 @@ ] }, "limit": { - "context": 32768, - "output": 8192 + "context": 128000, + "output": 16384 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": false }, "attachment": true, - "open_weights": true, - "release_date": "2026-02-23", - "last_updated": "2026-02-23", + "open_weights": false, + "knowledge": "2024-09", + "release_date": "2025-08-07", + "last_updated": "2025-08-07", "type": "chat" - } - ] - }, - "tencent-token-plan": { - "id": "tencent-token-plan", - "name": "Tencent Token Plan", - "display_name": "Tencent Token Plan", - "api": "https://api.lkeap.cloud.tencent.com/plan/v3", - "doc": "https://cloud.tencent.com/document/product/1823/130060", - "models": [ + }, { - "id": "hy4-preview", - "name": "Hy4 preview", - "display_name": "Hy4 preview", + "id": "o4-mini", + "name": "OpenAI o4 Mini", + "display_name": "OpenAI o4 Mini", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 1024000, - "output": 64000 + "context": 200000, + "output": 100000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -219422,35 +217451,42 @@ "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", "effort_options": [ - "none", + "low", + "medium", "high" - ] + ], + "visibility": "hidden" } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-08-28", - "last_updated": "2026-08-28", + "attachment": true, + "open_weights": false, + "knowledge": "2024-06", + "release_date": "2025-04-16", + "last_updated": "2025-04-16", "type": "chat" }, { - "id": "hy3", - "name": "Hy3", - "display_name": "Hy3", + "id": "gpt-5", + "name": "OpenAI GPT-5", + "display_name": "OpenAI GPT-5", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 256000, + "context": 400000, "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -219459,42 +217495,47 @@ "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", "effort_options": [ - "none", + "minimal", + "low", + "medium", "high" - ] + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-07-06", - "last_updated": "2026-07-06", + "attachment": true, + "open_weights": false, + "knowledge": "2025-01", + "release_date": "2025-08-07", + "last_updated": "2025-08-07", "type": "chat" - } - ] - }, - "mixlayer": { - "id": "mixlayer", - "name": "Mixlayer", - "display_name": "Mixlayer", - "api": "https://models.mixlayer.ai/v1", - "doc": "https://docs.mixlayer.com", - "models": [ + }, { - "id": "qwen/qwen3.5-9b", - "name": "Qwen3.5 9B", - "display_name": "Qwen3.5 9B", + "id": "gemini-2.5-pro", + "name": "Google Gemini 2.5 Pro", + "display_name": "Google Gemini 2.5 Pro", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 1048576, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -219505,37 +217546,47 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, + "default_enabled": true, + "mode": "budget", + "budget": { + "default": -1, + "min": 128, + "max": 32768, + "auto": -1, + "unit": "tokens" + }, "summaries": true, "visibility": "summary", "continuation": [ - "thinking_blocks" + "thought_signatures" ] } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-02-23", - "last_updated": "2026-02-23", + "attachment": true, + "open_weights": false, + "knowledge": "2025-06", + "release_date": "2025-06-17", + "last_updated": "2025-06-17", "type": "chat" }, { - "id": "qwen/qwen3.5-35b-a3b", - "name": "Qwen3.5 35B A3B", - "display_name": "Qwen3.5 35B A3B", + "id": "gpt-5-mini", + "name": "OpenAI GPT-5 Mini", + "display_name": "OpenAI GPT-5 Mini", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 400000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -219544,63 +217595,64 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] + "default_enabled": true, + "mode": "effort", + "effort": "medium", + "effort_options": [ + "minimal", + "low", + "medium", + "high" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-02-23", - "last_updated": "2026-02-23", + "attachment": true, + "open_weights": false, + "knowledge": "2025-01", + "release_date": "2025-08-07", + "last_updated": "2025-08-07", "type": "chat" }, { - "id": "qwen/qwen3.5-122b-a10b", - "name": "Qwen3.5 122B A10B", - "display_name": "Qwen3.5 122B A10B", + "id": "gpt-4o-mini", + "name": "OpenAI GPT-4o-mini", + "display_name": "OpenAI GPT-4o-mini", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 128000, + "output": 16384 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } + "supported": false }, - "attachment": false, - "open_weights": true, - "release_date": "2026-02-23", - "last_updated": "2026-02-23", + "attachment": true, + "open_weights": false, + "knowledge": "2024-07", + "release_date": "2024-07-18", + "last_updated": "2024-07-18", "type": "chat" }, { - "id": "qwen/qwen3.5-397b-a17b", - "name": "Qwen3.5 397B A17B", - "display_name": "Qwen3.5 397B A17B", + "id": "gpt-oss-120b", + "name": "OpenAI GPT-OSS 120b", + "display_name": "OpenAI GPT-OSS 120b", "modalities": { "input": [ "text" @@ -219610,8 +217662,8 @@ ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 131072, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -219622,24 +217674,24 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "low", + "medium", + "high" ] } }, "attachment": false, "open_weights": true, - "release_date": "2026-02-15", - "last_updated": "2026-02-15", + "knowledge": "2024-06", + "release_date": "2025-08-05", + "last_updated": "2025-08-05", "type": "chat" }, { - "id": "qwen/qwen3.5-27b", - "name": "Qwen3.5 27B", - "display_name": "Qwen3.5 27B", + "id": "mistral-nemo", + "name": "Mistral Nemo", + "display_name": "Mistral Nemo", "modalities": { "input": [ "text" @@ -219649,45 +217701,25 @@ ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 128000, + "output": 16400 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } + "supported": false }, "attachment": false, "open_weights": true, - "release_date": "2026-02-23", - "last_updated": "2026-02-23", + "knowledge": "2024-07", + "release_date": "2024-07-18", + "last_updated": "2024-07-18", "type": "chat" - } - ] - }, - "requesty": { - "id": "requesty", - "name": "Requesty", - "display_name": "Requesty", - "api": "https://router.requesty.ai/v1", - "doc": "https://requesty.ai/solution/llm-routing/models", - "models": [ + }, { - "id": "qwen3.7-max", - "name": "Qwen3.7 Max", - "display_name": "Qwen3.7 Max", + "id": "qwen2.5-coder-7b-fast", + "name": "Qwen2.5 Coder 7B fast", + "display_name": "Qwen2.5 Coder 7B fast", "modalities": { "input": [ "text" @@ -219697,92 +217729,126 @@ ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 32000, + "output": 8192 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "effort_options": [ - "none", - "low", - "medium", - "high", - "max" - ] - } + "supported": false }, "attachment": false, "open_weights": false, - "release_date": "2026-05-21", - "last_updated": "2026-05-21", + "knowledge": "2024-09", + "release_date": "2024-09-15", + "last_updated": "2024-09-15", "type": "chat" }, { - "id": "laguna-xs.2", - "name": "Laguna XS.2", - "display_name": "Laguna XS.2", + "id": "grok-4-fast-non-reasoning", + "name": "xAI Grok 4 Fast Non-Reasoning", + "display_name": "xAI Grok 4 Fast Non-Reasoning", "modalities": { "input": [ + "text", + "image", + "audio" + ], + "output": [ "text" + ] + }, + "limit": { + "context": 2000000, + "output": 2000000 + }, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": false + }, + "attachment": true, + "open_weights": false, + "knowledge": "2025-09", + "release_date": "2025-12-18", + "last_updated": "2025-12-18", + "type": "chat" + }, + { + "id": "claude-3.5-sonnet-v2", + "name": "Anthropic: Claude 3.5 Sonnet v2", + "display_name": "Anthropic: Claude 3.5 Sonnet v2", + "modalities": { + "input": [ + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 32768, - "output": 32768 + "context": 200000, + "output": 8192 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": true, - "default": true + "supported": false }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "none", - "low", - "medium", - "high", - "max" - ] - } + "attachment": true, + "open_weights": false, + "knowledge": "2024-10", + "release_date": "2024-10-22", + "last_updated": "2024-10-22", + "type": "chat" + }, + { + "id": "hermes-2-pro-llama-3-8b", + "name": "Hermes 2 Pro Llama 3 8B", + "display_name": "Hermes 2 Pro Llama 3 8B", + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "output": 131072 + }, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": false }, "attachment": false, "open_weights": true, - "release_date": "2026-04-28", - "last_updated": "2026-06-13", + "knowledge": "2024-05", + "release_date": "2024-04-30", + "last_updated": "2024-04-30", "type": "chat" }, { - "id": "gpt-5.5", - "name": "GPT-5.5", - "display_name": "GPT-5.5", + "id": "o1", + "name": "OpenAI: o1", + "display_name": "OpenAI: o1", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 200000, + "output": 100000 }, "temperature": false, - "tool_call": true, + "tool_call": false, "reasoning": { "supported": true, "default": true @@ -219794,13 +217860,6 @@ "mode": "effort", "effort": "medium", "effort_options": [ - "low", - "medium", - "high", - "xhigh" - ], - "verbosity": "medium", - "verbosity_options": [ "low", "medium", "high" @@ -219808,65 +217867,79 @@ "visibility": "hidden" } }, - "attachment": true, + "attachment": false, "open_weights": false, - "knowledge": "2025-12-01", - "release_date": "2026-04-23", - "last_updated": "2026-04-23", + "knowledge": "2025-01", + "release_date": "2024-12-05", + "last_updated": "2024-12-05", "type": "chat" }, { - "id": "gpt-6-astra", - "name": "GPT-6 Astra", - "display_name": "GPT-6 Astra", + "id": "gpt-4o", + "name": "OpenAI GPT-4o", + "display_name": "OpenAI GPT-4o", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 128000, + "output": 16384 }, - "temperature": false, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": false + }, + "attachment": true, + "open_weights": false, + "knowledge": "2024-05", + "release_date": "2024-05-13", + "last_updated": "2024-08-06", + "type": "chat" + }, + { + "id": "deepseek-v3.1-terminus", + "name": "DeepSeek V3.1 Terminus", + "display_name": "DeepSeek V3.1 Terminus", + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 16384 + }, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "effort_options": [ - "none", - "low", - "medium", - "high", - "max" - ] - } - }, - "attachment": true, + "attachment": false, "open_weights": false, - "knowledge": "2026-04-30", - "release_date": "2026-09-04", - "last_updated": "2026-09-04", + "knowledge": "2025-09", + "release_date": "2025-09-22", + "last_updated": "2025-09-22", "type": "chat" }, { - "id": "claude-opus-4-5", - "name": "Claude Opus 4.5 (latest)", - "display_name": "Claude Opus 4.5 (latest)", + "id": "claude-4.5-haiku", + "name": "Anthropic: Claude 4.5 Haiku", + "display_name": "Anthropic: Claude 4.5 Haiku", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" @@ -219874,67 +217947,39 @@ }, "limit": { "context": 200000, - "output": 64000 + "output": 8192 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "mixed", - "budget": { - "min": 1024, - "unit": "tokens" - }, - "effort": "high", - "effort_options": [ - "low", - "medium", - "high" - ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Claude Opus 4.5 uses manual thinking.type = \"enabled\" with budget_tokens; effort can be used alongside the thinking budget.", - "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header." - ] - } + "default": true }, "attachment": true, "open_weights": false, - "knowledge": "2025-05", - "release_date": "2025-11-24", - "last_updated": "2025-11-24", + "knowledge": "2025-10", + "release_date": "2025-10-15", + "last_updated": "2025-10-15", "type": "chat" }, { - "id": "gpt-5.4", - "name": "GPT-5.4", - "display_name": "GPT-5.4", + "id": "gpt-5.1", + "name": "OpenAI GPT-5.1", + "display_name": "OpenAI GPT-5.1", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1050000, + "context": 400000, "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -219950,8 +217995,7 @@ "none", "low", "medium", - "high", - "xhigh" + "high" ], "verbosity": "medium", "verbosity_options": [ @@ -219964,22 +218008,19 @@ }, "attachment": true, "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-03-05", - "last_updated": "2026-03-05", + "knowledge": "2025-01", + "release_date": "2025-11-13", + "last_updated": "2025-11-13", "type": "chat" }, { - "id": "gemini-3.5-flash-lite", - "name": "Gemini 3.5 Flash Lite", - "display_name": "Gemini 3.5 Flash Lite", + "id": "gemini-2.5-flash-lite", + "name": "Google Gemini 2.5 Flash Lite", + "display_name": "Google Gemini 2.5 Flash Lite", "modalities": { "input": [ "text", - "image", - "video", - "audio", - "pdf" + "image" ], "output": [ "text" @@ -219993,43 +218034,52 @@ "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, - "effort_options": [ - "none", - "low", - "medium", - "high", - "max" + "default_enabled": false, + "mode": "budget", + "budget": { + "default": -1, + "min": 512, + "max": 24576, + "auto": -1, + "unit": "tokens" + }, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thought_signatures" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-03", - "release_date": "2026-07-21", - "last_updated": "2026-07-21", + "knowledge": "2025-07", + "release_date": "2025-06-17", + "last_updated": "2025-06-17", "type": "chat" }, { - "id": "leanstral-1-5", - "name": "leanstral-1-5", - "display_name": "leanstral-1-5", + "id": "gemini-2.5-flash", + "name": "Google Gemini 2.5 Flash", + "display_name": "Google Gemini 2.5 Flash", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 32768 + "context": 1048576, + "output": 65535 }, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -220037,39 +218087,106 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "none", - "low", - "medium", - "high", - "max" + "supported": true, + "default_enabled": true, + "mode": "budget", + "budget": { + "default": -1, + "min": 0, + "max": 24576, + "auto": -1, + "off": 0, + "unit": "tokens" + }, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thought_signatures" ] } }, + "attachment": true, + "open_weights": false, + "knowledge": "2025-06", + "release_date": "2025-06-17", + "last_updated": "2025-06-17", + "type": "chat" + }, + { + "id": "ernie-4.5-21b-a3b-thinking", + "name": "Baidu Ernie 4.5 21B A3B Thinking", + "display_name": "Baidu Ernie 4.5 21B A3B Thinking", + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 8000 + }, + "temperature": true, + "tool_call": false, + "reasoning": { + "supported": true, + "default": true + }, "attachment": false, "open_weights": false, - "release_date": "2026-05-27", - "last_updated": "2026-05-27", + "knowledge": "2025-03", + "release_date": "2025-03-16", + "last_updated": "2025-03-16", "type": "chat" }, { - "id": "nemotron-3-nano-omni-30b-a3b-reasoning", - "name": "Nemotron 3 Nano Omni 30B A3B Reasoning", - "display_name": "Nemotron 3 Nano Omni 30B A3B Reasoning", + "id": "llama-3.3-70b-versatile", + "name": "Meta Llama 3.3 70B Versatile", + "display_name": "Meta Llama 3.3 70B Versatile", + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "output": 32678 + }, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": false + }, + "attachment": false, + "open_weights": true, + "knowledge": "2024-12", + "release_date": "2024-12-06", + "last_updated": "2024-12-06", + "type": "chat" + }, + { + "id": "gemini-3-pro-preview", + "name": "Google Gemini 3 Pro Preview", + "display_name": "Google Gemini 3 Pro Preview", "modalities": { "input": [ "text", "image", - "video", - "audio" + "audio", + "video" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 20480 + "context": 1048576, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -220080,39 +218197,43 @@ "extra_capabilities": { "reasoning": { "supported": true, - "effort_options": [ - "none", + "default_enabled": true, + "mode": "level", + "level": "high", + "level_options": [ "low", - "medium", - "high", - "max" + "high" + ], + "summaries": true, + "visibility": "summary", + "continuation": [ + "thought_signatures" ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-04-28", - "last_updated": "2026-04-28", + "open_weights": false, + "knowledge": "2025-11", + "release_date": "2025-11-18", + "last_updated": "2025-11-18", "type": "chat" }, { - "id": "glm-5.3-flash", - "name": "GLM-5.3-Flash", - "display_name": "GLM-5.3-Flash", + "id": "grok-4-1-fast-reasoning", + "name": "xAI Grok 4.1 Fast Reasoning", + "display_name": "xAI Grok 4.1 Fast Reasoning", "modalities": { "input": [ "text", - "image", - "video", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 262144 + "context": 2000000, + "output": 2000000 }, "temperature": true, "tool_call": true, @@ -220122,26 +218243,20 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "effort_options": [ - "none", - "low", - "medium", - "high", - "max" - ] + "supported": true } }, "attachment": true, - "open_weights": true, - "release_date": "2026-08-26", - "last_updated": "2026-08-26", + "open_weights": false, + "knowledge": "2025-11", + "release_date": "2025-11-19", + "last_updated": "2025-11-19", "type": "chat" }, { - "id": "mistral-medium-latest", - "name": "Mistral Medium (latest)", - "display_name": "Mistral Medium (latest)", + "id": "llama-4-maverick", + "name": "Meta Llama 4 Maverick 17B 128E", + "display_name": "Meta Llama 4 Maverick 17B 128E", "modalities": { "input": [ "text", @@ -220153,35 +218268,24 @@ }, "limit": { "context": 131072, - "output": 131072 + "output": 8192 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "none", - "low", - "medium", - "high", - "max" - ] - } + "supported": false }, "attachment": true, "open_weights": true, - "release_date": "2026-04-29", - "last_updated": "2026-04-29", + "knowledge": "2025-01", + "release_date": "2025-04-05", + "last_updated": "2025-04-05", "type": "chat" }, { - "id": "deepseek-v4-flash", - "name": "DeepSeek V4 Flash", - "display_name": "DeepSeek V4 Flash", + "id": "qwen3-coder-30b-a3b-instruct", + "name": "Qwen3 Coder 30B A3B Instruct", + "display_name": "Qwen3 Coder 30B A3B Instruct", "modalities": { "input": [ "text" @@ -220191,48 +218295,25 @@ ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 262144, + "output": 262144 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "high", - "effort_options": [ - "low", - "high", - "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." - ] - } + "supported": false }, "attachment": false, "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-04-24", - "last_updated": "2026-04-24", + "knowledge": "2025-07", + "release_date": "2025-07-31", + "last_updated": "2025-07-31", "type": "chat" }, { - "id": "seed-1.8", - "name": "Seed 1.8", - "display_name": "Seed 1.8", + "id": "mistral-small", + "name": "Mistral Small 3.2", + "display_name": "Mistral Small 3.2", "modalities": { "input": [ "text", @@ -220243,190 +218324,194 @@ ] }, "limit": { - "context": 256000, - "output": 64000 + "context": 128000, + "output": 16384 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "none", - "low", - "medium", - "high", - "max" - ] - } + "supported": false }, "attachment": true, + "open_weights": true, + "knowledge": "2025-03", + "release_date": "2025-06-20", + "last_updated": "2025-06-20", + "type": "chat" + }, + { + "id": "deepseek-tng-r1t2-chimera", + "name": "DeepSeek TNG R1T2 Chimera", + "display_name": "DeepSeek TNG R1T2 Chimera", + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 130000, + "output": 163840 + }, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": false + }, + "attachment": false, "open_weights": false, - "release_date": "2025-12-28", - "last_updated": "2025-12-28", + "knowledge": "2025-07", + "release_date": "2025-07-02", + "last_updated": "2025-07-02", "type": "chat" }, { - "id": "mimo-v2.6-pro", - "name": "MiMo-V2.6-Pro", - "display_name": "MiMo-V2.6-Pro", + "id": "grok-3-mini", + "name": "xAI Grok 3 Mini", + "display_name": "xAI Grok 3 Mini", "modalities": { "input": [ - "text", - "image", - "audio", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, + "context": 131072, "output": 131072 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "none", - "low", - "medium", - "high", - "max" - ] - } + "supported": false }, - "attachment": true, - "open_weights": true, - "release_date": "2026-09-22", - "last_updated": "2026-09-22", + "attachment": false, + "open_weights": false, + "knowledge": "2024-06", + "release_date": "2024-06-01", + "last_updated": "2024-06-01", "type": "chat" }, { - "id": "claude-sonnet-5-5", - "name": "Claude Sonnet 5.5", - "display_name": "Claude Sonnet 5.5", + "id": "claude-3.7-sonnet", + "name": "Anthropic: Claude 3.7 Sonnet", + "display_name": "Anthropic: Claude 3.7 Sonnet", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 200000, + "output": 64000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "none", - "low", - "medium", - "high", - "max" + "supported": true, + "default_enabled": false, + "mode": "budget", + "budget": { + "min": 1024, + "unit": "tokens" + }, + "interleaved": false, + "summaries": false, + "visibility": "full", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Anthropic uses thinking budget tokens" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-06", - "release_date": "2026-09-28", - "last_updated": "2026-09-28", + "knowledge": "2025-02", + "release_date": "2025-02-19", + "last_updated": "2025-02-19", "type": "chat" }, { - "id": "claude-fable-5.1", - "name": "Claude Fable 5.1", - "display_name": "Claude Fable 5.1", + "id": "gpt-5.1-codex-mini", + "name": "OpenAI: GPT-5.1 Codex Mini", + "display_name": "OpenAI: GPT-5.1 Codex Mini", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, + "context": 400000, "output": 128000 }, "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, + "default_enabled": false, "mode": "effort", - "effort": "high", + "effort": "none", "effort_options": [ + "none", "low", "medium", - "high", - "xhigh", - "max" + "high" ], - "interleaved": true, - "summaries": true, - "visibility": "omitted", - "continuation": [ - "thinking_blocks" + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" ], - "notes": [ - "Adaptive thinking is always on for Claude Fable 5 and Claude Mythos 5; thinking.type = \"disabled\" is rejected.", - "Manual budget_tokens requests return 400 on Claude Fable 5 and Claude Mythos 5.", - "thinking.display defaults to omitted; set display to summarized to receive readable thinking summaries." - ] + "visibility": "hidden" } }, "attachment": true, "open_weights": false, - "knowledge": "2026-06", - "release_date": "2026-09-01", - "last_updated": "2026-09-01", + "knowledge": "2025-01", + "release_date": "2025-11-13", + "last_updated": "2025-11-13", "type": "chat" }, { - "id": "step-3.7-flash", - "name": "Step 3.7 Flash", - "display_name": "Step 3.7 Flash", + "id": "grok-4-fast-reasoning", + "name": "xAI: Grok 4 Fast Reasoning", + "display_name": "xAI: Grok 4 Fast Reasoning", "modalities": { "input": [ "text", - "image", - "video" + "image" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 256000 + "context": 2000000, + "output": 2000000 }, "temperature": true, "tool_call": true, @@ -220434,108 +218519,74 @@ "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "none", - "low", - "medium", - "high", - "max" - ] - } - }, "attachment": true, - "open_weights": true, - "knowledge": "2026-03-01", - "release_date": "2026-05-29", - "last_updated": "2026-05-29", + "open_weights": false, + "knowledge": "2025-09", + "release_date": "2025-12-18", + "last_updated": "2025-12-18", "type": "chat" }, { - "id": "claude-haiku-5-5", - "name": "Claude Haiku 5.5", - "display_name": "Claude Haiku 5.5", + "id": "gemma2-9b-it", + "name": "Google Gemma 2", + "display_name": "Google Gemma 2", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 8192, + "output": 8192 }, - "temperature": false, - "tool_call": true, + "temperature": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "none", - "low", - "medium", - "high", - "max" - ] - } + "supported": false }, - "attachment": true, + "attachment": false, "open_weights": false, - "knowledge": "2026-06", - "release_date": "2026-10-07", - "last_updated": "2026-10-07", + "knowledge": "2024-06", + "release_date": "2024-06-25", + "last_updated": "2024-06-25", "type": "chat" }, { - "id": "nemotron-3-ultra-nvfp4", - "name": "nemotron-3-ultra-nvfp4", - "display_name": "nemotron-3-ultra-nvfp4", + "id": "grok-4-1-fast-non-reasoning", + "name": "xAI Grok 4.1 Fast Non-Reasoning", + "display_name": "xAI Grok 4.1 Fast Non-Reasoning", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 2000000, + "output": 30000 }, + "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "none", - "low", - "medium", - "high", - "max" - ] - } + "supported": false }, - "attachment": false, + "attachment": true, "open_weights": false, - "release_date": "2026-06-13", - "last_updated": "2026-06-13", + "knowledge": "2025-11", + "release_date": "2025-11-19", + "last_updated": "2025-11-19", "type": "chat" }, { - "id": "deepseek-v4.1-flash-1017", - "name": "DeepSeek V4.1 Flash 1017", - "display_name": "DeepSeek V4.1 Flash 1017", + "id": "gpt-4.1-mini-2025-04-14", + "name": "OpenAI GPT-4.1 Mini", + "display_name": "OpenAI GPT-4.1 Mini", "modalities": { "input": [ "text", @@ -220546,115 +218597,106 @@ ] }, "limit": { - "context": 1048576, - "output": 393216 + "context": 1047576, + "output": 32768 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "none", - "low", - "medium", - "high", - "max" - ] - } + "supported": false }, "attachment": true, - "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-10-17", - "last_updated": "2026-10-17", + "open_weights": false, + "knowledge": "2025-04", + "release_date": "2025-04-14", + "last_updated": "2025-04-14", "type": "chat" }, { - "id": "kimi-k3", - "name": "Kimi K3", - "display_name": "Kimi K3", + "id": "claude-opus-4", + "name": "Anthropic: Claude Opus 4", + "display_name": "Anthropic: Claude Opus 4", "modalities": { "input": [ "text", - "image", - "video" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 262144 + "context": 200000, + "output": 32000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, - "effort_options": [ - "none", - "low", - "medium", - "high", - "max" + "default_enabled": false, + "mode": "budget", + "budget": { + "min": 1024, + "unit": "tokens" + }, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", + "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-07-16", - "last_updated": "2026-07-16", + "open_weights": false, + "knowledge": "2025-05", + "release_date": "2025-05-22", + "last_updated": "2025-05-22", "type": "chat" }, { - "id": "gemini-3.1-flash-image", - "name": "Nano Banana 2", - "display_name": "Nano Banana 2", + "id": "claude-4.5-sonnet", + "name": "Anthropic: Claude Sonnet 4.5", + "display_name": "Anthropic: Claude Sonnet 4.5", "modalities": { "input": [ "text", - "image", - "video", - "pdf" + "image" ], "output": [ - "text", - "image" + "text" ] }, "limit": { - "context": 131072, - "output": 32768 + "context": 200000, + "output": 64000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true - } + "supported": true, + "default": true }, "attachment": true, "open_weights": false, - "knowledge": "2025-01", - "release_date": "2026-05-28", - "last_updated": "2026-05-28" + "knowledge": "2025-09", + "release_date": "2025-09-29", + "last_updated": "2025-09-29", + "type": "chat" }, { - "id": "deepseek-v4-pro", - "name": "DeepSeek V4 Pro", - "display_name": "DeepSeek V4 Pro", + "id": "grok-3", + "name": "xAI Grok 3", + "display_name": "xAI Grok 3", "modalities": { "input": [ "text" @@ -220664,174 +218706,114 @@ ] }, "limit": { - "context": 1000000, + "context": 131072, "output": 131072 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "high", - "effort_options": [ - "low", - "high", - "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." - ] - } + "supported": false }, "attachment": false, - "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-04-24", - "last_updated": "2026-04-24", + "open_weights": false, + "knowledge": "2024-06", + "release_date": "2024-06-01", + "last_updated": "2024-06-01", "type": "chat" }, { - "id": "claude-opus-5", - "name": "Claude Opus 5", - "display_name": "Claude Opus 5", + "id": "qwen3-coder", + "name": "Qwen3 Coder 480B A35B Instruct Turbo", + "display_name": "Qwen3 Coder 480B A35B Instruct Turbo", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 262144, + "output": 16384 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "effort_options": [ - "none", - "low", - "medium", - "high", - "max" - ] - } + "supported": false }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-05", - "release_date": "2026-07-24", - "last_updated": "2026-07-24", + "attachment": false, + "open_weights": true, + "knowledge": "2025-07", + "release_date": "2025-07-22", + "last_updated": "2025-07-22", "type": "chat" }, { - "id": "seed-2.0-pro", - "name": "Seed 2.0 Pro", - "display_name": "Seed 2.0 Pro", + "id": "sonar-pro", + "name": "Perplexity Sonar Pro", + "display_name": "Perplexity Sonar Pro", "modalities": { "input": [ - "text", - "image", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 256000, - "output": 128000 + "context": 200000, + "output": 4096 }, - "temperature": false, - "tool_call": true, + "temperature": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "none", - "low", - "medium", - "high", - "max" - ] - } + "supported": false }, - "attachment": true, + "attachment": false, "open_weights": false, - "release_date": "2026-02-14", - "last_updated": "2026-02-14", + "knowledge": "2025-01", + "release_date": "2024-01-01", + "last_updated": "2025-09-01", "type": "chat" }, { - "id": "mimo-v2.6-flash", - "name": "MiMo-V2.6-Flash", - "display_name": "MiMo-V2.6-Flash", + "id": "grok-4", + "name": "xAI Grok 4", + "display_name": "xAI Grok 4", "modalities": { "input": [ - "text", - "image", - "audio", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 256000, + "output": 256000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true + "supported": true }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "none", - "low", - "medium", - "high", - "max" - ] + "supported": true } }, - "attachment": true, - "open_weights": true, - "release_date": "2026-09-22", - "last_updated": "2026-09-22", + "attachment": false, + "open_weights": false, + "knowledge": "2024-07", + "release_date": "2024-07-09", + "last_updated": "2024-07-09", "type": "chat" }, { - "id": "gemma-4-31b-it", - "name": "Gemma 4 31B IT", - "display_name": "Gemma 4 31B IT", + "id": "claude-opus-4-1-20250805", + "name": "Anthropic: Claude Opus 4.1 (20250805)", + "display_name": "Anthropic: Claude Opus 4.1 (20250805)", "modalities": { "input": [ "text", @@ -220842,53 +218824,61 @@ ] }, "limit": { - "context": 262144, - "output": 8192 + "context": 200000, + "output": 32000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, - "effort_options": [ - "none", - "low", - "medium", - "high", - "max" + "default_enabled": false, + "mode": "budget", + "budget": { + "min": 1024, + "unit": "tokens" + }, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", + "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." ] } }, "attachment": true, - "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-04-02", - "last_updated": "2026-04-02", + "open_weights": false, + "knowledge": "2025-08", + "release_date": "2025-08-05", + "last_updated": "2025-08-05", "type": "chat" }, { - "id": "claude-opus-4-7", - "name": "Claude Opus 4.7", - "display_name": "Claude Opus 4.7", + "id": "claude-sonnet-4", + "name": "Anthropic: Claude Sonnet 4", + "display_name": "Anthropic: Claude Sonnet 4", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 200000, + "output": 64000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -220898,95 +218888,73 @@ "reasoning": { "supported": true, "default_enabled": false, - "mode": "effort", - "effort": "high", - "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" - ], + "mode": "budget", + "budget": { + "min": 1024, + "unit": "tokens" + }, "interleaved": true, "summaries": true, - "visibility": "omitted", + "visibility": "summary", "continuation": [ "thinking_blocks" ], "notes": [ - "Claude Opus 4.7 and newer Opus models require thinking.type = \"adaptive\" to enable thinking explicitly.", - "Manual budget_tokens requests return 400 on Claude Opus 4.7 and newer adaptive-only Opus models.", - "task_budget is separate from thinking control and should not be treated as a thinking budget." + "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", + "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-01-31", - "release_date": "2026-04-16", - "last_updated": "2026-04-16", + "knowledge": "2025-05", + "release_date": "2025-05-22", + "last_updated": "2025-05-22", "type": "chat" }, { - "id": "muse-spark-open", - "name": "Muse Spark Open", - "display_name": "Muse Spark Open", + "id": "llama-3.1-8b-instant", + "name": "Meta Llama 3.1 8B Instant", + "display_name": "Meta Llama 3.1 8B Instant", "modalities": { "input": [ - "text", - "image", - "video", - "audio" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 131072, + "output": 32678 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "none", - "low", - "medium", - "high", - "max" - ] - } + "supported": false }, - "attachment": true, + "attachment": false, "open_weights": true, - "knowledge": "2026-01-04", - "release_date": "2026-10-15", - "last_updated": "2026-10-15", + "knowledge": "2024-07", + "release_date": "2024-07-23", + "last_updated": "2024-07-23", "type": "chat" }, { - "id": "qwen3.5-35b-a3b", - "name": "Qwen3.5 35B-A3B", - "display_name": "Qwen3.5 35B-A3B", + "id": "qwen3-32b", + "name": "Qwen3 32B", + "display_name": "Qwen3 32B", "modalities": { "input": [ - "text", - "image", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 65536 + "context": 131072, + "output": 40960 }, "temperature": true, "tool_call": true, @@ -221002,207 +218970,188 @@ "visibility": "summary", "continuation": [ "thinking_blocks" - ], - "effort_options": [ - "none", - "low", - "medium", - "high", - "max" ] } }, - "attachment": true, + "attachment": false, "open_weights": true, - "release_date": "2026-02-23", - "last_updated": "2026-02-23", + "knowledge": "2025-04", + "release_date": "2025-04-28", + "last_updated": "2025-04-28", "type": "chat" }, { - "id": "muse-wonder-1.0", - "name": "Muse Wonder", - "display_name": "Muse Wonder", + "id": "gpt-4.1-mini", + "name": "OpenAI GPT-4.1 Mini", + "display_name": "OpenAI GPT-4.1 Mini", "modalities": { "input": [ "text", - "image", - "video", - "pdf", - "audio" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 943718 + "context": 1047576, + "output": 32768 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "none", - "low", - "medium", - "high", - "max" - ] - } + "supported": false }, "attachment": true, "open_weights": false, - "release_date": "2026-10-12", - "last_updated": "2026-10-12", + "knowledge": "2025-04", + "release_date": "2025-04-14", + "last_updated": "2025-04-14", "type": "chat" }, { - "id": "grok-4.5", - "name": "Grok 4.5", - "display_name": "Grok 4.5", + "id": "sonar-reasoning-pro", + "name": "Perplexity Sonar Reasoning Pro", + "display_name": "Perplexity Sonar Reasoning Pro", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 500000, - "output": 500000 + "context": 127000, + "output": 4096 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { "supported": true, "default": true }, "extra_capabilities": { "reasoning": { - "supported": true, "effort_options": [ - "none", + "minimal", "low", "medium", - "high", - "max" + "high" ] } }, - "attachment": true, + "attachment": false, "open_weights": false, - "release_date": "2026-07-08", - "last_updated": "2026-07-08", + "knowledge": "2025-01", + "release_date": "2024-01-01", + "last_updated": "2025-09-01", "type": "chat" }, { - "id": "kimi-k2.6", - "name": "Kimi K2.6", - "display_name": "Kimi K2.6", + "id": "gpt-4.1", + "name": "OpenAI GPT-4.1", + "display_name": "OpenAI GPT-4.1", "modalities": { "input": [ "text", - "image", - "video" + "image" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 1047576, + "output": 32768 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "effort_options": [ - "none", - "low", - "medium", - "high", - "max" - ] - } + "supported": false }, "attachment": true, + "open_weights": false, + "knowledge": "2025-04", + "release_date": "2025-04-14", + "last_updated": "2025-04-14", + "type": "chat" + }, + { + "id": "llama-prompt-guard-2-22m", + "name": "Meta Llama Prompt Guard 2 22M", + "display_name": "Meta Llama Prompt Guard 2 22M", + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 512, + "output": 2 + }, + "temperature": true, + "tool_call": false, + "reasoning": { + "supported": false + }, + "attachment": false, "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-04-20", - "last_updated": "2026-04-20", + "knowledge": "2024-10", + "release_date": "2025-04-29", + "last_updated": "2025-04-29", "type": "chat" }, { - "id": "claude-opus-5-5", - "name": "Claude Opus 5.5", - "display_name": "Claude Opus 5.5", + "id": "gpt-5.1-chat-latest", + "name": "OpenAI GPT-5.1 Chat", + "display_name": "OpenAI GPT-5.1 Chat", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 128000, + "output": 16384 }, "temperature": false, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "none", - "low", - "medium", - "high", - "max" - ] - } + "supported": false }, "attachment": true, "open_weights": false, - "knowledge": "2026-06", - "release_date": "2026-09-22", - "last_updated": "2026-09-22", + "knowledge": "2025-01", + "release_date": "2025-11-13", + "last_updated": "2025-11-13", "type": "chat" }, { - "id": "ring-2.6-1t", - "name": "ring-2.6-1t", - "display_name": "ring-2.6-1t", + "id": "o3", + "name": "OpenAI o3", + "display_name": "OpenAI o3", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 200000, + "output": 100000 }, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -221210,66 +219159,57 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", "effort_options": [ - "none", "low", "medium", - "high", - "max" - ] + "high" + ], + "visibility": "hidden" } }, - "attachment": false, + "attachment": true, "open_weights": false, - "release_date": "2026-05-08", - "last_updated": "2026-05-08", + "knowledge": "2024-06", + "release_date": "2025-04-16", + "last_updated": "2025-04-16", "type": "chat" }, { - "id": "kimi-k3.1-pro-highspeed", - "name": "Kimi K3.1 Pro Highspeed", - "display_name": "Kimi K3.1 Pro Highspeed", + "id": "kimi-k2-0711", + "name": "Kimi K2 (07/11)", + "display_name": "Kimi K2 (07/11)", "modalities": { "input": [ - "text", - "image", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 1048576 + "context": 131072, + "output": 16384 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "none", - "low", - "medium", - "high", - "max" - ] - } + "supported": false }, - "attachment": true, - "open_weights": false, - "release_date": "2026-10-23", - "last_updated": "2026-10-23", + "attachment": false, + "open_weights": true, + "knowledge": "2025-01", + "release_date": "2025-07-11", + "last_updated": "2025-07-11", "type": "chat" }, { - "id": "nemotron-lightning-3.5-30b-a3b", - "name": "nemotron-lightning-3.5-30b-a3b", - "display_name": "nemotron-lightning-3.5-30b-a3b", + "id": "gpt-oss-20b", + "name": "OpenAI GPT-OSS 20b", + "display_name": "OpenAI GPT-OSS 20b", "modalities": { "input": [ "text" @@ -221279,9 +219219,10 @@ ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 131072, + "output": 131072 }, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -221289,40 +219230,49 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, "effort_options": [ - "none", "low", "medium", - "high", - "max" + "high" ] } }, "attachment": false, - "open_weights": false, - "release_date": "2026-08-15", - "last_updated": "2026-08-15", + "open_weights": true, + "knowledge": "2024-06", + "release_date": "2025-08-05", + "last_updated": "2025-08-05", "type": "chat" - }, + } + ] + }, + "aixy": { + "id": "aixy", + "name": "Aixy", + "display_name": "Aixy", + "api": "https://api.aixy-gateway.com/v1", + "doc": "https://docs.aixy-gateway.com/integrations/overview", + "models": [ { - "id": "minimax-m3", - "name": "MiniMax-M3", - "display_name": "MiniMax-M3", + "id": "openai/gpt-6-sol", + "name": "GPT-6 Sol", + "display_name": "GPT-6 Sol", "modalities": { "input": [ "text", "image", - "video" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1000000, + "context": 1050000, "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -221330,39 +219280,40 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, "effort_options": [ "none", "low", "medium", "high", + "xhigh", "max" ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-06-01", - "last_updated": "2026-06-01", + "open_weights": false, + "knowledge": "2026-04-20", + "release_date": "2026-09-22", + "last_updated": "2026-09-22", "type": "chat" }, { - "id": "kimi-k3.1-pro", - "name": "Kimi K3.1 Pro", - "display_name": "Kimi K3.1 Pro", + "id": "openai/gpt-5.6-terra", + "name": "GPT-5.6 Terra", + "display_name": "GPT-5.6 Terra", "modalities": { "input": [ "text", "image", - "video" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 1048576 + "context": 1050000, + "output": 128000 }, "temperature": false, "tool_call": true, @@ -221372,40 +219323,53 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", "effort_options": [ "none", "low", "medium", "high", + "xhigh", "max" - ] + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, "attachment": true, "open_weights": false, - "release_date": "2026-10-23", - "last_updated": "2026-10-23", + "knowledge": "2026-02-16", + "release_date": "2026-07-09", + "last_updated": "2026-07-09", "type": "chat" }, { - "id": "minimax-m3.1-flash", - "name": "MiniMax-M3.1-Flash", - "display_name": "MiniMax-M3.1-Flash", + "id": "openai/gpt-6-astra", + "name": "GPT-6 Astra", + "display_name": "GPT-6 Astra", "modalities": { "input": [ "text", "image", - "video" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1000000, + "context": 1050000, "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -221413,25 +219377,27 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, "effort_options": [ - "none", "low", "medium", "high", + "xhigh", "max" ] } }, "attachment": true, "open_weights": false, - "release_date": "2026-10-19", - "last_updated": "2026-10-19", + "knowledge": "2026-04-30", + "release_date": "2026-09-04", + "last_updated": "2026-09-04", "type": "chat" }, { - "id": "claude-sonnet-5", - "name": "Claude Sonnet 5", - "display_name": "Claude Sonnet 5", + "id": "openai/gpt-6.1-sol", + "name": "GPT-6.1 Sol", + "display_name": "GPT-6.1 Sol", "modalities": { "input": [ "text", @@ -221443,7 +219409,7 @@ ] }, "limit": { - "context": 1000000, + "context": 1050000, "output": 128000 }, "temperature": false, @@ -221454,84 +219420,95 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, "effort_options": [ - "none", "low", "medium", "high", + "xhigh", "max" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-01-31", - "release_date": "2026-06-30", - "last_updated": "2026-06-30", + "knowledge": "2026-04-30", + "release_date": "2026-09-29", + "last_updated": "2026-09-29", "type": "chat" }, { - "id": "nvidia-nemotron-3-ultra", - "name": "Nemotron 3 Ultra 550B A55B", - "display_name": "Nemotron 3 Ultra 550B A55B", + "id": "openai/gpt-5.6-luna", + "name": "GPT-5.6 Luna", + "display_name": "GPT-5.6 Luna", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 131072 + "context": 1050000, + "output": 128000 }, - "temperature": true, - "tool_call": false, + "temperature": false, + "tool_call": true, "reasoning": { "supported": true, "default": true }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", "effort_options": [ "none", "low", "medium", "high", + "xhigh", "max" - ] + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-09", - "release_date": "2026-06-04", - "last_updated": "2026-06-04", + "attachment": true, + "open_weights": false, + "knowledge": "2026-02-16", + "release_date": "2026-07-09", + "last_updated": "2026-07-09", "type": "chat" }, { - "id": "muse-spark-1.3", - "name": "Muse Spark 1.3", - "display_name": "Muse Spark 1.3", + "id": "openai/gpt-5.6-sol", + "name": "GPT-5.6 Sol", + "display_name": "GPT-5.6 Sol", "modalities": { "input": [ "text", "image", - "video", - "pdf", - "audio" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 943718 + "context": 1050000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -221540,31 +219517,41 @@ "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", "effort_options": [ "none", "low", "medium", "high", + "xhigh", "max" - ] + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, "attachment": true, "open_weights": false, - "release_date": "2026-09-02", - "last_updated": "2026-09-02", + "knowledge": "2026-02-16", + "release_date": "2026-07-09", + "last_updated": "2026-07-09", "type": "chat" }, { - "id": "gemini-3.1-flash-lite", - "name": "Gemini 3.1 Flash Lite", - "display_name": "Gemini 3.1 Flash Lite", + "id": "openai/gpt-6-luna", + "name": "GPT-6 Luna", + "display_name": "GPT-6 Luna", "modalities": { "input": [ "text", "image", - "video", - "audio", "pdf" ], "output": [ @@ -221572,10 +219559,10 @@ ] }, "limit": { - "context": 1048576, - "output": 65535 + "context": 1050000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -221583,40 +219570,42 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, "effort_options": [ "none", "low", "medium", "high", + "xhigh", "max" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-01", - "release_date": "2026-05-07", - "last_updated": "2026-05-07", + "knowledge": "2026-05-18", + "release_date": "2026-09-22", + "last_updated": "2026-09-22", "type": "chat" }, { - "id": "minimax-m2.7", - "name": "MiniMax-M2.7", - "display_name": "MiniMax-M2.7", + "id": "gemini/gemini-4-argon", + "name": "Gemini 4 Argon", + "display_name": "Gemini 4 Argon", "modalities": { "input": [ - "text" + "text", + "image", + "audio" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 128000 + "context": 1048576, + "output": 32768 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -221624,39 +219613,29 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], "effort_options": [ - "none", "low", "medium", - "high", - "max" + "high" ] } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-03-18", - "last_updated": "2026-03-18", + "attachment": true, + "open_weights": false, + "knowledge": "2026-03", + "release_date": "2026-10-13", + "last_updated": "2026-10-13", "type": "chat" }, { - "id": "gemini-3.7-flash", - "name": "Gemini 3.7 Flash", - "display_name": "Gemini 3.7 Flash", + "id": "gemini/gemini-3.8-flash", + "name": "Gemini 3.8 Flash", + "display_name": "Gemini 3.8 Flash", "modalities": { "input": [ "text", "image", - "video", - "audio", - "pdf" + "audio" ], "output": [ "text" @@ -221664,7 +219643,7 @@ }, "limit": { "context": 1048576, - "output": 65535 + "output": 65536 }, "temperature": true, "tool_call": true, @@ -221693,18 +219672,19 @@ }, "attachment": true, "open_weights": false, - "knowledge": "2026-03", - "release_date": "2026-08-13", - "last_updated": "2026-08-13", + "release_date": "2026-09-02", + "last_updated": "2026-09-02", "type": "chat" }, { - "id": "glm-5.2-fast", - "name": "glm-5.2-fast", - "display_name": "glm-5.2-fast", + "id": "anthropic/claude-haiku-5-5", + "name": "Claude Haiku 5.5", + "display_name": "Claude Haiku 5.5", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" @@ -221712,8 +219692,9 @@ }, "limit": { "context": 1000000, - "output": 131072 + "output": 128000 }, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -221722,39 +219703,40 @@ "extra_capabilities": { "reasoning": { "effort_options": [ - "none", "low", "medium", "high", + "xhigh", "max" ] } }, - "attachment": false, + "attachment": true, "open_weights": false, - "release_date": "2026-07-13", - "last_updated": "2026-07-13", + "knowledge": "2026-06", + "release_date": "2026-10-07", + "last_updated": "2026-10-07", "type": "chat" }, { - "id": "qwen3.5-2b", - "name": "Qwen3.5 2B", - "display_name": "Qwen3.5 2B", + "id": "anthropic/claude-opus-5-5", + "name": "Claude Opus 5.5", + "display_name": "Claude Opus 5.5", "modalities": { "input": [ "text", "image", - "video" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 81920 + "context": 1000000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -221762,32 +219744,26 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], "effort_options": [ - "none", "low", "medium", "high", + "xhigh", "max" ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-03-02", - "last_updated": "2026-03-02", + "open_weights": false, + "knowledge": "2026-06", + "release_date": "2026-09-22", + "last_updated": "2026-09-22", "type": "chat" }, { - "id": "gpt-6-sol", - "name": "GPT-6 Sol", - "display_name": "GPT-6 Sol", + "id": "anthropic/claude-sonnet-5-5", + "name": "Claude Sonnet 5.5", + "display_name": "Claude Sonnet 5.5", "modalities": { "input": [ "text", @@ -221799,7 +219775,7 @@ ] }, "limit": { - "context": 1050000, + "context": 1000000, "output": 128000 }, "temperature": false, @@ -221811,25 +219787,25 @@ "extra_capabilities": { "reasoning": { "effort_options": [ - "none", "low", "medium", "high", + "xhigh", "max" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-04-20", - "release_date": "2026-09-22", - "last_updated": "2026-09-22", + "knowledge": "2026-06", + "release_date": "2026-09-28", + "last_updated": "2026-09-28", "type": "chat" }, { - "id": "gpt-5.6-sol", - "name": "GPT-5.6 Sol", - "display_name": "GPT-5.6 Sol", + "id": "anthropic/claude-fable-5-1", + "name": "Claude Fable 5.1", + "display_name": "Claude Fable 5.1", "modalities": { "input": [ "text", @@ -221841,7 +219817,7 @@ ] }, "limit": { - "context": 1050000, + "context": 1000000, "output": 128000 }, "temperature": false, @@ -221855,108 +219831,144 @@ "supported": true, "default_enabled": true, "mode": "effort", - "effort": "medium", + "effort": "high", "effort_options": [ - "none", "low", "medium", "high", "xhigh", "max" ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" + "interleaved": true, + "summaries": true, + "visibility": "omitted", + "continuation": [ + "thinking_blocks" ], - "visibility": "hidden" + "notes": [ + "Adaptive thinking is always on for Claude Fable 5 and Claude Mythos 5; thinking.type = \"disabled\" is rejected.", + "Manual budget_tokens requests return 400 on Claude Fable 5 and Claude Mythos 5.", + "thinking.display defaults to omitted; set display to summarized to receive readable thinking summaries." + ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-02-16", - "release_date": "2026-07-09", - "last_updated": "2026-07-09", + "knowledge": "2026-06", + "release_date": "2026-09-01", + "last_updated": "2026-09-01", "type": "chat" - }, + } + ] + }, + "echo": { + "id": "echo", + "name": "Echo", + "display_name": "Echo", + "api": "https://echo.tracerml.ai/v1", + "doc": "https://echo.tracerml.ai/docs/api", + "models": [ { - "id": "claude-sonnet-4-6", - "name": "Claude Sonnet 4.6", - "display_name": "Claude Sonnet 4.6", + "id": "echo", + "name": "Echo", + "display_name": "Echo", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 64000 + "context": 262144, + "output": 65536 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "mixed", - "budget": { - "min": 1024, - "unit": "tokens" - }, - "effort": "high", "effort_options": [ + "none", "low", "medium", - "high", - "max" - ], + "high" + ] + } + }, + "attachment": false, + "open_weights": false, + "release_date": "2026-07-19", + "last_updated": "2026-08-16", + "type": "chat" + } + ] + }, + "poolside": { + "id": "poolside", + "name": "Poolside", + "display_name": "Poolside", + "api": "https://inference.poolside.ai/v1", + "doc": "https://platform.poolside.ai", + "models": [ + { + "id": "poolside/laguna-xs-2.1", + "name": "Laguna XS 2.1", + "display_name": "Laguna XS 2.1", + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 262144, + "output": 32768 + }, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ "thinking_blocks" - ], - "notes": [ - "Anthropic recommends adaptive thinking with effort for Claude 4.6; budget_tokens remains a deprecated compatibility path.", - "Anthropic API defaults effort to high; lower effort levels should be chosen per workload." ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-02-17", - "last_updated": "2026-03-13", + "attachment": false, + "open_weights": true, + "release_date": "2026-07-02", + "last_updated": "2026-07-02", "type": "chat" }, { - "id": "qwen3.8-max", - "name": "Qwen3.8 Max", - "display_name": "Qwen3.8 Max", + "id": "poolside/laguna-m.1", + "name": "Laguna M.1", + "display_name": "Laguna M.1", "modalities": { "input": [ - "text", - "image", - "video", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 262144, + "output": 32768 }, "temperature": true, "tool_call": true, @@ -221967,40 +219979,37 @@ "extra_capabilities": { "reasoning": { "supported": true, - "effort_options": [ - "none", - "low", - "medium", - "high", - "max" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, - "attachment": true, - "open_weights": false, - "release_date": "2026-08-03", - "last_updated": "2026-08-03", + "attachment": false, + "open_weights": true, + "release_date": "2026-04-28", + "last_updated": "2026-06-13", "type": "chat" }, { - "id": "gpt-5.6-terra", - "name": "GPT-5.6 Terra", - "display_name": "GPT-5.6 Terra", + "id": "poolside/laguna-s-2.1", + "name": "Laguna S 2.1", + "display_name": "Laguna S 2.1", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 1048576, + "output": 32768 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -222009,79 +220018,72 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", - "effort_options": [ - "none", - "low", - "medium", - "high", - "xhigh", - "max" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-02-16", - "release_date": "2026-07-09", - "last_updated": "2026-07-09", + "attachment": false, + "open_weights": true, + "release_date": "2026-07-21", + "last_updated": "2026-07-21", "type": "chat" - }, + } + ] + }, + "scaleway": { + "id": "scaleway", + "name": "Scaleway", + "display_name": "Scaleway", + "api": "https://api.scaleway.ai/v1", + "doc": "https://www.scaleway.com/en/docs/generative-apis/", + "models": [ { - "id": "mimo-v2.5", - "name": "MiMo-V2.5", - "display_name": "MiMo-V2.5", + "id": "whisper-large-v3", + "name": "Whisper Large v3", + "display_name": "Whisper Large v3", "modalities": { "input": [ - "text", - "image", - "audio", - "video" + "audio" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 8192, + "output": 8192 }, - "temperature": true, - "tool_call": true, + "temperature": false, + "tool_call": false, "reasoning": { "supported": false }, "attachment": true, "open_weights": true, - "knowledge": "2024-12", - "release_date": "2026-04-22", - "last_updated": "2026-04-22", - "type": "chat" + "knowledge": "2023-09", + "release_date": "2023-09-01", + "last_updated": "2026-03-17" }, { - "id": "qwen3.8-2.4T-A95B", - "name": "Qwen3.8 2.4T A95B", - "display_name": "Qwen3.8 2.4T A95B", + "id": "gemma-4-26b-a4b-it", + "name": "Gemma 4 26B A4B IT", + "display_name": "Gemma 4 26B A4B IT", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 256000, + "output": 16384 }, "temperature": true, "tool_call": true, @@ -222096,63 +220098,49 @@ "none", "low", "medium", - "high", - "max" + "high" ] } }, - "attachment": false, + "attachment": true, "open_weights": true, - "release_date": "2026-08-12", - "last_updated": "2026-08-12", + "knowledge": "2025-04", + "release_date": "2026-04-02", + "last_updated": "2026-04-02", "type": "chat" }, { - "id": "gpt-6-luna", - "name": "GPT-6 Luna", - "display_name": "GPT-6 Luna", + "id": "lightonocr-3-0.8b", + "name": "LightOnOCR-3 0.8B", + "display_name": "LightOnOCR-3 0.8B", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 262144, + "output": 16384 }, - "temperature": false, - "tool_call": true, + "temperature": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "none", - "low", - "medium", - "high", - "max" - ] - } + "supported": false }, "attachment": true, - "open_weights": false, - "knowledge": "2026-05-18", - "release_date": "2026-09-22", - "last_updated": "2026-09-22", + "open_weights": true, + "release_date": "2026-10-08", + "last_updated": "2026-10-14", "type": "chat" }, { - "id": "deepseek-v4.1-flash", - "name": "DeepSeek V4.1 Flash", - "display_name": "DeepSeek V4.1 Flash", + "id": "qwen3.6-35b-a3b", + "name": "Qwen3.6 35B A3B", + "display_name": "Qwen3.6 35B A3B", "modalities": { "input": [ "text", @@ -222163,8 +220151,8 @@ ] }, "limit": { - "context": 1048576, - "output": 393216 + "context": 128000, + "output": 16384 }, "temperature": true, "tool_call": true, @@ -222175,31 +220163,35 @@ "extra_capabilities": { "reasoning": { "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ "none", "low", "medium", - "high", - "max" + "high" ] } }, "attachment": true, "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-09-10", - "last_updated": "2026-09-10", + "knowledge": "2025-04", + "release_date": "2026-04-17", + "last_updated": "2026-04-17", "type": "chat" }, { - "id": "seed-2.0-mini", - "name": "Seed 2.0 Mini", - "display_name": "Seed 2.0 Mini", + "id": "deepseek-v4.1-flash-1017", + "name": "DeepSeek V4.1 Flash 1017", + "display_name": "DeepSeek V4.1 Flash 1017", "modalities": { "input": [ "text", - "image", - "video" + "image" ], "output": [ "text" @@ -222207,7 +220199,7 @@ }, "limit": { "context": 256000, - "output": 32000 + "output": 32768 }, "temperature": true, "tool_call": true, @@ -222217,25 +220209,27 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "interleaved": true, "effort_options": [ "none", "low", - "medium", "high", "max" ] } }, "attachment": true, - "open_weights": false, - "release_date": "2026-02-14", - "last_updated": "2026-02-14", + "open_weights": true, + "knowledge": "2025-05", + "release_date": "2026-10-17", + "last_updated": "2026-10-17", "type": "chat" }, { - "id": "kat-coder-pro", - "name": "kat-coder-pro", - "display_name": "kat-coder-pro", + "id": "llama-3.3-70b-instruct", + "name": "Llama-3.3-70B-Instruct", + "display_name": "Llama-3.3-70B-Instruct", "modalities": { "input": [ "text" @@ -222245,110 +220239,91 @@ ] }, "limit": { - "context": 256000, - "output": 256000 + "context": 100000, + "output": 16384 }, + "temperature": true, "tool_call": true, "reasoning": { "supported": false }, "attachment": false, - "open_weights": false, - "release_date": "2026-03-27", - "last_updated": "2026-03-27", + "open_weights": true, + "knowledge": "2023-12", + "release_date": "2024-12-06", + "last_updated": "2024-12-06", "type": "chat" }, { - "id": "grok-build-0.1", - "name": "Grok Build 0.1", - "display_name": "Grok Build 0.1", + "id": "mistral-small-3.2-24b-instruct-2506", + "name": "Mistral Small 3.2 24B Instruct (2506)", + "display_name": "Mistral Small 3.2 24B Instruct (2506)", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 256000, - "output": 256000 + "context": 128000, + "output": 32768 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true - } + "supported": false }, "attachment": true, - "open_weights": false, - "release_date": "2026-04-16", - "last_updated": "2026-04-16", + "open_weights": true, + "knowledge": "2025-03", + "release_date": "2025-06-20", + "last_updated": "2025-06-20", "type": "chat" }, { - "id": "nemotron-3.5-content-safety", - "name": "Nemotron 3.5 Content Safety", - "display_name": "Nemotron 3.5 Content Safety", + "id": "qwen3-embedding-8b", + "name": "Qwen3 Embedding 8B", + "display_name": "Qwen3 Embedding 8B", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 131072, + "context": 32768, "output": 8192 }, - "temperature": true, + "temperature": false, "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "effort_options": [ - "none", - "low", - "medium", - "high", - "max" - ] - } + "supported": false }, - "attachment": true, + "attachment": false, "open_weights": true, - "release_date": "2026-06-02", - "last_updated": "2026-06-02", - "type": "chat" + "release_date": "2025-06-05", + "last_updated": "2025-06-05", + "type": "embedding" }, { - "id": "inkling", - "name": "Inkling", - "display_name": "Inkling", + "id": "gpt-oss-120b", + "name": "GPT-OSS 120B", + "display_name": "GPT-OSS 120B", "modalities": { "input": [ - "text", - "image", - "audio" + "text" ], "output": [ "text" ] }, "limit": { - "context": 65536, + "context": 128000, "output": 32768 }, "temperature": true, @@ -222359,66 +220334,76 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, "effort_options": [ - "none", "low", "medium", - "high", - "max" + "high" ] } }, - "attachment": true, + "attachment": false, "open_weights": true, - "release_date": "2026-07-15", - "last_updated": "2026-07-15", + "knowledge": "2024-06", + "release_date": "2025-08-05", + "last_updated": "2025-08-05", "type": "chat" }, { - "id": "mimo-v2.5-pro", - "name": "MiMo-V2.5-Pro", - "display_name": "MiMo-V2.5-Pro", + "id": "qwen3.8-27b", + "name": "Qwen3.8 27B", + "display_name": "Qwen3.8 27B", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 262144, + "output": 32768 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "none", + "low", + "medium", + "xhigh" + ] + } + }, + "attachment": true, "open_weights": true, - "knowledge": "2024-12", - "release_date": "2026-04-22", - "last_updated": "2026-04-22", + "release_date": "2026-08-14", + "last_updated": "2026-08-14", "type": "chat" }, { - "id": "grok-4.7", - "name": "Grok 4.7", - "display_name": "Grok 4.7", + "id": "glm-5.3", + "name": "GLM-5.3", + "display_name": "GLM-5.3", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 500000, - "output": 500000 + "context": 256000, + "output": 16384 }, "temperature": true, "tool_call": true, @@ -222428,39 +220413,36 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, "effort_options": [ - "none", "low", - "medium", "high", "max" ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-05", - "release_date": "2026-09-21", - "last_updated": "2026-09-21", + "attachment": false, + "open_weights": true, + "release_date": "2026-08-14", + "last_updated": "2026-08-14", "type": "chat" }, { - "id": "qwen3.6-plus", - "name": "Qwen3.6 Plus", - "display_name": "Qwen3.6 Plus", + "id": "mistral-medium-3.5-128b", + "name": "Mistral Medium 3.5 128B", + "display_name": "Mistral Medium 3.5 128B", "modalities": { "input": [ "text", - "image", - "video" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 65536 + "context": 256000, + "output": 16384 }, "temperature": true, "tool_call": true, @@ -222470,109 +220452,77 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], "effort_options": [ "none", - "low", - "medium", - "high", - "max" + "high" ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2025-04", - "release_date": "2026-04-02", - "last_updated": "2026-04-02", + "open_weights": true, + "release_date": "2026-04-29", + "last_updated": "2026-04-29", "type": "chat" }, { - "id": "grok-4.2-beta", - "name": "Grok 4.20 (Reasoning)", - "display_name": "Grok 4.20 (Reasoning)", + "id": "bge-multilingual-gemma2", + "name": "BGE Multilingual Gemma2", + "display_name": "BGE Multilingual Gemma2", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 2000000, - "output": 30000 + "context": 8191, + "output": 8192 }, - "temperature": true, - "tool_call": true, + "temperature": false, + "tool_call": false, "reasoning": { "supported": false }, - "attachment": true, - "open_weights": false, - "release_date": "2026-03-09", - "last_updated": "2026-03-09", - "type": "chat" + "attachment": false, + "open_weights": true, + "release_date": "2024-07-25", + "last_updated": "2024-07-25", + "type": "embedding" }, { - "id": "gemini-3-pro-image", - "name": "Nano Banana Pro", - "display_name": "Nano Banana Pro", + "id": "qwen3-235b-a22b-instruct-2507", + "name": "Qwen3 235B A22B Instruct 2507", + "display_name": "Qwen3 235B A22B Instruct 2507", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ - "text", - "image" + "text" ] }, "limit": { - "context": 1048576, - "output": 32768 + "context": 260000, + "output": 16384 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "level", - "level": "high", - "level_options": [ - "low", - "high" - ], - "summaries": true, - "visibility": "summary", - "continuation": [ - "thought_signatures" - ] - } + "supported": false }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-01", - "release_date": "2026-05-28", - "last_updated": "2026-05-28" + "attachment": false, + "open_weights": true, + "knowledge": "2025-04", + "release_date": "2025-07-21", + "last_updated": "2025-07-21", + "type": "chat" }, { - "id": "deepseek-v4-flash-0731", - "name": "DeepSeek V4 Flash 0731", - "display_name": "DeepSeek V4 Flash 0731", + "id": "glm-5.2", + "name": "GLM-5.2", + "display_name": "GLM-5.2", "modalities": { "input": [ "text" @@ -222582,8 +220532,8 @@ ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 256000, + "output": 16384 }, "temperature": true, "tool_call": true, @@ -222598,36 +220548,35 @@ "none", "low", "medium", - "high", - "max" + "high" ] } }, "attachment": false, "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-07-31", - "last_updated": "2026-07-31", + "release_date": "2026-06-13", + "last_updated": "2026-06-13", "type": "chat" }, { - "id": "gpt-5.4-pro", - "name": "GPT-5.4 Pro", - "display_name": "GPT-5.4 Pro", + "id": "qwen3.5-397b-a17b", + "name": "Qwen3.5 397B A17B", + "display_name": "Qwen3.5 397B A17B", "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 256000, + "output": 16384 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -222636,60 +220585,31 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "high", - "effort_options": [ - "medium", - "high", - "xhigh" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ], - "verbosity": "medium", - "verbosity_options": [ + "effort_options": [ + "none", "low", "medium", "high" - ], - "visibility": "hidden" + ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-03-05", - "last_updated": "2026-03-05", - "type": "chat" - }, - { - "id": "ling-2.6-flash", - "name": "ling-2.6-flash", - "display_name": "ling-2.6-flash", - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 262144, - "output": 262144 - }, - "tool_call": true, - "reasoning": { - "supported": false - }, - "attachment": false, - "open_weights": false, - "release_date": "2026-04-21", - "last_updated": "2026-04-21", + "open_weights": true, + "knowledge": "2025-04", + "release_date": "2026-02-15", + "last_updated": "2026-02-15", "type": "chat" }, { - "id": "deepseek-v4-pro-0813", - "name": "DeepSeek V4 Pro 0813", - "display_name": "DeepSeek V4 Pro 0813", + "id": "deepseek-v4-flash-0731", + "name": "DeepSeek V4 Flash 0731", + "display_name": "DeepSeek V4 Flash 0731", "modalities": { "input": [ "text" @@ -222699,8 +220619,8 @@ ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 256000, + "output": 32768 }, "temperature": true, "tool_call": true, @@ -222711,10 +220631,10 @@ "extra_capabilities": { "reasoning": { "supported": true, + "interleaved": true, "effort_options": [ "none", "low", - "medium", "high", "max" ] @@ -222722,25 +220642,39 @@ }, "attachment": false, "open_weights": true, - "release_date": "2026-08-13", - "last_updated": "2026-08-22", + "knowledge": "2025-05", + "release_date": "2026-07-31", + "last_updated": "2026-07-31", "type": "chat" - }, + } + ] + }, + "requesty": { + "id": "requesty", + "name": "Requesty", + "display_name": "Requesty", + "api": "https://router.requesty.ai/v1", + "doc": "https://requesty.ai/solution/llm-routing/models", + "models": [ { - "id": "hy3", - "name": "Hy3", - "display_name": "Hy3", + "id": "gemini-3.5-flash", + "name": "Gemini 3.5 Flash", + "display_name": "Gemini 3.5 Flash", "modalities": { "input": [ - "text" + "text", + "image", + "video", + "audio", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 1048576, + "output": 65535 }, "temperature": true, "tool_call": true, @@ -222751,37 +220685,44 @@ "extra_capabilities": { "reasoning": { "supported": true, - "effort_options": [ - "none", + "default_enabled": true, + "mode": "level", + "level": "high", + "level_options": [ + "minimal", "low", "medium", - "high", - "max" + "high" + ], + "summaries": true, + "visibility": "summary", + "continuation": [ + "thought_signatures" ] } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-07-06", - "last_updated": "2026-07-06", + "attachment": true, + "open_weights": false, + "knowledge": "2025-01", + "release_date": "2026-05-19", + "last_updated": "2026-05-19", "type": "chat" }, { - "id": "gemma-4-26b-a4b-it", - "name": "Gemma 4 26B A4B IT", - "display_name": "Gemma 4 26B A4B IT", + "id": "nemotron-3.5-lightning-30b-a3b", + "name": "Nemotron 3.5 Lightning 30B A3B", + "display_name": "Nemotron 3.5 Lightning 30B A3B", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 32768 + "context": 1048576, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -222791,7 +220732,6 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, "effort_options": [ "none", "low", @@ -222801,28 +220741,28 @@ ] } }, - "attachment": true, + "attachment": false, "open_weights": true, - "release_date": "2026-04-02", - "last_updated": "2026-04-02", + "knowledge": "2025-09", + "release_date": "2026-08-11", + "last_updated": "2026-08-11", "type": "chat" }, { - "id": "claude-opus-4-6", - "name": "Claude Opus 4.6", - "display_name": "Claude Opus 4.6", + "id": "gpt-5.4-nano", + "name": "GPT-5.4 nano", + "display_name": "GPT-5.4 nano", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, + "context": 400000, "output": 128000 }, "temperature": true, @@ -222835,41 +220775,35 @@ "reasoning": { "supported": true, "default_enabled": false, - "mode": "mixed", - "budget": { - "min": 1024, - "unit": "tokens" - }, - "effort": "high", + "mode": "effort", + "effort": "none", "effort_options": [ + "none", "low", "medium", "high", - "max" + "xhigh" ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" ], - "notes": [ - "Anthropic recommends adaptive thinking with effort for Claude 4.6; budget_tokens remains a deprecated compatibility path.", - "Anthropic API defaults effort to high; lower effort levels should be chosen per workload." - ] + "visibility": "hidden" } }, "attachment": true, "open_weights": false, - "knowledge": "2025-05-31", - "release_date": "2026-02-05", - "last_updated": "2026-03-13", + "knowledge": "2025-08-31", + "release_date": "2026-03-17", + "last_updated": "2026-03-17", "type": "chat" }, { - "id": "kimi-k2.7-code", - "name": "Kimi K2.7 Code", - "display_name": "Kimi K2.7 Code", + "id": "kimi-k3", + "name": "Kimi K3", + "display_name": "Kimi K3", "modalities": { "input": [ "text", @@ -222881,7 +220815,7 @@ ] }, "limit": { - "context": 262144, + "context": 1048576, "output": 262144 }, "temperature": false, @@ -222904,15 +220838,14 @@ }, "attachment": true, "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-06-12", - "last_updated": "2026-06-12", + "release_date": "2026-07-16", + "last_updated": "2026-07-16", "type": "chat" }, { - "id": "nemotron-3-ultra-550b-a55b", - "name": "Nemotron 3 Ultra 550B A55B", - "display_name": "Nemotron 3 Ultra 550B A55B", + "id": "nemotron-lightning-3.5-30b-a3b", + "name": "nemotron-lightning-3.5-30b-a3b", + "display_name": "nemotron-lightning-3.5-30b-a3b", "modalities": { "input": [ "text" @@ -222922,10 +220855,9 @@ ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 262144, + "output": 262144 }, - "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -222933,7 +220865,6 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, "effort_options": [ "none", "low", @@ -222944,16 +220875,15 @@ } }, "attachment": false, - "open_weights": true, - "knowledge": "2025-09", - "release_date": "2026-06-04", - "last_updated": "2026-06-04", + "open_weights": false, + "release_date": "2026-08-15", + "last_updated": "2026-08-15", "type": "chat" }, { - "id": "claude-opus-4-1", - "name": "Claude Opus 4.1 (latest)", - "display_name": "Claude Opus 4.1 (latest)", + "id": "gpt-6.1-terra", + "name": "GPT-6.1 Terra", + "display_name": "GPT-6.1 Terra", "modalities": { "input": [ "text", @@ -222965,47 +220895,37 @@ ] }, "limit": { - "context": 200000, - "output": 32000 + "context": 1050000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, - "reasoning": { - "supported": true, - "default": false - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "budget", - "budget": { - "min": 1024, - "unit": "tokens" - }, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", - "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." + "reasoning": { + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "none", + "low", + "medium", + "high", + "max" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-03-31", - "release_date": "2025-08-05", - "last_updated": "2025-08-05", + "knowledge": "2026-06-01", + "release_date": "2026-10-26", + "last_updated": "2026-10-26", "type": "chat" }, { - "id": "gpt-5.6-luna", - "name": "GPT-5.6 Luna", - "display_name": "GPT-5.6 Luna", + "id": "gpt-5.4", + "name": "GPT-5.4", + "display_name": "GPT-5.4", "modalities": { "input": [ "text", @@ -223020,25 +220940,24 @@ "context": 1050000, "output": 128000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, + "default_enabled": false, "mode": "effort", - "effort": "medium", + "effort": "none", "effort_options": [ "none", "low", "medium", "high", - "xhigh", - "max" + "xhigh" ], "verbosity": "medium", "verbosity_options": [ @@ -223051,9 +220970,9 @@ }, "attachment": true, "open_weights": false, - "knowledge": "2026-02-16", - "release_date": "2026-07-09", - "last_updated": "2026-07-09", + "knowledge": "2025-08-31", + "release_date": "2026-03-05", + "last_updated": "2026-03-05", "type": "chat" }, { @@ -223109,23 +221028,21 @@ "type": "chat" }, { - "id": "mistral-large-4", - "name": "Mistral Large 4", - "display_name": "Mistral Large 4", + "id": "nemotron-3-ultra-nvfp4", + "name": "nemotron-3-ultra-nvfp4", + "display_name": "nemotron-3-ultra-nvfp4", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 524288, + "context": 262144, "output": 262144 }, - "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -223142,20 +221059,21 @@ ] } }, - "attachment": true, + "attachment": false, "open_weights": false, - "release_date": "2026-10-06", - "last_updated": "2026-10-06", + "release_date": "2026-06-13", + "last_updated": "2026-06-13", "type": "chat" }, { - "id": "mistral-small-2603", - "name": "Mistral Small 4", - "display_name": "Mistral Small 4", + "id": "grok-build-0.1", + "name": "Grok Build 0.1", + "display_name": "Grok Build 0.1", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" @@ -223168,31 +221086,23 @@ "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true + "supported": true }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "none", - "low", - "medium", - "high", - "max" - ] + "supported": true } }, "attachment": true, - "open_weights": true, - "knowledge": "2025-06", - "release_date": "2026-03-16", - "last_updated": "2026-03-16", + "open_weights": false, + "release_date": "2026-04-16", + "last_updated": "2026-04-16", "type": "chat" }, { - "id": "gpt-5.3-codex", - "name": "GPT-5.3 Codex", - "display_name": "GPT-5.3 Codex", + "id": "gpt-6-sol", + "name": "GPT-6 Sol", + "display_name": "GPT-6 Sol", "modalities": { "input": [ "text", @@ -223204,10 +221114,10 @@ ] }, "limit": { - "context": 400000, + "context": 1050000, "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -223215,51 +221125,41 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", "effort_options": [ + "none", "low", "medium", "high", - "xhigh" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + "max" + ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-02-05", - "last_updated": "2026-02-05", + "knowledge": "2026-04-20", + "release_date": "2026-09-22", + "last_updated": "2026-09-22", "type": "chat" }, { - "id": "qwen3.8-flash-next", - "name": "Qwen3.8 Flash Next", - "display_name": "Qwen3.8 Flash Next", + "id": "gpt-5.6-terra", + "name": "GPT-5.6 Terra", + "display_name": "GPT-5.6 Terra", "modalities": { "input": [ "text", "image", - "video" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 1050000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -223267,75 +221167,79 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", "effort_options": [ "none", "low", "medium", "high", + "xhigh", "max" - ] + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, "attachment": true, - "open_weights": true, - "release_date": "2026-08-27", - "last_updated": "2026-08-27", + "open_weights": false, + "knowledge": "2026-02-16", + "release_date": "2026-07-09", + "last_updated": "2026-07-09", "type": "chat" }, { - "id": "nemotron-3-super-120b-a12b", - "name": "Nemotron 3 Super 120B A12B", - "display_name": "Nemotron 3 Super 120B A12B", + "id": "grok-4.2-beta", + "name": "Grok 4.20 (Reasoning)", + "display_name": "Grok 4.20 (Reasoning)", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 2000000, + "output": 30000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "none", - "low", - "medium", - "high", - "max" - ] - } + "supported": false }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-06", - "release_date": "2026-03-11", - "last_updated": "2026-03-11", + "attachment": true, + "open_weights": false, + "release_date": "2026-03-09", + "last_updated": "2026-03-09", "type": "chat" }, { - "id": "glm-5.1", - "name": "GLM-5.1", - "display_name": "GLM-5.1", + "id": "gpt-5.3-codex", + "name": "GPT-5.3 Codex", + "display_name": "GPT-5.3 Codex", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 200000, + "context": 400000, "output": 128000 }, "temperature": true, @@ -223347,40 +221251,50 @@ "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", "effort_options": [ - "none", "low", "medium", "high", - "max" - ] + "xhigh" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-04-07", - "last_updated": "2026-04-07", + "attachment": true, + "open_weights": false, + "knowledge": "2025-08-31", + "release_date": "2026-02-05", + "last_updated": "2026-02-05", "type": "chat" }, { - "id": "qwen3.8-flash", - "name": "Qwen3.8 Flash", - "display_name": "Qwen3.8 Flash", + "id": "gpt-6-astra", + "name": "GPT-6 Astra", + "display_name": "GPT-6 Astra", "modalities": { "input": [ "text", "image", - "video" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 1050000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -223400,21 +221314,20 @@ }, "attachment": true, "open_weights": false, - "release_date": "2026-08-26", - "last_updated": "2026-08-26", + "knowledge": "2026-04-30", + "release_date": "2026-09-04", + "last_updated": "2026-09-04", "type": "chat" }, { - "id": "gemini-3.1-pro-preview", - "name": "Gemini 3.1 Pro Preview", - "display_name": "Gemini 3.1 Pro Preview", + "id": "step-5-preview", + "name": "Step 5 Preview", + "display_name": "Step 5 Preview", "modalities": { "input": [ "text", "image", - "video", - "audio", - "pdf" + "video" ], "output": [ "text" @@ -223422,7 +221335,7 @@ }, "limit": { "context": 1048576, - "output": 65535 + "output": 65536 }, "temperature": true, "tool_call": true, @@ -223432,32 +221345,25 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "level", - "level": "high", - "level_options": [ + "effort_options": [ + "none", "low", - "high" - ], - "summaries": true, - "visibility": "summary", - "continuation": [ - "thought_signatures" + "medium", + "high", + "max" ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2025-01", - "release_date": "2026-02-19", - "last_updated": "2026-02-19", + "open_weights": true, + "release_date": "2026-09-16", + "last_updated": "2026-09-20", "type": "chat" }, { - "id": "claude-haiku-4-5", - "name": "Claude Haiku 4.5 (latest)", - "display_name": "Claude Haiku 4.5 (latest)", + "id": "claude-haiku-5-5", + "name": "Claude Haiku 5.5", + "display_name": "Claude Haiku 5.5", "modalities": { "input": [ "text", @@ -223469,64 +221375,51 @@ ] }, "limit": { - "context": 200000, - "output": 64000 + "context": 1000000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "budget", - "budget": { - "min": 1024, - "unit": "tokens" - }, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", - "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." + "effort_options": [ + "none", + "low", + "medium", + "high", + "max" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-02-28", - "release_date": "2025-10-15", - "last_updated": "2025-10-15", + "knowledge": "2026-06", + "release_date": "2026-10-07", + "last_updated": "2026-10-07", "type": "chat" }, { - "id": "gemini-3.8-flash", - "name": "Gemini 3.8 Flash", - "display_name": "Gemini 3.8 Flash", + "id": "gpt-5.4-pro", + "name": "GPT-5.4 Pro", + "display_name": "GPT-5.4 Pro", "modalities": { "input": [ "text", - "image", - "video", - "audio", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 65535 + "context": 1050000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -223536,94 +221429,87 @@ "reasoning": { "supported": true, "default_enabled": true, - "mode": "level", - "level": "high", - "level_options": [ - "minimal", + "mode": "effort", + "effort": "high", + "effort_options": [ + "medium", + "high", + "xhigh" + ], + "verbosity": "medium", + "verbosity_options": [ "low", "medium", "high" ], - "summaries": true, - "visibility": "summary", - "continuation": [ - "thought_signatures" - ] + "visibility": "hidden" } }, "attachment": true, "open_weights": false, - "release_date": "2026-09-02", - "last_updated": "2026-09-02", + "knowledge": "2025-08-31", + "release_date": "2026-03-05", + "last_updated": "2026-03-05", "type": "chat" }, { - "id": "gpt-5.4-nano", - "name": "GPT-5.4 nano", - "display_name": "GPT-5.4 nano", + "id": "seed-2.0-pro", + "name": "Seed 2.0 Pro", + "display_name": "Seed 2.0 Pro", "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 400000, + "context": 256000, "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "none", "effort_options": [ "none", "low", "medium", "high", - "xhigh" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + "max" + ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-03-17", - "last_updated": "2026-03-17", + "release_date": "2026-02-14", + "last_updated": "2026-02-14", "type": "chat" }, { - "id": "minimax-m2.7-highspeed", - "name": "MiniMax-M2.7-highspeed", - "display_name": "MiniMax-M2.7-highspeed", + "id": "qwen3.5-27b", + "name": "Qwen3.5 27B", + "display_name": "Qwen3.5 27B", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 128000 + "context": 262144, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -223649,31 +221535,66 @@ ] } }, - "attachment": false, + "attachment": true, "open_weights": true, - "release_date": "2026-03-18", - "last_updated": "2026-03-18", + "release_date": "2026-02-23", + "last_updated": "2026-02-23", "type": "chat" }, { - "id": "gpt-5.5-pro", - "name": "GPT-5.5 Pro", - "display_name": "GPT-5.5 Pro", + "id": "gemini-3.1-flash-image", + "name": "Nano Banana 2", + "display_name": "Nano Banana 2", "modalities": { "input": [ "text", "image", + "video", "pdf" ], + "output": [ + "text", + "image" + ] + }, + "limit": { + "context": 131072, + "output": 32768 + }, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true + } + }, + "attachment": true, + "open_weights": false, + "knowledge": "2025-01", + "release_date": "2026-05-28", + "last_updated": "2026-05-28" + }, + { + "id": "gemma-4-26b-a4b-it", + "name": "Gemma 4 26B A4B IT", + "display_name": "Gemma 4 26B A4B IT", + "modalities": { + "input": [ + "text", + "image" + ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 262144, + "output": 32768 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -223692,29 +221613,30 @@ } }, "attachment": true, - "open_weights": false, - "knowledge": "2025-12-01", - "release_date": "2026-04-23", - "last_updated": "2026-04-23", + "open_weights": true, + "release_date": "2026-04-02", + "last_updated": "2026-04-02", "type": "chat" }, { - "id": "glm-5.3", - "name": "GLM-5.3", - "display_name": "GLM-5.3", + "id": "kimi-k2.7-code", + "name": "Kimi K2.7 Code", + "display_name": "Kimi K2.7 Code", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 1048576 + "context": 262144, + "output": 262144 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -223732,16 +221654,17 @@ ] } }, - "attachment": false, + "attachment": true, "open_weights": true, - "release_date": "2026-08-14", - "last_updated": "2026-08-14", + "knowledge": "2025-01", + "release_date": "2026-06-12", + "last_updated": "2026-06-12", "type": "chat" }, { - "id": "claude-opus-4-8", - "name": "Claude Opus 4.8", - "display_name": "Claude Opus 4.8", + "id": "gpt-6.1-sol", + "name": "GPT-6.1 Sol", + "display_name": "GPT-6.1 Sol", "modalities": { "input": [ "text", @@ -223753,52 +221676,37 @@ ] }, "limit": { - "context": 1000000, + "context": 1050000, "output": 128000 }, "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "high", "effort_options": [ + "none", "low", "medium", "high", - "xhigh", "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "omitted", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Claude Opus 4.7 and newer Opus models require thinking.type = \"adaptive\" to enable thinking explicitly.", - "Manual budget_tokens requests return 400 on Claude Opus 4.7 and newer adaptive-only Opus models.", - "task_budget is separate from thinking control and should not be treated as a thinking budget." ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-01", - "release_date": "2026-05-28", - "last_updated": "2026-05-28", + "knowledge": "2026-04-30", + "release_date": "2026-09-29", + "last_updated": "2026-09-29", "type": "chat" }, { - "id": "devstral-latest", - "name": "devstral-latest", - "display_name": "devstral-latest", + "id": "nemotron-3-ultra-550b-a55b", + "name": "Nemotron 3 Ultra 550B A55B", + "display_name": "Nemotron 3 Ultra 550B A55B", "modalities": { "input": [ "text" @@ -223808,9 +221716,10 @@ ] }, "limit": { - "context": 256000, - "output": 256000 + "context": 1048576, + "output": 65536 }, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -223818,6 +221727,7 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, "effort_options": [ "none", "low", @@ -223828,15 +221738,16 @@ } }, "attachment": false, - "open_weights": false, - "release_date": "2026-05-27", - "last_updated": "2026-05-27", + "open_weights": true, + "knowledge": "2025-09", + "release_date": "2026-06-04", + "last_updated": "2026-06-04", "type": "chat" }, { - "id": "laguna-m.1", - "name": "Laguna M.1", - "display_name": "Laguna M.1", + "id": "nvidia-nemotron-3-super-120b-a12b", + "name": "Nemotron 3 Super 120B A12B", + "display_name": "Nemotron 3 Super 120B A12B", "modalities": { "input": [ "text" @@ -223846,17 +221757,18 @@ ] }, "limit": { - "context": 32768, - "output": 32768 + "context": 262144, + "output": 262144 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { "supported": true, "default": true }, "extra_capabilities": { "reasoning": { + "supported": true, "effort_options": [ "none", "low", @@ -223868,18 +221780,20 @@ }, "attachment": false, "open_weights": true, - "release_date": "2026-04-28", - "last_updated": "2026-06-13", + "knowledge": "2025-06", + "release_date": "2026-03-11", + "last_updated": "2026-03-11", "type": "chat" }, { - "id": "seed-2.0-code", - "name": "Seed 2.0 Code", - "display_name": "Seed 2.0 Code", + "id": "mimo-v2.6-pro", + "name": "MiMo-V2.6-Pro", + "display_name": "MiMo-V2.6-Pro", "modalities": { "input": [ "text", "image", + "audio", "video" ], "output": [ @@ -223887,7 +221801,7 @@ ] }, "limit": { - "context": 256000, + "context": 1048576, "output": 131072 }, "temperature": true, @@ -223908,9 +221822,9 @@ } }, "attachment": true, - "open_weights": false, - "release_date": "2026-02-14", - "last_updated": "2026-02-14", + "open_weights": true, + "release_date": "2026-09-22", + "last_updated": "2026-09-22", "type": "chat" }, { @@ -223958,24 +221872,22 @@ "type": "chat" }, { - "id": "gemini-3.6-flash", - "name": "Gemini 3.6 Flash", - "display_name": "Gemini 3.6 Flash", + "id": "gemini-3-pro-image", + "name": "Nano Banana Pro", + "display_name": "Nano Banana Pro", "modalities": { "input": [ "text", - "image", - "video", - "audio", - "pdf" + "image" ], "output": [ - "text" + "text", + "image" ] }, "limit": { "context": 1048576, - "output": 65535 + "output": 32768 }, "temperature": true, "tool_call": true, @@ -223990,9 +221902,7 @@ "mode": "level", "level": "high", "level_options": [ - "minimal", "low", - "medium", "high" ], "summaries": true, @@ -224004,15 +221914,14 @@ }, "attachment": true, "open_weights": false, - "knowledge": "2026-03", - "release_date": "2026-07-21", - "last_updated": "2026-07-21", - "type": "chat" + "knowledge": "2025-01", + "release_date": "2026-05-28", + "last_updated": "2026-05-28" }, { - "id": "claude-sonnet-4-5", - "name": "Claude Sonnet 4.5 (latest)", - "display_name": "Claude Sonnet 4.5 (latest)", + "id": "claude-fable-5.1", + "name": "Claude Fable 5.1", + "display_name": "Claude Fable 5.1", "modalities": { "input": [ "text", @@ -224025,94 +221934,111 @@ }, "limit": { "context": 1000000, - "output": 64000 + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "budget", - "budget": { - "min": 1024, - "unit": "tokens" - }, + "default_enabled": true, + "mode": "effort", + "effort": "high", + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" + ], "interleaved": true, "summaries": true, - "visibility": "summary", + "visibility": "omitted", "continuation": [ "thinking_blocks" ], "notes": [ - "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", - "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." + "Adaptive thinking is always on for Claude Fable 5 and Claude Mythos 5; thinking.type = \"disabled\" is rejected.", + "Manual budget_tokens requests return 400 on Claude Fable 5 and Claude Mythos 5.", + "thinking.display defaults to omitted; set display to summarized to receive readable thinking summaries." ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-07-31", - "release_date": "2025-09-29", - "last_updated": "2025-09-29", + "knowledge": "2026-06", + "release_date": "2026-09-01", + "last_updated": "2026-09-01", "type": "chat" }, { - "id": "step-5-preview", - "name": "Step 5 Preview", - "display_name": "Step 5 Preview", + "id": "claude-opus-4-7", + "name": "Claude Opus 4.7", + "display_name": "Claude Opus 4.7", "modalities": { "input": [ "text", "image", - "video" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 1000000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": false, + "mode": "effort", + "effort": "high", "effort_options": [ - "none", "low", "medium", "high", + "xhigh", "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "omitted", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Claude Opus 4.7 and newer Opus models require thinking.type = \"adaptive\" to enable thinking explicitly.", + "Manual budget_tokens requests return 400 on Claude Opus 4.7 and newer adaptive-only Opus models.", + "task_budget is separate from thinking control and should not be treated as a thinking budget." ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-09-16", - "last_updated": "2026-09-20", + "open_weights": false, + "knowledge": "2026-01-31", + "release_date": "2026-04-16", + "last_updated": "2026-04-16", "type": "chat" }, { - "id": "gemini-3.5-flash", - "name": "Gemini 3.5 Flash", - "display_name": "Gemini 3.5 Flash", + "id": "qwen3.7-max", + "name": "Qwen3.7 Max", + "display_name": "Qwen3.7 Max", "modalities": { "input": [ - "text", - "image", - "video", - "audio", - "pdf" + "text" ], "output": [ "text" @@ -224120,7 +222046,7 @@ }, "limit": { "context": 1048576, - "output": 65535 + "output": 131072 }, "temperature": true, "tool_call": true, @@ -224131,33 +222057,25 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "level", - "level": "high", - "level_options": [ - "minimal", + "effort_options": [ + "none", "low", "medium", - "high" - ], - "summaries": true, - "visibility": "summary", - "continuation": [ - "thought_signatures" + "high", + "max" ] } }, - "attachment": true, + "attachment": false, "open_weights": false, - "knowledge": "2025-01", - "release_date": "2026-05-19", - "last_updated": "2026-05-19", + "release_date": "2026-05-21", + "last_updated": "2026-05-21", "type": "chat" }, { - "id": "nvidia-nemotron-3-super-120b-a12b", - "name": "Nemotron 3 Super 120B A12B", - "display_name": "Nemotron 3 Super 120B A12B", + "id": "ling-2.6-1t", + "name": "ling-2.6-1t", + "display_name": "ling-2.6-1t", "modalities": { "input": [ "text" @@ -224170,6 +222088,33 @@ "context": 262144, "output": 262144 }, + "tool_call": true, + "reasoning": { + "supported": false + }, + "attachment": false, + "open_weights": false, + "release_date": "2026-04-23", + "last_updated": "2026-04-23", + "type": "chat" + }, + { + "id": "seed-1.8", + "name": "Seed 1.8", + "display_name": "Seed 1.8", + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 256000, + "output": 64000 + }, "temperature": true, "tool_call": true, "reasoning": { @@ -224178,7 +222123,6 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, "effort_options": [ "none", "low", @@ -224188,17 +222132,42 @@ ] } }, + "attachment": true, + "open_weights": false, + "release_date": "2025-12-28", + "last_updated": "2025-12-28", + "type": "chat" + }, + { + "id": "kat-coder-pro", + "name": "kat-coder-pro", + "display_name": "kat-coder-pro", + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 256000, + "output": 256000 + }, + "tool_call": true, + "reasoning": { + "supported": false + }, "attachment": false, - "open_weights": true, - "knowledge": "2025-06", - "release_date": "2026-03-11", - "last_updated": "2026-03-11", + "open_weights": false, + "release_date": "2026-03-27", + "last_updated": "2026-03-27", "type": "chat" }, { - "id": "claude-fable-5", - "name": "Claude Fable 5", - "display_name": "Claude Fable 5", + "id": "claude-haiku-4-5", + "name": "Claude Haiku 4.5 (latest)", + "display_name": "Claude Haiku 4.5 (latest)", "modalities": { "input": [ "text", @@ -224210,52 +222179,47 @@ ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 200000, + "output": 64000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "high", - "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" - ], + "default_enabled": false, + "mode": "budget", + "budget": { + "min": 1024, + "unit": "tokens" + }, "interleaved": true, "summaries": true, - "visibility": "omitted", + "visibility": "summary", "continuation": [ "thinking_blocks" ], "notes": [ - "Adaptive thinking is always on for Claude Fable 5 and Claude Mythos 5; thinking.type = \"disabled\" is rejected.", - "Manual budget_tokens requests return 400 on Claude Fable 5 and Claude Mythos 5.", - "thinking.display defaults to omitted; set display to summarized to receive readable thinking summaries." + "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", + "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-01-31", - "release_date": "2026-06-09", - "last_updated": "2026-06-09", + "knowledge": "2025-02-28", + "release_date": "2025-10-15", + "last_updated": "2025-10-15", "type": "chat" }, { - "id": "gpt-6.1-terra", - "name": "GPT-6.1 Terra", - "display_name": "GPT-6.1 Terra", + "id": "grok-4.7", + "name": "Grok 4.7", + "display_name": "Grok 4.7", "modalities": { "input": [ "text", @@ -224267,10 +222231,10 @@ ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 500000, + "output": 500000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -224289,27 +222253,30 @@ }, "attachment": true, "open_weights": false, - "knowledge": "2026-06-01", - "release_date": "2026-10-26", - "last_updated": "2026-10-26", + "knowledge": "2026-05", + "release_date": "2026-09-21", + "last_updated": "2026-09-21", "type": "chat" }, { - "id": "ling-3.0-tiny", - "name": "ling-3.0-tiny", - "display_name": "ling-3.0-tiny", + "id": "claude-sonnet-5", + "name": "Claude Sonnet 5", + "display_name": "Claude Sonnet 5", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 32768 + "context": 1000000, + "output": 128000 }, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -224317,6 +222284,7 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, "effort_options": [ "none", "low", @@ -224326,29 +222294,30 @@ ] } }, - "attachment": false, + "attachment": true, "open_weights": false, - "release_date": "2026-08-05", - "last_updated": "2026-08-05", + "knowledge": "2026-01-31", + "release_date": "2026-06-30", + "last_updated": "2026-06-30", "type": "chat" }, { - "id": "inkling-256k", - "name": "Inkling 256K", - "display_name": "Inkling 256K", + "id": "qwen3.8-flash", + "name": "Qwen3.8 Flash", + "display_name": "Qwen3.8 Flash", "modalities": { "input": [ "text", "image", - "audio" + "video" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 32768 + "context": 1048576, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -224358,6 +222327,7 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, "effort_options": [ "none", "low", @@ -224368,30 +222338,27 @@ } }, "attachment": true, - "open_weights": true, - "release_date": "2026-07-15", - "last_updated": "2026-07-15", + "open_weights": false, + "release_date": "2026-08-26", + "last_updated": "2026-08-26", "type": "chat" }, { - "id": "grok-4.3", - "name": "Grok 4.3", - "display_name": "Grok 4.3", + "id": "devstral-latest", + "name": "devstral-latest", + "display_name": "devstral-latest", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 30000 + "context": 256000, + "output": 256000 }, - "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -224399,7 +222366,6 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, "effort_options": [ "none", "low", @@ -224409,31 +222375,29 @@ ] } }, - "attachment": true, + "attachment": false, "open_weights": false, - "release_date": "2026-04-17", - "last_updated": "2026-04-17", + "release_date": "2026-05-27", + "last_updated": "2026-05-27", "type": "chat" }, { - "id": "gpt-6.1-sol", - "name": "GPT-6.1 Sol", - "display_name": "GPT-6.1 Sol", + "id": "qwen3.8-2.4T-A95B", + "name": "Qwen3.8 2.4T A95B", + "display_name": "Qwen3.8 2.4T A95B", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 262144, + "output": 262144 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -224441,6 +222405,7 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, "effort_options": [ "none", "low", @@ -224450,17 +222415,16 @@ ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-04-30", - "release_date": "2026-09-29", - "last_updated": "2026-09-29", + "attachment": false, + "open_weights": true, + "release_date": "2026-08-12", + "last_updated": "2026-08-12", "type": "chat" }, { - "id": "muse-glimmer-30b", - "name": "Muse Glimmer 30B", - "display_name": "Muse Glimmer 30B", + "id": "deepseek-v4.1-flash-1017", + "name": "DeepSeek V4.1 Flash 1017", + "display_name": "DeepSeek V4.1 Flash 1017", "modalities": { "input": [ "text", @@ -224471,8 +222435,8 @@ ] }, "limit": { - "context": 131072, - "output": 20480 + "context": 1048576, + "output": 393216 }, "temperature": true, "tool_call": true, @@ -224493,15 +222457,15 @@ }, "attachment": true, "open_weights": true, - "knowledge": "2026-01-04", - "release_date": "2026-08-10", - "last_updated": "2026-08-10", + "knowledge": "2025-05", + "release_date": "2026-10-17", + "last_updated": "2026-10-17", "type": "chat" }, { - "id": "ling-2.6-1t", - "name": "ling-2.6-1t", - "display_name": "ling-2.6-1t", + "id": "mimo-v2.5-pro", + "name": "MiMo-V2.5-Pro", + "display_name": "MiMo-V2.5-Pro", "modalities": { "input": [ "text" @@ -224511,82 +222475,37 @@ ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 1048576, + "output": 131072 }, + "temperature": true, "tool_call": true, "reasoning": { "supported": false }, "attachment": false, - "open_weights": false, - "release_date": "2026-04-23", - "last_updated": "2026-04-23", - "type": "chat" - }, - { - "id": "qwen3.5-27b", - "name": "Qwen3.5 27B", - "display_name": "Qwen3.5 27B", - "modalities": { - "input": [ - "text", - "image", - "video" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 262144, - "output": 65536 - }, - "temperature": true, - "tool_call": true, - "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "effort_options": [ - "none", - "low", - "medium", - "high", - "max" - ] - } - }, - "attachment": true, "open_weights": true, - "release_date": "2026-02-23", - "last_updated": "2026-02-23", + "knowledge": "2024-12", + "release_date": "2026-04-22", + "last_updated": "2026-04-22", "type": "chat" }, - { - "id": "nemotron-3.5-lightning-30b-a3b", - "name": "Nemotron 3.5 Lightning 30B A3B", - "display_name": "Nemotron 3.5 Lightning 30B A3B", + { + "id": "grok-4.5", + "name": "Grok 4.5", + "display_name": "Grok 4.5", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 500000, + "output": 500000 }, "temperature": true, "tool_call": true, @@ -224596,6 +222515,7 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, "effort_options": [ "none", "low", @@ -224605,22 +222525,22 @@ ] } }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-09", - "release_date": "2026-08-11", - "last_updated": "2026-08-11", + "attachment": true, + "open_weights": false, + "release_date": "2026-07-08", + "last_updated": "2026-07-08", "type": "chat" }, { - "id": "qwen3.7-plus", - "name": "Qwen3.7 Plus", - "display_name": "Qwen3.7 Plus", + "id": "muse-spark-open", + "name": "Muse Spark Open", + "display_name": "Muse Spark Open", "modalities": { "input": [ "text", "image", - "video" + "video", + "audio" ], "output": [ "text" @@ -224638,7 +222558,6 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, "effort_options": [ "none", "low", @@ -224649,29 +222568,28 @@ } }, "attachment": true, - "open_weights": false, - "knowledge": "2025-04", - "release_date": "2026-06-02", - "last_updated": "2026-06-02", + "open_weights": true, + "knowledge": "2026-01-04", + "release_date": "2026-10-15", + "last_updated": "2026-10-15", "type": "chat" }, { - "id": "mistral-medium-3-5", - "name": "Mistral Medium 3.5", - "display_name": "Mistral Medium 3.5", + "id": "mistral-medium-latest", + "name": "Mistral Medium (latest)", + "display_name": "Mistral Medium (latest)", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 209715 + "context": 131072, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -224691,28 +222609,30 @@ } }, "attachment": true, - "open_weights": false, - "release_date": "2026-04-30", - "last_updated": "2026-04-30", + "open_weights": true, + "release_date": "2026-04-29", + "last_updated": "2026-04-29", "type": "chat" }, { - "id": "glm-5.2", - "name": "GLM-5.2", - "display_name": "GLM-5.2", + "id": "claude-fable-5", + "name": "Claude Fable 5", + "display_name": "Claude Fable 5", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 1000000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -224721,115 +222641,158 @@ "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "high", "effort_options": [ - "none", "low", "medium", "high", + "xhigh", "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "omitted", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Adaptive thinking is always on for Claude Fable 5 and Claude Mythos 5; thinking.type = \"disabled\" is rejected.", + "Manual budget_tokens requests return 400 on Claude Fable 5 and Claude Mythos 5.", + "thinking.display defaults to omitted; set display to summarized to receive readable thinking summaries." ] } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-06-13", - "last_updated": "2026-06-13", + "attachment": true, + "open_weights": false, + "knowledge": "2026-01-31", + "release_date": "2026-06-09", + "last_updated": "2026-06-09", "type": "chat" }, { - "id": "nemotron-3-nano-omni", - "name": "nemotron-3-nano-omni", - "display_name": "nemotron-3-nano-omni", + "id": "claude-opus-4-1", + "name": "Claude Opus 4.1 (latest)", + "display_name": "Claude Opus 4.1 (latest)", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 300000, - "output": 300000 + "context": 200000, + "output": 32000 }, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "none", - "low", - "medium", - "high", - "max" + "supported": true, + "default_enabled": false, + "mode": "budget", + "budget": { + "min": 1024, + "unit": "tokens" + }, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", + "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." ] } }, - "attachment": false, + "attachment": true, "open_weights": false, - "release_date": "2026-05-20", - "last_updated": "2026-05-20", + "knowledge": "2025-03-31", + "release_date": "2025-08-05", + "last_updated": "2025-08-05", "type": "chat" }, { - "id": "fugu-ultra", - "name": "Fugu Ultra", - "display_name": "Fugu Ultra", + "id": "claude-sonnet-4-5", + "name": "Claude Sonnet 4.5 (latest)", + "display_name": "Claude Sonnet 4.5 (latest)", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 1000000, + "output": 64000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "none", - "low", - "medium", - "high", - "max" + "supported": true, + "default_enabled": false, + "mode": "budget", + "budget": { + "min": 1024, + "unit": "tokens" + }, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", + "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." ] } }, "attachment": true, "open_weights": false, - "release_date": "2026-06-15", - "last_updated": "2026-06-15", + "knowledge": "2025-07-31", + "release_date": "2025-09-29", + "last_updated": "2025-09-29", "type": "chat" }, { - "id": "grok-4.6", - "name": "Grok 4.6", - "display_name": "Grok 4.6", + "id": "glm-5.3-flash", + "name": "GLM-5.3-Flash", + "display_name": "GLM-5.3-Flash", "modalities": { "input": [ "text", - "image" + "image", + "video", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 500000, - "output": 500000 + "context": 1000000, + "output": 262144 }, "temperature": true, "tool_call": true, @@ -224850,35 +222813,29 @@ } }, "attachment": true, - "open_weights": false, - "knowledge": "2026-02-01", - "release_date": "2026-08-12", - "last_updated": "2026-08-12", + "open_weights": true, + "release_date": "2026-08-26", + "last_updated": "2026-08-26", "type": "chat" - } - ] - }, - "venice": { - "id": "venice", - "name": "Venice AI", - "display_name": "Venice AI", - "doc": "https://docs.venice.ai", - "models": [ + }, { - "id": "openai-gpt-52", - "name": "GPT-5.2", - "display_name": "GPT-5.2", + "id": "mimo-v2.6-flash", + "name": "MiMo-V2.6-Flash", + "display_name": "MiMo-V2.6-Flash", "modalities": { "input": [ - "text" + "text", + "image", + "audio", + "video" ], "output": [ "text" ] }, "limit": { - "context": 256000, - "output": 65536 + "context": 1048576, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -224893,21 +222850,20 @@ "low", "medium", "high", - "xhigh" + "max" ] } }, - "attachment": false, - "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2025-12-11", - "last_updated": "2025-12-11", + "attachment": true, + "open_weights": true, + "release_date": "2026-09-22", + "last_updated": "2026-09-22", "type": "chat" }, { - "id": "gemini-3-flash-preview", - "name": "Gemini 3 Flash Preview", - "display_name": "Gemini 3 Flash Preview", + "id": "gemini-3.6-flash", + "name": "Gemini 3.6 Flash", + "display_name": "Gemini 3.6 Flash", "modalities": { "input": [ "text", @@ -224921,8 +222877,8 @@ ] }, "limit": { - "context": 256000, - "output": 65536 + "context": 1048576, + "output": 65535 }, "temperature": true, "tool_call": true, @@ -224942,126 +222898,120 @@ "medium", "high" ], - "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ - "thinking_blocks" + "thought_signatures" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-01", - "release_date": "2025-12-17", - "last_updated": "2025-12-17", + "knowledge": "2026-03", + "release_date": "2026-07-21", + "last_updated": "2026-07-21", "type": "chat" }, { - "id": "minimax-m3-1-flash", - "name": "MiniMax M3.1 Flash", - "display_name": "MiniMax M3.1 Flash", + "id": "nvidia-nemotron-3-ultra", + "name": "Nemotron 3 Ultra 550B A55B", + "display_name": "Nemotron 3 Ultra 550B A55B", "modalities": { "input": [ - "text", - "image", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, + "context": 262144, "output": 131072 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { "supported": true, "default": true }, - "attachment": true, - "open_weights": false, - "release_date": "2026-10-19", - "last_updated": "2026-10-19", - "type": "chat" - }, - { - "id": "venice-uncensored-1-2", - "name": "Venice Uncensored 1.2", - "display_name": "Venice Uncensored 1.2", - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 8192 - }, - "tool_call": true, - "reasoning": { - "supported": false + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "none", + "low", + "medium", + "high", + "max" + ] + } }, - "attachment": true, + "attachment": false, "open_weights": true, - "release_date": "2026-04-01", - "last_updated": "2026-06-11", + "knowledge": "2025-09", + "release_date": "2026-06-04", + "last_updated": "2026-06-04", "type": "chat" }, { - "id": "gemini-3-6-flash", - "name": "Gemini 3.6 Flash", - "display_name": "Gemini 3.6 Flash", + "id": "gpt-5.5-pro", + "name": "GPT-5.5 Pro", + "display_name": "GPT-5.5 Pro", "modalities": { "input": [ "text", "image", - "audio", - "video" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 65536 + "context": 1050000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, "default": true }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "effort_options": [ + "none", + "low", + "medium", + "high", + "max" + ] + } + }, "attachment": true, "open_weights": false, - "knowledge": "2026-03", - "release_date": "2026-07-21", - "last_updated": "2026-07-21", + "knowledge": "2025-12-01", + "release_date": "2026-04-23", + "last_updated": "2026-04-23", "type": "chat" }, { - "id": "openai-gpt-61-sol", - "name": "GPT-6.1 Sol", - "display_name": "GPT-6.1 Sol", + "id": "kimi-k3.1-pro-highspeed", + "name": "Kimi K3.1 Pro Highspeed", + "display_name": "Kimi K3.1 Pro Highspeed", "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 1048576, + "output": 1048576 }, "temperature": false, "tool_call": true, @@ -225072,36 +223022,39 @@ "extra_capabilities": { "reasoning": { "effort_options": [ + "none", "low", "medium", "high", - "xhigh", "max" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-04-30", - "release_date": "2026-09-29", - "last_updated": "2026-09-29", + "release_date": "2026-10-23", + "last_updated": "2026-10-23", "type": "chat" }, { - "id": "aion-labs-aion-3-0", - "name": "Aion 3.0", - "display_name": "Aion 3.0", + "id": "gemini-3.8-flash", + "name": "Gemini 3.8 Flash", + "display_name": "Gemini 3.8 Flash", "modalities": { "input": [ - "text" + "text", + "image", + "video", + "audio", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 32768 + "context": 1048576, + "output": 65535 }, "temperature": true, "tool_call": true, @@ -225109,20 +223062,40 @@ "supported": true, "default": true }, - "attachment": false, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "level", + "level": "high", + "level_options": [ + "minimal", + "low", + "medium", + "high" + ], + "summaries": true, + "visibility": "summary", + "continuation": [ + "thought_signatures" + ] + } + }, + "attachment": true, "open_weights": false, - "release_date": "2026-05-05", - "last_updated": "2026-05-05", + "release_date": "2026-09-02", + "last_updated": "2026-09-02", "type": "chat" }, { - "id": "kimi-k3-fast-api", - "name": "Kimi K3 Fast", - "display_name": "Kimi K3 Fast", + "id": "claude-opus-5-5", + "name": "Claude Opus 5.5", + "display_name": "Claude Opus 5.5", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" @@ -225130,7 +223103,7 @@ }, "limit": { "context": 1000000, - "output": 131072 + "output": 128000 }, "temperature": false, "tool_call": true, @@ -225143,34 +223116,38 @@ "effort_options": [ "none", "low", + "medium", "high", "max" ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-07-16", - "last_updated": "2026-07-16", + "open_weights": false, + "knowledge": "2026-06", + "release_date": "2026-09-22", + "last_updated": "2026-09-22", "type": "chat" }, { - "id": "hy4-preview", - "name": "Hy4 Preview", - "display_name": "Hy4 Preview", + "id": "gpt-5.6-luna", + "name": "GPT-5.6 Luna", + "display_name": "GPT-5.6 Luna", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 64000 + "context": 1050000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -225179,23 +223156,37 @@ "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", "effort_options": [ "none", "low", + "medium", + "high", + "xhigh", + "max" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", "high" - ] + ], + "visibility": "hidden" } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-08-28", - "last_updated": "2026-08-28", + "attachment": true, + "open_weights": false, + "knowledge": "2026-02-16", + "release_date": "2026-07-09", + "last_updated": "2026-07-09", "type": "chat" }, { - "id": "qwen3-235b-a22b-thinking-2507", - "name": "Qwen 3 235B A22B Thinking 2507", - "display_name": "Qwen 3 235B A22B Thinking 2507", + "id": "laguna-xs.2", + "name": "Laguna XS.2", + "display_name": "Laguna XS.2", "modalities": { "input": [ "text" @@ -225205,93 +223196,105 @@ ] }, "limit": { - "context": 128000, - "output": 16384 + "context": 32768, + "output": 32768 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { "supported": true, "default": true }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], "effort_options": [ + "none", "low", "medium", - "high" + "high", + "max" ] } }, "attachment": false, "open_weights": true, - "release_date": "2025-07-25", - "last_updated": "2025-07-25", + "release_date": "2026-04-28", + "last_updated": "2026-06-13", "type": "chat" }, { - "id": "mercury-2", - "name": "Mercury 2", - "display_name": "Mercury 2", + "id": "claude-opus-4-5", + "name": "Claude Opus 4.5 (latest)", + "display_name": "Claude Opus 4.5 (latest)", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 50000 + "context": 200000, + "output": 64000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": false, + "mode": "mixed", + "budget": { + "min": 1024, + "unit": "tokens" + }, + "effort": "high", "effort_options": [ - "none", "low", "medium", "high" + ], + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Claude Opus 4.5 uses manual thinking.type = \"enabled\" with budget_tokens; effort can be used alongside the thinking budget.", + "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header." ] } }, - "attachment": false, + "attachment": true, "open_weights": false, - "release_date": "2026-02-24", - "last_updated": "2026-02-24", + "knowledge": "2025-05", + "release_date": "2025-11-24", + "last_updated": "2025-11-24", "type": "chat" }, { - "id": "xiaomi-mimo-v2-6-flash", - "name": "MiMo-V2.6-Flash", - "display_name": "MiMo-V2.6-Flash", + "id": "hy3", + "name": "Hy3", + "display_name": "Hy3", "modalities": { "input": [ - "text", - "image", - "audio", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 262144, + "output": 262144 }, "temperature": true, "tool_call": true, @@ -225301,51 +223304,26 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, "effort_options": [ "none", "low", "medium", - "high" + "high", + "max" ] } }, - "attachment": true, - "open_weights": true, - "release_date": "2026-09-22", - "last_updated": "2026-09-22", - "type": "chat" - }, - { - "id": "abliteration-abliterated-model-large-v2", - "name": "Abliterated Large V2", - "display_name": "Abliterated Large V2", - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 1000000, - "output": 32768 - }, - "tool_call": true, - "reasoning": { - "supported": true, - "default": true - }, "attachment": false, - "open_weights": false, - "release_date": "2026-10-01", - "last_updated": "2026-10-01", + "open_weights": true, + "release_date": "2026-07-06", + "last_updated": "2026-07-06", "type": "chat" }, { - "id": "openai-gpt-54", - "name": "GPT-5.4", - "display_name": "GPT-5.4", + "id": "nemotron-3.5-content-safety", + "name": "Nemotron 3.5 Content Safety", + "display_name": "Nemotron 3.5 Content Safety", "modalities": { "input": [ "text", @@ -225356,48 +223334,49 @@ ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 131072, + "output": 8192 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { "supported": true, "default": true }, "extra_capabilities": { "reasoning": { + "supported": true, "effort_options": [ "none", "low", "medium", "high", - "xhigh" + "max" ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-03-05", - "last_updated": "2026-03-05", + "open_weights": true, + "release_date": "2026-06-02", + "last_updated": "2026-06-02", "type": "chat" }, { - "id": "zai-org-glm-5-1", - "name": "GLM 5.1", - "display_name": "GLM 5.1", + "id": "deepseek-v4.1-flash", + "name": "DeepSeek V4.1 Flash", + "display_name": "DeepSeek V4.1 Flash", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 80000 + "context": 1048576, + "output": 393216 }, "temperature": true, "tool_call": true, @@ -225408,30 +223387,26 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], "effort_options": [ "none", "low", "medium", - "high" + "high", + "max" ] } }, - "attachment": false, + "attachment": true, "open_weights": true, - "release_date": "2026-04-07", - "last_updated": "2026-04-07", + "knowledge": "2025-05", + "release_date": "2026-09-10", + "last_updated": "2026-09-10", "type": "chat" }, { - "id": "grok-4-6", - "name": "Grok 4.6", - "display_name": "Grok 4.6", + "id": "fugu-ultra", + "name": "Fugu Ultra", + "display_name": "Fugu Ultra", "modalities": { "input": [ "text", @@ -225442,10 +223417,10 @@ ] }, "limit": { - "context": 500000, - "output": 200000 + "context": 1048576, + "output": 131072 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -225454,24 +223429,24 @@ "extra_capabilities": { "reasoning": { "effort_options": [ + "none", "low", "medium", "high", - "xhigh" + "max" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-02-01", - "release_date": "2026-08-12", - "last_updated": "2026-08-12", + "release_date": "2026-06-15", + "last_updated": "2026-06-15", "type": "chat" }, { - "id": "z-ai-glm-5-3", - "name": "GLM 5.3", - "display_name": "GLM 5.3", + "id": "nemotron-3-super-120b-a12b", + "name": "Nemotron 3 Super 120B A12B", + "display_name": "Nemotron 3 Super 120B A12B", "modalities": { "input": [ "text" @@ -225481,8 +223456,8 @@ ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 1048576, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -225490,73 +223465,69 @@ "supported": true, "default": true }, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "none", + "low", + "medium", + "high", + "max" + ] + } + }, "attachment": false, "open_weights": true, - "release_date": "2026-08-14", - "last_updated": "2026-08-14", + "knowledge": "2025-06", + "release_date": "2026-03-11", + "last_updated": "2026-03-11", "type": "chat" }, { - "id": "claude-opus-4-5", - "name": "Claude Opus 4.5", - "display_name": "Claude Opus 4.5", + "id": "qwen3.8-flash-next", + "name": "Qwen3.8 Flash Next", + "display_name": "Qwen3.8 Flash Next", "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 198000, - "output": 32768 + "context": 262144, + "output": 262144 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "mixed", - "budget": { - "min": 1024, - "unit": "tokens" - }, - "effort": "high", "effort_options": [ + "none", "low", "medium", - "high" - ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Claude Opus 4.5 uses manual thinking.type = \"enabled\" with budget_tokens; effort can be used alongside the thinking budget.", - "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header." + "high", + "max" ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2025-05", - "release_date": "2025-11-24", - "last_updated": "2025-11-24", + "open_weights": true, + "release_date": "2026-08-27", + "last_updated": "2026-08-27", "type": "chat" }, { - "id": "openai-gpt-4o-2024-11-20", - "name": "GPT-4o", - "display_name": "GPT-4o", + "id": "mistral-large-4", + "name": "Mistral Large 4", + "display_name": "Mistral Large 4", "modalities": { "input": [ "text", @@ -225567,53 +223538,77 @@ ] }, "limit": { - "context": 128000, - "output": 16384 + "context": 524288, + "output": 262144 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "none", + "low", + "medium", + "high", + "max" + ] + } }, "attachment": true, "open_weights": false, - "knowledge": "2023-09", - "release_date": "2024-11-20", - "last_updated": "2024-11-20", + "release_date": "2026-10-06", + "last_updated": "2026-10-06", "type": "chat" }, { - "id": "qwen-3-8-2-4t-a95b", - "name": "Qwen 3.8 2.4T", - "display_name": "Qwen 3.8 2.4T", + "id": "kimi-k3.1-pro", + "name": "Kimi K3.1 Pro", + "display_name": "Kimi K3.1 Pro", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 65536 + "context": 1048576, + "output": 1048576 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, "default": true }, - "attachment": false, - "open_weights": true, - "release_date": "2026-08-12", - "last_updated": "2026-08-12", + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "none", + "low", + "medium", + "high", + "max" + ] + } + }, + "attachment": true, + "open_weights": false, + "release_date": "2026-10-23", + "last_updated": "2026-10-23", "type": "chat" }, { - "id": "zai-org-glm-4.7", - "name": "GLM 4.7", - "display_name": "GLM 4.7", + "id": "deepseek-v4-pro", + "name": "DeepSeek V4 Pro", + "display_name": "DeepSeek V4 Pro", "modalities": { "input": [ "text" @@ -225623,8 +223618,8 @@ ] }, "limit": { - "context": 198000, - "output": 16384 + "context": 1000000, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -225635,36 +223630,41 @@ "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "high", + "effort_options": [ + "low", + "high", + "max" + ], "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ "thinking_blocks" ], - "effort_options": [ - "low", - "medium", - "high" + "notes": [ + "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." ] } }, "attachment": false, "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-12-22", - "last_updated": "2025-12-22", + "knowledge": "2025-05", + "release_date": "2026-04-24", + "last_updated": "2026-04-24", "type": "chat" }, { - "id": "gemini-3-5-flash-lite", - "name": "Gemini 3.5 Flash-Lite", - "display_name": "Gemini 3.5 Flash-Lite", + "id": "claude-sonnet-5-5", + "name": "Claude Sonnet 5.5", + "display_name": "Claude Sonnet 5.5", "modalities": { "input": [ "text", "image", - "audio", - "video" + "pdf" ], "output": [ "text" @@ -225672,25 +223672,36 @@ }, "limit": { "context": 1000000, - "output": 65536 + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, "default": true }, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "none", + "low", + "medium", + "high", + "max" + ] + } + }, "attachment": true, "open_weights": false, - "knowledge": "2026-03", - "release_date": "2026-07-21", - "last_updated": "2026-07-21", + "knowledge": "2026-06", + "release_date": "2026-09-28", + "last_updated": "2026-09-28", "type": "chat" }, { - "id": "deepseek-v4-flash-0731-fast", - "name": "DeepSeek V4 Flash 0731 Fast", - "display_name": "DeepSeek V4 Flash 0731 Fast", + "id": "glm-5.3", + "name": "GLM-5.3", + "display_name": "GLM-5.3", "modalities": { "input": [ "text" @@ -225700,8 +223711,8 @@ ] }, "limit": { - "context": 1000000, - "output": 32768 + "context": 1048576, + "output": 1048576 }, "temperature": true, "tool_call": true, @@ -225715,6 +223726,7 @@ "effort_options": [ "none", "low", + "medium", "high", "max" ] @@ -225722,31 +223734,29 @@ }, "attachment": false, "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-07-31", - "last_updated": "2026-07-31", + "release_date": "2026-08-14", + "last_updated": "2026-08-14", "type": "chat" }, { - "id": "gemini-3-5-flash", - "name": "Gemini 3.5 Flash", - "display_name": "Gemini 3.5 Flash", + "id": "gpt-5.6-sol", + "name": "GPT-5.6 Sol", + "display_name": "GPT-5.6 Sol", "modalities": { "input": [ "text", "image", - "audio", - "video" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 65536 + "context": 1050000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -225754,27 +223764,43 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", "effort_options": [ + "none", + "low", + "medium", + "high", + "xhigh", + "max" + ], + "verbosity": "medium", + "verbosity_options": [ "low", "medium", "high" - ] + ], + "visibility": "hidden" } }, "attachment": true, "open_weights": false, - "knowledge": "2025-01", - "release_date": "2026-05-19", - "last_updated": "2026-05-19", + "knowledge": "2026-02-16", + "release_date": "2026-07-09", + "last_updated": "2026-07-09", "type": "chat" }, { - "id": "deepseek-v4-flash", - "name": "DeepSeek V4 Flash 0423", - "display_name": "DeepSeek V4 Flash 0423", + "id": "claude-sonnet-4-6", + "name": "Claude Sonnet 4.6", + "display_name": "Claude Sonnet 4.6", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" @@ -225782,22 +223808,27 @@ }, "limit": { "context": 1000000, - "output": 32768 + "output": 64000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", + "default_enabled": false, + "mode": "mixed", + "budget": { + "min": 1024, + "unit": "tokens" + }, "effort": "high", "effort_options": [ "low", + "medium", "high", "max" ], @@ -225808,19 +223839,20 @@ "thinking_blocks" ], "notes": [ - "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." + "Anthropic recommends adaptive thinking with effort for Claude 4.6; budget_tokens remains a deprecated compatibility path.", + "Anthropic API defaults effort to high; lower effort levels should be chosen per workload." ] } }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-04-24", - "last_updated": "2026-04-24", + "attachment": true, + "open_weights": false, + "knowledge": "2025-08-31", + "release_date": "2026-02-17", + "last_updated": "2026-03-13", "type": "chat" }, { - "id": "xiaomi-mimo-v2-5", + "id": "mimo-v2.5", "name": "MiMo-V2.5", "display_name": "MiMo-V2.5", "modalities": { @@ -225835,24 +223867,13 @@ ] }, "limit": { - "context": 1000000, - "output": 65536 + "context": 1048576, + "output": 131072 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "none", - "low", - "medium", - "high" - ] - } + "supported": false }, "attachment": true, "open_weights": true, @@ -225862,23 +223883,24 @@ "type": "chat" }, { - "id": "claude-sonnet-5-5", - "name": "Claude Sonnet 5.5", - "display_name": "Claude Sonnet 5.5", + "id": "seed-2.0-mini", + "name": "Seed 2.0 Mini", + "display_name": "Seed 2.0 Mini", "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 256000, + "output": 32000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -225887,36 +223909,37 @@ "extra_capabilities": { "reasoning": { "effort_options": [ + "none", "low", "medium", "high", - "xhigh", "max" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-06", - "release_date": "2026-09-28", - "last_updated": "2026-09-28", + "release_date": "2026-02-14", + "last_updated": "2026-02-14", "type": "chat" }, { - "id": "mercury-2-5", - "name": "Mercury 2.5", - "display_name": "Mercury 2.5", + "id": "grok-4.3", + "name": "Grok 4.3", + "display_name": "Grok 4.3", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 260000, - "output": 65536 + "context": 1000000, + "output": 30000 }, "temperature": true, "tool_call": true, @@ -225926,38 +223949,42 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, "effort_options": [ "none", "low", "medium", - "high" + "high", + "max" ] } }, - "attachment": false, + "attachment": true, "open_weights": false, - "release_date": "2026-09-08", - "last_updated": "2026-09-08", + "release_date": "2026-04-17", + "last_updated": "2026-04-17", "type": "chat" }, { - "id": "openai-gpt-56-sol-pro", - "name": "GPT-5.6 Sol Pro", - "display_name": "GPT-5.6 Sol Pro", + "id": "nemotron-3-nano-omni-30b-a3b-reasoning", + "name": "Nemotron 3 Nano Omni 30B A3B Reasoning", + "display_name": "Nemotron 3 Nano Omni 30B A3B Reasoning", "modalities": { "input": [ "text", - "image" + "image", + "video", + "audio" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 131072, + "output": 20480 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -225965,41 +223992,39 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, "effort_options": [ "none", "low", "medium", "high", - "xhigh", "max" ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2026-02-16", - "release_date": "2026-07-09", - "last_updated": "2026-07-09", + "open_weights": true, + "release_date": "2026-04-28", + "last_updated": "2026-04-28", "type": "chat" }, { - "id": "claude-haiku-5-5", - "name": "Claude Haiku 5.5", - "display_name": "Claude Haiku 5.5", + "id": "minimax-m2.7-highspeed", + "name": "MiniMax-M2.7-highspeed", + "display_name": "MiniMax-M2.7-highspeed", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, + "context": 200000, "output": 128000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -226007,38 +224032,45 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ "none", "low", "medium", "high", - "xhigh", "max" ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-06", - "release_date": "2026-10-07", - "last_updated": "2026-10-07", + "attachment": false, + "open_weights": true, + "release_date": "2026-03-18", + "last_updated": "2026-03-18", "type": "chat" }, { - "id": "mercury-3", - "name": "Mercury 3", - "display_name": "Mercury 3", + "id": "qwen3.5-2b", + "name": "Qwen3.5 2B", + "display_name": "Qwen3.5 2B", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 262144, + "output": 81920 }, "temperature": true, "tool_call": true, @@ -226048,38 +224080,49 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ "none", "low", "medium", - "high" + "high", + "max" ] } }, - "attachment": false, - "open_weights": false, - "release_date": "2026-10-16", - "last_updated": "2026-10-20", + "attachment": true, + "open_weights": true, + "release_date": "2026-03-02", + "last_updated": "2026-03-02", "type": "chat" }, { - "id": "kimi-k3", - "name": "Kimi K3", - "display_name": "Kimi K3", + "id": "muse-wonder-1.0", + "name": "Muse Wonder", + "display_name": "Muse Wonder", "modalities": { "input": [ "text", - "image" + "image", + "video", + "pdf", + "audio" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 1048576, + "output": 943718 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -226087,28 +224130,30 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, "effort_options": [ "none", "low", + "medium", "high", "max" ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-07-16", - "last_updated": "2026-07-16", + "open_weights": false, + "release_date": "2026-10-12", + "last_updated": "2026-10-12", "type": "chat" }, { - "id": "zai-org-glm-5-2", - "name": "GLM 5.2", - "display_name": "GLM 5.2", + "id": "claude-opus-4-8", + "name": "Claude Opus 4.8", + "display_name": "Claude Opus 4.8", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" @@ -226116,33 +224161,51 @@ }, "limit": { "context": 1000000, - "output": 131072 + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": false, + "mode": "effort", + "effort": "high", "effort_options": [ - "none", + "low", + "medium", "high", + "xhigh", "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "omitted", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Claude Opus 4.7 and newer Opus models require thinking.type = \"adaptive\" to enable thinking explicitly.", + "Manual budget_tokens requests return 400 on Claude Opus 4.7 and newer adaptive-only Opus models.", + "task_budget is separate from thinking control and should not be treated as a thinking budget." ] } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-06-13", - "last_updated": "2026-06-13", + "attachment": true, + "open_weights": false, + "knowledge": "2026-01", + "release_date": "2026-05-28", + "last_updated": "2026-05-28", "type": "chat" }, { - "id": "nvidia-nemotron-3-ultra-550b-a55b", - "name": "NVIDIA Nemotron 3 Ultra", - "display_name": "NVIDIA Nemotron 3 Ultra", + "id": "leanstral-1-5", + "name": "leanstral-1-5", + "display_name": "leanstral-1-5", "modalities": { "input": [ "text" @@ -226152,10 +224215,9 @@ ] }, "limit": { - "context": 256000, + "context": 262144, "output": 32768 }, - "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -226167,63 +224229,76 @@ "none", "low", "medium", - "high" + "high", + "max" ] } }, "attachment": false, - "open_weights": true, - "knowledge": "2025-09", - "release_date": "2026-06-04", - "last_updated": "2026-06-04", + "open_weights": false, + "release_date": "2026-05-27", + "last_updated": "2026-05-27", "type": "chat" }, { - "id": "qwen3-6-35b-a3b", - "name": "Qwen 3.6 35B A3B", - "display_name": "Qwen 3.6 35B A3B", + "id": "nemotron-3-nano-omni", + "name": "nemotron-3-nano-omni", + "display_name": "nemotron-3-nano-omni", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 256000, - "output": 65536 + "context": 300000, + "output": 300000 }, - "temperature": true, "tool_call": true, "reasoning": { "supported": true, "default": true }, - "attachment": true, - "open_weights": true, - "release_date": "2026-04-17", - "last_updated": "2026-04-17", + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "none", + "low", + "medium", + "high", + "max" + ] + } + }, + "attachment": false, + "open_weights": false, + "release_date": "2026-05-20", + "last_updated": "2026-05-20", "type": "chat" }, { - "id": "deepseek-v4-1-flash-uncensored", - "name": "DeepSeek V4.1 Flash Uncensored", - "display_name": "DeepSeek V4.1 Flash Uncensored", + "id": "gemini-3.1-pro-preview", + "name": "Gemini 3.1 Pro Preview", + "display_name": "Gemini 3.1 Pro Preview", "modalities": { "input": [ "text", - "image" + "image", + "video", + "audio", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 1048576, + "output": 65535 }, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -226231,39 +224306,46 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "none", + "supported": true, + "default_enabled": true, + "mode": "level", + "level": "high", + "level_options": [ "low", - "high", - "max" + "high" + ], + "summaries": true, + "visibility": "summary", + "continuation": [ + "thought_signatures" ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-10-23", - "last_updated": "2026-10-23", + "open_weights": false, + "knowledge": "2025-01", + "release_date": "2026-02-19", + "last_updated": "2026-02-19", "type": "chat" }, { - "id": "openai-gpt-61-sol-ultrafast", - "name": "GPT-6.1 Sol Ultrafast", - "display_name": "GPT-6.1 Sol Ultrafast", + "id": "laguna-m.1", + "name": "Laguna M.1", + "display_name": "Laguna M.1", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 32768, + "output": 32768 }, - "temperature": false, - "tool_call": true, + "temperature": true, + "tool_call": false, "reasoning": { "supported": true, "default": true @@ -226271,25 +224353,24 @@ "extra_capabilities": { "reasoning": { "effort_options": [ + "none", "low", "medium", "high", - "xhigh", "max" ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-04-30", - "release_date": "2026-09-29", - "last_updated": "2026-09-29", + "attachment": false, + "open_weights": true, + "release_date": "2026-04-28", + "last_updated": "2026-06-13", "type": "chat" }, { - "id": "deepseek-v4-pro", - "name": "DeepSeek V4 Pro", - "display_name": "DeepSeek V4 Pro", + "id": "deepseek-v4-flash", + "name": "DeepSeek V4 Flash", + "display_name": "DeepSeek V4 Flash", "modalities": { "input": [ "text" @@ -226299,8 +224380,8 @@ ] }, "limit": { - "context": 1000000, - "output": 32768 + "context": 1048576, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -226338,20 +224419,21 @@ "type": "chat" }, { - "id": "claude-opus-5", - "name": "Claude Opus 5", - "display_name": "Claude Opus 5", + "id": "gpt-5.5", + "name": "GPT-5.5", + "display_name": "GPT-5.5", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1000000, + "context": 1050000, "output": 128000 }, "temperature": false, @@ -226363,107 +224445,148 @@ "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", "effort_options": [ "low", "medium", "high", - "xhigh", - "max" - ] + "xhigh" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, "attachment": true, "open_weights": false, - "knowledge": "2026-05", - "release_date": "2026-07-24", - "last_updated": "2026-07-24", + "knowledge": "2025-12-01", + "release_date": "2026-04-23", + "last_updated": "2026-04-23", "type": "chat" }, { - "id": "openai-gpt-6-luna", - "name": "GPT-6 Luna", - "display_name": "GPT-6 Luna", + "id": "claude-opus-4-6", + "name": "Claude Opus 4.6", + "display_name": "Claude Opus 4.6", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1050000, + "context": 1000000, "output": 128000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": false, + "mode": "mixed", + "budget": { + "min": 1024, + "unit": "tokens" + }, + "effort": "high", "effort_options": [ - "none", "low", "medium", "high", - "xhigh", "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Anthropic recommends adaptive thinking with effort for Claude 4.6; budget_tokens remains a deprecated compatibility path.", + "Anthropic API defaults effort to high; lower effort levels should be chosen per workload." ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-05-18", - "release_date": "2026-09-22", - "last_updated": "2026-09-22", + "knowledge": "2025-05-31", + "release_date": "2026-02-05", + "last_updated": "2026-03-13", "type": "chat" }, { - "id": "nvidia-nemotron-3-nano-30b-a3b", - "name": "NVIDIA Nemotron 3 Nano 30B", - "display_name": "NVIDIA Nemotron 3 Nano 30B", + "id": "inkling", + "name": "Inkling", + "display_name": "Inkling", "modalities": { "input": [ - "text" + "text", + "image", + "audio" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 16384 + "context": 65536, + "output": 32768 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "none", + "low", + "medium", + "high", + "max" + ] + } + }, + "attachment": true, "open_weights": true, - "knowledge": "2025-06-25", - "release_date": "2025-12-15", - "last_updated": "2025-12-15", + "release_date": "2026-07-15", + "last_updated": "2026-07-15", "type": "chat" }, { - "id": "openai-gpt-oss-120b", - "name": "OpenAI GPT OSS 120B", - "display_name": "OpenAI GPT OSS 120B", + "id": "qwen3.6-plus", + "name": "Qwen3.6 Plus", + "display_name": "Qwen3.6 Plus", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 16384 + "context": 1000000, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -226473,25 +224596,33 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ "none", "low", "medium", - "high" + "high", + "max" ] } }, - "attachment": false, - "open_weights": true, - "knowledge": "2024-06", - "release_date": "2025-08-05", - "last_updated": "2025-08-05", + "attachment": true, + "open_weights": false, + "knowledge": "2025-04", + "release_date": "2026-04-02", + "last_updated": "2026-04-02", "type": "chat" }, { - "id": "hermes-3-llama-3.1-405b", - "name": "Hermes 3 Llama 3.1 405b", - "display_name": "Hermes 3 Llama 3.1 405b", + "id": "glm-5.1", + "name": "GLM-5.1", + "display_name": "GLM-5.1", "modalities": { "input": [ "text" @@ -226501,81 +224632,89 @@ ] }, "limit": { - "context": 128000, - "output": 16384 + "context": 200000, + "output": 128000 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "effort_options": [ + "none", + "low", + "medium", + "high", + "max" + ] + } }, "attachment": false, "open_weights": true, - "knowledge": "2023-12-31", - "release_date": "2024-08-16", - "last_updated": "2024-08-16", + "release_date": "2026-04-07", + "last_updated": "2026-04-07", "type": "chat" }, { - "id": "claude-opus-4-7", - "name": "Claude Opus 4.7", - "display_name": "Claude Opus 4.7", + "id": "gemini-3.7-flash", + "name": "Gemini 3.7 Flash", + "display_name": "Gemini 3.7 Flash", "modalities": { "input": [ "text", - "image" + "image", + "video", + "audio", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 1048576, + "output": 65535 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "high", - "effort_options": [ + "default_enabled": true, + "mode": "level", + "level": "high", + "level_options": [ + "minimal", "low", "medium", - "high", - "xhigh", - "max" + "high" ], - "interleaved": true, "summaries": true, - "visibility": "omitted", + "visibility": "summary", "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Claude Opus 4.7 and newer Opus models require thinking.type = \"adaptive\" to enable thinking explicitly.", - "Manual budget_tokens requests return 400 on Claude Opus 4.7 and newer adaptive-only Opus models.", - "task_budget is separate from thinking control and should not be treated as a thinking budget." + "thought_signatures" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-01-31", - "release_date": "2026-04-16", - "last_updated": "2026-04-16", + "knowledge": "2026-03", + "release_date": "2026-08-13", + "last_updated": "2026-08-13", "type": "chat" }, { - "id": "google-gemma-3-27b-it", - "name": "Google Gemma 3 27B Instruct", - "display_name": "Google Gemma 3 27B Instruct", + "id": "gemma-4-31b-it", + "name": "Gemma 4 31B IT", + "display_name": "Gemma 4 31B IT", "modalities": { "input": [ "text", @@ -226586,29 +224725,41 @@ ] }, "limit": { - "context": 198000, - "output": 16384 + "context": 262144, + "output": 8192 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "effort_options": [ + "none", + "low", + "medium", + "high", + "max" + ] + } }, "attachment": true, "open_weights": true, - "knowledge": "2024-08", - "release_date": "2025-03-12", - "last_updated": "2025-03-12", + "knowledge": "2025-01", + "release_date": "2026-04-02", + "last_updated": "2026-04-02", "type": "chat" }, { - "id": "muse-spark-open", - "name": "Muse Spark Open", - "display_name": "Muse Spark Open", + "id": "glm-5.2", + "name": "GLM-5.2", + "display_name": "GLM-5.2", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" @@ -226626,39 +224777,41 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, "effort_options": [ + "none", "low", "medium", "high", - "xhigh" + "max" ] } }, - "attachment": true, + "attachment": false, "open_weights": true, - "knowledge": "2026-01-04", - "release_date": "2026-10-15", - "last_updated": "2026-10-15", + "release_date": "2026-06-13", + "last_updated": "2026-06-13", "type": "chat" }, { - "id": "openai-gpt-61-terra", - "name": "GPT-6.1 Terra", - "display_name": "GPT-6.1 Terra", + "id": "mistral-medium-3-5", + "name": "Mistral Medium 3.5", + "display_name": "Mistral Medium 3.5", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 262144, + "output": 209715 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -226671,26 +224824,25 @@ "low", "medium", "high", - "xhigh", "max" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-06-01", - "release_date": "2026-10-26", - "last_updated": "2026-10-26", + "release_date": "2026-04-30", + "last_updated": "2026-04-30", "type": "chat" }, { - "id": "claude-opus-5-5", - "name": "Claude Opus 5.5", - "display_name": "Claude Opus 5.5", + "id": "claude-opus-5", + "name": "Claude Opus 5", + "display_name": "Claude Opus 5", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" @@ -226708,26 +224860,27 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, "effort_options": [ + "none", "low", "medium", "high", - "xhigh", "max" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-06", - "release_date": "2026-09-22", - "last_updated": "2026-09-22", + "knowledge": "2026-05", + "release_date": "2026-07-24", + "last_updated": "2026-07-24", "type": "chat" }, { - "id": "google-gemma-4-31b-it", - "name": "Google Gemma 4 31B Instruct", - "display_name": "Google Gemma 4 31B Instruct", + "id": "qwen3.5-35b-a3b", + "name": "Qwen3.5 35B-A3B", + "display_name": "Qwen3.5 35B-A3B", "modalities": { "input": [ "text", @@ -226739,8 +224892,8 @@ ] }, "limit": { - "context": 256000, - "output": 8192 + "context": 262144, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -226750,38 +224903,46 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ "none", "low", "medium", - "high" + "high", + "max" ] } }, "attachment": true, "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-04-02", - "last_updated": "2026-04-02", + "release_date": "2026-02-23", + "last_updated": "2026-02-23", "type": "chat" }, { - "id": "nvidia-nemotron-3-5-super-vl-120b-a12b", - "name": "NVIDIA Nemotron 3.5 Super VL 120B", - "display_name": "NVIDIA Nemotron 3.5 Super VL 120B", + "id": "qwen3.8-max", + "name": "Qwen3.8 Max", + "display_name": "Qwen3.8 Max", "modalities": { "input": [ "text", "image", - "video" + "video", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 32768 + "context": 1048576, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -226789,16 +224950,28 @@ "supported": true, "default": true }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "effort_options": [ + "none", + "low", + "medium", + "high", + "max" + ] + } + }, "attachment": true, - "open_weights": true, - "release_date": "2026-10-15", - "last_updated": "2026-10-15", + "open_weights": false, + "release_date": "2026-08-03", + "last_updated": "2026-08-03", "type": "chat" }, { - "id": "claude-sonnet-5", - "name": "Claude Sonnet 5", - "display_name": "Claude Sonnet 5", + "id": "grok-4.6", + "name": "Grok 4.6", + "display_name": "Grok 4.6", "modalities": { "input": [ "text", @@ -226809,10 +224982,10 @@ ] }, "limit": { - "context": 1000000, - "output": 64000 + "context": 500000, + "output": 500000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -226822,67 +224995,70 @@ "reasoning": { "supported": true, "effort_options": [ + "none", "low", "medium", "high", - "xhigh", "max" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-01-31", - "release_date": "2026-06-30", - "last_updated": "2026-06-30", + "knowledge": "2026-02-01", + "release_date": "2026-08-12", + "last_updated": "2026-08-12", "type": "chat" }, { - "id": "qwen3-vl-235b-a22b", - "name": "Qwen3 VL 235B", - "display_name": "Qwen3 VL 235B", + "id": "minimax-m3", + "name": "MiniMax-M3", + "display_name": "MiniMax-M3", "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 16384 + "context": 1000000, + "output": 128000 }, + "temperature": true, "tool_call": true, "reasoning": { - "supported": true + "supported": true, + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "none", + "low", + "medium", + "high", + "max" ] } }, "attachment": true, "open_weights": true, - "release_date": "2026-01-16", - "last_updated": "2026-06-11", + "release_date": "2026-06-01", + "last_updated": "2026-06-01", "type": "chat" }, { - "id": "deepseek-v4-1-flash", - "name": "DeepSeek V4.1 Flash", - "display_name": "DeepSeek V4.1 Flash", + "id": "deepseek-v4-pro-0813", + "name": "DeepSeek V4 Pro 0813", + "display_name": "DeepSeek V4 Pro 0813", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" @@ -226900,39 +225076,41 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, "effort_options": [ "none", "low", + "medium", "high", "max" ] } }, - "attachment": true, + "attachment": false, "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-09-10", - "last_updated": "2026-09-10", + "release_date": "2026-08-13", + "last_updated": "2026-08-22", "type": "chat" }, { - "id": "openai-gpt-56-sol", - "name": "GPT-5.6 Sol", - "display_name": "GPT-5.6 Sol", + "id": "qwen3.7-plus", + "name": "Qwen3.7 Plus", + "display_name": "Qwen3.7 Plus", "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 1048576, + "output": 131072 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -226940,27 +225118,27 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, "effort_options": [ "none", "low", "medium", "high", - "xhigh", "max" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-02-16", - "release_date": "2026-07-09", - "last_updated": "2026-07-09", + "knowledge": "2025-04", + "release_date": "2026-06-02", + "last_updated": "2026-06-02", "type": "chat" }, { - "id": "qwen3-coder-480b-a35b-instruct-turbo", - "name": "Qwen 3 Coder 480B Turbo", - "display_name": "Qwen 3 Coder 480B Turbo", + "id": "ling-2.6-flash", + "name": "ling-2.6-flash", + "display_name": "ling-2.6-flash", "modalities": { "input": [ "text" @@ -226970,37 +225148,38 @@ ] }, "limit": { - "context": 256000, - "output": 65536 + "context": 262144, + "output": 262144 }, "tool_call": true, "reasoning": { "supported": false }, "attachment": false, - "open_weights": true, - "release_date": "2026-01-27", - "last_updated": "2026-06-11", + "open_weights": false, + "release_date": "2026-04-21", + "last_updated": "2026-04-21", "type": "chat" }, { - "id": "openai-gpt-55-pro", - "name": "GPT-5.5 Pro", - "display_name": "GPT-5.5 Pro", + "id": "step-3.7-flash", + "name": "Step 3.7 Flash", + "display_name": "Step 3.7 Flash", "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 262144, + "output": 256000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -227009,34 +225188,40 @@ "extra_capabilities": { "reasoning": { "effort_options": [ + "none", + "low", "medium", "high", - "xhigh" + "max" ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2025-12-01", - "release_date": "2026-04-23", - "last_updated": "2026-04-23", + "open_weights": true, + "knowledge": "2026-03-01", + "release_date": "2026-05-29", + "last_updated": "2026-05-29", "type": "chat" }, { - "id": "zai-org-glm-5", - "name": "GLM 5", - "display_name": "GLM 5", + "id": "gemini-3.1-flash-lite", + "name": "Gemini 3.1 Flash Lite", + "display_name": "Gemini 3.1 Flash Lite", "modalities": { "input": [ - "text" + "text", + "image", + "video", + "audio", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 198000, - "output": 32000 + "context": 1048576, + "output": 65535 }, "temperature": true, "tool_call": true, @@ -227047,73 +225232,38 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], "effort_options": [ "none", "low", "medium", - "high" + "high", + "max" ] } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-02-12", - "last_updated": "2026-02-12", - "type": "chat" - }, - { - "id": "mistral-small-3-2-24b-instruct", - "name": "Mistral Small 3.2 24B Instruct", - "display_name": "Mistral Small 3.2 24B Instruct", - "modalities": { - "input": [ - "image", - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 256000, - "output": 16384 - }, - "temperature": true, - "tool_call": true, - "reasoning": { - "supported": false - }, "attachment": true, - "open_weights": true, - "knowledge": "2023-10-31", - "release_date": "2025-06-20", - "last_updated": "2025-06-20", + "open_weights": false, + "knowledge": "2025-01", + "release_date": "2026-05-07", + "last_updated": "2026-05-07", "type": "chat" }, { - "id": "deepseek-v4-1-flash-1017", - "name": "DeepSeek v4.1 Flash (1017)", - "display_name": "DeepSeek v4.1 Flash (1017)", + "id": "ring-2.6-1t", + "name": "ring-2.6-1t", + "display_name": "ring-2.6-1t", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 262144, + "output": 262144 }, - "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -227124,113 +225274,69 @@ "effort_options": [ "none", "low", + "medium", "high", "max" ] } }, - "attachment": true, - "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-10-17", - "last_updated": "2026-10-17", - "type": "chat" - }, - { - "id": "grok-4-20", - "name": "Grok 4.20", - "display_name": "Grok 4.20", - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 2000000, - "output": 128000 - }, - "tool_call": true, - "reasoning": { - "supported": true, - "default": true - }, - "attachment": true, + "attachment": false, "open_weights": false, - "release_date": "2026-03-12", - "last_updated": "2026-06-11", + "release_date": "2026-05-08", + "last_updated": "2026-05-08", "type": "chat" }, { - "id": "claude-sonnet-4-6", - "name": "Claude Sonnet 4.6", - "display_name": "Claude Sonnet 4.6", + "id": "muse-spark-1.3", + "name": "Muse Spark 1.3", + "display_name": "Muse Spark 1.3", "modalities": { "input": [ "text", - "image" + "image", + "video", + "pdf", + "audio" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 64000 + "context": 1048576, + "output": 943718 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "mixed", - "budget": { - "min": 1024, - "unit": "tokens" - }, - "effort": "high", "effort_options": [ + "none", "low", "medium", "high", "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Anthropic recommends adaptive thinking with effort for Claude 4.6; budget_tokens remains a deprecated compatibility path.", - "Anthropic API defaults effort to high; lower effort levels should be chosen per workload." ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-02-17", - "last_updated": "2026-03-13", + "release_date": "2026-09-02", + "last_updated": "2026-09-02", "type": "chat" }, { - "id": "qwen-3-8-max", - "name": "Qwen 3.8 Max", - "display_name": "Qwen 3.8 Max", + "id": "glm-5.2-fast", + "name": "glm-5.2-fast", + "display_name": "glm-5.2-fast", "modalities": { "input": [ - "text", - "image", - "video" + "text" ], "output": [ "text" @@ -227240,63 +225346,84 @@ "context": 1000000, "output": 131072 }, - "temperature": true, "tool_call": true, "reasoning": { "supported": true, "default": true }, - "attachment": true, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "none", + "low", + "medium", + "high", + "max" + ] + } + }, + "attachment": false, "open_weights": false, - "release_date": "2026-07-19", - "last_updated": "2026-07-19", + "release_date": "2026-07-13", + "last_updated": "2026-07-13", "type": "chat" }, { - "id": "minimax-m3-preview", - "name": "MiniMax M3 Preview", - "display_name": "MiniMax M3 Preview", + "id": "mistral-small-2603", + "name": "Mistral Small 4", + "display_name": "Mistral Small 4", "modalities": { "input": [ "text", - "image", - "video" + "image" ], "output": [ "text" ] }, "limit": { - "context": 524288, - "output": 65536 + "context": 256000, + "output": 256000 }, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, "default": true }, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "none", + "low", + "medium", + "high", + "max" + ] + } + }, "attachment": true, "open_weights": true, - "release_date": "2026-06-12", - "last_updated": "2026-06-13", + "knowledge": "2025-06", + "release_date": "2026-03-16", + "last_updated": "2026-03-16", "type": "chat" }, { - "id": "grok-4-7", - "name": "Grok 4.7", - "display_name": "Grok 4.7", + "id": "deepseek-v4-flash-0731", + "name": "DeepSeek V4 Flash 0731", + "display_name": "DeepSeek V4 Flash 0731", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 500000, - "output": 200000 + "context": 1048576, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -227306,38 +225433,39 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, "effort_options": [ + "none", "low", "medium", "high", - "xhigh" + "max" ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-05", - "release_date": "2026-09-21", - "last_updated": "2026-09-21", + "attachment": false, + "open_weights": true, + "knowledge": "2025-05", + "release_date": "2026-07-31", + "last_updated": "2026-07-31", "type": "chat" }, { - "id": "qwen-3-8-27b", - "name": "Qwen 3.8 27B", - "display_name": "Qwen 3.8 27B", + "id": "muse-glimmer-30b", + "name": "Muse Glimmer 30B", + "display_name": "Muse Glimmer 30B", "modalities": { "input": [ "text", - "image", - "video" + "image" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 65536 + "context": 131072, + "output": 20480 }, "temperature": true, "tool_call": true, @@ -227351,35 +225479,37 @@ "none", "low", "medium", - "xhigh" + "high", + "max" ] } }, "attachment": true, "open_weights": true, - "release_date": "2026-08-14", - "last_updated": "2026-08-14", + "knowledge": "2026-01-04", + "release_date": "2026-08-10", + "last_updated": "2026-08-10", "type": "chat" }, { - "id": "google-gemma-4-26b-a4b-it", - "name": "Google Gemma 4 26B A4B Instruct", - "display_name": "Google Gemma 4 26B A4B Instruct", + "id": "gpt-6-luna", + "name": "GPT-6 Luna", + "display_name": "GPT-6 Luna", "modalities": { "input": [ "text", "image", - "video" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 256000, - "output": 8192 + "context": 1050000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -227391,30 +225521,34 @@ "none", "low", "medium", - "high" + "high", + "max" ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-04-02", - "last_updated": "2026-04-02", + "open_weights": false, + "knowledge": "2026-05-18", + "release_date": "2026-09-22", + "last_updated": "2026-09-22", "type": "chat" }, { - "id": "deepseek-v3.2", - "name": "DeepSeek V3.2", - "display_name": "DeepSeek V3.2", + "id": "inkling-256k", + "name": "Inkling 256K", + "display_name": "Inkling 256K", "modalities": { "input": [ - "text" + "text", + "image", + "audio" ], "output": [ "text" ] }, "limit": { - "context": 160000, + "context": 262144, "output": 32768 }, "temperature": true, @@ -227425,40 +225559,38 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, "effort_options": [ "none", "low", "medium", - "high" + "high", + "max" ] } }, - "attachment": false, + "attachment": true, "open_weights": true, - "knowledge": "2024-07", - "release_date": "2025-12-01", - "last_updated": "2025-12-01", + "release_date": "2026-07-15", + "last_updated": "2026-07-15", "type": "chat" }, { - "id": "kimi-k2-5", - "name": "Kimi K2.5", - "display_name": "Kimi K2.5", + "id": "minimax-m2.7", + "name": "MiniMax-M2.7", + "display_name": "MiniMax-M2.7", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 256000, - "output": 65536 + "context": 200000, + "output": 128000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -227477,25 +225609,26 @@ "none", "low", "medium", - "high" + "high", + "max" ] } }, - "attachment": true, + "attachment": false, "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-01-27", - "last_updated": "2026-01-27", + "release_date": "2026-03-18", + "last_updated": "2026-03-18", "type": "chat" }, { - "id": "grok-4-3", - "name": "Grok 4.3", - "display_name": "Grok 4.3", + "id": "minimax-m3.1-flash", + "name": "MiniMax-M3.1-Flash", + "display_name": "MiniMax-M3.1-Flash", "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ "text" @@ -227503,7 +225636,7 @@ }, "limit": { "context": 1000000, - "output": 32000 + "output": 128000 }, "temperature": true, "tool_call": true, @@ -227517,31 +225650,34 @@ "none", "low", "medium", - "high" + "high", + "max" ] } }, "attachment": true, "open_weights": false, - "release_date": "2026-04-17", - "last_updated": "2026-04-17", + "release_date": "2026-10-19", + "last_updated": "2026-10-19", "type": "chat" }, { - "id": "z-ai-glm-5-turbo", - "name": "GLM 5 Turbo", - "display_name": "GLM 5 Turbo", + "id": "seed-2.0-code", + "name": "Seed 2.0 Code", + "display_name": "Seed 2.0 Code", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 32768 + "context": 256000, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -227555,48 +225691,21 @@ "none", "low", "medium", - "high" + "high", + "max" ] } }, - "attachment": false, + "attachment": true, "open_weights": false, - "release_date": "2026-03-16", - "last_updated": "2026-03-16", - "type": "chat" - }, - { - "id": "llama-3.2-3b", - "name": "Llama 3.2 3B", - "display_name": "Llama 3.2 3B", - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 4096 - }, - "temperature": true, - "tool_call": true, - "reasoning": { - "supported": false - }, - "attachment": false, - "open_weights": true, - "knowledge": "2023-12", - "release_date": "2024-09-25", - "last_updated": "2024-09-25", + "release_date": "2026-02-14", + "last_updated": "2026-02-14", "type": "chat" }, { - "id": "deepseek-v4-flash-0731", - "name": "DeepSeek V4 Flash 0731", - "display_name": "DeepSeek V4 Flash 0731", + "id": "ling-3.0-tiny", + "name": "ling-3.0-tiny", + "display_name": "ling-3.0-tiny", "modalities": { "input": [ "text" @@ -227606,10 +225715,9 @@ ] }, "limit": { - "context": 1000000, + "context": 262144, "output": 32768 }, - "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -227617,38 +225725,38 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, "effort_options": [ "none", "low", + "medium", "high", "max" ] } }, "attachment": false, - "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-07-31", - "last_updated": "2026-07-31", + "open_weights": false, + "release_date": "2026-08-05", + "last_updated": "2026-08-05", "type": "chat" }, { - "id": "grok-build-0-1", - "name": "Grok Build 0.1", - "display_name": "Grok Build 0.1", + "id": "kimi-k2.6", + "name": "Kimi K2.6", + "display_name": "Kimi K2.6", "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 256000, - "output": 65536 + "context": 262144, + "output": 262144 }, "temperature": true, "tool_call": true, @@ -227656,28 +225764,44 @@ "supported": true, "default": true }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "effort_options": [ + "none", + "low", + "medium", + "high", + "max" + ] + } + }, "attachment": true, - "open_weights": false, - "release_date": "2026-04-16", - "last_updated": "2026-04-16", + "open_weights": true, + "knowledge": "2025-01", + "release_date": "2026-04-20", + "last_updated": "2026-04-20", "type": "chat" }, { - "id": "z-ai-glm-5v-turbo", - "name": "GLM 5V Turbo", - "display_name": "GLM 5V Turbo", + "id": "gemini-3.5-flash-lite", + "name": "Gemini 3.5 Flash Lite", + "display_name": "Gemini 3.5 Flash Lite", "modalities": { "input": [ "text", - "image" + "image", + "video", + "audio", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 32768 + "context": 1048576, + "output": 65535 }, "temperature": true, "tool_call": true, @@ -227687,24 +225811,36 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, "effort_options": [ "none", "low", "medium", - "high" + "high", + "max" ] } }, "attachment": true, "open_weights": false, - "release_date": "2026-04-01", - "last_updated": "2026-04-01", + "knowledge": "2026-03", + "release_date": "2026-07-21", + "last_updated": "2026-07-21", "type": "chat" - }, + } + ] + }, + "minimax-cn": { + "id": "minimax-cn", + "name": "MiniMax (minimax.cn)", + "display_name": "MiniMax (minimax.cn)", + "api": "https://api.minimax.cn/anthropic/v1", + "doc": "https://platform.minimaxi.com/docs/guides/quickstart", + "models": [ { - "id": "deepseek-v4-pro-0813", - "name": "DeepSeek V4 Pro 0813", - "display_name": "DeepSeek V4 Pro 0813", + "id": "MiniMax-M2.5", + "name": "MiniMax-M2.5", + "display_name": "MiniMax-M2.5", "modalities": { "input": [ "text" @@ -227714,8 +225850,8 @@ ] }, "limit": { - "context": 1000000, - "output": 32768 + "context": 204800, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -227725,30 +225861,23 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "effort_options": [ - "none", - "low", - "high", - "max" - ] + "supported": true } }, "attachment": false, "open_weights": true, - "release_date": "2026-08-13", - "last_updated": "2026-08-22", + "release_date": "2026-02-12", + "last_updated": "2026-02-12", "type": "chat" }, { - "id": "gemini-3-1-pro-preview", - "name": "Gemini 3.1 Pro Preview", - "display_name": "Gemini 3.1 Pro Preview", + "id": "MiniMax-M3.1-Flash", + "name": "MiniMax-M3.1-Flash", + "display_name": "MiniMax-M3.1-Flash", "modalities": { "input": [ "text", "image", - "audio", "video" ], "output": [ @@ -227757,7 +225886,7 @@ }, "limit": { "context": 1000000, - "output": 32768 + "output": 524288 }, "temperature": true, "tool_call": true, @@ -227770,21 +225899,22 @@ "effort_options": [ "low", "medium", - "high" + "high", + "xhigh", + "max" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-01", - "release_date": "2026-02-19", - "last_updated": "2026-02-19", + "release_date": "2026-10-19", + "last_updated": "2026-10-19", "type": "chat" }, { - "id": "minimax-m27", - "name": "MiniMax M2.7", - "display_name": "MiniMax M2.7", + "id": "MiniMax-M2.7-highspeed", + "name": "MiniMax-M2.7-highspeed", + "display_name": "MiniMax-M2.7-highspeed", "modalities": { "input": [ "text" @@ -227794,8 +225924,8 @@ ] }, "limit": { - "context": 198000, - "output": 32768 + "context": 204800, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -227805,11 +225935,12 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "none", - "low", - "medium", - "high" + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, @@ -227820,9 +225951,9 @@ "type": "chat" }, { - "id": "aion-labs-aion-3-5", - "name": "Aion 3.5", - "display_name": "Aion 3.5", + "id": "MiniMax-M2.1", + "name": "MiniMax-M2.1", + "display_name": "MiniMax-M2.1", "modalities": { "input": [ "text" @@ -227832,96 +225963,41 @@ ] }, "limit": { - "context": 262144, - "output": 32768 - }, - "tool_call": true, - "reasoning": { - "supported": true, - "default": true - }, - "attachment": false, - "open_weights": false, - "release_date": "2026-09-23", - "last_updated": "2026-09-23", - "type": "chat" - }, - { - "id": "claude-opus-4-6", - "name": "Claude Opus 4.6", - "display_name": "Claude Opus 4.6", - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 1000000, - "output": 128000 + "context": 204800, + "output": 131072 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "mixed", - "budget": { - "min": 1024, - "unit": "tokens" - }, - "effort": "high", - "effort_options": [ - "low", - "medium", - "high", - "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Anthropic recommends adaptive thinking with effort for Claude 4.6; budget_tokens remains a deprecated compatibility path.", - "Anthropic API defaults effort to high; lower effort levels should be chosen per workload." - ] + "supported": true } }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-05-31", - "release_date": "2026-02-05", - "last_updated": "2026-03-13", + "attachment": false, + "open_weights": true, + "release_date": "2025-12-23", + "last_updated": "2025-12-23", "type": "chat" }, { - "id": "gemini-3-8-flash", - "name": "Gemini 3.8 Flash", - "display_name": "Gemini 3.8 Flash", + "id": "MiniMax-M2", + "name": "MiniMax-M2", + "display_name": "MiniMax-M2", "modalities": { "input": [ - "text", - "image", - "audio", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 65536 + "context": 204800, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -227929,16 +226005,16 @@ "supported": true, "default": true }, - "attachment": true, - "open_weights": false, - "release_date": "2026-09-02", - "last_updated": "2026-09-02", + "attachment": false, + "open_weights": true, + "release_date": "2025-10-27", + "last_updated": "2025-10-27", "type": "chat" }, { - "id": "minimax-m25", - "name": "MiniMax M2.5", - "display_name": "MiniMax M2.5", + "id": "MiniMax-M2.7", + "name": "MiniMax-M2.7", + "display_name": "MiniMax-M2.7", "modalities": { "input": [ "text" @@ -227948,8 +226024,8 @@ ] }, "limit": { - "context": 198000, - "output": 32768 + "context": 204800, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -227965,24 +226041,19 @@ "visibility": "summary", "continuation": [ "thinking_blocks" - ], - "effort_options": [ - "low", - "medium", - "high" ] } }, "attachment": false, "open_weights": true, - "release_date": "2026-02-12", - "last_updated": "2026-02-12", + "release_date": "2026-03-18", + "last_updated": "2026-03-18", "type": "chat" }, { - "id": "qwen3-235b-a22b-instruct-2507", - "name": "Qwen 3 235B A22B Instruct 2507", - "display_name": "Qwen 3 235B A22B Instruct 2507", + "id": "MiniMax-M2.5-highspeed", + "name": "MiniMax-M2.5-highspeed", + "display_name": "MiniMax-M2.5-highspeed", "modalities": { "input": [ "text" @@ -227992,24 +226063,30 @@ ] }, "limit": { - "context": 128000, - "output": 16384 + "context": 204800, + "output": 131072 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true + } }, "attachment": false, "open_weights": true, - "release_date": "2025-07-21", - "last_updated": "2025-07-21", + "release_date": "2026-02-13", + "last_updated": "2026-02-13", "type": "chat" }, { - "id": "qwen3-5-397b-a17b", - "name": "Qwen 3.5 397B", - "display_name": "Qwen 3.5 397B", + "id": "MiniMax-M3", + "name": "MiniMax-M3", + "display_name": "MiniMax-M3", "modalities": { "input": [ "text", @@ -228021,8 +226098,8 @@ ] }, "limit": { - "context": 128000, - "output": 32768 + "context": 1000000, + "output": 524288 }, "temperature": true, "tool_call": true, @@ -228032,63 +226109,84 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "none", - "low", - "medium", - "high" - ] + "supported": true } }, "attachment": true, "open_weights": true, - "release_date": "2026-02-15", - "last_updated": "2026-02-15", + "release_date": "2026-06-01", + "last_updated": "2026-06-25", "type": "chat" - }, + } + ] + }, + "azure": { + "id": "azure", + "name": "Azure", + "display_name": "Azure", + "doc": "https://learn.microsoft.com/en-us/azure/ai-services/openai/concepts/models", + "models": [ { - "id": "aion-labs-aion-3-5-mini", - "name": "Aion 3.5 Mini", - "display_name": "Aion 3.5 Mini", + "id": "gpt-5-pro", + "name": "GPT-5 Pro", + "display_name": "GPT-5 Pro", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 32768 + "context": 400000, + "output": 128000 }, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, "default": true }, - "attachment": false, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "fixed", + "effort": "high", + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" + } + }, + "attachment": true, "open_weights": false, - "release_date": "2026-09-23", - "last_updated": "2026-09-23", + "knowledge": "2024-09-30", + "release_date": "2025-10-06", + "last_updated": "2025-10-06", "type": "chat" }, { - "id": "openai-gpt-56-luna-pro", - "name": "GPT-5.6 Luna Pro", - "display_name": "GPT-5.6 Luna Pro", + "id": "o3-mini", + "name": "o3-mini", + "display_name": "o3-mini", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 200000, + "output": 100000 }, "temperature": false, "tool_call": true, @@ -228098,67 +226196,82 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", "effort_options": [ "low", "medium", - "high", - "xhigh", - "max" - ] + "high" + ], + "visibility": "hidden" } }, - "attachment": true, + "attachment": false, "open_weights": false, - "knowledge": "2026-02-16", - "release_date": "2026-07-09", - "last_updated": "2026-07-09", + "knowledge": "2024-05", + "release_date": "2024-12-20", + "last_updated": "2025-01-29", "type": "chat" }, { - "id": "claude-opus-5-fast", - "name": "Claude Opus 5 Fast", - "display_name": "Claude Opus 5 Fast", + "id": "gpt-5.4-nano", + "name": "GPT-5.4 Nano", + "display_name": "GPT-5.4 Nano", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1000000, + "context": 400000, "output": 128000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": false, + "mode": "effort", + "effort": "none", "effort_options": [ + "none", "low", "medium", "high", - "xhigh", - "max" - ] + "xhigh" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, "attachment": true, "open_weights": false, - "knowledge": "2026-05", - "release_date": "2026-07-24", - "last_updated": "2026-07-24", + "knowledge": "2025-08-31", + "release_date": "2026-03-17", + "last_updated": "2026-03-17", "type": "chat" }, { - "id": "qwen3-next-80b", - "name": "Qwen 3 Next 80b", - "display_name": "Qwen 3 Next 80b", + "id": "deepseek-v3.2-speciale", + "name": "DeepSeek-V3.2-Speciale", + "display_name": "DeepSeek-V3.2-Speciale", "modalities": { "input": [ "text" @@ -228168,36 +226281,38 @@ ] }, "limit": { - "context": 256000, - "output": 16384 + "context": 128000, + "output": 128000 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": false + "supported": true, + "default": true }, "attachment": false, "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-09", - "last_updated": "2025-09", + "knowledge": "2024-07", + "release_date": "2025-12-01", + "last_updated": "2025-12-01", "type": "chat" }, { - "id": "claude-opus-5-5-fast", - "name": "Claude Opus 5.5 Fast", - "display_name": "Claude Opus 5.5 Fast", + "id": "gpt-6.1-terra", + "name": "GPT-6.1 Terra", + "display_name": "GPT-6.1 Terra", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1000000, + "context": 1050000, "output": 128000 }, "temperature": false, @@ -228209,6 +226324,7 @@ "extra_capabilities": { "reasoning": { "effort_options": [ + "none", "low", "medium", "high", @@ -228219,139 +226335,133 @@ }, "attachment": true, "open_weights": false, - "knowledge": "2026-06", - "release_date": "2026-09-22", - "last_updated": "2026-09-22", + "knowledge": "2026-06-01", + "release_date": "2026-10-26", + "last_updated": "2026-10-26", "type": "chat" }, { - "id": "deepseek-v4-1-flash-1017-uncensored", - "name": "DeepSeek V4.1 Flash 1017 Uncensored", - "display_name": "DeepSeek V4.1 Flash 1017 Uncensored", + "id": "gpt-5.4", + "name": "GPT-5.4", + "display_name": "GPT-5.4", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 1050000, + "output": 128000 }, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": false, + "mode": "effort", + "effort": "none", "effort_options": [ "none", "low", + "medium", "high", - "max" - ] + "xhigh" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, "attachment": true, - "open_weights": true, - "release_date": "2026-10-27", - "last_updated": "2026-10-27", + "open_weights": false, + "knowledge": "2025-08-31", + "release_date": "2026-03-05", + "last_updated": "2026-03-05", "type": "chat" }, { - "id": "kimi-k3-1-pro-highspeed", - "name": "Kimi K3.1 Pro Highspeed", - "display_name": "Kimi K3.1 Pro Highspeed", + "id": "gpt-5.4-mini", + "name": "GPT-5.4 Mini", + "display_name": "GPT-5.4 Mini", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 400000, + "output": 128000 }, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": false, + "mode": "effort", + "effort": "none", "effort_options": [ "none", "low", + "medium", "high", - "max" - ] - } - }, - "attachment": true, - "open_weights": false, - "release_date": "2026-10-23", - "last_updated": "2026-10-23", - "type": "chat" - }, - { - "id": "olafangensan-glm-4.7-flash-heretic", - "name": "GLM 4.7 Flash Heretic", - "display_name": "GLM 4.7 Flash Heretic", - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 200000, - "output": 24000 - }, - "tool_call": true, - "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "none", + "xhigh" + ], + "verbosity": "medium", + "verbosity_options": [ "low", "medium", "high" - ] + ], + "visibility": "hidden" } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-02-04", - "last_updated": "2026-06-11", + "attachment": true, + "open_weights": false, + "knowledge": "2025-08-31", + "release_date": "2026-03-17", + "last_updated": "2026-03-17", "type": "chat" }, { - "id": "claude-opus-4-8", - "name": "Claude Opus 4.8", - "display_name": "Claude Opus 4.8", + "id": "gpt-5.1-codex-max", + "name": "GPT-5.1 Codex Max", + "display_name": "GPT-5.1 Codex Max", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1000000, + "context": 400000, "output": 128000 }, "temperature": false, @@ -228365,91 +226475,76 @@ "supported": true, "default_enabled": false, "mode": "effort", - "effort": "high", + "effort": "none", "effort_options": [ + "none", "low", "medium", - "high", - "xhigh", - "max" + "high" ], - "interleaved": true, - "summaries": true, - "visibility": "omitted", - "continuation": [ - "thinking_blocks" + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" ], - "notes": [ - "Claude Opus 4.7 and newer Opus models require thinking.type = \"adaptive\" to enable thinking explicitly.", - "Manual budget_tokens requests return 400 on Claude Opus 4.7 and newer adaptive-only Opus models.", - "task_budget is separate from thinking control and should not be treated as a thinking budget." - ] + "visibility": "hidden" } }, "attachment": true, "open_weights": false, - "knowledge": "2026-01", - "release_date": "2026-05-28", - "last_updated": "2026-05-28", + "knowledge": "2024-09-30", + "release_date": "2025-11-13", + "last_updated": "2025-11-13", "type": "chat" }, { - "id": "openai-gpt-54-mini", - "name": "GPT-5.4 Mini", - "display_name": "GPT-5.4 Mini", + "id": "phi-4-multimodal", + "name": "Phi-4-multimodal", + "display_name": "Phi-4-multimodal", "modalities": { "input": [ "text", - "image" + "image", + "audio" ], "output": [ "text" ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 128000, + "output": 4096 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "none", - "low", - "medium", - "high", - "xhigh" - ] - } + "supported": false }, "attachment": true, - "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-03-17", - "last_updated": "2026-03-17", + "open_weights": true, + "knowledge": "2023-10", + "release_date": "2024-12-11", + "last_updated": "2024-12-11", "type": "chat" }, { - "id": "openai-gpt-55", - "name": "GPT-5.5", - "display_name": "GPT-5.5", + "id": "gpt-6-sol", + "name": "GPT-6 Sol", + "display_name": "GPT-6 Sol", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 1050000, + "output": 128000 }, "temperature": false, "tool_call": true, @@ -228464,36 +226559,37 @@ "low", "medium", "high", - "xhigh" + "xhigh", + "max" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-12-01", - "release_date": "2026-04-23", - "last_updated": "2026-04-23", + "knowledge": "2026-04-20", + "release_date": "2026-09-22", + "last_updated": "2026-09-22", "type": "chat" }, { - "id": "muse-wonder", - "name": "Muse Wonder", - "display_name": "Muse Wonder", + "id": "gpt-5.6-terra", + "name": "GPT-5.6 Terra", + "display_name": "GPT-5.6 Terra", "modalities": { "input": [ "text", "image", - "audio", - "video" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 1050000, + "output": 128000 }, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -228501,57 +226597,66 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", "effort_options": [ "none", "low", "medium", "high", + "xhigh", "max" - ] + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, "attachment": true, "open_weights": false, - "release_date": "2026-10-12", - "last_updated": "2026-10-15", + "knowledge": "2026-02-16", + "release_date": "2026-07-09", + "last_updated": "2026-07-09", "type": "chat" }, { - "id": "gemini-4-argon", - "name": "Gemini 4 Argon", - "display_name": "Gemini 4 Argon", + "id": "phi-4-mini", + "name": "Phi-4-mini", + "display_name": "Phi-4-mini", "modalities": { "input": [ - "text", - "image", - "audio", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 65536 + "context": 128000, + "output": 4096 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true + "supported": false }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-03", - "release_date": "2026-10-13", - "last_updated": "2026-10-13", + "attachment": false, + "open_weights": true, + "knowledge": "2023-10", + "release_date": "2024-12-11", + "last_updated": "2024-12-11", "type": "chat" }, { - "id": "abliteration-abliterated-model-large-flash", - "name": "Abliterated Large Flash", - "display_name": "Abliterated Large Flash", + "id": "cohere-embed-v-4-0", + "name": "Embed v4", + "display_name": "Embed v4", "modalities": { "input": [ "text", @@ -228562,37 +226667,39 @@ ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 128000, + "output": 1536 }, - "tool_call": true, + "temperature": false, + "tool_call": false, "reasoning": { - "supported": true, - "default": true + "supported": false }, "attachment": true, - "open_weights": false, - "release_date": "2026-10-23", - "last_updated": "2026-10-23", - "type": "chat" + "open_weights": true, + "release_date": "2025-04-15", + "last_updated": "2025-04-15", + "type": "embedding" }, { - "id": "zai-org-glm-4.6", - "name": "GLM 4.6", - "display_name": "GLM 4.6", + "id": "gpt-5.3-codex", + "name": "GPT-5.3 Codex", + "display_name": "GPT-5.3 Codex", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 198000, - "output": 16384 + "context": 400000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -228601,41 +226708,47 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], + "default_enabled": true, + "mode": "effort", + "effort": "medium", "effort_options": [ + "low", + "medium", + "high", + "xhigh" + ], + "verbosity": "medium", + "verbosity_options": [ "low", "medium", "high" - ] + ], + "visibility": "hidden" } }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-09-30", - "last_updated": "2025-09-30", + "attachment": true, + "open_weights": false, + "knowledge": "2025-08-31", + "release_date": "2026-02-24", + "last_updated": "2026-02-24", "type": "chat" }, { - "id": "openai-gpt-56-luna", - "name": "GPT-5.6 Luna", - "display_name": "GPT-5.6 Luna", + "id": "gpt-6-astra", + "name": "GPT-6 Astra", + "display_name": "GPT-6 Astra", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1000000, + "context": 1050000, "output": 128000 }, "temperature": false, @@ -228646,6 +226759,7 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, "effort_options": [ "none", "low", @@ -228658,53 +226772,27 @@ }, "attachment": true, "open_weights": false, - "knowledge": "2026-02-16", - "release_date": "2026-07-09", - "last_updated": "2026-07-09", - "type": "chat" - }, - { - "id": "venice-uncensored-role-play", - "name": "Venice Role Play Uncensored", - "display_name": "Venice Role Play Uncensored", - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 4096 - }, - "tool_call": true, - "reasoning": { - "supported": false - }, - "attachment": true, - "open_weights": true, - "release_date": "2026-02-20", - "last_updated": "2026-06-11", + "knowledge": "2026-04-30", + "release_date": "2026-09-04", + "last_updated": "2026-09-04", "type": "chat" }, { - "id": "claude-opus-4-8-fast", - "name": "Claude Opus 4.8 Fast", - "display_name": "Claude Opus 4.8 Fast", + "id": "gpt-5.1-codex", + "name": "GPT-5.1 Codex", + "display_name": "GPT-5.1 Codex", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1000000, + "context": 400000, "output": 128000 }, "temperature": false, @@ -228718,49 +226806,45 @@ "supported": true, "default_enabled": false, "mode": "effort", - "effort": "high", + "effort": "none", "effort_options": [ + "none", "low", "medium", - "high", - "xhigh", - "max" + "high" ], - "interleaved": true, - "summaries": true, - "visibility": "omitted", - "continuation": [ - "thinking_blocks" + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" ], - "notes": [ - "Claude Opus 4.7 and newer Opus models require thinking.type = \"adaptive\" to enable thinking explicitly.", - "Manual budget_tokens requests return 400 on Claude Opus 4.7 and newer adaptive-only Opus models.", - "task_budget is separate from thinking control and should not be treated as a thinking budget." - ] + "visibility": "hidden" } }, "attachment": true, "open_weights": false, - "knowledge": "2026-01", - "release_date": "2026-05-28", - "last_updated": "2026-05-28", + "knowledge": "2024-09-30", + "release_date": "2025-11-14", + "last_updated": "2025-11-14", "type": "chat" }, { - "id": "openai-gpt-6-sol", - "name": "GPT-6 Sol", - "display_name": "GPT-6 Sol", + "id": "claude-haiku-5-5", + "name": "Claude Haiku 5.5", + "display_name": "Claude Haiku 5.5", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1050000, + "context": 1000000, "output": 128000 }, "temperature": false, @@ -228772,7 +226856,6 @@ "extra_capabilities": { "reasoning": { "effort_options": [ - "none", "low", "medium", "high", @@ -228783,57 +226866,88 @@ }, "attachment": true, "open_weights": false, - "knowledge": "2026-04-20", - "release_date": "2026-09-22", - "last_updated": "2026-09-22", + "knowledge": "2026-06", + "release_date": "2026-10-07", + "last_updated": "2026-10-07", "type": "chat" }, { - "id": "z-ai-glm-5-3-flash", - "name": "GLM 5.3 Flash", - "display_name": "GLM 5.3 Flash", + "id": "gpt-image-2.5-sunburst", + "name": "GPT Image 2.5 Sunburst", + "display_name": "GPT Image 2.5 Sunburst", "modalities": { "input": [ "text", - "image", - "video" + "image" + ], + "output": [ + "image" + ] + }, + "limit": { + "context": 8192, + "output": 8192 + }, + "temperature": false, + "tool_call": false, + "reasoning": { + "supported": false + }, + "attachment": true, + "open_weights": false, + "release_date": "2026-09-08", + "last_updated": "2026-09-08" + }, + { + "id": "grok-4-20-non-reasoning", + "name": "Grok 4.20 (Non-Reasoning)", + "display_name": "Grok 4.20 (Non-Reasoning)", + "modalities": { + "input": [ + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 262000, + "output": 8192 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true + "supported": true }, - "attachment": true, - "open_weights": true, - "release_date": "2026-08-26", - "last_updated": "2026-08-26", + "extra_capabilities": { + "reasoning": { + "supported": true + } + }, + "attachment": false, + "open_weights": false, + "knowledge": "2025-09", + "release_date": "2026-04-08", + "last_updated": "2026-04-08", "type": "chat" }, { - "id": "kimi-k2-7-code", - "name": "Kimi K2.7 Code", - "display_name": "Kimi K2.7 Code", + "id": "gpt-5.2-codex", + "name": "GPT-5.2 Codex", + "display_name": "GPT-5.2 Codex", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 256000, - "output": 65536 + "context": 400000, + "output": 128000 }, "temperature": false, "tool_call": true, @@ -228843,131 +226957,182 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", "effort_options": [ + "low", + "medium", + "high", + "xhigh" + ], + "verbosity": "medium", + "verbosity_options": [ "low", "medium", "high" - ] + ], + "visibility": "hidden" } }, "attachment": true, - "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-06-12", - "last_updated": "2026-06-12", + "open_weights": false, + "knowledge": "2025-08-31", + "release_date": "2026-01-14", + "last_updated": "2026-01-14", "type": "chat" }, { - "id": "qwen-3-7-max", - "name": "Qwen 3.7 Max", - "display_name": "Qwen 3.7 Max", + "id": "gpt-5.2", + "name": "GPT-5.2", + "display_name": "GPT-5.2", "modalities": { "input": [ "text", "image", - "video" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 65536 + "context": 400000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": false, + "mode": "effort", + "effort": "none", + "effort_options": [ + "none", + "low", + "medium", + "high", + "xhigh" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" + } }, "attachment": true, "open_weights": false, - "release_date": "2026-05-21", - "last_updated": "2026-05-21", + "knowledge": "2025-08-31", + "release_date": "2025-12-11", + "last_updated": "2025-12-11", "type": "chat" }, { - "id": "gemini-3-7-flash", - "name": "Gemini 3.7 Flash", - "display_name": "Gemini 3.7 Flash", + "id": "gpt-5.4-pro", + "name": "GPT-5.4 Pro", + "display_name": "GPT-5.4 Pro", "modalities": { "input": [ "text", "image", - "audio", - "video" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 65536 + "context": 1050000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, "default": true }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "high", + "effort_options": [ + "medium", + "high", + "xhigh" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" + } + }, "attachment": true, "open_weights": false, - "knowledge": "2026-03", - "release_date": "2026-08-13", - "last_updated": "2026-08-13", + "knowledge": "2025-08-31", + "release_date": "2026-03-05", + "last_updated": "2026-03-05", "type": "chat" }, { - "id": "seed-2-1-turbo", - "name": "Seed 2.1 Turbo", - "display_name": "Seed 2.1 Turbo", + "id": "gpt-image-1.5", + "name": "GPT-Image-1.5", + "display_name": "GPT-Image-1.5", "modalities": { "input": [ "text", - "image", - "video" + "image" ], "output": [ - "text" + "text", + "image" ] }, "limit": { - "context": 256000, - "output": 65536 + "context": 8192, + "output": 8192 }, - "temperature": true, - "tool_call": true, + "temperature": false, + "tool_call": false, "reasoning": { - "supported": true, - "default": true + "supported": false }, "attachment": true, "open_weights": false, - "release_date": "2026-06-23", - "last_updated": "2026-06-23", - "type": "chat" + "release_date": "2025-11-25", + "last_updated": "2025-11-25" }, { - "id": "qwen3-5-35b-a3b", - "name": "Qwen 3.5 35B A3B", - "display_name": "Qwen 3.5 35B A3B", + "id": "kimi-k2.7-code", + "name": "Kimi K2.7 Code", + "display_name": "Kimi K2.7 Code", "modalities": { "input": [ "text", - "image", - "video" + "image" ], "output": [ "text" ] }, "limit": { - "context": 256000, - "output": 16384 + "context": 262144, + "output": 262144 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -228975,35 +227140,38 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "none", - "low", - "medium", - "high" + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, "attachment": true, "open_weights": true, - "release_date": "2026-02-23", - "last_updated": "2026-02-23", + "knowledge": "2025-01", + "release_date": "2026-06-12", + "last_updated": "2026-06-12", "type": "chat" }, { - "id": "openai-gpt-56-terra", - "name": "GPT-5.6 Terra", - "display_name": "GPT-5.6 Terra", + "id": "gpt-6.1-sol", + "name": "GPT-6.1 Sol", + "display_name": "GPT-6.1 Sol", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1000000, + "context": 1050000, "output": 128000 }, "temperature": false, @@ -229015,7 +227183,6 @@ "extra_capabilities": { "reasoning": { "effort_options": [ - "none", "low", "medium", "high", @@ -229026,60 +227193,28 @@ }, "attachment": true, "open_weights": false, - "knowledge": "2026-02-16", - "release_date": "2026-07-09", - "last_updated": "2026-07-09", + "knowledge": "2026-04-30", + "release_date": "2026-09-29", + "last_updated": "2026-09-29", "type": "chat" }, { - "id": "qwen-3-7-plus", - "name": "Qwen 3.7 Plus", - "display_name": "Qwen 3.7 Plus", + "id": "grok-4-20-reasoning", + "name": "Grok 4.20 (Reasoning)", + "display_name": "Grok 4.20 (Reasoning)", "modalities": { "input": [ - "text", - "image", - "video" - ], - "output": [ "text" - ] - }, - "limit": { - "context": 1000000, - "output": 65536 - }, - "temperature": true, - "tool_call": true, - "reasoning": { - "supported": true, - "default": true - }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-04", - "release_date": "2026-06-02", - "last_updated": "2026-06-02", - "type": "chat" - }, - { - "id": "openai-gpt-6-astra-pro", - "name": "GPT-6 Astra Pro", - "display_name": "GPT-6 Astra Pro", - "modalities": { - "input": [ - "text", - "image" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 262000, + "output": 8192 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -229087,61 +227222,54 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" - ] + "supported": true } }, - "attachment": true, + "attachment": false, "open_weights": false, - "knowledge": "2026-04-30", - "release_date": "2026-09-04", - "last_updated": "2026-09-04", + "knowledge": "2025-09", + "release_date": "2026-04-08", + "last_updated": "2026-04-08", "type": "chat" }, { - "id": "qwen-3-6-plus", - "name": "Qwen 3.6 Plus Uncensored", - "display_name": "Qwen 3.6 Plus Uncensored", + "id": "gpt-4-turbo-vision", + "name": "GPT-4 Turbo Vision", + "display_name": "GPT-4 Turbo Vision", "modalities": { "input": [ "text", - "image", - "video" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 65536 + "context": 128000, + "output": 4096 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true + "supported": false }, "attachment": true, "open_weights": false, - "knowledge": "2025-04", - "release_date": "2026-04-02", - "last_updated": "2026-04-02", + "knowledge": "2023-11", + "release_date": "2023-11-06", + "last_updated": "2024-04-09", "type": "chat" }, { - "id": "claude-fable-5", - "name": "Claude Fable 5", - "display_name": "Claude Fable 5", + "id": "claude-opus-4-7", + "name": "Claude Opus 4.7", + "display_name": "Claude Opus 4.7", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" @@ -229155,12 +227283,12 @@ "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, + "default_enabled": false, "mode": "effort", "effort": "high", "effort_options": [ @@ -229177,117 +227305,127 @@ "thinking_blocks" ], "notes": [ - "Adaptive thinking is always on for Claude Fable 5 and Claude Mythos 5; thinking.type = \"disabled\" is rejected.", - "Manual budget_tokens requests return 400 on Claude Fable 5 and Claude Mythos 5.", - "thinking.display defaults to omitted; set display to summarized to receive readable thinking summaries." + "Claude Opus 4.7 and newer Opus models require thinking.type = \"adaptive\" to enable thinking explicitly.", + "Manual budget_tokens requests return 400 on Claude Opus 4.7 and newer adaptive-only Opus models.", + "task_budget is separate from thinking control and should not be treated as a thinking budget." ] } }, "attachment": true, "open_weights": false, "knowledge": "2026-01-31", - "release_date": "2026-06-09", - "last_updated": "2026-06-09", + "release_date": "2026-04-16", + "last_updated": "2026-04-16", "type": "chat" }, { - "id": "gemma-4-uncensored", - "name": "Gemma 4 Uncensored", - "display_name": "Gemma 4 Uncensored", + "id": "gpt-5-codex", + "name": "GPT-5-Codex", + "display_name": "GPT-5-Codex", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 256000, - "output": 8192 + "context": 400000, + "output": 128000 }, + "temperature": false, "tool_call": true, "reasoning": { - "supported": false - }, - "attachment": true, - "open_weights": true, - "release_date": "2026-04-13", - "last_updated": "2026-06-11", - "type": "chat" - }, - { - "id": "llama-3.3-70b", - "name": "Llama 3.3 70B", - "display_name": "Llama 3.3 70B", - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 4096 + "supported": true, + "default": true }, - "temperature": true, - "tool_call": true, - "reasoning": { - "supported": false + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", + "effort_options": [ + "minimal", + "low", + "medium", + "high" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" + } }, - "attachment": false, - "open_weights": true, - "knowledge": "2023-12", - "release_date": "2024-12-06", - "last_updated": "2024-12-06", + "attachment": true, + "open_weights": false, + "knowledge": "2024-09-30", + "release_date": "2025-09-15", + "last_updated": "2025-09-15", "type": "chat" }, { - "id": "kimi-k3-1-pro", - "name": "Kimi K3.1 Pro", - "display_name": "Kimi K3.1 Pro", + "id": "claude-haiku-4-5", + "name": "Claude Haiku 4.5", + "display_name": "Claude Haiku 4.5", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 200000, + "output": 64000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "none", - "low", - "high", - "max" + "supported": true, + "default_enabled": false, + "mode": "budget", + "budget": { + "min": 1024, + "unit": "tokens" + }, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", + "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." ] } }, "attachment": true, "open_weights": false, - "release_date": "2026-10-23", - "last_updated": "2026-10-23", + "knowledge": "2025-02-28", + "release_date": "2025-11-18", + "last_updated": "2025-11-18", "type": "chat" }, { - "id": "aion-labs-aion-3-0-mini", - "name": "Aion 3.0 Mini", - "display_name": "Aion 3.0 Mini", + "id": "phi-4-reasoning-plus", + "name": "Phi-4-reasoning-plus", + "display_name": "Phi-4-reasoning-plus", "modalities": { "input": [ "text" @@ -229297,38 +227435,39 @@ ] }, "limit": { - "context": 128000, - "output": 32768 + "context": 32000, + "output": 4096 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { "supported": true, "default": true }, "attachment": false, - "open_weights": false, - "release_date": "2026-05-14", - "last_updated": "2026-05-14", + "open_weights": true, + "knowledge": "2023-10", + "release_date": "2024-12-11", + "last_updated": "2024-12-11", "type": "chat" }, { - "id": "qwen-3-8-flash", - "name": "Qwen 3.8 Flash", - "display_name": "Qwen 3.8 Flash", + "id": "grok-4.7", + "name": "Grok 4.7", + "display_name": "Grok 4.7", "modalities": { "input": [ "text", "image", - "video" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 500000, + "output": 500000 }, "temperature": true, "tool_call": true, @@ -229336,20 +227475,32 @@ "supported": true, "default": true }, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "low", + "medium", + "high", + "xhigh" + ] + } + }, "attachment": true, "open_weights": false, - "release_date": "2026-08-26", - "last_updated": "2026-08-26", + "knowledge": "2026-05", + "release_date": "2026-09-21", + "last_updated": "2026-09-21", "type": "chat" }, { - "id": "openai-gpt-56-terra-pro", - "name": "GPT-5.6 Terra Pro", - "display_name": "GPT-5.6 Terra Pro", + "id": "claude-sonnet-5", + "name": "Claude Sonnet 5", + "display_name": "Claude Sonnet 5", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" @@ -229367,6 +227518,7 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, "effort_options": [ "low", "medium", @@ -229378,71 +227530,43 @@ }, "attachment": true, "open_weights": false, - "knowledge": "2026-02-16", - "release_date": "2026-07-09", - "last_updated": "2026-07-09", + "knowledge": "2026-01-31", + "release_date": "2026-06-30", + "last_updated": "2026-06-30", "type": "chat" }, { - "id": "claude-fable-5-1", - "name": "Claude Fable 5.1", - "display_name": "Claude Fable 5.1", + "id": "gpt-3.5-turbo-0125", + "name": "GPT-3.5 Turbo 0125", + "display_name": "GPT-3.5 Turbo 0125", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 16384, + "output": 4096 }, - "temperature": false, - "tool_call": true, + "temperature": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "high", - "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "omitted", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Adaptive thinking is always on for Claude Fable 5 and Claude Mythos 5; thinking.type = \"disabled\" is rejected.", - "Manual budget_tokens requests return 400 on Claude Fable 5 and Claude Mythos 5.", - "thinking.display defaults to omitted; set display to summarized to receive readable thinking summaries." - ] - } + "supported": false }, - "attachment": true, + "attachment": false, "open_weights": false, - "knowledge": "2026-06", - "release_date": "2026-09-01", - "last_updated": "2026-09-01", + "knowledge": "2021-08", + "release_date": "2024-01-25", + "last_updated": "2024-01-25", "type": "chat" }, { - "id": "openai-gpt-4o-mini-2024-07-18", - "name": "GPT-4o Mini", - "display_name": "GPT-4o Mini", + "id": "mistral-medium-2505", + "name": "Mistral Medium 3", + "display_name": "Mistral Medium 3", "modalities": { "input": [ "text", @@ -229454,7 +227578,7 @@ }, "limit": { "context": 128000, - "output": 16384 + "output": 128000 }, "temperature": true, "tool_call": true, @@ -229463,134 +227587,110 @@ }, "attachment": true, "open_weights": false, - "knowledge": "2023-09", - "release_date": "2024-07-18", - "last_updated": "2024-07-18", + "knowledge": "2025-05", + "release_date": "2025-05-07", + "last_updated": "2025-05-07", "type": "chat" }, { - "id": "qwen3-6-27b", - "name": "Qwen 3.6 27B", - "display_name": "Qwen 3.6 27B", + "id": "llama-4-scout-17b-16e-instruct", + "name": "Llama 4 Scout 17B 16E Instruct", + "display_name": "Llama 4 Scout 17B 16E Instruct", "modalities": { "input": [ "text", - "image", - "video" + "image" ], "output": [ "text" ] }, "limit": { - "context": 256000, - "output": 65536 + "context": 128000, + "output": 8192 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "none", - "low", - "medium", - "high" - ] - } + "supported": false }, "attachment": true, "open_weights": true, - "release_date": "2026-04-22", - "last_updated": "2026-04-22", + "knowledge": "2024-08", + "release_date": "2025-04-05", + "last_updated": "2025-04-05", "type": "chat" }, { - "id": "grok-4-20-multi-agent", - "name": "Grok 4.20 Multi-Agent", - "display_name": "Grok 4.20 Multi-Agent", + "id": "llama-3.3-70b-instruct", + "name": "Llama-3.3-70B-Instruct", + "display_name": "Llama-3.3-70B-Instruct", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 2000000, - "output": 128000 + "context": 128000, + "output": 32768 }, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { - "supported": true, - "default": true + "supported": false }, - "attachment": true, - "open_weights": false, - "release_date": "2026-03-12", - "last_updated": "2026-06-11", + "attachment": false, + "open_weights": true, + "knowledge": "2023-12", + "release_date": "2024-12-06", + "last_updated": "2024-12-06", "type": "chat" }, { - "id": "openai-gpt-54-pro", - "name": "GPT-5.4 Pro", - "display_name": "GPT-5.4 Pro", + "id": "text-embedding-3-large", + "name": "text-embedding-3-large", + "display_name": "text-embedding-3-large", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 8192, + "output": 3072 }, - "temperature": false, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "none", - "medium", - "high", - "xhigh" - ] - } + "supported": false }, - "attachment": true, + "attachment": false, "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-03-05", - "last_updated": "2026-03-05", - "type": "chat" + "release_date": "2024-01-25", + "last_updated": "2024-01-25", + "type": "embedding" }, { - "id": "openai-gpt-6-astra", - "name": "GPT-6 Astra", - "display_name": "GPT-6 Astra", + "id": "claude-fable-5", + "name": "Claude Fable 5", + "display_name": "Claude Fable 5", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1050000, + "context": 1000000, "output": 128000 }, "temperature": false, @@ -229601,115 +227701,103 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "high", "effort_options": [ "low", "medium", "high", "xhigh", "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "omitted", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Adaptive thinking is always on for Claude Fable 5 and Claude Mythos 5; thinking.type = \"disabled\" is rejected.", + "Manual budget_tokens requests return 400 on Claude Fable 5 and Claude Mythos 5.", + "thinking.display defaults to omitted; set display to summarized to receive readable thinking summaries." ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-04-30", - "release_date": "2026-09-04", - "last_updated": "2026-09-04", + "knowledge": "2026-01-31", + "release_date": "2026-06-09", + "last_updated": "2026-06-09", "type": "chat" }, { - "id": "grok-4-5", - "name": "Grok 4.5", - "display_name": "Grok 4.5", + "id": "gpt-4.1-nano", + "name": "GPT-4.1 nano", + "display_name": "GPT-4.1 nano", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 500000, - "output": 32000 + "context": 1047576, + "output": 32768 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "low", - "medium", - "high" - ] - } + "supported": false }, "attachment": true, "open_weights": false, - "release_date": "2026-07-08", - "last_updated": "2026-07-08", + "knowledge": "2024-04", + "release_date": "2025-04-14", + "last_updated": "2025-04-14", "type": "chat" }, { - "id": "kimi-k2-6", - "name": "Kimi K2.6", - "display_name": "Kimi K2.6", + "id": "gpt-image-2.5-flare", + "name": "GPT Image 2.5 Flare", + "display_name": "GPT Image 2.5 Flare", "modalities": { "input": [ "text", "image" ], "output": [ - "text" + "image" ] }, "limit": { - "context": 256000, - "output": 65536 + "context": 8192, + "output": 8192 }, - "temperature": true, - "tool_call": true, + "temperature": false, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "effort_options": [ - "none", - "low", - "medium", - "high" - ] - } + "supported": false }, "attachment": true, - "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-04-20", - "last_updated": "2026-04-20", - "type": "chat" + "open_weights": false, + "release_date": "2026-09-08", + "last_updated": "2026-09-08" }, { - "id": "openai-gpt-53-codex", - "name": "GPT-5.3 Codex", - "display_name": "GPT-5.3 Codex", + "id": "gpt-5-nano", + "name": "GPT-5 Nano", + "display_name": "GPT-5 Nano", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" @@ -229719,7 +227807,7 @@ "context": 400000, "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -229727,74 +227815,88 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", "effort_options": [ - "none", + "minimal", "low", "medium", - "high", - "xhigh" - ] + "high" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, "attachment": true, "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-02-05", - "last_updated": "2026-02-05", + "knowledge": "2024-05-30", + "release_date": "2025-08-07", + "last_updated": "2025-08-07", "type": "chat" - } - ] - }, - "subconscious": { - "id": "subconscious", - "name": "Subconscious", - "display_name": "Subconscious", - "api": "https://api.subconscious.dev/v1", - "doc": "https://docs.subconscious.dev", - "models": [ + }, { - "id": "subconscious/deepseek-v4.1-flash-marathon", - "name": "DeepSeek V4.1 Flash Marathon", - "display_name": "DeepSeek V4.1 Flash Marathon", + "id": "claude-sonnet-4-5", + "name": "Claude Sonnet 4.5", + "display_name": "Claude Sonnet 4.5", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 5000000, - "output": 384000 + "context": 200000, + "output": 64000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "low", - "high", - "max" + "supported": true, + "default_enabled": false, + "mode": "budget", + "budget": { + "min": 1024, + "unit": "tokens" + }, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", + "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." ] } }, "attachment": true, - "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-09-10", - "last_updated": "2026-09-10", + "open_weights": false, + "knowledge": "2025-07-31", + "release_date": "2025-11-18", + "last_updated": "2025-11-18", "type": "chat" }, { - "id": "subconscious/glm-5.3-marathon", - "name": "GLM-5.3 Marathon", - "display_name": "GLM-5.3 Marathon", + "id": "text-embedding-ada-002", + "name": "text-embedding-ada-002", + "display_name": "text-embedding-ada-002", "modalities": { "input": [ "text" @@ -229804,10 +227906,38 @@ ] }, "limit": { - "context": 5000000, - "output": 131072 + "context": 8192, + "output": 1536 }, - "temperature": true, + "tool_call": false, + "reasoning": { + "supported": false + }, + "attachment": false, + "open_weights": false, + "release_date": "2022-12-15", + "last_updated": "2022-12-15", + "type": "embedding" + }, + { + "id": "gpt-chat-latest", + "name": "GPT Chat Latest", + "display_name": "GPT Chat Latest", + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 400000, + "output": 128000 + }, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -229815,47 +227945,34 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, "effort_options": [ - "low", - "high", - "max" + "medium" ] } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-08-14", - "last_updated": "2026-08-14", + "attachment": true, + "open_weights": false, + "knowledge": "2025-12-01", + "release_date": "2026-05-05", + "last_updated": "2026-05-28", "type": "chat" - } - ] - }, - "opper": { - "id": "opper", - "name": "Opper", - "display_name": "Opper", - "api": "https://api.opper.ai/v3/compat", - "doc": "https://opper.ai/models", - "models": [ + }, { - "id": "gemini-3-flash-preview", - "name": "Gemini 3 Flash Preview", - "display_name": "Gemini 3 Flash Preview", + "id": "deepseek-v3.2", + "name": "DeepSeek-V3.2", + "display_name": "DeepSeek-V3.2", "modalities": { "input": [ - "text", - "image", - "video", - "audio", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 128000, + "output": 128000 }, "temperature": true, "tool_call": true, @@ -229865,35 +227982,20 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "level", - "level": "high", - "level_options": [ - "minimal", - "low", - "medium", - "high" - ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] + "supported": true } }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-01", - "release_date": "2025-12-17", - "last_updated": "2025-12-17", + "attachment": false, + "open_weights": true, + "knowledge": "2024-07", + "release_date": "2025-12-01", + "last_updated": "2025-12-01", "type": "chat" }, { - "id": "gpt-5.5", - "name": "GPT-5.5", - "display_name": "GPT-5.5", + "id": "claude-opus-5-5", + "name": "Claude Opus 5.5", + "display_name": "Claude Opus 5.5", "modalities": { "input": [ "text", @@ -229905,7 +228007,7 @@ ] }, "limit": { - "context": 1050000, + "context": 1000000, "output": 128000 }, "temperature": false, @@ -229916,41 +228018,26 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", "effort_options": [ "low", "medium", "high", - "xhigh" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "xhigh", + "max" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-12-01", - "release_date": "2026-04-23", - "last_updated": "2026-04-23", + "knowledge": "2026-06", + "release_date": "2026-09-22", + "last_updated": "2026-09-22", "type": "chat" }, { - "id": "sonar-pro", - "name": "Sonar Pro", - "display_name": "Sonar Pro", + "id": "gpt-3.5-turbo-instruct", + "name": "GPT-3.5 Turbo Instruct", + "display_name": "GPT-3.5 Turbo Instruct", "modalities": { "input": [ "text" @@ -229960,8 +228047,8 @@ ] }, "limit": { - "context": 200000, - "output": 8000 + "context": 4096, + "output": 4096 }, "temperature": true, "tool_call": false, @@ -229970,19 +228057,20 @@ }, "attachment": false, "open_weights": false, - "knowledge": "2025-09-01", - "release_date": "2024-01-01", - "last_updated": "2025-09-01", + "knowledge": "2021-08", + "release_date": "2023-09-21", + "last_updated": "2023-09-21", "type": "chat" }, { - "id": "gpt-6-astra", - "name": "GPT-6 Astra", - "display_name": "GPT-6 Astra", + "id": "gpt-5.6-luna", + "name": "GPT-5.6 Luna", + "display_name": "GPT-5.6 Luna", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" @@ -230001,32 +228089,37 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], + "default_enabled": true, + "mode": "effort", + "effort": "medium", "effort_options": [ + "none", "low", "medium", "high", "xhigh", "max" - ] + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, "attachment": true, "open_weights": false, - "knowledge": "2026-04-30", - "release_date": "2026-09-04", - "last_updated": "2026-09-04", + "knowledge": "2026-02-16", + "release_date": "2026-07-09", + "last_updated": "2026-07-09", "type": "chat" }, { - "id": "claude-opus-4-5", - "name": "Claude Opus 4.5 (latest)", - "display_name": "Claude Opus 4.5 (latest)", + "id": "o4-mini", + "name": "o4-mini", + "display_name": "o4-mini", "modalities": { "input": [ "text", @@ -230039,52 +228132,39 @@ }, "limit": { "context": 200000, - "output": 64000 + "output": 100000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "mixed", - "budget": { - "min": 1024, - "unit": "tokens" - }, - "effort": "high", + "default_enabled": true, + "mode": "effort", + "effort": "medium", "effort_options": [ "low", "medium", "high" ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Claude Opus 4.5 uses manual thinking.type = \"enabled\" with budget_tokens; effort can be used alongside the thinking budget.", - "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header." - ] + "visibility": "hidden" } }, "attachment": true, "open_weights": false, - "knowledge": "2025-05", - "release_date": "2025-11-24", - "last_updated": "2025-11-24", + "knowledge": "2024-05", + "release_date": "2025-04-16", + "last_updated": "2025-04-16", "type": "chat" }, { - "id": "gpt-5.4", - "name": "GPT-5.4", - "display_name": "GPT-5.4", + "id": "claude-opus-4-5", + "name": "Claude Opus 4.5", + "display_name": "Claude Opus 4.5", "modalities": { "input": [ "text", @@ -230096,8 +228176,8 @@ ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 200000, + "output": 64000 }, "temperature": true, "tool_call": true, @@ -230109,17 +228189,13 @@ "reasoning": { "supported": true, "default_enabled": false, - "mode": "effort", - "effort": "none", + "mode": "mixed", + "budget": { + "min": 1024, + "unit": "tokens" + }, + "effort": "high", "effort_options": [ - "none", - "low", - "medium", - "high", - "xhigh" - ], - "verbosity": "medium", - "verbosity_options": [ "low", "medium", "high" @@ -230129,64 +228205,24 @@ "visibility": "summary", "continuation": [ "thinking_blocks" + ], + "notes": [ + "Claude Opus 4.5 uses manual thinking.type = \"enabled\" with budget_tokens; effort can be used alongside the thinking budget.", + "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header." ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-03-05", - "last_updated": "2026-03-05", - "type": "chat" - }, - { - "id": "gemini-3.5-flash-lite", - "name": "Gemini 3.5 Flash Lite", - "display_name": "Gemini 3.5 Flash Lite", - "modalities": { - "input": [ - "text", - "image", - "video", - "audio", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 1048576, - "output": 65536 - }, - "temperature": true, - "tool_call": true, - "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } - }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-03", - "release_date": "2026-07-21", - "last_updated": "2026-07-21", + "knowledge": "2025-03-31", + "release_date": "2025-11-24", + "last_updated": "2025-08-01", "type": "chat" }, { - "id": "glm-5.3-flash", - "name": "GLM-5.3-Flash", - "display_name": "GLM-5.3-Flash", + "id": "codestral-2501", + "name": "Codestral 25.01", + "display_name": "Codestral 25.01", "modalities": { "input": [ "text" @@ -230196,36 +228232,25 @@ ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 256000, + "output": 256000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } + "supported": false }, "attachment": false, - "open_weights": true, - "release_date": "2026-08-26", - "last_updated": "2026-08-26", + "open_weights": false, + "knowledge": "2024-03", + "release_date": "2025-01-01", + "last_updated": "2025-01-01", "type": "chat" }, { - "id": "qwen3.8-27b", - "name": "Qwen3.8 27B", - "display_name": "Qwen3.8 27B", + "id": "gpt-3.5-turbo-1106", + "name": "GPT-3.5 Turbo 1106", + "display_name": "GPT-3.5 Turbo 1106", "modalities": { "input": [ "text" @@ -230235,54 +228260,40 @@ ] }, "limit": { - "context": 262144, - "output": 32768 + "context": 16384, + "output": 4096 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "effort_options": [ - "low", - "medium", - "xhigh" - ] - } + "supported": false }, "attachment": false, - "open_weights": true, - "release_date": "2026-08-14", - "last_updated": "2026-08-14", + "open_weights": false, + "knowledge": "2021-08", + "release_date": "2023-11-06", + "last_updated": "2023-11-06", "type": "chat" }, { - "id": "deepseek-v4-flash", - "name": "DeepSeek V4 Flash", - "display_name": "DeepSeek V4 Flash", + "id": "gpt-5", + "name": "GPT-5", + "display_name": "GPT-5", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 384000 + "context": 400000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -230293,34 +228304,33 @@ "supported": true, "default_enabled": true, "mode": "effort", - "effort": "high", + "effort": "medium", "effort_options": [ + "minimal", "low", - "high", - "max" + "medium", + "high" ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" ], - "notes": [ - "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." - ] + "visibility": "hidden" } }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-04-24", - "last_updated": "2026-04-24", + "attachment": true, + "open_weights": false, + "knowledge": "2024-09-30", + "release_date": "2025-08-07", + "last_updated": "2025-08-07", "type": "chat" }, { - "id": "gpt-oss-20b", - "name": "GPT OSS 20B", - "display_name": "GPT OSS 20B", + "id": "phi-4-mini-reasoning", + "name": "Phi-4-mini-reasoning", + "display_name": "Phi-4-mini-reasoning", "modalities": { "input": [ "text" @@ -230331,7 +228341,7 @@ }, "limit": { "context": 128000, - "output": 8192 + "output": 4096 }, "temperature": true, "tool_call": true, @@ -230339,68 +228349,30 @@ "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } - }, "attachment": false, "open_weights": true, - "knowledge": "2024-06", - "release_date": "2025-08-05", - "last_updated": "2025-08-05", + "knowledge": "2023-10", + "release_date": "2024-12-11", + "last_updated": "2024-12-11", "type": "chat" }, { - "id": "mistral-large-2512", - "name": "Mistral Large 3", - "display_name": "Mistral Large 3", + "id": "gpt-5-mini", + "name": "GPT-5 Mini", + "display_name": "GPT-5 Mini", "modalities": { "input": [ "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 256000, - "output": 8192 - }, - "temperature": true, - "tool_call": true, - "reasoning": { - "supported": false - }, - "attachment": true, - "open_weights": true, - "knowledge": "2024-11", - "release_date": "2025-12-02", - "last_updated": "2025-12-02", - "type": "chat" - }, - { - "id": "kimi-k3", - "name": "Kimi K3", - "display_name": "Kimi K3", - "modalities": { - "input": [ - "text" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 400000, + "output": 128000 }, "temperature": false, "tool_call": true, @@ -230411,24 +228383,35 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] + "default_enabled": true, + "mode": "effort", + "effort": "medium", + "effort_options": [ + "minimal", + "low", + "medium", + "high" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-07-16", - "last_updated": "2026-07-16", + "attachment": true, + "open_weights": false, + "knowledge": "2024-05-30", + "release_date": "2025-08-07", + "last_updated": "2025-08-07", "type": "chat" }, { - "id": "deepseek-v4-pro", - "name": "DeepSeek V4 Pro", - "display_name": "DeepSeek V4 Pro", + "id": "cohere-command-a", + "name": "Command A", + "display_name": "Command A", "modalities": { "input": [ "text" @@ -230438,8 +228421,8 @@ ] }, "limit": { - "context": 1000000, - "output": 65536 + "context": 131072, + "output": 8192 }, "temperature": true, "tool_call": true, @@ -230447,92 +228430,55 @@ "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "high", - "effort_options": [ - "low", - "high", - "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." - ] - } - }, "attachment": false, "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-04-24", - "last_updated": "2026-04-24", + "knowledge": "2024-06-01", + "release_date": "2025-03-13", + "last_updated": "2025-03-13", "type": "chat" }, { - "id": "claude-opus-5", - "name": "Claude Opus 5", - "display_name": "Claude Opus 5", + "id": "text-embedding-3-small", + "name": "text-embedding-3-small", + "display_name": "text-embedding-3-small", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 8192, + "output": 1536 }, - "temperature": false, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } + "supported": false }, - "attachment": true, + "attachment": false, "open_weights": false, - "knowledge": "2026-05", - "release_date": "2026-07-24", - "last_updated": "2026-07-24", - "type": "chat" + "release_date": "2024-01-25", + "last_updated": "2024-01-25", + "type": "embedding" }, { - "id": "qwen3.8-2.4t-a95b", - "name": "Qwen3.8 2.4T A95B", - "display_name": "Qwen3.8 2.4T A95B", + "id": "deepseek-v4.1-flash", + "name": "DeepSeek-V4.1-Flash", + "display_name": "DeepSeek-V4.1-Flash", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 131072 + "context": 1000000, + "output": 128000 }, "temperature": true, "tool_call": true, @@ -230548,60 +228494,56 @@ "visibility": "summary", "continuation": [ "thinking_blocks" + ], + "effort_options": [ + "none", + "low", + "high", + "max" ] } }, - "attachment": false, + "attachment": true, "open_weights": true, - "release_date": "2026-08-12", - "last_updated": "2026-08-12", + "knowledge": "2025-05", + "release_date": "2026-09-10", + "last_updated": "2026-09-10", "type": "chat" }, { - "id": "gemma-4-31b-it", - "name": "Gemma 4 31B IT", - "display_name": "Gemma 4 31B IT", + "id": "gpt-4o-mini", + "name": "GPT-4o mini", + "display_name": "GPT-4o mini", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 256000, - "output": 8192 + "context": 128000, + "output": 16384 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } + "supported": false }, "attachment": true, - "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-04-02", - "last_updated": "2026-04-02", + "open_weights": false, + "knowledge": "2023-09", + "release_date": "2024-07-18", + "last_updated": "2024-07-18", "type": "chat" }, { - "id": "claude-opus-4-7", - "name": "Claude Opus 4.7", - "display_name": "Claude Opus 4.7", + "id": "o1", + "name": "o1", + "display_name": "o1", "modalities": { "input": [ "text", @@ -230613,52 +228555,40 @@ ] }, "limit": { - "context": 1000000, - "output": 64000 + "context": 200000, + "output": 100000 }, "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, + "default_enabled": true, "mode": "effort", - "effort": "high", + "effort": "medium", "effort_options": [ "low", "medium", - "high", - "xhigh", - "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "high" ], - "notes": [ - "Claude Opus 4.7 and newer Opus models require thinking.type = \"adaptive\" to enable thinking explicitly.", - "Manual budget_tokens requests return 400 on Claude Opus 4.7 and newer adaptive-only Opus models.", - "task_budget is separate from thinking control and should not be treated as a thinking budget." - ] + "visibility": "hidden" } }, "attachment": true, "open_weights": false, - "knowledge": "2026-01-31", - "release_date": "2026-04-16", - "last_updated": "2026-04-16", + "knowledge": "2023-09", + "release_date": "2024-12-05", + "last_updated": "2024-12-05", "type": "chat" }, { - "id": "qwen3-coder-next", - "name": "Qwen3 Coder Next", - "display_name": "Qwen3 Coder Next", + "id": "ministral-3b", + "name": "Ministral 3B", + "display_name": "Ministral 3B", "modalities": { "input": [ "text" @@ -230668,8 +228598,8 @@ ] }, "limit": { - "context": 262144, - "output": 65536 + "context": 128000, + "output": 8192 }, "temperature": true, "tool_call": true, @@ -230678,95 +228608,74 @@ }, "attachment": false, "open_weights": true, - "knowledge": "2025-09", - "release_date": "2026-02-03", - "last_updated": "2026-02-03", + "knowledge": "2024-03", + "release_date": "2024-10-22", + "last_updated": "2024-10-22", "type": "chat" }, { - "id": "grok-4.5", - "name": "Grok 4.5", - "display_name": "Grok 4.5", + "id": "gpt-4o", + "name": "GPT-4o", + "display_name": "GPT-4o", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 500000, - "output": 500000 + "context": 128000, + "output": 16384 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } + "supported": false }, "attachment": true, "open_weights": false, - "release_date": "2026-07-08", - "last_updated": "2026-07-08", + "knowledge": "2023-09", + "release_date": "2024-05-13", + "last_updated": "2024-08-06", "type": "chat" }, { - "id": "minimax-m3", - "name": "MiniMax-M3", - "display_name": "MiniMax-M3", + "id": "phi-4-reasoning", + "name": "Phi-4-reasoning", + "display_name": "Phi-4-reasoning", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 32000, + "output": 4096 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } - }, - "attachment": true, + "attachment": false, "open_weights": true, - "release_date": "2026-06-01", - "last_updated": "2026-06-01", + "knowledge": "2023-10", + "release_date": "2024-12-11", + "last_updated": "2024-12-11", "type": "chat" }, { - "id": "claude-sonnet-5", - "name": "Claude Sonnet 5", - "display_name": "Claude Sonnet 5", + "id": "codex-mini", + "name": "Codex Mini", + "display_name": "Codex Mini", "modalities": { "input": [ "text", @@ -230778,8 +228687,8 @@ ] }, "limit": { - "context": 1000000, - "output": 64000 + "context": 200000, + "output": 100000 }, "temperature": false, "tool_call": true, @@ -230789,77 +228698,24 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } - }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-01-31", - "release_date": "2026-06-30", - "last_updated": "2026-06-30", - "type": "chat" - }, - { - "id": "muse-spark-1.3", - "name": "Muse Spark 1.3", - "display_name": "Muse Spark 1.3", - "modalities": { - "input": [ - "text", - "image", - "video", - "pdf", - "audio" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 1048576, - "output": 131072 - }, - "temperature": true, - "tool_call": true, - "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], "effort_options": [ - "minimal", "low", "medium", - "high", - "xhigh", - "max" + "high" ] } }, "attachment": true, "open_weights": false, - "release_date": "2026-09-02", - "last_updated": "2026-09-02", + "knowledge": "2024-04", + "release_date": "2025-05-16", + "last_updated": "2025-05-16", "type": "chat" }, { - "id": "minimax-m2.7", - "name": "MiniMax-M2.7", - "display_name": "MiniMax-M2.7", + "id": "deepseek-v4-pro", + "name": "DeepSeek-V4-Pro", + "display_name": "DeepSeek-V4-Pro", "modalities": { "input": [ "text" @@ -230869,8 +228725,8 @@ ] }, "limit": { - "context": 196608, - "output": 131072 + "context": 1000000, + "output": 384000 }, "temperature": true, "tool_call": true, @@ -230881,30 +228737,40 @@ "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "high", + "effort_options": [ + "low", + "high", + "max" + ], "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ "thinking_blocks" + ], + "notes": [ + "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." ] } }, "attachment": false, "open_weights": true, - "release_date": "2026-03-18", - "last_updated": "2026-03-18", + "knowledge": "2025-05", + "release_date": "2026-04-24", + "last_updated": "2026-04-24", "type": "chat" }, { - "id": "gemini-3.7-flash", - "name": "Gemini 3.7 Flash", - "display_name": "Gemini 3.7 Flash", + "id": "claude-sonnet-5-5", + "name": "Claude Sonnet 5.5", + "display_name": "Claude Sonnet 5.5", "modalities": { "input": [ "text", "image", - "video", - "audio", "pdf" ], "output": [ @@ -230912,10 +228778,10 @@ ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 1000000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -230923,29 +228789,20 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "level", - "level": "high", - "level_options": [ - "minimal", + "effort_options": [ "low", "medium", - "high" - ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "high", + "xhigh", + "max" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-03", - "release_date": "2026-08-13", - "last_updated": "2026-08-13", + "knowledge": "2026-06", + "release_date": "2026-09-28", + "last_updated": "2026-09-28", "type": "chat" }, { @@ -230963,7 +228820,7 @@ ] }, "limit": { - "context": 1000000, + "context": 1050000, "output": 128000 }, "temperature": false, @@ -230992,12 +228849,7 @@ "medium", "high" ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] + "visibility": "hidden" } }, "attachment": true, @@ -231067,9 +228919,61 @@ "type": "chat" }, { - "id": "qwen3.8-max", - "name": "Qwen3.8 Max", - "display_name": "Qwen3.8 Max", + "id": "gpt-5.1", + "name": "GPT-5.1", + "display_name": "GPT-5.1", + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 400000, + "output": 128000 + }, + "temperature": false, + "tool_call": true, + "reasoning": { + "supported": true, + "default": false + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": false, + "mode": "effort", + "effort": "none", + "effort_options": [ + "none", + "low", + "medium", + "high" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" + } + }, + "attachment": true, + "open_weights": false, + "knowledge": "2024-09-30", + "release_date": "2025-11-14", + "last_updated": "2025-11-14", + "type": "chat" + }, + { + "id": "grok-4-1-fast-reasoning", + "name": "Grok 4.1 Fast (Reasoning)", + "display_name": "Grok 4.1 Fast (Reasoning)", "modalities": { "input": [ "text", @@ -231080,8 +228984,8 @@ ] }, "limit": { - "context": 983616, - "output": 131072 + "context": 128000, + "output": 8192 }, "temperature": true, "tool_call": true, @@ -231091,25 +228995,19 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] + "supported": true } }, "attachment": true, "open_weights": false, - "release_date": "2026-08-03", - "last_updated": "2026-08-03", + "release_date": "2025-06-27", + "last_updated": "2025-06-27", "type": "chat" }, { - "id": "gpt-5.6-terra", - "name": "GPT-5.6 Terra", - "display_name": "GPT-5.6 Terra", + "id": "claude-opus-4-8", + "name": "Claude Opus 4.8", + "display_name": "Claude Opus 4.8", "modalities": { "input": [ "text", @@ -231128,47 +229026,45 @@ "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, + "default_enabled": false, "mode": "effort", - "effort": "medium", + "effort": "high", "effort_options": [ - "none", "low", "medium", "high", "xhigh", "max" ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], "interleaved": true, "summaries": true, - "visibility": "summary", + "visibility": "omitted", "continuation": [ "thinking_blocks" + ], + "notes": [ + "Claude Opus 4.7 and newer Opus models require thinking.type = \"adaptive\" to enable thinking explicitly.", + "Manual budget_tokens requests return 400 on Claude Opus 4.7 and newer adaptive-only Opus models.", + "task_budget is separate from thinking control and should not be treated as a thinking budget." ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-02-16", - "release_date": "2026-07-09", - "last_updated": "2026-07-09", + "knowledge": "2025-12-31", + "release_date": "2026-05-28", + "last_updated": "2026-05-28", "type": "chat" }, { - "id": "gpt-oss-120b", - "name": "GPT OSS 120B", - "display_name": "GPT OSS 120B", + "id": "cohere-embed-v3-english", + "name": "Embed v3 English", + "display_name": "Embed v3 English", "modalities": { "input": [ "text" @@ -231178,50 +229074,62 @@ ] }, "limit": { - "context": 128000, - "output": 8192 + "context": 512, + "output": 1024 }, - "temperature": true, - "tool_call": true, + "temperature": false, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } + "supported": false }, "attachment": false, "open_weights": true, - "knowledge": "2024-06", - "release_date": "2025-08-05", - "last_updated": "2025-08-05", - "type": "chat" + "release_date": "2023-11-07", + "last_updated": "2023-11-07", + "type": "embedding" }, { - "id": "grok-build-0.1", - "name": "Grok Build 0.1", - "display_name": "Grok Build 0.1", + "id": "model-router", + "name": "Model Router", + "display_name": "Model Router", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 256000, - "output": 256000 + "context": 200000, + "output": 16384 + }, + "tool_call": true, + "reasoning": { + "supported": false + }, + "attachment": true, + "open_weights": false, + "release_date": "2025-05-19", + "last_updated": "2025-11-18", + "type": "chat" + }, + { + "id": "deepseek-v4-flash", + "name": "DeepSeek-V4-Flash", + "display_name": "DeepSeek-V4-Flash", + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1000000, + "output": 384000 }, "temperature": true, "tool_call": true, @@ -231232,28 +229140,41 @@ "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "high", + "effort_options": [ + "low", + "high", + "max" + ], "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ "thinking_blocks" + ], + "notes": [ + "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." ] } }, - "attachment": true, - "open_weights": false, - "release_date": "2026-04-16", - "last_updated": "2026-04-16", + "attachment": false, + "open_weights": true, + "knowledge": "2025-05", + "release_date": "2026-04-24", + "last_updated": "2026-04-24", "type": "chat" }, { - "id": "gpt-5.4-pro", - "name": "GPT-5.4 Pro", - "display_name": "GPT-5.4 Pro", + "id": "gpt-5.5", + "name": "GPT-5.5", + "display_name": "GPT-5.5", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" @@ -231274,8 +229195,9 @@ "supported": true, "default_enabled": true, "mode": "effort", - "effort": "high", + "effort": "medium", "effort_options": [ + "low", "medium", "high", "xhigh" @@ -231286,19 +229208,14 @@ "medium", "high" ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] + "visibility": "hidden" } }, "attachment": true, "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-03-05", - "last_updated": "2026-03-05", + "knowledge": "2025-12-01", + "release_date": "2026-04-24", + "last_updated": "2026-04-24", "type": "chat" }, { @@ -231357,13 +229274,13 @@ "open_weights": false, "knowledge": "2025-05-31", "release_date": "2026-02-05", - "last_updated": "2026-03-13", + "last_updated": "2026-07-17", "type": "chat" }, { - "id": "gpt-5.6-luna", - "name": "GPT-5.6 Luna", - "display_name": "GPT-5.6 Luna", + "id": "gpt-5.1-codex-mini", + "name": "GPT-5.1 Codex Mini", + "display_name": "GPT-5.1 Codex Mini", "modalities": { "input": [ "text", @@ -231375,28 +229292,26 @@ ] }, "limit": { - "context": 1000000, + "context": 400000, "output": 128000 }, "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, + "default_enabled": false, "mode": "effort", - "effort": "medium", + "effort": "none", "effort_options": [ "none", "low", "medium", - "high", - "xhigh", - "max" + "high" ], "verbosity": "medium", "verbosity_options": [ @@ -231404,25 +229319,20 @@ "medium", "high" ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] + "visibility": "hidden" } }, "attachment": true, "open_weights": false, - "knowledge": "2026-02-16", - "release_date": "2026-07-09", - "last_updated": "2026-07-09", + "knowledge": "2024-09-30", + "release_date": "2025-11-14", + "last_updated": "2025-11-14", "type": "chat" }, { - "id": "gpt-5.4-mini", - "name": "GPT-5.4 mini", - "display_name": "GPT-5.4 mini", + "id": "llama-4-maverick-17b-128e-instruct-fp8", + "name": "Llama 4 Maverick 17B 128E Instruct FP8", + "display_name": "Llama 4 Maverick 17B 128E Instruct FP8", "modalities": { "input": [ "text", @@ -231433,97 +229343,113 @@ ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 1000000, + "output": 16384 }, "temperature": true, "tool_call": true, + "reasoning": { + "supported": false + }, + "attachment": true, + "open_weights": true, + "knowledge": "2024-08", + "release_date": "2025-04-05", + "last_updated": "2025-04-05", + "type": "chat" + }, + { + "id": "claude-fable-5-1", + "name": "Claude Fable 5.1", + "display_name": "Claude Fable 5.1", + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1000000, + "output": 128000 + }, + "temperature": false, + "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, + "default_enabled": true, "mode": "effort", - "effort": "none", + "effort": "high", "effort_options": [ - "none", "low", "medium", "high", - "xhigh" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" + "xhigh", + "max" ], "interleaved": true, "summaries": true, - "visibility": "summary", + "visibility": "omitted", "continuation": [ "thinking_blocks" + ], + "notes": [ + "Adaptive thinking is always on for Claude Fable 5 and Claude Mythos 5; thinking.type = \"disabled\" is rejected.", + "Manual budget_tokens requests return 400 on Claude Fable 5 and Claude Mythos 5.", + "thinking.display defaults to omitted; set display to summarized to receive readable thinking summaries." ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-03-17", - "last_updated": "2026-03-17", + "knowledge": "2026-06", + "release_date": "2026-09-01", + "last_updated": "2026-09-01", "type": "chat" }, { - "id": "mistral-small-2603", - "name": "Mistral Small 4", - "display_name": "Mistral Small 4", + "id": "gpt-image-1", + "name": "GPT-Image-1", + "display_name": "GPT-Image-1", "modalities": { "input": [ "text", "image" ], "output": [ - "text" + "image" ] }, "limit": { - "context": 256000, + "context": 8192, "output": 8192 }, - "temperature": true, - "tool_call": true, + "temperature": false, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } + "supported": false }, "attachment": true, - "open_weights": true, - "knowledge": "2025-06", - "release_date": "2026-03-16", - "last_updated": "2026-03-16", - "type": "chat" + "open_weights": false, + "release_date": "2025-04-24", + "last_updated": "2025-04-24" }, { - "id": "sonar", - "name": "Sonar", - "display_name": "Sonar", + "id": "grok-4-1-fast-non-reasoning", + "name": "Grok 4.1 Fast (Non-Reasoning)", + "display_name": "Grok 4.1 Fast (Non-Reasoning)", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" @@ -231531,24 +229457,23 @@ }, "limit": { "context": 128000, - "output": 4096 + "output": 8192 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { "supported": false }, - "attachment": false, + "attachment": true, "open_weights": false, - "knowledge": "2025-09-01", - "release_date": "2024-01-01", - "last_updated": "2025-09-01", + "release_date": "2025-06-27", + "last_updated": "2025-06-27", "type": "chat" }, { - "id": "qwen3.6-35b-a3b", - "name": "Qwen3.6 35B-A3B", - "display_name": "Qwen3.6 35B-A3B", + "id": "gpt-4-turbo", + "name": "GPT-4 Turbo", + "display_name": "GPT-4 Turbo", "modalities": { "input": [ "text", @@ -231559,39 +229484,29 @@ ] }, "limit": { - "context": 262144, - "output": 65536 + "context": 128000, + "output": 4096 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } + "supported": false }, "attachment": true, - "open_weights": true, - "release_date": "2026-04-17", - "last_updated": "2026-04-17", + "open_weights": false, + "knowledge": "2023-12", + "release_date": "2023-11-06", + "last_updated": "2024-04-09", "type": "chat" }, { - "id": "gpt-5.3-codex", - "name": "GPT-5.3 Codex", - "display_name": "GPT-5.3 Codex", + "id": "claude-opus-5", + "name": "Claude Opus 5", + "display_name": "Claude Opus 5", "modalities": { "input": [ "text", + "image", "pdf" ], "output": [ @@ -231599,7 +229514,7 @@ ] }, "limit": { - "context": 400000, + "context": 1000000, "output": 128000 }, "temperature": false, @@ -231611,46 +229526,30 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", "effort_options": [ "low", "medium", "high", - "xhigh" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "xhigh", + "max" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-02-05", - "last_updated": "2026-02-05", + "knowledge": "2026-05", + "release_date": "2026-07-24", + "last_updated": "2026-07-24", "type": "chat" }, { - "id": "gemini-3.1-pro-preview", - "name": "Gemini 3.1 Pro Preview", - "display_name": "Gemini 3.1 Pro Preview", + "id": "claude-mythos-5", + "name": "Claude Mythos 5", + "display_name": "Claude Mythos 5", "modalities": { "input": [ "text", "image", - "video", - "audio", "pdf" ], "output": [ @@ -231658,10 +229557,10 @@ ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 1000000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -231671,31 +229570,39 @@ "reasoning": { "supported": true, "default_enabled": true, - "mode": "level", - "level": "high", - "level_options": [ + "mode": "effort", + "effort": "high", + "effort_options": [ "low", - "high" + "medium", + "high", + "xhigh", + "max" ], "interleaved": true, "summaries": true, - "visibility": "summary", + "visibility": "omitted", "continuation": [ "thinking_blocks" + ], + "notes": [ + "Adaptive thinking is always on for Claude Fable 5 and Claude Mythos 5; thinking.type = \"disabled\" is rejected.", + "Manual budget_tokens requests return 400 on Claude Fable 5 and Claude Mythos 5.", + "thinking.display defaults to omitted; set display to summarized to receive readable thinking summaries." ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-01", - "release_date": "2026-02-19", - "last_updated": "2026-02-19", + "knowledge": "2026-01-31", + "release_date": "2026-06-09", + "last_updated": "2026-06-09", "type": "chat" }, { - "id": "claude-haiku-4-5", - "name": "Claude Haiku 4.5 (latest)", - "display_name": "Claude Haiku 4.5 (latest)", + "id": "grok-4.6", + "name": "Grok 4.6", + "display_name": "Grok 4.6", "modalities": { "input": [ "text", @@ -231707,240 +229614,152 @@ }, "limit": { "context": 200000, - "output": 64000 + "output": 128000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "budget", - "budget": { - "min": 1024, - "unit": "tokens" - }, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", - "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." + "effort_options": [ + "low", + "medium", + "high", + "xhigh" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-02-28", - "release_date": "2025-10-15", - "last_updated": "2025-10-15", + "knowledge": "2026-02-01", + "release_date": "2026-08-12", + "last_updated": "2026-08-12", "type": "chat" }, { - "id": "gemini-3.8-flash", - "name": "Gemini 3.8 Flash", - "display_name": "Gemini 3.8 Flash", + "id": "mistral-small-2503", + "name": "Mistral Small 3.1", + "display_name": "Mistral Small 3.1", "modalities": { "input": [ "text", - "image", - "video", - "audio", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 128000, + "output": 32768 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "level", - "level": "high", - "level_options": [ - "minimal", - "low", - "medium", - "high" - ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } + "supported": false }, "attachment": true, "open_weights": false, - "release_date": "2026-09-02", - "last_updated": "2026-09-02", + "knowledge": "2024-09", + "release_date": "2025-03-01", + "last_updated": "2025-03-01", "type": "chat" }, { - "id": "gpt-5.4-nano", - "name": "GPT-5.4 nano", - "display_name": "GPT-5.4 nano", + "id": "cohere-embed-v3-multilingual", + "name": "Embed v3 Multilingual", + "display_name": "Embed v3 Multilingual", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 512, + "output": 1024 }, "temperature": false, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": false - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "none", - "effort_options": [ - "none", - "low", - "medium", - "high", - "xhigh" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } + "supported": false }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-03-17", - "last_updated": "2026-03-17", - "type": "chat" + "attachment": false, + "open_weights": true, + "release_date": "2023-11-07", + "last_updated": "2023-11-07", + "type": "embedding" }, { - "id": "gpt-5.5-pro", - "name": "GPT-5.5 Pro", - "display_name": "GPT-5.5 Pro", + "id": "gpt-4.1-mini", + "name": "GPT-4.1 mini", + "display_name": "GPT-4.1 mini", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 1047576, + "output": 32768 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "effort_options": [ - "medium", - "high", - "xhigh" - ] - } + "supported": false }, "attachment": true, "open_weights": false, - "knowledge": "2025-12-01", - "release_date": "2026-04-23", - "last_updated": "2026-04-23", + "knowledge": "2024-04", + "release_date": "2025-04-14", + "last_updated": "2025-04-14", "type": "chat" }, { - "id": "glm-5.3", - "name": "GLM-5.3", - "display_name": "GLM-5.3", + "id": "gpt-4.1", + "name": "GPT-4.1", + "display_name": "GPT-4.1", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 1047576, + "output": 32768 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } + "supported": false }, - "attachment": false, - "open_weights": true, - "release_date": "2026-08-14", - "last_updated": "2026-08-14", + "attachment": true, + "open_weights": false, + "knowledge": "2024-04", + "release_date": "2025-04-14", + "last_updated": "2025-04-14", "type": "chat" }, { - "id": "claude-opus-4-8", - "name": "Claude Opus 4.8", - "display_name": "Claude Opus 4.8", + "id": "gpt-6-luna", + "name": "GPT-6 Luna", + "display_name": "GPT-6 Luna", "modalities": { "input": [ "text", @@ -231952,52 +229771,38 @@ ] }, "limit": { - "context": 1000000, + "context": 1050000, "output": 128000 }, "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "high", "effort_options": [ + "none", "low", "medium", "high", "xhigh", "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Claude Opus 4.7 and newer Opus models require thinking.type = \"adaptive\" to enable thinking explicitly.", - "Manual budget_tokens requests return 400 on Claude Opus 4.7 and newer adaptive-only Opus models.", - "task_budget is separate from thinking control and should not be treated as a thinking budget." ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-01", - "release_date": "2026-05-28", - "last_updated": "2026-05-28", + "knowledge": "2026-05-18", + "release_date": "2026-09-22", + "last_updated": "2026-09-22", "type": "chat" }, { - "id": "claude-sonnet-4-5", - "name": "Claude Sonnet 4.5 (latest)", - "display_name": "Claude Sonnet 4.5 (latest)", + "id": "o3", + "name": "o3", + "display_name": "o3", "modalities": { "input": [ "text", @@ -232010,61 +229815,78 @@ }, "limit": { "context": 200000, - "output": 64000 + "output": 100000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "budget", - "budget": { - "min": 1024, - "unit": "tokens" - }, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "default_enabled": true, + "mode": "effort", + "effort": "medium", + "effort_options": [ + "low", + "medium", + "high" ], - "notes": [ - "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", - "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." - ] + "visibility": "hidden" } }, "attachment": true, "open_weights": false, - "knowledge": "2025-07-31", - "release_date": "2025-09-29", - "last_updated": "2025-09-29", + "knowledge": "2024-05", + "release_date": "2025-04-16", + "last_updated": "2025-04-16", "type": "chat" }, { - "id": "gemini-3.5-flash", - "name": "Gemini 3.5 Flash", - "display_name": "Gemini 3.5 Flash", + "id": "gpt-image-2", + "name": "GPT-Image-2", + "display_name": "GPT-Image-2", "modalities": { "input": [ "text", - "image", - "video", - "audio", - "pdf" + "image" + ], + "output": [ + "image" + ] + }, + "limit": { + "context": 8192, + "output": 8192 + }, + "temperature": false, + "tool_call": false, + "reasoning": { + "supported": false + }, + "attachment": true, + "open_weights": false, + "release_date": "2026-04-21", + "last_updated": "2026-04-21" + }, + { + "id": "kimi-k2.5", + "name": "Kimi K2.5", + "display_name": "Kimi K2.5", + "modalities": { + "input": [ + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 262144, + "output": 262144 }, "temperature": true, "tool_call": true, @@ -232075,15 +229897,6 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "level", - "level": "high", - "level_options": [ - "minimal", - "low", - "medium", - "high" - ], "interleaved": true, "summaries": true, "visibility": "summary", @@ -232093,31 +229906,28 @@ } }, "attachment": true, - "open_weights": false, + "open_weights": true, "knowledge": "2025-01", - "release_date": "2026-05-19", - "last_updated": "2026-05-19", + "release_date": "2026-01-27", + "last_updated": "2026-01-27", "type": "chat" }, { - "id": "muse-spark-1.2", - "name": "Muse Spark 1.2", - "display_name": "Muse Spark 1.2", + "id": "kimi-k2.6", + "name": "Kimi K2.6", + "display_name": "Kimi K2.6", "modalities": { "input": [ "text", - "image", - "video", - "pdf", - "audio" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 262144, + "output": 262144 }, "temperature": true, "tool_call": true, @@ -232128,31 +229938,57 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "effort_options": [ - "minimal", - "low", - "medium", - "high", - "xhigh" - ] + "interleaved": true } }, "attachment": true, - "open_weights": false, - "release_date": "2026-08-05", - "last_updated": "2026-08-05", + "open_weights": true, + "knowledge": "2025-01", + "release_date": "2026-04-20", + "last_updated": "2026-04-20", "type": "chat" }, { - "id": "claude-fable-5", - "name": "Claude Fable 5", - "display_name": "Claude Fable 5", + "id": "phi-4", + "name": "Phi-4", + "display_name": "Phi-4", + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 4096 + }, + "temperature": true, + "tool_call": false, + "reasoning": { + "supported": false + }, + "attachment": false, + "open_weights": true, + "knowledge": "2023-10", + "release_date": "2024-12-11", + "last_updated": "2024-12-11", + "type": "chat" + } + ] + }, + "infer": { + "id": "infer", + "name": "Infer by Flow7", + "display_name": "Infer by Flow7", + "api": "https://infer.flow7.org/v1", + "doc": "https://infer.flow7.org/opencode", + "models": [ + { + "id": "infer/gpt-6-astra:official", + "name": "GPT-6 Astra (Official API)", + "display_name": "GPT-6 Astra (Official API)", "modalities": { "input": [ "text", @@ -232164,7 +230000,7 @@ ] }, "limit": { - "context": 1000000, + "context": 271999, "output": 128000 }, "temperature": false, @@ -232175,41 +230011,26 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "high", "effort_options": [ "low", "medium", "high", "xhigh", "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Adaptive thinking is always on for Claude Fable 5 and Claude Mythos 5; thinking.type = \"disabled\" is rejected.", - "Manual budget_tokens requests return 400 on Claude Fable 5 and Claude Mythos 5.", - "thinking.display defaults to omitted; set display to summarized to receive readable thinking summaries." ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-01-31", - "release_date": "2026-06-09", - "last_updated": "2026-06-09", + "knowledge": "2026-04-30", + "release_date": "2026-09-04", + "last_updated": "2026-09-04", "type": "chat" }, { - "id": "claude-fable-5-1", - "name": "Claude Fable 5.1", - "display_name": "Claude Fable 5.1", + "id": "infer/gpt-5.6-sol:official", + "name": "GPT-5.6 Sol (Official API)", + "display_name": "GPT-5.6 Sol (Official API)", "modalities": { "input": [ "text", @@ -232221,7 +230042,7 @@ ] }, "limit": { - "context": 1000000, + "context": 271999, "output": 128000 }, "temperature": false, @@ -232232,53 +230053,46 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "high", "effort_options": [ + "none", "low", "medium", "high", "xhigh", "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Adaptive thinking is always on for Claude Fable 5 and Claude Mythos 5; thinking.type = \"disabled\" is rejected.", - "Manual budget_tokens requests return 400 on Claude Fable 5 and Claude Mythos 5.", - "thinking.display defaults to omitted; set display to summarized to receive readable thinking summaries." ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-06", - "release_date": "2026-09-01", - "last_updated": "2026-09-01", + "knowledge": "2026-02-16", + "release_date": "2026-07-09", + "last_updated": "2026-07-09", "type": "chat" - }, + } + ] + }, + "cerebras": { + "id": "cerebras", + "name": "Cerebras", + "display_name": "Cerebras", + "doc": "https://inference-docs.cerebras.ai/models/overview", + "models": [ { - "id": "grok-4.3", - "name": "Grok 4.3", - "display_name": "Grok 4.3", + "id": "gpt-oss-120b", + "name": "GPT OSS 120B", + "display_name": "GPT OSS 120B", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 30000 + "context": 131072, + "output": 40960 }, "temperature": true, "tool_call": true, @@ -232289,35 +230103,36 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "low", + "medium", + "high" ] } }, - "attachment": true, - "open_weights": false, - "release_date": "2026-04-17", - "last_updated": "2026-04-17", + "attachment": false, + "open_weights": true, + "knowledge": "2024-06", + "release_date": "2025-08-05", + "last_updated": "2025-08-05", "type": "chat" }, { - "id": "glm-5.2", - "name": "GLM-5.2", - "display_name": "GLM-5.2", + "id": "qwen-3.8-27b", + "name": "Qwen3.8 27B", + "display_name": "Qwen3.8 27B", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 131072, + "output": 40960 }, "temperature": true, "tool_call": true, @@ -232327,25 +230142,33 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "none", + "low", + "medium", + "high" ] } }, - "attachment": false, + "attachment": true, "open_weights": true, - "release_date": "2026-06-13", - "last_updated": "2026-06-13", + "release_date": "2026-08-14", + "last_updated": "2026-08-14", "type": "chat" - }, + } + ] + }, + "alibaba-coding-plan": { + "id": "alibaba-coding-plan", + "name": "Alibaba Coding Plan", + "display_name": "Alibaba Coding Plan", + "api": "https://coding-intl.dashscope.aliyuncs.com/v1", + "doc": "https://www.alibabacloud.com/help/en/model-studio/coding-plan", + "models": [ { - "id": "sonar-reasoning-pro", - "name": "Sonar Reasoning Pro", - "display_name": "Sonar Reasoning Pro", + "id": "MiniMax-M2.5", + "name": "MiniMax-M2.5", + "display_name": "MiniMax-M2.5", "modalities": { "input": [ "text" @@ -232355,11 +230178,11 @@ ] }, "limit": { - "context": 128000, - "output": 4096 + "context": 196608, + "output": 24576 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { "supported": true, "default": true @@ -232376,28 +230199,28 @@ } }, "attachment": false, - "open_weights": false, - "knowledge": "2025-09-01", - "release_date": "2024-01-01", - "last_updated": "2025-09-01", + "open_weights": true, + "release_date": "2026-02-12", + "last_updated": "2026-02-12", "type": "chat" }, { - "id": "grok-4.6", - "name": "Grok 4.6", - "display_name": "Grok 4.6", + "id": "qwen3.5-plus", + "name": "Qwen3.5 Plus", + "display_name": "Qwen3.5 Plus", "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 500000, - "output": 500000 + "context": 1000000, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -232418,91 +230241,42 @@ }, "attachment": true, "open_weights": false, - "knowledge": "2026-02-01", - "release_date": "2026-08-12", - "last_updated": "2026-08-12", - "type": "chat" - } - ] - }, - "mistral": { - "id": "mistral", - "name": "Mistral", - "display_name": "Mistral", - "doc": "https://docs.mistral.ai/getting-started/models/", - "models": [ - { - "id": "mistral-large-4-0-8", - "name": "Mistral Large 4.0.8", - "display_name": "Mistral Large 4.0.8", - "modalities": { - "input": [ - "text", - "image", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 1048576, - "output": 262144 - }, - "temperature": true, - "tool_call": true, - "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "low", - "medium", - "high" - ] - } - }, - "attachment": true, - "open_weights": false, - "release_date": "2026-10-06", - "last_updated": "2026-10-06", + "knowledge": "2025-04", + "release_date": "2026-02-16", + "last_updated": "2026-02-16", "type": "chat" }, { - "id": "mistral-small-2506", - "name": "Mistral Small 3.2", - "display_name": "Mistral Small 3.2", + "id": "qwen3-coder-next", + "name": "Qwen3 Coder Next", + "display_name": "Qwen3 Coder Next", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 16384 + "context": 262144, + "output": 65536 }, "temperature": true, "tool_call": true, "reasoning": { "supported": false }, - "attachment": true, + "attachment": false, "open_weights": true, - "knowledge": "2025-03", - "release_date": "2025-06-20", - "last_updated": "2025-06-20", + "release_date": "2026-02-03", + "last_updated": "2026-02-03", "type": "chat" }, { - "id": "zai-glm-5-3", - "name": "GLM-5.3", - "display_name": "GLM-5.3", + "id": "glm-4.7", + "name": "GLM-4.7", + "display_name": "GLM-4.7", "modalities": { "input": [ "text" @@ -232512,8 +230286,8 @@ ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 202752, + "output": 16384 }, "temperature": true, "tool_call": true, @@ -232523,27 +230297,29 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "low", - "high", - "max" + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, "attachment": false, "open_weights": true, - "release_date": "2026-08-14", - "last_updated": "2026-08-14", + "knowledge": "2025-04", + "release_date": "2025-12-22", + "last_updated": "2025-12-22", "type": "chat" }, { - "id": "magistral-medium-latest", - "name": "Magistral Medium (latest)", - "display_name": "Magistral Medium (latest)", + "id": "qwen3-max-2026-01-23", + "name": "Qwen3 Max", + "display_name": "Qwen3 Max", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" @@ -232551,59 +230327,35 @@ }, "limit": { "context": 262144, - "output": 16384 + "output": 32768 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "none", - "high" - ] - } - }, - "attachment": true, - "open_weights": true, - "knowledge": "2025-06", - "release_date": "2025-03-17", - "last_updated": "2025-03-20", - "type": "chat" - }, - { - "id": "voxtral-mini-tts-latest", - "name": "Voxtral Mini TTS (latest)", - "display_name": "Voxtral Mini TTS (latest)", - "modalities": { - "input": [ - "text" - ], - "output": [ - "audio" - ] - }, - "limit": { - "context": 8192, - "output": 8192 - }, - "temperature": false, - "tool_call": false, - "reasoning": { - "supported": false + "supported": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } }, "attachment": false, - "open_weights": true, - "release_date": "2026-03-23", - "last_updated": "2026-03-23" + "open_weights": false, + "knowledge": "2025-04", + "release_date": "2026-01-23", + "last_updated": "2026-01-23", + "type": "chat" }, { - "id": "glm-5-2", - "name": "GLM-5.2", - "display_name": "GLM-5.2", + "id": "glm-5", + "name": "GLM-5", + "display_name": "GLM-5", "modalities": { "input": [ "text" @@ -232613,8 +230365,8 @@ ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 202752, + "output": 16384 }, "temperature": true, "tool_call": true, @@ -232624,114 +230376,136 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "none", - "high", - "max" + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, "attachment": false, - "open_weights": true, - "release_date": "2026-06-13", - "last_updated": "2026-06-13", + "open_weights": false, + "release_date": "2026-02-11", + "last_updated": "2026-02-11", "type": "chat" }, { - "id": "ministral-14b-2512", - "name": "Ministral 3 14B", - "display_name": "Ministral 3 14B", + "id": "qwen3.6-plus", + "name": "Qwen3.6 Plus", + "display_name": "Qwen3.6 Plus", "modalities": { "input": [ "text", "image", - "pdf" + "video" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 1000000, + "output": 65536 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } }, "attachment": true, - "open_weights": true, - "release_date": "2025-12-02", - "last_updated": "2025-12-02", + "open_weights": false, + "knowledge": "2025-04", + "release_date": "2026-04-02", + "last_updated": "2026-04-02", "type": "chat" }, { - "id": "labs-devstral-small-2512", - "name": "Devstral Small 2", - "display_name": "Devstral Small 2", + "id": "qwen3-coder-plus", + "name": "Qwen3 Coder Plus", + "display_name": "Qwen3 Coder Plus", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 256000, - "output": 256000 + "context": 1000000, + "output": 65536 }, "temperature": true, "tool_call": true, "reasoning": { "supported": false }, - "attachment": true, + "attachment": false, "open_weights": true, - "knowledge": "2025-12", - "release_date": "2025-12-09", - "last_updated": "2025-12-09", + "knowledge": "2025-04", + "release_date": "2025-07-23", + "last_updated": "2025-07-23", "type": "chat" }, { - "id": "devstral-2512", - "name": "Devstral 2", - "display_name": "Devstral 2", + "id": "qwen3.7-plus", + "name": "Qwen3.7 Plus", + "display_name": "Qwen3.7 Plus", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 1000000, + "output": 64000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-12", - "release_date": "2025-12-09", - "last_updated": "2025-12-09", + "extra_capabilities": { + "reasoning": { + "supported": true + } + }, + "attachment": true, + "open_weights": false, + "knowledge": "2025-04", + "release_date": "2026-06-02", + "last_updated": "2026-06-02", "type": "chat" }, { - "id": "mistral-medium-latest", - "name": "Mistral Medium (latest)", - "display_name": "Mistral Medium (latest)", + "id": "kimi-k2.5", + "name": "Kimi K2.5", + "display_name": "Kimi K2.5", "modalities": { "input": [ "text", "image", - "pdf" + "video" ], "output": [ "text" @@ -232739,7 +230513,7 @@ }, "limit": { "context": 262144, - "output": 262144 + "output": 32768 }, "temperature": true, "tool_call": true, @@ -232749,235 +230523,308 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "none", - "high" + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, "attachment": true, "open_weights": true, - "release_date": "2026-04-29", - "last_updated": "2026-04-29", + "knowledge": "2025-01", + "release_date": "2026-01-27", + "last_updated": "2026-01-27", "type": "chat" - }, + } + ] + }, + "alibaba-token-plan": { + "id": "alibaba-token-plan", + "name": "Alibaba Token Plan", + "display_name": "Alibaba Token Plan", + "api": "https://token-plan.ap-southeast-1.maas.aliyuncs.com/compatible-mode/v1", + "doc": "https://www.alibabacloud.com/help/en/model-studio/token-plan-overview", + "models": [ { - "id": "mistral-medium-2505", - "name": "Mistral Medium 3", - "display_name": "Mistral Medium 3", + "id": "qwen-image-2.0-pro", + "name": "Qwen Image 2.0 Pro", + "display_name": "Qwen Image 2.0 Pro", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ - "text" + "image" ] }, "limit": { - "context": 131072, - "output": 131072 + "context": 8192, + "output": 8192 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { "supported": false }, - "attachment": true, + "attachment": false, "open_weights": false, - "knowledge": "2025-05", - "release_date": "2025-05-07", - "last_updated": "2025-05-07", - "type": "chat" + "release_date": "2026-03-03", + "last_updated": "2026-03-03" }, { - "id": "mistral-large-2512", - "name": "Mistral Large 3", - "display_name": "Mistral Large 3", + "id": "MiniMax-M2.5", + "name": "MiniMax-M2.5", + "display_name": "MiniMax-M2.5", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 196608, + "output": 32768 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": true, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } + }, + "attachment": false, "open_weights": true, - "knowledge": "2024-11", - "release_date": "2024-11-01", - "last_updated": "2025-12-02", + "release_date": "2026-02-12", + "last_updated": "2026-02-12", "type": "chat" }, { - "id": "codestral-latest", - "name": "Codestral (latest)", - "display_name": "Codestral (latest)", + "id": "qwen3.8-max-preview", + "name": "Qwen3.8 Max Preview", + "display_name": "Qwen3.8 Max Preview", "modalities": { "input": [ "text", - "pdf" + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 256000, - "output": 4096 + "context": 1000000, + "output": 131072 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "effort_options": [ + "low", + "medium", + "xhigh" + ] + } }, "attachment": true, - "open_weights": true, - "knowledge": "2024-10", - "release_date": "2024-05-29", - "last_updated": "2025-01-04", + "open_weights": false, + "release_date": "2026-07-19", + "last_updated": "2026-07-19", "type": "chat" }, { - "id": "voxtral-mini-latest", - "name": "Voxtral Mini (latest)", - "display_name": "Voxtral Mini (latest)", + "id": "happyhorse-1.1-i2v", + "name": "HappyHorse 1.1 Image-to-Video", + "display_name": "HappyHorse 1.1 Image-to-Video", "modalities": { "input": [ - "audio" + "image", + "text" ], "output": [ - "text" + "video" ] }, "limit": { "context": 8192, "output": 8192 }, - "temperature": false, + "temperature": true, "tool_call": false, "reasoning": { "supported": false }, "attachment": true, "open_weights": false, - "release_date": "2026-02-04", - "last_updated": "2026-02-04" + "release_date": "2026-07-17", + "last_updated": "2026-07-17" }, { - "id": "voxtral-small-2507", - "name": "Voxtral Small", - "display_name": "Voxtral Small", + "id": "kimi-k2.7-code", + "name": "Kimi K2.7 Code", + "display_name": "Kimi K2.7 Code", "modalities": { "input": [ "text", - "audio", - "pdf" + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 32768, - "output": 32000 + "context": 262144, + "output": 262144 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } }, "attachment": true, "open_weights": true, - "release_date": "2025-07-15", - "last_updated": "2025-07-15", + "knowledge": "2025-01", + "release_date": "2026-06-12", + "last_updated": "2026-06-12", "type": "chat" }, { - "id": "ministral-8b-latest", - "name": "Ministral 8B (latest)", - "display_name": "Ministral 8B (latest)", + "id": "qwen3.7-max", + "name": "Qwen3.7 Max", + "display_name": "Qwen3.7 Max", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 128000 + "context": 1000000, + "output": 131072 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": true, - "open_weights": true, - "knowledge": "2024-10", - "release_date": "2024-10-01", - "last_updated": "2024-10-04", + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } + }, + "attachment": false, + "open_weights": false, + "release_date": "2026-05-21", + "last_updated": "2026-05-21", "type": "chat" }, { - "id": "mistral-embed", - "name": "Mistral Embed", - "display_name": "Mistral Embed", + "id": "qwen3.6-flash", + "name": "Qwen3.6 Flash", + "display_name": "Qwen3.6 Flash", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 8000, - "output": 3072 + "context": 1000000, + "output": 65536 }, - "temperature": false, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } + }, + "attachment": true, "open_weights": false, - "release_date": "2023-12-11", - "last_updated": "2023-12-11", - "type": "embedding" + "release_date": "2026-04-27", + "last_updated": "2026-04-27", + "type": "chat" }, { - "id": "mistral-medium-2604", - "name": "Mistral Medium 3.5", - "display_name": "Mistral Medium 3.5", + "id": "qwen3.8-flash", + "name": "Qwen3.8 Flash", + "display_name": "Qwen3.8 Flash", "modalities": { "input": [ "text", "image", - "pdf" + "video" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 1000000, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -232987,33 +230834,42 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ - "none", - "high" + "low", + "medium", + "xhigh" ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-04-29", - "last_updated": "2026-04-29", + "open_weights": false, + "release_date": "2026-08-26", + "last_updated": "2026-08-26", "type": "chat" }, { - "id": "deepseek-v4-1-flash-1017", + "id": "deepseek-v4.1-flash-1017", "name": "DeepSeek V4.1 Flash 1017", "display_name": "DeepSeek V4.1 Flash 1017", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 128000 + "context": 1000000, + "output": 384000 }, "temperature": true, "tool_call": true, @@ -233023,15 +230879,21 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ - "none", "low", "high", "max" ] } }, - "attachment": false, + "attachment": true, "open_weights": true, "knowledge": "2025-05", "release_date": "2026-10-17", @@ -233039,37 +230901,35 @@ "type": "chat" }, { - "id": "open-mistral-nemo", - "name": "Open Mistral Nemo", - "display_name": "Open Mistral Nemo", + "id": "qwen-image-2.0", + "name": "Qwen Image 2.0", + "display_name": "Qwen Image 2.0", "modalities": { "input": [ "text" ], "output": [ - "text" + "image" ] }, "limit": { - "context": 128000, - "output": 128000 + "context": 8192, + "output": 8192 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { "supported": false }, "attachment": false, - "open_weights": true, - "knowledge": "2024-07", - "release_date": "2024-07-01", - "last_updated": "2024-07-01", - "type": "chat" + "open_weights": false, + "release_date": "2026-03-03", + "last_updated": "2026-03-03" }, { - "id": "devstral-medium-latest", - "name": "Devstral 2 (latest)", - "display_name": "Devstral 2 (latest)", + "id": "deepseek-v3.2", + "name": "DeepSeek V3.2", + "display_name": "DeepSeek V3.2", "modalities": { "input": [ "text" @@ -233079,54 +230939,57 @@ ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 131072, + "output": 65536 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true + } }, "attachment": false, "open_weights": true, - "knowledge": "2025-12", - "release_date": "2025-12-02", - "last_updated": "2025-12-02", + "knowledge": "2025-01", + "release_date": "2025-12-03", + "last_updated": "2025-12-05", "type": "chat" }, { - "id": "ministral-3b-2512", - "name": "Ministral 3 3B", - "display_name": "Ministral 3 3B", + "id": "happyhorse-1.1-t2v", + "name": "HappyHorse 1.1 Text-to-Video", + "display_name": "HappyHorse 1.1 Text-to-Video", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ - "text" + "video" ] }, "limit": { - "context": 131072, - "output": 262144 + "context": 8192, + "output": 8192 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { "supported": false }, - "attachment": true, - "open_weights": true, - "release_date": "2025-12-02", - "last_updated": "2025-12-02", - "type": "chat" + "attachment": false, + "open_weights": false, + "release_date": "2026-07-17", + "last_updated": "2026-07-17" }, { - "id": "mistral-medium-2508", - "name": "Mistral Medium 3.1", - "display_name": "Mistral Medium 3.1", + "id": "deepseek-v4.1-flash", + "name": "DeepSeek V4.1 Flash", + "display_name": "DeepSeek V4.1 Flash", "modalities": { "input": [ "text", @@ -233137,103 +231000,94 @@ ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 1000000, + "output": 384000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "effort_options": [ + "low", + "high", + "max" + ] + } }, "attachment": true, - "open_weights": false, + "open_weights": true, "knowledge": "2025-05", - "release_date": "2025-08-12", - "last_updated": "2025-08-12", + "release_date": "2026-09-10", + "last_updated": "2026-09-10", "type": "chat" }, { - "id": "mistral-large-4", - "name": "Mistral Large 4", - "display_name": "Mistral Large 4", + "id": "wan2.7-image-pro", + "name": "Wan2.7 Image Pro", + "display_name": "Wan2.7 Image Pro", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ - "text" + "image" ] }, "limit": { - "context": 1048576, - "output": 262144 + "context": 8192, + "output": 8192 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "low", - "medium", - "high" - ] - } + "supported": false }, - "attachment": true, + "attachment": false, "open_weights": false, - "release_date": "2026-10-06", - "last_updated": "2026-10-06", - "type": "chat" + "release_date": "2026-05-29", + "last_updated": "2026-05-29" }, { - "id": "mistral-small-2603", - "name": "Mistral Small 4", - "display_name": "Mistral Small 4", + "id": "wan2.7-image", + "name": "Wan2.7 Image", + "display_name": "Wan2.7 Image", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ - "text" + "image" ] }, "limit": { - "context": 262144, - "output": 256000 + "context": 8192, + "output": 8192 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "none", - "high" - ] - } + "supported": false }, - "attachment": true, - "open_weights": true, - "knowledge": "2025-06", - "release_date": "2026-03-16", - "last_updated": "2026-03-16", - "type": "chat" + "attachment": false, + "open_weights": false, + "release_date": "2026-05-29", + "last_updated": "2026-05-29" }, { - "id": "zai-glm-5-2", - "name": "GLM-5.2", - "display_name": "GLM-5.2", + "id": "deepseek-v4-pro", + "name": "DeepSeek V4 Pro", + "display_name": "DeepSeek V4 Pro", "modalities": { "input": [ "text" @@ -233243,8 +231097,8 @@ ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 1000000, + "output": 384000 }, "temperature": true, "tool_call": true, @@ -233254,81 +231108,81 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "high", "effort_options": [ - "none", + "low", "high", "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." ] } }, "attachment": false, "open_weights": true, - "release_date": "2026-06-13", - "last_updated": "2026-06-13", + "knowledge": "2025-05", + "release_date": "2026-04-24", + "last_updated": "2026-04-24", "type": "chat" }, { - "id": "ministral-8b-2512", - "name": "Ministral 3 8B", - "display_name": "Ministral 3 8B", + "id": "glm-5.3", + "name": "GLM-5.3", + "display_name": "GLM-5.3", "modalities": { "input": [ - "text", - "image", - "pdf" - ], - "output": [ "text" - ] - }, - "limit": { - "context": 262144, - "output": 262144 - }, - "temperature": true, - "tool_call": true, - "reasoning": { - "supported": false - }, - "attachment": true, - "open_weights": true, - "release_date": "2025-12-02", - "last_updated": "2025-12-02", - "type": "chat" - }, - { - "id": "pixtral-12b", - "name": "Pixtral 12B", - "display_name": "Pixtral 12B", - "modalities": { - "input": [ - "text", - "image" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 128000 + "context": 1000000, + "output": 131072 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": true, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "effort_options": [ + "low", + "high", + "max" + ] + } + }, + "attachment": false, "open_weights": true, - "knowledge": "2024-09", - "release_date": "2024-09-01", - "last_updated": "2024-09-01", + "release_date": "2026-08-14", + "last_updated": "2026-08-14", "type": "chat" }, { - "id": "devstral-latest", - "name": "Devstral 2", - "display_name": "Devstral 2", + "id": "glm-5", + "name": "GLM-5", + "display_name": "GLM-5", "modalities": { "input": [ "text" @@ -233338,98 +231192,100 @@ ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 202752, + "output": 16384 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } }, "attachment": false, - "open_weights": true, - "knowledge": "2025-12", - "release_date": "2025-12-09", - "last_updated": "2025-12-09", + "open_weights": false, + "release_date": "2026-02-12", + "last_updated": "2026-02-12", "type": "chat" }, { - "id": "voxtral-small-latest", - "name": "Voxtral Small (latest)", - "display_name": "Voxtral Small (latest)", + "id": "deepseek-v4-flash", + "name": "DeepSeek V4 Flash", + "display_name": "DeepSeek V4 Flash", "modalities": { "input": [ - "text", - "audio", - "pdf" - ], - "output": [ "text" - ] - }, - "limit": { - "context": 32768, - "output": 32000 - }, - "temperature": true, - "tool_call": true, - "reasoning": { - "supported": false - }, - "attachment": true, - "open_weights": true, - "release_date": "2025-07-15", - "last_updated": "2025-07-15", - "type": "chat" - }, - { - "id": "ministral-3b-latest", - "name": "Ministral 3B (latest)", - "display_name": "Ministral 3B (latest)", - "modalities": { - "input": [ - "text", - "image", - "pdf" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 128000 + "context": 1000000, + "output": 384000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": true, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "high", + "effort_options": [ + "low", + "high", + "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." + ] + } + }, + "attachment": false, "open_weights": true, - "knowledge": "2024-10", - "release_date": "2024-10-01", - "last_updated": "2024-10-04", + "knowledge": "2025-05", + "release_date": "2026-04-24", + "last_updated": "2026-04-24", "type": "chat" }, { - "id": "zai-glm-5-3-flash", - "name": "GLM-5.3-Flash", - "display_name": "GLM-5.3-Flash", + "id": "qwen3.6-plus", + "name": "Qwen3.6 Plus", + "display_name": "Qwen3.6 Plus", "modalities": { "input": [ "text", "image", - "video", - "pdf" + "video" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 1000000, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -233439,34 +231295,36 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "low", - "high", - "max" + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-08-26", - "last_updated": "2026-08-26", + "open_weights": false, + "knowledge": "2025-04", + "release_date": "2026-04-02", + "last_updated": "2026-04-02", "type": "chat" }, { - "id": "labs-leanstral-1-5-1", - "name": "Leanstral 1.5", - "display_name": "Leanstral 1.5", + "id": "glm-5.1", + "name": "GLM-5.1", + "display_name": "GLM-5.1", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 262144, + "context": 202752, "output": 128000 }, "temperature": true, @@ -233477,65 +231335,63 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "none", - "high" + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, - "attachment": true, + "attachment": false, "open_weights": true, - "release_date": "2026-06-30", - "last_updated": "2026-06-30", + "release_date": "2026-04-07", + "last_updated": "2026-04-07", "type": "chat" }, { - "id": "mistral-large-latest", - "name": "Mistral Large (latest)", - "display_name": "Mistral Large (latest)", + "id": "happyhorse-1.1-r2v", + "name": "HappyHorse 1.1 Reference-to-Video", + "display_name": "HappyHorse 1.1 Reference-to-Video", "modalities": { "input": [ - "text", "image", - "pdf" + "text" ], "output": [ - "text" + "video" ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 8192, + "output": 8192 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { "supported": false }, "attachment": true, - "open_weights": true, - "knowledge": "2024-11", - "release_date": "2024-11-01", - "last_updated": "2025-12-02", - "type": "chat" + "open_weights": false, + "release_date": "2026-07-17", + "last_updated": "2026-07-17" }, { - "id": "mistral-small-latest", - "name": "Mistral Small (latest)", - "display_name": "Mistral Small (latest)", + "id": "glm-5.2", + "name": "GLM-5.2", + "display_name": "GLM-5.2", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 256000 + "context": 1000000, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -233545,26 +231401,34 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ - "none", - "high" + "high", + "max" ] } }, - "attachment": true, + "attachment": false, "open_weights": true, - "knowledge": "2025-06", - "release_date": "2026-03-16", - "last_updated": "2026-03-16", + "release_date": "2026-06-13", + "last_updated": "2026-06-13", "type": "chat" }, { - "id": "codestral-2508", - "name": "Codestral 25.08", - "display_name": "Codestral 25.08", + "id": "qwen3.8-max", + "name": "Qwen3.8 Max", + "display_name": "Qwen3.8 Max", "modalities": { "input": [ "text", + "image", + "video", "pdf" ], "output": [ @@ -233572,47 +231436,52 @@ ] }, "limit": { - "context": 256000, - "output": 8192 + "context": 1000000, + "output": 131072 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "effort_options": [ + "low", + "medium", + "xhigh" + ] + } }, "attachment": true, "open_weights": false, - "release_date": "2025-07-30", - "last_updated": "2025-07-30", + "release_date": "2026-08-03", + "last_updated": "2026-08-03", "type": "chat" - } - ] - }, - "xiaomi-token-plan-ams": { - "id": "xiaomi-token-plan-ams", - "name": "Xiaomi Token Plan (Europe)", - "display_name": "Xiaomi Token Plan (Europe)", - "api": "https://token-plan-ams.xiaomimimo.com/v1", - "doc": "https://platform.xiaomimimo.com/#/docs", - "models": [ + }, { - "id": "mimo-v2.6-pro", - "name": "MiMo-V2.6-Pro", - "display_name": "MiMo-V2.6-Pro", + "id": "deepseek-v4-pro-0813", + "name": "DeepSeek V4 Pro 0813", + "display_name": "DeepSeek V4 Pro 0813", "modalities": { "input": [ - "text", - "image", - "audio", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 1000000, + "output": 384000 }, "temperature": true, "tool_call": true, @@ -233628,33 +231497,36 @@ "visibility": "summary", "continuation": [ "thinking_blocks" + ], + "effort_options": [ + "high", + "max" ] } }, - "attachment": true, + "attachment": false, "open_weights": true, - "release_date": "2026-09-22", - "last_updated": "2026-09-22", + "release_date": "2026-08-13", + "last_updated": "2026-08-22", "type": "chat" }, { - "id": "mimo-v2.6-flash", - "name": "MiMo-V2.6-Flash", - "display_name": "MiMo-V2.6-Flash", + "id": "qwen3.7-plus", + "name": "Qwen3.7 Plus", + "display_name": "Qwen3.7 Plus", "modalities": { "input": [ "text", "image", - "audio", "video" ], "output": [ "text" ] }, - "limit": { - "context": 1048576, - "output": 131072 + "limit": { + "context": 1000000, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -233674,15 +231546,16 @@ } }, "attachment": true, - "open_weights": true, - "release_date": "2026-09-22", - "last_updated": "2026-09-22", + "open_weights": false, + "knowledge": "2025-04", + "release_date": "2026-06-02", + "last_updated": "2026-06-02", "type": "chat" }, { - "id": "mimo-v2.5-pro", - "name": "MiMo-V2.5-Pro", - "display_name": "MiMo-V2.5-Pro", + "id": "deepseek-v4-flash-0731", + "name": "DeepSeek V4 Flash 0731", + "display_name": "DeepSeek V4 Flash 0731", "modalities": { "input": [ "text" @@ -233692,8 +231565,8 @@ ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 1000000, + "output": 384000 }, "temperature": true, "tool_call": true, @@ -233709,25 +231582,28 @@ "visibility": "summary", "continuation": [ "thinking_blocks" + ], + "effort_options": [ + "high", + "max" ] } }, "attachment": false, "open_weights": true, - "knowledge": "2024-12", - "release_date": "2026-04-22", - "last_updated": "2026-04-22", + "knowledge": "2025-05", + "release_date": "2026-07-31", + "last_updated": "2026-07-31", "type": "chat" }, { - "id": "mimo-v2.5", - "name": "MiMo-V2.5", - "display_name": "MiMo-V2.5", + "id": "kimi-k2.5", + "name": "Kimi K2.5", + "display_name": "Kimi K2.5", "modalities": { "input": [ "text", "image", - "audio", "video" ], "output": [ @@ -233735,8 +231611,8 @@ ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 262144, + "output": 98304 }, "temperature": true, "tool_call": true, @@ -233757,113 +231633,28 @@ }, "attachment": true, "open_weights": true, - "knowledge": "2024-12", - "release_date": "2026-04-22", - "last_updated": "2026-04-22", + "knowledge": "2025-01", + "release_date": "2026-01-27", + "last_updated": "2026-01-27", "type": "chat" }, { - "id": "mimo-v2.5-tts", - "name": "MiMo-V2.5-TTS", - "display_name": "MiMo-V2.5-TTS", - "modalities": { - "input": [ - "text" - ], - "output": [ - "audio" - ] - }, - "limit": { - "context": 8192, - "output": 8192 - }, - "temperature": false, - "tool_call": false, - "reasoning": { - "supported": false - }, - "attachment": false, - "open_weights": false, - "release_date": "2026-04-22", - "last_updated": "2026-04-22" - }, - { - "id": "mimo-v2.5-tts-voicedesign", - "name": "MiMo-V2.5-TTS-VoiceDesign", - "display_name": "MiMo-V2.5-TTS-VoiceDesign", - "modalities": { - "input": [ - "text" - ], - "output": [ - "audio" - ] - }, - "limit": { - "context": 8192, - "output": 8192 - }, - "temperature": false, - "tool_call": false, - "reasoning": { - "supported": false - }, - "attachment": false, - "open_weights": false, - "release_date": "2026-04-22", - "last_updated": "2026-04-22" - }, - { - "id": "mimo-v2.5-tts-voiceclone", - "name": "MiMo-V2.5-TTS-VoiceClone", - "display_name": "MiMo-V2.5-TTS-VoiceClone", - "modalities": { - "input": [ - "text" - ], - "output": [ - "audio" - ] - }, - "limit": { - "context": 8192, - "output": 8192 - }, - "temperature": false, - "tool_call": false, - "reasoning": { - "supported": false - }, - "attachment": false, - "open_weights": false, - "release_date": "2026-04-22", - "last_updated": "2026-04-22" - } - ] - }, - "reflection": { - "id": "reflection", - "name": "Reflection", - "display_name": "Reflection", - "api": "https://api.reflection.ai/openai/v1", - "doc": "https://developers.reflection.ai/models", - "models": [ - { - "id": "Beam-1.0-501B-A23B", - "name": "Beam 1.0 501B A23B", - "display_name": "Beam 1.0 501B A23B", + "id": "kimi-k2.6", + "name": "Kimi K2.6", + "display_name": "Kimi K2.6", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 262144, + "output": 262144 }, "temperature": true, "tool_call": true, @@ -233879,50 +231670,40 @@ "visibility": "summary", "continuation": [ "thinking_blocks" - ], - "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" ] } }, - "attachment": false, + "attachment": true, "open_weights": true, - "knowledge": "2026-06-30", - "release_date": "2026-10-05", - "last_updated": "2026-10-16", + "knowledge": "2025-01", + "release_date": "2026-04-20", + "last_updated": "2026-04-20", "type": "chat" } ] }, - "liquid": { - "id": "liquid", - "name": "Liquid Inference", - "display_name": "Liquid Inference", - "api": "https://router.inference.ai.exchange/v1", - "doc": "https://inference.ai.exchange/docs", + "clarifai": { + "id": "clarifai", + "name": "Clarifai", + "display_name": "Clarifai", + "api": "https://api.clarifai.com/v2/ext/openai/v1", + "doc": "https://docs.clarifai.com/compute/inference/", "models": [ { - "id": "liquid.auto", - "name": "Liquid Auto", - "display_name": "Liquid Auto", + "id": "openai/chat-completion/models/gpt-oss-120b-high-throughput", + "name": "GPT OSS 120B High Throughput", + "display_name": "GPT OSS 120B High Throughput", "modalities": { "input": [ - "text", - "image", - "audio", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 10000000, - "output": 1048576 + "context": 131072, + "output": 16384 }, "temperature": true, "tool_call": true, @@ -233930,38 +231711,37 @@ "supported": true, "default": true }, - "attachment": true, - "open_weights": false, - "release_date": "2026-10-20", - "last_updated": "2026-10-20", + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "low", + "medium", + "high" + ] + } + }, + "attachment": false, + "open_weights": true, + "knowledge": "2024-06", + "release_date": "2025-08-05", + "last_updated": "2025-08-05", "type": "chat" - } - ] - }, - "qiniu-ai": { - "id": "qiniu-ai", - "name": "Qiniu", - "display_name": "Qiniu", - "api": "https://api.qnaigc.com/v1", - "doc": "https://developer.qiniu.com/aitokenapi", - "models": [ + }, { - "id": "qwen3-vl-30b-a3b-thinking", - "name": "Qwen3-Vl 30b A3b Thinking", - "display_name": "Qwen3-Vl 30b A3b Thinking", + "id": "openai/chat-completion/models/gpt-oss-20b", + "name": "GPT OSS 20B", + "display_name": "GPT OSS 20B", "modalities": { "input": [ - "text", - "image", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 32000 + "context": 131072, + "output": 16384 }, "temperature": true, "tool_call": true, @@ -233972,27 +231752,28 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "low", + "medium", + "high" ] } }, - "attachment": true, - "open_weights": false, - "release_date": "2026-02-09", - "last_updated": "2026-02-09", + "attachment": false, + "open_weights": true, + "knowledge": "2024-06", + "release_date": "2025-08-05", + "last_updated": "2025-08-05", "type": "chat" }, { - "id": "qwen3-max", - "name": "Qwen3 Max", - "display_name": "Qwen3 Max", + "id": "moonshotai/chat-completion/models/Kimi-K2_6", + "name": "Kimi K2.6", + "display_name": "Kimi K2.6", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" @@ -234000,12 +231781,13 @@ }, "limit": { "context": 262144, - "output": 65536 + "output": 262144 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true + "supported": true, + "default": true }, "extra_capabilities": { "reasoning": { @@ -234018,20 +231800,21 @@ ] } }, - "attachment": false, - "open_weights": false, - "knowledge": "2025-04", - "release_date": "2025-09-23", - "last_updated": "2025-09-23", + "attachment": true, + "open_weights": true, + "knowledge": "2025-01", + "release_date": "2026-04-20", + "last_updated": "2026-04-20", "type": "chat" }, { - "id": "qwen3-235b-a22b-thinking-2507", - "name": "Qwen3 235B A22B Thinking 2507", - "display_name": "Qwen3 235B A22B Thinking 2507", + "id": "mistralai/completion/models/Ministral-3-14B-Reasoning-2512", + "name": "Ministral 3 14B Reasoning 2512", + "display_name": "Ministral 3 14B Reasoning 2512", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" @@ -234039,7 +231822,7 @@ }, "limit": { "context": 262144, - "output": 4096 + "output": 262144 }, "temperature": true, "tool_call": true, @@ -234047,41 +231830,29 @@ "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } - }, - "attachment": false, + "attachment": true, "open_weights": true, - "release_date": "2025-07-25", - "last_updated": "2025-07-25", + "knowledge": "2025-12", + "release_date": "2025-12-01", + "last_updated": "2025-12-12", "type": "chat" }, { - "id": "gemini-2.5-pro", - "name": "Gemini 2.5 Pro", - "display_name": "Gemini 2.5 Pro", + "id": "mistralai/completion/models/Ministral-3-3B-Reasoning-2512", + "name": "Ministral 3 3B Reasoning 2512", + "display_name": "Ministral 3 3B Reasoning 2512", "modalities": { "input": [ "text", - "image", - "video", - "audio" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 262144, + "output": 262144 }, "temperature": true, "tool_call": true, @@ -234089,36 +231860,16 @@ "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "budget", - "budget": { - "default": -1, - "min": 128, - "max": 32768, - "auto": -1, - "unit": "tokens" - }, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thought_signatures" - ] - } - }, "attachment": true, - "open_weights": false, - "knowledge": "2025-01", - "release_date": "2025-06-17", - "last_updated": "2025-06-17", + "open_weights": true, + "release_date": "2025-12", + "last_updated": "2026-02-25", "type": "chat" }, { - "id": "qwen3-30b-a3b-instruct-2507", - "name": "Qwen3 30b A3b Instruct 2507", - "display_name": "Qwen3 30b A3b Instruct 2507", + "id": "arcee_ai/AFM/models/trinity-mini", + "name": "Trinity Mini", + "display_name": "Trinity Mini", "modalities": { "input": [ "text" @@ -234128,24 +231879,26 @@ ] }, "limit": { - "context": 128000, - "output": 32000 + "context": 131072, + "output": 131072 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, "attachment": false, "open_weights": true, - "release_date": "2025-07-28", - "last_updated": "2025-07-28", + "knowledge": "2024-10", + "release_date": "2025-12-01", + "last_updated": "2026-05-28", "type": "chat" }, { - "id": "qwen2.5-vl-72b-instruct", - "name": "Qwen 2.5 VL 72B Instruct", - "display_name": "Qwen 2.5 VL 72B Instruct", + "id": "clarifai/main/models/mm-poly-8b", + "name": "MM Poly 8B", + "display_name": "MM Poly 8B", "modalities": { "input": [ "text", @@ -234157,83 +231910,79 @@ ] }, "limit": { - "context": 128000, - "output": 8192 + "context": 32768, + "output": 4096 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { "supported": false }, "attachment": true, - "open_weights": true, - "knowledge": "2024-04", - "release_date": "2025-01-26", - "last_updated": "2025-01-26", + "open_weights": false, + "release_date": "2025-06", + "last_updated": "2026-02-25", "type": "chat" }, { - "id": "gemini-2.5-flash-image", - "name": "Gemini 2.5 Flash Image", - "display_name": "Gemini 2.5 Flash Image", + "id": "qwen/qwenCoder/models/Qwen3-Coder-30B-A3B-Instruct", + "name": "Qwen3 Coder 30B A3B Instruct", + "display_name": "Qwen3 Coder 30B A3B Instruct", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ - "image" + "text" ] }, "limit": { - "context": 32768, - "output": 8192 + "context": 262144, + "output": 65536 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { "supported": false }, - "attachment": true, - "open_weights": false, - "knowledge": "2024-06", - "release_date": "2025-08-26", - "last_updated": "2025-08-26" + "attachment": false, + "open_weights": true, + "knowledge": "2025-04", + "release_date": "2025-07-31", + "last_updated": "2025-07-31", + "type": "chat" }, { - "id": "doubao-seed-2.0-code", - "name": "Doubao Seed 2.0 Code", - "display_name": "Doubao Seed 2.0 Code", + "id": "qwen/qwenLM/models/Qwen3-30B-A3B-Instruct-2507", + "name": "Qwen3 30B A3B Instruct 2507", + "display_name": "Qwen3 30B A3B Instruct 2507", "modalities": { "input": [ - "text", - "image", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 256000, - "output": 128000 + "context": 262144, + "output": 262144 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true + "supported": false }, - "attachment": true, - "open_weights": false, - "release_date": "2026-02-14", - "last_updated": "2026-02-14", + "attachment": false, + "open_weights": true, + "release_date": "2025-07-28", + "last_updated": "2025-07-28", "type": "chat" }, { - "id": "qwen3-max-preview", - "name": "Qwen3 Max Preview", - "display_name": "Qwen3 Max Preview", + "id": "qwen/qwenLM/models/Qwen3-30B-A3B-Thinking-2507", + "name": "Qwen3 30B A3B Thinking 2507", + "display_name": "Qwen3 30B A3B Thinking 2507", "modalities": { "input": [ "text" @@ -234243,13 +231992,14 @@ ] }, "limit": { - "context": 256000, - "output": 64000 + "context": 262144, + "output": 131072 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true + "supported": true, + "default": true }, "extra_capabilities": { "reasoning": { @@ -234263,16 +232013,15 @@ } }, "attachment": false, - "open_weights": false, - "knowledge": "2025-04", - "release_date": "2025-09-05", - "last_updated": "2025-09-23", + "open_weights": true, + "release_date": "2025-07-31", + "last_updated": "2026-02-25", "type": "chat" }, { - "id": "mimo-v2-flash", - "name": "Mimo-V2-Flash", - "display_name": "Mimo-V2-Flash", + "id": "minimaxai/chat-completion/models/MiniMax-M2_5-high-throughput", + "name": "MiniMax-M2.5 High Throughput", + "display_name": "MiniMax-M2.5 High Throughput", "modalities": { "input": [ "text" @@ -234282,8 +232031,8 @@ ] }, "limit": { - "context": 256000, - "output": 256000 + "context": 204800, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -234293,65 +232042,65 @@ }, "attachment": false, "open_weights": true, - "knowledge": "2024-12-01", - "release_date": "2025-12-16", - "last_updated": "2026-02-04", + "release_date": "2026-02-12", + "last_updated": "2026-02-12", "type": "chat" }, { - "id": "gpt-oss-20b", - "name": "gpt-oss-20b", - "display_name": "gpt-oss-20b", + "id": "deepseek-ai/deepseek-ocr/models/DeepSeek-OCR", + "name": "DeepSeek OCR", + "display_name": "DeepSeek OCR", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 4096 + "context": 8192, + "output": 8192 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true - } + "supported": false }, - "attachment": false, + "attachment": true, "open_weights": true, - "knowledge": "2024-06", - "release_date": "2025-08-05", - "last_updated": "2025-08-05", + "release_date": "2025-10-20", + "last_updated": "2026-02-25", "type": "chat" - }, + } + ] + }, + "umans-ai": { + "id": "umans-ai", + "name": "Umans AI", + "display_name": "Umans AI", + "api": "https://api.code.umans.ai/v1", + "doc": "https://app.umans.ai/offers/code/docs/orgs", + "models": [ { - "id": "gemini-3.0-pro-image-preview", - "name": "Gemini 3.0 Pro Image Preview", - "display_name": "Gemini 3.0 Pro Image Preview", + "id": "umans-deepseek-v4-flash-0731", + "name": "DeepSeek V4 Flash", + "display_name": "DeepSeek V4 Flash", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ - "text", - "image" + "text" ] }, "limit": { - "context": 32768, - "output": 8192 + "context": 1048576, + "output": 393215 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { "supported": true, "default": true @@ -234359,35 +232108,34 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "level", - "level": "high", - "level_options": [ - "low", - "high" - ], + "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ - "thought_signatures" + "thinking_blocks" + ], + "effort_options": [ + "low", + "high", + "max" ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-01", - "release_date": "2025-11-20", - "last_updated": "2025-11-20" + "attachment": false, + "open_weights": true, + "knowledge": "2025-05", + "release_date": "2026-07-31", + "last_updated": "2026-07-31", + "type": "chat" }, { - "id": "gemini-2.0-flash-lite", - "name": "Gemini 2.0 Flash Lite", - "display_name": "Gemini 2.0 Flash Lite", + "id": "umans-glm-5.3-flash", + "name": "GLM 5.3 Flash", + "display_name": "GLM 5.3 Flash", "modalities": { "input": [ "text", "image", - "audio", "video" ], "output": [ @@ -234396,37 +232144,55 @@ }, "limit": { "context": 1048576, - "output": 8192 + "output": 131071 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "effort_options": [ + "low", + "high", + "max" + ] + } }, "attachment": true, - "open_weights": false, - "knowledge": "2024-06", - "release_date": "2024-12-11", - "last_updated": "2024-12-11", + "open_weights": true, + "release_date": "2026-08-26", + "last_updated": "2026-08-26", "type": "chat" }, { - "id": "qwen-turbo", - "name": "Qwen-Turbo", - "display_name": "Qwen-Turbo", + "id": "umans-coder", + "name": "Umans Coder", + "display_name": "Umans Coder", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 4096 + "context": 1048576, + "output": 131071 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -234440,20 +232206,24 @@ "visibility": "summary", "continuation": [ "thinking_blocks" + ], + "effort_options": [ + "low", + "high", + "max" ] } }, - "attachment": false, - "open_weights": false, - "knowledge": "2024-04", - "release_date": "2024-11-01", - "last_updated": "2025-04-28", + "attachment": true, + "open_weights": true, + "release_date": "2026-08-26", + "last_updated": "2026-08-26", "type": "chat" }, { - "id": "claude-4.5-sonnet", - "name": "Claude 4.5 Sonnet", - "display_name": "Claude 4.5 Sonnet", + "id": "umans-deepseek-v4.1-flash-1017", + "name": "DeepSeek V4.1 Flash 1017", + "display_name": "DeepSeek V4.1 Flash 1017", "modalities": { "input": [ "text", @@ -234464,8 +232234,8 @@ ] }, "limit": { - "context": 200000, - "output": 64000 + "context": 1048576, + "output": 393215 }, "temperature": true, "tool_call": true, @@ -234473,17 +232243,33 @@ "supported": true, "default": true }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "effort_options": [ + "low", + "high", + "max" + ] + } + }, "attachment": true, - "open_weights": false, - "knowledge": "2025-07-31", - "release_date": "2025-09-29", - "last_updated": "2025-09-29", + "open_weights": true, + "knowledge": "2025-05", + "release_date": "2026-10-17", + "last_updated": "2026-10-17", "type": "chat" }, { - "id": "claude-3.5-sonnet", - "name": "Claude 3.5 Sonnet", - "display_name": "Claude 3.5 Sonnet", + "id": "umans-deepseek-v4.1-flash", + "name": "DeepSeek V4.1 Flash", + "display_name": "DeepSeek V4.1 Flash", "modalities": { "input": [ "text", @@ -234494,25 +232280,42 @@ ] }, "limit": { - "context": 200000, - "output": 8192 + "context": 1048576, + "output": 393215 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "effort_options": [ + "low", + "high", + "max" + ] + } }, "attachment": true, - "open_weights": false, - "knowledge": "2024-04-30", - "release_date": "2024-10-22", - "last_updated": "2024-10-22", + "open_weights": true, + "knowledge": "2025-05", + "release_date": "2026-10-17", + "last_updated": "2026-10-17", "type": "chat" }, { - "id": "qwen3-coder-480b-a35b-instruct", - "name": "Qwen3 Coder 480B A35B Instruct", - "display_name": "Qwen3 Coder 480B A35B Instruct", + "id": "umans-glm-5.3", + "name": "GLM 5.3", + "display_name": "GLM 5.3", "modalities": { "input": [ "text" @@ -234522,69 +232325,100 @@ ] }, "limit": { - "context": 262000, - "output": 4096 + "context": 1048576, + "output": 131071 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "effort_options": [ + "low", + "high", + "max" + ] + } }, "attachment": false, "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-07-22", - "last_updated": "2025-07-22", + "release_date": "2026-08-14", + "last_updated": "2026-08-14", "type": "chat" }, { - "id": "deepseek-v3.1", - "name": "DeepSeek-V3.1", - "display_name": "DeepSeek-V3.1", + "id": "umans-kimi-k3", + "name": "Kimi K3", + "display_name": "Kimi K3", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 32000 + "context": 1048576, + "output": 131072 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, "default": true }, - "attachment": false, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "effort_options": [ + "low", + "high", + "max" + ] + } + }, + "attachment": true, "open_weights": true, - "release_date": "2025-08-21", - "last_updated": "2025-08-21", + "release_date": "2026-07-16", + "last_updated": "2026-07-16", "type": "chat" }, { - "id": "gemini-2.5-flash", - "name": "Gemini 2.5 Flash", - "display_name": "Gemini 2.5 Flash", + "id": "umans-flash", + "name": "Umans Flash", + "display_name": "Umans Flash", "modalities": { "input": [ "text", - "image", - "audio", - "video" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 64000 + "context": 262144, + "output": 32768 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -234593,144 +232427,200 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "budget", - "budget": { - "default": -1, - "min": 0, - "max": 24576, - "auto": -1, - "off": 0, - "unit": "tokens" - }, + "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ - "thought_signatures" + "thinking_blocks" + ], + "effort_options": [ + "low", + "medium", + "high" ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2025-01", - "release_date": "2025-06-17", - "last_updated": "2025-06-17", + "open_weights": true, + "release_date": "2026-04-17", + "last_updated": "2026-04-17", "type": "chat" - }, + } + ] + }, + "commandcode": { + "id": "commandcode", + "name": "CommandCode", + "display_name": "CommandCode", + "api": "https://api.commandcode.ai/provider/v1", + "doc": "https://commandcode.ai/docs/provider", + "models": [ { - "id": "qwen3-next-80b-a3b-instruct", - "name": "Qwen3 Next 80B A3B Instruct", - "display_name": "Qwen3 Next 80B A3B Instruct", + "id": "gpt-6.1-terra", + "name": "GPT-6.1 Terra", + "display_name": "GPT-6.1 Terra", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 32768 + "context": 1050000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-09", - "last_updated": "2025-09", + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" + ] + } + }, + "attachment": true, + "open_weights": false, + "knowledge": "2026-06-01", + "release_date": "2026-10-26", + "last_updated": "2026-10-26", "type": "chat" }, { - "id": "deepseek-r1-0528", - "name": "DeepSeek-R1-0528", - "display_name": "DeepSeek-R1-0528", + "id": "gpt-5.4", + "name": "GPT-5.4", + "display_name": "GPT-5.4", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 32000 + "context": 400000, + "output": 128000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] + "default_enabled": false, + "mode": "effort", + "effort": "none", + "effort_options": [ + "none", + "low", + "medium", + "high", + "xhigh" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, - "attachment": false, - "open_weights": true, - "release_date": "2025-05-28", - "last_updated": "2025-05-28", + "attachment": true, + "open_weights": false, + "knowledge": "2025-08-31", + "release_date": "2026-03-05", + "last_updated": "2026-03-05", "type": "chat" }, { - "id": "qwen-vl-max-2025-01-25", - "name": "Qwen VL-MAX-2025-01-25", - "display_name": "Qwen VL-MAX-2025-01-25", + "id": "gpt-5.4-mini", + "name": "GPT-5.4 mini", + "display_name": "GPT-5.4 mini", "modalities": { "input": [ "text", - "image", - "audio", - "video" + "image" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 4096 + "context": 400000, + "output": 128000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": false + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": false, + "mode": "effort", + "effort": "none", + "effort_options": [ + "none", + "low", + "medium", + "high", + "xhigh" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" + } }, "attachment": true, "open_weights": false, - "release_date": "2025-08-05", - "last_updated": "2025-08-05", + "knowledge": "2025-08-31", + "release_date": "2026-03-17", + "last_updated": "2026-03-17", "type": "chat" }, { - "id": "qwen3-235b-a22b", - "name": "Qwen 3 235B A22B", - "display_name": "Qwen 3 235B A22B", + "id": "gpt-6-sol", + "name": "GPT-6 Sol", + "display_name": "GPT-6 Sol", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 32000 + "context": 1050000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -234738,68 +232628,92 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" ] } }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-04-28", - "last_updated": "2025-04-28", + "attachment": true, + "open_weights": false, + "knowledge": "2026-04-20", + "release_date": "2026-09-22", + "last_updated": "2026-09-22", "type": "chat" }, { - "id": "claude-4.0-opus", - "name": "Claude 4.0 Opus", - "display_name": "Claude 4.0 Opus", + "id": "gpt-5.6-terra", + "name": "GPT-5.6 Terra", + "display_name": "GPT-5.6 Terra", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 32000 + "context": 1050000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, "default": true }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", + "effort_options": [ + "none", + "low", + "medium", + "high", + "xhigh", + "max" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" + } + }, "attachment": true, "open_weights": false, - "knowledge": "2025-03-31", - "release_date": "2025-05-22", - "last_updated": "2025-05-22", + "knowledge": "2026-02-16", + "release_date": "2026-07-09", + "last_updated": "2026-07-09", "type": "chat" }, { - "id": "doubao-seed-2.0-pro", - "name": "Doubao Seed 2.0 Pro", - "display_name": "Doubao Seed 2.0 Pro", + "id": "gpt-5.3-codex", + "name": "GPT-5.3 Codex", + "display_name": "GPT-5.3 Codex", "modalities": { "input": [ "text", "image", - "video" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 256000, + "context": 400000, "output": 128000 }, "temperature": true, @@ -234808,29 +232722,53 @@ "supported": true, "default": true }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", + "effort_options": [ + "low", + "medium", + "high", + "xhigh" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" + } + }, "attachment": true, "open_weights": false, - "release_date": "2026-02-14", - "last_updated": "2026-02-14", + "knowledge": "2025-08-31", + "release_date": "2026-02-05", + "last_updated": "2026-02-05", "type": "chat" }, { - "id": "qwen3-next-80b-a3b-thinking", - "name": "Qwen3 Next 80B A3B Thinking", - "display_name": "Qwen3 Next 80B A3B Thinking", + "id": "gpt-6-astra", + "name": "GPT-6 Astra", + "display_name": "GPT-6 Astra", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 32768 + "context": 1050000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -234839,58 +232777,31 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" ] } }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-09", - "last_updated": "2025-09", - "type": "chat" - }, - { - "id": "doubao-1.5-vision-pro", - "name": "Doubao 1.5 Vision Pro", - "display_name": "Doubao 1.5 Vision Pro", - "modalities": { - "input": [ - "text", - "image", - "video" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 16000 - }, - "temperature": true, - "tool_call": false, - "reasoning": { - "supported": false - }, "attachment": true, "open_weights": false, - "release_date": "2025-08-05", - "last_updated": "2025-08-05", + "knowledge": "2026-04-30", + "release_date": "2026-09-04", + "last_updated": "2026-09-04", "type": "chat" }, { - "id": "claude-3.5-haiku", - "name": "Claude 3.5 Haiku", - "display_name": "Claude 3.5 Haiku", + "id": "claude-haiku-4-5-20251001", + "name": "Claude Haiku 4.5", + "display_name": "Claude Haiku 4.5", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" @@ -234898,155 +232809,245 @@ }, "limit": { "context": 200000, - "output": 8192 + "output": 64000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": false + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": false, + "mode": "budget", + "budget": { + "min": 1024, + "unit": "tokens" + }, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", + "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." + ] + } }, "attachment": true, "open_weights": false, - "knowledge": "2024-07-31", - "release_date": "2024-10-22", - "last_updated": "2024-10-22", + "knowledge": "2025-02-28", + "release_date": "2025-10-15", + "last_updated": "2025-10-15", "type": "chat" }, { - "id": "kimi-k2", - "name": "Kimi K2", - "display_name": "Kimi K2", + "id": "claude-haiku-5-5", + "name": "Claude Haiku 5.5", + "display_name": "Claude Haiku 5.5", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 128000, + "context": 1000000, "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, - "open_weights": true, - "knowledge": "2024-12-31", - "release_date": "2025-07-11", - "last_updated": "2025-07-11", + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" + ] + } + }, + "attachment": true, + "open_weights": false, + "knowledge": "2026-06", + "release_date": "2026-10-07", + "last_updated": "2026-10-07", "type": "chat" }, { - "id": "doubao-seed-1.6", - "name": "Doubao-Seed 1.6", - "display_name": "Doubao-Seed 1.6", + "id": "gpt-6.1-sol", + "name": "GPT-6.1 Sol", + "display_name": "GPT-6.1 Sol", "modalities": { "input": [ "text", "image", - "video" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 256000, - "output": 32000 + "context": 1050000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, "default": true }, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" + ] + } + }, "attachment": true, "open_weights": false, - "release_date": "2025-10-15", - "last_updated": "2025-10-15", + "knowledge": "2026-04-30", + "release_date": "2026-09-29", + "last_updated": "2026-09-29", "type": "chat" }, { - "id": "gemini-2.0-flash", - "name": "Gemini 2.0 Flash", - "display_name": "Gemini 2.0 Flash", + "id": "claude-opus-4-7", + "name": "Claude Opus 4.7", + "display_name": "Claude Opus 4.7", "modalities": { "input": [ "text", "image", - "audio", - "video" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 8192 + "context": 1000000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": false + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": false, + "mode": "effort", + "effort": "high", + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "omitted", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Claude Opus 4.7 and newer Opus models require thinking.type = \"adaptive\" to enable thinking explicitly.", + "Manual budget_tokens requests return 400 on Claude Opus 4.7 and newer adaptive-only Opus models.", + "task_budget is separate from thinking control and should not be treated as a thinking budget." + ] + } }, "attachment": true, "open_weights": false, - "knowledge": "2024-06", - "release_date": "2024-12-11", - "last_updated": "2024-12-11", + "knowledge": "2026-01-31", + "release_date": "2026-04-16", + "last_updated": "2026-04-16", "type": "chat" }, { - "id": "glm-4.5-air", - "name": "GLM 4.5 Air", - "display_name": "GLM 4.5 Air", + "id": "claude-sonnet-5", + "name": "Claude Sonnet 5", + "display_name": "Claude Sonnet 5", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 131000, - "output": 4096 + "context": 1000000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, "default": true }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-07-28", - "last_updated": "2025-07-28", + "extra_capabilities": { + "reasoning": { + "supported": true, + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" + ] + } + }, + "attachment": true, + "open_weights": false, + "knowledge": "2026-01-31", + "release_date": "2026-06-30", + "last_updated": "2026-06-30", "type": "chat" }, { - "id": "qwen3-32b", - "name": "Qwen3 32B", - "display_name": "Qwen3 32B", + "id": "claude-fable-5", + "name": "Claude Fable 5", + "display_name": "Claude Fable 5", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 40000, - "output": 4096 + "context": 1000000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -235055,38 +233056,55 @@ "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "high", + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" + ], "interleaved": true, "summaries": true, - "visibility": "summary", + "visibility": "omitted", "continuation": [ "thinking_blocks" + ], + "notes": [ + "Adaptive thinking is always on for Claude Fable 5 and Claude Mythos 5; thinking.type = \"disabled\" is rejected.", + "Manual budget_tokens requests return 400 on Claude Fable 5 and Claude Mythos 5.", + "thinking.display defaults to omitted; set display to summarized to receive readable thinking summaries." ] } }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-04-28", - "last_updated": "2025-04-28", + "attachment": true, + "open_weights": false, + "knowledge": "2026-01-31", + "release_date": "2026-06-09", + "last_updated": "2026-06-09", "type": "chat" }, { - "id": "qwen3-30b-a3b-thinking-2507", - "name": "Qwen3 30b A3b Thinking 2507", - "display_name": "Qwen3 30b A3b Thinking 2507", + "id": "claude-opus-5-5", + "name": "Claude Opus 5.5", + "display_name": "Claude Opus 5.5", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 126000, - "output": 32000 + "context": 1000000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -235094,182 +233112,307 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" ] } }, - "attachment": false, + "attachment": true, "open_weights": false, - "release_date": "2026-02-04", - "last_updated": "2026-02-04", + "knowledge": "2026-06", + "release_date": "2026-09-22", + "last_updated": "2026-09-22", "type": "chat" }, { - "id": "claude-4.5-haiku", - "name": "Claude 4.5 Haiku", - "display_name": "Claude 4.5 Haiku", + "id": "gpt-5.6-luna", + "name": "GPT-5.6 Luna", + "display_name": "GPT-5.6 Luna", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 64000 + "context": 1050000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, "default": true }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", + "effort_options": [ + "none", + "low", + "medium", + "high", + "xhigh", + "max" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" + } + }, "attachment": true, "open_weights": false, - "knowledge": "2025-02-28", - "release_date": "2025-10-15", - "last_updated": "2025-10-15", + "knowledge": "2026-02-16", + "release_date": "2026-07-09", + "last_updated": "2026-07-09", "type": "chat" }, { - "id": "doubao-seed-1.6-thinking", - "name": "Doubao-Seed 1.6 Thinking", - "display_name": "Doubao-Seed 1.6 Thinking", + "id": "claude-sonnet-5-5", + "name": "Claude Sonnet 5.5", + "display_name": "Claude Sonnet 5.5", "modalities": { "input": [ - "image", "text", - "video" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 256000, - "output": 32000 + "context": 1000000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, "default": true }, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" + ] + } + }, "attachment": true, "open_weights": false, - "release_date": "2025-08-15", - "last_updated": "2025-08-15", + "knowledge": "2026-06", + "release_date": "2026-09-28", + "last_updated": "2026-09-28", "type": "chat" }, { - "id": "qwen3-235b-a22b-instruct-2507", - "name": "Qwen3 235b A22B Instruct 2507", - "display_name": "Qwen3 235b A22B Instruct 2507", + "id": "gpt-5.6-sol", + "name": "GPT-5.6 Sol", + "display_name": "GPT-5.6 Sol", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 64000 + "context": 1050000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, - "open_weights": true, - "release_date": "2025-07-21", - "last_updated": "2025-07-21", + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", + "effort_options": [ + "none", + "low", + "medium", + "high", + "xhigh", + "max" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" + } + }, + "attachment": true, + "open_weights": false, + "knowledge": "2026-02-16", + "release_date": "2026-07-09", + "last_updated": "2026-07-09", "type": "chat" }, { - "id": "doubao-1.5-pro-32k", - "name": "Doubao 1.5 Pro 32k", - "display_name": "Doubao 1.5 Pro 32k", + "id": "claude-sonnet-4-6", + "name": "Claude Sonnet 4.6", + "display_name": "Claude Sonnet 4.6", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 12000 + "context": 1000000, + "output": 64000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": false }, - "attachment": false, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": false, + "mode": "mixed", + "budget": { + "min": 1024, + "unit": "tokens" + }, + "effort": "high", + "effort_options": [ + "low", + "medium", + "high", + "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Anthropic recommends adaptive thinking with effort for Claude 4.6; budget_tokens remains a deprecated compatibility path.", + "Anthropic API defaults effort to high; lower effort levels should be chosen per workload." + ] + } + }, + "attachment": true, "open_weights": false, - "release_date": "2025-08-05", - "last_updated": "2025-08-05", + "knowledge": "2025-08-31", + "release_date": "2026-02-17", + "last_updated": "2026-03-13", "type": "chat" }, { - "id": "claude-4.1-opus", - "name": "Claude 4.1 Opus", - "display_name": "Claude 4.1 Opus", + "id": "claude-opus-4-8", + "name": "Claude Opus 4.8", + "display_name": "Claude Opus 4.8", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 32000 + "context": 1000000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": false, + "mode": "effort", + "effort": "high", + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "omitted", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Claude Opus 4.7 and newer Opus models require thinking.type = \"adaptive\" to enable thinking explicitly.", + "Manual budget_tokens requests return 400 on Claude Opus 4.7 and newer adaptive-only Opus models.", + "task_budget is separate from thinking control and should not be treated as a thinking budget." + ] + } }, "attachment": true, "open_weights": false, - "knowledge": "2025-03-31", - "release_date": "2025-08-05", - "last_updated": "2025-08-05", + "knowledge": "2026-01", + "release_date": "2026-05-28", + "last_updated": "2026-05-28", "type": "chat" }, { - "id": "qwen3-30b-a3b", - "name": "Qwen3 30B A3B", - "display_name": "Qwen3 30B A3B", + "id": "gpt-5.5", + "name": "GPT-5.5", + "display_name": "GPT-5.5", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 40000, - "output": 4096 + "context": 400000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -235278,137 +233421,182 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] + "default_enabled": true, + "mode": "effort", + "effort": "medium", + "effort_options": [ + "low", + "medium", + "high", + "xhigh" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, - "attachment": false, - "open_weights": true, - "release_date": "2025-04-28", - "last_updated": "2025-04-28", + "attachment": true, + "open_weights": false, + "knowledge": "2025-12-01", + "release_date": "2026-04-23", + "last_updated": "2026-04-23", "type": "chat" }, { - "id": "gemini-2.5-flash-lite", - "name": "Gemini 2.5 Flash Lite", - "display_name": "Gemini 2.5 Flash Lite", + "id": "claude-fable-5-1", + "name": "Claude Fable 5.1", + "display_name": "Claude Fable 5.1", "modalities": { "input": [ "text", "image", - "audio", - "video" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 64000 + "context": 1000000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "budget", - "budget": { - "default": -1, - "min": 512, - "max": 24576, - "auto": -1, - "unit": "tokens" - }, + "default_enabled": true, + "mode": "effort", + "effort": "high", + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" + ], + "interleaved": true, "summaries": true, - "visibility": "summary", + "visibility": "omitted", "continuation": [ - "thought_signatures" + "thinking_blocks" + ], + "notes": [ + "Adaptive thinking is always on for Claude Fable 5 and Claude Mythos 5; thinking.type = \"disabled\" is rejected.", + "Manual budget_tokens requests return 400 on Claude Fable 5 and Claude Mythos 5.", + "thinking.display defaults to omitted; set display to summarized to receive readable thinking summaries." ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-01", - "release_date": "2025-06-17", - "last_updated": "2025-06-17", + "knowledge": "2026-06", + "release_date": "2026-09-01", + "last_updated": "2026-09-01", "type": "chat" }, { - "id": "doubao-seed-2.0-mini", - "name": "Doubao Seed 2.0 Mini", - "display_name": "Doubao Seed 2.0 Mini", + "id": "claude-opus-5", + "name": "Claude Opus 5", + "display_name": "Claude Opus 5", "modalities": { "input": [ "text", "image", - "video" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 256000, - "output": 32000 + "context": 1000000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, "default": true }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" + ] + } + }, "attachment": true, "open_weights": false, - "release_date": "2026-02-14", - "last_updated": "2026-02-14", + "knowledge": "2026-05", + "release_date": "2026-07-24", + "last_updated": "2026-07-24", "type": "chat" }, { - "id": "qwen-max-2025-01-25", - "name": "Qwen2.5-Max-2025-01-25", - "display_name": "Qwen2.5-Max-2025-01-25", + "id": "gpt-6-luna", + "name": "GPT-6 Luna", + "display_name": "GPT-6 Luna", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 4096 + "context": 1050000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" + ] + } + }, + "attachment": true, "open_weights": false, - "knowledge": "2024-04", - "release_date": "2024-04-03", - "last_updated": "2025-01-25", + "knowledge": "2026-05-18", + "release_date": "2026-09-22", + "last_updated": "2026-09-22", "type": "chat" }, { - "id": "doubao-seed-1.6-flash", - "name": "Doubao-Seed 1.6 Flash", - "display_name": "Doubao-Seed 1.6 Flash", + "id": "Qwen/Qwen3.8-Omni-Flash", + "name": "Qwen3.8 Omni Flash", + "display_name": "Qwen3.8 Omni Flash", "modalities": { "input": [ "text", "image", + "audio", "video" ], "output": [ @@ -235416,8 +233604,8 @@ ] }, "limit": { - "context": 256000, - "output": 32000 + "context": 1000000, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -235425,55 +233613,79 @@ "supported": true, "default": true }, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "low", + "medium", + "xhigh" + ] + } + }, "attachment": true, "open_weights": false, - "release_date": "2025-08-28", - "last_updated": "2025-08-28", + "release_date": "2026-09-17", + "last_updated": "2026-09-17", "type": "chat" }, { - "id": "deepseek-v3", - "name": "DeepSeek-V3", - "display_name": "DeepSeek-V3", + "id": "Qwen/Qwen3.8-Max-0902", + "name": "Qwen3.8 Max 0902", + "display_name": "Qwen3.8 Max 0902", "modalities": { "input": [ - "text" + "text", + "image", + "video", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 16000 + "context": 1000000, + "output": 131072 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, - "open_weights": true, - "release_date": "2024-12-26", - "last_updated": "2024-12-26", + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "low", + "medium", + "xhigh" + ] + } + }, + "attachment": true, + "open_weights": false, + "release_date": "2026-09-02", + "last_updated": "2026-09-02", "type": "chat" }, { - "id": "claude-4.5-opus", - "name": "Claude 4.5 Opus", - "display_name": "Claude 4.5 Opus", + "id": "Qwen/Qwen3.8-Max", + "name": "Qwen3.8 Max", + "display_name": "Qwen3.8 Max", "modalities": { "input": [ "text", - "image" + "image", + "video", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 200000 + "context": 1000000, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -235481,17 +233693,26 @@ "supported": true, "default": true }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "effort_options": [ + "low", + "medium", + "xhigh" + ] + } + }, "attachment": true, "open_weights": false, - "knowledge": "2025-05", - "release_date": "2025-11-24", - "last_updated": "2025-11-24", + "release_date": "2026-08-03", + "last_updated": "2026-08-03", "type": "chat" }, { - "id": "kling-v2-6", - "name": "Kling-V2 6", - "display_name": "Kling-V2 6", + "id": "Qwen/Qwen3.8-27B", + "name": "Qwen3.8 27B", + "display_name": "Qwen3.8 27B", "modalities": { "input": [ "text", @@ -235499,38 +233720,51 @@ "video" ], "output": [ - "video" + "text" ] }, "limit": { - "context": 99999999, - "output": 99999999 + "context": 262144, + "output": 32768 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "low", + "medium", + "xhigh" + ] + } }, "attachment": true, - "open_weights": false, - "release_date": "2026-01-13", - "last_updated": "2026-01-13" + "open_weights": true, + "release_date": "2026-08-14", + "last_updated": "2026-08-14", + "type": "chat" }, { - "id": "deepseek-r1", - "name": "DeepSeek-R1", - "display_name": "DeepSeek-R1", + "id": "Qwen/Qwen3.8-Flash", + "name": "Qwen3.8 Flash", + "display_name": "Qwen3.8 Flash", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 32000 + "context": 1000000, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -235541,28 +233775,27 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "low", + "medium", + "xhigh" ] } }, - "attachment": false, - "open_weights": true, - "knowledge": "2024-07", - "release_date": "2025-01-20", - "last_updated": "2025-05-29", + "attachment": true, + "open_weights": false, + "release_date": "2026-08-26", + "last_updated": "2026-08-26", "type": "chat" }, { - "id": "MiniMax-M1", - "name": "MiniMax M1", - "display_name": "MiniMax M1", + "id": "sakana/fugu-ultra", + "name": "Fugu Ultra", + "display_name": "Fugu Ultra", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" @@ -235570,78 +233803,71 @@ }, "limit": { "context": 1000000, - "output": 80000 + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, "default": true }, - "attachment": false, - "open_weights": true, - "release_date": "2025-06-17", - "last_updated": "2025-06-17", + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "high", + "xhigh" + ] + } + }, + "attachment": true, + "open_weights": false, + "release_date": "2026-06-15", + "last_updated": "2026-06-15", "type": "chat" }, { - "id": "claude-3.7-sonnet", - "name": "Claude 3.7 Sonnet", - "display_name": "Claude 3.7 Sonnet", + "id": "nvidia/nemotron-3-ultra-550b-a55b", + "name": "Nemotron 3 Ultra 550B A55B", + "display_name": "Nemotron 3 Ultra 550B A55B", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 200000, + "context": 1000000, "output": 128000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "budget", - "budget": { - "min": 1024, - "unit": "tokens" - }, - "interleaved": false, - "summaries": false, - "visibility": "full", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Anthropic uses thinking budget tokens" - ] + "supported": true } }, - "attachment": true, - "open_weights": false, - "knowledge": "2024-10-31", - "release_date": "2025-02-19", - "last_updated": "2025-02-19", + "attachment": false, + "open_weights": true, + "knowledge": "2025-09", + "release_date": "2026-06-04", + "last_updated": "2026-06-04", "type": "chat" }, { - "id": "qwen3.5-397b-a17b", - "name": "Qwen3.5 397B A17B", - "display_name": "Qwen3.5 397B A17B", + "id": "thinkingmachines/inkling", + "name": "Inkling", + "display_name": "Inkling", "modalities": { "input": [ "text", - "image" + "image", + "audio" ], "output": [ "text" @@ -235649,7 +233875,7 @@ }, "limit": { "context": 256000, - "output": 64000 + "output": 256000 }, "temperature": true, "tool_call": true, @@ -235659,32 +233885,29 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "low", + "medium", + "high", + "max" ] } }, "attachment": true, "open_weights": true, - "release_date": "2026-02-15", - "last_updated": "2026-02-15", + "release_date": "2026-07-15", + "last_updated": "2026-07-15", "type": "chat" }, - { - "id": "gemini-3.0-flash-preview", - "name": "Gemini 3.0 Flash Preview", - "display_name": "Gemini 3.0 Flash Preview", + { + "id": "thinkingmachines/inkling-small", + "name": "Inkling Small", + "display_name": "Inkling Small", "modalities": { "input": [ "text", "image", - "audio", - "video", - "pdf" + "audio" ], "output": [ "text" @@ -235692,7 +233915,7 @@ }, "limit": { "context": 1000000, - "output": 64000 + "output": 1000000 }, "temperature": true, "tool_call": true, @@ -235702,41 +233925,30 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "level", - "level": "high", - "level_options": [ - "minimal", + "effort_options": [ "low", "medium", - "high" - ], - "summaries": true, - "visibility": "summary", - "continuation": [ - "thought_signatures" + "high", + "max" ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2025-01", - "release_date": "2025-12-17", - "last_updated": "2025-12-17", + "open_weights": true, + "release_date": "2026-07-30", + "last_updated": "2026-07-30", "type": "chat" }, { - "id": "gemini-3.0-pro-preview", - "name": "Gemini 3.0 Pro Preview", - "display_name": "Gemini 3.0 Pro Preview", + "id": "z-ai/glm-5.3-flashx", + "name": "GLM-5.3-FlashX", + "display_name": "GLM-5.3-FlashX", "modalities": { "input": [ "text", "image", "video", - "pdf", - "audio" + "pdf" ], "output": [ "text" @@ -235744,7 +233956,7 @@ }, "limit": { "context": 1000000, - "output": 64000 + "output": 131072 }, "temperature": true, "tool_call": true, @@ -235754,43 +233966,37 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "level", - "level": "high", - "level_options": [ + "effort_options": [ "low", - "high" - ], - "summaries": true, - "visibility": "summary", - "continuation": [ - "thought_signatures" + "high", + "max" ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2025-01", - "release_date": "2025-11-18", - "last_updated": "2025-11-18", + "open_weights": true, + "release_date": "2026-08-26", + "last_updated": "2026-08-26", "type": "chat" }, { - "id": "glm-4.5", - "name": "GLM 4.5", - "display_name": "GLM 4.5", + "id": "z-ai/glm-5.3-flash", + "name": "GLM-5.3-Flash", + "display_name": "GLM-5.3-Flash", "modalities": { "input": [ - "text" + "text", + "image", + "video", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 98304 + "context": 1000000, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -235798,30 +234004,39 @@ "supported": true, "default": true }, - "attachment": false, + "extra_capabilities": { + "reasoning": { + "supported": true, + "effort_options": [ + "low", + "high", + "max" + ] + } + }, + "attachment": true, "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-07-28", - "last_updated": "2025-07-28", + "release_date": "2026-08-26", + "last_updated": "2026-08-26", "type": "chat" }, { - "id": "doubao-seed-2.0-lite", - "name": "Doubao Seed 2.0 Lite", - "display_name": "Doubao Seed 2.0 Lite", + "id": "xai/grok-4.7", + "name": "Grok 4.7", + "display_name": "Grok 4.7", "modalities": { "input": [ "text", "image", - "video" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 256000, - "output": 32000 + "context": 500000, + "output": 500000 }, "temperature": true, "tool_call": true, @@ -235829,27 +234044,39 @@ "supported": true, "default": true }, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "low", + "medium", + "high", + "xhigh" + ] + } + }, "attachment": true, "open_weights": false, - "release_date": "2026-02-14", - "last_updated": "2026-02-14", + "knowledge": "2026-05", + "release_date": "2026-09-21", + "last_updated": "2026-09-21", "type": "chat" }, { - "id": "doubao-1.5-thinking-pro", - "name": "Doubao 1.5 Thinking Pro", - "display_name": "Doubao 1.5 Thinking Pro", + "id": "xai/grok-4.5", + "name": "Grok 4.5", + "display_name": "Grok 4.5", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 16000 + "context": 500000, + "output": 500000 }, "temperature": true, "tool_call": true, @@ -235857,16 +234084,26 @@ "supported": true, "default": true }, - "attachment": false, + "extra_capabilities": { + "reasoning": { + "supported": true, + "effort_options": [ + "low", + "medium", + "high" + ] + } + }, + "attachment": true, "open_weights": false, - "release_date": "2025-08-05", - "last_updated": "2025-08-05", + "release_date": "2026-07-08", + "last_updated": "2026-07-08", "type": "chat" }, { - "id": "claude-4.0-sonnet", - "name": "Claude 4.0 Sonnet", - "display_name": "Claude 4.0 Sonnet", + "id": "xai/grok-4.6", + "name": "Grok 4.6", + "display_name": "Grok 4.6", "modalities": { "input": [ "text", @@ -235877,8 +234114,8 @@ ] }, "limit": { - "context": 200000, - "output": 64000 + "context": 500000, + "output": 500000 }, "temperature": true, "tool_call": true, @@ -235886,47 +234123,69 @@ "supported": true, "default": true }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "effort_options": [ + "low", + "medium", + "high", + "xhigh" + ] + } + }, "attachment": true, "open_weights": false, - "knowledge": "2025-03-31", - "release_date": "2025-05-22", - "last_updated": "2025-05-22", + "knowledge": "2026-02-01", + "release_date": "2026-08-12", + "last_updated": "2026-08-12", "type": "chat" }, { - "id": "meituan/longcat-flash-chat", - "name": "Meituan/Longcat-Flash-Chat", - "display_name": "Meituan/Longcat-Flash-Chat", + "id": "moonshotai/Kimi-K2.7-Code-Highspeed", + "name": "Kimi K2.7 Code Highspeed", + "display_name": "Kimi K2.7 Code Highspeed", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 131072 + "context": 262000, + "output": 262000 }, - "temperature": true, - "tool_call": false, + "temperature": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, - "open_weights": false, - "release_date": "2025-11-05", - "last_updated": "2025-11-05", + "extra_capabilities": { + "reasoning": { + "supported": true + } + }, + "attachment": true, + "open_weights": true, + "knowledge": "2025-01", + "release_date": "2026-06-12", + "last_updated": "2026-06-12", "type": "chat" }, { - "id": "meituan/longcat-flash-lite", - "name": "Meituan/Longcat-Flash-Lite", - "display_name": "Meituan/Longcat-Flash-Lite", + "id": "moonshotai/Kimi-K2.7-Code", + "name": "Kimi K2.7 Code", + "display_name": "Kimi K2.7 Code", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" @@ -235934,37 +234193,45 @@ }, "limit": { "context": 256000, - "output": 320000 + "output": 256000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, "default": true }, - "attachment": false, - "open_weights": false, - "release_date": "2026-02-06", - "last_updated": "2026-02-06", + "extra_capabilities": { + "reasoning": { + "supported": true + } + }, + "attachment": true, + "open_weights": true, + "knowledge": "2025-01", + "release_date": "2026-06-12", + "last_updated": "2026-06-12", "type": "chat" }, { - "id": "minimax/minimax-m2.1", - "name": "Minimax/Minimax-M2.1", - "display_name": "Minimax/Minimax-M2.1", + "id": "moonshotai/Kimi-K3", + "name": "Kimi K3", + "display_name": "Kimi K3", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 204800, - "output": 128000 + "context": 1000000, + "output": 131072 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -235972,19 +234239,24 @@ }, "extra_capabilities": { "reasoning": { - "supported": true + "supported": true, + "effort_options": [ + "low", + "high", + "max" + ] } }, - "attachment": false, + "attachment": true, "open_weights": true, - "release_date": "2025-12-23", - "last_updated": "2025-12-23", + "release_date": "2026-07-16", + "last_updated": "2026-07-16", "type": "chat" }, { - "id": "minimax/minimax-m2", - "name": "Minimax/Minimax-M2", - "display_name": "Minimax/Minimax-M2", + "id": "zai-org/GLM-5.2", + "name": "GLM-5.2", + "display_name": "GLM-5.2", "modalities": { "input": [ "text" @@ -235994,8 +234266,8 @@ ] }, "limit": { - "context": 200000, - "output": 128000 + "context": 1000000, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -236003,16 +234275,25 @@ "supported": true, "default": true }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "effort_options": [ + "high", + "max" + ] + } + }, "attachment": false, "open_weights": true, - "release_date": "2025-10-27", - "last_updated": "2025-10-27", + "release_date": "2026-06-13", + "last_updated": "2026-06-13", "type": "chat" }, { - "id": "minimax/minimax-m2.5", - "name": "Minimax/Minimax-M2.5", - "display_name": "Minimax/Minimax-M2.5", + "id": "zai-org/GLM-5.3", + "name": "GLM-5.3", + "display_name": "GLM-5.3", "modalities": { "input": [ "text" @@ -236022,8 +234303,8 @@ ] }, "limit": { - "context": 204800, - "output": 128000 + "context": 1000000, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -236033,19 +234314,24 @@ }, "extra_capabilities": { "reasoning": { - "supported": true + "supported": true, + "effort_options": [ + "low", + "high", + "max" + ] } }, "attachment": false, "open_weights": true, - "release_date": "2026-02-12", - "last_updated": "2026-02-12", + "release_date": "2026-08-14", + "last_updated": "2026-08-14", "type": "chat" }, { - "id": "minimax/minimax-m2.5-highspeed", - "name": "Minimax/Minimax-M2.5 Highspeed", - "display_name": "Minimax/Minimax-M2.5 Highspeed", + "id": "zai-org/GLM-5.2-Fast", + "name": "GLM-5.2-Fast", + "display_name": "GLM-5.2-Fast", "modalities": { "input": [ "text" @@ -236055,8 +234341,8 @@ ] }, "limit": { - "context": 204800, - "output": 128000 + "context": 1000000, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -236066,58 +234352,75 @@ }, "extra_capabilities": { "reasoning": { - "supported": true + "effort_options": [ + "high", + "max" + ] } }, "attachment": false, "open_weights": true, - "release_date": "2026-02-13", - "last_updated": "2026-02-13", + "release_date": "2026-06-13", + "last_updated": "2026-06-13", "type": "chat" }, { - "id": "x-ai/grok-4.1-fast-non-reasoning", - "name": "X-Ai/Grok 4.1 Fast Non Reasoning", - "display_name": "X-Ai/Grok 4.1 Fast Non Reasoning", + "id": "stepfun/Step-3.7-Flash", + "name": "Step 3.7 Flash", + "display_name": "Step 3.7 Flash", "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 2000000, - "output": 2000000 + "context": 256000, + "output": 256000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "low", + "medium", + "high" + ] + } }, "attachment": true, - "open_weights": false, - "release_date": "2025-11-19", - "last_updated": "2025-11-19", + "open_weights": true, + "knowledge": "2026-03-01", + "release_date": "2026-05-29", + "last_updated": "2026-05-29", "type": "chat" }, { - "id": "x-ai/grok-4.1-fast", - "name": "x-AI/Grok-4.1-Fast", - "display_name": "x-AI/Grok-4.1-Fast", + "id": "stepfun/Step-5-Preview", + "name": "Step 5 Preview", + "display_name": "Step 5 Preview", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 2000000, - "output": 2000000 + "context": 1000000, + "output": 1000000 }, "temperature": true, "tool_call": true, @@ -236125,28 +234428,36 @@ "supported": true, "default": true }, - "attachment": false, - "open_weights": false, - "release_date": "2025-11-20", - "last_updated": "2025-11-20", + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "low", + "medium", + "high" + ] + } + }, + "attachment": true, + "open_weights": true, + "release_date": "2026-09-16", + "last_updated": "2026-09-20", "type": "chat" }, { - "id": "x-ai/grok-4.1-fast-reasoning", - "name": "X-Ai/Grok 4.1 Fast Reasoning", - "display_name": "X-Ai/Grok 4.1 Fast Reasoning", + "id": "stepfun/Step-3.5-Flash", + "name": "Step 3.5 Flash", + "display_name": "Step 3.5 Flash", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 20000000, - "output": 2000000 + "context": 256000, + "output": 256000 }, "temperature": true, "tool_call": true, @@ -236154,16 +234465,25 @@ "supported": true, "default": true }, - "attachment": true, - "open_weights": false, - "release_date": "2025-11-19", - "last_updated": "2025-11-19", + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "low", + "high" + ] + } + }, + "attachment": false, + "open_weights": true, + "knowledge": "2025-01", + "release_date": "2026-01-29", + "last_updated": "2026-02-13", "type": "chat" }, { - "id": "x-ai/grok-code-fast-1", - "name": "x-AI/Grok-Code-Fast 1", - "display_name": "x-AI/Grok-Code-Fast 1", + "id": "tencent/hy4-preview", + "name": "Hy4 preview", + "display_name": "Hy4 preview", "modalities": { "input": [ "text" @@ -236173,8 +234493,8 @@ ] }, "limit": { - "context": 256000, - "output": 10000 + "context": 1024000, + "output": 64000 }, "temperature": true, "tool_call": true, @@ -236184,33 +234504,33 @@ }, "extra_capabilities": { "reasoning": { - "supported": true + "supported": true, + "effort_options": [ + "high" + ] } }, "attachment": false, - "open_weights": false, - "release_date": "2025-09-02", - "last_updated": "2025-09-02", + "open_weights": true, + "release_date": "2026-08-28", + "last_updated": "2026-08-28", "type": "chat" }, { - "id": "x-ai/grok-4-fast", - "name": "x-AI/Grok-4-Fast", - "display_name": "x-AI/Grok-4-Fast", + "id": "tencent/hy3-paid", + "name": "Hy3", + "display_name": "Hy3", "modalities": { "input": [ - "text", - "image", - "audio", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 2000000, - "output": 2000000 + "context": 256000, + "output": 128000 }, "temperature": true, "tool_call": true, @@ -236218,30 +234538,39 @@ "supported": true, "default": true }, - "attachment": true, - "open_weights": false, - "release_date": "2025-09-20", - "last_updated": "2025-09-20", + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "low", + "high" + ] + } + }, + "attachment": false, + "open_weights": true, + "release_date": "2026-07-06", + "last_updated": "2026-07-06", "type": "chat" }, { - "id": "x-ai/grok-4-fast-reasoning", - "name": "X-Ai/Grok-4-Fast-Reasoning", - "display_name": "X-Ai/Grok-4-Fast-Reasoning", + "id": "google/gemini-3.5-flash", + "name": "Gemini 3.5 Flash", + "display_name": "Gemini 3.5 Flash", "modalities": { "input": [ "text", "image", + "video", "audio", - "video" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 2000000, - "output": 2000000 + "context": 1000000, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -236249,57 +234578,93 @@ "supported": true, "default": true }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "level", + "level": "high", + "level_options": [ + "minimal", + "low", + "medium", + "high" + ], + "summaries": true, + "visibility": "summary", + "continuation": [ + "thought_signatures" + ] + } + }, "attachment": true, "open_weights": false, - "release_date": "2025-12-18", - "last_updated": "2025-12-18", + "knowledge": "2025-01", + "release_date": "2026-05-19", + "last_updated": "2026-05-19", "type": "chat" }, { - "id": "x-ai/grok-4-fast-non-reasoning", - "name": "X-Ai/Grok-4-Fast-Non-Reasoning", - "display_name": "X-Ai/Grok-4-Fast-Non-Reasoning", + "id": "google/gemini-4-argon", + "name": "Gemini 4 Argon", + "display_name": "Gemini 4 Argon", "modalities": { "input": [ "text", "image", + "video", "audio", - "video" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 2000000, - "output": 2000000 + "context": 1000000, + "output": 65536 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "low", + "medium", + "high" + ] + } }, "attachment": true, "open_weights": false, - "release_date": "2025-12-18", - "last_updated": "2025-12-18", + "knowledge": "2026-03", + "release_date": "2026-10-13", + "last_updated": "2026-10-13", "type": "chat" }, { - "id": "z-ai/glm-4.7", - "name": "Z-Ai/GLM 4.7", - "display_name": "Z-Ai/GLM 4.7", + "id": "google/gemini-3.6-flash", + "name": "Gemini 3.6 Flash", + "display_name": "Gemini 3.6 Flash", "modalities": { "input": [ - "text" + "text", + "image", + "video", + "audio", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 200000 + "context": 1000000, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -236310,36 +234675,48 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, + "default_enabled": true, + "mode": "level", + "level": "high", + "level_options": [ + "minimal", + "low", + "medium", + "high" + ], "summaries": true, "visibility": "summary", "continuation": [ - "thinking_blocks" + "thought_signatures" ] } }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-12-22", - "last_updated": "2025-12-22", + "attachment": true, + "open_weights": false, + "knowledge": "2026-03", + "release_date": "2026-07-21", + "last_updated": "2026-07-21", "type": "chat" }, { - "id": "z-ai/glm-4.6", - "name": "Z-AI/GLM 4.6", - "display_name": "Z-AI/GLM 4.6", + "id": "google/gemini-3.8-flash", + "name": "Gemini 3.8 Flash", + "display_name": "Gemini 3.8 Flash", "modalities": { "input": [ - "text" + "text", + "image", + "video", + "audio", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 200000 + "context": 1000000, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -236349,59 +234726,48 @@ }, "extra_capabilities": { "reasoning": { - "supported": true + "supported": true, + "default_enabled": true, + "mode": "level", + "level": "high", + "level_options": [ + "minimal", + "low", + "medium", + "high" + ], + "summaries": true, + "visibility": "summary", + "continuation": [ + "thought_signatures" + ] } }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-09-30", - "last_updated": "2025-09-30", - "type": "chat" - }, - { - "id": "z-ai/autoglm-phone-9b", - "name": "Z-Ai/Autoglm Phone 9b", - "display_name": "Z-Ai/Autoglm Phone 9b", - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 12800, - "output": 4096 - }, - "temperature": true, - "tool_call": true, - "reasoning": { - "supported": false - }, "attachment": true, "open_weights": false, - "release_date": "2025-12-23", - "last_updated": "2025-12-23", + "release_date": "2026-09-02", + "last_updated": "2026-09-02", "type": "chat" }, { - "id": "z-ai/glm-5", - "name": "Z-Ai/GLM 5", - "display_name": "Z-Ai/GLM 5", + "id": "google/gemini-3.7-flash", + "name": "Gemini 3.7 Flash", + "display_name": "Gemini 3.7 Flash", "modalities": { "input": [ - "text" + "text", + "image", + "video", + "audio", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 128000 + "context": 1048576, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -236412,35 +234778,48 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, + "default_enabled": true, + "mode": "level", + "level": "high", + "level_options": [ + "minimal", + "low", + "medium", + "high" + ], "summaries": true, "visibility": "summary", "continuation": [ - "thinking_blocks" + "thought_signatures" ] } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-02-12", - "last_updated": "2026-02-12", + "attachment": true, + "open_weights": false, + "knowledge": "2026-03", + "release_date": "2026-08-13", + "last_updated": "2026-08-13", "type": "chat" }, { - "id": "deepseek/deepseek-v3.2-exp-thinking", - "name": "DeepSeek/DeepSeek-V3.2-Exp-Thinking", - "display_name": "DeepSeek/DeepSeek-V3.2-Exp-Thinking", + "id": "google/gemini-3.1-flash-lite", + "name": "Gemini 3.1 Flash Lite", + "display_name": "Gemini 3.1 Flash Lite", "modalities": { "input": [ - "text" + "text", + "image", + "video", + "audio", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 32000 + "context": 1000000, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -236448,55 +234827,82 @@ "supported": true, "default": true }, - "attachment": false, + "extra_capabilities": { + "reasoning": { + "supported": true, + "effort_options": [ + "low", + "medium", + "high" + ] + } + }, + "attachment": true, "open_weights": false, - "release_date": "2025-09-29", - "last_updated": "2025-09-29", + "knowledge": "2025-01", + "release_date": "2026-05-07", + "last_updated": "2026-05-07", "type": "chat" }, { - "id": "deepseek/deepseek-math-v2", - "name": "Deepseek/Deepseek-Math-V2", - "display_name": "Deepseek/Deepseek-Math-V2", + "id": "google/gemini-3.5-flash-lite", + "name": "Gemini 3.5 Flash Lite", + "display_name": "Gemini 3.5 Flash Lite", "modalities": { "input": [ - "text" + "text", + "image", + "video", + "audio", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 160000, - "output": 160000 + "context": 1000000, + "output": 65536 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { "supported": true, "default": true }, - "attachment": false, + "extra_capabilities": { + "reasoning": { + "supported": true, + "effort_options": [ + "low", + "medium", + "high" + ] + } + }, + "attachment": true, "open_weights": false, - "release_date": "2025-12-04", - "last_updated": "2025-12-04", + "knowledge": "2026-03", + "release_date": "2026-07-21", + "last_updated": "2026-07-21", "type": "chat" }, { - "id": "deepseek/deepseek-v3.1-terminus-thinking", - "name": "DeepSeek/DeepSeek-V3.1-Terminus-Thinking", - "display_name": "DeepSeek/DeepSeek-V3.1-Terminus-Thinking", + "id": "mistral/mistral-large-4", + "name": "Mistral Large 4", + "display_name": "Mistral Large 4", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 32000 + "context": 524288, + "output": 262144 }, "temperature": true, "tool_call": true, @@ -236504,81 +234910,114 @@ "supported": true, "default": true }, - "attachment": false, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "high" + ] + } + }, + "attachment": true, "open_weights": false, - "release_date": "2025-09-22", - "last_updated": "2025-09-22", + "release_date": "2026-10-06", + "last_updated": "2026-10-06", "type": "chat" }, { - "id": "deepseek/deepseek-v3.2-exp", - "name": "DeepSeek/DeepSeek-V3.2-Exp", - "display_name": "DeepSeek/DeepSeek-V3.2-Exp", + "id": "deepseek/deepseek-v4.1-flash-fast", + "name": "DeepSeek V4.1 Flash Fast", + "display_name": "DeepSeek V4.1 Flash Fast", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 32000 + "context": 1000000, + "output": 384000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, - "open_weights": false, - "release_date": "2025-09-29", - "last_updated": "2025-09-29", + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "low", + "high", + "max" + ] + } + }, + "attachment": true, + "open_weights": true, + "knowledge": "2025-05", + "release_date": "2026-09-10", + "last_updated": "2026-09-10", "type": "chat" }, { - "id": "deepseek/deepseek-v3.1-terminus", - "name": "DeepSeek/DeepSeek-V3.1-Terminus", - "display_name": "DeepSeek/DeepSeek-V3.1-Terminus", + "id": "deepseek/deepseek-v4.1-flash", + "name": "DeepSeek V4.1 Flash", + "display_name": "DeepSeek V4.1 Flash", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 32000 + "context": 1000000, + "output": 384000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, - "open_weights": false, - "release_date": "2025-09-22", - "last_updated": "2025-09-22", + "extra_capabilities": { + "reasoning": { + "supported": true, + "effort_options": [ + "low", + "high", + "max" + ] + } + }, + "attachment": true, + "open_weights": true, + "knowledge": "2025-05", + "release_date": "2026-09-10", + "last_updated": "2026-09-10", "type": "chat" }, { - "id": "deepseek/deepseek-v3.2-251201", - "name": "Deepseek/DeepSeek-V3.2", - "display_name": "Deepseek/DeepSeek-V3.2", + "id": "deepseek/deepseek-v4-flash-vision-exp", + "name": "DeepSeek V4 Flash Vision Exp", + "display_name": "DeepSeek V4 Flash Vision Exp", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 32000 + "context": 1000000, + "output": 384000 }, "temperature": true, "tool_call": true, @@ -236586,17 +235025,38 @@ "supported": true, "default": true }, - "attachment": false, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "high", + "effort_options": [ + "low", + "high", + "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." + ] + } + }, + "attachment": true, "open_weights": true, - "knowledge": "2024-07", - "release_date": "2025-12-01", - "last_updated": "2025-12-01", + "release_date": "2026-08-21", + "last_updated": "2026-09-01", "type": "chat" }, { - "id": "openai/gpt-5", - "name": "OpenAI/GPT-5", - "display_name": "OpenAI/GPT-5", + "id": "deepseek/deepseek-v4-pro", + "name": "DeepSeek V4 Pro", + "display_name": "DeepSeek V4 Pro", "modalities": { "input": [ "text" @@ -236606,8 +235066,8 @@ ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 1000000, + "output": 384000 }, "temperature": true, "tool_call": true, @@ -236620,98 +235080,96 @@ "supported": true, "default_enabled": true, "mode": "effort", - "effort": "medium", + "effort": "high", "effort_options": [ - "minimal", "low", - "medium", - "high" + "high", + "max" ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ], - "visibility": "hidden" + "notes": [ + "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." + ] } }, "attachment": false, - "open_weights": false, - "knowledge": "2024-09-30", - "release_date": "2025-08-07", - "last_updated": "2025-08-07", + "open_weights": true, + "knowledge": "2025-05", + "release_date": "2026-04-24", + "last_updated": "2026-04-24", "type": "chat" }, { - "id": "openai/gpt-5.2", - "name": "OpenAI/GPT-5.2", - "display_name": "OpenAI/GPT-5.2", + "id": "deepseek/deepseek-v4-flash", + "name": "DeepSeek V4 Flash", + "display_name": "DeepSeek V4 Flash", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 1000000, + "output": 384000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, + "default_enabled": true, "mode": "effort", - "effort": "none", + "effort": "high", "effort_options": [ - "none", "low", - "medium", "high", - "xhigh" + "max" ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ], - "visibility": "hidden" + "notes": [ + "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." + ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2025-12-11", - "last_updated": "2025-12-11", + "attachment": false, + "open_weights": true, + "knowledge": "2025-05", + "release_date": "2026-04-24", + "last_updated": "2026-04-24", "type": "chat" }, { - "id": "moonshotai/kimi-k2.5", - "name": "Moonshotai/Kimi-K2.5", - "display_name": "Moonshotai/Kimi-K2.5", + "id": "deepseek/deepseek-v4-flash-fast", + "name": "DeepSeek V4 Flash Fast", + "display_name": "DeepSeek V4 Flash Fast", "modalities": { "input": [ - "text", - "image", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 256000, - "output": 256000 + "context": 1000000, + "output": 384000 }, "temperature": true, "tool_call": true, @@ -236721,26 +235179,24 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "low", + "high", + "max" ] } }, - "attachment": true, + "attachment": false, "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-01-27", - "last_updated": "2026-01-27", + "knowledge": "2025-05", + "release_date": "2026-04-24", + "last_updated": "2026-04-24", "type": "chat" }, { - "id": "moonshotai/kimi-k2-0905", - "name": "Kimi K2 0905", - "display_name": "Kimi K2 0905", + "id": "MiniMaxAI/MiniMax-M2.5", + "name": "MiniMax-M2.5", + "display_name": "MiniMax-M2.5", "modalities": { "input": [ "text" @@ -236750,24 +235206,30 @@ ] }, "limit": { - "context": 256000, - "output": 100000 + "context": 200000, + "output": 131072 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true + } }, "attachment": false, "open_weights": true, - "release_date": "2025-09-05", - "last_updated": "2025-09-05", + "release_date": "2026-02-12", + "last_updated": "2026-02-12", "type": "chat" }, { - "id": "moonshotai/kimi-k2-thinking", - "name": "Kimi K2 Thinking", - "display_name": "Kimi K2 Thinking", + "id": "MiniMaxAI/MiniMax-M2.7", + "name": "MiniMax-M2.7", + "display_name": "MiniMax-M2.7", "modalities": { "input": [ "text" @@ -236777,8 +235239,8 @@ ] }, "limit": { - "context": 256000, - "output": 100000 + "context": 200000, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -236799,91 +235261,105 @@ }, "attachment": false, "open_weights": true, - "knowledge": "2024-08", - "release_date": "2025-11-06", - "last_updated": "2025-11-06", + "release_date": "2026-03-18", + "last_updated": "2026-03-18", "type": "chat" }, { - "id": "stepfun/step-3.5-flash", - "name": "Stepfun/Step-3.5 Flash", - "display_name": "Stepfun/Step-3.5 Flash", + "id": "MiniMaxAI/MiniMax-M3", + "name": "MiniMax-M3", + "display_name": "MiniMax-M3", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 64000, - "output": 4096 + "context": 1000000, + "output": 512000 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, + "extra_capabilities": { + "reasoning": { + "supported": true + } + }, + "attachment": true, "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-01-29", - "last_updated": "2026-02-13", + "release_date": "2026-06-01", + "last_updated": "2026-06-01", "type": "chat" }, { - "id": "stepfun-ai/gelab-zero-4b-preview", - "name": "Stepfun-Ai/Gelab Zero 4b Preview", - "display_name": "Stepfun-Ai/Gelab Zero 4b Preview", + "id": "meta/muse-spark-1.1", + "name": "Muse Spark 1.1", + "display_name": "Muse Spark 1.1", "modalities": { "input": [ "text", - "image" + "image", + "pdf", + "video" ], "output": [ "text" ] }, "limit": { - "context": 8192, - "output": 4096 + "context": 1048576, + "output": 131072 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "effort_options": [ + "low", + "medium", + "high", + "xhigh" + ] + } }, "attachment": true, "open_weights": false, - "release_date": "2025-12-23", - "last_updated": "2025-12-23", + "release_date": "2026-04-08", + "last_updated": "2026-07-09", "type": "chat" - } - ] - }, - "zeldoc": { - "id": "zeldoc", - "name": "Zeldoc", - "display_name": "Zeldoc", - "api": "https://api.zeldoc.ai/v1", - "doc": "https://docs.zeldoc.ai", - "models": [ + }, { - "id": "zdev", - "name": "ZDev", - "display_name": "ZDev", + "id": "meta/muse-spark-1.3-contributor", + "name": "Muse Spark 1.3 (Contributor)", + "display_name": "Muse Spark 1.3 (Contributor)", "modalities": { "input": [ "text", - "image" + "image", + "video", + "pdf", + "audio" ], "output": [ "text" ] }, "limit": { - "context": 1000000, + "context": 1048576, "output": 131072 }, "temperature": true, @@ -236894,53 +235370,41 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], "effort_options": [ "low", + "medium", "high", - "max" + "xhigh" ] } }, "attachment": true, "open_weights": false, - "release_date": "2026-08-26", - "last_updated": "2026-08-26", + "release_date": "2026-09-02", + "last_updated": "2026-09-02", "type": "chat" - } - ] - }, - "xpersona": { - "id": "xpersona", - "name": "Xpersona", - "display_name": "Xpersona", - "api": "https://www.xpersona.co/v1", - "doc": "https://www.xpersona.co/docs", - "models": [ + }, { - "id": "gpt-5.5", - "name": "GPT-5.5", - "display_name": "GPT-5.5", + "id": "meta/muse-wonder-1.0", + "name": "Muse Wonder", + "display_name": "Muse Wonder", "modalities": { "input": [ "text", - "image" + "image", + "video", + "pdf", + "audio" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 1048576, + "output": 131072 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -236948,112 +235412,85 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", "effort_options": [ "low", "medium", "high", - "xhigh" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "xhigh", + "max" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-12-01", - "release_date": "2026-04-23", - "last_updated": "2026-04-23", + "release_date": "2026-10-12", + "last_updated": "2026-10-12", "type": "chat" }, { - "id": "gpt-5.4", - "name": "GPT-5.4", - "display_name": "GPT-5.4", + "id": "meta/muse-spark-1.2", + "name": "Muse Spark 1.2", + "display_name": "Muse Spark 1.2", "modalities": { "input": [ "text", - "image" + "image", + "video", + "pdf", + "audio" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 1048576, + "output": 131072 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "none", "effort_options": [ - "none", "low", "medium", "high", "xhigh" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-03-05", - "last_updated": "2026-03-05", + "release_date": "2026-08-05", + "last_updated": "2026-08-05", "type": "chat" }, { - "id": "gpt-5.6-sol", - "name": "GPT-5.6 Sol", - "display_name": "GPT-5.6 Sol", + "id": "meta/muse-spark-1.2-contributor", + "name": "Muse Spark 1.2 (Contributor)", + "display_name": "Muse Spark 1.2 (Contributor)", "modalities": { "input": [ "text", - "image" + "image", + "video", + "pdf", + "audio" ], "output": [ "text" ] }, "limit": { - "context": 372000, - "output": 128000 + "context": 1048576, + "output": 131072 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -237061,115 +235498,81 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", "effort_options": [ - "none", "low", "medium", "high", - "xhigh", - "max" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "xhigh" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-02-16", - "release_date": "2026-07-09", - "last_updated": "2026-07-09", + "release_date": "2026-08-05", + "last_updated": "2026-08-05", "type": "chat" }, { - "id": "claude-sonnet-4-6", - "name": "Claude Sonnet 4.6", - "display_name": "Claude Sonnet 4.6", + "id": "meta/muse-spark-1.3", + "name": "Muse Spark 1.3", + "display_name": "Muse Spark 1.3", "modalities": { "input": [ "text", - "image" + "image", + "video", + "pdf", + "audio" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 128000 + "context": 1048576, + "output": 131072 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "mixed", - "budget": { - "min": 1024, - "unit": "tokens" - }, - "effort": "high", "effort_options": [ "low", "medium", "high", + "xhigh", "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Anthropic recommends adaptive thinking with effort for Claude 4.6; budget_tokens remains a deprecated compatibility path.", - "Anthropic API defaults effort to high; lower effort levels should be chosen per workload." ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-02-17", - "last_updated": "2026-03-13", + "release_date": "2026-09-02", + "last_updated": "2026-09-02", "type": "chat" }, { - "id": "gpt-5.6-terra", - "name": "GPT-5.6 Terra", - "display_name": "GPT-5.6 Terra", + "id": "inclusionai/ling-3.1-flash:free", + "name": "Ling 3.1 Flash", + "display_name": "Ling 3.1 Flash", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 372000, - "output": 128000 + "context": 262144, + "output": 32768 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -237177,55 +235580,44 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", "effort_options": [ - "none", - "low", - "medium", - "high", - "xhigh", - "max" - ], - "verbosity": "medium", - "verbosity_options": [ "low", "medium", "high" - ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-02-16", - "release_date": "2026-07-09", - "last_updated": "2026-07-09", + "attachment": false, + "open_weights": true, + "release_date": "2026-09-29", + "last_updated": "2026-09-29", "type": "chat" - }, + } + ] + }, + "azure-cognitive-services": { + "id": "azure-cognitive-services", + "name": "Azure Cognitive Services", + "display_name": "Azure Cognitive Services", + "doc": "https://learn.microsoft.com/en-us/azure/ai-services/openai/concepts/models", + "models": [ { - "id": "xpersona-frieren-coder", - "name": "Xpersona Frieren 1", - "display_name": "Xpersona Frieren 1", + "id": "gpt-5-pro", + "name": "GPT-5 Pro", + "display_name": "GPT-5 Pro", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 384000 + "context": 400000, + "output": 128000 }, "temperature": false, "tool_call": true, @@ -237235,38 +235627,41 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ + "supported": true, + "default_enabled": true, + "mode": "fixed", + "effort": "high", + "verbosity": "medium", + "verbosity_options": [ "low", "medium", - "high", - "xhigh", - "max" - ] + "high" + ], + "visibility": "hidden" } }, "attachment": true, "open_weights": false, - "knowledge": "2025-12-30", - "release_date": "2026-05-01", - "last_updated": "2026-05-25", + "knowledge": "2024-09-30", + "release_date": "2025-10-06", + "last_updated": "2025-10-06", "type": "chat" }, { - "id": "xpersona-gpt-5.5", - "name": "GPT-5.5", - "display_name": "GPT-5.5", + "id": "o3-mini", + "name": "o3-mini", + "display_name": "o3-mini", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 200000, + "output": 100000 }, "temperature": false, "tool_call": true, @@ -237277,43 +235672,40 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], + "default_enabled": true, + "mode": "effort", + "effort": "medium", "effort_options": [ "low", "medium", - "high", - "xhigh", - "max" - ] + "high" + ], + "visibility": "hidden" } }, - "attachment": true, + "attachment": false, "open_weights": false, - "knowledge": "2025-12-30", - "release_date": "2026-04-23", - "last_updated": "2026-04-23", + "knowledge": "2024-05", + "release_date": "2024-12-20", + "last_updated": "2025-01-29", "type": "chat" }, { - "id": "gpt-5.4-mini", - "name": "GPT-5.4 mini", - "display_name": "GPT-5.4 mini", + "id": "gpt-5.4-nano", + "name": "GPT-5.4 Nano", + "display_name": "GPT-5.4 Nano", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 272000, + "context": 400000, "output": 128000 }, "temperature": true, @@ -237341,12 +235733,7 @@ "medium", "high" ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] + "visibility": "hidden" } }, "attachment": true, @@ -237357,198 +235744,166 @@ "type": "chat" }, { - "id": "claude-haiku-4-5", - "name": "Claude Haiku 4.5 (latest)", - "display_name": "Claude Haiku 4.5 (latest)", + "id": "deepseek-v3.2-speciale", + "name": "DeepSeek-V3.2-Speciale", + "display_name": "DeepSeek-V3.2-Speciale", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 200000, + "context": 128000, "output": 128000 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { "supported": true, - "default": false - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "budget", - "budget": { - "min": 1024, - "unit": "tokens" - }, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", - "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." - ] - } + "default": true }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-02-28", - "release_date": "2025-10-15", - "last_updated": "2025-10-15", + "attachment": false, + "open_weights": true, + "knowledge": "2024-07", + "release_date": "2025-12-01", + "last_updated": "2025-12-01", "type": "chat" }, { - "id": "claude-opus-4-8", - "name": "Claude Opus 4.8", - "display_name": "Claude Opus 4.8", + "id": "gpt-6.1-terra", + "name": "GPT-6.1 Terra", + "display_name": "GPT-6.1 Terra", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 200000, + "context": 1050000, "output": 128000 }, "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "high", "effort_options": [ + "none", "low", "medium", "high", "xhigh", "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Claude Opus 4.7 and newer Opus models require thinking.type = \"adaptive\" to enable thinking explicitly.", - "Manual budget_tokens requests return 400 on Claude Opus 4.7 and newer adaptive-only Opus models.", - "task_budget is separate from thinking control and should not be treated as a thinking budget." ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-01", - "release_date": "2026-05-28", - "last_updated": "2026-05-28", + "knowledge": "2026-06-01", + "release_date": "2026-10-26", + "last_updated": "2026-10-26", "type": "chat" }, { - "id": "gemini-3.5-flash", - "name": "Gemini 3.5 Flash", - "display_name": "Gemini 3.5 Flash", + "id": "gpt-5.4", + "name": "GPT-5.4", + "display_name": "GPT-5.4", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1000000, + "context": 1050000, "output": 128000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "level", - "level": "high", - "level_options": [ - "minimal", + "default_enabled": false, + "mode": "effort", + "effort": "none", + "effort_options": [ + "none", + "low", + "medium", + "high", + "xhigh" + ], + "verbosity": "medium", + "verbosity_options": [ "low", "medium", "high" ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] + "visibility": "hidden" } }, "attachment": true, "open_weights": false, - "knowledge": "2025-01", - "release_date": "2026-05-19", - "last_updated": "2026-05-19", + "knowledge": "2025-08-31", + "release_date": "2026-03-05", + "last_updated": "2026-03-05", "type": "chat" }, { - "id": "gpt-5.6", - "name": "GPT-5.6", - "display_name": "GPT-5.6", + "id": "gpt-5.4-mini", + "name": "GPT-5.4 Mini", + "display_name": "GPT-5.4 Mini", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 372000, + "context": 400000, "output": 128000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, + "default_enabled": false, "mode": "effort", - "effort": "medium", + "effort": "none", "effort_options": [ "none", "low", "medium", "high", - "xhigh", - "max" + "xhigh" ], "verbosity": "medium", "verbosity_options": [ @@ -237556,144 +235911,117 @@ "medium", "high" ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] + "visibility": "hidden" } }, "attachment": true, "open_weights": false, - "knowledge": "2026-02-16", - "release_date": "2026-07-09", - "last_updated": "2026-07-09", + "knowledge": "2025-08-31", + "release_date": "2026-03-17", + "last_updated": "2026-03-17", "type": "chat" }, { - "id": "claude-fable-5", - "name": "Claude Fable 5", - "display_name": "Claude Fable 5", + "id": "gpt-5.1-codex-max", + "name": "GPT-5.1 Codex Max", + "display_name": "GPT-5.1 Codex Max", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1000000, + "context": 400000, "output": 128000 }, "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, + "default_enabled": false, "mode": "effort", - "effort": "high", + "effort": "none", "effort_options": [ + "none", "low", "medium", - "high", - "xhigh", - "max" + "high" ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" ], - "notes": [ - "Adaptive thinking is always on for Claude Fable 5 and Claude Mythos 5; thinking.type = \"disabled\" is rejected.", - "Manual budget_tokens requests return 400 on Claude Fable 5 and Claude Mythos 5.", - "thinking.display defaults to omitted; set display to summarized to receive readable thinking summaries." - ] + "visibility": "hidden" } }, "attachment": true, "open_weights": false, - "knowledge": "2026-01-31", - "release_date": "2026-06-09", - "last_updated": "2026-06-09", + "knowledge": "2024-09-30", + "release_date": "2025-11-13", + "last_updated": "2025-11-13", "type": "chat" - } - ] - }, - "inco": { - "id": "inco", - "name": "Inco", - "display_name": "Inco", - "api": "https://api.inco.ai/v1", - "doc": "https://platform.inco.ai/docs", - "models": [ + }, { - "id": "glm-5.3:fast", - "name": "GLM-5.3 Fast", - "display_name": "GLM-5.3 Fast", + "id": "phi-4-multimodal", + "name": "Phi-4-multimodal", + "display_name": "Phi-4-multimodal", "modalities": { "input": [ - "text" + "text", + "image", + "audio" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 128000, + "output": 4096 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } + "supported": false }, - "attachment": false, + "attachment": true, "open_weights": true, - "release_date": "2026-08-14", - "last_updated": "2026-08-14", + "knowledge": "2023-10", + "release_date": "2024-12-11", + "last_updated": "2024-12-11", "type": "chat" }, { - "id": "minimax-m3", - "name": "MiniMax-M3", - "display_name": "MiniMax-M3", + "id": "gpt-6-sol", + "name": "GPT-6 Sol", + "display_name": "GPT-6 Sol", "modalities": { "input": [ "text", "image", - "video" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 512000 + "context": 1050000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -237701,40 +236029,42 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "none", + "low", + "medium", + "high", + "xhigh", + "max" ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-06-01", - "last_updated": "2026-06-01", + "open_weights": false, + "knowledge": "2026-04-20", + "release_date": "2026-09-22", + "last_updated": "2026-09-22", "type": "chat" }, { - "id": "minimax-m3:fast", - "name": "MiniMax M3 Fast", - "display_name": "MiniMax M3 Fast", + "id": "gpt-5.6-terra", + "name": "GPT-5.6 Terra", + "display_name": "GPT-5.6 Terra", "modalities": { "input": [ "text", "image", - "video" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 512000 + "context": 1050000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -237743,70 +236073,97 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] + "default_enabled": true, + "mode": "effort", + "effort": "medium", + "effort_options": [ + "none", + "low", + "medium", + "high", + "xhigh", + "max" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, "attachment": true, + "open_weights": false, + "knowledge": "2026-02-16", + "release_date": "2026-07-09", + "last_updated": "2026-07-09", + "type": "chat" + }, + { + "id": "phi-4-mini", + "name": "Phi-4-mini", + "display_name": "Phi-4-mini", + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 4096 + }, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": false + }, + "attachment": false, "open_weights": true, - "release_date": "2026-06-01", - "last_updated": "2026-06-01", + "knowledge": "2023-10", + "release_date": "2024-12-11", + "last_updated": "2024-12-11", "type": "chat" }, { - "id": "kimi-k3:fast", - "name": "Kimi K3", - "display_name": "Kimi K3", + "id": "cohere-embed-v-4-0", + "name": "Embed v4", + "display_name": "Embed v4", "modalities": { "input": [ "text", - "image", - "video" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 128000, + "output": 1536 }, "temperature": false, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } + "supported": false }, "attachment": true, "open_weights": true, - "release_date": "2026-07-16", - "last_updated": "2026-07-16", - "type": "chat" + "release_date": "2025-04-15", + "last_updated": "2025-04-15", + "type": "embedding" }, { - "id": "glm-5.3-flash:fast", - "name": "GLM-5.3-Flash", - "display_name": "GLM-5.3-Flash", + "id": "gpt-5.3-codex", + "name": "GPT-5.3 Codex", + "display_name": "GPT-5.3 Codex", "modalities": { "input": [ "text", "image", - "video", "pdf" ], "output": [ @@ -237814,10 +236171,10 @@ ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 400000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -237826,38 +236183,50 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] + "default_enabled": true, + "mode": "effort", + "effort": "medium", + "effort_options": [ + "low", + "medium", + "high", + "xhigh" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, "attachment": true, - "open_weights": true, - "release_date": "2026-08-26", - "last_updated": "2026-08-26", + "open_weights": false, + "knowledge": "2025-08-31", + "release_date": "2026-02-24", + "last_updated": "2026-02-24", "type": "chat" }, { - "id": "deepseek-v4.1-flash:fast", - "name": "DeepSeek V4.1 Flash", - "display_name": "DeepSeek V4.1 Flash", + "id": "gpt-6-astra", + "name": "GPT-6 Astra", + "display_name": "GPT-6 Astra", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 384000 + "context": 1050000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -237866,86 +236235,94 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "none", + "low", + "medium", + "high", + "xhigh", + "max" ] } }, "attachment": true, - "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-09-10", - "last_updated": "2026-09-10", + "open_weights": false, + "knowledge": "2026-04-30", + "release_date": "2026-09-04", + "last_updated": "2026-09-04", "type": "chat" }, { - "id": "glm-5.3", - "name": "GLM-5.3", - "display_name": "GLM-5.3", + "id": "gpt-5.1-codex", + "name": "GPT-5.1 Codex", + "display_name": "GPT-5.1 Codex", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 400000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] + "default_enabled": false, + "mode": "effort", + "effort": "none", + "effort_options": [ + "none", + "low", + "medium", + "high" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-08-14", - "last_updated": "2026-08-14", + "attachment": true, + "open_weights": false, + "knowledge": "2024-09-30", + "release_date": "2025-11-14", + "last_updated": "2025-11-14", "type": "chat" - } - ] - }, - "sarvam": { - "id": "sarvam", - "name": "Sarvam AI", - "display_name": "Sarvam AI", - "api": "https://api.sarvam.ai/v1", - "doc": "https://docs.sarvam.ai/api-reference-docs/getting-started/models", - "models": [ + }, { - "id": "sarvam-30b", - "name": "Sarvam-30B", - "display_name": "Sarvam-30B", + "id": "claude-haiku-5-5", + "name": "Claude Haiku 5.5", + "display_name": "Claude Haiku 5.5", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 65536, - "output": 65536 + "context": 1000000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -237953,30 +236330,53 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], "effort_options": [ "low", "medium", - "high" + "high", + "xhigh", + "max" ] } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-02-18", - "last_updated": "2026-02-18", + "attachment": true, + "open_weights": false, + "knowledge": "2026-06", + "release_date": "2026-10-07", + "last_updated": "2026-10-07", "type": "chat" }, { - "id": "sarvam-105b", - "name": "Sarvam-105B", - "display_name": "Sarvam-105B", + "id": "gpt-image-2.5-sunburst", + "name": "GPT Image 2.5 Sunburst", + "display_name": "GPT Image 2.5 Sunburst", + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "image" + ] + }, + "limit": { + "context": 8192, + "output": 8192 + }, + "temperature": false, + "tool_call": false, + "reasoning": { + "supported": false + }, + "attachment": true, + "open_weights": false, + "release_date": "2026-09-08", + "last_updated": "2026-09-08" + }, + { + "id": "grok-4-20-non-reasoning", + "name": "Grok 4.20 (Non-Reasoning)", + "display_name": "Grok 4.20 (Non-Reasoning)", "modalities": { "input": [ "text" @@ -237986,65 +236386,45 @@ ] }, "limit": { - "context": 131072, - "output": 131072 + "context": 262000, + "output": 8192 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true + "supported": true }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "effort_options": [ - "low", - "medium", - "high" - ] + "supported": true } }, "attachment": false, - "open_weights": true, - "release_date": "2026-02-18", - "last_updated": "2026-02-18", + "open_weights": false, + "knowledge": "2025-09", + "release_date": "2026-04-08", + "last_updated": "2026-04-08", "type": "chat" - } - ] - }, - "volcengine": { - "id": "volcengine", - "name": "Volcengine Ark", - "display_name": "Volcengine Ark", - "api": "https://ark.cn-beijing.volces.com/api/v3", - "doc": "https://www.volcengine.com/docs/82379/1330310", - "models": [ + }, { - "id": "doubao-seed-character-260628", - "name": "Seed Character", - "display_name": "Seed Character", + "id": "gpt-5.2-codex", + "name": "GPT-5.2 Codex", + "display_name": "GPT-5.2 Codex", "modalities": { "input": [ "text", "image", - "video" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 256000, - "output": 256000 + "context": 400000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -238053,93 +236433,103 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], + "default_enabled": true, + "mode": "effort", + "effort": "medium", "effort_options": [ - "minimal", "low", + "medium", + "high", + "xhigh" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", "high" - ] + ], + "visibility": "hidden" } }, "attachment": true, "open_weights": false, - "release_date": "2026-06-23", - "last_updated": "2026-06-23", + "knowledge": "2025-08-31", + "release_date": "2026-01-14", + "last_updated": "2026-01-14", "type": "chat" }, { - "id": "doubao-seed-1-8-251228", - "name": "Seed 1.8", - "display_name": "Seed 1.8", + "id": "gpt-5.2", + "name": "GPT-5.2", + "display_name": "GPT-5.2", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 256000, - "output": 64000 + "context": 400000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], + "default_enabled": false, + "mode": "effort", + "effort": "none", "effort_options": [ "none", - "minimal", "low", "medium", "high", - "xhigh", - "max" - ] + "xhigh" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, "attachment": true, "open_weights": false, - "release_date": "2025-12-28", - "last_updated": "2025-12-28", + "knowledge": "2025-08-31", + "release_date": "2025-12-11", + "last_updated": "2025-12-11", "type": "chat" }, { - "id": "doubao-seed-2-1-turbo-260628", - "name": "Seed 2.1 Turbo", - "display_name": "Seed 2.1 Turbo", + "id": "gpt-5.4-pro", + "name": "GPT-5.4 Pro", + "display_name": "GPT-5.4 Pro", "modalities": { "input": [ "text", "image", - "video" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 256000, - "output": 256000 + "context": 1050000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -238148,87 +236538,76 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], + "default_enabled": true, + "mode": "effort", + "effort": "high", "effort_options": [ - "none", - "minimal", - "low", "medium", "high", - "xhigh", - "max" - ] + "xhigh" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, "attachment": true, "open_weights": false, - "release_date": "2026-06-23", - "last_updated": "2026-06-23", + "knowledge": "2025-08-31", + "release_date": "2026-03-05", + "last_updated": "2026-03-05", "type": "chat" }, { - "id": "doubao-seed-1-6-flash-250828", - "name": "Seed 1.6 Flash", - "display_name": "Seed 1.6 Flash", + "id": "gpt-image-1.5", + "name": "GPT-Image-1.5", + "display_name": "GPT-Image-1.5", "modalities": { "input": [ "text", - "image", - "video" + "image" ], "output": [ - "text" + "text", + "image" ] }, "limit": { - "context": 256000, - "output": 32000 + "context": 8192, + "output": 8192 }, - "temperature": true, - "tool_call": true, + "temperature": false, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } + "supported": false }, "attachment": true, "open_weights": false, - "release_date": "2025-08-28", - "last_updated": "2025-08-28", - "type": "chat" + "release_date": "2025-11-25", + "last_updated": "2025-11-25" }, { - "id": "glm-5-2-260617", - "name": "GLM-5.2", - "display_name": "GLM-5.2", + "id": "kimi-k2.7-code", + "name": "Kimi K2.7 Code", + "display_name": "Kimi K2.7 Code", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 262144, + "output": 262144 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -238242,40 +236621,35 @@ "visibility": "summary", "continuation": [ "thinking_blocks" - ], - "effort_options": [ - "minimal", - "low", - "medium", - "high" ] } }, - "attachment": false, + "attachment": true, "open_weights": true, - "release_date": "2026-06-13", - "last_updated": "2026-06-13", + "knowledge": "2025-01", + "release_date": "2026-06-12", + "last_updated": "2026-06-12", "type": "chat" }, { - "id": "doubao-seed-2-0-code-preview-260215", - "name": "Seed 2.0 Code", - "display_name": "Seed 2.0 Code", + "id": "gpt-6.1-sol", + "name": "GPT-6.1 Sol", + "display_name": "GPT-6.1 Sol", "modalities": { "input": [ "text", "image", - "video" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 131072 + "context": 1050000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -238283,16 +236657,7 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], "effort_options": [ - "none", - "minimal", "low", "medium", "high", @@ -238303,26 +236668,26 @@ }, "attachment": true, "open_weights": false, - "release_date": "2026-02-14", - "last_updated": "2026-02-14", + "knowledge": "2026-04-30", + "release_date": "2026-09-29", + "last_updated": "2026-09-29", "type": "chat" }, { - "id": "doubao-seed-1-6-vision-250815", - "name": "Seed 1.6 Vision", - "display_name": "Seed 1.6 Vision", + "id": "grok-4-20-reasoning", + "name": "Grok 4.20 (Reasoning)", + "display_name": "Grok 4.20 (Reasoning)", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 256000, - "output": 32000 + "context": 262000, + "output": 8192 }, "temperature": true, "tool_call": true, @@ -238332,85 +236697,121 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] + "supported": true } }, + "attachment": false, + "open_weights": false, + "knowledge": "2025-09", + "release_date": "2026-04-08", + "last_updated": "2026-04-08", + "type": "chat" + }, + { + "id": "gpt-4-turbo-vision", + "name": "GPT-4 Turbo Vision", + "display_name": "GPT-4 Turbo Vision", + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 4096 + }, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": false + }, "attachment": true, "open_weights": false, - "release_date": "2025-08-15", - "last_updated": "2025-08-15", + "knowledge": "2023-11", + "release_date": "2023-11-06", + "last_updated": "2024-04-09", "type": "chat" }, { - "id": "doubao-seed-1-6-251015", - "name": "Seed 1.6", - "display_name": "Seed 1.6", + "id": "claude-opus-4-7", + "name": "Claude Opus 4.7", + "display_name": "Claude Opus 4.7", "modalities": { "input": [ "text", "image", - "video" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 256000, - "output": 64000 + "context": 1000000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": false, + "mode": "effort", + "effort": "high", + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" + ], "interleaved": true, "summaries": true, - "visibility": "summary", + "visibility": "omitted", "continuation": [ "thinking_blocks" ], - "effort_options": [ - "minimal", - "low", - "medium", - "high" + "notes": [ + "Claude Opus 4.7 and newer Opus models require thinking.type = \"adaptive\" to enable thinking explicitly.", + "Manual budget_tokens requests return 400 on Claude Opus 4.7 and newer adaptive-only Opus models.", + "task_budget is separate from thinking control and should not be treated as a thinking budget." ] } }, "attachment": true, "open_weights": false, - "release_date": "2025-10-15", - "last_updated": "2025-10-15", + "knowledge": "2026-01-31", + "release_date": "2026-04-16", + "last_updated": "2026-04-16", "type": "chat" }, { - "id": "deepseek-v4-pro-ga-260813", - "name": "DeepSeek V4 Pro 0813", - "display_name": "DeepSeek V4 Pro 0813", + "id": "gpt-5-codex", + "name": "GPT-5-Codex", + "display_name": "GPT-5-Codex", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 384000 + "context": 400000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -238419,35 +236820,39 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], + "default_enabled": true, + "mode": "effort", + "effort": "medium", "effort_options": [ "minimal", "low", "medium", "high" - ] + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-08-13", - "last_updated": "2026-08-22", + "attachment": true, + "open_weights": false, + "knowledge": "2024-09-30", + "release_date": "2025-09-15", + "last_updated": "2025-09-15", "type": "chat" }, { - "id": "glm-5-3-flash-260828", - "name": "GLM-5.3-Flash", - "display_name": "GLM-5.3-Flash", + "id": "claude-haiku-4-5", + "name": "Claude Haiku 4.5", + "display_name": "Claude Haiku 4.5", "modalities": { "input": [ "text", "image", - "video", "pdf" ], "output": [ @@ -238455,41 +236860,47 @@ ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 200000, + "output": 64000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": false, + "mode": "budget", + "budget": { + "min": 1024, + "unit": "tokens" + }, "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ "thinking_blocks" ], - "effort_options": [ - "low", - "high", - "max" + "notes": [ + "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", + "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-08-26", - "last_updated": "2026-08-26", + "open_weights": false, + "knowledge": "2025-02-28", + "release_date": "2025-11-18", + "last_updated": "2025-11-18", "type": "chat" }, { - "id": "deepseek-v4-flash-ga-260731", - "name": "DeepSeek V4 Flash 0731", - "display_name": "DeepSeek V4 Flash 0731", + "id": "phi-4-reasoning-plus", + "name": "Phi-4-reasoning-plus", + "display_name": "Phi-4-reasoning-plus", "modalities": { "input": [ "text" @@ -238499,56 +236910,39 @@ ] }, "limit": { - "context": 1000000, - "output": 384000 + "context": 32000, + "output": 4096 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "effort_options": [ - "minimal", - "low", - "medium", - "high" - ] - } - }, "attachment": false, "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-07-31", - "last_updated": "2026-07-31", + "knowledge": "2023-10", + "release_date": "2024-12-11", + "last_updated": "2024-12-11", "type": "chat" }, { - "id": "doubao-seed-2-0-mini-260428", - "name": "Seed 2.0 Mini", - "display_name": "Seed 2.0 Mini", + "id": "grok-4.7", + "name": "Grok 4.7", + "display_name": "Grok 4.7", "modalities": { "input": [ "text", "image", - "video" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 256000, - "output": 131072 + "context": 500000, + "output": 500000 }, "temperature": true, "tool_call": true, @@ -238558,49 +236952,40 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], "effort_options": [ - "none", - "minimal", "low", "medium", "high", - "xhigh", - "max" + "xhigh" ] } }, "attachment": true, "open_weights": false, - "release_date": "2026-02-14", - "last_updated": "2026-02-14", + "knowledge": "2026-05", + "release_date": "2026-09-21", + "last_updated": "2026-09-21", "type": "chat" }, { - "id": "doubao-seed-2-0-lite-260428", - "name": "Seed 2.0 Lite", - "display_name": "Seed 2.0 Lite", + "id": "claude-sonnet-5", + "name": "Claude Sonnet 5", + "display_name": "Claude Sonnet 5", "modalities": { "input": [ "text", "image", - "video" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 256000, - "output": 131072 + "context": 1000000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -238609,15 +236994,7 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], "effort_options": [ - "none", - "minimal", "low", "medium", "high", @@ -238628,129 +237005,170 @@ }, "attachment": true, "open_weights": false, - "release_date": "2026-02-14", - "last_updated": "2026-02-14", + "knowledge": "2026-01-31", + "release_date": "2026-06-30", + "last_updated": "2026-06-30", "type": "chat" }, { - "id": "doubao-seed-evolving", - "name": "Seed Evolving", - "display_name": "Seed Evolving", + "id": "gpt-3.5-turbo-0125", + "name": "GPT-3.5 Turbo 0125", + "display_name": "GPT-3.5 Turbo 0125", + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 16384, + "output": 4096 + }, + "temperature": true, + "tool_call": false, + "reasoning": { + "supported": false + }, + "attachment": false, + "open_weights": false, + "knowledge": "2021-08", + "release_date": "2024-01-25", + "last_updated": "2024-01-25", + "type": "chat" + }, + { + "id": "mistral-medium-2505", + "name": "Mistral Medium 3", + "display_name": "Mistral Medium 3", "modalities": { "input": [ "text", - "image", - "video" + "image" ], "output": [ "text" ] }, "limit": { - "context": 256000, - "output": 256000 + "context": 128000, + "output": 128000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "effort_options": [ - "none", - "minimal", - "low", - "medium", - "high", - "xhigh", - "max" - ] - } + "supported": false }, "attachment": true, "open_weights": false, - "release_date": "2026-06-23", - "last_updated": "2026-06-23", + "knowledge": "2025-05", + "release_date": "2025-05-07", + "last_updated": "2025-05-07", "type": "chat" }, { - "id": "doubao-seed-2-0-pro-260215", - "name": "Seed 2.0 Pro", - "display_name": "Seed 2.0 Pro", + "id": "llama-4-scout-17b-16e-instruct", + "name": "Llama 4 Scout 17B 16E Instruct", + "display_name": "Llama 4 Scout 17B 16E Instruct", "modalities": { "input": [ "text", - "image", - "video" + "image" ], "output": [ "text" ] }, "limit": { - "context": 256000, - "output": 128000 + "context": 128000, + "output": 8192 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "effort_options": [ - "none", - "minimal", - "low", - "medium", - "high", - "xhigh", - "max" - ] - } + "supported": false }, "attachment": true, - "open_weights": false, - "release_date": "2026-02-14", - "last_updated": "2026-02-14", + "open_weights": true, + "knowledge": "2024-08", + "release_date": "2025-04-05", + "last_updated": "2025-04-05", "type": "chat" }, { - "id": "doubao-seed-2-1-pro-260628", - "name": "Seed 2.1 Pro", - "display_name": "Seed 2.1 Pro", + "id": "llama-3.3-70b-instruct", + "name": "Llama-3.3-70B-Instruct", + "display_name": "Llama-3.3-70B-Instruct", + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 32768 + }, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": false + }, + "attachment": false, + "open_weights": true, + "knowledge": "2023-12", + "release_date": "2024-12-06", + "last_updated": "2024-12-06", + "type": "chat" + }, + { + "id": "text-embedding-3-large", + "name": "text-embedding-3-large", + "display_name": "text-embedding-3-large", + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 8192, + "output": 3072 + }, + "tool_call": false, + "reasoning": { + "supported": false + }, + "attachment": false, + "open_weights": false, + "release_date": "2024-01-25", + "last_updated": "2024-01-25", + "type": "embedding" + }, + { + "id": "claude-fable-5", + "name": "Claude Fable 5", + "display_name": "Claude Fable 5", "modalities": { "input": [ "text", "image", - "video" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 256000, - "output": 256000 + "context": 1000000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -238759,152 +237177,201 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], + "default_enabled": true, + "mode": "effort", + "effort": "high", "effort_options": [ - "none", - "minimal", "low", "medium", "high", "xhigh", "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "omitted", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Adaptive thinking is always on for Claude Fable 5 and Claude Mythos 5; thinking.type = \"disabled\" is rejected.", + "Manual budget_tokens requests return 400 on Claude Fable 5 and Claude Mythos 5.", + "thinking.display defaults to omitted; set display to summarized to receive readable thinking summaries." ] } }, "attachment": true, "open_weights": false, - "release_date": "2026-06-23", - "last_updated": "2026-06-23", + "knowledge": "2026-01-31", + "release_date": "2026-06-09", + "last_updated": "2026-06-09", "type": "chat" - } - ] - }, - "nano-gpt": { - "id": "nano-gpt", - "name": "NanoGPT", - "display_name": "NanoGPT", - "api": "https://nano-gpt.com/api/v1", - "doc": "https://docs.nano-gpt.com", - "models": [ + }, { - "id": "Gemma-4-31B-MeroMero-v2:thinking", - "name": "Gemma 4 31B MeroMero v2 Thinking", - "display_name": "Gemma 4 31B MeroMero v2 Thinking", + "id": "gpt-4.1-nano", + "name": "GPT-4.1 nano", + "display_name": "GPT-4.1 nano", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 262144, + "context": 1047576, "output": 32768 }, + "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true + "supported": false }, "attachment": true, - "open_weights": true, - "release_date": "2026-07-29", - "last_updated": "2026-08-24", + "open_weights": false, + "knowledge": "2024-04", + "release_date": "2025-04-14", + "last_updated": "2025-04-14", "type": "chat" }, { - "id": "kimi-k2-instruct-fast", - "name": "Kimi K2 0711 Fast", - "display_name": "Kimi K2 0711 Fast", + "id": "gpt-image-2.5-flare", + "name": "GPT Image 2.5 Flare", + "display_name": "GPT Image 2.5 Flare", "modalities": { "input": [ "text", - "pdf" + "image" ], "output": [ - "text" + "image" ] }, "limit": { - "context": 131072, - "output": 16384 + "context": 8192, + "output": 8192 }, - "temperature": true, + "temperature": false, "tool_call": false, "reasoning": { "supported": false }, "attachment": true, - "open_weights": true, - "release_date": "2025-07-11", - "last_updated": "2025-07-11", - "type": "chat" + "open_weights": false, + "release_date": "2026-09-08", + "last_updated": "2026-09-08" }, { - "id": "celeris-1", - "name": "Celeris 1", - "display_name": "Celeris 1", + "id": "gpt-5-nano", + "name": "GPT-5 Nano", + "display_name": "GPT-5 Nano", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 8192, - "output": 8192 + "context": 400000, + "output": 128000 }, - "tool_call": false, + "temperature": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", + "effort_options": [ + "minimal", + "low", + "medium", + "high" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" + } + }, + "attachment": true, "open_weights": false, - "release_date": "2026-07-25", - "last_updated": "2026-07-25", + "knowledge": "2024-05-30", + "release_date": "2025-08-07", + "last_updated": "2025-08-07", "type": "chat" }, { - "id": "gemma-4-31b-it-fabled", - "name": "Fabled", - "display_name": "Fabled", + "id": "claude-sonnet-4-5", + "name": "Claude Sonnet 4.5", + "display_name": "Claude Sonnet 4.5", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 32768 + "context": 200000, + "output": 64000 }, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": false, + "mode": "budget", + "budget": { + "min": 1024, + "unit": "tokens" + }, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", + "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." + ] + } }, "attachment": true, - "open_weights": true, - "release_date": "2026-07-29", - "last_updated": "2026-07-29", + "open_weights": false, + "knowledge": "2025-07-31", + "release_date": "2025-11-18", + "last_updated": "2025-11-18", "type": "chat" }, { - "id": "doubao-seed-2-0-lite-260215", - "name": "Doubao Seed 2.0 Lite", - "display_name": "Doubao Seed 2.0 Lite", + "id": "text-embedding-ada-002", + "name": "text-embedding-ada-002", + "display_name": "text-embedding-ada-002", "modalities": { "input": [ "text" @@ -238914,41 +237381,38 @@ ] }, "limit": { - "context": 256000, - "output": 32000 + "context": 8192, + "output": 1536 }, - "temperature": true, "tool_call": false, "reasoning": { - "supported": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true - } + "supported": false }, "attachment": false, "open_weights": false, - "release_date": "2026-02-14", - "last_updated": "2026-02-14", - "type": "chat" + "release_date": "2022-12-15", + "last_updated": "2022-12-15", + "type": "embedding" }, { - "id": "claw-low", - "name": "Claw Low", - "display_name": "Claw Low", + "id": "gpt-chat-latest", + "name": "GPT Chat Latest", + "display_name": "GPT Chat Latest", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 400000, + "output": 128000 }, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -238956,23 +237420,23 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, "effort_options": [ - "low", - "high", - "max" + "medium" ] } }, - "attachment": false, + "attachment": true, "open_weights": false, - "release_date": "2026-05-11", - "last_updated": "2026-05-11", + "knowledge": "2025-12-01", + "release_date": "2026-05-05", + "last_updated": "2026-05-28", "type": "chat" }, { - "id": "synth-2.5-flash", - "name": "Synth 2.5 Flash Preview", - "display_name": "Synth 2.5 Flash Preview", + "id": "deepseek-v3.2", + "name": "DeepSeek-V3.2", + "display_name": "DeepSeek-V3.2", "modalities": { "input": [ "text" @@ -238982,51 +237446,73 @@ ] }, "limit": { - "context": 30000, - "output": 30000 + "context": 128000, + "output": 128000 }, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true + } }, "attachment": false, - "open_weights": false, - "release_date": "2026-09-05", - "last_updated": "2026-09-05", + "open_weights": true, + "knowledge": "2024-07", + "release_date": "2025-12-01", + "last_updated": "2025-12-01", "type": "chat" }, { - "id": "gemma-4-12b-it-semancer", - "name": "Gemma 4 12B Semancer", - "display_name": "Gemma 4 12B Semancer", + "id": "claude-opus-5-5", + "name": "Claude Opus 5.5", + "display_name": "Claude Opus 5.5", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 32768 + "context": 1000000, + "output": 128000 }, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, "default": true }, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" + ] + } + }, "attachment": true, - "open_weights": true, - "release_date": "2026-09-09", - "last_updated": "2026-09-09", + "open_weights": false, + "knowledge": "2026-06", + "release_date": "2026-09-22", + "last_updated": "2026-09-22", "type": "chat" }, { - "id": "universal-summarizer", - "name": "Universal Summarizer", - "display_name": "Universal Summarizer", + "id": "gpt-3.5-turbo-instruct", + "name": "GPT-3.5 Turbo Instruct", + "display_name": "GPT-3.5 Turbo Instruct", "modalities": { "input": [ "text" @@ -239036,130 +237522,182 @@ ] }, "limit": { - "context": 32768, - "output": 32768 + "context": 4096, + "output": 4096 }, + "temperature": true, "tool_call": false, "reasoning": { "supported": false }, "attachment": false, "open_weights": false, - "release_date": "2025-12-23", - "last_updated": "2024-01-01", + "knowledge": "2021-08", + "release_date": "2023-09-21", + "last_updated": "2023-09-21", "type": "chat" }, { - "id": "holo3-35b-a3b", - "name": "Holo3-35B-A3B", - "display_name": "Holo3-35B-A3B", + "id": "gpt-5.6-luna", + "name": "GPT-5.6 Luna", + "display_name": "GPT-5.6 Luna", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 65536, - "output": 8192 + "context": 1050000, + "output": 128000 }, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, "default": true }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", + "effort_options": [ + "none", + "low", + "medium", + "high", + "xhigh", + "max" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" + } + }, "attachment": true, - "open_weights": true, - "release_date": "2024-01-01", - "last_updated": "2024-01-01", + "open_weights": false, + "knowledge": "2026-02-16", + "release_date": "2026-07-09", + "last_updated": "2026-07-09", "type": "chat" }, { - "id": "Gemma-4-26B-A4B-MeroMero", - "name": "Gemma 4 26B A4B MeroMero", - "display_name": "Gemma 4 26B A4B MeroMero", + "id": "o4-mini", + "name": "o4-mini", + "display_name": "o4-mini", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 32768 + "context": 200000, + "output": 100000 }, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, "default": true }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", + "effort_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" + } + }, "attachment": true, - "open_weights": true, - "release_date": "2026-07-29", - "last_updated": "2026-08-26", + "open_weights": false, + "knowledge": "2024-05", + "release_date": "2025-04-16", + "last_updated": "2025-04-16", "type": "chat" }, { - "id": "gemini-2.5-pro", - "name": "Gemini 2.5 Pro", - "display_name": "Gemini 2.5 Pro", + "id": "claude-opus-4-5", + "name": "Claude Opus 4.5", + "display_name": "Claude Opus 4.5", "modalities": { "input": [ "text", "image", - "video", - "audio" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 200000, + "output": 64000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "budget", + "default_enabled": false, + "mode": "mixed", "budget": { - "default": -1, - "min": 128, - "max": 32768, - "auto": -1, + "min": 1024, "unit": "tokens" }, + "effort": "high", + "effort_options": [ + "low", + "medium", + "high" + ], + "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ - "thought_signatures" + "thinking_blocks" + ], + "notes": [ + "Claude Opus 4.5 uses manual thinking.type = \"enabled\" with budget_tokens; effort can be used alongside the thinking budget.", + "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header." ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-01", - "release_date": "2025-06-17", - "last_updated": "2025-06-17", + "knowledge": "2025-03-31", + "release_date": "2025-11-24", + "last_updated": "2025-08-01", "type": "chat" }, { - "id": "phi-4-multimodal-instruct", - "name": "Phi 4 Multimodal", - "display_name": "Phi 4 Multimodal", + "id": "codestral-2501", + "name": "Codestral 25.01", + "display_name": "Codestral 25.01", "modalities": { "input": [ "text" @@ -239169,25 +237707,25 @@ ] }, "limit": { - "context": 128000, - "output": 16384 + "context": 256000, + "output": 256000 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { "supported": false }, "attachment": false, - "open_weights": true, - "knowledge": "2024-06", - "release_date": "2025-02-26", - "last_updated": "2025-02-26", + "open_weights": false, + "knowledge": "2024-03", + "release_date": "2025-01-01", + "last_updated": "2025-01-01", "type": "chat" }, { - "id": "longcat-2.0", - "name": "LongCat 2.0", - "display_name": "LongCat 2.0", + "id": "gpt-3.5-turbo-1106", + "name": "GPT-3.5 Turbo 1106", + "display_name": "GPT-3.5 Turbo 1106", "modalities": { "input": [ "text" @@ -239197,29 +237735,77 @@ ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 16384, + "output": 4096 }, "temperature": true, + "tool_call": false, + "reasoning": { + "supported": false + }, + "attachment": false, + "open_weights": false, + "knowledge": "2021-08", + "release_date": "2023-11-06", + "last_updated": "2023-11-06", + "type": "chat" + }, + { + "id": "gpt-5", + "name": "GPT-5", + "display_name": "GPT-5", + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 400000, + "output": 128000 + }, + "temperature": false, "tool_call": true, "reasoning": { - "supported": true + "supported": true, + "default": true }, "extra_capabilities": { "reasoning": { - "supported": true + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", + "effort_options": [ + "minimal", + "low", + "medium", + "high" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-06-30", - "last_updated": "2026-06-30", + "attachment": true, + "open_weights": false, + "knowledge": "2024-09-30", + "release_date": "2025-08-07", + "last_updated": "2025-08-07", "type": "chat" }, { - "id": "mercury-2", - "name": "Mercury 2", - "display_name": "Mercury 2", + "id": "phi-4-mini-reasoning", + "name": "Phi-4-mini-reasoning", + "display_name": "Phi-4-mini-reasoning", "modalities": { "input": [ "text" @@ -239230,7 +237816,7 @@ }, "limit": { "context": 128000, - "output": 50000 + "output": 4096 }, "temperature": true, "tool_call": true, @@ -239239,30 +237825,32 @@ "default": true }, "attachment": false, - "open_weights": false, - "release_date": "2026-02-24", - "last_updated": "2026-02-24", + "open_weights": true, + "knowledge": "2023-10", + "release_date": "2024-12-11", + "last_updated": "2024-12-11", "type": "chat" }, { - "id": "gemini-2.5-pro-exp-03-25", - "name": "Gemini 2.5 Pro Experimental 0325", - "display_name": "Gemini 2.5 Pro Experimental 0325", + "id": "gpt-5-mini", + "name": "GPT-5 Mini", + "display_name": "GPT-5 Mini", "modalities": { "input": [ "text", "image", - "audio" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 400000, + "output": 128000 }, - "tool_call": false, + "temperature": false, + "tool_call": true, "reasoning": { "supported": true, "default": true @@ -239271,31 +237859,34 @@ "reasoning": { "supported": true, "default_enabled": true, - "mode": "budget", - "budget": { - "default": -1, - "min": 128, - "max": 32768, - "auto": -1, - "unit": "tokens" - }, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thought_signatures" - ] + "mode": "effort", + "effort": "medium", + "effort_options": [ + "minimal", + "low", + "medium", + "high" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, "attachment": true, "open_weights": false, - "release_date": "2025-03-25", - "last_updated": "2025-03-25", + "knowledge": "2024-05-30", + "release_date": "2025-08-07", + "last_updated": "2025-08-07", "type": "chat" }, { - "id": "fastgpt", - "name": "Web Answer", - "display_name": "Web Answer", + "id": "cohere-command-a", + "name": "Command A", + "display_name": "Command A", "modalities": { "input": [ "text" @@ -239305,59 +237896,52 @@ ] }, "limit": { - "context": 32768, - "output": 32768 + "context": 131072, + "output": 8192 }, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, "attachment": false, - "open_weights": false, - "release_date": "2025-12-23", - "last_updated": "2024-01-01", + "open_weights": true, + "knowledge": "2024-06-01", + "release_date": "2025-03-13", + "last_updated": "2025-03-13", "type": "chat" }, { - "id": "Gemma-4-31B-Gembrain-uncensored-heretic", - "name": "Gemma 4 31B Gembrain Uncensored Heretic", - "display_name": "Gemma 4 31B Gembrain Uncensored Heretic", + "id": "text-embedding-3-small", + "name": "text-embedding-3-small", + "display_name": "text-embedding-3-small", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 8192, + "output": 1536 }, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "none", - "high" - ] - } + "supported": false }, - "attachment": true, - "open_weights": true, - "release_date": "2026-05-15", - "last_updated": "2026-05-15", - "type": "chat" + "attachment": false, + "open_weights": false, + "release_date": "2024-01-25", + "last_updated": "2024-01-25", + "type": "embedding" }, { - "id": "Qwen3.5-27B-Queen-Derestricted", - "name": "Qwen3.5 27B Queen Derestricted", - "display_name": "Qwen3.5 27B Queen Derestricted", + "id": "deepseek-v4.1-flash", + "name": "DeepSeek-V4.1-Flash", + "display_name": "DeepSeek-V4.1-Flash", "modalities": { "input": [ "text", @@ -239368,9 +237952,10 @@ ] }, "limit": { - "context": 262144, - "output": 16384 + "context": 1000000, + "output": 128000 }, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -239387,64 +237972,69 @@ ], "effort_options": [ "none", - "high" + "low", + "high", + "max" ] } }, "attachment": true, "open_weights": true, - "release_date": "2026-04-30", - "last_updated": "2026-04-30", + "knowledge": "2025-05", + "release_date": "2026-09-10", + "last_updated": "2026-09-10", "type": "chat" }, { - "id": "gemma-4-26b-a4b-it-shadowsiren", - "name": "Shadow Siren", - "display_name": "Shadow Siren", + "id": "gpt-4o-mini", + "name": "GPT-4o mini", + "display_name": "GPT-4o mini", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 32768 + "context": 128000, + "output": 16384 }, + "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true + "supported": false }, "attachment": true, - "open_weights": true, - "release_date": "2026-07-29", - "last_updated": "2026-07-29", + "open_weights": false, + "knowledge": "2023-09", + "release_date": "2024-07-18", + "last_updated": "2024-07-18", "type": "chat" }, { - "id": "gemini-2.5-pro-preview-06-05", - "name": "Gemini 2.5 Pro Preview 0605", - "display_name": "Gemini 2.5 Pro Preview 0605", + "id": "o1", + "name": "o1", + "display_name": "o1", "modalities": { "input": [ "text", "image", - "audio" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 200000, + "output": 100000 }, - "temperature": true, - "tool_call": false, + "temperature": false, + "tool_call": true, "reasoning": { "supported": true, "default": true @@ -239453,32 +238043,27 @@ "reasoning": { "supported": true, "default_enabled": true, - "mode": "budget", - "budget": { - "default": -1, - "min": 128, - "max": 32768, - "auto": -1, - "unit": "tokens" - }, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thought_signatures" - ] + "mode": "effort", + "effort": "medium", + "effort_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, "attachment": true, "open_weights": false, - "knowledge": "2025-01-31", - "release_date": "2025-06-05", - "last_updated": "2025-06-05", + "knowledge": "2023-09", + "release_date": "2024-12-05", + "last_updated": "2024-12-05", "type": "chat" }, { - "id": "command-a-reasoning-08-2025", - "name": "Cohere Command A (08/2025)", - "display_name": "Cohere Command A (08/2025)", + "id": "ministral-3b", + "name": "Ministral 3B", + "display_name": "Ministral 3B", "modalities": { "input": [ "text" @@ -239488,30 +238073,29 @@ ] }, "limit": { - "context": 256000, + "context": 128000, "output": 8192 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { "supported": false }, "attachment": false, "open_weights": true, - "knowledge": "2024-06-01", - "release_date": "2025-08-21", - "last_updated": "2025-08-21", + "knowledge": "2024-03", + "release_date": "2024-10-22", + "last_updated": "2024-10-22", "type": "chat" }, { - "id": "gemini-2.5-flash-preview-09-2025", - "name": "Gemini 2.5 Flash Preview (09/2025)", - "display_name": "Gemini 2.5 Flash Preview (09/2025)", + "id": "gpt-4o", + "name": "GPT-4o", + "display_name": "GPT-4o", "modalities": { "input": [ "text", "image", - "audio", "pdf" ], "output": [ @@ -239519,44 +238103,25 @@ ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 128000, + "output": 16384 }, + "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "budget", - "budget": { - "default": -1, - "min": 0, - "max": 24576, - "auto": -1, - "off": 0, - "unit": "tokens" - }, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thought_signatures" - ] - } + "supported": false }, "attachment": true, "open_weights": false, - "release_date": "2025-09-25", - "last_updated": "2025-09-25", + "knowledge": "2023-09", + "release_date": "2024-05-13", + "last_updated": "2024-08-06", "type": "chat" }, { - "id": "mercury-coder-small", - "name": "Mercury Coder Small", - "display_name": "Mercury Coder Small", + "id": "phi-4-reasoning", + "name": "Phi-4-reasoning", + "display_name": "Phi-4-reasoning", "modalities": { "input": [ "text" @@ -239566,35 +238131,41 @@ ] }, "limit": { - "context": 32768, - "output": 16384 + "context": 32000, + "output": 4096 }, + "temperature": true, "tool_call": false, "reasoning": { - "supported": false + "supported": true, + "default": true }, "attachment": false, - "open_weights": false, - "release_date": "2024-01-01", - "last_updated": "2024-01-01", + "open_weights": true, + "knowledge": "2023-10", + "release_date": "2024-12-11", + "last_updated": "2024-12-11", "type": "chat" }, { - "id": "hermes-low", - "name": "Hermes Low", - "display_name": "Hermes Low", + "id": "codex-mini", + "name": "Codex Mini", + "display_name": "Codex Mini", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 200000, + "output": 100000 }, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -239604,21 +238175,22 @@ "reasoning": { "effort_options": [ "low", - "high", - "max" + "medium", + "high" ] } }, - "attachment": false, + "attachment": true, "open_weights": false, - "release_date": "2026-05-11", - "last_updated": "2026-05-11", + "knowledge": "2024-04", + "release_date": "2025-05-16", + "last_updated": "2025-05-16", "type": "chat" }, { - "id": "claw-medium", - "name": "Claw Medium", - "display_name": "Claw Medium", + "id": "deepseek-v4-pro", + "name": "DeepSeek-V4-Pro", + "display_name": "DeepSeek-V4-Pro", "modalities": { "input": [ "text" @@ -239628,9 +238200,10 @@ ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 1000000, + "output": 384000 }, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -239638,36 +238211,52 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "high", "effort_options": [ "low", "high", "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." ] } }, "attachment": false, - "open_weights": false, - "release_date": "2026-05-11", - "last_updated": "2026-05-11", + "open_weights": true, + "knowledge": "2025-05", + "release_date": "2026-04-24", + "last_updated": "2026-04-24", "type": "chat" }, { - "id": "Gemma-4-31B-GarnetV2", - "name": "Gemma 4 31B Garnet V2", - "display_name": "Gemma 4 31B Garnet V2", + "id": "claude-sonnet-5-5", + "name": "Claude Sonnet 5.5", + "display_name": "Claude Sonnet 5.5", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 16384 + "context": 1000000, + "output": 128000 }, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -239676,34 +238265,40 @@ "extra_capabilities": { "reasoning": { "effort_options": [ - "none", - "high" + "low", + "medium", + "high", + "xhigh", + "max" ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-05-01", - "last_updated": "2026-05-01", + "open_weights": false, + "knowledge": "2026-06", + "release_date": "2026-09-28", + "last_updated": "2026-09-28", "type": "chat" }, { - "id": "Gemma-4-31B-Agares-v1", - "name": "Gemma 4 31B Agares v1", - "display_name": "Gemma 4 31B Agares v1", + "id": "gpt-5.6-sol", + "name": "GPT-5.6 Sol", + "display_name": "GPT-5.6 Sol", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 1050000, + "output": 128000 }, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -239711,51 +238306,43 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", "effort_options": [ "none", + "low", + "medium", + "high", + "xhigh", + "max" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", "high" - ] + ], + "visibility": "hidden" } }, "attachment": true, - "open_weights": true, - "release_date": "2026-05-15", - "last_updated": "2026-05-15", - "type": "chat" - }, - { - "id": "synth-2.5-pro", - "name": "Synth 2.5 Pro Preview", - "display_name": "Synth 2.5 Pro Preview", - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 30000, - "output": 30000 - }, - "tool_call": false, - "reasoning": { - "supported": false - }, - "attachment": false, "open_weights": false, - "release_date": "2026-09-05", - "last_updated": "2026-09-05", + "knowledge": "2026-02-16", + "release_date": "2026-07-09", + "last_updated": "2026-07-09", "type": "chat" }, { - "id": "longcat-2.0:thinking", - "name": "LongCat 2.0 Thinking", - "display_name": "LongCat 2.0 Thinking", + "id": "claude-sonnet-4-6", + "name": "Claude Sonnet 4.6", + "display_name": "Claude Sonnet 4.6", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" @@ -239763,60 +238350,105 @@ }, "limit": { "context": 1000000, - "output": 131072 + "output": 64000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, - "attachment": false, - "open_weights": true, - "release_date": "2026-06-30", - "last_updated": "2026-06-30", + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": false, + "mode": "mixed", + "budget": { + "min": 1024, + "unit": "tokens" + }, + "effort": "high", + "effort_options": [ + "low", + "medium", + "high", + "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Anthropic recommends adaptive thinking with effort for Claude 4.6; budget_tokens remains a deprecated compatibility path.", + "Anthropic API defaults effort to high; lower effort levels should be chosen per workload." + ] + } + }, + "attachment": true, + "open_weights": false, + "knowledge": "2025-08-31", + "release_date": "2026-02-17", + "last_updated": "2026-03-13", "type": "chat" }, { - "id": "Gemma-4-31B-DarkIdol", - "name": "Gemma 4 31B DarkIdol", - "display_name": "Gemma 4 31B DarkIdol", + "id": "gpt-5.1", + "name": "GPT-5.1", + "display_name": "GPT-5.1", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 16384 + "context": 400000, + "output": 128000 }, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": false, + "mode": "effort", + "effort": "none", "effort_options": [ "none", + "low", + "medium", "high" - ] + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, "attachment": true, - "open_weights": true, - "release_date": "2026-05-01", - "last_updated": "2026-05-01", + "open_weights": false, + "knowledge": "2024-09-30", + "release_date": "2025-11-14", + "last_updated": "2025-11-14", "type": "chat" }, { - "id": "Gemma-4-31B-Isometry-RP", - "name": "Gemma 4 31B Isometry RP", - "display_name": "Gemma 4 31B Isometry RP", + "id": "grok-4-1-fast-reasoning", + "name": "Grok 4.1 Fast (Reasoning)", + "display_name": "Grok 4.1 Fast (Reasoning)", "modalities": { "input": [ "text", @@ -239827,9 +238459,10 @@ ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 128000, + "output": 8192 }, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -239837,84 +238470,76 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "none", - "high" - ] + "supported": true } }, "attachment": true, - "open_weights": true, - "release_date": "2026-05-15", - "last_updated": "2026-05-15", + "open_weights": false, + "release_date": "2025-06-27", + "last_updated": "2025-06-27", "type": "chat" }, { - "id": "Gemma-4-31B-MeroMero", - "name": "Gemma 4 31B MeroMero", - "display_name": "Gemma 4 31B MeroMero", + "id": "claude-opus-4-8", + "name": "Claude Opus 4.8", + "display_name": "Claude Opus 4.8", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 1000000, + "output": 128000 }, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": false, + "mode": "effort", + "effort": "high", "effort_options": [ - "none", - "high" + "low", + "medium", + "high", + "xhigh", + "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "omitted", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Claude Opus 4.7 and newer Opus models require thinking.type = \"adaptive\" to enable thinking explicitly.", + "Manual budget_tokens requests return 400 on Claude Opus 4.7 and newer adaptive-only Opus models.", + "task_budget is separate from thinking control and should not be treated as a thinking budget." ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-05-02", - "last_updated": "2026-05-02", - "type": "chat" - }, - { - "id": "mistral-code-agent-latest", - "name": "Mistral Code Agent Latest", - "display_name": "Mistral Code Agent Latest", - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 262144, - "output": 32768 - }, - "tool_call": true, - "reasoning": { - "supported": false - }, - "attachment": false, - "open_weights": true, - "release_date": "2026-06-02", - "last_updated": "2026-06-02", + "open_weights": false, + "knowledge": "2025-12-31", + "release_date": "2026-05-28", + "last_updated": "2026-05-28", "type": "chat" }, { - "id": "doubao-seed-2-0-code-preview-260215", - "name": "Doubao Seed 2.0 Code Preview", - "display_name": "Doubao Seed 2.0 Code Preview", + "id": "cohere-embed-v3-english", + "name": "Embed v3 English", + "display_name": "Embed v3 English", "modalities": { "input": [ "text" @@ -239924,93 +238549,117 @@ ] }, "limit": { - "context": 256000, - "output": 128000 + "context": 512, + "output": 1024 }, - "temperature": true, + "temperature": false, "tool_call": false, "reasoning": { "supported": false }, "attachment": false, - "open_weights": false, - "release_date": "2026-02-14", - "last_updated": "2026-02-14", - "type": "chat" + "open_weights": true, + "release_date": "2023-11-07", + "last_updated": "2023-11-07", + "type": "embedding" }, { - "id": "gemini-2.0-pro-exp-02-05", - "name": "Gemini 2.0 Pro 0205", - "display_name": "Gemini 2.0 Pro 0205", + "id": "model-router", + "name": "Model Router", + "display_name": "Model Router", "modalities": { "input": [ "text", - "image", - "audio" + "image" ], "output": [ "text" ] }, "limit": { - "context": 2097152, - "output": 8192 + "context": 200000, + "output": 16384 }, - "tool_call": false, + "tool_call": true, "reasoning": { "supported": false }, "attachment": true, "open_weights": false, - "release_date": "2025-02-05", - "last_updated": "2025-02-05", + "release_date": "2025-05-19", + "last_updated": "2025-11-18", "type": "chat" }, { - "id": "gemma-4-31b-it-scarlet", - "name": "Gemma 4 31B Scarlet", - "display_name": "Gemma 4 31B Scarlet", + "id": "deepseek-v4-flash", + "name": "DeepSeek-V4-Flash", + "display_name": "DeepSeek-V4-Flash", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 32768 + "context": 1000000, + "output": 384000 }, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, "default": true }, - "attachment": true, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "high", + "effort_options": [ + "low", + "high", + "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." + ] + } + }, + "attachment": false, "open_weights": true, - "release_date": "2026-10-08", - "last_updated": "2026-10-08", + "knowledge": "2025-05", + "release_date": "2026-04-24", + "last_updated": "2026-04-24", "type": "chat" }, { - "id": "agnes-3.0-flash", - "name": "Agnes 3.0 Flash", - "display_name": "Agnes 3.0 Flash", + "id": "gpt-5.5", + "name": "GPT-5.5", + "display_name": "GPT-5.5", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 524288, - "output": 65536 + "context": 1050000, + "output": 128000 }, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -240018,81 +238667,147 @@ }, "extra_capabilities": { "reasoning": { - "supported": true + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", + "effort_options": [ + "low", + "medium", + "high", + "xhigh" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, "attachment": true, "open_weights": false, - "release_date": "2026-09-09", - "last_updated": "2026-09-09", + "knowledge": "2025-12-01", + "release_date": "2026-04-24", + "last_updated": "2026-04-24", "type": "chat" }, { - "id": "Gemma-4-31B-Fabled", - "name": "Gemma 4 31B Fabled (ArliAI)", - "display_name": "Gemma 4 31B Fabled (ArliAI)", + "id": "claude-opus-4-6", + "name": "Claude Opus 4.6", + "display_name": "Claude Opus 4.6", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 1000000, + "output": 128000 }, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": false, + "mode": "mixed", + "budget": { + "min": 1024, + "unit": "tokens" + }, + "effort": "high", "effort_options": [ - "none", - "high" + "low", + "medium", + "high", + "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Anthropic recommends adaptive thinking with effort for Claude 4.6; budget_tokens remains a deprecated compatibility path.", + "Anthropic API defaults effort to high; lower effort levels should be chosen per workload." ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-05-02", - "last_updated": "2026-05-02", + "open_weights": false, + "knowledge": "2025-05-31", + "release_date": "2026-02-05", + "last_updated": "2026-07-17", "type": "chat" }, { - "id": "nano-gpt-help", - "name": "NanoGPT Help", - "display_name": "NanoGPT Help", + "id": "gpt-5.1-codex-mini", + "name": "GPT-5.1 Codex Mini", + "display_name": "GPT-5.1 Codex Mini", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 6000, - "output": 512 + "context": 400000, + "output": 128000 }, - "tool_call": false, + "temperature": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": false + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": false, + "mode": "effort", + "effort": "none", + "effort_options": [ + "none", + "low", + "medium", + "high" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" + } }, - "attachment": false, + "attachment": true, "open_weights": false, - "release_date": "2026-06-06", - "last_updated": "2026-06-06", + "knowledge": "2024-09-30", + "release_date": "2025-11-14", + "last_updated": "2025-11-14", "type": "chat" }, { - "id": "Gemma-4-31B-StyleTune", - "name": "Gemma 4 31B StyleTune", - "display_name": "Gemma 4 31B StyleTune", + "id": "llama-4-maverick-17b-128e-instruct-fp8", + "name": "Llama 4 Maverick 17B 128E Instruct FP8", + "display_name": "Llama 4 Maverick 17B 128E Instruct FP8", "modalities": { "input": [ "text", @@ -240103,47 +238818,41 @@ ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 1000000, + "output": 16384 }, + "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "none", - "high" - ] - } + "supported": false }, "attachment": true, "open_weights": true, - "release_date": "2026-05-15", - "last_updated": "2026-05-15", + "knowledge": "2024-08", + "release_date": "2025-04-05", + "last_updated": "2025-04-05", "type": "chat" }, { - "id": "gemini-3-pro-image-preview", - "name": "Gemini 3 Pro Image", - "display_name": "Gemini 3 Pro Image", + "id": "claude-fable-5-1", + "name": "Claude Fable 5.1", + "display_name": "Claude Fable 5.1", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 65536, - "output": 32768 + "context": 1000000, + "output": 128000 }, - "temperature": true, - "tool_call": false, + "temperature": false, + "tool_call": true, "reasoning": { "supported": true, "default": true @@ -240152,148 +238861,138 @@ "reasoning": { "supported": true, "default_enabled": true, - "mode": "level", - "level": "high", - "level_options": [ + "mode": "effort", + "effort": "high", + "effort_options": [ "low", - "high" + "medium", + "high", + "xhigh", + "max" ], + "interleaved": true, "summaries": true, - "visibility": "summary", + "visibility": "omitted", "continuation": [ - "thought_signatures" + "thinking_blocks" + ], + "notes": [ + "Adaptive thinking is always on for Claude Fable 5 and Claude Mythos 5; thinking.type = \"disabled\" is rejected.", + "Manual budget_tokens requests return 400 on Claude Fable 5 and Claude Mythos 5.", + "thinking.display defaults to omitted; set display to summarized to receive readable thinking summaries." ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-01", - "release_date": "2025-11-20", - "last_updated": "2025-11-20", + "knowledge": "2026-06", + "release_date": "2026-09-01", + "last_updated": "2026-09-01", "type": "chat" }, { - "id": "doubao-seed-2-0-mini-260215", - "name": "Doubao Seed 2.0 Mini", - "display_name": "Doubao Seed 2.0 Mini", + "id": "gpt-image-1", + "name": "GPT-Image-1", + "display_name": "GPT-Image-1", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ - "text" + "image" ] }, "limit": { - "context": 256000, - "output": 32000 + "context": 8192, + "output": 8192 }, - "temperature": true, + "temperature": false, "tool_call": false, "reasoning": { "supported": false }, - "attachment": false, + "attachment": true, "open_weights": false, - "release_date": "2026-02-14", - "last_updated": "2026-02-14", - "type": "chat" + "release_date": "2025-04-24", + "last_updated": "2025-04-24" }, { - "id": "gemini-2.5-flash", - "name": "Gemini 2.5 Flash", - "display_name": "Gemini 2.5 Flash", + "id": "grok-4-1-fast-non-reasoning", + "name": "Grok 4.1 Fast (Non-Reasoning)", + "display_name": "Grok 4.1 Fast (Non-Reasoning)", "modalities": { "input": [ "text", - "image", - "video", - "audio" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 128000, + "output": 8192 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "budget", - "budget": { - "default": -1, - "min": 0, - "max": 24576, - "auto": -1, - "off": 0, - "unit": "tokens" - }, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thought_signatures" - ] - } + "supported": false }, "attachment": true, "open_weights": false, - "knowledge": "2025-01", - "release_date": "2025-06-17", - "last_updated": "2025-06-17", + "release_date": "2025-06-27", + "last_updated": "2025-06-27", "type": "chat" }, { - "id": "auto-model-standard", - "name": "Auto model (Standard)", - "display_name": "Auto model (Standard)", + "id": "gpt-4-turbo", + "name": "GPT-4 Turbo", + "display_name": "GPT-4 Turbo", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 1000000 + "context": 128000, + "output": 4096 }, + "temperature": true, "tool_call": true, "reasoning": { "supported": false }, - "attachment": false, + "attachment": true, "open_weights": false, - "release_date": "2025-04-16", - "last_updated": "2024-06-01", + "knowledge": "2023-12", + "release_date": "2023-11-06", + "last_updated": "2024-04-09", "type": "chat" }, { - "id": "Gemma-4-31B-AssGuard", - "name": "Gemma 4 31B AssGuard", - "display_name": "Gemma 4 31B AssGuard", + "id": "claude-opus-5", + "name": "Claude Opus 5", + "display_name": "Claude Opus 5", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 1000000, + "output": 128000 }, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -240301,51 +239000,84 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, "effort_options": [ - "none", - "high" + "low", + "medium", + "high", + "xhigh", + "max" ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-05-15", - "last_updated": "2026-05-15", + "open_weights": false, + "knowledge": "2026-05", + "release_date": "2026-07-24", + "last_updated": "2026-07-24", "type": "chat" }, { - "id": "qwen3-vl-235b-a22b-instruct-original", - "name": "Qwen3 VL 235B A22B Instruct Original", - "display_name": "Qwen3 VL 235B A22B Instruct Original", + "id": "claude-mythos-5", + "name": "Claude Mythos 5", + "display_name": "Claude Mythos 5", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 32768, - "output": 32768 + "context": 1000000, + "output": 128000 }, - "temperature": true, - "tool_call": false, + "temperature": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "high", + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "omitted", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Adaptive thinking is always on for Claude Fable 5 and Claude Mythos 5; thinking.type = \"disabled\" is rejected.", + "Manual budget_tokens requests return 400 on Claude Fable 5 and Claude Mythos 5.", + "thinking.display defaults to omitted; set display to summarized to receive readable thinking summaries." + ] + } }, "attachment": true, - "open_weights": true, - "knowledge": "2025-03-31", - "release_date": "2025-09-23", - "last_updated": "2025-09-23", + "open_weights": false, + "knowledge": "2026-01-31", + "release_date": "2026-06-09", + "last_updated": "2026-06-09", "type": "chat" }, { - "id": "Gemma-4-31B-Gembrain-X-Core", - "name": "Gemma 4 31B Gembrain X Core", - "display_name": "Gemma 4 31B Gembrain X Core", + "id": "grok-4.6", + "name": "Grok 4.6", + "display_name": "Grok 4.6", "modalities": { "input": [ "text", @@ -240356,9 +239088,10 @@ ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 200000, + "output": 128000 }, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -240366,22 +239099,26 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, "effort_options": [ - "none", - "high" + "low", + "medium", + "high", + "xhigh" ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-05-15", - "last_updated": "2026-05-15", + "open_weights": false, + "knowledge": "2026-02-01", + "release_date": "2026-08-12", + "last_updated": "2026-08-12", "type": "chat" }, { - "id": "Qwen3.5-27B-Claude-4.6-Opus-Reasoning-Distilled-Derestricted-Lite", - "name": "Qwen3.5 27B Claude 4.6 Opus Reasoning Distilled Derestricted Lite", - "display_name": "Qwen3.5 27B Claude 4.6 Opus Reasoning Distilled Derestricted Lite", + "id": "mistral-small-2503", + "name": "Mistral Small 3.1", + "display_name": "Mistral Small 3.1", "modalities": { "input": [ "text", @@ -240392,39 +239129,25 @@ ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 128000, + "output": 32768 }, + "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "effort_options": [ - "none", - "high" - ] - } + "supported": false }, "attachment": true, - "open_weights": true, - "release_date": "2026-05-15", - "last_updated": "2026-05-15", + "open_weights": false, + "knowledge": "2024-09", + "release_date": "2025-03-01", + "last_updated": "2025-03-01", "type": "chat" }, { - "id": "pokee-isaac", - "name": "Pokee-Isaac 28B", - "display_name": "Pokee-Isaac 28B", + "id": "cohere-embed-v3-multilingual", + "name": "Embed v3 Multilingual", + "display_name": "Embed v3 Multilingual", "modalities": { "input": [ "text" @@ -240434,55 +239157,58 @@ ] }, "limit": { - "context": 10000000, - "output": 60000 + "context": 512, + "output": 1024 }, - "tool_call": true, + "temperature": false, + "tool_call": false, "reasoning": { - "supported": true, - "default": true + "supported": false }, "attachment": false, - "open_weights": false, - "release_date": "2026-08-04", - "last_updated": "2026-08-04", - "type": "chat" + "open_weights": true, + "release_date": "2023-11-07", + "last_updated": "2023-11-07", + "type": "embedding" }, { - "id": "auto-model-basic", - "name": "Auto model (Basic)", - "display_name": "Auto model (Basic)", + "id": "gpt-4.1-mini", + "name": "GPT-4.1 mini", + "display_name": "GPT-4.1 mini", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 1000000 + "context": 1047576, + "output": 32768 }, + "temperature": true, "tool_call": true, "reasoning": { "supported": false }, - "attachment": false, + "attachment": true, "open_weights": false, - "release_date": "2025-04-16", - "last_updated": "2024-06-01", + "knowledge": "2024-04", + "release_date": "2025-04-14", + "last_updated": "2025-04-14", "type": "chat" }, { - "id": "gemini-2.5-flash-preview-09-2025-thinking", - "name": "Gemini 2.5 Flash Preview (09/2025) – Thinking", - "display_name": "Gemini 2.5 Flash Preview (09/2025) – Thinking", + "id": "gpt-4.1", + "name": "GPT-4.1", + "display_name": "GPT-4.1", "modalities": { "input": [ "text", "image", - "audio", "pdf" ], "output": [ @@ -240490,57 +239216,40 @@ ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 1047576, + "output": 32768 }, + "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "budget", - "budget": { - "default": -1, - "min": 0, - "max": 24576, - "auto": -1, - "off": 0, - "unit": "tokens" - }, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thought_signatures" - ] - } + "supported": false }, "attachment": true, "open_weights": false, - "release_date": "2025-09-25", - "last_updated": "2025-09-25", + "knowledge": "2024-04", + "release_date": "2025-04-14", + "last_updated": "2025-04-14", "type": "chat" }, { - "id": "Gemma-4-31B-Dark-Gemistry", - "name": "Gemma 4 31B Dark Gemistry", - "display_name": "Gemma 4 31B Dark Gemistry", + "id": "gpt-6-luna", + "name": "GPT-6 Luna", + "display_name": "GPT-6 Luna", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 1050000, + "output": 128000 }, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -240550,35 +239259,41 @@ "reasoning": { "effort_options": [ "none", - "high" + "low", + "medium", + "high", + "xhigh", + "max" ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-05-15", - "last_updated": "2026-05-15", + "open_weights": false, + "knowledge": "2026-05-18", + "release_date": "2026-09-22", + "last_updated": "2026-09-22", "type": "chat" }, { - "id": "gemini-2.5-flash-preview-05-20", - "name": "Gemini 2.5 Flash 0520", - "display_name": "Gemini 2.5 Flash 0520", + "id": "o3", + "name": "o3", + "display_name": "o3", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1048000, - "output": 65536 + "context": 200000, + "output": 100000 }, - "temperature": true, - "tool_call": false, + "temperature": false, + "tool_call": true, "reasoning": { "supported": true, "default": true @@ -240587,111 +239302,95 @@ "reasoning": { "supported": true, "default_enabled": true, - "mode": "budget", - "budget": { - "default": -1, - "min": 0, - "max": 24576, - "auto": -1, - "off": 0, - "unit": "tokens" - }, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thought_signatures" - ] + "mode": "effort", + "effort": "medium", + "effort_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, "attachment": true, "open_weights": false, - "knowledge": "2025-01", - "release_date": "2025-05-20", - "last_updated": "2025-05-20", + "knowledge": "2024-05", + "release_date": "2025-04-16", + "last_updated": "2025-04-16", "type": "chat" }, { - "id": "gemma-4-31b-it-novelist", - "name": "Novelist", - "display_name": "Novelist", + "id": "gpt-image-2", + "name": "GPT-Image-2", + "display_name": "GPT-Image-2", "modalities": { "input": [ "text", "image" ], "output": [ - "text" + "image" ] }, "limit": { - "context": 262144, - "output": 32768 + "context": 8192, + "output": 8192 }, - "tool_call": true, + "temperature": false, + "tool_call": false, "reasoning": { - "supported": true, - "default": true + "supported": false }, "attachment": true, - "open_weights": true, - "release_date": "2026-07-29", - "last_updated": "2026-07-29", - "type": "chat" + "open_weights": false, + "release_date": "2026-04-21", + "last_updated": "2026-04-21" }, { - "id": "gemini-2.5-flash-lite-preview-06-17", - "name": "Gemini 2.5 Flash Lite Preview", - "display_name": "Gemini 2.5 Flash Lite Preview", + "id": "kimi-k2.5", + "name": "Kimi K2.5", + "display_name": "Kimi K2.5", "modalities": { "input": [ "text", - "image", - "audio" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 262144, + "output": 262144 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "budget", - "budget": { - "default": -1, - "min": 512, - "max": 24576, - "auto": -1, - "unit": "tokens" - }, + "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ - "thought_signatures" + "thinking_blocks" ] } }, "attachment": true, - "open_weights": false, + "open_weights": true, "knowledge": "2025-01", - "release_date": "2025-06-17", - "last_updated": "2025-06-17", + "release_date": "2026-01-27", + "last_updated": "2026-01-27", "type": "chat" }, { - "id": "Gemma-4-26B-A4B-MeroMero:thinking", - "name": "Gemma 4 26B A4B MeroMero Thinking", - "display_name": "Gemma 4 26B A4B MeroMero Thinking", + "id": "kimi-k2.6", + "name": "Kimi K2.6", + "display_name": "Kimi K2.6", "modalities": { "input": [ "text", @@ -240703,23 +239402,31 @@ }, "limit": { "context": 262144, - "output": 32768 + "output": 262144 }, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, "default": true }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true + } + }, "attachment": true, "open_weights": true, - "release_date": "2026-07-29", - "last_updated": "2026-08-26", + "knowledge": "2025-01", + "release_date": "2026-04-20", + "last_updated": "2026-04-20", "type": "chat" }, { - "id": "hermes-medium", - "name": "Hermes Medium", - "display_name": "Hermes Medium", + "id": "phi-4", + "name": "Phi-4", + "display_name": "Phi-4", "modalities": { "input": [ "text" @@ -240729,48 +239436,48 @@ ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 128000, + "output": 4096 }, - "tool_call": true, + "temperature": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "low", - "high", - "max" - ] - } + "supported": false }, "attachment": false, - "open_weights": false, - "release_date": "2026-05-11", - "last_updated": "2026-05-11", + "open_weights": true, + "knowledge": "2023-10", + "release_date": "2024-12-11", + "last_updated": "2024-12-11", "type": "chat" - }, + } + ] + }, + "baseten": { + "id": "baseten", + "name": "Baseten", + "display_name": "Baseten", + "api": "https://inference.baseten.co/v1", + "doc": "https://docs.baseten.co/inference/model-apis/overview", + "models": [ { - "id": "gemini-2.5-pro-preview-03-25", - "name": "Gemini 2.5 Pro Preview 0325", - "display_name": "Gemini 2.5 Pro Preview 0325", + "id": "openai/gpt-oss-120b", + "name": "OpenAI GPT 120B", + "display_name": "OpenAI GPT 120B", "modalities": { "input": [ - "text", - "image", - "audio" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 128072, + "output": 128072 }, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { "supported": true, "default": true @@ -240778,45 +239485,47 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "budget", - "budget": { - "default": -1, - "min": 128, - "max": 32768, - "auto": -1, - "unit": "tokens" - }, + "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ - "thought_signatures" + "thinking_blocks" + ], + "effort_options": [ + "none", + "minimal", + "low", + "medium", + "high", + "xhigh", + "max" ] } }, - "attachment": true, - "open_weights": false, - "release_date": "2025-03-25", - "last_updated": "2025-03-25", + "attachment": false, + "open_weights": true, + "knowledge": "2024-06", + "release_date": "2025-08-05", + "last_updated": "2025-08-05", "type": "chat" }, { - "id": "Gemma-4-31B-Queen", - "name": "Gemma 4 31B Queen", - "display_name": "Gemma 4 31B Queen", + "id": "nvidia/NVIDIA-Nemotron-3-Ultra-550B-A55B", + "name": "Nemotron Ultra", + "display_name": "Nemotron Ultra", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 16384 + "context": 202800, + "output": 202800 }, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -240824,27 +239533,34 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ "none", "high" ] } }, - "attachment": true, + "attachment": false, "open_weights": true, - "release_date": "2026-05-01", - "last_updated": "2026-05-01", + "knowledge": "2025-09", + "release_date": "2026-06-04", + "last_updated": "2026-06-04", "type": "chat" }, { - "id": "gemini-2.5-pro-preview-05-06", - "name": "Gemini 2.5 Pro Preview 0506", - "display_name": "Gemini 2.5 Pro Preview 0506", + "id": "moonshotai/Kimi-K3", + "name": "Kimi K3", + "display_name": "Kimi K3", "modalities": { "input": [ "text", - "image", - "audio" + "image" ], "output": [ "text" @@ -240852,9 +239568,10 @@ }, "limit": { "context": 1048576, - "output": 65536 + "output": 262144 }, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { "supported": true, "default": true @@ -240862,44 +239579,44 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "budget", - "budget": { - "default": -1, - "min": 128, - "max": 32768, - "auto": -1, - "unit": "tokens" - }, + "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ - "thought_signatures" + "thinking_blocks" + ], + "effort_options": [ + "none", + "low", + "high", + "max" ] } }, "attachment": true, - "open_weights": false, - "release_date": "2025-05-06", - "last_updated": "2025-05-06", + "open_weights": true, + "release_date": "2026-07-16", + "last_updated": "2026-07-16", "type": "chat" }, { - "id": "GLM-4.6-Derestricted-v5", - "name": "GLM 4.6 Derestricted v5", - "display_name": "GLM 4.6 Derestricted v5", + "id": "zai-org/GLM-5.3-Flash", + "name": "GLM 5.3 Flash", + "display_name": "GLM 5.3 Flash", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 8192 + "context": 1048576, + "output": 131072 }, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -240907,22 +239624,30 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ - "none", - "high" + "low", + "high", + "max" ] } }, - "attachment": false, + "attachment": true, "open_weights": true, - "release_date": "2025-12-23", - "last_updated": "2025-12-23", + "release_date": "2026-08-26", + "last_updated": "2026-08-26", "type": "chat" }, { - "id": "gemma-4-12b-it", - "name": "Gemma 4 12B Instruct", - "display_name": "Gemma 4 12B Instruct", + "id": "zai-org/GLM-5.3-Fast", + "name": "GLM 5.3 Fast", + "display_name": "GLM 5.3 Fast", "modalities": { "input": [ "text", @@ -240933,30 +239658,38 @@ ] }, "limit": { - "context": 131072, - "output": 32768 + "context": 1048576, + "output": 262144 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "low", + "high", + "max" + ] + } }, "attachment": true, "open_weights": true, - "release_date": "2026-06-09", - "last_updated": "2026-06-09", + "release_date": "2026-08-14", + "last_updated": "2026-08-14", "type": "chat" }, { - "id": "gemini-2.5-flash-lite-preview-09-2025-thinking", - "name": "Gemini 2.5 Flash Lite Preview (09/2025) – Thinking", - "display_name": "Gemini 2.5 Flash Lite Preview (09/2025) – Thinking", + "id": "zai-org/GLM-5.2", + "name": "GLM 5.2", + "display_name": "GLM 5.2", "modalities": { "input": [ "text", - "image", - "audio", - "pdf" + "image" ], "output": [ "text" @@ -240964,44 +239697,41 @@ }, "limit": { "context": 1048576, - "output": 65536 + "output": 262144 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "budget", - "budget": { - "default": -1, - "min": 512, - "max": 24576, - "auto": -1, - "unit": "tokens" - }, + "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ - "thought_signatures" + "thinking_blocks" + ], + "effort_options": [ + "none", + "low", + "high", + "max" ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2025-01", - "release_date": "2025-09-25", - "last_updated": "2025-09-25", + "open_weights": true, + "release_date": "2026-06-13", + "last_updated": "2026-06-13", "type": "chat" }, { - "id": "gemma-4-12b-it-station-keeper", - "name": "Gemma 4 12B StationKeeper", - "display_name": "Gemma 4 12B StationKeeper", + "id": "zai-org/GLM-5.3", + "name": "GLM 5.3", + "display_name": "GLM 5.3", "modalities": { "input": [ "text", @@ -241012,51 +239742,87 @@ ] }, "limit": { - "context": 131072, - "output": 32768 + "context": 1048576, + "output": 262144 }, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, "default": true }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "effort_options": [ + "low", + "high", + "max" + ] + } + }, "attachment": true, "open_weights": true, - "release_date": "2026-09-09", - "last_updated": "2026-09-09", + "release_date": "2026-08-14", + "last_updated": "2026-08-14", "type": "chat" }, { - "id": "gemini-2.0-pro-reasoner", - "name": "Gemini 2.0 Pro Reasoner", - "display_name": "Gemini 2.0 Pro Reasoner", + "id": "zai-org/GLM-5.2-Fast", + "name": "GLM 5.2 Fast", + "display_name": "GLM 5.2 Fast", "modalities": { "input": [ "text", - "audio" + "image" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 65536 + "context": 1048576, + "output": 262144 }, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "effort_options": [ + "none", + "low", + "high", + "max" + ] + } }, "attachment": true, - "open_weights": false, - "release_date": "2024-01-01", - "last_updated": "2025-02-05", + "open_weights": true, + "release_date": "2026-06-13", + "last_updated": "2026-06-13", "type": "chat" }, { - "id": "gemini-2.5-flash-preview-05-20:thinking", - "name": "Gemini 2.5 Flash 0520 Thinking", - "display_name": "Gemini 2.5 Flash 0520 Thinking", + "id": "deepseek-ai/DeepSeek-V4.1-Flash-1017", + "name": "DeepSeek V4.1 Flash 1017", + "display_name": "DeepSeek V4.1 Flash 1017", "modalities": { "input": [ "text", @@ -241067,11 +239833,11 @@ ] }, "limit": { - "context": 1048000, - "output": 65536 + "context": 1048576, + "output": 262144 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { "supported": true, "default": true @@ -241079,119 +239845,126 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "budget", - "budget": { - "default": -1, - "min": 0, - "max": 24576, - "auto": -1, - "off": 0, - "unit": "tokens" - }, + "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ - "thought_signatures" + "thinking_blocks" + ], + "effort_options": [ + "none", + "low", + "medium", + "high", + "max" ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2025-01", - "release_date": "2025-05-20", - "last_updated": "2025-05-20", + "open_weights": true, + "knowledge": "2025-05", + "release_date": "2026-10-17", + "last_updated": "2026-10-17", "type": "chat" }, { - "id": "mistral-code-latest", - "name": "Mistral Code Latest", - "display_name": "Mistral Code Latest", + "id": "deepseek-ai/DeepSeek-V4.1-Flash", + "name": "DeepSeek V4.1 Flash", + "display_name": "DeepSeek V4.1 Flash", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 256000, + "context": 1048576, "output": 32768 }, + "temperature": true, "tool_call": true, "reasoning": { - "supported": false - }, - "attachment": false, - "open_weights": false, - "release_date": "2026-06-02", - "last_updated": "2026-06-02", - "type": "chat" - }, - { - "id": "ernie-5.1", - "name": "ERNIE 5.1", - "display_name": "ERNIE 5.1", - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 119000, - "output": 64000 - }, - "tool_call": false, - "reasoning": { - "supported": true + "supported": true, + "default": true }, "extra_capabilities": { "reasoning": { - "supported": true + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "effort_options": [ + "none", + "high", + "max" + ] } }, - "attachment": false, - "open_weights": false, - "release_date": "2026-05-10", - "last_updated": "2026-05-10", + "attachment": true, + "open_weights": true, + "knowledge": "2025-05", + "release_date": "2026-09-10", + "last_updated": "2026-09-10", "type": "chat" }, { - "id": "gemini-exp-1206", - "name": "Gemini 2.0 Pro 1206", - "display_name": "Gemini 2.0 Pro 1206", + "id": "deepseek-ai/DeepSeek-V4.1-Flash-1017-Fast", + "name": "DeepSeek V4.1 Flash 1017 Fast", + "display_name": "DeepSeek V4.1 Flash 1017 Fast", "modalities": { "input": [ "text", - "image", - "audio" + "image" ], "output": [ "text" ] }, "limit": { - "context": 2097152, - "output": 8192 + "context": 1048576, + "output": 262144 }, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "effort_options": [ + "none", + "low", + "medium", + "high", + "max" + ] + } }, "attachment": true, - "open_weights": false, - "release_date": "2024-12-06", - "last_updated": "2024-12-06", + "open_weights": true, + "knowledge": "2025-05", + "release_date": "2026-10-17", + "last_updated": "2026-10-17", "type": "chat" }, { - "id": "gemma-4-26b-a4b-it-chimeraxheretic", - "name": "Gemma 4 26B A4B Chimera X Heretic", - "display_name": "Gemma 4 26B A4B Chimera X Heretic", + "id": "deepseek-ai/DeepSeek-V4.1-Flash-Fast", + "name": "DeepSeek V4.1 Flash Fast", + "display_name": "DeepSeek V4.1 Flash Fast", "modalities": { "input": [ "text", @@ -241202,30 +239975,45 @@ ] }, "limit": { - "context": 262144, + "context": 1048576, "output": 32768 }, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, "default": true }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "effort_options": [ + "none", + "high", + "max" + ] + } + }, "attachment": true, "open_weights": true, - "release_date": "2026-10-08", - "last_updated": "2026-10-08", + "knowledge": "2025-05", + "release_date": "2026-09-10", + "last_updated": "2026-09-10", "type": "chat" }, { - "id": "gemini-2.5-flash-lite-preview-09-2025", - "name": "Gemini 2.5 Flash Lite Preview (09/2025)", - "display_name": "Gemini 2.5 Flash Lite Preview (09/2025)", + "id": "deepseek-ai/DeepSeek-V4-Flash-0731", + "name": "DeepSeek V4 Flash 0731", + "display_name": "DeepSeek V4 Flash 0731", "modalities": { "input": [ - "text", - "image", - "audio", - "pdf" + "text" ], "output": [ "text" @@ -241233,57 +240021,55 @@ }, "limit": { "context": 1048576, - "output": 65536 + "output": 384000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "budget", - "budget": { - "default": -1, - "min": 512, - "max": 24576, - "auto": -1, - "unit": "tokens" - }, + "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ - "thought_signatures" + "thinking_blocks" + ], + "effort_options": [ + "none", + "low", + "high", + "max" ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-01", - "release_date": "2025-09-25", - "last_updated": "2025-09-25", + "attachment": false, + "open_weights": true, + "knowledge": "2025-05", + "release_date": "2026-07-31", + "last_updated": "2026-07-31", "type": "chat" }, { - "id": "Qwen3.5-27B-Blossom-V6.4-Derestricted", - "name": "Qwen3.5 27B Blossom V6.4 Derestricted", - "display_name": "Qwen3.5 27B Blossom V6.4 Derestricted", + "id": "deepseek-ai/DeepSeek-V4-Pro-0813", + "name": "DeepSeek V4 Pro 0813", + "display_name": "DeepSeek V4 Pro 0813", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 262144, + "context": 1048576, "output": 262144 }, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -241300,61 +240086,90 @@ ], "effort_options": [ "none", - "high" + "low", + "high", + "max" ] } }, - "attachment": true, + "attachment": false, "open_weights": true, - "release_date": "2026-05-15", - "last_updated": "2026-05-15", + "release_date": "2026-08-13", + "last_updated": "2026-08-22", "type": "chat" - }, + } + ] + }, + "friendli": { + "id": "friendli", + "name": "Friendli", + "display_name": "Friendli", + "api": "https://api.friendli.ai/serverless/v1", + "doc": "https://friendli.ai/docs/guides/serverless_endpoints/introduction", + "models": [ { - "id": "Gemma-4-31B-MeroMero-v2", - "name": "Gemma 4 31B MeroMero v2", - "display_name": "Gemma 4 31B MeroMero v2", + "id": "zai-org/GLM-5.3-Flash", + "name": "GLM-5.3-Flash", + "display_name": "GLM-5.3-Flash", "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 32768 + "context": 1048576, + "output": 131072 }, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, "default": true }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "effort_options": [ + "low", + "high", + "max" + ] + } + }, "attachment": true, "open_weights": true, - "release_date": "2026-07-29", - "last_updated": "2026-08-23", + "release_date": "2026-08-26", + "last_updated": "2026-08-26", "type": "chat" }, { - "id": "Gemma-4-31B-Novelist", - "name": "Gemma 4 31B Novelist (ArliAI)", - "display_name": "Gemma 4 31B Novelist (ArliAI)", + "id": "zai-org/GLM-5.1", + "name": "GLM-5.1", + "display_name": "GLM-5.1", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 202752, + "output": 131072 }, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -241362,22 +240177,25 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "none", - "high" + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, - "attachment": true, + "attachment": false, "open_weights": true, - "release_date": "2026-05-15", - "last_updated": "2026-05-15", + "release_date": "2026-04-07", + "last_updated": "2026-04-07", "type": "chat" }, { - "id": "sarvam-105b", - "name": "Sarvam 105B", - "display_name": "Sarvam 105B", + "id": "zai-org/GLM-5.2", + "name": "GLM-5.2", + "display_name": "GLM-5.2", "modalities": { "input": [ "text" @@ -241387,8 +240205,8 @@ ] }, "limit": { - "context": 131072, - "output": 4096 + "context": 1048576, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -241398,23 +240216,29 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ - "low", - "medium", - "high" + "high", + "max" ] } }, "attachment": false, "open_weights": true, - "release_date": "2026-02-18", - "last_updated": "2026-02-18", + "release_date": "2026-06-13", + "last_updated": "2026-06-13", "type": "chat" }, { - "id": "Meta-Llama-3-1-8B-Instruct-FP8", - "name": "Llama 3.1 8B (decentralized)", - "display_name": "Llama 3.1 8B (decentralized)", + "id": "zai-org/GLM-5.3", + "name": "GLM-5.3", + "display_name": "GLM-5.3", "modalities": { "input": [ "text" @@ -241424,23 +240248,41 @@ ] }, "limit": { - "context": 128000, - "output": 16384 + "context": 1048576, + "output": 131072 }, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "effort_options": [ + "low", + "high", + "max" + ] + } }, "attachment": false, "open_weights": true, - "release_date": "2024-01-01", - "last_updated": "2024-07-23", + "release_date": "2026-08-14", + "last_updated": "2026-08-14", "type": "chat" }, { - "id": "gemma-4-31b-it-darkidol", - "name": "DarkIdol", - "display_name": "DarkIdol", + "id": "google/gemma-4-31B-it", + "name": "Gemma 4 31B IT", + "display_name": "Gemma 4 31B IT", "modalities": { "input": [ "text", @@ -241454,34 +240296,58 @@ "context": 262144, "output": 32768 }, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, "default": true }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } + }, "attachment": true, "open_weights": true, - "release_date": "2026-07-29", - "last_updated": "2026-07-29", + "knowledge": "2025-01", + "release_date": "2026-04-02", + "last_updated": "2026-04-02", "type": "chat" - }, + } + ] + }, + "moark": { + "id": "moark", + "name": "Moark", + "display_name": "Moark", + "api": "https://moark.com/v1", + "doc": "https://moark.com/docs/openapi/v1#tag/%E6%96%87%E6%9C%AC%E7%94%9F%E6%88%90", + "models": [ { - "id": "Gemma-4-31B-Cognitive-Unshackled", - "name": "Gemma 4 31B Cognitive Unshackled", - "display_name": "Gemma 4 31B Cognitive Unshackled", + "id": "kimi-k3", + "name": "Kimi K3", + "display_name": "Kimi K3", "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 16384 + "context": 1048576, + "output": 131072 }, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -241489,35 +240355,32 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "none", - "high" - ] + "supported": true } }, "attachment": true, "open_weights": true, - "release_date": "2026-05-01", - "last_updated": "2026-05-01", + "release_date": "2026-07-16", + "last_updated": "2026-07-16", "type": "chat" }, { - "id": "Gemma-4-31B-SDFT-Heretic-RP", - "name": "Gemma 4 31B SDFT Heretic RP", - "display_name": "Gemma 4 31B SDFT Heretic RP", + "id": "DeepSeek-V4-Pro", + "name": "DeepSeek V4 Pro", + "display_name": "DeepSeek V4 Pro", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 1000000, + "output": 384000 }, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -241525,99 +240388,78 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "high", "effort_options": [ - "none", - "high" + "low", + "high", + "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." ] } }, - "attachment": true, - "open_weights": true, - "release_date": "2026-05-15", - "last_updated": "2026-05-15", - "type": "chat" - }, - { - "id": "asi1-mini", - "name": "ASI1 Mini", - "display_name": "ASI1 Mini", - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 16384 - }, - "tool_call": false, - "reasoning": { - "supported": false - }, "attachment": false, - "open_weights": false, - "release_date": "2025-03-25", - "last_updated": "2025-03-25", + "open_weights": true, + "knowledge": "2025-05", + "release_date": "2026-04-24", + "last_updated": "2026-04-24", "type": "chat" }, { - "id": "gemini-2.5-flash-lite", - "name": "Gemini 2.5 Flash Lite", - "display_name": "Gemini 2.5 Flash Lite", + "id": "Qwen3.6-Flash", + "name": "Qwen3.6 Flash", + "display_name": "Qwen3.6 Flash", "modalities": { "input": [ "text", "image", - "video", - "audio" + "video" ], "output": [ "text" ] }, "limit": { - "context": 1048576, + "context": 1000000, "output": 65536 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "budget", - "budget": { - "default": -1, - "min": 512, - "max": 24576, - "auto": -1, - "unit": "tokens" - }, + "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ - "thought_signatures" + "thinking_blocks" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-01", - "release_date": "2025-06-17", - "last_updated": "2025-06-17", + "release_date": "2026-04-27", + "last_updated": "2026-04-27", "type": "chat" }, { - "id": "auto-model-premium", - "name": "Auto model (Premium)", - "display_name": "Auto model (Premium)", + "id": "GLM-5.3", + "name": "GLM-5.3", + "display_name": "GLM-5.3", "modalities": { "input": [ "text" @@ -241628,48 +240470,65 @@ }, "limit": { "context": 1000000, - "output": 1000000 + "output": 131072 }, + "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true + } }, "attachment": false, - "open_weights": false, - "release_date": "2025-04-16", - "last_updated": "2024-06-01", + "open_weights": true, + "release_date": "2026-08-14", + "last_updated": "2026-08-14", "type": "chat" }, { - "id": "glm-4-long", - "name": "GLM-4 Long", - "display_name": "GLM-4 Long", + "id": "Kimi-K2.6", + "name": "Kimi K2.6", + "display_name": "Kimi K2.6", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 4095 + "context": 262144, + "output": 262144 }, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, - "open_weights": false, - "release_date": "2024-01-01", - "last_updated": "2024-08-01", + "extra_capabilities": { + "reasoning": { + "supported": true + } + }, + "attachment": true, + "open_weights": true, + "knowledge": "2025-01", + "release_date": "2026-04-20", + "last_updated": "2026-04-20", "type": "chat" }, { - "id": "deepseek-v3-0324", - "name": "DeepSeek Chat 0324", - "display_name": "DeepSeek Chat 0324", + "id": "MiniMax-M2.1", + "name": "MiniMax-M2.1", + "display_name": "MiniMax-M2.1", "modalities": { "input": [ "text" @@ -241679,24 +240538,30 @@ ] }, "limit": { - "context": 128000, - "output": 8192 + "context": 204800, + "output": 131072 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true + } }, "attachment": false, "open_weights": true, - "release_date": "2025-03-24", - "last_updated": "2025-03-24", + "release_date": "2025-12-23", + "last_updated": "2025-12-23", "type": "chat" }, { - "id": "claw-high", - "name": "Claw High", - "display_name": "Claw High", + "id": "MiniMax-M2.7", + "name": "MiniMax-M2.7", + "display_name": "MiniMax-M2.7", "modalities": { "input": [ "text" @@ -241706,9 +240571,10 @@ ] }, "limit": { - "context": 1048576, + "context": 204800, "output": 131072 }, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -241716,40 +240582,39 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "low", - "high", - "max" + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, "attachment": false, - "open_weights": false, - "release_date": "2026-05-11", - "last_updated": "2026-05-11", + "open_weights": true, + "release_date": "2026-03-18", + "last_updated": "2026-03-18", "type": "chat" }, { - "id": "gemini-2.5-flash-nothinking", - "name": "Gemini 2.5 Flash (No Thinking)", - "display_name": "Gemini 2.5 Flash (No Thinking)", + "id": "GLM-4.7", + "name": "GLM-4.7", + "display_name": "GLM-4.7", "modalities": { "input": [ - "text", - "image", - "video", - "audio", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 204800, + "output": 131072 }, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { "supported": true, "default": true @@ -241757,33 +240622,25 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "budget", - "budget": { - "default": -1, - "min": 0, - "max": 24576, - "auto": -1, - "off": 0, - "unit": "tokens" - }, + "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ - "thought_signatures" + "thinking_blocks" ] } }, - "attachment": true, - "open_weights": false, - "release_date": "2025-06-05", - "last_updated": "2025-06-05", + "attachment": false, + "open_weights": true, + "knowledge": "2025-04", + "release_date": "2025-12-22", + "last_updated": "2025-12-22", "type": "chat" }, { - "id": "MiniMax-M1", - "name": "MiniMax M1", - "display_name": "MiniMax M1", + "id": "deepseek-v4-flash-0731", + "name": "DeepSeek V4 Flash 0731", + "display_name": "DeepSeek V4 Flash 0731", "modalities": { "input": [ "text" @@ -241794,35 +240651,43 @@ }, "limit": { "context": 1000000, - "output": 131072 + "output": 384000 }, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true + } }, "attachment": false, "open_weights": true, - "release_date": "2025-01-08", - "last_updated": "2025-06-16", + "knowledge": "2025-05", + "release_date": "2026-07-31", + "last_updated": "2026-07-31", "type": "chat" }, { - "id": "Qwen3.5-27B-BlueStar-v3-Derestricted", - "name": "Qwen3.5 27B BlueStar v3 Derestricted", - "display_name": "Qwen3.5 27B BlueStar v3 Derestricted", + "id": "DeepSeek-V4-Flash", + "name": "DeepSeek V4 Flash", + "display_name": "DeepSeek V4 Flash", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 16384 + "context": 1000000, + "output": 384000 }, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -241831,28 +240696,45 @@ "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "high", + "effort_options": [ + "low", + "high", + "max" + ], "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ "thinking_blocks" ], - "effort_options": [ - "none", - "high" + "notes": [ + "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." ] } }, - "attachment": true, + "attachment": false, "open_weights": true, - "release_date": "2026-04-30", - "last_updated": "2026-04-30", + "knowledge": "2025-05", + "release_date": "2026-04-24", + "last_updated": "2026-04-24", "type": "chat" - }, + } + ] + }, + "llmtech": { + "id": "llmtech", + "name": "LLM Tech", + "display_name": "LLM Tech", + "api": "https://api.llmtech.eu/v1", + "doc": "https://llmtech.eu/models/qwen3.8-27b", + "models": [ { - "id": "gemma-4-26b-a4b-it-darksoul", - "name": "Dark Soul", - "display_name": "Dark Soul", + "id": "nvidia/Qwen3.8-27B-NVFP4", + "name": "Qwen3.8 27B", + "display_name": "Qwen3.8 27B", "modalities": { "input": [ "text", @@ -241866,49 +240748,73 @@ "context": 262144, "output": 32768 }, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, "default": true }, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "low", + "medium", + "xhigh" + ] + } + }, "attachment": true, "open_weights": true, - "release_date": "2026-07-29", - "last_updated": "2026-07-29", + "release_date": "2026-08-14", + "last_updated": "2026-08-14", "type": "chat" - }, + } + ] + }, + "amazon-bedrock": { + "id": "amazon-bedrock", + "name": "Amazon Bedrock", + "display_name": "Amazon Bedrock", + "doc": "https://docs.aws.amazon.com/bedrock/latest/userguide/models-supported.html", + "models": [ { - "id": "gemma-4-31b-it-gemsicle", - "name": "Gemsicle", - "display_name": "Gemsicle", + "id": "minimax.minimax-m2", + "name": "MiniMax-M2", + "display_name": "MiniMax-M2", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 32768 + "context": 204608, + "output": 128000 }, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, "default": true }, - "attachment": true, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true + } + }, + "attachment": false, "open_weights": true, - "release_date": "2026-07-29", - "last_updated": "2026-07-29", + "release_date": "2025-10-27", + "last_updated": "2025-10-27", "type": "chat" }, { - "id": "hermes-high", - "name": "Hermes High", - "display_name": "Hermes High", + "id": "global.zai.glm-5.3", + "name": "GLM-5.3 (Global)", + "display_name": "GLM-5.3 (Global)", "modalities": { "input": [ "text" @@ -241918,9 +240824,10 @@ ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 1000000, + "output": 128000 }, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -241928,28 +240835,31 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "interleaved": true, "effort_options": [ "low", + "medium", "high", + "xhigh", "max" ] } }, "attachment": false, - "open_weights": false, - "release_date": "2026-05-11", - "last_updated": "2026-05-11", + "open_weights": true, + "release_date": "2026-08-14", + "last_updated": "2026-08-14", "type": "chat" }, { - "id": "gemini-2.5-flash-preview-04-17:thinking", - "name": "Gemini 2.5 Flash Preview Thinking", - "display_name": "Gemini 2.5 Flash Preview Thinking", + "id": "us.moonshotai.kimi-k3", + "name": "Kimi K3 (US)", + "display_name": "Kimi K3 (US)", "modalities": { "input": [ "text", - "image", - "audio" + "image" ], "output": [ "text" @@ -241957,9 +240867,10 @@ }, "limit": { "context": 1048576, - "output": 65536 + "output": 128000 }, - "tool_call": false, + "temperature": false, + "tool_call": true, "reasoning": { "supported": true, "default": true @@ -241967,36 +240878,23 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "budget", - "budget": { - "default": -1, - "min": 0, - "max": 24576, - "auto": -1, - "off": 0, - "unit": "tokens" - }, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thought_signatures" - ] + "interleaved": true } }, "attachment": true, - "open_weights": false, - "release_date": "2025-04-17", - "last_updated": "2025-04-17", + "open_weights": true, + "release_date": "2026-07-16", + "last_updated": "2026-07-16", "type": "chat" }, { - "id": "deepseek-chat-cheaper", - "name": "DeepSeek V3/Chat Cheaper", - "display_name": "DeepSeek V3/Chat Cheaper", + "id": "au.anthropic.claude-sonnet-5", + "name": "Claude Sonnet 5 (AU)", + "display_name": "Claude Sonnet 5 (AU)", "modalities": { "input": [ "text", + "image", "pdf" ], "output": [ @@ -242004,23 +240902,37 @@ ] }, "limit": { - "context": 128000, - "output": 8192 + "context": 1000000, + "output": 128000 }, + "temperature": false, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" + ] + } }, "attachment": true, - "open_weights": true, - "release_date": "2025-04-15", - "last_updated": "2025-04-15", + "open_weights": false, + "knowledge": "2026-01-31", + "release_date": "2026-06-30", + "last_updated": "2026-06-30", "type": "chat" }, { - "id": "gemma-4-26b-a4b-it-moonlight", - "name": "Moonlight Dusk", - "display_name": "Moonlight Dusk", + "id": "global.openai.gpt-6-sol", + "name": "GPT-6 Sol (Global)", + "display_name": "GPT-6 Sol (Global)", "modalities": { "input": [ "text", @@ -242031,37 +240943,53 @@ ] }, "limit": { - "context": 262144, - "output": 32768 + "context": 1050000, + "output": 128000 }, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, "default": true }, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "none", + "low", + "medium", + "high", + "xhigh", + "max" + ] + } + }, "attachment": true, - "open_weights": true, - "release_date": "2026-07-29", - "last_updated": "2026-07-29", + "open_weights": false, + "knowledge": "2026-04-20", + "release_date": "2026-09-22", + "last_updated": "2026-09-22", "type": "chat" }, { - "id": "Gemma-4-31B-Animus-V14.1", - "name": "Gemma 4 31B Animus V14.1", - "display_name": "Gemma 4 31B Animus V14.1", + "id": "openai.gpt-6-sol", + "name": "GPT-6 Sol", + "display_name": "GPT-6 Sol", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 1050000, + "output": 128000 }, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -242071,23 +240999,57 @@ "reasoning": { "effort_options": [ "none", - "high" + "low", + "medium", + "high", + "xhigh", + "max" ] } }, "attachment": true, + "open_weights": false, + "knowledge": "2026-04-20", + "release_date": "2026-09-22", + "last_updated": "2026-09-22", + "type": "chat" + }, + { + "id": "us.meta.llama3-1-8b-instruct-v1:0", + "name": "Llama 3.1 8B Instruct (US)", + "display_name": "Llama 3.1 8B Instruct (US)", + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 4096 + }, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": false + }, + "attachment": false, "open_weights": true, - "release_date": "2026-05-15", - "last_updated": "2026-05-15", + "knowledge": "2023-12", + "release_date": "2024-07-23", + "last_updated": "2024-07-23", "type": "chat" }, { - "id": "deepseek-chat", - "name": "DeepSeek V3/Deepseek Chat", - "display_name": "DeepSeek V3/Deepseek Chat", + "id": "au.anthropic.claude-sonnet-4-6", + "name": "AU Anthropic Claude Sonnet 4.6", + "display_name": "AU Anthropic Claude Sonnet 4.6", "modalities": { "input": [ "text", + "image", "pdf" ], "output": [ @@ -242095,58 +241057,123 @@ ] }, "limit": { - "context": 128000, - "output": 8192 + "context": 1000000, + "output": 128000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": false + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": false, + "mode": "mixed", + "budget": { + "min": 1024, + "unit": "tokens" + }, + "effort": "high", + "effort_options": [ + "low", + "medium", + "high", + "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Anthropic recommends adaptive thinking with effort for Claude 4.6; budget_tokens remains a deprecated compatibility path.", + "Anthropic API defaults effort to high; lower effort levels should be chosen per workload." + ] + } }, "attachment": true, + "open_weights": false, + "knowledge": "2025-08-31", + "release_date": "2026-02-17", + "last_updated": "2026-03-13", + "type": "chat" + }, + { + "id": "qwen.qwen3-235b-a22b-2507-v1:0", + "name": "Qwen3 235B-A22B Instruct 2507", + "display_name": "Qwen3 235B-A22B Instruct 2507", + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 262144, + "output": 131072 + }, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": false + }, + "attachment": false, "open_weights": true, - "knowledge": "2025-09", - "release_date": "2025-12-01", - "last_updated": "2026-02-28", + "release_date": "2025-07-21", + "last_updated": "2025-09-18", "type": "chat" }, { - "id": "ernie-5.0-thinking-preview", - "name": "Ernie 5.0 Thinking Preview", - "display_name": "Ernie 5.0 Thinking Preview", + "id": "jp.anthropic.claude-opus-5", + "name": "Claude Opus 5 (JP)", + "display_name": "Claude Opus 5 (JP)", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 16384 + "context": 1000000, + "output": 128000 }, - "tool_call": false, + "temperature": false, + "tool_call": true, "reasoning": { "supported": true, "default": true }, "extra_capabilities": { "reasoning": { - "supported": true + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" + ] } }, "attachment": true, "open_weights": false, - "release_date": "2025-11-18", - "last_updated": "2025-11-18", + "knowledge": "2026-05", + "release_date": "2026-07-24", + "last_updated": "2026-07-24", "type": "chat" }, { - "id": "holo3-35b-a3b:thinking", - "name": "Holo3-35B-A3B Thinking", - "display_name": "Holo3-35B-A3B Thinking", + "id": "xai.grok-4.3", + "name": "Grok 4.3", + "display_name": "Grok 4.3", "modalities": { "input": [ "text", @@ -242157,120 +241184,183 @@ ] }, "limit": { - "context": 65536, - "output": 8192 + "context": 1000000, + "output": 131072 }, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, "default": true }, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "none", + "low", + "medium", + "high" + ] + } + }, "attachment": true, - "open_weights": true, - "release_date": "2024-01-01", - "last_updated": "2024-01-01", + "open_weights": false, + "release_date": "2026-04-17", + "last_updated": "2026-06-28", "type": "chat" }, { - "id": "Qwen3.5-27B-Claude-4.6-Opus-Reasoning-Distilled-Derestricted", - "name": "Qwen3.5 27B Claude 4.6 Opus Reasoning Distilled Derestricted", - "display_name": "Qwen3.5 27B Claude 4.6 Opus Reasoning Distilled Derestricted", + "id": "openai.gpt-5.6-sol", + "name": "GPT-5.6 Sol", + "display_name": "GPT-5.6 Sol", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 1050000, + "output": 128000 }, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, "default": true }, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "none", + "low", + "medium", + "high", + "xhigh", + "max" + ] + } + }, + "attachment": true, + "open_weights": false, + "knowledge": "2026-02-16", + "release_date": "2026-07-09", + "last_updated": "2026-07-09", + "type": "chat" + }, + { + "id": "us.anthropic.claude-opus-4-5-20251101-v1:0", + "name": "Claude Opus 4.5 (US)", + "display_name": "Claude Opus 4.5 (US)", + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 200000, + "output": 64000 + }, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": true, + "default": false + }, "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": false, + "mode": "mixed", + "budget": { + "min": 1024, + "unit": "tokens" + }, + "effort": "high", + "effort_options": [ + "low", + "medium", + "high" + ], "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ "thinking_blocks" ], - "effort_options": [ - "none", - "high" + "notes": [ + "Claude Opus 4.5 uses manual thinking.type = \"enabled\" with budget_tokens; effort can be used alongside the thinking budget.", + "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header." ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-05-15", - "last_updated": "2026-05-15", + "open_weights": false, + "knowledge": "2025-05", + "release_date": "2025-11-24", + "last_updated": "2025-11-24", "type": "chat" }, { - "id": "gemini-2.5-flash-preview-04-17", - "name": "Gemini 2.5 Flash Preview", - "display_name": "Gemini 2.5 Flash Preview", + "id": "us.openai.gpt-6.1-terra", + "name": "GPT-6.1 Terra (US)", + "display_name": "GPT-6.1 Terra (US)", "modalities": { "input": [ "text", - "image", - "audio" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 1050000, + "output": 128000 }, - "tool_call": false, + "temperature": false, + "tool_call": true, "reasoning": { "supported": true, "default": true }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "budget", - "budget": { - "default": -1, - "min": 0, - "max": 24576, - "auto": -1, - "off": 0, - "unit": "tokens" - }, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thought_signatures" + "effort_options": [ + "none", + "low", + "medium", + "high", + "xhigh", + "max" ] } }, "attachment": true, "open_weights": false, - "release_date": "2025-04-17", - "last_updated": "2025-04-17", + "knowledge": "2026-06-01", + "release_date": "2026-10-26", + "last_updated": "2026-10-26", "type": "chat" }, { - "id": "gemma-4-31b-it-garnet", - "name": "Garnet", - "display_name": "Garnet", + "id": "google.gemma-4-31b", + "name": "Gemma 4 31B IT", + "display_name": "Gemma 4 31B IT", "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ "text" @@ -242280,101 +241370,199 @@ "context": 262144, "output": 32768 }, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, "default": true }, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "none", + "high" + ] + } + }, "attachment": true, "open_weights": true, - "release_date": "2026-07-29", - "last_updated": "2026-07-29", + "knowledge": "2025-01", + "release_date": "2026-04-02", + "last_updated": "2026-04-02", "type": "chat" }, { - "id": "ernie-5.1:thinking", - "name": "ERNIE 5.1 Thinking", - "display_name": "ERNIE 5.1 Thinking", + "id": "anthropic.claude-fable-5-1", + "name": "Claude Fable 5.1", + "display_name": "Claude Fable 5.1", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 119000, - "output": 64000 + "context": 1000000, + "output": 128000 }, - "tool_call": false, + "temperature": false, + "tool_call": true, "reasoning": { "supported": true, "default": true }, - "attachment": false, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "high", + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "omitted", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Adaptive thinking is always on for Claude Fable 5 and Claude Mythos 5; thinking.type = \"disabled\" is rejected.", + "Manual budget_tokens requests return 400 on Claude Fable 5 and Claude Mythos 5.", + "thinking.display defaults to omitted; set display to summarized to receive readable thinking summaries." + ] + } + }, + "attachment": true, "open_weights": false, - "release_date": "2026-05-10", - "last_updated": "2026-05-10", + "knowledge": "2026-06", + "release_date": "2026-09-01", + "last_updated": "2026-09-01", "type": "chat" }, { - "id": "auto-model", - "name": "Auto model", - "display_name": "Auto model", + "id": "global.anthropic.claude-sonnet-4-20250514-v1:0", + "name": "Claude Sonnet 4 (Global)", + "display_name": "Claude Sonnet 4 (Global)", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 1000000 + "context": 200000, + "output": 64000 }, + "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": false }, - "attachment": false, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": false, + "mode": "budget", + "budget": { + "min": 1024, + "unit": "tokens" + }, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", + "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." + ] + } + }, + "attachment": true, "open_weights": false, - "release_date": "2025-04-16", - "last_updated": "2024-06-01", + "knowledge": "2025-03-31", + "release_date": "2025-05-22", + "last_updated": "2025-05-22", "type": "chat" }, { - "id": "doubao-seed-2-0-pro-260215", - "name": "Doubao Seed 2.0 Pro", - "display_name": "Doubao Seed 2.0 Pro", + "id": "eu.anthropic.claude-opus-4-6-v1", + "name": "Claude Opus 4.6 (EU)", + "display_name": "Claude Opus 4.6 (EU)", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 256000, + "context": 1000000, "output": 128000 }, - "temperature": false, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": false }, - "attachment": false, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": false, + "mode": "mixed", + "budget": { + "min": 1024, + "unit": "tokens" + }, + "effort": "high", + "effort_options": [ + "low", + "medium", + "high", + "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Anthropic recommends adaptive thinking with effort for Claude 4.6; budget_tokens remains a deprecated compatibility path.", + "Anthropic API defaults effort to high; lower effort levels should be chosen per workload." + ] + } + }, + "attachment": true, "open_weights": false, - "release_date": "2026-02-14", - "last_updated": "2026-02-14", + "knowledge": "2025-05-31", + "release_date": "2026-02-05", + "last_updated": "2026-03-13", "type": "chat" }, { - "id": "phi-4-mini-instruct", - "name": "Phi 4 Mini", - "display_name": "Phi 4 Mini", + "id": "eu.amazon.nova-micro-v1:0", + "name": "Nova Micro (EU)", + "display_name": "Nova Micro (EU)", "modalities": { "input": [ "text" @@ -242385,184 +241573,168 @@ }, "limit": { "context": 128000, - "output": 16384 + "output": 10000 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { "supported": false }, "attachment": false, - "open_weights": true, - "knowledge": "2024-06", - "release_date": "2025-02-26", - "last_updated": "2025-02-26", + "open_weights": false, + "knowledge": "2024-10", + "release_date": "2024-12-03", + "last_updated": "2024-12-03", "type": "chat" }, { - "id": "gemma-4-31b-it-isometry", - "name": "Isometry", - "display_name": "Isometry", + "id": "deepseek.v3.2", + "name": "DeepSeek V3.2", + "display_name": "DeepSeek V3.2", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 32768 + "context": 163840, + "output": 81920 }, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, "default": true }, - "attachment": true, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true + } + }, + "attachment": false, "open_weights": true, - "release_date": "2026-07-29", - "last_updated": "2026-07-29", + "knowledge": "2024-07", + "release_date": "2025-12-01", + "last_updated": "2026-02-06", "type": "chat" }, { - "id": "gemma-4-26b-a4b-it-chimerax", - "name": "Chimera X", - "display_name": "Chimera X", + "id": "openai.gpt-oss-20b-1:0", + "name": "gpt-oss-20b", + "display_name": "gpt-oss-20b", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 32768 + "context": 131072, + "output": 128000 }, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, "default": true }, - "attachment": true, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "low", + "medium", + "high" + ] + } + }, + "attachment": false, "open_weights": true, - "release_date": "2026-07-29", - "last_updated": "2026-07-29", + "knowledge": "2024-06", + "release_date": "2025-08-05", + "last_updated": "2025-08-05", "type": "chat" }, { - "id": "gemma-4-26b-a4b-it-luminous", - "name": "Luminous Mirror", - "display_name": "Luminous Mirror", + "id": "meta.llama3-1-70b-instruct-v1:0", + "name": "Llama 3.1 70B Instruct", + "display_name": "Llama 3.1 70B Instruct", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 32768 + "context": 128000, + "output": 4096 }, + "temperature": true, "tool_call": true, "reasoning": { "supported": false }, - "attachment": true, + "attachment": false, "open_weights": true, - "release_date": "2026-07-29", - "last_updated": "2026-07-29", + "knowledge": "2023-12", + "release_date": "2024-07-23", + "last_updated": "2024-07-23", "type": "chat" }, { - "id": "gemma-4-26b-a4b-it-musica", - "name": "Musica", - "display_name": "Musica", + "id": "amazon.nova-micro-v1:0", + "name": "Nova Micro", + "display_name": "Nova Micro", "modalities": { "input": [ - "text", - "image" - ], - "output": [ "text" - ] - }, - "limit": { - "context": 262144, - "output": 32768 - }, - "tool_call": true, - "reasoning": { - "supported": true, - "default": true - }, - "attachment": true, - "open_weights": true, - "release_date": "2026-07-29", - "last_updated": "2026-07-29", - "type": "chat" - }, - { - "id": "gemma-4-12b-it-starry-ocean", - "name": "Gemma 4 12B Starry Ocean", - "display_name": "Gemma 4 12B Starry Ocean", - "modalities": { - "input": [ - "text", - "image" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 131072 + "context": 128000, + "output": 10000 }, + "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "none", - "high" - ] - } + "supported": false }, - "attachment": true, - "open_weights": true, - "release_date": "2026-10-15", - "last_updated": "2026-10-15", + "attachment": false, + "open_weights": false, + "knowledge": "2024-10", + "release_date": "2024-12-03", + "last_updated": "2024-12-03", "type": "chat" }, { - "id": "Gemma-4-31B-K1-v5", - "name": "Gemma 4 31B K1 v5", - "display_name": "Gemma 4 31B K1 v5", + "id": "au.anthropic.claude-opus-5", + "name": "Claude Opus 5 (AU)", + "display_name": "Claude Opus 5 (AU)", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 1000000, + "output": 128000 }, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -242571,85 +241743,82 @@ "extra_capabilities": { "reasoning": { "effort_options": [ - "none", - "high" + "low", + "medium", + "high", + "xhigh", + "max" ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-05-02", - "last_updated": "2026-05-02", - "type": "chat" - }, - { - "id": "gemma-4-31b-it-gembrain", - "name": "Gembrain", - "display_name": "Gembrain", - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 262144, - "output": 32768 - }, - "tool_call": true, - "reasoning": { - "supported": true, - "default": true - }, - "attachment": true, - "open_weights": true, - "release_date": "2026-07-29", - "last_updated": "2026-07-29", + "open_weights": false, + "knowledge": "2026-05", + "release_date": "2026-07-24", + "last_updated": "2026-07-24", "type": "chat" }, { - "id": "Gemma-4-31B-Claude-4.6-Opus-Reasoning-Distilled", - "name": "Gemma 4 31B Claude 4.6 Opus Reasoning Distilled", - "display_name": "Gemma 4 31B Claude 4.6 Opus Reasoning Distilled", + "id": "eu.anthropic.claude-opus-4-8", + "name": "Claude Opus 4.8 (EU)", + "display_name": "Claude Opus 4.8 (EU)", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 16384 + "context": 1000000, + "output": 128000 }, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": false, + "mode": "effort", + "effort": "high", "effort_options": [ - "none", - "high" + "low", + "medium", + "high", + "xhigh", + "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "omitted", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Claude Opus 4.7 and newer Opus models require thinking.type = \"adaptive\" to enable thinking explicitly.", + "Manual budget_tokens requests return 400 on Claude Opus 4.7 and newer adaptive-only Opus models.", + "task_budget is separate from thinking control and should not be treated as a thinking budget." ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-05-01", - "last_updated": "2026-05-01", + "open_weights": false, + "knowledge": "2026-01", + "release_date": "2026-05-28", + "last_updated": "2026-05-28", "type": "chat" }, { - "id": "gemma-4-26b-a4b-it-opusdistill", - "name": "Opus Distill", - "display_name": "Opus Distill", + "id": "meta.llama4-maverick-17b-instruct-v1:0", + "name": "Llama 4 Maverick 17B Instruct", + "display_name": "Llama 4 Maverick 17B Instruct", "modalities": { "input": [ "text", @@ -242660,24 +241829,25 @@ ] }, "limit": { - "context": 262144, - "output": 32768 + "context": 1000000, + "output": 8192 }, + "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true + "supported": false }, "attachment": true, "open_weights": true, - "release_date": "2026-07-29", - "last_updated": "2026-07-29", + "knowledge": "2024-08", + "release_date": "2025-04-05", + "last_updated": "2025-04-05", "type": "chat" }, { - "id": "venice-uncensored", - "name": "Venice Uncensored", - "display_name": "Venice Uncensored", + "id": "mistral.devstral-2-123b", + "name": "Devstral 2 123B", + "display_name": "Devstral 2 123B", "modalities": { "input": [ "text" @@ -242687,159 +241857,222 @@ ] }, "limit": { - "context": 128000, + "context": 262144, "output": 8192 }, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { "supported": false }, "attachment": false, "open_weights": true, - "release_date": "2025-10-01", - "last_updated": "2025-02-24", + "knowledge": "2025-12", + "release_date": "2025-12-09", + "last_updated": "2025-12-09", "type": "chat" }, { - "id": "bytedance-seed/seed-2.0-code", - "name": "ByteDance Seed 2.0 Code", - "display_name": "ByteDance Seed 2.0 Code", + "id": "us.anthropic.claude-sonnet-4-20250514-v1:0", + "name": "Claude Sonnet 4 (US)", + "display_name": "Claude Sonnet 4 (US)", "modalities": { "input": [ "text", "image", - "video" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 131072 + "context": 200000, + "output": 64000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "none", - "low", - "medium", - "high" + "supported": true, + "default_enabled": false, + "mode": "budget", + "budget": { + "min": 1024, + "unit": "tokens" + }, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", + "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." ] } }, "attachment": true, "open_weights": false, - "release_date": "2026-02-14", - "last_updated": "2026-02-14", + "knowledge": "2025-03-31", + "release_date": "2025-05-22", + "last_updated": "2025-05-22", "type": "chat" }, { - "id": "bytedance-seed/seed-2-1-turbo", - "name": "ByteDance Seed 2.1 Turbo", - "display_name": "ByteDance Seed 2.1 Turbo", + "id": "anthropic.claude-opus-4-7", + "name": "Claude Opus 4.7", + "display_name": "Claude Opus 4.7", "modalities": { "input": [ "text", "image", - "video" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 235929 + "context": 1000000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": false, + "mode": "effort", + "effort": "high", + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "omitted", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Claude Opus 4.7 and newer Opus models require thinking.type = \"adaptive\" to enable thinking explicitly.", + "Manual budget_tokens requests return 400 on Claude Opus 4.7 and newer adaptive-only Opus models.", + "task_budget is separate from thinking control and should not be treated as a thinking budget." + ] + } }, "attachment": true, "open_weights": false, - "release_date": "2026-06-23", - "last_updated": "2026-06-23", + "knowledge": "2026-01-31", + "release_date": "2026-04-16", + "last_updated": "2026-04-16", "type": "chat" }, { - "id": "bytedance-seed/seed-2.0-lite", - "name": "ByteDance Seed 2.0 Lite", - "display_name": "ByteDance Seed 2.0 Lite", + "id": "apac.amazon.nova-pro-v1:0", + "name": "Nova Pro (APAC)", + "display_name": "Nova Pro (APAC)", "modalities": { "input": [ "text", "image", - "video" + "video", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 131072 + "context": 300000, + "output": 10000 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { "supported": false }, "attachment": true, "open_weights": false, - "release_date": "2026-02-14", - "last_updated": "2026-02-14", + "knowledge": "2024-10", + "release_date": "2024-12-03", + "last_updated": "2024-12-03", "type": "chat" }, { - "id": "nex-agi/nex-n2.5-pro", - "name": "Nex N2.5 Pro", - "display_name": "Nex N2.5 Pro", + "id": "eu.anthropic.claude-opus-4-7", + "name": "Claude Opus 4.7 (EU)", + "display_name": "Claude Opus 4.7 (EU)", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 1000000, + "output": 128000 }, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": false, + "mode": "effort", + "effort": "high", "effort_options": [ - "none", + "low", "medium", - "high" + "high", + "xhigh", + "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "omitted", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Claude Opus 4.7 and newer Opus models require thinking.type = \"adaptive\" to enable thinking explicitly.", + "Manual budget_tokens requests return 400 on Claude Opus 4.7 and newer adaptive-only Opus models.", + "task_budget is separate from thinking control and should not be treated as a thinking budget." ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-09-22", - "last_updated": "2026-09-22", + "open_weights": false, + "knowledge": "2026-01-31", + "release_date": "2026-04-16", + "last_updated": "2026-04-16", "type": "chat" }, { - "id": "nex-agi/nex-n2.5-mini", - "name": "Nex N2.5 Mini", - "display_name": "Nex N2.5 Mini", + "id": "us.openai.gpt-6-luna", + "name": "GPT-6 Luna (US)", + "display_name": "GPT-6 Luna (US)", "modalities": { "input": [ "text", @@ -242850,10 +242083,11 @@ ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 1050000, + "output": 128000 }, - "tool_call": false, + "temperature": false, + "tool_call": true, "reasoning": { "supported": true, "default": true @@ -242862,21 +242096,25 @@ "reasoning": { "effort_options": [ "none", + "low", "medium", - "high" + "high", + "xhigh", + "max" ] } }, "attachment": true, - "open_weights": true, + "open_weights": false, + "knowledge": "2026-05-18", "release_date": "2026-09-22", "last_updated": "2026-09-22", "type": "chat" }, { - "id": "TEE/gemma4-31b:thinking", - "name": "Gemma 4 31B Thinking TEE", - "display_name": "Gemma 4 31B Thinking TEE", + "id": "mistral.mistral-large-3-675b-instruct", + "name": "Mistral Large 3", + "display_name": "Mistral Large 3", "modalities": { "input": [ "text", @@ -242888,38 +242126,39 @@ }, "limit": { "context": 262144, - "output": 131072 + "output": 8192 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true + "supported": false }, "attachment": true, "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-04-02", - "last_updated": "2026-04-02", + "knowledge": "2024-11", + "release_date": "2025-12-02", + "last_updated": "2025-12-02", "type": "chat" }, { - "id": "TEE/glm-5.2:thinking", - "name": "GLM 5.2 Thinking TEE", - "display_name": "GLM 5.2 Thinking TEE", + "id": "global.anthropic.claude-fable-5", + "name": "Claude Fable 5 (Global)", + "display_name": "Claude Fable 5 (Global)", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 1000000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -242927,90 +242166,72 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "high", "effort_options": [ "low", "medium", "high", "xhigh", "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "omitted", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Adaptive thinking is always on for Claude Fable 5 and Claude Mythos 5; thinking.type = \"disabled\" is rejected.", + "Manual budget_tokens requests return 400 on Claude Fable 5 and Claude Mythos 5.", + "thinking.display defaults to omitted; set display to summarized to receive readable thinking summaries." ] } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-06-13", - "last_updated": "2026-06-13", + "attachment": true, + "open_weights": false, + "knowledge": "2026-01-31", + "release_date": "2026-06-09", + "last_updated": "2026-06-09", "type": "chat" }, { - "id": "TEE/qwen2.5-vl-72b-instruct", - "name": "Qwen2.5 VL 72B TEE", - "display_name": "Qwen2.5 VL 72B TEE", + "id": "us.amazon.nova-pro-v1:0", + "name": "Nova Pro (US)", + "display_name": "Nova Pro (US)", "modalities": { "input": [ "text", - "image" + "image", + "video", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 65536, - "output": 8192 + "context": 300000, + "output": 10000 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { "supported": false }, "attachment": true, - "open_weights": true, - "knowledge": "2024-04", - "release_date": "2025-01-26", - "last_updated": "2025-01-26", - "type": "chat" - }, - { - "id": "TEE/glm-5.1-thinking", - "name": "GLM 5.1 Thinking TEE", - "display_name": "GLM 5.1 Thinking TEE", - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 202752, - "output": 65535 - }, - "temperature": true, - "tool_call": true, - "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "none", - "high" - ] - } - }, - "attachment": false, - "open_weights": true, - "release_date": "2026-04-07", - "last_updated": "2026-04-07", + "open_weights": false, + "knowledge": "2024-10", + "release_date": "2024-12-03", + "last_updated": "2024-12-03", "type": "chat" }, { - "id": "TEE/glm-5.3-flash", - "name": "GLM 5.3 Flash TEE", - "display_name": "GLM 5.3 Flash TEE", + "id": "us.openai.gpt-5.6-luna", + "name": "GPT-5.6 Luna (US)", + "display_name": "GPT-5.6 Luna (US)", "modalities": { "input": [ "text", @@ -243021,10 +242242,10 @@ ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 1050000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -243032,54 +242253,79 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, "effort_options": [ + "none", "low", + "medium", "high", + "xhigh", "max" ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-08-26", - "last_updated": "2026-08-26", + "open_weights": false, + "knowledge": "2026-02-16", + "release_date": "2026-07-09", + "last_updated": "2026-07-09", "type": "chat" }, { - "id": "TEE/qwen3.8-27b", - "name": "Qwen3.8 27B TEE", - "display_name": "Qwen3.8 27B TEE", + "id": "apac.anthropic.claude-sonnet-4-20250514-v1:0", + "name": "Claude Sonnet 4 (APAC)", + "display_name": "Claude Sonnet 4 (APAC)", "modalities": { "input": [ "text", "image", - "video" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 200000, + "output": 64000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": false, + "mode": "budget", + "budget": { + "min": 1024, + "unit": "tokens" + }, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", + "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." + ] + } }, "attachment": true, - "open_weights": true, - "release_date": "2026-08-14", - "last_updated": "2026-08-14", + "open_weights": false, + "knowledge": "2025-03-31", + "release_date": "2025-05-22", + "last_updated": "2025-05-22", "type": "chat" }, { - "id": "TEE/kimi-k3", - "name": "Kimi K3 TEE", - "display_name": "Kimi K3 TEE", + "id": "us.openai.gpt-6-sol", + "name": "GPT-6 Sol (US)", + "display_name": "GPT-6 Sol (US)", "modalities": { "input": [ "text", @@ -243090,8 +242336,8 @@ ] }, "limit": { - "context": 1048576, - "output": 65535 + "context": 1050000, + "output": 128000 }, "temperature": false, "tool_call": true, @@ -243101,37 +242347,42 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, "effort_options": [ + "none", "low", + "medium", "high", + "xhigh", "max" ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-07-16", - "last_updated": "2026-07-16", + "open_weights": false, + "knowledge": "2026-04-20", + "release_date": "2026-09-22", + "last_updated": "2026-09-22", "type": "chat" }, { - "id": "TEE/gemma-4-31b-it", - "name": "Gemma 4 31B IT TEE", - "display_name": "Gemma 4 31B IT TEE", + "id": "eu.anthropic.claude-fable-5", + "name": "Claude Fable 5 (EU)", + "display_name": "Claude Fable 5 (EU)", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 1000000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -243139,20 +242390,41 @@ }, "extra_capabilities": { "reasoning": { - "supported": true + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "high", + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "omitted", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Adaptive thinking is always on for Claude Fable 5 and Claude Mythos 5; thinking.type = \"disabled\" is rejected.", + "Manual budget_tokens requests return 400 on Claude Fable 5 and Claude Mythos 5.", + "thinking.display defaults to omitted; set display to summarized to receive readable thinking summaries." + ] } }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-04-02", - "last_updated": "2026-04-02", + "attachment": true, + "open_weights": false, + "knowledge": "2026-01-31", + "release_date": "2026-06-09", + "last_updated": "2026-06-09", "type": "chat" }, { - "id": "TEE/kimi-k2.6", - "name": "Kimi K2.6 TEE", - "display_name": "Kimi K2.6 TEE", + "id": "global.openai.gpt-6.1-terra", + "name": "GPT-6.1 Terra (Global)", + "display_name": "GPT-6.1 Terra (Global)", "modalities": { "input": [ "text", @@ -243163,77 +242435,53 @@ ] }, "limit": { - "context": 262144, - "output": 65536 + "context": 1050000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { - "supported": true + "supported": true, + "default": true }, "extra_capabilities": { "reasoning": { - "supported": true + "effort_options": [ + "none", + "low", + "medium", + "high", + "xhigh", + "max" + ] } }, "attachment": true, - "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-04-20", - "last_updated": "2026-04-20", - "type": "chat" - }, - { - "id": "TEE/gpt-oss-120b", - "name": "GPT-OSS 120B TEE", - "display_name": "GPT-OSS 120B TEE", - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 16384 - }, - "temperature": true, - "tool_call": false, - "reasoning": { - "supported": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true - } - }, - "attachment": false, - "open_weights": true, - "knowledge": "2024-06", - "release_date": "2025-08-05", - "last_updated": "2025-08-05", + "open_weights": false, + "knowledge": "2026-06-01", + "release_date": "2026-10-26", + "last_updated": "2026-10-26", "type": "chat" }, { - "id": "TEE/deepseek-v4.1-flash", - "name": "DeepSeek V4.1 Flash TEE", - "display_name": "DeepSeek V4.1 Flash TEE", + "id": "anthropic.claude-haiku-5-5", + "name": "Claude Haiku 5.5", + "display_name": "Claude Haiku 5.5", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 384000 + "context": 1000000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -243241,27 +242489,26 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, "effort_options": [ - "none", "low", "medium", "high", + "xhigh", "max" ] } }, "attachment": true, - "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-09-10", - "last_updated": "2026-09-10", + "open_weights": false, + "knowledge": "2026-06", + "release_date": "2026-10-07", + "last_updated": "2026-10-07", "type": "chat" }, { - "id": "TEE/deepseek-v3.2", - "name": "DeepSeek V3.2 TEE", - "display_name": "DeepSeek V3.2 TEE", + "id": "qwen.qwen3-32b-v1:0", + "name": "Qwen3 32B", + "display_name": "Qwen3 32B", "modalities": { "input": [ "text" @@ -243271,42 +242518,45 @@ ] }, "limit": { - "context": 164000, - "output": 65536 + "context": 32768, + "output": 16384 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": true + "supported": true, + "default": true }, "extra_capabilities": { "reasoning": { - "supported": true + "supported": true, + "interleaved": true } }, "attachment": false, "open_weights": true, - "knowledge": "2024-07", - "release_date": "2025-12-01", - "last_updated": "2025-12-01", + "knowledge": "2025-04", + "release_date": "2025-04-28", + "last_updated": "2025-09-18", "type": "chat" }, { - "id": "TEE/kimi-k2.7-code", - "name": "Kimi K2.7 Code TEE", - "display_name": "Kimi K2.7 Code TEE", + "id": "jp.anthropic.claude-sonnet-5", + "name": "Claude Sonnet 5 (JP)", + "display_name": "Claude Sonnet 5 (JP)", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 65536 + "context": 1000000, + "output": 128000 }, "temperature": false, "tool_call": true, @@ -243316,31 +242566,39 @@ }, "extra_capabilities": { "reasoning": { - "supported": true + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" + ] } }, "attachment": true, - "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-06-12", - "last_updated": "2026-06-12", + "open_weights": false, + "knowledge": "2026-01-31", + "release_date": "2026-06-30", + "last_updated": "2026-06-30", "type": "chat" }, { - "id": "TEE/nemotron-3.5-lightning", - "name": "Nvidia Nemotron 3.5 Lightning TEE", - "display_name": "Nvidia Nemotron 3.5 Lightning TEE", + "id": "global.xai.grok-4.7", + "name": "Grok 4.7 (Global)", + "display_name": "Grok 4.7 (Global)", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 65536 + "context": 500000, + "output": 500000 }, "temperature": true, "tool_call": true, @@ -243350,26 +242608,25 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, "effort_options": [ - "none", "low", "medium", - "high" + "high", + "xhigh" ] } }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-09", - "release_date": "2026-08-11", - "last_updated": "2026-08-11", + "attachment": true, + "open_weights": false, + "knowledge": "2026-05", + "release_date": "2026-09-21", + "last_updated": "2026-09-28", "type": "chat" }, { - "id": "TEE/gemma4-31b", - "name": "Gemma 4 31B", - "display_name": "Gemma 4 31B", + "id": "eu.mistral.pixtral-large-2502-v1:0", + "name": "Pixtral Large (25.02) (EU)", + "display_name": "Pixtral Large (25.02) (EU)", "modalities": { "input": [ "text", @@ -243380,8 +242637,8 @@ ] }, "limit": { - "context": 262144, - "output": 131072 + "context": 128000, + "output": 8192 }, "temperature": true, "tool_call": true, @@ -243389,67 +242646,88 @@ "supported": false }, "attachment": true, - "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-04-02", - "last_updated": "2026-04-02", + "open_weights": false, + "release_date": "2025-04-08", + "last_updated": "2025-04-08", "type": "chat" }, { - "id": "TEE/qwen3.6-35b-a3b", - "name": "Qwen3.6 35B A3B TEE", - "display_name": "Qwen3.6 35B A3B TEE", + "id": "eu.anthropic.claude-opus-4-5-20251101-v1:0", + "name": "Claude Opus 4.5 (EU)", + "display_name": "Claude Opus 4.5 (EU)", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 200000, + "output": 64000 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": true + "supported": true, + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": false, + "mode": "mixed", + "budget": { + "min": 1024, + "unit": "tokens" + }, + "effort": "high", + "effort_options": [ + "low", + "medium", + "high" + ], "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ "thinking_blocks" + ], + "notes": [ + "Claude Opus 4.5 uses manual thinking.type = \"enabled\" with budget_tokens; effort can be used alongside the thinking budget.", + "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header." ] } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-04-17", - "last_updated": "2026-04-17", + "attachment": true, + "open_weights": false, + "knowledge": "2025-05", + "release_date": "2025-11-24", + "last_updated": "2025-11-24", "type": "chat" }, { - "id": "TEE/glm-5.1", - "name": "GLM 5.1 TEE", - "display_name": "GLM 5.1 TEE", + "id": "global.anthropic.claude-fable-5-1", + "name": "Claude Fable 5.1 (Global)", + "display_name": "Claude Fable 5.1 (Global)", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 202752, - "output": 65535 + "context": 1000000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -243458,22 +242736,40 @@ "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "high", "effort_options": [ - "none", - "high" + "low", + "medium", + "high", + "xhigh", + "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "omitted", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Adaptive thinking is always on for Claude Fable 5 and Claude Mythos 5; thinking.type = \"disabled\" is rejected.", + "Manual budget_tokens requests return 400 on Claude Fable 5 and Claude Mythos 5.", + "thinking.display defaults to omitted; set display to summarized to receive readable thinking summaries." ] } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-04-07", - "last_updated": "2026-04-07", + "attachment": true, + "open_weights": false, + "knowledge": "2026-06", + "release_date": "2026-09-01", + "last_updated": "2026-09-01", "type": "chat" }, { - "id": "TEE/gemma-4-26b-a4b-uncensored", - "name": "Gemma 4 26B A4B Uncensored TEE", - "display_name": "Gemma 4 26B A4B Uncensored TEE", + "id": "qwen.qwen3-coder-next", + "name": "Qwen3 Coder Next", + "display_name": "Qwen3 Coder Next", "modalities": { "input": [ "text" @@ -243483,26 +242779,29 @@ ] }, "limit": { - "context": 65536, + "context": 262144, "output": 65536 }, + "temperature": true, "tool_call": true, "reasoning": { "supported": false }, "attachment": false, "open_weights": true, - "release_date": "2026-05-23", - "last_updated": "2026-05-23", + "knowledge": "2025-09", + "release_date": "2026-02-03", + "last_updated": "2026-02-06", "type": "chat" }, { - "id": "TEE/glm-5.3", - "name": "GLM 5.3 TEE", - "display_name": "GLM 5.3 TEE", + "id": "global.moonshotai.kimi-k3", + "name": "Kimi K3 (Global)", + "display_name": "Kimi K3 (Global)", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" @@ -243510,9 +242809,9 @@ }, "limit": { "context": 1048576, - "output": 131072 + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -243521,182 +242820,163 @@ "extra_capabilities": { "reasoning": { "supported": true, - "effort_options": [ - "low", - "high", - "max" - ] + "interleaved": true } }, - "attachment": false, + "attachment": true, "open_weights": true, - "release_date": "2026-08-14", - "last_updated": "2026-08-14", + "release_date": "2026-07-16", + "last_updated": "2026-07-16", "type": "chat" }, { - "id": "TEE/qwen3.6-27b", - "name": "Qwen3.6 27B TEE", - "display_name": "Qwen3.6 27B TEE", + "id": "us.anthropic.claude-opus-4-8", + "name": "Claude Opus 4.8 (US)", + "display_name": "Claude Opus 4.8 (US)", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 65536 + "context": 1000000, + "output": 128000 }, - "temperature": true, - "tool_call": false, + "temperature": false, + "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": false, + "mode": "effort", + "effort": "high", + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" + ], "interleaved": true, "summaries": true, - "visibility": "summary", + "visibility": "omitted", "continuation": [ "thinking_blocks" + ], + "notes": [ + "Claude Opus 4.7 and newer Opus models require thinking.type = \"adaptive\" to enable thinking explicitly.", + "Manual budget_tokens requests return 400 on Claude Opus 4.7 and newer adaptive-only Opus models.", + "task_budget is separate from thinking control and should not be treated as a thinking budget." ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-04-22", - "last_updated": "2026-04-22", - "type": "chat" - }, - { - "id": "TEE/qwen3.5-397b-a17b", - "name": "Qwen3.5 397B A17B TEE", - "display_name": "Qwen3.5 397B A17B TEE", - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 262144, - "output": 65536 - }, - "temperature": true, - "tool_call": false, - "reasoning": { - "supported": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } - }, - "attachment": false, - "open_weights": true, - "release_date": "2026-02-15", - "last_updated": "2026-02-15", + "open_weights": false, + "knowledge": "2026-01", + "release_date": "2026-05-28", + "last_updated": "2026-05-28", "type": "chat" }, { - "id": "TEE/llama3-3-70b", - "name": "Llama 3.3 70B", - "display_name": "Llama 3.3 70B", + "id": "us.amazon.nova-premier-v1:0", + "name": "Nova Premier (US)", + "display_name": "Nova Premier (US)", "modalities": { "input": [ - "text" + "text", + "image", + "video", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 16384 + "context": 1000000, + "output": 10000 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { "supported": false }, - "attachment": false, - "open_weights": true, - "knowledge": "2023-12", - "release_date": "2024-12-06", - "last_updated": "2024-12-06", + "attachment": true, + "open_weights": false, + "knowledge": "2024-10", + "release_date": "2025-04-30", + "last_updated": "2025-04-30", "type": "chat" }, { - "id": "TEE/qwen3.5-27b", - "name": "Qwen3.5 27B TEE", - "display_name": "Qwen3.5 27B TEE", + "id": "global.openai.gpt-6.1-sol", + "name": "GPT-6.1 Sol (Global)", + "display_name": "GPT-6.1 Sol (Global)", "modalities": { "input": [ "text", - "image", - "video" + "image" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 65536 + "context": 1050000, + "output": 128000 }, - "temperature": true, - "tool_call": false, + "temperature": false, + "tool_call": true, "reasoning": { - "supported": true + "supported": true, + "default": true }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-02-23", - "last_updated": "2026-02-23", + "open_weights": false, + "knowledge": "2026-04-30", + "release_date": "2026-09-29", + "last_updated": "2026-09-29", "type": "chat" }, { - "id": "TEE/glm-5.2", - "name": "GLM 5.2 TEE", - "display_name": "GLM 5.2 TEE", + "id": "anthropic.claude-opus-5-5", + "name": "Claude Opus 5.5", + "display_name": "Claude Opus 5.5", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 1000000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -243704,23 +242984,31 @@ }, "extra_capabilities": { "reasoning": { - "supported": true + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" + ] } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-06-13", - "last_updated": "2026-06-13", + "attachment": true, + "open_weights": false, + "knowledge": "2026-06", + "release_date": "2026-09-22", + "last_updated": "2026-09-22", "type": "chat" }, { - "id": "meituan/longcat-2.5-preview", - "name": "LongCat 2.5 Preview", - "display_name": "LongCat 2.5 Preview", + "id": "anthropic.claude-opus-4-8", + "name": "Claude Opus 4.8", + "display_name": "Claude Opus 4.8", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" @@ -243728,175 +243016,232 @@ }, "limit": { "context": 1000000, - "output": 131072 + "output": 128000 }, "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": false, + "mode": "effort", + "effort": "high", + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "omitted", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Claude Opus 4.7 and newer Opus models require thinking.type = \"adaptive\" to enable thinking explicitly.", + "Manual budget_tokens requests return 400 on Claude Opus 4.7 and newer adaptive-only Opus models.", + "task_budget is separate from thinking control and should not be treated as a thinking budget." + ] + } }, "attachment": true, "open_weights": false, - "release_date": "2026-09-25", - "last_updated": "2026-09-25", + "knowledge": "2026-01", + "release_date": "2026-05-28", + "last_updated": "2026-05-28", "type": "chat" }, { - "id": "pamanseau/OpenReasoning-Nemotron-32B", - "name": "OpenReasoning Nemotron 32B", - "display_name": "OpenReasoning Nemotron 32B", + "id": "us.anthropic.claude-sonnet-5", + "name": "Claude Sonnet 5 (US)", + "display_name": "Claude Sonnet 5 (US)", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 32768, - "output": 65536 + "context": 1000000, + "output": 128000 }, - "tool_call": false, + "temperature": false, + "tool_call": true, "reasoning": { "supported": true, "default": true }, - "attachment": false, - "open_weights": true, - "release_date": "2025-08-21", - "last_updated": "2025-08-21", - "type": "chat" - }, - { - "id": "mlabonne/NeuralDaredevil-8B-abliterated", - "name": "Neural Daredevil 8B abliterated", - "display_name": "Neural Daredevil 8B abliterated", - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 8192, - "output": 8192 - }, - "tool_call": false, - "reasoning": { - "supported": false + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" + ] + } }, - "attachment": false, - "open_weights": true, - "release_date": "2024-01-01", - "last_updated": "2024-12-01", + "attachment": true, + "open_weights": false, + "knowledge": "2026-01-31", + "release_date": "2026-06-30", + "last_updated": "2026-06-30", "type": "chat" }, { - "id": "undi95/remm-slerp-l2-13b", - "name": "ReMM SLERP 13B", - "display_name": "ReMM SLERP 13B", + "id": "mistral.magistral-small-2509", + "name": "Magistral Small 1.2", + "display_name": "Magistral Small 1.2", "modalities": { "input": [ "text", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 6144, - "output": 4096 + "context": 128000, + "output": 40000 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, "attachment": true, "open_weights": true, - "knowledge": "2023-06-30", - "release_date": "2023-07-22", - "last_updated": "2023-07-22", + "release_date": "2025-09-18", + "last_updated": "2025-09-18", "type": "chat" }, { - "id": "inference-net/schematron-v2-small", - "name": "Schematron V2 Small", - "display_name": "Schematron V2 Small", + "id": "openai.gpt-5.4", + "name": "GPT-5.4", + "display_name": "GPT-5.4", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 4096 + "context": 1000000, + "output": 128000 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "none", + "low", + "medium", + "high", + "xhigh" + ] + } + }, + "attachment": true, "open_weights": false, - "release_date": "2026-04-16", - "last_updated": "2026-04-16", + "knowledge": "2025-08-31", + "release_date": "2026-03-05", + "last_updated": "2026-06-01", "type": "chat" }, { - "id": "inference-net/schematron-v2-turbo", - "name": "Schematron V2 Turbo", - "display_name": "Schematron V2 Turbo", + "id": "jp.anthropic.claude-haiku-4-5-20251001-v1:0", + "name": "Claude Haiku 4.5 (JP)", + "display_name": "Claude Haiku 4.5 (JP)", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 8192 + "context": 200000, + "output": 64000 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": false }, - "attachment": false, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": false, + "mode": "budget", + "budget": { + "min": 1024, + "unit": "tokens" + }, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", + "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." + ] + } + }, + "attachment": true, "open_weights": false, - "release_date": "2026-04-16", - "last_updated": "2026-04-16", + "knowledge": "2025-02-28", + "release_date": "2025-10-15", + "last_updated": "2025-10-15", "type": "chat" }, { - "id": "bytedance/doubao-seed-character", - "name": "Doubao Seed Character", - "display_name": "Doubao Seed Character", + "id": "eu.anthropic.claude-sonnet-5-5", + "name": "Claude Sonnet 5.5 (EU)", + "display_name": "Claude Sonnet 5.5 (EU)", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 32768 + "context": 1000000, + "output": 128000 }, - "temperature": true, - "tool_call": false, + "temperature": false, + "tool_call": true, "reasoning": { "supported": true, "default": true @@ -243904,48 +243249,77 @@ "extra_capabilities": { "reasoning": { "effort_options": [ - "none", - "high" + "low", + "medium", + "high", + "xhigh", + "max" ] } }, "attachment": true, "open_weights": false, - "release_date": "2026-06-23", - "last_updated": "2026-06-23", + "knowledge": "2026-06", + "release_date": "2026-09-28", + "last_updated": "2026-09-28", "type": "chat" }, { - "id": "bytedance/doubao-seed-2.1-pro", - "name": "Doubao Seed 2.1 Pro", - "display_name": "Doubao Seed 2.1 Pro", + "id": "in.anthropic.claude-haiku-4-5-20251001-v1:0", + "name": "Claude Haiku 4.5 (India)", + "display_name": "Claude Haiku 4.5 (India)", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 256000, - "output": 128000 + "context": 200000, + "output": 64000 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": false }, - "attachment": false, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": false, + "mode": "budget", + "budget": { + "min": 1024, + "unit": "tokens" + }, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", + "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." + ] + } + }, + "attachment": true, "open_weights": false, - "release_date": "2026-06-23", - "last_updated": "2026-06-23", + "knowledge": "2025-02-28", + "release_date": "2025-10-15", + "last_updated": "2025-10-15", "type": "chat" }, { - "id": "bytedance/doubao-seed-2.1-turbo", - "name": "Doubao Seed 2.1 Turbo", - "display_name": "Doubao Seed 2.1 Turbo", + "id": "us.deepseek.r1-v1:0", + "name": "DeepSeek-R1 (US)", + "display_name": "DeepSeek-R1 (US)", "modalities": { "input": [ "text" @@ -243955,51 +243329,68 @@ ] }, "limit": { - "context": 256000, - "output": 128000 + "context": 128000, + "output": 32768 }, "temperature": true, "tool_call": false, "reasoning": { - "supported": false + "supported": true, + "default": true }, "attachment": false, - "open_weights": false, - "release_date": "2026-06-23", - "last_updated": "2026-06-23", + "open_weights": true, + "knowledge": "2024-07", + "release_date": "2025-01-20", + "last_updated": "2025-05-29", "type": "chat" }, { - "id": "minimax/minimax-m2-her", - "name": "MiniMax M2-her", - "display_name": "MiniMax M2-her", + "id": "global.anthropic.claude-sonnet-5", + "name": "Claude Sonnet 5 (Global)", + "display_name": "Claude Sonnet 5 (Global)", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 65532, - "output": 2048 + "context": 1000000, + "output": 128000 }, - "temperature": true, - "tool_call": false, + "temperature": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" + ] + } + }, + "attachment": true, "open_weights": false, - "release_date": "2026-01-23", - "last_updated": "2026-01-23", + "knowledge": "2026-01-31", + "release_date": "2026-06-30", + "last_updated": "2026-06-30", "type": "chat" }, { - "id": "minimax/minimax-m2.1", - "name": "MiniMax M2.1", - "display_name": "MiniMax M2.1", + "id": "us-gov.openai.gpt-oss-20b-1:0", + "name": "gpt-oss-20b (GovCloud)", + "display_name": "gpt-oss-20b (GovCloud)", "modalities": { "input": [ "text" @@ -244009,8 +243400,8 @@ ] }, "limit": { - "context": 200000, - "output": 131072 + "context": 128000, + "output": 16384 }, "temperature": true, "tool_call": true, @@ -244020,46 +243411,24 @@ }, "extra_capabilities": { "reasoning": { - "supported": true + "effort_options": [ + "low", + "medium", + "high" + ] } }, "attachment": false, "open_weights": true, - "release_date": "2025-12-23", - "last_updated": "2025-12-23", - "type": "chat" - }, - { - "id": "minimax/minimax-01", - "name": "MiniMax 01", - "display_name": "MiniMax 01", - "modalities": { - "input": [ - "text", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 1000192, - "output": 16384 - }, - "tool_call": false, - "reasoning": { - "supported": false - }, - "attachment": true, - "open_weights": false, - "release_date": "2024-01-01", - "last_updated": "2025-01-15", + "knowledge": "2024-06", + "release_date": "2025-08-05", + "last_updated": "2025-08-05", "type": "chat" }, { - "id": "minimax/minimax-m3", - "name": "MiniMax M3", - "display_name": "MiniMax M3", + "id": "mistral.ministral-3-3b-instruct", + "name": "Ministral 3 3B", + "display_name": "Ministral 3 3B", "modalities": { "input": [ "text", @@ -244070,98 +243439,55 @@ ] }, "limit": { - "context": 512000, - "output": 80000 + "context": 256000, + "output": 8192 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true - } + "supported": false }, "attachment": true, "open_weights": true, - "release_date": "2026-06-01", - "last_updated": "2026-06-01", + "release_date": "2025-12-02", + "last_updated": "2025-12-02", "type": "chat" }, { - "id": "minimax/minimax-m3.1-flash", - "name": "MiniMax M3.1 Flash", - "display_name": "MiniMax M3.1 Flash", + "id": "eu.amazon.nova-pro-v1:0", + "name": "Nova Pro (EU)", + "display_name": "Nova Pro (EU)", "modalities": { "input": [ "text", "image", - "video" + "video", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 524288 + "context": 300000, + "output": 10000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" - ] - } + "supported": false }, "attachment": true, "open_weights": false, - "release_date": "2026-10-19", - "last_updated": "2026-10-19", - "type": "chat" - }, - { - "id": "minimax/minimax-m2", - "name": "MiniMax M2", - "display_name": "MiniMax M2", - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 200000, - "output": 131072 - }, - "temperature": true, - "tool_call": false, - "reasoning": { - "supported": true, - "default": true - }, - "attachment": false, - "open_weights": true, - "release_date": "2025-10-27", - "last_updated": "2025-10-27", + "knowledge": "2024-10", + "release_date": "2024-12-03", + "last_updated": "2024-12-03", "type": "chat" }, { - "id": "minimax/minimax-m2.5", - "name": "MiniMax M2.5", - "display_name": "MiniMax M2.5", + "id": "minimax.minimax-m2.1", + "name": "MiniMax-M2.1", + "display_name": "MiniMax-M2.1", "modalities": { "input": [ "text" @@ -244171,7 +243497,7 @@ ] }, "limit": { - "context": 204800, + "context": 196608, "output": 131072 }, "temperature": true, @@ -244182,19 +243508,20 @@ }, "extra_capabilities": { "reasoning": { - "supported": true + "supported": true, + "interleaved": true } }, "attachment": false, "open_weights": true, - "release_date": "2026-02-12", - "last_updated": "2026-02-12", + "release_date": "2025-12-23", + "last_updated": "2025-12-23", "type": "chat" }, { - "id": "minimax/minimax-latest", - "name": "MiniMax Latest", - "display_name": "MiniMax Latest", + "id": "qwen.qwen3-vl-235b-a22b", + "name": "Qwen3 VL 235B A22B Instruct", + "display_name": "Qwen3 VL 235B A22B Instruct", "modalities": { "input": [ "text", @@ -244205,24 +243532,25 @@ ] }, "limit": { - "context": 512000, - "output": 80000 + "context": 262144, + "output": 262000 }, + "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true + "supported": false }, "attachment": true, "open_weights": true, - "release_date": "2026-05-03", - "last_updated": "2026-05-03", + "knowledge": "2025-03-31", + "release_date": "2025-09-23", + "last_updated": "2025-11-25", "type": "chat" }, { - "id": "minimax/minimax-m2.7", - "name": "MiniMax M2.7", - "display_name": "MiniMax M2.7", + "id": "apac.amazon.nova-micro-v1:0", + "name": "Nova Micro (APAC)", + "display_name": "Nova Micro (APAC)", "modalities": { "input": [ "text" @@ -244232,92 +243560,67 @@ ] }, "limit": { - "context": 204800, - "output": 131072 + "context": 128000, + "output": 10000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } + "supported": false }, "attachment": false, - "open_weights": true, - "release_date": "2026-03-18", - "last_updated": "2026-03-18", + "open_weights": false, + "knowledge": "2024-10", + "release_date": "2024-12-03", + "last_updated": "2024-12-03", "type": "chat" }, { - "id": "minimax/minimax-m3:thinking", - "name": "MiniMax M3 Thinking", - "display_name": "MiniMax M3 Thinking", + "id": "jp.anthropic.claude-haiku-5-5", + "name": "Claude Haiku 5.5 (JP)", + "display_name": "Claude Haiku 5.5 (JP)", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 512000, - "output": 80000 + "context": 1000000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, "default": true }, - "attachment": true, - "open_weights": true, - "release_date": "2026-06-01", - "last_updated": "2026-06-01", - "type": "chat" - }, - { - "id": "minimax/minimax-m2.7-turbo", - "name": "MiniMax M2.7 Turbo", - "display_name": "MiniMax M2.7 Turbo", - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 204800, - "output": 131072 - }, - "tool_call": true, - "reasoning": { - "supported": true, - "default": true + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" + ] + } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-03-18", - "last_updated": "2026-03-18", + "attachment": true, + "open_weights": false, + "knowledge": "2026-06", + "release_date": "2026-10-07", + "last_updated": "2026-10-07", "type": "chat" }, { - "id": "baseten/Kimi-K2-Instruct-FP4", - "name": "Kimi K2 0711 Instruct FP4", - "display_name": "Kimi K2 0711 Instruct FP4", + "id": "qwen.qwen3-next-80b-a3b", + "name": "Qwen3-Next 80B-A3B Instruct", + "display_name": "Qwen3-Next 80B-A3B Instruct", "modalities": { "input": [ "text" @@ -244327,27 +243630,30 @@ ] }, "limit": { - "context": 131072, - "output": 131072 + "context": 262144, + "output": 262000 }, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { "supported": false }, "attachment": false, "open_weights": true, - "release_date": "2024-01-01", - "last_updated": "2025-07-11", + "knowledge": "2025-04", + "release_date": "2025-09-11", + "last_updated": "2025-11-25", "type": "chat" }, { - "id": "unbiased/pareto-26.9", - "name": "Pareto 26.9", - "display_name": "Pareto 26.9", + "id": "google.gemma-4-26b-a4b", + "name": "Gemma 4 26B A4B IT", + "display_name": "Gemma 4 26B A4B IT", "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ "text" @@ -244355,22 +243661,32 @@ }, "limit": { "context": 262144, - "output": 131072 + "output": 32768 }, + "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "none", + "high" + ] + } }, "attachment": true, - "open_weights": false, - "release_date": "2026-09-18", - "last_updated": "2026-09-18", + "open_weights": true, + "release_date": "2026-04-02", + "last_updated": "2026-04-02", "type": "chat" }, { - "id": "unbiased/pareto-26.10", - "name": "Pareto 26.10", - "display_name": "Pareto 26.10", + "id": "nvidia.nemotron-nano-12b-v2", + "name": "NVIDIA Nemotron Nano 12B v2 VL BF16", + "display_name": "NVIDIA Nemotron Nano 12B v2 VL BF16", "modalities": { "input": [ "text", @@ -244381,23 +243697,24 @@ ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 131072, + "output": 8192 }, + "temperature": true, "tool_call": true, "reasoning": { "supported": false }, "attachment": true, - "open_weights": false, - "release_date": "2026-10-01", - "last_updated": "2026-10-01", + "open_weights": true, + "release_date": "2025-10-28", + "last_updated": "2025-10-28", "type": "chat" }, { - "id": "aleph-alpha/kolibri-1", - "name": "Kolibri 1", - "display_name": "Kolibri 1", + "id": "openai.gpt-oss-safeguard-20b", + "name": "GPT OSS Safeguard 20B", + "display_name": "GPT OSS Safeguard 20B", "modalities": { "input": [ "text" @@ -244407,8 +243724,8 @@ ] }, "limit": { - "context": 1048576, - "output": 943718 + "context": 128000, + "output": 16384 }, "temperature": true, "tool_call": true, @@ -244419,7 +243736,6 @@ "extra_capabilities": { "reasoning": { "effort_options": [ - "none", "low", "medium", "high" @@ -244428,105 +243744,138 @@ }, "attachment": false, "open_weights": true, - "knowledge": "2026-06-18", - "release_date": "2026-10-03", - "last_updated": "2026-10-03", + "release_date": "2025-10-29", + "last_updated": "2025-10-29", "type": "chat" }, { - "id": "x-ai/grok-4.20-multi-agent", - "name": "Grok 4.20 Multi-Agent", - "display_name": "Grok 4.20 Multi-Agent", + "id": "anthropic.claude-opus-4-6-v1", + "name": "Claude Opus 4.6", + "display_name": "Claude Opus 4.6", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 2000000, - "output": 131072 + "context": 1000000, + "output": 128000 }, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": false, + "mode": "mixed", + "budget": { + "min": 1024, + "unit": "tokens" + }, + "effort": "high", "effort_options": [ "low", "medium", "high", - "xhigh" + "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Anthropic recommends adaptive thinking with effort for Claude 4.6; budget_tokens remains a deprecated compatibility path.", + "Anthropic API defaults effort to high; lower effort levels should be chosen per workload." ] } }, "attachment": true, "open_weights": false, - "release_date": "2026-03-31", - "last_updated": "2026-03-31", + "knowledge": "2025-05-31", + "release_date": "2026-02-05", + "last_updated": "2026-03-13", "type": "chat" }, { - "id": "x-ai/grok-4.5", - "name": "Grok 4.5", - "display_name": "Grok 4.5", + "id": "us.anthropic.claude-sonnet-4-5-20250929-v1:0", + "name": "Claude Sonnet 4.5 (US)", + "display_name": "Claude Sonnet 4.5 (US)", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 500000, - "output": 450000 + "context": 200000, + "output": 64000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, - "effort_options": [ - "low", - "medium", - "high" + "default_enabled": false, + "mode": "budget", + "budget": { + "min": 1024, + "unit": "tokens" + }, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", + "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." ] } }, "attachment": true, "open_weights": false, - "release_date": "2026-07-08", - "last_updated": "2026-07-08", + "knowledge": "2025-07-31", + "release_date": "2025-09-29", + "last_updated": "2025-09-29", "type": "chat" }, { - "id": "x-ai/grok-4.20", - "name": "Grok 4.20", - "display_name": "Grok 4.20", + "id": "zai.glm-5", + "name": "GLM-5", + "display_name": "GLM-5", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 2000000, + "context": 202752, "output": 131072 }, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -244534,114 +243883,135 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "low", - "medium", - "high" - ] + "supported": true, + "interleaved": true } }, - "attachment": true, - "open_weights": false, - "release_date": "2026-03-31", - "last_updated": "2026-03-31", + "attachment": false, + "open_weights": true, + "release_date": "2026-02-12", + "last_updated": "2026-03-18", "type": "chat" }, { - "id": "x-ai/grok-build-0.1", - "name": "Grok Build 0.1", - "display_name": "Grok Build 0.1", + "id": "au.anthropic.claude-sonnet-4-5-20250929-v1:0", + "name": "Claude Sonnet 4.5 (AU)", + "display_name": "Claude Sonnet 4.5 (AU)", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 256000, - "output": 230400 + "context": 200000, + "output": 64000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, - "effort_options": [ - "low", - "medium", - "high" + "default_enabled": false, + "mode": "budget", + "budget": { + "min": 1024, + "unit": "tokens" + }, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", + "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." ] } }, "attachment": true, "open_weights": false, - "release_date": "2026-04-16", - "last_updated": "2026-04-16", + "knowledge": "2025-07-31", + "release_date": "2025-09-29", + "last_updated": "2025-09-29", "type": "chat" }, { - "id": "x-ai/grok-4.7", - "name": "Grok 4.7", - "display_name": "Grok 4.7", + "id": "au.anthropic.claude-haiku-4-5-20251001-v1:0", + "name": "Claude Haiku 4.5 (AU)", + "display_name": "Claude Haiku 4.5 (AU)", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 500000, - "output": 500000 + "context": 200000, + "output": 64000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "low", - "medium", - "high", - "xhigh" + "supported": true, + "default_enabled": false, + "mode": "budget", + "budget": { + "min": 1024, + "unit": "tokens" + }, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", + "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-05", - "release_date": "2026-09-21", - "last_updated": "2026-09-21", + "knowledge": "2025-02-28", + "release_date": "2025-10-15", + "last_updated": "2025-10-15", "type": "chat" }, { - "id": "x-ai/grok-4.3", - "name": "Grok 4.3", - "display_name": "Grok 4.3", + "id": "us-gov.openai.gpt-oss-120b-1:0", + "name": "gpt-oss-120b (GovCloud)", + "display_name": "gpt-oss-120b (GovCloud)", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 900000 + "context": 128000, + "output": 16384 }, "temperature": true, "tool_call": true, @@ -244651,7 +244021,6 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, "effort_options": [ "low", "medium", @@ -244659,29 +244028,30 @@ ] } }, - "attachment": true, - "open_weights": false, - "release_date": "2026-04-17", - "last_updated": "2026-04-17", + "attachment": false, + "open_weights": true, + "knowledge": "2024-06", + "release_date": "2025-08-05", + "last_updated": "2025-08-05", "type": "chat" }, { - "id": "x-ai/grok-latest", - "name": "Grok Latest", - "display_name": "Grok Latest", + "id": "minimax.minimax-m2.5", + "name": "MiniMax-M2.5", + "display_name": "MiniMax-M2.5", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 500000, - "output": 450000 + "context": 196608, + "output": 98304 }, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -244689,149 +244059,113 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "low", - "medium", - "high", - "xhigh" - ] + "supported": true, + "interleaved": true } }, - "attachment": true, - "open_weights": false, - "release_date": "2026-05-03", - "last_updated": "2026-05-03", + "attachment": false, + "open_weights": true, + "release_date": "2026-02-12", + "last_updated": "2026-03-18", "type": "chat" }, { - "id": "x-ai/grok-4.6", - "name": "Grok 4.6", - "display_name": "Grok 4.6", + "id": "anthropic.claude-opus-4-5-20251101-v1:0", + "name": "Claude Opus 4.5", + "display_name": "Claude Opus 4.5", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 500000, - "output": 450000 + "context": 200000, + "output": 64000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": false, + "mode": "mixed", + "budget": { + "min": 1024, + "unit": "tokens" + }, + "effort": "high", "effort_options": [ "low", "medium", - "high", - "xhigh" + "high" + ], + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Claude Opus 4.5 uses manual thinking.type = \"enabled\" with budget_tokens; effort can be used alongside the thinking budget.", + "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header." ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-02-01", - "release_date": "2026-08-12", - "last_updated": "2026-08-12", + "knowledge": "2025-05", + "release_date": "2025-11-24", + "last_updated": "2025-11-24", "type": "chat" }, { - "id": "ornith-ai/ornith-1.5-122b", - "name": "Ornith 1.5 122B", - "display_name": "Ornith 1.5 122B", + "id": "zai.glm-4.7", + "name": "GLM-4.7", + "display_name": "GLM-4.7", "modalities": { "input": [ - "text", - "image" - ], - "output": [ "text" - ] - }, - "limit": { - "context": 262144, - "output": 32768 - }, - "tool_call": true, - "reasoning": { - "supported": true, - "default": true - }, - "attachment": true, - "open_weights": true, - "release_date": "2026-10-20", - "last_updated": "2026-10-20", - "type": "chat" - }, - { - "id": "ornith-ai/ornith-1.5-35b-a3b:thinking", - "name": "Ornith 1.5 35B Thinking", - "display_name": "Ornith 1.5 35B Thinking", - "modalities": { - "input": [ - "text", - "image" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 32768 + "context": 202752, + "output": 131072 }, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, "default": true }, - "attachment": true, - "open_weights": true, - "release_date": "2026-07-29", - "last_updated": "2026-08-20", - "type": "chat" - }, - { - "id": "ornith-ai/ornith-1.5-35b-a3b", - "name": "Ornith 1.5 35B", - "display_name": "Ornith 1.5 35B", - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 262144, - "output": 32768 - }, - "tool_call": true, - "reasoning": { - "supported": true, - "default": true + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true + } }, - "attachment": true, + "attachment": false, "open_weights": true, - "release_date": "2026-07-29", - "last_updated": "2026-08-20", + "knowledge": "2025-04", + "release_date": "2025-12-22", + "last_updated": "2025-12-22", "type": "chat" }, { - "id": "poolside/laguna-s-2.1:thinking", - "name": "Laguna S 2.1 Thinking", - "display_name": "Laguna S 2.1 Thinking", + "id": "zai.glm-4.7-flash", + "name": "GLM-4.7-Flash", + "display_name": "GLM-4.7-Flash", "modalities": { "input": [ "text" @@ -244841,7 +244175,7 @@ ] }, "limit": { - "context": 1048576, + "context": 202752, "output": 131072 }, "temperature": true, @@ -244850,27 +244184,37 @@ "supported": true, "default": true }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true + } + }, "attachment": false, "open_weights": true, - "release_date": "2026-07-21", - "last_updated": "2026-07-21", + "knowledge": "2025-04", + "release_date": "2026-01-19", + "last_updated": "2026-01-19", "type": "chat" }, { - "id": "poolside/laguna-s-2.1", - "name": "Laguna S 2.1", - "display_name": "Laguna S 2.1", + "id": "global.amazon.nova-2-lite-v1:0", + "name": "Nova 2 Lite (Global)", + "display_name": "Nova 2 Lite (Global)", "modalities": { "input": [ - "text" + "text", + "image", + "video", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 1000000, + "output": 65535 }, "temperature": true, "tool_call": true, @@ -244880,31 +244224,35 @@ }, "extra_capabilities": { "reasoning": { - "supported": true + "effort_options": [ + "low", + "medium", + "high" + ] } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-07-21", - "last_updated": "2026-07-21", + "attachment": true, + "open_weights": false, + "knowledge": "2025-10", + "release_date": "2025-12-02", + "last_updated": "2025-12-01", "type": "chat" }, { - "id": "fireworks/ember-1", - "name": "Ember 1", - "display_name": "Ember 1", + "id": "us.zai.glm-5.3", + "name": "GLM-5.3 (US)", + "display_name": "GLM-5.3 (US)", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 1000000, + "output": 128000 }, "temperature": true, "tool_call": true, @@ -244914,24 +244262,27 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "interleaved": true, "effort_options": [ - "none", "low", + "medium", "high", + "xhigh", "max" ] } }, - "attachment": true, - "open_weights": false, - "release_date": "2026-09-22", - "last_updated": "2026-09-22", + "attachment": false, + "open_weights": true, + "release_date": "2026-08-14", + "last_updated": "2026-08-14", "type": "chat" }, { - "id": "nothingiisreal/L3.1-70B-Celeste-V0.1-BF16", - "name": "Llama 3.1 70B Celeste v0.1", - "display_name": "Llama 3.1 70B Celeste v0.1", + "id": "us.writer.palmyra-x5-v1:0", + "name": "Palmyra X5 (US)", + "display_name": "Palmyra X5 (US)", "modalities": { "input": [ "text" @@ -244941,23 +244292,25 @@ ] }, "limit": { - "context": 32768, - "output": 16384 + "context": 1040000, + "output": 8192 }, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, "attachment": false, - "open_weights": true, - "release_date": "2024-01-01", - "last_updated": "2024-07-23", + "open_weights": false, + "release_date": "2025-04-28", + "last_updated": "2025-04-28", "type": "chat" }, { - "id": "anthracite-org/magnum-v2-72b", - "name": "Magnum V2 72B", - "display_name": "Magnum V2 72B", + "id": "meta.llama3-3-70b-instruct-v1:0", + "name": "Llama 3.3 70B Instruct", + "display_name": "Llama 3.3 70B Instruct", "modalities": { "input": [ "text" @@ -244967,102 +244320,121 @@ ] }, "limit": { - "context": 16384, - "output": 8192 + "context": 128000, + "output": 4096 }, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { "supported": false }, "attachment": false, "open_weights": true, - "release_date": "2024-01-01", - "last_updated": "2024-07-01", + "knowledge": "2023-12", + "release_date": "2024-12-06", + "last_updated": "2024-12-06", "type": "chat" }, { - "id": "anthracite-org/magnum-v4-72b", - "name": "Magnum v4 72B", - "display_name": "Magnum v4 72B", + "id": "qwen.qwen3-coder-30b-a3b-v1:0", + "name": "Qwen3-Coder 30B-A3B Instruct", + "display_name": "Qwen3-Coder 30B-A3B Instruct", "modalities": { "input": [ - "text", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 16384, - "output": 8192 + "context": 262144, + "output": 131072 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { "supported": false }, - "attachment": true, + "attachment": false, "open_weights": true, - "knowledge": "2024-06-30", - "release_date": "2024-10-22", - "last_updated": "2024-10-22", + "knowledge": "2025-04", + "release_date": "2025-07-31", + "last_updated": "2025-09-18", "type": "chat" }, { - "id": "abliteration-ai/abliterated-model-large-v2", - "name": "Abliterated Model Large V2", - "display_name": "Abliterated Model Large V2", + "id": "eu.anthropic.claude-sonnet-4-20250514-v1:0", + "name": "Claude Sonnet 4 (EU)", + "display_name": "Claude Sonnet 4 (EU)", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 999990 + "context": 200000, + "output": 64000 }, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "low", - "high", - "max" + "supported": true, + "default_enabled": false, + "mode": "budget", + "budget": { + "min": 1024, + "unit": "tokens" + }, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", + "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." ] } }, - "attachment": false, + "attachment": true, "open_weights": false, - "release_date": "2026-08-31", - "last_updated": "2026-08-31", + "knowledge": "2025-03-31", + "release_date": "2025-05-22", + "last_updated": "2025-05-22", "type": "chat" }, { - "id": "abliteration-ai/abliterated-model", - "name": "Abliterated Model", - "display_name": "Abliterated Model", + "id": "global.anthropic.claude-sonnet-5-5", + "name": "Claude Sonnet 5.5 (Global)", + "display_name": "Claude Sonnet 5.5 (Global)", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 262134 + "context": 1000000, + "output": 128000 }, - "tool_call": false, + "temperature": false, + "tool_call": true, "reasoning": { "supported": true, "default": true @@ -245070,8 +244442,6 @@ "extra_capabilities": { "reasoning": { "effort_options": [ - "none", - "minimal", "low", "medium", "high", @@ -245082,26 +244452,30 @@ }, "attachment": true, "open_weights": false, - "release_date": "2026-08-27", - "last_updated": "2026-08-27", + "knowledge": "2026-06", + "release_date": "2026-09-28", + "last_updated": "2026-09-28", "type": "chat" }, { - "id": "abliteration-ai/abliterated-model-large", - "name": "Abliterated Model Large", - "display_name": "Abliterated Model Large", + "id": "in.openai.gpt-5.6-terra", + "name": "GPT-5.6 Terra (India)", + "display_name": "GPT-5.6 Terra (India)", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 999990 + "context": 1050000, + "output": 128000 }, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -245111,7 +244485,6 @@ "reasoning": { "effort_options": [ "none", - "minimal", "low", "medium", "high", @@ -245120,29 +244493,32 @@ ] } }, - "attachment": false, + "attachment": true, "open_weights": false, - "release_date": "2026-08-27", - "last_updated": "2026-08-27", + "knowledge": "2026-02-16", + "release_date": "2026-07-09", + "last_updated": "2026-07-09", "type": "chat" }, { - "id": "abliteration-ai/abliterated-model-large-flash", - "name": "Abliterated Large Flash", - "display_name": "Abliterated Large Flash", + "id": "us.anthropic.claude-haiku-5-5", + "name": "Claude Haiku 5.5 (US)", + "display_name": "Claude Haiku 5.5 (US)", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 262144 + "context": 1000000, + "output": 128000 }, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -245151,8 +244527,6 @@ "extra_capabilities": { "reasoning": { "effort_options": [ - "none", - "minimal", "low", "medium", "high", @@ -245163,46 +244537,61 @@ }, "attachment": true, "open_weights": false, - "release_date": "2026-10-23", - "last_updated": "2026-10-23", + "knowledge": "2026-06", + "release_date": "2026-10-07", + "last_updated": "2026-10-07", "type": "chat" }, { - "id": "elorian/glimpse", - "name": "Elorian Glimpse", - "display_name": "Elorian Glimpse", + "id": "eu.anthropic.claude-haiku-5-5", + "name": "Claude Haiku 5.5 (EU)", + "display_name": "Claude Haiku 5.5 (EU)", "modalities": { "input": [ "text", "image", - "video" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 256000 + "context": 1000000, + "output": 128000 }, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, "default": true }, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" + ] + } + }, "attachment": true, "open_weights": false, - "release_date": "2026-10-17", - "last_updated": "2026-10-17", + "knowledge": "2026-06", + "release_date": "2026-10-07", + "last_updated": "2026-10-07", "type": "chat" }, { - "id": "deepcogito/cogito-v1-preview-qwen-32B", - "name": "Cogito v1 Preview Qwen 32B", - "display_name": "Cogito v1 Preview Qwen 32B", + "id": "mistral.pixtral-large-2502-v1:0", + "name": "Pixtral Large (25.02)", + "display_name": "Pixtral Large (25.02)", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" @@ -245210,74 +244599,65 @@ }, "limit": { "context": 128000, - "output": 32768 + "output": 8192 }, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { "supported": false }, - "attachment": false, - "open_weights": true, - "release_date": "2025-05-10", - "last_updated": "2025-05-10", + "attachment": true, + "open_weights": false, + "release_date": "2025-04-08", + "last_updated": "2025-04-08", "type": "chat" }, { - "id": "LatitudeGames/Wayfarer-Large-70B-Llama-3.3", - "name": "Llama 3.3 70B Wayfarer", - "display_name": "Llama 3.3 70B Wayfarer", + "id": "global.openai.gpt-5.6-luna", + "name": "GPT-5.6 Luna (Global)", + "display_name": "GPT-5.6 Luna (Global)", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 32768, - "output": 16384 + "context": 1050000, + "output": 128000 }, - "tool_call": false, + "temperature": false, + "tool_call": true, "reasoning": { - "supported": false - }, - "attachment": false, - "open_weights": true, - "release_date": "2025-02-20", - "last_updated": "2025-02-20", - "type": "chat" - }, - { - "id": "featherless-ai/Qwerky-72B", - "name": "Qwerky 72B", - "display_name": "Qwerky 72B", - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 32000, - "output": 8192 + "supported": true, + "default": true }, - "tool_call": false, - "reasoning": { - "supported": false + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "none", + "low", + "medium", + "high", + "xhigh", + "max" + ] + } }, - "attachment": false, - "open_weights": true, - "release_date": "2024-01-01", - "last_updated": "2025-03-20", + "attachment": true, + "open_weights": false, + "knowledge": "2026-02-16", + "release_date": "2026-07-09", + "last_updated": "2026-07-09", "type": "chat" }, { - "id": "cohere/command-r-plus-08-2024", - "name": "Cohere: Command R+", - "display_name": "Cohere: Command R+", + "id": "writer.palmyra-x5-v1:0", + "name": "Palmyra X5", + "display_name": "Palmyra X5", "modalities": { "input": [ "text" @@ -245287,51 +244667,66 @@ ] }, "limit": { - "context": 128000, - "output": 4096 + "context": 1040000, + "output": 8192 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, "attachment": false, - "open_weights": true, - "knowledge": "2024-06-01", - "release_date": "2024-08-30", - "last_updated": "2024-08-30", + "open_weights": false, + "release_date": "2025-04-28", + "last_updated": "2025-04-28", "type": "chat" }, { - "id": "failspy/Meta-Llama-3-70B-Instruct-abliterated-v3.5", - "name": "Llama 3 70B abliterated", - "display_name": "Llama 3 70B abliterated", + "id": "us.openai.gpt-6.1-sol", + "name": "GPT-6.1 Sol (US)", + "display_name": "GPT-6.1 Sol (US)", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 8192, - "output": 8192 + "context": 1050000, + "output": 128000 }, - "tool_call": false, + "temperature": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, - "open_weights": true, - "release_date": "2025-07-26", - "last_updated": "2025-07-26", + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" + ] + } + }, + "attachment": true, + "open_weights": false, + "knowledge": "2026-04-30", + "release_date": "2026-09-29", + "last_updated": "2026-09-29", "type": "chat" }, { - "id": "sakana/fugu-max", - "name": "Fugu Max", - "display_name": "Fugu Max", + "id": "us.anthropic.claude-opus-5-5", + "name": "Claude Opus 5.5 (US)", + "display_name": "Claude Opus 5.5 (US)", "modalities": { "input": [ "text", @@ -245355,6 +244750,8 @@ "extra_capabilities": { "reasoning": { "effort_options": [ + "low", + "medium", "high", "xhigh", "max" @@ -245363,18 +244760,20 @@ }, "attachment": true, "open_weights": false, - "release_date": "2026-09-11", - "last_updated": "2026-09-11", + "knowledge": "2026-06", + "release_date": "2026-09-22", + "last_updated": "2026-09-22", "type": "chat" }, { - "id": "sakana/fugu-ultra-v1.1", - "name": "Fugu Ultra v1.1", - "display_name": "Fugu Ultra v1.1", + "id": "global.anthropic.claude-opus-5", + "name": "Claude Opus 5 (Global)", + "display_name": "Claude Opus 5 (Global)", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" @@ -245382,8 +244781,9 @@ }, "limit": { "context": 1000000, - "output": 16384 + "output": 128000 }, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -245392,6 +244792,8 @@ "extra_capabilities": { "reasoning": { "effort_options": [ + "low", + "medium", "high", "xhigh", "max" @@ -245400,18 +244802,20 @@ }, "attachment": true, "open_weights": false, + "knowledge": "2026-05", "release_date": "2026-07-24", "last_updated": "2026-07-24", "type": "chat" }, { - "id": "sakana/fugu-ultra", - "name": "Fugu Ultra", - "display_name": "Fugu Ultra", + "id": "us.anthropic.claude-fable-5-1", + "name": "Claude Fable 5.1 (US)", + "display_name": "Claude Fable 5.1 (US)", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" @@ -245419,7 +244823,7 @@ }, "limit": { "context": 1000000, - "output": 16384 + "output": 128000 }, "temperature": false, "tool_call": true, @@ -245429,165 +244833,127 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "high", "effort_options": [ + "low", + "medium", "high", "xhigh", "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "omitted", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Adaptive thinking is always on for Claude Fable 5 and Claude Mythos 5; thinking.type = \"disabled\" is rejected.", + "Manual budget_tokens requests return 400 on Claude Fable 5 and Claude Mythos 5.", + "thinking.display defaults to omitted; set display to summarized to receive readable thinking summaries." ] } }, "attachment": true, "open_weights": false, - "release_date": "2026-06-15", - "last_updated": "2026-06-15", - "type": "chat" - }, - { - "id": "EVA-UNIT-01/EVA-Qwen2.5-72B-v0.2", - "name": "EVA-Qwen2.5-72B-v0.2", - "display_name": "EVA-Qwen2.5-72B-v0.2", - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 16384, - "output": 8192 - }, - "tool_call": false, - "reasoning": { - "supported": false - }, - "attachment": false, - "open_weights": true, - "release_date": "2025-09-25", - "last_updated": "2025-09-25", - "type": "chat" - }, - { - "id": "EVA-UNIT-01/EVA-LLaMA-3.33-70B-v0.1", - "name": "EVA-LLaMA-3.33-70B-v0.1", - "display_name": "EVA-LLaMA-3.33-70B-v0.1", - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 32768, - "output": 16384 - }, - "tool_call": false, - "reasoning": { - "supported": false - }, - "attachment": false, - "open_weights": true, - "release_date": "2025-09-25", - "last_updated": "2025-09-25", + "knowledge": "2026-06", + "release_date": "2026-09-01", + "last_updated": "2026-09-01", "type": "chat" }, { - "id": "EVA-UNIT-01/EVA-Qwen2.5-32B-v0.2", - "name": "EVA-Qwen2.5-32B-v0.2", - "display_name": "EVA-Qwen2.5-32B-v0.2", + "id": "openai.gpt-5.6-luna", + "name": "GPT-5.6 Luna", + "display_name": "GPT-5.6 Luna", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 16384, - "output": 8192 + "context": 1050000, + "output": 128000 }, - "tool_call": false, + "temperature": false, + "tool_call": true, "reasoning": { - "supported": false - }, - "attachment": false, - "open_weights": true, - "release_date": "2025-07-26", - "last_updated": "2025-07-26", - "type": "chat" - }, - { - "id": "EVA-UNIT-01/EVA-LLaMA-3.33-70B-v0.0", - "name": "EVA Llama 3.33 70B", - "display_name": "EVA Llama 3.33 70B", - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 32768, - "output": 16384 + "supported": true, + "default": true }, - "tool_call": false, - "reasoning": { - "supported": false + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "none", + "low", + "medium", + "high", + "xhigh", + "max" + ] + } }, - "attachment": false, - "open_weights": true, - "release_date": "2025-07-26", - "last_updated": "2025-07-26", + "attachment": true, + "open_weights": false, + "knowledge": "2026-02-16", + "release_date": "2026-07-09", + "last_updated": "2026-07-09", "type": "chat" }, { - "id": "ReadyArt/MS3.2-The-Omega-Directive-24B-Unslop-v2.0", - "name": "Omega Directive 24B Unslop v2.0", - "display_name": "Omega Directive 24B Unslop v2.0", + "id": "us.mistral.pixtral-large-2502-v1:0", + "name": "Pixtral Large (25.02) (US)", + "display_name": "Pixtral Large (25.02) (US)", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 32768, - "output": 32768 + "context": 128000, + "output": 8192 }, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { "supported": false }, - "attachment": false, - "open_weights": true, - "release_date": "2025-12-08", - "last_updated": "2025-12-08", + "attachment": true, + "open_weights": false, + "release_date": "2025-04-08", + "last_updated": "2025-04-08", "type": "chat" }, { - "id": "xingchen-agi/xing4.0-29b-a4b", - "name": "Xing 4.0 29B A4B", - "display_name": "Xing 4.0 29B A4B", + "id": "in.anthropic.claude-opus-5", + "name": "Claude Opus 5 (India)", + "display_name": "Claude Opus 5 (India)", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 1000000, + "output": 128000 }, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -245596,8 +244962,6 @@ "extra_capabilities": { "reasoning": { "effort_options": [ - "none", - "minimal", "low", "medium", "high", @@ -245606,16 +244970,17 @@ ] } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-10-24", - "last_updated": "2026-10-24", + "attachment": true, + "open_weights": false, + "knowledge": "2026-05", + "release_date": "2026-07-24", + "last_updated": "2026-07-24", "type": "chat" }, { - "id": "GalrionSoftworks/MN-LooseCannon-12B-v1", - "name": "MN-LooseCannon-12B-v1", - "display_name": "MN-LooseCannon-12B-v1", + "id": "nvidia.nemotron-nano-9b-v2", + "name": "NVIDIA Nemotron Nano 9B v2", + "display_name": "NVIDIA Nemotron Nano 9B v2", "modalities": { "input": [ "text" @@ -245625,35 +244990,39 @@ ] }, "limit": { - "context": 16384, + "context": 131072, "output": 8192 }, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { "supported": false }, "attachment": false, "open_weights": true, - "release_date": "2024-01-01", - "last_updated": "2024-07-01", + "knowledge": "2024-09", + "release_date": "2025-08-18", + "last_updated": "2025-08-18", "type": "chat" }, { - "id": "reka/reka-edge-2603", - "name": "Reka Edge 2603", - "display_name": "Reka Edge 2603", + "id": "ca.amazon.nova-lite-v1:0", + "name": "Nova Lite (CA)", + "display_name": "Nova Lite (CA)", "modalities": { "input": [ "text", - "image" + "image", + "video", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 16384, - "output": 16384 + "context": 300000, + "output": 10000 }, "temperature": true, "tool_call": true, @@ -245661,132 +245030,145 @@ "supported": false }, "attachment": true, - "open_weights": true, - "release_date": "2026-03-11", - "last_updated": "2026-03-11", + "open_weights": false, + "knowledge": "2024-10", + "release_date": "2024-12-03", + "last_updated": "2024-12-03", "type": "chat" }, { - "id": "reka/reka-flash-3", - "name": "Reka Flash 3", - "display_name": "Reka Flash 3", + "id": "au.anthropic.claude-opus-4-6-v1", + "name": "AU Anthropic Claude Opus 4.6", + "display_name": "AU Anthropic Claude Opus 4.6", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 65536, - "output": 65536 + "context": 1000000, + "output": 128000 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-01-31", - "release_date": "2025-03-10", - "last_updated": "2025-03-10", + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": false, + "mode": "mixed", + "budget": { + "min": 1024, + "unit": "tokens" + }, + "effort": "high", + "effort_options": [ + "low", + "medium", + "high", + "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Anthropic recommends adaptive thinking with effort for Claude 4.6; budget_tokens remains a deprecated compatibility path.", + "Anthropic API defaults effort to high; lower effort levels should be chosen per workload." + ] + } + }, + "attachment": true, + "open_weights": false, + "knowledge": "2025-05-31", + "release_date": "2026-02-05", + "last_updated": "2026-03-13", "type": "chat" }, { - "id": "xiaomi/mimo-v2.6-pro", - "name": "MiMo V2.6 Pro", - "display_name": "MiMo V2.6 Pro", + "id": "eu.amazon.nova-lite-v1:0", + "name": "Nova Lite (EU)", + "display_name": "Nova Lite (EU)", "modalities": { "input": [ "text", "image", - "audio", - "video" + "video", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 300000, + "output": 10000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "none", - "high" - ] - } + "supported": false }, "attachment": true, - "open_weights": true, - "release_date": "2026-09-22", - "last_updated": "2026-09-22", + "open_weights": false, + "knowledge": "2024-10", + "release_date": "2024-12-03", + "last_updated": "2024-12-03", "type": "chat" }, { - "id": "xiaomi/mimo-v2.6-flash-uncensored:thinking", - "name": "MiMo V2.6 Flash Uncensored Thinking", - "display_name": "MiMo V2.6 Flash Uncensored Thinking", + "id": "us.amazon.nova-micro-v1:0", + "name": "Nova Micro (US)", + "display_name": "Nova Micro (US)", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 128000, + "output": 10000 }, + "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "max" - ] - } + "supported": false }, - "attachment": true, - "open_weights": true, - "release_date": "2026-09-26", - "last_updated": "2026-09-26", + "attachment": false, + "open_weights": false, + "knowledge": "2024-10", + "release_date": "2024-12-03", + "last_updated": "2024-12-03", "type": "chat" }, { - "id": "xiaomi/mimo-v2.6-flash", - "name": "MiMo V2.6 Flash", - "display_name": "MiMo V2.6 Flash", + "id": "openai.gpt-oss-120b-1:0", + "name": "gpt-oss-120b", + "display_name": "gpt-oss-120b", "modalities": { "input": [ - "text", - "image", - "audio", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 131072, + "output": 128000 }, "temperature": true, "tool_call": true, @@ -245797,35 +245179,35 @@ "extra_capabilities": { "reasoning": { "effort_options": [ - "none", + "low", + "medium", "high" ] } }, - "attachment": true, + "attachment": false, "open_weights": true, - "release_date": "2026-09-22", - "last_updated": "2026-09-22", + "knowledge": "2024-06", + "release_date": "2025-08-05", + "last_updated": "2025-08-05", "type": "chat" }, { - "id": "xiaomi/mimo-v2.6-pro-ultraspeed", - "name": "MiMo V2.6 Pro UltraSpeed", - "display_name": "MiMo V2.6 Pro UltraSpeed", + "id": "us.xai.grok-4.6", + "name": "Grok 4.6 (US)", + "display_name": "Grok 4.6 (US)", "modalities": { "input": [ "text", - "image", - "audio", - "video" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 500000, + "output": 500000 }, "temperature": true, "tool_call": true, @@ -245836,77 +245218,67 @@ "extra_capabilities": { "reasoning": { "effort_options": [ - "none", - "high" + "low", + "medium", + "high", + "xhigh" ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-09-21", - "last_updated": "2026-09-21", + "open_weights": false, + "knowledge": "2026-02-01", + "release_date": "2026-08-12", + "last_updated": "2026-08-18", "type": "chat" }, { - "id": "xiaomi/mimo-v2.5", - "name": "MiMo V2.5", - "display_name": "MiMo V2.5", + "id": "qwen.qwen3-coder-480b-a35b-v1:0", + "name": "Qwen3-Coder 480B-A35B Instruct", + "display_name": "Qwen3-Coder 480B-A35B Instruct", "modalities": { "input": [ - "text", - "image", - "audio", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 131072, + "output": 65536 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "none", - "high" - ] - } + "supported": false }, - "attachment": true, + "attachment": false, "open_weights": true, - "knowledge": "2024-12", - "release_date": "2026-04-22", - "last_updated": "2026-04-22", + "knowledge": "2025-04", + "release_date": "2025-07-23", + "last_updated": "2025-09-18", "type": "chat" }, { - "id": "xiaomi/mimo-v2.5:thinking", - "name": "MiMo V2.5 Thinking", - "display_name": "MiMo V2.5 Thinking", + "id": "openai.gpt-6.1-sol", + "name": "GPT-6.1 Sol", + "display_name": "GPT-6.1 Sol", "modalities": { "input": [ "text", "image", - "audio", - "video" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 1050000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -245915,22 +245287,25 @@ "extra_capabilities": { "reasoning": { "effort_options": [ - "none", - "high" + "low", + "medium", + "high", + "xhigh", + "max" ] } }, "attachment": true, - "open_weights": true, - "knowledge": "2024-12", - "release_date": "2026-04-22", - "last_updated": "2026-04-22", + "open_weights": false, + "knowledge": "2026-04-30", + "release_date": "2026-09-29", + "last_updated": "2026-09-29", "type": "chat" }, { - "id": "xiaomi/mimo-v2.5-pro", - "name": "MiMo V2.5 Pro", - "display_name": "MiMo V2.5 Pro", + "id": "deepseek.v3-v1:0", + "name": "DeepSeek-V3.1", + "display_name": "DeepSeek-V3.1", "modalities": { "input": [ "text" @@ -245940,8 +245315,8 @@ ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 163840, + "output": 81920 }, "temperature": true, "tool_call": true, @@ -245951,74 +245326,92 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "none", - "high" - ] + "supported": true, + "interleaved": true } }, "attachment": false, "open_weights": true, - "knowledge": "2024-12", - "release_date": "2026-04-22", - "last_updated": "2026-04-22", + "release_date": "2025-08-21", + "last_updated": "2025-09-18", "type": "chat" }, { - "id": "xiaomi/mimo-v2.6-flash-uncensored", - "name": "MiMo V2.6 Flash Uncensored", - "display_name": "MiMo V2.6 Flash Uncensored", + "id": "us.anthropic.claude-sonnet-4-6", + "name": "Claude Sonnet 4.6 (US)", + "display_name": "Claude Sonnet 4.6 (US)", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 1000000, + "output": 128000 }, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": false, + "mode": "mixed", + "budget": { + "min": 1024, + "unit": "tokens" + }, + "effort": "high", "effort_options": [ - "none", "low", "medium", - "high" + "high", + "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Anthropic recommends adaptive thinking with effort for Claude 4.6; budget_tokens remains a deprecated compatibility path.", + "Anthropic API defaults effort to high; lower effort levels should be chosen per workload." ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-09-25", - "last_updated": "2026-09-25", + "open_weights": false, + "knowledge": "2025-08-31", + "release_date": "2026-02-17", + "last_updated": "2026-03-13", "type": "chat" }, { - "id": "xiaomi/mimo-v2.6-flash-abliterated", - "name": "MiMo V2.6 Flash Abliterated", - "display_name": "MiMo V2.6 Flash Abliterated", + "id": "moonshot.kimi-k2-thinking", + "name": "Kimi K2 Thinking", + "display_name": "Kimi K2 Thinking", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 262144, + "output": 16000 }, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -246026,117 +245419,126 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "none", - "low", - "medium", - "high" - ] + "supported": true, + "interleaved": true } }, - "attachment": true, + "attachment": false, "open_weights": true, - "release_date": "2026-09-25", - "last_updated": "2026-09-25", + "knowledge": "2024-08", + "release_date": "2025-11-06", + "last_updated": "2025-12-02", "type": "chat" }, { - "id": "xiaomi/mimo-v2.5-pro:thinking", - "name": "MiMo V2.5 Pro Thinking", - "display_name": "MiMo V2.5 Pro Thinking", + "id": "google.gemma-3-12b-it", + "name": "Gemma 3 12B IT", + "display_name": "Gemma 3 12B IT", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 131072, + "output": 8192 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "none", - "high" - ] - } + "supported": false }, - "attachment": false, + "attachment": true, "open_weights": true, - "knowledge": "2024-12", - "release_date": "2026-04-22", - "last_updated": "2026-04-22", + "knowledge": "2024-08", + "release_date": "2025-03-12", + "last_updated": "2025-03-12", "type": "chat" }, { - "id": "VongolaChouko/Starcannon-Unleashed-12B-v1.0", - "name": "Mistral Nemo Starcannon 12b v1", - "display_name": "Mistral Nemo Starcannon 12b v1", + "id": "amazon.nova-pro-v1:0", + "name": "Nova Pro", + "display_name": "Nova Pro", "modalities": { "input": [ - "text" + "text", + "image", + "video", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 16384, - "output": 8192 + "context": 300000, + "output": 10000 }, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { "supported": false }, - "attachment": false, - "open_weights": true, - "release_date": "2024-01-01", - "last_updated": "2024-07-01", + "attachment": true, + "open_weights": false, + "knowledge": "2024-10", + "release_date": "2024-12-03", + "last_updated": "2024-12-03", "type": "chat" }, { - "id": "aisingapore/gemma-sea-lion-v4-27b-it", - "name": "Gemma SEA-LION v4 27B", - "display_name": "Gemma SEA-LION v4 27B", + "id": "jp.amazon.nova-2-lite-v1:0", + "name": "Nova 2 Lite (JP)", + "display_name": "Nova 2 Lite (JP)", "modalities": { "input": [ - "text" + "text", + "image", + "video", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 128000 + "context": 1000000, + "output": 65535 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, - "open_weights": true, - "release_date": "2025-09-23", - "last_updated": "2025-09-23", + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "low", + "medium", + "high" + ] + } + }, + "attachment": true, + "open_weights": false, + "knowledge": "2025-10", + "release_date": "2025-12-02", + "last_updated": "2025-12-01", "type": "chat" }, { - "id": "aisingapore/qwen-sea-lion-v4-32b-it", - "name": "Qwen SEA-LION v4 32B", - "display_name": "Qwen SEA-LION v4 32B", + "id": "google.gemma-3-4b-it", + "name": "Gemma 3 4B IT", + "display_name": "Gemma 3 4B IT", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" @@ -246144,25 +245546,29 @@ }, "limit": { "context": 131072, - "output": 131072 + "output": 4096 }, - "tool_call": true, + "temperature": true, + "tool_call": false, "reasoning": { "supported": false }, - "attachment": false, + "attachment": true, "open_weights": true, - "release_date": "2026-10-15", - "last_updated": "2026-10-15", + "knowledge": "2024-08", + "release_date": "2025-03-12", + "last_updated": "2025-03-12", "type": "chat" }, { - "id": "nvidia/nemotron-3.5-lightning:thinking", - "name": "Nvidia Nemotron 3.5 Lightning Thinking", - "display_name": "Nvidia Nemotron 3.5 Lightning Thinking", + "id": "us.anthropic.claude-opus-4-6-v1", + "name": "Claude Opus 4.6 (US)", + "display_name": "Claude Opus 4.6 (US)", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" @@ -246170,65 +245576,100 @@ }, "limit": { "context": 1000000, - "output": 65536 + "output": 128000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": false, + "mode": "mixed", + "budget": { + "min": 1024, + "unit": "tokens" + }, + "effort": "high", "effort_options": [ - "none", - "high" + "low", + "medium", + "high", + "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Anthropic recommends adaptive thinking with effort for Claude 4.6; budget_tokens remains a deprecated compatibility path.", + "Anthropic API defaults effort to high; lower effort levels should be chosen per workload." ] } }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-09", - "release_date": "2026-08-11", - "last_updated": "2026-08-11", + "attachment": true, + "open_weights": false, + "knowledge": "2025-05-31", + "release_date": "2026-02-05", + "last_updated": "2026-03-13", "type": "chat" }, { - "id": "nvidia/nemotron-3-ultra-550b-a55b:thinking", - "name": "Nvidia Nemotron 3 Ultra 550B Thinking", - "display_name": "Nvidia Nemotron 3 Ultra 550B Thinking", + "id": "global.openai.gpt-6-astra", + "name": "GPT-6 Astra (Global)", + "display_name": "GPT-6 Astra (Global)", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 65536 + "context": 1050000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, "default": true }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-09", - "release_date": "2026-06-04", - "last_updated": "2026-06-04", + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" + ] + } + }, + "attachment": true, + "open_weights": false, + "knowledge": "2026-04-30", + "release_date": "2026-09-04", + "last_updated": "2026-09-04", "type": "chat" }, { - "id": "nvidia/nemotron-3.5-content-safety", - "name": "Nemotron 3.5 Content Safety", - "display_name": "Nemotron 3.5 Content Safety", + "id": "google.gemma-4-e2b", + "name": "Gemma 4 E2B IT", + "display_name": "Gemma 4 E2B IT", "modalities": { "input": [ - "text" + "text", + "image", + "audio", + "video" ], "output": [ "text" @@ -246236,40 +245677,44 @@ }, "limit": { "context": 131072, - "output": 32768 + "output": 8192 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { "supported": true, "default": true }, "extra_capabilities": { "reasoning": { - "supported": true + "effort_options": [ + "none", + "high" + ] } }, - "attachment": false, + "attachment": true, "open_weights": true, - "release_date": "2026-06-02", - "last_updated": "2026-06-02", + "release_date": "2026-04-02", + "last_updated": "2026-04-02", "type": "chat" }, { - "id": "nvidia/nemotron-3-ultra-550b-a55b", - "name": "Nvidia Nemotron 3 Ultra 550B", - "display_name": "Nvidia Nemotron 3 Ultra 550B", + "id": "xai.grok-4.6", + "name": "Grok 4.6", + "display_name": "Grok 4.6", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 512288, - "output": 65536 + "context": 500000, + "output": 500000 }, "temperature": true, "tool_call": true, @@ -246279,23 +245724,31 @@ }, "extra_capabilities": { "reasoning": { - "supported": true + "effort_options": [ + "low", + "medium", + "high", + "xhigh" + ] } }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-09", - "release_date": "2026-06-04", - "last_updated": "2026-06-04", + "attachment": true, + "open_weights": false, + "knowledge": "2026-02-01", + "release_date": "2026-08-12", + "last_updated": "2026-08-18", "type": "chat" }, { - "id": "nvidia/nemotron-3.5-lightning", - "name": "Nvidia Nemotron 3.5 Lightning", - "display_name": "Nvidia Nemotron 3.5 Lightning", + "id": "us.amazon.nova-2-lite-v1:0", + "name": "Nova 2 Lite (US)", + "display_name": "Nova 2 Lite (US)", "modalities": { "input": [ - "text" + "text", + "image", + "video", + "pdf" ], "output": [ "text" @@ -246303,7 +245756,7 @@ }, "limit": { "context": 1000000, - "output": 65536 + "output": 65535 }, "temperature": true, "tool_call": true, @@ -246313,24 +245766,24 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, "effort_options": [ - "none", + "low", + "medium", "high" ] } }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-09", - "release_date": "2026-08-11", - "last_updated": "2026-08-11", + "attachment": true, + "open_weights": false, + "knowledge": "2025-10", + "release_date": "2025-12-02", + "last_updated": "2025-12-01", "type": "chat" }, { - "id": "nvidia/nemotron-3-super-120b-a12b", - "name": "Nvidia Nemotron 3 Super 120B", - "display_name": "Nvidia Nemotron 3 Super 120B", + "id": "us.meta.llama3-1-70b-instruct-v1:0", + "name": "Llama 3.1 70B Instruct (US)", + "display_name": "Llama 3.1 70B Instruct (US)", "modalities": { "input": [ "text" @@ -246340,123 +245793,174 @@ ] }, "limit": { - "context": 262144, - "output": 16384 + "context": 128000, + "output": 4096 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": true, - "default": true + "supported": false }, "attachment": false, "open_weights": true, - "knowledge": "2025-06", - "release_date": "2026-03-11", - "last_updated": "2026-03-11", + "knowledge": "2023-12", + "release_date": "2024-07-23", + "last_updated": "2024-07-23", "type": "chat" }, { - "id": "nvidia/nemotron-3-nano-30b-a3b", - "name": "Nvidia Nemotron 3 Nano 30B", - "display_name": "Nvidia Nemotron 3 Nano 30B", + "id": "anthropic.claude-opus-5", + "name": "Claude Opus 5", + "display_name": "Claude Opus 5", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 235929 + "context": 1000000, + "output": 128000 }, - "temperature": true, - "tool_call": false, + "temperature": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-06-25", - "release_date": "2025-12-15", - "last_updated": "2025-12-15", + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" + ] + } + }, + "attachment": true, + "open_weights": false, + "knowledge": "2026-05", + "release_date": "2026-07-24", + "last_updated": "2026-07-24", "type": "chat" }, { - "id": "nvidia/Llama-3.1-Nemotron-70B-Instruct-HF", - "name": "Nvidia Nemotron 70b", - "display_name": "Nvidia Nemotron 70b", + "id": "global.anthropic.claude-haiku-4-5-20251001-v1:0", + "name": "Claude Haiku 4.5 (Global)", + "display_name": "Claude Haiku 4.5 (Global)", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 16384, - "output": 8192 + "context": 200000, + "output": 64000 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": false }, - "attachment": false, - "open_weights": true, - "release_date": "2025-04-15", - "last_updated": "2025-04-15", + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": false, + "mode": "budget", + "budget": { + "min": 1024, + "unit": "tokens" + }, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", + "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." + ] + } + }, + "attachment": true, + "open_weights": false, + "knowledge": "2025-02-28", + "release_date": "2025-10-15", + "last_updated": "2025-10-15", "type": "chat" }, { - "id": "nvidia/Llama-3.3-Nemotron-Super-49B-v1", - "name": "Nvidia Nemotron Super 49B", - "display_name": "Nvidia Nemotron Super 49B", + "id": "global.xai.grok-4.6", + "name": "Grok 4.6 (Global)", + "display_name": "Grok 4.6 (Global)", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 16384 + "context": 500000, + "output": 500000 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, - "open_weights": true, - "release_date": "2025-03-18", - "last_updated": "2025-03-18", + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "low", + "medium", + "high", + "xhigh" + ] + } + }, + "attachment": true, + "open_weights": false, + "knowledge": "2026-02-01", + "release_date": "2026-08-12", + "last_updated": "2026-08-18", "type": "chat" }, { - "id": "nvidia/nemotron-3.5-super-vl", - "name": "Nvidia Nemotron 3.5 Super VL", - "display_name": "Nvidia Nemotron 3.5 Super VL", + "id": "openai.gpt-6.1-terra", + "name": "GPT-6.1 Terra", + "display_name": "GPT-6.1 Terra", "modalities": { "input": [ "text", "image", - "video" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 16384 + "context": 1050000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -246466,50 +245970,82 @@ "reasoning": { "effort_options": [ "none", + "low", "medium", - "high" + "high", + "xhigh", + "max" ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-10-15", - "last_updated": "2026-10-15", + "open_weights": false, + "knowledge": "2026-06-01", + "release_date": "2026-10-26", + "last_updated": "2026-10-26", "type": "chat" }, { - "id": "nvidia/nemotron-3-super-120b-a12b:thinking", - "name": "Nvidia Nemotron 3 Super 120B Thinking", - "display_name": "Nvidia Nemotron 3 Super 120B Thinking", + "id": "jp.anthropic.claude-opus-4-7", + "name": "Claude Opus 4.7 (JP)", + "display_name": "Claude Opus 4.7 (JP)", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 16384 + "context": 1000000, + "output": 128000 }, - "temperature": true, - "tool_call": false, + "temperature": false, + "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-06", - "release_date": "2026-03-11", - "last_updated": "2026-03-11", + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": false, + "mode": "effort", + "effort": "high", + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "omitted", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Claude Opus 4.7 and newer Opus models require thinking.type = \"adaptive\" to enable thinking explicitly.", + "Manual budget_tokens requests return 400 on Claude Opus 4.7 and newer adaptive-only Opus models.", + "task_budget is separate from thinking control and should not be treated as a thinking budget." + ] + } + }, + "attachment": true, + "open_weights": false, + "knowledge": "2026-01-31", + "release_date": "2026-04-16", + "last_updated": "2026-04-16", "type": "chat" }, { - "id": "anthropic/claude-opus-4.5:thinking", - "name": "Claude 4.5 Opus Thinking", - "display_name": "Claude 4.5 Opus Thinking", + "id": "global.anthropic.claude-opus-4-6-v1", + "name": "Claude Opus 4.6 (Global)", + "display_name": "Claude Opus 4.6 (Global)", "modalities": { "input": [ "text", @@ -246521,19 +246057,19 @@ ] }, "limit": { - "context": 200000, - "output": 64000 + "context": 1000000, + "output": 128000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, + "default_enabled": false, "mode": "mixed", "budget": { "min": 1024, @@ -246543,7 +246079,8 @@ "effort_options": [ "low", "medium", - "high" + "high", + "max" ], "interleaved": true, "summaries": true, @@ -246552,22 +246089,64 @@ "thinking_blocks" ], "notes": [ - "Claude Opus 4.5 uses manual thinking.type = \"enabled\" with budget_tokens; effort can be used alongside the thinking budget.", - "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header." + "Anthropic recommends adaptive thinking with effort for Claude 4.6; budget_tokens remains a deprecated compatibility path.", + "Anthropic API defaults effort to high; lower effort levels should be chosen per workload." ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-05", - "release_date": "2025-11-24", - "last_updated": "2025-11-24", + "knowledge": "2025-05-31", + "release_date": "2026-02-05", + "last_updated": "2026-03-13", "type": "chat" }, { - "id": "anthropic/claude-fable-5.1", - "name": "Claude Fable 5.1", - "display_name": "Claude Fable 5.1", + "id": "openai.gpt-6-astra", + "name": "GPT-6 Astra", + "display_name": "GPT-6 Astra", + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1050000, + "output": 128000 + }, + "temperature": false, + "tool_call": true, + "reasoning": { + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" + ] + } + }, + "attachment": true, + "open_weights": false, + "knowledge": "2026-04-30", + "release_date": "2026-09-04", + "last_updated": "2026-09-04", + "type": "chat" + }, + { + "id": "au.anthropic.claude-opus-4-8", + "name": "Claude Opus 4.8 (AU)", + "display_name": "Claude Opus 4.8 (AU)", "modalities": { "input": [ "text", @@ -246586,12 +246165,12 @@ "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, + "default_enabled": false, "mode": "effort", "effort": "high", "effort_options": [ @@ -246608,23 +246187,23 @@ "thinking_blocks" ], "notes": [ - "Adaptive thinking is always on for Claude Fable 5 and Claude Mythos 5; thinking.type = \"disabled\" is rejected.", - "Manual budget_tokens requests return 400 on Claude Fable 5 and Claude Mythos 5.", - "thinking.display defaults to omitted; set display to summarized to receive readable thinking summaries." + "Claude Opus 4.7 and newer Opus models require thinking.type = \"adaptive\" to enable thinking explicitly.", + "Manual budget_tokens requests return 400 on Claude Opus 4.7 and newer adaptive-only Opus models.", + "task_budget is separate from thinking control and should not be treated as a thinking budget." ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-06", - "release_date": "2026-09-01", - "last_updated": "2026-09-01", + "knowledge": "2026-01", + "release_date": "2026-05-28", + "last_updated": "2026-05-28", "type": "chat" }, { - "id": "anthropic/claude-opus-4.1", - "name": "Claude 4.1 Opus", - "display_name": "Claude 4.1 Opus", + "id": "eu.anthropic.claude-sonnet-4-6", + "name": "Claude Sonnet 4.6 (EU)", + "display_name": "Claude Sonnet 4.6 (EU)", "modalities": { "input": [ "text", @@ -246636,8 +246215,8 @@ ] }, "limit": { - "context": 200000, - "output": 32000 + "context": 1000000, + "output": 128000 }, "temperature": true, "tool_call": true, @@ -246649,11 +246228,18 @@ "reasoning": { "supported": true, "default_enabled": false, - "mode": "budget", + "mode": "mixed", "budget": { "min": 1024, "unit": "tokens" }, + "effort": "high", + "effort_options": [ + "low", + "medium", + "high", + "max" + ], "interleaved": true, "summaries": true, "visibility": "summary", @@ -246661,22 +246247,50 @@ "thinking_blocks" ], "notes": [ - "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", - "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." + "Anthropic recommends adaptive thinking with effort for Claude 4.6; budget_tokens remains a deprecated compatibility path.", + "Anthropic API defaults effort to high; lower effort levels should be chosen per workload." ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-03-31", - "release_date": "2025-08-05", - "last_updated": "2025-08-05", + "knowledge": "2025-08-31", + "release_date": "2026-02-17", + "last_updated": "2026-03-13", "type": "chat" }, { - "id": "anthropic/claude-opus-5", - "name": "Claude Opus 5", - "display_name": "Claude Opus 5", + "id": "us.meta.llama3-3-70b-instruct-v1:0", + "name": "Llama 3.3 70B Instruct (US)", + "display_name": "Llama 3.3 70B Instruct (US)", + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 4096 + }, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": false + }, + "attachment": false, + "open_weights": true, + "knowledge": "2023-12", + "release_date": "2024-12-06", + "last_updated": "2024-12-06", + "type": "chat" + }, + { + "id": "anthropic.claude-sonnet-5", + "name": "Claude Sonnet 5", + "display_name": "Claude Sonnet 5", "modalities": { "input": [ "text", @@ -246699,7 +246313,6 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, "effort_options": [ "low", "medium", @@ -246711,15 +246324,43 @@ }, "attachment": true, "open_weights": false, - "knowledge": "2026-05", - "release_date": "2026-07-24", - "last_updated": "2026-07-24", + "knowledge": "2026-01-31", + "release_date": "2026-06-30", + "last_updated": "2026-06-30", "type": "chat" }, { - "id": "anthropic/claude-opus-4.1:thinking:32768", - "name": "Claude 4.1 Opus Thinking (32K)", - "display_name": "Claude 4.1 Opus Thinking (32K)", + "id": "mistral.voxtral-small-24b-2507", + "name": "Voxtral Small 24B 2507", + "display_name": "Voxtral Small 24B 2507", + "modalities": { + "input": [ + "text", + "audio" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 32768, + "output": 8192 + }, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": false + }, + "attachment": true, + "open_weights": true, + "release_date": "2025-07-15", + "last_updated": "2025-07-15", + "type": "chat" + }, + { + "id": "us.xai.grok-4.7", + "name": "Grok 4.7 (US)", + "display_name": "Grok 4.7 (US)", "modalities": { "input": [ "text", @@ -246731,8 +246372,8 @@ ] }, "limit": { - "context": 200000, - "output": 32000 + "context": 500000, + "output": 500000 }, "temperature": true, "tool_call": true, @@ -246742,36 +246383,25 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "budget", - "budget": { - "min": 1024, - "unit": "tokens" - }, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", - "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." + "effort_options": [ + "low", + "medium", + "high", + "xhigh" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-03-31", - "release_date": "2025-08-05", - "last_updated": "2025-08-05", + "knowledge": "2026-05", + "release_date": "2026-09-21", + "last_updated": "2026-09-28", "type": "chat" }, { - "id": "anthropic/claude-opus-4.8", - "name": "Claude Opus 4.8", - "display_name": "Claude Opus 4.8", + "id": "us.anthropic.claude-opus-4-7", + "name": "Claude Opus 4.7 (US)", + "display_name": "Claude Opus 4.7 (US)", "modalities": { "input": [ "text", @@ -246820,50 +246450,43 @@ }, "attachment": true, "open_weights": false, - "knowledge": "2026-01", - "release_date": "2026-05-28", - "last_updated": "2026-05-28", + "knowledge": "2026-01-31", + "release_date": "2026-04-16", + "last_updated": "2026-04-16", "type": "chat" }, { - "id": "anthropic/claude-sonnet-5", - "name": "Claude Sonnet 5", - "display_name": "Claude Sonnet 5", + "id": "writer.palmyra-x4-v1:0", + "name": "Palmyra X4", + "display_name": "Palmyra X4", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 122880, + "output": 8192 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { - "supported": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true - } + "supported": true, + "default": true }, - "attachment": true, + "attachment": false, "open_weights": false, - "knowledge": "2026-01-31", - "release_date": "2026-06-30", - "last_updated": "2026-06-30", + "release_date": "2024-10-09", + "last_updated": "2025-04-28", "type": "chat" }, { - "id": "anthropic/claude-opus-4.6:thinking:max", - "name": "Claude 4.6 Opus Thinking Max", - "display_name": "Claude 4.6 Opus Thinking Max", + "id": "jp.anthropic.claude-opus-4-8", + "name": "Claude Opus 4.8 (JP)", + "display_name": "Claude Opus 4.8 (JP)", "modalities": { "input": [ "text", @@ -246878,51 +246501,49 @@ "context": 1000000, "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "mixed", - "budget": { - "min": 1024, - "unit": "tokens" - }, + "default_enabled": false, + "mode": "effort", "effort": "high", "effort_options": [ "low", "medium", "high", + "xhigh", "max" ], "interleaved": true, "summaries": true, - "visibility": "summary", + "visibility": "omitted", "continuation": [ "thinking_blocks" ], "notes": [ - "Anthropic recommends adaptive thinking with effort for Claude 4.6; budget_tokens remains a deprecated compatibility path.", - "Anthropic API defaults effort to high; lower effort levels should be chosen per workload." + "Claude Opus 4.7 and newer Opus models require thinking.type = \"adaptive\" to enable thinking explicitly.", + "Manual budget_tokens requests return 400 on Claude Opus 4.7 and newer adaptive-only Opus models.", + "task_budget is separate from thinking control and should not be treated as a thinking budget." ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-05-31", - "release_date": "2026-02-05", - "last_updated": "2026-03-13", + "knowledge": "2026-01", + "release_date": "2026-05-28", + "last_updated": "2026-05-28", "type": "chat" }, { - "id": "anthropic/claude-opus-4.6", - "name": "Claude 4.6 Opus", - "display_name": "Claude 4.6 Opus", + "id": "openai.gpt-5.5", + "name": "GPT-5.5", + "display_name": "GPT-5.5", "modalities": { "input": [ "text", @@ -246937,51 +246558,34 @@ "context": 1000000, "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "mixed", - "budget": { - "min": 1024, - "unit": "tokens" - }, - "effort": "high", "effort_options": [ + "none", "low", "medium", "high", - "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Anthropic recommends adaptive thinking with effort for Claude 4.6; budget_tokens remains a deprecated compatibility path.", - "Anthropic API defaults effort to high; lower effort levels should be chosen per workload." + "xhigh" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-05-31", - "release_date": "2026-02-05", - "last_updated": "2026-03-13", + "knowledge": "2025-12-01", + "release_date": "2026-04-23", + "last_updated": "2026-06-01", "type": "chat" }, { - "id": "anthropic/claude-sonnet-latest", - "name": "Claude Sonnet Latest", - "display_name": "Claude Sonnet Latest", + "id": "global.anthropic.claude-opus-4-8", + "name": "Claude Opus 4.8 (Global)", + "display_name": "Claude Opus 4.8 (Global)", "modalities": { "input": [ "text", @@ -246996,36 +246600,54 @@ "context": 1000000, "output": 128000 }, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": false, + "mode": "effort", + "effort": "high", "effort_options": [ "low", "medium", "high", "xhigh", "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "omitted", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Claude Opus 4.7 and newer Opus models require thinking.type = \"adaptive\" to enable thinking explicitly.", + "Manual budget_tokens requests return 400 on Claude Opus 4.7 and newer adaptive-only Opus models.", + "task_budget is separate from thinking control and should not be treated as a thinking budget." ] } }, "attachment": true, "open_weights": false, - "release_date": "2026-03-01", - "last_updated": "2026-03-01", + "knowledge": "2026-01", + "release_date": "2026-05-28", + "last_updated": "2026-05-28", "type": "chat" }, { - "id": "anthropic/claude-opus-4.5", - "name": "Claude 4.5 Opus", - "display_name": "Claude 4.5 Opus", + "id": "amazon.nova-lite-v1:0", + "name": "Nova Lite", + "display_name": "Nova Lite", "modalities": { "input": [ "text", "image", + "video", "pdf" ], "output": [ @@ -247033,93 +246655,54 @@ ] }, "limit": { - "context": 200000, - "output": 64000 + "context": 300000, + "output": 10000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": false - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "mixed", - "budget": { - "min": 1024, - "unit": "tokens" - }, - "effort": "high", - "effort_options": [ - "low", - "medium", - "high" - ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Claude Opus 4.5 uses manual thinking.type = \"enabled\" with budget_tokens; effort can be used alongside the thinking budget.", - "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header." - ] - } + "supported": false }, "attachment": true, "open_weights": false, - "knowledge": "2025-05", - "release_date": "2025-11-24", - "last_updated": "2025-11-24", + "knowledge": "2024-10", + "release_date": "2024-12-03", + "last_updated": "2024-12-03", "type": "chat" }, { - "id": "anthropic/claude-opus-latest", - "name": "Claude Opus Latest", - "display_name": "Claude Opus Latest", + "id": "google.gemma-3-27b-it", + "name": "Gemma 3 27B IT", + "display_name": "Gemma 3 27B IT", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 131072, + "output": 8192 }, - "tool_call": true, + "temperature": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" - ] - } + "supported": false }, "attachment": true, - "open_weights": false, - "release_date": "2026-03-29", - "last_updated": "2026-03-29", + "open_weights": true, + "knowledge": "2024-08", + "release_date": "2025-03-12", + "last_updated": "2025-03-12", "type": "chat" }, { - "id": "anthropic/claude-haiku-latest", - "name": "Claude Haiku Latest", - "display_name": "Claude Haiku Latest", + "id": "openai.gpt-5.6-terra", + "name": "GPT-5.6 Terra", + "display_name": "GPT-5.6 Terra", "modalities": { "input": [ "text", @@ -247131,9 +246714,10 @@ ] }, "limit": { - "context": 1000000, + "context": 1050000, "output": 128000 }, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -247142,6 +246726,7 @@ "extra_capabilities": { "reasoning": { "effort_options": [ + "none", "low", "medium", "high", @@ -247152,14 +246737,15 @@ }, "attachment": true, "open_weights": false, - "release_date": "2026-03-29", - "last_updated": "2026-03-29", + "knowledge": "2026-02-16", + "release_date": "2026-07-09", + "last_updated": "2026-07-09", "type": "chat" }, { - "id": "anthropic/claude-sonnet-4.6", - "name": "Claude Sonnet 4.6", - "display_name": "Claude Sonnet 4.6", + "id": "in.anthropic.claude-sonnet-5", + "name": "Claude Sonnet 5 (India)", + "display_name": "Claude Sonnet 5 (India)", "modalities": { "input": [ "text", @@ -247174,51 +246760,34 @@ "context": 1000000, "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "mixed", - "budget": { - "min": 1024, - "unit": "tokens" - }, - "effort": "high", "effort_options": [ "low", "medium", "high", + "xhigh", "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Anthropic recommends adaptive thinking with effort for Claude 4.6; budget_tokens remains a deprecated compatibility path.", - "Anthropic API defaults effort to high; lower effort levels should be chosen per workload." ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-02-17", - "last_updated": "2026-03-13", + "knowledge": "2026-01-31", + "release_date": "2026-06-30", + "last_updated": "2026-06-30", "type": "chat" }, { - "id": "anthropic/claude-opus-4.1:thinking:1024", - "name": "Claude 4.1 Opus Thinking (1K)", - "display_name": "Claude 4.1 Opus Thinking (1K)", + "id": "us.anthropic.claude-haiku-4-5-20251001-v1:0", + "name": "Claude Haiku 4.5 (US)", + "display_name": "Claude Haiku 4.5 (US)", "modalities": { "input": [ "text", @@ -247231,18 +246800,18 @@ }, "limit": { "context": 200000, - "output": 32000 + "output": 64000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, + "default_enabled": false, "mode": "budget", "budget": { "min": 1024, @@ -247262,15 +246831,15 @@ }, "attachment": true, "open_weights": false, - "knowledge": "2025-03-31", - "release_date": "2025-08-05", - "last_updated": "2025-08-05", + "knowledge": "2025-02-28", + "release_date": "2025-10-15", + "last_updated": "2025-10-15", "type": "chat" }, { - "id": "anthropic/claude-haiku-4.5", - "name": "Claude Haiku 4.5", - "display_name": "Claude Haiku 4.5", + "id": "eu.anthropic.claude-opus-5-5", + "name": "Claude Opus 5.5 (EU)", + "display_name": "Claude Opus 5.5 (EU)", "modalities": { "input": [ "text", @@ -247282,61 +246851,51 @@ ] }, "limit": { - "context": 200000, - "output": 64000 + "context": 1000000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "budget", - "budget": { - "min": 1024, - "unit": "tokens" - }, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", - "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-02-28", - "release_date": "2025-10-15", - "last_updated": "2025-10-15", + "knowledge": "2026-06", + "release_date": "2026-09-22", + "last_updated": "2026-09-22", "type": "chat" }, { - "id": "anthropic/claude-fable-latest", - "name": "Claude Fable Latest", - "display_name": "Claude Fable Latest", + "id": "global.openai.gpt-5.6-terra", + "name": "GPT-5.6 Terra (Global)", + "display_name": "GPT-5.6 Terra (Global)", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, + "context": 1050000, "output": 128000 }, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -247344,40 +246903,27 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "high", "effort_options": [ + "none", "low", "medium", "high", "xhigh", "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "omitted", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Adaptive thinking is always on for Claude Fable 5 and Claude Mythos 5; thinking.type = \"disabled\" is rejected.", - "Manual budget_tokens requests return 400 on Claude Fable 5 and Claude Mythos 5.", - "thinking.display defaults to omitted; set display to summarized to receive readable thinking summaries." ] } }, "attachment": true, "open_weights": false, - "release_date": "2026-06-09", - "last_updated": "2026-06-09", + "knowledge": "2026-02-16", + "release_date": "2026-07-09", + "last_updated": "2026-07-09", "type": "chat" }, { - "id": "anthropic/claude-opus-4.1:thinking:8192", - "name": "Claude 4.1 Opus Thinking (8K)", - "display_name": "Claude 4.1 Opus Thinking (8K)", + "id": "global.anthropic.claude-sonnet-4-6", + "name": "Claude Sonnet 4.6 (Global)", + "display_name": "Claude Sonnet 4.6 (Global)", "modalities": { "input": [ "text", @@ -247389,24 +246935,31 @@ ] }, "limit": { - "context": 200000, - "output": 32000 + "context": 1000000, + "output": 128000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "budget", + "default_enabled": false, + "mode": "mixed", "budget": { "min": 1024, "unit": "tokens" }, + "effort": "high", + "effort_options": [ + "low", + "medium", + "high", + "max" + ], "interleaved": true, "summaries": true, "visibility": "summary", @@ -247414,27 +246967,26 @@ "thinking_blocks" ], "notes": [ - "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", - "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." + "Anthropic recommends adaptive thinking with effort for Claude 4.6; budget_tokens remains a deprecated compatibility path.", + "Anthropic API defaults effort to high; lower effort levels should be chosen per workload." ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-03-31", - "release_date": "2025-08-05", - "last_updated": "2025-08-05", + "knowledge": "2025-08-31", + "release_date": "2026-02-17", + "last_updated": "2026-03-13", "type": "chat" }, { - "id": "anthropic/claude-sonnet-5.5:thinking", - "name": "Claude Sonnet 5.5 Thinking", - "display_name": "Claude Sonnet 5.5 Thinking", + "id": "us.meta.llama4-maverick-17b-instruct-v1:0", + "name": "Llama 4 Maverick 17B Instruct (US)", + "display_name": "Llama 4 Maverick 17B Instruct (US)", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" @@ -247442,9 +246994,37 @@ }, "limit": { "context": 1000000, - "output": 128000 + "output": 8192 }, - "temperature": false, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": false + }, + "attachment": true, + "open_weights": true, + "knowledge": "2024-08", + "release_date": "2025-04-05", + "last_updated": "2025-04-05", + "type": "chat" + }, + { + "id": "openai.gpt-oss-120b", + "name": "gpt-oss-120b", + "display_name": "gpt-oss-120b", + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "output": 131072 + }, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -247452,28 +247032,24 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, "effort_options": [ "low", "medium", - "high", - "xhigh", - "max" + "high" ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-06", - "release_date": "2026-09-28", - "last_updated": "2026-09-28", + "attachment": false, + "open_weights": true, + "knowledge": "2024-06", + "release_date": "2025-08-05", + "last_updated": "2025-08-05", "type": "chat" }, { - "id": "anthropic/claude-opus-4.6:thinking:medium", - "name": "Claude 4.6 Opus Thinking Medium", - "display_name": "Claude 4.6 Opus Thinking Medium", + "id": "global.anthropic.claude-opus-4-5-20251101-v1:0", + "name": "Claude Opus 4.5 (Global)", + "display_name": "Claude Opus 4.5 (Global)", "modalities": { "input": [ "text", @@ -247485,19 +247061,19 @@ ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 200000, + "output": 64000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, + "default_enabled": false, "mode": "mixed", "budget": { "min": 1024, @@ -247507,8 +247083,7 @@ "effort_options": [ "low", "medium", - "high", - "max" + "high" ], "interleaved": true, "summaries": true, @@ -247517,22 +247092,22 @@ "thinking_blocks" ], "notes": [ - "Anthropic recommends adaptive thinking with effort for Claude 4.6; budget_tokens remains a deprecated compatibility path.", - "Anthropic API defaults effort to high; lower effort levels should be chosen per workload." + "Claude Opus 4.5 uses manual thinking.type = \"enabled\" with budget_tokens; effort can be used alongside the thinking budget.", + "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header." ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-05-31", - "release_date": "2026-02-05", - "last_updated": "2026-03-13", + "knowledge": "2025-05", + "release_date": "2025-11-24", + "last_updated": "2025-11-24", "type": "chat" }, { - "id": "anthropic/claude-opus-4.6:thinking:low", - "name": "Claude 4.6 Opus Thinking Low", - "display_name": "Claude 4.6 Opus Thinking Low", + "id": "eu.anthropic.claude-sonnet-4-5-20250929-v1:0", + "name": "Claude Sonnet 4.5 (EU)", + "display_name": "Claude Sonnet 4.5 (EU)", "modalities": { "input": [ "text", @@ -247544,31 +247119,24 @@ ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 200000, + "output": 64000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "mixed", + "default_enabled": false, + "mode": "budget", "budget": { "min": 1024, "unit": "tokens" }, - "effort": "high", - "effort_options": [ - "low", - "medium", - "high", - "max" - ], "interleaved": true, "summaries": true, "visibility": "summary", @@ -247576,26 +247144,27 @@ "thinking_blocks" ], "notes": [ - "Anthropic recommends adaptive thinking with effort for Claude 4.6; budget_tokens remains a deprecated compatibility path.", - "Anthropic API defaults effort to high; lower effort levels should be chosen per workload." + "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", + "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-05-31", - "release_date": "2026-02-05", - "last_updated": "2026-03-13", + "knowledge": "2025-07-31", + "release_date": "2025-09-29", + "last_updated": "2025-09-29", "type": "chat" }, { - "id": "anthropic/claude-opus-4.6:thinking", - "name": "Claude 4.6 Opus Thinking", - "display_name": "Claude 4.6 Opus Thinking", + "id": "eu.amazon.nova-2-lite-v1:0", + "name": "Nova 2 Lite (EU)", + "display_name": "Nova 2 Lite (EU)", "modalities": { "input": [ "text", "image", + "video", "pdf" ], "output": [ @@ -247604,7 +247173,7 @@ }, "limit": { "context": 1000000, - "output": 128000 + "output": 65535 }, "temperature": true, "tool_call": true, @@ -247614,112 +247183,63 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "mixed", - "budget": { - "min": 1024, - "unit": "tokens" - }, - "effort": "high", "effort_options": [ "low", "medium", - "high", - "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Anthropic recommends adaptive thinking with effort for Claude 4.6; budget_tokens remains a deprecated compatibility path.", - "Anthropic API defaults effort to high; lower effort levels should be chosen per workload." + "high" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-05-31", - "release_date": "2026-02-05", - "last_updated": "2026-03-13", + "knowledge": "2025-10", + "release_date": "2025-12-02", + "last_updated": "2025-12-01", "type": "chat" }, { - "id": "anthropic/claude-opus-4.7:thinking", - "name": "Claude 4.7 Opus Thinking", - "display_name": "Claude 4.7 Opus Thinking", + "id": "us.writer.palmyra-x4-v1:0", + "name": "Palmyra X4 (US)", + "display_name": "Palmyra X4 (US)", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 122880, + "output": 8192 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "high", - "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "omitted", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Claude Opus 4.7 and newer Opus models require thinking.type = \"adaptive\" to enable thinking explicitly.", - "Manual budget_tokens requests return 400 on Claude Opus 4.7 and newer adaptive-only Opus models.", - "task_budget is separate from thinking control and should not be treated as a thinking budget." - ] - } - }, - "attachment": true, + "attachment": false, "open_weights": false, - "knowledge": "2026-01-31", - "release_date": "2026-04-16", - "last_updated": "2026-04-16", + "release_date": "2024-10-09", + "last_updated": "2025-04-28", "type": "chat" }, { - "id": "anthropic/claude-opus-4.8:thinking", - "name": "Claude Opus 4.8 Thinking", - "display_name": "Claude Opus 4.8 Thinking", + "id": "global.openai.gpt-6-luna", + "name": "GPT-6 Luna (Global)", + "display_name": "GPT-6 Luna (Global)", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, + "context": 1050000, "output": 128000 }, "temperature": false, @@ -247730,41 +247250,27 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "high", "effort_options": [ + "none", "low", "medium", "high", "xhigh", "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "omitted", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Claude Opus 4.7 and newer Opus models require thinking.type = \"adaptive\" to enable thinking explicitly.", - "Manual budget_tokens requests return 400 on Claude Opus 4.7 and newer adaptive-only Opus models.", - "task_budget is separate from thinking control and should not be treated as a thinking budget." ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-01", - "release_date": "2026-05-28", - "last_updated": "2026-05-28", + "knowledge": "2026-05-18", + "release_date": "2026-09-22", + "last_updated": "2026-09-22", "type": "chat" }, { - "id": "anthropic/claude-opus-4.7", - "name": "Claude 4.7 Opus", - "display_name": "Claude 4.7 Opus", + "id": "eu.anthropic.claude-sonnet-5", + "name": "Claude Sonnet 5 (EU)", + "display_name": "Claude Sonnet 5 (EU)", "modalities": { "input": [ "text", @@ -247783,43 +247289,28 @@ "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "high", "effort_options": [ "low", "medium", "high", "xhigh", "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "omitted", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Claude Opus 4.7 and newer Opus models require thinking.type = \"adaptive\" to enable thinking explicitly.", - "Manual budget_tokens requests return 400 on Claude Opus 4.7 and newer adaptive-only Opus models.", - "task_budget is separate from thinking control and should not be treated as a thinking budget." ] } }, "attachment": true, "open_weights": false, "knowledge": "2026-01-31", - "release_date": "2026-04-16", - "last_updated": "2026-04-16", + "release_date": "2026-06-30", + "last_updated": "2026-06-30", "type": "chat" }, { - "id": "anthropic/claude-sonnet-5.5", + "id": "anthropic.claude-sonnet-5-5", "name": "Claude Sonnet 5.5", "display_name": "Claude Sonnet 5.5", "modalities": { @@ -247847,7 +247338,9 @@ "effort_options": [ "low", "medium", - "high" + "high", + "xhigh", + "max" ] } }, @@ -247859,9 +247352,9 @@ "type": "chat" }, { - "id": "anthropic/claude-sonnet-4.5:thinking", - "name": "Claude Sonnet 4.5 Thinking", - "display_name": "Claude Sonnet 4.5 Thinking", + "id": "jp.anthropic.claude-sonnet-4-5-20250929-v1:0", + "name": "Claude Sonnet 4.5 (JP)", + "display_name": "Claude Sonnet 4.5 (JP)", "modalities": { "input": [ "text", @@ -247880,12 +247373,12 @@ "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, + "default_enabled": false, "mode": "budget", "budget": { "min": 1024, @@ -247911,21 +247404,20 @@ "type": "chat" }, { - "id": "anthropic/claude-sonnet-5:thinking", - "name": "Claude Sonnet 5 Thinking", - "display_name": "Claude Sonnet 5 Thinking", + "id": "global.openai.gpt-5.6-sol", + "name": "GPT-5.6 Sol (Global)", + "display_name": "GPT-5.6 Sol (Global)", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, + "context": 1050000, "output": 128000 }, "temperature": false, @@ -247936,9 +247428,8 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, "effort_options": [ + "none", "low", "medium", "high", @@ -247949,15 +247440,15 @@ }, "attachment": true, "open_weights": false, - "knowledge": "2026-01-31", - "release_date": "2026-06-30", - "last_updated": "2026-06-30", + "knowledge": "2026-02-16", + "release_date": "2026-07-09", + "last_updated": "2026-07-09", "type": "chat" }, { - "id": "anthropic/claude-sonnet-4.6:thinking", - "name": "Claude Sonnet 4.6 Thinking", - "display_name": "Claude Sonnet 4.6 Thinking", + "id": "us.anthropic.claude-opus-4-1-20250805-v1:0", + "name": "Claude Opus 4.1 (US)", + "display_name": "Claude Opus 4.1 (US)", "modalities": { "input": [ "text", @@ -247969,31 +247460,24 @@ ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 200000, + "output": 32000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "mixed", + "default_enabled": false, + "mode": "budget", "budget": { "min": 1024, "unit": "tokens" }, - "effort": "high", - "effort_options": [ - "low", - "medium", - "high", - "max" - ], "interleaved": true, "summaries": true, "visibility": "summary", @@ -248001,22 +247485,22 @@ "thinking_blocks" ], "notes": [ - "Anthropic recommends adaptive thinking with effort for Claude 4.6; budget_tokens remains a deprecated compatibility path.", - "Anthropic API defaults effort to high; lower effort levels should be chosen per workload." + "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", + "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-02-17", - "last_updated": "2026-03-13", + "knowledge": "2025-03-31", + "release_date": "2025-08-05", + "last_updated": "2025-08-05", "type": "chat" }, { - "id": "anthropic/claude-fable-5", - "name": "Claude Fable 5", - "display_name": "Claude Fable 5", + "id": "au.anthropic.claude-opus-4-7", + "name": "Claude Opus 4.7 (AU)", + "display_name": "Claude Opus 4.7 (AU)", "modalities": { "input": [ "text", @@ -248035,12 +247519,12 @@ "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, + "default_enabled": false, "mode": "effort", "effort": "high", "effort_options": [ @@ -248057,23 +247541,23 @@ "thinking_blocks" ], "notes": [ - "Adaptive thinking is always on for Claude Fable 5 and Claude Mythos 5; thinking.type = \"disabled\" is rejected.", - "Manual budget_tokens requests return 400 on Claude Fable 5 and Claude Mythos 5.", - "thinking.display defaults to omitted; set display to summarized to receive readable thinking summaries." + "Claude Opus 4.7 and newer Opus models require thinking.type = \"adaptive\" to enable thinking explicitly.", + "Manual budget_tokens requests return 400 on Claude Opus 4.7 and newer adaptive-only Opus models.", + "task_budget is separate from thinking control and should not be treated as a thinking budget." ] } }, "attachment": true, "open_weights": false, "knowledge": "2026-01-31", - "release_date": "2026-06-09", - "last_updated": "2026-06-09", + "release_date": "2026-04-16", + "last_updated": "2026-04-16", "type": "chat" }, { - "id": "anthropic/claude-sonnet-4.5", - "name": "Claude Sonnet 4.5", - "display_name": "Claude Sonnet 4.5", + "id": "anthropic.claude-sonnet-4-6", + "name": "Claude Sonnet 4.6", + "display_name": "Claude Sonnet 4.6", "modalities": { "input": [ "text", @@ -248085,8 +247569,8 @@ ] }, "limit": { - "context": 200000, - "output": 64000 + "context": 1000000, + "output": 128000 }, "temperature": true, "tool_call": true, @@ -248098,115 +247582,18 @@ "reasoning": { "supported": true, "default_enabled": false, - "mode": "budget", - "budget": { - "min": 1024, - "unit": "tokens" - }, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", - "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." - ] - } - }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-07-31", - "release_date": "2025-09-29", - "last_updated": "2025-09-29", - "type": "chat" - }, - { - "id": "anthropic/claude-opus-4.1:thinking", - "name": "Claude 4.1 Opus Thinking", - "display_name": "Claude 4.1 Opus Thinking", - "modalities": { - "input": [ - "text", - "image", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 200000, - "output": 32000 - }, - "temperature": true, - "tool_call": true, - "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "budget", + "mode": "mixed", "budget": { "min": 1024, "unit": "tokens" }, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort": "high", + "effort_options": [ + "low", + "medium", + "high", + "max" ], - "notes": [ - "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", - "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." - ] - } - }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-03-31", - "release_date": "2025-08-05", - "last_updated": "2025-08-05", - "type": "chat" - }, - { - "id": "anthropic/claude-haiku-4.5:thinking", - "name": "Claude Haiku 4.5 Thinking", - "display_name": "Claude Haiku 4.5 Thinking", - "modalities": { - "input": [ - "text", - "image", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 200000, - "output": 64000 - }, - "temperature": true, - "tool_call": true, - "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "budget", - "budget": { - "min": 1024, - "unit": "tokens" - }, "interleaved": true, "summaries": true, "visibility": "summary", @@ -248214,26 +247601,27 @@ "thinking_blocks" ], "notes": [ - "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", - "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." + "Anthropic recommends adaptive thinking with effort for Claude 4.6; budget_tokens remains a deprecated compatibility path.", + "Anthropic API defaults effort to high; lower effort levels should be chosen per workload." ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-02-28", - "release_date": "2025-10-15", - "last_updated": "2025-10-15", + "knowledge": "2025-08-31", + "release_date": "2026-02-17", + "last_updated": "2026-03-13", "type": "chat" }, { - "id": "anthropic/claude-haiku-5.5", - "name": "Claude Haiku 5.5", - "display_name": "Claude Haiku 5.5", + "id": "us.amazon.nova-lite-v1:0", + "name": "Nova Lite (US)", + "display_name": "Nova Lite (US)", "modalities": { "input": [ "text", "image", + "video", "pdf" ], "output": [ @@ -248241,49 +247629,36 @@ ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 300000, + "output": 10000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" - ] - } + "supported": false }, "attachment": true, "open_weights": false, - "knowledge": "2026-06", - "release_date": "2026-10-07", - "last_updated": "2026-10-07", + "knowledge": "2024-10", + "release_date": "2024-12-03", + "last_updated": "2024-12-03", "type": "chat" }, { - "id": "anthropic/claude-opus-5.5", - "name": "Claude Opus 5.5", - "display_name": "Claude Opus 5.5", + "id": "us.openai.gpt-5.6-terra", + "name": "GPT-5.6 Terra (US)", + "display_name": "GPT-5.6 Terra (US)", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, + "context": 1050000, "output": 128000 }, "temperature": false, @@ -248295,6 +247670,7 @@ "extra_capabilities": { "reasoning": { "effort_options": [ + "none", "low", "medium", "high", @@ -248305,70 +247681,90 @@ }, "attachment": true, "open_weights": false, - "knowledge": "2026-06", - "release_date": "2026-09-22", - "last_updated": "2026-09-22", + "knowledge": "2026-02-16", + "release_date": "2026-07-09", + "last_updated": "2026-07-09", "type": "chat" }, { - "id": "soob3123/Veiled-Calla-12B", - "name": "Veiled Calla 12B", - "display_name": "Veiled Calla 12B", + "id": "meta.llama4-scout-17b-instruct-v1:0", + "name": "Llama 4 Scout 17B Instruct", + "display_name": "Llama 4 Scout 17B Instruct", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 32768, + "context": 10000000, "output": 8192 }, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { "supported": false }, - "attachment": false, + "attachment": true, "open_weights": true, - "release_date": "2025-04-13", - "last_updated": "2025-04-13", + "knowledge": "2024-08", + "release_date": "2025-04-05", + "last_updated": "2025-04-05", "type": "chat" }, { - "id": "soob3123/amoral-gemma3-27B-v2", - "name": "Amoral Gemma3 27B v2", - "display_name": "Amoral Gemma3 27B v2", + "id": "global.anthropic.claude-haiku-5-5", + "name": "Claude Haiku 5.5 (Global)", + "display_name": "Claude Haiku 5.5 (Global)", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 32768, - "output": 8192 + "context": 1000000, + "output": 128000 }, - "tool_call": false, + "temperature": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, - "open_weights": true, - "release_date": "2025-05-23", - "last_updated": "2025-05-23", + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" + ] + } + }, + "attachment": true, + "open_weights": false, + "knowledge": "2026-06", + "release_date": "2026-10-07", + "last_updated": "2026-10-07", "type": "chat" }, { - "id": "Steelskull/L3.3-Nevoria-R1-70b", - "name": "Steelskull Nevoria R1 70b", - "display_name": "Steelskull Nevoria R1 70b", + "id": "mistral.voxtral-mini-3b-2507", + "name": "Voxtral Mini 3B 2507", + "display_name": "Voxtral Mini 3B 2507", "modalities": { "input": [ - "text" + "text", + "audio" ], "output": [ "text" @@ -248376,22 +247772,23 @@ }, "limit": { "context": 32768, - "output": 16384 + "output": 4096 }, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { "supported": false }, - "attachment": false, + "attachment": true, "open_weights": true, - "release_date": "2024-01-01", - "last_updated": "2024-12-06", + "release_date": "2025-07-15", + "last_updated": "2025-07-15", "type": "chat" }, { - "id": "Steelskull/L3.3-MS-Nevoria-70b", - "name": "Steelskull Nevoria 70b", - "display_name": "Steelskull Nevoria 70b", + "id": "meta.llama3-1-8b-instruct-v1:0", + "name": "Llama 3.1 8B Instruct", + "display_name": "Llama 3.1 8B Instruct", "modalities": { "input": [ "text" @@ -248401,88 +247798,149 @@ ] }, "limit": { - "context": 32768, - "output": 16384 + "context": 128000, + "output": 4096 }, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { "supported": false }, "attachment": false, "open_weights": true, - "release_date": "2024-01-01", - "last_updated": "2024-12-06", + "knowledge": "2023-12", + "release_date": "2024-07-23", + "last_updated": "2024-07-23", "type": "chat" }, { - "id": "Steelskull/L3.3-Cu-Mai-R1-70b", - "name": "Llama 3.3 70B Cu Mai", - "display_name": "Llama 3.3 70B Cu Mai", + "id": "anthropic.claude-opus-4-1-20250805-v1:0", + "name": "Claude Opus 4.1", + "display_name": "Claude Opus 4.1", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 32768, - "output": 16384 + "context": 200000, + "output": 32000 }, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": false }, - "attachment": false, - "open_weights": true, - "release_date": "2024-01-01", - "last_updated": "2024-12-06", + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": false, + "mode": "budget", + "budget": { + "min": 1024, + "unit": "tokens" + }, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", + "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." + ] + } + }, + "attachment": true, + "open_weights": false, + "knowledge": "2025-03-31", + "release_date": "2025-08-05", + "last_updated": "2025-08-05", "type": "chat" }, { - "id": "Steelskull/L3.3-Electra-R1-70b", - "name": "Steelskull Electra R1 70b", - "display_name": "Steelskull Electra R1 70b", + "id": "us.anthropic.claude-fable-5", + "name": "Claude Fable 5 (US)", + "display_name": "Claude Fable 5 (US)", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 32768, - "output": 16384 + "context": 1000000, + "output": 128000 }, - "tool_call": false, + "temperature": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, - "open_weights": true, - "release_date": "2024-01-01", - "last_updated": "2024-12-06", + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "high", + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "omitted", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Adaptive thinking is always on for Claude Fable 5 and Claude Mythos 5; thinking.type = \"disabled\" is rejected.", + "Manual budget_tokens requests return 400 on Claude Fable 5 and Claude Mythos 5.", + "thinking.display defaults to omitted; set display to summarized to receive readable thinking summaries." + ] + } + }, + "attachment": true, + "open_weights": false, + "knowledge": "2026-01-31", + "release_date": "2026-06-09", + "last_updated": "2026-06-09", "type": "chat" }, { - "id": "particle-ai/particle-1", - "name": "Particle-1", - "display_name": "Particle-1", + "id": "us.anthropic.claude-sonnet-5-5", + "name": "Claude Sonnet 5.5 (US)", + "display_name": "Claude Sonnet 5.5 (US)", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 1000000, + "output": 128000 }, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -248491,8 +247949,6 @@ "extra_capabilities": { "reasoning": { "effort_options": [ - "none", - "minimal", "low", "medium", "high", @@ -248503,14 +247959,15 @@ }, "attachment": true, "open_weights": false, - "release_date": "2026-10-29", - "last_updated": "2026-10-29", + "knowledge": "2026-06", + "release_date": "2026-09-28", + "last_updated": "2026-09-28", "type": "chat" }, { - "id": "TheDrummer/Artemis-v1.1", - "name": "TheDrummer/Artemis v1.1", - "display_name": "TheDrummer/Artemis v1.1", + "id": "moonshotai.kimi-k2.5", + "name": "Kimi K2.5", + "display_name": "Kimi K2.5", "modalities": { "input": [ "text", @@ -248522,78 +247979,129 @@ }, "limit": { "context": 262144, - "output": 32768 + "output": 16384 }, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, "default": true }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true + } + }, "attachment": true, "open_weights": true, - "release_date": "2026-09-06", - "last_updated": "2026-09-06", + "knowledge": "2025-01", + "release_date": "2026-01-27", + "last_updated": "2026-02-06", "type": "chat" }, { - "id": "TheDrummer/Anubis-70B-v1", - "name": "Anubis 70B v1", - "display_name": "Anubis 70B v1", + "id": "us.openai.gpt-5.6-sol", + "name": "GPT-5.6 Sol (US)", + "display_name": "GPT-5.6 Sol (US)", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 65536, - "output": 16384 + "context": 1050000, + "output": 128000 }, - "tool_call": false, + "temperature": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, - "open_weights": true, - "release_date": "2024-01-01", - "last_updated": "2024-01-01", + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "none", + "low", + "medium", + "high", + "xhigh", + "max" + ] + } + }, + "attachment": true, + "open_weights": false, + "knowledge": "2026-02-16", + "release_date": "2026-07-09", + "last_updated": "2026-07-09", "type": "chat" }, { - "id": "TheDrummer/Rocinante-12B-v1.1", - "name": "Rocinante 12b", - "display_name": "Rocinante 12b", + "id": "global.anthropic.claude-sonnet-4-5-20250929-v1:0", + "name": "Claude Sonnet 4.5 (Global)", + "display_name": "Claude Sonnet 4.5 (Global)", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 16384, - "output": 8192 + "context": 200000, + "output": 64000 }, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": false }, - "attachment": false, - "open_weights": true, - "release_date": "2024-01-01", - "last_updated": "2024-01-01", + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": false, + "mode": "budget", + "budget": { + "min": 1024, + "unit": "tokens" + }, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", + "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." + ] + } + }, + "attachment": true, + "open_weights": false, + "knowledge": "2025-07-31", + "release_date": "2025-09-29", + "last_updated": "2025-09-29", "type": "chat" }, { - "id": "TheDrummer/UnslopNemo-12B-v4.1", - "name": "UnslopNemo 12b v4", - "display_name": "UnslopNemo 12b v4", + "id": "global.anthropic.claude-opus-5-5", + "name": "Claude Opus 5.5 (Global)", + "display_name": "Claude Opus 5.5 (Global)", "modalities": { "input": [ "text", + "image", "pdf" ], "output": [ @@ -248601,77 +248109,122 @@ ] }, "limit": { - "context": 8192, - "output": 26214 + "context": 1000000, + "output": 128000 }, - "tool_call": false, + "temperature": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" + ] + } }, "attachment": true, - "open_weights": true, - "release_date": "2024-01-01", - "last_updated": "2024-01-01", + "open_weights": false, + "knowledge": "2026-06", + "release_date": "2026-09-22", + "last_updated": "2026-09-22", "type": "chat" }, { - "id": "TheDrummer/Cydonia-24B-v4.1", - "name": "The Drummer Cydonia 24B v4.1", - "display_name": "The Drummer Cydonia 24B v4.1", + "id": "mistral.ministral-3-8b-instruct", + "name": "Ministral 3 8B", + "display_name": "Ministral 3 8B", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 117964 + "context": 128000, + "output": 4096 }, "temperature": true, "tool_call": true, "reasoning": { "supported": false }, - "attachment": false, + "attachment": true, "open_weights": true, - "knowledge": "2024-04-30", - "release_date": "2025-09-27", - "last_updated": "2025-09-27", + "release_date": "2025-12-02", + "last_updated": "2025-12-02", "type": "chat" }, { - "id": "TheDrummer/Magidonia-24B-v4.3", - "name": "The Drummer Magidonia 24B v4.3", - "display_name": "The Drummer Magidonia 24B v4.3", + "id": "global.anthropic.claude-opus-4-7", + "name": "Claude Opus 4.7 (Global)", + "display_name": "Claude Opus 4.7 (Global)", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 32768, - "output": 32768 + "context": 1000000, + "output": 128000 }, - "tool_call": false, + "temperature": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": false }, - "attachment": false, - "open_weights": true, - "release_date": "2025-12-25", - "last_updated": "2025-12-25", + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": false, + "mode": "effort", + "effort": "high", + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "omitted", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Claude Opus 4.7 and newer Opus models require thinking.type = \"adaptive\" to enable thinking explicitly.", + "Manual budget_tokens requests return 400 on Claude Opus 4.7 and newer adaptive-only Opus models.", + "task_budget is separate from thinking control and should not be treated as a thinking budget." + ] + } + }, + "attachment": true, + "open_weights": false, + "knowledge": "2026-01-31", + "release_date": "2026-04-16", + "last_updated": "2026-04-16", "type": "chat" }, { - "id": "TheDrummer/Anubis-70B-v1.1", - "name": "Anubis 70B v1.1", - "display_name": "Anubis 70B v1.1", + "id": "openai.gpt-oss-20b", + "name": "gpt-oss-20b", + "display_name": "gpt-oss-20b", "modalities": { "input": [ "text" @@ -248681,49 +248234,78 @@ ] }, "limit": { - "context": 32000, - "output": 16384 + "context": 131072, + "output": 131072 }, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "low", + "medium", + "high" + ] + } }, "attachment": false, "open_weights": true, - "release_date": "2024-01-01", - "last_updated": "2024-01-01", + "knowledge": "2024-06", + "release_date": "2025-08-05", + "last_updated": "2025-08-05", "type": "chat" }, { - "id": "TheDrummer/Cydonia-24B-v4", - "name": "The Drummer Cydonia 24B v4", - "display_name": "The Drummer Cydonia 24B v4", + "id": "openai.gpt-6-luna", + "name": "GPT-6 Luna", + "display_name": "GPT-6 Luna", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 32768, - "output": 32768 + "context": 1050000, + "output": 128000 }, - "tool_call": false, + "temperature": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, - "open_weights": true, - "release_date": "2025-07-22", - "last_updated": "2025-07-22", + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "none", + "low", + "medium", + "high", + "xhigh", + "max" + ] + } + }, + "attachment": true, + "open_weights": false, + "knowledge": "2026-05-18", + "release_date": "2026-09-22", + "last_updated": "2026-09-22", "type": "chat" }, { - "id": "TheDrummer/Cydonia-24B-v4.3", - "name": "The Drummer Cydonia 24B v4.3", - "display_name": "The Drummer Cydonia 24B v4.3", + "id": "deepseek.r1-v1:0", + "name": "DeepSeek-R1", + "display_name": "DeepSeek-R1", "modalities": { "input": [ "text" @@ -248733,81 +248315,58 @@ ] }, "limit": { - "context": 131072, + "context": 128000, "output": 32768 }, + "temperature": true, "tool_call": false, "reasoning": { - "supported": false + "supported": true, + "default": true }, "attachment": false, "open_weights": true, - "release_date": "2025-12-25", - "last_updated": "2025-12-25", + "knowledge": "2024-07", + "release_date": "2025-01-20", + "last_updated": "2025-05-29", "type": "chat" }, { - "id": "TheDrummer/skyfall-36b-v2", - "name": "TheDrummer Skyfall 36B V2", - "display_name": "TheDrummer Skyfall 36B V2", + "id": "mistral.ministral-3-14b-instruct", + "name": "Ministral 14B 3.0", + "display_name": "Ministral 14B 3.0", "modalities": { "input": [ "text", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 32768, - "output": 29491 + "context": 128000, + "output": 4096 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { "supported": false }, "attachment": true, "open_weights": true, - "knowledge": "2024-06-30", - "release_date": "2025-03-10", - "last_updated": "2025-03-10", - "type": "chat" - }, - { - "id": "TheDrummer/Cydonia-24B-v2", - "name": "The Drummer Cydonia 24B v2", - "display_name": "The Drummer Cydonia 24B v2", - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 32768, - "output": 32768 - }, - "tool_call": false, - "reasoning": { - "supported": false - }, - "attachment": false, - "open_weights": true, - "release_date": "2025-02-17", - "last_updated": "2025-02-17", + "release_date": "2025-12-02", + "last_updated": "2025-12-02", "type": "chat" }, { - "id": "microsoft/wizardlm-2-8x22b", - "name": "WizardLM-2 8x22B", - "display_name": "WizardLM-2 8x22B", + "id": "jp.anthropic.claude-sonnet-4-6", + "name": "Claude Sonnet 4.6 (JP)", + "display_name": "Claude Sonnet 4.6 (JP)", "modalities": { "input": [ "text", + "image", "pdf" ], "output": [ @@ -248815,27 +248374,59 @@ ] }, "limit": { - "context": 65536, - "output": 8192 + "context": 1000000, + "output": 128000 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": false + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": false, + "mode": "mixed", + "budget": { + "min": 1024, + "unit": "tokens" + }, + "effort": "high", + "effort_options": [ + "low", + "medium", + "high", + "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Anthropic recommends adaptive thinking with effort for Claude 4.6; budget_tokens remains a deprecated compatibility path.", + "Anthropic API defaults effort to high; lower effort levels should be chosen per workload." + ] + } }, "attachment": true, - "open_weights": true, - "release_date": "2024-04-15", - "last_updated": "2024-04-15", + "open_weights": false, + "knowledge": "2025-08-31", + "release_date": "2026-02-17", + "last_updated": "2026-03-13", "type": "chat" }, { - "id": "z-ai/glm-4.7:thinking", - "name": "GLM 4.7 Thinking", - "display_name": "GLM 4.7 Thinking", + "id": "anthropic.claude-haiku-4-5-20251001-v1:0", + "name": "Claude Haiku 4.5", + "display_name": "Claude Haiku 4.5", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" @@ -248843,38 +248434,61 @@ }, "limit": { "context": 200000, - "output": 65535 + "output": 64000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-12-22", - "last_updated": "2025-12-22", + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": false, + "mode": "budget", + "budget": { + "min": 1024, + "unit": "tokens" + }, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", + "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." + ] + } + }, + "attachment": true, + "open_weights": false, + "knowledge": "2025-02-28", + "release_date": "2025-10-15", + "last_updated": "2025-10-15", "type": "chat" }, { - "id": "z-ai/glm-5.3-flash-uncensored", - "name": "GLM 5.3 Flash Uncensored", - "display_name": "GLM 5.3 Flash Uncensored", + "id": "anthropic.claude-fable-5", + "name": "Claude Fable 5", + "display_name": "Claude Fable 5", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 32768 + "context": 1000000, + "output": 128000 }, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -248882,36 +248496,56 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "high", "effort_options": [ "low", + "medium", "high", + "xhigh", "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "omitted", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Adaptive thinking is always on for Claude Fable 5 and Claude Mythos 5; thinking.type = \"disabled\" is rejected.", + "Manual budget_tokens requests return 400 on Claude Fable 5 and Claude Mythos 5.", + "thinking.display defaults to omitted; set display to summarized to receive readable thinking summaries." ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-07-29", - "last_updated": "2026-08-27", + "open_weights": false, + "knowledge": "2026-01-31", + "release_date": "2026-06-09", + "last_updated": "2026-06-09", "type": "chat" }, { - "id": "z-ai/glm-5.2:thinking", - "name": "GLM 5.2 Thinking", - "display_name": "GLM 5.2 Thinking", + "id": "au.anthropic.claude-haiku-5-5", + "name": "Claude Haiku 5.5 (AU)", + "display_name": "Claude Haiku 5.5 (AU)", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 1000000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -248920,21 +248554,25 @@ "extra_capabilities": { "reasoning": { "effort_options": [ + "low", + "medium", "high", + "xhigh", "max" ] } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-06-13", - "last_updated": "2026-06-13", + "attachment": true, + "open_weights": false, + "knowledge": "2026-06", + "release_date": "2026-10-07", + "last_updated": "2026-10-07", "type": "chat" }, { - "id": "z-ai/glm-4.6-original", - "name": "GLM 4.6 Original", - "display_name": "GLM 4.6 Original", + "id": "openai.gpt-oss-safeguard-120b", + "name": "GPT OSS Safeguard 120B", + "display_name": "GPT OSS Safeguard 120B", "modalities": { "input": [ "text" @@ -248944,8 +248582,8 @@ ] }, "limit": { - "context": 256000, - "output": 65535 + "context": 128000, + "output": 16384 }, "temperature": true, "tool_call": true, @@ -248953,62 +248591,81 @@ "supported": true, "default": true }, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "low", + "medium", + "high" + ] + } + }, "attachment": false, "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-09-30", - "last_updated": "2025-09-30", + "release_date": "2025-10-29", + "last_updated": "2025-10-29", "type": "chat" }, { - "id": "z-ai/glm-4.5v", - "name": "GLM 4.5V", - "display_name": "GLM 4.5V", + "id": "amazon.nova-2-lite-v1:0", + "name": "Nova 2 Lite", + "display_name": "Nova 2 Lite", "modalities": { "input": [ "text", - "image" + "image", + "video", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 65536, - "output": 16384 + "context": 1000000, + "output": 65535 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { "supported": true, "default": true }, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "low", + "medium", + "high" + ] + } + }, "attachment": true, - "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-08-11", - "last_updated": "2025-08-11", + "open_weights": false, + "knowledge": "2025-10", + "release_date": "2025-12-02", + "last_updated": "2025-12-01", "type": "chat" }, { - "id": "z-ai/glm-5.3-flash", - "name": "GLM 5.3 Flash", - "display_name": "GLM 5.3 Flash", + "id": "au.anthropic.claude-sonnet-5-5", + "name": "Claude Sonnet 5.5 (AU)", + "display_name": "Claude Sonnet 5.5 (AU)", "modalities": { "input": [ "text", "image", - "video" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 1000000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -249016,82 +248673,68 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, "effort_options": [ "low", + "medium", "high", + "xhigh", "max" ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-08-26", - "last_updated": "2026-08-26", + "open_weights": false, + "knowledge": "2026-06", + "release_date": "2026-09-28", + "last_updated": "2026-09-28", "type": "chat" }, { - "id": "z-ai/GLM-4.5-Air:thinking", - "name": "GLM 4.5 Air (Thinking)", - "display_name": "GLM 4.5 Air (Thinking)", + "id": "eu.anthropic.claude-opus-5", + "name": "Claude Opus 5 (EU)", + "display_name": "Claude Opus 5 (EU)", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 98304 + "context": 1000000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, "default": true }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-07-28", - "last_updated": "2025-07-28", - "type": "chat" - }, - { - "id": "z-ai/glm-5v-turbo", - "name": "GLM 5V Turbo", - "display_name": "GLM 5V Turbo", - "modalities": { - "input": [ - "text", - "image", - "video" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 202800, - "output": 131072 - }, - "temperature": true, - "tool_call": true, - "reasoning": { - "supported": false + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" + ] + } }, "attachment": true, "open_weights": false, - "release_date": "2026-04-01", - "last_updated": "2026-04-01", + "knowledge": "2026-05", + "release_date": "2026-07-24", + "last_updated": "2026-07-24", "type": "chat" }, { - "id": "z-ai/glm-5-turbo", - "name": "GLM 5 Turbo", - "display_name": "GLM 5 Turbo", + "id": "nvidia.nemotron-nano-3-30b", + "name": "NVIDIA Nemotron Nano 3 30B", + "display_name": "NVIDIA Nemotron Nano 3 30B", "modalities": { "input": [ "text" @@ -249101,62 +248744,31 @@ ] }, "limit": { - "context": 202800, - "output": 131072 + "context": 262144, + "output": 8192 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true - } + "supported": true, + "default": true }, "attachment": false, - "open_weights": false, - "release_date": "2026-03-16", - "last_updated": "2026-03-16", + "open_weights": true, + "knowledge": "2025-06-25", + "release_date": "2025-12-15", + "last_updated": "2025-12-23", "type": "chat" }, { - "id": "z-ai/glm-4.6v", - "name": "GLM 4.6V", - "display_name": "GLM 4.6V", + "id": "eu.anthropic.claude-haiku-4-5-20251001-v1:0", + "name": "Claude Haiku 4.5 (EU)", + "display_name": "Claude Haiku 4.5 (EU)", "modalities": { "input": [ "text", "image", - "video" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 24000 - }, - "temperature": true, - "tool_call": false, - "reasoning": { - "supported": false - }, - "attachment": true, - "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-12-08", - "last_updated": "2025-12-08", - "type": "chat" - }, - { - "id": "z-ai/glm-4.7", - "name": "GLM 4.7", - "display_name": "GLM 4.7", - "modalities": { - "input": [ - "text" + "pdf" ], "output": [ "text" @@ -249164,39 +248776,51 @@ }, "limit": { "context": 200000, - "output": 65535 + "output": 64000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": false, + "mode": "budget", + "budget": { + "min": 1024, + "unit": "tokens" + }, "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ "thinking_blocks" + ], + "notes": [ + "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", + "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." ] } }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-12-22", - "last_updated": "2025-12-22", + "attachment": true, + "open_weights": false, + "knowledge": "2025-02-28", + "release_date": "2025-10-15", + "last_updated": "2025-10-15", "type": "chat" }, { - "id": "z-ai/glm-5:thinking", - "name": "GLM 5 Thinking", - "display_name": "GLM 5 Thinking", + "id": "anthropic.claude-sonnet-4-5-20250929-v1:0", + "name": "Claude Sonnet 4.5", + "display_name": "Claude Sonnet 4.5", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" @@ -249204,37 +248828,61 @@ }, "limit": { "context": 200000, - "output": 128000 + "output": 64000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, - "attachment": false, - "open_weights": true, - "release_date": "2026-02-12", - "last_updated": "2026-02-12", + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": false, + "mode": "budget", + "budget": { + "min": 1024, + "unit": "tokens" + }, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", + "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." + ] + } + }, + "attachment": true, + "open_weights": false, + "knowledge": "2025-07-31", + "release_date": "2025-09-29", + "last_updated": "2025-09-29", "type": "chat" }, { - "id": "z-ai/glm-4.6:thinking", - "name": "GLM 4.6 Thinking", - "display_name": "GLM 4.6 Thinking", + "id": "us.anthropic.claude-opus-5", + "name": "Claude Opus 5 (US)", + "display_name": "Claude Opus 5 (US)", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 65535 + "context": 1000000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -249243,35 +248891,40 @@ "extra_capabilities": { "reasoning": { "effort_options": [ - "none", - "high" + "low", + "medium", + "high", + "xhigh", + "max" ] } }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-09-30", - "last_updated": "2025-09-30", + "attachment": true, + "open_weights": false, + "knowledge": "2026-05", + "release_date": "2026-07-24", + "last_updated": "2026-07-24", "type": "chat" }, { - "id": "z-ai/glm-5.3-flash-apex", - "name": "Apex Flash 1", - "display_name": "Apex Flash 1", + "id": "in.openai.gpt-5.6-luna", + "name": "GPT-5.6 Luna (India)", + "display_name": "GPT-5.6 Luna (India)", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 32768 + "context": 1050000, + "output": 128000 }, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -249280,51 +248933,55 @@ "extra_capabilities": { "reasoning": { "effort_options": [ + "none", "low", + "medium", "high", + "xhigh", "max" ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-10-05", - "last_updated": "2026-10-05", + "open_weights": false, + "knowledge": "2026-02-16", + "release_date": "2026-07-09", + "last_updated": "2026-07-09", "type": "chat" }, { - "id": "z-ai/glm-4.7-original:thinking", - "name": "GLM 4.7 Original Thinking", - "display_name": "GLM 4.7 Original Thinking", + "id": "us.meta.llama4-scout-17b-instruct-v1:0", + "name": "Llama 4 Scout 17B Instruct (US)", + "display_name": "Llama 4 Scout 17B Instruct (US)", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 65535 + "context": 10000000, + "output": 8192 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true + "supported": false }, - "attachment": false, + "attachment": true, "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-12-22", - "last_updated": "2025-12-22", + "knowledge": "2024-08", + "release_date": "2025-04-05", + "last_updated": "2025-04-05", "type": "chat" }, { - "id": "z-ai/glm-4.6", - "name": "GLM 4.6", - "display_name": "GLM 4.6", + "id": "nvidia.nemotron-super-3-120b", + "name": "NVIDIA Nemotron 3 Super 120B A12B", + "display_name": "NVIDIA Nemotron 3 Super 120B A12B", "modalities": { "input": [ "text" @@ -249334,8 +248991,8 @@ ] }, "limit": { - "context": 200000, - "output": 65535 + "context": 262144, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -249343,39 +249000,32 @@ "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "effort_options": [ - "none", - "high" - ] - } - }, "attachment": false, "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-09-30", - "last_updated": "2025-09-30", + "knowledge": "2025-06", + "release_date": "2026-03-11", + "last_updated": "2026-03-11", "type": "chat" }, { - "id": "z-ai/glm-5.1:thinking", - "name": "GLM 5.1 Thinking", - "display_name": "GLM 5.1 Thinking", + "id": "jp.anthropic.claude-opus-5-5", + "name": "Claude Opus 5.5 (JP)", + "display_name": "Claude Opus 5.5 (JP)", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 131072 + "context": 1000000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -249384,105 +249034,148 @@ "extra_capabilities": { "reasoning": { "effort_options": [ - "none", - "high" + "low", + "medium", + "high", + "xhigh", + "max" ] } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-04-07", - "last_updated": "2026-04-07", + "attachment": true, + "open_weights": false, + "knowledge": "2026-06", + "release_date": "2026-09-22", + "last_updated": "2026-09-22", "type": "chat" }, { - "id": "z-ai/GLM-4.6-turbo:thinking", - "name": "GLM 4.6 Turbo (Thinking)", - "display_name": "GLM 4.6 Turbo (Thinking)", + "id": "us.openai.gpt-6-astra", + "name": "GPT-6 Astra (US)", + "display_name": "GPT-6 Astra (US)", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 204800, - "output": 131072 + "context": 1050000, + "output": 128000 }, - "tool_call": false, + "temperature": false, + "tool_call": true, "reasoning": { "supported": true, "default": true }, - "attachment": false, - "open_weights": true, - "release_date": "2025-10-02", - "last_updated": "2025-10-02", + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" + ] + } + }, + "attachment": true, + "open_weights": false, + "knowledge": "2026-04-30", + "release_date": "2026-09-04", + "last_updated": "2026-09-04", "type": "chat" }, { - "id": "z-ai/GLM-4.5-Air", - "name": "GLM 4.5 Air", - "display_name": "GLM 4.5 Air", + "id": "apac.amazon.nova-lite-v1:0", + "name": "Nova Lite (APAC)", + "display_name": "Nova Lite (APAC)", "modalities": { "input": [ - "text" + "text", + "image", + "video", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 98304 + "context": 300000, + "output": 10000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": false }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-07-28", - "last_updated": "2025-07-28", + "attachment": true, + "open_weights": false, + "knowledge": "2024-10", + "release_date": "2024-12-03", + "last_updated": "2024-12-03", "type": "chat" }, { - "id": "z-ai/glm-4.7-flash:thinking", - "name": "GLM 4.7 Flash Thinking", - "display_name": "GLM 4.7 Flash Thinking", + "id": "au.anthropic.claude-opus-5-5", + "name": "Claude Opus 5.5 (AU)", + "display_name": "Claude Opus 5.5 (AU)", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 200000, + "context": 1000000, "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, "default": true }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-04", - "release_date": "2026-01-19", - "last_updated": "2026-01-19", + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" + ] + } + }, + "attachment": true, + "open_weights": false, + "knowledge": "2026-06", + "release_date": "2026-09-22", + "last_updated": "2026-09-22", "type": "chat" - }, + } + ] + }, + "ovhcloud": { + "id": "ovhcloud", + "name": "OVHcloud AI Endpoints", + "display_name": "OVHcloud AI Endpoints", + "api": "https://oai.endpoints.kepler.ai.cloud.ovh.net/v1", + "doc": "https://www.ovhcloud.com/en/public-cloud/ai-endpoints/catalog//", + "models": [ { - "id": "z-ai/glm-4.6v-original", - "name": "GLM 4.6V Original", - "display_name": "GLM 4.6V Original", + "id": "qwen2.5-vl-72b-instruct", + "name": "Qwen2.5-VL-72B-Instruct", + "display_name": "Qwen2.5-VL-72B-Instruct", "modalities": { "input": [ "text", @@ -249493,8 +249186,8 @@ ] }, "limit": { - "context": 128000, - "output": 24000 + "context": 32768, + "output": 32768 }, "temperature": true, "tool_call": false, @@ -249503,15 +249196,15 @@ }, "attachment": true, "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-12-08", - "last_updated": "2025-12-08", + "knowledge": "2024-04", + "release_date": "2025-01-26", + "last_updated": "2025-01-26", "type": "chat" }, { - "id": "z-ai/glm-5.1", - "name": "GLM 5.1", - "display_name": "GLM 5.1", + "id": "mistral-nemo-instruct-2407", + "name": "Mistral-Nemo-Instruct-2407", + "display_name": "Mistral-Nemo-Instruct-2407", "modalities": { "input": [ "text" @@ -249521,36 +249214,28 @@ ] }, "limit": { - "context": 200000, - "output": 131072 + "context": 65536, + "output": 65536 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "effort_options": [ - "none", - "high" - ] - } + "supported": false }, "attachment": false, "open_weights": true, - "release_date": "2026-04-07", - "last_updated": "2026-04-07", + "knowledge": "2024-07", + "release_date": "2024-07-18", + "last_updated": "2024-07-18", "type": "chat" }, { - "id": "z-ai/glm-5-original:thinking", - "name": "GLM 5 Original Thinking", - "display_name": "GLM 5 Original Thinking", + "id": "mistral-small-3.2-24b-instruct-2506", + "name": "Mistral-Small-3.2-24B-Instruct-2506", + "display_name": "Mistral-Small-3.2-24B-Instruct-2506", "modalities": { "input": [ + "image", "text" ], "output": [ @@ -249558,62 +249243,53 @@ ] }, "limit": { - "context": 200000, - "output": 128000 + "context": 131072, + "output": 131072 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true + "supported": false }, - "attachment": false, + "attachment": true, "open_weights": true, - "release_date": "2026-02-12", - "last_updated": "2026-02-12", + "knowledge": "2023-10-31", + "release_date": "2025-06-20", + "last_updated": "2025-06-20", "type": "chat" }, { - "id": "z-ai/glm-5.3-flash-cybersecurity", - "name": "GLM 5.3 Flash Cybersecurity", - "display_name": "GLM 5.3 Flash Cybersecurity", + "id": "meta-llama-3_3-70b-instruct", + "name": "Meta-Llama-3_3-70B-Instruct", + "display_name": "Meta-Llama-3_3-70B-Instruct", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 32768 + "context": 131072, + "output": 131072 }, + "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "low", - "high", - "max" - ] - } + "supported": false }, - "attachment": true, + "attachment": false, "open_weights": true, - "release_date": "2026-09-19", - "last_updated": "2026-09-19", + "knowledge": "2023-12", + "release_date": "2024-12-06", + "last_updated": "2024-12-06", "type": "chat" }, { - "id": "z-ai/glm-5.3-uncensored", - "name": "GLM 5.3 Uncensored", - "display_name": "GLM 5.3 Uncensored", + "id": "gpt-oss-120b", + "name": "gpt-oss-120b", + "display_name": "gpt-oss-120b", "modalities": { "input": [ "text" @@ -249623,9 +249299,10 @@ ] }, "limit": { - "context": 1048576, + "context": 131072, "output": 131072 }, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -249633,36 +249310,37 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, "effort_options": [ "low", - "high", - "max" + "medium", + "high" ] } }, "attachment": false, "open_weights": true, - "release_date": "2026-09-26", - "last_updated": "2026-09-26", + "knowledge": "2024-06", + "release_date": "2025-08-05", + "last_updated": "2025-08-05", "type": "chat" }, { - "id": "z-ai/glm-5v-turbo:thinking", - "name": "GLM 5V Turbo Thinking", - "display_name": "GLM 5V Turbo Thinking", + "id": "qwen3.8-27b", + "name": "Qwen3.8-27B", + "display_name": "Qwen3.8-27B", "modalities": { "input": [ "text", - "image", - "video" + "image" ], "output": [ "text" ] }, "limit": { - "context": 202800, - "output": 131072 + "context": 262144, + "output": 262144 }, "temperature": true, "tool_call": true, @@ -249670,27 +249348,39 @@ "supported": true, "default": true }, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "none", + "minimal", + "low", + "medium", + "high" + ] + } + }, "attachment": true, - "open_weights": false, - "release_date": "2026-04-01", - "last_updated": "2026-04-01", + "open_weights": true, + "release_date": "2026-08-14", + "last_updated": "2026-08-14", "type": "chat" }, { - "id": "z-ai/glm-5", - "name": "GLM 5", - "display_name": "GLM 5", + "id": "qwen3.6-27b", + "name": "Qwen3.6-27B", + "display_name": "Qwen3.6-27B", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 128000 + "context": 262144, + "output": 262144 }, "temperature": true, "tool_call": true, @@ -249706,30 +249396,38 @@ "visibility": "summary", "continuation": [ "thinking_blocks" + ], + "effort_options": [ + "none", + "minimal", + "low", + "medium", + "high" ] } }, - "attachment": false, + "attachment": true, "open_weights": true, - "release_date": "2026-02-12", - "last_updated": "2026-02-12", + "release_date": "2026-04-22", + "last_updated": "2026-04-22", "type": "chat" }, { - "id": "z-ai/glm-5.3", - "name": "GLM 5.3", - "display_name": "GLM 5.3", + "id": "qwen3.5-9b", + "name": "Qwen3.5-9B", + "display_name": "Qwen3.5-9B", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 262144, + "output": 262144 }, "temperature": true, "tool_call": true, @@ -249740,23 +249438,30 @@ "extra_capabilities": { "reasoning": { "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ + "none", "low", - "high", - "max" + "medium", + "high" ] } }, - "attachment": false, + "attachment": true, "open_weights": true, - "release_date": "2026-08-14", - "last_updated": "2026-08-14", + "release_date": "2026-02-23", + "last_updated": "2026-02-23", "type": "chat" }, { - "id": "z-ai/glm-4.7-original", - "name": "GLM 4.7 Original", - "display_name": "GLM 4.7 Original", + "id": "qwen3guard-gen-0.6b", + "name": "Qwen3Guard-Gen-0.6B", + "display_name": "Qwen3Guard-Gen-0.6B", "modalities": { "input": [ "text" @@ -249766,37 +249471,36 @@ ] }, "limit": { - "context": 200000, - "output": 65535 + "context": 32768, + "output": 16384 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true + "supported": false }, "attachment": false, "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-12-22", - "last_updated": "2025-12-22", + "release_date": "2026-01-22", + "last_updated": "2026-01-22", "type": "chat" }, { - "id": "z-ai/glm-5-original", - "name": "GLM 5 Original", - "display_name": "GLM 5 Original", + "id": "qwen3.5-397b-a17b", + "name": "Qwen3.5-397B-A17B", + "display_name": "Qwen3.5-397B-A17B", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 128000 + "context": 262144, + "output": 262144 }, "temperature": true, "tool_call": true, @@ -249804,16 +249508,33 @@ "supported": true, "default": true }, - "attachment": false, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "effort_options": [ + "none", + "low", + "medium", + "high" + ] + } + }, + "attachment": true, "open_weights": true, - "release_date": "2026-02-12", - "last_updated": "2026-02-12", + "release_date": "2026-02-15", + "last_updated": "2026-02-15", "type": "chat" }, { - "id": "z-ai/GLM-4.6-turbo", - "name": "GLM 4.6 Turbo", - "display_name": "GLM 4.6 Turbo", + "id": "qwen3guard-gen-8b", + "name": "Qwen3Guard-Gen-8B", + "display_name": "Qwen3Guard-Gen-8B", "modalities": { "input": [ "text" @@ -249823,23 +249544,24 @@ ] }, "limit": { - "context": 204800, - "output": 131072 + "context": 32768, + "output": 16384 }, + "temperature": true, "tool_call": false, "reasoning": { "supported": false }, "attachment": false, "open_weights": true, - "release_date": "2025-10-02", - "last_updated": "2025-10-02", + "release_date": "2026-01-22", + "last_updated": "2026-01-22", "type": "chat" }, { - "id": "z-ai/glm-5.3:thinking", - "name": "GLM 5.3 Thinking", - "display_name": "GLM 5.3 Thinking", + "id": "gpt-oss-20b", + "name": "gpt-oss-20b", + "display_name": "gpt-oss-20b", "modalities": { "input": [ "text" @@ -249849,7 +249571,7 @@ ] }, "limit": { - "context": 1048576, + "context": 131072, "output": 131072 }, "temperature": true, @@ -249860,62 +249582,92 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, "effort_options": [ - "high", - "max" + "low", + "medium", + "high" ] } }, "attachment": false, "open_weights": true, - "release_date": "2026-08-14", - "last_updated": "2026-08-14", + "knowledge": "2024-06", + "release_date": "2025-08-05", + "last_updated": "2025-08-05", "type": "chat" - }, + } + ] + }, + "zhipuai-coding-plan": { + "id": "zhipuai-coding-plan", + "name": "Zhipu AI Coding Plan", + "display_name": "Zhipu AI Coding Plan", + "api": "https://open.bigmodel.cn/api/coding/paas/v4", + "doc": "https://docs.bigmodel.cn/cn/coding-plan/overview", + "models": [ { - "id": "z-ai/glm-latest", - "name": "GLM Latest", - "display_name": "GLM Latest", + "id": "glm-4.6v", + "name": "GLM-4.6V", + "display_name": "GLM-4.6V", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 128000, + "output": 32768 }, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, "default": true }, - "attachment": false, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } + }, + "attachment": true, "open_weights": true, - "release_date": "2026-05-03", - "last_updated": "2026-05-03", + "knowledge": "2025-04", + "release_date": "2025-12-08", + "last_updated": "2025-12-08", "type": "chat" }, { - "id": "z-ai/glm-5.3-flash-apex-abliterated", - "name": "Apex Flash 1 Abliterated", - "display_name": "Apex Flash 1 Abliterated", + "id": "glm-5.3-flash", + "name": "GLM-5.3-Flash", + "display_name": "GLM-5.3-Flash", "modalities": { "input": [ "text", - "image" + "image", + "video", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 32768 + "context": 1000000, + "output": 131072 }, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -249923,6 +249675,13 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ "low", "high", @@ -249932,14 +249691,14 @@ }, "attachment": true, "open_weights": true, - "release_date": "2026-10-05", - "last_updated": "2026-10-05", + "release_date": "2026-08-26", + "last_updated": "2026-08-26", "type": "chat" }, { - "id": "z-ai/GLM-4.5:thinking", - "name": "GLM 4.5 (Thinking)", - "display_name": "GLM 4.5 (Thinking)", + "id": "glm-5.3-highspeed", + "name": "GLM-5.3 Highspeed", + "display_name": "GLM-5.3 Highspeed", "modalities": { "input": [ "text" @@ -249949,26 +249708,41 @@ ] }, "limit": { - "context": 128000, - "output": 65536 + "context": 1000000, + "output": 131072 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { "supported": true, "default": true }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "effort_options": [ + "low", + "high", + "max" + ] + } + }, "attachment": false, "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-07-28", - "last_updated": "2025-07-28", + "release_date": "2026-08-14", + "last_updated": "2026-08-14", "type": "chat" }, { - "id": "z-ai/glm-4.5", - "name": "GLM 4.5", - "display_name": "GLM 4.5", + "id": "glm-5.3", + "name": "GLM-5.3", + "display_name": "GLM-5.3", "modalities": { "input": [ "text" @@ -249978,25 +249752,49 @@ ] }, "limit": { - "context": 128000, - "output": 65536 + "context": 1000000, + "output": 131072 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "effort_options": [ + "low", + "high", + "max" + ] + } }, "attachment": false, "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-07-28", - "last_updated": "2025-07-28", + "release_date": "2026-08-14", + "last_updated": "2026-08-14", "type": "chat" - }, + } + ] + }, + "perplexity": { + "id": "perplexity", + "name": "Perplexity", + "display_name": "Perplexity", + "doc": "https://docs.perplexity.ai", + "models": [ { - "id": "z-ai/glm-4.7-flash", - "name": "GLM 4.7 Flash", - "display_name": "GLM 4.7 Flash", + "id": "sonar", + "name": "Sonar", + "display_name": "Sonar", "modalities": { "input": [ "text" @@ -250006,31 +249804,25 @@ ] }, "limit": { - "context": 200000, - "output": 128000 + "context": 128000, + "output": 4096 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true - } + "supported": false }, "attachment": false, - "open_weights": true, - "knowledge": "2025-04", - "release_date": "2026-01-19", - "last_updated": "2026-01-19", + "open_weights": false, + "knowledge": "2025-09-01", + "release_date": "2024-01-01", + "last_updated": "2025-09-01", "type": "chat" }, { - "id": "z-ai/glm-5.2", - "name": "GLM 5.2", - "display_name": "GLM 5.2", + "id": "sonar-deep-research", + "name": "Perplexity Sonar Deep Research", + "display_name": "Perplexity Sonar Deep Research", "modalities": { "input": [ "text" @@ -250040,35 +249832,36 @@ ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 128000, + "output": 32768 }, - "temperature": true, - "tool_call": true, + "temperature": false, + "tool_call": false, "reasoning": { "supported": true, "default": true }, "extra_capabilities": { "reasoning": { - "supported": true, "effort_options": [ - "none", - "high", - "max" + "minimal", + "low", + "medium", + "high" ] } }, "attachment": false, - "open_weights": true, - "release_date": "2026-06-13", - "last_updated": "2026-06-13", + "open_weights": false, + "knowledge": "2025-01", + "release_date": "2025-02-01", + "last_updated": "2025-09-01", "type": "chat" }, { - "id": "z-ai/glm-4.5v:thinking", - "name": "GLM 4.5V Thinking", - "display_name": "GLM 4.5V Thinking", + "id": "sonar-pro", + "name": "Sonar Pro", + "display_name": "Sonar Pro", "modalities": { "input": [ "text", @@ -250079,8 +249872,37 @@ ] }, "limit": { - "context": 65536, - "output": 16384 + "context": 200000, + "output": 8192 + }, + "temperature": true, + "tool_call": false, + "reasoning": { + "supported": false + }, + "attachment": true, + "open_weights": false, + "knowledge": "2025-09-01", + "release_date": "2024-01-01", + "last_updated": "2025-09-01", + "type": "chat" + }, + { + "id": "sonar-reasoning-pro", + "name": "Sonar Reasoning Pro", + "display_name": "Sonar Reasoning Pro", + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 4096 }, "temperature": true, "tool_call": false, @@ -250088,17 +249910,36 @@ "supported": true, "default": true }, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "minimal", + "low", + "medium", + "high" + ] + } + }, "attachment": true, - "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-08-11", - "last_updated": "2025-08-11", + "open_weights": false, + "knowledge": "2025-09-01", + "release_date": "2024-01-01", + "last_updated": "2025-09-01", "type": "chat" - }, + } + ] + }, + "crusoe": { + "id": "crusoe", + "name": "Crusoe", + "display_name": "Crusoe", + "api": "https://api.inference.crusoecloud.com/v1", + "doc": "https://docs.crusoecloud.com/managed-inference/overview", + "models": [ { - "id": "ibm-granite/granite-4.2-8b", - "name": "Granite 4.2 8B", - "display_name": "Granite 4.2 8B", + "id": "openai/gpt-oss-120b", + "name": "GPT OSS 120B", + "display_name": "GPT OSS 120B", "modalities": { "input": [ "text" @@ -250109,7 +249950,7 @@ }, "limit": { "context": 131072, - "output": 117964 + "output": 32768 }, "temperature": true, "tool_call": true, @@ -250119,23 +249960,26 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "interleaved": true, "effort_options": [ - "none", "low", + "medium", "high" ] } }, "attachment": false, "open_weights": true, - "release_date": "2026-08-25", - "last_updated": "2026-08-25", + "knowledge": "2024-06", + "release_date": "2025-08-05", + "last_updated": "2025-08-05", "type": "chat" }, { - "id": "NeverSleep/Lumimaid-v0.2-70B", - "name": "Lumimaid v0.2", - "display_name": "Lumimaid v0.2", + "id": "Qwen/Qwen3-235B-A22B-Instruct-2507", + "name": "Qwen3 235B-A22B Instruct 2507", + "display_name": "Qwen3 235B-A22B Instruct 2507", "modalities": { "input": [ "text" @@ -250145,23 +249989,24 @@ ] }, "limit": { - "context": 16384, - "output": 8192 + "context": 262144, + "output": 16384 }, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { "supported": false }, "attachment": false, "open_weights": true, - "release_date": "2024-01-01", - "last_updated": "2024-07-01", + "release_date": "2025-07-21", + "last_updated": "2025-07-21", "type": "chat" }, { - "id": "upstage/solar-mini4:thinking", - "name": "Solar Mini 4 Thinking", - "display_name": "Solar Mini 4 Thinking", + "id": "nvidia/NVIDIA-Nemotron-3-Nano-30B-A3B", + "name": "Nemotron 3 Nano 30B A3B", + "display_name": "Nemotron 3 Nano 30B A3B", "modalities": { "input": [ "text" @@ -250171,8 +250016,40 @@ ] }, "limit": { - "context": 524288, - "output": 131072 + "context": 262144, + "output": 262144 + }, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": true, + "default": true + }, + "attachment": false, + "open_weights": true, + "knowledge": "2025-06-25", + "release_date": "2025-12-15", + "last_updated": "2025-12-15", + "type": "chat" + }, + { + "id": "nvidia/Nemotron-3-Nano-Omni-Reasoning-30B-A3B", + "name": "Nemotron 3 Nano Omni 30B A3B Reasoning", + "display_name": "Nemotron 3 Nano Omni 30B A3B Reasoning", + "modalities": { + "input": [ + "text", + "image", + "video", + "audio" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 256000, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -250182,25 +250059,20 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "none", - "low", - "medium", - "high" - ] + "supported": true, + "interleaved": true } }, - "attachment": false, - "open_weights": false, - "knowledge": "2026-02", - "release_date": "2026-09-22", - "last_updated": "2026-09-22", + "attachment": true, + "open_weights": true, + "release_date": "2026-04-28", + "last_updated": "2026-04-28", "type": "chat" }, { - "id": "upstage/solar-pro4:thinking", - "name": "Solar Pro 4 Thinking", - "display_name": "Solar Pro 4 Thinking", + "id": "nvidia/NVIDIA-Nemotron-3-Super-120B-A12B", + "name": "Nemotron 3 Super 120B A12B", + "display_name": "Nemotron 3 Super 120B A12B", "modalities": { "input": [ "text" @@ -250210,8 +250082,8 @@ ] }, "limit": { - "context": 524288, - "output": 131072 + "context": 262144, + "output": 262144 }, "temperature": true, "tool_call": true, @@ -250221,25 +250093,20 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "none", - "low", - "medium", - "high" - ] + "supported": true } }, "attachment": false, - "open_weights": false, - "knowledge": "2026-02", - "release_date": "2026-08-06", - "last_updated": "2026-08-06", + "open_weights": true, + "knowledge": "2025-06", + "release_date": "2026-03-11", + "last_updated": "2026-03-11", "type": "chat" }, { - "id": "upstage/solar-pro-3", - "name": "Solar Pro 3", - "display_name": "Solar Pro 3", + "id": "meta-llama/Llama-3.3-70B-Instruct", + "name": "Llama-3.3-70B-Instruct", + "display_name": "Llama-3.3-70B-Instruct", "modalities": { "input": [ "text" @@ -250249,81 +250116,97 @@ ] }, "limit": { - "context": 131072, - "output": 117964 + "context": 128000, + "output": 4096 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { "supported": false }, "attachment": false, - "open_weights": false, - "knowledge": "2025-07", - "release_date": "2026-01-26", - "last_updated": "2026-03-23", + "open_weights": true, + "knowledge": "2023-12", + "release_date": "2024-12-06", + "last_updated": "2024-12-06", "type": "chat" }, { - "id": "upstage/solar-mini4", - "name": "Solar Mini 4", - "display_name": "Solar Mini 4", + "id": "moonshotai/Kimi-K2.6", + "name": "Kimi K2.6", + "display_name": "Kimi K2.6", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 524288, - "output": 131072 + "context": 262144, + "output": 262144 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, - "open_weights": false, - "knowledge": "2026-02", - "release_date": "2026-09-22", - "last_updated": "2026-09-22", + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true + } + }, + "attachment": true, + "open_weights": true, + "knowledge": "2025-01", + "release_date": "2026-04-20", + "last_updated": "2026-04-20", "type": "chat" }, { - "id": "upstage/solar-pro4", - "name": "Solar Pro 4", - "display_name": "Solar Pro 4", + "id": "google/gemma-4-31b-it", + "name": "Gemma 4 31B IT", + "display_name": "Gemma 4 31B IT", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 524288, - "output": 131072 + "context": 262144, + "output": 32768 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, - "open_weights": false, - "knowledge": "2026-02", - "release_date": "2026-08-06", - "last_updated": "2026-08-06", + "extra_capabilities": { + "reasoning": { + "supported": true + } + }, + "attachment": true, + "open_weights": true, + "knowledge": "2025-01", + "release_date": "2026-04-02", + "last_updated": "2026-04-02", "type": "chat" }, { - "id": "inclusionai/ling-3.0-flash", - "name": "Ling 3.0 Flash", - "display_name": "Ling 3.0 Flash", + "id": "zai/GLM-5.1", + "name": "GLM-5.1", + "display_name": "GLM-5.1", "modalities": { "input": [ "text" @@ -250333,24 +250216,31 @@ ] }, "limit": { - "context": 262144, - "output": 32768 + "context": 200000, + "output": 131072 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true + } }, "attachment": false, "open_weights": true, - "release_date": "2026-07-23", - "last_updated": "2026-07-23", + "release_date": "2026-04-07", + "last_updated": "2026-04-07", "type": "chat" }, { - "id": "inclusionai/ling-3.1-flash", - "name": "Ling 3.1 Flash", - "display_name": "Ling 3.1 Flash", + "id": "zai/GLM-5.2", + "name": "GLM-5.2", + "display_name": "GLM-5.2", "modalities": { "input": [ "text" @@ -250360,8 +250250,8 @@ ] }, "limit": { - "context": 262144, - "output": 32768 + "context": 1000000, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -250371,24 +250261,20 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "none", - "low", - "medium", - "high" - ] + "supported": true, + "interleaved": true } }, "attachment": false, "open_weights": true, - "release_date": "2026-09-29", - "last_updated": "2026-09-29", + "release_date": "2026-06-13", + "last_updated": "2026-06-13", "type": "chat" }, { - "id": "inclusionai/ling-3.0-flash:thinking", - "name": "Ling 3.0 Flash Thinking", - "display_name": "Ling 3.0 Flash Thinking", + "id": "deepseek-ai/DeepSeek-V3-0324", + "name": "DeepSeek V3 0324", + "display_name": "DeepSeek V3 0324", "modalities": { "input": [ "text" @@ -250398,50 +250284,45 @@ ] }, "limit": { - "context": 262144, - "output": 32768 + "context": 163840, + "output": 163840 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "none", - "minimal", - "low", - "medium", - "high", - "xhigh" - ] - } + "supported": false }, "attachment": false, "open_weights": true, - "release_date": "2026-07-23", - "last_updated": "2026-07-23", + "release_date": "2025-03-24", + "last_updated": "2025-03-24", "type": "chat" - }, + } + ] + }, + "iteracompute": { + "id": "iteracompute", + "name": "IteraCompute", + "display_name": "IteraCompute", + "api": "https://api.iteracompute.com/v1", + "doc": "https://iteracompute.com/docs.html", + "models": [ { - "id": "inclusionai/ling-3.0-flash-vl", - "name": "Ling 3.0 Flash VL", - "display_name": "Ling 3.0 Flash VL", + "id": "z-ai/glm-5.3-flash", + "name": "GLM-5.3-Flash", + "display_name": "GLM-5.3-Flash", "modalities": { "input": [ "text", - "image", - "video" + "image" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 32768 + "context": 1048576, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -250451,28 +250332,27 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, "effort_options": [ - "none", "low", - "medium", - "high" + "high", + "max" ] } }, "attachment": true, "open_weights": true, - "release_date": "2026-09-10", - "last_updated": "2026-09-10", + "release_date": "2026-08-26", + "last_updated": "2026-08-26", "type": "chat" }, { - "id": "deepseek/deepseek-v4-flash-latest", - "name": "DeepSeek V4 Flash Latest", - "display_name": "DeepSeek V4 Flash Latest", + "id": "z-ai/glm-5.3", + "name": "GLM-5.3", + "display_name": "GLM-5.3", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" @@ -250480,8 +250360,9 @@ }, "limit": { "context": 1048576, - "output": 393216 + "output": 131072 }, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -250489,24 +250370,24 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, "effort_options": [ - "none", "low", "high", "max" ] } }, - "attachment": true, + "attachment": false, "open_weights": true, - "release_date": "2026-08-02", - "last_updated": "2026-08-02", + "release_date": "2026-08-14", + "last_updated": "2026-08-14", "type": "chat" }, { - "id": "deepseek/deepseek-v4.1-flash-1017-uncensored:thinking", - "name": "DeepSeek V4.1 Flash 1017 Uncensored Thinking", - "display_name": "DeepSeek V4.1 Flash 1017 Uncensored Thinking", + "id": "minimax/minimax-m3", + "name": "MiniMax-M3", + "display_name": "MiniMax-M3", "modalities": { "input": [ "text", @@ -250518,8 +250399,9 @@ }, "limit": { "context": 1048576, - "output": 393216 + "output": 524288 }, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -250527,24 +250409,19 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "none", - "low", - "high", - "max" - ] + "supported": true } }, "attachment": true, "open_weights": true, - "release_date": "2026-10-27", - "last_updated": "2026-10-27", + "release_date": "2026-06-01", + "last_updated": "2026-06-01", "type": "chat" }, { - "id": "deepseek/deepseek-v4.1-flash-0910-uncensored:thinking", - "name": "DeepSeek V4.1 Flash 0910 Uncensored Thinking", - "display_name": "DeepSeek V4.1 Flash 0910 Uncensored Thinking", + "id": "moonshotai/kimi-k3", + "name": "Kimi K3", + "display_name": "Kimi K3", "modalities": { "input": [ "text", @@ -250555,9 +250432,10 @@ ] }, "limit": { - "context": 524288, - "output": 32768 + "context": 1048576, + "output": 999999 }, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -250565,8 +250443,8 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, "effort_options": [ - "none", "low", "high", "max" @@ -250575,14 +250453,14 @@ }, "attachment": true, "open_weights": true, - "release_date": "2026-10-27", - "last_updated": "2026-10-27", + "release_date": "2026-07-16", + "last_updated": "2026-07-16", "type": "chat" }, { - "id": "deepseek/deepseek-v4-flash", - "name": "DeepSeek V4 Flash", - "display_name": "DeepSeek V4 Flash", + "id": "moonshotai/kimi-k2.7-code", + "name": "Kimi K2.7 Code", + "display_name": "Kimi K2.7 Code", "modalities": { "input": [ "text" @@ -250592,8 +250470,8 @@ ] }, "limit": { - "context": 1048576, - "output": 384000 + "context": 262144, + "output": 235929 }, "temperature": true, "tool_call": true, @@ -250603,37 +250481,20 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "high", - "effort_options": [ - "low", - "high", - "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." - ] + "supported": true } }, "attachment": false, "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-04-24", - "last_updated": "2026-04-24", + "knowledge": "2025-01", + "release_date": "2026-06-12", + "last_updated": "2026-06-12", "type": "chat" }, { - "id": "deepseek/deepseek-v4.1-flash-1017", - "name": "DeepSeek V4.1 Flash 1017", - "display_name": "DeepSeek V4.1 Flash 1017", + "id": "qwen/qwen3.8-2.4t-a95b", + "name": "Qwen3.8 2.4T A95B", + "display_name": "Qwen3.8 2.4T A95B", "modalities": { "input": [ "text", @@ -250645,7 +250506,7 @@ }, "limit": { "context": 1048576, - "output": 393216 + "output": 131072 }, "temperature": true, "tool_call": true, @@ -250655,36 +250516,36 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, "effort_options": [ - "none", "low", - "high", - "max" + "medium", + "xhigh" ] } }, "attachment": true, "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-10-17", - "last_updated": "2026-10-17", + "release_date": "2026-08-12", + "last_updated": "2026-08-12", "type": "chat" }, { - "id": "deepseek/deepseek-v4-pro-0813:thinking", - "name": "DeepSeek V4 Pro 0813 Thinking", - "display_name": "DeepSeek V4 Pro 0813 Thinking", + "id": "qwen/qwen3.8-27b", + "name": "Qwen3.8 27B", + "display_name": "Qwen3.8 27B", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 384000 + "context": 131072, + "output": 8192 }, "temperature": true, "tool_call": true, @@ -250695,23 +250556,22 @@ "extra_capabilities": { "reasoning": { "effort_options": [ - "none", "low", - "high", - "max" + "medium", + "xhigh" ] } }, - "attachment": false, + "attachment": true, "open_weights": true, - "release_date": "2026-08-13", - "last_updated": "2026-08-22", + "release_date": "2026-08-14", + "last_updated": "2026-08-14", "type": "chat" }, { - "id": "deepseek/deepseek-v4-pro", - "name": "DeepSeek V4 Pro", - "display_name": "DeepSeek V4 Pro", + "id": "deepseek/deepseek-v4.1-flash", + "name": "DeepSeek V4.1 Flash", + "display_name": "DeepSeek V4.1 Flash", "modalities": { "input": [ "text" @@ -250722,7 +250582,7 @@ }, "limit": { "context": 1048576, - "output": 384000 + "output": 393216 }, "temperature": true, "tool_call": true, @@ -250733,36 +250593,24 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "high", "effort_options": [ "low", "high", "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." ] } }, "attachment": false, "open_weights": true, "knowledge": "2025-05", - "release_date": "2026-04-24", - "last_updated": "2026-04-24", + "release_date": "2026-09-10", + "last_updated": "2026-09-10", "type": "chat" }, { - "id": "deepseek/deepseek-latest", - "name": "DeepSeek Latest", - "display_name": "DeepSeek Latest", + "id": "deepseek/deepseek-v4-pro-0813", + "name": "DeepSeek V4 Pro 0813", + "display_name": "DeepSeek V4 Pro 0813", "modalities": { "input": [ "text" @@ -250773,8 +250621,9 @@ }, "limit": { "context": 1048576, - "output": 384000 + "output": 393216 }, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -250782,8 +250631,8 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, "effort_options": [ - "none", "low", "high", "max" @@ -250792,18 +250641,17 @@ }, "attachment": false, "open_weights": true, - "release_date": "2026-05-03", - "last_updated": "2026-05-03", + "release_date": "2026-08-13", + "last_updated": "2026-08-22", "type": "chat" }, { - "id": "deepseek/deepseek-v4-flash-vision-exp", - "name": "DeepSeek V4 Flash Vision Exp", - "display_name": "DeepSeek V4 Flash Vision Exp", + "id": "deepseek/deepseek-v4-flash-0731", + "name": "DeepSeek V4 Flash 0731 (legacy alias)", + "display_name": "DeepSeek V4 Flash 0731 (legacy alias)", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" @@ -250811,7 +250659,7 @@ }, "limit": { "context": 1048576, - "output": 384000 + "output": 393216 }, "temperature": true, "tool_call": true, @@ -250822,35 +250670,32 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "high", "effort_options": [ "low", "high", "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." ] } }, - "attachment": true, + "attachment": false, "open_weights": true, - "release_date": "2026-08-21", - "last_updated": "2026-09-01", + "knowledge": "2025-05", + "release_date": "2026-09-10", + "last_updated": "2026-09-10", "type": "chat" - }, + } + ] + }, + "watsonx": { + "id": "watsonx", + "name": "watsonx.ai", + "display_name": "watsonx.ai", + "doc": "https://www.ibm.com/docs/en/watsonx/saas?topic=solutions-supported-foundation-models", + "models": [ { - "id": "deepseek/deepseek-v4-pro:thinking", - "name": "DeepSeek V4 Pro (Thinking)", - "display_name": "DeepSeek V4 Pro (Thinking)", + "id": "openai/gpt-oss-120b", + "name": "GPT OSS 120B", + "display_name": "GPT OSS 120B", "modalities": { "input": [ "text" @@ -250860,8 +250705,8 @@ ] }, "limit": { - "context": 1048576, - "output": 384000 + "context": 131072, + "output": 32768 }, "temperature": true, "tool_call": true, @@ -250871,24 +250716,25 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, "effort_options": [ - "none", - "high", - "max" + "low", + "medium", + "high" ] } }, "attachment": false, "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-04-24", - "last_updated": "2026-04-24", + "knowledge": "2024-06", + "release_date": "2025-08-05", + "last_updated": "2025-08-05", "type": "chat" }, { - "id": "deepseek/deepseek-v4.1-flash-uncensored:thinking", - "name": "DeepSeek V4.1 Flash Uncensored Thinking", - "display_name": "DeepSeek V4.1 Flash Uncensored Thinking", + "id": "meta-llama/llama-4-maverick-17b-128e-instruct-fp8", + "name": "Llama 4 Maverick 17B 128E Instruct FP8", + "display_name": "Llama 4 Maverick 17B 128E Instruct FP8", "modalities": { "input": [ "text", @@ -250899,109 +250745,82 @@ ] }, "limit": { - "context": 1048576, - "output": 393216 + "context": 131072, + "output": 8192 }, + "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "none", - "low", - "medium", - "high" - ] - } + "supported": false }, "attachment": true, "open_weights": true, - "release_date": "2026-10-01", - "last_updated": "2026-10-01", + "knowledge": "2024-08", + "release_date": "2025-04-05", + "last_updated": "2025-04-05", "type": "chat" }, { - "id": "deepseek/deepseek-v4.1-flash", - "name": "DeepSeek V4.1 Flash", - "display_name": "DeepSeek V4.1 Flash", + "id": "meta-llama/llama-3-3-70b-instruct", + "name": "Llama-3.3-70B-Instruct", + "display_name": "Llama-3.3-70B-Instruct", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 384000 + "context": 131072, + "output": 4096 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "effort_options": [ - "none", - "low", - "high", - "max" - ] - } + "supported": false }, - "attachment": true, + "attachment": false, "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-09-10", - "last_updated": "2026-09-10", + "knowledge": "2023-12", + "release_date": "2024-12-06", + "last_updated": "2024-12-06", "type": "chat" }, { - "id": "deepseek/deepseek-v3.2", - "name": "DeepSeek V3.2", - "display_name": "DeepSeek V3.2", + "id": "mistralai/mistral-small-3-1-24b-instruct-2503", + "name": "Mistral Small 3.1 24B", + "display_name": "Mistral Small 3.1 24B", "modalities": { "input": [ "text", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 163000, - "output": 65536 + "context": 131072, + "output": 16384 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true - } + "supported": false }, "attachment": true, "open_weights": true, - "knowledge": "2024-07", - "release_date": "2025-12-01", - "last_updated": "2025-12-01", + "knowledge": "2024-06", + "release_date": "2025-03-17", + "last_updated": "2025-03-17", "type": "chat" }, { - "id": "deepseek/deepseek-v4-flash-0731", - "name": "DeepSeek V4 Flash 0731", - "display_name": "DeepSeek V4 Flash 0731", + "id": "ibm/granite-4-h-small", + "name": "Granite-4.0-H-Small", + "display_name": "Granite-4.0-H-Small", "modalities": { "input": [ "text" @@ -251011,40 +250830,39 @@ ] }, "limit": { - "context": 1048576, + "context": 131072, "output": 131072 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "effort_options": [ - "none", - "low", - "high", - "max" - ] - } + "supported": false }, "attachment": false, "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-07-31", - "last_updated": "2026-07-31", + "release_date": "2025-10-02", + "last_updated": "2025-10-02", "type": "chat" - }, + } + ] + }, + "xiaomi-token-plan-cn": { + "id": "xiaomi-token-plan-cn", + "name": "Xiaomi Token Plan (China)", + "display_name": "Xiaomi Token Plan (China)", + "api": "https://token-plan-cn.xiaomimimo.com/v1", + "doc": "https://platform.xiaomimimo.com/#/docs", + "models": [ { - "id": "deepseek/deepseek-v4-pro-0813", - "name": "DeepSeek V4 Pro 0813", - "display_name": "DeepSeek V4 Pro 0813", + "id": "mimo-v2.6-pro", + "name": "MiMo-V2.6-Pro", + "display_name": "MiMo-V2.6-Pro", "modalities": { "input": [ - "text" + "text", + "image", + "audio", + "video" ], "output": [ "text" @@ -251052,7 +250870,7 @@ }, "limit": { "context": 1048576, - "output": 384000 + "output": 131072 }, "temperature": true, "tool_call": true, @@ -251063,24 +250881,24 @@ "extra_capabilities": { "reasoning": { "supported": true, - "effort_options": [ - "none", - "low", - "high", - "max" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, - "attachment": false, + "attachment": true, "open_weights": true, - "release_date": "2026-08-13", - "last_updated": "2026-08-22", + "release_date": "2026-09-22", + "last_updated": "2026-09-22", "type": "chat" }, { - "id": "deepseek/deepseek-v4-flash-0731:thinking", - "name": "DeepSeek V4 Flash 0731 (Thinking)", - "display_name": "DeepSeek V4 Flash 0731 (Thinking)", + "id": "mimo-v2.5-pro", + "name": "MiMo-V2.5-Pro", + "display_name": "MiMo-V2.5-Pro", "modalities": { "input": [ "text" @@ -251101,29 +250919,32 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "none", - "low", - "high", - "max" + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, "attachment": false, "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-07-31", - "last_updated": "2026-07-31", + "knowledge": "2024-12", + "release_date": "2026-04-22", + "last_updated": "2026-04-22", "type": "chat" }, { - "id": "deepseek/deepseek-v4.1-flash-uncensored", - "name": "DeepSeek V4.1 Flash Uncensored", - "display_name": "DeepSeek V4.1 Flash Uncensored", + "id": "mimo-v2.6-flash", + "name": "MiMo-V2.6-Flash", + "display_name": "MiMo-V2.6-Flash", "modalities": { "input": [ "text", - "image" + "image", + "audio", + "video" ], "output": [ "text" @@ -251131,8 +250952,9 @@ }, "limit": { "context": 1048576, - "output": 393216 + "output": 131072 }, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -251140,67 +250962,57 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "none", - "low", - "medium", - "high" + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, "attachment": true, "open_weights": true, - "release_date": "2026-10-01", - "last_updated": "2026-10-01", + "release_date": "2026-09-22", + "last_updated": "2026-09-22", "type": "chat" }, { - "id": "deepseek/deepseek-v4.1-flash:thinking", - "name": "DeepSeek V4.1 Flash Thinking", - "display_name": "DeepSeek V4.1 Flash Thinking", + "id": "mimo-v2.5-tts-voicedesign", + "name": "MiMo-V2.5-TTS-VoiceDesign", + "display_name": "MiMo-V2.5-TTS-VoiceDesign", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ - "text" + "audio" ] }, "limit": { - "context": 1000000, - "output": 384000 + "context": 8192, + "output": 8192 }, - "temperature": true, - "tool_call": true, + "temperature": false, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "none", - "low", - "high", - "max" - ] - } + "supported": false }, - "attachment": true, - "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-09-10", - "last_updated": "2026-09-10", - "type": "chat" + "attachment": false, + "open_weights": false, + "release_date": "2026-04-22", + "last_updated": "2026-04-22" }, { - "id": "deepseek/deepseek-v4-flash-0423", - "name": "DeepSeek V4 Flash 0423", - "display_name": "DeepSeek V4 Flash 0423", + "id": "mimo-v2.5", + "name": "MiMo-V2.5", + "display_name": "MiMo-V2.5", "modalities": { "input": [ - "text" + "text", + "image", + "audio", + "video" ], "output": [ "text" @@ -251208,7 +251020,7 @@ }, "limit": { "context": 1048576, - "output": 384000 + "output": 131072 }, "temperature": true, "tool_call": true, @@ -251218,105 +251030,92 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "none", - "low", - "high", - "max" + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, - "attachment": false, + "attachment": true, "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-04-23", - "last_updated": "2026-04-23", + "knowledge": "2024-12", + "release_date": "2026-04-22", + "last_updated": "2026-04-22", "type": "chat" }, { - "id": "deepseek/deepseek-v4.1-flash-1017-uncensored", - "name": "DeepSeek V4.1 Flash 1017 Uncensored", - "display_name": "DeepSeek V4.1 Flash 1017 Uncensored", + "id": "mimo-v2.5-tts-voiceclone", + "name": "MiMo-V2.5-TTS-VoiceClone", + "display_name": "MiMo-V2.5-TTS-VoiceClone", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ - "text" + "audio" ] }, "limit": { - "context": 1048576, - "output": 393216 + "context": 8192, + "output": 8192 }, - "tool_call": true, + "temperature": false, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "none", - "low", - "high", - "max" - ] - } + "supported": false }, - "attachment": true, - "open_weights": true, - "release_date": "2026-10-27", - "last_updated": "2026-10-27", - "type": "chat" + "attachment": false, + "open_weights": false, + "release_date": "2026-04-22", + "last_updated": "2026-04-22" }, { - "id": "deepseek/deepseek-v4.1-flash-0910-uncensored", - "name": "DeepSeek V4.1 Flash 0910 Uncensored", - "display_name": "DeepSeek V4.1 Flash 0910 Uncensored", + "id": "mimo-v2.5-tts", + "name": "MiMo-V2.5-TTS", + "display_name": "MiMo-V2.5-TTS", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ - "text" + "audio" ] }, "limit": { - "context": 524288, - "output": 32768 + "context": 8192, + "output": 8192 }, - "tool_call": true, + "temperature": false, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "none", - "low", - "high", - "max" - ] - } + "supported": false }, - "attachment": true, - "open_weights": true, - "release_date": "2026-10-27", - "last_updated": "2026-10-27", - "type": "chat" - }, + "attachment": false, + "open_weights": false, + "release_date": "2026-04-22", + "last_updated": "2026-04-22" + } + ] + }, + "volcengine-coding-plan": { + "id": "volcengine-coding-plan", + "name": "Volcengine Ark Coding Plan", + "display_name": "Volcengine Ark Coding Plan", + "api": "https://ark.cn-beijing.volces.com/api/coding/v3", + "doc": "https://www.volcengine.com/docs/82379/1928261", + "models": [ { - "id": "deepseek/deepseek-v4.1-flash-1017:thinking", - "name": "DeepSeek V4.1 Flash 1017 Thinking", - "display_name": "DeepSeek V4.1 Flash 1017 Thinking", + "id": "kimi-k3", + "name": "Kimi K3", + "display_name": "Kimi K3", "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ "text" @@ -251324,9 +251123,9 @@ }, "limit": { "context": 1048576, - "output": 393216 + "output": 131072 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -251334,8 +251133,14 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ - "none", "low", "high", "max" @@ -251344,26 +251149,27 @@ }, "attachment": true, "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-10-17", - "last_updated": "2026-10-17", + "release_date": "2026-07-16", + "last_updated": "2026-07-16", "type": "chat" }, { - "id": "deepseek/deepseek-v4-flash-0423:thinking", - "name": "DeepSeek V4 Flash 0423 (Thinking)", - "display_name": "DeepSeek V4 Flash 0423 (Thinking)", + "id": "doubao-seed-2.0-lite", + "name": "Seed 2.0 Lite", + "display_name": "Seed 2.0 Lite", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 384000 + "context": 256000, + "output": 32000 }, "temperature": true, "tool_call": true, @@ -251373,66 +251179,86 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ "none", "low", - "high", - "max" + "high" ] } }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-04-23", - "last_updated": "2026-04-23", + "attachment": true, + "open_weights": false, + "release_date": "2026-02-14", + "last_updated": "2026-02-14", "type": "chat" }, { - "id": "deepseek/deepseek-v3.2:thinking", - "name": "DeepSeek V3.2 Thinking", - "display_name": "DeepSeek V3.2 Thinking", + "id": "kimi-k2.7-code", + "name": "Kimi K2.7 Code", + "display_name": "Kimi K2.7 Code", "modalities": { "input": [ "text", - "pdf" + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 163000, - "output": 65536 + "context": 262144, + "output": 262144 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, "default": true }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } + }, "attachment": true, "open_weights": true, - "knowledge": "2024-07", - "release_date": "2025-12-01", - "last_updated": "2025-12-01", + "knowledge": "2025-01", + "release_date": "2026-06-12", + "last_updated": "2026-06-12", "type": "chat" }, { - "id": "deepseek/deepseek-v4-flash:thinking", - "name": "DeepSeek V4 Flash (Thinking)", - "display_name": "DeepSeek V4 Flash (Thinking)", + "id": "glm-5.3-flash", + "name": "GLM-5.3-Flash", + "display_name": "GLM-5.3-Flash", "modalities": { "input": [ - "text" + "text", + "image", + "video", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 384000 + "context": 1000000, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -251442,39 +251268,45 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ - "none", + "low", "high", "max" ] } }, - "attachment": false, + "attachment": true, "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-04-24", - "last_updated": "2026-04-24", + "release_date": "2026-08-26", + "last_updated": "2026-08-26", "type": "chat" }, { - "id": "google/gemini-pro-latest", - "name": "Gemini Pro Latest", - "display_name": "Gemini Pro Latest", + "id": "doubao-seed-2.1-turbo", + "name": "Seed 2.1 Turbo", + "display_name": "Seed 2.1 Turbo", "modalities": { "input": [ "text", "image", - "video", - "audio" + "video" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 256000, + "output": 256000 }, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -251482,37 +251314,41 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ + "none", "low", - "medium", "high" ] } }, "attachment": true, "open_weights": false, - "release_date": "2026-03-29", - "last_updated": "2026-03-29", + "release_date": "2026-06-23", + "last_updated": "2026-06-23", "type": "chat" }, { - "id": "google/gemini-3-flash-preview", - "name": "Gemini 3 Flash (Preview)", - "display_name": "Gemini 3 Flash (Preview)", + "id": "deepseek-v4-pro", + "name": "DeepSeek V4 Pro", + "display_name": "DeepSeek V4 Pro", "modalities": { "input": [ - "text", - "image", - "video", - "audio" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 1000000, + "output": 384000 }, "temperature": true, "tool_call": true, @@ -251524,46 +251360,46 @@ "reasoning": { "supported": true, "default_enabled": true, - "mode": "level", - "level": "high", - "level_options": [ - "minimal", + "mode": "effort", + "effort": "high", + "effort_options": [ "low", - "medium", - "high" + "high", + "max" ], + "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ - "thought_signatures" + "thinking_blocks" + ], + "notes": [ + "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-01", - "release_date": "2025-12-17", - "last_updated": "2025-12-17", + "attachment": false, + "open_weights": true, + "knowledge": "2025-05", + "release_date": "2026-04-24", + "last_updated": "2026-04-24", "type": "chat" }, { - "id": "google/gemini-3.1-pro-preview-high", - "name": "Gemini 3.1 Pro (Preview High)", - "display_name": "Gemini 3.1 Pro (Preview High)", + "id": "glm-5.3", + "name": "GLM-5.3", + "display_name": "GLM-5.3", "modalities": { "input": [ - "text", - "image", - "video", - "audio" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 1000000, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -251574,46 +251410,40 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "level", - "level": "high", - "level_options": [ - "low", - "high" - ], + "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ - "thought_signatures" + "thinking_blocks" + ], + "effort_options": [ + "low", + "high", + "max" ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-01", - "release_date": "2026-02-19", - "last_updated": "2026-02-19", + "attachment": false, + "open_weights": true, + "release_date": "2026-08-14", + "last_updated": "2026-08-14", "type": "chat" }, { - "id": "google/gemini-3.5-flash-lite", - "name": "Gemini 3.5 Flash Lite", - "display_name": "Gemini 3.5 Flash Lite", + "id": "deepseek-v4-flash", + "name": "DeepSeek V4 Flash", + "display_name": "DeepSeek V4 Flash", "modalities": { "input": [ - "text", - "image", - "video", - "audio", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 1000000, + "output": 384000 }, "temperature": true, "tool_call": true, @@ -251624,38 +251454,49 @@ "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "high", "effort_options": [ - "minimal", "low", - "medium", - "high" - ] - } - }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-03", - "release_date": "2026-07-21", - "last_updated": "2026-07-21", + "high", + "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." + ] + } + }, + "attachment": false, + "open_weights": true, + "knowledge": "2025-05", + "release_date": "2026-04-24", + "last_updated": "2026-04-24", "type": "chat" }, { - "id": "google/gemini-3.1-pro-preview-low", - "name": "Gemini 3.1 Pro (Preview Low)", - "display_name": "Gemini 3.1 Pro (Preview Low)", + "id": "doubao-seed-evolving", + "name": "Seed Evolving", + "display_name": "Seed Evolving", "modalities": { "input": [ "text", "image", - "audio" + "video" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 256000, + "output": 256000 }, "temperature": true, "tool_call": true, @@ -251666,37 +251507,34 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "level", - "level": "high", - "level_options": [ - "low", - "high" - ], + "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ - "thought_signatures" + "thinking_blocks" + ], + "effort_options": [ + "none", + "low", + "high" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-01", - "release_date": "2026-02-19", - "last_updated": "2026-02-19", + "release_date": "2026-06-23", + "last_updated": "2026-06-23", "type": "chat" }, { - "id": "google/gemini-3-flash-preview-thinking", - "name": "Gemini 3 Flash Thinking", - "display_name": "Gemini 3 Flash Thinking", + "id": "minimax-m3", + "name": "MiniMax-M3", + "display_name": "MiniMax-M3", "modalities": { "input": [ "text", "image", - "video", - "audio" + "video" ], "output": [ "text" @@ -251704,10 +251542,10 @@ }, "limit": { "context": 1048576, - "output": 65536 + "output": 512000 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { "supported": true, "default": true @@ -251715,37 +251553,38 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "level", - "level": "high", - "level_options": [ - "minimal", - "low", - "medium", - "high" - ], + "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ - "thought_signatures" + "thinking_blocks" ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2025-01", - "release_date": "2025-12-17", - "last_updated": "2025-12-17", + "open_weights": true, + "release_date": "2026-06-01", + "last_updated": "2026-06-01", "type": "chat" - }, + } + ] + }, + "vultr": { + "id": "vultr", + "name": "Vultr", + "display_name": "Vultr", + "api": "https://api.vultrinference.com/v1", + "doc": "https://api.vultrinference.com/", + "models": [ { - "id": "google/gemma-4-26b-a4b-it:thinking", - "name": "Gemma 4 26B A4B Thinking", - "display_name": "Gemma 4 26B A4B Thinking", + "id": "mimo-v2.6-flash-rl", + "name": "MiMo-V2.6-Flash", + "display_name": "MiMo-V2.6-Flash", "modalities": { "input": [ "text", "image", + "audio", "video" ], "output": [ @@ -251753,7 +251592,7 @@ ] }, "limit": { - "context": 262144, + "context": 1048576, "output": 131072 }, "temperature": true, @@ -251764,65 +251603,42 @@ }, "attachment": true, "open_weights": true, - "release_date": "2026-04-02", - "last_updated": "2026-04-02", + "release_date": "2026-09-22", + "last_updated": "2026-09-22", "type": "chat" }, { - "id": "google/gemini-3.5-flash-thinking", - "name": "Gemini 3.5 Flash Thinking", - "display_name": "Gemini 3.5 Flash Thinking", + "id": "glm-5.x-menthol", + "name": "GLM-5", + "display_name": "GLM-5", "modalities": { "input": [ - "text", - "image", - "video", - "audio" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 202752, + "output": 131072 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "level", - "level": "high", - "level_options": [ - "minimal", - "low", - "medium", - "high" - ], - "summaries": true, - "visibility": "summary", - "continuation": [ - "thought_signatures" - ] - } - }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-01", - "release_date": "2026-05-19", - "last_updated": "2026-05-19", + "attachment": false, + "open_weights": true, + "release_date": "2026-02-12", + "last_updated": "2026-02-12", "type": "chat" }, { - "id": "google/gemma-4-31b-it", - "name": "Gemma 4 31B", - "display_name": "Gemma 4 31B", + "id": "glm-5.3-flash", + "name": "GLM-5.3-Flash", + "display_name": "GLM-5.3-Flash", "modalities": { "input": [ "text", @@ -251834,7 +251650,7 @@ ] }, "limit": { - "context": 262144, + "context": 1048576, "output": 131072 }, "temperature": true, @@ -251845,24 +251661,29 @@ }, "extra_capabilities": { "reasoning": { - "supported": true + "supported": true, + "effort_options": [ + "low", + "high", + "max" + ] } }, "attachment": true, "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-04-02", - "last_updated": "2026-04-02", + "release_date": "2026-08-26", + "last_updated": "2026-08-26", "type": "chat" }, { - "id": "google/gemma-4-31b-it:thinking", - "name": "Gemma 4 31B Thinking", - "display_name": "Gemma 4 31B Thinking", + "id": "mimo-v2.6-pro-rl", + "name": "MiMo-V2.6-Pro", + "display_name": "MiMo-V2.6-Pro", "modalities": { "input": [ "text", "image", + "audio", "video" ], "output": [ @@ -251870,7 +251691,7 @@ ] }, "limit": { - "context": 262144, + "context": 1048576, "output": 131072 }, "temperature": true, @@ -251881,66 +251702,52 @@ }, "attachment": true, "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-04-02", - "last_updated": "2026-04-02", + "release_date": "2026-09-22", + "last_updated": "2026-09-22", "type": "chat" }, { - "id": "google/gemini-3.1-flash-lite", - "name": "Gemini 3.1 Flash Lite", - "display_name": "Gemini 3.1 Flash Lite", + "id": "nemotron-3.5-content-safety", + "name": "Nemotron 3.5 Content Safety", + "display_name": "Nemotron 3.5 Content Safety", "modalities": { "input": [ "text", - "image", - "video", - "audio", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 131072, + "output": 8192 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { "supported": true, "default": true }, "extra_capabilities": { "reasoning": { - "supported": true, - "effort_options": [ - "minimal", - "low", - "medium", - "high" - ] + "supported": true } }, "attachment": true, - "open_weights": false, - "knowledge": "2025-01", - "release_date": "2026-05-07", - "last_updated": "2026-05-07", + "open_weights": true, + "release_date": "2026-06-02", + "last_updated": "2026-06-02", "type": "chat" }, { - "id": "google/gemini-3.7-flash", - "name": "Gemini 3.7 Flash", - "display_name": "Gemini 3.7 Flash", + "id": "deepseek-v4.1-flash", + "name": "DeepSeek V4.1 Flash", + "display_name": "DeepSeek V4.1 Flash", "modalities": { "input": [ "text", - "image", - "video", - "audio", - "pdf" + "image" ], "output": [ "text" @@ -251948,7 +251755,7 @@ }, "limit": { "context": 1048576, - "output": 65536 + "output": 384000 }, "temperature": true, "tool_call": true, @@ -251959,36 +251766,29 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "level", - "level": "high", - "level_options": [ - "minimal", + "effort_options": [ "low", - "medium", - "high" - ], - "summaries": true, - "visibility": "summary", - "continuation": [ - "thought_signatures" + "high", + "max" ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2026-03", - "release_date": "2026-08-13", - "last_updated": "2026-08-13", + "open_weights": true, + "knowledge": "2025-05", + "release_date": "2026-09-10", + "last_updated": "2026-09-10", "type": "chat" }, { - "id": "google/gemma4-31b-splituntied", - "name": "Gemma 4 31B Split-Untied", - "display_name": "Gemma 4 31B Split-Untied", + "id": "qwen3.8-27b", + "name": "Qwen3.8 27B", + "display_name": "Qwen3.8 27B", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" @@ -251998,20 +251798,31 @@ "context": 262144, "output": 32768 }, + "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "low", + "medium", + "xhigh" + ] + } + }, + "attachment": true, "open_weights": true, - "release_date": "2026-09-17", - "last_updated": "2026-09-17", + "release_date": "2026-08-14", + "last_updated": "2026-08-14", "type": "chat" }, { - "id": "google/gemma-4-26b-a4b-it", - "name": "Gemma 4 26B A4B", - "display_name": "Gemma 4 26B A4B", + "id": "qwen3.8-flash-next", + "name": "Qwen3.8 Flash Next", + "display_name": "Qwen3.8 Flash Next", "modalities": { "input": [ "text", @@ -252034,25 +251845,26 @@ }, "extra_capabilities": { "reasoning": { - "supported": true + "effort_options": [ + "low", + "medium", + "xhigh" + ] } }, "attachment": true, "open_weights": true, - "release_date": "2026-04-02", - "last_updated": "2026-04-02", + "release_date": "2026-08-27", + "last_updated": "2026-08-27", "type": "chat" }, { - "id": "google/gemini-3.1-pro-preview-customtools", - "name": "Gemini 3.1 Pro (Preview Custom Tools)", - "display_name": "Gemini 3.1 Pro (Preview Custom Tools)", + "id": "glm-5.3", + "name": "GLM-5.3", + "display_name": "GLM-5.3", "modalities": { "input": [ - "text", - "image", - "video", - "audio" + "text" ], "output": [ "text" @@ -252060,7 +251872,7 @@ }, "limit": { "context": 1048576, - "output": 65536 + "output": 131072 }, "temperature": true, "tool_call": true, @@ -252071,45 +251883,36 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "level", - "level": "high", - "level_options": [ + "effort_options": [ "low", - "high" - ], - "summaries": true, - "visibility": "summary", - "continuation": [ - "thought_signatures" + "high", + "max" ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-01", - "release_date": "2026-02-19", - "last_updated": "2026-02-19", + "attachment": false, + "open_weights": true, + "release_date": "2026-08-14", + "last_updated": "2026-08-14", "type": "chat" }, { - "id": "google/gemini-flash-latest", - "name": "Gemini Flash Latest", - "display_name": "Gemini Flash Latest", + "id": "nemotron-3-nano-omni-30b-a3b-reasoning", + "name": "Nemotron 3 Nano Omni 30B A3B Reasoning", + "display_name": "Nemotron 3 Nano Omni 30B A3B Reasoning", "modalities": { "input": [ "text", "image", "video", - "audio", - "pdf" + "audio" ], "output": [ "text" ] }, "limit": { - "context": 1048576, + "context": 262144, "output": 65536 }, "temperature": true, @@ -252120,31 +251923,22 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "minimal", - "low", - "medium", - "high" - ] + "supported": true } }, "attachment": true, - "open_weights": false, - "knowledge": "2026-03", - "release_date": "2026-08-13", - "last_updated": "2026-08-13", + "open_weights": true, + "release_date": "2026-04-28", + "last_updated": "2026-04-28", "type": "chat" }, { - "id": "google/gemini-3.1-pro-preview", - "name": "Gemini 3.1 Pro (Preview)", - "display_name": "Gemini 3.1 Pro (Preview)", + "id": "glm-5.2", + "name": "GLM-5.2", + "display_name": "GLM-5.2", "modalities": { "input": [ - "text", - "image", - "video", - "audio" + "text" ], "output": [ "text" @@ -252152,7 +251946,7 @@ }, "limit": { "context": 1048576, - "output": 65536 + "output": 131072 }, "temperature": true, "tool_call": true, @@ -252163,46 +251957,36 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "level", - "level": "high", - "level_options": [ - "low", - "high" - ], - "summaries": true, - "visibility": "summary", - "continuation": [ - "thought_signatures" + "effort_options": [ + "none", + "high", + "max" ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-01", - "release_date": "2026-02-19", - "last_updated": "2026-02-19", + "attachment": false, + "open_weights": true, + "release_date": "2026-06-13", + "last_updated": "2026-06-13", "type": "chat" }, { - "id": "google/gemini-3.8-flash", - "name": "Gemini 3.8 Flash", - "display_name": "Gemini 3.8 Flash", + "id": "minimax-m3", + "name": "MiniMax-M3", + "display_name": "MiniMax-M3", "modalities": { "input": [ "text", "image", - "video", - "audio", - "pdf" + "video" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 524288, + "output": 512000 }, "temperature": true, "tool_call": true, @@ -252212,68 +251996,50 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "level", - "level": "high", - "level_options": [ - "minimal", - "low", - "medium", - "high" - ], - "summaries": true, - "visibility": "summary", - "continuation": [ - "thought_signatures" - ] + "supported": true } }, "attachment": true, - "open_weights": false, - "release_date": "2026-09-02", - "last_updated": "2026-09-02", + "open_weights": true, + "release_date": "2026-06-01", + "last_updated": "2026-06-01", "type": "chat" }, { - "id": "google/gemma-4-26b-a4b-it-cybersecurity", - "name": "Gemma 4 26B A4B Cybersecurity", - "display_name": "Gemma 4 26B A4B Cybersecurity", + "id": "laguna-s-2.1", + "name": "Laguna S 2.1", + "display_name": "Laguna S 2.1", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 262144, + "context": 1048576, "output": 32768 }, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, "default": true }, - "attachment": true, + "attachment": false, "open_weights": true, - "release_date": "2026-09-19", - "last_updated": "2026-09-19", + "release_date": "2026-07-21", + "last_updated": "2026-07-21", "type": "chat" }, { - "id": "google/gemini-4-argon", - "name": "Gemini 4 Argon", - "display_name": "Gemini 4 Argon", + "id": "deepseek-v4-flash-0731", + "name": "DeepSeek V4 Flash 0731", + "display_name": "DeepSeek V4 Flash 0731", "modalities": { "input": [ - "text", - "image", - "video", - "audio", - "pdf" + "text" ], "output": [ "text" @@ -252281,9 +252047,9 @@ }, "limit": { "context": 1048576, - "output": 65536 + "output": 384000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -252291,39 +252057,38 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, "effort_options": [ "low", - "medium", - "high" + "high", + "max" ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-03", - "release_date": "2026-10-13", - "last_updated": "2026-10-13", + "attachment": false, + "open_weights": true, + "knowledge": "2025-05", + "release_date": "2026-07-31", + "last_updated": "2026-07-31", "type": "chat" }, { - "id": "google/gemini-3.6-flash", - "name": "Gemini 3.6 Flash", - "display_name": "Gemini 3.6 Flash", + "id": "muse-glimmer-30b", + "name": "Muse Glimmer 30B", + "display_name": "Muse Glimmer 30B", "modalities": { "input": [ "text", "image", - "video", - "audio", - "pdf" + "video" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 131072, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -252333,34 +252098,34 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "level", - "level": "high", - "level_options": [ - "minimal", + "effort_options": [ "low", "medium", - "high" - ], - "summaries": true, - "visibility": "summary", - "continuation": [ - "thought_signatures" + "high", + "xhigh" ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2026-03", - "release_date": "2026-07-21", - "last_updated": "2026-07-21", + "open_weights": true, + "knowledge": "2026-01-04", + "release_date": "2026-08-10", + "last_updated": "2026-08-10", "type": "chat" - }, + } + ] + }, + "chutes": { + "id": "chutes", + "name": "Chutes", + "display_name": "Chutes", + "api": "https://llm.chutes.ai/v1", + "doc": "https://llm.chutes.ai/v1/models", + "models": [ { - "id": "google/diffusiongemma", - "name": "DiffusionGemma", - "display_name": "DiffusionGemma", + "id": "Qwen/Qwen3.8-Max-0902", + "name": "Qwen3.8 Max 0902", + "display_name": "Qwen3.8 Max 0902", "modalities": { "input": [ "text" @@ -252370,47 +252135,35 @@ ] }, "limit": { - "context": 262144, - "output": 32768 + "context": 8192, + "output": 8192 }, "temperature": true, "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "none", - "xhigh" - ] - } + "supported": false }, "attachment": false, "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-06-09", - "last_updated": "2026-07-15", + "release_date": "2026-10-20", + "last_updated": "2026-10-25", "type": "chat" }, { - "id": "google/gemini-3.5-flash", - "name": "Gemini 3.5 Flash", - "display_name": "Gemini 3.5 Flash", + "id": "Qwen/Qwen3.6-27B-TEE", + "name": "Qwen3.6 27B TEE", + "display_name": "Qwen3.6 27B TEE", "modalities": { "input": [ "text", - "image", - "video", - "audio" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1048576, + "context": 262144, "output": 65536 }, "temperature": true, @@ -252422,79 +252175,60 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "level", - "level": "high", - "level_options": [ - "minimal", - "low", - "medium", - "high" - ], + "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ - "thought_signatures" + "thinking_blocks" ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2025-01", - "release_date": "2026-05-19", - "last_updated": "2026-05-19", + "open_weights": true, + "release_date": "2026-04-22", + "last_updated": "2026-04-22", "type": "chat" }, { - "id": "google/gemini-flash-lite-latest", - "name": "Gemini Flash Lite Latest", - "display_name": "Gemini Flash Lite Latest", + "id": "Qwen/Qwen3.8-2.4T-A95B", + "name": "Qwen3.8 2.4T A95B", + "display_name": "Qwen3.8 2.4T A95B", "modalities": { "input": [ - "text", - "image", - "video", - "audio", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 8192, + "output": 8192 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true + "supported": true }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "minimal", - "low", - "medium", - "high" - ] + "supported": true } }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-03", - "release_date": "2026-07-21", - "last_updated": "2026-07-21", + "attachment": false, + "open_weights": true, + "release_date": "2026-10-26", + "last_updated": "2026-10-26", "type": "chat" }, { - "id": "arcee-ai/trinity-large-thinking", - "name": "Trinity Large Thinking", - "display_name": "Trinity Large Thinking", + "id": "Qwen/Qwen3.8-27B-TEE", + "name": "Qwen3.8 27B TEE", + "display_name": "Qwen3.8 27B TEE", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" @@ -252502,7 +252236,7 @@ }, "limit": { "context": 262144, - "output": 80000 + "output": 65536 }, "temperature": true, "tool_call": true, @@ -252510,97 +252244,86 @@ "supported": true, "default": true }, - "attachment": false, + "attachment": true, "open_weights": true, - "release_date": "2026-04-01", - "last_updated": "2026-05-28", + "release_date": "2026-08-14", + "last_updated": "2026-08-14", "type": "chat" }, { - "id": "nanogpt/coding-router:high", - "name": "Coding Router High", - "display_name": "Coding Router High", + "id": "Qwen/Qwen3.5-397B-A17B-TEE", + "name": "Qwen3.5 397B A17B TEE", + "display_name": "Qwen3.5 397B A17B TEE", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 262144, + "output": 65536 }, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, "default": true }, - "attachment": false, - "open_weights": false, - "release_date": "2026-05-12", - "last_updated": "2026-05-12", - "type": "chat" - }, - { - "id": "nanogpt/coding-router:max", - "name": "Coding Router Max", - "display_name": "Coding Router Max", - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 1000000, - "output": 128000 - }, - "tool_call": true, - "reasoning": { - "supported": true, - "default": true + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } }, - "attachment": false, - "open_weights": false, - "release_date": "2026-05-12", - "last_updated": "2026-05-12", + "attachment": true, + "open_weights": true, + "release_date": "2026-02-15", + "last_updated": "2026-02-15", "type": "chat" }, { - "id": "nanogpt/coding-router", - "name": "Coding Router", - "display_name": "Coding Router", + "id": "moonshotai/Kimi-K3-TEE", + "name": "Kimi K3 TEE", + "display_name": "Kimi K3 TEE", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 1048576, + "output": 65535 }, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, "default": true }, - "attachment": false, - "open_weights": false, - "release_date": "2026-05-12", - "last_updated": "2026-05-12", + "attachment": true, + "open_weights": true, + "release_date": "2026-07-16", + "last_updated": "2026-07-16", "type": "chat" }, { - "id": "nanogpt/coding-router:low", - "name": "Coding Router Low", - "display_name": "Coding Router Low", + "id": "moonshotai/Kimi-K2.7-Code", + "name": "Kimi K2.7 Code", + "display_name": "Kimi K2.7 Code", "modalities": { "input": [ "text" @@ -252610,64 +252333,42 @@ ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 8192, + "output": 8192 }, - "tool_call": true, + "temperature": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "attachment": false, - "open_weights": false, - "release_date": "2026-05-12", - "last_updated": "2026-05-12", - "type": "chat" - }, - { - "id": "nanogpt/coding-router:medium", - "name": "Coding Router Medium", - "display_name": "Coding Router Medium", - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 1000000, - "output": 128000 + "supported": true }, - "tool_call": true, - "reasoning": { - "supported": true, - "default": true + "extra_capabilities": { + "reasoning": { + "supported": true + } }, "attachment": false, - "open_weights": false, - "release_date": "2026-05-12", - "last_updated": "2026-05-12", + "open_weights": true, + "release_date": "2026-10-26", + "last_updated": "2026-10-26", "type": "chat" }, { - "id": "thinkingmachines/Inkling-Small:thinking", - "name": "Inkling Small Thinking", - "display_name": "Inkling Small Thinking", + "id": "moonshotai/Kimi-K2.6-TEE", + "name": "Kimi K2.6 TEE", + "display_name": "Kimi K2.6 TEE", "modalities": { "input": [ "text", "image", - "audio" + "video" ], "output": [ "text" ] }, "limit": { - "context": 524288, - "output": 32768 + "context": 262144, + "output": 65535 }, "temperature": true, "tool_call": true, @@ -252677,96 +252378,96 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "minimal", - "low", - "medium", - "high", - "max" + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, "attachment": true, "open_weights": true, - "release_date": "2026-07-30", - "last_updated": "2026-07-30", + "knowledge": "2025-01", + "release_date": "2026-04-20", + "last_updated": "2026-04-20", "type": "chat" }, { - "id": "thinkingmachines/Inkling-Small", - "name": "Inkling Small", - "display_name": "Inkling Small", + "id": "zai-org/GLM-5.3", + "name": "GLM 5.3", + "display_name": "GLM 5.3", "modalities": { "input": [ - "text", - "image", - "audio" + "text" ], "output": [ "text" ] }, "limit": { - "context": 524288, - "output": 32768 + "context": 8192, + "output": 8192 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": false + "supported": true }, - "attachment": true, + "extra_capabilities": { + "reasoning": { + "supported": true + } + }, + "attachment": false, "open_weights": true, - "release_date": "2026-07-30", - "last_updated": "2026-07-30", + "release_date": "2026-10-26", + "last_updated": "2026-10-26", "type": "chat" }, { - "id": "thinkingmachines/inkling", - "name": "Inkling", - "display_name": "Inkling", + "id": "zai-org/GLM-5.2-Fast", + "name": "GLM 5.2 Fast", + "display_name": "GLM 5.2 Fast", "modalities": { "input": [ - "text", - "image", - "audio" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048000, - "output": 32768 + "context": 8192, + "output": 8192 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { "supported": false }, - "attachment": true, + "attachment": false, "open_weights": true, - "release_date": "2026-07-15", - "last_updated": "2026-07-15", + "release_date": "2026-10-26", + "last_updated": "2026-10-26", "type": "chat" }, { - "id": "thinkingmachines/inkling:thinking", - "name": "Inkling Thinking", - "display_name": "Inkling Thinking", + "id": "zai-org/GLM-5.2-TEE", + "name": "GLM 5.2 TEE", + "display_name": "GLM 5.2 TEE", "modalities": { "input": [ - "text", - "image", - "audio" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1040000, - "output": 32768 + "context": 1048576, + "output": 65535 }, "temperature": true, "tool_call": true, @@ -252776,25 +252477,25 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "minimal", - "low", - "medium", - "high", - "xhigh" + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, - "attachment": true, + "attachment": false, "open_weights": true, - "release_date": "2026-07-15", - "last_updated": "2026-07-15", + "release_date": "2026-06-13", + "last_updated": "2026-06-13", "type": "chat" }, { - "id": "meganova-ai/manta-flash-1.0", - "name": "Manta Flash 1.0", - "display_name": "Manta Flash 1.0", + "id": "zai-org/GLM-5.1-TEE", + "name": "GLM 5.1 TEE", + "display_name": "GLM 5.1 TEE", "modalities": { "input": [ "text" @@ -252804,75 +252505,66 @@ ] }, "limit": { - "context": 16384, - "output": 16384 + "context": 202752, + "output": 65535 }, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { - "supported": false - }, - "attachment": false, - "open_weights": false, - "release_date": "2025-09-20", - "last_updated": "2025-12-20", - "type": "chat" - }, - { - "id": "meganova-ai/manta-mini-1.0", - "name": "Manta Mini 1.0", - "display_name": "Manta Mini 1.0", - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 8192, - "output": 8192 + "supported": true, + "default": true }, - "tool_call": false, - "reasoning": { - "supported": false + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } }, "attachment": false, - "open_weights": false, - "release_date": "2025-09-20", - "last_updated": "2025-12-20", + "open_weights": true, + "release_date": "2026-04-07", + "last_updated": "2026-04-07", "type": "chat" }, { - "id": "meganova-ai/manta-pro-1.0", - "name": "Manta Pro 1.0", - "display_name": "Manta Pro 1.0", + "id": "google/gemma-4-31B-turbo-TEE", + "name": "gemma 4 31B turbo TEE", + "display_name": "gemma 4 31B turbo TEE", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 65536, - "output": 32768 + "context": 131072, + "output": 65536 }, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, - "open_weights": false, - "release_date": "2025-09-20", - "last_updated": "2025-12-20", + "attachment": true, + "open_weights": true, + "knowledge": "2025-01", + "release_date": "2026-04-02", + "last_updated": "2026-04-02", "type": "chat" }, { - "id": "LLM360/K2-Think", - "name": "K2-Think", - "display_name": "K2-Think", + "id": "deepseek-ai/DeepSeek-V3.2-TEE", + "name": "DeepSeek V3.2 TEE", + "display_name": "DeepSeek V3.2 TEE", "modalities": { "input": [ "text" @@ -252882,23 +252574,37 @@ ] }, "limit": { - "context": 128000, - "output": 32768 + "context": 131072, + "output": 65536 }, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } }, "attachment": false, "open_weights": true, - "release_date": "2025-07-26", - "last_updated": "2025-07-26", + "knowledge": "2024-07", + "release_date": "2025-12-01", + "last_updated": "2025-12-01", "type": "chat" }, { - "id": "speakleash/bielik-11b-v3.0-instruct", - "name": "Bielik 11B v3.0", - "display_name": "Bielik 11B v3.0", + "id": "deepseek-ai/DeepSeek-V4-Flash-0731-TEE", + "name": "DeepSeek V4 Flash 0731 TEE", + "display_name": "DeepSeek V4 Flash 0731 TEE", "modalities": { "input": [ "text" @@ -252908,63 +252614,53 @@ ] }, "limit": { - "context": 32768, - "output": 32768 + "context": 1048576, + "output": 131072 }, + "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, "attachment": false, "open_weights": true, - "release_date": "2026-10-15", - "last_updated": "2026-10-15", + "knowledge": "2025-05", + "release_date": "2026-07-31", + "last_updated": "2026-07-31", "type": "chat" }, { - "id": "openai/gpt-sol-latest", - "name": "GPT Sol Latest", - "display_name": "GPT Sol Latest", + "id": "deepseek-ai/DeepSeek-V4.1-Flash-1017", + "name": "DeepSeek V4.1 Flash 1017", + "display_name": "DeepSeek V4.1 Flash 1017", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 8192, + "output": 8192 }, - "tool_call": true, + "temperature": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" - ] - } + "supported": false }, - "attachment": true, - "open_weights": false, - "release_date": "2026-09-11", - "last_updated": "2026-09-11", + "attachment": false, + "open_weights": true, + "release_date": "2026-10-26", + "last_updated": "2026-10-26", "type": "chat" }, { - "id": "openai/o4-mini-high", - "name": "OpenAI o4-mini high", - "display_name": "OpenAI o4-mini high", + "id": "deepseek-ai/DeepSeek-V4-Pro-0813", + "name": "DeepSeek V4 Pro 0813", + "display_name": "DeepSeek V4 Pro 0813", "modalities": { "input": [ "text" @@ -252974,45 +252670,42 @@ ] }, "limit": { - "context": 200000, - "output": 100000 + "context": 8192, + "output": 8192 }, - "temperature": false, - "tool_call": true, + "temperature": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true + "supported": true }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", - "effort_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + "supported": true } }, "attachment": false, - "open_weights": false, - "knowledge": "2024-05", - "release_date": "2025-04-16", - "last_updated": "2025-04-16", + "open_weights": true, + "release_date": "2026-10-20", + "last_updated": "2026-10-25", "type": "chat" - }, + } + ] + }, + "aimlapi": { + "id": "aimlapi", + "name": "AI/ML API", + "display_name": "AI/ML API", + "api": "https://api.aimlapi.com/v1", + "doc": "https://docs.aimlapi.com", + "models": [ { - "id": "openai/gpt-5.1-codex-max", - "name": "GPT 5.1 Codex Max", - "display_name": "GPT 5.1 Codex Max", + "id": "openai/gpt-5.2-pro", + "name": "GPT-5.2 Pro", + "display_name": "GPT-5.2 Pro", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" @@ -253026,19 +252719,18 @@ "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, + "default_enabled": true, "mode": "effort", - "effort": "none", + "effort": "high", "effort_options": [ - "none", - "low", "medium", - "high" + "high", + "xhigh" ], "verbosity": "medium", "verbosity_options": [ @@ -253046,25 +252738,29 @@ "medium", "high" ], - "visibility": "hidden" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] } }, "attachment": true, "open_weights": false, - "knowledge": "2024-09-30", - "release_date": "2025-11-13", - "last_updated": "2025-11-13", + "knowledge": "2025-08-31", + "release_date": "2025-12-11", + "last_updated": "2025-12-11", "type": "chat" }, { - "id": "openai/gpt-5", - "name": "GPT 5", - "display_name": "GPT 5", + "id": "openai/gpt-5-pro", + "name": "GPT-5 Pro", + "display_name": "GPT-5 Pro", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" @@ -253072,7 +252768,7 @@ }, "limit": { "context": 400000, - "output": 128000 + "output": 272000 }, "temperature": false, "tool_call": true, @@ -253084,34 +252780,33 @@ "reasoning": { "supported": true, "default_enabled": true, - "mode": "effort", - "effort": "medium", - "effort_options": [ - "minimal", - "low", - "medium", - "high" - ], + "mode": "fixed", + "effort": "high", "verbosity": "medium", "verbosity_options": [ "low", "medium", "high" ], - "visibility": "hidden" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] } }, "attachment": true, "open_weights": false, "knowledge": "2024-09-30", - "release_date": "2025-08-07", - "last_updated": "2025-08-07", + "release_date": "2025-10-06", + "last_updated": "2025-10-06", "type": "chat" }, { - "id": "openai/gpt-4o-mini", - "name": "GPT-4o mini", - "display_name": "GPT-4o mini", + "id": "openai/gpt-5.4-nano", + "name": "GPT-5.4 Nano", + "display_name": "GPT-5.4 Nano", "modalities": { "input": [ "text", @@ -253122,52 +252817,23 @@ ] }, "limit": { - "context": 128000, - "output": 16384 - }, - "temperature": true, - "tool_call": false, - "reasoning": { - "supported": false - }, - "attachment": true, - "open_weights": false, - "knowledge": "2023-09", - "release_date": "2024-07-18", - "last_updated": "2024-07-18", - "type": "chat" - }, - { - "id": "openai/gpt-5.5", - "name": "GPT 5.5", - "display_name": "GPT 5.5", - "modalities": { - "input": [ - "text", - "image", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 1050000, + "context": 400000, "output": 128000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, + "default_enabled": false, "mode": "effort", - "effort": "medium", + "effort": "none", "effort_options": [ + "none", "low", "medium", "high", @@ -253184,15 +252850,15 @@ }, "attachment": true, "open_weights": false, - "knowledge": "2025-12-01", - "release_date": "2026-04-23", - "last_updated": "2026-04-23", + "knowledge": "2025-08-31", + "release_date": "2026-03-17", + "last_updated": "2026-03-17", "type": "chat" }, { - "id": "openai/gpt-4.1-nano", - "name": "GPT 4.1 Nano", - "display_name": "GPT 4.1 Nano", + "id": "openai/gpt-6.1-terra", + "name": "GPT-6.1 Terra", + "display_name": "GPT-6.1 Terra", "modalities": { "input": [ "text", @@ -253204,108 +252870,87 @@ ] }, "limit": { - "context": 1047576, - "output": 32768 - }, - "temperature": true, - "tool_call": false, - "reasoning": { - "supported": false - }, - "attachment": true, - "open_weights": false, - "knowledge": "2024-04", - "release_date": "2025-04-14", - "last_updated": "2025-04-14", - "type": "chat" - }, - { - "id": "openai/o1", - "name": "OpenAI o1", - "display_name": "OpenAI o1", - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 200000, - "output": 100000 + "context": 1050000, + "output": 128000 }, "temperature": false, - "tool_call": false, + "tool_call": true, "reasoning": { "supported": true, "default": true }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", "effort_options": [ "low", "medium", "high" - ], - "visibility": "hidden" + ] } }, - "attachment": false, + "attachment": true, "open_weights": false, - "knowledge": "2023-09", - "release_date": "2024-12-05", - "last_updated": "2024-12-05", + "knowledge": "2026-06-01", + "release_date": "2026-10-26", + "last_updated": "2026-10-26", "type": "chat" }, { - "id": "openai/gpt-6.1-terra-pro", - "name": "GPT 6.1 Terra Pro", - "display_name": "GPT 6.1 Terra Pro", + "id": "openai/gpt-5.4-mini", + "name": "GPT-5.4 Mini", + "display_name": "GPT-5.4 Mini", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1050000, + "context": 400000, "output": 128000 }, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": false, + "mode": "effort", + "effort": "none", "effort_options": [ + "none", "low", "medium", "high", - "xhigh", - "max" - ] + "xhigh" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, "attachment": true, "open_weights": false, - "release_date": "2026-10-27", - "last_updated": "2026-10-27", + "knowledge": "2025-08-31", + "release_date": "2026-03-17", + "last_updated": "2026-03-17", "type": "chat" }, { - "id": "openai/gpt-6-astra", - "name": "GPT 6 Astra", - "display_name": "GPT 6 Astra", + "id": "openai/gpt-5-4", + "name": "GPT-5.4", + "display_name": "GPT-5.4", "modalities": { "input": [ "text", @@ -253320,50 +252965,59 @@ "context": 1050000, "output": 128000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": false, + "mode": "effort", + "effort": "none", "effort_options": [ + "none", "low", "medium", "high", - "xhigh", - "max" - ] + "xhigh" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, "attachment": true, "open_weights": false, - "knowledge": "2026-04-30", - "release_date": "2026-09-04", - "last_updated": "2026-09-04", + "knowledge": "2025-08-31", + "release_date": "2026-03-05", + "last_updated": "2026-03-05", "type": "chat" }, { - "id": "openai/gpt-5.4", - "name": "GPT 5.4", - "display_name": "GPT 5.4", + "id": "openai/gpt-5.1-codex-max", + "name": "GPT-5.1-Codex-Max", + "display_name": "GPT-5.1-Codex-Max", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1050000, + "context": 400000, "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -253379,8 +253033,7 @@ "none", "low", "medium", - "high", - "xhigh" + "high" ], "verbosity": "medium", "verbosity_options": [ @@ -253388,33 +253041,40 @@ "medium", "high" ], - "visibility": "hidden" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-03-05", - "last_updated": "2026-03-05", + "knowledge": "2024-09-30", + "release_date": "2025-11-13", + "last_updated": "2025-11-13", "type": "chat" }, { - "id": "openai/gpt-oss-20b", - "name": "GPT OSS 20B", - "display_name": "GPT OSS 20B", + "id": "openai/gpt-6-sol", + "name": "GPT-6 Sol", + "display_name": "GPT-6 Sol", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 16384 + "context": 1050000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -253422,25 +253082,25 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, "effort_options": [ + "none", "low", "medium", "high" ] } }, - "attachment": false, - "open_weights": true, - "knowledge": "2024-06", - "release_date": "2025-08-05", - "last_updated": "2025-08-05", + "attachment": true, + "open_weights": false, + "knowledge": "2026-04-20", + "release_date": "2026-09-22", + "last_updated": "2026-09-22", "type": "chat" }, { - "id": "openai/gpt-luna-latest", - "name": "GPT Luna Latest", - "display_name": "GPT Luna Latest", + "id": "openai/gpt-5.6-terra", + "name": "GPT-5.6 Terra", + "display_name": "GPT-5.6 Terra", "modalities": { "input": [ "text", @@ -253455,6 +253115,7 @@ "context": 1050000, "output": 128000 }, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -253462,6 +253123,10 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", "effort_options": [ "none", "low", @@ -253469,61 +253134,42 @@ "high", "xhigh", "max" - ] + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, "attachment": true, "open_weights": false, - "release_date": "2026-09-11", - "last_updated": "2026-09-11", + "knowledge": "2026-02-16", + "release_date": "2026-07-09", + "last_updated": "2026-07-09", "type": "chat" }, { - "id": "openai/gpt-4-turbo", - "name": "GPT-4 Turbo", - "display_name": "GPT-4 Turbo", + "id": "openai/gpt-5.3-codex", + "name": "GPT-5.3 Codex", + "display_name": "GPT-5.3 Codex", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 4096 + "context": 400000, + "output": 128000 }, "temperature": true, - "tool_call": false, - "reasoning": { - "supported": false - }, - "attachment": true, - "open_weights": false, - "knowledge": "2023-12", - "release_date": "2023-11-06", - "last_updated": "2024-04-09", - "type": "chat" - }, - { - "id": "openai/o3-mini-low", - "name": "OpenAI o3-mini (Low)", - "display_name": "OpenAI o3-mini (Low)", - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 200000, - "output": 100000 - }, - "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -253536,24 +253182,36 @@ "mode": "effort", "effort": "medium", "effort_options": [ + "low", + "medium", + "high", + "xhigh" + ], + "verbosity": "medium", + "verbosity_options": [ "low", "medium", "high" ], - "visibility": "hidden" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] } }, - "attachment": false, + "attachment": true, "open_weights": false, - "knowledge": "2024-05", - "release_date": "2024-12-20", - "last_updated": "2025-01-29", + "knowledge": "2025-08-31", + "release_date": "2026-02-05", + "last_updated": "2026-02-05", "type": "chat" }, { - "id": "openai/gpt-6-astra-pro", - "name": "GPT 6 Astra Pro", - "display_name": "GPT 6 Astra Pro", + "id": "openai/gpt-6-astra", + "name": "GPT-6 Astra", + "display_name": "GPT-6 Astra", "modalities": { "input": [ "text", @@ -253568,6 +253226,7 @@ "context": 1050000, "output": 128000 }, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -253575,6 +253234,7 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, "effort_options": [ "low", "medium", @@ -253586,12 +253246,13 @@ }, "attachment": true, "open_weights": false, + "knowledge": "2026-04-30", "release_date": "2026-09-04", "last_updated": "2026-09-04", "type": "chat" }, { - "id": "openai/gpt-4o", + "id": "openai/gpt-4o-2024-08-06", "name": "GPT-4o", "display_name": "GPT-4o", "modalities": { @@ -253615,14 +253276,14 @@ "attachment": true, "open_weights": false, "knowledge": "2023-09", - "release_date": "2024-05-13", + "release_date": "2024-08-06", "last_updated": "2024-08-06", "type": "chat" }, { - "id": "openai/gpt-5-pro", - "name": "GPT 5 Pro", - "display_name": "GPT 5 Pro", + "id": "openai/gpt-5.4-pro", + "name": "GPT-5.4 Pro", + "display_name": "GPT-5.4 Pro", "modalities": { "input": [ "text", @@ -253633,11 +253294,11 @@ ] }, "limit": { - "context": 400000, + "context": 1050000, "output": 128000 }, "temperature": false, - "tool_call": false, + "tool_call": true, "reasoning": { "supported": true, "default": true @@ -253646,164 +253307,147 @@ "reasoning": { "supported": true, "default_enabled": true, - "mode": "fixed", + "mode": "effort", "effort": "high", + "effort_options": [ + "medium", + "high", + "xhigh" + ], "verbosity": "medium", "verbosity_options": [ "low", "medium", "high" ], - "visibility": "hidden" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] } }, "attachment": true, "open_weights": false, - "knowledge": "2024-09-30", - "release_date": "2025-10-06", - "last_updated": "2025-10-06", + "knowledge": "2025-08-31", + "release_date": "2026-03-05", + "last_updated": "2026-03-05", "type": "chat" }, { - "id": "openai/o3", - "name": "OpenAI o3", - "display_name": "OpenAI o3", + "id": "openai/gpt-6.1-sol", + "name": "GPT-6.1 Sol", + "display_name": "GPT-6.1 Sol", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 100000 + "context": 1050000, + "output": 128000 }, "temperature": false, - "tool_call": false, + "tool_call": true, "reasoning": { "supported": true, "default": true }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", "effort_options": [ "low", "medium", "high" - ], - "visibility": "hidden" + ] } }, - "attachment": false, + "attachment": true, "open_weights": false, - "knowledge": "2024-05", - "release_date": "2025-04-16", - "last_updated": "2025-04-16", + "knowledge": "2026-04-30", + "release_date": "2026-09-29", + "last_updated": "2026-09-29", "type": "chat" }, { - "id": "openai/gpt-chat-latest", - "name": "GPT Chat Latest", - "display_name": "GPT Chat Latest", + "id": "openai/gpt-4o-2024-11-20", + "name": "GPT-4o", + "display_name": "GPT-4o", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 128000, + "output": 16384 }, + "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" - ] - } + "supported": false }, "attachment": true, "open_weights": false, - "release_date": "2026-05-03", - "last_updated": "2026-05-03", + "knowledge": "2023-09", + "release_date": "2024-11-20", + "last_updated": "2024-11-20", "type": "chat" }, { - "id": "openai/gpt-astra-latest", - "name": "GPT Astra Latest", - "display_name": "GPT Astra Latest", + "id": "openai/gpt-4o-2024-05-13", + "name": "GPT-4o (2024-05-13)", + "display_name": "GPT-4o (2024-05-13)", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 128000, + "output": 16384 }, + "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" - ] - } + "supported": false }, "attachment": true, "open_weights": false, - "release_date": "2026-09-11", - "last_updated": "2026-09-11", + "knowledge": "2023-09", + "release_date": "2024-05-13", + "last_updated": "2024-05-13", "type": "chat" }, { - "id": "openai/gpt-6-sol", - "name": "GPT 6 Sol", - "display_name": "GPT 6 Sol", + "id": "openai/gpt-5-nano", + "name": "GPT-5 Nano", + "display_name": "GPT-5 Nano", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1050000, + "context": 400000, "output": 128000 }, "temperature": false, @@ -253814,30 +253458,45 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", "effort_options": [ - "none", + "minimal", "low", "medium", - "high", - "xhigh", - "max" + "high" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-04-20", - "release_date": "2026-09-22", - "last_updated": "2026-09-22", + "knowledge": "2024-05-30", + "release_date": "2025-08-07", + "last_updated": "2025-08-07", "type": "chat" }, { - "id": "openai/o4-mini", - "name": "OpenAI o4-mini", - "display_name": "OpenAI o4-mini", + "id": "openai/o3-pro", + "name": "o3 Pro", + "display_name": "o3 Pro", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" @@ -253864,20 +253523,25 @@ "medium", "high" ], - "visibility": "hidden" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] } }, - "attachment": false, + "attachment": true, "open_weights": false, "knowledge": "2024-05", - "release_date": "2025-04-16", - "last_updated": "2025-04-16", + "release_date": "2025-06-10", + "last_updated": "2025-06-10", "type": "chat" }, { - "id": "openai/gpt-5.6-sol", - "name": "GPT 5.6 Sol", - "display_name": "GPT 5.6 Sol", + "id": "openai/gpt-5.5-pro", + "name": "GPT-5.5 Pro", + "display_name": "GPT-5.5 Pro", "modalities": { "input": [ "text", @@ -253901,66 +253565,29 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", - "effort_options": [ - "none", - "low", - "medium", - "high", - "xhigh", - "max" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ], - "verbosity": "medium", - "verbosity_options": [ - "low", + "effort_options": [ "medium", "high" - ], - "visibility": "hidden" + ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-02-16", - "release_date": "2026-07-09", - "last_updated": "2026-07-09", - "type": "chat" - }, - { - "id": "openai/gpt-4o-2024-11-20", - "name": "GPT-4o (2024-11-20)", - "display_name": "GPT-4o (2024-11-20)", - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 16384 - }, - "temperature": true, - "tool_call": true, - "reasoning": { - "supported": false - }, - "attachment": true, - "open_weights": false, - "knowledge": "2023-09", - "release_date": "2024-11-20", - "last_updated": "2024-11-20", + "knowledge": "2025-12-01", + "release_date": "2026-04-23", + "last_updated": "2026-04-23", "type": "chat" }, { - "id": "openai/gpt-5.6-terra", - "name": "GPT 5.6 Terra", - "display_name": "GPT 5.6 Terra", + "id": "openai/gpt-5.6-luna", + "name": "GPT-5.6 Luna", + "display_name": "GPT-5.6 Luna", "modalities": { "input": [ "text", @@ -254012,48 +253639,9 @@ "type": "chat" }, { - "id": "openai/gpt-oss-120b", - "name": "GPT OSS 120B", - "display_name": "GPT OSS 120B", - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 16384 - }, - "temperature": true, - "tool_call": true, - "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "effort_options": [ - "low", - "medium", - "high" - ] - } - }, - "attachment": false, - "open_weights": true, - "knowledge": "2024-06", - "release_date": "2025-08-05", - "last_updated": "2025-08-05", - "type": "chat" - }, - { - "id": "openai/gpt-5.1-codex", - "name": "GPT 5.1 Codex", - "display_name": "GPT 5.1 Codex", + "id": "openai/gpt-5", + "name": "GPT-5", + "display_name": "GPT-5", "modalities": { "input": [ "text", @@ -254068,19 +253656,19 @@ "output": 128000 }, "temperature": false, - "tool_call": false, + "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, + "default_enabled": true, "mode": "effort", - "effort": "none", + "effort": "medium", "effort_options": [ - "none", + "minimal", "low", "medium", "high" @@ -254091,20 +253679,25 @@ "medium", "high" ], - "visibility": "hidden" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] } }, "attachment": true, "open_weights": false, "knowledge": "2024-09-30", - "release_date": "2025-11-13", - "last_updated": "2025-11-13", + "release_date": "2025-08-07", + "last_updated": "2025-08-07", "type": "chat" }, { - "id": "openai/gpt-6-luna", - "name": "GPT 6 Luna", - "display_name": "GPT 6 Luna", + "id": "openai/gpt-5-5", + "name": "GPT-5.5", + "display_name": "GPT-5.5", "modalities": { "input": [ "text", @@ -254127,27 +253720,36 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", "effort_options": [ - "none", "low", "medium", "high", - "xhigh", - "max" - ] + "xhigh" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, "attachment": true, "open_weights": false, - "knowledge": "2026-05-18", - "release_date": "2026-09-22", - "last_updated": "2026-09-22", + "knowledge": "2025-12-01", + "release_date": "2026-04-23", + "last_updated": "2026-04-23", "type": "chat" }, { "id": "openai/gpt-5-mini", - "name": "GPT 5 Mini", - "display_name": "GPT 5 Mini", + "name": "GPT-5 Mini", + "display_name": "GPT-5 Mini", "modalities": { "input": [ "text", @@ -254162,7 +253764,7 @@ "output": 128000 }, "temperature": false, - "tool_call": false, + "tool_call": true, "reasoning": { "supported": true, "default": true @@ -254185,7 +253787,12 @@ "medium", "high" ], - "visibility": "hidden" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] } }, "attachment": true, @@ -254196,38 +253803,46 @@ "type": "chat" }, { - "id": "openai/gpt-4o-2024-08-06", - "name": "GPT-4o (2024-08-06)", - "display_name": "GPT-4o (2024-08-06)", + "id": "openai/gpt-oss-safeguard-20b", + "name": "gpt-oss-safeguard-20b", + "display_name": "gpt-oss-safeguard-20b", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 16384 + "context": 131072, + "output": 65536 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": true, - "open_weights": false, - "knowledge": "2023-09", - "release_date": "2024-08-06", - "last_updated": "2024-08-06", + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "low", + "medium", + "high" + ] + } + }, + "attachment": false, + "open_weights": true, + "release_date": "2025-10-29", + "last_updated": "2025-10-29", "type": "chat" }, { - "id": "openai/gpt-5.1-codex-mini", - "name": "GPT 5.1 Codex Mini", - "display_name": "GPT 5.1 Codex Mini", + "id": "openai/gpt-5-1", + "name": "GPT-5.1", + "display_name": "GPT-5.1", "modalities": { "input": [ "text", @@ -254241,8 +253856,8 @@ "context": 400000, "output": 128000 }, - "temperature": false, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { "supported": true, "default": false @@ -254276,137 +253891,117 @@ "type": "chat" }, { - "id": "openai/o3-mini", - "name": "OpenAI o3-mini", - "display_name": "OpenAI o3-mini", + "id": "openai/gpt-4o-mini", + "name": "GPT-4o Mini", + "display_name": "GPT-4o Mini", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 100000 + "context": 128000, + "output": 16384 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", - "effort_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" - } + "supported": false }, - "attachment": false, + "attachment": true, "open_weights": false, - "knowledge": "2024-05", - "release_date": "2024-12-20", - "last_updated": "2025-01-29", + "knowledge": "2023-09", + "release_date": "2024-07-18", + "last_updated": "2024-07-18", "type": "chat" }, { - "id": "openai/gpt-5.1", - "name": "GPT 5.1", - "display_name": "GPT 5.1", + "id": "openai/gpt-oss-120b", + "name": "GPT OSS 120B", + "display_name": "GPT OSS 120B", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 131000, + "output": 117964 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "none", - "effort_options": [ - "none", - "low", - "medium", - "high" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ], - "verbosity": "medium", - "verbosity_options": [ + "effort_options": [ "low", "medium", "high" - ], - "visibility": "hidden" + ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2024-09-30", - "release_date": "2025-11-13", - "last_updated": "2025-11-13", + "attachment": false, + "open_weights": true, + "knowledge": "2024-06", + "release_date": "2025-08-05", + "last_updated": "2025-08-05", "type": "chat" }, { - "id": "openai/gpt-5.6-luna", - "name": "GPT 5.6 Luna", - "display_name": "GPT 5.6 Luna", + "id": "openai/gpt-5.4-image-2", + "name": "GPT-5.4 Image 2", + "display_name": "GPT-5.4 Image 2", "modalities": { "input": [ - "text", "image", + "text", "pdf" ], "output": [ + "image", "text" ] }, "limit": { - "context": 1050000, + "context": 272000, "output": 128000 }, "temperature": false, - "tool_call": true, + "tool_call": false, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, + "default_enabled": false, "mode": "effort", - "effort": "medium", + "effort": "none", "effort_options": [ "none", "low", "medium", "high", - "xhigh", - "max" + "xhigh" ], "verbosity": "medium", "verbosity_options": [ @@ -254419,15 +254014,13 @@ }, "attachment": true, "open_weights": false, - "knowledge": "2026-02-16", - "release_date": "2026-07-09", - "last_updated": "2026-07-09", - "type": "chat" + "release_date": "2026-04-21", + "last_updated": "2026-04-21" }, { - "id": "openai/gpt-5.4-mini", - "name": "GPT 5.4 Mini", - "display_name": "GPT 5.4 Mini", + "id": "openai/gpt-4o", + "name": "GPT-4o", + "display_name": "GPT-4o", "modalities": { "input": [ "text", @@ -254439,48 +254032,25 @@ ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 128000, + "output": 16384 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": false - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "none", - "effort_options": [ - "none", - "low", - "medium", - "high", - "xhigh" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" - } + "supported": false }, "attachment": true, "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-03-17", - "last_updated": "2026-03-17", + "knowledge": "2023-09", + "release_date": "2024-05-13", + "last_updated": "2024-08-06", "type": "chat" }, { - "id": "openai/gpt-5.3-codex", - "name": "GPT 5.3 Codex", - "display_name": "GPT 5.3 Codex", + "id": "openai/gpt-5.6-terra-pro", + "name": "GPT-5.6 Terra Pro", + "display_name": "GPT-5.6 Terra Pro", "modalities": { "input": [ "text", @@ -254492,10 +254062,10 @@ ] }, "limit": { - "context": 400000, + "context": 1050000, "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -254504,35 +254074,31 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", - "effort_options": [ - "low", - "medium", - "high", - "xhigh" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ], - "verbosity": "medium", - "verbosity_options": [ + "effort_options": [ + "none", "low", "medium", "high" - ], - "visibility": "hidden" + ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-02-05", - "last_updated": "2026-02-05", + "knowledge": "2026-02-16", + "release_date": "2026-07-09", + "last_updated": "2026-07-09", "type": "chat" }, { - "id": "openai/gpt-6-sol-pro", - "name": "GPT 6 Sol Pro", - "display_name": "GPT 6 Sol Pro", + "id": "openai/gpt-5.6-luna-pro", + "name": "GPT-5.6 Luna Pro", + "display_name": "GPT-5.6 Luna Pro", "modalities": { "input": [ "text", @@ -254547,6 +254113,7 @@ "context": 1050000, "output": 128000 }, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -254554,25 +254121,32 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ + "none", "low", "medium", - "high", - "xhigh", - "max" + "high" ] } }, "attachment": true, "open_weights": false, - "release_date": "2026-09-22", - "last_updated": "2026-09-22", + "knowledge": "2026-02-16", + "release_date": "2026-07-09", + "last_updated": "2026-07-09", "type": "chat" }, { - "id": "openai/gpt-5.6-terra-pro", - "name": "GPT 5.6 Terra Pro", - "display_name": "GPT 5.6 Terra Pro", + "id": "openai/gpt-5.6-sol", + "name": "GPT-5.6 Sol", + "display_name": "GPT-5.6 Sol", "modalities": { "input": [ "text", @@ -254595,6 +254169,10 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", "effort_options": [ "none", "low", @@ -254602,7 +254180,14 @@ "high", "xhigh", "max" - ] + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, "attachment": true, @@ -254613,49 +254198,61 @@ "type": "chat" }, { - "id": "openai/gpt-latest", - "name": "GPT Latest", - "display_name": "GPT Latest", + "id": "openai/gpt-5-2", + "name": "GPT-5.2", + "display_name": "GPT-5.2", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1050000, + "context": 400000, "output": 128000 }, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": false, + "mode": "effort", + "effort": "none", "effort_options": [ + "none", "low", "medium", "high", - "xhigh", - "max" - ] + "xhigh" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, "attachment": true, "open_weights": false, - "release_date": "2026-03-29", - "last_updated": "2026-03-29", + "knowledge": "2025-08-31", + "release_date": "2025-12-11", + "last_updated": "2025-12-11", "type": "chat" }, { "id": "openai/gpt-5.6-sol-pro", - "name": "GPT 5.6 Sol Pro", - "display_name": "GPT 5.6 Sol Pro", + "name": "GPT-5.6 Sol Pro", + "display_name": "GPT-5.6 Sol Pro", "modalities": { "input": [ "text", @@ -254678,13 +254275,18 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ "none", "low", "medium", - "high", - "xhigh", - "max" + "high" ] } }, @@ -254697,23 +254299,24 @@ }, { "id": "openai/gpt-4.1-mini", - "name": "GPT 4.1 Mini", - "display_name": "GPT 4.1 Mini", + "name": "GPT-4.1 Mini", + "display_name": "GPT-4.1 Mini", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1047576, + "context": 1000000, "output": 32768 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { "supported": false }, @@ -254725,59 +254328,39 @@ "type": "chat" }, { - "id": "openai/gpt-5.1-2025-11-13", - "name": "GPT-5.1 (2025-11-13)", - "display_name": "GPT-5.1 (2025-11-13)", + "id": "openai/gpt-4.1", + "name": "GPT-4.1", + "display_name": "GPT-4.1", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 1047576, + "output": 32768 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": false - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "none", - "effort_options": [ - "none", - "low", - "medium", - "high" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" - } + "supported": false }, - "attachment": false, + "attachment": true, "open_weights": false, - "knowledge": "2024-09-30", - "release_date": "2025-11-13", - "last_updated": "2025-11-13", + "knowledge": "2024-04", + "release_date": "2025-04-14", + "last_updated": "2025-04-14", "type": "chat" }, { - "id": "openai/gpt-5.4-nano", - "name": "GPT 5.4 Nano", - "display_name": "GPT 5.4 Nano", + "id": "openai/gpt-6-luna", + "name": "GPT-6 Luna", + "display_name": "GPT-6 Luna", "modalities": { "input": [ "text", @@ -254789,48 +254372,36 @@ ] }, "limit": { - "context": 400000, + "context": 1050000, "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "none", "effort_options": [ "none", - "low", - "medium", - "high", - "xhigh" - ], - "verbosity": "medium", - "verbosity_options": [ "low", "medium", "high" - ], - "visibility": "hidden" + ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-03-17", - "last_updated": "2026-03-17", + "knowledge": "2026-05-18", + "release_date": "2026-09-22", + "last_updated": "2026-09-22", "type": "chat" }, { - "id": "openai/gpt-oss-safeguard-20b", - "name": "GPT OSS Safeguard 20B", - "display_name": "GPT OSS Safeguard 20B", + "id": "openai/gpt-oss-20b", + "name": "GPT OSS 20B", + "display_name": "GPT OSS 20B", "modalities": { "input": [ "text" @@ -254840,8 +254411,8 @@ ] }, "limit": { - "context": 128000, - "output": 16384 + "context": 131000, + "output": 117964 }, "temperature": true, "tool_call": true, @@ -254851,6 +254422,13 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ "low", "medium", @@ -254860,14 +254438,15 @@ }, "attachment": false, "open_weights": true, - "release_date": "2025-10-29", - "last_updated": "2025-10-29", + "knowledge": "2024-06", + "release_date": "2025-08-05", + "last_updated": "2025-08-05", "type": "chat" }, { - "id": "openai/gpt-4.1", - "name": "GPT 4.1", - "display_name": "GPT 4.1", + "id": "sakana/fugu-max", + "name": "Fugu Max", + "display_name": "Fugu Max", "modalities": { "input": [ "text", @@ -254879,25 +254458,32 @@ ] }, "limit": { - "context": 1047576, - "output": 32768 + "context": 1000000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "high" + ] + } }, "attachment": true, "open_weights": false, - "knowledge": "2024-04", - "release_date": "2025-04-14", - "last_updated": "2025-04-14", + "release_date": "2026-09-11", + "last_updated": "2026-09-11", "type": "chat" }, { - "id": "openai/gpt-6-luna-pro", - "name": "GPT 6 Luna Pro", - "display_name": "GPT 6 Luna Pro", + "id": "sakana/fugu-ultra-v2", + "name": "Fugu Ultra v2", + "display_name": "Fugu Ultra v2", "modalities": { "input": [ "text", @@ -254909,9 +254495,10 @@ ] }, "limit": { - "context": 1050000, + "context": 1000000, "output": 128000 }, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -254920,36 +254507,32 @@ "extra_capabilities": { "reasoning": { "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" + "high" ] } }, "attachment": true, "open_weights": false, - "release_date": "2026-09-22", - "last_updated": "2026-09-22", + "knowledge": "2026-08-28", + "release_date": "2026-09-11", + "last_updated": "2026-09-11", "type": "chat" }, { - "id": "openai/gpt-5.2-codex", - "name": "GPT 5.2 Codex", - "display_name": "GPT 5.2 Codex", + "id": "sakana/fugu-ultra", + "name": "Fugu Ultra", + "display_name": "Fugu Ultra", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 400000, + "context": 1000000, "output": 128000 }, "temperature": false, @@ -254960,52 +254543,35 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", "effort_options": [ - "low", - "medium", - "high", - "xhigh" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", "high" - ], - "visibility": "hidden" + ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2025-12-11", - "last_updated": "2025-12-11", + "release_date": "2026-06-15", + "last_updated": "2026-06-15", "type": "chat" }, { - "id": "openai/o1-pro", - "name": "OpenAI o1 Pro", - "display_name": "OpenAI o1 Pro", + "id": "zhipu/glm-5.3", + "name": "GLM 5.3", + "display_name": "GLM 5.3", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 100000 + "context": 1024000, + "output": 131072 }, - "temperature": false, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { "supported": true, "default": true @@ -255013,28 +254579,22 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", "effort_options": [ "low", - "medium", "high" - ], - "visibility": "hidden" + ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2023-09", - "release_date": "2025-03-19", - "last_updated": "2025-03-19", + "attachment": false, + "open_weights": true, + "release_date": "2026-08-14", + "last_updated": "2026-08-14", "type": "chat" }, { - "id": "openai/o3-pro-2025-06-10", - "name": "OpenAI o3-pro (2025-06-10)", - "display_name": "OpenAI o3-pro (2025-06-10)", + "id": "zhipu/glm-5.2", + "name": "GLM 5.2", + "display_name": "GLM 5.2", "modalities": { "input": [ "text" @@ -255044,10 +254604,10 @@ ] }, "limit": { - "context": 200000, - "output": 100000 + "context": 1000000, + "output": 131072 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -255056,41 +254616,36 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", "effort_options": [ - "low", - "medium", "high" - ], - "visibility": "hidden" + ] } }, "attachment": false, - "open_weights": false, - "knowledge": "2024-05", - "release_date": "2025-06-10", - "last_updated": "2025-06-10", + "open_weights": true, + "release_date": "2026-06-13", + "last_updated": "2026-06-13", "type": "chat" }, { - "id": "openai/o3-mini-high", - "name": "OpenAI o3-mini (High)", - "display_name": "OpenAI o3-mini (High)", + "id": "thinkingmachines/inkling", + "name": "Inkling", + "display_name": "Inkling", "modalities": { "input": [ - "text" + "text", + "image", + "audio" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 100000 + "context": 1048576, + "output": 1048576 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -255099,43 +254654,44 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ "low", "medium", "high" - ], - "visibility": "hidden" + ] } }, - "attachment": false, - "open_weights": false, - "knowledge": "2024-05", - "release_date": "2024-12-20", - "last_updated": "2025-01-29", + "attachment": true, + "open_weights": true, + "release_date": "2026-07-15", + "last_updated": "2026-07-15", "type": "chat" }, { - "id": "openai/gpt-6.1-terra", - "name": "GPT 6.1 Terra", - "display_name": "GPT 6.1 Terra", + "id": "thinkingmachines/inkling-small", + "name": "Inkling Small", + "display_name": "Inkling Small", "modalities": { "input": [ "text", "image", - "pdf" + "audio" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 524288, + "output": 262144 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -255143,42 +254699,45 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ - "none", "low", "medium", - "high", - "xhigh", - "max" + "high" ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2026-06-01", - "release_date": "2026-10-26", - "last_updated": "2026-10-26", + "open_weights": true, + "release_date": "2026-07-30", + "last_updated": "2026-07-30", "type": "chat" }, { - "id": "openai/gpt-5.6-luna-pro", - "name": "GPT 5.6 Luna Pro", - "display_name": "GPT 5.6 Luna Pro", + "id": "z-ai/glm-4.6v", + "name": "GLM 4.6V", + "display_name": "GLM 4.6V", "modalities": { "input": [ "text", "image", - "pdf" + "video" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 131072, + "output": 32768 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -255186,31 +254745,31 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "none", - "low", - "medium", - "high", - "xhigh", - "max" + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2026-02-16", - "release_date": "2026-07-09", - "last_updated": "2026-07-09", + "open_weights": true, + "knowledge": "2025-04", + "release_date": "2025-12-08", + "last_updated": "2025-12-08", "type": "chat" }, { - "id": "openai/gpt-5.2", - "name": "GPT 5.2", - "display_name": "GPT 5.2", + "id": "z-ai/glm-5.3-flash", + "name": "GLM 5.3 Flash", + "display_name": "GLM 5.3 Flash", "modalities": { "input": [ "text", "image", + "video", "pdf" ], "output": [ @@ -255218,63 +254777,56 @@ ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 1048576, + "output": 131072 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "none", + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ - "none", "low", - "medium", "high", - "xhigh" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + "max" + ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2025-12-11", - "last_updated": "2025-12-11", + "open_weights": true, + "release_date": "2026-08-26", + "last_updated": "2026-08-26", "type": "chat" }, { - "id": "openai/gpt-6.1-sol", - "name": "GPT 6.1 Sol", - "display_name": "GPT 6.1 Sol", + "id": "z-ai/glm-4.5v", + "name": "GLM 4.5V", + "display_name": "GLM 4.5V", "modalities": { "input": [ "text", "image", - "pdf" + "video" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 65536, + "output": 16384 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -255282,40 +254834,39 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2026-04-30", - "release_date": "2026-09-29", - "last_updated": "2026-09-29", + "open_weights": true, + "knowledge": "2025-04", + "release_date": "2025-08-11", + "last_updated": "2025-08-11", "type": "chat" }, { - "id": "openai/gpt-terra-latest", - "name": "GPT Terra Latest", - "display_name": "GPT Terra Latest", + "id": "z-ai/glm-4.7-flash", + "name": "GLM 4.7 Flash", + "display_name": "GLM 4.7 Flash", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 202752, + "output": 16384 }, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -255323,30 +254874,31 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "none", - "low", - "medium", - "high", - "xhigh", - "max" + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, - "attachment": true, - "open_weights": false, - "release_date": "2026-09-11", - "last_updated": "2026-09-11", + "attachment": false, + "open_weights": true, + "knowledge": "2025-04", + "release_date": "2026-01-19", + "last_updated": "2026-01-19", "type": "chat" }, { - "id": "openai/gpt-6.1-sol-pro", - "name": "GPT 6.1 Sol Pro", - "display_name": "GPT 6.1 Sol Pro", + "id": "z-ai/glm-5v-turbo", + "name": "GLM 5V Turbo", + "display_name": "GLM 5V Turbo", "modalities": { "input": [ "text", "image", + "video", "pdf" ], "output": [ @@ -255354,86 +254906,58 @@ ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 202752, + "output": 131072 }, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" - ] - } - }, "attachment": true, "open_weights": false, - "release_date": "2026-09-29", - "last_updated": "2026-09-29", + "release_date": "2026-04-01", + "last_updated": "2026-04-01", "type": "chat" }, { - "id": "openai/gpt-5-nano", - "name": "GPT 5 Nano", - "display_name": "GPT 5 Nano", + "id": "z-ai/glm-5-turbo", + "name": "GLM 5 Turbo", + "display_name": "GLM 5 Turbo", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 262144, + "output": 131072 }, - "temperature": false, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { "supported": true, "default": true }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", - "effort_options": [ - "minimal", - "low", - "medium", - "high" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + "supported": true } }, - "attachment": true, + "attachment": false, "open_weights": false, - "knowledge": "2024-05-30", - "release_date": "2025-08-07", - "last_updated": "2025-08-07", + "release_date": "2026-03-16", + "last_updated": "2026-03-16", "type": "chat" }, { - "id": "tencent/hy4-preview", - "name": "Tencent Hy4 Preview", - "display_name": "Tencent Hy4 Preview", + "id": "morph/morph-v3-fast", + "name": "V3 Fast", + "display_name": "V3 Fast", "modalities": { "input": [ "text" @@ -255443,8 +254967,62 @@ ] }, "limit": { - "context": 1048576, - "output": 64000 + "context": 81920, + "output": 38000 + }, + "temperature": true, + "tool_call": false, + "reasoning": { + "supported": false + }, + "attachment": false, + "open_weights": false, + "release_date": "2025-07-07", + "last_updated": "2025-07-07", + "type": "chat" + }, + { + "id": "morph/morph-v3-large", + "name": "V3 Large", + "display_name": "V3 Large", + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 262144, + "output": 131072 + }, + "temperature": true, + "tool_call": false, + "reasoning": { + "supported": false + }, + "attachment": false, + "open_weights": false, + "release_date": "2025-07-07", + "last_updated": "2025-07-07", + "type": "chat" + }, + { + "id": "inception/mercury-2", + "name": "Mercury 2", + "display_name": "Mercury 2", + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 50000 }, "temperature": true, "tool_call": true, @@ -255454,24 +255032,23 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, "effort_options": [ - "none", "low", + "medium", "high" ] } }, "attachment": false, - "open_weights": true, - "release_date": "2026-08-28", - "last_updated": "2026-08-28", + "open_weights": false, + "release_date": "2026-02-24", + "last_updated": "2026-02-24", "type": "chat" }, { - "id": "tencent/hy3", - "name": "Tencent Hy3", - "display_name": "Tencent Hy3", + "id": "inception/mercury-2.5", + "name": "Mercury 2.5", + "display_name": "Mercury 2.5", "modalities": { "input": [ "text" @@ -255481,8 +255058,8 @@ ] }, "limit": { - "context": 262144, - "output": 128000 + "context": 260000, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -255492,24 +255069,23 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, "effort_options": [ - "none", "low", + "medium", "high" ] } }, "attachment": false, - "open_weights": true, - "release_date": "2026-07-06", - "last_updated": "2026-07-06", + "open_weights": false, + "release_date": "2026-09-08", + "last_updated": "2026-09-08", "type": "chat" }, { - "id": "MiniMaxAI/MiniMax-M1-80k", - "name": "MiniMax M1 80K", - "display_name": "MiniMax M1 80K", + "id": "meta-llama/Llama-3.3-70B-Instruct-Turbo", + "name": "Llama 3.3 70B Instruct Turbo", + "display_name": "Llama 3.3 70B Instruct Turbo", "modalities": { "input": [ "text" @@ -255519,24 +255095,25 @@ ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 128000, + "output": 127000 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { "supported": false }, "attachment": false, "open_weights": true, - "release_date": "2025-06-17", - "last_updated": "2025-06-17", + "knowledge": "2023-12", + "release_date": "2024-12-06", + "last_updated": "2024-12-06", "type": "chat" }, { - "id": "amazon/nova-pro-v1", - "name": "Amazon Nova Pro 1.0", - "display_name": "Amazon Nova Pro 1.0", + "id": "cohere/command-r-08-2024", + "name": "Command R (08-2024)", + "display_name": "Command R (08-2024)", "modalities": { "input": [ "text" @@ -255546,25 +255123,25 @@ ] }, "limit": { - "context": 300000, - "output": 32000 + "context": 128000, + "output": 4000 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { "supported": false }, "attachment": false, - "open_weights": false, - "knowledge": "2024-10-31", - "release_date": "2024-12-05", - "last_updated": "2024-12-05", + "open_weights": true, + "knowledge": "2024-06-01", + "release_date": "2024-08-30", + "last_updated": "2024-08-30", "type": "chat" }, { - "id": "amazon/nova-2-lite-v1", - "name": "Amazon Nova 2 Lite", - "display_name": "Amazon Nova 2 Lite", + "id": "cohere/command-r-plus-08-2024", + "name": "Command R+ (08-2024)", + "display_name": "Command R+ (08-2024)", "modalities": { "input": [ "text" @@ -255574,25 +255151,25 @@ ] }, "limit": { - "context": 1000000, - "output": 65535 + "context": 128000, + "output": 4000 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { "supported": false }, "attachment": false, - "open_weights": false, - "knowledge": "2025-10", - "release_date": "2025-12-02", - "last_updated": "2025-12-02", + "open_weights": true, + "knowledge": "2024-06-01", + "release_date": "2024-08-30", + "last_updated": "2024-08-30", "type": "chat" }, { - "id": "amazon/nova-lite-v1", - "name": "Amazon Nova Lite 1.0", - "display_name": "Amazon Nova Lite 1.0", + "id": "cohere/command-r7b-12-2024", + "name": "Command R7B (12-2024)", + "display_name": "Command R7B (12-2024)", "modalities": { "input": [ "text" @@ -255602,79 +255179,69 @@ ] }, "limit": { - "context": 300000, - "output": 5120 + "context": 128000, + "output": 4000 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { "supported": false }, "attachment": false, - "open_weights": false, - "knowledge": "2024-10-31", - "release_date": "2024-12-05", - "last_updated": "2024-12-05", + "open_weights": true, + "knowledge": "2024-06-01", + "release_date": "2024-12-13", + "last_updated": "2024-12-13", "type": "chat" }, { - "id": "pqhaz/DeepSeek-V4.1-Flash-1017-heretic", - "name": "DeepSeek V4.1 Flash Heretic", - "display_name": "DeepSeek V4.1 Flash Heretic", + "id": "cohere/command-a", + "name": "Command A", + "display_name": "Command A", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 512000, - "output": 128000 + "context": 256000, + "output": 256000 }, + "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "none", - "minimal", - "low", - "medium", - "high", - "xhigh", - "max" - ] - } + "supported": false }, - "attachment": true, + "attachment": false, "open_weights": true, - "release_date": "2026-10-31", - "last_updated": "2026-10-31", + "knowledge": "2024-08-31", + "release_date": "2025-03-13", + "last_updated": "2025-03-13", "type": "chat" }, { - "id": "pqhaz/DeepSeek-V4.1-Flash-1017-heretic:thinking", - "name": "DeepSeek V4.1 Flash Heretic Thinking", - "display_name": "DeepSeek V4.1 Flash Heretic Thinking", + "id": "perceptron/perceptron-mk1.5", + "name": "Mk1.5", + "display_name": "Mk1.5", "modalities": { "input": [ "text", - "image" + "image", + "video", + "audio" ], "output": [ "text" ] }, "limit": { - "context": 512000, - "output": 128000 + "context": 36864, + "output": 8192 }, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -255683,26 +255250,23 @@ "extra_capabilities": { "reasoning": { "effort_options": [ - "none", "minimal", "low", "medium", - "high", - "xhigh", - "max" + "high" ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-10-31", - "last_updated": "2026-10-31", + "open_weights": false, + "release_date": "2026-09-25", + "last_updated": "2026-09-25", "type": "chat" }, { - "id": "huihui-ai/Llama-3.3-70B-Instruct-abliterated", - "name": "Llama 3.3 70B Instruct abliterated", - "display_name": "Llama 3.3 70B Instruct abliterated", + "id": "moonshotai/kimi-k2-thinking", + "name": "Kimi K2 Thinking", + "display_name": "Kimi K2 Thinking", "modalities": { "input": [ "text" @@ -255712,23 +255276,37 @@ ] }, "limit": { - "context": 32768, - "output": 16384 + "context": 262144, + "output": 98304 }, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } }, "attachment": false, "open_weights": true, - "release_date": "2025-08-08", - "last_updated": "2025-08-08", + "knowledge": "2024-08", + "release_date": "2025-11-06", + "last_updated": "2025-11-06", "type": "chat" }, { - "id": "huihui-ai/Qwen2.5-32B-Instruct-abliterated", - "name": "Qwen 2.5 32B Abliterated", - "display_name": "Qwen 2.5 32B Abliterated", + "id": "moonshotai/kimi-k2-0905", + "name": "Kimi K2 0905", + "display_name": "Kimi K2 0905", "modalities": { "input": [ "text" @@ -255738,23 +255316,24 @@ ] }, "limit": { - "context": 32768, - "output": 8192 + "context": 262144, + "output": 98304 }, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { "supported": false }, "attachment": false, "open_weights": true, - "release_date": "2025-01-06", - "last_updated": "2025-01-06", + "release_date": "2025-09-05", + "last_updated": "2025-09-05", "type": "chat" }, { - "id": "huihui-ai/DeepSeek-R1-Distill-Qwen-32B-abliterated", - "name": "DeepSeek R1 Qwen Abliterated", - "display_name": "DeepSeek R1 Qwen Abliterated", + "id": "moonshotai/kimi-k2", + "name": "Kimi K2 0711", + "display_name": "Kimi K2 0711", "modalities": { "input": [ "text" @@ -255764,51 +255343,64 @@ ] }, "limit": { - "context": 16384, - "output": 8192 + "context": 131072, + "output": 98304 }, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { - "supported": true, - "default": true + "supported": false }, "attachment": false, "open_weights": true, - "release_date": "2024-01-01", - "last_updated": "2025-01-20", + "knowledge": "2024-12-31", + "release_date": "2025-07-11", + "last_updated": "2025-07-11", "type": "chat" }, { - "id": "huihui-ai/DeepSeek-R1-Distill-Llama-70B-abliterated", - "name": "DeepSeek R1 Llama 70B Abliterated", - "display_name": "DeepSeek R1 Llama 70B Abliterated", + "id": "stepfun/step-5-preview", + "name": "Step 5 Preview", + "display_name": "Step 5 Preview", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 16384, - "output": 8192 + "context": 1000000, + "output": 64000 }, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { "supported": true, "default": true }, - "attachment": false, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "low", + "medium", + "high" + ] + } + }, + "attachment": true, "open_weights": true, - "release_date": "2024-01-01", - "last_updated": "2025-01-20", + "release_date": "2026-09-16", + "last_updated": "2026-09-20", "type": "chat" }, { - "id": "Sao10K/L3.3-70B-Euryale-v2.3", - "name": "Llama 3.3 70B Euryale", - "display_name": "Llama 3.3 70B Euryale", + "id": "mancer/weaver", + "name": "Weaver (alpha)", + "display_name": "Weaver (alpha)", "modalities": { "input": [ "text" @@ -255818,8 +255410,8 @@ ] }, "limit": { - "context": 20480, - "output": 16384 + "context": 8000, + "output": 2000 }, "temperature": true, "tool_call": false, @@ -255827,19 +255419,21 @@ "supported": false }, "attachment": false, - "open_weights": true, - "knowledge": "2023-12-31", - "release_date": "2024-12-18", - "last_updated": "2024-12-18", + "open_weights": false, + "knowledge": "2023-06-30", + "release_date": "2023-08-02", + "last_updated": "2023-08-02", "type": "chat" }, { - "id": "Sao10K/L3-8B-Stheno-v3.2", - "name": "Sao10K Stheno 8b", - "display_name": "Sao10K Stheno 8b", + "id": "rekaai/reka-edge", + "name": "Reka Edge", + "display_name": "Reka Edge", "modalities": { "input": [ - "text" + "image", + "text", + "video" ], "output": [ "text" @@ -255847,22 +255441,23 @@ }, "limit": { "context": 16384, - "output": 8192 + "output": 16384 }, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { "supported": false }, - "attachment": false, + "attachment": true, "open_weights": true, - "release_date": "2024-01-01", - "last_updated": "2024-11-29", + "release_date": "2026-03-11", + "last_updated": "2026-03-11", "type": "chat" }, { - "id": "Sao10K/L3.1-70B-Euryale-v2.2", - "name": "Llama 3.1 70B Euryale", - "display_name": "Llama 3.1 70B Euryale", + "id": "tencent/hy-mt2-30b-a3b", + "name": "Hy-MT2-30B-A3B", + "display_name": "Hy-MT2-30B-A3B", "modalities": { "input": [ "text" @@ -255872,8 +255467,8 @@ ] }, "limit": { - "context": 20480, - "output": 16384 + "context": 8192, + "output": 4096 }, "temperature": true, "tool_call": false, @@ -255882,15 +255477,14 @@ }, "attachment": false, "open_weights": true, - "knowledge": "2023-12-31", - "release_date": "2024-08-28", - "last_updated": "2024-08-28", + "release_date": "2026-05-21", + "last_updated": "2026-05-21", "type": "chat" }, { - "id": "Sao10K/L3.1-70B-Hanami-x1", - "name": "Llama 3.1 70B Hanami", - "display_name": "Llama 3.1 70B Hanami", + "id": "tencent/hy-mt2-7b", + "name": "Hy-MT2-7B", + "display_name": "Hy-MT2-7B", "modalities": { "input": [ "text" @@ -255900,23 +255494,24 @@ ] }, "limit": { - "context": 32768, - "output": 16384 + "context": 8192, + "output": 4096 }, + "temperature": true, "tool_call": false, "reasoning": { "supported": false }, "attachment": false, "open_weights": true, - "release_date": "2024-01-01", - "last_updated": "2024-07-23", + "release_date": "2026-05-21", + "last_updated": "2026-05-21", "type": "chat" }, { - "id": "reflection/beam-1.0", - "name": "Beam 1.0 501B A23B", - "display_name": "Beam 1.0 501B A23B", + "id": "tencent/hy3", + "name": "Hy3", + "display_name": "Hy3", "modalities": { "input": [ "text" @@ -255926,7 +255521,7 @@ ] }, "limit": { - "context": 1048576, + "context": 262144, "output": 131072 }, "temperature": true, @@ -255937,53 +255532,29 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ "low", - "medium", - "high", - "xhigh", - "max" + "high" ] } }, "attachment": false, "open_weights": true, - "knowledge": "2026-06-30", - "release_date": "2026-10-05", - "last_updated": "2026-10-16", - "type": "chat" - }, - { - "id": "liquid/lfm-2.5-24b-a2b", - "name": "LFM2.5 24B A2B", - "display_name": "LFM2.5 24B A2B", - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 8192 - }, - "tool_call": true, - "reasoning": { - "supported": true, - "default": true - }, - "attachment": false, - "open_weights": false, - "release_date": "2026-10-14", - "last_updated": "2026-10-14", + "release_date": "2026-07-06", + "last_updated": "2026-07-06", "type": "chat" }, { - "id": "liquid/lfm-2.5-2.6b", - "name": "LFM2.5 2.6B", - "display_name": "LFM2.5 2.6B", + "id": "tencent/hy4-preview", + "name": "Hy4 preview", + "display_name": "Hy4 preview", "modalities": { "input": [ "text" @@ -255993,9 +255564,10 @@ ] }, "limit": { - "context": 128000, - "output": 32768 + "context": 1048576, + "output": 64000 }, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -256004,25 +255576,28 @@ "extra_capabilities": { "reasoning": { "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ - "minimal", "low", - "medium", - "high", - "xhigh" + "high" ] } }, "attachment": false, "open_weights": true, - "release_date": "2026-08-12", - "last_updated": "2026-08-12", + "release_date": "2026-08-28", + "last_updated": "2026-08-28", "type": "chat" }, { - "id": "Gryphe/MythoMax-L2-13b", - "name": "MythoMax 13B", - "display_name": "MythoMax 13B", + "id": "tencent/hy-mt2-1.8b", + "name": "Hy-MT2-1.8B", + "display_name": "Hy-MT2-1.8B", "modalities": { "input": [ "text" @@ -256032,8 +255607,8 @@ ] }, "limit": { - "context": 4096, - "output": 3686 + "context": 8192, + "output": 4096 }, "temperature": true, "tool_call": false, @@ -256042,14 +255617,14 @@ }, "attachment": false, "open_weights": true, - "release_date": "2023-08-10", - "last_updated": "2023-08-10", + "release_date": "2026-05-21", + "last_updated": "2026-05-21", "type": "chat" }, { - "id": "aion-labs/aion-rp-llama-3.1-8b", - "name": "Llama 3.1 8b (uncensored)", - "display_name": "Llama 3.1 8b (uncensored)", + "id": "anthracite-org/magnum-v4-72b", + "name": "Magnum v4 72B", + "display_name": "Magnum v4 72B", "modalities": { "input": [ "text" @@ -256059,25 +255634,25 @@ ] }, "limit": { - "context": 32768, + "context": 32000, "output": 16384 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { "supported": false }, "attachment": false, "open_weights": true, - "knowledge": "2023-12-31", - "release_date": "2025-02-04", - "last_updated": "2025-02-04", + "knowledge": "2024-06-30", + "release_date": "2024-10-22", + "last_updated": "2024-10-22", "type": "chat" }, { - "id": "aion-labs/aion-3.5-mini", - "name": "AionLabs: Aion 3.5 Mini", - "display_name": "AionLabs: Aion 3.5 Mini", + "id": "thedrummer/unslopnemo-12b", + "name": "UnslopNemo 12B", + "display_name": "UnslopNemo 12B", "modalities": { "input": [ "text" @@ -256087,24 +255662,25 @@ ] }, "limit": { - "context": 262144, + "context": 32768, "output": 32768 }, - "tool_call": true, + "temperature": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true + "supported": false }, "attachment": false, - "open_weights": false, - "release_date": "2026-09-23", - "last_updated": "2026-09-23", + "open_weights": true, + "knowledge": "2024-04-30", + "release_date": "2024-11-08", + "last_updated": "2024-11-08", "type": "chat" }, { - "id": "aion-labs/aion-3.0-mini", - "name": "AionLabs: Aion 3.0 Mini", - "display_name": "AionLabs: Aion 3.0 Mini", + "id": "thedrummer/skyfall-36b-v2", + "name": "Skyfall 36B V2", + "display_name": "Skyfall 36B V2", "modalities": { "input": [ "text" @@ -256114,25 +255690,25 @@ ] }, "limit": { - "context": 131072, + "context": 32768, "output": 32768 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true + "supported": false }, "attachment": false, - "open_weights": false, - "release_date": "2026-05-14", - "last_updated": "2026-05-14", + "open_weights": true, + "knowledge": "2024-06-30", + "release_date": "2025-03-10", + "last_updated": "2025-03-10", "type": "chat" }, { - "id": "aion-labs/aion-3.0", - "name": "AionLabs: Aion 3.0", - "display_name": "AionLabs: Aion 3.0", + "id": "thedrummer/cydonia-24b-v4.1", + "name": "Cydonia 24B V4.1", + "display_name": "Cydonia 24B V4.1", "modalities": { "input": [ "text" @@ -256143,27 +255719,28 @@ }, "limit": { "context": 131072, - "output": 32768 + "output": 131072 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true + "supported": false }, "attachment": false, - "open_weights": false, - "release_date": "2026-05-05", - "last_updated": "2026-05-05", + "open_weights": true, + "knowledge": "2024-04-30", + "release_date": "2025-09-27", + "last_updated": "2025-09-27", "type": "chat" }, { - "id": "aion-labs/aion-3.5", - "name": "AionLabs: Aion 3.5", - "display_name": "AionLabs: Aion 3.5", + "id": "google/gemma-4-26b-a4b-it", + "name": "Gemma 4 26B A4B IT", + "display_name": "Gemma 4 26B A4B IT", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" @@ -256171,132 +255748,195 @@ }, "limit": { "context": 262144, - "output": 32768 + "output": 128000 }, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, "default": true }, - "attachment": false, - "open_weights": false, - "release_date": "2026-09-23", - "last_updated": "2026-09-23", + "extra_capabilities": { + "reasoning": { + "supported": true + } + }, + "attachment": true, + "open_weights": true, + "release_date": "2026-04-02", + "last_updated": "2026-04-02", "type": "chat" }, { - "id": "aion-labs/aion-2.0", - "name": "AionLabs: Aion-2.0", - "display_name": "AionLabs: Aion-2.0", + "id": "google/gemini-flash-latest", + "name": "Gemini Flash Latest", + "display_name": "Gemini Flash Latest", "modalities": { "input": [ - "text" + "text", + "image", + "video", + "audio", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 32768 + "context": 1048576, + "output": 65536 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "effort_options": [ + "low", + "medium", + "high" + ] + } + }, + "attachment": true, "open_weights": false, - "release_date": "2025-12-21", - "last_updated": "2025-12-21", + "knowledge": "2026-03", + "release_date": "2026-08-13", + "last_updated": "2026-08-13", "type": "chat" }, { - "id": "lightonai/LightOnOCR-2-1B", - "name": "LightOnOCR 2", - "display_name": "LightOnOCR 2", + "id": "google/gemini-3.6-flash", + "name": "Gemini 3.6 Flash", + "display_name": "Gemini 3.6 Flash", "modalities": { "input": [ "text", - "image" + "image", + "video", + "audio", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 32768, - "output": 8192 + "context": 1048576, + "output": 65536 }, + "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "level", + "level": "high", + "level_options": [ + "minimal", + "low", + "medium", + "high" + ], + "summaries": true, + "visibility": "summary", + "continuation": [ + "thought_signatures" + ] + } }, "attachment": true, - "open_weights": true, - "release_date": "2026-09-10", - "last_updated": "2026-09-10", + "open_weights": false, + "knowledge": "2026-03", + "release_date": "2026-07-21", + "last_updated": "2026-07-21", "type": "chat" }, { - "id": "shisa-ai/shisa-v2-llama3.3-70b", - "name": "Shisa V2 Llama 3.3 70B", - "display_name": "Shisa V2 Llama 3.3 70B", + "id": "google/gemma-3-12b-it", + "name": "Gemma 3 12B", + "display_name": "Gemma 3 12B", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 16384 + "context": 131000, + "output": 96000 }, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { "supported": false }, - "attachment": false, + "attachment": true, "open_weights": true, - "release_date": "2025-07-26", - "last_updated": "2025-07-26", + "knowledge": "2024-08", + "release_date": "2025-03-12", + "last_updated": "2025-03-12", "type": "chat" }, { - "id": "shisa-ai/shisa-v2.1-llama3.3-70b", - "name": "Shisa V2.1 Llama 3.3 70B", - "display_name": "Shisa V2.1 Llama 3.3 70B", + "id": "google/gemma-3-27b-it", + "name": "Gemma 3 27B", + "display_name": "Gemma 3 27B", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 32768, - "output": 4096 + "context": 128000, + "output": 117964 }, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { "supported": false }, - "attachment": false, + "attachment": true, "open_weights": true, - "release_date": "2024-01-01", - "last_updated": "2024-12-06", + "knowledge": "2024-08", + "release_date": "2025-03-12", + "last_updated": "2025-03-12", "type": "chat" }, { - "id": "heabsy/cyberheabsy", - "name": "CyberHeabsy", - "display_name": "CyberHeabsy", + "id": "google/gemini-3.8-flash", + "name": "Gemini 3.8 Flash", + "display_name": "Gemini 3.8 Flash", "modalities": { "input": [ - "text" + "text", + "image", + "video", + "audio", + "pdf" ], "output": [ "text" @@ -256306,6 +255946,7 @@ "context": 1048576, "output": 65536 }, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -256313,37 +255954,50 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "none", + "supported": true, + "default_enabled": true, + "mode": "level", + "level": "high", + "level_options": [ + "minimal", "low", "medium", "high" + ], + "summaries": true, + "visibility": "summary", + "continuation": [ + "thought_signatures" ] } }, - "attachment": false, + "attachment": true, "open_weights": false, - "release_date": "2026-09-27", - "last_updated": "2026-09-27", + "release_date": "2026-09-02", + "last_updated": "2026-09-02", "type": "chat" }, { - "id": "heabsy/qwen3.8-27b-uncensored-fast", - "name": "Qwen 3.8 27B Uncensored Fast by Heabsy", - "display_name": "Qwen 3.8 27B Uncensored Fast by Heabsy", + "id": "google/gemini-3.1-flash-lite-preview", + "name": "Gemini 3.1 Flash Lite Preview", + "display_name": "Gemini 3.1 Flash Lite Preview", "modalities": { "input": [ "text", - "image" + "image", + "video", + "audio", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 524288, - "output": 235929 + "context": 1000000, + "output": 65536 }, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -256351,22 +256005,31 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ - "none", + "low", + "medium", "high" ] } }, "attachment": true, "open_weights": false, - "release_date": "2026-10-16", - "last_updated": "2026-10-16", + "knowledge": "2025-01", + "release_date": "2026-03-03", + "last_updated": "2026-03-03", "type": "chat" }, { - "id": "moondream/moondream3.1-9b-a2b", - "name": "Moondream 3.1 9B A2B", - "display_name": "Moondream 3.1 9B A2B", + "id": "google/gemma-3-4b-it", + "name": "Gemma 3 4B", + "display_name": "Gemma 3 4B", "modalities": { "input": [ "text", @@ -256377,65 +256040,120 @@ ] }, "limit": { - "context": 32000, - "output": 4096 + "context": 131000, + "output": 32768 }, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { - "supported": true, - "default": true + "supported": false }, "attachment": true, - "open_weights": false, - "release_date": "2026-10-16", - "last_updated": "2026-10-16", + "open_weights": true, + "knowledge": "2024-08", + "release_date": "2025-03-12", + "last_updated": "2025-03-12", "type": "chat" }, { - "id": "moondream/moondream3-preview", - "name": "Moondream 3 Preview", - "display_name": "Moondream 3 Preview", + "id": "google/gemma-2-27b-it", + "name": "Gemma 2 27B", + "display_name": "Gemma 2 27B", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 32000, - "output": 4096 + "context": 8192, + "output": 2048 }, + "temperature": true, "tool_call": false, + "reasoning": { + "supported": false + }, + "attachment": false, + "open_weights": true, + "knowledge": "2024-06-30", + "release_date": "2024-07-13", + "last_updated": "2024-07-13", + "type": "chat" + }, + { + "id": "google/gemini-3.1-pro-preview", + "name": "Gemini 3.1 Pro Preview", + "display_name": "Gemini 3.1 Pro Preview", + "modalities": { + "input": [ + "text", + "image", + "video", + "audio", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1000000, + "output": 65536 + }, + "temperature": true, + "tool_call": true, "reasoning": { "supported": true, "default": true }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "level", + "level": "high", + "level_options": [ + "low", + "high" + ], + "summaries": true, + "visibility": "summary", + "continuation": [ + "thought_signatures" + ] + } + }, "attachment": true, "open_weights": false, - "release_date": "2026-10-16", - "last_updated": "2026-10-16", + "knowledge": "2025-01", + "release_date": "2026-02-19", + "last_updated": "2026-02-19", "type": "chat" }, { - "id": "prism-ml/ternary-bonsai-2-27b", - "name": "Ternary Bonsai 2 27B", - "display_name": "Ternary Bonsai 2 27B", + "id": "google/gemini-3.7-flash", + "name": "Gemini 3.7 Flash", + "display_name": "Gemini 3.7 Flash", "modalities": { "input": [ "text", - "image" + "image", + "video", + "audio", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 32768 + "context": 1048576, + "output": 65536 }, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -256443,63 +256161,86 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ + "supported": true, + "default_enabled": true, + "mode": "level", + "level": "high", + "level_options": [ + "minimal", + "low", "medium", - "xhigh" + "high" + ], + "summaries": true, + "visibility": "summary", + "continuation": [ + "thought_signatures" ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-09-18", - "last_updated": "2026-09-18", + "open_weights": false, + "knowledge": "2026-03", + "release_date": "2026-08-13", + "last_updated": "2026-08-13", "type": "chat" }, { - "id": "prism-ml/ternary-bonsai-2-9b", - "name": "Ternary Bonsai2 9B", - "display_name": "Ternary Bonsai2 9B", + "id": "google/gemma-4-31b-it", + "name": "Gemma 4 31B Instruct", + "display_name": "Gemma 4 31B Instruct", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 65536 + "context": 256000, + "output": 16384 }, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, "default": true }, - "attachment": false, + "extra_capabilities": { + "reasoning": { + "supported": true + } + }, + "attachment": true, "open_weights": true, - "release_date": "2026-10-23", - "last_updated": "2026-10-23", + "knowledge": "2025-01", + "release_date": "2026-04-02", + "last_updated": "2026-04-02", "type": "chat" }, { - "id": "moonshotai/kimi-latest", - "name": "Kimi Latest", - "display_name": "Kimi Latest", + "id": "google/gemini-3.1-pro-preview-customtools", + "name": "Gemini 3.1 Pro Preview Custom Tools", + "display_name": "Gemini 3.1 Pro Preview Custom Tools", "modalities": { "input": [ "text", "image", - "video" + "video", + "audio", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 943718 + "context": 1048756, + "output": 65536 }, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -256507,34 +256248,47 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ + "supported": true, + "default_enabled": true, + "mode": "level", + "level": "high", + "level_options": [ "low", - "high", - "max" + "high" + ], + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, "attachment": true, "open_weights": false, - "release_date": "2026-05-03", - "last_updated": "2026-05-03", + "knowledge": "2025-01", + "release_date": "2026-02-19", + "last_updated": "2026-02-19", "type": "chat" }, { - "id": "moonshotai/kimi-k2.6:thinking", - "name": "Kimi K2.6 Thinking", - "display_name": "Kimi K2.6 Thinking", + "id": "google/gemini-3-8-flash", + "name": "Gemini 3.8 Flash", + "display_name": "Gemini 3.8 Flash", "modalities": { "input": [ "text", - "image" + "image", + "video", + "audio", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 262144, + "context": 1048576, "output": 65536 }, "temperature": true, @@ -256543,17 +256297,25 @@ "supported": true, "default": true }, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "low", + "medium", + "high" + ] + } + }, "attachment": true, - "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-04-20", - "last_updated": "2026-04-20", + "open_weights": false, + "release_date": "2026-09-02", + "last_updated": "2026-09-02", "type": "chat" }, { - "id": "moonshotai/kimi-k2-instruct", - "name": "Kimi K2 Instruct", - "display_name": "Kimi K2 Instruct", + "id": "gryphe/mythomax-l2-13b", + "name": "MythoMax 13B", + "display_name": "MythoMax 13B", "modalities": { "input": [ "text" @@ -256563,67 +256325,87 @@ ] }, "limit": { - "context": 256000, - "output": 8192 + "context": 4096, + "output": 4096 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { "supported": false }, "attachment": false, "open_weights": true, - "release_date": "2025-07-11", - "last_updated": "2025-07-11", + "release_date": "2023-08-10", + "last_updated": "2023-08-10", "type": "chat" }, { - "id": "moonshotai/kimi-k3", - "name": "Kimi K3", - "display_name": "Kimi K3", + "id": "mistralai/mistral-medium-3.1", + "name": "Medium 3.1", + "display_name": "Medium 3.1", "modalities": { "input": [ "text", "image", - "video" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 943718 + "context": 131072, + "output": 104857 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true + "supported": false }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "effort_options": [ - "low", - "high", - "max" - ] - } + "attachment": true, + "open_weights": false, + "knowledge": "2025-06-30", + "release_date": "2025-08-12", + "last_updated": "2025-08-12", + "type": "chat" + }, + { + "id": "mistralai/mistral-large-2407", + "name": "Large 2407", + "display_name": "Large 2407", + "modalities": { + "input": [ + "text", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "output": 104857 + }, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": false }, "attachment": true, "open_weights": true, - "release_date": "2026-07-16", - "last_updated": "2026-07-16", + "knowledge": "2024-03-31", + "release_date": "2024-07-24", + "last_updated": "2024-07-24", "type": "chat" }, { - "id": "moonshotai/Kimi-K2-Instruct-0905", - "name": "Kimi K2 0905", - "display_name": "Kimi K2 0905", + "id": "mistralai/ministral-14b-2512", + "name": "Ministral 3 14B 2512", + "display_name": "Ministral 3 14B 2512", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" @@ -256631,23 +256413,23 @@ }, "limit": { "context": 262144, - "output": 100352 + "output": 209715 }, "temperature": true, "tool_call": true, "reasoning": { "supported": false }, - "attachment": false, + "attachment": true, "open_weights": true, - "release_date": "2025-09-05", - "last_updated": "2025-09-05", + "release_date": "2025-12-02", + "last_updated": "2025-12-02", "type": "chat" }, { - "id": "moonshotai/kimi-k2-instruct-0711", - "name": "Kimi K2 0711", - "display_name": "Kimi K2 0711", + "id": "mistralai/codestral-2508", + "name": "Codestral 2508", + "display_name": "Codestral 2508", "modalities": { "input": [ "text" @@ -256657,7 +256439,7 @@ ] }, "limit": { - "context": 128000, + "context": 256000, "output": 8192 }, "temperature": true, @@ -256666,15 +256448,44 @@ "supported": false }, "attachment": false, - "open_weights": true, - "release_date": "2025-07-11", - "last_updated": "2025-07-11", + "open_weights": false, + "release_date": "2025-07-30", + "last_updated": "2025-07-30", "type": "chat" }, { - "id": "moonshotai/kimi-k2.6", - "name": "Kimi K2.6", - "display_name": "Kimi K2.6", + "id": "mistralai/mistral-saba", + "name": "Saba", + "display_name": "Saba", + "modalities": { + "input": [ + "text", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 32768, + "output": 26214 + }, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": false + }, + "attachment": true, + "open_weights": false, + "knowledge": "2024-09-30", + "release_date": "2025-02-17", + "last_updated": "2025-02-17", + "type": "chat" + }, + { + "id": "mistralai/ministral-8b-2512", + "name": "Ministral 3 8B 2512", + "display_name": "Ministral 3 8B 2512", "modalities": { "input": [ "text", @@ -256686,29 +256497,23 @@ }, "limit": { "context": 262144, - "output": 65536 + "output": 209715 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true - } + "supported": false }, "attachment": true, "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-04-20", - "last_updated": "2026-04-20", + "release_date": "2025-12-02", + "last_updated": "2025-12-02", "type": "chat" }, { - "id": "moonshotai/kimi-k2.5", - "name": "Kimi K2.5", - "display_name": "Kimi K2.5", + "id": "mistralai/ministral-3b-2512", + "name": "Ministral 3 3B 2512", + "display_name": "Ministral 3 3B 2512", "modalities": { "input": [ "text", @@ -256719,143 +256524,111 @@ ] }, "limit": { - "context": 262144, - "output": 65536 + "context": 131072, + "output": 104857 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { - "supported": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } + "supported": false }, "attachment": true, "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-01-27", - "last_updated": "2026-01-27", + "release_date": "2025-12-02", + "last_updated": "2025-12-02", "type": "chat" }, { - "id": "moonshotai/kimi-k3.1-pro-highspeed", - "name": "Kimi K3.1 Pro Highspeed", - "display_name": "Kimi K3.1 Pro Highspeed", + "id": "mistralai/mixtral-8x22b-instruct", + "name": "Mixtral 8x22B Instruct", + "display_name": "Mixtral 8x22B Instruct", "modalities": { "input": [ "text", - "image", - "video" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 1048576 + "context": 65536, + "output": 52428 }, + "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "low", - "high", - "max" - ] - } + "supported": false }, "attachment": true, - "open_weights": false, - "release_date": "2026-10-24", - "last_updated": "2026-10-24", + "open_weights": true, + "knowledge": "2024-01-31", + "release_date": "2024-04-17", + "last_updated": "2024-04-17", "type": "chat" }, { - "id": "moonshotai/kimi-k3.1-pro", - "name": "Kimi K3.1 Pro", - "display_name": "Kimi K3.1 Pro", + "id": "mistralai/mistral-medium-3", + "name": "Medium 3", + "display_name": "Medium 3", "modalities": { "input": [ "text", "image", - "video" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 1048576 + "context": 131072, + "output": 104857 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "low", - "high", - "max" - ] - } + "supported": false }, "attachment": true, "open_weights": false, - "release_date": "2026-10-23", - "last_updated": "2026-10-23", + "knowledge": "2025-03-31", + "release_date": "2025-05-07", + "last_updated": "2025-05-07", "type": "chat" }, { - "id": "moonshotai/kimi-k2.5:thinking", - "name": "Kimi K2.5 Thinking", - "display_name": "Kimi K2.5 Thinking", + "id": "mistralai/voxtral-small-24b-2507", + "name": "Voxtral Small 24B 2507", + "display_name": "Voxtral Small 24B 2507", "modalities": { "input": [ "text", - "image" + "audio" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 65536 + "context": 32000, + "output": 32768 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true + "supported": false }, "attachment": true, "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-01-27", - "last_updated": "2026-01-27", + "release_date": "2025-07-15", + "last_updated": "2025-07-15", "type": "chat" }, { - "id": "moonshotai/kimi-k2-thinking", - "name": "Kimi K2 Thinking", - "display_name": "Kimi K2 Thinking", + "id": "mistralai/mistral-nemo", + "name": "Mistral NeMo", + "display_name": "Mistral NeMo", "modalities": { "input": [ "text" @@ -256865,37 +256638,25 @@ ] }, "limit": { - "context": 262144, - "output": 98304 + "context": 128000, + "output": 16384 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } + "supported": false }, "attachment": false, "open_weights": true, - "knowledge": "2024-08", - "release_date": "2025-11-06", - "last_updated": "2025-11-06", + "knowledge": "2024-07", + "release_date": "2024-07-18", + "last_updated": "2024-07-18", "type": "chat" }, { - "id": "moonshotai/kimi-k2.7-code", - "name": "Kimi K2.7 Code", - "display_name": "Kimi K2.7 Code", + "id": "mistralai/mistral-small-3.1-24b-instruct", + "name": "Small 3.1 24B", + "display_name": "Small 3.1 24B", "modalities": { "input": [ "text", @@ -256906,94 +256667,111 @@ ] }, "limit": { - "context": 262144, - "output": 65536 + "context": 128000, + "output": 128000 }, - "temperature": false, - "tool_call": true, + "temperature": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true - } + "supported": false }, "attachment": true, "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-06-12", - "last_updated": "2026-06-12", + "knowledge": "2023-10-31", + "release_date": "2025-03-17", + "last_updated": "2025-03-17", "type": "chat" }, { - "id": "moonshotai/kimi-k2.7-code-highspeed", - "name": "Kimi K2.7 Code High-Speed", - "display_name": "Kimi K2.7 Code High-Speed", + "id": "mistralai/mistral-large", + "name": "Large", + "display_name": "Large", "modalities": { "input": [ "text", - "image" + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 102400 + }, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": false + }, + "attachment": true, + "open_weights": false, + "knowledge": "2024-11-30", + "release_date": "2024-02-26", + "last_updated": "2024-02-26", + "type": "chat" + }, + { + "id": "mistralai/mistral-small-3.2-24b-instruct", + "name": "Small 3.2 24B", + "display_name": "Small 3.2 24B", + "modalities": { + "input": [ + "image", + "text" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 65536 + "context": 128000, + "output": 16384 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true - } + "supported": false }, "attachment": true, "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-06-12", - "last_updated": "2026-06-12", + "knowledge": "2023-10-31", + "release_date": "2025-06-20", + "last_updated": "2025-06-20", "type": "chat" }, { - "id": "qwen/qwen3.8-27b-uncensored:thinking", - "name": "Qwen 3.8 27B Uncensored Thinking", - "display_name": "Qwen 3.8 27B Uncensored Thinking", + "id": "mistralai/mistral-small-24b-instruct-2501", + "name": "Small 3", + "display_name": "Small 3", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 32768, + "output": 16384 }, - "tool_call": true, + "temperature": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true + "supported": false }, - "attachment": true, + "attachment": false, "open_weights": true, - "release_date": "2026-07-29", - "last_updated": "2026-08-25", + "knowledge": "2023-10-31", + "release_date": "2025-01-30", + "last_updated": "2025-01-30", "type": "chat" }, { - "id": "qwen/qwen3.7-max", - "name": "Qwen3.7 Max", - "display_name": "Qwen3.7 Max", + "id": "mistralai/devstral-2512", + "name": "Devstral 2 2512", + "display_name": "Devstral 2 2512", "modalities": { "input": [ "text" @@ -257003,47 +256781,38 @@ ] }, "limit": { - "context": 1000000, - "output": 65536 + "context": 262144, + "output": 262144 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "effort_options": [ - "none", - "high" - ] - } + "supported": false }, "attachment": false, - "open_weights": false, - "release_date": "2026-05-21", - "last_updated": "2026-05-21", + "open_weights": true, + "knowledge": "2025-12", + "release_date": "2025-12-09", + "last_updated": "2025-12-09", "type": "chat" }, { - "id": "qwen/qwen3.8-max-prime", - "name": "Qwen3.8 Max Prime", - "display_name": "Qwen3.8 Max Prime", + "id": "mistralai/mistral-medium-3-5", + "name": "Medium 3.5", + "display_name": "Medium 3.5", "modalities": { "input": [ "text", "image", - "video" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 262144, + "output": 209715 }, "temperature": true, "tool_call": true, @@ -257054,37 +256823,32 @@ "extra_capabilities": { "reasoning": { "effort_options": [ - "minimal", - "low", - "medium", - "high", - "xhigh" + "high" ] } }, "attachment": true, "open_weights": false, - "release_date": "2026-09-23", - "last_updated": "2026-09-23", + "release_date": "2026-04-30", + "last_updated": "2026-04-30", "type": "chat" }, { - "id": "qwen/qwen3.6-35b-a3b:thinking", - "name": "Qwen3.6 35B A3B Thinking", - "display_name": "Qwen3.6 35B A3B Thinking", + "id": "mistralai/mistral-small-2603", + "name": "Small 4", + "display_name": "Small 4", "modalities": { "input": [ "text", - "image", - "video" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 16384 + "context": 262144, + "output": 256000 }, "temperature": true, "tool_call": true, @@ -257094,25 +256858,23 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "none", + "high" ] } }, "attachment": true, "open_weights": true, - "release_date": "2026-04-17", - "last_updated": "2026-04-17", + "knowledge": "2025-06", + "release_date": "2026-03-16", + "last_updated": "2026-03-16", "type": "chat" }, { - "id": "qwen/qwen3-235b-a22b-thinking-2507", - "name": "Qwen 3 235b A22B 2507 Thinking", - "display_name": "Qwen 3 235b A22B 2507 Thinking", + "id": "alibaba/qwen3.8-2.4t-a95b", + "name": "Qwen3.8 2.4T A95B", + "display_name": "Qwen3.8 2.4T A95B", "modalities": { "input": [ "text" @@ -257122,13 +256884,14 @@ ] }, "limit": { - "context": 131072, - "output": 81920 + "context": 262144, + "output": 52429 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true + "supported": true, + "default": true }, "extra_capabilities": { "reasoning": { @@ -257138,19 +256901,23 @@ "visibility": "summary", "continuation": [ "thinking_blocks" + ], + "effort_options": [ + "low", + "medium" ] } }, "attachment": false, "open_weights": true, - "release_date": "2025-07-25", - "last_updated": "2025-07-25", + "release_date": "2026-08-12", + "last_updated": "2026-08-12", "type": "chat" }, { - "id": "qwen/qwen3-30b-a3b-instruct-2507", - "name": "Qwen3 30B A3B Instruct 2507", - "display_name": "Qwen3 30B A3B Instruct 2507", + "id": "alibaba/qwen3-next-80b-a3b-instruct", + "name": "Qwen3 Next 80B A3B Instruct", + "display_name": "Qwen3 Next 80B A3B Instruct", "modalities": { "input": [ "text" @@ -257160,90 +256927,36 @@ ] }, "limit": { - "context": 256000, - "output": 32768 + "context": 129024, + "output": 16384 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { "supported": false }, "attachment": false, "open_weights": true, - "release_date": "2025-07-28", - "last_updated": "2025-07-28", - "type": "chat" - }, - { - "id": "qwen/qwen-long", - "name": "Qwen Long 10M", - "display_name": "Qwen Long 10M", - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 10000000, - "output": 8192 - }, - "tool_call": false, - "reasoning": { - "supported": false - }, - "attachment": false, - "open_weights": false, - "release_date": "2024-08-01", - "last_updated": "2024-08-01", + "knowledge": "2025-04", + "release_date": "2025-09", + "last_updated": "2025-09", "type": "chat" }, { - "id": "qwen/qwen2.5-vl-72b-instruct", - "name": "Qwen25 VL 72b", - "display_name": "Qwen25 VL 72b", + "id": "alibaba/qwen-plus", + "name": "Qwen Plus", + "display_name": "Qwen Plus", "modalities": { "input": [ - "text", - "image" - ], - "output": [ "text" - ] - }, - "limit": { - "context": 32000, - "output": 32000 - }, - "tool_call": false, - "reasoning": { - "supported": false - }, - "attachment": true, - "open_weights": true, - "release_date": "2025-05-10", - "last_updated": "2025-05-10", - "type": "chat" - }, - { - "id": "qwen/qwen3.5-9b", - "name": "Qwen3.5 9B", - "display_name": "Qwen3.5 9B", - "modalities": { - "input": [ - "text", - "image", - "video" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 65536 + "context": 131000, + "output": 16384 }, "temperature": true, "tool_call": true, @@ -257262,29 +256975,29 @@ ] } }, - "attachment": true, - "open_weights": true, - "release_date": "2026-02-23", - "last_updated": "2026-02-23", + "attachment": false, + "open_weights": false, + "knowledge": "2024-04", + "release_date": "2024-01-25", + "last_updated": "2025-09-11", "type": "chat" }, { - "id": "qwen/qwen3.8-max-0902", - "name": "Qwen3.8 Max 0902", - "display_name": "Qwen3.8 Max 0902", + "id": "alibaba/qwen3.8-flash", + "name": "Qwen3.8 Flash", + "display_name": "Qwen3.8 Flash", "modalities": { "input": [ "text", "image", - "video", - "pdf" + "video" ], "output": [ "text" ] }, "limit": { - "context": 991808, + "context": 1000000, "output": 131072 }, "temperature": true, @@ -257295,25 +257008,29 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ - "none", "low", - "medium", - "high", - "max" + "medium" ] } }, "attachment": true, "open_weights": false, - "release_date": "2026-09-02", - "last_updated": "2026-09-02", + "release_date": "2026-08-26", + "last_updated": "2026-08-26", "type": "chat" }, { - "id": "qwen/qwen3-coder-30b-a3b-instruct", - "name": "Qwen3 Coder 30B A3B Instruct", - "display_name": "Qwen3 Coder 30B A3B Instruct", + "id": "alibaba/qwen3-coder-480b-a35b-instruct", + "name": "Qwen3 Coder", + "display_name": "Qwen3 Coder", "modalities": { "input": [ "text" @@ -257323,7 +257040,7 @@ ] }, "limit": { - "context": 128000, + "context": 262000, "output": 65536 }, "temperature": true, @@ -257334,42 +257051,14 @@ "attachment": false, "open_weights": true, "knowledge": "2025-04", - "release_date": "2025-07-31", - "last_updated": "2025-07-31", - "type": "chat" - }, - { - "id": "qwen/qwen3.8-27b-antislop", - "name": "Qwen 3.8 27B Antislop", - "display_name": "Qwen 3.8 27B Antislop", - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 262144, - "output": 32768 - }, - "tool_call": true, - "reasoning": { - "supported": true, - "default": true - }, - "attachment": true, - "open_weights": true, - "release_date": "2026-10-05", - "last_updated": "2026-10-05", + "release_date": "2025-07-22", + "last_updated": "2025-07-22", "type": "chat" }, { - "id": "qwen/qwen3.8-27b", - "name": "Qwen3.8 27B", - "display_name": "Qwen3.8 27B", + "id": "alibaba/qwen3.8-max-prime", + "name": "Qwen3.8 Max Prime", + "display_name": "Qwen3.8 Max Prime", "modalities": { "input": [ "text", @@ -257381,8 +257070,8 @@ ] }, "limit": { - "context": 1048576, - "output": 32768 + "context": 1000000, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -257390,16 +257079,27 @@ "supported": true, "default": true }, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "minimal", + "low", + "medium", + "high", + "xhigh" + ] + } + }, "attachment": true, - "open_weights": true, - "release_date": "2026-08-14", - "last_updated": "2026-08-14", + "open_weights": false, + "release_date": "2026-09-23", + "last_updated": "2026-09-23", "type": "chat" }, { - "id": "qwen/qwen3.5-35b-a3b:thinking", - "name": "Qwen3.5 35B A3B Thinking", - "display_name": "Qwen3.5 35B A3B Thinking", + "id": "alibaba/qwen3.8-27b", + "name": "Qwen3.8 27B", + "display_name": "Qwen3.8 27B", "modalities": { "input": [ "text", @@ -257411,8 +257111,8 @@ ] }, "limit": { - "context": 260096, - "output": 65536 + "context": 262144, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -257428,58 +257128,37 @@ "visibility": "summary", "continuation": [ "thinking_blocks" + ], + "effort_options": [ + "low", + "medium" ] } }, "attachment": true, "open_weights": true, - "release_date": "2026-02-23", - "last_updated": "2026-02-23", - "type": "chat" - }, - { - "id": "qwen/qwen3.8-27b-synthia", - "name": "Qwen 3.8 27B Synthia", - "display_name": "Qwen 3.8 27B Synthia", - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 262144, - "output": 32768 - }, - "tool_call": false, - "reasoning": { - "supported": false - }, - "attachment": false, - "open_weights": true, - "release_date": "2026-10-05", - "last_updated": "2026-10-05", + "release_date": "2026-08-14", + "last_updated": "2026-08-14", "type": "chat" }, { - "id": "qwen/qwen3.7-flash:thinking", - "name": "Qwen3.7 Flash Thinking", - "display_name": "Qwen3.7 Flash Thinking", + "id": "alibaba/qwen3.8-max", + "name": "Qwen3.8 Max", + "display_name": "Qwen3.8 Max", "modalities": { "input": [ "text", "image", - "video" + "video", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 983616, - "output": 65536 + "context": 1000000, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -257489,22 +257168,29 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ - "none", - "high" + "low", + "medium" ] } }, "attachment": true, "open_weights": false, - "release_date": "2026-07-15", - "last_updated": "2026-07-15", + "release_date": "2026-08-03", + "last_updated": "2026-08-03", "type": "chat" }, { - "id": "qwen/qwen-turbo", - "name": "Qwen Turbo", - "display_name": "Qwen Turbo", + "id": "alibaba/qwen-max", + "name": "Qwen Max", + "display_name": "Qwen Max", "modalities": { "input": [ "text" @@ -257514,47 +257200,35 @@ ] }, "limit": { - "context": 1000000, + "context": 32000, "output": 8192 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } + "supported": false }, "attachment": false, "open_weights": false, "knowledge": "2024-04", - "release_date": "2024-11-01", - "last_updated": "2025-04-28", + "release_date": "2024-04-03", + "last_updated": "2025-01-25", "type": "chat" }, { - "id": "qwen/qwen3-vl-235b-a22b-instruct", - "name": "Qwen3 VL 235B A22B Instruct", - "display_name": "Qwen3 VL 235B A22B Instruct", + "id": "aion-labs/aion-rp-llama-3.1-8b", + "name": "Aion-RP 1.0 (8B)", + "display_name": "Aion-RP 1.0 (8B)", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 131072, + "context": 32768, "output": 32768 }, "temperature": true, @@ -257562,73 +257236,84 @@ "reasoning": { "supported": false }, - "attachment": true, + "attachment": false, "open_weights": true, - "knowledge": "2025-03-31", - "release_date": "2025-09-23", - "last_updated": "2025-09-23", + "knowledge": "2023-12-31", + "release_date": "2025-02-04", + "last_updated": "2025-02-04", "type": "chat" }, { - "id": "qwen/qwen3.6-flash", - "name": "Qwen3.6 Flash", - "display_name": "Qwen3.6 Flash", + "id": "anthropic/claude-haiku-4.5", + "name": "Claude 4.5 Haiku", + "display_name": "Claude 4.5 Haiku", "modalities": { "input": [ "text", "image", - "video" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 991808, - "output": 65536 + "context": 200000, + "output": 64000 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": true + "supported": true, + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": false, + "mode": "budget", + "budget": { + "min": 1024, + "unit": "tokens" + }, "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ "thinking_blocks" + ], + "notes": [ + "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", + "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." ] } }, "attachment": true, "open_weights": false, - "release_date": "2026-04-27", - "last_updated": "2026-04-27", + "knowledge": "2025-02-28", + "release_date": "2025-10-15", + "last_updated": "2025-10-15", "type": "chat" }, { - "id": "qwen/qwen3.8-omni-flash", - "name": "Qwen3.8 Omni Flash", - "display_name": "Qwen3.8 Omni Flash", + "id": "anthropic/claude-fable-5.1", + "name": "Claude Fable 5.1", + "display_name": "Claude Fable 5.1", "modalities": { "input": [ "text", "image", - "audio", - "video" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 991808, - "output": 131072 + "context": 1000000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -257636,151 +257321,203 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "high", "effort_options": [ - "none", - "high" + "low", + "medium", + "high", + "xhigh", + "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "omitted", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Adaptive thinking is always on for Claude Fable 5 and Claude Mythos 5; thinking.type = \"disabled\" is rejected.", + "Manual budget_tokens requests return 400 on Claude Fable 5 and Claude Mythos 5.", + "thinking.display defaults to omitted; set display to summarized to receive readable thinking summaries." ] } }, "attachment": true, "open_weights": false, - "release_date": "2026-09-17", - "last_updated": "2026-09-17", + "knowledge": "2026-06", + "release_date": "2026-09-01", + "last_updated": "2026-09-01", "type": "chat" }, { - "id": "qwen/qwen3.8-27b-queen", - "name": "Qwen 3.8 27B Queen", - "display_name": "Qwen 3.8 27B Queen", + "id": "anthropic/claude-sonnet-4.5", + "name": "Claude 4.5 Sonnet", + "display_name": "Claude 4.5 Sonnet", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 524288, - "output": 32768 + "context": 200000, + "output": 64000 }, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": false, + "mode": "budget", + "budget": { + "min": 1024, + "unit": "tokens" + }, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", + "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." + ] + } }, "attachment": true, - "open_weights": true, - "release_date": "2026-09-09", - "last_updated": "2026-09-09", + "open_weights": false, + "knowledge": "2025-07-31", + "release_date": "2025-09-29", + "last_updated": "2025-09-29", "type": "chat" }, { - "id": "qwen/qwen3.8-27b-darkidol", - "name": "Qwen 3.8 27B DarkIdol", - "display_name": "Qwen 3.8 27B DarkIdol", + "id": "anthropic/claude-sonnet-5", + "name": "Claude Sonnet 5", + "display_name": "Claude Sonnet 5", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 32768 + "context": 1000000, + "output": 128000 }, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, "default": true }, - "attachment": true, - "open_weights": true, - "release_date": "2026-10-05", - "last_updated": "2026-10-05", - "type": "chat" - }, - { - "id": "qwen/qwen3-coder-next", - "name": "Qwen3 Coder Next", - "display_name": "Qwen3 Coder Next", - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 262144, - "output": 65536 - }, - "temperature": true, - "tool_call": true, - "reasoning": { - "supported": false + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" + ] + } }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-09", - "release_date": "2026-02-03", - "last_updated": "2026-02-03", + "attachment": true, + "open_weights": false, + "knowledge": "2026-01-31", + "release_date": "2026-06-30", + "last_updated": "2026-06-30", "type": "chat" }, { - "id": "qwen/qwen3.5-35b-a3b", - "name": "Qwen3.5 35B A3B", - "display_name": "Qwen3.5 35B A3B", + "id": "anthropic/claude-fable-5", + "name": "Claude Fable 5", + "display_name": "Claude Fable 5", "modalities": { "input": [ "text", "image", - "video" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 260096, - "output": 65536 + "context": 1000000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { - "supported": true + "supported": true, + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "high", + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" + ], "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ "thinking_blocks" + ], + "notes": [ + "Adaptive thinking is always on for Claude Fable 5 and Claude Mythos 5; thinking.type = \"disabled\" is rejected.", + "Manual budget_tokens requests return 400 on Claude Fable 5 and Claude Mythos 5.", + "thinking.display defaults to omitted; set display to summarized to receive readable thinking summaries." ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-02-23", - "last_updated": "2026-02-23", + "open_weights": false, + "knowledge": "2026-01-31", + "release_date": "2026-06-09", + "last_updated": "2026-06-09", "type": "chat" }, { - "id": "qwen/qwen3.8-max:thinking", - "name": "Qwen3.8 Max Thinking", - "display_name": "Qwen3.8 Max Thinking", + "id": "anthropic/claude-haiku-5.5", + "name": "Claude Haiku 5.5", + "display_name": "Claude Haiku 5.5", "modalities": { "input": [ "text", "image", - "video", "pdf" ], "output": [ @@ -257788,10 +257525,10 @@ ] }, "limit": { - "context": 991000, - "output": 131072 + "context": 1000000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -257803,224 +257540,251 @@ "low", "medium", "high", + "xhigh", "max" ] } }, "attachment": true, "open_weights": false, - "release_date": "2026-08-03", - "last_updated": "2026-08-03", + "knowledge": "2026-06", + "release_date": "2026-10-07", + "last_updated": "2026-10-07", "type": "chat" }, { - "id": "qwen/qwen3.5-plus", - "name": "Qwen3.5 Plus", - "display_name": "Qwen3.5 Plus", + "id": "anthropic/claude-opus-4.5", + "name": "Claude 4.5 Opus", + "display_name": "Claude 4.5 Opus", "modalities": { "input": [ "text", "image", - "video" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 983616, - "output": 65536 + "context": 200000, + "output": 64000 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": true + "supported": true, + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": false, + "mode": "mixed", + "budget": { + "min": 1024, + "unit": "tokens" + }, + "effort": "high", + "effort_options": [ + "low", + "medium", + "high" + ], "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ "thinking_blocks" + ], + "notes": [ + "Claude Opus 4.5 uses manual thinking.type = \"enabled\" with budget_tokens; effort can be used alongside the thinking budget.", + "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header." ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-04", - "release_date": "2026-02-16", - "last_updated": "2026-02-16", + "knowledge": "2025-05", + "release_date": "2025-11-24", + "last_updated": "2025-11-24", "type": "chat" }, { - "id": "qwen/qwen3.8-27b-cybersecurity", - "name": "Qwen 3.8 27B Cybersecurity", - "display_name": "Qwen 3.8 27B Cybersecurity", + "id": "anthropic/claude-sonnet-5.5", + "name": "Claude Sonnet 5.5", + "display_name": "Claude Sonnet 5.5", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 32768 + "context": 1000000, + "output": 128000 }, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, "default": true }, - "attachment": true, - "open_weights": true, - "release_date": "2026-09-19", - "last_updated": "2026-09-19", - "type": "chat" - }, - { - "id": "qwen/qwen-2.5-72b-instruct", - "name": "Qwen2.5 72B", - "display_name": "Qwen2.5 72B", - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 8192 - }, - "tool_call": true, - "reasoning": { - "supported": false + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" + ] + } }, - "attachment": false, - "open_weights": true, - "release_date": "2025-07-03", - "last_updated": "2025-07-03", + "attachment": true, + "open_weights": false, + "knowledge": "2026-06", + "release_date": "2026-09-28", + "last_updated": "2026-09-28", "type": "chat" }, { - "id": "qwen/qwen3.8-27b-swift", - "name": "Qwen 3.8 27B Swift", - "display_name": "Qwen 3.8 27B Swift", + "id": "anthropic/claude-opus-4.7", + "name": "Claude 4.7 Opus", + "display_name": "Claude 4.7 Opus", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 32768 + "context": 1000000, + "output": 128000 }, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": false, + "mode": "effort", + "effort": "high", + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "omitted", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Claude Opus 4.7 and newer Opus models require thinking.type = \"adaptive\" to enable thinking explicitly.", + "Manual budget_tokens requests return 400 on Claude Opus 4.7 and newer adaptive-only Opus models.", + "task_budget is separate from thinking control and should not be treated as a thinking budget." + ] + } }, "attachment": true, - "open_weights": true, - "release_date": "2026-10-05", - "last_updated": "2026-10-05", + "open_weights": false, + "knowledge": "2026-01-31", + "release_date": "2026-04-16", + "last_updated": "2026-04-16", "type": "chat" }, { - "id": "qwen/qwen3.5-flash", - "name": "Qwen3.5 Flash", - "display_name": "Qwen3.5 Flash", + "id": "anthropic/claude-opus-4.8", + "name": "Claude 4.8 Opus", + "display_name": "Claude 4.8 Opus", "modalities": { "input": [ "text", "image", - "video" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 991808, - "output": 65536 + "context": 1000000, + "output": 128000 }, - "temperature": true, - "tool_call": false, + "temperature": false, + "tool_call": true, "reasoning": { - "supported": true + "supported": true, + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": false, + "mode": "effort", + "effort": "high", + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" + ], "interleaved": true, "summaries": true, - "visibility": "summary", + "visibility": "omitted", "continuation": [ "thinking_blocks" + ], + "notes": [ + "Claude Opus 4.7 and newer Opus models require thinking.type = \"adaptive\" to enable thinking explicitly.", + "Manual budget_tokens requests return 400 on Claude Opus 4.7 and newer adaptive-only Opus models.", + "task_budget is separate from thinking control and should not be treated as a thinking budget." ] } }, "attachment": true, "open_weights": false, - "release_date": "2026-02-23", - "last_updated": "2026-02-23", - "type": "chat" - }, - { - "id": "qwen/qwen3-next-80b-a3b-instruct", - "name": "Qwen3 Next 80B A3B (Instruct)", - "display_name": "Qwen3 Next 80B A3B (Instruct)", - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 262144, - "output": 32768 - }, - "temperature": true, - "tool_call": true, - "reasoning": { - "supported": false - }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-09", - "last_updated": "2025-09", + "knowledge": "2026-01", + "release_date": "2026-05-28", + "last_updated": "2026-05-28", "type": "chat" }, { - "id": "qwen/qwen3.5-122b-a10b:thinking", - "name": "Qwen3.5 122B A10B Thinking", - "display_name": "Qwen3.5 122B A10B Thinking", + "id": "anthropic/claude-opus-5.5", + "name": "Claude Opus 5.5", + "display_name": "Claude Opus 5.5", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 32768 + "context": 1000000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -258028,81 +257792,101 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" ] } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-02-23", - "last_updated": "2026-02-23", + "attachment": true, + "open_weights": false, + "knowledge": "2026-06", + "release_date": "2026-09-22", + "last_updated": "2026-09-22", "type": "chat" }, { - "id": "qwen/qwen-plus", - "name": "Qwen Plus", - "display_name": "Qwen Plus", + "id": "anthropic/claude-sonnet-4.6", + "name": "Claude 4.6 Sonnet", + "display_name": "Claude 4.6 Sonnet", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 995904, - "output": 32768 + "context": 200000, + "output": 64000 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": false, + "mode": "mixed", + "budget": { + "min": 1024, + "unit": "tokens" + }, + "effort": "high", + "effort_options": [ + "low", + "medium", + "high", + "max" + ], "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ "thinking_blocks" + ], + "notes": [ + "Anthropic recommends adaptive thinking with effort for Claude 4.6; budget_tokens remains a deprecated compatibility path.", + "Anthropic API defaults effort to high; lower effort levels should be chosen per workload." ] } }, - "attachment": false, + "attachment": true, "open_weights": false, - "knowledge": "2024-04", - "release_date": "2024-01-25", - "last_updated": "2025-09-11", + "knowledge": "2025-08-31", + "release_date": "2026-02-17", + "last_updated": "2026-03-13", "type": "chat" }, { - "id": "qwen/qwen3.5-flash:thinking", - "name": "Qwen3.5 Flash Thinking", - "display_name": "Qwen3.5 Flash Thinking", + "id": "anthropic/claude-opus-5", + "name": "Claude Opus 5", + "display_name": "Claude Opus 5", "modalities": { "input": [ "text", "image", - "video" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 991808, - "output": 65536 + "context": 1000000, + "output": 128000 }, - "temperature": true, - "tool_call": false, + "temperature": false, + "tool_call": true, "reasoning": { "supported": true, "default": true @@ -258110,143 +257894,131 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" ] } }, "attachment": true, "open_weights": false, - "release_date": "2026-02-23", - "last_updated": "2026-02-23", + "knowledge": "2026-05", + "release_date": "2026-07-24", + "last_updated": "2026-07-24", "type": "chat" }, { - "id": "qwen/qwen3-235b-a22b", - "name": "Qwen 3 235b A22B", - "display_name": "Qwen 3 235b A22B", + "id": "amazon/nova-micro-v1", + "name": "Nova Micro 1.0", + "display_name": "Nova Micro 1.0", "modalities": { "input": [ - "text", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 16384 + "context": 128000, + "output": 5120 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } + "supported": false }, - "attachment": true, - "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-04-28", - "last_updated": "2025-04-28", + "attachment": false, + "open_weights": false, + "knowledge": "2024-10-31", + "release_date": "2024-12-05", + "last_updated": "2024-12-05", "type": "chat" }, { - "id": "qwen/qwen3.8-max", - "name": "Qwen3.8 Max", - "display_name": "Qwen3.8 Max", + "id": "amazon/nova-lite-v1", + "name": "Nova Lite 1.0", + "display_name": "Nova Lite 1.0", "modalities": { "input": [ "text", - "image", - "video", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 991000, - "output": 65536 + "context": 300000, + "output": 5120 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true - } + "supported": false }, "attachment": true, "open_weights": false, - "release_date": "2026-08-03", - "last_updated": "2026-08-03", + "knowledge": "2024-10-31", + "release_date": "2024-12-05", + "last_updated": "2024-12-05", "type": "chat" }, { - "id": "qwen/qwen3.5-omni-plus", - "name": "Qwen3.5 Omni Plus", - "display_name": "Qwen3.5 Omni Plus", + "id": "amazon/nova-pro-v1", + "name": "Nova Pro 1.0", + "display_name": "Nova Pro 1.0", "modalities": { "input": [ "text", - "image", - "video", - "audio" + "image" ], "output": [ "text" ] }, "limit": { - "context": 983616, - "output": 65536 + "context": 300000, + "output": 5120 }, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { "supported": false }, "attachment": true, "open_weights": false, - "release_date": "2026-03-30", - "last_updated": "2026-03-30", + "knowledge": "2024-10-31", + "release_date": "2024-12-05", + "last_updated": "2024-12-05", "type": "chat" }, { - "id": "qwen/qwen3-14b", - "name": "Qwen 3 14b", - "display_name": "Qwen 3 14b", + "id": "bytedance-seed/seed-2.0-mini", + "name": "Seed-2.0-Mini", + "display_name": "Seed-2.0-Mini", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 41000, - "output": 32768 + "context": 262144, + "output": 131072 }, + "temperature": true, "tool_call": true, "reasoning": { - "supported": true + "supported": true, + "default": true }, "extra_capabilities": { "reasoning": { @@ -258256,22 +258028,29 @@ "visibility": "summary", "continuation": [ "thinking_blocks" + ], + "effort_options": [ + "low", + "medium", + "high" ] } }, - "attachment": false, - "open_weights": true, - "release_date": "2024-01-01", - "last_updated": "2024-01-01", + "attachment": true, + "open_weights": false, + "release_date": "2026-02-14", + "last_updated": "2026-02-14", "type": "chat" }, { - "id": "qwen/qwen3.5-122b-a10b", - "name": "Qwen3.5 122B A10B", - "display_name": "Qwen3.5 122B A10B", + "id": "bytedance-seed/seed-2.0-lite", + "name": "Seed-2.0-Lite", + "display_name": "Seed-2.0-Lite", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" @@ -258279,12 +258058,13 @@ }, "limit": { "context": 262144, - "output": 32768 + "output": 131072 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true + "supported": true, + "default": true }, "extra_capabilities": { "reasoning": { @@ -258294,47 +258074,53 @@ "visibility": "summary", "continuation": [ "thinking_blocks" + ], + "effort_options": [ + "low", + "medium", + "high" ] } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-02-23", - "last_updated": "2026-02-23", + "attachment": true, + "open_weights": false, + "release_date": "2026-02-14", + "last_updated": "2026-02-14", "type": "chat" }, { - "id": "qwen/qwen3.8-27b-fable", - "name": "Qwen 3.8 27B Fable", - "display_name": "Qwen 3.8 27B Fable", + "id": "bytedance-seed/seed-1.6-flash", + "name": "Seed 1.6 Flash", + "display_name": "Seed 1.6 Flash", "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 524288, + "context": 262144, "output": 32768 }, + "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true + "supported": false }, "attachment": true, - "open_weights": true, - "release_date": "2026-07-29", - "last_updated": "2026-08-28", + "open_weights": false, + "release_date": "2025-08-28", + "last_updated": "2025-08-28", "type": "chat" }, { - "id": "qwen/qwen3.5-plus:thinking", - "name": "Qwen3.5 Plus Thinking", - "display_name": "Qwen3.5 Plus Thinking", + "id": "bytedance-seed/seed-2.0-code", + "name": "Seed-2.0-Code", + "display_name": "Seed-2.0-Code", "modalities": { "input": [ "text", @@ -258346,11 +258132,11 @@ ] }, "limit": { - "context": 983616, - "output": 65536 + "context": 262144, + "output": 131072 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { "supported": true, "default": true @@ -258363,48 +258149,51 @@ "visibility": "summary", "continuation": [ "thinking_blocks" + ], + "effort_options": [ + "low", + "medium", + "high" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-04", - "release_date": "2026-02-16", - "last_updated": "2026-02-16", + "release_date": "2026-02-14", + "last_updated": "2026-02-14", "type": "chat" }, { - "id": "qwen/qwen3.8-27b-fimi", - "name": "Qwen 3.8 27B Fimi", - "display_name": "Qwen 3.8 27B Fimi", + "id": "inference-net/schematron-v2-turbo", + "name": "Schematron V2 Turbo", + "display_name": "Schematron V2 Turbo", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 32768 + "context": 128000, + "output": 8192 }, - "tool_call": true, + "temperature": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true + "supported": false }, - "attachment": true, - "open_weights": true, - "release_date": "2026-10-05", - "last_updated": "2026-10-05", + "attachment": false, + "open_weights": false, + "release_date": "2026-04-16", + "last_updated": "2026-04-16", "type": "chat" }, { - "id": "qwen/qwen2.5-coder-32b-instruct", - "name": "Qwen 2.5 Coder 32b", - "display_name": "Qwen 2.5 Coder 32b", + "id": "inference-net/schematron-v2-small", + "name": "Schematron V2 Small", + "display_name": "Schematron V2 Small", "modalities": { "input": [ "text" @@ -258414,8 +258203,8 @@ ] }, "limit": { - "context": 32000, - "output": 8192 + "context": 128000, + "output": 4096 }, "temperature": true, "tool_call": false, @@ -258423,15 +258212,15 @@ "supported": false }, "attachment": false, - "open_weights": true, - "release_date": "2024-11-12", - "last_updated": "2024-11-12", + "open_weights": false, + "release_date": "2026-04-16", + "last_updated": "2026-04-16", "type": "chat" }, { - "id": "qwen/qwen3-next-80b-a3b-thinking", - "name": "Qwen3 Next 80B A3B (Thinking)", - "display_name": "Qwen3 Next 80B A3B (Thinking)", + "id": "cognitivecomputations/dolphin-mistral-24b-venice-edition", + "name": "Venice Uncensored", + "display_name": "Venice Uncensored", "modalities": { "input": [ "text" @@ -258441,117 +258230,82 @@ ] }, "limit": { - "context": 256000, - "output": 32768 + "context": 128000, + "output": 8192 }, "temperature": true, "tool_call": false, "reasoning": { - "supported": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } + "supported": false }, "attachment": false, "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-09", - "last_updated": "2025-09", + "knowledge": "2024-04-30", + "release_date": "2025-07-09", + "last_updated": "2025-07-09", "type": "chat" }, { - "id": "qwen/qwen3.6-plus", - "name": "Qwen 3.6 Plus", - "display_name": "Qwen 3.6 Plus", + "id": "perplexity/sonar", + "name": "Sonar", + "display_name": "Sonar", "modalities": { "input": [ - "text", - "image", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 991808, - "output": 65536 + "context": 128000, + "output": 100000 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } + "supported": false }, - "attachment": true, + "attachment": false, "open_weights": false, - "knowledge": "2025-04", - "release_date": "2026-04-02", - "last_updated": "2026-04-02", + "knowledge": "2025-09-01", + "release_date": "2024-01-01", + "last_updated": "2025-09-01", "type": "chat" }, { - "id": "qwen/qwen3-32b", - "name": "Qwen 3 32b", - "display_name": "Qwen 3 32b", + "id": "perplexity/sonar-pro", + "name": "Sonar Pro", + "display_name": "Sonar Pro", "modalities": { "input": [ "text", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 41000, - "output": 32768 + "context": 200000, + "output": 100000 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } + "supported": false }, "attachment": true, - "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-04-28", - "last_updated": "2025-04-28", + "open_weights": false, + "knowledge": "2025-09-01", + "release_date": "2024-01-01", + "last_updated": "2025-09-01", "type": "chat" }, { - "id": "qwen/qwen3-coder", - "name": "Qwen 3 Coder 480B", - "display_name": "Qwen 3 Coder 480B", + "id": "deepseek/deepseek-chat", + "name": "DeepSeek V4 Flash", + "display_name": "DeepSeek V4 Flash", "modalities": { "input": [ "text" @@ -258561,25 +258315,42 @@ ] }, "limit": { - "context": 262000, - "output": 65536 + "context": 1000000, + "output": 384000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "effort_options": [ + "none", + "low", + "high" + ] + } }, "attachment": false, "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-07-22", - "last_updated": "2025-07-22", + "knowledge": "2025-05", + "release_date": "2026-04-24", + "last_updated": "2026-04-24", "type": "chat" }, { - "id": "qwen/qwen3.8-27b-kiwen", - "name": "Qwen 3.8 27B Kiwen", - "display_name": "Qwen 3.8 27B Kiwen", + "id": "deepseek/deepseek-v4.1-flash", + "name": "DeepSeek V4.1 Flash", + "display_name": "DeepSeek V4.1 Flash", "modalities": { "input": [ "text", @@ -258590,119 +258361,165 @@ ] }, "limit": { - "context": 262144, - "output": 32768 + "context": 1048576, + "output": 384000 }, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, "default": true }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "effort_options": [ + "low", + "high" + ] + } + }, "attachment": true, "open_weights": true, - "release_date": "2026-10-05", - "last_updated": "2026-10-05", + "knowledge": "2025-05", + "release_date": "2026-09-10", + "last_updated": "2026-09-10", "type": "chat" }, { - "id": "qwen/qwen3-235b-a22b-instruct-2507", - "name": "Qwen 3 235b A22B 2507", - "display_name": "Qwen 3 235b A22B 2507", + "id": "deepseek/deepseek-v4-flash-vision-exp", + "name": "DeepSeek V4 Flash Vision Exp", + "display_name": "DeepSeek V4 Flash Vision Exp", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 16384 + "context": 1048576, + "output": 384000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "high", + "effort_options": [ + "low", + "high", + "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." + ] + } + }, + "attachment": true, "open_weights": true, - "release_date": "2025-07-21", - "last_updated": "2025-07-21", + "release_date": "2026-08-21", + "last_updated": "2026-09-01", "type": "chat" }, { - "id": "qwen/qwen3.6-35b-a3b", - "name": "Qwen3.6 35B A3B", - "display_name": "Qwen3.6 35B A3B", + "id": "deepseek/deepseek-v4-pro", + "name": "DeepSeek V4 Pro", + "display_name": "DeepSeek V4 Pro", "modalities": { "input": [ - "text", - "image", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 16384 + "context": 1000000, + "output": 384000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true + "supported": true, + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "high", + "effort_options": [ + "low", + "high", + "max" + ], "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ "thinking_blocks" + ], + "notes": [ + "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." ] } }, - "attachment": true, + "attachment": false, "open_weights": true, - "release_date": "2026-04-17", - "last_updated": "2026-04-17", + "knowledge": "2025-05", + "release_date": "2026-04-24", + "last_updated": "2026-04-24", "type": "chat" }, { - "id": "qwen/qwen3.8-27b-hemmingway", - "name": "Qwen 3.8 27B Hemingway", - "display_name": "Qwen 3.8 27B Hemingway", + "id": "deepseek/deepseek-chat-v3-0324", + "name": "DeepSeek V3", + "display_name": "DeepSeek V3", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 32768 + "context": 128000, + "output": 124000 }, + "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true + "supported": false }, - "attachment": true, + "attachment": false, "open_weights": true, - "release_date": "2026-09-21", - "last_updated": "2026-09-21", + "knowledge": "2024-07-31", + "release_date": "2025-03-24", + "last_updated": "2025-03-24", "type": "chat" }, { - "id": "qwen/qwen3-30b-a3b", - "name": "Qwen3 30B A3B", - "display_name": "Qwen3 30B A3B", + "id": "deepseek/deepseek-v4-flash", + "name": "DeepSeek V4 Flash", + "display_name": "DeepSeek V4 Flash", "modalities": { "input": [ "text" @@ -258712,48 +258529,59 @@ ] }, "limit": { - "context": 41000, - "output": 32768 + "context": 1000000, + "output": 384000 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": true + "supported": true, + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "high", + "effort_options": [ + "low", + "high", + "max" + ], "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ "thinking_blocks" + ], + "notes": [ + "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." ] } }, "attachment": false, "open_weights": true, - "release_date": "2025-04-28", - "last_updated": "2025-04-28", + "knowledge": "2025-05", + "release_date": "2026-04-24", + "last_updated": "2026-04-24", "type": "chat" }, { - "id": "qwen/qwen3.8-flash", - "name": "Qwen3.8 Flash", - "display_name": "Qwen3.8 Flash", + "id": "deepseek/deepseek-v4-pro-0813", + "name": "DeepSeek V4 Pro 0813", + "display_name": "DeepSeek V4 Pro 0813", "modalities": { "input": [ - "text", - "image", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 1048576, + "output": 384000 }, "temperature": true, "tool_call": true, @@ -258764,22 +258592,29 @@ "extra_capabilities": { "reasoning": { "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ "none", + "low", "high" ] } }, - "attachment": true, - "open_weights": false, - "release_date": "2026-08-26", - "last_updated": "2026-08-26", + "attachment": false, + "open_weights": true, + "release_date": "2026-08-13", + "last_updated": "2026-08-22", "type": "chat" }, { - "id": "qwen/qwen3.6-27b:thinking", - "name": "Qwen3.6 27B Thinking", - "display_name": "Qwen3.6 27B Thinking", + "id": "moonshot/kimi-k3", + "name": "Kimi K3", + "display_name": "Kimi K3", "modalities": { "input": [ "text", @@ -258791,10 +258626,10 @@ ] }, "limit": { - "context": 262144, - "output": 65536 + "context": 1048576, + "output": 131072 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -258808,49 +258643,52 @@ "visibility": "summary", "continuation": [ "thinking_blocks" + ], + "effort_options": [ + "low", + "high", + "max" ] } }, "attachment": true, "open_weights": true, - "release_date": "2026-04-22", - "last_updated": "2026-04-22", + "release_date": "2026-07-16", + "last_updated": "2026-07-16", "type": "chat" }, { - "id": "qwen/qwen3.8-27b:thinking", - "name": "Qwen3.8 27B Thinking", - "display_name": "Qwen3.8 27B Thinking", + "id": "undi95/remm-slerp-l2-13b", + "name": "ReMM SLERP 13B", + "display_name": "ReMM SLERP 13B", "modalities": { "input": [ - "text", - "image", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 32768 + "context": 6144, + "output": 4096 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true + "supported": false }, - "attachment": true, + "attachment": false, "open_weights": true, - "release_date": "2026-08-14", - "last_updated": "2026-08-14", + "knowledge": "2023-06-30", + "release_date": "2023-07-22", + "last_updated": "2023-07-22", "type": "chat" }, { - "id": "qwen/qwen3.6-max-preview", - "name": "Qwen3.6 Max Preview", - "display_name": "Qwen3.6 Max Preview", + "id": "sao10k/l3.1-euryale-70b", + "name": "Llama 3.1 Euryale 70B v2.2", + "display_name": "Llama 3.1 Euryale 70B v2.2", "modalities": { "input": [ "text" @@ -258860,36 +258698,25 @@ ] }, "limit": { - "context": 245760, - "output": 65536 + "context": 131072, + "output": 16384 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } + "supported": false }, "attachment": false, - "open_weights": false, - "knowledge": "2025-04", - "release_date": "2026-04-20", - "last_updated": "2026-04-20", + "open_weights": true, + "knowledge": "2023-12-31", + "release_date": "2024-08-28", + "last_updated": "2024-08-28", "type": "chat" }, { - "id": "qwen/qwen3.7-max:thinking", - "name": "Qwen3.7 Max Thinking", - "display_name": "Qwen3.7 Max Thinking", + "id": "sao10k/l3.3-euryale-70b", + "name": "Llama 3.3 Euryale 70B", + "display_name": "Llama 3.3 Euryale 70B", "modalities": { "input": [ "text" @@ -258899,104 +258726,87 @@ ] }, "limit": { - "context": 1000000, - "output": 65536 + "context": 131072, + "output": 16384 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "none", - "high" - ] - } + "supported": false }, "attachment": false, - "open_weights": false, - "release_date": "2026-05-21", - "last_updated": "2026-05-21", + "open_weights": true, + "knowledge": "2023-12-31", + "release_date": "2024-12-18", + "last_updated": "2024-12-18", "type": "chat" }, { - "id": "qwen/qwen3.8-27b-novelist", - "name": "Qwen 3.8 27B Novelist", - "display_name": "Qwen 3.8 27B Novelist", + "id": "sao10k/l3-lunaris-8b", + "name": "Llama 3 8B Lunaris", + "display_name": "Llama 3 8B Lunaris", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 32768 + "context": 8192, + "output": 7372 }, - "tool_call": true, + "temperature": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true + "supported": false }, - "attachment": true, + "attachment": false, "open_weights": true, - "release_date": "2026-10-05", - "last_updated": "2026-10-05", + "knowledge": "2023-12-31", + "release_date": "2024-08-13", + "last_updated": "2024-08-13", "type": "chat" }, { - "id": "qwen/qwen3.7-flash", - "name": "Qwen3.7 Flash", - "display_name": "Qwen3.7 Flash", + "id": "x-ai/grok-4-20-0309-non-reasoning", + "name": "Grok 4.20 Non-Reasoning", + "display_name": "Grok 4.20 Non-Reasoning", "modalities": { "input": [ "text", "image", - "video" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 991808, - "output": 65536 + "context": 2000000, + "output": 1999000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "effort_options": [ - "none", - "high" - ] - } + "supported": false }, "attachment": true, "open_weights": false, - "release_date": "2026-07-15", - "last_updated": "2026-07-15", + "release_date": "2026-03-09", + "last_updated": "2026-03-09", "type": "chat" }, { - "id": "qwen/qwen3.6-27b", - "name": "Qwen3.6 27B", - "display_name": "Qwen3.6 27B", + "id": "x-ai/grok-4-fast-non-reasoning", + "name": "Grok 4 Fast Non-Reasoning", + "display_name": "Grok 4 Fast Non-Reasoning", "modalities": { "input": [ "text", "image", + "audio", "video" ], "output": [ @@ -259004,75 +258814,52 @@ ] }, "limit": { - "context": 262144, - "output": 65536 + "context": 2000000, + "output": 1999000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } + "supported": false }, "attachment": true, - "open_weights": true, - "release_date": "2026-04-22", - "last_updated": "2026-04-22", + "open_weights": false, + "release_date": "2025-12-18", + "last_updated": "2025-12-18", "type": "chat" }, { - "id": "qwen/qwen3.5-397b-a17b", - "name": "Qwen3.5 397B A17B", - "display_name": "Qwen3.5 397B A17B", + "id": "nousresearch/hermes-3-llama-3.1-70b", + "name": "Hermes 3 70B Instruct", + "display_name": "Hermes 3 70B Instruct", "modalities": { "input": [ - "text", - "image", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 65536 + "context": 131072, + "output": 16384 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } + "supported": false }, - "attachment": true, + "attachment": false, "open_weights": true, - "release_date": "2026-02-15", - "last_updated": "2026-02-15", + "knowledge": "2023-12-31", + "release_date": "2024-08-18", + "last_updated": "2024-08-18", "type": "chat" }, { - "id": "qwen/qwen3-8b", - "name": "Qwen 3 8B", - "display_name": "Qwen 3 8B", + "id": "nousresearch/hermes-3-llama-3.1-405b", + "name": "Hermes 3 405B Instruct", + "display_name": "Hermes 3 405B Instruct", "modalities": { "input": [ "text" @@ -259082,146 +258869,74 @@ ] }, "limit": { - "context": 41000, - "output": 32768 + "context": 131072, + "output": 16384 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } + "supported": false }, "attachment": false, "open_weights": true, - "release_date": "2025-04-29", - "last_updated": "2025-04-29", + "knowledge": "2023-12-31", + "release_date": "2024-08-16", + "last_updated": "2024-08-16", "type": "chat" }, { - "id": "qwen/qwen3.8-27b-kimiko", - "name": "Qwen 3.8 27B Kimiko", - "display_name": "Qwen 3.8 27B Kimiko", + "id": "upstage/solar-pro-3", + "name": "Solar Pro 3", + "display_name": "Solar Pro 3", "modalities": { "input": [ - "text", - "image" - ], - "output": [ "text" - ] - }, - "limit": { - "context": 262144, - "output": 32768 - }, - "tool_call": true, - "reasoning": { - "supported": true, - "default": true - }, - "attachment": true, - "open_weights": true, - "release_date": "2026-10-05", - "last_updated": "2026-10-05", - "type": "chat" - }, - { - "id": "qwen/qwen3-vl-235b-a22b-thinking", - "name": "Qwen3 VL 235B A22B Thinking", - "display_name": "Qwen3 VL 235B A22B Thinking", - "modalities": { - "input": [ - "text", - "image" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 32768 + "context": 128000, + "output": 8192 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { "supported": true, "default": true }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "low", + "medium", + "high" ] } }, - "attachment": true, - "open_weights": true, - "knowledge": "2025-03-31", - "release_date": "2025-09-23", - "last_updated": "2025-09-23", - "type": "chat" - }, - { - "id": "qwen/qwen3.5-omni-flash", - "name": "Qwen3.5 Omni Flash", - "display_name": "Qwen3.5 Omni Flash", - "modalities": { - "input": [ - "text", - "image", - "video", - "audio" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 49152, - "output": 16384 - }, - "tool_call": false, - "reasoning": { - "supported": false - }, - "attachment": true, + "attachment": false, "open_weights": false, - "release_date": "2026-03-30", - "last_updated": "2026-03-30", + "knowledge": "2025-07", + "release_date": "2026-01-26", + "last_updated": "2026-03-23", "type": "chat" }, { - "id": "qwen/qwen3.7-plus:thinking", - "name": "Qwen3.7 Plus Thinking", - "display_name": "Qwen3.7 Plus Thinking", + "id": "upstage/solar-pro4", + "name": "Solar Pro 4", + "display_name": "Solar Pro 4", "modalities": { "input": [ - "text", - "image", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 983616, - "output": 65536 + "context": 524288, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -259233,83 +258948,83 @@ "reasoning": { "effort_options": [ "none", + "low", + "medium", "high" ] } }, - "attachment": true, + "attachment": false, "open_weights": false, - "knowledge": "2025-04", - "release_date": "2026-06-02", - "last_updated": "2026-06-02", + "knowledge": "2026-02", + "release_date": "2026-08-06", + "last_updated": "2026-08-06", "type": "chat" }, { - "id": "qwen/qwen3.5-27b:thinking", - "name": "Qwen3.5 27B Thinking", - "display_name": "Qwen3.5 27B Thinking", + "id": "unbiased/pareto", + "name": "Pareto", + "display_name": "Pareto", "modalities": { "input": [ "text", - "image", - "video" + "image" ], "output": [ "text" ] }, "limit": { - "context": 260096, - "output": 65536 + "context": 262144, + "output": 131072 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true + "supported": false }, "attachment": true, - "open_weights": true, - "release_date": "2026-02-23", - "last_updated": "2026-02-23", + "open_weights": false, + "release_date": "2026-09-17", + "last_updated": "2026-09-17", "type": "chat" }, { - "id": "qwen/qwen3.8-27b-uncensored", - "name": "Qwen 3.8 27B Uncensored", - "display_name": "Qwen 3.8 27B Uncensored", + "id": "ibm-granite/granite-4.0-h-micro", + "name": "Granite 4.0 Micro", + "display_name": "Granite 4.0 Micro", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 131000, + "output": 131000 }, + "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true + "supported": false }, - "attachment": true, + "attachment": false, "open_weights": true, - "release_date": "2026-07-29", - "last_updated": "2026-08-21", + "release_date": "2025-10-02", + "last_updated": "2025-10-02", "type": "chat" }, { - "id": "qwen/qwen3.5-397b-a17b:thinking", - "name": "Qwen3.5 397B A17B Thinking", - "display_name": "Qwen3.5 397B A17B Thinking", + "id": "meta/muse-spark-1.1", + "name": "Muse Spark 1.1", + "display_name": "Muse Spark 1.1", "modalities": { "input": [ "text", "image", + "pdf", "video" ], "output": [ @@ -259317,8 +259032,8 @@ ] }, "limit": { - "context": 262144, - "output": 65536 + "context": 1048576, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -259334,100 +259049,126 @@ "visibility": "summary", "continuation": [ "thinking_blocks" + ], + "effort_options": [ + "low", + "medium", + "high" ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-02-15", - "last_updated": "2026-02-15", + "open_weights": false, + "release_date": "2026-04-08", + "last_updated": "2026-07-09", "type": "chat" }, { - "id": "qwen/qwen-max", - "name": "Qwen 2.5 Max", - "display_name": "Qwen 2.5 Max", + "id": "meta/muse-spark-1.2", + "name": "Muse Spark 1.2", + "display_name": "Muse Spark 1.2", "modalities": { "input": [ - "text" + "text", + "image", + "video", + "pdf", + "audio" ], "output": [ "text" ] }, "limit": { - "context": 32000, - "output": 8192 + "context": 1048576, + "output": 131072 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "effort_options": [ + "low", + "medium", + "high" + ] + } }, - "attachment": false, + "attachment": true, "open_weights": false, - "knowledge": "2024-04", - "release_date": "2024-04-03", - "last_updated": "2025-01-25", + "release_date": "2026-08-05", + "last_updated": "2026-08-05", "type": "chat" }, { - "id": "qwen/qwen3.5-27b", - "name": "Qwen3.5 27B", - "display_name": "Qwen3.5 27B", + "id": "meta/muse-spark-1.3", + "name": "Muse Spark 1.3", + "display_name": "Muse Spark 1.3", "modalities": { "input": [ "text", "image", - "video" + "video", + "pdf", + "audio" ], "output": [ "text" ] }, "limit": { - "context": 260096, - "output": 65536 + "context": 1048576, + "output": 131072 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true + "supported": true, + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "low", + "medium", + "high" ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-02-23", - "last_updated": "2026-02-23", + "open_weights": false, + "release_date": "2026-09-02", + "last_updated": "2026-09-02", "type": "chat" }, { - "id": "qwen/qwen3.7-plus", - "name": "Qwen3.7 Plus", - "display_name": "Qwen3.7 Plus", + "id": "meta/muse-glimmer-30b", + "name": "Muse Glimmer 30B", + "display_name": "Muse Glimmer 30B", "modalities": { "input": [ "text", - "image", - "video" + "image" ], "output": [ "text" ] }, "limit": { - "context": 991808, - "output": 65536 + "context": 131072, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -259438,23 +259179,30 @@ "extra_capabilities": { "reasoning": { "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ - "none", + "low", + "medium", "high" ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2025-04", - "release_date": "2026-06-02", - "last_updated": "2026-06-02", + "open_weights": true, + "knowledge": "2026-01-04", + "release_date": "2026-08-10", + "last_updated": "2026-08-10", "type": "chat" }, { - "id": "qwen/qwen3-coder-flash", - "name": "Qwen3 Coder Flash", - "display_name": "Qwen3 Coder Flash", + "id": "relace/relace-search", + "name": "Search", + "display_name": "Search", "modalities": { "input": [ "text" @@ -259464,25 +259212,24 @@ ] }, "limit": { - "context": 128000, - "output": 65536 + "context": 256000, + "output": 128000 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { "supported": false }, "attachment": false, "open_weights": false, - "knowledge": "2025-04", - "release_date": "2025-07-28", - "last_updated": "2025-07-28", + "release_date": "2025-12-08", + "last_updated": "2025-12-08", "type": "chat" }, { - "id": "Doctor-Shotgun/MS3.2-24B-Magnum-Diamond", - "name": "MS3.2 24B Magnum Diamond", - "display_name": "MS3.2 24B Magnum Diamond", + "id": "relace/relace-apply-3", + "name": "Apply 3", + "display_name": "Apply 3", "modalities": { "input": [ "text" @@ -259492,23 +259239,24 @@ ] }, "limit": { - "context": 32768, - "output": 32768 + "context": 256000, + "output": 128000 }, + "temperature": false, "tool_call": false, "reasoning": { "supported": false }, "attachment": false, - "open_weights": true, - "release_date": "2025-11-24", - "last_updated": "2025-11-24", + "open_weights": false, + "release_date": "2025-09-26", + "last_updated": "2025-09-26", "type": "chat" }, { - "id": "orca/orcacyber-zero-1.5", - "name": "OrcaCyber Zero 1.5", - "display_name": "OrcaCyber Zero 1.5", + "id": "xiaomi/mimo-v2.5-pro", + "name": "MiMo V2.5 Pro", + "display_name": "MiMo V2.5 Pro", "modalities": { "input": [ "text" @@ -259518,9 +259266,10 @@ ] }, "limit": { - "context": 999488, + "context": 1000000, "output": 128000 }, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -259528,56 +259277,83 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "none", - "low", - "medium", - "high" + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, "attachment": false, - "open_weights": false, - "release_date": "2026-10-30", - "last_updated": "2026-10-30", + "open_weights": true, + "knowledge": "2024-12", + "release_date": "2026-04-22", + "last_updated": "2026-04-22", "type": "chat" }, { - "id": "inflatebot/MN-12B-Mag-Mell-R1", - "name": "Mag Mell R1", - "display_name": "Mag Mell R1", + "id": "xiaomi/mimo-v2.5", + "name": "MiMo V2.5", + "display_name": "MiMo V2.5", "modalities": { "input": [ - "text" + "text", + "image", + "audio", + "video" ], "output": [ "text" ] }, "limit": { - "context": 16384, - "output": 8192 + "context": 1000000, + "output": 128000 }, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } + }, + "attachment": true, "open_weights": true, - "release_date": "2024-01-01", - "last_updated": "2024-07-01", + "knowledge": "2024-12", + "release_date": "2026-04-22", + "last_updated": "2026-04-22", "type": "chat" - }, + } + ] + }, + "zhipuai": { + "id": "zhipuai", + "name": "Zhipu AI", + "display_name": "Zhipu AI", + "api": "https://open.bigmodel.cn/api/paas/v4", + "doc": "https://docs.bigmodel.cn/cn/guide/start/pricing", + "models": [ { - "id": "meta/muse-spark-1.3-contributor", - "name": "Muse Spark 1.3 Contributor", - "display_name": "Muse Spark 1.3 Contributor", + "id": "glm-5.3-flashx", + "name": "GLM-5.3-FlashX", + "display_name": "GLM-5.3-FlashX", "modalities": { "input": [ "text", "image", "video", - "audio", "pdf" ], "output": [ @@ -259585,9 +259361,10 @@ ] }, "limit": { - "context": 1048576, - "output": 943718 + "context": 1000000, + "output": 131072 }, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -259595,37 +259372,43 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ - "minimal", "low", - "medium", "high", - "xhigh" + "max" ] } }, "attachment": true, - "open_weights": false, - "release_date": "2026-09-02", - "last_updated": "2026-09-02", + "open_weights": true, + "release_date": "2026-09-18", + "last_updated": "2026-09-18", "type": "chat" }, { - "id": "meta/muse-spark-open", - "name": "Muse Spark Open", - "display_name": "Muse Spark Open", + "id": "glm-4.6v", + "name": "GLM-4.6V", + "display_name": "GLM-4.6V", "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 128000, + "output": 32768 }, "temperature": true, "tool_call": true, @@ -259635,40 +259418,40 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "low", - "medium", - "high", - "xhigh" + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, "attachment": true, "open_weights": true, - "knowledge": "2026-01-04", - "release_date": "2026-10-15", - "last_updated": "2026-10-15", + "knowledge": "2025-04", + "release_date": "2025-12-08", + "last_updated": "2025-12-08", "type": "chat" }, { - "id": "meta/muse-spark-1.3", - "name": "Muse Spark 1.3", - "display_name": "Muse Spark 1.3", + "id": "glm-5.3-flash", + "name": "GLM-5.3-Flash", + "display_name": "GLM-5.3-Flash", "modalities": { "input": [ "text", "image", "video", - "pdf", - "audio" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 943718 + "context": 1000000, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -259679,42 +259462,43 @@ "extra_capabilities": { "reasoning": { "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ - "minimal", "low", - "medium", "high", - "xhigh" + "max" ] } }, "attachment": true, - "open_weights": false, - "release_date": "2026-09-02", - "last_updated": "2026-09-02", + "open_weights": true, + "release_date": "2026-08-26", + "last_updated": "2026-08-26", "type": "chat" }, { - "id": "meta/muse-spark-1.1", - "name": "Muse Spark 1.1", - "display_name": "Muse Spark 1.1", + "id": "glm-4.7", + "name": "GLM-4.7", + "display_name": "GLM-4.7", "modalities": { "input": [ - "text", - "image", - "video", - "audio" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 65536 + "context": 204800, + "output": 131072 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { "supported": true, "default": true @@ -259722,41 +259506,38 @@ "extra_capabilities": { "reasoning": { "supported": true, - "effort_options": [ - "minimal", - "low", - "medium", - "high", - "xhigh" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, - "attachment": true, - "open_weights": false, - "release_date": "2026-04-08", - "last_updated": "2026-07-09", + "attachment": false, + "open_weights": true, + "knowledge": "2025-04", + "release_date": "2025-12-22", + "last_updated": "2025-12-22", "type": "chat" }, { - "id": "meta/muse-wonder", - "name": "Muse Wonder", - "display_name": "Muse Wonder", + "id": "glm-4.6", + "name": "GLM-4.6", + "display_name": "GLM-4.6", "modalities": { "input": [ - "text", - "image", - "video", - "audio", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 1048576 + "context": 204800, + "output": 131072 }, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -259764,41 +259545,37 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "minimal", - "low", - "medium", - "high", - "xhigh", - "max" + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, - "attachment": true, - "open_weights": false, - "release_date": "2026-10-12", - "last_updated": "2026-10-12", + "attachment": false, + "open_weights": true, + "knowledge": "2025-04", + "release_date": "2025-09-30", + "last_updated": "2025-09-30", "type": "chat" }, { - "id": "meta/muse-spark-1.2", - "name": "Muse Spark 1.2", - "display_name": "Muse Spark 1.2", + "id": "glm-4.5-air", + "name": "GLM-4.5-Air", + "display_name": "GLM-4.5-Air", "modalities": { "input": [ - "text", - "image", - "video", - "pdf", - "audio" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 65536 + "context": 131072, + "output": 98304 }, "temperature": true, "tool_call": true, @@ -259809,41 +259586,38 @@ "extra_capabilities": { "reasoning": { "supported": true, - "effort_options": [ - "minimal", - "low", - "medium", - "high", - "xhigh" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, - "attachment": true, - "open_weights": false, - "release_date": "2026-08-05", - "last_updated": "2026-08-05", + "attachment": false, + "open_weights": true, + "knowledge": "2025-04", + "release_date": "2025-07-28", + "last_updated": "2025-07-28", "type": "chat" }, { - "id": "meta/muse-spark-1.2-contributor", - "name": "Muse Spark 1.2 Contributor (Data Used for Training)", - "display_name": "Muse Spark 1.2 Contributor (Data Used for Training)", + "id": "glm-4.5-flash", + "name": "GLM-4.5-Flash", + "display_name": "GLM-4.5-Flash", "modalities": { "input": [ - "text", - "image", - "video", - "audio", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 65536 + "context": 131072, + "output": 98304 }, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -259851,37 +259625,37 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "minimal", - "low", - "medium", - "high", - "xhigh" + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, - "attachment": true, + "attachment": false, "open_weights": false, - "release_date": "2026-08-05", - "last_updated": "2026-08-05", + "knowledge": "2025-04", + "release_date": "2025-07-28", + "last_updated": "2025-07-28", "type": "chat" }, { - "id": "meta/muse-glimmer-30b", - "name": "Muse Glimmer 30B", - "display_name": "Muse Glimmer 30B", + "id": "glm-5.3", + "name": "GLM-5.3", + "display_name": "GLM-5.3", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 117964 + "context": 1000000, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -259891,26 +259665,30 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ - "minimal", "low", - "medium", "high", - "xhigh" + "max" ] } }, - "attachment": true, + "attachment": false, "open_weights": true, - "knowledge": "2026-01-04", - "release_date": "2026-08-10", - "last_updated": "2026-08-10", + "release_date": "2026-08-14", + "last_updated": "2026-08-14", "type": "chat" }, { - "id": "stepfun/step-5-preview", - "name": "Step 5 Preview", - "display_name": "Step 5 Preview", + "id": "glm-4.5v", + "name": "GLM-4.5V", + "display_name": "GLM-4.5V", "modalities": { "input": [ "text", @@ -259922,8 +259700,8 @@ ] }, "limit": { - "context": 1000000, - "output": 65536 + "context": 64000, + "output": 16384 }, "temperature": true, "tool_call": true, @@ -259933,36 +259711,37 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "low", - "medium", - "high" + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, "attachment": true, "open_weights": true, - "release_date": "2026-09-16", - "last_updated": "2026-09-20", + "knowledge": "2025-04", + "release_date": "2025-08-11", + "last_updated": "2025-08-11", "type": "chat" }, { - "id": "stepfun/step-3.7-flash:thinking", - "name": "Step 3.7 Flash Thinking", - "display_name": "Step 3.7 Flash Thinking", + "id": "glm-5", + "name": "GLM-5", + "display_name": "GLM-5", "modalities": { "input": [ - "text", - "image", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 256000 + "context": 204800, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -259972,25 +259751,25 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "none", - "low", - "medium", - "high" + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, - "attachment": true, + "attachment": false, "open_weights": true, - "knowledge": "2026-03-01", - "release_date": "2026-05-29", - "last_updated": "2026-05-29", + "release_date": "2026-02-12", + "last_updated": "2026-02-12", "type": "chat" }, { - "id": "stepfun-ai/step-3.5-flash-2603", - "name": "Step 3.5 Flash 2603", - "display_name": "Step 3.5 Flash 2603", + "id": "glm-4.7-flashx", + "name": "GLM-4.7-FlashX", + "display_name": "GLM-4.7-FlashX", "modalities": { "input": [ "text" @@ -260000,35 +259779,37 @@ ] }, "limit": { - "context": 262144, - "output": 65536 + "context": 200000, + "output": 131072 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { "supported": true, "default": true }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "low", - "medium", - "high" + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, "attachment": false, "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-04-02", - "last_updated": "2026-04-02", + "knowledge": "2025-04", + "release_date": "2026-01-19", + "last_updated": "2026-01-19", "type": "chat" }, { - "id": "stepfun-ai/step-3.5-flash", - "name": "Step 3.5 Flash", - "display_name": "Step 3.5 Flash", + "id": "glm-4.7-flash", + "name": "GLM-4.7-Flash", + "display_name": "GLM-4.7-Flash", "modalities": { "input": [ "text" @@ -260038,96 +259819,122 @@ ] }, "limit": { - "context": 262144, - "output": 65536 + "context": 200000, + "output": 131072 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { "supported": true, "default": true }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "low", - "medium", - "high" + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, "attachment": false, "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-01-29", - "last_updated": "2026-02-13", + "knowledge": "2025-04", + "release_date": "2026-01-19", + "last_updated": "2026-01-19", "type": "chat" }, { - "id": "unsloth/gemma-3-12b-it", - "name": "Gemma 3 12B IT", - "display_name": "Gemma 3 12B IT", + "id": "glm-4.6v-flash", + "name": "GLM-4.6V-Flash", + "display_name": "GLM-4.6V-Flash", "modalities": { "input": [ "text", - "pdf" + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 16384 + "context": 128000, + "output": 32768 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } }, "attachment": true, "open_weights": true, - "knowledge": "2024-08", - "release_date": "2025-03-12", - "last_updated": "2025-03-12", + "release_date": "2025-12-08", + "last_updated": "2025-12-08", "type": "chat" }, { - "id": "unsloth/gemma-3-4b-it", - "name": "Gemma 3 4B IT", - "display_name": "Gemma 3 4B IT", + "id": "glm-5.1", + "name": "GLM-5.1", + "display_name": "GLM-5.1", "modalities": { "input": [ - "text", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 8192 + "context": 200000, + "output": 131072 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": true, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } + }, + "attachment": false, "open_weights": true, - "knowledge": "2024-08", - "release_date": "2025-03-12", - "last_updated": "2025-03-12", + "release_date": "2026-04-07", + "last_updated": "2026-04-07", "type": "chat" }, { - "id": "unsloth/gemma-3-27b-it", - "name": "Gemma 3 27B IT", - "display_name": "Gemma 3 27B IT", + "id": "glm-5v-turbo", + "name": "GLM-5V-Turbo", + "display_name": "GLM-5V-Turbo", "modalities": { "input": [ "text", + "image", + "video", "pdf" ], "output": [ @@ -260135,25 +259942,36 @@ ] }, "limit": { - "context": 128000, - "output": 96000 + "context": 200000, + "output": 131072 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } }, "attachment": true, - "open_weights": true, - "knowledge": "2024-08", - "release_date": "2025-03-12", - "last_updated": "2025-03-12", + "open_weights": false, + "release_date": "2026-04-01", + "last_updated": "2026-04-01", "type": "chat" }, { - "id": "Envoid/Llama-3.05-Nemotron-Tenyxchat-Storybreaker-70B", - "name": "Nemotron Tenyxchat Storybreaker 70b", - "display_name": "Nemotron Tenyxchat Storybreaker 70b", + "id": "glm-5.2", + "name": "GLM-5.2", + "display_name": "GLM-5.2", "modalities": { "input": [ "text" @@ -260163,23 +259981,40 @@ ] }, "limit": { - "context": 16384, - "output": 8192 + "context": 1000000, + "output": 131072 }, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "effort_options": [ + "high", + "max" + ] + } }, "attachment": false, "open_weights": true, - "release_date": "2024-01-01", - "last_updated": "2024-12-01", + "release_date": "2026-06-13", + "last_updated": "2026-06-13", "type": "chat" }, { - "id": "Envoid/Llama-3.05-NT-Storybreaker-Ministral-70B", - "name": "Llama 3.05 Storybreaker Ministral 70b", - "display_name": "Llama 3.05 Storybreaker Ministral 70b", + "id": "glm-4.5", + "name": "GLM-4.5", + "display_name": "GLM-4.5", "modalities": { "input": [ "text" @@ -260189,79 +260024,155 @@ ] }, "limit": { - "context": 16384, - "output": 8192 + "context": 131072, + "output": 98304 }, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } }, "attachment": false, "open_weights": true, - "release_date": "2024-01-01", - "last_updated": "2024-12-01", + "knowledge": "2025-04", + "release_date": "2025-07-28", + "last_updated": "2025-07-28", "type": "chat" - }, + } + ] + }, + "modelis": { + "id": "modelis", + "name": "Modelis", + "display_name": "Modelis", + "api": "https://modelishub.com/v1", + "doc": "https://modelishub.com/pricing", + "models": [ { - "id": "NousResearch/hermes-4-405b:thinking", - "name": "Hermes 4 Large (Thinking)", - "display_name": "Hermes 4 Large (Thinking)", + "id": "claude-fable-5", + "name": "Claude Fable 5", + "display_name": "Claude Fable 5", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 8192 + "context": 1000000, + "output": 128000 }, - "temperature": true, - "tool_call": false, + "temperature": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, - "open_weights": true, - "knowledge": "2024-08-31", - "release_date": "2025-08-26", - "last_updated": "2025-08-26", + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "high", + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "omitted", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Adaptive thinking is always on for Claude Fable 5 and Claude Mythos 5; thinking.type = \"disabled\" is rejected.", + "Manual budget_tokens requests return 400 on Claude Fable 5 and Claude Mythos 5.", + "thinking.display defaults to omitted; set display to summarized to receive readable thinking summaries." + ] + } + }, + "attachment": true, + "open_weights": false, + "knowledge": "2026-01-31", + "release_date": "2026-06-09", + "last_updated": "2026-06-09", "type": "chat" }, { - "id": "NousResearch/hermes-4-405b", - "name": "Hermes 4 Large", - "display_name": "Hermes 4 Large", + "id": "gemini-2.5-pro", + "name": "Gemini 2.5 Pro", + "display_name": "Gemini 2.5 Pro", "modalities": { "input": [ - "text" + "text", + "image", + "audio", + "video", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 8192 + "context": 1048576, + "output": 65536 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, - "open_weights": true, - "knowledge": "2024-08-31", - "release_date": "2025-08-26", - "last_updated": "2025-08-26", + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "budget", + "budget": { + "default": -1, + "min": 128, + "max": 32768, + "auto": -1, + "unit": "tokens" + }, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thought_signatures" + ] + } + }, + "attachment": true, + "open_weights": false, + "knowledge": "2025-01", + "release_date": "2025-06-17", + "last_updated": "2025-06-17", "type": "chat" }, { - "id": "NousResearch/hermes-3-llama-3.1-70b", - "name": "Hermes 3 70B", - "display_name": "Hermes 3 70B", + "id": "deepseek-v4-pro", + "name": "DeepSeek V4 Pro", + "display_name": "DeepSeek V4 Pro", "modalities": { "input": [ "text" @@ -260271,175 +260182,268 @@ ] }, "limit": { - "context": 65536, - "output": 8192 + "context": 1000000, + "output": 384000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "high", + "effort_options": [ + "low", + "high", + "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." + ] + } }, "attachment": false, "open_weights": true, - "knowledge": "2023-12-31", - "release_date": "2024-08-18", - "last_updated": "2024-08-18", + "knowledge": "2025-05", + "release_date": "2026-04-24", + "last_updated": "2026-04-24", "type": "chat" }, { - "id": "mistralai/mistral-small-4-119b-2603", - "name": "Mistral Small 4 119B", - "display_name": "Mistral Small 4 119B", + "id": "claude-sonnet-4-6", + "name": "Claude Sonnet 4.6", + "display_name": "Claude Sonnet 4.6", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 16384 + "context": 1000000, + "output": 64000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": false, + "mode": "mixed", + "budget": { + "min": 1024, + "unit": "tokens" + }, + "effort": "high", + "effort_options": [ + "low", + "medium", + "high", + "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Anthropic recommends adaptive thinking with effort for Claude 4.6; budget_tokens remains a deprecated compatibility path.", + "Anthropic API defaults effort to high; lower effort levels should be chosen per workload." + ] + } }, "attachment": true, - "open_weights": true, - "knowledge": "2025-06", - "release_date": "2026-03-16", - "last_updated": "2026-03-16", + "open_weights": false, + "knowledge": "2025-08-31", + "release_date": "2026-02-17", + "last_updated": "2026-03-13", "type": "chat" }, { - "id": "mistralai/mistral-small-3.2-24b-instruct", - "name": "Mistral Small 3.2 24B (2506)", - "display_name": "Mistral Small 3.2 24B (2506)", + "id": "gemini-2.5-flash", + "name": "Gemini 2.5 Flash", + "display_name": "Gemini 2.5 Flash", "modalities": { "input": [ - "text" + "text", + "image", + "audio", + "video", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 16384 + "context": 1048576, + "output": 65536 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": false - }, - "attachment": false, - "open_weights": true, - "knowledge": "2023-10-31", - "release_date": "2025-06-20", - "last_updated": "2025-06-20", - "type": "chat" - }, - { - "id": "mistralai/ministral-14b-2512", - "name": "Ministral 14B", - "display_name": "Ministral 14B", - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 262144, - "output": 32768 + "supported": true, + "default": true }, - "temperature": true, - "tool_call": false, - "reasoning": { - "supported": false + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "budget", + "budget": { + "default": -1, + "min": 0, + "max": 24576, + "auto": -1, + "off": 0, + "unit": "tokens" + }, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thought_signatures" + ] + } }, - "attachment": false, - "open_weights": true, - "release_date": "2025-12-02", - "last_updated": "2025-12-02", + "attachment": true, + "open_weights": false, + "knowledge": "2025-01", + "release_date": "2025-06-17", + "last_updated": "2025-06-17", "type": "chat" - }, - { - "id": "mistralai/mistral-medium-3.5:thinking", - "name": "Mistral Medium 3.5 Thinking", - "display_name": "Mistral Medium 3.5 Thinking", + }, + { + "id": "claude-opus-4-8", + "name": "Claude Opus 4.8", + "display_name": "Claude Opus 4.8", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 256000, - "output": 32768 + "context": 1000000, + "output": 128000 }, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": false, + "mode": "effort", + "effort": "high", "effort_options": [ - "none", - "high" + "low", + "medium", + "high", + "xhigh", + "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "omitted", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Claude Opus 4.7 and newer Opus models require thinking.type = \"adaptive\" to enable thinking explicitly.", + "Manual budget_tokens requests return 400 on Claude Opus 4.7 and newer adaptive-only Opus models.", + "task_budget is separate from thinking control and should not be treated as a thinking budget." ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-04-30", - "last_updated": "2026-04-30", + "open_weights": false, + "knowledge": "2026-01", + "release_date": "2026-05-28", + "last_updated": "2026-05-28", "type": "chat" }, { - "id": "mistralai/mistral-medium-3", - "name": "Mistral Medium 3", - "display_name": "Mistral Medium 3", + "id": "deepseek-v4-flash", + "name": "DeepSeek V4 Flash", + "display_name": "DeepSeek V4 Flash", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 32768 + "context": 1000000, + "output": 384000 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-03-31", - "release_date": "2025-05-07", - "last_updated": "2025-05-07", + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "high", + "effort_options": [ + "low", + "high", + "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." + ] + } + }, + "attachment": false, + "open_weights": true, + "knowledge": "2025-05", + "release_date": "2026-04-24", + "last_updated": "2026-04-24", "type": "chat" }, { - "id": "mistralai/mistral-saba", - "name": "Mistral Saba", - "display_name": "Mistral Saba", + "id": "qwen/qwen3.7-max", + "name": "Qwen3.7 Max", + "display_name": "Qwen3.7 Max", "modalities": { "input": [ "text" @@ -260449,81 +260453,121 @@ ] }, "limit": { - "context": 32768, - "output": 26214 + "context": 1000000, + "output": 131072 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true + } }, "attachment": false, "open_weights": false, - "knowledge": "2024-09-30", - "release_date": "2025-02-17", - "last_updated": "2025-02-17", + "release_date": "2026-05-21", + "last_updated": "2026-05-21", "type": "chat" }, { - "id": "mistralai/mixtral-8x22b-instruct-v0.1", - "name": "Mixtral 8x22B", - "display_name": "Mixtral 8x22B", + "id": "qwen/qwen3.7-plus", + "name": "Qwen3.7 Plus", + "display_name": "Qwen3.7 Plus", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 65536, - "output": 52428 + "context": 1000000, + "output": 64000 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, - "open_weights": true, - "knowledge": "2024-01-31", - "release_date": "2024-04-17", - "last_updated": "2024-04-17", + "extra_capabilities": { + "reasoning": { + "supported": true + } + }, + "attachment": true, + "open_weights": false, + "knowledge": "2025-04", + "release_date": "2026-06-02", + "last_updated": "2026-06-02", "type": "chat" - }, + } + ] + }, + "munito": { + "id": "munito", + "name": "Munito AI", + "display_name": "Munito AI", + "api": "https://api.munito.ai/inference/v1", + "doc": "https://docs.munito.ai/models", + "models": [ { - "id": "mistralai/mistral-small-24b-instruct-2501", - "name": "Mistral Small 24B", - "display_name": "Mistral Small 24B", + "id": "zai-org/glm-5.3-flash", + "name": "GLM-5.3-Flash", + "display_name": "GLM-5.3-Flash", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 32768, - "output": 8192 + "context": 1048576, + "output": 943718 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "effort_options": [ + "low", + "high", + "max" + ] + } + }, + "attachment": true, "open_weights": true, - "knowledge": "2023-10-31", - "release_date": "2025-01-30", - "last_updated": "2025-01-30", + "release_date": "2026-08-26", + "last_updated": "2026-08-26", "type": "chat" }, { - "id": "mistralai/ministral-3b-2512", - "name": "Ministral 3B", - "display_name": "Ministral 3B", + "id": "aleph-alpha/kolibri-1", + "name": "Kolibri 1", + "display_name": "Kolibri 1", "modalities": { "input": [ "text" @@ -260533,52 +260577,90 @@ ] }, "limit": { - "context": 131072, - "output": 32768 + "context": 1048576, + "output": 943718 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "effort_options": [ + "none", + "low", + "medium", + "high" + ] + } }, "attachment": false, "open_weights": true, - "release_date": "2025-12-02", - "last_updated": "2025-12-02", + "knowledge": "2026-06-18", + "release_date": "2026-10-03", + "last_updated": "2026-10-03", "type": "chat" }, { - "id": "mistralai/mistral-nemo-instruct-2407", - "name": "Mistral Nemo", - "display_name": "Mistral Nemo", + "id": "qwen/qwen3.8-27b", + "name": "Qwen3.8 27B", + "display_name": "Qwen3.8 27B", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 16384, - "output": 8192 + "context": 262144, + "output": 235929 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "effort_options": [ + "none", + "low", + "medium", + "xhigh" + ] + } + }, + "attachment": true, "open_weights": true, - "knowledge": "2024-07", - "release_date": "2024-07-18", - "last_updated": "2024-07-18", + "release_date": "2026-08-14", + "last_updated": "2026-08-14", "type": "chat" }, { - "id": "mistralai/mistral-large-4", - "name": "Mistral Large 4", - "display_name": "Mistral Large 4", + "id": "deepseek-ai/deepseek-v4.1-flash-1017", + "name": "DeepSeek V4.1 Flash 1017", + "display_name": "DeepSeek V4.1 Flash 1017", "modalities": { "input": [ "text", @@ -260589,8 +260671,8 @@ ] }, "limit": { - "context": 524288, - "output": 262144 + "context": 1048576, + "output": 943718 }, "temperature": true, "tool_call": true, @@ -260600,22 +260682,32 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ "none", - "high" + "low", + "high", + "max" ] } }, "attachment": true, - "open_weights": false, - "release_date": "2026-10-06", - "last_updated": "2026-10-06", + "open_weights": true, + "knowledge": "2025-05", + "release_date": "2026-10-17", + "last_updated": "2026-10-17", "type": "chat" }, { - "id": "mistralai/mistral-medium-3.1", - "name": "Mistral Medium 3.1", - "display_name": "Mistral Medium 3.1", + "id": "deepseek-ai/deepseek-v4-flash", + "name": "DeepSeek V4 Flash 0731", + "display_name": "DeepSeek V4 Flash 0731", "modalities": { "input": [ "text" @@ -260625,52 +260717,103 @@ ] }, "limit": { - "context": 131072, - "output": 32768 + "context": 1048576, + "output": 943718 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "high", + "effort_options": [ + "low", + "high", + "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." + ] + } }, "attachment": false, - "open_weights": false, - "knowledge": "2025-06-30", - "release_date": "2025-08-12", - "last_updated": "2025-08-12", + "open_weights": true, + "knowledge": "2025-05", + "release_date": "2026-07-31", + "last_updated": "2026-07-31", "type": "chat" - }, + } + ] + }, + "deepseek": { + "id": "deepseek", + "name": "DeepSeek", + "display_name": "DeepSeek", + "api": "https://api.deepseek.com", + "doc": "https://api-docs.deepseek.com/quick_start/pricing", + "models": [ { - "id": "mistralai/ministral-8b-2512", - "name": "Ministral 8B", - "display_name": "Ministral 8B", + "id": "deepseek-flash", + "name": "DeepSeek V4.1 Flash", + "display_name": "DeepSeek V4.1 Flash", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 32768 + "context": 1000000, + "output": 384000 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "effort_options": [ + "low", + "high", + "max" + ] + } + }, + "attachment": true, "open_weights": true, - "release_date": "2025-12-02", - "last_updated": "2025-12-02", + "knowledge": "2025-05", + "release_date": "2026-09-10", + "last_updated": "2026-09-10", "type": "chat" }, { - "id": "mistralai/mistral-medium-3.5", - "name": "Mistral Medium 3.5", - "display_name": "Mistral Medium 3.5", + "id": "deepseek-v4-flash-vision-exp", + "name": "DeepSeek V4 Flash Vision Exp", + "display_name": "DeepSeek V4 Flash Vision Exp", "modalities": { "input": [ "text", @@ -260681,9 +260824,10 @@ ] }, "limit": { - "context": 256000, - "output": 32768 + "context": 1000000, + "output": 384000 }, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -260691,22 +260835,37 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "high", "effort_options": [ - "none", - "high" + "low", + "high", + "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." ] } }, "attachment": true, "open_weights": true, - "release_date": "2026-04-29", - "last_updated": "2026-04-29", + "knowledge": "2025-05", + "release_date": "2026-08-21", + "last_updated": "2026-08-21", "type": "chat" }, { - "id": "mistralai/devstral-2-123b-instruct-2512", - "name": "Devstral 2 123B", - "display_name": "Devstral 2 123B", + "id": "deepseek-v4-pro", + "name": "DeepSeek V4 Pro", + "display_name": "DeepSeek V4 Pro", "modalities": { "input": [ "text" @@ -260716,25 +260875,47 @@ ] }, "limit": { - "context": 262144, - "output": 65536 + "context": 1048576, + "output": 393216 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "high", + "effort_options": [ + "low", + "high", + "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." + ] + } }, "attachment": false, "open_weights": true, - "knowledge": "2025-12", - "release_date": "2025-12-09", - "last_updated": "2025-12-09", + "release_date": "2026-08-13", + "last_updated": "2026-08-22", "type": "chat" }, { - "id": "mistralai/mistral-small-3.1-24b-instruct", - "name": "Mistral Small 3.1 24B (2503)", - "display_name": "Mistral Small 3.1 24B (2503)", + "id": "deepseek-v4-flash", + "name": "DeepSeek V4 Flash", + "display_name": "DeepSeek V4 Flash", "modalities": { "input": [ "text", @@ -260745,82 +260926,109 @@ ] }, "limit": { - "context": 128000, - "output": 102400 + "context": 1048576, + "output": 393216 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": false - }, - "attachment": true, - "open_weights": true, - "knowledge": "2023-10-31", - "release_date": "2025-03-17", - "last_updated": "2025-03-17", - "type": "chat" - }, - { - "id": "mistralai/devstral-small-2505", - "name": "Mistral Devstral Small 2505", - "display_name": "Mistral Devstral Small 2505", - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 32768, - "output": 8192 + "supported": true, + "default": true }, - "tool_call": false, - "reasoning": { - "supported": false + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "high", + "effort_options": [ + "low", + "high", + "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." + ] + } }, - "attachment": false, + "attachment": true, "open_weights": true, - "release_date": "2025-08-02", - "last_updated": "2025-08-02", + "knowledge": "2025-05", + "release_date": "2026-10-17", + "last_updated": "2026-10-17", "type": "chat" - }, + } + ] + }, + "zai": { + "id": "zai", + "name": "Z.AI", + "display_name": "Z.AI", + "api": "https://api.z.ai/api/paas/v4", + "doc": "https://docs.z.ai/guides/overview/pricing", + "models": [ { - "id": "mistralai/ministral-14b-instruct-2512", - "name": "Ministral 3 14B", - "display_name": "Ministral 3 14B", + "id": "glm-5.3-flashx", + "name": "GLM-5.3-FlashX", + "display_name": "GLM-5.3-FlashX", "modalities": { "input": [ "text", - "image" + "image", + "video", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 32768 + "context": 1000000, + "output": 131072 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "effort_options": [ + "low", + "high", + "max" + ] + } }, "attachment": true, "open_weights": true, - "release_date": "2025-12-02", - "last_updated": "2025-12-02", + "release_date": "2026-09-18", + "last_updated": "2026-09-18", "type": "chat" }, { - "id": "mistralai/mistral-large", - "name": "Mistral Large 2411", - "display_name": "Mistral Large 2411", + "id": "glm-4.6v", + "name": "GLM-4.6V", + "display_name": "GLM-4.6V", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" @@ -260828,36 +261036,50 @@ }, "limit": { "context": 128000, - "output": 102400 + "output": 32768 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, - "open_weights": false, - "knowledge": "2024-11-30", - "release_date": "2024-02-26", - "last_updated": "2024-02-26", + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } + }, + "attachment": true, + "open_weights": true, + "knowledge": "2025-04", + "release_date": "2025-12-08", + "last_updated": "2025-12-08", "type": "chat" }, { - "id": "mistralai/mistral-small-4-119b-2603:thinking", - "name": "Mistral Small 4 119B Thinking", - "display_name": "Mistral Small 4 119B Thinking", + "id": "glm-5.3-flash", + "name": "GLM-5.3-Flash", + "display_name": "GLM-5.3-Flash", "modalities": { "input": [ "text", - "image" + "image", + "video", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 16384 + "context": 1000000, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -260867,23 +261089,30 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ - "none", - "high" + "low", + "high", + "max" ] } }, "attachment": true, "open_weights": true, - "knowledge": "2025-06", - "release_date": "2026-03-16", - "last_updated": "2026-03-16", + "release_date": "2026-08-26", + "last_updated": "2026-08-26", "type": "chat" }, { - "id": "mistralai/codestral-2508", - "name": "Codestral 2508", - "display_name": "Codestral 2508", + "id": "glm-4.7", + "name": "GLM-4.7", + "display_name": "GLM-4.7", "modalities": { "input": [ "text" @@ -260893,24 +261122,37 @@ ] }, "limit": { - "context": 256000, - "output": 32768 + "context": 204800, + "output": 131072 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } }, "attachment": false, - "open_weights": false, - "release_date": "2025-07-30", - "last_updated": "2025-07-30", + "open_weights": true, + "knowledge": "2025-04", + "release_date": "2025-12-22", + "last_updated": "2025-12-22", "type": "chat" }, { - "id": "Salesforce/Llama-xLAM-2-70b-fc-r", - "name": "Llama-xLAM-2 70B fc-r", - "display_name": "Llama-xLAM-2 70B fc-r", + "id": "glm-4.6", + "name": "GLM-4.6", + "display_name": "GLM-4.6", "modalities": { "input": [ "text" @@ -260920,23 +261162,37 @@ ] }, "limit": { - "context": 128000, - "output": 16384 + "context": 204800, + "output": 131072 }, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } }, "attachment": false, "open_weights": true, - "release_date": "2025-04-13", - "last_updated": "2025-04-13", + "knowledge": "2025-04", + "release_date": "2025-09-30", + "last_updated": "2025-09-30", "type": "chat" }, { - "id": "meta-llama/llama-3.3-70b-instruct", - "name": "Llama 3.3 70b Instruct", - "display_name": "Llama 3.3 70b Instruct", + "id": "glm-4.5-air", + "name": "GLM-4.5-Air", + "display_name": "GLM-4.5-Air", "modalities": { "input": [ "text" @@ -260947,28 +261203,39 @@ }, "limit": { "context": 131072, - "output": 16384 + "output": 98304 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } }, "attachment": false, "open_weights": true, - "knowledge": "2023-12", - "release_date": "2024-12-06", - "last_updated": "2024-12-06", + "knowledge": "2025-04", + "release_date": "2025-07-28", + "last_updated": "2025-07-28", "type": "chat" }, { - "id": "meta-llama/llama-3.2-3b-instruct", - "name": "Llama 3.2 3b Instruct", - "display_name": "Llama 3.2 3b Instruct", + "id": "glm-4.5-flash", + "name": "GLM-4.5-Flash", + "display_name": "GLM-4.5-Flash", "modalities": { "input": [ - "text", - "pdf" + "text" ], "output": [ "text" @@ -260976,82 +261243,122 @@ }, "limit": { "context": 131072, - "output": 8192 + "output": 98304 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": true, - "open_weights": true, - "knowledge": "2023-12", - "release_date": "2024-09-25", - "last_updated": "2024-09-25", + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } + }, + "attachment": false, + "open_weights": false, + "knowledge": "2025-04", + "release_date": "2025-07-28", + "last_updated": "2025-07-28", "type": "chat" }, { - "id": "meta-llama/llama-4-maverick", - "name": "Llama 4 Maverick", - "display_name": "Llama 4 Maverick", + "id": "glm-5.3", + "name": "GLM-5.3", + "display_name": "GLM-5.3", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 1000000, + "output": 131072 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": true, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "effort_options": [ + "low", + "high", + "max" + ] + } + }, + "attachment": false, "open_weights": true, - "knowledge": "2024-08", - "release_date": "2025-04-05", - "last_updated": "2025-04-05", + "release_date": "2026-08-14", + "last_updated": "2026-08-14", "type": "chat" }, { - "id": "meta-llama/llama-4-scout", - "name": "Llama 4 Scout", - "display_name": "Llama 4 Scout", + "id": "glm-4.5v", + "name": "GLM-4.5V", + "display_name": "GLM-4.5V", "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 328000, - "output": 65536 + "context": 64000, + "output": 16384 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } }, "attachment": true, "open_weights": true, - "knowledge": "2024-08", - "release_date": "2025-04-05", - "last_updated": "2025-04-05", + "knowledge": "2025-04", + "release_date": "2025-08-11", + "last_updated": "2025-08-11", "type": "chat" }, { - "id": "meta-llama/llama-3.1-8b-instruct", - "name": "Llama 3.1 8b Instruct", - "display_name": "Llama 3.1 8b Instruct", + "id": "glm-5", + "name": "GLM-5", + "display_name": "GLM-5", "modalities": { "input": [ "text" @@ -261061,95 +261368,47 @@ ] }, "limit": { - "context": 131072, - "output": 16384 + "context": 204800, + "output": 131072 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false - }, - "attachment": false, - "open_weights": true, - "knowledge": "2023-12", - "release_date": "2024-07-23", - "last_updated": "2024-07-23", - "type": "chat" - }, - { - "id": "abacusai/Dracarys-72B-Instruct", - "name": "Llama 3.1 70B Dracarys 2", - "display_name": "Llama 3.1 70B Dracarys 2", - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 32768, - "output": 8192 + "supported": true, + "default": true }, - "tool_call": false, - "reasoning": { - "supported": false + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } }, "attachment": false, "open_weights": true, - "release_date": "2025-08-02", - "last_updated": "2025-08-02", + "release_date": "2026-02-12", + "last_updated": "2026-02-12", "type": "chat" }, { - "id": "perceptron/perceptron-mk1", - "name": "Perceptron Mk1", - "display_name": "Perceptron Mk1", + "id": "glm-4.7-flashx", + "name": "GLM-4.7-FlashX", + "display_name": "GLM-4.7-FlashX", "modalities": { "input": [ - "text", - "image", - "video" - ], - "output": [ "text" - ] - }, - "limit": { - "context": 32768, - "output": 8192 - }, - "temperature": true, - "tool_call": false, - "reasoning": { - "supported": true, - "default": true - }, - "attachment": true, - "open_weights": false, - "release_date": "2026-05-12", - "last_updated": "2026-05-12", - "type": "chat" - }, - { - "id": "perceptron/perceptron-mk1.5", - "name": "Perceptron Mk1.5", - "display_name": "Perceptron Mk1.5", - "modalities": { - "input": [ - "text", - "image", - "video", - "audio" ], "output": [ "text" ] }, "limit": { - "context": 36864, - "output": 8192 + "context": 200000, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -261159,25 +261418,26 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "none", - "minimal", - "low", - "medium", - "high" + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, - "attachment": true, - "open_weights": false, - "release_date": "2026-09-25", - "last_updated": "2026-09-25", + "attachment": false, + "open_weights": true, + "knowledge": "2025-04", + "release_date": "2026-01-19", + "last_updated": "2026-01-19", "type": "chat" }, { - "id": "deepseek-ai/deepseek-v3.2-exp-thinking", - "name": "DeepSeek V3.2 Exp Thinking", - "display_name": "DeepSeek V3.2 Exp Thinking", + "id": "glm-4.7-flash", + "name": "GLM-4.7-Flash", + "display_name": "GLM-4.7-Flash", "modalities": { "input": [ "text" @@ -261187,27 +261447,42 @@ ] }, "limit": { - "context": 163840, - "output": 65536 + "context": 200000, + "output": 131072 }, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { "supported": true, "default": true }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } + }, "attachment": false, "open_weights": true, - "release_date": "2024-01-01", - "last_updated": "2025-09-29", + "knowledge": "2025-04", + "release_date": "2026-01-19", + "last_updated": "2026-01-19", "type": "chat" }, { - "id": "deepseek-ai/DeepSeek-V3.1-Terminus", - "name": "DeepSeek V3.1 Terminus", - "display_name": "DeepSeek V3.1 Terminus", + "id": "glm-4.6v-flash", + "name": "GLM-4.6V-Flash", + "display_name": "GLM-4.6V-Flash", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" @@ -261215,22 +261490,35 @@ }, "limit": { "context": 128000, - "output": 65536 + "output": 32768 }, + "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } + }, + "attachment": true, "open_weights": true, - "release_date": "2025-08-02", - "last_updated": "2025-08-02", + "release_date": "2025-12-08", + "last_updated": "2025-12-08", "type": "chat" }, { - "id": "deepseek-ai/DeepSeek-V3.1:thinking", - "name": "DeepSeek V3.1 Thinking", - "display_name": "DeepSeek V3.1 Thinking", + "id": "glm-5.1", + "name": "GLM-5.1", + "display_name": "GLM-5.1", "modalities": { "input": [ "text" @@ -261240,78 +261528,121 @@ ] }, "limit": { - "context": 128000, - "output": 65536 + "context": 200000, + "output": 131072 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } }, "attachment": false, "open_weights": true, - "release_date": "2025-08-21", - "last_updated": "2025-08-21", + "release_date": "2026-04-07", + "last_updated": "2026-04-07", "type": "chat" }, { - "id": "deepseek-ai/DeepSeek-V3.1-Terminus:thinking", - "name": "DeepSeek V3.1 Terminus (Thinking)", - "display_name": "DeepSeek V3.1 Terminus (Thinking)", + "id": "glm-5v-turbo", + "name": "GLM-5V-Turbo", + "display_name": "GLM-5V-Turbo", "modalities": { "input": [ - "text" + "text", + "image", + "video", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 65536 + "context": 200000, + "output": 131072 }, + "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, - "open_weights": true, - "release_date": "2024-01-01", - "last_updated": "2025-09-22", + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } + }, + "attachment": true, + "open_weights": false, + "release_date": "2026-04-01", + "last_updated": "2026-04-01", "type": "chat" }, { - "id": "deepseek-ai/DeepSeek-V3.1", - "name": "DeepSeek V3.1", - "display_name": "DeepSeek V3.1", + "id": "glm-5.2", + "name": "GLM-5.2", + "display_name": "GLM-5.2", "modalities": { "input": [ - "text", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 65536 + "context": 1000000, + "output": 131072 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": true, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "effort_options": [ + "high", + "max" + ] + } + }, + "attachment": false, "open_weights": true, - "release_date": "2025-08-21", - "last_updated": "2025-08-21", + "release_date": "2026-06-13", + "last_updated": "2026-06-13", "type": "chat" }, { - "id": "deepseek-ai/deepseek-v3.2-exp", - "name": "DeepSeek V3.2 Exp", - "display_name": "DeepSeek V3.2 Exp", + "id": "glm-5-turbo", + "name": "GLM-5-Turbo", + "display_name": "GLM-5-Turbo", "modalities": { "input": [ "text" @@ -261321,23 +261652,36 @@ ] }, "limit": { - "context": 163840, - "output": 65536 + "context": 200000, + "output": 131072 }, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } }, "attachment": false, - "open_weights": true, - "release_date": "2024-01-01", - "last_updated": "2025-09-29", + "open_weights": false, + "release_date": "2026-03-16", + "last_updated": "2026-03-16", "type": "chat" }, { - "id": "deepseek-ai/DeepSeek-R1-0528", - "name": "DeepSeek R1 0528", - "display_name": "DeepSeek R1 0528", + "id": "glm-4.5", + "name": "GLM-4.5", + "display_name": "GLM-4.5", "modalities": { "input": [ "text" @@ -261347,8 +261691,8 @@ ] }, "limit": { - "context": 163840, - "output": 32768 + "context": 131072, + "output": 98304 }, "temperature": true, "tool_call": true, @@ -261369,27 +261713,39 @@ }, "attachment": false, "open_weights": true, - "release_date": "2025-05-28", - "last_updated": "2025-05-28", + "knowledge": "2025-04", + "release_date": "2025-07-28", + "last_updated": "2025-07-28", "type": "chat" - }, + } + ] + }, + "kilo": { + "id": "kilo", + "name": "Kilo Gateway", + "display_name": "Kilo Gateway", + "api": "https://api.kilo.ai/api/gateway", + "doc": "https://kilo.ai", + "models": [ { - "id": "inception/mercury-3", - "name": "Mercury 3", - "display_name": "Mercury 3", + "id": "openai/gpt-5.2-pro", + "name": "GPT-5.2 Pro", + "display_name": "GPT-5.2 Pro", "modalities": { "input": [ - "text" + "image", + "text", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 400000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -261397,36 +261753,50 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "high", "effort_options": [ - "none", + "medium", + "high", + "xhigh" + ], + "verbosity": "medium", + "verbosity_options": [ "low", "medium", "high" - ] + ], + "visibility": "hidden" } }, - "attachment": false, + "attachment": true, "open_weights": false, - "release_date": "2026-10-16", - "last_updated": "2026-10-20", + "knowledge": "2025-08-31", + "release_date": "2025-12-11", + "last_updated": "2025-12-11", "type": "chat" }, { - "id": "inception/mercury-2.5-preview", - "name": "Mercury 2.5 Preview", - "display_name": "Mercury 2.5 Preview", + "id": "openai/gpt-5-pro", + "name": "GPT-5 Pro", + "display_name": "GPT-5 Pro", "modalities": { "input": [ - "text" + "image", + "text", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 260000, - "output": 65536 + "context": 400000, + "output": 128000 }, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -261435,26 +261805,33 @@ "extra_capabilities": { "reasoning": { "supported": true, - "effort_options": [ - "none", + "default_enabled": true, + "mode": "fixed", + "effort": "high", + "verbosity": "medium", + "verbosity_options": [ "low", "medium", "high" - ] + ], + "visibility": "hidden" } }, - "attachment": false, + "attachment": true, "open_weights": false, - "release_date": "2026-09-01", - "last_updated": "2026-09-01", + "knowledge": "2024-09-30", + "release_date": "2025-10-06", + "last_updated": "2025-10-06", "type": "chat" }, { - "id": "MarinaraSpaghetti/NemoMix-Unleashed-12B", - "name": "NemoMix 12B Unleashed", - "display_name": "NemoMix 12B Unleashed", + "id": "openai/gpt-5.4-nano", + "name": "GPT-5.4 nano", + "display_name": "GPT-5.4 nano", "modalities": { "input": [ + "pdf", + "image", "text" ], "output": [ @@ -261462,47 +261839,63 @@ ] }, "limit": { - "context": 32768, - "output": 8192 + "context": 400000, + "output": 128000 }, - "tool_call": false, + "temperature": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": false }, - "attachment": false, - "open_weights": true, - "release_date": "2024-01-01", - "last_updated": "2024-07-01", + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": false, + "mode": "effort", + "effort": "none", + "effort_options": [ + "none", + "low", + "medium", + "high", + "xhigh" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" + } + }, + "attachment": true, + "open_weights": false, + "knowledge": "2025-08-31", + "release_date": "2026-03-17", + "last_updated": "2026-03-17", "type": "chat" - } - ] - }, - "modular": { - "id": "modular", - "name": "Modular", - "display_name": "Modular", - "api": "https://api.modular.com/v1", - "doc": "https://docs.modular.com/models", - "models": [ + }, { - "id": "minimax/minimax-m3", - "name": "MiniMax-M3", - "display_name": "MiniMax-M3", + "id": "openai/gpt-6-astra-pro", + "name": "OpenAI: GPT-6 Astra Pro ($$$$)", + "display_name": "OpenAI: GPT-6 Astra Pro ($$$$)", "modalities": { "input": [ - "text", + "pdf", "image", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 512000 + "context": 1050000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -261510,39 +261903,40 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-06-01", - "last_updated": "2026-06-01", + "open_weights": false, + "release_date": "2026-09-04", + "last_updated": "2026-09-04", "type": "chat" }, { - "id": "deepseek/deepseek-v4.1-flash", - "name": "DeepSeek V4.1 Flash", - "display_name": "DeepSeek V4.1 Flash", + "id": "openai/gpt-6.1-terra", + "name": "OpenAI: GPT-6.1 Terra", + "display_name": "OpenAI: GPT-6.1 Terra", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 384000 + "context": 1050000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -261550,176 +261944,200 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, "effort_options": [ + "none", "low", + "medium", "high", + "xhigh", "max" ] } }, "attachment": true, - "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-09-10", - "last_updated": "2026-09-10", + "open_weights": false, + "knowledge": "2026-06-01", + "release_date": "2026-10-26", + "last_updated": "2026-10-26", "type": "chat" }, { - "id": "google/gemma-4-31b-it", - "name": "Gemma 4 31B IT", - "display_name": "Gemma 4 31B IT", + "id": "openai/gpt-5.4", + "name": "GPT-5.4", + "display_name": "GPT-5.4", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 32768 + "context": 1050000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] + "default_enabled": false, + "mode": "effort", + "effort": "none", + "effort_options": [ + "none", + "low", + "medium", + "high", + "xhigh" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, "attachment": true, - "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-04-02", - "last_updated": "2026-04-02", + "open_weights": false, + "knowledge": "2025-08-31", + "release_date": "2026-03-05", + "last_updated": "2026-03-05", "type": "chat" }, { - "id": "MiniMaxAI/MiniMax-M3.1-Flash", - "name": "MiniMax-M3.1-Flash", - "display_name": "MiniMax-M3.1-Flash", + "id": "openai/gpt-5.4-mini", + "name": "GPT-5.4 mini", + "display_name": "GPT-5.4 mini", "modalities": { "input": [ - "text", + "pdf", "image", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 512000 + "context": 400000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], + "default_enabled": false, + "mode": "effort", + "effort": "none", "effort_options": [ + "none", "low", "medium", "high", - "xhigh", - "max" - ] + "xhigh" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, "attachment": true, "open_weights": false, - "release_date": "2026-10-19", - "last_updated": "2026-10-19", + "knowledge": "2025-08-31", + "release_date": "2026-03-17", + "last_updated": "2026-03-17", "type": "chat" - } - ] - }, - "runinfra": { - "id": "runinfra", - "name": "RunInfra", - "display_name": "RunInfra", - "api": "https://api.runinfra.ai/v1", - "doc": "https://runinfra.ai/docs", - "models": [ + }, { - "id": "zai-org/GLM-5.3-Flash", - "name": "GLM-5.3-Flash", - "display_name": "GLM-5.3-Flash", + "id": "openai/gpt-5.1-codex-max", + "name": "GPT-5.1 Codex Max", + "display_name": "GPT-5.1 Codex Max", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 32768 + "context": 400000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, + "default_enabled": false, + "mode": "effort", + "effort": "none", "effort_options": [ + "none", "low", - "high", - "max" - ] + "medium", + "high" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, "attachment": true, - "open_weights": true, - "release_date": "2026-08-26", - "last_updated": "2026-08-26", + "open_weights": false, + "knowledge": "2024-09-30", + "release_date": "2025-11-13", + "last_updated": "2025-11-13", "type": "chat" }, { - "id": "zai-org/GLM-5.3", - "name": "GLM-5.3", - "display_name": "GLM-5.3", + "id": "openai/gpt-6-sol", + "name": "GPT-6 Sol", + "display_name": "GPT-6 Sol", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 32768 + "context": 1050000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -261727,44 +262145,42 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], "effort_options": [ + "none", "low", + "medium", "high", + "xhigh", "max" ] } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-08-14", - "last_updated": "2026-08-14", + "attachment": true, + "open_weights": false, + "knowledge": "2026-04-20", + "release_date": "2026-09-22", + "last_updated": "2026-09-22", "type": "chat" }, { - "id": "ornith-ai/Ornith-1.5-35B-A3B", - "name": "Ornith 1.5 35B A3B", - "display_name": "Ornith 1.5 35B A3B", + "id": "openai/gpt-5.6-terra", + "name": "GPT-5.6 Terra", + "display_name": "GPT-5.6 Terra", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 32768 + "context": 1050000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -261773,38 +262189,52 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] + "default_enabled": true, + "mode": "effort", + "effort": "medium", + "effort_options": [ + "none", + "low", + "medium", + "high", + "xhigh", + "max" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, "attachment": true, - "open_weights": true, - "release_date": "2026-08-18", - "last_updated": "2026-08-23", + "open_weights": false, + "knowledge": "2026-02-16", + "release_date": "2026-07-09", + "last_updated": "2026-07-09", "type": "chat" }, { - "id": "Qwen/Qwen3.8-27B", - "name": "Qwen3.8 27B", - "display_name": "Qwen3.8 27B", + "id": "openai/gpt-5.3-codex", + "name": "GPT-5.3 Codex", + "display_name": "GPT-5.3 Codex", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 32768 + "context": 400000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -261812,37 +262242,51 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", "effort_options": [ - "none", "low", "medium", + "high", "xhigh" - ] + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, "attachment": true, - "open_weights": true, - "release_date": "2026-08-14", - "last_updated": "2026-08-14", + "open_weights": false, + "knowledge": "2025-08-31", + "release_date": "2026-02-05", + "last_updated": "2026-02-05", "type": "chat" }, { - "id": "Inferact/Qwen3.8-2.4T-A95B-NVFP4", - "name": "Qwen3.8 2.4T A95B (NVFP4)", - "display_name": "Qwen3.8 2.4T A95B (NVFP4)", + "id": "openai/gpt-6-astra", + "name": "GPT-6 Astra", + "display_name": "GPT-6 Astra", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 32768 + "context": 1050000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -261850,66 +262294,125 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, "effort_options": [ "low", "medium", - "xhigh" + "high", + "xhigh", + "max" ] } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-08-12", - "last_updated": "2026-08-12", + "attachment": true, + "open_weights": false, + "knowledge": "2026-04-30", + "release_date": "2026-09-04", + "last_updated": "2026-09-04", "type": "chat" }, { - "id": "nvidia/NVIDIA-Nemotron-3.5-Lightning-30B-A3B-BF16", - "name": "Nemotron 3.5 Lightning 30B A3B", - "display_name": "Nemotron 3.5 Lightning 30B A3B", + "id": "openai/gpt-5.1-codex", + "name": "GPT-5.1 Codex", + "display_name": "GPT-5.1 Codex", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 32768 + "context": 400000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-09", - "release_date": "2026-08-11", - "last_updated": "2026-08-11", + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": false, + "mode": "effort", + "effort": "none", + "effort_options": [ + "none", + "low", + "medium", + "high" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" + } + }, + "attachment": true, + "open_weights": false, + "knowledge": "2024-09-30", + "release_date": "2025-11-13", + "last_updated": "2025-11-13", "type": "chat" }, { - "id": "deepseek-ai/DeepSeek-V4-Flash-0731", - "name": "DeepSeek V4 Flash 0731", - "display_name": "DeepSeek V4 Flash 0731", + "id": "openai/gpt-4o-2024-08-06", + "name": "GPT-4o (2024-08-06)", + "display_name": "GPT-4o (2024-08-06)", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 32768 + "context": 128000, + "output": 16384 }, "temperature": true, "tool_call": true, + "reasoning": { + "supported": false + }, + "attachment": true, + "open_weights": false, + "knowledge": "2023-09", + "release_date": "2024-08-06", + "last_updated": "2024-08-06", + "type": "chat" + }, + { + "id": "openai/gpt-5.2-codex", + "name": "GPT-5.2 Codex", + "display_name": "GPT-5.2 Codex", + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 400000, + "output": 128000 + }, + "temperature": false, + "tool_call": true, "reasoning": { "supported": true, "default": true @@ -261917,26 +262420,39 @@ "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", "effort_options": [ - "none", "low", - "max" - ] + "medium", + "high", + "xhigh" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-07-31", - "last_updated": "2026-07-31", + "attachment": true, + "open_weights": false, + "knowledge": "2025-08-31", + "release_date": "2025-12-11", + "last_updated": "2025-12-11", "type": "chat" }, { - "id": "deepseek-ai/DeepSeek-V4-Pro-0813", - "name": "DeepSeek V4 Pro 0813", - "display_name": "DeepSeek V4 Pro 0813", + "id": "openai/gpt-5.2", + "name": "GPT-5.2", + "display_name": "GPT-5.2", "modalities": { "input": [ + "pdf", + "image", "text" ], "output": [ @@ -261944,54 +262460,63 @@ ] }, "limit": { - "context": 1048576, - "output": 32768 + "context": 400000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { - "supported": true + "supported": true, + "default_enabled": false, + "mode": "effort", + "effort": "none", + "effort_options": [ + "none", + "low", + "medium", + "high", + "xhigh" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-08-13", - "last_updated": "2026-08-22", + "attachment": true, + "open_weights": false, + "knowledge": "2025-08-31", + "release_date": "2025-12-11", + "last_updated": "2025-12-11", "type": "chat" - } - ] - }, - "novita-ai": { - "id": "novita-ai", - "name": "Novita AI", - "display_name": "Novita AI", - "api": "https://api.novita.ai/openai", - "doc": "https://novita.ai/docs/guides/introduction", - "models": [ + }, { - "id": "zai-org/glm-4.5v", - "name": "GLM-4.5V", - "display_name": "GLM-4.5V", + "id": "openai/gpt-5.4-pro", + "name": "GPT-5.4 Pro", + "display_name": "GPT-5.4 Pro", "modalities": { "input": [ "text", "image", - "video" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 65536, - "output": 16384 + "context": 1050000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -262000,40 +262525,49 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] + "default_enabled": true, + "mode": "effort", + "effort": "high", + "effort_options": [ + "medium", + "high", + "xhigh" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, "attachment": true, - "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-08-11", - "last_updated": "2025-08-11", + "open_weights": false, + "knowledge": "2025-08-31", + "release_date": "2026-03-05", + "last_updated": "2026-03-05", "type": "chat" }, { - "id": "zai-org/glm-5.3-flash", - "name": "GLM-5.3-Flash", - "display_name": "GLM-5.3-Flash", + "id": "openai/gpt-6.1-sol", + "name": "GPT-6.1 Sol", + "display_name": "GPT-6.1 Sol", "modalities": { "input": [ "text", "image", - "video" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 1050000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -262041,73 +262575,71 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], "effort_options": [ "low", + "medium", "high", + "xhigh", "max" ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-08-26", - "last_updated": "2026-08-26", + "open_weights": false, + "knowledge": "2026-04-30", + "release_date": "2026-09-29", + "last_updated": "2026-09-29", "type": "chat" }, { - "id": "zai-org/autoglm-phone-9b-multilingual", - "name": "AutoGLM Phone 9B Multilingual", - "display_name": "AutoGLM Phone 9B Multilingual", + "id": "openai/gpt-4o-2024-11-20", + "name": "GPT-4o (2024-11-20)", + "display_name": "GPT-4o (2024-11-20)", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 65536, - "output": 65536 + "context": 128000, + "output": 16384 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { "supported": false }, "attachment": true, - "open_weights": true, - "release_date": "2025-12-10", - "last_updated": "2025-12-10", + "open_weights": false, + "knowledge": "2023-09", + "release_date": "2024-11-20", + "last_updated": "2024-11-20", "type": "chat" }, { - "id": "zai-org/glm-4.6v", - "name": "GLM-4.6V", - "display_name": "GLM-4.6V", + "id": "openai/gpt-6.1-sol-pro", + "name": "OpenAI: GPT-6.1 Sol Pro", + "display_name": "OpenAI: GPT-6.1 Sol Pro", "modalities": { "input": [ - "text", + "pdf", "image", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 32768 + "context": 1050000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -262115,68 +262647,59 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" ] } }, "attachment": true, - "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-12-08", - "last_updated": "2025-12-08", + "open_weights": false, + "release_date": "2026-09-29", + "last_updated": "2026-09-29", "type": "chat" }, { - "id": "zai-org/glm-4.7", - "name": "GLM-4.7", - "display_name": "GLM-4.7", + "id": "openai/gpt-4o-2024-05-13", + "name": "GPT-4o (2024-05-13)", + "display_name": "GPT-4o (2024-05-13)", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 204800, - "output": 131072 + "context": 128000, + "output": 4096 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } + "supported": false }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-12-22", - "last_updated": "2025-12-22", + "attachment": true, + "open_weights": false, + "knowledge": "2023-09", + "release_date": "2024-05-13", + "last_updated": "2024-05-13", "type": "chat" }, { - "id": "zai-org/glm-4.6", - "name": "GLM-4.6", - "display_name": "GLM-4.6", + "id": "openai/gpt-6-sol-pro", + "name": "OpenAI: GPT-6 Sol Pro", + "display_name": "OpenAI: GPT-6 Sol Pro", "modalities": { "input": [ + "pdf", + "image", "text" ], "output": [ @@ -262184,10 +262707,10 @@ ] }, "limit": { - "context": 204800, - "output": 131072 + "context": 1050000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -262195,118 +262718,102 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "none", + "low", + "medium", + "high", + "xhigh", + "max" ] } }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-09-30", - "last_updated": "2025-09-30", + "attachment": true, + "open_weights": false, + "release_date": "2026-09-22", + "last_updated": "2026-09-22", "type": "chat" }, { - "id": "zai-org/glm-4.5-air", - "name": "GLM-4.5-Air", - "display_name": "GLM-4.5-Air", + "id": "openai/gpt-audio-mini", + "name": "GPT-Audio Mini", + "display_name": "GPT-Audio Mini", "modalities": { "input": [ - "text" + "text", + "audio", + "pdf" ], "output": [ - "text" + "text", + "audio" ] }, "limit": { - "context": 131072, - "output": 98304 + "context": 128000, + "output": 16384 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } + "supported": false }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-07-28", - "last_updated": "2025-07-28", + "attachment": true, + "open_weights": false, + "knowledge": "2023-10-01", + "release_date": "2025-10-06", + "last_updated": "2025-10-06", "type": "chat" }, { - "id": "zai-org/glm-5.1", - "name": "GLM-5.1", - "display_name": "GLM-5.1", + "id": "openai/gpt-4.1-nano", + "name": "GPT-4.1 nano", + "display_name": "GPT-4.1 nano", "modalities": { "input": [ - "text" + "image", + "text", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 204800, - "output": 131072 + "context": 1047576, + "output": 942818 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } + "supported": false }, - "attachment": false, - "open_weights": true, - "release_date": "2026-04-07", - "last_updated": "2026-04-07", + "attachment": true, + "open_weights": false, + "knowledge": "2024-04", + "release_date": "2025-04-14", + "last_updated": "2025-04-14", "type": "chat" }, { - "id": "zai-org/glm-5", - "name": "GLM-5", - "display_name": "GLM-5", + "id": "openai/gpt-5-nano", + "name": "GPT-5 Nano", + "display_name": "GPT-5 Nano", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 202800, - "output": 131072 + "context": 400000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -262315,37 +262822,50 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] + "default_enabled": true, + "mode": "effort", + "effort": "medium", + "effort_options": [ + "minimal", + "low", + "medium", + "high" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-02-12", - "last_updated": "2026-02-12", + "attachment": true, + "open_weights": false, + "knowledge": "2024-05-30", + "release_date": "2025-08-07", + "last_updated": "2025-08-07", "type": "chat" }, { - "id": "zai-org/glm-5.3", - "name": "GLM-5.3", - "display_name": "GLM-5.3", + "id": "openai/o3-pro", + "name": "o3-pro", + "display_name": "o3-pro", "modalities": { "input": [ - "text" + "text", + "pdf", + "image" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 200000, + "output": 100000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -262354,83 +262874,77 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], + "default_enabled": true, + "mode": "effort", + "effort": "medium", "effort_options": [ - "none", "low", - "high", - "max" - ] + "medium", + "high" + ], + "visibility": "hidden" } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-08-14", - "last_updated": "2026-08-14", + "attachment": true, + "open_weights": false, + "knowledge": "2024-05", + "release_date": "2025-06-10", + "last_updated": "2025-06-10", "type": "chat" }, { - "id": "zai-org/glm-4.7-flash", - "name": "GLM-4.7-Flash", - "display_name": "GLM-4.7-Flash", + "id": "openai/gpt-chat-latest", + "name": "OpenAI: GPT Chat Latest", + "display_name": "OpenAI: GPT Chat Latest", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 200000, + "context": 400000, "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { - "supported": true, - "default": true + "supported": true }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] + "supported": true } }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-04", - "release_date": "2026-01-19", - "last_updated": "2026-01-19", + "attachment": true, + "open_weights": false, + "release_date": "2026-05-05", + "last_updated": "2026-05-05", "type": "chat" }, { - "id": "zai-org/glm-5.2", - "name": "GLM-5.2", - "display_name": "GLM-5.2", + "id": "openai/gpt-5.5-pro", + "name": "GPT-5.5 Pro", + "display_name": "GPT-5.5 Pro", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 1050000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -262439,100 +262953,121 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], "effort_options": [ - "none", + "medium", "high", - "max" + "xhigh" ] } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-06-13", - "last_updated": "2026-06-13", + "attachment": true, + "open_weights": false, + "knowledge": "2025-12-01", + "release_date": "2026-04-23", + "last_updated": "2026-04-23", "type": "chat" }, { - "id": "paddlepaddle/paddleocr-vl", - "name": "PaddleOCR VL", - "display_name": "PaddleOCR VL", + "id": "openai/gpt-3.5-turbo-16k", + "name": "OpenAI: GPT-3.5 Turbo 16k", + "display_name": "OpenAI: GPT-3.5 Turbo 16k", "modalities": { "input": [ "text", - "image" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 16384, - "output": 16384 + "context": 16385, + "output": 4096 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { "supported": false }, "attachment": true, - "open_weights": true, - "release_date": "2025-10-16", - "last_updated": "2025-10-16", + "open_weights": false, + "release_date": "2023-08-28", + "last_updated": "2023-08-28", "type": "chat" }, { - "id": "minimaxai/minimax-m1-80k", - "name": "MiniMax M1 80K", - "display_name": "MiniMax M1 80K", + "id": "openai/gpt-5.6-luna", + "name": "GPT-5.6 Luna", + "display_name": "GPT-5.6 Luna", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 40000 + "context": 1050000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, "default": true }, - "attachment": false, - "open_weights": true, - "release_date": "2025-06-17", - "last_updated": "2025-06-17", + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", + "effort_options": [ + "none", + "low", + "medium", + "high", + "xhigh", + "max" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" + } + }, + "attachment": true, + "open_weights": false, + "knowledge": "2026-02-16", + "release_date": "2026-07-09", + "last_updated": "2026-07-09", "type": "chat" }, { - "id": "minimax/minimax-m3", - "name": "MiniMax-M3", - "display_name": "MiniMax-M3", + "id": "openai/gpt-5", + "name": "GPT-5", + "display_name": "GPT-5", "modalities": { "input": [ "text", "image", - "video" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 400000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -262541,39 +263076,50 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] + "default_enabled": true, + "mode": "effort", + "effort": "medium", + "effort_options": [ + "minimal", + "low", + "medium", + "high" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, "attachment": true, - "open_weights": true, - "release_date": "2026-06-01", - "last_updated": "2026-06-01", + "open_weights": false, + "knowledge": "2024-09-30", + "release_date": "2025-08-07", + "last_updated": "2025-08-07", "type": "chat" }, { - "id": "minimax/minimax-m3.1-flash", - "name": "MiniMax-M3.1-Flash", - "display_name": "MiniMax-M3.1-Flash", + "id": "openai/gpt-6-luna-pro", + "name": "OpenAI: GPT-6 Luna Pro", + "display_name": "OpenAI: GPT-6 Luna Pro", "modalities": { "input": [ - "text", + "pdf", "image", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 524288 + "context": 1050000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -262581,14 +263127,8 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], "effort_options": [ + "none", "low", "medium", "high", @@ -262599,27 +263139,29 @@ }, "attachment": true, "open_weights": false, - "release_date": "2026-10-19", - "last_updated": "2026-10-19", + "release_date": "2026-09-22", + "last_updated": "2026-09-22", "type": "chat" }, { - "id": "minimax/minimax-m2.5", - "name": "MiniMax-M2.5", - "display_name": "MiniMax-M2.5", + "id": "openai/gpt-5-mini", + "name": "GPT-5 Mini", + "display_name": "GPT-5 Mini", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 204800, - "output": 131100 + "context": 400000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -262628,24 +263170,35 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] + "default_enabled": true, + "mode": "effort", + "effort": "medium", + "effort_options": [ + "minimal", + "low", + "medium", + "high" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-02-12", - "last_updated": "2026-02-12", + "attachment": true, + "open_weights": false, + "knowledge": "2024-05-30", + "release_date": "2025-08-07", + "last_updated": "2025-08-07", "type": "chat" }, { - "id": "minimax/minimax-m2.7", - "name": "MiniMax-M2.7", - "display_name": "MiniMax-M2.7", + "id": "openai/gpt-oss-safeguard-20b", + "name": "GPT OSS Safeguard 20B", + "display_name": "GPT OSS Safeguard 20B", "modalities": { "input": [ "text" @@ -262655,8 +263208,8 @@ ] }, "limit": { - "context": 204800, - "output": 131072 + "context": 131072, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -262666,161 +263219,120 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "high" ] } }, "attachment": false, "open_weights": true, - "release_date": "2026-03-18", - "last_updated": "2026-03-18", + "release_date": "2025-10-29", + "last_updated": "2025-10-29", "type": "chat" }, { - "id": "minimax/minimax-m2.7-highspeed", - "name": "MiniMax-M2.7-highspeed", - "display_name": "MiniMax-M2.7-highspeed", + "id": "openai/gpt-4o-mini-2024-07-18", + "name": "OpenAI: GPT-4o-mini (2024-07-18)", + "display_name": "OpenAI: GPT-4o-mini (2024-07-18)", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 204800, - "output": 131072 + "context": 128000, + "output": 16384 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } + "supported": false }, - "attachment": false, - "open_weights": true, - "release_date": "2026-03-18", - "last_updated": "2026-03-18", + "attachment": true, + "open_weights": false, + "knowledge": "2023-09", + "release_date": "2024-07-18", + "last_updated": "2024-07-18", "type": "chat" }, { - "id": "xiaomimimo/mimo-v2.6-pro", - "name": "MiMo-V2.6-Pro", - "display_name": "MiMo-V2.6-Pro", + "id": "openai/gpt-3.5-turbo-0613", + "name": "OpenAI: GPT-3.5 Turbo (older v0613)", + "display_name": "OpenAI: GPT-3.5 Turbo (older v0613)", "modalities": { "input": [ "text", - "image", - "audio", - "video" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 4095, + "output": 3685 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } + "supported": false }, "attachment": true, - "open_weights": true, - "release_date": "2026-09-22", - "last_updated": "2026-09-22", + "open_weights": false, + "release_date": "2024-01-25", + "last_updated": "2024-01-25", "type": "chat" }, { - "id": "xiaomimimo/mimo-v2.6-flash", - "name": "MiMo-V2.6-Flash", - "display_name": "MiMo-V2.6-Flash", + "id": "openai/gpt-4o-mini", + "name": "GPT-4o mini", + "display_name": "GPT-4o mini", "modalities": { "input": [ "text", "image", - "audio", - "video" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 128000, + "output": 16384 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } + "supported": false }, "attachment": true, - "open_weights": true, - "release_date": "2026-09-22", - "last_updated": "2026-09-22", + "open_weights": false, + "knowledge": "2023-09", + "release_date": "2024-07-18", + "last_updated": "2024-07-18", "type": "chat" }, { - "id": "xiaomimimo/mimo-v2.5", - "name": "MiMo-V2.5", - "display_name": "MiMo-V2.5", + "id": "openai/gpt-oss-120b", + "name": "GPT OSS 120B", + "display_name": "GPT OSS 120B", "modalities": { "input": [ - "text", - "image", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 131072, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -262831,226 +263343,342 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "low", + "medium", + "high" ] } }, - "attachment": true, + "attachment": false, "open_weights": true, - "knowledge": "2024-12", - "release_date": "2026-04-22", - "last_updated": "2026-04-22", + "knowledge": "2024-06", + "release_date": "2025-08-05", + "last_updated": "2025-08-05", "type": "chat" }, { - "id": "xiaomimimo/mimo-v2.5-pro", - "name": "MiMo-V2.5-Pro", - "display_name": "MiMo-V2.5-Pro", + "id": "openai/gpt-5.4-image-2", + "name": "GPT-5.4 Image 2", + "display_name": "GPT-5.4 Image 2", "modalities": { "input": [ - "text" + "image", + "text", + "pdf" ], "output": [ + "image", "text" ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 272000, + "output": 128000 }, - "temperature": true, - "tool_call": true, + "temperature": false, + "tool_call": false, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] + "default_enabled": false, + "mode": "effort", + "effort": "none", + "effort_options": [ + "none", + "low", + "medium", + "high", + "xhigh" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, - "attachment": false, - "open_weights": true, - "knowledge": "2024-12", - "release_date": "2026-04-22", - "last_updated": "2026-04-22", - "type": "chat" + "attachment": true, + "open_weights": false, + "release_date": "2026-04-21", + "last_updated": "2026-04-21" }, { - "id": "gryphe/mythomax-l2-13b", - "name": "MythoMax L2 13B", - "display_name": "MythoMax L2 13B", + "id": "openai/gpt-4", + "name": "GPT-4", + "display_name": "GPT-4", "modalities": { "input": [ - "text" + "text", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 4096, - "output": 3200 + "context": 8191, + "output": 4096 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { "supported": false }, - "attachment": false, - "open_weights": true, - "release_date": "2023-08-10", - "last_updated": "2023-08-10", + "attachment": true, + "open_weights": false, + "knowledge": "2023-11", + "release_date": "2023-11-06", + "last_updated": "2024-04-09", "type": "chat" }, { - "id": "apodex/apodex-1.1-mini", - "name": "Apodex 1.1 Mini", - "display_name": "Apodex 1.1 Mini", + "id": "openai/gpt-4o", + "name": "GPT-4o", + "display_name": "GPT-4o", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 128000, + "output": 16384 }, "temperature": true, "tool_call": true, + "reasoning": { + "supported": false + }, + "attachment": true, + "open_weights": false, + "knowledge": "2023-09", + "release_date": "2024-05-13", + "last_updated": "2024-08-06", + "type": "chat" + }, + { + "id": "openai/gpt-5.6-terra-pro", + "name": "GPT-5.6 Terra", + "display_name": "GPT-5.6 Terra", + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1050000, + "output": 128000 + }, + "temperature": false, + "tool_call": true, "reasoning": { "supported": true, "default": true }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "none", + "low", + "medium", + "high", + "xhigh", + "max" ] } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-08-24", - "last_updated": "2026-08-24", + "attachment": true, + "open_weights": false, + "knowledge": "2026-02-16", + "release_date": "2026-07-09", + "last_updated": "2026-07-09", "type": "chat" }, { - "id": "baichuan/baichuan-m2-32b", - "name": "Baichuan M2 32B", - "display_name": "Baichuan M2 32B", + "id": "openai/gpt-5.6-luna-pro", + "name": "GPT-5.6 Luna", + "display_name": "GPT-5.6 Luna", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 131072 + "context": 1050000, + "output": 128000 }, - "temperature": true, - "tool_call": false, + "temperature": false, + "tool_call": true, "reasoning": { "supported": true, "default": true }, - "attachment": false, - "open_weights": true, - "release_date": "2025-08-13", - "last_updated": "2025-08-13", + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "none", + "low", + "medium", + "high", + "xhigh", + "max" + ] + } + }, + "attachment": true, + "open_weights": false, + "knowledge": "2026-02-16", + "release_date": "2026-07-09", + "last_updated": "2026-07-09", "type": "chat" }, { - "id": "baai/bge-reranker-v2-m3", - "name": "BGE Reranker V2 M3", - "display_name": "BGE Reranker V2 M3", + "id": "openai/gpt-5.6-sol", + "name": "GPT-5.6 Sol", + "display_name": "GPT-5.6 Sol", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 8000, - "output": 8192 + "context": 1050000, + "output": 128000 }, "temperature": false, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, - "open_weights": true, - "release_date": "2024-03-15", - "last_updated": "2024-03-15", - "type": "rerank" + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", + "effort_options": [ + "none", + "low", + "medium", + "high", + "xhigh", + "max" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" + } + }, + "attachment": true, + "open_weights": false, + "knowledge": "2026-02-16", + "release_date": "2026-07-09", + "last_updated": "2026-07-09", + "type": "chat" }, { - "id": "baai/bge-m3", - "name": "BGE M3", - "display_name": "BGE M3", + "id": "openai/gpt-5.1", + "name": "GPT-5.1", + "display_name": "GPT-5.1", "modalities": { "input": [ - "text" + "image", + "text", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 8192, - "output": 8192 + "context": 400000, + "output": 128000 }, "temperature": false, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": false }, - "attachment": false, - "open_weights": true, - "release_date": "2024-01-30", - "last_updated": "2024-01-30", - "type": "embedding" + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": false, + "mode": "effort", + "effort": "none", + "effort_options": [ + "none", + "low", + "medium", + "high" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" + } + }, + "attachment": true, + "open_weights": false, + "knowledge": "2024-09-30", + "release_date": "2025-11-13", + "last_updated": "2025-11-13", + "type": "chat" }, { - "id": "nvidia/nemotron-3-nano-30b-a3b", - "name": "Nemotron 3 Nano 30B A3B", - "display_name": "Nemotron 3 Nano 30B A3B", + "id": "openai/gpt-5.5", + "name": "GPT-5.5", + "display_name": "GPT-5.5", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 32768 + "context": 1050000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -263059,93 +263687,128 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] + "default_enabled": true, + "mode": "effort", + "effort": "medium", + "effort_options": [ + "low", + "medium", + "high", + "xhigh" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-06-25", - "release_date": "2025-12-15", - "last_updated": "2025-12-15", + "attachment": true, + "open_weights": false, + "knowledge": "2025-12-01", + "release_date": "2026-04-23", + "last_updated": "2026-04-23", "type": "chat" }, { - "id": "microsoft/wizardlm-2-8x22b", - "name": "WizardLM 2 8x22B", - "display_name": "WizardLM 2 8x22B", + "id": "openai/gpt-5-image-mini", + "name": "GPT-5 Image Mini", + "display_name": "GPT-5 Image Mini", "modalities": { "input": [ + "pdf", + "image", "text" ], "output": [ + "image", "text" ] }, "limit": { - "context": 65535, - "output": 8000 + "context": 400000, + "output": 128000 }, "temperature": true, "tool_call": false, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, - "open_weights": true, - "release_date": "2024-04-15", - "last_updated": "2024-04-15", - "type": "chat" + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "high" + ] + } + }, + "attachment": true, + "open_weights": false, + "release_date": "2025-10-16", + "last_updated": "2025-10-16" }, { - "id": "inclusionai/ling-3.0-flash-fin", - "name": "Ling 3.0 Flash Fin", - "display_name": "Ling 3.0 Flash Fin", + "id": "openai/gpt-5.1-codex-mini", + "name": "GPT-5.1 Codex mini", + "display_name": "GPT-5.1 Codex mini", "modalities": { "input": [ - "text" + "image", + "text", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 32768 + "context": 400000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } - }, - "attachment": false, - "open_weights": true, - "release_date": "2026-08-27", - "last_updated": "2026-09-03", + "default_enabled": false, + "mode": "effort", + "effort": "none", + "effort_options": [ + "none", + "low", + "medium", + "high" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" + } + }, + "attachment": true, + "open_weights": false, + "knowledge": "2024-09-30", + "release_date": "2025-11-13", + "last_updated": "2025-11-13", "type": "chat" }, { - "id": "inclusionai/ling-3.0-flash", - "name": "Ling 3.0 Flash", - "display_name": "Ling 3.0 Flash", + "id": "openai/gpt-6.1-terra-pro", + "name": "OpenAI: GPT-6.1 Terra Pro", + "display_name": "OpenAI: GPT-6.1 Terra Pro", "modalities": { "input": [ + "pdf", + "image", "text" ], "output": [ @@ -263153,10 +263816,10 @@ ] }, "limit": { - "context": 262144, - "output": 32768 + "context": 1050000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -263164,38 +263827,40 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" ] } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-07-23", - "last_updated": "2026-07-23", + "attachment": true, + "open_weights": false, + "release_date": "2026-10-26", + "last_updated": "2026-10-26", "type": "chat" }, { - "id": "inclusionai/ling-3.1-flash", - "name": "Ling 3.1 Flash", - "display_name": "Ling 3.1 Flash", + "id": "openai/gpt-5.6-sol-pro", + "name": "GPT-5.6 Sol", + "display_name": "GPT-5.6 Sol", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 262144 + "context": 1050000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -263203,146 +263868,140 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "none", + "low", + "medium", + "high", + "xhigh", + "max" ] } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-09-29", - "last_updated": "2026-09-29", + "attachment": true, + "open_weights": false, + "knowledge": "2026-02-16", + "release_date": "2026-07-09", + "last_updated": "2026-07-09", "type": "chat" }, { - "id": "inclusionai/ling-3.0-flash-vl", - "name": "Ling 3.0 Flash VL", - "display_name": "Ling 3.0 Flash VL", + "id": "openai/gpt-5-image", + "name": "GPT-5 Image", + "display_name": "GPT-5 Image", "modalities": { "input": [ - "text", "image", - "video" + "text", + "pdf" ], "output": [ + "image", "text" ] }, "limit": { - "context": 262144, - "output": 32768 + "context": 400000, + "output": 128000 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { "supported": true, "default": true }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "high" ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-09-10", - "last_updated": "2026-09-10", - "type": "chat" + "open_weights": false, + "knowledge": "2024-10-01", + "release_date": "2025-10-14", + "last_updated": "2025-10-14" }, { - "id": "inclusionai/ling-3.0-flash-sante", - "name": "Ling 3.0 Flash Sante", - "display_name": "Ling 3.0 Flash Sante", + "id": "openai/gpt-4.1-mini", + "name": "GPT-4.1 mini", + "display_name": "GPT-4.1 mini", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 262144, + "context": 1047576, "output": 32768 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } + "supported": false }, - "attachment": false, + "attachment": true, "open_weights": false, - "release_date": "2026-09-04", - "last_updated": "2026-09-04", + "knowledge": "2024-04", + "release_date": "2025-04-14", + "last_updated": "2025-04-14", "type": "chat" }, { - "id": "deepseek/deepseek_v3", - "name": "DeepSeek-V3", - "display_name": "DeepSeek-V3", + "id": "openai/gpt-4.1", + "name": "GPT-4.1", + "display_name": "GPT-4.1", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 64000, - "output": 16000 + "context": 1047576, + "output": 32768 }, "temperature": true, "tool_call": true, "reasoning": { "supported": false }, - "attachment": false, - "open_weights": true, - "release_date": "2024-12-26", - "last_updated": "2024-12-26", + "attachment": true, + "open_weights": false, + "knowledge": "2024-04", + "release_date": "2025-04-14", + "last_updated": "2025-04-14", "type": "chat" }, { - "id": "deepseek/deepseek-v4.1-flash-1017", - "name": "DeepSeek V4.1 Flash 1017", - "display_name": "DeepSeek V4.1 Flash 1017", + "id": "openai/gpt-6-luna", + "name": "GPT-6 Luna", + "display_name": "GPT-6 Luna", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 393216 + "context": 1050000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -263350,44 +264009,42 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], "effort_options": [ + "none", "low", + "medium", "high", + "xhigh", "max" ] } }, "attachment": true, - "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-10-17", - "last_updated": "2026-10-17", + "open_weights": false, + "knowledge": "2026-05-18", + "release_date": "2026-09-22", + "last_updated": "2026-09-22", "type": "chat" }, { - "id": "deepseek/deepseek-v4-pro", - "name": "DeepSeek V4 Pro", - "display_name": "DeepSeek V4 Pro", + "id": "openai/o3", + "name": "o3", + "display_name": "o3", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 393216 + "context": 200000, + "output": 100000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -263398,62 +264055,57 @@ "supported": true, "default_enabled": true, "mode": "effort", - "effort": "high", + "effort": "medium", "effort_options": [ "low", - "high", - "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "medium", + "high" ], - "notes": [ - "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." - ] + "visibility": "hidden" } }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-04-24", - "last_updated": "2026-04-24", + "attachment": true, + "open_weights": false, + "knowledge": "2024-05", + "release_date": "2025-04-16", + "last_updated": "2025-04-16", "type": "chat" }, { - "id": "deepseek/deepseek-ocr-2", - "name": "DeepSeek OCR 2", - "display_name": "DeepSeek OCR 2", + "id": "openai/gpt-audio", + "name": "GPT-Audio", + "display_name": "GPT-Audio", "modalities": { "input": [ "text", - "image" + "audio", + "pdf" ], "output": [ - "text" + "text", + "audio" ] }, "limit": { - "context": 8192, - "output": 8192 + "context": 128000, + "output": 16384 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { "supported": false }, "attachment": true, - "open_weights": true, - "release_date": "2026-01-27", - "last_updated": "2026-01-27", + "open_weights": false, + "knowledge": "2023-10-01", + "release_date": "2025-08-28", + "last_updated": "2025-08-28", "type": "chat" }, { - "id": "deepseek/deepseek-r1-0528", - "name": "DeepSeek R1 0528", - "display_name": "DeepSeek R1 0528", + "id": "openai/gpt-oss-20b", + "name": "GPT OSS 20B", + "display_name": "GPT OSS 20B", "modalities": { "input": [ "text" @@ -263463,8 +264115,8 @@ ] }, "limit": { - "context": 163840, - "output": 32768 + "context": 131072, + "output": 117964 }, "temperature": true, "tool_call": true, @@ -263475,24 +264127,24 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "low", + "medium", + "high" ] } }, "attachment": false, "open_weights": true, - "release_date": "2025-05-28", - "last_updated": "2025-05-28", + "knowledge": "2024-06", + "release_date": "2025-08-05", + "last_updated": "2025-08-05", "type": "chat" }, { - "id": "deepseek/deepseek-v4.1-flash", - "name": "DeepSeek V4.1 Flash", - "display_name": "DeepSeek V4.1 Flash", + "id": "nex-agi/nex-n2.5-pro", + "name": "Nex AGI: Nex-N2.5-Pro", + "display_name": "Nex AGI: Nex-N2.5-Pro", "modalities": { "input": [ "text", @@ -263503,8 +264155,8 @@ ] }, "limit": { - "context": 1048576, - "output": 393216 + "context": 262144, + "output": 235929 }, "temperature": true, "tool_call": true, @@ -263514,76 +264166,61 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], "effort_options": [ - "low", - "high", - "max" + "none", + "medium", + "high" ] } }, "attachment": true, - "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-09-10", - "last_updated": "2026-09-10", + "open_weights": false, + "release_date": "2026-09-08", + "last_updated": "2026-09-08", "type": "chat" }, { - "id": "deepseek/deepseek-v4-flash-0731", - "name": "DeepSeek V4 Flash 0731", - "display_name": "DeepSeek V4 Flash 0731", + "id": "nex-agi/nex-n2.5-mini", + "name": "Nex AGI: Nex-N2.5-Mini", + "display_name": "Nex AGI: Nex-N2.5-Mini", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 393216 + "context": 262144, + "output": 235929 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { "supported": true, "default": true }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], "effort_options": [ - "low", - "high", - "max" + "none", + "medium", + "high" ] } }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-07-31", - "last_updated": "2026-07-31", + "attachment": true, + "open_weights": false, + "release_date": "2026-09-08", + "last_updated": "2026-09-08", "type": "chat" }, { - "id": "deepseek/deepseek-v4-pro-0813", - "name": "DeepSeek V4 Pro 0813", - "display_name": "DeepSeek V4 Pro 0813", + "id": "meituan/longcat-2.0", + "name": "Meituan: LongCat 2.0", + "display_name": "Meituan: LongCat 2.0", "modalities": { "input": [ "text" @@ -263593,8 +264230,8 @@ ] }, "limit": { - "context": 1048576, - "output": 393216 + "context": 1048756, + "output": 262144 }, "temperature": true, "tool_call": true, @@ -263605,40 +264242,38 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], "effort_options": [ - "low", - "high", - "max" + "none", + "high" ] } }, "attachment": false, "open_weights": true, - "release_date": "2026-08-13", - "last_updated": "2026-08-22", + "release_date": "2026-06-30", + "last_updated": "2026-06-30", "type": "chat" }, { - "id": "deepseek/deepseek-r1-turbo", - "name": "DeepSeek R1 (Turbo)", - "display_name": "DeepSeek R1 (Turbo)", + "id": "openrouter/auto", + "name": "Auto Router", + "display_name": "Auto Router", "modalities": { "input": [ - "text" + "audio", + "image", + "pdf", + "text", + "video" ], "output": [ + "image", "text" ] }, "limit": { - "context": 64000, - "output": 16000 + "context": 2000000, + "output": 32768 }, "temperature": true, "tool_call": true, @@ -263646,129 +264281,118 @@ "supported": true, "default": true }, - "attachment": false, - "open_weights": true, - "knowledge": "2024-07", - "release_date": "2025-01-20", - "last_updated": "2025-05-29", + "extra_capabilities": { + "reasoning": { + "supported": true + } + }, + "attachment": true, + "open_weights": false, + "release_date": "2026-03-15", + "last_updated": "2026-03-15", "type": "chat" }, { - "id": "deepseek/deepseek-r1-0528-qwen3-8b", - "name": "DeepSeek R1 0528 Qwen3 8B", - "display_name": "DeepSeek R1 0528 Qwen3 8B", + "id": "openrouter/free", + "name": "OpenRouter Free Models Router", + "display_name": "OpenRouter Free Models Router", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 32000 + "context": 200000, + "output": 32768 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { "supported": true, "default": true }, - "attachment": false, - "open_weights": true, - "release_date": "2025-05-29", - "last_updated": "2025-05-29", + "attachment": true, + "open_weights": false, + "release_date": "2026-02-01", + "last_updated": "2026-02-01", "type": "chat" }, { - "id": "google/gemma-4-31b-it", - "name": "Gemma 4 31B IT", - "display_name": "Gemma 4 31B IT", + "id": "openrouter/pareto-code", + "name": "Pareto Code Router", + "display_name": "Pareto Code Router", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 131072 + "context": 200000, + "output": 65536 }, - "temperature": true, - "tool_call": true, + "temperature": false, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } + "supported": false }, - "attachment": true, - "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-04-02", - "last_updated": "2026-04-02", + "attachment": false, + "open_weights": false, + "release_date": "2026-04-21", + "last_updated": "2026-05-01", "type": "chat" }, { - "id": "google/gemma-3-12b-it", - "name": "Gemma 3 12B IT", - "display_name": "Gemma 3 12B IT", + "id": "openrouter/bodybuilder", + "name": "Body Builder (beta)", + "display_name": "Body Builder (beta)", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 8192 + "context": 128000, + "output": 32768 }, - "temperature": true, "tool_call": false, "reasoning": { "supported": false }, - "attachment": true, - "open_weights": true, - "knowledge": "2024-08", - "release_date": "2025-03-12", - "last_updated": "2025-03-12", + "attachment": false, + "open_weights": false, + "release_date": "2026-03-15", + "last_updated": "2026-03-15", "type": "chat" }, { - "id": "google/gemma-4-26b-a4b-it", - "name": "Gemma 4 26B A4B IT", - "display_name": "Gemma 4 26B A4B IT", + "id": "sakana/fugu-max", + "name": "Fugu Max", + "display_name": "Fugu Max", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 131072 + "context": 1000000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -263776,67 +264400,77 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "high", + "xhigh", + "max" ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-04-02", - "last_updated": "2026-04-02", + "open_weights": false, + "release_date": "2026-09-11", + "last_updated": "2026-09-11", "type": "chat" }, { - "id": "google/gemma-3-27b-it", - "name": "Gemma 3 27B IT", - "display_name": "Gemma 3 27B IT", + "id": "sakana/fugu-ultra-v2", + "name": "Fugu Ultra v2", + "display_name": "Fugu Ultra v2", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 98304, - "output": 16384 + "context": 1000000, + "output": 128000 }, - "temperature": true, - "tool_call": false, + "temperature": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "high", + "xhigh", + "max" + ] + } }, "attachment": true, - "open_weights": true, - "knowledge": "2024-08", - "release_date": "2025-03-12", - "last_updated": "2025-03-12", + "open_weights": false, + "knowledge": "2026-08-28", + "release_date": "2026-09-11", + "last_updated": "2026-09-11", "type": "chat" }, { - "id": "mindai/macaron-v1-tall", - "name": "Macaron V1 Tall", - "display_name": "Macaron V1 Tall", + "id": "sakana/fugu-ultra", + "name": "Fugu Ultra", + "display_name": "Fugu Ultra", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 32768 + "context": 1000000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -263844,42 +264478,38 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], "effort_options": [ - "none", - "high" + "high", + "xhigh", + "max" ] } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-07-21", - "last_updated": "2026-07-21", + "attachment": true, + "open_weights": false, + "release_date": "2026-06-15", + "last_updated": "2026-06-15", "type": "chat" }, { - "id": "mindai/macaron-v1-venti", - "name": "Macaron V1 Venti", - "display_name": "Macaron V1 Venti", + "id": "sakana/sakana-namazu", + "name": "Sakana Namazu", + "display_name": "Sakana Namazu", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 262144, + "output": 65536 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -263887,30 +264517,22 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], "effort_options": [ "none", - "high", - "max" + "high" ] } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-07-21", - "last_updated": "2026-07-21", + "attachment": true, + "open_weights": false, + "release_date": "2026-08-03", + "last_updated": "2026-08-03", "type": "chat" }, { - "id": "nousresearch/hermes-2-pro-llama-3-8b", - "name": "Hermes 2 Pro Llama 3 8B", - "display_name": "Hermes 2 Pro Llama 3 8B", + "id": "nvidia/nemotron-3.5-lightning", + "name": "Nemotron 3.5 Lightning 30B A3B", + "display_name": "Nemotron 3.5 Lightning 30B A3B", "modalities": { "input": [ "text" @@ -263920,35 +264542,48 @@ ] }, "limit": { - "context": 8192, - "output": 8192 + "context": 262144, + "output": 235929 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "effort_options": [ + "none", + "high" + ] + } }, "attachment": false, "open_weights": true, - "release_date": "2024-04-30", - "last_updated": "2024-04-30", + "knowledge": "2025-09", + "release_date": "2026-08-11", + "last_updated": "2026-08-11", "type": "chat" }, { - "id": "openai/gpt-oss-120b", - "name": "GPT OSS 120B", - "display_name": "GPT OSS 120B", + "id": "nvidia/nemotron-3.5-super-vl", + "name": "NVIDIA: Nemotron 3.5 Super VL", + "display_name": "NVIDIA: Nemotron 3.5 Super VL", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 32768 + "context": 262144, + "output": 235929 }, "temperature": true, "tool_call": true, @@ -263958,31 +264593,22 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], "effort_options": [ - "low", - "medium", + "none", "high" ] } }, - "attachment": false, + "attachment": true, "open_weights": true, - "knowledge": "2024-06", - "release_date": "2025-08-05", - "last_updated": "2025-08-05", + "release_date": "2026-10-15", + "last_updated": "2026-10-15", "type": "chat" }, { - "id": "tencent/hy4-preview", - "name": "Hy4 preview", - "display_name": "Hy4 preview", + "id": "nvidia/nemotron-3-ultra-550b-a55b", + "name": "Nemotron 3 Ultra 550B A55B", + "display_name": "Nemotron 3 Ultra 550B A55B", "modalities": { "input": [ "text" @@ -263992,8 +264618,8 @@ ] }, "limit": { - "context": 1000000, - "output": 64000 + "context": 202800, + "output": 182520 }, "temperature": true, "tool_call": true, @@ -264004,28 +264630,24 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], "effort_options": [ "none", + "medium", "high" ] } }, "attachment": false, "open_weights": true, - "release_date": "2026-08-28", - "last_updated": "2026-08-28", + "knowledge": "2025-09", + "release_date": "2026-06-04", + "last_updated": "2026-06-04", "type": "chat" }, { - "id": "tencent/hy3", - "name": "Hy3", - "display_name": "Hy3", + "id": "nvidia/nemotron-3.5-lightning:free", + "name": "NVIDIA: Nemotron 3.5 Lightning (free)", + "display_name": "NVIDIA: Nemotron 3.5 Lightning (free)", "modalities": { "input": [ "text" @@ -264035,8 +264657,8 @@ ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 1000000, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -264046,13 +264668,6 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], "effort_options": [ "none", "high" @@ -264061,14 +264676,15 @@ }, "attachment": false, "open_weights": true, - "release_date": "2026-07-06", - "last_updated": "2026-07-06", + "knowledge": "2025-09", + "release_date": "2026-08-11", + "last_updated": "2026-08-11", "type": "chat" }, { - "id": "baidu/ernie-4.5-21B-a3b", - "name": "ERNIE 4.5 21B A3B", - "display_name": "ERNIE 4.5 21B A3B", + "id": "nvidia/nemotron-3-nano-30b-a3b", + "name": "Nemotron 3 Nano 30B A3B", + "display_name": "Nemotron 3 Nano 30B A3B", "modalities": { "input": [ "text" @@ -264078,27 +264694,38 @@ ] }, "limit": { - "context": 120000, - "output": 8000 + "context": 262144, + "output": 235929 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "none", + "high" + ] + } }, "attachment": false, "open_weights": true, - "release_date": "2025-06-30", - "last_updated": "2025-06-30", + "knowledge": "2025-06-25", + "release_date": "2025-12-15", + "last_updated": "2025-12-15", "type": "chat" }, { - "id": "baidu/cobuddy", - "name": "CoBuddy", - "display_name": "CoBuddy", + "id": "nvidia/nemotron-3.5-content-safety", + "name": "Nemotron 3.5 Content Safety", + "display_name": "Nemotron 3.5 Content Safety", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" @@ -264106,10 +264733,10 @@ }, "limit": { "context": 131072, - "output": 65536 + "output": 117964 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { "supported": true, "default": true @@ -264117,56 +264744,62 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "none", + "high" ] } }, - "attachment": false, - "open_weights": false, - "release_date": "2026-05-06", - "last_updated": "2026-05-06", + "attachment": true, + "open_weights": true, + "release_date": "2026-06-02", + "last_updated": "2026-06-02", "type": "chat" }, { - "id": "moonshotai/kimi-k2-instruct", - "name": "Kimi K2 Instruct", - "display_name": "Kimi K2 Instruct", + "id": "nvidia/nemotron-3.5-content-safety:free", + "name": "NVIDIA: Nemotron 3.5 Content Safety (free)", + "display_name": "NVIDIA: Nemotron 3.5 Content Safety (free)", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 98304 + "context": 128000, + "output": 8192 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "none", + "high" + ] + } + }, + "attachment": true, "open_weights": true, - "release_date": "2025-07-11", - "last_updated": "2025-07-11", + "release_date": "2026-06-02", + "last_updated": "2026-06-02", "type": "chat" }, { - "id": "moonshotai/kimi-k3", - "name": "Kimi K3", - "display_name": "Kimi K3", + "id": "nvidia/nemotron-3-super-120b-a12b", + "name": "Nemotron 3 Super 120B A12B", + "display_name": "Nemotron 3 Super 120B A12B", "modalities": { "input": [ - "text", - "image", - "video" + "text" ], "output": [ "text" @@ -264174,9 +264807,9 @@ }, "limit": { "context": 1048576, - "output": 1048576 + "output": 262144 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -264184,44 +264817,35 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], "effort_options": [ "none", "low", - "high", - "max" + "medium" ] } }, - "attachment": true, + "attachment": false, "open_weights": true, - "release_date": "2026-07-16", - "last_updated": "2026-07-16", + "knowledge": "2025-06", + "release_date": "2026-03-11", + "last_updated": "2026-03-11", "type": "chat" }, { - "id": "moonshotai/kimi-k2.6", - "name": "Kimi K2.6", - "display_name": "Kimi K2.6", + "id": "nvidia/nemotron-3-ultra-550b-a55b:free", + "name": "NVIDIA: Nemotron 3 Ultra (free)", + "display_name": "NVIDIA: Nemotron 3 Ultra (free)", "modalities": { "input": [ - "text", - "image", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 1000000, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -264231,41 +264855,40 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "none", + "medium", + "high" ] } }, - "attachment": true, + "attachment": false, "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-04-20", - "last_updated": "2026-04-20", + "knowledge": "2025-09", + "release_date": "2026-06-04", + "last_updated": "2026-06-04", "type": "chat" }, { - "id": "moonshotai/kimi-k2.5", - "name": "Kimi K2.5", - "display_name": "Kimi K2.5", + "id": "nvidia/nemotron-3-nano-omni-30b-a3b-reasoning:free", + "name": "NVIDIA: Nemotron 3 Nano Omni (free)", + "display_name": "NVIDIA: Nemotron 3 Nano Omni (free)", "modalities": { "input": [ "text", "image", - "video" + "video", + "audio" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 256000, + "output": 65536 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -264273,26 +264896,22 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "none", + "high" ] } }, "attachment": true, "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-01-27", - "last_updated": "2026-01-27", + "release_date": "2026-04-28", + "last_updated": "2026-04-28", "type": "chat" }, { - "id": "moonshotai/kimi-k3.1-pro-highspeed", - "name": "Kimi K3.1 Pro Highspeed", - "display_name": "Kimi K3.1 Pro Highspeed", + "id": "nvidia/nemotron-3.5-super-vl:free", + "name": "NVIDIA: Nemotron 3.5 Super VL (free)", + "display_name": "NVIDIA: Nemotron 3.5 Super VL (free)", "modalities": { "input": [ "text", @@ -264304,10 +264923,10 @@ ] }, "limit": { - "context": 1048576, - "output": 1048576 + "context": 1000000, + "output": 131072 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -264315,35 +264934,27 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], "effort_options": [ - "low", - "high", - "max" + "none", + "high" ] } }, "attachment": true, - "open_weights": false, - "release_date": "2026-10-23", - "last_updated": "2026-10-23", + "open_weights": true, + "release_date": "2026-10-15", + "last_updated": "2026-10-15", "type": "chat" }, { - "id": "moonshotai/kimi-k3.1-pro", - "name": "Kimi K3.1 Pro", - "display_name": "Kimi K3.1 Pro", + "id": "thinkingmachines/inkling-small:free", + "name": "Thinking Machines: Inkling Small (free)", + "display_name": "Thinking Machines: Inkling Small (free)", "modalities": { "input": [ "text", "image", - "video" + "audio" ], "output": [ "text" @@ -264351,9 +264962,9 @@ }, "limit": { "context": 1048576, - "output": 1048576 + "output": 262144 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -264361,68 +264972,81 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], "effort_options": [ + "none", + "minimal", "low", + "medium", "high", "max" ] } }, "attachment": true, - "open_weights": false, - "release_date": "2026-10-23", - "last_updated": "2026-10-23", + "open_weights": true, + "release_date": "2026-07-30", + "last_updated": "2026-07-30", "type": "chat" }, { - "id": "moonshotai/kimi-k2-0905", - "name": "Kimi K2 Instruct 0905", - "display_name": "Kimi K2 Instruct 0905", + "id": "thinkingmachines/inkling:free", + "name": "Thinking Machines: Inkling (free)", + "display_name": "Thinking Machines: Inkling (free)", "modalities": { "input": [ - "text" + "text", + "image", + "audio" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 98304 + "context": 1048576, + "output": 262144 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "none", + "minimal", + "low", + "medium", + "high", + "max" + ] + } + }, + "attachment": true, "open_weights": true, - "release_date": "2025-09-05", - "last_updated": "2025-09-05", + "release_date": "2026-07-15", + "last_updated": "2026-07-15", "type": "chat" }, { - "id": "moonshotai/kimi-k2-thinking", - "name": "Kimi K2 Thinking", - "display_name": "Kimi K2 Thinking", + "id": "thinkingmachines/inkling", + "name": "Inkling", + "display_name": "Inkling", "modalities": { "input": [ - "text" + "text", + "image", + "audio" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 98304 + "context": 524288, + "output": 471859 }, "temperature": true, "tool_call": true, @@ -264432,41 +265056,41 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "none", + "minimal", + "low", + "medium", + "high", + "max" ] } }, - "attachment": false, + "attachment": true, "open_weights": true, - "knowledge": "2024-08", - "release_date": "2025-11-06", - "last_updated": "2025-11-06", + "release_date": "2026-07-15", + "last_updated": "2026-07-15", "type": "chat" }, { - "id": "moonshotai/kimi-k2.7-code", - "name": "Kimi K2.7 Code", - "display_name": "Kimi K2.7 Code", + "id": "thinkingmachines/inkling-small", + "name": "Inkling Small", + "display_name": "Inkling Small", "modalities": { "input": [ "text", "image", - "video" + "audio" ], "output": [ "text" ] }, "limit": { - "context": 262144, + "context": 524288, "output": 262144 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -264474,37 +265098,39 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "none", + "minimal", + "low", + "medium", + "high", + "max" ] } }, "attachment": true, "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-06-12", - "last_updated": "2026-06-12", + "release_date": "2026-07-30", + "last_updated": "2026-07-30", "type": "chat" }, { - "id": "qwen/qwen3.7-max", - "name": "Qwen3.7 Max", - "display_name": "Qwen3.7 Max", + "id": "z-ai/glm-5.3-flashx", + "name": "Z.ai: GLM 5.3 FlashX", + "display_name": "Z.ai: GLM 5.3 FlashX", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 65536 + "context": 1048576, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -264514,25 +265140,23 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "low", + "high", + "max" ] } }, - "attachment": false, + "attachment": true, "open_weights": false, - "release_date": "2026-05-21", - "last_updated": "2026-05-21", + "release_date": "2026-09-18", + "last_updated": "2026-09-18", "type": "chat" }, { - "id": "qwen/qwen3.8-27b", - "name": "Qwen3.8 27B", - "display_name": "Qwen3.8 27B", + "id": "z-ai/glm-5.3-flashx-legal", + "name": "Z.ai: GLM 5.3 FlashX Legal", + "display_name": "Z.ai: GLM 5.3 FlashX Legal", "modalities": { "input": [ "text", @@ -264544,7 +265168,7 @@ ] }, "limit": { - "context": 1000000, + "context": 1048576, "output": 131072 }, "temperature": true, @@ -264555,30 +265179,23 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], "effort_options": [ "low", - "medium", - "xhigh" + "high", + "max" ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-08-14", - "last_updated": "2026-08-14", + "open_weights": false, + "release_date": "2026-11-02", + "last_updated": "2026-11-02", "type": "chat" }, { - "id": "qwen/qwen3-vl-30b-a3b-instruct", - "name": "Qwen3 VL 30B A3B Instruct", - "display_name": "Qwen3 VL 30B A3B Instruct", + "id": "z-ai/glm-4.6v", + "name": "GLM-4.6V", + "display_name": "GLM-4.6V", "modalities": { "input": [ "text", @@ -264596,28 +265213,40 @@ "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "none", + "high" + ] + } }, "attachment": true, "open_weights": true, - "release_date": "2025-10-04", - "last_updated": "2025-10-04", + "knowledge": "2025-04", + "release_date": "2025-12-08", + "last_updated": "2025-12-08", "type": "chat" }, { - "id": "qwen/qwen3.8-2.4t-a95b", - "name": "Qwen3.8 2.4T A95B", - "display_name": "Qwen3.8 2.4T A95B", + "id": "z-ai/glm-5.3-flash", + "name": "GLM-5.3-Flash", + "display_name": "GLM-5.3-Flash", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 1000000, + "context": 1048576, "output": 131072 }, "temperature": true, @@ -264629,42 +265258,34 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], "effort_options": [ "low", - "medium", - "xhigh" + "high", + "max" ] } }, - "attachment": false, + "attachment": true, "open_weights": true, - "release_date": "2026-08-12", - "last_updated": "2026-08-12", + "release_date": "2026-08-26", + "last_updated": "2026-08-26", "type": "chat" }, { - "id": "qwen/qwen3.5-35b-a3b", - "name": "Qwen3.5 35B-A3B", - "display_name": "Qwen3.5 35B-A3B", + "id": "z-ai/glm-4.7", + "name": "GLM-4.7", + "display_name": "GLM-4.7", "modalities": { "input": [ - "text", - "image", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 65536 + "context": 202752, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -264680,31 +265301,34 @@ "visibility": "summary", "continuation": [ "thinking_blocks" + ], + "effort_options": [ + "none", + "high" ] } }, - "attachment": true, + "attachment": false, "open_weights": true, - "release_date": "2026-02-23", - "last_updated": "2026-02-23", + "knowledge": "2025-04", + "release_date": "2025-12-22", + "last_updated": "2025-12-22", "type": "chat" }, { - "id": "qwen/qwen3.8-max", - "name": "Qwen3.8 Max", - "display_name": "Qwen3.8 Max", + "id": "z-ai/glm-4.6", + "name": "GLM-4.6", + "display_name": "GLM-4.6", "modalities": { "input": [ - "text", - "image", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, + "context": 202752, "output": 131072 }, "temperature": true, @@ -264716,42 +265340,34 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], "effort_options": [ - "low", - "medium", - "xhigh" + "none", + "high" ] } }, - "attachment": true, - "open_weights": false, - "release_date": "2026-08-03", - "last_updated": "2026-08-03", + "attachment": false, + "open_weights": true, + "knowledge": "2025-04", + "release_date": "2025-09-30", + "last_updated": "2025-09-30", "type": "chat" }, { - "id": "qwen/qwen3.5-122b-a10b", - "name": "Qwen3.5 122B-A10B", - "display_name": "Qwen3.5 122B-A10B", + "id": "z-ai/glm-4.5-air", + "name": "GLM-4.5-Air", + "display_name": "GLM-4.5-Air", "modalities": { "input": [ - "text", - "image", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 65536 + "context": 131072, + "output": 98304 }, "temperature": true, "tool_call": true, @@ -264761,38 +265377,34 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "none", + "high" ] } }, - "attachment": true, + "attachment": false, "open_weights": true, - "release_date": "2026-02-23", - "last_updated": "2026-02-23", + "knowledge": "2025-04", + "release_date": "2025-07-28", + "last_updated": "2025-07-28", "type": "chat" }, { - "id": "qwen/qwen3.6-35b-a3b", - "name": "Qwen3.6 35B-A3B", - "display_name": "Qwen3.6 35B-A3B", + "id": "z-ai/glm-5.3", + "name": "GLM-5.3", + "display_name": "GLM-5.3", "modalities": { "input": [ - "text", - "image", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 65536 + "context": 1048576, + "output": 943718 }, "temperature": true, "tool_call": true, @@ -264803,37 +265415,35 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "low", + "high", + "max" ] } }, - "attachment": true, + "attachment": false, "open_weights": true, - "release_date": "2026-04-17", - "last_updated": "2026-04-17", + "release_date": "2026-08-14", + "last_updated": "2026-08-14", "type": "chat" }, { - "id": "qwen/qwen3.8-flash", - "name": "Qwen3.8 Flash", - "display_name": "Qwen3.8 Flash", + "id": "z-ai/glm-4.5v", + "name": "GLM-4.5V", + "display_name": "GLM-4.5V", "modalities": { "input": [ "text", - "image", - "video" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 65536, + "output": 16384 }, "temperature": true, "tool_call": true, @@ -264843,30 +265453,23 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], "effort_options": [ - "low", - "medium", - "xhigh" + "none", + "high" ] } }, "attachment": true, - "open_weights": false, - "release_date": "2026-08-26", - "last_updated": "2026-08-26", + "open_weights": true, + "knowledge": "2025-04", + "release_date": "2025-08-11", + "last_updated": "2025-08-11", "type": "chat" }, { - "id": "qwen/qwen3-embedding-8b", - "name": "Qwen3 Embedding 8B", - "display_name": "Qwen3 Embedding 8B", + "id": "z-ai/glm-5", + "name": "GLM-5", + "display_name": "GLM-5", "modalities": { "input": [ "text" @@ -264876,37 +265479,51 @@ ] }, "limit": { - "context": 32768, - "output": 8192 + "context": 202752, + "output": 131072 }, - "temperature": false, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "effort_options": [ + "none", + "high" + ] + } }, "attachment": false, "open_weights": true, - "release_date": "2025-06-05", - "last_updated": "2025-06-05", - "type": "embedding" + "release_date": "2026-02-12", + "last_updated": "2026-02-12", + "type": "chat" }, { - "id": "qwen/qwen3.6-27b", - "name": "Qwen3.6 27B", - "display_name": "Qwen3.6 27B", + "id": "z-ai/glm-4.7-flash", + "name": "GLM-4.7-Flash", + "display_name": "GLM-4.7-Flash", "modalities": { "input": [ - "text", - "image", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 65536 + "context": 131072, + "output": 117964 }, "temperature": true, "tool_call": true, @@ -264917,37 +265534,34 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "none", + "high" ] } }, - "attachment": true, + "attachment": false, "open_weights": true, - "release_date": "2026-04-22", - "last_updated": "2026-04-22", + "knowledge": "2025-04", + "release_date": "2026-01-19", + "last_updated": "2026-01-19", "type": "chat" }, { - "id": "qwen/qwen3.5-397b-a17b", - "name": "Qwen3.5 397B-A17B", - "display_name": "Qwen3.5 397B-A17B", + "id": "z-ai/glm-5.1", + "name": "GLM-5.1", + "display_name": "GLM-5.1", "modalities": { "input": [ - "text", - "image", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 65536 + "context": 202752, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -264958,38 +265572,35 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "none", + "high" ] } }, - "attachment": true, + "attachment": false, "open_weights": true, - "release_date": "2026-02-15", - "last_updated": "2026-02-15", + "release_date": "2026-04-07", + "last_updated": "2026-04-07", "type": "chat" }, { - "id": "qwen/qwen3-omni-30b-a3b-thinking", - "name": "Qwen3 Omni 30B A3B Thinking", - "display_name": "Qwen3 Omni 30B A3B Thinking", + "id": "z-ai/glm-5v-turbo", + "name": "GLM-5V-Turbo", + "display_name": "GLM-5V-Turbo", "modalities": { "input": [ - "text", "image", - "video", - "audio" + "text", + "video" ], "output": [ "text" ] }, "limit": { - "context": 65536, - "output": 16384 + "context": 202752, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -264999,25 +265610,22 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "none", + "high" ] } }, "attachment": true, - "open_weights": true, - "release_date": "2025-09-22", - "last_updated": "2025-09-22", + "open_weights": false, + "release_date": "2026-04-01", + "last_updated": "2026-04-01", "type": "chat" }, { - "id": "qwen/qwen3-reranker-8b", - "name": "Qwen3 Reranker 8B", - "display_name": "Qwen3 Reranker 8B", + "id": "z-ai/glm-5.2", + "name": "GLM-5.2", + "display_name": "GLM-5.2", "modalities": { "input": [ "text" @@ -265027,37 +265635,46 @@ ] }, "limit": { - "context": 32768, - "output": 8192 + "context": 1048576, + "output": 943718 }, - "temperature": false, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "effort_options": [ + "none", + "high", + "xhigh" + ] + } }, "attachment": false, "open_weights": true, - "release_date": "2025-06-05", - "last_updated": "2025-06-05", - "type": "rerank" + "release_date": "2026-06-13", + "last_updated": "2026-06-13", + "type": "chat" }, { - "id": "qwen/qwen3.5-27b", - "name": "Qwen3.5 27B", - "display_name": "Qwen3.5 27B", + "id": "z-ai/glm-5-turbo", + "name": "GLM-5-Turbo", + "display_name": "GLM-5-Turbo", "modalities": { "input": [ - "text", - "image", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 65536 + "context": 202752, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -265068,24 +265685,22 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "none", + "high" ] } }, - "attachment": true, - "open_weights": true, - "release_date": "2026-02-23", - "last_updated": "2026-02-23", + "attachment": false, + "open_weights": false, + "release_date": "2026-03-16", + "last_updated": "2026-03-16", "type": "chat" }, { - "id": "qwen/qwen-mt-plus", - "name": "Qwen MT Plus", - "display_name": "Qwen MT Plus", + "id": "z-ai/glm-4.5", + "name": "GLM-4.5", + "display_name": "GLM-4.5", "modalities": { "input": [ "text" @@ -265095,37 +265710,45 @@ ] }, "limit": { - "context": 16384, - "output": 8192 + "context": 131072, + "output": 98304 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "none", + "high" + ] + } }, "attachment": false, - "open_weights": false, - "release_date": "2025-07-24", - "last_updated": "2025-07-24", + "open_weights": true, + "knowledge": "2025-04", + "release_date": "2025-07-28", + "last_updated": "2025-07-28", "type": "chat" }, { - "id": "stepfun/step-3.7-flash", - "name": "Step 3.7 Flash", - "display_name": "Step 3.7 Flash", + "id": "z-ai/glm-5.3-prime", + "name": "Z.ai: GLM 5.3 Prime", + "display_name": "Z.ai: GLM 5.3 Prime", "modalities": { "input": [ - "text", - "image", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 256000 + "context": 1000000, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -265135,36 +265758,27 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], "effort_options": [ "low", - "medium", - "high" + "high", + "max" ] } }, - "attachment": true, - "open_weights": true, - "knowledge": "2026-03-01", - "release_date": "2026-05-29", - "last_updated": "2026-05-29", + "attachment": false, + "open_weights": false, + "release_date": "2026-09-23", + "last_updated": "2026-09-23", "type": "chat" }, { - "id": "stepfun/step-5-preview", - "name": "Step 5 Preview", - "display_name": "Step 5 Preview", + "id": "kilo-auto/balanced", + "name": "Auto Balanced", + "display_name": "Auto Balanced", "modalities": { "input": [ "text", - "image", - "video" + "image" ], "output": [ "text" @@ -265172,7 +265786,7 @@ }, "limit": { "context": 1000000, - "output": 64000 + "output": 65536 }, "temperature": true, "tool_call": true, @@ -265180,32 +265794,16 @@ "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "effort_options": [ - "low", - "medium", - "high" - ] - } - }, "attachment": true, - "open_weights": true, - "release_date": "2026-09-16", - "last_updated": "2026-09-20", + "open_weights": false, + "release_date": "1970-01-01", + "last_updated": "1970-01-01", "type": "chat" }, { - "id": "meta-llama/llama-3.2-1b-instruct", - "name": "Llama 3.2 1B Instruct", - "display_name": "Llama 3.2 1B Instruct", + "id": "kilo-auto/free", + "name": "Auto Free", + "display_name": "Auto Free", "modalities": { "input": [ "text" @@ -265215,25 +265813,25 @@ ] }, "limit": { - "context": 131000, - "output": 32000 + "context": 256000, + "output": 32768 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, "attachment": false, - "open_weights": true, - "knowledge": "2023-12", - "release_date": "2024-09-25", - "last_updated": "2024-09-25", + "open_weights": false, + "release_date": "1970-01-01", + "last_updated": "1970-01-01", "type": "chat" }, { - "id": "meta-llama/llama-4-scout-17b-16e-instruct", - "name": "Llama 4 Scout 17B Instruct", - "display_name": "Llama 4 Scout 17B Instruct", + "id": "kilo-auto/efficient", + "name": "Auto Efficient", + "display_name": "Auto Efficient", "modalities": { "input": [ "text", @@ -265244,53 +265842,55 @@ ] }, "limit": { - "context": 131072, - "output": 131072 + "context": 1000000, + "output": 65536 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, "attachment": true, - "open_weights": true, - "knowledge": "2024-08", - "release_date": "2025-04-05", - "last_updated": "2025-04-05", + "open_weights": false, + "release_date": "1970-01-01", + "last_updated": "1970-01-01", "type": "chat" }, { - "id": "meta-llama/llama-3.3-70b-instruct", - "name": "Llama-3.3-70B-Instruct", - "display_name": "Llama-3.3-70B-Instruct", + "id": "kilo-auto/frontier", + "name": "Auto Frontier", + "display_name": "Auto Frontier", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 12288, - "output": 12288 + "context": 1000000, + "output": 128000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, - "open_weights": true, - "knowledge": "2023-12", - "release_date": "2024-12-06", - "last_updated": "2024-12-06", + "attachment": true, + "open_weights": false, + "release_date": "1970-01-01", + "last_updated": "1970-01-01", "type": "chat" }, { - "id": "meta-llama/llama-4-maverick-17b-128e-instruct-fp8", - "name": "Llama 4 Maverick 17B Instruct", - "display_name": "Llama 4 Maverick 17B Instruct", + "id": "kilo-auto/small", + "name": "Auto Small", + "display_name": "Auto Small", "modalities": { "input": [ "text", @@ -265301,25 +265901,25 @@ ] }, "limit": { - "context": 1048576, - "output": 8192 + "context": 262144, + "output": 32768 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, "attachment": true, - "open_weights": true, - "knowledge": "2024-08", - "release_date": "2025-04-05", - "last_updated": "2025-04-05", + "open_weights": false, + "release_date": "1970-01-01", + "last_updated": "1970-01-01", "type": "chat" }, { - "id": "meta-llama/llama-3.1-8b-instruct", - "name": "Llama-3.1-8B-Instruct", - "display_name": "Llama-3.1-8B-Instruct", + "id": "morph/morph-v3-fast", + "name": "Morph: Morph V3 Fast", + "display_name": "Morph: Morph V3 Fast", "modalities": { "input": [ "text" @@ -265329,8 +265929,8 @@ ] }, "limit": { - "context": 16384, - "output": 16384 + "context": 262144, + "output": 131072 }, "temperature": true, "tool_call": false, @@ -265338,87 +265938,55 @@ "supported": false }, "attachment": false, - "open_weights": true, - "knowledge": "2023-12", - "release_date": "2024-07-23", - "last_updated": "2024-07-23", + "open_weights": false, + "release_date": "2025-07-07", + "last_updated": "2025-07-07", "type": "chat" - } - ] - }, - "kimi-code-plan-cn": { - "id": "kimi-code-plan-cn", - "name": "Kimi For Coding (kimi.com)", - "display_name": "Kimi For Coding (kimi.com)", - "api": "https://api.kimi.com/coding/v1", - "doc": "https://www.kimi.com/code/docs/en/kimi-code/models.html", - "models": [ + }, { - "id": "k3", - "name": "Kimi K3", - "display_name": "Kimi K3", + "id": "morph/morph-v3-large", + "name": "Morph: Morph V3 Large", + "display_name": "Morph: Morph V3 Large", "modalities": { "input": [ - "text", - "image", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, + "context": 262144, "output": 131072 }, - "temperature": false, - "tool_call": true, + "temperature": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "effort_options": [ - "none", - "low", - "high", - "max" - ] - } + "supported": false }, - "attachment": true, - "open_weights": true, - "release_date": "2026-07-16", - "last_updated": "2026-07-16", + "attachment": false, + "open_weights": false, + "release_date": "2025-07-07", + "last_updated": "2025-07-07", "type": "chat" }, { - "id": "kimi-for-coding-highspeed", - "name": "Kimi For Coding HighSpeed", - "display_name": "Kimi For Coding HighSpeed", + "id": "inception/mercury-2", + "name": "Inception: Mercury 2", + "display_name": "Inception: Mercury 2", "modalities": { "input": [ - "text", - "image", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 32768 + "context": 128000, + "output": 50000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -265426,41 +265994,37 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "none", + "low", + "medium", + "high" ] } }, - "attachment": true, - "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-06-12", - "last_updated": "2026-06-12", + "attachment": false, + "open_weights": false, + "release_date": "2026-02-24", + "last_updated": "2026-02-24", "type": "chat" }, { - "id": "kimi-for-coding", - "name": "Kimi K2.8 Preview", - "display_name": "Kimi K2.8 Preview", + "id": "inception/mercury-3", + "name": "Inception: Mercury 3", + "display_name": "Inception: Mercury 3", "modalities": { "input": [ - "text", - "image", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 32768 + "context": 1000000, + "output": 131072 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -265468,46 +266032,37 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], "effort_options": [ "none", "low", - "high", - "max" + "medium", + "high" ] } }, - "attachment": true, + "attachment": false, "open_weights": false, - "release_date": "2026-09-11", - "last_updated": "2026-09-11", + "release_date": "2026-10-16", + "last_updated": "2026-10-16", "type": "chat" }, { - "id": "k3.1-pro-highspeed", - "name": "Kimi K3.1 Pro Highspeed", - "display_name": "Kimi K3.1 Pro Highspeed", + "id": "inception/mercury-2.5", + "name": "Inception: Mercury 2.5", + "display_name": "Inception: Mercury 2.5", "modalities": { "input": [ - "text", - "image", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 260000, + "output": 65536 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -265515,386 +266070,250 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], "effort_options": [ + "none", "low", - "high", - "max" + "medium", + "high" ] } }, - "attachment": true, + "attachment": false, "open_weights": false, - "release_date": "2026-10-23", - "last_updated": "2026-10-23", + "release_date": "2026-09-08", + "last_updated": "2026-09-08", "type": "chat" }, { - "id": "k3-256k", - "name": "Kimi K3-256K", - "display_name": "Kimi K3-256K", + "id": "meta-llama/llama-3.1-8b-instruct", + "name": "Llama-3.1-8B-Instruct", + "display_name": "Llama-3.1-8B-Instruct", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 131072 + "context": 131072, + "output": 117964 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "effort_options": [ - "none", - "low", - "high", - "max" - ] - } + "supported": false }, - "attachment": true, + "attachment": false, "open_weights": true, - "release_date": "2026-07-16", - "last_updated": "2026-07-16", + "knowledge": "2023-12", + "release_date": "2024-07-23", + "last_updated": "2024-07-23", "type": "chat" }, { - "id": "k3.1-pro", - "name": "Kimi K3.1 Pro", - "display_name": "Kimi K3.1 Pro", + "id": "meta-llama/llama-4-scout", + "name": "Meta: Llama 4 Scout", + "display_name": "Meta: Llama 4 Scout", "modalities": { "input": [ "text", - "image", - "video" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 327680, + "output": 16384 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "effort_options": [ - "low", - "high", - "max" - ] - } + "supported": false }, "attachment": true, - "open_weights": false, - "release_date": "2026-10-23", - "last_updated": "2026-10-23", + "open_weights": true, + "knowledge": "2024-08", + "release_date": "2025-04-05", + "last_updated": "2025-04-05", "type": "chat" - } - ] - }, - "standardcompute": { - "id": "standardcompute", - "name": "Standard Compute", - "display_name": "Standard Compute", - "api": "https://api.stdcmpt.com/v1", - "doc": "https://docs.standardcompute.com/models", - "models": [ + }, { - "id": "standardcompute", - "name": "Standard Compute", - "display_name": "Standard Compute", + "id": "meta-llama/llama-3.3-70b-instruct", + "name": "Llama-3.3-70B-Instruct", + "display_name": "Llama-3.3-70B-Instruct", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 50000 + "context": 131072, + "output": 16384 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } + "supported": false }, - "attachment": true, - "open_weights": false, - "release_date": "2026-03-01", - "last_updated": "2026-10-13", + "attachment": false, + "open_weights": true, + "knowledge": "2023-12", + "release_date": "2024-12-06", + "last_updated": "2024-12-06", "type": "chat" }, { - "id": "minimax/minimax-m3", - "name": "MiniMax-M3", - "display_name": "MiniMax-M3", + "id": "meta-llama/llama-3.1-70b-instruct", + "name": "Llama-3.1-70B-Instruct", + "display_name": "Llama-3.1-70B-Instruct", "modalities": { "input": [ - "text", - "image", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 50000 + "context": 131072, + "output": 16384 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } + "supported": false }, - "attachment": true, + "attachment": false, "open_weights": true, - "release_date": "2026-06-01", - "last_updated": "2026-06-01", + "knowledge": "2023-12", + "release_date": "2024-07-23", + "last_updated": "2024-07-23", "type": "chat" }, { - "id": "x-ai/grok-4.3", - "name": "Grok 4.3", - "display_name": "Grok 4.3", + "id": "meta-llama/llama-4-maverick", + "name": "Meta: Llama 4 Maverick", + "display_name": "Meta: Llama 4 Maverick", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 30000 + "context": 128000, + "output": 16384 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "effort_options": [ - "none", - "low", - "medium", - "high" - ] - } + "supported": false }, "attachment": true, - "open_weights": false, - "release_date": "2026-04-17", - "last_updated": "2026-04-17", + "open_weights": true, + "knowledge": "2024-08", + "release_date": "2025-04-05", + "last_updated": "2025-04-05", "type": "chat" }, { - "id": "xiaomi/mimo-v2.6-pro", - "name": "MiMo-V2.6-Pro", - "display_name": "MiMo-V2.6-Pro", + "id": "meta-llama/llama-guard-4-12b", + "name": "Meta: Llama Guard 4 12B", + "display_name": "Meta: Llama Guard 4 12B", "modalities": { "input": [ "text", - "image", - "audio", - "video" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 50000 + "context": 163840, + "output": 16384 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } + "supported": false }, "attachment": true, "open_weights": true, - "release_date": "2026-09-22", - "last_updated": "2026-09-22", + "release_date": "2025-04-29", + "last_updated": "2025-04-29", "type": "chat" }, { - "id": "xiaomi/mimo-v2.6-flash", - "name": "MiMo-V2.6-Flash", - "display_name": "MiMo-V2.6-Flash", + "id": "meta-llama/llama-3.2-1b-instruct", + "name": "Llama 3.2 1B Instruct", + "display_name": "Llama 3.2 1B Instruct", "modalities": { "input": [ - "text", - "image", - "audio", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 50000 + "context": 60000, + "output": 54000 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } + "supported": false }, - "attachment": true, + "attachment": false, "open_weights": true, - "release_date": "2026-09-22", - "last_updated": "2026-09-22", + "knowledge": "2023-12", + "release_date": "2024-09-25", + "last_updated": "2024-09-25", "type": "chat" }, { - "id": "xiaomi/mimo-v2.5", - "name": "MiMo-V2.5", - "display_name": "MiMo-V2.5", + "id": "meta-llama/llama-3.2-3b-instruct", + "name": "Meta: Llama 3.2 3B Instruct", + "display_name": "Meta: Llama 3.2 3B Instruct", "modalities": { "input": [ - "text", - "image", - "audio", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 50000 + "context": 80000, + "output": 72000 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } + "supported": false }, - "attachment": true, + "attachment": false, "open_weights": true, - "knowledge": "2024-12", - "release_date": "2026-04-22", - "last_updated": "2026-04-22", + "knowledge": "2023-12", + "release_date": "2024-09-25", + "last_updated": "2024-09-25", "type": "chat" }, { - "id": "xiaomi/mimo-v2.5-pro", - "name": "MiMo-V2.5-Pro", - "display_name": "MiMo-V2.5-Pro", + "id": "cohere/command-r-08-2024", + "name": "Command R", + "display_name": "Command R", "modalities": { "input": [ "text" @@ -265904,159 +266323,95 @@ ] }, "limit": { - "context": 1048576, - "output": 50000 + "context": 128000, + "output": 4000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } + "supported": false }, "attachment": false, "open_weights": true, - "knowledge": "2024-12", - "release_date": "2026-04-22", - "last_updated": "2026-04-22", + "knowledge": "2024-06-01", + "release_date": "2024-08-30", + "last_updated": "2024-08-30", "type": "chat" }, { - "id": "anthropic/claude-fable-5.1", - "name": "Claude Fable 5.1", - "display_name": "Claude Fable 5.1", + "id": "cohere/command-r-plus-08-2024", + "name": "Command R+", + "display_name": "Command R+", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 50000 + "context": 128000, + "output": 4000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "high", - "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Adaptive thinking is always on for Claude Fable 5 and Claude Mythos 5; thinking.type = \"disabled\" is rejected.", - "Manual budget_tokens requests return 400 on Claude Fable 5 and Claude Mythos 5.", - "thinking.display defaults to omitted; set display to summarized to receive readable thinking summaries." - ] - } + "supported": false }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-06", - "release_date": "2026-09-01", - "last_updated": "2026-09-01", + "attachment": false, + "open_weights": true, + "knowledge": "2024-06-01", + "release_date": "2024-08-30", + "last_updated": "2024-08-30", "type": "chat" }, { - "id": "anthropic/claude-opus-5", - "name": "Claude Opus 5", - "display_name": "Claude Opus 5", + "id": "cohere/command-r7b-12-2024", + "name": "Command R7B", + "display_name": "Command R7B", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 50000 + "context": 128000, + "output": 4000 }, - "temperature": false, - "tool_call": true, + "temperature": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "effort_options": [ - "none", - "low", - "medium", - "high", - "xhigh", - "max" - ] - } + "supported": false }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-05", - "release_date": "2026-07-24", - "last_updated": "2026-07-24", + "attachment": false, + "open_weights": true, + "knowledge": "2024-06-01", + "release_date": "2024-12-13", + "last_updated": "2024-12-13", "type": "chat" }, { - "id": "anthropic/claude-sonnet-5", - "name": "Claude Sonnet 5", - "display_name": "Claude Sonnet 5", + "id": "cohere/command-a-plus", + "name": "Cohere: Command A+", + "display_name": "Cohere: Command A+", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 50000 + "context": 192000, + "output": 64000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -266064,98 +266419,64 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], "effort_options": [ "none", - "low", - "medium", - "high", - "xhigh", - "max" + "high" ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2026-01-31", - "release_date": "2026-06-30", - "last_updated": "2026-06-30", + "open_weights": true, + "knowledge": "2025-04-01", + "release_date": "2026-05-20", + "last_updated": "2026-06-09", "type": "chat" }, { - "id": "anthropic/claude-sonnet-5.5", - "name": "Claude Sonnet 5.5", - "display_name": "Claude Sonnet 5.5", + "id": "cohere/command-a", + "name": "Command A", + "display_name": "Command A", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 50000 + "context": 256000, + "output": 8192 }, - "temperature": false, - "tool_call": true, + "temperature": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" - ] - } + "supported": false }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-06", - "release_date": "2026-09-28", - "last_updated": "2026-09-28", + "attachment": false, + "open_weights": true, + "knowledge": "2024-08-31", + "release_date": "2025-03-13", + "last_updated": "2025-03-13", "type": "chat" }, { - "id": "anthropic/claude-fable-5", - "name": "Claude Fable 5", - "display_name": "Claude Fable 5", + "id": "cohere/north-mini-code:free", + "name": "Cohere: North Mini Code (free)", + "display_name": "Cohere: North Mini Code (free)", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 50000 + "context": 256000, + "output": 64000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -266163,46 +266484,26 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "high", "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Adaptive thinking is always on for Claude Fable 5 and Claude Mythos 5; thinking.type = \"disabled\" is rejected.", - "Manual budget_tokens requests return 400 on Claude Fable 5 and Claude Mythos 5.", - "thinking.display defaults to omitted; set display to summarized to receive readable thinking summaries." + "none", + "high" ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-01-31", + "attachment": false, + "open_weights": true, + "knowledge": "2025-09-23", "release_date": "2026-06-09", "last_updated": "2026-06-09", "type": "chat" }, { - "id": "anthropic/claude-haiku-5.5", - "name": "Claude Haiku 5.5", - "display_name": "Claude Haiku 5.5", + "id": "minimax/minimax-m1", + "name": "MiniMax: MiniMax M1", + "display_name": "MiniMax: MiniMax M1", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" @@ -266210,9 +266511,9 @@ }, "limit": { "context": 1000000, - "output": 50000 + "output": 40000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -266220,49 +266521,35 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], "effort_options": [ "none", - "low", - "medium", - "high", - "xhigh", - "max" + "high" ] } }, - "attachment": true, + "attachment": false, "open_weights": false, - "knowledge": "2026-06", - "release_date": "2026-10-07", - "last_updated": "2026-10-07", + "release_date": "2025-06-17", + "last_updated": "2025-06-17", "type": "chat" }, { - "id": "anthropic/claude-opus-5.5", - "name": "Claude Opus 5.5", - "display_name": "Claude Opus 5.5", + "id": "minimax/minimax-m2.5", + "name": "MiniMax-M2.5", + "display_name": "MiniMax-M2.5", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 50000 + "context": 198000, + "output": 32768 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -266271,79 +266558,49 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" + "high" ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-06", - "release_date": "2026-09-22", - "last_updated": "2026-09-22", + "attachment": false, + "open_weights": true, + "release_date": "2026-02-12", + "last_updated": "2026-02-12", "type": "chat" }, { - "id": "z-ai/glm-5.3-flash", - "name": "GLM-5.3-Flash", - "display_name": "GLM-5.3-Flash", + "id": "minimax/minimax-01", + "name": "MiniMax: MiniMax-01", + "display_name": "MiniMax: MiniMax-01", "modalities": { "input": [ "text", - "image", - "video", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 50000 + "context": 1000192, + "output": 900172 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "effort_options": [ - "low", - "high", - "max" - ] - } + "supported": false }, "attachment": true, - "open_weights": true, - "release_date": "2026-08-26", - "last_updated": "2026-08-26", + "open_weights": false, + "release_date": "2025-01-15", + "last_updated": "2025-01-15", "type": "chat" }, { - "id": "z-ai/glm-5.3", - "name": "GLM-5.3", - "display_name": "GLM-5.3", + "id": "minimax/minimax-m2", + "name": "MiniMax-M2", + "display_name": "MiniMax-M2", "modalities": { "input": [ "text" @@ -266353,8 +266610,8 @@ ] }, "limit": { - "context": 1000000, - "output": 50000 + "context": 204800, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -266364,41 +266621,34 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], "effort_options": [ - "low", - "high", - "max" + "high" ] } }, "attachment": false, "open_weights": true, - "release_date": "2026-08-14", - "last_updated": "2026-08-14", + "release_date": "2025-10-27", + "last_updated": "2025-10-27", "type": "chat" }, { - "id": "z-ai/glm-5.2", - "name": "GLM-5.2", - "display_name": "GLM-5.2", + "id": "minimax/minimax-m3", + "name": "MiniMax-M3", + "display_name": "MiniMax-M3", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 50000 + "context": 524288, + "output": 512000 }, "temperature": true, "tool_call": true, @@ -266409,29 +266659,22 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], "effort_options": [ "none", - "high", - "xhigh" + "high" ] } }, - "attachment": false, + "attachment": true, "open_weights": true, - "release_date": "2026-06-13", - "last_updated": "2026-06-13", + "release_date": "2026-06-01", + "last_updated": "2026-06-01", "type": "chat" }, { - "id": "deepseek/deepseek-v4-flash", - "name": "DeepSeek V4 Flash", - "display_name": "DeepSeek V4 Flash", + "id": "minimax/minimax-m2.1", + "name": "MiniMax-M2.1", + "display_name": "MiniMax-M2.1", "modalities": { "input": [ "text" @@ -266441,8 +266684,8 @@ ] }, "limit": { - "context": 1000000, - "output": 50000 + "context": 204800, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -266453,36 +266696,21 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "high", "effort_options": [ - "low", - "high", - "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." + "high" ] } }, "attachment": false, "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-04-24", - "last_updated": "2026-04-24", + "release_date": "2025-12-23", + "last_updated": "2025-12-23", "type": "chat" }, { - "id": "deepseek/deepseek-v4-pro", - "name": "DeepSeek V4 Pro", - "display_name": "DeepSeek V4 Pro", + "id": "minimax/minimax-m2-her", + "name": "MiniMax-M2 Her", + "display_name": "MiniMax-M2 Her", "modalities": { "input": [ "text" @@ -266492,95 +266720,24 @@ ] }, "limit": { - "context": 1000000, - "output": 50000 + "context": 65536, + "output": 2048 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "high", - "effort_options": [ - "low", - "high", - "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." - ] - } + "supported": false }, "attachment": false, - "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-04-24", - "last_updated": "2026-04-24", - "type": "chat" - }, - { - "id": "deepseek/deepseek-v4.1-flash", - "name": "DeepSeek V4.1 Flash", - "display_name": "DeepSeek V4.1 Flash", - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 1000000, - "output": 50000 - }, - "temperature": true, - "tool_call": true, - "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "effort_options": [ - "none", - "low", - "high", - "max" - ] - } - }, - "attachment": true, - "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-09-10", - "last_updated": "2026-09-10", + "open_weights": false, + "release_date": "2026-01-23", + "last_updated": "2026-01-23", "type": "chat" }, { - "id": "deepseek/deepseek-v4-flash-0731", - "name": "DeepSeek V4 Flash 0731", - "display_name": "DeepSeek V4 Flash 0731", + "id": "minimax/minimax-m2.7", + "name": "MiniMax-M2.7", + "display_name": "MiniMax-M2.7", "modalities": { "input": [ "text" @@ -266590,8 +266747,8 @@ ] }, "limit": { - "context": 1000000, - "output": 50000 + "context": 204800, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -266609,35 +266766,33 @@ "thinking_blocks" ], "effort_options": [ - "none", - "low", - "high", - "max" + "high" ] } }, "attachment": false, "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-07-31", - "last_updated": "2026-07-31", + "release_date": "2026-03-18", + "last_updated": "2026-03-18", "type": "chat" }, { - "id": "deepseek/deepseek-v4-pro-0813", - "name": "DeepSeek V4 Pro 0813", - "display_name": "DeepSeek V4 Pro 0813", + "id": "minimax/minimax-m3.1-flash", + "name": "MiniMax: MiniMax M3.1 Flash", + "display_name": "MiniMax: MiniMax M3.1 Flash", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 50000 + "context": 1048576, + "output": 524288 }, "temperature": true, "tool_call": true, @@ -266647,99 +266802,77 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], "effort_options": [ - "none", "low", + "medium", "high", + "xhigh", "max" ] } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-08-13", - "last_updated": "2026-08-22", + "attachment": true, + "open_weights": false, + "release_date": "2026-10-19", + "last_updated": "2026-10-19", "type": "chat" }, { - "id": "google/gemini-2.5-pro", - "name": "Gemini 2.5 Pro", - "display_name": "Gemini 2.5 Pro", + "id": "perceptron/perceptron-mk1", + "name": "Perceptron: Perceptron Mk1", + "display_name": "Perceptron: Perceptron Mk1", "modalities": { "input": [ "text", "image", - "audio", - "video", - "pdf" + "video" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 50000 + "context": 32768, + "output": 8192 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { "supported": true, "default": true }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "budget", - "budget": { - "default": -1, - "min": 128, - "max": 32768, - "auto": -1, - "unit": "tokens" - }, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "none", + "high" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-01", - "release_date": "2025-06-17", - "last_updated": "2025-06-17", + "release_date": "2026-05-12", + "last_updated": "2026-05-12", "type": "chat" }, { - "id": "google/gemini-3.5-flash-lite", - "name": "Gemini 3.5 Flash Lite", - "display_name": "Gemini 3.5 Flash Lite", + "id": "perceptron/perceptron-mk1.5", + "name": "Perceptron: Perceptron Mk1.5", + "display_name": "Perceptron: Perceptron Mk1.5", "modalities": { "input": [ "text", "image", "video", - "audio", - "pdf" + "audio" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 50000 + "context": 36864, + "output": 8192 }, "temperature": true, "tool_call": true, @@ -266749,14 +266882,8 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], "effort_options": [ + "none", "minimal", "low", "medium", @@ -266766,76 +266893,47 @@ }, "attachment": true, "open_weights": false, - "knowledge": "2026-03", - "release_date": "2026-07-21", - "last_updated": "2026-07-21", + "release_date": "2026-09-25", + "last_updated": "2026-09-25", "type": "chat" }, { - "id": "google/gemini-2.5-flash", - "name": "Gemini 2.5 Flash", - "display_name": "Gemini 2.5 Flash", + "id": "bytedance/ui-tars-1.5-7b", + "name": "ByteDance: UI-TARS 7B ", + "display_name": "ByteDance: UI-TARS 7B ", "modalities": { "input": [ - "text", "image", - "audio", - "video", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 50000 + "context": 128000, + "output": 2048 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "budget", - "budget": { - "default": -1, - "min": 0, - "max": 24576, - "auto": -1, - "off": 0, - "unit": "tokens" - }, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } + "supported": false }, "attachment": true, "open_weights": false, - "knowledge": "2025-01", - "release_date": "2025-06-17", - "last_updated": "2025-06-17", + "release_date": "2025-07-22", + "last_updated": "2025-07-22", "type": "chat" }, { - "id": "google/gemini-3.1-pro-preview", - "name": "Gemini 3.1 Pro Preview", - "display_name": "Gemini 3.1 Pro Preview", + "id": "moonshotai/kimi-k3", + "name": "Kimi K3", + "display_name": "Kimi K3", "modalities": { "input": [ "text", "image", - "video", - "audio", - "pdf" + "video" ], "output": [ "text" @@ -266843,7 +266941,7 @@ }, "limit": { "context": 1048576, - "output": 50000 + "output": 943718 }, "temperature": true, "tool_call": true, @@ -266854,47 +266952,36 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "level", - "level": "high", - "level_options": [ + "effort_options": [ + "none", "low", - "high" - ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "high", + "max" ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2025-01", - "release_date": "2026-02-19", - "last_updated": "2026-02-19", + "open_weights": true, + "release_date": "2026-07-16", + "last_updated": "2026-07-16", "type": "chat" }, { - "id": "google/gemini-3.8-flash", - "name": "Gemini 3.8 Flash", - "display_name": "Gemini 3.8 Flash", + "id": "moonshotai/kimi-k2.7-code", + "name": "Kimi K2.7 Code", + "display_name": "Kimi K2.7 Code", "modalities": { "input": [ "text", - "image", - "video", - "audio", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 50000 + "context": 262144, + "output": 235929 }, "temperature": true, "tool_call": true, @@ -266905,48 +266992,35 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "level", - "level": "high", - "level_options": [ - "minimal", - "low", - "medium", + "effort_options": [ "high" - ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" ] } }, "attachment": true, - "open_weights": false, - "release_date": "2026-09-02", - "last_updated": "2026-09-02", + "open_weights": true, + "knowledge": "2025-01", + "release_date": "2026-06-12", + "last_updated": "2026-06-12", "type": "chat" }, { - "id": "openai/gpt-6-astra", - "name": "GPT-6 Astra", - "display_name": "GPT-6 Astra", + "id": "moonshotai/kimi-k2-thinking", + "name": "Kimi K2 Thinking", + "display_name": "Kimi K2 Thinking", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 50000 + "context": 262144, + "output": 98304 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -266962,97 +267036,61 @@ "thinking_blocks" ], "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" + "high" ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-04-30", - "release_date": "2026-09-04", - "last_updated": "2026-09-04", + "attachment": false, + "open_weights": true, + "knowledge": "2024-08", + "release_date": "2025-11-06", + "last_updated": "2025-11-06", "type": "chat" }, { - "id": "openai/gpt-5.6-sol", - "name": "GPT-5.6 Sol", - "display_name": "GPT-5.6 Sol", + "id": "moonshotai/kimi-k2-0905", + "name": "Kimi K2 Instruct 0905", + "display_name": "Kimi K2 Instruct 0905", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 50000 + "context": 262144, + "output": 98304 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", - "effort_options": [ - "none", - "low", - "medium", - "high", - "xhigh", - "max" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } + "supported": false }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-02-16", - "release_date": "2026-07-09", - "last_updated": "2026-07-09", + "attachment": false, + "open_weights": true, + "release_date": "2025-09-05", + "last_updated": "2025-09-05", "type": "chat" }, { - "id": "openai/gpt-5.6-terra", - "name": "GPT-5.6 Terra", - "display_name": "GPT-5.6 Terra", + "id": "moonshotai/kimi-k3.1-pro", + "name": "MoonshotAI: Kimi K3.1 Pro", + "display_name": "MoonshotAI: Kimi K3.1 Pro", "modalities": { "input": [ "text", "image", - "pdf" + "video" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 50000 + "context": 1048576, + "output": 943718 }, "temperature": false, "tool_call": true, @@ -267062,58 +267100,66 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", "effort_options": [ "none", "low", - "medium", "high", - "xhigh", "max" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-02-16", - "release_date": "2026-07-09", - "last_updated": "2026-07-09", + "release_date": "2026-10-23", + "last_updated": "2026-10-23", "type": "chat" }, { - "id": "openai/gpt-6-luna", - "name": "GPT-6 Luna", - "display_name": "GPT-6 Luna", + "id": "moonshotai/kimi-k2", + "name": "Kimi K2 0711", + "display_name": "Kimi K2 0711", + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "output": 98304 + }, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": false + }, + "attachment": false, + "open_weights": true, + "knowledge": "2024-12-31", + "release_date": "2025-07-11", + "last_updated": "2025-07-11", + "type": "chat" + }, + { + "id": "moonshotai/kimi-k2.5", + "name": "Kimi K2.5", + "display_name": "Kimi K2.5", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 50000 + "context": 262144, + "output": 235929 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -267130,40 +267176,35 @@ ], "effort_options": [ "none", - "low", - "medium", - "high", - "xhigh", - "max" + "high" ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2026-05-18", - "release_date": "2026-09-22", - "last_updated": "2026-09-22", + "open_weights": true, + "knowledge": "2025-01", + "release_date": "2026-01-27", + "last_updated": "2026-01-27", "type": "chat" }, { - "id": "openai/gpt-5.6-luna", - "name": "GPT-5.6 Luna", - "display_name": "GPT-5.6 Luna", + "id": "moonshotai/kimi-k2.6", + "name": "Kimi K2.6", + "display_name": "Kimi K2.6", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 50000 + "context": 262144, + "output": 235929 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -267172,47 +267213,26 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", "effort_options": [ "none", - "low", - "medium", - "high", - "xhigh", - "max" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", "high" - ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2026-02-16", - "release_date": "2026-07-09", - "last_updated": "2026-07-09", + "open_weights": true, + "knowledge": "2025-01", + "release_date": "2026-04-20", + "last_updated": "2026-04-20", "type": "chat" }, { - "id": "moonshotai/kimi-k3", - "name": "Kimi K3", - "display_name": "Kimi K3", + "id": "reflection/beam-1.0-501b-a23b", + "name": "Reflection: Beam 1.0 501B A23B", + "display_name": "Reflection: Beam 1.0 501B A23B", "modalities": { "input": [ - "text", - "image", - "video" + "text" ], "output": [ "text" @@ -267220,9 +267240,9 @@ }, "limit": { "context": 1048576, - "output": 50000 + "output": 131072 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -267230,31 +267250,26 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], "effort_options": [ - "none", "low", + "medium", "high", + "xhigh", "max" ] } }, - "attachment": true, + "attachment": false, "open_weights": true, - "release_date": "2026-07-16", - "last_updated": "2026-07-16", + "knowledge": "2026-06-30", + "release_date": "2026-10-05", + "last_updated": "2026-10-16", "type": "chat" }, { - "id": "qwen/qwen3.7-max", - "name": "Qwen3.7 Max", - "display_name": "Qwen3.7 Max", + "id": "reflection/beam-free", + "name": "Reflection: Beam 1.0 (free)", + "display_name": "Reflection: Beam 1.0 (free)", "modalities": { "input": [ "text" @@ -267264,8 +267279,8 @@ ] }, "limit": { - "context": 1000000, - "output": 50000 + "context": 1048576, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -267275,31 +267290,30 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" ] } }, "attachment": false, "open_weights": false, - "release_date": "2026-05-21", - "last_updated": "2026-05-21", + "release_date": "2025-08-26", + "last_updated": "2025-08-26", "type": "chat" }, { - "id": "qwen/qwen3.8-max-0902", - "name": "Qwen3.8 Max 0902", - "display_name": "Qwen3.8 Max 0902", + "id": "stepfun/step-5-preview", + "name": "Step 5 Preview", + "display_name": "Step 5 Preview", "modalities": { "input": [ "text", "image", - "video", - "pdf" + "video" ], "output": [ "text" @@ -267307,7 +267321,7 @@ }, "limit": { "context": 1000000, - "output": 50000 + "output": 64000 }, "temperature": true, "tool_call": true, @@ -267317,37 +267331,26 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], "effort_options": [ - "minimal", "low", "medium", - "high", - "xhigh" + "high" ] } }, "attachment": true, - "open_weights": false, - "release_date": "2026-09-02", - "last_updated": "2026-09-02", + "open_weights": true, + "release_date": "2026-09-16", + "last_updated": "2026-09-20", "type": "chat" }, { - "id": "qwen/qwen3.8-27b", - "name": "Qwen3.8 27B", - "display_name": "Qwen3.8 27B", + "id": "stepfun/step-3.5-flash", + "name": "Step 3.5 Flash", + "display_name": "Step 3.5 Flash", "modalities": { "input": [ - "text", - "image", - "video" + "text" ], "output": [ "text" @@ -267355,7 +267358,7 @@ }, "limit": { "context": 262144, - "output": 32768 + "output": 65536 }, "temperature": true, "tool_call": true, @@ -267365,31 +267368,22 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], "effort_options": [ - "none", - "low", - "medium", - "xhigh" + "high" ] } }, - "attachment": true, + "attachment": false, "open_weights": true, - "release_date": "2026-08-14", - "last_updated": "2026-08-14", + "knowledge": "2025-01", + "release_date": "2026-01-29", + "last_updated": "2026-02-13", "type": "chat" }, { - "id": "qwen/qwen3.8-flash", - "name": "Qwen3.8 Flash", - "display_name": "Qwen3.8 Flash", + "id": "stepfun/step-3.7-flash", + "name": "Step 3.7 Flash", + "display_name": "Step 3.7 Flash", "modalities": { "input": [ "text", @@ -267401,8 +267395,8 @@ ] }, "limit": { - "context": 1000000, - "output": 50000 + "context": 256000, + "output": 230400 }, "temperature": true, "tool_call": true, @@ -267412,165 +267406,108 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], "effort_options": [ "low", "medium", - "xhigh" + "high" ] } }, "attachment": true, - "open_weights": false, - "release_date": "2026-08-26", - "last_updated": "2026-08-26", + "open_weights": true, + "knowledge": "2026-03-01", + "release_date": "2026-05-29", + "last_updated": "2026-05-29", "type": "chat" }, { - "id": "qwen/qwen3.7-flash", - "name": "Qwen3.7 Flash", - "display_name": "Qwen3.7 Flash", + "id": "mancer/weaver", + "name": "Mancer: Weaver (alpha)", + "display_name": "Mancer: Weaver (alpha)", "modalities": { "input": [ - "text", - "image", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 50000 + "context": 8000, + "output": 6000 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } + "supported": false }, - "attachment": true, + "attachment": false, "open_weights": false, - "release_date": "2026-07-15", - "last_updated": "2026-07-15", + "knowledge": "2023-06-30", + "release_date": "2023-08-02", + "last_updated": "2023-08-02", "type": "chat" }, { - "id": "qwen/qwen3.7-plus", - "name": "Qwen3.7 Plus", - "display_name": "Qwen3.7 Plus", + "id": "writer/palmyra-x5", + "name": "Writer: Palmyra X5", + "display_name": "Writer: Palmyra X5", "modalities": { "input": [ - "text", - "image", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 50000 + "context": 1040000, + "output": 8192 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } + "supported": false }, - "attachment": true, + "attachment": false, "open_weights": false, - "knowledge": "2025-04", - "release_date": "2026-06-02", - "last_updated": "2026-06-02", + "release_date": "2025-04-28", + "last_updated": "2025-04-28", "type": "chat" }, { - "id": "mistralai/mistral-large-4-0", - "name": "Mistral Large 4", - "display_name": "Mistral Large 4", + "id": "rekaai/reka-edge", + "name": "Reka Edge", + "display_name": "Reka Edge", "modalities": { "input": [ + "image", "text", - "image" + "video" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 50000 + "context": 16384, + "output": 14745 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "effort_options": [ - "none", - "high" - ] - } + "supported": false }, "attachment": true, - "open_weights": false, - "release_date": "2026-10-06", - "last_updated": "2026-10-06", + "open_weights": true, + "release_date": "2026-03-11", + "last_updated": "2026-03-11", "type": "chat" - } - ] - }, - "deepinfra": { - "id": "deepinfra", - "name": "Deep Infra", - "display_name": "Deep Infra", - "doc": "https://deepinfra.com/models", - "models": [ + }, { - "id": "zai-org/GLM-4.7", - "name": "GLM-4.7", - "display_name": "GLM-4.7", + "id": "rekaai/reka-flash-3", + "name": "Reka Flash 3", + "display_name": "Reka Flash 3", "modalities": { "input": [ "text" @@ -267580,37 +267517,33 @@ ] }, "limit": { - "context": 202752, - "output": 16384 + "context": 65536, + "output": 58982 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { "supported": true, "default": true }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "high" ] } }, "attachment": false, "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-12-22", - "last_updated": "2025-12-22", + "knowledge": "2025-01-31", + "release_date": "2025-03-10", + "last_updated": "2025-03-10", "type": "chat" }, { - "id": "zai-org/GLM-5.1", - "name": "GLM-5.1", - "display_name": "GLM-5.1", + "id": "tencent/hy-mt2-30b-a3b", + "name": "Hy-MT2-30B-A3B", + "display_name": "Hy-MT2-30B-A3B", "modalities": { "input": [ "text" @@ -267620,78 +267553,51 @@ ] }, "limit": { - "context": 202752, - "output": 16384 + "context": 8192, + "output": 4096 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } + "supported": false }, "attachment": false, "open_weights": true, - "knowledge": "2025-04", - "release_date": "2026-04-07", - "last_updated": "2026-04-07", + "release_date": "2026-05-21", + "last_updated": "2026-05-21", "type": "chat" }, { - "id": "zai-org/GLM-5.3-Flash", - "name": "GLM-5.3-Flash", - "display_name": "GLM-5.3-Flash", + "id": "tencent/hy-mt2-7b", + "name": "Hy-MT2-7B", + "display_name": "Hy-MT2-7B", "modalities": { "input": [ - "text", - "image", - "video", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 8192, + "output": 4096 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "effort_options": [ - "low", - "high", - "max" - ] - } + "supported": false }, - "attachment": true, + "attachment": false, "open_weights": true, - "release_date": "2026-08-26", - "last_updated": "2026-08-26", + "release_date": "2026-05-21", + "last_updated": "2026-05-21", "type": "chat" }, { - "id": "zai-org/GLM-4.7-Flash", - "name": "GLM-4.7-Flash", - "display_name": "GLM-4.7-Flash", + "id": "tencent/hy3", + "name": "Hy3", + "display_name": "Hy3", "modalities": { "input": [ "text" @@ -267701,8 +267607,8 @@ ] }, "limit": { - "context": 202752, - "output": 16384 + "context": 262144, + "output": 128000 }, "temperature": true, "tool_call": true, @@ -267713,25 +267619,23 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "none", + "low", + "high" ] } }, "attachment": false, "open_weights": true, - "knowledge": "2025-04", - "release_date": "2026-01-19", - "last_updated": "2026-01-19", + "release_date": "2026-07-06", + "last_updated": "2026-07-06", "type": "chat" }, { - "id": "zai-org/GLM-5", - "name": "GLM-5", - "display_name": "GLM-5", + "id": "tencent/hy4-preview", + "name": "Hy4 preview", + "display_name": "Hy4 preview", "modalities": { "input": [ "text" @@ -267741,8 +267645,8 @@ ] }, "limit": { - "context": 202752, - "output": 16384 + "context": 1048576, + "output": 64000 }, "temperature": true, "tool_call": true, @@ -267753,25 +267657,23 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "none", + "low", + "high" ] } }, "attachment": false, "open_weights": true, - "knowledge": "2025-12", - "release_date": "2026-02-12", - "last_updated": "2026-02-12", + "release_date": "2026-08-28", + "last_updated": "2026-08-28", "type": "chat" }, { - "id": "zai-org/GLM-5.2", - "name": "GLM-5.2", - "display_name": "GLM-5.2", + "id": "tencent/hy3-preview", + "name": "Hy3 preview", + "display_name": "Hy3 preview", "modalities": { "input": [ "text" @@ -267781,8 +267683,8 @@ ] }, "limit": { - "context": 1048576, - "output": 32768 + "context": 262144, + "output": 235929 }, "temperature": true, "tool_call": true, @@ -267792,29 +267694,23 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], "effort_options": [ - "high", - "max" + "none", + "low", + "high" ] } }, "attachment": false, "open_weights": true, - "release_date": "2026-06-13", - "last_updated": "2026-06-13", + "release_date": "2026-04-20", + "last_updated": "2026-04-20", "type": "chat" }, { - "id": "zai-org/GLM-5.3", - "name": "GLM-5.3", - "display_name": "GLM-5.3", + "id": "tencent/hy-mt2-1.8b", + "name": "Hy-MT2-1.8B", + "display_name": "Hy-MT2-1.8B", "modalities": { "input": [ "text" @@ -267824,35 +267720,24 @@ ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 8192, + "output": 4096 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "effort_options": [ - "low", - "high", - "max" - ] - } + "supported": false }, "attachment": false, "open_weights": true, - "release_date": "2026-08-14", - "last_updated": "2026-08-14", + "release_date": "2026-05-21", + "last_updated": "2026-05-21", "type": "chat" }, { - "id": "zai-org/GLM-4.6", - "name": "GLM-4.6", - "display_name": "GLM-4.6", + "id": "tencent/hunyuan-a13b-instruct", + "name": "Tencent: Hunyuan A13B Instruct", + "display_name": "Tencent: Hunyuan A13B Instruct", "modalities": { "input": [ "text" @@ -267862,42 +267747,37 @@ ] }, "limit": { - "context": 202752, - "output": 131072 + "context": 131072, + "output": 117964 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { "supported": true, "default": true }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "none", + "high" ] } }, "attachment": false, - "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-09-30", - "last_updated": "2025-09-30", + "open_weights": false, + "release_date": "2025-07-08", + "last_updated": "2025-07-08", "type": "chat" }, { - "id": "Qwen/Qwen3.8-Max", - "name": "Qwen3.8 Max", - "display_name": "Qwen3.8 Max", + "id": "scaledown/extract", + "name": "ScaleDown: Extract", + "display_name": "ScaleDown: Extract", "modalities": { "input": [ "text", "image", - "video", "pdf" ], "output": [ @@ -267905,57 +267785,81 @@ ] }, "limit": { - "context": 256000, - "output": 131072 + "context": 1000000, + "output": 900000 }, - "temperature": true, - "tool_call": true, + "temperature": false, + "tool_call": false, "reasoning": { - "supported": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true - } + "supported": false }, "attachment": true, "open_weights": false, - "release_date": "2026-08-03", - "last_updated": "2026-08-03", + "release_date": "2026-10-13", + "last_updated": "2026-10-13", "type": "chat" }, { - "id": "Qwen/Qwen3-VL-30B-A3B-Instruct", - "name": "Qwen3 VL 30B A3B Instruct", - "display_name": "Qwen3 VL 30B A3B Instruct", + "id": "scaledown/summarize", + "name": "ScaleDown: Summarize", + "display_name": "ScaleDown: Summarize", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 32768 + "context": 1000000, + "output": 4000 }, - "temperature": true, - "tool_call": true, + "temperature": false, + "tool_call": false, "reasoning": { "supported": false }, "attachment": true, + "open_weights": false, + "release_date": "2026-10-13", + "last_updated": "2026-10-13", + "type": "chat" + }, + { + "id": "anthracite-org/magnum-v4-72b", + "name": "Magnum v4 72B", + "display_name": "Magnum v4 72B", + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 32768, + "output": 4096 + }, + "temperature": true, + "tool_call": false, + "reasoning": { + "supported": false + }, + "attachment": false, "open_weights": true, - "release_date": "2025-10-04", - "last_updated": "2025-10-04", + "knowledge": "2024-06-30", + "release_date": "2024-10-22", + "last_updated": "2024-10-22", "type": "chat" }, { - "id": "Qwen/Qwen3-Coder-480B-A35B-Instruct-Turbo", - "name": "Qwen3 Coder 480B A35B Instruct Turbo", - "display_name": "Qwen3 Coder 480B A35B Instruct Turbo", + "id": "thedrummer/unslopnemo-12b", + "name": "TheDrummer: UnslopNemo 12B", + "display_name": "TheDrummer: UnslopNemo 12B", "modalities": { "input": [ "text" @@ -267965,80 +267869,92 @@ ] }, "limit": { - "context": 262144, - "output": 66536 + "context": 1024000, + "output": 819200 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { "supported": false }, "attachment": false, "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-07-22", - "last_updated": "2025-07-22", + "knowledge": "2024-04-30", + "release_date": "2024-11-08", + "last_updated": "2024-11-08", "type": "chat" }, { - "id": "Qwen/Qwen3.5-397B-A17B", - "name": "Qwen 3.5 397B A17B", - "display_name": "Qwen 3.5 397B A17B", + "id": "thedrummer/skyfall-36b-v2", + "name": "TheDrummer: Skyfall 36B V2", + "display_name": "TheDrummer: Skyfall 36B V2", "modalities": { "input": [ - "text", - "image", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 81920 + "context": 32768, + "output": 29491 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true + "supported": false }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } + "attachment": false, + "open_weights": true, + "knowledge": "2024-06-30", + "release_date": "2025-03-10", + "last_updated": "2025-03-10", + "type": "chat" + }, + { + "id": "thedrummer/cydonia-24b-v4.1", + "name": "TheDrummer: Cydonia 24B V4.1", + "display_name": "TheDrummer: Cydonia 24B V4.1", + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] }, - "attachment": true, + "limit": { + "context": 131072, + "output": 117964 + }, + "temperature": true, + "tool_call": false, + "reasoning": { + "supported": false + }, + "attachment": false, "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-02-15", - "last_updated": "2026-02-15", + "knowledge": "2024-04-30", + "release_date": "2025-09-27", + "last_updated": "2025-09-27", "type": "chat" }, { - "id": "Qwen/Qwen3.6-27B", - "name": "Qwen3.6 27B", - "display_name": "Qwen3.6 27B", + "id": "stealth/glyph-cluster", + "name": "Stealth: Glyph Cluster (free)", + "display_name": "Stealth: Glyph Cluster (free)", "modalities": { "input": [ - "text", - "image", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 65536 + "context": 256000, + "output": 256000 }, "temperature": true, "tool_call": true, @@ -268048,66 +267964,78 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "low", + "medium", + "high", + "xhigh" ] } }, - "attachment": true, - "open_weights": true, - "release_date": "2026-04-22", - "last_updated": "2026-04-22", + "attachment": false, + "open_weights": false, + "release_date": "2025-08-26", + "last_updated": "2025-08-26", "type": "chat" }, { - "id": "Qwen/Qwen3-Next-80B-A3B-Instruct", - "name": "Qwen3-Next 80B-A3B Instruct", - "display_name": "Qwen3-Next 80B-A3B Instruct", + "id": "fireworks/ember-1", + "name": "Fireworks: Ember-1", + "display_name": "Fireworks: Ember-1", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 32768 + "context": 1048576, + "output": 943718 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-09", - "last_updated": "2025-09", + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "none", + "low", + "high", + "max" + ] + } + }, + "attachment": true, + "open_weights": false, + "release_date": "2026-09-22", + "last_updated": "2026-09-22", "type": "chat" }, { - "id": "Qwen/Qwen3.6-35B-A3B", - "name": "Qwen3.6 35B A3B", - "display_name": "Qwen3.6 35B A3B", + "id": "google/gemini-3.5-flash", + "name": "Gemini 3.5 Flash", + "display_name": "Gemini 3.5 Flash", "modalities": { "input": [ "text", "image", - "video" + "video", + "audio", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 81920 + "context": 1048576, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -268118,36 +268046,85 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, + "default_enabled": true, + "mode": "level", + "level": "high", + "level_options": [ + "minimal", + "low", + "medium", + "high" + ], "summaries": true, "visibility": "summary", "continuation": [ - "thinking_blocks" + "thought_signatures" ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-04-17", - "last_updated": "2026-04-17", + "open_weights": false, + "knowledge": "2025-01", + "release_date": "2026-05-19", + "last_updated": "2026-05-19", "type": "chat" }, { - "id": "Qwen/Qwen3.5-27B", - "name": "Qwen3.5 27B", - "display_name": "Qwen3.5 27B", + "id": "google/gemini-nano-banana-2.1", + "name": "Nano Banana 2.1", + "display_name": "Nano Banana 2.1", "modalities": { "input": [ + "image", + "text", + "pdf" + ], + "output": [ "text", + "image" + ] + }, + "limit": { + "context": 65536, + "output": 58982 + }, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "none", + "minimal", + "high" + ] + } + }, + "attachment": true, + "open_weights": false, + "release_date": "2026-10-06", + "last_updated": "2026-10-06" + }, + { + "id": "google/gemini-2.5-pro-preview", + "name": "Gemini 2.5 Pro Preview 06-05", + "display_name": "Gemini 2.5 Pro Preview 06-05", + "modalities": { + "input": [ + "pdf", "image", - "video" + "text", + "audio" ], "output": [ "text" ] }, "limit": { - "context": 262144, + "context": 1048576, "output": 65536 }, "temperature": true, @@ -268159,38 +268136,50 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, + "default_enabled": true, + "mode": "budget", + "budget": { + "default": -1, + "min": 128, + "max": 32768, + "auto": -1, + "unit": "tokens" + }, "summaries": true, "visibility": "summary", "continuation": [ - "thinking_blocks" + "thought_signatures" ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-02-23", - "last_updated": "2026-02-23", + "open_weights": false, + "knowledge": "2025-01-31", + "release_date": "2025-06-05", + "last_updated": "2025-06-05", "type": "chat" }, { - "id": "Qwen/Qwen3-32B", - "name": "Qwen3 32B", - "display_name": "Qwen3 32B", + "id": "google/gemini-3.1-flash-image", + "name": "Nano Banana 2", + "display_name": "Nano Banana 2", "modalities": { "input": [ - "text" + "image", + "text", + "pdf" ], "output": [ - "text" + "text", + "image" ] }, "limit": { - "context": 40960, - "output": 16384 + "context": 131072, + "output": 32768 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { "supported": true, "default": true @@ -268198,29 +268187,27 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "none", + "minimal", + "high" ] } }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-04-28", - "last_updated": "2025-04-28", - "type": "chat" + "attachment": true, + "open_weights": false, + "knowledge": "2025-01", + "release_date": "2026-05-28", + "last_updated": "2026-05-28" }, { - "id": "Qwen/Qwen3.5-122B-A10B", - "name": "Qwen3.5 122B-A10B", - "display_name": "Qwen3.5 122B-A10B", + "id": "google/gemma-4-26b-a4b-it", + "name": "Gemma 4 26B A4B IT", + "display_name": "Gemma 4 26B A4B IT", "modalities": { "input": [ - "text", "image", + "text", "video" ], "output": [ @@ -268229,7 +268216,7 @@ }, "limit": { "context": 262144, - "output": 65536 + "output": 32768 }, "temperature": true, "tool_call": true, @@ -268240,64 +268227,64 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "none", + "high" ] } }, "attachment": true, "open_weights": true, - "release_date": "2026-02-23", - "last_updated": "2026-02-23", + "release_date": "2026-04-02", + "last_updated": "2026-04-02", "type": "chat" }, { - "id": "Qwen/Qwen3-VL-235B-A22B-Instruct", - "name": "Qwen3 VL 235B A22B Instruct", - "display_name": "Qwen3 VL 235B A22B Instruct", + "id": "google/lyria-3-pro-preview", + "name": "Lyria 3 Pro Preview", + "display_name": "Lyria 3 Pro Preview", "modalities": { "input": [ "text", "image" ], "output": [ - "text" + "text", + "audio" ] }, "limit": { - "context": 262144, - "output": 32768 + "context": 1048576, + "output": 65536 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { "supported": false }, "attachment": true, - "open_weights": true, - "knowledge": "2025-03-31", - "release_date": "2025-09-23", - "last_updated": "2025-09-23", + "open_weights": false, + "release_date": "2026-03-25", + "last_updated": "2026-03-25", "type": "chat" }, { - "id": "Qwen/Qwen3-30B-A3B", - "name": "Qwen3 30B A3B", - "display_name": "Qwen3 30B A3B", + "id": "google/gemma-4-31b-it:free", + "name": "Google: Gemma 4 31B (free)", + "display_name": "Google: Gemma 4 31B (free)", "modalities": { "input": [ - "text" + "image", + "text", + "video" ], "output": [ "text" ] }, "limit": { - "context": 40960, - "output": 16384 + "context": 262144, + "output": 32768 }, "temperature": true, "tool_call": true, @@ -268307,38 +268294,38 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "none", + "high" ] } }, - "attachment": false, + "attachment": true, "open_weights": true, - "release_date": "2025-04-28", - "last_updated": "2025-04-28", + "knowledge": "2025-01", + "release_date": "2026-04-02", + "last_updated": "2026-04-02", "type": "chat" }, { - "id": "Qwen/Qwen3.8-27B", - "name": "Qwen3.8 27B", - "display_name": "Qwen3.8 27B", + "id": "google/gemini-4-argon", + "name": "Gemini 4 Argon", + "display_name": "Gemini 4 Argon", "modalities": { "input": [ "text", "image", - "video" + "video", + "audio", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 32768 + "context": 1048576, + "output": 943718 }, "temperature": true, "tool_call": true, @@ -268351,72 +268338,113 @@ "effort_options": [ "low", "medium", - "xhigh" + "high" ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-08-14", - "last_updated": "2026-08-14", + "open_weights": false, + "knowledge": "2026-03", + "release_date": "2026-10-13", + "last_updated": "2026-10-13", "type": "chat" }, { - "id": "Qwen/Qwen3-Max", - "name": "Qwen3 Max", - "display_name": "Qwen3 Max", + "id": "google/gemini-3-pro-image", + "name": "Nano Banana Pro", + "display_name": "Nano Banana Pro", "modalities": { "input": [ - "text" + "image", + "text", + "pdf" ], "output": [ - "text" + "text", + "image" ] }, "limit": { - "context": 256000, - "output": 65536 + "context": 65536, + "output": 32768 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true + "supported": true, + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, + "default_enabled": true, + "mode": "level", + "level": "high", + "level_options": [ + "low", + "high" + ], "summaries": true, "visibility": "summary", "continuation": [ - "thinking_blocks" + "thought_signatures" ] } }, - "attachment": false, + "attachment": true, "open_weights": false, - "knowledge": "2025-04", - "release_date": "2025-09-23", - "last_updated": "2025-09-23", + "knowledge": "2025-01", + "release_date": "2026-05-28", + "last_updated": "2026-05-28" + }, + { + "id": "google/lyria-3-clip-preview", + "name": "Lyria 3 Clip Preview", + "display_name": "Lyria 3 Clip Preview", + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text", + "audio" + ] + }, + "limit": { + "context": 1048576, + "output": 65536 + }, + "temperature": true, + "tool_call": false, + "reasoning": { + "supported": false + }, + "attachment": true, + "open_weights": false, + "release_date": "2026-03-25", + "last_updated": "2026-03-25", "type": "chat" }, { - "id": "Qwen/Qwen3.5-35B-A3B", - "name": "Qwen 3.5 35B A3B", - "display_name": "Qwen 3.5 35B A3B", + "id": "google/gemini-3.6-flash", + "name": "Gemini 3.6 Flash", + "display_name": "Gemini 3.6 Flash", "modalities": { "input": [ "text", "image", - "video" + "video", + "audio", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 81920 + "context": 1048576, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -268427,107 +268455,105 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, + "default_enabled": true, + "mode": "level", + "level": "high", + "level_options": [ + "minimal", + "low", + "medium", + "high" + ], "summaries": true, "visibility": "summary", "continuation": [ - "thinking_blocks" + "thought_signatures" ] } }, "attachment": true, - "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-02-23", - "last_updated": "2026-02-23", + "open_weights": false, + "knowledge": "2026-03", + "release_date": "2026-07-21", + "last_updated": "2026-07-21", "type": "chat" }, { - "id": "Qwen/Qwen3.7-Max", - "name": "Qwen3.7 Max", - "display_name": "Qwen3.7 Max", + "id": "google/gemma-3-12b-it", + "name": "Gemma 3 12B IT", + "display_name": "Gemma 3 12B IT", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 256000, - "output": 131072 + "context": 131072, + "output": 16384 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true - } + "supported": false }, - "attachment": false, - "open_weights": false, - "release_date": "2026-05-21", - "last_updated": "2026-05-21", + "attachment": true, + "open_weights": true, + "knowledge": "2024-08", + "release_date": "2025-03-12", + "last_updated": "2025-03-12", "type": "chat" }, { - "id": "Qwen/Qwen3.8-2.4T-A95B", - "name": "Qwen3.8 2.4T A95B", - "display_name": "Qwen3.8 2.4T A95B", + "id": "google/gemma-3-27b-it", + "name": "Gemma 3 27B IT", + "display_name": "Gemma 3 27B IT", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 131072 + "context": 131072, + "output": 117964 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "effort_options": [ - "low", - "medium", - "xhigh" - ] - } + "supported": false }, - "attachment": false, + "attachment": true, "open_weights": true, - "release_date": "2026-08-12", - "last_updated": "2026-08-12", + "knowledge": "2024-08", + "release_date": "2025-03-12", + "last_updated": "2025-03-12", "type": "chat" }, { - "id": "Qwen/Qwen3.5-9B", - "name": "Qwen3.5 9B", - "display_name": "Qwen3.5 9B", + "id": "google/gemini-3.8-flash", + "name": "Gemini 3.8 Flash", + "display_name": "Gemini 3.8 Flash", "modalities": { "input": [ "text", "image", - "video" + "video", + "audio", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 262144, + "context": 1048576, "output": 65536 }, "temperature": true, @@ -268539,91 +268565,124 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, + "default_enabled": true, + "mode": "level", + "level": "high", + "level_options": [ + "minimal", + "low", + "medium", + "high" + ], "summaries": true, "visibility": "summary", "continuation": [ - "thinking_blocks" + "thought_signatures" ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-02-23", - "last_updated": "2026-02-23", + "open_weights": false, + "release_date": "2026-09-02", + "last_updated": "2026-09-02", "type": "chat" }, { - "id": "Qwen/Qwen3-235B-A22B-Instruct-2507", - "name": "Qwen3 235B-A22B Instruct 2507", - "display_name": "Qwen3 235B-A22B Instruct 2507", + "id": "google/gemini-3.1-flash-lite-preview", + "name": "Gemini 3.1 Flash Lite Preview", + "display_name": "Gemini 3.1 Flash Lite Preview", "modalities": { "input": [ - "text" + "text", + "image", + "video", + "audio", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 16384 + "context": 1048576, + "output": 65536 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, - "open_weights": true, - "release_date": "2025-07-21", - "last_updated": "2025-07-21", + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "none", + "minimal", + "low", + "medium", + "high" + ] + } + }, + "attachment": true, + "open_weights": false, + "knowledge": "2025-01", + "release_date": "2026-03-03", + "last_updated": "2026-03-03", "type": "chat" }, { - "id": "Qwen/Qwen3.8-Flash", - "name": "Qwen3.8 Flash", - "display_name": "Qwen3.8 Flash", + "id": "google/gemini-3.1-flash-lite-image", + "name": "Nano Banana 2 Lite", + "display_name": "Nano Banana 2 Lite", "modalities": { "input": [ - "text", "image", - "video" + "text", + "pdf" ], "output": [ - "text" + "text", + "image" ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 65536, + "output": 58982 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true + "supported": true, + "default": true }, "extra_capabilities": { "reasoning": { - "supported": true + "supported": true, + "effort_options": [ + "none", + "minimal", + "high" + ] } }, "attachment": true, "open_weights": false, - "release_date": "2026-08-26", - "last_updated": "2026-08-26", - "type": "chat" + "knowledge": "2025-01", + "release_date": "2026-06-30", + "last_updated": "2026-06-30" }, { - "id": "XiaomiMiMo/MiMo-V2.6-Pro", - "name": "MiMo-V2.6-Pro", - "display_name": "MiMo-V2.6-Pro", + "id": "google/gemini-2.5-pro", + "name": "Gemini 2.5 Pro", + "display_name": "Gemini 2.5 Pro", "modalities": { "input": [ "text", "image", "audio", - "video" + "video", + "pdf" ], "output": [ "text" @@ -268631,7 +268690,7 @@ }, "limit": { "context": 1048576, - "output": 131072 + "output": 65536 }, "temperature": true, "tool_call": true, @@ -268639,94 +268698,124 @@ "supported": true, "default": true }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "budget", + "budget": { + "default": -1, + "min": 128, + "max": 32768, + "auto": -1, + "unit": "tokens" + }, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thought_signatures" + ] + } + }, "attachment": true, - "open_weights": true, - "release_date": "2026-09-22", - "last_updated": "2026-09-22", + "open_weights": false, + "knowledge": "2025-01", + "release_date": "2025-06-17", + "last_updated": "2025-06-17", "type": "chat" }, { - "id": "XiaomiMiMo/MiMo-V2.5", - "name": "MiMo-V2.5", - "display_name": "MiMo-V2.5", + "id": "google/gemini-2.5-flash-lite", + "name": "Gemini 2.5 Flash-Lite", + "display_name": "Gemini 2.5 Flash-Lite", "modalities": { "input": [ "text", "image", "audio", - "video" + "video", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 16384 + "context": 1048576, + "output": 65535 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, + "default_enabled": false, + "mode": "budget", + "budget": { + "default": -1, + "min": 512, + "max": 24576, + "auto": -1, + "unit": "tokens" + }, "summaries": true, "visibility": "summary", "continuation": [ - "thinking_blocks" + "thought_signatures" ] } }, "attachment": true, - "open_weights": true, - "knowledge": "2024-12", - "release_date": "2026-04-22", - "last_updated": "2026-04-22", + "open_weights": false, + "knowledge": "2025-01", + "release_date": "2025-06-17", + "last_updated": "2025-06-17", "type": "chat" }, { - "id": "XiaomiMiMo/MiMo-V2.6-Flash", - "name": "MiMo-V2.6-Flash", - "display_name": "MiMo-V2.6-Flash", + "id": "google/gemma-3-4b-it", + "name": "Gemma 3 4B IT", + "display_name": "Gemma 3 4B IT", "modalities": { "input": [ "text", - "image", - "audio", - "video" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 131072, + "output": 16384 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true + "supported": false }, "attachment": true, "open_weights": true, - "release_date": "2026-09-22", - "last_updated": "2026-09-22", + "knowledge": "2024-08", + "release_date": "2025-03-12", + "last_updated": "2025-03-12", "type": "chat" }, { - "id": "XiaomiMiMo/MiMo-V2.5-Pro", - "name": "MiMo-V2.5-Pro", - "display_name": "MiMo-V2.5-Pro", + "id": "google/gemini-2.5-flash", + "name": "Gemini 2.5 Flash", + "display_name": "Gemini 2.5 Flash", "modalities": { "input": [ "text", - "audio" + "image", + "audio", + "video", + "pdf" ], "output": [ "text" @@ -268734,7 +268823,7 @@ }, "limit": { "context": 1048576, - "output": 16384 + "output": 65535 }, "temperature": true, "tool_call": true, @@ -268745,66 +268834,77 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, + "default_enabled": true, + "mode": "budget", + "budget": { + "default": -1, + "min": 0, + "max": 24576, + "auto": -1, + "off": 0, + "unit": "tokens" + }, "summaries": true, "visibility": "summary", "continuation": [ - "thinking_blocks" + "thought_signatures" ] } }, "attachment": true, - "open_weights": true, - "knowledge": "2024-12", - "release_date": "2026-04-22", - "last_updated": "2026-04-22", + "open_weights": false, + "knowledge": "2025-01", + "release_date": "2025-06-17", + "last_updated": "2025-06-17", "type": "chat" }, { - "id": "ByteDance/Seed-2.0-mini", - "name": "Seed 2.0 Mini", - "display_name": "Seed 2.0 Mini", + "id": "google/gemma-2-27b-it", + "name": "Gemma 2 27B", + "display_name": "Gemma 2 27B", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 256000, - "output": 32000 + "context": 8192, + "output": 2048 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true + "supported": false }, - "attachment": true, - "open_weights": false, - "release_date": "2026-02-14", - "last_updated": "2026-02-14", + "attachment": false, + "open_weights": true, + "knowledge": "2024-06-30", + "release_date": "2024-07-13", + "last_updated": "2024-07-13", "type": "chat" }, { - "id": "ByteDance/Seed-2.0-code", - "name": "Seed 2.0 Code", - "display_name": "Seed 2.0 Code", + "id": "google/gemini-3.1-pro-preview", + "name": "Gemini 3.1 Pro Preview", + "display_name": "Gemini 3.1 Pro Preview", "modalities": { "input": [ "text", - "image" + "image", + "video", + "audio", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 256000, - "output": 131072 + "context": 1048576, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -268814,64 +268914,97 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "none", + "supported": true, + "default_enabled": true, + "mode": "level", + "level": "high", + "level_options": [ "low", - "medium", "high" + ], + "summaries": true, + "visibility": "summary", + "continuation": [ + "thought_signatures" ] } }, "attachment": true, "open_weights": false, - "release_date": "2026-02-14", - "last_updated": "2026-02-14", + "knowledge": "2025-01", + "release_date": "2026-02-19", + "last_updated": "2026-02-19", "type": "chat" }, { - "id": "ByteDance/Seed-2.0-pro", - "name": "Seed 2.0 Pro", - "display_name": "Seed 2.0 Pro", + "id": "google/gemini-3.7-flash", + "name": "Gemini 3.7 Flash", + "display_name": "Gemini 3.7 Flash", "modalities": { "input": [ "text", - "image" + "image", + "video", + "audio", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 256000, - "output": 128000 + "context": 1048576, + "output": 65536 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, "default": true }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "level", + "level": "high", + "level_options": [ + "minimal", + "low", + "medium", + "high" + ], + "summaries": true, + "visibility": "summary", + "continuation": [ + "thought_signatures" + ] + } + }, "attachment": true, "open_weights": false, - "release_date": "2026-02-14", - "last_updated": "2026-02-14", + "knowledge": "2026-03", + "release_date": "2026-08-13", + "last_updated": "2026-08-13", "type": "chat" }, { - "id": "nvidia/Llama-3.3-Nemotron-Super-49B-v1.5", - "name": "Llama 3.3 Nemotron Super 49B v1.5", - "display_name": "Llama 3.3 Nemotron Super 49B v1.5", + "id": "google/gemma-4-31b-it", + "name": "Gemma 4 31B IT", + "display_name": "Gemma 4 31B IT", "modalities": { "input": [ - "text" + "image", + "text", + "video" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 131072 + "context": 262144, + "output": 16384 }, "temperature": true, "tool_call": true, @@ -268879,27 +269012,41 @@ "supported": true, "default": true }, - "attachment": false, + "extra_capabilities": { + "reasoning": { + "supported": true, + "effort_options": [ + "none", + "high" + ] + } + }, + "attachment": true, "open_weights": true, - "release_date": "2025-07-25", - "last_updated": "2025-07-25", + "knowledge": "2025-01", + "release_date": "2026-04-02", + "last_updated": "2026-04-02", "type": "chat" }, { - "id": "nvidia/Nemotron-3-Nano-30B-A3B", - "name": "Nemotron 3 Nano 30B A3B", - "display_name": "Nemotron 3 Nano 30B A3B", + "id": "google/gemini-3.1-flash-lite", + "name": "Gemini 3.1 Flash Lite", + "display_name": "Gemini 3.1 Flash Lite", "modalities": { "input": [ - "text" + "text", + "image", + "video", + "audio", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 1048576, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -268907,30 +269054,43 @@ "supported": true, "default": true }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-06-25", - "release_date": "2025-12-15", - "last_updated": "2025-12-15", + "extra_capabilities": { + "reasoning": { + "supported": true, + "effort_options": [ + "none", + "minimal", + "low", + "medium", + "high" + ] + } + }, + "attachment": true, + "open_weights": false, + "knowledge": "2025-01", + "release_date": "2026-05-07", + "last_updated": "2026-05-07", "type": "chat" }, { - "id": "nvidia/Nemotron-3-Nano-Omni-30B-A3B-Reasoning", - "name": "Nemotron 3 Nano Omni 30B A3B Reasoning", - "display_name": "Nemotron 3 Nano Omni 30B A3B Reasoning", + "id": "google/gemini-3.1-pro-preview-customtools", + "name": "Gemini 3.1 Pro Preview Custom Tools", + "display_name": "Gemini 3.1 Pro Preview Custom Tools", "modalities": { "input": [ "text", "image", "video", - "audio" + "audio", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 262144, + "context": 1048576, "output": 65536 }, "temperature": true, @@ -268941,23 +269101,38 @@ }, "extra_capabilities": { "reasoning": { - "supported": true + "supported": true, + "default_enabled": true, + "mode": "level", + "level": "high", + "level_options": [ + "low", + "high" + ], + "summaries": true, + "visibility": "summary", + "continuation": [ + "thought_signatures" + ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-04-28", - "last_updated": "2026-04-28", + "open_weights": false, + "knowledge": "2025-01", + "release_date": "2026-02-19", + "last_updated": "2026-02-19", "type": "chat" }, { - "id": "anthropic/claude-fable-5", - "name": "Claude Fable 5", - "display_name": "Claude Fable 5", + "id": "google/gemini-3-flash-preview", + "name": "Gemini 3 Flash Preview", + "display_name": "Gemini 3 Flash Preview", "modalities": { "input": [ "text", "image", + "video", + "audio", "pdf" ], "output": [ @@ -268965,10 +269140,10 @@ ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 1048576, + "output": 65536 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -268978,146 +269153,166 @@ "reasoning": { "supported": true, "default_enabled": true, - "mode": "effort", - "effort": "high", - "effort_options": [ + "mode": "level", + "level": "high", + "level_options": [ + "minimal", "low", "medium", - "high", - "xhigh", - "max" + "high" ], - "interleaved": true, "summaries": true, - "visibility": "omitted", + "visibility": "summary", "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Adaptive thinking is always on for Claude Fable 5 and Claude Mythos 5; thinking.type = \"disabled\" is rejected.", - "Manual budget_tokens requests return 400 on Claude Fable 5 and Claude Mythos 5.", - "thinking.display defaults to omitted; set display to summarized to receive readable thinking summaries." + "thought_signatures" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-01-31", - "release_date": "2026-06-09", - "last_updated": "2026-06-09", + "knowledge": "2025-01", + "release_date": "2025-12-17", + "last_updated": "2025-12-17", "type": "chat" }, { - "id": "google/gemma-3-12b-it", - "name": "Gemma 3 12B IT", - "display_name": "Gemma 3 12B IT", + "id": "google/gemini-2.5-flash-image", + "name": "Nano Banana", + "display_name": "Nano Banana", "modalities": { "input": [ + "image", "text", - "image" + "pdf" ], "output": [ - "text" + "text", + "image" ] }, "limit": { - "context": 131072, - "output": 131072 + "context": 32768, + "output": 8192 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { "supported": false }, "attachment": true, - "open_weights": true, - "knowledge": "2024-08", - "release_date": "2025-03-12", - "last_updated": "2025-03-12", - "type": "chat" + "open_weights": false, + "knowledge": "2024-06", + "release_date": "2025-08-26", + "last_updated": "2025-08-26" }, { - "id": "google/gemma-4-E4B-it", - "name": "Gemma 4 E4B IT", - "display_name": "Gemma 4 E4B IT", + "id": "google/gemini-3.1-flash-image-preview", + "name": "Nano Banana 2 Preview", + "display_name": "Nano Banana 2 Preview", "modalities": { "input": [ "text", "image", - "audio" + "pdf" ], "output": [ - "text" + "text", + "image" ] }, "limit": { - "context": 131072, - "output": 8192 + "context": 65536, + "output": 58982 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { "supported": true, "default": true }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "effort_options": [ + "none", + "minimal", + "high" + ] + } + }, "attachment": true, - "open_weights": true, - "release_date": "2026-04-02", - "last_updated": "2026-04-02", - "type": "chat" + "open_weights": false, + "knowledge": "2025-01", + "release_date": "2026-02-26", + "last_updated": "2026-02-26" }, { - "id": "google/gemma-4-31B-it", - "name": "Gemma 4 31B IT", - "display_name": "Gemma 4 31B IT", + "id": "google/gemini-3-pro-image-preview", + "name": "Nano Banana Pro Preview", + "display_name": "Nano Banana Pro Preview", "modalities": { "input": [ - "text", "image", - "video" + "text", + "pdf" ], "output": [ - "text" + "text", + "image" ] }, "limit": { - "context": 262144, + "context": 65536, "output": 32768 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { "supported": true, "default": true }, "extra_capabilities": { "reasoning": { - "supported": true + "supported": true, + "default_enabled": true, + "mode": "level", + "level": "high", + "level_options": [ + "low", + "high" + ], + "summaries": true, + "visibility": "summary", + "continuation": [ + "thought_signatures" + ] } }, "attachment": true, - "open_weights": true, + "open_weights": false, "knowledge": "2025-01", - "release_date": "2026-04-02", - "last_updated": "2026-04-02", - "type": "chat" + "release_date": "2025-11-20", + "last_updated": "2025-11-20" }, { - "id": "google/gemma-4-26B-A4B-it", - "name": "Gemma 4 26B A4B IT", - "display_name": "Gemma 4 26B A4B IT", + "id": "google/gemini-3.5-flash-lite", + "name": "Gemini 3.5 Flash Lite", + "display_name": "Gemini 3.5 Flash Lite", "modalities": { "input": [ "text", - "image" + "image", + "video", + "audio", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 32768 + "context": 1048576, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -269127,52 +269322,58 @@ }, "extra_capabilities": { "reasoning": { - "supported": true + "supported": true, + "effort_options": [ + "minimal", + "low", + "medium", + "high" + ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-04-02", - "last_updated": "2026-04-02", + "open_weights": false, + "knowledge": "2026-03", + "release_date": "2026-07-21", + "last_updated": "2026-07-21", "type": "chat" }, { - "id": "google/gemma-3-4b-it", - "name": "Gemma 3 4B IT", - "display_name": "Gemma 3 4B IT", + "id": "gryphe/mythomax-l2-13b", + "name": "MythoMax 13B", + "display_name": "MythoMax 13B", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 131072 + "context": 4096, + "output": 3686 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { "supported": false }, - "attachment": true, + "attachment": false, "open_weights": true, - "knowledge": "2024-08", - "release_date": "2025-03-12", - "last_updated": "2025-03-12", + "release_date": "2023-08-10", + "last_updated": "2023-08-10", "type": "chat" }, { - "id": "google/gemma-3-27b-it", - "name": "Gemma 3 27B IT", - "display_name": "Gemma 3 27B IT", + "id": "mistralai/mistral-medium-3.1", + "name": "Mistral Medium 3.1", + "display_name": "Mistral Medium 3.1", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" @@ -269180,7 +269381,7 @@ }, "limit": { "context": 131072, - "output": 131072 + "output": 104857 }, "temperature": true, "tool_call": true, @@ -269188,87 +269389,87 @@ "supported": false }, "attachment": true, - "open_weights": true, - "knowledge": "2024-08", - "release_date": "2025-03-12", - "last_updated": "2025-03-12", + "open_weights": false, + "knowledge": "2025-06-30", + "release_date": "2025-08-12", + "last_updated": "2025-08-12", "type": "chat" }, { - "id": "thinkingmachines/Inkling", - "name": "Inkling", - "display_name": "Inkling", + "id": "mistralai/mistral-large-2512", + "name": "Mistral Large 3", + "display_name": "Mistral Large 3", "modalities": { "input": [ "text", "image", - "audio" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 524288, - "output": 1048576 + "context": 262144, + "output": 209715 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true + "supported": false }, "attachment": true, "open_weights": true, - "release_date": "2026-07-15", - "last_updated": "2026-07-15", + "knowledge": "2024-11", + "release_date": "2025-12-02", + "last_updated": "2025-12-02", "type": "chat" }, { - "id": "thinkingmachines/Inkling-Small", - "name": "Inkling Small", - "display_name": "Inkling Small", + "id": "mistralai/mistral-large-2407", + "name": "Mistral Large 2407", + "display_name": "Mistral Large 2407", "modalities": { "input": [ "text", - "image", - "audio" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 524288, - "output": 1048576 + "context": 131072, + "output": 104857 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true + "supported": false }, "attachment": true, "open_weights": true, - "release_date": "2026-07-30", - "last_updated": "2026-07-30", + "knowledge": "2024-03-31", + "release_date": "2024-07-24", + "last_updated": "2024-07-24", "type": "chat" }, { - "id": "openai/gpt-oss-20b", - "name": "GPT OSS 20B", - "display_name": "GPT OSS 20B", + "id": "mistralai/mistral-large-4-0", + "name": "Mistral: Mistral Large 4", + "display_name": "Mistral: Mistral Large 4", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 16384 + "context": 1048576, + "output": 262144 }, "temperature": true, "tool_call": true, @@ -269278,374 +269479,279 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, "effort_options": [ - "low", - "medium", + "none", "high" ] } }, - "attachment": false, - "open_weights": true, - "knowledge": "2024-06", - "release_date": "2025-08-05", - "last_updated": "2025-08-05", + "attachment": true, + "open_weights": false, + "release_date": "2026-10-06", + "last_updated": "2026-10-06", "type": "chat" }, { - "id": "openai/gpt-oss-120b", - "name": "GPT OSS 120B", - "display_name": "GPT OSS 120B", + "id": "mistralai/ministral-14b-2512", + "name": "Ministral 3 14B 2512", + "display_name": "Ministral 3 14B 2512", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 16384 + "context": 262144, + "output": 209715 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "effort_options": [ - "low", - "medium", - "high" - ] - } + "supported": false }, - "attachment": false, + "attachment": true, "open_weights": true, - "knowledge": "2024-06", - "release_date": "2025-08-05", - "last_updated": "2025-08-05", + "release_date": "2025-12-02", + "last_updated": "2025-12-02", "type": "chat" }, { - "id": "tencent/Hy3", - "name": "Hy3", - "display_name": "Hy3", + "id": "mistralai/codestral-2508", + "name": "Codestral 25.08", + "display_name": "Codestral 25.08", "modalities": { "input": [ - "text" + "text", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 128000 + "context": 256000, + "output": 204800 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true - } + "supported": false }, - "attachment": false, - "open_weights": true, - "release_date": "2026-07-06", - "last_updated": "2026-07-06", + "attachment": true, + "open_weights": false, + "release_date": "2025-07-30", + "last_updated": "2025-07-30", "type": "chat" }, { - "id": "tencent/Hy4-preview", - "name": "Hy4 preview", - "display_name": "Hy4 preview", + "id": "mistralai/mistral-saba", + "name": "Saba", + "display_name": "Saba", "modalities": { "input": [ - "text" + "text", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 64000 + "context": 32768, + "output": 26214 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "effort_options": [ - "none", - "high" - ] - } + "supported": false }, - "attachment": false, - "open_weights": true, - "release_date": "2026-08-28", - "last_updated": "2026-08-28", + "attachment": true, + "open_weights": false, + "knowledge": "2024-09-30", + "release_date": "2025-02-17", + "last_updated": "2025-02-17", "type": "chat" }, { - "id": "MiniMaxAI/MiniMax-M3", - "name": "MiniMax-M3", - "display_name": "MiniMax-M3", + "id": "mistralai/ministral-8b-2512", + "name": "Ministral 3 8B 2512", + "display_name": "Ministral 3 8B 2512", "modalities": { "input": [ "text", - "image", - "video" + "image" ], "output": [ "text" ] }, "limit": { - "context": 524288, - "output": 512000 + "context": 262144, + "output": 209715 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true - } + "supported": false }, "attachment": true, "open_weights": true, - "release_date": "2026-06-01", - "last_updated": "2026-06-01", + "release_date": "2025-12-02", + "last_updated": "2025-12-02", "type": "chat" }, { - "id": "MiniMaxAI/MiniMax-M2.7", - "name": "MiniMax-M2.7", - "display_name": "MiniMax-M2.7", + "id": "mistralai/ministral-3b-2512", + "name": "Ministral 3 3B 2512", + "display_name": "Ministral 3 3B 2512", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 196608, - "output": 131072 + "context": 131072, + "output": 104857 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } + "supported": false }, - "attachment": false, + "attachment": true, "open_weights": true, - "release_date": "2026-03-18", - "last_updated": "2026-03-18", + "release_date": "2025-12-02", + "last_updated": "2025-12-02", "type": "chat" }, { - "id": "MiniMaxAI/MiniMax-M2.5", - "name": "MiniMax M2.5", - "display_name": "MiniMax M2.5", + "id": "mistralai/mixtral-8x22b-instruct", + "name": "Mixtral 8x22B Instruct", + "display_name": "Mixtral 8x22B Instruct", "modalities": { "input": [ - "text" + "text", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 196608, - "output": 131072 + "context": 65536, + "output": 52428 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } + "supported": false }, - "attachment": false, + "attachment": true, "open_weights": true, - "knowledge": "2025-06", - "release_date": "2026-02-12", - "last_updated": "2026-02-12", + "knowledge": "2024-01-31", + "release_date": "2024-04-17", + "last_updated": "2024-04-17", "type": "chat" }, { - "id": "moonshotai/Kimi-K2.5", - "name": "Kimi K2.5", - "display_name": "Kimi K2.5", + "id": "mistralai/mistral-medium-3", + "name": "Mistral Medium 3", + "display_name": "Mistral Medium 3", "modalities": { "input": [ "text", "image", - "video" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 32768 + "context": 131072, + "output": 104857 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } + "supported": false }, "attachment": true, - "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-01-27", - "last_updated": "2026-01-27", + "open_weights": false, + "knowledge": "2025-03-31", + "release_date": "2025-05-07", + "last_updated": "2025-05-07", "type": "chat" }, { - "id": "moonshotai/Kimi-K2.7-Code", - "name": "Kimi K2.7 Code", - "display_name": "Kimi K2.7 Code", + "id": "mistralai/voxtral-small-24b-2507", + "name": "Voxtral Small 24B 2507", + "display_name": "Voxtral Small 24B 2507", "modalities": { "input": [ "text", - "image", - "video" + "audio", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 32768, + "output": 26214 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } + "supported": false }, "attachment": true, "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-06-12", - "last_updated": "2026-06-12", + "release_date": "2025-07-15", + "last_updated": "2025-07-15", "type": "chat" }, { - "id": "moonshotai/Kimi-K2.6", - "name": "Kimi K2.6", - "display_name": "Kimi K2.6", + "id": "mistralai/mistral-nemo", + "name": "Mistral Nemo", + "display_name": "Mistral Nemo", "modalities": { "input": [ - "text", - "image", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 262144, + "context": 131072, "output": 16384 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } + "supported": false }, - "attachment": true, + "attachment": false, "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-04-20", - "last_updated": "2026-04-20", + "knowledge": "2024-07", + "release_date": "2024-07-18", + "last_updated": "2024-07-18", "type": "chat" }, { - "id": "moonshotai/Kimi-K3", - "name": "Kimi K3", - "display_name": "Kimi K3", + "id": "mistralai/mistral-small-3.1-24b-instruct", + "name": "Mistral Small 3.1 24B", + "display_name": "Mistral Small 3.1 24B", "modalities": { "input": [ "text", @@ -269656,103 +269762,81 @@ ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 128000, + "output": 102400 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "effort_options": [ - "low", - "high", - "max" - ] - } + "supported": false }, "attachment": true, "open_weights": true, - "release_date": "2026-07-16", - "last_updated": "2026-07-16", + "knowledge": "2023-10-31", + "release_date": "2025-03-17", + "last_updated": "2025-03-17", "type": "chat" }, { - "id": "stepfun-ai/Step-5-Preview", - "name": "Step 5 Preview", - "display_name": "Step 5 Preview", + "id": "mistralai/mistral-large", + "name": "Mistral Large", + "display_name": "Mistral Large", "modalities": { "input": [ "text", - "image", - "video" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 1000000 + "context": 128000, + "output": 102400 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "low", - "medium", - "high" - ] - } + "supported": false }, "attachment": true, - "open_weights": true, - "release_date": "2026-09-16", - "last_updated": "2026-09-20", + "open_weights": false, + "knowledge": "2024-11-30", + "release_date": "2024-02-26", + "last_updated": "2024-02-26", "type": "chat" }, { - "id": "stepfun-ai/Step-3.7-Flash", - "name": "Step 3.7 Flash", - "display_name": "Step 3.7 Flash", + "id": "mistralai/mistral-small-3.2-24b-instruct", + "name": "Mistral Small 3.2 24B", + "display_name": "Mistral Small 3.2 24B", "modalities": { "input": [ - "text", "image", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 256000 + "context": 128000, + "output": 16384 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true + "supported": false }, "attachment": true, "open_weights": true, - "knowledge": "2026-03-01", - "release_date": "2026-05-29", - "last_updated": "2026-05-29", + "knowledge": "2023-10-31", + "release_date": "2025-06-20", + "last_updated": "2025-06-20", "type": "chat" }, { - "id": "mistralai/Mistral-Small-24B-Instruct-2501", + "id": "mistralai/mistral-small-24b-instruct-2501", "name": "Mistral Small 3", "display_name": "Mistral Small 3", "modalities": { @@ -269780,94 +269864,114 @@ "type": "chat" }, { - "id": "meta-llama/Llama-4-Maverick-17B-128E-Instruct-FP8", - "name": "Llama 4 Maverick 17B FP8", - "display_name": "Llama 4 Maverick 17B FP8", + "id": "mistralai/devstral-2512", + "name": "Devstral 2", + "display_name": "Devstral 2", "modalities": { "input": [ "text", - "image" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 16384 + "context": 262144, + "output": 209715 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { "supported": false }, "attachment": true, "open_weights": true, - "knowledge": "2024-08", - "release_date": "2025-04-05", - "last_updated": "2025-04-05", + "knowledge": "2025-12", + "release_date": "2025-12-09", + "last_updated": "2025-12-09", "type": "chat" }, { - "id": "meta-llama/Llama-Guard-4-12B", - "name": "Llama Guard 4 12B", - "display_name": "Llama Guard 4 12B", + "id": "mistralai/mistral-medium-3-5", + "name": "Mistral Medium 3.5", + "display_name": "Mistral Medium 3.5", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 163840, - "output": 163840 + "context": 262144, + "output": 209715 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "none", + "high" + ] + } }, "attachment": true, - "open_weights": true, - "release_date": "2025-04-29", - "last_updated": "2025-04-29", + "open_weights": false, + "release_date": "2026-04-30", + "last_updated": "2026-04-30", "type": "chat" }, { - "id": "meta-llama/Llama-3.3-70B-Instruct-Turbo", - "name": "Llama 3.3 70B Turbo", - "display_name": "Llama 3.3 70B Turbo", + "id": "mistralai/mistral-small-2603", + "name": "Mistral Small 4", + "display_name": "Mistral Small 4", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 16384 + "context": 262144, + "output": 209715 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "none", + "high" + ] + } + }, + "attachment": true, "open_weights": true, - "knowledge": "2023-12", - "release_date": "2024-12-06", - "last_updated": "2024-12-06", + "knowledge": "2025-06", + "release_date": "2026-03-16", + "last_updated": "2026-03-16", "type": "chat" }, { - "id": "meta-llama/Llama-4-Scout-17B-16E-Instruct", - "name": "Llama 4 Scout 17B", - "display_name": "Llama 4 Scout 17B", + "id": "particle-ai/particle-1", + "name": "Particle AI: Particle-1", + "display_name": "Particle AI: Particle-1", "modalities": { "input": [ "text", @@ -269878,37 +269982,49 @@ ] }, "limit": { - "context": 327680, - "output": 16384 + "context": 1048576, + "output": 131072 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "none", + "minimal", + "low", + "medium", + "high", + "xhigh", + "max" + ] + } }, "attachment": true, - "open_weights": true, - "knowledge": "2024-08", - "release_date": "2025-04-05", - "last_updated": "2025-04-05", + "open_weights": false, + "release_date": "2026-10-28", + "last_updated": "2026-10-28", "type": "chat" }, { - "id": "deepseek-ai/DeepSeek-V4.1-Flash", - "name": "DeepSeek V4.1 Flash", - "display_name": "DeepSeek V4.1 Flash", + "id": "xingchen-agi/xing4.0-29b-a4b:free", + "name": "XingChen: Xing4.0 29B A4B (free)", + "display_name": "XingChen: Xing4.0 29B A4B (free)", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 384000 + "context": 262144, + "output": 235929 }, "temperature": true, "tool_call": true, @@ -269918,65 +270034,71 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, "effort_options": [ "none", - "low", - "high", - "xhigh", - "max" + "high" ] } }, - "attachment": true, - "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-09-10", - "last_updated": "2026-09-10", + "attachment": false, + "open_weights": false, + "release_date": "2026-10-16", + "last_updated": "2026-10-16", "type": "chat" }, { - "id": "deepseek-ai/DeepSeek-V3-0324", - "name": "DeepSeek V3 0324", - "display_name": "DeepSeek V3 0324", + "id": "dots-studio/dots-3-note-preview:free", + "name": "Dots Studio: Dots3-Note Preview (free)", + "display_name": "Dots Studio: Dots3-Note Preview (free)", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 163840, - "output": 163840 + "context": 512000, + "output": 460800 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, - "open_weights": true, - "release_date": "2025-03-24", - "last_updated": "2025-03-24", + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "none", + "high" + ] + } + }, + "attachment": true, + "open_weights": false, + "release_date": "2026-08-14", + "last_updated": "2026-08-14", "type": "chat" }, { - "id": "deepseek-ai/DeepSeek-V4-Flash-0731", - "name": "DeepSeek V4 Flash 0731", - "display_name": "DeepSeek V4 Flash 0731", + "id": "prism-ml/ternary-bonsai-2-27b", + "name": "PrismML: Ternary Bonsai 2 27B", + "display_name": "PrismML: Ternary Bonsai 2 27B", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 384000 + "context": 262144, + "output": 32768 }, "temperature": true, "tool_call": true, @@ -269986,36 +270108,35 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, "effort_options": [ - "low", - "high", - "max" + "none", + "medium", + "xhigh" ] } }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-07-31", - "last_updated": "2026-07-31", + "attachment": true, + "open_weights": false, + "release_date": "2026-09-18", + "last_updated": "2026-09-18", "type": "chat" }, { - "id": "deepseek-ai/DeepSeek-V4-Pro-0813", - "name": "DeepSeek V4 Pro 0813", - "display_name": "DeepSeek V4 Pro 0813", + "id": "prism-ml/ternary-bonsai-2-9b", + "name": "PrismML: Ternary Bonsai 2 9B", + "display_name": "PrismML: Ternary Bonsai 2 9B", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 384000 + "context": 262144, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -270025,24 +270146,22 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, "effort_options": [ - "low", - "high", - "max" + "none", + "high" ] } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-08-13", - "last_updated": "2026-08-22", + "attachment": true, + "open_weights": false, + "release_date": "2026-10-16", + "last_updated": "2026-10-16", "type": "chat" }, { - "id": "deepseek-ai/DeepSeek-V4-Pro", - "name": "DeepSeek V4 Pro", - "display_name": "DeepSeek V4 Pro", + "id": "aion-labs/aion-2.0", + "name": "AionLabs: Aion-2.0", + "display_name": "AionLabs: Aion-2.0", "modalities": { "input": [ "text" @@ -270052,8 +270171,8 @@ ] }, "limit": { - "context": 1048576, - "output": 16384 + "context": 131072, + "output": 32768 }, "temperature": true, "tool_call": true, @@ -270063,37 +270182,21 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "high", "effort_options": [ - "low", - "high", - "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." + "high" ] } }, "attachment": false, - "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-04-24", - "last_updated": "2026-04-24", + "open_weights": false, + "release_date": "2025-12-21", + "last_updated": "2025-12-21", "type": "chat" }, { - "id": "deepseek-ai/DeepSeek-V3.2", - "name": "DeepSeek-V3.2", - "display_name": "DeepSeek-V3.2", + "id": "aion-labs/aion-3.0", + "name": "AionLabs: Aion-3.0", + "display_name": "AionLabs: Aion-3.0", "modalities": { "input": [ "text" @@ -270103,8 +270206,8 @@ ] }, "limit": { - "context": 163840, - "output": 64000 + "context": 131072, + "output": 32768 }, "temperature": true, "tool_call": true, @@ -270114,26 +270217,21 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "high" ] } }, "attachment": false, - "open_weights": true, - "knowledge": "2024-07", - "release_date": "2025-12-01", - "last_updated": "2025-12-01", + "open_weights": false, + "release_date": "2026-05-05", + "last_updated": "2026-05-05", "type": "chat" }, { - "id": "deepseek-ai/DeepSeek-V3.1", - "name": "DeepSeek-V3.1", - "display_name": "DeepSeek-V3.1", + "id": "aion-labs/aion-3.5-mini", + "name": "AionLabs: Aion 3.5 Mini", + "display_name": "AionLabs: Aion 3.5 Mini", "modalities": { "input": [ "text" @@ -270143,8 +270241,8 @@ ] }, "limit": { - "context": 163840, - "output": 8192 + "context": 262144, + "output": 32768 }, "temperature": true, "tool_call": true, @@ -270152,28 +270250,36 @@ "supported": true, "default": true }, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "low", + "high", + "max" + ] + } + }, "attachment": false, - "open_weights": true, - "release_date": "2025-08-21", - "last_updated": "2025-08-21", + "open_weights": false, + "release_date": "2026-09-23", + "last_updated": "2026-09-23", "type": "chat" }, { - "id": "deepseek-ai/DeepSeek-V4-Flash-Vision-Exp", - "name": "DeepSeek V4 Flash Vision Exp", - "display_name": "DeepSeek V4 Flash Vision Exp", + "id": "aion-labs/aion-3.0-mini", + "name": "AionLabs: Aion-3.0-Mini", + "display_name": "AionLabs: Aion-3.0-Mini", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 384000 + "context": 131072, + "output": 32768 }, "temperature": true, "tool_call": true, @@ -270183,48 +270289,32 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "high", "effort_options": [ - "low", - "high", - "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." + "high" ] } }, - "attachment": true, - "open_weights": true, - "release_date": "2026-08-21", - "last_updated": "2026-09-01", + "attachment": false, + "open_weights": false, + "release_date": "2026-05-14", + "last_updated": "2026-05-14", "type": "chat" }, { - "id": "deepseek-ai/DeepSeek-V4.1-Flash-1017", - "name": "DeepSeek V4.1 Flash 1017", - "display_name": "DeepSeek V4.1 Flash 1017", + "id": "aion-labs/aion-3.5", + "name": "AionLabs: Aion 3.5", + "display_name": "AionLabs: Aion 3.5", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 384000 + "context": 262144, + "output": 32768 }, "temperature": true, "tool_call": true, @@ -270235,24 +270325,50 @@ "extra_capabilities": { "reasoning": { "effort_options": [ - "none", "low", "high", "max" ] } }, - "attachment": true, + "attachment": false, + "open_weights": false, + "release_date": "2026-09-23", + "last_updated": "2026-09-23", + "type": "chat" + }, + { + "id": "aion-labs/aion-rp-llama-3.1-8b", + "name": "AionLabs: Aion-RP 1.0 (8B)", + "display_name": "AionLabs: Aion-RP 1.0 (8B)", + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 32768, + "output": 29491 + }, + "temperature": true, + "tool_call": false, + "reasoning": { + "supported": false + }, + "attachment": false, "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-10-17", - "last_updated": "2026-10-17", + "knowledge": "2023-12-31", + "release_date": "2025-02-04", + "last_updated": "2025-02-04", "type": "chat" }, { - "id": "deepseek-ai/DeepSeek-V4-Flash", - "name": "DeepSeek V4 Flash", - "display_name": "DeepSeek V4 Flash", + "id": "liquid/lfm-2.5-2.6b:free", + "name": "LiquidAI: LFM2.5-2.6B (free)", + "display_name": "LiquidAI: LFM2.5-2.6B (free)", "modalities": { "input": [ "text" @@ -270262,8 +270378,8 @@ ] }, "limit": { - "context": 1048576, - "output": 16384 + "context": 65536, + "output": 8192 }, "temperature": true, "tool_call": true, @@ -270273,37 +270389,21 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "high", "effort_options": [ - "low", - "high", - "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." + "high" ] } }, "attachment": false, - "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-04-24", - "last_updated": "2026-04-24", + "open_weights": false, + "release_date": "2026-08-11", + "last_updated": "2026-08-11", "type": "chat" }, { - "id": "deepseek-ai/DeepSeek-R1-0528", - "name": "DeepSeek-R1-0528", - "display_name": "DeepSeek-R1-0528", + "id": "liquid/lfm-2.5-2.6b", + "name": "LiquidAI: LFM2.5-2.6B", + "display_name": "LiquidAI: LFM2.5-2.6B", "modalities": { "input": [ "text" @@ -270313,8 +270413,8 @@ ] }, "limit": { - "context": 163840, - "output": 64000 + "context": 128000, + "output": 8192 }, "temperature": true, "tool_call": true, @@ -270325,25 +270425,21 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "high" ] } }, "attachment": false, - "open_weights": true, - "knowledge": "2024-07", - "release_date": "2025-05-28", - "last_updated": "2025-05-28", + "open_weights": false, + "release_date": "2026-08-11", + "last_updated": "2026-08-11", "type": "chat" }, { - "id": "deepseek-ai/DeepSeek-V3", - "name": "DeepSeek-V3", - "display_name": "DeepSeek-V3", + "id": "liquid/lfm-2.5-24b-a2b", + "name": "LiquidAI: LFM2.5-24B-A2B", + "display_name": "LiquidAI: LFM2.5-24B-A2B", "modalities": { "input": [ "text" @@ -270353,118 +270449,106 @@ ] }, "limit": { - "context": 163840, + "context": 128000, "output": 8192 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "high" + ] + } }, "attachment": false, - "open_weights": true, - "release_date": "2024-12-26", - "last_updated": "2024-12-26", + "open_weights": false, + "release_date": "2026-10-13", + "last_updated": "2026-10-13", "type": "chat" - } - ] - }, - "arcee": { - "id": "arcee", - "name": "Arcee", - "display_name": "Arcee", - "api": "https://api.arcee.ai/api/v1", - "doc": "https://docs.arcee.ai", - "models": [ + }, { - "id": "trinity-large-thinking", - "name": "Trinity Large Thinking", - "display_name": "Trinity Large Thinking", + "id": "moondream/moondream3.1-9b-a2b", + "name": "Moondream: Moondream 3.1", + "display_name": "Moondream: Moondream 3.1", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 80000 + "context": 32000, + "output": 4096 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { "supported": true, "default": true }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "none", + "high" ] } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-04-01", - "last_updated": "2026-05-28", + "attachment": true, + "open_weights": false, + "release_date": "2026-10-15", + "last_updated": "2026-10-15", "type": "chat" }, { - "id": "zai-org/glm-5.3-flash", - "name": "GLM-5.3-Flash", - "display_name": "GLM-5.3-Flash", + "id": "moondream/moondream3-preview", + "name": "Moondream: Moondream 3 Preview", + "display_name": "Moondream: Moondream 3 Preview", "modalities": { "input": [ "text", - "image", - "video", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 32000, + "output": 4096 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { "supported": true, "default": true }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], "effort_options": [ - "high", - "max" + "none", + "high" ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-08-26", - "last_updated": "2026-08-26", + "open_weights": false, + "release_date": "2026-10-15", + "last_updated": "2026-10-15", "type": "chat" }, { - "id": "zai-org/glm-5.3", - "name": "GLM-5.3", - "display_name": "GLM-5.3", + "id": "microsoft/phi-4", + "name": "Microsoft: Phi 4", + "display_name": "Microsoft: Phi 4", "modalities": { "input": [ "text" @@ -270474,53 +270558,91 @@ ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 16384, + "output": 14745 + }, + "temperature": true, + "tool_call": false, + "reasoning": { + "supported": false + }, + "attachment": false, + "open_weights": false, + "release_date": "2025-01-10", + "last_updated": "2025-01-10", + "type": "chat" + }, + { + "id": "anthropic/claude-haiku-4.5", + "name": "Claude Haiku 4.5 (latest)", + "display_name": "Claude Haiku 4.5 (latest)", + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 200000, + "output": 64000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": false, + "mode": "budget", + "budget": { + "min": 1024, + "unit": "tokens" + }, "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ "thinking_blocks" ], - "effort_options": [ - "high", - "max" + "notes": [ + "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", + "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." ] } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-08-14", - "last_updated": "2026-08-14", + "attachment": true, + "open_weights": false, + "knowledge": "2025-02-28", + "release_date": "2025-10-15", + "last_updated": "2025-10-15", "type": "chat" }, { - "id": "zai-org/glm-5.2", - "name": "GLM-5.2", - "display_name": "GLM-5.2", + "id": "anthropic/claude-fable-5.1", + "name": "Claude Fable 5.1", + "display_name": "Claude Fable 5.1", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 131072 + "context": 1000000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -270529,76 +270651,97 @@ "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "high", + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" + ], "interleaved": true, "summaries": true, - "visibility": "summary", + "visibility": "omitted", "continuation": [ "thinking_blocks" ], - "effort_options": [ - "high", - "max" + "notes": [ + "Adaptive thinking is always on for Claude Fable 5 and Claude Mythos 5; thinking.type = \"disabled\" is rejected.", + "Manual budget_tokens requests return 400 on Claude Fable 5 and Claude Mythos 5.", + "thinking.display defaults to omitted; set display to summarized to receive readable thinking summaries." ] } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-06-13", - "last_updated": "2026-06-13", + "attachment": true, + "open_weights": false, + "knowledge": "2026-06", + "release_date": "2026-09-01", + "last_updated": "2026-09-01", "type": "chat" }, { - "id": "deepseek/deepseek-v4-flash-latest", - "name": "DeepSeek V4 Flash Latest", - "display_name": "DeepSeek V4 Flash Latest", + "id": "anthropic/claude-sonnet-4.5", + "name": "Claude Sonnet 4.5 (latest)", + "display_name": "Claude Sonnet 4.5 (latest)", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 384000 + "context": 1000000, + "output": 64000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": false, + "mode": "budget", + "budget": { + "min": 1024, + "unit": "tokens" + }, "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ "thinking_blocks" ], - "effort_options": [ - "high", - "max" + "notes": [ + "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", + "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." ] } }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-07-31", - "last_updated": "2026-07-31", + "attachment": true, + "open_weights": false, + "knowledge": "2025-07-31", + "release_date": "2025-09-29", + "last_updated": "2025-09-29", "type": "chat" }, { - "id": "deepseek/deepseek-v4.1-flash-1017", - "name": "DeepSeek V4.1 Flash 1017", - "display_name": "DeepSeek V4.1 Flash 1017", + "id": "anthropic/claude-sonnet-5", + "name": "Claude Sonnet 5", + "display_name": "Claude Sonnet 5", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" @@ -270606,9 +270749,9 @@ }, "limit": { "context": 1000000, - "output": 384000 + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -270617,42 +270760,42 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], "effort_options": [ + "none", + "low", + "medium", "high", + "xhigh", "max" ] } }, "attachment": true, - "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-10-17", - "last_updated": "2026-10-17", + "open_weights": false, + "knowledge": "2026-01-31", + "release_date": "2026-06-30", + "last_updated": "2026-06-30", "type": "chat" }, - { - "id": "deepseek/deepseek-v4-pro", - "name": "DeepSeek V4 Pro", - "display_name": "DeepSeek V4 Pro", + { + "id": "anthropic/claude-fable-5", + "name": "Claude Fable 5", + "display_name": "Claude Fable 5", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 512000, - "output": 384000 + "context": 1000000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -270666,35 +270809,40 @@ "effort": "high", "effort_options": [ "low", + "medium", "high", + "xhigh", "max" ], "interleaved": true, "summaries": true, - "visibility": "summary", + "visibility": "omitted", "continuation": [ "thinking_blocks" ], "notes": [ - "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." + "Adaptive thinking is always on for Claude Fable 5 and Claude Mythos 5; thinking.type = \"disabled\" is rejected.", + "Manual budget_tokens requests return 400 on Claude Fable 5 and Claude Mythos 5.", + "thinking.display defaults to omitted; set display to summarized to receive readable thinking summaries." ] } }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-04-24", - "last_updated": "2026-04-24", + "attachment": true, + "open_weights": false, + "knowledge": "2026-01-31", + "release_date": "2026-06-09", + "last_updated": "2026-06-09", "type": "chat" }, { - "id": "deepseek/deepseek-v4.1-flash", - "name": "DeepSeek V4.1 Flash", - "display_name": "DeepSeek V4.1 Flash", + "id": "anthropic/claude-haiku-5.5", + "name": "Claude Haiku 5.5", + "display_name": "Claude Haiku 5.5", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" @@ -270702,9 +270850,9 @@ }, "limit": { "context": 1000000, - "output": 384000 + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -270712,125 +270860,159 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], "effort_options": [ + "none", + "low", + "medium", "high", + "xhigh", "max" ] } }, "attachment": true, - "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-09-10", - "last_updated": "2026-09-10", + "open_weights": false, + "knowledge": "2026-06", + "release_date": "2026-10-07", + "last_updated": "2026-10-07", "type": "chat" }, { - "id": "deepseek/deepseek-v4-pro-0813", - "name": "DeepSeek V4 Pro 0813", - "display_name": "DeepSeek V4 Pro 0813", + "id": "anthropic/claude-opus-4.5", + "name": "Claude Opus 4.5 (latest)", + "display_name": "Claude Opus 4.5 (latest)", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 384000 + "context": 200000, + "output": 64000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": false, + "mode": "mixed", + "budget": { + "min": 1024, + "unit": "tokens" + }, + "effort": "high", + "effort_options": [ + "low", + "medium", + "high" + ], "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ "thinking_blocks" ], - "effort_options": [ - "high", - "max" + "notes": [ + "Claude Opus 4.5 uses manual thinking.type = \"enabled\" with budget_tokens; effort can be used alongside the thinking budget.", + "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header." ] } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-08-13", - "last_updated": "2026-08-22", + "attachment": true, + "open_weights": false, + "knowledge": "2025-05", + "release_date": "2025-11-24", + "last_updated": "2025-11-24", "type": "chat" }, { - "id": "thinkingmachines/inkling-small", - "name": "Inkling Small", - "display_name": "Inkling Small", + "id": "anthropic/claude-opus-4.6", + "name": "Claude Opus 4.6", + "display_name": "Claude Opus 4.6", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 1000000, + "output": 128000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": false, + "mode": "mixed", + "budget": { + "min": 1024, + "unit": "tokens" + }, + "effort": "high", + "effort_options": [ + "low", + "medium", + "high", + "max" + ], "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ "thinking_blocks" + ], + "notes": [ + "Anthropic recommends adaptive thinking with effort for Claude 4.6; budget_tokens remains a deprecated compatibility path.", + "Anthropic API defaults effort to high; lower effort levels should be chosen per workload." ] } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-07-30", - "last_updated": "2026-07-30", + "attachment": true, + "open_weights": false, + "knowledge": "2025-05-31", + "release_date": "2026-02-05", + "last_updated": "2026-03-13", "type": "chat" }, { - "id": "moonshotai/kimi-k3", - "name": "Kimi K3", - "display_name": "Kimi K3", + "id": "anthropic/claude-sonnet-5.5", + "name": "Claude Sonnet 5.5", + "display_name": "Claude Sonnet 5.5", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 1000000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -270838,120 +271020,144 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], "effort_options": [ "low", + "medium", "high", + "xhigh", "max" ] } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-07-16", - "last_updated": "2026-07-16", + "attachment": true, + "open_weights": false, + "knowledge": "2026-06", + "release_date": "2026-09-28", + "last_updated": "2026-09-28", "type": "chat" - } - ] - }, - "tensorx": { - "id": "tensorx", - "name": "TensorX", - "display_name": "TensorX", - "api": "https://api.tensorx.ai/v1", - "doc": "https://docs.tensorx.ai/", - "models": [ + }, { - "id": "minimax/minimax-m3", - "name": "MiniMax-M3", - "display_name": "MiniMax-M3", + "id": "anthropic/claude-opus-4.7", + "name": "Claude Opus 4.7", + "display_name": "Claude Opus 4.7", "modalities": { "input": [ "text", "image", - "video" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 1000000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { - "supported": true + "supported": true, + "default_enabled": false, + "mode": "effort", + "effort": "high", + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "omitted", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Claude Opus 4.7 and newer Opus models require thinking.type = \"adaptive\" to enable thinking explicitly.", + "Manual budget_tokens requests return 400 on Claude Opus 4.7 and newer adaptive-only Opus models.", + "task_budget is separate from thinking control and should not be treated as a thinking budget." + ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-06-01", - "last_updated": "2026-06-01", + "open_weights": false, + "knowledge": "2026-01-31", + "release_date": "2026-04-16", + "last_updated": "2026-04-16", "type": "chat" }, { - "id": "minimax/minimax-m2.5", - "name": "MiniMax-M2.5", - "display_name": "MiniMax-M2.5", + "id": "anthropic/claude-opus-4.8", + "name": "Claude Opus 4.8", + "display_name": "Claude Opus 4.8", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 196608, - "output": 65536 + "context": 1000000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": false, + "mode": "effort", + "effort": "high", "effort_options": [ - "minimal", "low", "medium", "high", "xhigh", "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "omitted", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Claude Opus 4.7 and newer Opus models require thinking.type = \"adaptive\" to enable thinking explicitly.", + "Manual budget_tokens requests return 400 on Claude Opus 4.7 and newer adaptive-only Opus models.", + "task_budget is separate from thinking control and should not be treated as a thinking budget." ] } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-02-12", - "last_updated": "2026-02-12", + "attachment": true, + "open_weights": false, + "knowledge": "2026-01", + "release_date": "2026-05-28", + "last_updated": "2026-05-28", "type": "chat" }, { - "id": "z-ai/glm-5.3-flash", - "name": "GLM-5.3-Flash", - "display_name": "GLM-5.3-Flash", + "id": "anthropic/claude-opus-4.1", + "name": "Claude Opus 4.1 (latest)", + "display_name": "Claude Opus 4.1 (latest)", "modalities": { "input": [ "text", "image", - "video", "pdf" ], "output": [ @@ -270959,40 +271165,51 @@ ] }, "limit": { - "context": 1048576, - "output": 64000 + "context": 200000, + "output": 32000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, - "effort_options": [ - "low", - "high", - "max" + "default_enabled": false, + "mode": "budget", + "budget": { + "min": 1024, + "unit": "tokens" + }, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", + "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-08-26", - "last_updated": "2026-08-26", + "open_weights": false, + "knowledge": "2025-03-31", + "release_date": "2025-08-05", + "last_updated": "2025-08-05", "type": "chat" }, { - "id": "z-ai/glm-5v-turbo", - "name": "GLM-5V-Turbo", - "display_name": "GLM-5V-Turbo", + "id": "anthropic/claude-opus-5.5", + "name": "Claude Opus 5.5", + "display_name": "Claude Opus 5.5", "modalities": { "input": [ "text", "image", - "video", "pdf" ], "output": [ @@ -271000,10 +271217,10 @@ ] }, "limit": { - "context": 202752, - "output": 131072 + "context": 1000000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -271012,8 +271229,6 @@ "extra_capabilities": { "reasoning": { "effort_options": [ - "none", - "minimal", "low", "medium", "high", @@ -271024,60 +271239,89 @@ }, "attachment": true, "open_weights": false, - "release_date": "2026-04-01", - "last_updated": "2026-04-01", + "knowledge": "2026-06", + "release_date": "2026-09-22", + "last_updated": "2026-09-22", "type": "chat" }, { - "id": "z-ai/glm-5-turbo", - "name": "GLM-5-Turbo", - "display_name": "GLM-5-Turbo", + "id": "anthropic/claude-sonnet-4.6", + "name": "Claude Sonnet 4.6", + "display_name": "Claude Sonnet 4.6", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 202752, - "output": 131072 + "context": 1000000, + "output": 128000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { - "supported": true + "supported": true, + "default_enabled": false, + "mode": "mixed", + "budget": { + "min": 1024, + "unit": "tokens" + }, + "effort": "high", + "effort_options": [ + "low", + "medium", + "high", + "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Anthropic recommends adaptive thinking with effort for Claude 4.6; budget_tokens remains a deprecated compatibility path.", + "Anthropic API defaults effort to high; lower effort levels should be chosen per workload." + ] } }, - "attachment": false, + "attachment": true, "open_weights": false, - "release_date": "2026-03-16", - "last_updated": "2026-03-16", + "knowledge": "2025-08-31", + "release_date": "2026-02-17", + "last_updated": "2026-03-13", "type": "chat" }, { - "id": "z-ai/glm-5.1", - "name": "GLM-5.1", - "display_name": "GLM-5.1", + "id": "anthropic/claude-opus-5", + "name": "Claude Opus 5", + "display_name": "Claude Opus 5", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 202752, - "output": 202752 + "context": 1000000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -271085,19 +271329,28 @@ }, "extra_capabilities": { "reasoning": { - "supported": true + "supported": true, + "effort_options": [ + "none", + "low", + "medium", + "high", + "xhigh", + "max" + ] } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-04-07", - "last_updated": "2026-04-07", + "attachment": true, + "open_weights": false, + "knowledge": "2026-05", + "release_date": "2026-07-24", + "last_updated": "2026-07-24", "type": "chat" }, { - "id": "z-ai/glm-5", - "name": "GLM-5", - "display_name": "GLM-5", + "id": "amazon/nova-micro-v1", + "name": "Amazon: Nova Micro 1.0", + "display_name": "Amazon: Nova Micro 1.0", "modalities": { "input": [ "text" @@ -271107,94 +271360,68 @@ ] }, "limit": { - "context": 202752, - "output": 202752 + "context": 128000, + "output": 5120 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "effort_options": [ - "none", - "minimal", - "low", - "medium", - "high", - "xhigh", - "max" - ] - } + "supported": false }, "attachment": false, - "open_weights": true, - "release_date": "2026-02-12", - "last_updated": "2026-02-12", + "open_weights": false, + "knowledge": "2024-10-31", + "release_date": "2024-12-05", + "last_updated": "2024-12-05", "type": "chat" }, { - "id": "z-ai/glm-5.3", - "name": "GLM-5.3", - "display_name": "GLM-5.3", + "id": "amazon/nova-lite-v1", + "name": "Amazon: Nova Lite 1.0", + "display_name": "Amazon: Nova Lite 1.0", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 64000 + "context": 300000, + "output": 5120 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "effort_options": [ - "low", - "high", - "max" - ] - } + "supported": false }, - "attachment": false, - "open_weights": true, - "release_date": "2026-08-14", - "last_updated": "2026-08-14", + "attachment": true, + "open_weights": false, + "knowledge": "2024-10-31", + "release_date": "2024-12-05", + "last_updated": "2024-12-05", "type": "chat" }, { - "id": "z-ai/glm-5.2", - "name": "GLM-5.2", - "display_name": "GLM-5.2", + "id": "amazon/nova-2-lite-v1", + "name": "Amazon: Nova 2 Lite", + "display_name": "Amazon: Nova 2 Lite", "modalities": { "input": [ - "text" + "text", + "image", + "video", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 1000000, + "output": 65535 }, "temperature": true, "tool_call": true, @@ -271204,134 +271431,94 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, "effort_options": [ - "high", - "max" + "none", + "high" ] } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-06-13", - "last_updated": "2026-06-13", + "attachment": true, + "open_weights": false, + "knowledge": "2025-10", + "release_date": "2025-12-02", + "last_updated": "2025-12-02", "type": "chat" }, { - "id": "deepseek/deepseek-v4-pro", - "name": "DeepSeek V4 Pro", - "display_name": "DeepSeek V4 Pro", + "id": "amazon/nova-pro-v1", + "name": "Amazon: Nova Pro 1.0", + "display_name": "Amazon: Nova Pro 1.0", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 384000 + "context": 300000, + "output": 5120 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "high", - "effort_options": [ - "low", - "high", - "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." - ] - } + "supported": false }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-04-24", - "last_updated": "2026-04-24", + "attachment": true, + "open_weights": false, + "knowledge": "2024-10-31", + "release_date": "2024-12-05", + "last_updated": "2024-12-05", "type": "chat" }, { - "id": "deepseek/deepseek-r1-0528", - "name": "DeepSeek R1-0528", - "display_name": "DeepSeek R1-0528", + "id": "amazon/nova-premier-v1", + "name": "Amazon: Nova Premier 1.0", + "display_name": "Amazon: Nova Premier 1.0", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 164000, - "output": 8192 + "context": 1000000, + "output": 32000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "effort_options": [ - "minimal", - "low", - "medium", - "high", - "xhigh", - "max" - ] - } + "supported": false }, - "attachment": false, - "open_weights": true, - "knowledge": "2024-07", - "release_date": "2025-05-28", - "last_updated": "2025-05-28", + "attachment": true, + "open_weights": false, + "knowledge": "2024-10", + "release_date": "2025-04-30", + "last_updated": "2025-04-30", "type": "chat" }, { - "id": "deepseek/deepseek-v4.1-flash", - "name": "DeepSeek V4.1 Flash", - "display_name": "DeepSeek V4.1 Flash", + "id": "bytedance-seed/seed-1.6", + "name": "ByteDance Seed: Seed 1.6 (retires Nov 11)", + "display_name": "ByteDance Seed: Seed 1.6 (retires Nov 11)", "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 384000 + "context": 262144, + "output": 32768 }, "temperature": true, "tool_call": true, @@ -271341,35 +271528,35 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, "effort_options": [ - "high", - "max" + "none", + "high" ] } }, "attachment": true, - "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-09-10", - "last_updated": "2026-09-10", + "open_weights": false, + "release_date": "2025-10-15", + "last_updated": "2025-10-15", "type": "chat" }, { - "id": "deepseek/deepseek-v3.2", - "name": "DeepSeek V3.2", - "display_name": "DeepSeek V3.2", + "id": "bytedance-seed/seed-2.0-mini", + "name": "Seed 2.0 Mini", + "display_name": "Seed 2.0 Mini", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 163840, - "output": 163840 + "context": 262144, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -271379,40 +271566,38 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, "effort_options": [ "none", "minimal", "low", "medium", - "high", - "xhigh", - "max" + "high" ] } }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-05", - "release_date": "2025-12-01", - "last_updated": "2025-12-01", + "attachment": true, + "open_weights": false, + "release_date": "2026-02-14", + "last_updated": "2026-02-14", "type": "chat" }, { - "id": "deepseek/deepseek-v4-flash-0731", - "name": "DeepSeek V4 Flash 0731", - "display_name": "DeepSeek V4 Flash 0731", + "id": "bytedance-seed/seed-2.0-lite", + "name": "Seed 2.0 Lite", + "display_name": "Seed 2.0 Lite", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 384000 + "context": 262144, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -271422,35 +271607,38 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, "effort_options": [ - "high", - "max" + "none", + "minimal", + "low", + "medium", + "high" ] } }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-07-31", - "last_updated": "2026-07-31", + "attachment": true, + "open_weights": false, + "release_date": "2026-02-14", + "last_updated": "2026-02-14", "type": "chat" }, { - "id": "deepseek/deepseek-v4-pro-0813", - "name": "DeepSeek V4 Pro 0813", - "display_name": "DeepSeek V4 Pro 0813", + "id": "bytedance-seed/seed-2-1-turbo", + "name": "ByteDance Seed: Seed 2.1 Turbo", + "display_name": "ByteDance Seed: Seed 2.1 Turbo", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 64000 + "context": 262144, + "output": 235929 }, "temperature": true, "tool_call": true, @@ -271460,23 +271648,22 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, "effort_options": [ - "high", - "max" + "none", + "high" ] } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-08-13", - "last_updated": "2026-08-22", + "attachment": true, + "open_weights": false, + "release_date": "2026-06-23", + "last_updated": "2026-06-23", "type": "chat" }, { - "id": "moonshotai/kimi-k3", - "name": "Kimi K3", - "display_name": "Kimi K3", + "id": "bytedance-seed/seed-1.6-flash", + "name": "ByteDance Seed: Seed 1.6 Flash (retires Nov 11)", + "display_name": "ByteDance Seed: Seed 1.6 Flash (retires Nov 11)", "modalities": { "input": [ "text", @@ -271488,10 +271675,10 @@ ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 262144, + "output": 32768 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -271499,19 +271686,22 @@ }, "extra_capabilities": { "reasoning": { - "supported": true + "effort_options": [ + "none", + "high" + ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-07-16", - "last_updated": "2026-07-16", + "open_weights": false, + "release_date": "2025-08-28", + "last_updated": "2025-08-28", "type": "chat" }, { - "id": "moonshotai/kimi-k2.6", - "name": "Kimi K2.6", - "display_name": "Kimi K2.6", + "id": "bytedance-seed/seed-2.0-code", + "name": "Seed 2.0 Code", + "display_name": "Seed 2.0 Code", "modalities": { "input": [ "text", @@ -271524,7 +271714,7 @@ }, "limit": { "context": 262144, - "output": 262144 + "output": 131072 }, "temperature": true, "tool_call": true, @@ -271534,102 +271724,87 @@ }, "extra_capabilities": { "reasoning": { - "supported": true + "effort_options": [ + "none", + "low", + "medium", + "high" + ] } }, "attachment": true, - "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-04-20", - "last_updated": "2026-04-20", + "open_weights": false, + "release_date": "2026-02-14", + "last_updated": "2026-02-14", "type": "chat" }, { - "id": "moonshotai/kimi-k2.5", - "name": "Kimi K2.5", - "display_name": "Kimi K2.5", + "id": "qwen/qwen3-vl-235b-a22b-instruct", + "name": "Qwen3 VL 235B A22B Instruct", + "display_name": "Qwen3 VL 235B A22B Instruct", "modalities": { "input": [ "text", - "image", - "video" + "image" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 131072, + "output": 32768 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } + "supported": false }, "attachment": true, "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-01-27", - "last_updated": "2026-01-27", + "knowledge": "2025-03-31", + "release_date": "2025-09-23", + "last_updated": "2025-09-23", "type": "chat" }, { - "id": "moonshotai/kimi-k2.7-code", - "name": "Kimi K2.7 Code", - "display_name": "Kimi K2.7 Code", + "id": "qwen/qwen2.5-vl-72b-instruct", + "name": "Qwen: Qwen2.5 VL 72B Instruct", + "display_name": "Qwen: Qwen2.5 VL 72B Instruct", "modalities": { "input": [ "text", - "image", - "video" + "image" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 128000, + "output": 115200 }, - "temperature": false, - "tool_call": true, + "temperature": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true - } + "supported": false }, "attachment": true, "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-06-12", - "last_updated": "2026-06-12", + "knowledge": "2024-04", + "release_date": "2025-01-26", + "last_updated": "2025-01-26", "type": "chat" }, { - "id": "qwen/qwen3.5-9b", - "name": "Qwen3.5 9B", - "display_name": "Qwen3.5 9B", + "id": "qwen/qwen3.8-omni-flash", + "name": "Qwen3.8 Omni Flash", + "display_name": "Qwen3.8 Omni Flash", "modalities": { "input": [ "text", "image", + "audio", "video" ], "output": [ @@ -271637,8 +271812,8 @@ ] }, "limit": { - "context": 262144, - "output": 65536 + "context": 1000000, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -271648,39 +271823,26 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], "effort_options": [ "none", - "minimal", - "low", - "medium", - "high", - "xhigh", - "max" + "high" ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-02-23", - "last_updated": "2026-02-23", + "open_weights": false, + "release_date": "2026-09-17", + "last_updated": "2026-09-17", "type": "chat" }, { - "id": "qwen/qwen3.8-27b", - "name": "Qwen3.8 27B", - "display_name": "Qwen3.8 27B", + "id": "qwen/qwen3-vl-30b-a3b-instruct", + "name": "Qwen3 VL 30B A3B Instruct", + "display_name": "Qwen3 VL 30B A3B Instruct", "modalities": { "input": [ "text", - "image", - "video" + "image" ], "output": [ "text" @@ -271688,27 +271850,17 @@ }, "limit": { "context": 262144, - "output": 32768 + "output": 16384 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "low", - "medium", - "xhigh" - ] - } + "supported": false }, "attachment": true, "open_weights": true, - "release_date": "2026-08-14", - "last_updated": "2026-08-14", + "release_date": "2025-10-04", + "last_updated": "2025-10-04", "type": "chat" }, { @@ -271724,8 +271876,8 @@ ] }, "limit": { - "context": 262144, - "output": 64000 + "context": 1000000, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -271750,14 +271902,13 @@ "type": "chat" }, { - "id": "qwen/qwen3.5-122b-a10b", - "name": "Qwen3.5 122B-A10B", - "display_name": "Qwen3.5 122B-A10B", + "id": "qwen/qwen3-vl-30b-a3b-thinking", + "name": "Qwen: Qwen3 VL 30B A3B Thinking", + "display_name": "Qwen: Qwen3 VL 30B A3B Thinking", "modalities": { "input": [ "text", - "image", - "video" + "image" ], "output": [ "text" @@ -271765,7 +271916,7 @@ }, "limit": { "context": 262144, - "output": 262144 + "output": 235929 }, "temperature": true, "tool_call": true, @@ -271783,26 +271934,48 @@ "thinking_blocks" ], "effort_options": [ - "none", - "minimal", - "low", - "medium", - "high", - "xhigh", - "max" + "high" ] } }, "attachment": true, + "open_weights": false, + "release_date": "2025-10-06", + "last_updated": "2025-10-06", + "type": "chat" + }, + { + "id": "qwen/qwen3-next-80b-a3b-instruct", + "name": "Qwen3-Next 80B-A3B Instruct", + "display_name": "Qwen3-Next 80B-A3B Instruct", + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 262144, + "output": 235929 + }, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": false + }, + "attachment": false, "open_weights": true, - "release_date": "2026-02-23", - "last_updated": "2026-02-23", + "knowledge": "2025-04", + "release_date": "2025-09", + "last_updated": "2025-09", "type": "chat" }, { - "id": "qwen/qwen3.8-flash-next", - "name": "Qwen3.8 Flash Next", - "display_name": "Qwen3.8 Flash Next", + "id": "qwen/qwen3.5-27b", + "name": "Qwen3.5 27B", + "display_name": "Qwen3.5 27B", "modalities": { "input": [ "text", @@ -271815,7 +271988,7 @@ }, "limit": { "context": 262144, - "output": 64000 + "output": 81920 }, "temperature": true, "tool_call": true, @@ -271825,23 +271998,29 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ - "low", - "medium", - "xhigh" + "none", + "high" ] } }, "attachment": true, "open_weights": true, - "release_date": "2026-08-27", - "last_updated": "2026-08-27", + "release_date": "2026-02-23", + "last_updated": "2026-02-23", "type": "chat" }, { - "id": "qwen/qwen3-235b-a22b-2507", - "name": "Qwen3 235B-A22B-2507", - "display_name": "Qwen3 235B-A22B-2507", + "id": "qwen/qwen-2.5-72b-instruct", + "name": "Qwen2.5 72B Instruct", + "display_name": "Qwen2.5 72B Instruct", "modalities": { "input": [ "text" @@ -271851,8 +272030,8 @@ ] }, "limit": { - "context": 131000, - "output": 262144 + "context": 32768, + "output": 16384 }, "temperature": true, "tool_call": true, @@ -271860,25 +272039,15 @@ "supported": false }, "attachment": false, - "open_weights": true, - "knowledge": "2025-06-30", - "release_date": "2025-07-21", - "last_updated": "2025-07-21", + "open_weights": false, + "release_date": "2024-09-19", + "last_updated": "2024-09-19", "type": "chat" - } - ] - }, - "chutes": { - "id": "chutes", - "name": "Chutes", - "display_name": "Chutes", - "api": "https://llm.chutes.ai/v1", - "doc": "https://llm.chutes.ai/v1/models", - "models": [ + }, { - "id": "zai-org/GLM-5.2-TEE", - "name": "GLM 5.2 TEE", - "display_name": "GLM 5.2 TEE", + "id": "qwen/qwen3.7-max", + "name": "Qwen3.7 Max", + "display_name": "Qwen3.7 Max", "modalities": { "input": [ "text" @@ -271888,8 +272057,8 @@ ] }, "limit": { - "context": 1048576, - "output": 65535 + "context": 1000000, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -271900,35 +272069,35 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "none", + "high" ] } }, "attachment": false, - "open_weights": true, - "release_date": "2026-06-13", - "last_updated": "2026-06-13", + "open_weights": false, + "release_date": "2026-05-21", + "last_updated": "2026-05-21", "type": "chat" }, { - "id": "zai-org/GLM-5.1-TEE", - "name": "GLM 5.1 TEE", - "display_name": "GLM 5.1 TEE", + "id": "qwen/qwen3.6-flash", + "name": "Qwen3.6 Flash", + "display_name": "Qwen3.6 Flash", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 202752, - "output": 65535 + "context": 1000000, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -271944,19 +272113,23 @@ "visibility": "summary", "continuation": [ "thinking_blocks" + ], + "effort_options": [ + "none", + "high" ] } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-04-07", - "last_updated": "2026-04-07", + "attachment": true, + "open_weights": false, + "release_date": "2026-04-27", + "last_updated": "2026-04-27", "type": "chat" }, { - "id": "zai-org/GLM-5.2-Fast", - "name": "GLM 5.2 Fast", - "display_name": "GLM 5.2 Fast", + "id": "qwen/qwen3-30b-a3b-instruct-2507", + "name": "Qwen: Qwen3 30B A3B Instruct 2507", + "display_name": "Qwen: Qwen3 30B A3B Instruct 2507", "modalities": { "input": [ "text" @@ -271966,24 +272139,24 @@ ] }, "limit": { - "context": 8192, - "output": 8192 + "context": 262144, + "output": 235929 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { "supported": false }, "attachment": false, "open_weights": true, - "release_date": "2026-10-26", - "last_updated": "2026-10-26", + "release_date": "2025-07-28", + "last_updated": "2025-07-28", "type": "chat" }, { - "id": "zai-org/GLM-5.3", - "name": "GLM 5.3", - "display_name": "GLM 5.3", + "id": "qwen/qwen-plus", + "name": "Qwen Plus", + "display_name": "Qwen Plus", "modalities": { "input": [ "text" @@ -271993,41 +272166,49 @@ ] }, "limit": { - "context": 8192, - "output": 8192 + "context": 1000000, + "output": 32768 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { "supported": true }, "extra_capabilities": { "reasoning": { - "supported": true + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] } }, "attachment": false, - "open_weights": true, - "release_date": "2026-10-26", - "last_updated": "2026-10-26", + "open_weights": false, + "knowledge": "2024-04", + "release_date": "2024-01-25", + "last_updated": "2025-09-11", "type": "chat" }, { - "id": "Qwen/Qwen3.5-397B-A17B-TEE", - "name": "Qwen3.5 397B A17B TEE", - "display_name": "Qwen3.5 397B A17B TEE", + "id": "qwen/qwen3.8-flash", + "name": "Qwen3.8 Flash", + "display_name": "Qwen3.8 Flash", "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 65536 + "context": 1000000, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -272038,28 +272219,27 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "none", + "high" ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-02-15", - "last_updated": "2026-02-15", + "open_weights": false, + "release_date": "2026-08-26", + "last_updated": "2026-08-26", "type": "chat" }, { - "id": "Qwen/Qwen3.6-27B-TEE", - "name": "Qwen3.6 27B TEE", - "display_name": "Qwen3.6 27B TEE", + "id": "qwen/qwen3.6-35b-a3b", + "name": "Qwen3.6 35B-A3B", + "display_name": "Qwen3.6 35B-A3B", "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ "text" @@ -272067,7 +272247,7 @@ }, "limit": { "context": 262144, - "output": 65536 + "output": 235929 }, "temperature": true, "tool_call": true, @@ -272083,19 +272263,23 @@ "visibility": "summary", "continuation": [ "thinking_blocks" + ], + "effort_options": [ + "none", + "high" ] } }, "attachment": true, "open_weights": true, - "release_date": "2026-04-22", - "last_updated": "2026-04-22", + "release_date": "2026-04-17", + "last_updated": "2026-04-17", "type": "chat" }, { - "id": "Qwen/Qwen3.8-2.4T-A95B", - "name": "Qwen3.8 2.4T A95B", - "display_name": "Qwen3.8 2.4T A95B", + "id": "qwen/qwen3-30b-a3b", + "name": "Qwen3 30B A3B", + "display_name": "Qwen3 30B A3B", "modalities": { "input": [ "text" @@ -272105,40 +272289,52 @@ ] }, "limit": { - "context": 8192, - "output": 8192 + "context": 40960, + "output": 16384 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": true + "supported": true, + "default": true }, "extra_capabilities": { "reasoning": { - "supported": true + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "effort_options": [ + "none", + "high" + ] } }, "attachment": false, "open_weights": true, - "release_date": "2026-10-26", - "last_updated": "2026-10-26", + "release_date": "2025-04-28", + "last_updated": "2025-04-28", "type": "chat" }, { - "id": "Qwen/Qwen3.8-27B-TEE", - "name": "Qwen3.8 27B TEE", - "display_name": "Qwen3.8 27B TEE", + "id": "qwen/qwen3.7-flash", + "name": "Qwen3.7 Flash", + "display_name": "Qwen3.7 Flash", "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 262144, + "context": 1000000, "output": 65536 }, "temperature": true, @@ -272147,16 +272343,25 @@ "supported": true, "default": true }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "effort_options": [ + "none", + "high" + ] + } + }, "attachment": true, - "open_weights": true, - "release_date": "2026-08-14", - "last_updated": "2026-08-14", + "open_weights": false, + "release_date": "2026-07-15", + "last_updated": "2026-07-15", "type": "chat" }, { - "id": "Qwen/Qwen3.8-Max-0902", - "name": "Qwen3.8 Max 0902", - "display_name": "Qwen3.8 Max 0902", + "id": "qwen/qwen3-coder-next", + "name": "Qwen3 Coder Next", + "display_name": "Qwen3 Coder Next", "modalities": { "input": [ "text" @@ -272166,36 +272371,38 @@ ] }, "limit": { - "context": 8192, - "output": 8192 + "context": 262144, + "output": 235929 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { "supported": false }, "attachment": false, "open_weights": true, - "release_date": "2026-10-20", - "last_updated": "2026-10-25", + "knowledge": "2025-09", + "release_date": "2026-02-03", + "last_updated": "2026-02-03", "type": "chat" }, { - "id": "google/gemma-4-31B-turbo-TEE", - "name": "gemma 4 31B turbo TEE", - "display_name": "gemma 4 31B turbo TEE", + "id": "qwen/qwen3.8-max-0902", + "name": "Qwen3.8 Max 0902", + "display_name": "Qwen3.8 Max 0902", "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 65536 + "context": 1000000, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -272203,91 +272410,95 @@ "supported": true, "default": true }, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "minimal", + "low", + "medium", + "high", + "xhigh" + ] + } + }, "attachment": true, - "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-04-02", - "last_updated": "2026-04-02", + "open_weights": false, + "release_date": "2026-09-02", + "last_updated": "2026-09-02", "type": "chat" }, { - "id": "moonshotai/Kimi-K2.6-TEE", - "name": "Kimi K2.6 TEE", - "display_name": "Kimi K2.6 TEE", + "id": "qwen/qwen-2.5-7b-instruct", + "name": "Qwen: Qwen2.5 7B Instruct", + "display_name": "Qwen: Qwen2.5 7B Instruct", "modalities": { "input": [ - "text", - "image", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 65535 + "context": 32768, + "output": 29491 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } + "supported": false }, - "attachment": true, - "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-04-20", - "last_updated": "2026-04-20", + "attachment": false, + "open_weights": false, + "release_date": "2024-10-16", + "last_updated": "2024-10-16", "type": "chat" }, { - "id": "moonshotai/Kimi-K2.7-Code", - "name": "Kimi K2.7 Code", - "display_name": "Kimi K2.7 Code", + "id": "qwen/qwen3.8-max-prime", + "name": "Qwen 3.8 Max Prime", + "display_name": "Qwen 3.8 Max Prime", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 8192, - "output": 8192 + "context": 1000000, + "output": 131072 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": true + "supported": true, + "default": true }, "extra_capabilities": { "reasoning": { - "supported": true + "effort_options": [ + "minimal", + "low", + "medium", + "high", + "xhigh" + ] } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-10-26", - "last_updated": "2026-10-26", + "attachment": true, + "open_weights": false, + "release_date": "2026-09-23", + "last_updated": "2026-09-23", "type": "chat" }, { - "id": "moonshotai/Kimi-K3-TEE", - "name": "Kimi K3 TEE", - "display_name": "Kimi K3 TEE", + "id": "qwen/qwen3.5-flash-02-23", + "name": "Qwen: Qwen3.5-Flash", + "display_name": "Qwen: Qwen3.5-Flash", "modalities": { "input": [ "text", @@ -272299,8 +272510,8 @@ ] }, "limit": { - "context": 1048576, - "output": 65535 + "context": 1000000, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -272308,18 +272519,34 @@ "supported": true, "default": true }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "effort_options": [ + "none", + "high" + ] + } + }, "attachment": true, - "open_weights": true, - "release_date": "2026-07-16", - "last_updated": "2026-07-16", + "open_weights": false, + "release_date": "2026-02-25", + "last_updated": "2026-02-25", "type": "chat" }, { - "id": "deepseek-ai/DeepSeek-V4-Pro-0813", - "name": "DeepSeek V4 Pro 0813", - "display_name": "DeepSeek V4 Pro 0813", + "id": "qwen/qwen3-vl-8b-instruct", + "name": "Qwen: Qwen3 VL 8B Instruct", + "display_name": "Qwen: Qwen3 VL 8B Instruct", "modalities": { "input": [ + "image", "text" ], "output": [ @@ -272327,40 +272554,37 @@ ] }, "limit": { - "context": 8192, - "output": 8192 + "context": 262144, + "output": 235929 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true - } + "supported": false }, - "attachment": false, - "open_weights": true, - "release_date": "2026-10-20", - "last_updated": "2026-10-25", + "attachment": true, + "open_weights": false, + "release_date": "2025-10-14", + "last_updated": "2025-10-14", "type": "chat" }, { - "id": "deepseek-ai/DeepSeek-V4-Flash-0731-TEE", - "name": "DeepSeek V4 Flash 0731 TEE", - "display_name": "DeepSeek V4 Flash 0731 TEE", + "id": "qwen/qwen3.8-27b", + "name": "Qwen3.8 27B", + "display_name": "Qwen3.8 27B", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 262144, + "output": 235929 }, "temperature": true, "tool_call": true, @@ -272368,17 +272592,26 @@ "supported": true, "default": true }, - "attachment": false, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "none", + "low", + "medium", + "xhigh" + ] + } + }, + "attachment": true, "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-07-31", - "last_updated": "2026-07-31", + "release_date": "2026-08-14", + "last_updated": "2026-08-14", "type": "chat" }, { - "id": "deepseek-ai/DeepSeek-V3.2-TEE", - "name": "DeepSeek V3.2 TEE", - "display_name": "DeepSeek V3.2 TEE", + "id": "qwen/qwen3-14b", + "name": "Qwen: Qwen3 14B", + "display_name": "Qwen: Qwen3 14B", "modalities": { "input": [ "text" @@ -272388,8 +272621,8 @@ ] }, "limit": { - "context": 131072, - "output": 65536 + "context": 40960, + "output": 16384 }, "temperature": true, "tool_call": true, @@ -272405,20 +272638,23 @@ "visibility": "summary", "continuation": [ "thinking_blocks" + ], + "effort_options": [ + "none", + "high" ] } }, "attachment": false, - "open_weights": true, - "knowledge": "2024-07", - "release_date": "2025-12-01", - "last_updated": "2025-12-01", + "open_weights": false, + "release_date": "2025-04-28", + "last_updated": "2025-04-28", "type": "chat" }, { - "id": "deepseek-ai/DeepSeek-V4.1-Flash-1017", - "name": "DeepSeek V4.1 Flash 1017", - "display_name": "DeepSeek V4.1 Flash 1017", + "id": "qwen/qwen3-coder-flash", + "name": "Qwen3 Coder Flash", + "display_name": "Qwen3 Coder Flash", "modalities": { "input": [ "text" @@ -272428,44 +272664,38 @@ ] }, "limit": { - "context": 8192, - "output": 8192 + "context": 1000000, + "output": 65536 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { "supported": false }, "attachment": false, - "open_weights": true, - "release_date": "2026-10-26", - "last_updated": "2026-10-26", + "open_weights": false, + "knowledge": "2025-04", + "release_date": "2025-07-28", + "last_updated": "2025-07-28", "type": "chat" - } - ] - }, - "pendra": { - "id": "pendra", - "name": "Pendra", - "display_name": "Pendra", - "api": "https://api.pendra.ai/api/v1", - "doc": "https://pendra.ai/docs/integrations/opencode", - "models": [ + }, { - "id": "deepseek-v4-flash", - "name": "DeepSeek V4 Flash", - "display_name": "DeepSeek V4 Flash", + "id": "qwen/qwen3.6-27b", + "name": "Qwen3.6 27B", + "display_name": "Qwen3.6 27B", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 384000 + "context": 262144, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -272476,39 +272706,33 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "high", - "effort_options": [ - "low", - "high", - "max" - ], "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ "thinking_blocks" ], - "notes": [ - "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." + "effort_options": [ + "none", + "high" ] } }, - "attachment": false, + "attachment": true, "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-04-24", - "last_updated": "2026-04-24", + "release_date": "2026-04-22", + "last_updated": "2026-04-22", "type": "chat" }, { - "id": "qwen3-coder:30b", - "name": "Qwen3-Coder 30B-A3B Instruct", - "display_name": "Qwen3-Coder 30B-A3B Instruct", + "id": "qwen/qwen3.5-9b", + "name": "Qwen3.5 9B", + "display_name": "Qwen3.5 9B", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" @@ -272516,24 +272740,39 @@ }, "limit": { "context": 262144, - "output": 65536 + "output": 235929 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "effort_options": [ + "none", + "high" + ] + } + }, + "attachment": true, "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-07-31", - "last_updated": "2025-07-31", + "release_date": "2026-02-23", + "last_updated": "2026-02-23", "type": "chat" }, { - "id": "gpt-oss:120b", - "name": "GPT OSS 120B", - "display_name": "GPT OSS 120B", + "id": "qwen/qwen-2.5-coder-32b-instruct", + "name": "Qwen2.5 Coder 32B Instruct", + "display_name": "Qwen2.5 Coder 32B Instruct", "modalities": { "input": [ "text" @@ -272543,35 +272782,24 @@ ] }, "limit": { - "context": 131072, - "output": 32768 + "context": 32768, + "output": 29491 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "low", - "medium", - "high" - ] - } + "supported": false }, "attachment": false, "open_weights": true, - "knowledge": "2024-06", - "release_date": "2025-08-05", - "last_updated": "2025-08-05", + "release_date": "2024-11-12", + "last_updated": "2024-11-12", "type": "chat" }, { - "id": "llama3.3:70b", - "name": "Llama-3.3-70B-Instruct", - "display_name": "Llama-3.3-70B-Instruct", + "id": "qwen/qwen3-coder-30b-a3b-instruct", + "name": "Qwen3-Coder 30B-A3B Instruct", + "display_name": "Qwen3-Coder 30B-A3B Instruct", "modalities": { "input": [ "text" @@ -272581,8 +272809,8 @@ ] }, "limit": { - "context": 128000, - "output": 4096 + "context": 262144, + "output": 235929 }, "temperature": true, "tool_call": true, @@ -272591,26 +272819,28 @@ }, "attachment": false, "open_weights": true, - "knowledge": "2023-12", - "release_date": "2024-12-06", - "last_updated": "2024-12-06", + "knowledge": "2025-04", + "release_date": "2025-07-31", + "last_updated": "2025-07-31", "type": "chat" }, { - "id": "glm-4.7-flash", - "name": "GLM-4.7-Flash", - "display_name": "GLM-4.7-Flash", + "id": "qwen/qwen3.6-plus", + "name": "Qwen3.6 Plus", + "display_name": "Qwen3.6 Plus", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 131072 + "context": 1000000, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -272620,24 +272850,33 @@ }, "extra_capabilities": { "reasoning": { - "supported": true + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "effort_options": [ + "none", + "high" + ] } }, - "attachment": false, - "open_weights": true, + "attachment": true, + "open_weights": false, "knowledge": "2025-04", - "release_date": "2026-01-19", - "last_updated": "2026-01-19", + "release_date": "2026-04-02", + "last_updated": "2026-04-02", "type": "chat" }, { - "id": "qwen3.6:27b", - "name": "Qwen3.6 27B", - "display_name": "Qwen3.6 27B", + "id": "qwen/qwen3-coder", + "name": "Qwen: Qwen3 Coder 480B A35B", + "display_name": "Qwen: Qwen3 Coder 480B A35B", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" @@ -272650,28 +272889,19 @@ "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true + "supported": false }, - "attachment": true, + "attachment": false, "open_weights": true, - "release_date": "2026-04-22", - "last_updated": "2026-04-22", + "knowledge": "2025-04", + "release_date": "2025-07-22", + "last_updated": "2025-07-22", "type": "chat" - } - ] - }, - "wallaby": { - "id": "wallaby", - "name": "Wallaby", - "display_name": "Wallaby", - "api": "https://api.wallabytoken.com/v1", - "doc": "https://wallabytoken.com/docs", - "models": [ + }, { - "id": "moonshotai/kimi-k3", - "name": "Kimi K3", - "display_name": "Kimi K3", + "id": "qwen/qwen3.5-35b-a3b", + "name": "Qwen3.5 35B-A3B", + "display_name": "Qwen3.5 35B-A3B", "modalities": { "input": [ "text", @@ -272683,10 +272913,10 @@ ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 262144, + "output": 81920 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -272702,59 +272932,21 @@ "thinking_blocks" ], "effort_options": [ - "low", - "medium", - "high", - "max" + "none", + "high" ] } }, "attachment": true, "open_weights": true, - "release_date": "2026-07-16", - "last_updated": "2026-07-16", - "type": "chat" - } - ] - }, - "alibaba-cn": { - "id": "alibaba-cn", - "name": "alibaba-cn", - "display_name": "alibaba-cn", - "api": "https://dashscope.aliyuncs.com/compatible-mode/v1", - "doc": "https://www.alibabacloud.com/help/en/model-studio/models", - "models": [ - { - "id": "deepseek-v3-2-exp", - "name": "DeepSeek V3.2 Exp", - "display_name": "DeepSeek V3.2 Exp", - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 65536 - }, - "temperature": true, - "tool_call": true, - "reasoning": { - "supported": false - }, - "attachment": false, - "open_weights": false, - "release_date": "2025-01-01", - "last_updated": "2025-01-01", + "release_date": "2026-02-23", + "last_updated": "2026-02-23", "type": "chat" }, { - "id": "qwen3.7-max", - "name": "Qwen3.7 Max", - "display_name": "Qwen3.7 Max", + "id": "qwen/qwen3-235b-a22b-thinking-2507", + "name": "Qwen: Qwen3 235B A22B Thinking 2507", + "display_name": "Qwen: Qwen3 235B A22B Thinking 2507", "modalities": { "input": [ "text" @@ -272764,8 +272956,8 @@ ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 128000, + "output": 16384 }, "temperature": true, "tool_call": true, @@ -272775,22 +272967,33 @@ }, "extra_capabilities": { "reasoning": { - "supported": true + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "effort_options": [ + "high" + ] } }, "attachment": false, - "open_weights": false, - "release_date": "2026-05-21", - "last_updated": "2026-05-21", + "open_weights": true, + "release_date": "2025-07-25", + "last_updated": "2025-07-25", "type": "chat" }, { - "id": "qwen3-max", - "name": "Qwen3 Max", - "display_name": "Qwen3 Max", + "id": "qwen/qwen3.5-397b-a17b", + "name": "Qwen3.5 397B-A17B", + "display_name": "Qwen3.5 397B-A17B", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" @@ -272798,7 +273001,7 @@ }, "limit": { "context": 262144, - "output": 65536 + "output": 235929 }, "temperature": true, "tool_call": true, @@ -272814,76 +273017,107 @@ "visibility": "summary", "continuation": [ "thinking_blocks" + ], + "effort_options": [ + "none", + "high" ] } }, - "attachment": false, - "open_weights": false, - "knowledge": "2025-04", - "release_date": "2026-01-23", - "last_updated": "2026-09-22", + "attachment": true, + "open_weights": true, + "release_date": "2026-02-15", + "last_updated": "2026-02-15", "type": "chat" }, { - "id": "qwen-mt-turbo", - "name": "Qwen Mt Turbo", - "display_name": "Qwen Mt Turbo", + "id": "qwen/qwen3.7-plus", + "name": "Qwen3.7 Plus", + "display_name": "Qwen3.7 Plus", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 16384, - "output": 8192 + "context": 1000000, + "output": 131072 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, + "extra_capabilities": { + "reasoning": { + "supported": true, + "effort_options": [ + "none", + "high" + ] + } + }, + "attachment": true, "open_weights": false, - "knowledge": "2024-04", - "release_date": "2025-01", - "last_updated": "2025-01", + "knowledge": "2025-04", + "release_date": "2026-06-02", + "last_updated": "2026-06-02", "type": "chat" }, { - "id": "qwen-long", - "name": "Qwen Long", - "display_name": "Qwen Long", + "id": "qwen/qwen3.5-122b-a10b", + "name": "Qwen3.5 122B-A10B", + "display_name": "Qwen3.5 122B-A10B", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 8192 + "context": 262144, + "output": 65536 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, - "open_weights": false, - "knowledge": "2024-04", - "release_date": "2025-01-25", - "last_updated": "2025-01-25", + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "effort_options": [ + "none", + "high" + ] + } + }, + "attachment": true, + "open_weights": true, + "release_date": "2026-02-23", + "last_updated": "2026-02-23", "type": "chat" }, { - "id": "deepseek-r1-distill-llama-8b", - "name": "DeepSeek R1 Distill Llama 8B", - "display_name": "DeepSeek R1 Distill Llama 8B", + "id": "qwen/qwen3-32b", + "name": "Qwen3 32B", + "display_name": "Qwen3 32B", "modalities": { "input": [ "text" @@ -272893,7 +273127,7 @@ ] }, "limit": { - "context": 32768, + "context": 40960, "output": 16384 }, "temperature": true, @@ -272902,16 +273136,32 @@ "supported": true, "default": true }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "effort_options": [ + "none", + "high" + ] + } + }, "attachment": false, - "open_weights": false, - "release_date": "2025-01-01", - "last_updated": "2025-01-01", + "open_weights": true, + "knowledge": "2025-04", + "release_date": "2025-04-28", + "last_updated": "2025-04-28", "type": "chat" }, { - "id": "deepseek-r1-distill-qwen-14b", - "name": "DeepSeek R1 Distill Qwen 14B", - "display_name": "DeepSeek R1 Distill Qwen 14B", + "id": "qwen/qwen3-235b-a22b-2507", + "name": "Qwen: Qwen3 235B A22B Instruct 2507", + "display_name": "Qwen: Qwen3 235B A22B Instruct 2507", "modalities": { "input": [ "text" @@ -272921,56 +273171,74 @@ ] }, "limit": { - "context": 32768, + "context": 262144, "output": 16384 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true + "supported": false }, "attachment": false, - "open_weights": false, - "release_date": "2025-01-01", - "last_updated": "2025-01-01", + "open_weights": true, + "release_date": "2025-07-21", + "last_updated": "2025-07-21", "type": "chat" }, { - "id": "qwen3-coder-30b-a3b-instruct", - "name": "Qwen3-Coder 30B-A3B Instruct", - "display_name": "Qwen3-Coder 30B-A3B Instruct", + "id": "qwen/qwen3.5-plus-02-15", + "name": "Qwen: Qwen3.5 Plus 2026-02-15", + "display_name": "Qwen: Qwen3.5 Plus 2026-02-15", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 262144, + "context": 1000000, "output": 65536 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-04", - "last_updated": "2025-04", + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "effort_options": [ + "none", + "high" + ] + } + }, + "attachment": true, + "open_weights": false, + "release_date": "2026-02-16", + "last_updated": "2026-02-16", "type": "chat" }, { - "id": "deepseek-v4-flash", - "name": "DeepSeek V4 Flash", - "display_name": "DeepSeek V4 Flash", + "id": "qwen/qwen3.5-plus-20260420", + "name": "Qwen: Qwen3.5 Plus 2026-04-20", + "display_name": "Qwen: Qwen3.5 Plus 2026-04-20", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" @@ -272978,7 +273246,7 @@ }, "limit": { "context": 1000000, - "output": 384000 + "output": 65536 }, "temperature": true, "tool_call": true, @@ -272989,36 +273257,28 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "high", - "effort_options": [ - "low", - "high", - "max" - ], "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ "thinking_blocks" ], - "notes": [ - "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." + "effort_options": [ + "none", + "high" ] } }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-04-24", - "last_updated": "2026-04-24", + "attachment": true, + "open_weights": false, + "release_date": "2026-04-27", + "last_updated": "2026-04-27", "type": "chat" }, { - "id": "qwen-math-turbo", - "name": "Qwen Math Turbo", - "display_name": "Qwen Math Turbo", + "id": "inference-net/schematron-v2-turbo", + "name": "Inference.net: Schematron V2 Turbo", + "display_name": "Inference.net: Schematron V2 Turbo", "modalities": { "input": [ "text" @@ -273028,144 +273288,107 @@ ] }, "limit": { - "context": 4096, - "output": 3072 + "context": 128000, + "output": 8192 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { "supported": false }, "attachment": false, "open_weights": false, - "knowledge": "2024-04", - "release_date": "2024-09-19", - "last_updated": "2024-09-19", + "release_date": "2026-04-16", + "last_updated": "2026-04-16", "type": "chat" }, { - "id": "kimi-k3", - "name": "Kimi K3", - "display_name": "Kimi K3", + "id": "inference-net/schematron-v2-small", + "name": "Inference.net: Schematron V2 Small", + "display_name": "Inference.net: Schematron V2 Small", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 1048576 + "context": 128000, + "output": 4096 }, - "temperature": false, - "tool_call": true, + "temperature": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "effort_options": [ - "low", - "high", - "max" - ] - } + "supported": false }, - "attachment": true, - "open_weights": true, - "release_date": "2026-07-16", - "last_updated": "2026-07-16", + "attachment": false, + "open_weights": false, + "release_date": "2026-04-16", + "last_updated": "2026-04-16", "type": "chat" }, { - "id": "qwen3-vl-plus", - "name": "Qwen3 VL Plus", - "display_name": "Qwen3 VL Plus", + "id": "cognitivecomputations/dolphin-mistral-24b-venice-edition", + "name": "Venice: Uncensored", + "display_name": "Venice: Uncensored", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, - "limit": { - "context": 262144, - "output": 32768 - }, - "temperature": true, - "tool_call": true, - "reasoning": { - "supported": true, - "default": false, - "budget": { - "default": 81920, - "min": 0, - "max": 81920 - } - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } + "limit": { + "context": 128000, + "output": 8192 + }, + "temperature": true, + "tool_call": false, + "reasoning": { + "supported": false }, "attachment": false, - "open_weights": false, - "knowledge": "2025-04", - "release_date": "2025-09-23", - "last_updated": "2025-09-23", + "open_weights": true, + "knowledge": "2024-04-30", + "release_date": "2025-07-09", + "last_updated": "2025-07-09", "type": "chat" }, { - "id": "qwen-deep-research", - "name": "Qwen Deep Research", - "display_name": "Qwen Deep Research", + "id": "perplexity/sonar", + "name": "Perplexity: Sonar", + "display_name": "Perplexity: Sonar", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 32768 + "context": 127072, + "output": 114364 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { "supported": false }, - "attachment": false, + "attachment": true, "open_weights": false, - "knowledge": "2024-04", - "release_date": "2024-01", - "last_updated": "2024-01", + "release_date": "2025-01-27", + "last_updated": "2025-01-27", "type": "chat" }, { - "id": "deepseek-v4-pro", - "name": "DeepSeek V4 Pro", - "display_name": "DeepSeek V4 Pro", + "id": "perplexity/sonar-deep-research", + "name": "Perplexity: Sonar Deep Research", + "display_name": "Perplexity: Sonar Deep Research", "modalities": { "input": [ "text" @@ -273175,167 +273398,132 @@ ] }, "limit": { - "context": 1000000, - "output": 384000 + "context": 128000, + "output": 115200 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { "supported": true, "default": true }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "high", "effort_options": [ - "low", - "high", - "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." + "high" ] } }, "attachment": false, - "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-04-24", - "last_updated": "2026-04-24", + "open_weights": false, + "release_date": "2025-03-07", + "last_updated": "2025-03-07", "type": "chat" }, { - "id": "qwen-turbo", - "name": "Qwen Turbo", - "display_name": "Qwen Turbo", + "id": "perplexity/sonar-pro-search", + "name": "Perplexity: Sonar Pro Search", + "display_name": "Perplexity: Sonar Pro Search", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 16384 + "context": 200000, + "output": 8000 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { "supported": true, - "default": false, - "budget": { - "default": 38912, - "min": 0, - "max": 38912 - } + "default": true }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "high" ] } }, - "search": { - "supported": true, - "default": false, - "forced_search": false, - "search_strategy": "turbo" - }, - "attachment": false, + "attachment": true, "open_weights": false, - "knowledge": "2024-04", - "release_date": "2024-11-01", - "last_updated": "2025-07-15", + "release_date": "2025-10-30", + "last_updated": "2025-10-30", "type": "chat" }, { - "id": "qwen3-asr-flash", - "name": "Qwen3-ASR Flash", - "display_name": "Qwen3-ASR Flash", + "id": "perplexity/sonar-pro", + "name": "Perplexity: Sonar Pro", + "display_name": "Perplexity: Sonar Pro", "modalities": { "input": [ - "audio" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 53248, - "output": 4096 + "context": 200000, + "output": 8000 }, - "temperature": false, + "temperature": true, "tool_call": false, "reasoning": { "supported": false }, "attachment": true, "open_weights": false, - "knowledge": "2024-04", - "release_date": "2025-09-08", - "last_updated": "2025-09-08" + "release_date": "2025-03-07", + "last_updated": "2025-03-07", + "type": "chat" }, { - "id": "qwen3.6-flash", - "name": "Qwen3.6 Flash", - "display_name": "Qwen3.6 Flash", + "id": "perplexity/sonar-reasoning-pro", + "name": "Perplexity: Sonar Reasoning Pro", + "display_name": "Perplexity: Sonar Reasoning Pro", "modalities": { "input": [ "text", - "image", - "video" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 65536 + "context": 128000, + "output": 115200 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { "supported": true, "default": true }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "high" ] } }, "attachment": true, "open_weights": false, - "release_date": "2026-04-27", - "last_updated": "2026-04-27", + "release_date": "2025-03-07", + "last_updated": "2025-03-07", "type": "chat" }, { - "id": "qwen-flash", - "name": "Qwen Flash", - "display_name": "Qwen Flash", + "id": "deepseek/deepseek-chat", + "name": "DeepSeek Chat", + "display_name": "DeepSeek Chat", "modalities": { "input": [ "text" @@ -273345,19 +273533,42 @@ ] }, "limit": { - "context": 1000000, + "context": 128000, + "output": 16000 + }, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": false + }, + "attachment": false, + "open_weights": true, + "knowledge": "2025-09", + "release_date": "2025-12-01", + "last_updated": "2026-02-28", + "type": "chat" + }, + { + "id": "deepseek/deepseek-r1-0528", + "name": "DeepSeek R1 0528", + "display_name": "DeepSeek R1 0528", + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 163840, "output": 32768 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false, - "budget": { - "default": 81920, - "min": 0, - "max": 81920 - } + "default": true }, "extra_capabilities": { "reasoning": { @@ -273367,40 +273578,33 @@ "visibility": "summary", "continuation": [ "thinking_blocks" + ], + "effort_options": [ + "high" ] } }, - "search": { - "supported": true, - "default": false, - "forced_search": false, - "search_strategy": "turbo" - }, "attachment": false, - "open_weights": false, - "knowledge": "2024-04", - "release_date": "2025-07-28", - "last_updated": "2025-07-28", + "open_weights": true, + "release_date": "2025-05-28", + "last_updated": "2025-05-28", "type": "chat" }, { - "id": "qwen3.8-omni-flash", - "name": "Qwen3.8 Omni Flash", - "display_name": "Qwen3.8 Omni Flash", + "id": "deepseek/deepseek-chat-v3.1", + "name": "DeepSeek V3.1", + "display_name": "DeepSeek V3.1", "modalities": { "input": [ - "text", - "image", - "audio", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 163840, + "output": 147456 }, "temperature": true, "tool_call": true, @@ -273410,62 +273614,63 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "xhigh", "effort_options": [ "none", - "low", - "medium", - "xhigh" - ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "high" ] } }, - "attachment": true, - "open_weights": false, - "release_date": "2026-09-17", - "last_updated": "2026-09-17", + "attachment": false, + "open_weights": true, + "knowledge": "2025-03-31", + "release_date": "2025-08-21", + "last_updated": "2025-08-21", "type": "chat" }, { - "id": "qwen3-coder-480b-a35b-instruct", - "name": "Qwen3-Coder 480B-A35B Instruct", - "display_name": "Qwen3-Coder 480B-A35B Instruct", + "id": "deepseek/deepseek-v4.1-flash-1017", + "name": "DeepSeek V4.1 Flash 1017", + "display_name": "DeepSeek V4.1 Flash 1017", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 65536 + "context": 1048576, + "output": 393216 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "none", + "low", + "high", + "max" + ] + } + }, + "attachment": true, "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-04", - "last_updated": "2025-04", + "knowledge": "2025-05", + "release_date": "2026-10-17", + "last_updated": "2026-10-17", "type": "chat" }, { - "id": "qwq-plus", - "name": "QwQ Plus", - "display_name": "QwQ Plus", + "id": "deepseek/deepseek-v3.2-exp", + "name": "DeepSeek: DeepSeek V3.2 Exp", + "display_name": "DeepSeek: DeepSeek V3.2 Exp", "modalities": { "input": [ "text" @@ -273475,48 +273680,33 @@ ] }, "limit": { - "context": 131072, - "output": 8192 + "context": 163840, + "output": 147456 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { "supported": true, - "default": true, - "budget": { - "default": 32768, - "min": 0, - "max": 32768 - } + "default": true }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "none", + "high" ] } }, - "search": { - "supported": true, - "default": false, - "forced_search": false, - "search_strategy": "turbo" - }, "attachment": false, "open_weights": false, - "knowledge": "2024-04", - "release_date": "2025-03-05", - "last_updated": "2025-03-05", + "release_date": "2025-09-29", + "last_updated": "2025-09-29", "type": "chat" }, { - "id": "deepseek-r1-distill-llama-70b", - "name": "DeepSeek R1 Distill Llama 70B", - "display_name": "DeepSeek R1 Distill Llama 70B", + "id": "deepseek/deepseek-v3.2", + "name": "DeepSeek V3.2", + "display_name": "DeepSeek V3.2", "modalities": { "input": [ "text" @@ -273526,75 +273716,88 @@ ] }, "limit": { - "context": 32768, - "output": 16384 + "context": 163840, + "output": 147456 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { "supported": true, "default": true }, "extra_capabilities": { "reasoning": { - "supported": true + "supported": true, + "effort_options": [ + "none", + "high" + ] } }, "attachment": false, - "open_weights": false, - "release_date": "2025-01-01", - "last_updated": "2025-01-01", + "open_weights": true, + "knowledge": "2024-07", + "release_date": "2025-12-01", + "last_updated": "2025-12-01", "type": "chat" }, { - "id": "qwen2-5-omni-7b", - "name": "Qwen2.5-Omni 7B", - "display_name": "Qwen2.5-Omni 7B", + "id": "deepseek/deepseek-v4.1-flash", + "name": "DeepSeek V4.1 Flash", + "display_name": "DeepSeek V4.1 Flash", "modalities": { "input": [ "text", - "image", - "audio", - "video" + "image" ], "output": [ - "text", - "audio" + "text" ] }, "limit": { - "context": 32768, - "output": 2048 + "context": 1048576, + "output": 943718 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "effort_options": [ + "none", + "low", + "high", + "max" + ] + } }, "attachment": true, "open_weights": true, - "knowledge": "2024-04", - "release_date": "2024-12", - "last_updated": "2024-12", + "knowledge": "2025-05", + "release_date": "2026-09-10", + "last_updated": "2026-09-10", "type": "chat" }, { - "id": "kimi-k2.6", - "name": "Moonshot Kimi K2.6", - "display_name": "Moonshot Kimi K2.6", + "id": "deepseek/deepseek-v4-flash-vision-exp", + "name": "DeepSeek V4 Flash Vision Exp", + "display_name": "DeepSeek V4 Flash Vision Exp", "modalities": { "input": [ "text", - "image", - "video" + "image" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 16384 + "context": 1048576, + "output": 262144 }, "temperature": true, "tool_call": true, @@ -273605,38 +273808,46 @@ "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "high", + "effort_options": [ + "low", + "high", + "max" + ], "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ "thinking_blocks" + ], + "notes": [ + "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." ] } }, "attachment": true, "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-04-21", - "last_updated": "2026-04-21", + "release_date": "2026-08-21", + "last_updated": "2026-09-01", "type": "chat" }, { - "id": "qwen3.5-plus", - "name": "Qwen3.5 Plus", - "display_name": "Qwen3.5 Plus", + "id": "deepseek/deepseek-v3.1-terminus", + "name": "DeepSeek: DeepSeek V3.1 Terminus", + "display_name": "DeepSeek: DeepSeek V3.1 Terminus", "modalities": { "input": [ - "text", - "image", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 65536 + "context": 163840, + "output": 147456 }, "temperature": true, "tool_call": true, @@ -273646,39 +273857,33 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "none", + "high" ] } }, - "attachment": true, + "attachment": false, "open_weights": false, - "knowledge": "2025-04", - "release_date": "2026-02-16", - "last_updated": "2026-09-22", + "release_date": "2025-09-22", + "last_updated": "2025-09-22", "type": "chat" }, { - "id": "kimi-k2.5", - "name": "Moonshot Kimi K2.5", - "display_name": "Moonshot Kimi K2.5", + "id": "deepseek/deepseek-v4-pro", + "name": "DeepSeek V4 Pro", + "display_name": "DeepSeek V4 Pro", "modalities": { "input": [ - "text", - "image", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 32768 + "context": 1048576, + "output": 393216 }, "temperature": true, "tool_call": true, @@ -273689,79 +273894,75 @@ "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "high", + "effort_options": [ + "low", + "high", + "max" + ], "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ "thinking_blocks" + ], + "notes": [ + "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." ] } }, - "attachment": true, + "attachment": false, "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-01-27", - "last_updated": "2026-01-27", + "knowledge": "2025-05", + "release_date": "2026-04-24", + "last_updated": "2026-04-24", "type": "chat" }, { - "id": "qwen3-vl-235b-a22b", - "name": "Qwen3-VL 235B-A22B", - "display_name": "Qwen3-VL 235B-A22B", + "id": "deepseek/deepseek-chat-v3-0324", + "name": "DeepSeek V3 0324", + "display_name": "DeepSeek V3 0324", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 32768 + "context": 128000, + "output": 115200 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } + "supported": false }, - "attachment": true, + "attachment": false, "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-04", - "last_updated": "2025-04", + "knowledge": "2024-07-31", + "release_date": "2025-03-24", + "last_updated": "2025-03-24", "type": "chat" }, { - "id": "qwen3.5-flash", - "name": "Qwen3.5 Flash", - "display_name": "Qwen3.5 Flash", + "id": "deepseek/deepseek-v4-flash", + "name": "DeepSeek V4 Flash", + "display_name": "DeepSeek V4 Flash", "modalities": { "input": [ - "text", - "image", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 65536 + "context": 1048575, + "output": 943717 }, "temperature": true, "tool_call": true, @@ -273772,25 +273973,36 @@ "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "high", + "effort_options": [ + "low", + "high", + "max" + ], "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ "thinking_blocks" + ], + "notes": [ + "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-04", - "release_date": "2026-02-23", - "last_updated": "2026-09-22", + "attachment": false, + "open_weights": true, + "knowledge": "2025-05", + "release_date": "2026-04-24", + "last_updated": "2026-04-24", "type": "chat" }, { - "id": "qwen3-next-80b-a3b-instruct", - "name": "Qwen3 Next 80B A3B Instruct", - "display_name": "Qwen3 Next 80B A3B Instruct", + "id": "deepseek/deepseek-v4-pro-0813", + "name": "DeepSeek V4 Pro 0813", + "display_name": "DeepSeek V4 Pro 0813", "modalities": { "input": [ "text" @@ -273800,25 +274012,36 @@ ] }, "limit": { - "context": 131072, - "output": 32768 + "context": 1048576, + "output": 943718 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "effort_options": [ + "none", + "low", + "high", + "max" + ] + } }, "attachment": false, "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-09", - "last_updated": "2025-09", + "release_date": "2026-08-13", + "last_updated": "2026-08-22", "type": "chat" }, { - "id": "qwen-plus", - "name": "Qwen Plus", - "display_name": "Qwen Plus", + "id": "deepseek/deepseek-v4-flash-0731", + "name": "DeepSeek V4 Flash 0731", + "display_name": "DeepSeek V4 Flash 0731", "modalities": { "input": [ "text" @@ -273828,60 +274051,48 @@ ] }, "limit": { - "context": 1000000, - "output": 32768 + "context": 1048576, + "output": 943718 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false, - "budget": { - "default": 81920, - "min": 0, - "max": 81920 - } + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "none", + "low", + "high", + "max" ] } }, - "search": { - "supported": true, - "default": false, - "forced_search": false, - "search_strategy": "turbo" - }, "attachment": false, - "open_weights": false, - "knowledge": "2024-04", - "release_date": "2024-01-25", - "last_updated": "2025-09-11", + "open_weights": true, + "knowledge": "2025-05", + "release_date": "2026-07-31", + "last_updated": "2026-07-31", "type": "chat" }, { - "id": "qwen-vl-ocr", - "name": "Qwen Vl Ocr", - "display_name": "Qwen Vl Ocr", + "id": "undi95/remm-slerp-l2-13b", + "name": "ReMM SLERP 13B", + "display_name": "ReMM SLERP 13B", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 34096, - "output": 4096 + "context": 6144, + "output": 5529 }, "temperature": true, "tool_call": false, @@ -273889,27 +274100,29 @@ "supported": false }, "attachment": false, - "open_weights": false, - "knowledge": "2024-04", - "release_date": "2024-10-28", - "last_updated": "2026-09-11", + "open_weights": true, + "knowledge": "2023-06-30", + "release_date": "2023-07-22", + "last_updated": "2023-07-22", "type": "chat" }, { - "id": "deepseek-r1-0528", - "name": "DeepSeek R1 0528", - "display_name": "DeepSeek R1 0528", + "id": "elorian/glimpse", + "name": "Elorian: Glimpse", + "display_name": "Elorian: Glimpse", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 16384 + "context": 262144, + "output": 256000 }, "temperature": true, "tool_call": true, @@ -273919,25 +274132,22 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "none", + "high" ] } }, - "attachment": false, + "attachment": true, "open_weights": false, - "release_date": "2025-05-28", - "last_updated": "2025-05-28", + "release_date": "2026-10-17", + "last_updated": "2026-10-17", "type": "chat" }, { - "id": "qwen3-235b-a22b", - "name": "Qwen3 235B A22B", - "display_name": "Qwen3 235B A22B", + "id": "sao10k/l3.1-euryale-70b", + "name": "Sao10K: Llama 3.1 Euryale 70B v2.2", + "display_name": "Sao10K: Llama 3.1 Euryale 70B v2.2", "modalities": { "input": [ "text" @@ -273948,46 +274158,56 @@ }, "limit": { "context": 131072, - "output": 32768 + "output": 16384 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": false, - "budget": { - "default": 81920, - "min": 0, - "max": 81920 - } + "supported": false }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } + "attachment": false, + "open_weights": true, + "knowledge": "2023-12-31", + "release_date": "2024-08-28", + "last_updated": "2024-08-28", + "type": "chat" + }, + { + "id": "sao10k/l3-lunaris-8b", + "name": "Sao10K: Llama 3 8B Lunaris", + "display_name": "Sao10K: Llama 3 8B Lunaris", + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 8192, + "output": 7372 + }, + "temperature": true, + "tool_call": false, + "reasoning": { + "supported": false }, "attachment": false, "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-04", - "last_updated": "2025-04", + "knowledge": "2023-12-31", + "release_date": "2024-08-13", + "last_updated": "2024-08-13", "type": "chat" }, { - "id": "qwen3.8-max", - "name": "Qwen3.8 Max", - "display_name": "Qwen3.8 Max", + "id": "x-ai/grok-build-0.1", + "name": "Grok Build 0.1", + "display_name": "Grok Build 0.1", "modalities": { "input": [ "text", "image", - "video", "pdf" ], "output": [ @@ -273995,8 +274215,8 @@ ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 256000, + "output": 230400 }, "temperature": true, "tool_call": true, @@ -274007,156 +274227,156 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "budget": { - "min": 0, - "max": 262144, - "unit": "tokens" - }, - "effort": "xhigh", "effort_options": [ - "none", - "low", - "medium", - "xhigh" - ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Prefer reasoning_effort; thinking_budget is an alternative, not an additional control.", - "Omitting both controls differs from explicitly selecting xhigh, which maps to 262144 budget tokens." - ], - "budget_exclusive_with_effort": true + "high" + ] } }, "attachment": true, "open_weights": false, - "release_date": "2026-08-03", - "last_updated": "2026-08-03", + "release_date": "2026-04-16", + "last_updated": "2026-04-16", "type": "chat" }, { - "id": "qwen-plus-character", - "name": "Qwen Plus Character", - "display_name": "Qwen Plus Character", + "id": "x-ai/grok-4.7", + "name": "Grok 4.7", + "display_name": "Grok 4.7", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 32768, - "output": 4096 + "context": 500000, + "output": 450000 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "low", + "medium", + "high", + "xhigh" + ] + } + }, + "attachment": true, "open_weights": false, - "knowledge": "2024-04", - "release_date": "2024-01", - "last_updated": "2024-01", + "knowledge": "2026-05", + "release_date": "2026-09-21", + "last_updated": "2026-09-21", "type": "chat" }, { - "id": "qwen-doc-turbo", - "name": "Qwen Doc Turbo", - "display_name": "Qwen Doc Turbo", + "id": "x-ai/grok-4.5", + "name": "Grok 4.5", + "display_name": "Grok 4.5", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 8192 + "context": 500000, + "output": 450000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, + "extra_capabilities": { + "reasoning": { + "supported": true, + "effort_options": [ + "low", + "medium", + "high" + ] + } + }, + "attachment": true, "open_weights": false, - "knowledge": "2024-04", - "release_date": "2024-01", - "last_updated": "2024-01", + "release_date": "2026-07-08", + "last_updated": "2026-07-08", "type": "chat" }, { - "id": "qwen3-14b", - "name": "Qwen3 14B", - "display_name": "Qwen3 14B", + "id": "x-ai/grok-4.3", + "name": "Grok 4.3", + "display_name": "Grok 4.3", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 8192 + "context": 1000000, + "output": 900000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true, - "budget": { - "default": 38912, - "min": 0, - "max": 38912 - } + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "none", + "low", + "medium", + "high" ] } }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-04", - "last_updated": "2025-04", + "attachment": true, + "open_weights": false, + "release_date": "2026-04-17", + "last_updated": "2026-04-17", "type": "chat" }, { - "id": "deepseek-v4.1-flash", - "name": "DeepSeek V4.1 Flash", - "display_name": "DeepSeek V4.1 Flash", + "id": "x-ai/grok-4.20", + "name": "SpaceXAI: Grok 4.20", + "display_name": "SpaceXAI: Grok 4.20", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 384000 + "context": 2000000, + "output": 1800000 }, "temperature": true, "tool_call": true, @@ -274166,87 +274386,104 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], "effort_options": [ - "low", - "high", - "max" + "none", + "high" ] } }, "attachment": true, - "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-09-10", - "last_updated": "2026-09-10", + "open_weights": false, + "release_date": "2026-03-31", + "last_updated": "2026-03-31", "type": "chat" }, { - "id": "qwen-vl-plus", - "name": "Qwen-VL Plus", - "display_name": "Qwen-VL Plus", + "id": "x-ai/grok-4.6", + "name": "Grok 4.6", + "display_name": "Grok 4.6", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 8192 + "context": 500000, + "output": 450000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "effort_options": [ + "low", + "medium", + "high", + "xhigh" + ] + } }, "attachment": true, "open_weights": false, - "knowledge": "2024-04", - "release_date": "2024-01-25", - "last_updated": "2025-08-15", + "knowledge": "2026-02-01", + "release_date": "2026-08-12", + "last_updated": "2026-08-12", "type": "chat" }, { - "id": "moonshot-kimi-k2-instruct", - "name": "Moonshot Kimi K2 Instruct", - "display_name": "Moonshot Kimi K2 Instruct", + "id": "x-ai/grok-4.20-multi-agent", + "name": "SpaceXAI: Grok 4.20 Multi-Agent", + "display_name": "SpaceXAI: Grok 4.20 Multi-Agent", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 8192 + "context": 2000000, + "output": 1800000 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, - "open_weights": true, - "release_date": "2025-01-01", - "last_updated": "2025-01-01", + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "low", + "medium", + "high", + "xhigh" + ] + } + }, + "attachment": true, + "open_weights": false, + "release_date": "2026-03-31", + "last_updated": "2026-03-31", "type": "chat" }, { - "id": "qwen3-next-80b-a3b-thinking", - "name": "Qwen3 Next 80B A3B Thinking", - "display_name": "Qwen3 Next 80B A3B Thinking", + "id": "nousresearch/hermes-4-405b", + "name": "Nous: Hermes 4 405B", + "display_name": "Nous: Hermes 4 405B", "modalities": { "input": [ "text" @@ -274257,54 +274494,44 @@ }, "limit": { "context": 131072, - "output": 32768 + "output": 117964 }, "temperature": true, "tool_call": false, "reasoning": { "supported": true, - "default": true, - "budget": { - "default": 81920, - "min": 0, - "max": 81920 - } + "default": true }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "none", + "high" ] } }, "attachment": false, "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-09", - "last_updated": "2025-09", + "knowledge": "2024-08-31", + "release_date": "2025-08-26", + "last_updated": "2025-08-26", "type": "chat" }, { - "id": "qwen3.6-plus", - "name": "Qwen3.6 Plus", - "display_name": "Qwen3.6 Plus", + "id": "upstage/solar-pro4", + "name": "Upstage: Solar Pro 4", + "display_name": "Upstage: Solar Pro 4", "modalities": { "input": [ - "text", - "image", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 65536 + "context": 524288, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -274314,26 +274541,28 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "none", + "minimal", + "low", + "medium", + "high", + "xhigh", + "max" ] } }, - "attachment": true, + "attachment": false, "open_weights": false, - "knowledge": "2025-04", - "release_date": "2026-04-02", - "last_updated": "2026-04-02", + "knowledge": "2026-02", + "release_date": "2026-08-06", + "last_updated": "2026-08-06", "type": "chat" }, { - "id": "kimi-k2-thinking", - "name": "Moonshot Kimi K2 Thinking", - "display_name": "Moonshot Kimi K2 Thinking", + "id": "upstage/solar-mini4", + "name": "Upstage: Solar Mini 4", + "display_name": "Upstage: Solar Mini 4", "modalities": { "input": [ "text" @@ -274343,8 +274572,8 @@ ] }, "limit": { - "context": 262144, - "output": 16384 + "context": 524288, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -274354,25 +274583,28 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "none", + "minimal", + "low", + "medium", + "high", + "xhigh", + "max" ] } }, "attachment": false, - "open_weights": true, - "release_date": "2025-11-06", - "last_updated": "2025-11-06", + "open_weights": false, + "knowledge": "2026-02", + "release_date": "2026-09-22", + "last_updated": "2026-09-22", "type": "chat" }, { - "id": "qwen-vl-max", - "name": "Qwen-VL Max", - "display_name": "Qwen-VL Max", + "id": "unbiased/pareto-26.10", + "name": "Pareto 26.10", + "display_name": "Pareto 26.10", "modalities": { "input": [ "text", @@ -274383,8 +274615,8 @@ ] }, "limit": { - "context": 131072, - "output": 8192 + "context": 1048576, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -274393,43 +274625,42 @@ }, "attachment": true, "open_weights": false, - "knowledge": "2024-04", - "release_date": "2024-04-08", - "last_updated": "2025-08-13", + "release_date": "2026-10-15", + "last_updated": "2026-10-15", "type": "chat" }, { - "id": "tongyi-intent-detect-v3", - "name": "Tongyi Intent Detect V3", - "display_name": "Tongyi Intent Detect V3", + "id": "unbiased/pareto-26.9", + "name": "Pareto 26.9", + "display_name": "Pareto 26.9", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 8192, - "output": 1024 + "context": 262144, + "output": 131072 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { "supported": false }, - "attachment": false, + "attachment": true, "open_weights": false, - "knowledge": "2024-04", - "release_date": "2024-01", - "last_updated": "2024-01", + "release_date": "2026-10-13", + "last_updated": "2026-10-13", "type": "chat" }, { - "id": "qwen3-32b", - "name": "Qwen3 32B", - "display_name": "Qwen3 32B", + "id": "ibm-granite/granite-4.2-8b", + "name": "IBM: Granite 4.2 8B", + "display_name": "IBM: Granite 4.2 8B", "modalities": { "input": [ "text" @@ -274440,80 +274671,90 @@ }, "limit": { "context": 131072, - "output": 16384 + "output": 117964 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true, - "budget": { - "default": 38912, - "min": 0, - "max": 38912 - } + "default": true }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "none", + "low", + "high" ] } }, "attachment": false, "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-04", - "last_updated": "2025-04", + "release_date": "2026-08-25", + "last_updated": "2026-08-25", "type": "chat" }, { - "id": "qwen-math-plus", - "name": "Qwen Math Plus", - "display_name": "Qwen Math Plus", + "id": "meta/muse-spark-1.1", + "name": "Muse Spark 1.1", + "display_name": "Muse Spark 1.1", "modalities": { "input": [ - "text" + "text", + "image", + "pdf", + "video" ], "output": [ "text" ] }, "limit": { - "context": 4096, - "output": 3072 + "context": 1048576, + "output": 943718 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, + "extra_capabilities": { + "reasoning": { + "supported": true, + "effort_options": [ + "minimal", + "low", + "medium", + "high", + "xhigh" + ] + } + }, + "attachment": true, "open_weights": false, - "knowledge": "2024-04", - "release_date": "2024-08-16", - "last_updated": "2024-09-19", + "release_date": "2026-04-08", + "last_updated": "2026-07-09", "type": "chat" }, { - "id": "glm-5.1", - "name": "GLM-5.1", - "display_name": "GLM-5.1", + "id": "meta/muse-spark-1.3-contributor", + "name": "Meta: Muse Spark 1.3 Contributor", + "display_name": "Meta: Muse Spark 1.3 Contributor", "modalities": { "input": [ - "text" + "text", + "image", + "video", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 202752, - "output": 128000 + "context": 1048576, + "output": 943718 }, "temperature": true, "tool_call": true, @@ -274523,70 +274764,84 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "minimal", + "low", + "medium", + "high", + "xhigh", + "max" ] } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-04-14", - "last_updated": "2026-04-14", + "attachment": true, + "open_weights": false, + "release_date": "2026-09-02", + "last_updated": "2026-09-02", "type": "chat" }, { - "id": "qwen-omni-turbo", - "name": "Qwen-Omni Turbo", - "display_name": "Qwen-Omni Turbo", + "id": "meta/muse-spark-open", + "name": "Muse Spark Open", + "display_name": "Muse Spark Open", "modalities": { "input": [ "text", "image", - "audio", - "video" + "video", + "pdf", + "audio" ], "output": [ - "text", - "audio" + "text" ] }, "limit": { - "context": 32768, - "output": 2048 + "context": 1048576, + "output": 943718 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "low", + "medium", + "high", + "xhigh" + ] + } }, "attachment": true, - "open_weights": false, - "knowledge": "2024-04", - "release_date": "2025-01-19", - "last_updated": "2025-03-26", + "open_weights": true, + "knowledge": "2026-01-04", + "release_date": "2026-10-15", + "last_updated": "2026-10-15", "type": "chat" }, { - "id": "qwen3.8-flash", - "name": "Qwen3.8 Flash", - "display_name": "Qwen3.8 Flash", + "id": "meta/muse-wonder", + "name": "Meta: Muse Wonder", + "display_name": "Meta: Muse Wonder", "modalities": { "input": [ "text", "image", - "video" + "video", + "pdf", + "audio" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 1048576, + "output": 943718 }, "temperature": true, "tool_call": true, @@ -274596,85 +274851,83 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "budget": { - "min": 0, - "max": 262144, - "unit": "tokens" - }, - "effort": "xhigh", "effort_options": [ - "none", + "minimal", "low", "medium", - "xhigh" - ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Prefer reasoning_effort; thinking_budget is an alternative, not an additional control.", - "Omitting both controls differs from explicitly selecting xhigh, which maps to 262144 budget tokens." - ], - "budget_exclusive_with_effort": true + "high", + "xhigh", + "max" + ] } }, "attachment": true, "open_weights": false, - "release_date": "2026-08-26", - "last_updated": "2026-08-26", + "release_date": "2026-10-12", + "last_updated": "2026-10-12", "type": "chat" }, { - "id": "qwen3-omni-flash-realtime", - "name": "Qwen3-Omni Flash Realtime", - "display_name": "Qwen3-Omni Flash Realtime", + "id": "meta/muse-spark-1.2", + "name": "Muse Spark 1.2", + "display_name": "Muse Spark 1.2", "modalities": { "input": [ "text", "image", - "audio" + "video", + "pdf" ], "output": [ - "text", - "audio" + "text" ] }, "limit": { - "context": 65536, - "output": 16384 + "context": 1048576, + "output": 943718 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "effort_options": [ + "minimal", + "low", + "medium", + "high", + "xhigh" + ] + } }, "attachment": true, "open_weights": false, - "knowledge": "2024-04", - "release_date": "2025-09-15", - "last_updated": "2025-09-15" + "release_date": "2026-08-05", + "last_updated": "2026-08-05", + "type": "chat" }, { - "id": "qwen3.6-max-preview", - "name": "Qwen3.6 Max Preview", - "display_name": "Qwen3.6 Max Preview", + "id": "meta/muse-spark-1.2-contributor", + "name": "Meta: Muse Spark 1.2 Contributor", + "display_name": "Meta: Muse Spark 1.2 Contributor", "modalities": { "input": [ - "text" + "text", + "image", + "video", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 245800, - "output": 65536 + "context": 1048576, + "output": 943718 }, "temperature": true, "tool_call": true, @@ -274684,38 +274937,39 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "minimal", + "low", + "medium", + "high", + "xhigh" ] } }, - "attachment": false, + "attachment": true, "open_weights": false, - "release_date": "2026-04-20", - "last_updated": "2026-04-21", + "release_date": "2026-08-21", + "last_updated": "2026-08-21", "type": "chat" }, { - "id": "qwen3.7-flash", - "name": "Qwen3.7 Flash", - "display_name": "Qwen3.7 Flash", + "id": "meta/muse-spark-1.3", + "name": "Muse Spark 1.3", + "display_name": "Muse Spark 1.3", "modalities": { "input": [ "text", "image", - "video" + "video", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 1048576, + "output": 943718 }, "temperature": true, "tool_call": true, @@ -274725,29 +274979,39 @@ }, "extra_capabilities": { "reasoning": { - "supported": true + "supported": true, + "effort_options": [ + "minimal", + "low", + "medium", + "high", + "xhigh", + "max" + ] } }, "attachment": true, "open_weights": false, - "release_date": "2026-07-15", - "last_updated": "2026-07-15", + "release_date": "2026-09-02", + "last_updated": "2026-09-02", "type": "chat" }, { - "id": "glm-5", - "name": "GLM-5", - "display_name": "GLM-5", + "id": "meta/muse-glimmer-30b", + "name": "Muse Glimmer 30B", + "display_name": "Muse Glimmer 30B", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 202752, + "context": 131072, "output": 16384 }, "temperature": true, @@ -274758,25 +275022,25 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "low", + "medium", + "high", + "xhigh" ] } }, - "attachment": false, - "open_weights": false, - "release_date": "2026-02-11", - "last_updated": "2026-02-11", + "attachment": true, + "open_weights": true, + "knowledge": "2026-01-04", + "release_date": "2026-08-10", + "last_updated": "2026-08-10", "type": "chat" }, { - "id": "glm-5.3", - "name": "GLM-5.3", - "display_name": "GLM-5.3", + "id": "inclusionai/ling-3.1-flash", + "name": "inclusionAI: Ling 3.1 Flash", + "display_name": "inclusionAI: Ling 3.1 Flash", "modalities": { "input": [ "text" @@ -274787,7 +275051,7 @@ }, "limit": { "context": 1000000, - "output": 131072 + "output": 262144 }, "temperature": true, "tool_call": true, @@ -274797,57 +275061,60 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], "effort_options": [ - "low", - "high", - "max" + "none", + "high" ] } }, "attachment": false, "open_weights": true, - "release_date": "2026-08-14", - "last_updated": "2026-08-14", + "release_date": "2026-09-29", + "last_updated": "2026-09-29", "type": "chat" }, { - "id": "deepseek-v3", - "name": "DeepSeek V3", - "display_name": "DeepSeek V3", + "id": "inclusionai/ling-3.0-flash-vl", + "name": "inclusionAI: Ling 3.0 Flash VL", + "display_name": "inclusionAI: Ling 3.0 Flash VL", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 65536, - "output": 8192 + "context": 131072, + "output": 32768 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, - "open_weights": false, - "release_date": "2024-12-01", - "last_updated": "2024-12-01", + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "none", + "high" + ] + } + }, + "attachment": true, + "open_weights": true, + "release_date": "2026-09-10", + "last_updated": "2026-09-10", "type": "chat" }, { - "id": "deepseek-r1-distill-qwen-1-5b", - "name": "DeepSeek R1 Distill Qwen 1.5B", - "display_name": "DeepSeek R1 Distill Qwen 1.5B", + "id": "inclusionai/ling-3.1-flash-1019-free", + "name": "inclusionAI: Ling 3.1 Flash 1019 (free)", + "display_name": "inclusionAI: Ling 3.1 Flash 1019 (free)", "modalities": { "input": [ "text" @@ -274857,25 +275124,33 @@ ] }, "limit": { - "context": 32768, - "output": 16384 + "context": 1000000, + "output": 262144 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { "supported": true, "default": true }, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "none", + "high" + ] + } + }, "attachment": false, "open_weights": false, - "release_date": "2025-01-01", - "last_updated": "2025-01-01", + "release_date": "2025-08-26", + "last_updated": "2025-08-26", "type": "chat" }, { - "id": "deepseek-r1", - "name": "DeepSeek R1", - "display_name": "DeepSeek R1", + "id": "inclusionai/ling-3.0-flash", + "name": "inclusionAI: Ling 3.0 Flash", + "display_name": "inclusionAI: Ling 3.0 Flash", "modalities": { "input": [ "text" @@ -274885,8 +275160,8 @@ ] }, "limit": { - "context": 131072, - "output": 16384 + "context": 262144, + "output": 32768 }, "temperature": true, "tool_call": true, @@ -274896,66 +275171,49 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "none", + "high" ] } }, "attachment": false, - "open_weights": false, - "release_date": "2025-01-01", - "last_updated": "2025-01-01", + "open_weights": true, + "release_date": "2026-07-23", + "last_updated": "2026-07-23", "type": "chat" }, { - "id": "qwen3-vl-30b-a3b", - "name": "Qwen3-VL 30B-A3B", - "display_name": "Qwen3-VL 30B-A3B", + "id": "relace/relace-search", + "name": "Relace: Relace Search", + "display_name": "Relace: Relace Search", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 32768 + "context": 256000, + "output": 128000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } + "supported": false }, - "attachment": true, - "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-04", - "last_updated": "2025-04", + "attachment": false, + "open_weights": false, + "release_date": "2025-12-08", + "last_updated": "2025-12-08", "type": "chat" }, { - "id": "deepseek-r1-distill-qwen-7b", - "name": "DeepSeek R1 Distill Qwen 7B", - "display_name": "DeepSeek R1 Distill Qwen 7B", + "id": "relace/relace-apply-3", + "name": "Relace: Relace Apply 3", + "display_name": "Relace: Relace Apply 3", "modalities": { "input": [ "text" @@ -274965,29 +275223,29 @@ ] }, "limit": { - "context": 32768, - "output": 16384 + "context": 256000, + "output": 128000 }, - "temperature": true, - "tool_call": true, + "temperature": false, + "tool_call": false, "reasoning": { - "supported": true, - "default": true + "supported": false }, "attachment": false, "open_weights": false, - "release_date": "2025-01-01", - "last_updated": "2025-01-01", + "release_date": "2025-09-26", + "last_updated": "2025-09-26", "type": "chat" }, { - "id": "qwen3.5-397b-a17b", - "name": "Qwen3.5 397B-A17B", - "display_name": "Qwen3.5 397B-A17B", + "id": "xiaomi/mimo-v2.6-pro", + "name": "MiMo-V2.6-Pro", + "display_name": "MiMo-V2.6-Pro", "modalities": { "input": [ "text", "image", + "audio", "video" ], "output": [ @@ -274995,8 +275253,8 @@ ] }, "limit": { - "context": 262144, - "output": 65536 + "context": 1048576, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -275006,26 +275264,22 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "none", + "high" ] } }, "attachment": true, "open_weights": true, - "knowledge": "2025-04", - "release_date": "2026-02-16", - "last_updated": "2026-02-16", + "release_date": "2026-09-22", + "last_updated": "2026-09-22", "type": "chat" }, { - "id": "qwen3-8b", - "name": "Qwen3 8B", - "display_name": "Qwen3 8B", + "id": "xiaomi/mimo-v2.5-pro", + "name": "MiMo-V2.5-Pro", + "display_name": "MiMo-V2.5-Pro", "modalities": { "input": [ "text" @@ -275035,19 +275289,14 @@ ] }, "limit": { - "context": 131072, - "output": 8192 + "context": 262144, + "output": 235929 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true, - "budget": { - "default": 38912, - "min": 0, - "max": 38912 - } + "default": true }, "extra_capabilities": { "reasoning": { @@ -275057,30 +275306,37 @@ "visibility": "summary", "continuation": [ "thinking_blocks" + ], + "effort_options": [ + "none", + "high" ] } }, "attachment": false, "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-04", - "last_updated": "2025-04", + "knowledge": "2024-12", + "release_date": "2026-04-22", + "last_updated": "2026-04-22", "type": "chat" }, { - "id": "MiniMax-M2.5", - "name": "MiniMax-M2.5", - "display_name": "MiniMax-M2.5", + "id": "xiaomi/mimo-v2.6-flash", + "name": "MiMo-V2.6-Flash", + "display_name": "MiMo-V2.6-Flash", "modalities": { "input": [ - "text" + "text", + "image", + "audio", + "video" ], "output": [ "text" ] }, "limit": { - "context": 204800, + "context": 1048576, "output": 131072 }, "temperature": true, @@ -275091,28 +275347,28 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "none", + "high" ] } }, - "attachment": false, + "attachment": true, "open_weights": true, - "release_date": "2026-02-12", - "last_updated": "2026-02-12", + "release_date": "2026-09-22", + "last_updated": "2026-09-22", "type": "chat" }, { - "id": "qwen3-coder-plus", - "name": "Qwen3 Coder Plus", - "display_name": "Qwen3 Coder Plus", + "id": "xiaomi/mimo-v2.5", + "name": "MiMo-V2.5", + "display_name": "MiMo-V2.5", "modalities": { "input": [ - "text" + "text", + "image", + "audio", + "video" ], "output": [ "text" @@ -275124,60 +275380,50 @@ }, "temperature": true, "tool_call": true, - "reasoning": { - "supported": false - }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-07-23", - "last_updated": "2026-09-11", - "type": "chat" - }, - { - "id": "deepseek-r1-distill-qwen-32b", - "name": "DeepSeek R1 Distill Qwen 32B", - "display_name": "DeepSeek R1 Distill Qwen 32B", - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 32768, - "output": 16384 - }, - "temperature": true, - "tool_call": true, "reasoning": { "supported": true, "default": true }, - "attachment": false, - "open_weights": false, - "release_date": "2025-01-01", - "last_updated": "2025-01-01", + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "effort_options": [ + "none", + "high" + ] + } + }, + "attachment": true, + "open_weights": true, + "knowledge": "2024-12", + "release_date": "2026-04-22", + "last_updated": "2026-04-22", "type": "chat" }, { - "id": "qvq-max", - "name": "QVQ Max", - "display_name": "QVQ Max", + "id": "xiaomi/mimo-v2.6-pro-ultraspeed", + "name": "MiMo-V2.6-Pro-UltraSpeed", + "display_name": "MiMo-V2.6-Pro-UltraSpeed", "modalities": { "input": [ "text", - "image" + "image", + "audio", + "video" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 8192 + "context": 1048576, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -275185,47 +275431,24 @@ "supported": true, "default": true }, - "attachment": true, - "open_weights": false, - "knowledge": "2024-04", - "release_date": "2025-03-25", - "last_updated": "2025-03-25", - "type": "chat" - }, - { - "id": "qwen-omni-turbo-realtime", - "name": "Qwen-Omni Turbo Realtime", - "display_name": "Qwen-Omni Turbo Realtime", - "modalities": { - "input": [ - "text", - "image", - "audio" - ], - "output": [ - "text", - "audio" - ] - }, - "limit": { - "context": 32768, - "output": 2048 - }, - "temperature": true, - "tool_call": true, - "reasoning": { - "supported": false + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "none", + "high" + ] + } }, "attachment": true, - "open_weights": false, - "knowledge": "2024-04", - "release_date": "2025-05-08", - "last_updated": "2025-05-08" + "open_weights": true, + "release_date": "2026-09-21", + "last_updated": "2026-09-21", + "type": "chat" }, { - "id": "qwen-max", - "name": "Qwen Max", - "display_name": "Qwen Max", + "id": "recursive/flint-v1.0:free", + "name": "Recursive: Flint (free)", + "display_name": "Recursive: Flint (free)", "modalities": { "input": [ "text" @@ -275235,31 +275458,41 @@ ] }, "limit": { - "context": 32768, - "output": 8192 + "context": 262144, + "output": 235929 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false - }, - "search": { "supported": true, - "default": false, - "forced_search": false, - "search_strategy": "turbo" + "default": true + }, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "high" + ] + } }, "attachment": false, "open_weights": false, - "knowledge": "2024-04", - "release_date": "2024-04-03", - "last_updated": "2025-01-25", + "release_date": "2026-10-21", + "last_updated": "2026-10-21", "type": "chat" - }, + } + ] + }, + "mixlayer": { + "id": "mixlayer", + "name": "Mixlayer", + "display_name": "Mixlayer", + "api": "https://models.mixlayer.ai/v1", + "doc": "https://docs.mixlayer.com", + "models": [ { - "id": "deepseek-v3-1", - "name": "DeepSeek V3.1", - "display_name": "DeepSeek V3.1", + "id": "qwen/qwen3.5-27b", + "name": "Qwen3.5 27B", + "display_name": "Qwen3.5 27B", "modalities": { "input": [ "text" @@ -275269,37 +275502,47 @@ ] }, "limit": { - "context": 131072, - "output": 65536 + "context": 262144, + "output": 262144 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } }, "attachment": false, - "open_weights": false, - "release_date": "2025-01-01", - "last_updated": "2025-01-01", + "open_weights": true, + "release_date": "2026-02-23", + "last_updated": "2026-02-23", "type": "chat" }, { - "id": "qwen3.7-plus", - "name": "Qwen3.7 Plus", - "display_name": "Qwen3.7 Plus", + "id": "qwen/qwen3.5-9b", + "name": "Qwen3.5 9B", + "display_name": "Qwen3.5 9B", "modalities": { "input": [ - "text", - "image", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 64000 + "context": 262144, + "output": 262144 }, "temperature": true, "tool_call": true, @@ -275309,20 +275552,25 @@ }, "extra_capabilities": { "reasoning": { - "supported": true + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-04", - "release_date": "2026-06-02", - "last_updated": "2026-06-02", + "attachment": false, + "open_weights": true, + "release_date": "2026-02-23", + "last_updated": "2026-02-23", "type": "chat" }, { - "id": "qwen3-coder-flash", - "name": "Qwen3 Coder Flash", - "display_name": "Qwen3 Coder Flash", + "id": "qwen/qwen3.5-35b-a3b", + "name": "Qwen3.5 35B A3B", + "display_name": "Qwen3.5 35B A3B", "modalities": { "input": [ "text" @@ -275332,40 +275580,8 @@ ] }, "limit": { - "context": 1000000, - "output": 65536 - }, - "temperature": true, - "tool_call": true, - "reasoning": { - "supported": false - }, - "attachment": false, - "open_weights": false, - "knowledge": "2025-04", - "release_date": "2025-07-28", - "last_updated": "2025-07-28", - "type": "chat" - }, - { - "id": "qwen3-omni-flash", - "name": "Qwen3-Omni Flash", - "display_name": "Qwen3-Omni Flash", - "modalities": { - "input": [ - "text", - "image", - "audio", - "video" - ], - "output": [ - "text", - "audio" - ] - }, - "limit": { - "context": 65536, - "output": 16384 + "context": 262144, + "output": 262144 }, "temperature": true, "tool_call": true, @@ -275384,17 +275600,16 @@ ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2024-04", - "release_date": "2025-09-15", - "last_updated": "2025-09-15", + "attachment": false, + "open_weights": true, + "release_date": "2026-02-23", + "last_updated": "2026-02-23", "type": "chat" }, { - "id": "glm-5.2", - "name": "GLM-5.2", - "display_name": "GLM-5.2", + "id": "qwen/qwen3.5-397b-a17b", + "name": "Qwen3.5 397B A17B", + "display_name": "Qwen3.5 397B A17B", "modalities": { "input": [ "text" @@ -275404,8 +275619,8 @@ ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 262144, + "output": 262144 }, "temperature": true, "tool_call": true, @@ -275421,23 +275636,19 @@ "visibility": "summary", "continuation": [ "thinking_blocks" - ], - "effort_options": [ - "high", - "max" ] } }, "attachment": false, "open_weights": true, - "release_date": "2026-06-13", - "last_updated": "2026-06-13", + "release_date": "2026-02-15", + "last_updated": "2026-02-15", "type": "chat" }, { - "id": "qwen-mt-plus", - "name": "Qwen Mt Plus", - "display_name": "Qwen Mt Plus", + "id": "qwen/qwen3.5-122b-a10b", + "name": "Qwen3.5 122B A10B", + "display_name": "Qwen3.5 122B A10B", "modalities": { "input": [ "text" @@ -275447,30 +275658,48 @@ ] }, "limit": { - "context": 16384, - "output": 8192 + "context": 262144, + "output": 262144 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } }, "attachment": false, - "open_weights": false, - "knowledge": "2024-04", - "release_date": "2025-01", - "last_updated": "2025-01", + "open_weights": true, + "release_date": "2026-02-23", + "last_updated": "2026-02-23", "type": "chat" - }, + } + ] + }, + "qvac": { + "id": "qvac", + "name": "QVAC", + "display_name": "QVAC", + "doc": "https://www.npmjs.com/package/@qvac/ai-sdk-provider", + "models": [ { - "id": "kimi/kimi-k2.5", - "name": "kimi/kimi-k2.5", - "display_name": "kimi/kimi-k2.5", + "id": "gemma4-31b", + "name": "Gemma 4 31B IT", + "display_name": "Gemma 4 31B IT", "modalities": { "input": [ "text", - "image", - "video" + "image" ], "output": [ "text" @@ -275478,47 +275707,38 @@ }, "limit": { "context": 262144, - "output": 262144 + "output": 32768 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } + "default": true }, "attachment": true, "open_weights": true, "knowledge": "2025-01", - "release_date": "2026-01-27", - "last_updated": "2026-01-27", + "release_date": "2026-04-02", + "last_updated": "2026-04-02", "type": "chat" }, { - "id": "siliconflow/deepseek-r1-0528", - "name": "siliconflow/deepseek-r1-0528", - "display_name": "siliconflow/deepseek-r1-0528", + "id": "qwen3.6-35b-a3b", + "name": "Qwen3.6 35B-A3B", + "display_name": "Qwen3.6 35B-A3B", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 163840, - "output": 32768 + "context": 262144, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -275537,16 +275757,16 @@ ] } }, - "attachment": false, - "open_weights": false, - "release_date": "2025-05-28", - "last_updated": "2025-11-25", + "attachment": true, + "open_weights": true, + "release_date": "2026-04-17", + "last_updated": "2026-04-17", "type": "chat" }, { - "id": "siliconflow/deepseek-v3.2", - "name": "siliconflow/deepseek-v3.2", - "display_name": "siliconflow/deepseek-v3.2", + "id": "gpt-oss-120b", + "name": "GPT OSS 120B", + "display_name": "GPT OSS 120B", "modalities": { "input": [ "text" @@ -275556,8 +275776,8 @@ ] }, "limit": { - "context": 163840, - "output": 65536 + "context": 131072, + "output": 32768 }, "temperature": true, "tool_call": true, @@ -275571,15 +275791,16 @@ } }, "attachment": false, - "open_weights": false, - "release_date": "2025-12-03", - "last_updated": "2025-12-03", + "open_weights": true, + "knowledge": "2024-06", + "release_date": "2025-08-05", + "last_updated": "2025-08-05", "type": "chat" }, { - "id": "siliconflow/deepseek-v3-0324", - "name": "siliconflow/deepseek-v3-0324", - "display_name": "siliconflow/deepseek-v3-0324", + "id": "qwen3.5-0.8b", + "name": "Qwen3.5 0.8B", + "display_name": "Qwen3.5 0.8B", "modalities": { "input": [ "text" @@ -275589,8 +275810,8 @@ ] }, "limit": { - "context": 163840, - "output": 163840 + "context": 32768, + "output": 8192 }, "temperature": true, "tool_call": true, @@ -275598,25 +275819,27 @@ "supported": false }, "attachment": false, - "open_weights": false, - "release_date": "2024-12-26", - "last_updated": "2025-11-25", + "open_weights": true, + "release_date": "2026-03-02", + "last_updated": "2026-03-02", "type": "chat" }, { - "id": "siliconflow/deepseek-v3.1-terminus", - "name": "siliconflow/deepseek-v3.1-terminus", - "display_name": "siliconflow/deepseek-v3.1-terminus", + "id": "qwen3.6-27b", + "name": "Qwen3.6 27B", + "display_name": "Qwen3.6 27B", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 163840, + "context": 262144, "output": 65536 }, "temperature": true, @@ -275625,27 +275848,40 @@ "supported": true, "default": true }, - "attachment": false, - "open_weights": false, - "release_date": "2025-09-29", - "last_updated": "2025-11-25", + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } + }, + "attachment": true, + "open_weights": true, + "release_date": "2026-04-22", + "last_updated": "2026-04-22", "type": "chat" }, { - "id": "MiniMax/MiniMax-M2.7", - "name": "MiniMax-M2.7", - "display_name": "MiniMax-M2.7", + "id": "qwen3.5-9b", + "name": "Qwen3.5 9B", + "display_name": "Qwen3.5 9B", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 204800, - "output": 131072 + "context": 32768, + "output": 8192 }, "temperature": true, "tool_call": true, @@ -275661,19 +275897,23 @@ "visibility": "summary", "continuation": [ "thinking_blocks" + ], + "effort_options": [ + "none", + "max" ] } }, - "attachment": false, + "attachment": true, "open_weights": true, - "release_date": "2026-03-18", - "last_updated": "2026-03-18", + "release_date": "2026-02-23", + "last_updated": "2026-02-23", "type": "chat" }, { - "id": "qwen3-coder-plus-2025-09-23", - "name": "Qwen3 Coder Plus 2025 09 23", - "display_name": "Qwen3 Coder Plus 2025 09 23", + "id": "qwen3.5-2b", + "name": "Qwen3.5 2B", + "display_name": "Qwen3.5 2B", "modalities": { "input": [ "text" @@ -275683,21 +275923,35 @@ ] }, "limit": { - "context": 1000000, - "output": 65536 + "context": 32768, + "output": 8192 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } }, "attachment": false, + "open_weights": true, + "release_date": "2026-03-02", + "last_updated": "2026-03-02", "type": "chat" }, { - "id": "qwen3-coder-plus-2025-07-22", - "name": "Qwen3 Coder Plus 2025 07 22", - "display_name": "Qwen3 Coder Plus 2025 07 22", + "id": "qwen3.5-4b", + "name": "Qwen3.5 4B", + "display_name": "Qwen3.5 4B", "modalities": { "input": [ "text" @@ -275707,80 +275961,123 @@ ] }, "limit": { - "context": 1000000, - "output": 65536 + "context": 32768, + "output": 8192 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } }, "attachment": false, + "open_weights": true, + "release_date": "2026-03-02", + "last_updated": "2026-03-02", "type": "chat" }, { - "id": "qwen-vl-ocr-latest", - "name": "Qwen Vl Ocr Latest", - "display_name": "Qwen Vl Ocr Latest", + "id": "gpt-oss-20b", + "name": "GPT OSS 20B", + "display_name": "GPT OSS 20B", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 34096, - "output": 4096 + "context": 131072, + "output": 32768 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true + } }, "attachment": false, + "open_weights": true, + "knowledge": "2024-06", + "release_date": "2025-08-05", + "last_updated": "2025-08-05", "type": "chat" - }, + } + ] + }, + "jalapeno": { + "id": "jalapeno", + "name": "Jalapeno Cloud", + "display_name": "Jalapeno Cloud", + "api": "https://api.jalapeno-cloud.ai/v1", + "doc": "https://www.jalapeno-cloud.ai/docs/", + "models": [ { - "id": "qvq-max-2025-05-15", - "name": "Qvq Max 2025 05 15", - "display_name": "Qvq Max 2025 05 15", + "id": "Qwen3.5-122B-A10B", + "name": "Qwen3.5 122B-A10B", + "display_name": "Qwen3.5 122B-A10B", "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 8192 + "context": 262144, + "output": 65536 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { "supported": true, - "default": false, - "budget": { - "default": 16384, - "min": 0, - "max": 16384 + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] } }, - "attachment": false, + "attachment": true, + "open_weights": true, + "release_date": "2026-02-23", + "last_updated": "2026-02-23", "type": "chat" }, { - "id": "qwen-turbo-latest", - "name": "Qwen Turbo Latest", - "display_name": "Qwen Turbo Latest", + "id": "Qwen3-VL-235B-A22B-Thinking", + "name": "Qwen3 VL 235B A22B Thinking", + "display_name": "Qwen3 VL 235B A22B Thinking", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" @@ -275788,18 +276085,13 @@ }, "limit": { "context": 131072, - "output": 16384 + "output": 32768 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false, - "budget": { - "default": 38912, - "min": 0, - "max": 38912 - } + "default": true }, "extra_capabilities": { "reasoning": { @@ -275812,19 +276104,17 @@ ] } }, - "search": { - "supported": true, - "default": false, - "forced_search": false, - "search_strategy": "turbo" - }, - "attachment": false, + "attachment": true, + "open_weights": true, + "knowledge": "2025-03-31", + "release_date": "2025-09-23", + "last_updated": "2025-09-23", "type": "chat" }, { - "id": "qwen-turbo-2024-09-19", - "name": "Qwen Turbo 2024 09 19", - "display_name": "Qwen Turbo 2024 09 19", + "id": "Qwen3-Next-80B-A3B-Instruct", + "name": "Qwen3-Next 80B-A3B Instruct", + "display_name": "Qwen3-Next 80B-A3B Instruct", "modalities": { "input": [ "text" @@ -275834,8 +276124,8 @@ ] }, "limit": { - "context": 131072, - "output": 8192 + "context": 129024, + "output": 32768 }, "temperature": true, "tool_call": true, @@ -275843,34 +276133,35 @@ "supported": false }, "attachment": false, + "open_weights": true, + "knowledge": "2025-04", + "release_date": "2025-09", + "last_updated": "2025-09", "type": "chat" }, { - "id": "qwen-flash-2025-07-28", - "name": "Qwen Flash 2025 07 28", - "display_name": "Qwen Flash 2025 07 28", + "id": "Qwen3.5-27B", + "name": "Qwen3.5 27B", + "display_name": "Qwen3.5 27B", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 32768 + "context": 262144, + "output": 65536 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false, - "budget": { - "default": 81920, - "min": 0, - "max": 81920 - } + "default": true }, "extra_capabilities": { "reasoning": { @@ -275883,19 +276174,16 @@ ] } }, - "search": { - "supported": true, - "default": false, - "forced_search": false, - "search_strategy": "turbo" - }, - "attachment": false, + "attachment": true, + "open_weights": true, + "release_date": "2026-02-23", + "last_updated": "2026-02-23", "type": "chat" }, { - "id": "qwen-plus-latest", - "name": "Qwen Plus Latest", - "display_name": "Qwen Plus Latest", + "id": "Hy3", + "name": "Hy3", + "display_name": "Hy3", "modalities": { "input": [ "text" @@ -275905,19 +276193,14 @@ ] }, "limit": { - "context": 1000000, - "output": 32768 + "context": 202752, + "output": 128000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false, - "budget": { - "default": 81920, - "min": 0, - "max": 81920 - } + "default": true }, "extra_capabilities": { "reasoning": { @@ -275930,19 +276213,16 @@ ] } }, - "search": { - "supported": true, - "default": false, - "forced_search": false, - "search_strategy": "turbo" - }, "attachment": false, + "open_weights": true, + "release_date": "2026-07-06", + "last_updated": "2026-07-06", "type": "chat" }, { - "id": "qwen-plus-2024-09-19", - "name": "Qwen Plus 2024 09 19", - "display_name": "Qwen Plus 2024 09 19", + "id": "DeepSeek-V4-Pro", + "name": "DeepSeek V4 Pro", + "display_name": "DeepSeek V4 Pro", "modalities": { "input": [ "text" @@ -275952,43 +276232,67 @@ ] }, "limit": { - "context": 131072, - "output": 8192 + "context": 1048576, + "output": 384000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "high", + "effort_options": [ + "low", + "high", + "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." + ] + } }, "attachment": false, + "open_weights": true, + "knowledge": "2025-05", + "release_date": "2026-04-24", + "last_updated": "2026-04-24", "type": "chat" }, { - "id": "qwen-plus-2025-07-14", - "name": "Qwen Plus 2025 07 14", - "display_name": "Qwen Plus 2025 07 14", + "id": "Kimi-K2.5", + "name": "Kimi K2.5", + "display_name": "Kimi K2.5", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 16384 + "context": 262144, + "output": 180224 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": false, - "budget": { - "default": 38912, - "min": 0, - "max": 38912 - } + "default": true }, "extra_capabilities": { "reasoning": { @@ -276001,19 +276305,17 @@ ] } }, - "search": { - "supported": true, - "default": false, - "forced_search": false, - "search_strategy": "turbo" - }, - "attachment": false, + "attachment": true, + "open_weights": true, + "knowledge": "2025-01", + "release_date": "2026-01-27", + "last_updated": "2026-01-27", "type": "chat" }, { - "id": "qwen-plus-2025-09-11", - "name": "Qwen Plus 2025 09 11", - "display_name": "Qwen Plus 2025 09 11", + "id": "GLM-5.1", + "name": "GLM-5.1", + "display_name": "GLM-5.1", "modalities": { "input": [ "text" @@ -276023,19 +276325,14 @@ ] }, "limit": { - "context": 1000000, - "output": 32768 + "context": 202752, + "output": 131072 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false, - "budget": { - "default": 81920, - "min": 0, - "max": 81920 - } + "default": true }, "extra_capabilities": { "reasoning": { @@ -276048,19 +276345,16 @@ ] } }, - "search": { - "supported": true, - "default": false, - "forced_search": false, - "search_strategy": "turbo" - }, "attachment": false, + "open_weights": true, + "release_date": "2026-04-07", + "last_updated": "2026-04-07", "type": "chat" }, { - "id": "qwen-max-latest", - "name": "Qwen Max Latest", - "display_name": "Qwen Max Latest", + "id": "GLM-5.2", + "name": "GLM-5.2", + "display_name": "GLM-5.2", "modalities": { "input": [ "text" @@ -276070,27 +276364,36 @@ ] }, "limit": { - "context": 131072, - "output": 8192 + "context": 1048576, + "output": 131072 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false - }, - "search": { "supported": true, - "default": false, - "forced_search": false, - "search_strategy": "turbo" + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } }, "attachment": false, + "open_weights": true, + "release_date": "2026-06-13", + "last_updated": "2026-06-13", "type": "chat" }, { - "id": "qwen-max-2024-09-19", - "name": "Qwen Max 2024 09 19", - "display_name": "Qwen Max 2024 09 19", + "id": "Qwen3-Next-80B-A3B-Thinking", + "name": "Qwen3-Next 80B-A3B (Thinking)", + "display_name": "Qwen3-Next 80B-A3B (Thinking)", "modalities": { "input": [ "text" @@ -276100,138 +276403,195 @@ ] }, "limit": { - "context": 32768, - "output": 8192 + "context": 262144, + "output": 32768 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false - }, - "search": { "supported": true, - "default": false, - "forced_search": false, - "search_strategy": "turbo" + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } }, "attachment": false, + "open_weights": true, + "knowledge": "2025-04", + "release_date": "2025-09", + "last_updated": "2025-09", "type": "chat" }, { - "id": "qwen-max-2024-04-28", - "name": "Qwen Max 2024 04 28", - "display_name": "Qwen Max 2024 04 28", + "id": "Kimi-K2.7-Code", + "name": "Kimi K2.7 Code", + "display_name": "Kimi K2.7 Code", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 8000, - "output": 2000 + "context": 271360, + "output": 262144 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } + }, + "attachment": true, + "open_weights": true, + "knowledge": "2025-01", + "release_date": "2026-06-12", + "last_updated": "2026-06-12", "type": "chat" }, { - "id": "qwen-max-2024-04-03", - "name": "Qwen Max 2024 04 03", - "display_name": "Qwen Max 2024 04 03", + "id": "Qwen3.5-397B-A17B", + "name": "Qwen3.5 397B-A17B", + "display_name": "Qwen3.5 397B-A17B", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 8000, - "output": 2000 + "context": 262144, + "output": 65536 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } + }, + "attachment": true, + "open_weights": true, + "release_date": "2026-02-15", + "last_updated": "2026-02-15", "type": "chat" }, { - "id": "qwen-max-2025-01-25", - "name": "Qwen Max 2025 01 25", - "display_name": "Qwen Max 2025 01 25", + "id": "Qwen3-VL-235B-A22B-Instruct", + "name": "Qwen3 VL 235B A22B Instruct", + "display_name": "Qwen3 VL 235B A22B Instruct", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 8192 + "context": 129024, + "output": 32768 }, "temperature": true, "tool_call": true, "reasoning": { "supported": false }, - "search": { - "supported": true, - "default": false, - "forced_search": false, - "search_strategy": "turbo" - }, - "attachment": false, + "attachment": true, + "open_weights": true, + "knowledge": "2025-03-31", + "release_date": "2025-09-23", + "last_updated": "2025-09-23", "type": "chat" }, { - "id": "qwen3-max-2025-09-23", - "name": "Qwen3 Max 20250923", - "display_name": "Qwen3 Max 20250923", + "id": "Kimi-K3", + "name": "Kimi K3", + "display_name": "Kimi K3", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 65536 + "context": 1048576, + "output": 131072 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { - "supported": false - }, - "search": { "supported": true, - "default": false, - "forced_search": false, - "search_strategy": "turbo" + "default": true }, - "attachment": false, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } + }, + "attachment": true, + "open_weights": true, + "release_date": "2026-07-16", + "last_updated": "2026-07-16", "type": "chat" }, { - "id": "qwen3-max-preview", - "name": "Qwen3 Max Preview", - "display_name": "Qwen3 Max Preview", + "id": "Qwen3.5-35B-A3B", + "name": "Qwen3.5 35B-A3B", + "display_name": "Qwen3.5 35B-A3B", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" @@ -276244,7 +276604,8 @@ "temperature": true, "tool_call": true, "reasoning": { - "supported": true + "supported": true, + "default": true }, "extra_capabilities": { "reasoning": { @@ -276257,19 +276618,16 @@ ] } }, - "search": { - "supported": true, - "default": false, - "forced_search": false, - "search_strategy": "turbo" - }, - "attachment": false, + "attachment": true, + "open_weights": true, + "release_date": "2026-02-23", + "last_updated": "2026-02-23", "type": "chat" }, { - "id": "qwen3-235b-a22b-thinking-2507", - "name": "Qwen3 235B A22B Thinking 2507", - "display_name": "Qwen3 235B A22B Thinking 2507", + "id": "DeepSeek-V4-Flash", + "name": "DeepSeek V4 Flash", + "display_name": "DeepSeek V4 Flash", "modalities": { "input": [ "text" @@ -276279,250 +276637,247 @@ ] }, "limit": { - "context": 131072, - "output": 32768 + "context": 1048576, + "output": 384000 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { "supported": true, - "default": true, - "budget": { - "default": 81920, - "min": 0, - "max": 81920 - } + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "high", + "effort_options": [ + "low", + "high", + "max" + ], "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ "thinking_blocks" + ], + "notes": [ + "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." ] } }, "attachment": false, + "open_weights": true, + "knowledge": "2025-05", + "release_date": "2026-04-24", + "last_updated": "2026-04-24", "type": "chat" }, { - "id": "qwen3-235b-a22b-instruct-2507", - "name": "Qwen3 235B A22B Instruct 2507", - "display_name": "Qwen3 235B A22B Instruct 2507", + "id": "MiniMax-M3", + "name": "MiniMax-M3", + "display_name": "MiniMax-M3", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 32768 + "context": 524288, + "output": 512000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } + }, + "attachment": true, + "open_weights": true, + "release_date": "2026-06-01", + "last_updated": "2026-06-01", "type": "chat" - }, + } + ] + }, + "privatemode-ai": { + "id": "privatemode-ai", + "name": "Privatemode AI", + "display_name": "Privatemode AI", + "api": "http://localhost:8080/v1", + "doc": "https://docs.privatemode.ai/api/overview", + "models": [ { - "id": "qwen3-30b-a3b-instruct-2507", - "name": "Qwen3 30B A3B Instruct 2507", - "display_name": "Qwen3 30B A3B Instruct 2507", + "id": "whisper-large-v3", + "name": "Whisper large-v3", + "display_name": "Whisper large-v3", "modalities": { "input": [ - "text" + "audio" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 32768 + "context": 448, + "output": 4096 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { "supported": false }, - "attachment": false, - "type": "chat" + "attachment": true, + "open_weights": true, + "release_date": "2023-11-06", + "last_updated": "2023-11-06" }, { - "id": "qwen3-30b-a3b-thinking-2507", - "name": "Qwen3 30B A3B Thinking 2507", - "display_name": "Qwen3 30B A3B Thinking 2507", + "id": "voxtral-mini-3b", + "name": "Voxtral Mini 3B", + "display_name": "Voxtral Mini 3B", "modalities": { "input": [ - "text" + "audio" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 32768 + "context": 32000, + "output": 32000 }, "temperature": true, "tool_call": false, "reasoning": { - "supported": true, - "default": true, - "budget": { - "default": 81920, - "min": 0, - "max": 81920 - } - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } + "supported": false }, - "attachment": false, - "type": "chat" + "attachment": true, + "open_weights": true, + "release_date": "2025-07-15", + "last_updated": "2025-07-15" }, { - "id": "qwen3-30b-a3b", - "name": "Qwen3 30B A3B", - "display_name": "Qwen3 30B A3B", + "id": "kimi-latest", + "name": "Kimi (latest)", + "display_name": "Kimi (latest)", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 32768 + "context": 256000, + "output": 262144 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false, - "budget": { - "default": 81920, - "min": 0, - "max": 81920 - } - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } + "default": true }, - "attachment": false, + "attachment": true, + "open_weights": true, + "knowledge": "2025-01", + "release_date": "2026-04-20", + "last_updated": "2026-04-20", "type": "chat" }, { - "id": "qwen3-4b", - "name": "Qwen3 4B", - "display_name": "Qwen3 4B", + "id": "deepseek-ocr-2", + "name": "DeepSeek OCR 2", + "display_name": "DeepSeek OCR 2", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 131072, + "context": 8192, "output": 8192 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true, - "budget": { - "default": 38912, - "min": 0, - "max": 38912 - } - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } + "supported": false }, - "attachment": false, + "attachment": true, + "open_weights": true, + "release_date": "2026-01-27", + "last_updated": "2026-01-27", "type": "chat" }, { - "id": "qwen3-1.7b", - "name": "Qwen3 1.7B", - "display_name": "Qwen3 1.7B", + "id": "glm-5.3-flash", + "name": "GLM-5.3-Flash", + "display_name": "GLM-5.3-Flash", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 32768, - "output": 8192 + "context": 1000000, + "output": 131072 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true, - "budget": { - "default": 30720, - "min": 0, - "max": 30720 - } + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "low", + "high", + "max" ] } }, - "attachment": false, + "attachment": true, + "open_weights": true, + "release_date": "2026-08-26", + "last_updated": "2026-08-26", "type": "chat" }, { - "id": "qwen3-0.6b", - "name": "Qwen3 0.6B", - "display_name": "Qwen3 0.6B", + "id": "qwen3-embedding-4b", + "name": "Qwen3-Embedding 4B", + "display_name": "Qwen3-Embedding 4B", "modalities": { "input": [ "text" @@ -276532,80 +276887,64 @@ ] }, "limit": { - "context": 32768, - "output": 8192 + "context": 32000, + "output": 2560 }, - "temperature": true, - "tool_call": true, + "temperature": false, + "tool_call": false, "reasoning": { - "supported": true, - "default": true, - "budget": { - "default": 30720, - "min": 0, - "max": 30720 - } - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } + "supported": false }, "attachment": false, - "type": "chat" + "open_weights": true, + "knowledge": "2025-06", + "release_date": "2025-06-05", + "last_updated": "2025-06-05", + "type": "embedding" }, { - "id": "qwen3-vl-plus-2025-09-23", - "name": "Qwen3 VL Plus 2025 09 23", - "display_name": "Qwen3 VL Plus 2025 09 23", + "id": "gpt-oss-120b", + "name": "gpt-oss-120b", + "display_name": "gpt-oss-120b", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 262144, + "context": 128000, "output": 32768 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false, - "budget": { - "default": 81920, - "min": 0, - "max": 81920 - } + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "low", + "medium", + "high" ] } }, "attachment": false, + "open_weights": true, + "knowledge": "2024-06", + "release_date": "2025-08-05", + "last_updated": "2025-08-05", "type": "chat" }, { - "id": "qwq-plus-latest", - "name": "QwQ Plus Latest", - "display_name": "QwQ Plus Latest", + "id": "glm-5.3", + "name": "GLM-5.3", + "display_name": "GLM-5.3", "modalities": { "input": [ "text" @@ -276615,63 +276954,47 @@ ] }, "limit": { - "context": 131072, - "output": 8192 + "context": 1000000, + "output": 131072 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { "supported": true, - "default": true, - "budget": { - "default": 32768, - "min": 0, - "max": 32768 - } + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "low", + "high", + "max" ] } }, - "search": { - "supported": false - }, "attachment": false, + "open_weights": true, + "release_date": "2026-08-14", + "last_updated": "2026-08-14", "type": "chat" - } - ] - }, - "berget": { - "id": "berget", - "name": "Berget.AI", - "display_name": "Berget.AI", - "api": "https://api.berget.ai/v1", - "doc": "https://api.berget.ai", - "models": [ - { - "id": "zai-org/GLM-5.3-Flash", - "name": "GLM-5.3-Flash", - "display_name": "GLM-5.3-Flash", + }, + { + "id": "glm-flash-latest", + "name": "GLM Flash (latest)", + "display_name": "GLM Flash (latest)", "modalities": { "input": [ "text", - "image", - "video" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 16384 + "context": 1000000, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -276681,13 +277004,6 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], "effort_options": [ "low", "high", @@ -276702,9 +277018,9 @@ "type": "chat" }, { - "id": "zai-org/GLM-5.2", - "name": "GLM-5.2", - "display_name": "GLM-5.2", + "id": "glm-latest", + "name": "GLM (latest)", + "display_name": "GLM (latest)", "modalities": { "input": [ "text" @@ -276714,8 +277030,8 @@ ] }, "limit": { - "context": 524288, - "output": 32768 + "context": 1000000, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -276725,23 +277041,23 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, "effort_options": [ - "none", - "high" + "low", + "high", + "max" ] } }, "attachment": false, "open_weights": true, - "release_date": "2026-06-13", - "last_updated": "2026-06-13", + "release_date": "2026-08-14", + "last_updated": "2026-08-14", "type": "chat" }, { - "id": "Qwen/Qwen3.8-27B-FP8", - "name": "Qwen3.8 27B", - "display_name": "Qwen3.8 27B", + "id": "kimi-k2.6", + "name": "Kimi K2.6", + "display_name": "Kimi K2.6", "modalities": { "input": [ "text", @@ -276752,8 +277068,8 @@ ] }, "limit": { - "context": 262144, - "output": 65536 + "context": 256000, + "output": 262144 }, "temperature": true, "tool_call": true, @@ -276763,45 +277079,41 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "effort_options": [ - "none", - "low", - "medium", - "xhigh" - ] + "supported": true } }, "attachment": true, "open_weights": true, - "release_date": "2026-08-14", - "last_updated": "2026-08-14", + "knowledge": "2025-01", + "release_date": "2026-04-20", + "last_updated": "2026-04-20", "type": "chat" - }, + } + ] + }, + "edenai": { + "id": "edenai", + "name": "Eden AI", + "display_name": "Eden AI", + "api": "https://api.edenai.run/v3", + "doc": "https://docs.edenai.co", + "models": [ { - "id": "google/gemma-4-31B-it", - "name": "Gemma 4 31B Instruct", - "display_name": "Gemma 4 31B Instruct", + "id": "deepseek-v4-flash-vision-exp", + "name": "DeepSeek V4 Flash Vision Exp", + "display_name": "DeepSeek V4 Flash Vision Exp", "modalities": { "input": [ - "audio", - "image", "text", - "video" + "image" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 8192 + "context": 1000000, + "output": 384000 }, "temperature": true, "tool_call": true, @@ -276811,32 +277123,49 @@ }, "extra_capabilities": { "reasoning": { - "supported": true + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "high", + "effort_options": [ + "low", + "high", + "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." + ] } }, "attachment": true, "open_weights": true, - "knowledge": "2025-12", - "release_date": "2026-04-02", - "last_updated": "2026-04-02", + "release_date": "2026-08-21", + "last_updated": "2026-09-01", "type": "chat" }, { - "id": "moonshotai/Kimi-K3", - "name": "Kimi K3", - "display_name": "Kimi K3", + "id": "openai/gpt-5.2-pro", + "name": "GPT-5.2 Pro", + "display_name": "GPT-5.2 Pro", "modalities": { "input": [ + "image", "text", - "image" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 327680, - "output": 32768 + "context": 400000, + "output": 128000 }, "temperature": false, "tool_call": true, @@ -276847,130 +277176,148 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], + "default_enabled": true, + "mode": "effort", + "effort": "high", "effort_options": [ - "low", + "medium", "high", - "max" - ] + "xhigh" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, "attachment": true, - "open_weights": true, - "release_date": "2026-07-16", - "last_updated": "2026-07-16", + "open_weights": false, + "knowledge": "2025-08-31", + "release_date": "2025-12-11", + "last_updated": "2025-12-11", "type": "chat" }, { - "id": "mistralai/Mistral-Small-3.2-24B-Instruct-2506", - "name": "Mistral Small 3.2 24B Instruct 2506", - "display_name": "Mistral Small 3.2 24B Instruct 2506", + "id": "openai/gpt-5-pro", + "name": "GPT-5 Pro", + "display_name": "GPT-5 Pro", "modalities": { "input": [ - "text" + "image", + "text", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 32000, - "output": 8192 + "context": 400000, + "output": 272000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, "default": true }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-09", - "release_date": "2025-06-20", - "last_updated": "2025-06-20", + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "fixed", + "effort": "high", + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" + } + }, + "attachment": true, + "open_weights": false, + "knowledge": "2024-09-30", + "release_date": "2025-10-06", + "last_updated": "2025-10-06", "type": "chat" - } - ] - }, - "302ai": { - "id": "302ai", - "name": "302.AI", - "display_name": "302.AI", - "api": "https://api.302.ai/v1", - "doc": "https://doc.302.ai", - "models": [ + }, { - "id": "gemini-3-flash-preview", - "name": "gemini-3-flash-preview", - "display_name": "gemini-3-flash-preview", + "id": "openai/gpt-5.4-nano", + "name": "GPT-5.4 nano", + "display_name": "GPT-5.4 nano", "modalities": { "input": [ - "text", - "image" + "pdf", + "image", + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 65536 + "context": 400000, + "output": 128000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "level", - "level": "high", - "level_options": [ - "minimal", + "default_enabled": false, + "mode": "effort", + "effort": "none", + "effort_options": [ + "none", + "low", + "medium", + "high", + "xhigh" + ], + "verbosity": "medium", + "verbosity_options": [ "low", "medium", "high" ], - "summaries": true, - "visibility": "summary", - "continuation": [ - "thought_signatures" - ] + "visibility": "hidden" } }, "attachment": true, "open_weights": false, - "knowledge": "2025-06", - "release_date": "2025-12-17", - "last_updated": "2025-12-17", + "knowledge": "2025-08-31", + "release_date": "2026-03-17", + "last_updated": "2026-03-17", "type": "chat" }, { - "id": "grok-4-1-fast-reasoning", - "name": "grok-4-1-fast-reasoning", - "display_name": "grok-4-1-fast-reasoning", + "id": "openai/gpt-6.1-terra", + "name": "GPT-6.1 Terra", + "display_name": "GPT-6.1 Terra", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 2000000, - "output": 30000 + "context": 1050000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -276978,50 +277325,59 @@ }, "extra_capabilities": { "reasoning": { - "supported": true + "effort_options": [ + "none", + "low", + "medium", + "high", + "xhigh", + "max" + ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-06", - "release_date": "2025-11-19", - "last_updated": "2025-11-19", + "knowledge": "2026-06-01", + "release_date": "2026-10-26", + "last_updated": "2026-10-26", "type": "chat" }, { - "id": "gpt-5", - "name": "gpt-5", - "display_name": "gpt-5", + "id": "openai/gpt-5.4", + "name": "GPT-5.4", + "display_name": "GPT-5.4", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 400000, + "context": 1050000, "output": 128000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, + "default_enabled": false, "mode": "effort", - "effort": "medium", + "effort": "none", "effort_options": [ - "minimal", + "none", "low", "medium", - "high" + "high", + "xhigh" ], "verbosity": "medium", "verbosity_options": [ @@ -277034,17 +277390,19 @@ }, "attachment": true, "open_weights": false, - "knowledge": "2024-09-30", - "release_date": "2025-08-07", - "last_updated": "2025-08-07", + "knowledge": "2025-08-31", + "release_date": "2026-03-05", + "last_updated": "2026-03-05", "type": "chat" }, { - "id": "qwen3.7-max", - "name": "Qwen3.7 Max", - "display_name": "Qwen3.7 Max", + "id": "openai/gpt-5.4-mini", + "name": "GPT-5.4 mini", + "display_name": "GPT-5.4 mini", "modalities": { "input": [ + "pdf", + "image", "text" ], "output": [ @@ -277052,44 +277410,63 @@ ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 400000, + "output": 128000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { - "supported": true + "supported": true, + "default_enabled": false, + "mode": "effort", + "effort": "none", + "effort_options": [ + "none", + "low", + "medium", + "high", + "xhigh" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, - "attachment": false, + "attachment": true, "open_weights": false, - "release_date": "2026-05-21", - "last_updated": "2026-05-21", + "knowledge": "2025-08-31", + "release_date": "2026-03-17", + "last_updated": "2026-03-17", "type": "chat" }, { - "id": "gemini-2.5-pro", - "name": "gemini-2.5-pro", - "display_name": "gemini-2.5-pro", + "id": "openai/gpt-6-sol", + "name": "GPT-6 Sol", + "display_name": "GPT-6 Sol", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 65536 + "context": 1050000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -277097,34 +277474,27 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "budget", - "budget": { - "default": -1, - "min": 128, - "max": 32768, - "auto": -1, - "unit": "tokens" - }, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thought_signatures" + "effort_options": [ + "none", + "low", + "medium", + "high", + "xhigh", + "max" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-01", - "release_date": "2025-06-17", - "last_updated": "2025-06-17", + "knowledge": "2026-04-20", + "release_date": "2026-09-22", + "last_updated": "2026-09-22", "type": "chat" }, { - "id": "gpt-5.5", - "name": "GPT-5.5", - "display_name": "GPT-5.5", + "id": "openai/gpt-5.6-terra", + "name": "GPT-5.6 Terra", + "display_name": "GPT-5.6 Terra", "modalities": { "input": [ "text", @@ -277152,10 +277522,12 @@ "mode": "effort", "effort": "medium", "effort_options": [ + "none", "low", "medium", "high", - "xhigh" + "xhigh", + "max" ], "verbosity": "medium", "verbosity_options": [ @@ -277168,72 +277540,110 @@ }, "attachment": true, "open_weights": false, - "knowledge": "2025-12-01", - "release_date": "2026-04-23", - "last_updated": "2026-04-23", + "knowledge": "2026-02-16", + "release_date": "2026-07-09", + "last_updated": "2026-07-09", "type": "chat" }, { - "id": "gpt-4.1-nano", - "name": "gpt-4.1-nano", - "display_name": "gpt-4.1-nano", + "id": "openai/gpt-5.3-codex", + "name": "GPT-5.3 Codex", + "display_name": "GPT-5.3 Codex", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1047576, - "output": 32768 + "context": 400000, + "output": 128000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", + "effort_options": [ + "low", + "medium", + "high", + "xhigh" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" + } }, "attachment": true, "open_weights": false, - "knowledge": "2024-04", - "release_date": "2025-04-14", - "last_updated": "2025-04-14", + "knowledge": "2025-08-31", + "release_date": "2026-02-05", + "last_updated": "2026-02-05", "type": "chat" }, { - "id": "doubao-seed-1-8-251215", - "name": "doubao-seed-1-8-251215", - "display_name": "doubao-seed-1-8-251215", + "id": "openai/gpt-6-astra", + "name": "GPT-6 Astra", + "display_name": "GPT-6 Astra", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 224000, - "output": 64000 + "context": 1050000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" + ] + } }, "attachment": true, "open_weights": false, - "release_date": "2025-12-28", - "last_updated": "2025-12-28", + "knowledge": "2026-04-30", + "release_date": "2026-09-04", + "last_updated": "2026-09-04", "type": "chat" }, { - "id": "gpt-6-astra", - "name": "GPT-6 Astra", - "display_name": "GPT-6 Astra", + "id": "openai/gpt-pro-latest", + "name": "GPT Pro Latest (GPT-5.6 Sol)", + "display_name": "GPT Pro Latest (GPT-5.6 Sol)", "modalities": { "input": [ "text", @@ -277256,8 +277666,8 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, "effort_options": [ + "none", "low", "medium", "high", @@ -277268,59 +277678,60 @@ }, "attachment": true, "open_weights": false, - "knowledge": "2026-04-30", - "release_date": "2026-09-04", - "last_updated": "2026-09-04", + "knowledge": "2026-02-16", + "release_date": "2026-07-09", + "last_updated": "2026-07-09", "type": "chat" }, { - "id": "gemini-2.5-flash-image", - "name": "gemini-2.5-flash-image", - "display_name": "gemini-2.5-flash-image", + "id": "openai/gpt-4o-2024-08-06", + "name": "GPT-4o (2024-08-06)", + "display_name": "GPT-4o (2024-08-06)", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ - "text", - "image" + "text" ] }, "limit": { - "context": 32768, - "output": 32768 + "context": 128000, + "output": 16384 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { "supported": false }, "attachment": true, "open_weights": false, - "knowledge": "2025-01", - "release_date": "2025-08-26", - "last_updated": "2025-08-26" + "knowledge": "2023-09", + "release_date": "2024-08-06", + "last_updated": "2024-08-06", + "type": "chat" }, { - "id": "gpt-5.4", - "name": "gpt-5.4", - "display_name": "gpt-5.4", + "id": "openai/gpt-5.2", + "name": "GPT-5.2", + "display_name": "GPT-5.2", "modalities": { "input": [ - "text", + "pdf", "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1050000, + "context": 400000, "output": 128000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -277351,20 +277762,18 @@ "attachment": true, "open_weights": false, "knowledge": "2025-08-31", - "release_date": "2026-03-05", - "last_updated": "2026-03-05", + "release_date": "2025-12-11", + "last_updated": "2025-12-11", "type": "chat" }, { - "id": "gemini-3.5-flash-lite", - "name": "Gemini 3.5 Flash Lite", - "display_name": "Gemini 3.5 Flash Lite", + "id": "openai/gpt-5.4-pro", + "name": "GPT-5.4 Pro", + "display_name": "GPT-5.4 Pro", "modalities": { "input": [ "text", "image", - "video", - "audio", "pdf" ], "output": [ @@ -277372,10 +277781,10 @@ ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 1050000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -277384,37 +277793,47 @@ "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "high", "effort_options": [ - "minimal", + "medium", + "high", + "xhigh" + ], + "verbosity": "medium", + "verbosity_options": [ "low", "medium", "high" - ] + ], + "visibility": "hidden" } }, "attachment": true, "open_weights": false, - "knowledge": "2026-03", - "release_date": "2026-07-21", - "last_updated": "2026-07-21", + "knowledge": "2025-08-31", + "release_date": "2026-03-05", + "last_updated": "2026-03-05", "type": "chat" }, { - "id": "gpt-5.1-chat-latest", - "name": "gpt-5.1-chat-latest", - "display_name": "gpt-5.1-chat-latest", + "id": "openai/gpt-6.1-sol", + "name": "GPT-6.1 Sol", + "display_name": "GPT-6.1 Sol", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 16384 + "context": 1050000, + "output": 128000 }, "temperature": false, "tool_call": true, @@ -277425,25 +277844,30 @@ "extra_capabilities": { "reasoning": { "effort_options": [ - "medium" + "low", + "medium", + "high", + "xhigh", + "max" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2024-09-30", - "release_date": "2025-11-13", - "last_updated": "2025-11-13", + "knowledge": "2026-04-30", + "release_date": "2026-09-29", + "last_updated": "2026-09-29", "type": "chat" }, { - "id": "ministral-14b-2512", - "name": "ministral-14b-2512", - "display_name": "ministral-14b-2512", + "id": "openai/gpt-4o-2024-11-20", + "name": "GPT-4o (2024-11-20)", + "display_name": "GPT-4o (2024-11-20)", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" @@ -277451,7 +277875,7 @@ }, "limit": { "context": 128000, - "output": 128000 + "output": 16384 }, "temperature": true, "tool_call": true, @@ -277459,52 +277883,63 @@ "supported": false }, "attachment": true, - "open_weights": true, - "knowledge": "2024-12", - "release_date": "2025-12-02", - "last_updated": "2025-12-02", + "open_weights": false, + "knowledge": "2023-09", + "release_date": "2024-11-20", + "last_updated": "2024-11-20", "type": "chat" }, { - "id": "glm-4.5v", - "name": "GLM-4.5V", - "display_name": "GLM-4.5V", + "id": "openai/gpt-mini-latest", + "name": "GPT Mini Latest (GPT-5.6 Luna)", + "display_name": "GPT Mini Latest (GPT-5.6 Luna)", "modalities": { "input": [ "text", "image", - "video" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 64000, - "output": 16384 + "context": 1050000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, "default": true }, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "none", + "low", + "medium", + "high", + "xhigh", + "max" + ] + } + }, "attachment": true, - "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-08-11", - "last_updated": "2025-08-11", + "open_weights": false, + "knowledge": "2026-02-16", + "release_date": "2026-07-09", + "last_updated": "2026-07-09", "type": "chat" }, { - "id": "glm-5.3-flash", - "name": "GLM-5.3-Flash", - "display_name": "GLM-5.3-Flash", + "id": "openai/gpt-5-search-api", + "name": "GPT-5 Search API", + "display_name": "GPT-5 Search API", "modalities": { "input": [ "text", "image", - "video", "pdf" ], "output": [ @@ -277512,10 +277947,39 @@ ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 272000, + "output": 100000 }, - "temperature": true, + "temperature": false, + "tool_call": true, + "reasoning": { + "supported": false + }, + "attachment": true, + "open_weights": false, + "release_date": "2025-10-14", + "last_updated": "2025-10-14", + "type": "chat" + }, + { + "id": "openai/gpt-5-nano", + "name": "GPT-5 Nano", + "display_name": "GPT-5 Nano", + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 400000, + "output": 128000 + }, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -277524,32 +277988,39 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], + "default_enabled": true, + "mode": "effort", + "effort": "medium", "effort_options": [ + "minimal", "low", - "high", - "max" - ] + "medium", + "high" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, "attachment": true, - "open_weights": true, - "release_date": "2026-08-26", - "last_updated": "2026-08-26", + "open_weights": false, + "knowledge": "2024-05-30", + "release_date": "2025-08-07", + "last_updated": "2025-08-07", "type": "chat" }, { - "id": "gemini-2.5-flash-preview-09-2025", - "name": "gemini-2.5-flash-preview-09-2025", - "display_name": "gemini-2.5-flash-preview-09-2025", + "id": "openai/o3-pro", + "name": "o3-pro", + "display_name": "o3-pro", "modalities": { "input": [ "text", + "pdf", "image" ], "output": [ @@ -277557,10 +278028,10 @@ ] }, "limit": { - "context": 1000000, - "output": 65536 + "context": 200000, + "output": 100000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -277570,39 +278041,31 @@ "reasoning": { "supported": true, "default_enabled": true, - "mode": "budget", - "budget": { - "default": -1, - "min": 0, - "max": 24576, - "auto": -1, - "off": 0, - "unit": "tokens" - }, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thought_signatures" - ] + "mode": "effort", + "effort": "medium", + "effort_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, "attachment": true, "open_weights": false, - "knowledge": "2025-01", - "release_date": "2025-09-26", - "last_updated": "2025-09-26", + "knowledge": "2024-05", + "release_date": "2025-06-10", + "last_updated": "2025-06-10", "type": "chat" }, { - "id": "glm-5v-turbo", - "name": "GLM-5V-Turbo", - "display_name": "GLM-5V-Turbo", + "id": "openai/gpt-5.5-pro", + "name": "GPT-5.5 Pro", + "display_name": "GPT-5.5 Pro", "modalities": { "input": [ "text", "image", - "video", - "audio", "pdf" ], "output": [ @@ -277610,10 +278073,10 @@ ] }, "limit": { - "context": 200000, - "output": 131072 + "context": 1050000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -277622,24 +278085,24 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "medium", + "high", + "xhigh" ] } }, "attachment": true, "open_weights": false, - "release_date": "2026-04-01", - "last_updated": "2026-04-01", + "knowledge": "2025-12-01", + "release_date": "2026-04-23", + "last_updated": "2026-04-23", "type": "chat" }, { - "id": "claude-sonnet-5-5", - "name": "Claude Sonnet 5.5", - "display_name": "Claude Sonnet 5.5", + "id": "openai/gpt-5.6-luna", + "name": "GPT-5.6 Luna", + "display_name": "GPT-5.6 Luna", "modalities": { "input": [ "text", @@ -277651,7 +278114,7 @@ ] }, "limit": { - "context": 1000000, + "context": 1050000, "output": 128000 }, "temperature": false, @@ -277662,39 +278125,53 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", "effort_options": [ + "none", "low", "medium", "high", "xhigh", "max" - ] + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, "attachment": true, "open_weights": false, - "knowledge": "2026-06", - "release_date": "2026-09-28", - "last_updated": "2026-09-28", + "knowledge": "2026-02-16", + "release_date": "2026-07-09", + "last_updated": "2026-07-09", "type": "chat" }, { - "id": "glm-5-turbo", - "name": "glm-5-turbo", - "display_name": "glm-5-turbo", + "id": "openai/gpt-5", + "name": "GPT-5", + "display_name": "GPT-5", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 131072 + "context": 400000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -277703,24 +278180,35 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] + "default_enabled": true, + "mode": "effort", + "effort": "medium", + "effort_options": [ + "minimal", + "low", + "medium", + "high" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, - "attachment": false, + "attachment": true, "open_weights": false, - "release_date": "2026-03-16", - "last_updated": "2026-03-16", + "knowledge": "2024-09-30", + "release_date": "2025-08-07", + "last_updated": "2025-08-07", "type": "chat" }, { - "id": "claude-haiku-5-5", - "name": "Claude Haiku 5.5", - "display_name": "Claude Haiku 5.5", + "id": "openai/gpt-5-mini", + "name": "GPT-5 Mini", + "display_name": "GPT-5 Mini", "modalities": { "input": [ "text", @@ -277732,7 +278220,7 @@ ] }, "limit": { - "context": 1000000, + "context": 400000, "output": 128000 }, "temperature": false, @@ -277743,30 +278231,41 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", "effort_options": [ + "minimal", "low", "medium", - "high", - "xhigh", - "max" - ] + "high" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, "attachment": true, "open_weights": false, - "knowledge": "2026-06", - "release_date": "2026-10-07", - "last_updated": "2026-10-07", + "knowledge": "2024-05-30", + "release_date": "2025-08-07", + "last_updated": "2025-08-07", "type": "chat" }, { - "id": "mistral-large-2512", - "name": "mistral-large-2512", - "display_name": "mistral-large-2512", + "id": "openai/gpt-4o-mini", + "name": "GPT-4o mini", + "display_name": "GPT-4o mini", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" @@ -277774,7 +278273,7 @@ }, "limit": { "context": 128000, - "output": 262144 + "output": 16384 }, "temperature": true, "tool_call": true, @@ -277782,119 +278281,152 @@ "supported": false }, "attachment": true, - "open_weights": true, - "knowledge": "2024-12", - "release_date": "2025-12-02", - "last_updated": "2025-12-02", + "open_weights": false, + "knowledge": "2023-09", + "release_date": "2024-07-18", + "last_updated": "2024-07-18", "type": "chat" }, { - "id": "kimi-k3", - "name": "Kimi K3", - "display_name": "Kimi K3", + "id": "openai/gpt-4o", + "name": "GPT-4o", + "display_name": "GPT-4o", "modalities": { "input": [ "text", "image", - "video" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 128000, + "output": 16384 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "effort_options": [ - "low", - "high", - "max" - ] - } + "supported": false }, "attachment": true, - "open_weights": true, - "release_date": "2026-07-16", - "last_updated": "2026-07-16", + "open_weights": false, + "knowledge": "2023-09", + "release_date": "2024-05-13", + "last_updated": "2024-08-06", "type": "chat" }, { - "id": "gemini-2.0-flash-lite", - "name": "gemini-2.0-flash-lite", - "display_name": "gemini-2.0-flash-lite", + "id": "openai/gpt-5.6-sol", + "name": "GPT-5.6 Sol", + "display_name": "GPT-5.6 Sol", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 2000000, - "output": 8192 + "context": 1050000, + "output": 128000 }, - "temperature": true, - "tool_call": false, + "temperature": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", + "effort_options": [ + "none", + "low", + "medium", + "high", + "xhigh", + "max" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" + } }, "attachment": true, "open_weights": false, - "knowledge": "2024-11", - "release_date": "2024-12-11", - "last_updated": "2024-12-11", + "knowledge": "2026-02-16", + "release_date": "2026-07-09", + "last_updated": "2026-07-09", "type": "chat" }, { - "id": "qwen3-max-2025-09-23", - "name": "qwen3-max-2025-09-23", - "display_name": "qwen3-max-2025-09-23", + "id": "openai/gpt-5.1", + "name": "GPT-5.1", + "display_name": "GPT-5.1", "modalities": { "input": [ - "text" + "image", + "text", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 258048, - "output": 65536 + "context": 400000, + "output": 128000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": false }, - "attachment": false, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": false, + "mode": "effort", + "effort": "none", + "effort_options": [ + "none", + "low", + "medium", + "high" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" + } + }, + "attachment": true, "open_weights": false, - "knowledge": "2025-04", - "release_date": "2025-09-23", - "last_updated": "2025-09-23", + "knowledge": "2024-09-30", + "release_date": "2025-11-13", + "last_updated": "2025-11-13", "type": "chat" }, { - "id": "claude-opus-5", - "name": "Claude Opus 5", - "display_name": "Claude Opus 5", + "id": "openai/gpt-latest", + "name": "GPT Latest (GPT-6 Astra)", + "display_name": "GPT Latest (GPT-6 Astra)", "modalities": { "input": [ "text", @@ -277906,7 +278438,7 @@ ] }, "limit": { - "context": 1000000, + "context": 1050000, "output": 128000 }, "temperature": false, @@ -277917,7 +278449,6 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, "effort_options": [ "low", "medium", @@ -277929,92 +278460,101 @@ }, "attachment": true, "open_weights": false, - "knowledge": "2026-05", - "release_date": "2026-07-24", - "last_updated": "2026-07-24", + "knowledge": "2026-04-30", + "release_date": "2026-09-04", + "last_updated": "2026-09-04", "type": "chat" }, { - "id": "glm-4.6v", - "name": "GLM-4.6V", - "display_name": "GLM-4.6V", + "id": "openai/gpt-5.5", + "name": "GPT-5.5", + "display_name": "GPT-5.5", "modalities": { "input": [ "text", "image", - "video" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 32768 + "context": 1050000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, "default": true }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", + "effort_options": [ + "low", + "medium", + "high", + "xhigh" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" + } + }, "attachment": true, - "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-12-08", - "last_updated": "2025-12-08", + "open_weights": false, + "knowledge": "2025-12-01", + "release_date": "2026-04-23", + "last_updated": "2026-04-23", "type": "chat" }, { - "id": "glm-4.7", - "name": "glm-4.7", - "display_name": "glm-4.7", + "id": "openai/gpt-4.1-mini", + "name": "GPT-4.1 mini", + "display_name": "GPT-4.1 mini", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 204800, - "output": 131072 + "context": 1047576, + "output": 32768 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } + "supported": false }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-12-22", - "last_updated": "2025-12-22", + "attachment": true, + "open_weights": false, + "knowledge": "2024-04", + "release_date": "2025-04-14", + "last_updated": "2025-04-14", "type": "chat" }, { - "id": "gemini-3.5-flash-thinking", - "name": "gemini-3.5-flash-thinking", - "display_name": "gemini-3.5-flash-thinking", + "id": "openai/gpt-4.1", + "name": "GPT-4.1", + "display_name": "GPT-4.1", "modalities": { "input": [ "text", "image", - "video", - "audio", "pdf" ], "output": [ @@ -278022,60 +278562,40 @@ ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 1047576, + "output": 32768 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "level", - "level": "high", - "level_options": [ - "minimal", - "low", - "medium", - "high" - ], - "summaries": true, - "visibility": "summary", - "continuation": [ - "thought_signatures" - ] - } + "supported": false }, "attachment": true, "open_weights": false, - "knowledge": "2025-01", - "release_date": "2026-05-19", - "last_updated": "2026-05-19", + "knowledge": "2024-04", + "release_date": "2025-04-14", + "last_updated": "2025-04-14", "type": "chat" }, { - "id": "qwen3.6-flash", - "name": "Qwen3.6 Flash", - "display_name": "Qwen3.6 Flash", + "id": "openai/gpt-6-luna", + "name": "GPT-6 Luna", + "display_name": "GPT-6 Luna", "modalities": { "input": [ "text", "image", - "video" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 65536 + "context": 1050000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -278083,52 +278603,72 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "none", + "low", + "medium", + "high", + "xhigh", + "max" ] } }, "attachment": true, "open_weights": false, - "release_date": "2026-04-27", - "last_updated": "2026-04-27", + "knowledge": "2026-05-18", + "release_date": "2026-09-22", + "last_updated": "2026-09-22", "type": "chat" }, { - "id": "MiniMax-M2", - "name": "MiniMax-M2", - "display_name": "MiniMax-M2", + "id": "openai/o3", + "name": "o3", + "display_name": "o3", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 200000, + "output": 100000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, - "open_weights": true, - "release_date": "2025-10-27", - "last_updated": "2025-10-27", + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", + "effort_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" + } + }, + "attachment": true, + "open_weights": false, + "knowledge": "2024-05", + "release_date": "2025-04-16", + "last_updated": "2025-04-16", "type": "chat" }, { - "id": "qwen3-coder-480b-a35b-instruct", - "name": "qwen3-coder-480b-a35b-instruct", - "display_name": "qwen3-coder-480b-a35b-instruct", + "id": "greenpt/llama-3.3-70b-instruct", + "name": "Llama-3.3-70B-Instruct (Greenpt)", + "display_name": "Llama-3.3-70B-Instruct (Greenpt)", "modalities": { "input": [ "text" @@ -278138,8 +278678,8 @@ ] }, "limit": { - "context": 262144, - "output": 65536 + "context": 128000, + "output": 4096 }, "temperature": true, "tool_call": true, @@ -278148,20 +278688,19 @@ }, "attachment": false, "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-07-22", - "last_updated": "2025-07-22", + "knowledge": "2023-12", + "release_date": "2024-12-06", + "last_updated": "2024-12-06", "type": "chat" }, { - "id": "claude-opus-4-7", - "name": "claude-opus-4-7", - "display_name": "claude-opus-4-7", + "id": "greenpt/deepseek-v4.1-flash", + "name": "DeepSeek V4.1 Flash (Greenpt)", + "display_name": "DeepSeek V4.1 Flash (Greenpt)", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" @@ -278169,64 +278708,47 @@ }, "limit": { "context": 1000000, - "output": 128000 + "output": 384000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "high", "effort_options": [ + "none", "low", - "medium", "high", - "xhigh", "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "omitted", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Claude Opus 4.7 and newer Opus models require thinking.type = \"adaptive\" to enable thinking explicitly.", - "Manual budget_tokens requests return 400 on Claude Opus 4.7 and newer adaptive-only Opus models.", - "task_budget is separate from thinking control and should not be treated as a thinking budget." ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2026-01-31", - "release_date": "2026-04-16", - "last_updated": "2026-04-16", + "open_weights": true, + "knowledge": "2025-05", + "release_date": "2026-09-10", + "last_updated": "2026-09-10", "type": "chat" }, { - "id": "qwen3.5-35b-a3b", - "name": "Qwen3.5 35B-A3B", - "display_name": "Qwen3.5 35B-A3B", + "id": "greenpt/gpt-oss-120b", + "name": "GPT OSS 120B (Greenpt)", + "display_name": "GPT OSS 120B (Greenpt)", "modalities": { "input": [ - "text", - "image", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 65536 + "context": 128000, + "output": 32768 }, "temperature": true, "tool_call": true, @@ -278237,36 +278759,34 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "low", + "medium", + "high" ] } }, - "attachment": true, + "attachment": false, "open_weights": true, - "release_date": "2026-02-23", - "last_updated": "2026-02-23", + "knowledge": "2024-06", + "release_date": "2025-08-05", + "last_updated": "2025-08-05", "type": "chat" }, { - "id": "gpt-4o", - "name": "gpt-4o", - "display_name": "gpt-4o", + "id": "greenpt/qwen3-235b-a22b-instruct-2507", + "name": "Qwen3 235B-A22B Instruct 2507 (Greenpt)", + "display_name": "Qwen3 235B-A22B Instruct 2507 (Greenpt)", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 128000, + "context": 250000, "output": 16384 }, "temperature": true, @@ -278274,29 +278794,27 @@ "reasoning": { "supported": false }, - "attachment": true, - "open_weights": false, - "knowledge": "2023-09", - "release_date": "2024-05-13", - "last_updated": "2024-08-06", + "attachment": false, + "open_weights": true, + "release_date": "2025-07-21", + "last_updated": "2025-07-21", "type": "chat" }, { - "id": "grok-4.5", - "name": "Grok 4.5", - "display_name": "Grok 4.5", + "id": "greenpt/deepseek-v4-flash-0731", + "name": "DeepSeek V4 Flash 0731 (Greenpt)", + "display_name": "DeepSeek V4 Flash 0731 (Greenpt)", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 500000, - "output": 500000 + "context": 1000000, + "output": 384000 }, "temperature": true, "tool_call": true, @@ -278308,36 +278826,37 @@ "reasoning": { "supported": true, "effort_options": [ + "none", "low", - "medium", - "high" + "high", + "max" ] } }, - "attachment": true, - "open_weights": false, - "release_date": "2026-07-08", - "last_updated": "2026-07-08", + "attachment": false, + "open_weights": true, + "knowledge": "2025-05", + "release_date": "2026-07-31", + "last_updated": "2026-07-31", "type": "chat" }, { - "id": "gpt-5-pro", - "name": "gpt-5-pro", - "display_name": "gpt-5-pro", + "id": "nebius/openai/gpt-oss-120b", + "name": "GPT OSS 120B (Nebius)", + "display_name": "GPT OSS 120B (Nebius)", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 400000, - "output": 272000 + "context": 131072, + "output": 32768 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -278346,34 +278865,27 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "fixed", - "effort": "high", - "verbosity": "medium", - "verbosity_options": [ + "effort_options": [ "low", "medium", "high" - ], - "visibility": "hidden" + ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2024-09-30", - "release_date": "2025-10-06", - "last_updated": "2025-10-06", + "attachment": false, + "open_weights": true, + "knowledge": "2024-06", + "release_date": "2025-08-05", + "last_updated": "2025-08-05", "type": "chat" }, { - "id": "kimi-k2.6", - "name": "Kimi K2.6", - "display_name": "Kimi K2.6", + "id": "nebius/Qwen/Qwen3-30B-A3B-Instruct-2507", + "name": "Qwen3 30B-A3B Instruct 2507 (Nebius)", + "display_name": "Qwen3 30B-A3B Instruct 2507 (Nebius)", "modalities": { "input": [ - "text", - "image", - "video" + "text" ], "output": [ "text" @@ -278381,83 +278893,53 @@ }, "limit": { "context": 262144, - "output": 262144 + "output": 16384 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } + "supported": false }, - "attachment": true, + "attachment": false, "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-04-20", - "last_updated": "2026-04-20", + "release_date": "2025-07-28", + "last_updated": "2025-07-28", "type": "chat" }, { - "id": "qwen3.5-plus", - "name": "Qwen3.5 Plus", - "display_name": "Qwen3.5 Plus", + "id": "nebius/Qwen/Qwen3-235B-A22B-Instruct-2507", + "name": "Qwen3 235B-A22B Instruct 2507 (Nebius)", + "display_name": "Qwen3 235B-A22B Instruct 2507 (Nebius)", "modalities": { "input": [ - "text", - "image", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 65536 + "context": 262144, + "output": 16384 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } + "supported": false }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-04", - "release_date": "2026-02-16", - "last_updated": "2026-02-16", + "attachment": false, + "open_weights": true, + "release_date": "2025-07-21", + "last_updated": "2025-07-21", "type": "chat" }, { - "id": "kimi-k2.5", - "name": "Kimi K2.5", - "display_name": "Kimi K2.5", + "id": "nebius/nvidia/nemotron-3-super-120b-a12b", + "name": "Nemotron 3 Super 120B A12B (Nebius)", + "display_name": "Nemotron 3 Super 120B A12B (Nebius)", "modalities": { "input": [ - "text", - "image", - "video" + "text" ], "output": [ "text" @@ -278467,7 +278949,7 @@ "context": 262144, "output": 262144 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -278475,41 +278957,37 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "none", + "low", + "medium" ] } }, - "attachment": true, + "attachment": false, "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-01-27", - "last_updated": "2026-01-27", + "knowledge": "2025-06", + "release_date": "2026-03-11", + "last_updated": "2026-03-11", "type": "chat" }, { - "id": "claude-opus-5-5", - "name": "Claude Opus 5.5", - "display_name": "Claude Opus 5.5", + "id": "nebius/nvidia/Nemotron-3-Ultra-550b-a55b", + "name": "Nemotron 3 Ultra 550B A55B (Nebius)", + "display_name": "Nemotron 3 Ultra 550B A55B (Nebius)", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, + "context": 1048576, "output": 128000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -278517,88 +278995,67 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, "effort_options": [ - "low", + "none", "medium", - "high", - "xhigh", - "max" + "high" ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-06", - "release_date": "2026-09-22", - "last_updated": "2026-09-22", + "attachment": false, + "open_weights": true, + "knowledge": "2025-09", + "release_date": "2026-06-04", + "last_updated": "2026-06-04", "type": "chat" }, { - "id": "gemini-3-pro-image-preview", - "name": "gemini-3-pro-image-preview", - "display_name": "gemini-3-pro-image-preview", + "id": "nebius/google/gemma-3-27b-it", + "name": "Gemma 3 27B IT (Nebius)", + "display_name": "Gemma 3 27B IT (Nebius)", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ - "text", - "image" + "text" ] }, "limit": { - "context": 32768, - "output": 64000 + "context": 110000, + "output": 131072 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "level", - "level": "high", - "level_options": [ - "low", - "high" - ], - "summaries": true, - "visibility": "summary", - "continuation": [ - "thought_signatures" - ] - } + "supported": false }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-06", - "release_date": "2025-11-20", - "last_updated": "2025-11-20" + "attachment": false, + "open_weights": true, + "knowledge": "2024-08", + "release_date": "2025-03-12", + "last_updated": "2025-03-12", + "type": "chat" }, { - "id": "o3", - "name": "o3", - "display_name": "o3", + "id": "nebius/meta-models/muse-spark-open", + "name": "Muse Spark Open (Nebius)", + "display_name": "Muse Spark Open (Nebius)", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 100000 + "context": 1048576, + "output": 131072 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -278606,29 +279063,25 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", "effort_options": [ "low", "medium", - "high" - ], - "visibility": "hidden" + "high", + "xhigh" + ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2024-05", - "release_date": "2025-04-16", - "last_updated": "2025-04-16", + "open_weights": true, + "knowledge": "2026-01-04", + "release_date": "2026-10-15", + "last_updated": "2026-10-15", "type": "chat" }, { - "id": "gemini-2.5-flash", - "name": "gemini-2.5-flash", - "display_name": "gemini-2.5-flash", + "id": "nebius/deepseek-ai/DeepSeek-V4.1-Flash-1017", + "name": "DeepSeek V4.1 Flash 1017 (Nebius)", + "display_name": "DeepSeek V4.1 Flash 1017 (Nebius)", "modalities": { "input": [ "text", @@ -278639,8 +279092,8 @@ ] }, "limit": { - "context": 1000000, - "output": 65536 + "context": 1048576, + "output": 384000 }, "temperature": true, "tool_call": true, @@ -278650,40 +279103,29 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "budget", - "budget": { - "default": -1, - "min": 0, - "max": 24576, - "auto": -1, - "off": 0, - "unit": "tokens" - }, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thought_signatures" + "effort_options": [ + "none", + "low", + "high", + "max" ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2025-01", - "release_date": "2025-06-17", - "last_updated": "2025-06-17", + "open_weights": true, + "knowledge": "2025-05", + "release_date": "2026-10-17", + "last_updated": "2026-10-17", "type": "chat" }, { - "id": "kimi-k3.1-pro", - "name": "Kimi K3.1 Pro", - "display_name": "Kimi K3.1 Pro", + "id": "nebius/deepseek-ai/DeepSeek-V4.1-Flash", + "name": "DeepSeek V4.1 Flash (Nebius)", + "display_name": "DeepSeek V4.1 Flash (Nebius)", "modalities": { "input": [ "text", - "image", - "video" + "image" ], "output": [ "text" @@ -278691,9 +279133,9 @@ }, "limit": { "context": 1048576, - "output": 131072 + "output": 384000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -278702,12 +279144,6 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], "effort_options": [ "none", "low", @@ -278717,30 +279153,29 @@ } }, "attachment": true, - "open_weights": false, - "release_date": "2026-10-23", - "last_updated": "2026-10-23", + "open_weights": true, + "knowledge": "2025-05", + "release_date": "2026-09-10", + "last_updated": "2026-09-10", "type": "chat" }, { - "id": "claude-sonnet-5", - "name": "Claude Sonnet 5", - "display_name": "Claude Sonnet 5", + "id": "nebius/deepseek-ai/DeepSeek-V4-Flash-0731", + "name": "DeepSeek V4 Flash 0731 (Nebius)", + "display_name": "DeepSeek V4 Flash 0731 (Nebius)", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 1048576, + "output": 384000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -278750,105 +279185,115 @@ "reasoning": { "supported": true, "effort_options": [ + "none", "low", - "medium", "high", - "xhigh", "max" ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-01-31", - "release_date": "2026-06-30", - "last_updated": "2026-06-30", + "attachment": false, + "open_weights": true, + "knowledge": "2025-05", + "release_date": "2026-07-31", + "last_updated": "2026-07-31", "type": "chat" }, { - "id": "doubao-seed-1-6-vision-250815", - "name": "doubao-seed-1-6-vision-250815", - "display_name": "doubao-seed-1-6-vision-250815", + "id": "nebius/deepseek-ai/DeepSeek-V4-Pro-0813", + "name": "DeepSeek V4 Pro 0813 (Nebius)", + "display_name": "DeepSeek V4 Pro 0813 (Nebius)", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 256000, - "output": 32000 + "context": 1048576, + "output": 384000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": true, - "open_weights": false, - "release_date": "2025-08-15", - "last_updated": "2025-08-15", + "extra_capabilities": { + "reasoning": { + "supported": true, + "effort_options": [ + "none", + "low", + "high", + "max" + ] + } + }, + "attachment": false, + "open_weights": true, + "release_date": "2026-08-13", + "last_updated": "2026-08-22", "type": "chat" }, { - "id": "gpt-5.2-chat-latest", - "name": "gpt-5.2-chat-latest", - "display_name": "gpt-5.2-chat-latest", + "id": "greenference/gpt-oss-20b", + "name": "GPT OSS 20B (Greenference)", + "display_name": "GPT OSS 20B (Greenference)", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 16384 + "context": 131072, + "output": 32768 }, - "temperature": false, - "tool_call": true, + "temperature": true, + "tool_call": false, "reasoning": { "supported": true, "default": true }, "extra_capabilities": { "reasoning": { + "supported": true, "effort_options": [ - "medium" + "low", + "medium", + "high" ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2025-12-11", - "last_updated": "2025-12-11", + "attachment": false, + "open_weights": true, + "knowledge": "2024-06", + "release_date": "2025-08-05", + "last_updated": "2025-08-05", "type": "chat" }, { - "id": "claude-opus-4-7-thinking", - "name": "claude-opus-4-7-thinking", - "display_name": "claude-opus-4-7-thinking", + "id": "deepinfra/nemotron-3-ultra-550b-a55b", + "name": "Nemotron 3 Ultra 550B A55B (Deep Infra)", + "display_name": "Nemotron 3 Ultra 550B A55B (Deep Infra)", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, + "context": 262144, "output": 128000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -278857,55 +279302,35 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "high", "effort_options": [ - "low", + "none", "medium", - "high", - "xhigh", - "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "omitted", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Claude Opus 4.7 and newer Opus models require thinking.type = \"adaptive\" to enable thinking explicitly.", - "Manual budget_tokens requests return 400 on Claude Opus 4.7 and newer adaptive-only Opus models.", - "task_budget is separate from thinking control and should not be treated as a thinking budget." + "high" ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-01-31", - "release_date": "2026-04-16", - "last_updated": "2026-04-16", + "attachment": false, + "open_weights": true, + "knowledge": "2025-09", + "release_date": "2026-06-04", + "last_updated": "2026-06-04", "type": "chat" }, { - "id": "gemini-3.1-flash-lite", - "name": "Gemini 3.1 Flash Lite", - "display_name": "Gemini 3.1 Flash Lite", + "id": "deepinfra/nemotron-3-super-120b-a12b", + "name": "Nemotron 3 Super 120B A12B (Deep Infra)", + "display_name": "Nemotron 3 Super 120B A12B (Deep Infra)", "modalities": { "input": [ - "text", - "image", - "video", - "audio", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 262144, + "output": 262144 }, "temperature": true, "tool_call": true, @@ -278915,38 +279340,35 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, "effort_options": [ - "minimal", + "none", "low", - "medium", - "high" + "medium" ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-01", - "release_date": "2026-05-07", - "last_updated": "2026-05-07", + "attachment": false, + "open_weights": true, + "knowledge": "2025-06", + "release_date": "2026-03-11", + "last_updated": "2026-03-11", "type": "chat" }, { - "id": "gemini-3-pro-preview", - "name": "gemini-3-pro-preview", - "display_name": "gemini-3-pro-preview", + "id": "deepinfra/openai/gpt-oss-120b", + "name": "GPT OSS 120B (Deep Infra)", + "display_name": "GPT OSS 120B (Deep Infra)", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 64000 + "context": 131072, + "output": 32768 }, "temperature": true, "tool_call": true, @@ -278957,46 +279379,35 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "level", - "level": "high", - "level_options": [ + "effort_options": [ "low", + "medium", "high" - ], - "summaries": true, - "visibility": "summary", - "continuation": [ - "thought_signatures" ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-06", - "release_date": "2025-11-18", - "last_updated": "2025-11-18", + "attachment": false, + "open_weights": true, + "knowledge": "2024-06", + "release_date": "2025-08-05", + "last_updated": "2025-08-05", "type": "chat" }, { - "id": "gemini-3.7-flash", - "name": "Gemini 3.7 Flash", - "display_name": "Gemini 3.7 Flash", + "id": "deepinfra/openai/gpt-oss-20b", + "name": "GPT OSS 20B (Deep Infra)", + "display_name": "GPT OSS 20B (Deep Infra)", "modalities": { "input": [ - "text", - "image", - "video", - "audio", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 131072, + "output": 32768 }, "temperature": true, "tool_call": true, @@ -279007,130 +279418,80 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "level", - "level": "high", - "level_options": [ - "minimal", + "effort_options": [ "low", "medium", "high" - ], - "summaries": true, - "visibility": "summary", - "continuation": [ - "thought_signatures" ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-03", - "release_date": "2026-08-13", - "last_updated": "2026-08-13", + "attachment": false, + "open_weights": true, + "knowledge": "2024-06", + "release_date": "2025-08-05", + "last_updated": "2025-08-05", "type": "chat" }, { - "id": "gpt-6-sol", - "name": "GPT-6 Sol", - "display_name": "GPT-6 Sol", + "id": "deepinfra/Qwen/Qwen3-Next-80B-A3B-Instruct", + "name": "Qwen3-Next 80B-A3B Instruct (Deep Infra)", + "display_name": "Qwen3-Next 80B-A3B Instruct (Deep Infra)", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 262144, + "output": 32768 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "none", - "low", - "medium", - "high", - "xhigh", - "max" - ] - } + "supported": false }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-04-20", - "release_date": "2026-09-22", - "last_updated": "2026-09-22", + "attachment": false, + "open_weights": true, + "knowledge": "2025-04", + "release_date": "2025-09", + "last_updated": "2025-09", "type": "chat" }, { - "id": "gpt-5.6-sol", - "name": "GPT-5.6 Sol", - "display_name": "GPT-5.6 Sol", + "id": "deepinfra/Qwen/Qwen3-Coder-480B-A35B-Instruct", + "name": "Qwen3-Coder 480B-A35B Instruct (Deep Infra)", + "display_name": "Qwen3-Coder 480B-A35B Instruct (Deep Infra)", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 262144, + "output": 65536 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", - "effort_options": [ - "none", - "low", - "medium", - "high", - "xhigh", - "max" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" - } - }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-02-16", - "release_date": "2026-07-09", - "last_updated": "2026-07-09", + "supported": false + }, + "attachment": false, + "open_weights": true, + "knowledge": "2025-04", + "release_date": "2025-07-22", + "last_updated": "2025-07-22", "type": "chat" }, { - "id": "qwen3-235b-a22b", - "name": "Qwen3-235B-A22B", - "display_name": "Qwen3-235B-A22B", + "id": "deepinfra/Qwen/Qwen2.5-7B-Instruct", + "name": "Qwen2.5 7B Instruct (Deep Infra)", + "display_name": "Qwen2.5 7B Instruct (Deep Infra)", "modalities": { "input": [ "text" @@ -279140,115 +279501,68 @@ ] }, "limit": { - "context": 128000, - "output": 16384 + "context": 32768, + "output": 8192 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } + "supported": false }, "attachment": false, "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-04-28", - "last_updated": "2025-04-28", + "release_date": "2024-09-19", + "last_updated": "2024-09-19", "type": "chat" }, { - "id": "claude-sonnet-4-6", - "name": "claude-sonnet-4-6", - "display_name": "claude-sonnet-4-6", + "id": "deepinfra/Qwen/Qwen3-VL-30B-A3B-Instruct", + "name": "Qwen3 VL 30B A3B Instruct (Deep Infra)", + "display_name": "Qwen3 VL 30B A3B Instruct (Deep Infra)", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 64000 + "context": 262144, + "output": 32768 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": false - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "mixed", - "budget": { - "min": 1024, - "unit": "tokens" - }, - "effort": "high", - "effort_options": [ - "low", - "medium", - "high", - "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Anthropic recommends adaptive thinking with effort for Claude 4.6; budget_tokens remains a deprecated compatibility path.", - "Anthropic API defaults effort to high; lower effort levels should be chosen per workload." - ] - } + "supported": false }, "attachment": true, - "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-02-17", - "last_updated": "2026-03-13", + "open_weights": true, + "release_date": "2025-10-04", + "last_updated": "2025-10-04", "type": "chat" }, { - "id": "qwen3.8-max", - "name": "Qwen3.8 Max", - "display_name": "Qwen3.8 Max", + "id": "deepinfra/Qwen/Qwen3-Max", + "name": "Qwen3 Max (Deep Infra)", + "display_name": "Qwen3 Max (Deep Infra)", "modalities": { "input": [ - "text", - "image", - "video", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 256000, + "output": 65536 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true + "supported": true }, "extra_capabilities": { "reasoning": { @@ -279258,24 +279572,20 @@ "visibility": "summary", "continuation": [ "thinking_blocks" - ], - "effort_options": [ - "low", - "medium", - "xhigh" ] } }, - "attachment": true, + "attachment": false, "open_weights": false, - "release_date": "2026-08-03", - "last_updated": "2026-08-03", + "knowledge": "2025-04", + "release_date": "2025-09-23", + "last_updated": "2025-09-23", "type": "chat" }, { - "id": "grok-4.1", - "name": "grok-4.1", - "display_name": "grok-4.1", + "id": "deepinfra/Qwen/Qwen3-VL-235B-A22B-Instruct", + "name": "Qwen3 VL 235B A22B Instruct (Deep Infra)", + "display_name": "Qwen3 VL 235B A22B Instruct (Deep Infra)", "modalities": { "input": [ "text", @@ -279286,8 +279596,8 @@ ] }, "limit": { - "context": 200000, - "output": 64000 + "context": 262144, + "output": 32768 }, "temperature": true, "tool_call": true, @@ -279295,66 +279605,56 @@ "supported": false }, "attachment": true, - "open_weights": false, - "knowledge": "2025-06", - "release_date": "2025-11-18", - "last_updated": "2025-11-18", + "open_weights": true, + "knowledge": "2025-03-31", + "release_date": "2025-09-23", + "last_updated": "2025-09-23", "type": "chat" }, { - "id": "MiniMax-M3", - "name": "MiniMax-M3", - "display_name": "MiniMax-M3", + "id": "deepinfra/Qwen/Qwen3-235B-A22B-Instruct-2507", + "name": "Qwen3 235B-A22B Instruct 2507 (Deep Infra)", + "display_name": "Qwen3 235B-A22B Instruct 2507 (Deep Infra)", "modalities": { "input": [ - "text", - "image", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 512000 + "context": 262144, + "output": 16384 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true - } + "supported": false }, - "attachment": true, + "attachment": false, "open_weights": true, - "release_date": "2026-06-01", - "last_updated": "2026-06-01", + "release_date": "2025-07-21", + "last_updated": "2025-07-21", "type": "chat" }, { - "id": "gpt-5.6-terra", - "name": "GPT-5.6 Terra", - "display_name": "GPT-5.6 Terra", + "id": "deepinfra/Qwen/Qwen3-235B-A22B-Thinking-2507", + "name": "Qwen3 235B-A22B Thinking 2507 (Deep Infra)", + "display_name": "Qwen3 235B-A22B Thinking 2507 (Deep Infra)", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 262144, + "output": 81920 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -279363,37 +279663,24 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", - "effort_options": [ - "none", - "low", - "medium", - "high", - "xhigh", - "max" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-02-16", - "release_date": "2026-07-09", - "last_updated": "2026-07-09", + "attachment": false, + "open_weights": true, + "release_date": "2025-07-25", + "last_updated": "2025-07-25", "type": "chat" }, { - "id": "qwen3.7-max-2026-06-08", - "name": "qwen3.7-max-2026-06-08", - "display_name": "qwen3.7-max-2026-06-08", + "id": "deepinfra/nvidia/Llama-3.1-Nemotron-70B-Instruct", + "name": "Llama 3.1 Nemotron 70B Instruct (Deep Infra)", + "display_name": "Llama 3.1 Nemotron 70B Instruct (Deep Infra)", "modalities": { "input": [ "text" @@ -279403,25 +279690,24 @@ ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 131072, + "output": 8192 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true + "supported": false }, "attachment": false, - "open_weights": false, - "release_date": "2026-05-21", - "last_updated": "2026-05-21", + "open_weights": true, + "release_date": "2025-04-15", + "last_updated": "2025-04-15", "type": "chat" }, { - "id": "glm-4.6", - "name": "glm-4.6", - "display_name": "glm-4.6", + "id": "deepinfra/nvidia/Nemotron-3-Nano-30B-A3B", + "name": "Nemotron 3 Nano 30B A3B (Deep Infra)", + "display_name": "Nemotron 3 Nano 30B A3B (Deep Infra)", "modalities": { "input": [ "text" @@ -279431,8 +279717,8 @@ ] }, "limit": { - "context": 204800, - "output": 131072 + "context": 262144, + "output": 262144 }, "temperature": true, "tool_call": true, @@ -279440,37 +279726,32 @@ "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "supported": true - } - }, "attachment": false, "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-09-30", - "last_updated": "2025-09-30", + "knowledge": "2025-06-25", + "release_date": "2025-12-15", + "last_updated": "2025-12-15", "type": "chat" }, { - "id": "gpt-6-luna", - "name": "GPT-6 Luna", - "display_name": "GPT-6 Luna", + "id": "deepinfra/thinkingmachines/Inkling-Small", + "name": "Inkling Small (Deep Infra)", + "display_name": "Inkling Small (Deep Infra)", "modalities": { "input": [ "text", "image", - "pdf" + "audio" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 524288, + "output": 1048576 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -279480,39 +279761,39 @@ "reasoning": { "effort_options": [ "none", + "minimal", "low", "medium", "high", - "xhigh", "max" ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2026-05-18", - "release_date": "2026-09-22", - "last_updated": "2026-09-22", + "open_weights": true, + "release_date": "2026-07-30", + "last_updated": "2026-07-30", "type": "chat" }, { - "id": "gpt-5-mini", - "name": "gpt-5-mini", - "display_name": "gpt-5-mini", + "id": "deepinfra/thinkingmachines/Inkling", + "name": "Inkling (Deep Infra)", + "display_name": "Inkling (Deep Infra)", "modalities": { "input": [ "text", - "image" + "image", + "audio" ], "output": [ "text" ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 524288, + "output": 1048576 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -279520,82 +279801,79 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", "effort_options": [ + "none", "minimal", "low", "medium", - "high" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + "high", + "max" + ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2024-05-30", - "release_date": "2025-08-07", - "last_updated": "2025-08-07", + "open_weights": true, + "release_date": "2026-07-15", + "last_updated": "2026-07-15", "type": "chat" }, { - "id": "deepseek-v3.2", - "name": "deepseek-v3.2", - "display_name": "deepseek-v3.2", + "id": "deepinfra/stepfun-ai/Step-3.7-Flash", + "name": "Step 3.7 Flash (Deep Infra)", + "display_name": "Step 3.7 Flash (Deep Infra)", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 8192 + "context": 262144, + "output": 256000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true + "supported": true, + "default": true }, "extra_capabilities": { "reasoning": { - "supported": true + "effort_options": [ + "low", + "medium", + "high" + ] } }, - "attachment": false, + "attachment": true, "open_weights": true, - "knowledge": "2024-12", - "release_date": "2025-12-01", - "last_updated": "2025-12-01", + "knowledge": "2026-03-01", + "release_date": "2026-05-29", + "last_updated": "2026-05-29", "type": "chat" }, { - "id": "grok-4.7", - "name": "Grok 4.7", - "display_name": "Grok 4.7", + "id": "deepinfra/stepfun-ai/Step-5-Preview", + "name": "Step 5 Preview (Deep Infra)", + "display_name": "Step 5 Preview (Deep Infra)", "modalities": { "input": [ "text", "image", - "pdf" + "video" ], "output": [ "text" ] }, "limit": { - "context": 500000, - "output": 500000 + "context": 1048576, + "output": 1000000 }, "temperature": true, "tool_call": true, @@ -279605,45 +279883,34 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], "effort_options": [ "low", "medium", - "high", - "xhigh" + "high" ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2026-05", - "release_date": "2026-09-21", - "last_updated": "2026-09-21", + "open_weights": true, + "release_date": "2026-09-16", + "last_updated": "2026-09-20", "type": "chat" }, { - "id": "qwen3.6-plus", - "name": "Qwen3.6 Plus", - "display_name": "Qwen3.6 Plus", + "id": "deepinfra/stepfun-ai/Step-3.5-Flash", + "name": "Step 3.5 Flash (Deep Infra)", + "display_name": "Step 3.5 Flash (Deep Infra)", "modalities": { "input": [ - "text", - "image", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 65536 + "context": 262144, + "output": 256000 }, "temperature": true, "tool_call": true, @@ -279653,194 +279920,168 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "low", + "high" ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-04", - "release_date": "2026-04-02", - "last_updated": "2026-04-02", + "attachment": false, + "open_weights": true, + "knowledge": "2025-01", + "release_date": "2026-01-29", + "last_updated": "2026-02-13", "type": "chat" }, { - "id": "kimi-k2-thinking", - "name": "kimi-k2-thinking", - "display_name": "kimi-k2-thinking", + "id": "deepinfra/meta-llama/Llama-Guard-4-12B", + "name": "Llama Guard 4 12B (Deep Infra)", + "display_name": "Llama Guard 4 12B (Deep Infra)", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 163840, + "output": 163840 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } + "supported": false }, - "attachment": false, + "attachment": true, "open_weights": true, - "knowledge": "2025-06", - "release_date": "2025-11-06", - "last_updated": "2025-11-06", + "release_date": "2025-04-29", + "last_updated": "2025-04-29", "type": "chat" }, { - "id": "gemini-3.1-flash-lite-preview", - "name": "Gemini 3.1 Flash Lite Preview", - "display_name": "Gemini 3.1 Flash Lite Preview", + "id": "deepinfra/meta-llama/Llama-3.2-11B-Vision-Instruct", + "name": "Llama-3.2-11B-Vision-Instruct (Deep Infra)", + "display_name": "Llama-3.2-11B-Vision-Instruct (Deep Infra)", "modalities": { "input": [ "text", - "image", - "video", - "audio", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 131072, + "output": 4096 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "minimal", - "low", - "medium", - "high" - ] - } + "supported": false }, "attachment": true, - "open_weights": false, - "knowledge": "2025-01", - "release_date": "2026-03-03", - "last_updated": "2026-03-03", + "open_weights": true, + "knowledge": "2023-12", + "release_date": "2024-09-25", + "last_updated": "2024-09-25", "type": "chat" }, { - "id": "doubao-seed-1-6-thinking-250715", - "name": "doubao-seed-1-6-thinking-250715", - "display_name": "doubao-seed-1-6-thinking-250715", + "id": "deepinfra/meta-llama/Llama-3.3-70B-Instruct", + "name": "Llama-3.3-70B-Instruct (Deep Infra)", + "display_name": "Llama-3.3-70B-Instruct (Deep Infra)", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 256000, - "output": 16000 + "context": 131072, + "output": 4096 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true + "supported": false }, - "attachment": true, - "open_weights": false, - "release_date": "2025-07-15", - "last_updated": "2025-07-15", + "attachment": false, + "open_weights": true, + "knowledge": "2023-12", + "release_date": "2024-12-06", + "last_updated": "2024-12-06", "type": "chat" }, { - "id": "gpt-5.1", - "name": "gpt-5.1", - "display_name": "gpt-5.1", + "id": "deepinfra/meta-llama/Llama-3.2-3B-Instruct", + "name": "Llama 3.2 3B Instruct (Deep Infra)", + "display_name": "Llama 3.2 3B Instruct (Deep Infra)", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 131072, + "output": 131072 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": false + "supported": false }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "none", - "effort_options": [ - "none", - "low", - "medium", - "high" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" - } + "attachment": false, + "open_weights": true, + "knowledge": "2023-12", + "release_date": "2024-09-25", + "last_updated": "2024-09-25", + "type": "chat" + }, + { + "id": "deepinfra/meta-llama/Llama-Guard-3-8B", + "name": "Llama-Guard-3-8B (Deep Infra)", + "display_name": "Llama-Guard-3-8B (Deep Infra)", + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] }, - "attachment": true, - "open_weights": false, - "knowledge": "2024-09-30", - "release_date": "2025-11-13", - "last_updated": "2025-11-13", + "limit": { + "context": 131072, + "output": 4096 + }, + "temperature": true, + "tool_call": false, + "reasoning": { + "supported": false + }, + "attachment": false, + "open_weights": true, + "knowledge": "2023-12", + "release_date": "2024-07-23", + "last_updated": "2024-07-23", "type": "chat" }, { - "id": "kimi-k2.7-code", - "name": "Kimi K2.7 Code", - "display_name": "Kimi K2.7 Code", + "id": "deepinfra/moonshotai/Kimi-K2.5", + "name": "Kimi K2.5 (Deep Infra)", + "display_name": "Kimi K2.5 (Deep Infra)", "modalities": { "input": [ "text", - "image", - "video" + "image" ], "output": [ "text" @@ -279870,26 +280111,52 @@ "attachment": true, "open_weights": true, "knowledge": "2025-01", - "release_date": "2026-06-12", - "last_updated": "2026-06-12", + "release_date": "2026-01-27", + "last_updated": "2026-01-27", "type": "chat" }, { - "id": "claude-opus-4-1-20250805-thinking", - "name": "claude-opus-4-1-20250805-thinking", - "display_name": "claude-opus-4-1-20250805-thinking", + "id": "deepinfra/moonshotai/Kimi-K2-Instruct", + "name": "Kimi K2 Instruct (Deep Infra)", + "display_name": "Kimi K2 Instruct (Deep Infra)", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 32000 + "context": 131072, + "output": 32768 + }, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": false + }, + "attachment": false, + "open_weights": true, + "release_date": "2025-07-11", + "last_updated": "2025-07-11", + "type": "chat" + }, + { + "id": "deepinfra/zai-org/GLM-4.7-Flash", + "name": "GLM-4.7-Flash (Deep Infra)", + "display_name": "GLM-4.7-Flash (Deep Infra)", + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 202752, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -279899,51 +280166,33 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "budget", - "budget": { - "min": 1024, - "unit": "tokens" - }, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", - "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." - ] + "supported": true } }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-03-31", - "release_date": "2025-08-05", - "last_updated": "2025-08-05", + "attachment": false, + "open_weights": true, + "knowledge": "2025-04", + "release_date": "2026-01-19", + "last_updated": "2026-01-19", "type": "chat" }, { - "id": "gpt-5.6-luna", - "name": "GPT-5.6 Luna", - "display_name": "GPT-5.6 Luna", + "id": "deepinfra/tencent/Hy3", + "name": "Hy3 (Deep Infra)", + "display_name": "Hy3 (Deep Infra)", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1050000, + "context": 262144, "output": 128000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -279952,49 +280201,34 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", "effort_options": [ "none", "low", - "medium", - "high", - "xhigh", - "max" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", "high" - ], - "visibility": "hidden" + ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-02-16", - "release_date": "2026-07-09", - "last_updated": "2026-07-09", + "attachment": false, + "open_weights": true, + "release_date": "2026-07-06", + "last_updated": "2026-07-06", "type": "chat" }, { - "id": "grok-4-fast-reasoning", - "name": "grok-4-fast-reasoning", - "display_name": "grok-4-fast-reasoning", + "id": "deepinfra/tencent/Hy4-preview", + "name": "Hy4 preview (Deep Infra)", + "display_name": "Hy4 preview (Deep Infra)", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 2000000, - "output": 30000 + "context": 1048576, + "output": 64000 }, "temperature": true, "tool_call": true, @@ -280002,121 +280236,84 @@ "supported": true, "default": true }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-06", - "release_date": "2025-12-18", - "last_updated": "2025-12-18", + "extra_capabilities": { + "reasoning": { + "supported": true, + "effort_options": [ + "none", + "low", + "high" + ] + } + }, + "attachment": false, + "open_weights": true, + "release_date": "2026-08-28", + "last_updated": "2026-08-28", "type": "chat" }, { - "id": "claude-opus-4-1-20250805", - "name": "claude-opus-4-1-20250805", - "display_name": "claude-opus-4-1-20250805", + "id": "deepinfra/google/gemma-3-12b-it", + "name": "Gemma 3 12B IT (Deep Infra)", + "display_name": "Gemma 3 12B IT (Deep Infra)", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 32000 + "context": 131072, + "output": 131072 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": false - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "budget", - "budget": { - "min": 1024, - "unit": "tokens" - }, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", - "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." - ] - } + "supported": false }, "attachment": true, - "open_weights": false, - "knowledge": "2025-03-31", - "release_date": "2025-08-05", - "last_updated": "2025-08-05", + "open_weights": true, + "knowledge": "2024-08", + "release_date": "2025-03-12", + "last_updated": "2025-03-12", "type": "chat" }, { - "id": "claude-sonnet-4-5-20250929", - "name": "claude-sonnet-4-5-20250929", - "display_name": "claude-sonnet-4-5-20250929", + "id": "deepinfra/google/gemma-3-27b-it", + "name": "Gemma 3 27B IT (Deep Infra)", + "display_name": "Gemma 3 27B IT (Deep Infra)", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 64000 + "context": 131072, + "output": 131072 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": false - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "budget", - "budget": { - "min": 1024, - "unit": "tokens" - }, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", - "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." - ] - } + "supported": false }, "attachment": true, - "open_weights": false, - "knowledge": "2025-07-31", - "release_date": "2025-09-29", - "last_updated": "2025-09-29", + "open_weights": true, + "knowledge": "2024-08", + "release_date": "2025-03-12", + "last_updated": "2025-03-12", "type": "chat" }, { - "id": "gemini-2.5-flash-lite-preview-09-2025", - "name": "gemini-2.5-flash-lite-preview-09-2025", - "display_name": "gemini-2.5-flash-lite-preview-09-2025", + "id": "deepinfra/google/gemma-3-4b-it", + "name": "Gemma 3 4B IT (Deep Infra)", + "display_name": "Gemma 3 4B IT (Deep Infra)", "modalities": { "input": [ "text", @@ -280127,97 +280324,80 @@ ] }, "limit": { - "context": 1000000, - "output": 65536 + "context": 131072, + "output": 131072 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": false - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "budget", - "budget": { - "default": -1, - "min": 512, - "max": 24576, - "auto": -1, - "unit": "tokens" - }, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thought_signatures" - ] - } + "supported": false }, "attachment": true, - "open_weights": false, - "knowledge": "2025-01", - "release_date": "2025-09-26", - "last_updated": "2025-09-26", + "open_weights": true, + "knowledge": "2024-08", + "release_date": "2025-03-12", + "last_updated": "2025-03-12", "type": "chat" }, { - "id": "gpt-5.4-mini", - "name": "gpt-5.4-mini", - "display_name": "gpt-5.4-mini", + "id": "deepinfra/Gryphe/MythoMax-L2-13b", + "name": "MythoMax L2 13B (Deep Infra)", + "display_name": "MythoMax L2 13B (Deep Infra)", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 4096, + "output": 3200 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": false + "supported": false }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "none", - "effort_options": [ - "none", - "low", - "medium", - "high", - "xhigh" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" - } + "attachment": false, + "open_weights": true, + "release_date": "2023-08-10", + "last_updated": "2023-08-10", + "type": "chat" + }, + { + "id": "deepinfra/mistralai/Mistral-Small-24B-Instruct-2501", + "name": "Mistral Small 3 (Deep Infra)", + "display_name": "Mistral Small 3 (Deep Infra)", + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-03-17", - "last_updated": "2026-03-17", + "limit": { + "context": 32768, + "output": 16384 + }, + "temperature": true, + "tool_call": false, + "reasoning": { + "supported": false + }, + "attachment": false, + "open_weights": true, + "knowledge": "2023-10-31", + "release_date": "2025-01-30", + "last_updated": "2025-01-30", "type": "chat" }, { - "id": "qwen3-235b-a22b-instruct-2507", - "name": "qwen3-235b-a22b-instruct-2507", - "display_name": "qwen3-235b-a22b-instruct-2507", + "id": "deepinfra/mistralai/Mixtral-8x7B-Instruct-v0.1", + "name": "Mixtral 8x7B Instruct v0.1 (Deep Infra)", + "display_name": "Mixtral 8x7B Instruct v0.1 (Deep Infra)", "modalities": { "input": [ "text" @@ -280227,8 +280407,8 @@ ] }, "limit": { - "context": 128000, - "output": 65536 + "context": 32768, + "output": 32768 }, "temperature": true, "tool_call": true, @@ -280237,101 +280417,108 @@ }, "attachment": false, "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-07-21", - "last_updated": "2025-07-21", + "release_date": "2023-12-11", + "last_updated": "2023-12-11", "type": "chat" }, { - "id": "qwen3.6-35b-a3b", - "name": "Qwen3.6 35B-A3B", - "display_name": "Qwen3.6 35B-A3B", + "id": "deepinfra/NousResearch/Hermes-3-Llama-3.1-70B", + "name": "Hermes 3 70B Instruct (Deep Infra)", + "display_name": "Hermes 3 70B Instruct (Deep Infra)", "modalities": { "input": [ - "text", - "image", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 65536 + "context": 131072, + "output": 16384 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true + "supported": false }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } + "attachment": false, + "open_weights": true, + "knowledge": "2023-12-31", + "release_date": "2024-08-18", + "last_updated": "2024-08-18", + "type": "chat" + }, + { + "id": "deepinfra/NousResearch/Hermes-3-Llama-3.1-405B", + "name": "Hermes 3 405B Instruct (Deep Infra)", + "display_name": "Hermes 3 405B Instruct (Deep Infra)", + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] }, - "attachment": true, + "limit": { + "context": 131072, + "output": 16384 + }, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": false + }, + "attachment": false, "open_weights": true, - "release_date": "2026-04-17", - "last_updated": "2026-04-17", + "knowledge": "2023-12-31", + "release_date": "2024-08-16", + "last_updated": "2024-08-16", "type": "chat" }, { - "id": "gemini-3.1-flash-image-preview", - "name": "gemini-3.1-flash-image-preview", - "display_name": "gemini-3.1-flash-image-preview", + "id": "deepinfra/microsoft/WizardLM-2-8x22B", + "name": "WizardLM 2 8x22B (Deep Infra)", + "display_name": "WizardLM 2 8x22B (Deep Infra)", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ - "text", - "image" + "text" ] }, "limit": { - "context": 131072, - "output": 32768 + "context": 65536, + "output": 8000 }, "temperature": true, "tool_call": false, "reasoning": { - "supported": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true - } + "supported": false }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-01", - "release_date": "2026-02-26", - "last_updated": "2026-02-26" + "attachment": false, + "open_weights": true, + "release_date": "2024-04-15", + "last_updated": "2024-04-15", + "type": "chat" }, { - "id": "glm-5.1", - "name": "GLM-5.1", - "display_name": "GLM-5.1", + "id": "deepinfra/meta-models/Muse-Glimmer-30B", + "name": "Muse Glimmer 30B (Deep Infra)", + "display_name": "Muse Glimmer 30B (Deep Infra)", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 200000, + "context": 131072, "output": 131072 }, "temperature": true, @@ -280342,76 +280529,76 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "low", + "medium", + "high", + "xhigh" ] } }, - "attachment": false, + "attachment": true, "open_weights": true, - "release_date": "2026-04-07", - "last_updated": "2026-04-07", + "knowledge": "2026-01-04", + "release_date": "2026-08-10", + "last_updated": "2026-08-10", "type": "chat" }, { - "id": "qwen3-30b-a3b", - "name": "Qwen3-30B-A3B", - "display_name": "Qwen3-30B-A3B", + "id": "deepinfra/ByteDance/Seed-2.0-mini", + "name": "Seed 2.0 Mini (Deep Infra)", + "display_name": "Seed 2.0 Mini (Deep Infra)", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 8192 + "context": 256000, + "output": 32000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true + "supported": true, + "default": true }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "none", + "minimal", + "low", + "medium", + "high" ] } }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-04-28", - "last_updated": "2025-04-28", + "attachment": true, + "open_weights": false, + "release_date": "2026-02-14", + "last_updated": "2026-02-14", "type": "chat" }, { - "id": "qwen3.8-flash", - "name": "Qwen3.8 Flash", - "display_name": "Qwen3.8 Flash", + "id": "deepinfra/ByteDance/Seed-2.0-code", + "name": "Seed 2.0 Code (Deep Infra)", + "display_name": "Seed 2.0 Code (Deep Infra)", "modalities": { "input": [ "text", - "image", - "video" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, + "context": 256000, "output": 131072 }, "temperature": true, @@ -280422,43 +280609,35 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], "effort_options": [ + "none", "low", "medium", - "xhigh" + "high" ] } }, "attachment": true, "open_weights": false, - "release_date": "2026-08-26", - "last_updated": "2026-08-26", + "release_date": "2026-02-14", + "last_updated": "2026-02-14", "type": "chat" }, { - "id": "claude-sonnet-4-6-thinking", - "name": "claude-sonnet-4-6-thinking", - "display_name": "claude-sonnet-4-6-thinking", + "id": "deepinfra/ibm-granite/granite-4.2-8b", + "name": "Granite 4.2 8B (Deep Infra)", + "display_name": "Granite 4.2 8B (Deep Infra)", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 64000 + "context": 131072, + "output": 117964 }, "temperature": true, "tool_call": true, @@ -280468,58 +280647,34 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "mixed", - "budget": { - "min": 1024, - "unit": "tokens" - }, - "effort": "high", "effort_options": [ + "none", "low", - "medium", - "high", - "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Anthropic recommends adaptive thinking with effort for Claude 4.6; budget_tokens remains a deprecated compatibility path.", - "Anthropic API defaults effort to high; lower effort levels should be chosen per workload." + "high" ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-02-17", - "last_updated": "2026-03-13", + "attachment": false, + "open_weights": true, + "release_date": "2026-08-25", + "last_updated": "2026-08-25", "type": "chat" }, { - "id": "gemini-3.1-pro-preview", - "name": "Gemini 3.1 Pro Preview", - "display_name": "Gemini 3.1 Pro Preview", + "id": "deepinfra/deepseek-ai/DeepSeek-R1-0528", + "name": "DeepSeek R1 0528 (Deep Infra)", + "display_name": "DeepSeek R1 0528 (Deep Infra)", "modalities": { "input": [ - "text", - "image", - "video", - "audio", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 163840, + "output": 32768 }, "temperature": true, "tool_call": true, @@ -280530,96 +280685,65 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "level", - "level": "high", - "level_options": [ - "low", - "high" - ], + "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ - "thought_signatures" + "thinking_blocks" ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-01", - "release_date": "2026-02-19", - "last_updated": "2026-02-19", + "attachment": false, + "open_weights": true, + "release_date": "2025-05-28", + "last_updated": "2025-05-28", "type": "chat" }, { - "id": "gemini-2.5-flash-nothink", - "name": "gemini-2.5-flash-nothink", - "display_name": "gemini-2.5-flash-nothink", + "id": "deepinfra/deepseek-ai/DeepSeek-V3", + "name": "DeepSeek-V3 (Deep Infra)", + "display_name": "DeepSeek-V3 (Deep Infra)", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 65536 + "context": 163840, + "output": 8192 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "budget", - "budget": { - "default": -1, - "min": 0, - "max": 24576, - "auto": -1, - "off": 0, - "unit": "tokens" - }, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thought_signatures" - ] - } + "supported": false }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-01", - "release_date": "2025-06-17", - "last_updated": "2025-06-17", + "attachment": false, + "open_weights": true, + "release_date": "2024-12-26", + "last_updated": "2024-12-26", "type": "chat" }, { - "id": "gpt-5.6-terra-pro", - "name": "gpt-5.6-terra-pro", - "display_name": "gpt-5.6-terra-pro", + "id": "deepinfra/deepseek-ai/DeepSeek-V4.1-Flash-1017", + "name": "DeepSeek V4.1 Flash 1017 (Deep Infra)", + "display_name": "DeepSeek V4.1 Flash 1017 (Deep Infra)", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 1048576, + "output": 384000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -280630,39 +280754,36 @@ "effort_options": [ "none", "low", - "medium", "high", - "xhigh", "max" ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2026-02-16", - "release_date": "2026-07-09", - "last_updated": "2026-07-09", + "open_weights": true, + "knowledge": "2025-05", + "release_date": "2026-10-17", + "last_updated": "2026-10-17", "type": "chat" }, { - "id": "gpt-5.6-sol-pro", - "name": "gpt-5.6-sol-pro", - "display_name": "gpt-5.6-sol-pro", + "id": "deepinfra/deepseek-ai/DeepSeek-V4.1-Flash", + "name": "DeepSeek V4.1 Flash (Deep Infra)", + "display_name": "DeepSeek V4.1 Flash (Deep Infra)", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 1048576, + "output": 384000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -280670,57 +280791,53 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, "effort_options": [ "none", "low", - "medium", "high", - "xhigh", "max" ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2026-02-16", - "release_date": "2026-07-09", - "last_updated": "2026-07-09", + "open_weights": true, + "knowledge": "2025-05", + "release_date": "2026-09-10", + "last_updated": "2026-09-10", "type": "chat" }, { - "id": "gpt-4.1-mini", - "name": "gpt-4.1-mini", - "display_name": "gpt-4.1-mini", + "id": "deepinfra/deepseek-ai/DeepSeek-V3-0324", + "name": "DeepSeek V3 0324 (Deep Infra)", + "display_name": "DeepSeek V3 0324 (Deep Infra)", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1047576, - "output": 32768 + "context": 163840, + "output": 163840 }, "temperature": true, "tool_call": true, "reasoning": { "supported": false }, - "attachment": true, - "open_weights": false, - "knowledge": "2024-04", - "release_date": "2025-04-14", - "last_updated": "2025-04-14", + "attachment": false, + "open_weights": true, + "release_date": "2025-03-24", + "last_updated": "2025-03-24", "type": "chat" }, { - "id": "MiniMax-M2.1", - "name": "MiniMax-M2.1", - "display_name": "MiniMax-M2.1", + "id": "deepinfra/deepseek-ai/DeepSeek-R1", + "name": "DeepSeek-R1 (Deep Infra)", + "display_name": "DeepSeek-R1 (Deep Infra)", "modalities": { "input": [ "text" @@ -280730,8 +280847,8 @@ ] }, "limit": { - "context": 204800, - "output": 131072 + "context": 163840, + "output": 32768 }, "temperature": true, "tool_call": true, @@ -280741,135 +280858,118 @@ }, "extra_capabilities": { "reasoning": { - "supported": true + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] } }, "attachment": false, "open_weights": true, - "release_date": "2025-12-23", - "last_updated": "2025-12-23", + "knowledge": "2024-07", + "release_date": "2025-01-20", + "last_updated": "2025-05-29", "type": "chat" }, { - "id": "claude-haiku-4-5", - "name": "claude-haiku-4-5", - "display_name": "claude-haiku-4-5", + "id": "deepinfra/deepseek-ai/DeepSeek-V4-Flash-0731", + "name": "DeepSeek V4 Flash 0731 (Deep Infra)", + "display_name": "DeepSeek V4 Flash 0731 (Deep Infra)", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 64000 + "context": 1048576, + "output": 384000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "budget", - "budget": { - "min": 1024, - "unit": "tokens" - }, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", - "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." + "effort_options": [ + "none", + "low", + "high", + "max" ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-02-28", - "release_date": "2025-10-15", - "last_updated": "2025-10-15", + "attachment": false, + "open_weights": true, + "knowledge": "2025-05", + "release_date": "2026-07-31", + "last_updated": "2026-07-31", "type": "chat" }, { - "id": "claude-haiku-4-5-20251001", - "name": "claude-haiku-4-5-20251001", - "display_name": "claude-haiku-4-5-20251001", + "id": "deepinfra/deepseek-ai/DeepSeek-V4-Pro-0813", + "name": "DeepSeek V4 Pro 0813 (Deep Infra)", + "display_name": "DeepSeek V4 Pro 0813 (Deep Infra)", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 64000 + "context": 1048576, + "output": 384000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "budget", - "budget": { - "min": 1024, - "unit": "tokens" - }, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", - "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." + "effort_options": [ + "none", + "low", + "high", + "max" ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-02-28", - "release_date": "2025-10-15", - "last_updated": "2025-10-15", + "attachment": false, + "open_weights": true, + "release_date": "2026-08-13", + "last_updated": "2026-08-22", "type": "chat" }, { - "id": "gpt-5-thinking", - "name": "gpt-5-thinking", - "display_name": "gpt-5-thinking", + "id": "xai/grok-build-0.1", + "name": "Grok Build 0.1", + "display_name": "Grok Build 0.1", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 256000, + "output": 256000 }, "temperature": true, "tool_call": true, @@ -280877,23 +280977,25 @@ "supported": true, "default": true }, + "extra_capabilities": { + "reasoning": { + "supported": true + } + }, "attachment": true, "open_weights": false, - "knowledge": "2024-10", - "release_date": "2025-08-07", - "last_updated": "2025-08-07", + "release_date": "2026-04-16", + "last_updated": "2026-04-16", "type": "chat" }, { - "id": "gemini-3.8-flash", - "name": "Gemini 3.8 Flash", - "display_name": "Gemini 3.8 Flash", + "id": "xai/grok-latest", + "name": "Grok Latest (Grok 4.7)", + "display_name": "Grok Latest (Grok 4.7)", "modalities": { "input": [ "text", "image", - "video", - "audio", "pdf" ], "output": [ @@ -280901,8 +281003,8 @@ ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 500000, + "output": 500000 }, "temperature": true, "tool_call": true, @@ -280912,44 +281014,38 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "level", - "level": "high", - "level_options": [ - "minimal", + "effort_options": [ "low", "medium", - "high" - ], - "summaries": true, - "visibility": "summary", - "continuation": [ - "thought_signatures" + "high", + "xhigh" ] } }, "attachment": true, "open_weights": false, - "release_date": "2026-09-02", - "last_updated": "2026-09-02", + "knowledge": "2026-05", + "release_date": "2026-09-21", + "last_updated": "2026-09-21", "type": "chat" }, { - "id": "glm-5", - "name": "glm-5", - "display_name": "glm-5", + "id": "xai/grok-4.7", + "name": "Grok 4.7", + "display_name": "Grok 4.7", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 204800, - "output": 131072 + "context": 500000, + "output": 500000 }, "temperature": true, "tool_call": true, @@ -280959,80 +281055,70 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "low", + "medium", + "high", + "xhigh" ] } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-02-12", - "last_updated": "2026-02-12", + "attachment": true, + "open_weights": false, + "knowledge": "2026-05", + "release_date": "2026-09-21", + "last_updated": "2026-09-21", "type": "chat" }, { - "id": "gpt-5.4-nano", - "name": "gpt-5.4-nano", - "display_name": "gpt-5.4-nano", + "id": "xai/grok-4.5", + "name": "Grok 4.5", + "display_name": "Grok 4.5", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 500000, + "output": 500000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "none", "effort_options": [ - "none", "low", "medium", "high", "xhigh" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-03-17", - "last_updated": "2026-03-17", + "release_date": "2026-07-08", + "last_updated": "2026-07-08", "type": "chat" }, { - "id": "glm-5.3", - "name": "GLM-5.3", - "display_name": "GLM-5.3", + "id": "xai/grok-4.20-0309-reasoning", + "name": "Grok 4.20 (Reasoning)", + "display_name": "Grok 4.20 (Reasoning)", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" @@ -281040,7 +281126,7 @@ }, "limit": { "context": 1000000, - "output": 131072 + "output": 30000 }, "temperature": true, "tool_call": true, @@ -281048,100 +281134,56 @@ "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "effort_options": [ - "low", - "high", - "max" - ] - } - }, - "attachment": false, - "open_weights": true, - "release_date": "2026-08-14", - "last_updated": "2026-08-14", + "attachment": true, + "open_weights": false, + "release_date": "2026-03-09", + "last_updated": "2026-03-09", "type": "chat" }, { - "id": "claude-opus-4-8", - "name": "Claude Opus 4.8", - "display_name": "Claude Opus 4.8", + "id": "xai/grok-4-fast-non-reasoning", + "name": "X-Ai/Grok-4-Fast-Non-Reasoning", + "display_name": "X-Ai/Grok-4-Fast-Non-Reasoning", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 2000000, + "output": 2000000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": false - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "high", - "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "omitted", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Claude Opus 4.7 and newer Opus models require thinking.type = \"adaptive\" to enable thinking explicitly.", - "Manual budget_tokens requests return 400 on Claude Opus 4.7 and newer adaptive-only Opus models.", - "task_budget is separate from thinking control and should not be treated as a thinking budget." - ] - } + "supported": false }, - "attachment": true, + "attachment": false, "open_weights": false, - "knowledge": "2026-01", - "release_date": "2026-05-28", - "last_updated": "2026-05-28", + "release_date": "2025-12-18", + "last_updated": "2025-12-18", "type": "chat" }, { - "id": "MiniMax-M2.7", - "name": "MiniMax-M2.7", - "display_name": "MiniMax-M2.7", + "id": "xai/grok-4.3", + "name": "Grok 4.3", + "display_name": "Grok 4.3", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 204800, - "output": 131072 + "context": 1000000, + "output": 30000 }, "temperature": true, "tool_call": true, @@ -281152,27 +281194,29 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "none", + "low", + "medium", + "high", + "xhigh" ] } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-03-18", - "last_updated": "2026-03-18", + "attachment": true, + "open_weights": false, + "release_date": "2026-04-17", + "last_updated": "2026-04-17", "type": "chat" }, { - "id": "MiniMax-M1", - "name": "MiniMax-M1", - "display_name": "MiniMax-M1", + "id": "xai/grok-4.20-0309-non-reasoning", + "name": "Grok 4.20 (Non-Reasoning)", + "display_name": "Grok 4.20 (Non-Reasoning)", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" @@ -281180,23 +281224,23 @@ }, "limit": { "context": 1000000, - "output": 128000 + "output": 30000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": false }, - "attachment": false, + "attachment": true, "open_weights": false, - "release_date": "2025-06-16", - "last_updated": "2025-06-16", + "release_date": "2026-03-09", + "last_updated": "2026-03-09", "type": "chat" }, { - "id": "claude-sonnet-4-5-20250929-thinking", - "name": "claude-sonnet-4-5-20250929-thinking", - "display_name": "claude-sonnet-4-5-20250929-thinking", + "id": "xai/grok-4.6", + "name": "Grok 4.6", + "display_name": "Grok 4.6", "modalities": { "input": [ "text", @@ -281208,8 +281252,8 @@ ] }, "limit": { - "context": 200000, - "output": 64000 + "context": 500000, + "output": 500000 }, "temperature": true, "tool_call": true, @@ -281220,215 +281264,162 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "budget", - "budget": { - "min": 1024, - "unit": "tokens" - }, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", - "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." + "effort_options": [ + "low", + "medium", + "high", + "xhigh" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-03", - "release_date": "2025-09-29", - "last_updated": "2025-09-29", + "knowledge": "2026-02-01", + "release_date": "2026-08-12", + "last_updated": "2026-08-12", "type": "chat" }, { - "id": "gemini-4-argon", - "name": "Gemini 4 Argon", - "display_name": "Gemini 4 Argon", + "id": "cohere/command-r-08-2024", + "name": "Command R", + "display_name": "Command R", "modalities": { "input": [ - "text", - "image", - "video", - "audio", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 128000, + "output": 4000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "low", - "medium", - "high" - ] - } + "supported": false }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-03", - "release_date": "2026-10-13", - "last_updated": "2026-10-13", + "attachment": false, + "open_weights": true, + "knowledge": "2024-06-01", + "release_date": "2024-08-30", + "last_updated": "2024-08-30", "type": "chat" }, { - "id": "gemini-3.6-flash", - "name": "Gemini 3.6 Flash", - "display_name": "Gemini 3.6 Flash", + "id": "cohere/command-r-plus-08-2024", + "name": "Command R+", + "display_name": "Command R+", "modalities": { "input": [ - "text", - "image", - "video", - "audio", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 128000, + "output": 4000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "level", - "level": "high", - "level_options": [ - "minimal", - "low", - "medium", - "high" - ], - "summaries": true, - "visibility": "summary", - "continuation": [ - "thought_signatures" - ] - } + "supported": false }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-03", - "release_date": "2026-07-21", - "last_updated": "2026-07-21", + "attachment": false, + "open_weights": true, + "knowledge": "2024-06-01", + "release_date": "2024-08-30", + "last_updated": "2024-08-30", "type": "chat" }, { - "id": "gpt-4.1", - "name": "gpt-4.1", - "display_name": "gpt-4.1", + "id": "cohere/command-r7b-12-2024", + "name": "Command R7B", + "display_name": "Command R7B", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1047576, - "output": 32768 + "context": 132000, + "output": 4000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": false }, - "attachment": true, - "open_weights": false, - "knowledge": "2024-04", - "release_date": "2025-04-14", - "last_updated": "2025-04-14", + "attachment": false, + "open_weights": true, + "knowledge": "2024-06-01", + "release_date": "2024-12-13", + "last_updated": "2024-12-13", "type": "chat" }, { - "id": "grok-4-fast-non-reasoning", - "name": "grok-4-fast-non-reasoning", - "display_name": "grok-4-fast-non-reasoning", + "id": "cohere/c4ai-aya-expanse-32b", + "name": "Aya Expanse 32B", + "display_name": "Aya Expanse 32B", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 2000000, - "output": 30000 + "context": 128000, + "output": 4000 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { "supported": false }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-06", - "release_date": "2025-12-18", - "last_updated": "2025-12-18", + "attachment": false, + "open_weights": true, + "release_date": "2024-10-24", + "last_updated": "2024-10-24", "type": "chat" }, { - "id": "gpt-5.3-chat-latest", - "name": "GPT-5.3 Chat (latest)", - "display_name": "GPT-5.3 Chat (latest)", + "id": "cohere/command-a-03-2025", + "name": "Command A", + "display_name": "Command A", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 16384 + "context": 288000, + "output": 8000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": false }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-03-03", - "last_updated": "2026-03-03", + "attachment": false, + "open_weights": true, + "knowledge": "2024-06-01", + "release_date": "2025-03-13", + "last_updated": "2025-03-13", "type": "chat" }, { - "id": "MiniMax-M2.5", + "id": "minimax/MiniMax-M2.5", "name": "MiniMax-M2.5", "display_name": "MiniMax-M2.5", "modalities": { @@ -281461,16 +281452,14 @@ "type": "chat" }, { - "id": "gemini-3.5-flash", - "name": "Gemini 3.5 Flash", - "display_name": "Gemini 3.5 Flash", + "id": "minimax/MiniMax-M3.1-Flash", + "name": "MiniMax-M3.1-Flash", + "display_name": "MiniMax-M3.1-Flash", "modalities": { "input": [ "text", "image", - "video", - "audio", - "pdf" + "video" ], "output": [ "text" @@ -281478,7 +281467,7 @@ }, "limit": { "context": 1048576, - "output": 65536 + "output": 512000 }, "temperature": true, "tool_call": true, @@ -281488,46 +281477,97 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "level", - "level": "high", - "level_options": [ - "minimal", + "effort_options": [ "low", "medium", - "high" - ], - "summaries": true, - "visibility": "summary", - "continuation": [ - "thought_signatures" + "high", + "xhigh", + "max" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-01", - "release_date": "2026-05-19", - "last_updated": "2026-05-19", + "release_date": "2026-10-19", + "last_updated": "2026-10-19", "type": "chat" }, { - "id": "deepseek-flash", - "name": "DeepSeek V4.1 Flash", - "display_name": "DeepSeek V4.1 Flash", + "id": "minimax/MiniMax-M2.1", + "name": "MiniMax-M2.1", + "display_name": "MiniMax-M2.1", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 384000 + "context": 204800, + "output": 131072 + }, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true + } + }, + "attachment": false, + "open_weights": true, + "release_date": "2025-12-23", + "last_updated": "2025-12-23", + "type": "chat" + }, + { + "id": "minimax/MiniMax-M2", + "name": "MiniMax-M2", + "display_name": "MiniMax-M2", + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 204800, + "output": 131072 + }, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": true, + "default": true + }, + "attachment": false, + "open_weights": true, + "release_date": "2025-10-27", + "last_updated": "2025-10-27", + "type": "chat" + }, + { + "id": "minimax/MiniMax-M2.7", + "name": "MiniMax-M2.7", + "display_name": "MiniMax-M2.7", + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 204800, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -281543,38 +281583,66 @@ "visibility": "summary", "continuation": [ "thinking_blocks" - ], - "effort_options": [ - "low", - "high", - "max" ] } }, + "attachment": false, + "open_weights": true, + "release_date": "2026-03-18", + "last_updated": "2026-03-18", + "type": "chat" + }, + { + "id": "minimax/MiniMax-M3", + "name": "MiniMax-M3", + "display_name": "MiniMax-M3", + "modalities": { + "input": [ + "text", + "image", + "video" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 524288, + "output": 512000 + }, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true + } + }, "attachment": true, "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-09-10", - "last_updated": "2026-09-10", + "release_date": "2026-06-01", + "last_updated": "2026-06-01", "type": "chat" }, { - "id": "claude-opus-5-thinking", - "name": "claude-opus-5-thinking", - "display_name": "claude-opus-5-thinking", + "id": "tensorx/moonshotai/kimi-k2.5", + "name": "Kimi K2.5 (TensorX)", + "display_name": "Kimi K2.5 (TensorX)", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 262144, + "output": 262144 }, "temperature": false, "tool_call": true, @@ -281585,93 +281653,78 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2026-05", - "release_date": "2026-07-24", - "last_updated": "2026-07-24", + "open_weights": true, + "knowledge": "2025-01", + "release_date": "2026-01-27", + "last_updated": "2026-01-27", "type": "chat" }, { - "id": "claude-opus-4-5-20251101", - "name": "claude-opus-4-5-20251101", - "display_name": "claude-opus-4-5-20251101", + "id": "tensorx/deepseek/deepseek-r1-0528", + "name": "DeepSeek R1 0528 (TensorX)", + "display_name": "DeepSeek R1 0528 (TensorX)", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 64000 + "context": 164000, + "output": 32768 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "mixed", - "budget": { - "min": 1024, - "unit": "tokens" - }, - "effort": "high", - "effort_options": [ - "low", - "medium", - "high" - ], "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ "thinking_blocks" - ], - "notes": [ - "Claude Opus 4.5 uses manual thinking.type = \"enabled\" with budget_tokens; effort can be used alongside the thinking budget.", - "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header." ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-03-31", - "release_date": "2025-11-24", - "last_updated": "2025-11-24", + "attachment": false, + "open_weights": true, + "release_date": "2025-05-28", + "last_updated": "2025-05-28", "type": "chat" }, { - "id": "claude-fable-5", - "name": "Claude Fable 5", - "display_name": "Claude Fable 5", + "id": "tensorx/deepseek/deepseek-v4.1-flash-1017", + "name": "DeepSeek V4.1 Flash 1017 (TensorX)", + "display_name": "DeepSeek V4.1 Flash 1017 (TensorX)", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 1048576, + "output": 384000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -281679,56 +281732,39 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "high", "effort_options": [ + "none", "low", - "medium", "high", - "xhigh", "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "omitted", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Adaptive thinking is always on for Claude Fable 5 and Claude Mythos 5; thinking.type = \"disabled\" is rejected.", - "Manual budget_tokens requests return 400 on Claude Fable 5 and Claude Mythos 5.", - "thinking.display defaults to omitted; set display to summarized to receive readable thinking summaries." ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2026-01-31", - "release_date": "2026-06-09", - "last_updated": "2026-06-09", + "open_weights": true, + "knowledge": "2025-05", + "release_date": "2026-10-17", + "last_updated": "2026-10-17", "type": "chat" }, { - "id": "gpt-5.6-luna-pro", - "name": "gpt-5.6-luna-pro", - "display_name": "gpt-5.6-luna-pro", + "id": "tensorx/deepseek/deepseek-v4.1-flash", + "name": "DeepSeek V4.1 Flash (TensorX)", + "display_name": "DeepSeek V4.1 Flash (TensorX)", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 1048576, + "output": 384000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -281736,27 +281772,26 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, "effort_options": [ "none", "low", - "medium", "high", - "xhigh", "max" ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2026-02-16", - "release_date": "2026-07-09", - "last_updated": "2026-07-09", + "open_weights": true, + "knowledge": "2025-05", + "release_date": "2026-09-10", + "last_updated": "2026-09-10", "type": "chat" }, { - "id": "kimi-k2-0905-preview", - "name": "kimi-k2-0905-preview", - "display_name": "kimi-k2-0905-preview", + "id": "tensorx/deepseek/deepseek-v4-pro-0813", + "name": "DeepSeek V4 Pro 0813 (TensorX)", + "display_name": "DeepSeek V4 Pro 0813 (TensorX)", "modalities": { "input": [ "text" @@ -281766,40 +281801,49 @@ ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 1048576, + "output": 384000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "effort_options": [ + "none", + "low", + "high", + "max" + ] + } }, "attachment": false, "open_weights": true, - "knowledge": "2025-06", - "release_date": "2025-09-05", - "last_updated": "2025-09-05", + "release_date": "2026-08-13", + "last_updated": "2026-08-22", "type": "chat" }, { - "id": "claude-fable-5-1", - "name": "Claude Fable 5.1", - "display_name": "Claude Fable 5.1", + "id": "tensorx/deepseek/deepseek-v4-flash-0731", + "name": "DeepSeek V4 Flash 0731 (TensorX)", + "display_name": "DeepSeek V4 Flash 0731 (TensorX)", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 1048576, + "output": 384000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -281808,53 +281852,36 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "high", "effort_options": [ + "none", "low", - "medium", "high", - "xhigh", "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "omitted", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Adaptive thinking is always on for Claude Fable 5 and Claude Mythos 5; thinking.type = \"disabled\" is rejected.", - "Manual budget_tokens requests return 400 on Claude Fable 5 and Claude Mythos 5.", - "thinking.display defaults to omitted; set display to summarized to receive readable thinking summaries." ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-06", - "release_date": "2026-09-01", - "last_updated": "2026-09-01", + "attachment": false, + "open_weights": true, + "knowledge": "2025-05", + "release_date": "2026-07-31", + "last_updated": "2026-07-31", "type": "chat" }, { - "id": "grok-4.3", - "name": "Grok 4.3", - "display_name": "Grok 4.3", + "id": "ionos/openai/gpt-oss-120b", + "name": "GPT OSS 120B (IONOS)", + "display_name": "GPT OSS 120B (IONOS)", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 30000 + "context": 128000, + "output": 32768 }, "temperature": true, "tool_call": true, @@ -281866,23 +281893,23 @@ "reasoning": { "supported": true, "effort_options": [ - "none", "low", "medium", "high" ] } }, - "attachment": true, - "open_weights": false, - "release_date": "2026-04-17", - "last_updated": "2026-04-17", + "attachment": false, + "open_weights": true, + "knowledge": "2024-06", + "release_date": "2025-08-05", + "last_updated": "2025-08-05", "type": "chat" }, { - "id": "glm-4.5", - "name": "GLM-4.5", - "display_name": "GLM-4.5", + "id": "ionos/Qwen/Qwen3-Coder-Next", + "name": "Qwen3 Coder Next (IONOS)", + "display_name": "Qwen3 Coder Next (IONOS)", "modalities": { "input": [ "text" @@ -281892,133 +281919,92 @@ ] }, "limit": { - "context": 131072, - "output": 98304 + "context": 128000, + "output": 65536 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true + "supported": false }, "attachment": false, "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-07-28", - "last_updated": "2025-07-28", + "knowledge": "2025-09", + "release_date": "2026-02-03", + "last_updated": "2026-02-03", "type": "chat" }, { - "id": "gpt-5.2", - "name": "gpt-5.2", - "display_name": "gpt-5.2", + "id": "ionos/meta-llama/Llama-3.3-70B-Instruct", + "name": "Llama-3.3-70B-Instruct (IONOS)", + "display_name": "Llama-3.3-70B-Instruct (IONOS)", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 128000, + "output": 4096 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": false - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "none", - "effort_options": [ - "none", - "low", - "medium", - "high", - "xhigh" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" - } + "supported": false }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2025-12-11", - "last_updated": "2025-12-11", + "attachment": false, + "open_weights": true, + "knowledge": "2023-12", + "release_date": "2024-12-06", + "last_updated": "2024-12-06", "type": "chat" }, { - "id": "gpt-6.1-sol", - "name": "GPT-6.1 Sol", - "display_name": "GPT-6.1 Sol", + "id": "arkanecloud/ministral-14b", + "name": "Ministral 14B (Arkanecloud)", + "display_name": "Ministral 14B (Arkanecloud)", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 32768, + "output": 262144 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" - ] - } + "supported": false }, "attachment": true, - "open_weights": false, - "knowledge": "2026-04-30", - "release_date": "2026-09-29", - "last_updated": "2026-09-29", + "open_weights": true, + "release_date": "2025-12-02", + "last_updated": "2025-12-02", "type": "chat" }, { - "id": "qwen3.7-plus", - "name": "Qwen3.7 Plus", - "display_name": "Qwen3.7 Plus", + "id": "arkanecloud/openai/gpt-oss-120b", + "name": "GPT OSS 120B (Arkanecloud)", + "display_name": "GPT OSS 120B (Arkanecloud)", "modalities": { "input": [ - "text", - "image", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 64000 + "context": 131072, + "output": 32768 }, "temperature": true, "tool_call": true, @@ -282028,20 +282014,25 @@ }, "extra_capabilities": { "reasoning": { - "supported": true + "supported": true, + "effort_options": [ + "low", + "medium", + "high" + ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-04", - "release_date": "2026-06-02", - "last_updated": "2026-06-02", + "attachment": false, + "open_weights": true, + "knowledge": "2024-06", + "release_date": "2025-08-05", + "last_updated": "2025-08-05", "type": "chat" }, { - "id": "deepseek-v3.2-thinking", - "name": "DeepSeek-V3.2-Thinking", - "display_name": "DeepSeek-V3.2-Thinking", + "id": "arkanecloud/openai/gpt-oss-20b", + "name": "GPT OSS 20B (Arkanecloud)", + "display_name": "GPT OSS 20B (Arkanecloud)", "modalities": { "input": [ "text" @@ -282051,8 +282042,8 @@ ] }, "limit": { - "context": 128000, - "output": 128000 + "context": 131072, + "output": 32768 }, "temperature": true, "tool_call": true, @@ -282060,17 +282051,27 @@ "supported": true, "default": true }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "effort_options": [ + "low", + "medium", + "high" + ] + } + }, "attachment": false, "open_weights": true, - "knowledge": "2024-12", - "release_date": "2025-12-01", - "last_updated": "2025-12-01", + "knowledge": "2024-06", + "release_date": "2025-08-05", + "last_updated": "2025-08-05", "type": "chat" }, { - "id": "glm-5.2", - "name": "GLM-5.2", - "display_name": "GLM-5.2", + "id": "arkanecloud/Qwen/Qwen3-235B-A22B-Instruct-2507", + "name": "Qwen3 235B-A22B Instruct 2507 (Arkanecloud)", + "display_name": "Qwen3 235B-A22B Instruct 2507 (Arkanecloud)", "modalities": { "input": [ "text" @@ -282080,52 +282081,35 @@ ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 262000, + "output": 16384 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "effort_options": [ - "high", - "max" - ] - } + "supported": false }, "attachment": false, "open_weights": true, - "release_date": "2026-06-13", - "last_updated": "2026-06-13", + "release_date": "2025-07-21", + "last_updated": "2025-07-21", "type": "chat" }, { - "id": "grok-4.20-beta-0309-reasoning", - "name": "grok-4.20-beta-0309-reasoning", - "display_name": "grok-4.20-beta-0309-reasoning", + "id": "arkanecloud/nvidia/nemotron-3-ultra-550b-a55b", + "name": "Nemotron 3 Ultra 550B A55B (Arkanecloud)", + "display_name": "Nemotron 3 Ultra 550B A55B (Arkanecloud)", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 2000000, - "output": 30000 + "context": 512288, + "output": 128000 }, "temperature": true, "tool_call": true, @@ -282135,31 +282119,36 @@ }, "extra_capabilities": { "reasoning": { - "supported": true + "supported": true, + "effort_options": [ + "none", + "medium", + "high" + ] } }, - "attachment": true, - "open_weights": false, - "release_date": "2026-03-09", - "last_updated": "2026-03-09", + "attachment": false, + "open_weights": true, + "knowledge": "2025-09", + "release_date": "2026-06-04", + "last_updated": "2026-06-04", "type": "chat" }, { - "id": "grok-4.6", - "name": "Grok 4.6", - "display_name": "Grok 4.6", + "id": "arkanecloud/nvidia/nemotron-3-super-120b-a12b", + "name": "Nemotron 3 Super 120B A12B (Arkanecloud)", + "display_name": "Nemotron 3 Super 120B A12B (Arkanecloud)", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 500000, - "output": 500000 + "context": 256000, + "output": 262144 }, "temperature": true, "tool_call": true, @@ -282169,35 +282158,24 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, "effort_options": [ + "none", "low", - "medium", - "high", - "xhigh" + "medium" ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-02-01", - "release_date": "2026-08-12", - "last_updated": "2026-08-12", + "attachment": false, + "open_weights": true, + "knowledge": "2025-06", + "release_date": "2026-03-11", + "last_updated": "2026-03-11", "type": "chat" - } - ] - }, - "drun": { - "id": "drun", - "name": "D.Run (China)", - "display_name": "D.Run (China)", - "api": "https://chat.d.run/v1", - "doc": "https://www.d.run", - "models": [ + }, { - "id": "public/minimax-m25", - "name": "MiniMax M2.5", - "display_name": "MiniMax M2.5", + "id": "arkanecloud/meta-llama/Llama-3.1-8B-Instruct", + "name": "Llama-3.1-8B-Instruct (Arkanecloud)", + "display_name": "Llama-3.1-8B-Instruct (Arkanecloud)", "modalities": { "input": [ "text" @@ -282207,36 +282185,25 @@ ] }, "limit": { - "context": 204800, - "output": 131072 + "context": 131072, + "output": 4096 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } + "supported": false }, "attachment": false, "open_weights": true, - "release_date": "2026-02-12", - "last_updated": "2026-02-12", + "knowledge": "2023-12", + "release_date": "2024-07-23", + "last_updated": "2024-07-23", "type": "chat" }, { - "id": "public/deepseek-v3", - "name": "DeepSeek V3", - "display_name": "DeepSeek V3", + "id": "arkanecloud/meta-llama/Llama-3.3-70B-Instruct", + "name": "Llama-3.3-70B-Instruct (Arkanecloud)", + "display_name": "Llama-3.3-70B-Instruct (Arkanecloud)", "modalities": { "input": [ "text" @@ -282246,8 +282213,8 @@ ] }, "limit": { - "context": 131072, - "output": 8192 + "context": 128000, + "output": 4096 }, "temperature": true, "tool_call": true, @@ -282256,15 +282223,15 @@ }, "attachment": false, "open_weights": true, - "knowledge": "2024-07", - "release_date": "2024-12-26", - "last_updated": "2024-12-26", + "knowledge": "2023-12", + "release_date": "2024-12-06", + "last_updated": "2024-12-06", "type": "chat" }, { - "id": "public/deepseek-r1", - "name": "DeepSeek R1", - "display_name": "DeepSeek R1", + "id": "arkanecloud/meta-llama/Llama-3.1-70B-Instruct", + "name": "Llama-3.1-70B-Instruct (Arkanecloud)", + "display_name": "Llama-3.1-70B-Instruct (Arkanecloud)", "modalities": { "input": [ "text" @@ -282275,45 +282242,24 @@ }, "limit": { "context": 131072, - "output": 32000 + "output": 4096 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } + "supported": false }, "attachment": false, "open_weights": true, - "knowledge": "2024-12", - "release_date": "2025-01-20", - "last_updated": "2025-05-29", + "knowledge": "2023-12", + "release_date": "2024-07-23", + "last_updated": "2024-07-23", "type": "chat" - } - ] - }, - "hpc-ai": { - "id": "hpc-ai", - "name": "HPC-AI", - "display_name": "HPC-AI", - "api": "https://api.hpc-ai.com/inference/v1", - "doc": "https://www.hpc-ai.com/doc/docs/quickstart/", - "models": [ + }, { - "id": "zai-org/glm-5.1", - "name": "GLM 5.1", - "display_name": "GLM 5.1", + "id": "arkanecloud/deepseek-ai/DeepSeek-V4.1-Flash-1017", + "name": "DeepSeek V4.1 Flash 1017 (Arkanecloud)", + "display_name": "DeepSeek V4.1 Flash 1017 (Arkanecloud)", "modalities": { "input": [ "text" @@ -282323,8 +282269,8 @@ ] }, "limit": { - "context": 202000, - "output": 202000 + "context": 1048576, + "output": 384000 }, "temperature": true, "tool_call": true, @@ -282334,28 +282280,29 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "none", + "low", + "high", + "max" ] } }, "attachment": false, "open_weights": true, - "release_date": "2026-04-07", - "last_updated": "2026-04-07", + "knowledge": "2025-05", + "release_date": "2026-10-17", + "last_updated": "2026-10-17", "type": "chat" }, { - "id": "zai-org/glm-5.2", - "name": "GLM-5.2", - "display_name": "GLM-5.2", + "id": "arkanecloud/deepseek-ai/DeepSeek-V4.1-Flash", + "name": "DeepSeek V4.1 Flash (Arkanecloud)", + "display_name": "DeepSeek V4.1 Flash (Arkanecloud)", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" @@ -282363,7 +282310,7 @@ }, "limit": { "context": 1048576, - "output": 131072 + "output": 384000 }, "temperature": true, "tool_call": true, @@ -282374,28 +282321,25 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], "effort_options": [ + "none", + "low", "high", "max" ] } }, - "attachment": false, + "attachment": true, "open_weights": true, - "release_date": "2026-06-13", - "last_updated": "2026-06-13", + "knowledge": "2025-05", + "release_date": "2026-09-10", + "last_updated": "2026-09-10", "type": "chat" }, { - "id": "minimax/minimax-m2.5", - "name": "MiniMax-M2.5", - "display_name": "MiniMax-M2.5", + "id": "arkanecloud/deepseek-ai/DeepSeek-V4-Flash-0731", + "name": "DeepSeek V4 Flash 0731 (Arkanecloud)", + "display_name": "DeepSeek V4 Flash 0731 (Arkanecloud)", "modalities": { "input": [ "text" @@ -282405,8 +282349,8 @@ ] }, "limit": { - "context": 196000, - "output": 195000 + "context": 1048576, + "output": 384000 }, "temperature": true, "tool_call": true, @@ -282416,23 +282360,32 @@ }, "extra_capabilities": { "reasoning": { - "supported": true + "supported": true, + "effort_options": [ + "none", + "low", + "high", + "max" + ] } }, "attachment": false, "open_weights": true, - "release_date": "2026-02-12", - "last_updated": "2026-02-12", + "knowledge": "2025-05", + "release_date": "2026-07-31", + "last_updated": "2026-07-31", "type": "chat" }, { - "id": "anthropic/claude-opus-4.7", - "name": "Claude Opus 4.7", - "display_name": "Claude Opus 4.7", + "id": "tencent/step-5-preview", + "name": "Step 5 Preview (Tencent)", + "display_name": "Step 5 Preview (Tencent)", "modalities": { "input": [ "text", - "image" + "image", + "video", + "audio" ], "output": [ "text" @@ -282440,62 +282393,45 @@ }, "limit": { "context": 1000000, - "output": 128000 + "output": 1000000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "high", "effort_options": [ "low", "medium", - "high", - "xhigh", - "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "omitted", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Claude Opus 4.7 and newer Opus models require thinking.type = \"adaptive\" to enable thinking explicitly.", - "Manual budget_tokens requests return 400 on Claude Opus 4.7 and newer adaptive-only Opus models.", - "task_budget is separate from thinking control and should not be treated as a thinking budget." + "high" ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2026-01-31", - "release_date": "2026-04-16", - "last_updated": "2026-04-16", + "open_weights": true, + "release_date": "2026-09-16", + "last_updated": "2026-09-20", "type": "chat" }, { - "id": "deepseek/deepseek-v4-flash", - "name": "DeepSeek V4 Flash", - "display_name": "DeepSeek V4 Flash", + "id": "tencent/deepseek-v4.1-flash-1017", + "name": "DeepSeek V4.1 Flash 1017 (Tencent)", + "display_name": "DeepSeek V4.1 Flash 1017 (Tencent)", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 128000 + "context": 1000000, + "output": 384000 }, "temperature": true, "tool_call": true, @@ -282505,37 +282441,25 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "high", "effort_options": [ + "none", "low", "high", "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." ] } }, - "attachment": false, + "attachment": true, "open_weights": true, "knowledge": "2025-05", - "release_date": "2026-04-24", - "last_updated": "2026-04-24", + "release_date": "2026-10-17", + "last_updated": "2026-10-17", "type": "chat" }, { - "id": "deepseek/deepseek-v4-pro", - "name": "DeepSeek V4 Pro", - "display_name": "DeepSeek V4 Pro", + "id": "tencent/hy3", + "name": "Hy3 (Tencent)", + "display_name": "Hy3 (Tencent)", "modalities": { "input": [ "text" @@ -282545,7 +282469,7 @@ ] }, "limit": { - "context": 1002000, + "context": 256000, "output": 128000 }, "temperature": true, @@ -282557,36 +282481,23 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "high", "effort_options": [ + "none", "low", - "high", - "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." + "high" ] } }, "attachment": false, "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-04-24", - "last_updated": "2026-04-24", + "release_date": "2026-07-06", + "last_updated": "2026-07-06", "type": "chat" }, { - "id": "openai/gpt-5.5", - "name": "GPT-5.5", - "display_name": "GPT-5.5", + "id": "tencent/deepseek-v4.1-flash", + "name": "DeepSeek V4.1 Flash (Tencent)", + "display_name": "DeepSeek V4.1 Flash (Tencent)", "modalities": { "input": [ "text", @@ -282597,8 +282508,8 @@ ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 1000000, + "output": 384000 }, "temperature": true, "tool_call": true, @@ -282609,47 +282520,36 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", "effort_options": [ + "none", "low", - "medium", "high", - "xhigh" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + "max" + ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2025-12-01", - "release_date": "2026-04-23", - "last_updated": "2026-04-23", + "open_weights": true, + "knowledge": "2025-05", + "release_date": "2026-09-10", + "last_updated": "2026-09-10", "type": "chat" }, { - "id": "moonshotai/kimi-k2.5", - "name": "Kimi K2.5", - "display_name": "Kimi K2.5", + "id": "tencent/hy4-preview", + "name": "Hy4 preview (Tencent)", + "display_name": "Hy4 preview (Tencent)", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 256000, - "output": 256000 + "context": 1000000, + "output": 64000 }, "temperature": true, "tool_call": true, @@ -282660,37 +282560,34 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "none", + "low", + "high" ] } }, - "attachment": true, + "attachment": false, "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-01-27", - "last_updated": "2026-01-27", + "release_date": "2026-08-28", + "last_updated": "2026-08-28", "type": "chat" }, { - "id": "moonshotai/kimi-k2.7-code", - "name": "Kimi K2.7 Code", - "display_name": "Kimi K2.7 Code", + "id": "tencent/deepseek-v4-pro-0813", + "name": "DeepSeek V4 Pro 0813 (Tencent)", + "display_name": "DeepSeek V4 Pro 0813 (Tencent)", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 256000, - "output": 256000 + "context": 1000000, + "output": 384000 }, "temperature": true, "tool_call": true, @@ -282700,29 +282597,25 @@ }, "extra_capabilities": { "reasoning": { - "supported": true + "supported": true, + "effort_options": [ + "none", + "low", + "high", + "max" + ] } }, - "attachment": true, + "attachment": false, "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-06-12", - "last_updated": "2026-06-12", + "release_date": "2026-08-13", + "last_updated": "2026-08-22", "type": "chat" - } - ] - }, - "llmtech": { - "id": "llmtech", - "name": "LLM Tech", - "display_name": "LLM Tech", - "api": "https://api.llmtech.eu/v1", - "doc": "https://llmtech.eu/models/qwen3.8-27b", - "models": [ + }, { - "id": "nvidia/Qwen3.8-27B-NVFP4", - "name": "Qwen3.8 27B", - "display_name": "Qwen3.8 27B", + "id": "perplexityai/sonar", + "name": "Sonar", + "display_name": "Sonar", "modalities": { "input": [ "text", @@ -282733,152 +282626,140 @@ ] }, "limit": { - "context": 262144, - "output": 32768 + "context": 127072, + "output": 4096 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "low", - "medium", - "xhigh" - ] - } + "supported": false }, "attachment": true, - "open_weights": true, - "release_date": "2026-08-14", - "last_updated": "2026-08-14", + "open_weights": false, + "knowledge": "2025-09-01", + "release_date": "2024-01-01", + "last_updated": "2025-09-01", "type": "chat" - } - ] - }, - "edenai": { - "id": "edenai", - "name": "Eden AI", - "display_name": "Eden AI", - "api": "https://api.edenai.run/v3", - "doc": "https://docs.edenai.co", - "models": [ + }, { - "id": "deepseek-v4-flash-vision-exp", - "name": "DeepSeek V4 Flash Vision Exp", - "display_name": "DeepSeek V4 Flash Vision Exp", + "id": "perplexityai/sonar-deep-research", + "name": "Sonar Deep Research", + "display_name": "Sonar Deep Research", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 384000 + "context": 128000, + "output": 32768 }, - "temperature": true, - "tool_call": true, + "temperature": false, + "tool_call": false, "reasoning": { "supported": true, "default": true }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "high", "effort_options": [ + "minimal", "low", - "high", - "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." + "medium", + "high" ] } }, - "attachment": true, - "open_weights": true, - "release_date": "2026-08-21", - "last_updated": "2026-09-01", + "attachment": false, + "open_weights": false, + "knowledge": "2025-01", + "release_date": "2025-02-01", + "last_updated": "2025-09-01", "type": "chat" }, { - "id": "nebius/Qwen/Qwen3-30B-A3B-Instruct-2507", - "name": "Qwen3 30B-A3B Instruct 2507 (Nebius)", - "display_name": "Qwen3 30B-A3B Instruct 2507 (Nebius)", + "id": "perplexityai/sonar-pro", + "name": "Sonar Pro", + "display_name": "Sonar Pro", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 16384 + "context": 200000, + "output": 8192 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { "supported": false }, - "attachment": false, - "open_weights": true, - "release_date": "2025-07-28", - "last_updated": "2025-07-28", + "attachment": true, + "open_weights": false, + "knowledge": "2025-09-01", + "release_date": "2024-01-01", + "last_updated": "2025-09-01", "type": "chat" }, { - "id": "nebius/Qwen/Qwen3-235B-A22B-Instruct-2507", - "name": "Qwen3 235B-A22B Instruct 2507 (Nebius)", - "display_name": "Qwen3 235B-A22B Instruct 2507 (Nebius)", + "id": "perplexityai/sonar-reasoning-pro", + "name": "Sonar Reasoning Pro", + "display_name": "Sonar Reasoning Pro", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 16384 + "context": 128000, + "output": 4096 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, - "open_weights": true, - "release_date": "2025-07-21", - "last_updated": "2025-07-21", + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "minimal", + "low", + "medium", + "high" + ] + } + }, + "attachment": true, + "open_weights": false, + "knowledge": "2025-09-01", + "release_date": "2024-01-01", + "last_updated": "2025-09-01", "type": "chat" }, { - "id": "nebius/meta-models/muse-spark-open", - "name": "Muse Spark Open (Nebius)", - "display_name": "Muse Spark Open (Nebius)", + "id": "google/gemini-3.5-flash", + "name": "Gemini 3.5 Flash", + "display_name": "Gemini 3.5 Flash", "modalities": { "input": [ "text", - "image" + "image", + "video", + "audio", + "pdf" ], "output": [ "text" @@ -282886,7 +282767,7 @@ }, "limit": { "context": 1048576, - "output": 131072 + "output": 65536 }, "temperature": true, "tool_call": true, @@ -282896,76 +282777,79 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ + "supported": true, + "default_enabled": true, + "mode": "level", + "level": "high", + "level_options": [ + "minimal", "low", "medium", - "high", - "xhigh" + "high" + ], + "summaries": true, + "visibility": "summary", + "continuation": [ + "thought_signatures" ] } }, "attachment": true, - "open_weights": true, - "knowledge": "2026-01-04", - "release_date": "2026-10-15", - "last_updated": "2026-10-15", + "open_weights": false, + "knowledge": "2025-01", + "release_date": "2026-05-19", + "last_updated": "2026-05-19", "type": "chat" }, { - "id": "nebius/nvidia/nemotron-3-super-120b-a12b", - "name": "Nemotron 3 Super 120B A12B (Nebius)", - "display_name": "Nemotron 3 Super 120B A12B (Nebius)", + "id": "google/deep-research-preview-04-2026", + "name": "Gemini Deep Research Preview", + "display_name": "Gemini Deep Research Preview", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 1048576, + "output": 65536 }, - "temperature": true, - "tool_call": true, + "temperature": false, + "tool_call": false, "reasoning": { "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "none", - "low", - "medium" - ] - } - }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-06", - "release_date": "2026-03-11", - "last_updated": "2026-03-11", + "attachment": true, + "open_weights": false, + "knowledge": "2025-01", + "release_date": "2026-04-21", + "last_updated": "2026-04-21", "type": "chat" }, { - "id": "nebius/nvidia/Nemotron-3-Ultra-550b-a55b", - "name": "Nemotron 3 Ultra 550B A55B (Nebius)", - "display_name": "Nemotron 3 Ultra 550B A55B (Nebius)", + "id": "google/gemini-nano-banana-2.1", + "name": "Nano Banana 2.1", + "display_name": "Nano Banana 2.1", "modalities": { "input": [ + "image", "text" ], "output": [ - "text" + "text", + "image" ] }, "limit": { - "context": 1048576, - "output": 128000 + "context": 65536, + "output": 32768 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -282973,67 +282857,68 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, "effort_options": [ - "none", + "minimal", "medium", "high" ] } }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-09", - "release_date": "2026-06-04", - "last_updated": "2026-06-04", - "type": "chat" + "attachment": true, + "open_weights": false, + "release_date": "2026-10-06", + "last_updated": "2026-10-06" }, { - "id": "nebius/google/gemma-3-27b-it", - "name": "Gemma 3 27B IT (Nebius)", - "display_name": "Gemma 3 27B IT (Nebius)", + "id": "google/deep-research-max-preview-04-2026", + "name": "Deep Research Max Preview", + "display_name": "Deep Research Max Preview", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 110000, - "output": 131072 + "context": 1048576, + "output": 65536 }, - "temperature": true, - "tool_call": true, + "temperature": false, + "tool_call": false, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, - "open_weights": true, - "knowledge": "2024-08", - "release_date": "2025-03-12", - "last_updated": "2025-03-12", + "attachment": true, + "open_weights": false, + "knowledge": "2025-01", + "release_date": "2026-04-21", + "last_updated": "2026-04-21", "type": "chat" }, { - "id": "nebius/openai/gpt-oss-120b", - "name": "GPT OSS 120B (Nebius)", - "display_name": "GPT OSS 120B (Nebius)", + "id": "google/gemini-3.1-flash-image", + "name": "Nano Banana 2", + "display_name": "Nano Banana 2", "modalities": { "input": [ + "image", "text" ], "output": [ - "text" + "text", + "image" ] }, "limit": { - "context": 131072, + "context": 65536, "output": 32768 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { "supported": true, "default": true @@ -283042,27 +282927,28 @@ "reasoning": { "supported": true, "effort_options": [ - "low", - "medium", + "minimal", "high" ] } }, - "attachment": false, - "open_weights": true, - "knowledge": "2024-06", - "release_date": "2025-08-05", - "last_updated": "2025-08-05", - "type": "chat" + "attachment": true, + "open_weights": false, + "knowledge": "2025-01", + "release_date": "2026-05-28", + "last_updated": "2026-05-28" }, { - "id": "nebius/deepseek-ai/DeepSeek-V4.1-Flash", - "name": "DeepSeek V4.1 Flash (Nebius)", - "display_name": "DeepSeek V4.1 Flash (Nebius)", + "id": "google/gemini-4-argon", + "name": "Gemini 4 Argon", + "display_name": "Gemini 4 Argon", "modalities": { "input": [ "text", - "image" + "image", + "video", + "audio", + "pdf" ], "output": [ "text" @@ -283070,9 +282956,9 @@ }, "limit": { "context": 1048576, - "output": 384000 + "output": 65536 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -283080,40 +282966,40 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, "effort_options": [ - "none", "low", - "high", - "max" + "medium", + "high" ] } }, "attachment": true, - "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-09-10", - "last_updated": "2026-09-10", + "open_weights": false, + "knowledge": "2026-03", + "release_date": "2026-10-13", + "last_updated": "2026-10-13", "type": "chat" }, { - "id": "nebius/deepseek-ai/DeepSeek-V4-Flash-0731", - "name": "DeepSeek V4 Flash 0731 (Nebius)", - "display_name": "DeepSeek V4 Flash 0731 (Nebius)", + "id": "google/gemini-3-pro-image", + "name": "Nano Banana Pro", + "display_name": "Nano Banana Pro", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ - "text" + "text", + "image" ] }, "limit": { - "context": 1048576, - "output": 384000 + "context": 65536, + "output": 32768 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { "supported": true, "default": true @@ -283121,28 +283007,37 @@ "extra_capabilities": { "reasoning": { "supported": true, - "effort_options": [ - "none", + "default_enabled": true, + "mode": "level", + "level": "high", + "level_options": [ "low", - "high", - "max" + "high" + ], + "summaries": true, + "visibility": "summary", + "continuation": [ + "thought_signatures" ] } }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-07-31", - "last_updated": "2026-07-31", - "type": "chat" + "attachment": true, + "open_weights": false, + "knowledge": "2025-01", + "release_date": "2026-05-28", + "last_updated": "2026-05-28" }, { - "id": "nebius/deepseek-ai/DeepSeek-V4-Pro-0813", - "name": "DeepSeek V4 Pro 0813 (Nebius)", - "display_name": "DeepSeek V4 Pro 0813 (Nebius)", + "id": "google/gemini-flash-latest", + "name": "Gemini Flash Latest (Gemini 3.8 Flash)", + "display_name": "Gemini Flash Latest (Gemini 3.8 Flash)", "modalities": { "input": [ - "text" + "text", + "image", + "video", + "audio", + "pdf" ], "output": [ "text" @@ -283150,7 +283045,7 @@ }, "limit": { "context": 1048576, - "output": 384000 + "output": 65536 }, "temperature": true, "tool_call": true, @@ -283160,29 +283055,30 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, "effort_options": [ - "none", "low", - "high", - "max" + "medium", + "high" ] } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-08-13", - "last_updated": "2026-08-22", + "attachment": true, + "open_weights": false, + "release_date": "2026-09-02", + "last_updated": "2026-09-02", "type": "chat" }, { - "id": "nebius/deepseek-ai/DeepSeek-V4.1-Flash-1017", - "name": "DeepSeek V4.1 Flash 1017 (Nebius)", - "display_name": "DeepSeek V4.1 Flash 1017 (Nebius)", + "id": "google/gemini-3.6-flash", + "name": "Gemini 3.6 Flash", + "display_name": "Gemini 3.6 Flash", "modalities": { "input": [ "text", - "image" + "image", + "video", + "audio", + "pdf" ], "output": [ "text" @@ -283190,7 +283086,7 @@ }, "limit": { "context": 1048576, - "output": 384000 + "output": 65536 }, "temperature": true, "tool_call": true, @@ -283200,58 +283096,92 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "none", + "supported": true, + "default_enabled": true, + "mode": "level", + "level": "high", + "level_options": [ + "minimal", "low", - "high", - "max" + "medium", + "high" + ], + "summaries": true, + "visibility": "summary", + "continuation": [ + "thought_signatures" ] } }, "attachment": true, - "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-10-17", - "last_updated": "2026-10-17", + "open_weights": false, + "knowledge": "2026-03", + "release_date": "2026-07-21", + "last_updated": "2026-07-21", "type": "chat" }, { - "id": "infomaniak/mistralai/Ministral-3-14B-Instruct-2512", - "name": "Ministral 3 14B (Infomaniak)", - "display_name": "Ministral 3 14B (Infomaniak)", + "id": "google/gemini-3.8-flash", + "name": "Gemini 3.8 Flash", + "display_name": "Gemini 3.8 Flash", "modalities": { "input": [ "text", - "image" + "image", + "video", + "audio", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 100000, - "output": 262144 + "context": 1048576, + "output": 65536 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "level", + "level": "high", + "level_options": [ + "minimal", + "low", + "medium", + "high" + ], + "summaries": true, + "visibility": "summary", + "continuation": [ + "thought_signatures" + ] + } }, "attachment": true, - "open_weights": true, - "release_date": "2025-12-02", - "last_updated": "2025-12-02", + "open_weights": false, + "release_date": "2026-09-02", + "last_updated": "2026-09-02", "type": "chat" }, { - "id": "minimax/MiniMax-M3.1-Flash", - "name": "MiniMax-M3.1-Flash", - "display_name": "MiniMax-M3.1-Flash", + "id": "google/gemini-3.1-flash-lite-preview", + "name": "Gemini 3.1 Flash Lite Preview", + "display_name": "Gemini 3.1 Flash Lite Preview", "modalities": { "input": [ "text", "image", - "video" + "video", + "audio", + "pdf" ], "output": [ "text" @@ -283259,7 +283189,7 @@ }, "limit": { "context": 1048576, - "output": 512000 + "output": 65536 }, "temperature": true, "tool_call": true, @@ -283270,56 +283200,111 @@ "extra_capabilities": { "reasoning": { "effort_options": [ + "minimal", "low", "medium", - "high", - "xhigh", - "max" + "high" ] } }, "attachment": true, "open_weights": false, - "release_date": "2026-10-19", - "last_updated": "2026-10-19", + "knowledge": "2025-01", + "release_date": "2026-03-03", + "last_updated": "2026-03-03", "type": "chat" }, { - "id": "minimax/MiniMax-M2", - "name": "MiniMax-M2", - "display_name": "MiniMax-M2", + "id": "google/gemini-3.1-flash-lite-image", + "name": "Nano Banana 2 Lite", + "display_name": "Nano Banana 2 Lite", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ - "text" + "text", + "image" ] }, "limit": { - "context": 204800, - "output": 131072 + "context": 65536, + "output": 4096 }, "temperature": true, + "tool_call": false, + "reasoning": { + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "effort_options": [ + "minimal", + "high" + ] + } + }, + "attachment": true, + "open_weights": false, + "knowledge": "2025-01", + "release_date": "2026-06-30", + "last_updated": "2026-06-30" + }, + { + "id": "google/gemini-pro-latest", + "name": "Gemini Pro Latest (Gemini 4 Argon)", + "display_name": "Gemini Pro Latest (Gemini 4 Argon)", + "modalities": { + "input": [ + "text", + "image", + "video", + "audio", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1048576, + "output": 65536 + }, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, "default": true }, - "attachment": false, - "open_weights": true, - "release_date": "2025-10-27", - "last_updated": "2025-10-27", + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "low", + "medium", + "high" + ] + } + }, + "attachment": true, + "open_weights": false, + "knowledge": "2026-03", + "release_date": "2026-10-13", + "last_updated": "2026-10-13", "type": "chat" }, { - "id": "minimax/MiniMax-M3", - "name": "MiniMax-M3", - "display_name": "MiniMax-M3", + "id": "google/gemini-robotics-er-2-preview", + "name": "Gemini Robotics-ER 2 Preview", + "display_name": "Gemini Robotics-ER 2 Preview", "modalities": { "input": [ "text", "image", + "pdf", + "audio", "video" ], "output": [ @@ -283327,8 +283312,8 @@ ] }, "limit": { - "context": 524288, - "output": 512000 + "context": 131072, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -283338,30 +283323,39 @@ }, "extra_capabilities": { "reasoning": { - "supported": true + "effort_options": [ + "minimal", + "low", + "medium", + "high" + ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-06-01", - "last_updated": "2026-06-01", + "open_weights": false, + "release_date": "2026-07-30", + "last_updated": "2026-07-30", "type": "chat" }, { - "id": "minimax/MiniMax-M2.1", - "name": "MiniMax-M2.1", - "display_name": "MiniMax-M2.1", + "id": "google/gemini-3.1-pro-preview", + "name": "Gemini 3.1 Pro Preview", + "display_name": "Gemini 3.1 Pro Preview", "modalities": { "input": [ - "text" + "text", + "image", + "video", + "audio", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 204800, - "output": 131072 + "context": 1048576, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -283371,30 +283365,47 @@ }, "extra_capabilities": { "reasoning": { - "supported": true + "supported": true, + "default_enabled": true, + "mode": "level", + "level": "high", + "level_options": [ + "low", + "high" + ], + "summaries": true, + "visibility": "summary", + "continuation": [ + "thought_signatures" + ] } }, - "attachment": false, - "open_weights": true, - "release_date": "2025-12-23", - "last_updated": "2025-12-23", + "attachment": true, + "open_weights": false, + "knowledge": "2025-01", + "release_date": "2026-02-19", + "last_updated": "2026-02-19", "type": "chat" }, { - "id": "minimax/MiniMax-M2.7", - "name": "MiniMax-M2.7", - "display_name": "MiniMax-M2.7", + "id": "google/gemini-3.7-flash", + "name": "Gemini 3.7 Flash", + "display_name": "Gemini 3.7 Flash", "modalities": { "input": [ - "text" + "text", + "image", + "video", + "audio", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 204800, - "output": 131072 + "context": 1048576, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -283405,35 +283416,48 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, + "default_enabled": true, + "mode": "level", + "level": "high", + "level_options": [ + "minimal", + "low", + "medium", + "high" + ], "summaries": true, "visibility": "summary", "continuation": [ - "thinking_blocks" + "thought_signatures" ] } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-03-18", - "last_updated": "2026-03-18", + "attachment": true, + "open_weights": false, + "knowledge": "2026-03", + "release_date": "2026-08-13", + "last_updated": "2026-08-13", "type": "chat" }, { - "id": "minimax/MiniMax-M2.5", - "name": "MiniMax-M2.5", - "display_name": "MiniMax-M2.5", + "id": "google/gemini-3.1-flash-lite", + "name": "Gemini 3.1 Flash Lite", + "display_name": "Gemini 3.1 Flash Lite", "modalities": { "input": [ - "text" + "text", + "image", + "video", + "audio", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 204800, - "output": 131072 + "context": 1048576, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -283443,19 +283467,26 @@ }, "extra_capabilities": { "reasoning": { - "supported": true + "supported": true, + "effort_options": [ + "minimal", + "low", + "medium", + "high" + ] } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-02-12", - "last_updated": "2026-02-12", + "attachment": true, + "open_weights": false, + "knowledge": "2025-01", + "release_date": "2026-05-07", + "last_updated": "2026-05-07", "type": "chat" }, { - "id": "vertex/gemini-pro-latest", - "name": "Gemini Pro Latest (Gemini 4 Argon, Vertex AI)", - "display_name": "Gemini Pro Latest (Gemini 4 Argon, Vertex AI)", + "id": "google/gemini-3.1-pro-preview-customtools", + "name": "Gemini 3.1 Pro Preview Custom Tools", + "display_name": "Gemini 3.1 Pro Preview Custom Tools", "modalities": { "input": [ "text", @@ -283472,7 +283503,7 @@ "context": 1048576, "output": 65536 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -283480,24 +283511,32 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ + "supported": true, + "default_enabled": true, + "mode": "level", + "level": "high", + "level_options": [ "low", - "medium", "high" + ], + "summaries": true, + "visibility": "summary", + "continuation": [ + "thought_signatures" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-03", - "release_date": "2026-10-13", - "last_updated": "2026-10-13", + "knowledge": "2025-01", + "release_date": "2026-02-19", + "last_updated": "2026-02-19", "type": "chat" }, { - "id": "vertex/gemini-3-flash-preview", - "name": "Gemini 3 Flash Preview (Vertex AI)", - "display_name": "Gemini 3 Flash Preview (Vertex AI)", + "id": "google/gemini-3-flash-preview", + "name": "Gemini 3 Flash Preview", + "display_name": "Gemini 3 Flash Preview", "modalities": { "input": [ "text", @@ -283547,13 +283586,15 @@ "type": "chat" }, { - "id": "vertex/gemini-2.5-flash-image", - "name": "Nano Banana (Vertex AI)", - "display_name": "Nano Banana (Vertex AI)", + "id": "google/gemini-2.5-flash-image", + "name": "Nano Banana", + "display_name": "Nano Banana", "modalities": { "input": [ + "audio", + "image", "text", - "image" + "video" ], "output": [ "text", @@ -283576,27 +283617,25 @@ "last_updated": "2025-08-26" }, { - "id": "vertex/gemini-3.5-flash-lite", - "name": "Gemini 3.5 Flash Lite (Vertex AI)", - "display_name": "Gemini 3.5 Flash Lite (Vertex AI)", + "id": "google/gemini-3.1-flash-image-preview", + "name": "Nano Banana 2 Preview", + "display_name": "Nano Banana 2 Preview", "modalities": { "input": [ - "text", "image", - "video", - "audio", - "pdf" + "text" ], "output": [ - "text" + "text", + "image" ] }, "limit": { - "context": 1048576, + "context": 65536, "output": 65536 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { "supported": true, "default": true @@ -283605,28 +283644,26 @@ "reasoning": { "supported": true, "effort_options": [ + "none", "minimal", - "low", - "medium", "high" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-03", - "release_date": "2026-07-21", - "last_updated": "2026-07-21", - "type": "chat" + "knowledge": "2025-01", + "release_date": "2026-02-26", + "last_updated": "2026-02-26" }, { - "id": "vertex/gemini-3.1-flash-image", - "name": "Nano Banana 2 (Vertex AI)", - "display_name": "Nano Banana 2 (Vertex AI)", + "id": "google/gemini-3-pro-image-preview", + "name": "Nano Banana Pro Preview", + "display_name": "Nano Banana Pro Preview", "modalities": { "input": [ - "image", - "text" + "text", + "image" ], "output": [ "text", @@ -283634,7 +283671,7 @@ ] }, "limit": { - "context": 131072, + "context": 65536, "output": 32768 }, "temperature": true, @@ -283646,22 +283683,30 @@ "extra_capabilities": { "reasoning": { "supported": true, - "effort_options": [ - "minimal", + "default_enabled": true, + "mode": "level", + "level": "high", + "level_options": [ + "low", "high" + ], + "summaries": true, + "visibility": "summary", + "continuation": [ + "thought_signatures" ] } }, "attachment": true, "open_weights": false, "knowledge": "2025-01", - "release_date": "2026-05-28", - "last_updated": "2026-05-28" + "release_date": "2025-11-20", + "last_updated": "2025-11-20" }, { - "id": "vertex/gemini-3.1-flash-lite", - "name": "Gemini 3.1 Flash Lite (Vertex AI)", - "display_name": "Gemini 3.1 Flash Lite (Vertex AI)", + "id": "google/gemini-3.5-flash-lite", + "name": "Gemini 3.5 Flash Lite", + "display_name": "Gemini 3.5 Flash Lite", "modalities": { "input": [ "text", @@ -283697,21 +283742,75 @@ }, "attachment": true, "open_weights": false, - "knowledge": "2025-01", - "release_date": "2026-05-07", - "last_updated": "2026-05-07", + "knowledge": "2026-03", + "release_date": "2026-07-21", + "last_updated": "2026-07-21", "type": "chat" }, { - "id": "vertex/gemini-3.7-flash", - "name": "Gemini 3.7 Flash (Vertex AI)", - "display_name": "Gemini 3.7 Flash (Vertex AI)", + "id": "mistral/devstral-medium-latest", + "name": "Devstral 2 (latest)", + "display_name": "Devstral 2 (latest)", + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 262144, + "output": 262144 + }, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": false + }, + "attachment": false, + "open_weights": true, + "knowledge": "2025-12", + "release_date": "2025-12-02", + "last_updated": "2025-12-02", + "type": "chat" + }, + { + "id": "mistral/codestral-latest", + "name": "Codestral (latest)", + "display_name": "Codestral (latest)", + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 256000, + "output": 4096 + }, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": false + }, + "attachment": false, + "open_weights": true, + "knowledge": "2024-10", + "release_date": "2024-05-29", + "last_updated": "2025-01-04", + "type": "chat" + }, + { + "id": "mistral/mistral-large-2512", + "name": "Mistral Large 3", + "display_name": "Mistral Large 3", "modalities": { "input": [ "text", "image", - "video", - "audio", "pdf" ], "output": [ @@ -283719,98 +283818,236 @@ ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 262144, + "output": 262144 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true + "supported": false }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "level", - "level": "high", - "level_options": [ - "minimal", - "low", - "medium", - "high" - ], - "summaries": true, - "visibility": "summary", - "continuation": [ - "thought_signatures" - ] - } + "attachment": true, + "open_weights": true, + "knowledge": "2024-11", + "release_date": "2025-12-02", + "last_updated": "2025-12-02", + "type": "chat" + }, + { + "id": "mistral/mistral-large-2407", + "name": "Mistral Large 2407", + "display_name": "Mistral Large 2407", + "modalities": { + "input": [ + "text", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "output": 104857 + }, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": false }, "attachment": true, - "open_weights": false, - "knowledge": "2026-03", - "release_date": "2026-08-13", - "last_updated": "2026-08-13", + "open_weights": true, + "knowledge": "2024-03-31", + "release_date": "2024-07-24", + "last_updated": "2024-07-24", "type": "chat" }, { - "id": "vertex/gemini-3-pro-image", - "name": "Nano Banana Pro (Vertex AI)", - "display_name": "Nano Banana Pro (Vertex AI)", + "id": "mistral/ministral-14b-2512", + "name": "Ministral 3 14B 2512", + "display_name": "Ministral 3 14B 2512", + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 262144, + "output": 209715 + }, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": false + }, + "attachment": true, + "open_weights": true, + "release_date": "2025-12-02", + "last_updated": "2025-12-02", + "type": "chat" + }, + { + "id": "mistral/mistral-medium-2505", + "name": "Mistral Medium 3", + "display_name": "Mistral Medium 3", "modalities": { "input": [ "text", "image" ], "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "output": 131072 + }, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": false + }, + "attachment": true, + "open_weights": false, + "knowledge": "2025-05", + "release_date": "2025-05-07", + "last_updated": "2025-05-07", + "type": "chat" + }, + { + "id": "mistral/mistral-medium-latest", + "name": "Mistral Medium (latest)", + "display_name": "Mistral Medium (latest)", + "modalities": { + "input": [ "text", "image" + ], + "output": [ + "text" ] }, "limit": { - "context": 65536, - "output": 32768 + "context": 262144, + "output": 262144 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { "supported": true, "default": true }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "level", - "level": "high", - "level_options": [ - "low", + "effort_options": [ + "none", "high" - ], - "summaries": true, - "visibility": "summary", - "continuation": [ - "thought_signatures" ] } }, "attachment": true, + "open_weights": true, + "release_date": "2026-04-29", + "last_updated": "2026-04-29", + "type": "chat" + }, + { + "id": "mistral/codestral-2508", + "name": "Codestral 25.08", + "display_name": "Codestral 25.08", + "modalities": { + "input": [ + "text", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 256000, + "output": 8192 + }, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": false + }, + "attachment": true, "open_weights": false, - "knowledge": "2025-01", - "release_date": "2026-05-28", - "last_updated": "2026-05-28" + "release_date": "2025-07-30", + "last_updated": "2025-07-30", + "type": "chat" }, { - "id": "vertex/gemini-flash-latest", - "name": "Gemini Flash Latest (Gemini 3.8 Flash, Vertex AI)", - "display_name": "Gemini Flash Latest (Gemini 3.8 Flash, Vertex AI)", + "id": "mistral/ministral-8b-2512", + "name": "Ministral 3 8B 2512", + "display_name": "Ministral 3 8B 2512", + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 262144, + "output": 209715 + }, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": false + }, + "attachment": true, + "open_weights": true, + "release_date": "2025-12-02", + "last_updated": "2025-12-02", + "type": "chat" + }, + { + "id": "mistral/ministral-3b-2512", + "name": "Ministral 3 3B 2512", + "display_name": "Ministral 3 3B 2512", + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "output": 104857 + }, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": false + }, + "attachment": true, + "open_weights": true, + "release_date": "2025-12-02", + "last_updated": "2025-12-02", + "type": "chat" + }, + { + "id": "mistral/mistral-medium-3", + "name": "Mistral Medium 3", + "display_name": "Mistral Medium 3", "modalities": { "input": [ "text", "image", - "video", - "audio", "pdf" ], "output": [ @@ -283818,8 +284055,37 @@ ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 262144, + "output": 104857 + }, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": false + }, + "attachment": true, + "open_weights": false, + "knowledge": "2025-03-31", + "release_date": "2025-05-07", + "last_updated": "2025-05-07", + "type": "chat" + }, + { + "id": "mistral/mistral-small-latest", + "name": "Mistral Small (latest)", + "display_name": "Mistral Small (latest)", + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 262144, + "output": 256000 }, "temperature": true, "tool_call": true, @@ -283830,76 +284096,71 @@ "extra_capabilities": { "reasoning": { "effort_options": [ - "low", - "medium", + "none", "high" ] } }, "attachment": true, - "open_weights": false, - "release_date": "2026-09-02", - "last_updated": "2026-09-02", + "open_weights": true, + "knowledge": "2025-06", + "release_date": "2026-03-16", + "last_updated": "2026-03-16", "type": "chat" }, { - "id": "vertex/gemini-3.1-flash-lite-image", - "name": "Nano Banana 2 Lite (Vertex AI)", - "display_name": "Nano Banana 2 Lite (Vertex AI)", + "id": "mistral/mistral-medium-2604", + "name": "Mistral Medium 3.5", + "display_name": "Mistral Medium 3.5", "modalities": { "input": [ "text", "image" ], "output": [ - "text", - "image" + "text" ] }, "limit": { - "context": 65536, - "output": 4096 + "context": 262144, + "output": 262144 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { "supported": true, "default": true }, "extra_capabilities": { "reasoning": { - "supported": true, "effort_options": [ - "minimal", + "none", "high" ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2025-01", - "release_date": "2026-06-30", - "last_updated": "2026-06-30" + "open_weights": true, + "release_date": "2026-04-29", + "last_updated": "2026-04-29", + "type": "chat" }, { - "id": "vertex/gemini-3.1-pro-preview", - "name": "Gemini 3.1 Pro Preview (Vertex AI)", - "display_name": "Gemini 3.1 Pro Preview (Vertex AI)", + "id": "mistral/magistral-medium-latest", + "name": "Magistral Medium (latest)", + "display_name": "Magistral Medium (latest)", "modalities": { "input": [ "text", - "image", - "video", - "audio", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 262144, + "output": 16384 }, "temperature": true, "tool_call": true, @@ -283909,38 +284170,26 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "level", - "level": "high", - "level_options": [ - "low", + "effort_options": [ + "none", "high" - ], - "summaries": true, - "visibility": "summary", - "continuation": [ - "thought_signatures" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-01", - "release_date": "2026-02-19", - "last_updated": "2026-02-19", + "knowledge": "2025-06", + "release_date": "2025-03-17", + "last_updated": "2025-03-20", "type": "chat" }, { - "id": "vertex/gemini-3.8-flash", - "name": "Gemini 3.8 Flash (Vertex AI)", - "display_name": "Gemini 3.8 Flash (Vertex AI)", + "id": "mistral/devstral-2512", + "name": "Devstral 2", + "display_name": "Devstral 2", "modalities": { "input": [ "text", - "image", - "video", - "audio", "pdf" ], "output": [ @@ -283948,50 +284197,29 @@ ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 262144, + "output": 262144 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "level", - "level": "high", - "level_options": [ - "minimal", - "low", - "medium", - "high" - ], - "summaries": true, - "visibility": "summary", - "continuation": [ - "thought_signatures" - ] - } + "supported": false }, "attachment": true, - "open_weights": false, - "release_date": "2026-09-02", - "last_updated": "2026-09-02", + "open_weights": true, + "knowledge": "2025-12", + "release_date": "2025-12-09", + "last_updated": "2025-12-09", "type": "chat" }, { - "id": "vertex/gemini-4-argon", - "name": "Gemini 4 Argon (Vertex AI)", - "display_name": "Gemini 4 Argon (Vertex AI)", + "id": "mistral/mistral-medium-3-5", + "name": "Mistral Medium 3.5", + "display_name": "Mistral Medium 3.5", "modalities": { "input": [ "text", "image", - "video", - "audio", "pdf" ], "output": [ @@ -283999,10 +284227,10 @@ ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 262144, + "output": 209715 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -284011,90 +284239,61 @@ "extra_capabilities": { "reasoning": { "effort_options": [ - "low", - "medium", + "none", "high" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-03", - "release_date": "2026-10-13", - "last_updated": "2026-10-13", + "release_date": "2026-04-30", + "last_updated": "2026-04-30", "type": "chat" }, { - "id": "vertex/gemini-3.6-flash", - "name": "Gemini 3.6 Flash (Vertex AI)", - "display_name": "Gemini 3.6 Flash (Vertex AI)", + "id": "mistral/voxtral-small-latest", + "name": "Voxtral Small (latest)", + "display_name": "Voxtral Small (latest)", "modalities": { "input": [ "text", - "image", - "video", - "audio", - "pdf" + "audio" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 32768, + "output": 32000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "level", - "level": "high", - "level_options": [ - "minimal", - "low", - "medium", - "high" - ], - "summaries": true, - "visibility": "summary", - "continuation": [ - "thought_signatures" - ] - } + "supported": false }, "attachment": true, - "open_weights": false, - "knowledge": "2026-03", - "release_date": "2026-07-21", - "last_updated": "2026-07-21", + "open_weights": true, + "release_date": "2025-07-15", + "last_updated": "2025-07-15", "type": "chat" }, { - "id": "vertex/gemini-3.5-flash", - "name": "Gemini 3.5 Flash (Vertex AI)", - "display_name": "Gemini 3.5 Flash (Vertex AI)", + "id": "mistral/mistral-small-2603", + "name": "Mistral Small 4", + "display_name": "Mistral Small 4", "modalities": { "input": [ "text", - "image", - "video", - "audio", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 262144, + "output": 256000 }, "temperature": true, "tool_call": true, @@ -284104,72 +284303,53 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "level", - "level": "high", - "level_options": [ - "minimal", - "low", - "medium", + "effort_options": [ + "none", "high" - ], - "summaries": true, - "visibility": "summary", - "continuation": [ - "thought_signatures" ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2025-01", - "release_date": "2026-05-19", - "last_updated": "2026-05-19", + "open_weights": true, + "knowledge": "2025-06", + "release_date": "2026-03-16", + "last_updated": "2026-03-16", "type": "chat" }, { - "id": "vertex/gemini-nano-banana-2.1", - "name": "Nano Banana 2.1 (Vertex AI)", - "display_name": "Nano Banana 2.1 (Vertex AI)", + "id": "mistral/mistral-large-latest", + "name": "Mistral Large (latest)", + "display_name": "Mistral Large (latest)", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ - "text", - "image" + "text" ] }, "limit": { - "context": 131072, - "output": 32768 + "context": 262144, + "output": 262144 }, - "temperature": false, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "minimal", - "medium", - "high" - ] - } + "supported": false }, "attachment": true, - "open_weights": false, - "release_date": "2026-10-06", - "last_updated": "2026-10-06" + "open_weights": true, + "knowledge": "2024-11", + "release_date": "2024-11-01", + "last_updated": "2025-12-02", + "type": "chat" }, { - "id": "databricks/databricks-gpt-oss-20b", - "name": "GPT OSS 20B (Databricks)", - "display_name": "GPT OSS 20B (Databricks)", + "id": "groq/openai/gpt-oss-safeguard-20b", + "name": "GPT OSS Safeguard 20B (Groq)", + "display_name": "GPT OSS Safeguard 20B (Groq)", "modalities": { "input": [ "text" @@ -284180,7 +284360,7 @@ }, "limit": { "context": 131072, - "output": 32768 + "output": 131072 }, "temperature": true, "tool_call": true, @@ -284199,15 +284379,14 @@ }, "attachment": false, "open_weights": true, - "knowledge": "2024-06", - "release_date": "2025-08-05", - "last_updated": "2025-08-05", + "release_date": "2025-10-29", + "last_updated": "2025-10-29", "type": "chat" }, { - "id": "databricks/databricks-gpt-oss-120b", - "name": "GPT OSS 120B (Databricks)", - "display_name": "GPT OSS 120B (Databricks)", + "id": "groq/openai/gpt-oss-120b", + "name": "GPT OSS 120B (Groq)", + "display_name": "GPT OSS 120B (Groq)", "modalities": { "input": [ "text" @@ -284228,6 +284407,7 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, "effort_options": [ "low", "medium", @@ -284243,9 +284423,9 @@ "type": "chat" }, { - "id": "databricks/databricks-deepseek-v4-flash-0731", - "name": "DeepSeek V4 Flash 0731 (Databricks)", - "display_name": "DeepSeek V4 Flash 0731 (Databricks)", + "id": "groq/openai/gpt-oss-20b", + "name": "GPT OSS 20B (Groq)", + "display_name": "GPT OSS 20B (Groq)", "modalities": { "input": [ "text" @@ -284255,8 +284435,8 @@ ] }, "limit": { - "context": 1000000, - "output": 384000 + "context": 131072, + "output": 32768 }, "temperature": true, "tool_call": true, @@ -284266,25 +284446,25 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, "effort_options": [ - "none", "low", - "high", - "max" + "medium", + "high" ] } }, "attachment": false, "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-07-31", - "last_updated": "2026-07-31", + "knowledge": "2024-06", + "release_date": "2025-08-05", + "last_updated": "2025-08-05", "type": "chat" }, { - "id": "databricks/databricks-qwen3-next-80b-a3b-instruct", - "name": "Qwen3-Next 80B-A3B Instruct (Databricks)", - "display_name": "Qwen3-Next 80B-A3B Instruct (Databricks)", + "id": "groq/meta-llama/llama-prompt-guard-2-86m", + "name": "Llama Prompt Guard 2 86M (Groq)", + "display_name": "Llama Prompt Guard 2 86M (Groq)", "modalities": { "input": [ "text" @@ -284294,25 +284474,24 @@ ] }, "limit": { - "context": 262144, - "output": 32768 + "context": 512, + "output": 512 }, - "temperature": true, - "tool_call": true, + "temperature": false, + "tool_call": false, "reasoning": { "supported": false }, "attachment": false, "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-09", - "last_updated": "2025-09", + "release_date": "2025-04-29", + "last_updated": "2025-04-29", "type": "chat" }, { - "id": "ionos/Qwen/Qwen3-Coder-Next", - "name": "Qwen3 Coder Next (IONOS)", - "display_name": "Qwen3 Coder Next (IONOS)", + "id": "groq/meta-llama/llama-prompt-guard-2-22m", + "name": "Llama Prompt Guard 2 22M (Groq)", + "display_name": "Llama Prompt Guard 2 22M (Groq)", "modalities": { "input": [ "text" @@ -284322,25 +284501,24 @@ ] }, "limit": { - "context": 128000, - "output": 65536 + "context": 512, + "output": 512 }, - "temperature": true, - "tool_call": true, + "temperature": false, + "tool_call": false, "reasoning": { "supported": false }, "attachment": false, "open_weights": true, - "knowledge": "2025-09", - "release_date": "2026-02-03", - "last_updated": "2026-02-03", + "release_date": "2025-04-29", + "last_updated": "2025-04-29", "type": "chat" }, { - "id": "ionos/openai/gpt-oss-120b", - "name": "GPT OSS 120B (IONOS)", - "display_name": "GPT OSS 120B (IONOS)", + "id": "together_ai/openai/gpt-oss-120b", + "name": "GPT OSS 120B (Together AI)", + "display_name": "GPT OSS 120B (Together AI)", "modalities": { "input": [ "text" @@ -284350,7 +284528,7 @@ ] }, "limit": { - "context": 128000, + "context": 131072, "output": 32768 }, "temperature": true, @@ -284377,37 +284555,51 @@ "type": "chat" }, { - "id": "ionos/meta-llama/Llama-3.3-70B-Instruct", - "name": "Llama-3.3-70B-Instruct (IONOS)", - "display_name": "Llama-3.3-70B-Instruct (IONOS)", + "id": "together_ai/thinkingmachines/Inkling", + "name": "Inkling (Together AI)", + "display_name": "Inkling (Together AI)", "modalities": { "input": [ - "text" + "text", + "image", + "audio" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 4096 + "context": 524288, + "output": 1048576 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "none", + "minimal", + "low", + "medium", + "high", + "max" + ] + } + }, + "attachment": true, "open_weights": true, - "knowledge": "2023-12", - "release_date": "2024-12-06", - "last_updated": "2024-12-06", + "release_date": "2026-07-15", + "last_updated": "2026-07-15", "type": "chat" }, { - "id": "cerebras/gpt-oss-120b", - "name": "GPT OSS 120B (Cerebras)", - "display_name": "GPT OSS 120B (Cerebras)", + "id": "together_ai/reflection/Beam-1.0-501B-A23B", + "name": "Beam 1.0 501B A23B (Together AI)", + "display_name": "Beam 1.0 501B A23B (Together AI)", "modalities": { "input": [ "text" @@ -284417,8 +284609,8 @@ ] }, "limit": { - "context": 131072, - "output": 32768 + "context": 1048576, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -284428,226 +284620,160 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, "effort_options": [ "low", "medium", - "high" + "high", + "xhigh", + "max" ] } }, "attachment": false, "open_weights": true, - "knowledge": "2024-06", - "release_date": "2025-08-05", - "last_updated": "2025-08-05", + "knowledge": "2026-06-30", + "release_date": "2026-10-05", + "last_updated": "2026-10-16", "type": "chat" }, { - "id": "azure/gpt-5.1-codex-max", - "name": "GPT-5.1 Codex Max (Azure)", - "display_name": "GPT-5.1 Codex Max (Azure)", + "id": "together_ai/meta-models/Muse-Glimmer-30B", + "name": "Muse Glimmer 30B (Together AI)", + "display_name": "Muse Glimmer 30B (Together AI)", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 272000, - "output": 128000 + "context": 131072, + "output": 131072 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "none", "effort_options": [ - "none", - "low", - "medium", - "high" - ], - "verbosity": "medium", - "verbosity_options": [ "low", "medium", - "high" - ], - "visibility": "hidden" + "high", + "xhigh" + ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2024-09-30", - "release_date": "2025-11-13", - "last_updated": "2025-11-13", + "open_weights": true, + "knowledge": "2026-01-04", + "release_date": "2026-08-10", + "last_updated": "2026-08-10", "type": "chat" }, { - "id": "azure/gpt-4.1-nano", - "name": "GPT-4.1 nano (Azure)", - "display_name": "GPT-4.1 nano (Azure)", + "id": "together_ai/deepseek-ai/DeepSeek-V4.1-Flash-1017", + "name": "DeepSeek V4.1 Flash 1017 (Together AI)", + "display_name": "DeepSeek V4.1 Flash 1017 (Together AI)", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1047576, - "output": 32768 + "context": 1048576, + "output": 384000 }, "temperature": true, "tool_call": true, - "reasoning": { - "supported": false - }, - "attachment": true, - "open_weights": false, - "knowledge": "2024-04", - "release_date": "2025-04-14", - "last_updated": "2025-04-14", - "type": "chat" - }, - { - "id": "azure/gpt-5.1-codex", - "name": "GPT-5.1 Codex (Azure)", - "display_name": "GPT-5.1 Codex (Azure)", - "modalities": { - "input": [ - "text", - "image", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 272000, - "output": 128000 - }, - "temperature": false, - "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "none", "effort_options": [ "none", "low", - "medium", - "high" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + "high", + "max" + ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2024-09-30", - "release_date": "2025-11-13", - "last_updated": "2025-11-13", + "open_weights": true, + "knowledge": "2025-05", + "release_date": "2026-10-17", + "last_updated": "2026-10-17", "type": "chat" }, { - "id": "azure/gpt-5.1-codex-mini", - "name": "GPT-5.1 Codex mini (Azure)", - "display_name": "GPT-5.1 Codex mini (Azure)", + "id": "together_ai/deepseek-ai/DeepSeek-V4.1-Flash", + "name": "DeepSeek V4.1 Flash (Together AI)", + "display_name": "DeepSeek V4.1 Flash (Together AI)", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 272000, - "output": 128000 + "context": 1048576, + "output": 384000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "none", "effort_options": [ "none", "low", - "medium", - "high" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + "high", + "max" + ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2024-09-30", - "release_date": "2025-11-13", - "last_updated": "2025-11-13", + "open_weights": true, + "knowledge": "2025-05", + "release_date": "2026-09-10", + "last_updated": "2026-09-10", "type": "chat" }, { - "id": "azure/gpt-5.2-codex", - "name": "GPT-5.2 Codex (Azure)", - "display_name": "GPT-5.2 Codex (Azure)", + "id": "together_ai/deepseek-ai/DeepSeek-V4-Pro-0813", + "name": "DeepSeek V4 Pro 0813 (Together AI)", + "display_name": "DeepSeek V4 Pro 0813 (Together AI)", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 272000, - "output": 128000 + "context": 1048576, + "output": 384000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -284656,35 +284782,24 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", "effort_options": [ + "none", "low", - "medium", "high", - "xhigh" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + "max" + ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2025-12-11", - "last_updated": "2025-12-11", + "attachment": false, + "open_weights": true, + "release_date": "2026-08-13", + "last_updated": "2026-08-22", "type": "chat" }, { - "id": "perplexityai/sonar-pro", - "name": "Sonar Pro", - "display_name": "Sonar Pro", + "id": "infomaniak/mistralai/Ministral-3-14B-Instruct-2512", + "name": "Ministral 3 14B (Infomaniak)", + "display_name": "Ministral 3 14B (Infomaniak)", "modalities": { "input": [ "text", @@ -284695,25 +284810,24 @@ ] }, "limit": { - "context": 200000, - "output": 8192 + "context": 100000, + "output": 262144 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { "supported": false }, "attachment": true, - "open_weights": false, - "knowledge": "2025-09-01", - "release_date": "2024-01-01", - "last_updated": "2025-09-01", + "open_weights": true, + "release_date": "2025-12-02", + "last_updated": "2025-12-02", "type": "chat" }, { - "id": "perplexityai/sonar-deep-research", - "name": "Sonar Deep Research", - "display_name": "Sonar Deep Research", + "id": "microsoft/o3-mini", + "name": "o3-mini (Microsoft)", + "display_name": "o3-mini (Microsoft)", "modalities": { "input": [ "text" @@ -284723,8 +284837,8 @@ ] }, "limit": { - "context": 128000, - "output": 32768 + "context": 200000, + "output": 100000 }, "temperature": false, "tool_call": false, @@ -284734,69 +284848,73 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", "effort_options": [ - "minimal", "low", "medium", "high" - ] + ], + "visibility": "hidden" } }, "attachment": false, "open_weights": false, - "knowledge": "2025-01", - "release_date": "2025-02-01", - "last_updated": "2025-09-01", + "knowledge": "2024-05", + "release_date": "2024-12-20", + "last_updated": "2025-01-29", "type": "chat" }, { - "id": "perplexityai/sonar", - "name": "Sonar", - "display_name": "Sonar", + "id": "microsoft/gpt-4", + "name": "GPT-4 (Microsoft)", + "display_name": "GPT-4 (Microsoft)", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 127072, - "output": 4096 + "context": 8191, + "output": 8192 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { "supported": false }, - "attachment": true, + "attachment": false, "open_weights": false, - "knowledge": "2025-09-01", - "release_date": "2024-01-01", - "last_updated": "2025-09-01", + "knowledge": "2023-11", + "release_date": "2023-11-06", + "last_updated": "2024-04-09", "type": "chat" }, { - "id": "perplexityai/sonar-reasoning-pro", - "name": "Sonar Reasoning Pro", - "display_name": "Sonar Reasoning Pro", + "id": "anthropic/claude-haiku-5-5", + "name": "Claude Haiku 5.5", + "display_name": "Claude Haiku 5.5", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 4096 + "context": 1000000, + "output": 128000 }, - "temperature": true, - "tool_call": false, + "temperature": false, + "tool_call": true, "reasoning": { "supported": true, "default": true @@ -284804,55 +284922,88 @@ "extra_capabilities": { "reasoning": { "effort_options": [ - "minimal", + "none", "low", "medium", - "high" + "high", + "xhigh", + "max" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-09-01", - "release_date": "2024-01-01", - "last_updated": "2025-09-01", + "knowledge": "2026-06", + "release_date": "2026-10-07", + "last_updated": "2026-10-07", "type": "chat" }, { - "id": "scaleway/qwen3-coder-30b-a3b-instruct", - "name": "Qwen3-Coder 30B-A3B Instruct (Scaleway)", - "display_name": "Qwen3-Coder 30B-A3B Instruct (Scaleway)", + "id": "anthropic/claude-opus-4-7", + "name": "Claude Opus 4.7", + "display_name": "Claude Opus 4.7", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 65536 + "context": 1000000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": false }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-07-31", - "last_updated": "2025-07-31", + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": false, + "mode": "effort", + "effort": "high", + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "omitted", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Claude Opus 4.7 and newer Opus models require thinking.type = \"adaptive\" to enable thinking explicitly.", + "Manual budget_tokens requests return 400 on Claude Opus 4.7 and newer adaptive-only Opus models.", + "task_budget is separate from thinking control and should not be treated as a thinking budget." + ] + } + }, + "attachment": true, + "open_weights": false, + "knowledge": "2026-01-31", + "release_date": "2026-04-16", + "last_updated": "2026-04-16", "type": "chat" }, { - "id": "scaleway/deepseek-v4.1-flash-1017", - "name": "DeepSeek V4.1 Flash 1017 (Scaleway)", - "display_name": "DeepSeek V4.1 Flash 1017 (Scaleway)", + "id": "anthropic/claude-sonnet-5", + "name": "Claude Sonnet 5", + "display_name": "Claude Sonnet 5", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" @@ -284860,48 +285011,53 @@ }, "limit": { "context": 1000000, - "output": 384000 + "output": 128000 }, - "temperature": true, - "tool_call": false, + "temperature": false, + "tool_call": true, "reasoning": { "supported": true, "default": true }, "extra_capabilities": { "reasoning": { + "supported": true, "effort_options": [ "none", "low", + "medium", "high", + "xhigh", "max" ] } }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-10-17", - "last_updated": "2026-10-17", + "attachment": true, + "open_weights": false, + "knowledge": "2026-01-31", + "release_date": "2026-06-30", + "last_updated": "2026-06-30", "type": "chat" }, { - "id": "scaleway/gpt-oss-120b", - "name": "GPT OSS 120B (Scaleway)", - "display_name": "GPT OSS 120B (Scaleway)", + "id": "anthropic/claude-opus-latest", + "name": "Claude Opus Latest (Claude Opus 5.5)", + "display_name": "Claude Opus Latest (Claude Opus 5.5)", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 32768 + "context": 1000000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -284909,39 +285065,41 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, "effort_options": [ "low", "medium", - "high" + "high", + "xhigh", + "max" ] } }, - "attachment": false, - "open_weights": true, - "knowledge": "2024-06", - "release_date": "2025-08-05", - "last_updated": "2025-08-05", + "attachment": true, + "open_weights": false, + "knowledge": "2026-06", + "release_date": "2026-09-22", + "last_updated": "2026-09-22", "type": "chat" }, { - "id": "scaleway/deepseek-v4.1-flash", - "name": "DeepSeek V4.1 Flash (Scaleway)", - "display_name": "DeepSeek V4.1 Flash (Scaleway)", + "id": "anthropic/claude-haiku-latest", + "name": "Claude Haiku Latest (Claude Haiku 5.5)", + "display_name": "Claude Haiku Latest (Claude Haiku 5.5)", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 256000, - "output": 384000 + "context": 1000000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -284949,39 +285107,42 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, "effort_options": [ "none", "low", + "medium", "high", + "xhigh", "max" ] } }, "attachment": true, - "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-09-10", - "last_updated": "2026-09-10", + "open_weights": false, + "knowledge": "2026-06", + "release_date": "2026-10-07", + "last_updated": "2026-10-07", "type": "chat" }, { - "id": "scaleway/deepseek-v4-flash-0731", - "name": "DeepSeek V4 Flash 0731 (Scaleway)", - "display_name": "DeepSeek V4 Flash 0731 (Scaleway)", + "id": "anthropic/claude-fable-5", + "name": "Claude Fable 5", + "display_name": "Claude Fable 5", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 256000, - "output": 384000 + "context": 1000000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -284990,124 +285151,212 @@ "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "high", "effort_options": [ - "none", "low", + "medium", "high", + "xhigh", "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "omitted", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Adaptive thinking is always on for Claude Fable 5 and Claude Mythos 5; thinking.type = \"disabled\" is rejected.", + "Manual budget_tokens requests return 400 on Claude Fable 5 and Claude Mythos 5.", + "thinking.display defaults to omitted; set display to summarized to receive readable thinking summaries." ] } }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-07-31", - "last_updated": "2026-07-31", + "attachment": true, + "open_weights": false, + "knowledge": "2026-01-31", + "release_date": "2026-06-09", + "last_updated": "2026-06-09", "type": "chat" }, { - "id": "scaleway/llama-3.3-70b-instruct", - "name": "Llama-3.3-70B-Instruct (Scaleway)", - "display_name": "Llama-3.3-70B-Instruct (Scaleway)", + "id": "anthropic/claude-opus-5-5", + "name": "Claude Opus 5.5", + "display_name": "Claude Opus 5.5", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 4096 + "context": 1000000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, - "open_weights": true, - "knowledge": "2023-12", - "release_date": "2024-12-06", - "last_updated": "2024-12-06", + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" + ] + } + }, + "attachment": true, + "open_weights": false, + "knowledge": "2026-06", + "release_date": "2026-09-22", + "last_updated": "2026-09-22", "type": "chat" }, { - "id": "scaleway/qwen3-235b-a22b-instruct-2507", - "name": "Qwen3 235B-A22B Instruct 2507 (Scaleway)", - "display_name": "Qwen3 235B-A22B Instruct 2507 (Scaleway)", + "id": "anthropic/claude-fable-latest", + "name": "Claude Fable Latest (Claude Fable 5.1)", + "display_name": "Claude Fable Latest (Claude Fable 5.1)", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 250000, - "output": 16384 + "context": 1000000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, - "open_weights": true, - "release_date": "2025-07-21", - "last_updated": "2025-07-21", + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "high", + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "omitted", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Adaptive thinking is always on for Claude Fable 5 and Claude Mythos 5; thinking.type = \"disabled\" is rejected.", + "Manual budget_tokens requests return 400 on Claude Fable 5 and Claude Mythos 5.", + "thinking.display defaults to omitted; set display to summarized to receive readable thinking summaries." + ] + } + }, + "attachment": true, + "open_weights": false, + "knowledge": "2026-06", + "release_date": "2026-09-01", + "last_updated": "2026-09-01", "type": "chat" }, { - "id": "scaleway/gemma-3-27b-it", - "name": "Gemma 3 27B IT (Scaleway)", - "display_name": "Gemma 3 27B IT (Scaleway)", + "id": "anthropic/claude-opus-4-5", + "name": "Claude Opus 4.5 (latest)", + "display_name": "Claude Opus 4.5 (latest)", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 40000, - "output": 131072 + "context": 200000, + "output": 64000 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": false + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": false, + "mode": "mixed", + "budget": { + "min": 1024, + "unit": "tokens" + }, + "effort": "high", + "effort_options": [ + "low", + "medium", + "high" + ], + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Claude Opus 4.5 uses manual thinking.type = \"enabled\" with budget_tokens; effort can be used alongside the thinking budget.", + "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header." + ] + } }, "attachment": true, - "open_weights": true, - "knowledge": "2024-08", - "release_date": "2025-03-12", - "last_updated": "2025-03-12", + "open_weights": false, + "knowledge": "2025-05", + "release_date": "2025-11-24", + "last_updated": "2025-11-24", "type": "chat" }, { - "id": "zai/glm-5.3-flash", - "name": "GLM-5.3-Flash", - "display_name": "GLM-5.3-Flash", + "id": "anthropic/claude-sonnet-latest", + "name": "Claude Sonnet Latest (Claude Sonnet 5.5)", + "display_name": "Claude Sonnet Latest (Claude Sonnet 5.5)", "modalities": { "input": [ "text", "image", - "video" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 1000000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -285115,171 +285364,258 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, "effort_options": [ "low", + "medium", "high", + "xhigh", "max" ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-08-26", - "last_updated": "2026-08-26", + "open_weights": false, + "knowledge": "2026-06", + "release_date": "2026-09-28", + "last_updated": "2026-09-28", "type": "chat" }, { - "id": "zai/glm-5v-turbo", - "name": "GLM-5V-Turbo", - "display_name": "GLM-5V-Turbo", + "id": "anthropic/claude-sonnet-5-5", + "name": "Claude Sonnet 5.5", + "display_name": "Claude Sonnet 5.5", "modalities": { "input": [ - "image", "text", - "video" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 202752, - "output": 131072 + "context": 1000000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, "default": true }, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" + ] + } + }, "attachment": true, "open_weights": false, - "release_date": "2026-04-01", - "last_updated": "2026-04-01", + "knowledge": "2026-06", + "release_date": "2026-09-28", + "last_updated": "2026-09-28", "type": "chat" }, { - "id": "zai/glm-5-turbo", - "name": "GLM-5-Turbo", - "display_name": "GLM-5-Turbo", + "id": "anthropic/claude-sonnet-4-6", + "name": "Claude Sonnet 4.6", + "display_name": "Claude Sonnet 4.6", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 202752, - "output": 131072 + "context": 1000000, + "output": 64000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { - "supported": true + "supported": true, + "default_enabled": false, + "mode": "mixed", + "budget": { + "min": 1024, + "unit": "tokens" + }, + "effort": "high", + "effort_options": [ + "low", + "medium", + "high", + "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Anthropic recommends adaptive thinking with effort for Claude 4.6; budget_tokens remains a deprecated compatibility path.", + "Anthropic API defaults effort to high; lower effort levels should be chosen per workload." + ] } }, - "attachment": false, + "attachment": true, "open_weights": false, - "release_date": "2026-03-16", - "last_updated": "2026-03-16", + "knowledge": "2025-08-31", + "release_date": "2026-02-17", + "last_updated": "2026-03-13", "type": "chat" }, { - "id": "zai/glm-4.6v", - "name": "GLM-4.6V", - "display_name": "GLM-4.6V", + "id": "anthropic/claude-opus-4-8", + "name": "Claude Opus 4.8", + "display_name": "Claude Opus 4.8", "modalities": { "input": [ "text", "image", - "video" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 32768 + "context": 1000000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": false, + "mode": "effort", + "effort": "high", + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "omitted", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Claude Opus 4.7 and newer Opus models require thinking.type = \"adaptive\" to enable thinking explicitly.", + "Manual budget_tokens requests return 400 on Claude Opus 4.7 and newer adaptive-only Opus models.", + "task_budget is separate from thinking control and should not be treated as a thinking budget." + ] + } }, "attachment": true, - "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-12-08", - "last_updated": "2025-12-08", + "open_weights": false, + "knowledge": "2026-01", + "release_date": "2026-05-28", + "last_updated": "2026-05-28", "type": "chat" }, { - "id": "zai/glm-4.7", - "name": "GLM-4.7", - "display_name": "GLM-4.7", + "id": "anthropic/claude-opus-4-6", + "name": "Claude Opus 4.6", + "display_name": "Claude Opus 4.6", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 202752, - "output": 131072 + "context": 1000000, + "output": 128000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": false, + "mode": "mixed", + "budget": { + "min": 1024, + "unit": "tokens" + }, + "effort": "high", + "effort_options": [ + "low", + "medium", + "high", + "max" + ], "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ "thinking_blocks" + ], + "notes": [ + "Anthropic recommends adaptive thinking with effort for Claude 4.6; budget_tokens remains a deprecated compatibility path.", + "Anthropic API defaults effort to high; lower effort levels should be chosen per workload." ] } }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-12-22", - "last_updated": "2025-12-22", + "attachment": true, + "open_weights": false, + "knowledge": "2025-05-31", + "release_date": "2026-02-05", + "last_updated": "2026-03-13", "type": "chat" }, { - "id": "zai/glm-4.6", - "name": "GLM-4.6", - "display_name": "GLM-4.6", + "id": "anthropic/claude-fable-5-1", + "name": "Claude Fable 5.1", + "display_name": "Claude Fable 5.1", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 202752, - "output": 131072 + "context": 1000000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -285287,33 +285623,56 @@ }, "extra_capabilities": { "reasoning": { - "supported": true + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "high", + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "omitted", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Adaptive thinking is always on for Claude Fable 5 and Claude Mythos 5; thinking.type = \"disabled\" is rejected.", + "Manual budget_tokens requests return 400 on Claude Fable 5 and Claude Mythos 5.", + "thinking.display defaults to omitted; set display to summarized to receive readable thinking summaries." + ] } }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-09-30", - "last_updated": "2025-09-30", + "attachment": true, + "open_weights": false, + "knowledge": "2026-06", + "release_date": "2026-09-01", + "last_updated": "2026-09-01", "type": "chat" }, { - "id": "zai/glm-5.1", - "name": "GLM-5.1", - "display_name": "GLM-5.1", + "id": "anthropic/claude-opus-5", + "name": "Claude Opus 5", + "display_name": "Claude Opus 5", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 202752, - "output": 131072 + "context": 1000000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -285321,61 +285680,89 @@ }, "extra_capabilities": { "reasoning": { - "supported": true + "supported": true, + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" + ] } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-04-07", - "last_updated": "2026-04-07", + "attachment": true, + "open_weights": false, + "knowledge": "2026-05", + "release_date": "2026-07-24", + "last_updated": "2026-07-24", "type": "chat" }, { - "id": "zai/glm-5", - "name": "GLM-5", - "display_name": "GLM-5", + "id": "anthropic/claude-opus-4-5-20251101", + "name": "Claude Opus 4.5", + "display_name": "Claude Opus 4.5", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 202752, - "output": 131072 + "context": 200000, + "output": 64000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": false, + "mode": "mixed", + "budget": { + "min": 1024, + "unit": "tokens" + }, + "effort": "high", + "effort_options": [ + "low", + "medium", + "high" + ], "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ "thinking_blocks" + ], + "notes": [ + "Claude Opus 4.5 uses manual thinking.type = \"enabled\" with budget_tokens; effort can be used alongside the thinking budget.", + "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header." ] } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-02-12", - "last_updated": "2026-02-12", + "attachment": true, + "open_weights": false, + "knowledge": "2025-05", + "release_date": "2025-11-24", + "last_updated": "2025-11-24", "type": "chat" }, { - "id": "zai/glm-5.3", - "name": "GLM-5.3", - "display_name": "GLM-5.3", + "id": "sference/deepseek-ai/DeepSeek-V4.1-Flash-1017", + "name": "DeepSeek V4.1 Flash 1017 (Sference)", + "display_name": "DeepSeek V4.1 Flash 1017 (Sference)", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" @@ -285383,7 +285770,7 @@ }, "limit": { "context": 1048576, - "output": 131072 + "output": 384000 }, "temperature": true, "tool_call": true, @@ -285393,24 +285780,25 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, "effort_options": [ + "none", "low", "high", "max" ] } }, - "attachment": false, + "attachment": true, "open_weights": true, - "release_date": "2026-08-14", - "last_updated": "2026-08-14", + "knowledge": "2025-05", + "release_date": "2026-10-17", + "last_updated": "2026-10-17", "type": "chat" }, { - "id": "zai/glm-5.2", - "name": "GLM-5.2", - "display_name": "GLM-5.2", + "id": "sference/deepseek-ai/DeepSeek-V4-Flash-0731", + "name": "DeepSeek V4 Flash 0731 (Sference)", + "display_name": "DeepSeek V4 Flash 0731 (Sference)", "modalities": { "input": [ "text" @@ -285421,7 +285809,7 @@ }, "limit": { "context": 1048576, - "output": 131072 + "output": 384000 }, "temperature": true, "tool_call": true, @@ -285434,6 +285822,7 @@ "supported": true, "effort_options": [ "none", + "low", "high", "max" ] @@ -285441,14 +285830,15 @@ }, "attachment": false, "open_weights": true, - "release_date": "2026-06-13", - "last_updated": "2026-06-13", + "knowledge": "2025-05", + "release_date": "2026-07-31", + "last_updated": "2026-07-31", "type": "chat" }, { - "id": "fireworks_ai/gpt-oss-120b", - "name": "GPT OSS 120B (Fireworks AI)", - "display_name": "GPT OSS 120B (Fireworks AI)", + "id": "amazon/amazon.nova-micro-v1:0", + "name": "Nova Micro", + "display_name": "Nova Micro", "modalities": { "input": [ "text" @@ -285458,89 +285848,63 @@ ] }, "limit": { - "context": 131072, - "output": 32768 + "context": 128000, + "output": 10000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "effort_options": [ - "low", - "medium", - "high" - ] - } + "supported": false }, "attachment": false, - "open_weights": true, - "knowledge": "2024-06", - "release_date": "2025-08-05", - "last_updated": "2025-08-05", + "open_weights": false, + "knowledge": "2024-10", + "release_date": "2024-12-03", + "last_updated": "2024-12-03", "type": "chat" }, { - "id": "fireworks_ai/accounts/fireworks/models/ember-1", - "name": "Ember-1 (Fireworks AI)", - "display_name": "Ember-1 (Fireworks AI)", + "id": "amazon/qwen.qwen3-coder-next", + "name": "Qwen3 Coder Next (Amazon Bedrock)", + "display_name": "Qwen3 Coder Next (Amazon Bedrock)", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 262144, + "output": 65536 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "none", - "low", - "high", - "max" - ] - } + "supported": false }, - "attachment": true, - "open_weights": false, - "release_date": "2026-09-22", - "last_updated": "2026-09-22", + "attachment": false, + "open_weights": true, + "knowledge": "2025-09", + "release_date": "2026-02-03", + "last_updated": "2026-02-03", "type": "chat" }, { - "id": "fireworks_ai/accounts/fireworks/models/muse-spark-open", - "name": "Muse Spark Open (Fireworks AI)", - "display_name": "Muse Spark Open (Fireworks AI)", + "id": "amazon/openai.gpt-oss-safeguard-20b", + "name": "GPT OSS Safeguard 20B (Amazon Bedrock)", + "display_name": "GPT OSS Safeguard 20B (Amazon Bedrock)", "modalities": { "input": [ - "text", - "image", - "video", - "pdf", - "audio" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, + "context": 128000, "output": 131072 }, "temperature": true, @@ -285554,22 +285918,20 @@ "effort_options": [ "low", "medium", - "high", - "xhigh" + "high" ] } }, - "attachment": true, + "attachment": false, "open_weights": true, - "knowledge": "2026-01-04", - "release_date": "2026-10-15", - "last_updated": "2026-10-15", + "release_date": "2025-10-29", + "last_updated": "2025-10-29", "type": "chat" }, { - "id": "fireworks_ai/accounts/fireworks/models/gpt-oss-120b", - "name": "GPT OSS 120B (Fireworks AI)", - "display_name": "GPT OSS 120B (Fireworks AI)", + "id": "amazon/zai.glm-4.7-flash", + "name": "GLM-4.7-Flash (Amazon Bedrock)", + "display_name": "GLM-4.7-Flash (Amazon Bedrock)", "modalities": { "input": [ "text" @@ -285579,8 +285941,8 @@ ] }, "limit": { - "context": 131072, - "output": 32768 + "context": 203000, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -285588,27 +285950,17 @@ "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "effort_options": [ - "low", - "medium", - "high" - ] - } - }, "attachment": false, "open_weights": true, - "knowledge": "2024-06", - "release_date": "2025-08-05", - "last_updated": "2025-08-05", + "knowledge": "2025-04", + "release_date": "2026-01-19", + "last_updated": "2026-01-19", "type": "chat" }, { - "id": "fireworks_ai/accounts/fireworks/models/inkling", - "name": "Inkling (Fireworks AI)", - "display_name": "Inkling (Fireworks AI)", + "id": "amazon/mistral.pixtral-large-2502-v1:0", + "name": "Pixtral Large (25.02) (Amazon Bedrock)", + "display_name": "Pixtral Large (25.02) (Amazon Bedrock)", "modalities": { "input": [ "text", @@ -285619,37 +285971,24 @@ ] }, "limit": { - "context": 1048576, - "output": 1048576 + "context": 128000, + "output": 8192 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "none", - "minimal", - "low", - "medium", - "high", - "max" - ] - } + "supported": false }, "attachment": true, - "open_weights": true, - "release_date": "2026-07-15", - "last_updated": "2026-07-15", + "open_weights": false, + "release_date": "2025-04-08", + "last_updated": "2025-04-08", "type": "chat" }, { - "id": "cohere/command-a-03-2025", - "name": "Command A", - "display_name": "Command A", + "id": "amazon/moonshot.kimi-k2-thinking", + "name": "Kimi K2 Thinking (Amazon Bedrock)", + "display_name": "Kimi K2 Thinking (Amazon Bedrock)", "modalities": { "input": [ "text" @@ -285659,28 +285998,30 @@ ] }, "limit": { - "context": 288000, - "output": 8000 + "context": 256000, + "output": 262144 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, "attachment": false, "open_weights": true, - "knowledge": "2024-06-01", - "release_date": "2025-03-13", - "last_updated": "2025-03-13", + "knowledge": "2024-08", + "release_date": "2025-11-06", + "last_updated": "2025-11-06", "type": "chat" }, { - "id": "cohere/c4ai-aya-expanse-32b", - "name": "Aya Expanse 32B", - "display_name": "Aya Expanse 32B", + "id": "amazon/google.gemma-3-12b-it", + "name": "Gemma 3 12B IT (Amazon Bedrock)", + "display_name": "Gemma 3 12B IT (Amazon Bedrock)", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" @@ -285688,253 +286029,230 @@ }, "limit": { "context": 128000, - "output": 4000 + "output": 131072 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { "supported": false }, - "attachment": false, + "attachment": true, "open_weights": true, - "release_date": "2024-10-24", - "last_updated": "2024-10-24", + "knowledge": "2024-08", + "release_date": "2025-03-12", + "last_updated": "2025-03-12", "type": "chat" }, { - "id": "cohere/command-r-08-2024", - "name": "Command R", - "display_name": "Command R", + "id": "amazon/amazon.nova-pro-v1:0", + "name": "Nova Pro", + "display_name": "Nova Pro", "modalities": { "input": [ - "text" + "text", + "image", + "video", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 4000 + "context": 300000, + "output": 10000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": false }, - "attachment": false, - "open_weights": true, - "knowledge": "2024-06-01", - "release_date": "2024-08-30", - "last_updated": "2024-08-30", + "attachment": true, + "open_weights": false, + "knowledge": "2024-10", + "release_date": "2024-12-03", + "last_updated": "2024-12-03", "type": "chat" }, { - "id": "cohere/command-r7b-12-2024", - "name": "Command R7B", - "display_name": "Command R7B", + "id": "amazon/google.gemma-3-4b-it", + "name": "Gemma 3 4B IT (Amazon Bedrock)", + "display_name": "Gemma 3 4B IT (Amazon Bedrock)", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 132000, - "output": 4000 + "context": 128000, + "output": 131072 }, "temperature": true, "tool_call": true, "reasoning": { "supported": false }, - "attachment": false, + "attachment": true, "open_weights": true, - "knowledge": "2024-06-01", - "release_date": "2024-12-13", - "last_updated": "2024-12-13", + "knowledge": "2024-08", + "release_date": "2025-03-12", + "last_updated": "2025-03-12", "type": "chat" }, { - "id": "cohere/command-r-plus-08-2024", - "name": "Command R+", - "display_name": "Command R+", + "id": "amazon/mistral.voxtral-small-24b-2507", + "name": "Voxtral Small 24B 2507 (Amazon Bedrock)", + "display_name": "Voxtral Small 24B 2507 (Amazon Bedrock)", "modalities": { "input": [ - "text" + "text", + "audio" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 4000 + "context": 32768, + "output": 32768 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { "supported": false }, - "attachment": false, + "attachment": true, "open_weights": true, - "knowledge": "2024-06-01", - "release_date": "2024-08-30", - "last_updated": "2024-08-30", + "release_date": "2025-07-15", + "last_updated": "2025-07-15", "type": "chat" }, { - "id": "xai/grok-4.20-0309-reasoning", - "name": "Grok 4.20 (Reasoning)", - "display_name": "Grok 4.20 (Reasoning)", + "id": "amazon/amazon.nova-lite-v1:0", + "name": "Nova Lite", + "display_name": "Nova Lite", "modalities": { "input": [ "text", - "image" + "image", + "video", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 30000 + "context": 300000, + "output": 10000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true + "supported": false }, "attachment": true, "open_weights": false, - "release_date": "2026-03-09", - "last_updated": "2026-03-09", + "knowledge": "2024-10", + "release_date": "2024-12-03", + "last_updated": "2024-12-03", "type": "chat" }, { - "id": "xai/grok-4.5", - "name": "Grok 4.5", - "display_name": "Grok 4.5", + "id": "amazon/google.gemma-3-27b-it", + "name": "Gemma 3 27B IT (Amazon Bedrock)", + "display_name": "Gemma 3 27B IT (Amazon Bedrock)", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 500000, - "output": 500000 + "context": 128000, + "output": 131072 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "effort_options": [ - "low", - "medium", - "high", - "xhigh" - ] - } + "supported": false }, "attachment": true, - "open_weights": false, - "release_date": "2026-07-08", - "last_updated": "2026-07-08", + "open_weights": true, + "knowledge": "2024-08", + "release_date": "2025-03-12", + "last_updated": "2025-03-12", "type": "chat" }, { - "id": "xai/grok-build-0.1", - "name": "Grok Build 0.1", - "display_name": "Grok Build 0.1", + "id": "amazon/mistral.voxtral-mini-3b-2507", + "name": "Voxtral Mini 3B 2507 (Amazon Bedrock)", + "display_name": "Voxtral Mini 3B 2507 (Amazon Bedrock)", "modalities": { "input": [ "text", - "image", - "pdf" + "audio" ], "output": [ "text" ] }, "limit": { - "context": 256000, - "output": 256000 + "context": 32768, + "output": 32768 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true - } + "supported": false }, "attachment": true, - "open_weights": false, - "release_date": "2026-04-16", - "last_updated": "2026-04-16", + "open_weights": true, + "release_date": "2025-07-15", + "last_updated": "2025-07-15", "type": "chat" }, { - "id": "xai/grok-4.7", - "name": "Grok 4.7", - "display_name": "Grok 4.7", + "id": "amazon/moonshotai.kimi-k2.5", + "name": "Kimi K2.5 (Amazon Bedrock)", + "display_name": "Kimi K2.5 (Amazon Bedrock)", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 500000, - "output": 500000 + "context": 262144, + "output": 262144 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "low", - "medium", - "high", - "xhigh" - ] - } - }, "attachment": true, - "open_weights": false, - "knowledge": "2026-05", - "release_date": "2026-09-21", - "last_updated": "2026-09-21", + "open_weights": true, + "knowledge": "2025-01", + "release_date": "2026-01-27", + "last_updated": "2026-01-27", "type": "chat" }, { - "id": "xai/grok-4-fast-non-reasoning", - "name": "X-Ai/Grok-4-Fast-Non-Reasoning", - "display_name": "X-Ai/Grok-4-Fast-Non-Reasoning", + "id": "scaleway/deepseek-v4.1-flash-1017", + "name": "DeepSeek V4.1 Flash 1017 (Scaleway)", + "display_name": "DeepSeek V4.1 Flash 1017 (Scaleway)", "modalities": { "input": [ "text" @@ -285944,66 +286262,64 @@ ] }, "limit": { - "context": 2000000, - "output": 2000000 + "context": 1000000, + "output": 384000 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "none", + "low", + "high", + "max" + ] + } }, "attachment": false, - "open_weights": false, - "release_date": "2025-12-18", - "last_updated": "2025-12-18", + "open_weights": true, + "knowledge": "2025-05", + "release_date": "2026-10-17", + "last_updated": "2026-10-17", "type": "chat" }, { - "id": "xai/grok-4.3", - "name": "Grok 4.3", - "display_name": "Grok 4.3", + "id": "scaleway/llama-3.3-70b-instruct", + "name": "Llama-3.3-70B-Instruct (Scaleway)", + "display_name": "Llama-3.3-70B-Instruct (Scaleway)", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 30000 + "context": 128000, + "output": 4096 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "effort_options": [ - "none", - "low", - "medium", - "high", - "xhigh" - ] - } + "supported": false }, - "attachment": true, - "open_weights": false, - "release_date": "2026-04-17", - "last_updated": "2026-04-17", + "attachment": false, + "open_weights": true, + "knowledge": "2023-12", + "release_date": "2024-12-06", + "last_updated": "2024-12-06", "type": "chat" }, { - "id": "xai/grok-4.20-0309-non-reasoning", - "name": "Grok 4.20 (Non-Reasoning)", - "display_name": "Grok 4.20 (Non-Reasoning)", + "id": "scaleway/gemma-3-27b-it", + "name": "Gemma 3 27B IT (Scaleway)", + "display_name": "Gemma 3 27B IT (Scaleway)", "modalities": { "input": [ "text", @@ -286014,37 +286330,37 @@ ] }, "limit": { - "context": 1000000, - "output": 30000 + "context": 40000, + "output": 131072 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { "supported": false }, "attachment": true, - "open_weights": false, - "release_date": "2026-03-09", - "last_updated": "2026-03-09", + "open_weights": true, + "knowledge": "2024-08", + "release_date": "2025-03-12", + "last_updated": "2025-03-12", "type": "chat" }, { - "id": "xai/grok-latest", - "name": "Grok Latest (Grok 4.7)", - "display_name": "Grok Latest (Grok 4.7)", + "id": "scaleway/deepseek-v4.1-flash", + "name": "DeepSeek V4.1 Flash (Scaleway)", + "display_name": "DeepSeek V4.1 Flash (Scaleway)", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 500000, - "output": 500000 + "context": 256000, + "output": 384000 }, "temperature": true, "tool_call": true, @@ -286054,38 +286370,37 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, "effort_options": [ + "none", "low", - "medium", "high", - "xhigh" + "max" ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2026-05", - "release_date": "2026-09-21", - "last_updated": "2026-09-21", + "open_weights": true, + "knowledge": "2025-05", + "release_date": "2026-09-10", + "last_updated": "2026-09-10", "type": "chat" }, { - "id": "xai/grok-4.6", - "name": "Grok 4.6", - "display_name": "Grok 4.6", + "id": "scaleway/gpt-oss-120b", + "name": "GPT OSS 120B (Scaleway)", + "display_name": "GPT OSS 120B (Scaleway)", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 500000, - "output": 500000 + "context": 128000, + "output": 32768 }, "temperature": true, "tool_call": true, @@ -286099,95 +286414,89 @@ "effort_options": [ "low", "medium", - "high", - "xhigh" + "high" ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-02-01", - "release_date": "2026-08-12", - "last_updated": "2026-08-12", + "attachment": false, + "open_weights": true, + "knowledge": "2024-06", + "release_date": "2025-08-05", + "last_updated": "2025-08-05", "type": "chat" }, { - "id": "anthropic/claude-opus-4-5", - "name": "Claude Opus 4.5 (latest)", - "display_name": "Claude Opus 4.5 (latest)", + "id": "scaleway/qwen3-coder-30b-a3b-instruct", + "name": "Qwen3-Coder 30B-A3B Instruct (Scaleway)", + "display_name": "Qwen3-Coder 30B-A3B Instruct (Scaleway)", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 64000 + "context": 128000, + "output": 65536 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": false + "supported": false }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "mixed", - "budget": { - "min": 1024, - "unit": "tokens" - }, - "effort": "high", - "effort_options": [ - "low", - "medium", - "high" - ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Claude Opus 4.5 uses manual thinking.type = \"enabled\" with budget_tokens; effort can be used alongside the thinking budget.", - "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header." - ] - } + "attachment": false, + "open_weights": true, + "knowledge": "2025-04", + "release_date": "2025-07-31", + "last_updated": "2025-07-31", + "type": "chat" + }, + { + "id": "scaleway/qwen3-235b-a22b-instruct-2507", + "name": "Qwen3 235B-A22B Instruct 2507 (Scaleway)", + "display_name": "Qwen3 235B-A22B Instruct 2507 (Scaleway)", + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-05", - "release_date": "2025-11-24", - "last_updated": "2025-11-24", + "limit": { + "context": 250000, + "output": 16384 + }, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": false + }, + "attachment": false, + "open_weights": true, + "release_date": "2025-07-21", + "last_updated": "2025-07-21", "type": "chat" }, { - "id": "anthropic/claude-sonnet-5-5", - "name": "Claude Sonnet 5.5", - "display_name": "Claude Sonnet 5.5", + "id": "scaleway/deepseek-v4-flash-0731", + "name": "DeepSeek V4 Flash 0731 (Scaleway)", + "display_name": "DeepSeek V4 Flash 0731 (Scaleway)", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 256000, + "output": 384000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -286195,26 +286504,26 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, "effort_options": [ + "none", "low", - "medium", "high", - "xhigh", "max" ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-06", - "release_date": "2026-09-28", - "last_updated": "2026-09-28", + "attachment": false, + "open_weights": true, + "knowledge": "2025-05", + "release_date": "2026-07-31", + "last_updated": "2026-07-31", "type": "chat" }, { - "id": "anthropic/claude-haiku-5-5", - "name": "Claude Haiku 5.5", - "display_name": "Claude Haiku 5.5", + "id": "azure/gpt-5.1-codex-max", + "name": "GPT-5.1 Codex Max (Azure)", + "display_name": "GPT-5.1 Codex Max (Azure)", "modalities": { "input": [ "text", @@ -286226,38 +286535,47 @@ ] }, "limit": { - "context": 1000000, + "context": 272000, "output": 128000 }, "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": false, + "mode": "effort", + "effort": "none", "effort_options": [ "none", "low", "medium", - "high", - "xhigh", - "max" - ] + "high" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, "attachment": true, "open_weights": false, - "knowledge": "2026-06", - "release_date": "2026-10-07", - "last_updated": "2026-10-07", + "knowledge": "2024-09-30", + "release_date": "2025-11-13", + "last_updated": "2025-11-13", "type": "chat" }, { - "id": "anthropic/claude-opus-5", - "name": "Claude Opus 5", - "display_name": "Claude Opus 5", + "id": "azure/gpt-5.1-codex", + "name": "GPT-5.1 Codex (Azure)", + "display_name": "GPT-5.1 Codex (Azure)", "modalities": { "input": [ "text", @@ -286269,38 +286587,47 @@ ] }, "limit": { - "context": 1000000, + "context": 272000, "output": 128000 }, "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": false, + "mode": "effort", + "effort": "none", "effort_options": [ + "none", "low", "medium", - "high", - "xhigh", - "max" - ] + "high" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, "attachment": true, "open_weights": false, - "knowledge": "2026-05", - "release_date": "2026-07-24", - "last_updated": "2026-07-24", + "knowledge": "2024-09-30", + "release_date": "2025-11-13", + "last_updated": "2025-11-13", "type": "chat" }, { - "id": "anthropic/claude-opus-4-7", - "name": "Claude Opus 4.7", - "display_name": "Claude Opus 4.7", + "id": "azure/gpt-5.2-codex", + "name": "GPT-5.2 Codex (Azure)", + "display_name": "GPT-5.2 Codex (Azure)", "modalities": { "input": [ "text", @@ -286312,52 +286639,47 @@ ] }, "limit": { - "context": 1000000, + "context": 272000, "output": 128000 }, "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, + "default_enabled": true, "mode": "effort", - "effort": "high", + "effort": "medium", "effort_options": [ "low", "medium", "high", - "xhigh", - "max" + "xhigh" ], - "interleaved": true, - "summaries": true, - "visibility": "omitted", - "continuation": [ - "thinking_blocks" + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" ], - "notes": [ - "Claude Opus 4.7 and newer Opus models require thinking.type = \"adaptive\" to enable thinking explicitly.", - "Manual budget_tokens requests return 400 on Claude Opus 4.7 and newer adaptive-only Opus models.", - "task_budget is separate from thinking control and should not be treated as a thinking budget." - ] + "visibility": "hidden" } }, "attachment": true, "open_weights": false, - "knowledge": "2026-01-31", - "release_date": "2026-04-16", - "last_updated": "2026-04-16", + "knowledge": "2025-08-31", + "release_date": "2025-12-11", + "last_updated": "2025-12-11", "type": "chat" }, { - "id": "anthropic/claude-opus-5-5", - "name": "Claude Opus 5.5", - "display_name": "Claude Opus 5.5", + "id": "azure/gpt-4.1-nano", + "name": "GPT-4.1 nano (Azure)", + "display_name": "GPT-4.1 nano (Azure)", "modalities": { "input": [ "text", @@ -286369,37 +286691,25 @@ ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 1047576, + "output": 32768 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" - ] - } + "supported": false }, "attachment": true, "open_weights": false, - "knowledge": "2026-06", - "release_date": "2026-09-22", - "last_updated": "2026-09-22", + "knowledge": "2024-04", + "release_date": "2025-04-14", + "last_updated": "2025-04-14", "type": "chat" }, { - "id": "anthropic/claude-sonnet-5", - "name": "Claude Sonnet 5", - "display_name": "Claude Sonnet 5", + "id": "azure/gpt-5.1-codex-mini", + "name": "GPT-5.1 Codex mini (Azure)", + "display_name": "GPT-5.1 Codex mini (Azure)", "modalities": { "input": [ "text", @@ -286411,54 +286721,60 @@ ] }, "limit": { - "context": 1000000, + "context": 272000, "output": 128000 }, "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": false, + "mode": "effort", + "effort": "none", "effort_options": [ "none", "low", "medium", - "high", - "xhigh", - "max" - ] + "high" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, "attachment": true, "open_weights": false, - "knowledge": "2026-01-31", - "release_date": "2026-06-30", - "last_updated": "2026-06-30", + "knowledge": "2024-09-30", + "release_date": "2025-11-13", + "last_updated": "2025-11-13", "type": "chat" }, { - "id": "anthropic/claude-sonnet-latest", - "name": "Claude Sonnet Latest (Claude Sonnet 5.5)", - "display_name": "Claude Sonnet Latest (Claude Sonnet 5.5)", + "id": "cerebras/gpt-oss-120b", + "name": "GPT OSS 120B (Cerebras)", + "display_name": "GPT OSS 120B (Cerebras)", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 131072, + "output": 32768 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -286466,100 +286782,81 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, "effort_options": [ "low", "medium", - "high", - "xhigh", - "max" + "high" ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-06", - "release_date": "2026-09-28", - "last_updated": "2026-09-28", + "attachment": false, + "open_weights": true, + "knowledge": "2024-06", + "release_date": "2025-08-05", + "last_updated": "2025-08-05", "type": "chat" }, { - "id": "anthropic/claude-sonnet-4-6", - "name": "Claude Sonnet 4.6", - "display_name": "Claude Sonnet 4.6", + "id": "fireworks_ai/gpt-oss-120b", + "name": "GPT OSS 120B (Fireworks AI)", + "display_name": "GPT OSS 120B (Fireworks AI)", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 64000 + "context": 131072, + "output": 32768 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "mixed", - "budget": { - "min": 1024, - "unit": "tokens" - }, - "effort": "high", "effort_options": [ "low", "medium", - "high", - "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Anthropic recommends adaptive thinking with effort for Claude 4.6; budget_tokens remains a deprecated compatibility path.", - "Anthropic API defaults effort to high; lower effort levels should be chosen per workload." + "high" ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-02-17", - "last_updated": "2026-03-13", + "attachment": false, + "open_weights": true, + "knowledge": "2024-06", + "release_date": "2025-08-05", + "last_updated": "2025-08-05", "type": "chat" }, { - "id": "anthropic/claude-opus-latest", - "name": "Claude Opus Latest (Claude Opus 5.5)", - "display_name": "Claude Opus Latest (Claude Opus 5.5)", + "id": "fireworks_ai/accounts/fireworks/models/muse-spark-open", + "name": "Muse Spark Open (Fireworks AI)", + "display_name": "Muse Spark Open (Fireworks AI)", "modalities": { "input": [ "text", "image", - "pdf" + "video", + "pdf", + "audio" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 1048576, + "output": 131072 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -286571,37 +286868,34 @@ "low", "medium", "high", - "xhigh", - "max" + "xhigh" ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2026-06", - "release_date": "2026-09-22", - "last_updated": "2026-09-22", + "open_weights": true, + "knowledge": "2026-01-04", + "release_date": "2026-10-15", + "last_updated": "2026-10-15", "type": "chat" }, { - "id": "anthropic/claude-haiku-latest", - "name": "Claude Haiku Latest (Claude Haiku 5.5)", - "display_name": "Claude Haiku Latest (Claude Haiku 5.5)", + "id": "fireworks_ai/accounts/fireworks/models/gpt-oss-120b", + "name": "GPT OSS 120B (Fireworks AI)", + "display_name": "GPT OSS 120B (Fireworks AI)", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 131072, + "output": 32768 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -286609,101 +286903,80 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, "effort_options": [ - "none", "low", "medium", - "high", - "xhigh", - "max" + "high" ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-06", - "release_date": "2026-10-07", - "last_updated": "2026-10-07", + "attachment": false, + "open_weights": true, + "knowledge": "2024-06", + "release_date": "2025-08-05", + "last_updated": "2025-08-05", "type": "chat" }, { - "id": "anthropic/claude-opus-4-6", - "name": "Claude Opus 4.6", - "display_name": "Claude Opus 4.6", + "id": "fireworks_ai/accounts/fireworks/models/inkling", + "name": "Inkling (Fireworks AI)", + "display_name": "Inkling (Fireworks AI)", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 1048576, + "output": 1048576 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "mixed", - "budget": { - "min": 1024, - "unit": "tokens" - }, - "effort": "high", "effort_options": [ + "none", + "minimal", "low", "medium", "high", "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Anthropic recommends adaptive thinking with effort for Claude 4.6; budget_tokens remains a deprecated compatibility path.", - "Anthropic API defaults effort to high; lower effort levels should be chosen per workload." ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2025-05-31", - "release_date": "2026-02-05", - "last_updated": "2026-03-13", + "open_weights": true, + "release_date": "2026-07-15", + "last_updated": "2026-07-15", "type": "chat" }, { - "id": "anthropic/claude-fable-latest", - "name": "Claude Fable Latest (Claude Fable 5.1)", - "display_name": "Claude Fable Latest (Claude Fable 5.1)", + "id": "fireworks_ai/accounts/fireworks/models/ember-1", + "name": "Ember-1 (Fireworks AI)", + "display_name": "Ember-1 (Fireworks AI)", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 1048576, + "output": 131072 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -286711,46 +286984,30 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "high", "effort_options": [ + "none", "low", - "medium", "high", - "xhigh", "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "omitted", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Adaptive thinking is always on for Claude Fable 5 and Claude Mythos 5; thinking.type = \"disabled\" is rejected.", - "Manual budget_tokens requests return 400 on Claude Fable 5 and Claude Mythos 5.", - "thinking.display defaults to omitted; set display to summarized to receive readable thinking summaries." ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-06", - "release_date": "2026-09-01", - "last_updated": "2026-09-01", + "release_date": "2026-09-22", + "last_updated": "2026-09-22", "type": "chat" }, { - "id": "anthropic/claude-opus-4-8", - "name": "Claude Opus 4.8", - "display_name": "Claude Opus 4.8", + "id": "qwen/qwen3.8-omni-flash", + "name": "Qwen3.8 Omni Flash", + "display_name": "Qwen3.8 Omni Flash", "modalities": { "input": [ "text", "image", - "pdf" + "audio", + "video" ], "output": [ "text" @@ -286758,114 +287015,77 @@ }, "limit": { "context": 1000000, - "output": 128000 + "output": 131072 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "high", "effort_options": [ + "none", "low", "medium", - "high", - "xhigh", - "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "omitted", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Claude Opus 4.7 and newer Opus models require thinking.type = \"adaptive\" to enable thinking explicitly.", - "Manual budget_tokens requests return 400 on Claude Opus 4.7 and newer adaptive-only Opus models.", - "task_budget is separate from thinking control and should not be treated as a thinking budget." + "xhigh" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-01", - "release_date": "2026-05-28", - "last_updated": "2026-05-28", + "release_date": "2026-09-17", + "last_updated": "2026-09-17", "type": "chat" }, { - "id": "anthropic/claude-opus-4-5-20251101", - "name": "Claude Opus 4.5", - "display_name": "Claude Opus 4.5", + "id": "qwen/qwen3.8-2.4t-a95b", + "name": "Qwen3.8 2.4T A95B", + "display_name": "Qwen3.8 2.4T A95B", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 64000 + "context": 1000000, + "output": 131072 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "mixed", - "budget": { - "min": 1024, - "unit": "tokens" - }, - "effort": "high", "effort_options": [ "low", "medium", - "high" - ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Claude Opus 4.5 uses manual thinking.type = \"enabled\" with budget_tokens; effort can be used alongside the thinking budget.", - "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header." + "xhigh" ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-05", - "release_date": "2025-11-24", - "last_updated": "2025-11-24", + "attachment": false, + "open_weights": true, + "release_date": "2026-08-12", + "last_updated": "2026-08-12", "type": "chat" }, { - "id": "anthropic/claude-fable-5", - "name": "Claude Fable 5", - "display_name": "Claude Fable 5", + "id": "qwen/qwen3.8-flash", + "name": "Qwen3.8 Flash", + "display_name": "Qwen3.8 Flash", "modalities": { "input": [ "text", "image", - "pdf" + "video" ], "output": [ "text" @@ -286873,9 +287093,9 @@ }, "limit": { "context": 1000000, - "output": 128000 + "output": 131072 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -286884,45 +287104,28 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "high", "effort_options": [ + "none", "low", "medium", - "high", - "xhigh", - "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "omitted", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Adaptive thinking is always on for Claude Fable 5 and Claude Mythos 5; thinking.type = \"disabled\" is rejected.", - "Manual budget_tokens requests return 400 on Claude Fable 5 and Claude Mythos 5.", - "thinking.display defaults to omitted; set display to summarized to receive readable thinking summaries." + "xhigh" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-01-31", - "release_date": "2026-06-09", - "last_updated": "2026-06-09", + "release_date": "2026-08-26", + "last_updated": "2026-08-26", "type": "chat" }, { - "id": "anthropic/claude-fable-5-1", - "name": "Claude Fable 5.1", - "display_name": "Claude Fable 5.1", + "id": "qwen/deepseek-v4.1-flash-1017", + "name": "DeepSeek V4.1 Flash 1017 (Alibaba)", + "display_name": "DeepSeek V4.1 Flash 1017 (Alibaba)", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" @@ -286930,9 +287133,9 @@ }, "limit": { "context": 1000000, - "output": 128000 + "output": 384000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -286940,52 +287143,38 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "high", "effort_options": [ + "none", "low", - "medium", "high", - "xhigh", "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "omitted", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Adaptive thinking is always on for Claude Fable 5 and Claude Mythos 5; thinking.type = \"disabled\" is rejected.", - "Manual budget_tokens requests return 400 on Claude Fable 5 and Claude Mythos 5.", - "thinking.display defaults to omitted; set display to summarized to receive readable thinking summaries." ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2026-06", - "release_date": "2026-09-01", - "last_updated": "2026-09-01", + "open_weights": true, + "knowledge": "2025-05", + "release_date": "2026-10-17", + "last_updated": "2026-10-17", "type": "chat" }, { - "id": "greenpt/gpt-oss-120b", - "name": "GPT OSS 120B (Greenpt)", - "display_name": "GPT OSS 120B (Greenpt)", + "id": "qwen/qwen3.8-max-0902", + "name": "Qwen3.8 Max 0902", + "display_name": "Qwen3.8 Max 0902", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 32768 + "context": 1000000, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -286995,25 +287184,24 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, "effort_options": [ + "none", "low", "medium", - "high" + "xhigh" ] } }, - "attachment": false, - "open_weights": true, - "knowledge": "2024-06", - "release_date": "2025-08-05", - "last_updated": "2025-08-05", + "attachment": true, + "open_weights": false, + "release_date": "2026-09-02", + "last_updated": "2026-09-02", "type": "chat" }, { - "id": "greenpt/deepseek-v4.1-flash", - "name": "DeepSeek V4.1 Flash (Greenpt)", - "display_name": "DeepSeek V4.1 Flash (Greenpt)", + "id": "qwen/deepseek-v4.1-flash", + "name": "DeepSeek V4.1 Flash (Alibaba)", + "display_name": "DeepSeek V4.1 Flash (Alibaba)", "modalities": { "input": [ "text", @@ -287052,12 +287240,14 @@ "type": "chat" }, { - "id": "greenpt/deepseek-v4-flash-0731", - "name": "DeepSeek V4 Flash 0731 (Greenpt)", - "display_name": "DeepSeek V4 Flash 0731 (Greenpt)", + "id": "qwen/qwen3.8-27b", + "name": "Qwen3.8 27B", + "display_name": "Qwen3.8 27B", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" @@ -287065,7 +287255,7 @@ }, "limit": { "context": 1000000, - "output": 384000 + "output": 32768 }, "temperature": true, "tool_call": true, @@ -287075,26 +287265,24 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, "effort_options": [ "none", "low", - "high", - "max" + "medium", + "xhigh" ] } }, - "attachment": false, + "attachment": true, "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-07-31", - "last_updated": "2026-07-31", + "release_date": "2026-08-14", + "last_updated": "2026-08-14", "type": "chat" }, { - "id": "greenpt/llama-3.3-70b-instruct", - "name": "Llama-3.3-70B-Instruct (Greenpt)", - "display_name": "Llama-3.3-70B-Instruct (Greenpt)", + "id": "qwen/qwen3-coder-flash", + "name": "Qwen3 Coder Flash", + "display_name": "Qwen3 Coder Flash", "modalities": { "input": [ "text" @@ -287104,52 +287292,66 @@ ] }, "limit": { - "context": 128000, - "output": 4096 + "context": 1000000, + "output": 65536 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { "supported": false }, "attachment": false, - "open_weights": true, - "knowledge": "2023-12", - "release_date": "2024-12-06", - "last_updated": "2024-12-06", + "open_weights": false, + "knowledge": "2025-04", + "release_date": "2025-07-28", + "last_updated": "2025-07-28", "type": "chat" }, { - "id": "greenpt/qwen3-235b-a22b-instruct-2507", - "name": "Qwen3 235B-A22B Instruct 2507 (Greenpt)", - "display_name": "Qwen3 235B-A22B Instruct 2507 (Greenpt)", + "id": "qwen/qwen3.8-max", + "name": "Qwen3.8 Max", + "display_name": "Qwen3.8 Max", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 250000, - "output": 16384 + "context": 1000000, + "output": 131072 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, - "open_weights": true, - "release_date": "2025-07-21", - "last_updated": "2025-07-21", + "extra_capabilities": { + "reasoning": { + "supported": true, + "effort_options": [ + "none", + "low", + "medium", + "xhigh" + ] + } + }, + "attachment": true, + "open_weights": false, + "release_date": "2026-08-03", + "last_updated": "2026-08-03", "type": "chat" }, { - "id": "groq/openai/gpt-oss-20b", - "name": "GPT OSS 20B (Groq)", - "display_name": "GPT OSS 20B (Groq)", + "id": "qwen/deepseek-v4-pro-0813", + "name": "DeepSeek V4 Pro 0813 (Alibaba)", + "display_name": "DeepSeek V4 Pro 0813 (Alibaba)", "modalities": { "input": [ "text" @@ -287159,8 +287361,8 @@ ] }, "limit": { - "context": 131072, - "output": 32768 + "context": 1000000, + "output": 384000 }, "temperature": true, "tool_call": true, @@ -287172,23 +287374,23 @@ "reasoning": { "supported": true, "effort_options": [ + "none", "low", - "medium", - "high" + "high", + "max" ] } }, "attachment": false, "open_weights": true, - "knowledge": "2024-06", - "release_date": "2025-08-05", - "last_updated": "2025-08-05", + "release_date": "2026-08-13", + "last_updated": "2026-08-22", "type": "chat" }, { - "id": "groq/openai/gpt-oss-120b", - "name": "GPT OSS 120B (Groq)", - "display_name": "GPT OSS 120B (Groq)", + "id": "qwen/qwen-max", + "name": "Qwen Max", + "display_name": "Qwen Max", "modalities": { "input": [ "text" @@ -287198,36 +287400,25 @@ ] }, "limit": { - "context": 131072, - "output": 32768 + "context": 32768, + "output": 8192 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "effort_options": [ - "low", - "medium", - "high" - ] - } + "supported": false }, "attachment": false, - "open_weights": true, - "knowledge": "2024-06", - "release_date": "2025-08-05", - "last_updated": "2025-08-05", + "open_weights": false, + "knowledge": "2024-04", + "release_date": "2024-04-03", + "last_updated": "2025-01-25", "type": "chat" }, { - "id": "groq/openai/gpt-oss-safeguard-20b", - "name": "GPT OSS Safeguard 20B (Groq)", - "display_name": "GPT OSS Safeguard 20B (Groq)", + "id": "qwen/deepseek-v4-flash-0731", + "name": "DeepSeek V4 Flash 0731 (Alibaba)", + "display_name": "DeepSeek V4 Flash 0731 (Alibaba)", "modalities": { "input": [ "text" @@ -287237,8 +287428,8 @@ ] }, "limit": { - "context": 131072, - "output": 131072 + "context": 1000000, + "output": 384000 }, "temperature": true, "tool_call": true, @@ -287248,23 +287439,26 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, "effort_options": [ + "none", "low", - "medium", - "high" + "high", + "max" ] } }, "attachment": false, "open_weights": true, - "release_date": "2025-10-29", - "last_updated": "2025-10-29", + "knowledge": "2025-05", + "release_date": "2026-07-31", + "last_updated": "2026-07-31", "type": "chat" }, { - "id": "groq/meta-llama/llama-prompt-guard-2-22m", - "name": "Llama Prompt Guard 2 22M (Groq)", - "display_name": "Llama Prompt Guard 2 22M (Groq)", + "id": "qwen/qwen-mt-plus", + "name": "Qwen MT Plus", + "display_name": "Qwen MT Plus", "modalities": { "input": [ "text" @@ -287274,24 +287468,24 @@ ] }, "limit": { - "context": 512, - "output": 512 + "context": 4096, + "output": 8192 }, - "temperature": false, + "temperature": true, "tool_call": false, "reasoning": { "supported": false }, "attachment": false, - "open_weights": true, - "release_date": "2025-04-29", - "last_updated": "2025-04-29", + "open_weights": false, + "release_date": "2025-07-24", + "last_updated": "2025-07-24", "type": "chat" }, { - "id": "groq/meta-llama/llama-prompt-guard-2-86m", - "name": "Llama Prompt Guard 2 86M (Groq)", - "display_name": "Llama Prompt Guard 2 86M (Groq)", + "id": "ovhcloud/gpt-oss-120b", + "name": "GPT OSS 120B (OVHcloud)", + "display_name": "GPT OSS 120B (OVHcloud)", "modalities": { "input": [ "text" @@ -287301,24 +287495,36 @@ ] }, "limit": { - "context": 512, - "output": 512 + "context": 131072, + "output": 32768 }, - "temperature": false, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "effort_options": [ + "low", + "medium", + "high" + ] + } }, "attachment": false, "open_weights": true, - "release_date": "2025-04-29", - "last_updated": "2025-04-29", + "knowledge": "2024-06", + "release_date": "2025-08-05", + "last_updated": "2025-08-05", "type": "chat" }, { - "id": "microsoft/gpt-4", - "name": "GPT-4 (Microsoft)", - "display_name": "GPT-4 (Microsoft)", + "id": "ovhcloud/gpt-oss-20b", + "name": "GPT OSS 20B (OVHcloud)", + "display_name": "GPT OSS 20B (OVHcloud)", "modalities": { "input": [ "text" @@ -287328,25 +287534,36 @@ ] }, "limit": { - "context": 8191, - "output": 8192 + "context": 131072, + "output": 32768 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "effort_options": [ + "low", + "medium", + "high" + ] + } }, "attachment": false, - "open_weights": false, - "knowledge": "2023-11", - "release_date": "2023-11-06", - "last_updated": "2024-04-09", + "open_weights": true, + "knowledge": "2024-06", + "release_date": "2025-08-05", + "last_updated": "2025-08-05", "type": "chat" }, { - "id": "microsoft/o3-mini", - "name": "o3-mini (Microsoft)", - "display_name": "o3-mini (Microsoft)", + "id": "deepseek/deepseek-chat", + "name": "DeepSeek Chat", + "display_name": "DeepSeek Chat", "modalities": { "input": [ "text" @@ -287356,40 +287573,25 @@ ] }, "limit": { - "context": 200000, - "output": 100000 + "context": 131072, + "output": 384000 }, - "temperature": false, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", - "effort_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" - } + "supported": false }, "attachment": false, - "open_weights": false, - "knowledge": "2024-05", - "release_date": "2024-12-20", - "last_updated": "2025-01-29", + "open_weights": true, + "knowledge": "2025-09", + "release_date": "2025-12-01", + "last_updated": "2026-02-28", "type": "chat" }, { - "id": "deepseek/deepseek-v4-flash", - "name": "DeepSeek V4 Flash", - "display_name": "DeepSeek V4 Flash", + "id": "deepseek/deepseek-v4-flash-vision-exp", + "name": "DeepSeek V4 Flash Vision Exp", + "display_name": "DeepSeek V4 Flash Vision Exp", "modalities": { "input": [ "text", @@ -287433,9 +287635,8 @@ }, "attachment": true, "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-04-24", - "last_updated": "2026-04-24", + "release_date": "2026-08-21", + "last_updated": "2026-09-01", "type": "chat" }, { @@ -287490,9 +287691,9 @@ "type": "chat" }, { - "id": "deepseek/deepseek-v4-flash-vision-exp", - "name": "DeepSeek V4 Flash Vision Exp", - "display_name": "DeepSeek V4 Flash Vision Exp", + "id": "deepseek/deepseek-v4-flash", + "name": "DeepSeek V4 Flash", + "display_name": "DeepSeek V4 Flash", "modalities": { "input": [ "text", @@ -287536,57 +287737,68 @@ }, "attachment": true, "open_weights": true, - "release_date": "2026-08-21", - "last_updated": "2026-09-01", + "knowledge": "2025-05", + "release_date": "2026-04-24", + "last_updated": "2026-04-24", "type": "chat" }, { - "id": "deepseek/deepseek-chat", - "name": "DeepSeek Chat", - "display_name": "DeepSeek Chat", + "id": "moonshot/kimi-k3", + "name": "Kimi K3", + "display_name": "Kimi K3", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 384000 + "context": 1048576, + "output": 131072 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, + "extra_capabilities": { + "reasoning": { + "supported": true, + "effort_options": [ + "low", + "high", + "max" + ] + } + }, + "attachment": true, "open_weights": true, - "knowledge": "2025-09", - "release_date": "2025-12-01", - "last_updated": "2026-02-28", + "release_date": "2026-07-16", + "last_updated": "2026-07-16", "type": "chat" }, { - "id": "google/gemini-pro-latest", - "name": "Gemini Pro Latest (Gemini 4 Argon)", - "display_name": "Gemini Pro Latest (Gemini 4 Argon)", + "id": "moonshot/kimi-k2.7-code", + "name": "Kimi K2.7 Code", + "display_name": "Kimi K2.7 Code", "modalities": { "input": [ "text", "image", - "video", - "audio", - "pdf" + "video" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 262144, + "output": 262144 }, "temperature": false, "tool_call": true, @@ -287596,122 +287808,108 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "low", - "medium", - "high" - ] + "supported": true } }, "attachment": true, - "open_weights": false, - "knowledge": "2026-03", - "release_date": "2026-10-13", - "last_updated": "2026-10-13", + "open_weights": true, + "knowledge": "2025-01", + "release_date": "2026-06-12", + "last_updated": "2026-06-12", "type": "chat" }, { - "id": "google/gemini-3-flash-preview", - "name": "Gemini 3 Flash Preview", - "display_name": "Gemini 3 Flash Preview", + "id": "moonshot/kimi-k2.7-code-highspeed", + "name": "Kimi K2.7 Code Highspeed", + "display_name": "Kimi K2.7 Code Highspeed", "modalities": { "input": [ "text", "image", - "video", - "audio", - "pdf" + "video" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 262144, + "output": 262144 }, - "temperature": true, - "tool_call": true, + "temperature": false, + "tool_call": false, "reasoning": { "supported": true, "default": true }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "level", - "level": "high", - "level_options": [ - "minimal", - "low", - "medium", - "high" - ], - "summaries": true, - "visibility": "summary", - "continuation": [ - "thought_signatures" - ] + "supported": true } }, "attachment": true, - "open_weights": false, + "open_weights": true, "knowledge": "2025-01", - "release_date": "2025-12-17", - "last_updated": "2025-12-17", + "release_date": "2026-06-12", + "last_updated": "2026-06-12", "type": "chat" }, { - "id": "google/gemini-2.5-flash-image", - "name": "Nano Banana", - "display_name": "Nano Banana", + "id": "moonshot/kimi-k3.1-pro", + "name": "Kimi K3.1 Pro", + "display_name": "Kimi K3.1 Pro", "modalities": { "input": [ - "audio", - "image", "text", + "image", "video" ], "output": [ - "text", - "image" + "text" ] }, "limit": { - "context": 32768, - "output": 32768 + "context": 1048576, + "output": 131072 }, - "temperature": true, - "tool_call": false, + "temperature": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "low", + "high", + "max" + ] + } }, "attachment": true, "open_weights": false, - "knowledge": "2024-06", - "release_date": "2025-08-26", - "last_updated": "2025-08-26" + "release_date": "2026-10-23", + "last_updated": "2026-10-23", + "type": "chat" }, { - "id": "google/gemini-3.5-flash-lite", - "name": "Gemini 3.5 Flash Lite", - "display_name": "Gemini 3.5 Flash Lite", + "id": "moonshot/kimi-k2.6", + "name": "Kimi K2.6", + "display_name": "Kimi K2.6", "modalities": { "input": [ "text", "image", - "video", - "audio", - "pdf" + "video" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 262144, + "output": 262144 }, "temperature": true, "tool_call": true, @@ -287721,72 +287919,67 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "effort_options": [ - "minimal", - "low", - "medium", - "high" - ] + "supported": true } }, "attachment": true, - "open_weights": false, - "knowledge": "2026-03", - "release_date": "2026-07-21", - "last_updated": "2026-07-21", + "open_weights": true, + "knowledge": "2025-01", + "release_date": "2026-04-20", + "last_updated": "2026-04-20", "type": "chat" }, { - "id": "google/deep-research-max-preview-04-2026", - "name": "Deep Research Max Preview", - "display_name": "Deep Research Max Preview", + "id": "zai/glm-4.6v", + "name": "GLM-4.6V", + "display_name": "GLM-4.6V", "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 131072, + "output": 32768 }, - "temperature": false, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { "supported": true, "default": true }, "attachment": true, - "open_weights": false, - "knowledge": "2025-01", - "release_date": "2026-04-21", - "last_updated": "2026-04-21", + "open_weights": true, + "knowledge": "2025-04", + "release_date": "2025-12-08", + "last_updated": "2025-12-08", "type": "chat" }, { - "id": "google/gemini-3.1-flash-image", - "name": "Nano Banana 2", - "display_name": "Nano Banana 2", + "id": "zai/glm-5.3-flash", + "name": "GLM-5.3-Flash", + "display_name": "GLM-5.3-Flash", "modalities": { "input": [ + "text", "image", - "text" + "video" ], "output": [ - "text", - "image" + "text" ] }, "limit": { - "context": 65536, - "output": 32768 + "context": 1048576, + "output": 131072 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { "supported": true, "default": true @@ -287795,37 +287988,36 @@ "reasoning": { "supported": true, "effort_options": [ - "minimal", - "high" + "low", + "high", + "max" ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2025-01", - "release_date": "2026-05-28", - "last_updated": "2026-05-28" + "open_weights": true, + "release_date": "2026-08-26", + "last_updated": "2026-08-26", + "type": "chat" }, { - "id": "google/gemini-3-pro-image-preview", - "name": "Nano Banana Pro Preview", - "display_name": "Nano Banana Pro Preview", + "id": "zai/glm-4.7", + "name": "GLM-4.7", + "display_name": "GLM-4.7", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ - "text", - "image" + "text" ] }, "limit": { - "context": 65536, - "output": 32768 + "context": 202752, + "output": 131072 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { "supported": true, "default": true @@ -287833,45 +288025,36 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "level", - "level": "high", - "level_options": [ - "low", - "high" - ], + "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ - "thought_signatures" + "thinking_blocks" ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-01", - "release_date": "2025-11-20", - "last_updated": "2025-11-20" + "attachment": false, + "open_weights": true, + "knowledge": "2025-04", + "release_date": "2025-12-22", + "last_updated": "2025-12-22", + "type": "chat" }, { - "id": "google/gemini-3.1-flash-lite", - "name": "Gemini 3.1 Flash Lite", - "display_name": "Gemini 3.1 Flash Lite", + "id": "zai/glm-4.6", + "name": "GLM-4.6", + "display_name": "GLM-4.6", "modalities": { "input": [ - "text", - "image", - "video", - "audio", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 202752, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -287881,33 +288064,23 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "effort_options": [ - "minimal", - "low", - "medium", - "high" - ] + "supported": true } }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-01", - "release_date": "2026-05-07", - "last_updated": "2026-05-07", + "attachment": false, + "open_weights": true, + "knowledge": "2025-04", + "release_date": "2025-09-30", + "last_updated": "2025-09-30", "type": "chat" }, { - "id": "google/gemini-3.7-flash", - "name": "Gemini 3.7 Flash", - "display_name": "Gemini 3.7 Flash", + "id": "zai/glm-5.3", + "name": "GLM-5.3", + "display_name": "GLM-5.3", "modalities": { "input": [ - "text", - "image", - "video", - "audio", - "pdf" + "text" ], "output": [ "text" @@ -287915,7 +288088,7 @@ }, "limit": { "context": 1048576, - "output": 65536 + "output": 131072 }, "temperature": true, "tool_call": true, @@ -287926,125 +288099,108 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "level", - "level": "high", - "level_options": [ - "minimal", + "effort_options": [ "low", - "medium", - "high" - ], - "summaries": true, - "visibility": "summary", - "continuation": [ - "thought_signatures" + "high", + "max" ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-03", - "release_date": "2026-08-13", - "last_updated": "2026-08-13", + "attachment": false, + "open_weights": true, + "release_date": "2026-08-14", + "last_updated": "2026-08-14", "type": "chat" }, { - "id": "google/deep-research-preview-04-2026", - "name": "Gemini Deep Research Preview", - "display_name": "Gemini Deep Research Preview", + "id": "zai/glm-5", + "name": "GLM-5", + "display_name": "GLM-5", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 202752, + "output": 131072 }, - "temperature": false, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { "supported": true, "default": true }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-01", - "release_date": "2026-04-21", - "last_updated": "2026-04-21", + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } + }, + "attachment": false, + "open_weights": true, + "release_date": "2026-02-12", + "last_updated": "2026-02-12", "type": "chat" }, { - "id": "google/gemini-3-pro-image", - "name": "Nano Banana Pro", - "display_name": "Nano Banana Pro", + "id": "zai/glm-5.1", + "name": "GLM-5.1", + "display_name": "GLM-5.1", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ - "text", - "image" + "text" ] }, "limit": { - "context": 65536, - "output": 32768 + "context": 202752, + "output": 131072 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { "supported": true, "default": true }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "level", - "level": "high", - "level_options": [ - "low", - "high" - ], - "summaries": true, - "visibility": "summary", - "continuation": [ - "thought_signatures" - ] + "supported": true } }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-01", - "release_date": "2026-05-28", - "last_updated": "2026-05-28" + "attachment": false, + "open_weights": true, + "release_date": "2026-04-07", + "last_updated": "2026-04-07", + "type": "chat" }, { - "id": "google/gemini-3.1-flash-lite-preview", - "name": "Gemini 3.1 Flash Lite Preview", - "display_name": "Gemini 3.1 Flash Lite Preview", + "id": "zai/glm-5v-turbo", + "name": "GLM-5V-Turbo", + "display_name": "GLM-5V-Turbo", "modalities": { "input": [ - "text", "image", - "video", - "audio", - "pdf" + "text", + "video" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 202752, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -288052,42 +288208,27 @@ "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "minimal", - "low", - "medium", - "high" - ] - } - }, "attachment": true, "open_weights": false, - "knowledge": "2025-01", - "release_date": "2026-03-03", - "last_updated": "2026-03-03", + "release_date": "2026-04-01", + "last_updated": "2026-04-01", "type": "chat" }, { - "id": "google/gemini-robotics-er-2-preview", - "name": "Gemini Robotics-ER 2 Preview", - "display_name": "Gemini Robotics-ER 2 Preview", + "id": "zai/glm-5.2", + "name": "GLM-5.2", + "display_name": "GLM-5.2", "modalities": { "input": [ - "text", - "image", - "pdf", - "audio", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 65536 + "context": 1048576, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -288097,39 +288238,35 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, "effort_options": [ - "minimal", - "low", - "medium", - "high" + "none", + "high", + "max" ] } }, - "attachment": true, - "open_weights": false, - "release_date": "2026-07-30", - "last_updated": "2026-07-30", + "attachment": false, + "open_weights": true, + "release_date": "2026-06-13", + "last_updated": "2026-06-13", "type": "chat" }, { - "id": "google/gemini-3.1-pro-preview-customtools", - "name": "Gemini 3.1 Pro Preview Custom Tools", - "display_name": "Gemini 3.1 Pro Preview Custom Tools", + "id": "zai/glm-5-turbo", + "name": "GLM-5-Turbo", + "display_name": "GLM-5-Turbo", "modalities": { "input": [ - "text", - "image", - "video", - "audio", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 202752, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -288139,32 +288276,19 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "level", - "level": "high", - "level_options": [ - "low", - "high" - ], - "summaries": true, - "visibility": "summary", - "continuation": [ - "thought_signatures" - ] + "supported": true } }, - "attachment": true, + "attachment": false, "open_weights": false, - "knowledge": "2025-01", - "release_date": "2026-02-19", - "last_updated": "2026-02-19", + "release_date": "2026-03-16", + "last_updated": "2026-03-16", "type": "chat" }, { - "id": "google/gemini-flash-latest", - "name": "Gemini Flash Latest (Gemini 3.8 Flash)", - "display_name": "Gemini Flash Latest (Gemini 3.8 Flash)", + "id": "vertex/gemini-3.5-flash", + "name": "Gemini 3.5 Flash (Vertex AI)", + "display_name": "Gemini 3.5 Flash (Vertex AI)", "modalities": { "input": [ "text", @@ -288189,27 +288313,38 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ + "supported": true, + "default_enabled": true, + "mode": "level", + "level": "high", + "level_options": [ + "minimal", "low", "medium", "high" + ], + "summaries": true, + "visibility": "summary", + "continuation": [ + "thought_signatures" ] } }, "attachment": true, "open_weights": false, - "release_date": "2026-09-02", - "last_updated": "2026-09-02", + "knowledge": "2025-01", + "release_date": "2026-05-19", + "last_updated": "2026-05-19", "type": "chat" }, { - "id": "google/gemini-3.1-flash-image-preview", - "name": "Nano Banana 2 Preview", - "display_name": "Nano Banana 2 Preview", + "id": "vertex/gemini-nano-banana-2.1", + "name": "Nano Banana 2.1 (Vertex AI)", + "display_name": "Nano Banana 2.1 (Vertex AI)", "modalities": { "input": [ - "image", - "text" + "text", + "image" ], "output": [ "text", @@ -288217,10 +288352,10 @@ ] }, "limit": { - "context": 65536, - "output": 65536 + "context": 131072, + "output": 32768 }, - "temperature": true, + "temperature": false, "tool_call": false, "reasoning": { "supported": true, @@ -288228,28 +288363,26 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, "effort_options": [ - "none", "minimal", + "medium", "high" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-01", - "release_date": "2026-02-26", - "last_updated": "2026-02-26" + "release_date": "2026-10-06", + "last_updated": "2026-10-06" }, { - "id": "google/gemini-3.1-flash-lite-image", - "name": "Nano Banana 2 Lite", - "display_name": "Nano Banana 2 Lite", + "id": "vertex/gemini-3.1-flash-image", + "name": "Nano Banana 2 (Vertex AI)", + "display_name": "Nano Banana 2 (Vertex AI)", "modalities": { "input": [ - "text", - "image" + "image", + "text" ], "output": [ "text", @@ -288257,8 +288390,8 @@ ] }, "limit": { - "context": 65536, - "output": 4096 + "context": 131072, + "output": 32768 }, "temperature": true, "tool_call": false, @@ -288278,13 +288411,13 @@ "attachment": true, "open_weights": false, "knowledge": "2025-01", - "release_date": "2026-06-30", - "last_updated": "2026-06-30" + "release_date": "2026-05-28", + "last_updated": "2026-05-28" }, { - "id": "google/gemini-3.1-pro-preview", - "name": "Gemini 3.1 Pro Preview", - "display_name": "Gemini 3.1 Pro Preview", + "id": "vertex/gemini-4-argon", + "name": "Gemini 4 Argon (Vertex AI)", + "display_name": "Gemini 4 Argon (Vertex AI)", "modalities": { "input": [ "text", @@ -288301,7 +288434,7 @@ "context": 1048576, "output": 65536 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -288309,50 +288442,40 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "level", - "level": "high", - "level_options": [ + "effort_options": [ "low", + "medium", "high" - ], - "summaries": true, - "visibility": "summary", - "continuation": [ - "thought_signatures" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-01", - "release_date": "2026-02-19", - "last_updated": "2026-02-19", + "knowledge": "2026-03", + "release_date": "2026-10-13", + "last_updated": "2026-10-13", "type": "chat" }, { - "id": "google/gemini-3.8-flash", - "name": "Gemini 3.8 Flash", - "display_name": "Gemini 3.8 Flash", + "id": "vertex/gemini-3-pro-image", + "name": "Nano Banana Pro (Vertex AI)", + "display_name": "Nano Banana Pro (Vertex AI)", "modalities": { "input": [ "text", - "image", - "video", - "audio", - "pdf" + "image" ], "output": [ - "text" + "text", + "image" ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 65536, + "output": 32768 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { "supported": true, "default": true @@ -288364,9 +288487,7 @@ "mode": "level", "level": "high", "level_options": [ - "minimal", "low", - "medium", "high" ], "summaries": true, @@ -288378,14 +288499,14 @@ }, "attachment": true, "open_weights": false, - "release_date": "2026-09-02", - "last_updated": "2026-09-02", - "type": "chat" + "knowledge": "2025-01", + "release_date": "2026-05-28", + "last_updated": "2026-05-28" }, { - "id": "google/gemini-4-argon", - "name": "Gemini 4 Argon", - "display_name": "Gemini 4 Argon", + "id": "vertex/gemini-flash-latest", + "name": "Gemini Flash Latest (Gemini 3.8 Flash, Vertex AI)", + "display_name": "Gemini Flash Latest (Gemini 3.8 Flash, Vertex AI)", "modalities": { "input": [ "text", @@ -288402,7 +288523,7 @@ "context": 1048576, "output": 65536 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -288419,15 +288540,14 @@ }, "attachment": true, "open_weights": false, - "knowledge": "2026-03", - "release_date": "2026-10-13", - "last_updated": "2026-10-13", + "release_date": "2026-09-02", + "last_updated": "2026-09-02", "type": "chat" }, { - "id": "google/gemini-3.6-flash", - "name": "Gemini 3.6 Flash", - "display_name": "Gemini 3.6 Flash", + "id": "vertex/gemini-3.6-flash", + "name": "Gemini 3.6 Flash (Vertex AI)", + "display_name": "Gemini 3.6 Flash (Vertex AI)", "modalities": { "input": [ "text", @@ -288477,9 +288597,9 @@ "type": "chat" }, { - "id": "google/gemini-3.5-flash", - "name": "Gemini 3.5 Flash", - "display_name": "Gemini 3.5 Flash", + "id": "vertex/gemini-3.8-flash", + "name": "Gemini 3.8 Flash (Vertex AI)", + "display_name": "Gemini 3.8 Flash (Vertex AI)", "modalities": { "input": [ "text", @@ -288523,19 +288643,18 @@ }, "attachment": true, "open_weights": false, - "knowledge": "2025-01", - "release_date": "2026-05-19", - "last_updated": "2026-05-19", + "release_date": "2026-09-02", + "last_updated": "2026-09-02", "type": "chat" }, { - "id": "google/gemini-nano-banana-2.1", - "name": "Nano Banana 2.1", - "display_name": "Nano Banana 2.1", + "id": "vertex/gemini-3.1-flash-lite-image", + "name": "Nano Banana 2 Lite (Vertex AI)", + "display_name": "Nano Banana 2 Lite (Vertex AI)", "modalities": { "input": [ - "image", - "text" + "text", + "image" ], "output": [ "text", @@ -288544,53 +288663,57 @@ }, "limit": { "context": 65536, - "output": 32768 + "output": 4096 }, - "temperature": false, - "tool_call": true, + "temperature": true, + "tool_call": false, "reasoning": { "supported": true, "default": true }, "extra_capabilities": { "reasoning": { + "supported": true, "effort_options": [ "minimal", - "medium", "high" ] } }, "attachment": true, "open_weights": false, - "release_date": "2026-10-06", - "last_updated": "2026-10-06" + "knowledge": "2025-01", + "release_date": "2026-06-30", + "last_updated": "2026-06-30" }, { - "id": "greenference/gpt-oss-20b", - "name": "GPT OSS 20B (Greenference)", - "display_name": "GPT OSS 20B (Greenference)", + "id": "vertex/gemini-pro-latest", + "name": "Gemini Pro Latest (Gemini 4 Argon, Vertex AI)", + "display_name": "Gemini Pro Latest (Gemini 4 Argon, Vertex AI)", "modalities": { "input": [ - "text" + "text", + "image", + "video", + "audio", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 32768 + "context": 1048576, + "output": 65536 }, - "temperature": true, - "tool_call": false, + "temperature": false, + "tool_call": true, "reasoning": { "supported": true, "default": true }, "extra_capabilities": { "reasoning": { - "supported": true, "effort_options": [ "low", "medium", @@ -288598,29 +288721,32 @@ ] } }, - "attachment": false, - "open_weights": true, - "knowledge": "2024-06", - "release_date": "2025-08-05", - "last_updated": "2025-08-05", + "attachment": true, + "open_weights": false, + "knowledge": "2026-03", + "release_date": "2026-10-13", + "last_updated": "2026-10-13", "type": "chat" }, { - "id": "together_ai/meta-models/Muse-Glimmer-30B", - "name": "Muse Glimmer 30B (Together AI)", - "display_name": "Muse Glimmer 30B (Together AI)", + "id": "vertex/gemini-3.1-pro-preview", + "name": "Gemini 3.1 Pro Preview (Vertex AI)", + "display_name": "Gemini 3.1 Pro Preview (Vertex AI)", "modalities": { "input": [ "text", - "image" + "image", + "video", + "audio", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 131072 + "context": 1048576, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -288630,38 +288756,47 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ + "supported": true, + "default_enabled": true, + "mode": "level", + "level": "high", + "level_options": [ "low", - "medium", - "high", - "xhigh" + "high" + ], + "summaries": true, + "visibility": "summary", + "continuation": [ + "thought_signatures" ] } }, "attachment": true, - "open_weights": true, - "knowledge": "2026-01-04", - "release_date": "2026-08-10", - "last_updated": "2026-08-10", + "open_weights": false, + "knowledge": "2025-01", + "release_date": "2026-02-19", + "last_updated": "2026-02-19", "type": "chat" }, { - "id": "together_ai/thinkingmachines/Inkling", - "name": "Inkling (Together AI)", - "display_name": "Inkling (Together AI)", + "id": "vertex/gemini-3.7-flash", + "name": "Gemini 3.7 Flash (Vertex AI)", + "display_name": "Gemini 3.7 Flash (Vertex AI)", "modalities": { "input": [ "text", "image", - "audio" + "video", + "audio", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 524288, - "output": 1048576 + "context": 1048576, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -288671,37 +288806,49 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "none", + "supported": true, + "default_enabled": true, + "mode": "level", + "level": "high", + "level_options": [ "minimal", "low", "medium", - "high", - "max" + "high" + ], + "summaries": true, + "visibility": "summary", + "continuation": [ + "thought_signatures" ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-07-15", - "last_updated": "2026-07-15", + "open_weights": false, + "knowledge": "2026-03", + "release_date": "2026-08-13", + "last_updated": "2026-08-13", "type": "chat" }, { - "id": "together_ai/openai/gpt-oss-120b", - "name": "GPT OSS 120B (Together AI)", - "display_name": "GPT OSS 120B (Together AI)", + "id": "vertex/gemini-3.1-flash-lite", + "name": "Gemini 3.1 Flash Lite (Vertex AI)", + "display_name": "Gemini 3.1 Flash Lite (Vertex AI)", "modalities": { "input": [ - "text" + "text", + "image", + "video", + "audio", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 32768 + "context": 1048576, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -288713,26 +288860,31 @@ "reasoning": { "supported": true, "effort_options": [ + "minimal", "low", "medium", "high" ] } }, - "attachment": false, - "open_weights": true, - "knowledge": "2024-06", - "release_date": "2025-08-05", - "last_updated": "2025-08-05", + "attachment": true, + "open_weights": false, + "knowledge": "2025-01", + "release_date": "2026-05-07", + "last_updated": "2026-05-07", "type": "chat" }, { - "id": "together_ai/reflection/Beam-1.0-501B-A23B", - "name": "Beam 1.0 501B A23B (Together AI)", - "display_name": "Beam 1.0 501B A23B (Together AI)", + "id": "vertex/gemini-3-flash-preview", + "name": "Gemini 3 Flash Preview (Vertex AI)", + "display_name": "Gemini 3 Flash Preview (Vertex AI)", "modalities": { "input": [ - "text" + "text", + "image", + "video", + "audio", + "pdf" ], "output": [ "text" @@ -288740,7 +288892,7 @@ }, "limit": { "context": 1048576, - "output": 131072 + "output": 65536 }, "temperature": true, "tool_call": true, @@ -288750,70 +288902,70 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ + "supported": true, + "default_enabled": true, + "mode": "level", + "level": "high", + "level_options": [ + "minimal", "low", "medium", - "high", - "xhigh", - "max" + "high" + ], + "summaries": true, + "visibility": "summary", + "continuation": [ + "thought_signatures" ] } }, - "attachment": false, - "open_weights": true, - "knowledge": "2026-06-30", - "release_date": "2026-10-05", - "last_updated": "2026-10-16", + "attachment": true, + "open_weights": false, + "knowledge": "2025-01", + "release_date": "2025-12-17", + "last_updated": "2025-12-17", "type": "chat" }, { - "id": "together_ai/deepseek-ai/DeepSeek-V4.1-Flash", - "name": "DeepSeek V4.1 Flash (Together AI)", - "display_name": "DeepSeek V4.1 Flash (Together AI)", + "id": "vertex/gemini-2.5-flash-image", + "name": "Nano Banana (Vertex AI)", + "display_name": "Nano Banana (Vertex AI)", "modalities": { "input": [ "text", "image" ], "output": [ - "text" + "text", + "image" ] }, "limit": { - "context": 1048576, - "output": 384000 + "context": 32768, + "output": 32768 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "effort_options": [ - "none", - "low", - "high", - "max" - ] - } + "supported": false }, "attachment": true, - "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-09-10", - "last_updated": "2026-09-10", - "type": "chat" + "open_weights": false, + "knowledge": "2024-06", + "release_date": "2025-08-26", + "last_updated": "2025-08-26" }, { - "id": "together_ai/deepseek-ai/DeepSeek-V4-Pro-0813", - "name": "DeepSeek V4 Pro 0813 (Together AI)", - "display_name": "DeepSeek V4 Pro 0813 (Together AI)", + "id": "vertex/gemini-3.5-flash-lite", + "name": "Gemini 3.5 Flash Lite (Vertex AI)", + "display_name": "Gemini 3.5 Flash Lite (Vertex AI)", "modalities": { "input": [ - "text" + "text", + "image", + "video", + "audio", + "pdf" ], "output": [ "text" @@ -288821,7 +288973,7 @@ }, "limit": { "context": 1048576, - "output": 384000 + "output": 65536 }, "temperature": true, "tool_call": true, @@ -288833,27 +288985,29 @@ "reasoning": { "supported": true, "effort_options": [ - "none", + "minimal", "low", - "high", - "max" + "medium", + "high" ] } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-08-13", - "last_updated": "2026-08-22", + "attachment": true, + "open_weights": false, + "knowledge": "2026-03", + "release_date": "2026-07-21", + "last_updated": "2026-07-21", "type": "chat" }, { - "id": "together_ai/deepseek-ai/DeepSeek-V4.1-Flash-1017", - "name": "DeepSeek V4.1 Flash 1017 (Together AI)", - "display_name": "DeepSeek V4.1 Flash 1017 (Together AI)", + "id": "meta/muse-spark-1.1", + "name": "Muse Spark 1.1 (Meta)", + "display_name": "Muse Spark 1.1 (Meta)", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" @@ -288861,7 +289015,7 @@ }, "limit": { "context": 1048576, - "output": 384000 + "output": 131072 }, "temperature": true, "tool_call": true, @@ -288871,25 +289025,26 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, "effort_options": [ - "none", + "minimal", "low", + "medium", "high", - "max" + "xhigh" ] } }, "attachment": true, - "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-10-17", - "last_updated": "2026-10-17", + "open_weights": false, + "release_date": "2026-04-08", + "last_updated": "2026-07-09", "type": "chat" }, { - "id": "openai/gpt-5-search-api", - "name": "GPT-5 Search API", - "display_name": "GPT-5 Search API", + "id": "meta/muse-wonder-1.0", + "name": "Muse Wonder (Meta)", + "display_name": "Muse Wonder (Meta)", "modalities": { "input": [ "text", @@ -288901,24 +289056,37 @@ ] }, "limit": { - "context": 272000, - "output": 100000 + "context": 1048576, + "output": 131072 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "minimal", + "low", + "medium", + "high", + "xhigh", + "max" + ] + } }, "attachment": true, "open_weights": false, - "release_date": "2025-10-14", - "last_updated": "2025-10-14", + "release_date": "2026-10-12", + "last_updated": "2026-10-12", "type": "chat" }, { - "id": "openai/gpt-5", - "name": "GPT-5", - "display_name": "GPT-5", + "id": "meta/muse-spark-1.2", + "name": "Muse Spark 1.2 (Meta)", + "display_name": "Muse Spark 1.2 (Meta)", "modalities": { "input": [ "text", @@ -288930,10 +289098,10 @@ ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 1048576, + "output": 131072 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -288942,35 +289110,25 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", "effort_options": [ "minimal", "low", "medium", - "high" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + "high", + "xhigh" + ] } }, "attachment": true, "open_weights": false, - "knowledge": "2024-09-30", - "release_date": "2025-08-07", - "last_updated": "2025-08-07", + "release_date": "2026-08-05", + "last_updated": "2026-08-05", "type": "chat" }, { - "id": "openai/gpt-4o-mini", - "name": "GPT-4o mini", - "display_name": "GPT-4o mini", + "id": "meta/muse-spark-1.3", + "name": "Muse Spark 1.3 (Meta)", + "display_name": "Muse Spark 1.3 (Meta)", "modalities": { "input": [ "text", @@ -288982,40 +289140,52 @@ ] }, "limit": { - "context": 128000, - "output": 16384 + "context": 1048576, + "output": 131072 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "effort_options": [ + "minimal", + "low", + "medium", + "high", + "xhigh", + "max" + ] + } }, "attachment": true, "open_weights": false, - "knowledge": "2023-09", - "release_date": "2024-07-18", - "last_updated": "2024-07-18", + "release_date": "2026-09-02", + "last_updated": "2026-09-02", "type": "chat" }, { - "id": "openai/gpt-5.5", - "name": "GPT-5.5", - "display_name": "GPT-5.5", + "id": "flexai/Step-3.7-Flash", + "name": "Step 3.7 Flash (FlexAI)", + "display_name": "Step 3.7 Flash (FlexAI)", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 262144, + "output": 256000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -289023,51 +289193,38 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", "effort_options": [ - "low", - "medium", - "high", - "xhigh" - ], - "verbosity": "medium", - "verbosity_options": [ "low", "medium", "high" - ], - "visibility": "hidden" + ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2025-12-01", - "release_date": "2026-04-23", - "last_updated": "2026-04-23", + "open_weights": true, + "knowledge": "2026-03-01", + "release_date": "2026-05-29", + "last_updated": "2026-05-29", "type": "chat" }, { - "id": "openai/gpt-6-astra", - "name": "GPT-6 Astra", - "display_name": "GPT-6 Astra", + "id": "flexai/DeepSeek-V4.1-Flash-1017", + "name": "DeepSeek V4.1 Flash 1017 (FlexAI)", + "display_name": "DeepSeek V4.1 Flash 1017 (FlexAI)", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 1048576, + "output": 384000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -289075,95 +289232,78 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, "effort_options": [ + "none", "low", - "medium", "high", - "xhigh", "max" ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2026-04-30", - "release_date": "2026-09-04", - "last_updated": "2026-09-04", + "open_weights": true, + "knowledge": "2025-05", + "release_date": "2026-10-17", + "last_updated": "2026-10-17", "type": "chat" }, { - "id": "openai/gpt-5.4", - "name": "GPT-5.4", - "display_name": "GPT-5.4", + "id": "flexai/gpt-oss-120b", + "name": "GPT OSS 120B (FlexAI)", + "display_name": "GPT OSS 120B (FlexAI)", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 131072, + "output": 32768 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "none", "effort_options": [ - "none", - "low", - "medium", - "high", - "xhigh" - ], - "verbosity": "medium", - "verbosity_options": [ "low", "medium", "high" - ], - "visibility": "hidden" + ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-03-05", - "last_updated": "2026-03-05", + "attachment": false, + "open_weights": true, + "knowledge": "2024-06", + "release_date": "2025-08-05", + "last_updated": "2025-08-05", "type": "chat" }, { - "id": "openai/gpt-mini-latest", - "name": "GPT Mini Latest (GPT-5.6 Luna)", - "display_name": "GPT Mini Latest (GPT-5.6 Luna)", + "id": "flexai/Muse-Glimmer-30B", + "name": "Muse Glimmer 30B (FlexAI)", + "display_name": "Muse Glimmer 30B (FlexAI)", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 131072, + "output": 131072 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -289172,71 +289312,37 @@ "extra_capabilities": { "reasoning": { "effort_options": [ - "none", "low", "medium", "high", - "xhigh", - "max" + "xhigh" ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2026-02-16", - "release_date": "2026-07-09", - "last_updated": "2026-07-09", + "open_weights": true, + "knowledge": "2026-01-04", + "release_date": "2026-08-10", + "last_updated": "2026-08-10", "type": "chat" }, { - "id": "openai/gpt-4o", - "name": "GPT-4o", - "display_name": "GPT-4o", + "id": "flexai/DeepSeek-V4-Flash-0731", + "name": "DeepSeek V4 Flash 0731 (FlexAI)", + "display_name": "DeepSeek V4 Flash 0731 (FlexAI)", "modalities": { "input": [ - "text", - "image", - "pdf" - ], - "output": [ "text" - ] - }, - "limit": { - "context": 128000, - "output": 16384 - }, - "temperature": true, - "tool_call": true, - "reasoning": { - "supported": false - }, - "attachment": true, - "open_weights": false, - "knowledge": "2023-09", - "release_date": "2024-05-13", - "last_updated": "2024-08-06", - "type": "chat" - }, - { - "id": "openai/gpt-5-pro", - "name": "GPT-5 Pro", - "display_name": "GPT-5 Pro", - "modalities": { - "input": [ - "image", - "text", - "pdf" ], "output": [ "text" ] }, "limit": { - "context": 400000, - "output": 272000 + "context": 1048576, + "output": 384000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -289245,44 +289351,38 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "fixed", - "effort": "high", - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + "effort_options": [ + "none", + "low", + "high", + "max" + ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2024-09-30", - "release_date": "2025-10-06", - "last_updated": "2025-10-06", + "attachment": false, + "open_weights": true, + "knowledge": "2025-05", + "release_date": "2026-07-31", + "last_updated": "2026-07-31", "type": "chat" }, { - "id": "openai/o3", - "name": "o3", - "display_name": "o3", + "id": "flexai/gpt-oss-20b", + "name": "GPT OSS 20B (FlexAI)", + "display_name": "GPT OSS 20B (FlexAI)", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 100000 + "context": 131072, + "output": 32768 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -289291,43 +289391,37 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", "effort_options": [ "low", "medium", "high" - ], - "visibility": "hidden" + ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2024-05", - "release_date": "2025-04-16", - "last_updated": "2025-04-16", + "attachment": false, + "open_weights": true, + "knowledge": "2024-06", + "release_date": "2025-08-05", + "last_updated": "2025-08-05", "type": "chat" }, { - "id": "openai/gpt-6-sol", - "name": "GPT-6 Sol", - "display_name": "GPT-6 Sol", + "id": "databricks/databricks-gpt-oss-120b", + "name": "GPT OSS 120B (Databricks)", + "display_name": "GPT OSS 120B (Databricks)", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 131072, + "output": 32768 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -289336,41 +289430,36 @@ "extra_capabilities": { "reasoning": { "effort_options": [ - "none", "low", "medium", - "high", - "xhigh", - "max" + "high" ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-04-20", - "release_date": "2026-09-22", - "last_updated": "2026-09-22", + "attachment": false, + "open_weights": true, + "knowledge": "2024-06", + "release_date": "2025-08-05", + "last_updated": "2025-08-05", "type": "chat" }, { - "id": "openai/gpt-5.6-sol", - "name": "GPT-5.6 Sol", - "display_name": "GPT-5.6 Sol", + "id": "databricks/databricks-gpt-oss-20b", + "name": "GPT OSS 20B (Databricks)", + "display_name": "GPT OSS 20B (Databricks)", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 131072, + "output": 32768 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -289378,83 +289467,65 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", "effort_options": [ - "none", - "low", - "medium", - "high", - "xhigh", - "max" - ], - "verbosity": "medium", - "verbosity_options": [ "low", "medium", "high" - ], - "visibility": "hidden" + ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-02-16", - "release_date": "2026-07-09", - "last_updated": "2026-07-09", + "attachment": false, + "open_weights": true, + "knowledge": "2024-06", + "release_date": "2025-08-05", + "last_updated": "2025-08-05", "type": "chat" }, { - "id": "openai/gpt-4o-2024-11-20", - "name": "GPT-4o (2024-11-20)", - "display_name": "GPT-4o (2024-11-20)", + "id": "databricks/databricks-qwen3-next-80b-a3b-instruct", + "name": "Qwen3-Next 80B-A3B Instruct (Databricks)", + "display_name": "Qwen3-Next 80B-A3B Instruct (Databricks)", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 16384 + "context": 262144, + "output": 32768 }, "temperature": true, "tool_call": true, "reasoning": { "supported": false }, - "attachment": true, - "open_weights": false, - "knowledge": "2023-09", - "release_date": "2024-11-20", - "last_updated": "2024-11-20", + "attachment": false, + "open_weights": true, + "knowledge": "2025-04", + "release_date": "2025-09", + "last_updated": "2025-09", "type": "chat" }, { - "id": "openai/gpt-5.6-terra", - "name": "GPT-5.6 Terra", - "display_name": "GPT-5.6 Terra", + "id": "databricks/databricks-deepseek-v4-flash-0731", + "name": "DeepSeek V4 Flash 0731 (Databricks)", + "display_name": "DeepSeek V4 Flash 0731 (Databricks)", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 1000000, + "output": 384000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -289462,42 +289533,39 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", "effort_options": [ "none", "low", - "medium", "high", - "xhigh", "max" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-02-16", - "release_date": "2026-07-09", - "last_updated": "2026-07-09", + "attachment": false, + "open_weights": true, + "knowledge": "2025-05", + "release_date": "2026-07-31", + "last_updated": "2026-07-31", "type": "chat" - }, + } + ] + }, + "tokengo": { + "id": "tokengo", + "name": "TokenGo", + "display_name": "TokenGo", + "api": "https://api.tokengo.com/v1", + "doc": "https://www.tokengo.com/docs", + "models": [ { - "id": "openai/gpt-6-luna", - "name": "GPT-6 Luna", - "display_name": "GPT-6 Luna", + "id": "z-ai/glm-5.3-flash", + "name": "GLM-5.3-Flash", + "display_name": "GLM-5.3-Flash", "modalities": { "input": [ "text", "image", + "video", "pdf" ], "output": [ @@ -289505,10 +289573,10 @@ ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 1000000, + "output": 131072 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -289516,42 +289584,37 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, "effort_options": [ - "none", "low", - "medium", "high", - "xhigh", "max" ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2026-05-18", - "release_date": "2026-09-22", - "last_updated": "2026-09-22", + "open_weights": true, + "release_date": "2026-08-26", + "last_updated": "2026-08-26", "type": "chat" }, { - "id": "openai/gpt-5-mini", - "name": "GPT-5 Mini", - "display_name": "GPT-5 Mini", + "id": "z-ai/glm-5.3", + "name": "GLM-5.3", + "display_name": "GLM-5.3", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 1000000, + "output": 131072 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -289560,80 +289623,36 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", "effort_options": [ - "minimal", "low", - "medium", - "high" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + "high", + "max" + ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2024-05-30", - "release_date": "2025-08-07", - "last_updated": "2025-08-07", + "attachment": false, + "open_weights": true, + "release_date": "2026-08-14", + "last_updated": "2026-08-14", "type": "chat" }, { - "id": "openai/gpt-4o-2024-08-06", - "name": "GPT-4o (2024-08-06)", - "display_name": "GPT-4o (2024-08-06)", + "id": "z-ai/glm-5", + "name": "GLM-5", + "display_name": "GLM-5", "modalities": { "input": [ - "text", - "image", - "pdf" - ], - "output": [ "text" - ] - }, - "limit": { - "context": 128000, - "output": 16384 - }, - "temperature": true, - "tool_call": true, - "reasoning": { - "supported": false - }, - "attachment": true, - "open_weights": false, - "knowledge": "2023-09", - "release_date": "2024-08-06", - "last_updated": "2024-08-06", - "type": "chat" - }, - { - "id": "openai/gpt-5.4-pro", - "name": "GPT-5.4 Pro", - "display_name": "GPT-5.4 Pro", - "modalities": { - "input": [ - "text", - "image", - "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 204800, + "output": 131072 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -289642,101 +289661,70 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "high", - "effort_options": [ - "medium", - "high", - "xhigh" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-03-05", - "last_updated": "2026-03-05", + "attachment": false, + "open_weights": true, + "release_date": "2026-02-12", + "last_updated": "2026-02-12", "type": "chat" }, { - "id": "openai/gpt-5.1", - "name": "GPT-5.1", - "display_name": "GPT-5.1", + "id": "z-ai/glm-5.1", + "name": "GLM-5.1", + "display_name": "GLM-5.1", "modalities": { "input": [ - "image", - "text", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 200000, + "output": 131072 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "none", - "effort_options": [ - "none", - "low", - "medium", - "high" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + "supported": true } }, - "attachment": true, - "open_weights": false, - "knowledge": "2024-09-30", - "release_date": "2025-11-13", - "last_updated": "2025-11-13", + "attachment": false, + "open_weights": true, + "release_date": "2026-04-07", + "last_updated": "2026-04-07", "type": "chat" }, { - "id": "openai/gpt-5.6-luna", - "name": "GPT-5.6 Luna", - "display_name": "GPT-5.6 Luna", + "id": "z-ai/glm-5.2", + "name": "GLM-5.2", + "display_name": "GLM-5.2", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 1000000, + "output": 131072 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -289745,41 +289733,24 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", "effort_options": [ - "none", - "low", - "medium", "high", - "xhigh", "max" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-02-16", - "release_date": "2026-07-09", - "last_updated": "2026-07-09", + "attachment": false, + "open_weights": true, + "release_date": "2026-06-13", + "last_updated": "2026-06-13", "type": "chat" }, { - "id": "openai/gpt-5.4-mini", - "name": "GPT-5.4 mini", - "display_name": "GPT-5.4 mini", + "id": "minimax/minimax-m2.5", + "name": "MiniMax-M2.5", + "display_name": "MiniMax-M2.5", "modalities": { "input": [ - "pdf", - "image", "text" ], "output": [ @@ -289787,61 +289758,43 @@ ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 204800, + "output": 131072 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "none", - "effort_options": [ - "none", - "low", - "medium", - "high", - "xhigh" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + "supported": true } }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-03-17", - "last_updated": "2026-03-17", + "attachment": false, + "open_weights": true, + "release_date": "2026-02-12", + "last_updated": "2026-02-12", "type": "chat" }, { - "id": "openai/gpt-pro-latest", - "name": "GPT Pro Latest (GPT-5.6 Sol)", - "display_name": "GPT Pro Latest (GPT-5.6 Sol)", + "id": "moonshotai/kimi-k3", + "name": "Kimi K3", + "display_name": "Kimi K3", "modalities": { "input": [ "text", "image", - "pdf" + "video" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 1048576, + "output": 131072 }, "temperature": false, "tool_call": true, @@ -289851,40 +289804,37 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, "effort_options": [ - "none", "low", - "medium", "high", - "xhigh", "max" ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2026-02-16", - "release_date": "2026-07-09", - "last_updated": "2026-07-09", + "open_weights": true, + "release_date": "2026-07-16", + "last_updated": "2026-07-16", "type": "chat" }, { - "id": "openai/gpt-5.3-codex", - "name": "GPT-5.3 Codex", - "display_name": "GPT-5.3 Codex", + "id": "moonshotai/kimi-k2.6", + "name": "Kimi K2.6", + "display_name": "Kimi K2.6", "modalities": { "input": [ "text", "image", - "pdf" + "video" ], "output": [ "text" ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 262144, + "output": 262144 }, "temperature": true, "tool_call": true, @@ -289894,51 +289844,35 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", - "effort_options": [ - "low", - "medium", - "high", - "xhigh" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + "supported": true } }, "attachment": true, - "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-02-05", - "last_updated": "2026-02-05", + "open_weights": true, + "knowledge": "2025-01", + "release_date": "2026-04-20", + "last_updated": "2026-04-20", "type": "chat" }, { - "id": "openai/gpt-latest", - "name": "GPT Latest (GPT-6 Astra)", - "display_name": "GPT Latest (GPT-6 Astra)", + "id": "qwen/qwen3.5-397b-a17b", + "name": "Qwen3.5 397B-A17B", + "display_name": "Qwen3.5 397B-A17B", "modalities": { "input": [ "text", "image", - "pdf" + "video" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 262144, + "output": 65536 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -289946,60 +289880,55 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2026-04-30", - "release_date": "2026-09-04", - "last_updated": "2026-09-04", + "open_weights": true, + "release_date": "2026-02-15", + "last_updated": "2026-02-15", "type": "chat" }, { - "id": "openai/gpt-4.1-mini", - "name": "GPT-4.1 mini", - "display_name": "GPT-4.1 mini", + "id": "deepseek/deepseek-v3.1", + "name": "DeepSeek-V3.1", + "display_name": "DeepSeek-V3.1", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1047576, - "output": 32768 + "context": 131072, + "output": 8192 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": true, - "open_weights": false, - "knowledge": "2024-04", - "release_date": "2025-04-14", - "last_updated": "2025-04-14", + "attachment": false, + "open_weights": true, + "release_date": "2025-08-21", + "last_updated": "2025-08-21", "type": "chat" }, { - "id": "openai/gpt-5.4-nano", - "name": "GPT-5.4 nano", - "display_name": "GPT-5.4 nano", + "id": "deepseek/deepseek-v3.2", + "name": "DeepSeek V3.2", + "display_name": "DeepSeek V3.2", "modalities": { "input": [ - "pdf", - "image", "text" ], "output": [ @@ -290007,63 +289936,44 @@ ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 128000, + "output": 64000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "none", - "effort_options": [ - "none", - "low", - "medium", - "high", - "xhigh" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + "supported": true } }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-03-17", - "last_updated": "2026-03-17", + "attachment": false, + "open_weights": true, + "knowledge": "2024-07", + "release_date": "2025-12-01", + "last_updated": "2025-12-01", "type": "chat" }, { - "id": "openai/o3-pro", - "name": "o3-pro", - "display_name": "o3-pro", + "id": "deepseek/deepseek-v4-pro", + "name": "DeepSeek V4 Pro", + "display_name": "DeepSeek V4 Pro", "modalities": { "input": [ - "text", - "pdf", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 100000 + "context": 1000000, + "output": 384000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -290074,41 +289984,47 @@ "supported": true, "default_enabled": true, "mode": "effort", - "effort": "medium", + "effort": "high", "effort_options": [ "low", - "medium", - "high" + "high", + "max" ], - "visibility": "hidden" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." + ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2024-05", - "release_date": "2025-06-10", - "last_updated": "2025-06-10", + "attachment": false, + "open_weights": true, + "knowledge": "2025-05", + "release_date": "2026-04-24", + "last_updated": "2026-04-24", "type": "chat" }, { - "id": "openai/gpt-5.5-pro", - "name": "GPT-5.5 Pro", - "display_name": "GPT-5.5 Pro", + "id": "deepseek/deepseek-v4-flash", + "name": "DeepSeek V4 Flash", + "display_name": "DeepSeek V4 Flash", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 1000000, + "output": 384000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -290117,39 +290033,58 @@ "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "high", "effort_options": [ - "medium", + "low", "high", - "xhigh" + "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-12-01", - "release_date": "2026-04-23", - "last_updated": "2026-04-23", + "attachment": false, + "open_weights": true, + "knowledge": "2025-05", + "release_date": "2026-04-24", + "last_updated": "2026-04-24", "type": "chat" - }, + } + ] + }, + "scnet-token-plan": { + "id": "scnet-token-plan", + "name": "SCNet Token Plan", + "display_name": "SCNet Token Plan", + "api": "https://api.scnet.cn/api/llm/v1", + "doc": "https://www.scnet.cn/ac/openapi/doc/2.0/moduleapi/plans/token-plan.html", + "models": [ { - "id": "openai/gpt-5.2-pro", - "name": "GPT-5.2 Pro", - "display_name": "GPT-5.2 Pro", + "id": "MiniMax-M2.5", + "name": "MiniMax-M2.5", + "display_name": "MiniMax-M2.5", "modalities": { "input": [ - "image", - "text", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 204800, + "output": 131072 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -290158,38 +290093,29 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "high", - "effort_options": [ - "medium", - "high", - "xhigh" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2025-12-11", - "last_updated": "2025-12-11", + "attachment": false, + "open_weights": true, + "release_date": "2026-02-12", + "last_updated": "2026-02-12", "type": "chat" }, { - "id": "openai/gpt-4.1", - "name": "GPT-4.1", - "display_name": "GPT-4.1", + "id": "GLM-5.3-Flash", + "name": "GLM-5.3-Flash", + "display_name": "GLM-5.3-Flash", "modalities": { "input": [ "text", "image", + "video", "pdf" ], "output": [ @@ -290197,40 +290123,49 @@ ] }, "limit": { - "context": 1047576, - "output": 32768 + "context": 1000000, + "output": 131072 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } }, "attachment": true, - "open_weights": false, - "knowledge": "2024-04", - "release_date": "2025-04-14", - "last_updated": "2025-04-14", + "open_weights": true, + "release_date": "2026-08-26", + "last_updated": "2026-08-26", "type": "chat" }, { - "id": "openai/gpt-6.1-terra", - "name": "GPT-6.1 Terra", - "display_name": "GPT-6.1 Terra", + "id": "DeepSeek-V4-Pro", + "name": "DeepSeek V4 Pro", + "display_name": "DeepSeek V4 Pro", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 1000000, + "output": 384000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -290238,95 +290173,91 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "high", "effort_options": [ - "none", "low", - "medium", "high", - "xhigh", "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-06-01", - "release_date": "2026-10-26", - "last_updated": "2026-10-26", + "attachment": false, + "open_weights": true, + "knowledge": "2025-05", + "release_date": "2026-04-24", + "last_updated": "2026-04-24", "type": "chat" }, { - "id": "openai/gpt-5.2", - "name": "GPT-5.2", - "display_name": "GPT-5.2", + "id": "Kimi-K2.5", + "name": "Kimi K2.5", + "display_name": "Kimi K2.5", "modalities": { "input": [ - "pdf", - "image", - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 262144, + "output": 262144 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "none", - "effort_options": [ - "none", - "low", - "medium", - "high", - "xhigh" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2025-12-11", - "last_updated": "2025-12-11", + "open_weights": true, + "knowledge": "2025-01", + "release_date": "2026-01-27", + "last_updated": "2026-01-27", "type": "chat" }, { - "id": "openai/gpt-6.1-sol", - "name": "GPT-6.1 Sol", - "display_name": "GPT-6.1 Sol", + "id": "GLM-5", + "name": "GLM-5", + "display_name": "GLM-5", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 204800, + "output": 131072 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -290334,41 +290265,38 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-04-30", - "release_date": "2026-09-29", - "last_updated": "2026-09-29", + "attachment": false, + "open_weights": true, + "release_date": "2026-02-12", + "last_updated": "2026-02-12", "type": "chat" }, { - "id": "openai/gpt-5-nano", - "name": "GPT-5 Nano", - "display_name": "GPT-5 Nano", + "id": "GLM-5.1", + "name": "GLM-5.1", + "display_name": "GLM-5.1", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 200000, + "output": 131072 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -290377,35 +290305,24 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", - "effort_options": [ - "minimal", - "low", - "medium", - "high" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2024-05-30", - "release_date": "2025-08-07", - "last_updated": "2025-08-07", + "attachment": false, + "open_weights": true, + "release_date": "2026-04-07", + "last_updated": "2026-04-07", "type": "chat" }, { - "id": "tencent/hy4-preview", - "name": "Hy4 preview (Tencent)", - "display_name": "Hy4 preview (Tencent)", + "id": "GLM-5.2", + "name": "GLM-5.2", + "display_name": "GLM-5.2", "modalities": { "input": [ "text" @@ -290416,7 +290333,7 @@ }, "limit": { "context": 1000000, - "output": 64000 + "output": 131072 }, "temperature": true, "tool_call": true, @@ -290427,27 +290344,27 @@ "extra_capabilities": { "reasoning": { "supported": true, - "effort_options": [ - "none", - "low", - "high" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, "attachment": false, "open_weights": true, - "release_date": "2026-08-28", - "last_updated": "2026-08-28", + "release_date": "2026-06-13", + "last_updated": "2026-06-13", "type": "chat" }, { - "id": "tencent/deepseek-v4.1-flash-1017", - "name": "DeepSeek V4.1 Flash 1017 (Tencent)", - "display_name": "DeepSeek V4.1 Flash 1017 (Tencent)", + "id": "GLM-5.3", + "name": "GLM-5.3", + "display_name": "GLM-5.3", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" @@ -290455,7 +290372,7 @@ }, "limit": { "context": 1000000, - "output": 384000 + "output": 131072 }, "temperature": true, "tool_call": true, @@ -290465,29 +290382,28 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "none", - "low", - "high", - "max" + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, - "attachment": true, + "attachment": false, "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-10-17", - "last_updated": "2026-10-17", + "release_date": "2026-08-14", + "last_updated": "2026-08-14", "type": "chat" }, { - "id": "tencent/deepseek-v4.1-flash", - "name": "DeepSeek V4.1 Flash (Tencent)", - "display_name": "DeepSeek V4.1 Flash (Tencent)", + "id": "DeepSeek-V4.1-Flash", + "name": "DeepSeek V4.1 Flash", + "display_name": "DeepSeek V4.1 Flash", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" @@ -290506,15 +290422,19 @@ "extra_capabilities": { "reasoning": { "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ - "none", - "low", "high", "max" ] } }, - "attachment": true, + "attachment": false, "open_weights": true, "knowledge": "2025-05", "release_date": "2026-09-10", @@ -290522,12 +290442,15 @@ "type": "chat" }, { - "id": "tencent/deepseek-v4-pro-0813", - "name": "DeepSeek V4 Pro 0813 (Tencent)", - "display_name": "DeepSeek V4 Pro 0813 (Tencent)", + "id": "Qwen3.8-Max", + "name": "Qwen3.8 Max", + "display_name": "Qwen3.8 Max", "modalities": { "input": [ - "text" + "text", + "image", + "video", + "pdf" ], "output": [ "text" @@ -290535,7 +290458,7 @@ }, "limit": { "context": 1000000, - "output": 384000 + "output": 131072 }, "temperature": true, "tool_call": true, @@ -290546,24 +290469,24 @@ "extra_capabilities": { "reasoning": { "supported": true, - "effort_options": [ - "none", - "low", - "high", - "max" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-08-13", - "last_updated": "2026-08-22", + "attachment": true, + "open_weights": false, + "release_date": "2026-08-03", + "last_updated": "2026-08-03", "type": "chat" }, { - "id": "tencent/hy3", - "name": "Hy3 (Tencent)", - "display_name": "Hy3 (Tencent)", + "id": "Kimi-K2.7-Code", + "name": "Kimi K2.7 Code", + "display_name": "Kimi K2.7 Code", "modalities": { "input": [ "text" @@ -290573,10 +290496,10 @@ ] }, "limit": { - "context": 256000, - "output": 128000 + "context": 262144, + "output": 262144 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -290585,37 +290508,37 @@ "extra_capabilities": { "reasoning": { "supported": true, - "effort_options": [ - "none", - "low", - "high" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, "attachment": false, "open_weights": true, - "release_date": "2026-07-06", - "last_updated": "2026-07-06", + "knowledge": "2025-01", + "release_date": "2026-06-12", + "last_updated": "2026-06-12", "type": "chat" }, { - "id": "tencent/step-5-preview", - "name": "Step 5 Preview (Tencent)", - "display_name": "Step 5 Preview (Tencent)", + "id": "Kimi-K2.6", + "name": "Kimi K2.6", + "display_name": "Kimi K2.6", "modalities": { "input": [ "text", - "image", - "video", - "audio" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 1000000 + "context": 262144, + "output": 262144 }, "temperature": true, "tool_call": true, @@ -290625,51 +290548,26 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "low", - "medium", - "high" + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, "attachment": true, "open_weights": true, - "release_date": "2026-09-16", - "last_updated": "2026-09-20", - "type": "chat" - }, - { - "id": "amazon/mistral.voxtral-small-24b-2507", - "name": "Voxtral Small 24B 2507 (Amazon Bedrock)", - "display_name": "Voxtral Small 24B 2507 (Amazon Bedrock)", - "modalities": { - "input": [ - "text", - "audio" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 32768, - "output": 32768 - }, - "temperature": true, - "tool_call": false, - "reasoning": { - "supported": false - }, - "attachment": true, - "open_weights": true, - "release_date": "2025-07-15", - "last_updated": "2025-07-15", + "knowledge": "2025-01", + "release_date": "2026-04-20", + "last_updated": "2026-04-20", "type": "chat" }, { - "id": "amazon/zai.glm-4.7-flash", - "name": "GLM-4.7-Flash (Amazon Bedrock)", - "display_name": "GLM-4.7-Flash (Amazon Bedrock)", + "id": "MiniMax-M2.7", + "name": "MiniMax-M2.7", + "display_name": "MiniMax-M2.7", "modalities": { "input": [ "text" @@ -290679,7 +290577,7 @@ ] }, "limit": { - "context": 203000, + "context": 204800, "output": 131072 }, "temperature": true, @@ -290688,78 +290586,107 @@ "supported": true, "default": true }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } + }, "attachment": false, "open_weights": true, - "knowledge": "2025-04", - "release_date": "2026-01-19", - "last_updated": "2026-01-19", + "release_date": "2026-03-18", + "last_updated": "2026-03-18", "type": "chat" }, { - "id": "amazon/moonshotai.kimi-k2.5", - "name": "Kimi K2.5 (Amazon Bedrock)", - "display_name": "Kimi K2.5 (Amazon Bedrock)", + "id": "Qwen3.8-Flash", + "name": "Qwen3.8 Flash", + "display_name": "Qwen3.8 Flash", "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 1000000, + "output": 131072 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, "default": true }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } + }, "attachment": true, - "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-01-27", - "last_updated": "2026-01-27", + "open_weights": false, + "release_date": "2026-08-26", + "last_updated": "2026-08-26", "type": "chat" }, { - "id": "amazon/amazon.nova-lite-v1:0", - "name": "Nova Lite", - "display_name": "Nova Lite", + "id": "Kimi-K3", + "name": "Kimi K3", + "display_name": "Kimi K3", "modalities": { "input": [ - "text", - "image", - "video", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 300000, - "output": 10000 + "context": 1048576, + "output": 131072 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": true, - "open_weights": false, - "knowledge": "2024-10", - "release_date": "2024-12-03", - "last_updated": "2024-12-03", + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } + }, + "attachment": false, + "open_weights": true, + "release_date": "2026-07-16", + "last_updated": "2026-07-16", "type": "chat" }, { - "id": "amazon/moonshot.kimi-k2-thinking", - "name": "Kimi K2 Thinking (Amazon Bedrock)", - "display_name": "Kimi K2 Thinking (Amazon Bedrock)", + "id": "DeepSeek-V4-Flash-0731", + "name": "DeepSeek V4 Flash 0731", + "display_name": "DeepSeek V4 Flash 0731", "modalities": { "input": [ "text" @@ -290769,8 +290696,8 @@ ] }, "limit": { - "context": 256000, - "output": 262144 + "context": 1000000, + "output": 384000 }, "temperature": true, "tool_call": true, @@ -290778,17 +290705,32 @@ "supported": true, "default": true }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "effort_options": [ + "high", + "max" + ] + } + }, "attachment": false, "open_weights": true, - "knowledge": "2024-08", - "release_date": "2025-11-06", - "last_updated": "2025-11-06", + "knowledge": "2025-05", + "release_date": "2026-07-31", + "last_updated": "2026-07-31", "type": "chat" }, { - "id": "amazon/openai.gpt-oss-safeguard-20b", - "name": "GPT OSS Safeguard 20B (Amazon Bedrock)", - "display_name": "GPT OSS Safeguard 20B (Amazon Bedrock)", + "id": "DeepSeek-V4-Pro-0813", + "name": "DeepSeek V4 Pro 0813", + "display_name": "DeepSeek V4 Pro 0813", "modalities": { "input": [ "text" @@ -290798,8 +290740,8 @@ ] }, "limit": { - "context": 128000, - "output": 131072 + "context": 1000000, + "output": 384000 }, "temperature": true, "tool_call": true, @@ -290809,23 +290751,29 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ - "low", - "medium", - "high" + "high", + "max" ] } }, "attachment": false, "open_weights": true, - "release_date": "2025-10-29", - "last_updated": "2025-10-29", + "release_date": "2026-08-13", + "last_updated": "2026-08-22", "type": "chat" }, { - "id": "amazon/amazon.nova-micro-v1:0", - "name": "Nova Micro", - "display_name": "Nova Micro", + "id": "DeepSeek-V4-Flash", + "name": "DeepSeek V4 Flash", + "display_name": "DeepSeek V4 Flash", "modalities": { "input": [ "text" @@ -290835,59 +290783,100 @@ ] }, "limit": { - "context": 128000, - "output": 10000 + "context": 1000000, + "output": 384000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "high", + "effort_options": [ + "low", + "high", + "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." + ] + } }, "attachment": false, - "open_weights": false, - "knowledge": "2024-10", - "release_date": "2024-12-03", - "last_updated": "2024-12-03", + "open_weights": true, + "knowledge": "2025-05", + "release_date": "2026-04-24", + "last_updated": "2026-04-24", "type": "chat" }, { - "id": "amazon/google.gemma-3-4b-it", - "name": "Gemma 3 4B IT (Amazon Bedrock)", - "display_name": "Gemma 3 4B IT (Amazon Bedrock)", + "id": "MiniMax-M3", + "name": "MiniMax-M3", + "display_name": "MiniMax-M3", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 131072 + "context": 1048576, + "output": 512000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": true, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } + }, + "attachment": false, "open_weights": true, - "knowledge": "2024-08", - "release_date": "2025-03-12", - "last_updated": "2025-03-12", + "release_date": "2026-06-01", + "last_updated": "2026-06-01", "type": "chat" - }, + } + ] + }, + "gmicloud": { + "id": "gmicloud", + "name": "GMI Cloud", + "display_name": "GMI Cloud", + "api": "https://api.gmi-serving.com/v1", + "doc": "https://docs.gmicloud.ai/inference-engine/api-reference/llm-api-reference", + "models": [ { - "id": "amazon/amazon.nova-pro-v1:0", - "name": "Nova Pro", - "display_name": "Nova Pro", + "id": "openai/gpt-5.5", + "name": "GPT-5.5", + "display_name": "GPT-5.5", "modalities": { "input": [ "text", "image", - "video", "pdf" ], "output": [ @@ -290895,25 +290884,47 @@ ] }, "limit": { - "context": 300000, - "output": 10000 + "context": 1050000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", + "effort_options": [ + "low", + "medium", + "high", + "xhigh" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" + } }, "attachment": true, "open_weights": false, - "knowledge": "2024-10", - "release_date": "2024-12-03", - "last_updated": "2024-12-03", + "knowledge": "2025-12-01", + "release_date": "2026-04-23", + "last_updated": "2026-04-23", "type": "chat" }, { - "id": "amazon/qwen.qwen3-coder-next", - "name": "Qwen3 Coder Next (Amazon Bedrock)", - "display_name": "Qwen3 Coder Next (Amazon Bedrock)", + "id": "Qwen/Qwen3.7-Max", + "name": "Qwen3.7 Max", + "display_name": "Qwen3.7 Max", "modalities": { "input": [ "text" @@ -290923,139 +290934,141 @@ ] }, "limit": { - "context": 262144, - "output": 65536 + "context": 1000000, + "output": 131072 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true + } }, "attachment": false, - "open_weights": true, - "knowledge": "2025-09", - "release_date": "2026-02-03", - "last_updated": "2026-02-03", + "open_weights": false, + "release_date": "2026-05-21", + "last_updated": "2026-05-21", "type": "chat" }, { - "id": "amazon/google.gemma-3-12b-it", - "name": "Gemma 3 12B IT (Amazon Bedrock)", - "display_name": "Gemma 3 12B IT (Amazon Bedrock)", + "id": "Qwen/Qwen3.8-Max", + "name": "Qwen3.8 Max", + "display_name": "Qwen3.8 Max", "modalities": { "input": [ "text", - "image" + "image", + "video", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 128000, + "context": 262144, "output": 131072 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false - }, - "attachment": true, - "open_weights": true, - "knowledge": "2024-08", - "release_date": "2025-03-12", - "last_updated": "2025-03-12", - "type": "chat" - }, - { - "id": "amazon/mistral.pixtral-large-2502-v1:0", - "name": "Pixtral Large (25.02) (Amazon Bedrock)", - "display_name": "Pixtral Large (25.02) (Amazon Bedrock)", - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 8192 + "supported": true, + "default": true }, - "temperature": true, - "tool_call": true, - "reasoning": { - "supported": false + "extra_capabilities": { + "reasoning": { + "supported": true + } }, "attachment": true, "open_weights": false, - "release_date": "2025-04-08", - "last_updated": "2025-04-08", + "release_date": "2026-08-03", + "last_updated": "2026-08-03", "type": "chat" }, { - "id": "amazon/google.gemma-3-27b-it", - "name": "Gemma 3 27B IT (Amazon Bedrock)", - "display_name": "Gemma 3 27B IT (Amazon Bedrock)", + "id": "Qwen/Qwen3.8-Flash", + "name": "Qwen3.8 Flash", + "display_name": "Qwen3.8 Flash", "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 128000, + "context": 1048575, "output": 131072 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true + } }, "attachment": true, - "open_weights": true, - "knowledge": "2024-08", - "release_date": "2025-03-12", - "last_updated": "2025-03-12", + "open_weights": false, + "release_date": "2026-08-26", + "last_updated": "2026-08-26", "type": "chat" }, { - "id": "amazon/mistral.voxtral-mini-3b-2507", - "name": "Voxtral Mini 3B 2507 (Amazon Bedrock)", - "display_name": "Voxtral Mini 3B 2507 (Amazon Bedrock)", + "id": "moonshotai/kimi-k2.7-code-highspeed", + "name": "Kimi K2.7 Code Highspeed", + "display_name": "Kimi K2.7 Code Highspeed", "modalities": { "input": [ - "text", - "audio" + "text" ], "output": [ "text" ] }, "limit": { - "context": 32768, - "output": 32768 + "context": 262144, + "output": 262144 }, - "temperature": true, - "tool_call": false, + "temperature": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": true, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } + }, + "attachment": false, "open_weights": true, - "release_date": "2025-07-15", - "last_updated": "2025-07-15", + "knowledge": "2025-01", + "release_date": "2026-06-12", + "last_updated": "2026-06-12", "type": "chat" }, { - "id": "flexai/DeepSeek-V4-Flash-0731", - "name": "DeepSeek V4 Flash 0731 (FlexAI)", - "display_name": "DeepSeek V4 Flash 0731 (FlexAI)", + "id": "moonshotai/Kimi-K2.6", + "name": "Kimi K2.6", + "display_name": "Kimi K2.6", "modalities": { "input": [ "text" @@ -291065,8 +291078,8 @@ ] }, "limit": { - "context": 1048576, - "output": 384000 + "context": 65536, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -291077,25 +291090,25 @@ "extra_capabilities": { "reasoning": { "supported": true, - "effort_options": [ - "none", - "low", - "high", - "max" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, "attachment": false, "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-07-31", - "last_updated": "2026-07-31", + "knowledge": "2025-01", + "release_date": "2026-04-20", + "last_updated": "2026-04-20", "type": "chat" }, { - "id": "flexai/gpt-oss-20b", - "name": "GPT OSS 20B (FlexAI)", - "display_name": "GPT OSS 20B (FlexAI)", + "id": "zai-org/GLM-5.2-FP8", + "name": "GLM-5.2", + "display_name": "GLM-5.2", "modalities": { "input": [ "text" @@ -291105,8 +291118,8 @@ ] }, "limit": { - "context": 131072, - "output": 32768 + "context": 1000000, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -291117,36 +291130,39 @@ "extra_capabilities": { "reasoning": { "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ - "low", - "medium", - "high" + "high", + "max" ] } }, "attachment": false, "open_weights": true, - "knowledge": "2024-06", - "release_date": "2025-08-05", - "last_updated": "2025-08-05", + "release_date": "2026-06-13", + "last_updated": "2026-06-13", "type": "chat" }, { - "id": "flexai/Step-3.7-Flash", - "name": "Step 3.7 Flash (FlexAI)", - "display_name": "Step 3.7 Flash (FlexAI)", + "id": "zai-org/GLM-5-FP8", + "name": "GLM-5", + "display_name": "GLM-5", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 256000 + "context": 202752, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -291156,24 +291172,25 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "low", - "medium", - "high" + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, - "attachment": true, + "attachment": false, "open_weights": true, - "knowledge": "2026-03-01", - "release_date": "2026-05-29", - "last_updated": "2026-05-29", + "release_date": "2026-02-12", + "last_updated": "2026-02-12", "type": "chat" }, { - "id": "flexai/gpt-oss-120b", - "name": "GPT OSS 120B (FlexAI)", - "display_name": "GPT OSS 120B (FlexAI)", + "id": "zai-org/GLM-5.1-FP8", + "name": "GLM-5.1", + "display_name": "GLM-5.1", "modalities": { "input": [ "text" @@ -291183,8 +291200,8 @@ ] }, "limit": { - "context": 131072, - "output": 32768 + "context": 202752, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -291195,24 +291212,24 @@ "extra_capabilities": { "reasoning": { "supported": true, - "effort_options": [ - "low", - "medium", - "high" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, "attachment": false, "open_weights": true, - "knowledge": "2024-06", - "release_date": "2025-08-05", - "last_updated": "2025-08-05", + "release_date": "2026-04-07", + "last_updated": "2026-04-07", "type": "chat" }, { - "id": "flexai/DeepSeek-V4.1-Flash-1017", - "name": "DeepSeek V4.1 Flash 1017 (FlexAI)", - "display_name": "DeepSeek V4.1 Flash 1017 (FlexAI)", + "id": "anthropic/claude-opus-4.6", + "name": "Claude Opus 4.6", + "display_name": "Claude Opus 4.6", "modalities": { "input": [ "text", @@ -291223,36 +291240,54 @@ ] }, "limit": { - "context": 1048576, - "output": 384000 + "context": 409600, + "output": 128000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": false, + "mode": "mixed", + "budget": { + "min": 1024, + "unit": "tokens" + }, + "effort": "high", "effort_options": [ - "none", "low", + "medium", "high", "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Anthropic recommends adaptive thinking with effort for Claude 4.6; budget_tokens remains a deprecated compatibility path.", + "Anthropic API defaults effort to high; lower effort levels should be chosen per workload." ] } }, "attachment": true, - "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-10-17", - "last_updated": "2026-10-17", + "open_weights": false, + "knowledge": "2025-05-31", + "release_date": "2026-02-05", + "last_updated": "2026-03-13", "type": "chat" }, { - "id": "flexai/Muse-Glimmer-30B", - "name": "Muse Glimmer 30B (FlexAI)", - "display_name": "Muse Glimmer 30B (FlexAI)", + "id": "anthropic/claude-opus-4.7", + "name": "Claude Opus 4.7", + "display_name": "Claude Opus 4.7", "modalities": { "input": [ "text", @@ -291263,39 +291298,56 @@ ] }, "limit": { - "context": 131072, - "output": 131072 + "context": 409600, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": false, + "mode": "effort", + "effort": "high", "effort_options": [ "low", "medium", "high", - "xhigh" + "xhigh", + "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "omitted", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Claude Opus 4.7 and newer Opus models require thinking.type = \"adaptive\" to enable thinking explicitly.", + "Manual budget_tokens requests return 400 on Claude Opus 4.7 and newer adaptive-only Opus models.", + "task_budget is separate from thinking control and should not be treated as a thinking budget." ] } }, "attachment": true, - "open_weights": true, - "knowledge": "2026-01-04", - "release_date": "2026-08-10", - "last_updated": "2026-08-10", + "open_weights": false, + "knowledge": "2026-01-31", + "release_date": "2026-04-16", + "last_updated": "2026-04-16", "type": "chat" }, { - "id": "mistral/mistral-large-2407", - "name": "Mistral Large 2407", - "display_name": "Mistral Large 2407", + "id": "anthropic/claude-opus-4.8", + "name": "Claude Opus 4.8", + "display_name": "Claude Opus 4.8", "modalities": { "input": [ "text", + "image", "pdf" ], "output": [ @@ -291303,25 +291355,52 @@ ] }, "limit": { - "context": 131072, - "output": 104857 + "context": 1000000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": false + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": false, + "mode": "effort", + "effort": "high", + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "omitted", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Claude Opus 4.7 and newer Opus models require thinking.type = \"adaptive\" to enable thinking explicitly.", + "Manual budget_tokens requests return 400 on Claude Opus 4.7 and newer adaptive-only Opus models.", + "task_budget is separate from thinking control and should not be treated as a thinking budget." + ] + } }, "attachment": true, - "open_weights": true, - "knowledge": "2024-03-31", - "release_date": "2024-07-24", - "last_updated": "2024-07-24", + "open_weights": false, + "knowledge": "2026-01", + "release_date": "2026-05-28", + "last_updated": "2026-05-28", "type": "chat" }, { - "id": "mistral/magistral-medium-latest", - "name": "Magistral Medium (latest)", - "display_name": "Magistral Medium (latest)", + "id": "anthropic/claude-sonnet-4.6", + "name": "Claude Sonnet 4.6", + "display_name": "Claude Sonnet 4.6", "modalities": { "input": [ "text", @@ -291332,133 +291411,139 @@ ] }, "limit": { - "context": 262144, - "output": 16384 + "context": 409600, + "output": 64000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": false, + "mode": "mixed", + "budget": { + "min": 1024, + "unit": "tokens" + }, + "effort": "high", "effort_options": [ - "none", - "high" + "low", + "medium", + "high", + "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Anthropic recommends adaptive thinking with effort for Claude 4.6; budget_tokens remains a deprecated compatibility path.", + "Anthropic API defaults effort to high; lower effort levels should be chosen per workload." ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-06", - "release_date": "2025-03-17", - "last_updated": "2025-03-20", + "knowledge": "2025-08-31", + "release_date": "2026-02-17", + "last_updated": "2026-03-13", "type": "chat" }, { - "id": "mistral/ministral-14b-2512", - "name": "Ministral 3 14B 2512", - "display_name": "Ministral 3 14B 2512", + "id": "MiniMaxAI/MiniMax-M2.7", + "name": "MiniMax-M2.7", + "display_name": "MiniMax-M2.7", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 209715 + "context": 196608, + "output": 131072 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": true, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } + }, + "attachment": false, "open_weights": true, - "release_date": "2025-12-02", - "last_updated": "2025-12-02", + "release_date": "2026-03-18", + "last_updated": "2026-03-18", "type": "chat" }, { - "id": "mistral/mistral-medium-3", - "name": "Mistral Medium 3", - "display_name": "Mistral Medium 3", + "id": "MiniMaxAI/MiniMax-M3", + "name": "MiniMax-M3", + "display_name": "MiniMax-M3", "modalities": { "input": [ "text", "image", - "pdf" + "video" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 104857 + "context": 1048576, + "output": 512000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false - }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-03-31", - "release_date": "2025-05-07", - "last_updated": "2025-05-07", - "type": "chat" - }, - { - "id": "mistral/devstral-2512", - "name": "Devstral 2", - "display_name": "Devstral 2", - "modalities": { - "input": [ - "text", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 262144, - "output": 262144 + "supported": true, + "default": true }, - "temperature": true, - "tool_call": true, - "reasoning": { - "supported": false + "extra_capabilities": { + "reasoning": { + "supported": true + } }, "attachment": true, "open_weights": true, - "knowledge": "2025-12", - "release_date": "2025-12-09", - "last_updated": "2025-12-09", + "release_date": "2026-06-01", + "last_updated": "2026-06-01", "type": "chat" }, { - "id": "mistral/mistral-medium-latest", - "name": "Mistral Medium (latest)", - "display_name": "Mistral Medium (latest)", + "id": "deepseek-ai/DeepSeek-V4-Pro", + "name": "DeepSeek V4 Pro", + "display_name": "DeepSeek V4 Pro", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 1048576, + "output": 384000 }, "temperature": true, "tool_call": true, @@ -291468,121 +291553,198 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "high", "effort_options": [ - "none", - "high" + "low", + "high", + "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." ] } }, - "attachment": true, + "attachment": false, "open_weights": true, - "release_date": "2026-04-29", - "last_updated": "2026-04-29", + "knowledge": "2025-05", + "release_date": "2026-04-24", + "last_updated": "2026-04-24", "type": "chat" }, { - "id": "mistral/mistral-medium-2505", - "name": "Mistral Medium 3", - "display_name": "Mistral Medium 3", + "id": "deepseek-ai/DeepSeek-V4-Flash", + "name": "DeepSeek V4 Flash", + "display_name": "DeepSeek V4 Flash", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 131072 + "context": 1048575, + "output": 384000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": true, - "open_weights": false, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "high", + "effort_options": [ + "low", + "high", + "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." + ] + } + }, + "attachment": false, + "open_weights": true, "knowledge": "2025-05", - "release_date": "2025-05-07", - "last_updated": "2025-05-07", + "release_date": "2026-04-24", + "last_updated": "2026-04-24", "type": "chat" - }, + } + ] + }, + "vancine": { + "id": "vancine", + "name": "Vancine", + "display_name": "Vancine", + "api": "https://vancine.com/v1", + "doc": "https://vancine.com/docs", + "models": [ { - "id": "mistral/mistral-large-2512", - "name": "Mistral Large 3", - "display_name": "Mistral Large 3", + "id": "kimi-k3", + "name": "Kimi K3", + "display_name": "Kimi K3", "modalities": { "input": [ "text", "image", - "pdf" + "video" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 1048576, + "output": 131072 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "effort_options": [ + "low", + "high", + "max" + ] + } }, "attachment": true, "open_weights": true, - "knowledge": "2024-11", - "release_date": "2025-12-02", - "last_updated": "2025-12-02", + "release_date": "2026-07-16", + "last_updated": "2026-07-16", "type": "chat" }, { - "id": "mistral/codestral-latest", - "name": "Codestral (latest)", - "display_name": "Codestral (latest)", + "id": "qwen3.8-flash", + "name": "Qwen3.8 Flash", + "display_name": "Qwen3.8 Flash", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 256000, - "output": 4096 + "context": 1000000, + "output": 131072 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, - "open_weights": true, - "knowledge": "2024-10", - "release_date": "2024-05-29", - "last_updated": "2025-01-04", + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } + }, + "attachment": true, + "open_weights": false, + "release_date": "2026-08-26", + "last_updated": "2026-08-26", "type": "chat" }, { - "id": "mistral/mistral-medium-2604", - "name": "Mistral Medium 3.5", - "display_name": "Mistral Medium 3.5", + "id": "glm-5.3-flash", + "name": "GLM-5.3-Flash", + "display_name": "GLM-5.3-Flash", "modalities": { "input": [ "text", - "image" + "image", + "video", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 1000000, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -291592,90 +291754,118 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, "effort_options": [ - "none", - "high" + "low", + "high", + "max" ] } }, "attachment": true, "open_weights": true, - "release_date": "2026-04-29", - "last_updated": "2026-04-29", + "release_date": "2026-08-26", + "last_updated": "2026-08-26", "type": "chat" }, { - "id": "mistral/devstral-medium-latest", - "name": "Devstral 2 (latest)", - "display_name": "Devstral 2 (latest)", + "id": "deepseek-v4.1-flash", + "name": "DeepSeek V4.1 Flash", + "display_name": "DeepSeek V4.1 Flash", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 1000000, + "output": 384000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "effort_options": [ + "low", + "high", + "max" + ] + } + }, + "attachment": true, "open_weights": true, - "knowledge": "2025-12", - "release_date": "2025-12-02", - "last_updated": "2025-12-02", + "knowledge": "2025-05", + "release_date": "2026-09-10", + "last_updated": "2026-09-10", "type": "chat" }, { - "id": "mistral/ministral-3b-2512", - "name": "Ministral 3 3B 2512", - "display_name": "Ministral 3 3B 2512", + "id": "hy4-preview", + "name": "Hy4 preview", + "display_name": "Hy4 preview", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 104857 + "context": 1024000, + "output": 64000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": true, + "extra_capabilities": { + "reasoning": { + "supported": true, + "effort_options": [ + "none", + "high" + ] + } + }, + "attachment": false, "open_weights": true, - "release_date": "2025-12-02", - "last_updated": "2025-12-02", + "release_date": "2026-08-28", + "last_updated": "2026-08-28", "type": "chat" }, { - "id": "mistral/mistral-small-2603", - "name": "Mistral Small 4", - "display_name": "Mistral Small 4", + "id": "glm-5.3", + "name": "GLM-5.3", + "display_name": "GLM-5.3", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 256000 + "context": 1000000, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -291685,121 +291875,158 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, "effort_options": [ - "none", - "high" + "low", + "high", + "max" ] } }, - "attachment": true, + "attachment": false, "open_weights": true, - "knowledge": "2025-06", - "release_date": "2026-03-16", - "last_updated": "2026-03-16", + "release_date": "2026-08-14", + "last_updated": "2026-08-14", "type": "chat" }, { - "id": "mistral/ministral-8b-2512", - "name": "Ministral 3 8B 2512", - "display_name": "Ministral 3 8B 2512", + "id": "qwen3.8-max", + "name": "Qwen3.8 Max", + "display_name": "Qwen3.8 Max", "modalities": { "input": [ "text", - "image" + "image", + "video", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 209715 + "context": 1000000, + "output": 131072 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "effort_options": [ + "low", + "medium", + "xhigh" + ] + } }, "attachment": true, - "open_weights": true, - "release_date": "2025-12-02", - "last_updated": "2025-12-02", + "open_weights": false, + "release_date": "2026-08-03", + "last_updated": "2026-08-03", "type": "chat" }, { - "id": "mistral/voxtral-small-latest", - "name": "Voxtral Small (latest)", - "display_name": "Voxtral Small (latest)", + "id": "MiniMax-M3", + "name": "MiniMax-M3", + "display_name": "MiniMax-M3", "modalities": { "input": [ "text", - "audio" + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 32768, - "output": 32000 + "context": 1048576, + "output": 512000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } }, "attachment": true, "open_weights": true, - "release_date": "2025-07-15", - "last_updated": "2025-07-15", + "release_date": "2026-06-01", + "last_updated": "2026-06-01", "type": "chat" - }, + } + ] + }, + "alibaba-token-plan-cn": { + "id": "alibaba-token-plan-cn", + "name": "Alibaba Token Plan (China)", + "display_name": "Alibaba Token Plan (China)", + "api": "https://token-plan.cn-beijing.maas.aliyuncs.com/compatible-mode/v1", + "doc": "https://www.alibabacloud.com/help/zh/model-studio/token-plan-overview", + "models": [ { - "id": "mistral/mistral-large-latest", - "name": "Mistral Large (latest)", - "display_name": "Mistral Large (latest)", + "id": "qwen-image-2.0-pro", + "name": "Qwen Image 2.0 Pro", + "display_name": "Qwen Image 2.0 Pro", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ - "text" + "image" ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 8192, + "output": 8192 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { "supported": false }, - "attachment": true, - "open_weights": true, - "knowledge": "2024-11", - "release_date": "2024-11-01", - "last_updated": "2025-12-02", - "type": "chat" + "attachment": false, + "open_weights": false, + "release_date": "2026-03-03", + "last_updated": "2026-03-03" }, { - "id": "mistral/mistral-small-latest", - "name": "Mistral Small (latest)", - "display_name": "Mistral Small (latest)", + "id": "MiniMax-M2.5", + "name": "MiniMax-M2.5", + "display_name": "MiniMax-M2.5", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 256000 + "context": 196608, + "output": 32768 }, "temperature": true, "tool_call": true, @@ -291809,36 +292036,38 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "none", - "high" + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, - "attachment": true, + "attachment": false, "open_weights": true, - "knowledge": "2025-06", - "release_date": "2026-03-16", - "last_updated": "2026-03-16", + "release_date": "2026-02-12", + "last_updated": "2026-02-12", "type": "chat" }, { - "id": "mistral/mistral-medium-3-5", - "name": "Mistral Medium 3.5", - "display_name": "Mistral Medium 3.5", + "id": "qwen3.8-max-preview", + "name": "Qwen3.8 Max Preview", + "display_name": "Qwen3.8 Max Preview", "modalities": { "input": [ "text", "image", - "pdf" + "video" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 209715 + "context": 1000000, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -291848,53 +292077,62 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ - "none", - "high" + "low", + "medium", + "xhigh" ] } }, "attachment": true, "open_weights": false, - "release_date": "2026-04-30", - "last_updated": "2026-04-30", + "release_date": "2026-07-19", + "last_updated": "2026-07-19", "type": "chat" }, { - "id": "mistral/codestral-2508", - "name": "Codestral 25.08", - "display_name": "Codestral 25.08", + "id": "happyhorse-1.1-i2v", + "name": "HappyHorse 1.1 Image-to-Video", + "display_name": "HappyHorse 1.1 Image-to-Video", "modalities": { "input": [ - "text", - "pdf" + "image", + "text" ], "output": [ - "text" + "video" ] }, "limit": { - "context": 256000, + "context": 8192, "output": 8192 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { "supported": false }, "attachment": true, "open_weights": false, - "release_date": "2025-07-30", - "last_updated": "2025-07-30", - "type": "chat" + "release_date": "2026-07-17", + "last_updated": "2026-07-17" }, { - "id": "deepinfra/nemotron-3-ultra-550b-a55b", - "name": "Nemotron 3 Ultra 550B A55B (Deep Infra)", - "display_name": "Nemotron 3 Ultra 550B A55B (Deep Infra)", + "id": "kimi-k2.7-code", + "name": "Kimi K2.7 Code", + "display_name": "Kimi K2.7 Code", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" @@ -291902,9 +292140,9 @@ }, "limit": { "context": 262144, - "output": 128000 + "output": 262144 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -291913,24 +292151,25 @@ "extra_capabilities": { "reasoning": { "supported": true, - "effort_options": [ - "none", - "medium", - "high" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, - "attachment": false, + "attachment": true, "open_weights": true, - "knowledge": "2025-09", - "release_date": "2026-06-04", - "last_updated": "2026-06-04", + "knowledge": "2025-01", + "release_date": "2026-06-12", + "last_updated": "2026-06-12", "type": "chat" }, { - "id": "deepinfra/nemotron-3-super-120b-a12b", - "name": "Nemotron 3 Super 120B A12B (Deep Infra)", - "display_name": "Nemotron 3 Super 120B A12B (Deep Infra)", + "id": "qwen3.7-max", + "name": "Qwen3.7 Max", + "display_name": "Qwen3.7 Max", "modalities": { "input": [ "text" @@ -291940,8 +292179,8 @@ ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 1000000, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -291951,35 +292190,38 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "none", - "low", - "medium" + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, "attachment": false, - "open_weights": true, - "knowledge": "2025-06", - "release_date": "2026-03-11", - "last_updated": "2026-03-11", + "open_weights": false, + "release_date": "2026-05-21", + "last_updated": "2026-05-21", "type": "chat" }, { - "id": "deepinfra/zai-org/GLM-4.7-Flash", - "name": "GLM-4.7-Flash (Deep Infra)", - "display_name": "GLM-4.7-Flash (Deep Infra)", + "id": "qwen3.6-flash", + "name": "Qwen3.6 Flash", + "display_name": "Qwen3.6 Flash", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 202752, - "output": 131072 + "context": 1000000, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -291989,103 +292231,143 @@ }, "extra_capabilities": { "reasoning": { - "supported": true + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] } }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-04", - "release_date": "2026-01-19", - "last_updated": "2026-01-19", + "attachment": true, + "open_weights": false, + "release_date": "2026-04-27", + "last_updated": "2026-04-27", "type": "chat" }, { - "id": "deepinfra/Qwen/Qwen3-VL-30B-A3B-Instruct", - "name": "Qwen3 VL 30B A3B Instruct (Deep Infra)", - "display_name": "Qwen3 VL 30B A3B Instruct (Deep Infra)", + "id": "qwen3.8-flash", + "name": "Qwen3.8 Flash", + "display_name": "Qwen3.8 Flash", "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 32768 + "context": 1000000, + "output": 131072 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "effort_options": [ + "low", + "medium", + "xhigh" + ] + } }, "attachment": true, - "open_weights": true, - "release_date": "2025-10-04", - "last_updated": "2025-10-04", + "open_weights": false, + "release_date": "2026-08-26", + "last_updated": "2026-08-26", "type": "chat" }, { - "id": "deepinfra/Qwen/Qwen2.5-7B-Instruct", - "name": "Qwen2.5 7B Instruct (Deep Infra)", - "display_name": "Qwen2.5 7B Instruct (Deep Infra)", + "id": "deepseek-v4.1-flash-1017", + "name": "DeepSeek V4.1 Flash 1017", + "display_name": "DeepSeek V4.1 Flash 1017", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 32768, - "output": 8192 + "context": 1000000, + "output": 384000 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "effort_options": [ + "low", + "high", + "max" + ] + } + }, + "attachment": true, "open_weights": true, - "release_date": "2024-09-19", - "last_updated": "2024-09-19", + "knowledge": "2025-05", + "release_date": "2026-10-17", + "last_updated": "2026-10-17", "type": "chat" }, { - "id": "deepinfra/Qwen/Qwen3-Next-80B-A3B-Instruct", - "name": "Qwen3-Next 80B-A3B Instruct (Deep Infra)", - "display_name": "Qwen3-Next 80B-A3B Instruct (Deep Infra)", + "id": "qwen-image-2.0", + "name": "Qwen Image 2.0", + "display_name": "Qwen Image 2.0", "modalities": { "input": [ "text" ], "output": [ - "text" + "image" ] }, "limit": { - "context": 262144, - "output": 32768 + "context": 8192, + "output": 8192 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { "supported": false }, "attachment": false, - "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-09", - "last_updated": "2025-09", - "type": "chat" + "open_weights": false, + "release_date": "2026-03-03", + "last_updated": "2026-03-03" }, { - "id": "deepinfra/Qwen/Qwen3-235B-A22B-Thinking-2507", - "name": "Qwen3 235B-A22B Thinking 2507 (Deep Infra)", - "display_name": "Qwen3 235B-A22B Thinking 2507 (Deep Infra)", + "id": "deepseek-v3.2", + "name": "DeepSeek V3.2", + "display_name": "DeepSeek V3.2", "modalities": { "input": [ "text" @@ -292095,8 +292377,8 @@ ] }, "limit": { - "context": 262144, - "output": 81920 + "context": 131072, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -292106,70 +292388,64 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] + "supported": true } }, "attachment": false, "open_weights": true, - "release_date": "2025-07-25", - "last_updated": "2025-07-25", + "knowledge": "2025-01", + "release_date": "2025-12-03", + "last_updated": "2025-12-05", "type": "chat" }, { - "id": "deepinfra/Qwen/Qwen3-VL-235B-A22B-Instruct", - "name": "Qwen3 VL 235B A22B Instruct (Deep Infra)", - "display_name": "Qwen3 VL 235B A22B Instruct (Deep Infra)", + "id": "happyhorse-1.1-t2v", + "name": "HappyHorse 1.1 Text-to-Video", + "display_name": "HappyHorse 1.1 Text-to-Video", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ - "text" + "video" ] }, "limit": { - "context": 262144, - "output": 32768 + "context": 8192, + "output": 8192 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { "supported": false }, - "attachment": true, - "open_weights": true, - "knowledge": "2025-03-31", - "release_date": "2025-09-23", - "last_updated": "2025-09-23", - "type": "chat" + "attachment": false, + "open_weights": false, + "release_date": "2026-07-17", + "last_updated": "2026-07-17" }, { - "id": "deepinfra/Qwen/Qwen3-Max", - "name": "Qwen3 Max (Deep Infra)", - "display_name": "Qwen3 Max (Deep Infra)", + "id": "deepseek-v4.1-flash", + "name": "DeepSeek V4.1 Flash", + "display_name": "DeepSeek V4.1 Flash", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 256000, - "output": 65536 + "context": 1000000, + "output": 384000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true + "supported": true, + "default": true }, "extra_capabilities": { "reasoning": { @@ -292179,87 +292455,88 @@ "visibility": "summary", "continuation": [ "thinking_blocks" + ], + "effort_options": [ + "low", + "high", + "max" ] } }, - "attachment": false, - "open_weights": false, - "knowledge": "2025-04", - "release_date": "2025-09-23", - "last_updated": "2025-09-23", + "attachment": true, + "open_weights": true, + "knowledge": "2025-05", + "release_date": "2026-09-10", + "last_updated": "2026-09-10", "type": "chat" }, { - "id": "deepinfra/Qwen/Qwen3-Coder-480B-A35B-Instruct", - "name": "Qwen3-Coder 480B-A35B Instruct (Deep Infra)", - "display_name": "Qwen3-Coder 480B-A35B Instruct (Deep Infra)", + "id": "wan2.7-image-pro", + "name": "Wan2.7 Image Pro", + "display_name": "Wan2.7 Image Pro", "modalities": { "input": [ "text" ], "output": [ - "text" + "image" ] }, "limit": { - "context": 262144, - "output": 65536 + "context": 8192, + "output": 8192 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { "supported": false }, "attachment": false, - "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-07-22", - "last_updated": "2025-07-22", - "type": "chat" + "open_weights": false, + "release_date": "2026-05-29", + "last_updated": "2026-05-29" }, { - "id": "deepinfra/Qwen/Qwen3-235B-A22B-Instruct-2507", - "name": "Qwen3 235B-A22B Instruct 2507 (Deep Infra)", - "display_name": "Qwen3 235B-A22B Instruct 2507 (Deep Infra)", + "id": "wan2.7-image", + "name": "Wan2.7 Image", + "display_name": "Wan2.7 Image", "modalities": { "input": [ "text" ], "output": [ - "text" + "image" ] }, "limit": { - "context": 262144, - "output": 16384 + "context": 8192, + "output": 8192 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { "supported": false }, "attachment": false, - "open_weights": true, - "release_date": "2025-07-21", - "last_updated": "2025-07-21", - "type": "chat" + "open_weights": false, + "release_date": "2026-05-29", + "last_updated": "2026-05-29" }, { - "id": "deepinfra/meta-models/Muse-Glimmer-30B", - "name": "Muse Glimmer 30B (Deep Infra)", - "display_name": "Muse Glimmer 30B (Deep Infra)", + "id": "deepseek-v4-pro", + "name": "DeepSeek V4 Pro", + "display_name": "DeepSeek V4 Pro", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 131072 + "context": 1000000, + "output": 384000 }, "temperature": true, "tool_call": true, @@ -292269,37 +292546,48 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "high", "effort_options": [ "low", - "medium", "high", - "xhigh" + "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." ] } }, - "attachment": true, + "attachment": false, "open_weights": true, - "knowledge": "2026-01-04", - "release_date": "2026-08-10", - "last_updated": "2026-08-10", + "knowledge": "2025-05", + "release_date": "2026-04-24", + "last_updated": "2026-04-24", "type": "chat" }, { - "id": "deepinfra/ByteDance/Seed-2.0-mini", - "name": "Seed 2.0 Mini (Deep Infra)", - "display_name": "Seed 2.0 Mini (Deep Infra)", + "id": "glm-5.3", + "name": "GLM-5.3", + "display_name": "GLM-5.3", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 256000, - "output": 32000 + "context": 1000000, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -292309,37 +292597,41 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ - "none", - "minimal", "low", - "medium", - "high" + "high", + "max" ] } }, - "attachment": true, - "open_weights": false, - "release_date": "2026-02-14", - "last_updated": "2026-02-14", + "attachment": false, + "open_weights": true, + "release_date": "2026-08-14", + "last_updated": "2026-08-14", "type": "chat" }, { - "id": "deepinfra/ByteDance/Seed-2.0-code", - "name": "Seed 2.0 Code (Deep Infra)", - "display_name": "Seed 2.0 Code (Deep Infra)", + "id": "glm-5", + "name": "GLM-5", + "display_name": "GLM-5", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 256000, - "output": 131072 + "context": 202752, + "output": 16384 }, "temperature": true, "tool_call": true, @@ -292349,24 +292641,25 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "none", - "low", - "medium", - "high" + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, - "attachment": true, + "attachment": false, "open_weights": false, - "release_date": "2026-02-14", - "last_updated": "2026-02-14", + "release_date": "2026-02-12", + "last_updated": "2026-02-12", "type": "chat" }, { - "id": "deepinfra/nvidia/Nemotron-3-Nano-30B-A3B", - "name": "Nemotron 3 Nano 30B A3B (Deep Infra)", - "display_name": "Nemotron 3 Nano 30B A3B (Deep Infra)", + "id": "deepseek-v4-flash", + "name": "DeepSeek V4 Flash", + "display_name": "DeepSeek V4 Flash", "modalities": { "input": [ "text" @@ -292376,8 +292669,8 @@ ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 1000000, + "output": 384000 }, "temperature": true, "tool_call": true, @@ -292385,71 +292678,81 @@ "supported": true, "default": true }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "high", + "effort_options": [ + "low", + "high", + "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." + ] + } + }, "attachment": false, "open_weights": true, - "knowledge": "2025-06-25", - "release_date": "2025-12-15", - "last_updated": "2025-12-15", + "knowledge": "2025-05", + "release_date": "2026-04-24", + "last_updated": "2026-04-24", "type": "chat" }, { - "id": "deepinfra/nvidia/Llama-3.1-Nemotron-70B-Instruct", - "name": "Llama 3.1 Nemotron 70B Instruct (Deep Infra)", - "display_name": "Llama 3.1 Nemotron 70B Instruct (Deep Infra)", + "id": "qwen3.6-plus", + "name": "Qwen3.6 Plus", + "display_name": "Qwen3.6 Plus", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 8192 + "context": 1000000, + "output": 65536 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false - }, - "attachment": false, - "open_weights": true, - "release_date": "2025-04-15", - "last_updated": "2025-04-15", - "type": "chat" - }, - { - "id": "deepinfra/microsoft/WizardLM-2-8x22B", - "name": "WizardLM 2 8x22B (Deep Infra)", - "display_name": "WizardLM 2 8x22B (Deep Infra)", - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 65536, - "output": 8000 + "supported": true, + "default": true }, - "temperature": true, - "tool_call": false, - "reasoning": { - "supported": false + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } }, - "attachment": false, - "open_weights": true, - "release_date": "2024-04-15", - "last_updated": "2024-04-15", + "attachment": true, + "open_weights": false, + "knowledge": "2025-04", + "release_date": "2026-04-02", + "last_updated": "2026-04-02", "type": "chat" }, { - "id": "deepinfra/ibm-granite/granite-4.2-8b", - "name": "Granite 4.2 8B (Deep Infra)", - "display_name": "Granite 4.2 8B (Deep Infra)", + "id": "glm-5.1", + "name": "GLM-5.1", + "display_name": "GLM-5.1", "modalities": { "input": [ "text" @@ -292459,8 +292762,8 @@ ] }, "limit": { - "context": 131072, - "output": 117964 + "context": 202752, + "output": 128000 }, "temperature": true, "tool_call": true, @@ -292470,123 +292773,153 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "none", - "low", - "high" + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, "attachment": false, "open_weights": true, - "release_date": "2026-08-25", - "last_updated": "2026-08-25", + "release_date": "2026-04-07", + "last_updated": "2026-04-07", "type": "chat" }, { - "id": "deepinfra/google/gemma-3-12b-it", - "name": "Gemma 3 12B IT (Deep Infra)", - "display_name": "Gemma 3 12B IT (Deep Infra)", + "id": "happyhorse-1.1-r2v", + "name": "HappyHorse 1.1 Reference-to-Video", + "display_name": "HappyHorse 1.1 Reference-to-Video", "modalities": { "input": [ - "text", - "image" + "image", + "text" ], "output": [ - "text" + "video" ] }, "limit": { - "context": 131072, - "output": 131072 + "context": 8192, + "output": 8192 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { "supported": false }, "attachment": true, - "open_weights": true, - "knowledge": "2024-08", - "release_date": "2025-03-12", - "last_updated": "2025-03-12", - "type": "chat" + "open_weights": false, + "release_date": "2026-07-17", + "last_updated": "2026-07-17" }, { - "id": "deepinfra/google/gemma-3-4b-it", - "name": "Gemma 3 4B IT (Deep Infra)", - "display_name": "Gemma 3 4B IT (Deep Infra)", + "id": "glm-5.2", + "name": "GLM-5.2", + "display_name": "GLM-5.2", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 131072, + "context": 1000000, "output": 131072 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": true, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "effort_options": [ + "high", + "max" + ] + } + }, + "attachment": false, "open_weights": true, - "knowledge": "2024-08", - "release_date": "2025-03-12", - "last_updated": "2025-03-12", + "release_date": "2026-06-13", + "last_updated": "2026-06-13", "type": "chat" }, { - "id": "deepinfra/google/gemma-3-27b-it", - "name": "Gemma 3 27B IT (Deep Infra)", - "display_name": "Gemma 3 27B IT (Deep Infra)", + "id": "qwen3.8-max", + "name": "Qwen3.8 Max", + "display_name": "Qwen3.8 Max", "modalities": { "input": [ "text", - "image" + "image", + "video", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 131072, + "context": 1000000, "output": 131072 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "effort_options": [ + "low", + "medium", + "xhigh" + ] + } }, "attachment": true, - "open_weights": true, - "knowledge": "2024-08", - "release_date": "2025-03-12", - "last_updated": "2025-03-12", + "open_weights": false, + "release_date": "2026-08-03", + "last_updated": "2026-08-03", "type": "chat" }, { - "id": "deepinfra/thinkingmachines/Inkling", - "name": "Inkling (Deep Infra)", - "display_name": "Inkling (Deep Infra)", + "id": "deepseek-v4-pro-0813", + "name": "DeepSeek V4 Pro 0813", + "display_name": "DeepSeek V4 Pro 0813", "modalities": { - "input": [ - "text", - "image", - "audio" + "input": [ + "text" ], "output": [ "text" ] }, "limit": { - "context": 524288, - "output": 1048576 + "context": 1000000, + "output": 384000 }, "temperature": true, "tool_call": true, @@ -292596,39 +292929,42 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ - "none", - "minimal", - "low", - "medium", "high", "max" ] } }, - "attachment": true, + "attachment": false, "open_weights": true, - "release_date": "2026-07-15", - "last_updated": "2026-07-15", + "release_date": "2026-08-13", + "last_updated": "2026-08-22", "type": "chat" }, { - "id": "deepinfra/thinkingmachines/Inkling-Small", - "name": "Inkling Small (Deep Infra)", - "display_name": "Inkling Small (Deep Infra)", + "id": "qwen3.7-plus", + "name": "Qwen3.7 Plus", + "display_name": "Qwen3.7 Plus", "modalities": { "input": [ "text", "image", - "audio" + "video" ], "output": [ "text" ] }, "limit": { - "context": 524288, - "output": 1048576 + "context": 1000000, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -292638,26 +292974,26 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "none", - "minimal", - "low", - "medium", - "high", - "max" + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-07-30", - "last_updated": "2026-07-30", + "open_weights": false, + "knowledge": "2025-04", + "release_date": "2026-06-02", + "last_updated": "2026-06-02", "type": "chat" }, { - "id": "deepinfra/openai/gpt-oss-20b", - "name": "GPT OSS 20B (Deep Infra)", - "display_name": "GPT OSS 20B (Deep Infra)", + "id": "deepseek-v4-flash-0731", + "name": "DeepSeek V4 Flash 0731", + "display_name": "DeepSeek V4 Flash 0731", "modalities": { "input": [ "text" @@ -292667,8 +293003,8 @@ ] }, "limit": { - "context": 131072, - "output": 32768 + "context": 1000000, + "output": 384000 }, "temperature": true, "tool_call": true, @@ -292679,35 +293015,42 @@ "extra_capabilities": { "reasoning": { "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ - "low", - "medium", - "high" + "high", + "max" ] } }, "attachment": false, "open_weights": true, - "knowledge": "2024-06", - "release_date": "2025-08-05", - "last_updated": "2025-08-05", + "knowledge": "2025-05", + "release_date": "2026-07-31", + "last_updated": "2026-07-31", "type": "chat" }, { - "id": "deepinfra/openai/gpt-oss-120b", - "name": "GPT OSS 120B (Deep Infra)", - "display_name": "GPT OSS 120B (Deep Infra)", + "id": "kimi-k2.5", + "name": "Kimi K2.5", + "display_name": "Kimi K2.5", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 32768 + "context": 262144, + "output": 98304 }, "temperature": true, "tool_call": true, @@ -292718,27 +293061,30 @@ "extra_capabilities": { "reasoning": { "supported": true, - "effort_options": [ - "low", - "medium", - "high" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, - "attachment": false, + "attachment": true, "open_weights": true, - "knowledge": "2024-06", - "release_date": "2025-08-05", - "last_updated": "2025-08-05", + "knowledge": "2025-01", + "release_date": "2026-01-27", + "last_updated": "2026-01-27", "type": "chat" }, { - "id": "deepinfra/tencent/Hy3", - "name": "Hy3 (Deep Infra)", - "display_name": "Hy3 (Deep Infra)", + "id": "kimi-k2.6", + "name": "Kimi K2.6", + "display_name": "Kimi K2.6", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" @@ -292746,7 +293092,7 @@ }, "limit": { "context": 262144, - "output": 128000 + "output": 262144 }, "temperature": true, "tool_call": true, @@ -292757,23 +293103,34 @@ "extra_capabilities": { "reasoning": { "supported": true, - "effort_options": [ - "none", - "low", - "high" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, - "attachment": false, + "attachment": true, "open_weights": true, - "release_date": "2026-07-06", - "last_updated": "2026-07-06", + "knowledge": "2025-01", + "release_date": "2026-04-20", + "last_updated": "2026-04-20", "type": "chat" - }, + } + ] + }, + "merge-gateway": { + "id": "merge-gateway", + "name": "Merge Gateway", + "display_name": "Merge Gateway", + "api": "https://api-gateway.merge.dev/v1/ai-sdk", + "doc": "https://docs.merge.dev/merge-gateway", + "models": [ { - "id": "deepinfra/tencent/Hy4-preview", - "name": "Hy4 preview (Deep Infra)", - "display_name": "Hy4 preview (Deep Infra)", + "id": "openai/o3-mini", + "name": "o3 Mini", + "display_name": "o3 Mini", "modalities": { "input": [ "text" @@ -292783,10 +293140,10 @@ ] }, "limit": { - "context": 1048576, - "output": 64000 + "context": 200000, + "output": 100000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -292795,50 +293152,81 @@ "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", "effort_options": [ - "none", "low", + "medium", "high" - ] + ], + "visibility": "hidden" } }, "attachment": false, - "open_weights": true, - "release_date": "2026-08-28", - "last_updated": "2026-08-28", + "open_weights": false, + "knowledge": "2024-05", + "release_date": "2024-12-20", + "last_updated": "2025-01-29", "type": "chat" }, { - "id": "deepinfra/Gryphe/MythoMax-L2-13b", - "name": "MythoMax L2 13B (Deep Infra)", - "display_name": "MythoMax L2 13B (Deep Infra)", + "id": "openai/gpt-5.4-nano", + "name": "GPT-5.4 Nano", + "display_name": "GPT-5.4 Nano", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 4096, - "output": 3200 + "context": 400000, + "output": 128000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": false }, - "attachment": false, - "open_weights": true, - "release_date": "2023-08-10", - "last_updated": "2023-08-10", + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": false, + "mode": "effort", + "effort": "none", + "effort_options": [ + "none", + "low", + "medium", + "high", + "xhigh" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" + } + }, + "attachment": true, + "open_weights": false, + "knowledge": "2025-08-31", + "release_date": "2026-03-17", + "last_updated": "2026-03-17", "type": "chat" }, { - "id": "deepinfra/moonshotai/Kimi-K2.5", - "name": "Kimi K2.5 (Deep Infra)", - "display_name": "Kimi K2.5 (Deep Infra)", + "id": "openai/gpt-6.1-terra", + "name": "GPT-6.1 Terra", + "display_name": "GPT-6.1 Terra", "modalities": { "input": [ "text", @@ -292849,8 +293237,8 @@ ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 1050000, + "output": 128000 }, "temperature": false, "tool_call": true, @@ -292860,107 +293248,147 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "none", + "low", + "medium", + "high", + "xhigh", + "max" ] } }, "attachment": true, - "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-01-27", - "last_updated": "2026-01-27", + "open_weights": false, + "knowledge": "2026-06-01", + "release_date": "2026-10-26", + "last_updated": "2026-10-26", "type": "chat" }, { - "id": "deepinfra/moonshotai/Kimi-K2-Instruct", - "name": "Kimi K2 Instruct (Deep Infra)", - "display_name": "Kimi K2 Instruct (Deep Infra)", + "id": "openai/gpt-5.4", + "name": "GPT-5.4", + "display_name": "GPT-5.4", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 32768 + "context": 1050000, + "output": 128000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": false }, - "attachment": false, - "open_weights": true, - "release_date": "2025-07-11", - "last_updated": "2025-07-11", + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": false, + "mode": "effort", + "effort": "none", + "effort_options": [ + "none", + "low", + "medium", + "high", + "xhigh" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" + } + }, + "attachment": true, + "open_weights": false, + "knowledge": "2025-08-31", + "release_date": "2026-03-05", + "last_updated": "2026-03-05", "type": "chat" }, { - "id": "deepinfra/stepfun-ai/Step-5-Preview", - "name": "Step 5 Preview (Deep Infra)", - "display_name": "Step 5 Preview (Deep Infra)", + "id": "openai/gpt-5.4-mini", + "name": "GPT-5.4 Mini", + "display_name": "GPT-5.4 Mini", "modalities": { "input": [ "text", "image", - "video" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 1000000 + "context": 400000, + "output": 128000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": false, + "mode": "effort", + "effort": "none", "effort_options": [ + "none", + "low", + "medium", + "high", + "xhigh" + ], + "verbosity": "medium", + "verbosity_options": [ "low", "medium", "high" - ] + ], + "visibility": "hidden" } }, "attachment": true, - "open_weights": true, - "release_date": "2026-09-16", - "last_updated": "2026-09-20", + "open_weights": false, + "knowledge": "2025-08-31", + "release_date": "2026-03-17", + "last_updated": "2026-03-17", "type": "chat" }, { - "id": "deepinfra/stepfun-ai/Step-3.7-Flash", - "name": "Step 3.7 Flash (Deep Infra)", - "display_name": "Step 3.7 Flash (Deep Infra)", + "id": "openai/gpt-6-sol", + "name": "GPT-6 Sol", + "display_name": "GPT-6 Sol", "modalities": { "input": [ "text", - "image", - "video" + "image" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 256000 + "context": 1050000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -292969,36 +293397,41 @@ "extra_capabilities": { "reasoning": { "effort_options": [ + "none", "low", "medium", - "high" + "high", + "xhigh", + "max" ] } }, "attachment": true, - "open_weights": true, - "knowledge": "2026-03-01", - "release_date": "2026-05-29", - "last_updated": "2026-05-29", + "open_weights": false, + "knowledge": "2026-04-20", + "release_date": "2026-09-22", + "last_updated": "2026-09-22", "type": "chat" }, { - "id": "deepinfra/stepfun-ai/Step-3.5-Flash", - "name": "Step 3.5 Flash (Deep Infra)", - "display_name": "Step 3.5 Flash (Deep Infra)", + "id": "openai/gpt-5.6-terra", + "name": "GPT-5.6 Terra", + "display_name": "GPT-5.6 Terra", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 256000 + "context": 1050000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -293006,61 +293439,92 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", "effort_options": [ + "none", + "low", + "medium", + "high", + "xhigh", + "max" + ], + "verbosity": "medium", + "verbosity_options": [ "low", + "medium", "high" - ] + ], + "visibility": "hidden" } }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-01-29", - "last_updated": "2026-02-13", + "attachment": true, + "open_weights": false, + "knowledge": "2026-02-16", + "release_date": "2026-07-09", + "last_updated": "2026-07-09", "type": "chat" }, { - "id": "deepinfra/NousResearch/Hermes-3-Llama-3.1-70B", - "name": "Hermes 3 70B Instruct (Deep Infra)", - "display_name": "Hermes 3 70B Instruct (Deep Infra)", + "id": "openai/gpt-6-astra", + "name": "GPT-6 Astra", + "display_name": "GPT-6 Astra", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 16384 + "context": 1050000, + "output": 128000 }, - "temperature": true, - "tool_call": false, + "temperature": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, - "open_weights": true, - "knowledge": "2023-12-31", - "release_date": "2024-08-18", - "last_updated": "2024-08-18", + "extra_capabilities": { + "reasoning": { + "supported": true, + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" + ] + } + }, + "attachment": true, + "open_weights": false, + "knowledge": "2026-04-30", + "release_date": "2026-09-04", + "last_updated": "2026-09-04", "type": "chat" }, { - "id": "deepinfra/NousResearch/Hermes-3-Llama-3.1-405B", - "name": "Hermes 3 405B Instruct (Deep Infra)", - "display_name": "Hermes 3 405B Instruct (Deep Infra)", + "id": "openai/gpt-5.3-chat-latest", + "name": "GPT-5.3 Chat Latest", + "display_name": "GPT-5.3 Chat Latest", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 131072, + "context": 128000, "output": 16384 }, "temperature": true, @@ -293068,17 +293532,17 @@ "reasoning": { "supported": false }, - "attachment": false, - "open_weights": true, - "knowledge": "2023-12-31", - "release_date": "2024-08-16", - "last_updated": "2024-08-16", + "attachment": true, + "open_weights": false, + "knowledge": "2025-08-31", + "release_date": "2026-03-03", + "last_updated": "2026-03-03", "type": "chat" }, { - "id": "deepinfra/mistralai/Mistral-Small-24B-Instruct-2501", - "name": "Mistral Small 3 (Deep Infra)", - "display_name": "Mistral Small 3 (Deep Infra)", + "id": "openai/gpt-oss-safeguard-120b", + "name": "GPT OSS Safeguard 120B", + "display_name": "GPT OSS Safeguard 120B", "modalities": { "input": [ "text" @@ -293088,146 +293552,191 @@ ] }, "limit": { - "context": 32768, - "output": 16384 + "context": 4096, + "output": 4096 }, "temperature": true, "tool_call": false, "reasoning": { - "supported": false + "supported": true, + "default": true }, "attachment": false, "open_weights": true, - "knowledge": "2023-10-31", - "release_date": "2025-01-30", - "last_updated": "2025-01-30", + "release_date": "2025-10-29", + "last_updated": "2025-10-29", "type": "chat" }, { - "id": "deepinfra/mistralai/Mixtral-8x7B-Instruct-v0.1", - "name": "Mixtral 8x7B Instruct v0.1 (Deep Infra)", - "display_name": "Mixtral 8x7B Instruct v0.1 (Deep Infra)", + "id": "openai/gpt-4o-2024-08-06", + "name": "GPT-4o (2024-08-06)", + "display_name": "GPT-4o (2024-08-06)", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 32768, - "output": 32768 + "context": 128000, + "output": 16384 }, "temperature": true, "tool_call": true, "reasoning": { "supported": false }, - "attachment": false, - "open_weights": true, - "release_date": "2023-12-11", - "last_updated": "2023-12-11", + "attachment": true, + "open_weights": false, + "knowledge": "2023-09", + "release_date": "2024-08-06", + "last_updated": "2024-08-06", "type": "chat" }, { - "id": "deepinfra/meta-llama/Llama-Guard-4-12B", - "name": "Llama Guard 4 12B (Deep Infra)", - "display_name": "Llama Guard 4 12B (Deep Infra)", + "id": "openai/gpt-5.2", + "name": "GPT-5.2", + "display_name": "GPT-5.2", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 163840, - "output": 163840 + "context": 400000, + "output": 128000 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": false + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": false, + "mode": "effort", + "effort": "none", + "effort_options": [ + "none", + "low", + "medium", + "high", + "xhigh" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" + } }, "attachment": true, - "open_weights": true, - "release_date": "2025-04-29", - "last_updated": "2025-04-29", + "open_weights": false, + "knowledge": "2025-08-31", + "release_date": "2025-12-11", + "last_updated": "2025-12-11", "type": "chat" }, { - "id": "deepinfra/meta-llama/Llama-Guard-3-8B", - "name": "Llama-Guard-3-8B (Deep Infra)", - "display_name": "Llama-Guard-3-8B (Deep Infra)", + "id": "openai/gpt-6.1-sol", + "name": "GPT-6.1 Sol", + "display_name": "GPT-6.1 Sol", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 4096 + "context": 1050000, + "output": 128000 }, - "temperature": true, - "tool_call": false, + "temperature": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, - "open_weights": true, - "knowledge": "2023-12", - "release_date": "2024-07-23", - "last_updated": "2024-07-23", + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" + ] + } + }, + "attachment": true, + "open_weights": false, + "knowledge": "2026-04-30", + "release_date": "2026-09-29", + "last_updated": "2026-09-29", "type": "chat" }, { - "id": "deepinfra/meta-llama/Llama-3.2-3B-Instruct", - "name": "Llama 3.2 3B Instruct (Deep Infra)", - "display_name": "Llama 3.2 3B Instruct (Deep Infra)", + "id": "openai/gpt-4o-2024-11-20", + "name": "GPT-4o (2024-11-20)", + "display_name": "GPT-4o (2024-11-20)", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 131072 + "context": 128000, + "output": 16384 }, "temperature": true, "tool_call": true, "reasoning": { "supported": false }, - "attachment": false, - "open_weights": true, - "knowledge": "2023-12", - "release_date": "2024-09-25", - "last_updated": "2024-09-25", + "attachment": true, + "open_weights": false, + "knowledge": "2023-09", + "release_date": "2024-11-20", + "last_updated": "2024-11-20", "type": "chat" }, { - "id": "deepinfra/meta-llama/Llama-3.3-70B-Instruct", - "name": "Llama-3.3-70B-Instruct (Deep Infra)", - "display_name": "Llama-3.3-70B-Instruct (Deep Infra)", + "id": "openai/gpt-4o-2024-05-13", + "name": "GPT-4o (2024-05-13)", + "display_name": "GPT-4o (2024-05-13)", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 131072, + "context": 128000, "output": 4096 }, "temperature": true, @@ -293235,127 +293744,120 @@ "reasoning": { "supported": false }, - "attachment": false, - "open_weights": true, - "knowledge": "2023-12", - "release_date": "2024-12-06", - "last_updated": "2024-12-06", + "attachment": true, + "open_weights": false, + "knowledge": "2023-09", + "release_date": "2024-05-13", + "last_updated": "2024-05-13", "type": "chat" }, { - "id": "deepinfra/meta-llama/Llama-3.2-11B-Vision-Instruct", - "name": "Llama-3.2-11B-Vision-Instruct (Deep Infra)", - "display_name": "Llama-3.2-11B-Vision-Instruct (Deep Infra)", + "id": "openai/gpt-audio-mini", + "name": "GPT Audio Mini", + "display_name": "GPT Audio Mini", "modalities": { "input": [ "text", - "image" + "audio" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 4096 + "context": 128000, + "output": 16384 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { "supported": false }, "attachment": true, - "open_weights": true, - "knowledge": "2023-12", - "release_date": "2024-09-25", - "last_updated": "2024-09-25", + "open_weights": false, + "knowledge": "2023-10-01", + "release_date": "2025-10-06", + "last_updated": "2025-10-06", "type": "chat" }, { - "id": "deepinfra/deepseek-ai/DeepSeek-V4.1-Flash", - "name": "DeepSeek V4.1 Flash (Deep Infra)", - "display_name": "DeepSeek V4.1 Flash (Deep Infra)", + "id": "openai/gpt-4.1-nano", + "name": "GPT-4.1 Nano", + "display_name": "GPT-4.1 Nano", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 384000 + "context": 1047576, + "output": 32768 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "effort_options": [ - "none", - "low", - "high", - "max" - ] - } + "supported": false }, "attachment": true, - "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-09-10", - "last_updated": "2026-09-10", + "open_weights": false, + "knowledge": "2024-04", + "release_date": "2025-04-14", + "last_updated": "2025-04-14", "type": "chat" }, { - "id": "deepinfra/deepseek-ai/DeepSeek-V3-0324", - "name": "DeepSeek V3 0324 (Deep Infra)", - "display_name": "DeepSeek V3 0324 (Deep Infra)", + "id": "openai/gpt-5-search-api", + "name": "GPT-5 Search API", + "display_name": "GPT-5 Search API", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 163840, - "output": 163840 + "context": 272000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": false }, - "attachment": false, - "open_weights": true, - "release_date": "2025-03-24", - "last_updated": "2025-03-24", + "attachment": true, + "open_weights": false, + "release_date": "2025-10-14", + "last_updated": "2025-10-14", "type": "chat" }, { - "id": "deepinfra/deepseek-ai/DeepSeek-V4-Flash-0731", - "name": "DeepSeek V4 Flash 0731 (Deep Infra)", - "display_name": "DeepSeek V4 Flash 0731 (Deep Infra)", + "id": "openai/gpt-5-nano", + "name": "GPT-5 Nano", + "display_name": "GPT-5 Nano", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 384000 + "context": 400000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -293364,77 +293866,80 @@ "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", "effort_options": [ - "none", + "minimal", "low", - "high", - "max" - ] + "medium", + "high" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-07-31", - "last_updated": "2026-07-31", + "attachment": true, + "open_weights": false, + "knowledge": "2024-05-30", + "release_date": "2025-08-07", + "last_updated": "2025-08-07", "type": "chat" }, { - "id": "deepinfra/deepseek-ai/DeepSeek-V4-Pro-0813", - "name": "DeepSeek V4 Pro 0813 (Deep Infra)", - "display_name": "DeepSeek V4 Pro 0813 (Deep Infra)", + "id": "openai/gpt-5-chat-latest", + "name": "GPT-5 Chat Latest", + "display_name": "GPT-5 Chat Latest", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 384000 + "context": 128000, + "output": 16384 }, - "temperature": true, - "tool_call": true, + "temperature": false, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "effort_options": [ - "none", - "low", - "high", - "max" - ] - } + "supported": false }, - "attachment": false, - "open_weights": true, - "release_date": "2026-08-13", - "last_updated": "2026-08-22", + "attachment": true, + "open_weights": false, + "knowledge": "2024-09-30", + "release_date": "2025-08-07", + "last_updated": "2025-08-07", "type": "chat" }, { - "id": "deepinfra/deepseek-ai/DeepSeek-R1", - "name": "DeepSeek-R1 (Deep Infra)", - "display_name": "DeepSeek-R1 (Deep Infra)", + "id": "openai/gpt-5.6-luna", + "name": "GPT-5.6 Luna", + "display_name": "GPT-5.6 Luna", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 163840, - "output": 32768 + "context": 1050000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -293443,39 +293948,52 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] + "default_enabled": true, + "mode": "effort", + "effort": "medium", + "effort_options": [ + "none", + "low", + "medium", + "high", + "xhigh", + "max" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, - "attachment": false, - "open_weights": true, - "knowledge": "2024-07", - "release_date": "2025-01-20", - "last_updated": "2025-05-29", + "attachment": true, + "open_weights": false, + "knowledge": "2026-02-16", + "release_date": "2026-07-09", + "last_updated": "2026-07-09", "type": "chat" }, { - "id": "deepinfra/deepseek-ai/DeepSeek-V4.1-Flash-1017", - "name": "DeepSeek V4.1 Flash 1017 (Deep Infra)", - "display_name": "DeepSeek V4.1 Flash 1017 (Deep Infra)", + "id": "openai/o4-mini", + "name": "o4 Mini", + "display_name": "o4 Mini", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 384000 + "context": 200000, + "output": 100000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -293483,38 +294001,44 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", "effort_options": [ - "none", "low", - "high", - "max" - ] + "medium", + "high" + ], + "visibility": "hidden" } }, "attachment": true, - "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-10-17", - "last_updated": "2026-10-17", + "open_weights": false, + "knowledge": "2024-05", + "release_date": "2025-04-16", + "last_updated": "2025-04-16", "type": "chat" }, { - "id": "deepinfra/deepseek-ai/DeepSeek-R1-0528", - "name": "DeepSeek R1 0528 (Deep Infra)", - "display_name": "DeepSeek R1 0528 (Deep Infra)", + "id": "openai/gpt-5", + "name": "GPT-5", + "display_name": "GPT-5", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 163840, - "output": 32768 + "context": 400000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -293523,65 +294047,50 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] + "default_enabled": true, + "mode": "effort", + "effort": "medium", + "effort_options": [ + "minimal", + "low", + "medium", + "high" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, - "attachment": false, - "open_weights": true, - "release_date": "2025-05-28", - "last_updated": "2025-05-28", - "type": "chat" - }, - { - "id": "deepinfra/deepseek-ai/DeepSeek-V3", - "name": "DeepSeek-V3 (Deep Infra)", - "display_name": "DeepSeek-V3 (Deep Infra)", - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 163840, - "output": 8192 - }, - "temperature": true, - "tool_call": true, - "reasoning": { - "supported": false - }, - "attachment": false, - "open_weights": true, - "release_date": "2024-12-26", - "last_updated": "2024-12-26", + "attachment": true, + "open_weights": false, + "knowledge": "2024-09-30", + "release_date": "2025-08-07", + "last_updated": "2025-08-07", "type": "chat" }, { - "id": "tensorx/deepseek/deepseek-v4.1-flash-1017", - "name": "DeepSeek V4.1 Flash 1017 (TensorX)", - "display_name": "DeepSeek V4.1 Flash 1017 (TensorX)", + "id": "openai/gpt-5-mini", + "name": "GPT-5 Mini", + "display_name": "GPT-5 Mini", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 384000 + "context": 400000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -293589,25 +294098,36 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", "effort_options": [ - "none", + "minimal", "low", - "high", - "max" - ] + "medium", + "high" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, "attachment": true, - "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-10-17", - "last_updated": "2026-10-17", + "open_weights": false, + "knowledge": "2024-05-30", + "release_date": "2025-08-07", + "last_updated": "2025-08-07", "type": "chat" - }, - { - "id": "tensorx/deepseek/deepseek-r1-0528", - "name": "DeepSeek R1 0528 (TensorX)", - "display_name": "DeepSeek R1 0528 (TensorX)", + }, + { + "id": "openai/gpt-oss-safeguard-20b", + "name": "GPT OSS Safeguard 20B", + "display_name": "GPT OSS Safeguard 20B", "modalities": { "input": [ "text" @@ -293617,77 +294137,55 @@ ] }, "limit": { - "context": 164000, - "output": 32768 + "context": 4096, + "output": 4096 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } - }, "attachment": false, "open_weights": true, - "release_date": "2025-05-28", - "last_updated": "2025-05-28", + "release_date": "2025-10-29", + "last_updated": "2025-10-29", "type": "chat" }, { - "id": "tensorx/deepseek/deepseek-v4.1-flash", - "name": "DeepSeek V4.1 Flash (TensorX)", - "display_name": "DeepSeek V4.1 Flash (TensorX)", + "id": "openai/gpt-4o-mini", + "name": "GPT-4o Mini", + "display_name": "GPT-4o Mini", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 384000 + "context": 128000, + "output": 16384 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "effort_options": [ - "none", - "low", - "high", - "max" - ] - } + "supported": false }, "attachment": true, - "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-09-10", - "last_updated": "2026-09-10", + "open_weights": false, + "knowledge": "2023-09", + "release_date": "2024-07-18", + "last_updated": "2024-07-18", "type": "chat" }, { - "id": "tensorx/deepseek/deepseek-v4-flash-0731", - "name": "DeepSeek V4 Flash 0731 (TensorX)", - "display_name": "DeepSeek V4 Flash 0731 (TensorX)", + "id": "openai/gpt-oss-120b", + "name": "GPT-OSS 120B", + "display_name": "GPT-OSS 120B", "modalities": { "input": [ "text" @@ -293697,50 +294195,46 @@ ] }, "limit": { - "context": 1048576, - "output": 384000 + "context": 128000, + "output": 32000 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { "supported": true, "default": true }, "extra_capabilities": { "reasoning": { - "supported": true, - "effort_options": [ - "none", - "low", - "high", - "max" - ] + "supported": true } }, "attachment": false, "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-07-31", - "last_updated": "2026-07-31", + "knowledge": "2024-06", + "release_date": "2025-08-05", + "last_updated": "2025-08-05", "type": "chat" }, { - "id": "tensorx/deepseek/deepseek-v4-pro-0813", - "name": "DeepSeek V4 Pro 0813 (TensorX)", - "display_name": "DeepSeek V4 Pro 0813 (TensorX)", + "id": "openai/o1", + "name": "o1", + "display_name": "o1", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 384000 + "context": 200000, + "output": 100000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -293749,77 +294243,69 @@ "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", "effort_options": [ - "none", "low", - "high", - "max" - ] + "medium", + "high" + ], + "visibility": "hidden" } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-08-13", - "last_updated": "2026-08-22", + "attachment": true, + "open_weights": false, + "knowledge": "2023-09", + "release_date": "2024-12-05", + "last_updated": "2024-12-05", "type": "chat" }, { - "id": "tensorx/moonshotai/kimi-k2.5", - "name": "Kimi K2.5 (TensorX)", - "display_name": "Kimi K2.5 (TensorX)", + "id": "openai/gpt-4", + "name": "GPT-4", + "display_name": "GPT-4", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 8192, + "output": 8192 }, - "temperature": false, - "tool_call": true, + "temperature": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } + "supported": false }, - "attachment": true, - "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-01-27", - "last_updated": "2026-01-27", + "attachment": false, + "open_weights": false, + "knowledge": "2023-11", + "release_date": "2023-11-06", + "last_updated": "2024-04-09", "type": "chat" }, { - "id": "arkanecloud/ministral-14b", - "name": "Ministral 14B (Arkanecloud)", - "display_name": "Ministral 14B (Arkanecloud)", + "id": "openai/gpt-4o", + "name": "GPT-4o", + "display_name": "GPT-4o", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 32768, - "output": 262144 + "context": 128000, + "output": 16384 }, "temperature": true, "tool_call": true, @@ -293827,15 +294313,16 @@ "supported": false }, "attachment": true, - "open_weights": true, - "release_date": "2025-12-02", - "last_updated": "2025-12-02", + "open_weights": false, + "knowledge": "2023-09", + "release_date": "2024-05-13", + "last_updated": "2024-08-06", "type": "chat" }, { - "id": "arkanecloud/Qwen/Qwen3-235B-A22B-Instruct-2507", - "name": "Qwen3 235B-A22B Instruct 2507 (Arkanecloud)", - "display_name": "Qwen3 235B-A22B Instruct 2507 (Arkanecloud)", + "id": "openai/gpt-3.5-turbo", + "name": "GPT-3.5 Turbo", + "display_name": "GPT-3.5 Turbo", "modalities": { "input": [ "text" @@ -293845,37 +294332,40 @@ ] }, "limit": { - "context": 262000, - "output": 16384 + "context": 16385, + "output": 4096 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { "supported": false }, "attachment": false, - "open_weights": true, - "release_date": "2025-07-21", - "last_updated": "2025-07-21", + "open_weights": false, + "knowledge": "2021-09-01", + "release_date": "2023-03-01", + "last_updated": "2023-11-06", "type": "chat" }, { - "id": "arkanecloud/nvidia/nemotron-3-ultra-550b-a55b", - "name": "Nemotron 3 Ultra 550B A55B (Arkanecloud)", - "display_name": "Nemotron 3 Ultra 550B A55B (Arkanecloud)", + "id": "openai/gpt-5.6-sol", + "name": "GPT-5.6 Sol", + "display_name": "GPT-5.6 Sol", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 512288, + "context": 1050000, "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -293884,114 +294374,130 @@ "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", "effort_options": [ "none", + "low", + "medium", + "high", + "xhigh", + "max" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", "medium", "high" - ] + ], + "visibility": "hidden" } }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-09", - "release_date": "2026-06-04", - "last_updated": "2026-06-04", + "attachment": true, + "open_weights": false, + "knowledge": "2026-02-16", + "release_date": "2026-07-09", + "last_updated": "2026-07-09", "type": "chat" }, { - "id": "arkanecloud/nvidia/nemotron-3-super-120b-a12b", - "name": "Nemotron 3 Super 120B A12B (Arkanecloud)", - "display_name": "Nemotron 3 Super 120B A12B (Arkanecloud)", + "id": "openai/gpt-5.1", + "name": "GPT-5.1", + "display_name": "GPT-5.1", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 256000, - "output": 262144 + "context": 400000, + "output": 128000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": false, + "mode": "effort", + "effort": "none", "effort_options": [ "none", "low", - "medium" - ] + "medium", + "high" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-06", - "release_date": "2026-03-11", - "last_updated": "2026-03-11", + "attachment": true, + "open_weights": false, + "knowledge": "2024-09-30", + "release_date": "2025-11-13", + "last_updated": "2025-11-13", "type": "chat" }, { - "id": "arkanecloud/openai/gpt-oss-20b", - "name": "GPT OSS 20B (Arkanecloud)", - "display_name": "GPT OSS 20B (Arkanecloud)", + "id": "openai/whisper-1", + "name": "Whisper", + "display_name": "Whisper", "modalities": { "input": [ - "text" + "audio" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 32768 + "context": 448000, + "output": 4096 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "effort_options": [ - "low", - "medium", - "high" - ] - } + "supported": false }, - "attachment": false, - "open_weights": true, - "knowledge": "2024-06", - "release_date": "2025-08-05", - "last_updated": "2025-08-05", - "type": "chat" + "attachment": true, + "open_weights": false, + "release_date": "2023-03-01", + "last_updated": "2023-03-01" }, { - "id": "arkanecloud/openai/gpt-oss-120b", - "name": "GPT OSS 120B (Arkanecloud)", - "display_name": "GPT OSS 120B (Arkanecloud)", + "id": "openai/gpt-5.5", + "name": "GPT-5.5", + "display_name": "GPT-5.5", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 32768 + "context": 1050000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -294000,27 +294506,40 @@ "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", "effort_options": [ + "low", + "medium", + "high", + "xhigh" + ], + "verbosity": "medium", + "verbosity_options": [ "low", "medium", "high" - ] + ], + "visibility": "hidden" } }, - "attachment": false, - "open_weights": true, - "knowledge": "2024-06", - "release_date": "2025-08-05", - "last_updated": "2025-08-05", + "attachment": true, + "open_weights": false, + "knowledge": "2025-12-01", + "release_date": "2026-04-23", + "last_updated": "2026-04-23", "type": "chat" }, { - "id": "arkanecloud/meta-llama/Llama-3.3-70B-Instruct", - "name": "Llama-3.3-70B-Instruct (Arkanecloud)", - "display_name": "Llama-3.3-70B-Instruct (Arkanecloud)", + "id": "openai/gpt-4-turbo", + "name": "GPT-4 Turbo", + "display_name": "GPT-4 Turbo", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" @@ -294035,127 +294554,121 @@ "reasoning": { "supported": false }, - "attachment": false, - "open_weights": true, + "attachment": true, + "open_weights": false, "knowledge": "2023-12", - "release_date": "2024-12-06", - "last_updated": "2024-12-06", + "release_date": "2023-11-06", + "last_updated": "2024-04-09", "type": "chat" }, { - "id": "arkanecloud/meta-llama/Llama-3.1-8B-Instruct", - "name": "Llama-3.1-8B-Instruct (Arkanecloud)", - "display_name": "Llama-3.1-8B-Instruct (Arkanecloud)", + "id": "openai/gpt-5.2-chat-latest", + "name": "GPT-5.2 Chat Latest", + "display_name": "GPT-5.2 Chat Latest", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 4096 + "context": 128000, + "output": 16384 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": false }, - "attachment": false, - "open_weights": true, - "knowledge": "2023-12", - "release_date": "2024-07-23", - "last_updated": "2024-07-23", + "attachment": true, + "open_weights": false, + "knowledge": "2025-08-31", + "release_date": "2025-12-11", + "last_updated": "2025-12-11", "type": "chat" }, { - "id": "arkanecloud/meta-llama/Llama-3.1-70B-Instruct", - "name": "Llama-3.1-70B-Instruct (Arkanecloud)", - "display_name": "Llama-3.1-70B-Instruct (Arkanecloud)", + "id": "openai/gpt-4.1-mini", + "name": "GPT-4.1 Mini", + "display_name": "GPT-4.1 Mini", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 4096 + "context": 1047576, + "output": 32768 }, "temperature": true, "tool_call": true, "reasoning": { "supported": false }, - "attachment": false, - "open_weights": true, - "knowledge": "2023-12", - "release_date": "2024-07-23", - "last_updated": "2024-07-23", + "attachment": true, + "open_weights": false, + "knowledge": "2024-04", + "release_date": "2025-04-14", + "last_updated": "2025-04-14", "type": "chat" }, { - "id": "arkanecloud/deepseek-ai/DeepSeek-V4.1-Flash", - "name": "DeepSeek V4.1 Flash (Arkanecloud)", - "display_name": "DeepSeek V4.1 Flash (Arkanecloud)", + "id": "openai/gpt-4.1", + "name": "GPT-4.1", + "display_name": "GPT-4.1", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 384000 + "context": 1047576, + "output": 32768 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "effort_options": [ - "none", - "low", - "high", - "max" - ] - } + "supported": false }, "attachment": true, - "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-09-10", - "last_updated": "2026-09-10", + "open_weights": false, + "knowledge": "2024-04", + "release_date": "2025-04-14", + "last_updated": "2025-04-14", "type": "chat" }, { - "id": "arkanecloud/deepseek-ai/DeepSeek-V4-Flash-0731", - "name": "DeepSeek V4 Flash 0731 (Arkanecloud)", - "display_name": "DeepSeek V4 Flash 0731 (Arkanecloud)", + "id": "openai/gpt-6-luna", + "name": "GPT-6 Luna", + "display_name": "GPT-6 Luna", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 384000 + "context": 1050000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -294163,78 +294676,70 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, "effort_options": [ "none", "low", + "medium", "high", + "xhigh", "max" ] } }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-07-31", - "last_updated": "2026-07-31", + "attachment": true, + "open_weights": false, + "knowledge": "2026-05-18", + "release_date": "2026-09-22", + "last_updated": "2026-09-22", "type": "chat" }, { - "id": "arkanecloud/deepseek-ai/DeepSeek-V4.1-Flash-1017", - "name": "DeepSeek V4.1 Flash 1017 (Arkanecloud)", - "display_name": "DeepSeek V4.1 Flash 1017 (Arkanecloud)", + "id": "openai/gpt-5.1-chat-latest", + "name": "GPT-5.1 Chat Latest", + "display_name": "GPT-5.1 Chat Latest", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 384000 + "context": 128000, + "output": 16384 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "none", - "low", - "high", - "max" - ] - } + "supported": false }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-10-17", - "last_updated": "2026-10-17", + "attachment": true, + "open_weights": false, + "knowledge": "2024-09-30", + "release_date": "2025-11-13", + "last_updated": "2025-11-13", "type": "chat" }, { - "id": "moonshot/kimi-k3", - "name": "Kimi K3", - "display_name": "Kimi K3", + "id": "openai/o3", + "name": "o3", + "display_name": "o3", "modalities": { "input": [ "text", "image", - "video" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 200000, + "output": 100000 }, "temperature": false, "tool_call": true, @@ -294245,114 +294750,71 @@ "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", "effort_options": [ "low", - "high", - "max" - ] + "medium", + "high" + ], + "visibility": "hidden" } }, "attachment": true, - "open_weights": true, - "release_date": "2026-07-16", - "last_updated": "2026-07-16", + "open_weights": false, + "knowledge": "2024-05", + "release_date": "2025-04-16", + "last_updated": "2025-04-16", "type": "chat" }, { - "id": "moonshot/kimi-k2.6", - "name": "Kimi K2.6", - "display_name": "Kimi K2.6", + "id": "openai/gpt-audio", + "name": "GPT Audio", + "display_name": "GPT Audio", "modalities": { "input": [ "text", - "image", - "video" + "audio" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 128000, + "output": 16384 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true - } - }, - "attachment": true, - "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-04-20", - "last_updated": "2026-04-20", - "type": "chat" - }, - { - "id": "moonshot/kimi-k3.1-pro", - "name": "Kimi K3.1 Pro", - "display_name": "Kimi K3.1 Pro", - "modalities": { - "input": [ - "text", - "image", - "video" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 1048576, - "output": 131072 - }, - "temperature": false, - "tool_call": true, - "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "low", - "high", - "max" - ] - } + "supported": false }, "attachment": true, "open_weights": false, - "release_date": "2026-10-23", - "last_updated": "2026-10-23", + "knowledge": "2023-10-01", + "release_date": "2025-08-28", + "last_updated": "2025-08-28", "type": "chat" }, { - "id": "moonshot/kimi-k2.7-code", - "name": "Kimi K2.7 Code", - "display_name": "Kimi K2.7 Code", + "id": "openai/gpt-oss-20b", + "name": "GPT-OSS 20B", + "display_name": "GPT-OSS 20B", "modalities": { "input": [ - "text", - "image", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 128000, + "output": 32000 }, - "temperature": false, - "tool_call": true, + "temperature": true, + "tool_call": false, "reasoning": { "supported": true, "default": true @@ -294362,66 +294824,69 @@ "supported": true } }, - "attachment": true, + "attachment": false, "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-06-12", - "last_updated": "2026-06-12", + "knowledge": "2024-06", + "release_date": "2025-08-05", + "last_updated": "2025-08-05", "type": "chat" }, { - "id": "moonshot/kimi-k2.7-code-highspeed", - "name": "Kimi K2.7 Code Highspeed", - "display_name": "Kimi K2.7 Code Highspeed", + "id": "sakana/fugu-max", + "name": "Fugu Max", + "display_name": "Fugu Max", "modalities": { "input": [ "text", - "image", - "video" + "image" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 1000000, + "output": 128000 }, "temperature": false, - "tool_call": false, + "tool_call": true, "reasoning": { "supported": true, "default": true }, "extra_capabilities": { "reasoning": { - "supported": true + "effort_options": [ + "high", + "xhigh", + "max" + ] } }, "attachment": true, - "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-06-12", - "last_updated": "2026-06-12", + "open_weights": false, + "release_date": "2026-09-11", + "last_updated": "2026-09-11", "type": "chat" }, { - "id": "sference/deepseek-ai/DeepSeek-V4-Flash-0731", - "name": "DeepSeek V4 Flash 0731 (Sference)", - "display_name": "DeepSeek V4 Flash 0731 (Sference)", + "id": "sakana/fugu-ultra", + "name": "Fugu Ultra", + "display_name": "Fugu Ultra", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 384000 + "context": 1000000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -294429,26 +294894,23 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, "effort_options": [ - "none", - "low", "high", + "xhigh", "max" ] } }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-07-31", - "last_updated": "2026-07-31", + "attachment": true, + "open_weights": false, + "release_date": "2026-06-15", + "last_updated": "2026-06-15", "type": "chat" }, { - "id": "sference/deepseek-ai/DeepSeek-V4.1-Flash-1017", - "name": "DeepSeek V4.1 Flash 1017 (Sference)", - "display_name": "DeepSeek V4.1 Flash 1017 (Sference)", + "id": "sakana/sakana-namazu", + "name": "Sakana Namazu", + "display_name": "Sakana Namazu", "modalities": { "input": [ "text", @@ -294459,8 +294921,8 @@ ] }, "limit": { - "context": 1048576, - "output": 384000 + "context": 262144, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -294468,32 +294930,19 @@ "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "none", - "low", - "high", - "max" - ] - } - }, "attachment": true, - "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-10-17", - "last_updated": "2026-10-17", + "open_weights": false, + "release_date": "2026-08-03", + "last_updated": "2026-08-03", "type": "chat" }, { - "id": "qwen/qwen3.8-max-0902", - "name": "Qwen3.8 Max 0902", - "display_name": "Qwen3.8 Max 0902", + "id": "nvidia/nemotron-3.5-lightning-30b-a3b", + "name": "Nemotron 3.5 Lightning 30B A3B", + "display_name": "Nemotron 3.5 Lightning 30B A3B", "modalities": { "input": [ - "text", - "image", - "video" + "text" ], "output": [ "text" @@ -294501,7 +294950,7 @@ }, "limit": { "context": 1000000, - "output": 131072 + "output": 262144 }, "temperature": true, "tool_call": true, @@ -294515,114 +294964,90 @@ "none", "low", "medium", - "xhigh" + "high" ] } }, - "attachment": true, - "open_weights": false, - "release_date": "2026-09-02", - "last_updated": "2026-09-02", + "attachment": false, + "open_weights": true, + "knowledge": "2025-09", + "release_date": "2026-08-11", + "last_updated": "2026-08-11", "type": "chat" }, { - "id": "qwen/qwen3.8-27b", - "name": "Qwen3.8 27B", - "display_name": "Qwen3.8 27B", + "id": "nvidia/nemotron-nano-9b-v2", + "name": "Nemotron Nano 9B", + "display_name": "Nemotron Nano 9B", "modalities": { "input": [ - "text", - "image", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 32768 + "context": 128000, + "output": 8192 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "none", - "low", - "medium", - "xhigh" - ] - } - }, - "attachment": true, + "attachment": false, "open_weights": true, - "release_date": "2026-08-14", - "last_updated": "2026-08-14", + "knowledge": "2024-09", + "release_date": "2025-08-18", + "last_updated": "2025-08-18", "type": "chat" }, { - "id": "qwen/deepseek-v4.1-flash-1017", - "name": "DeepSeek V4.1 Flash 1017 (Alibaba)", - "display_name": "DeepSeek V4.1 Flash 1017 (Alibaba)", + "id": "nvidia/nemotron-3-nano-30b-a3b", + "name": "Nemotron 3 Nano 30B A3B", + "display_name": "Nemotron 3 Nano 30B A3B", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 384000 + "context": 256000, + "output": 32000 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "none", - "low", - "high", - "max" - ] - } - }, - "attachment": true, + "attachment": false, "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-10-17", - "last_updated": "2026-10-17", + "knowledge": "2025-06-25", + "release_date": "2025-12-15", + "last_updated": "2025-12-15", "type": "chat" }, { - "id": "qwen/qwen3.8-omni-flash", - "name": "Qwen3.8 Omni Flash", - "display_name": "Qwen3.8 Omni Flash", + "id": "thinkingmachines/inkling", + "name": "Inkling", + "display_name": "Inkling", "modalities": { "input": [ - "text", - "image", - "audio", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 1048000, + "output": 32000 }, "temperature": true, "tool_call": true, @@ -294630,37 +295055,28 @@ "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "none", - "low", - "medium", - "xhigh" - ] - } - }, - "attachment": true, - "open_weights": false, - "release_date": "2026-09-17", - "last_updated": "2026-09-17", + "attachment": false, + "open_weights": true, + "release_date": "2026-07-15", + "last_updated": "2026-07-15", "type": "chat" }, { - "id": "qwen/qwen3.8-2.4t-a95b", - "name": "Qwen3.8 2.4T A95B", - "display_name": "Qwen3.8 2.4T A95B", + "id": "xai/grok-build-0.1", + "name": "Grok Build 0.1", + "display_name": "Grok Build 0.1", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 256000, + "output": 256000 }, "temperature": true, "tool_call": true, @@ -294670,37 +295086,31 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "effort_options": [ - "low", - "medium", - "xhigh" - ] + "supported": true } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-08-12", - "last_updated": "2026-08-12", + "attachment": true, + "open_weights": false, + "release_date": "2026-04-16", + "last_updated": "2026-04-16", "type": "chat" }, { - "id": "qwen/qwen3.8-max", - "name": "Qwen3.8 Max", - "display_name": "Qwen3.8 Max", + "id": "xai/grok-4.7", + "name": "Grok 4.7", + "display_name": "Grok 4.7", "modalities": { "input": [ "text", - "image", - "video" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 500000, + "output": 500000 }, "temperature": true, "tool_call": true, @@ -294710,25 +295120,26 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, "effort_options": [ - "none", + "minimal", "low", "medium", + "high", "xhigh" ] } }, "attachment": true, "open_weights": false, - "release_date": "2026-08-03", - "last_updated": "2026-08-03", + "knowledge": "2026-05", + "release_date": "2026-09-21", + "last_updated": "2026-09-21", "type": "chat" }, { - "id": "qwen/deepseek-v4.1-flash", - "name": "DeepSeek V4.1 Flash (Alibaba)", - "display_name": "DeepSeek V4.1 Flash (Alibaba)", + "id": "xai/grok-4.5", + "name": "Grok 4.5", + "display_name": "Grok 4.5", "modalities": { "input": [ "text", @@ -294739,8 +295150,8 @@ ] }, "limit": { - "context": 1000000, - "output": 384000 + "context": 500000, + "output": 500000 }, "temperature": true, "tool_call": true, @@ -294752,27 +295163,26 @@ "reasoning": { "supported": true, "effort_options": [ - "none", "low", - "high", - "max" + "medium", + "high" ] } }, "attachment": true, - "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-09-10", - "last_updated": "2026-09-10", + "open_weights": false, + "release_date": "2026-07-08", + "last_updated": "2026-07-08", "type": "chat" }, { - "id": "qwen/deepseek-v4-flash-0731", - "name": "DeepSeek V4 Flash 0731 (Alibaba)", - "display_name": "DeepSeek V4 Flash 0731 (Alibaba)", + "id": "xai/grok-4.20-0309-reasoning", + "name": "Grok 4.20", + "display_name": "Grok 4.20", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" @@ -294780,7 +295190,7 @@ }, "limit": { "context": 1000000, - "output": 384000 + "output": 1000000 }, "temperature": true, "tool_call": true, @@ -294788,31 +295198,20 @@ "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "effort_options": [ - "none", - "low", - "high", - "max" - ] - } - }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-07-31", - "last_updated": "2026-07-31", + "attachment": true, + "open_weights": false, + "release_date": "2026-03-09", + "last_updated": "2026-03-09", "type": "chat" }, { - "id": "qwen/deepseek-v4-pro-0813", - "name": "DeepSeek V4 Pro 0813 (Alibaba)", - "display_name": "DeepSeek V4 Pro 0813 (Alibaba)", + "id": "xai/grok-4.3", + "name": "Grok 4.3", + "display_name": "Grok 4.3", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" @@ -294820,7 +295219,7 @@ }, "limit": { "context": 1000000, - "output": 384000 + "output": 1000000 }, "temperature": true, "tool_call": true, @@ -294830,30 +295229,23 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "effort_options": [ - "none", - "low", - "high", - "max" - ] + "supported": true } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-08-13", - "last_updated": "2026-08-22", + "attachment": true, + "open_weights": false, + "release_date": "2026-04-17", + "last_updated": "2026-04-17", "type": "chat" }, { - "id": "qwen/qwen3.8-flash", - "name": "Qwen3.8 Flash", - "display_name": "Qwen3.8 Flash", + "id": "xai/grok-4.20-0309-non-reasoning", + "name": "Grok 4.20 Non-Reasoning", + "display_name": "Grok 4.20 Non-Reasoning", "modalities": { "input": [ "text", - "image", - "video" + "image" ], "output": [ "text" @@ -294861,63 +295253,64 @@ }, "limit": { "context": 1000000, - "output": 131072 + "output": 1000000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "effort_options": [ - "none", - "low", - "medium", - "xhigh" - ] - } + "supported": false }, "attachment": true, "open_weights": false, - "release_date": "2026-08-26", - "last_updated": "2026-08-26", + "release_date": "2026-03-09", + "last_updated": "2026-03-09", "type": "chat" }, { - "id": "qwen/qwen-max", - "name": "Qwen Max", - "display_name": "Qwen Max", + "id": "xai/grok-4.6", + "name": "Grok 4.6", + "display_name": "Grok 4.6", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 32768, - "output": 8192 + "context": 500000, + "output": 500000 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, + "extra_capabilities": { + "reasoning": { + "supported": true, + "effort_options": [ + "low", + "medium", + "high", + "xhigh" + ] + } + }, + "attachment": true, "open_weights": false, - "knowledge": "2024-04", - "release_date": "2024-04-03", - "last_updated": "2025-01-25", + "knowledge": "2026-02-01", + "release_date": "2026-08-12", + "last_updated": "2026-08-12", "type": "chat" }, { - "id": "qwen/qwen3-coder-flash", - "name": "Qwen3 Coder Flash", - "display_name": "Qwen3 Coder Flash", + "id": "cohere/command-r-08-2024", + "name": "Command R 08-2024", + "display_name": "Command R 08-2024", "modalities": { "input": [ "text" @@ -294927,25 +295320,25 @@ ] }, "limit": { - "context": 1000000, - "output": 65536 + "context": 128000, + "output": 4000 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { "supported": false }, "attachment": false, - "open_weights": false, - "knowledge": "2025-04", - "release_date": "2025-07-28", - "last_updated": "2025-07-28", + "open_weights": true, + "knowledge": "2024-06-01", + "release_date": "2024-08-30", + "last_updated": "2024-08-30", "type": "chat" }, { - "id": "qwen/qwen-mt-plus", - "name": "Qwen MT Plus", - "display_name": "Qwen MT Plus", + "id": "cohere/command-r-plus-08-2024", + "name": "Command R+ 08-2024", + "display_name": "Command R+ 08-2024", "modalities": { "input": [ "text" @@ -294955,122 +295348,92 @@ ] }, "limit": { - "context": 4096, - "output": 8192 + "context": 128000, + "output": 4000 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { "supported": false }, "attachment": false, - "open_weights": false, - "release_date": "2025-07-24", - "last_updated": "2025-07-24", + "open_weights": true, + "knowledge": "2024-06-01", + "release_date": "2024-08-30", + "last_updated": "2024-08-30", "type": "chat" }, { - "id": "meta/muse-wonder-1.0", - "name": "Muse Wonder (Meta)", - "display_name": "Muse Wonder (Meta)", + "id": "cohere/command-r7b-12-2024", + "name": "Command R7B 12-2024", + "display_name": "Command R7B 12-2024", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 128000, + "output": 4000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "minimal", - "low", - "medium", - "high", - "xhigh", - "max" - ] - } + "supported": false }, - "attachment": true, - "open_weights": false, - "release_date": "2026-10-12", - "last_updated": "2026-10-12", + "attachment": false, + "open_weights": true, + "knowledge": "2024-06-01", + "release_date": "2024-12-13", + "last_updated": "2024-12-13", "type": "chat" }, { - "id": "meta/muse-spark-1.3", - "name": "Muse Spark 1.3 (Meta)", - "display_name": "Muse Spark 1.3 (Meta)", + "id": "cohere/command-a-03-2025", + "name": "Command A 03-2025", + "display_name": "Command A 03-2025", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 256000, + "output": 8000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "effort_options": [ - "minimal", - "low", - "medium", - "high", - "xhigh", - "max" - ] - } + "supported": false }, - "attachment": true, - "open_weights": false, - "release_date": "2026-09-02", - "last_updated": "2026-09-02", + "attachment": false, + "open_weights": true, + "knowledge": "2024-06-01", + "release_date": "2025-03-13", + "last_updated": "2025-03-13", "type": "chat" }, { - "id": "meta/muse-spark-1.1", - "name": "Muse Spark 1.1 (Meta)", - "display_name": "Muse Spark 1.1 (Meta)", + "id": "minimax/minimax-m2.5", + "name": "MiniMax M2.5", + "display_name": "MiniMax M2.5", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 204800, + "output": 8192 }, "temperature": true, "tool_call": true, @@ -295080,39 +295443,30 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "effort_options": [ - "minimal", - "low", - "medium", - "high", - "xhigh" - ] + "supported": true } }, - "attachment": true, - "open_weights": false, - "release_date": "2026-04-08", - "last_updated": "2026-07-09", + "attachment": false, + "open_weights": true, + "release_date": "2026-02-12", + "last_updated": "2026-02-12", "type": "chat" }, { - "id": "meta/muse-spark-1.2", - "name": "Muse Spark 1.2 (Meta)", - "display_name": "Muse Spark 1.2 (Meta)", + "id": "minimax/minimax-m2.5-highspeed", + "name": "MiniMax M2.5 Highspeed", + "display_name": "MiniMax M2.5 Highspeed", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 204800, + "output": 8192 }, "temperature": true, "tool_call": true, @@ -295122,26 +295476,19 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "effort_options": [ - "minimal", - "low", - "medium", - "high", - "xhigh" - ] + "supported": true } }, - "attachment": true, - "open_weights": false, - "release_date": "2026-08-05", - "last_updated": "2026-08-05", + "attachment": false, + "open_weights": true, + "release_date": "2026-02-13", + "last_updated": "2026-02-13", "type": "chat" }, { - "id": "ovhcloud/gpt-oss-20b", - "name": "GPT OSS 20B (OVHcloud)", - "display_name": "GPT OSS 20B (OVHcloud)", + "id": "minimax/minimax-m2.7-highspeed", + "name": "MiniMax M2.7 Highspeed", + "display_name": "MiniMax M2.7 Highspeed", "modalities": { "input": [ "text" @@ -295151,8 +295498,8 @@ ] }, "limit": { - "context": 131072, - "output": 32768 + "context": 204800, + "output": 8192 }, "temperature": true, "tool_call": true, @@ -295163,24 +295510,24 @@ "extra_capabilities": { "reasoning": { "supported": true, - "effort_options": [ - "low", - "medium", - "high" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, "attachment": false, "open_weights": true, - "knowledge": "2024-06", - "release_date": "2025-08-05", - "last_updated": "2025-08-05", + "release_date": "2026-03-18", + "last_updated": "2026-03-18", "type": "chat" }, { - "id": "ovhcloud/gpt-oss-120b", - "name": "GPT OSS 120B (OVHcloud)", - "display_name": "GPT OSS 120B (OVHcloud)", + "id": "minimax/minimax-m2", + "name": "MiniMax M2", + "display_name": "MiniMax M2", "modalities": { "input": [ "text" @@ -295190,8 +295537,8 @@ ] }, "limit": { - "context": 131072, - "output": 32768 + "context": 204800, + "output": 8192 }, "temperature": true, "tool_call": true, @@ -295199,41 +295546,20 @@ "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "effort_options": [ - "low", - "medium", - "high" - ] - } - }, "attachment": false, "open_weights": true, - "knowledge": "2024-06", - "release_date": "2025-08-05", - "last_updated": "2025-08-05", + "release_date": "2025-10-27", + "last_updated": "2025-10-27", "type": "chat" - } - ] - }, - "minimax-cn-coding-plan": { - "id": "minimax-cn-coding-plan", - "name": "MiniMax Token Plan (minimax.cn)", - "display_name": "MiniMax Token Plan (minimax.cn)", - "api": "https://api.minimax.cn/anthropic/v1", - "doc": "https://platform.minimaxi.com/docs/token-plan/intro", - "models": [ + }, { - "id": "MiniMax-M3.1-Flash", - "name": "MiniMax-M3.1-Flash", - "display_name": "MiniMax-M3.1-Flash", + "id": "minimax/minimax-m3", + "name": "MiniMax M3", + "display_name": "MiniMax M3", "modalities": { "input": [ "text", - "image", - "video" + "image" ], "output": [ "text" @@ -295241,7 +295567,7 @@ }, "limit": { "context": 1000000, - "output": 524288 + "output": 128000 }, "temperature": true, "tool_call": true, @@ -295251,25 +295577,19 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" - ] + "supported": true } }, "attachment": true, - "open_weights": false, - "release_date": "2026-10-19", - "last_updated": "2026-10-19", + "open_weights": true, + "release_date": "2026-06-01", + "last_updated": "2026-06-01", "type": "chat" }, { - "id": "MiniMax-M2.5-highspeed", - "name": "MiniMax-M2.5-highspeed", - "display_name": "MiniMax-M2.5-highspeed", + "id": "minimax/minimax-m2.1", + "name": "MiniMax M2.1", + "display_name": "MiniMax M2.1", "modalities": { "input": [ "text" @@ -295280,7 +295600,7 @@ }, "limit": { "context": 204800, - "output": 131072 + "output": 8192 }, "temperature": true, "tool_call": true, @@ -295295,14 +295615,14 @@ }, "attachment": false, "open_weights": true, - "release_date": "2026-02-13", - "last_updated": "2026-02-13", + "release_date": "2025-12-23", + "last_updated": "2025-12-23", "type": "chat" }, { - "id": "MiniMax-M2.7-highspeed", - "name": "MiniMax-M2.7-highspeed", - "display_name": "MiniMax-M2.7-highspeed", + "id": "minimax/minimax-m2.7", + "name": "MiniMax M2.7", + "display_name": "MiniMax M2.7", "modalities": { "input": [ "text" @@ -295313,7 +295633,7 @@ }, "limit": { "context": 204800, - "output": 131072 + "output": 8192 }, "temperature": true, "tool_call": true, @@ -295339,14 +295659,13 @@ "type": "chat" }, { - "id": "MiniMax-M3.1-Flash-Preview", - "name": "MiniMax-M3.1-Flash-Preview", - "display_name": "MiniMax-M3.1-Flash-Preview", + "id": "minimax/minimax-m3.1-flash", + "name": "MiniMax M3.1 Flash", + "display_name": "MiniMax M3.1 Flash", "modalities": { "input": [ "text", - "image", - "video" + "image" ], "output": [ "text" @@ -295354,7 +295673,7 @@ }, "limit": { "context": 1000000, - "output": 512000 + "output": 128000 }, "temperature": true, "tool_call": true, @@ -295375,14 +295694,14 @@ }, "attachment": true, "open_weights": false, - "release_date": "2026-09-27", - "last_updated": "2026-09-27", + "release_date": "2026-10-19", + "last_updated": "2026-10-19", "type": "chat" }, { - "id": "MiniMax-M2", - "name": "MiniMax-M2", - "display_name": "MiniMax-M2", + "id": "bytedance/dola-seed-2.0-code-preview", + "name": "Dola Seed 2.0 Code (preview)", + "display_name": "Dola Seed 2.0 Code (preview)", "modalities": { "input": [ "text" @@ -295392,173 +295711,137 @@ ] }, "limit": { - "context": 204800, - "output": 131072 + "context": 131072, + "output": 32768 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true + "supported": false }, "attachment": false, - "open_weights": true, - "release_date": "2025-10-27", - "last_updated": "2025-10-27", + "open_weights": false, + "release_date": "2026-03-28", + "last_updated": "2026-03-28", "type": "chat" }, { - "id": "MiniMax-M3", - "name": "MiniMax-M3", - "display_name": "MiniMax-M3", + "id": "bytedance/dola-seed-2.0-code", + "name": "Seed 2.0 Code", + "display_name": "Seed 2.0 Code", "modalities": { "input": [ "text", - "image", - "video" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 524288 + "context": 256000, + "output": 128000 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true - } + "supported": false }, "attachment": true, - "open_weights": true, - "release_date": "2026-06-01", - "last_updated": "2026-06-25", + "open_weights": false, + "release_date": "2026-02-14", + "last_updated": "2026-02-14", "type": "chat" }, { - "id": "MiniMax-M2.1", - "name": "MiniMax-M2.1", - "display_name": "MiniMax-M2.1", + "id": "bytedance/dola-seed-2.0-lite", + "name": "Seed 2.0 Lite", + "display_name": "Seed 2.0 Lite", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 204800, - "output": 131072 + "context": 256000, + "output": 128000 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true - } + "supported": false }, - "attachment": false, - "open_weights": true, - "release_date": "2025-12-23", - "last_updated": "2025-12-23", + "attachment": true, + "open_weights": false, + "release_date": "2026-02-14", + "last_updated": "2026-02-14", "type": "chat" }, { - "id": "MiniMax-M2.7", - "name": "MiniMax-M2.7", - "display_name": "MiniMax-M2.7", + "id": "bytedance/dola-seed-2.0-pro", + "name": "Seed 2.0 Pro", + "display_name": "Seed 2.0 Pro", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 204800, - "output": 131072 + "context": 256000, + "output": 128000 }, - "temperature": true, - "tool_call": true, + "temperature": false, + "tool_call": false, "reasoning": { "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } - }, - "attachment": false, - "open_weights": true, - "release_date": "2026-03-18", - "last_updated": "2026-03-18", + "attachment": true, + "open_weights": false, + "release_date": "2026-02-14", + "last_updated": "2026-02-14", "type": "chat" }, { - "id": "MiniMax-M2.5", - "name": "MiniMax-M2.5", - "display_name": "MiniMax-M2.5", + "id": "bytedance/dola-seed-2.0-mini", + "name": "Seed 2.0 Mini", + "display_name": "Seed 2.0 Mini", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 204800, - "output": 131072 + "context": 256000, + "output": 128000 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true - } + "supported": false }, - "attachment": false, - "open_weights": true, - "release_date": "2026-02-12", - "last_updated": "2026-02-12", + "attachment": true, + "open_weights": false, + "release_date": "2026-02-14", + "last_updated": "2026-02-14", "type": "chat" - } - ] - }, - "togetherai": { - "id": "togetherai", - "name": "Together AI", - "display_name": "Together AI", - "doc": "https://docs.together.ai/docs/serverless-models", - "models": [ + }, { - "id": "essentialai/Rnj-1-Instruct", - "name": "Rnj-1 Instruct", - "display_name": "Rnj-1 Instruct", + "id": "writer/palmyra-x4", + "name": "Palmyra X4", + "display_name": "Palmyra X4", "modalities": { "input": [ "text" @@ -295568,8 +295851,8 @@ ] }, "limit": { - "context": 32768, - "output": 32768 + "context": 128000, + "output": 32000 }, "temperature": true, "tool_call": true, @@ -295577,15 +295860,15 @@ "supported": false }, "attachment": false, - "open_weights": true, - "release_date": "2025-12-04", - "last_updated": "2025-12-04", + "open_weights": false, + "release_date": "2024-10-09", + "last_updated": "2025-04-28", "type": "chat" }, { - "id": "zai-org/GLM-5.1", - "name": "GLM-5.1", - "display_name": "GLM-5.1", + "id": "writer/palmyra-x5", + "name": "Palmyra X5", + "display_name": "Palmyra X5", "modalities": { "input": [ "text" @@ -295595,43 +295878,38 @@ ] }, "limit": { - "context": 202752, - "output": 131072 + "context": 1000000, + "output": 250000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true - } + "supported": false }, "attachment": false, - "open_weights": true, - "release_date": "2026-04-07", - "last_updated": "2026-04-07", + "open_weights": false, + "release_date": "2025-04-28", + "last_updated": "2025-04-28", "type": "chat" }, { - "id": "zai-org/GLM-5.3-Flash", - "name": "GLM-5.3-Flash", - "display_name": "GLM-5.3-Flash", + "id": "google/gemini-3.5-flash", + "name": "Gemini 3.5 Flash", + "display_name": "Gemini 3.5 Flash", "modalities": { "input": [ "text", "image", - "video" + "audio", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1048575, - "output": 400000 + "context": 1048576, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -295642,68 +295920,68 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, + "default_enabled": true, + "mode": "level", + "level": "high", + "level_options": [ + "minimal", + "low", + "medium", + "high" + ], "summaries": true, "visibility": "summary", "continuation": [ - "thinking_blocks" - ], - "effort_options": [ - "low", - "high", - "max" + "thought_signatures" ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-08-26", - "last_updated": "2026-08-26", + "open_weights": false, + "knowledge": "2025-01", + "release_date": "2026-05-19", + "last_updated": "2026-05-19", "type": "chat" }, { - "id": "zai-org/GLM-5", - "name": "GLM-5", - "display_name": "GLM-5", + "id": "google/gemini-3.1-flash-image", + "name": "Gemini 3.1 Flash Image", + "display_name": "Gemini 3.1 Flash Image", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ - "text" + "text", + "image" ] }, "limit": { - "context": 202752, - "output": 131072 + "context": 32768, + "output": 32768 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { "supported": true, "default": true }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] + "supported": true } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-02-12", - "last_updated": "2026-02-12", - "type": "chat" + "attachment": true, + "open_weights": false, + "knowledge": "2025-01", + "release_date": "2026-05-28", + "last_updated": "2026-05-28" }, { - "id": "zai-org/GLM-5.2", - "name": "GLM-5.2", - "display_name": "GLM-5.2", + "id": "google/gemma-4-26b-a4b-it", + "name": "Gemma 4 26B A4B", + "display_name": "Gemma 4 26B A4B", "modalities": { "input": [ "text" @@ -295713,51 +295991,42 @@ ] }, "limit": { - "context": 1048575, - "output": 164000 + "context": 262144, + "output": 65536 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { "supported": true, "default": true }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "effort_options": [ - "high", - "max" - ] + "supported": true } }, "attachment": false, "open_weights": true, - "release_date": "2026-06-13", - "last_updated": "2026-06-13", + "release_date": "2026-04-02", + "last_updated": "2026-04-02", "type": "chat" }, { - "id": "zai-org/GLM-5.3", - "name": "GLM-5.3", - "display_name": "GLM-5.3", + "id": "google/gemini-2.5-computer-use-preview-10-2025", + "name": "Gemini 2.5 Computer Use Preview (10-2025)", + "display_name": "Gemini 2.5 Computer Use Preview (10-2025)", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 1048575, - "output": 262144 + "context": 128000, + "output": 64000 }, "temperature": true, "tool_call": true, @@ -295765,109 +296034,111 @@ "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "effort_options": [ - "low", - "high", - "max" - ] - } - }, - "attachment": false, - "open_weights": true, - "release_date": "2026-08-14", - "last_updated": "2026-08-14", + "attachment": true, + "open_weights": false, + "knowledge": "2025-01", + "release_date": "2025-10-07", + "last_updated": "2025-10-07", "type": "chat" }, { - "id": "Qwen/Qwen3.5-397B-A17B", - "name": "Qwen3.5 397B A17B", - "display_name": "Qwen3.5 397B A17B", + "id": "google/gemini-4-argon", + "name": "Gemini 4 Argon", + "display_name": "Gemini 4 Argon", "modalities": { "input": [ "text", - "image" + "image", + "video", + "audio", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 130000 + "context": 1048576, + "output": 65536 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } - }, "attachment": true, - "open_weights": true, - "release_date": "2026-02-15", - "last_updated": "2026-02-15", + "open_weights": false, + "knowledge": "2026-03", + "release_date": "2026-10-13", + "last_updated": "2026-10-13", "type": "chat" }, { - "id": "Qwen/Qwen3-235B-A22B-Instruct-2507-tput", - "name": "Qwen3 235B A22B Instruct 2507 FP8", - "display_name": "Qwen3 235B A22B Instruct 2507 FP8", + "id": "google/gemini-3-pro-image", + "name": "Gemini 3 Pro Image", + "display_name": "Gemini 3 Pro Image", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ - "text" + "text", + "image" ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 65536, + "output": 65536 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, - "open_weights": true, - "release_date": "2025-07-21", - "last_updated": "2025-07-21", - "type": "chat" + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "level", + "level": "high", + "level_options": [ + "low", + "high" + ], + "summaries": true, + "visibility": "summary", + "continuation": [ + "thought_signatures" + ] + } + }, + "attachment": true, + "open_weights": false, + "knowledge": "2025-01", + "release_date": "2026-05-28", + "last_updated": "2026-05-28" }, { - "id": "Qwen/Qwen3.6-Plus", - "name": "Qwen3.6 Plus", - "display_name": "Qwen3.6 Plus", + "id": "google/gemini-flash-latest", + "name": "Gemini Flash Latest", + "display_name": "Gemini Flash Latest", "modalities": { "input": [ - "text" + "text", + "image", + "video", + "audio", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1000000, + "context": 1048576, "output": 65536 }, "temperature": true, @@ -295878,81 +296149,106 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "minimal", + "low", + "medium", + "high" ] } }, - "attachment": false, + "attachment": true, "open_weights": false, - "knowledge": "2025-04", - "release_date": "2026-04-02", - "last_updated": "2026-04-02", + "knowledge": "2026-03", + "release_date": "2026-08-13", + "last_updated": "2026-08-13", "type": "chat" }, { - "id": "Qwen/Qwen2.5-7B-Instruct-Turbo", - "name": "Qwen 2.5 7B Instruct Turbo", - "display_name": "Qwen 2.5 7B Instruct Turbo", + "id": "google/gemini-3.6-flash", + "name": "Gemini 3.6 Flash", + "display_name": "Gemini 3.6 Flash", "modalities": { "input": [ - "text" + "text", + "image", + "video", + "audio", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 32768, - "output": 32768 + "context": 1048576, + "output": 65536 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, - "open_weights": true, - "release_date": "2024-09-19", - "last_updated": "2024-09-19", + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "level", + "level": "high", + "level_options": [ + "minimal", + "low", + "medium", + "high" + ], + "summaries": true, + "visibility": "summary", + "continuation": [ + "thought_signatures" + ] + } + }, + "attachment": true, + "open_weights": false, + "knowledge": "2026-03", + "release_date": "2026-07-21", + "last_updated": "2026-07-21", "type": "chat" }, { - "id": "Qwen/Qwen3-Coder-Next-FP8", - "name": "Qwen3 Coder Next FP8", - "display_name": "Qwen3 Coder Next FP8", + "id": "google/gemma-3-12b-it", + "name": "Gemma 3 12B", + "display_name": "Gemma 3 12B", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 128000, + "output": 8192 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { "supported": false }, - "attachment": false, + "attachment": true, "open_weights": true, - "knowledge": "2025-09", - "release_date": "2026-02-03", - "last_updated": "2026-02-03", + "knowledge": "2024-08", + "release_date": "2025-03-12", + "last_updated": "2025-03-12", "type": "chat" }, { - "id": "Qwen/Qwen3-Coder-480B-A35B-Instruct-FP8", - "name": "Qwen3 Coder 480B A35B Instruct", - "display_name": "Qwen3 Coder 480B A35B Instruct", + "id": "google/gemma-3-27b-it", + "name": "Gemma 3 27B IT", + "display_name": "Gemma 3 27B IT", "modalities": { "input": [ "text" @@ -295962,36 +296258,40 @@ ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 131072, + "output": 32768 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { "supported": false }, "attachment": false, "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-07-22", - "last_updated": "2025-07-22", + "knowledge": "2024-08", + "release_date": "2025-03-12", + "last_updated": "2025-03-12", "type": "chat" }, { - "id": "Qwen/Qwen3.7-Max", - "name": "Qwen3.7 Max", - "display_name": "Qwen3.7 Max", + "id": "google/gemini-3.8-flash", + "name": "Gemini 3.8 Flash", + "display_name": "Gemini 3.8 Flash", "modalities": { "input": [ - "text" + "text", + "image", + "video", + "audio", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 1048576, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -296001,31 +296301,46 @@ }, "extra_capabilities": { "reasoning": { - "supported": true + "supported": true, + "default_enabled": true, + "mode": "level", + "level": "high", + "level_options": [ + "minimal", + "low", + "medium", + "high" + ], + "summaries": true, + "visibility": "summary", + "continuation": [ + "thought_signatures" + ] } }, - "attachment": false, + "attachment": true, "open_weights": false, - "release_date": "2026-05-21", - "last_updated": "2026-05-21", + "release_date": "2026-09-02", + "last_updated": "2026-09-02", "type": "chat" }, { - "id": "Qwen/Qwen3.5-9B", - "name": "Qwen3.5 9B", - "display_name": "Qwen3.5 9B", + "id": "google/gemini-2.5-pro", + "name": "Gemini 2.5 Pro", + "display_name": "Gemini 2.5 Pro", "modalities": { "input": [ "text", "image", - "video" + "audio", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 262144, + "context": 1048576, "output": 65536 }, "temperature": true, @@ -296037,147 +296352,170 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, + "default_enabled": true, + "mode": "budget", + "budget": { + "default": -1, + "min": 128, + "max": 32768, + "auto": -1, + "unit": "tokens" + }, "summaries": true, "visibility": "summary", "continuation": [ - "thinking_blocks" + "thought_signatures" ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-02-23", - "last_updated": "2026-02-23", + "open_weights": false, + "knowledge": "2025-01", + "release_date": "2025-06-17", + "last_updated": "2025-06-17", "type": "chat" }, { - "id": "LiquidAI/LFM2-24B-A2B", - "name": "LFM2-24B-A2B", - "display_name": "LFM2-24B-A2B", + "id": "google/gemini-flash-lite-latest", + "name": "Gemini Flash-Lite Latest", + "display_name": "Gemini Flash-Lite Latest", "modalities": { "input": [ - "text" + "text", + "image", + "video", + "audio", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 32768, - "output": 32768 + "context": 1048576, + "output": 65536 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, - "open_weights": true, - "release_date": "2026-02-24", - "last_updated": "2026-02-24", + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "minimal", + "low", + "medium", + "high" + ] + } + }, + "attachment": true, + "open_weights": false, + "knowledge": "2026-03", + "release_date": "2026-07-21", + "last_updated": "2026-07-21", "type": "chat" }, { - "id": "meta-models/Muse-Spark-Open-816B-A42B", - "name": "Muse Spark Open", - "display_name": "Muse Spark Open", + "id": "google/gemini-2.5-flash-lite", + "name": "Gemini 2.5 Flash-Lite", + "display_name": "Gemini 2.5 Flash-Lite", "modalities": { "input": [ "text", "image", - "video" + "audio", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 1000000, + "output": 65536 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, + "default_enabled": false, + "mode": "budget", + "budget": { + "default": -1, + "min": 512, + "max": 24576, + "auto": -1, + "unit": "tokens" + }, "summaries": true, "visibility": "summary", "continuation": [ - "thinking_blocks" - ], - "effort_options": [ - "none", - "low", - "medium", - "high", - "xhigh" + "thought_signatures" ] } }, "attachment": true, - "open_weights": true, - "knowledge": "2026-01-04", - "release_date": "2026-10-15", - "last_updated": "2026-10-15", + "open_weights": false, + "knowledge": "2025-01", + "release_date": "2025-06-17", + "last_updated": "2025-06-17", "type": "chat" }, { - "id": "nvidia/nemotron-3-ultra-550b-a55b", - "name": "Nemotron 3 Ultra 550B A55B", - "display_name": "Nemotron 3 Ultra 550B A55B", + "id": "google/gemma-3-4b-it", + "name": "Gemma 3 4B", + "display_name": "Gemma 3 4B", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 512300, - "output": 512300 + "context": 128000, + "output": 8192 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true - } + "supported": false }, - "attachment": false, + "attachment": true, "open_weights": true, - "knowledge": "2025-09", - "release_date": "2026-06-04", - "last_updated": "2026-06-04", + "knowledge": "2024-08", + "release_date": "2025-03-12", + "last_updated": "2025-03-12", "type": "chat" }, { - "id": "nvidia/NVIDIA-Nemotron-3.5-Super-VL", - "name": "Nemotron 3.5 Super VL 120B A12B", - "display_name": "Nemotron 3.5 Super VL 120B A12B", + "id": "google/gemini-2.5-flash", + "name": "Gemini 2.5 Flash", + "display_name": "Gemini 2.5 Flash", "modalities": { "input": [ "text", "image", - "video" + "audio", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 1048576, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -296188,37 +296526,49 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, + "default_enabled": true, + "mode": "budget", + "budget": { + "default": -1, + "min": 0, + "max": 24576, + "auto": -1, + "off": 0, + "unit": "tokens" + }, "summaries": true, "visibility": "summary", "continuation": [ - "thinking_blocks" + "thought_signatures" ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-10-15", - "last_updated": "2026-10-15", + "open_weights": false, + "knowledge": "2025-01", + "release_date": "2025-06-17", + "last_updated": "2025-06-17", "type": "chat" }, { - "id": "thinkingmachines/Inkling", - "name": "Inkling", - "display_name": "Inkling", + "id": "google/gemini-3.1-pro-preview", + "name": "Gemini 3.1 Pro Preview", + "display_name": "Gemini 3.1 Pro Preview", "modalities": { "input": [ "text", "image", - "audio" + "video", + "audio", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 524288, - "output": 131072 + "context": 1048576, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -296228,27 +296578,32 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "none", - "minimal", + "supported": true, + "default_enabled": true, + "mode": "level", + "level": "high", + "level_options": [ "low", - "medium", - "high", - "xhigh", - "max" + "high" + ], + "summaries": true, + "visibility": "summary", + "continuation": [ + "thought_signatures" ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-07-15", - "last_updated": "2026-07-15", + "open_weights": false, + "knowledge": "2025-01", + "release_date": "2026-02-19", + "last_updated": "2026-02-19", "type": "chat" }, { - "id": "openai/gpt-oss-120b", - "name": "GPT OSS 120B", - "display_name": "GPT OSS 120B", + "id": "google/gemini-embedding-2", + "name": "Gemini Embedding 2", + "display_name": "Gemini Embedding 2", "modalities": { "input": [ "text" @@ -296258,82 +296613,68 @@ ] }, "limit": { - "context": 131072, - "output": 131072 + "context": 8192, + "output": 8192 }, - "temperature": true, - "tool_call": true, + "temperature": false, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "effort_options": [ - "low", - "medium", - "high" - ] - } + "supported": false }, "attachment": false, - "open_weights": true, - "knowledge": "2024-06", - "release_date": "2025-08-05", - "last_updated": "2025-08-05", - "type": "chat" + "open_weights": false, + "knowledge": "2025-11", + "release_date": "2026-04-22", + "last_updated": "2026-04-22", + "type": "embedding" }, { - "id": "MiniMaxAI/MiniMax-M3", - "name": "MiniMax-M3", - "display_name": "MiniMax-M3", + "id": "google/gemini-embedding-001", + "name": "Gemini Embedding 001", + "display_name": "Gemini Embedding 001", "modalities": { "input": [ - "text", - "image", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 524288, - "output": 524288 + "context": 2048, + "output": 4096 }, - "temperature": true, - "tool_call": true, + "temperature": false, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true - } + "supported": false }, - "attachment": true, - "open_weights": true, - "release_date": "2026-06-01", - "last_updated": "2026-06-01", - "type": "chat" + "attachment": false, + "open_weights": false, + "knowledge": "2025-05", + "release_date": "2025-05-20", + "last_updated": "2025-05-20", + "type": "embedding" }, { - "id": "MiniMaxAI/MiniMax-M2.7", - "name": "MiniMax-M2.7", - "display_name": "MiniMax-M2.7", + "id": "google/gemini-3.7-flash", + "name": "Gemini 3.7 Flash", + "display_name": "Gemini 3.7 Flash", "modalities": { "input": [ - "text" + "text", + "image", + "video", + "audio", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 196608, - "output": 131072 + "context": 1048576, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -296344,24 +296685,33 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, + "default_enabled": true, + "mode": "level", + "level": "high", + "level_options": [ + "minimal", + "low", + "medium", + "high" + ], "summaries": true, "visibility": "summary", "continuation": [ - "thinking_blocks" + "thought_signatures" ] } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-03-18", - "last_updated": "2026-03-18", + "attachment": true, + "open_weights": false, + "knowledge": "2026-03", + "release_date": "2026-08-13", + "last_updated": "2026-08-13", "type": "chat" }, { - "id": "MiniMaxAI/MiniMax-M2.5", - "name": "MiniMax-M2.5", - "display_name": "MiniMax-M2.5", + "id": "google/gemma-4-31b-it", + "name": "Gemma 4 31B It", + "display_name": "Gemma 4 31B It", "modalities": { "input": [ "text" @@ -296371,11 +296721,11 @@ ] }, "limit": { - "context": 204800, - "output": 131072 + "context": 262144, + "output": 65536 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { "supported": true, "default": true @@ -296387,17 +296737,21 @@ }, "attachment": false, "open_weights": true, - "release_date": "2026-02-12", - "last_updated": "2026-02-12", + "knowledge": "2025-01", + "release_date": "2026-04-02", + "last_updated": "2026-04-02", "type": "chat" }, { - "id": "reflection/Beam-1.0-501B-A23B", - "name": "Beam 1.0 501B A23B", - "display_name": "Beam 1.0 501B A23B", + "id": "google/gemini-3.1-flash-lite", + "name": "Gemini 3.1 Flash-Lite", + "display_name": "Gemini 3.1 Flash-Lite", "modalities": { "input": [ - "text" + "text", + "image", + "audio", + "pdf" ], "output": [ "text" @@ -296405,7 +296759,7 @@ }, "limit": { "context": 1048576, - "output": 131072 + "output": 65536 }, "temperature": true, "tool_call": true, @@ -296416,37 +296770,31 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], "effort_options": [ "minimal", "low", "medium", - "high", - "xhigh", - "max" + "high" ] } }, - "attachment": false, - "open_weights": true, - "knowledge": "2026-06-30", - "release_date": "2026-10-05", - "last_updated": "2026-10-16", + "attachment": true, + "open_weights": false, + "knowledge": "2025-01", + "release_date": "2026-05-07", + "last_updated": "2026-05-07", "type": "chat" }, { - "id": "moonshotai/Kimi-K3", - "name": "Kimi K3", - "display_name": "Kimi K3", + "id": "google/gemini-3.1-pro-preview-customtools", + "name": "Gemini 3.1 Pro Preview Custom Tools", + "display_name": "Gemini 3.1 Pro Preview Custom Tools", "modalities": { "input": [ "text", - "image" + "image", + "audio", + "pdf" ], "output": [ "text" @@ -296454,9 +296802,9 @@ }, "limit": { "context": 1048576, - "output": 131072 + "output": 65536 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -296465,108 +296813,116 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, + "default_enabled": true, + "mode": "level", + "level": "high", + "level_options": [ + "low", + "high" + ], "summaries": true, "visibility": "summary", "continuation": [ - "thinking_blocks" - ], - "effort_options": [ - "none", - "low", - "high", - "max" + "thought_signatures" ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-07-16", - "last_updated": "2026-07-16", + "open_weights": false, + "knowledge": "2025-01", + "release_date": "2026-02-19", + "last_updated": "2026-02-19", "type": "chat" }, { - "id": "meta-llama/Llama-3.3-70B-Instruct-Turbo", - "name": "Llama 3.3 70B", - "display_name": "Llama 3.3 70B", + "id": "google/gemini-3-flash-preview", + "name": "Gemini 3 Flash Preview", + "display_name": "Gemini 3 Flash Preview", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 131072 + "context": 1048576, + "output": 65536 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, - "open_weights": true, - "knowledge": "2023-12", - "release_date": "2024-12-06", - "last_updated": "2024-12-06", + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "level", + "level": "high", + "level_options": [ + "minimal", + "low", + "medium", + "high" + ], + "summaries": true, + "visibility": "summary", + "continuation": [ + "thought_signatures" + ] + } + }, + "attachment": true, + "open_weights": false, + "knowledge": "2025-01", + "release_date": "2025-12-17", + "last_updated": "2025-12-17", "type": "chat" }, { - "id": "deepseek-ai/DeepSeek-V4.1-Flash", - "name": "DeepSeek V4.1 Flash", - "display_name": "DeepSeek V4.1 Flash", + "id": "google/gemini-2.5-flash-image", + "name": "Gemini 2.5 Flash Image", + "display_name": "Gemini 2.5 Flash Image", "modalities": { "input": [ "text", "image" ], "output": [ - "text" + "text", + "image" ] }, "limit": { - "context": 1048576, - "output": 384000 + "context": 32768, + "output": 32768 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "effort_options": [ - "none", - "low", - "high", - "max" - ] - } + "supported": false }, "attachment": true, - "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-09-10", - "last_updated": "2026-09-10", - "type": "chat" + "open_weights": false, + "knowledge": "2024-06", + "release_date": "2025-08-26", + "last_updated": "2025-08-26" }, { - "id": "deepseek-ai/DeepSeek-V4-Flash-0731", - "name": "DeepSeek V4 Flash 0731", - "display_name": "DeepSeek V4 Flash 0731", + "id": "google/gemini-3.5-flash-lite", + "name": "Gemini 3.5 Flash-Lite", + "display_name": "Gemini 3.5 Flash-Lite", "modalities": { "input": [ - "text" + "text", + "image", + "video", + "audio", + "pdf" ], "output": [ "text" @@ -296574,7 +296930,7 @@ }, "limit": { "context": 1048576, - "output": 384000 + "output": 65536 }, "temperature": true, "tool_call": true, @@ -296584,30 +296940,20 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "effort_options": [ - "high", - "max" - ] + "supported": true } }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-07-31", - "last_updated": "2026-07-31", + "attachment": true, + "open_weights": false, + "knowledge": "2026-03", + "release_date": "2026-07-21", + "last_updated": "2026-07-21", "type": "chat" }, { - "id": "deepseek-ai/DeepSeek-V4-Pro-0813", - "name": "DeepSeek V4 Pro 0813", - "display_name": "DeepSeek V4 Pro 0813", + "id": "mistral/devstral-medium-latest", + "name": "Devstral 2 (latest)", + "display_name": "Devstral 2 (latest)", "modalities": { "input": [ "text" @@ -296617,40 +296963,25 @@ ] }, "limit": { - "context": 1048576, - "output": 384000 + "context": 262144, + "output": 262144 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "effort_options": [ - "high", - "max" - ] - } + "supported": false }, "attachment": false, "open_weights": true, - "release_date": "2026-08-13", - "last_updated": "2026-08-22", + "knowledge": "2025-12", + "release_date": "2025-12-02", + "last_updated": "2025-12-02", "type": "chat" }, { - "id": "deepseek-ai/DeepSeek-R1", - "name": "DeepSeek-R1", - "display_name": "DeepSeek-R1", + "id": "mistral/codestral-latest", + "name": "Codestral (latest)", + "display_name": "Codestral (latest)", "modalities": { "input": [ "text" @@ -296660,330 +296991,232 @@ ] }, "limit": { - "context": 163839, - "output": 163839 + "context": 256000, + "output": 4096 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } + "supported": false }, "attachment": false, "open_weights": true, - "knowledge": "2024-07", - "release_date": "2025-01-20", - "last_updated": "2025-05-29", + "knowledge": "2024-10", + "release_date": "2024-05-29", + "last_updated": "2025-01-04", "type": "chat" }, { - "id": "deepseek-ai/DeepSeek-V3-1", - "name": "DeepSeek V3.1", - "display_name": "DeepSeek V3.1", + "id": "mistral/mistral-large-2512", + "name": "Mistral Large 3", + "display_name": "Mistral Large 3", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 131072 + "context": 256000, + "output": 256000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true + "supported": false }, - "attachment": false, + "attachment": true, "open_weights": true, - "release_date": "2025-08-21", - "last_updated": "2025-08-21", + "knowledge": "2024-11", + "release_date": "2025-12-02", + "last_updated": "2025-12-02", "type": "chat" }, { - "id": "deepseek-ai/DeepSeek-V3", - "name": "DeepSeek-V3", - "display_name": "DeepSeek-V3", + "id": "mistral/mistral-medium-2505", + "name": "Mistral Medium 3", + "display_name": "Mistral Medium 3", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 131072 + "context": 128000, + "output": 128000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": false }, - "attachment": false, - "open_weights": true, - "release_date": "2024-12-26", - "last_updated": "2024-12-26", + "attachment": true, + "open_weights": false, + "knowledge": "2025-05", + "release_date": "2025-05-07", + "last_updated": "2025-05-07", "type": "chat" - } - ] - }, - "moonshot-ai": { - "id": "moonshot-ai", - "name": "Moonshot AI", - "display_name": "Moonshot AI", - "api": "https://api.moonshot.ai/v1", - "doc": "https://platform.moonshot.ai/docs/api/chat", - "models": [ + }, { - "id": "kimi-k3", - "name": "Kimi K3", - "display_name": "Kimi K3", + "id": "mistral/mistral-medium-latest", + "name": "Mistral Medium (latest)", + "display_name": "Mistral Medium (latest)", "modalities": { "input": [ "text", - "image", - "video" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 1048576 + "context": 262144, + "output": 262144 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "effort_options": [ - "low", - "high", - "max" - ] - } - }, "attachment": true, "open_weights": true, - "release_date": "2026-07-16", - "last_updated": "2026-07-16", + "release_date": "2026-04-29", + "last_updated": "2026-04-29", "type": "chat" }, { - "id": "kimi-k2.6", - "name": "Kimi K2.6", - "display_name": "Kimi K2.6", + "id": "mistral/mistral-small-latest", + "name": "Mistral Small (latest)", + "display_name": "Mistral Small (latest)", "modalities": { "input": [ "text", - "image", - "video" + "image" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 256000, + "output": 256000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } - }, "attachment": true, "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-04-20", - "last_updated": "2026-04-20", + "knowledge": "2025-06", + "release_date": "2026-03-16", + "last_updated": "2026-03-16", "type": "chat" }, { - "id": "kimi-k3.1-pro-highspeed", - "name": "Kimi K3.1 Pro Highspeed", - "display_name": "Kimi K3.1 Pro Highspeed", + "id": "mistral/magistral-medium-latest", + "name": "Magistral Medium (latest)", + "display_name": "Magistral Medium (latest)", "modalities": { "input": [ - "text", - "image", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 1048576 + "context": 128000, + "output": 16384 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "effort_options": [ - "low", - "high", - "max" - ] - } - }, - "attachment": true, + "attachment": false, "open_weights": false, - "release_date": "2026-10-23", - "last_updated": "2026-10-23", + "knowledge": "2025-06", + "release_date": "2025-03-17", + "last_updated": "2025-03-20", "type": "chat" }, { - "id": "kimi-k3.1-pro", - "name": "Kimi K3.1 Pro", - "display_name": "Kimi K3.1 Pro", + "id": "mistral/pixtral-large-latest", + "name": "Pixtral Large (latest)", + "display_name": "Pixtral Large (latest)", "modalities": { "input": [ "text", - "image", - "video" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 1048576 + "context": 128000, + "output": 128000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "effort_options": [ - "low", - "high", - "max" - ] - } + "supported": false }, "attachment": true, - "open_weights": false, - "release_date": "2026-10-23", - "last_updated": "2026-10-23", + "open_weights": true, + "knowledge": "2024-11", + "release_date": "2024-11-01", + "last_updated": "2024-11-04", "type": "chat" }, { - "id": "kimi-k2.7-code", - "name": "Kimi K2.7 Code", - "display_name": "Kimi K2.7 Code", + "id": "mistral/devstral-2512", + "name": "Devstral 2", + "display_name": "Devstral 2", "modalities": { "input": [ - "text", - "image", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 256000, + "output": 256000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "fixed", - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } + "supported": false }, - "attachment": true, + "attachment": false, "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-04-21", - "last_updated": "2026-04-21", + "knowledge": "2025-12", + "release_date": "2025-12-09", + "last_updated": "2025-12-09", "type": "chat" }, { - "id": "kimi-k2.7-code-highspeed", - "name": "Kimi K2.7 Code Highspeed", - "display_name": "Kimi K2.7 Code Highspeed", + "id": "mistral/mistral-large-latest", + "name": "Mistral Large (latest)", + "display_name": "Mistral Large (latest)", "modalities": { "input": [ "text", - "image", - "video" + "image" ], "output": [ "text" @@ -296996,103 +297229,75 @@ "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "fixed", - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } + "supported": false }, "attachment": true, "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-04-21", - "last_updated": "2026-04-21", + "knowledge": "2024-11", + "release_date": "2024-11-01", + "last_updated": "2025-12-02", "type": "chat" - } - ] - }, - "pareto": { - "id": "pareto", - "name": "Pareto Inference", - "display_name": "Pareto Inference", - "api": "https://api.paretoinference.com/v1", - "doc": "https://docs.paretoinference.com/", - "models": [ + }, { - "id": "z-ai/glm-5.3-flash", - "name": "GLM-5.3-Flash", - "display_name": "GLM-5.3-Flash", + "id": "anthropic/claude-haiku-4-5-20251001", + "name": "Claude Haiku 4.5 (20251001)", + "display_name": "Claude Haiku 4.5 (20251001)", "modalities": { "input": [ "text", "image", - "video" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 200000, + "output": 64000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": false, + "mode": "budget", + "budget": { + "min": 1024, + "unit": "tokens" + }, "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ "thinking_blocks" ], - "effort_options": [ - "low", - "high", - "max" + "notes": [ + "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", + "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-08-26", - "last_updated": "2026-08-26", + "open_weights": false, + "knowledge": "2025-02-28", + "release_date": "2025-10-15", + "last_updated": "2025-10-15", "type": "chat" - } - ] - }, - "neon": { - "id": "neon", - "name": "Neon", - "display_name": "Neon", - "api": "${NEON_AI_GATEWAY_BASE_URL}/v1", - "doc": "https://neon.com/docs", - "models": [ + }, { - "id": "gemini-3-6-flash", - "name": "Gemini 3.6 Flash", - "display_name": "Gemini 3.6 Flash", + "id": "anthropic/claude-haiku-5-5", + "name": "Claude Haiku 5.5", + "display_name": "Claude Haiku 5.5", "modalities": { "input": [ "text", "image", - "video", - "audio", "pdf" ], "output": [ @@ -297100,8 +297305,8 @@ ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 1000000, + "output": 128000 }, "temperature": false, "tool_call": true, @@ -297109,27 +297314,17 @@ "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "minimal", - "low", - "medium", - "high" - ] - } - }, "attachment": true, "open_weights": false, - "knowledge": "2026-03", - "release_date": "2026-07-21", - "last_updated": "2026-07-21", + "knowledge": "2026-06", + "release_date": "2026-10-07", + "last_updated": "2026-10-07", "type": "chat" }, { - "id": "gpt-6-1-sol", - "name": "GPT-6.1 Sol", - "display_name": "GPT-6.1 Sol", + "id": "anthropic/claude-sonnet-4-5-20250929", + "name": "Claude Sonnet 4.5 (20250929)", + "display_name": "Claude Sonnet 4.5 (20250929)", "modalities": { "input": [ "text", @@ -297141,93 +297336,108 @@ ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 200000, + "output": 64000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "low", - "medium", - "high", - "xhigh" + "supported": true, + "default_enabled": false, + "mode": "budget", + "budget": { + "min": 1024, + "unit": "tokens" + }, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", + "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-04-30", - "release_date": "2026-09-29", - "last_updated": "2026-09-29", + "knowledge": "2025-07-31", + "release_date": "2025-09-29", + "last_updated": "2025-09-29", "type": "chat" }, { - "id": "gpt-5", - "name": "GPT-5", - "display_name": "GPT-5", + "id": "anthropic/claude-opus-4-7", + "name": "Claude Opus 4.7", + "display_name": "Claude Opus 4.7", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 400000, + "context": 1000000, "output": 128000 }, "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, + "default_enabled": false, "mode": "effort", - "effort": "medium", + "effort": "high", "effort_options": [ - "minimal", "low", "medium", - "high" + "high", + "xhigh", + "max" ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" + "interleaved": true, + "summaries": true, + "visibility": "omitted", + "continuation": [ + "thinking_blocks" ], - "visibility": "hidden" + "notes": [ + "Claude Opus 4.7 and newer Opus models require thinking.type = \"adaptive\" to enable thinking explicitly.", + "Manual budget_tokens requests return 400 on Claude Opus 4.7 and newer adaptive-only Opus models.", + "task_budget is separate from thinking control and should not be treated as a thinking budget." + ] } }, "attachment": true, "open_weights": false, - "knowledge": "2024-09-30", - "release_date": "2025-08-07", - "last_updated": "2025-08-07", + "knowledge": "2026-01-31", + "release_date": "2026-04-16", + "last_updated": "2026-04-16", "type": "chat" }, { - "id": "gemini-3-1-pro", - "name": "Gemini 3.1 Pro Preview Custom Tools", - "display_name": "Gemini 3.1 Pro Preview Custom Tools", + "id": "anthropic/claude-sonnet-5", + "name": "Claude Sonnet 5", + "display_name": "Claude Sonnet 5", "modalities": { "input": [ "text", "image", - "video", - "audio", "pdf" ], "output": [ @@ -297235,10 +297445,10 @@ ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 1000000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -297246,38 +297456,42 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, "effort_options": [ "low", "medium", - "high" + "high", + "xhigh", + "max" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-01", - "release_date": "2026-02-19", - "last_updated": "2026-02-19", + "knowledge": "2026-01-31", + "release_date": "2026-06-30", + "last_updated": "2026-06-30", "type": "chat" }, { - "id": "grok-4-6", - "name": "Grok 4.6", - "display_name": "Grok 4.6", + "id": "anthropic/claude-fable-5", + "name": "Claude Fable 5", + "display_name": "Claude Fable 5", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 500000, - "output": 524288 + "context": 1000000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -297285,28 +297499,46 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "high", "effort_options": [ "low", "medium", "high", - "xhigh" + "xhigh", + "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "omitted", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Adaptive thinking is always on for Claude Fable 5 and Claude Mythos 5; thinking.type = \"disabled\" is rejected.", + "Manual budget_tokens requests return 400 on Claude Fable 5 and Claude Mythos 5.", + "thinking.display defaults to omitted; set display to summarized to receive readable thinking summaries." ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-02-01", - "release_date": "2026-08-12", - "last_updated": "2026-08-12", + "knowledge": "2026-01-31", + "release_date": "2026-06-09", + "last_updated": "2026-06-09", "type": "chat" }, { - "id": "glm-5-2", - "name": "GLM-5.2", - "display_name": "GLM-5.2", + "id": "anthropic/claude-opus-5-5", + "name": "Claude Opus 5.5", + "display_name": "Claude Opus 5.5", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" @@ -297314,36 +297546,25 @@ }, "limit": { "context": 1000000, - "output": 65536 + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "none", - "minimal", - "low", - "medium", - "high", - "max" - ] - } - }, - "attachment": false, - "open_weights": true, - "release_date": "2026-06-13", - "last_updated": "2026-06-13", + "attachment": true, + "open_weights": false, + "knowledge": "2026-06", + "release_date": "2026-09-22", + "last_updated": "2026-09-22", "type": "chat" }, { - "id": "gpt-6-astra", - "name": "GPT-6 Astra", - "display_name": "GPT-6 Astra", + "id": "anthropic/claude-sonnet-5-5", + "name": "Claude Sonnet 5.5", + "display_name": "Claude Sonnet 5.5", "modalities": { "input": [ "text", @@ -297355,7 +297576,7 @@ ] }, "limit": { - "context": 1050000, + "context": 1000000, "output": 128000 }, "temperature": false, @@ -297364,29 +297585,17 @@ "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" - ] - } - }, "attachment": true, "open_weights": false, - "knowledge": "2026-04-30", - "release_date": "2026-09-04", - "last_updated": "2026-09-04", + "knowledge": "2026-06", + "release_date": "2026-09-28", + "last_updated": "2026-09-28", "type": "chat" }, { - "id": "claude-opus-4-5", - "name": "Claude Opus 4.5 (latest)", - "display_name": "Claude Opus 4.5 (latest)", + "id": "anthropic/claude-sonnet-4-6", + "name": "Claude Sonnet 4.6", + "display_name": "Claude Sonnet 4.6", "modalities": { "input": [ "text", @@ -297398,8 +297607,8 @@ ] }, "limit": { - "context": 200000, - "output": 64000 + "context": 1000000, + "output": 128000 }, "temperature": true, "tool_call": true, @@ -297420,7 +297629,8 @@ "effort_options": [ "low", "medium", - "high" + "high", + "max" ], "interleaved": true, "summaries": true, @@ -297429,28 +297639,26 @@ "thinking_blocks" ], "notes": [ - "Claude Opus 4.5 uses manual thinking.type = \"enabled\" with budget_tokens; effort can be used alongside the thinking budget.", - "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header." + "Anthropic recommends adaptive thinking with effort for Claude 4.6; budget_tokens remains a deprecated compatibility path.", + "Anthropic API defaults effort to high; lower effort levels should be chosen per workload." ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-05", - "release_date": "2025-11-24", - "last_updated": "2025-11-24", + "knowledge": "2025-08-31", + "release_date": "2026-02-17", + "last_updated": "2026-03-13", "type": "chat" }, { - "id": "gemini-3-5-flash-lite", - "name": "Gemini 3.5 Flash Lite", - "display_name": "Gemini 3.5 Flash Lite", + "id": "anthropic/claude-opus-4-8", + "name": "Claude Opus 4.8", + "display_name": "Claude Opus 4.8", "modalities": { "input": [ "text", "image", - "video", - "audio", "pdf" ], "output": [ @@ -297458,42 +297666,56 @@ ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 1000000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": false, + "mode": "effort", + "effort": "high", "effort_options": [ - "minimal", "low", "medium", - "high" + "high", + "xhigh", + "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "omitted", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Claude Opus 4.7 and newer Opus models require thinking.type = \"adaptive\" to enable thinking explicitly.", + "Manual budget_tokens requests return 400 on Claude Opus 4.7 and newer adaptive-only Opus models.", + "task_budget is separate from thinking control and should not be treated as a thinking budget." ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-03", - "release_date": "2026-07-21", - "last_updated": "2026-07-21", + "knowledge": "2026-01", + "release_date": "2026-05-28", + "last_updated": "2026-05-28", "type": "chat" }, { - "id": "gemini-3-5-flash", - "name": "Gemini 3.5 Flash", - "display_name": "Gemini 3.5 Flash", + "id": "anthropic/claude-opus-4-6", + "name": "Claude Opus 4.6", + "display_name": "Claude Opus 4.6", "modalities": { "input": [ "text", "image", - "video", - "audio", "pdf" ], "output": [ @@ -297501,36 +297723,54 @@ ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 1000000, + "output": 128000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": false, + "mode": "mixed", + "budget": { + "min": 1024, + "unit": "tokens" + }, + "effort": "high", "effort_options": [ - "minimal", "low", "medium", - "high" + "high", + "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Anthropic recommends adaptive thinking with effort for Claude 4.6; budget_tokens remains a deprecated compatibility path.", + "Anthropic API defaults effort to high; lower effort levels should be chosen per workload." ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-01", - "release_date": "2026-05-19", - "last_updated": "2026-05-19", + "knowledge": "2025-05-31", + "release_date": "2026-02-05", + "last_updated": "2026-03-13", "type": "chat" }, { - "id": "claude-sonnet-5-5", - "name": "Claude Sonnet 5.5", - "display_name": "Claude Sonnet 5.5", + "id": "anthropic/claude-fable-5-1", + "name": "Claude Fable 5.1", + "display_name": "Claude Fable 5.1", "modalities": { "input": [ "text", @@ -297553,39 +297793,56 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "high", "effort_options": [ "low", "medium", "high", "xhigh", "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "omitted", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Adaptive thinking is always on for Claude Fable 5 and Claude Mythos 5; thinking.type = \"disabled\" is rejected.", + "Manual budget_tokens requests return 400 on Claude Fable 5 and Claude Mythos 5.", + "thinking.display defaults to omitted; set display to summarized to receive readable thinking summaries." ] } }, "attachment": true, "open_weights": false, "knowledge": "2026-06", - "release_date": "2026-09-28", - "last_updated": "2026-09-28", + "release_date": "2026-09-01", + "last_updated": "2026-09-01", "type": "chat" }, { - "id": "gpt-oss-20b", - "name": "GPT OSS 20B", - "display_name": "GPT OSS 20B", + "id": "anthropic/claude-opus-5", + "name": "Claude Opus 5", + "display_name": "Claude Opus 5", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 25000 + "context": 1000000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -297597,21 +297854,23 @@ "effort_options": [ "low", "medium", - "high" + "high", + "xhigh", + "max" ] } }, - "attachment": false, - "open_weights": true, - "knowledge": "2024-06", - "release_date": "2025-08-05", - "last_updated": "2025-08-05", + "attachment": true, + "open_weights": false, + "knowledge": "2026-05", + "release_date": "2026-07-24", + "last_updated": "2026-07-24", "type": "chat" }, { - "id": "gpt-5-5-pro", - "name": "GPT-5.5 Pro", - "display_name": "GPT-5.5 Pro", + "id": "anthropic/claude-opus-4-1-20250805", + "name": "Claude Opus 4.1 (20250805)", + "display_name": "Claude Opus 4.1 (20250805)", "modalities": { "input": [ "text", @@ -297623,35 +297882,47 @@ ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 200000, + "output": 32000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "medium", - "high", - "xhigh" + "supported": true, + "default_enabled": false, + "mode": "budget", + "budget": { + "min": 1024, + "unit": "tokens" + }, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", + "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-12-01", - "release_date": "2026-04-23", - "last_updated": "2026-04-23", + "knowledge": "2025-03-31", + "release_date": "2025-08-05", + "last_updated": "2025-08-05", "type": "chat" }, { - "id": "claude-haiku-5-5", - "name": "Claude Haiku 5.5", - "display_name": "Claude Haiku 5.5", + "id": "anthropic/claude-opus-4-5-20251101", + "name": "Claude Opus 4.5 (20251101)", + "display_name": "Claude Opus 4.5 (20251101)", "modalities": { "input": [ "text", @@ -297663,317 +297934,260 @@ ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 200000, + "output": 64000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": false, + "mode": "mixed", + "budget": { + "min": 1024, + "unit": "tokens" + }, + "effort": "high", + "effort_options": [ + "low", + "medium", + "high" + ], + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Claude Opus 4.5 uses manual thinking.type = \"enabled\" with budget_tokens; effort can be used alongside the thinking budget.", + "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header." + ] + } }, "attachment": true, "open_weights": false, - "knowledge": "2026-06", - "release_date": "2026-10-07", - "last_updated": "2026-10-07", + "knowledge": "2025-05", + "release_date": "2025-11-24", + "last_updated": "2025-11-24", "type": "chat" }, { - "id": "kimi-k3", - "name": "Kimi K3", - "display_name": "Kimi K3", + "id": "qwen/qwen3-vl-235b-a22b-instruct", + "name": "Qwen3-VL 235B A22B Instruct", + "display_name": "Qwen3-VL 235B A22B Instruct", "modalities": { "input": [ "text", - "image", - "video" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 131072, + "output": 32768 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "effort_options": [ - "none", - "minimal", - "low", - "medium", - "high", - "max" - ] - } + "supported": false }, "attachment": true, "open_weights": true, - "release_date": "2026-07-16", - "last_updated": "2026-07-16", + "knowledge": "2025-03-31", + "release_date": "2025-09-23", + "last_updated": "2025-09-23", "type": "chat" }, { - "id": "claude-opus-5", - "name": "Claude Opus 5", - "display_name": "Claude Opus 5", + "id": "qwen/qwen3-max", + "name": "Qwen3 Max", + "display_name": "Qwen3 Max", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 262144, + "output": 65536 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true + "supported": true }, "extra_capabilities": { "reasoning": { "supported": true, - "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, - "attachment": true, + "attachment": false, "open_weights": false, - "knowledge": "2026-05", - "release_date": "2026-07-24", - "last_updated": "2026-07-24", + "knowledge": "2025-04", + "release_date": "2025-09-23", + "last_updated": "2025-09-23", "type": "chat" }, { - "id": "meta-llama-3-3-70b-instruct", - "name": "Llama-3.3-70B-Instruct", - "display_name": "Llama-3.3-70B-Instruct", + "id": "qwen/qwen3-vl-30b-a3b-instruct", + "name": "Qwen3-VL 30B-A3B Instruct", + "display_name": "Qwen3-VL 30B-A3B Instruct", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 8192 + "context": 131072, + "output": 32768 }, "temperature": true, "tool_call": true, "reasoning": { "supported": false }, - "attachment": false, + "attachment": true, "open_weights": true, - "knowledge": "2023-12", - "release_date": "2024-12-06", - "last_updated": "2024-12-06", + "release_date": "2025-10-04", + "last_updated": "2025-10-04", "type": "chat" }, { - "id": "gpt-5-6-terra", - "name": "GPT-5.6 Terra", - "display_name": "GPT-5.6 Terra", + "id": "qwen/qwen3-vl-flash", + "name": "Qwen3-VL Flash", + "display_name": "Qwen3-VL Flash", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 262144, + "output": 65536 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true + "supported": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", - "effort_options": [ - "none", - "low", - "medium", - "high", - "xhigh", - "max" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-02-16", - "release_date": "2026-07-09", - "last_updated": "2026-07-09", + "release_date": "2025-10-15", + "last_updated": "2025-10-15", "type": "chat" }, { - "id": "claude-opus-4-7", - "name": "Claude Opus 4.7", - "display_name": "Claude Opus 4.7", + "id": "qwen/qwen3.8-2.4t-a95b", + "name": "Qwen3.8 2.4T A95B", + "display_name": "Qwen3.8 2.4T A95B", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 262144, + "output": 1010000 }, - "temperature": false, - "tool_call": true, + "temperature": true, + "tool_call": false, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "high", - "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "omitted", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Claude Opus 4.7 and newer Opus models require thinking.type = \"adaptive\" to enable thinking explicitly.", - "Manual budget_tokens requests return 400 on Claude Opus 4.7 and newer adaptive-only Opus models.", - "task_budget is separate from thinking control and should not be treated as a thinking budget." - ] + "supported": true } }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-01-31", - "release_date": "2026-04-16", - "last_updated": "2026-04-16", + "attachment": false, + "open_weights": true, + "release_date": "2026-08-12", + "last_updated": "2026-08-12", "type": "chat" }, { - "id": "gpt-5-6-luna", - "name": "GPT-5.6 Luna", - "display_name": "GPT-5.6 Luna", + "id": "qwen/qwen3-next-80b-a3b-instruct", + "name": "Qwen3-Next 80B-A3B Instruct", + "display_name": "Qwen3-Next 80B-A3B Instruct", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 131072, + "output": 32768 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", - "effort_options": [ - "none", - "low", - "medium", - "high", - "xhigh", - "max" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" - } + "supported": false }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-02-16", - "release_date": "2026-07-09", - "last_updated": "2026-07-09", + "attachment": false, + "open_weights": true, + "knowledge": "2025-04", + "release_date": "2025-09", + "last_updated": "2025-09", "type": "chat" }, { - "id": "glm-5-3-flash", - "name": "GLM-5.3 Flash", - "display_name": "GLM-5.3 Flash", + "id": "qwen/qwen3.5-27b", + "name": "Qwen3.5 27B", + "display_name": "Qwen3.5 27B", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 131072, + "output": 32768 }, "temperature": true, "tool_call": true, @@ -297983,70 +298197,67 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "low", - "high", - "max" + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, - "attachment": true, + "attachment": false, "open_weights": true, - "release_date": "2026-08-26", - "last_updated": "2026-08-26", + "release_date": "2026-02-23", + "last_updated": "2026-02-23", "type": "chat" }, { - "id": "gemini-3-1-flash-lite", - "name": "Gemini 3.1 Flash Lite Preview", - "display_name": "Gemini 3.1 Flash Lite Preview", + "id": "qwen/qwen3-max-preview", + "name": "Qwen3 Max Preview", + "display_name": "Qwen3 Max Preview", "modalities": { "input": [ - "text", - "image", - "video", - "audio", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, + "context": 256000, "output": 65536 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true + "supported": true }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "low", - "medium", - "high" + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, - "attachment": true, + "attachment": false, "open_weights": false, - "knowledge": "2025-01", - "release_date": "2026-03-03", - "last_updated": "2026-03-03", + "knowledge": "2025-04", + "release_date": "2025-09-05", + "last_updated": "2025-09-23", "type": "chat" }, { - "id": "claude-opus-5-5", - "name": "Claude Opus 5.5", - "display_name": "Claude Opus 5.5", + "id": "qwen/qwen3.7-max", + "name": "Qwen3.7 Max", + "display_name": "Qwen3.7 Max", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" @@ -298054,9 +298265,9 @@ }, "limit": { "context": 1000000, - "output": 128000 + "output": 250000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -298064,31 +298275,22 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" - ] + "supported": true } }, - "attachment": true, + "attachment": false, "open_weights": false, - "knowledge": "2026-06", - "release_date": "2026-09-22", - "last_updated": "2026-09-22", + "release_date": "2026-05-21", + "last_updated": "2026-05-21", "type": "chat" }, { - "id": "claude-sonnet-5", - "name": "Claude Sonnet 5", - "display_name": "Claude Sonnet 5", + "id": "qwen/qwen3.6-flash", + "name": "Qwen3.6 Flash", + "display_name": "Qwen3.6 Flash", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" @@ -298096,9 +298298,9 @@ }, "limit": { "context": 1000000, - "output": 128000 + "output": 250000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -298107,26 +298309,24 @@ "extra_capabilities": { "reasoning": { "supported": true, - "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, - "attachment": true, + "attachment": false, "open_weights": false, - "knowledge": "2026-01-31", - "release_date": "2026-06-30", - "last_updated": "2026-06-30", + "release_date": "2026-04-27", + "last_updated": "2026-04-27", "type": "chat" }, { - "id": "qwen3-next-80b-a3b-instruct", - "name": "Qwen3-Next 80B-A3B Instruct", - "display_name": "Qwen3-Next 80B-A3B Instruct", + "id": "qwen/qwen-plus", + "name": "Qwen Plus", + "display_name": "Qwen Plus", "modalities": { "input": [ "text" @@ -298136,37 +298336,48 @@ ] }, "limit": { - "context": 131072, - "output": 10000 + "context": 1000000, + "output": 250000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } }, "attachment": false, - "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-09", - "last_updated": "2025-09", + "open_weights": false, + "knowledge": "2024-04", + "release_date": "2024-01-25", + "last_updated": "2025-09-11", "type": "chat" }, { - "id": "deepseek-v4-1-flash", - "name": "DeepSeek V4.1 Flash", - "display_name": "DeepSeek V4.1 Flash", + "id": "qwen/qwen3.8-flash", + "name": "Qwen3.8 Flash", + "display_name": "Qwen3.8 Flash", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 384000 + "context": 977000, + "output": 128000 }, "temperature": true, "tool_call": true, @@ -298176,24 +298387,19 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "low", - "high", - "max" - ] + "supported": true } }, - "attachment": true, - "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-09-10", - "last_updated": "2026-09-10", + "attachment": false, + "open_weights": false, + "release_date": "2026-08-26", + "last_updated": "2026-08-26", "type": "chat" }, { - "id": "glm-5-3", - "name": "GLM-5.3", - "display_name": "GLM-5.3", + "id": "qwen/qwen3.6-35b-a3b", + "name": "Qwen3.6 35B A3B", + "display_name": "Qwen3.6 35B A3B", "modalities": { "input": [ "text" @@ -298203,8 +298409,8 @@ ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 262144, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -298214,38 +298420,38 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "low", - "high", - "max" + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, "attachment": false, "open_weights": true, - "release_date": "2026-08-14", - "last_updated": "2026-08-14", + "release_date": "2026-04-17", + "last_updated": "2026-04-17", "type": "chat" }, { - "id": "gpt-6-sol", - "name": "GPT-6 Sol", - "display_name": "GPT-6 Sol", + "id": "qwen/qwen3-30b-a3b", + "name": "Qwen3 30B A3B", + "display_name": "Qwen3 30B A3B", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 131072, + "output": 32768 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -298253,31 +298459,28 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "none", - "low", - "medium", - "high", - "xhigh" + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-04-20", - "release_date": "2026-09-22", - "last_updated": "2026-09-22", + "attachment": false, + "open_weights": true, + "release_date": "2025-04-28", + "last_updated": "2025-04-28", "type": "chat" }, { - "id": "claude-sonnet-4-6", - "name": "Claude Sonnet 4.6", - "display_name": "Claude Sonnet 4.6", + "id": "qwen/qwen3.5-plus", + "name": "Qwen3.5 Plus", + "display_name": "Qwen3.5 Plus", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" @@ -298285,68 +298488,49 @@ }, "limit": { "context": 1000000, - "output": 64000 + "output": 250000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "mixed", - "budget": { - "min": 1024, - "unit": "tokens" - }, - "effort": "high", - "effort_options": [ - "low", - "medium", - "high", - "max" - ], "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ "thinking_blocks" - ], - "notes": [ - "Anthropic recommends adaptive thinking with effort for Claude 4.6; budget_tokens remains a deprecated compatibility path.", - "Anthropic API defaults effort to high; lower effort levels should be chosen per workload." ] } }, - "attachment": true, + "attachment": false, "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-02-17", - "last_updated": "2026-03-13", + "knowledge": "2025-04", + "release_date": "2026-02-16", + "last_updated": "2026-02-16", "type": "chat" }, { - "id": "gpt-5-6-sol", - "name": "GPT-5.6 Sol", - "display_name": "GPT-5.6 Sol", + "id": "qwen/qwen3.5-flash", + "name": "Qwen3.5 Flash", + "display_name": "Qwen3.5 Flash", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 1000000, + "output": 250000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -298355,37 +298539,52 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", - "effort_options": [ - "none", - "low", - "medium", - "high", - "xhigh", - "max" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] } }, - "attachment": true, + "attachment": false, "open_weights": false, - "knowledge": "2026-02-16", - "release_date": "2026-07-09", - "last_updated": "2026-07-09", + "release_date": "2026-02-23", + "last_updated": "2026-02-23", "type": "chat" }, { - "id": "gpt-oss-120b", - "name": "GPT OSS 120B", - "display_name": "GPT OSS 120B", + "id": "qwen/qwen3-coder-next", + "name": "Qwen3 Coder Next", + "display_name": "Qwen3 Coder Next", + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 262144, + "output": 65536 + }, + "temperature": true, + "tool_call": false, + "reasoning": { + "supported": false + }, + "attachment": false, + "open_weights": true, + "knowledge": "2025-09", + "release_date": "2026-02-03", + "last_updated": "2026-02-03", + "type": "chat" + }, + { + "id": "qwen/qwen3-235b-a22b", + "name": "Qwen3 235B A22B", + "display_name": "Qwen3 235B A22B", "modalities": { "input": [ "text" @@ -298396,7 +298595,7 @@ }, "limit": { "context": 131072, - "output": 25000 + "output": 32768 }, "temperature": true, "tool_call": true, @@ -298407,66 +298606,53 @@ "extra_capabilities": { "reasoning": { "supported": true, - "effort_options": [ - "low", - "medium", - "high" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, "attachment": false, "open_weights": true, - "knowledge": "2024-06", - "release_date": "2025-08-05", - "last_updated": "2025-08-05", + "knowledge": "2025-04", + "release_date": "2025-04-28", + "last_updated": "2025-04-28", "type": "chat" }, { - "id": "gpt-6-luna", - "name": "GPT-6 Luna", - "display_name": "GPT-6 Luna", + "id": "qwen/qwen3-coder-480b-a35b-instruct", + "name": "Qwen3 Coder 480B A35B Instruct", + "display_name": "Qwen3 Coder 480B A35B Instruct", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 131072, + "output": 32768 }, - "temperature": false, - "tool_call": true, + "temperature": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "none", - "low", - "medium", - "high", - "xhigh" - ] - } + "supported": false }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-05-18", - "release_date": "2026-09-22", - "last_updated": "2026-09-22", + "attachment": false, + "open_weights": true, + "knowledge": "2025-04", + "release_date": "2025-07-22", + "last_updated": "2025-07-22", "type": "chat" }, { - "id": "grok-4-7", - "name": "Grok 4.7", - "display_name": "Grok 4.7", + "id": "qwen/qwen3-next-80b-a3b-thinking", + "name": "Qwen3-Next 80B-A3B (Thinking)", + "display_name": "Qwen3-Next 80B-A3B (Thinking)", "modalities": { "input": [ "text" @@ -298476,36 +298662,37 @@ ] }, "limit": { - "context": 500000, - "output": 500000 + "context": 131072, + "output": 32768 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { "supported": true, "default": true }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "low", - "medium", - "high", - "xhigh" + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, "attachment": false, - "open_weights": false, - "knowledge": "2026-05", - "release_date": "2026-09-21", - "last_updated": "2026-09-21", + "open_weights": true, + "knowledge": "2025-04", + "release_date": "2025-09", + "last_updated": "2025-09", "type": "chat" }, { - "id": "gpt-5-mini", - "name": "GPT-5 Mini", - "display_name": "GPT-5 Mini", + "id": "qwen/qwen3.8-27b", + "name": "Qwen3.8 27B", + "display_name": "Qwen3.8 27B", "modalities": { "input": [ "text", @@ -298516,47 +298703,53 @@ ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 262144, + "output": 65536 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", - "effort_options": [ - "minimal", - "low", - "medium", - "high" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" - } + "attachment": true, + "open_weights": true, + "release_date": "2026-08-14", + "last_updated": "2026-08-14", + "type": "chat" + }, + { + "id": "qwen/qwen3-coder-flash", + "name": "Qwen3 Coder Flash", + "display_name": "Qwen3 Coder Flash", + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1000000, + "output": 250000 }, - "attachment": true, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": false + }, + "attachment": false, "open_weights": false, - "knowledge": "2024-05-30", - "release_date": "2025-08-07", - "last_updated": "2025-08-07", + "knowledge": "2025-04", + "release_date": "2025-07-28", + "last_updated": "2025-07-28", "type": "chat" }, { - "id": "gpt-5-1", - "name": "GPT-5.1", - "display_name": "GPT-5.1", + "id": "qwen/qwen3-vl-235b-a22b-thinking", + "name": "Qwen3-VL 235B A22B Thinking", + "display_name": "Qwen3-VL 235B A22B Thinking", "modalities": { "input": [ "text", @@ -298567,60 +298760,49 @@ ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 131072, + "output": 32768 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "none", - "effort_options": [ - "none", - "low", - "medium", - "high" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2024-09-30", - "release_date": "2025-11-13", - "last_updated": "2025-11-13", + "open_weights": true, + "knowledge": "2025-03-31", + "release_date": "2025-09-23", + "last_updated": "2025-09-23", "type": "chat" }, { - "id": "gpt-5-3-codex", - "name": "GPT-5.3 Codex", - "display_name": "GPT-5.3 Codex", + "id": "qwen/qwen3.6-27b", + "name": "Qwen3.6 27B", + "display_name": "Qwen3.6 27B", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 256000, + "output": 64000 }, "temperature": true, "tool_call": true, @@ -298631,48 +298813,36 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", - "effort_options": [ - "low", - "medium", - "high", - "xhigh" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-02-05", - "last_updated": "2026-02-05", + "open_weights": true, + "release_date": "2026-04-22", + "last_updated": "2026-04-22", "type": "chat" }, { - "id": "inkling", - "name": "Inkling", - "display_name": "Inkling", + "id": "qwen/qwen3.5-9b", + "name": "Qwen3.5 9B", + "display_name": "Qwen3.5 9B", "modalities": { "input": [ "text", - "image", - "audio" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 262144, + "output": 32768 }, "temperature": true, "tool_call": true, @@ -298682,31 +298852,28 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "none", - "minimal", - "low", - "medium", - "high", - "max" + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, "attachment": true, "open_weights": true, - "release_date": "2026-07-15", - "last_updated": "2026-07-15", + "release_date": "2026-02-23", + "last_updated": "2026-02-23", "type": "chat" }, { - "id": "claude-opus-4-6", - "name": "Claude Opus 4.6", - "display_name": "Claude Opus 4.6", + "id": "qwen/qwen-flash", + "name": "Qwen Flash", + "display_name": "Qwen Flash", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" @@ -298714,122 +298881,89 @@ }, "limit": { "context": 1000000, - "output": 128000 + "output": 250000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "mixed", - "budget": { - "min": 1024, - "unit": "tokens" - }, - "effort": "high", - "effort_options": [ - "low", - "medium", - "high", - "max" - ], "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ "thinking_blocks" - ], - "notes": [ - "Anthropic recommends adaptive thinking with effort for Claude 4.6; budget_tokens remains a deprecated compatibility path.", - "Anthropic API defaults effort to high; lower effort levels should be chosen per workload." ] } }, - "attachment": true, + "attachment": false, "open_weights": false, - "knowledge": "2025-05-31", - "release_date": "2026-02-05", - "last_updated": "2026-03-13", + "knowledge": "2024-04", + "release_date": "2025-07-28", + "last_updated": "2025-07-28", "type": "chat" }, { - "id": "claude-opus-4-1", - "name": "Claude Opus 4.1 (latest)", - "display_name": "Claude Opus 4.1 (latest)", + "id": "qwen/qwen3.6-plus", + "name": "Qwen3.6 Plus", + "display_name": "Qwen3.6 Plus", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 32000 + "context": 1000000, + "output": 250000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "budget", - "budget": { - "min": 1024, - "unit": "tokens" - }, "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ "thinking_blocks" - ], - "notes": [ - "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", - "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." ] } }, - "attachment": true, + "attachment": false, "open_weights": false, - "knowledge": "2025-03-31", - "release_date": "2025-08-05", - "last_updated": "2025-08-05", + "knowledge": "2025-04", + "release_date": "2026-04-02", + "last_updated": "2026-04-02", "type": "chat" }, { - "id": "gemini-3-8-flash", - "name": "Gemini 3.8 Flash", - "display_name": "Gemini 3.8 Flash", + "id": "qwen/qwen3.6-max-preview", + "name": "Qwen3.6 Max Preview", + "display_name": "Qwen3.6 Max Preview", "modalities": { "input": [ - "text", - "image", - "video", - "audio", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, + "context": 256000, "output": 65536 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -298837,23 +298971,26 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "low", - "medium", - "high" + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, - "attachment": true, + "attachment": false, "open_weights": false, - "release_date": "2026-09-02", - "last_updated": "2026-09-02", + "knowledge": "2025-04", + "release_date": "2026-04-20", + "last_updated": "2026-04-20", "type": "chat" }, { - "id": "qwen35-122b-a10b", - "name": "Qwen3.5 122B-A10B", - "display_name": "Qwen3.5 122B-A10B", + "id": "qwen/qwen3-coder-plus", + "name": "Qwen3 Coder Plus", + "display_name": "Qwen3 Coder Plus", "modalities": { "input": [ "text" @@ -298863,50 +299000,63 @@ ] }, "limit": { - "context": 262144, - "output": 25000 + "context": 1000000, + "output": 250000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true + "supported": false }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "none", - "low", - "medium", - "high" - ] - } + "attachment": false, + "open_weights": false, + "knowledge": "2025-04", + "release_date": "2025-07-23", + "last_updated": "2025-07-23", + "type": "chat" + }, + { + "id": "qwen/qwen3-235b-a22b-instruct-2507", + "name": "Qwen3 235B A22B Instruct 2507", + "display_name": "Qwen3 235B A22B Instruct 2507", + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "output": 32768 + }, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": false }, "attachment": false, "open_weights": true, - "release_date": "2026-02-23", - "last_updated": "2026-02-23", + "release_date": "2025-07-21", + "last_updated": "2025-07-21", "type": "chat" }, { - "id": "gemini-3-flash", - "name": "Gemini 3 Flash Preview", - "display_name": "Gemini 3 Flash Preview", + "id": "qwen/qwen3.5-35b-a3b", + "name": "Qwen3.5 35B A3B", + "display_name": "Qwen3.5 35B A3B", "modalities": { "input": [ - "text", - "image", - "video", - "audio", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 131072, + "output": 32768 }, "temperature": true, "tool_call": true, @@ -298917,100 +299067,78 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "level", - "level": "high", - "level_options": [ - "minimal", - "low", - "medium", - "high" - ], + "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ - "thought_signatures" + "thinking_blocks" ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-01", - "release_date": "2025-12-17", - "last_updated": "2025-12-17", + "attachment": false, + "open_weights": true, + "release_date": "2026-02-23", + "last_updated": "2026-02-23", "type": "chat" }, { - "id": "claude-haiku-4-5", - "name": "Claude Haiku 4.5 (latest)", - "display_name": "Claude Haiku 4.5 (latest)", + "id": "qwen/qwen3.5-4b", + "name": "Qwen3.5 4B", + "display_name": "Qwen3.5 4B", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 64000 + "context": 262144, + "output": 32768 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "budget", - "budget": { - "min": 1024, - "unit": "tokens" - }, "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ "thinking_blocks" - ], - "notes": [ - "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", - "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2025-02-28", - "release_date": "2025-10-15", - "last_updated": "2025-10-15", + "open_weights": true, + "release_date": "2026-03-02", + "last_updated": "2026-03-02", "type": "chat" }, { - "id": "gpt-5-5", - "name": "GPT-5.5", - "display_name": "GPT-5.5", + "id": "qwen/qwen3.8-max", + "name": "Qwen3.8 Max", + "display_name": "Qwen3.8 Max", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 1000000, + "output": 131072 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -299018,89 +299146,58 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", - "effort_options": [ - "low", - "medium", - "high", - "xhigh" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + "supported": true } }, "attachment": true, "open_weights": false, - "knowledge": "2025-12-01", - "release_date": "2026-04-23", - "last_updated": "2026-04-23", + "release_date": "2026-08-03", + "last_updated": "2026-08-03", "type": "chat" }, { - "id": "gpt-5-4", - "name": "GPT-5.4", - "display_name": "GPT-5.4", + "id": "qwen/qwen3.5-397b-a17b", + "name": "Qwen3.5 397B A17B", + "display_name": "Qwen3.5 397B A17B", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 131072, + "output": 32768 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "none", - "effort_options": [ - "none", - "low", - "medium", - "high", - "xhigh" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-03-05", - "last_updated": "2026-03-05", + "attachment": false, + "open_weights": true, + "release_date": "2026-02-15", + "last_updated": "2026-02-15", "type": "chat" }, { - "id": "gpt-5-2", - "name": "GPT-5.2", - "display_name": "GPT-5.2", + "id": "qwen/qwen3.7-plus", + "name": "Qwen3.7 Plus", + "display_name": "Qwen3.7 Plus", "modalities": { "input": [ "text", @@ -299111,105 +299208,70 @@ ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 1000000, + "output": 65536 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "none", - "effort_options": [ - "none", - "low", - "medium", - "high", - "xhigh" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + "supported": true } }, "attachment": true, "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2025-12-11", - "last_updated": "2025-12-11", + "knowledge": "2025-04", + "release_date": "2026-06-02", + "last_updated": "2026-06-02", "type": "chat" }, { - "id": "claude-opus-4-8", - "name": "Claude Opus 4.8", - "display_name": "Claude Opus 4.8", + "id": "qwen/qwen3.5-122b-a10b", + "name": "Qwen3.5 122B A10B", + "display_name": "Qwen3.5 122B A10B", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 131072, + "output": 32768 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "high", - "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" - ], "interleaved": true, "summaries": true, - "visibility": "omitted", + "visibility": "summary", "continuation": [ "thinking_blocks" - ], - "notes": [ - "Claude Opus 4.7 and newer Opus models require thinking.type = \"adaptive\" to enable thinking explicitly.", - "Manual budget_tokens requests return 400 on Claude Opus 4.7 and newer adaptive-only Opus models.", - "task_budget is separate from thinking control and should not be treated as a thinking budget." ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-01", - "release_date": "2026-05-28", - "last_updated": "2026-05-28", + "attachment": false, + "open_weights": true, + "release_date": "2026-02-23", + "last_updated": "2026-02-23", "type": "chat" }, { - "id": "meta-llama-3-1-8b-instruct", - "name": "Llama 3.1 8B Instruct", - "display_name": "Llama 3.1 8B Instruct", + "id": "qwen/qwen3-32b", + "name": "Qwen3 32B", + "display_name": "Qwen3 32B", "modalities": { "input": [ "text" @@ -299220,24 +299282,36 @@ }, "limit": { "context": 131072, - "output": 8192 + "output": 32768 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } }, "attachment": false, "open_weights": true, - "knowledge": "2023-12-31", - "release_date": "2024-07-23", - "last_updated": "2024-07-23", + "knowledge": "2025-04", + "release_date": "2025-04-28", + "last_updated": "2025-04-28", "type": "chat" }, { - "id": "gemma-3-12b", - "name": "Gemma 3 12B", - "display_name": "Gemma 3 12B", + "id": "qwen/qwen3-vl-plus", + "name": "Qwen3-VL Plus", + "display_name": "Qwen3-VL Plus", "modalities": { "input": [ "text", @@ -299248,43 +299322,78 @@ ] }, "limit": { - "context": 131072, - "output": 8192 + "context": 262144, + "output": 65536 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } }, "attachment": true, - "open_weights": true, - "knowledge": "2024-08-31", - "release_date": "2025-03-12", - "last_updated": "2025-03-12", + "open_weights": false, + "knowledge": "2025-04", + "release_date": "2025-09-23", + "last_updated": "2025-09-23", "type": "chat" }, { - "id": "gemini-4-argon", - "name": "Gemini 4 Argon", - "display_name": "Gemini 4 Argon", + "id": "deepseek/deepseek-v3", + "name": "DeepSeek V3", + "display_name": "DeepSeek V3", "modalities": { "input": [ - "text", - "image", - "video", - "audio", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 163840, + "output": 81920 }, - "temperature": false, + "temperature": true, "tool_call": true, + "reasoning": { + "supported": false + }, + "attachment": false, + "open_weights": true, + "release_date": "2024-12-26", + "last_updated": "2024-12-26", + "type": "chat" + }, + { + "id": "deepseek/deepseek-v4-flash-0423", + "name": "DeepSeek V4 Flash 0423", + "display_name": "DeepSeek V4 Flash 0423", + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "output": 32768 + }, + "temperature": true, + "tool_call": false, "reasoning": { "supported": true, "default": true @@ -299292,40 +299401,40 @@ "extra_capabilities": { "reasoning": { "effort_options": [ + "none", + "minimal", "low", "medium", - "high" + "high", + "xhigh", + "max" ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-03", - "release_date": "2026-10-13", - "last_updated": "2026-10-13", + "attachment": false, + "open_weights": true, + "knowledge": "2025-05", + "release_date": "2026-04-23", + "last_updated": "2026-04-23", "type": "chat" }, { - "id": "gemini-3-7-flash", - "name": "Gemini 3.7 Flash", - "display_name": "Gemini 3.7 Flash", + "id": "deepseek/deepseek-v4-pro-0423", + "name": "DeepSeek V4 Pro 0423", + "display_name": "DeepSeek V4 Pro 0423", "modalities": { "input": [ - "text", - "image", - "video", - "audio", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 1000000, + "output": 393216 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -299334,189 +299443,130 @@ "extra_capabilities": { "reasoning": { "effort_options": [ + "none", "low", "medium", - "high" + "high", + "max" ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-03", - "release_date": "2026-08-13", - "last_updated": "2026-08-13", + "attachment": false, + "open_weights": true, + "knowledge": "2025-05", + "release_date": "2026-04-23", + "last_updated": "2026-04-23", "type": "chat" }, { - "id": "claude-sonnet-4-5", - "name": "Claude Sonnet 4.5 (latest)", - "display_name": "Claude Sonnet 4.5 (latest)", + "id": "deepseek/deepseek-r1-0528", + "name": "DeepSeek R1 (0528)", + "display_name": "DeepSeek R1 (0528)", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 64000 + "context": 65536, + "output": 32768 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "budget", - "budget": { - "min": 1024, - "unit": "tokens" - }, "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ "thinking_blocks" - ], - "notes": [ - "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", - "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-07-31", - "release_date": "2025-09-29", - "last_updated": "2025-09-29", + "attachment": false, + "open_weights": true, + "release_date": "2025-05-28", + "last_updated": "2025-05-28", "type": "chat" }, { - "id": "claude-fable-5", - "name": "Claude Fable 5", - "display_name": "Claude Fable 5", + "id": "deepseek/deepseek-v3.1", + "name": "DeepSeek V3.1", + "display_name": "DeepSeek V3.1", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 131072, + "output": 32768 }, - "temperature": false, - "tool_call": true, + "temperature": true, + "tool_call": false, "reasoning": { "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "high", - "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "omitted", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Adaptive thinking is always on for Claude Fable 5 and Claude Mythos 5; thinking.type = \"disabled\" is rejected.", - "Manual budget_tokens requests return 400 on Claude Fable 5 and Claude Mythos 5.", - "thinking.display defaults to omitted; set display to summarized to receive readable thinking summaries." - ] - } - }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-01-31", - "release_date": "2026-06-09", - "last_updated": "2026-06-09", + "attachment": false, + "open_weights": true, + "release_date": "2025-08-21", + "last_updated": "2025-08-21", "type": "chat" }, { - "id": "gpt-5-4-mini", - "name": "GPT-5.4 mini", - "display_name": "GPT-5.4 mini", + "id": "deepseek/deepseek-v4-flash-0731-fast", + "name": "DeepSeek V4 Flash 0731", + "display_name": "DeepSeek V4 Flash 0731", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 1000000, + "output": 384000 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "none", - "effort_options": [ - "none", - "low", - "medium", - "high", - "xhigh" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + "supported": true } }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-03-17", - "last_updated": "2026-03-17", + "attachment": false, + "open_weights": true, + "knowledge": "2025-05", + "release_date": "2026-07-31", + "last_updated": "2026-07-31", "type": "chat" }, { - "id": "claude-fable-5-1", - "name": "Claude Fable 5.1", - "display_name": "Claude Fable 5.1", + "id": "deepseek/deepseek-v4.1-flash-1017", + "name": "DeepSeek V4.1 Flash 1017", + "display_name": "DeepSeek V4.1 Flash 1017", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" @@ -299524,9 +299574,9 @@ }, "limit": { "context": 1000000, - "output": 128000 + "output": 384000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -299534,70 +299584,62 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "high", "effort_options": [ + "none", + "minimal", "low", "medium", "high", "xhigh", "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "omitted", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Adaptive thinking is always on for Claude Fable 5 and Claude Mythos 5; thinking.type = \"disabled\" is rejected.", - "Manual budget_tokens requests return 400 on Claude Fable 5 and Claude Mythos 5.", - "thinking.display defaults to omitted; set display to summarized to receive readable thinking summaries." ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2026-06", - "release_date": "2026-09-01", - "last_updated": "2026-09-01", + "open_weights": true, + "knowledge": "2025-05", + "release_date": "2026-10-17", + "last_updated": "2026-10-17", "type": "chat" }, { - "id": "llama-4-maverick", - "name": "Llama 4 Maverick 17B Instruct", - "display_name": "Llama 4 Maverick 17B Instruct", + "id": "deepseek/deepseek-v3.2", + "name": "DeepSeek V3.2", + "display_name": "DeepSeek V3.2", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 8192 + "context": 128000, + "output": 32768 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": true, + "extra_capabilities": { + "reasoning": { + "supported": true + } + }, + "attachment": false, "open_weights": true, - "knowledge": "2024-08", - "release_date": "2025-04-05", - "last_updated": "2025-04-05", + "knowledge": "2024-07", + "release_date": "2025-12-01", + "last_updated": "2025-12-01", "type": "chat" }, { - "id": "gpt-5-nano", - "name": "GPT-5 Nano", - "display_name": "GPT-5 Nano", + "id": "deepseek/deepseek-v4.1-flash", + "name": "DeepSeek V4.1 Flash", + "display_name": "DeepSeek V4.1 Flash", "modalities": { "input": [ "text", @@ -299608,10 +299650,10 @@ ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 1000000, + "output": 384000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -299620,108 +299662,130 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", "effort_options": [ + "none", "minimal", "low", "medium", - "high" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + "high", + "xhigh", + "max" + ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2024-05-30", - "release_date": "2025-08-07", - "last_updated": "2025-08-07", + "open_weights": true, + "knowledge": "2025-05", + "release_date": "2026-09-10", + "last_updated": "2026-09-10", "type": "chat" }, { - "id": "gpt-5-4-nano", - "name": "GPT-5.4 nano", - "display_name": "GPT-5.4 nano", + "id": "deepseek/deepseek-v4-pro", + "name": "DeepSeek V4 Pro", + "display_name": "DeepSeek V4 Pro", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 1000000, + "output": 384000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, + "default_enabled": true, "mode": "effort", - "effort": "none", + "effort": "high", "effort_options": [ - "none", "low", - "medium", "high", - "xhigh" + "max" ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ], - "visibility": "hidden" + "notes": [ + "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." + ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-03-17", - "last_updated": "2026-03-17", + "attachment": false, + "open_weights": true, + "knowledge": "2025-05", + "release_date": "2026-04-24", + "last_updated": "2026-04-24", "type": "chat" - } - ] - }, - "lucidquery": { - "id": "lucidquery", - "name": "LucidQuery", - "display_name": "LucidQuery", - "api": "https://api.lucidquery.com/v1", - "doc": "https://lucidquery.com/docs", - "models": [ + }, { - "id": "lucidquery-agi-01-swift", - "name": "AGI-01 Swift", - "display_name": "AGI-01 Swift", + "id": "deepseek/deepseek-r1", + "name": "DeepSeek R1", + "display_name": "DeepSeek R1", "modalities": { "input": [ - "text", - "image" + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 163840, + "output": 40960 + }, + "temperature": true, + "tool_call": false, + "reasoning": { + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } + }, + "attachment": false, + "open_weights": true, + "knowledge": "2024-07", + "release_date": "2025-01-20", + "last_updated": "2025-05-29", + "type": "chat" + }, + { + "id": "deepseek/deepseek-v4-flash", + "name": "DeepSeek V4 Flash", + "display_name": "DeepSeek V4 Flash", + "modalities": { + "input": [ + "text" ], "output": [ "text" ] }, "limit": { - "context": 300000, - "output": 120000 + "context": 1000000, + "output": 384000 }, "temperature": true, "tool_call": true, @@ -299729,17 +299793,39 @@ "supported": true, "default": true }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-06-05", - "release_date": "2026-06-16", - "last_updated": "2026-06-16", + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "high", + "effort_options": [ + "low", + "high", + "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." + ] + } + }, + "attachment": false, + "open_weights": true, + "knowledge": "2025-05", + "release_date": "2026-04-24", + "last_updated": "2026-04-24", "type": "chat" }, { - "id": "lucidquery-nexus-coder", - "name": "LucidQuery Nexus Coder", - "display_name": "LucidQuery Nexus Coder", + "id": "deepseek/deepseek-v4-pro-0813", + "name": "DeepSeek V4 Pro 0813", + "display_name": "DeepSeek V4 Pro 0813", "modalities": { "input": [ "text" @@ -299749,26 +299835,39 @@ ] }, "limit": { - "context": 250000, - "output": 60000 + "context": 1000000, + "output": 384000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, "default": true }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "effort_options": [ + "none", + "minimal", + "low", + "medium", + "high", + "xhigh", + "max" + ] + } + }, "attachment": false, - "open_weights": false, - "knowledge": "2025-08-01", - "release_date": "2025-09-01", - "last_updated": "2025-09-01", + "open_weights": true, + "release_date": "2026-08-13", + "last_updated": "2026-08-22", "type": "chat" }, { - "id": "lucidnova-rf1-100b", - "name": "LucidNova RF1 100B", - "display_name": "LucidNova RF1 100B", + "id": "deepseek/deepseek-v4-flash-0731", + "name": "DeepSeek V4 Flash 0731", + "display_name": "DeepSeek V4 Flash 0731", "modalities": { "input": [ "text" @@ -299778,26 +299877,38 @@ ] }, "limit": { - "context": 120000, - "output": 8000 + "context": 1048576, + "output": 384000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, "default": true }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "effort_options": [ + "none", + "low", + "medium", + "high", + "max" + ] + } + }, "attachment": false, - "open_weights": false, - "knowledge": "2025-09-16", - "release_date": "2024-12-28", - "last_updated": "2025-09-10", + "open_weights": true, + "knowledge": "2025-05", + "release_date": "2026-07-31", + "last_updated": "2026-07-31", "type": "chat" }, { - "id": "lucidquery-agi-01-frontier", - "name": "AGI-01 Frontier", - "display_name": "AGI-01 Frontier", + "id": "moonshot/kimi-k3", + "name": "Kimi K3", + "display_name": "Kimi K3", "modalities": { "input": [ "text", @@ -299808,52 +299919,53 @@ ] }, "limit": { - "context": 300000, - "output": 120000 + "context": 1048576, + "output": 1048576 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, "default": true }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "effort_options": [ + "none", + "minimal", + "low", + "medium", + "high", + "xhigh", + "max" + ] + } + }, "attachment": true, - "open_weights": false, - "knowledge": "2026-06-05", - "release_date": "2026-06-16", - "last_updated": "2026-06-16", + "open_weights": true, + "release_date": "2026-07-16", + "last_updated": "2026-07-16", "type": "chat" - } - ] - }, - "meta": { - "id": "meta", - "name": "Meta", - "display_name": "Meta", - "api": "https://api.meta.ai/v1", - "doc": "https://dev.meta.ai/docs", - "models": [ + }, { - "id": "muse-spark-1.3-contributor", - "name": "Muse Spark 1.3 Contributor", - "display_name": "Muse Spark 1.3 Contributor", + "id": "moonshot/kimi-k2.7-code", + "name": "Kimi K2.7 Code", + "display_name": "Kimi K2.7 Code", "modalities": { "input": [ "text", - "image", - "video", - "pdf", - "audio" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 262144, + "output": 32768 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -299861,42 +299973,40 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "minimal", - "low", - "medium", - "high", - "xhigh" + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, "attachment": true, - "open_weights": false, - "release_date": "2026-09-02", - "last_updated": "2026-09-02", + "open_weights": true, + "knowledge": "2025-01", + "release_date": "2026-06-12", + "last_updated": "2026-06-12", "type": "chat" }, { - "id": "muse-wonder-1.0", - "name": "Muse Wonder", - "display_name": "Muse Wonder", + "id": "moonshot/kimi-k2.7-code-highspeed", + "name": "Kimi K2.7 Code Highspeed", + "display_name": "Kimi K2.7 Code Highspeed", "modalities": { "input": [ "text", - "image", - "video", - "pdf", - "audio" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 262144, + "output": 32768 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -299904,33 +300014,30 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "minimal", - "low", - "medium", - "high", - "xhigh", - "max" + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, "attachment": true, - "open_weights": false, - "release_date": "2026-10-12", - "last_updated": "2026-10-12", + "open_weights": true, + "knowledge": "2025-01", + "release_date": "2026-06-12", + "last_updated": "2026-06-12", "type": "chat" }, { - "id": "muse-spark-1.3", - "name": "Muse Spark 1.3", - "display_name": "Muse Spark 1.3", + "id": "moonshot/kimi-k3.1-pro", + "name": "Kimi K3.1 Pro", + "display_name": "Kimi K3.1 Pro", "modalities": { "input": [ "text", - "image", - "video", - "pdf", - "audio" + "image" ], "output": [ "text" @@ -299938,9 +300045,9 @@ }, "limit": { "context": 1048576, - "output": 131072 + "output": 1048576 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -299948,43 +300055,37 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, "effort_options": [ - "minimal", "low", - "medium", "high", - "xhigh", "max" ] } }, "attachment": true, "open_weights": false, - "release_date": "2026-09-02", - "last_updated": "2026-09-02", + "release_date": "2026-10-23", + "last_updated": "2026-10-23", "type": "chat" }, { - "id": "muse-spark-1.1", - "name": "Muse Spark 1.1", - "display_name": "Muse Spark 1.1", + "id": "moonshot/kimi-k2.5", + "name": "Kimi K2.5", + "display_name": "Kimi K2.5", "modalities": { "input": [ "text", - "image", - "pdf", - "video" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 262144, + "output": 262144 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -299993,40 +300094,37 @@ "extra_capabilities": { "reasoning": { "supported": true, - "effort_options": [ - "minimal", - "low", - "medium", - "high", - "xhigh" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, "attachment": true, - "open_weights": false, - "release_date": "2026-04-08", - "last_updated": "2026-07-09", + "open_weights": true, + "knowledge": "2025-01", + "release_date": "2026-01-27", + "last_updated": "2026-01-27", "type": "chat" }, { - "id": "muse-spark-1.2", - "name": "Muse Spark 1.2", - "display_name": "Muse Spark 1.2", + "id": "moonshot/kimi-k2.6", + "name": "Kimi K2.6", + "display_name": "Kimi K2.6", "modalities": { "input": [ "text", - "image", - "video", - "pdf", - "audio" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 262144, + "output": 262144 }, "temperature": true, "tool_call": true, @@ -300037,39 +300135,38 @@ "extra_capabilities": { "reasoning": { "supported": true, - "effort_options": [ - "minimal", - "low", - "medium", - "high", - "xhigh" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, "attachment": true, - "open_weights": false, - "release_date": "2026-08-05", - "last_updated": "2026-08-05", + "open_weights": true, + "knowledge": "2025-01", + "release_date": "2026-04-20", + "last_updated": "2026-04-20", "type": "chat" }, { - "id": "muse-spark-1.2-contributor", - "name": "Muse Spark 1.2 Contributor", - "display_name": "Muse Spark 1.2 Contributor", + "id": "zai/glm-5.3-flash", + "name": "GLM-5.3 Flash", + "display_name": "GLM-5.3 Flash", "modalities": { "input": [ "text", "image", "video", - "pdf", - "audio" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1048576, + "context": 1000000, "output": 131072 }, "temperature": true, @@ -300080,47 +300177,35 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, "effort_options": [ - "minimal", "low", - "medium", "high", - "xhigh" + "max" ] } }, "attachment": true, - "open_weights": false, - "release_date": "2026-08-05", - "last_updated": "2026-08-05", + "open_weights": true, + "release_date": "2026-08-26", + "last_updated": "2026-08-26", "type": "chat" - } - ] - }, - "munito": { - "id": "munito", - "name": "Munito AI", - "display_name": "Munito AI", - "api": "https://api.munito.ai/inference/v1", - "doc": "https://docs.munito.ai/models", - "models": [ + }, { - "id": "zai-org/glm-5.3-flash", - "name": "GLM-5.3-Flash", - "display_name": "GLM-5.3-Flash", + "id": "zai/glm-4.7", + "name": "GLM-4.7", + "display_name": "GLM-4.7", "modalities": { "input": [ - "text", - "image", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 943718 + "context": 200000, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -300136,24 +300221,20 @@ "visibility": "summary", "continuation": [ "thinking_blocks" - ], - "effort_options": [ - "low", - "high", - "max" ] } }, - "attachment": true, + "attachment": false, "open_weights": true, - "release_date": "2026-08-26", - "last_updated": "2026-08-26", + "knowledge": "2025-04", + "release_date": "2025-12-22", + "last_updated": "2025-12-22", "type": "chat" }, { - "id": "aleph-alpha/kolibri-1", - "name": "Kolibri 1", - "display_name": "Kolibri 1", + "id": "zai/glm-4.6", + "name": "GLM-4.6", + "display_name": "GLM-4.6", "modalities": { "input": [ "text" @@ -300163,8 +300244,8 @@ ] }, "limit": { - "context": 1048576, - "output": 943718 + "context": 200000, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -300174,45 +300255,31 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "effort_options": [ - "none", - "low", - "medium", - "high" - ] + "supported": true } }, "attachment": false, "open_weights": true, - "knowledge": "2026-06-18", - "release_date": "2026-10-03", - "last_updated": "2026-10-03", + "knowledge": "2025-04", + "release_date": "2025-09-30", + "last_updated": "2025-09-30", "type": "chat" }, { - "id": "qwen/qwen3.8-27b", - "name": "Qwen3.8 27B", - "display_name": "Qwen3.8 27B", + "id": "zai/glm-4.5-air", + "name": "GLM-4.5 Air", + "display_name": "GLM-4.5 Air", "modalities": { "input": [ - "text", - "image", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 235929 + "context": 128000, + "output": 98304 }, "temperature": true, "tool_call": true, @@ -300220,33 +300287,17 @@ "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "effort_options": [ - "none", - "low", - "medium", - "xhigh" - ] - } - }, - "attachment": true, + "attachment": false, "open_weights": true, - "release_date": "2026-08-14", - "last_updated": "2026-08-14", + "knowledge": "2025-04", + "release_date": "2025-07-28", + "last_updated": "2025-07-28", "type": "chat" }, { - "id": "deepseek-ai/deepseek-v4-flash", - "name": "DeepSeek V4 Flash 0731", - "display_name": "DeepSeek V4 Flash 0731", + "id": "zai/glm-5.3", + "name": "GLM-5.3", + "display_name": "GLM-5.3", "modalities": { "input": [ "text" @@ -300256,8 +300307,8 @@ ] }, "limit": { - "context": 1048576, - "output": 943718 + "context": 1000000, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -300268,36 +300319,23 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "high", "effort_options": [ "low", "high", "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." ] } }, "attachment": false, "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-07-31", - "last_updated": "2026-07-31", + "release_date": "2026-08-14", + "last_updated": "2026-08-14", "type": "chat" }, { - "id": "deepseek-ai/deepseek-v4.1-flash-1017", - "name": "DeepSeek V4.1 Flash 1017", - "display_name": "DeepSeek V4.1 Flash 1017", + "id": "zai/glm-4.5v", + "name": "Glm 4.5V", + "display_name": "Glm 4.5V", "modalities": { "input": [ "text", @@ -300308,8 +300346,37 @@ ] }, "limit": { - "context": 1048576, - "output": 943718 + "context": 128000, + "output": 32000 + }, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": true, + "default": true + }, + "attachment": true, + "open_weights": true, + "knowledge": "2025-04", + "release_date": "2025-08-11", + "last_updated": "2025-08-11", + "type": "chat" + }, + { + "id": "zai/glm-5", + "name": "GLM-5", + "display_name": "GLM-5", + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 200000, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -300325,103 +300392,93 @@ "visibility": "summary", "continuation": [ "thinking_blocks" - ], - "effort_options": [ - "none", - "low", - "high", - "max" ] } }, - "attachment": true, + "attachment": false, "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-10-17", - "last_updated": "2026-10-17", + "release_date": "2026-02-12", + "last_updated": "2026-02-12", "type": "chat" - } - ] - }, - "kenari": { - "id": "kenari", - "name": "Kenari", - "display_name": "Kenari", - "api": "https://kenari.id/v1", - "doc": "https://kenari.id/docs", - "models": [ + }, { - "id": "gemini-3-1-flash-tts", - "name": "Gemini 3.1 Flash TTS Preview", - "display_name": "Gemini 3.1 Flash TTS Preview", + "id": "zai/glm-4.7-flashx", + "name": "GLM-4.7 FlashX", + "display_name": "GLM-4.7 FlashX", "modalities": { "input": [ "text" ], "output": [ - "audio" + "text" ] }, "limit": { - "context": 8192, - "output": 16384 + "context": 200000, + "output": 131072 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, "attachment": false, - "open_weights": false, - "knowledge": "2025-01", - "release_date": "2026-04-15", - "last_updated": "2026-04-15" + "open_weights": true, + "knowledge": "2025-04", + "release_date": "2026-01-19", + "last_updated": "2026-01-19", + "type": "chat" }, { - "id": "whisper-large-v3-turbo", - "name": "Whisper Large v3 Turbo", - "display_name": "Whisper Large v3 Turbo", + "id": "zai/glm-4.7-flash", + "name": "GLM 4.7 Flash", + "display_name": "GLM 4.7 Flash", "modalities": { "input": [ - "audio" + "text" ], "output": [ "text" ] }, "limit": { - "context": 448, - "output": 448 + "context": 200000, + "output": 128000 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": true, + "extra_capabilities": { + "reasoning": { + "supported": true + } + }, + "attachment": false, "open_weights": true, - "release_date": "2024-10-01", - "last_updated": "2024-10-01" + "knowledge": "2025-04", + "release_date": "2026-01-19", + "last_updated": "2026-01-19", + "type": "chat" }, { - "id": "gemini-3-6-flash", - "name": "Gemini 3.6 Flash", - "display_name": "Gemini 3.6 Flash", + "id": "zai/glm-5.1", + "name": "GLM-5.1", + "display_name": "GLM-5.1", "modalities": { "input": [ - "text", - "image", - "video", - "audio", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 200000, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -300431,38 +300488,35 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "minimal", - "low", - "medium", - "high" + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-03", - "release_date": "2026-07-21", - "last_updated": "2026-07-21", + "attachment": false, + "open_weights": true, + "release_date": "2026-04-07", + "last_updated": "2026-04-07", "type": "chat" }, { - "id": "mimo-v2-5", - "name": "MiMo-V2.5", - "display_name": "MiMo-V2.5", + "id": "zai/glm-5.2", + "name": "GLM-5.2", + "display_name": "GLM-5.2", "modalities": { "input": [ - "text", - "image", - "audio", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, + "context": 1000000, "output": 131072 }, "temperature": true, @@ -300471,32 +300525,38 @@ "supported": true, "default": true }, - "attachment": true, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } + }, + "attachment": false, "open_weights": true, - "knowledge": "2024-12", - "release_date": "2026-04-22", - "last_updated": "2026-04-22", + "release_date": "2026-06-13", + "last_updated": "2026-06-13", "type": "chat" }, { - "id": "gemini-3-1-pro", - "name": "Gemini 3.1 Pro Preview", - "display_name": "Gemini 3.1 Pro Preview", + "id": "zai/glm-5-turbo", + "name": "GLM-5 Turbo", + "display_name": "GLM-5 Turbo", "modalities": { "input": [ - "text", - "image", - "video", - "audio", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 200000, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -300506,82 +300566,82 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "low", - "medium", - "high" + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, - "attachment": true, + "attachment": false, "open_weights": false, - "knowledge": "2025-01", - "release_date": "2026-02-19", - "last_updated": "2026-02-19", + "release_date": "2026-03-16", + "last_updated": "2026-03-16", "type": "chat" }, { - "id": "kimi-k2-7-code:free", - "name": "Kimi K2.7 Code (Free)", - "display_name": "Kimi K2.7 Code (Free)", + "id": "zai/glm-4.5", + "name": "GLM-4.5", + "display_name": "GLM-4.5", "modalities": { "input": [ - "text", - "image", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 128000, + "output": 98304 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, "default": true }, - "attachment": true, + "attachment": false, "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-06-12", - "last_updated": "2026-06-12", + "knowledge": "2025-04", + "release_date": "2025-07-28", + "last_updated": "2025-07-28", "type": "chat" }, { - "id": "gpt-image-2", - "name": "GPT-Image-2", - "display_name": "GPT-Image-2", + "id": "meta/llama-3.1-8b-instruct", + "name": "Llama 3.1 8B", + "display_name": "Llama 3.1 8B", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ - "image" + "text" ] }, "limit": { - "context": 272000, - "output": 16384 + "context": 128000, + "output": 2048 }, - "temperature": false, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { "supported": false }, - "attachment": true, - "open_weights": false, - "release_date": "2026-04-21", - "last_updated": "2026-04-21" + "attachment": false, + "open_weights": true, + "knowledge": "2023-12", + "release_date": "2024-07-23", + "last_updated": "2024-07-23", + "type": "chat" }, { - "id": "grok-4-6", - "name": "Grok 4.6", - "display_name": "Grok 4.6", + "id": "meta/muse-spark-open", + "name": "Muse Spark Open", + "display_name": "Muse Spark Open", "modalities": { "input": [ "text", @@ -300592,8 +300652,8 @@ ] }, "limit": { - "context": 500000, - "output": 500000 + "context": 1048576, + "output": 262144 }, "temperature": true, "tool_call": true, @@ -300612,16 +300672,16 @@ } }, "attachment": true, - "open_weights": false, - "knowledge": "2026-02-01", - "release_date": "2026-08-12", - "last_updated": "2026-08-12", + "open_weights": true, + "knowledge": "2026-01-04", + "release_date": "2026-10-15", + "last_updated": "2026-10-15", "type": "chat" }, { - "id": "glm-5-2", - "name": "GLM-5.2", - "display_name": "GLM-5.2", + "id": "meta/llama-3.3-70b-instruct", + "name": "Llama 3.3 70B Instruct", + "display_name": "Llama 3.3 70B Instruct", "modalities": { "input": [ "text" @@ -300631,77 +300691,62 @@ ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 131072, + "output": 32768 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "none", - "high", - "xhigh" - ] - } + "supported": false }, "attachment": false, "open_weights": true, - "release_date": "2026-06-13", - "last_updated": "2026-06-13", + "knowledge": "2023-12", + "release_date": "2024-12-06", + "last_updated": "2024-12-06", "type": "chat" }, { - "id": "gemini-3-5-flash", - "name": "Gemini 3.5 Flash", - "display_name": "Gemini 3.5 Flash", + "id": "meta/llama-3.1-70b-instruct", + "name": "Llama 3.1 70B", + "display_name": "Llama 3.1 70B", "modalities": { "input": [ - "text", - "image", - "video", - "audio", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 128000, + "output": 2048 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "minimal", - "low", - "medium", - "high" - ] - } + "supported": false }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-01", - "release_date": "2026-05-19", - "last_updated": "2026-05-19", + "attachment": false, + "open_weights": true, + "knowledge": "2023-12", + "release_date": "2024-07-23", + "last_updated": "2024-07-23", "type": "chat" - }, + } + ] + }, + "arcee": { + "id": "arcee", + "name": "Arcee", + "display_name": "Arcee", + "api": "https://api.arcee.ai/api/v1", + "doc": "https://docs.arcee.ai", + "models": [ { - "id": "deepseek-v4-flash", - "name": "DeepSeek V4 Flash", - "display_name": "DeepSeek V4 Flash", + "id": "trinity-large-thinking", + "name": "Trinity Large Thinking", + "display_name": "Trinity Large Thinking", "modalities": { "input": [ "text" @@ -300711,8 +300756,8 @@ ] }, "limit": { - "context": 1000000, - "output": 384000 + "context": 262144, + "output": 80000 }, "temperature": true, "tool_call": true, @@ -300723,36 +300768,24 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "high", - "effort_options": [ - "low", - "high", - "max" - ], "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ "thinking_blocks" - ], - "notes": [ - "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." ] } }, "attachment": false, "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-04-24", - "last_updated": "2026-04-24", + "release_date": "2026-04-01", + "last_updated": "2026-05-28", "type": "chat" }, { - "id": "gpt-oss-20b", - "name": "GPT OSS 20B", - "display_name": "GPT OSS 20B", + "id": "thinkingmachines/inkling-small", + "name": "Inkling Small", + "display_name": "Inkling Small", "modalities": { "input": [ "text" @@ -300762,8 +300795,8 @@ ] }, "limit": { - "context": 131072, - "output": 32768 + "context": 262144, + "output": 262144 }, "temperature": true, "tool_call": true, @@ -300774,37 +300807,34 @@ "extra_capabilities": { "reasoning": { "supported": true, - "effort_options": [ - "low", - "medium", - "high" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, "attachment": false, "open_weights": true, - "knowledge": "2024-06", - "release_date": "2025-08-05", - "last_updated": "2025-08-05", + "release_date": "2026-07-30", + "last_updated": "2026-07-30", "type": "chat" }, { - "id": "qwen3-8-max", - "name": "Qwen3.8 Max", - "display_name": "Qwen3.8 Max", + "id": "moonshotai/kimi-k3", + "name": "Kimi K3", + "display_name": "Kimi K3", "modalities": { "input": [ - "text", - "image", - "video", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, + "context": 1048576, "output": 131072 }, "temperature": true, @@ -300815,23 +300845,30 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ "low", - "medium", - "xhigh" + "high", + "max" ] } }, - "attachment": true, - "open_weights": false, - "release_date": "2026-08-03", - "last_updated": "2026-08-03", + "attachment": false, + "open_weights": true, + "release_date": "2026-07-16", + "last_updated": "2026-07-16", "type": "chat" }, { - "id": "nano-banana-2", - "name": "Nano Banana 2", - "display_name": "Nano Banana 2", + "id": "zai-org/glm-5.3-flash", + "name": "GLM-5.3-Flash", + "display_name": "GLM-5.3-Flash", "modalities": { "input": [ "text", @@ -300840,38 +300877,44 @@ "pdf" ], "output": [ - "text", - "image" + "text" ] }, "limit": { - "context": 131072, - "output": 32768 + "context": 1000000, + "output": 131072 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { "supported": true, "default": true }, "extra_capabilities": { "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ - "minimal", - "high" + "high", + "max" ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2025-01", - "release_date": "2026-05-28", - "last_updated": "2026-05-28" + "open_weights": true, + "release_date": "2026-08-26", + "last_updated": "2026-08-26", + "type": "chat" }, { - "id": "mimo-v2-5-pro", - "name": "MiMo-V2.5-Pro", - "display_name": "MiMo-V2.5-Pro", + "id": "zai-org/glm-5.3", + "name": "GLM-5.3", + "display_name": "GLM-5.3", "modalities": { "input": [ "text" @@ -300881,7 +300924,7 @@ ] }, "limit": { - "context": 1048576, + "context": 1000000, "output": 131072 }, "temperature": true, @@ -300890,49 +300933,78 @@ "supported": true, "default": true }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "effort_options": [ + "high", + "max" + ] + } + }, "attachment": false, "open_weights": true, - "knowledge": "2024-12", - "release_date": "2026-04-22", - "last_updated": "2026-04-22", + "release_date": "2026-08-14", + "last_updated": "2026-08-14", "type": "chat" }, { - "id": "grok-imagine-image-2-0", - "name": "Grok Imagine Image 2.0", - "display_name": "Grok Imagine Image 2.0", + "id": "zai-org/glm-5.2", + "name": "GLM-5.2", + "display_name": "GLM-5.2", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ - "image" + "text" ] }, "limit": { - "context": 64000, - "output": 8192 + "context": 262144, + "output": 131072 }, - "temperature": false, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": true, - "open_weights": false, - "release_date": "2026-08-07", - "last_updated": "2026-08-07" + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "effort_options": [ + "high", + "max" + ] + } + }, + "attachment": false, + "open_weights": true, + "release_date": "2026-06-13", + "last_updated": "2026-06-13", + "type": "chat" }, { - "id": "qwen3-7-plus", - "name": "Qwen3.7 Plus", - "display_name": "Qwen3.7 Plus", + "id": "deepseek/deepseek-v4.1-flash-1017", + "name": "DeepSeek V4.1 Flash 1017", + "display_name": "DeepSeek V4.1 Flash 1017", "modalities": { "input": [ "text", - "image", - "video" + "image" ], "output": [ "text" @@ -300940,7 +301012,7 @@ }, "limit": { "context": 1000000, - "output": 64000 + "output": 384000 }, "temperature": true, "tool_call": true, @@ -300948,30 +301020,43 @@ "supported": true, "default": true }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "effort_options": [ + "high", + "max" + ] + } + }, "attachment": true, - "open_weights": false, - "knowledge": "2025-04", - "release_date": "2026-06-02", - "last_updated": "2026-06-02", + "open_weights": true, + "knowledge": "2025-05", + "release_date": "2026-10-17", + "last_updated": "2026-10-17", "type": "chat" }, { - "id": "nano-banana-2-lite", - "name": "Nano Banana 2 Lite", - "display_name": "Nano Banana 2 Lite", + "id": "deepseek/deepseek-v4-flash-latest", + "name": "DeepSeek V4 Flash Latest", + "display_name": "DeepSeek V4 Flash Latest", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ - "text", - "image" + "text" ] }, "limit": { - "context": 65536, - "output": 4096 + "context": 1048576, + "output": 384000 }, "temperature": true, "tool_call": true, @@ -300981,37 +301066,44 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ - "minimal", - "high" + "high", + "max" ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-01", - "release_date": "2026-06-30", - "last_updated": "2026-06-30" + "attachment": false, + "open_weights": true, + "knowledge": "2025-05", + "release_date": "2026-07-31", + "last_updated": "2026-07-31", + "type": "chat" }, { - "id": "kimi-k3", - "name": "Kimi K3", - "display_name": "Kimi K3", + "id": "deepseek/deepseek-v4.1-flash", + "name": "DeepSeek V4.1 Flash", + "display_name": "DeepSeek V4.1 Flash", "modalities": { "input": [ "text", - "image", - "video" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 1000000, + "output": 384000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -301020,8 +301112,13 @@ "extra_capabilities": { "reasoning": { "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ - "low", "high", "max" ] @@ -301029,12 +301126,13 @@ }, "attachment": true, "open_weights": true, - "release_date": "2026-07-16", - "last_updated": "2026-07-16", + "knowledge": "2025-05", + "release_date": "2026-09-10", + "last_updated": "2026-09-10", "type": "chat" }, { - "id": "deepseek-v4-pro", + "id": "deepseek/deepseek-v4-pro", "name": "DeepSeek V4 Pro", "display_name": "DeepSeek V4 Pro", "modalities": { @@ -301046,7 +301144,7 @@ ] }, "limit": { - "context": 1000000, + "context": 512000, "output": 384000 }, "temperature": true, @@ -301085,14 +301183,66 @@ "type": "chat" }, { - "id": "claude-opus-5", - "name": "Claude Opus 5", - "display_name": "Claude Opus 5", + "id": "deepseek/deepseek-v4-pro-0813", + "name": "DeepSeek V4 Pro 0813", + "display_name": "DeepSeek V4 Pro 0813", + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1048576, + "output": 384000 + }, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "effort_options": [ + "high", + "max" + ] + } + }, + "attachment": false, + "open_weights": true, + "release_date": "2026-08-13", + "last_updated": "2026-08-22", + "type": "chat" + } + ] + }, + "stepfun-step-plan": { + "id": "stepfun-step-plan", + "name": "StepFun Step Plan (China)", + "display_name": "StepFun Step Plan (China)", + "api": "https://api.stepfun.com/step_plan/v1", + "doc": "https://platform.stepfun.com/docs/zh/step-plan/integrations/reasoning-api", + "models": [ + { + "id": "step-5-preview", + "name": "Step 5 Preview", + "display_name": "Step 5 Preview", "modalities": { "input": [ "text", "image", - "pdf" + "video" ], "output": [ "text" @@ -301100,9 +301250,9 @@ }, "limit": { "context": 1000000, - "output": 128000 + "output": 384000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -301111,65 +301261,70 @@ "extra_capabilities": { "reasoning": { "supported": true, + "mode": "effort", + "effort": "medium", "effort_options": [ "low", "medium", - "high", - "xhigh", - "max" + "high" + ], + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2026-05", - "release_date": "2026-07-24", - "last_updated": "2026-07-24", + "open_weights": true, + "release_date": "2026-09-16", + "last_updated": "2026-09-20", "type": "chat" }, { - "id": "grok-imagine-image", - "name": "Grok Imagine Image", - "display_name": "Grok Imagine Image", + "id": "step-router-v1", + "name": "Step Router v1", + "display_name": "Step Router v1", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ - "image" + "text" ] }, "limit": { - "context": 16000, - "output": 8192 + "context": 256000, + "output": 256000 }, - "temperature": false, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { "supported": false }, - "attachment": true, + "attachment": false, "open_weights": false, - "release_date": "2026-01-28", - "last_updated": "2026-01-28" + "knowledge": "2025-01", + "release_date": "2026-05-29", + "last_updated": "2026-05-29", + "type": "chat" }, { - "id": "gemma-4-31b-it", - "name": "Gemma 4 31B IT", - "display_name": "Gemma 4 31B IT", + "id": "step-3.5-flash-2603", + "name": "Step 3.5 Flash 2603", + "display_name": "Step 3.5 Flash 2603", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 32768 + "context": 256000, + "output": 256000 }, "temperature": true, "tool_call": true, @@ -301179,10 +301334,20 @@ }, "extra_capabilities": { "reasoning": { - "supported": true + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "effort_options": [ + "low", + "high" + ] } }, - "attachment": true, + "attachment": false, "open_weights": true, "knowledge": "2025-01", "release_date": "2026-04-02", @@ -301190,24 +301355,22 @@ "type": "chat" }, { - "id": "gpt-5-6-terra", - "name": "GPT-5.6 Terra", - "display_name": "GPT-5.6 Terra", + "id": "step-3.5-flash", + "name": "Step 3.5 Flash", + "display_name": "Step 3.5 Flash", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 256000, + "output": 256000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -301216,107 +301379,126 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", - "effort_options": [ - "none", - "low", - "medium", - "high", - "xhigh", - "max" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ], - "verbosity": "medium", - "verbosity_options": [ + "effort_options": [ "low", - "medium", "high" - ], - "visibility": "hidden" + ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-02-16", - "release_date": "2026-07-09", - "last_updated": "2026-07-09", + "attachment": false, + "open_weights": true, + "knowledge": "2025-01", + "release_date": "2026-01-29", + "last_updated": "2026-02-13", "type": "chat" }, { - "id": "claude-opus-4-7", - "name": "Claude Opus 4.7", - "display_name": "Claude Opus 4.7", + "id": "step-3.7-flash", + "name": "Step 3.7 Flash", + "display_name": "Step 3.7 Flash", "modalities": { "input": [ "text", "image", - "pdf" + "video" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 256000, + "output": 256000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "high", - "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" - ], "interleaved": true, "summaries": true, - "visibility": "omitted", + "visibility": "summary", "continuation": [ "thinking_blocks" ], - "notes": [ - "Claude Opus 4.7 and newer Opus models require thinking.type = \"adaptive\" to enable thinking explicitly.", - "Manual budget_tokens requests return 400 on Claude Opus 4.7 and newer adaptive-only Opus models.", - "task_budget is separate from thinking control and should not be treated as a thinking budget." + "effort_options": [ + "low", + "medium", + "high" ] } }, "attachment": true, + "open_weights": true, + "knowledge": "2026-03-01", + "release_date": "2026-05-29", + "last_updated": "2026-05-29", + "type": "chat" + } + ] + }, + "lucidquery": { + "id": "lucidquery", + "name": "LucidQuery", + "display_name": "LucidQuery", + "api": "https://api.lucidquery.com/v1", + "doc": "https://lucidquery.com/docs", + "models": [ + { + "id": "lucidquery-agi-01-frontier", + "name": "AGI-01 Frontier", + "display_name": "AGI-01 Frontier", + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 300000, + "output": 120000 + }, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": true, + "default": true + }, + "attachment": true, "open_weights": false, - "knowledge": "2026-01-31", - "release_date": "2026-04-16", - "last_updated": "2026-04-16", + "knowledge": "2026-06-05", + "release_date": "2026-06-16", + "last_updated": "2026-06-16", "type": "chat" }, { - "id": "gpt-5-6-luna", - "name": "GPT-5.6 Luna", - "display_name": "GPT-5.6 Luna", + "id": "lucidquery-nexus-coder", + "name": "LucidQuery Nexus Coder", + "display_name": "LucidQuery Nexus Coder", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 250000, + "output": 60000 }, "temperature": false, "tool_call": true, @@ -301324,54 +301506,58 @@ "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", - "effort_options": [ - "none", - "low", - "medium", - "high", - "xhigh", - "max" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" - } + "attachment": false, + "open_weights": false, + "knowledge": "2025-08-01", + "release_date": "2025-09-01", + "last_updated": "2025-09-01", + "type": "chat" + }, + { + "id": "lucidnova-rf1-100b", + "name": "LucidNova RF1 100B", + "display_name": "LucidNova RF1 100B", + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] }, - "attachment": true, + "limit": { + "context": 120000, + "output": 8000 + }, + "temperature": false, + "tool_call": true, + "reasoning": { + "supported": true, + "default": true + }, + "attachment": false, "open_weights": false, - "knowledge": "2026-02-16", - "release_date": "2026-07-09", - "last_updated": "2026-07-09", + "knowledge": "2025-09-16", + "release_date": "2024-12-28", + "last_updated": "2025-09-10", "type": "chat" }, { - "id": "glm-5-3-flash", - "name": "GLM-5.3-Flash", - "display_name": "GLM-5.3-Flash", + "id": "lucidquery-agi-01-swift", + "name": "AGI-01 Swift", + "display_name": "AGI-01 Swift", "modalities": { "input": [ "text", - "image", - "video", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 300000, + "output": 120000 }, "temperature": true, "tool_call": true, @@ -301379,40 +301565,37 @@ "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "low", - "high", - "max" - ] - } - }, "attachment": true, - "open_weights": true, - "release_date": "2026-08-26", - "last_updated": "2026-08-26", + "open_weights": false, + "knowledge": "2026-06-05", + "release_date": "2026-06-16", + "last_updated": "2026-06-16", "type": "chat" - }, + } + ] + }, + "io-net": { + "id": "io-net", + "name": "IO.NET", + "display_name": "IO.NET", + "api": "https://api.intelligence.io.solutions/api/v1", + "doc": "https://io.net/docs/guides/intelligence/io-intelligence", + "models": [ { - "id": "gemini-3-1-flash-lite", - "name": "Gemini 3.1 Flash Lite", - "display_name": "Gemini 3.1 Flash Lite", + "id": "openai/gpt-oss-120b", + "name": "GPT-OSS 120B", + "display_name": "GPT-OSS 120B", "modalities": { "input": [ - "text", - "image", - "video", - "audio", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 131072, + "output": 4096 }, "temperature": true, "tool_call": true, @@ -301422,25 +301605,25 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, "effort_options": [ - "minimal", "low", "medium", "high" ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-01", - "release_date": "2026-05-07", - "last_updated": "2026-05-07", + "attachment": false, + "open_weights": true, + "knowledge": "2024-10", + "release_date": "2025-08-05", + "last_updated": "2025-08-05", "type": "chat" }, { - "id": "nemotron-3-super-120b-a12b:free", - "name": "Nemotron 3 Super 120B A12B (Free)", - "display_name": "Nemotron 3 Super 120B A12B (Free)", + "id": "openai/gpt-oss-20b", + "name": "GPT-OSS 20B", + "display_name": "GPT-OSS 20B", "modalities": { "input": [ "text" @@ -301450,8 +301633,8 @@ ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 64000, + "output": 4096 }, "temperature": true, "tool_call": true, @@ -301461,74 +301644,95 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, "effort_options": [ - "none", "low", - "medium" + "medium", + "high" ] } }, "attachment": false, "open_weights": true, - "knowledge": "2025-06", - "release_date": "2026-03-11", - "last_updated": "2026-03-11", + "knowledge": "2024-10", + "release_date": "2025-08-05", + "last_updated": "2025-08-05", "type": "chat" }, { - "id": "minimax-m3", - "name": "MiniMax-M3", - "display_name": "MiniMax-M3", + "id": "Qwen/Qwen3-Next-80B-A3B-Instruct", + "name": "Qwen 3 Next 80B Instruct", + "display_name": "Qwen 3 Next 80B Instruct", "modalities": { "input": [ - "text", - "image", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 512000 + "context": 262144, + "output": 4096 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true + "supported": false }, - "extra_capabilities": { - "reasoning": { - "supported": true - } + "attachment": false, + "open_weights": true, + "knowledge": "2024-12", + "release_date": "2025-09", + "last_updated": "2025-09", + "type": "chat" + }, + { + "id": "Qwen/Qwen2.5-VL-32B-Instruct", + "name": "Qwen 2.5 VL 32B Instruct", + "display_name": "Qwen 2.5 VL 32B Instruct", + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 32000, + "output": 4096 + }, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": false }, "attachment": true, "open_weights": true, - "release_date": "2026-06-01", - "last_updated": "2026-06-01", + "knowledge": "2024-09", + "release_date": "2024-11-01", + "last_updated": "2024-11-01", "type": "chat" }, { - "id": "claude-sonnet-5", - "name": "Claude Sonnet 5", - "display_name": "Claude Sonnet 5", + "id": "Qwen/Qwen3-235B-A22B-Thinking-2507", + "name": "Qwen 3 235B Thinking", + "display_name": "Qwen 3 235B Thinking", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 262144, + "output": 4096 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -301537,26 +301741,25 @@ "extra_capabilities": { "reasoning": { "supported": true, - "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-01-31", - "release_date": "2026-06-30", - "last_updated": "2026-06-30", + "attachment": false, + "open_weights": true, + "knowledge": "2024-12", + "release_date": "2025-07-25", + "last_updated": "2025-07-25", "type": "chat" }, { - "id": "deepseek-v4-1-flash", - "name": "DeepSeek V4.1 Flash", - "display_name": "DeepSeek V4.1 Flash", + "id": "meta-llama/Llama-3.2-90B-Vision-Instruct", + "name": "Llama 3.2 90B Vision Instruct", + "display_name": "Llama 3.2 90B Vision Instruct", "modalities": { "input": [ "text", @@ -301567,36 +301770,82 @@ ] }, "limit": { - "context": 1000000, - "output": 384000 + "context": 16000, + "output": 4096 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true + "supported": false }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "none", - "low", - "high", - "max" - ] - } + "attachment": true, + "open_weights": true, + "knowledge": "2023-12", + "release_date": "2024-09-25", + "last_updated": "2024-09-25", + "type": "chat" + }, + { + "id": "meta-llama/Llama-3.3-70B-Instruct", + "name": "Llama 3.3 70B Instruct", + "display_name": "Llama 3.3 70B Instruct", + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 4096 + }, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": false + }, + "attachment": false, + "open_weights": true, + "knowledge": "2023-12", + "release_date": "2024-12-06", + "last_updated": "2024-12-06", + "type": "chat" + }, + { + "id": "meta-llama/Llama-4-Maverick-17B-128E-Instruct-FP8", + "name": "Llama 4 Maverick 17B 128E Instruct", + "display_name": "Llama 4 Maverick 17B 128E Instruct", + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 430000, + "output": 4096 + }, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": false }, "attachment": true, "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-09-10", - "last_updated": "2026-09-10", + "knowledge": "2024-12", + "release_date": "2025-04-05", + "last_updated": "2025-04-05", "type": "chat" }, { - "id": "glm-5-3", - "name": "GLM-5.3", - "display_name": "GLM-5.3", + "id": "moonshotai/Kimi-K2-Thinking", + "name": "Kimi K2 Thinking", + "display_name": "Kimi K2 Thinking", "modalities": { "input": [ "text" @@ -301606,8 +301855,8 @@ ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 32768, + "output": 4096 }, "temperature": true, "tool_call": true, @@ -301617,113 +301866,150 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "low", - "high", - "max" + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, "attachment": false, "open_weights": true, - "release_date": "2026-08-14", - "last_updated": "2026-08-14", + "knowledge": "2024-08", + "release_date": "2025-11-06", + "last_updated": "2025-11-06", "type": "chat" }, { - "id": "claude-sonnet-4-6", - "name": "Claude Sonnet 4.6", - "display_name": "Claude Sonnet 4.6", + "id": "moonshotai/Kimi-K2-Instruct-0905", + "name": "Kimi K2 Instruct", + "display_name": "Kimi K2 Instruct", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 64000 + "context": 32768, + "output": 4096 + }, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": false + }, + "attachment": false, + "open_weights": true, + "knowledge": "2024-08", + "release_date": "2025-09-05", + "last_updated": "2025-09-05", + "type": "chat" + }, + { + "id": "zai-org/GLM-4.6", + "name": "GLM 4.6", + "display_name": "GLM 4.6", + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 200000, + "output": 4096 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "mixed", - "budget": { - "min": 1024, - "unit": "tokens" - }, - "effort": "high", "effort_options": [ + "none", "low", "medium", - "high", - "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Anthropic recommends adaptive thinking with effort for Claude 4.6; budget_tokens remains a deprecated compatibility path.", - "Anthropic API defaults effort to high; lower effort levels should be chosen per workload." + "high" ] } }, - "attachment": true, + "attachment": false, + "open_weights": true, + "knowledge": "2024-10", + "release_date": "2025-09-30", + "last_updated": "2025-09-30", + "type": "chat" + }, + { + "id": "mistralai/Devstral-Small-2505", + "name": "Devstral Small 2505", + "display_name": "Devstral Small 2505", + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 4096 + }, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": false + }, + "attachment": false, "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-02-17", - "last_updated": "2026-03-13", + "knowledge": "2024-12", + "release_date": "2025-05-01", + "last_updated": "2025-05-01", "type": "chat" }, { - "id": "kimi-k2-6:free", - "name": "Kimi K2.6 (Free)", - "display_name": "Kimi K2.6 (Free)", + "id": "mistralai/Mistral-Large-Instruct-2411", + "name": "Mistral Large Instruct 2411", + "display_name": "Mistral Large Instruct 2411", "modalities": { "input": [ - "text", - "image", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 128000, + "output": 4096 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true + "supported": false }, - "attachment": true, + "attachment": false, "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-04-20", - "last_updated": "2026-04-20", + "knowledge": "2024-10", + "release_date": "2024-11-18", + "last_updated": "2024-11-18", "type": "chat" }, { - "id": "glm-4-7-flash:free", - "name": "GLM-4.7-Flash (Free)", - "display_name": "GLM-4.7-Flash (Free)", + "id": "mistralai/Mistral-Nemo-Instruct-2407", + "name": "Mistral Nemo Instruct 2407", + "display_name": "Mistral Nemo Instruct 2407", "modalities": { "input": [ "text" @@ -301733,80 +302019,53 @@ ] }, "limit": { - "context": 200000, - "output": 131072 + "context": 128000, + "output": 4096 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true + "supported": false }, "attachment": false, "open_weights": true, - "knowledge": "2025-04", - "release_date": "2026-01-19", - "last_updated": "2026-01-19", + "knowledge": "2024-05", + "release_date": "2024-07-18", + "last_updated": "2024-07-18", "type": "chat" }, { - "id": "gpt-5-6-sol", - "name": "GPT-5.6 Sol", - "display_name": "GPT-5.6 Sol", + "id": "mistralai/Magistral-Small-2506", + "name": "Magistral Small 2506", + "display_name": "Magistral Small 2506", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 128000, + "output": 4096 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", - "effort_options": [ - "none", - "low", - "medium", - "high", - "xhigh", - "max" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" - } + "supported": false }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-02-16", - "release_date": "2026-07-09", - "last_updated": "2026-07-09", + "attachment": false, + "open_weights": true, + "knowledge": "2025-01", + "release_date": "2025-06-10", + "last_updated": "2025-06-10", "type": "chat" }, { - "id": "minimax-m2-7-highspeed", - "name": "MiniMax-M2.7-highspeed", - "display_name": "MiniMax-M2.7-highspeed", + "id": "Intel/Qwen3-Coder-480B-A35B-Instruct-int4-mixed-ar", + "name": "Qwen 3 Coder 480B", + "display_name": "Qwen 3 Coder 480B", "modalities": { "input": [ "text" @@ -301816,25 +302075,25 @@ ] }, "limit": { - "context": 204800, - "output": 131072 + "context": 106000, + "output": 4096 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true + "supported": false }, "attachment": false, "open_weights": true, - "release_date": "2026-03-18", - "last_updated": "2026-03-18", + "knowledge": "2024-12", + "release_date": "2025-07-22", + "last_updated": "2025-07-22", "type": "chat" }, { - "id": "gpt-oss-120b", - "name": "GPT OSS 120B", - "display_name": "GPT OSS 120B", + "id": "deepseek-ai/DeepSeek-R1-0528", + "name": "DeepSeek R1", + "display_name": "DeepSeek R1", "modalities": { "input": [ "text" @@ -301844,8 +302103,8 @@ ] }, "limit": { - "context": 131072, - "output": 32768 + "context": 128000, + "output": 4096 }, "temperature": true, "tool_call": true, @@ -301856,84 +302115,122 @@ "extra_capabilities": { "reasoning": { "supported": true, - "effort_options": [ - "low", - "medium", - "high" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, "attachment": false, "open_weights": true, - "knowledge": "2024-06", - "release_date": "2025-08-05", - "last_updated": "2025-08-05", + "knowledge": "2024-07", + "release_date": "2025-05-28", + "last_updated": "2025-05-28", "type": "chat" - }, + } + ] + }, + "vivgrid": { + "id": "vivgrid", + "name": "Vivgrid", + "display_name": "Vivgrid", + "api": "https://api.vivgrid.com/v1", + "doc": "https://docs.vivgrid.com/models", + "models": [ { - "id": "mimo-v2-5:free", - "name": "MiMo-V2.5 (Free)", - "display_name": "MiMo-V2.5 (Free)", + "id": "gpt-5.4-nano", + "name": "GPT-5.4 Nano", + "display_name": "GPT-5.4 Nano", "modalities": { "input": [ "text", "image", - "audio", - "video" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 400000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": false, + "mode": "effort", + "effort": "none", + "effort_options": [ + "none", + "low", + "medium", + "high", + "xhigh" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" + } }, "attachment": true, - "open_weights": true, - "knowledge": "2024-12", - "release_date": "2026-04-22", - "last_updated": "2026-04-22", + "open_weights": false, + "knowledge": "2025-08-31", + "release_date": "2026-03-17", + "last_updated": "2026-03-17", "type": "chat" }, { - "id": "glm-5-1", - "name": "GLM-5.1", - "display_name": "GLM-5.1", + "id": "kimi-k3", + "name": "Kimi K3", + "display_name": "Kimi K3", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 200000, + "context": 1000000, "output": 131072 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, "default": true }, - "attachment": false, + "extra_capabilities": { + "reasoning": { + "supported": true + } + }, + "attachment": true, "open_weights": true, - "release_date": "2026-04-07", - "last_updated": "2026-04-07", + "release_date": "2026-07-16", + "last_updated": "2026-07-16", "type": "chat" }, { - "id": "grok-build-0-1", - "name": "Grok Build 0.1", - "display_name": "Grok Build 0.1", + "id": "gpt-6.1-terra", + "name": "GPT-6.1 Terra", + "display_name": "GPT-6.1 Terra", "modalities": { "input": [ "text", @@ -301945,143 +302242,210 @@ ] }, "limit": { - "context": 256000, - "output": 256000 + "context": 1050000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, "default": true }, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "none", + "low", + "medium", + "high", + "xhigh", + "max" + ] + } + }, "attachment": true, "open_weights": false, - "release_date": "2026-04-16", - "last_updated": "2026-04-16", + "knowledge": "2026-06-01", + "release_date": "2026-10-26", + "last_updated": "2026-10-26", "type": "chat" }, { - "id": "hy3", - "name": "Hy3", - "display_name": "Hy3", + "id": "gpt-5.4", + "name": "GPT-5.4", + "display_name": "GPT-5.4", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 256000, + "context": 400000, "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": false, + "mode": "effort", + "effort": "none", "effort_options": [ "none", "low", + "medium", + "high", + "xhigh" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", "high" - ] + ], + "visibility": "hidden" } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-07-06", - "last_updated": "2026-07-06", + "attachment": true, + "open_weights": false, + "knowledge": "2025-08-31", + "release_date": "2026-03-05", + "last_updated": "2026-03-05", "type": "chat" }, { - "id": "nemotron-3-ultra-550b-a55b", - "name": "Nemotron 3 Ultra 550B A55B", - "display_name": "Nemotron 3 Ultra 550B A55B", + "id": "gpt-5.4-mini", + "name": "GPT-5.4 Mini", + "display_name": "GPT-5.4 Mini", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1000000, + "context": 400000, "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { - "supported": true + "supported": true, + "default_enabled": false, + "mode": "effort", + "effort": "none", + "effort_options": [ + "none", + "low", + "medium", + "high", + "xhigh" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-09", - "release_date": "2026-06-04", - "last_updated": "2026-06-04", + "attachment": true, + "open_weights": false, + "knowledge": "2025-08-31", + "release_date": "2026-03-17", + "last_updated": "2026-03-17", "type": "chat" }, { - "id": "gemini-2-5-flash-lite", - "name": "Gemini 2.5 Flash-Lite", - "display_name": "Gemini 2.5 Flash-Lite", + "id": "gpt-5.1-codex-max", + "name": "GPT-5.1 Codex Max", + "display_name": "GPT-5.1 Codex Max", "modalities": { "input": [ "text", - "image", - "audio", - "video", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 400000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": false, + "mode": "effort", + "effort": "none", + "effort_options": [ + "none", + "low", + "medium", + "high" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" + } }, "attachment": true, "open_weights": false, - "knowledge": "2025-01", - "release_date": "2025-06-17", - "last_updated": "2025-06-17", + "knowledge": "2024-09-30", + "release_date": "2025-11-13", + "last_updated": "2025-11-13", "type": "chat" }, { - "id": "nemotron-3-super-120b-a12b", - "name": "Nemotron 3 Super 120B A12B", - "display_name": "Nemotron 3 Super 120B A12B", + "id": "gpt-6-sol", + "name": "GPT-6 Sol", + "display_name": "GPT-6 Sol", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 1050000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -302092,21 +302456,24 @@ "effort_options": [ "none", "low", - "medium" + "medium", + "high", + "xhigh", + "max" ] } }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-06", - "release_date": "2026-03-11", - "last_updated": "2026-03-11", + "attachment": true, + "open_weights": false, + "knowledge": "2026-04-20", + "release_date": "2026-09-22", + "last_updated": "2026-09-22", "type": "chat" }, { - "id": "gpt-5-5", - "name": "GPT-5.5", - "display_name": "GPT-5.5", + "id": "gpt-5.6-terra", + "name": "GPT 5.6 Terra", + "display_name": "GPT 5.6 Terra", "modalities": { "input": [ "text", @@ -302121,7 +302488,7 @@ "context": 1050000, "output": 128000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -302134,10 +302501,12 @@ "mode": "effort", "effort": "medium", "effort_options": [ + "none", "low", "medium", "high", - "xhigh" + "xhigh", + "max" ], "verbosity": "medium", "verbosity_options": [ @@ -302150,44 +302519,66 @@ }, "attachment": true, "open_weights": false, - "knowledge": "2025-12-01", - "release_date": "2026-04-23", - "last_updated": "2026-04-23", + "knowledge": "2026-02-16", + "release_date": "2026-07-09", + "last_updated": "2026-07-09", "type": "chat" }, { - "id": "nemotron-3-nano-30b-a3b", - "name": "Nemotron 3 Nano 30B A3B", - "display_name": "Nemotron 3 Nano 30B A3B", + "id": "gpt-5.3-codex", + "name": "GPT-5.3 Codex", + "display_name": "GPT-5.3 Codex", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 400000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, "default": true }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-06-25", - "release_date": "2025-12-15", - "last_updated": "2025-12-15", + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", + "effort_options": [ + "low", + "medium", + "high", + "xhigh" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" + } + }, + "attachment": true, + "open_weights": false, + "knowledge": "2025-08-31", + "release_date": "2026-02-05", + "last_updated": "2026-02-05", "type": "chat" }, { - "id": "claude-opus-4-8", - "name": "Claude Opus 4.8", - "display_name": "Claude Opus 4.8", + "id": "gpt-6-astra", + "name": "GPT-6 Astra", + "display_name": "GPT-6 Astra", "modalities": { "input": [ "text", @@ -302199,107 +302590,104 @@ ] }, "limit": { - "context": 1000000, + "context": 1050000, "output": 128000 }, "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "high", "effort_options": [ "low", "medium", "high", "xhigh", "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "omitted", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Claude Opus 4.7 and newer Opus models require thinking.type = \"adaptive\" to enable thinking explicitly.", - "Manual budget_tokens requests return 400 on Claude Opus 4.7 and newer adaptive-only Opus models.", - "task_budget is separate from thinking control and should not be treated as a thinking budget." ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-01", - "release_date": "2026-05-28", - "last_updated": "2026-05-28", + "knowledge": "2026-04-30", + "release_date": "2026-09-04", + "last_updated": "2026-09-04", "type": "chat" }, { - "id": "step-3-7-flash:free", - "name": "Step 3.7 Flash (Free)", - "display_name": "Step 3.7 Flash (Free)", + "id": "gpt-5.1-codex", + "name": "GPT-5.1 Codex", + "display_name": "GPT-5.1 Codex", "modalities": { "input": [ "text", - "image", - "video" + "image" ], "output": [ "text" ] }, "limit": { - "context": 256000, - "output": 256000 + "context": 400000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": false, + "mode": "effort", + "effort": "none", "effort_options": [ + "none", "low", "medium", "high" - ] + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, "attachment": true, - "open_weights": true, - "knowledge": "2026-03-01", - "release_date": "2026-05-29", - "last_updated": "2026-05-29", + "open_weights": false, + "knowledge": "2024-09-30", + "release_date": "2025-11-13", + "last_updated": "2025-11-13", "type": "chat" }, { - "id": "step-3-7-flash", - "name": "Step 3.7 Flash", - "display_name": "Step 3.7 Flash", + "id": "claude-haiku-5-5", + "name": "Claude Haiku 5.5", + "display_name": "Claude Haiku 5.5", "modalities": { "input": [ "text", "image", - "video" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 256000, - "output": 256000 + "context": 1000000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -302310,21 +302698,23 @@ "effort_options": [ "low", "medium", - "high" + "high", + "xhigh", + "max" ] } }, "attachment": true, - "open_weights": true, - "knowledge": "2026-03-01", - "release_date": "2026-05-29", - "last_updated": "2026-05-29", + "open_weights": false, + "knowledge": "2026-06", + "release_date": "2026-10-07", + "last_updated": "2026-10-07", "type": "chat" }, { - "id": "mistral-medium-3-5:free", - "name": "Mistral Medium 3.5 (Free)", - "display_name": "Mistral Medium 3.5 (Free)", + "id": "gpt-5.2-codex", + "name": "GPT-5.2 Codex", + "display_name": "GPT-5.2 Codex", "modalities": { "input": [ "text", @@ -302335,10 +302725,10 @@ ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 400000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -302346,35 +302736,49 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", "effort_options": [ - "none", + "low", + "medium", + "high", + "xhigh" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", "high" - ] + ], + "visibility": "hidden" } }, "attachment": true, - "open_weights": true, - "release_date": "2026-04-29", - "last_updated": "2026-04-29", + "open_weights": false, + "knowledge": "2025-08-31", + "release_date": "2025-12-11", + "last_updated": "2025-12-11", "type": "chat" }, { - "id": "kimi-k2-7-code", - "name": "Kimi K2.7 Code", - "display_name": "Kimi K2.7 Code", + "id": "gpt-6.1-sol", + "name": "GPT-6.1 Sol", + "display_name": "GPT-6.1 Sol", "modalities": { "input": [ "text", "image", - "video" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 1050000, + "output": 128000 }, "temperature": false, "tool_call": true, @@ -302382,51 +302786,75 @@ "supported": true, "default": true }, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" + ] + } + }, "attachment": true, - "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-06-12", - "last_updated": "2026-06-12", + "open_weights": false, + "knowledge": "2026-04-30", + "release_date": "2026-09-29", + "last_updated": "2026-09-29", "type": "chat" }, { - "id": "minimax-m2-7", - "name": "MiniMax-M2.7", - "display_name": "MiniMax-M2.7", + "id": "claude-sonnet-5", + "name": "Claude Sonnet 5", + "display_name": "Claude Sonnet 5", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 204800, - "output": 131072 + "context": 1000000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, "default": true }, - "attachment": false, - "open_weights": true, - "release_date": "2026-03-18", - "last_updated": "2026-03-18", + "extra_capabilities": { + "reasoning": { + "supported": true, + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" + ] + } + }, + "attachment": true, + "open_weights": false, + "knowledge": "2026-01-31", + "release_date": "2026-06-30", + "last_updated": "2026-06-30", "type": "chat" }, { - "id": "gemini-3-7-flash", - "name": "Gemini 3.7 Flash", - "display_name": "Gemini 3.7 Flash", + "id": "claude-fable-5", + "name": "Claude Fable 5", + "display_name": "Claude Fable 5", "modalities": { "input": [ "text", "image", - "video", - "audio", "pdf" ], "output": [ @@ -302434,10 +302862,10 @@ ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 1000000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -302445,91 +302873,88 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "high", "effort_options": [ "low", "medium", - "high" + "high", + "xhigh", + "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "omitted", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Adaptive thinking is always on for Claude Fable 5 and Claude Mythos 5; thinking.type = \"disabled\" is rejected.", + "Manual budget_tokens requests return 400 on Claude Fable 5 and Claude Mythos 5.", + "thinking.display defaults to omitted; set display to summarized to receive readable thinking summaries." ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-03", - "release_date": "2026-08-13", - "last_updated": "2026-08-13", + "knowledge": "2026-01-31", + "release_date": "2026-06-09", + "last_updated": "2026-06-09", "type": "chat" }, { - "id": "mistral-large:free", - "name": "Mistral Large (Free)", - "display_name": "Mistral Large (Free)", + "id": "glm-5.3-flash", + "name": "GLM-5.3-Flash", + "display_name": "GLM-5.3-Flash", "modalities": { "input": [ "text", - "image" + "image", + "video", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 1000000, + "output": 131072 }, "temperature": true, "tool_call": true, - "reasoning": { - "supported": false - }, - "attachment": true, - "open_weights": true, - "knowledge": "2024-11", - "release_date": "2024-11-01", - "last_updated": "2025-12-02", - "type": "chat" - }, - { - "id": "nano-banana-pro", - "name": "Nano Banana Pro", - "display_name": "Nano Banana Pro", - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text", - "image" - ] - }, - "limit": { - "context": 65536, - "output": 32768 - }, - "temperature": true, - "tool_call": false, "reasoning": { "supported": true, "default": true }, "extra_capabilities": { "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ "low", - "high" + "high", + "max" ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2025-01", - "release_date": "2026-05-28", - "last_updated": "2026-05-28" + "open_weights": true, + "release_date": "2026-08-26", + "last_updated": "2026-08-26", + "type": "chat" }, { - "id": "deepseek-v4-flash:free", - "name": "DeepSeek V4 Flash (Free)", - "display_name": "DeepSeek V4 Flash (Free)", + "id": "deepseek-v3.2", + "name": "DeepSeek-V3.2", + "display_name": "DeepSeek-V3.2", "modalities": { "input": [ "text" @@ -302539,8 +302964,8 @@ ] }, "limit": { - "context": 1000000, - "output": 384000 + "context": 128000, + "output": 128000 }, "temperature": true, "tool_call": true, @@ -302550,28 +302975,32 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, "effort_options": [ + "none", "low", - "high", - "max" + "medium", + "high" ] } }, "attachment": false, "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-04-24", - "last_updated": "2026-04-24", + "knowledge": "2024-07", + "release_date": "2025-12-01", + "last_updated": "2025-12-01", "type": "chat" }, { - "id": "claude-fable-5", - "name": "Claude Fable 5", - "display_name": "Claude Fable 5", + "id": "gemini-3.8-flash", + "name": "Gemini 3.8 Flash", + "display_name": "Gemini 3.8 Flash", "modalities": { "input": [ "text", "image", + "video", + "audio", "pdf" ], "output": [ @@ -302579,10 +303008,10 @@ ] }, "limit": { - "context": 1000000, + "context": 1048576, "output": 128000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -302592,97 +303021,79 @@ "reasoning": { "supported": true, "default_enabled": true, - "mode": "effort", - "effort": "high", - "effort_options": [ + "mode": "level", + "level": "high", + "level_options": [ + "minimal", "low", "medium", - "high", - "xhigh", - "max" + "high" ], - "interleaved": true, "summaries": true, - "visibility": "omitted", + "visibility": "summary", "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Adaptive thinking is always on for Claude Fable 5 and Claude Mythos 5; thinking.type = \"disabled\" is rejected.", - "Manual budget_tokens requests return 400 on Claude Fable 5 and Claude Mythos 5.", - "thinking.display defaults to omitted; set display to summarized to receive readable thinking summaries." + "thought_signatures" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-01-31", - "release_date": "2026-06-09", - "last_updated": "2026-06-09", + "release_date": "2026-09-02", + "last_updated": "2026-09-02", "type": "chat" }, { - "id": "gpt-5-4-mini", - "name": "GPT-5.4 mini", - "display_name": "GPT-5.4 mini", + "id": "claude-opus-5-5", + "name": "Claude Opus 5.5", + "display_name": "Claude Opus 5.5", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 400000, + "context": 1000000, "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "none", "effort_options": [ - "none", "low", "medium", "high", - "xhigh" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + "xhigh", + "max" + ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-03-17", - "last_updated": "2026-03-17", + "knowledge": "2026-06", + "release_date": "2026-09-22", + "last_updated": "2026-09-22", "type": "chat" }, { - "id": "gemini-2-5-flash", - "name": "Gemini 2.5 Flash", - "display_name": "Gemini 2.5 Flash", + "id": "gemini-3.1-flash-lite-preview", + "name": "Gemini 3.1 Flash Lite Preview", + "display_name": "Gemini 3.1 Flash Lite Preview", "modalities": { "input": [ "text", "image", - "audio", "video", + "audio", "pdf" ], "output": [ @@ -302699,27 +303110,39 @@ "supported": true, "default": true }, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "none", + "low", + "medium", + "high" + ] + } + }, "attachment": true, "open_weights": false, "knowledge": "2025-01", - "release_date": "2025-06-17", - "last_updated": "2025-06-17", + "release_date": "2026-03-03", + "last_updated": "2026-03-03", "type": "chat" }, { - "id": "hy3:free", - "name": "Hy3 (Free)", - "display_name": "Hy3 (Free)", + "id": "gpt-5.6-luna", + "name": "GPT 5.6 Luna", + "display_name": "GPT 5.6 Luna", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 256000, + "context": 1050000, "output": 128000 }, "temperature": true, @@ -302730,23 +303153,38 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", "effort_options": [ "none", "low", + "medium", + "high", + "xhigh", + "max" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", "high" - ] + ], + "visibility": "hidden" } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-07-06", - "last_updated": "2026-07-06", + "attachment": true, + "open_weights": false, + "knowledge": "2026-02-16", + "release_date": "2026-07-09", + "last_updated": "2026-07-09", "type": "chat" }, { - "id": "grok-4-5", - "name": "Grok 4.5", - "display_name": "Grok 4.5", + "id": "gpt-5-mini", + "name": "GPT-5 Mini", + "display_name": "GPT-5 Mini", "modalities": { "input": [ "text", @@ -302757,10 +303195,10 @@ ] }, "limit": { - "context": 500000, - "output": 500000 + "context": 272000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -302768,36 +303206,48 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", "effort_options": [ + "minimal", "low", "medium", "high" - ] + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, "attachment": true, "open_weights": false, - "release_date": "2026-07-08", - "last_updated": "2026-07-08", + "knowledge": "2024-05-30", + "release_date": "2025-08-07", + "last_updated": "2025-08-07", "type": "chat" }, { - "id": "kimi-k2-6", - "name": "Kimi K2.6", - "display_name": "Kimi K2.6", + "id": "deepseek-v4.1-flash", + "name": "DeepSeek V4.1 Flash", + "display_name": "DeepSeek V4.1 Flash", "modalities": { "input": [ "text", - "image", - "video" + "image" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 1000000, + "output": 384000 }, "temperature": true, "tool_call": true, @@ -302805,117 +303255,183 @@ "supported": true, "default": true }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "effort_options": [ + "low", + "high", + "max" + ] + } + }, "attachment": true, "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-04-20", - "last_updated": "2026-04-20", + "knowledge": "2025-05", + "release_date": "2026-09-10", + "last_updated": "2026-09-10", "type": "chat" - } - ] - }, - "stepfun": { - "id": "stepfun", - "name": "StepFun (China)", - "display_name": "StepFun (China)", - "api": "https://api.stepfun.com/v1", - "doc": "https://platform.stepfun.com/docs/zh/overview/concept", - "models": [ + }, { - "id": "stepaudio-2.5-asr", - "name": "StepAudio 2.5 ASR", - "display_name": "StepAudio 2.5 ASR", + "id": "deepseek-v4-pro", + "name": "DeepSeek V4 Pro", + "display_name": "DeepSeek V4 Pro", "modalities": { "input": [ - "audio" + "text" ], "output": [ "text" ] }, "limit": { - "context": 8192, - "output": 8192 + "context": 1000000, + "output": 384000 }, - "temperature": false, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": true, - "open_weights": false, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "high", + "effort_options": [ + "low", + "high", + "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." + ] + } + }, + "attachment": false, + "open_weights": true, + "knowledge": "2025-05", "release_date": "2026-04-24", - "last_updated": "2026-07-02" + "last_updated": "2026-04-24", + "type": "chat" }, { - "id": "step-tts-2", - "name": "Step TTS 2", - "display_name": "Step TTS 2", + "id": "claude-sonnet-5-5", + "name": "Claude Sonnet 5.5", + "display_name": "Claude Sonnet 5.5", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ - "audio" + "text" ] }, "limit": { - "context": 8192, - "output": 8192 + "context": 1000000, + "output": 128000 }, "temperature": false, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" + ] + } + }, + "attachment": true, "open_weights": false, - "release_date": "2026-03-01", - "last_updated": "2026-07-02" + "knowledge": "2026-06", + "release_date": "2026-09-28", + "last_updated": "2026-09-28", + "type": "chat" }, { - "id": "stepaudio-2.5-tts", - "name": "StepAudio 2.5 TTS", - "display_name": "StepAudio 2.5 TTS", + "id": "glm-5.3", + "name": "GLM-5.3", + "display_name": "GLM-5.3", "modalities": { "input": [ "text" ], "output": [ - "audio" + "text" ] }, "limit": { - "context": 8192, - "output": 8192 + "context": 1000000, + "output": 131072 }, - "temperature": false, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "effort_options": [ + "low", + "high", + "max" + ] + } }, "attachment": false, - "open_weights": false, - "release_date": "2026-04-16", - "last_updated": "2026-07-02" + "open_weights": true, + "release_date": "2026-08-14", + "last_updated": "2026-08-14", + "type": "chat" }, { - "id": "step-3.7-flash", - "name": "Step 3.7 Flash", - "display_name": "Step 3.7 Flash", + "id": "gpt-5.6-sol", + "name": "GPT 5.6 Sol", + "display_name": "GPT 5.6 Sol", "modalities": { "input": [ "text", "image", - "video" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 256000, - "output": 256000 + "context": 1050000, + "output": 128000 }, "temperature": true, "tool_call": true, @@ -302926,43 +303442,51 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], + "default_enabled": true, + "mode": "effort", + "effort": "medium", "effort_options": [ + "none", + "low", + "medium", + "high", + "xhigh", + "max" + ], + "verbosity": "medium", + "verbosity_options": [ "low", "medium", "high" - ] + ], + "visibility": "hidden" } }, "attachment": true, - "open_weights": true, - "knowledge": "2026-03-01", - "release_date": "2026-05-29", - "last_updated": "2026-05-29", + "open_weights": false, + "knowledge": "2026-02-16", + "release_date": "2026-07-09", + "last_updated": "2026-07-09", "type": "chat" }, { - "id": "step-3.5-flash-2603", - "name": "Step 3.5 Flash 2603", - "display_name": "Step 3.5 Flash 2603", + "id": "viv-fast", + "name": "Viv Fast", + "display_name": "Viv Fast", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 256000, + "context": 1000000, "output": 256000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -302970,43 +303494,39 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], "effort_options": [ "low", - "high" + "high", + "max" ] } }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-04-02", - "last_updated": "2026-04-02", + "attachment": true, + "open_weights": false, + "knowledge": "2026-09", + "release_date": "2026-09-21", + "last_updated": "2026-09-21", "type": "chat" - }, - { - "id": "step-5-preview", - "name": "Step 5 Preview", - "display_name": "Step 5 Preview", + }, + { + "id": "gemini-3.1-pro-preview", + "name": "Gemini 3.1 Pro Preview", + "display_name": "Gemini 3.1 Pro Preview", "modalities": { "input": [ "text", "image", - "video" + "video", + "audio", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 384000 + "context": 1048576, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -303017,31 +303537,31 @@ "extra_capabilities": { "reasoning": { "supported": true, - "mode": "effort", - "effort": "medium", - "effort_options": [ + "default_enabled": true, + "mode": "level", + "level": "high", + "level_options": [ "low", - "medium", "high" ], - "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ - "thinking_blocks" + "thought_signatures" ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-09-16", - "last_updated": "2026-09-20", + "open_weights": false, + "knowledge": "2025-01", + "release_date": "2026-02-19", + "last_updated": "2026-02-19", "type": "chat" }, { - "id": "step-3.5-flash", - "name": "Step 3.5 Flash", - "display_name": "Step 3.5 Flash", + "id": "deepseek-v4-flash", + "name": "DeepSeek V4 Flash", + "display_name": "DeepSeek V4 Flash", "modalities": { "input": [ "text" @@ -303051,8 +303571,8 @@ ] }, "limit": { - "context": 256000, - "output": 256000 + "context": 1000000, + "output": 384000 }, "temperature": true, "tool_call": true, @@ -303063,138 +303583,160 @@ "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "high", + "effort_options": [ + "low", + "high", + "max" + ], "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ "thinking_blocks" ], - "effort_options": [ - "low", - "high" + "notes": [ + "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." ] } }, "attachment": false, "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-01-29", - "last_updated": "2026-02-13", + "knowledge": "2025-05", + "release_date": "2026-07-31", + "last_updated": "2026-07-31", "type": "chat" - } - ] - }, - "llmtr": { - "id": "llmtr", - "name": "LLMTR", - "display_name": "LLMTR", - "api": "https://llmtr.com/v1", - "doc": "https://llmtr.com/docs", - "models": [ + }, { - "id": "qwen3-6-35b", - "name": "Qwen3.6 35B-A3B", - "display_name": "Qwen3.6 35B-A3B", + "id": "gpt-5.5", + "name": "GPT-5.5", + "display_name": "GPT-5.5", "modalities": { "input": [ "text", "image", - "video" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 16384, - "output": 16384 + "context": 1050000, + "output": 128000 }, - "temperature": true, - "tool_call": false, + "temperature": false, + "tool_call": true, "reasoning": { "supported": true, "default": true }, - "attachment": true, - "open_weights": true, - "release_date": "2026-04-17", - "last_updated": "2026-04-17", - "type": "chat" - }, - { - "id": "trendyol-asure-12b", - "name": "Trendyol Asure 12B", - "display_name": "Trendyol Asure 12B", - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 40960, - "output": 40960 - }, - "temperature": true, - "tool_call": false, - "reasoning": { - "supported": false + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", + "effort_options": [ + "low", + "medium", + "high", + "xhigh" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" + } }, "attachment": true, - "open_weights": true, - "release_date": "2026-02-19", - "last_updated": "2026-02-20", + "open_weights": false, + "knowledge": "2025-12-01", + "release_date": "2026-04-23", + "last_updated": "2026-04-23", "type": "chat" }, { - "id": "gemma-4", - "name": "Gemma 4", - "display_name": "Gemma 4", + "id": "claude-fable-5-1", + "name": "Claude Fable 5.1", + "display_name": "Claude Fable 5.1", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 131072 + "context": 1000000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, "default": true }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "high", + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "omitted", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Adaptive thinking is always on for Claude Fable 5 and Claude Mythos 5; thinking.type = \"disabled\" is rejected.", + "Manual budget_tokens requests return 400 on Claude Fable 5 and Claude Mythos 5.", + "thinking.display defaults to omitted; set display to summarized to receive readable thinking summaries." + ] + } + }, "attachment": true, - "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-04-02", - "last_updated": "2026-04-02", + "open_weights": false, + "knowledge": "2026-06", + "release_date": "2026-09-01", + "last_updated": "2026-09-01", "type": "chat" }, { - "id": "muse-glimmer-30b-tr", - "name": "Muse Glimmer 30B (TR)", - "display_name": "Muse Glimmer 30B (TR)", + "id": "gemini-3.7-flash", + "name": "Gemini 3.7 Flash", + "display_name": "Gemini 3.7 Flash", "modalities": { "input": [ "text", - "image" + "image", + "video", + "audio", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 131072 + "context": 1048576, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -303202,17 +303744,36 @@ "supported": true, "default": true }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "level", + "level": "high", + "level_options": [ + "minimal", + "low", + "medium", + "high" + ], + "summaries": true, + "visibility": "summary", + "continuation": [ + "thought_signatures" + ] + } + }, "attachment": true, - "open_weights": true, - "knowledge": "2026-01-04", - "release_date": "2026-08-10", - "last_updated": "2026-08-10", + "open_weights": false, + "knowledge": "2026-03", + "release_date": "2026-08-13", + "last_updated": "2026-08-13", "type": "chat" }, { - "id": "magibu-11b-v8", - "name": "Magibu 11B v8", - "display_name": "Magibu 11B v8", + "id": "glm-5.2", + "name": "GLM-5.2", + "display_name": "GLM-5.2", "modalities": { "input": [ "text" @@ -303222,52 +303783,83 @@ ] }, "limit": { - "context": 8192, - "output": 8192 + "context": 1000000, + "output": 128000 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "effort_options": [ + "high", + "max" + ] + } }, "attachment": false, - "open_weights": false, - "release_date": "2026-06-05", - "last_updated": "2026-08-16", + "open_weights": true, + "release_date": "2026-06-13", + "last_updated": "2026-06-13", "type": "chat" }, { - "id": "medgemma-4b", - "name": "MedGemma 4B", - "display_name": "MedGemma 4B", + "id": "claude-opus-5", + "name": "Claude Opus 5", + "display_name": "Claude Opus 5", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 8192, - "output": 8192 + "context": 1000000, + "output": 128000 }, - "temperature": true, - "tool_call": false, + "temperature": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" + ] + } }, "attachment": true, - "open_weights": true, - "release_date": "2026-04-26", - "last_updated": "2026-08-16", + "open_weights": false, + "knowledge": "2026-05", + "release_date": "2026-07-24", + "last_updated": "2026-07-24", "type": "chat" }, { - "id": "publicai/apertus-8b-instruct", - "name": "Apertus 8B Instruct", - "display_name": "Apertus 8B Instruct", + "id": "deepseek-v4-pro-0813", + "name": "DeepSeek V4 Pro 0813", + "display_name": "DeepSeek V4 Pro 0813", "modalities": { "input": [ "text" @@ -303277,66 +303869,55 @@ ] }, "limit": { - "context": 65536, - "output": 8192 + "context": 1000000, + "output": 384000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false - }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-09", - "release_date": "2025-09-02", - "last_updated": "2025-09-02", - "type": "chat" - }, - { - "id": "publicai/apertus-70b-instruct", - "name": "Apertus 70B Instruct", - "display_name": "Apertus 70B Instruct", - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 65536, - "output": 8192 + "supported": true, + "default": true }, - "temperature": true, - "tool_call": true, - "reasoning": { - "supported": false + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "effort_options": [ + "high", + "max" + ] + } }, "attachment": false, "open_weights": true, - "knowledge": "2025-09", - "release_date": "2025-09-02", - "last_updated": "2025-09-02", + "release_date": "2026-08-13", + "last_updated": "2026-08-22", "type": "chat" }, { - "id": "poolside/laguna-xs-2.1", - "name": "Laguna XS 2.1", - "display_name": "Laguna XS 2.1", + "id": "gpt-6-luna", + "name": "GPT-6 Luna", + "display_name": "GPT-6 Luna", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 32768 + "context": 1050000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -303344,33 +303925,47 @@ }, "extra_capabilities": { "reasoning": { - "supported": true + "effort_options": [ + "none", + "low", + "medium", + "high", + "xhigh", + "max" + ] } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-07-02", - "last_updated": "2026-07-02", + "attachment": true, + "open_weights": false, + "knowledge": "2026-05-18", + "release_date": "2026-09-22", + "last_updated": "2026-09-22", "type": "chat" - }, + } + ] + }, + "tencent-tokenhub": { + "id": "tencent-tokenhub", + "name": "Tencent TokenHub", + "display_name": "Tencent TokenHub", + "api": "https://tokenhub.tencentmaas.com/v1", + "doc": "https://cloud.tencent.com/document/product/1823/130050", + "models": [ { - "id": "mimo/mimo-v2.5", - "name": "MiMo-V2.5", - "display_name": "MiMo-V2.5", + "id": "hy3", + "name": "Hy3", + "display_name": "Hy3", "modalities": { "input": [ - "text", - "image", - "audio", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 256000, + "output": 128000 }, "temperature": true, "tool_call": true, @@ -303378,17 +303973,25 @@ "supported": true, "default": true }, - "attachment": true, + "extra_capabilities": { + "reasoning": { + "supported": true, + "effort_options": [ + "none", + "high" + ] + } + }, + "attachment": false, "open_weights": true, - "knowledge": "2024-12", - "release_date": "2026-04-22", - "last_updated": "2026-04-22", + "release_date": "2026-07-06", + "last_updated": "2026-07-06", "type": "chat" }, { - "id": "mimo/mimo-v2.5-pro", - "name": "MiMo-V2.5-Pro", - "display_name": "MiMo-V2.5-Pro", + "id": "hy4-preview", + "name": "Hy4 preview", + "display_name": "Hy4 preview", "modalities": { "input": [ "text" @@ -303398,8 +304001,8 @@ ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 1024000, + "output": 64000 }, "temperature": true, "tool_call": true, @@ -303407,31 +304010,38 @@ "supported": true, "default": true }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "effort_options": [ + "none", + "high" + ] + } + }, "attachment": false, "open_weights": true, - "knowledge": "2024-12", - "release_date": "2026-04-22", - "last_updated": "2026-04-22", + "release_date": "2026-08-28", + "last_updated": "2026-08-28", "type": "chat" }, { - "id": "sakana/fugu-ultra", - "name": "Fugu Ultra", - "display_name": "Fugu Ultra", + "id": "hy3-preview", + "name": "Hy3 preview", + "display_name": "Hy3 preview", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 1000000 + "context": 256000, + "output": 64000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -303440,21 +304050,31 @@ "extra_capabilities": { "reasoning": { "effort_options": [ - "high", - "xhigh" + "low", + "medium", + "high" ] } }, - "attachment": true, - "open_weights": false, - "release_date": "2026-06-15", - "last_updated": "2026-06-15", + "attachment": false, + "open_weights": true, + "release_date": "2026-04-20", + "last_updated": "2026-04-20", "type": "chat" - }, + } + ] + }, + "upstage": { + "id": "upstage", + "name": "Upstage", + "display_name": "Upstage", + "api": "https://api.upstage.ai/v1/solar", + "doc": "https://developers.upstage.ai/docs/apis/chat", + "models": [ { - "id": "upstage/solar-pro3", - "name": "Solar Pro 3", - "display_name": "Solar Pro 3", + "id": "solar-pro3", + "name": "solar-pro3", + "display_name": "solar-pro3", "modalities": { "input": [ "text" @@ -303484,15 +304104,15 @@ }, "attachment": false, "open_weights": false, - "knowledge": "2025-07", + "knowledge": "2025-03", "release_date": "2026-01-26", "last_updated": "2026-03-23", "type": "chat" }, { - "id": "upstage/solar-pro2", - "name": "Solar Pro 2", - "display_name": "Solar Pro 2", + "id": "solar-pro2", + "name": "solar-pro2", + "display_name": "solar-pro2", "modalities": { "input": [ "text" @@ -303527,9 +304147,9 @@ "type": "chat" }, { - "id": "upstage/solar-pro4", - "name": "Solar Pro 4", - "display_name": "Solar Pro 4", + "id": "solar-mini", + "name": "solar-mini", + "display_name": "solar-mini", "modalities": { "input": [ "text" @@ -303539,104 +304159,78 @@ ] }, "limit": { - "context": 524288, - "output": 131072 + "context": 32768, + "output": 4096 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "none", - "minimal", - "low", - "medium", - "high", - "xhigh", - "max" - ] - } + "supported": false }, "attachment": false, "open_weights": false, - "knowledge": "2026-02", - "release_date": "2026-08-06", - "last_updated": "2026-08-06", + "knowledge": "2024-09", + "release_date": "2024-06-12", + "last_updated": "2025-04-22", "type": "chat" }, { - "id": "google/gemini-2.5-flash-lite", - "name": "Gemini 2.5 Flash-Lite", - "display_name": "Gemini 2.5 Flash-Lite", + "id": "solar-pro4", + "name": "Solar Pro 4", + "display_name": "Solar Pro 4", "modalities": { "input": [ - "text", - "image", - "audio", - "video", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 524288, + "output": 131072 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "budget", - "budget": { - "default": -1, - "min": 512, - "max": 24576, - "auto": -1, - "unit": "tokens" - }, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thought_signatures" + "effort_options": [ + "none", + "minimal", + "low", + "medium", + "high", + "xhigh", + "max" ] } }, - "attachment": true, + "attachment": false, "open_weights": false, - "knowledge": "2025-01", - "release_date": "2025-06-17", - "last_updated": "2025-06-17", + "knowledge": "2026-02", + "release_date": "2026-08-06", + "last_updated": "2026-08-06", "type": "chat" }, { - "id": "thinkingmachines/inkling", - "name": "Inkling", - "display_name": "Inkling", + "id": "solar-mini4", + "name": "Solar Mini 4", + "display_name": "Solar Mini 4", "modalities": { "input": [ - "text", - "image", - "audio" + "text" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 524288, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -303652,33 +304246,43 @@ "low", "medium", "high", - "xhigh" + "xhigh", + "max" ] } }, - "attachment": true, - "open_weights": true, - "release_date": "2026-07-15", - "last_updated": "2026-07-15", + "attachment": false, + "open_weights": false, + "knowledge": "2026-02", + "release_date": "2026-09-22", + "last_updated": "2026-09-22", "type": "chat" - }, + } + ] + }, + "hpc-ai": { + "id": "hpc-ai", + "name": "HPC-AI", + "display_name": "HPC-AI", + "api": "https://api.hpc-ai.com/inference/v1", + "doc": "https://www.hpc-ai.com/doc/docs/quickstart/", + "models": [ { - "id": "thinkingmachines/inkling-small", - "name": "Inkling Small", - "display_name": "Inkling Small", + "id": "openai/gpt-5.5", + "name": "GPT-5.5", + "display_name": "GPT-5.5", "modalities": { "input": [ "text", - "image", - "audio" + "image" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 1050000, + "output": 128000 }, "temperature": true, "tool_call": true, @@ -303688,57 +304292,73 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", "effort_options": [ - "none", - "minimal", "low", "medium", "high", "xhigh" - ] + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, "attachment": true, - "open_weights": true, - "release_date": "2026-07-30", - "last_updated": "2026-07-30", + "open_weights": false, + "knowledge": "2025-12-01", + "release_date": "2026-04-23", + "last_updated": "2026-04-23", "type": "chat" }, { - "id": "mistral/voxtral-small-latest", - "name": "Voxtral Small (latest)", - "display_name": "Voxtral Small (latest)", + "id": "minimax/minimax-m2.5", + "name": "MiniMax-M2.5", + "display_name": "MiniMax-M2.5", "modalities": { "input": [ - "text", - "audio" + "text" ], "output": [ "text" ] }, "limit": { - "context": 32000, - "output": 32000 + "context": 196000, + "output": 195000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": true, + "extra_capabilities": { + "reasoning": { + "supported": true + } + }, + "attachment": false, "open_weights": true, - "release_date": "2025-07-15", - "last_updated": "2025-07-15", + "release_date": "2026-02-12", + "last_updated": "2026-02-12", "type": "chat" }, { - "id": "qwen/qwen3-max", - "name": "Qwen3 Max", - "display_name": "Qwen3 Max", + "id": "moonshotai/kimi-k2.7-code", + "name": "Kimi K2.7 Code", + "display_name": "Kimi K2.7 Code", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" @@ -303746,7 +304366,7 @@ }, "limit": { "context": 256000, - "output": 65536 + "output": 256000 }, "temperature": true, "tool_call": true, @@ -303756,26 +304376,20 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] + "supported": true } }, - "attachment": false, - "open_weights": false, - "knowledge": "2025-04", - "release_date": "2025-09-23", - "last_updated": "2025-09-23", + "attachment": true, + "open_weights": true, + "knowledge": "2025-01", + "release_date": "2026-06-12", + "last_updated": "2026-06-12", "type": "chat" }, { - "id": "qwen/qwen3-vl-plus", - "name": "Qwen3-VL Plus", - "display_name": "Qwen3-VL Plus", + "id": "moonshotai/kimi-k2.5", + "name": "Kimi K2.5", + "display_name": "Kimi K2.5", "modalities": { "input": [ "text", @@ -303787,7 +304401,7 @@ }, "limit": { "context": 256000, - "output": 32768 + "output": 256000 }, "temperature": true, "tool_call": true, @@ -303807,29 +304421,27 @@ } }, "attachment": true, - "open_weights": false, - "knowledge": "2025-04", - "release_date": "2025-09-23", - "last_updated": "2025-09-23", + "open_weights": true, + "knowledge": "2025-01", + "release_date": "2026-01-27", + "last_updated": "2026-01-27", "type": "chat" }, { - "id": "qwen/qwen3.6-flash", - "name": "Qwen3.6 Flash", - "display_name": "Qwen3.6 Flash", + "id": "zai-org/glm-5.1", + "name": "GLM 5.1", + "display_name": "GLM 5.1", "modalities": { "input": [ - "text", - "image", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 65536 + "context": 202000, + "output": 202000 }, "temperature": true, "tool_call": true, @@ -303848,16 +304460,16 @@ ] } }, - "attachment": true, - "open_weights": false, - "release_date": "2026-04-27", - "last_updated": "2026-04-27", + "attachment": false, + "open_weights": true, + "release_date": "2026-04-07", + "last_updated": "2026-04-07", "type": "chat" }, { - "id": "qwen/qwen-flash", - "name": "Qwen Flash", - "display_name": "Qwen Flash", + "id": "zai-org/glm-5.2", + "name": "GLM-5.2", + "display_name": "GLM-5.2", "modalities": { "input": [ "text" @@ -303867,8 +304479,8 @@ ] }, "limit": { - "context": 1000000, - "output": 32768 + "context": 1048576, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -303884,25 +304496,27 @@ "visibility": "summary", "continuation": [ "thinking_blocks" + ], + "effort_options": [ + "high", + "max" ] } }, "attachment": false, - "open_weights": false, - "knowledge": "2024-04", - "release_date": "2025-07-28", - "last_updated": "2025-07-28", + "open_weights": true, + "release_date": "2026-06-13", + "last_updated": "2026-06-13", "type": "chat" }, { - "id": "qwen/qwen3.5-plus", - "name": "Qwen3.5 Plus", - "display_name": "Qwen3.5 Plus", + "id": "anthropic/claude-opus-4.7", + "name": "Claude Opus 4.7", + "display_name": "Claude Opus 4.7", "modalities": { "input": [ "text", - "image", - "video" + "image" ], "output": [ "text" @@ -303910,36 +304524,51 @@ }, "limit": { "context": 1000000, - "output": 65536 + "output": 128000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": false, + "mode": "effort", + "effort": "high", + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" + ], "interleaved": true, "summaries": true, - "visibility": "summary", + "visibility": "omitted", "continuation": [ "thinking_blocks" + ], + "notes": [ + "Claude Opus 4.7 and newer Opus models require thinking.type = \"adaptive\" to enable thinking explicitly.", + "Manual budget_tokens requests return 400 on Claude Opus 4.7 and newer adaptive-only Opus models.", + "task_budget is separate from thinking control and should not be treated as a thinking budget." ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-04", - "release_date": "2026-02-16", - "last_updated": "2026-02-16", + "knowledge": "2026-01-31", + "release_date": "2026-04-16", + "last_updated": "2026-04-16", "type": "chat" }, { - "id": "qwen/qwen-plus", - "name": "Qwen Plus", - "display_name": "Qwen Plus", + "id": "deepseek/deepseek-v4-pro", + "name": "DeepSeek V4 Pro", + "display_name": "DeepSeek V4 Pro", "modalities": { "input": [ "text" @@ -303949,8 +304578,8 @@ ] }, "limit": { - "context": 1000000, - "output": 32768 + "context": 1002000, + "output": 128000 }, "temperature": true, "tool_call": true, @@ -303961,38 +304590,47 @@ "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "high", + "effort_options": [ + "low", + "high", + "max" + ], "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ "thinking_blocks" + ], + "notes": [ + "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." ] } }, "attachment": false, - "open_weights": false, - "knowledge": "2024-04", - "release_date": "2024-01-25", - "last_updated": "2025-09-11", + "open_weights": true, + "knowledge": "2025-05", + "release_date": "2026-04-24", + "last_updated": "2026-04-24", "type": "chat" }, { - "id": "qwen/qwen3.6-plus", - "name": "Qwen3.6 Plus", - "display_name": "Qwen3.6 Plus", + "id": "deepseek/deepseek-v4-flash", + "name": "DeepSeek V4 Flash", + "display_name": "DeepSeek V4 Flash", "modalities": { "input": [ - "text", - "image", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 65536 + "context": 1048576, + "output": 128000 }, "temperature": true, "tool_call": true, @@ -304003,37 +304641,55 @@ "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "high", + "effort_options": [ + "low", + "high", + "max" + ], "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ "thinking_blocks" + ], + "notes": [ + "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-04", - "release_date": "2026-04-02", - "last_updated": "2026-04-02", + "attachment": false, + "open_weights": true, + "knowledge": "2025-05", + "release_date": "2026-04-24", + "last_updated": "2026-04-24", "type": "chat" - }, + } + ] + }, + "zenifra": { + "id": "zenifra", + "name": "Zenifra", + "display_name": "Zenifra", + "api": "https://ai.zenifra.com/v1", + "doc": "https://docs.zenifra.com", + "models": [ { - "id": "qwen/qwen3.5-397b-a17b", - "name": "Qwen3.5 397B-A17B", - "display_name": "Qwen3.5 397B-A17B", + "id": "alibaba/qwen3.6-35b-a3b", + "name": "Qwen3.6 35B-A3B", + "display_name": "Qwen3.6 35B-A3B", "modalities": { "input": [ - "text", - "image", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 256000, + "context": 262144, "output": 65536 }, "temperature": true, @@ -304053,57 +304709,37 @@ ] } }, - "attachment": true, - "open_weights": true, - "release_date": "2026-02-15", - "last_updated": "2026-02-15", - "type": "chat" - }, - { - "id": "qwen/qwen3-coder-plus", - "name": "Qwen3 Coder Plus", - "display_name": "Qwen3 Coder Plus", - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 1000000, - "output": 65536 - }, - "temperature": true, - "tool_call": true, - "reasoning": { - "supported": false - }, "attachment": false, - "open_weights": false, - "knowledge": "2025-04", - "release_date": "2025-07-23", - "last_updated": "2025-07-23", + "open_weights": true, + "release_date": "2026-04-17", + "last_updated": "2026-04-17", "type": "chat" - }, + } + ] + }, + "kosmik": { + "id": "kosmik", + "name": "Kosmik Compute", + "display_name": "Kosmik Compute", + "api": "https://api.koscompute.com/v1", + "doc": "https://api.koscompute.com/docs/", + "models": [ { - "id": "qwen/qwen3.7-plus", - "name": "Qwen3.7 Plus", - "display_name": "Qwen3.7 Plus", + "id": "qwen/qwen3.8-27b", + "name": "Qwen3.8 27B", + "display_name": "Qwen3.8 27B", "modalities": { "input": [ "text", - "image", - "video" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 64000 + "context": 262144, + "output": 32768 }, "temperature": true, "tool_call": true, @@ -304113,20 +304749,40 @@ }, "extra_capabilities": { "reasoning": { - "supported": true + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "effort_options": [ + "none", + "low", + "medium", + "xhigh" + ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2025-04", - "release_date": "2026-06-02", - "last_updated": "2026-06-02", + "open_weights": true, + "release_date": "2026-08-14", + "last_updated": "2026-08-14", "type": "chat" - }, + } + ] + }, + "scx-ai": { + "id": "scx-ai", + "name": "SCX.ai", + "display_name": "SCX.ai", + "api": "https://api.scx.ai/v1", + "doc": "https://platform.scx.ai/docs", + "models": [ { - "id": "qwen/qwen3-coder-flash", - "name": "Qwen3 Coder Flash", - "display_name": "Qwen3 Coder Flash", + "id": "GLM-5.2", + "name": "GLM-5.2", + "display_name": "GLM-5.2", "modalities": { "input": [ "text" @@ -304137,38 +304793,52 @@ }, "limit": { "context": 1000000, - "output": 65536 + "output": 131072 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "effort_options": [ + "none", + "high", + "max" + ] + } }, "attachment": false, - "open_weights": false, - "knowledge": "2025-04", - "release_date": "2025-07-28", - "last_updated": "2025-07-28", + "open_weights": true, + "release_date": "2026-06-13", + "last_updated": "2026-06-13", "type": "chat" }, { - "id": "meta/muse-spark-1.2-contributor", - "name": "Muse Spark 1.2 Contributor", - "display_name": "Muse Spark 1.2 Contributor", + "id": "Qwen3.8-Max", + "name": "Qwen3.8 Max", + "display_name": "Qwen3.8 Max", "modalities": { "input": [ "text", "image", - "video", - "pdf", - "audio" + "video" ], "output": [ "text" ] }, "limit": { - "context": 1048576, + "context": 1000000, "output": 131072 }, "temperature": true, @@ -304179,25 +304849,31 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ - "minimal", + "none", "low", "medium", - "high", "xhigh" ] } }, "attachment": true, "open_weights": false, - "release_date": "2026-08-05", - "last_updated": "2026-08-05", + "release_date": "2026-08-03", + "last_updated": "2026-08-03", "type": "chat" }, { - "id": "perplexity/sonar-deep-research", - "name": "Sonar Deep Research", - "display_name": "Sonar Deep Research", + "id": "gpt-oss-120b", + "name": "GPT OSS 120B", + "display_name": "GPT OSS 120B", "modalities": { "input": [ "text" @@ -304207,128 +304883,187 @@ ] }, "limit": { - "context": 128000, - "output": 32768 + "context": 131072, + "output": 131072 }, - "temperature": false, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { "supported": true, "default": true }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "effort_options": [ + "low", + "medium", + "high" + ] + } + }, "attachment": false, - "open_weights": false, - "knowledge": "2025-01", - "release_date": "2025-02-01", - "last_updated": "2025-09-01", + "open_weights": true, + "knowledge": "2024-06", + "release_date": "2025-08-05", + "last_updated": "2025-08-05", "type": "chat" - } - ] - }, - "stepfun-ai": { - "id": "stepfun-ai", - "name": "StepFun (Global)", - "display_name": "StepFun (Global)", - "api": "https://api.stepfun.ai/v1", - "doc": "https://platform.stepfun.ai/docs/en/overview/concept", - "models": [ + }, { - "id": "stepaudio-2.5-asr", - "name": "StepAudio 2.5 ASR", - "display_name": "StepAudio 2.5 ASR", + "id": "MiniMax-M2.7", + "name": "MiniMax-M2.7", + "display_name": "MiniMax-M2.7", "modalities": { "input": [ - "audio" + "text" ], "output": [ "text" ] }, "limit": { - "context": 8192, - "output": 8192 + "context": 196608, + "output": 196608 }, - "temperature": false, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": true, - "open_weights": false, - "release_date": "2026-04-24", - "last_updated": "2026-07-02" - }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "effort_options": [ + "low", + "medium", + "high" + ] + } + }, + "attachment": false, + "open_weights": true, + "release_date": "2026-03-18", + "last_updated": "2026-03-18", + "type": "chat" + } + ] + }, + "lynkr": { + "id": "lynkr", + "name": "Lynkr", + "display_name": "Lynkr", + "api": "http://127.0.0.1:8081/v1", + "doc": "https://github.com/Fast-Editor/Lynkr", + "models": [ { - "id": "step-tts-2", - "name": "Step TTS 2", - "display_name": "Step TTS 2", + "id": "lynkr-auto", + "name": "Lynkr Auto (complexity routing)", + "display_name": "Lynkr Auto (complexity routing)", "modalities": { "input": [ "text" ], "output": [ - "audio" + "text" ] }, "limit": { - "context": 8192, + "context": 128000, "output": 8192 }, - "temperature": false, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { "supported": false }, "attachment": false, "open_weights": false, - "release_date": "2026-03-01", - "last_updated": "2026-07-02" - }, + "release_date": "2025-12-03", + "last_updated": "2026-07-11", + "type": "chat" + } + ] + }, + "gitlab": { + "id": "gitlab", + "name": "GitLab Duo", + "display_name": "GitLab Duo", + "doc": "https://docs.gitlab.com/user/duo_agent_platform/", + "models": [ { - "id": "stepaudio-2.5-tts", - "name": "StepAudio 2.5 TTS", - "display_name": "StepAudio 2.5 TTS", + "id": "duo-chat-gpt-6-astra", + "name": "Agentic Chat (GPT-6 Astra)", + "display_name": "Agentic Chat (GPT-6 Astra)", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ - "audio" + "text" ] }, "limit": { - "context": 8192, - "output": 8192 + "context": 1050000, + "output": 128000 }, "temperature": false, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" + ] + } + }, + "attachment": true, "open_weights": false, - "release_date": "2026-04-16", - "last_updated": "2026-07-02" + "knowledge": "2026-04-30", + "release_date": "2026-09-04", + "last_updated": "2026-09-04", + "type": "chat" }, { - "id": "step-3.7-flash", - "name": "Step 3.7 Flash", - "display_name": "Step 3.7 Flash", + "id": "duo-chat-opus-5", + "name": "Agentic Chat (Claude Opus 5)", + "display_name": "Agentic Chat (Claude Opus 5)", "modalities": { "input": [ "text", "image", - "video" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 256000, - "output": 256000 + "context": 1000000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -304336,44 +305071,41 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], "effort_options": [ "low", "medium", - "high" + "high", + "xhigh", + "max" ] } }, "attachment": true, - "open_weights": true, - "knowledge": "2026-03-01", - "release_date": "2026-05-29", - "last_updated": "2026-05-29", + "open_weights": false, + "knowledge": "2026-05", + "release_date": "2026-07-24", + "last_updated": "2026-07-24", "type": "chat" }, { - "id": "step-3.5-flash-2603", - "name": "Step 3.5 Flash 2603", - "display_name": "Step 3.5 Flash 2603", + "id": "duo-chat-opus-4-8", + "name": "Agentic Chat (Claude Opus 4.8)", + "display_name": "Agentic Chat (Claude Opus 4.8)", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 256000, - "output": 256000 + "context": 1000000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -304381,43 +305113,38 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], "effort_options": [ "low", - "high" + "medium", + "high", + "xhigh", + "max" ] } }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-04-02", - "last_updated": "2026-04-02", + "attachment": true, + "open_weights": false, + "knowledge": "2026-01", + "release_date": "2026-05-28", + "last_updated": "2026-05-28", "type": "chat" }, { - "id": "step-5-preview", - "name": "Step 5 Preview", - "display_name": "Step 5 Preview", + "id": "duo-chat-gpt-5-4-nano", + "name": "Agentic Chat (GPT-5.4 Nano)", + "display_name": "Agentic Chat (GPT-5.4 Nano)", "modalities": { "input": [ "text", - "image", - "video" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 384000 + "context": 400000, + "output": 128000 }, "temperature": true, "tool_call": true, @@ -304427,43 +305154,38 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "mode": "effort", - "effort": "medium", "effort_options": [ + "none", "low", "medium", - "high" - ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "high", + "xhigh" ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-09-16", - "last_updated": "2026-09-20", + "open_weights": false, + "knowledge": "2025-08-31", + "release_date": "2026-03-17", + "last_updated": "2026-03-17", "type": "chat" }, { - "id": "step-3.5-flash", - "name": "Step 3.5 Flash", - "display_name": "Step 3.5 Flash", + "id": "duo-chat-gpt-5-4-mini", + "name": "Agentic Chat (GPT-5.4 Mini)", + "display_name": "Agentic Chat (GPT-5.4 Mini)", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 256000, - "output": 256000 + "context": 400000, + "output": 128000 }, "temperature": true, "tool_call": true, @@ -304473,43 +305195,31 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], "effort_options": [ + "none", "low", - "high" + "medium", + "high", + "xhigh" ] } }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-01-29", - "last_updated": "2026-02-13", + "attachment": true, + "open_weights": false, + "knowledge": "2025-08-31", + "release_date": "2026-03-17", + "last_updated": "2026-03-17", "type": "chat" - } - ] - }, - "cloudflare-ai-gateway": { - "id": "cloudflare-ai-gateway", - "name": "Cloudflare AI Gateway", - "display_name": "Cloudflare AI Gateway", - "doc": "https://developers.cloudflare.com/ai-gateway/", - "models": [ + }, { - "id": "minimax/m3.1-flash", - "name": "MiniMax-M3.1-Flash", - "display_name": "MiniMax-M3.1-Flash", + "id": "duo-chat-fable-5-1", + "name": "Agentic Chat (Claude Fable 5.1)", + "display_name": "Agentic Chat (Claude Fable 5.1)", "modalities": { "input": [ "text", "image", - "video" + "pdf" ], "output": [ "text" @@ -304517,9 +305227,9 @@ }, "limit": { "context": 1000000, - "output": 512000 + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -304538,54 +305248,70 @@ }, "attachment": true, "open_weights": false, - "release_date": "2026-10-19", - "last_updated": "2026-10-19", + "knowledge": "2026-06", + "release_date": "2026-09-01", + "last_updated": "2026-09-01", "type": "chat" }, { - "id": "unbiased/pareto", - "name": "Pareto", - "display_name": "Pareto", + "id": "duo-chat-gpt-5-3-codex", + "name": "Agentic Chat (GPT-5.3 Codex)", + "display_name": "Agentic Chat (GPT-5.3 Codex)", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 131072 + "context": 400000, + "output": 128000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "none", + "low", + "medium", + "high", + "xhigh" + ] + } }, "attachment": true, "open_weights": false, - "release_date": "2026-09-17", - "last_updated": "2026-09-17", + "knowledge": "2025-08-31", + "release_date": "2026-02-05", + "last_updated": "2026-02-05", "type": "chat" }, { - "id": "fireworks/ember-1", - "name": "Ember-1", - "display_name": "Ember-1", + "id": "duo-chat-opus-4-5", + "name": "Agentic Chat (Claude Opus 4.5)", + "display_name": "Agentic Chat (Claude Opus 4.5)", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1040000, - "output": 131072 + "context": 200000, + "output": 64000 }, "temperature": true, "tool_call": true, @@ -304595,44 +305321,39 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], "effort_options": [ - "none", "low", - "high", - "max" + "medium", + "high" ] } }, "attachment": true, "open_weights": false, - "release_date": "2026-09-22", - "last_updated": "2026-09-22", + "knowledge": "2025-05", + "release_date": "2025-11-24", + "last_updated": "2025-11-24", "type": "chat" }, { - "id": "alibaba/qwen3.7-max", - "name": "Qwen3.7 Max", - "display_name": "Qwen3.7 Max", + "id": "duo-chat-gpt-6-sol", + "name": "Agentic Chat (GPT-6 Sol)", + "display_name": "Agentic Chat (GPT-6 Sol)", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 1050000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -304640,63 +305361,71 @@ }, "extra_capabilities": { "reasoning": { - "supported": true + "effort_options": [ + "none", + "low", + "medium", + "high", + "xhigh", + "max" + ] } }, - "attachment": false, + "attachment": true, "open_weights": false, - "release_date": "2026-05-21", - "last_updated": "2026-05-21", + "knowledge": "2026-04-20", + "release_date": "2026-09-22", + "last_updated": "2026-09-22", "type": "chat" }, { - "id": "alibaba/qwen3-max", - "name": "Qwen3 Max", - "display_name": "Qwen3 Max", + "id": "duo-chat-gpt-5-1", + "name": "Agentic Chat (GPT-5.1)", + "display_name": "Agentic Chat (GPT-5.1)", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 65536 + "context": 400000, + "output": 128000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true + "supported": true, + "default": true }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "none", + "low", + "medium", + "high" ] } }, - "attachment": false, + "attachment": true, "open_weights": false, - "knowledge": "2025-04", - "release_date": "2025-09-23", - "last_updated": "2025-09-23", + "knowledge": "2024-09-30", + "release_date": "2025-11-13", + "last_updated": "2025-11-13", "type": "chat" }, { - "id": "alibaba/qwen3.8-max", - "name": "Qwen3.8 Max", - "display_name": "Qwen3.8 Max", + "id": "duo-chat-opus-5-5", + "name": "Agentic Chat (Claude Opus 5.5)", + "display_name": "Agentic Chat (Claude Opus 5.5)", "modalities": { "input": [ "text", "image", - "video", "pdf" ], "output": [ @@ -304705,9 +305434,9 @@ }, "limit": { "context": 1000000, - "output": 131072 + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -304715,45 +305444,41 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], "effort_options": [ "low", "medium", - "xhigh" + "high", + "xhigh", + "max" ] } }, "attachment": true, "open_weights": false, - "release_date": "2026-08-03", - "last_updated": "2026-08-03", + "knowledge": "2026-06", + "release_date": "2026-09-22", + "last_updated": "2026-09-22", "type": "chat" }, { - "id": "alibaba/qwen3.5-397b-a17b", - "name": "Qwen3.5 397B-A17B", - "display_name": "Qwen3.5 397B-A17B", + "id": "duo-chat-gpt-5-2-codex", + "name": "Agentic Chat (GPT-5.2 Codex)", + "display_name": "Agentic Chat (GPT-5.2 Codex)", "modalities": { "input": [ "text", "image", - "video" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 65536 + "context": 400000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -304761,30 +305486,30 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "low", + "medium", + "high", + "xhigh" ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-02-15", - "last_updated": "2026-02-15", + "open_weights": false, + "knowledge": "2025-08-31", + "release_date": "2025-12-11", + "last_updated": "2025-12-11", "type": "chat" }, { - "id": "alibaba/qwen3.7-plus", - "name": "Qwen3.7 Plus", - "display_name": "Qwen3.7 Plus", + "id": "duo-chat-fable-5", + "name": "Agentic Chat (Claude Fable 5)", + "display_name": "Agentic Chat (Claude Fable 5)", "modalities": { "input": [ "text", "image", - "video" + "pdf" ], "output": [ "text" @@ -304792,9 +305517,9 @@ }, "limit": { "context": 1000000, - "output": 64000 + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -304802,20 +305527,26 @@ }, "extra_capabilities": { "reasoning": { - "supported": true + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" + ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-04", - "release_date": "2026-06-02", - "last_updated": "2026-06-02", + "knowledge": "2026-01-31", + "release_date": "2026-06-09", + "last_updated": "2026-06-09", "type": "chat" }, { - "id": "xai/grok-4.20-0309-reasoning", - "name": "Grok 4.20 (Reasoning)", - "display_name": "Grok 4.20 (Reasoning)", + "id": "duo-chat-haiku-4-5", + "name": "Agentic Chat (Claude Haiku 4.5)", + "display_name": "Agentic Chat (Claude Haiku 4.5)", "modalities": { "input": [ "text", @@ -304827,8 +305558,8 @@ ] }, "limit": { - "context": 2000000, - "output": 30000 + "context": 200000, + "output": 64000 }, "temperature": true, "tool_call": true, @@ -304838,14 +305569,15 @@ }, "attachment": true, "open_weights": false, - "release_date": "2026-03-09", - "last_updated": "2026-03-09", + "knowledge": "2025-02-28", + "release_date": "2025-10-15", + "last_updated": "2025-10-15", "type": "chat" }, { - "id": "xai/grok-4.5", - "name": "Grok 4.5", - "display_name": "Grok 4.5", + "id": "duo-chat-gpt-5-2", + "name": "Agentic Chat (GPT-5.2)", + "display_name": "Agentic Chat (GPT-5.2)", "modalities": { "input": [ "text", @@ -304856,8 +305588,8 @@ ] }, "limit": { - "context": 500000, - "output": 500000 + "context": 400000, + "output": 128000 }, "temperature": true, "tool_call": true, @@ -304867,24 +305599,26 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, "effort_options": [ + "none", "low", "medium", - "high" + "high", + "xhigh" ] } }, "attachment": true, "open_weights": false, - "release_date": "2026-07-08", - "last_updated": "2026-07-08", + "knowledge": "2025-08-31", + "release_date": "2025-12-11", + "last_updated": "2025-12-11", "type": "chat" }, { - "id": "xai/grok-4.7", - "name": "Grok 4.7", - "display_name": "Grok 4.7", + "id": "duo-chat-sonnet-5", + "name": "Agentic Chat (Claude Sonnet 5)", + "display_name": "Agentic Chat (Claude Sonnet 5)", "modalities": { "input": [ "text", @@ -304896,10 +305630,10 @@ ] }, "limit": { - "context": 500000, - "output": 500000 + "context": 1000000, + "output": 64000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -304911,21 +305645,22 @@ "low", "medium", "high", - "xhigh" + "xhigh", + "max" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-05", - "release_date": "2026-09-21", - "last_updated": "2026-09-21", + "knowledge": "2026-01-31", + "release_date": "2026-06-30", + "last_updated": "2026-06-30", "type": "chat" }, { - "id": "xai/grok-4.3", - "name": "Grok 4.3", - "display_name": "Grok 4.3", + "id": "duo-chat-haiku-5-5", + "name": "Agentic Chat (Claude Haiku 5.5)", + "display_name": "Agentic Chat (Claude Haiku 5.5)", "modalities": { "input": [ "text", @@ -304938,9 +305673,9 @@ }, "limit": { "context": 1000000, - "output": 30000 + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -304948,68 +305683,81 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, "effort_options": [ - "none", "low", "medium", - "high" + "high", + "xhigh", + "max" ] } }, "attachment": true, "open_weights": false, - "release_date": "2026-04-17", - "last_updated": "2026-04-17", + "knowledge": "2026-06", + "release_date": "2026-10-07", + "last_updated": "2026-10-07", "type": "chat" }, { - "id": "xai/grok-4.20-0309-non-reasoning", - "name": "Grok 4.20 (Non-Reasoning)", - "display_name": "Grok 4.20 (Non-Reasoning)", + "id": "duo-chat-gpt-5-mini", + "name": "Agentic Chat (GPT-5 Mini)", + "display_name": "Agentic Chat (GPT-5 Mini)", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 2000000, - "output": 30000 + "context": 400000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "minimal", + "low", + "medium", + "high" + ] + } }, "attachment": true, "open_weights": false, - "release_date": "2026-03-09", - "last_updated": "2026-03-09", + "knowledge": "2024-05-30", + "release_date": "2025-08-07", + "last_updated": "2025-08-07", "type": "chat" }, { - "id": "xai/grok-4.6", - "name": "Grok 4.6", - "display_name": "Grok 4.6", + "id": "duo-chat-gpt-5-6-terra", + "name": "Agentic Chat (GPT-5.6 Terra)", + "display_name": "Agentic Chat (GPT-5.6 Terra)", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 500000, - "output": 500000 + "context": 1050000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -305017,25 +305765,27 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, "effort_options": [ + "none", "low", "medium", - "high" + "high", + "xhigh", + "max" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-02-01", - "release_date": "2026-08-12", - "last_updated": "2026-08-12", + "knowledge": "2026-02-16", + "release_date": "2026-07-09", + "last_updated": "2026-07-09", "type": "chat" }, { - "id": "anthropic/claude-fable-5.1", - "name": "Claude Fable 5.1", - "display_name": "Claude Fable 5.1", + "id": "duo-chat-sonnet-5-5", + "name": "Agentic Chat (Claude Sonnet 5.5)", + "display_name": "Agentic Chat (Claude Sonnet 5.5)", "modalities": { "input": [ "text", @@ -305048,7 +305798,7 @@ }, "limit": { "context": 1000000, - "output": 128000 + "output": 64000 }, "temperature": false, "tool_call": true, @@ -305058,41 +305808,26 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "high", "effort_options": [ "low", "medium", "high", "xhigh", "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "omitted", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Adaptive thinking is always on for Claude Fable 5 and Claude Mythos 5; thinking.type = \"disabled\" is rejected.", - "Manual budget_tokens requests return 400 on Claude Fable 5 and Claude Mythos 5.", - "thinking.display defaults to omitted; set display to summarized to receive readable thinking summaries." ] } }, "attachment": true, "open_weights": false, "knowledge": "2026-06", - "release_date": "2026-09-01", - "last_updated": "2026-09-01", + "release_date": "2026-09-28", + "last_updated": "2026-09-28", "type": "chat" }, { - "id": "anthropic/claude-opus-5", - "name": "Claude Opus 5", - "display_name": "Claude Opus 5", + "id": "duo-chat-gpt-5-6-luna", + "name": "Agentic Chat (GPT-5.6 Luna)", + "display_name": "Agentic Chat (GPT-5.6 Luna)", "modalities": { "input": [ "text", @@ -305104,7 +305839,7 @@ ] }, "limit": { - "context": 1000000, + "context": 1050000, "output": 128000 }, "temperature": false, @@ -305115,8 +305850,8 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, "effort_options": [ + "none", "low", "medium", "high", @@ -305127,15 +305862,15 @@ }, "attachment": true, "open_weights": false, - "knowledge": "2026-05", - "release_date": "2026-07-24", - "last_updated": "2026-07-24", + "knowledge": "2026-02-16", + "release_date": "2026-07-09", + "last_updated": "2026-07-09", "type": "chat" }, { - "id": "anthropic/claude-opus-4.8", - "name": "Claude Opus 4.8", - "display_name": "Claude Opus 4.8", + "id": "duo-chat-gpt-6-1-sol", + "name": "Agentic Chat (GPT-6.1 Sol)", + "display_name": "Agentic Chat (GPT-6.1 Sol)", "modalities": { "input": [ "text", @@ -305147,64 +305882,48 @@ ] }, "limit": { - "context": 1000000, + "context": 1050000, "output": 128000 }, "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "high", "effort_options": [ "low", "medium", "high", "xhigh", "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "omitted", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Claude Opus 4.7 and newer Opus models require thinking.type = \"adaptive\" to enable thinking explicitly.", - "Manual budget_tokens requests return 400 on Claude Opus 4.7 and newer adaptive-only Opus models.", - "task_budget is separate from thinking control and should not be treated as a thinking budget." ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-01", - "release_date": "2026-05-28", - "last_updated": "2026-05-28", + "knowledge": "2026-04-30", + "release_date": "2026-09-29", + "last_updated": "2026-09-29", "type": "chat" }, { - "id": "anthropic/claude-sonnet-5", - "name": "Claude Sonnet 5", - "display_name": "Claude Sonnet 5", + "id": "duo-chat-gpt-5-codex", + "name": "Agentic Chat (GPT-5 Codex)", + "display_name": "Agentic Chat (GPT-5 Codex)", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, + "context": 400000, "output": 128000 }, "temperature": false, @@ -305215,27 +305934,55 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, "effort_options": [ "low", "medium", - "high", - "xhigh", - "max" + "high" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-01-31", - "release_date": "2026-06-30", - "last_updated": "2026-06-30", + "knowledge": "2024-09-30", + "release_date": "2025-09-15", + "last_updated": "2025-09-15", "type": "chat" }, { - "id": "anthropic/claude-opus-4.6", - "name": "Claude Opus 4.6", - "display_name": "Claude Opus 4.6", + "id": "duo-chat-sonnet-4-5", + "name": "Agentic Chat (Claude Sonnet 4.5)", + "display_name": "Agentic Chat (Claude Sonnet 4.5)", + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 200000, + "output": 64000 + }, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": true, + "default": true + }, + "attachment": true, + "open_weights": false, + "knowledge": "2025-07-31", + "release_date": "2025-09-29", + "last_updated": "2025-09-29", + "type": "chat" + }, + { + "id": "duo-chat-opus-4-6", + "name": "Agentic Chat (Claude Opus 4.6)", + "display_name": "Agentic Chat (Claude Opus 4.6)", "modalities": { "input": [ "text", @@ -305254,33 +306001,15 @@ "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "mixed", - "budget": { - "min": 1024, - "unit": "tokens" - }, - "effort": "high", "effort_options": [ "low", "medium", "high", "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Anthropic recommends adaptive thinking with effort for Claude 4.6; budget_tokens remains a deprecated compatibility path.", - "Anthropic API defaults effort to high; lower effort levels should be chosen per workload." ] } }, @@ -305292,9 +306021,9 @@ "type": "chat" }, { - "id": "anthropic/claude-opus-4.5", - "name": "Claude Opus 4.5 (latest)", - "display_name": "Claude Opus 4.5 (latest)", + "id": "duo-chat-gpt-6-luna", + "name": "Agentic Chat (GPT-6 Luna)", + "display_name": "Agentic Chat (GPT-6 Luna)", "modalities": { "input": [ "text", @@ -305306,53 +306035,38 @@ ] }, "limit": { - "context": 200000, - "output": 64000 + "context": 1050000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "mixed", - "budget": { - "min": 1024, - "unit": "tokens" - }, - "effort": "high", "effort_options": [ + "none", "low", "medium", - "high" - ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Claude Opus 4.5 uses manual thinking.type = \"enabled\" with budget_tokens; effort can be used alongside the thinking budget.", - "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header." + "high", + "xhigh", + "max" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-05", - "release_date": "2025-11-24", - "last_updated": "2025-11-24", + "knowledge": "2026-05-18", + "release_date": "2026-09-22", + "last_updated": "2026-09-22", "type": "chat" }, { - "id": "anthropic/claude-sonnet-4.6", - "name": "Claude Sonnet 4.6", - "display_name": "Claude Sonnet 4.6", + "id": "duo-chat-sonnet-4-6", + "name": "Agentic Chat (Claude Sonnet 4.6)", + "display_name": "Agentic Chat (Claude Sonnet 4.6)", "modalities": { "input": [ "text", @@ -305365,39 +306079,21 @@ }, "limit": { "context": 1000000, - "output": 128000 + "output": 64000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "mixed", - "budget": { - "min": 1024, - "unit": "tokens" - }, - "effort": "high", "effort_options": [ "low", "medium", "high", "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Anthropic recommends adaptive thinking with effort for Claude 4.6; budget_tokens remains a deprecated compatibility path.", - "Anthropic API defaults effort to high; lower effort levels should be chosen per workload." ] } }, @@ -305409,9 +306105,9 @@ "type": "chat" }, { - "id": "anthropic/claude-haiku-4.5", - "name": "Claude Haiku 4.5 (latest)", - "display_name": "Claude Haiku 4.5 (latest)", + "id": "duo-chat-gpt-5-4", + "name": "Agentic Chat (GPT-5.4)", + "display_name": "Agentic Chat (GPT-5.4)", "modalities": { "input": [ "text", @@ -305423,47 +306119,37 @@ ] }, "limit": { - "context": 200000, - "output": 64000 + "context": 1050000, + "output": 128000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "budget", - "budget": { - "min": 1024, - "unit": "tokens" - }, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", - "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." + "effort_options": [ + "none", + "low", + "medium", + "high", + "xhigh" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-02-28", - "release_date": "2025-10-15", - "last_updated": "2025-10-15", + "knowledge": "2025-08-31", + "release_date": "2026-03-05", + "last_updated": "2026-03-05", "type": "chat" }, { - "id": "anthropic/claude-opus-4.7", - "name": "Claude Opus 4.7", - "display_name": "Claude Opus 4.7", + "id": "duo-chat-gpt-5-6-sol", + "name": "Agentic Chat (GPT-5.6 Sol)", + "display_name": "Agentic Chat (GPT-5.6 Sol)", "modalities": { "input": [ "text", @@ -305475,52 +306161,38 @@ ] }, "limit": { - "context": 1000000, + "context": 1050000, "output": 128000 }, "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "high", "effort_options": [ + "none", "low", "medium", "high", "xhigh", "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "omitted", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Claude Opus 4.7 and newer Opus models require thinking.type = \"adaptive\" to enable thinking explicitly.", - "Manual budget_tokens requests return 400 on Claude Opus 4.7 and newer adaptive-only Opus models.", - "task_budget is separate from thinking control and should not be treated as a thinking budget." ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-01-31", - "release_date": "2026-04-16", - "last_updated": "2026-04-16", + "knowledge": "2026-02-16", + "release_date": "2026-07-09", + "last_updated": "2026-07-09", "type": "chat" }, { - "id": "anthropic/claude-sonnet-5.5", - "name": "Claude Sonnet 5.5", - "display_name": "Claude Sonnet 5.5", + "id": "duo-chat-gpt-5-5", + "name": "Agentic Chat (GPT-5.5)", + "display_name": "Agentic Chat (GPT-5.5)", "modalities": { "input": [ "text", @@ -305532,7 +306204,7 @@ ] }, "limit": { - "context": 1000000, + "context": 1050000, "output": 128000 }, "temperature": false, @@ -305544,25 +306216,25 @@ "extra_capabilities": { "reasoning": { "effort_options": [ + "none", "low", "medium", "high", - "xhigh", - "max" + "xhigh" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-06", - "release_date": "2026-09-28", - "last_updated": "2026-09-28", + "knowledge": "2025-12-01", + "release_date": "2026-04-23", + "last_updated": "2026-04-23", "type": "chat" }, { - "id": "anthropic/claude-fable-5", - "name": "Claude Fable 5", - "display_name": "Claude Fable 5", + "id": "duo-chat-opus-4-7", + "name": "Agentic Chat (Claude Opus 4.7)", + "display_name": "Agentic Chat (Claude Opus 4.7)", "modalities": { "input": [ "text", @@ -305585,98 +306257,82 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "high", "effort_options": [ "low", "medium", "high", "xhigh", "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "omitted", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Adaptive thinking is always on for Claude Fable 5 and Claude Mythos 5; thinking.type = \"disabled\" is rejected.", - "Manual budget_tokens requests return 400 on Claude Fable 5 and Claude Mythos 5.", - "thinking.display defaults to omitted; set display to summarized to receive readable thinking summaries." ] } }, "attachment": true, "open_weights": false, "knowledge": "2026-01-31", - "release_date": "2026-06-09", - "last_updated": "2026-06-09", + "release_date": "2026-04-16", + "last_updated": "2026-04-16", "type": "chat" - }, + } + ] + }, + "bee": { + "id": "bee", + "name": "Bee by HEOSSI", + "display_name": "Bee by HEOSSI", + "api": "https://api.bee.heossi.com/bee", + "doc": "https://bee.heossi.com/docs/sdks", + "models": [ { - "id": "anthropic/claude-sonnet-4.5", - "name": "Claude Sonnet 4.5 (latest)", - "display_name": "Claude Sonnet 4.5 (latest)", + "id": "bee-brood", + "name": "Bee Brood 1.0", + "display_name": "Bee Brood 1.0", "modalities": { "input": [ "text", "image", - "pdf" + "video", + "audio" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 64000 + "context": 262144, + "output": 16384 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "budget", - "budget": { - "min": 1024, - "unit": "tokens" - }, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", - "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-07-31", - "release_date": "2025-09-29", - "last_updated": "2025-09-29", + "release_date": "2026-07-10", + "last_updated": "2026-09-28", "type": "chat" }, { - "id": "anthropic/claude-haiku-5.5", - "name": "Claude Haiku 5.5", - "display_name": "Claude Haiku 5.5", + "id": "bee-swarm", + "name": "Bee Swarm 1.0", + "display_name": "Bee Swarm 1.0", "modalities": { "input": [ "text", "image", - "pdf" + "audio" ], "output": [ "text" @@ -305684,10 +306340,9 @@ }, "limit": { "context": 1000000, - "output": 128000 + "output": 64000 }, - "temperature": false, - "tool_call": true, + "tool_call": false, "reasoning": { "supported": true, "default": true @@ -305705,68 +306360,57 @@ }, "attachment": true, "open_weights": false, - "knowledge": "2026-06", - "release_date": "2026-10-07", - "last_updated": "2026-10-07", + "release_date": "2026-07-10", + "last_updated": "2026-09-28", "type": "chat" }, { - "id": "anthropic/claude-opus-5.5", - "name": "Claude Opus 5.5", - "display_name": "Claude Opus 5.5", + "id": "bee-cell", + "name": "Bee Cell 1.0", + "display_name": "Bee Cell 1.0", "modalities": { "input": [ "text", "image", - "pdf" + "audio" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 131072, + "output": 8192 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" - ] - } + "supported": false }, "attachment": true, "open_weights": false, - "knowledge": "2026-06", - "release_date": "2026-09-22", - "last_updated": "2026-09-22", + "release_date": "2026-07-10", + "last_updated": "2026-09-28", "type": "chat" }, { - "id": "deepseek/deepseek-v4-pro", - "name": "DeepSeek V4 Pro", - "display_name": "DeepSeek V4 Pro", + "id": "bee-hive", + "name": "Bee Hive 1.0", + "display_name": "Bee Hive 1.0", "modalities": { "input": [ - "text" + "text", + "image", + "video", + "audio" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 384000 + "context": 262144, + "output": 32768 }, "temperature": true, "tool_call": true, @@ -305776,52 +306420,39 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "high", "effort_options": [ "low", + "medium", "high", + "xhigh", "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." ] } }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-04-24", - "last_updated": "2026-04-24", + "attachment": true, + "open_weights": false, + "release_date": "2026-07-10", + "last_updated": "2026-09-28", "type": "chat" }, { - "id": "google/gemini-2.5-pro", - "name": "Gemini 2.5 Pro", - "display_name": "Gemini 2.5 Pro", + "id": "bee-buzz", + "name": "Bee Buzz 1.0", + "display_name": "Bee Buzz 1.0", "modalities": { "input": [ "text", "image", - "audio", "video", - "pdf" + "audio" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 65536 + "context": 262144, + "output": 24576 }, "temperature": true, "tool_call": true, @@ -305831,49 +306462,39 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "budget", - "budget": { - "default": -1, - "min": 128, - "max": 32768, - "auto": -1, - "unit": "tokens" - }, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thought_signatures" + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-01", - "release_date": "2025-06-17", - "last_updated": "2025-06-17", + "release_date": "2026-07-10", + "last_updated": "2026-09-28", "type": "chat" }, { - "id": "google/gemini-3.5-flash-lite", - "name": "Gemini 3.5 Flash Lite", - "display_name": "Gemini 3.5 Flash Lite", + "id": "bee-comb", + "name": "Bee Comb 2.0", + "display_name": "Bee Comb 2.0", "modalities": { "input": [ "text", "image", "video", - "audio", - "pdf" + "audio" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 262144, + "output": 16384 }, "temperature": true, "tool_call": true, @@ -305883,182 +306504,175 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, "effort_options": [ - "minimal", "low", "medium", - "high" + "high", + "xhigh", + "max" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-03", - "release_date": "2026-07-21", - "last_updated": "2026-07-21", + "release_date": "2026-07-10", + "last_updated": "2026-09-28", "type": "chat" - }, + } + ] + }, + "frogbot": { + "id": "frogbot", + "name": "FrogBot", + "display_name": "FrogBot", + "api": "https://app.frogbot.ai/api/v1", + "doc": "https://docs.frogbot.ai", + "models": [ { - "id": "google/gemini-2.5-flash", - "name": "Gemini 2.5 Flash", - "display_name": "Gemini 2.5 Flash", + "id": "zai-glm-5-1", + "name": "Z.AI GLM-5.1", + "display_name": "Z.AI GLM-5.1", "modalities": { "input": [ - "text", - "image", - "audio", - "video", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 65536 + "context": 198000, + "output": 8192 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "budget", - "budget": { - "default": -1, - "min": 0, - "max": 24576, - "auto": -1, - "off": 0, - "unit": "tokens" - }, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thought_signatures" - ] - } + "supported": false }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-01", - "release_date": "2025-06-17", - "last_updated": "2025-06-17", + "attachment": false, + "open_weights": true, + "knowledge": "2024-10", + "release_date": "2026-04-07", + "last_updated": "2026-04-07", "type": "chat" }, { - "id": "google/gemini-3.1-flash-lite", - "name": "Gemini 3.1 Flash Lite", - "display_name": "Gemini 3.1 Flash Lite", + "id": "minimax-m2-5", + "name": "MiniMax-M2.5", + "display_name": "MiniMax-M2.5", "modalities": { "input": [ - "text", - "image", - "video", - "audio", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 65536 + "context": 192000, + "output": 8192 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true + "supported": false }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "effort_options": [ - "minimal", - "low", - "medium", - "high" - ] - } + "attachment": false, + "open_weights": true, + "knowledge": "2024-09", + "release_date": "2026-02-12", + "last_updated": "2026-02-12", + "type": "chat" + }, + { + "id": "minimax-m2-7", + "name": "MiniMax-M2.7", + "display_name": "MiniMax-M2.7", + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-01", - "release_date": "2026-05-07", - "last_updated": "2026-05-07", + "limit": { + "context": 192000, + "output": 8192 + }, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": false + }, + "attachment": false, + "open_weights": true, + "knowledge": "2024-09", + "release_date": "2026-03-18", + "last_updated": "2026-03-18", "type": "chat" }, { - "id": "google/gemini-3.7-flash", - "name": "Gemini 3.7 Flash", - "display_name": "Gemini 3.7 Flash", + "id": "gpt-5-4-mini", + "name": "GPT-5.4 Mini", + "display_name": "GPT-5.4 Mini", "modalities": { "input": [ "text", - "image", - "video", - "audio", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 400000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "level", - "level": "high", - "level_options": [ - "minimal", + "default_enabled": false, + "mode": "effort", + "effort": "none", + "effort_options": [ + "none", + "low", + "medium", + "high", + "xhigh" + ], + "verbosity": "medium", + "verbosity_options": [ "low", "medium", "high" ], - "summaries": true, - "visibility": "summary", - "continuation": [ - "thought_signatures" - ] + "visibility": "hidden" } }, "attachment": true, "open_weights": false, - "knowledge": "2026-03", - "release_date": "2026-08-13", - "last_updated": "2026-08-13", + "knowledge": "2025-08-31", + "release_date": "2026-03-17", + "last_updated": "2026-03-17", "type": "chat" }, { - "id": "google/gemini-3.1-pro", - "name": "Gemini 3.1 Pro Preview", - "display_name": "Gemini 3.1 Pro Preview", + "id": "grok-4-3", + "name": "Grok 4.3", + "display_name": "Grok 4.3", "modalities": { "input": [ "text", "image", - "video", - "audio", - "pdf" + "video" ], "output": [ "text" @@ -306066,7 +306680,7 @@ }, "limit": { "context": 1000000, - "output": 65536 + "output": 128000 }, "temperature": true, "tool_call": true, @@ -306076,38 +306690,28 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "level", - "level": "high", - "level_options": [ + "effort_options": [ "low", + "medium", "high" - ], - "summaries": true, - "visibility": "summary", - "continuation": [ - "thought_signatures" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-01", - "release_date": "2026-02-19", - "last_updated": "2026-02-19", + "knowledge": "2024-11", + "release_date": "2026-04-17", + "last_updated": "2026-04-17", "type": "chat" }, { - "id": "google/gemini-3-flash", - "name": "Gemini 3 Flash Preview", - "display_name": "Gemini 3 Flash Preview", + "id": "claude-opus-4-7", + "name": "Claude Opus 4.7", + "display_name": "Claude Opus 4.7", "modalities": { "input": [ "text", "image", - "video", - "audio", "pdf" ], "output": [ @@ -306115,51 +306719,56 @@ ] }, "limit": { - "context": 1000000, - "output": 65536 + "context": 200000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "level", - "level": "high", - "level_options": [ - "minimal", + "default_enabled": false, + "mode": "effort", + "effort": "high", + "effort_options": [ "low", "medium", - "high" + "high", + "xhigh", + "max" ], + "interleaved": true, "summaries": true, - "visibility": "summary", + "visibility": "omitted", "continuation": [ - "thought_signatures" + "thinking_blocks" + ], + "notes": [ + "Claude Opus 4.7 and newer Opus models require thinking.type = \"adaptive\" to enable thinking explicitly.", + "Manual budget_tokens requests return 400 on Claude Opus 4.7 and newer adaptive-only Opus models.", + "task_budget is separate from thinking control and should not be treated as a thinking budget." ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-01", - "release_date": "2025-12-17", - "last_updated": "2025-12-17", + "knowledge": "2026-01-31", + "release_date": "2026-04-16", + "last_updated": "2026-04-16", "type": "chat" }, { - "id": "google/gemini-2.5-flash-lite", - "name": "Gemini 2.5 Flash-Lite", - "display_name": "Gemini 2.5 Flash-Lite", + "id": "claude-haiku-4-5", + "name": "Claude Haiku 4.5", + "display_name": "Claude Haiku 4.5", "modalities": { "input": [ "text", "image", - "audio", - "video", "pdf" ], "output": [ @@ -306167,8 +306776,8 @@ ] }, "limit": { - "context": 1000000, - "output": 65536 + "context": 200000, + "output": 64000 }, "temperature": true, "tool_call": true, @@ -306182,36 +306791,36 @@ "default_enabled": false, "mode": "budget", "budget": { - "default": -1, - "min": 512, - "max": 24576, - "auto": -1, + "min": 1024, "unit": "tokens" }, + "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ - "thought_signatures" + "thinking_blocks" + ], + "notes": [ + "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", + "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-01", - "release_date": "2025-06-17", - "last_updated": "2025-06-17", + "knowledge": "2025-02-28", + "release_date": "2025-10-15", + "last_updated": "2025-10-15", "type": "chat" }, { - "id": "google/gemini-3.8-flash", - "name": "Gemini 3.8 Flash", - "display_name": "Gemini 3.8 Flash", + "id": "qwen-3-6-plus", + "name": "Qwen 3.6 Plus", + "display_name": "Qwen 3.6 Plus", "modalities": { "input": [ "text", "image", - "video", - "audio", "pdf" ], "output": [ @@ -306219,8 +306828,8 @@ ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 1000000, + "output": 64000 }, "temperature": true, "tool_call": true, @@ -306228,144 +306837,110 @@ "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "level", - "level": "high", - "level_options": [ - "minimal", - "low", - "medium", - "high" - ], - "summaries": true, - "visibility": "summary", - "continuation": [ - "thought_signatures" - ] - } - }, "attachment": true, "open_weights": false, - "release_date": "2026-09-02", - "last_updated": "2026-09-02", + "knowledge": "2025-04", + "release_date": "2026-04-02", + "last_updated": "2026-04-02", "type": "chat" }, { - "id": "google/gemini-4-argon", - "name": "Gemini 4 Argon", - "display_name": "Gemini 4 Argon", + "id": "kimi-k2-6", + "name": "Kimi-K2.6", + "display_name": "Kimi-K2.6", "modalities": { "input": [ "text", - "image", - "video", - "audio", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 256000, + "output": 128000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "low", - "medium", - "high" - ] - } - }, "attachment": true, - "open_weights": false, - "knowledge": "2026-03", - "release_date": "2026-10-13", - "last_updated": "2026-10-13", + "open_weights": true, + "knowledge": "2025-01", + "release_date": "2026-04-20", + "last_updated": "2026-04-20", "type": "chat" }, { - "id": "google/gemini-3.6-flash", - "name": "Gemini 3.6 Flash", - "display_name": "Gemini 3.6 Flash", + "id": "gpt-5-4-nano", + "name": "GPT-5.4 Nano", + "display_name": "GPT-5.4 Nano", "modalities": { "input": [ "text", - "image", - "video", - "audio", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 400000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "level", - "level": "high", - "level_options": [ - "minimal", + "default_enabled": false, + "mode": "effort", + "effort": "none", + "effort_options": [ + "none", + "low", + "medium", + "high", + "xhigh" + ], + "verbosity": "medium", + "verbosity_options": [ "low", "medium", "high" ], - "summaries": true, - "visibility": "summary", - "continuation": [ - "thought_signatures" - ] + "visibility": "hidden" } }, "attachment": true, "open_weights": false, - "knowledge": "2026-03", - "release_date": "2026-07-21", - "last_updated": "2026-07-21", + "knowledge": "2024-05-30", + "release_date": "2026-03-17", + "last_updated": "2026-03-17", "type": "chat" }, { - "id": "google/gemini-3.5-flash", - "name": "Gemini 3.5 Flash", - "display_name": "Gemini 3.5 Flash", + "id": "grok-code-fast-1", + "name": "Grok 4.1 Fast (Reasoning)", + "display_name": "Grok 4.1 Fast (Reasoning)", "modalities": { "input": [ - "text", - "image", - "video", - "audio", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 256000, + "output": 128000 }, "temperature": true, "tool_call": true, @@ -306375,34 +306950,20 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "level", - "level": "high", - "level_options": [ - "minimal", - "low", - "medium", - "high" - ], - "summaries": true, - "visibility": "summary", - "continuation": [ - "thought_signatures" - ] + "supported": true } }, - "attachment": true, + "attachment": false, "open_weights": false, - "knowledge": "2025-01", - "release_date": "2026-05-19", - "last_updated": "2026-05-19", + "knowledge": "2023-10", + "release_date": "2025-09-02", + "last_updated": "2025-09-02", "type": "chat" }, { - "id": "openai/gpt-5", - "name": "GPT-5", - "display_name": "GPT-5", + "id": "gpt-5-5", + "name": "GPT-5.5", + "display_name": "GPT-5.5", "modalities": { "input": [ "text", @@ -306413,7 +306974,7 @@ ] }, "limit": { - "context": 128000, + "context": 272000, "output": 128000 }, "temperature": false, @@ -306429,10 +306990,10 @@ "mode": "effort", "effort": "medium", "effort_options": [ - "minimal", "low", "medium", - "high" + "high", + "xhigh" ], "verbosity": "medium", "verbosity_options": [ @@ -306445,19 +307006,21 @@ }, "attachment": true, "open_weights": false, - "knowledge": "2024-09-30", - "release_date": "2025-08-07", - "last_updated": "2025-08-07", + "knowledge": "2025-08-31", + "release_date": "2026-04-23", + "last_updated": "2026-04-23", "type": "chat" }, { - "id": "openai/gpt-4o-mini", - "name": "GPT-4o mini", - "display_name": "GPT-4o mini", + "id": "gemini-2.5-pro", + "name": "Gemini 2.5 Pro", + "display_name": "Gemini 2.5 Pro", "modalities": { "input": [ "text", "image", + "audio", + "video", "pdf" ], "output": [ @@ -306465,40 +307028,58 @@ ] }, "limit": { - "context": 128000, - "output": 16384 + "context": 1048576, + "output": 65536 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "budget", + "budget": { + "default": -1, + "min": 128, + "max": 32768, + "auto": -1, + "unit": "tokens" + }, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thought_signatures" + ] + } }, "attachment": true, "open_weights": false, - "knowledge": "2023-09", - "release_date": "2024-07-18", - "last_updated": "2024-07-18", + "knowledge": "2025-01", + "release_date": "2025-06-17", + "last_updated": "2025-06-17", "type": "chat" }, { - "id": "openai/gpt-5.5", - "name": "GPT-5.5", - "display_name": "GPT-5.5", + "id": "gpt-oss-120b", + "name": "GPT OSS 120B", + "display_name": "GPT OSS 120B", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 131072, + "output": 32768 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -306507,35 +307088,24 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", "effort_options": [ - "low", - "medium", - "high", - "xhigh" - ], - "verbosity": "medium", - "verbosity_options": [ "low", "medium", "high" - ], - "visibility": "hidden" + ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-12-01", - "release_date": "2026-04-23", - "last_updated": "2026-04-23", + "attachment": false, + "open_weights": true, + "knowledge": "2024-06", + "release_date": "2025-08-05", + "last_updated": "2025-08-05", "type": "chat" }, { - "id": "openai/gpt-4.1-nano", - "name": "GPT-4.1 nano", - "display_name": "GPT-4.1 nano", + "id": "gpt-4o", + "name": "GPT-4o", + "display_name": "GPT-4o", "modalities": { "input": [ "text", @@ -306546,8 +307116,8 @@ ] }, "limit": { - "context": 1000000, - "output": 32768 + "context": 128000, + "output": 16384 }, "temperature": true, "tool_call": true, @@ -306556,30 +307126,28 @@ }, "attachment": true, "open_weights": false, - "knowledge": "2024-04", - "release_date": "2025-04-14", - "last_updated": "2025-04-14", + "knowledge": "2023-09", + "release_date": "2024-05-13", + "last_updated": "2024-08-06", "type": "chat" }, { - "id": "openai/gpt-6-astra", - "name": "GPT-6 Astra", - "display_name": "GPT-6 Astra", + "id": "deepseek-v4-pro", + "name": "DeepSeek v4 Pro", + "display_name": "DeepSeek v4 Pro", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 128000, + "output": 8192 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -306588,24 +307156,36 @@ "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "high", "effort_options": [ "low", - "medium", - "high" + "high", + "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-04-30", - "release_date": "2026-09-04", - "last_updated": "2026-09-04", + "attachment": false, + "open_weights": true, + "knowledge": "2026-01", + "release_date": "2026-04-24", + "last_updated": "2026-04-24", "type": "chat" }, { - "id": "openai/gpt-5.4", - "name": "GPT-5.4", - "display_name": "GPT-5.4", + "id": "claude-sonnet-4-6", + "name": "Claude Sonnet 4.6", + "display_name": "Claude Sonnet 4.6", "modalities": { "input": [ "text", @@ -306617,8 +307197,8 @@ ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 200000, + "output": 64000 }, "temperature": true, "tool_call": true, @@ -306630,39 +307210,47 @@ "reasoning": { "supported": true, "default_enabled": false, - "mode": "effort", - "effort": "none", + "mode": "mixed", + "budget": { + "min": 1024, + "unit": "tokens" + }, + "effort": "high", "effort_options": [ - "none", "low", "medium", "high", - "xhigh" + "max" ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ], - "visibility": "hidden" + "notes": [ + "Anthropic recommends adaptive thinking with effort for Claude 4.6; budget_tokens remains a deprecated compatibility path.", + "Anthropic API defaults effort to high; lower effort levels should be chosen per workload." + ] } }, "attachment": true, "open_weights": false, "knowledge": "2025-08-31", - "release_date": "2026-03-05", - "last_updated": "2026-03-05", + "release_date": "2026-02-17", + "last_updated": "2026-03-13", "type": "chat" }, { - "id": "openai/gpt-4o", - "name": "GPT-4o", - "display_name": "GPT-4o", + "id": "gemini-2.5-flash", + "name": "Gemini 2.5 Flash", + "display_name": "Gemini 2.5 Flash", "modalities": { "input": [ "text", "image", + "audio", + "video", "pdf" ], "output": [ @@ -306670,41 +307258,11 @@ ] }, "limit": { - "context": 128000, - "output": 16384 + "context": 1048576, + "output": 65536 }, "temperature": true, "tool_call": true, - "reasoning": { - "supported": false - }, - "attachment": true, - "open_weights": false, - "knowledge": "2023-09", - "release_date": "2024-05-13", - "last_updated": "2024-08-06", - "type": "chat" - }, - { - "id": "openai/o3", - "name": "o3", - "display_name": "o3", - "modalities": { - "input": [ - "text", - "image", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 200000, - "output": 100000 - }, - "temperature": false, - "tool_call": true, "reasoning": { "supported": true, "default": true @@ -306713,42 +307271,47 @@ "reasoning": { "supported": true, "default_enabled": true, - "mode": "effort", - "effort": "medium", - "effort_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + "mode": "budget", + "budget": { + "default": -1, + "min": 0, + "max": 24576, + "auto": -1, + "off": 0, + "unit": "tokens" + }, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thought_signatures" + ] } }, "attachment": true, "open_weights": false, - "knowledge": "2024-05", - "release_date": "2025-04-16", - "last_updated": "2025-04-16", + "knowledge": "2025-01", + "release_date": "2025-06-17", + "last_updated": "2025-06-17", "type": "chat" }, { - "id": "openai/gpt-6-sol", - "name": "GPT-6 Sol", - "display_name": "GPT-6 Sol", + "id": "grok-4-1-fast-reasoning", + "name": "Grok 4.1 Fast (Reasoning)", + "display_name": "Grok 4.1 Fast (Reasoning)", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1050000, + "context": 2000000, "output": 128000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -306756,31 +307319,25 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "none", - "low", - "medium", - "high", - "xhigh", - "max" - ] + "supported": true } }, "attachment": true, "open_weights": false, - "knowledge": "2026-04-20", - "release_date": "2026-09-22", - "last_updated": "2026-09-22", + "knowledge": "2025-11", + "release_date": "2025-11-19", + "last_updated": "2025-11-19", "type": "chat" }, { - "id": "openai/o4-mini", - "name": "o4-mini", - "display_name": "o4-mini", + "id": "claude-opus-4-6", + "name": "Claude Opus 4.6", + "display_name": "Claude Opus 4.6", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" @@ -306788,97 +307345,88 @@ }, "limit": { "context": 200000, - "output": 100000 + "output": 128000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", + "default_enabled": false, + "mode": "mixed", + "budget": { + "min": 1024, + "unit": "tokens" + }, + "effort": "high", "effort_options": [ "low", "medium", - "high" + "high", + "max" ], - "visibility": "hidden" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Anthropic recommends adaptive thinking with effort for Claude 4.6; budget_tokens remains a deprecated compatibility path.", + "Anthropic API defaults effort to high; lower effort levels should be chosen per workload." + ] } }, "attachment": true, "open_weights": false, - "knowledge": "2024-05", - "release_date": "2025-04-16", - "last_updated": "2025-04-16", + "knowledge": "2025-05-31", + "release_date": "2026-02-05", + "last_updated": "2026-03-13", "type": "chat" }, { - "id": "openai/gpt-5.6-sol", - "name": "GPT-5.6 Sol", - "display_name": "GPT-5.6 Sol", + "id": "grok-4-1-fast-non-reasoning", + "name": "Grok 4.1 Fast (Non-Reasoning)", + "display_name": "Grok 4.1 Fast (Non-Reasoning)", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1050000, + "context": 2000000, "output": 128000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", - "effort_options": [ - "none", - "low", - "medium", - "high", - "xhigh", - "max" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" - } + "supported": false }, "attachment": true, "open_weights": false, - "knowledge": "2026-02-16", - "release_date": "2026-07-09", - "last_updated": "2026-07-09", + "knowledge": "2025-11", + "release_date": "2025-11-19", + "last_updated": "2025-11-19", "type": "chat" }, { - "id": "openai/gpt-5.6-terra", - "name": "GPT-5.6 Terra", - "display_name": "GPT-5.6 Terra", + "id": "gemini-3-1-pro-preview", + "name": "Gemini 3.1 Pro Preview", + "display_name": "Gemini 3.1 Pro Preview", "modalities": { "input": [ "text", "image", + "video", + "audio", "pdf" ], "output": [ @@ -306886,10 +307434,10 @@ ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 1000000, + "output": 64000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -306897,42 +307445,30 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", "effort_options": [ - "none", - "low", - "medium", - "high", - "xhigh", - "max" - ], - "verbosity": "medium", - "verbosity_options": [ "low", "medium", "high" - ], - "visibility": "hidden" + ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-02-16", - "release_date": "2026-07-09", - "last_updated": "2026-07-09", + "knowledge": "2026-01", + "release_date": "2026-02-19", + "last_updated": "2026-02-19", "type": "chat" }, { - "id": "openai/gpt-6-luna", - "name": "GPT-6 Luna", - "display_name": "GPT-6 Luna", + "id": "gemini-3-flash-preview", + "name": "Gemini 3 Flash Preview", + "display_name": "Gemini 3 Flash Preview", "modalities": { "input": [ "text", "image", + "video", + "audio", "pdf" ], "output": [ @@ -306940,10 +307476,10 @@ ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 1048576, + "output": 65536 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -306951,27 +307487,34 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "none", + "supported": true, + "default_enabled": true, + "mode": "level", + "level": "high", + "level_options": [ + "minimal", "low", "medium", - "high", - "xhigh", - "max" + "high" + ], + "summaries": true, + "visibility": "summary", + "continuation": [ + "thought_signatures" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-05-18", - "release_date": "2026-09-22", - "last_updated": "2026-09-22", + "knowledge": "2025-01", + "release_date": "2025-12-17", + "last_updated": "2025-12-17", "type": "chat" }, { - "id": "openai/gpt-5-mini", - "name": "GPT-5 Mini", - "display_name": "GPT-5 Mini", + "id": "gpt-5-3-codex", + "name": "GPT-5.3 Codex", + "display_name": "GPT-5.3 Codex", "modalities": { "input": [ "text", @@ -306982,7 +307525,7 @@ ] }, "limit": { - "context": 128000, + "context": 400000, "output": 128000 }, "temperature": false, @@ -306998,10 +307541,10 @@ "mode": "effort", "effort": "medium", "effort_options": [ - "minimal", "low", "medium", - "high" + "high", + "xhigh" ], "verbosity": "medium", "verbosity_options": [ @@ -307014,15 +307557,15 @@ }, "attachment": true, "open_weights": false, - "knowledge": "2024-05-30", - "release_date": "2025-08-07", - "last_updated": "2025-08-07", + "knowledge": "2026-01-31", + "release_date": "2026-02-05", + "last_updated": "2026-02-05", "type": "chat" }, { - "id": "openai/gpt-5.4-pro", - "name": "GPT-5.4 Pro", - "display_name": "GPT-5.4 Pro", + "id": "kimi-k2.5", + "name": "Kimi-K2.5", + "display_name": "Kimi-K2.5", "modalities": { "input": [ "text", @@ -307033,10 +307576,10 @@ ] }, "limit": { - "context": 1000000, + "context": 256000, "output": 128000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -307045,34 +307588,25 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "high", - "effort_options": [ - "medium", - "high", - "xhigh" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-03-05", - "last_updated": "2026-03-05", + "open_weights": true, + "knowledge": "2025-01", + "release_date": "2026-01-27", + "last_updated": "2026-01-27", "type": "chat" }, { - "id": "openai/o3-mini", - "name": "o3-mini", - "display_name": "o3-mini", + "id": "gpt-oss-20b", + "name": "GPT OSS 20B", + "display_name": "GPT OSS 20B", "modalities": { "input": [ "text" @@ -307082,10 +307616,10 @@ ] }, "limit": { - "context": 200000, - "output": 100000 + "context": 131072, + "output": 32768 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -307094,26 +307628,31 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", "effort_options": [ "low", "medium", "high" - ], - "visibility": "hidden" + ] } }, "attachment": false, - "open_weights": false, - "knowledge": "2024-05", - "release_date": "2024-12-20", - "last_updated": "2025-01-29", + "open_weights": true, + "knowledge": "2024-06", + "release_date": "2025-08-05", + "last_updated": "2025-08-05", "type": "chat" - }, + } + ] + }, + "snowflake-cortex": { + "id": "snowflake-cortex", + "name": "Snowflake Cortex", + "display_name": "Snowflake Cortex", + "api": "https://${SNOWFLAKE_ACCOUNT}.snowflakecomputing.com/api/v2/cortex/v1", + "doc": "https://docs.snowflake.com/en/user-guide/snowflake-cortex/cortex-rest-api", + "models": [ { - "id": "openai/gpt-5.1", + "id": "openai-gpt-5.1", "name": "GPT-5.1", "display_name": "GPT-5.1", "modalities": { @@ -307126,34 +307665,24 @@ ] }, "limit": { - "context": 128000, - "output": 128000 + "context": 272000, + "output": 8192 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "none", "effort_options": [ "none", + "minimal", "low", "medium", "high" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + ] } }, "attachment": true, @@ -307164,63 +307693,37 @@ "type": "chat" }, { - "id": "openai/gpt-5.6-luna", - "name": "GPT-5.6 Luna", - "display_name": "GPT-5.6 Luna", + "id": "mistral-large2", + "name": "Mistral Large 2407", + "display_name": "Mistral Large 2407", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 128000, + "output": 8192 }, - "temperature": false, - "tool_call": true, + "temperature": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", - "effort_options": [ - "none", - "low", - "medium", - "high", - "xhigh", - "max" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" - } + "supported": false }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-02-16", - "release_date": "2026-07-09", - "last_updated": "2026-07-09", + "attachment": false, + "open_weights": true, + "knowledge": "2024-03-31", + "release_date": "2024-07-24", + "last_updated": "2024-07-24", "type": "chat" }, { - "id": "openai/gpt-5.4-mini", - "name": "GPT-5.4 mini", - "display_name": "GPT-5.4 mini", + "id": "claude-opus-4-7", + "name": "Claude Opus 4.7", + "display_name": "Claude Opus 4.7", "modalities": { "input": [ "text", @@ -307231,10 +307734,10 @@ ] }, "limit": { - "context": 128000, + "context": 1000000, "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -307245,38 +307748,44 @@ "supported": true, "default_enabled": false, "mode": "effort", - "effort": "none", + "effort": "high", "effort_options": [ - "none", "low", "medium", "high", - "xhigh" + "xhigh", + "max" ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" + "interleaved": true, + "summaries": true, + "visibility": "omitted", + "continuation": [ + "thinking_blocks" ], - "visibility": "hidden" + "notes": [ + "Claude Opus 4.7 and newer Opus models require thinking.type = \"adaptive\" to enable thinking explicitly.", + "Manual budget_tokens requests return 400 on Claude Opus 4.7 and newer adaptive-only Opus models.", + "task_budget is separate from thinking control and should not be treated as a thinking budget." + ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-03-17", - "last_updated": "2026-03-17", + "knowledge": "2026-01-31", + "release_date": "2026-04-16", + "last_updated": "2026-04-16", "type": "chat" }, { - "id": "openai/gpt-4.1-mini", - "name": "GPT-4.1 mini", - "display_name": "GPT-4.1 mini", + "id": "gemini-3.1-pro", + "name": "Gemini 3.1 Pro Preview", + "display_name": "Gemini 3.1 Pro Preview", "modalities": { "input": [ "text", "image", + "video", + "audio", "pdf" ], "output": [ @@ -307284,89 +307793,54 @@ ] }, "limit": { - "context": 1047576, - "output": 32768 - }, - "temperature": true, - "tool_call": true, - "reasoning": { - "supported": false - }, - "attachment": true, - "open_weights": false, - "knowledge": "2024-04", - "release_date": "2025-04-14", - "last_updated": "2025-04-14", - "type": "chat" - }, - { - "id": "openai/gpt-5.4-nano", - "name": "GPT-5.4 nano", - "display_name": "GPT-5.4 nano", - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 128000 + "context": 1000000, + "output": 64000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "none", - "effort_options": [ - "none", - "low", - "medium", - "high", - "xhigh" - ], - "verbosity": "medium", - "verbosity_options": [ + "default_enabled": true, + "mode": "level", + "level": "high", + "level_options": [ "low", - "medium", "high" ], - "visibility": "hidden" + "summaries": true, + "visibility": "summary", + "continuation": [ + "thought_signatures" + ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-03-17", - "last_updated": "2026-03-17", + "knowledge": "2025-01", + "release_date": "2026-02-19", + "last_updated": "2026-02-19", "type": "chat" }, { - "id": "openai/gpt-5.5-pro", - "name": "GPT-5.5 Pro", - "display_name": "GPT-5.5 Pro", + "id": "openai-gpt-5.6-luna", + "name": "GPT-5.6 Luna", + "display_name": "GPT-5.6 Luna", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, + "context": 1050000, "output": 128000 }, "temperature": false, @@ -307377,68 +307851,89 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, "effort_options": [ + "none", + "minimal", + "low", "medium", - "high", - "xhigh" + "high" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-12-01", - "release_date": "2026-04-23", - "last_updated": "2026-04-23", + "knowledge": "2026-02-16", + "release_date": "2026-07-09", + "last_updated": "2026-07-09", "type": "chat" }, { - "id": "openai/gpt-4.1", - "name": "GPT-4.1", - "display_name": "GPT-4.1", + "id": "claude-haiku-4-5", + "name": "Claude Haiku 4.5 (latest)", + "display_name": "Claude Haiku 4.5 (latest)", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1047576, - "output": 32768 + "context": 200000, + "output": 64000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": false + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": false, + "mode": "budget", + "budget": { + "min": 1024, + "unit": "tokens" + }, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", + "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." + ] + } }, "attachment": true, "open_weights": false, - "knowledge": "2024-04", - "release_date": "2025-04-14", - "last_updated": "2025-04-14", + "knowledge": "2025-02-28", + "release_date": "2025-10-15", + "last_updated": "2025-10-15", "type": "chat" }, { - "id": "openai/gpt-6.1-terra", - "name": "GPT-6.1 Terra", - "display_name": "GPT-6.1 Terra", + "id": "claude-sonnet-5", + "name": "Claude Sonnet 5", + "display_name": "Claude Sonnet 5", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 1000000, + "output": 64000 }, "temperature": false, "tool_call": true, @@ -307448,42 +307943,34 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "none", - "low", - "medium", - "high", - "xhigh", - "max" - ] + "supported": true } }, "attachment": true, "open_weights": false, - "knowledge": "2026-06-01", - "release_date": "2026-10-26", - "last_updated": "2026-10-26", + "knowledge": "2026-01-31", + "release_date": "2026-06-30", + "last_updated": "2026-06-30", "type": "chat" }, { - "id": "openai/gpt-6.1-sol", - "name": "GPT-6.1 Sol", - "display_name": "GPT-6.1 Sol", + "id": "openai-gpt-5.2", + "name": "GPT-5.2", + "display_name": "GPT-5.2", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1050000, + "context": 400000, "output": 128000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -307493,25 +307980,24 @@ "reasoning": { "effort_options": [ "none", + "minimal", "low", "medium", - "high", - "xhigh", - "max" + "high" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-04-30", - "release_date": "2026-09-29", - "last_updated": "2026-09-29", + "knowledge": "2025-08-31", + "release_date": "2025-12-11", + "last_updated": "2025-12-11", "type": "chat" }, { - "id": "openai/gpt-5-nano", - "name": "GPT-5 Nano", - "display_name": "GPT-5 Nano", + "id": "claude-fable-5", + "name": "Claude Fable 5", + "display_name": "Claude Fable 5", "modalities": { "input": [ "text", @@ -307522,7 +308008,7 @@ ] }, "limit": { - "context": 128000, + "context": 1000000, "output": 128000 }, "temperature": false, @@ -307536,93 +308022,101 @@ "supported": true, "default_enabled": true, "mode": "effort", - "effort": "medium", + "effort": "high", "effort_options": [ - "minimal", "low", "medium", - "high" + "high", + "xhigh", + "max" ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" + "interleaved": true, + "summaries": true, + "visibility": "omitted", + "continuation": [ + "thinking_blocks" ], - "visibility": "hidden" + "notes": [ + "Adaptive thinking is always on for Claude Fable 5 and Claude Mythos 5; thinking.type = \"disabled\" is rejected.", + "Manual budget_tokens requests return 400 on Claude Fable 5 and Claude Mythos 5.", + "thinking.display defaults to omitted; set display to summarized to receive readable thinking summaries." + ] } }, "attachment": true, "open_weights": false, - "knowledge": "2024-05-30", - "release_date": "2025-08-07", - "last_updated": "2025-08-07", + "knowledge": "2026-01-31", + "release_date": "2026-06-09", + "last_updated": "2026-06-09", "type": "chat" }, { - "id": "reflection-ai/beam-1.0-501b-a23b", - "name": "Beam 1.0 501B A23B", - "display_name": "Beam 1.0 501B A23B", + "id": "claude-sonnet-4-5", + "name": "Claude Sonnet 4.5 (latest)", + "display_name": "Claude Sonnet 4.5 (latest)", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 131072 + "context": 200000, + "output": 64000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": false, + "mode": "budget", + "budget": { + "min": 1024, + "unit": "tokens" + }, "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ "thinking_blocks" ], - "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" + "notes": [ + "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", + "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." ] } }, - "attachment": false, - "open_weights": true, - "knowledge": "2026-06-30", - "release_date": "2026-10-05", - "last_updated": "2026-10-16", + "attachment": true, + "open_weights": false, + "knowledge": "2025-07-31", + "release_date": "2025-09-29", + "last_updated": "2025-09-29", "type": "chat" }, { - "id": "moonshotai/kimi-k3", - "name": "Kimi K3", - "display_name": "Kimi K3", + "id": "openai-gpt-5.6-terra", + "name": "GPT-5.6 Terra", + "display_name": "GPT-5.6 Terra", "modalities": { "input": [ "text", - "image", - "video" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 1050000, + "output": 128000 }, "temperature": false, "tool_call": true, @@ -307632,172 +308126,183 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, "effort_options": [ "none", + "minimal", "low", - "high", - "max" + "medium", + "high" ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-07-16", - "last_updated": "2026-07-16", + "open_weights": false, + "knowledge": "2026-02-16", + "release_date": "2026-07-09", + "last_updated": "2026-07-09", "type": "chat" }, { - "id": "meta/muse-spark-open", - "name": "Muse Spark Open", - "display_name": "Muse Spark Open", + "id": "snowflake-llama3.3-70b", + "name": "Llama-3.3-70B-Instruct", + "display_name": "Llama-3.3-70B-Instruct", "modalities": { "input": [ - "text", - "image", - "video", - "audio" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1040000, - "output": 131072 + "context": 128000, + "output": 4096 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "low", - "medium", - "high", - "xhigh" - ] - } + "supported": false }, - "attachment": true, + "attachment": false, "open_weights": true, - "knowledge": "2026-01-04", - "release_date": "2026-10-15", - "last_updated": "2026-10-15", + "knowledge": "2023-12", + "release_date": "2024-12-06", + "last_updated": "2024-12-06", "type": "chat" }, { - "id": "meta/muse-wonder-1.0", - "name": "Muse Wonder", - "display_name": "Muse Wonder", + "id": "claude-opus-4-5", + "name": "Claude Opus 4.5 (latest)", + "display_name": "Claude Opus 4.5 (latest)", "modalities": { "input": [ "text", - "image", - "video", - "pdf", - "audio" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 200000, + "output": 64000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": false, + "mode": "mixed", + "budget": { + "min": 1024, + "unit": "tokens" + }, + "effort": "high", "effort_options": [ - "minimal", "low", "medium", - "high", - "xhigh", - "max" + "high" + ], + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Claude Opus 4.5 uses manual thinking.type = \"enabled\" with budget_tokens; effort can be used alongside the thinking budget.", + "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header." ] } }, "attachment": true, "open_weights": false, - "release_date": "2026-10-12", - "last_updated": "2026-10-12", + "knowledge": "2025-05", + "release_date": "2025-11-24", + "last_updated": "2025-11-24", "type": "chat" }, { - "id": "meta/muse-spark-1.3", - "name": "Muse Spark 1.3", - "display_name": "Muse Spark 1.3", + "id": "claude-sonnet-4-6", + "name": "Claude Sonnet 4.6", + "display_name": "Claude Sonnet 4.6", "modalities": { "input": [ "text", - "image", - "video", - "pdf", - "audio" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 1000000, + "output": 64000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": false, + "mode": "mixed", + "budget": { + "min": 1024, + "unit": "tokens" + }, + "effort": "high", "effort_options": [ - "minimal", "low", "medium", "high", - "xhigh", "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Anthropic recommends adaptive thinking with effort for Claude 4.6; budget_tokens remains a deprecated compatibility path.", + "Anthropic API defaults effort to high; lower effort levels should be chosen per workload." ] } }, "attachment": true, "open_weights": false, - "release_date": "2026-09-02", - "last_updated": "2026-09-02", + "knowledge": "2025-08-31", + "release_date": "2026-02-17", + "last_updated": "2026-03-13", "type": "chat" }, { - "id": "meta/muse-spark-1.1", - "name": "Muse Spark 1.1", - "display_name": "Muse Spark 1.1", + "id": "openai-gpt-5-nano", + "name": "GPT-5 Nano", + "display_name": "GPT-5 Nano", "modalities": { "input": [ "text", - "image", - "pdf", - "video" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 272000, + "output": 8192 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -307805,152 +308310,96 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, "effort_options": [ + "none", "minimal", "low", "medium", - "high", - "xhigh" + "high" ] } }, "attachment": true, "open_weights": false, - "release_date": "2026-04-08", - "last_updated": "2026-07-09", + "knowledge": "2024-05-30", + "release_date": "2025-08-07", + "last_updated": "2025-08-07", "type": "chat" }, { - "id": "meta/muse-spark-1.2", - "name": "Muse Spark 1.2", - "display_name": "Muse Spark 1.2", - "modalities": { - "input": [ - "text", - "image", - "video", - "pdf", - "audio" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 1048576, - "output": 131072 - }, - "temperature": true, - "tool_call": true, - "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "effort_options": [ - "minimal", - "low", - "medium", - "high", - "xhigh" - ] - } - }, - "attachment": true, - "open_weights": false, - "release_date": "2026-08-05", - "last_updated": "2026-08-05", - "type": "chat" - } - ] - }, - "pioneer": { - "id": "pioneer", - "name": "Pioneer", - "display_name": "Pioneer", - "api": "https://api.fastino.ai/v1", - "doc": "https://agent.pioneer.ai/llms.txt", - "models": [ - { - "id": "gpt-5.5", - "name": "GPT-5.5", - "display_name": "GPT-5.5", + "id": "claude-opus-4-8", + "name": "Claude Opus 4.8", + "display_name": "Claude Opus 4.8", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1050000, + "context": 1000000, "output": 128000 }, "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, + "default_enabled": false, "mode": "effort", - "effort": "medium", + "effort": "high", "effort_options": [ "low", "medium", "high", - "xhigh" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" + "xhigh", + "max" ], "interleaved": true, "summaries": true, - "visibility": "summary", + "visibility": "omitted", "continuation": [ "thinking_blocks" + ], + "notes": [ + "Claude Opus 4.7 and newer Opus models require thinking.type = \"adaptive\" to enable thinking explicitly.", + "Manual budget_tokens requests return 400 on Claude Opus 4.7 and newer adaptive-only Opus models.", + "task_budget is separate from thinking control and should not be treated as a thinking budget." ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-12-01", - "release_date": "2026-04-23", - "last_updated": "2026-04-23", + "knowledge": "2026-01", + "release_date": "2026-05-28", + "last_updated": "2026-05-28", "type": "chat" }, { - "id": "claude-opus-5", - "name": "Claude Opus 5", - "display_name": "Claude Opus 5", + "id": "deepseek-r1", + "name": "DeepSeek-R1", + "display_name": "DeepSeek-R1", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 128000, + "output": 32768 }, - "temperature": false, - "tool_call": true, + "temperature": true, + "tool_call": false, "reasoning": { "supported": true, "default": true @@ -307963,32 +308412,24 @@ "visibility": "summary", "continuation": [ "thinking_blocks" - ], - "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-05", - "release_date": "2026-07-24", - "last_updated": "2026-07-24", + "attachment": false, + "open_weights": true, + "knowledge": "2024-07", + "release_date": "2025-01-20", + "last_updated": "2025-05-29", "type": "chat" }, { - "id": "claude-sonnet-5", - "name": "Claude Sonnet 5", - "display_name": "Claude Sonnet 5", + "id": "claude-opus-4-6", + "name": "Claude Opus 4.6", + "display_name": "Claude Opus 4.6", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" @@ -307998,112 +308439,91 @@ "context": 1000000, "output": 128000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": false, + "mode": "mixed", + "budget": { + "min": 1024, + "unit": "tokens" + }, + "effort": "high", + "effort_options": [ + "low", + "medium", + "high", + "max" + ], "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ "thinking_blocks" ], - "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" + "notes": [ + "Anthropic recommends adaptive thinking with effort for Claude 4.6; budget_tokens remains a deprecated compatibility path.", + "Anthropic API defaults effort to high; lower effort levels should be chosen per workload." ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-01-31", - "release_date": "2026-06-30", - "last_updated": "2026-06-30", + "knowledge": "2025-05-31", + "release_date": "2026-02-05", + "last_updated": "2026-03-13", "type": "chat" }, { - "id": "gpt-5.6-sol", - "name": "GPT-5.6 Sol", - "display_name": "GPT-5.6 Sol", + "id": "openai-gpt-4.1", + "name": "GPT-4.1", + "display_name": "GPT-4.1", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 128000, + "output": 32000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", - "effort_options": [ - "none", - "low", - "medium", - "high", - "xhigh", - "max" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } + "supported": false }, "attachment": true, "open_weights": false, - "knowledge": "2026-02-16", - "release_date": "2026-07-09", - "last_updated": "2026-07-09", + "knowledge": "2024-04", + "release_date": "2025-04-14", + "last_updated": "2025-04-14", "type": "chat" }, { - "id": "gpt-5.6-terra", - "name": "GPT-5.6 Terra", - "display_name": "GPT-5.6 Terra", + "id": "claude-opus-5", + "name": "Claude Opus 5", + "display_name": "Claude Opus 5", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1050000, + "context": 1000000, "output": 128000 }, "temperature": false, @@ -308111,51 +308531,27 @@ "reasoning": { "supported": true, "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", - "effort_options": [ - "none", - "low", - "medium", - "high", - "xhigh", - "max" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] + }, + "extra_capabilities": { + "reasoning": { + "supported": true } }, "attachment": true, "open_weights": false, - "knowledge": "2026-02-16", - "release_date": "2026-07-09", - "last_updated": "2026-07-09", + "knowledge": "2026-05", + "release_date": "2026-07-24", + "last_updated": "2026-07-24", "type": "chat" }, { - "id": "gpt-5.6-luna", - "name": "GPT-5.6 Luna", - "display_name": "GPT-5.6 Luna", + "id": "openai-gpt-5.6-sol", + "name": "GPT-5.6 Sol", + "display_name": "GPT-5.6 Sol", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" @@ -308173,29 +308569,12 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", "effort_options": [ "none", - "low", - "medium", - "high", - "xhigh", - "max" - ], - "verbosity": "medium", - "verbosity_options": [ + "minimal", "low", "medium", "high" - ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" ] } }, @@ -308207,24 +308586,23 @@ "type": "chat" }, { - "id": "claude-opus-5-fast", - "name": "Claude Opus 5", - "display_name": "Claude Opus 5", + "id": "openai-gpt-5.4", + "name": "GPT-5.4", + "display_name": "GPT-5.4", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, + "context": 1050000, "output": 128000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -308232,98 +308610,79 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], "effort_options": [ + "none", + "minimal", "low", "medium", - "high", - "xhigh", - "max" + "high" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-05", - "release_date": "2026-07-24", - "last_updated": "2026-07-24", + "knowledge": "2025-08-31", + "release_date": "2026-03-05", + "last_updated": "2026-03-05", "type": "chat" }, { - "id": "claude-haiku-4-5", - "name": "Claude Haiku 4.5 (latest)", - "display_name": "Claude Haiku 4.5 (latest)", + "id": "openai-gpt-5", + "name": "GPT-5", + "display_name": "GPT-5", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 64000 + "context": 272000, + "output": 8192 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "budget", - "budget": { - "min": 1024, - "unit": "tokens" - }, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", - "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." + "effort_options": [ + "none", + "minimal", + "low", + "medium", + "high" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-02-28", - "release_date": "2025-10-15", - "last_updated": "2025-10-15", + "knowledge": "2024-09-30", + "release_date": "2025-08-07", + "last_updated": "2025-08-07", "type": "chat" }, { - "id": "claude-fable-5", - "name": "Claude Fable 5", - "display_name": "Claude Fable 5", + "id": "openai-gpt-5-mini", + "name": "GPT-5 Mini", + "display_name": "GPT-5 Mini", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 272000, + "output": 8192 }, "temperature": false, "tool_call": true, @@ -308333,54 +308692,40 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "high", "effort_options": [ + "none", + "minimal", "low", "medium", - "high", - "xhigh", - "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Adaptive thinking is always on for Claude Fable 5 and Claude Mythos 5; thinking.type = \"disabled\" is rejected.", - "Manual budget_tokens requests return 400 on Claude Fable 5 and Claude Mythos 5.", - "thinking.display defaults to omitted; set display to summarized to receive readable thinking summaries." + "high" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-01-31", - "release_date": "2026-06-09", - "last_updated": "2026-06-09", + "knowledge": "2024-05-30", + "release_date": "2025-08-07", + "last_updated": "2025-08-07", "type": "chat" }, { - "id": "zai-org/GLM-5.2", - "name": "GLM-5.2", - "display_name": "GLM-5.2", + "id": "openai-gpt-5.5", + "name": "GPT-5.5", + "display_name": "GPT-5.5", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 1040000, + "context": 1050000, "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -308388,34 +308733,39 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], "effort_options": [ - "high", - "max" + "none", + "minimal", + "low", + "medium", + "high" ] } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-06-13", - "last_updated": "2026-06-13", + "attachment": true, + "open_weights": false, + "knowledge": "2025-12-01", + "release_date": "2026-04-23", + "last_updated": "2026-04-23", "type": "chat" - }, + } + ] + }, + "runinfra": { + "id": "runinfra", + "name": "RunInfra", + "display_name": "RunInfra", + "api": "https://api.runinfra.ai/v1", + "doc": "https://runinfra.ai/docs", + "models": [ { - "id": "nvidia/NVIDIA-Nemotron-3.5-Super-VL-120B-A12B-NVFP4", - "name": "Nemotron 3.5 Super VL 120B A12B", - "display_name": "Nemotron 3.5 Super VL 120B A12B", + "id": "Qwen/Qwen3.8-27B", + "name": "Qwen3.8 27B", + "display_name": "Qwen3.8 27B", "modalities": { "input": [ "text", - "image", - "video" + "image" ], "output": [ "text" @@ -308423,7 +308773,7 @@ }, "limit": { "context": 262144, - "output": 4096 + "output": 32768 }, "temperature": true, "tool_call": true, @@ -308433,31 +308783,24 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], "effort_options": [ + "none", "low", "medium", - "high", "xhigh" ] } }, "attachment": true, "open_weights": true, - "release_date": "2026-10-15", - "last_updated": "2026-10-15", + "release_date": "2026-08-14", + "last_updated": "2026-08-14", "type": "chat" }, { - "id": "nvidia/NVIDIA-Nemotron-3.5-Lightning-30B-A3B-BF16", - "name": "Nemotron 3.5 Lightning 30B A3B", - "display_name": "Nemotron 3.5 Lightning 30B A3B", + "id": "Inferact/Qwen3.8-2.4T-A95B-NVFP4", + "name": "Qwen3.8 2.4T A95B (NVFP4)", + "display_name": "Qwen3.8 2.4T A95B (NVFP4)", "modalities": { "input": [ "text" @@ -308467,8 +308810,8 @@ ] }, "limit": { - "context": 65536, - "output": 4096 + "context": 262144, + "output": 32768 }, "temperature": true, "tool_call": true, @@ -308478,32 +308821,23 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], "effort_options": [ "low", "medium", - "high", "xhigh" ] } }, "attachment": false, "open_weights": true, - "knowledge": "2025-09", - "release_date": "2026-08-11", - "last_updated": "2026-08-11", + "release_date": "2026-08-12", + "last_updated": "2026-08-12", "type": "chat" }, { - "id": "fastino/gliner2.5-base-v1", - "name": "GLiNER 2.5 Base", - "display_name": "GLiNER 2.5 Base", + "id": "nvidia/NVIDIA-Nemotron-3.5-Lightning-30B-A3B-BF16", + "name": "Nemotron 3.5 Lightning 30B A3B", + "display_name": "Nemotron 3.5 Lightning 30B A3B", "modalities": { "input": [ "text" @@ -308513,132 +308847,106 @@ ] }, "limit": { - "context": 4096, - "output": 4096 + "context": 262144, + "output": 32768 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, "attachment": false, "open_weights": true, - "release_date": "2026-08-24", - "last_updated": "2026-08-24", + "knowledge": "2025-09", + "release_date": "2026-08-11", + "last_updated": "2026-08-11", "type": "chat" }, { - "id": "fastino/gliner2-base-v1", - "name": "GLiNER2 Base", - "display_name": "GLiNER2 Base", + "id": "ornith-ai/Ornith-1.5-35B-A3B", + "name": "Ornith 1.5 35B A3B", + "display_name": "Ornith 1.5 35B A3B", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 2048, - "output": 2048 + "context": 262144, + "output": 32768 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": false - }, - "attachment": false, - "open_weights": true, - "release_date": "2025-06-30", - "last_updated": "2025-06-30", - "type": "chat" - }, - { - "id": "fastino/gliner2.5-multi-v1", - "name": "GLiNER 2.5 Multi", - "display_name": "GLiNER 2.5 Multi", - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 4096, - "output": 4096 + "supported": true, + "default": true }, - "temperature": true, - "tool_call": false, - "reasoning": { - "supported": false + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } }, - "attachment": false, + "attachment": true, "open_weights": true, - "release_date": "2026-08-24", - "last_updated": "2026-08-24", + "release_date": "2026-08-18", + "last_updated": "2026-08-23", "type": "chat" }, { - "id": "fastino/gliner2-large-v1", - "name": "GLiNER2 Large", - "display_name": "GLiNER2 Large", + "id": "zai-org/GLM-5.3-Flash", + "name": "GLM-5.3-Flash", + "display_name": "GLM-5.3-Flash", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 8192, - "output": 8192 + "context": 1048576, + "output": 32768 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": false - }, - "attachment": false, - "open_weights": true, - "release_date": "2025-06-30", - "last_updated": "2025-06-30", - "type": "chat" - }, - { - "id": "fastino/GLiNER-2.5-Decide", - "name": "GLiNER-2.5-Decide", - "display_name": "GLiNER-2.5-Decide", - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 8192, - "output": 8192 + "supported": true, + "default": true }, - "temperature": true, - "tool_call": false, - "reasoning": { - "supported": false + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "effort_options": [ + "low", + "high", + "max" + ] + } }, - "attachment": false, + "attachment": true, "open_weights": true, - "release_date": "2026-09-24", - "last_updated": "2026-09-24", + "release_date": "2026-08-26", + "last_updated": "2026-08-26", "type": "chat" }, { - "id": "fastino/gliguard-PII-multi", - "name": "GLiNER2-Guardrails-PII-Multi", - "display_name": "GLiNER2-Guardrails-PII-Multi", + "id": "zai-org/GLM-5.3", + "name": "GLM-5.3", + "display_name": "GLM-5.3", "modalities": { "input": [ "text" @@ -308648,24 +308956,41 @@ ] }, "limit": { - "context": 8192, - "output": 8192 + "context": 1048576, + "output": 32768 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "effort_options": [ + "low", + "high", + "max" + ] + } }, "attachment": false, "open_weights": true, - "release_date": "2026-07-08", - "last_updated": "2026-07-08", + "release_date": "2026-08-14", + "last_updated": "2026-08-14", "type": "chat" }, { - "id": "fastino/gliguard-LLMGuardrails-300M", - "name": "GLiGuard LLM Guardrails 300M", - "display_name": "GLiGuard LLM Guardrails 300M", + "id": "deepseek-ai/DeepSeek-V4-Flash-0731", + "name": "DeepSeek V4 Flash 0731", + "display_name": "DeepSeek V4 Flash 0731", "modalities": { "input": [ "text" @@ -308675,24 +309000,36 @@ ] }, "limit": { - "context": 8192, - "output": 8192 + "context": 1048576, + "output": 32768 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "effort_options": [ + "none", + "low", + "max" + ] + } }, "attachment": false, "open_weights": true, - "release_date": "2026-04-30", - "last_updated": "2026-04-30", + "knowledge": "2025-05", + "release_date": "2026-07-31", + "last_updated": "2026-07-31", "type": "chat" }, { - "id": "fastino/gliner2-multi-v1", - "name": "GLiNER2 Multi", - "display_name": "GLiNER2 Multi", + "id": "deepseek-ai/DeepSeek-V4-Pro-0813", + "name": "DeepSeek V4 Pro 0813", + "display_name": "DeepSeek V4 Pro 0813", "modalities": { "input": [ "text" @@ -308702,90 +309039,154 @@ ] }, "limit": { - "context": 8192, - "output": 8192 + "context": 1048576, + "output": 32768 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true + } }, "attachment": false, "open_weights": true, - "release_date": "2025-11-30", - "last_updated": "2025-11-30", + "release_date": "2026-08-13", + "last_updated": "2026-08-22", "type": "chat" - }, + } + ] + }, + "github-copilot": { + "id": "github-copilot", + "name": "GitHub Copilot", + "display_name": "GitHub Copilot", + "api": "https://api.githubcopilot.com", + "doc": "https://docs.github.com/en/copilot", + "models": [ { - "id": "fastino/gliner2-privacy-filter-PII-multi", - "name": "GLiNER2 Privacy Filter PII (Multi)", - "display_name": "GLiNER2 Privacy Filter PII (Multi)", + "id": "gemini-3.5-flash", + "name": "Gemini 3.5 Flash", + "display_name": "Gemini 3.5 Flash", "modalities": { "input": [ - "text" + "text", + "image", + "video", + "audio", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 8192, - "output": 8192 + "context": 200000, + "output": 64000 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, - "open_weights": true, - "release_date": "2026-04-30", - "last_updated": "2026-04-30", + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "level", + "level": "high", + "level_options": [ + "minimal", + "low", + "medium", + "high" + ], + "summaries": true, + "visibility": "summary", + "continuation": [ + "thought_signatures" + ] + } + }, + "attachment": true, + "open_weights": false, + "knowledge": "2025-01", + "release_date": "2026-05-19", + "last_updated": "2026-05-19", "type": "chat" }, { - "id": "fastino/gliner2-multi-large-v1", - "name": "GLiNER2 Multi Large", - "display_name": "GLiNER2 Multi Large", + "id": "gpt-5.4-nano", + "name": "GPT-5.4 nano", + "display_name": "GPT-5.4 nano", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 8192, - "output": 8192 + "context": 400000, + "output": 128000 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": false }, - "attachment": false, - "open_weights": true, - "release_date": "2025-11-30", - "last_updated": "2025-11-30", + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": false, + "mode": "effort", + "effort": "none", + "effort_options": [ + "none", + "low", + "medium", + "high", + "xhigh" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" + } + }, + "attachment": true, + "open_weights": false, + "knowledge": "2025-08-31", + "release_date": "2026-03-17", + "last_updated": "2026-03-17", "type": "chat" }, { - "id": "moonshotai/Kimi-K3-Fast", + "id": "kimi-k3", "name": "Kimi K3", "display_name": "Kimi K3", "modalities": { "input": [ "text", - "image", - "video" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, + "context": 1048576, "output": 131072 }, "temperature": false, @@ -308797,12 +309198,6 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], "effort_options": [ "low", "high", @@ -308817,22 +309212,22 @@ "type": "chat" }, { - "id": "moonshotai/Kimi-K3", - "name": "Kimi K3", - "display_name": "Kimi K3", + "id": "gpt-6.1-terra", + "name": "GPT-6.1 Terra", + "display_name": "GPT-6.1 Terra", "modalities": { "input": [ "text", "image", - "video" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 1050000, + "output": 128000 }, "temperature": false, "tool_call": true, @@ -308842,89 +309237,80 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], "effort_options": [ + "none", "low", + "medium", "high", + "xhigh", "max" ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-07-16", - "last_updated": "2026-07-16", + "open_weights": false, + "knowledge": "2026-06-01", + "release_date": "2026-10-26", + "last_updated": "2026-10-26", "type": "chat" }, { - "id": "deepseek-ai/DeepSeek-V4-Flash", - "name": "DeepSeek V4 Flash", - "display_name": "DeepSeek V4 Flash", + "id": "gpt-5.4", + "name": "GPT-5.4", + "display_name": "GPT-5.4", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 1050000, + "output": 128000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, + "default_enabled": false, "mode": "effort", - "effort": "high", + "effort": "none", "effort_options": [ + "none", "low", + "medium", "high", - "max" + "xhigh" ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" ], - "notes": [ - "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." - ] + "visibility": "hidden" } }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-04-24", - "last_updated": "2026-04-24", + "attachment": true, + "open_weights": false, + "knowledge": "2025-08-31", + "release_date": "2026-03-05", + "last_updated": "2026-03-05", "type": "chat" - } - ] - }, - "perplexity": { - "id": "perplexity", - "name": "Perplexity", - "display_name": "Perplexity", - "doc": "https://docs.perplexity.ai", - "models": [ + }, { - "id": "sonar-pro", - "name": "Sonar Pro", - "display_name": "Sonar Pro", + "id": "gpt-5.4-mini", + "name": "GPT-5.4 mini", + "display_name": "GPT-5.4 mini", "modalities": { "input": [ "text", @@ -308935,39 +309321,64 @@ ] }, "limit": { - "context": 200000, - "output": 8192 + "context": 400000, + "output": 128000 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": false + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": false, + "mode": "effort", + "effort": "none", + "effort_options": [ + "none", + "low", + "medium", + "high", + "xhigh" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" + } }, "attachment": true, "open_weights": false, - "knowledge": "2025-09-01", - "release_date": "2024-01-01", - "last_updated": "2025-09-01", + "knowledge": "2025-08-31", + "release_date": "2026-03-17", + "last_updated": "2026-03-17", "type": "chat" }, { - "id": "sonar-deep-research", - "name": "Perplexity Sonar Deep Research", - "display_name": "Perplexity Sonar Deep Research", + "id": "gpt-6-sol", + "name": "GPT-6 Sol", + "display_name": "GPT-6 Sol", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 32768 + "context": 1050000, + "output": 128000 }, "temperature": false, - "tool_call": false, + "tool_call": true, "reasoning": { "supported": true, "default": true @@ -308975,112 +309386,92 @@ "extra_capabilities": { "reasoning": { "effort_options": [ - "minimal", + "none", "low", "medium", - "high" + "high", + "xhigh", + "max" ] } }, - "attachment": false, - "open_weights": false, - "knowledge": "2025-01", - "release_date": "2025-02-01", - "last_updated": "2025-09-01", - "type": "chat" - }, - { - "id": "sonar", - "name": "Sonar", - "display_name": "Sonar", - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 4096 - }, - "temperature": true, - "tool_call": false, - "reasoning": { - "supported": false - }, - "attachment": false, + "attachment": true, "open_weights": false, - "knowledge": "2025-09-01", - "release_date": "2024-01-01", - "last_updated": "2025-09-01", + "knowledge": "2026-04-20", + "release_date": "2026-09-22", + "last_updated": "2026-09-22", "type": "chat" }, { - "id": "sonar-reasoning-pro", - "name": "Sonar Reasoning Pro", - "display_name": "Sonar Reasoning Pro", + "id": "gpt-5.6-terra", + "name": "GPT-5.6 Terra", + "display_name": "GPT-5.6 Terra", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 4096 + "context": 1050000, + "output": 128000 }, - "temperature": true, - "tool_call": false, + "temperature": false, + "tool_call": true, "reasoning": { "supported": true, "default": true }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", "effort_options": [ - "minimal", + "none", + "low", + "medium", + "high", + "xhigh", + "max" + ], + "verbosity": "medium", + "verbosity_options": [ "low", "medium", "high" - ] + ], + "visibility": "hidden" } }, "attachment": true, "open_weights": false, - "knowledge": "2025-09-01", - "release_date": "2024-01-01", - "last_updated": "2025-09-01", + "knowledge": "2026-02-16", + "release_date": "2026-07-09", + "last_updated": "2026-07-09", "type": "chat" - } - ] - }, - "neosmith": { - "id": "neosmith", - "name": "NeoSmith", - "display_name": "NeoSmith", - "api": "https://router.neosmith.ai/v1", - "doc": "https://neosmith.ai/docs", - "models": [ + }, { - "id": "neosmith.intelligent-pro", - "name": "NeoSmith Pro", - "display_name": "NeoSmith Pro", + "id": "gpt-5.3-codex", + "name": "GPT-5.3 Codex", + "display_name": "GPT-5.3 Codex", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1000000, + "context": 400000, "output": 128000 }, "temperature": true, @@ -309089,45 +309480,81 @@ "supported": true, "default": true }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", + "effort_options": [ + "low", + "medium", + "high", + "xhigh" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" + } + }, "attachment": true, "open_weights": false, - "release_date": "2026-04-26", - "last_updated": "2026-07-18", + "knowledge": "2025-08-31", + "release_date": "2026-02-05", + "last_updated": "2026-02-05", "type": "chat" }, { - "id": "neosmith.intelligent-maestro", - "name": "NeoSmith Maestro", - "display_name": "NeoSmith Maestro", + "id": "gpt-6-astra", + "name": "GPT-6 Astra", + "display_name": "GPT-6 Astra", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1000000, + "context": 1050000, "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, "default": true }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" + ] + } + }, "attachment": true, "open_weights": false, - "release_date": "2026-08-03", - "last_updated": "2026-08-08", + "knowledge": "2026-04-30", + "release_date": "2026-09-04", + "last_updated": "2026-09-04", "type": "chat" }, { - "id": "neosmith.intelligent-basic", - "name": "NeoSmith Basic", - "display_name": "NeoSmith Basic", + "id": "kimi-k2.7-code", + "name": "Kimi K2.7 Code", + "display_name": "Kimi K2.7 Code", "modalities": { "input": [ "text", @@ -309138,133 +309565,180 @@ ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 256000, + "output": 32000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, "default": true }, + "extra_capabilities": { + "reasoning": { + "supported": true + } + }, "attachment": true, - "open_weights": false, - "release_date": "2026-04-26", - "last_updated": "2026-08-03", + "open_weights": true, + "knowledge": "2025-01", + "release_date": "2026-06-12", + "last_updated": "2026-06-12", "type": "chat" }, { - "id": "neosmith.neolite", - "name": "NeoSmith NeoLite", - "display_name": "NeoSmith NeoLite", + "id": "gpt-6.1-sol", + "name": "GPT-6.1 Sol", + "display_name": "GPT-6.1 Sol", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 512000, - "output": 64000 + "context": 1050000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, "default": true }, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" + ] + } + }, "attachment": true, "open_weights": false, - "release_date": "2026-06-20", - "last_updated": "2026-08-03", + "knowledge": "2026-04-30", + "release_date": "2026-09-29", + "last_updated": "2026-09-29", "type": "chat" - } - ] - }, - "submodel": { - "id": "submodel", - "name": "submodel", - "display_name": "submodel", - "api": "https://llm.submodel.ai/v1", - "doc": "https://submodel.gitbook.io", - "models": [ + }, { - "id": "zai-org/GLM-4.5-FP8", - "name": "GLM 4.5 FP8", - "display_name": "GLM 4.5 FP8", + "id": "claude-haiku-4.5", + "name": "Claude Haiku 4.5 (latest)", + "display_name": "Claude Haiku 4.5 (latest)", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 131072 + "context": 200000, + "output": 64000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-07-28", - "last_updated": "2025-07-28", + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": false, + "mode": "budget", + "budget": { + "min": 1024, + "unit": "tokens" + }, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", + "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." + ] + } + }, + "attachment": true, + "open_weights": false, + "knowledge": "2025-02-28", + "release_date": "2025-10-15", + "last_updated": "2025-10-15", "type": "chat" - }, - { - "id": "zai-org/GLM-4.5-Air", - "name": "GLM 4.5 Air", - "display_name": "GLM 4.5 Air", + }, + { + "id": "gemini-4-argon", + "name": "Gemini 4 Argon", + "display_name": "Gemini 4 Argon", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 131072 + "context": 1048576, + "output": 65536 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-07-28", - "last_updated": "2025-07-28", + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "low", + "medium", + "high" + ] + } + }, + "attachment": true, + "open_weights": false, + "knowledge": "2026-03", + "release_date": "2026-10-13", + "last_updated": "2026-10-13", "type": "chat" }, { - "id": "Qwen/Qwen3-235B-A22B-Thinking-2507", - "name": "Qwen3 235B A22B Thinking 2507", - "display_name": "Qwen3 235B A22B Thinking 2507", + "id": "claude-fable-5.1", + "name": "Claude Fable 5.1", + "display_name": "Claude Fable 5.1", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 131072 + "context": 1000000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -309273,90 +309747,135 @@ "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "high", + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" + ], "interleaved": true, "summaries": true, - "visibility": "summary", + "visibility": "omitted", "continuation": [ "thinking_blocks" + ], + "notes": [ + "Adaptive thinking is always on for Claude Fable 5 and Claude Mythos 5; thinking.type = \"disabled\" is rejected.", + "Manual budget_tokens requests return 400 on Claude Fable 5 and Claude Mythos 5.", + "thinking.display defaults to omitted; set display to summarized to receive readable thinking summaries." ] } }, - "attachment": false, - "open_weights": true, - "release_date": "2025-07-25", - "last_updated": "2025-07-25", + "attachment": true, + "open_weights": false, + "knowledge": "2026-06", + "release_date": "2026-09-01", + "last_updated": "2026-09-01", "type": "chat" }, { - "id": "Qwen/Qwen3-Coder-480B-A35B-Instruct-FP8", - "name": "Qwen3 Coder 480B A35B Instruct", - "display_name": "Qwen3 Coder 480B A35B Instruct", + "id": "grok-4.7", + "name": "Grok 4.7", + "display_name": "Grok 4.7", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 500000, + "output": 128000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-07-22", - "last_updated": "2025-07-22", + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "low", + "medium", + "high", + "xhigh" + ] + } + }, + "attachment": true, + "open_weights": false, + "knowledge": "2026-05", + "release_date": "2026-09-21", + "last_updated": "2026-09-21", "type": "chat" }, { - "id": "Qwen/Qwen3-235B-A22B-Instruct-2507", - "name": "Qwen3 235B A22B Instruct 2507", - "display_name": "Qwen3 235B A22B Instruct 2507", + "id": "claude-sonnet-5", + "name": "Claude Sonnet 5", + "display_name": "Claude Sonnet 5", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 131072 + "context": 1000000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, - "open_weights": true, - "release_date": "2025-07-21", - "last_updated": "2025-07-21", + "extra_capabilities": { + "reasoning": { + "supported": true, + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" + ] + } + }, + "attachment": true, + "open_weights": false, + "knowledge": "2026-01-31", + "release_date": "2026-06-30", + "last_updated": "2026-06-30", "type": "chat" }, { - "id": "openai/gpt-oss-120b", - "name": "GPT OSS 120B", - "display_name": "GPT OSS 120B", + "id": "grok-4.5", + "name": "Grok 4.5", + "display_name": "Grok 4.5", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 32768 + "context": 500000, + "output": 128000 }, "temperature": true, "tool_call": true, @@ -309366,86 +309885,136 @@ }, "extra_capabilities": { "reasoning": { - "supported": true + "supported": true, + "effort_options": [ + "low", + "medium", + "high" + ] } }, - "attachment": false, - "open_weights": true, - "knowledge": "2024-06", - "release_date": "2025-08-05", - "last_updated": "2025-08-05", + "attachment": true, + "open_weights": false, + "release_date": "2026-07-08", + "last_updated": "2026-07-08", "type": "chat" }, { - "id": "deepseek-ai/DeepSeek-V3-0324", - "name": "DeepSeek V3 0324", - "display_name": "DeepSeek V3 0324", + "id": "claude-fable-5", + "name": "Claude Fable 5", + "display_name": "Claude Fable 5", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 75000, - "output": 163840 + "context": 1000000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, - "open_weights": true, - "release_date": "2025-03-24", - "last_updated": "2025-03-24", + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "high", + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "omitted", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Adaptive thinking is always on for Claude Fable 5 and Claude Mythos 5; thinking.type = \"disabled\" is rejected.", + "Manual budget_tokens requests return 400 on Claude Fable 5 and Claude Mythos 5.", + "thinking.display defaults to omitted; set display to summarized to receive readable thinking summaries." + ] + } + }, + "attachment": true, + "open_weights": false, + "knowledge": "2026-01-31", + "release_date": "2026-06-09", + "last_updated": "2026-06-09", "type": "chat" }, { - "id": "deepseek-ai/DeepSeek-V3.1", - "name": "DeepSeek V3.1", - "display_name": "DeepSeek V3.1", + "id": "claude-haiku-5.5", + "name": "Claude Haiku 5.5", + "display_name": "Claude Haiku 5.5", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 75000, - "output": 163840 + "context": 1000000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, "default": true }, - "attachment": false, - "open_weights": true, - "release_date": "2025-08-21", - "last_updated": "2025-08-21", + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" + ] + } + }, + "attachment": true, + "open_weights": false, + "knowledge": "2026-06", + "release_date": "2026-10-07", + "last_updated": "2026-10-07", "type": "chat" }, { - "id": "deepseek-ai/DeepSeek-R1-0528", - "name": "DeepSeek R1 0528", - "display_name": "DeepSeek R1 0528", + "id": "gemini-3.6-flash", + "name": "Gemini 3.6 Flash", + "display_name": "Gemini 3.6 Flash", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 75000, - "output": 163840 + "context": 1000000, + "output": 64000 }, "temperature": true, "tool_call": true, @@ -309456,39 +310025,37 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, + "default_enabled": true, + "mode": "level", + "level": "high", + "level_options": [ + "minimal", + "low", + "medium", + "high" + ], "summaries": true, "visibility": "summary", "continuation": [ - "thinking_blocks" + "thought_signatures" ] } }, - "attachment": false, - "open_weights": true, - "release_date": "2025-05-28", - "last_updated": "2025-05-28", + "attachment": true, + "open_weights": false, + "knowledge": "2026-03", + "release_date": "2026-07-21", + "last_updated": "2026-07-21", "type": "chat" - } - ] - }, - "modelis": { - "id": "modelis", - "name": "Modelis", - "display_name": "Modelis", - "api": "https://modelishub.com/v1", - "doc": "https://modelishub.com/pricing", - "models": [ + }, { - "id": "gemini-2.5-pro", - "name": "Gemini 2.5 Pro", - "display_name": "Gemini 2.5 Pro", + "id": "gemini-3.8-flash", + "name": "Gemini 3.8 Flash", + "display_name": "Gemini 3.8 Flash", "modalities": { "input": [ "text", "image", - "audio", - "video", "pdf" ], "output": [ @@ -309496,8 +310063,8 @@ ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 1000000, + "output": 64000 }, "temperature": true, "tool_call": true, @@ -309509,14 +310076,14 @@ "reasoning": { "supported": true, "default_enabled": true, - "mode": "budget", - "budget": { - "default": -1, - "min": 128, - "max": 32768, - "auto": -1, - "unit": "tokens" - }, + "mode": "level", + "level": "high", + "level_options": [ + "minimal", + "low", + "medium", + "high" + ], "summaries": true, "visibility": "summary", "continuation": [ @@ -309526,28 +310093,29 @@ }, "attachment": true, "open_weights": false, - "knowledge": "2025-01", - "release_date": "2025-06-17", - "last_updated": "2025-06-17", + "release_date": "2026-09-02", + "last_updated": "2026-09-02", "type": "chat" }, { - "id": "deepseek-v4-flash", - "name": "DeepSeek V4 Flash", - "display_name": "DeepSeek V4 Flash", + "id": "gpt-5.6-luna", + "name": "GPT-5.6 Luna", + "display_name": "GPT-5.6 Luna", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 384000 + "context": 1050000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -309558,37 +310126,40 @@ "supported": true, "default_enabled": true, "mode": "effort", - "effort": "high", + "effort": "medium", "effort_options": [ + "none", "low", + "medium", "high", + "xhigh", "max" ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" ], - "notes": [ - "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." - ] + "visibility": "hidden" } }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-04-24", - "last_updated": "2026-04-24", + "attachment": true, + "open_weights": false, + "knowledge": "2026-02-16", + "release_date": "2026-07-09", + "last_updated": "2026-07-09", "type": "chat" }, { - "id": "deepseek-v4-pro", - "name": "DeepSeek V4 Pro", - "display_name": "DeepSeek V4 Pro", + "id": "claude-sonnet-5.5", + "name": "Claude Sonnet 5.5", + "display_name": "Claude Sonnet 5.5", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" @@ -309596,9 +310167,9 @@ }, "limit": { "context": 1000000, - "output": 384000 + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -309606,54 +310177,40 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "high", "effort_options": [ "low", + "medium", "high", + "xhigh", "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." ] } }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-04-24", - "last_updated": "2026-04-24", + "attachment": true, + "open_weights": false, + "knowledge": "2026-06", + "release_date": "2026-09-28", + "last_updated": "2026-09-28", "type": "chat" }, { - "id": "gemini-2.5-flash", - "name": "Gemini 2.5 Flash", - "display_name": "Gemini 2.5 Flash", + "id": "gpt-5-mini", + "name": "GPT-5 Mini", + "display_name": "GPT-5 Mini", "modalities": { "input": [ "text", - "image", - "audio", - "video", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 264000, + "output": 64000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -309663,33 +310220,34 @@ "reasoning": { "supported": true, "default_enabled": true, - "mode": "budget", - "budget": { - "default": -1, - "min": 0, - "max": 24576, - "auto": -1, - "off": 0, - "unit": "tokens" - }, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thought_signatures" - ] + "mode": "effort", + "effort": "medium", + "effort_options": [ + "minimal", + "low", + "medium", + "high" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, "attachment": true, "open_weights": false, - "knowledge": "2025-01", - "release_date": "2025-06-17", - "last_updated": "2025-06-17", + "knowledge": "2024-05-30", + "release_date": "2025-08-07", + "last_updated": "2025-08-07", "type": "chat" }, { - "id": "claude-sonnet-4-6", - "name": "Claude Sonnet 4.6", - "display_name": "Claude Sonnet 4.6", + "id": "claude-opus-4.7", + "name": "Claude Opus 4.7", + "display_name": "Claude Opus 4.7", "modalities": { "input": [ "text", @@ -309701,10 +310259,10 @@ ] }, "limit": { - "context": 1000000, - "output": 64000 + "context": 200000, + "output": 32000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -309714,39 +310272,37 @@ "reasoning": { "supported": true, "default_enabled": false, - "mode": "mixed", - "budget": { - "min": 1024, - "unit": "tokens" - }, + "mode": "effort", "effort": "high", "effort_options": [ "low", "medium", "high", + "xhigh", "max" ], "interleaved": true, "summaries": true, - "visibility": "summary", + "visibility": "omitted", "continuation": [ "thinking_blocks" ], "notes": [ - "Anthropic recommends adaptive thinking with effort for Claude 4.6; budget_tokens remains a deprecated compatibility path.", - "Anthropic API defaults effort to high; lower effort levels should be chosen per workload." + "Claude Opus 4.7 and newer Opus models require thinking.type = \"adaptive\" to enable thinking explicitly.", + "Manual budget_tokens requests return 400 on Claude Opus 4.7 and newer adaptive-only Opus models.", + "task_budget is separate from thinking control and should not be treated as a thinking budget." ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-02-17", - "last_updated": "2026-03-13", + "knowledge": "2026-01-31", + "release_date": "2026-04-16", + "last_updated": "2026-04-16", "type": "chat" }, { - "id": "claude-opus-4-8", + "id": "claude-opus-4.8", "name": "Claude Opus 4.8", "display_name": "Claude Opus 4.8", "modalities": { @@ -309760,8 +310316,8 @@ ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 200000, + "output": 64000 }, "temperature": false, "tool_call": true, @@ -309803,9 +310359,9 @@ "type": "chat" }, { - "id": "claude-fable-5", - "name": "Claude Fable 5", - "display_name": "Claude Fable 5", + "id": "gpt-5.6-sol", + "name": "GPT-5.6 Sol", + "display_name": "GPT-5.6 Sol", "modalities": { "input": [ "text", @@ -309817,7 +310373,7 @@ ] }, "limit": { - "context": 1000000, + "context": 1050000, "output": 128000 }, "temperature": false, @@ -309831,76 +310387,40 @@ "supported": true, "default_enabled": true, "mode": "effort", - "effort": "high", + "effort": "medium", "effort_options": [ + "none", "low", "medium", "high", "xhigh", "max" ], - "interleaved": true, - "summaries": true, - "visibility": "omitted", - "continuation": [ - "thinking_blocks" + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" ], - "notes": [ - "Adaptive thinking is always on for Claude Fable 5 and Claude Mythos 5; thinking.type = \"disabled\" is rejected.", - "Manual budget_tokens requests return 400 on Claude Fable 5 and Claude Mythos 5.", - "thinking.display defaults to omitted; set display to summarized to receive readable thinking summaries." - ] + "visibility": "hidden" } }, "attachment": true, "open_weights": false, - "knowledge": "2026-01-31", - "release_date": "2026-06-09", - "last_updated": "2026-06-09", - "type": "chat" - }, - { - "id": "qwen/qwen3.7-max", - "name": "Qwen3.7 Max", - "display_name": "Qwen3.7 Max", - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 1000000, - "output": 131072 - }, - "temperature": true, - "tool_call": true, - "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true - } - }, - "attachment": false, - "open_weights": false, - "release_date": "2026-05-21", - "last_updated": "2026-05-21", + "knowledge": "2026-02-16", + "release_date": "2026-07-09", + "last_updated": "2026-07-09", "type": "chat" }, { - "id": "qwen/qwen3.7-plus", - "name": "Qwen3.7 Plus", - "display_name": "Qwen3.7 Plus", + "id": "claude-opus-5.5", + "name": "Claude Opus 5.5", + "display_name": "Claude Opus 5.5", "modalities": { "input": [ "text", "image", - "video" + "pdf" ], "output": [ "text" @@ -309908,9 +310428,9 @@ }, "limit": { "context": 1000000, - "output": 64000 + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -309918,29 +310438,26 @@ }, "extra_capabilities": { "reasoning": { - "supported": true + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" + ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-04", - "release_date": "2026-06-02", - "last_updated": "2026-06-02", + "knowledge": "2026-06", + "release_date": "2026-09-22", + "last_updated": "2026-09-22", "type": "chat" - } - ] - }, - "infer": { - "id": "infer", - "name": "Infer by Flow7", - "display_name": "Infer by Flow7", - "api": "https://infer.flow7.org/v1", - "doc": "https://infer.flow7.org/opencode", - "models": [ + }, { - "id": "infer/gpt-5.6-sol:official", - "name": "GPT-5.6 Sol (Official API)", - "display_name": "GPT-5.6 Sol (Official API)", + "id": "mai-code-1.1-flash", + "name": "MAI-Code-1.1-Flash", + "display_name": "MAI-Code-1.1-Flash", "modalities": { "input": [ "text", @@ -309952,7 +310469,7 @@ ] }, "limit": { - "context": 271999, + "context": 256000, "output": 128000 }, "temperature": false, @@ -309964,26 +310481,22 @@ "extra_capabilities": { "reasoning": { "effort_options": [ - "none", "low", "medium", - "high", - "xhigh", - "max" + "high" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-02-16", - "release_date": "2026-07-09", - "last_updated": "2026-07-09", + "release_date": "2026-08-11", + "last_updated": "2026-08-11", "type": "chat" }, { - "id": "infer/gpt-6-astra:official", - "name": "GPT-6 Astra (Official API)", - "display_name": "GPT-6 Astra (Official API)", + "id": "gpt-5.5", + "name": "GPT-5.5", + "display_name": "GPT-5.5", "modalities": { "input": [ "text", @@ -309995,7 +310508,7 @@ ] }, "limit": { - "context": 271999, + "context": 1050000, "output": 128000 }, "temperature": false, @@ -310006,35 +310519,36 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", "effort_options": [ "low", "medium", "high", - "xhigh", - "max" - ] + "xhigh" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, "attachment": true, "open_weights": false, - "knowledge": "2026-04-30", - "release_date": "2026-09-04", - "last_updated": "2026-09-04", + "knowledge": "2025-12-01", + "release_date": "2026-04-23", + "last_updated": "2026-04-23", "type": "chat" - } - ] - }, - "ovhcloud": { - "id": "ovhcloud", - "name": "OVHcloud AI Endpoints", - "display_name": "OVHcloud AI Endpoints", - "api": "https://oai.endpoints.kepler.ai.cloud.ovh.net/v1", - "doc": "https://www.ovhcloud.com/en/public-cloud/ai-endpoints/catalog//", - "models": [ + }, { - "id": "qwen2.5-vl-72b-instruct", - "name": "Qwen2.5-VL-72B-Instruct", - "display_name": "Qwen2.5-VL-72B-Instruct", + "id": "gemini-3.7-flash", + "name": "Gemini 3.7 Flash", + "display_name": "Gemini 3.7 Flash", "modalities": { "input": [ "text", @@ -310045,85 +310559,119 @@ ] }, "limit": { - "context": 32768, - "output": 32768 + "context": 1000000, + "output": 64000 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "level", + "level": "high", + "level_options": [ + "minimal", + "low", + "medium", + "high" + ], + "summaries": true, + "visibility": "summary", + "continuation": [ + "thought_signatures" + ] + } }, "attachment": true, - "open_weights": true, - "knowledge": "2024-04", - "release_date": "2025-01-26", - "last_updated": "2025-01-26", + "open_weights": false, + "knowledge": "2026-03", + "release_date": "2026-08-13", + "last_updated": "2026-08-13", "type": "chat" }, { - "id": "qwen3.5-9b", - "name": "Qwen3.5-9B", - "display_name": "Qwen3.5-9B", + "id": "claude-sonnet-4.6", + "name": "Claude Sonnet 4.6", + "display_name": "Claude Sonnet 4.6", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 200000, + "output": 32000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": false, + "mode": "mixed", + "budget": { + "min": 1024, + "unit": "tokens" + }, + "effort": "high", + "effort_options": [ + "low", + "medium", + "high", + "max" + ], "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ "thinking_blocks" ], - "effort_options": [ - "none", - "low", - "medium", - "high" + "notes": [ + "Anthropic recommends adaptive thinking with effort for Claude 4.6; budget_tokens remains a deprecated compatibility path.", + "Anthropic API defaults effort to high; lower effort levels should be chosen per workload." ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-02-23", - "last_updated": "2026-02-23", + "open_weights": false, + "knowledge": "2025-08-31", + "release_date": "2026-02-17", + "last_updated": "2026-03-13", "type": "chat" }, { - "id": "qwen3.8-27b", - "name": "Qwen3.8-27B", - "display_name": "Qwen3.8-27B", + "id": "claude-opus-5", + "name": "Claude Opus 5", + "display_name": "Claude Opus 5", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 1000000, + "output": 64000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -310131,36 +310679,39 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, "effort_options": [ - "none", - "minimal", "low", "medium", - "high" + "high", + "xhigh", + "max" ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-08-14", - "last_updated": "2026-08-14", + "open_weights": false, + "knowledge": "2026-05", + "release_date": "2026-07-24", + "last_updated": "2026-07-24", "type": "chat" }, { - "id": "gpt-oss-20b", - "name": "gpt-oss-20b", - "display_name": "gpt-oss-20b", + "id": "grok-4.6", + "name": "Grok 4.6", + "display_name": "Grok 4.6", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 131072 + "context": 500000, + "output": 128000 }, "temperature": true, "tool_call": true, @@ -310174,91 +310725,37 @@ "effort_options": [ "low", "medium", - "high" + "high", + "xhigh" ] } }, - "attachment": false, - "open_weights": true, - "knowledge": "2024-06", - "release_date": "2025-08-05", - "last_updated": "2025-08-05", - "type": "chat" - }, - { - "id": "meta-llama-3_3-70b-instruct", - "name": "Meta-Llama-3_3-70B-Instruct", - "display_name": "Meta-Llama-3_3-70B-Instruct", - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 131072 - }, - "temperature": true, - "tool_call": true, - "reasoning": { - "supported": false - }, - "attachment": false, - "open_weights": true, - "knowledge": "2023-12", - "release_date": "2024-12-06", - "last_updated": "2024-12-06", - "type": "chat" - }, - { - "id": "mistral-small-3.2-24b-instruct-2506", - "name": "Mistral-Small-3.2-24B-Instruct-2506", - "display_name": "Mistral-Small-3.2-24B-Instruct-2506", - "modalities": { - "input": [ - "image", - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 131072 - }, - "temperature": true, - "tool_call": true, - "reasoning": { - "supported": false - }, "attachment": true, - "open_weights": true, - "knowledge": "2023-10-31", - "release_date": "2025-06-20", - "last_updated": "2025-06-20", + "open_weights": false, + "knowledge": "2026-02-01", + "release_date": "2026-08-12", + "last_updated": "2026-08-12", "type": "chat" }, { - "id": "gpt-oss-120b", - "name": "gpt-oss-120b", - "display_name": "gpt-oss-120b", + "id": "gpt-6-luna", + "name": "GPT-6 Luna", + "display_name": "GPT-6 Luna", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 131072 + "context": 1050000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -310266,25 +310763,36 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, "effort_options": [ + "none", "low", "medium", - "high" + "high", + "xhigh", + "max" ] } }, - "attachment": false, - "open_weights": true, - "knowledge": "2024-06", - "release_date": "2025-08-05", - "last_updated": "2025-08-05", + "attachment": true, + "open_weights": false, + "knowledge": "2026-05-18", + "release_date": "2026-09-22", + "last_updated": "2026-09-22", "type": "chat" - }, + } + ] + }, + "siliconflow-com": { + "id": "siliconflow-com", + "name": "SiliconFlow", + "display_name": "SiliconFlow", + "api": "https://api.siliconflow.com/v1", + "doc": "https://cloud.siliconflow.com/models", + "models": [ { - "id": "qwen3guard-gen-0.6b", - "name": "Qwen3Guard-Gen-0.6B", - "display_name": "Qwen3Guard-Gen-0.6B", + "id": "openai/gpt-oss-120b", + "name": "openai/gpt-oss-120b", + "display_name": "openai/gpt-oss-120b", "modalities": { "input": [ "text" @@ -310294,52 +310802,31 @@ ] }, "limit": { - "context": 32768, - "output": 16384 + "context": 131000, + "output": 8000 }, "temperature": true, "tool_call": false, "reasoning": { - "supported": false - }, - "attachment": false, - "open_weights": true, - "release_date": "2026-01-22", - "last_updated": "2026-01-22", - "type": "chat" - }, - { - "id": "mistral-nemo-instruct-2407", - "name": "Mistral-Nemo-Instruct-2407", - "display_name": "Mistral-Nemo-Instruct-2407", - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 65536, - "output": 65536 + "supported": true, + "default": true }, - "temperature": true, - "tool_call": true, - "reasoning": { - "supported": false + "extra_capabilities": { + "reasoning": { + "supported": true + } }, "attachment": false, "open_weights": true, - "knowledge": "2024-07", - "release_date": "2024-07-18", - "last_updated": "2024-07-18", + "knowledge": "2024-06", + "release_date": "2025-08-05", + "last_updated": "2025-08-05", "type": "chat" }, { - "id": "qwen3guard-gen-8b", - "name": "Qwen3Guard-Gen-8B", - "display_name": "Qwen3Guard-Gen-8B", + "id": "openai/gpt-oss-20b", + "name": "openai/gpt-oss-20b", + "display_name": "openai/gpt-oss-20b", "modalities": { "input": [ "text" @@ -310349,24 +310836,30 @@ ] }, "limit": { - "context": 32768, - "output": 16384 + "context": 131000, + "output": 8000 }, "temperature": true, "tool_call": false, "reasoning": { - "supported": false + "supported": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true + } }, "attachment": false, "open_weights": true, - "release_date": "2026-01-22", - "last_updated": "2026-01-22", + "knowledge": "2024-06", + "release_date": "2025-08-05", + "last_updated": "2025-08-05", "type": "chat" }, { - "id": "qwen3.6-27b", - "name": "Qwen3.6-27B", - "display_name": "Qwen3.6-27B", + "id": "Qwen/Qwen3.5-122B-A10B", + "name": "Qwen3.5 122B-A10B", + "display_name": "Qwen3.5 122B-A10B", "modalities": { "input": [ "text", @@ -310378,13 +310871,12 @@ }, "limit": { "context": 262144, - "output": 262144 + "output": 32768 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true + "supported": true }, "extra_capabilities": { "reasoning": { @@ -310394,26 +310886,19 @@ "visibility": "summary", "continuation": [ "thinking_blocks" - ], - "effort_options": [ - "none", - "minimal", - "low", - "medium", - "high" ] } }, "attachment": true, "open_weights": true, - "release_date": "2026-04-22", - "last_updated": "2026-04-22", + "release_date": "2026-02-23", + "last_updated": "2026-02-23", "type": "chat" }, - { - "id": "qwen3.5-397b-a17b", - "name": "Qwen3.5-397B-A17B", - "display_name": "Qwen3.5-397B-A17B", + { + "id": "Qwen/Qwen3.5-27B", + "name": "Qwen3.5 27B", + "display_name": "Qwen3.5 27B", "modalities": { "input": [ "text", @@ -310425,13 +310910,12 @@ }, "limit": { "context": 262144, - "output": 262144 + "output": 32768 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true + "supported": true }, "extra_capabilities": { "reasoning": { @@ -310441,34 +310925,19 @@ "visibility": "summary", "continuation": [ "thinking_blocks" - ], - "effort_options": [ - "none", - "low", - "medium", - "high" ] } }, "attachment": true, "open_weights": true, - "release_date": "2026-02-15", - "last_updated": "2026-02-15", + "release_date": "2026-02-23", + "last_updated": "2026-02-23", "type": "chat" - } - ] - }, - "cloudferro-sherlock": { - "id": "cloudferro-sherlock", - "name": "CloudFerro Sherlock", - "display_name": "CloudFerro Sherlock", - "api": "https://api-sherlock.cloudferro.com/openai/v1/", - "doc": "https://docs.sherlock.cloudferro.com/", - "models": [ + }, { - "id": "zai-org/GLM-5.3", - "name": "GLM-5.3", - "display_name": "GLM-5.3", + "id": "Qwen/Qwen3-Coder-480B-A35B-Instruct", + "name": "Qwen/Qwen3-Coder-480B-A35B-Instruct", + "display_name": "Qwen/Qwen3-Coder-480B-A35B-Instruct", "modalities": { "input": [ "text" @@ -310478,34 +310947,25 @@ ] }, "limit": { - "context": 262144, - "output": 131072 + "context": 262000, + "output": 65536 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "effort_options": [ - "low", - "high" - ] - } + "supported": false }, "attachment": false, "open_weights": true, - "release_date": "2026-08-14", - "last_updated": "2026-08-14", + "knowledge": "2025-04", + "release_date": "2025-07-22", + "last_updated": "2025-07-22", "type": "chat" }, { - "id": "speakleash/Bielik-11B-v3.0-Instruct", - "name": "Bielik 11B v3.0 Instruct", - "display_name": "Bielik 11B v3.0 Instruct", + "id": "Qwen/Qwen2.5-7B-Instruct", + "name": "Qwen/Qwen2.5-7B-Instruct", + "display_name": "Qwen/Qwen2.5-7B-Instruct", "modalities": { "input": [ "text" @@ -310515,8 +310975,8 @@ ] }, "limit": { - "context": 32000, - "output": 32000 + "context": 33000, + "output": 4000 }, "temperature": true, "tool_call": true, @@ -310525,65 +310985,54 @@ }, "attachment": false, "open_weights": true, - "knowledge": "2025-03", - "release_date": "2025-03-13", - "last_updated": "2025-03-13", + "release_date": "2024-09-19", + "last_updated": "2024-09-19", "type": "chat" }, { - "id": "openai/gpt-oss-120b", - "name": "OpenAI GPT OSS 120B", - "display_name": "OpenAI GPT OSS 120B", + "id": "Qwen/Qwen3-VL-32B-Instruct", + "name": "Qwen/Qwen3-VL-32B-Instruct", + "display_name": "Qwen/Qwen3-VL-32B-Instruct", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 131000, - "output": 131000 + "context": 262000, + "output": 32768 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "effort_options": [ - "low", - "medium", - "high" - ] - } + "supported": false }, - "attachment": false, + "attachment": true, "open_weights": true, - "knowledge": "2024-06", - "release_date": "2025-08-05", - "last_updated": "2025-08-05", + "release_date": "2025-10-21", + "last_updated": "2025-11-25", "type": "chat" }, { - "id": "MiniMaxAI/MiniMax-M2.5", - "name": "MiniMax-M2.5", - "display_name": "MiniMax-M2.5", + "id": "Qwen/Qwen3-VL-30B-A3B-Thinking", + "name": "Qwen/Qwen3-VL-30B-A3B-Thinking", + "display_name": "Qwen/Qwen3-VL-30B-A3B-Thinking", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 196000, - "output": 16000 + "context": 262000, + "output": 32768 }, "temperature": true, "tool_call": true, @@ -310593,20 +311042,25 @@ }, "extra_capabilities": { "reasoning": { - "supported": true + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] } }, - "attachment": false, + "attachment": true, "open_weights": true, - "knowledge": "2026-01", - "release_date": "2026-02-12", - "last_updated": "2026-02-12", + "release_date": "2025-10-11", + "last_updated": "2025-11-25", "type": "chat" }, { - "id": "meta-llama/Llama-3.3-70B-Instruct", - "name": "Llama 3.3 70B Instruct", - "display_name": "Llama 3.3 70B Instruct", + "id": "Qwen/Qwen2.5-72B-Instruct", + "name": "Qwen/Qwen2.5-72B-Instruct", + "display_name": "Qwen/Qwen2.5-72B-Instruct", "modalities": { "input": [ "text" @@ -310616,8 +311070,8 @@ ] }, "limit": { - "context": 70000, - "output": 70000 + "context": 33000, + "output": 4000 }, "temperature": true, "tool_call": true, @@ -310626,24 +311080,14 @@ }, "attachment": false, "open_weights": true, - "knowledge": "2024-10-09", - "release_date": "2024-12-06", - "last_updated": "2024-12-06", + "release_date": "2024-09-18", + "last_updated": "2025-11-25", "type": "chat" - } - ] - }, - "synthetic": { - "id": "synthetic", - "name": "Synthetic", - "display_name": "Synthetic", - "api": "https://api.synthetic.new/openai/v1", - "doc": "https://synthetic.new/pricing", - "models": [ + }, { - "id": "hf:nvidia/NVIDIA-Nemotron-3-Super-120B-A12B-NVFP4", - "name": "Nemotron 3 Super 120B A12B", - "display_name": "Nemotron 3 Super 120B A12B", + "id": "Qwen/Qwen3-14B", + "name": "Qwen/Qwen3-14B", + "display_name": "Qwen/Qwen3-14B", "modalities": { "input": [ "text" @@ -310653,8 +311097,8 @@ ] }, "limit": { - "context": 262144, - "output": 65536 + "context": 131000, + "output": 32768 }, "temperature": true, "tool_call": true, @@ -310670,38 +311114,30 @@ "visibility": "summary", "continuation": [ "thinking_blocks" - ], - "effort_options": [ - "none", - "low", - "medium", - "high" ] } }, "attachment": false, "open_weights": true, - "knowledge": "2025-06", - "release_date": "2026-03-11", - "last_updated": "2026-03-11", + "release_date": "2025-04-30", + "last_updated": "2025-11-25", "type": "chat" }, { - "id": "hf:Qwen/Qwen3.6-27B", - "name": "Qwen3.6 27B", - "display_name": "Qwen3.6 27B", + "id": "Qwen/Qwen3-8B", + "name": "Qwen/Qwen3-8B", + "display_name": "Qwen/Qwen3-8B", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 65536 + "context": 131000, + "output": 32768 }, "temperature": true, "tool_call": true, @@ -310717,25 +311153,19 @@ "visibility": "summary", "continuation": [ "thinking_blocks" - ], - "effort_options": [ - "none", - "low", - "medium", - "high" ] } }, - "attachment": true, + "attachment": false, "open_weights": true, - "release_date": "2026-04-22", - "last_updated": "2026-04-22", + "release_date": "2025-04-29", + "last_updated": "2025-04-29", "type": "chat" }, { - "id": "hf:deepseek-ai/DeepSeek-V4.1-Flash", - "name": "DeepSeek V4.1 Flash", - "display_name": "DeepSeek V4.1 Flash", + "id": "Qwen/Qwen3.5-9B", + "name": "Qwen/Qwen3.5-9B", + "display_name": "Qwen/Qwen3.5-9B", "modalities": { "input": [ "text", @@ -310746,39 +311176,35 @@ ] }, "limit": { - "context": 524288, - "output": 65536 + "context": 262144, + "output": 32768 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true + "supported": true }, "extra_capabilities": { "reasoning": { "supported": true, "interleaved": true, - "effort_options": [ - "none", - "low", - "high", - "xhigh", - "max" + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, "attachment": true, "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-09-10", - "last_updated": "2026-09-10", + "release_date": "2026-02-23", + "last_updated": "2026-02-23", "type": "chat" }, { - "id": "hf:deepseek-ai/DeepSeek-V4.1-Flash-1017", - "name": "DeepSeek V4.1 Flash 1017", - "display_name": "DeepSeek V4.1 Flash 1017", + "id": "Qwen/Qwen3-VL-32B-Thinking", + "name": "Qwen/Qwen3-VL-32B-Thinking", + "display_name": "Qwen/Qwen3-VL-32B-Thinking", "modalities": { "input": [ "text", @@ -310789,8 +311215,8 @@ ] }, "limit": { - "context": 524288, - "output": 65536 + "context": 262000, + "output": 32768 }, "temperature": true, "tool_call": true, @@ -310802,40 +311228,36 @@ "reasoning": { "supported": true, "interleaved": true, - "effort_options": [ - "none", - "low", - "high", - "xhigh", - "max" + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, "attachment": true, "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-10-17", - "last_updated": "2026-10-17", + "release_date": "2025-10-21", + "last_updated": "2025-11-25", "type": "chat" }, { - "id": "hf:moonshotai/Kimi-K2.7-Code", - "name": "Kimi K2.7 Code", - "display_name": "Kimi K2.7 Code", + "id": "Qwen/Qwen3.8-2.4T-A95B", + "name": "Qwen3.8 2.4T A95B", + "display_name": "Qwen3.8 2.4T A95B", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 65536 + "context": 1049000, + "output": 131000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -310843,31 +311265,47 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "effort_options": [ - "low", - "medium", - "high" - ] + "supported": true } }, + "attachment": false, + "open_weights": true, + "release_date": "2026-08-12", + "last_updated": "2026-08-12", + "type": "chat" + }, + { + "id": "Qwen/Qwen3-VL-30B-A3B-Instruct", + "name": "Qwen/Qwen3-VL-30B-A3B-Instruct", + "display_name": "Qwen/Qwen3-VL-30B-A3B-Instruct", + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 262000, + "output": 32768 + }, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": false + }, "attachment": true, "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-06-12", - "last_updated": "2026-06-12", + "release_date": "2025-10-04", + "last_updated": "2025-10-04", "type": "chat" }, { - "id": "hf:moonshotai/Kimi-K3", - "name": "Kimi K3", - "display_name": "Kimi K3", + "id": "Qwen/Qwen3.6-27B", + "name": "Qwen3.6 27B", + "display_name": "Qwen3.6 27B", "modalities": { "input": [ "text", @@ -310878,14 +311316,13 @@ ] }, "limit": { - "context": 524288, - "output": 65536 + "context": 262144, + "output": 32768 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true + "supported": true }, "extra_capabilities": { "reasoning": { @@ -310895,24 +311332,19 @@ "visibility": "summary", "continuation": [ "thinking_blocks" - ], - "effort_options": [ - "low", - "high", - "max" ] } }, "attachment": true, "open_weights": true, - "release_date": "2026-07-16", - "last_updated": "2026-07-16", + "release_date": "2026-04-22", + "last_updated": "2026-04-22", "type": "chat" }, { - "id": "hf:openai/gpt-oss-120b", - "name": "GPT OSS 120B", - "display_name": "GPT OSS 120B", + "id": "Qwen/Qwen3-Coder-30B-A3B-Instruct", + "name": "Qwen/Qwen3-Coder-30B-A3B-Instruct", + "display_name": "Qwen/Qwen3-Coder-30B-A3B-Instruct", "modalities": { "input": [ "text" @@ -310922,37 +311354,25 @@ ] }, "limit": { - "context": 131072, + "context": 262000, "output": 65536 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "effort_options": [ - "none", - "low", - "medium", - "high" - ] - } + "supported": false }, "attachment": false, "open_weights": true, - "knowledge": "2024-06", - "release_date": "2025-08-05", - "last_updated": "2025-08-05", + "knowledge": "2025-04", + "release_date": "2025-07-31", + "last_updated": "2025-07-31", "type": "chat" }, { - "id": "hf:zai-org/GLM-5.3-Flash", - "name": "GLM-5.3-Flash", - "display_name": "GLM-5.3-Flash", + "id": "Qwen/Qwen3-VL-8B-Instruct", + "name": "Qwen/Qwen3-VL-8B-Instruct", + "display_name": "Qwen/Qwen3-VL-8B-Instruct", "modalities": { "input": [ "text", @@ -310963,41 +311383,24 @@ ] }, "limit": { - "context": 524288, - "output": 65536 + "context": 262000, + "output": 32768 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "effort_options": [ - "low", - "high", - "max" - ] - } + "supported": false }, "attachment": true, "open_weights": true, - "release_date": "2026-08-26", - "last_updated": "2026-08-26", + "release_date": "2025-10-15", + "last_updated": "2025-11-25", "type": "chat" }, { - "id": "hf:zai-org/GLM-4.7-Flash", - "name": "GLM-4.7-Flash", - "display_name": "GLM-4.7-Flash", + "id": "Qwen/Qwen3-30B-A3B-Instruct-2507", + "name": "Qwen/Qwen3-30B-A3B-Instruct-2507", + "display_name": "Qwen/Qwen3-30B-A3B-Instruct-2507", "modalities": { "input": [ "text" @@ -311007,43 +311410,24 @@ ] }, "limit": { - "context": 196608, - "output": 65536 + "context": 262000, + "output": 32768 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "effort_options": [ - "none", - "low", - "medium", - "high" - ] - } + "supported": false }, "attachment": false, "open_weights": true, - "knowledge": "2025-04", - "release_date": "2026-01-19", - "last_updated": "2026-01-19", + "release_date": "2025-07-28", + "last_updated": "2025-07-28", "type": "chat" }, { - "id": "hf:zai-org/GLM-5.2", - "name": "GLM-5.2", - "display_name": "GLM-5.2", + "id": "Qwen/Qwen3.5-397B-A17B", + "name": "Qwen3.5 397B-A17B", + "display_name": "Qwen3.5 397B-A17B", "modalities": { "input": [ "text" @@ -311053,14 +311437,13 @@ ] }, "limit": { - "context": 524288, - "output": 65536 + "context": 262144, + "output": 32768 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true + "supported": true }, "extra_capabilities": { "reasoning": { @@ -311070,24 +311453,19 @@ "visibility": "summary", "continuation": [ "thinking_blocks" - ], - "effort_options": [ - "none", - "high", - "xhigh" ] } }, "attachment": false, "open_weights": true, - "release_date": "2026-06-13", - "last_updated": "2026-06-13", + "release_date": "2026-02-15", + "last_updated": "2026-02-15", "type": "chat" }, { - "id": "hf:zai-org/GLM-5.3", - "name": "GLM-5.3", - "display_name": "GLM-5.3", + "id": "Qwen/Qwen3-32B", + "name": "Qwen/Qwen3-32B", + "display_name": "Qwen/Qwen3-32B", "modalities": { "input": [ "text" @@ -311097,8 +311475,8 @@ ] }, "limit": { - "context": 524288, - "output": 65536 + "context": 131000, + "output": 32768 }, "temperature": true, "tool_call": true, @@ -311114,24 +311492,20 @@ "visibility": "summary", "continuation": [ "thinking_blocks" - ], - "effort_options": [ - "low", - "high", - "max" ] } }, "attachment": false, "open_weights": true, - "release_date": "2026-08-14", - "last_updated": "2026-08-14", + "knowledge": "2025-04", + "release_date": "2025-04-28", + "last_updated": "2025-04-28", "type": "chat" }, { - "id": "hf:MiniMaxAI/MiniMax-M3", - "name": "MiniMax-M3", - "display_name": "MiniMax-M3", + "id": "Qwen/Qwen3.6-35B-A3B", + "name": "Qwen3.6 35B-A3B", + "display_name": "Qwen3.6 35B-A3B", "modalities": { "input": [ "text", @@ -311142,14 +311516,13 @@ ] }, "limit": { - "context": 524288, - "output": 65536 + "context": 262144, + "output": 32768 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true + "supported": true }, "extra_capabilities": { "reasoning": { @@ -311159,33 +311532,19 @@ "visibility": "summary", "continuation": [ "thinking_blocks" - ], - "effort_options": [ - "low", - "medium", - "high" ] } }, "attachment": true, "open_weights": true, - "release_date": "2026-06-01", - "last_updated": "2026-06-01", + "release_date": "2026-04-17", + "last_updated": "2026-04-17", "type": "chat" - } - ] - }, - "the-grid-ai": { - "id": "the-grid-ai", - "name": "The Grid AI", - "display_name": "The Grid AI", - "api": "https://api.thegrid.ai/v1", - "doc": "https://thegrid.ai/docs", - "models": [ + }, { - "id": "gpt-sol-latest", - "name": "GPT Sol Latest", - "display_name": "GPT Sol Latest", + "id": "Qwen/Qwen3.5-35B-A3B", + "name": "Qwen3.5 35B-A3B", + "display_name": "Qwen3.5 35B-A3B", "modalities": { "input": [ "text", @@ -311196,14 +311555,13 @@ ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 262144, + "output": 32768 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true + "supported": true }, "extra_capabilities": { "reasoning": { @@ -311213,24 +311571,19 @@ "visibility": "summary", "continuation": [ "thinking_blocks" - ], - "effort_options": [ - "low", - "medium", - "high" ] } }, "attachment": true, - "open_weights": false, - "release_date": "2026-07-06", - "last_updated": "2026-07-06", + "open_weights": true, + "release_date": "2026-02-23", + "last_updated": "2026-02-23", "type": "chat" }, { - "id": "gemini-pro-latest", - "name": "Gemini Pro Latest", - "display_name": "Gemini Pro Latest", + "id": "nex-agi/Nex-N2-Pro", + "name": "Nex-N2-Pro", + "display_name": "Nex-N2-Pro", "modalities": { "input": [ "text", @@ -311241,10 +311594,10 @@ ] }, "limit": { - "context": 1000000, - "output": 65536 + "context": 262144, + "output": 256000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -311258,38 +311611,32 @@ "visibility": "summary", "continuation": [ "thinking_blocks" - ], - "effort_options": [ - "low", - "medium", - "high" ] } }, "attachment": true, - "open_weights": false, - "release_date": "2026-07-06", - "last_updated": "2026-07-06", + "open_weights": true, + "release_date": "2026-06-02", + "last_updated": "2026-06-02", "type": "chat" }, { - "id": "kimi-latest", - "name": "Kimi Latest", - "display_name": "Kimi Latest", + "id": "meituan-longcat/LongCat-2.0", + "name": "LongCat-2.0", + "display_name": "LongCat-2.0", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 943718 + "context": 1049000, + "output": 131072 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -311303,24 +311650,19 @@ "visibility": "summary", "continuation": [ "thinking_blocks" - ], - "effort_options": [ - "low", - "high", - "max" ] } }, - "attachment": true, - "open_weights": false, - "release_date": "2026-07-06", - "last_updated": "2026-07-06", + "attachment": false, + "open_weights": true, + "release_date": "2026-06-30", + "last_updated": "2026-06-30", "type": "chat" }, { - "id": "agent-prime", - "name": "Agent Prime", - "display_name": "Agent Prime", + "id": "stepfun-ai/Step-3.5-Flash", + "name": "stepfun-ai/Step-3.5-Flash", + "display_name": "stepfun-ai/Step-3.5-Flash", "modalities": { "input": [ "text" @@ -311330,8 +311672,38 @@ ] }, "limit": { - "context": 196608, - "output": 131072 + "context": 262000, + "output": 66000 + }, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": true, + "default": true + }, + "attachment": false, + "open_weights": true, + "knowledge": "2025-01", + "release_date": "2026-01-29", + "last_updated": "2026-02-13", + "type": "chat" + }, + { + "id": "moonshotai/Kimi-K2.5", + "name": "moonshotai/Kimi-K2.5", + "display_name": "moonshotai/Kimi-K2.5", + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 262000, + "output": 99560 }, "temperature": true, "tool_call": true, @@ -311347,27 +311719,24 @@ "visibility": "summary", "continuation": [ "thinking_blocks" - ], - "effort_options": [ - "low", - "medium", - "high" ] } }, - "attachment": false, - "open_weights": false, - "release_date": "2026-05-04", - "last_updated": "2026-07-15", + "attachment": true, + "open_weights": true, + "knowledge": "2025-01", + "release_date": "2026-01-27", + "last_updated": "2026-01-27", "type": "chat" }, { - "id": "bytedance-pro-latest", - "name": "ByteDance Pro Latest", - "display_name": "ByteDance Pro Latest", + "id": "moonshotai/Kimi-K2.7-Code", + "name": "Kimi K2.7 Code", + "display_name": "Kimi K2.7 Code", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" @@ -311375,7 +311744,7 @@ }, "limit": { "context": 262144, - "output": 131072 + "output": 99615 }, "temperature": false, "tool_call": true, @@ -311391,35 +311760,32 @@ "visibility": "summary", "continuation": [ "thinking_blocks" - ], - "effort_options": [ - "low", - "medium", - "high" ] } }, - "attachment": false, - "open_weights": false, - "release_date": "2026-07-06", - "last_updated": "2026-07-06", + "attachment": true, + "open_weights": true, + "knowledge": "2025-01", + "release_date": "2026-06-12", + "last_updated": "2026-06-12", "type": "chat" }, { - "id": "text-prime", - "name": "Text Prime", - "display_name": "Text Prime", + "id": "moonshotai/Kimi-K2.6", + "name": "moonshotai/Kimi-K2.6", + "display_name": "moonshotai/Kimi-K2.6", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 196608, - "output": 131072 + "context": 262000, + "output": 99560 }, "temperature": true, "tool_call": true, @@ -311435,24 +311801,20 @@ "visibility": "summary", "continuation": [ "thinking_blocks" - ], - "effort_options": [ - "low", - "medium", - "high" ] } }, - "attachment": false, - "open_weights": false, - "release_date": "2026-02-26", - "last_updated": "2026-07-15", + "attachment": true, + "open_weights": true, + "knowledge": "2025-01", + "release_date": "2026-04-20", + "last_updated": "2026-04-20", "type": "chat" }, { - "id": "code-max", - "name": "Code Max", - "display_name": "Code Max", + "id": "moonshotai/Kimi-K3", + "name": "Kimi K3", + "display_name": "Kimi K3", "modalities": { "input": [ "text", @@ -311463,8 +311825,8 @@ ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 1048576, + "output": 262000 }, "temperature": false, "tool_call": true, @@ -311480,37 +311842,31 @@ "visibility": "summary", "continuation": [ "thinking_blocks" - ], - "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" ] } }, "attachment": true, - "open_weights": false, - "release_date": "2026-05-04", - "last_updated": "2026-07-30", + "open_weights": true, + "release_date": "2026-07-16", + "last_updated": "2026-07-16", "type": "chat" }, { - "id": "code-standard", - "name": "Code Standard", - "display_name": "Code Standard", + "id": "zai-org/GLM-5.3-Flash", + "name": "GLM-5.3-Flash", + "display_name": "GLM-5.3-Flash", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 65536 + "context": 1049000, + "output": 262000 }, "temperature": true, "tool_call": true, @@ -311529,21 +311885,21 @@ ], "effort_options": [ "low", - "medium", - "high" + "high", + "max" ] } }, - "attachment": false, - "open_weights": false, - "release_date": "2026-05-04", - "last_updated": "2026-10-06", + "attachment": true, + "open_weights": true, + "release_date": "2026-08-26", + "last_updated": "2026-08-26", "type": "chat" }, { - "id": "minimax-latest", - "name": "MiniMax Latest", - "display_name": "MiniMax Latest", + "id": "zai-org/GLM-5", + "name": "zai-org/GLM-5", + "display_name": "zai-org/GLM-5", "modalities": { "input": [ "text" @@ -311553,10 +311909,10 @@ ] }, "limit": { - "context": 1000000, - "output": 512000 + "context": 205000, + "output": 131072 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -311570,38 +311926,32 @@ "visibility": "summary", "continuation": [ "thinking_blocks" - ], - "effort_options": [ - "low", - "medium", - "high" ] } }, "attachment": false, - "open_weights": false, - "release_date": "2026-07-06", - "last_updated": "2026-07-06", + "open_weights": true, + "release_date": "2026-02-12", + "last_updated": "2026-02-12", "type": "chat" }, { - "id": "agent-max", - "name": "Agent Max", - "display_name": "Agent Max", + "id": "zai-org/GLM-5.1", + "name": "zai-org/GLM-5.1", + "display_name": "zai-org/GLM-5.1", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 205000, + "output": 131000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -311615,40 +311965,32 @@ "visibility": "summary", "continuation": [ "thinking_blocks" - ], - "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" ] } }, - "attachment": true, - "open_weights": false, - "release_date": "2026-05-04", - "last_updated": "2026-07-30", + "attachment": false, + "open_weights": true, + "release_date": "2026-04-07", + "last_updated": "2026-04-07", "type": "chat" }, { - "id": "claude-opus-latest", - "name": "Claude Opus Latest", - "display_name": "Claude Opus Latest", + "id": "zai-org/GLM-5.2", + "name": "GLM-5.2", + "display_name": "GLM-5.2", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 1049000, + "output": 262000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -311664,24 +312006,21 @@ "thinking_blocks" ], "effort_options": [ - "low", - "medium", "high", - "xhigh", "max" ] } }, - "attachment": true, - "open_weights": false, - "release_date": "2026-07-06", - "last_updated": "2026-07-30", + "attachment": false, + "open_weights": true, + "release_date": "2026-06-13", + "last_updated": "2026-06-13", "type": "chat" }, { - "id": "agent-standard", - "name": "Agent Standard", - "display_name": "Agent Standard", + "id": "zai-org/GLM-5.3", + "name": "GLM-5.3", + "display_name": "GLM-5.3", "modalities": { "input": [ "text" @@ -311691,8 +312030,8 @@ ] }, "limit": { - "context": 128000, - "output": 65536 + "context": 1049000, + "output": 262000 }, "temperature": true, "tool_call": true, @@ -311711,21 +312050,21 @@ ], "effort_options": [ "low", - "medium", - "high" + "high", + "max" ] } }, "attachment": false, - "open_weights": false, - "release_date": "2026-05-04", - "last_updated": "2026-10-06", + "open_weights": true, + "release_date": "2026-08-14", + "last_updated": "2026-08-14", "type": "chat" }, { - "id": "code-prime", - "name": "Code Prime", - "display_name": "Code Prime", + "id": "zai-org/GLM-4.5-Air", + "name": "zai-org/GLM-4.5-Air", + "display_name": "zai-org/GLM-4.5-Air", "modalities": { "input": [ "text" @@ -311735,7 +312074,36 @@ ] }, "limit": { - "context": 196608, + "context": 131000, + "output": 98304 + }, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": false + }, + "attachment": false, + "open_weights": true, + "knowledge": "2025-04", + "release_date": "2025-07-28", + "last_updated": "2025-07-28", + "type": "chat" + }, + { + "id": "zai-org/GLM-5V-Turbo", + "name": "zai-org/GLM-5V-Turbo", + "display_name": "zai-org/GLM-5V-Turbo", + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 200000, "output": 131072 }, "temperature": true, @@ -311752,24 +312120,19 @@ "visibility": "summary", "continuation": [ "thinking_blocks" - ], - "effort_options": [ - "low", - "medium", - "high" ] } }, - "attachment": false, + "attachment": true, "open_weights": false, - "release_date": "2026-05-04", - "last_updated": "2026-07-15", + "release_date": "2026-04-01", + "last_updated": "2026-04-01", "type": "chat" }, { - "id": "deepseek-pro-latest", - "name": "DeepSeek Pro Latest", - "display_name": "DeepSeek Pro Latest", + "id": "tencent/Hy3", + "name": "Hy3", + "display_name": "Hy3", "modalities": { "input": [ "text" @@ -311779,10 +312142,10 @@ ] }, "limit": { - "context": 1000000, - "output": 384000 + "context": 262144, + "output": 99615 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -311790,30 +312153,19 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "effort_options": [ - "low", - "medium", - "high" - ] + "supported": true } }, "attachment": false, - "open_weights": false, + "open_weights": true, "release_date": "2026-07-06", "last_updated": "2026-07-06", "type": "chat" }, { - "id": "text-standard", - "name": "Text Standard", - "display_name": "Text Standard", + "id": "tencent/Hunyuan-A13B-Instruct", + "name": "tencent/Hunyuan-A13B-Instruct", + "display_name": "tencent/Hunyuan-A13B-Instruct", "modalities": { "input": [ "text" @@ -311823,85 +312175,92 @@ ] }, "limit": { - "context": 128000, - "output": 65536 + "context": 131000, + "output": 117964 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { "supported": true, "default": true }, + "attachment": false, + "open_weights": true, + "release_date": "2025-06-30", + "last_updated": "2025-11-25", + "type": "chat" + }, + { + "id": "google/gemma-4-26B-A4B-it", + "name": "Gemma 4 26B A4B IT", + "display_name": "Gemma 4 26B A4B IT", + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 262144, + "output": 99615 + }, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": true + }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "effort_options": [ - "low", - "medium", - "high" - ] + "supported": true } }, - "attachment": false, - "open_weights": false, - "release_date": "2026-02-26", - "last_updated": "2026-10-06", + "attachment": true, + "open_weights": true, + "release_date": "2026-04-02", + "last_updated": "2026-04-02", "type": "chat" }, { - "id": "glm-latest", - "name": "GLM Latest", - "display_name": "GLM Latest", + "id": "google/gemma-4-31B-it", + "name": "Gemma 4 31B IT", + "display_name": "Gemma 4 31B IT", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 262144, + "output": 99615 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true + "supported": true }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "effort_options": [ - "low", - "high", - "max" - ] + "supported": true } }, - "attachment": false, - "open_weights": false, - "release_date": "2026-07-06", - "last_updated": "2026-08-18", + "attachment": true, + "open_weights": true, + "knowledge": "2025-01", + "release_date": "2026-04-02", + "last_updated": "2026-04-02", "type": "chat" }, { - "id": "text-max", - "name": "Text Max", - "display_name": "Text Max", + "id": "google/gemma-4-12B-it", + "name": "Gemma 4 12B IT", + "display_name": "Gemma 4 12B IT", "modalities": { "input": [ "text", @@ -311912,172 +312271,115 @@ ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 262144, + "output": 99615 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" - ] - } + "supported": false }, "attachment": true, - "open_weights": false, - "release_date": "2026-02-26", - "last_updated": "2026-07-30", + "open_weights": true, + "release_date": "2026-06-09", + "last_updated": "2026-06-09", "type": "chat" - } - ] - }, - "agnes": { - "id": "agnes", - "name": "Agnes AI", - "display_name": "Agnes AI", - "api": "https://apihub.agnes-ai.com/v1", - "doc": "https://agnes-ai.com/doc", - "models": [ + }, { - "id": "agnes-3.0-flash", - "name": "Agnes 3.0 Flash", - "display_name": "Agnes 3.0 Flash", + "id": "ByteDance-Seed/Seed-OSS-36B-Instruct", + "name": "ByteDance-Seed/Seed-OSS-36B-Instruct", + "display_name": "ByteDance-Seed/Seed-OSS-36B-Instruct", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 512000, - "output": 65536 + "context": 262000, + "output": 99560 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true + "supported": true }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] + "supported": true } }, - "attachment": true, - "open_weights": false, - "release_date": "2026-09", - "last_updated": "2026-09", + "attachment": false, + "open_weights": true, + "release_date": "2025-09-04", + "last_updated": "2025-11-25", "type": "chat" }, { - "id": "agnes-2.5-flash", - "name": "Agnes 2.5 Flash", - "display_name": "Agnes 2.5 Flash", + "id": "inclusionAI/Ling-flash-2.0", + "name": "inclusionAI/Ling-flash-2.0", + "display_name": "inclusionAI/Ling-flash-2.0", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 512000, - "output": 65536 + "context": 131000, + "output": 49780 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } + "supported": false }, - "attachment": true, - "open_weights": false, - "release_date": "2026-07", - "last_updated": "2026-07", + "attachment": false, + "open_weights": true, + "release_date": "2025-09-18", + "last_updated": "2025-11-25", "type": "chat" }, { - "id": "agnes-2.5-pro-alpha", - "name": "Agnes 2.5 Pro Alpha", - "display_name": "Agnes 2.5 Pro Alpha", + "id": "MiniMaxAI/MiniMax-M2.5", + "name": "MiniMaxAI/MiniMax-M2.5", + "display_name": "MiniMaxAI/MiniMax-M2.5", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 65536 + "context": 197000, + "output": 131000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true + "supported": true }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] + "supported": true } }, - "attachment": true, + "attachment": false, "open_weights": true, - "release_date": "2026-07-24", - "last_updated": "2026-07-24", + "release_date": "2026-02-12", + "last_updated": "2026-02-12", "type": "chat" }, { - "id": "agnes-2.0-flash", - "name": "Agnes 2.0 Flash", - "display_name": "Agnes 2.0 Flash", + "id": "MiniMaxAI/MiniMax-M3", + "name": "MiniMax-M3", + "display_name": "MiniMax-M3", "modalities": { "input": [ "text", @@ -312088,8 +312390,8 @@ ] }, "limit": { - "context": 512000, - "output": 65536 + "context": 1048576, + "output": 131000 }, "temperature": true, "tool_call": true, @@ -312109,120 +312411,84 @@ } }, "attachment": true, - "open_weights": false, - "release_date": "2026-05-25", - "last_updated": "2026-05-25", + "open_weights": true, + "release_date": "2026-06-01", + "last_updated": "2026-06-01", "type": "chat" - } - ] - }, - "lowrouter": { - "id": "lowrouter", - "name": "LowRouter", - "display_name": "LowRouter", - "api": "https://api.lowrouter.ai/v1", - "doc": "https://lowrouter.ai/docs/models/available", - "models": [ + }, { - "id": "auto/openai/gpt-5", - "name": "GPT-5", - "display_name": "GPT-5", + "id": "deepseek-ai/DeepSeek-V3.1-Terminus", + "name": "deepseek-ai/DeepSeek-V3.1-Terminus", + "display_name": "deepseek-ai/DeepSeek-V3.1-Terminus", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 164000, + "output": 8192 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", - "effort_options": [ - "minimal", - "low", - "medium", - "high" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" - } - }, - "attachment": true, - "open_weights": false, - "knowledge": "2024-09-30", - "release_date": "2025-08-07", - "last_updated": "2025-08-07", + "attachment": false, + "open_weights": true, + "release_date": "2025-09-29", + "last_updated": "2025-11-25", "type": "chat" }, { - "id": "auto/openai/gpt-4o-mini", - "name": "GPT-4o mini", - "display_name": "GPT-4o mini", + "id": "deepseek-ai/DeepSeek-V3.2-Exp", + "name": "deepseek-ai/DeepSeek-V3.2-Exp", + "display_name": "deepseek-ai/DeepSeek-V3.2-Exp", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 16384 + "context": 164000, + "output": 65536 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": true, - "open_weights": false, - "knowledge": "2023-09", - "release_date": "2024-07-18", - "last_updated": "2024-07-18", + "attachment": false, + "open_weights": true, + "release_date": "2025-10-10", + "last_updated": "2025-11-25", "type": "chat" }, { - "id": "auto/openai/gpt-5.5", - "name": "GPT-5.5", - "display_name": "GPT-5.5", + "id": "deepseek-ai/DeepSeek-V4-Pro", + "name": "DeepSeek V4 Pro", + "display_name": "DeepSeek V4 Pro", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 1000000, + "output": 384000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -312233,128 +312499,112 @@ "supported": true, "default_enabled": true, "mode": "effort", - "effort": "medium", + "effort": "high", "effort_options": [ "low", - "medium", "high", - "xhigh" + "max" ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ], - "visibility": "hidden" + "notes": [ + "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." + ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-12-01", - "release_date": "2026-04-23", - "last_updated": "2026-04-23", + "attachment": false, + "open_weights": true, + "knowledge": "2025-05", + "release_date": "2026-04-24", + "last_updated": "2026-04-24", "type": "chat" }, { - "id": "auto/openai/gpt-6-astra", - "name": "GPT-6 Astra", - "display_name": "GPT-6 Astra", + "id": "deepseek-ai/DeepSeek-V3", + "name": "deepseek-ai/DeepSeek-V3", + "display_name": "deepseek-ai/DeepSeek-V3", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 164000, + "output": 8192 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "effort_options": [ - "low", - "medium", - "high", - "xhigh" - ] - } + "supported": false }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-04-30", - "release_date": "2026-09-04", - "last_updated": "2026-09-04", + "attachment": false, + "open_weights": true, + "release_date": "2025-03-24", + "last_updated": "2025-03-24", "type": "chat" }, { - "id": "auto/openai/gpt-5.4", - "name": "GPT-5.4", - "display_name": "GPT-5.4", + "id": "deepseek-ai/DeepSeek-V4-Flash-Vision-Exp", + "name": "DeepSeek V4 Flash Vision Exp", + "display_name": "DeepSeek V4 Flash Vision Exp", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 1000000, + "output": 384000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, + "default_enabled": true, "mode": "effort", - "effort": "none", + "effort": "high", "effort_options": [ - "none", "low", - "medium", "high", - "xhigh" + "max" ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ], - "visibility": "hidden" + "notes": [ + "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." + ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-03-05", - "last_updated": "2026-03-05", + "open_weights": true, + "release_date": "2026-08-21", + "last_updated": "2026-09-01", "type": "chat" }, { - "id": "auto/openai/gpt-oss-20b", - "name": "GPT OSS 20B", - "display_name": "GPT OSS 20B", + "id": "deepseek-ai/DeepSeek-V3.1", + "name": "deepseek-ai/DeepSeek-V3.1", + "display_name": "deepseek-ai/DeepSeek-V3.1", "modalities": { "input": [ "text" @@ -312364,8 +312614,8 @@ ] }, "limit": { - "context": 131072, - "output": 32768 + "context": 164000, + "output": 8192 }, "temperature": true, "tool_call": true, @@ -312373,72 +312623,68 @@ "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "effort_options": [ - "low", - "medium", - "high" - ] - } - }, "attachment": false, "open_weights": true, - "knowledge": "2024-06", - "release_date": "2025-08-05", - "last_updated": "2025-08-05", + "release_date": "2025-08-21", + "last_updated": "2025-08-21", "type": "chat" }, { - "id": "auto/openai/gpt-4o", - "name": "GPT-4o", - "display_name": "GPT-4o", + "id": "deepseek-ai/DeepSeek-R1", + "name": "deepseek-ai/DeepSeek-R1", + "display_name": "deepseek-ai/DeepSeek-R1", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 16384 + "context": 164000, + "output": 8192 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": true, - "open_weights": false, - "knowledge": "2023-09", - "release_date": "2024-05-13", - "last_updated": "2024-08-06", + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } + }, + "attachment": false, + "open_weights": true, + "release_date": "2025-05-28", + "last_updated": "2025-05-28", "type": "chat" }, { - "id": "auto/openai/o3", - "name": "o3", - "display_name": "o3", + "id": "deepseek-ai/DeepSeek-V4-Flash-0731", + "name": "DeepSeek V4 Flash 0731", + "display_name": "DeepSeek V4 Flash 0731", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 100000 + "context": 1000000, + "output": 384000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -312447,43 +312693,38 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", - "effort_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2024-05", - "release_date": "2025-04-16", - "last_updated": "2025-04-16", + "attachment": false, + "open_weights": true, + "knowledge": "2025-05", + "release_date": "2026-07-31", + "last_updated": "2026-07-31", "type": "chat" }, { - "id": "auto/openai/gpt-6-sol", - "name": "GPT-6 Sol", - "display_name": "GPT-6 Sol", + "id": "deepseek-ai/DeepSeek-V4-Pro-0813", + "name": "DeepSeek V4 Pro 0813", + "display_name": "DeepSeek V4 Pro 0813", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 1000000, + "output": 384000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -312491,41 +312732,38 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "none", - "low", - "medium", - "high", - "xhigh" + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-04-20", - "release_date": "2026-09-22", - "last_updated": "2026-09-22", + "attachment": false, + "open_weights": true, + "release_date": "2026-08-13", + "last_updated": "2026-08-22", "type": "chat" }, { - "id": "auto/openai/gpt-5.6-sol", - "name": "GPT-5.6 Sol", - "display_name": "GPT-5.6 Sol", + "id": "deepseek-ai/DeepSeek-V4-Flash", + "name": "DeepSeek V4 Flash", + "display_name": "DeepSeek V4 Flash", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 1000000, + "output": 384000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -312536,50 +312774,47 @@ "supported": true, "default_enabled": true, "mode": "effort", - "effort": "medium", + "effort": "high", "effort_options": [ - "none", "low", - "medium", "high", - "xhigh", "max" ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ], - "visibility": "hidden" + "notes": [ + "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." + ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-02-16", - "release_date": "2026-07-09", - "last_updated": "2026-07-09", + "attachment": false, + "open_weights": true, + "knowledge": "2025-05", + "release_date": "2026-04-24", + "last_updated": "2026-04-24", "type": "chat" }, { - "id": "auto/openai/gpt-5.6-terra", - "name": "GPT-5.6 Terra", - "display_name": "GPT-5.6 Terra", + "id": "deepseek-ai/DeepSeek-V3.2", + "name": "deepseek-ai/DeepSeek-V3.2", + "display_name": "deepseek-ai/DeepSeek-V3.2", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 164000, + "output": 65536 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -312587,36 +312822,27 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", - "effort_options": [ - "none", - "low", - "medium", - "high", - "xhigh", - "max" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + "supported": true } }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-02-16", - "release_date": "2026-07-09", - "last_updated": "2026-07-09", + "attachment": false, + "open_weights": true, + "knowledge": "2024-07", + "release_date": "2025-12-01", + "last_updated": "2025-12-01", "type": "chat" - }, + } + ] + }, + "huggingface": { + "id": "huggingface", + "name": "Hugging Face", + "display_name": "Hugging Face", + "api": "https://router.huggingface.co/v1", + "doc": "https://huggingface.co/docs/inference-providers", + "models": [ { - "id": "auto/openai/gpt-oss-120b", + "id": "openai/gpt-oss-120b", "name": "GPT OSS 120B", "display_name": "GPT OSS 120B", "modalities": { @@ -312655,24 +312881,22 @@ "type": "chat" }, { - "id": "auto/openai/gpt-6-luna", - "name": "GPT-6 Luna", - "display_name": "GPT-6 Luna", + "id": "openai/gpt-oss-20b", + "name": "GPT OSS 20B", + "display_name": "GPT OSS 20B", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 131072, + "output": 32768 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -312680,26 +312904,25 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, "effort_options": [ - "none", "low", "medium", - "high", - "xhigh" + "high" ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-05-18", - "release_date": "2026-09-22", - "last_updated": "2026-09-22", + "attachment": false, + "open_weights": true, + "knowledge": "2024-06", + "release_date": "2025-08-05", + "last_updated": "2025-08-05", "type": "chat" }, { - "id": "auto/openai/gpt-5-mini", - "name": "GPT-5 Mini", - "display_name": "GPT-5 Mini", + "id": "Qwen/Qwen3.5-122B-A10B", + "name": "Qwen3.5 122B-A10B", + "display_name": "Qwen3.5 122B-A10B", "modalities": { "input": [ "text", @@ -312710,10 +312933,10 @@ ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 262144, + "output": 65536 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -312722,50 +312945,38 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", - "effort_options": [ - "minimal", - "low", - "medium", - "high" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2024-05-30", - "release_date": "2025-08-07", - "last_updated": "2025-08-07", + "open_weights": true, + "release_date": "2026-02-23", + "last_updated": "2026-02-23", "type": "chat" }, { - "id": "auto/openai/gpt-5.6-luna", - "name": "GPT-5.6 Luna", - "display_name": "GPT-5.6 Luna", + "id": "Qwen/Qwen3-VL-235B-A22B-Thinking", + "name": "Qwen3 VL 235B A22B Thinking", + "display_name": "Qwen3 VL 235B A22B Thinking", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 131072, + "output": 32768 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -312774,101 +312985,68 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", - "effort_options": [ - "none", - "low", - "medium", - "high", - "xhigh", - "max" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ], - "verbosity": "medium", - "verbosity_options": [ + "effort_options": [ "low", "medium", "high" - ], - "visibility": "hidden" + ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2026-02-16", - "release_date": "2026-07-09", - "last_updated": "2026-07-09", + "open_weights": true, + "knowledge": "2025-03-31", + "release_date": "2025-09-23", + "last_updated": "2025-09-23", "type": "chat" }, { - "id": "auto/openai/gpt-5.4-mini", - "name": "GPT-5.4 mini", - "display_name": "GPT-5.4 mini", + "id": "Qwen/Qwen3-Embedding-4B", + "name": "Qwen 3 Embedding 4B", + "display_name": "Qwen 3 Embedding 4B", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 32768, + "output": 8192 }, - "temperature": true, - "tool_call": true, + "temperature": false, + "tool_call": false, "reasoning": { - "supported": true, - "default": false - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "none", - "effort_options": [ - "none", - "low", - "medium", - "high", - "xhigh" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" - } + "supported": false }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-03-17", - "last_updated": "2026-03-17", - "type": "chat" + "attachment": false, + "open_weights": true, + "knowledge": "2024-12", + "release_date": "2025-06-05", + "last_updated": "2025-06-05", + "type": "embedding" }, { - "id": "auto/openai/gpt-4.1-mini", - "name": "GPT-4.1 mini", - "display_name": "GPT-4.1 mini", + "id": "Qwen/Qwen3-Next-80B-A3B-Instruct", + "name": "Qwen3-Next-80B-A3B-Instruct", + "display_name": "Qwen3-Next-80B-A3B-Instruct", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1047576, + "context": 262144, "output": 32768 }, "temperature": true, @@ -312876,28 +313054,29 @@ "reasoning": { "supported": false }, - "attachment": true, - "open_weights": false, - "knowledge": "2024-04", - "release_date": "2025-04-14", - "last_updated": "2025-04-14", + "attachment": false, + "open_weights": true, + "knowledge": "2025-04", + "release_date": "2025-09", + "last_updated": "2025-09", "type": "chat" }, { - "id": "auto/openai/gpt-oss-safeguard-20b", - "name": "GPT OSS Safeguard 20B", - "display_name": "GPT OSS Safeguard 20B", + "id": "Qwen/Qwen3.5-27B", + "name": "Qwen3.5 27B", + "display_name": "Qwen3.5 27B", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 131072 + "context": 262144, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -312905,46 +313084,55 @@ "supported": true, "default": true }, - "attachment": false, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } + }, + "attachment": true, "open_weights": true, - "release_date": "2025-10-29", - "last_updated": "2025-10-29", + "release_date": "2026-02-23", + "last_updated": "2026-02-23", "type": "chat" }, { - "id": "auto/openai/gpt-4.1", - "name": "GPT-4.1", - "display_name": "GPT-4.1", + "id": "Qwen/Qwen3-Coder-Next", + "name": "Qwen3-Coder-Next", + "display_name": "Qwen3-Coder-Next", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1047576, - "output": 32768 + "context": 262144, + "output": 65536 }, "temperature": true, "tool_call": true, "reasoning": { "supported": false }, - "attachment": true, - "open_weights": false, - "knowledge": "2024-04", - "release_date": "2025-04-14", - "last_updated": "2025-04-14", + "attachment": false, + "open_weights": true, + "knowledge": "2025-09", + "release_date": "2026-02-03", + "last_updated": "2026-02-03", "type": "chat" }, { - "id": "auto/openai/gpt-oss-safeguard-120b", - "name": "GPT OSS Safeguard 120B", - "display_name": "GPT OSS Safeguard 120B", + "id": "Qwen/Qwen3-Coder-480B-A35B-Instruct", + "name": "Qwen3-Coder-480B-A35B-Instruct", + "display_name": "Qwen3-Coder-480B-A35B-Instruct", "modalities": { "input": [ "text" @@ -312954,39 +313142,38 @@ ] }, "limit": { - "context": 131072, - "output": 32768 + "context": 262144, + "output": 65536 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true + "supported": false }, "attachment": false, "open_weights": true, - "release_date": "2025-10-29", - "last_updated": "2025-10-29", + "knowledge": "2025-04", + "release_date": "2025-07-22", + "last_updated": "2025-07-22", "type": "chat" }, { - "id": "auto/openai/gpt-6.1-terra", - "name": "GPT-6.1 Terra", - "display_name": "GPT-6.1 Terra", + "id": "Qwen/Qwen3-8B", + "name": "Qwen3 8B", + "display_name": "Qwen3 8B", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 40960, + "output": 32768 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -312994,26 +313181,31 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ "none", "low", "medium", - "high", - "xhigh" + "high" ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-06-01", - "release_date": "2026-10-26", - "last_updated": "2026-10-26", + "attachment": false, + "open_weights": true, + "release_date": "2025-04-29", + "last_updated": "2025-04-29", "type": "chat" }, { - "id": "auto/openai/gpt-5.2", - "name": "GPT-5.2", - "display_name": "GPT-5.2", + "id": "Qwen/Qwen3.5-9B", + "name": "Qwen3.5 9B", + "display_name": "Qwen3.5 9B", "modalities": { "input": [ "text", @@ -313024,63 +313216,49 @@ ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 262144, + "output": 65536 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "none", - "effort_options": [ - "none", - "low", - "medium", - "high", - "xhigh" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2025-12-11", - "last_updated": "2025-12-11", + "open_weights": true, + "release_date": "2026-02-23", + "last_updated": "2026-02-23", "type": "chat" }, { - "id": "auto/openai/gpt-6.1-sol", - "name": "GPT-6.1 Sol", - "display_name": "GPT-6.1 Sol", + "id": "Qwen/Qwen3.8-2.4T-A95B", + "name": "Qwen3.8 2.4T A95B", + "display_name": "Qwen3.8 2.4T A95B", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 262144, + "output": 131072 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -313088,25 +313266,24 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, "effort_options": [ "low", "medium", - "high", "xhigh" ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-04-30", - "release_date": "2026-09-29", - "last_updated": "2026-09-29", + "attachment": false, + "open_weights": true, + "release_date": "2026-08-12", + "last_updated": "2026-08-12", "type": "chat" }, { - "id": "auto/openai/gpt-5-nano", - "name": "GPT-5 Nano", - "display_name": "GPT-5 Nano", + "id": "Qwen/Qwen3-VL-30B-A3B-Instruct", + "name": "Qwen3 VL 30B A3B Instruct", + "display_name": "Qwen3 VL 30B A3B Instruct", "modalities": { "input": [ "text", @@ -313117,75 +313294,63 @@ ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 131072, + "output": 32768 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", - "effort_options": [ - "minimal", - "low", - "medium", - "high" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" - } + "supported": false }, "attachment": true, - "open_weights": false, - "knowledge": "2024-05-30", - "release_date": "2025-08-07", - "last_updated": "2025-08-07", + "open_weights": true, + "release_date": "2025-10-04", + "last_updated": "2025-10-04", "type": "chat" }, { - "id": "auto/mistralai/ministral-14b-2512", - "name": "Ministral 3 14B", - "display_name": "Ministral 3 14B", + "id": "Qwen/Qwen3-30B-A3B", + "name": "Qwen3 30B A3B", + "display_name": "Qwen3 30B A3B", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 40960, + "output": 16384 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": true, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } + }, + "attachment": false, "open_weights": true, - "release_date": "2025-12-02", - "last_updated": "2025-12-02", + "release_date": "2025-04-28", + "last_updated": "2025-04-28", "type": "chat" }, { - "id": "auto/mistralai/mistral-large-2512", - "name": "Mistral Large 3", - "display_name": "Mistral Large 3", + "id": "Qwen/Qwen3.6-27B", + "name": "Qwen3.6 27B", + "display_name": "Qwen3.6 27B", "modalities": { "input": [ "text", @@ -313197,28 +313362,38 @@ }, "limit": { "context": 262144, - "output": 262144 + "output": 65536 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } }, "attachment": true, "open_weights": true, - "knowledge": "2024-11", - "release_date": "2025-12-02", - "last_updated": "2025-12-02", + "release_date": "2026-04-22", + "last_updated": "2026-04-22", "type": "chat" }, { - "id": "auto/mistralai/ministral-3b-2512", - "name": "Ministral 3 3B", - "display_name": "Ministral 3 3B", + "id": "Qwen/Qwen3-Coder-30B-A3B-Instruct", + "name": "Qwen3-Coder 30B-A3B Instruct", + "display_name": "Qwen3-Coder 30B-A3B Instruct", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" @@ -313226,23 +313401,24 @@ }, "limit": { "context": 262144, - "output": 262144 + "output": 65536 }, "temperature": true, "tool_call": true, "reasoning": { "supported": false }, - "attachment": true, + "attachment": false, "open_weights": true, - "release_date": "2025-12-02", - "last_updated": "2025-12-02", + "knowledge": "2025-04", + "release_date": "2025-07-31", + "last_updated": "2025-07-31", "type": "chat" }, { - "id": "auto/mistralai/mistral-large-4", - "name": "Mistral Large 4", - "display_name": "Mistral Large 4", + "id": "Qwen/Qwen3.5-397B-A17B", + "name": "Qwen3.5-397B-A17B", + "display_name": "Qwen3.5-397B-A17B", "modalities": { "input": [ "text", @@ -313253,8 +313429,8 @@ ] }, "limit": { - "context": 524288, - "output": 262144 + "context": 262144, + "output": 32768 }, "temperature": true, "tool_call": true, @@ -313264,60 +313440,60 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ "none", + "low", + "medium", "high" ] } }, "attachment": true, - "open_weights": false, - "release_date": "2026-10-06", - "last_updated": "2026-10-06", + "open_weights": true, + "knowledge": "2025-04", + "release_date": "2026-02-15", + "last_updated": "2026-02-15", "type": "chat" }, { - "id": "auto/mistralai/mistral-small-2603", - "name": "Mistral Small 4", - "display_name": "Mistral Small 4", + "id": "Qwen/Qwen3-Embedding-8B", + "name": "Qwen 3 Embedding 8B", + "display_name": "Qwen 3 Embedding 8B", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 256000, - "output": 256000 + "context": 32768, + "output": 8192 }, - "temperature": true, - "tool_call": true, + "temperature": false, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "none", - "high" - ] - } + "supported": false }, - "attachment": true, + "attachment": false, "open_weights": true, - "knowledge": "2025-06", - "release_date": "2026-03-16", - "last_updated": "2026-03-16", - "type": "chat" + "knowledge": "2024-12", + "release_date": "2025-06-05", + "last_updated": "2025-06-05", + "type": "embedding" }, { - "id": "auto/mistralai/ministral-8b-2512", - "name": "Ministral 3 8B", - "display_name": "Ministral 3 8B", + "id": "Qwen/Qwen3-VL-235B-A22B-Instruct", + "name": "Qwen3 VL 235B A22B Instruct", + "display_name": "Qwen3 VL 235B A22B Instruct", "modalities": { "input": [ "text", @@ -313328,8 +313504,8 @@ ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 131072, + "output": 32768 }, "temperature": true, "tool_call": true, @@ -313338,14 +313514,15 @@ }, "attachment": true, "open_weights": true, - "release_date": "2025-12-02", - "last_updated": "2025-12-02", + "knowledge": "2025-03-31", + "release_date": "2025-09-23", + "last_updated": "2025-09-23", "type": "chat" }, { - "id": "auto/mistralai/mistral-medium-3.5", - "name": "Mistral Medium 3.5", - "display_name": "Mistral Medium 3.5", + "id": "Qwen/Qwen3.8-27B", + "name": "Qwen3.8 27B", + "display_name": "Qwen3.8 27B", "modalities": { "input": [ "text", @@ -313357,7 +313534,7 @@ }, "limit": { "context": 262144, - "output": 262144 + "output": 32768 }, "temperature": true, "tool_call": true, @@ -313368,21 +313545,22 @@ "extra_capabilities": { "reasoning": { "effort_options": [ - "none", - "high" + "low", + "medium", + "xhigh" ] } }, "attachment": true, "open_weights": true, - "release_date": "2026-04-29", - "last_updated": "2026-04-29", + "release_date": "2026-08-14", + "last_updated": "2026-08-14", "type": "chat" }, { - "id": "auto/mistralai/codestral-2508", - "name": "Codestral 25.08", - "display_name": "Codestral 25.08", + "id": "Qwen/Qwen3-235B-A22B-Instruct-2507", + "name": "Qwen3 235B-A22B Instruct 2507", + "display_name": "Qwen3 235B-A22B Instruct 2507", "modalities": { "input": [ "text" @@ -313392,8 +313570,8 @@ ] }, "limit": { - "context": 128000, - "output": 8192 + "context": 262144, + "output": 16384 }, "temperature": true, "tool_call": true, @@ -313401,39 +313579,26 @@ "supported": false }, "attachment": false, - "open_weights": false, - "release_date": "2025-07-30", - "last_updated": "2025-07-30", + "open_weights": true, + "release_date": "2025-07-21", + "last_updated": "2025-07-21", "type": "chat" - } - ] - }, - "vispark": { - "id": "vispark", - "name": "Vispark", - "display_name": "Vispark", - "api": "https://api.lab.vispark.in/v1", - "doc": "https://lab.vispark.in/#vision", - "models": [ + }, { - "id": "vispark/vision-small", - "name": "Vision Small", - "display_name": "Vision Small", + "id": "Qwen/Qwen3-32B", + "name": "Qwen3 32B", + "display_name": "Qwen3 32B", "modalities": { "input": [ - "text", - "image", - "audio", - "video", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 65536 + "context": 131072, + "output": 16384 }, "temperature": true, "tool_call": true, @@ -313441,30 +313606,39 @@ "supported": true, "default": true }, - "attachment": true, - "open_weights": false, - "release_date": "2024-05-15", - "last_updated": "2026-09", + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } + }, + "attachment": false, + "open_weights": true, + "knowledge": "2025-04", + "release_date": "2025-04-28", + "last_updated": "2025-04-28", "type": "chat" }, { - "id": "vispark/vision-medium", - "name": "Vision Medium", - "display_name": "Vision Medium", + "id": "Qwen/Qwen3.6-35B-A3B", + "name": "Qwen3.6 35B-A3B", + "display_name": "Qwen3.6 35B-A3B", "modalities": { "input": [ "text", - "image", - "audio", - "video", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, + "context": 262144, "output": 65536 }, "temperature": true, @@ -313473,30 +313647,38 @@ "supported": true, "default": true }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } + }, "attachment": true, - "open_weights": false, - "release_date": "2024-05-15", - "last_updated": "2026-09", + "open_weights": true, + "release_date": "2026-04-17", + "last_updated": "2026-04-17", "type": "chat" }, { - "id": "vispark/vision-large", - "name": "Vision Large", - "display_name": "Vision Large", + "id": "Qwen/Qwen3.5-35B-A3B", + "name": "Qwen3.5 35B-A3B", + "display_name": "Qwen3.5 35B-A3B", "modalities": { "input": [ "text", - "image", - "audio", - "video", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, + "context": 262144, "output": 65536 }, "temperature": true, @@ -313505,25 +313687,27 @@ "supported": true, "default": true }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } + }, "attachment": true, - "open_weights": false, - "release_date": "2024-05-15", - "last_updated": "2026-09", + "open_weights": true, + "release_date": "2026-02-23", + "last_updated": "2026-02-23", "type": "chat" - } - ] - }, - "gmicloud": { - "id": "gmicloud", - "name": "GMI Cloud", - "display_name": "GMI Cloud", - "api": "https://api.gmi-serving.com/v1", - "doc": "https://docs.gmicloud.ai/inference-engine/api-reference/llm-api-reference", - "models": [ + }, { - "id": "zai-org/GLM-5-FP8", - "name": "GLM-5", - "display_name": "GLM-5", + "id": "Qwen/Qwen2.5-Coder-32B-Instruct", + "name": "Qwen2.5-Coder-32B-Instruct", + "display_name": "Qwen2.5-Coder-32B-Instruct", "modalities": { "input": [ "text" @@ -313533,36 +313717,24 @@ ] }, "limit": { - "context": 202752, - "output": 131072 + "context": 131072, + "output": 8192 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } + "supported": false }, "attachment": false, "open_weights": true, - "release_date": "2026-02-12", - "last_updated": "2026-02-12", + "release_date": "2024-11-12", + "last_updated": "2024-11-12", "type": "chat" }, { - "id": "zai-org/GLM-5.1-FP8", - "name": "GLM-5.1", - "display_name": "GLM-5.1", + "id": "Qwen/Qwen3-235B-A22B", + "name": "Qwen3 235B-A22B", + "display_name": "Qwen3 235B-A22B", "modalities": { "input": [ "text" @@ -313572,8 +313744,8 @@ ] }, "limit": { - "context": 202752, - "output": 131072 + "context": 40960, + "output": 16384 }, "temperature": true, "tool_call": true, @@ -313594,14 +313766,15 @@ }, "attachment": false, "open_weights": true, - "release_date": "2026-04-07", - "last_updated": "2026-04-07", + "knowledge": "2025-04", + "release_date": "2025-04-28", + "last_updated": "2025-04-28", "type": "chat" }, { - "id": "zai-org/GLM-5.2-FP8", - "name": "GLM-5.2", - "display_name": "GLM-5.2", + "id": "Qwen/Qwen3-235B-A22B-Thinking-2507", + "name": "Qwen3-235B-A22B-Thinking-2507", + "display_name": "Qwen3-235B-A22B-Thinking-2507", "modalities": { "input": [ "text" @@ -313611,8 +313784,8 @@ ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 262144, + "output": 81920 }, "temperature": true, "tool_call": true, @@ -313628,29 +313801,23 @@ "visibility": "summary", "continuation": [ "thinking_blocks" - ], - "effort_options": [ - "high", - "max" ] } }, "attachment": false, "open_weights": true, - "release_date": "2026-06-13", - "last_updated": "2026-06-13", + "knowledge": "2025-04", + "release_date": "2025-07-25", + "last_updated": "2025-07-25", "type": "chat" }, { - "id": "Qwen/Qwen3.8-Max", - "name": "Qwen3.8 Max", - "display_name": "Qwen3.8 Max", + "id": "XiaomiMiMo/MiMo-V2.5", + "name": "MiMo-V2.5", + "display_name": "MiMo-V2.5", "modalities": { "input": [ - "text", - "image", - "video", - "pdf" + "text" ], "output": [ "text" @@ -313668,19 +313835,26 @@ }, "extra_capabilities": { "reasoning": { - "supported": true + "effort_options": [ + "none", + "low", + "medium", + "high", + "xhigh" + ] } }, - "attachment": true, - "open_weights": false, - "release_date": "2026-08-03", - "last_updated": "2026-08-03", + "attachment": false, + "open_weights": true, + "knowledge": "2024-12", + "release_date": "2026-04-22", + "last_updated": "2026-04-22", "type": "chat" }, { - "id": "Qwen/Qwen3.7-Max", - "name": "Qwen3.7 Max", - "display_name": "Qwen3.7 Max", + "id": "XiaomiMiMo/MiMo-V2.5-Pro", + "name": "MiMo-V2.5-Pro", + "display_name": "MiMo-V2.5-Pro", "modalities": { "input": [ "text" @@ -313690,7 +313864,7 @@ ] }, "limit": { - "context": 1000000, + "context": 1048576, "output": 131072 }, "temperature": true, @@ -313701,32 +313875,38 @@ }, "extra_capabilities": { "reasoning": { - "supported": true + "effort_options": [ + "none", + "low", + "medium", + "high", + "xhigh" + ] } }, "attachment": false, - "open_weights": false, - "release_date": "2026-05-21", - "last_updated": "2026-05-21", + "open_weights": true, + "knowledge": "2024-12", + "release_date": "2026-04-22", + "last_updated": "2026-04-22", "type": "chat" }, { - "id": "Qwen/Qwen3.8-Flash", - "name": "Qwen3.8 Flash", - "display_name": "Qwen3.8 Flash", + "id": "thinkingmachines/Inkling-Small", + "name": "Inkling Small", + "display_name": "Inkling Small", "modalities": { "input": [ "text", - "image", - "video" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1048575, - "output": 131072 + "context": 524288, + "output": 1048576 }, "temperature": true, "tool_call": true, @@ -313734,194 +313914,123 @@ "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "supported": true - } - }, "attachment": true, - "open_weights": false, - "release_date": "2026-08-26", - "last_updated": "2026-08-26", + "open_weights": true, + "release_date": "2026-07-30", + "last_updated": "2026-07-30", "type": "chat" }, { - "id": "anthropic/claude-opus-4.8", - "name": "Claude Opus 4.8", - "display_name": "Claude Opus 4.8", + "id": "thinkingmachines/Inkling", + "name": "Inkling", + "display_name": "Inkling", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 1048576, + "output": 1048576 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "high", "effort_options": [ "low", "medium", - "high", - "xhigh", - "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "omitted", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Claude Opus 4.7 and newer Opus models require thinking.type = \"adaptive\" to enable thinking explicitly.", - "Manual budget_tokens requests return 400 on Claude Opus 4.7 and newer adaptive-only Opus models.", - "task_budget is separate from thinking control and should not be treated as a thinking budget." + "high" ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2026-01", - "release_date": "2026-05-28", - "last_updated": "2026-05-28", + "open_weights": true, + "release_date": "2026-07-15", + "last_updated": "2026-07-15", "type": "chat" }, { - "id": "anthropic/claude-opus-4.6", - "name": "Claude Opus 4.6", - "display_name": "Claude Opus 4.6", + "id": "stepfun-ai/Step-3.7-Flash", + "name": "Step 3.7 Flash", + "display_name": "Step 3.7 Flash", "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 409600, - "output": 128000 + "context": 262144, + "output": 256000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "mixed", - "budget": { - "min": 1024, - "unit": "tokens" - }, - "effort": "high", "effort_options": [ "low", "medium", - "high", - "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Anthropic recommends adaptive thinking with effort for Claude 4.6; budget_tokens remains a deprecated compatibility path.", - "Anthropic API defaults effort to high; lower effort levels should be chosen per workload." + "high" ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2025-05-31", - "release_date": "2026-02-05", - "last_updated": "2026-03-13", + "open_weights": true, + "knowledge": "2026-03-01", + "release_date": "2026-05-29", + "last_updated": "2026-05-29", "type": "chat" }, { - "id": "anthropic/claude-sonnet-4.6", - "name": "Claude Sonnet 4.6", - "display_name": "Claude Sonnet 4.6", + "id": "stepfun-ai/Step-3.5-Flash", + "name": "Step 3.5 Flash", + "display_name": "Step 3.5 Flash", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 409600, - "output": 64000 + "context": 262144, + "output": 256000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "mixed", - "budget": { - "min": 1024, - "unit": "tokens" - }, - "effort": "high", - "effort_options": [ - "low", - "medium", - "high", - "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Anthropic recommends adaptive thinking with effort for Claude 4.6; budget_tokens remains a deprecated compatibility path.", - "Anthropic API defaults effort to high; lower effort levels should be chosen per workload." - ] - } + "default": true }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-02-17", - "last_updated": "2026-03-13", + "attachment": false, + "open_weights": true, + "knowledge": "2025-01", + "release_date": "2026-01-29", + "last_updated": "2026-02-13", "type": "chat" }, { - "id": "anthropic/claude-opus-4.7", - "name": "Claude Opus 4.7", - "display_name": "Claude Opus 4.7", + "id": "meta-llama/Llama-Guard-4-12B", + "name": "Llama Guard 4 12B", + "display_name": "Llama Guard 4 12B", "modalities": { "input": [ "text", @@ -313932,150 +314041,93 @@ ] }, "limit": { - "context": 409600, - "output": 128000 + "context": 163840, + "output": 163840 }, - "temperature": false, - "tool_call": true, + "temperature": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": false - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "high", - "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "omitted", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Claude Opus 4.7 and newer Opus models require thinking.type = \"adaptive\" to enable thinking explicitly.", - "Manual budget_tokens requests return 400 on Claude Opus 4.7 and newer adaptive-only Opus models.", - "task_budget is separate from thinking control and should not be treated as a thinking budget." - ] - } + "supported": false }, "attachment": true, - "open_weights": false, - "knowledge": "2026-01-31", - "release_date": "2026-04-16", - "last_updated": "2026-04-16", + "open_weights": true, + "release_date": "2025-04-29", + "last_updated": "2025-04-29", "type": "chat" }, { - "id": "openai/gpt-5.5", - "name": "GPT-5.5", - "display_name": "GPT-5.5", + "id": "meta-llama/Llama-3.1-8B-Instruct", + "name": "Llama-3.1-8B-Instruct", + "display_name": "Llama-3.1-8B-Instruct", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 131072, + "output": 4096 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", - "effort_options": [ - "low", - "medium", - "high", - "xhigh" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" - } + "supported": false }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-12-01", - "release_date": "2026-04-23", - "last_updated": "2026-04-23", + "attachment": false, + "open_weights": true, + "knowledge": "2023-12", + "release_date": "2024-07-23", + "last_updated": "2024-07-23", "type": "chat" }, { - "id": "MiniMaxAI/MiniMax-M3", - "name": "MiniMax-M3", - "display_name": "MiniMax-M3", + "id": "meta-llama/Llama-3.3-70B-Instruct", + "name": "Llama-3.3-70B-Instruct", + "display_name": "Llama-3.3-70B-Instruct", "modalities": { "input": [ - "text", - "image", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 512000 + "context": 131072, + "output": 4096 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true - } + "supported": false }, - "attachment": true, + "attachment": false, "open_weights": true, - "release_date": "2026-06-01", - "last_updated": "2026-06-01", + "knowledge": "2023-12", + "release_date": "2024-12-06", + "last_updated": "2024-12-06", "type": "chat" }, { - "id": "MiniMaxAI/MiniMax-M2.7", - "name": "MiniMax-M2.7", - "display_name": "MiniMax-M2.7", + "id": "moonshotai/Kimi-K2.5", + "name": "Kimi-K2.5", + "display_name": "Kimi-K2.5", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 196608, - "output": 131072 + "context": 262144, + "output": 262144 }, "temperature": true, "tool_call": true, @@ -314094,16 +314146,17 @@ ] } }, - "attachment": false, + "attachment": true, "open_weights": true, - "release_date": "2026-03-18", - "last_updated": "2026-03-18", + "knowledge": "2025-01", + "release_date": "2026-01-27", + "last_updated": "2026-01-27", "type": "chat" }, { - "id": "moonshotai/Kimi-K2.6", - "name": "Kimi K2.6", - "display_name": "Kimi K2.6", + "id": "moonshotai/Kimi-K2-Thinking", + "name": "Kimi-K2-Thinking", + "display_name": "Kimi-K2-Thinking", "modalities": { "input": [ "text" @@ -314113,8 +314166,8 @@ ] }, "limit": { - "context": 65536, - "output": 65536 + "context": 262144, + "output": 262144 }, "temperature": true, "tool_call": true, @@ -314135,18 +314188,19 @@ }, "attachment": false, "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-04-20", - "last_updated": "2026-04-20", + "knowledge": "2024-08", + "release_date": "2025-11-06", + "last_updated": "2025-11-06", "type": "chat" }, { - "id": "moonshotai/kimi-k2.7-code-highspeed", - "name": "Kimi K2.7 Code Highspeed", - "display_name": "Kimi K2.7 Code Highspeed", + "id": "moonshotai/Kimi-K2.7-Code", + "name": "Kimi K2.7 Code", + "display_name": "Kimi K2.7 Code", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" @@ -314164,16 +314218,10 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] + "supported": true } }, - "attachment": false, + "attachment": true, "open_weights": true, "knowledge": "2025-01", "release_date": "2026-06-12", @@ -314181,20 +314229,22 @@ "type": "chat" }, { - "id": "deepseek-ai/DeepSeek-V4-Pro", - "name": "DeepSeek V4 Pro", - "display_name": "DeepSeek V4 Pro", + "id": "moonshotai/Kimi-K2.6", + "name": "Kimi-K2.6", + "display_name": "Kimi-K2.6", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 384000 + "context": 262144, + "output": 262144 }, "temperature": true, "tool_call": true, @@ -314205,36 +314255,53 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "high", - "effort_options": [ - "low", - "high", - "max" - ], "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ "thinking_blocks" - ], - "notes": [ - "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." ] } }, + "attachment": true, + "open_weights": true, + "knowledge": "2025-01", + "release_date": "2026-04-20", + "last_updated": "2026-04-20", + "type": "chat" + }, + { + "id": "moonshotai/Kimi-K2-Instruct-0905", + "name": "Kimi-K2-Instruct-0905", + "display_name": "Kimi-K2-Instruct-0905", + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 262144, + "output": 16384 + }, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": false + }, "attachment": false, "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-04-24", - "last_updated": "2026-04-24", + "knowledge": "2024-10", + "release_date": "2025-09-05", + "last_updated": "2025-09-05", "type": "chat" }, { - "id": "deepseek-ai/DeepSeek-V4-Flash", - "name": "DeepSeek V4 Flash", - "display_name": "DeepSeek V4 Flash", + "id": "moonshotai/Kimi-K2-Instruct", + "name": "Kimi-K2-Instruct", + "display_name": "Kimi-K2-Instruct", "modalities": { "input": [ "text" @@ -314244,11 +314311,40 @@ ] }, "limit": { - "context": 1048575, - "output": 384000 + "context": 131072, + "output": 16384 }, "temperature": true, "tool_call": true, + "reasoning": { + "supported": false + }, + "attachment": false, + "open_weights": true, + "knowledge": "2024-10", + "release_date": "2025-07-11", + "last_updated": "2025-07-11", + "type": "chat" + }, + { + "id": "moonshotai/Kimi-K3", + "name": "Kimi K3", + "display_name": "Kimi K3", + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1000000, + "output": 131072 + }, + "temperature": false, + "tool_call": true, "reasoning": { "supported": true, "default": true @@ -314256,45 +314352,23 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "high", "effort_options": [ "low", "high", "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." ] } }, - "attachment": false, + "attachment": true, "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-04-24", - "last_updated": "2026-04-24", + "release_date": "2026-07-16", + "last_updated": "2026-07-16", "type": "chat" - } - ] - }, - "siliconflow-com": { - "id": "siliconflow-com", - "name": "SiliconFlow", - "display_name": "SiliconFlow", - "api": "https://api.siliconflow.com/v1", - "doc": "https://cloud.siliconflow.com/models", - "models": [ + }, { - "id": "nex-agi/Nex-N2-Pro", - "name": "Nex-N2-Pro", - "display_name": "Nex-N2-Pro", + "id": "zai-org/GLM-5.3-Flash", + "name": "GLM-5.3-Flash", + "display_name": "GLM-5.3-Flash", "modalities": { "input": [ "text", @@ -314305,8 +314379,8 @@ ] }, "limit": { - "context": 262144, - "output": 256000 + "context": 1048576, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -314317,24 +314391,23 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "low", + "high", + "max" ] } }, "attachment": true, "open_weights": true, - "release_date": "2026-06-02", - "last_updated": "2026-06-02", + "release_date": "2026-08-26", + "last_updated": "2026-08-26", "type": "chat" }, { - "id": "zai-org/GLM-5.1", - "name": "zai-org/GLM-5.1", - "display_name": "zai-org/GLM-5.1", + "id": "zai-org/GLM-5", + "name": "GLM-5", + "display_name": "GLM-5", "modalities": { "input": [ "text" @@ -314344,8 +314417,8 @@ ] }, "limit": { - "context": 205000, - "output": 131000 + "context": 202752, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -314366,14 +314439,14 @@ }, "attachment": false, "open_weights": true, - "release_date": "2026-04-07", - "last_updated": "2026-04-07", + "release_date": "2026-02-12", + "last_updated": "2026-02-12", "type": "chat" }, { - "id": "zai-org/GLM-5.3-Flash", - "name": "GLM-5.3-Flash", - "display_name": "GLM-5.3-Flash", + "id": "zai-org/GLM-4.6V-Flash", + "name": "GLM-4.6V-Flash", + "display_name": "GLM-4.6V-Flash", "modalities": { "input": [ "text", @@ -314384,8 +314457,8 @@ ] }, "limit": { - "context": 1049000, - "output": 262000 + "context": 131072, + "output": 32768 }, "temperature": true, "tool_call": true, @@ -314393,32 +314466,16 @@ "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "effort_options": [ - "low", - "high", - "max" - ] - } - }, "attachment": true, "open_weights": true, - "release_date": "2026-08-26", - "last_updated": "2026-08-26", + "release_date": "2025-12-08", + "last_updated": "2025-12-08", "type": "chat" }, { - "id": "zai-org/GLM-4.5-Air", - "name": "zai-org/GLM-4.5-Air", - "display_name": "zai-org/GLM-4.5-Air", + "id": "zai-org/GLM-4.6", + "name": "GLM-4.6", + "display_name": "GLM-4.6", "modalities": { "input": [ "text" @@ -314428,25 +314485,31 @@ ] }, "limit": { - "context": 131000, - "output": 98304 + "context": 204800, + "output": 131072 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true + } }, "attachment": false, "open_weights": true, "knowledge": "2025-04", - "release_date": "2025-07-28", - "last_updated": "2025-07-28", + "release_date": "2025-09-30", + "last_updated": "2025-09-30", "type": "chat" }, { - "id": "zai-org/GLM-5", - "name": "zai-org/GLM-5", - "display_name": "zai-org/GLM-5", + "id": "zai-org/GLM-5.1", + "name": "GLM-5.1", + "display_name": "GLM-5.1", "modalities": { "input": [ "text" @@ -314456,7 +314519,7 @@ ] }, "limit": { - "context": 205000, + "context": 202752, "output": 131072 }, "temperature": true, @@ -314478,14 +314541,44 @@ }, "attachment": false, "open_weights": true, - "release_date": "2026-02-12", - "last_updated": "2026-02-12", + "release_date": "2026-04-07", + "last_updated": "2026-04-07", + "type": "chat" + }, + { + "id": "zai-org/GLM-4.5V", + "name": "GLM-4.5V", + "display_name": "GLM-4.5V", + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 65536, + "output": 16384 + }, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": true, + "default": true + }, + "attachment": true, + "open_weights": true, + "knowledge": "2025-04", + "release_date": "2025-08-11", + "last_updated": "2025-08-11", "type": "chat" }, { - "id": "zai-org/GLM-5V-Turbo", - "name": "zai-org/GLM-5V-Turbo", - "display_name": "zai-org/GLM-5V-Turbo", + "id": "zai-org/AutoGLM-Phone-9B-Multilingual", + "name": "AutoGLM Phone 9B Multilingual", + "display_name": "AutoGLM Phone 9B Multilingual", "modalities": { "input": [ "text", @@ -314496,30 +314589,18 @@ ] }, "limit": { - "context": 200000, - "output": 131072 + "context": 65536, + "output": 65536 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } + "supported": false }, "attachment": true, - "open_weights": false, - "release_date": "2026-04-01", - "last_updated": "2026-04-01", + "open_weights": true, + "release_date": "2025-12-10", + "last_updated": "2025-12-10", "type": "chat" }, { @@ -314535,8 +314616,8 @@ ] }, "limit": { - "context": 1049000, - "output": 262000 + "context": 262144, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -314546,17 +314627,7 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "effort_options": [ - "high", - "max" - ] + "supported": true } }, "attachment": false, @@ -314578,8 +314649,8 @@ ] }, "limit": { - "context": 1049000, - "output": 262000 + "context": 1048576, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -314590,12 +314661,6 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], "effort_options": [ "low", "high", @@ -314610,37 +314675,38 @@ "type": "chat" }, { - "id": "Qwen/Qwen3-VL-30B-A3B-Instruct", - "name": "Qwen/Qwen3-VL-30B-A3B-Instruct", - "display_name": "Qwen/Qwen3-VL-30B-A3B-Instruct", + "id": "zai-org/GLM-4.5-Air", + "name": "GLM-4.5-Air", + "display_name": "GLM-4.5-Air", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 262000, - "output": 32768 + "context": 131072, + "output": 98304 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": true, + "attachment": false, "open_weights": true, - "release_date": "2025-10-04", - "last_updated": "2025-10-04", + "knowledge": "2025-04", + "release_date": "2025-07-28", + "last_updated": "2025-07-28", "type": "chat" }, { - "id": "Qwen/Qwen3.5-397B-A17B", - "name": "Qwen3.5 397B-A17B", - "display_name": "Qwen3.5 397B-A17B", + "id": "zai-org/GLM-4.7-Flash", + "name": "GLM-4.7-Flash", + "display_name": "GLM-4.7-Flash", "modalities": { "input": [ "text" @@ -314650,13 +314716,14 @@ ] }, "limit": { - "context": 262144, - "output": 32768 + "context": 200000, + "output": 128000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true + "supported": true, + "default": true }, "extra_capabilities": { "reasoning": { @@ -314671,31 +314738,32 @@ }, "attachment": false, "open_weights": true, - "release_date": "2026-02-15", - "last_updated": "2026-02-15", + "knowledge": "2025-04", + "release_date": "2026-01-19", + "last_updated": "2026-01-19", "type": "chat" }, { - "id": "Qwen/Qwen3.6-27B", - "name": "Qwen3.6 27B", - "display_name": "Qwen3.6 27B", + "id": "zai-org/GLM-4.7", + "name": "GLM-4.7", + "display_name": "GLM-4.7", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 32768 + "context": 204800, + "output": 131072 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true + "supported": true, + "default": true }, "extra_capabilities": { "reasoning": { @@ -314708,16 +314776,17 @@ ] } }, - "attachment": true, + "attachment": false, "open_weights": true, - "release_date": "2026-04-22", - "last_updated": "2026-04-22", + "knowledge": "2025-04", + "release_date": "2025-12-22", + "last_updated": "2025-12-22", "type": "chat" }, { - "id": "Qwen/Qwen2.5-7B-Instruct", - "name": "Qwen/Qwen2.5-7B-Instruct", - "display_name": "Qwen/Qwen2.5-7B-Instruct", + "id": "tencent/Hy3", + "name": "Hy3", + "display_name": "Hy3", "modalities": { "input": [ "text" @@ -314727,24 +314796,35 @@ ] }, "limit": { - "context": 33000, - "output": 4000 + "context": 262144, + "output": 128000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "effort_options": [ + "none", + "low", + "high" + ] + } }, "attachment": false, "open_weights": true, - "release_date": "2024-09-19", - "last_updated": "2024-09-19", + "release_date": "2026-07-06", + "last_updated": "2026-07-06", "type": "chat" }, { - "id": "Qwen/Qwen3-8B", - "name": "Qwen/Qwen3-8B", - "display_name": "Qwen/Qwen3-8B", + "id": "tencent/Hy4-preview", + "name": "Hy4 preview", + "display_name": "Hy4 preview", "modalities": { "input": [ "text" @@ -314754,8 +314834,8 @@ ] }, "limit": { - "context": 131000, - "output": 32768 + "context": 1000000, + "output": 64000 }, "temperature": true, "tool_call": true, @@ -314766,24 +314846,22 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "none", + "high" ] } }, "attachment": false, "open_weights": true, - "release_date": "2025-04-29", - "last_updated": "2025-04-29", + "release_date": "2026-08-28", + "last_updated": "2026-08-28", "type": "chat" }, { - "id": "Qwen/Qwen3.6-35B-A3B", - "name": "Qwen3.6 35B-A3B", - "display_name": "Qwen3.6 35B-A3B", + "id": "google/gemma-4-26B-A4B-it", + "name": "Gemma 4 26B A4B IT", + "display_name": "Gemma 4 26B A4B IT", "modalities": { "input": [ "text", @@ -314800,29 +314878,24 @@ "temperature": true, "tool_call": true, "reasoning": { - "supported": true + "supported": true, + "default": true }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] + "supported": true } }, "attachment": true, "open_weights": true, - "release_date": "2026-04-17", - "last_updated": "2026-04-17", + "release_date": "2026-04-02", + "last_updated": "2026-04-02", "type": "chat" }, { - "id": "Qwen/Qwen3-VL-32B-Thinking", - "name": "Qwen/Qwen3-VL-32B-Thinking", - "display_name": "Qwen/Qwen3-VL-32B-Thinking", + "id": "google/gemma-3-12b-it", + "name": "Gemma 3 12B IT", + "display_name": "Gemma 3 12B IT", "modalities": { "input": [ "text", @@ -314833,64 +314906,54 @@ ] }, "limit": { - "context": 262000, - "output": 32768 + "context": 131072, + "output": 131072 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } + "supported": false }, "attachment": true, "open_weights": true, - "release_date": "2025-10-21", - "last_updated": "2025-11-25", + "knowledge": "2024-08", + "release_date": "2025-03-12", + "last_updated": "2025-03-12", "type": "chat" }, { - "id": "Qwen/Qwen3-Coder-30B-A3B-Instruct", - "name": "Qwen/Qwen3-Coder-30B-A3B-Instruct", - "display_name": "Qwen/Qwen3-Coder-30B-A3B-Instruct", + "id": "google/gemma-3-27b-it", + "name": "Gemma 3 27B IT", + "display_name": "Gemma 3 27B IT", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 262000, - "output": 65536 + "context": 131072, + "output": 131072 }, "temperature": true, "tool_call": true, "reasoning": { "supported": false }, - "attachment": false, + "attachment": true, "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-07-31", - "last_updated": "2025-07-31", + "knowledge": "2024-08", + "release_date": "2025-03-12", + "last_updated": "2025-03-12", "type": "chat" }, { - "id": "Qwen/Qwen3-VL-30B-A3B-Thinking", - "name": "Qwen/Qwen3-VL-30B-A3B-Thinking", - "display_name": "Qwen/Qwen3-VL-30B-A3B-Thinking", + "id": "google/gemma-3-4b-it", + "name": "Gemma 3 4B IT", + "display_name": "Gemma 3 4B IT", "modalities": { "input": [ "text", @@ -314901,36 +314964,25 @@ ] }, "limit": { - "context": 262000, - "output": 32768 + "context": 131072, + "output": 131072 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } + "supported": false }, "attachment": true, "open_weights": true, - "release_date": "2025-10-11", - "last_updated": "2025-11-25", + "knowledge": "2024-08", + "release_date": "2025-03-12", + "last_updated": "2025-03-12", "type": "chat" }, { - "id": "Qwen/Qwen3.5-27B", - "name": "Qwen3.5 27B", - "display_name": "Qwen3.5 27B", + "id": "google/gemma-4-31B-it", + "name": "Gemma 4 31B IT", + "display_name": "Gemma 4 31B IT", "modalities": { "input": [ "text", @@ -314947,29 +314999,25 @@ "temperature": true, "tool_call": true, "reasoning": { - "supported": true + "supported": true, + "default": true }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] + "supported": true } }, "attachment": true, "open_weights": true, - "release_date": "2026-02-23", - "last_updated": "2026-02-23", + "knowledge": "2025-01", + "release_date": "2026-04-02", + "last_updated": "2026-04-02", "type": "chat" }, { - "id": "Qwen/Qwen3-30B-A3B-Instruct-2507", - "name": "Qwen/Qwen3-30B-A3B-Instruct-2507", - "display_name": "Qwen/Qwen3-30B-A3B-Instruct-2507", + "id": "MiniMaxAI/MiniMax-M2.5", + "name": "MiniMax-M2.5", + "display_name": "MiniMax-M2.5", "modalities": { "input": [ "text" @@ -314979,24 +315027,36 @@ ] }, "limit": { - "context": 262000, - "output": 32768 + "context": 204800, + "output": 131072 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } }, "attachment": false, "open_weights": true, - "release_date": "2025-07-28", - "last_updated": "2025-07-28", + "release_date": "2026-02-12", + "last_updated": "2026-02-12", "type": "chat" }, { - "id": "Qwen/Qwen3-VL-32B-Instruct", - "name": "Qwen/Qwen3-VL-32B-Instruct", - "display_name": "Qwen/Qwen3-VL-32B-Instruct", + "id": "MiniMaxAI/MiniMax-M3.1-Flash", + "name": "MiniMax-M3.1-Flash", + "display_name": "MiniMax-M3.1-Flash", "modalities": { "input": [ "text", @@ -315007,24 +315067,43 @@ ] }, "limit": { - "context": 262000, - "output": 32768 + "context": 1000000, + "output": 512000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" + ] + } }, "attachment": true, - "open_weights": true, - "release_date": "2025-10-21", - "last_updated": "2025-11-25", + "open_weights": false, + "release_date": "2026-10-19", + "last_updated": "2026-10-19", "type": "chat" }, { - "id": "Qwen/Qwen3-32B", - "name": "Qwen/Qwen3-32B", - "display_name": "Qwen/Qwen3-32B", + "id": "MiniMaxAI/MiniMax-M1-80k", + "name": "MiniMax M1 80K", + "display_name": "MiniMax M1 80K", "modalities": { "input": [ "text" @@ -315034,8 +315113,8 @@ ] }, "limit": { - "context": 131000, - "output": 32768 + "context": 1000000, + "output": 80000 }, "temperature": true, "tool_call": true, @@ -315043,45 +315122,33 @@ "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } - }, "attachment": false, "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-04-28", - "last_updated": "2025-04-28", + "release_date": "2025-06-17", + "last_updated": "2025-06-17", "type": "chat" }, { - "id": "Qwen/Qwen3.5-122B-A10B", - "name": "Qwen3.5 122B-A10B", - "display_name": "Qwen3.5 122B-A10B", + "id": "MiniMaxAI/MiniMax-M2.1", + "name": "MiniMax-M2.1", + "display_name": "MiniMax-M2.1", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 32768 + "context": 204800, + "output": 131072 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true + "supported": true, + "default": true }, "extra_capabilities": { "reasoning": { @@ -315094,16 +315161,17 @@ ] } }, - "attachment": true, + "attachment": false, "open_weights": true, - "release_date": "2026-02-23", - "last_updated": "2026-02-23", + "knowledge": "2025-10", + "release_date": "2025-12-23", + "last_updated": "2025-12-23", "type": "chat" }, { - "id": "Qwen/Qwen2.5-72B-Instruct", - "name": "Qwen/Qwen2.5-72B-Instruct", - "display_name": "Qwen/Qwen2.5-72B-Instruct", + "id": "MiniMaxAI/MiniMax-M2", + "name": "MiniMax-M2", + "display_name": "MiniMax-M2", "modalities": { "input": [ "text" @@ -315113,52 +315181,64 @@ ] }, "limit": { - "context": 33000, - "output": 4000 + "context": 204800, + "output": 131072 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, "attachment": false, "open_weights": true, - "release_date": "2024-09-18", - "last_updated": "2025-11-25", + "release_date": "2025-10-27", + "last_updated": "2025-10-27", "type": "chat" }, { - "id": "Qwen/Qwen3-VL-8B-Instruct", - "name": "Qwen/Qwen3-VL-8B-Instruct", - "display_name": "Qwen/Qwen3-VL-8B-Instruct", + "id": "MiniMaxAI/MiniMax-M2.7", + "name": "MiniMax-M2.7", + "display_name": "MiniMax-M2.7", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 262000, - "output": 32768 + "context": 204800, + "output": 131072 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": true, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } + }, + "attachment": false, "open_weights": true, - "release_date": "2025-10-15", - "last_updated": "2025-11-25", + "release_date": "2026-03-18", + "last_updated": "2026-03-18", "type": "chat" }, { - "id": "Qwen/Qwen3.5-35B-A3B", - "name": "Qwen3.5 35B-A3B", - "display_name": "Qwen3.5 35B-A3B", + "id": "MiniMaxAI/MiniMax-M3", + "name": "MiniMax-M3", + "display_name": "MiniMax-M3", "modalities": { "input": [ "text", @@ -315169,35 +315249,30 @@ ] }, "limit": { - "context": 262144, - "output": 32768 + "context": 524288, + "output": 512000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true + "supported": true, + "default": true }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] + "supported": true } }, "attachment": true, "open_weights": true, - "release_date": "2026-02-23", - "last_updated": "2026-02-23", + "release_date": "2026-06-01", + "last_updated": "2026-06-01", "type": "chat" }, { - "id": "Qwen/Qwen3-14B", - "name": "Qwen/Qwen3-14B", - "display_name": "Qwen/Qwen3-14B", + "id": "deepseek-ai/DeepSeek-R1-0528", + "name": "DeepSeek-R1-0528", + "display_name": "DeepSeek-R1-0528", "modalities": { "input": [ "text" @@ -315207,7 +315282,7 @@ ] }, "limit": { - "context": 131000, + "context": 163840, "output": 32768 }, "temperature": true, @@ -315229,14 +315304,15 @@ }, "attachment": false, "open_weights": true, - "release_date": "2025-04-30", - "last_updated": "2025-11-25", + "knowledge": "2025-05", + "release_date": "2025-05-28", + "last_updated": "2025-05-28", "type": "chat" }, { - "id": "Qwen/Qwen3.8-2.4T-A95B", - "name": "Qwen3.8 2.4T A95B", - "display_name": "Qwen3.8 2.4T A95B", + "id": "deepseek-ai/DeepSeek-V4-Pro", + "name": "DeepSeek V4 Pro", + "display_name": "DeepSeek V4 Pro", "modalities": { "input": [ "text" @@ -315246,8 +315322,8 @@ ] }, "limit": { - "context": 1049000, - "output": 131000 + "context": 1048576, + "output": 393216 }, "temperature": true, "tool_call": true, @@ -315257,19 +315333,37 @@ }, "extra_capabilities": { "reasoning": { - "supported": true + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "high", + "effort_options": [ + "low", + "high", + "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." + ] } }, "attachment": false, "open_weights": true, - "release_date": "2026-08-12", - "last_updated": "2026-08-12", + "knowledge": "2025-05", + "release_date": "2026-04-24", + "last_updated": "2026-04-24", "type": "chat" }, { - "id": "Qwen/Qwen3-Coder-480B-A35B-Instruct", - "name": "Qwen/Qwen3-Coder-480B-A35B-Instruct", - "display_name": "Qwen/Qwen3-Coder-480B-A35B-Instruct", + "id": "deepseek-ai/DeepSeek-V3", + "name": "DeepSeek-V3", + "display_name": "DeepSeek-V3", "modalities": { "input": [ "text" @@ -315279,8 +315373,8 @@ ] }, "limit": { - "context": 262000, - "output": 65536 + "context": 64000, + "output": 8192 }, "temperature": true, "tool_call": true, @@ -315289,15 +315383,14 @@ }, "attachment": false, "open_weights": true, - "knowledge": "2025-04", - "release_date": "2025-07-22", - "last_updated": "2025-07-22", + "release_date": "2024-12-26", + "last_updated": "2024-12-26", "type": "chat" }, { - "id": "Qwen/Qwen3.5-9B", - "name": "Qwen/Qwen3.5-9B", - "display_name": "Qwen/Qwen3.5-9B", + "id": "deepseek-ai/DeepSeek-V4.1-Flash-1017", + "name": "DeepSeek V4.1 Flash 1017", + "display_name": "DeepSeek V4.1 Flash 1017", "modalities": { "input": [ "text", @@ -315308,95 +315401,77 @@ ] }, "limit": { - "context": 262144, - "output": 32768 + "context": 1048576, + "output": 384000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true + "supported": true, + "default": true }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "low", + "high", + "max" ] } }, "attachment": true, "open_weights": true, - "release_date": "2026-02-23", - "last_updated": "2026-02-23", + "knowledge": "2025-05", + "release_date": "2026-10-17", + "last_updated": "2026-10-17", "type": "chat" }, { - "id": "ByteDance-Seed/Seed-OSS-36B-Instruct", - "name": "ByteDance-Seed/Seed-OSS-36B-Instruct", - "display_name": "ByteDance-Seed/Seed-OSS-36B-Instruct", + "id": "deepseek-ai/DeepSeek-V4.1-Flash", + "name": "DeepSeek V4.1 Flash", + "display_name": "DeepSeek V4.1 Flash", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 262000, - "output": 99560 + "context": 1048576, + "output": 384000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true + "supported": true, + "default": true }, "extra_capabilities": { "reasoning": { - "supported": true + "supported": true, + "effort_options": [ + "none", + "low", + "high", + "xhigh", + "max" + ] } }, - "attachment": false, - "open_weights": true, - "release_date": "2025-09-04", - "last_updated": "2025-11-25", - "type": "chat" - }, - { - "id": "google/gemma-4-12B-it", - "name": "Gemma 4 12B IT", - "display_name": "Gemma 4 12B IT", - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 262144, - "output": 99615 - }, - "temperature": true, - "tool_call": true, - "reasoning": { - "supported": false - }, "attachment": true, "open_weights": true, - "release_date": "2026-06-09", - "last_updated": "2026-06-09", + "knowledge": "2025-05", + "release_date": "2026-09-10", + "last_updated": "2026-09-10", "type": "chat" }, { - "id": "google/gemma-4-31B-it", - "name": "Gemma 4 31B IT", - "display_name": "Gemma 4 31B IT", + "id": "deepseek-ai/DeepSeek-V4-Flash-Vision-Exp", + "name": "DeepSeek V4 Flash Vision Exp", + "display_name": "DeepSeek V4 Flash Vision Exp", "modalities": { "input": [ "text", @@ -315407,63 +315482,74 @@ ] }, "limit": { - "context": 262144, - "output": 99615 + "context": 1048576, + "output": 384000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true + "supported": true, + "default": true }, "extra_capabilities": { "reasoning": { - "supported": true + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "high", + "effort_options": [ + "low", + "high", + "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." + ] } }, "attachment": true, "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-04-02", - "last_updated": "2026-04-02", + "release_date": "2026-08-21", + "last_updated": "2026-09-01", "type": "chat" }, { - "id": "google/gemma-4-26B-A4B-it", - "name": "Gemma 4 26B A4B IT", - "display_name": "Gemma 4 26B A4B IT", + "id": "deepseek-ai/DeepSeek-V3-0324", + "name": "DeepSeek V3 0324", + "display_name": "DeepSeek V3 0324", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 99615 + "context": 163840, + "output": 163840 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true - } + "supported": false }, - "attachment": true, + "attachment": false, "open_weights": true, - "release_date": "2026-04-02", - "last_updated": "2026-04-02", + "release_date": "2025-03-24", + "last_updated": "2025-03-24", "type": "chat" }, { - "id": "openai/gpt-oss-20b", - "name": "openai/gpt-oss-20b", - "display_name": "openai/gpt-oss-20b", + "id": "deepseek-ai/DeepSeek-V3.1", + "name": "DeepSeek-V3.1", + "display_name": "DeepSeek-V3.1", "modalities": { "input": [ "text" @@ -315473,30 +315559,25 @@ ] }, "limit": { - "context": 131000, - "output": 8000 + "context": 131072, + "output": 8192 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true - } + "supported": true, + "default": true }, "attachment": false, "open_weights": true, - "knowledge": "2024-06", - "release_date": "2025-08-05", - "last_updated": "2025-08-05", + "release_date": "2025-08-21", + "last_updated": "2025-08-21", "type": "chat" }, { - "id": "openai/gpt-oss-120b", - "name": "openai/gpt-oss-120b", - "display_name": "openai/gpt-oss-120b", + "id": "deepseek-ai/DeepSeek-R1", + "name": "DeepSeek-R1", + "display_name": "DeepSeek-R1", "modalities": { "input": [ "text" @@ -315506,31 +315587,37 @@ ] }, "limit": { - "context": 131000, - "output": 8000 + "context": 64000, + "output": 32768 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { "supported": true, "default": true }, "extra_capabilities": { "reasoning": { - "supported": true + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] } }, "attachment": false, "open_weights": true, - "knowledge": "2024-06", - "release_date": "2025-08-05", - "last_updated": "2025-08-05", + "knowledge": "2024-07", + "release_date": "2025-01-20", + "last_updated": "2025-05-29", "type": "chat" }, { - "id": "tencent/Hy3", - "name": "Hy3", - "display_name": "Hy3", + "id": "deepseek-ai/DeepSeek-V4-Flash-0731", + "name": "DeepSeek V4 Flash 0731", + "display_name": "DeepSeek V4 Flash 0731", "modalities": { "input": [ "text" @@ -315540,8 +315627,8 @@ ] }, "limit": { - "context": 262144, - "output": 99615 + "context": 1048576, + "output": 384000 }, "temperature": true, "tool_call": true, @@ -315551,19 +315638,30 @@ }, "extra_capabilities": { "reasoning": { - "supported": true + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "effort_options": [ + "high", + "max" + ] } }, "attachment": false, "open_weights": true, - "release_date": "2026-07-06", - "last_updated": "2026-07-06", + "knowledge": "2025-05", + "release_date": "2026-07-31", + "last_updated": "2026-07-31", "type": "chat" }, { - "id": "tencent/Hunyuan-A13B-Instruct", - "name": "tencent/Hunyuan-A13B-Instruct", - "display_name": "tencent/Hunyuan-A13B-Instruct", + "id": "deepseek-ai/DeepSeek-V4-Pro-0813", + "name": "DeepSeek V4 Pro 0813", + "display_name": "DeepSeek V4 Pro 0813", "modalities": { "input": [ "text" @@ -315573,29 +315671,38 @@ ] }, "limit": { - "context": 131000, - "output": 117964 + "context": 1000000, + "output": 384000 }, "temperature": true, - "tool_call": false, + "tool_call": true, "reasoning": { "supported": true, "default": true }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "effort_options": [ + "low", + "high", + "max" + ] + } + }, "attachment": false, "open_weights": true, - "release_date": "2025-06-30", - "last_updated": "2025-11-25", + "release_date": "2026-08-13", + "last_updated": "2026-08-22", "type": "chat" }, { - "id": "MiniMaxAI/MiniMax-M3", - "name": "MiniMax-M3", - "display_name": "MiniMax-M3", + "id": "deepseek-ai/DeepSeek-V4-Flash", + "name": "DeepSeek V4 Flash", + "display_name": "DeepSeek V4 Flash", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" @@ -315603,7 +315710,7 @@ }, "limit": { "context": 1048576, - "output": 131000 + "output": 384000 }, "temperature": true, "tool_call": true, @@ -315614,24 +315721,36 @@ "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "high", + "effort_options": [ + "low", + "high", + "max" + ], "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ "thinking_blocks" + ], + "notes": [ + "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." ] } }, - "attachment": true, + "attachment": false, "open_weights": true, - "release_date": "2026-06-01", - "last_updated": "2026-06-01", + "knowledge": "2025-05", + "release_date": "2026-04-24", + "last_updated": "2026-04-24", "type": "chat" }, { - "id": "MiniMaxAI/MiniMax-M2.5", - "name": "MiniMaxAI/MiniMax-M2.5", - "display_name": "MiniMaxAI/MiniMax-M2.5", + "id": "deepseek-ai/DeepSeek-V3.2", + "name": "DeepSeek-V3.2", + "display_name": "DeepSeek-V3.2", "modalities": { "input": [ "text" @@ -315641,13 +315760,14 @@ ] }, "limit": { - "context": 197000, - "output": 131000 + "context": 163840, + "output": 65536 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true + "supported": true, + "default": true }, "extra_capabilities": { "reasoning": { @@ -315656,53 +315776,82 @@ }, "attachment": false, "open_weights": true, - "release_date": "2026-02-12", - "last_updated": "2026-02-12", + "knowledge": "2024-07", + "release_date": "2025-12-01", + "last_updated": "2025-12-01", "type": "chat" - }, + } + ] + }, + "meta": { + "id": "meta", + "name": "Meta", + "display_name": "Meta", + "api": "https://api.meta.ai/v1", + "doc": "https://dev.meta.ai/docs", + "models": [ { - "id": "inclusionAI/Ling-flash-2.0", - "name": "inclusionAI/Ling-flash-2.0", - "display_name": "inclusionAI/Ling-flash-2.0", + "id": "muse-spark-1.1", + "name": "Muse Spark 1.1", + "display_name": "Muse Spark 1.1", "modalities": { "input": [ - "text" + "text", + "image", + "pdf", + "video" ], "output": [ "text" ] }, "limit": { - "context": 131000, - "output": 49780 + "context": 1048576, + "output": 131072 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, - "open_weights": true, - "release_date": "2025-09-18", - "last_updated": "2025-11-25", + "extra_capabilities": { + "reasoning": { + "supported": true, + "effort_options": [ + "minimal", + "low", + "medium", + "high", + "xhigh" + ] + } + }, + "attachment": true, + "open_weights": false, + "release_date": "2026-04-08", + "last_updated": "2026-07-09", "type": "chat" }, { - "id": "moonshotai/Kimi-K2.5", - "name": "moonshotai/Kimi-K2.5", - "display_name": "moonshotai/Kimi-K2.5", + "id": "muse-spark-1.3-contributor", + "name": "Muse Spark 1.3 Contributor", + "display_name": "Muse Spark 1.3 Contributor", "modalities": { "input": [ "text", - "image" + "image", + "video", + "pdf", + "audio" ], "output": [ "text" ] }, "limit": { - "context": 262000, - "output": 99560 + "context": 1048576, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -315712,40 +315861,42 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "minimal", + "low", + "medium", + "high", + "xhigh" ] } }, "attachment": true, - "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-01-27", - "last_updated": "2026-01-27", + "open_weights": false, + "release_date": "2026-09-02", + "last_updated": "2026-09-02", "type": "chat" }, { - "id": "moonshotai/Kimi-K2.7-Code", - "name": "Kimi K2.7 Code", - "display_name": "Kimi K2.7 Code", + "id": "muse-wonder-1.0", + "name": "Muse Wonder", + "display_name": "Muse Wonder", "modalities": { "input": [ "text", - "image" + "image", + "video", + "pdf", + "audio" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 99615 + "context": 1048576, + "output": 131072 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -315753,38 +315904,41 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "minimal", + "low", + "medium", + "high", + "xhigh", + "max" ] } }, "attachment": true, - "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-06-12", - "last_updated": "2026-06-12", + "open_weights": false, + "release_date": "2026-10-12", + "last_updated": "2026-10-12", "type": "chat" }, { - "id": "moonshotai/Kimi-K2.6", - "name": "moonshotai/Kimi-K2.6", - "display_name": "moonshotai/Kimi-K2.6", + "id": "muse-spark-1.2", + "name": "Muse Spark 1.2", + "display_name": "Muse Spark 1.2", "modalities": { "input": [ "text", - "image" + "image", + "video", + "pdf", + "audio" ], "output": [ "text" ] }, "limit": { - "context": 262000, - "output": 99560 + "context": 1048576, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -315795,29 +315949,32 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "minimal", + "low", + "medium", + "high", + "xhigh" ] } }, "attachment": true, - "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-04-20", - "last_updated": "2026-04-20", + "open_weights": false, + "release_date": "2026-08-05", + "last_updated": "2026-08-05", "type": "chat" }, { - "id": "moonshotai/Kimi-K3", - "name": "Kimi K3", - "display_name": "Kimi K3", + "id": "muse-spark-1.2-contributor", + "name": "Muse Spark 1.2 Contributor", + "display_name": "Muse Spark 1.2 Contributor", "modalities": { "input": [ "text", - "image" + "image", + "video", + "pdf", + "audio" ], "output": [ "text" @@ -315825,46 +315982,50 @@ }, "limit": { "context": 1048576, - "output": 262000 + "output": 131072 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "default": true + }, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "minimal", + "low", + "medium", + "high", + "xhigh" ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-07-16", - "last_updated": "2026-07-16", + "open_weights": false, + "release_date": "2026-08-05", + "last_updated": "2026-08-05", "type": "chat" }, { - "id": "stepfun-ai/Step-3.5-Flash", - "name": "stepfun-ai/Step-3.5-Flash", - "display_name": "stepfun-ai/Step-3.5-Flash", + "id": "muse-spark-1.3", + "name": "Muse Spark 1.3", + "display_name": "Muse Spark 1.3", "modalities": { "input": [ - "text" + "text", + "image", + "video", + "pdf", + "audio" ], "output": [ "text" ] }, "limit": { - "context": 262000, - "output": 66000 + "context": 1048576, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -315872,30 +316033,52 @@ "supported": true, "default": true }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-01-29", - "last_updated": "2026-02-13", + "extra_capabilities": { + "reasoning": { + "supported": true, + "effort_options": [ + "minimal", + "low", + "medium", + "high", + "xhigh", + "max" + ] + } + }, + "attachment": true, + "open_weights": false, + "release_date": "2026-09-02", + "last_updated": "2026-09-02", "type": "chat" - }, + } + ] + }, + "longcat": { + "id": "longcat", + "name": "LongCat", + "display_name": "LongCat", + "api": "https://api.longcat.chat/openai", + "doc": "https://longcat.chat/platform/docs/", + "models": [ { - "id": "deepseek-ai/DeepSeek-V4-Flash-0731", - "name": "DeepSeek V4 Flash 0731", - "display_name": "DeepSeek V4 Flash 0731", + "id": "LongCat-2.5-Preview", + "name": "LongCat-2.5-Preview", + "display_name": "LongCat-2.5-Preview", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 384000 + "context": 1048576, + "output": 262144 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -315912,17 +316095,16 @@ ] } }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-07-31", - "last_updated": "2026-07-31", + "attachment": true, + "open_weights": false, + "release_date": "2026-09-25", + "last_updated": "2026-09-25", "type": "chat" }, { - "id": "deepseek-ai/DeepSeek-V4-Pro-0813", - "name": "DeepSeek V4 Pro 0813", - "display_name": "DeepSeek V4 Pro 0813", + "id": "LongCat-2.0", + "name": "LongCat-2.0", + "display_name": "LongCat-2.0", "modalities": { "input": [ "text" @@ -315932,10 +316114,10 @@ ] }, "limit": { - "context": 1000000, - "output": 384000 + "context": 1048576, + "output": 131072 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -315954,25 +316136,35 @@ }, "attachment": false, "open_weights": true, - "release_date": "2026-08-13", - "last_updated": "2026-08-22", + "release_date": "2026-06-30", + "last_updated": "2026-06-30", "type": "chat" - }, + } + ] + }, + "llmtr": { + "id": "llmtr", + "name": "LLMTR", + "display_name": "LLMTR", + "api": "https://llmtr.com/v1", + "doc": "https://llmtr.com/docs", + "models": [ { - "id": "deepseek-ai/DeepSeek-V3.2-Exp", - "name": "deepseek-ai/DeepSeek-V3.2-Exp", - "display_name": "deepseek-ai/DeepSeek-V3.2-Exp", + "id": "gemma-4", + "name": "Gemma 4", + "display_name": "Gemma 4", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 164000, - "output": 65536 + "context": 131072, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -315980,16 +316172,17 @@ "supported": true, "default": true }, - "attachment": false, + "attachment": true, "open_weights": true, - "release_date": "2025-10-10", - "last_updated": "2025-11-25", + "knowledge": "2025-01", + "release_date": "2026-04-02", + "last_updated": "2026-04-02", "type": "chat" }, { - "id": "deepseek-ai/DeepSeek-V3.1-Terminus", - "name": "deepseek-ai/DeepSeek-V3.1-Terminus", - "display_name": "deepseek-ai/DeepSeek-V3.1-Terminus", + "id": "magibu-11b-v8", + "name": "Magibu 11B v8", + "display_name": "Magibu 11B v8", "modalities": { "input": [ "text" @@ -315999,36 +316192,36 @@ ] }, "limit": { - "context": 164000, + "context": 8192, "output": 8192 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true + "supported": false }, "attachment": false, - "open_weights": true, - "release_date": "2025-09-29", - "last_updated": "2025-11-25", + "open_weights": false, + "release_date": "2026-06-05", + "last_updated": "2026-08-16", "type": "chat" }, { - "id": "deepseek-ai/DeepSeek-R1", - "name": "deepseek-ai/DeepSeek-R1", - "display_name": "deepseek-ai/DeepSeek-R1", + "id": "muse-glimmer-30b-tr", + "name": "Muse Glimmer 30B (TR)", + "display_name": "Muse Glimmer 30B (TR)", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 164000, - "output": 8192 + "context": 131072, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -316036,140 +316229,103 @@ "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } - }, - "attachment": false, + "attachment": true, "open_weights": true, - "release_date": "2025-05-28", - "last_updated": "2025-05-28", + "knowledge": "2026-01-04", + "release_date": "2026-08-10", + "last_updated": "2026-08-10", "type": "chat" }, { - "id": "deepseek-ai/DeepSeek-V4-Pro", - "name": "DeepSeek V4 Pro", - "display_name": "DeepSeek V4 Pro", + "id": "trendyol-asure-12b", + "name": "Trendyol Asure 12B", + "display_name": "Trendyol Asure 12B", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 384000 + "context": 40960, + "output": 40960 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "high", - "effort_options": [ - "low", - "high", - "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." - ] - } + "supported": false }, - "attachment": false, + "attachment": true, "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-04-24", - "last_updated": "2026-04-24", + "release_date": "2026-02-19", + "last_updated": "2026-02-20", "type": "chat" }, { - "id": "deepseek-ai/DeepSeek-V3.2", - "name": "deepseek-ai/DeepSeek-V3.2", - "display_name": "deepseek-ai/DeepSeek-V3.2", + "id": "medgemma-4b", + "name": "MedGemma 4B", + "display_name": "MedGemma 4B", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 164000, - "output": 65536 + "context": 8192, + "output": 8192 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true - } + "supported": false }, - "attachment": false, + "attachment": true, "open_weights": true, - "knowledge": "2024-07", - "release_date": "2025-12-01", - "last_updated": "2025-12-01", + "release_date": "2026-04-26", + "last_updated": "2026-08-16", "type": "chat" }, { - "id": "deepseek-ai/DeepSeek-V3.1", - "name": "deepseek-ai/DeepSeek-V3.1", - "display_name": "deepseek-ai/DeepSeek-V3.1", + "id": "qwen3-6-35b", + "name": "Qwen3.6 35B-A3B", + "display_name": "Qwen3.6 35B-A3B", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 164000, - "output": 8192 + "context": 16384, + "output": 16384 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { "supported": true, "default": true }, - "attachment": false, + "attachment": true, "open_weights": true, - "release_date": "2025-08-21", - "last_updated": "2025-08-21", + "release_date": "2026-04-17", + "last_updated": "2026-04-17", "type": "chat" }, { - "id": "deepseek-ai/DeepSeek-V4-Flash-Vision-Exp", - "name": "DeepSeek V4 Flash Vision Exp", - "display_name": "DeepSeek V4 Flash Vision Exp", + "id": "sakana/fugu-ultra", + "name": "Fugu Ultra", + "display_name": "Fugu Ultra", "modalities": { "input": [ "text", @@ -316181,9 +316337,9 @@ }, "limit": { "context": 1000000, - "output": 384000 + "output": 1000000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -316191,36 +316347,22 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "high", "effort_options": [ - "low", "high", - "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." + "xhigh" ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-08-21", - "last_updated": "2026-09-01", + "open_weights": false, + "release_date": "2026-06-15", + "last_updated": "2026-06-15", "type": "chat" }, { - "id": "deepseek-ai/DeepSeek-V4-Flash", - "name": "DeepSeek V4 Flash", - "display_name": "DeepSeek V4 Flash", + "id": "publicai/apertus-8b-instruct", + "name": "Apertus 8B Instruct", + "display_name": "Apertus 8B Instruct", "modalities": { "input": [ "text" @@ -316230,48 +316372,25 @@ ] }, "limit": { - "context": 1000000, - "output": 384000 + "context": 65536, + "output": 8192 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "high", - "effort_options": [ - "low", - "high", - "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." - ] - } + "supported": false }, "attachment": false, "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-04-24", - "last_updated": "2026-04-24", + "knowledge": "2025-09", + "release_date": "2025-09-02", + "last_updated": "2025-09-02", "type": "chat" }, { - "id": "deepseek-ai/DeepSeek-V3", - "name": "deepseek-ai/DeepSeek-V3", - "display_name": "deepseek-ai/DeepSeek-V3", + "id": "publicai/apertus-70b-instruct", + "name": "Apertus 70B Instruct", + "display_name": "Apertus 70B Instruct", "modalities": { "input": [ "text" @@ -316281,7 +316400,7 @@ ] }, "limit": { - "context": 164000, + "context": 65536, "output": 8192 }, "temperature": true, @@ -316291,25 +316410,28 @@ }, "attachment": false, "open_weights": true, - "release_date": "2025-03-24", - "last_updated": "2025-03-24", + "knowledge": "2025-09", + "release_date": "2025-09-02", + "last_updated": "2025-09-02", "type": "chat" }, { - "id": "meituan-longcat/LongCat-2.0", - "name": "LongCat-2.0", - "display_name": "LongCat-2.0", + "id": "thinkingmachines/inkling", + "name": "Inkling", + "display_name": "Inkling", "modalities": { "input": [ - "text" + "text", + "image", + "audio" ], "output": [ "text" ] }, "limit": { - "context": 1049000, - "output": 131072 + "context": 262144, + "output": 262144 }, "temperature": true, "tool_call": true, @@ -316319,45 +316441,39 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "none", + "minimal", + "low", + "medium", + "high", + "xhigh" ] } }, - "attachment": false, + "attachment": true, "open_weights": true, - "release_date": "2026-06-30", - "last_updated": "2026-06-30", + "release_date": "2026-07-15", + "last_updated": "2026-07-15", "type": "chat" - } - ] - }, - "tokenrouter": { - "id": "tokenrouter", - "name": "TokenRouter", - "display_name": "TokenRouter", - "api": "https://api.tokenrouter.com/v1", - "doc": "https://www.tokenrouter.com/docs/tokenrouter-feature-guide/", - "models": [ + }, { - "id": "z-ai/glm-5.3-free", - "name": "GLM-5.3 (free)", - "display_name": "GLM-5.3 (free)", + "id": "thinkingmachines/inkling-small", + "name": "Inkling Small", + "display_name": "Inkling Small", "modalities": { "input": [ - "text" + "text", + "image", + "audio" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 262144, + "output": 262144 }, "temperature": true, "tool_call": true, @@ -316367,135 +316483,106 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], "effort_options": [ + "none", + "minimal", "low", + "medium", "high", - "max" + "xhigh" ] } }, - "attachment": false, + "attachment": true, "open_weights": true, - "release_date": "2026-08-14", - "last_updated": "2026-08-14", + "release_date": "2026-07-30", + "last_updated": "2026-07-30", "type": "chat" - } - ] - }, - "sensenova": { - "id": "sensenova", - "name": "SenseNova (China)", - "display_name": "SenseNova (China)", - "api": "https://token.sensenova.cn/v1", - "doc": "https://platform.sensenova.cn/docs", - "models": [ + }, { - "id": "deepseek-v4-flash", - "name": "DeepSeek V4 Flash", - "display_name": "DeepSeek V4 Flash", + "id": "google/gemini-2.5-flash-lite", + "name": "Gemini 2.5 Flash-Lite", + "display_name": "Gemini 2.5 Flash-Lite", "modalities": { "input": [ - "text" + "text", + "image", + "audio", + "video", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1000000, + "context": 1048576, "output": 65536 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "high", - "effort_options": [ - "low", - "high", - "max" - ], - "interleaved": true, + "default_enabled": false, + "mode": "budget", + "budget": { + "default": -1, + "min": 512, + "max": 24576, + "auto": -1, + "unit": "tokens" + }, "summaries": true, "visibility": "summary", "continuation": [ - "thinking_blocks" - ], - "notes": [ - "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." + "thought_signatures" ] } }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-04-24", - "last_updated": "2026-04-24", + "attachment": true, + "open_weights": false, + "knowledge": "2025-01", + "release_date": "2025-06-17", + "last_updated": "2025-06-17", "type": "chat" }, { - "id": "kimi-k3", - "name": "Kimi K3", - "display_name": "Kimi K3", + "id": "mistral/voxtral-small-latest", + "name": "Voxtral Small (latest)", + "display_name": "Voxtral Small (latest)", "modalities": { "input": [ "text", - "image" + "audio" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 32000, + "output": 32000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "effort_options": [ - "low", - "high", - "max" - ] - } + "supported": false }, "attachment": true, "open_weights": true, - "release_date": "2026-07-16", - "last_updated": "2026-07-16", + "release_date": "2025-07-15", + "last_updated": "2025-07-15", "type": "chat" }, { - "id": "deepseek-v4-pro", - "name": "DeepSeek V4 Pro", - "display_name": "DeepSeek V4 Pro", + "id": "poolside/laguna-xs-2.1", + "name": "Laguna XS 2.1", + "display_name": "Laguna XS 2.1", "modalities": { "input": [ "text" @@ -316505,8 +316592,8 @@ ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 262144, + "output": 32768 }, "temperature": true, "tool_call": true, @@ -316514,49 +316601,26 @@ "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "high", - "effort_options": [ - "low", - "high", - "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." - ] - } - }, "attachment": false, "open_weights": true, - "release_date": "2026-08-13", - "last_updated": "2026-08-22", + "release_date": "2026-07-02", + "last_updated": "2026-07-02", "type": "chat" }, { - "id": "deepseek-v4.1-flash", - "name": "DeepSeek V4.1 Flash", - "display_name": "DeepSeek V4.1 Flash", + "id": "qwen/qwen3-max", + "name": "Qwen3 Max", + "display_name": "Qwen3 Max", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, + "context": 256000, "output": 65536 }, "temperature": true, @@ -316573,37 +316637,32 @@ "visibility": "summary", "continuation": [ "thinking_blocks" - ], - "effort_options": [ - "none", - "low", - "high", - "max" ] } }, - "attachment": true, - "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-09-10", - "last_updated": "2026-09-10", + "attachment": false, + "open_weights": false, + "knowledge": "2025-04", + "release_date": "2025-09-23", + "last_updated": "2025-09-23", "type": "chat" }, { - "id": "sensenova-6.8-flash-lite", - "name": "SenseNova 6.8 Flash Lite", - "display_name": "SenseNova 6.8 Flash Lite", + "id": "qwen/qwen3.6-flash", + "name": "Qwen3.6 Flash", + "display_name": "Qwen3.6 Flash", "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 262144, + "context": 1000000, "output": 65536 }, "temperature": true, @@ -316614,24 +316673,25 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "none", - "low", - "medium", - "high" + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, "attachment": true, "open_weights": false, - "release_date": "2026-08-11", - "last_updated": "2026-08-28", + "release_date": "2026-04-27", + "last_updated": "2026-04-27", "type": "chat" }, { - "id": "glm-5.2", - "name": "GLM-5.2", - "display_name": "GLM-5.2", + "id": "qwen/qwen-plus", + "name": "Qwen Plus", + "display_name": "Qwen Plus", "modalities": { "input": [ "text" @@ -316642,7 +316702,7 @@ }, "limit": { "context": 1000000, - "output": 131072 + "output": 32768 }, "temperature": true, "tool_call": true, @@ -316658,72 +316718,35 @@ "visibility": "summary", "continuation": [ "thinking_blocks" - ], - "effort_options": [ - "none", - "high" ] } }, "attachment": false, - "open_weights": true, - "release_date": "2026-06-13", - "last_updated": "2026-06-13", + "open_weights": false, + "knowledge": "2024-04", + "release_date": "2024-01-25", + "last_updated": "2025-09-11", "type": "chat" - } - ] - }, - "digitalocean": { - "id": "digitalocean", - "name": "DigitalOcean", - "display_name": "DigitalOcean", - "api": "https://inference.do-ai.run/v1", - "doc": "https://docs.digitalocean.com/products/gradient-ai-platform/details/models/", - "models": [ - { - "id": "qwen3-tts-voicedesign", - "name": "Qwen3 TTS VoiceDesign", - "display_name": "Qwen3 TTS VoiceDesign", - "modalities": { - "input": [ - "text" - ], - "output": [ - "audio" - ] - }, - "limit": { - "context": 32768, - "output": 1 - }, - "temperature": false, - "tool_call": false, - "reasoning": { - "supported": false - }, - "attachment": false, - "open_weights": true, - "release_date": "2026-04-21", - "last_updated": "2026-04-30" }, { - "id": "openai-gpt-6.1-terra", - "name": "OpenAI GPT-6.1 Terra", - "display_name": "OpenAI GPT-6.1 Terra", + "id": "qwen/qwen3.5-plus", + "name": "Qwen3.5 Plus", + "display_name": "Qwen3.5 Plus", "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 1000000, + "output": 65536 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -316731,27 +316754,26 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "none", - "low", - "medium", - "high", - "xhigh", - "max" + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-06-01", - "release_date": "2026-10-26", - "last_updated": "2026-10-26", + "knowledge": "2025-04", + "release_date": "2026-02-16", + "last_updated": "2026-02-16", "type": "chat" }, { - "id": "gte-large-en-v1.5", - "name": "GTE Large (v1.5)", - "display_name": "GTE Large (v1.5)", + "id": "qwen/qwen3-coder-flash", + "name": "Qwen3 Coder Flash", + "display_name": "Qwen3 Coder Flash", "modalities": { "input": [ "text" @@ -316761,24 +316783,25 @@ ] }, "limit": { - "context": 8192, - "output": 1024 + "context": 1000000, + "output": 65536 }, - "temperature": false, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { "supported": false }, "attachment": false, - "open_weights": true, - "release_date": "2024-03-27", - "last_updated": "2026-04-16", - "type": "embedding" + "open_weights": false, + "knowledge": "2025-04", + "release_date": "2025-07-28", + "last_updated": "2025-07-28", + "type": "chat" }, { - "id": "arcee-trinity-large-thinking", - "name": "Arcee Trinity Large Thinking (Public Preview)", - "display_name": "Arcee Trinity Large Thinking (Public Preview)", + "id": "qwen/qwen-flash", + "name": "Qwen Flash", + "display_name": "Qwen Flash", "modalities": { "input": [ "text" @@ -316788,8 +316811,8 @@ ] }, "limit": { - "context": 128000, - "output": 32000 + "context": 1000000, + "output": 32768 }, "temperature": true, "tool_call": true, @@ -316799,27 +316822,31 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "low", - "medium", - "high" + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, "attachment": false, - "open_weights": true, - "release_date": "2026-04-01", - "last_updated": "2026-05-28", + "open_weights": false, + "knowledge": "2024-04", + "release_date": "2025-07-28", + "last_updated": "2025-07-28", "type": "chat" }, { - "id": "anthropic-claude-sonnet-5.5", - "name": "Anthropic Claude Sonnet 5.5", - "display_name": "Anthropic Claude Sonnet 5.5", + "id": "qwen/qwen3.6-plus", + "name": "Qwen3.6 Plus", + "display_name": "Qwen3.6 Plus", "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ "text" @@ -316827,9 +316854,9 @@ }, "limit": { "context": 1000000, - "output": 128000 + "output": 65536 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -316837,26 +316864,26 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-06", - "release_date": "2026-09-28", - "last_updated": "2026-09-28", + "knowledge": "2025-04", + "release_date": "2026-04-02", + "last_updated": "2026-04-02", "type": "chat" }, { - "id": "glm-5.3-flash", - "name": "GLM5.3 Flash", - "display_name": "GLM5.3 Flash", + "id": "qwen/qwen3-coder-plus", + "name": "Qwen3 Coder Plus", + "display_name": "Qwen3 Coder Plus", "modalities": { "input": [ "text" @@ -316866,166 +316893,78 @@ ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 1000000, + "output": 65536 }, "temperature": true, "tool_call": true, - "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" - ] - } - }, - "attachment": false, - "open_weights": true, - "release_date": "2026-08-26", - "last_updated": "2026-08-26", - "type": "chat" - }, - { - "id": "multi-qa-mpnet-base-dot-v1", - "name": "Multi-QA-mpnet-base-dot-v1", - "display_name": "Multi-QA-mpnet-base-dot-v1", - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 512, - "output": 768 - }, - "temperature": false, - "tool_call": false, "reasoning": { "supported": false }, "attachment": false, - "open_weights": true, - "release_date": "2021-08-23", - "last_updated": "2021-08-23", - "type": "embedding" - }, - { - "id": "deepseek-v4.1-flash-1017", - "name": "DeepSeek V4.1 Flash 1017", - "display_name": "DeepSeek V4.1 Flash 1017", - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 1048576, - "output": 1048576 - }, - "temperature": true, - "tool_call": true, - "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "low", - "high", - "xhigh", - "max" - ] - } - }, - "attachment": true, - "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-10-17", - "last_updated": "2026-10-17", + "open_weights": false, + "knowledge": "2025-04", + "release_date": "2025-07-23", + "last_updated": "2025-07-23", "type": "chat" }, { - "id": "anthropic-claude-haiku-4.5", - "name": "Anthropic Claude Haiku 4.5", - "display_name": "Anthropic Claude Haiku 4.5", + "id": "qwen/qwen3.5-397b-a17b", + "name": "Qwen3.5 397B-A17B", + "display_name": "Qwen3.5 397B-A17B", "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 8192 + "context": 256000, + "output": 65536 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "budget", - "budget": { - "min": 1024, - "unit": "tokens" - }, "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ "thinking_blocks" - ], - "notes": [ - "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", - "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2025-02-28", - "release_date": "2025-10-15", - "last_updated": "2025-10-15", + "open_weights": true, + "release_date": "2026-02-15", + "last_updated": "2026-02-15", "type": "chat" }, { - "id": "anthropic-claude-4.5-sonnet", - "name": "Anthropic Claude 4.5 Sonnet", - "display_name": "Anthropic Claude 4.5 Sonnet", + "id": "qwen/qwen3.7-plus", + "name": "Qwen3.7 Plus", + "display_name": "Qwen3.7 Plus", "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 200000, + "context": 1000000, "output": 64000 }, "temperature": true, @@ -317036,24 +316975,20 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "low", - "medium", - "high" - ] + "supported": true } }, "attachment": true, "open_weights": false, - "knowledge": "2025-07-31", - "release_date": "2025-09-29", - "last_updated": "2025-09-29", + "knowledge": "2025-04", + "release_date": "2026-06-02", + "last_updated": "2026-06-02", "type": "chat" }, { - "id": "kimi-k3", - "name": "Kimi K3", - "display_name": "Kimi K3", + "id": "qwen/qwen3-vl-plus", + "name": "Qwen3-VL Plus", + "display_name": "Qwen3-VL Plus", "modalities": { "input": [ "text", @@ -317064,52 +316999,8 @@ ] }, "limit": { - "context": 1048576, - "output": 131072 - }, - "temperature": false, - "tool_call": true, - "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "effort_options": [ - "low", - "high", - "max" - ] - } - }, - "attachment": true, - "open_weights": true, - "release_date": "2026-07-16", - "last_updated": "2026-07-16", - "type": "chat" - }, - { - "id": "deepseek-v4-pro", - "name": "Deepseek V4 Pro", - "display_name": "Deepseek V4 Pro", - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 1048576, - "output": 384000 + "context": 256000, + "output": 32768 }, "temperature": true, "tool_call": true, @@ -317120,91 +317011,67 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "high", - "effort_options": [ - "low", - "high", - "max" - ], "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ "thinking_blocks" - ], - "notes": [ - "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." ] } }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-04-24", - "last_updated": "2026-04-24", + "attachment": true, + "open_weights": false, + "knowledge": "2025-04", + "release_date": "2025-09-23", + "last_updated": "2025-09-23", "type": "chat" }, { - "id": "openai-gpt-6-luna", - "name": "OpenAI GPT-6 Luna", - "display_name": "OpenAI GPT-6 Luna", + "id": "perplexity/sonar-deep-research", + "name": "Sonar Deep Research", + "display_name": "Sonar Deep Research", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 128000, + "output": 32768 }, "temperature": false, - "tool_call": true, + "tool_call": false, "reasoning": { "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "none", - "low", - "medium", - "high", - "xhigh" - ] - } - }, - "attachment": true, + "attachment": false, "open_weights": false, - "knowledge": "2026-05-18", - "release_date": "2026-09-22", - "last_updated": "2026-09-22", + "knowledge": "2025-01", + "release_date": "2025-02-01", + "last_updated": "2025-09-01", "type": "chat" }, { - "id": "openai-gpt-5.3-codex", - "name": "OpenAI GPT-5.3 Codex", - "display_name": "OpenAI GPT-5.3 Codex", + "id": "upstage/solar-pro3", + "name": "Solar Pro 3", + "display_name": "Solar Pro 3", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 131072, + "output": 8192 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -317213,39 +317080,36 @@ "extra_capabilities": { "reasoning": { "effort_options": [ - "none", "low", "medium", - "high", - "xhigh" + "high" ] } }, - "attachment": true, + "attachment": false, "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-02-05", - "last_updated": "2026-02-05", + "knowledge": "2025-07", + "release_date": "2026-01-26", + "last_updated": "2026-03-23", "type": "chat" }, { - "id": "openai-o3-mini", - "name": "OpenAI o3 mini", - "display_name": "OpenAI o3 mini", + "id": "upstage/solar-pro2", + "name": "Solar Pro 2", + "display_name": "Solar Pro 2", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 100000 + "context": 65536, + "output": 8192 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -317254,23 +317118,22 @@ "extra_capabilities": { "reasoning": { "effort_options": [ - "low", - "medium", + "minimal", "high" ] } }, - "attachment": true, + "attachment": false, "open_weights": false, - "knowledge": "2024-05", - "release_date": "2024-12-20", - "last_updated": "2025-01-29", + "knowledge": "2025-03", + "release_date": "2025-05-20", + "last_updated": "2025-05-20", "type": "chat" }, { - "id": "openai-gpt-oss-120b", - "name": "OpenAI GPT-oss-120b", - "display_name": "OpenAI GPT-oss-120b", + "id": "upstage/solar-pro4", + "name": "Solar Pro 4", + "display_name": "Solar Pro 4", "modalities": { "input": [ "text" @@ -317280,8 +317143,8 @@ ] }, "limit": { - "context": 128000, - "output": 4096 + "context": 524288, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -317291,45 +317154,45 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], "effort_options": [ + "none", + "minimal", "low", "medium", - "high" + "high", + "xhigh", + "max" ] } }, "attachment": false, - "open_weights": true, - "knowledge": "2024-06", - "release_date": "2025-08-05", - "last_updated": "2025-08-05", + "open_weights": false, + "knowledge": "2026-02", + "release_date": "2026-08-06", + "last_updated": "2026-08-06", "type": "chat" }, { - "id": "anthropic-claude-opus-5", - "name": "Anthropic Claude Opus 5", - "display_name": "Anthropic Claude Opus 5", + "id": "meta/muse-spark-1.2-contributor", + "name": "Muse Spark 1.2 Contributor", + "display_name": "Muse Spark 1.2 Contributor", "modalities": { "input": [ "text", - "image" + "image", + "video", + "pdf", + "audio" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 1048576, + "output": 131072 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -317338,111 +317201,94 @@ "extra_capabilities": { "reasoning": { "effort_options": [ + "minimal", "low", "medium", "high", - "xhigh", - "max" + "xhigh" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-05", - "release_date": "2026-07-24", - "last_updated": "2026-07-24", + "release_date": "2026-08-05", + "last_updated": "2026-08-05", "type": "chat" }, { - "id": "openai-gpt-image-1.5", - "name": "OpenAI GPT Image 1.5", - "display_name": "OpenAI GPT Image 1.5", + "id": "mimo/mimo-v2.5-pro", + "name": "MiMo-V2.5-Pro", + "display_name": "MiMo-V2.5-Pro", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ - "text", - "image" + "text" ] }, "limit": { - "context": 8192, - "output": 16384 + "context": 1000000, + "output": 131072 }, - "temperature": false, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": true, - "open_weights": false, - "release_date": "2025-11-25", - "last_updated": "2025-11-25" + "attachment": false, + "open_weights": true, + "knowledge": "2024-12", + "release_date": "2026-04-22", + "last_updated": "2026-04-22", + "type": "chat" }, { - "id": "anthropic-claude-opus-4.6", - "name": "Anthropic Claude Opus 4.6", - "display_name": "Anthropic Claude Opus 4.6", + "id": "mimo/mimo-v2.5", + "name": "MiMo-V2.5", + "display_name": "MiMo-V2.5", "modalities": { "input": [ "text", - "image" + "image", + "audio", + "video" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 8192 + "context": 1000000, + "output": 131072 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "mixed", - "budget": { - "min": 1024, - "unit": "tokens" - }, - "effort": "high", - "effort_options": [ - "low", - "medium", - "high", - "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Anthropic recommends adaptive thinking with effort for Claude 4.6; budget_tokens remains a deprecated compatibility path.", - "Anthropic API defaults effort to high; lower effort levels should be chosen per workload." - ] - } + "default": true }, "attachment": true, - "open_weights": false, - "knowledge": "2025-05-31", - "release_date": "2026-02-05", - "last_updated": "2026-03-13", + "open_weights": true, + "knowledge": "2024-12", + "release_date": "2026-04-22", + "last_updated": "2026-04-22", "type": "chat" - }, + } + ] + }, + "hetzner": { + "id": "hetzner", + "name": "Hetzner", + "display_name": "Hetzner", + "api": "https://inference.hetzner.com/api/v1", + "doc": "https://experiments.hetzner.com/docs/inference", + "models": [ { - "id": "openai-gpt-4.1", - "name": "OpenAI GPT-4.1", - "display_name": "OpenAI GPT-4.1", + "id": "Qwen3.8-27B", + "name": "Qwen3.8-27B", + "display_name": "Qwen3.8-27B", "modalities": { "input": [ "text", @@ -317453,25 +317299,25 @@ ] }, "limit": { - "context": 1047576, - "output": 32768 + "context": 262144, + "output": 262144 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, "attachment": true, - "open_weights": false, - "knowledge": "2024-04", - "release_date": "2025-04-14", - "last_updated": "2025-04-14", + "open_weights": true, + "release_date": "2026-08-14", + "last_updated": "2026-08-14", "type": "chat" }, { - "id": "kimi-k2.6", - "name": "Kimi K2.6", - "display_name": "Kimi K2.6", + "id": "Qwen/Qwen3.6-35B-A3B-FP8", + "name": "Qwen3.6 35B A3B FP8", + "display_name": "Qwen3.6 35B A3B FP8", "modalities": { "input": [ "text", @@ -317485,7 +317331,7 @@ "context": 262144, "output": 262144 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -317499,40 +317345,41 @@ "visibility": "summary", "continuation": [ "thinking_blocks" - ], - "effort_options": [ - "none", - "low", - "medium", - "high" ] } }, "attachment": true, "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-04-20", - "last_updated": "2026-04-20", + "release_date": "2026-04-17", + "last_updated": "2026-04-17", "type": "chat" - }, + } + ] + }, + "lmstudio": { + "id": "lmstudio", + "name": "LMStudio", + "display_name": "LMStudio", + "api": "http://127.0.0.1:1234/v1", + "doc": "https://lmstudio.ai/models", + "models": [ { - "id": "openai-gpt-5.5", - "name": "OpenAI GPT-5.5", - "display_name": "OpenAI GPT-5.5", + "id": "openai/gpt-oss-20b", + "name": "GPT OSS 20B", + "display_name": "GPT OSS 20B", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 131072, + "output": 32768 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -317540,66 +317387,90 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, "effort_options": [ - "none", "low", "medium", - "high", - "xhigh" + "high" ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-12-01", - "release_date": "2026-04-23", - "last_updated": "2026-04-23", + "attachment": false, + "open_weights": true, + "knowledge": "2024-06", + "release_date": "2025-08-05", + "last_updated": "2025-08-05", "type": "chat" }, { - "id": "minimax-m3.1-flash", - "name": "MiniMax M3.1-Flash (Public Preview)", - "display_name": "MiniMax M3.1-Flash (Public Preview)", + "id": "qwen/qwen3-coder-30b", + "name": "Qwen3 Coder 30B", + "display_name": "Qwen3 Coder 30B", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 1048576 + "context": 262144, + "output": 65536 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" - ] - } + "supported": false }, - "attachment": true, - "open_weights": false, - "release_date": "2026-10-19", - "last_updated": "2026-10-19", + "attachment": false, + "open_weights": true, + "knowledge": "2025-04", + "release_date": "2025-07-31", + "last_updated": "2025-07-31", "type": "chat" }, { - "id": "nemotron-nano-12b-v2-vl", - "name": "Nemotron-nano 12b v2-vl", - "display_name": "Nemotron-nano 12b v2-vl", + "id": "qwen/qwen3-30b-a3b-2507", + "name": "Qwen3 30B A3B 2507", + "display_name": "Qwen3 30B A3B 2507", + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 262144, + "output": 16384 + }, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": false + }, + "attachment": false, + "open_weights": true, + "knowledge": "2025-04", + "release_date": "2025-07-28", + "last_updated": "2025-07-28", + "type": "chat" + } + ] + }, + "munito-eu": { + "id": "munito-eu", + "name": "Munito AI (EU)", + "display_name": "Munito AI (EU)", + "api": "https://api.eu.munito.ai/inference/v1", + "doc": "https://docs.munito.ai/regions", + "models": [ + { + "id": "zai-org/glm-5.3-flash", + "name": "GLM-5.3-Flash", + "display_name": "GLM-5.3-Flash", "modalities": { "input": [ "text", @@ -317611,8 +317482,8 @@ ] }, "limit": { - "context": 128000, - "output": 16384 + "context": 1048576, + "output": 943718 }, "temperature": true, "tool_call": true, @@ -317622,10 +317493,15 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ - "none", "low", - "medium", "high", "max" ] @@ -317633,28 +317509,27 @@ }, "attachment": true, "open_weights": true, - "release_date": "2025-10-28", - "last_updated": "2025-10-28", + "release_date": "2026-08-26", + "last_updated": "2026-08-26", "type": "chat" }, { - "id": "anthropic-claude-5.5-haiku", - "name": "Anthropic Claude 5.5 Haiku", - "display_name": "Anthropic Claude 5.5 Haiku", + "id": "aleph-alpha/kolibri-1", + "name": "Kolibri 1", + "display_name": "Kolibri 1", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 1048576, + "output": 943718 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -317662,37 +317537,45 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ + "none", "low", "medium", - "high", - "xhigh", - "max" + "high" ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-06", - "release_date": "2026-10-07", - "last_updated": "2026-10-07", + "attachment": false, + "open_weights": true, + "knowledge": "2026-06-18", + "release_date": "2026-10-03", + "last_updated": "2026-10-03", "type": "chat" }, { - "id": "minimax-m2.5", - "name": "MiniMax M2.5 (Public Preview)", - "display_name": "MiniMax M2.5 (Public Preview)", + "id": "qwen/qwen3.8-27b", + "name": "Qwen3.8 27B", + "display_name": "Qwen3.8 27B", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 65536, - "output": 65536 + "context": 262144, + "output": 235929 }, "temperature": true, "tool_call": true, @@ -317703,24 +317586,30 @@ "extra_capabilities": { "reasoning": { "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ + "none", "low", "medium", - "high" + "xhigh" ] } }, - "attachment": false, + "attachment": true, "open_weights": true, - "knowledge": "2025-08", - "release_date": "2026-02-12", - "last_updated": "2026-04-16", + "release_date": "2026-08-14", + "last_updated": "2026-08-14", "type": "chat" }, { - "id": "all-mini-lm-l6-v2", - "name": "All-MiniLM-L6-v2", - "display_name": "All-MiniLM-L6-v2", + "id": "deepseek-ai/deepseek-v4-flash", + "name": "DeepSeek V4 Flash 0731", + "display_name": "DeepSeek V4 Flash 0731", "modalities": { "input": [ "text" @@ -317730,107 +317619,162 @@ ] }, "limit": { - "context": 256, - "output": 384 + "context": 1048576, + "output": 943718 }, - "temperature": false, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "high", + "effort_options": [ + "low", + "high", + "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." + ] + } }, "attachment": false, "open_weights": true, - "release_date": "2021-08-30", - "last_updated": "2021-08-30", - "type": "embedding" - }, + "knowledge": "2025-05", + "release_date": "2026-07-31", + "last_updated": "2026-07-31", + "type": "chat" + } + ] + }, + "model-oracle-ai": { + "id": "model-oracle-ai", + "name": "Model Oracle AI", + "display_name": "Model Oracle AI", + "api": "https://api.modeloracle.com/api/v1", + "doc": "https://modeloracle.com/setup/", + "models": [ { - "id": "qwen3.8-max", - "name": "Qwen3.8-Max", - "display_name": "Qwen3.8-Max", + "id": "gpt-5.4-nano", + "name": "GPT-5.4 nano", + "display_name": "GPT-5.4 nano", "modalities": { "input": [ "text", - "image", - "video" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 262144 + "context": 400000, + "output": 128000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": false, + "mode": "effort", + "effort": "none", "effort_options": [ "none", "low", "medium", "high", - "max" - ] + "xhigh" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, "attachment": true, "open_weights": false, - "release_date": "2026-08-03", - "last_updated": "2026-08-03", + "knowledge": "2025-08-31", + "release_date": "2026-03-17", + "last_updated": "2026-03-17", "type": "chat" }, { - "id": "openai-gpt-5.4-nano", - "name": "OpenAI GPT-5.4 Nano", - "display_name": "OpenAI GPT-5.4 Nano", + "id": "gpt-5.4", + "name": "GPT-5.4", + "display_name": "GPT-5.4", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 400000, + "context": 1050000, "output": 128000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": false, + "mode": "effort", + "effort": "none", "effort_options": [ "none", "low", "medium", "high", "xhigh" - ] + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, "attachment": true, "open_weights": false, "knowledge": "2025-08-31", - "release_date": "2026-03-17", - "last_updated": "2026-03-17", + "release_date": "2026-03-05", + "last_updated": "2026-03-05", "type": "chat" }, { - "id": "openai-gpt-5.4-mini", - "name": "OpenAI GPT-5.4 Mini", - "display_name": "OpenAI GPT-5.4 Mini", + "id": "gpt-5.4-mini", + "name": "GPT-5.4 mini", + "display_name": "GPT-5.4 mini", "modalities": { "input": [ "text", @@ -317844,21 +317788,32 @@ "context": 400000, "output": 128000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": false, + "mode": "effort", + "effort": "none", "effort_options": [ "none", "low", "medium", "high", "xhigh" - ] + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, "attachment": true, @@ -317869,21 +317824,22 @@ "type": "chat" }, { - "id": "anthropic-claude-4.6-sonnet", - "name": "Anthropic Claude Sonnet 4.6", - "display_name": "Anthropic Claude Sonnet 4.6", + "id": "auto", + "name": "Auto", + "display_name": "Auto", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 8192 + "context": 1000000, + "output": 128000 }, "temperature": true, "tool_call": true, @@ -317893,56 +317849,85 @@ }, "extra_capabilities": { "reasoning": { + "supported": true, "effort_options": [ + "none", + "minimal", "low", "medium", "high", + "xhigh", "max" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-02-17", - "last_updated": "2026-03-13", + "release_date": "2026-06-29", + "last_updated": "2026-07-07", "type": "chat" }, { - "id": "bge-reranker-v2-m3", - "name": "BGE Reranker v2 M3", - "display_name": "BGE Reranker v2 M3", + "id": "claude-haiku-4.5", + "name": "Claude Haiku 4.5 (latest)", + "display_name": "Claude Haiku 4.5 (latest)", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 8192, - "output": 1 + "context": 200000, + "output": 64000 }, - "temperature": false, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": false }, - "attachment": false, - "open_weights": true, - "release_date": "2024-03-15", - "last_updated": "2024-03-15", - "type": "rerank" + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": false, + "mode": "budget", + "budget": { + "min": 1024, + "unit": "tokens" + }, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", + "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." + ] + } + }, + "attachment": true, + "open_weights": false, + "knowledge": "2025-02-28", + "release_date": "2025-10-15", + "last_updated": "2025-10-15", + "type": "chat" }, { - "id": "anthropic-claude-fable-5", - "name": "Anthropic Claude Fable 5", - "display_name": "Anthropic Claude Fable 5", + "id": "claude-sonnet-5", + "name": "Claude Sonnet 5", + "display_name": "Claude Sonnet 5", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" @@ -317961,81 +317946,41 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "high", "effort_options": [ "low", "medium", "high", "xhigh", "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "omitted", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Adaptive thinking is always on for Claude Fable 5 and Claude Mythos 5; thinking.type = \"disabled\" is rejected.", - "Manual budget_tokens requests return 400 on Claude Fable 5 and Claude Mythos 5.", - "thinking.display defaults to omitted; set display to summarized to receive readable thinking summaries." ] } }, "attachment": true, "open_weights": false, "knowledge": "2026-01-31", - "release_date": "2026-06-09", - "last_updated": "2026-06-09", - "type": "chat" - }, - { - "id": "mistral-3-14B", - "name": "Ministral 3 14B", - "display_name": "Ministral 3 14B", - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 262144, - "output": 128000 - }, - "temperature": true, - "tool_call": true, - "reasoning": { - "supported": false - }, - "attachment": false, - "open_weights": true, - "release_date": "2025-12-02", - "last_updated": "2025-12-02", + "release_date": "2026-06-30", + "last_updated": "2026-06-30", "type": "chat" }, { - "id": "deepseek-v4.1-flash", - "name": "DeepSeek V4.1 Flash", - "display_name": "DeepSeek V4.1 Flash", + "id": "claude-fable-5", + "name": "Claude Fable 5", + "display_name": "Claude Fable 5", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 1048576 + "context": 1000000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -318044,38 +317989,54 @@ "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "high", "effort_options": [ "low", + "medium", "high", "xhigh", "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "omitted", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Adaptive thinking is always on for Claude Fable 5 and Claude Mythos 5; thinking.type = \"disabled\" is rejected.", + "Manual budget_tokens requests return 400 on Claude Fable 5 and Claude Mythos 5.", + "thinking.display defaults to omitted; set display to summarized to receive readable thinking summaries." ] } }, "attachment": true, - "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-09-10", - "last_updated": "2026-09-10", + "open_weights": false, + "knowledge": "2026-01-31", + "release_date": "2026-06-09", + "last_updated": "2026-06-09", "type": "chat" }, { - "id": "mimo-v2.5-pro", - "name": "MiMo V2.5 Pro", - "display_name": "MiMo V2.5 Pro", + "id": "o4-mini", + "name": "o4-mini", + "display_name": "o4-mini", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 200000, + "output": 100000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -318084,42 +318045,42 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], + "default_enabled": true, + "mode": "effort", + "effort": "medium", "effort_options": [ - "none", + "low", + "medium", "high" - ] + ], + "visibility": "hidden" } }, - "attachment": false, - "open_weights": true, - "knowledge": "2024-12", - "release_date": "2026-04-22", - "last_updated": "2026-04-22", + "attachment": true, + "open_weights": false, + "knowledge": "2024-05", + "release_date": "2025-04-16", + "last_updated": "2025-04-16", "type": "chat" }, { - "id": "deepseek-v4-flash-0731", - "name": "DeepSeek V4 Flash 0731", - "display_name": "DeepSeek V4 Flash 0731", + "id": "gpt-5", + "name": "GPT-5", + "display_name": "GPT-5", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 1048576 + "context": 400000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -318128,63 +318089,92 @@ "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", "effort_options": [ + "minimal", "low", "medium", "high" - ] + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" } }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-07-31", - "last_updated": "2026-07-31", + "attachment": true, + "open_weights": false, + "knowledge": "2024-09-30", + "release_date": "2025-08-07", + "last_updated": "2025-08-07", "type": "chat" }, { - "id": "reflection-beam", - "name": "Beam", - "display_name": "Beam", + "id": "claude-opus-4.8", + "name": "Claude Opus 4.8", + "display_name": "Claude Opus 4.8", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 1048576 + "context": 1000000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": false, + "mode": "effort", + "effort": "high", "effort_options": [ "low", "medium", "high", "xhigh", "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "omitted", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Claude Opus 4.7 and newer Opus models require thinking.type = \"adaptive\" to enable thinking explicitly.", + "Manual budget_tokens requests return 400 on Claude Opus 4.7 and newer adaptive-only Opus models.", + "task_budget is separate from thinking control and should not be treated as a thinking budget." ] } }, - "attachment": false, + "attachment": true, "open_weights": false, - "release_date": "2026-10-21", - "last_updated": "2026-10-21", + "knowledge": "2026-01", + "release_date": "2026-05-28", + "last_updated": "2026-05-28", "type": "chat" }, { - "id": "deepseek-v4-pro-0813", - "name": "DeepSeek V4 Pro 0813", - "display_name": "DeepSeek V4 Pro 0813", + "id": "deepseek-v4-pro", + "name": "DeepSeek V4 Pro", + "display_name": "DeepSeek V4 Pro", "modalities": { "input": [ "text" @@ -318194,8 +318184,8 @@ ] }, "limit": { - "context": 1048576, - "output": 1048576 + "context": 1000000, + "output": 384000 }, "temperature": true, "tool_call": true, @@ -318206,84 +318196,91 @@ "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "high", "effort_options": [ - "none", "low", "high", "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." ] } }, "attachment": false, "open_weights": true, - "release_date": "2026-08-13", - "last_updated": "2026-08-22", + "knowledge": "2025-05", + "release_date": "2026-04-24", + "last_updated": "2026-04-24", "type": "chat" }, { - "id": "anthropic-claude-opus-4.8", - "name": "Anthropic Claude Opus 4.8", - "display_name": "Anthropic Claude Opus 4.8", + "id": "gpt-5.5", + "name": "GPT-5.5", + "display_name": "GPT-5.5", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1000000, + "context": 1050000, "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, + "default_enabled": true, "mode": "effort", - "effort": "high", + "effort": "medium", "effort_options": [ "low", "medium", "high", - "xhigh", - "max" + "xhigh" ], - "interleaved": true, - "summaries": true, - "visibility": "omitted", - "continuation": [ - "thinking_blocks" + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" ], - "notes": [ - "Claude Opus 4.7 and newer Opus models require thinking.type = \"adaptive\" to enable thinking explicitly.", - "Manual budget_tokens requests return 400 on Claude Opus 4.7 and newer adaptive-only Opus models.", - "task_budget is separate from thinking control and should not be treated as a thinking budget." - ] + "visibility": "hidden" } }, "attachment": true, "open_weights": false, - "knowledge": "2026-01", - "release_date": "2026-05-28", - "last_updated": "2026-05-28", + "knowledge": "2025-12-01", + "release_date": "2026-04-23", + "last_updated": "2026-04-23", "type": "chat" }, { - "id": "anthropic-claude-fable-5.1", - "name": "Anthropic Claude Fable 5.1", - "display_name": "Anthropic Claude Fable 5.1", + "id": "glm-5.2", + "name": "GLM-5.2", + "display_name": "GLM-5.2", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" @@ -318291,9 +318288,9 @@ }, "limit": { "context": 1000000, - "output": 128000 + "output": 131072 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -318302,141 +318299,110 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "high", - "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" - ], "interleaved": true, "summaries": true, - "visibility": "omitted", + "visibility": "summary", "continuation": [ "thinking_blocks" ], - "notes": [ - "Adaptive thinking is always on for Claude Fable 5 and Claude Mythos 5; thinking.type = \"disabled\" is rejected.", - "Manual budget_tokens requests return 400 on Claude Fable 5 and Claude Mythos 5.", - "thinking.display defaults to omitted; set display to summarized to receive readable thinking summaries." + "effort_options": [ + "none", + "high", + "xhigh" ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-06", - "release_date": "2026-09-01", - "last_updated": "2026-09-01", + "attachment": false, + "open_weights": true, + "release_date": "2026-06-13", + "last_updated": "2026-06-13", "type": "chat" }, { - "id": "openai-gpt-image-2", - "name": "OpenAI GPT Image 2", - "display_name": "OpenAI GPT Image 2", + "id": "gpt-4.1-mini", + "name": "GPT-4.1 mini", + "display_name": "GPT-4.1 mini", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ - "image", "text" ] }, "limit": { - "context": 8192, - "output": 16384 + "context": 1047576, + "output": 32768 }, - "temperature": false, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { "supported": false }, "attachment": true, "open_weights": false, - "release_date": "2026-04-21", - "last_updated": "2026-04-21" - }, - { - "id": "qwen3-embedding-0.6b", - "name": "Qwen3 Embedding 0.6B", - "display_name": "Qwen3 Embedding 0.6B", - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 8000, - "output": 1024 - }, - "temperature": false, - "tool_call": false, - "reasoning": { - "supported": false - }, - "attachment": false, - "open_weights": true, - "release_date": "2025-06-03", - "last_updated": "2026-04-16", - "type": "embedding" + "knowledge": "2024-04", + "release_date": "2025-04-14", + "last_updated": "2025-04-14", + "type": "chat" }, { - "id": "gemma-4-31B-it", - "name": "Gemma 4", - "display_name": "Gemma 4", + "id": "gpt-4.1", + "name": "GPT-4.1", + "display_name": "GPT-4.1", "modalities": { "input": [ "text", "image", - "video" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 256000, - "output": 8192 + "context": 1047576, + "output": 32768 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true - } + "supported": false }, "attachment": true, - "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-04-02", - "last_updated": "2026-04-02", + "open_weights": false, + "knowledge": "2024-04", + "release_date": "2025-04-14", + "last_updated": "2025-04-14", "type": "chat" - }, + } + ] + }, + "databricks": { + "id": "databricks", + "name": "Databricks", + "display_name": "Databricks", + "api": "https://${DATABRICKS_HOST}/ai-gateway/mlflow/v1", + "doc": "https://docs.databricks.com/aws/en/machine-learning/foundation-models/", + "models": [ { - "id": "openai-gpt-5-nano", - "name": "OpenAI GPT-5 Nano", - "display_name": "OpenAI GPT-5 Nano", + "id": "databricks-gpt-5-6-terra", + "name": "GPT-5.6 Terra", + "display_name": "GPT-5.6 Terra", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 400000, + "context": 1050000, "output": 128000 }, "temperature": false, @@ -318448,7 +318414,6 @@ "extra_capabilities": { "reasoning": { "effort_options": [ - "minimal", "low", "medium", "high" @@ -318457,15 +318422,15 @@ }, "attachment": true, "open_weights": false, - "knowledge": "2024-05-30", - "release_date": "2025-08-07", - "last_updated": "2025-08-07", + "knowledge": "2026-02-16", + "release_date": "2026-07-09", + "last_updated": "2026-07-09", "type": "chat" }, { - "id": "openai-gpt-5.6-luna", - "name": "OpenAI GPT-5.6 Luna", - "display_name": "OpenAI GPT-5.6 Luna", + "id": "databricks-gpt-5-4-mini", + "name": "GPT-5.4 mini", + "display_name": "GPT-5.4 mini", "modalities": { "input": [ "text", @@ -318476,10 +318441,10 @@ ] }, "limit": { - "context": 1050000, + "context": 400000, "output": 128000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -318488,38 +318453,36 @@ "extra_capabilities": { "reasoning": { "effort_options": [ - "none", "low", "medium", - "high", - "xhigh", - "max" + "high" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-02-16", - "release_date": "2026-07-09", - "last_updated": "2026-07-09", + "knowledge": "2025-08-31", + "release_date": "2026-03-17", + "last_updated": "2026-03-17", "type": "chat" }, { - "id": "openai-o1", - "name": "OpenAI o1", - "display_name": "OpenAI o1", + "id": "databricks-gpt-5-5", + "name": "GPT-5.5", + "display_name": "GPT-5.5", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 100000 + "context": 1050000, + "output": 128000 }, "temperature": false, "tool_call": true, @@ -318538,15 +318501,15 @@ }, "attachment": true, "open_weights": false, - "knowledge": "2023-09", - "release_date": "2024-12-05", - "last_updated": "2024-12-05", + "knowledge": "2025-12-01", + "release_date": "2026-04-23", + "last_updated": "2026-04-23", "type": "chat" }, { - "id": "bge-m3", - "name": "BGE M3", - "display_name": "BGE M3", + "id": "databricks-gpt-oss-120b", + "name": "GPT OSS 120B", + "display_name": "GPT OSS 120B", "modalities": { "input": [ "text" @@ -318556,64 +318519,81 @@ ] }, "limit": { - "context": 8192, - "output": 1024 + "context": 131072, + "output": 25000 }, - "temperature": false, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "low", + "medium", + "high" + ] + } }, "attachment": false, "open_weights": true, - "release_date": "2024-01-30", - "last_updated": "2024-01-30", - "type": "embedding" + "knowledge": "2024-06", + "release_date": "2025-08-05", + "last_updated": "2025-08-05", + "type": "chat" }, { - "id": "stable-diffusion-3.5-large", - "name": "Stable Diffusion 3.5 Large", - "display_name": "Stable Diffusion 3.5 Large", + "id": "databricks-kimi-k2-7-code", + "name": "Kimi K2.7 Code", + "display_name": "Kimi K2.7 Code", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ - "image" + "text" ] }, "limit": { - "context": 256, - "output": 1 + "context": 262144, + "output": 262144 }, "temperature": false, - "tool_call": false, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, + "attachment": true, "open_weights": true, - "release_date": "2024-10-22", - "last_updated": "2026-04-30" + "knowledge": "2025-01", + "release_date": "2026-06-12", + "last_updated": "2026-06-12", + "type": "chat" }, { - "id": "openai-gpt-5.4", - "name": "OpenAI GPT-5.4", - "display_name": "OpenAI GPT-5.4", + "id": "databricks-gpt-5-4", + "name": "GPT-5.4", + "display_name": "GPT-5.4", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 400000, + "context": 1050000, "output": 128000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -318622,11 +318602,9 @@ "extra_capabilities": { "reasoning": { "effort_options": [ - "none", "low", "medium", - "high", - "xhigh" + "high" ] } }, @@ -318638,88 +318616,78 @@ "type": "chat" }, { - "id": "glm-5.3", - "name": "GLM5.3", - "display_name": "GLM5.3", + "id": "databricks-claude-opus-4-1", + "name": "Claude Opus 4.1 (latest)", + "display_name": "Claude Opus 4.1 (latest)", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 128000 + "context": 200000, + "output": 32000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, - "effort_options": [ - "low", - "high", - "max" + "default_enabled": false, + "mode": "budget", + "budget": { + "min": 1024, + "unit": "tokens" + }, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", + "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." ] } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-08-14", - "last_updated": "2026-08-14", + "attachment": true, + "open_weights": false, + "knowledge": "2025-03-31", + "release_date": "2025-08-05", + "last_updated": "2025-08-05", "type": "chat" }, { - "id": "e5-large-v2", - "name": "E5 Large v2", - "display_name": "E5 Large v2", - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 512, - "output": 1024 - }, - "temperature": false, - "tool_call": false, - "reasoning": { - "supported": false - }, - "attachment": false, - "open_weights": true, - "release_date": "2023-05", - "last_updated": "2023-05", - "type": "embedding" - }, - { - "id": "openai-gpt-5.6-sol", - "name": "OpenAI GPT-5.6 Sol", - "display_name": "OpenAI GPT-5.6 Sol", + "id": "databricks-gemini-3-flash", + "name": "Gemini 3 Flash Preview", + "display_name": "Gemini 3 Flash Preview", "modalities": { "input": [ "text", - "image" + "image", + "video", + "audio", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 1048576, + "output": 65536 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -318728,55 +318696,81 @@ "extra_capabilities": { "reasoning": { "effort_options": [ - "none", + "minimal", "low", "medium", - "high", - "xhigh", - "max" + "high" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-02-16", - "release_date": "2026-07-09", - "last_updated": "2026-07-09", + "knowledge": "2025-01", + "release_date": "2025-12-17", + "last_updated": "2025-12-17", "type": "chat" }, { - "id": "nemotron-3.5-super-vl", - "name": "NVIDIA Nemotron 3.5 Super VL", - "display_name": "NVIDIA Nemotron 3.5 Super VL", + "id": "databricks-claude-opus-4-7", + "name": "Claude Opus 4.7", + "display_name": "Claude Opus 4.7", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 1048576 + "context": 1000000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": false, + "mode": "effort", + "effort": "high", + "effort_options": [ + "low", + "medium", + "high", + "xhigh", + "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "omitted", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Claude Opus 4.7 and newer Opus models require thinking.type = \"adaptive\" to enable thinking explicitly.", + "Manual budget_tokens requests return 400 on Claude Opus 4.7 and newer adaptive-only Opus models.", + "task_budget is separate from thinking control and should not be treated as a thinking budget." + ] + } }, "attachment": true, - "open_weights": true, - "release_date": "2026-10-15", - "last_updated": "2026-10-15", + "open_weights": false, + "knowledge": "2026-01-31", + "release_date": "2026-04-16", + "last_updated": "2026-04-16", "type": "chat" }, { - "id": "anthropic-claude-5-sonnet", - "name": "Anthropic Claude Sonnet 5", - "display_name": "Anthropic Claude Sonnet 5", + "id": "databricks-gpt-oss-20b", + "name": "GPT OSS 20B", + "display_name": "GPT OSS 20B", "modalities": { "input": [ "text" @@ -318786,10 +318780,10 @@ ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 131072, + "output": 25000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -318800,22 +318794,21 @@ "effort_options": [ "low", "medium", - "high", - "xhigh" + "high" ] } }, "attachment": false, - "open_weights": false, - "knowledge": "2026-01-31", - "release_date": "2026-06-30", - "last_updated": "2026-06-30", + "open_weights": true, + "knowledge": "2024-06", + "release_date": "2025-08-05", + "last_updated": "2025-08-05", "type": "chat" }, { - "id": "openai-o3", - "name": "OpenAI o3", - "display_name": "OpenAI o3", + "id": "databricks-gpt-5-1", + "name": "GPT-5.1", + "display_name": "GPT-5.1", "modalities": { "input": [ "text", @@ -318826,10 +318819,10 @@ ] }, "limit": { - "context": 200000, - "output": 100000 + "context": 400000, + "output": 128000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -318838,6 +318831,7 @@ "extra_capabilities": { "reasoning": { "effort_options": [ + "none", "low", "medium", "high" @@ -318846,44 +318840,56 @@ }, "attachment": true, "open_weights": false, - "knowledge": "2024-05", - "release_date": "2025-04-16", - "last_updated": "2025-04-16", + "knowledge": "2024-09-30", + "release_date": "2025-11-13", + "last_updated": "2025-11-13", "type": "chat" }, { - "id": "openai-gpt-4o", - "name": "OpenAI GPT-4o", - "display_name": "OpenAI GPT-4o", + "id": "databricks-gpt-5-6-sol", + "name": "GPT-5.6 Sol", + "display_name": "GPT-5.6 Sol", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 16384 + "context": 1050000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "low", + "medium", + "high", + "max" + ] + } }, "attachment": true, "open_weights": false, - "knowledge": "2023-09", - "release_date": "2024-05-13", - "last_updated": "2024-08-06", + "knowledge": "2026-02-16", + "release_date": "2026-07-09", + "last_updated": "2026-07-09", "type": "chat" }, { - "id": "openai-gpt-6-sol", - "name": "OpenAI GPT-6 Sol", - "display_name": "OpenAI GPT-6 Sol", + "id": "databricks-gpt-5-4-nano", + "name": "GPT-5.4 nano", + "display_name": "GPT-5.4 nano", "modalities": { "input": [ "text", @@ -318894,10 +318900,10 @@ ] }, "limit": { - "context": 1050000, + "context": 400000, "output": 128000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -318906,25 +318912,23 @@ "extra_capabilities": { "reasoning": { "effort_options": [ - "none", "low", "medium", - "high", - "xhigh" + "high" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-04-20", - "release_date": "2026-09-22", - "last_updated": "2026-09-22", + "knowledge": "2025-08-31", + "release_date": "2026-03-17", + "last_updated": "2026-03-17", "type": "chat" }, { - "id": "openai-gpt-5", - "name": "OpenAI GPT-5", - "display_name": "OpenAI GPT-5", + "id": "databricks-gpt-5-mini", + "name": "GPT-5 Mini", + "display_name": "GPT-5 Mini", "modalities": { "input": [ "text", @@ -318956,159 +318960,147 @@ }, "attachment": true, "open_weights": false, - "knowledge": "2024-09-30", + "knowledge": "2024-05-30", "release_date": "2025-08-07", "last_updated": "2025-08-07", "type": "chat" }, { - "id": "qwen3.5-397b-a17b", - "name": "Qwen 3.5 397B A17B", - "display_name": "Qwen 3.5 397B A17B", + "id": "databricks-claude-sonnet-4", + "name": "Claude Sonnet 4", + "display_name": "Claude Sonnet 4", "modalities": { "input": [ "text", "image", - "video" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 131072 + "context": 200000, + "output": 64000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": false, + "mode": "budget", + "budget": { + "min": 1024, + "unit": "tokens" + }, "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ "thinking_blocks" ], - "effort_options": [ - "none", - "low", - "medium", - "high" + "notes": [ + "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", + "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-02-15", - "last_updated": "2026-02-15", + "open_weights": false, + "knowledge": "2025-03-31", + "release_date": "2025-05-22", + "last_updated": "2025-05-22", "type": "chat" }, { - "id": "wan2-2-t2v-a14b", - "name": "Wan2.2-T2V-A14B", - "display_name": "Wan2.2-T2V-A14B", - "modalities": { - "input": [ - "text" - ], - "output": [ - "video" - ] - }, - "limit": { - "context": 100, - "output": 1 - }, - "temperature": false, - "tool_call": false, - "reasoning": { - "supported": false - }, - "attachment": false, - "open_weights": true, - "release_date": "2025-07-28", - "last_updated": "2026-04-30" - }, - { - "id": "nemotron-3-ultra-550b", - "name": "Nemotron 3 Ultra", - "display_name": "Nemotron 3 Ultra", + "id": "databricks-gemini-3-pro", + "name": "Gemini 3 Pro Preview", + "display_name": "Gemini 3 Pro Preview", "modalities": { "input": [ - "text" + "text", + "image", + "video", + "audio", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 131072, - "output": 131072 + "context": 1048576, + "output": 65536 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-09", - "release_date": "2026-06-04", - "last_updated": "2026-06-04", + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "low", + "medium", + "high" + ] + } + }, + "attachment": true, + "open_weights": false, + "knowledge": "2025-01", + "release_date": "2025-11-18", + "last_updated": "2025-11-18", "type": "chat" }, { - "id": "openai-gpt-5.4-pro", - "name": "OpenAI GPT-5.4 Pro", - "display_name": "OpenAI GPT-5.4 Pro", + "id": "databricks-gemini-2-5-pro", + "name": "Gemini 2.5 Pro", + "display_name": "Gemini 2.5 Pro", "modalities": { "input": [ "text", - "image" + "image", + "audio", + "video", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 1048576, + "output": 65536 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "medium", - "high", - "xhigh" - ] - } - }, "attachment": true, "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-03-05", - "last_updated": "2026-03-05", + "knowledge": "2025-01", + "release_date": "2025-06-17", + "last_updated": "2025-06-17", "type": "chat" }, { - "id": "anthropic-claude-opus-4.5", - "name": "Anthropic Claude Opus 4.5", - "display_name": "Anthropic Claude Opus 4.5", + "id": "databricks-claude-opus-4-5", + "name": "Claude Opus 4.5 (latest)", + "display_name": "Claude Opus 4.5 (latest)", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" @@ -319116,7 +319108,7 @@ }, "limit": { "context": 200000, - "output": 8192 + "output": 64000 }, "temperature": true, "tool_call": true, @@ -319159,103 +319151,107 @@ "type": "chat" }, { - "id": "openai-gpt-5-mini", - "name": "OpenAI GPT-5 Mini", - "display_name": "OpenAI GPT-5 Mini", + "id": "databricks-claude-sonnet-4-5", + "name": "Claude Sonnet 4.5 (latest)", + "display_name": "Claude Sonnet 4.5 (latest)", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 200000, + "output": 64000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "minimal", - "low", - "medium", - "high" + "supported": true, + "default_enabled": false, + "mode": "budget", + "budget": { + "min": 1024, + "unit": "tokens" + }, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", + "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." ] } }, "attachment": true, "open_weights": false, - "knowledge": "2024-05-30", - "release_date": "2025-08-07", - "last_updated": "2025-08-07", + "knowledge": "2025-07-31", + "release_date": "2025-09-29", + "last_updated": "2025-09-29", "type": "chat" }, { - "id": "openai-gpt-5.2-pro", - "name": "OpenAI GPT-5.2 Pro", - "display_name": "OpenAI GPT-5.2 Pro", + "id": "databricks-gemini-2-5-flash", + "name": "Gemini 2.5 Flash", + "display_name": "Gemini 2.5 Flash", "modalities": { "input": [ "text", - "image" + "image", + "audio", + "video", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 1048576, + "output": 65536 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "minimal", - "low", - "medium", - "high" - ] - } - }, "attachment": true, "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2025-12-11", - "last_updated": "2025-12-11", + "knowledge": "2025-01", + "release_date": "2025-06-17", + "last_updated": "2025-06-17", "type": "chat" }, { - "id": "openai-gpt-6-1-sol", - "name": "OpenAI GPT-6.1 Sol", - "display_name": "OpenAI GPT-6.1 Sol", + "id": "databricks-glm-5-2", + "name": "GLM-5.2", + "display_name": "GLM-5.2", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 1000000, + "output": 131072 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -319264,39 +319260,38 @@ "extra_capabilities": { "reasoning": { "effort_options": [ - "low", - "medium", + "none", "high", - "xhigh", "max" ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2026-04-30", - "release_date": "2026-09-29", - "last_updated": "2026-09-29", + "attachment": false, + "open_weights": true, + "release_date": "2026-06-13", + "last_updated": "2026-06-13", "type": "chat" }, { - "id": "openai-gpt-5.2", - "name": "OpenAI GPT-5.2", - "display_name": "OpenAI GPT-5.2", + "id": "databricks-gemini-3-5-flash", + "name": "Gemini 3.5 Flash", + "display_name": "Gemini 3.5 Flash", "modalities": { "input": [ "text", - "image" + "image", + "video", + "audio" ], "output": [ "text" ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 1048576, + "output": 65536 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -319314,53 +319309,27 @@ }, "attachment": true, "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2025-12-11", - "last_updated": "2025-12-11", + "knowledge": "2025-01", + "release_date": "2026-05-19", + "last_updated": "2026-05-19", "type": "chat" }, { - "id": "openai-gpt-image-1", - "name": "GPT Image 1", - "display_name": "GPT Image 1", + "id": "databricks-gpt-5-2", + "name": "GPT-5.2", + "display_name": "GPT-5.2", "modalities": { "input": [ "text", "image" ], - "output": [ - "image" - ] - }, - "limit": { - "context": 8192, - "output": 8192 - }, - "temperature": false, - "tool_call": false, - "reasoning": { - "supported": false - }, - "attachment": true, - "open_weights": false, - "release_date": "2025-04-24", - "last_updated": "2025-04-24" - }, - { - "id": "openai-gpt-oss-20b", - "name": "OpenAI GPT-oss-20b", - "display_name": "OpenAI GPT-oss-20b", - "modalities": { - "input": [ - "text" - ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 4096 + "context": 400000, + "output": 128000 }, "temperature": true, "tool_call": true, @@ -319371,36 +319340,38 @@ "extra_capabilities": { "reasoning": { "effort_options": [ + "none", "low", "medium", "high" ] } }, - "attachment": false, - "open_weights": true, - "knowledge": "2024-06", - "release_date": "2025-08-05", - "last_updated": "2025-08-05", + "attachment": true, + "open_weights": false, + "knowledge": "2025-08-31", + "release_date": "2025-12-11", + "last_updated": "2025-12-11", "type": "chat" }, { - "id": "deepseek-3.2", - "name": "Deepseek 3.2", - "display_name": "Deepseek 3.2", + "id": "databricks-gpt-5", + "name": "GPT-5", + "display_name": "GPT-5", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 163840, - "output": 163840 + "context": 400000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -319408,61 +319379,25 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], "effort_options": [ - "none", + "minimal", "low", "medium", "high" ] } }, - "attachment": false, - "open_weights": true, - "knowledge": "2024-07", - "release_date": "2025-12-01", - "last_updated": "2025-12-01", - "type": "chat" - }, - { - "id": "llama-4-maverick", - "name": "Llama 4 Maverick", - "display_name": "Llama 4 Maverick", - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 16384 - }, - "temperature": true, - "tool_call": true, - "reasoning": { - "supported": false - }, "attachment": true, - "open_weights": true, - "knowledge": "2024-08", - "release_date": "2025-04-05", - "last_updated": "2025-04-05", + "open_weights": false, + "knowledge": "2024-09-30", + "release_date": "2025-08-07", + "last_updated": "2025-08-07", "type": "chat" }, { - "id": "anthropic-claude-opus-5.5", - "name": "Anthropic Claude Opus 5.5", - "display_name": "Anthropic Claude Opus 5.5", + "id": "databricks-gpt-5-nano", + "name": "GPT-5 Nano", + "display_name": "GPT-5 Nano", "modalities": { "input": [ "text", @@ -319473,7 +319408,7 @@ ] }, "limit": { - "context": 1000000, + "context": 400000, "output": 128000 }, "temperature": false, @@ -319485,57 +319420,29 @@ "extra_capabilities": { "reasoning": { "effort_options": [ + "minimal", "low", "medium", - "high", - "xhigh", - "max" + "high" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-06", - "release_date": "2026-09-22", - "last_updated": "2026-09-22", - "type": "chat" - }, - { - "id": "deepseek-4-flash", - "name": "Deepseek V4 Flash", - "display_name": "Deepseek V4 Flash", - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 1048576, - "output": 384000 - }, - "temperature": true, - "tool_call": true, - "reasoning": { - "supported": false - }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-04-24", - "last_updated": "2026-04-24", + "knowledge": "2024-05-30", + "release_date": "2025-08-07", + "last_updated": "2025-08-07", "type": "chat" }, { - "id": "openai-gpt-6-astra", - "name": "OpenAI GPT-6 Astra", - "display_name": "OpenAI GPT-6 Astra", + "id": "databricks-gpt-5-6-luna", + "name": "GPT-5.6 Luna", + "display_name": "GPT-5.6 Luna", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" @@ -319556,174 +319463,196 @@ "effort_options": [ "low", "medium", - "high", - "xhigh", - "max" + "high" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-04-30", - "release_date": "2026-09-04", - "last_updated": "2026-09-04", + "knowledge": "2026-02-16", + "release_date": "2026-07-09", + "last_updated": "2026-07-09", "type": "chat" }, { - "id": "openai-gpt-4o-mini", - "name": "OpenAI GPT-4o mini", - "display_name": "OpenAI GPT-4o mini", + "id": "databricks-claude-opus-4-6", + "name": "Claude Opus 4.6", + "display_name": "Claude Opus 4.6", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 16384 + "context": 1000000, + "output": 128000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { + "supported": true, + "default_enabled": false, + "mode": "mixed", + "budget": { + "min": 1024, + "unit": "tokens" + }, + "effort": "high", "effort_options": [ "low", "medium", - "high" + "high", + "max" + ], + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Anthropic recommends adaptive thinking with effort for Claude 4.6; budget_tokens remains a deprecated compatibility path.", + "Anthropic API defaults effort to high; lower effort levels should be chosen per workload." ] } }, "attachment": true, "open_weights": false, - "knowledge": "2023-09", - "release_date": "2024-07-18", - "last_updated": "2024-07-18", + "knowledge": "2025-05-31", + "release_date": "2026-02-05", + "last_updated": "2026-03-13", "type": "chat" }, { - "id": "anthropic-claude-opus-4.7", - "name": "Anthropic Claude Opus 4.7", - "display_name": "Anthropic Claude Opus 4.7", + "id": "databricks-gemini-3-1-pro", + "name": "Gemini 3.1 Pro Preview", + "display_name": "Gemini 3.1 Pro Preview", "modalities": { "input": [ "text", - "image" + "image", + "video", + "audio", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 8192 + "context": 1048576, + "output": 65536 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "high", "effort_options": [ + "minimal", "low", "medium", - "high", - "xhigh", - "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "omitted", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Claude Opus 4.7 and newer Opus models require thinking.type = \"adaptive\" to enable thinking explicitly.", - "Manual budget_tokens requests return 400 on Claude Opus 4.7 and newer adaptive-only Opus models.", - "task_budget is separate from thinking control and should not be treated as a thinking budget." + "high" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-01-31", - "release_date": "2026-04-16", - "last_updated": "2026-04-16", + "knowledge": "2025-01", + "release_date": "2026-02-19", + "last_updated": "2026-02-19", "type": "chat" }, { - "id": "glm-5.2", - "name": "GLM-5.2", - "display_name": "GLM-5.2", + "id": "databricks-claude-sonnet-4-6", + "name": "Claude Sonnet 4.6", + "display_name": "Claude Sonnet 4.6", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 131072 + "context": 1000000, + "output": 64000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": false, + "mode": "mixed", + "budget": { + "min": 1024, + "unit": "tokens" + }, + "effort": "high", + "effort_options": [ + "low", + "medium", + "high", + "max" + ], "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ "thinking_blocks" ], - "effort_options": [ - "medium", - "high", - "xhigh" + "notes": [ + "Anthropic recommends adaptive thinking with effort for Claude 4.6; budget_tokens remains a deprecated compatibility path.", + "Anthropic API defaults effort to high; lower effort levels should be chosen per workload." ] } }, - "attachment": false, - "open_weights": true, - "release_date": "2026-06-13", - "last_updated": "2026-06-13", + "attachment": true, + "open_weights": false, + "knowledge": "2025-08-31", + "release_date": "2026-02-17", + "last_updated": "2026-03-13", "type": "chat" }, { - "id": "nemotron-3-nano-omni", - "name": "Nemotron 3 Nano Omni", - "display_name": "Nemotron 3 Nano Omni", + "id": "databricks-gemini-3-1-flash-lite", + "name": "Gemini 3.1 Flash Lite Preview", + "display_name": "Gemini 3.1 Flash Lite Preview", "modalities": { "input": [ "text", "image", - "video" + "video", + "audio", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 65536, + "context": 1048576, "output": 65536 }, "temperature": true, @@ -319735,179 +319664,211 @@ "extra_capabilities": { "reasoning": { "effort_options": [ - "none", + "minimal", "low", "medium", - "high", - "max" + "high" ] } }, "attachment": true, - "open_weights": true, - "release_date": "2026-04-28", - "last_updated": "2026-04-28", + "open_weights": false, + "knowledge": "2025-01", + "release_date": "2026-03-03", + "last_updated": "2026-03-03", "type": "chat" }, { - "id": "openai-gpt-5.6-terra", - "name": "OpenAI GPT-5.6 Terra", - "display_name": "OpenAI GPT-5.6 Terra", + "id": "databricks-claude-haiku-4-5", + "name": "Claude Haiku 4.5 (latest)", + "display_name": "Claude Haiku 4.5 (latest)", "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 200000, + "output": 64000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "none", - "low", - "medium", - "high", - "xhigh", - "max" + "supported": true, + "default_enabled": false, + "mode": "budget", + "budget": { + "min": 1024, + "unit": "tokens" + }, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", + "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-02-16", - "release_date": "2026-07-09", - "last_updated": "2026-07-09", + "knowledge": "2025-02-28", + "release_date": "2025-10-15", + "last_updated": "2025-10-15", "type": "chat" - }, + } + ] + }, + "regolo-ai": { + "id": "regolo-ai", + "name": "Regolo AI", + "display_name": "Regolo AI", + "api": "https://api.regolo.ai/v1", + "doc": "https://docs.regolo.ai/", + "models": [ { - "id": "fal-ai/fast-sdxl", - "name": "Fast SDXL", - "display_name": "Fast SDXL", + "id": "gemma4-31b", + "name": "Gemma 4 31B IT", + "display_name": "Gemma 4 31B IT", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ - "image" + "text" ] }, "limit": { - "context": 8192, - "output": 8192 + "context": 100000, + "output": 100000 }, - "temperature": false, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, + "attachment": true, "open_weights": true, - "release_date": "2023-07-26", - "last_updated": "2026-04-16" + "knowledge": "2025-01", + "release_date": "2026-04-02", + "last_updated": "2026-04-02", + "type": "chat" }, { - "id": "fal-ai/flux/schnell", - "name": "FLUX.1 [schnell]", - "display_name": "FLUX.1 [schnell]", + "id": "qwen3.5-122b", + "name": "Qwen3.5-122B", + "display_name": "Qwen3.5-122B", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ - "image" + "text" ] }, "limit": { - "context": 8192, - "output": 8192 + "context": 262144, + "output": 16384 }, - "temperature": false, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { - "supported": false + "supported": true, + "default": true }, - "attachment": false, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } + }, + "attachment": true, "open_weights": true, - "release_date": "2024-08-01", - "last_updated": "2026-04-16" + "release_date": "2026-02-23", + "last_updated": "2026-02-23", + "type": "chat" }, { - "id": "fal-ai/stable-audio-25/text-to-audio", - "name": "Stable Audio 2.5 (Text-to-Audio)", - "display_name": "Stable Audio 2.5 (Text-to-Audio)", + "id": "deepseek-ocr-2", + "name": "DeepSeek OCR 2", + "display_name": "DeepSeek OCR 2", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ - "audio" + "text" ] }, "limit": { - "context": 8192, - "output": 8192 + "context": 4000, + "output": 4000 }, - "temperature": false, + "temperature": true, "tool_call": false, "reasoning": { "supported": false }, - "attachment": false, - "open_weights": false, - "release_date": "2025-10-08", - "last_updated": "2026-04-16" + "attachment": true, + "open_weights": true, + "release_date": "2026-01-27", + "last_updated": "2026-01-27", + "type": "chat" }, { - "id": "fal-ai/elevenlabs/tts/multilingual-v2", - "name": "ElevenLabs Multilingual TTS v2", - "display_name": "ElevenLabs Multilingual TTS v2", + "id": "llama-3.3-70b-instruct", + "name": "Llama 3.3 70B Instruct", + "display_name": "Llama 3.3 70B Instruct", "modalities": { "input": [ "text" ], "output": [ - "audio" + "text" ] }, "limit": { - "context": 8192, - "output": 8192 + "context": 128000, + "output": 16384 }, - "temperature": false, - "tool_call": false, + "temperature": true, + "tool_call": true, "reasoning": { "supported": false }, "attachment": false, - "open_weights": false, - "release_date": "2023-08-22", - "last_updated": "2023-08-22" - } - ] - }, - "inception": { - "id": "inception", - "name": "Inception", - "display_name": "Inception", - "api": "https://api.inceptionlabs.ai/v1/", - "doc": "https://docs.inceptionlabs.ai/get-started/models", - "models": [ + "open_weights": true, + "knowledge": "2023-12", + "release_date": "2024-12-06", + "last_updated": "2024-12-06", + "type": "chat" + }, { - "id": "mercury-2", - "name": "Mercury 2", - "display_name": "Mercury 2", + "id": "qwen3-coder-next", + "name": "Qwen3-Coder-Next", + "display_name": "Qwen3-Coder-Next", "modalities": { "input": [ "text" @@ -319917,8 +319878,8 @@ ] }, "limit": { - "context": 128000, - "output": 50000 + "context": 262144, + "output": 16384 }, "temperature": true, "tool_call": true, @@ -319926,26 +319887,17 @@ "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "low", - "medium", - "high" - ] - } - }, "attachment": false, - "open_weights": false, - "knowledge": "2025-01-01", - "release_date": "2026-02-24", - "last_updated": "2026-02-24", + "open_weights": true, + "knowledge": "2025-09", + "release_date": "2026-02-03", + "last_updated": "2026-02-03", "type": "chat" }, { - "id": "mercury-2.5", - "name": "Mercury 2.5", - "display_name": "Mercury 2.5", + "id": "qwen3-embedding-8b", + "name": "Qwen3-Embedding-8B", + "display_name": "Qwen3-Embedding-8B", "modalities": { "input": [ "text" @@ -319955,35 +319907,24 @@ ] }, "limit": { - "context": 260000, - "output": 65536 + "context": 32768, + "output": 8192 }, - "temperature": true, - "tool_call": true, + "temperature": false, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "low", - "medium", - "high" - ] - } + "supported": false }, "attachment": false, - "open_weights": false, - "knowledge": "2025-11-01", - "release_date": "2026-09-08", - "last_updated": "2026-09-08", - "type": "chat" + "open_weights": true, + "release_date": "2025-06-05", + "last_updated": "2025-06-05", + "type": "embedding" }, { - "id": "mercury-3", - "name": "Mercury 3", - "display_name": "Mercury 3", + "id": "glm5.2", + "name": "GLM-5.2", + "display_name": "GLM-5.2", "modalities": { "input": [ "text" @@ -319993,8 +319934,8 @@ ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 96000, + "output": 96000 }, "temperature": true, "tool_call": true, @@ -320005,22 +319946,21 @@ "extra_capabilities": { "reasoning": { "effort_options": [ - "low", - "medium", - "high" + "high", + "max" ] } }, "attachment": false, - "open_weights": false, - "release_date": "2026-10-16", - "last_updated": "2026-10-20", + "open_weights": true, + "release_date": "2026-06-13", + "last_updated": "2026-06-13", "type": "chat" }, { - "id": "mercury-edit-2", - "name": "Mercury Edit 2", - "display_name": "Mercury Edit 2", + "id": "qwen3-reranker-4b", + "name": "Qwen3-Reranker-4B", + "display_name": "Qwen3-Reranker-4B", "modalities": { "input": [ "text" @@ -320030,190 +319970,104 @@ ] }, "limit": { - "context": 32000, + "context": 32768, "output": 8192 }, - "temperature": true, + "temperature": false, "tool_call": false, "reasoning": { "supported": false }, "attachment": false, - "open_weights": false, - "release_date": "2026-03-30", - "last_updated": "2026-03-30", - "type": "chat" - } - ] - }, - "unorouter": { - "id": "unorouter", - "name": "UnoRouter", - "display_name": "UnoRouter", - "api": "https://api.unorouter.com/v1", - "doc": "https://unorouter.com/models", - "models": [ - { - "id": "qwen3.5-397b-a17b:free", - "name": "Qwen3.5 397B-A17B", - "display_name": "Qwen3.5 397B-A17B", - "modalities": { - "input": [ - "text", - "image", - "video" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 262144, - "output": 65536 - }, - "temperature": true, - "tool_call": true, - "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } - }, - "attachment": true, "open_weights": true, - "release_date": "2026-02-15", - "last_updated": "2026-02-15", - "type": "chat" + "release_date": "2026-02-01", + "last_updated": "2026-02-01", + "type": "rerank" }, { - "id": "gpt-5.5", - "name": "GPT-5.5", - "display_name": "GPT-5.5", + "id": "brick-complexity-pro", + "name": "Brick Complexity Pro", + "display_name": "Brick Complexity Pro", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 100000, + "output": 15000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "medium", - "effort_options": [ - "low", - "medium", - "high", - "xhigh" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" - } + "supported": false }, "attachment": true, "open_weights": false, - "knowledge": "2025-12-01", - "release_date": "2026-04-23", - "last_updated": "2026-04-23", + "release_date": "2026-02-06", + "last_updated": "2026-02-06", "type": "chat" }, { - "id": "gpt-5.4", - "name": "GPT-5.4", - "display_name": "GPT-5.4", + "id": "gpt-oss-120b", + "name": "GPT-OSS-120B", + "display_name": "GPT-OSS-120B", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 128000, + "output": 16384 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "none", "effort_options": [ - "none", - "low", - "medium", - "high", - "xhigh" - ], - "verbosity": "medium", - "verbosity_options": [ "low", "medium", "high" - ], - "visibility": "hidden" + ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-03-05", - "last_updated": "2026-03-05", + "attachment": false, + "open_weights": true, + "knowledge": "2024-06", + "release_date": "2025-08-05", + "last_updated": "2025-08-05", "type": "chat" }, { - "id": "deepseek-v4-flash", - "name": "DeepSeek V4 Flash", - "display_name": "DeepSeek V4 Flash", + "id": "qwen3.8-27b", + "name": "Qwen3.8 27B", + "display_name": "Qwen3.8 27B", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 384000 + "context": 120000, + "output": 120000 }, "temperature": true, "tool_call": true, @@ -320221,98 +320075,70 @@ "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "high", - "effort_options": [ - "low", - "high", - "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." - ] - } - }, - "attachment": false, + "attachment": true, "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-04-24", - "last_updated": "2026-04-24", + "release_date": "2026-08-14", + "last_updated": "2026-08-14", "type": "chat" }, { - "id": "deepseek-v4-pro:free", - "name": "DeepSeek V4 Pro", - "display_name": "DeepSeek V4 Pro", + "id": "brick-v1-beta", + "name": "Brick v1 Beta", + "display_name": "Brick v1 Beta", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 384000 + "context": 100000, + "output": 15000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true + "supported": false }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-04-24", - "last_updated": "2026-04-24", + "attachment": true, + "open_weights": false, + "release_date": "2026-02-06", + "last_updated": "2026-02-06", "type": "chat" }, { - "id": "gemma-4-31b-it:free", - "name": "Gemma 4 31B IT", - "display_name": "Gemma 4 31B IT", + "id": "faster-whisper-large-v3", + "name": "Faster Whisper Large v3", + "display_name": "Faster Whisper Large v3", "modalities": { "input": [ - "text", - "image" + "audio" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 32768 + "context": 448, + "output": 4096 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true + "supported": false }, "attachment": true, "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-04-02", - "last_updated": "2026-04-02", - "type": "chat" + "release_date": "2023-11-06", + "last_updated": "2023-11-06" }, { - "id": "minimax-m2.7:free", - "name": "MiniMax-M2.7", - "display_name": "MiniMax-M2.7", + "id": "apertus-70b", + "name": "Apertus 70B", + "display_name": "Apertus 70B", "modalities": { "input": [ "text" @@ -320322,36 +320148,37 @@ ] }, "limit": { - "context": 204800, - "output": 131072 + "context": 30000, + "output": 30000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true + "supported": false }, "attachment": false, "open_weights": true, - "release_date": "2026-03-18", - "last_updated": "2026-03-18", + "knowledge": "2025-09", + "release_date": "2025-09-02", + "last_updated": "2025-09-02", "type": "chat" }, { - "id": "deepseek-v4-pro", - "name": "DeepSeek V4 Pro", - "display_name": "DeepSeek V4 Pro", + "id": "qwen3.5-9b", + "name": "Qwen3.5-9B", + "display_name": "Qwen3.5-9B", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 384000 + "context": 262144, + "output": 8192 }, "temperature": true, "tool_call": true, @@ -320362,108 +320189,80 @@ "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "high", - "effort_options": [ - "low", - "high", - "max" - ], "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ "thinking_blocks" - ], - "notes": [ - "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." ] } }, - "attachment": false, + "attachment": true, "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-04-24", - "last_updated": "2026-04-24", + "release_date": "2026-02-23", + "last_updated": "2026-02-23", "type": "chat" }, { - "id": "kimi-k2.6", - "name": "Kimi K2.6", - "display_name": "Kimi K2.6", + "id": "qwen-image", + "name": "Qwen-Image", + "display_name": "Qwen-Image", "modalities": { "input": [ - "text", - "image", - "video" + "text" ], "output": [ - "text" + "image" ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 8192, + "output": 4096 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true - } + "supported": false }, - "attachment": true, - "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-04-20", - "last_updated": "2026-04-20", - "type": "chat" + "attachment": false, + "open_weights": false, + "release_date": "2026-03-01", + "last_updated": "2026-03-01" }, { - "id": "claude-sonnet-5", - "name": "Claude Sonnet 5", - "display_name": "Claude Sonnet 5", + "id": "mistral-small-4-119b", + "name": "Mistral Small 4 119B", + "display_name": "Mistral Small 4 119B", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 256000, + "output": 16384 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "supported": true - } - }, "attachment": true, - "open_weights": false, - "knowledge": "2026-01-31", - "release_date": "2026-06-30", - "last_updated": "2026-06-30", + "open_weights": true, + "knowledge": "2025-06", + "release_date": "2026-03-16", + "last_updated": "2026-03-16", "type": "chat" }, { - "id": "minimax-m2.7", - "name": "MiniMax-M2.7", - "display_name": "MiniMax-M2.7", + "id": "gpt-oss-20b", + "name": "GPT-OSS-20B", + "display_name": "GPT-OSS-20B", "modalities": { "input": [ "text" @@ -320473,8 +320272,8 @@ ] }, "limit": { - "context": 204800, - "output": 131072 + "context": 128000, + "output": 16384 }, "temperature": true, "tool_call": true, @@ -320485,156 +320284,252 @@ "extra_capabilities": { "reasoning": { "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" + "effort_options": [ + "low", + "medium", + "high" ] } }, "attachment": false, "open_weights": true, - "release_date": "2026-03-18", - "last_updated": "2026-03-18", + "knowledge": "2024-06", + "release_date": "2025-08-05", + "last_updated": "2025-08-05", "type": "chat" - }, + } + ] + }, + "qihang-ai": { + "id": "qihang-ai", + "name": "QiHang", + "display_name": "QiHang", + "api": "https://api.qhaigc.net/v1", + "doc": "https://www.qhaigc.net/docs", + "models": [ { - "id": "step-3.7-flash:free", - "name": "Step 3.7 Flash", - "display_name": "Step 3.7 Flash", + "id": "claude-haiku-4-5-20251001", + "name": "Claude Haiku 4.5", + "display_name": "Claude Haiku 4.5", "modalities": { "input": [ "text", "image", - "video" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 256000, - "output": 256000 + "context": 200000, + "output": 64000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": false, + "mode": "budget", + "budget": { + "min": 1024, + "unit": "tokens" + }, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", + "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." + ] + } }, "attachment": true, - "open_weights": true, - "knowledge": "2026-03-01", - "release_date": "2026-05-29", - "last_updated": "2026-05-29", + "open_weights": false, + "knowledge": "2025-07-31", + "release_date": "2025-10-15", + "last_updated": "2025-10-15", "type": "chat" }, { - "id": "glm-5.2:free", - "name": "GLM-5.2", - "display_name": "GLM-5.2", + "id": "gpt-5.2-codex", + "name": "GPT-5.2 Codex", + "display_name": "GPT-5.2 Codex", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 400000, + "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, "default": true }, - "attachment": false, - "open_weights": true, - "release_date": "2026-06-13", - "last_updated": "2026-06-13", + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", + "effort_options": [ + "low", + "medium", + "high", + "xhigh" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" + } + }, + "attachment": true, + "open_weights": false, + "knowledge": "2025-08-31", + "release_date": "2025-12-11", + "last_updated": "2025-12-11", "type": "chat" }, { - "id": "gpt-5.4:free", - "name": "GPT-5.4", - "display_name": "GPT-5.4", + "id": "gpt-5.2", + "name": "GPT-5.2", + "display_name": "GPT-5.2", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1050000, + "context": 400000, "output": 128000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": false, + "mode": "effort", + "effort": "none", + "effort_options": [ + "none", + "low", + "medium", + "high", + "xhigh" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" + } }, "attachment": true, "open_weights": false, "knowledge": "2025-08-31", - "release_date": "2026-03-05", - "last_updated": "2026-03-05", + "release_date": "2025-12-11", + "last_updated": "2025-12-11", "type": "chat" }, { - "id": "nemotron-3-ultra-550b-a55b:free", - "name": "Nemotron 3 Ultra 550B A55B", - "display_name": "Nemotron 3 Ultra 550B A55B", + "id": "claude-sonnet-4-5-20250929", + "name": "Claude Sonnet 4.5", + "display_name": "Claude Sonnet 4.5", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 200000, + "output": 64000 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": true + "default": false }, - "attachment": false, - "open_weights": true, - "knowledge": "2025-09", - "release_date": "2026-06-04", - "last_updated": "2026-06-04", + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": false, + "mode": "budget", + "budget": { + "min": 1024, + "unit": "tokens" + }, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", + "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." + ] + } + }, + "attachment": true, + "open_weights": false, + "knowledge": "2025-07-31", + "release_date": "2025-09-29", + "last_updated": "2025-09-29", "type": "chat" }, { - "id": "gpt-5.5:free", - "name": "GPT-5.5", - "display_name": "GPT-5.5", + "id": "gpt-5-mini", + "name": "GPT-5-Mini", + "display_name": "GPT-5-Mini", "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1050000, - "output": 128000 + "context": 200000, + "output": 64000 }, "temperature": false, "tool_call": true, @@ -320642,21 +320537,44 @@ "supported": true, "default": true }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", + "effort_options": [ + "minimal", + "low", + "medium", + "high" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" + } + }, "attachment": true, "open_weights": false, - "knowledge": "2025-12-01", - "release_date": "2026-04-23", - "last_updated": "2026-04-23", + "knowledge": "2024-09-30", + "release_date": "2025-08-07", + "last_updated": "2025-08-07", "type": "chat" }, { - "id": "claude-haiku-4-5-20251001", - "name": "Claude Haiku 4.5", - "display_name": "Claude Haiku 4.5", + "id": "gemini-2.5-flash", + "name": "Gemini 2.5 Flash", + "display_name": "Gemini 2.5 Flash", "modalities": { "input": [ "text", "image", + "audio", + "video", "pdf" ], "output": [ @@ -320664,52 +320582,52 @@ ] }, "limit": { - "context": 200000, - "output": 64000 + "context": 1048576, + "output": 65536 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, + "default_enabled": true, "mode": "budget", "budget": { - "min": 1024, + "default": -1, + "min": 0, + "max": 24576, + "auto": -1, + "off": 0, "unit": "tokens" }, - "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Claude 4 manual thinking uses thinking.type = \"enabled\" with budget_tokens.", - "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header for this model family." + "thought_signatures" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2025-02-28", - "release_date": "2025-10-15", - "last_updated": "2025-10-15", + "knowledge": "2025-01", + "release_date": "2025-06-17", + "last_updated": "2025-06-17", "type": "chat" }, { - "id": "claude-opus-4-8", - "name": "Claude Opus 4.8", - "display_name": "Claude Opus 4.8", + "id": "gemini-3-pro-preview", + "name": "Gemini 3 Pro Preview", + "display_name": "Gemini 3 Pro Preview", "modalities": { "input": [ "text", "image", - "pdf" + "audio", + "video" ], "output": [ "text" @@ -320717,51 +320635,42 @@ }, "limit": { "context": 1000000, - "output": 128000 + "output": 65000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "high", - "effort_options": [ + "default_enabled": true, + "mode": "level", + "level": "high", + "level_options": [ "low", - "medium", - "high", - "xhigh", - "max" + "high" ], - "interleaved": true, "summaries": true, - "visibility": "omitted", + "visibility": "summary", "continuation": [ - "thinking_blocks" - ], - "notes": [ - "Claude Opus 4.7 and newer Opus models require thinking.type = \"adaptive\" to enable thinking explicitly.", - "Manual budget_tokens requests return 400 on Claude Opus 4.7 and newer adaptive-only Opus models.", - "task_budget is separate from thinking control and should not be treated as a thinking budget." + "thought_signatures" ] } }, "attachment": true, "open_weights": false, - "knowledge": "2026-01", - "release_date": "2026-05-28", - "last_updated": "2026-05-28", + "knowledge": "2025-11", + "release_date": "2025-11-18", + "last_updated": "2025-11-18", "type": "chat" }, { - "id": "gemini-3.5-flash", - "name": "Gemini 3.5 Flash", - "display_name": "Gemini 3.5 Flash", + "id": "gemini-3-flash-preview", + "name": "Gemini 3 Flash Preview", + "display_name": "Gemini 3 Flash Preview", "modalities": { "input": [ "text", @@ -320806,14 +320715,80 @@ "attachment": true, "open_weights": false, "knowledge": "2025-01", - "release_date": "2026-05-19", - "last_updated": "2026-05-19", + "release_date": "2025-12-17", + "last_updated": "2025-12-17", "type": "chat" }, { - "id": "deepseek-v4-flash:free", - "name": "DeepSeek V4 Flash", - "display_name": "DeepSeek V4 Flash", + "id": "claude-opus-4-5-20251101", + "name": "Claude Opus 4.5", + "display_name": "Claude Opus 4.5", + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 200000, + "output": 32000 + }, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": true, + "default": false + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": false, + "mode": "mixed", + "budget": { + "min": 1024, + "unit": "tokens" + }, + "effort": "high", + "effort_options": [ + "low", + "medium", + "high" + ], + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Claude Opus 4.5 uses manual thinking.type = \"enabled\" with budget_tokens; effort can be used alongside the thinking budget.", + "Interleaved thinking requires the interleaved-thinking-2025-05-14 beta header." + ] + } + }, + "attachment": true, + "open_weights": false, + "knowledge": "2025-03", + "release_date": "2025-11-24", + "last_updated": "2025-11-24", + "type": "chat" + } + ] + }, + "xiaomi": { + "id": "xiaomi", + "name": "Xiaomi", + "display_name": "Xiaomi", + "api": "https://api.xiaomimimo.com/v1", + "doc": "https://platform.xiaomimimo.com/#/docs", + "models": [ + { + "id": "mimo-v2.5-pro-ultraspeed", + "name": "MiMo-V2.5-Pro-UltraSpeed", + "display_name": "MiMo-V2.5-Pro-UltraSpeed", "modalities": { "input": [ "text" @@ -320823,8 +320798,8 @@ ] }, "limit": { - "context": 1000000, - "output": 384000 + "context": 1048576, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -320832,69 +320807,70 @@ "supported": true, "default": true }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } + }, "attachment": false, "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-04-24", - "last_updated": "2026-04-24", + "knowledge": "2024-12", + "release_date": "2026-06-08", + "last_updated": "2026-06-09", "type": "chat" }, { - "id": "gpt-5.2", - "name": "GPT-5.2", - "display_name": "GPT-5.2", + "id": "mimo-v2.6-pro", + "name": "MiMo-V2.6-Pro", + "display_name": "MiMo-V2.6-Pro", "modalities": { "input": [ "text", - "image" + "image", + "audio", + "video" ], "output": [ "text" ] }, "limit": { - "context": 400000, - "output": 128000 + "context": 1048576, + "output": 131072 }, "temperature": true, "tool_call": true, "reasoning": { "supported": true, - "default": false + "default": true }, "extra_capabilities": { "reasoning": { "supported": true, - "default_enabled": false, - "mode": "effort", - "effort": "none", - "effort_options": [ - "none", - "low", - "medium", - "high", - "xhigh" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] } }, "attachment": true, - "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2025-12-11", - "last_updated": "2025-12-11", + "open_weights": true, + "release_date": "2026-09-22", + "last_updated": "2026-09-22", "type": "chat" }, { - "id": "glm-4.5-flash:free", - "name": "GLM-4.5-Flash", - "display_name": "GLM-4.5-Flash", + "id": "mimo-v2.5-pro", + "name": "MiMo-V2.5-Pro", + "display_name": "MiMo-V2.5-Pro", "modalities": { "input": [ "text" @@ -320904,8 +320880,8 @@ ] }, "limit": { - "context": 131072, - "output": 98304 + "context": 1048576, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -320913,27 +320889,83 @@ "supported": true, "default": true }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } + }, "attachment": false, - "open_weights": false, - "knowledge": "2025-04", - "release_date": "2025-07-28", - "last_updated": "2025-07-28", + "open_weights": true, + "knowledge": "2024-12", + "release_date": "2026-04-22", + "last_updated": "2026-04-22", "type": "chat" }, { - "id": "glm-5.2", - "name": "GLM-5.2", - "display_name": "GLM-5.2", + "id": "mimo-v2.6-flash", + "name": "MiMo-V2.6-Flash", + "display_name": "MiMo-V2.6-Flash", "modalities": { "input": [ + "text", + "image", + "audio", + "video" + ], + "output": [ "text" + ] + }, + "limit": { + "context": 1048576, + "output": 131072 + }, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } + }, + "attachment": true, + "open_weights": true, + "release_date": "2026-09-22", + "last_updated": "2026-09-22", + "type": "chat" + }, + { + "id": "mimo-v2.5", + "name": "MiMo-V2.5", + "display_name": "MiMo-V2.5", + "modalities": { + "input": [ + "text", + "image", + "audio", + "video" ], "output": [ "text" ] }, "limit": { - "context": 1000000, + "context": 1048576, "output": 131072 }, "temperature": true, @@ -320944,13 +320976,62 @@ }, "extra_capabilities": { "reasoning": { - "supported": true + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] } }, - "attachment": false, + "attachment": true, "open_weights": true, - "release_date": "2026-06-13", - "last_updated": "2026-06-13", + "knowledge": "2024-12", + "release_date": "2026-04-22", + "last_updated": "2026-04-22", + "type": "chat" + }, + { + "id": "mimo-v2.6-pro-ultraspeed", + "name": "MiMo-V2.6-Pro-UltraSpeed", + "display_name": "MiMo-V2.6-Pro-UltraSpeed", + "modalities": { + "input": [ + "text", + "image", + "audio", + "video" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1048576, + "output": 131072 + }, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } + }, + "attachment": true, + "open_weights": true, + "release_date": "2026-09-21", + "last_updated": "2026-09-21", "type": "chat" } ] @@ -320963,9 +321044,9 @@ "doc": "https://www.kimi.com/code/docs/en/kimi-code/models.html", "models": [ { - "id": "k3", - "name": "Kimi K3", - "display_name": "Kimi K3", + "id": "kimi-for-coding-highspeed", + "name": "Kimi For Coding HighSpeed", + "display_name": "Kimi For Coding HighSpeed", "modalities": { "input": [ "text", @@ -320977,8 +321058,8 @@ ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 262144, + "output": 32768 }, "temperature": false, "tool_call": true, @@ -320994,25 +321075,20 @@ "visibility": "summary", "continuation": [ "thinking_blocks" - ], - "effort_options": [ - "none", - "low", - "high", - "max" ] } }, "attachment": true, "open_weights": true, - "release_date": "2026-07-16", - "last_updated": "2026-07-16", + "knowledge": "2025-01", + "release_date": "2026-06-12", + "last_updated": "2026-06-12", "type": "chat" }, { - "id": "kimi-for-coding-highspeed", - "name": "Kimi For Coding HighSpeed", - "display_name": "Kimi For Coding HighSpeed", + "id": "k3.1-pro", + "name": "Kimi K3.1 Pro", + "display_name": "Kimi K3.1 Pro", "modalities": { "input": [ "text", @@ -321024,8 +321100,8 @@ ] }, "limit": { - "context": 262144, - "output": 32768 + "context": 1048576, + "output": 131072 }, "temperature": false, "tool_call": true, @@ -321041,20 +321117,24 @@ "visibility": "summary", "continuation": [ "thinking_blocks" + ], + "effort_options": [ + "low", + "high", + "max" ] } }, "attachment": true, - "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-06-12", - "last_updated": "2026-06-12", + "open_weights": false, + "release_date": "2026-10-23", + "last_updated": "2026-10-23", "type": "chat" }, { - "id": "kimi-for-coding", - "name": "Kimi K2.8 Preview", - "display_name": "Kimi K2.8 Preview", + "id": "k3", + "name": "Kimi K3", + "display_name": "Kimi K3", "modalities": { "input": [ "text", @@ -321067,7 +321147,7 @@ }, "limit": { "context": 1048576, - "output": 32768 + "output": 131072 }, "temperature": false, "tool_call": true, @@ -321093,9 +321173,9 @@ } }, "attachment": true, - "open_weights": false, - "release_date": "2026-09-11", - "last_updated": "2026-09-11", + "open_weights": true, + "release_date": "2026-07-16", + "last_updated": "2026-07-16", "type": "chat" }, { @@ -321191,9 +321271,9 @@ "type": "chat" }, { - "id": "k3.1-pro", - "name": "Kimi K3.1 Pro", - "display_name": "Kimi K3.1 Pro", + "id": "kimi-for-coding", + "name": "Kimi K2.8 Preview", + "display_name": "Kimi K2.8 Preview", "modalities": { "input": [ "text", @@ -321206,7 +321286,7 @@ }, "limit": { "context": 1048576, - "output": 131072 + "output": 32768 }, "temperature": false, "tool_call": true, @@ -321224,6 +321304,7 @@ "thinking_blocks" ], "effort_options": [ + "none", "low", "high", "max" @@ -321232,8 +321313,8 @@ }, "attachment": true, "open_weights": false, - "release_date": "2026-10-23", - "last_updated": "2026-10-23", + "release_date": "2026-09-11", + "last_updated": "2026-09-11", "type": "chat" }, { @@ -327114,11 +327195,6 @@ "supported": true, "default": false }, - "extra_capabilities": { - "reasoning": { - "supported": true - } - }, "type": "chat" }, { @@ -336264,13 +336340,12 @@ "type": "chat" }, { - "id": "deepseek-v4.1-flash:1017", - "name": "DeepSeek V4.1 Flash 1017", - "display_name": "DeepSeek V4.1 Flash 1017", + "id": "beam", + "name": "Beam 1.0 501B A23B", + "display_name": "Beam 1.0 501B A23B", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" @@ -336278,7 +336353,7 @@ }, "limit": { "context": 1048576, - "output": 384000 + "output": 131072 }, "temperature": true, "tool_call": true, @@ -336288,44 +336363,40 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], "effort_options": [ "low", + "medium", "high", + "xhigh", "max" ] } }, - "attachment": true, + "attachment": false, "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-10-17", - "last_updated": "2026-10-17", + "knowledge": "2026-06-30", + "release_date": "2026-10-05", + "last_updated": "2026-10-16", "type": "chat" }, { - "id": "deepseek-v4-pro", - "name": "deepseek-v4-pro", - "display_name": "deepseek-v4-pro", + "id": "kimi-k2.7-code", + "name": "kimi-k2.7-code", + "display_name": "kimi-k2.7-code", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 1048576 + "context": 262144, + "output": 262144 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -336333,31 +336404,72 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "high", - "effort_options": [ - "low", - "high", - "max" - ], - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "notes": [ - "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." - ] + "supported": true } }, + "attachment": true, + "open_weights": true, + "knowledge": "2025-01", + "release_date": "2026-06-12", + "last_updated": "2026-06-12", + "type": "chat" + }, + { + "id": "nemotron-3-ultra", + "name": "nemotron-3-ultra", + "display_name": "nemotron-3-ultra", + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 262144, + "output": 128000 + }, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": true, + "default": true + }, "attachment": false, "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-04-24", - "last_updated": "2026-04-24", + "knowledge": "2025-09", + "release_date": "2026-06-04", + "last_updated": "2026-06-04", + "type": "chat" + }, + { + "id": "mimo-v2.6-pro", + "name": "MiMo-V2.6-Pro", + "display_name": "MiMo-V2.6-Pro", + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1048576, + "output": 131072 + }, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": true, + "default": true + }, + "attachment": true, + "open_weights": true, + "release_date": "2026-09-22", + "last_updated": "2026-09-22", "type": "chat" }, { @@ -336401,9 +336513,9 @@ "type": "chat" }, { - "id": "kimi-k2.6", - "name": "kimi-k2.6", - "display_name": "kimi-k2.6", + "id": "deepseek-v4.1-flash:1017", + "name": "DeepSeek V4.1 Flash 1017", + "display_name": "DeepSeek V4.1 Flash 1017", "modalities": { "input": [ "text", @@ -336414,8 +336526,8 @@ ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 1048576, + "output": 384000 }, "temperature": true, "tool_call": true, @@ -336425,32 +336537,42 @@ }, "extra_capabilities": { "reasoning": { - "supported": true + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "effort_options": [ + "low", + "high", + "max" + ] } }, "attachment": true, "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-04-20", - "last_updated": "2026-04-20", + "knowledge": "2025-05", + "release_date": "2026-10-17", + "last_updated": "2026-10-17", "type": "chat" }, { - "id": "minimax-m3", - "name": "minimax-m3", - "display_name": "minimax-m3", + "id": "mimo-v2.6-flash", + "name": "MiMo-V2.6-Flash", + "display_name": "MiMo-V2.6-Flash", "modalities": { "input": [ "text", - "image", - "video" + "image" ], "output": [ "text" ] }, "limit": { - "context": 512000, + "context": 1048576, "output": 131072 }, "temperature": true, @@ -336459,12 +336581,46 @@ "supported": true, "default": true }, + "attachment": true, + "open_weights": true, + "release_date": "2026-09-22", + "last_updated": "2026-09-22", + "type": "chat" + }, + { + "id": "deepseek-v4.1-flash", + "name": "DeepSeek V4.1 Flash", + "display_name": "DeepSeek V4.1 Flash", + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1048576, + "output": 384000 + }, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": true, + "default": true + }, "extra_capabilities": { "reasoning": { "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], "effort_options": [ "low", - "medium", "high", "max" ] @@ -336472,28 +336628,27 @@ }, "attachment": true, "open_weights": true, - "knowledge": "2025-01", - "release_date": "2026-06-01", - "last_updated": "2026-06-01", + "knowledge": "2025-05", + "release_date": "2026-09-10", + "last_updated": "2026-09-10", "type": "chat" }, { - "id": "minimax-m3.1-flash", - "name": "MiniMax-M3.1-Flash", - "display_name": "MiniMax-M3.1-Flash", + "id": "mistral-large-4", + "name": "Mistral Large 4", + "display_name": "Mistral Large 4", "modalities": { "input": [ "text", - "image", - "video" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 512000 + "context": 1048576, + "output": 262144 }, "temperature": true, "tool_call": true, @@ -336504,24 +336659,21 @@ "extra_capabilities": { "reasoning": { "effort_options": [ - "low", - "medium", - "high", - "xhigh", - "max" + "none", + "high" ] } }, "attachment": true, "open_weights": false, - "release_date": "2026-10-19", - "last_updated": "2026-10-19", + "release_date": "2026-10-06", + "last_updated": "2026-10-06", "type": "chat" }, { - "id": "minimax-m2.7", - "name": "minimax-m2.7", - "display_name": "minimax-m2.7", + "id": "deepseek-v4-pro", + "name": "deepseek-v4-pro", + "display_name": "deepseek-v4-pro", "modalities": { "input": [ "text" @@ -336531,8 +336683,8 @@ ] }, "limit": { - "context": 196608, - "output": 196608 + "context": 1048576, + "output": 1048576 }, "temperature": true, "tool_call": true, @@ -336543,28 +336695,39 @@ "extra_capabilities": { "reasoning": { "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "high", + "effort_options": [ + "low", + "high", + "max" + ], "interleaved": true, "summaries": true, "visibility": "summary", "continuation": [ "thinking_blocks" + ], + "notes": [ + "The DeepSeek API maps medium and xhigh to high for V4 models; effort_options lists distinct model-effective levels." ] } }, "attachment": false, "open_weights": true, - "release_date": "2026-03-18", - "last_updated": "2026-03-18", + "knowledge": "2025-05", + "release_date": "2026-04-24", + "last_updated": "2026-04-24", "type": "chat" }, { - "id": "deepseek-v4.1-flash", - "name": "DeepSeek V4.1 Flash", - "display_name": "DeepSeek V4.1 Flash", + "id": "deepseek-v4-pro:0813", + "name": "DeepSeek V4 Pro 0813", + "display_name": "DeepSeek V4 Pro 0813", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" @@ -336572,7 +336735,44 @@ }, "limit": { "context": 1048576, - "output": 384000 + "output": 1048576 + }, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "effort_options": [ + "low", + "high", + "max" + ] + } + }, + "attachment": false, + "open_weights": true, + "release_date": "2026-08-13", + "last_updated": "2026-08-22", + "type": "chat" + }, + { + "id": "glm-5.2", + "name": "GLM-5.2", + "display_name": "GLM-5.2", + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 976000, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -336590,37 +336790,36 @@ "thinking_blocks" ], "effort_options": [ - "low", "high", "max" ] } }, - "attachment": true, + "attachment": false, "open_weights": true, - "knowledge": "2025-05", - "release_date": "2026-09-10", - "last_updated": "2026-09-10", + "release_date": "2026-06-13", + "last_updated": "2026-06-13", "type": "chat" }, { - "id": "kimi-k2.7-code", - "name": "kimi-k2.7-code", - "display_name": "kimi-k2.7-code", + "id": "minimax-m3", + "name": "minimax-m3", + "display_name": "minimax-m3", "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 512000, + "output": 131072 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -336628,14 +336827,20 @@ }, "extra_capabilities": { "reasoning": { - "supported": true + "supported": true, + "effort_options": [ + "low", + "medium", + "high", + "max" + ] } }, "attachment": true, "open_weights": true, "knowledge": "2025-01", - "release_date": "2026-06-12", - "last_updated": "2026-06-12", + "release_date": "2026-06-01", + "last_updated": "2026-06-01", "type": "chat" }, { @@ -336668,21 +336873,20 @@ "type": "chat" }, { - "id": "mistral-large-4", - "name": "Mistral Large 4", - "display_name": "Mistral Large 4", + "id": "minimax-m2.7", + "name": "minimax-m2.7", + "display_name": "minimax-m2.7", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 262144 + "context": 196608, + "output": 196608 }, "temperature": true, "tool_call": true, @@ -336692,62 +336896,38 @@ }, "extra_capabilities": { "reasoning": { - "effort_options": [ - "none", - "high" + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" ] } }, - "attachment": true, - "open_weights": false, - "release_date": "2026-10-06", - "last_updated": "2026-10-06", - "type": "chat" - }, - { - "id": "nemotron-3-ultra", - "name": "nemotron-3-ultra", - "display_name": "nemotron-3-ultra", - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 262144, - "output": 128000 - }, - "temperature": true, - "tool_call": true, - "reasoning": { - "supported": true, - "default": true - }, "attachment": false, "open_weights": true, - "knowledge": "2025-09", - "release_date": "2026-06-04", - "last_updated": "2026-06-04", + "release_date": "2026-03-18", + "last_updated": "2026-03-18", "type": "chat" }, { - "id": "beam", - "name": "Beam 1.0 501B A23B", - "display_name": "Beam 1.0 501B A23B", + "id": "minimax-m3.1-flash", + "name": "MiniMax-M3.1-Flash", + "display_name": "MiniMax-M3.1-Flash", "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 1048576, - "output": 131072 + "context": 1000000, + "output": 512000 }, "temperature": true, "tool_call": true, @@ -336766,65 +336946,28 @@ ] } }, - "attachment": false, - "open_weights": true, - "knowledge": "2026-06-30", - "release_date": "2026-10-05", - "last_updated": "2026-10-16", - "type": "chat" - }, - { - "id": "deepseek-v4-pro:0813", - "name": "DeepSeek V4 Pro 0813", - "display_name": "DeepSeek V4 Pro 0813", - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 1048576, - "output": 1048576 - }, - "temperature": true, - "tool_call": true, - "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "effort_options": [ - "low", - "high", - "max" - ] - } - }, - "attachment": false, - "open_weights": true, - "release_date": "2026-08-13", - "last_updated": "2026-08-22", + "attachment": true, + "open_weights": false, + "release_date": "2026-10-19", + "last_updated": "2026-10-19", "type": "chat" }, { - "id": "glm-5.2", - "name": "GLM-5.2", - "display_name": "GLM-5.2", + "id": "kimi-k2.6", + "name": "kimi-k2.6", + "display_name": "kimi-k2.6", "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 976000, - "output": 131072 + "context": 262144, + "output": 262144 }, "temperature": true, "tool_call": true, @@ -336834,23 +336977,14 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ], - "effort_options": [ - "high", - "max" - ] + "supported": true } }, - "attachment": false, + "attachment": true, "open_weights": true, - "release_date": "2026-06-13", - "last_updated": "2026-06-13", + "knowledge": "2025-01", + "release_date": "2026-04-20", + "last_updated": "2026-04-20", "type": "chat" } ] @@ -345570,8 +345704,8 @@ ] }, "limit": { - "context": 8192, - "output": 8192 + "context": 1050000, + "output": 1050000 }, "tool_call": false, "reasoning": { @@ -345586,12 +345720,13 @@ "modalities": { "input": [ "text", - "image" + "image", + "video" ] }, "limit": { - "context": 8192, - "output": 8192 + "context": 1048576, + "output": 1048576 }, "tool_call": false, "reasoning": { @@ -345605,13 +345740,14 @@ "display_name": "kimi-k3.1-pro-highspeed", "modalities": { "input": [ + "text", "image", - "text" + "video" ] }, "limit": { - "context": 8192, - "output": 8192 + "context": 1048576, + "output": 1048576 }, "tool_call": false, "reasoning": { @@ -345706,12 +345842,13 @@ "modalities": { "input": [ "text", - "image" + "image", + "video" ] }, "limit": { - "context": 8192, - "output": 8192 + "context": 1000000, + "output": 1000000 }, "tool_call": false, "reasoning": { @@ -345771,8 +345908,8 @@ ] }, "limit": { - "context": 8192, - "output": 8192 + "context": 1048576, + "output": 1048576 }, "tool_call": false, "reasoning": { @@ -349546,30 +349683,6 @@ }, "type": "chat" }, - { - "id": "laguna-xs-2.1-free", - "name": "laguna-xs-2.1-free", - "display_name": "laguna-xs-2.1-free", - "modalities": { - "input": [ - "text" - ] - }, - "limit": { - "context": 262144, - "output": 262144 - }, - "tool_call": true, - "reasoning": { - "supported": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true - } - }, - "type": "chat" - }, { "id": "deepseek-v4-flash", "name": "deepseek-v4-flash", @@ -349754,30 +349867,6 @@ }, "type": "chat" }, - { - "id": "laguna-s-2.1-free", - "name": "laguna-s-2.1-free", - "display_name": "laguna-s-2.1-free", - "modalities": { - "input": [ - "text" - ] - }, - "limit": { - "context": 1048576, - "output": 1048576 - }, - "tool_call": true, - "reasoning": { - "supported": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true - } - }, - "type": "chat" - }, { "id": "command-a-plus-05-2026", "name": "command-a-plus-05-2026", @@ -352044,8 +352133,8 @@ ] }, "limit": { - "context": 8192, - "output": 8192 + "context": 262144, + "output": 262144 }, "tool_call": false, "reasoning": { @@ -360600,12 +360689,7 @@ }, "tool_call": false, "reasoning": { - "supported": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true - } + "supported": false }, "type": "chat" }, @@ -360624,12 +360708,7 @@ }, "tool_call": false, "reasoning": { - "supported": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true - } + "supported": false }, "type": "chat" }, @@ -365878,7 +365957,7 @@ }, "limit": { "context": 1048576, - "output": 393216 + "output": 943718 }, "temperature": true, "tool_call": true, @@ -367661,7 +367740,7 @@ }, "limit": { "context": 262144, - "output": 235929 + "output": 32768 }, "temperature": true, "tool_call": true, @@ -368350,8 +368429,8 @@ ] }, "limit": { - "context": 131072, - "output": 117964 + "context": 80000, + "output": 72000 }, "tool_call": false, "reasoning": { @@ -368719,7 +368798,7 @@ }, "limit": { "context": 1048576, - "output": 131072 + "output": 943718 }, "tool_call": true, "reasoning": { @@ -370129,50 +370208,6 @@ }, "type": "chat" }, - { - "id": "nousresearch/hermes-3-llama-3.1-405b", - "name": "Nous: Hermes 3 405B Instruct", - "display_name": "Nous: Hermes 3 405B Instruct", - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 16384 - }, - "tool_call": false, - "reasoning": { - "supported": false - }, - "type": "chat" - }, - { - "id": "nousresearch/hermes-3-llama-3.1-70b", - "name": "Nous: Hermes 3 70B Instruct", - "display_name": "Nous: Hermes 3 70B Instruct", - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 16384 - }, - "tool_call": false, - "reasoning": { - "supported": false - }, - "type": "chat" - }, { "id": "nousresearch/hermes-4-405b", "name": "Nous: Hermes 4 405B", @@ -374067,7 +374102,7 @@ }, "limit": { "context": 131072, - "output": 32768 + "output": 117964 }, "tool_call": true, "reasoning": { @@ -374727,122 +374762,6 @@ }, "type": "chat" }, - { - "id": "poolside/laguna-s-2.1", - "name": "Poolside: Laguna S 2.1", - "display_name": "Poolside: Laguna S 2.1", - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 1048576, - "output": 131072 - }, - "tool_call": true, - "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": true - } - }, - "type": "chat" - }, - { - "id": "poolside/laguna-s-2.1:free", - "name": "Poolside: Laguna S 2.1 (free)", - "display_name": "Poolside: Laguna S 2.1 (free)", - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 262144, - "output": 32768 - }, - "tool_call": true, - "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": true - } - }, - "type": "chat" - }, - { - "id": "poolside/laguna-xs-2.1", - "name": "Poolside: Laguna XS 2.1", - "display_name": "Poolside: Laguna XS 2.1", - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 262144, - "output": 32768 - }, - "tool_call": true, - "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": true - } - }, - "type": "chat" - }, - { - "id": "poolside/laguna-xs-2.1:free", - "name": "Poolside: Laguna XS 2.1 (free)", - "display_name": "Poolside: Laguna XS 2.1 (free)", - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 262144, - "output": 32768 - }, - "tool_call": true, - "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "default_enabled": true - } - }, - "type": "chat" - }, { "id": "prism-ml/ternary-bonsai-2-27b", "name": "PrismML: Ternary Bonsai 2 27B", @@ -375323,7 +375242,7 @@ }, "limit": { "context": 262144, - "output": 16384 + "output": 235929 }, "tool_call": true, "reasoning": { @@ -378057,7 +377976,7 @@ ] }, "limit": { - "context": 204800, + "context": 202752, "output": 131072 }, "temperature": true, @@ -378402,6 +378321,46 @@ }, "type": "chat" }, + { + "id": "z-ai/glm-5.3-flashx-legal", + "name": "Z.ai: GLM 5.3 FlashX Legal", + "display_name": "Z.ai: GLM 5.3 FlashX Legal", + "modalities": { + "input": [ + "text", + "image", + "video" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1048576, + "output": 131072 + }, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": true, + "default": true, + "effort": "max" + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "max", + "effort_options": [ + "low", + "high", + "max" + ] + } + }, + "type": "chat" + }, { "id": "z-ai/glm-5.3-prime", "name": "Z.ai: GLM 5.3 Prime", @@ -384913,18 +384872,17 @@ "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 196608, + "context": 200000, "output": 32000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -386064,8 +386022,8 @@ ] }, "limit": { - "context": 1000000, - "output": 384000 + "context": 1050000, + "output": 393216 }, "temperature": true, "tool_call": true, @@ -386109,15 +386067,16 @@ "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 384000 + "context": 1048576, + "output": 32768 }, "temperature": true, "tool_call": true, @@ -386402,8 +386361,8 @@ "input": [ "text", "image", - "video", "audio", + "video", "pdf" ], "output": [ @@ -386411,8 +386370,8 @@ ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 1048000, + "output": 65535 }, "temperature": true, "tool_call": true, @@ -386454,8 +386413,6 @@ "input": [ "text", "image", - "video", - "audio", "pdf" ], "output": [ @@ -386464,7 +386421,7 @@ }, "limit": { "context": 1000000, - "output": 65536 + "output": 65000 }, "temperature": true, "tool_call": true, @@ -386540,8 +386497,6 @@ "input": [ "text", "image", - "video", - "audio", "pdf" ], "output": [ @@ -386550,7 +386505,7 @@ }, "limit": { "context": 1000000, - "output": 65536 + "output": 64000 }, "temperature": true, "tool_call": true, @@ -386592,8 +386547,6 @@ "input": [ "text", "image", - "video", - "audio", "pdf" ], "output": [ @@ -386602,7 +386555,7 @@ }, "limit": { "context": 1000000, - "output": 65536 + "output": 65000 }, "temperature": true, "tool_call": true, @@ -386630,8 +386583,6 @@ "input": [ "text", "image", - "video", - "audio", "pdf" ], "output": [ @@ -386640,7 +386591,7 @@ }, "limit": { "context": 1000000, - "output": 65536 + "output": 64000 }, "temperature": true, "tool_call": true, @@ -386682,8 +386633,6 @@ "input": [ "text", "image", - "video", - "audio", "pdf" ], "output": [ @@ -386691,8 +386640,8 @@ ] }, "limit": { - "context": 1048576, - "output": 65536 + "context": 1000000, + "output": 65535 }, "temperature": true, "tool_call": true, @@ -386734,8 +386683,6 @@ "input": [ "text", "image", - "video", - "audio", "pdf" ], "output": [ @@ -386744,7 +386691,7 @@ }, "limit": { "context": 1000000, - "output": 65536 + "output": 65535 }, "temperature": true, "tool_call": true, @@ -386785,8 +386732,6 @@ "input": [ "text", "image", - "video", - "audio", "pdf" ], "output": [ @@ -386795,7 +386740,7 @@ }, "limit": { "context": 1000000, - "output": 65536 + "output": 1000000 }, "temperature": false, "tool_call": true, @@ -387388,7 +387333,7 @@ ] }, "limit": { - "context": 1048576, + "context": 1000000, "output": 131072 }, "temperature": false, @@ -387579,7 +387524,7 @@ }, "limit": { "context": 1048576, - "output": 131072 + "output": 1048576 }, "temperature": true, "tool_call": true, @@ -387606,9 +387551,7 @@ "input": [ "text", "image", - "video", - "pdf", - "audio" + "pdf" ], "output": [ "text" @@ -387616,9 +387559,8 @@ }, "limit": { "context": 1048576, - "output": 131072 + "output": 1048576 }, - "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -387648,7 +387590,7 @@ }, "limit": { "context": 1048576, - "output": 131072 + "output": 1048576 }, "temperature": true, "tool_call": true, @@ -387884,8 +387826,8 @@ ] }, "limit": { - "context": 205000, - "output": 196608 + "context": 196608, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -387893,12 +387835,6 @@ "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true - } - }, "attachment": false, "open_weights": true, "release_date": "2025-10-27", @@ -387945,7 +387881,7 @@ ] }, "limit": { - "context": 204800, + "context": 196608, "output": 131072 }, "temperature": true, @@ -387956,8 +387892,7 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true + "supported": true } }, "attachment": false, @@ -387980,7 +387915,7 @@ }, "limit": { "context": 204800, - "output": 131000 + "output": 131100 }, "temperature": true, "tool_call": true, @@ -388023,6 +387958,7 @@ }, "attachment": false, "open_weights": true, + "knowledge": "2025-01-01", "release_date": "2026-02-13", "last_updated": "2026-02-13", "type": "chat" @@ -388041,7 +387977,7 @@ }, "limit": { "context": 204800, - "output": 131000 + "output": 131100 }, "temperature": true, "tool_call": true, @@ -388113,7 +388049,7 @@ "input": [ "text", "image", - "pdf" + "video" ], "output": [ "text" @@ -388121,7 +388057,7 @@ }, "limit": { "context": 512000, - "output": 512000 + "output": 131072 }, "temperature": true, "tool_call": true, @@ -388147,7 +388083,8 @@ "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ "text" @@ -388155,7 +388092,7 @@ }, "limit": { "context": 1048576, - "output": 1048576 + "output": 524288 }, "temperature": true, "tool_call": true, @@ -388176,8 +388113,7 @@ "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" @@ -388185,7 +388121,7 @@ }, "limit": { "context": 262144, - "output": 209715 + "output": 262144 }, "temperature": true, "tool_call": true, @@ -388271,7 +388207,7 @@ "input": [ "text", "image", - "video" + "pdf" ], "output": [ "text" @@ -388279,9 +388215,9 @@ }, "limit": { "context": 256000, - "output": 256000 + "output": 32768 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -388307,17 +388243,17 @@ "input": [ "text", "image", - "video" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 262000, - "output": 262000 + "context": 262144, + "output": 32768 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -388373,7 +388309,7 @@ "input": [ "text", "image", - "video" + "pdf" ], "output": [ "text" @@ -388531,7 +388467,7 @@ ] }, "limit": { - "context": 1047576, + "context": 1000000, "output": 32768 }, "temperature": true, @@ -388561,7 +388497,7 @@ ] }, "limit": { - "context": 1047576, + "context": 1000000, "output": 32768 }, "temperature": true, @@ -388673,8 +388609,7 @@ "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" @@ -388777,8 +388712,7 @@ "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" @@ -388828,9 +388762,7 @@ "display_name": "OpenAI: GPT-5 Nano", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" @@ -388867,7 +388799,7 @@ "visibility": "hidden" } }, - "attachment": true, + "attachment": false, "open_weights": false, "knowledge": "2024-05-30", "release_date": "2025-08-07", @@ -388881,8 +388813,7 @@ "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" @@ -388892,7 +388823,7 @@ "context": 400000, "output": 272000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -389082,8 +389013,7 @@ "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" @@ -389135,8 +389065,7 @@ "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" @@ -389144,9 +389073,9 @@ }, "limit": { "context": 400000, - "output": 128000 + "output": 272000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -389343,8 +389272,7 @@ "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" @@ -389396,8 +389324,7 @@ "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" @@ -389449,8 +389376,7 @@ "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" @@ -389460,7 +389386,7 @@ "context": 1050000, "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -389508,10 +389434,10 @@ ] }, "limit": { - "context": 1000000, + "context": 1050000, "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -389560,10 +389486,10 @@ ] }, "limit": { - "context": 1000000, + "context": 1050000, "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -389599,7 +389525,7 @@ "context": 1050000, "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -389653,7 +389579,7 @@ "context": 1050000, "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -389707,7 +389633,7 @@ "context": 1050000, "output": 128000 }, - "temperature": true, + "temperature": false, "tool_call": true, "reasoning": { "supported": true, @@ -390326,7 +390252,7 @@ }, "limit": { "context": 262144, - "output": 65536 + "output": 262144 }, "temperature": true, "tool_call": true, @@ -390345,12 +390271,7 @@ ] } }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-04", - "release_date": "2025-09-23", - "last_updated": "2025-09-23", - "type": "chat" + "type": "imageGeneration" }, { "id": "qwen/qwen3.5-flash", @@ -390367,8 +390288,8 @@ ] }, "limit": { - "context": 1000000, - "output": 65536 + "context": 1020000, + "output": 1020000 }, "temperature": true, "tool_call": true, @@ -390389,6 +390310,7 @@ }, "attachment": true, "open_weights": false, + "knowledge": "2025-01-01", "release_date": "2026-02-23", "last_updated": "2026-02-23", "type": "chat" @@ -390409,7 +390331,7 @@ }, "limit": { "context": 1000000, - "output": 65536 + "output": 64000 }, "temperature": true, "tool_call": true, @@ -390450,14 +390372,13 @@ ] }, "limit": { - "context": 1000000, + "context": 991808, "output": 65536 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true + "supported": true }, "extra_capabilities": { "reasoning": { @@ -390484,7 +390405,8 @@ "input": [ "text", "image", - "video" + "video", + "pdf" ], "output": [ "text" @@ -390492,7 +390414,7 @@ }, "limit": { "context": 1000000, - "output": 65536 + "output": 64000 }, "temperature": true, "tool_call": true, @@ -390533,8 +390455,8 @@ ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 991808, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -390567,7 +390489,7 @@ }, "limit": { "context": 1000000, - "output": 131072 + "output": 64000 }, "temperature": true, "tool_call": true, @@ -390635,7 +390557,7 @@ ] }, "limit": { - "context": 262144, + "context": 1010000, "output": 131072 }, "temperature": true, @@ -390646,7 +390568,13 @@ }, "extra_capabilities": { "reasoning": { - "supported": true + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] } }, "attachment": false, @@ -390662,8 +390590,7 @@ "modalities": { "input": [ "text", - "image", - "video" + "image" ], "output": [ "text" @@ -390671,7 +390598,7 @@ }, "limit": { "context": 262144, - "output": 32768 + "output": 262144 }, "temperature": true, "tool_call": true, @@ -390736,14 +390663,13 @@ ] }, "limit": { - "context": 1000000, - "output": 131072 + "context": 991000, + "output": 65536 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true + "supported": true }, "extra_capabilities": { "reasoning": { @@ -390772,7 +390698,7 @@ ] }, "limit": { - "context": 1000000, + "context": 991808, "output": 131072 }, "temperature": true, @@ -390803,7 +390729,7 @@ ] }, "limit": { - "context": 1000000, + "context": 991808, "output": 131072 }, "temperature": true, @@ -390964,8 +390890,7 @@ "modalities": { "input": [ "text", - "image", - "video" + "image" ], "output": [ "text" @@ -390995,22 +390920,20 @@ "modalities": { "input": [ "text", - "image", - "video" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "output": 1000000 + "context": 1048576, + "output": 1048576 }, "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true + "supported": false }, "attachment": true, "open_weights": true, @@ -391032,7 +390955,7 @@ }, "limit": { "context": 262144, - "output": 262144 + "output": 131072 }, "temperature": true, "tool_call": true, @@ -391064,8 +390987,8 @@ ] }, "limit": { - "context": 1024000, - "output": 64000 + "context": 1000000, + "output": 65536 }, "temperature": true, "tool_call": true, @@ -391156,7 +391079,7 @@ }, "limit": { "context": 1000000, - "output": 900000 + "output": 1000000 }, "temperature": true, "tool_call": true, @@ -391191,7 +391114,7 @@ }, "limit": { "context": 500000, - "output": 450000 + "output": 500000 }, "temperature": true, "tool_call": true, @@ -391217,8 +391140,7 @@ "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" @@ -391226,7 +391148,7 @@ }, "limit": { "context": 500000, - "output": 450000 + "output": 500000 }, "temperature": true, "tool_call": true, @@ -391253,8 +391175,7 @@ "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" @@ -391262,7 +391183,7 @@ }, "limit": { "context": 500000, - "output": 450000 + "output": 500000 }, "temperature": true, "tool_call": true, @@ -391284,8 +391205,7 @@ "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" @@ -391293,13 +391213,12 @@ }, "limit": { "context": 256000, - "output": 230400 + "output": 256000 }, "temperature": true, "tool_call": true, "reasoning": { - "supported": true, - "default": true + "supported": true }, "extra_capabilities": { "reasoning": { @@ -391394,14 +391313,16 @@ "modalities": { "input": [ "text", - "image" + "image", + "audio", + "video" ], "output": [ "text" ] }, "limit": { - "context": 1048576, + "context": 1000000, "output": 131072 }, "temperature": true, @@ -391423,14 +391344,16 @@ "modalities": { "input": [ "text", - "image" + "image", + "audio", + "video" ], "output": [ "text" ] }, "limit": { - "context": 1048576, + "context": 1000000, "output": 131072 }, "temperature": true, @@ -391581,8 +391504,8 @@ ] }, "limit": { - "context": 131072, - "output": 32768 + "context": 200000, + "output": 128000 }, "temperature": true, "tool_call": true, @@ -391590,17 +391513,6 @@ "supported": true, "default": true }, - "extra_capabilities": { - "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] - } - }, "attachment": true, "open_weights": true, "knowledge": "2025-04", @@ -391681,8 +391593,8 @@ ] }, "limit": { - "context": 204800, - "output": 131072 + "context": 200000, + "output": 128000 }, "temperature": true, "tool_call": true, @@ -391721,8 +391633,8 @@ ] }, "limit": { - "context": 202752, - "output": 16384 + "context": 200000, + "output": 128000 }, "temperature": true, "tool_call": true, @@ -391732,13 +391644,7 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "interleaved": true, - "summaries": true, - "visibility": "summary", - "continuation": [ - "thinking_blocks" - ] + "supported": true } }, "attachment": false, @@ -391770,6 +391676,17 @@ "supported": true, "default": true }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } + }, "attachment": false, "open_weights": true, "knowledge": "2025-04", @@ -391790,8 +391707,8 @@ ] }, "limit": { - "context": 204800, - "output": 131072 + "context": 200000, + "output": 128000 }, "temperature": true, "tool_call": true, @@ -391829,8 +391746,8 @@ ] }, "limit": { - "context": 262144, - "output": 131072 + "context": 200000, + "output": 128000 }, "temperature": true, "tool_call": true, @@ -391840,7 +391757,13 @@ }, "extra_capabilities": { "reasoning": { - "supported": true + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] } }, "attachment": false, @@ -391863,7 +391786,7 @@ }, "limit": { "context": 200000, - "output": 131072 + "output": 128000 }, "temperature": true, "tool_call": true, @@ -391873,7 +391796,13 @@ }, "extra_capabilities": { "reasoning": { - "supported": true + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] } }, "attachment": false, @@ -391896,7 +391825,7 @@ }, "limit": { "context": 1000000, - "output": 131072 + "output": 128000 }, "temperature": true, "tool_call": true, @@ -391906,7 +391835,13 @@ }, "extra_capabilities": { "reasoning": { - "supported": true + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] } }, "attachment": false, @@ -391929,7 +391864,7 @@ }, "limit": { "context": 1000000, - "output": 131072 + "output": 128000 }, "temperature": true, "tool_call": true, @@ -391939,7 +391874,13 @@ }, "extra_capabilities": { "reasoning": { - "supported": true + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] } }, "attachment": false, @@ -391956,8 +391897,7 @@ "input": [ "text", "image", - "video", - "pdf" + "video" ], "output": [ "text" @@ -391975,7 +391915,13 @@ }, "extra_capabilities": { "reasoning": { - "supported": true + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] } }, "attachment": true, @@ -392001,7 +391947,7 @@ }, "limit": { "context": 1000000, - "output": 131072 + "output": 128000 }, "temperature": true, "tool_call": true, @@ -392009,6 +391955,17 @@ "supported": true, "default": true }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } + }, "attachment": true, "open_weights": true, "release_date": "2026-08-26", @@ -392059,8 +392016,8 @@ ] }, "limit": { - "context": 202752, - "output": 131072 + "context": 200000, + "output": 128000 }, "temperature": true, "tool_call": true, @@ -392068,6 +392025,17 @@ "supported": true, "default": true }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } + }, "attachment": true, "open_weights": false, "release_date": "2026-04-01", diff --git a/rust-toolchain.toml b/rust-toolchain.toml new file mode 100644 index 0000000000..12dd5258cd --- /dev/null +++ b/rust-toolchain.toml @@ -0,0 +1,4 @@ +[toolchain] +channel = "1.95.0" +profile = "minimal" +components = ["rustfmt", "clippy"] diff --git a/scripts/afterPack.js b/scripts/afterPack.js index 2c835fed18..c0b38a2e3e 100644 --- a/scripts/afterPack.js +++ b/scripts/afterPack.js @@ -835,9 +835,13 @@ async function encodeMacVssExtension(context) { async function afterPack(context) { const { targets, appOutDir } = context + await validateProviderCatalog(context) + await validateAcpRegistry(context) + await copyFffNativePackages(context) await copyParcelWatcherNativePackages(context) await copyOpendalNativePackages(context) + await validateHarnessDuckDbNativePackage(context) await packageLightOcrAssets(context) await validateNativeKitPrebuilds(context) await encodeMacVssExtension(context) @@ -847,4 +851,79 @@ async function afterPack(context) { } } +export async function validateProviderCatalog(context) { + const catalogPath = path.join( + getResourcesDir(context), + 'app.asar.unpacked', + 'resources', + 'model-db', + 'providers.json' + ) + const info = await fs.lstat(catalogPath) + if (!info.isFile() || info.isSymbolicLink()) { + throw new Error('Bundled Provider catalog is not a regular file') + } + const catalog = await readJson(catalogPath) + if ( + !catalog.providers || + typeof catalog.providers !== 'object' || + Array.isArray(catalog.providers) || + Object.keys(catalog.providers).length === 0 + ) { + throw new Error('Bundled Provider catalog is empty or invalid') + } +} + +export async function validateAcpRegistry(context) { + const directory = path.join( + getResourcesDir(context), + 'app.asar.unpacked', + 'resources', + 'acp-registry' + ) + const manifestPath = path.join(directory, 'registry.json') + const info = await fs.lstat(manifestPath) + if (!info.isFile() || info.isSymbolicLink()) { + throw new Error('Bundled ACP registry is not a regular file') + } + const manifest = await readJson(manifestPath) + if ( + typeof manifest.version !== 'string' || + !manifest.version.trim() || + !Array.isArray(manifest.agents) || + !manifest.agents.length + ) { + throw new Error('Bundled ACP registry is empty or invalid') + } + for (const agent of manifest.agents) { + if ( + !agent.icon?.startsWith('https://cdn.agentclientprotocol.com/registry/') || + !agent.icon.endsWith('.svg') + ) + continue + if (typeof agent.id !== 'string' || !/^[A-Za-z0-9._-]+$/.test(agent.id)) { + throw new Error('Bundled ACP registry icon has an invalid agent ID') + } + const icon = await fs.lstat(path.join(directory, 'icons', `${agent.id}.svg`)) + if (!icon.isFile() || icon.isSymbolicLink()) { + throw new Error('Bundled ACP registry icon is not a regular file') + } + } +} + +export async function validateHarnessDuckDbNativePackage(context) { + const unpacked = path.join(getResourcesDir(context), 'app.asar.unpacked') + const harness = path.join(unpacked, 'harness') + if (!(await pathExists(harness))) return + const packageName = `@duckdb/node-bindings-${context.electronPlatformName}-${getArchName(context.arch)}` + const sourceDir = path.join(unpacked, 'node_modules', packageName) + const nativePackage = await readJson(path.join(sourceDir, 'package.json')) + const apiPackage = await readJson( + path.join(unpacked, 'node_modules', '@duckdb/node-api', 'package.json') + ) + if (nativePackage.name !== packageName || nativePackage.version !== apiPackage.version) { + throw new Error('Harness DuckDB native package identity does not match its API') + } +} + export default afterPack diff --git a/scripts/agent-cleanup-guard.mjs b/scripts/agent-cleanup-guard.mjs index 39e7c27360..b527715569 100644 --- a/scripts/agent-cleanup-guard.mjs +++ b/scripts/agent-cleanup-guard.mjs @@ -72,7 +72,7 @@ const DEEPCHAT_HARNESS_PUBLIC_EXPORTS = new Set([ 'DeepChatHarnessDependencies', 'DeepChatHarnessSkillPort' ]) -const DEEPCHAT_AGENT_HARNESS_MAX_LINES = 350 +const DEEPCHAT_AGENT_HARNESS_MAX_LINES = 360 const DEEPCHAT_PENDING_INPUTS_FILE = path.join(ROOT, 'src/main/session/data/pendingInputs.ts') const DEEPCHAT_AGENT_INSTANCE_FILE = path.join( ROOT, @@ -135,7 +135,7 @@ const DEEPCHAT_ROOT_OWNERSHIP_RULES = [ name, ownerFile: DEEPCHAT_PENDING_INPUTS_FILE })), - ...['tryAcquirePendingQueueDrain', 'releasePendingQueueDrain'].map((name) => ({ + ...['beginInput', 'endInput'].map((name) => ({ name, ownerFile: DEEPCHAT_AGENT_INSTANCE_FILE })) @@ -145,7 +145,6 @@ const DEEPCHAT_ROOT_OWNERSHIP_RULES = [ kind: 'operation-controller-lifecycle', calls: [ { name: 'ensureOperationController', ownerFile: DEEPCHAT_RUN_LIFECYCLE_FILE }, - { name: 'setAbortController', ownerFile: DEEPCHAT_AGENT_INSTANCE_FILE }, { name: 'clearAbortController', ownerFile: DEEPCHAT_AGENT_INSTANCE_FILE } ] } @@ -476,6 +475,7 @@ async function findViolations() { const scanRoots = [ path.join(ROOT, 'src/main/agent'), path.join(ROOT, 'src/main/app/composition.ts'), + path.join(ROOT, 'src/harness/core'), path.join(ROOT, 'src/main/skill'), path.join(ROOT, 'src/main/mcp/toolManager.ts'), path.join(ROOT, 'src/main/sync/index.ts'), diff --git a/scripts/build-harness.mjs b/scripts/build-harness.mjs new file mode 100644 index 0000000000..76f5d61cc9 --- /dev/null +++ b/scripts/build-harness.mjs @@ -0,0 +1,257 @@ +#!/usr/bin/env node +import { spawnSync } from 'node:child_process' +import { cp, mkdir, readFile, copyFile, chmod } from 'node:fs/promises' +import { builtinModules, createRequire } from 'node:module' +import path from 'node:path' +import { fileURLToPath, pathToFileURL } from 'node:url' +import { build } from 'vite' + +const repositoryRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..') +const require = createRequire(import.meta.url) +const targets = { + 'darwin-arm64': 'aarch64-apple-darwin', + 'darwin-x64': 'x86_64-apple-darwin', + 'win32-arm64': 'aarch64-pc-windows-msvc', + 'win32-x64': 'x86_64-pc-windows-msvc', + 'linux-arm64': 'aarch64-unknown-linux-gnu', + 'linux-x64': 'x86_64-unknown-linux-gnu' +} + +export const harnessExternalModules = [ + ...builtinModules, + /^node:/, + 'better-sqlite3-multiple-ciphers', + '@duckdb/node-api', + '@duckdb/node-bindings', + 'sharp', + '@ff-labs/fff-node', + 'opendal', + 'pdf-parse-new', + 'node-pty', + '@larksuiteoapi/node-sdk' +] + +export async function buildHarness({ + platform = process.platform, + arch = process.arch, + outDir = path.join(repositoryRoot, 'out', 'harness'), + plugins = [], + additionalPackages = new Map() +} = {}) { + const target = targets[`${platform}-${arch}`] + if (!target) throw new Error(`Unsupported harness target: ${platform}-${arch}`) + const crate = path.join(repositoryRoot, 'crates', 'deepchat-harness') + const output = path.resolve(outDir) + const result = spawnSync( + 'cargo', + [ + 'build', + '--locked', + '--release', + '--target', + target, + '--manifest-path', + path.join(crate, 'Cargo.toml') + ], + { cwd: repositoryRoot, stdio: 'inherit' } + ) + if (result.error) throw result.error + if (result.status !== 0) throw new Error(`Harness build failed for ${target}`) + await build({ + configFile: false, + root: repositoryRoot, + publicDir: false, + plugins: [ + ...plugins, + { + name: 'harness-module-path', + load(id) { + if (!id.endsWith('?modulePath')) return null + const reference = this.emitFile({ type: 'chunk', id: id.slice(0, -11) }) + return `import { fileURLToPath } from 'node:url' +export default fileURLToPath(import.meta.ROLLUP_FILE_URL_${reference})` + } + } + ], + resolve: { + conditions: ['node'], + mainFields: ['main', 'module'], + alias: { + '@': path.join(repositoryRoot, 'src', 'main'), + '@shared': path.join(repositoryRoot, 'src', 'shared') + } + }, + build: { + target: 'node24', + outDir: output, + emptyOutDir: true, + copyPublicDir: false, + lib: { + entry: { + core: path.join(repositoryRoot, 'src', 'harness', 'plugins', 'core.ts'), + memory: path.join(repositoryRoot, 'src', 'harness', 'plugins', 'memory.ts'), + hooks: path.join(repositoryRoot, 'src', 'harness', 'plugins', 'hooks.ts'), + 'memory-vector': path.join( + repositoryRoot, + 'src', + 'harness', + 'plugins', + 'memory-vector.ts' + ) + }, + formats: ['es'], + fileName: (_, name) => `${name}.mjs` + }, + rollupOptions: { + external: [...harnessExternalModules, ...additionalPackages.keys()] + } + } + }) + const extension = platform === 'win32' ? '.exe' : '' + const binary = path.join(output, `deepchat-harness-${platform}-${arch}${extension}`) + await copyFile( + path.join(crate, 'target', target, 'release', `deepchat-harness${extension}`), + binary + ) + await chmod(binary, 0o755) + const sqlite = path.dirname(require.resolve('better-sqlite3-multiple-ciphers/package.json')) + const sqliteOutput = path.join(output, 'node_modules', 'better-sqlite3-multiple-ciphers') + await mkdir(path.join(sqliteOutput, 'prebuilds'), { recursive: true }) + await copyFile(path.join(sqlite, 'package.json'), path.join(sqliteOutput, 'package.json')) + await cp(path.join(sqlite, 'lib'), path.join(sqliteOutput, 'lib'), { recursive: true }) + await copyFile( + path.join(sqlite, 'prebuilds', `${platform}-${arch}.node`), + path.join(sqliteOutput, 'prebuilds', `${platform}-${arch}.node`) + ) + if (platform === 'linux') { + await copyFile( + path.join(sqlite, 'prebuilds', `linuxmusl-${arch}.node`), + path.join(sqliteOutput, 'prebuilds', `linuxmusl-${arch}.node`) + ) + } + const runtimePackage = require.resolve('@arcships/rutis-runtime/package.json') + const runtimeRoot = path.dirname(runtimePackage) + const runtimeOutput = path.join(output, 'node_modules', '@arcships', 'rutis-runtime') + await mkdir(runtimeOutput, { recursive: true }) + await copyFile(runtimePackage, path.join(runtimeOutput, 'package.json')) + await cp(path.join(runtimeRoot, 'src'), path.join(runtimeOutput, 'src'), { recursive: true }) + const runtimeRequire = createRequire(runtimePackage) + // The compiled first-party runtime needs Cordis and WebSocket support, not the TS generator. + const apiPackage = require.resolve('@duckdb/node-api/package.json') + const apiRequire = createRequire(apiPackage) + const bindingsPackage = apiRequire.resolve('@duckdb/node-bindings/package.json') + const bindingsRequire = createRequire(bindingsPackage) + const packages = new Map([ + ...additionalPackages, + ...['@deepseek-ai/cordis', '@deepseek-ai/cosmokit', 'ws'].map((name) => [ + name, + runtimeRequire.resolve(`${name}/package.json`) + ]), + ['@duckdb/node-api', apiPackage], + ['@duckdb/node-bindings', bindingsPackage], + ['detect-libc', bindingsRequire.resolve('detect-libc/package.json')], + [ + `@duckdb/node-bindings-${platform}-${arch}`, + bindingsRequire.resolve(`@duckdb/node-bindings-${platform}-${arch}/package.json`) + ] + ]) + const sharpPackage = path.resolve(path.dirname(require.resolve('sharp')), '../package.json') + const sharpRequire = createRequire(sharpPackage) + const fffPackage = require.resolve('@ff-labs/fff-node/package.json') + const fffRequire = createRequire(fffPackage) + const ffiPackage = fffRequire.resolve('ffi-rs/package.json') + const ffiRequire = createRequire(ffiPackage) + const nativePlatform = platform === 'linux' ? `linux-${arch}-gnu` : `${platform}-${arch}` + const napiPlatform = platform === 'win32' ? `${nativePlatform}-msvc` : nativePlatform + packages.set('sharp', sharpPackage) + packages.set('@ff-labs/fff-node', fffPackage) + packages.set('ffi-rs', ffiPackage) + packages.set(`@ff-labs/fff-bin-${nativePlatform}`, + fffRequire.resolve(`@ff-labs/fff-bin-${nativePlatform}/package.json`)) + packages.set(`@yuuang/ffi-rs-${napiPlatform}`, + ffiRequire.resolve(`@yuuang/ffi-rs-${napiPlatform}/package.json`)) + packages.set('@img/colour', path.join(path.dirname(sharpRequire.resolve('@img/colour')), 'package.json')) + packages.set('semver', sharpRequire.resolve('semver/package.json')) + packages.set(`@img/sharp-${platform}-${arch}`, + sharpRequire.resolve(`@img/sharp-${platform}-${arch}/package`)) + if (platform !== 'win32') + packages.set(`@img/sharp-libvips-${platform}-${arch}`, + sharpRequire.resolve(`@img/sharp-libvips-${platform}-${arch}/package`)) + const opendalPackage = require.resolve('opendal/package.json') + packages.set('opendal', opendalPackage) + packages.set(`@opendal/lib-${napiPlatform}`, + createRequire(opendalPackage).resolve(`@opendal/lib-${napiPlatform}/package.json`)) + const pdfPackage = require.resolve('pdf-parse-new/package.json') + const pdfRequire = createRequire(pdfPackage) + const debugPackage = pdfRequire.resolve('debug/package.json') + packages.set('pdf-parse-new', pdfPackage) + packages.set('node-ensure', pdfRequire.resolve('node-ensure/package.json')) + packages.set('debug', debugPackage) + packages.set('ms', createRequire(debugPackage).resolve('ms/package.json')) + packages.set('node-pty', require.resolve('node-pty/package.json')) + packages.set('@larksuiteoapi/node-sdk', require.resolve('@larksuiteoapi/node-sdk/package.json')) + for (const [name, packageFile] of packages) { + const source = path.dirname(packageFile) + const metadata = JSON.parse(await readFile(packageFile, 'utf8')) + const dependencyRequire = createRequire(packageFile) + for (const dependency of Object.keys(metadata.dependencies ?? {})) { + let dependencyFile + try { + dependencyFile = dependencyRequire.resolve(`${dependency}/package.json`) + } catch (error) { + if (error.code !== 'ERR_PACKAGE_PATH_NOT_EXPORTED') throw error + let directory = path.dirname(dependencyRequire.resolve(dependency)) + while (true) { + const file = path.join(directory, 'package.json') + try { + if (JSON.parse(await readFile(file, 'utf8')).name === dependency) { + dependencyFile = file + break + } + } catch (error) { + if (error.code !== 'ENOENT') throw error + } + const parent = path.dirname(directory) + if (parent === directory) throw new Error(`Cannot locate ${dependency}`) + directory = parent + } + } + if (!packages.has(dependency)) packages.set(dependency, dependencyFile) + else if (JSON.parse(await readFile(packages.get(dependency), 'utf8')).version !== + JSON.parse(await readFile(dependencyFile, 'utf8')).version) + packages.set(`${name}/node_modules/${dependency}`, dependencyFile) + } + await cp(source, path.join(output, 'node_modules', name), { + recursive: true, + dereference: true, + filter: (file) => { + const relative = path.relative(source, file) + const parts = relative.split(path.sep) + return ( + !parts.includes('node_modules') && + !['test', 'tests'].includes(parts[0]) && + !/\.(?:map|pdb|d\.[cm]?ts)$/.test(relative) + ) + } + }) + } + const pkg = JSON.parse(await readFile(runtimePackage, 'utf8')) + if (pkg.version !== '0.16.0') throw new Error('Rutis SDK/runtime version mismatch') + return target +} + +export default async function beforePack(context) { + const arch = + typeof context.arch === 'string' + ? context.arch + : new Map([ + [1, 'x64'], + [3, 'arm64'] + ]).get(context.arch) + if (!arch) throw new Error(`Unsupported harness package architecture: ${context.arch}`) + await buildHarness({ platform: context.electronPlatformName, arch }) +} + +if (process.argv[1] && pathToFileURL(path.resolve(process.argv[1])).href === import.meta.url) { + await buildHarness() +} diff --git a/scripts/build-headless.mjs b/scripts/build-headless.mjs new file mode 100644 index 0000000000..9ae7cfff47 --- /dev/null +++ b/scripts/build-headless.mjs @@ -0,0 +1,238 @@ +#!/usr/bin/env node +import { access, cp, mkdir, readFile, readdir, rename, rm, writeFile } from 'node:fs/promises' +import { spawnSync } from 'node:child_process' +import { createRequire } from 'node:module' +import path from 'node:path' +import { fileURLToPath, pathToFileURL } from 'node:url' +import { build } from 'vite' +import { buildHarness, harnessExternalModules } from './build-harness.mjs' +import { buildCli } from './build-cli.mjs' + +const repositoryRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..') +const require = createRequire(import.meta.url) +const electronPackage = /^(?:electron(?:$|[/-])|@electron(?:-toolkit)?\/)/ +const electronImport = + /\b(?:from\s*|import\s*|(?:require|import)\s*\(\s*)['"](?:electron(?:[/-][^'"]*)?|@electron(?:-toolkit)?\/[^'"]*)['"]/ +const licenseName = /^(?:licen[sc]es?|copying|notice)(?:[._-]|$)/i + +async function* files(directory) { + for (const entry of await readdir(directory, { withFileTypes: true })) { + const file = path.join(directory, entry.name) + if (entry.isSymbolicLink()) throw new Error(`Headless distribution contains a symlink: ${file}`) + if (entry.isDirectory()) yield* files(file) + else yield file + } +} + +export async function buildHeadless() { + const metadata = JSON.parse(await readFile(path.join(repositoryRoot, 'package.json'), 'utf8')) + const output = path.join(repositoryRoot, 'out', 'headless') + const harness = path.join(output, 'harness') + const packageFiles = new Set() + const directories = new Set() + const boundary = { + name: 'headless-dependencies', + enforce: 'pre', + resolveId(source, importer) { + if (electronPackage.test(source)) { + throw new Error(`Electron import is unavailable in headless: ${source} from ${importer}`) + } + }, + async moduleParsed({ id }) { + if (!path.isAbsolute(id) || !id.includes(`${path.sep}node_modules${path.sep}`)) return + let directory = path.dirname(id.split('?')[0]) + while (!directories.has(directory)) { + directories.add(directory) + const file = path.join(directory, 'package.json') + try { + if (JSON.parse(await readFile(file, 'utf8')).name) { + packageFiles.add(file) + break + } + } catch (error) { + if (error.code !== 'ENOENT' && error.code !== 'ENOTDIR') throw error + } + const parent = path.dirname(directory) + if (parent === directory) break + directory = parent + } + } + } + await rm(output, { recursive: true, force: true }) + const watcher = require.resolve('@parcel/watcher/package.json') + const watcherNative = `@parcel/watcher-${process.platform}-${process.arch}${process.platform === 'linux' ? '-glibc' : ''}` + const additionalPackages = new Map([ + ['@parcel/watcher', watcher], + [watcherNative, createRequire(watcher).resolve(`${watcherNative}/package.json`)] + ]) + const target = await buildHarness({ outDir: harness, plugins: [boundary], additionalPackages }) + await rename(path.join(harness, 'node_modules'), path.join(output, 'node_modules')) + await build({ + configFile: false, + root: repositoryRoot, + publicDir: false, + plugins: [boundary], + resolve: { + conditions: ['node'], + mainFields: ['main', 'module'], + alias: { + '@': path.join(repositoryRoot, 'src', 'main'), + '@shared': path.join(repositoryRoot, 'src', 'shared') + } + }, + define: { __DEEPCHAT_HOST_VERSION__: JSON.stringify(metadata.version) }, + build: { + target: 'node24', + outDir: output, + emptyOutDir: false, + copyPublicDir: false, + lib: { + entry: { + index: path.join(repositoryRoot, 'src', 'headless', 'index.ts'), + helper: path.join(repositoryRoot, 'src', 'headless', 'helper.ts') + }, + formats: ['es'], + fileName: (_, name) => `${name}.mjs` + }, + rollupOptions: { external: [...harnessExternalModules, ...additionalPackages.keys()] } + } + }) + await buildCli({ outDir: path.join(output, 'cli') }) + await writeFile( + path.join(output, 'cli', 'deepchat'), + `#!/bin/sh +set -eu +case "$0" in + */*) script_dir=\${0%/*} ;; + *) script_dir=. ;; +esac +script_dir=$(CDPATH= cd -P -- "$script_dir" && pwd) +exec node "$script_dir/deepchat.mjs" "$@" +`, + { mode: 0o755 } + ) + await writeFile( + path.join(output, 'cli', 'deepchat.cmd'), + '@echo off\r\nnode "%~dp0deepchat.mjs" %*\r\nexit /b %errorlevel%\r\n' + ) + for (const relative of [ + 'resources/model-db', + 'resources/acp-registry', + 'resources/skills', + 'resources/runtime-versions.json', + 'runtime/duckdb', + 'LICENSE' + ]) { + const target = path.join(output, relative) + await mkdir(path.dirname(target), { recursive: true }) + await cp(path.join(repositoryRoot, relative), target, { recursive: true, dereference: true }) + } + // These two packages are copied selectively by the shared Harness build. + for (const name of ['better-sqlite3-multiple-ciphers', '@arcships/rutis-runtime']) { + packageFiles.add(require.resolve(`${name}/package.json`)) + } + for await (const file of files(output)) { + if (path.basename(file) === 'package.json') { + const dependency = JSON.parse(await readFile(file, 'utf8')) + if (electronPackage.test(dependency.name ?? '')) { + throw new Error(`Electron dependency is unavailable in headless: ${file}`) + } + if (dependency.name && dependency.version) packageFiles.add(file) + } else if (/\.[cm]?js$/.test(file) && electronImport.test(await readFile(file, 'utf8'))) { + throw new Error(`Electron import is unavailable in headless: ${file}`) + } + } + const dependencies = [] + for (const file of packageFiles) { + const dependency = JSON.parse(await readFile(file, 'utf8')) + dependencies.push({ + name: dependency.name, + version: dependency.version, + license: dependency.license, + source: `npm:${dependency.name}@${dependency.version}`, + directory: path.dirname(file) + }) + } + const cargo = spawnSync( + 'cargo', + [ + 'metadata', + '--offline', + '--locked', + '--format-version', + '1', + '--filter-platform', + target, + '--manifest-path', + path.join(repositoryRoot, 'crates', 'deepchat-harness', 'Cargo.toml') + ], + { cwd: repositoryRoot, encoding: 'utf8', maxBuffer: 16 * 1024 * 1024 } + ) + if (cargo.error) throw cargo.error + if (cargo.status !== 0) throw new Error(`Cannot inventory Rust licenses: ${cargo.stderr}`) + for (const dependency of JSON.parse(cargo.stdout).packages) { + dependencies.push({ + name: dependency.name, + version: dependency.version, + license: dependency.license, + source: dependency.source ?? `cargo:${dependency.name}@${dependency.version}`, + directory: path.dirname(dependency.manifest_path), + licenseFile: dependency.license_file + }) + } + const licenses = new Map() + for (const dependency of dependencies) { + const name = `${dependency.source.startsWith('npm:') ? 'npm' : 'cargo'}__${dependency.name}@${dependency.version}` + if (licenses.has(name)) continue + const source = dependency.directory + const target = path.join(output, 'licenses', name.replaceAll('/', '__')) + const entries = (await readdir(source)).filter((entry) => licenseName.test(entry)) + if (dependency.licenseFile && !entries.includes(dependency.licenseFile)) { + try { + await access(path.resolve(source, dependency.licenseFile)) + entries.push(dependency.licenseFile) + } catch (error) { + if (error.code !== 'ENOENT') throw error + } + } + for (const entry of entries) { + await mkdir(target, { recursive: true }) + await cp(path.resolve(source, entry), path.join(target, path.basename(entry)), { + recursive: true, + dereference: true + }) + } + licenses.set(name, { + name: dependency.name, + version: dependency.version, + license: dependency.license, + source: dependency.source, + files: entries.map((entry) => + path.relative(output, path.join(target, path.basename(entry))).split(path.sep).join('/') + ) + }) + } + await writeFile( + path.join(output, 'licenses.json'), + `${JSON.stringify( + [...licenses.values()].sort((a, b) => a.name.localeCompare(b.name)), + null, + 2 + )}\n` + ) + const distributionPackage = { + name: 'deepchat-headless', + version: metadata.version, + private: true, + type: 'module', + engines: { node: metadata.engines.node } + } + await writeFile( + path.join(output, 'package.json'), + `${JSON.stringify(distributionPackage, null, 2)}\n` + ) +} + +if (process.argv[1] && pathToFileURL(path.resolve(process.argv[1])).href === import.meta.url) { + await buildHeadless() +} diff --git a/scripts/ci/classify-package-impact.mjs b/scripts/ci/classify-package-impact.mjs index 4fe009c258..b16006c6ab 100644 --- a/scripts/ci/classify-package-impact.mjs +++ b/scripts/ci/classify-package-impact.mjs @@ -22,6 +22,8 @@ const sharedPackagePaths = new Set([ 'resources/runtime-versions.json', 'scripts/afterPack.js', 'scripts/build-cli.mjs', + 'scripts/build-harness.mjs', + 'rust-toolchain.toml', 'scripts/build-cua-plugin-runtime.mjs', 'scripts/compare-light-ocr-package-size.mjs', 'scripts/install-runtime.mjs', @@ -30,6 +32,7 @@ const sharedPackagePaths = new Set([ 'scripts/package-plugin.mjs', 'scripts/plugin.mjs', 'scripts/smoke-duckdb-vss.js', + 'scripts/smoke-harness-memory.mjs', 'scripts/smoke-light-ocr.js', 'scripts/smoke-opendal-native.js', 'scripts/ci/check-package-size.mjs', @@ -80,7 +83,11 @@ const packageImpactRules = Object.freeze([ matches: (changedPath) => sharedPackagePaths.has(changedPath) || changedPath.startsWith('.github/actions/light-ocr-package-size/') || - changedPath.startsWith('plugins/') + changedPath.startsWith('plugins/') || + changedPath.startsWith('crates/deepchat-harness/') || + changedPath.startsWith('src/harness/') || + changedPath.startsWith('src/main/harness/') || + changedPath.startsWith('src/shared/harness/') }, { id: 'windows-package-input', diff --git a/scripts/smoke-harness-memory.mjs b/scripts/smoke-harness-memory.mjs new file mode 100644 index 0000000000..0ef3a3f562 --- /dev/null +++ b/scripts/smoke-harness-memory.mjs @@ -0,0 +1,145 @@ +#!/usr/bin/env node +import assert from 'node:assert/strict' +import { execFileSync } from 'node:child_process' +import { mkdtemp, rm } from 'node:fs/promises' +import os from 'node:os' +import path from 'node:path' +import { fileURLToPath, pathToFileURL } from 'node:url' +import { parseArgs } from 'node:util' +import { build } from 'vite' + +const repositoryRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..') +const { values } = parseArgs({ + args: process.argv.slice(2).filter((arg) => arg !== '--'), + options: { 'resources-path': { type: 'string' }, executable: { type: 'string' } } +}) +assert(values['resources-path'], '--resources-path is required') +assert(values.executable, '--executable is required') +const resourcesPath = path.resolve(values['resources-path']) +const electronExecutable = path.resolve(values.executable) +const expectedVersions = JSON.parse( + execFileSync(electronExecutable, ['-p', 'JSON.stringify(process.versions)'], { + encoding: 'utf8', + env: { ...process.env, ELECTRON_RUN_AS_NODE: '1', NODE_OPTIONS: '', NODE_PATH: '' } + }) +) +const temporaryRoot = await mkdtemp(path.join(os.tmpdir(), 'deepchat-harness-smoke-')) + +try { + const outDir = path.join(temporaryRoot, 'client') + await build({ + configFile: false, + root: repositoryRoot, + publicDir: false, + resolve: { + alias: { + '@': path.join(repositoryRoot, 'src', 'main'), + '@shared': path.join(repositoryRoot, 'src', 'shared') + } + }, + build: { + target: 'node24', + outDir, + emptyOutDir: true, + copyPublicDir: false, + lib: { + entry: Object.fromEntries( + ['runtime', 'admission', 'memory-vector'].map((name) => [ + name, + path.join(repositoryRoot, 'src', 'main', 'harness', `${name}.ts`) + ]) + ), + formats: ['es'], + fileName: (_, name) => `${name}.mjs` + }, + rollupOptions: { external: [/^node:/] } + }, + logLevel: 'warn' + }) + const { HarnessRuntime } = await import(pathToFileURL(path.join(outDir, 'runtime.mjs')).href) + const { RutisAgentInvocationAdmission } = await import( + pathToFileURL(path.join(outDir, 'admission.mjs')).href + ) + const { RutisMemoryVectorStores } = await import( + pathToFileURL(path.join(outDir, 'memory-vector.mjs')).href + ) + const embedding = { providerId: 'smoke', modelId: 'smoke' } + for (const restarting of [false, true]) { + const runtime = await HarnessRuntime.start({ + directory: path.join(resourcesPath, 'app.asar.unpacked', 'harness'), + electronExecutable, + capacity: 1, + maxPending: 4, + observe: () => {} + }) + const admission = RutisAgentInvocationAdmission.create({ runtime, capacity: 1, maxPending: 4 }) + const stores = new RutisMemoryVectorStores(runtime, { + memoryDbDir: path.join(temporaryRoot, 'memory'), + host: { + appPath: path.join(resourcesPath, 'app.asar'), + userDataPath: temporaryRoot, + isPackaged: true + } + }) + try { + assert.equal(runtime.memoryIdentity, undefined) + await stores.resetVectorStore('unused') + assert.equal(runtime.memoryIdentity, undefined) + const identity = runtime.identity + const permit = await admission.acquire({ ownerId: 'packaged-memory-smoke' }) + try { + const store = await stores.createVectorStore('smoke', embedding, 2) + assert.equal(store.isUsable(), true) + assert(![process.pid, identity.hostPid].includes(runtime.memoryIdentity.pid)) + assert.equal(runtime.memoryIdentity.modules, expectedVersions.modules) + assert.equal(runtime.memoryIdentity.node, expectedVersions.node) + if (restarting) { + assert.deepEqual(await store.listMemoryIds(null, 512), ['other']) + } else { + await store.upsert([ + { memoryId: 'anchor', embedding: [1, 0] }, + { memoryId: 'other', embedding: [1, 0.5] } + ]) + const matches = await store.query([1, 0], { topK: 2 }) + assert.deepEqual( + matches.map((match) => match.memoryId), + ['anchor', 'other'] + ) + assert.equal(matches[0].distance, 0) + assert(Math.abs(matches[1].distance - (1 - 2 / Math.sqrt(5))) < 1e-6) + await store.deleteByMemoryIds(['anchor']) + } + await store.close() + if (restarting) { + await stores.resetVectorStore('smoke') + const cleared = await stores.createVectorStore('smoke', embedding, 2) + assert.deepEqual(await cleared.listMemoryIds(null, 512), []) + await cleared.close() + } + assert.deepEqual(runtime.identity, identity) + console.log('[Harness Smoke] packaged runtime:', { + restarting, + ...identity, + memory: runtime.memoryIdentity + }) + } finally { + permit.release() + } + await admission.run({ ownerId: 'after-memory' }, async () => {}) + assert.equal(runtime.memoryTransport.pending, 0) + } finally { + try { + await admission.close() + await stores.dispose() + } finally { + await runtime.stop() + } + for (const pid of [runtime.identity.hostPid, runtime.memoryIdentity?.pid]) { + if (pid) assert.throws(() => process.kill(pid, 0), { code: 'ESRCH' }) + } + } + } + console.log('[Harness Smoke] packaged native read/write, restart, reset and shutdown passed') +} finally { + await rm(temporaryRoot, { recursive: true, force: true }) +} diff --git a/scripts/smoke-headless.mjs b/scripts/smoke-headless.mjs new file mode 100644 index 0000000000..93e1583472 --- /dev/null +++ b/scripts/smoke-headless.mjs @@ -0,0 +1,370 @@ +#!/usr/bin/env node +import assert from 'node:assert/strict' +import { execFile, spawn } from 'node:child_process' +import { randomBytes } from 'node:crypto' +import { once } from 'node:events' +import { access, cp, mkdir, mkdtemp, readFile, rm, writeFile } from 'node:fs/promises' +import { createServer } from 'node:http' +import os from 'node:os' +import path from 'node:path' +import { setTimeout as delay } from 'node:timers/promises' +import { fileURLToPath } from 'node:url' +import { promisify } from 'node:util' + +assert.equal(process.versions.electron, undefined, 'Run this smoke with ordinary Node') +const repository = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..') +const directory = await mkdtemp(path.join(os.tmpdir(), 'deepchat-headless-smoke-')) +const distribution = path.join(directory, 'distribution') +const profile = path.join(directory, 'profile') +const descriptorPath = path.join(profile, 'local-control', 'local-control.json') +const home = path.join(directory, 'home') +const temporary = path.join(directory, 'tmp') +const children = [] +const held = new Set() +const requests = new Map() +const key = randomBytes(32).toString('base64') +const env = Object.fromEntries( + ['PATH', 'Path', 'SystemRoot', 'WINDIR', 'COMSPEC', 'PATHEXT', 'LANG', 'LC_ALL'] + .filter((name) => process.env[name] !== undefined) + .map((name) => [name, process.env[name]]) +) +Object.assign(env, { + HOME: home, + USERPROFILE: home, + APPDATA: path.join(home, 'roaming'), + LOCALAPPDATA: path.join(home, 'local'), + XDG_CONFIG_HOME: path.join(home, 'config'), + XDG_CACHE_HOME: path.join(home, 'cache'), + XDG_DATA_HOME: path.join(home, 'data'), + XDG_RUNTIME_DIR: temporary, + TMPDIR: temporary, + TEMP: temporary, + TMP: temporary, + DEEPCHAT_USER_DATA_DIR: profile, + NODE_OPTIONS: '', + NODE_PATH: '', + NO_PROXY: '127.0.0.1' +}) + +async function within(promise, label) { + let timer + try { + return await Promise.race([ + promise, + new Promise((_, reject) => { + timer = setTimeout(() => reject(new Error(label)), 45_000) + }) + ]) + } finally { + clearTimeout(timer) + } +} +async function until(read, label) { + const deadline = Date.now() + 45_000 + do { + const value = await read() + if (value) return value + await delay(50) + } while (Date.now() < deadline) + throw new Error(label) +} +function alive(pid) { + try { + process.kill(pid, 0) + return true + } catch (error) { + if (error.code === 'ESRCH') return false + throw error + } +} +function launch() { + const child = spawn( + process.execPath, + [path.join(distribution, 'index.mjs'), '--profile', profile], + { + cwd: directory, + env: { ...env, DEEPCHAT_HOST_KEY: key }, + stdio: ['ignore', 'pipe', 'pipe'], + windowsHide: true + } + ) + const owner = { child, stdout: '', stderr: '', ready: null } + child.stdout.on('data', (chunk) => { + owner.stdout = (owner.stdout + chunk).slice(-65536) + for (const line of owner.stdout.split('\n')) { + try { + const value = JSON.parse(line) + if (value.ready === true) owner.ready = value + } catch {} + } + }) + child.stderr.on('data', (chunk) => { + owner.stderr = (owner.stderr + chunk).slice(-65536) + }) + owner.exit = new Promise((resolve) => { + child.once('error', (error) => resolve({ error })) + child.once('close', (code, signal) => resolve({ code, signal })) + }) + children.push(owner) + return owner +} +async function boot() { + const owner = launch() + await until(() => { + if (owner.child.exitCode !== null || owner.child.signalCode !== null) + throw new Error(`Headless startup failed: ${owner.stderr}`) + return owner.ready + }, 'Headless did not become ready') + assert.equal(owner.ready.pid, owner.child.pid) + assert.equal(owner.ready.profileDirectory, profile) + assert(Number.isSafeInteger(owner.ready.harnessPid)) + await access(descriptorPath) + owner.corePid = (await cli('system', 'status')).pid + assert.notEqual(owner.corePid, owner.child.pid) + return owner +} +async function cli(...args) { + const { stdout } = await promisify(execFile)( + process.execPath, + [path.join(distribution, 'cli', 'deepchat.mjs'), ...args, '--json', '--timeout', '15000'], + { cwd: directory, env, timeout: 20_000, maxBuffer: 1024 * 1024, windowsHide: true } + ) + const response = JSON.parse(stdout) + assert.equal(response.ok, true, JSON.stringify(response)) + return response.result +} +async function submit(prompt) { + const run = await cli( + 'agent', + 'run', + '--agent', + 'deepchat', + '--provider', + 'custom-headless-smoke', + '--model', + 'headless-smoke', + '--prompt', + prompt, + '--max-turns', + '1' + ) + assert.equal(typeof run.runId, 'string') + return run.runId +} +async function snapshot(runId, predicate) { + return await until(async () => { + const value = await cli('run', 'get', '--run', runId) + assert.equal(value.runId, runId) + return predicate(value) && value + }, `Run ${runId} did not reach its expected state`) +} +async function stop(owner, signal) { + owner.child.kill(signal) + const result = await within(owner.exit, 'Headless owner did not exit') + assert.equal(result.error, undefined) + if (signal !== 'SIGKILL' && process.platform !== 'win32') { + assert.equal(result.code, 0, owner.stderr) + assert.equal(result.signal, null) + } + for (const pid of [owner.child.pid, owner.ready.harnessPid, owner.corePid]) { + await until(() => !alive(pid), `Owned process ${pid} survived shutdown`) + } +} + +const server = createServer(async (request, response) => { + if (request.url !== '/v1/chat/completions') return response.writeHead(404).end() + let body = '' + try { + for await (const chunk of request) body += chunk + } catch { + response.destroy() + return + } + const input = JSON.parse(body) + const prompt = JSON.stringify(input.messages) + const marker = ['HEADLESS_CANCEL', 'HEADLESS_RESTART'].find((value) => prompt.includes(value)) + const common = { id: 'headless-fixture', created: 1, model: 'headless-smoke' } + if (!input.stream) + return response + .writeHead(200, { 'Content-Type': 'application/json' }) + .end( + JSON.stringify({ + ...common, + object: 'chat.completion', + choices: [ + { + index: 0, + finish_reason: 'stop', + message: { role: 'assistant', content: 'Headless smoke' } + } + ] + }) + ) + const chunk = (content, finish_reason) => + `data: ${JSON.stringify({ + ...common, + object: 'chat.completion.chunk', + choices: [{ index: 0, delta: { role: 'assistant', content }, finish_reason }] + })}\n\n` + response.writeHead(200, { 'Content-Type': 'text/event-stream' }) + response.write(chunk(marker ? 'HEADLESS_PARTIAL' : 'HEADLESS_OK', null)) + if (!marker) return response.end(chunk('', 'stop') + 'data: [DONE]\n\n') + held.add(marker) + requests.set(marker, (requests.get(marker) ?? 0) + 1) + response.once('close', () => held.delete(marker)) +}) + +try { + for (const target of [profile, home, temporary]) await mkdir(target, { recursive: true }) + await cp(path.join(repository, 'out', 'headless'), distribution, { + recursive: true, + dereference: true + }) + await promisify(execFile)( + process.execPath, + [ + '--input-type=commonjs', + '-e', + "if(process.versions.electron)throw Error('Electron runtime');try{require.resolve('electron');throw Error('Electron resolved')}catch(e){if(e.code!=='MODULE_NOT_FOUND')throw e}" + ], + { cwd: distribution, env } + ) + server.listen(0, '127.0.0.1') + await once(server, 'listening') + await writeFile( + path.join(profile, 'app-settings.json'), + JSON.stringify({ + init_complete: true, + privacyModeEnabled: true, + loggingEnabled: false, + providers: [ + { + id: 'custom-headless-smoke', + name: 'Headless fixture', + custom: true, + apiType: 'openai-completions', + baseUrl: `http://127.0.0.1:${server.address().port}/v1`, + apiKey: 'fixture-key', + enable: true + } + ] + }) + ) + await mkdir(path.join(profile, 'provider_models')) + await writeFile( + path.join(profile, 'provider_models', 'models_custom-headless-smoke.json'), + JSON.stringify({ + models: [], + custom_models: [ + { + id: 'headless-smoke', + name: 'Headless smoke', + providerId: 'custom-headless-smoke', + enabled: true, + vision: false, + functionCall: false, + reasoning: false, + contextLength: 32000, + maxTokens: 1000 + } + ] + }) + ) + const first = await boot() + const metadata = JSON.parse(await readFile(path.join(distribution, 'package.json'), 'utf8')) + assert.equal((await cli('system', 'version')).appVersion, metadata.version) + const contender = launch() + const rejected = await within(contender.exit, 'A second profile owner was not rejected') + assert.notEqual(rejected.code, 0) + assert.equal(contender.ready, null) + assert.match(contender.stderr, /already has an owner|CoreProfileOwnershipUnavailable/) + const cancelled = await submit('HEADLESS_CANCEL') + await until(() => held.has('HEADLESS_CANCEL'), 'Cancelled Run never entered the Provider') + await snapshot(cancelled, (value) => value.phase === 'running') + assert.equal((await cli('run', 'cancel', '--run', cancelled)).cancelRequested, true) + await snapshot(cancelled, (value) => value.phase === 'terminal') + assert.equal((await cli('run', 'cancel', '--run', cancelled)).cancelRequested, false) + const interrupted = await submit('HEADLESS_RESTART') + await until(() => held.has('HEADLESS_RESTART'), 'Interrupted Run never entered the Provider') + await snapshot(interrupted, (value) => + value.messages.some((message) => message.role === 'assistant' && message.status === 'pending') + ) + await stop(first, 'SIGKILL') + const restarted = await boot() + const recovered = await snapshot(interrupted, (value) => value.phase === 'terminal') + assert( + recovered.messages.some((message) => message.role === 'assistant' && message.status === 'error') + ) + assert( + recovered.messages.some( + (message) => message.role === 'user' && message.text === 'HEADLESS_RESTART' + ) + ) + assert.equal(requests.get('HEADLESS_RESTART'), 1, 'Recovery must not replay a Provider request') + const completed = await submit('HEADLESS_COMPLETE') + const result = await snapshot( + completed, + (value) => + value.phase === 'terminal' && + value.messages.some( + (message) => message.role === 'assistant' && message.text === 'HEADLESS_OK' + ) + ) + assert( + result.messages.some( + (message) => message.role === 'assistant' && message.text === 'HEADLESS_OK' + ) + ) + await stop(restarted, 'SIGTERM') + if (process.platform !== 'win32') { + await assert.rejects(access(descriptorPath), { code: 'ENOENT' }) + } else { + let staleDescriptor = false + try { + const descriptor = JSON.parse(await readFile(descriptorPath, 'utf8')) + assert.equal(descriptor.pid, restarted.corePid) + assert.equal(alive(descriptor.pid), false) + staleDescriptor = true + } catch (error) { + if (error.code !== 'ENOENT') throw error + } + await assert.rejects(cli('system', 'status'), (error) => { + const response = JSON.parse(error.stdout) + assert.equal(response.error.code, 'unavailable') + if (staleDescriptor) assert.match(response.error.message, /stopped process/) + return true + }) + } + console.log( + JSON.stringify({ + ok: true, + platform: process.platform, + arch: process.arch, + node: process.versions.node, + gracefulSignal: process.platform !== 'win32', + checks: [ + 'outside-checkout', + 'no-electron', + 'ready', + 'version', + 'profile-exclusion', + 'run-query-cancel', + 'owner-death-recovery', + 'no-replay', + 'completed-run', + 'owned-process-cleanup' + ] + }) + ) +} finally { + server.closeAllConnections() + for (const owner of children) { + if (owner.child.exitCode === null && owner.child.signalCode === null) + owner.child.kill('SIGKILL') + } + await Promise.allSettled( + children.map((owner) => within(owner.exit, 'Smoke owner cleanup timed out')) + ) + if (server.listening) await new Promise((resolve) => server.close(resolve)) + await rm(directory, { recursive: true, force: true, maxRetries: 10, retryDelay: 200 }) +} diff --git a/src/cli/discovery.ts b/src/cli/discovery.ts index 6b270c0088..43f15ccd35 100644 --- a/src/cli/discovery.ts +++ b/src/cli/discovery.ts @@ -39,7 +39,7 @@ function resolveDefaultProfilePath( export function resolveCliUserDataPath(options: CliDiscoveryOptions = {}): string { const env = options.env ?? process.env - const explicitPath = env[EXPLICIT_PROFILE_ENV]?.trim() + const explicitPath = env.DEEPCHAT_USER_DATA_DIR?.trim() || env[EXPLICIT_PROFILE_ENV]?.trim() if (explicitPath) return path.resolve(explicitPath) return resolveDefaultProfilePath( env, diff --git a/src/harness/core/admission.ts b/src/harness/core/admission.ts new file mode 100644 index 0000000000..52eaf17fc4 --- /dev/null +++ b/src/harness/core/admission.ts @@ -0,0 +1,30 @@ +import { z } from 'zod' +import { RutisAgentInvocationAdmission } from '@/harness/admission' +import type { AgentInvocationAdmissionDiagnosticsOptions } from '@shared/harness/admission' + +export interface CoreAdmissionService { + admission(generation: string, sequence: number, method: string, args: unknown[]): Promise +} + +export async function createCoreAdmission( + service: CoreAdmissionService, + generation: string, + diagnostics?: AgentInvocationAdmissionDiagnosticsOptions +): Promise { + const limits = z + .object({ + capacity: z.number().int().positive().max(Number.MAX_SAFE_INTEGER), + maxPending: z.number().int().nonnegative().max(Number.MAX_SAFE_INTEGER) + }) + .strict() + .parse(await service.admission(generation, 0, 'limits', [])) + let sequence = 0 + return RutisAgentInvocationAdmission.create({ + ...limits, + diagnostics, + runtime: { + identity: undefined, + request: async (method, args) => await service.admission(generation, ++sequence, method, args) + } + }) +} diff --git a/src/harness/core/browser.ts b/src/harness/core/browser.ts new file mode 100644 index 0000000000..9dec99de18 --- /dev/null +++ b/src/harness/core/browser.ts @@ -0,0 +1,163 @@ +import { randomUUID } from 'node:crypto' +import { z } from 'zod' +import type { AgentBrowserToolPort } from '@/tool/runtimePorts' +import { getYoBrowserToolDefinitions } from '@/tool/browser/definitions' +import { YoBrowserUnavailableError } from '@/tool/browser/errors' +import { + CoreBrowserEventSchema, + coreBrowserError, + type CoreBrowserAuthorization, + type CoreBrowserCommand, + type CoreBrowserOutcome +} from '@shared/harness/browser' +import { CORE_NATIVE_CALLS, CORE_NATIVE_QUEUED } from '@shared/harness/core' + +type Call = { + signal: AbortSignal + authorize?: Parameters[4] + authorized: boolean + authorizationError?: unknown + outcome: ReturnType> +} + +export class CoreNativeBrowser implements AgentBrowserToolPort { + private readonly calls = new Map() + private readonly pending = new Set>() + private closing: Promise | undefined + private failure: Error | undefined + + constructor( + private readonly invoke: (command: CoreBrowserCommand) => Promise, + private readonly failed: (error: Error) => void + ) {} + + getToolDefinitions() { + return getYoBrowserToolDefinitions() + } + + callTool(...args: Parameters): Promise { + const operation = this.execute(...args) + this.pending.add(operation) + void operation.then( + () => this.pending.delete(operation), + () => this.pending.delete(operation) + ) + return operation + } + + async accept(input: unknown): Promise { + const event = CoreBrowserEventSchema.parse(input) + const call = this.calls.get(event.id) + if (!call || this.failure) throw this.fail(new Error('Unknown Native browser call')) + if (event.type === 'browser.completed') { + call.outcome.resolve(event.outcome) + return undefined + } + if (!call.authorize || call.authorized) + throw this.fail(new Error('Unexpected Native browser authorization')) + call.authorized = true + try { + call.signal.throwIfAborted() + if (this.closing) throw new Error('Core browser is stopping') + await call.authorize(event.args) + call.signal.throwIfAborted() + if (this.closing) throw new Error('Core browser is stopping') + return { ok: true } + } catch (error) { + call.authorizationError = error + return { ok: false, error: coreBrowserError(error) } + } + } + + close(): Promise { + if (this.closing) return this.closing + this.closing = (async () => { + await this.invoke({ operation: 'close' }) + for (const call of this.calls.values()) + call.outcome.reject(new Error('Native browser has stopped')) + await Promise.allSettled(this.pending) + })() + return this.closing + } + + private async execute( + toolName: string, + args: Record, + conversationId?: string, + runId?: string, + authorize?: Parameters[4], + signal: AbortSignal = new AbortController().signal + ): Promise { + signal.throwIfAborted() + if (this.failure || this.closing) throw this.failure ?? new Error('Core browser is stopping') + if (this.calls.size >= CORE_NATIVE_QUEUED + CORE_NATIVE_CALLS - 2) + throw new Error('Core browser call limit exceeded') + const id = randomUUID() + const command = z.enum(['get_browser_status', 'load_url', 'cdp_send']).parse(toolName) + const call: Call = { + signal, + authorize, + authorized: false, + outcome: Promise.withResolvers() + } + void call.outcome.promise.catch(() => {}) + this.calls.set(id, call) + let admitted = false + let cancellation: Promise | undefined + const abort = () => { + if (!admitted || cancellation) return + cancellation = this.invoke({ operation: 'cancel', id }) + void cancellation.catch((error) => this.fail(error)) + } + signal.addEventListener('abort', abort, { once: true }) + try { + z.object({ accepted: z.literal(true) }) + .strict() + .parse( + await this.invoke({ + operation: 'open', + id, + toolName: command, + args, + conversationId, + runId, + authorize: Boolean(authorize) + }) + ) + admitted = true + if (signal.aborted) abort() + const outcome = await call.outcome.promise + await cancellation + signal.throwIfAborted() + if ('authorizationError' in call) throw call.authorizationError + if (!outcome.ok) { + if (outcome.unavailable) throw new YoBrowserUnavailableError(outcome.unavailable) + const error = new Error(outcome.error.message) + error.name = outcome.error.name + throw error + } + return outcome.value + } catch (error) { + if (!admitted) { + try { + await this.invoke({ operation: 'cancel', id }) + } catch (failure) { + this.fail(failure) + } + } + throw error + } finally { + signal.removeEventListener('abort', abort) + this.calls.delete(id) + } + } + + private fail(input: unknown): Error { + if (this.failure) return this.failure + const error = input instanceof Error ? input : new Error('Core browser delivery failed') + this.failure = error + for (const call of this.calls.values()) call.outcome.reject(error) + this.failed(error) + return error + } +} diff --git a/src/harness/core/control.ts b/src/harness/core/control.ts new file mode 100644 index 0000000000..095c68a0a4 --- /dev/null +++ b/src/harness/core/control.ts @@ -0,0 +1,196 @@ +import { + HARNESS_CONTROL_METADATA_BYTES, + HARNESS_CONTROL_PENDING, + HarnessControlCommandSchema, + HarnessControlReceiptSchema, + type HarnessControlCommand, + type HarnessControlPort, + type HarnessControlReceipt +} from '@shared/harness/control' + +export interface CoreControlService { + control(generation: string, sequence: number, command: HarnessControlCommand): Promise +} + +export class CoreHarnessControl implements HarnessControlPort { + private sequence = 0 + private tail: Promise = Promise.resolve() + private pending = 0 + private pendingBytes = 0 + private failure: Error | undefined + private stopped = false + private readonly listeners = new Set<() => void>() + private readonly grants = new Map void>() + + constructor( + private readonly service: CoreControlService, + private readonly generation: string, + private readonly failOwner: () => void + ) {} + + get closed(): boolean { + return this.stopped || this.failure !== undefined + } + + onClose(listener: () => void): () => void { + if (this.closed) listener() + else this.listeners.add(listener) + return () => this.listeners.delete(listener) + } + + onGrant(scopeId: string, listener: (leaseId: string) => void): () => void { + if (this.grants.has(scopeId)) throw new Error('Duplicate control scope listener') + this.grants.set(scopeId, listener) + return () => { + this.grants.delete(scopeId) + } + } + + private applyGrants(receipt: HarnessControlReceipt): void { + for (const grant of receipt.grants) this.grants.get(grant.scopeId)?.(grant.leaseId) + } + + send( + input: HarnessControlCommand, + receive?: (receipt: HarnessControlReceipt) => void + ): Promise { + return this.enqueue(input, async (command) => { + const receipt = await this.request(command) + receive?.(receipt) + this.applyGrants(receipt) + return receipt + }) + } + + transition( + input: Extract, + receive: (receipt: HarnessControlReceipt) => boolean + ): Promise { + return this.enqueue(input, async (command) => { + if (command.method !== 'prepareOperation' && command.method !== 'prepareRun') { + throw new Error('Expected a control transition') + } + const deadline = performance.now() + 10_000 + const prepared = await this.request(command, deadline) + const aborted = receive(prepared) + this.applyGrants(prepared) + if (prepared.outcome !== 'accepted') return prepared + const result = prepared.result as Record | null + if (result?.reused === true && typeof result.operationId === 'string') return prepared + if (typeof result?.transitionId !== 'string') { + throw new Error('Harness control transition has no acknowledgement identity') + } + const committed = await this.request( + { method: 'commit', scopeId: command.scopeId, transitionId: result.transitionId, aborted }, + deadline + ) + receive(committed) + this.applyGrants(committed) + return committed + }) + } + + private enqueue( + input: HarnessControlCommand, + work: (command: HarnessControlCommand) => Promise + ): Promise { + if (this.closed) return Promise.reject(this.failure ?? new Error('Harness control stopped')) + let command: HarnessControlCommand + let bytes: number + try { + command = HarnessControlCommandSchema.parse(input) + bytes = Buffer.byteLength(JSON.stringify(command)) + const cleanup = [ + 'abort', + 'operationAborted', + 'abortDeferred', + 'ackInputEffect', + 'endInput', + 'clearOperation', + 'clearRun', + 'releaseLease', + 'clearSession', + 'parkInteraction', + 'releaseAcceptance', + 'retire', + 'release' + ].includes(command.method) + if ( + this.pending >= HARNESS_CONTROL_PENDING - (cleanup ? 0 : 16) || + this.pendingBytes + bytes > HARNESS_CONTROL_METADATA_BYTES - (cleanup ? 0 : 64 * 1024) + ) { + throw new Error('Harness control command queue exceeded its bound') + } + } catch (error) { + return Promise.reject(this.fail(error)) + } + this.pending++ + this.pendingBytes += bytes + const task = this.tail.then(async () => { + if (this.closed) throw this.failure ?? new Error('Harness control stopped') + try { + return await work(command) + } catch (error) { + if (this.stopped) throw error + throw this.fail(error) + } + }) + this.tail = task.then( + () => undefined, + () => undefined + ) + return task.finally(() => { + this.pending-- + this.pendingBytes -= bytes + }) + } + + private async request( + command: HarnessControlCommand, + deadline = performance.now() + 10_000 + ): Promise { + let timer: ReturnType | undefined + try { + const receipt = await Promise.race([ + this.service.control(this.generation, ++this.sequence, command), + new Promise((_, reject) => { + timer = setTimeout( + () => reject(new Error('Harness control request timed out')), + Math.max(0, deadline - performance.now()) + ) + timer.unref() + }) + ]) + if (this.closed) throw this.failure ?? new Error('Harness control stopped') + return HarnessControlReceiptSchema.parse(receipt) + } finally { + clearTimeout(timer) + } + } + + stop(): void { + if (this.stopped) return + this.stopped = true + this.fence() + } + + private fail(cause: unknown): Error { + if (this.failure) return this.failure + this.failure = cause instanceof Error ? cause : new Error(String(cause)) + this.fence() + if (!this.stopped) this.failOwner() + return this.failure + } + + private fence(): void { + for (const listener of this.listeners) { + try { + listener() + } catch { + // One local cleanup cannot keep another effect handle alive. + } + } + this.listeners.clear() + this.grants.clear() + } +} diff --git a/src/harness/core/controller.ts b/src/harness/core/controller.ts new file mode 100644 index 0000000000..ade1959ed7 --- /dev/null +++ b/src/harness/core/controller.ts @@ -0,0 +1,487 @@ +import logger from '@shared/logger' +import type { DeepchatEventName } from '@shared/contracts/events' +import { projectEnvironmentsChangedEvent, sessionsUpdatedEvent } from '@shared/contracts/events' +import type { initializeCoreProfile } from '@/app/profileStartup' +import type { DatabaseRequestGate } from '@/app/databaseRequestGate' +import type { StartupWorkloadCoordinator } from '@/app/startupWorkloadCoordinator' +import { createRouteDispatcher } from '@/routes' +import { createNodeScheduler } from '@/routes/scheduler' +import { createProviderRoutes } from '@/provider/routes' +import { ProviderImportService } from '@/provider/providerImportService' +import { createToolRoutes } from '@/tool/routes' +import { createPluginRoutes } from '@/plugin/routes' +import { createSkillRoutes } from '@/skill/routes' +import { createMcpRoutes } from '@/mcp/routes' +import { createRemoteRoutes } from '@/remote/routes' +import { createSchedulerRoutes } from '@/scheduler/routes' +import { createMemoryRoutes } from '@/memory/routes' +import { createFileRoutes } from '@/file/routes' +import { createOcrRoutes } from '@/ocr/routes' +import { createToolchainRoutes } from '@/toolchains/routes' +import { createKnowledgeRoutes } from '@/knowledge/routes' +import { createWorkspaceRoutes } from '@/workspace/routes' +import { createOrchestrationRoutes } from '@/orchestration/routes' +import { createLivePort } from './execution' +import { createProjectRoutes } from '@/project/routes' +import { createSessionRoutes } from '@/session/routes' +import { createAgentRoutes } from '@/agent/routes' +import { createPromptRoutes } from '@/agent/promptRoutes' +import { createAcpRoutes } from '@/agent/acp/routes' +import { toAppSessionId } from '@/agent/shared/agentSessionIds' +import { createOnboardingRoutes } from '@/onboarding/routes' +import { createExporterRoutes } from '@/exporter/routes' +import { createSyncRoutes } from '@/sync/routes' +import { createSyncHostRoutes } from '@/sync/host/routes' +import { createSyncPeerRoutes } from '@/sync/peer/routes' +import { createPlatformRoutes } from '@/platform/routes' +import type { ProxySettingMode } from '@/platform/proxySettings' +import { createHookRoutes } from '@/hook/routes' +import { createAppSettingsRoutes } from '@/app/settingsRoutes' +import { createAppRoutes } from '@/app/routes' +import { createCliRoutes, createArtifactRoutes, createCliComputeRoutes } from '@/cli' +import { createApprovalRoutes } from '@/approval/routes' +import { createCliProviderModelAdminRoutes } from '@/cli/providerModelAdminRoutes' +import { createCliMcpAdminRoutes } from '@/cli/mcpAdminRoutes' +import { ImportMode } from '@/sync' +import { OAuthService, type GitHubCopilotAuthorizationPort } from './oauth' +import type { ArtifactSpool } from '@/cli/artifactSpool' +import type { createCoreMaintenance } from './maintenance' +import type { createCoreData } from './data' +import type { createCoreProviders } from './providers' +import type { createCoreRuntime } from './runtime' +import type { createCoreSettings } from './settings' + +export type CoreControllerNative = Pick< + Parameters[0], + 'isMainWindowContext' | 'rendererPerformance' | 'splash' +> & { + isSettingsWindow(windowId: number | null): Promise + pluginSettingsWindow: Parameters[1] + images: Parameters[1] + pickToolchainPath: Parameters[0]['pickPath'] + hasTrustedRenderer: Parameters[0]['hasTrustedRenderer'] + applyProxy(mode: ProxySettingMode, url: string, resolve: boolean): Promise + copilotAuthorization: GitHubCopilotAuthorizationPort + sessionPresentation: Parameters[0]['presentation'] +} + +export function createCoreController(options: { + profile: Pick< + Awaited>, + 'settingsStore' | 'proxySettings' | 'databaseSecurityService' | 'database' + > + data: ReturnType + providers: ReturnType + settings: ReturnType + runtime: Awaited> + bootstrap: { temporaryDirectory: string; isPackaged: boolean; appVersion: string } + databaseRequests: DatabaseRequestGate + startupWorkloadCoordinator: StartupWorkloadCoordinator + artifactSpool: ArtifactSpool + maintenance: ReturnType + native: CoreControllerNative + publishEvent(name: DeepchatEventName, payload: unknown): void +}) { + const { + profile, + data, + providers, + settings, + runtime, + bootstrap, + databaseRequests, + startupWorkloadCoordinator, + artifactSpool, + maintenance, + native, + publishEvent: publishDeepchatEvent + } = options + const { + settingsDatabase, + providerSettings, + sessionData, + memoryDatabase, + promptSettings, + tapeInspectorHeadWatcher, + usageStatsService + } = data + const { providerRuntime, agentSettings, acpAuthService, acpRuntimeOwner, cliComputeService } = + providers + const acpProviderAdminPort = providerRuntime + const { appSettingsService, skillSettings, commandShellService, recordSettingsActivity } = + settings + const { + execution, + toolService, + pluginService, + skillService, + skillSyncService, + mcpService, + remoteService, + cronJobs, + memoryService, + fileService, + ocrRuntimeService, + toolchainService, + knowledgeService, + knowledgeSettings, + workspaceService, + projectService, + sessionPermissionPort, + exporter, + syncService, + syncSettings, + syncHostService, + syncPeerService, + hookService, + cliServer, + cliMutationGuard, + cliSkillService, + ensureSkillServicesInitialized + } = runtime + const { + deepChatAgentHarness, + orchestrationCapabilityResolver, + sessionAssignment, + sessionLifecycle, + sessionQuery, + desktopSessionBinding, + sessionTurn, + sessionHistorySearch, + agentSessionExportService, + sessionTranslation, + acpAgentRuntime, + cliRunService + } = execution + const { database: mainDatabase, databaseSecurityService } = profile + const { run: runDatabaseMaintenance, pullLatestBackupFromCloud } = maintenance + const oauthService = new OAuthService( + { + getGitHubCopilotSignInTarget: async (providerId) => { + if (databaseRequests.state !== 'running') + throw new Error(`App database maintenance is ${databaseRequests.state}`) + return await providerRuntime.getGitHubCopilotSignInTarget(providerId) + }, + commitGitHubCopilotCredential: async (target, key) => { + if (databaseRequests.state !== 'running') + throw new Error(`App database maintenance is ${databaseRequests.state}`) + await providerRuntime.commitGitHubCopilotCredential(target, key) + }, + invalidateGitHubCopilotSignIns: () => providerRuntime.invalidateGitHubCopilotSignIns() + }, + providers.accounts, + native.copilotAuthorization + ) + const providerQueryScheduler = createNodeScheduler() + const providerRoutes = createProviderRoutes({ + providerSettings, + providerRuntime, + acpProviderAdminPort, + providerImportService: new ProviderImportService({ + getProviders: () => providerSettings.getProviders(), + getDefaultProviders: () => providerSettings.getDefaultProviders(), + addCustomModel: (providerId, model) => { + const added = providerSettings.addCustomModel(providerId, model) + providerRuntime.invalidateProviderCatalogFromSync(providerId) + return added + }, + updateProvidersBatch: (batchUpdate) => providerRuntime.updateProvidersBatch(batchUpdate) + }), + oauthService, + scheduler: providerQueryScheduler, + recordSettingsActivity: (input) => settingsDatabase.recordSettingsActivity(input) + }) + const toolRoutes = createToolRoutes(toolService) + const pluginRoutes = createPluginRoutes(pluginService, native.pluginSettingsWindow) + const skillRoutes = createSkillRoutes({ + tempDirectory: bootstrap.temporaryDirectory, + skillService, + skillSyncService, + skillSettings, + ensureInitialized: ensureSkillServicesInitialized, + assertSessionActiveSkillsMutable: async (conversationId) => { + const state = await deepChatAgentHarness.getSessionState(conversationId) + if (state?.status === 'generating') { + throw new Error('Cannot change Session Skills while the session is generating.') + } + }, + recordSettingsActivity: (input) => settingsDatabase.recordSettingsActivity(input) + }) + const mcpRoutes = createMcpRoutes({ + mcpService, + mcpAppHost: (() => { + if (!mcpService.appHost) { + throw new Error('MCP Apps host is not configured') + } + return mcpService.appHost + })(), + isSettingsWindow: native.isSettingsWindow, + recordSettingsActivity: (input) => settingsDatabase.recordSettingsActivity(input) + }) + const remoteRoutes = createRemoteRoutes(remoteService) + const schedulerRoutes = createSchedulerRoutes(cronJobs) + const memoryRoutes = createMemoryRoutes({ + memoryService, + getAgentType: (agentId) => agentSettings.getAgentType(agentId), + getTapeInspection: () => sessionData.tapeStore, + getAuditEntries: () => memoryDatabase.agentMemoryAuditTable + }) + const fileRoutes = createFileRoutes(fileService, native.images) + const ocrRoutes = createOcrRoutes({ runtime: ocrRuntimeService }) + const toolchainRoutes = createToolchainRoutes({ + service: toolchainService, + pickPath: native.pickToolchainPath + }) + const knowledgeRoutes = createKnowledgeRoutes({ + service: knowledgeService, + settings: knowledgeSettings, + applyConfigChange: async () => { + mcpService.handleConfigChanged() + await knowledgeService.syncConfigChanges() + }, + recordActivity: (input) => { + void settingsDatabase.recordSettingsActivity(input).catch((error) => { + console.warn('[SettingsActivity] Failed to record settings activity:', error) + }) + } + }) + const workspaceRoutes = createWorkspaceRoutes(workspaceService) + const orchestrationRoutes = createOrchestrationRoutes({ + resolveCapability: (target) => + 'sessionId' in target + ? orchestrationCapabilityResolver.resolveSession(target.sessionId) + : orchestrationCapabilityResolver.resolveDraft(target.agentId), + getPolicy: (sessionId) => sessionAssignment.getOrchestrationPolicy(sessionId), + setPolicy: (sessionId, policy) => + sessionAssignment.updateOrchestrationPolicy(sessionId, policy), + liveDelegations: createLivePort(() => execution.liveDelegationService) + }) + const projectRoutes = createProjectRoutes({ + projectService, + publishEnvironmentsChanged: (action, environmentPath, version) => { + publishDeepchatEvent(projectEnvironmentsChangedEvent.name, { + action, + path: environmentPath, + version + }) + } + }) + const sessionRoutes = createSessionRoutes({ + lifecycle: sessionLifecycle, + projection: sessionQuery, + desktop: desktopSessionBinding, + openDesktopSession: runtime.openDesktopSession, + presentation: native.sessionPresentation, + turn: sessionTurn, + assignment: sessionAssignment, + permission: sessionPermissionPort, + agentSettings, + scheduler: createNodeScheduler(), + historySearch: sessionHistorySearch, + exportService: agentSessionExportService, + translation: sessionTranslation, + usageStats: usageStatsService, + tapeInspectorHeadWatcher, + sessionReferences: sessionData.sessionReferences + }) + const agentRoutes = createAgentRoutes({ + agentSettings, + recordActivity: (input) => { + void settingsDatabase.recordSettingsActivity(input).catch((error) => { + console.warn('[SettingsActivity] Failed to record settings activity:', error) + }) + }, + reconcileScheduler: async () => { + await cronJobs.reconcileScheduler('agent-change') + } + }) + const promptRoutes = createPromptRoutes({ + settings: promptSettings, + recordActivity: (input) => { + void settingsDatabase.recordSettingsActivity(input).catch((error) => { + console.warn('[SettingsActivity] Failed to record settings activity:', error) + }) + } + }) + const acpRoutes = createAcpRoutes({ + auth: acpAuthService, + owner: acpRuntimeOwner, + runtime: acpAgentRuntime, + isMainWindowContext: native.isMainWindowContext, + importHistory: async (sessionId) => { + const localId = toAppSessionId(sessionId) + const instance = acpAgentRuntime.getHydrated(localId) + if (!instance) throw new Error('ACP session is not initialized') + await instance.runIdleOperation(async () => { + const session = acpRuntimeOwner.getOrCreate().sessionController.getSession(localId) + const snapshot = + session && + (await acpRuntimeOwner + .getOrCreate() + .sessionController.getExtensions(localId, session.agentId)) + if (!session || !snapshot?.history) throw new Error('Read ACP history before importing') + sessionData.transcript.importAcpHistory( + sessionId, + session.agentId, + session.sessionId, + snapshot.history + ) + publishDeepchatEvent('sessions.messages.changed', { + sessionId, + messages: sessionData.transcript.getMessages(sessionId), + version: Date.now() + }) + publishDeepchatEvent('sessions.updated', { sessionIds: [sessionId], reason: 'updated' }) + }) + } + }) + const onboardingRoutes = createOnboardingRoutes(profile.settingsStore) + const exporterRoutes = createExporterRoutes(exporter) + const syncRoutes = createSyncRoutes({ + sync: syncService, + settings: syncSettings, + importFromSync: (backupFileName, importMode) => + runDatabaseMaintenance({ + kind: 'import-backup', + fileName: backupFileName, + mode: importMode ?? ImportMode.INCREMENT + }), + pullLatestBackupFromCloud: (importMode) => pullLatestBackupFromCloud(importMode), + recordActivity: (input) => { + void settingsDatabase.recordSettingsActivity(input).catch((error) => { + console.warn('[SettingsActivity] Failed to record settings activity:', error) + }) + } + }) + const syncHostRoutes = createSyncHostRoutes({ host: syncHostService }) + const platformRoutes = createPlatformRoutes({ + proxySettings: profile.proxySettings, + applyProxyMode: async (mode) => { + if (await native.applyProxy(mode, profile.proxySettings.getCustomUrl(), true)) + providerRuntime.handleProxyResolved() + }, + applyCustomProxyUrl: async (url) => { + await native.applyProxy( + profile.proxySettings.getMode(), + url, + profile.proxySettings.getMode() === 'custom' + ) + } + }) + const hookRoutes = createHookRoutes({ service: hookService }) + const appSettingsRoutes = createAppSettingsRoutes({ + settings: profile.settingsStore, + appSettings: appSettingsService, + commandShell: commandShellService, + publishEvent: publishDeepchatEvent, + recordActivity: recordSettingsActivity, + listActivities: (limit) => settingsDatabase.listSettingsActivity(limit) + }) + const appRoutes = createAppRoutes({ + isPackaged: bootstrap.isPackaged, + logging: { openFolder: () => appSettingsService.openLoggingFolder() }, + rendererPerformance: native.rendererPerformance, + isMainWindowContext: native.isMainWindowContext, + agentSettings, + projects: projectService, + databaseSecurity: databaseSecurityService, + database: { + getDatabase: () => mainDatabase.getDatabase(), + repairSchema: () => runDatabaseMaintenance({ kind: 'repair-schema' }) + }, + startupSession: sessionQuery, + desktopSession: desktopSessionBinding, + startup: startupWorkloadCoordinator, + ensureDefaultWorkspace: () => projectService.ensureDefaultWorkspace(), + enableDatabaseEncryption: (password) => + runDatabaseMaintenance({ kind: 'enable-encryption', password }), + changeDatabasePassword: (currentPassword, newPassword) => + runDatabaseMaintenance({ kind: 'change-password', currentPassword, newPassword }), + disableDatabaseEncryption: (currentPassword) => + runDatabaseMaintenance({ kind: 'disable-encryption', currentPassword }), + recordActivity: (input) => { + void settingsDatabase.recordSettingsActivity(input).catch((error) => { + console.warn('[SettingsActivity] Failed to record settings activity:', error) + }) + }, + publishSessionsUpdated: (sessionIds) => { + publishDeepchatEvent(sessionsUpdatedEvent.name, { sessionIds, reason: 'created' }) + }, + splash: native.splash + }) + const cliRoutes = createCliRoutes({ + appVersion: bootstrap.appVersion, + getStatus: () => cliServer.getStatus(), + hasTrustedRenderer: native.hasTrustedRenderer + }) + const approvalRoutes = createApprovalRoutes({ + resolve: (input, caller) => cliMutationGuard.resolve(input, caller) + }) + const artifactRoutes = createArtifactRoutes(artifactSpool) + const cliComputeRoutes = createCliComputeRoutes(cliComputeService) + const cliProviderModelAdminRoutes = createCliProviderModelAdminRoutes({ + providerSettings, + providerRuntime, + scheduler: providerQueryScheduler, + recordSettingsActivity: (input) => { + void settingsDatabase.recordSettingsActivity(input).catch((error) => { + console.warn('[SettingsActivity] Failed to record CLI provider activity:', error) + }) + } + }) + const cliSkillRoutes = cliSkillService.createRoutes() + const cliMcpAdminRoutes = createCliMcpAdminRoutes({ + mcp: mcpService, + recordSettingsActivity: (input) => { + void settingsDatabase.recordSettingsActivity(input).catch((error) => { + console.warn('[SettingsActivity] Failed to record CLI MCP activity:', error) + }) + }, + log: logger + }) + const cliRunRoutes = cliRunService.createRoutes() + const routeDispatcher = createRouteDispatcher({ + appDatabaseMaintenance: databaseRequests, + routeMaps: [ + providerRoutes, + toolRoutes, + pluginRoutes, + skillRoutes, + mcpRoutes, + remoteRoutes, + schedulerRoutes, + memoryRoutes, + fileRoutes, + ocrRoutes, + toolchainRoutes, + knowledgeRoutes, + workspaceRoutes, + orchestrationRoutes, + projectRoutes, + sessionRoutes, + agentRoutes, + promptRoutes, + acpRoutes, + onboardingRoutes, + exporterRoutes, + syncRoutes, + syncHostRoutes, + createSyncPeerRoutes(syncPeerService), + platformRoutes, + hookRoutes, + appSettingsRoutes, + appRoutes, + approvalRoutes, + cliRoutes, + artifactRoutes, + cliComputeRoutes, + cliProviderModelAdminRoutes, + cliSkillRoutes, + cliMcpAdminRoutes, + cliRunRoutes + ], + isSettingsWindow: native.isSettingsWindow, + startupWorkloadCoordinator + }) + return { routeDispatcher, oauthService } +} diff --git a/src/harness/core/data.ts b/src/harness/core/data.ts new file mode 100644 index 0000000000..0618dcb932 --- /dev/null +++ b/src/harness/core/data.ts @@ -0,0 +1,208 @@ +import path from 'node:path' +import logger from '@shared/logger' +import type { DeepchatEventName } from '@shared/contracts/events' +import type { initializeCoreProfile } from '@/app/profileStartup' +import type { ProviderDbBootstrap } from '@/provider/providerDbLoader' +import { ProviderSettings } from '@/provider/settings' +import { createSessionData } from '@/session/data' +import { MemoryDatabase } from '@/memory/data/database' +import { AgentRepository } from '@/agent/repository' +import { AgentLifecycleGate } from '@/agent/lifecycleGate' +import { SessionDeletionGate } from '@/session/deletionGate' +import { ProjectDatabase } from '@/project/data/database' +import { SchedulerDatabase } from '@/scheduler/data/database' +import { AppDatabase } from '@/app/data/database' +import { AppSessionService } from '@/agent/shared/appSessionService' +import { PromptSettings } from '@/agent/promptSettings' +import { TaskContractService } from '@/tape/application/taskContractService' +import { TaskEvaluationService } from '@/tape/application/taskEvaluationService' +import { LiveDelegationDatabase } from '@/orchestration/data/database' +import { LiveDelegationRepository } from '@/orchestration/liveDelegationRepository' +import { SessionRuntimeEvents } from '@/session/runtimeEvents' +import { + TapeInspectorHeadWatcher, + type TapeInspectorHeadWatcherOptions +} from '@/tape/application/traceInspectorHeadWatcher' +import { LegacyChatImportService } from '@/app/startupMigrations/legacyChatImportService' +import { UsageStatsService } from '@/session/usageStatsService' +import type { SessionDataMigrationSQLitePort } from '@/app/startupMigrations/sessionDataMigrations' + +type CoreProfile = Awaited> + +export function createCoreData(options: { + profile: Pick< + CoreProfile, + | 'database' + | 'settingsStore' + | 'privacySettings' + | 'providerDatabase' + | 'agentDatabase' + | 'settingsDatabase' + | 'currentAppVersion' + | 'previousAppVersion' + > + profileDirectory: string + providerCatalogBootstrap: ProviderDbBootstrap + publishEvent(name: DeepchatEventName, payload: unknown): void + emitTapeHead: TapeInspectorHeadWatcherOptions['emit'] + onRendererDestroyed: TapeInspectorHeadWatcherOptions['watchRendererDestroyed'] + projectEnvironmentChanged(): void +}) { + const { profile, publishEvent: publishDeepchatEvent } = options + const providerSettings = new ProviderSettings( + profile.settingsStore, + profile.privacySettings, + profile.providerDatabase, + publishDeepchatEvent, + profile.currentAppVersion, + options.providerCatalogBootstrap, + profile.previousAppVersion + ) + + const memoryDatabase = new MemoryDatabase(profile.database) + const sessionData = createSessionData( + profile.database, + () => memoryDatabase.ingestionProjectionTable, + { + publishPendingInputsChanged: (sessionId) => + publishDeepchatEvent('sessions.pendingInputs.changed', { + sessionId, + version: Date.now() + }), + publishMessagesChanged: (sessionId, messages) => + publishDeepchatEvent('sessions.messages.changed', { + sessionId, + messages, + version: Date.now() + }) + } + ) + const tapeInspectorHeadWatcher = new TapeInspectorHeadWatcher({ + readHead: (sessionId) => sessionData.tapeStore.getTapeInspectorHead(sessionId), + emit: options.emitTapeHead, + watchRendererDestroyed: options.onRendererDestroyed, + onError: (error, sessionId) => { + logger.warn('[TapeInspectorHeadWatcher] Failed to read committed head', { + sessionId, + error + }) + } + }) + const taskContractService = new TaskContractService( + () => sessionData.database.deepchatContractStore + ) + const taskEvaluationService = new TaskEvaluationService( + () => sessionData.database.deepchatContractStore + ) + const liveDelegationRepository = new LiveDelegationRepository( + new LiveDelegationDatabase(profile.database), + taskContractService, + taskEvaluationService + ) + const sessionRuntimeEvents = new SessionRuntimeEvents() + const projectDatabase = new ProjectDatabase(profile.database) + const agentDatabase = profile.agentDatabase + const settingsDatabase = profile.settingsDatabase + const providerDatabase = profile.providerDatabase + const schedulerDatabase = new SchedulerDatabase(profile.database) + const appDatabase = new AppDatabase(profile.database) + const agentRepository = new AgentRepository(agentDatabase, sessionData.database, memoryDatabase) + const agentLifecycle = new AgentLifecycleGate() + const sessionDeletionGate = new SessionDeletionGate() + const promptSettings = new PromptSettings(profile.settingsStore, { + publishCustomPromptsChanged: (prompts) => + publishDeepchatEvent('config.customPrompts.changed', { + prompts, + version: Date.now() + }), + publishSystemPromptsChanged: (state) => + publishDeepchatEvent('config.systemPrompts.changed', { + ...state, + version: Date.now() + }) + }) + const appSessionService = new AppSessionService( + projectDatabase, + sessionData.database, + options.projectEnvironmentChanged + ) + const resolveSessionRunId = (sessionId: string) => { + const visited = new Set() + let currentSessionId: string | null = sessionId + while (currentSessionId && visited.size < 32 && !visited.has(currentSessionId)) { + visited.add(currentSessionId) + const session = appSessionService.get(currentSessionId) + if (!session) return undefined + if (session.metadata?.source === 'cli_run') return session.id + currentSessionId = session.parentSessionId ?? null + } + return currentSessionId ? undefined : null + } + const sessionDataMigrationSQLite: SessionDataMigrationSQLitePort = { + get appSettingsTable() { + return settingsDatabase.appSettingsTable + }, + getDatabase: () => sessionData.database.getDatabase(), + get newSessionsTable() { + return sessionData.database.newSessionsTable + }, + get newSessionActiveSkillsTable() { + return sessionData.database.newSessionActiveSkillsTable + }, + get newSessionDisabledAgentToolsTable() { + return sessionData.database.newSessionDisabledAgentToolsTable + }, + get deepchatSearchDocumentsTable() { + return sessionData.database.deepchatSearchDocumentsTable + }, + get deepchatUserMessagesTable() { + return sessionData.database.deepchatUserMessagesTable + }, + get deepchatUserMessageFilesTable() { + return sessionData.database.deepchatUserMessageFilesTable + }, + get deepchatUserMessageLinksTable() { + return sessionData.database.deepchatUserMessageLinksTable + }, + get deepchatAssistantBlocksTable() { + return sessionData.database.deepchatAssistantBlocksTable + } + } + const legacyChatImportService = new LegacyChatImportService( + appDatabase, + sessionData.database, + projectDatabase, + memoryDatabase, + sessionData.tapeStore, + path.join(options.profileDirectory, 'app_db', 'chat.db'), + options.projectEnvironmentChanged + ) + const usageStatsService = new UsageStatsService( + sessionData.database, + providerSettings, + profile.settingsStore, + sessionData.tapeStore + ) + return { + providerSettings, + memoryDatabase, + sessionData, + tapeInspectorHeadWatcher, + liveDelegationRepository, + sessionRuntimeEvents, + projectDatabase, + agentDatabase, + settingsDatabase, + providerDatabase, + schedulerDatabase, + agentRepository, + agentLifecycle, + sessionDeletionGate, + promptSettings, + appSessionService, + resolveSessionRunId, + sessionDataMigrationSQLite, + legacyChatImportService, + usageStatsService + } +} diff --git a/src/harness/core/desktop.ts b/src/harness/core/desktop.ts new file mode 100644 index 0000000000..685efb8d69 --- /dev/null +++ b/src/harness/core/desktop.ts @@ -0,0 +1,363 @@ +import { randomUUID } from 'node:crypto' +import path from 'node:path' +import { z } from 'zod' +import { deserialize, serialize } from 'node:v8' +import logger from '@shared/logger' +import { + CoreDesktopArguments, + CoreDesktopCapabilitiesSchema, + CoreDesktopEventSchema, + CoreWindowTargetSchema, + CoreProxySnapshotSchema, + type CoreDesktopCommand, + type CoreDesktopMethod +} from '@shared/harness/desktop' +import type { CoreKernelNative } from './kernel' +import { CORE_VALUE_BYTES, CORE_PRESENTATION_QUEUED } from '@shared/harness/core' +import type { RemoteAuthWindowEvent } from '@/remote/ports' +import { computerUseSetPreviewModeRoute } from '@shared/contracts/routes' +import { startupWorkloadChangedEvent, liveDelegationChangedEvent } from '@shared/contracts/events' + +type Watch = { + id: string + webContentsId: number + listeners: Set<() => void> + registration: Promise + destroyed: boolean +} +type Retirement = { + kind: 'calls' | 'watches' | 'settledCalls' + id: string + receipt: ReturnType> +} + +export class CoreNativeDesktop { + private readonly pending = new Set>() + private readonly watches = new Map() + private readonly byRenderer = new Map() + private readonly authWindows = new Set() + private readonly retirements: Retirement[] = [] + private retiring = false + private closing: Promise | undefined + private eventTail: Promise = Promise.resolve() + private eventCount = 0 + private eventBytes = 0 + private readonly snapshots = new Map< + string, + { + snapshot: unknown[] + bytes: number + operation: Promise + version?: { revision: number; updatedAt: number } + } + >() + private failure: Error | undefined + private retired = false + private used = false + + constructor( + private readonly invoke: (command: CoreDesktopCommand) => Promise, + private readonly remoteEvent: (event: RemoteAuthWindowEvent) => void, + private readonly failed: (error: Error) => void + ) {} + + async createPorts(paths: { + temporaryDirectory: string + profileDirectory: string + }): Promise { + this.used = true + const capabilities = CoreDesktopCapabilitiesSchema.parse( + await this.invoke({ operation: 'capabilities' }) + ) + const ports: Record = {} + for (const method of Object.keys(CoreDesktopArguments) as CoreDesktopMethod[]) { + const parts = method.split('.') + let target = ports + for (const part of parts.slice(0, -1)) + target = (target[part] ??= {}) as Record + const event = + method.startsWith('renderer.') || + method === 'sendToRenderer' || + method === 'publishMcpConsent' || + method === 'observeDelegation' || + method === 'runJournalObserver' || + method === 'mcpReadinessChanged' || + method === 'reportStartupFailure' || + method === 'onSessionInactive' || + method.startsWith('semanticNotifications.') || + (method.startsWith('mcpPreview.') && method !== 'mcpPreview.shouldCaptureAfterClick') + target[parts.at(-1)!] = (...args: unknown[]) => { + let signal: AbortSignal | undefined + if ( + (method === 'fetchRegistryIcon' || + method === 'agentDesktop.readImageSize' || + (method === 'approvalPresentation.present' && args.length === 3) || + method.startsWith('copilotAuthorization.')) && + args.length > 1 + ) + signal = args.pop() as AbortSignal | undefined + if (method === 'cacheImage') { + const options = args[1] as + | { signal?: AbortSignal; allowPrivateNetwork?: boolean } + | undefined + signal = options?.signal + args[1] = options ? { allowPrivateNetwork: options.allowPrivateNetwork } : undefined + } + if (event) { + const delivery = this.enqueue(method, args) + // Renderer updates must not retain one Promise waiter per coalesced publication. + return method.startsWith('renderer.') ? undefined : delivery + } + return this.call(method, args, signal) + } + } + const native = ports as unknown as CoreKernelNative + Object.assign(native.agentDesktop, { + tempDirectory: paths.temporaryDirectory, + profileTempDirectory: path.join(paths.profileDirectory, 'temp') + }) + native.onRendererDestroyed = (webContentsId, listener) => { + if (this.closing || this.failure) throw this.failure ?? new Error('Core desktop is stopping') + let watch = this.byRenderer.get(webContentsId) + if (watch?.destroyed) { + listener() + return () => {} + } + if (!watch) { + watch = { + id: randomUUID(), + webContentsId, + listeners: new Set([listener]), + registration: Promise.resolve(), + destroyed: false + } + this.byRenderer.set(webContentsId, watch) + this.watches.set(watch.id, watch) + watch.registration = this.invoke({ operation: 'watch', id: watch.id, webContentsId }) + void watch.registration.catch((error) => this.fail(error)) + } + watch.listeners.add(listener) + const owned = watch + return () => { + owned.listeners.delete(listener) + if (owned.listeners.size || this.byRenderer.get(webContentsId) !== owned) return + this.byRenderer.delete(webContentsId) + void owned.registration + .then(() => this.retire('watches', owned.id)) + .then( + () => this.watches.delete(owned.id), + (error) => this.fail(error) + ) + } + } + const openAuth = native.remoteAuthWindows.open + native.remoteAuthWindows.open = async (input) => { + this.authWindows.add(input.id) + try { + await openAuth(input) + } catch (error) { + this.authWindows.delete(input.id) + throw error + } + } + if (!capabilities.preview) native.mcpPreview = undefined + if (!capabilities.journal) native.runJournalObserver = undefined + return native + } + + accept(input: unknown): void { + const event = CoreDesktopEventSchema.parse(input) + if (event.type === 'desktop.remoteAuthWindow') { + if (!this.authWindows.has(event.event.id)) + throw this.fail(new Error('Unknown Native auth window')) + this.remoteEvent(event.event) + if (event.event.type === 'closed') this.authWindows.delete(event.event.id) + return + } + const watch = this.watches.get(event.id) + if (!watch) throw this.fail(new Error('Unknown Native renderer subscription')) + if (watch.destroyed) throw this.fail(new Error('Repeated Native renderer destruction')) + watch.destroyed = true + watch.listeners.forEach((listener) => listener()) + } + + close(): Promise { + if (this.closing) return this.closing + if (!this.used) return Promise.resolve() + this.closing = (async () => { + await this.invoke({ operation: 'close' }) + this.retired = true + for (const retirement of this.retirements.splice(0)) retirement.receipt.resolve() + await Promise.allSettled(this.pending) + await this.eventTail + this.watches.clear() + this.byRenderer.clear() + this.authWindows.clear() + })() + return this.closing + } + + private call(method: CoreDesktopMethod, args: unknown[], signal?: AbortSignal): Promise { + if (this.failure || this.closing) + return Promise.reject(this.failure ?? new Error('Core desktop is stopping')) + signal?.throwIfAborted() + const id = randomUUID() + let retirement: Promise | undefined + const abort = () => { + retirement ??= this.retire('calls', id) + void retirement.catch((error) => this.fail(error)) + } + signal?.addEventListener('abort', abort, { once: true }) + const operation = (async () => { + try { + const result = await this.invoke({ operation: 'call', id, method, args }) + signal?.throwIfAborted() + if ( + [ + 'isSettingsWindow', + 'isMainWindowContext', + 'hasTrustedRenderer', + 'applyProxy', + 'remoteSessionWindow.present', + 'sessionPresentation.focusBrowser', + 'sessionPresentation.dismissComputerUsePreview' + ].includes(method) + ) + return z.boolean().parse(result) + if (method === 'sessionPresentation.setComputerUsePreview') + return computerUseSetPreviewModeRoute.output.parse(result) + if (method === 'remoteSessionWindow.select') + return CoreWindowTargetSchema.nullable().parse(result) + if (method === 'getProxySnapshot') return CoreProxySnapshotSchema.parse(result) + if (method === 'pluginSettingsWindow.getPluginIdForWebContents') + return z.string().nullable().parse(result) + return result + } catch (error) { + signal?.throwIfAborted() + throw error + } finally { + signal?.removeEventListener('abort', abort) + await retirement + if (retirement) await this.retire('settledCalls', id) + } + })() + this.pending.add(operation) + void operation.then( + () => this.pending.delete(operation), + () => this.pending.delete(operation) + ) + return operation + } + + private enqueue(method: CoreDesktopMethod, args: unknown[]): Promise { + const encoded = serialize(args) + let snapshotKey: string | undefined + let version: { revision: number; updatedAt: number } | undefined + if (method === 'renderer.broadcast') { + const [event] = CoreDesktopArguments['renderer.broadcast'].parse(args) + if (event.name === startupWorkloadChangedEvent.name) + snapshotKey = JSON.stringify([ + event.name, + startupWorkloadChangedEvent.payload.parse(event.payload).target + ]) + else if (event.name === liveDelegationChangedEvent.name) { + const payload = liveDelegationChangedEvent.payload.parse(event.payload) + snapshotKey = JSON.stringify([event.name, payload.parentSessionId, payload.delegation.id]) + version = { revision: payload.delegation.revision, updatedAt: payload.delegation.updatedAt } + } + } + const previous = snapshotKey ? this.snapshots.get(snapshotKey) : undefined + if (previous) { + if ( + version && + previous.version && + (previous.version.revision > version.revision || + (previous.version.revision === version.revision && + previous.version.updatedAt > version.updatedAt)) + ) + return previous.operation + if (this.eventBytes - previous.bytes + encoded.length > CORE_VALUE_BYTES) + throw this.fail(new Error('Core presentation queue exceeded')) + this.eventBytes += encoded.length - previous.bytes + previous.snapshot = deserialize(encoded) + previous.bytes = encoded.length + previous.version = version + return previous.operation + } + if ( + this.eventCount >= CORE_PRESENTATION_QUEUED || + this.eventBytes + encoded.length > CORE_VALUE_BYTES + ) + throw this.fail(new Error('Core presentation queue exceeded')) + const entry = { + snapshot: deserialize(encoded) as unknown[], + bytes: encoded.length, + version, + operation: Promise.resolve(undefined) + } + this.eventCount++ + this.eventBytes += encoded.length + const operation = this.eventTail.then(async () => { + if (snapshotKey && this.snapshots.get(snapshotKey) === entry) + this.snapshots.delete(snapshotKey) + try { + return await this.call(method, entry.snapshot) + } finally { + this.eventCount-- + this.eventBytes -= entry.bytes + } + }) + entry.operation = operation + if (snapshotKey) this.snapshots.set(snapshotKey, entry) + this.eventTail = operation.then( + () => {}, + (error) => logger.warn('[Core] Native presentation failed', { method, error }) + ) + return operation + } + + private retire(kind: Retirement['kind'], id: string): Promise { + if (this.retired) return Promise.resolve() + const receipt = Promise.withResolvers() + this.retirements.push({ kind, id, receipt }) + if (!this.retiring) { + this.retiring = true + queueMicrotask(() => { + void this.retireBatch() + }) + } + return receipt.promise + } + + private async retireBatch(): Promise { + try { + while (this.retirements.length) { + if (this.closing) { + await this.closing + continue + } + const batch = this.retirements.slice(0, 32) + await this.invoke({ + operation: 'retire', + calls: batch.filter((x) => x.kind === 'calls').map((x) => x.id), + watches: batch.filter((x) => x.kind === 'watches').map((x) => x.id), + settledCalls: batch.filter((x) => x.kind === 'settledCalls').map((x) => x.id) + }) + this.retirements.splice(0, batch.length) + for (const item of batch) item.receipt.resolve() + } + } catch (error) { + for (const item of this.retirements.splice(0)) item.receipt.reject(error) + this.fail(error) + } finally { + this.retiring = false + } + } + + private fail(input: unknown): Error { + if (this.failure) return this.failure + this.failure = input instanceof Error ? input : new Error('Core desktop failed') + this.failed(this.failure) + return this.failure + } +} diff --git a/src/harness/core/execution.ts b/src/harness/core/execution.ts new file mode 100644 index 0000000000..2b4132fe29 --- /dev/null +++ b/src/harness/core/execution.ts @@ -0,0 +1,594 @@ +import logger from '@shared/logger' +import type { AssistantMessageBlock } from '@shared/types/agent-interface' +import type { SkillMetadataSnapshotPort, SkillServicePort } from '@shared/types/skill' +import { liveDelegationChangedEvent } from '@shared/contracts/events' +import { projectFinalAssistantAnswer } from '@shared/lib/assistantDeliverySegments' +import { resolveAcpAgentAlias } from '@shared/utils/acpAgentAlias' +import { + createDeepChatAgentHarness, + type DeepChatHarnessDependencies +} from '@/agent/deepchat/harness' +import { AcpAgentRuntime } from '@/agent/acp/instance' +import type { AcpRuntimeOwner } from '@/agent/acp/client/acpRuntimeOwner' +import type { AcpSessionPersistence } from '@/agent/acp/runtime' +import { AgentManager } from '@/agent/manager/agentManager' +import { createDeepChatAgentBackend } from '@/agent/manager/deepChatAgentBackend' +import { createDirectAcpAgentBackend } from '@/agent/manager/directAcpAgentBackend' +import type { AgentRepository } from '@/agent/repository' +import type { AppSessionService } from '@/agent/shared/appSessionService' +import { toAppSessionId } from '@/agent/shared/agentSessionIds' +import { resolveAssistantModelSelection } from '@/agent/shared/assistantModelSelection' +import { AgentUnavailableError } from '@/agent/shared/agentCatalogCodec' +import type { AgentLifecycleGate } from '@/agent/lifecycleGate' +import { DesktopSessionBinding } from '@/desktop/sessionBinding' +import { SessionQuery } from '@/session/query' +import { SessionAssignmentPolicy } from '@/session/assignmentPolicy' +import { sanitizeGenerationSettings } from '@/agent/deepchat/runtime/generationSettings' +import { SessionAssignment } from '@/session/assignment' +import { SessionDeletion } from '@/session/deletion' +import type { SessionDeletionGate } from '@/session/deletionGate' +import { SessionTurn } from '@/session/turn' +import { SessionLifecycle, type SessionLifecycleDependencies } from '@/session/lifecycle' +import { SessionTranscriptMutations } from '@/session/transcriptMutations' +import { SessionHistorySearch } from '@/session/sessionHistorySearch' +import { SessionTranslation } from '@/session/sessionTranslation' +import { AgentSessionExportService } from '@/exporter/agentSessionExporter' +import { CliRunService } from '@/cli/runService' +import type { DatabaseRequestGate } from '@/app/databaseRequestGate' +import type { TypedEventHub } from '@/events/typedEventHub' +import { OrchestrationCapabilityResolver } from '@/orchestration/capability' +import { LiveDelegationSafetyCoordinator } from '@/orchestration/liveDelegationSafety' +import { + LiveDelegationService, + type LiveDelegationLifecycleObservation +} from '@/orchestration/liveDelegationService' +import type { LiveDelegationRepository } from '@/orchestration/liveDelegationRepository' +import type { LiveDelegationConsentAuthority } from '@/orchestration/liveDelegationConsent' +import type { AgentInvocationAdmissionPort } from '@shared/harness/admission' +import type { ProjectDatabase } from '@/project/data/database' +import type { ProjectService } from '@/project' +import type { ProviderRuntime } from '@/provider' +import type { ProviderSettings } from '@/provider/settings' +import type { AcpAsLlmProviderSessionControlPort } from '@/provider/ports' +import type { AgentToolDependencies } from '@/tool/runtimePorts' +import type { NowledgeMemConnections } from '@/nowledgeMem' + +export function createLivePort(resolve: () => T): T { + return new Proxy({} as T, { + get(_target, property) { + const target = resolve() + const value = Reflect.get(target, property, target) + return typeof value === 'function' ? value.bind(target) : value + } + }) +} + +export function createCoreExecution(options: { + harness: Omit< + DeepChatHarnessDependencies, + 'interactionContinuationAdmission' | 'taskContractContext' + > & { + providerRuntime: ProviderRuntime + providerSettings: ProviderSettings + skillService: SkillServicePort & SkillMetadataSnapshotPort + } + agentRepository: AgentRepository + appSessionService: AppSessionService + acpRuntimeOwner: AcpRuntimeOwner + acpSessionPersistence: AcpSessionPersistence + acpAsLlmProviderSessionControl: AcpAsLlmProviderSessionControlPort + agentLifecycle: AgentLifecycleGate + sessionDeletionGate: SessionDeletionGate + projectDatabase: ProjectDatabase + projectService: Pick< + ProjectService, + 'getDefaultProjectPath' | 'notifyEnvironmentProjectionChanged' + > + agentToolDependencies: AgentToolDependencies + agentInvocationAdmission: AgentInvocationAdmissionPort + liveDelegationRepository: LiveDelegationRepository + liveDelegationConsent: LiveDelegationConsentAuthority + nowledgeMemConnections: NowledgeMemConnections + databaseRequests: Pick + typedEventHub: TypedEventHub + resolveSessionRunId(sessionId: string): string | null | undefined + onRendererDestroyed: NonNullable + destroySessionBrowser(sessionId: string): Promise + observeDelegation(observation: LiveDelegationLifecycleObservation): void + observationsEnabled(): boolean +}) { + const { + providerRuntime, + providerSettings, + promptSettings, + agentSettings, + sessionData, + sessionPermissionPort, + sessionUiPort, + skillService, + publishEvent: publishDeepchatEvent + } = options.harness + const { + agentRepository, + appSessionService, + acpRuntimeOwner, + acpSessionPersistence, + acpAsLlmProviderSessionControl, + agentLifecycle, + sessionDeletionGate, + projectDatabase, + projectService, + agentToolDependencies, + agentInvocationAdmission, + liveDelegationRepository, + liveDelegationConsent, + nowledgeMemConnections, + databaseRequests, + typedEventHub, + resolveSessionRunId + } = options + let liveDelegationService: LiveDelegationService + const deepChatAgentHarness = createDeepChatAgentHarness({ + ...options.harness, + interactionContinuationAdmission: { + resume: async (sessionId, signal) => + await liveDelegationService.beforeInteractionContinuation(sessionId, signal), + suspend: (sessionId) => liveDelegationService.suspendInteractionContinuation(sessionId) + }, + taskContractContext: { + prepare: (sessionId) => liveDelegationService.prepareTaskContractContext(sessionId) + } + }) + const sessionTranscriptMutations = new SessionTranscriptMutations({ + transcript: sessionData.transcript, + runtime: deepChatAgentHarness, + clearTranscript: sessionData.clearTranscript + }) + const memoryIngestionObserver = deepChatAgentHarness.memoryIngestionObserver + const acpAgentRuntime = new AcpAgentRuntime( + acpRuntimeOwner, + (input) => deepChatAgentHarness.createAcpAgentInstanceDependencies(input), + sessionData.pendingInputs + ) + const agentManager = new AgentManager(agentRepository, appSessionService, { + deepchat: createDeepChatAgentBackend({ + port: deepChatAgentHarness, + runtime: deepChatAgentHarness.deepChatRuntime, + transcript: sessionData.transcript, + tape: sessionData.tape + }), + acp: createDirectAcpAgentBackend({ + runtime: acpAgentRuntime, + sessionState: deepChatAgentHarness, + transcript: sessionData.transcript, + tape: sessionData.tape, + deleteDurableSession: async (sessionId) => { + await acpSessionPersistence.deleteSessions(sessionId) + }, + resolveInput: async (sessionId, descriptor) => { + const session = appSessionService.get(sessionId) + if (!session || resolveAcpAgentAlias(session.agentId) !== descriptor.id) { + throw new AgentUnavailableError(descriptor.id, 'invalid-config', 'acp') + } + const agent = (await agentSettings.getAcpAgents()).find( + (candidate) => candidate.id === descriptor.id && candidate.source === descriptor.source + ) + if (!agent || !agent.command.trim()) { + throw new AgentUnavailableError(descriptor.id, 'invalid-config', 'acp') + } + return { + sessionId: toAppSessionId(session.id), + descriptor, + agent, + scope: session.sessionKind === 'subagent' ? 'subagent' : 'regular', + workdir: session.projectDir?.trim() ?? '' + } + } + }) + }) + const sessionQuery = new SessionQuery({ + sessions: appSessionService, + runtime: { + getAgentKind: (agentId) => agentManager.resolveBackend(agentId).kind, + snapshot: async (sessionId, options) => + await agentManager.resolveSessionHandle(toAppSessionId(sessionId)).handle.snapshot(options), + snapshotIfHydrated: async (sessionId) => + await agentManager.snapshotIfHydrated(toAppSessionId(sessionId)), + waitForFirstTurnReady: async (sessionId, options) => + await agentManager + .resolveSessionHandle(toAppSessionId(sessionId)) + .handle.waitForFirstTurnReady(options) + }, + transcript: sessionData.transcript, + tape: sessionData.tape, + messages: createLivePort(() => sessionData.database.deepchatMessagesTable), + searchResults: createLivePort(() => sessionData.database.deepchatMessageSearchResultsTable), + traces: createLivePort(() => sessionData.database.deepchatMessageTracesTable), + usesAgentTitle: (sessionId) => + !!acpRuntimeOwner.peek()?.sessionController.getSession(toAppSessionId(sessionId))?.extensions + ?.sessionTitle, + onTitleRenamed: async (sessionId, agentId) => { + if (agentManager.resolveBackend(agentId).kind === 'acp') + await acpRuntimeOwner + .getOrCreate() + .sessionController.markTitleManual(toAppSessionId(sessionId), agentId) + }, + titles: providerRuntime, + agentConfig: { + getAssistantModel: async (agentId) => { + const selection = await resolveAssistantModelSelection( + { + agentManager: agentManager, + agentSettings + }, + agentId, + '', + '' + ) + return selection.providerId && selection.modelId ? selection : null + } + }, + events: { + publish: (payload) => publishDeepchatEvent('sessions.updated', payload) + }, + ui: sessionUiPort + }) + const desktopSessionBinding = new DesktopSessionBinding(sessionQuery, options.onRendererDestroyed) + const sessionAssignmentPolicy = new SessionAssignmentPolicy( + { + resolveAgent: (agentId) => { + const descriptor = agentManager.resolveBackend(agentId).descriptor + return { id: descriptor.id, kind: descriptor.kind } + } + }, + { + getDefaultModel: () => agentSettings.getDefaultModel(), + getDefaultProjectPath: () => projectService.getDefaultProjectPath(), + resolveDeepChatAgentConfig: async (agentId) => + await agentSettings.resolveDeepChatAgentConfig(agentId), + isModelAvailable: (providerId, modelId) => + providerSettings.getProviderById(providerId)?.enable === true && + providerSettings.isKnownModel(providerId, modelId) && + providerSettings.getModelStatus(providerId, modelId) + } + ) + const clearNewAgentSessionSkills = skillService.clearNewAgentSessionSkills + if (!clearNewAgentSessionSkills) { + throw new Error('Skill presenter must provide session skill cleanup.') + } + const sessionDeletion = new SessionDeletion({ + sessions: appSessionService, + gate: sessionDeletionGate, + orchestration: { + prepareSessionDeletion: async (sessionId) => + await liveDelegationService.prepareSessionDeletion(sessionId) + }, + runtime: { + cleanupSessionBackends: async (sessionId) => + await agentManager.cleanupSessionBackends(sessionId), + destroySessionBrowser: async (sessionId) => await options.destroySessionBrowser(sessionId) + }, + state: deepChatAgentHarness, + permissions: sessionPermissionPort, + skills: { + clearNewAgentSessionSkills: async (sessionId) => + await clearNewAgentSessionSkills.call(skillService, sessionId), + completeNewAgentSessionSkillsDeletion: (sessionId) => + skillService.completeNewAgentSessionSkillsDeletion(sessionId) + } + }) + const sessionAssignment = new SessionAssignment({ + sessions: appSessionService, + runtime: { + getSessionAgentKind: (sessionId) => + agentManager.resolveSessionBackend(sessionId).descriptor.kind, + resolveSession: (sessionId) => agentManager.resolveSessionHandle(sessionId), + resolveTransferSource: (sessionId) => agentManager.resolveTransferSource(sessionId), + resolveDeepChatTransferTarget: (agentId) => + agentManager.resolveDeepChatTransferTarget(agentId), + resolveSubagentFacet: (sessionId) => agentManager.resolveSubagentFacet(sessionId) + }, + policy: sessionAssignmentPolicy, + projection: sessionQuery, + deletion: sessionDeletion, + environment: { + syncPath: (projectDir) => { + projectDatabase.newEnvironmentsTable.syncPath(projectDir) + projectService.notifyEnvironmentProjectionChanged() + } + }, + acp: acpAsLlmProviderSessionControl, + agentLifecycle + }) + const sessionTurn = new SessionTurn({ + sessions: appSessionService, + runtime: { + resolveSession: (sessionId) => { + const { handle } = agentManager.resolveSessionHandle(sessionId) + const turn = { + pending: handle.pending, + toolInteractions: handle.toolInteractions, + send: (input: Parameters[0]) => handle.send(input), + cancel: () => handle.cancel(), + snapshot: () => handle.snapshot() + } + return handle.kind === 'deepchat' + ? { + ...turn, + kind: handle.kind, + compaction: { + getSnapshot: () => handle.deepchat.getCompactionSnapshot(), + compact: () => handle.deepchat.compact() + }, + getContextOccupancy: () => handle.deepchat.getContextOccupancy(), + isPendingQueueResumeAvailable: () => handle.deepchat.isPendingQueueResumeAvailable(), + resumePendingQueue: () => handle.deepchat.resumePendingQueue(), + retryPendingQueueInput: (itemId) => handle.deepchat.retryPendingQueueInput(itemId) + } + : { ...turn, kind: handle.kind } + } + }, + transcript: { + hasMessages: (sessionId) => sessionData.transcript.hasMessages(sessionId), + clearMessages: (sessionId) => sessionTranscriptMutations.clearMessages(sessionId), + prepareRetryMessage: (sessionId, messageId) => + sessionTranscriptMutations.prepareRetryMessage(sessionId, messageId), + commitRetryMessage: (sessionId, sourceOrderSeq) => + sessionTranscriptMutations.commitRetryMessage(sessionId, sourceOrderSeq), + deleteMessage: (sessionId, messageId) => + sessionTranscriptMutations.deleteMessage(sessionId, messageId), + editUserMessage: (sessionId, messageId, text, inlineItems) => + sessionTranscriptMutations.editUserMessage(sessionId, messageId, text, inlineItems) + }, + workdir: sessionAssignment, + projection: sessionQuery + }) + const sessionLifecycle = new SessionLifecycle({ + onRendererDestroyed: options.onRendererDestroyed, + forkAcpSession: async (sourceId, targetId, messageId) => { + const message = sessionData.transcript.getMessage(messageId) + if ( + !message || + message.sessionId !== sourceId || + message.role !== 'assistant' || + message.status !== 'sent' + ) + throw new Error('Fork requires a completed ACP assistant message') + const metadata = JSON.parse(message.metadata) as { acp?: { turnId?: string } } + const blocks = JSON.parse(message.content) as AssistantMessageBlock[] + const anchors = blocks.flatMap((block) => { + try { + const meta = JSON.parse(String(block.extra?.providerOptionsJson ?? '{}')) + return typeof meta.acp?.turnId === 'string' ? [meta.acp.turnId] : [] + } catch { + return [] + } + }) + const turnId = anchors.at(-1) ?? metadata.acp?.turnId + if (!turnId) throw new Error('This message has no verified remote turn anchor') + const result = await acpAgentRuntime.forkSession( + toAppSessionId(sourceId), + toAppSessionId(targetId), + turnId, + messageId + ) + sessionData.transcript.importAcpHistory( + targetId, + result.agentId, + result.remoteSessionId, + result.history + ) + await acpRuntimeOwner + .getOrCreate() + .sessionController.completeFork(toAppSessionId(sourceId), messageId) + }, + sessions: appSessionService, + runtime: { + resolveSession: (sessionId) => { + const { handle } = agentManager.resolveSessionHandle(sessionId) + return { + kind: handle.kind, + initialize: (config) => handle.lifecycle.initialize(config), + isInitialized: () => handle.lifecycle.isInitialized(), + snapshot: () => handle.snapshot(), + getGenerationSettings: () => handle.settings.getGenerationSettings(), + setPermissionMode: (mode) => handle.settings.setPermissionMode(mode), + close: () => handle.close() + } + } + }, + transcript: { + hasMessages: (sessionId) => sessionData.transcript.hasMessages(sessionId), + forkSessionFromMessage: (sourceSessionId, targetSessionId, targetMessageId) => + sessionTranscriptMutations.forkSessionFromMessage( + sourceSessionId, + targetSessionId, + targetMessageId + ) + }, + skills: { + setActiveSkills: async (sessionId, activeSkills) => { + await skillService.setActiveSkills(sessionId, activeSkills) + } + }, + assignmentPolicy: sessionAssignmentPolicy, + workdir: sessionAssignment, + initialTurn: sessionTurn, + projection: sessionQuery, + desktop: desktopSessionBinding, + deletion: sessionDeletion, + deletionGate: sessionDeletionGate, + permissions: sessionPermissionPort, + agentLifecycle + }) + const cliRunService = new CliRunService({ + lifecycle: sessionLifecycle, + turn: sessionTurn, + projection: sessionQuery, + sessions: appSessionService, + getPendingAssistantMessages: (runId) => + sessionData.transcript.getPendingAssistantMessages(runId), + hasWaitingDescendantInteraction: (runId) => + liveDelegationRepository + .listActiveTurns() + .some( + ({ delegation, turn }) => + (turn.status === 'waiting_permission' || turn.status === 'waiting_question') && + resolveSessionRunId(delegation.parentSessionId) === runId + ), + readSubscriptionSnapshot: (read) => databaseRequests.runRequest('events.subscribe', read), + eventHub: typedEventHub, + log: logger + }) + const sessionHistorySearch = new SessionHistorySearch(sessionData.database, appSessionService) + const agentSessionExportService = new AgentSessionExportService({ + nowledgeMemConnections, + agentManager: agentManager, + appSessionService, + transcript: sessionData.transcript, + providerSettings: providerSettings + }) + const sessionTranslation = new SessionTranslation({ + agentManager: agentManager, + agentSettings, + providerRuntime: providerRuntime + }) + const orchestrationCapabilityResolver = new OrchestrationCapabilityResolver({ + sessions: agentToolDependencies.sessions, + agents: agentSettings + }) + const liveDelegationSafety = new LiveDelegationSafetyCoordinator({ + sessions: agentToolDependencies.sessions, + assignmentPolicy: sessionAssignmentPolicy, + assignment: sessionAssignment, + permissions: sessionPermissionPort, + executionSnapshots: deepChatAgentHarness + }) + const createLiveDelegationService = (): LiveDelegationService => + (liveDelegationService = new LiveDelegationService({ + repository: liveDelegationRepository, + admission: agentInvocationAdmission, + deletionGate: sessionDeletionGate, + safety: liveDelegationSafety, + consent: liveDelegationConsent, + sessions: { + ...agentToolDependencies.sessions, + ...agentToolDependencies.subagents, + resolveSubagentExecutionSnapshot: async (input) => { + const assignment = await sessionAssignmentPolicy.resolveSubagentAssignment(input) + const isAcp = agentManager.resolveBackend(assignment.agentId).descriptor.kind === 'acp' + return { + providerId: assignment.providerId, + modelId: assignment.modelId, + toolModeOverride: assignment.toolModeOverride, + generationSettings: isAcp + ? null + : await sanitizeGenerationSettings( + providerSettings, + promptSettings, + assignment.providerId, + assignment.modelId, + assignment.generationSettings ?? {} + ) + } + }, + findDelegationChild: async (parentSessionId, delegationId) => { + const matches = appSessionService + .list({ includeSubagents: true, parentSessionId }) + .filter( + (session) => session.subagentMeta?.liveDelegation?.delegationId === delegationId + ) + if (matches.length > 1) { + throw new Error(`Multiple child Sessions share live delegation ${delegationId}.`) + } + return matches[0] + ? await agentToolDependencies.sessions.resolveConversationSessionInfo(matches[0].id) + : null + }, + getAssistantResult: async (sessionId, messageId) => { + const messages = sessionData.database.deepchatMessagesTable + const identity = messageId + ? messages.getAssistantIdentity(messageId) + : messages.getLatestAssistantIdentity(sessionId) + if (!identity || identity.session_id !== sessionId) return null + + const resultRows = + sessionData.database.deepchatAssistantBlocksTable.listResultProjectionByMessageId( + identity.id + ) + if (resultRows.length > 0) { + const answer = projectFinalAssistantAnswer( + resultRows.map( + (row): AssistantMessageBlock => ({ + type: row.block_type, + status: row.status, + timestamp: row.updated_at, + content: row.text_content ?? undefined + }) + ) + ) + return answer + ? { + messageId: identity.id, + answerMarkdown: answer, + updatedAt: identity.updated_at + } + : null + } + + const legacyMessage = await sessionQuery.getMessage(identity.id) + if (!legacyMessage || legacyMessage.sessionId !== sessionId) return null + try { + const parsed = JSON.parse(legacyMessage.content) as AssistantMessageBlock[] | string + const answer = Array.isArray(parsed) + ? projectFinalAssistantAnswer(parsed) + : typeof parsed === 'string' + ? parsed.trim() + : '' + return answer + ? { messageId: identity.id, answerMarkdown: answer, updatedAt: identity.updated_at } + : null + } catch { + const answer = legacyMessage.content.trim() + return answer + ? { messageId: identity.id, answerMarkdown: answer, updatedAt: identity.updated_at } + : null + } + } + }, + observe: options.observeDelegation, + observationsEnabled: options.observationsEnabled, + onChanged: (parentSessionId, delegationId) => { + sessionQuery.notify({ sessionIds: [parentSessionId], reason: 'updated' }) + try { + publishDeepchatEvent(liveDelegationChangedEvent.name, { + schemaVersion: 1, + parentSessionId, + delegation: liveDelegationService.getSummary(parentSessionId, delegationId) + }) + } catch (error) { + logger.warn('[LiveDelegationService] Failed to publish delegation projection', { error }) + } + } + })) + liveDelegationService = createLiveDelegationService() + return { + deepChatAgentHarness, + memoryIngestionObserver, + acpAgentRuntime, + sessionQuery, + desktopSessionBinding, + sessionAssignment, + sessionTurn, + sessionLifecycle, + cliRunService, + sessionHistorySearch, + agentSessionExportService, + sessionTranslation, + get liveDelegationService() { + return liveDelegationService + }, + createLiveDelegationService, + orchestrationCapabilityResolver + } +} diff --git a/src/harness/core/helpers.ts b/src/harness/core/helpers.ts new file mode 100644 index 0000000000..4dc100f5ce --- /dev/null +++ b/src/harness/core/helpers.ts @@ -0,0 +1,326 @@ +import { randomUUID } from 'node:crypto' +import { EventEmitter } from 'node:events' +import { deserialize, serialize } from 'node:v8' +import type { BackgroundExecHost } from '@/agent/shared/process/backgroundExecSessionManager' +import type { CodeModeHost } from '@/tool/codeMode/runCodeRuntimeManager' +import type { SchedulerHost } from '@/scheduler/schedulerProcessManager' +import type { + FileWatcherHost, + SpawnFileWatcherHost +} from '@/platform/fileWatcher/watcherHostClient' +import { + CORE_HELPER_QUEUE, + CORE_HELPER_BATCH, + CORE_HELPER_QUEUE_BYTES, + CoreHelperEventsSchema, + CoreHelperAdmissionSchema, + parseCoreHelperMessage, + type CoreHelperCommand, + type CoreHelperEvent, + type CoreHelperKind +} from '@shared/harness/helpers' + +type Helper = { + id: string + kind: CoreHelperKind + pid: number | undefined + events: EventEmitter + backlog: Array<{ event: CoreHelperEvent['event']; bytes: number }> + flushing: boolean + dead: boolean + opened: ReturnType> + closing?: Promise +} + +export class CoreNativeHelpers { + private readonly helpers = new Map() + private sequence = 1 + private queued = 0 + private queuedBytes = 0 + private buffered = 0 + private bufferedBytes = 0 + private outgoing: Promise = Promise.resolve() + private closed = false + private used = false + private closing: Promise | undefined + private failure: Error | undefined + private readonly terminations = new Map>>() + private terminating = false + + constructor( + private readonly invoke: (command: CoreHelperCommand) => Promise, + private readonly failed: (error: Error) => void + ) {} + + async spawnBackgroundExecHost(signal: AbortSignal): Promise { + const helper = await this.open('exec', signal) + return { ...this.channel(helper), kill: () => this.terminate(helper) } + } + + async spawnSchedulerHost(signal: AbortSignal): Promise { + return this.channel(await this.open('scheduler', signal)) + } + + readonly spawnFileWatcherHost: SpawnFileWatcherHost = async (kind, signal) => { + const helper = await this.open(kind, signal) + const channel = this.channel(helper) + return { + ...channel, + terminate: async () => await this.terminate(helper) + } satisfies FileWatcherHost + } + + async spawnCodeModeHost(signal: AbortSignal): Promise { + const helper = await this.open('code', signal) + return { + get pid() { + return helper.pid + }, + postMessage: (message) => this.post(helper, message), + kill: () => this.kill(helper), + on: (event, listener) => { + helper.events.on(event, listener) + this.flush(helper) + }, + off: (event, listener) => { + helper.events.off(event, listener) + }, + getRssBytes: async (requestSignal) => { + requestSignal.throwIfAborted() + if (this.failure || this.closing) + throw this.failure ?? new Error('Core helpers are stopping') + const value = await this.invoke({ operation: 'rss', id: helper.id }) + requestSignal.throwIfAborted() + if (value === null) return null + if (typeof value !== 'number' || !Number.isFinite(value) || value < 0) + throw new Error('Invalid owned helper RSS sample') + return value + } + } + } + + accept(input: unknown): void { + if (this.failure || this.closed) throw this.failure ?? new Error('Core helpers are closed') + const parsed = CoreHelperEventsSchema.safeParse(input) + if (!parsed.success) throw this.fail(parsed.error) + const batch = parsed.data + for (const value of batch) { + const helper = this.helpers.get(value.id) + if (!helper || value.sequence !== this.sequence++) + throw this.fail(new Error('Unknown or out-of-order Native helper event')) + if (value.event.type === 'opened') { + helper.pid = value.pid ?? helper.pid + helper.opened.resolve() + continue + } + if (value.event.type === 'openFailed') { + const error = new Error(value.event.message) + error.name = value.event.name + helper.opened.reject(error) + continue + } + const bytes = serialize(value.event).length + if ( + this.buffered >= CORE_HELPER_QUEUE || + this.bufferedBytes + bytes > CORE_HELPER_QUEUE_BYTES + ) + throw this.fail(new Error('Core helper event queue exceeded')) + helper.backlog.push({ event: value.event, bytes }) + this.buffered++ + this.bufferedBytes += bytes + if (value.event.type === 'exit') { + helper.dead = true + helper.pid = undefined + helper.opened.reject(new Error('Native helper exited during startup')) + } else helper.pid = value.pid ?? helper.pid + this.flush(helper) + } + } + + close(): Promise { + if (this.closing) return this.closing + this.closing = (async () => { + await this.outgoing + if (this.used) await this.invoke({ operation: 'close' }) + this.closed = true + for (const helper of this.helpers.values()) this.remove(helper) + })() + return this.closing + } + + private async open(kind: CoreHelperKind, signal: AbortSignal): Promise { + signal.throwIfAborted() + if (this.failure || this.closing) throw this.failure ?? new Error('Core helpers are stopping') + const opened = Promise.withResolvers() + void opened.promise.catch(() => {}) + const helper: Helper = { + id: randomUUID(), + kind, + pid: undefined, + events: new EventEmitter(), + backlog: [], + flushing: false, + dead: false, + opened + } + this.helpers.set(helper.id, helper) + this.used = true + let admitted = false + const abort = () => { + if (admitted) this.kill(helper) + } + signal.addEventListener('abort', abort, { once: true }) + try { + CoreHelperAdmissionSchema.parse(await this.invoke({ operation: 'open', id: helper.id, kind })) + admitted = true + if (signal.aborted) signal.throwIfAborted() + await opened.promise + if (helper.dead || this.closing || this.failure) + throw this.failure ?? new Error('Native helper startup was retired') + return helper + } catch (error) { + try { + await this.terminate(helper) + } catch (failure) { + this.fail(failure) + } + this.remove(helper) + throw signal.aborted ? signal.reason : error + } finally { + signal.removeEventListener('abort', abort) + } + } + + private channel(helper: Helper) { + return { + get pid() { + return helper.pid + }, + postMessage: (message: unknown) => this.post(helper, message), + kill: () => this.kill(helper), + onMessage: (listener: (message: unknown) => void) => { + helper.events.on('message', listener) + this.flush(helper) + }, + onExit: (listener: (code: number) => void) => { + helper.events.on('exit', listener) + this.flush(helper) + }, + onError: (listener: (kind: string, location: string) => void) => { + helper.events.on('error', listener) + this.flush(helper) + } + } + } + + private post(helper: Helper, message: unknown): void { + if (this.failure || this.closing || helper.dead) + throw this.failure ?? new Error('Native helper is stopping') + const encoded = serialize(parseCoreHelperMessage(helper.kind, message)) + const snapshot: unknown = deserialize(encoded) + const bytes = encoded.length + if (this.queued >= CORE_HELPER_QUEUE || this.queuedBytes + bytes > CORE_HELPER_QUEUE_BYTES) + throw this.fail(new Error('Core helper command queue exceeded')) + this.queued++ + this.queuedBytes += bytes + const operation = this.outgoing.then(async () => { + try { + if (this.failure) throw this.failure + await this.invoke({ operation: 'send', id: helper.id, message: snapshot }) + } finally { + this.queued-- + this.queuedBytes -= bytes + } + }) + this.outgoing = operation.catch((error) => { + this.fail(error) + }) + } + + private kill(helper: Helper): void { + void this.terminate(helper).catch((error) => this.fail(error)) + } + + private terminate(helper: Helper): Promise { + if (helper.closing) return helper.closing + if (this.closing) return this.closing + const receipt = Promise.withResolvers() + helper.closing = receipt.promise + this.terminations.set(helper.id, receipt) + if (!this.terminating) { + this.terminating = true + queueMicrotask(() => { + void this.terminateBatch() + }) + } + return helper.closing + } + + private async terminateBatch(): Promise { + try { + await this.outgoing + while (this.terminations.size) { + const batch = [...this.terminations.entries()].slice(0, CORE_HELPER_BATCH) + if (this.closing) await this.closing + else await this.invoke({ operation: 'terminate', ids: batch.map(([id]) => id) }) + for (const [id, receipt] of batch) { + this.terminations.delete(id) + receipt.resolve() + } + } + } catch (error) { + for (const receipt of this.terminations.values()) receipt.reject(error) + this.terminations.clear() + this.fail(error) + } finally { + this.terminating = false + } + } + + private flush(helper: Helper): void { + if (helper.flushing || !helper.events.eventNames().length) return + helper.flushing = true + queueMicrotask(() => { + helper.flushing = false + try { + while (helper.backlog.length) { + const value = helper.backlog[0] + helper.backlog.shift() + this.buffered-- + this.bufferedBytes -= value.bytes + switch (value.event.type) { + case 'message': + helper.events.emit('message', value.event.message) + break + case 'exit': + helper.events.emit('exit', value.event.code) + this.remove(helper) + break + case 'error': + helper.events.emit('error', value.event.kind, value.event.location) + break + } + } + } catch (error) { + this.fail(error) + } + }) + } + + private remove(helper: Helper): void { + helper.opened.reject(this.failure ?? new Error('Native helper is retired')) + this.helpers.delete(helper.id) + this.buffered -= helper.backlog.length + for (const value of helper.backlog) this.bufferedBytes -= value.bytes + helper.backlog = [] + } + + private fail(input: unknown): Error { + if (this.failure) return this.failure + const error = input instanceof Error ? input : new Error('Core Native helper failed') + this.failure = error + for (const helper of this.helpers.values()) helper.opened.reject(error) + this.failed(error) + return error + } +} diff --git a/src/harness/core/kernel.ts b/src/harness/core/kernel.ts new file mode 100644 index 0000000000..f3c66ed650 --- /dev/null +++ b/src/harness/core/kernel.ts @@ -0,0 +1,489 @@ +import path from 'node:path' +import logger from '@shared/logger' +import { + settingsChangedEvent, + sessionsTapeInspectorHeadChangedEvent, + type DeepchatEventName +} from '@shared/contracts/events' +import { toAppSessionId } from '@/agent/shared/agentSessionIds' +import type { initializeCoreProfile } from '@/app/profileStartup' +import { DatabaseRequestGate } from '@/app/databaseRequestGate' +import { StartupWorkloadCoordinator } from '@/app/startupWorkloadCoordinator' +import { AgentCliTokenAuthority, ArtifactSpool } from '@/cli' +import { TypedEventHub, type TypedEventHubOptions } from '@/events/typedEventHub' +import { SessionEventRouter } from '@/events/sessionEventRouter' +import { providerDbLoader, type ProviderDbBootstrap } from '@/provider/providerDbLoader' +import { createCoreData } from './data' +import { createCoreProviders } from './providers' +import { createCoreRuntime } from './runtime' +import type { createCoreSettings } from './settings' +import { createCoreMaintenance, type CoreMaintenanceNative } from './maintenance' +import { createCoreController, type CoreControllerNative } from './controller' +import { CoreHttp } from './network' +import type { CoreProxySnapshot } from '@shared/harness/desktop' + +type RuntimeOptions = Parameters[0] +type ProviderOptions = Parameters[0] +type Runtime = Awaited> + +export type CoreKernelNative = Pick< + RuntimeOptions, + | 'workspaceDesktop' + | 'approvalPresentation' + | 'ocrCacheKeyProvider' + | 'initializeFloatingButton' + | 'assertSyncTokenStorageAvailable' + | 'semanticNotifications' + | 'secureCrypto' + | 'openExternal' + | 'openPath' + | 'selectDirectory' + | 'cacheImage' + | 'agentDesktop' + | 'pluginSettingsWindow' + | 'remoteAuthWindows' + | 'remoteSessionWindow' + | 'showNotification' + | 'mcpPreview' + | 'sessionUiPort' + | 'mcpReadinessChanged' + | 'reportStartupFailure' + | 'onSessionInactive' +> & + CoreMaintenanceNative & + CoreControllerNative & { + renderer: TypedEventHubOptions['renderer'] + onRendererDestroyed: ProviderOptions['onRendererDestroyed'] + sendToRenderer: ProviderOptions['sendToRenderer'] + destroySessionBrowser: RuntimeOptions['execution']['destroySessionBrowser'] + observeDelegation: RuntimeOptions['execution']['observeDelegation'] + runJournalObserver: RuntimeOptions['runJournalObserver'] + notifySessionCompletion(session: { id: string; title: string }): Promise + providerCrypto: ProviderOptions['crypto'] + fetchRegistryIcon: ProviderOptions['fetchRegistryIcon'] + mcpAppDesktop: RuntimeOptions['mcpApps']['desktop'] + publishMcpConsent: RuntimeOptions['mcpApps']['publishConsent'] + getProxySnapshot(): Promise + } + +export async function createCoreKernel(options: { + control: import('@shared/harness/control').HarnessControlPort + profile: Awaited> + settings: ReturnType + bootstrap: RuntimeOptions['bootstrap'] + providerCatalogBootstrap: ProviderDbBootstrap + nextSettingsVersion(): number + native: CoreKernelNative + browser: RuntimeOptions['browser'] + helpers: Pick< + RuntimeOptions, + 'spawnBackgroundExecHost' | 'spawnCodeModeHost' | 'spawnFileWatcherHost' | 'spawnSchedulerHost' + > + admission: RuntimeOptions['agentInvocationAdmission'] + vectors: RuntimeOptions['memoryVectorStores'] + memory: RuntimeOptions['memory'] + hooks: RuntimeOptions['hooks'] + bindEventPublisher(publish: (name: DeepchatEventName, payload: unknown) => void): void + isStopping(): boolean + allowInsecureTls: boolean + onFailure(): void +}) { + if (options.isStopping()) throw new Error('Core Kernel startup is stopping') + const { native, bootstrap, profile, settings } = options + let stopping: Promise | undefined + let starting: Promise | undefined + let systemResume: Promise | undefined + let stoppingRequested = false + let runtime: Runtime | undefined + let data: ReturnType | undefined + let providers: ReturnType | undefined + let maintenance: ReturnType | undefined + let controller: ReturnType | undefined + let constructionIncomplete = false + const isStopping = () => stoppingRequested || options.isStopping() + const databaseRequests = new DatabaseRequestGate(() => { + if (isStopping()) throw new Error('Core Kernel is stopping') + }) + const syncActiveSessions = new Set() + const typedEventHub = new TypedEventHub({ renderer: native.renderer, log: logger }) + const events = new SessionEventRouter({ + hub: typedEventHub, + resolveSessionRunId: (id) => (data ? data.resolveSessionRunId(id) : null), + getBoundRendererIds: (id) => + runtime?.execution.desktopSessionBinding.getWebContentsIdsForSession(id) ?? [] + }) + const publishEvent = (name: DeepchatEventName, input: unknown) => { + const payload = + name === 'settings.changed' + ? { ...settingsChangedEvent.payload.parse(input), version: options.nextSettingsVersion() } + : input + events.publish(name, payload) + if (name === 'sessions.status.changed') { + const state = payload as { sessionId: string; status: string } + if (state.status === 'generating' || state.status === 'running') + syncActiveSessions.add(state.sessionId) + else { + syncActiveSessions.delete(state.sessionId) + runtime?.syncReplica.wake() + } + } + } + options.bindEventPublisher(publishEvent) + const startupWorkloadCoordinator = new StartupWorkloadCoordinator() + const startupRunId = startupWorkloadCoordinator.createRun('main') + const unsubscribeStartup = startupWorkloadCoordinator.subscribe((payload) => + publishEvent('startup.workload.changed', payload) + ) + const agentCliTokenAuthority = new AgentCliTokenAuthority() + const artifactSpool = new ArtifactSpool({ + directory: path.join(bootstrap.profileDirectory, 'local-control', 'artifacts'), + consumeAgentBytes: (token, bytes) => agentCliTokenAuthority.consumeBytes(token, bytes), + log: logger + }) + const http = new CoreHttp((error) => { + logger.error('[Core] HTTP transport retirement failed', error) + options.onFailure() + }) + const refreshProxy = async () => { + http.apply(await native.getProxySnapshot()) + return http.getProxyUrl() + } + const applyProxy: CoreKernelNative['applyProxy'] = async (...args) => { + const resolved = await native.applyProxy(...args) + await refreshProxy() + return resolved + } + const stop = (): Promise => { + if (stopping) return stopping + stoppingRequested = true + databaseRequests.beginShutdown() + profile.databaseSecurityService.stop() + profile.secretStore.pause() + providers?.agentSettings.stop() + runtime?.execution.deepChatAgentHarness.abortGenerations() + runtime?.backgroundExec.stop() + runtime?.toolService.stopCodeRuntime() + startupWorkloadCoordinator.cancelTarget('settings') + runtime?.mcpApps.close() + const httpRetirement = http.close() + void httpRetirement.catch(() => {}) + providerDbLoader.stop() + stopping = (async () => { + const errors: unknown[] = [] + const step = async (name: string, action: () => void | Promise) => { + try { + await action() + } catch (error) { + errors.push(error) + logger.warn('[Core] Kernel retirement failed', { name, error }) + } + } + await step('permissions', async () => await runtime?.host.clearPermissionCaches()) + await step('settings', () => settings.appSettingsService.stop()) + agentCliTokenAuthority.clear() + await step('startup', () => startupWorkloadCoordinator.cancelAndDrainTarget('main')) + await step('settings-startup', () => + startupWorkloadCoordinator.cancelAndDrainTarget('settings') + ) + await step('agent-settings-stop', () => providers?.agentSettings.stop()) + await step('provider-catalog', () => providerDbLoader.drain()) + await step('agent-settings', async () => await providers?.agentSettings.drain()) + await step('accounts', async () => { + if (controller) await controller.oauthService.stop() + else await providers?.accounts.stop() + }) + await step('acp-auth', async () => await providers?.acpAuthService.shutdown()) + if (runtime) { + const owner = runtime + await step('cli', () => owner.cliServer.stop()) + await step('automatic-sync', () => owner.automaticSync.close()) + await step('sync-peer', () => owner.syncPeerService.stop()) + await step('sync-host', () => owner.syncHostService.stop()) + await step('replica-endpoint', () => owner.replicaEndpoint.close()) + await step('sync-replica', () => owner.syncReplica.close()) + await step('delegation', () => owner.execution.liveDelegationService.stop()) + await step('scheduler', () => owner.cronJobs.destroy()) + await step('remote', () => owner.remoteService.destroy()) + await step('hooks', () => options.hooks.retire()) + await step('context-hooks', async () => await owner.pluginService.contextHooks?.stop()) + await step('sessions', async () => { + const sessions = data!.appSessionService.list({ includeSubagents: true }) + const results = await Promise.allSettled( + sessions.flatMap((session) => { + const id = toAppSessionId(session.id) + return [ + owner.execution.deepChatAgentHarness.cleanupSession(id), + owner.execution.acpAgentRuntime.cleanupSession(id) + ] + }) + ) + const failures = results.flatMap((result) => + result.status === 'rejected' ? [result.reason] : [] + ) + if (failures.length) throw new AggregateError(failures, 'Core sessions did not retire') + }) + await step('background-exec', () => owner.backgroundExec.shutdown()) + await step('code-mode', () => owner.toolService.shutdownCodeRuntime()) + await step('runtime-initialization', () => owner.drainInitialization(step)) + await step('activation', async () => await starting?.catch(() => undefined)) + await step('plugins', () => owner.pluginService.shutdown()) + await step('mcp', () => owner.mcpService.shutdown()) + await step('workspace', () => owner.workspaceService.destroy()) + await step('skill-sync', () => owner.skillSyncService.destroy()) + await step('skills', () => owner.skillService.destroy()) + await step('watchers', () => owner.fileWatcherService.destroy()) + await step('memory', () => owner.memory.stop()) + await step('knowledge', () => owner.knowledgeService.destroy()) + await step('ocr', () => owner.ocrRuntimeService.close()) + owner.cliMutationGuard.clear() + await step('cli-audit', () => owner.cliAuditLog.close()) + } + await step('tape-watchers', async () => await data?.tapeInspectorHeadWatcher.close()) + typedEventHub.close() + unsubscribeStartup() + await step('artifacts', () => artifactSpool.close()) + providers?.unsubscribeProviderDbCatalog() + await step('providers', async () => await providers?.providerRuntime.shutdown()) + await step('acp', async () => await providers?.acpRuntimeOwner.shutdown()) + await step('requests', () => databaseRequests.drainForShutdown()) + await step('http', () => httpRetirement) + if (constructionIncomplete) + errors.push(new Error('Partial Core construction requires physical retirement')) + if (errors.length) throw new AggregateError(errors, 'Core Kernel did not retire') + })() + return stopping + } + try { + constructionIncomplete = true + await applyProxy(profile.proxySettings.getMode(), profile.proxySettings.getCustomUrl(), true) + if (isStopping()) throw new Error('Core Kernel startup was stopped') + data = createCoreData({ + profile, + profileDirectory: bootstrap.profileDirectory, + providerCatalogBootstrap: options.providerCatalogBootstrap, + publishEvent, + emitTapeHead: (webContentsId, pulse) => + typedEventHub.publish(sessionsTapeInspectorHeadChangedEvent.name, pulse, { + kind: 'renderer', + webContentsId + }), + onRendererDestroyed: native.onRendererDestroyed, + projectEnvironmentChanged: () => runtime?.projectService.notifyEnvironmentProjectionChanged() + }) + constructionIncomplete = false + await providerDbLoader.initialize() + if (isStopping()) throw new Error('Core Kernel startup was stopped') + constructionIncomplete = true + providers = createCoreProviders({ + profile, + data, + artifactSpool, + profileDirectory: bootstrap.profileDirectory, + homeDirectory: bootstrap.homeDirectory, + temporaryDirectory: bootstrap.temporaryDirectory, + registryDirectory: path.join( + bootstrap.isPackaged + ? path.join(bootstrap.resourcesPath, 'app.asar.unpacked') + : bootstrap.appPath, + 'resources', + 'acp-registry' + ), + crypto: native.providerCrypto, + openExternal: native.openExternal, + fetchRegistryIcon: native.fetchRegistryIcon, + sendToRenderer: native.sendToRenderer, + onRendererDestroyed: native.onRendererDestroyed, + publishEvent, + runCatalogRefresh: (operation) => + databaseRequests.runRequest('providers.catalog.refresh', operation), + projectEnvironmentChanged: () => runtime?.projectService.notifyEnvironmentProjectionChanged(), + readLanguage: () => settings.desktopSettings.getLanguage(), + allowInsecureTls: options.allowInsecureTls, + registry: { + getNpmRegistry: () => runtime!.mcpService.getNpmRegistry(), + getUvRegistry: () => runtime!.mcpService.getUvRegistry() + }, + memory: { + cleanupDeletedAgentResources: (id) => + runtime!.memoryService.cleanupDeletedAgentResources(id), + onAgentMemoryMaintenanceConfigChanged: (id) => + runtime!.memoryService.onAgentMemoryMaintenanceConfigChanged(id) + }, + cleanupAgentSkills: async (id) => { + await runtime!.ensureSkillServicesInitialized() + await runtime!.skillService.cleanupAgentSkills(id) + }, + skillSettings: settings.skillSettings, + assertAcpInstallAllowed: () => + databaseRequests.assertRouteAllowed('config.ensureAcpAgentInstalled') + }) + runtime = await createCoreRuntime({ + control: options.control, + ...native, + ...options.helpers, + importSnapshot: async (filePath, mode) => { + if (!maintenance) throw new Error('Core runtime is starting') + return await maintenance.run({ kind: 'import-backup-file', filePath, mode }) + }, + resolveRouteDispatcher: () => controller?.routeDispatcher, + profile, + data, + providers, + bootstrap, + settings, + browser: options.browser, + artifactSpool, + startupWorkloadCoordinator, + startupRunId, + databaseRequests, + syncActiveSessions, + mcpApps: { publishConsent: native.publishMcpConsent, desktop: native.mcpAppDesktop }, + getProxyUrl: refreshProxy, + whenProxyReady: async () => { + await refreshProxy() + }, + agentInvocationAdmission: options.admission, + memoryVectorStores: options.vectors, + memory: options.memory, + hooks: options.hooks, + execution: { + typedEventHub, + resolveSessionRunId: data.resolveSessionRunId, + onRendererDestroyed: native.onRendererDestroyed, + destroySessionBrowser: native.destroySessionBrowser, + observeDelegation: native.observeDelegation, + observationsEnabled: () => profile.settingsStore.get('loggingEnabled') ?? false + }, + onSessionCompleted: (id) => { + const session = data!.appSessionService.get(id) + if (session?.sessionKind !== 'regular' || data!.resolveSessionRunId(id) !== null) return + void native + .notifySessionCompletion({ id, title: session.title }) + .catch((error) => logger.warn('[Core] Session notification failed', { id, error })) + }, + agentCliTokenAuthority, + publishEvent, + isStopping + }) + constructionIncomplete = false + if (isStopping()) throw new Error('Core Kernel startup was stopped') + maintenance = createCoreMaintenance({ + profile, + data, + providers, + runtime, + settings, + databaseRequests, + startupWorkloadCoordinator, + native, + isStopping, + stopForCleanup: stop + }) + controller = createCoreController({ + profile, + data, + providers, + runtime, + settings, + bootstrap, + databaseRequests, + startupWorkloadCoordinator, + artifactSpool, + maintenance, + native: { ...native, applyProxy }, + publishEvent + }) + providers.agentSettings.start() + const start = (): Promise => { + if (starting) return starting + const owner = runtime! + const assertActive = () => { + if (isStopping()) throw new Error('Core Kernel startup was stopped') + } + starting = (async () => { + assertActive() + await owner.initializeStartup() + assertActive() + owner.cronJobs.start() + owner.memory.start() + await native.configurationHost.resume() + assertActive() + await settings.appSettingsService.observeSystemTheme() + assertActive() + try { + await artifactSpool.initialize() + assertActive() + await owner.cliServer.start() + } catch (error) { + native.reportStartupFailure('cli_control', 'unknown') + logger.error('[CLI] Failed to start local control server', error) + } + assertActive() + try { + await owner.syncHostService.startIfEnabled() + } catch (error) { + native.reportStartupFailure('sync_host', 'unknown') + logger.error('[SyncHost] Failed to start host mode', error) + } + assertActive() + try { + await owner.automaticSync.start() + } catch (error) { + logger.error('[SyncReplica] Failed to start automatic sync', error) + } + assertActive() + if (owner.cliServer.getStatus().running) { + try { + await owner.cliLauncherService.ensureInstalled() + } catch (error) { + native.reportStartupFailure('cli_launcher', 'unknown') + logger.warn('[CLI] Failed to install or refresh the command launcher', error) + } + } + assertActive() + owner.startStartupTasks() + owner.scheduleBackgroundWork() + })() + return starting + } + return { + data, + providers, + runtime, + controller, + maintenance, + get stopping() { + return stoppingRequested + }, + databaseRequests, + typedEventHub, + artifactSpool, + mcpApps: runtime.mcpApps, + publishEvent, + observeSystemResume: () => { + if (isStopping() || databaseRequests.state !== 'running' || systemResume) return + // The Native event lane must remain free for scheduler/helper replies. + systemResume = databaseRequests + .runRequest('system.resume', () => runtime!.cronJobs.handleSystemResume()) + .catch((error) => logger.warn('[Core] System resume failed', error)) + .finally(() => { + systemResume = undefined + }) + }, + observeSettingsWindow: (action: 'opened' | 'closed' | 'ready') => { + if (action === 'opened') startupWorkloadCoordinator.createRun('settings') + else if (action === 'closed') startupWorkloadCoordinator.cancelTarget('settings') + else startupWorkloadCoordinator.replayTarget('settings') + }, + start, + stop + } + } catch (error) { + try { + await stop() + } catch (cleanupError) { + throw new AggregateError([error, cleanupError], 'Core Kernel startup did not retire') + } + throw error + } +} diff --git a/src/harness/core/maintenance.ts b/src/harness/core/maintenance.ts new file mode 100644 index 0000000000..22271ead1c --- /dev/null +++ b/src/harness/core/maintenance.ts @@ -0,0 +1,331 @@ +import type { initializeCoreProfile } from '@/app/profileStartup' +import type { DatabaseSecurityMigrationDatabasePort } from '@/app/databaseSecurity' +import type { DatabaseSecurityStatus } from '@shared/contracts/routes' +import type { DatabaseRepairReport } from '@shared/types/databaseSchema' +import type { SyncImportDatabasePort, SyncImportResult } from '@/sync' +import { toAppSessionId } from '@/agent/shared/agentSessionIds' +import type { DatabaseRequestGate } from '@/app/databaseRequestGate' +import type { StartupWorkloadCoordinator } from '@/app/startupWorkloadCoordinator' +import type { createCoreData } from './data' +import type { createCoreProviders } from './providers' +import type { createCoreRuntime } from './runtime' +import type { createCoreSettings } from './settings' + +export type CoreMaintenanceNative = { + configurationHost: { + pause(): Promise + resume(): Promise + stopWrites(): Promise + drainWrites(): Promise + startWrites(): Promise + } + remoteAuthWindows: { closeAll(): Promise } +} + +type ApplicationDatabaseMaintenancePort = SyncImportDatabasePort & + DatabaseSecurityMigrationDatabasePort + +export type DatabaseImportCommand = + | { kind: 'import-backup-file'; filePath: string; mode: 'increment' | 'overwrite' } + | { kind: 'import-backup'; fileName: string; mode: 'increment' | 'overwrite' } + +export type DatabaseEncryptionCommand = + | { kind: 'enable-encryption'; password: string } + | { kind: 'change-password'; currentPassword: string; newPassword: string } + | { kind: 'disable-encryption'; currentPassword: string } + +export type DatabaseRepairCommand = { kind: 'repair-schema' } +type DatabaseMaintenanceCommand = + | DatabaseImportCommand + | DatabaseEncryptionCommand + | DatabaseRepairCommand +type DatabaseMaintenanceResult = SyncImportResult | DatabaseSecurityStatus | DatabaseRepairReport + +export function createCoreMaintenance(options: { + profile: Pick< + Awaited>, + 'database' | 'secretStore' | 'databaseSecurityService' + > + data: Pick< + ReturnType, + 'appSessionService' | 'providerSettings' | 'legacyChatImportService' + > + providers: Pick, 'providerRuntime' | 'agentSettings'> + settings: Pick, 'appSettingsService'> + runtime: Pick< + Awaited>, + | 'execution' + | 'syncService' + | 'replicaEndpoint' + | 'automaticSync' + | 'memory' + | 'cronJobs' + | 'remoteService' + | 'hookService' + | 'pluginService' + | 'syncReplica' + | 'mcpApps' + | 'scheduleBackgroundWork' + > + databaseRequests: DatabaseRequestGate + startupWorkloadCoordinator: StartupWorkloadCoordinator + native: CoreMaintenanceNative + isStopping(): boolean + stopForCleanup(): Promise +}) { + const { + profile, + data, + providers, + settings, + runtime, + databaseRequests, + startupWorkloadCoordinator, + native + } = options + const { database: mainDatabase, databaseSecurityService } = profile + const { appSessionService, providerSettings, legacyChatImportService } = data + const { providerRuntime, agentSettings } = providers + const { appSettingsService } = settings + const { + execution, + syncService, + replicaEndpoint, + automaticSync, + memory, + cronJobs, + remoteService, + hookService, + pluginService, + syncReplica + } = runtime + const { deepChatAgentHarness, acpAgentRuntime } = execution + function runDatabaseMaintenance(command: DatabaseImportCommand): Promise + function runDatabaseMaintenance( + command: DatabaseEncryptionCommand + ): Promise + function runDatabaseMaintenance(command: DatabaseRepairCommand): Promise + async function runDatabaseMaintenance( + command: DatabaseMaintenanceCommand + ): Promise { + if (options.isStopping()) { + throw new Error('App database owner is stopping') + } + const maintenanceState = databaseRequests.state + if (maintenanceState !== 'running') { + throw new Error(`App database maintenance is ${maintenanceState}`) + } + if (syncService.isBackupInProgress()) throw new Error('sync.tunnel.error.busy') + try { + return await databaseRequests.runMaintenance(() => performDatabaseMaintenance(command)) + } catch (error) { + if (databaseRequests.state === 'failed' && !options.isStopping()) + await options.stopForCleanup() + throw error + } + } + + async function performDatabaseMaintenance( + command: DatabaseMaintenanceCommand + ): Promise { + profile.secretStore.pause() + runtime.mcpApps.clear() + try { + await native.configurationHost.pause() + } catch (error) { + databaseRequests.fail() + throw error + } + try { + await providerRuntime.invalidateGitHubCopilotSignIns() + await startupWorkloadCoordinator.cancelAndDrainTarget('main') + await agentSettings.cancelAndDrainAcpWork() + if (options.isStopping()) { + throw new Error('App database owner is stopping') + } + } catch (error) { + if (databaseRequests.state === 'maintenance') { + try { + await native.configurationHost.resume() + if (options.isStopping() || databaseRequests.state !== 'maintenance') { + throw new Error('App database owner is stopping') + } + profile.secretStore.resume() + databaseRequests.resume() + } catch (resumeError) { + if (!options.isStopping()) databaseRequests.fail() + throw new AggregateError([error, resumeError], 'Native configuration did not resume') + } + agentSettings.notifyAgentCatalogChanged() + } + throw error + } + + let operationResult: DatabaseMaintenanceResult | undefined + let operationError: unknown + try { + await native.configurationHost.stopWrites() + await appSettingsService.stop() + await native.configurationHost.drainWrites() + replicaEndpoint.stop() + await automaticSync?.pause() + memory.pause() + await execution.liveDelegationService.stop() + await cronJobs.stop() + await remoteService.destroy() + await native.remoteAuthWindows.closeAll() + await hookService.stop() + await pluginService.contextHooks?.stop() + await memory.drain() + await suspendSessionRuntimes() + try { + await deepChatAgentHarness.drainPendingInputEffects() + } catch (error) { + databaseRequests.fail() + throw error + } + await databaseRequests.drain() + if (options.isStopping()) { + throw new Error('App database owner is stopping') + } + const database: ApplicationDatabaseMaintenancePort = { + getDatabasePath: () => mainDatabase.getDatabasePath(), + checkpointAndClose: () => { + const database = mainDatabase.getDatabase() + if (database.open) { + database.pragma('wal_checkpoint(TRUNCATE)') + } + mainDatabase.close() + }, + close: () => mainDatabase.close(), + reopen: () => reopenApplicationDatabase(), + reopenWithPassword: (password) => { + mainDatabase.reopenWithPassword(password) + }, + isOpen: () => mainDatabase.getDatabase().open, + importLegacyChatDb: (sourceDbPath, mode) => + legacyChatImportService.importFromSourceDb(sourceDbPath, mode) + } + switch (command.kind) { + case 'import-backup-file': + operationResult = await syncService.importBackupFile( + command.filePath, + command.mode, + database + ) + break + case 'import-backup': + operationResult = await syncService.importFromSync( + command.fileName, + command.mode, + database + ) + break + case 'enable-encryption': + operationResult = await databaseSecurityService.enableEncryption({ + password: command.password, + database, + providerSettings + }) + break + case 'change-password': + operationResult = await databaseSecurityService.changePassword({ + currentPassword: command.currentPassword, + newPassword: command.newPassword, + database, + providerSettings + }) + break + case 'disable-encryption': + operationResult = await databaseSecurityService.disableEncryption({ + currentPassword: command.currentPassword, + database, + providerSettings + }) + break + case 'repair-schema': + operationResult = await mainDatabase.repairSchema() + break + default: + throw new Error('Unsupported database maintenance command') + } + } catch (error) { + operationError = error + } + + if (databaseRequests.state === 'failed') throw operationError + try { + if (options.isStopping()) { + throw new Error('App database owner is stopping') + } + if (!mainDatabase.getDatabase().open) { + reopenApplicationDatabase() + } + await deepChatAgentHarness.reconcileAfterDatabaseReopen() + profile.secretStore.resume() + memory.start() + pluginService.contextHooks?.start() + hookService.start() + cronJobs.start() + await remoteService.initialize() + if (options.isStopping()) { + await remoteService.destroy() + await native.remoteAuthWindows.closeAll() + throw new Error('App database owner is stopping') + } + execution.createLiveDelegationService().start() + const startupRunId = startupWorkloadCoordinator.createRun('main') + runtime.scheduleBackgroundWork(startupRunId) + appSettingsService.start() + databaseRequests.resume() + await appSettingsService.publishSnapshot() + await appSettingsService.observeSystemTheme() + await native.configurationHost.startWrites() + agentSettings.notifyAgentCatalogChanged() + syncReplica?.wake() + await automaticSync?.start() + await native.configurationHost.resume() + } catch (error) { + if (!options.isStopping()) databaseRequests.fail() + throw error + } + + if (operationError) throw operationError + if (operationResult === undefined) throw new Error('Database maintenance returned no result') + return operationResult + } + + function reopenApplicationDatabase(): void { + mainDatabase.reopen() + } + + async function suspendSessionRuntimes(): Promise { + const results = await Promise.allSettled( + appSessionService.list({ includeSubagents: true }).flatMap((session) => { + const sessionId = toAppSessionId(session.id) + return [ + deepChatAgentHarness.cleanupSession(sessionId), + acpAgentRuntime.cleanupSession(sessionId) + ] + }) + ) + const failure = results.find( + (result): result is PromiseRejectedResult => result.status === 'rejected' + ) + if (failure) throw failure.reason + } + + async function pullLatestBackupFromCloud(importMode: 'increment' | 'overwrite' = 'increment') { + const download = await syncService.downloadLatestBackupFromCloud() + if (!download.success || !download.fileName) return download + const backupFileName = download.fileName + const result = await runDatabaseMaintenance({ + kind: 'import-backup', + fileName: backupFileName, + mode: importMode + }) + return { ...result, fileName: backupFileName } + } + + return { run: runDatabaseMaintenance, pullLatestBackupFromCloud, suspendSessionRuntimes } +} diff --git a/src/harness/core/memory.ts b/src/harness/core/memory.ts new file mode 100644 index 0000000000..4333368614 --- /dev/null +++ b/src/harness/core/memory.ts @@ -0,0 +1,170 @@ +import path from 'node:path' +import type { Context } from '@arcships/rutis' +import { z } from 'zod' +import { + isMemoryVectorControl, + MEMORY_VECTOR_FRAME_BYTES, + MEMORY_VECTOR_PENDING, + MemoryVectorCommandSchema, + MemoryVectorConfigSchema, + type MemoryVectorCommand, + type MemoryVectorConfig +} from '@shared/harness/memory-vector' + +export interface CoreMemoryService { + memory( + generation: string, + sequence: number, + method: 'start' | 'call' | 'retire', + input: unknown, + data: Uint8Array | null + ): Promise +} + +const identitySchema = z + .object({ + generation: z.string().uuid(), + pid: z.number().int().positive(), + node: z.string().max(64), + modules: z.string().max(32) + }) + .strict() + +export class CoreMemoryRuntime { + private config: MemoryVectorConfig | undefined + private startup: Promise | undefined + private ready = false + private stopping = false + private retirement: Promise | undefined + private failure: Error | undefined + private sequence = 0 + private readonly pending = new Map>>() + private readonly unsubscribe: () => void + + constructor( + context: Pick, + private readonly service: CoreMemoryService, + private readonly generation: string + ) { + this.unsubscribe = context.on('deepchat/memory/stopped', (generation, message) => { + if (generation === this.config?.generation && typeof message === 'string') + this.fail(new Error(message)) + }) + } + + get memoryAvailable(): boolean { + return this.ready && !this.failure && !this.stopping + } + + async startMemory(input: MemoryVectorConfig): Promise { + if (this.failure) throw this.failure + if (this.stopping) throw new Error('Core Memory is stopping') + const config = MemoryVectorConfigSchema.parse(input) + if ( + !path.isAbsolute(config.memoryDbDir) || + !path.isAbsolute(config.host.appPath) || + !path.isAbsolute(config.host.userDataPath) + ) + throw new Error('Core Memory paths must be absolute') + if (this.config && JSON.stringify(this.config) !== JSON.stringify(config)) + throw new Error('Core Memory configuration cannot change') + this.config = config + this.startup ??= (async () => { + const timeout = Promise.withResolvers() + const timer = setTimeout( + () => timeout.reject(new Error('Core Memory startup timed out')), + 15_000 + ) + timer.unref() + try { + const identity = identitySchema.parse( + await Promise.race([ + this.service.memory(this.generation, 0, 'start', config, null), + timeout.promise + ]) + ) + if ( + identity.generation !== config.generation || + identity.modules !== process.versions.modules || + identity.node.split('.')[0] !== process.versions.node.split('.')[0] + ) + throw new Error('Core Memory runtime identity changed') + if (this.failure) throw this.failure + this.ready = true + } catch (error) { + this.fail(error instanceof Error ? error : new Error(String(error))) + throw this.failure + } finally { + clearTimeout(timer) + } + })() + await this.startup + } + + requestMemory(input: MemoryVectorCommand): Promise { + if (!this.memoryAvailable) + return Promise.reject(this.failure ?? new Error('Core Memory is unavailable')) + let command: MemoryVectorCommand + let data: Uint8Array | undefined + let metadata: unknown + const id = this.sequence + 1 + try { + command = MemoryVectorCommandSchema.parse(input) + if (command.generation !== this.config?.generation) + throw new Error('Core Memory generation changed') + const control = isMemoryVectorControl(command.method) + if (this.pending.size >= (control ? MEMORY_VECTOR_PENDING : MEMORY_VECTOR_PENDING - 8)) + throw new Error('Core Memory operation limit exceeded') + data = 'data' in command ? command.data : undefined + metadata = Object.fromEntries( + Object.entries(command).filter(([key, value]) => key !== 'data' && value !== undefined) + ) + const bytes = Buffer.byteLength(JSON.stringify([this.generation, id, 'call', metadata])) + if (bytes + (data?.byteLength ?? 0) > (control ? 64 * 1024 : MEMORY_VECTOR_FRAME_BYTES)) + throw new Error('Core Memory frame limit exceeded') + if (!Number.isSafeInteger(id)) throw new Error('Core Memory sequence exhausted') + } catch (error) { + return Promise.reject(error) + } + this.sequence = id + const pending = Promise.withResolvers() + this.pending.set(id, pending) + void this.service.memory(this.generation, id, 'call', metadata, data ?? null).then( + (value) => { + this.pending.delete(id) + pending.resolve(value) + }, + (error: unknown) => { + this.pending.delete(id) + if ( + !(error instanceof Error) || + [ + 'MemoryVectorRuntimeUnavailableError', + 'MemoryVectorProtocolError', + 'HarnessTransportError', + 'BindingError' + ].includes(error.name) + ) + this.fail(error instanceof Error ? error : new Error(String(error))) + pending.reject(error) + } + ) + return pending.promise + } + + retire(): Promise { + this.stopping = true + return (this.retirement ??= (async () => { + // The host fences startup and reaps the child before the Core profile can unlock. + await this.service.memory(this.generation, 0, 'retire', null, null) + this.fail(new Error('Core Memory generation retired')) + this.unsubscribe() + })()) + } + + private fail(error: Error): void { + this.failure ??= error + for (const pending of this.pending.values()) pending.reject(this.failure) + this.pending.clear() + } +} diff --git a/src/harness/core/native.ts b/src/harness/core/native.ts new file mode 100644 index 0000000000..8237f369e8 --- /dev/null +++ b/src/harness/core/native.ts @@ -0,0 +1,87 @@ +import { deserialize, serialize } from 'node:v8' +import { + CORE_NATIVE_CALLS, + CORE_NATIVE_QUEUED, + CORE_VALUE_BYTES, + isCoreNativeDrain, + type CoreNativeMethod, + type CorePayload +} from '@shared/harness/core' +import type { CorePayloadStore } from './payload' + +export class CoreNativeCalls { + private ordinary = 0 + private control = 0 + private queued = 0 + private queuedBytes = 0 + private changed = Promise.withResolvers() + private readonly active = new Set>() + + constructor( + private readonly store: CorePayloadStore, + private readonly invoke: ( + method: CoreNativeMethod, + payload: CorePayload + ) => Promise, + private readonly isStopping: () => boolean + ) {} + + call(method: CoreNativeMethod, args: unknown[]): Promise { + const operation = this.execute(method, args) + this.active.add(operation) + void operation.then( + () => this.active.delete(operation), + () => this.active.delete(operation) + ) + return operation + } + + async drain(): Promise { + await Promise.allSettled(this.active) + } + + private async execute(method: CoreNativeMethod, args: unknown[]): Promise { + const control = isCoreNativeDrain(method, args) + const assertActive = () => { + if (this.isStopping() && !control) throw new Error('Core Native admission is stopping') + } + assertActive() + const full = () => (control ? this.control >= 2 : this.ordinary >= CORE_NATIVE_CALLS - 2) + if (full()) { + if (this.queued >= CORE_NATIVE_QUEUED) throw new Error('Core Native request queue exceeded') + const snapshot = (() => { + const encoded = serialize(args) + return { bytes: encoded.length, value: deserialize(encoded) as unknown[] } + })() + if (this.queuedBytes + snapshot.bytes > CORE_VALUE_BYTES) + throw new Error('Core Native queued payload limit exceeded') + args = snapshot.value + this.queued++ + this.queuedBytes += snapshot.bytes + try { + while (full()) { + await this.changed.promise + assertActive() + } + } finally { + this.queued-- + this.queuedBytes -= snapshot.bytes + } + } + if (control) this.control++ + else this.ordinary++ + let ref: CorePayload | undefined + try { + ref = await this.store.encode(args) + assertActive() + return await this.store.decode(await this.invoke(method, ref)) + } finally { + if (control) this.control-- + else this.ordinary-- + const changed = this.changed + this.changed = Promise.withResolvers() + changed.resolve() + if (ref) await this.store.release(ref) + } + } +} diff --git a/src/harness/core/network.ts b/src/harness/core/network.ts new file mode 100644 index 0000000000..379a8e1db6 --- /dev/null +++ b/src/harness/core/network.ts @@ -0,0 +1,87 @@ +import { setGlobalDispatcher } from 'undici' +import { applyProxyEnvironment, createGlobalFetchDispatcher } from '@/platform/fetchDispatcher' +import { CoreProxySnapshotSchema, type CoreProxySnapshot } from '@shared/harness/desktop' + +type Dispatcher = ReturnType + +export class CoreHttp { + private snapshot: CoreProxySnapshot | undefined + private current: Dispatcher | undefined + private readonly retiring = new Map>() + private closing: Promise | undefined + + constructor(private readonly failed: (error: unknown) => void) {} + + apply(input: CoreProxySnapshot): void { + if (this.closing) throw new Error('Core HTTP is stopping') + const snapshot = CoreProxySnapshotSchema.parse(input) + if (this.snapshot && snapshot.version < this.snapshot.version) return + if (this.snapshot && snapshot.version === this.snapshot.version) { + if ( + snapshot.proxyUrl !== this.snapshot.proxyUrl || + snapshot.noProxy !== this.snapshot.noProxy + ) + throw new Error('Native proxy changed without a revision') + return + } + if ( + this.snapshot && + snapshot.proxyUrl === this.snapshot.proxyUrl && + snapshot.noProxy === this.snapshot.noProxy + ) { + this.snapshot = snapshot + return + } + const next = createGlobalFetchDispatcher( + snapshot.proxyUrl === null + ? undefined + : { + httpProxy: snapshot.proxyUrl, + httpsProxy: snapshot.proxyUrl, + noProxy: snapshot.noProxy + } + ) + const previous = this.current + setGlobalDispatcher(next) + applyProxyEnvironment(snapshot.proxyUrl, snapshot.noProxy) + this.current = next + this.snapshot = snapshot + if (previous) { + const retirement = previous + .close() + .catch(async () => await previous.destroy()) + .then(() => { + this.retiring.delete(previous) + }) + this.retiring.set(previous, retirement) + void retirement.catch((error) => { + if (!this.closing) this.failed(error) + }) + } + } + + getProxyUrl(): string | null { + if (!this.snapshot || this.closing) throw new Error('Core HTTP is unavailable') + return this.snapshot.proxyUrl + } + + close(): Promise { + if (this.closing) return this.closing + const dispatchers = [...this.retiring.keys(), ...(this.current ? [this.current] : [])] + this.closing = (async () => { + const results = await Promise.allSettled( + dispatchers.map( + async (dispatcher) => await dispatcher.destroy(new Error('Core HTTP is stopping')) + ) + ) + await Promise.allSettled(this.retiring.values()) + const errors = results.flatMap((result) => + result.status === 'rejected' ? [result.reason] : [] + ) + if (errors.length) throw new AggregateError(errors, 'Core HTTP did not retire') + this.retiring.clear() + this.current = undefined + })() + return this.closing + } +} diff --git a/src/harness/core/oauth.ts b/src/harness/core/oauth.ts new file mode 100644 index 0000000000..8da248e63b --- /dev/null +++ b/src/harness/core/oauth.ts @@ -0,0 +1,128 @@ +import logger from '@shared/logger' +import { getGitHubCopilotToken } from '@/provider/auth/githubCopilotToken' +import type { OpenAICodexAuthStatus } from '@shared/types/openai-codex' +import type { XaiGrokAuthStatus } from '@shared/types/xai-grok' +import type { + GitHubCopilotCredentialOwner, + OAuthServicePort, + ProviderAccountAuthPort +} from '@shared/types/oauth' + +export interface GitHubCopilotAuthorizationPort { + browser(clientId: string | undefined, signal: AbortSignal): Promise + device(clientId: string | undefined, signal: AbortSignal): Promise +} + +export class OAuthService implements OAuthServicePort { + private stopped = false + private readonly copilotController = new AbortController() + private readonly copilotLogins = new Set>() + private stopping: Promise | undefined + + constructor( + private readonly providerAuth: GitHubCopilotCredentialOwner, + private readonly accounts: ProviderAccountAuthPort, + private readonly authorization: GitHubCopilotAuthorizationPort + ) {} + + stop(): Promise { + if (this.stopping) return this.stopping + this.stopped = true + this.copilotController.abort() + this.stopping = (async () => { + const results = await Promise.allSettled([ + Promise.resolve().then(async () => await this.accounts.stop()), + Promise.resolve().then( + async () => await this.providerAuth.invalidateGitHubCopilotSignIns() + ), + Promise.allSettled(this.copilotLogins) + ]) + const errors = results.flatMap((result) => + result.status === 'rejected' ? [result.reason] : [] + ) + if (errors.length === 1) throw errors[0] + if (errors.length) throw new AggregateError(errors, 'OAuth owner did not retire') + })() + return this.stopping + } + + /** + * Start GitHub Copilot Device Flow login process (recommended) + */ + startGitHubCopilotDeviceFlowLogin(providerId: string): Promise { + return this.signIn(providerId, 'device') + } + + async getOpenAICodexStatus(): Promise { + return this.accounts.getOpenAICodexStatus() + } + + async startOpenAICodexBrowserLogin(): Promise { + return this.accounts.startOpenAICodexBrowserLogin() + } + + async completeOpenAICodexBrowserLoginFromUrl( + callbackUrl: string + ): Promise { + return this.accounts.completeOpenAICodexBrowserLoginFromUrl(callbackUrl) + } + + async cancelOpenAICodexLogin(): Promise { + return this.accounts.cancelOpenAICodexLogin() + } + + async logoutOpenAICodex(): Promise { + return this.accounts.logoutOpenAICodex() + } + + async getXaiGrokStatus(): Promise { + return this.accounts.getXaiGrokStatus() + } + + async startXaiGrokDeviceLogin(): Promise { + return this.accounts.startXaiGrokDeviceLogin() + } + + async cancelXaiGrokLogin(): Promise { + return this.accounts.cancelXaiGrokLogin() + } + + async logoutXaiGrok(): Promise { + return this.accounts.logoutXaiGrok() + } + + /** + * Start GitHub Copilot OAuth login process (traditional method) + */ + startGitHubCopilotLogin(providerId: string): Promise { + return this.signIn(providerId, 'browser') + } + + private signIn(providerId: string, kind: 'browser' | 'device'): Promise { + if (this.stopped) return Promise.reject(new Error('OAuth service is stopped')) + const assertActive = () => { + if (this.stopped) throw new Error('OAuth service is stopped') + } + const operation = Promise.resolve().then(async () => { + assertActive() + const target = await this.providerAuth.getGitHubCopilotSignInTarget(providerId) + assertActive() + const accessToken = await this.authorization[kind]( + target.clientId, + this.copilotController.signal + ) + assertActive() + await getGitHubCopilotToken(accessToken, this.copilotController.signal) + assertActive() + await this.providerAuth.commitGitHubCopilotCredential(target, accessToken) + logger.info('[GitHub Copilot] Sign-in completed', { kind }) + return true + }) + this.copilotLogins.add(operation) + void operation.then( + () => this.copilotLogins.delete(operation), + () => this.copilotLogins.delete(operation) + ) + return operation + } +} diff --git a/src/harness/core/payload.ts b/src/harness/core/payload.ts new file mode 100644 index 0000000000..276369f1d3 --- /dev/null +++ b/src/harness/core/payload.ts @@ -0,0 +1,174 @@ +import { createCipheriv, createDecipheriv, randomBytes, randomUUID } from 'node:crypto' +import { constants } from 'node:fs' +import { mkdtemp, open, realpath, rm, lstat } from 'node:fs/promises' +import path from 'node:path' +import { serialize, deserialize } from 'node:v8' +import { + CORE_CARRIER_FILES, + CORE_INLINE_BYTES, + CORE_VALUE_BYTES, + CoreCarrierConfigSchema, + CorePayloadSchema, + type CoreCarrierConfig, + type CorePayload +} from '@shared/harness/core' + +export class CorePayloadStore { + private readonly key: Buffer + private readonly files = new Map() + private readonly operations = new Set>() + private closed = false + + constructor( + readonly config: CoreCarrierConfig, + private readonly sender: 'main' | 'core' + ) { + CoreCarrierConfigSchema.parse(config) + if (!path.isAbsolute(config.directory)) throw new Error('Core carrier root must be absolute') + this.key = Buffer.from(config.key, 'base64') + if (this.key.length !== 32 || this.key.toString('base64') !== config.key) + throw new Error('Invalid Core carrier key') + } + + static async create(temporaryDirectory: string): Promise { + const directory = await realpath(await mkdtemp(path.join(temporaryDirectory, 'deepchat-core-'))) + return new CorePayloadStore( + { directory, generation: randomUUID(), key: randomBytes(32).toString('base64') }, + 'main' + ) + } + + private assertActive(): void { + if (this.closed) throw new Error('Core payload generation is closed') + } + + private file(ref: Extract): string { + return path.join( + this.config.directory, + `${this.config.generation}.${ref.sender}.${ref.operation}` + ) + } + + private aad(ref: Extract): Buffer { + return Buffer.from(`${this.config.generation}:${ref.sender}:${ref.operation}:${ref.bytes}`) + } + + private track(run: () => Promise): Promise { + const operation = run() + this.operations.add(operation) + void operation.then( + () => this.operations.delete(operation), + () => this.operations.delete(operation) + ) + return operation + } + + encode(value: unknown): Promise { + return this.track(() => this.encodeValue(value)) + } + + private async encodeValue(value: unknown): Promise { + this.assertActive() + const bytes = serialize(value) + if (bytes.length > CORE_VALUE_BYTES) throw new Error('Core value requires a paged transfer') + if (bytes.length <= CORE_INLINE_BYTES) return { inline: bytes.toString('base64') } + for (const [file, state] of this.files) { + if (state === 'writing') continue + try { + await lstat(file) + } catch (error) { + if ((error as NodeJS.ErrnoException).code !== 'ENOENT') throw error + this.files.delete(file) + } + } + this.assertActive() + if (this.files.size >= CORE_CARRIER_FILES) throw new Error('Core carrier quota exceeded') + const ref = { operation: randomUUID(), sender: this.sender, bytes: bytes.length } + const file = this.file(ref) + this.files.set(file, 'writing') + try { + const nonce = randomBytes(12) + const cipher = createCipheriv('aes-256-gcm', this.key, nonce) + cipher.setAAD(this.aad(ref)) + const encrypted = Buffer.concat([cipher.update(bytes), cipher.final()]) + const handle = await open( + file, + constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL | (constants.O_NOFOLLOW ?? 0), + 0o600 + ) + try { + await handle.writeFile(Buffer.concat([nonce, cipher.getAuthTag(), encrypted])) + } finally { + await handle.close() + } + this.assertActive() + this.files.set(file, 'ready') + return ref + } catch (error) { + this.files.delete(file) + await rm(file, { force: true }) + throw error + } + } + + decode(input: unknown): Promise { + return this.track(() => this.decodeValue(input)) + } + + private async decodeValue(input: unknown): Promise { + this.assertActive() + const ref = CorePayloadSchema.parse(input) + if ('inline' in ref) { + const bytes = Buffer.from(ref.inline, 'base64') + if (bytes.length > CORE_INLINE_BYTES || bytes.toString('base64') !== ref.inline) + throw new Error('Invalid inline Core payload') + return deserialize(bytes) + } + if (ref.sender === this.sender) throw new Error('Invalid Core payload direction') + const file = this.file(ref) + if ((await realpath(this.config.directory)) !== this.config.directory) + throw new Error('Core carrier root changed') + const handle = await open(file, constants.O_RDONLY | (constants.O_NOFOLLOW ?? 0)) + let consumed = false + try { + const stat = await handle.stat() + if (!stat.isFile() || stat.size !== ref.bytes + 28) + throw new Error('Invalid Core carrier size') + const bytes = Buffer.allocUnsafe(ref.bytes + 28) + let offset = 0 + while (offset < bytes.length) { + const read = await handle.read(bytes, offset, bytes.length - offset, offset) + if (!read.bytesRead) throw new Error('Core carrier was truncated') + offset += read.bytesRead + } + if ((await handle.read(Buffer.alloc(1), 0, 1, bytes.length)).bytesRead) + throw new Error('Core carrier grew beyond its declared size') + this.assertActive() + const decipher = createDecipheriv('aes-256-gcm', this.key, bytes.subarray(0, 12)) + decipher.setAAD(this.aad(ref)) + decipher.setAuthTag(bytes.subarray(12, 28)) + const plain = Buffer.concat([decipher.update(bytes.subarray(28)), decipher.final()]) + const value: unknown = deserialize(plain) + consumed = true + return value + } finally { + await handle.close() + if (consumed) await rm(file, { force: true }) + } + } + + async release(ref: CorePayload): Promise { + if ('inline' in ref) return + const file = this.file(ref) + if (ref.sender !== this.sender || !this.files.delete(file)) return + await rm(file, { force: true }) + } + + async close(): Promise { + this.closed = true + await Promise.allSettled(this.operations) + this.key.fill(0) + await Promise.all([...this.files.keys()].map((file) => rm(file, { force: true }))) + this.files.clear() + } +} diff --git a/src/harness/core/providers.ts b/src/harness/core/providers.ts new file mode 100644 index 0000000000..221e2e8dd7 --- /dev/null +++ b/src/harness/core/providers.ts @@ -0,0 +1,247 @@ +import logger from '@shared/logger' +import { CliComputeService } from '@/cli/computeService' +import { CliAudioTranscriptionService } from '@/cli/audioTranscriptionService' +import type { ArtifactSpool } from '@/cli/artifactSpool' +import path from 'node:path' +import type { initializeCoreProfile } from '@/app/profileStartup' +import type { createCoreData } from './data' +import type { CredentialCryptoPort } from '@/config/secretStore' +import type { ProviderAccountAuthPort } from '@shared/types/oauth' +import type { DeepchatEventName } from '@shared/contracts/events' +import { ProviderRuntime } from '@/provider' +import type { ProviderCatalogServiceDeps } from '@/provider/catalog/providerCatalogService' +import { AcpProvider } from '@/provider/providers/acpProvider' +import { buildDefaultProviderHeaders } from '@/provider/providerHeaders' +import { providerDbLoader } from '@/provider/providerDbLoader' +import { OpenAICodexCredentialStore } from '@/provider/auth/openaiCodex/credentialStore' +import { XaiGrokCredentialStore } from '@/provider/auth/xaiGrok/credentialStore' +import { initializeGlobalOpenAICodexAuth } from '@/provider/auth/openaiCodex' +import { initializeGlobalXaiGrokAuth } from '@/provider/auth/xaiGrok' +import { AgentSettings } from '@/agent/settings' +import { AcpSessionPersistence } from '@/agent/acp/runtime' +import { AcpRegistryService } from '@/agent/acp/catalog/acpRegistryService' +import { + createAcpRuntimeOwner, + type AcpRuntimeOwnerDependencies +} from '@/agent/acp/createRuntimeOwner' +import { AcpAuthService, type AcpAuthServiceDependencies } from '@/agent/acp/auth/acpAuthService' +import { emitAgentCatalogChanged, emitAcpAgentModelsChanged } from '@/app/agentEvents' +import { emitModelsChanged } from '@/provider/eventPublishers' +import type { MemoryServicePort } from '@/memory' +import type { SkillSettings } from '@/skill/settings' + +type CoreProfile = Awaited> +type CoreData = ReturnType + +export function createCoreProviders(options: { + profile: Pick< + CoreProfile, + 'settingsStore' | 'privacySettings' | 'acpCatalogSettings' | 'mcpSettings' | 'currentAppVersion' + > + data: Pick< + CoreData, + | 'agentDatabase' + | 'sessionData' + | 'projectDatabase' + | 'providerSettings' + | 'agentRepository' + | 'appSessionService' + | 'agentLifecycle' + > + artifactSpool: ArtifactSpool + profileDirectory: string + homeDirectory: string + temporaryDirectory: string + registryDirectory: string + crypto: CredentialCryptoPort + openExternal(url: string): Promise + fetchRegistryIcon(url: string, signal: AbortSignal): Promise + sendToRenderer: AcpAuthServiceDependencies['sendToRenderer'] + onRendererDestroyed: AcpAuthServiceDependencies['onRendererDestroyed'] + publishEvent(name: DeepchatEventName, payload: unknown): void + runCatalogRefresh: NonNullable + projectEnvironmentChanged(): void + readLanguage(): string + allowInsecureTls: boolean + registry: AcpRuntimeOwnerDependencies['registry'] + memory: Pick< + MemoryServicePort, + 'cleanupDeletedAgentResources' | 'onAgentMemoryMaintenanceConfigChanged' + > + cleanupAgentSkills(agentId: string): Promise + skillSettings: SkillSettings + assertAcpInstallAllowed(): void +}) { + const { profile, publishEvent: publishDeepchatEvent, skillSettings, artifactSpool } = options + const { + agentDatabase, + sessionData, + projectDatabase, + providerSettings, + agentRepository, + appSessionService, + agentLifecycle + } = options.data + const acpBootstrap = { + appVersion: profile.currentAppVersion, + homeDirectory: options.homeDirectory, + temporaryDirectory: options.temporaryDirectory + } + const acpSessionPersistence = new AcpSessionPersistence( + agentDatabase, + sessionData.database, + projectDatabase, + acpBootstrap.homeDirectory, + options.projectEnvironmentChanged + ) + let providerRuntime!: ProviderRuntime + const codexAuth = initializeGlobalOpenAICodexAuth( + publishDeepchatEvent, + new OpenAICodexCredentialStore({ + filePath: path.join(options.profileDirectory, 'openai-codex-auth', 'credentials.json'), + crypto: options.crypto + }), + options.openExternal + ) + const grokAuth = initializeGlobalXaiGrokAuth( + publishDeepchatEvent, + new XaiGrokCredentialStore({ + filePath: path.join(options.profileDirectory, 'xai-grok-auth', 'credentials.json'), + crypto: options.crypto + }), + options.openExternal + ) + const acpRegistry = new AcpRegistryService({ + userDataDirectory: options.profileDirectory, + builtInDirectory: options.registryDirectory, + isPrivacyModeEnabled: () => profile.privacySettings.isEnabled(), + fetchIcon: options.fetchRegistryIcon + }) + const agentSettings = new AgentSettings( + profile.settingsStore, + agentRepository, + profile.acpCatalogSettings, + acpRegistry, + options.profileDirectory, + { + getModelConfig: (modelId, providerId) => providerSettings.getModelConfig(modelId, providerId), + setAcpProviderEnabled: (enabled) => { + const provider = providerSettings.getProviderById('acp') + if (provider && provider.enable !== enabled) { + providerRuntime.updateProviderAtomic('acp', { enable: enabled }) + } + }, + clearAcpProviderModels: () => + providerRuntime.replaceProviderCatalogFromSource('acp', [], 'acp'), + clearAcpProviderModelStatus: () => providerSettings.clearProviderModelStatusCache('acp'), + refreshAcpProviderAgents: async (agentIds) => { + const provider = providerRuntime.getProviderInstance('acp') + if (!(provider instanceof AcpProvider)) { + throw new Error('ACP provider is not initialized.') + } + await provider.refreshAgents(agentIds) + } + }, + { + publishCatalogChanged: (agentIds) => + emitAgentCatalogChanged(agentSettings, publishDeepchatEvent, agentIds), + publishAcpModelsChanged: () => { + emitModelsChanged(publishDeepchatEvent, 'acp') + emitAcpAgentModelsChanged(publishDeepchatEvent) + }, + publishSessionsUpdated: () => + publishDeepchatEvent('sessions.updated', { + sessionIds: [], + reason: 'list-refreshed' + }) + }, + (agentId) => options.memory.cleanupDeletedAgentResources(agentId), + options.cleanupAgentSkills, + (agentId) => options.memory.onAgentMemoryMaintenanceConfigChanged(agentId), + skillSettings, + agentLifecycle, + options.assertAcpInstallAllowed + ) + const acpRuntimeOwner = createAcpRuntimeOwner({ + bootstrap: acpBootstrap, + providerConfig: providerSettings, + agentSettings, + mcpSettings: profile.mcpSettings, + sessionPersistence: acpSessionPersistence, + titles: { + get: (sessionId) => appSessionService.get(sessionId)?.title, + apply: (sessionId, expected, title) => { + const session = appSessionService.get(sessionId) + if (!session || session.title !== expected) return false + appSessionService.update(sessionId, { title }) + publishDeepchatEvent('sessions.updated', { sessionIds: [sessionId], reason: 'updated' }) + return true + } + }, + publishEvent: publishDeepchatEvent, + registry: options.registry + }) + const acpAuthService = new AcpAuthService({ + owner: acpRuntimeOwner, + agentSettings, + sendToRenderer: options.sendToRenderer, + onRendererDestroyed: options.onRendererDestroyed + }) + providerRuntime = new ProviderRuntime( + providerSettings, + { + getLanguage: options.readLanguage, + defaultHeaders: buildDefaultProviderHeaders(profile.currentAppVersion), + allowInsecureTls: options.allowInsecureTls + }, + agentSettings, + profile.mcpSettings, + acpRuntimeOwner, + acpSessionPersistence, + publishDeepchatEvent, + options.runCatalogRefresh + ) + const cliComputeService = new CliComputeService({ + providerSettings, + providerRuntime, + artifactSpool, + mediaCacheDirectory: path.join(options.profileDirectory, 'images'), + log: logger + }) + const cliAudioTranscriptionService = new CliAudioTranscriptionService({ + providerSettings, + providerRuntime, + artifactSpool, + log: logger + }) + const accounts: ProviderAccountAuthPort = { + getOpenAICodexStatus: () => codexAuth.getStatus(), + startOpenAICodexBrowserLogin: () => codexAuth.startBrowserLogin(), + completeOpenAICodexBrowserLoginFromUrl: (url) => + codexAuth.completeBrowserLoginFromCallbackUrl(url), + cancelOpenAICodexLogin: () => codexAuth.cancelLogin(), + logoutOpenAICodex: () => codexAuth.logout(), + getXaiGrokStatus: () => grokAuth.getStatus(), + startXaiGrokDeviceLogin: () => grokAuth.startDeviceLogin(), + cancelXaiGrokLogin: () => grokAuth.cancelLogin(), + logoutXaiGrok: () => grokAuth.logout(), + stop: async () => { + codexAuth.stop() + grokAuth.stop() + } + } + const unsubscribeProviderDbCatalog = providerDbLoader.subscribeCatalogChanges((change) => { + if (change.reason === 'updated') providerRuntime.handleProviderDbUpdated() + }) + return { + providerRuntime, + agentSettings, + acpSessionPersistence, + acpRuntimeOwner, + acpAuthService, + accounts, + unsubscribeProviderDbCatalog, + cliComputeService, + cliAudioTranscriptionService + } +} diff --git a/src/harness/core/runtime.ts b/src/harness/core/runtime.ts new file mode 100644 index 0000000000..239b1cfe98 --- /dev/null +++ b/src/harness/core/runtime.ts @@ -0,0 +1,1951 @@ +import type { createCoreSettings } from './settings' +import { SyncReplicaStore } from '@/sync/replica/store' +import { SyncReplicaEndpoint } from '@/sync/replica/endpoint' +import { AutomaticSync } from '@/sync/replica/automatic' +import { SyncPeerService } from '@/sync/peer' +import { NowledgeMemConnections } from '@/nowledgeMem' +import { NOWLEDGE_PLUGIN_ID } from '@shared/types/nowledgeMemPlugin' +import logger from '@shared/logger' +import { + type MainLogStartupComponent, + type MainLogStartupComponentFailureCategory +} from '@/logging/mainLogEvents' +import { projectEnvironmentsChangedEvent } from '@shared/contracts/events/project.events' +import path from 'path' +import { type DeepchatEventName } from '@shared/contracts/events' +import { type RutisMemoryVectorStores } from '@/harness/memory-vector' +import { FileService } from '@/file' +import { noteNodeDemandFromMcp, mergeDetectionEnv, ToolchainService } from '@/toolchains' +import { McpService } from '@/mcp' +import { McpAppSandboxRegistry, type McpAppSandboxPort } from '@/mcp/apps/sandboxRegistry' +import { McpOAuthCredentialStore } from '@/mcp/oauthCredentialStore' +import { SyncService } from '@/sync' +import { type DatabaseRequestGate } from '@/app/databaseRequestGate' +import { SyncSettings } from '@/sync/settings' +import { type SemanticNotificationPublisher } from '@/notifications' +import { KnowledgeService } from '@/knowledge' +import { FileWatcherService, WatcherPool } from '@/platform/fileWatcher' +import { + WatcherHostClient, + type SpawnFileWatcherHost +} from '@/platform/fileWatcher/watcherHostClient' +import { WorkspaceService } from '@/workspace' +import type { WorkspaceDesktopPort } from '@/workspace/ports' +import { ToolService } from '@/tool' +import { KnowledgeSettings } from '@/knowledge/settings' +import { emitAgentCatalogChanged } from '@/app/agentEvents' +import { SyncHostService } from '@/sync/host' +import { + CommandPermissionService, + FilePermissionService, + SettingsPermissionService, + ToolPermissionBroker +} from '@/tool/permission' +import type { AgentToolDependencies } from '@/tool/runtimePorts' +import { ConversationExporterService } from '@/exporter' +import { SkillService } from '@/skill' +import type { SkillSessionStatePort } from '@/skill' +import { SkillSyncService } from '@/skill/sync' +import { SkillExecutionAuthorityResolver } from '@/skill/skillExecutionAuthority' +import { HookSettings } from '@/hook/config' +import { SchedulerService, createCronJobRunSessionStarter } from '@/scheduler' +import { SchedulerProcessManager } from '@/scheduler/schedulerProcessManager' +import { toAppSessionId } from '@/agent/shared/agentSessionIds' +import type { MemoryCapability } from '../plugins/memory' +import type { HooksCapability } from '../plugins/hooks' +import { ProjectService } from '@/project' +import { LiveDelegationConsentAuthority } from '@/orchestration/liveDelegationConsent' +import { RemoteService } from '@/remote' +import { PluginService } from '@/plugin' +import { PluginRuntimeSupervisor } from '@/plugin/runtimeSupervisor' +import { createMinimalProcessEnvironment } from '@/mcp/processEnvironment' +import { + normalizeDeepChatSubagentSlots, + resolveDeepChatSubagentCapability +} from '@shared/lib/deepchatSubagents' +import { DEFAULT_DISABLED_AGENT_TOOLS } from '@shared/agentTools' +import { composeSubagentAuthority } from '@/session/subagentAuthority' +import type { ProviderCatalogPort } from '@/provider/ports' +import { createSkillExecutionAuthorityTapePort } from '@/tape/application/capabilityAdapters' +import { createInMemoryServerFactory } from '@/mcp/inMemoryServers/builder' +import { + AgentCliCommandAccess, + CliSkillService, + CliLauncherService, + CliMutationGuard, + CliRequestPolicy, + CliAuditLog, + CliServer, + CliOcrService, + ProgrammaticToolParentRegistry, + ProgrammaticToolDispatcher, + resolveBundledCliDirectory, + type ArtifactSpool, + type AgentCliTokenAuthority, + type CliApprovalPresentationPort +} from '@/cli' +import { CliRequestError } from '@/cli/errors' +import { ApprovalBroker } from '@/approval' +import { getShellEnvironment } from '@/agent/shared/process/shellEnvHelper' +import { + TOOL_SURFACE_PRODUCTION_ROLLOUT_POLICY_V1, + ToolSurfaceRolloutOwner +} from '@/agent/deepchat/runtime/toolSurfaceRollout' +import { AttachmentCapabilityRouter } from '@/ocr/attachmentCapabilityRouter' +import { OcrRuntimeService } from '@/ocr/ocrRuntimeService' +import type { OcrCacheKeyProvider } from '@/ocr/ocrCacheKeyProvider' +import { ToolchainResolutionError } from '@/toolchains/errors' +import type { ToolchainKind } from '@shared/types/toolchains' +import { dispatchDeepchatRoute, type RouteDispatcher } from '@/routes' +import { providerDbLoader } from '@/provider/providerDbLoader' +import { AcpRegistryMigrationService } from '@/agent/acp/catalog/acpRegistryMigrationService' +import { + isStartupWorkloadCancellation, + scheduleObservedStartupTask, + type StartupWorkloadCoordinator, + type StartupWorkloadTaskContext +} from '@/app/startupWorkloadCoordinator' +import { + runBuiltinMcpAllowlistCompatibilityMigration, + runDisabledAgentToolCapabilityCleanupMigration, + runMainlineNormalizationMigration +} from '@/app/startupMigrations/sessionDataMigrations' +import { BackgroundExecService } from '@/agent/shared/process/backgroundExecSessionManager' +import { createMemoryProviderBindings } from '@/app/memoryProviderBindings' +import { createSessionPermissionPort } from '@/app/sessionPermissionAdapter' +import type { initializeCoreProfile } from '@/app/profileStartup' +import type { createCoreData } from './data' +import type { createCoreProviders } from './providers' +import { createCoreExecution, createLivePort } from './execution' +import type { CredentialCryptoPort } from '@/config/secretStore' +import type { ProjectHost } from '@/project' +import type { RemoteServiceDeps, RemoteSessionWindowPort } from '@/remote/ports' + +type CoreProfile = Awaited> +type CoreExecution = ReturnType +type CoreExecutionOptions = Parameters[0] + +export async function createCoreRuntime(options: { + control: import('@shared/harness/control').HarnessControlPort + profile: Pick< + CoreProfile, + | 'settingsStore' + | 'secretStore' + | 'mcpSettings' + | 'privacySettings' + | 'pluginToolPolicies' + | 'database' + | 'settingsDatabase' + | 'providerDatabase' + > + data: ReturnType + providers: ReturnType + bootstrap: { + profileDirectory: string + homeDirectory: string + temporaryDirectory: string + appDataDirectory: string + documentsDirectory: string | null + projectHomeDirectory: string | null + appPath: string + appVersion: string + isPackaged: boolean + resourcesPath: string + workingDirectory: string + builtinSkillsDirectories: string[] + } + settings: Pick< + ReturnType, + | 'desktopSettings' + | 'appSettingsService' + | 'skillSettings' + | 'traceSettings' + | 'agentDefaults' + | 'commandShellService' + | 'ocrSettings' + > + spawnFileWatcherHost: SpawnFileWatcherHost + workspaceDesktop: WorkspaceDesktopPort + artifactSpool: ArtifactSpool + approvalPresentation: CliApprovalPresentationPort + ocrCacheKeyProvider: OcrCacheKeyProvider + resolveRouteDispatcher(): RouteDispatcher | undefined + startupWorkloadCoordinator: StartupWorkloadCoordinator + startupRunId: string + initializeFloatingButton(): Promise + databaseRequests: DatabaseRequestGate + syncActiveSessions: Set + importSnapshot: NonNullable[0]['importSnapshot']> + assertSyncTokenStorageAvailable(): void | Promise + semanticNotifications: SemanticNotificationPublisher + secureCrypto: CredentialCryptoPort + getProxyUrl(): Promise + whenProxyReady(): Promise + openExternal(url: string): Promise + openPath(filePath: string): Promise + selectDirectory: ProjectHost['selectDirectory'] + cacheImage: NonNullable + browser: AgentToolDependencies['browser'] + agentDesktop: AgentToolDependencies['desktop'] + pluginSettingsWindow: ConstructorParameters[0]['settingsWindow'] + remoteAuthWindows: RemoteServiceDeps['authWindows'] + remoteSessionWindow: RemoteSessionWindowPort + showNotification: NonNullable['showNotification'] + mcpPreview: ConstructorParameters[15] + mcpApps: { + desktop: NonNullable[16]>['desktop'] + publishConsent: Parameters[0] + } + spawnBackgroundExecHost: ConstructorParameters[0] + spawnCodeModeHost: ConstructorParameters[0]['spawnCodeModeHost'] + spawnSchedulerHost: ConstructorParameters[0]['spawnHost'] + agentInvocationAdmission: CoreExecutionOptions['agentInvocationAdmission'] + memoryVectorStores: Pick< + RutisMemoryVectorStores, + 'createVectorStore' | 'resetVectorStore' | 'markVectorStoreQuarantined' + > + memory: MemoryCapability + hooks: HooksCapability + execution: Pick< + CoreExecutionOptions, + | 'typedEventHub' + | 'resolveSessionRunId' + | 'onRendererDestroyed' + | 'destroySessionBrowser' + | 'observeDelegation' + | 'observationsEnabled' + > + onSessionCompleted: CoreExecutionOptions['harness']['onSessionCompleted'] + onSessionInactive(sessionId: string): void + runJournalObserver: CoreExecutionOptions['harness']['runJournalObserver'] + agentCliTokenAuthority: AgentCliTokenAuthority + sessionUiPort: CoreExecutionOptions['harness']['sessionUiPort'] + publishEvent(name: DeepchatEventName, payload: unknown): void + isStopping(): boolean + mcpReadinessChanged(ready: boolean): void + reportStartupFailure( + component: MainLogStartupComponent, + category: MainLogStartupComponentFailureCategory, + failure?: { message: string; error: unknown; startupRunId: string } + ): void +}) { + const dependencies = options.profile + const { + bootstrap, + databaseRequests, + startupWorkloadCoordinator, + syncActiveSessions, + semanticNotifications, + agentInvocationAdmission, + memoryVectorStores, + agentCliTokenAuthority, + artifactSpool, + sessionUiPort, + publishEvent: publishDeepchatEvent + } = options + const { settingsDatabase, providerDatabase, database: mainDatabase } = dependencies + const { + projectDatabase, + schedulerDatabase, + sessionData, + providerSettings, + promptSettings, + appSessionService, + agentRepository, + agentLifecycle, + sessionDeletionGate, + memoryDatabase, + sessionRuntimeEvents, + liveDelegationRepository, + legacyChatImportService, + sessionDataMigrationSQLite, + usageStatsService, + agentDatabase + } = options.data + const { + providerRuntime, + agentSettings, + acpRuntimeOwner, + acpSessionPersistence, + cliComputeService, + cliAudioTranscriptionService + } = options.providers + const { + desktopSettings, + appSettingsService, + skillSettings, + traceSettings, + agentDefaults, + commandShellService, + ocrSettings + } = options.settings + const cliAuditLog = new CliAuditLog({ + directory: path.join(bootstrap.profileDirectory, 'local-control') + }) + const commandPermissionHandler = new CommandPermissionService() + const resolveCliDirectory = () => + resolveBundledCliDirectory({ + appPath: bootstrap.appPath, + resourcesPath: bootstrap.resourcesPath, + isPackaged: bootstrap.isPackaged + }) + const cliLauncherService = new CliLauncherService({ + homeDirectory: bootstrap.homeDirectory, + userDataDirectory: bootstrap.profileDirectory, + environmentPath: process.env.PATH, + shell: process.env.SHELL, + localAppDataDirectory: process.env.LOCALAPPDATA, + resolveCliDirectory + }) + const agentCliCommandAccess = new AgentCliCommandAccess({ + tokenAuthority: agentCliTokenAuthority, + commandPermission: commandPermissionHandler, + resolveCliDirectory + }) + const commandPermissionService = commandPermissionHandler + const filePermissionService = new FilePermissionService() + const settingsPermissionService = new SettingsPermissionService() + const approvalBroker = new ApprovalBroker({ log: logger }) + const toolPermissionBroker = new ToolPermissionBroker({ approvalBroker }) + const cliMutationGuard = new CliMutationGuard(approvalBroker, options.approvalPresentation) + const cliRequestPolicy = new CliRequestPolicy({ + mutationGuard: cliMutationGuard, + audit: (record) => cliAuditLog.record(record) + }) + const liveDelegationConsent = new LiveDelegationConsentAuthority() + const programmaticToolParents = new ProgrammaticToolParentRegistry({ + tokenAuthority: agentCliTokenAuthority, + executionJournal: sessionData.programmaticExecutionJournal + }) + const toolSurfaceRollout = new ToolSurfaceRolloutOwner(TOOL_SURFACE_PRODUCTION_ROLLOUT_POLICY_V1) + let ocrRuntime: OcrRuntimeService | undefined + let mcpService: McpService + const toolchainHomeDir = bootstrap.homeDirectory + const toolchainService = ToolchainService.initialize({ + appPath: bootstrap.appPath, + userDataDir: bootstrap.profileDirectory, + env: mergeDetectionEnv(process.env, toolchainHomeDir, process.platform), + allowProbe: () => !dependencies.privacySettings.isEnabled(), + onProgress: (progress) => + publishDeepchatEvent('toolchains.progress', { ...progress, version: Date.now() }), + onMissing: (missing) => + publishDeepchatEvent('toolchains.missing', { missing, version: Date.now() }), + onStateChanged: (kind?: ToolchainKind) => { + publishDeepchatEvent('toolchains.changed', { version: Date.now() }) + ocrRuntime?.refreshAvailability(kind) + const state = ToolchainService.getInstance().getState() + if (state.node.source === 'unconfigured' && state.uv.source === 'unconfigured') { + return + } + void mcpService?.retryUnstartedEnabledServers().catch((error) => { + logger.warn('[ToolchainService] Failed to retry MCP servers after PATH refresh', error) + }) + } + }) + const shellEnvironmentRefresh = + process.platform !== 'win32' + ? getShellEnvironment() + .then((shellEnv) => { + if (options.isStopping()) return + toolchainService.updateDetectionEnv( + mergeDetectionEnv(shellEnv, toolchainHomeDir, process.platform) + ) + }) + .catch((error) => { + logger.warn('[ToolchainService] Failed to refresh login-shell PATH', error) + }) + : Promise.resolve() + const ocrRuntimeService = new OcrRuntimeService({ + cacheKeyProvider: options.ocrCacheKeyProvider, + appPath: bootstrap.appPath, + isPackaged: bootstrap.isPackaged, + nodeRuntimePath: null, + resolveNode: () => { + const resolved = toolchainService.resolve('node', { purpose: 'ocr' }) + if (!resolved.version) { + throw new ToolchainResolutionError( + 'node', + 'version_mismatch', + 'OCR Node version is unavailable' + ) + } + return { executable: resolved.node, version: resolved.version } + }, + tempBaseDir: bootstrap.temporaryDirectory, + userDataDir: bootstrap.profileDirectory + }) + ocrRuntime = ocrRuntimeService + const cliOcrService = new CliOcrService({ + appVersion: bootstrap.appVersion, + ocrRuntime: ocrRuntimeService, + artifactSpool, + log: logger + }) + const attachmentRouter = new AttachmentCapabilityRouter({ + extraction: ocrRuntimeService, + getAutomaticOcrEnabled: () => ocrSettings.getAutomaticExtractionEnabled(), + getBackendPreference: () => ocrSettings.getBackend(), + getMaxFileSize: () => dependencies.settingsStore.get('maxFileSize') ?? 30 * 1024 * 1024, + onDiagnostic: (event) => { + if (traceSettings.isEnabled()) { + logger.info('[OCR] attachment representation resolved', event) + } + } + }) + const acpAsLlmProviderPermission = providerRuntime + const acpAsLlmProviderSessionControl = providerRuntime + let coreExecution: CoreExecution + let deepChatAgentHarness: CoreExecution['deepChatAgentHarness'] + let sessionQuery: CoreExecution['sessionQuery'] + let sessionAssignment: CoreExecution['sessionAssignment'] + let sessionTurn: CoreExecution['sessionTurn'] + let sessionLifecycle: CoreExecution['sessionLifecycle'] + let acpAgentRuntime: CoreExecution['acpAgentRuntime'] + const liveDelegationService = createLivePort(() => coreExecution.liveDelegationService) + let pluginInitializationPromise: Promise | null = null + let skillInitializationPromise: Promise | null = null + let skillSyncScanPromise: Promise | null = null + const fileWatcherService = new FileWatcherService( + new WatcherPool({ + content: new WatcherHostClient('content', options.spawnFileWatcherHost), + git: new WatcherHostClient('git', options.spawnFileWatcherHost) + }) + ) + const projectService = new ProjectService( + projectDatabase, + sessionData.database, + { + tempDirectory: bootstrap.temporaryDirectory, + userDataDirectory: bootstrap.profileDirectory, + appDataDirectory: bootstrap.appDataDirectory, + documentsDirectory: bootstrap.documentsDirectory, + homeDirectory: bootstrap.projectHomeDirectory, + selectDirectory: options.selectDirectory, + openPath: (directory) => options.openPath(directory) + }, + dependencies.settingsStore, + (projectPath, version) => + publishDeepchatEvent('config.defaultProjectPath.changed', { + path: projectPath, + version + }), + (action, environmentPath, version) => + publishDeepchatEvent(projectEnvironmentsChangedEvent.name, { + action, + path: environmentPath, + version + }) + ) + const nowledgeMemConnections = new NowledgeMemConnections( + dependencies.settingsStore, + dependencies.secretStore, + dependencies.mcpSettings + ) + dependencies.mcpSettings.setPluginBindingResolver(NOWLEDGE_PLUGIN_ID, (config) => + nowledgeMemConnections.getMcpBindings(config) + ) + const exporter = new ConversationExporterService({ + nowledgeMemConnections, + sqlitePresenter: sessionData.database, + settings: dependencies.settingsStore + }) + const fileService = new FileService(dependencies.settingsStore, bootstrap.profileDirectory) + const workspaceService = new WorkspaceService( + fileService, + fileWatcherService, + { + publishInvalidated: (event) => publishDeepchatEvent('workspace.invalidated', event), + publishWatchStatusChanged: (event) => + publishDeepchatEvent('workspace.watch.status.changed', event) + }, + options.workspaceDesktop + ) + const syncSettings = new SyncSettings( + dependencies.settingsStore, + dependencies.secretStore, + publishDeepchatEvent, + bootstrap.homeDirectory + ) + const hookSettings = new HookSettings(dependencies.settingsStore) + const knowledgeSettings = new KnowledgeSettings( + dependencies.settingsStore, + dependencies.mcpSettings + ) + const syncService = new SyncService( + syncSettings, + mainDatabase, + settingsDatabase, + providerDatabase, + publishDeepchatEvent, + { + profileDirectory: bootstrap.profileDirectory, + temporaryDirectory: bootstrap.temporaryDirectory, + openPath: (directory) => options.openPath(directory) + } + ) + const syncTokenStorage = { + protectToken: async (token: string) => { + await options.assertSyncTokenStorageAvailable() + return await dependencies.secretStore.wrap(token) + }, + revealToken: (wrapped: string) => dependencies.secretStore.unwrap(wrapped) + } + const syncPeerService = new SyncPeerService({ + directory: path.join(bootstrap.profileDirectory, 'sync-peer'), + replicaId: () => syncReplica!.replicaId, + ...syncTokenStorage, + isLocalDatabaseEncrypted: () => Boolean(mainDatabase.getDatabasePassword()), + importSnapshot: (filePath, mode) => { + if (databaseRequests.state !== 'running') throw new Error('sync.tunnel.error.busy') + return options.importSnapshot(filePath, mode) + } + }) + const syncAppliedSessions = new Set() + const hasPendingSyncSession = (id: string): boolean => + Boolean( + mainDatabase + .getDatabase() + .prepare( + "SELECT 1 FROM deepchat_pending_inputs WHERE session_id=? AND state != 'consumed' LIMIT 1" + ) + .get(id) + ) + const syncReplica = new SyncReplicaStore({ + database: () => mainDatabase.getDatabase(), + directory: path.join(bootstrap.profileDirectory, 'sync-replica'), + canApply: (kind, id) => + databaseRequests.state === 'running' && + (kind === 'session' + ? !syncActiveSessions.has(id) && + !hasPendingSyncSession(id) && + (syncAppliedSessions.has(id) || !sessionDeletionGate.hasActiveOperations(id)) + : syncActiveSessions.size === 0 && + !sessionDeletionGate.hasActiveOperationsOutside(syncAppliedSessions)), + guard: async (units, operation) => { + const ids = [ + ...new Set(units.filter((unit) => unit.kind === 'session').map((unit) => unit.id)) + ].sort() + const apply = async (index: number): Promise> => { + if (index === ids.length) return operation() + const id = ids[index] + if ( + syncActiveSessions.has(id) || + hasPendingSyncSession(id) || + sessionDeletionGate.hasActiveOperations(id) + ) + return false + return sessionDeletionGate.runWithSessionDeletion(id, async () => { + if (syncActiveSessions.has(id)) return false + await deepChatAgentHarness.cleanupSession(id) + await acpAgentRuntime.cleanupSession(toAppSessionId(id)) + syncAppliedSessions.add(id) + try { + return await apply(index + 1) + } finally { + syncAppliedSessions.delete(id) + } + }) + } + return apply(0) + }, + applied: (kind, id, deleted) => { + if (kind === 'session') + publishDeepchatEvent('sessions.updated', { + sessionIds: [id], + reason: deleted ? 'deleted' : 'synced' + }) + if (kind === 'provider') { + providerSettings.invalidateSyncCaches() + providerRuntime.handleProviderAtomicUpdate({ + operation: 'remove', + providerId: id, + requiresRebuild: true + }) + const provider = providerSettings.getProviderById(id) + if (provider) + providerRuntime.handleProviderAtomicUpdate({ + operation: 'add', + providerId: id, + provider, + requiresRebuild: false + }) + publishDeepchatEvent('providers.changed', { + reason: 'providers', + providerIds: [id], + version: Date.now() + }) + publishDeepchatEvent('models.changed', { + reason: 'runtime-refresh', + providerId: id, + version: Date.now() + }) + } + if (kind === 'setting' && id === 'customPrompts') promptSettings.invalidateSyncCache() + if (kind === 'setting') { + const key = ( + [ + 'autoCompactionEnabled', + 'autoCompactionTriggerThreshold', + 'autoCompactionRetainRecentPairs', + 'copyWithCotEnabled' + ] as const + ).find((value) => value === id) + if (key) { + const values = { + autoCompactionEnabled: agentDefaults.getAutoCompactionEnabled(), + autoCompactionTriggerThreshold: agentDefaults.getAutoCompactionTriggerThreshold(), + autoCompactionRetainRecentPairs: agentDefaults.getAutoCompactionRetainRecentPairs(), + copyWithCotEnabled: desktopSettings.getCopyWithCotEnabled() + } + publishDeepchatEvent('settings.changed', { + changedKeys: [key], + version: Date.now(), + values: { [key]: values[key] } + }) + } + } + const refresh = async () => { + if (kind === 'agent') + await emitAgentCatalogChanged(agentSettings, publishDeepchatEvent, [id]) + if (kind === 'mcp') { + mcpService.handleConfigChanged() + publishDeepchatEvent('mcp.config.changed', { + mcpServers: await dependencies.mcpSettings.getMcpServers(), + mcpEnabled: await dependencies.mcpSettings.getMcpEnabled(), + version: Date.now() + }) + deepChatAgentHarness.refreshToolRegistry() + } + if (kind === 'setting' && id === 'customPrompts') + publishDeepchatEvent('config.customPrompts.changed', { + prompts: await promptSettings.getCustomPrompts(), + version: Date.now() + }) + if (kind === 'setting' && (id === 'systemPrompts' || id === 'default_system_prompt')) + await promptSettings.publishSystemPromptState() + } + void refresh().catch((error) => + logger.warn('[Sync] Could not refresh imported settings', error) + ) + publishDeepchatEvent('sync.device.changed', { version: Date.now() }) + } + }) + const replicaEndpoint = new SyncReplicaEndpoint({ + store: syncReplica, + directory: path.join(bootstrap.profileDirectory, 'sync-host'), + hostId: () => syncHostService.getHostId(), + available: () => + databaseRequests.state === 'running' && + !syncService.isBackupInProgress() && + !mainDatabase.getDatabasePassword() + }) + const automaticSync = new AutomaticSync({ + store: syncReplica, + directory: path.join(bootstrap.profileDirectory, 'sync-peer'), + connection: () => syncPeerService.connection(), + available: () => + databaseRequests.state === 'running' && + !syncService.isBackupInProgress() && + !mainDatabase.getDatabasePassword(), + isEncrypted: () => Boolean(mainDatabase.getDatabasePassword()), + changed: () => publishDeepchatEvent('sync.device.changed', { version: Date.now() }) + }) + syncPeerService.automatic = automaticSync + const syncHostService = new SyncHostService({ + changed: () => publishDeepchatEvent('sync.device.changed', { version: Date.now() }), + replica: replicaEndpoint, + resolveCloudflared: () => toolchainService.resolve('cloudflared').cloudflared, + ...syncTokenStorage, + + createBackup: () => { + if (databaseRequests.state !== 'running') throw new Error('sync.tunnel.error.busy') + return syncService.createHostBackup() + }, + getFolderPath: () => syncSettings.getFolderPath(), + getUserDataPath: () => bootstrap.profileDirectory, + getAppVersion: () => bootstrap.appVersion, + logger + }) + // Define the storage root for built-in knowledge databases. + const dbDir = path.join(bootstrap.profileDirectory, 'app_db') + const knowledgeService = new KnowledgeService({ + config: knowledgeSettings, + storageRoot: dbDir, + extensionDirectory: path.join( + bootstrap.appPath.replace('app.asar', 'app.asar.unpacked'), + 'runtime', + 'duckdb', + 'extensions' + ), + files: fileService, + embeddings: providerRuntime, + events: { + publishFileUpdated: (file) => + publishDeepchatEvent('knowledge.file.updated', { ...file, version: Date.now() }), + publishFileProgress: (fileId, progress) => + publishDeepchatEvent('knowledge.file.progress', { + fileId, + ...progress, + version: Date.now() + }) + } + }) + const pluginRuntimeSupervisor = new PluginRuntimeSupervisor() + const mcpApps = new McpAppSandboxRegistry(options.execution.onRendererDestroyed) + mcpApps.setConsentPublisher(options.mcpApps.publishConsent) + const mcpAppSandbox: McpAppSandboxPort = { + getProtocolPolicy: async (instanceId) => + await databaseRequests.runRequest('mcp.apps.protocolPolicy', () => + mcpApps.getProtocolPolicy(instanceId) + ), + requestBrowserPermission: async (...args) => + await databaseRequests.runRequest('mcp.apps.browserPermission', () => + mcpApps.requestBrowserPermission(...args) + ) + } + mcpService = new McpService( + providerSettings, + agentSettings, + promptSettings, + dependencies.mcpSettings, + dependencies.privacySettings, + createInMemoryServerFactory({ + sqlitePresenter: sessionData.database, + sessions: appSessionService, + transcript: sessionData.transcript, + settings: sessionData.settings, + locale: desktopSettings, + promptSettings, + knowledgeSettings, + knowledgeService: knowledgeService + }), + providerRuntime, + () => deepChatAgentHarness.refreshToolRegistry(), + semanticNotifications, + publishDeepchatEvent, + dependencies.pluginToolPolicies, + { + homeDirectory: bootstrap.homeDirectory, + appVersion: bootstrap.appVersion, + toolchains: toolchainService, + getProxyUrl: options.getProxyUrl + }, + { + store: new McpOAuthCredentialStore({ + filePath: path.join(bootstrap.profileDirectory, 'mcp-oauth', 'credentials.json'), + crypto: options.secureCrypto + }), + openExternal: (url) => options.openExternal(url) + }, + options.cacheImage, + pluginRuntimeSupervisor, + options.mcpPreview, + { + registry: mcpApps, + desktop: options.mcpApps.desktop, + permissionBroker: toolPermissionBroker, + getPermissionMode: async (conversationId) => + sessionData.settings.get(conversationId)?.permission_mode ?? 'default', + validateSource: (input) => { + const message = sessionData.database.deepchatMessagesTable.get(input.messageId) + return Boolean( + message?.session_id === input.conversationId && + sessionData.database.deepchatAssistantBlocksTable.matchesMcpAppSource( + input.messageId, + input.blockId, + input.descriptor, + input.toolInput + ) + ) + }, + persistModelContext: (messageId, blockId, descriptor, toolInput, context) => + sessionData.database.deepchatAssistantBlocksTable.updateMcpAppModelContext( + messageId, + blockId, + descriptor, + toolInput, + context + ) + }, + () => options.reportStartupFailure('mcp', 'unknown') + ) + await noteNodeDemandFromMcp(dependencies.mcpSettings, toolchainService).catch((error) => { + logger.warn('[ToolchainService] Failed to note MCP Node demand', error) + }) + const skillSessionStatePort: SkillSessionStatePort = { + hasNewSession: async (conversationId) => Boolean(await sessionQuery.getSession(conversationId)), + getPersistedNewSessionSkills: (conversationId) => + sessionData.database.newSessionsTable.getActiveSkills(conversationId), + setPersistedNewSessionSkills: (conversationId, skills) => { + sessionData.database.newSessionsTable.updateActiveSkills(conversationId, skills) + projectDatabase.newEnvironmentsTable.syncForSession(conversationId) + projectService.notifyEnvironmentProjectionChanged() + }, + repairImportedLegacySessionSkills: async (conversationId) => { + return await legacyChatImportService.repairImportedLegacySessionSkills(conversationId) + } + } + const skillService = new SkillService( + skillSettings, + skillSessionStatePort, + fileWatcherService, + publishDeepchatEvent, + { + homeDirectory: bootstrap.homeDirectory, + tempDirectory: bootstrap.temporaryDirectory, + userDataDirectory: bootstrap.profileDirectory, + builtinSkillsDirectories: bootstrap.builtinSkillsDirectories, + openSkillsDirectory: async (directory) => { + await options.openPath(directory) + } + }, + { + isDeepChatAgent: async (agentId) => + (await agentSettings.getAgent(agentId))?.type === 'deepchat', + listDeepChatAgents: async () => + (await agentSettings.listAgents()) + .filter((agent) => agent.type === 'deepchat') + .map((agent) => ({ + id: agent.id, + enabledSkillNames: agent.config?.enabledSkillNames, + protected: agent.protected + })), + getSessionAgentId: async (sessionId) => + (await sessionQuery.getSession(sessionId))?.agentId ?? null, + getSessionProjectDir: async (sessionId) => + (await sessionQuery.getSession(sessionId))?.projectDir ?? null, + listSessions: async () => + (await sessionQuery.listSessions({ includeSubagents: true })).map((session) => ({ + id: session.id, + agentId: session.agentId + })) + } + ) + const cliSkillService = new CliSkillService({ + skills: skillService, + agentExists: async (agentId) => (await agentSettings.getAgent(agentId))?.type === 'deepchat', + recordSettingsActivity: (input) => { + void settingsDatabase.recordSettingsActivity(input).catch((error) => { + logger.warn('[SettingsActivity] Failed to record CLI Skill activity:', error) + }) + }, + log: logger + }) + + const resolveConversationExecutionAuthorityNow = (conversationId: string) => { + const session = appSessionService.get(conversationId) + if (!session) return null + const resolveAgentConfig = (agentId: string) => { + const config = agentRepository.resolveDeepChatAgentConfig(agentId) + return { + ...config, + disabledAgentTools: Array.isArray(config.disabledAgentTools) + ? config.disabledAgentTools + : [...DEFAULT_DISABLED_AGENT_TOOLS] + } + } + const agentType = agentRepository.getAgentType(session.agentId) + const agentConfig = resolveAgentConfig(session.agentId) + const persistedDisabledAgentTools = appSessionService.getDisabledAgentTools(session.id) + let authority = composeSubagentAuthority({ + disabledAgentTools: persistedDisabledAgentTools, + enabledMcpServerIds: agentConfig.enabledMcpServerIds + }) + if (session.sessionKind === 'subagent') { + const parentSessionId = session.parentSessionId?.trim() + const parent = parentSessionId ? appSessionService.get(parentSessionId) : null + if (!parent || parent.sessionKind !== 'regular') { + throw new Error(`Subagent Session ${session.id} has no resolvable parent tool policy.`) + } + authority = composeSubagentAuthority( + { disabledAgentTools: persistedDisabledAgentTools }, + { disabledAgentTools: appSessionService.getDisabledAgentTools(parent.id) }, + resolveAgentConfig(parent.agentId), + agentConfig + ) + } + return { + sessionId: session.id, + agentId: session.agentId, + projectDir: session.projectDir ?? null, + sessionKind: session.sessionKind, + disabledAgentTools: authority.disabledAgentTools, + enabledMcpServerIds: authority.enabledMcpServerIds, + subagentCapability: resolveDeepChatSubagentCapability({ + agentType, + sessionKind: session.sessionKind, + agentPolicyEnabled: agentConfig.subagentEnabled === true, + slots: normalizeDeepChatSubagentSlots(agentConfig.subagents) + }) + } + } + const backgroundExec = new BackgroundExecService(options.spawnBackgroundExecHost) + const agentToolDependencies: AgentToolDependencies = { + backgroundExec, + readSession: (callerSessionId, input) => + sessionData.sessionReferences.read(callerSessionId, input), + agentInvocationAdmission, + sessions: { + resolveConversationWorkdir: async (conversationId) => { + try { + const session = await sessionQuery.getSession(conversationId) + const normalized = session?.projectDir?.trim() + if (normalized) { + return normalized + } + } catch (error) { + console.warn('[Main] Failed to resolve new session workdir:', { + conversationId, + error + }) + } + + return null + }, + resolveConversationExecutionAuthorityNow, + resolveConversationExecutionAuthority: async (conversationId) => + resolveConversationExecutionAuthorityNow(conversationId), + resolveConversationSessionInfo: async (conversationId) => { + const session = await sessionQuery.getSession(conversationId) + if (!session) { + return null + } + + const agent = await agentSettings.getAgent(session.agentId) + const agentType = await agentSettings.getAgentType(session.agentId) + const permissionMode = await sessionAssignment.getPermissionMode(session.id) + const generationSettings = await sessionAssignment.getSessionGenerationSettings(session.id) + const disabledAgentTools = await sessionAssignment.getSessionDisabledAgentTools(session.id) + const activeSkills = await skillService.getActiveSkills(session.id) + const agentConfig = await agentSettings.resolveDeepChatAgentConfig(session.agentId) + const availableSubagentSlots = normalizeDeepChatSubagentSlots(agentConfig.subagents) + const subagentCapability = resolveDeepChatSubagentCapability({ + agentType, + sessionKind: session.sessionKind, + agentPolicyEnabled: agentConfig.subagentEnabled === true, + slots: availableSubagentSlots + }) + + return { + sessionId: session.id, + agentId: session.agentId, + agentName: agent?.name?.trim() || session.agentId, + agentType, + providerId: session.providerId, + modelId: session.modelId, + projectDir: session.projectDir ?? null, + permissionMode, + orchestrationPolicy: session.orchestrationPolicy, + toolModeOverride: session.toolModeOverride ?? null, + generationSettings, + disabledAgentTools, + activeSkills, + sessionKind: session.sessionKind, + parentSessionId: session.parentSessionId ?? null, + subagentMeta: session.subagentMeta ?? null, + subagentCapability, + status: session.status + } + } + }, + tape: { + searchTape: async (conversationId, query, options) => { + return await sessionQuery.searchTape(conversationId, query, options) + }, + getTapeContext: async (conversationId, entryIds, options) => { + return await sessionQuery.getTapeContext(conversationId, entryIds, options) + } + }, + memory: { + isMemoryEnabled: (agentId) => memoryService.isEnabled(agentId), + rememberMemory: async (agentId, input, sourceSession, model, beforeMutation) => + memoryService.rememberMemory( + { + kind: input.kind, + category: input.category, + content: input.content, + importance: input.importance + }, + { agentId, sourceSession }, + model, + beforeMutation + ), + recallMemory: async (agentId, query, scopeContext) => { + const items = await memoryService.recall(agentId, query, undefined, scopeContext) + return items.map((item) => ({ + id: item.id, + kind: item.kind, + content: item.content + })) + }, + forgetMemory: async (agentId, memoryId, beforeMutation) => + await memoryService.forgetMemory(agentId, memoryId, beforeMutation) + }, + cronJobs: { + listCronJobs: async () => await cronJobs.list(), + upsertCronJob: async (input, beforeMutation) => + (await cronJobs.upsert(input, beforeMutation)).job, + deleteCronJob: async (id, beforeMutation) => { + await cronJobs.delete(id, beforeMutation) + }, + toggleCronJob: async (id, enabled, beforeMutation) => + (await cronJobs.toggle(id, enabled, beforeMutation)).job, + runCronJobNow: async (id, beforeMutation) => (await cronJobs.runNow(id, beforeMutation)).run, + listCronJobRuns: async (jobId, limit) => cronJobs.listRuns(jobId, limit), + previewCronSchedule: async (input) => cronJobs.previewSchedule(input) + }, + subagents: { + createSubagentSession: async (input) => { + const created = await sessionLifecycle.createSubagentSession(input) + return await agentToolDependencies.sessions.resolveConversationSessionInfo(created.id) + }, + linkSubagentTape: async (input) => await sessionAssignment.linkSubagentTape(input), + sendConversationMessage: async (conversationId, content) => { + await sessionTurn.sendMessage(conversationId, content) + }, + cancelConversation: async (conversationId) => { + await sessionTurn.cancelGeneration(conversationId) + }, + subscribeSessionRuntimeUpdates: (listener) => sessionRuntimeEvents.subscribe(listener) + }, + liveDelegation: liveDelegationService, + skills: skillService, + skillExecutionAuthority: new SkillExecutionAuthorityResolver({ + tape: createSkillExecutionAuthorityTapePort(sessionData.tapeStore), + environments: skillService + }), + browser: options.browser, + files: { + getMimeType: (filePath) => fileService.getMimeType(filePath), + prepareFileCompletely: (absPath, typeInfo, contentType) => + fileService.prepareFileCompletely(absPath, typeInfo, contentType) + }, + provider: { + executeWithRateLimit: (providerId, options) => + providerRuntime.executeWithRateLimit(providerId, options), + generateCompletionStandalone: ( + providerId, + messages, + modelId, + temperature, + maxTokens, + options + ) => + providerRuntime.generateCompletionStandalone( + providerId, + messages, + modelId, + temperature, + maxTokens, + options + ), + generateImageStandalone: (providerId, prompt, modelId, imageOptions, options) => + providerRuntime.generateImageStandalone(providerId, prompt, modelId, imageOptions, options) + }, + cacheImage: options.cacheImage, + desktop: options.agentDesktop, + permissions: { + getApprovedFilePaths: (conversationId, requiredPermission, provisionalLeaseId) => + filePermissionService.getApprovedPaths( + conversationId, + requiredPermission, + provisionalLeaseId + ), + consumeSettingsApproval: (conversationId, toolName, provisionalLeaseId) => + settingsPermissionService.consumeApproval(conversationId, toolName, provisionalLeaseId) + } + } + + // Initialize the merged MCP and built-in Tool service. + const toolService = new ToolService({ + pluginToolPolicies: dependencies.pluginToolPolicies, + spawnCodeModeHost: options.spawnCodeModeHost, + mcpService: mcpService, + providerSettings: providerSettings, + settings: dependencies.settingsStore, + agentSettings, + skillSettings, + desktopSettings: appSettingsService, + commandPermissionHandler, + commandEnvironment: agentCliCommandAccess, + permissionBroker: toolPermissionBroker, + liveDelegationConsent, + agentTools: agentToolDependencies, + effectObserver: { + beforeToolAuthorization: async (observation, signal) => { + const permissionMode = await liveDelegationService.beforeToolAuthorization( + observation, + signal + ) + return permissionMode ? { permissionMode } : null + }, + beforeToolExecution: async (observation, signal) => { + await liveDelegationService.beforeToolExecution(observation, signal) + } + } + }) + + // Plugin activation is a shared startup barrier for Skill migration and MCP startup. + const pluginService = new PluginService({ + workingDirectory: bootstrap.workingDirectory, + profileDirectory: bootstrap.profileDirectory, + homeDirectory: bootstrap.homeDirectory, + appPath: bootstrap.appPath, + appVersion: bootstrap.appVersion, + isPackaged: bootstrap.isPackaged, + resourcesPath: bootstrap.resourcesPath, + openExternal: (url) => options.openExternal(url), + openPath: (filePath) => options.openPath(filePath), + hookEnvironment: () => + toolchainService.prependResolvedToEnv( + createMinimalProcessEnvironment(process.env, process.platform) + ), + toolPolicies: dependencies.pluginToolPolicies, + nowledgeMem: nowledgeMemConnections, + exportNowledgeSession: (input) => + coreExecution.agentSessionExportService.submitToNowledgeMem(input), + contextTape: sessionData.tapeStore, + mcpSettings: dependencies.mcpSettings, + mcpService: mcpService, + skillService: skillService, + settingsWindow: options.pluginSettingsWindow, + runtimeSupervisor: pluginRuntimeSupervisor + }) + + // Initialize Skill Sync service + const skillSyncService = new SkillSyncService(skillService, skillSettings, publishDeepchatEvent) + + const hookService = options.hooks.open( + hookSettings, + { getSession: (sessionId) => sessionQuery.getSession(sessionId) }, + bootstrap.appVersion + ) + const providerCatalogPort: ProviderCatalogPort = { + getProviderModels: (providerId) => providerSettings.getProviderModels(providerId), + getCustomModels: (providerId) => providerSettings.getCustomModels(providerId), + getAgentType: async (agentId) => await agentSettings.getAgentType(agentId) + } + const sessionPermissionPort = createSessionPermissionPort({ + agentCliTokenAuthority, + commandPermissionService, + filePermissionService, + settingsPermissionService, + toolPermissionBroker + }) + const memoryService = options.memory.open({ + repository: createLivePort(() => memoryDatabase.agentMemoryTable), + directiveRepository: createLivePort(() => memoryDatabase.agentMemoryDirectiveTable), + auditRepository: createLivePort(() => memoryDatabase.agentMemoryAuditTable), + resolveAgentConfig: (agentId) => agentRepository.resolveDeepChatAgentConfig(agentId), + resolveAgentDefaultModel: (agentId) => { + const config = agentRepository.resolveDeepChatAgentConfig(agentId) + const model = config.assistantModel ?? config.defaultModelPreset + return model?.providerId && model?.modelId + ? { providerId: model.providerId, modelId: model.modelId } + : null + }, + // Management memory APIs only read/write real DeepChat agents. + isManagedAgent: (agentId) => agentRepository.getDeepChatAgentConfig(agentId) !== null, + listManagedAgentConfigs: () => agentRepository.listResolvedDeepChatAgentConfigs(), + listManagedMemoryAgentIds: () => + agentRepository + .listAgents({ agentType: 'deepchat', enabled: true }) + .map((agent) => agent.id) + .filter( + (agentId) => agentRepository.resolveDeepChatAgentConfig(agentId).memoryEnabled === true + ), + ...createMemoryProviderBindings(providerRuntime), + createVectorStore: (agentId, embedding, dimensions) => + memoryVectorStores.createVectorStore(agentId, embedding, dimensions), + resetVectorStore: (agentId) => memoryVectorStores.resetVectorStore(agentId), + markVectorStoreQuarantined: (agentId) => memoryVectorStores.markVectorStoreQuarantined(agentId), + onMemoryChanged: (agentId, reason, context) => + publishDeepchatEvent('memory.updated', { + agentId, + reason, + version: Date.now(), + ...(typeof context?.memoryId === 'string' ? { memoryId: context.memoryId } : {}), + ...(typeof context?.directiveId === 'string' ? { directiveId: context.directiveId } : {}), + ...(typeof context?.sessionId === 'string' ? { sessionId: context.sessionId } : {}), + ...(context?.createdIds?.length ? { createdIds: context.createdIds } : {}) + }) + }) + + coreExecution = createCoreExecution({ + harness: { + control: options.control, + providerRuntime, + providerSettings, + agentSettings, + database: sessionData.database, + sessionData, + toolService, + hookObserver: hookService, + pluginContext: pluginService.contextHooks, + onSessionCompleted: options.onSessionCompleted, + publishEvent: publishDeepchatEvent, + publishSessionUpdate: (update) => { + sessionRuntimeEvents.publish(update) + if (update.kind === 'status') { + if (update.status === 'generating') syncActiveSessions.add(update.sessionId) + else { + syncActiveSessions.delete(update.sessionId) + syncReplica?.wake() + } + } + if (update.kind === 'status' && (update.status === 'idle' || update.status === 'error')) { + options.onSessionInactive(update.sessionId) + } + }, + providerCatalogPort, + sessionPermissionPort, + acpAsLlmProviderPermission: acpAsLlmProviderPermission, + sessionUiPort, + memoryPort: memoryService, + getMemoryIngestionProjection: () => memoryDatabase.ingestionProjectionTable, + cacheImage: options.cacheImage, + runJournalObserver: options.runJournalObserver, + skillService: skillService, + skillSettings, + traceSettings, + commandShell: commandShellService, + promptSettings, + attachmentRouter, + toolSurfaceRunMode: toolSurfaceRollout, + agentCliTokenAuthority, + programmaticToolParents + }, + agentRepository, + appSessionService, + acpRuntimeOwner, + acpSessionPersistence, + acpAsLlmProviderSessionControl, + agentLifecycle, + sessionDeletionGate, + projectDatabase, + projectService, + agentToolDependencies, + agentInvocationAdmission, + liveDelegationRepository, + liveDelegationConsent, + nowledgeMemConnections, + databaseRequests, + ...options.execution + }) + await coreExecution.deepChatAgentHarness.ready + deepChatAgentHarness = coreExecution.deepChatAgentHarness + options.memory.bindIngestion(coreExecution.memoryIngestionObserver) + sessionQuery = coreExecution.sessionQuery + sessionAssignment = coreExecution.sessionAssignment + sessionTurn = coreExecution.sessionTurn + sessionLifecycle = coreExecution.sessionLifecycle + acpAgentRuntime = coreExecution.acpAgentRuntime + const openDesktopSession = async (sessionId: string): Promise => { + if (options.isStopping()) return false + const target = await options.remoteSessionWindow.select() + if (!target || options.isStopping()) return false + const binding = coreExecution.desktopSessionBinding + await binding.activate(target.webContentsId, sessionId) + if (options.isStopping() || binding.getActiveId(target.webContentsId) !== sessionId) + return false + return await options.remoteSessionWindow.present(target) + } + const remoteService = new RemoteService({ + profileDirectory: bootstrap.profileDirectory, + authWindows: options.remoteAuthWindows, + settings: dependencies.settingsStore, + catalog: { + getAgentType: (agentId) => agentSettings.getAgentType(agentId), + listAgents: async () => + (await agentSettings.listAgents()) + .filter((agent) => agent.enabled !== false) + .map((agent) => ({ + agentId: agent.id, + agentName: agent.name || agent.id, + agentType: agent.type, + source: agent.source + })), + listModelProviders: async () => { + const enabledProviders = providerSettings.getEnabledProviders() + const enabledModelGroups = await providerSettings.getAllEnabledModels() + const providerNameById = new Map( + enabledProviders.map((provider) => [provider.id, provider.name]) + ) + return enabledModelGroups + .filter((group) => providerNameById.has(group.providerId) && group.models.length > 0) + .map((group) => ({ + providerId: group.providerId, + providerName: providerNameById.get(group.providerId) ?? group.providerId, + models: group.models.map((model) => ({ + modelId: model.id, + modelName: model.name || model.id + })) + })) + } + }, + workspace: { + getDefaultProjectPath: async () => projectService.getDefaultProjectPath(), + prepareFile: (filePath, type) => fileService.prepareFile(filePath, type) + }, + lifecycle: sessionLifecycle, + turn: { + sendMessage: (...args) => sessionTurn.sendMessage(...args), + respondToolInteraction: (...args) => sessionTurn.respondToolInteraction(...args), + cancelGeneration: (sessionId) => sessionTurn.cancelGeneration(sessionId) + }, + assignment: sessionAssignment, + projection: sessionQuery, + desktop: { + openSession: openDesktopSession + }, + notifications: { + showNotification: options.showNotification + } + }) + const cronJobs = new SchedulerService({ + createSchedulerManager: (deps) => + new SchedulerProcessManager({ ...deps, spawnHost: options.spawnSchedulerHost }), + database: schedulerDatabase, + agentSettings, + sessionEvents: sessionRuntimeEvents, + runSessionStarter: createCronJobRunSessionStarter({ + lifecycle: sessionLifecycle, + turn: sessionTurn, + agentCatalog: agentSettings + }), + remoteDeliveryPort: remoteService + }) + async function initializeSkills(): Promise { + if (!skillSettings.isEnabled()) { + logger.info('SkillService disabled by config') + return + } + await ensureSkillServicesInitialized() + } + + async function ensureSkillServicesInitialized(): Promise { + if (options.isStopping()) { + throw new Error('Cannot initialize Skill services while app is stopping') + } + if (!skillInitializationPromise) { + const initialization = (async () => { + await initializePlugins() + await skillService.initialize() + logger.info('SkillService initialized') + await skillSyncService.initialize() + })() + skillInitializationPromise = initialization + void initialization.catch(() => { + if (skillInitializationPromise === initialization) { + skillInitializationPromise = null + } + }) + } + await skillInitializationPromise + } + + async function initializePlugins(): Promise { + if (!pluginInitializationPromise) { + const initialization = pluginService.initialize() + pluginInitializationPromise = initialization + void initialization.catch(() => { + if (pluginInitializationPromise === initialization) { + pluginInitializationPromise = null + } + }) + } + try { + await pluginInitializationPromise + } catch (error) { + options.reportStartupFailure('plugin_host', 'unknown') + console.error('[PluginHost] Failed to initialize plugins:', error) + } + } + + async function initializeSkillSyncScan(signal?: AbortSignal): Promise { + if (signal?.aborted || options.isStopping()) { + return + } + if (!skillSyncScanPromise) { + const scan = (async () => { + try { + if (!skillSettings.isEnabled() || signal?.aborted) return + await ensureSkillServicesInitialized() + if (signal?.aborted) return + await skillSyncService.scanAndDetectNewDiscoveries() + logger.info('SkillSyncService background scan completed') + } catch (error) { + options.reportStartupFailure('skill_sync', 'unknown') + console.error('Failed to run SkillSyncService background scan:', error) + } + })() + skillSyncScanPromise = scan + void scan.finally(() => { + if (skillSyncScanPromise === scan) { + skillSyncScanPromise = null + } + }) + } + await skillSyncScanPromise + } + + async function initializeMcp() { + await initializePlugins() + + try { + await options.whenProxyReady() + await mcpService.initialize() + } catch (error) { + options.reportStartupFailure('mcp', 'unknown') + console.error('Failed to initialize McpService:', error) + return + } + try { + await pluginRuntimeSupervisor.reconcileAll() + } catch (error) { + options.reportStartupFailure('plugin_runtime', 'unknown') + console.error('[PluginHost] Failed to reconcile eager plugin runtimes:', error) + } + + try { + deepChatAgentHarness.refreshToolRegistry() + options.mcpReadinessChanged(!options.isStopping() && databaseRequests.state === 'running') + } catch (error) { + options.reportStartupFailure('mcp_integration', 'unknown') + console.error('Failed to finish MCP startup integration:', error) + } + } + + async function initializeRemoteControl() { + try { + await remoteService.initialize() + } catch (error) { + options.reportStartupFailure('remote_runtime', 'unknown') + console.error('RemoteService.initialize failed:', error) + } + } + + async function drainInitialization( + runDestroyStep: (name: string, step: () => Promise) => Promise + ): Promise { + await runDestroyStep('shellEnvironment.drain', async () => await shellEnvironmentRefresh) + const pendingSkillInitialization = skillInitializationPromise + if (pendingSkillInitialization) { + await runDestroyStep('skillInitialization.drain', async () => { + await pendingSkillInitialization + }) + } + const pendingPluginInitialization = pluginInitializationPromise + if (pendingPluginInitialization) { + await runDestroyStep('pluginInitialization.drain', async () => { + await pendingPluginInitialization + }) + } + const pendingSkillSyncScan = skillSyncScanPromise + if (pendingSkillSyncScan) { + await runDestroyStep('skillSyncScan.drain', async () => { + await pendingSkillSyncScan + }) + } + } + const programmaticToolDispatcher = new ProgrammaticToolDispatcher({ + parents: programmaticToolParents, + executeChild: async (input) => await toolService.callProgrammaticToolChild(input), + authorizeChild: async ({ + caller, + grant, + childOrdinal, + entry, + arguments: childArguments, + permission, + signal + }) => { + const requestId = permission.requestId?.trim() + if (!requestId) { + throw new CliRequestError( + 'unavailable', + 'Programmatic Tool permission request is unavailable', + { httpStatus: 503 } + ) + } + await cliMutationGuard.authorize({ + operation: 'tool.call', + effect: permission.permissionType === 'read' ? 'read' : 'destructive', + principal: caller.principal, + connectionId: caller.connectionId, + clientRequestId: `${grant.operation.providerToolCallId}:${childOrdinal}`, + arguments: { + target: entry.target.providerVisibleName, + arguments: childArguments + }, + displayData: { + target: entry.target.providerVisibleName, + ...(typeof permission.argumentsPreview === 'string' + ? { argumentsPreview: permission.argumentsPreview } + : {}) + }, + signal, + timeoutMs: grant.quotas.maxDurationMs + }) + if (!toolPermissionBroker.approve(requestId, grant.operation.sessionId)) { + throw new CliRequestError( + 'unavailable', + 'Programmatic Tool permission request is no longer active', + { httpStatus: 503 } + ) + } + }, + cancelChildPermission: (requestId, sessionId) => { + toolPermissionBroker.cancel(requestId, sessionId) + } + }) + const cliServer = new CliServer({ + userDataPath: bootstrap.profileDirectory, + appVersion: bootstrap.appVersion, + dispatch: async (method, input, caller, signal) => { + if (cliAudioTranscriptionService.handlesRpc(method)) { + return await databaseRequests.runRequest(method, () => + cliAudioTranscriptionService.dispatchRpc(method, input, caller, signal) + ) + } + if (cliOcrService.handlesRpc(method)) { + return await databaseRequests.runRequest(method, () => + cliOcrService.dispatchRpc(method, input, caller, signal) + ) + } + const routeDispatcher = options.resolveRouteDispatcher() + if (!routeDispatcher) throw new Error('CLI route dispatcher is not ready') + signal.throwIfAborted() + const output = await dispatchDeepchatRoute(routeDispatcher, method, input, { caller }) + signal.throwIfAborted() + return output + }, + dispatchProgrammaticTool: async ( + method, + input, + caller, + operation, + signal, + takeSettlementOwnership + ) => { + return await databaseRequests.runRequest(method, () => + programmaticToolDispatcher.dispatch( + method, + input, + caller, + operation, + signal, + takeSettlementOwnership + ) + ) + }, + completeProgrammaticToolPreDispatchFailure: (method, operation, error) => { + programmaticToolDispatcher.completePreDispatchFailure(method, operation, error) + }, + dispatchStream: async (method, input, caller, requestId, signal, emit) => { + if (coreExecution.cliRunService.handlesStream(method)) { + databaseRequests.assertRouteAllowed(method) + return await coreExecution.cliRunService.dispatchStream(method, input, caller, signal, emit) + } + return await databaseRequests.runRequest(method, () => + cliComputeService.dispatchStream(method, input, caller, requestId, signal, emit) + ) + }, + dispatchUpload: async (method, input, upload, caller, signal) => { + return await databaseRequests.runRequest(method, async () => { + if (cliAudioTranscriptionService.handlesUpload(method)) { + return await cliAudioTranscriptionService.dispatchUpload( + method, + input, + upload, + caller, + signal + ) + } + if (cliOcrService.handlesUpload(method)) { + return await cliOcrService.dispatchUpload(method, input, upload, caller, signal) + } + if (cliSkillService.handlesUpload(method)) { + return await cliSkillService.dispatchUpload(method, input, upload, caller, signal) + } + throw new Error(`CLI upload service is not ready for ${method}`) + }) + }, + authorize: async (input) => { + return await databaseRequests.runAuthorization(input.entry.contract.name, () => + cliRequestPolicy.authorize(input) + ) + }, + beginAgentRequest: (token) => agentCliTokenAuthority.beginRequest(token), + artifactSpool, + log: logger + }) + let hasInitialized = false + function scheduleMainStartupTask( + startupRunId: string, + task: Omit[0], 'runId'>, + errorMessage: string, + failure: { + component: MainLogStartupComponent + category: MainLogStartupComponentFailureCategory + } + ): void { + scheduleObservedStartupTask({ + coordinator: startupWorkloadCoordinator, + startupRunId, + task, + onFailure: (failedStartupRunId, error) => { + options.reportStartupFailure(failure.component, failure.category, { + message: errorMessage, + error, + startupRunId: failedStartupRunId + }) + } + }) + } + + function startStartupTasks() { + if (hasInitialized) { + console.info('[Startup][Main] Main startup skipped because startup already ran') + return + } + + const mainRunId = options.startupRunId + liveDelegationService.start() + hasInitialized = true + + const providers = providerSettings.getProviders() + console.info(`[Startup][Main] Main startup begin providers=${providers.length}`) + scheduleMainStartupTask( + mainRunId, + { + id: 'main:floating-button', + target: 'main', + phase: 'deferred', + resource: 'io', + labelKey: 'startup.main.floatingButton', + run: async () => { + await options.initializeFloatingButton() + } + }, + 'Failed to schedule floating button initialization:', + { component: 'floating_widget', category: 'resource' } + ) + + scheduleMainStartupTask( + mainRunId, + { + id: 'main:skills-sync-scan', + target: 'main', + phase: 'background', + resource: 'cpu', + labelKey: 'startup.main.skillsSyncScan', + run: async (taskContext) => { + await taskContext.yield() + await initializeSkillSyncScan(taskContext.signal) + } + }, + 'Failed to schedule SkillSyncService background scan:', + { component: 'skill_sync', category: 'unknown' } + ) + + scheduleMainStartupTask( + mainRunId, + { + id: 'main:mcp-init', + target: 'main', + phase: 'background', + resource: 'io', + labelKey: 'startup.main.mcpInit', + run: async (taskContext) => { + await taskContext.yield() + await initializeMcp() + } + }, + 'Failed to schedule MCP initialization:', + { component: 'mcp', category: 'unknown' } + ) + + scheduleMainStartupTask( + mainRunId, + { + id: 'main:remote-runtime', + target: 'main', + phase: 'background', + resource: 'io', + labelKey: 'startup.main.remoteRuntime', + run: async (taskContext) => { + await taskContext.yield() + await initializeRemoteControl() + } + }, + 'Failed to schedule remote runtime initialization:', + { component: 'remote_runtime', category: 'unknown' } + ) + + void startupWorkloadCoordinator + .whenIdle('main', async () => { + await startupWorkloadCoordinator.scheduleTask({ + id: 'main:provider-warmup-idle', + target: 'main', + phase: 'background', + resource: 'io', + labelKey: 'startup.main.provider.warmup', + visibleId: 'main.provider.warmup', + dedupeKey: 'main.provider.warmup:idle', + runId: mainRunId, + run: async (taskContext) => { + await initializeIdleProviderWarmup(taskContext) + } + }) + }) + .catch((error) => { + if (isStartupWorkloadCancellation(error)) return + console.error('Failed to schedule idle provider warmup:', error) + }) + } + + async function initializeIdleProviderWarmup(taskContext: StartupWorkloadTaskContext) { + taskContext.signal.throwIfAborted() + const [registryRefresh, catalogRefresh] = await Promise.allSettled([ + agentSettings.refreshAcpRegistry(false, { + automatic: true, + signal: taskContext.signal + }), + providerDbLoader.refreshIfNeeded(true, { + automatic: true, + signal: taskContext.signal + }) + ]) + taskContext.signal.throwIfAborted() + if (registryRefresh.status === 'rejected') throw registryRefresh.reason + if (catalogRefresh.status === 'rejected') throw catalogRefresh.reason + if (catalogRefresh.value.status === 'error') { + console.warn('[ProviderDbLoader] Startup refresh failed:', catalogRefresh.value.message) + } + const enabledProviders = providerSettings + .getEnabledProviders() + .map((provider) => provider.id) + .filter((providerId, index, ids) => ids.indexOf(providerId) === index) + + if (enabledProviders.length === 0) { + taskContext.reportProgress(1) + return + } + + console.info( + `[Startup][Main] startup.provider.warmup.deferred begin providers=${enabledProviders.length}` + ) + + for (const [index, providerId] of enabledProviders.entries()) { + if (taskContext.signal.aborted) { + const error = new Error(`Provider warmup aborted for ${providerId}`) + error.name = 'AbortError' + throw error + } + + const providerModels = providerSettings.getProviderModels(providerId) + const customModels = providerSettings.getCustomModels(providerId) + providerSettings.getDbProviderModels(providerId) + providerSettings.getBatchModelStatus(providerId, [ + ...providerModels.map((model) => model.id), + ...customModels.map((model) => model.id) + ]) + + taskContext.reportProgress((index + 1) / enabledProviders.length) + await taskContext.yield() + } + + console.info( + `[Startup][Main] startup.provider.warmup.deferred done providers=${enabledProviders.length}` + ) + } + + async function runAcpRegistryMigration(): Promise { + const service = new AcpRegistryMigrationService( + dependencies.settingsStore, + agentSettings, + agentDatabase + ) + try { + await service.runIfNeeded() + } catch (error) { + options.reportStartupFailure('acp_registry_migration', 'persistence') + console.error('Failed to migrate ACP registry references:', error) + } + + try { + await service.compensateEnabledRegistryAgentInstalls() + } catch (error) { + options.reportStartupFailure('acp_install_compensation', 'persistence') + console.error('Failed to compensate ACP install states:', error) + } + } + + function scheduleBackgroundWork(startupRunId = options.startupRunId): void { + scheduleMainStartupTask( + startupRunId, + { + id: 'main:legacy-import', + target: 'main', + phase: 'background', + resource: 'io', + labelKey: 'startup.main.legacyImport', + run: async () => legacyChatImportService.start(false) + }, + 'Failed to start legacy import task:', + { component: 'legacy_import', category: 'persistence' } + ) + + scheduleMainStartupTask( + startupRunId, + { + id: 'main:usage-stats-backfill', + target: 'main', + phase: 'background', + resource: 'io', + labelKey: 'startup.main.usageStatsBackfill', + run: async (taskContext) => usageStatsService.startBackfill(taskContext) + }, + 'Failed to start usage stats backfill:', + { component: 'usage_stats_backfill', category: 'persistence' } + ) + + scheduleMainStartupTask( + startupRunId, + { + id: 'main:sqlite-mainline-normalization', + target: 'main', + phase: 'background', + resource: 'io', + labelKey: 'startup.main.sqliteMainlineNormalization', + run: async (taskContext) => + runMainlineNormalizationMigration( + { sqlitePresenter: sessionDataMigrationSQLite }, + taskContext + ) + }, + 'Failed to start normalization backfill:', + { component: 'sqlite_mainline_normalization', category: 'persistence' } + ) + + scheduleMainStartupTask( + startupRunId, + { + id: 'main:disabled-agent-tool-capability-cleanup', + target: 'main', + phase: 'background', + resource: 'io', + labelKey: 'startup.main.disabledSearchToolCleanup', + run: async (taskContext) => + runDisabledAgentToolCapabilityCleanupMigration( + { + sqlitePresenter: sessionDataMigrationSQLite, + agentSettings + }, + taskContext + ) + }, + 'Failed to start disabled agent tool capability cleanup:', + { component: 'disabled_agent_tool_capability_cleanup', category: 'persistence' } + ) + + scheduleMainStartupTask( + startupRunId, + { + id: 'main:toolchain-gc', + target: 'main', + phase: 'background', + resource: 'io', + labelKey: 'startup.main.toolchainGc', + run: async (taskContext) => { + await taskContext.yield() + toolchainService.gcUnreachableTrees() + } + }, + 'Failed to collect unused toolchain trees:', + { component: 'toolchain_gc', category: 'resource' } + ) + } + + async function initializeStartup(): Promise { + await runAcpRegistryMigration() + await runBuiltinMcpAllowlistCompatibilityMigration({ + sqlitePresenter: sessionDataMigrationSQLite, + agentSettings + }) + // Migrations must settle before any renderer or background runtime reads Agent Skill scope. + await initializeSkills() + await agentSettings.retryPendingDeletedAgentSkillCleanup() + } + return { + execution: coreExecution, + host: { + resolveWorkspacePreviewRequest: async (url: string) => + await databaseRequests.runRequest('workspace.nativePreview', () => + workspaceService.resolvePreviewRequest(url) + ), + hasProvider: async (providerId: string) => + await databaseRequests.runRequest('providers.nativeExists', async () => + Boolean(providerSettings.getProviderById(providerId)) + ), + getShutdownConfirmation: async () => knowledgeService.getShutdownConfirmation(), + clearPermissionCaches: async () => { + commandPermissionService.clearAll() + filePermissionService.clearAll() + settingsPermissionService.clearAll() + approvalBroker.clear() + mcpApps.clear() + } + }, + mcpApps, + mcpAppSandbox, + openDesktopSession, + initializeStartup, + startStartupTasks, + scheduleBackgroundWork, + toolchainService, + ocrRuntimeService, + cliServer, + cliLauncherService, + cliAuditLog, + cliMutationGuard, + commandPermissionService, + filePermissionService, + settingsPermissionService, + approvalBroker, + projectService, + exporter, + fileService, + fileWatcherService, + workspaceService, + syncSettings, + syncService, + syncPeerService, + syncReplica, + automaticSync, + syncHostService, + replicaEndpoint, + knowledgeSettings, + knowledgeService, + mcpService, + skillService, + skillSyncService, + cliSkillService, + toolService, + pluginService, + hookService, + memoryService, + memory: options.memory, + remoteService, + cronJobs, + backgroundExec, + sessionPermissionPort, + initializeSkills, + ensureSkillServicesInitialized, + initializeSkillSyncScan, + initializeMcp, + initializeRemoteControl, + drainInitialization + } +} diff --git a/src/harness/core/settings.ts b/src/harness/core/settings.ts new file mode 100644 index 0000000000..9dfdfa9f6e --- /dev/null +++ b/src/harness/core/settings.ts @@ -0,0 +1,93 @@ +import { randomUUID } from 'node:crypto' +import type { initializeCoreProfile } from '@/app/profileStartup' +import { AppSettingsService } from '@/app/settingsService' +import { LoggingService, type LoggingHost } from '@/app/logging' +import { DesktopSettings, type DesktopSettingsHost } from '@/desktop/settings' +import { FontSettings } from '@/desktop/fontSettings' +import { SkillSettings } from '@/skill/settings' +import { AgentTraceSettings } from '@/agent/traceSettings' +import { OcrSettings } from '@/ocr/ocrSettings' +import { UpdateSettings } from '@/upgrade/settings' +import { DeepChatDefaults } from '@/agent/deepchat/defaults' +import { CommandShellService } from '@/agent/shared/process/commandShellService' +import type { DeepchatEventPublisher } from '@shared/contracts/events' +import type { NativeConfigurationSnapshot } from '@shared/types/nativeConfiguration' +import type { SettingsActivityInput } from '@shared/contracts/routes' + +export function createCoreSettings(options: { + profile: Pick< + Awaited>, + 'settingsStore' | 'privacySettings' | 'settingsDatabase' | 'currentAppVersion' + > + homeDirectory: string + ownerId?: string + desktop: DesktopSettingsHost + logging: LoggingHost + enumerateFonts(): Promise + publishNativeSnapshot(snapshot: NativeConfigurationSnapshot): void | Promise + publishEvent: DeepchatEventPublisher + nextVersion(): number + assertActive(): void +}) { + const { profile, publishEvent } = options + let appSettingsService: AppSettingsService + const committed = () => appSettingsService.publishNativeSnapshot() + const desktopSettings = new DesktopSettings( + profile.settingsStore, + options.desktop, + publishEvent, + committed + ) + const fontSettings = new FontSettings(profile.settingsStore, publishEvent, options.enumerateFonts) + const skillSettings = new SkillSettings(profile.settingsStore, options.homeDirectory) + const traceSettings = new AgentTraceSettings(profile.settingsStore) + const ocrSettings = new OcrSettings(profile.settingsStore, publishEvent) + const loggingService = new LoggingService( + profile.settingsStore, + options.logging, + publishEvent, + committed + ) + const updateSettings = new UpdateSettings(profile.settingsStore, profile.currentAppVersion) + const agentDefaults = new DeepChatDefaults({ + settings: profile.settingsStore, + publishSettingChanged: (key, value) => + publishEvent('settings.changed', { + changedKeys: [key], + version: Date.now(), + values: { [key]: value } + }) + }) + const commandShellService = new CommandShellService({ settings: profile.settingsStore }) + const recordSettingsActivity = (input: SettingsActivityInput) => { + void profile.settingsDatabase.recordSettingsActivity(input).catch((error) => { + console.warn('[SettingsActivity] Failed to record settings activity:', error) + }) + } + appSettingsService = new AppSettingsService({ + ownerId: options.ownerId ?? randomUUID(), + updateSettings, + publishNativeSnapshot: options.publishNativeSnapshot, + agentDefaults, + privacy: profile.privacySettings, + traceSettings, + desktopSettings, + fonts: fontSettings, + logging: loggingService, + ocr: ocrSettings, + recordActivity: recordSettingsActivity, + publishEvent, + nextVersion: options.nextVersion, + assertActive: options.assertActive + }) + return { + appSettingsService, + desktopSettings, + skillSettings, + traceSettings, + ocrSettings, + agentDefaults, + commandShellService, + recordSettingsActivity + } +} diff --git a/src/harness/memory-vector.ts b/src/harness/memory-vector.ts new file mode 100644 index 0000000000..85dd7b1d96 --- /dev/null +++ b/src/harness/memory-vector.ts @@ -0,0 +1,411 @@ +import { AsyncSemaphore } from '@/lib/asyncSemaphore' +import fs from 'node:fs' +import path from 'node:path' +import { DuckDBInstance } from '@duckdb/node-api' +import { MemoryVectorStore, type PackedMemoryVectors } from '@/memory/infra/memoryVectorStore' +import { + createMemoryVectorStorePaths, + MEMORY_VECTOR_OWNER_FILE, + MEMORY_VECTOR_LEASE_SUFFIX +} from '@/memory/infra/memoryVectorStorePaths' +import { isSafeAgentId } from '@shared/types/agent-memory' +import { + isDuckDbFatalError, + MemoryVectorStoreQuarantineRequiredError +} from '@/memory/infra/vectorStoreErrors' +import { + decodeMemoryVector, + isMemoryVectorControl, + MEMORY_VECTOR_PENDING, + MEMORY_VECTOR_NATIVE_PART_BYTES, + MemoryVectorNativeCommandSchema, + MemoryVectorConfigSchema, + type MemoryVectorNativeCommand, + type MemoryVectorConfig +} from '@shared/harness/memory-vector' + +interface Resource { + agentId: string + dimensions: number + opening: Promise + tail: Promise + closing: boolean + openingDone: boolean + writes: number + disposal?: Promise +} + +interface Upload { + id: string + resource: string + memoryIds: string[] + bytes: Buffer + offset: number + committing: boolean + records: ReturnType> + writing: Promise +} + +export class MemoryVectorOwner { + private readonly config: MemoryVectorConfig + private readonly resources = new Map() + private readonly agents = new Map() + private readonly jobs = new AsyncSemaphore(2) + private readonly operations = new Set() + private readonly recovered = new Set() + private readonly quarantined = new Set() + private upload: Upload | undefined + private stopped = false + private readonly ownership: Promise + + constructor(config: MemoryVectorConfig) { + this.config = MemoryVectorConfigSchema.parse(config) + const memoryDbDir = this.config.memoryDbDir + this.ownership = (async () => { + fs.mkdirSync(memoryDbDir, { recursive: true }) + let owner: DuckDBInstance + try { + owner = await DuckDBInstance.create(path.join(memoryDbDir, MEMORY_VECTOR_OWNER_FILE), { + threads: '1', + memory_limit: '16MB' + }) + } catch { + const error = new Error('Memory vector file ownership is unavailable') + error.name = 'MemoryVectorOwnershipUnavailableError' + throw error + } + try { + if (this.stopped) throw new Error('Memory vector owner is stopping') + for (const entry of fs.readdirSync(memoryDbDir, { withFileTypes: true })) { + const suffix = entry.name.endsWith(MEMORY_VECTOR_LEASE_SUFFIX) + ? MEMORY_VECTOR_LEASE_SUFFIX + : entry.name.endsWith('.v2.duckdb.quarantine') + ? '.v2.duckdb.quarantine' + : undefined + if (!entry.isFile() || !suffix) continue + const agentId = entry.name.slice(0, -suffix.length) + if (!isSafeAgentId(agentId)) continue + this.recovered.add(agentId) + MemoryVectorStore.markQuarantined(this.paths(agentId)) + } + MemoryVectorStore.recoverQuarantinedStores(memoryDbDir) + return owner + } catch (error) { + owner.closeSync() + throw error + } + })() + void this.ownership.catch(() => undefined) + } + + async command(input: unknown): Promise { + const command = MemoryVectorNativeCommandSchema.parse(input) + if (this.stopped || command.generation !== this.config.generation) { + throw new Error('Memory vector generation is unavailable') + } + if ( + this.operations.has(command.operation) || + this.operations.size >= + (isMemoryVectorControl(command.method) ? MEMORY_VECTOR_PENDING : MEMORY_VECTOR_PENDING - 8) + ) { + throw new Error('Memory vector operation limit exceeded') + } + this.operations.add(command.operation) + try { + await this.ownership + if (this.stopped) throw new Error('Memory vector owner is stopping') + return await this.execute(command) + } finally { + this.operations.delete(command.operation) + } + } + + async dispose(): Promise { + this.stopped = true + this.upload?.records.reject(new Error('Memory vector owner is stopping')) + this.upload = undefined + await Promise.all([...this.resources].map(([id, resource]) => this.close(id, resource, true))) + const owner = await this.ownership.catch(() => undefined) + owner?.closeSync() + } + + private paths(agentId: string) { + return createMemoryVectorStorePaths(this.config.memoryDbDir, agentId) + } + + private lease(agentId: string) { + return `${this.paths(agentId).current}.lease` + } + + private quarantine(agentId: string): void { + this.quarantined.add(agentId) + const id = this.agents.get(agentId) + const resource = id ? this.resources.get(id) : undefined + if (resource) resource.closing = true + try { + MemoryVectorStore.markQuarantined(this.paths(agentId)) + } catch (error) { + throw new MemoryVectorStoreQuarantineRequiredError('Vector quarantine marker failed', { + cause: error + }) + } + } + + private assertRecoverable(agentId: string): void { + if (this.quarantined.has(agentId)) + throw new MemoryVectorStoreQuarantineRequiredError( + 'Native vector recovery requires a new process' + ) + } + + private async execute(command: MemoryVectorNativeCommand): Promise { + if (command.method === 'open') { + this.assertRecoverable(command.agentId) + if (this.resources.has(command.resource) || this.agents.has(command.agentId)) { + throw new Error('Memory vector store already has an owner') + } + if (fs.existsSync(this.lease(command.agentId))) { + this.recovered.add(command.agentId) + MemoryVectorStore.markQuarantined(this.paths(command.agentId)) + } + fs.writeFileSync(this.lease(command.agentId), '') + const opening = this.jobs.run(() => + MemoryVectorStore.create( + this.paths(command.agentId), + command.dimensions, + command.embedding, + this.config.host + ) + ) + const resource: Resource = { + agentId: command.agentId, + dimensions: command.dimensions, + opening, + tail: opening, + closing: false, + openingDone: false, + writes: 0 + } + this.resources.set(command.resource, resource) + this.agents.set(command.agentId, command.resource) + void opening.then( + () => { + resource.openingDone = true + }, + () => { + resource.openingDone = true + } + ) + try { + const store = await opening + if (resource.closing) throw new Error('Memory vector resource was revoked while opening') + return { + resource: command.resource, + usable: store.isUsable() && !this.recovered.has(command.agentId) + } + } catch (error) { + if ( + error instanceof MemoryVectorStoreQuarantineRequiredError || + isDuckDbFatalError(error) + ) { + this.quarantine(resource.agentId) + throw error + } + // Failed or late opens remain owned until the native promise has settled and cleanup runs. + await this.close(command.resource, resource) + throw error + } + } + if (command.method === 'reset' || command.method === 'quarantine') { + const id = this.agents.get(command.agentId) + const resource = id ? this.resources.get(id) : undefined + if (command.method === 'quarantine') { + this.quarantine(command.agentId) + return null + } + this.assertRecoverable(command.agentId) + if (id && resource) await this.close(id, resource) + if (command.method === 'reset') { + MemoryVectorStore.destroyFiles(this.paths(command.agentId)) + this.recovered.delete(command.agentId) + } + return null + } + const resource = this.resources.get(command.resource) + if (command.method === 'close') { + if (resource) await this.close(command.resource, resource, command.preserveLease) + return null + } + if (!resource || resource.closing) throw new Error('Memory vector resource is unavailable') + switch (command.method) { + case 'query': { + const vector = decodeMemoryVector( + Buffer.from(command.vector, 'base64'), + resource.dimensions + ) + return this.run(resource, (store) => store.query(vector, { topK: command.topK })) + } + case 'queryByMemoryId': + return this.run(resource, (store) => + store.queryByMemoryId(command.memoryId, { topK: command.topK }) + ) + case 'delete': + return this.run(resource, (store) => store.deleteByMemoryIds(command.memoryIds), true) + case 'list': + return this.run(resource, (store) => store.listMemoryIds(command.afterId, command.limit)) + case 'upsert': { + const complete = command.offset === 0 && command.data.length === command.bytes + if ( + !(command.data.buffer instanceof ArrayBuffer) || + new Set(command.memoryIds).size !== command.memoryIds.length || + command.bytes !== command.memoryIds.length * resource.dimensions * 4 + ) + throw new Error('Invalid memory vector upload shape') + if (command.offset === 0) { + if (this.upload) throw new Error('Memory vector upload is busy') + const bytes = complete + ? Buffer.from(command.data.buffer, command.data.byteOffset, command.data.byteLength) + : Buffer.allocUnsafe(command.bytes) + const records = Promise.withResolvers() + void records.promise.catch(() => undefined) + const writing = this.run(resource, async (store) => { + const batch = await records.promise + if (resource.closing || this.recovered.has(resource.agentId) || !store.isUsable()) + throw new Error('Memory vector resource is unavailable') + resource.writes++ + try { + await store.upsertPacked(batch) + } finally { + resource.writes-- + } + }) + void writing.catch(() => undefined) + this.upload = { + id: command.upload, + resource: command.resource, + memoryIds: command.memoryIds, + bytes, + offset: 0, + committing: false, + records, + writing + } + } + const upload = this.upload + if ( + !upload || + upload.id !== command.upload || + upload.resource !== command.resource || + upload.committing + ) + throw new Error('Memory vector upload is unavailable') + try { + if ( + command.offset !== upload.offset || + command.bytes !== upload.bytes.length || + command.memoryIds.length !== upload.memoryIds.length || + command.memoryIds.some((id, index) => id !== upload.memoryIds[index]) || + command.data.length > MEMORY_VECTOR_NATIVE_PART_BYTES || + command.data.length > upload.bytes.length - upload.offset + ) + throw new Error('Invalid memory vector upload part') + if (!complete) upload.bytes.set(command.data, upload.offset) + upload.offset += command.data.length + if (upload.offset !== upload.bytes.length) return null + upload.committing = true + upload.records.resolve({ + memoryIds: upload.memoryIds, + dimensions: resource.dimensions, + bytes: upload.bytes + }) + await upload.writing + return null + } catch (error) { + upload.records.reject(error) + await upload.writing.catch(() => undefined) + if (this.upload === upload) this.upload = undefined + throw error + } finally { + if (upload.committing || upload.offset === upload.bytes.length) this.upload = undefined + } + } + case 'abort': + if ( + this.upload?.id === command.upload && + this.upload.resource === command.resource && + !this.upload.committing + ) { + const upload = this.upload + upload.records.reject(new Error('Memory vector upload was aborted')) + await upload.writing.catch(() => undefined) + if (this.upload === upload) this.upload = undefined + } + return null + } + } + + private run( + resource: Resource, + task: (store: MemoryVectorStore) => Promise, + write = false + ): Promise { + if (write) resource.writes++ + const result = resource.tail + .catch(() => undefined) + .then(async () => { + const store = await resource.opening + try { + return await this.jobs.run(() => { + if (resource.closing || this.recovered.has(resource.agentId) || !store.isUsable()) + throw new Error('Memory vector resource is unavailable') + return task(store) + }) + } catch (error) { + if ( + error instanceof MemoryVectorStoreQuarantineRequiredError || + isDuckDbFatalError(error) + ) { + this.quarantine(resource.agentId) + } + throw error + } + }) + .finally(() => { + if (write) resource.writes-- + }) + resource.tail = result + return result + } + + private close(id: string, resource: Resource, preserveLease = false): Promise { + if (this.upload?.resource === id && !this.upload.committing) { + this.upload.records.reject(new Error('Memory vector resource is closing')) + this.upload = undefined + } + if (resource.disposal) return resource.disposal + resource.closing = true + const dirty = preserveLease || resource.writes > 0 || !resource.openingDone + resource.disposal = (async () => { + await resource.tail.catch(() => undefined) + this.assertRecoverable(resource.agentId) + const store = await resource.opening.catch(() => undefined) + if (store) { + try { + await this.jobs.run(() => store.close()) + } catch (error) { + this.quarantine(resource.agentId) + throw new MemoryVectorStoreQuarantineRequiredError( + 'Native vector close failed; recovery requires a new process', + { cause: error } + ) + } + } + if (!dirty) fs.rmSync(this.lease(resource.agentId), { force: true }) + if (this.resources.get(id) === resource) { + this.resources.delete(id) + this.agents.delete(resource.agentId) + } + })() + return resource.disposal + } +} diff --git a/src/harness/plugins/core.ts b/src/harness/plugins/core.ts new file mode 100644 index 0000000000..75371ee767 --- /dev/null +++ b/src/harness/plugins/core.ts @@ -0,0 +1,614 @@ +import { createCoreAdmission, type CoreAdmissionService } from '../core/admission' +import { CoreHarnessControl, type CoreControlService } from '../core/control' +import { CoreNativeHelpers } from '../core/helpers' +import { CoreNativeBrowser } from '../core/browser' +import { CoreNativeDesktop } from '../core/desktop' +import { createCoreKernel } from '../core/kernel' +import { CoreNativeCalls } from '../core/native' +import { createCoreSettings } from '../core/settings' +import { CoreMemoryRuntime, type CoreMemoryService } from '../core/memory' +import type { MemoryCapability } from './memory' +import type { HooksCapability } from './hooks' +import { RutisMemoryVectorStores } from '@/harness/memory-vector' +import path from 'node:path' +import { + CoreConfigurationCommandSchema, + type CoreConfigurationNative +} from '@shared/harness/configuration' +import { + createDeepchatEventEnvelope, + type DeepchatEventEnvelope, + type DeepchatEventName +} from '@shared/contracts/events' +import { definePlugin } from '@arcships/rutis' +import { z } from 'zod' +import { initializeCoreProfile } from '@/app/profileStartup' +import { dispatchDeepchatRoute } from '@/routes' +import type { CoreProfileHost } from '@/app/profileStartup' +import type { DatabaseSecurityCryptoPort } from '@/app/databaseSecurity' +import { CorePayloadStore } from '../core/payload' +import { + CoreCarrierConfigSchema, + CoreNativeEventSchema, + CoreKernelBootstrapSchema, + CoreRouteInvocationSchema, + CoreMcpAppProtocolCommandSchema, + CoreMcpAppProtocolPolicySchema, + CoreHostQuerySchema, + CoreFailureComponentSchema, + isCoreControlRoute, + type CoreCarrierConfig, + type CoreNativeMethod, + type CorePayload, + type CoreStatus +} from '@shared/harness/core' + +const profileBootstrap = z + .object({ + userDataDirectory: z.string().min(1).max(8192), + homeDirectory: z.string().min(1).max(8192), + currentAppVersion: z.string().min(1).max(64), + systemLocale: z.string().min(1).max(256), + appPath: z.string().min(1).max(32768), + isPackaged: z.boolean() + }) + .strict() + +export default definePlugin({ + name: 'deepchat-core', + inject: ['deepchatCoreNative', 'deepchatMemoryCapability', 'deepchatHooksCapability'], + provides: { + deepchatCore: { + identity: 'async', + openProfile: 'async', + assembleKernel: 'async', + startKernel: 'async', + invokeRoute: 'async', + invokeControlRoute: 'async', + mcpAppProtocol: 'async', + hostQuery: 'async', + configure: 'async', + observeSystemTheme: 'async', + nativeEvents: 'async', + status: 'async', + beginShutdown: 'async', + stop: 'async' + } + }, + async apply(ctx, input) { + const memoryCapability = ctx.use('deepchatMemoryCapability') + const hooksCapability = ctx.use('deepchatHooksCapability') + const config = CoreCarrierConfigSchema.parse(input) + const store = new CorePayloadStore(config, 'core') + const native = ctx.use< + CoreAdmissionService & + CoreControlService & + CoreMemoryService & { + invoke( + generation: string, + method: CoreNativeMethod, + args: CorePayload + ): Promise + } + >('deepchatCoreNative') + let profile: Awaited> | undefined + let profilePaths: z.infer | undefined + let kernel: Awaited> | undefined + let assembling: Promise | undefined + let starting: Promise | undefined + let kernelAttempted = false + let kernelPublish: ((name: DeepchatEventName, payload: unknown) => void) | undefined + const admission = await createCoreAdmission(native, config.generation, { + observationsEnabled: () => profile?.settingsStore.get('loggingEnabled') ?? false + }) + const memory = new CoreMemoryRuntime(ctx, native, config.generation) + let vectorStores: RutisMemoryVectorStores | undefined + let phase: CoreStatus['phase'] = 'idle' + let opening: Promise | undefined + let stopping: Promise | undefined + let ownsProfile = false + let configuration: ReturnType | undefined + let configurationTail: Promise = Promise.resolve() + let configurationEvents: DeepchatEventEnvelope[] | undefined + const businessCalls = new Set>() + let settingsVersion = 0 + const isStopping = () => + phase === 'stopping' || phase === 'stopped' || Boolean(kernel?.stopping) + const status = (): CoreStatus => ({ + generation: config.generation, + pid: process.pid, + node: process.versions.node, + modules: process.versions.modules, + phase: phase !== 'stopped' && kernel?.stopping ? 'stopping' : phase, + businessReady: phase === 'running' && !isStopping() + }) + const nativeCalls = new CoreNativeCalls( + store, + async (method, ref) => await native.invoke(config.generation, method, ref), + () => isStopping() || ctx.signal.aborted + ) + const callNative = (method: CoreNativeMethod, args: unknown[]) => nativeCalls.call(method, args) + let failureReported = false + const fail = (component: z.infer) => { + if (failureReported || phase === 'stopped') return + failureReported = true + // Report through reserved capacity even when presentation delivery has failed. + void callNative('core.failure', [component]).catch(() => {}) + void stop().catch(() => {}) + } + const control = new CoreHarnessControl(native, config.generation, () => fail('control')) + const helpers = new CoreNativeHelpers( + async (command) => await callNative('helpers', [command]), + () => fail('helpers') + ) + const browser = new CoreNativeBrowser( + async (command) => await callNative('browser', [command]), + () => fail('browser') + ) + const desktop = new CoreNativeDesktop( + async (command) => await callNative('desktop', [command]), + (event) => kernel?.runtime.remoteService.handleAuthWindowEvent(event), + () => fail('desktop') + ) + const host: CoreProfileHost = { + setLoggingEnabled: async (enabled) => { + await callNative('profile.logging', [enabled]) + }, + showDatabaseUnlockProgress: async (payload, options) => { + await callNative('profile.progress', [payload, options]) + }, + requestDatabaseUnlock: async (payload) => + z + .string() + .nullable() + .parse(await callNative('profile.unlock', [payload])), + requestDatabaseRecovery: async (payload) => { + const value = await callNative('profile.recovery', [payload]) + return z + .union([ + z.object({ action: z.literal('start-empty') }).strict(), + z.object({ action: z.literal('password'), password: z.string() }).strict() + ]) + .nullable() + .parse(value) + }, + quit: async () => { + await callNative('profile.quit', []) + } + } + const crypto: DatabaseSecurityCryptoPort = { + isAvailable: async () => z.boolean().parse(await callNative('crypto.available', [])), + encrypt: async (value) => z.string().parse(await callNative('crypto.encrypt', [value])), + decrypt: async (value) => z.string().parse(await callNative('crypto.decrypt', [value])), + getBackend: async () => + z + .string() + .optional() + .parse(await callNative('crypto.backend', [])) + } + const runConfiguration = async ( + execute: (owner: NonNullable['appSettingsService']) => Promise + ): Promise => { + if ( + isStopping() || + !['profile-open', 'kernel-assembled', 'starting', 'running'].includes(phase) || + !configuration + ) + throw new Error('Core configuration is unavailable') + const operation = configurationTail.then(async () => { + if ( + isStopping() || + !['profile-open', 'kernel-assembled', 'starting', 'running'].includes(phase) || + !configuration + ) + throw new Error('Core configuration is unavailable') + const events: DeepchatEventEnvelope[] = [] + configurationEvents = events + try { + const [outcome] = await Promise.allSettled([execute(configuration.appSettingsService)]) + if (events.length) await callNative('configuration', [{ operation: 'events', events }]) + if (outcome.status === 'rejected') throw outcome.reason + return await store.encode(outcome.value) + } finally { + configurationEvents = undefined + } + }) + configurationTail = operation.then( + () => undefined, + () => undefined + ) + return await operation + } + const trackBusinessCall = (operation: Promise): Promise => { + businessCalls.add(operation) + void operation.then( + () => businessCalls.delete(operation), + () => businessCalls.delete(operation) + ) + return operation + } + const invokeRoute = (ref: CorePayload, control: boolean): Promise => + trackBusinessCall( + (async () => { + const input = CoreRouteInvocationSchema.parse(await store.decode(ref)) + if (isCoreControlRoute(input.routeName) !== control) + throw new Error('Invalid Core route lane') + if (isStopping() || phase !== 'running' || !kernel) + throw new Error('Core business routes are unavailable') + return await store.encode( + await dispatchDeepchatRoute( + kernel.controller.routeDispatcher, + input.routeName, + input.input, + input.context + ) + ) + })() + ) + const stop = (): Promise => { + if (stopping) return stopping + phase = 'stopping' + profile?.databaseSecurityService.stop() + profile?.secretStore.pause() + const kernelRetirement = kernel?.stop() + void kernelRetirement?.catch(() => {}) + stopping = (async () => { + // An in-flight Native effect must settle before profile startup can retire its lock. + await opening?.catch(() => undefined) + await assembling?.catch(() => undefined) + const errors: unknown[] = [] + const retire = async (action: () => unknown | Promise) => { + try { + await action() + } catch (error) { + errors.push(error) + } + } + await retire(async () => await (kernelRetirement ?? kernel?.stop())) + await retire(() => hooksCapability.retire()) + await retire(async () => await starting?.catch(() => undefined)) + if (kernelAttempted) await retire(() => browser.close()) + await retire(() => helpers.close()) + await retire(() => desktop.close()) + await retire(() => configurationTail) + await retire(async () => await configuration?.appSettingsService.stop()) + await retire(async () => await vectorStores?.dispose()) + await retire(() => memory.retire()) + await retire(() => admission.close()) + await retire(() => nativeCalls.drain()) + await Promise.allSettled(businessCalls) + control.stop() + if (kernelAttempted && !kernel) + errors.push(new Error('Partial Core Kernel requires physical retirement')) + if (errors.length) throw new AggregateError(errors, 'Core did not retire') + profile?.closeProfile() + if (ownsProfile) throw new Error('Core profile ownership did not retire') + profile = undefined + phase = 'stopped' + })() + return stopping + } + ctx.effect(async () => { + try { + await stop() + } finally { + await store.close() + } + }) + ctx.provide('deepchatCore', { + identity: status, + async openProfile(ref: CorePayload): Promise { + if (opening || phase !== 'idle') throw new Error('Core profile generation already started') + phase = 'opening' + opening = (async () => { + try { + const bootstrap = profileBootstrap.parse(await store.decode(ref)) + profilePaths = bootstrap + profile = await initializeCoreProfile({ + ...bootstrap, + host, + crypto, + observe: async (observation) => { + await callNative('profile.observation', [observation]) + }, + onOwnershipChanged: (owned) => { + ownsProfile = owned + } + }) + if (isStopping()) { + throw new Error('Core profile startup was stopped') + } + if (!path.isAbsolute(bootstrap.appPath)) throw new Error('Invalid Core app path') + vectorStores = new RutisMemoryVectorStores(memory, { + memoryDbDir: path.join(bootstrap.userDataDirectory, 'app_db', 'AgentMemory'), + host: { + appPath: bootstrap.appPath, + userDataPath: bootstrap.userDataDirectory, + isPackaged: bootstrap.isPackaged + } + }) + const effect = (command: CoreConfigurationNative) => + callNative('configuration', [command]) + configuration = createCoreSettings({ + profile, + homeDirectory: bootstrap.homeDirectory, + ownerId: config.generation, + desktop: { + systemLocale: bootstrap.systemLocale, + readTheme: async () => + z + .object({ theme: z.enum(['dark', 'light', 'system']), isDark: z.boolean() }) + .strict() + .parse(await effect({ operation: 'readTheme' })), + applyTheme: async (theme) => + z.boolean().parse(await effect({ operation: 'applyTheme', theme })), + refreshLanguage: async (locale) => { + await effect({ operation: 'refreshLanguage', locale }) + }, + refreshTheme: async (isDark) => { + await effect({ operation: 'refreshTheme', isDark }) + }, + readLaunchAtLogin: async () => + z.boolean().parse(await effect({ operation: 'readLaunchAtLogin' })), + setLaunchAtLogin: async (enabled) => + z.boolean().parse(await effect({ operation: 'setLaunchAtLogin', enabled })), + applyContentProtection: async (enabled) => { + await effect({ operation: 'applyContentProtection', enabled }) + }, + applyFloatingButtonEnabled: async (enabled) => { + await effect({ operation: 'applyFloatingButtonEnabled', enabled }) + } + }, + logging: { + applyLogging: async (enabled) => { + await effect({ operation: 'applyLogging', enabled }) + }, + openLoggingFolder: async () => { + await effect({ operation: 'openLoggingFolder' }) + } + }, + enumerateFonts: async () => + z.array(z.string()).parse(await effect({ operation: 'enumerateFonts' })), + publishNativeSnapshot: async (snapshot) => { + await effect({ operation: 'snapshot', snapshot }) + }, + publishEvent: (name, payload) => { + if (kernelPublish) return kernelPublish(name, payload) + if (!configurationEvents || configurationEvents.length >= 64) + throw new Error('Core configuration event batch is unavailable') + configurationEvents.push(createDeepchatEventEnvelope(name, payload)) + }, + nextVersion: () => ++settingsVersion, + assertActive: () => { + if ( + isStopping() || + !['profile-open', 'kernel-assembled', 'starting', 'running'].includes(phase) + ) + throw new Error('Core configuration is unavailable') + } + }) + phase = 'profile-open' + return await store.encode(status()) + } catch (error) { + if (!stopping) { + await vectorStores?.dispose() + await memory.retire() + profile?.closeProfile() + if (!ownsProfile) { + profile = undefined + phase = 'stopped' + } else phase = 'stopping' + } + throw error + } + })() + return await opening + }, + async assembleKernel(ref: CorePayload): Promise { + if ( + assembling || + phase !== 'profile-open' || + !profile || + !configuration || + !vectorStores || + !profilePaths + ) + throw new Error('Core Kernel is unavailable') + const ownerProfile = profile + const ownerSettings = configuration + const ownerVectors = vectorStores + const paths = profilePaths + phase = 'assembling' + assembling = (async () => { + try { + const input = CoreKernelBootstrapSchema.parse(await store.decode(ref)) + for (const value of Object.values(input).flat()) + if (typeof value === 'string' && !path.isAbsolute(value)) + throw new Error('Invalid Core Kernel path') + const nativePorts = await desktop.createPorts({ + temporaryDirectory: input.temporaryDirectory, + profileDirectory: paths.userDataDirectory + }) + if (isStopping()) throw new Error('Core Kernel startup was stopped') + kernelAttempted = true + kernel = await createCoreKernel({ + control, + profile: ownerProfile, + settings: ownerSettings, + bootstrap: { + ...input, + profileDirectory: paths.userDataDirectory, + homeDirectory: paths.homeDirectory, + appPath: paths.appPath, + appVersion: paths.currentAppVersion, + isPackaged: paths.isPackaged + }, + providerCatalogBootstrap: { + userDataDirectory: paths.userDataDirectory, + builtInCatalogPath: input.providerCatalogPath + }, + nextSettingsVersion: () => ++settingsVersion, + native: nativePorts, + browser, + helpers: { + spawnBackgroundExecHost: helpers.spawnBackgroundExecHost.bind(helpers), + spawnCodeModeHost: helpers.spawnCodeModeHost.bind(helpers), + spawnSchedulerHost: helpers.spawnSchedulerHost.bind(helpers), + spawnFileWatcherHost: helpers.spawnFileWatcherHost + }, + admission, + vectors: ownerVectors, + memory: memoryCapability, + hooks: hooksCapability, + bindEventPublisher: (publish) => { + kernelPublish = publish + }, + isStopping, + allowInsecureTls: input.allowInsecureTls, + onFailure: () => fail('http') + }) + if (isStopping()) throw new Error('Core Kernel startup was stopped') + phase = 'kernel-assembled' + return await store.encode(status()) + } catch (error) { + if (!isStopping()) phase = kernelAttempted ? 'stopping' : 'profile-open' + throw error + } + })() + return await assembling + }, + async configure(ref: CorePayload): Promise { + return await runConfiguration(async (owner) => { + const command = CoreConfigurationCommandSchema.parse(await store.decode(ref)) + if (!['profile-open', 'kernel-assembled', 'starting', 'running'].includes(phase)) + throw new Error('Core configuration is unavailable') + switch (command.operation) { + case 'read': + return await owner.getSnapshot(command.input.keys) + case 'update': + return await owner.update(command.input.changes) + case 'nativeSnapshot': + return owner.readNativeSnapshot() + case 'floatingBounds': + return await owner.setFloatingButtonBounds(command.bounds) + case 'initialize': + return await owner.initializeDesktop() + } + }) + }, + async startKernel(): Promise { + if (starting || phase !== 'kernel-assembled' || !kernel) + throw new Error('Core Kernel cannot start') + phase = 'starting' + starting = (async () => { + try { + await kernel.start() + if (isStopping()) throw new Error('Core Kernel startup was stopped') + phase = 'running' + return await store.encode(status()) + } catch (error) { + void stop().catch(() => {}) + throw error + } + })() + return await starting + }, + async invokeRoute(ref: CorePayload): Promise { + return await invokeRoute(ref, false) + }, + async invokeControlRoute(ref: CorePayload): Promise { + return await invokeRoute(ref, true) + }, + async mcpAppProtocol(ref: CorePayload): Promise { + return await trackBusinessCall( + (async () => { + const input = CoreMcpAppProtocolCommandSchema.parse(await store.decode(ref)) + if (isStopping() || phase !== 'running' || !kernel) + throw new Error('Core MCP App protocol is unavailable') + const sandbox = kernel.runtime.mcpAppSandbox + return await store.encode( + input.operation === 'policy' + ? CoreMcpAppProtocolPolicySchema.parse( + await sandbox.getProtocolPolicy(input.instanceId) + ) + : z + .boolean() + .parse( + await sandbox.requestBrowserPermission( + input.instanceId, + input.context, + input.kinds + ) + ) + ) + })() + ) + }, + async hostQuery(ref: CorePayload): Promise { + return await trackBusinessCall( + (async () => { + const input = CoreHostQuerySchema.parse(await store.decode(ref)) + if (isStopping() || phase !== 'running' || !kernel) + throw new Error('Core host queries are unavailable') + const host = kernel.runtime.host + return await store.encode( + input.operation === 'workspacePreview' + ? await host.resolveWorkspacePreviewRequest(input.url) + : await host.hasProvider(input.providerId) + ) + })() + ) + }, + async observeSystemTheme() { + return await runConfiguration(async (owner) => await owner.observeSystemTheme()) + }, + async nativeEvents(ref: CorePayload): Promise { + const event = CoreNativeEventSchema.parse(await store.decode(ref)) + if (Array.isArray(event)) { + helpers.accept(event) + return await store.encode(undefined) + } + if (event.type === 'host.settingsWindow' || event.type === 'host.systemResume') { + if (isStopping() || !kernel) throw new Error('Core host control is unavailable') + if (event.type === 'host.systemResume') { + if (phase !== 'running' || kernel.databaseRequests.state !== 'running') + return await store.encode(undefined) + kernel.observeSystemResume() + } else { + kernel.observeSettingsWindow(event.action) + } + return await store.encode(undefined) + } + if (event.type === 'host.clearPermissions' || event.type === 'host.shutdownConfirmation') { + return await trackBusinessCall( + (async () => { + if (isStopping() || phase !== 'running' || !kernel) + throw new Error('Core host control is unavailable') + return await store.encode( + event.type === 'host.clearPermissions' + ? await kernel.runtime.host.clearPermissionCaches() + : await kernel.runtime.host.getShutdownConfirmation() + ) + })() + ) + } + if (event.type.startsWith('desktop.')) { + desktop.accept(event) + return await store.encode(undefined) + } + return await store.encode(await browser.accept(event)) + }, + async status() { + return await store.encode(status()) + }, + async stop() { + await stop() + return await store.encode(status()) + }, + async beginShutdown() { + void stop().catch(() => {}) + return await store.encode(status()) + } + }) + } +}) diff --git a/src/harness/plugins/hooks.ts b/src/harness/plugins/hooks.ts new file mode 100644 index 0000000000..a8b8d7c67d --- /dev/null +++ b/src/harness/plugins/hooks.ts @@ -0,0 +1,29 @@ +import { definePlugin } from '@arcships/rutis' +import { HookService } from '@/hook' + +export interface HooksCapability { + open(...ports: ConstructorParameters): HookService + retire(): Promise +} + +export default definePlugin({ + name: 'deepchat-hooks', + provides: { deepchatHooksCapability: { open: 'sync', retire: 'async' } }, + apply(ctx) { + let service: HookService | undefined + let retirement: Promise | undefined + const hooks: HooksCapability = { + open(...ports) { + if (ctx.signal.aborted || retirement || service) + throw new Error('Hooks capability cannot reopen') + return (service = new HookService(...ports)) + }, + retire() { + return (retirement ??= service?.dispose() ?? Promise.resolve()) + } + } + ctx.signal.addEventListener('abort', () => void hooks.retire().catch(() => {}), { once: true }) + ctx.effect(() => hooks.retire()) + ctx.provide('deepchatHooksCapability', hooks) + } +}) diff --git a/src/harness/plugins/memory-vector.ts b/src/harness/plugins/memory-vector.ts new file mode 100644 index 0000000000..657ea57f7b --- /dev/null +++ b/src/harness/plugins/memory-vector.ts @@ -0,0 +1,76 @@ +import { definePlugin } from '@arcships/rutis' +import { MemoryVectorOwner } from '../memory-vector' +import { + MEMORY_VECTOR_PENDING, + type MemoryVectorNativeCommand, + type MemoryVectorConfig +} from '@shared/harness/memory-vector' +import { + isDuckDbFatalError, + MemoryVectorStoreQuarantineRequiredError +} from '@/memory/infra/vectorStoreErrors' + +export default definePlugin({ + name: 'deepchat-memory-vector', + provides: { harnessMemoryVector: { dispatch: 'async', identity: 'async' } }, + apply(ctx, config) { + const owner = new MemoryVectorOwner(config) + let nextRequest = 1 + const requests = new Map< + number, + { + command: unknown + resolve: (value: unknown) => void + reject: (error: unknown) => void + } + >() + ctx.effect(async () => { + for (const request of requests.values()) + request.reject(new Error('Memory vector generation ended')) + requests.clear() + await owner.dispose() + }) + ctx.provide('harnessMemoryVector', { + identity: () => ({ + generation: config.generation, + pid: process.pid, + node: process.versions.node, + modules: process.versions.modules + }), + dispatch(sequence: number, command: unknown): Promise { + if ( + !Number.isSafeInteger(sequence) || + sequence < nextRequest || + sequence - nextRequest >= MEMORY_VECTOR_PENDING || + requests.has(sequence) + ) { + throw new Error('Invalid memory vector command sequence') + } + return new Promise((resolve, reject) => { + requests.set(sequence, { command, resolve, reject }) + while (requests.has(nextRequest)) { + const request = requests.get(nextRequest)! + requests.delete(nextRequest++) + // Start in wire order; close must fence an opening native handle without waiting for it. + owner.command(request.command).then( + (value) => { + // Poisoned native handles cannot run GC finalizers before the file owner dies. + if ((request.command as MemoryVectorNativeCommand).method === 'quarantine') + process.kill(process.pid, 'SIGKILL') + else request.resolve(value) + }, + (error) => { + if ( + error instanceof MemoryVectorStoreQuarantineRequiredError || + isDuckDbFatalError(error) + ) + process.kill(process.pid, 'SIGKILL') + else request.reject(error) + } + ) + } + }) + } + }) + } +}) diff --git a/src/harness/plugins/memory.ts b/src/harness/plugins/memory.ts new file mode 100644 index 0000000000..4927d9099d --- /dev/null +++ b/src/harness/plugins/memory.ts @@ -0,0 +1,93 @@ +import { definePlugin } from '@arcships/rutis' +import logger from '@shared/logger' +import { MemoryService, type MemoryServicePort } from '@/memory' +import type { MemoryServiceDeps } from '@/memory/types' +import type { MemoryIngestionObserver } from '@/agent/deepchat/memory/memoryIngestionObserver' + +export class MemoryCapability { + private service: MemoryServicePort | undefined + private ingestion: MemoryIngestionObserver | undefined + private retirement: Promise | undefined + + constructor(private readonly signal: AbortSignal) {} + + open(deps: MemoryServiceDeps): MemoryServicePort { + if (this.signal.aborted || this.retirement || this.service) + throw new Error('Memory capability cannot reopen') + // The shared transaction owner must stay local; never serialize repository callbacks. + if (typeof deps?.repository?.runInTransaction !== 'function') + throw new Error('Memory capability requires owner-local Store ports') + return (this.service = new MemoryService(deps)) + } + + bindIngestion(observer: MemoryIngestionObserver): void { + if (this.signal.aborted || this.retirement || !this.service || this.ingestion) + throw new Error('Memory ingestion cannot attach') + this.ingestion = observer + } + + start(): void { + if (this.signal.aborted || this.retirement || !this.service || !this.ingestion) + throw new Error('Memory capability is unavailable') + this.ingestion.resumeIngestion() + this.service.startBackgroundMaintenance() + } + + pause(): void { + this.service?.stopBackgroundMaintenance() + } + + async drain(): Promise { + const ingestion = await this.ingestion?.drainAndFence() + if (ingestion?.timedOut) + throw new Error( + `Memory ingestion did not drain for sessions: ${ingestion.pendingSessions.join(', ')}` + ) + const agents = await this.service?.drainBackgroundMaintenance() + if (agents?.length) + throw new Error(`Memory maintenance did not drain for agents: ${agents.join(', ')}`) + } + + stop(): Promise { + if (this.retirement) return this.retirement + this.retirement = (async () => { + // Fence ingestion before disposing the facade; disposal aborts pending Provider work. + const ingestion = (() => { + try { + return this.ingestion?.drainAndFence() + } catch (error) { + return Promise.reject(error) + } + })() + const outcomes = await Promise.allSettled([this.service?.dispose(), ingestion] as const) + const errors = outcomes.flatMap((outcome) => + outcome.status === 'rejected' ? [outcome.reason] : [] + ) + const drained = outcomes[1] + if (drained.status === 'fulfilled' && drained.value?.timedOut) + logger.warn('[Memory] Late ingestion remains fenced', drained.value) + if (errors.length) throw new AggregateError(errors, 'Memory capability did not retire') + })() + return this.retirement + } +} + +export default definePlugin({ + name: 'deepchat-memory', + provides: { + deepchatMemoryCapability: { + open: 'sync', + bindIngestion: 'sync', + start: 'sync', + pause: 'sync', + drain: 'async', + stop: 'async' + } + }, + apply(ctx) { + const memory = new MemoryCapability(ctx.signal) + ctx.signal.addEventListener('abort', () => void memory.stop().catch(() => {}), { once: true }) + ctx.effect(() => memory.stop()) + ctx.provide('deepchatMemoryCapability', memory) + } +}) diff --git a/src/headless/helper.ts b/src/headless/helper.ts new file mode 100644 index 0000000000..7554807807 --- /dev/null +++ b/src/headless/helper.ts @@ -0,0 +1,287 @@ +import { EventEmitter } from 'node:events' +import { randomUUID } from 'node:crypto' +import { serialize } from 'node:v8' +import { fileURLToPath } from 'node:url' +import { + isMainThread, + parentPort as workerParent, + workerData, + Worker, + type MessagePort +} from 'node:worker_threads' +import { CORE_HELPER_QUEUE, CORE_HELPER_QUEUE_BYTES } from '@shared/harness/helpers' + +let sending = 0 +let sendingBytes = 0 + +function retainMessage(message: unknown): () => void { + const bytes = serialize(message).length + if (sending >= CORE_HELPER_QUEUE || sendingBytes + bytes > CORE_HELPER_QUEUE_BYTES) + throw new Error('Headless helper send queue exceeded') + sending++ + sendingBytes += bytes + let released = false + return () => { + if (released) return + released = true + sending-- + sendingBytes -= bytes + } +} + +function createCodePort(port: MessagePort | Worker, failed: (error: unknown) => void) { + const messages = new EventEmitter() + const sends = new Map void>() + let sequence = 0 + port.on('message', (message: unknown) => { + try { + if ( + !message || + typeof message !== 'object' || + !('id' in message) || + typeof message.id !== 'number' || + !Number.isSafeInteger(message.id) || + message.id <= 0 || + !('type' in message) + ) + throw new Error('Invalid Code worker delivery') + if (message.type === 'received') { + const release = sends.get(message.id) + if (!release) throw new Error('Unknown Code worker delivery receipt') + sends.delete(message.id) + release() + } else if (message.type === 'delivery' && 'value' in message) { + messages.emit('message', message.value) + // Receipts are bounded by the peer's outstanding deliveries and never reach the VM. + port.postMessage({ type: 'received', id: message.id }) + } else throw new Error('Invalid Code worker delivery') + } catch (error) { + failed(error) + } + }) + return { + postMessage(message: unknown) { + let release: (() => void) | undefined + const id = ++sequence + try { + release = retainMessage(message) + sends.set(id, release) + port.postMessage({ type: 'delivery', id, value: message }) + } catch (error) { + sends.delete(id) + release?.() + failed(error) + } + }, + on(_event: 'message', listener: (message: unknown) => void) { + messages.on('message', listener) + } + } +} + +async function runHelper(): Promise { + if (!process.send) throw new Error('Headless helper requires an owned IPC channel') + const kind = process.argv[2] + const messages = new EventEmitter() + const startup = Promise.withResolvers() + const requests = new Map>>() + let shutdown: Promise | undefined + let shutdownId: string | undefined + let shutdownReceipt: ReturnType> | undefined + let worker: Worker | undefined + let hostStarted = false + let failure = false + + const fail = (error: unknown) => { + if (failure) return + failure = true + console.error(error) + requestStop() + } + const parentPort = { + postMessage(message: unknown) { + if ( + message && + typeof message === 'object' && + 'id' in message && + typeof message.id === 'string' + ) { + requests.get(message.id)?.resolve() + requests.delete(message.id) + if (message.id === shutdownId) { + if ('ok' in message && message.ok === false) + shutdownReceipt?.reject(new Error('Headless helper shutdown failed')) + else shutdownReceipt?.resolve() + return + } + } + if (!process.connected) return + let release: (() => void) | undefined + try { + release = retainMessage(message) + const delivered = release + process.send!(message, (error) => { + delivered() + if (error && !shutdown) fail(error) + }) + } catch (error) { + release?.() + fail(error) + } + }, + on(_event: 'message', listener: (message: unknown) => void) { + messages.on('message', listener) + } + } + Object.defineProperty(process, 'parentPort', { value: parentPort }) + + function stop(): Promise { + shutdown ??= Promise.resolve().then(async () => { + await startup.promise + if (worker) await worker.terminate() + else if (hostStarted && (kind === 'exec' || kind === 'content' || kind === 'git')) { + shutdownId = randomUUID() + shutdownReceipt = Promise.withResolvers() + messages.emit('message', { + type: kind === 'exec' ? 'background-exec:request' : 'file-watcher:request', + id: shutdownId, + method: 'shutdown', + args: [] + }) + await shutdownReceipt.promise + await Promise.all([...requests.values()].map((request) => request.promise)) + if (kind === 'exec') { + const { childProcessRegistry } = + await import('@/agent/shared/process/childProcessRegistry') + const recovery = await childProcessRegistry.reapStale('background-exec', { + ownerPid: process.pid + }) + if ( + recovery.refused.length || + recovery.skipped.length || + childProcessRegistry.list('background-exec', process.pid).length + ) + throw new Error('Disconnected headless background execution did not retire') + } + } + process.exit(failure ? 1 : 0) + }) + return shutdown + } + + function requestStop(): void { + void stop().catch((error) => { + console.error(error) + process.exit(1) + }) + } + + process.on('message', (message: unknown) => { + if (shutdown) return + if (message && typeof message === 'object' && 'type' in message) { + if (message.type === 'deepchat-headless:stop') { + requestStop() + return + } + if (message.type === 'deepchat-headless:rss' && 'id' in message) { + parentPort.postMessage({ + type: 'deepchat-headless:rss', + id: message.id, + bytes: process.memoryUsage().rss + }) + return + } + if ( + (message.type === 'background-exec:request' || message.type === 'file-watcher:request') && + 'id' in message && + typeof message.id === 'string' + ) + requests.set(message.id, Promise.withResolvers()) + } + messages.emit('message', message) + }) + process.once('disconnect', requestStop) + process.once('SIGTERM', requestStop) + process.once('SIGINT', requestStop) + + try { + switch (kind) { + case 'exec': { + process.env.DEEPCHAT_EXEC_UTILITY_HOST = '1' + const { runBackgroundExecUtilityHostIfRequested } = + await import('@/agent/shared/process/backgroundExecUtilityHost') + runBackgroundExecUtilityHostIfRequested() + break + } + case 'code': { + // Keep parent IPC responsive when an asynchronous VM continuation blocks its Worker. + worker = new Worker(fileURLToPath(import.meta.url), { + workerData: 'code', + resourceLimits: { maxOldGenerationSizeMb: 64 } + }) + const ready = Promise.withResolvers() + worker.once('error', (error) => { + ready.reject(error) + fail(error) + }) + worker.once('exit', (code) => { + ready.reject(new Error(`Code worker exited during startup: ${code}`)) + if (!shutdown) process.exit(code) + }) + const codePort = createCodePort(worker, fail) + codePort.on('message', (message) => { + parentPort.postMessage(message) + if ( + message && + typeof message === 'object' && + 'type' in message && + message.type === 'READY' + ) + ready.resolve() + }) + messages.on('message', (message) => codePort.postMessage(message)) + await ready.promise + break + } + case 'content': + case 'git': { + process.env.DEEPCHAT_FILE_WATCHER_HOST = '1' + process.env.DEEPCHAT_FILE_WATCHER_HOST_KIND = kind + const { runFileWatcherUtilityHostIfRequested } = + await import('@/platform/fileWatcher/fileWatcherUtilityHost') + runFileWatcherUtilityHostIfRequested() + break + } + case 'scheduler': { + process.env.DEEPCHAT_CRON_SCHEDULER_HOST = '1' + const { runSchedulerUtilityHostIfRequested } = + await import('@/scheduler/schedulerUtilityHost') + runSchedulerUtilityHostIfRequested() + break + } + default: + throw new Error(`Unknown headless helper kind: ${kind}`) + } + hostStarted = true + } catch (error) { + fail(error) + } finally { + startup.resolve() + } + if (!process.connected) requestStop() + else if (!shutdown) parentPort.postMessage({ type: 'deepchat-headless:ready' }) +} + +if (isMainThread) await runHelper() +else { + if (workerData !== 'code' || !workerParent) throw new Error('Invalid headless Code worker') + Object.defineProperty(process, 'parentPort', { + value: createCodePort(workerParent, (error) => { + console.error(error) + process.exit(1) + }) + }) + process.env.DEEPCHAT_CODE_MODE_HOST = '1' + const { runCodeModeUtilityHost } = await import('@/tool/codeMode/codeModeUtilityHost') + runCodeModeUtilityHost() +} diff --git a/src/headless/helpers.ts b/src/headless/helpers.ts new file mode 100644 index 0000000000..ff034205ac --- /dev/null +++ b/src/headless/helpers.ts @@ -0,0 +1,287 @@ +import { fork } from 'node:child_process' +import { EventEmitter } from 'node:events' +import path from 'node:path' +import { fileURLToPath } from 'node:url' +import { randomUUID } from 'node:crypto' +import { serialize } from 'node:v8' +import type { CoreHelperNative } from '@/harness/helpers' +import { + CORE_HELPER_QUEUE, + CORE_HELPER_QUEUE_BYTES, + type CoreHelperKind +} from '@shared/harness/helpers' +import { + ChildProcessRegistry, + observeProcessIdentity +} from '@/agent/shared/process/childProcessRegistry' +import { + terminateProcessTree, + terminateProcessTreeByPid, + waitForClose +} from '@/agent/shared/process/processTree' + +type HelperFactories = ConstructorParameters[0] + +export interface HeadlessHelperOptions { + profileDirectory: string + directory?: string + env?: NodeJS.ProcessEnv + nodeExecutable?: string +} + +export function createHeadlessHelperFactories(options: HeadlessHelperOptions): HelperFactories { + const directory = options.directory ?? path.dirname(fileURLToPath(import.meta.url)) + if (!path.isAbsolute(directory) || !path.isAbsolute(options.profileDirectory)) + throw new Error('Headless helper directories must be absolute') + const entry = path.join(directory, 'helper.mjs') + const environment: NodeJS.ProcessEnv = { + ...(options.env ?? process.env), + DEEPCHAT_USER_DATA_DIR: options.profileDirectory + } + delete environment.DEEPCHAT_E2E_USER_DATA_DIR + const registry = new ChildProcessRegistry({ + rootDir: path.join(options.profileDirectory, 'child-processes') + }) + + async function open(kind: CoreHelperKind, signal: AbortSignal) { + signal.throwIfAborted() + const env: NodeJS.ProcessEnv = {} + if (kind === 'code') { + for (const name of ['LANG', 'LC_ALL', 'TMPDIR', 'TEMP', 'TMP', 'SystemRoot']) { + if (environment[name]) env[name] = environment[name] + } + } else Object.assign(env, environment) + delete env.NODE_OPTIONS + const child = fork(entry, [kind], { + execPath: options.nodeExecutable ?? process.execPath, + execArgv: kind === 'code' ? ['--max-old-space-size=64'] : [], + env, + serialization: 'advanced', + stdio: ['ignore', 'ignore', 'ignore', 'ipc'], + detached: process.platform !== 'win32' + }) + const events = new EventEmitter() + const ready = Promise.withResolvers() + const closed = Promise.withResolvers() + const pendingMessages: unknown[] = [] + const rss = new Map>>() + let exitCode: number | undefined + let ownerIdentity: string | undefined + let retirement: Promise | undefined + let closing = false + let listening = false + let pendingBytes = 0 + let sending = 0 + let sendingBytes = 0 + const pid = child.pid + + child.on('message', (message: unknown) => { + if (message && typeof message === 'object' && 'type' in message) { + if (message.type === 'deepchat-headless:ready') { + ready.resolve() + return + } + if (message.type === 'deepchat-headless:rss' && 'id' in message && 'bytes' in message) { + if (typeof message.id === 'string') { + const request = rss.get(message.id) + if (request) { + const bytes = message.bytes + if (typeof bytes === 'number' && Number.isFinite(bytes) && bytes >= 0) + request.resolve(bytes) + else request.reject(new Error('Invalid headless helper RSS reply')) + } + } + return + } + } + if (listening) events.emit('message', message) + else { + pendingBytes += serialize(message).length + if (pendingMessages.length >= CORE_HELPER_QUEUE || pendingBytes > CORE_HELPER_QUEUE_BYTES) { + fail(new Error('Headless helper startup message queue exceeded')) + } else pendingMessages.push(message) + } + }) + child.once('close', (code) => { + exitCode = code ?? 1 + closed.resolve() + ready.reject(new Error(`Headless ${kind} helper exited during startup: ${exitCode}`)) + for (const request of rss.values()) request.resolve(null) + events.emit('exit', exitCode) + }) + child.on('error', (error) => { + ready.reject(error) + for (const request of rss.values()) request.reject(error) + events.emit('helperError', error.name, error.message) + }) + + const fail = (error: Error) => { + ready.reject(error) + for (const request of rss.values()) request.reject(error) + events.emit('helperError', error.name, error.message) + void terminate().catch((retirementError) => { + events.emit('helperError', 'HeadlessHelperRetirementError', String(retirementError)) + }) + } + const send = (message: unknown) => { + const bytes = serialize(message).length + if (sending >= CORE_HELPER_QUEUE || sendingBytes + bytes > CORE_HELPER_QUEUE_BYTES) + throw new Error('Headless helper send queue exceeded') + sending++ + sendingBytes += bytes + let released = false + const release = () => { + if (released) return + released = true + sending-- + sendingBytes -= bytes + } + try { + child.send(message as Parameters[0], (error) => { + release() + if (error && !closing) fail(error) + }) + } catch (error) { + release() + throw error + } + } + const postMessage = (message: unknown) => { + if (closing || !child.connected) throw new Error('Headless helper is stopping') + try { + send(message) + } catch (error) { + fail(error instanceof Error ? error : new Error('Headless helper send failed')) + throw error + } + } + const terminate = (): Promise => { + closing = true + retirement ??= Promise.resolve().then(async () => { + if (exitCode === undefined) { + let requested = false + if (child.connected) { + try { + send({ type: 'deepchat-headless:stop' }) + requested = true + } catch {} + } + if (!requested || !(await waitForClose(child, 2000))) { + if (!(await terminateProcessTree(child, { nativeIdentity: ownerIdentity }))) + throw new Error(`Headless ${kind} helper did not retire`) + } + } + await closed.promise + if (kind !== 'exec' || !pid) return + if ( + ownerIdentity && + !(await terminateProcessTreeByPid(pid, { + nativeIdentity: ownerIdentity, + ownedGroup: false, + graceMs: 2000 + })) + ) + throw new Error('Headless exec helper descendants did not retire') + const recovery = await registry.reapStale('background-exec', { ownerPid: pid }) + if ( + recovery.refused.length || + recovery.skipped.length || + registry.list('background-exec', pid).length + ) + throw new Error('Owned headless background execution did not retire') + }) + return retirement + } + const onAbort = () => ready.reject(signal.reason) + signal.addEventListener('abort', onAbort, { once: true }) + if (signal.aborted) onAbort() + try { + await ready.promise + signal.throwIfAborted() + if (kind === 'exec' && process.platform === 'darwin') { + if (!pid) throw new Error('Headless exec helper has no owned PID') + const observed = await observeProcessIdentity(pid) + signal.throwIfAborted() + if (!observed.alive || !observed.nativeIdentity || exitCode !== undefined) + throw new Error('Headless exec helper has no native identity') + ownerIdentity = observed.nativeIdentity + } + signal.throwIfAborted() + if (exitCode !== undefined) throw new Error('Headless helper startup was retired') + } catch (error) { + await terminate() + throw error + } finally { + signal.removeEventListener('abort', onAbort) + } + + const on: EventEmitter['on'] = (event, listener) => { + events.on(event === 'error' ? 'helperError' : event, listener) + if (event === 'message') { + listening = true + pendingBytes = 0 + for (const message of pendingMessages.splice(0)) events.emit('message', message) + } else if (event === 'exit' && exitCode !== undefined) { + const code = exitCode + queueMicrotask(() => listener(code)) + } + return events + } + return { + pid, + postMessage, + onMessage: (listener: (message: unknown) => void) => void on('message', listener), + onExit: (listener: (code: number) => void) => void on('exit', listener), + onError: (listener: (kind: string, location: string) => void) => void on('error', listener), + terminate, + events, + on, + async getRssBytes(requestSignal: AbortSignal): Promise { + requestSignal.throwIfAborted() + if (exitCode !== undefined) return null + const id = randomUUID() + const request = Promise.withResolvers() + const abort = () => request.reject(requestSignal.reason) + rss.set(id, request) + requestSignal.addEventListener('abort', abort, { once: true }) + try { + requestSignal.throwIfAborted() + postMessage({ type: 'deepchat-headless:rss', id }) + return await request.promise + } finally { + requestSignal.removeEventListener('abort', abort) + rss.delete(id) + } + } + } + } + + return { + async exec(signal) { + const host = await open('exec', signal) + return { ...host, kill: host.terminate } + }, + async watcher(kind, signal) { + return await open(kind, signal) + }, + async scheduler(signal) { + const host = await open('scheduler', signal) + return { ...host, kill: host.terminate } + }, + async code(signal) { + const host = await open('code', signal) + return { + pid: host.pid, + postMessage: host.postMessage, + kill: host.terminate, + on(event, listener) { + host.on(event, listener) + }, + off(event, listener) { + host.events.off(event === 'error' ? 'helperError' : event, listener) + }, + getRssBytes: host.getRssBytes + } + } + } +} diff --git a/src/headless/index.ts b/src/headless/index.ts new file mode 100644 index 0000000000..44a133c1d7 --- /dev/null +++ b/src/headless/index.ts @@ -0,0 +1,234 @@ +import { mkdtemp, rm } from 'node:fs/promises' +import os from 'node:os' +import path from 'node:path' +import { createInterface } from 'node:readline/promises' +import { fileURLToPath } from 'node:url' +import { CoreRuntimeConnection, createCoreProfileNative } from '@/harness/core' +import { CoreDesktopNative } from '@/harness/desktop' +import { CoreBrowserNative } from '@/harness/browser' +import { CoreHelperNative } from '@/harness/helpers' +import { HarnessRuntime } from '@/harness/runtime' +import { + DEFAULT_AGENT_INVOCATION_CAPACITY, + DEFAULT_AGENT_INVOCATION_MAX_PENDING +} from '@shared/harness/admission' +import type { DeepchatEventPayload } from '@shared/contracts/events' +import { createHeadlessHelperFactories } from './helpers' +import { createHeadlessNative } from './native' +import { loadLocalControlDescriptor } from '../cli/discovery' + +declare const __DEEPCHAT_HOST_VERSION__: string + +async function main(): Promise { + const args = process.argv.slice(2) + if (args.length === 1 && (args[0] === '--help' || args[0] === '-h')) { + process.stdout.write( + 'Usage: node index.mjs --profile \n\n' + + 'Requires Node 24.22.1 or later in the Node 24 series.\n' + + 'DEEPCHAT_HOST_KEY: base64-encoded 32-byte credential master key (required).\n' + + 'DEEPCHAT_HOST_DATABASE_PASSWORD: manual database unlock password (optional).\n' + + 'Use DEEPCHAT_USER_DATA_DIR= node cli/deepchat.mjs .\n' + + 'The host stays running after clients disconnect; SIGINT/SIGTERM drains and stops it.\n' + + 'Sensitive CLI mutations require confirmation on the host terminal.\n' + ) + return + } + if (args.length !== 2 || args[0] !== '--profile' || !args[1]?.trim()) + throw new Error('Usage: node index.mjs --profile ') + const [major, minor, patch] = process.versions.node.split('.').map(Number) + if (process.versions.electron || major !== 24 || minor < 22 || (minor === 22 && patch < 1)) + throw new Error('Headless requires Node >=24.22.1 <25') + const encodedKey = process.env.DEEPCHAT_HOST_KEY + const databasePassword = process.env.DEEPCHAT_HOST_DATABASE_PASSWORD + delete process.env.DEEPCHAT_HOST_KEY + delete process.env.DEEPCHAT_HOST_DATABASE_PASSWORD + const masterKey = Buffer.from(encodedKey ?? '', 'base64') + if (masterKey.length !== 32 || masterKey.toString('base64') !== encodedKey) { + masterKey.fill(0) + throw new Error('DEEPCHAT_HOST_KEY must be a canonical base64-encoded 32-byte key') + } + const directory = path.dirname(fileURLToPath(import.meta.url)) + const profileDirectory = path.resolve(args[1]) + process.env.DEEPCHAT_USER_DATA_DIR = profileDirectory + delete process.env.DEEPCHAT_E2E_USER_DATA_DIR + process.env.DEEPCHAT_HARNESS_EXECUTABLE = path.join( + directory, + 'harness', + `deepchat-harness-${process.platform}-${process.arch}${process.platform === 'win32' ? '.exe' : ''}` + ) + const temporaryDirectory = await mkdtemp(path.join(os.tmpdir(), 'deepchat-host-')) + const stopped = Promise.withResolvers() + let runtime: HarnessRuntime | undefined + let connection: CoreRuntimeConnection | undefined + let helpers: CoreHelperNative | undefined + let desktop: CoreDesktopNative | undefined + let browser: CoreBrowserNative | undefined + let stopping: Promise | undefined + let stopRequested = false + let promptActive = false + const requestApproval = async ( + payload: DeepchatEventPayload<'approvals.requested'>, + signal: AbortSignal + ): Promise<'approved' | 'denied'> => { + if (promptActive) throw new Error('The host terminal already has a pending approval') + promptActive = true + let terminal: ReturnType | undefined + try { + terminal = createInterface({ input: process.stdin, output: process.stderr }) + const failure = new AbortController() + terminal.on('error', (error) => failure.abort(error)) + const deadline = AbortSignal.timeout(Math.max(1, payload.expiresAt - Date.now())) + const answer = await terminal.question( + `${JSON.stringify(payload)}\nType approve ${payload.requestId} to allow: `, + { signal: AbortSignal.any([signal, deadline, failure.signal]) } + ) + return answer.trim() === `approve ${payload.requestId}` ? 'approved' : 'denied' + } finally { + terminal?.close() + process.stdin.pause() + promptActive = false + } + } + const native = (() => { + try { + return createHeadlessNative({ + profileDirectory, + temporaryDirectory, + masterKey, + databasePassword, + publishEvent() {}, + requestApproval: process.stdin.isTTY && process.stderr.isTTY ? requestApproval : undefined, + quit: async () => { + throw new Error('Profile startup requires manual recovery; the profile was preserved') + } + }) + } finally { + masterKey.fill(0) + } + })() + const stop = (): Promise => { + if (stopping) return stopping + stopRequested = true + stopping = (async () => { + const errors: unknown[] = [] + const retire = async (action: () => unknown) => { + try { + await action() + } catch (error) { + errors.push(error) + } + } + await retire(() => connection?.stop()) + await retire(() => helpers?.close()) + await retire(() => browser?.close()) + await retire(() => desktop?.close()) + await retire(() => runtime?.stop()) + await retire(() => connection?.closeAfterHostExit()) + await retire(() => native.close()) + await retire(() => rm(temporaryDirectory, { recursive: true, force: true })) + if (errors.length) { + process.exitCode = 1 + process.stderr.write('Headless shutdown encountered an owner retirement failure\n') + } + stopped.resolve() + })() + return stopping + } + const failed = (error: Error) => { + if (stopRequested) return + stopRequested = true + process.exitCode = 1 + process.stderr.write(`Headless Core failed: ${error.message}\n`) + void stop() + } + const onSignal = () => { + stopRequested = true + if (runtime) void stop() + } + process.once('SIGINT', onSignal) + process.once('SIGTERM', onSignal) + try { + runtime = await HarnessRuntime.start({ + directory: path.join(directory, 'harness'), + electronExecutable: process.execPath, + capacity: DEFAULT_AGENT_INVOCATION_CAPACITY, + maxPending: DEFAULT_AGENT_INVOCATION_MAX_PENDING, + observe() {} + }) + if (stopRequested) throw new Error('Headless startup was stopped') + helpers = new CoreHelperNative( + createHeadlessHelperFactories({ directory, profileDirectory }), + async (events) => await connection!.deliverNativeEvents(events), + failed + ) + desktop = new CoreDesktopNative( + native.kernel, + async (event) => await connection!.deliverDesktopEvent(event), + native.close, + failed + ) + browser = new CoreBrowserNative( + { + getToolDefinitions: () => [], + callTool: async () => { + throw new Error('Desktop browser capability is unavailable on a headless host') + } + }, + async (event) => await connection!.deliverBrowserEvent(event), + async () => {}, + failed + ) + const profileNative = createCoreProfileNative(native.crypto, native.profileHost) + connection = await CoreRuntimeConnection.connect({ + runtime, + temporaryDirectory, + onFailure: failed, + native: async (method, input) => { + if (method === 'core.failure') + throw new Error('Headless Core reported a capability failure') + if (method === 'helpers') return await helpers!.invoke(input) + if (method === 'desktop') return await desktop!.invoke(input) + if (method === 'browser') return await browser!.invoke(input) + if (method === 'configuration') return await native.configuration(method, input) + return await profileNative(method, input) + } + }) + if (stopping) throw new Error('Headless startup was stopped') + await connection.openProfile({ + userDataDirectory: profileDirectory, + homeDirectory: os.homedir(), + currentAppVersion: __DEEPCHAT_HOST_VERSION__, + systemLocale: Intl.DateTimeFormat().resolvedOptions().locale, + appPath: directory, + isPackaged: false + }) + await connection.configure({ operation: 'initialize' }) + await connection.assembleKernel({ + temporaryDirectory, + appDataDirectory: profileDirectory, + documentsDirectory: null, + projectHomeDirectory: os.homedir(), + resourcesPath: path.join(directory, 'resources'), + workingDirectory: process.cwd(), + builtinSkillsDirectories: [path.join(directory, 'resources', 'skills')], + providerCatalogPath: path.join(directory, 'resources', 'model-db', 'providers.json'), + allowInsecureTls: false + }) + const status = await connection.startKernel() + const descriptor = await loadLocalControlDescriptor() + if (descriptor.pid !== status.pid) throw new Error('Headless local control owner did not match') + process.stdout.write( + `${JSON.stringify({ ready: true, pid: process.pid, harnessPid: runtime.identity?.hostPid, profileDirectory })}\n` + ) + await stopped.promise + } finally { + await stop() + process.removeListener('SIGINT', onSignal) + process.removeListener('SIGTERM', onSignal) + } +} + +void main().catch((error: unknown) => { + process.exitCode = 1 + process.stderr.write(`${error instanceof Error ? error.message : 'Headless startup failed'}\n`) +}) diff --git a/src/headless/native.ts b/src/headless/native.ts new file mode 100644 index 0000000000..56140e48de --- /dev/null +++ b/src/headless/native.ts @@ -0,0 +1,338 @@ +import { createCipheriv, createDecipheriv, hkdfSync, randomBytes } from 'node:crypto' +import path from 'node:path' +import type { CoreKernelNative } from '../harness/core/kernel' +import type { CoreProfileHost } from '../main/app/profileStartup' +import type { DatabaseSecurityCryptoPort } from '../main/app/databaseSecurity' +import { createCoreConfigurationNative } from '../main/harness/configuration' +import { cacheImage } from '../main/platform/imageCache' +import { isAcpRegistryIconUrl } from '../main/agent/acp/catalog/acpRegistryConstants' +import { setLegacyConsoleDebugEnabled } from '../shared/logger' +import type { DeepchatEventPayload, DeepchatEventPublisher } from '../shared/contracts/events' +import type { NativeConfigurationSnapshot } from '../shared/types/nativeConfiguration' +import type { CoreProxySnapshot } from '../shared/harness/desktop' + +export interface HeadlessNativeOptions { + profileDirectory: string + temporaryDirectory: string + masterKey: Uint8Array + databasePassword?: string + publishEvent: DeepchatEventPublisher + publishNativeSnapshot?(snapshot: NativeConfigurationSnapshot): void | Promise + requestApproval?( + payload: DeepchatEventPayload<'approvals.requested'>, + signal: AbortSignal + ): Promise<'approved' | 'denied'> + quit?(): void | Promise +} + +const CREDENTIAL_PREFIX = 'headless:v1:' +const CREDENTIAL_CONTEXT = Buffer.from('deepchat.headless.credentials.v1') + +export function createHeadlessNative({ + masterKey, + databasePassword: initialDatabasePassword, + ...options +}: HeadlessNativeOptions) { + if (!path.isAbsolute(options.profileDirectory) || !path.isAbsolute(options.temporaryDirectory)) + throw new Error('Headless Native directories must be absolute') + if (!(masterKey instanceof Uint8Array) || masterKey.byteLength !== 32) + throw new Error('Headless Native requires a 32-byte master key') + + const credentialKey = Buffer.from( + hkdfSync('sha256', masterKey, CREDENTIAL_CONTEXT, 'credentials', 32) + ) + const ocrKey = Buffer.from(hkdfSync('sha256', masterKey, CREDENTIAL_CONTEXT, 'ocr-cache', 32)) + const lifetime = new AbortController() + const pending = new Set>() + const configurationCalls = new Set>() + let closing: Promise | undefined + let databasePassword = initialDatabasePassword + let theme: 'dark' | 'light' | 'system' = 'system' + const inheritedProxy = + process.env.https_proxy ?? + process.env.HTTPS_PROXY ?? + process.env.http_proxy ?? + process.env.HTTP_PROXY ?? + null + const inheritedNoProxy = process.env.no_proxy ?? process.env.NO_PROXY ?? '' + let proxy: CoreProxySnapshot = { version: 0, proxyUrl: null, noProxy: inheritedNoProxy } + + const assertActive = () => lifetime.signal.throwIfAborted() + const unavailable = (): never => { + assertActive() + throw new Error('Desktop capability is unavailable in headless mode') + } + const noPresentation = () => { + assertActive() + } + const emptyCleanup = async () => {} + const active = async () => assertActive() + const perform = (effect: (signal: AbortSignal) => Promise, signal?: AbortSignal) => { + assertActive() + const operationSignal = signal ? AbortSignal.any([lifetime.signal, signal]) : lifetime.signal + const operation = Promise.resolve().then(async () => { + operationSignal.throwIfAborted() + const result = await effect(operationSignal) + operationSignal.throwIfAborted() + return result + }) + pending.add(operation) + void operation.then( + () => pending.delete(operation), + () => pending.delete(operation) + ) + return operation + } + + const crypto: DatabaseSecurityCryptoPort = { + isAvailable: async () => !lifetime.signal.aborted, + getBackend: async () => { + assertActive() + return 'headless-aes-256-gcm' + }, + encrypt: async (value) => { + assertActive() + const nonce = randomBytes(12) + const cipher = createCipheriv('aes-256-gcm', credentialKey, nonce) + cipher.setAAD(CREDENTIAL_CONTEXT) + const ciphertext = Buffer.concat([cipher.update(value, 'utf8'), cipher.final()]) + return ( + CREDENTIAL_PREFIX + + Buffer.concat([nonce, cipher.getAuthTag(), ciphertext]).toString('base64url') + ) + }, + decrypt: async (wrapped) => { + assertActive() + if (!wrapped.startsWith(CREDENTIAL_PREFIX)) + throw new Error('Unsupported headless credential envelope') + const encoded = wrapped.slice(CREDENTIAL_PREFIX.length) + const envelope = Buffer.from(encoded, 'base64url') + if (envelope.byteLength < 28 || envelope.toString('base64url') !== encoded) + throw new Error('Invalid headless credential envelope') + try { + const decipher = createDecipheriv('aes-256-gcm', credentialKey, envelope.subarray(0, 12)) + decipher.setAAD(CREDENTIAL_CONTEXT) + decipher.setAuthTag(envelope.subarray(12, 28)) + return Buffer.concat([decipher.update(envelope.subarray(28)), decipher.final()]).toString( + 'utf8' + ) + } catch { + throw new Error('Headless credential authentication failed') + } + } + } + const applyLogging = async (enabled: boolean) => { + assertActive() + setLegacyConsoleDebugEnabled(enabled) + } + const profileHost: CoreProfileHost = { + setLoggingEnabled: applyLogging, + showDatabaseUnlockProgress: async () => assertActive(), + requestDatabaseUnlock: async ({ reason }) => { + assertActive() + const password = databasePassword + databasePassword = undefined + if (!password || reason === 'invalid') + throw new Error('Headless database unlock requires an explicit valid database password') + return password + }, + requestDatabaseRecovery: async () => { + assertActive() + throw new Error('Database recovery requires an explicit offline recovery operation') + }, + quit: async () => { + assertActive() + await options.quit?.() + } + } + const configure = createCoreConfigurationNative({ + desktop: { + systemLocale: Intl.DateTimeFormat().resolvedOptions().locale, + readTheme: async () => ({ theme, isDark: theme === 'dark' }), + applyTheme: async (value) => { + theme = value + return theme === 'dark' + }, + refreshLanguage: active, + refreshTheme: active, + readLaunchAtLogin: async () => false, + setLaunchAtLogin: unavailable, + applyContentProtection: async (enabled) => { + if (enabled) unavailable() + }, + applyFloatingButtonEnabled: async (enabled) => { + if (enabled) unavailable() + } + }, + logging: { applyLogging, openLoggingFolder: unavailable }, + enumerateFonts: async () => [], + publishNativeSnapshot: (snapshot) => options.publishNativeSnapshot?.(snapshot), + publishEvent: options.publishEvent + }) + const configuration: typeof configure = (method, args) => { + const operation = perform(async () => await configure(method, args)) + configurationCalls.add(operation) + void operation.then( + () => configurationCalls.delete(operation), + () => configurationCalls.delete(operation) + ) + return operation + } + const kernel: CoreKernelNative = { + // There is no Native settings file mirror or renderer writer in this host. + configurationHost: { + pause: active, + resume: active, + stopWrites: active, + drainWrites: async () => { + await Promise.all(configurationCalls) + }, + startWrites: active + }, + isSettingsWindow: async () => false, + isMainWindowContext: async () => false, + rendererPerformance: { record: async () => false }, + splash: { showDebugScenario: unavailable, closeDebugScenario: async () => false }, + images: { saveImage: unavailable, copyImage: unavailable }, + pickToolchainPath: unavailable, + hasTrustedRenderer: async () => false, + applyProxy: async (mode, url) => { + assertActive() + let proxyUrl = mode === 'none' ? null : mode === 'custom' ? url : inheritedProxy + if (proxyUrl) { + try { + const parsed = new URL(proxyUrl) + if (!['http:', 'https:'].includes(parsed.protocol)) throw new Error() + proxyUrl = parsed.href + } catch { + throw new Error('Invalid headless HTTP proxy URL') + } + } + proxy = { version: proxy.version + 1, proxyUrl: proxyUrl || null, noProxy: inheritedNoProxy } + return proxy.proxyUrl !== null + }, + copilotAuthorization: { browser: unavailable, device: unavailable }, + sessionPresentation: { + focusBrowser: unavailable, + setComputerUsePreview: unavailable, + dismissComputerUsePreview: async () => false + }, + workspaceDesktop: { + revealFile: unavailable, + openFile: unavailable, + listFileOpenApps: async () => [], + openFileWithApp: unavailable + }, + approvalPresentation: { + getTarget: async () => (options.requestApproval ? { host: 'terminal' } : null), + present: async (target, payload, signal) => { + if (!('host' in target) || target.host !== 'terminal' || !options.requestApproval) + unavailable() + return await perform( + async (operationSignal) => await options.requestApproval!(payload, operationSignal), + signal + ) + }, + close: emptyCleanup + }, + ocrCacheKeyProvider: { + loadOrCreateKey: async () => { + assertActive() + return Buffer.from(ocrKey) + } + }, + initializeFloatingButton: active, + assertSyncTokenStorageAvailable: active, + semanticNotifications: { + occur: (intent) => { + assertActive() + console.warn(`[Headless] ${intent.code}`) + }, + recover: noPresentation + }, + secureCrypto: crypto, + providerCrypto: crypto, + getProxySnapshot: async () => { + assertActive() + return { ...proxy } + }, + openExternal: unavailable, + openPath: unavailable, + selectDirectory: unavailable, + cacheImage: (data, input) => + perform(async (signal) => { + if (process.env.DEEPCHAT_USER_DATA_DIR !== options.profileDirectory) + throw new Error('Headless image cache profile does not match bootstrap') + return await cacheImage(data, { ...input, signal }) + }, input?.signal), + agentDesktop: { + tempDirectory: options.temporaryDirectory, + profileTempDirectory: path.join(options.profileDirectory, 'temp'), + readImageSize: (filePath, signal) => + perform(async (operationSignal) => { + const { default: sharp } = await import('sharp') + operationSignal.throwIfAborted() + const { width, height } = await sharp(filePath).metadata() + if (!width || !height) throw new Error('Image dimensions are unavailable') + return { width, height } + }, signal), + createSettingsWindow: unavailable, + sendSettingsNavigation: async () => false + }, + pluginSettingsWindow: { + open: unavailable, + close: emptyCleanup, + closeAll: emptyCleanup, + getPluginIdForWebContents: async () => null + }, + remoteAuthWindows: { open: unavailable, close: emptyCleanup, closeAll: emptyCleanup }, + remoteSessionWindow: { select: async () => null, present: unavailable }, + showNotification: unavailable, + mcpPreview: undefined, + sessionUiPort: { refreshSessionUi: active }, + mcpReadinessChanged: noPresentation, + reportStartupFailure: (component) => { + assertActive() + console.warn(`[Headless] Startup failed: ${component}`) + }, + onSessionInactive: noPresentation, + renderer: { + broadcast: (event) => { + assertActive() + options.publishEvent(event.name, event.payload) + }, + send() {} + }, + onRendererDestroyed: () => () => {}, + sendToRenderer() {}, + destroySessionBrowser: emptyCleanup, + observeDelegation: noPresentation, + runJournalObserver: undefined, + notifySessionCompletion: active, + fetchRegistryIcon: (url, signal) => + perform(async (operationSignal) => { + if (!isAcpRegistryIconUrl(url)) throw new Error('Unsupported ACP registry icon URL') + const response = await fetch(url, { signal: operationSignal, redirect: 'error' }) + if (!response.ok) throw new Error(`Failed to fetch ACP registry icon: ${response.status}`) + const text = await response.text() + if (text.length > 1024 * 1024) throw new Error('ACP registry icon exceeds the size limit') + return text + }, signal), + mcpAppDesktop: { observeOwner: unavailable, openExternal: unavailable }, + publishMcpConsent: unavailable + } + + const close = (): Promise => { + if (closing) return closing + closing = Promise.resolve().then(async () => { + await Promise.allSettled(pending) + }) + lifetime.abort(new Error('Headless Native is stopped')) + credentialKey.fill(0) + ocrKey.fill(0) + databasePassword = undefined + return closing + } + + return { kernel, crypto, profileHost, configuration, close } +} diff --git a/src/main/agent/acp/catalog/acpRegistryConstants.ts b/src/main/agent/acp/catalog/acpRegistryConstants.ts index 605197fc1d..6cb62730ca 100644 --- a/src/main/agent/acp/catalog/acpRegistryConstants.ts +++ b/src/main/agent/acp/catalog/acpRegistryConstants.ts @@ -5,12 +5,6 @@ export const ACP_REGISTRY_ICON_PREFIX = 'https://cdn.agentclientprotocol.com/reg export const ACP_REGISTRY_CACHE_TTL_MS = 60 * 60 * 1000 -export const ACP_REGISTRY_RESOURCE_DIR = ['resources', 'acp-registry'] as const - -export const ACP_REGISTRY_RESOURCE_PATH = ['resources', 'acp-registry', 'registry.json'] as const - -export const ACP_REGISTRY_ICON_RESOURCE_DIR = ['resources', 'acp-registry', 'icons'] as const - export const ACP_REGISTRY_ICON_CACHE_DIRNAME = 'icons' const ACP_REGISTRY_FILE_SEGMENT_PATTERN = /^[A-Za-z0-9._-]+$/ diff --git a/src/main/agent/acp/catalog/acpRegistryService.ts b/src/main/agent/acp/catalog/acpRegistryService.ts index 67a9b3041b..4f294541e4 100644 --- a/src/main/agent/acp/catalog/acpRegistryService.ts +++ b/src/main/agent/acp/catalog/acpRegistryService.ts @@ -1,6 +1,5 @@ import fs from 'fs' import path from 'path' -import { app, net } from 'electron' import type { AcpRegistryAgent } from '@shared/types/acp' import type { AcpRegistryBinaryDistribution, @@ -11,9 +10,6 @@ import { SVGSanitizer } from '@/lib/svgSanitizer' import { ACP_REGISTRY_CACHE_TTL_MS, ACP_REGISTRY_ICON_CACHE_DIRNAME, - ACP_REGISTRY_ICON_RESOURCE_DIR, - ACP_REGISTRY_RESOURCE_DIR, - ACP_REGISTRY_RESOURCE_PATH, ACP_REGISTRY_URL, getAcpRegistryIconFileName, isAcpRegistryIconUrl @@ -31,7 +27,10 @@ type RegistryManifest = { agents: AcpRegistryAgent[] } -type AcpRegistryServiceOptions = { +export type AcpRegistryServiceOptions = { + userDataDirectory: string + builtInDirectory: string + fetchIcon: (url: string, signal: AbortSignal) => Promise isPrivacyModeEnabled?: () => boolean } @@ -218,11 +217,15 @@ export class AcpRegistryService { private readonly iconMarkupCache = new Map>() private readonly isPrivacyModeEnabled: () => boolean private manifest: RegistryManifest | null = null + private readonly stopController = new AbortController() + private refreshPromise: Promise | null = null - constructor(options: AcpRegistryServiceOptions = {}) { + constructor(private readonly options: AcpRegistryServiceOptions) { + if (!path.isAbsolute(options.userDataDirectory) || !path.isAbsolute(options.builtInDirectory)) { + throw new Error('ACP registry directories must be absolute') + } this.isPrivacyModeEnabled = options.isPrivacyModeEnabled ?? (() => false) - const userDataPath = app.getPath('userData') - this.cacheDir = path.join(userDataPath, 'acp-registry') + this.cacheDir = path.join(options.userDataDirectory, 'acp-registry') this.cacheFilePath = path.join(this.cacheDir, 'registry.json') this.metaFilePath = path.join(this.cacheDir, 'meta.json') this.iconCacheDir = path.join(this.cacheDir, ACP_REGISTRY_ICON_CACHE_DIRNAME) @@ -235,18 +238,25 @@ export class AcpRegistryService { } } - async initialize(): Promise { + initialize(): void { + if (this.stopController.signal.aborted) return this.manifest = this.loadFromBuiltIn() ?? this.loadFromCache() const cached = this.loadFromCache() if (cached) { this.manifest = cached } + } - if (this.isAutomaticRefreshBlocked()) { - return - } + stop(): void { + this.stopController.abort() + } + + get isStopped(): boolean { + return this.stopController.signal.aborted + } - await this.refreshIfNeeded(false, { automatic: true }) + async drain(): Promise { + await Promise.allSettled([this.refreshPromise]) } listAgents(): AcpRegistryAgent[] { @@ -258,8 +268,11 @@ export class AcpRegistryService { return agent ? clone(agent) : null } - async refresh(force = false): Promise { - await this.refreshIfNeeded(force) + async refresh( + force = false, + options: { automatic?: boolean; signal?: AbortSignal } = {} + ): Promise { + await this.refreshIfNeeded(force, options) return this.listAgents() } @@ -270,6 +283,8 @@ export class AcpRegistryService { return null } + if (this.stopController.signal.aborted) return null + if (normalizedIconUrl && !isAcpRegistryIconUrl(normalizedIconUrl)) { return null } @@ -284,13 +299,15 @@ export class AcpRegistryService { this.iconMarkupCache.set(normalizedAgentId, pending) } - return await pending + const markup = await pending + return this.stopController.signal.aborted ? null : markup } private getManifest(): RegistryManifest { if (this.manifest) { return this.manifest } + this.stopController.signal.throwIfAborted() this.manifest = this.loadFromCache() ?? this.loadFromBuiltIn() if (!this.manifest) { @@ -331,21 +348,13 @@ export class AcpRegistryService { } private loadFromBuiltIn(): RegistryManifest | null { - for (const candidate of this.getBuiltInManifestCandidatePaths()) { - try { - if (!fs.existsSync(candidate)) { - continue - } - const parsed = JSON.parse(fs.readFileSync(candidate, 'utf-8')) - const normalized = normalizeManifest(parsed) - if (normalized) { - return normalized - } - } catch (error) { - console.warn('[ACP Registry] Failed to load built-in snapshot:', candidate, error) - } + const file = path.join(this.options.builtInDirectory, 'registry.json') + try { + if (!fs.existsSync(file)) return null + return normalizeManifest(JSON.parse(fs.readFileSync(file, 'utf-8'))) + } catch (error) { + console.warn('[ACP Registry] Failed to load built-in snapshot:', file, error) } - return null } @@ -353,12 +362,17 @@ export class AcpRegistryService { force: boolean, options: { automatic?: boolean + signal?: AbortSignal } = {} ): Promise { + this.stopController.signal.throwIfAborted() + options.signal?.throwIfAborted() if (options.automatic && this.isAutomaticRefreshBlocked()) { return } + if (this.refreshPromise) return this.refreshPromise + const meta = this.readMeta() const now = Date.now() const expired = !meta || now - meta.lastUpdated > ACP_REGISTRY_CACHE_TTL_MS @@ -367,7 +381,10 @@ export class AcpRegistryService { return } - await this.fetchAndCache(meta) + this.refreshPromise = this.fetchAndCache(meta, options.signal).finally(() => { + this.refreshPromise = null + }) + await this.refreshPromise } private isAutomaticRefreshBlocked(): boolean { @@ -378,15 +395,28 @@ export class AcpRegistryService { } } - private async fetchAndCache(previousMeta: RegistryCacheMeta | null): Promise { + private async fetchAndCache( + previousMeta: RegistryCacheMeta | null, + callerSignal?: AbortSignal + ): Promise { const now = Date.now() const controller = new AbortController() const timeout = setTimeout(() => controller.abort(), 15000) + const signal = AbortSignal.any([ + this.stopController.signal, + controller.signal, + ...(callerSignal ? [callerSignal] : []) + ]) + const assertCurrent = () => { + this.stopController.signal.throwIfAborted() + callerSignal?.throwIfAborted() + } try { const response = await fetch(ACP_REGISTRY_URL, { - signal: controller.signal + signal }) + assertCurrent() if (!response.ok) { if (previousMeta) { @@ -396,6 +426,7 @@ export class AcpRegistryService { } const text = await response.text() + assertCurrent() if (!text || text.length > 5 * 1024 * 1024) { if (previousMeta) { this.writeMeta({ ...previousMeta, lastAttemptedAt: now }) @@ -411,15 +442,17 @@ export class AcpRegistryService { return } - const tmpPath = `${this.cacheFilePath}.tmp` - fs.writeFileSync(tmpPath, JSON.stringify(normalized, null, 2), 'utf-8') - fs.renameSync(tmpPath, this.cacheFilePath) - try { - await this.syncIconCache(normalized) + await this.syncIconCache(normalized, signal) } catch (error) { + assertCurrent() console.warn('[ACP Registry] Failed to sync icon cache after manifest refresh:', error) } + assertCurrent() + + const tmpPath = `${this.cacheFilePath}.tmp` + fs.writeFileSync(tmpPath, JSON.stringify(normalized, null, 2), 'utf-8') + fs.renameSync(tmpPath, this.cacheFilePath) this.writeMeta({ version: normalized.version, @@ -430,6 +463,7 @@ export class AcpRegistryService { this.manifest = normalized } catch (error) { + assertCurrent() if (previousMeta) { this.writeMeta({ ...previousMeta, lastAttemptedAt: now }) } @@ -439,26 +473,6 @@ export class AcpRegistryService { } } - private getBuiltInResourceRoots(): string[] { - return [ - path.join(app.getAppPath(), ...ACP_REGISTRY_RESOURCE_DIR), - path.join(process.cwd(), ...ACP_REGISTRY_RESOURCE_DIR) - ] - } - - private getBuiltInManifestCandidatePaths(): string[] { - return this.getBuiltInResourceRoots().map((root) => - path.join(root, path.basename(ACP_REGISTRY_RESOURCE_PATH.at(-1)!)) - ) - } - - private getBuiltInIconCandidatePaths(agentId: string): string[] { - const filename = getAcpRegistryIconFileName(agentId) - return this.getBuiltInResourceRoots().map((root) => - path.join(root, path.basename(ACP_REGISTRY_ICON_RESOURCE_DIR.at(-1)!), filename) - ) - } - private getCachedIconPath(agentId: string): string { return path.join(this.iconCacheDir, getAcpRegistryIconFileName(agentId)) } @@ -469,13 +483,12 @@ export class AcpRegistryService { return cachedPath } - for (const candidate of this.getBuiltInIconCandidatePaths(agentId)) { - if (fs.existsSync(candidate)) { - return candidate - } - } - - return null + const builtInPath = path.join( + this.options.builtInDirectory, + ACP_REGISTRY_ICON_CACHE_DIRNAME, + getAcpRegistryIconFileName(agentId) + ) + return fs.existsSync(builtInPath) ? builtInPath : null } private async loadIconMarkupFromDisk(agentId: string): Promise { @@ -497,7 +510,8 @@ export class AcpRegistryService { } } - private async syncIconCache(manifest: RegistryManifest): Promise { + private async syncIconCache(manifest: RegistryManifest, signal: AbortSignal): Promise { + signal.throwIfAborted() fs.mkdirSync(this.iconCacheDir, { recursive: true }) const cacheableAgents = manifest.agents.filter( @@ -505,12 +519,13 @@ export class AcpRegistryService { ) const expectedAgentIds = new Set(cacheableAgents.map((agent) => agent.id)) - await Promise.allSettled(cacheableAgents.map((agent) => this.syncAgentIcon(agent))) + await Promise.allSettled(cacheableAgents.map((agent) => this.syncAgentIcon(agent, signal))) + signal.throwIfAborted() this.pruneStaleIconCache(expectedAgentIds) this.iconMarkupCache.clear() } - private async syncAgentIcon(agent: AcpRegistryAgent): Promise { + private async syncAgentIcon(agent: AcpRegistryAgent, refreshSignal: AbortSignal): Promise { const iconUrl = agent.icon?.trim() if (!iconUrl || !isAcpRegistryIconUrl(iconUrl)) { return @@ -518,17 +533,11 @@ export class AcpRegistryService { const controller = new AbortController() const timeout = setTimeout(() => controller.abort(), 10000) + const signal = AbortSignal.any([refreshSignal, controller.signal]) try { - const response = await net.fetch(iconUrl, { - signal: controller.signal - }) - - if (!response.ok) { - throw new Error(`Failed to fetch icon: ${response.status} ${response.statusText}`) - } - - const text = await response.text() + const text = await this.options.fetchIcon(iconUrl, signal) + signal.throwIfAborted() const sanitized = this.svgSanitizer.sanitize(text) if (!sanitized) { throw new Error(`Failed to sanitize icon for agent ${agent.id}`) diff --git a/src/main/agent/acp/catalog/settings.ts b/src/main/agent/acp/catalog/settings.ts index 0f115bac06..2bf674fb3b 100644 --- a/src/main/agent/acp/catalog/settings.ts +++ b/src/main/agent/acp/catalog/settings.ts @@ -1,4 +1,3 @@ -import ElectronStore from 'electron-store' import { nanoid } from 'nanoid' import type { AcpAgentConfig } from '@shared/types/acp' import type { @@ -10,7 +9,7 @@ import type { AcpLegacyBuiltinAgentId, AcpManualAgent } from '@shared/types/acp' -import { McpSettings } from '@/mcp/settings' +import type { McpSettings } from '@/mcp/settings' import { ACP_LEGACY_AGENT_ID_ALIASES, resolveAcpAgentAlias } from '@shared/utils/acpAgentAlias' import type { StoreLike } from '@/config/storeLike' import type { AgentDatabase } from '@/agent/data/database' @@ -166,23 +165,27 @@ const getActiveLegacyProfile = (agent: AcpBuiltinAgent): AcpAgentProfile | null ) } +export function createAcpCatalogDefaults(): InternalStore { + return { + enabled: false, + version: ACP_STORE_VERSION, + registryStates: {}, + manualAgents: [], + installStates: {}, + sharedMcpSelections: [] + } +} + export class AcpCatalogSettings { private store: StoreLike> - private readonly mcpSettings: McpSettings + private readonly mcpSettings: Pick - constructor(options: { mcpSettings: McpSettings }) { + constructor(options: { + mcpSettings: Pick + store: StoreLike> + }) { this.mcpSettings = options.mcpSettings - this.store = new ElectronStore({ - name: 'acp_agents', - defaults: { - enabled: false, - version: ACP_STORE_VERSION, - registryStates: {}, - manualAgents: [], - installStates: {}, - sharedMcpSelections: [] - } - }) + this.store = options.store this.ensureStoreInitialized() } @@ -195,10 +198,9 @@ export class AcpCatalogSettings { } connectDatabase(database: AgentDatabase): void { - this.store = new AcpDbStore( - this.store as StoreLike>, - () => database.catalogSettingsTable - ) as StoreLike> + this.store = new AcpDbStore(this.store.store, () => database.catalogSettingsTable) as StoreLike< + InternalStore & Record + > } getGlobalEnabled(): boolean { @@ -242,36 +244,6 @@ export class AcpCatalogSettings { return state ? clone(state) : null } - setAgentEnabled(agentId: string, enabled: boolean): void { - const resolvedId = resolveAcpAgentAlias(agentId) - const manualAgents = this.getManualAgents() - const manualIndex = manualAgents.findIndex((agent) => agent.id === resolvedId) - if (manualIndex !== -1) { - manualAgents[manualIndex].enabled = enabled - this.store.set('manualAgents', manualAgents) - return - } - - const states = this.getRegistryStates() - states[resolvedId] = normalizeRegistryState(resolvedId, states[resolvedId], { enabled }) - states[resolvedId].enabled = enabled - states[resolvedId].updatedAt = Date.now() - this.store.set('registryStates', states) - } - - setAgentEnvOverride(agentId: string, env: Record): void { - const resolvedId = resolveAcpAgentAlias(agentId) - const states = this.getRegistryStates() - states[resolvedId] = normalizeRegistryState(resolvedId, states[resolvedId]) - states[resolvedId].envOverride = normalizeEnv(env) - states[resolvedId].updatedAt = Date.now() - this.store.set('registryStates', states) - } - - getAgentEnvOverride(agentId: string): Record | undefined { - return this.getRegistryStates()[resolveAcpAgentAlias(agentId)]?.envOverride - } - getManualAgents(): AcpManualAgent[] { const raw = this.store.get('manualAgents') if (!Array.isArray(raw)) { @@ -295,80 +267,11 @@ export class AcpCatalogSettings { this.store.set('sharedMcpSelections', validated) } - addManualAgent(agent: Omit & { id?: string }): AcpManualAgent { - const normalized = normalizeManualAgent( - { - ...agent, - id: agent.id ?? nanoid(8) - }, - { enabled: agent.enabled } - ) - - if (!normalized) { - throw new Error('Invalid ACP manual agent payload') - } - - const manualAgents = this.getManualAgents() - const next = manualAgents.filter((item) => item.id !== normalized.id) - next.push(normalized) - this.store.set('manualAgents', next) - return normalized - } - - updateManualAgent( - agentId: string, - updates: Partial> - ): AcpManualAgent | null { - const manualAgents = this.getManualAgents() - const index = manualAgents.findIndex((agent) => agent.id === agentId) - if (index === -1) { - return null - } - - const normalized = normalizeManualAgent( - { - ...manualAgents[index], - ...updates, - id: agentId - }, - { enabled: updates.enabled ?? manualAgents[index].enabled } - ) - - if (!normalized) { - return null - } - - manualAgents[index] = normalized - this.store.set('manualAgents', manualAgents) - return normalized - } - - removeManualAgent(agentId: string): boolean { - const manualAgents = this.getManualAgents() - const next = manualAgents.filter((agent) => agent.id !== agentId) - if (next.length === manualAgents.length) { - return false - } - this.store.set('manualAgents', next) - return true - } - getInstallState(agentId: string): AcpAgentInstallState | null { const installStates = this.getInstallStates() return installStates[resolveAcpAgentAlias(agentId)] ?? null } - setInstallState(agentId: string, state: AcpAgentInstallState | null): void { - const resolvedId = resolveAcpAgentAlias(agentId) - const installStates = this.getInstallStates() - if (!state) { - delete installStates[resolvedId] - } else { - installStates[resolvedId] = state - } - this.store.set('installStates', installStates) - } - getInstallStates(): Record { const raw = this.store.get('installStates') if (!raw || typeof raw !== 'object' || Array.isArray(raw)) { diff --git a/src/main/agent/acp/catalog/settingsDbStore.ts b/src/main/agent/acp/catalog/settingsDbStore.ts index 05a92a5390..2973a358a7 100644 --- a/src/main/agent/acp/catalog/settingsDbStore.ts +++ b/src/main/agent/acp/catalog/settingsDbStore.ts @@ -4,10 +4,14 @@ import type { StoreLike } from '@/config/storeLike' const clone = (value: T): T => JSON.parse(JSON.stringify(value)) as T export class AcpDbStore implements StoreLike> { + private readonly legacySnapshot: Record + constructor( - private readonly legacyStore: StoreLike>, + legacySnapshot: Record, private readonly getSettingsTable: () => AgentCatalogSettingsTable - ) {} + ) { + this.legacySnapshot = clone(legacySnapshot) + } private get settingsTable(): AgentCatalogSettingsTable { return this.getSettingsTable() @@ -32,12 +36,19 @@ export class AcpDbStore implements StoreLike> { const value = this.settingsTable.getAgentSetting(key) return value === undefined ? defaultValue : value } - const legacyValue = this.legacyStore.get(key) + const legacyValue = this.legacySnapshot[key] as TValue | undefined return legacyValue === undefined ? defaultValue : clone(legacyValue) } set(keyOrValues: string | Record, value?: unknown): void { if (typeof keyOrValues !== 'string') { + if ( + Object.keys(keyOrValues).some( + (key) => key !== 'enabled' && key !== 'version' && key !== 'sharedMcpSelections' + ) + ) { + throw new Error('Legacy ACP settings are read-only after database attachment') + } for (const [key, nextValue] of Object.entries(keyOrValues)) this.set(key, nextValue) return } @@ -51,7 +62,7 @@ export class AcpDbStore implements StoreLike> { this.settingsTable.setAgentSetting(keyOrValues, value) return } - this.legacyStore.set(keyOrValues, value) + throw new Error('Legacy ACP settings are read-only after database attachment') } delete(key: string): void { @@ -63,11 +74,11 @@ export class AcpDbStore implements StoreLike> { this.settingsTable.deleteAgentSetting(key) return } - this.legacyStore.delete(key) + throw new Error('Legacy ACP settings are read-only after database attachment') } private getLegacyStoreSnapshot(): Record { - const snapshot = { ...this.legacyStore.store } + const snapshot = clone(this.legacySnapshot) delete snapshot.enabled delete snapshot.version delete snapshot.sharedMcpSelections diff --git a/src/main/agent/acp/client/connection/AcpConnectionManager.ts b/src/main/agent/acp/client/connection/AcpConnectionManager.ts index 4ef65431b7..0748655d00 100644 --- a/src/main/agent/acp/client/connection/AcpConnectionManager.ts +++ b/src/main/agent/acp/client/connection/AcpConnectionManager.ts @@ -5,6 +5,7 @@ import { AcpProcessManager, type AcpProcessHandle } from '@/agent/acp/runtime' import type { AcpConnectionRef, AcpRegistryPort, StartAcpConnectionInput } from '../types' import { PROTOCOL_VERSION } from '@agentclientprotocol/sdk' import type { DeepChatEventPublisher } from '@/agent/deepchat/runtime/types' +import type { AcpRuntimeBootstrap } from '../../runtime/types' export class AcpConnectionManager { readonly processManager: AcpProcessManager @@ -13,9 +14,11 @@ export class AcpConnectionManager { provider: LLM_PROVIDER, agentSettings: AgentSettingsPort, registry: AcpRegistryPort, - publishEvent: DeepChatEventPublisher + publishEvent: DeepChatEventPublisher, + bootstrap: AcpRuntimeBootstrap ) { this.processManager = new AcpProcessManager({ + bootstrap, publishEvent, providerId: provider.id, resolveLaunchSpec: (agentId, workdir) => agentSettings.resolveAcpLaunchSpec(agentId, workdir), diff --git a/src/main/agent/acp/client/index.ts b/src/main/agent/acp/client/index.ts index b922786db3..138666158b 100644 --- a/src/main/agent/acp/client/index.ts +++ b/src/main/agent/acp/client/index.ts @@ -4,6 +4,7 @@ import { AcpSessionController, AcpSessionPersistence, type AcpSessionCapabilityEvents, + type AcpRuntimeBootstrap, type AcpProcessHandle, type AcpSessionRecord } from '@/agent/acp/runtime' @@ -27,6 +28,7 @@ export class AcpClientRuntime { readonly sessionController: AcpSessionController constructor(input: { + bootstrap: AcpRuntimeBootstrap provider: LLM_PROVIDER agentSettings: AgentSettingsPort mcpSettings: McpSettings @@ -40,7 +42,8 @@ export class AcpClientRuntime { input.provider, input.agentSettings, input.registry, - input.publishEvent + input.publishEvent, + input.bootstrap ) this.sessionRuntime = new AcpSessionRuntime({ providerId: input.provider.id, diff --git a/src/main/agent/acp/createRuntimeOwner.ts b/src/main/agent/acp/createRuntimeOwner.ts index 71da64ce31..eea84eaaec 100644 --- a/src/main/agent/acp/createRuntimeOwner.ts +++ b/src/main/agent/acp/createRuntimeOwner.ts @@ -3,9 +3,11 @@ import type { AgentSettingsPort } from '@/agent/settings' import type { DeepChatEventPublisher } from '@/agent/deepchat/runtime/types' import { AcpClientRuntime, AcpRuntimeOwner, type AcpRegistryPort } from './client' import { AcpSessionPersistence } from './runtime' +import type { AcpRuntimeBootstrap } from './runtime/types' import type { McpSettings } from '@/mcp/settings' export interface AcpRuntimeOwnerDependencies { + bootstrap: AcpRuntimeBootstrap providerConfig: Pick agentSettings: AgentSettingsPort mcpSettings: McpSettings @@ -23,6 +25,7 @@ export function createAcpRuntimeOwner(dependencies: AcpRuntimeOwnerDependencies) const provider = dependencies.providerConfig.getProviderById('acp') if (!provider) throw new Error('[ACP] Provider configuration not found') return new AcpClientRuntime({ + bootstrap: dependencies.bootstrap, publishEvent: dependencies.publishEvent, provider, agentSettings: dependencies.agentSettings, diff --git a/src/main/agent/acp/launch/acpLaunchSpecService.ts b/src/main/agent/acp/launch/acpLaunchSpecService.ts index 1bfbaed85d..fded44e9bd 100644 --- a/src/main/agent/acp/launch/acpLaunchSpecService.ts +++ b/src/main/agent/acp/launch/acpLaunchSpecService.ts @@ -4,6 +4,7 @@ import path from 'path' import { execFileSync } from 'child_process' import { createHash } from 'crypto' import { unzipSync } from 'fflate' +import { replaceDirectory } from '@/toolchains/extract' import type { AcpAgentConfig } from '@shared/types/acp' import type { AcpAgentInstallState, @@ -89,6 +90,17 @@ const listFilesRecursive = (root: string): string[] => { export class AcpLaunchSpecService { private readonly installRoot: string + private stopped = false + private readonly uninstallingAgents = new Set() + private readonly installations = new Map< + string, + { + agentId: string + controller: AbortController + repair: boolean + promise: Promise + } + >() constructor(registryRoot: string) { this.installRoot = path.join(registryRoot, 'agents') @@ -151,7 +163,61 @@ export class AcpLaunchSpecService { async ensureRegistryAgentInstalled( agent: AcpRegistryAgent, currentState: AcpAgentInstallState | null, - options?: { repair?: boolean } + options?: { repair?: boolean; signal?: AbortSignal } + ): Promise { + if (this.stopped) throw new DOMException('ACP installer is stopping', 'AbortError') + if (this.uninstallingAgents.has(agent.id)) throw new Error('ACP uninstall is in progress') + options?.signal?.throwIfAborted() + const key = JSON.stringify([agent.id, agent.version]) + const existing = this.installations.get(key) + if (existing) { + if (!options?.repair || existing.repair) return existing.promise + await existing.promise.catch(() => undefined) + existing.controller.signal.throwIfAborted() + return this.ensureRegistryAgentInstalled(agent, currentState, options) + } + if (this.installations.size >= 32) throw new Error('Too many pending ACP installations') + const controller = new AbortController() + const signal = options?.signal + ? AbortSignal.any([controller.signal, options.signal]) + : controller.signal + const promise = Promise.resolve() + .then(() => { + signal.throwIfAborted() + return this.installRegistryAgent(agent, currentState, options?.repair === true, signal) + }) + .finally(() => { + if (this.installations.get(key)?.promise === promise) this.installations.delete(key) + }) + this.installations.set(key, { + agentId: agent.id, + controller, + repair: options?.repair === true, + promise + }) + return promise + } + + stop(): void { + this.stopped = true + this.cancelPendingInstallations() + } + + cancelPendingInstallations(agentId?: string): void { + for (const job of this.installations.values()) { + if (!agentId || job.agentId === agentId) job.controller.abort() + } + } + + async drain(): Promise { + await Promise.allSettled([...this.installations.values()].map((job) => job.promise)) + } + + private async installRegistryAgent( + agent: AcpRegistryAgent, + currentState: AcpAgentInstallState | null, + repair: boolean, + signal: AbortSignal ): Promise { const selection = this.selectRegistryDistribution(agent) const lastCheckedAt = Date.now() @@ -180,9 +246,13 @@ export class AcpLaunchSpecService { const binaryConfig = selection.binary const installDir = this.getBinaryInstallDir(agent) + const previousDir = `${installDir}.prev` + if (!fs.existsSync(installDir) && fs.existsSync(previousDir)) { + fs.renameSync(previousDir, installDir) + } const commandPath = this.resolveInstalledBinaryPath(installDir, binaryConfig.cmd) - if (!options?.repair && commandPath) { + if (!repair && commandPath) { return { status: 'installed', distributionType: 'binary', @@ -194,24 +264,26 @@ export class AcpLaunchSpecService { } } + let stagingDir: string | undefined try { - if (options?.repair && fs.existsSync(installDir)) { - fs.rmSync(installDir, { recursive: true, force: true }) - } - ensureDir(installDir) - + signal.throwIfAborted() + ensureDir(path.dirname(installDir)) + stagingDir = fs.mkdtempSync(`${installDir}.staging-`) const downloadedArchive = await this.downloadArchive( binaryConfig.archive, agent, - binaryConfig.sha256 + binaryConfig.sha256, + signal ) try { - await this.extractArchive(downloadedArchive.archivePath, installDir) + signal.throwIfAborted() + await this.extractArchive(downloadedArchive.archivePath, stagingDir) + signal.throwIfAborted() } finally { fs.rmSync(downloadedArchive.tempDir, { recursive: true, force: true }) } - const installedCommand = this.resolveInstalledBinaryPath(installDir, binaryConfig.cmd) + const installedCommand = this.resolveInstalledBinaryPath(stagingDir, binaryConfig.cmd) if (!installedCommand) { throw new Error(`Installed archive does not contain ${binaryConfig.cmd}`) } @@ -219,6 +291,14 @@ export class AcpLaunchSpecService { if (process.platform !== 'win32') { fs.chmodSync(installedCommand, 0o755) } + signal.throwIfAborted() + fs.rmSync(previousDir, { recursive: true, force: true }) + replaceDirectory(stagingDir, installDir) + try { + fs.rmSync(previousDir, { recursive: true, force: true }) + } catch (error) { + console.warn('[ACP] Failed to remove the previous installation:', error) + } return { status: 'installed', @@ -230,6 +310,7 @@ export class AcpLaunchSpecService { error: null } } catch (error) { + signal.throwIfAborted() return { status: 'error', distributionType: 'binary', @@ -239,6 +320,8 @@ export class AcpLaunchSpecService { installDir, error: error instanceof Error ? error.message : String(error) } + } finally { + if (stagingDir) fs.rmSync(stagingDir, { recursive: true, force: true }) } } @@ -246,28 +329,45 @@ export class AcpLaunchSpecService { agent: AcpRegistryAgent, installState: AcpAgentInstallState | null ): Promise { - const selection = this.selectRegistryDistribution(agent) - if (selection?.type !== 'binary') { - return - } + if (this.stopped) throw new DOMException('ACP installer is stopping', 'AbortError') + if (this.uninstallingAgents.has(agent.id)) throw new Error('ACP uninstall is in progress') + this.uninstallingAgents.add(agent.id) + try { + this.cancelPendingInstallations(agent.id) + await Promise.allSettled( + [...this.installations.values()] + .filter((job) => job.agentId === agent.id) + .map((job) => job.promise) + ) + if (this.stopped) throw new DOMException('ACP installer is stopping', 'AbortError') + const selection = this.selectRegistryDistribution(agent) + if (selection?.type !== 'binary') { + return + } - const candidateDirs = new Set() - if (typeof installState?.installDir === 'string' && installState.installDir.trim()) { - candidateDirs.add(path.resolve(installState.installDir)) - } - candidateDirs.add(this.getBinaryInstallDir(agent)) + const candidateDirs = new Set() + if (typeof installState?.installDir === 'string' && installState.installDir.trim()) { + candidateDirs.add(path.resolve(installState.installDir)) + } + candidateDirs.add(this.getBinaryInstallDir(agent)) - for (const installDir of candidateDirs) { - this.removeInstallDir(installDir) - } + for (const installDir of candidateDirs) { + this.removeInstallDir(installDir) + } - this.pruneEmptyAgentInstallRoot(agent.id) + this.pruneEmptyAgentInstallRoot(agent.id) + } finally { + this.uninstallingAgents.delete(agent.id) + } } async resolveRegistryLaunchSpec( agent: AcpRegistryAgent, - installState: AcpAgentInstallState | null + installState: AcpAgentInstallState | null, + signal?: AbortSignal ): Promise { + if (this.stopped) throw new DOMException('ACP installer is stopping', 'AbortError') + signal?.throwIfAborted() const selection = this.selectRegistryDistribution(agent) if (!selection) { throw new Error( @@ -276,7 +376,7 @@ export class AcpLaunchSpecService { } if (selection.type === 'binary') { - const ensuredState = await this.ensureRegistryAgentInstalled(agent, installState) + const ensuredState = await this.ensureRegistryAgentInstalled(agent, installState, { signal }) if (ensuredState.status !== 'installed' || !ensuredState.installDir) { throw new Error(ensuredState.error || `Failed to install ACP agent ${agent.id}`) } @@ -407,7 +507,8 @@ export class AcpLaunchSpecService { private async downloadArchive( url: string, agent: AcpRegistryAgent, - expectedSha256?: string + expectedSha256: string | undefined, + signal: AbortSignal ): Promise { const safeAgentId = sanitizeInstallSegment(agent.id, 'agent id') const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), `deepchat-acp-${safeAgentId}-`)) @@ -417,14 +518,18 @@ export class AcpLaunchSpecService { throw new Error(`Invalid archive URL for ACP registry agent ${agent.id}`) } const archivePath = path.join(tempDir, archiveName) - const response = await fetch(url, { - signal: AbortSignal.timeout(ACP_BINARY_DOWNLOAD_TIMEOUT_MS) - }) + const downloadSignal = AbortSignal.any([ + signal, + AbortSignal.timeout(ACP_BINARY_DOWNLOAD_TIMEOUT_MS) + ]) + const response = await fetch(url, { signal: downloadSignal }) + downloadSignal.throwIfAborted() if (!response.ok) { throw new Error(`Failed to download archive: ${response.status} ${response.statusText}`) } const archive = Buffer.from(await response.arrayBuffer()) + downloadSignal.throwIfAborted() if (expectedSha256 !== undefined) { const normalizedSha256 = expectedSha256.trim().toLowerCase() if (!SHA256_PATTERN.test(normalizedSha256)) { diff --git a/src/main/agent/acp/routes.ts b/src/main/agent/acp/routes.ts index a1cb4dc6b6..c6d45c541b 100644 --- a/src/main/agent/acp/routes.ts +++ b/src/main/agent/acp/routes.ts @@ -28,7 +28,7 @@ export function createAcpRoutes(dependencies: { auth: AcpAuthService owner: AcpRuntimeOwner runtime: AcpAgentRuntime - isMainWindowContext(caller: RendererRouteCaller): boolean + isMainWindowContext(caller: RendererRouteCaller): Promise importHistory(sessionId: string): Promise }) { const controller = () => dependencies.owner.getOrCreate().sessionController @@ -108,7 +108,7 @@ export function createAcpRoutes(dependencies: { [ acpElicitationListRoute.name, async (_input, context) => { - if (!dependencies.isMainWindowContext(requireRendererCaller(context))) + if (!(await dependencies.isMainWindowContext(requireRendererCaller(context)))) throw new Error('ACP elicitation requires the main window') const bridge = dependencies.owner.peek()?.processManager.elicitation return acpElicitationListRoute.output.parse({ @@ -120,7 +120,7 @@ export function createAcpRoutes(dependencies: { [ acpElicitationRespondRoute.name, async (rawInput, context) => { - if (!dependencies.isMainWindowContext(requireRendererCaller(context))) + if (!(await dependencies.isMainWindowContext(requireRendererCaller(context)))) throw new Error('ACP elicitation requires the main window') const input = acpElicitationRespondRoute.input.parse(rawInput) return { diff --git a/src/main/agent/acp/runtime/acpProcessManager.ts b/src/main/agent/acp/runtime/acpProcessManager.ts index aedd898f13..f4873d101e 100644 --- a/src/main/agent/acp/runtime/acpProcessManager.ts +++ b/src/main/agent/acp/runtime/acpProcessManager.ts @@ -5,7 +5,6 @@ import spawn from 'cross-spawn' import type { ChildProcessWithoutNullStreams } from 'child_process' import { Readable, Writable } from 'node:stream' import { randomUUID } from 'node:crypto' -import { app } from 'electron' import * as fs from 'fs' import * as path from 'path' import { client, PROTOCOL_VERSION, RequestError } from '@agentclientprotocol/sdk' @@ -24,7 +23,7 @@ import type { AcpResolvedLaunchSpec } from '@shared/types/acp' import type { DeepChatEventPublisher } from '@/agent/deepchat/runtime/types' -import type { AgentProcessHandle, AgentProcessManager } from './types' +import type { AcpRuntimeBootstrap, AgentProcessHandle, AgentProcessManager } from './types' import { getPathEntriesFromEnv, getShellEnvironment, @@ -32,7 +31,7 @@ import { setPathEntriesOnEnv } from '@/agent/shared/process/shellEnvHelper' import { childProcessRegistry } from '@/agent/shared/process/childProcessRegistry' -import { RuntimeHelper } from '@/lib/runtimeHelper' +import { expandRuntimePath } from '@/lib/runtimeHelper' import { ToolchainService } from '@/toolchains' import { buildCapabilitySnapshot, @@ -88,6 +87,7 @@ export interface AcpProcessHandle extends AgentProcessHandle { } interface AcpProcessManagerOptions { + bootstrap: AcpRuntimeBootstrap publishEvent: DeepChatEventPublisher providerId: string resolveLaunchSpec: (agentId: string, workdir?: string) => Promise @@ -140,14 +140,6 @@ interface NpxCacheRepairResult { movedTo?: string } -/** - * Check if running in Electron environment. - * Reference: @modelcontextprotocol/sdk/client/stdio.js - */ -function isElectron(): boolean { - return 'type' in process -} - interface PermissionResolverEntry { agentId: string connectionId: string @@ -267,8 +259,8 @@ export class AcpProcessManager implements AgentProcessManager() - private readonly runtimeHelper = RuntimeHelper.getInstance() - private readonly terminalManager = new AcpTerminalManager() + private readonly bootstrap: AcpRuntimeBootstrap + private readonly terminalManager: AcpTerminalManager private readonly sessionWorkdirs = new Map() private readonly sessionConversations = new Map() private readonly fsHandlers = new Map() @@ -297,6 +289,14 @@ export class AcpProcessManager implements AgentProcessManager constructor(options: AcpProcessManagerOptions) { + if ( + !path.isAbsolute(options.bootstrap.homeDirectory) || + !path.isAbsolute(options.bootstrap.temporaryDirectory) + ) { + throw new Error('ACP bootstrap directories must be absolute') + } + this.bootstrap = { ...options.bootstrap } + this.terminalManager = new AcpTerminalManager(this.bootstrap.temporaryDirectory) this.elicitation = new AcpElicitationBridge(() => this.publishEvent('acp.elicitation.changed', { version: this.elicitation.version }) ) @@ -416,12 +416,12 @@ export class AcpProcessManager implements AgentProcessManager - typeof arg === 'string' ? this.runtimeHelper.expandPath(arg) : arg + typeof arg === 'string' ? expandRuntimePath(arg, this.bootstrap.homeDirectory) : arg ) const rewritten = useResolvedToolchain @@ -2017,7 +2017,7 @@ export class AcpProcessManager implements AgentProcessManager void = () => undefined - ) {} + ) { + if (!path.isAbsolute(homeDirectory)) { + throw new Error('ACP session home directory must be absolute') + } + } getProjectMetadata( workdir: string, @@ -395,13 +399,8 @@ export class AcpSessionPersistence { } getDefaultWorkdir(): string { - try { - const home = app.getPath('home') - if (this.isWorkdirUsable(home)) { - return home - } - } catch { - // fall through to process fallbacks + if (this.isWorkdirUsable(this.homeDirectory)) { + return this.homeDirectory } if (this.isWorkdirUsable(process.env.HOME)) { diff --git a/src/main/agent/acp/runtime/acpTerminalManager.ts b/src/main/agent/acp/runtime/acpTerminalManager.ts index 4377173a40..68920395f1 100644 --- a/src/main/agent/acp/runtime/acpTerminalManager.ts +++ b/src/main/agent/acp/runtime/acpTerminalManager.ts @@ -3,7 +3,6 @@ import path from 'path' import { spawn } from 'node-pty' import type { IPty } from 'node-pty' import { nanoid } from 'nanoid' -import { app } from 'electron' import { RequestError } from '@agentclientprotocol/sdk' import type * as schema from '@agentclientprotocol/sdk' @@ -37,19 +36,25 @@ export class AcpTerminalManager { private readonly terminals = new Map() private readonly defaultMaxOutputBytes = 1024 * 1024 // 1MB default + constructor(private readonly temporaryDirectory: string) { + if (!path.isAbsolute(temporaryDirectory)) { + throw new Error('ACP terminal temporary directory must be absolute') + } + } + private resolveTerminalCwd(cwd?: string | null): string { const normalized = cwd?.trim() if (normalized) { return path.resolve(normalized) } - const fallbackDir = path.join(app.getPath('temp'), 'deepchat-acp', 'terminals') + const fallbackDir = path.join(this.temporaryDirectory, 'deepchat-acp', 'terminals') try { fs.mkdirSync(fallbackDir, { recursive: true }) console.warn(`[ACP Terminal] Missing cwd, using fallback directory: ${fallbackDir}`) return fallbackDir } catch (error) { - const tempDir = app.getPath('temp') + const tempDir = this.temporaryDirectory console.warn( `[ACP Terminal] Failed to create fallback directory, using temp path instead: ${tempDir}`, error diff --git a/src/main/agent/acp/runtime/types.ts b/src/main/agent/acp/runtime/types.ts index 23fe614f34..2c76eb6f4b 100644 --- a/src/main/agent/acp/runtime/types.ts +++ b/src/main/agent/acp/runtime/types.ts @@ -12,6 +12,12 @@ export type { AgentSessionState } +export interface AcpRuntimeBootstrap { + appVersion: string + homeDirectory: string + temporaryDirectory: string +} + export interface AgentSessionManager< TSession extends AgentSessionState = AgentSessionState, TAgentDescriptor = string, diff --git a/src/main/agent/deepchat/harness/createDeepChatAgentHarness.ts b/src/main/agent/deepchat/harness/createDeepChatAgentHarness.ts index 7cdfca7015..c4e88c7681 100644 --- a/src/main/agent/deepchat/harness/createDeepChatAgentHarness.ts +++ b/src/main/agent/deepchat/harness/createDeepChatAgentHarness.ts @@ -11,7 +11,6 @@ import { CompactionService } from '@/agent/deepchat/runtime/compactionService' import { DeepChatLoopRunner } from '@/agent/deepchat/runtime/deepChatLoopRunner' import { DeferredToolExecutor } from '@/agent/deepchat/runtime/deferredToolExecutor' import { InteractionCoordinator } from '@/agent/deepchat/runtime/interactionCoordinator' -import { InteractionParkingRegistry } from '@/agent/deepchat/runtime/interactionParkingRegistry' import { MessageProjectionService } from '@/agent/deepchat/runtime/messageProjectionService' import { PendingInputAdmissionCoordinator } from '@/agent/deepchat/runtime/pendingInputAdmissionCoordinator' import { PendingInputPump } from '@/agent/deepchat/runtime/pendingInputPump' @@ -190,7 +189,7 @@ function createDeepChatRuntimeServices(deps: DeepChatHarnessDependencies): DeepC const tapeService = sessionData.tapeStore const pendingInputCoordinator = sessionData.pendingInputs - const runtime = new DeepChatAgentRuntime() + const runtime = new DeepChatAgentRuntime(deps.control) const identity = new SessionIdentityService({ registry: runtime, database }) const messageProjection = new MessageProjectionService({ registry: runtime, @@ -314,7 +313,6 @@ function createDeepChatRuntimeServices(deps: DeepChatHarnessDependencies): DeepC commandShell, messageProjection }) - const interactionParking = new InteractionParkingRegistry() const toolSurfaceDiagnostics = new ToolSurfaceShadowDiagnosticsRegistry() const toolSurfaceCanaryDiagnostics = new ToolSurfaceCanaryDiagnosticsRegistry() const programmaticToolParents = @@ -325,6 +323,7 @@ function createDeepChatRuntimeServices(deps: DeepChatHarnessDependencies): DeepC }) const pruningFeedback = new JevPruningFeedback() const sessionLifecycle = new SessionLifecycleCoordinator({ + fenceInputEffects: (sessionId) => pendingInputPump.fenceSessionInputEffects(sessionId), registry: runtime, providerSettings, promptSettings: deps.promptSettings, @@ -337,7 +336,6 @@ function createDeepChatRuntimeServices(deps: DeepChatHarnessDependencies): DeepC compaction, memory, runLifecycle, - interactionParking, toolSurfaceDiagnostics, toolSurfaceCanaryDiagnostics, programmaticToolParents, @@ -459,14 +457,11 @@ function createDeepChatRuntimeServices(deps: DeepChatHarnessDependencies): DeepC hookSink }) const pendingInputPump = new PendingInputPump({ + control: deps.control, pendingInputs: pendingInputCoordinator, transcript: messageStore, runLifecycle, turnStarter: turnCoordinator, - // Steer merging depends on an in-flight steerActiveTurn reaching the steer marker before a - // settlement-triggered drain adopts the claim, and that window is currently defined only by - // this read's async depth. The extra boundary is kept until the race is closed properly. - sessionState: { get: async (sessionId) => await sessionState.get(sessionId) }, sessionSettings }) pendingInputWakeup.bind(pendingInputPump) @@ -493,7 +488,6 @@ function createDeepChatRuntimeServices(deps: DeepChatHarnessDependencies): DeepC hookSink, turnCoordinator, continuationAdmission: deps.interactionContinuationAdmission, - interactionParking, executionJournal: tapeService, viewManifests: tapeService, toolSurfaces: tapeService @@ -577,10 +571,20 @@ function createDeepChatRuntimeServices(deps: DeepChatHarnessDependencies): DeepC input ) - const reconcilePersistedRuntimeState = (): void => { + const reconcilePersistedRuntimeState = (): Promise => { + const retiredEffects = pendingInputPump.fenceInputEffectsForRecovery() const forceRecoverMessagesBySession = reportStartupExecutionRecovery(tapeService) const pendingInputRecovery = pendingInputCoordinator.recoverInputsAfterRestart() - pendingInputPump.replaceRestartedQueueInputs(pendingInputRecovery.heldQueueInputIds) + const inputRecovery = pendingInputPump.replaceRestartedQueueInputs( + [...pendingInputRecovery.affectedSessionIds].map((sessionId) => ({ + sessionId, + heldInputIds: pendingInputCoordinator + .listPendingInputs(sessionId) + .filter(({ id }) => pendingInputRecovery.heldQueueInputIds.has(id)) + .map(({ id }) => id) + })), + retiredEffects + ) if (pendingInputRecovery.affectedSessionIds.size > 0) { logger.info( `DeepChatAgent: reconciled ${pendingInputRecovery.affectedSessionIds.size} sessions with pending inputs` @@ -602,10 +606,14 @@ function createDeepChatRuntimeServices(deps: DeepChatHarnessDependencies): DeepC if (recovered > 0) { logger.info(`DeepChatAgent: recovered ${recovered} pending messages to error status`) } + return inputRecovery } - reconcilePersistedRuntimeState() + const ready = reconcilePersistedRuntimeState() + void ready.catch(() => {}) return { + ready, + drainPendingInputEffects: () => pendingInputPump.drainForMaintenance(), runtime, sessionLifecycle, sessionState, diff --git a/src/main/agent/deepchat/harness/deepChatAgentHarness.ts b/src/main/agent/deepchat/harness/deepChatAgentHarness.ts index ea0029715a..6176ec3617 100644 --- a/src/main/agent/deepchat/harness/deepChatAgentHarness.ts +++ b/src/main/agent/deepchat/harness/deepChatAgentHarness.ts @@ -27,10 +27,7 @@ import type { SessionStatePort } from '@/session/data/contracts' import type { SessionTranscriptRuntimePort } from '@/session/transcriptMutations' import type { DeepChatRuntimeServices } from './runtimeServices' -/** - * Public boundary of the DeepChat agent runtime. Every method delegates to exactly one owner; the - * harness holds no phase, queue, cache, subscription, or derived fact of its own. - */ +/** Delegates each public operation to its existing runtime owner without retaining state. */ export class DeepChatAgentHarness implements DeepChatAgentBackendPort, SessionStatePort, SessionTranscriptRuntimePort { @@ -74,7 +71,16 @@ export class DeepChatAgentHarness this.services.runtime.markToolRegistryChanged() } - readonly reconcileAfterDatabaseReopen = (): void => this.services.reconcileAfterDatabaseReopen() + readonly abortGenerations = (): void => this.services.runtime.abortGenerations() + + get ready(): Promise { + return this.services.ready + } + + readonly drainPendingInputEffects = (): Promise => this.services.drainPendingInputEffects() + + readonly reconcileAfterDatabaseReopen = (): Promise => + this.services.reconcileAfterDatabaseReopen() createAcpAgentInstanceDependencies( input: Parameters[0] @@ -251,7 +257,7 @@ export class DeepChatAgentHarness } async setSessionProjectDir(sessionId: string, projectDir: string | null): Promise { - this.services.sessionSettings.setProjectDir(sessionId, projectDir) + await this.services.sessionSettings.setProjectDir(sessionId, projectDir) } async getPermissionMode(sessionId: string): Promise { @@ -317,8 +323,8 @@ export class DeepChatAgentHarness return this.services.transcriptMutation.prepareClearMessages(sessionId) } - finishClearMessages(sessionId: string): void { - this.services.transcriptMutation.finishClearMessages(sessionId) + finishClearMessages(sessionId: string): Promise { + return this.services.transcriptMutation.finishClearMessages(sessionId) } prepareRetry( @@ -336,15 +342,15 @@ export class DeepChatAgentHarness this.services.transcriptMutation.invalidateTranscriptFrom(sessionId, orderSeq) } - finishTranscriptTruncate(sessionId: string): void { - this.services.transcriptMutation.finishTranscriptTruncate(sessionId) + finishTranscriptTruncate(sessionId: string): Promise { + return this.services.transcriptMutation.finishTranscriptTruncate(sessionId) } resetForkTarget(targetSessionId: string, clonedMemoryCursorOrderSeq: number): void { this.services.transcriptMutation.resetForkTarget(targetSessionId, clonedMemoryCursorOrderSeq) } - assertNoActivePendingInputs(sessionId: string): void { - this.services.transcriptMutation.assertNoActivePendingInputs(sessionId) + async assertNoActivePendingInputs(sessionId: string): Promise { + await this.services.transcriptMutation.assertNoActivePendingInputs(sessionId) } } diff --git a/src/main/agent/deepchat/harness/runtimeServices.ts b/src/main/agent/deepchat/harness/runtimeServices.ts index 78e3f43f97..e38b01d337 100644 --- a/src/main/agent/deepchat/harness/runtimeServices.ts +++ b/src/main/agent/deepchat/harness/runtimeServices.ts @@ -65,6 +65,7 @@ export type DeepChatHarnessSkillPort = Pick< SkillMetadataSnapshotPort export interface DeepChatHarnessDependencies { + control: import('@shared/harness/control').HarnessControlPort pluginContext?: PluginContextPort providerRuntime: ProviderExecutionPort providerSettings: ProviderModelResolutionPort @@ -109,6 +110,8 @@ export interface DeepChatHarnessDependencies { * contract is package-private so no caller can reach an owner around the harness. */ export interface DeepChatRuntimeServices { + ready: Promise + drainPendingInputEffects(): Promise runtime: DeepChatAgentRuntime sessionLifecycle: SessionLifecycleCoordinator sessionState: SessionStateResolver @@ -124,5 +127,5 @@ export interface DeepChatRuntimeServices { toolSurfaceDiagnostics: ToolSurfaceShadowDiagnosticsRegistry toolSurfaceCanaryDiagnostics: ToolSurfaceCanaryDiagnosticsRegistry acpCompatibility: AcpAgentInstanceDependencyFactory - reconcileAfterDatabaseReopen(): void + reconcileAfterDatabaseReopen(): Promise } diff --git a/src/main/agent/deepchat/instance/controlSession.ts b/src/main/agent/deepchat/instance/controlSession.ts new file mode 100644 index 0000000000..3463e2bb73 --- /dev/null +++ b/src/main/agent/deepchat/instance/controlSession.ts @@ -0,0 +1,873 @@ +import { randomUUID } from 'node:crypto' +import { createAbortError } from '../runtime/abortErrors' +import logger from '@shared/logger' +import { resolveProviderPermissionSafely } from '../runtime/providerPermissionResolution' +import { redactRuntimeErrorForLog } from '../runtime/runtimeErrorLogging' +import type { DeepChatActiveProviderPermission } from './deepChatAgentInstance' +import { + HarnessPermissionDecisionSchema, + HarnessBeginInputResultSchema, + HarnessQueryInputResultSchema, + HarnessClaimEffectGrantSchema, + HarnessEndInputResultSchema +} from '@shared/harness/control' +import type { LoopRun } from '@/agent/deepchat/loop/loopRun' +import type { PersistedToolBatchState } from '@/agent/deepchat/loop/ports' +import type { + HarnessControlCommand, + HarnessAcceptanceLane, + HarnessControlLease, + HarnessControlLeaseTarget, + HarnessControlOwner, + HarnessControlPort, + HarnessControlReceipt, + HarnessControlScope, + HarnessInputSnapshot, + HarnessPendingInteraction, + HarnessInteractionUpdate +} from '@shared/harness/control' + +export interface PendingInteractionProjection { + interactions: readonly HarnessPendingInteraction[] + batch?: PersistedToolBatchState + reconcile?: boolean +} + +export const STALE_DEEPCHAT_INSTANCE_ERROR_NAME = 'StaleDeepChatAgentInstanceError' + +export function createStaleDeepChatInstanceError(sessionId: string): Error { + const error = new Error(`DeepChat agent instance was replaced: ${sessionId}`) + error.name = STALE_DEEPCHAT_INSTANCE_ERROR_NAME + return error +} + +export function isStaleDeepChatInstanceError(error: unknown): boolean { + return error instanceof Error && error.name === STALE_DEEPCHAT_INSTANCE_ERROR_NAME +} + +interface ControllerHandle { + id: string + controller: AbortController + detach(): void + parentId?: string +} + +export interface ProviderPermissionTakeInput { + requestId: string + messageId: string + toolCallId: string + runId: string | null + leaseId: string + granted: boolean + expected?: DeepChatActiveProviderPermission +} + +export type ProviderPermissionTakeResult = + | { kind: 'callback'; permission: DeepChatActiveProviderPermission } + | { kind: 'direct' | 'conflict' | 'spent' | 'stale' } + +export class DeepChatControlSession { + readonly id = randomUUID() + readonly ready: Promise + private state: HarnessControlScope | undefined + private revision = 0 + private retired = false + private readonly controllers = new Map() + private readonly controllerIds = new WeakMap() + private readonly deferredLeases = new WeakMap() + private readonly runs = new Map>() + private readonly fencedRuns = new Set() + private readonly batchContracts = new Map< + string, + NonNullable + >() + private readonly pendingBatches = new Set() + private readonly permissions = new Map< + string, + { permission: DeepChatActiveProviderPermission; runId: string } + >() + private readonly permissionIds = new WeakMap() + private readonly detach: () => void + private readonly detachGrants: () => void + private readonly acceptanceWaiters = new Map< + string, + ReturnType> + >() + + constructor( + readonly sessionId: string, + private readonly port: HarnessControlPort, + private readonly changed: (state: HarnessControlScope | undefined, current: boolean) => void + ) { + if (port.closed) throw new Error('Harness control stopped') + this.detachGrants = port.onGrant(this.id, (leaseId) => { + if (this.current) this.acceptanceWaiters.get(leaseId)?.resolve() + }) + this.detach = port.onClose(() => this.fenceLocal()) + this.ready = port + .send({ method: 'open', sessionId, scopeId: this.id }, (receipt) => { + this.receive(receipt) + }) + .then((receipt) => { + if (!this.current || receipt.outcome !== 'accepted') { + throw createStaleDeepChatInstanceError(sessionId) + } + }) + void this.ready.catch(() => {}) + } + + get current(): boolean { + return !this.retired && !this.port.closed && this.state?.current === true + } + + get firstTurnReady(): boolean { + return this.current && this.state?.firstTurnReady === true + } + + get status(): HarnessControlScope['status'] { + return this.state?.status ?? 'idle' + } + + get draining(): boolean { + return this.current && this.state?.drainLeaseId != null + } + + get interactions(): HarnessPendingInteraction[] { + return this.current ? (this.state?.interactions.map((entry) => ({ ...entry })) ?? []) : [] + } + + get batch(): PersistedToolBatchState | undefined { + const state = this.current ? this.state?.batch : undefined + if (!state) return undefined + const executionContract = this.batchContracts.get(state.id) + return { + callOrder: [...state.callOrder], + invokedCallIds: [...state.invokedCallIds], + committedResultCallIds: [...state.committedResultCallIds], + pendingInteractionCallIds: [...state.pendingInteractionCallIds], + ...(executionContract ? { executionContract } : {}) + } + } + + private projectionOwner(): HarnessControlOwner { + return this.state?.run + ? { kind: 'run', id: this.state.run.id } + : this.state?.operation + ? { kind: 'operation', id: this.state.operation.id } + : { kind: 'orphan' } + } + + private prepareProjection(projection: PendingInteractionProjection): { + update: HarnessInteractionUpdate + finish(): void + } { + const batch = projection.batch + const batchId = batch && randomUUID() + if (batchId) { + this.pendingBatches.add(batchId) + if (batch?.executionContract) this.batchContracts.set(batchId, batch.executionContract) + } + return { + update: { + interactions: projection.interactions.map(({ messageId, toolCallId, origin, order }) => ({ + messageId, + toolCallId, + origin, + order + })), + ...(projection.reconcile === undefined ? {} : { reconcile: projection.reconcile }), + ...(batch && batchId + ? { + batch: { + id: batchId, + callOrder: [...batch.callOrder], + invokedCallIds: [...batch.invokedCallIds], + committedResultCallIds: [...batch.committedResultCallIds], + pendingInteractionCallIds: [...batch.pendingInteractionCallIds] + } + } + : {}) + }, + finish: () => { + if (!batchId) return + this.pendingBatches.delete(batchId) + if (this.state?.batch?.id !== batchId) this.batchContracts.delete(batchId) + } + } + } + + async replaceInteractions( + projection: PendingInteractionProjection, + owner: HarnessControlOwner = this.projectionOwner() + ): Promise { + const pending = this.prepareProjection(projection) + try { + const receipt = await this.send({ + method: 'updateInteractions', + scopeId: this.id, + owner, + update: pending.update + }) + return receipt.result === true && this.current + } finally { + pending.finish() + } + } + + async advanceBatch( + input: { invokedCallId?: string; committedResultCallId?: string }, + owner: HarnessControlOwner = this.projectionOwner() + ): Promise { + await this.send({ + method: 'advanceBatch', + scopeId: this.id, + owner, + ...(input.invokedCallId ? { invokedCallId: input.invokedCallId } : {}), + ...(input.committedResultCallId ? { committedResultCallId: input.committedResultCallId } : {}) + }) + } + + async transitionInteractionOrigin( + messageId: string, + toolCallId: string, + origin: HarnessPendingInteraction['origin'], + owner: HarnessControlOwner = this.projectionOwner() + ): Promise { + const receipt = await this.send({ + method: 'interactionOrigin', + scopeId: this.id, + owner, + messageId, + toolCallId, + origin + }) + return receipt.result === true && this.current + } + + async checkInteraction(lease: HarnessControlLease, owner: HarnessControlOwner): Promise { + if (lease.target.kind !== 'interaction') return false + const receipt = await this.send({ + method: 'checkInteraction', + scopeId: this.id, + owner, + leaseId: lease.id, + messageId: lease.target.messageId, + toolCallId: lease.target.toolCallId + }) + return receipt.result === true && this.current + } + + get activeSteerInputId(): string | undefined { + return this.current ? (this.state?.input.activeSteerId ?? undefined) : undefined + } + + async queryInput(snapshot: HarnessInputSnapshot, reason?: 'enqueue' | 'completed' | 'manual') { + await this.ready + let result!: ReturnType + const receipt = await this.port.send( + { method: 'queryInput', scopeId: this.id, snapshot, ...(reason ? { reason } : {}) }, + (receipt) => { + this.receive(receipt) + if (receipt.outcome === 'accepted') + result = HarnessQueryInputResultSchema.parse(receipt.result) + } + ) + this.assertReceipt(receipt) + return result + } + + async beginInput( + input: Omit< + Extract, + 'method' | 'scopeId' | 'requestId' + > + ) { + await this.ready + const requestId = randomUUID() + let result!: ReturnType + const receipt = await this.port.send( + { method: 'beginInput', scopeId: this.id, requestId, ...input }, + (receipt) => { + this.receive(receipt) + if (receipt.outcome !== 'accepted') return + result = HarnessBeginInputResultSchema.parse(receipt.result) + const claim = result.action === 'claim' || result.action === 'prepare' + const allowed = + input.intent === 'enqueue' + ? ['pending', 'claim', 'busy', 'blocked'] + : input.intent === 'drain' + ? ['claim', 'busy', 'blocked'] + : input.intent === 'prepare' + ? ['prepare', 'busy', 'blocked'] + : ['steer', 'busy', 'blocked'] + if ( + !allowed.includes(result.action) || + (!claim && result.claimId !== undefined) || + (!claim && result.action !== 'steer' && result.effectId !== undefined) || + (result.action !== 'claim' && result.drainLeaseId !== undefined) || + (result.consumeBeforeProviderStream && result.source !== 'queue') || + (result.wakeReason && (input.intent !== 'enqueue' || result.action !== 'pending')) + ) + throw new Error('Invalid input effect decision') + if ( + (claim && + (result.claimId !== requestId || result.effectId !== requestId || !result.source)) || + (result.action === 'claim' && result.drainLeaseId !== requestId) || + (result.action === 'steer' && result.effectId !== requestId) || + (input.intent === 'drain' && result.action === 'claim' && !result.inputId) || + (result.action === 'prepare' && result.inputId !== input.itemId) + ) + throw new Error('Invalid input effect grant identity') + } + ) + if (!this.current && receipt.outcome === 'accepted' && result.effectId) { + await this.ackInputEffect({ + effectId: result.effectId, + ...(result.claimId ? { claimId: result.claimId } : {}), + outcome: 'rejected' + }) + if (result.drainLeaseId) await this.endInput(result.drainLeaseId) + } + this.assertReceipt(receipt) + return result + } + + async prepareClaimEffect( + claimId: string, + owner: HarnessControlOwner, + action: Extract['action'] + ) { + const effectId = randomUUID() + let result!: ReturnType + const receipt = await this.port.send( + { method: 'prepareClaimEffect', scopeId: this.id, claimId, owner, action, effectId }, + (receipt) => { + this.receive(receipt) + if (receipt.outcome !== 'accepted') return + result = HarnessClaimEffectGrantSchema.parse(receipt.result) + if (result.effectId !== effectId || result.action !== action) + throw new Error('Invalid claimed input effect identity') + } + ) + if (receipt.outcome !== 'accepted') throw createStaleDeepChatInstanceError(this.sessionId) + return result + } + + async ackInputEffect( + input: Omit, 'method' | 'scopeId'> + ): Promise { + const receipt = await this.port.send( + { method: 'ackInputEffect', scopeId: this.id, ...input }, + (receipt) => { + this.receive(receipt) + if (receipt.outcome === 'accepted' && receipt.result !== true) + throw new Error('Invalid input effect acknowledgement') + } + ) + if (receipt.outcome !== 'accepted') throw createStaleDeepChatInstanceError(this.sessionId) + } + + async inputHold( + input: Omit, 'method' | 'scopeId'> + ): Promise { + await this.ready + const receipt = await this.port.send( + { method: 'inputHold', scopeId: this.id, ...input }, + (receipt) => { + this.receive(receipt) + if (receipt.outcome === 'accepted' && typeof receipt.result !== 'boolean') + throw new Error('Invalid input hold acknowledgement') + } + ) + return receipt.outcome === 'accepted' && receipt.result === true && this.current + } + + async endInput(drainLeaseId: string) { + let result = { released: false, nextWake: null } as ReturnType< + typeof HarnessEndInputResultSchema.parse + > + await this.port.send({ method: 'endInput', scopeId: this.id, drainLeaseId }, (receipt) => { + this.receive(receipt) + if (receipt.outcome === 'accepted') result = HarnessEndInputResultSchema.parse(receipt.result) + }) + return result + } + + getPermission(requestId: string): DeepChatActiveProviderPermission | undefined { + const selected = this.state?.permissions.find( + (permission) => permission.requestId === requestId + ) + return selected ? this.permissions.get(selected.id)?.permission : undefined + } + + async registerDeferred( + messageId: string, + toolCallId: string, + lease: HarnessControlLease, + owner: HarnessControlOwner + ): Promise { + if (this.retired || this.port.closed) throw createStaleDeepChatInstanceError(this.sessionId) + const parent = + owner.kind === 'operation' + ? this.controllers.get(owner.id)?.controller + : owner.kind === 'run' + ? this.runs.get(owner.id)?.abortController + : undefined + const parentId = parent && this.controllerIds.get(parent) + if (!parent || !parentId) throw createStaleDeepChatInstanceError(this.sessionId) + const handle = this.attachController(new AbortController(), parentId) + const child: HarnessControlLease = { + id: handle.id, + target: { kind: 'deferred', messageId, toolCallId, parentLeaseId: lease.id, owner } + } + this.deferredLeases.set(handle.controller, child) + if (parent.signal.aborted) handle.controller.abort() + try { + await this.ready + const receipt = await this.send({ + method: 'acquireLease', + scopeId: this.id, + leaseId: child.id, + target: child.target + }) + if (receipt.result !== 'granted' || !this.current) + throw createStaleDeepChatInstanceError(this.sessionId) + if (parent.signal.aborted) handle.controller.abort() + return handle.controller + } catch (error) { + handle.controller.abort() + await this.releaseLease(child).catch(() => {}) + this.dropController(child.id) + throw error + } + } + + async clearDeferred(controller: AbortController): Promise { + const lease = this.deferredLeases.get(controller) + if (!lease) return false + const cleared = await this.releaseLease(lease) + this.dropController(lease.id) + return cleared + } + + hasDeferred(toolCallId: string): boolean { + return ( + this.current && + this.state?.deferred.some( + (entry) => entry.toolCallId === toolCallId && this.controllers.has(entry.id) + ) === true + ) + } + + abortDeferred(): void { + void this.send({ method: 'abortDeferred', scopeId: this.id }).catch(() => {}) + const handles = [...this.controllers.values()] + for (const handle of handles) + if (handle.parentId) { + handle.controller.abort() + this.dropController(handle.id) + } + } + + async registerPermission( + permission: DeepChatActiveProviderPermission, + runId: string + ): Promise { + if (this.retired || this.port.closed) throw createStaleDeepChatInstanceError(this.sessionId) + const id = randomUUID() + this.permissions.set(id, { permission, runId }) + this.permissionIds.set(permission, id) + try { + await this.ready + const receipt = await this.send({ + method: 'registerPermission', + scopeId: this.id, + permissionId: id, + requestId: permission.requestId, + messageId: permission.messageId, + toolCallId: permission.toolCallId, + runId + }) + if ( + receipt.result !== true || + !this.current || + this.runs.get(runId)?.abortController.signal.aborted + ) { + throw createStaleDeepChatInstanceError(this.sessionId) + } + } catch (error) { + this.denyPermission(id) + throw error + } + } + + async takePermission(input: ProviderPermissionTakeInput): Promise { + const permissionId = input.expected ? this.permissionIds.get(input.expected) : null + if (input.expected && !permissionId) return { kind: 'conflict' } + const decision: { kind: ProviderPermissionTakeResult['kind'] } = { kind: 'stale' } + await this.port.send( + { + method: 'takePermission', + scopeId: this.id, + permissionId: permissionId ?? null, + requestId: input.requestId, + messageId: input.messageId, + toolCallId: input.toolCallId, + runId: input.runId, + leaseId: input.leaseId, + granted: input.granted, + resolutionId: randomUUID() + }, + (receipt) => { + this.receive(receipt) + if (receipt.outcome !== 'accepted') return + decision.kind = HarnessPermissionDecisionSchema.parse(receipt.result) + if (decision.kind === 'callback' && permissionId) this.permissions.delete(permissionId) + } + ) + if ( + !this.current || + (input.runId && this.runs.get(input.runId)?.abortController.signal.aborted) + ) { + return { kind: 'stale' } + } + const { kind } = decision + if (kind === 'callback' && input.expected) return { kind, permission: input.expected } + if (kind === 'callback') throw new Error('Rust permission grant has no callback handle') + return { kind } + } + + private denyPermission(id: string): void { + const handle = this.permissions.get(id) + if (!handle) return + this.permissions.delete(id) + void resolveProviderPermissionSafely(() => handle.permission.resolve(false)).catch((error) => { + logger.warn( + '[DeepChatAgent] Failed to cancel ACP permission:', + redactRuntimeErrorForLog(error) + ) + }) + } + + private denyPermissions(runId?: string): void { + const handles = [...this.permissions] + for (const [id, handle] of handles) + if (!runId || handle.runId === runId) this.denyPermission(id) + } + + async acquireAcceptance( + lane: HarnessAcceptanceLane, + signal?: AbortSignal + ): Promise<() => Promise> { + if (signal?.aborted) throw createAbortError() + const leaseId = randomUUID() + const granted = Promise.withResolvers() + void granted.promise.catch(() => {}) + this.acceptanceWaiters.set(leaseId, granted) + const abort = () => granted.reject(createAbortError()) + signal?.addEventListener('abort', abort, { once: true }) + let submitted = false + let released = false + const release = async () => { + if (released || !submitted) return + released = true + await this.send({ method: 'releaseAcceptance', scopeId: this.id, lane, leaseId }) + } + try { + await Promise.race([this.ready, granted.promise]) + if (!this.current) throw createStaleDeepChatInstanceError(this.sessionId) + if (signal?.aborted) throw createAbortError() + submitted = true + const receipt = await this.send({ + method: 'queueAcceptance', + scopeId: this.id, + lane, + leaseId + }) + if (receipt.result === 'granted') granted.resolve() + else if (receipt.result !== 'queued') throw createStaleDeepChatInstanceError(this.sessionId) + await granted.promise + if (!this.current) throw createStaleDeepChatInstanceError(this.sessionId) + if (signal?.aborted) throw createAbortError() + return release + } catch (error) { + await release().catch(() => {}) + throw error + } finally { + signal?.removeEventListener('abort', abort) + this.acceptanceWaiters.delete(leaseId) + } + } + + async acquireLease( + target: HarnessControlLeaseTarget + ): Promise { + await this.ready + if (!this.current) return null + const lease = { id: randomUUID(), target } + const receipt = await this.send({ + method: 'acquireLease', + scopeId: this.id, + leaseId: lease.id, + target + }) + if (receipt.result === 'granted') { + if (this.current) return lease + await this.releaseLease(lease) + return null + } + return receipt.result === 'parked' ? 'parked' : null + } + + async releaseLease(lease: HarnessControlLease): Promise { + const receipt = await this.send({ + method: 'releaseLease', + scopeId: this.id, + leaseId: lease.id, + target: lease.target + }) + return receipt.result === true + } + + async parkInteraction(lease: HarnessControlLease): Promise { + if (lease.target.kind !== 'interaction') throw new Error('Expected interaction lease') + const receipt = await this.send({ + method: 'parkInteraction', + scopeId: this.id, + leaseId: lease.id, + messageId: lease.target.messageId, + toolCallId: lease.target.toolCallId + }) + if (receipt.result !== true) throw createStaleDeepChatInstanceError(this.sessionId) + } + + get controller(): AbortController | undefined { + const id = this.state?.operation?.id + return id ? this.controllers.get(id)?.controller : undefined + } + + get run(): LoopRun | undefined { + const id = this.state?.run?.id + return id ? this.runs.get(id) : undefined + } + + isRunCurrent(id: string): boolean { + return this.current && this.state?.run?.id === id && !this.fencedRuns.has(id) + } + + owner(controller: AbortController): { kind: 'operation'; id: string } { + const id = this.controllerIds.get(controller) + if (!id) throw new Error('Operation controller has no Rust control identity') + return { kind: 'operation', id } + } + + async prepareOperation(): Promise { + if (this.retired || this.port.closed) throw createStaleDeepChatInstanceError(this.sessionId) + const handle = this.attachController(new AbortController()) + await this.ready + const receipt = await this.port.transition( + { method: 'prepareOperation', scopeId: this.id, operationId: handle.id }, + (receipt) => { + this.receive(receipt) + return handle.controller.signal.aborted + } + ) + this.assertReceipt(receipt) + const operationId = (receipt.result as { operationId?: string } | null)?.operationId + const controller = operationId ? this.controllers.get(operationId)?.controller : undefined + if (!controller) throw new Error('Rust control grant has no local controller') + if (controller !== handle.controller) this.dropController(handle.id) + return controller + } + + async registerRun(run: LoopRun): Promise> { + if (this.retired || this.port.closed) throw createStaleDeepChatInstanceError(this.sessionId) + const handle = this.attachController(run.abortController) + this.runs.set(run.runId, run) + await this.ready + const receipt = await this.port.transition( + { + method: 'prepareRun', + scopeId: this.id, + operationId: handle.id, + runId: run.runId, + messageId: run.messageId + }, + (receipt) => { + this.receive(receipt) + return run.abortController.signal.aborted + } + ) + this.assertReceipt(receipt) + return run + } + + async clearController( + controller: AbortController, + settled?: () => void, + origin?: { operationId: string; runId?: string } + ): Promise { + const id = origin?.operationId ?? this.controllerIds.get(controller) + if (!id) return false + const receipt = await this.port.send( + { method: 'clearOperation', scopeId: this.id, operationId: id, runId: origin?.runId ?? null }, + (receipt) => { + this.receive(receipt) + if ((receipt.result as { settles?: boolean } | null)?.settles === true && this.current) + settled?.() + } + ) + const cleared = (receipt.result as { cleared?: boolean } | null)?.cleared === true + if (cleared || id !== this.state?.operation?.id) this.dropController(id) + return cleared + } + + async clearRun(runId: string): Promise { + const run = this.runs.get(runId) + const receipt = await this.send({ method: 'clearRun', scopeId: this.id, runId }) + if (receipt.result === true || !this.isRunCurrent(runId)) { + this.runs.delete(runId) + this.fencedRuns.delete(runId) + const id = run && this.controllerIds.get(run.abortController) + if (id && id !== this.state?.operation?.id) this.dropController(id) + } + return receipt.result === true + } + + abort(): void { + void this.send({ method: 'abort', scopeId: this.id }).catch(() => {}) + // Abort listeners can create new work; cancel only the handles present at the request. + const handles = [...this.controllers.values()] + for (const handle of handles) handle.controller.abort() + this.denyPermissions() + } + + async setStatus( + owner: HarnessControlOwner, + status: HarnessControlScope['status'], + interactions?: PendingInteractionProjection + ): Promise<{ previousStatus: HarnessControlScope['status'] } | undefined> { + let previousStatus = this.status + const pending = interactions && this.prepareProjection(interactions) + try { + const receipt = await this.port.send( + { + method: 'status', + scopeId: this.id, + owner, + status, + ...(pending ? { interactions: pending.update } : {}) + }, + (receipt) => { + previousStatus = this.status + this.receive(receipt) + } + ) + return receipt.result === true && this.current && receipt.revision === this.revision + ? { previousStatus } + : undefined + } finally { + pending?.finish() + } + } + + async markReady(runId: string): Promise { + const receipt = await this.send({ method: 'ready', scopeId: this.id, runId }) + return receipt.result === true + } + + async clearReady(): Promise { + await this.send({ method: 'clearReady', scopeId: this.id }) + } + + retire(): void { + void this.send({ method: 'retire', scopeId: this.id }).catch(() => {}) + this.fenceLocal() + this.detach() + } + + release(): void { + void this.send({ method: 'release', scopeId: this.id }).catch(() => {}) + this.fenceLocal() + this.detach() + } + + private send(command: HarnessControlCommand): Promise { + return this.port.send(command, (receipt) => this.receive(receipt)) + } + + private attachController(controller: AbortController, parentId?: string): ControllerHandle { + const existing = this.controllerIds.get(controller) + if (existing) { + const handle = this.controllers.get(existing) + if (handle) return handle + } + const id = randomUUID() + const aborted = () => { + if (parentId) return + void this.send({ method: 'operationAborted', scopeId: this.id, operationId: id }).catch( + () => {} + ) + const children = [...this.controllers.values()] + for (const child of children) if (child.parentId === id) child.controller.abort() + if (this.state?.run?.operationId === id) this.denyPermissions(this.state.run.id) + } + const handle = { + id, + parentId, + controller, + detach: () => controller.signal.removeEventListener('abort', aborted) + } + this.controllerIds.set(controller, id) + this.controllers.set(id, handle) + controller.signal.addEventListener('abort', aborted, { once: true }) + return handle + } + + private dropController(id: string): void { + this.controllers.get(id)?.detach() + this.controllers.delete(id) + } + + private receive(receipt: HarnessControlReceipt): void { + if (receipt.scope?.id === this.id && receipt.revision >= this.revision) { + const previousBatch = this.state?.batch?.id + this.revision = receipt.revision + this.state = receipt.scope + if ( + previousBatch && + previousBatch !== this.state.batch?.id && + !this.pendingBatches.has(previousBatch) + ) + this.batchContracts.delete(previousBatch) + this.changed(this.state, this.current) + } + for (const fence of receipt.fences) { + if (fence.kind === 'scope' && fence.id === this.id) this.fenceLocal() + else if (fence.kind === 'operation') this.controllers.get(fence.id)?.controller.abort() + else if (fence.kind === 'run') this.fencedRuns.add(fence.id) + else if (fence.kind === 'permission') this.denyPermission(fence.id) + } + for (const id of this.fencedRuns) if (id !== this.state?.run?.id) this.fencedRuns.delete(id) + } + + private assertReceipt(receipt: HarnessControlReceipt): void { + if (!this.current || receipt.outcome === 'stale') { + throw createStaleDeepChatInstanceError(this.sessionId) + } + } + + private fenceLocal(): void { + this.retired = true + this.batchContracts.clear() + this.pendingBatches.clear() + for (const waiter of this.acceptanceWaiters.values()) + waiter.reject(createStaleDeepChatInstanceError(this.sessionId)) + this.acceptanceWaiters.clear() + this.detachGrants() + this.denyPermissions() + for (const handle of this.controllers.values()) handle.controller.abort() + for (const id of this.runs.keys()) this.fencedRuns.add(id) + this.changed(this.state, this.current) + } +} diff --git a/src/main/agent/deepchat/instance/deepChatAgentInstance.ts b/src/main/agent/deepchat/instance/deepChatAgentInstance.ts index 02468e44f0..8e7d410f12 100644 --- a/src/main/agent/deepchat/instance/deepChatAgentInstance.ts +++ b/src/main/agent/deepchat/instance/deepChatAgentInstance.ts @@ -1,3 +1,17 @@ +import type { + HarnessAcceptanceLane, + HarnessControlLease, + HarnessControlOwner, + HarnessControlPort, + HarnessControlCommand, + HarnessInputSnapshot +} from '@shared/harness/control' +import { + DeepChatControlSession, + type ProviderPermissionTakeInput, + type ProviderPermissionTakeResult, + type PendingInteractionProjection +} from './controlSession' import type { AppSessionId } from '@/agent/shared/agentSessionIds' import type { MCPToolDefinition } from '@shared/types/core/mcp' import type { LoopRun } from '@/agent/deepchat/loop/loopRun' @@ -13,7 +27,6 @@ import type { import type { MemorySessionHandle } from '@/agent/deepchat/memory/memoryPromptContributor' export type DeepChatActiveGeneration = LoopRun -export type PendingQueueDrainLease = symbol export interface DeepChatPendingInteractionRef { readonly messageId: string @@ -51,23 +64,15 @@ const MAX_CONTEXT_WINDOW_OBSERVATIONS = 16 export class DeepChatAgentInstance { readonly kind = 'deepchat' as const + private inputEffectsRetired = false private runtimeState?: DeepChatSessionState private generationSettings?: SessionGenerationSettings private agentId?: string private projectDir?: string | null - private firstTurnReady = false + private pendingReadyClears = 0 + private readonly control: DeepChatControlSession private readonly firstTurnReadyWaiters = new Set<(ready: boolean) => void>() - private abortController?: AbortController - private activeRun?: DeepChatActiveGeneration private preStreamTranscriptAnchorId?: string - private activeSteerPendingInputId?: string - private pendingQueueDrainLease?: PendingQueueDrainLease - private pendingInteractions: DeepChatPendingInteractionRef[] = [] - private pendingToolBatchState?: PersistedToolBatchState - private readonly interactionLocks = new Set() - private readonly resumingMessages = new Set() - private readonly deferredToolAbortControllers = new Map() - private readonly activeProviderPermissions = new Map() private readonly runtimeActivatedSkills = new Set() private toolProfileCache?: DeepChatToolProfileCacheEntry private toolProfileRevisionToken = Symbol('deepchat-tool-profile-revision') @@ -75,7 +80,17 @@ export class DeepChatAgentInstance { private readonly contextWindowObservations = new Map() private readonly memorySessionHandle: MemorySessionHandle - constructor(readonly sessionId: AppSessionId) { + constructor( + readonly sessionId: AppSessionId, + control: HarnessControlPort + ) { + this.control = new DeepChatControlSession(sessionId, control, (state, current) => { + if (state && this.runtimeState) this.runtimeState.status = state.status + if (!current) this.settleFirstTurnReadyWaiters(false) + else if (state?.firstTurnReady && this.pendingReadyClears === 0) { + this.settleFirstTurnReadyWaiters(true) + } + }) this.memorySessionHandle = Object.freeze({ sessionId }) } @@ -84,7 +99,7 @@ export class DeepChatAgentInstance { } setRuntimeState(state: DeepChatSessionState): void { - this.runtimeState = state + this.runtimeState = { ...state, status: this.control.status } } getGenerationSettings(): SessionGenerationSettings | undefined { @@ -185,7 +200,7 @@ export class DeepChatAgentInstance { } async waitForFirstTurnReady(options?: { timeoutMs?: number }): Promise { - if (this.firstTurnReady) return true + if (this.control.firstTurnReady && this.pendingReadyClears === 0) return true const timeoutMs = Math.max(0, options?.timeoutMs ?? 30000) if (timeoutMs === 0) return false @@ -206,35 +221,78 @@ export class DeepChatAgentInstance { }) } - markFirstTurnReady(): void { - if (this.firstTurnReady) return - this.firstTurnReady = true - this.settleFirstTurnReadyWaiters(true) + get controlScopeId(): string { + return this.control.id + } + + get inputEffectsFenced(): boolean { + return this.inputEffectsRetired + } + + fenceInputEffects(): void { + this.inputEffectsRetired = true + } + + controlReady(): Promise { + return this.control.ready + } + + isControlCurrent(): boolean { + return this.control.current + } + + retireControl(): void { + this.control.retire() + } + + async markFirstTurnReady(runId: string): Promise { + return await this.control.markReady(runId) } clearFirstTurnReady(): void { - this.firstTurnReady = false + this.pendingReadyClears++ this.settleFirstTurnReadyWaiters(false) + void this.control + .clearReady() + .catch(() => {}) + .finally(() => { + this.pendingReadyClears-- + if (this.control.firstTurnReady && this.pendingReadyClears === 0) { + this.settleFirstTurnReadyWaiters(true) + } + }) } getAbortController(): AbortController | undefined { - return this.abortController + return this.control.controller } getAbortSignal(): AbortSignal | undefined { - return this.activeRun?.abortController.signal ?? this.abortController?.signal + return this.control.run?.abortController.signal ?? this.control.controller?.signal } - setAbortController(controller: AbortController): void { - this.abortController = controller + async ensureOperationController(): Promise { + return await this.control.prepareOperation() } - clearAbortController(controller?: AbortController): boolean { - if (!this.abortController || (controller && this.abortController !== controller)) { - return false - } - this.abortController = undefined - return true + operationOwner(controller: AbortController): { kind: 'operation'; id: string } { + return this.control.owner(controller) + } + + async transitionControlStatus( + owner: HarnessControlOwner, + status: DeepChatSessionState['status'], + interactions?: PendingInteractionProjection + ): Promise<{ previousStatus: DeepChatSessionState['status'] } | undefined> { + return await this.control.setStatus(owner, status, interactions) + } + + async clearAbortController( + controller?: AbortController, + settled?: () => void, + origin?: { operationId: string; runId?: string } + ): Promise { + return controller ? await this.control.clearController(controller, settled, origin) : false } getPreStreamTranscriptAnchorId(): string | undefined { @@ -250,266 +308,206 @@ export class DeepChatAgentInstance { } getActiveGeneration(): DeepChatActiveGeneration | undefined { - return this.activeRun + return this.control.run } - registerActiveGeneration(run: LoopRun): LoopRun { - const previousController = this.activeRun?.abortController ?? this.abortController - if (previousController && previousController !== run.abortController) { - previousController.abort() - } - this.activeRun = run - this.abortController = run.abortController - return run + async registerActiveGeneration( + run: LoopRun + ): Promise> { + return await this.control.registerRun(run) } - clearActiveGeneration(runId: string): boolean { - if (!this.activeRun || this.activeRun.runId !== runId) { - return false - } - const { abortController } = this.activeRun - this.activeRun = undefined - this.clearAbortController(abortController) - return true + async clearActiveGeneration(runId: string): Promise { + return await this.control.clearRun(runId) } isActiveRun(runId: string): boolean { - return this.activeRun?.runId === runId + return this.control.isRunCurrent(runId) } requestGenerationAbort(): void { - if (this.activeRun) { - this.activeRun.abortController.abort() - return - } - if (this.abortController) { - this.abortController.abort() - this.abortController = undefined - } + this.control.abort() } abortAndClearGeneration(): void { - const controller = this.activeRun?.abortController ?? this.abortController - controller?.abort() - this.abortController = undefined - this.activeRun = undefined + const run = this.control.run + const controller = this.control.controller + this.control.abort() + if (run) void this.control.clearRun(run.runId).catch(() => {}) + else if (controller) void this.control.clearController(controller).catch(() => {}) } getActiveSteerPendingInputId(): string | undefined { - return this.activeSteerPendingInputId + return this.control.activeSteerInputId } - setActiveSteerPendingInputId(itemId: string): void { - this.activeSteerPendingInputId = itemId + async queryInput(snapshot: HarnessInputSnapshot, reason?: 'enqueue' | 'completed' | 'manual') { + return await this.control.queryInput(snapshot, reason) } - clearActiveSteerPendingInputId(expectedItemId?: string): boolean { - if ( - !this.activeSteerPendingInputId || - (expectedItemId && this.activeSteerPendingInputId !== expectedItemId) - ) { - return false - } - this.activeSteerPendingInputId = undefined - return true + async beginInput( + input: Omit< + Extract, + 'method' | 'scopeId' | 'requestId' + > + ) { + return await this.control.beginInput(input) + } + + async prepareClaimEffect( + claimId: string, + owner: HarnessControlOwner, + action: Extract['action'] + ) { + return await this.control.prepareClaimEffect(claimId, owner, action) + } + + async ackInputEffect( + input: Omit, 'method' | 'scopeId'> + ): Promise { + await this.control.ackInputEffect(input) + } + + async inputHold( + input: Omit, 'method' | 'scopeId'> + ): Promise { + return await this.control.inputHold(input) + } + + async endInput(drainLeaseId: string) { + return await this.control.endInput(drainLeaseId) } isPendingQueueDraining(): boolean { - return this.pendingQueueDrainLease !== undefined + return this.control.draining } - tryAcquirePendingQueueDrain(): PendingQueueDrainLease | null { - if (this.pendingQueueDrainLease) { - return null - } - const lease = Symbol('pending-queue-drain') - this.pendingQueueDrainLease = lease - return lease + async acquireAttachmentAcceptance( + lane: HarnessAcceptanceLane, + signal?: AbortSignal + ): Promise<() => Promise> { + return await this.control.acquireAcceptance(lane, signal) } - releasePendingQueueDrain(lease: PendingQueueDrainLease): boolean { - if (this.pendingQueueDrainLease !== lease) { - return false - } - this.pendingQueueDrainLease = undefined - return true - } - - replacePendingInteractions(interactions: readonly DeepChatPendingInteractionRef[]): void { - this.pendingInteractions = interactions.map((interaction) => ({ ...interaction })) - const pendingToolBatchCallIds = this.pendingInteractions - .filter((interaction) => interaction.origin !== 'acp-permission') - .map((interaction) => interaction.toolCallId) - if (pendingToolBatchCallIds.length === 0) { - this.pendingToolBatchState = undefined - } else if (this.pendingToolBatchState) { - this.pendingToolBatchState = { - ...this.pendingToolBatchState, - pendingInteractionCallIds: pendingToolBatchCallIds - } - } + async replacePendingInteractions( + interactions: readonly DeepChatPendingInteractionRef[], + owner?: HarnessControlOwner, + options?: { reconcile?: boolean } + ): Promise { + return await this.control.replaceInteractions({ interactions, ...options }, owner) } - replacePendingToolBatch( + async replacePendingToolBatch( interactions: readonly DeepChatPendingInteractionRef[], - state: PersistedToolBatchState - ): void { - this.pendingInteractions = interactions.map((interaction) => ({ ...interaction })) - this.pendingToolBatchState = { - callOrder: [...state.callOrder], - invokedCallIds: [...state.invokedCallIds], - committedResultCallIds: [...state.committedResultCallIds], - pendingInteractionCallIds: [...state.pendingInteractionCallIds], - ...(state.executionContract ? { executionContract: state.executionContract } : {}) - } + state: PersistedToolBatchState, + owner?: HarnessControlOwner + ): Promise { + return await this.control.replaceInteractions({ interactions, batch: state }, owner) } getPendingToolBatchState(): PersistedToolBatchState | undefined { - const state = this.pendingToolBatchState - return state - ? { - callOrder: [...state.callOrder], - invokedCallIds: [...state.invokedCallIds], - committedResultCallIds: [...state.committedResultCallIds], - pendingInteractionCallIds: [...state.pendingInteractionCallIds], - ...(state.executionContract ? { executionContract: state.executionContract } : {}) - } - : undefined + return this.control.batch } - advancePendingToolBatch(input: { invokedCallId?: string; committedResultCallId?: string }): void { - const state = this.pendingToolBatchState - if (!state) { - return - } - const invokedCallIds = [...state.invokedCallIds] - const committedResultCallIds = [...state.committedResultCallIds] - if (input.invokedCallId && !invokedCallIds.includes(input.invokedCallId)) { - invokedCallIds.push(input.invokedCallId) - } - if ( - input.committedResultCallId && - !committedResultCallIds.includes(input.committedResultCallId) - ) { - committedResultCallIds.push(input.committedResultCallId) - } - this.pendingToolBatchState = { - ...state, - invokedCallIds, - committedResultCallIds - } + async advancePendingToolBatch( + input: { invokedCallId?: string; committedResultCallId?: string }, + owner?: HarnessControlOwner + ): Promise { + await this.control.advanceBatch(input, owner) } getFirstPendingInteraction(): DeepChatPendingInteractionRef | undefined { - const first = this.pendingInteractions[0] - return first ? { ...first } : undefined + return this.control.interactions[0] } getPendingInteractions(): DeepChatPendingInteractionRef[] { - return this.pendingInteractions.map((interaction) => ({ ...interaction })) + return this.control.interactions } - transitionPendingInteractionOrigin( + async transitionPendingInteractionOrigin( messageId: string, toolCallId: string, - origin: PendingToolInteractionOrigin - ): boolean { - const interaction = this.pendingInteractions.find( - (candidate) => candidate.messageId === messageId && candidate.toolCallId === toolCallId - ) - if (!interaction) { - return false - } - this.pendingInteractions = this.pendingInteractions.map((candidate) => - candidate === interaction ? { ...candidate, origin } : candidate - ) - return true + origin: PendingToolInteractionOrigin, + owner?: HarnessControlOwner + ): Promise { + return await this.control.transitionInteractionOrigin(messageId, toolCallId, origin, owner) } hasPendingInteractions(): boolean { - return this.pendingInteractions.length > 0 + return this.control.interactions.length > 0 } - tryLockInteraction(messageId: string, toolCallId: string): boolean { - const key = this.buildInteractionKey(messageId, toolCallId) - if (this.interactionLocks.has(key)) { - return false - } - this.interactionLocks.add(key) - return true + async checkPendingInteraction( + lease: HarnessControlLease, + owner: HarnessControlOwner + ): Promise { + return await this.control.checkInteraction(lease, owner) } - unlockInteraction(messageId: string, toolCallId: string): void { - this.interactionLocks.delete(this.buildInteractionKey(messageId, toolCallId)) + async acquireInteraction( + messageId: string, + toolCallId: string + ): Promise { + return await this.control.acquireLease({ kind: 'interaction', messageId, toolCallId }) } - tryBeginResume(messageId: string): boolean { - if (this.resumingMessages.has(messageId)) { - return false - } - this.resumingMessages.add(messageId) - return true + async parkInteraction(lease: HarnessControlLease): Promise { + await this.control.parkInteraction(lease) } - finishResume(messageId: string): void { - this.resumingMessages.delete(messageId) + async unlockInteraction(lease: HarnessControlLease): Promise { + await this.control.releaseLease(lease) } - registerDeferredToolAbortController(toolCallId: string): AbortController { - this.deferredToolAbortControllers.get(toolCallId)?.abort() - const controller = new AbortController() - this.deferredToolAbortControllers.set(toolCallId, controller) - return controller + async tryBeginResume(messageId: string): Promise { + const lease = await this.control.acquireLease({ kind: 'resume', messageId }) + return lease === 'parked' ? null : lease } - clearDeferredToolAbortController(toolCallId: string, controller?: AbortController): boolean { - const current = this.deferredToolAbortControllers.get(toolCallId) - if (!current || (controller && current !== controller)) { - return false - } - this.deferredToolAbortControllers.delete(toolCallId) - return true + async finishResume(lease: HarnessControlLease): Promise { + await this.control.releaseLease(lease) + } + + async registerDeferredToolAbortController( + messageId: string, + toolCallId: string, + lease: HarnessControlLease, + owner: HarnessControlOwner + ): Promise { + return await this.control.registerDeferred(messageId, toolCallId, lease, owner) + } + + async clearDeferredToolAbortController(controller: AbortController): Promise { + return await this.control.clearDeferred(controller) } hasDeferredToolAbortController(toolCallId: string): boolean { - return this.deferredToolAbortControllers.has(toolCallId) + return this.control.hasDeferred(toolCallId) } abortDeferredToolCalls(): void { - for (const controller of this.deferredToolAbortControllers.values()) { - controller.abort() - } - this.deferredToolAbortControllers.clear() + this.control.abortDeferred() } - registerActiveProviderPermission(permission: DeepChatActiveProviderPermission): void { - this.activeProviderPermissions.set(permission.requestId, permission) + async registerActiveProviderPermission( + permission: DeepChatActiveProviderPermission, + runId: string + ): Promise { + await this.control.registerPermission(permission, runId) } getActiveProviderPermission(requestId: string): DeepChatActiveProviderPermission | undefined { - return this.activeProviderPermissions.get(requestId) - } - - clearActiveProviderPermission( - requestId: string, - expected?: DeepChatActiveProviderPermission - ): boolean { - const current = this.activeProviderPermissions.get(requestId) - if (!current || (expected && current !== expected)) { - return false - } - this.activeProviderPermissions.delete(requestId) - return true + return this.control.getPermission(requestId) } - takeActiveProviderPermissions(): DeepChatActiveProviderPermission[] { - const permissions = [...this.activeProviderPermissions.values()] - this.activeProviderPermissions.clear() - return permissions + async takeProviderPermission( + input: ProviderPermissionTakeInput + ): Promise { + return await this.control.takePermission(input) } hasActiveProviderPermission(requestId: string): boolean { - return this.activeProviderPermissions.has(requestId) + return this.control.getPermission(requestId) !== undefined } replaceRuntimeActivatedSkills(skillNames: readonly string[]): void { @@ -574,18 +572,12 @@ export class DeepChatAgentInstance { this.projectDir = undefined this.clearFirstTurnReady() this.preStreamTranscriptAnchorId = undefined - this.activeSteerPendingInputId = undefined - this.pendingQueueDrainLease = undefined - this.pendingInteractions = [] - this.pendingToolBatchState = undefined - this.interactionLocks.clear() - this.resumingMessages.clear() this.abortDeferredToolCalls() - this.activeProviderPermissions.clear() this.runtimeActivatedSkills.clear() this.invalidateToolProfileCache() this.clearCompactionState() this.contextWindowObservations.clear() + this.control.release() } private contextWindowObservationKey(providerId: string, modelId: string): string { @@ -597,8 +589,4 @@ export class DeepChatAgentInstance { this.firstTurnReadyWaiters.clear() for (const waiter of waiters) waiter(ready) } - - private buildInteractionKey(messageId: string, toolCallId: string): string { - return `${messageId}:${toolCallId}` - } } diff --git a/src/main/agent/deepchat/instance/deepChatAgentRuntime.ts b/src/main/agent/deepchat/instance/deepChatAgentRuntime.ts index 90ff8a0096..50b6e5d5a5 100644 --- a/src/main/agent/deepchat/instance/deepChatAgentRuntime.ts +++ b/src/main/agent/deepchat/instance/deepChatAgentRuntime.ts @@ -2,17 +2,13 @@ import type { AppSessionId } from '@/agent/shared/agentSessionIds' import type { DeepChatSessionState } from '@shared/types/agent-interface' import { DeepChatAgentInstance } from './deepChatAgentInstance' -export const STALE_DEEPCHAT_INSTANCE_ERROR_NAME = 'StaleDeepChatAgentInstanceError' - -export function createStaleDeepChatInstanceError(sessionId: string): Error { - const error = new Error(`DeepChat agent instance was replaced: ${sessionId}`) - error.name = STALE_DEEPCHAT_INSTANCE_ERROR_NAME - return error -} - -export function isStaleDeepChatInstanceError(error: unknown): boolean { - return error instanceof Error && error.name === STALE_DEEPCHAT_INSTANCE_ERROR_NAME -} +export { + STALE_DEEPCHAT_INSTANCE_ERROR_NAME, + createStaleDeepChatInstanceError, + isStaleDeepChatInstanceError +} from './controlSession' +import { createStaleDeepChatInstanceError } from './controlSession' +import type { HarnessControlPort } from '@shared/harness/control' export interface SessionRuntimeScope { readonly sessionId: AppSessionId @@ -34,15 +30,19 @@ export type SessionScopeRegistry = Pick< export class DeepChatAgentRuntime { private readonly instances = new Map() + private readonly controlScopes = new Map() private readonly scopes = new WeakMap() private toolRegistryRevision = 0 + constructor(private readonly control: HarnessControlPort) {} + getOrHydrate(sessionId: AppSessionId): DeepChatAgentInstance { const current = this.instances.get(sessionId) if (current) return current - const instance = new DeepChatAgentInstance(sessionId) + const instance = new DeepChatAgentInstance(sessionId, this.control) this.instances.set(sessionId, instance) + this.controlScopes.set(sessionId, instance.controlScopeId) return instance } @@ -71,9 +71,9 @@ export class DeepChatAgentRuntime { sessionId, instance, state: () => instance.getRuntimeState(), - isCurrent: () => this.instances.get(sessionId) === instance, + isCurrent: () => this.instances.get(sessionId) === instance && instance.isControlCurrent(), assertCurrent: () => { - if (this.instances.get(sessionId) !== instance) { + if (this.instances.get(sessionId) !== instance || !instance.isControlCurrent()) { throw createStaleDeepChatInstanceError(sessionId) } } @@ -85,9 +85,30 @@ export class DeepChatAgentRuntime { } evict(sessionId: AppSessionId): boolean { + const instance = this.instances.get(sessionId) + if (!instance) return false + instance.retireControl() return this.instances.delete(sessionId) } + async clearSessionControl( + sessionId: AppSessionId, + origin?: DeepChatAgentInstance + ): Promise { + const originScopeId = origin?.controlScopeId ?? this.controlScopes.get(sessionId) ?? null + const receipt = await this.control.send({ method: 'clearSession', sessionId, originScopeId }) + if (receipt.outcome !== 'accepted' || receipt.result !== true) + throw createStaleDeepChatInstanceError(sessionId) + if (this.controlScopes.get(sessionId) === originScopeId) this.controlScopes.delete(sessionId) + } + + abortGenerations(): void { + for (const instance of this.instances.values()) { + instance.requestGenerationAbort() + instance.abortDeferredToolCalls() + } + } + getToolRegistryRevision(): number { return this.toolRegistryRevision } diff --git a/src/main/agent/deepchat/runtime/compactionRuntimeCoordinator.ts b/src/main/agent/deepchat/runtime/compactionRuntimeCoordinator.ts index e03a55d8e5..46bb4ad09f 100644 --- a/src/main/agent/deepchat/runtime/compactionRuntimeCoordinator.ts +++ b/src/main/agent/deepchat/runtime/compactionRuntimeCoordinator.ts @@ -35,14 +35,10 @@ import { resolveProviderInputCapabilities } from './providerInputCapabilities' import { resolveProviderModelRuntimeFacts } from './providerModelRuntimeFacts' import { toAppSessionId } from '@/agent/shared/agentSessionIds' import type { CommandShellService } from '@/agent/shared/process/commandShellService' -import { - isSummaryGapReason, - type SummaryGapReason -} from './contextContributions' +import { isSummaryGapReason, type SummaryGapReason } from './contextContributions' type ManualCompactionLifecycle = Pick< RunLifecycleCoordinator, - | 'canSettleOperation' | 'clearOperationController' | 'ensureOperationController' | 'hasPendingInteractions' @@ -128,7 +124,13 @@ export class CompactionRuntimeCoordinator { sessionId: string, expectedInstance?: DeepChatAgentInstance ): Promise { - const projection = this.resolveProjection(sessionId, expectedInstance) + const hydrated = + expectedInstance ?? this.deps.registry.getHydratedScope(toAppSessionId(sessionId))?.instance + if (!hydrated && !this.deps.sessionStore.get(sessionId)) + throw new Error(`Session ${sessionId} not found`) + const instance = hydrated ?? this.instance(sessionId) + await instance.controlReady() + const projection = this.resolveProjection(sessionId, instance) return { state: projection.state, emitSeq: this.currentEmitSeq(sessionId), @@ -140,7 +142,8 @@ export class CompactionRuntimeCoordinator { sessionId: string, expectedInstance?: DeepChatAgentInstance ): { state: SessionCompactionState; latestAnchorEntryId: number | null } { - const hydratedInstance = expectedInstance ?? this.deps.registry.getHydratedScope(toAppSessionId(sessionId))?.instance + const hydratedInstance = + expectedInstance ?? this.deps.registry.getHydratedScope(toAppSessionId(sessionId))?.instance const runtimeState = hydratedInstance?.getRuntimeState() const session = this.deps.sessionStore.get(sessionId) if (!runtimeState && !session) { @@ -149,7 +152,8 @@ export class CompactionRuntimeCoordinator { const instance = hydratedInstance ?? this.instance(sessionId) this.assertCurrent(sessionId, instance) - const reconstructionAnchor = this.deps.sessionStore.getReconstructionAnchorPromptState(sessionId) + const reconstructionAnchor = + this.deps.sessionStore.getReconstructionAnchorPromptState(sessionId) const latestAnchorEntryId = reconstructionAnchor?.entryId ?? null const persistedState = this.fromSummary( this.deps.sessionStore.getSummaryState(sessionId), @@ -172,27 +176,23 @@ export class CompactionRuntimeCoordinator { return { state: { ...persistedState }, latestAnchorEntryId } } - async compact( - sessionId: string - ): Promise<{ compacted: boolean; state: SessionCompactionState }> { + async compact(sessionId: string): Promise<{ compacted: boolean; state: SessionCompactionState }> { const instance = this.instance(sessionId) const scope = this.deps.runLifecycle.scopeFor(sessionId, instance) + await instance.controlReady() const state = instance.getRuntimeState() ?? (await this.deps.sessionState.getSummary(sessionId)) if (!state) { throw new Error(`Session ${sessionId} not found`) } this.assertCurrent(sessionId, instance) - const modelConfig = this.deps.providerSettings.getModelConfig( - state.modelId, - state.providerId - ) + const modelConfig = this.deps.providerSettings.getModelConfig(state.modelId, state.providerId) if (shouldBypassDeepChatContextBudget(state.providerId, modelConfig, state.modelId)) { throw new Error('Manual compaction is only available for DeepChat agent sessions.') } if (state.status !== 'idle') { throw new Error('Manual compaction is only available when the session is idle.') } - if (this.deps.runLifecycle.hasPendingInteractions(sessionId)) { + if (await this.deps.runLifecycle.hasPendingInteractions(sessionId)) { throw new Error('Pending tool interactions must be resolved before compacting.') } @@ -203,8 +203,12 @@ export class CompactionRuntimeCoordinator { modelConfig ) const { capabilitySnapshot } = providerModelFacts - this.deps.runLifecycle.transitionStatus(scope, 'generating') - const compactionAbortController = this.deps.runLifecycle.ensureOperationController(scope) + const compactionAbortController = await this.deps.runLifecycle.ensureOperationController(scope) + await this.deps.runLifecycle.transitionStatus( + scope, + 'generating', + instance.operationOwner(compactionAbortController) + ) const compactionAbortSignal = compactionAbortController.signal try { throwIfAbortRequested(compactionAbortSignal) @@ -229,20 +233,13 @@ export class CompactionRuntimeCoordinator { modelConfig, state.modelId ) - const maxTokens = capAgentRequestMaxTokens( - generationSettings.maxTokens, - contextBudgetLength - ) + const maxTokens = capAgentRequestMaxTokens(generationSettings.maxTokens, contextBudgetLength) const activeSkillNames = await awaitWithAbort( this.deps.toolResolver.resolveActiveSkillNamesForToolProfile(sessionId), compactionAbortSignal ) this.assertCurrent(sessionId, instance) - const projectDir = this.deps.sessionSettings.resolveProjectDir( - sessionId, - undefined, - instance - ) + const projectDir = this.deps.sessionSettings.resolveProjectDir(sessionId, undefined, instance) const tools = await awaitWithAbort( this.deps.toolResolver.loadToolDefinitionsForSession( sessionId, @@ -321,14 +318,12 @@ export class CompactionRuntimeCoordinator { state: this.resolveProjection(sessionId, instance).state } } finally { - const stillOwnsLifecycle = this.deps.runLifecycle.canSettleOperation( + await this.deps.runLifecycle.clearOperationController(scope, compactionAbortController) + await this.deps.runLifecycle.transitionStatus( scope, - compactionAbortController + 'idle', + instance.operationOwner(compactionAbortController) ) - this.deps.runLifecycle.clearOperationController(scope, compactionAbortController) - if (stillOwnsLifecycle) { - this.deps.runLifecycle.transitionStatus(scope, 'idle') - } } } @@ -415,14 +410,28 @@ export class CompactionRuntimeCoordinator { throw error } - if (result.anchorCommitted && result.outcome !== 'unchanged' && scope.isCurrent() && this.deps.pluginContext?.hasHooks()) { + if ( + result.anchorCommitted && + result.outcome !== 'unchanged' && + scope.isCurrent() && + this.deps.pluginContext?.hasHooks() + ) { const state = expectedInstance.getRuntimeState() if (state && state.providerId !== 'acp') { try { await this.deps.pluginContext.accept({ - sessionId, messageId: compactionMessageId, prompt: '', model: state.modelId, - cwd: this.deps.sessionSettings.resolveProjectDir(sessionId, undefined, expectedInstance), - source: 'compact', boundaryId: intent.compactionAttemptId, signal: options?.signal + sessionId, + messageId: compactionMessageId, + prompt: '', + model: state.modelId, + cwd: this.deps.sessionSettings.resolveProjectDir( + sessionId, + undefined, + expectedInstance + ), + source: 'compact', + boundaryId: intent.compactionAttemptId, + signal: options?.signal }) } catch (error) { logger.warn('[PluginHooks] Compaction hook dispatch failed', { sessionId, error }) @@ -528,7 +537,8 @@ export class CompactionRuntimeCoordinator { expectedInstance = this.instance(sessionId) ): void { this.assertCurrent(sessionId, expectedInstance) - const reconstructionAnchor = this.deps.sessionStore.getReconstructionAnchorPromptState(sessionId) + const reconstructionAnchor = + this.deps.sessionStore.getReconstructionAnchorPromptState(sessionId) const projectedState = { ...state, boundaryReason: diff --git a/src/main/agent/deepchat/runtime/contextOccupancyCoordinator.ts b/src/main/agent/deepchat/runtime/contextOccupancyCoordinator.ts index e5a10b97eb..0cf58a620b 100644 --- a/src/main/agent/deepchat/runtime/contextOccupancyCoordinator.ts +++ b/src/main/agent/deepchat/runtime/contextOccupancyCoordinator.ts @@ -44,6 +44,7 @@ export class ContextOccupancyCoordinator { async getSnapshot(sessionId: string): Promise { const scope = this.deps.runtime.getOrHydrateScope(toAppSessionId(sessionId)) + await scope.instance.controlReady() let state = scope.state() if (!state) { try { diff --git a/src/main/agent/deepchat/runtime/deepChatLoopRunner.ts b/src/main/agent/deepchat/runtime/deepChatLoopRunner.ts index 66fad0a0c6..e3242ec15d 100644 --- a/src/main/agent/deepchat/runtime/deepChatLoopRunner.ts +++ b/src/main/agent/deepchat/runtime/deepChatLoopRunner.ts @@ -3,6 +3,7 @@ import { projectPluginContext } from './pluginContext' import type { ProviderModelResolutionPort } from '@/provider/settings' import type { CacheImageOptions } from '@/platform/imageCache' import logger from '@shared/logger' +import { redactRuntimeErrorForLog } from './runtimeErrorLogging' import type { AssistantMessageBlock, ChatMessageRecord, @@ -721,9 +722,10 @@ export class DeepChatLoopRunner { } const resourceInstance = providedResourceInstance ?? this.ports.registry.getOrHydrateScope(toAppSessionId(sessionId)).instance const resourceScope = this.ports.runLifecycle.scopeFor(sessionId, resourceInstance) + await resourceInstance.controlReady() resourceScope.assertCurrent() const abortController = - providedAbortController ?? this.ports.runLifecycle.ensureOperationController(resourceScope) + providedAbortController ?? await this.ports.runLifecycle.ensureOperationController(resourceScope) const abortSignal = abortController.signal throwIfAbortRequested(abortSignal) const state = resourceInstance.getRuntimeState() @@ -1573,8 +1575,9 @@ export class DeepChatLoopRunner { try { this.ports.pluginContext?.beginRun?.(sessionId) pluginRunStarted = true - const activeGeneration = this.ports.runLifecycle.registerRun(resourceScope, loopRun) + const activeGeneration = await this.ports.runLifecycle.registerRun(resourceScope, loopRun) onRunRegistered?.(activeGeneration.runId) + throwIfAbortRequested(abortSignal) const rateLimitMessageId = `${RATE_LIMIT_STREAM_MESSAGE_PREFIX}${activeGeneration.runId}` let crossedPreStreamBoundary = false const crossPreStreamBoundary = () => { @@ -2220,7 +2223,9 @@ export class DeepChatLoopRunner { initialBlocks, initialAccounting, onFirstProviderRoundReady: () => { - this.ports.runLifecycle.markFirstTurnReady(resourceScope, activeGeneration.runId) + void this.ports.runLifecycle.markFirstTurnReady(resourceScope, activeGeneration.runId).catch((error) => { + logger.warn('[DeepChatAgent] Failed to publish first provider round readiness:', redactRuntimeErrorForLog(error)) + }) }, shouldYieldForPendingInput: () => Boolean(this.ports.pendingInputCoordinator.getNextSteerInput(sessionId)), @@ -2405,10 +2410,10 @@ export class DeepChatLoopRunner { executionRef }) }, - onStreamingProviderPermission: (permission, tool, commitDecision) => { - this.ports.providerPermissionCoordinator.register( - sessionId, - messageId, + onStreamingProviderPermission: async (permission, tool, commitDecision) => { + await this.ports.providerPermissionCoordinator.register( + resourceScope, + activeGeneration, permission, tool, commitDecision @@ -2497,7 +2502,7 @@ export class DeepChatLoopRunner { ? error : wrapTerminalCommitFailure(error, terminalCommitError) } finally { - this.ports.runLifecycle.clearRun(resourceScope, loopRun.runId) + await this.ports.runLifecycle.clearRun(resourceScope, loopRun.runId) } throw errorToPropagate } finally { diff --git a/src/main/agent/deepchat/runtime/deferredToolExecutor.ts b/src/main/agent/deepchat/runtime/deferredToolExecutor.ts index 7174c2b24e..cffbf7c1cf 100644 --- a/src/main/agent/deepchat/runtime/deferredToolExecutor.ts +++ b/src/main/agent/deepchat/runtime/deferredToolExecutor.ts @@ -1,3 +1,5 @@ +import type { HarnessControlLease, HarnessControlOwner } from '@shared/harness/control' +import type { SessionRuntimeScope } from '@/agent/deepchat/instance/deepChatAgentRuntime' import { randomUUID } from 'node:crypto' import type { AssistantMessageBlock } from '@shared/types/agent-interface' import type { @@ -83,7 +85,7 @@ export interface DeferredToolExecutorDependencies { cacheImage(data: string, options?: CacheImageOptions): Promise runLifecycle: Pick< RunLifecycleCoordinator, - 'registerDeferredToolController' | 'clearDeferredToolController' | 'getAbortSignal' + 'registerDeferredToolController' | 'clearDeferredToolController' > sessionSettings: Pick sessionState: Pick @@ -123,7 +125,7 @@ export class DeferredToolExecutor { constructor(private readonly dependencies: DeferredToolExecutorDependencies) {} async execute( - sessionId: string, + context: { scope: SessionRuntimeScope; lease: HarnessControlLease; owner: HarnessControlOwner }, messageId: string, toolCall: NonNullable, onToolCallStarted?: () => void, @@ -134,6 +136,8 @@ export class DeferredToolExecutor { onDispatchCommitted?: () => void, permissionLease?: ToolPermissionLeaseCapability ): Promise { + const { scope, lease, owner } = context + const sessionId = scope.sessionId const toolName = toolCall.name if (!toolName) { return { @@ -149,12 +153,10 @@ export class DeferredToolExecutor { } const toolCallId = toolCall.id - const deferredAbortController = this.dependencies.runLifecycle.registerDeferredToolController( - sessionId, - toolCallId + const deferredAbortController = await this.dependencies.runLifecycle.registerDeferredToolController( + scope, messageId, toolCallId, lease, owner ) - const deferredAbortSignal = - deferredAbortController?.signal ?? this.dependencies.runLifecycle.getAbortSignal(sessionId) + const deferredAbortSignal = deferredAbortController.signal let invoked = false let runId: string | null = null let runStartedCommitted = false @@ -816,11 +818,7 @@ export class DeferredToolExecutor { invoked } } finally { - this.dependencies.runLifecycle.clearDeferredToolController( - sessionId, - toolCallId, - deferredAbortController ?? undefined - ) + await this.dependencies.runLifecycle.clearDeferredToolController(scope, deferredAbortController) } } } diff --git a/src/main/agent/deepchat/runtime/interactionCoordinator.ts b/src/main/agent/deepchat/runtime/interactionCoordinator.ts index b5138d7bef..34e2c54890 100644 --- a/src/main/agent/deepchat/runtime/interactionCoordinator.ts +++ b/src/main/agent/deepchat/runtime/interactionCoordinator.ts @@ -1,3 +1,4 @@ +import type { HarnessControlOwner } from '@shared/harness/control' import type { AssistantMessageBlock, ToolInteractionResponse, @@ -58,7 +59,6 @@ import { throwIfAbortRequested } from './abortErrors' import type { RunLifecycleCoordinator } from './runLifecycleCoordinator' import type { RuntimeHookScope, RuntimeHookSink } from './runtimeHookSink' import { ExecutionJournalError, isExecutionJournalError } from '@/tape/domain/executionJournal' -import type { InteractionParkingRegistry } from './interactionParkingRegistry' import type { ExecutionJournalRecoveryReader, TapeExecutionViewManifestReader, @@ -66,10 +66,7 @@ import type { TapeViewManifestReader } from '@/tape/ports/capabilities' import { resolveDeferredExecutionContract } from './deferredExecutionContract' -import { - DeferredToolSurfaceError, - resolveDeferredToolSurfaceDispatch -} from './deferredToolSurface' +import { DeferredToolSurfaceError, resolveDeferredToolSurfaceDispatch } from './deferredToolSurface' import { CommandShellProfileSchema } from '@shared/commandShell' import { isAgentToolPathBearing, isCommandSignatureForProfile } from '@/tool/permission' @@ -123,7 +120,6 @@ export interface InteractionCoordinatorPorts { turnCoordinator: TurnResumePort continuationAdmission: InteractionContinuationAdmissionPort publishEvent: DeepChatEventPublisher - interactionParking: Pick executionJournal: Pick< ExecutionJournalRecoveryReader, 'hasAnyCommittedDispatchForMessageToolCall' @@ -151,11 +147,12 @@ export class InteractionCoordinator { response: ToolInteractionResponse ): Promise { const instance = this.ports.registry.getOrHydrateScope(toAppSessionId(sessionId)).instance - if (this.ports.interactionParking.isParked(sessionId, messageId)) { + const scope = this.ports.runLifecycle.scopeFor(sessionId, instance) + const interactionLease = await instance.acquireInteraction(messageId, toolCallId) + if (interactionLease === 'parked') { throw new ExecutionJournalError(DEFERRED_INTERACTION_PARKED_ERROR, 'persistence_failed') } - const scope = this.ports.runLifecycle.scopeFor(sessionId, instance) - if (!instance.tryLockInteraction(messageId, toolCallId)) { + if (!interactionLease) { return { resumed: false } } @@ -164,14 +161,13 @@ export class InteractionCoordinator { interactionOwnerRun, messageId ) + let interactionOwner: HarnessControlOwner | undefined let resumedWaitingAdmission = false let interactionAbortController: AbortController | null = null let interactionAbortSignal: AbortSignal | undefined let claimedToolSurfaceDeferredDispatch: ToolSurfaceDeferredDispatch | undefined const resumeWaitingAdmission = async () => { - if ( - await this.ports.continuationAdmission.resume(sessionId, interactionAbortSignal) - ) { + if (await this.ports.continuationAdmission.resume(sessionId, interactionAbortSignal)) { resumedWaitingAdmission = true } } @@ -182,9 +178,12 @@ export class InteractionCoordinator { } interactionAbortSignal = interactionOwnerRun.abortController.signal } else { - interactionAbortController = this.ports.runLifecycle.ensureOperationController(scope) + interactionAbortController = await this.ports.runLifecycle.ensureOperationController(scope) interactionAbortSignal = interactionAbortController.signal } + interactionOwner = interactionOwnerRun + ? { kind: 'run' as const, id: interactionOwnerRun.runId } + : instance.operationOwner(interactionAbortController!) throwIfAbortRequested(interactionAbortSignal) const message = await this.ports.messageStore.getMessage(messageId) if (!message || message.role !== 'assistant') { @@ -195,21 +194,17 @@ export class InteractionCoordinator { } let blocks = parseAssistantBlocks(message.content) - const pendingEntries = reconcilePendingInteractionEntries( + const pendingEntries = await reconcilePendingInteractionEntries( instance, - collectPendingInteractionEntries(messageId, blocks) + collectPendingInteractionEntries(messageId, blocks), + interactionOwner ) - replacePendingInteractions(instance, pendingEntries) if (pendingEntries.length === 0) { throw new Error('No pending interaction found in target message.') } - const firstPendingInteraction = instance.getFirstPendingInteraction() const currentEntry = pendingEntries[0] - if ( - firstPendingInteraction?.messageId !== messageId || - firstPendingInteraction.toolCallId !== toolCallId - ) { + if (!(await instance.checkPendingInteraction(interactionLease, interactionOwner))) { throw new Error('Interaction queue out of order. Please handle the first pending item.') } @@ -249,15 +244,21 @@ export class InteractionCoordinator { this.ports.messageStore.updateAssistantContent(messageId, blocks) this.ports.messageProjection.refresh(sessionId, messageId) this.ports.messageStore.updateMessageStatus(messageId, 'pending') - this.ports.runLifecycle.transitionStatus(scope, 'generating') + await this.ports.runLifecycle.transitionStatus(scope, 'generating', interactionOwner) return { resumed: false, handledInline: result.handledInline === true } } - instance.advancePendingToolBatch({ committedResultCallId: toolCall.id }) + await instance.advancePendingToolBatch( + { committedResultCallId: toolCall.id }, + interactionOwner + ) } else if (response.kind === 'question_other') { const deferredResult = 'User chose to answer with a follow-up message.' markQuestionResolved(actionBlock, '', true) updateToolCallResponse(blocks, toolCall.id, deferredResult, false) - instance.advancePendingToolBatch({ committedResultCallId: toolCall.id }) + await instance.advancePendingToolBatch( + { committedResultCallId: toolCall.id }, + interactionOwner + ) waitingForUserMessage = true } else { const answerText = @@ -268,7 +269,10 @@ export class InteractionCoordinator { } markQuestionResolved(actionBlock, normalizedAnswer) updateToolCallResponse(blocks, toolCall.id, normalizedAnswer, false) - instance.advancePendingToolBatch({ committedResultCallId: toolCall.id }) + await instance.advancePendingToolBatch( + { committedResultCallId: toolCall.id }, + interactionOwner + ) } } else if (actionBlock.action_type === 'tool_call_permission') { if (response.kind !== 'permission') { @@ -289,6 +293,8 @@ export class InteractionCoordinator { permissionType, granted: response.granted, ownerRun: interactionOwnerRun, + controlOwner: interactionOwner, + interactionLeaseId: interactionLease.id, signal: interactionAbortSignal }), interactionAbortSignal @@ -334,8 +340,8 @@ export class InteractionCoordinator { 'duplicate_dispatch', { cause: error } ) - this.ports.runLifecycle.transitionStatus(scope, 'idle') - this.ports.interactionParking.park(sessionId, messageId) + await this.ports.runLifecycle.transitionStatus(scope, 'idle', interactionOwner) + await instance.parkInteraction(interactionLease) markPermissionResolved(actionBlock, true, permissionType) updateToolCallResponse( blocks, @@ -363,9 +369,9 @@ export class InteractionCoordinator { ) } } - replacePendingInteractions(instance, []) try { this.ports.messageStore.updateAssistantContent(messageId, blocks) + await replacePendingInteractions(instance, [], interactionOwner) this.ports.messageProjection.refresh(sessionId, messageId) } catch (projectionError) { throw new ExecutionJournalError( @@ -414,7 +420,7 @@ export class InteractionCoordinator { tool: { callId: toolCall.id, name: toolCall.name, params: toolCall.params } }) execution = await this.ports.deferredToolExecutor.execute( - sessionId, + { scope, lease: interactionLease, owner: interactionOwner }, messageId, toolCall.server_name === permissionGrant.serverName ? toolCall @@ -483,18 +489,24 @@ export class InteractionCoordinator { ) } if (execution.journalFailure) { - this.ports.runLifecycle.transitionStatus(scope, 'idle') + await this.ports.runLifecycle.transitionStatus(scope, 'idle', interactionOwner) if (!execution.journalFailure.dispatchCommitted) { throw execution.journalFailure.error } - this.ports.interactionParking.park(sessionId, messageId) + await instance.parkInteraction(interactionLease) markPermissionResolved(actionBlock, true, permissionType) if (execution.invoked) { - instance.advancePendingToolBatch({ invokedCallId: toolCall.id }) + await instance.advancePendingToolBatch( + { invokedCallId: toolCall.id }, + interactionOwner + ) } if (execution.journalFailure.outcomeCommitted) { - instance.advancePendingToolBatch({ committedResultCallId: toolCall.id }) + await instance.advancePendingToolBatch( + { committedResultCallId: toolCall.id }, + interactionOwner + ) } updateToolCallResponse(blocks, toolCall.id, execution.responseText, execution.isError) for (const block of blocks) { @@ -517,9 +529,9 @@ export class InteractionCoordinator { ) } } - replacePendingInteractions(instance, []) try { this.ports.messageStore.updateAssistantContent(messageId, blocks) + await replacePendingInteractions(instance, [], interactionOwner) this.ports.messageProjection.refresh(sessionId, messageId) } catch (projectionError) { throw new ExecutionJournalError( @@ -532,11 +544,14 @@ export class InteractionCoordinator { } markPermissionResolved(actionBlock, true, permissionType) if (execution.invoked) { - instance.advancePendingToolBatch({ invokedCallId: toolCall.id }) + await instance.advancePendingToolBatch({ invokedCallId: toolCall.id }, interactionOwner) } if (execution.terminalError) { const terminalMetadata = stampInteractionResolution(resumeAccounting, 'error') - instance.advancePendingToolBatch({ committedResultCallId: toolCall.id }) + await instance.advancePendingToolBatch( + { committedResultCallId: toolCall.id }, + interactionOwner + ) hooks.emit({ event: 'PostToolUseFailure', tool: { @@ -566,17 +581,16 @@ export class InteractionCoordinator { usage: buildUsageFromMetadata(terminalMetadata) ?? null, error: { message: execution.terminalError } }) - this.ports.runLifecycle.transitionStatus( + await this.ports.runLifecycle.transitionStatus( scope, 'error', + interactionOwner, buildUsageFromMetadata(terminalMetadata) ) - replacePendingInteractions( + await reconcilePendingInteractionEntries( instance, - reconcilePendingInteractionEntries( - instance, - collectPendingInteractionEntries(messageId, blocks) - ) + collectPendingInteractionEntries(messageId, blocks), + interactionOwner ) return { resumed: false } } @@ -602,10 +616,11 @@ export class InteractionCoordinator { } if (execution.requiresPermission && execution.permissionRequest) { - instance.transitionPendingInteractionOrigin( + await instance.transitionPendingInteractionOrigin( messageId, toolCall.id, - 'post-call-permission' + 'post-call-permission', + interactionOwner ) hooks.emit({ event: 'PermissionRequest', @@ -621,7 +636,10 @@ export class InteractionCoordinator { permissionRequest: JSON.stringify(execution.permissionRequest) } } else { - instance.advancePendingToolBatch({ committedResultCallId: toolCall.id }) + await instance.advancePendingToolBatch( + { committedResultCallId: toolCall.id }, + interactionOwner + ) shouldDispatchResolvedToolHook = true } } else { @@ -631,7 +649,10 @@ export class InteractionCoordinator { } markPermissionResolved(actionBlock, false, permissionType) updateToolCallResponse(blocks, toolCall.id, 'User denied the request.', true) - instance.advancePendingToolBatch({ committedResultCallId: toolCall.id }) + await instance.advancePendingToolBatch( + { committedResultCallId: toolCall.id }, + interactionOwner + ) shouldDispatchResolvedToolHook = true } @@ -644,10 +665,7 @@ export class InteractionCoordinator { throw new Error(`Unsupported action type: ${actionBlock.action_type}`) } - const remainingPending = reconcilePendingInteractionEntries( - instance, - collectPendingInteractionEntries(messageId, blocks) - ) + const remainingPending = collectPendingInteractionEntries(messageId, blocks) const awaitsUserFollowUp = waitingForUserMessage || hasQuestionFollowUpIntent(blocks) const finishesForUserFollowUp = awaitsUserFollowUp && remainingPending.length === 0 const persistedMetadata = finishesForUserFollowUp @@ -669,14 +687,14 @@ export class InteractionCoordinator { accountingChanged ? JSON.stringify(resumeAccounting) : undefined ) } - replacePendingInteractions(instance, remainingPending) + await reconcilePendingInteractionEntries(instance, remainingPending, interactionOwner) this.ports.messageProjection.refresh(sessionId, messageId) if (remainingPending.length > 0) { if (resumedWaitingAdmission) this.ports.continuationAdmission.suspend(sessionId) emitResolvedToolHook?.() this.ports.messageStore.updateMessageStatus(messageId, 'pending') - this.ports.runLifecycle.transitionStatus(scope, 'generating') + await this.ports.runLifecycle.transitionStatus(scope, 'generating', interactionOwner) return { resumed: false } } @@ -687,15 +705,16 @@ export class InteractionCoordinator { stopReason: 'user_follow_up', usage: buildUsageFromMetadata(persistedMetadata) }) - this.ports.runLifecycle.transitionStatus( + await this.ports.runLifecycle.transitionStatus( scope, 'idle', + interactionOwner, buildUsageFromMetadata(persistedMetadata) ) return { resumed: false, waitingForUserMessage: true } } - this.ports.runLifecycle.clearOperationController( + await this.ports.runLifecycle.clearOperationController( scope, interactionAbortController ?? undefined ) @@ -719,6 +738,7 @@ export class InteractionCoordinator { ) } if (isExecutionJournalError(error)) throw error + if (!scope.isCurrent()) return { resumed: false } if (interactionAbortSignal?.aborted) { if (!claimedToolSurfaceDeferredDispatch) { revokeToolSurfaceDeferredDispatch(sessionId, messageId, toolCallId) @@ -730,14 +750,17 @@ export class InteractionCoordinator { this.ports.messageStore.getMessage(messageId)?.metadata ?? '{}' ) if (interactionAbortController) { - this.ports.runLifecycle.clearOperationController(scope, interactionAbortController) + await this.ports.runLifecycle.clearOperationController(scope, interactionAbortController) } - instance.replacePendingInteractions([]) - this.ports.runLifecycle.settleAbortedTurn( + await instance.replacePendingInteractions([], interactionOwner) + await this.ports.runLifecycle.settleAbortedTurn( sessionId, messageId, undefined, - JSON.stringify(stampInteractionResolution(accounting, 'cancelled')) + JSON.stringify(stampInteractionResolution(accounting, 'cancelled')), + interactionAbortController + ? instance.operationOwner(interactionAbortController) + : undefined ) this.ports.runLifecycle.schedulePendingInputDrain(sessionId, 'completed') return { resumed: false } @@ -745,9 +768,9 @@ export class InteractionCoordinator { throw error } finally { if (interactionAbortController) { - this.ports.runLifecycle.clearOperationController(scope, interactionAbortController) + await this.ports.runLifecycle.clearOperationController(scope, interactionAbortController) } - instance.unlockInteraction(messageId, toolCallId) + await instance.unlockInteraction(interactionLease) } } @@ -790,10 +813,7 @@ export class InteractionCoordinator { return false } - const metadata = stampInteractionResolution( - parseMessageMetadata(message.metadata), - 'cancelled' - ) + const metadata = stampInteractionResolution(parseMessageMetadata(message.metadata), 'cancelled') const metadataJson = JSON.stringify(metadata) const remainingPending = collectPendingInteractionEntries(messageId, blocks) if (remainingPending.length === 0) { @@ -807,12 +827,14 @@ export class InteractionCoordinator { // Remove only the dismissed entry from the instance queue, preserving pending // interactions that belong to other messages. const instance = this.ports.registry.getOrHydrateScope(toAppSessionId(sessionId)).instance - instance.replacePendingInteractions( - instance.getPendingInteractions().filter( - (pending) => pending.messageId !== messageId || pending.toolCallId !== toolCallId - ) + await instance.replacePendingInteractions( + instance + .getPendingInteractions() + .filter((pending) => pending.messageId !== messageId || pending.toolCallId !== toolCallId) ) - if (this.ports.runLifecycle.settleOrphanedInteraction(sessionId, messageId, metadataJson)) { + if ( + await this.ports.runLifecycle.settleOrphanedInteraction(sessionId, messageId, metadataJson) + ) { this.ports.runLifecycle.schedulePendingInputDrain(sessionId, 'completed') } return true @@ -973,11 +995,7 @@ export class InteractionCoordinator { const permissionType = payload.permissionType const payloadServerName = payload.serverName?.trim() const toolCallServerName = toolCall.server_name?.trim() - if ( - payloadServerName && - toolCallServerName && - payloadServerName !== toolCallServerName - ) { + if (payloadServerName && toolCallServerName && payloadServerName !== toolCallServerName) { throw new Error('Permission approval tool server identity does not match the tool call.') } const serverName = toolCallServerName || payloadServerName @@ -1090,8 +1108,10 @@ export class InteractionCoordinator { sessionId: string, permission: SessionPermissionRequest ): Promise> { - const grant: SessionPermissionGrant = - await this.ports.sessionPermissionPort.approvePermission(sessionId, permission) + const grant: SessionPermissionGrant = await this.ports.sessionPermissionPort.approvePermission( + sessionId, + permission + ) if (grant?.kind === 'granted') return grant if (grant?.kind === 'command') { this.ports.sessionPermissionPort.revokeOneShotCommandPermission( diff --git a/src/main/agent/deepchat/runtime/interactionParkingRegistry.ts b/src/main/agent/deepchat/runtime/interactionParkingRegistry.ts deleted file mode 100644 index a188d6f4a2..0000000000 --- a/src/main/agent/deepchat/runtime/interactionParkingRegistry.ts +++ /dev/null @@ -1,17 +0,0 @@ -export class InteractionParkingRegistry { - private readonly messageIdsBySession = new Map>() - - park(sessionId: string, messageId: string): void { - const messageIds = this.messageIdsBySession.get(sessionId) ?? new Set() - messageIds.add(messageId) - this.messageIdsBySession.set(sessionId, messageIds) - } - - isParked(sessionId: string, messageId: string): boolean { - return this.messageIdsBySession.get(sessionId)?.has(messageId) === true - } - - clearSession(sessionId: string): void { - this.messageIdsBySession.delete(sessionId) - } -} diff --git a/src/main/agent/deepchat/runtime/interactionProjection.ts b/src/main/agent/deepchat/runtime/interactionProjection.ts index ad2e4d62ba..0de7062f75 100644 --- a/src/main/agent/deepchat/runtime/interactionProjection.ts +++ b/src/main/agent/deepchat/runtime/interactionProjection.ts @@ -1,8 +1,10 @@ import type { AssistantMessageBlock } from '@shared/types/agent-interface' +import type { HarnessControlOwner } from '@shared/harness/control' import { projectPendingInteraction } from '@shared/chat/pendingInteraction' import type { ToolCallImagePreview } from '@shared/types/core/mcp' import type { LoopRun } from '@/agent/deepchat/loop/loopRun' import type { DeepChatAgentInstance } from '@/agent/deepchat/instance/deepChatAgentInstance' +import { createStaleDeepChatInstanceError } from '@/agent/deepchat/instance/controlSession' import type { PendingToolInteraction } from './types' export type PendingInteractionEntry = { @@ -18,6 +20,7 @@ export type ProviderPermissionInteractionInput = { permissionType: 'read' | 'write' | 'all' | 'command' granted: boolean ownerRun?: LoopRun + controlOwner?: HarnessControlOwner signal?: AbortSignal } @@ -198,37 +201,41 @@ export function collectPendingInteractionEntries( return entries } -export function replacePendingInteractions( +export async function replacePendingInteractions( instance: DeepChatAgentInstance, - entries: readonly PendingInteractionEntry[] -): void { - instance.replacePendingInteractions( + entries: readonly PendingInteractionEntry[], + owner?: HarnessControlOwner, + reconcile = false +): Promise { + const accepted = await instance.replacePendingInteractions( entries.map(({ interaction }) => ({ messageId: interaction.messageId, toolCallId: interaction.toolCallId, origin: interaction.origin, order: interaction.order - })) + })), + owner, + { reconcile } ) + if (!accepted) throw createStaleDeepChatInstanceError(instance.sessionId) } -export function reconcilePendingInteractionEntries( +export async function reconcilePendingInteractionEntries( instance: DeepChatAgentInstance, - entries: PendingInteractionEntry[] -): PendingInteractionEntry[] { - const knownInteractions = instance.getPendingInteractions() - for (const entry of entries) { - const known = knownInteractions.find( - (interaction) => - interaction.messageId === entry.interaction.messageId && - interaction.toolCallId === entry.interaction.toolCallId + entries: PendingInteractionEntry[], + owner?: HarnessControlOwner +): Promise { + await replacePendingInteractions(instance, entries, owner, true) + return instance.getPendingInteractions().flatMap((known) => { + const entry = entries.find( + ({ interaction }) => + interaction.messageId === known.messageId && interaction.toolCallId === known.toolCallId ) - if (known) { - entry.interaction.origin = known.origin - entry.interaction.order = known.order - } - } - return entries.sort((left, right) => left.interaction.order - right.interaction.order) + if (!entry) return [] + entry.interaction.origin = known.origin + entry.interaction.order = known.order + return [entry] + }) } export function parseQuestionOptions(raw: unknown): Array<{ label: string; description?: string }> { diff --git a/src/main/agent/deepchat/runtime/pendingInputAdmissionCoordinator.ts b/src/main/agent/deepchat/runtime/pendingInputAdmissionCoordinator.ts index 984cda60ee..5ad76953f4 100644 --- a/src/main/agent/deepchat/runtime/pendingInputAdmissionCoordinator.ts +++ b/src/main/agent/deepchat/runtime/pendingInputAdmissionCoordinator.ts @@ -1,17 +1,14 @@ import type { ProviderModelResolutionPort } from '@/provider/settings' import logger from '@shared/logger' import type { - DeepChatSessionState, - ChatMessageRecord, MessageStartResult, - PendingInputEnqueueSource, PendingSessionInputRecord, QueuePendingInputOptions, SendMessageInput } from '@shared/types/agent-interface' import { - createStaleDeepChatInstanceError, - type SessionScopeRegistry + type SessionScopeRegistry, + type SessionRuntimeScope } from '@/agent/deepchat/instance/deepChatAgentRuntime' import { toAppSessionId } from '@/agent/shared/agentSessionIds' import { isSendMessageInputEmpty } from '@/agent/shared/agentSessionNormalization' @@ -21,11 +18,10 @@ import type { AttachmentCapabilityRouter, AttachmentPreparationResult } from '@/ocr/attachmentCapabilityRouter' -import { awaitWithAbort } from '@/lib/awaitWithAbort' import { createAbortError, PENDING_INPUT_ABORT_REASON } from './abortErrors' import { supportsProviderVision } from './providerInputCapabilities' import type { ClaimedPendingInputHandle } from './pendingInputContracts' -import type { PendingInputWakeReason } from './runLifecycleCoordinator' +import type { PendingInputPump } from './pendingInputPump' import { redactRuntimeErrorForLog } from './runtimeErrorLogging' import type { SessionSettingsCoordinator } from './sessionSettingsCoordinator' import type { SessionStateResolver } from './sessionStateResolver' @@ -41,42 +37,26 @@ export type PendingInputAdmissionStorePort = Pick< | 'isAtCapacity' | 'listPendingInputs' | 'moveQueuedInput' - | 'acceptSteerMessage' - | 'promoteQueuedInputToSteerMessage' - | 'queuePendingInput' | 'retryBlockedInput' | 'retryReleasedQueueInput' | 'updateQueuedInput' > -export interface PendingInputAdmissionPumpPort { - shouldClaimImmediately( - sessionId: string, - status: DeepChatSessionState['status'], - source: PendingInputEnqueueSource - ): boolean - startAcceptedInput( - record: PendingSessionInputRecord, - source: PendingInputEnqueueSource, - projectDir: string | null - ): void - schedule(sessionId: string, reason: PendingInputWakeReason): void - hasInteractionBlocker(sessionId: string): boolean - canDrain( - sessionId: string, - status: DeepChatSessionState['status'], - reason: PendingInputWakeReason - ): boolean - drain(sessionId: string, reason: PendingInputWakeReason): Promise - claimQueuedInputForPreparation( - sessionId: string, - itemId: string - ): ClaimedPendingInputHandle - releaseRestartHoldForInput(itemId: string): void - releaseRestartHoldForSession(sessionId: string): boolean - hasRestartHeldQueueInputs(sessionId: string): boolean - hasOnlyRestartHeldQueueInputs(sessionId: string): boolean -} +export type PendingInputAdmissionPumpPort = Pick< + PendingInputPump, + | 'enqueue' + | 'acceptVisibleSteerInput' + | 'promoteQueuedInput' + | 'schedule' + | 'hasInteractionBlocker' + | 'canDrain' + | 'drain' + | 'claimQueuedInputForPreparation' + | 'releaseRestartHoldForInput' + | 'releaseRestartHoldForSession' + | 'hasRestartHeldQueueInputs' + | 'hasOnlyRestartHeldQueueInputs' +> export interface PendingInputAdmissionCoordinatorPorts { providerSettings: Pick @@ -90,27 +70,30 @@ export interface PendingInputAdmissionCoordinatorPorts { } export class PendingInputAdmissionCoordinator { - private readonly attachmentAcceptanceTails = new Map>() - constructor(private readonly ports: PendingInputAdmissionCoordinatorPorts) {} list(sessionId: string): PendingSessionInputRecord[] { return this.ports.pendingInputs.listPendingInputs(sessionId) } - isPendingQueueResumeAvailable(sessionId: string): boolean { - return this.ports.pump.hasRestartHeldQueueInputs(sessionId) + async isPendingQueueResumeAvailable(sessionId: string): Promise { + await this.ports.registry.getOrHydrateScope(toAppSessionId(sessionId)).instance.controlReady() + return await this.ports.pump.hasRestartHeldQueueInputs(sessionId) } async queue( sessionId: string, content: string | SendMessageInput, - options?: QueuePendingInputOptions + options?: QueuePendingInputOptions, + expectedScope?: SessionRuntimeScope ): Promise { + const scope = expectedScope ?? this.ports.registry.getOrHydrateScope(toAppSessionId(sessionId)) + expectedScope?.assertCurrent() const state = await this.ports.sessionState.get(sessionId) if (!state) { throw new Error(`Session ${sessionId} not found`) } + scope.assertCurrent() const projectDir = options && Object.prototype.hasOwnProperty.call(options, 'projectDir') ? this.ports.sessionSettings.resolveProjectDir(sessionId, options.projectDir) @@ -121,22 +104,14 @@ export class PendingInputAdmissionCoordinator { throw new Error('Message cannot be empty.') } - const source = options?.source ?? 'send' - const shouldClaimImmediately = this.ports.pump.shouldClaimImmediately( + return await this.ports.pump.enqueue( sessionId, - state.status, - source + input, + options?.source ?? 'send', + projectDir, + scope, + options?.signal ) - const record = this.ports.pendingInputs.queuePendingInput(sessionId, input, { - state: shouldClaimImmediately ? 'claimed' : 'pending' - }) - - if (record.state === 'claimed') { - this.ports.pump.startAcceptedInput(record, source, projectDir) - } else { - this.ports.pump.schedule(sessionId, 'enqueue') - } - return record } async sendQueuedMessage( @@ -153,7 +128,7 @@ export class PendingInputAdmissionCoordinator { return { requestId: null, messageId: null } } - const releaseAcceptanceLane = await this.acquireAttachmentAcceptanceLane( + const { release: releaseAcceptanceLane, scope } = await this.acquireAttachmentAcceptanceLane( sessionId, 'send', context?.signal @@ -166,6 +141,7 @@ export class PendingInputAdmissionCoordinator { const prepared = await this.prepareMessageInputNow(sessionId, content, { signal: context?.signal }) + scope.assertCurrent() if (prepared.summary.status === 'needs_user_action') { return { requestId: null, @@ -176,17 +152,22 @@ export class PendingInputAdmissionCoordinator { if (context?.signal?.aborted) throw createAbortError() - await this.queue(sessionId, prepared.content, { - ...options, - signal: context?.signal - }) + await this.queue( + sessionId, + prepared.content, + { + ...options, + signal: context?.signal + }, + scope + ) return { requestId: null, messageId: null, attachmentPreparation: prepared.summary } } finally { - releaseAcceptanceLane() + await releaseAcceptanceLane() } } @@ -196,19 +177,18 @@ export class PendingInputAdmissionCoordinator { options?: { signal?: AbortSignal } ): Promise { const input = typeof content === 'string' ? { text: content, files: [] } : content - const hasPriorSteerAcceptance = this.attachmentAcceptanceTails.has( - this.buildAttachmentAcceptanceLaneKey(sessionId, 'steer') + const { release: releaseAcceptanceLane, scope } = await this.acquireAttachmentAcceptanceLane( + sessionId, + 'steer', + options?.signal ) - const releaseAcceptanceLane = - input.files?.length || hasPriorSteerAcceptance - ? await this.acquireAttachmentAcceptanceLane(sessionId, 'steer', options?.signal) - : () => {} try { const state = await this.ports.sessionState.get(sessionId) + scope.assertCurrent() if (!state) { throw new Error(`Session ${sessionId} not found`) } - if (this.ports.pump.hasInteractionBlocker(sessionId)) { + if (await this.ports.pump.hasInteractionBlocker(sessionId)) { throw new Error('Please resolve pending tool interactions before steering.') } if (isSendMessageInputEmpty(input)) { @@ -221,6 +201,7 @@ export class PendingInputAdmissionCoordinator { content: input, summary: { status: 'ready', issues: [], suggestedActions: [] } } + scope.assertCurrent() if (prepared.summary.status === 'needs_user_action') { return { requestId: null, @@ -235,13 +216,13 @@ export class PendingInputAdmissionCoordinator { attachmentPreparation: prepared.summary } - const instance = this.ports.registry.getHydratedScope(toAppSessionId(sessionId))?.instance + const instance = scope.instance const activeGeneration = instance?.getActiveGeneration() const preStreamController = instance?.getAbortController() if (activeGeneration) { this.requireActiveAssistantMessage(sessionId, activeGeneration.messageId) - const accepted = this.acceptVisibleSteerInput(sessionId, prepared.content) + const accepted = await this.acceptVisibleSteerInput(scope, prepared.content) return { ...preparedResult, userMessage: accepted.message } } @@ -251,7 +232,7 @@ export class PendingInputAdmissionCoordinator { (!preStreamController.signal.aborted || preStreamController.signal.reason === PENDING_INPUT_ABORT_REASON) ) { - const accepted = this.acceptVisibleSteerInput(sessionId, prepared.content, { + const accepted = await this.acceptVisibleSteerInput(scope, prepared.content, { preStreamAnchorMessageId: instance.getPreStreamTranscriptAnchorId() ?? null }) if (accepted.sourceMessage) { @@ -268,24 +249,24 @@ export class PendingInputAdmissionCoordinator { ? this.ports.pendingInputs.getInput(sessionId, openSteerInputId) : null if (openSteerInput?.mode === 'steer' && openSteerInput.state === 'pending') { - const accepted = this.acceptVisibleSteerInput(sessionId, prepared.content) + const accepted = await this.acceptVisibleSteerInput(scope, prepared.content) this.ports.pump.schedule(sessionId, 'enqueue') return { ...preparedResult, userMessage: accepted.message } } - if (!this.ports.pump.canDrain(sessionId, state.status, 'enqueue')) { + if (!(await this.ports.pump.canDrain(sessionId, state.status, 'enqueue'))) { throw new Error('Unable to start the steered input.') } - const accepted = this.acceptVisibleSteerInput(sessionId, prepared.content) - releaseAcceptanceLane() + const accepted = await this.acceptVisibleSteerInput(scope, prepared.content) + await releaseAcceptanceLane() const started = await this.ports.pump.drain(sessionId, 'enqueue') if (!started) { this.ports.pump.schedule(sessionId, 'enqueue') } return { ...preparedResult, userMessage: accepted.message } } finally { - releaseAcceptanceLane() + await releaseAcceptanceLane() } } @@ -314,10 +295,14 @@ export class PendingInputAdmissionCoordinator { } async steerPendingInput(sessionId: string, itemId: string): Promise { - const releaseAcceptanceLane = await this.acquireAttachmentAcceptanceLane(sessionId, 'steer') + const { release: releaseAcceptanceLane, scope } = await this.acquireAttachmentAcceptanceLane( + sessionId, + 'steer' + ) try { await this.ensureSessionReady(sessionId) - if (this.ports.pump.hasInteractionBlocker(sessionId)) { + scope.assertCurrent() + if (await this.ports.pump.hasInteractionBlocker(sessionId)) { throw new Error('Please resolve pending tool interactions before steering.') } @@ -334,40 +319,36 @@ export class PendingInputAdmissionCoordinator { throw new Error('Resolve the blocked attachment input before steering another item.') } - const claim = this.ports.pump.claimQueuedInputForPreparation(sessionId, itemId) + const claim = await this.ports.pump.claimQueuedInputForPreparation(sessionId, itemId) let prepared: AttachmentPreparationResult try { prepared = await this.prepareMessageInputNow(sessionId, pendingInput.payload) + scope.assertCurrent() } catch (error) { - this.releaseClaimAfterFailure(claim) + await this.releaseClaimAfterFailure(claim) throw error } if (prepared.summary.status === 'needs_user_action') { try { - return claim.settle({ + return await claim.settle({ kind: 'block', attachmentPreparation: prepared.summary }) } catch (error) { - this.releaseClaimAfterFailure(claim) + await this.releaseClaimAfterFailure(claim) throw error } } - claim.settle({ kind: 'release-for-mutation' }) - this.ports.pendingInputs.updateQueuedInput(sessionId, itemId, prepared.content) - - const instance = this.ports.registry.getHydratedScope(toAppSessionId(sessionId))?.instance + const instance = scope.instance const activeGeneration = instance?.getActiveGeneration() const preStreamController = instance?.getAbortController() if (activeGeneration) { this.requireActiveAssistantMessage(sessionId, activeGeneration.messageId) - const promoted = this.ports.pendingInputs.promoteQueuedInputToSteerMessage( - sessionId, - itemId + const promoted = ( + await this.ports.pump.promoteQueuedInput(scope, itemId, prepared.content, claim) ).pendingInput - this.ports.pump.releaseRestartHoldForInput(itemId) return promoted } @@ -377,9 +358,11 @@ export class PendingInputAdmissionCoordinator { (!preStreamController.signal.aborted || preStreamController.signal.reason === PENDING_INPUT_ABORT_REASON) ) { - const accepted = this.ports.pendingInputs.promoteQueuedInputToSteerMessage( - sessionId, + const accepted = await this.ports.pump.promoteQueuedInput( + scope, itemId, + prepared.content, + claim, { preStreamAnchorMessageId: instance.getPreStreamTranscriptAnchorId() ?? null } @@ -390,30 +373,27 @@ export class PendingInputAdmissionCoordinator { if (!preStreamController.signal.aborted) { preStreamController.abort(PENDING_INPUT_ABORT_REASON) } - this.ports.pump.releaseRestartHoldForInput(itemId) return accepted.pendingInput } - const record = this.ports.pendingInputs.promoteQueuedInputToSteerMessage( - sessionId, - itemId + const record = ( + await this.ports.pump.promoteQueuedInput(scope, itemId, prepared.content, claim) ).pendingInput - this.ports.pump.releaseRestartHoldForInput(itemId) - releaseAcceptanceLane() + await releaseAcceptanceLane() const started = await this.ports.pump.drain(sessionId, 'enqueue') if (!started) { this.ports.pump.schedule(sessionId, 'enqueue') } return record } finally { - releaseAcceptanceLane() + await releaseAcceptanceLane() } } async deletePendingInput(sessionId: string, itemId: string): Promise { await this.ensureSessionReady(sessionId) this.ports.pendingInputs.deletePendingInput(sessionId, itemId) - this.ports.pump.releaseRestartHoldForInput(itemId) + await this.ports.pump.releaseRestartHoldForInput(sessionId, itemId) this.ports.pump.schedule(sessionId, 'enqueue') } @@ -485,25 +465,25 @@ export class PendingInputAdmissionCoordinator { if (this.ports.pendingInputs.hasClaimedInput(sessionId)) { return false } - if (!this.ports.pump.canDrain(sessionId, state.status, 'manual')) { + if (!(await this.ports.pump.canDrain(sessionId, state.status, 'manual'))) { return false } - if (!this.ports.pump.releaseRestartHoldForSession(sessionId)) { + if (!(await this.ports.pump.releaseRestartHoldForSession(sessionId))) { return false } return await this.ports.pump.drain(sessionId, 'manual') } - assertNoActiveInputs( + async assertNoActiveInputs( sessionId: string, options?: { allowRestartHeldQueue?: boolean } - ): void { + ): Promise { if (!this.ports.pendingInputs.hasActiveInputs(sessionId)) { return } if ( options?.allowRestartHeldQueue === true && - this.ports.pump.hasOnlyRestartHeldQueueInputs(sessionId) + (await this.ports.pump.hasOnlyRestartHeldQueueInputs(sessionId)) ) { return } @@ -514,44 +494,10 @@ export class PendingInputAdmissionCoordinator { sessionId: string, lane: 'send' | 'steer', signal?: AbortSignal - ): Promise<() => void> { - const key = this.buildAttachmentAcceptanceLaneKey(sessionId, lane) - const previous = this.attachmentAcceptanceTails.get(key) ?? Promise.resolve() - let resolveSlot!: () => void - const slot = new Promise((resolve) => { - resolveSlot = resolve - }) - const tail = previous.then( - () => slot, - () => slot - ) - this.attachmentAcceptanceTails.set(key, tail) - - let released = false - const release = () => { - if (released) return - released = true - resolveSlot() - void tail.then(() => { - if (this.attachmentAcceptanceTails.get(key) === tail) { - this.attachmentAcceptanceTails.delete(key) - } - }) - } - try { - await awaitWithAbort(previous, signal) - return release - } catch (error) { - release() - throw error - } - } - - private buildAttachmentAcceptanceLaneKey( - sessionId: string, - lane: 'send' | 'steer' - ): string { - return `${lane}:${sessionId}` + ): Promise<{ release: () => Promise; scope: SessionRuntimeScope }> { + const scope = this.ports.registry.getOrHydrateScope(toAppSessionId(sessionId)) + const release = await scope.instance.acquireAttachmentAcceptance(lane, signal) + return { release, scope } } private async prepareMessageInputNow( @@ -584,36 +530,12 @@ export class PendingInputAdmissionCoordinator { } } - private acceptVisibleSteerInput( - sessionId: string, + private async acceptVisibleSteerInput( + scope: SessionRuntimeScope, input: SendMessageInput, - options?: { - preStreamAnchorMessageId?: string | null - } - ): { - pendingInput: PendingSessionInputRecord - message: ChatMessageRecord - sourceMessage?: ChatMessageRecord - } { - const instance = this.ports.registry.getHydratedScope(toAppSessionId(sessionId))?.instance - if (!instance) { - throw createStaleDeepChatInstanceError(sessionId) - } - const activeItemId = instance.getActiveSteerPendingInputId() ?? null - const activeItem = activeItemId - ? this.ports.pendingInputs.getInput(sessionId, activeItemId) - : null - const mergeItemId = - activeItem?.mode === 'steer' && activeItem.state === 'pending' ? activeItem.id : null - if (activeItemId && !mergeItemId) { - instance.clearActiveSteerPendingInputId() - } - const accepted = this.ports.pendingInputs.acceptSteerMessage(sessionId, input, { - mergeItemId, - ...(options ? { preStreamAnchorMessageId: options.preStreamAnchorMessageId ?? null } : {}) - }) - instance.setActiveSteerPendingInputId(accepted.pendingInput.id) - return accepted + options?: { preStreamAnchorMessageId?: string | null } + ) { + return await this.ports.pump.acceptVisibleSteerInput(scope, input, options) } private requireActiveAssistantMessage(sessionId: string, messageId: string): void { @@ -623,12 +545,12 @@ export class PendingInputAdmissionCoordinator { } } - private releaseClaimAfterFailure(claim: ClaimedPendingInputHandle): void { + private async releaseClaimAfterFailure(claim: ClaimedPendingInputHandle): Promise { if (claim.disposition) { return } try { - claim.settle({ kind: 'release-before-user-fact' }) + await claim.settle({ kind: 'release-before-user-fact' }) } catch (releaseError) { logger.error( `[DeepChatAgent] failed to release pending input after admission failure item=${claim.id}`, diff --git a/src/main/agent/deepchat/runtime/pendingInputContracts.ts b/src/main/agent/deepchat/runtime/pendingInputContracts.ts index 6b773479cd..378195cb36 100644 --- a/src/main/agent/deepchat/runtime/pendingInputContracts.ts +++ b/src/main/agent/deepchat/runtime/pendingInputContracts.ts @@ -1,3 +1,4 @@ +import type { HarnessControlOwner } from '@shared/harness/control' import type { AttachmentPreparationSummary, MessageStartResult, @@ -24,9 +25,15 @@ export interface ClaimedPendingInputHandle { readonly assistantMessageId: string | null readonly disposition: ClaimedInputDisposition | null + bindOwner(controller: AbortController, owner: HarnessControlOwner): void + prepareUserFact(): Promise<(effect: () => string) => string> + prepareConsume(): Promise<() => void> + rollback(effect: () => void): Promise + flushEffects(): Promise + settle( disposition: TDisposition - ): ClaimedInputSettlementResult + ): Promise> } export interface TurnCompletion { diff --git a/src/main/agent/deepchat/runtime/pendingInputPump.ts b/src/main/agent/deepchat/runtime/pendingInputPump.ts index 916225abd1..fe46d43234 100644 --- a/src/main/agent/deepchat/runtime/pendingInputPump.ts +++ b/src/main/agent/deepchat/runtime/pendingInputPump.ts @@ -1,4 +1,12 @@ import logger from '@shared/logger' +import type { + HarnessControlOwner, + HarnessControlPort, + HarnessInputSnapshot, + HarnessBeginInputResult, + HarnessClaimEffectGrant +} from '@shared/harness/control' +import { createAbortError } from './abortErrors' import type { DeepChatSessionState, PendingInputEnqueueSource, @@ -6,10 +14,6 @@ import type { SendMessageInput } from '@shared/types/agent-interface' import type { SessionRuntimeScope } from '@/agent/deepchat/instance/deepChatAgentRuntime' -import type { - DeepChatAgentInstance, - PendingQueueDrainLease -} from '@/agent/deepchat/instance/deepChatAgentInstance' import type { SessionPendingInputs } from '@/session/data/pendingInputs' import type { SessionTranscript } from '@/session/data/transcript' import { @@ -21,7 +25,6 @@ import type { PendingInputWakeReason } from './runLifecycleCoordinator' import type { RunLifecycleCoordinator } from './runLifecycleCoordinator' import { redactRuntimeErrorForLog } from './runtimeErrorLogging' import type { SessionSettingsCoordinator } from './sessionSettingsCoordinator' -import type { SessionStateResolver } from './sessionStateResolver' import type { ClaimedInputDisposition, ClaimedInputSettlementResult, @@ -32,17 +35,18 @@ import type { export type PendingInputPumpStorePort = Pick< SessionPendingInputs, + | 'updateQueuedInput' + | 'promoteQueuedInputToSteerMessage' + | 'acceptSteerMessage' + | 'queuePendingInput' | 'blockClaimedInput' | 'claimQueuedInput' | 'claimSteerInput' | 'consumeQueuedInput' | 'consumeSteerInput' | 'getInput' - | 'getNextQueuedInput' - | 'getNextSteerInput' | 'hasBlockingInput' | 'hasClaimedInput' - | 'hasPendingTurnInput' | 'listPendingInputs' | 'releaseClaimedInput' | 'releaseClaimedQueueInput' @@ -59,12 +63,6 @@ interface PendingInputGateSnapshot { pendingInteractions: PendingInteractionEntry[] } -interface LaunchedPendingInputDrain { - readonly instance: DeepChatAgentInstance - readonly lease: PendingQueueDrainLease - readonly claim: ClaimedPendingInputHandle -} - export interface PendingInputTurnStarter { start( sessionId: string, @@ -80,628 +78,884 @@ export interface PendingInputTurnContext { } export interface PendingInputPumpPorts { + control: HarnessControlPort pendingInputs: PendingInputPumpStorePort transcript: Pick runLifecycle: PendingInputPumpLifecyclePort turnStarter: PendingInputTurnStarter - sessionState: Pick sessionSettings: Pick } class DurablePendingInputClaim implements ClaimedPendingInputHandle { - private fencedDisposition: ClaimedInputDisposition | null = null + private owner: HarnessControlOwner = { kind: 'orphan' } + private controller?: AbortController + private terminalProjection: ClaimedInputDisposition | null = null + private pendingOutcome: Promise = Promise.resolve() + private prepared?: HarnessClaimEffectGrant & { attempted: boolean; revoked: boolean } + private fenced = false constructor( private readonly pendingInputs: PendingInputPumpStorePort, - private readonly sessionId: string, + private readonly scope: SessionRuntimeScope, + private readonly claimId: string, readonly id: string, - readonly source: PendingInputTurnSource + readonly source: PendingInputTurnSource, + private readonly onTerminal: () => void ) {} - get disposition(): ClaimedInputDisposition | null { - return this.fencedDisposition + get sessionId(): string { + return this.scope.sessionId } + get disposition(): ClaimedInputDisposition | null { + return this.terminalProjection + } get messageIds(): string[] { return this.requireRecord().messageIds } - get assistantMessageId(): string | null { return this.requireRecord().assistantMessageId } - settle( - disposition: TDisposition - ): ClaimedInputSettlementResult { - if (this.fencedDisposition) { - throw new Error( - `Pending input ${this.id} is already fenced as ${this.fencedDisposition.kind}` + bindOwner(controller: AbortController, owner: HarnessControlOwner): void { + this.controller = controller + this.owner = owner + } + + async flushEffects(): Promise { + await this.pendingOutcome + } + + fence(): void { + this.fenced = true + this.controller?.abort() + const ticket = this.prepared + if (ticket && !ticket.attempted && !ticket.revoked) { + ticket.revoked = true + this.pendingOutcome = this.pendingOutcome.then(() => + this.scope.instance.ackInputEffect({ + claimId: this.claimId, + effectId: ticket.effectId, + outcome: 'rejected' + }) ) + void this.pendingOutcome.catch(() => {}) } + } - this.assertClaimed() + async prepareUserFact(): Promise<(effect: () => string) => string> { + const ticket = await this.prepare('user-fact') + return (effect) => this.perform(ticket, effect) + } + + async prepareConsume(): Promise<() => void> { + const ticket = await this.prepare('consume') + return () => { + this.perform(ticket, () => this.apply({ kind: 'consume' }), { kind: 'consume' }) + } + } + + async rollback(effect: () => void): Promise { + const ticket = await this.prepare('rollback') + this.perform(ticket, effect) + await this.flushEffects() + } + + async settle( + disposition: TDisposition + ): Promise> { + const ticket = await this.prepare(disposition.kind) + let value: PendingSessionInputRecord | null try { - const result = this.apply(disposition) - this.fencedDisposition = disposition - return result as ClaimedInputSettlementResult + value = this.perform(ticket, () => this.apply(disposition), disposition) } catch (error) { - // SessionPendingInputs persists before publishing. If publication throws, remember the - // durable outcome so Turn/Pump finalization never applies a second transition. - let wasApplied = false - try { - wasApplied = this.wasApplied(disposition) - } catch (verificationError) { - // The persistence boundary was crossed but its outcome cannot be observed. Fence the claim - // conservatively so no caller can apply a second, potentially conflicting transition. - this.fencedDisposition = disposition - logger.warn( - `[DeepChatAgent] failed to verify pending input settlement session=${this.sessionId} item=${this.id}`, - redactRuntimeErrorForLog(verificationError) - ) + await this.flushEffects().catch((ackError) => this.logAckError(ackError)) + throw error + } + await this.flushEffects() + return value as ClaimedInputSettlementResult + } + + private async prepare(action: HarnessClaimEffectGrant['action']) { + await this.flushEffects() + if (this.fenced || this.scope.instance.inputEffectsFenced) + throw new Error('Input effect owner was retired') + if (this.terminalProjection) throw new Error(`Pending input ${this.id} is already settled.`) + if (this.prepared && !this.prepared.attempted && !this.prepared.revoked) { + this.prepared.revoked = true + await this.scope.instance.ackInputEffect({ + claimId: this.claimId, + effectId: this.prepared.effectId, + outcome: 'rejected' + }) + } + const grant = await this.scope.instance.prepareClaimEffect(this.claimId, this.owner, action) + const ticket = { ...grant, attempted: false, revoked: false } + if (this.fenced || this.scope.instance.inputEffectsFenced) { + await this.scope.instance.ackInputEffect({ + claimId: this.claimId, + effectId: grant.effectId, + outcome: 'rejected' + }) + throw new Error('Input effect owner was retired') + } + this.prepared = ticket + return ticket + } + + private perform( + ticket: NonNullable, + effect: () => T, + disposition?: ClaimedInputDisposition + ): T { + if (this.fenced || this.scope.instance.inputEffectsFenced || ticket.attempted || ticket.revoked) + throw new Error(`Pending input effect is already spent: ${this.id}`) + let attempted = false + let outcome: 'applied' | 'rejected' | 'unknown' = 'rejected' + let value: T | undefined + let appliedMessageId: string | undefined + try { + if ( + ticket.action === 'user-fact' || + ticket.action === 'rollback' || + ticket.action === 'block' || + ticket.action === 'release-for-mutation' + ) { + this.scope.assertCurrent() + if (ticket.action === 'user-fact' && this.controller?.signal.aborted) + throw createAbortError() } - if (wasApplied) { - this.fencedDisposition = disposition + this.assertClaimed() + ticket.attempted = true + attempted = true + value = effect() + if (ticket.action === 'user-fact' && typeof value === 'string') appliedMessageId = value + outcome = 'applied' + return value + } catch (error) { + if (attempted) { + try { + if (disposition) outcome = this.settlementOutcome(disposition) + else if (ticket.action === 'user-fact') { + appliedMessageId = this.requireRecord().messageIds.at(-1) + outcome = appliedMessageId ? 'applied' : 'rejected' + } else outcome = 'unknown' + } catch (verificationError) { + outcome = 'unknown' + logger.warn( + `[DeepChatAgent] failed to verify input effect item=${this.id}`, + redactRuntimeErrorForLog(verificationError) + ) + } } throw error + } finally { + ticket.revoked = true + // A spent or indeterminate effect only narrows the already granted Root ticket. + if (disposition && outcome !== 'rejected') this.terminalProjection = disposition + const acknowledgement = this.scope.instance.ackInputEffect({ + claimId: this.claimId, + effectId: ticket.effectId, + outcome, + ...(appliedMessageId ? { messageId: appliedMessageId } : {}) + }) + this.pendingOutcome = acknowledgement.then(() => { + if (disposition && outcome !== 'rejected') this.onTerminal() + }) + void this.pendingOutcome.catch(() => {}) + void acknowledgement.catch(() => {}) } } + private logAckError(error: unknown): void { + logger.warn( + `[DeepChatAgent] input effect acknowledgement failed item=${this.id}`, + redactRuntimeErrorForLog(error) + ) + } + private apply(disposition: ClaimedInputDisposition): PendingSessionInputRecord | null { + const sessionId = this.scope.sessionId switch (disposition.kind) { case 'consume': - if (this.source === 'steer') { - this.pendingInputs.consumeSteerInput(this.sessionId, this.id) - } else { - this.pendingInputs.consumeQueuedInput(this.sessionId, this.id) - } + if (this.source === 'steer') this.pendingInputs.consumeSteerInput(sessionId, this.id) + else this.pendingInputs.consumeQueuedInput(sessionId, this.id) return null case 'block': return this.pendingInputs.blockClaimedInput( - this.sessionId, + sessionId, this.id, disposition.attachmentPreparation ) case 'release-for-mutation': return this.source === 'steer' - ? this.pendingInputs.releaseClaimedInput(this.sessionId, this.id) - : this.pendingInputs.releaseClaimedQueueInput(this.sessionId, this.id) + ? this.pendingInputs.releaseClaimedInput(sessionId, this.id) + : this.pendingInputs.releaseClaimedQueueInput(sessionId, this.id) case 'release-before-user-fact': case 'release-after-rollback': return this.source === 'steer' - ? this.pendingInputs.releaseClaimedInput(this.sessionId, this.id) - : this.pendingInputs.releaseClaimedQueueInputForRetry(this.sessionId, this.id) + ? this.pendingInputs.releaseClaimedInput(sessionId, this.id) + : this.pendingInputs.releaseClaimedQueueInputForRetry(sessionId, this.id) } } private assertClaimed(): void { const record = this.requireRecord() - const expectedMode = this.source === 'steer' ? 'steer' : 'queue' - if (record.mode !== expectedMode) { - throw new Error(`Pending input ${this.id} changed mode before settlement.`) - } - if (record.state !== 'claimed') { - throw new Error(`Pending input ${this.id} is not claimed.`) - } + if (record.mode !== (this.source === 'steer' ? 'steer' : 'queue') || record.state !== 'claimed') + throw new Error(`Pending input ${this.id} is not the originating claimed input.`) } private requireRecord(): PendingSessionInputRecord { - const record = this.pendingInputs.getInput(this.sessionId, this.id) - if (!record) { - throw new Error(`Pending input not found: ${this.id}`) - } + const record = this.pendingInputs.getInput(this.scope.sessionId, this.id) + if (!record) throw new Error(`Pending input not found: ${this.id}`) return record } - private wasApplied(disposition: ClaimedInputDisposition): boolean { - const record = this.pendingInputs.getInput(this.sessionId, this.id) + private settlementOutcome( + disposition: ClaimedInputDisposition + ): 'applied' | 'rejected' | 'unknown' { + const record = this.pendingInputs.getInput(this.scope.sessionId, this.id) const expectedMode = this.source === 'steer' ? 'steer' : 'queue' + let applied: boolean switch (disposition.kind) { case 'consume': - return this.source === 'steer' - ? record?.mode === expectedMode && record.state === 'consumed' - : record === null + applied = + this.source === 'steer' + ? record?.mode === expectedMode && record.state === 'consumed' + : record === null + break case 'block': - return record?.mode === expectedMode && record.state === 'blocked' + applied = record?.mode === expectedMode && record.state === 'blocked' + break case 'release-for-mutation': - return record?.mode === expectedMode && record.state === 'pending' + applied = record?.mode === expectedMode && record.state === 'pending' + break case 'release-before-user-fact': case 'release-after-rollback': - return ( + applied = record?.mode === expectedMode && record.state === (this.source === 'queue' ? 'retry_required' : 'pending') - ) + break } + if (applied) return 'applied' + return record?.mode === expectedMode && record.state === 'claimed' ? 'rejected' : 'unknown' } } export class PendingInputPump { - private readonly launchedDrains = new Map() - private readonly deferredWakeups = new Map() - private readonly restartHeldQueueInputIds = new Set() - private readonly manuallyResumedQueueInputIds = new Set() + private recovery: Promise = Promise.resolve() + private epoch = 0 + private suspended = false + private readonly turns = new Set>() + private readonly effectHandles = new Set() constructor(private readonly ports: PendingInputPumpPorts) {} - holdRestartedQueueInputs(inputIds: Iterable): void { - for (const inputId of inputIds) { - this.restartHeldQueueInputIds.add(inputId) + fenceSessionInputEffects(sessionId: string): void { + const handles = [...this.effectHandles] + for (const handle of handles) { + if (handle.sessionId === sessionId) handle.fence() } } - replaceRestartedQueueInputs(inputIds: Iterable): void { - this.restartHeldQueueInputIds.clear() - this.manuallyResumedQueueInputIds.clear() - this.holdRestartedQueueInputs(inputIds) + fenceInputEffectsForRecovery(): Promise { + this.epoch++ + this.suspended = true + const handles = [...this.effectHandles] + for (const handle of handles) handle.fence() + return Promise.all(handles.map((handle) => handle.flushEffects())).then(() => {}) } - releaseRestartHoldForInput(itemId: string): void { - this.restartHeldQueueInputIds.delete(itemId) - this.manuallyResumedQueueInputIds.delete(itemId) + replaceRestartedQueueInputs( + sessions: Array<{ sessionId: string; heldInputIds: string[] }>, + retiredEffects: Promise = Promise.resolve() + ): Promise { + this.recovery = retiredEffects + .then(() => this.ports.control.send({ method: 'inputRecovery', sessions })) + .then((receipt) => { + if (receipt.outcome !== 'accepted') throw new Error('Input recovery was rejected') + this.suspended = false + }) + void this.recovery.catch(() => {}) + return this.recovery } - releaseRestartHoldForSession(sessionId: string): boolean { - const queueInputs = this.ports.pendingInputs - .listPendingInputs(sessionId) - .filter((input) => input.mode === 'queue') - .sort((left, right) => (left.queueOrder ?? 0) - (right.queueOrder ?? 0)) - const resumedHead = queueInputs[0] - if ( - !resumedHead || - resumedHead.state !== 'pending' || - !this.restartHeldQueueInputIds.has(resumedHead.id) - ) { - return false - } - for (const input of queueInputs) { - if (this.restartHeldQueueInputIds.has(input.id)) { - this.restartHeldQueueInputIds.delete(input.id) - } + async drainForMaintenance(): Promise { + const effects = this.fenceInputEffectsForRecovery() + let timer: ReturnType | undefined + try { + await Promise.race([ + Promise.all([effects, ...this.turns]), + new Promise((_, reject) => { + timer = setTimeout( + () => reject(new Error('Timed out waiting for pending input turns (30000ms)')), + 30_000 + ) + }) + ]) + } finally { + clearTimeout(timer) } - this.manuallyResumedQueueInputIds.add(resumedHead.id) - return true } - hasRestartHeldQueueInputs(sessionId: string): boolean { - const head = this.ports.pendingInputs - .listPendingInputs(sessionId) - .filter((input) => input.mode === 'queue') - .sort((left, right) => (left.queueOrder ?? 0) - (right.queueOrder ?? 0))[0] - return Boolean( - head?.state === 'pending' && this.restartHeldQueueInputIds.has(head.id) - ) + async releaseRestartHoldForInput(sessionId: string, itemId: string): Promise { + await this.recovery + const scope = this.ports.runLifecycle.getHydratedScope(sessionId) + if (scope) await scope.instance.inputHold({ action: 'release-item', itemId }) } - hasOnlyRestartHeldQueueInputs(sessionId: string): boolean { - if ( - this.ports.pendingInputs.hasBlockingInput(sessionId) || - this.ports.pendingInputs.hasClaimedInput(sessionId) - ) { - return false - } - const inputs = this.ports.pendingInputs.listPendingInputs(sessionId) - return ( - inputs.length > 0 && - inputs.every( - (input) => input.mode === 'queue' && this.restartHeldQueueInputIds.has(input.id) - ) - ) + async releaseRestartHoldForSession(sessionId: string): Promise { + await this.recovery + const scope = this.ports.runLifecycle.getHydratedScope(sessionId) + if (!scope) return false + const observed = this.capture(sessionId) + const head = observed.snapshot.queue[0] + if (head?.state !== 'pending') return false + return await scope.instance.inputHold({ + action: 'resume', + headId: head.id, + queueIds: observed.snapshot.queue.map(({ id }) => id) + }) } - hasInteractionBlocker(sessionId: string): boolean { - const snapshot = this.readGateSnapshot(sessionId) - return ( - snapshot.awaitingQuestionFollowUp || - this.ports.runLifecycle.reconcilePendingInteractions( - sessionId, - snapshot.pendingInteractions - ) - ) + async hasRestartHeldQueueInputs(sessionId: string): Promise { + return (await this.query(sessionId))?.hasRestartHeld ?? false } - shouldClaimImmediately( - sessionId: string, - status: DeepChatSessionState['status'], - source: PendingInputEnqueueSource - ): boolean { - if (source !== 'send' && !this.canDrainFromStatus(status, 'enqueue')) { - return false - } - const instance = this.ports.runLifecycle.getHydratedScope(sessionId)?.instance - const snapshot = this.readGateSnapshot(sessionId) - const isUnclaimedQuestionFollowUp = - source === 'send' && - snapshot.awaitingQuestionFollowUp && - !this.ports.pendingInputs.hasBlockingInput(sessionId) && - !this.ports.pendingInputs.hasClaimedInput(sessionId) && - !instance?.isPendingQueueDraining() - const hasRetryRequiredInput = this.ports.pendingInputs - .listPendingInputs(sessionId) - .some((input) => input.mode === 'queue' && input.state === 'retry_required') - - if (isUnclaimedQuestionFollowUp && !hasRetryRequiredInput) { - return true - } - if (!this.canDrainWithSnapshot(sessionId, status, 'enqueue', snapshot)) { - return false - } - const hasWaitingTurnInput = - hasRetryRequiredInput || - (source === 'send' - ? this.hasExecutablePendingTurnInput(sessionId) - : this.ports.pendingInputs.hasPendingTurnInput(sessionId)) + async hasOnlyRestartHeldQueueInputs(sessionId: string): Promise { + return (await this.query(sessionId))?.onlyRestartHeld ?? false + } + + async hasInteractionBlocker(sessionId: string): Promise { + await this.recovery + const gate = this.readGateSnapshot(sessionId) return ( - !hasWaitingTurnInput && - !this.ports.pendingInputs.hasBlockingInput(sessionId) && - !this.ports.pendingInputs.hasClaimedInput(sessionId) + gate.awaitingQuestionFollowUp || + (await this.ports.runLifecycle.reconcilePendingInteractions( + sessionId, + gate.pendingInteractions + )) ) } - canDrain( + async canDrain( sessionId: string, - status: DeepChatSessionState['status'], + _status: DeepChatSessionState['status'], reason: PendingInputWakeReason - ): boolean { - if (!this.canDrainFromStatus(status, reason)) { - return false - } - return this.canDrainWithSnapshot( - sessionId, - status, - reason, - this.readGateSnapshot(sessionId) - ) + ): Promise { + return (await this.query(sessionId, reason))?.canDrain ?? false } - private canDrainWithSnapshot( + async enqueue( sessionId: string, - status: DeepChatSessionState['status'], - reason: PendingInputWakeReason, - snapshot: PendingInputGateSnapshot - ): boolean { - if (!this.meetsDrainPreconditions(sessionId, status, reason, snapshot)) { - return false - } - return !this.ports.runLifecycle - .getHydratedScope(sessionId) - ?.instance.isPendingQueueDraining() - } - - startAcceptedInput( - record: PendingSessionInputRecord, + input: SendMessageInput, source: PendingInputEnqueueSource, - projectDir: string | null - ): void { - if (record.mode !== 'queue' || record.state !== 'claimed') { - throw new Error(`Accepted pending input ${record.id} is not a claimed queue input.`) - } - const scope = this.ports.runLifecycle.getHydratedScope(record.sessionId) - const claim = this.createClaim(record.sessionId, record.id, source) - const drainLease = scope?.instance.tryAcquirePendingQueueDrain() ?? null - if (!scope || !drainLease) { - const released = this.tryRelease(claim) - logger.error( - `[DeepChatAgent] pending input start rejected session=${record.sessionId} stage=adopt-claim` - ) - if (released) { - this.schedule(record.sessionId, 'enqueue') + projectDir: string | null, + expectedScope?: SessionRuntimeScope, + signal?: AbortSignal + ): Promise { + const epoch = this.epoch + await this.recovery + const scope = expectedScope ?? this.ports.runLifecycle.getHydratedScope(sessionId) + if (!scope) throw new Error(`Session ${sessionId} is not hydrated`) + while (true) { + if (signal?.aborted) throw createAbortError() + const observed = await this.observe(scope, epoch) + const grant = await scope.instance.beginInput({ + intent: 'enqueue', + source, + snapshot: observed.snapshot + }) + if (grant.action !== 'pending' && grant.action !== 'claim') + throw new Error('Pending input acceptance is unavailable') + if (!this.matches(scope, observed)) { + await this.rejectGrant(scope, grant) + continue + } + let transferred = false + try { + let record: PendingSessionInputRecord + let attempted = false + try { + scope.assertCurrent() + if (signal?.aborted) throw createAbortError() + attempted = true + record = this.ports.pendingInputs.queuePendingInput(sessionId, input, { + state: grant.action === 'claim' ? 'claimed' : 'pending' + }) + } catch (error) { + if (grant.effectId) + await scope.instance.ackInputEffect({ + claimId: grant.claimId, + effectId: grant.effectId, + outcome: attempted ? 'unknown' : 'rejected' + }) + throw error + } + if (grant.action === 'pending') { + if (grant.wakeReason) this.schedule(sessionId, grant.wakeReason) + return record + } + this.requireClaimGrant(grant) + await scope.instance.ackInputEffect({ + claimId: grant.claimId, + effectId: grant.effectId!, + outcome: 'applied', + inputId: record.id + }) + const claim = this.createClaim(scope, record.id, source, grant.claimId!) + if (!scope.isCurrent()) { + await this.tryRelease(claim) + this.effectHandles.delete(claim) + return record + } + this.launch(scope, record, claim, projectDir, 'enqueue', grant) + transferred = true + return record + } finally { + if (!transferred && grant.drainLeaseId) await this.end(scope, grant.drainLeaseId) } - return } - - this.launch(scope, record, claim, projectDir, 'enqueue', drainLease) } - claimQueuedInputForPreparation( + async claimQueuedInputForPreparation( sessionId: string, itemId: string - ): ClaimedPendingInputHandle { - const claim = this.createClaim(sessionId, itemId, 'queue') + ): Promise { + const epoch = this.epoch + await this.recovery + const scope = this.ports.runLifecycle.getHydratedScope(sessionId) + if (!scope) throw new Error(`Session ${sessionId} is not hydrated`) + const observed = await this.observe(scope, epoch) + const grant = await scope.instance.beginInput({ + intent: 'prepare', + source: 'queue', + itemId, + snapshot: observed.snapshot + }) + this.requireClaimGrant(grant) + if (!this.matches(scope, observed)) { + await this.rejectGrant(scope, grant) + throw new Error('Queued input changed before preparation') + } + const claim = this.createClaim(scope, itemId, 'queue', grant.claimId!) + let record: PendingSessionInputRecord try { - this.ports.pendingInputs.claimQueuedInput(sessionId, itemId) - return claim + scope.assertCurrent() + record = this.ports.pendingInputs.claimQueuedInput(sessionId, itemId) } catch (error) { - this.tryRelease(claim) + const outcome = this.claimOutcome(sessionId, itemId) + await scope.instance.ackInputEffect({ + claimId: grant.claimId, + effectId: grant.effectId!, + outcome, + ...(outcome === 'applied' ? { inputId: itemId } : {}) + }) + if (outcome === 'applied') await this.tryRelease(claim) + this.effectHandles.delete(claim) throw error } + await scope.instance.ackInputEffect({ + claimId: grant.claimId, + effectId: grant.effectId!, + outcome: 'applied', + inputId: record.id + }) + return claim } - async drain(sessionId: string, reason: PendingInputWakeReason): Promise { - const scope = this.ports.runLifecycle.getHydratedScope(sessionId) - if (!scope) { - return false - } - const drainLease = scope.instance.tryAcquirePendingQueueDrain() - if (!drainLease) { - if (this.shouldDeferWakeup(scope, reason)) { - this.rememberDeferredWakeup(sessionId, reason) + async acceptVisibleSteerInput( + scope: SessionRuntimeScope, + input: SendMessageInput, + options?: { preStreamAnchorMessageId?: string | null } + ) { + const epoch = this.epoch + await this.recovery + while (true) { + const observed = await this.observe(scope, epoch) + const grant = await scope.instance.beginInput({ + intent: 'steer', + source: 'steer', + snapshot: observed.snapshot + }) + if (grant.action !== 'steer' || !grant.effectId) + throw new Error('Steer acceptance is unavailable') + if (!this.matches(scope, observed)) { + await this.rejectGrant(scope, grant) + continue + } + let accepted: ReturnType + let attempted = false + try { + scope.assertCurrent() + attempted = true + accepted = this.ports.pendingInputs.acceptSteerMessage(scope.sessionId, input, { + mergeItemId: grant.mergeItemId ?? null, + ...(options ? { preStreamAnchorMessageId: options.preStreamAnchorMessageId ?? null } : {}) + }) + } catch (error) { + await scope.instance + .ackInputEffect({ effectId: grant.effectId, outcome: attempted ? 'unknown' : 'rejected' }) + .catch((ackError) => + this.logDrainError(scope.sessionId, 'enqueue', 'acknowledge-steer', ackError) + ) + throw error } - return false + await scope.instance.ackInputEffect({ + effectId: grant.effectId, + outcome: 'applied', + inputId: accepted.pendingInput.id + }) + return accepted } + } - let launchOwnsLease = false + async promoteQueuedInput( + scope: SessionRuntimeScope, + itemId: string, + content: SendMessageInput, + claim: ClaimedPendingInputHandle, + options?: { preStreamAnchorMessageId?: string | null } + ) { + const epoch = this.epoch + let grant: HarnessBeginInputResult | undefined + let promotionAttempted = false + let acknowledgementStarted = false try { - const state = await this.ports.sessionState.get(sessionId) - if (!state || !scope.isCurrent()) { - return false - } - const snapshot = this.readGateSnapshot(sessionId) - if (!this.meetsDrainPreconditions(sessionId, state.status, reason, snapshot)) { - return false - } - if ( - this.ports.pendingInputs.hasBlockingInput(sessionId) || - this.ports.pendingInputs.hasClaimedInput(sessionId) - ) { - return false - } - - const nextSteerInput = this.ports.pendingInputs.getNextSteerInput(sessionId) - const nextQueuedInput = nextSteerInput - ? null - : this.ports.pendingInputs.getNextQueuedInput(sessionId) - if (nextQueuedInput && this.restartHeldQueueInputIds.has(nextQueuedInput.id)) { - return false - } - const nextPendingInput = nextSteerInput ?? nextQueuedInput - if (!nextPendingInput) { - return false + await this.recovery + if (claim.id !== itemId || claim.source !== 'queue') + throw new Error('Invalid prepared input claim') + const observed = await this.observe(scope, epoch) + grant = await scope.instance.beginInput({ + intent: 'steer', + source: 'steer', + itemId, + snapshot: observed.snapshot + }) + if (grant.action !== 'steer' || !grant.effectId) + throw new Error('Steer promotion is unavailable') + if (!this.matches(scope, observed)) throw new Error('Queued input changed before promotion') + await claim.settle({ kind: 'release-for-mutation' }) + this.assertInputOwner(scope, epoch) + this.ports.pendingInputs.updateQueuedInput(scope.sessionId, itemId, content) + this.assertInputOwner(scope, epoch) + promotionAttempted = true + const accepted = this.ports.pendingInputs.promoteQueuedInputToSteerMessage( + scope.sessionId, + itemId, + options + ) + acknowledgementStarted = true + await scope.instance.ackInputEffect({ + effectId: grant.effectId, + outcome: 'applied', + inputId: itemId + }) + return accepted + } catch (error) { + if (grant?.effectId && !acknowledgementStarted) { + let outcome: 'applied' | 'rejected' | 'unknown' = 'rejected' + if (promotionAttempted) { + try { + const record = this.ports.pendingInputs.getInput(scope.sessionId, itemId) + outcome = + record?.mode === 'steer' + ? 'applied' + : record?.mode === 'queue' + ? 'rejected' + : 'unknown' + } catch { + outcome = 'unknown' + } + } + await scope.instance + .ackInputEffect({ + effectId: grant.effectId, + outcome, + ...(outcome === 'applied' ? { inputId: itemId } : {}) + }) + .catch((ackError) => + this.logDrainError(scope.sessionId, 'enqueue', 'acknowledge-promotion', ackError) + ) } + await this.tryRelease(claim) + throw error + } + } - let projectDir: string | null - try { - projectDir = this.ports.sessionSettings.resolveProjectDir(sessionId) - } catch (error) { - this.logDrainError(sessionId, reason, 'resolve-project-dir', error) + async drain(sessionId: string, reason: PendingInputWakeReason): Promise { + const epoch = this.epoch + await this.recovery + if (this.suspended) return false + const scope = this.ports.runLifecycle.getHydratedScope(sessionId) + if (!scope) return false + const observed = await this.observe(scope, epoch) + const projectDir = this.ports.sessionSettings.resolveProjectDir(sessionId) + const grant = await scope.instance.beginInput({ + intent: 'drain', + source: 'queue', + reason, + snapshot: observed.snapshot + }) + if (grant.action !== 'claim') return false + this.requireClaimGrant(grant) + let transferred = false + try { + if (!this.matches(scope, observed)) { + await this.rejectGrant(scope, { ...grant, drainLeaseId: undefined }) return false } - - const source: PendingInputTurnSource = nextSteerInput ? 'steer' : 'queue' - const claim = this.createClaim(sessionId, nextPendingInput.id, source) - - let claimedInput: PendingSessionInputRecord + const itemId = grant.inputId! + const source = grant.source! + const claim = this.createClaim(scope, itemId, source, grant.claimId!) + let record: PendingSessionInputRecord + let acknowledged = false try { scope.assertCurrent() - claimedInput = + record = source === 'steer' - ? this.ports.pendingInputs.claimSteerInput(sessionId, nextPendingInput.id) - : this.ports.pendingInputs.claimQueuedInput(sessionId, nextPendingInput.id) + ? this.ports.pendingInputs.claimSteerInput(sessionId, itemId) + : this.ports.pendingInputs.claimQueuedInput(sessionId, itemId) } catch (error) { - const persisted = this.ports.pendingInputs.getInput(sessionId, nextPendingInput.id) - if ( - source !== 'steer' || - persisted?.state !== 'claimed' || - !persisted.assistantMessageId - ) { - this.tryRelease(claim) + const outcome = this.claimOutcome(sessionId, itemId) + await scope.instance.ackInputEffect({ + claimId: grant.claimId, + effectId: grant.effectId!, + outcome, + ...(outcome === 'applied' ? { inputId: itemId } : {}) + }) + acknowledged = true + const persisted = + outcome === 'applied' ? this.ports.pendingInputs.getInput(sessionId, itemId) : null + if (source !== 'steer' || !persisted?.assistantMessageId) { + if (outcome === 'applied') await this.tryRelease(claim) + this.effectHandles.delete(claim) this.logDrainError(sessionId, reason, 'claim-input', error) return false } - claimedInput = persisted + record = persisted + } + if (!acknowledged) { + await scope.instance.ackInputEffect({ + claimId: grant.claimId, + effectId: grant.effectId!, + outcome: 'applied', + inputId: record.id + }) } - if (!scope.isCurrent() && source === 'queue') { - this.tryRelease(claim) + await this.tryRelease(claim) + this.effectHandles.delete(claim) return false } - - if (source === 'steer') { - scope.instance.clearActiveSteerPendingInputId(claimedInput.id) - } - - this.launch(scope, claimedInput, claim, projectDir, reason, drainLease) - launchOwnsLease = true + this.launch(scope, record, claim, projectDir, reason, grant) + transferred = true return true } finally { - if (!launchOwnsLease) { - scope.instance.releasePendingQueueDrain(drainLease) - this.flushDeferredWakeup(sessionId) - } + if (!transferred) await this.end(scope, grant.drainLeaseId!) } } schedule(sessionId: string, reason: PendingInputWakeReason): void { - void this.drain(sessionId, reason).catch((error) => { - logger.error( - `[DeepChatAgent] drainPendingQueueIfPossible error session=${sessionId} reason=${reason}`, - redactRuntimeErrorForLog(error) - ) - }) + void this.drain(sessionId, reason).catch((error) => + this.logDrainError(sessionId, reason, 'drain', error) + ) + } + + private async query(sessionId: string, reason?: PendingInputWakeReason) { + await this.recovery + const scope = this.ports.runLifecycle.getHydratedScope(sessionId) + if (!scope) return undefined + const observed = await this.observe(scope) + return await scope.instance.queryInput(observed.snapshot, reason) + } + + private capture(sessionId: string) { + const gate = this.readGateSnapshot(sessionId) + const inputs = this.ports.pendingInputs.listPendingInputs(sessionId) + const queue = inputs + .filter((input) => input.mode === 'queue') + .sort((left, right) => (left.queueOrder ?? 0) - (right.queueOrder ?? 0)) + .map(({ id, state }) => ({ id, state: state as 'pending' | 'blocked' | 'retry_required' })) + const steer = inputs + .filter((input) => input.mode === 'steer') + .sort((left, right) => left.createdAt - right.createdAt)[0] + const snapshot: HarnessInputSnapshot = { + queue, + steerHead: steer ? { id: steer.id, state: steer.state as 'pending' | 'blocked' } : null, + hasClaimed: this.ports.pendingInputs.hasClaimedInput(sessionId), + hasBlocking: this.ports.pendingInputs.hasBlockingInput(sessionId), + awaitingFollowUp: gate.awaitingQuestionFollowUp + } + return { + snapshot, + gate, + epoch: this.epoch, + key: JSON.stringify({ + snapshot, + refs: gate.pendingInteractions.map(({ interaction }) => ({ + messageId: interaction.messageId, + toolCallId: interaction.toolCallId, + origin: interaction.origin, + order: interaction.order + })) + }) + } + } + + private matches( + scope: SessionRuntimeScope, + observed: ReturnType + ): boolean { + return ( + !this.suspended && + !scope.instance.inputEffectsFenced && + this.epoch === observed.epoch && + this.capture(scope.sessionId).key === observed.key + ) + } + + private assertInputOwner(scope: SessionRuntimeScope, epoch: number): void { + scope.assertCurrent() + if (this.suspended || scope.instance.inputEffectsFenced || epoch !== this.epoch) + throw new Error('Input effect owner was retired') + } + + private async observe(scope: SessionRuntimeScope, epoch = this.epoch) { + await scope.instance.controlReady() + this.assertInputOwner(scope, epoch) + const observed = this.capture(scope.sessionId) + await this.ports.runLifecycle.reconcilePendingInteractions( + scope.sessionId, + observed.gate.pendingInteractions + ) + scope.assertCurrent() + return observed + } + + private requireClaimGrant(grant: HarnessBeginInputResult): void { + if ( + !grant.claimId || + !grant.effectId || + (grant.action === 'claim' && !grant.drainLeaseId) || + (grant.action !== 'claim' && grant.action !== 'prepare') + ) + throw new Error('Pending input claim was not granted') + } + + private claimOutcome(sessionId: string, itemId: string): 'applied' | 'rejected' | 'unknown' { + try { + const record = this.ports.pendingInputs.getInput(sessionId, itemId) + return record?.state === 'claimed' + ? 'applied' + : record?.state === 'pending' + ? 'rejected' + : 'unknown' + } catch { + return 'unknown' + } + } + + private createClaim( + scope: SessionRuntimeScope, + itemId: string, + source: PendingInputTurnSource, + claimId: string + ): DurablePendingInputClaim { + const handle = new DurablePendingInputClaim( + this.ports.pendingInputs, + scope, + claimId, + itemId, + source, + () => this.effectHandles.delete(handle) + ) + this.effectHandles.add(handle) + return handle + } + + private async rejectGrant( + scope: SessionRuntimeScope, + grant: HarnessBeginInputResult + ): Promise { + if (grant.effectId) + await scope.instance.ackInputEffect({ + effectId: grant.effectId, + ...(grant.claimId ? { claimId: grant.claimId } : {}), + outcome: 'rejected' + }) + if (grant.drainLeaseId) await this.end(scope, grant.drainLeaseId) } private launch( scope: SessionRuntimeScope, - claimedInput: PendingSessionInputRecord, + record: PendingSessionInputRecord, claim: ClaimedPendingInputHandle, projectDir: string | null, reason: PendingInputWakeReason, - drainLease: PendingQueueDrainLease + grant: HarnessBeginInputResult ): void { - this.launchedDrains.set(claimedInput.sessionId, { - instance: scope.instance, - lease: drainLease, - claim - }) let turn: Promise try { - turn = this.ports.turnStarter.start(claimedInput.sessionId, claimedInput.payload, { + turn = this.ports.turnStarter.start(record.sessionId, record.payload, { projectDir, claimedInput: claim, - consumeClaimBeforeProviderStream: - claim.source === 'queue' && this.manuallyResumedQueueInputIds.has(claimedInput.id) + consumeClaimBeforeProviderStream: grant.consumeBeforeProviderStream }) } catch (error) { turn = Promise.reject(error) } - void turn + const completion = turn .then( - (completion) => { - const mismatch = this.getCompletionMismatch(claimedInput.id, claim, completion) - if (mismatch && claim.disposition) { + (result) => { + if (!claim.disposition || result.claimedInputDisposition?.kind !== claim.disposition.kind) this.logDrainError( - claimedInput.sessionId, + record.sessionId, reason, 'claim-consistency', - mismatch + new Error('Pending input completion mismatch') ) - } }, - (error) => { - this.logDrainError(claimedInput.sessionId, reason, 'process-message', error) - } + (error) => this.logDrainError(record.sessionId, reason, 'process-message', error) ) .finally(async () => { - if (!claim.disposition) { + await claim.flushEffects() + if (!claim.disposition) this.logDrainError( - claimedInput.sessionId, + record.sessionId, reason, 'unsettled-claim', - new Error(`Turn left pending input ${claimedInput.id} unsettled.`) + new Error(`Turn left pending input ${record.id} unsettled.`) ) - } - if (claim.disposition?.kind === 'consume') { - this.manuallyResumedQueueInputIds.delete(claimedInput.id) - } - this.clearLaunchedDrain(claimedInput.sessionId, scope.instance, drainLease) - scope.instance.releasePendingQueueDrain(drainLease) - this.flushDeferredWakeup(claimedInput.sessionId) - await this.scheduleNextIfReady(claimedInput.sessionId, claimedInput.id, reason) - }) - .catch((error) => { - this.logDrainError(claimedInput.sessionId, reason, 'finalization', error) + await this.end(scope, grant.drainLeaseId!) + const stillWaiting = this.ports.pendingInputs + .listPendingInputs(record.sessionId) + .some( + ({ id, state }) => + id === record.id && (state === 'pending' || state === 'retry_required') + ) + if (!stillWaiting) this.schedule(record.sessionId, 'completed') }) + .catch((error) => this.logDrainError(record.sessionId, reason, 'finalization', error)) + .finally(() => this.turns.delete(completion)) + this.turns.add(completion) } - private shouldDeferWakeup( - scope: SessionRuntimeScope, - reason: PendingInputWakeReason - ): boolean { - const launchedDrain = this.launchedDrains.get(scope.sessionId) - const status = scope.state()?.status - return ( - launchedDrain?.instance === scope.instance && - launchedDrain.claim.disposition !== null && - status !== undefined && - this.canDrainFromStatus(status, reason) - ) + private async end(scope: SessionRuntimeScope, drainLeaseId: string): Promise { + const result = await scope.instance.endInput(drainLeaseId) + if (result.nextWake) this.schedule(scope.sessionId, result.nextWake) } - private clearLaunchedDrain( - sessionId: string, - instance: DeepChatAgentInstance, - lease: PendingQueueDrainLease - ): void { - const launchedDrain = this.launchedDrains.get(sessionId) - if (launchedDrain?.instance === instance && launchedDrain.lease === lease) { - this.launchedDrains.delete(sessionId) - } - } - - private async scheduleNextIfReady( - sessionId: string, - claimedInputId: string, - reason: PendingInputWakeReason - ): Promise { + private async tryRelease(claim: ClaimedPendingInputHandle): Promise { + if (claim.disposition) return try { - const releasedInputIsStillWaiting = this.ports.pendingInputs - .listPendingInputs(sessionId) - .some( - (item) => - item.id === claimedInputId && - (item.state === 'pending' || item.state === 'retry_required') - ) - if ( - !releasedInputIsStillWaiting && - this.ports.pendingInputs.hasPendingTurnInput(sessionId) && - (await this.ports.sessionState.get(sessionId))?.status === 'idle' && - !this.hasInteractionBlocker(sessionId) - ) { - this.schedule(sessionId, 'completed') - } - } catch (error) { - this.logDrainError(sessionId, reason, 'cleanup', error) - } - } - - private createClaim( - sessionId: string, - itemId: string, - source: PendingInputTurnSource - ): ClaimedPendingInputHandle { - return new DurablePendingInputClaim(this.ports.pendingInputs, sessionId, itemId, source) - } - - private rememberDeferredWakeup(sessionId: string, reason: PendingInputWakeReason): void { - const current = this.deferredWakeups.get(sessionId) - const priority: Record = { - completed: 0, - enqueue: 1, - manual: 2 - } - this.deferredWakeups.set( - sessionId, - !current || priority[reason] > priority[current] ? reason : current - ) - } - - private flushDeferredWakeup(sessionId: string): void { - const reason = this.deferredWakeups.get(sessionId) - if (!reason) { - return - } - const scope = this.ports.runLifecycle.getHydratedScope(sessionId) - if (!scope) { - this.deferredWakeups.delete(sessionId) - return - } - if (scope.instance.isPendingQueueDraining()) { - return - } - this.deferredWakeups.delete(sessionId) - this.schedule(sessionId, reason) - } - - private tryRelease(claim: ClaimedPendingInputHandle): boolean { - if (claim.disposition) { - return true - } - try { - claim.settle({ kind: 'release-before-user-fact' }) - return true + await claim.settle({ kind: 'release-before-user-fact' }) } catch (error) { logger.warn( - `[DeepChatAgent] failed to release claimed pending input item=${claim.id}`, + `[DeepChatAgent] failed to release pending input item=${claim.id}`, redactRuntimeErrorForLog(error) ) - return claim.disposition !== null - } - } - - private getCompletionMismatch( - itemId: string, - claim: ClaimedPendingInputHandle, - completion: TurnCompletion - ): Error | null { - const settled = claim.disposition - const reported = completion.claimedInputDisposition - if (!settled || !reported || settled.kind !== reported.kind) { - return new Error(`Turn completed without settling pending input ${itemId} consistently.`) } - return null - } - - private meetsDrainPreconditions( - sessionId: string, - status: DeepChatSessionState['status'], - reason: PendingInputWakeReason, - snapshot: PendingInputGateSnapshot - ): boolean { - if (!this.canDrainFromStatus(status, reason) || snapshot.awaitingQuestionFollowUp) { - return false - } - return !this.ports.runLifecycle.reconcilePendingInteractions( - sessionId, - snapshot.pendingInteractions - ) } private readGateSnapshot(sessionId: string): PendingInputGateSnapshot { @@ -742,21 +996,6 @@ export class PendingInputPump { return { awaitingQuestionFollowUp, pendingInteractions } } - private canDrainFromStatus( - status: DeepChatSessionState['status'], - reason: PendingInputWakeReason - ): boolean { - return status === 'idle' || (reason !== 'completed' && status === 'error') - } - - private hasExecutablePendingTurnInput(sessionId: string): boolean { - if (this.ports.pendingInputs.getNextSteerInput(sessionId)) { - return true - } - const nextQueueInput = this.ports.pendingInputs.getNextQueuedInput(sessionId) - return Boolean(nextQueueInput && !this.restartHeldQueueInputIds.has(nextQueueInput.id)) - } - private logDrainError( sessionId: string, reason: PendingInputWakeReason, diff --git a/src/main/agent/deepchat/runtime/process.ts b/src/main/agent/deepchat/runtime/process.ts index b5ebd9c60f..9eb2a5d015 100644 --- a/src/main/agent/deepchat/runtime/process.ts +++ b/src/main/agent/deepchat/runtime/process.ts @@ -1242,12 +1242,7 @@ export async function processStream(params: ProcessParams): Promise { + await controls?.onStreamingProviderPermission?.(permission, tool, (granted) => { markStreamingProviderPermissionResolved( actionBlock, granted, @@ -1256,6 +1251,11 @@ export async function processStream(params: ProcessParams): Promise { + await resourceInstance.controlReady() return await buildSystemPromptWithSkills(this.builderDependencies, { sessionId, basePrompt, @@ -104,6 +105,7 @@ export class PromptAssemblyService { contextLength?: number } = {} ): Promise { + await resourceInstance.controlReady() return await buildSystemPromptAssemblyWithSkills(this.builderDependencies, { sessionId, basePrompt, diff --git a/src/main/agent/deepchat/runtime/providerPermissionCoordinator.ts b/src/main/agent/deepchat/runtime/providerPermissionCoordinator.ts index 2c32ad1748..176ee75931 100644 --- a/src/main/agent/deepchat/runtime/providerPermissionCoordinator.ts +++ b/src/main/agent/deepchat/runtime/providerPermissionCoordinator.ts @@ -1,4 +1,5 @@ import type { DeepChatAgentInstance } from '@/agent/deepchat/instance/deepChatAgentInstance' +import type { SessionRuntimeScope } from '@/agent/deepchat/instance/deepChatAgentRuntime' import { parseMessageMetadata } from '@/session/usageStats' import type { AcpAsLlmProviderPermissionPort } from '@/provider/ports' import { @@ -9,11 +10,7 @@ import { } from './interactionProjection' import { buildTerminalErrorBlocks, type SessionTranscript } from '@/session/data/transcript' import { buildUsageFromMetadata, stampInteractionResolution } from './runtimeMetadata' -import type { - DeepChatEventPublisher, - PendingToolInteraction, - StreamState -} from './types' +import type { DeepChatEventPublisher, PendingToolInteraction, StreamState } from './types' import { markStreamChanged } from './types' import type { LoopRun } from '@/agent/deepchat/loop/loopRun' import type { RunLifecycleCoordinator } from './runLifecycleCoordinator' @@ -23,16 +20,16 @@ import { resolveProviderPermissionSafely } from './providerPermissionResolution' type ProviderPermissionRunLifecyclePort = Pick< RunLifecycleCoordinator, | 'getHydratedScope' - | 'getOrCreateScope' | 'isMessageAssociatedWithRun' | 'isRunCurrentForScope' | 'observeTerminal' | 'resolveStreamRequestId' - | 'transitionCurrentStatus' + | 'scopeFor' + | 'transitionStatus' > interface ProviderPermissionCoordinatorDependencies { - messageStore: SessionTranscript + messageStore: Pick runLifecycle: ProviderPermissionRunLifecyclePort permissionPort: AcpAsLlmProviderPermissionPort messageProjection: Pick @@ -42,93 +39,118 @@ interface ProviderPermissionCoordinatorDependencies { export class ProviderPermissionCoordinator { constructor(private readonly deps: ProviderPermissionCoordinatorDependencies) {} - register( - sessionId: string, - messageId: string, + async register( + scope: SessionRuntimeScope, + ownerRun: LoopRun, permission: NonNullable, tool: { callId?: string; name?: string; params?: string }, commitDecision: (granted: boolean) => void - ): void { + ): Promise { const requestId = permission.requestId?.trim() const providerId = permission.providerId?.trim() - if (!requestId || providerId !== 'acp') { - return - } - - this.deps.runLifecycle.getOrCreateScope(sessionId).instance.registerActiveProviderPermission({ - requestId, - messageId, - toolCallId: tool.callId || '', - providerId, - permissionType: permission.permissionType, - resolve: async (granted) => { - await this.deps.permissionPort.resolveAgentPermission(requestId, granted) - commitDecision(granted) - } - }) + if (!requestId || providerId !== 'acp') return + await scope.instance.registerActiveProviderPermission( + { + requestId, + messageId: ownerRun.messageId, + toolCallId: tool.callId || '', + providerId, + permissionType: permission.permissionType, + resolve: async (granted) => { + await this.deps.permissionPort.resolveAgentPermission(requestId, granted) + if ( + this.deps.runLifecycle.isRunCurrentForScope(scope, ownerRun.runId) && + (!granted || !ownerRun.abortController.signal.aborted) + ) + commitDecision(granted) + } + }, + ownerRun.runId + ) } - async resolve(input: ProviderPermissionInteractionInput): Promise { - const instance = this.deps.runLifecycle.getHydratedScope(input.sessionId)?.instance - const activeCandidate = instance?.getActiveProviderPermission(input.requestId) + async resolve( + input: ProviderPermissionInteractionInput & { interactionLeaseId: string } + ): Promise { + const scope = this.deps.runLifecycle.getHydratedScope(input.sessionId) + if (!scope) return + const { instance } = scope + const candidate = instance.getActiveProviderPermission(input.requestId) const active = - activeCandidate?.messageId === input.messageId && - activeCandidate.toolCallId === input.toolCallId - ? activeCandidate + candidate?.messageId === input.messageId && candidate.toolCallId === input.toolCallId + ? candidate : undefined - const hasConflictingActive = Boolean(activeCandidate && !active) const ownerRun = this.deps.runLifecycle.isMessageAssociatedWithRun( input.ownerRun, input.messageId ) ? input.ownerRun : undefined + if (input.signal?.aborted || ownerRun?.abortController.signal.aborted) return - if (input.signal?.aborted || ownerRun?.abortController.signal.aborted) { + const granted = ownerRun ? input.granted : false + const decision = await instance.takeProviderPermission({ + requestId: input.requestId, + messageId: input.messageId, + toolCallId: input.toolCallId, + runId: ownerRun?.runId ?? null, + leaseId: input.interactionLeaseId, + granted, + expected: active + }) + if ( + decision.kind === 'stale' || + input.signal?.aborted || + !scope.isCurrent() || + ownerRun?.abortController.signal.aborted + ) return - } - - if (ownerRun) { - if (hasConflictingActive) { + if (decision.kind === 'conflict') { + if (ownerRun) { const projection: ProviderPermissionProjection = { status: 'error', message: 'ACP permission request ownership changed.' } this.updateActiveState(ownerRun, input, projection) this.updatePersistedState(input, projection) - if (instance) { - this.removePending(instance, input) - } - return - } - - let resolution: { status: 'resolved' } | { status: 'stale'; error: unknown } - try { - resolution = await resolveProviderPermissionSafely( - active - ? () => active.resolve(input.granted) - : () => this.deps.permissionPort.resolveAgentPermission(input.requestId, input.granted) + await this.removePending(instance, input) + } else { + await this.fail( + input, + 'ACP permission request ownership changed.', + instance, + !input.ownerRun ) - } finally { - instance?.clearActiveProviderPermission(input.requestId, active) } + return + } - const currentScope = this.deps.runLifecycle.getHydratedScope(input.sessionId) + const resolve = async () => + decision.kind === 'spent' + ? { + status: 'stale' as const, + error: new Error('ACP permission resolution was already claimed.') + } + : await resolveProviderPermissionSafely(() => + decision.kind === 'callback' + ? decision.permission.resolve(granted) + : this.deps.permissionPort.resolveAgentPermission(input.requestId, granted) + ) + if (ownerRun) { + const resolution = await resolve() if ( input.signal?.aborted || ownerRun.abortController.signal.aborted || - !currentScope || - !this.deps.runLifecycle.isRunCurrentForScope(currentScope, ownerRun.runId) - ) { + !this.deps.runLifecycle.isRunCurrentForScope(scope, ownerRun.runId) + ) return - } if (resolution.status === 'stale') { console.warn( `[DeepChatAgent] ACP permission request expired while its generation remained active: ${input.requestId}`, resolution.error ) } - if (!active || resolution.status === 'stale') { + if (decision.kind !== 'callback' || resolution.status === 'stale') { const projection: ProviderPermissionProjection = resolution.status === 'resolved' ? { status: 'resolved', granted: input.granted } @@ -136,36 +158,17 @@ export class ProviderPermissionCoordinator { this.updateActiveState(ownerRun, input, projection) this.updatePersistedState(input, projection) } - this.removePending(currentScope.instance, input) - return - } - - if (hasConflictingActive) { - this.fail(input, 'ACP permission request ownership changed.', instance) + await this.removePending(instance, input) return } - let resolution: - | { status: 'resolved' } - | { status: 'stale'; error: unknown } - | { status: 'failed'; error: unknown } + let resolution: Awaited> | { status: 'failed'; error: unknown } try { - try { - resolution = await resolveProviderPermissionSafely( - active - ? () => active.resolve(false) - : () => this.deps.permissionPort.resolveAgentPermission(input.requestId, false) - ) - } catch (error) { - resolution = { status: 'failed', error } - } - } finally { - instance?.clearActiveProviderPermission(input.requestId, active) - } - - if (input.signal?.aborted) { - return + resolution = await resolve() + } catch (error) { + resolution = { status: 'failed', error } } + if (input.signal?.aborted || !scope.isCurrent()) return if (resolution.status === 'stale') { console.warn( `[DeepChatAgent] Failing stale ACP permission request ${input.requestId}:`, @@ -177,12 +180,13 @@ export class ProviderPermissionCoordinator { resolution.error ) } - this.fail( + await this.fail( input, resolution.status === 'stale' ? 'Permission request expired.' : 'ACP permission request lost its active generation.', - instance + instance, + !input.ownerRun ) } @@ -213,11 +217,12 @@ export class ProviderPermissionCoordinator { } } - private fail( + private async fail( input: ProviderPermissionInteractionInput, errorMessage: string, - instance?: DeepChatAgentInstance - ): void { + instance?: DeepChatAgentInstance, + publishStatus = true + ): Promise { const message = this.deps.messageStore.getMessage(input.messageId) if (!message || message.role !== 'assistant') { return @@ -237,10 +242,7 @@ export class ProviderPermissionCoordinator { ) this.deps.messageProjection.refresh(input.sessionId, input.messageId) this.deps.publishEvent('chat.stream.failed', { - requestId: this.deps.runLifecycle.resolveStreamRequestId( - input.sessionId, - input.messageId - ), + requestId: this.deps.runLifecycle.resolveStreamRequestId(input.sessionId, input.messageId), sessionId: input.sessionId, messageId: input.messageId, failedAt: Date.now(), @@ -253,28 +255,31 @@ export class ProviderPermissionCoordinator { usage: buildUsageFromMetadata(terminalMetadata) }) if (instance) { - this.removePending(instance, input) - if (!instance.getActiveGeneration()) { - this.deps.runLifecycle.transitionCurrentStatus( - input.sessionId, + await this.removePending(instance, input) + if (publishStatus && !instance.getActiveGeneration()) { + await this.deps.runLifecycle.transitionStatus( + this.deps.runLifecycle.scopeFor(input.sessionId, instance), 'error', + input.controlOwner ?? { kind: 'orphan' }, buildUsageFromMetadata(terminalMetadata) ) } } } - private removePending( + private async removePending( instance: DeepChatAgentInstance, input: ProviderPermissionInteractionInput - ): void { - instance.replacePendingInteractions( + ): Promise { + await instance.replacePendingInteractions( instance .getPendingInteractions() .filter( (interaction) => interaction.messageId !== input.messageId || interaction.toolCallId !== input.toolCallId - ) + ), + input.controlOwner ?? + (input.ownerRun ? { kind: 'run', id: input.ownerRun.runId } : { kind: 'orphan' }) ) } } diff --git a/src/main/agent/deepchat/runtime/runLifecycleCoordinator.ts b/src/main/agent/deepchat/runtime/runLifecycleCoordinator.ts index 964cdc65de..c574b94c69 100644 --- a/src/main/agent/deepchat/runtime/runLifecycleCoordinator.ts +++ b/src/main/agent/deepchat/runtime/runLifecycleCoordinator.ts @@ -1,3 +1,4 @@ +import type { HarnessControlLease, HarnessControlOwner } from '@shared/harness/control' import logger from '@shared/logger' import type { DeepChatSessionState } from '@shared/types/agent-interface' import type { AppSessionId } from '@/agent/shared/agentSessionIds' @@ -17,11 +18,8 @@ import { parseMessageMetadata } from '@/session/usageStats' import { collectPendingInteractionEntries, parseAssistantBlocks, - reconcilePendingInteractionEntries, - replacePendingInteractions, type PendingInteractionEntry } from './interactionProjection' -import { resolveProviderPermissionSafely } from './providerPermissionResolution' import { redactRuntimeErrorForLog } from './runtimeErrorLogging' import { buildUsageFromMetadata, stampInteractionResolution } from './runtimeMetadata' import type { SessionStatusPublisher } from './sessionStatusPublisher' @@ -83,75 +81,45 @@ export class RunLifecycleCoordinator { return this.getHydratedScope(sessionId)?.instance.getAbortSignal() } - ensureOperationController(scope: SessionRuntimeScope): AbortController { + async ensureOperationController(scope: SessionRuntimeScope): Promise { + await scope.instance.controlReady() scope.assertCurrent() - const activeRun = scope.instance.getActiveGeneration() - if (activeRun) { - if (!activeRun.abortController.signal.aborted) { - return activeRun.abortController - } - // A cancelled run may remain registered until its handler settles. A new operation must never - // inherit its already-aborted controller. - this.clearRun(scope, activeRun.runId) - } - - const existing = scope.instance.getAbortController() - existing?.abort() - - const controller = new AbortController() - scope.instance.setAbortController(controller) - return controller + return await scope.instance.ensureOperationController() } - clearOperationController( + async clearOperationController( scope: SessionRuntimeScope, - controller?: AbortController - ): boolean { - if (!controller || !scope.isCurrent()) { - return false - } - return scope.instance.clearAbortController(controller) - } - - canSettleOperation(scope: SessionRuntimeScope, controller: AbortController): boolean { - if (!scope.isCurrent()) { - return false - } - const current = scope.instance.getAbortController() - return current === undefined || current === controller + controller?: AbortController, + settled?: () => void, + origin?: { operationId: string; runId?: string } + ): Promise { + if (!controller || !scope.isCurrent()) return false + return await scope.instance.clearAbortController(controller, settled, origin) } - registerRun( + async registerRun( scope: SessionRuntimeScope, run: LoopRun - ): LoopRun { + ): Promise> { + await scope.instance.controlReady() scope.assertCurrent() if (run.sessionId !== scope.sessionId) { throw new Error( `Loop run ${run.runId} belongs to session ${run.sessionId}, not ${scope.sessionId}` ) } - return scope.instance.registerActiveGeneration(run) + return await scope.instance.registerActiveGeneration(run) } - clearRun(scope: SessionRuntimeScope, runId: string): boolean { - // Cleanup stays bound to the originating instance even after replacement. Status and terminal - // projections use the independent current-scope and active-run fences. - if ( - scope.instance.sessionId !== scope.sessionId || - !scope.instance.clearActiveGeneration(runId) - ) { - return false - } - this.cancelProviderPermissions(scope.instance) - return true + async clearRun(scope: SessionRuntimeScope, runId: string): Promise { + if (scope.instance.sessionId !== scope.sessionId) return false + return await scope.instance.clearActiveGeneration(runId) } isRunCurrent(sessionId: AppSessionId | string, runId: string): boolean { return ( - this.ports.runtime - .getHydratedScope(toAppSessionId(sessionId)) - ?.instance.isActiveRun(runId) ?? false + this.ports.runtime.getHydratedScope(toAppSessionId(sessionId))?.instance.isActiveRun(runId) ?? + false ) } @@ -166,41 +134,35 @@ export class RunLifecycleCoordinator { return run?.messageId === messageId } - markFirstTurnReady(scope: SessionRuntimeScope, runId: string): boolean { - const run = scope.instance.getActiveGeneration() - if ( - !this.isRunCurrentForScope(scope, runId) || - !run || - run.abortController.signal.aborted - ) { - return false - } - scope.instance.markFirstTurnReady() - return true + async markFirstTurnReady(scope: SessionRuntimeScope, runId: string): Promise { + return await scope.instance.markFirstTurnReady(runId) } clearFirstTurnReady(sessionId: string): void { this.getHydratedScope(sessionId)?.instance.clearFirstTurnReady() } - transitionCurrentStatus( + async transitionCurrentStatus( sessionId: string, status: DeepChatSessionState['status'], usage?: Record - ): boolean { + ): Promise { const scope = this.getHydratedScope(sessionId) - return scope ? this.ports.statusPublisher.transition(scope, status, usage) : false + return scope + ? await this.ports.statusPublisher.transition(scope, status, { kind: 'orphan' }, usage) + : false } - transitionStatus( + async transitionStatus( scope: SessionRuntimeScope, status: DeepChatSessionState['status'], + owner: HarnessControlOwner, usage?: Record - ): boolean { - return this.ports.statusPublisher.transition(scope, status, usage) + ): Promise { + return await this.ports.statusPublisher.transition(scope, status, owner, usage) } - refreshPendingInteractions(sessionId: string): boolean { + private readPendingInteractions(sessionId: string): PendingInteractionEntry[] { const pendingEntries: PendingInteractionEntry[] = [] for (const message of this.ports.transcript.getMessages(sessionId)) { if (message.role !== 'assistant') { @@ -214,39 +176,71 @@ export class RunLifecycleCoordinator { ) ) } - return this.reconcilePendingInteractions(sessionId, pendingEntries) + return pendingEntries + } + + async refreshPendingInteractions(sessionId: string): Promise { + return await this.reconcilePendingInteractions( + sessionId, + this.readPendingInteractions(sessionId) + ) } - reconcilePendingInteractions( + async reconcilePendingInteractions( sessionId: string, pendingEntries: PendingInteractionEntry[] - ): boolean { + ): Promise { const scope = this.getHydratedScope(sessionId) if (!scope) { return pendingEntries.length > 0 } - replacePendingInteractions( - scope.instance, - reconcilePendingInteractionEntries(scope.instance, pendingEntries) - ) - return scope.instance.hasPendingInteractions() + await scope.instance.controlReady() + const deadline = performance.now() + 10_000 + while (true) { + scope.assertCurrent() + // Rebuild after readiness and rejected observations; never rebind captured entries. + pendingEntries = this.readPendingInteractions(sessionId) + const accepted = await scope.instance.replacePendingInteractions( + pendingEntries.map(({ interaction }) => ({ + messageId: interaction.messageId, + toolCallId: interaction.toolCallId, + origin: interaction.origin, + order: interaction.order + })), + undefined, + { reconcile: true } + ) + scope.assertCurrent() + if (accepted) return scope.instance.hasPendingInteractions() + if (performance.now() >= deadline) + throw new Error('Pending interactions kept changing during reconciliation') + } } - hasPendingInteractions(sessionId: string): boolean { - return this.refreshPendingInteractions(sessionId) + async hasPendingInteractions(sessionId: string): Promise { + return await this.refreshPendingInteractions(sessionId) } - registerDeferredToolController(sessionId: string, toolCallId: string): AbortController { - return this.getOrCreateScope(sessionId).instance.registerDeferredToolAbortController(toolCallId) + async registerDeferredToolController( + scope: SessionRuntimeScope, + messageId: string, + toolCallId: string, + lease: HarnessControlLease, + owner: HarnessControlOwner + ): Promise { + return await scope.instance.registerDeferredToolAbortController( + messageId, + toolCallId, + lease, + owner + ) } - clearDeferredToolController( - sessionId: string, - toolCallId: string, - controller?: AbortController - ): boolean { - const scope = this.getHydratedScope(sessionId) - return scope?.instance.clearDeferredToolAbortController(toolCallId, controller) ?? false + async clearDeferredToolController( + scope: SessionRuntimeScope, + controller: AbortController + ): Promise { + return await scope.instance.clearDeferredToolAbortController(controller) } getActiveGeneration(sessionId: string): { eventId: string; runId: string } | null { @@ -267,31 +261,29 @@ export class RunLifecycleCoordinator { } const { instance } = scope - if (!instance.hasPendingInteractions()) { - this.refreshPendingInteractions(sessionId) - } - const pendingInteractions = instance.getPendingInteractions() - const hasDeferredHandler = pendingInteractions.some((interaction) => - instance.hasDeferredToolAbortController(interaction.toolCallId) - ) + const hasDeferredHandler = instance + .getPendingInteractions() + .some((interaction) => instance.hasDeferredToolAbortController(interaction.toolCallId)) const hasAsyncSettlementOwner = Boolean( instance.getActiveGeneration() || instance.getAbortController() || hasDeferredHandler ) instance.requestGenerationAbort() instance.abortDeferredToolCalls() - this.cancelProviderPermissions(instance) if (hasAsyncSettlementOwner) { return } + if (!instance.hasPendingInteractions()) await this.refreshPendingInteractions(sessionId) + if (!scope.isCurrent()) return + const pendingInteractions = instance.getPendingInteractions() if (pendingInteractions.length === 0) { // Nothing owns the turn and no approval can resume it. A session left `generating` here // cannot be ended by any other path, so publish the terminal state instead of reporting a // stop that changed nothing. An `error` session already has its terminal state; settling it // again would emit a second one and replace the error with `idle`. if (this.currentState(sessionId)?.status === 'generating') { - this.settleOrphanedInteraction(sessionId, null) + await this.settleOrphanedInteraction(sessionId, null) this.schedulePendingInputDrain(sessionId, 'completed') } return @@ -316,15 +308,19 @@ export class RunLifecycleCoordinator { JSON.stringify(terminalMetadata) ) } - instance.replacePendingInteractions([]) + await instance.replacePendingInteractions([]) const primaryTerminal = terminalMessages[0] - this.settleInterruptedTurn(sessionId, { - messageId: null, - errorMessage: 'common.error.userCanceledGeneration', - stopReason: 'user_stop', - runId: primaryTerminal.terminalMetadata.runId, - metadata: JSON.stringify(primaryTerminal.terminalMetadata) - }) + await this.settleInterruptedTurn( + sessionId, + { + messageId: null, + errorMessage: 'common.error.userCanceledGeneration', + stopReason: 'user_stop', + runId: primaryTerminal.terminalMetadata.runId, + metadata: JSON.stringify(primaryTerminal.terminalMetadata) + }, + { kind: 'orphan' } + ) this.schedulePendingInputDrain(sessionId, 'completed') } @@ -336,69 +332,65 @@ export class RunLifecycleCoordinator { revokeToolSurfaceDeferredDispatchesForSession(scope.sessionId) scope.instance.abortAndClearGeneration() scope.instance.abortDeferredToolCalls() - this.cancelProviderPermissions(scope.instance) } observeTerminal(sessionId: string, result: ProcessResult): void { this.ports.terminalObserver.observeTerminal(sessionId, this.currentState(sessionId), result) } - applyProcessResultStatus( + async applyProcessResultStatus( sessionId: string, result: ProcessResult | null | undefined, - runId?: string - ): void { - // Terminal observers describe the run that ended even if a newer run has replaced it. Mutable - // Session projection is fenced by the active run identity below. - const isActive = !runId || this.isRunCurrent(sessionId, runId) + runId?: string, + owner: HarnessControlOwner = runId ? { kind: 'run', id: runId } : { kind: 'orphan' } + ): Promise { const scope = this.getHydratedScope(sessionId) if (!result?.status) { - if (isActive && scope) { - scope.instance.replacePendingInteractions([]) - this.transitionStatus(scope, 'idle') - } + if (scope) + await this.ports.statusPublisher.transition(scope, 'idle', owner, undefined, { + interactions: [] + }) return } if (result.status === 'paused') { - if (isActive && scope) { - if (result.toolBatchExecutionState) { - scope.instance.replacePendingToolBatch( - result.pendingInteractions ?? [], - result.toolBatchExecutionState - ) - } else { - scope.instance.replacePendingInteractions(result.pendingInteractions ?? []) - } - this.transitionStatus(scope, 'generating') - } + if (scope) + await this.ports.statusPublisher.transition(scope, 'generating', owner, undefined, { + interactions: result.pendingInteractions ?? [], + ...(result.toolBatchExecutionState ? { batch: result.toolBatchExecutionState } : {}) + }) return } + // Terminal observation describes the ended Run; Root separately authorizes current projection. this.observeTerminal(sessionId, result) - if (!isActive || !scope) { - return - } - scope.instance.replacePendingInteractions([]) - this.ports.statusPublisher.transition( - scope, - result.status === 'error' ? 'error' : 'idle', - result.usage - ) + if (scope) + await this.ports.statusPublisher.transition( + scope, + result.status === 'error' ? 'error' : 'idle', + owner, + result.usage, + { interactions: [] } + ) } - settleAbortedTurn( + async settleAbortedTurn( sessionId: string, messageId: string | null, runId?: string, - metadata?: string - ): void { - this.settleInterruptedTurn(sessionId, { - messageId, - errorMessage: 'common.error.userCanceledGeneration', - stopReason: 'user_stop', - runId, - metadata - }) + metadata?: string, + owner?: HarnessControlOwner + ): Promise { + await this.settleInterruptedTurn( + sessionId, + { + messageId, + errorMessage: 'common.error.userCanceledGeneration', + stopReason: 'user_stop', + runId, + metadata + }, + owner + ) } /** @@ -408,11 +400,11 @@ export class RunLifecycleCoordinator { * turn, so a live run, a newer replacement operation, or another approval is never settled out * from under itself. */ - settleOrphanedInteraction( + async settleOrphanedInteraction( sessionId: string, messageId: string | null, metadata?: string - ): boolean { + ): Promise { const scope = this.getHydratedScope(sessionId) if (!scope) { return false @@ -423,21 +415,20 @@ export class RunLifecycleCoordinator { // The caller may have closed one entry of a queue that is stale relative to the transcript. // Rebuild it before deciding, or another message's approval would be settled along with it. - this.refreshPendingInteractions(sessionId) + await this.refreshPendingInteractions(sessionId) if (scope.instance.getPendingInteractions().length > 0) { return false } - this.settleInterruptedTurn(sessionId, { + return await this.settleInterruptedTurn(sessionId, { messageId, errorMessage: 'common.error.sessionInterrupted', stopReason: 'interaction', metadata }) - return true } - private settleInterruptedTurn( + private async settleInterruptedTurn( sessionId: string, input: { messageId: string | null @@ -445,8 +436,9 @@ export class RunLifecycleCoordinator { stopReason: string runId?: string metadata?: string - } - ): void { + }, + owner: HarnessControlOwner = input.runId ? { kind: 'run', id: input.runId } : { kind: 'orphan' } + ): Promise { this.writeTerminalErrorBlock(sessionId, input.messageId, input.errorMessage, input.metadata) const usage = input.metadata ? buildUsageFromMetadata(parseMessageMetadata(input.metadata)) @@ -459,9 +451,10 @@ export class RunLifecycleCoordinator { }) const scope = this.getHydratedScope(sessionId) - if (scope && this.canSettleAbortedRun(scope, input.runId)) { - this.ports.statusPublisher.transition(scope, 'idle', usage) + if (scope) { + return await this.ports.statusPublisher.transition(scope, 'idle', owner, usage) } + return false } async requestPendingInputDrain( @@ -480,20 +473,6 @@ export class RunLifecycleCoordinator { }) } - private canSettleAbortedRun(scope: SessionRuntimeScope, runId?: string): boolean { - if (!scope.isCurrent()) { - return false - } - const activeRun = scope.instance.getActiveGeneration() - const controller = scope.instance.getAbortController() - const hasReplacementController = Boolean( - controller && (!activeRun || controller !== activeRun.abortController) - ) - return runId - ? activeRun?.runId === runId || (!activeRun && !hasReplacementController) - : !hasReplacementController - } - private currentState(sessionId: string): DeepChatSessionState | undefined { return this.getHydratedScope(sessionId)?.state() } @@ -518,15 +497,4 @@ export class RunLifecycleCoordinator { this.ports.transcript.setMessageError(messageId, blocks, metadata) this.ports.messageProjection.refresh(sessionId, messageId) } - - private cancelProviderPermissions(instance: DeepChatAgentInstance): void { - for (const permission of instance.takeActiveProviderPermissions()) { - void resolveProviderPermissionSafely(() => permission.resolve(false)).catch((error) => { - logger.warn( - `[DeepChatAgent] Failed to cancel ACP permission request ${permission.requestId}:`, - redactRuntimeErrorForLog(error) - ) - }) - } - } } diff --git a/src/main/agent/deepchat/runtime/sessionLifecycleCoordinator.ts b/src/main/agent/deepchat/runtime/sessionLifecycleCoordinator.ts index 5b7d5add13..43337afd1b 100644 --- a/src/main/agent/deepchat/runtime/sessionLifecycleCoordinator.ts +++ b/src/main/agent/deepchat/runtime/sessionLifecycleCoordinator.ts @@ -1,8 +1,5 @@ import logger from '@shared/logger' -import type { - PermissionMode, - SessionGenerationSettings -} from '@shared/types/agent-interface' +import type { PermissionMode, SessionGenerationSettings } from '@shared/types/agent-interface' import type { ToolServicePort } from '@shared/types/tool' import { toAppSessionId } from '@/agent/shared/agentSessionIds' import type { ProviderModelResolutionPort } from '@/provider/settings' @@ -21,7 +18,6 @@ import type { RunLifecycleCoordinator } from './runLifecycleCoordinator' import type { SessionIdentityService } from './sessionIdentityService' import type { SessionSettingsCoordinator } from './sessionSettingsCoordinator' import type { JevPruningFeedback } from './jevPruningFeedback' -import type { InteractionParkingRegistry } from './interactionParkingRegistry' import type { ToolSurfaceShadowDiagnosticsRegistryPort } from './toolSurfaceDiagnostics' import type { ToolSurfaceCanaryDiagnosticsRegistry } from './toolSurfaceCanaryDiagnostics' import { revokeToolSurfaceDeferredDispatchesForSession } from './toolSurface' @@ -36,10 +32,12 @@ export interface SessionInitConfig { generationSettings?: Partial } -export type SessionLifecycleRegistry = SessionScopeRegistry & Pick +export type SessionLifecycleRegistry = SessionScopeRegistry & + Pick export interface SessionLifecycleCoordinatorDependencies { registry: SessionLifecycleRegistry + fenceInputEffects(sessionId: string): void providerSettings: ProviderModelResolutionPort promptSettings: Pick sessionStore: Pick @@ -57,7 +55,6 @@ export interface SessionLifecycleCoordinatorDependencies { RunLifecycleCoordinator, 'cancel' | 'clearFirstTurnReady' | 'cancelScopeOperations' | 'scopeFor' > - interactionParking: Pick toolSurfaceDiagnostics: Pick toolSurfaceCanaryDiagnostics: Pick programmaticToolParents: Pick @@ -88,6 +85,7 @@ export class SessionLifecycleCoordinator { generationSettings ) const instance = this.deps.registry.getOrHydrateScope(toAppSessionId(sessionId)).instance + await instance.controlReady() instance.setAgentId( config.agentId?.trim() || this.deps.identity.getAgentId(sessionId) || 'deepchat' ) @@ -130,11 +128,16 @@ export class SessionLifecycleCoordinator { async destroy(sessionId: string): Promise { const instance = this.deps.registry.getHydratedScope(toAppSessionId(sessionId))?.instance + this.deps.fenceInputEffects(sessionId) this.deps.memory.beginSessionDestroy(sessionId) if (instance) { + instance.fenceInputEffects() this.deps.runLifecycle.cancelScopeOperations( this.deps.runLifecycle.scopeFor(sessionId, instance) ) + if (this.deps.registry.getHydratedScope(toAppSessionId(sessionId))?.instance === instance) { + this.deps.registry.evict(toAppSessionId(sessionId)) + } } this.deps.runLifecycle.clearFirstTurnReady(sessionId) @@ -145,14 +148,13 @@ export class SessionLifecycleCoordinator { this.deps.programmaticToolParents.releaseSession(sessionId) this.deps.pruningFeedback.clear(sessionId) this.deps.toolSurfaceCanaryDiagnostics.clearSession(sessionId) - this.deps.interactionParking.clearSession(sessionId) + await this.deps.registry.clearSessionControl(toAppSessionId(sessionId), instance) if (instance) { try { this.deps.toolSurfaceDiagnostics.clear(instance) } catch {} } instance?.clearOwnedState() - this.deps.registry.evict(toAppSessionId(sessionId)) this.deps.memory.finishSessionDestroy(sessionId) this.deps.toolService.clearConversationToolMapping(sessionId) } diff --git a/src/main/agent/deepchat/runtime/sessionSettingsCoordinator.ts b/src/main/agent/deepchat/runtime/sessionSettingsCoordinator.ts index 122cc0f533..354ab6f327 100644 --- a/src/main/agent/deepchat/runtime/sessionSettingsCoordinator.ts +++ b/src/main/agent/deepchat/runtime/sessionSettingsCoordinator.ts @@ -59,10 +59,10 @@ export class SessionSettingsCoordinator { this.deps.registry.scopeFor(toAppSessionId(sessionId), instance).assertCurrent() } - private resolveUpdateScope(sessionId: string): { + private async resolveUpdateScope(sessionId: string): Promise<{ scope: SessionRuntimeScope dbSession: ReturnType - } { + }> { const appSessionId = toAppSessionId(sessionId) const hydratedScope = this.deps.registry.getHydratedScope(appSessionId) const dbSession = this.deps.sessionStore.get(sessionId) @@ -71,6 +71,7 @@ export class SessionSettingsCoordinator { } const scope = hydratedScope ?? this.deps.registry.getOrHydrateScope(appSessionId) + await scope.instance.controlReady() scope.assertCurrent() return { scope, dbSession } } @@ -90,7 +91,7 @@ export class SessionSettingsCoordinator { throw new Error('Session model update requires providerId and modelId.') } - const { scope, dbSession } = this.resolveUpdateScope(sessionId) + const { scope, dbSession } = await this.resolveUpdateScope(sessionId) const state = scope.state() const permissionMode = state?.permissionMode ?? dbSession?.permission_mode if (!permissionMode) { @@ -149,7 +150,7 @@ export class SessionSettingsCoordinator { throw new Error('Turn execution snapshot requires providerId and modelId.') } - const { scope, dbSession } = this.resolveUpdateScope(sessionId) + const { scope, dbSession } = await this.resolveUpdateScope(sessionId) const state = scope.state() const permissionMode = state?.permissionMode ?? dbSession?.permission_mode if (!permissionMode) { @@ -210,7 +211,7 @@ export class SessionSettingsCoordinator { throw new Error('Session agent context update requires agentId, providerId and modelId.') } - const { scope } = this.resolveUpdateScope(sessionId) + const { scope } = await this.resolveUpdateScope(sessionId) const state = scope.state() if (state?.status === 'generating') { throw new Error('Cannot move session while it is generating.') @@ -270,8 +271,11 @@ export class SessionSettingsCoordinator { } } - setProjectDir(sessionId: string, projectDir: string | null): void { - this.applyProjectDir(sessionId, this.instance(sessionId), projectDir) + async setProjectDir(sessionId: string, projectDir: string | null): Promise { + const instance = this.instance(sessionId) + await instance.controlReady() + this.assertCurrent(sessionId, instance) + this.applyProjectDir(sessionId, instance, projectDir) } resolveProjectDir( @@ -334,7 +338,7 @@ export class SessionSettingsCoordinator { sessionId: string, settings: Partial ): Promise { - const { scope, dbSession } = this.resolveUpdateScope(sessionId) + const { scope, dbSession } = await this.resolveUpdateScope(sessionId) const state = scope.state() const providerId = state?.providerId ?? dbSession?.provider_id const modelId = state?.modelId ?? dbSession?.model_id @@ -372,6 +376,7 @@ export class SessionSettingsCoordinator { expectedInstance = this.instance(sessionId), providedProviderModelFacts?: ProviderModelRuntimeFacts ): Promise { + await expectedInstance.controlReady() this.assertCurrent(sessionId, expectedInstance) const state = expectedInstance.getRuntimeState() let dbSession: PersistedSessionGenerationRow | undefined diff --git a/src/main/agent/deepchat/runtime/sessionStateResolver.ts b/src/main/agent/deepchat/runtime/sessionStateResolver.ts index 375ec7112d..ec3c8ddaf5 100644 --- a/src/main/agent/deepchat/runtime/sessionStateResolver.ts +++ b/src/main/agent/deepchat/runtime/sessionStateResolver.ts @@ -40,6 +40,7 @@ export class SessionStateResolver { hydrationMode: SessionStateHydrationMode ): Promise { const instance = this.deps.registry.getOrHydrateScope(toAppSessionId(sessionId)).instance + await instance.controlReady() const state = instance.getRuntimeState() if (state) { this.deps.identity.getAgentId(sessionId) @@ -48,7 +49,7 @@ export class SessionStateResolver { } return { ...state, - ...(this.deps.runLifecycle.hasPendingInteractions(sessionId) + ...((await this.deps.runLifecycle.hasPendingInteractions(sessionId)) ? { status: 'generating' as const } : {}) } @@ -64,7 +65,7 @@ export class SessionStateResolver { } this.deps.identity.getAgentId(sessionId) - const hasPendingInteractions = this.deps.runLifecycle.hasPendingInteractions(sessionId) + const hasPendingInteractions = await this.deps.runLifecycle.hasPendingInteractions(sessionId) const rebuilt: DeepChatSessionState = { status: 'idle', providerId: dbSession.provider_id, diff --git a/src/main/agent/deepchat/runtime/sessionStatusPublisher.ts b/src/main/agent/deepchat/runtime/sessionStatusPublisher.ts index 4b2cd920a1..cd58eae313 100644 --- a/src/main/agent/deepchat/runtime/sessionStatusPublisher.ts +++ b/src/main/agent/deepchat/runtime/sessionStatusPublisher.ts @@ -1,3 +1,5 @@ +import type { PendingInteractionProjection } from '../instance/controlSession' +import type { HarnessControlOwner } from '@shared/harness/control' import type { DeepChatSessionState } from '@shared/types/agent-interface' import type { SessionUiPort } from '@/session/contracts' import type { SessionRuntimeScope } from '@/agent/deepchat/instance/deepChatAgentRuntime' @@ -12,11 +14,13 @@ export interface SessionStatusPublisherPorts { export class SessionStatusPublisher { constructor(private readonly ports: SessionStatusPublisherPorts) {} - transition( + async transition( scope: SessionRuntimeScope, status: DeepChatSessionState['status'], - usage?: Record - ): boolean { + owner: HarnessControlOwner, + usage?: Record, + interactions?: PendingInteractionProjection + ): Promise { if (!scope.isCurrent()) { return false } @@ -25,12 +29,15 @@ export class SessionStatusPublisher { if (!current) { return false } - if (current.status === status && usage === undefined) { - return true + const applied = await scope.instance.transitionControlStatus(owner, status, interactions) + if (!applied || !scope.isCurrent()) { + return false } - + if (scope.state()?.status !== status) return false + const { previousStatus } = applied const sessionId = scope.sessionId - if (current.status === status) { + if (previousStatus === status && usage === undefined) return true + if (previousStatus === status) { this.ports.publishSessionUpdate({ sessionId, kind: 'status', @@ -41,7 +48,6 @@ export class SessionStatusPublisher { return true } - current.status = status this.ports.publishEvent('sessions.status.changed', { sessionId, status, diff --git a/src/main/agent/deepchat/runtime/toolResolver.ts b/src/main/agent/deepchat/runtime/toolResolver.ts index d91c5189db..e40406358f 100644 --- a/src/main/agent/deepchat/runtime/toolResolver.ts +++ b/src/main/agent/deepchat/runtime/toolResolver.ts @@ -306,6 +306,7 @@ export class DeepChatToolResolver { const resourceInstance = providedResourceInstance ?? this.dependencies.registry.getOrHydrateScope(toAppSessionId(sessionId)).instance + await resourceInstance.controlReady() const catalog = this.createSessionToolCatalogPort( sessionId, projectDir, @@ -331,6 +332,7 @@ export class DeepChatToolResolver { const resourceInstance = providedResourceInstance ?? this.dependencies.registry.getOrHydrateScope(toAppSessionId(sessionId)).instance + await resourceInstance.controlReady() this.assertCurrent(sessionId, resourceInstance) const providerId = resourceInstance.getRuntimeState()?.providerId?.trim() if (this.dependencies.identity.isAcpBackedSubagentSession(sessionId, providerId)) { diff --git a/src/main/agent/deepchat/runtime/transcriptMutationCoordinator.ts b/src/main/agent/deepchat/runtime/transcriptMutationCoordinator.ts index d3990bb6f7..822186abab 100644 --- a/src/main/agent/deepchat/runtime/transcriptMutationCoordinator.ts +++ b/src/main/agent/deepchat/runtime/transcriptMutationCoordinator.ts @@ -56,13 +56,14 @@ export class TranscriptMutationCoordinator { this.deps.memory.clearProjectionRetry(sessionId) } - finishClearMessages(sessionId: string): void { + async finishClearMessages(sessionId: string): Promise { const instance = this.deps.registry.getOrHydrateScope(toAppSessionId(sessionId)).instance - instance.replacePendingInteractions([]) + await instance.replacePendingInteractions([]) this.deps.compaction.reset(sessionId, instance) - this.deps.runLifecycle.transitionStatus( + await this.deps.runLifecycle.transitionStatus( this.deps.runLifecycle.scopeFor(sessionId, instance), - 'idle' + 'idle', + { kind: 'orphan' } ) } @@ -79,10 +80,10 @@ export class TranscriptMutationCoordinator { if (state.status === 'generating') { throw new Error('Cannot retry while session is generating.') } - if (this.deps.runLifecycle.hasPendingInteractions(sessionId)) { + if (await this.deps.runLifecycle.hasPendingInteractions(sessionId)) { throw new Error('Please resolve pending tool interactions before retrying.') } - this.deps.admission.assertNoActiveInputs(sessionId, options) + await this.deps.admission.assertNoActiveInputs(sessionId, options) this.deps.runLifecycle.assertCurrentInstance(sessionId, instance) return { projectDir: this.deps.sessionSettings.resolveProjectDir(sessionId, undefined, instance) @@ -104,18 +105,20 @@ export class TranscriptMutationCoordinator { this.deps.memory.invalidateFromOrderSeq(sessionId, orderSeq) } - finishTranscriptTruncate(sessionId: string): void { - this.deps.runLifecycle.refreshPendingInteractions(sessionId) - this.deps.runLifecycle.transitionCurrentStatus(sessionId, 'idle') + async finishTranscriptTruncate(sessionId: string): Promise { + await this.deps.runLifecycle.refreshPendingInteractions(sessionId) + await this.deps.runLifecycle.transitionCurrentStatus(sessionId, 'idle') } resetForkTarget(targetSessionId: string, clonedMemoryCursorOrderSeq: number): void { - const targetInstance = this.deps.registry.getOrHydrateScope(toAppSessionId(targetSessionId)).instance + const targetInstance = this.deps.registry.getOrHydrateScope( + toAppSessionId(targetSessionId) + ).instance this.deps.compaction.reset(targetSessionId, targetInstance) this.deps.memory.seedExtractionCursor(targetSessionId, clonedMemoryCursorOrderSeq) } - assertNoActivePendingInputs(sessionId: string): void { - this.deps.admission.assertNoActiveInputs(sessionId) + async assertNoActivePendingInputs(sessionId: string): Promise { + await this.deps.admission.assertNoActiveInputs(sessionId) } } diff --git a/src/main/agent/deepchat/runtime/turnCoordinator.ts b/src/main/agent/deepchat/runtime/turnCoordinator.ts index b859d1c4ff..6db4c15309 100644 --- a/src/main/agent/deepchat/runtime/turnCoordinator.ts +++ b/src/main/agent/deepchat/runtime/turnCoordinator.ts @@ -1,3 +1,4 @@ +import type { HarnessControlOwner } from '@shared/harness/control' import type { PluginContextPort } from '@shared/types/userPlugin' import type { ProviderModelResolutionPort } from '@/provider/settings' import logger from '@shared/logger' @@ -54,10 +55,7 @@ import type { CompactionRuntimeCoordinator } from './compactionRuntimeCoordinato import { hasCompactionBoundaryAdvanced, type CompactionService } from './compactionService' import { isContextWindowErrorLike } from './contextWindowError' import { resolveInterleavedReasoningConfig } from './generationSettings' -import { - updateToolCallResponse, - parseAssistantBlocks -} from './interactionProjection' +import { updateToolCallResponse, parseAssistantBlocks } from './interactionProjection' import { buildTerminalErrorBlocks, type SessionTranscript } from '@/session/data/transcript' import type { DeepChatEventPublisher, ProcessResult } from './types' import { @@ -66,10 +64,7 @@ import { stampTerminalMetadata } from './runtimeMetadata' import type { SessionSettingsStore } from '@/session/data/settings' -import type { - TapeProviderAttemptReader, - TapeReconciliationPort -} from '@/tape/ports/capabilities' +import type { TapeProviderAttemptReader, TapeReconciliationPort } from '@/tape/ports/capabilities' import { isExecutionJournalError } from '@/tape/domain/executionJournal' import { isCommittedRunProjectionError } from './runTerminalProjectionError' import { @@ -106,10 +101,7 @@ import type { RuntimeHookSink } from './runtimeHookSink' import type { DeepChatTaskContractContextPort } from '@/agent/deepchat/loop/ports' import type { SessionIdentityService } from './sessionIdentityService' import { meetTaskContractToolDefinitions } from './taskContractCapability' -import type { - ClaimedPendingInputHandle, - TurnCompletion -} from './pendingInputContracts' +import type { ClaimedPendingInputHandle, TurnCompletion } from './pendingInputContracts' import { createDeepSeekResponsesReplayProjector } from '@/provider/deepseekResponsesAdapter' import type { CommandShellService } from '@/agent/shared/process/commandShellService' import type { SessionPendingInputs } from '@/session/data/pendingInputs' @@ -129,7 +121,6 @@ type TurnRunLifecyclePort = Pick< RunLifecycleCoordinator, | 'applyProcessResultStatus' | 'assertCurrentInstance' - | 'canSettleOperation' | 'clearOperationController' | 'clearRun' | 'ensureOperationController' @@ -139,7 +130,6 @@ type TurnRunLifecyclePort = Pick< | 'schedulePendingInputDrain' | 'scopeFor' | 'settleAbortedTurn' - | 'transitionCurrentStatus' | 'transitionStatus' > @@ -184,10 +174,8 @@ export interface TurnCoordinatorPorts { 'resolveProjectDir' | 'getEffectiveGenerationSettings' > promptAssembly: Pick - identity: Partial> & Pick< - SessionIdentityService, - 'getAgentId' | 'getSessionKind' | 'isAcpBackedSubagentSession' - > + identity: Partial> & + Pick skillContextMaterializer: SkillContextMaterializer taskContractContext: DeepChatTaskContractContextPort commandShell: Pick @@ -278,10 +266,7 @@ export class TurnCoordinator { modelConfig, state.modelId ) - const contextObservation = instance.getContextWindowObservation( - state.providerId, - state.modelId - ) + const contextObservation = instance.getContextWindowObservation(state.providerId, state.modelId) const effectiveContextBudget = useContextBudget ? resolveEffectiveContextBudget({ configuredContextLength: configuredContextBudgetLength, @@ -314,13 +299,11 @@ export class TurnCoordinator { } const requestedSessionActiveSkillNames = input.sessionActiveSkillNamesOverride === undefined - ? await this.runPreStreamStep( - { sessionId, messageId, step: 'active-skills', signal }, - () => - awaitWithAbort( - this.ports.toolResolver.resolveActiveSkillNamesForToolProfile(sessionId), - signal - ) + ? await this.runPreStreamStep({ sessionId, messageId, step: 'active-skills', signal }, () => + awaitWithAbort( + this.ports.toolResolver.resolveActiveSkillNamesForToolProfile(sessionId), + signal + ) ) : [...input.sessionActiveSkillNamesOverride] const sessionActiveSkillNames = await awaitWithAbort( @@ -489,11 +472,12 @@ export class TurnCoordinator { let pluginRunStarted = false let initializedScope: SessionRuntimeScope | undefined let initializedAbortController: AbortController | undefined + let initializedOperationOwner: { kind: 'operation'; id: string } | undefined let statusTransitionAttempted = false let statusBeforeInitialization: DeepChatSessionState['status'] | undefined let linkedSteerMessageIds: string[] = [] let reservedSteerAssistantMessageId: string | null = null - const initializeTurn = () => { + const initializeTurn = async () => { const instance = this.ports.registry.getHydratedScope(toAppSessionId(sessionId))?.instance if (!instance) throw new Error(`Session ${sessionId} not found`) instance.clearPreStreamTranscriptAnchor() @@ -505,10 +489,12 @@ export class TurnCoordinator { instance.setPreStreamTranscriptAnchorId(reservedTranscriptAnchor) } const scope = this.ports.runLifecycle.scopeFor(sessionId, instance) + await instance.controlReady() + scope.assertCurrent() initializedScope = scope const state = instance.getRuntimeState() if (!state) throw new Error(`Session ${sessionId} not found`) - if (this.ports.runLifecycle.hasPendingInteractions(sessionId)) { + if (await this.ports.runLifecycle.hasPendingInteractions(sessionId)) { throw new Error('Pending tool interactions must be resolved before sending a new message.') } if (isSendMessageInputEmpty(content)) { @@ -526,18 +512,26 @@ export class TurnCoordinator { state.modelId, providerModelFacts ) - const projectDir = this.ports.sessionSettings.resolveProjectDir(sessionId, context?.projectDir, instance) + const projectDir = this.ports.sessionSettings.resolveProjectDir( + sessionId, + context?.projectDir, + instance + ) logger.info( `[DeepChatAgent] processMessage session=${sessionId} promptLength=${content.text.length} fileCount=${content.files?.length ?? 0} hasProjectDir=${projectDir !== null}` ) this.ports.pluginContext?.beginRun?.(sessionId) pluginRunStarted = true - const preStreamAbortController = this.ports.runLifecycle.ensureOperationController(scope) + const preStreamAbortController = + await this.ports.runLifecycle.ensureOperationController(scope) + const operationOwner = instance.operationOwner(preStreamAbortController) + claimedInput?.bindOwner(preStreamAbortController, operationOwner) + initializedOperationOwner = operationOwner initializedAbortController = preStreamAbortController statusBeforeInitialization = state.status statusTransitionAttempted = true - this.ports.runLifecycle.transitionStatus(scope, 'generating') + await this.ports.runLifecycle.transitionStatus(scope, 'generating', operationOwner) return { scope, instance, @@ -547,11 +541,12 @@ export class TurnCoordinator { supportsAudioInput, projectDir, preStreamAbortController, + operationOwner, preStreamAbortSignal: preStreamAbortController.signal } } - let initializedTurn: ReturnType + let initializedTurn: Awaited> try { if (isSteerClaim && claimedInput) { linkedSteerMessageIds = claimedInput.messageIds @@ -560,7 +555,7 @@ export class TurnCoordinator { throw new Error(`Claimed steer input ${claimedInput.id} has no reserved transcript.`) } } - initializedTurn = initializeTurn() + initializedTurn = await initializeTurn() } catch (error) { if (pluginRunStarted) this.ports.pluginContext?.endRun?.(sessionId) if (claimedInput && !claimedInput.disposition) { @@ -574,9 +569,9 @@ export class TurnCoordinator { ) this.ports.messageProjection.refresh(sessionId, reservedSteerAssistantMessageId) } - claimedInput.settle({ kind: 'consume' }) + await claimedInput.settle({ kind: 'consume' }) } else { - claimedInput.settle({ kind: 'release-before-user-fact' }) + await claimedInput.settle({ kind: 'release-before-user-fact' }) } } catch (releaseError) { console.warn('[DeepChatAgent] failed to release claimed pending input:', releaseError) @@ -584,12 +579,17 @@ export class TurnCoordinator { } if (initializedScope && initializedAbortController) { try { - this.ports.runLifecycle.clearOperationController( + await this.ports.runLifecycle.clearOperationController( initializedScope, - initializedAbortController + initializedAbortController, + undefined, + initializedOperationOwner ? { operationId: initializedOperationOwner.id } : undefined ) } catch (cleanupError) { - console.warn('[DeepChatAgent] failed to clear rejected turn abort controller:', cleanupError) + console.warn( + '[DeepChatAgent] failed to clear rejected turn abort controller:', + cleanupError + ) } } initializedScope?.instance.clearPreStreamTranscriptAnchor() @@ -599,7 +599,11 @@ export class TurnCoordinator { statusBeforeInitialization !== undefined ) { try { - this.ports.runLifecycle.transitionStatus(initializedScope, statusBeforeInitialization) + await this.ports.runLifecycle.transitionStatus( + initializedScope, + statusBeforeInitialization, + initializedOperationOwner! + ) } catch (cleanupError) { console.warn('[DeepChatAgent] failed to restore rejected turn status:', cleanupError) } @@ -615,6 +619,7 @@ export class TurnCoordinator { supportsAudioInput, projectDir, preStreamAbortController, + operationOwner, preStreamAbortSignal } = initializedTurn const providerReplayProjector = createDeepSeekResponsesReplayProjector({ @@ -633,6 +638,10 @@ export class TurnCoordinator { let assistantMessageId: string | null = reservedSteerAssistantMessageId let assistantCreationAttempted = isSteerClaim let streamRunId: string | undefined + const turnOwner = (): HarnessControlOwner => + streamRunId ? { kind: 'run', id: streamRunId } : operationOwner + const transitionTurnStatus = async (status: DeepChatSessionState['status']) => + await this.ports.runLifecycle.transitionStatus(scope, status, turnOwner()) let attachmentPreparation: AttachmentPreparationSummary | undefined try { @@ -690,12 +699,12 @@ export class TurnCoordinator { } if (attachmentPreparation?.status === 'needs_user_action') { if (claimedInput) { - claimedInput.settle({ + await claimedInput.settle({ kind: 'block', attachmentPreparation }) } - this.ports.runLifecycle.transitionStatus(scope, 'idle') + await transitionTurnStatus('idle') return complete({ requestId: null, messageId: null, attachmentPreparation }) } const { @@ -737,16 +746,18 @@ export class TurnCoordinator { awaitWithAbort( this.ports.skillContextMaterializer.prepareFresh({ sessionId, - agentId: instance.getAgentId()?.trim() || this.ports.identity.getAgentId(sessionId) || 'deepchat', + agentId: + instance.getAgentId()?.trim() || + this.ports.identity.getAgentId(sessionId) || + 'deepchat', messageSkillNames: messageActiveSkillNames, sessionSkillNames: sessionActiveSkillNames }), preStreamAbortSignal ) ) - const candidateSkillBodies = this.ports.skillContextMaterializer.preview( - preparedSkillContexts - ) + const candidateSkillBodies = + this.ports.skillContextMaterializer.preview(preparedSkillContexts) let unguardedBasePromptAssembly = await this.assembleBasePrompt({ sessionId, messageId: userMessageId, @@ -784,10 +795,8 @@ export class TurnCoordinator { const ensureHistory = () => runSynchronousPreStreamStep(sessionId, 'tape-ready', () => getTapeContextHistoryRecords( - this.ports.tapeReconciliation.ensureSessionTapeReady( - sessionId, - this.ports.messageStore - ).historyRecords + this.ports.tapeReconciliation.ensureSessionTapeReady(sessionId, this.ports.messageStore) + .historyRecords ) ) const prepareCompactionIntent = async (historyRecords: ChatMessageRecord[]) => { @@ -892,6 +901,9 @@ export class TurnCoordinator { historyRecords = preparedInput.history summaryState = preparedInput.summary } else { + const appendClaimedUserFact = claimedInput + ? await claimedInput.prepareUserFact() + : undefined const preparedInput = await this.ports.inputPreparationCoordinator.prepareInitial({ ensureHistory, prepareIntent: prepareCompactionIntent, @@ -920,10 +932,12 @@ export class TurnCoordinator { 'user-message-create', () => claimedInput && claimedInput.source !== 'steer' - ? this.ports.pendingInputs.createClaimedQueueUserMessage( - sessionId, - claimedInput.id, - userContent + ? appendClaimedUserFact!(() => + this.ports.pendingInputs.createClaimedQueueUserMessage( + sessionId, + claimedInput.id, + userContent + ) ) : this.ports.messageStore.createUserMessage( sessionId, @@ -1004,18 +1018,21 @@ export class TurnCoordinator { if (state.providerId !== 'acp' && this.ports.pluginContext?.hasHooks()) { await this.ports.pluginContext.accept({ - sessionId, messageId: userMessageId, prompt: content.text, cwd: projectDir, - model: state.modelId, parentSessionId: this.ports.identity.getParentSessionId?.(sessionId), - agentId: this.ports.identity.getAgentId(sessionId), signal: preStreamAbortSignal + sessionId, + messageId: userMessageId, + prompt: content.text, + cwd: projectDir, + model: state.modelId, + parentSessionId: this.ports.identity.getParentSessionId?.(sessionId), + agentId: this.ports.identity.getAgentId(sessionId), + signal: preStreamAbortSignal }) scope.assertCurrent() throwIfAbortRequested(preStreamAbortSignal) } const buildContextView = ( - contextContributions: Awaited< - ReturnType - > + contextContributions: Awaited> ) => { const contextBuildStartedAt = Date.now() // A retried user prompt that is anchored and kept in the transcript is @@ -1124,7 +1141,7 @@ export class TurnCoordinator { throwIfAbortRequested(preStreamAbortSignal) if (claimedInput?.source === 'send') { - claimedInput.settle({ kind: 'consume' }) + await claimedInput.settle({ kind: 'consume' }) } if (context?.emitRefreshBeforeStream) { @@ -1141,6 +1158,10 @@ export class TurnCoordinator { }, preStreamStartedAt ) + const consumeBeforeProviderStream = + context?.consumeClaimBeforeProviderStream && claimedInput && !claimedInput.disposition + ? await claimedInput.prepareConsume() + : undefined let streamResult: { runId: string; result: ProcessResult } try { streamResult = await this.ports.loopRunner.run({ @@ -1162,7 +1183,7 @@ export class TurnCoordinator { initialRuntimeContextLimitTokens: runtimeContextLimitTokens ?? null, taskContractContext, providerReplayProjector, - abortController: preStreamAbortController, + abortController: preStreamAbortController ?? undefined, maxProviderRounds: context?.maxProviderRounds, interleavedReasoning, materializedSkillContexts, @@ -1184,7 +1205,7 @@ export class TurnCoordinator { claimedInput && !claimedInput.disposition ) { - claimedInput.settle({ kind: 'consume' }) + consumeBeforeProviderStream?.() } providerBoundary.complete() }, @@ -1204,18 +1225,20 @@ export class TurnCoordinator { result.status === 'paused' || result.status === 'aborted' ) { - claimedInput.settle({ kind: 'consume' }) + await claimedInput.settle({ kind: 'consume' }) } else { - this.rollbackPendingInputTurn(sessionId, userMessageId, instance) + await claimedInput.rollback(() => + this.rollbackPendingInputTurn(sessionId, userMessageId, instance) + ) userMessageId = null assistantMessageId = null - claimedInput.settle({ kind: 'release-after-rollback' }) + await claimedInput.settle({ kind: 'release-after-rollback' }) } } try { - this.ports.runLifecycle.applyProcessResultStatus(sessionId, result, runId) + await this.ports.runLifecycle.applyProcessResultStatus(sessionId, result, runId) } finally { - this.ports.runLifecycle.clearRun(scope, runId) + await this.ports.runLifecycle.clearRun(scope, runId) } if (!claimedInput && result?.status === 'completed') { this.ports.runLifecycle.schedulePendingInputDrain(sessionId, 'completed') @@ -1265,16 +1288,18 @@ export class TurnCoordinator { if (claimedInput && !claimedInput.disposition) { try { if (streamRunId) { - claimedInput.settle({ kind: 'consume' }) + await claimedInput.settle({ kind: 'consume' }) } else if (isSteerClaim) { - claimedInput.settle({ kind: 'consume' }) + await claimedInput.settle({ kind: 'consume' }) } else if (!userMessageId) { - claimedInput.settle({ kind: 'release-before-user-fact' }) + await claimedInput.settle({ kind: 'release-before-user-fact' }) } else { - this.rollbackPendingInputTurn(sessionId, userMessageId, instance) + await claimedInput.rollback(() => + this.rollbackPendingInputTurn(sessionId, userMessageId, instance) + ) userMessageId = null assistantMessageId = null - claimedInput.settle({ kind: 'release-after-rollback' }) + await claimedInput.settle({ kind: 'release-after-rollback' }) } } catch (releaseError) { console.warn('[DeepChatAgent] failed to settle Journal-failed input:', releaseError) @@ -1294,7 +1319,7 @@ export class TurnCoordinator { } } observeRejectedTurn(err) - this.ports.runLifecycle.transitionCurrentStatus(sessionId, 'idle') + await transitionTurnStatus('idle') throw err } const aborted = committedAbortTerminal @@ -1302,8 +1327,7 @@ export class TurnCoordinator { : committedErrorTerminal ? false : preStreamAbortSignal.aborted - const pendingInputHandoff = - preStreamAbortSignal.reason === PENDING_INPUT_ABORT_REASON + const pendingInputHandoff = preStreamAbortSignal.reason === PENDING_INPUT_ABORT_REASON const staleInstance = isStaleDeepChatInstanceError(errorToProject) if (!userMessageId && pendingInputHandoff && claimedInput?.source !== 'steer') { userMessageId = claimedInput?.messageIds.at(-1) ?? null @@ -1318,14 +1342,14 @@ export class TurnCoordinator { if (claimedInput && !claimedInput.disposition) { if (committedErrorTerminal || isSteerClaim) { try { - claimedInput.settle({ kind: 'consume' }) + await claimedInput.settle({ kind: 'consume' }) } catch (releaseError) { console.warn('[DeepChatAgent] failed to consume claimed steer input:', releaseError) } } else if (!userMessageId) { pendingInputFailedBeforeUserFact = true try { - claimedInput.settle({ kind: 'release-before-user-fact' }) + await claimedInput.settle({ kind: 'release-before-user-fact' }) } catch (releaseError) { console.warn('[DeepChatAgent] failed to release claimed pending input:', releaseError) } @@ -1334,12 +1358,14 @@ export class TurnCoordinator { // Abort or instance replacement preserves the partial turn. Other failures make the // input retryable only after all facts derived from this user message are rolled back. if (aborted || staleInstance) { - claimedInput.settle({ kind: 'consume' }) + await claimedInput.settle({ kind: 'consume' }) } else { - this.rollbackPendingInputTurn(sessionId, userMessageId, instance) + await claimedInput.rollback(() => + this.rollbackPendingInputTurn(sessionId, userMessageId, instance) + ) userMessageId = null assistantMessageId = null - claimedInput.settle({ kind: 'release-after-rollback' }) + await claimedInput.settle({ kind: 'release-after-rollback' }) } } catch (releaseError) { console.warn('[DeepChatAgent] failed to release claimed queue input:', releaseError) @@ -1369,11 +1395,21 @@ export class TurnCoordinator { this.ports.messageProjection.refresh(sessionId, assistantMessageId) assistantMessageId = null } - this.ports.runLifecycle.clearOperationController(scope, preStreamAbortController) - this.ports.runLifecycle.applyProcessResultStatus(sessionId, { - status: 'completed', - stopReason: PENDING_INPUT_ABORT_REASON - }) + await this.ports.runLifecycle.clearOperationController( + scope, + preStreamAbortController, + undefined, + { operationId: operationOwner.id, runId: streamRunId } + ) + await this.ports.runLifecycle.applyProcessResultStatus( + sessionId, + { + status: 'completed', + stopReason: PENDING_INPUT_ABORT_REASON + }, + streamRunId, + turnOwner() + ) if (!claimedInput) { this.ports.runLifecycle.schedulePendingInputDrain(sessionId, 'completed') } @@ -1384,7 +1420,12 @@ export class TurnCoordinator { if (userMessageId && !isSteerClaim) { this.ports.messageProjection.refresh(sessionId, userMessageId) } - this.ports.runLifecycle.clearOperationController(scope, preStreamAbortController) + await this.ports.runLifecycle.clearOperationController( + scope, + preStreamAbortController, + undefined, + { operationId: operationOwner.id, runId: streamRunId } + ) const abortMetadata = stampTerminalMetadata( { ...(streamRunId ? { runId: streamRunId } : {}), @@ -1396,11 +1437,12 @@ export class TurnCoordinator { 'aborted', 'user_stop' ) - this.ports.runLifecycle.settleAbortedTurn( + await this.ports.runLifecycle.settleAbortedTurn( sessionId, assistantMessageId, streamRunId, - JSON.stringify(abortMetadata) + JSON.stringify(abortMetadata), + turnOwner() ) // Once the user fact exists, stop/steer advances to the next item. Before that boundary the // released item remains visible and retryable until an explicit user action. @@ -1418,12 +1460,7 @@ export class TurnCoordinator { const stopReason = committedErrorTerminal?.stopReason ?? (isContextWindowErrorLike(errorToProject) ? 'context_window' : 'pre_stream_error') - if ( - !assistantMessageId && - !assistantCreationAttempted && - userMessageId && - !claimedInput - ) { + if (!assistantMessageId && !assistantCreationAttempted && userMessageId && !claimedInput) { try { assistantCreationAttempted = true assistantMessageId = this.ports.messageStore.createAssistantMessage( @@ -1461,10 +1498,7 @@ export class TurnCoordinator { ) this.ports.messageProjection.refresh(sessionId, assistantMessageId) this.ports.publishEvent('chat.stream.failed', { - requestId: this.ports.runLifecycle.resolveStreamRequestId( - sessionId, - assistantMessageId - ), + requestId: this.ports.runLifecycle.resolveStreamRequestId(sessionId, assistantMessageId), sessionId, messageId: assistantMessageId, failedAt: Date.now(), @@ -1479,22 +1513,22 @@ export class TurnCoordinator { usage: buildUsageFromMetadata(terminalMetadata) ?? null, error: { message: errorMessage } }) - this.ports.runLifecycle.transitionCurrentStatus(sessionId, 'error') + await transitionTurnStatus('error') return complete({ requestId: assistantMessageId, messageId: assistantMessageId }) } finally { if (pluginRunStarted) this.ports.pluginContext?.endRun?.(sessionId) - const ownsTurnLifecycle = this.ports.runLifecycle.canSettleOperation( + await this.ports.runLifecycle.clearOperationController( scope, - preStreamAbortController + preStreamAbortController, + () => { + instance.clearPreStreamTranscriptAnchor() + instance.replaceRuntimeActivatedSkills([]) + }, + { operationId: operationOwner.id, runId: streamRunId } ) - this.ports.runLifecycle.clearOperationController(scope, preStreamAbortController) - if (ownsTurnLifecycle) { - instance.clearPreStreamTranscriptAnchor() - instance.replaceRuntimeActivatedSkills([]) - } } } @@ -1507,26 +1541,36 @@ export class TurnCoordinator { ): Promise { const instance = this.ports.registry.getOrHydrateScope(toAppSessionId(sessionId)).instance const scope = this.ports.runLifecycle.scopeFor(sessionId, instance) - if (!instance.tryBeginResume(messageId)) { + const resumeLease = await instance.tryBeginResume(messageId) + if (!resumeLease) { return false } instance.replaceRuntimeActivatedSkills([]) let preStreamAbortController: AbortController | null = null let preStreamAbortSignal: AbortSignal | undefined let streamRunId: string | undefined + let operationOwner: { kind: 'operation'; id: string } | undefined + const turnOwner = (): HarnessControlOwner | undefined => + streamRunId ? { kind: 'run', id: streamRunId } : operationOwner + const transitionTurnStatus = async (status: DeepChatSessionState['status']) => { + const owner = turnOwner() + return owner ? await this.ports.runLifecycle.transitionStatus(scope, status, owner) : false + } const resumeAccounting = initialAccounting ?? parseMessageMetadata(this.ports.messageStore.getMessage(messageId)?.metadata ?? '{}') try { + await instance.controlReady() scope.assertCurrent() const state = instance.getRuntimeState() if (!state) { throw new Error(`Session ${sessionId} not found`) } - this.ports.runLifecycle.transitionStatus(scope, 'generating') - preStreamAbortController = this.ports.runLifecycle.ensureOperationController(scope) + preStreamAbortController = await this.ports.runLifecycle.ensureOperationController(scope) + operationOwner = instance.operationOwner(preStreamAbortController) + await transitionTurnStatus('generating') preStreamAbortSignal = preStreamAbortController.signal instance.clearPreStreamTranscriptAnchor() instance.setPreStreamTranscriptAnchorId(messageId) @@ -1556,7 +1600,11 @@ export class TurnCoordinator { searchIntent && providerModelFacts.capabilitySnapshot.supportsSearch && providerModelFacts.capabilitySnapshot.searchExecution === 'provider' - const projectDir = this.ports.sessionSettings.resolveProjectDir(sessionId, undefined, instance) + const projectDir = this.ports.sessionSettings.resolveProjectDir( + sessionId, + undefined, + instance + ) const recoveredSkillBatch = resumeAccounting.runId ? this.ports.skillContextMaterializer.recoverResume({ sessionId, @@ -1828,9 +1876,8 @@ export class TurnCoordinator { budgetToolCall.id, resumeBudget.message ) - await this.runPreStreamStep( - { sessionId, messageId, step: 'tool-output-cleanup' }, - () => this.ports.toolOutputGuard.cleanupOffloadedOutput(budgetToolCall.offloadPath) + await this.runPreStreamStep({ sessionId, messageId, step: 'tool-output-cleanup' }, () => + this.ports.toolOutputGuard.cleanupOffloadedOutput(budgetToolCall.offloadPath) ) } else if (resumeBudget?.kind === 'terminal_error') { updateToolCallResponse(initialBlocks, budgetToolCall.id, resumeBudget.message, true) @@ -1854,15 +1901,14 @@ export class TurnCoordinator { errorMessage: resumeBudget.message, usage: buildUsageFromMetadata(terminalMetadata) }) - this.ports.runLifecycle.transitionCurrentStatus(sessionId, 'error') + await transitionTurnStatus('error') this.ports.memoryIngestionObserver.afterTurnSettled({ session: instance.getMemorySessionHandle(), origin: 'resume', outcome: { kind: 'returned', status: 'error' } }) - await this.runPreStreamStep( - { sessionId, messageId, step: 'tool-output-cleanup' }, - () => this.ports.toolOutputGuard.cleanupOffloadedOutput(budgetToolCall.offloadPath) + await this.runPreStreamStep({ sessionId, messageId, step: 'tool-output-cleanup' }, () => + this.ports.toolOutputGuard.cleanupOffloadedOutput(budgetToolCall.offloadPath) ) return false } @@ -1891,7 +1937,7 @@ export class TurnCoordinator { initialRuntimeContextLimitTokens: runtimeContextLimitTokens ?? null, taskContractContext, toolMode, - abortController: preStreamAbortController, + abortController: preStreamAbortController ?? undefined, tools, toolCatalogSnapshot, commandShell, @@ -1928,9 +1974,9 @@ export class TurnCoordinator { const { runId, result } = streamResult streamRunId = runId try { - this.ports.runLifecycle.applyProcessResultStatus(sessionId, result, runId) + await this.ports.runLifecycle.applyProcessResultStatus(sessionId, result, runId) } finally { - this.ports.runLifecycle.clearRun(scope, runId) + await this.ports.runLifecycle.clearRun(scope, runId) } if (result?.status === 'completed' || result?.status === 'aborted') { this.ports.runLifecycle.schedulePendingInputDrain(sessionId, 'completed') @@ -1944,6 +1990,7 @@ export class TurnCoordinator { } return true } catch (error) { + if (!scope.isCurrent()) return false const committedProjectionError = isCommittedRunProjectionError(error) ? error : null const committedErrorTerminal = committedProjectionError?.terminal.outcome === 'error' @@ -1967,7 +2014,7 @@ export class TurnCoordinator { isExecutionJournalError(error) || (committedProjectionError && !committedErrorTerminal && !committedAbortTerminal) ) { - this.ports.runLifecycle.transitionCurrentStatus(sessionId, 'idle') + await transitionTurnStatus('idle') throw error } if (isStaleDeepChatInstanceError(errorToProject)) { @@ -1992,14 +2039,21 @@ export class TurnCoordinator { ) this.ports.messageProjection.refresh(sessionId, messageId) } - this.ports.runLifecycle.clearOperationController( + await this.ports.runLifecycle.clearOperationController( scope, - preStreamAbortController ?? undefined + preStreamAbortController ?? undefined, + undefined, + operationOwner ? { operationId: operationOwner.id, runId: streamRunId } : undefined + ) + await this.ports.runLifecycle.applyProcessResultStatus( + sessionId, + { + status: 'completed', + stopReason: PENDING_INPUT_ABORT_REASON + }, + streamRunId, + turnOwner() ) - this.ports.runLifecycle.applyProcessResultStatus(sessionId, { - status: 'completed', - stopReason: PENDING_INPUT_ABORT_REASON - }) this.ports.runLifecycle.schedulePendingInputDrain(sessionId, 'completed') return false } @@ -2010,11 +2064,14 @@ export class TurnCoordinator { ? false : (preStreamAbortSignal?.aborted ?? false) if (aborted) { - this.ports.runLifecycle.clearOperationController( + await this.ports.runLifecycle.clearOperationController( scope, - preStreamAbortController ?? undefined + preStreamAbortController ?? undefined, + undefined, + operationOwner ? { operationId: operationOwner.id, runId: streamRunId } : undefined ) - this.ports.runLifecycle.settleAbortedTurn( + if (!scope.isCurrent()) return false + await this.ports.runLifecycle.settleAbortedTurn( sessionId, messageId, streamRunId, @@ -2022,7 +2079,8 @@ export class TurnCoordinator { streamRunId ? stampTerminalMetadata(resumeAccounting, 'aborted', 'user_stop', streamRunId) : stampInteractionResolution(resumeAccounting, 'cancelled') - ) + ), + turnOwner() ) // Stop/steer: continue the queue automatically with the next item (steer items first). this.ports.runLifecycle.schedulePendingInputDrain(sessionId, 'completed') @@ -2053,21 +2111,19 @@ export class TurnCoordinator { errorMessage, usage: buildUsageFromMetadata(terminalMetadata) }) - this.ports.runLifecycle.transitionCurrentStatus(sessionId, 'error') + await transitionTurnStatus('error') throw errorToProject } finally { - const ownsTurnLifecycle = - preStreamAbortController !== null && - this.ports.runLifecycle.canSettleOperation(scope, preStreamAbortController) - this.ports.runLifecycle.clearOperationController( + await this.ports.runLifecycle.clearOperationController( scope, - preStreamAbortController ?? undefined + preStreamAbortController ?? undefined, + () => { + instance.clearPreStreamTranscriptAnchor() + instance.replaceRuntimeActivatedSkills([]) + }, + operationOwner ? { operationId: operationOwner.id, runId: streamRunId } : undefined ) - if (ownsTurnLifecycle) { - instance.clearPreStreamTranscriptAnchor() - instance.replaceRuntimeActivatedSkills([]) - } - instance.finishResume(messageId) + await instance.finishResume(resumeLease) } } @@ -2138,7 +2194,8 @@ function resolveAssistantTurnMessageSkillNames( const assistant = messageStore.getMessage(assistantMessageId) if (!assistant || assistant.sessionId !== sessionId || assistant.role !== 'assistant') return [] const user = messageStore.getLastUserMessageBeforeOrAt(sessionId, assistant.orderSeq) - const activeSkills = user?.role === 'user' ? extractUserMessageInput(user.content).activeSkills : [] + const activeSkills = + user?.role === 'user' ? extractUserMessageInput(user.content).activeSkills : [] return Array.isArray(activeSkills) ? activeSkills : [] } diff --git a/src/main/agent/deepchat/runtime/types.ts b/src/main/agent/deepchat/runtime/types.ts index afdec84116..f49a21ec96 100644 --- a/src/main/agent/deepchat/runtime/types.ts +++ b/src/main/agent/deepchat/runtime/types.ts @@ -190,7 +190,7 @@ export interface ProcessControlCollaborators { params?: string }, commitDecision: (granted: boolean) => void - ) => void + ) => Promise getActiveSkillNames?: () => string[] getEnabledMcpServerIds?: () => string[] | null | undefined getAgentId?: () => string | undefined diff --git a/src/main/agent/invocationAdmission.ts b/src/main/agent/invocationAdmission.ts index 0679fafbd3..58284d0783 100644 --- a/src/main/agent/invocationAdmission.ts +++ b/src/main/agent/invocationAdmission.ts @@ -1,702 +1,2 @@ -import { types as utilTypes } from 'node:util' -import { - BoundedNumberRing, - MAX_DIAGNOSTIC_DISTRIBUTION_SAMPLES, - summarizeNumberDistribution, - type NumberDistribution -} from '@/lib/boundedNumberRing' -import { BoundedObservationQueue } from '@/lib/boundedObservationQueue' -import { elapsedMonotonicBetween, readMonotonicNow } from '@/lib/monotonicTime' - -export const DEFAULT_AGENT_INVOCATION_CAPACITY = 6 -export const DEFAULT_AGENT_INVOCATION_MAX_PENDING = 256 -const MAX_AGENT_INVOCATION_IDENTIFIER_LENGTH = 256 - -export interface AgentInvocationPermit { - release(): void -} - -export type AgentInvocationLeaseState = 'suspended' | 'acquiring' | 'active' | 'released' - -export interface AgentInvocationLease { - readonly state: AgentInvocationLeaseState - resume(options?: { signal?: AbortSignal }): Promise - suspend(): void - release(): void -} - -export interface AgentInvocationAdmissionOptions { - ownerId: string - maxActiveForOwner?: number - signal?: AbortSignal - correlation?: AgentInvocationAdmissionCorrelation -} - -export interface AgentInvocationAdmissionCorrelation { - kind: 'live_delegation' - parentSessionId: string - delegationId: string - turnId: string -} - -export interface AgentInvocationAdmissionSnapshot { - capacity: number - active: number - pending: number - pendingOwners: number - closed: boolean - activeHighWater: number - pendingHighWater: number - granted: number - rejected: number - observationsDropped: number - waitMs: NumberDistribution - holdMs: NumberDistribution -} - -type AgentInvocationAdmissionState = Pick< - AgentInvocationAdmissionSnapshot, - 'capacity' | 'active' | 'pending' -> - -export type AgentInvocationAdmissionObservation = - | ({ - type: 'queued' - acquisitionSeq: number - } & AgentInvocationAdmissionCorrelation & - AgentInvocationAdmissionState) - | ({ - type: 'granted' - acquisitionSeq: number - waitMs?: number - } & AgentInvocationAdmissionCorrelation & - AgentInvocationAdmissionState) - | ({ - type: 'released' - acquisitionSeq: number - holdMs?: number - reason: 'permit_released' | 'lease_suspended' | 'lease_released' - active: number - pending: number - } & AgentInvocationAdmissionCorrelation) - | ({ - type: 'rejected' - acquisitionSeq: number - waitMs?: number - reason: 'queue_full' | 'aborted' | 'closed' - } & AgentInvocationAdmissionCorrelation & - AgentInvocationAdmissionState) - | ({ - type: 'closed' - } & Omit) - -export interface AgentInvocationAdmissionDiagnosticsOptions { - observe?: (observation: AgentInvocationAdmissionObservation) => void - observationsEnabled?: () => boolean - now?: () => number -} - -export interface AgentInvocationAdmissionPort { - acquire(options: AgentInvocationAdmissionOptions): Promise - createLease(options: AgentInvocationAdmissionOptions): AgentInvocationLease - run(options: AgentInvocationAdmissionOptions, task: () => Promise): Promise -} - -export class AgentInvocationAdmissionClosedError extends Error { - constructor(message = 'Agent invocation admission is closed.') { - super(message) - this.name = 'AgentInvocationAdmissionClosedError' - } -} - -export class AgentInvocationAdmissionAbortedError extends Error { - constructor() { - super('Agent invocation admission was cancelled while queued.') - this.name = 'AgentInvocationAdmissionAbortedError' - } -} - -export class AgentInvocationAdmissionQueueFullError extends Error { - constructor(maxPending: number) { - super(`Agent invocation admission queue is full (${maxPending} pending).`) - this.name = 'AgentInvocationAdmissionQueueFullError' - } -} - -interface AdmissionWaiter { - ownerId: string - maxActiveForOwner: number - correlation?: AgentInvocationAdmissionCorrelation - acquisitionSeq: number - queuedAt: number | undefined - resolve: (permit: AgentInvocationPermit) => void - reject: (error: Error) => void - signal?: AbortSignal - onAbort?: () => void - settled: boolean -} - -interface InternalAgentInvocationPermit extends AgentInvocationPermit { - releaseWithReason(reason: 'permit_released' | 'lease_suspended' | 'lease_released'): void -} - -export class AgentInvocationAdmission implements AgentInvocationAdmissionPort { - private readonly ownerQueues = new Map() - private readonly ownerRing: string[] = [] - private readonly activeByOwner = new Map() - private active = 0 - private pending = 0 - private closedError: AgentInvocationAdmissionClosedError | null = null - private acquisitionSequence = 0 - private activeHighWater = 0 - private pendingHighWater = 0 - private granted = 0 - private rejected = 0 - private readonly waitSamples = new BoundedNumberRing(MAX_DIAGNOSTIC_DISTRIBUTION_SAMPLES) - private readonly holdSamples = new BoundedNumberRing(MAX_DIAGNOSTIC_DISTRIBUTION_SAMPLES) - private readonly observationQueue: BoundedObservationQueue - private readonly now: () => number - - constructor( - private readonly capacity = DEFAULT_AGENT_INVOCATION_CAPACITY, - private readonly maxPending = DEFAULT_AGENT_INVOCATION_MAX_PENDING, - diagnostics: AgentInvocationAdmissionDiagnosticsOptions = {} - ) { - if (!Number.isInteger(capacity) || capacity < 1) { - throw new Error('Agent invocation capacity must be a positive integer.') - } - if (!Number.isInteger(maxPending) || maxPending < 0) { - throw new Error('Agent invocation pending limit must be a non-negative integer.') - } - this.observationQueue = new BoundedObservationQueue({ - observe: diagnostics.observe ?? (() => undefined), - enabled: diagnostics.observe ? (diagnostics.observationsEnabled ?? (() => true)) : () => false - }) - this.now = diagnostics.now ?? performance.now.bind(performance) - } - - acquire(options: AgentInvocationAdmissionOptions): Promise { - let ownerId: string - let maxActiveForOwner: number - try { - ownerId = normalizeOwnerId(options.ownerId) - maxActiveForOwner = normalizeOwnerLimit(options.maxActiveForOwner, this.capacity) - } catch (error) { - return Promise.reject(error) - } - const acquisitionSeq = this.nextAcquisitionSequence() - const startedAt = readMonotonicNow(this.now) - const correlation = this.snapshotCorrelation(options.correlation) - if (this.closedError) { - this.recordImmediateRejection(correlation, acquisitionSeq, 'closed') - return Promise.reject(this.closedError) - } - if (options.signal?.aborted) { - this.recordImmediateRejection(correlation, acquisitionSeq, 'aborted') - return Promise.reject(new AgentInvocationAdmissionAbortedError()) - } - if ( - this.active < this.capacity && - this.pending === 0 && - this.getActiveForOwner(ownerId) < maxActiveForOwner - ) { - return Promise.resolve(this.grant(ownerId, correlation, acquisitionSeq, startedAt)) - } - if (this.pending >= this.maxPending) { - this.recordImmediateRejection(correlation, acquisitionSeq, 'queue_full') - return Promise.reject(new AgentInvocationAdmissionQueueFullError(this.maxPending)) - } - - return new Promise((resolve, reject) => { - const waiter: AdmissionWaiter = { - ownerId, - maxActiveForOwner, - correlation, - acquisitionSeq, - queuedAt: startedAt, - resolve, - reject, - signal: options.signal, - settled: false - } - if (options.signal) { - waiter.onAbort = () => this.abortWaiter(waiter) - options.signal.addEventListener('abort', waiter.onAbort, { once: true }) - } - const queue = this.ownerQueues.get(ownerId) - if (queue) { - queue.push(waiter) - } else { - this.ownerQueues.set(ownerId, [waiter]) - this.ownerRing.push(ownerId) - } - this.pending += 1 - this.pendingHighWater = Math.max(this.pendingHighWater, this.pending) - if (waiter.correlation) { - this.emit({ - type: 'queued', - ...waiter.correlation, - acquisitionSeq, - ...this.currentState() - }) - } - this.dispatch() - }) - } - - createLease(options: AgentInvocationAdmissionOptions): AgentInvocationLease { - return new StatefulAgentInvocationLease(this, options) - } - - async run(options: AgentInvocationAdmissionOptions, task: () => Promise): Promise { - const permit = await this.acquire(options) - try { - if (options.signal?.aborted) { - throw new AgentInvocationAdmissionAbortedError() - } - return await task() - } finally { - permit.release() - } - } - - close(reason = 'Agent invocation admission is closed.'): void { - if (this.closedError) { - return - } - this.closedError = new AgentInvocationAdmissionClosedError(reason) - for (const queue of this.ownerQueues.values()) { - for (const waiter of queue) { - if (!waiter.settled) this.pending -= 1 - this.rejectWaiter(waiter, this.closedError, 'closed') - } - } - this.ownerQueues.clear() - this.ownerRing.splice(0) - this.pending = 0 - const snapshot = this.snapshot() - this.emit({ - type: 'closed', - capacity: snapshot.capacity, - active: snapshot.active, - pending: snapshot.pending, - activeHighWater: snapshot.activeHighWater, - pendingHighWater: snapshot.pendingHighWater, - granted: snapshot.granted, - rejected: snapshot.rejected, - observationsDropped: snapshot.observationsDropped, - waitMs: snapshot.waitMs, - holdMs: snapshot.holdMs - }) - } - - flushObservations(): Promise { - return this.observationQueue.flush() - } - - snapshot(): AgentInvocationAdmissionSnapshot { - return { - capacity: this.capacity, - active: this.active, - pending: this.pending, - pendingOwners: this.ownerQueues.size, - closed: this.closedError !== null, - activeHighWater: this.activeHighWater, - pendingHighWater: this.pendingHighWater, - granted: this.granted, - rejected: this.rejected, - observationsDropped: this.observationQueue.droppedCount, - waitMs: summarizeNumberDistribution(this.waitSamples.snapshot()), - holdMs: summarizeNumberDistribution(this.holdSamples.snapshot()) - } - } - - private dispatch(): void { - while (this.active < this.capacity && this.pending > 0 && this.ownerRing.length > 0) { - const ownersInRound = this.ownerRing.length - let grantedInRound = false - - for ( - let ownerIndex = 0; - ownerIndex < ownersInRound && this.active < this.capacity; - ownerIndex += 1 - ) { - const ownerId = this.ownerRing.shift()! - const queue = this.ownerQueues.get(ownerId) - if (!queue || queue.length === 0) { - this.ownerQueues.delete(ownerId) - continue - } - - const waiter = queue[0] - if (this.getActiveForOwner(ownerId) >= waiter.maxActiveForOwner) { - this.ownerRing.push(ownerId) - continue - } - - queue.shift() - if (queue.length > 0) { - this.ownerRing.push(ownerId) - } else { - this.ownerQueues.delete(ownerId) - } - if (waiter.settled) { - continue - } - - waiter.settled = true - this.detachAbort(waiter) - this.pending -= 1 - grantedInRound = true - waiter.resolve( - this.grant(ownerId, waiter.correlation, waiter.acquisitionSeq, waiter.queuedAt) - ) - } - - if (!grantedInRound) { - return - } - } - } - - private abortWaiter(waiter: AdmissionWaiter): void { - if (waiter.settled) { - return - } - const queue = this.ownerQueues.get(waiter.ownerId) - if (queue) { - const index = queue.indexOf(waiter) - if (index >= 0) { - queue.splice(index, 1) - } - if (queue.length === 0) { - this.ownerQueues.delete(waiter.ownerId) - const ownerIndex = this.ownerRing.indexOf(waiter.ownerId) - if (ownerIndex >= 0) { - this.ownerRing.splice(ownerIndex, 1) - } - } - } - this.pending -= 1 - this.rejectWaiter(waiter, new AgentInvocationAdmissionAbortedError(), 'aborted') - this.dispatch() - } - - private rejectWaiter(waiter: AdmissionWaiter, error: Error, reason: 'aborted' | 'closed'): void { - if (waiter.settled) { - return - } - waiter.settled = true - this.detachAbort(waiter) - this.rejected += 1 - const waitMs = this.elapsedSince(waiter.queuedAt) - if (waitMs !== undefined) this.waitSamples.push(waitMs) - if (waiter.correlation) { - this.emit({ - type: 'rejected', - ...waiter.correlation, - acquisitionSeq: waiter.acquisitionSeq, - ...(waitMs === undefined ? {} : { waitMs }), - reason, - ...this.currentState() - }) - } - waiter.reject(error) - } - - private detachAbort(waiter: AdmissionWaiter): void { - if (waiter.signal && waiter.onAbort) { - waiter.signal.removeEventListener('abort', waiter.onAbort) - } - } - - private getActiveForOwner(ownerId: string): number { - return this.activeByOwner.get(ownerId) ?? 0 - } - - private grant( - ownerId: string, - correlation: AgentInvocationAdmissionCorrelation | undefined, - acquisitionSeq: number, - startedAt: number | undefined - ): InternalAgentInvocationPermit { - this.active += 1 - this.activeByOwner.set(ownerId, this.getActiveForOwner(ownerId) + 1) - this.activeHighWater = Math.max(this.activeHighWater, this.active) - this.granted += 1 - const grantedAt = readMonotonicNow(this.now) - const waitMs = elapsedMonotonicBetween(startedAt, grantedAt) - if (waitMs !== undefined) this.waitSamples.push(waitMs) - if (correlation) { - this.emit({ - type: 'granted', - ...correlation, - acquisitionSeq, - ...(waitMs === undefined ? {} : { waitMs }), - ...this.currentState() - }) - } - let released = false - const releaseWithReason: InternalAgentInvocationPermit['releaseWithReason'] = (reason) => { - if (released) { - return - } - if (this.active <= 0) { - throw new Error('Agent invocation permit accounting underflow.') - } - const ownerActive = this.getActiveForOwner(ownerId) - if (ownerActive <= 0) { - throw new Error(`Agent invocation owner permit accounting underflow: ${ownerId}`) - } - released = true - this.active -= 1 - if (ownerActive === 1) { - this.activeByOwner.delete(ownerId) - } else { - this.activeByOwner.set(ownerId, ownerActive - 1) - } - const holdMs = this.elapsedSince(grantedAt) - if (holdMs !== undefined) this.holdSamples.push(holdMs) - if (correlation) { - this.emit({ - type: 'released', - ...correlation, - acquisitionSeq, - ...(holdMs === undefined ? {} : { holdMs }), - reason, - active: this.active, - pending: this.pending - }) - } - this.dispatch() - } - return { - release: () => releaseWithReason('permit_released'), - releaseWithReason - } - } - - private snapshotCorrelation( - correlation: AgentInvocationAdmissionCorrelation | undefined - ): AgentInvocationAdmissionCorrelation | undefined { - if (!correlation) return undefined - try { - if (typeof correlation !== 'object' || utilTypes.isProxy(correlation)) return undefined - const prototype = Object.getPrototypeOf(correlation) - if (prototype !== Object.prototype && prototype !== null) return undefined - const kind = Object.getOwnPropertyDescriptor(correlation, 'kind') - const parentSessionId = Object.getOwnPropertyDescriptor(correlation, 'parentSessionId') - const delegationId = Object.getOwnPropertyDescriptor(correlation, 'delegationId') - const turnId = Object.getOwnPropertyDescriptor(correlation, 'turnId') - if ( - !kind || - !('value' in kind) || - kind.value !== 'live_delegation' || - !parentSessionId || - !('value' in parentSessionId) || - !isBoundedAdmissionIdentifier(parentSessionId.value) || - !delegationId || - !('value' in delegationId) || - !isBoundedAdmissionIdentifier(delegationId.value) || - !turnId || - !('value' in turnId) || - !isBoundedAdmissionIdentifier(turnId.value) - ) { - return undefined - } - return { - kind: kind.value, - parentSessionId: parentSessionId.value, - delegationId: delegationId.value, - turnId: turnId.value - } - } catch { - return undefined - } - } - - private recordImmediateRejection( - correlation: AgentInvocationAdmissionCorrelation | undefined, - acquisitionSeq: number, - reason: 'queue_full' | 'aborted' | 'closed' - ): void { - this.rejected += 1 - if (!correlation) return - this.emit({ - type: 'rejected', - ...correlation, - acquisitionSeq, - waitMs: 0, - reason, - ...this.currentState() - }) - } - - private currentState(): AgentInvocationAdmissionState { - return { capacity: this.capacity, active: this.active, pending: this.pending } - } - - private nextAcquisitionSequence(): number { - this.acquisitionSequence = - this.acquisitionSequence >= Number.MAX_SAFE_INTEGER ? 1 : this.acquisitionSequence + 1 - return this.acquisitionSequence - } - - private elapsedSince(startedAt: number | undefined): number | undefined { - return elapsedMonotonicBetween(startedAt, readMonotonicNow(this.now)) - } - - private emit(observation: AgentInvocationAdmissionObservation): void { - if (observation.type !== 'closed') { - this.observationQueue.enqueue(observation) - return - } - this.observationQueue.enqueueWithDroppedCount((observationsDropped) => ({ - ...observation, - observationsDropped - })) - } -} - -class StatefulAgentInvocationLease implements AgentInvocationLease { - private currentState: AgentInvocationLeaseState = 'suspended' - private permit: InternalAgentInvocationPermit | null = null - private acquisition: - | { - controller: AbortController - promise: Promise - cleanupSignals: () => void - } - | undefined - - constructor( - private readonly admission: AgentInvocationAdmission, - private readonly options: AgentInvocationAdmissionOptions - ) {} - - get state(): AgentInvocationLeaseState { - return this.currentState - } - - resume(options?: { signal?: AbortSignal }): Promise { - if (this.currentState === 'released') { - return Promise.reject(new AgentInvocationAdmissionAbortedError()) - } - if (this.currentState === 'active') return Promise.resolve() - if (this.acquisition) { - if (this.currentState === 'acquiring') return this.acquisition.promise - return this.acquisition.promise - .catch(() => undefined) - .then(async () => await this.resume(options)) - } - - const controller = new AbortController() - const cleanupSignals = forwardAbortSignals([this.options.signal, options?.signal], controller) - this.currentState = 'acquiring' - const promise = this.admission - .acquire({ ...this.options, signal: controller.signal }) - .then((permit) => { - const internalPermit = permit as InternalAgentInvocationPermit - if ( - controller.signal.aborted || - this.currentState !== 'acquiring' || - this.acquisition?.controller !== controller - ) { - internalPermit.releaseWithReason( - this.currentState === 'released' ? 'lease_released' : 'lease_suspended' - ) - throw new AgentInvocationAdmissionAbortedError() - } - this.permit = internalPermit - this.currentState = 'active' - }) - .finally(() => { - if (this.acquisition?.controller !== controller) return - this.acquisition.cleanupSignals() - this.acquisition = undefined - if (this.currentState === 'acquiring') this.currentState = 'suspended' - }) - this.acquisition = { controller, promise, cleanupSignals } - return promise - } - - suspend(): void { - if (this.currentState === 'released' || this.currentState === 'suspended') return - if (this.currentState === 'active') { - this.releaseActivePermit('lease_suspended', 'suspended') - return - } - this.currentState = 'suspended' - this.acquisition?.controller.abort('Agent invocation lease suspended.') - } - - release(): void { - if (this.currentState === 'released') return - if (this.currentState === 'active') { - this.releaseActivePermit('lease_released', 'released') - return - } - this.currentState = 'released' - this.acquisition?.controller.abort('Agent invocation lease released.') - } - - private releaseActivePermit( - reason: 'lease_suspended' | 'lease_released', - nextState: 'suspended' | 'released' - ): void { - if (!this.permit) throw new Error('Active Agent invocation lease has no permit.') - this.permit.releaseWithReason(reason) - this.permit = null - this.currentState = nextState - } -} - -function forwardAbortSignals( - signals: Array, - target: AbortController -): () => void { - const listeners: Array<{ signal: AbortSignal; listener: () => void }> = [] - for (const signal of signals) { - if (!signal) continue - const listener = () => target.abort(signal.reason) - if (signal.aborted) { - listener() - break - } - signal.addEventListener('abort', listener, { once: true }) - listeners.push({ signal, listener }) - } - return () => { - for (const { signal, listener } of listeners) { - signal.removeEventListener('abort', listener) - } - } -} - -function normalizeOwnerId(ownerId: string): string { - const normalized = ownerId.trim() - if (!normalized || normalized.length > MAX_AGENT_INVOCATION_IDENTIFIER_LENGTH) { - throw new Error( - `Agent invocation ownerId must contain 1 to ${MAX_AGENT_INVOCATION_IDENTIFIER_LENGTH} characters.` - ) - } - return normalized -} - -function isBoundedAdmissionIdentifier(value: unknown): value is string { - return ( - typeof value === 'string' && - value.length > 0 && - value.length <= MAX_AGENT_INVOCATION_IDENTIFIER_LENGTH - ) -} - -function normalizeOwnerLimit(requested: number | undefined, capacity: number): number { - if (requested === undefined) { - return capacity - } - if (!Number.isSafeInteger(requested) || requested < 1) { - throw new Error('Agent invocation owner limit must be a positive safe integer.') - } - return Math.min(requested, capacity) -} +export * from '@shared/harness/admission' +export { RutisAgentInvocationAdmission } from '../harness/admission' diff --git a/src/main/agent/settings.ts b/src/main/agent/settings.ts index c9efd0062a..c92b3992ba 100644 --- a/src/main/agent/settings.ts +++ b/src/main/agent/settings.ts @@ -22,7 +22,7 @@ import { normalizeDeepChatSubagentConfig } from '@shared/lib/deepchatSubagents' import { resolveAcpAgentAlias } from '@shared/utils/acpAgentAlias' import type { SettingsStore } from '@/config/settingsStore' import { AcpCatalogSettings } from '@/agent/acp/catalog/settings' -import { AcpRegistryService } from '@/agent/acp/catalog/acpRegistryService' +import type { AcpRegistryService } from '@/agent/acp/catalog/acpRegistryService' import { AcpLaunchSpecService } from '@/agent/acp/launch/acpLaunchSpecService' import { normalizeAutoCompactionRetainRecentPairs, @@ -172,15 +172,24 @@ const getLiveLegacyModelSelection = (value: unknown): ModelSelection | null => { } export class AgentSettings implements AgentSettingsPort { - private readonly registry: AcpRegistryService private readonly launchSpecs: AcpLaunchSpecService private deleteDeepChatAgentTasks?: Map> + private registryRefreshPromise: Promise | null = null + private acpWork?: Map }> + private acpInstalls?: Map< + string, + { + repair: boolean + promise: Promise + } + > + private acpUninstalls?: Map> constructor( private readonly settings: SettingsStore, private readonly repository: AgentRepository, private readonly acpCatalog: AcpCatalogSettings, - isPrivacyModeEnabled: () => boolean, + private readonly registry: AcpRegistryService, userDataPath: string, private readonly provider: AgentSettingsProviderPort, private readonly events: AgentSettingsEvents, @@ -190,15 +199,14 @@ export class AgentSettings implements AgentSettingsPort { private readonly cleanupDeletedAgentSkills: (agentId: string) => Promise, private readonly notifyMemoryConfigChanged: (agentId: string) => void, private readonly skillMigrationSettings: AgentSkillMigrationSettingsPort, - private readonly agentLifecycle: AgentLifecycleGatePort + private readonly agentLifecycle: AgentLifecycleGatePort, + private readonly assertAcpOperationAllowed: () => void ) { - this.registry = new AcpRegistryService({ - isPrivacyModeEnabled - }) this.launchSpecs = new AcpLaunchSpecService(path.join(userDataPath, 'acp-registry')) } start(): void { + this.registry.initialize() this.initializeUnifiedAgents() const needsIndependentConfigMigration = (this.settings.get('unifiedAgentsMigrationVersion') ?? 0) < @@ -219,21 +227,59 @@ export class AgentSettings implements AgentSettingsPort { } } this.provider.setAcpProviderEnabled(this.acpCatalog.getGlobalEnabled()) - const previousAgents = this.registry.listAgents() - void this.registry - .initialize() - .then(async () => { - const registryAgents = this.registry.listAgents() - this.syncRegistryAgentsToRepository() - const changedAgentIds = findChangedAcpRegistryAgentIds(previousAgents, registryAgents) - if (changedAgentIds.length > 0) { - await this.provider.refreshAcpProviderAgents(changedAgentIds) - } - this.notifyAcpAgentsChanged() + } + + stop(): void { + this.registry.stop() + this.cancelAcpWork() + this.launchSpecs.stop() + } + + async drain(): Promise { + await Promise.allSettled([this.registryRefreshPromise]) + while (this.acpWork?.size || this.acpUninstalls?.size) { + await Promise.allSettled([ + ...[...(this.acpWork?.values() ?? [])].map((job) => job.promise), + ...(this.acpUninstalls?.values() ?? []) + ]) + } + await this.launchSpecs.drain() + await this.registry.drain() + } + + async cancelAndDrainAcpWork(): Promise { + this.cancelAcpWork() + await this.drain() + } + + private cancelAcpWork(agentId?: string): void { + for (const [controller, job] of this.acpWork ?? []) { + if (!agentId || job.agentId === agentId) controller.abort() + } + this.launchSpecs.cancelPendingInstallations(agentId) + } + + private runAcpWork( + run: (signal: AbortSignal, assertCurrent: () => void) => Promise, + agentId?: string, + controller = new AbortController() + ): Promise { + const jobs = (this.acpWork ??= new Map()) + const assertCurrent = () => { + controller.signal.throwIfAborted() + if (this.registry.isStopped) throw new DOMException('ACP owner is stopping', 'AbortError') + this.assertAcpOperationAllowed() + } + const promise = Promise.resolve() + .then(() => { + assertCurrent() + return run(controller.signal, assertCurrent) }) - .catch((error) => { - console.error('[ACP] Failed to initialize registry service:', error) + .finally(() => { + jobs.delete(controller) }) + jobs.set(controller, { agentId, promise }) + return promise } async getAcpEnabled(): Promise { @@ -299,15 +345,46 @@ export class AgentSettings implements AgentSettingsPort { }) } - async refreshAcpRegistry(force = true): Promise { + async refreshAcpRegistry( + force = true, + options: { automatic?: boolean; signal?: AbortSignal } = {} + ): Promise { + if (this.registry.isStopped) throw new DOMException('ACP registry is stopping', 'AbortError') + options.signal?.throwIfAborted() + if (this.registryRefreshPromise) return this.registryRefreshPromise + this.registryRefreshPromise = this.refreshRegistry(force, options).finally(() => { + this.registryRefreshPromise = null + }) + return this.registryRefreshPromise + } + + private async refreshRegistry( + force: boolean, + options: { automatic?: boolean; signal?: AbortSignal } + ): Promise { + const assertCurrent = () => { + if (this.registry.isStopped) throw new DOMException('ACP registry is stopping', 'AbortError') + options.signal?.throwIfAborted() + } + assertCurrent() const previousAgents = this.registry.listAgents() - const refreshedAgents = await this.registry.refresh(force) + const refreshedAgents = await this.registry.refresh(force, options) + assertCurrent() this.syncRegistryAgentsToRepository() const changedAgentIds = findChangedAcpRegistryAgentIds(previousAgents, refreshedAgents) + for (const agentId of changedAgentIds) this.cancelAcpWork(agentId) + await Promise.allSettled( + [...(this.acpWork?.values() ?? [])] + .filter((job) => job.agentId && changedAgentIds.includes(job.agentId)) + .map((job) => job.promise) + ) + assertCurrent() if (changedAgentIds.length > 0) { await this.provider.refreshAcpProviderAgents(changedAgentIds) } + assertCurrent() const agents = await this.listAcpRegistryAgents() + assertCurrent() this.notifyAcpAgentsChanged() return agents } @@ -351,28 +428,48 @@ export class AgentSettings implements AgentSettingsPort { async uninstallAcpRegistryAgent(agentId: string): Promise { const registryAgent = this.getRegistryAgentOrThrow(agentId) + const uninstalls = (this.acpUninstalls ??= new Map()) + const existing = uninstalls.get(registryAgent.id) + if (existing) return existing if (this.repository.hasAgentSessions(registryAgent.id)) { throw new Error( 'ACP registry agent still has related conversations. Move or delete them first.' ) } - const currentState = this.repository.getAgentInstallState(registryAgent.id) - await this.launchSpecs.uninstallRegistryAgent(registryAgent, currentState) - const updated = this.repository.clearRegistryAcpAgentInstallation(registryAgent.id, { - status: 'not_installed', - version: registryAgent.version, - distributionType: this.launchSpecs.selectRegistryDistribution(registryAgent)?.type, - lastCheckedAt: Date.now(), - installedAt: null, - installDir: null, - error: null + const promise = this.uninstallRegistryAgent(registryAgent).finally(() => { + if (uninstalls.get(registryAgent.id) === promise) uninstalls.delete(registryAgent.id) }) - if (!updated) { - throw new Error( - `ACP registry agent not found or still has related conversations: ${registryAgent.id}` - ) - } - this.handleAcpAgentsMutated([registryAgent.id]) + uninstalls.set(registryAgent.id, promise) + return promise + } + + private async uninstallRegistryAgent(registryAgent: AcpRegistryAgent): Promise { + this.cancelAcpWork(registryAgent.id) + await Promise.allSettled( + [...(this.acpWork?.values() ?? [])] + .filter((job) => job.agentId === registryAgent.id) + .map((job) => job.promise) + ) + await this.runAcpWork(async (_signal, assertCurrent) => { + const currentState = this.repository.getAgentInstallState(registryAgent.id) + await this.launchSpecs.uninstallRegistryAgent(registryAgent, currentState) + assertCurrent() + const updated = this.repository.clearRegistryAcpAgentInstallation(registryAgent.id, { + status: 'not_installed', + version: registryAgent.version, + distributionType: this.launchSpecs.selectRegistryDistribution(registryAgent)?.type, + lastCheckedAt: Date.now(), + installedAt: null, + installDir: null, + error: null + }) + if (!updated) { + throw new Error( + `ACP registry agent not found or still has related conversations: ${registryAgent.id}` + ) + } + this.handleAcpAgentsMutated([registryAgent.id]) + }, registryAgent.id) } async listManualAcpAgents(): Promise { @@ -419,23 +516,32 @@ export class AgentSettings implements AgentSettingsPort { } async resolveAcpLaunchSpec(agentId: string, _workdir?: string): Promise { - const resolvedId = resolveAcpAgentAlias(agentId) - const manualAgent = this.repository.getManualAcpAgent(resolvedId) - if (manualAgent) return this.launchSpecs.resolveManualLaunchSpec(manualAgent) - - const registryAgent = this.getRegistryAgentOrThrow(resolvedId) - const installState = this.repository.getAgentInstallState(registryAgent.id) - const launchSpec = await this.launchSpecs.resolveRegistryLaunchSpec(registryAgent, installState) - this.repository.setAgentInstallState(resolvedId, { - status: 'installed', - distributionType: launchSpec.distributionType, - version: launchSpec.version, - lastCheckedAt: Date.now(), - installedAt: installState?.installedAt ?? Date.now(), - installDir: launchSpec.installDir ?? null, - error: null - }) - return launchSpec + return this.runAcpWork(async (signal, assertCurrent) => { + const resolvedId = resolveAcpAgentAlias(agentId) + if (this.acpUninstalls?.has(resolvedId)) throw new Error('ACP uninstall is in progress') + const manualAgent = this.repository.getManualAcpAgent(resolvedId) + if (manualAgent) return this.launchSpecs.resolveManualLaunchSpec(manualAgent) + + const registryAgent = this.getRegistryAgentOrThrow(resolvedId) + const installState = this.repository.getAgentInstallState(registryAgent.id) + const launchSpec = await this.launchSpecs.resolveRegistryLaunchSpec( + registryAgent, + installState, + signal + ) + assertCurrent() + this.assertRegistryDescriptor(registryAgent) + this.repository.setAgentInstallState(resolvedId, { + status: 'installed', + distributionType: launchSpec.distributionType, + version: launchSpec.version, + lastCheckedAt: Date.now(), + installedAt: installState?.installedAt ?? Date.now(), + installDir: launchSpec.installDir ?? null, + error: null + }) + return launchSpec + }, resolveAcpAgentAlias(agentId)) } async getAcpSharedMcpSelections(): Promise { @@ -664,16 +770,57 @@ export class AgentSettings implements AgentSettingsPort { this.events.publishSessionsUpdated() } - private async installRegistryAgent( + private installRegistryAgent(agentId: string, repair: boolean): Promise { + const resolvedId = resolveAcpAgentAlias(agentId) + if (this.acpUninstalls?.has(resolvedId)) { + return Promise.reject(new Error('ACP uninstall is in progress')) + } + const installs = (this.acpInstalls ??= new Map()) + const existing = installs.get(resolvedId) + if (existing) { + if (!repair || existing.repair) return existing.promise + return this.runAcpWork(async (_signal, assertCurrent) => { + await existing.promise.catch(() => undefined) + assertCurrent() + return this.installRegistryAgent(resolvedId, true) + }, resolvedId) + } + const controller = new AbortController() + const promise = this.runAcpWork( + (signal, assertCurrent) => + this.performRegistryInstall(resolvedId, repair, signal, assertCurrent), + resolvedId, + controller + ).finally(() => { + if (installs.get(resolvedId)?.promise === promise) installs.delete(resolvedId) + }) + installs.set(resolvedId, { repair, promise }) + return promise + } + + private assertRegistryDescriptor(agent: AcpRegistryAgent): void { + const current = this.registry.getAgent(agent.id) + if ( + !current || + current.version !== agent.version || + !isDeepStrictEqual(current.distribution, agent.distribution) + ) { + throw new DOMException('ACP registry descriptor changed', 'AbortError') + } + } + + private async performRegistryInstall( agentId: string, - repair: boolean + repair: boolean, + signal: AbortSignal, + assertCurrent: () => void ): Promise { const registryAgent = this.getRegistryAgentOrThrow(agentId) const currentState = this.repository.getAgentInstallState(registryAgent.id) const installingState: AcpAgentInstallState = { status: 'installing', version: registryAgent.version, - distributionType: this.launchSpecs.selectRegistryDistribution(registryAgent)?.type, + distributionType: this.launchSpecs.selectRegistryDistribution(registryAgent)?.type ?? null, lastCheckedAt: Date.now(), installedAt: currentState?.installedAt ?? null, installDir: currentState?.installDir ?? null, @@ -681,18 +828,30 @@ export class AgentSettings implements AgentSettingsPort { } this.repository.setAgentInstallState(registryAgent.id, installingState) this.notifyAcpAgentsChanged([registryAgent.id]) - if (repair) await this.provider.refreshAcpProviderAgents([registryAgent.id]) - try { + if (repair) await this.provider.refreshAcpProviderAgents([registryAgent.id]) + assertCurrent() + this.assertRegistryDescriptor(registryAgent) const installedState = await this.launchSpecs.ensureRegistryAgentInstalled( registryAgent, currentState, - repair ? { repair: true } : undefined + { repair, signal } ) + assertCurrent() + this.assertRegistryDescriptor(registryAgent) this.repository.setAgentInstallState(registryAgent.id, installedState) this.handleAcpAgentsMutated([registryAgent.id]) return installedState } catch (error) { + if (signal.aborted || (error instanceof Error && error.name === 'AbortError')) { + if ( + isDeepStrictEqual(this.repository.getAgentInstallState(registryAgent.id), installingState) + ) { + this.repository.setAgentInstallState(registryAgent.id, currentState) + } + throw error + } + assertCurrent() const failedState: AcpAgentInstallState = { status: 'error', version: registryAgent.version, @@ -902,7 +1061,13 @@ export class AgentSettings implements AgentSettingsPort { private handleAcpAgentsMutated(agentIds?: string[]): void { this.provider.clearAcpProviderModelStatus() this.notifyAcpAgentsChanged(agentIds) - void this.provider.refreshAcpProviderAgents(agentIds).catch((error) => { + void this.runAcpWork( + async (_signal, assertCurrent) => { + await this.provider.refreshAcpProviderAgents(agentIds) + assertCurrent() + }, + agentIds?.length === 1 ? agentIds[0] : undefined + ).catch((error) => { console.warn('[ACP] Failed to refresh agent processes after config change:', error) }) } diff --git a/src/main/agent/shared/process/backgroundExecSessionManager.ts b/src/main/agent/shared/process/backgroundExecSessionManager.ts index 439b58443e..0bb3304742 100644 --- a/src/main/agent/shared/process/backgroundExecSessionManager.ts +++ b/src/main/agent/shared/process/backgroundExecSessionManager.ts @@ -1,8 +1,6 @@ import { spawn, type ChildProcess } from 'child_process' import fs from 'fs' import path from 'path' -import { fileURLToPath } from 'url' -import type { UtilityProcess } from 'electron' import { nanoid } from 'nanoid' import { z } from 'zod' import logger from './backgroundExecLogger' @@ -13,7 +11,7 @@ import { prepareShellCommandForUtf8Output } from './shellOutputEncoding' import { describeSpawnFailure, resolveUsableSpawnCwd } from './spawnGuard' -import { terminateProcessTree } from './processTree' +import { terminateProcessTree, waitForClose } from './processTree' import { childProcessRegistry } from './childProcessRegistry' import { resolveSessionDir } from '@/agent/shared/storage/sessionPaths' import { @@ -103,6 +101,10 @@ interface BackgroundSession { status: 'running' | 'done' | 'error' | 'killed' exitCode?: number errorMessage?: string + retirementError?: Error + terminationPromise?: Promise + launchEnvironment?: Record + nativeIdentity?: string createdAt: number lastAccessedAt: number outputBuffer: string @@ -222,6 +224,7 @@ function isMissingUtilitySessionError( export class BackgroundExecSessionManager { private sessions = new Map>() private cleanupIntervalId?: NodeJS.Timeout + private stopped = false constructor() { this.startCleanupTimer() @@ -243,8 +246,10 @@ export class BackgroundExecSessionManager { } } + let session: BackgroundSession try { - return this.startValidated(conversationId, command, cwd, options, ownedTree) + if (this.stopped) throw new Error('Background exec owner is stopping') + session = this.startValidated(conversationId, command, cwd, options, ownedTree) } catch (error) { if (ownedTree) { await cleanupOwnedSkillExecutionPackageTree(ownedTree).catch((cleanupError) => { @@ -253,6 +258,65 @@ export class BackgroundExecSessionManager { } throw error } + + try { + const child = session.child + const gate = child.stdio?.[3] + if (typeof child.pid === 'number') { + const registration = await childProcessRegistry.record( + { + subsystem: 'background-exec', + recordId: session.sessionId, + pid: child.pid, + commandLine: gate + ? child.spawnargs.slice(2) + : [session.shell, ...child.spawnargs.slice(1)], + cwd: session.cwd + }, + child + ) + if (typeof registration !== 'string') session.nativeIdentity = registration.nativeIdentity + // OS observation may precede Node's exit/close callbacks for a fast command. + if (registration === 'exited') await session.closePromise + if (registration === 'failed' && child.exitCode === null && child.signalCode === null) { + throw new Error('Background execution launch could not be registered') + } + } + if (this.stopped) throw new Error('Background exec owner is stopping') + if (gate) { + if (session.status !== 'running') throw new Error('Background execution launch stopped') + if (!session.nativeIdentity) + throw new Error('Background execution launch has no native identity') + if (!('write' in gate) || !('end' in gate) || !session.launchEnvironment) + throw new Error('Invalid execution gate') + const data = Buffer.from(JSON.stringify(session.launchEnvironment)) + if (data.length > 2 * 1024 * 1024) + throw new Error('Execution environment exceeds launch limit') + const header = Buffer.alloc(4) + header.writeUInt32BE(data.length) + await new Promise((resolve, reject) => { + gate.write(Buffer.concat([header, data]), (error?: Error | null) => { + if (error) reject(error) + else { + gate.end() + resolve() + } + }) + }) + } + if (this.stopped) throw new Error('Background exec owner is stopping') + logger.info( + `[BackgroundExec] Started session ${session.sessionId} for conversation ${conversationId}` + ) + return { sessionId: session.sessionId, status: 'running' } + } catch (error) { + await this.killInternal(session, 'startup-failed') + if (this.sessions.get(conversationId)?.get(session.sessionId) === session) + await this.remove(conversationId, session.sessionId) + throw error + } finally { + session.launchEnvironment = undefined + } } private startValidated( @@ -261,7 +325,7 @@ export class BackgroundExecSessionManager { cwd: string, options: BackgroundExecStartOptions, ownedSkillExecutionPackageTree?: OwnedSkillExecutionPackageTree - ): StartSessionResult { + ): BackgroundSession { const config = getConfig() const sessionId = `bg_${nanoid(12)}` const commandShell = ResolvedCommandShellSchema.parse(options.commandShell) @@ -282,7 +346,15 @@ export class BackgroundExecSessionManager { const args = directInvocation ? directInvocation.args : [...commandShell.args, prepareShellCommandForUtf8Output(commandShell.dialect, command)] - const preparedEnv = prepareProcessEnvForUtf8Output(options.env ?? {}) + // Do not resurrect deleted Core variables from this reusable utility's environment. + const preparedEnv = prepareProcessEnvForUtf8Output( + options.env ?? + Object.fromEntries( + Object.entries(process.env).filter( + (entry): entry is [string, string] => typeof entry[1] === 'string' + ) + ) + ) const spawnCwd = resolveUsableSpawnCwd(cwd) const sessionDir = resolveSessionDir(conversationId) @@ -294,12 +366,20 @@ export class BackgroundExecSessionManager { ? this.createOutputFilePath(sessionDir, sessionId, options?.outputPrefix) : null - const child = spawn(executable, args, { - cwd: spawnCwd, - env: { ...process.env, ...preparedEnv }, - detached: process.platform !== 'win32', - windowsHide: true, - stdio: ['pipe', 'pipe', 'pipe'] + const gateExecutable = process.platform === 'darwin' && process.env.DEEPCHAT_HARNESS_EXECUTABLE + const child = spawn( + gateExecutable || executable, + gateExecutable ? ['--exec-gate', executable, ...args] : args, + { + cwd: spawnCwd, + env: gateExecutable ? { ...process.env } : preparedEnv, + detached: process.platform !== 'win32', + windowsHide: true, + stdio: gateExecutable ? ['pipe', 'pipe', 'pipe', 'pipe'] : ['pipe', 'pipe', 'pipe'] + } + ) + child.stdio?.[3]?.on('error', (error) => { + logger.warn('[BackgroundExec] Launch gate closed:', error) }) let resolveClose = () => {} @@ -343,6 +423,7 @@ export class BackgroundExecSessionManager { closeSettled: false, registryRecordCleared: false, timedOut: false, + ...(gateExecutable ? { launchEnvironment: preparedEnv } : {}), ...(ownedSkillExecutionPackageTree ? { ownedSkillExecutionPackageTree } : {}) } @@ -352,7 +433,9 @@ export class BackgroundExecSessionManager { const timeout = options?.timeout ?? config.timeoutSec * 1000 if (timeout > 0) { session.killTimeoutId = setTimeout(() => { - void this.killInternal(session, 'timeout') + void this.killInternal(session, 'timeout').catch((error) => { + logger.warn('[BackgroundExec] Timed out process did not retire:', error) + }) }, timeout) } @@ -361,19 +444,7 @@ export class BackgroundExecSessionManager { } this.sessions.get(conversationId)!.set(sessionId, session) - if (typeof child.pid === 'number') { - childProcessRegistry.record({ - subsystem: 'background-exec', - recordId: sessionId, - pid: child.pid, - commandLine: [executable, ...args], - cwd: spawnCwd - }) - } - - logger.info(`[BackgroundExec] Started session ${sessionId} for conversation ${conversationId}`) - - return { sessionId, status: 'running' } + return session } list(conversationId: string): SessionMeta[] { @@ -513,6 +584,7 @@ export class BackgroundExecSessionManager { const session = this.getSession(conversationId, sessionId) session.lastAccessedAt = Date.now() await session.closePromise + if (session.retirementError) throw session.retirementError return this.buildCompletionResult(session, previewChars) } @@ -548,7 +620,11 @@ export class BackgroundExecSessionManager { beforeMutation?: () => void ): Promise { const session = this.getSession(conversationId, sessionId) - if (session.status !== 'running') return + if (session.retirementError) throw session.retirementError + if (session.status !== 'running') { + await session.terminationPromise + return + } beforeMutation?.() await this.killInternal(session, 'user') } @@ -588,6 +664,7 @@ export class BackgroundExecSessionManager { } else { await session.closePromise } + if (session.retirementError) throw session.retirementError await session.outputWriteQueue.catch((error) => { logger.warn('[BackgroundExec] Failed while draining output write queue:', error) @@ -625,6 +702,7 @@ export class BackgroundExecSessionManager { } async shutdown(): Promise { + this.stopped = true if (this.cleanupIntervalId) { clearInterval(this.cleanupIntervalId) } @@ -636,11 +714,13 @@ export class BackgroundExecSessionManager { } } - await Promise.all( - allSessions.map(({ conversationId, sessionId }) => - this.remove(conversationId, sessionId).catch(() => {}) - ) + const results = await Promise.allSettled( + allSessions.map(({ conversationId, sessionId }) => this.remove(conversationId, sessionId)) ) + const errors = results + .filter((result) => result.status === 'rejected') + .map((result) => result.reason) + if (errors.length) throw new AggregateError(errors, 'Background execution did not retire') } private getSession(conversationId: string, sessionId: string): BackgroundSession { @@ -695,7 +775,9 @@ export class BackgroundExecSessionManager { session, `\n[Process terminated: output exceeded ${session.maxOutputBytes} bytes.]\n` ) - void this.killInternal(session, 'output-limit') + void this.killInternal(session, 'output-limit').catch((error) => { + logger.warn('[BackgroundExec] Output-limited process did not retire:', error) + }) return } session.totalOutputBytes += dataBytes @@ -720,6 +802,15 @@ export class BackgroundExecSessionManager { } private setupProcessLifecycle(session: BackgroundSession): void { + const retireNativeExit = (code: number | null): boolean => { + if (!session.nativeIdentity) return false + const retirement = this.killInternal(session, 'exit') + session.exitCode = code ?? undefined + void retirement.catch((error) => { + logger.warn('[BackgroundExec] Native process group did not retire after root exit:', error) + }) + return true + } session.child.on('error', (error) => { if (session.status === 'running') { session.status = 'error' @@ -736,8 +827,13 @@ export class BackgroundExecSessionManager { shell: session.shell }) queueMicrotask(() => { + if (session.terminationPromise) return if (!session.closeSettled && session.exitCode === undefined) { - void this.finalizeSession(session, null, null) + if (session.nativeIdentity) { + void this.killInternal(session, 'error').catch((error) => { + logger.warn('[BackgroundExec] Failed process group did not retire:', error) + }) + } else void this.finalizeSession(session, null, null) } }) }) @@ -747,10 +843,18 @@ export class BackgroundExecSessionManager { clearTimeout(session.killTimeoutId) session.killTimeoutId = undefined } - if (session.closeSettled || session.finalizationPromise || session.closeWatchdogId) return + if ( + session.terminationPromise || + session.closeSettled || + session.finalizationPromise || + session.closeWatchdogId + ) + return + if (retireNativeExit(code)) return session.closeWatchdogId = setTimeout(() => { session.closeWatchdogId = undefined - if (session.closeSettled || session.finalizationPromise) return + if (session.terminationPromise || session.closeSettled || session.finalizationPromise) + return logger.warn( `[BackgroundExec] Session ${session.sessionId} exited without closing inherited stdio; forcing finalization` ) @@ -772,8 +876,13 @@ export class BackgroundExecSessionManager { clearTimeout(session.closeWatchdogId) session.closeWatchdogId = undefined } - this.clearRegistryRecord(session) + if (session.terminationPromise) { + if (session.nativeIdentity) session.exitCode = code ?? undefined + else this.recordProcessTerminalState(session, code, signal) + return + } if (session.closeSettled || session.finalizationPromise) return + if (retireNativeExit(code)) return this.recordProcessTerminalState(session, code, signal) void this.finalizeSession(session, code, signal) }) @@ -785,7 +894,9 @@ export class BackgroundExecSessionManager { signal: NodeJS.Signals | null ): void { if (session.status !== 'killed') { - if (signal === 'SIGTERM' || signal === 'SIGKILL') { + if (session.retirementError) { + session.status = 'error' + } else if (signal === 'SIGTERM' || signal === 'SIGKILL') { session.status = 'killed' } else if (code !== 0 && code !== null) { session.status = 'error' @@ -796,8 +907,11 @@ export class BackgroundExecSessionManager { session.exitCode = code ?? undefined } - private async killInternal(session: BackgroundSession, reason: string): Promise { - if (session.status !== 'running') return + private killInternal(session: BackgroundSession, reason: string): Promise { + if (session.terminationPromise) return session.terminationPromise + if (session.retirementError) return Promise.reject(session.retirementError) + if (session.closeSettled || (session.status !== 'running' && !session.nativeIdentity)) + return Promise.resolve() logger.info(`[BackgroundExec] Killing session ${session.sessionId} (reason: ${reason})`) @@ -808,31 +922,50 @@ export class BackgroundExecSessionManager { if (reason === 'timeout') { session.timedOut = true } - session.status = 'killed' + if (reason !== 'exit' && session.status === 'running') session.status = 'killed' - let closed = false - try { - closed = await terminateProcessTree(session.child, { graceMs: 2000 }) - } catch (error) { - logger.warn('[BackgroundExec] Failed to terminate process tree:', error) - } - if (!closed && !session.closeSettled) { - if (session.ownedSkillExecutionPackageTree) { - logger.warn( - '[BackgroundExec] Retaining owned Skill package tree for an unclosed process tree:', - { rootPath: session.ownedSkillExecutionPackageTree.rootPath } + session.terminationPromise = Promise.resolve().then(async () => { + let closed = false + try { + const gate = session.child.stdio?.[3] + if (gate && !session.nativeIdentity) { + gate.destroy() + closed = await waitForClose(session.child, 2500) + } else { + closed = await terminateProcessTree(session.child, { + graceMs: 2000, + nativeIdentity: session.nativeIdentity + }) + } + } catch (error) { + logger.warn('[BackgroundExec] Failed to terminate process tree:', error) + } + if (!closed) { + if (reason === 'exit') session.status = 'error' + session.retirementError = new Error( + `Background execution ${session.sessionId} did not retire` ) - session.ownedSkillExecutionPackageTree = undefined + if (session.ownedSkillExecutionPackageTree) { + logger.warn( + '[BackgroundExec] Retaining owned Skill package tree for an unclosed process tree:', + { rootPath: session.ownedSkillExecutionPackageTree.rootPath } + ) + session.ownedSkillExecutionPackageTree = undefined + } + session.child.stdout?.destroy() + session.child.stderr?.destroy() + session.child.stdin?.destroy() + session.child.unref() + session.exitCode = undefined + } else if (reason === 'exit') { + this.recordProcessTerminalState(session, session.exitCode ?? null, session.child.signalCode) } - session.child.stdout?.destroy() - session.child.stderr?.destroy() - session.child.stdin?.destroy() - session.child.unref() - session.exitCode = undefined - await this.finalizeSession(session, null, 'SIGKILL', { clearRegistryRecord: false }) - } - - await session.closePromise + await this.finalizeSession(session, session.exitCode ?? null, session.child.signalCode, { + clearRegistryRecord: closed + }) + if (session.retirementError) throw session.retirementError + }) + return session.terminationPromise } private getRecentOutput(buffer: string, maxChars: number): string { @@ -1007,6 +1140,7 @@ export class BackgroundExecSessionManager { } await session.closePromise + if (session.retirementError) throw session.retirementError } private async finalizeSession( @@ -1185,11 +1319,38 @@ export class BackgroundExecSessionManager { } } -class BackgroundExecUtilityProxy { - private host: UtilityProcess | null = null - private hostReady: Promise | null = null +export interface BackgroundExecHost { + readonly pid: number | undefined + postMessage(message: BackgroundExecRpcRequest): void + kill(): Promise + onMessage(listener: (message: unknown) => void): void + onExit(listener: (code: number) => void): void + onError(listener: (type: string, location: string) => void): void +} + +export type BackgroundExecPort = Pick< + BackgroundExecService, + | 'start' + | 'list' + | 'poll' + | 'log' + | 'waitForCompletionOrYield' + | 'getCompletionResult' + | 'write' + | 'kill' + | 'clear' + | 'remove' + | 'cleanupConversation' +> + +export class BackgroundExecService { + private host: BackgroundExecHost | null = null + private hostReady: Promise | null = null private requestId = 0 - private shuttingDown = false + private readonly lifetime = new AbortController() + private shutdownPromise: Promise | null = null + private retirement: Promise | null = null + private readonly operations = new Set>() private readonly pendingRequests = new Map< string, { @@ -1202,7 +1363,35 @@ class BackgroundExecUtilityProxy { private readonly crashedSessions = new Map() private completedSessionReconciliationTimer: NodeJS.Timeout | null = null - async start( + constructor(private readonly spawnHost: (signal: AbortSignal) => Promise) {} + + private assertActive(): void { + this.lifetime.signal.throwIfAborted() + } + + private trackOperation(operation: () => Promise): Promise { + const promise = Promise.resolve() + .then(operation) + .finally(() => { + this.operations.delete(promise) + }) + this.operations.add(promise) + return promise + } + + start( + conversationId: string, + command: string, + cwd: string, + options: BackgroundExecStartOptions, + beforeStart?: () => Promise + ): Promise { + return this.trackOperation(() => + this.startSession(conversationId, command, cwd, options, beforeStart) + ) + } + + private async startSession( conversationId: string, command: string, cwd: string, @@ -1214,7 +1403,19 @@ class BackgroundExecUtilityProxy { try { const result = await this.request( 'start', - [conversationId, command, cwd, options], + [ + conversationId, + command, + cwd, + { + ...options, + env: Object.fromEntries( + Object.entries({ ...process.env, ...options.env }).filter( + (entry): entry is [string, string] => typeof entry[1] === 'string' + ) + ) + } + ], { beforePost: beforeStart, onPosted: () => { @@ -1222,6 +1423,7 @@ class BackgroundExecUtilityProxy { } } ) + this.assertActive() this.activeSessions.set(result.sessionId, { conversationId, sessionId: result.sessionId, @@ -1247,6 +1449,7 @@ class BackgroundExecUtilityProxy { } async list(conversationId: string): Promise { + this.assertActive() const completedAtListStart = new Map(this.completedSessions) const active = Array.from(this.activeSessions.values()) .filter((session) => session.conversationId === conversationId) @@ -1264,6 +1467,7 @@ class BackgroundExecUtilityProxy { logger.warn('[BackgroundExecProxy] Failed to list utility sessions:', error) } } + this.assertActive() if (hostListSucceeded) { const hostSessionIds = new Set(hostSessions.map((session) => session.sessionId)) for (const [sessionId, session] of completedAtListStart) { @@ -1296,6 +1500,7 @@ class BackgroundExecUtilityProxy { sessionId: string, previewChars?: number ): Promise { + this.assertActive() const crashed = this.getCrashedSession(conversationId, sessionId) if (crashed) { return this.toCrashedPollResult(crashed) @@ -1315,6 +1520,7 @@ class BackgroundExecUtilityProxy { offset = 0, limit = 1000 ): Promise { + this.assertActive() const crashed = this.getCrashedSession(conversationId, sessionId) if (crashed) { return { @@ -1333,6 +1539,7 @@ class BackgroundExecUtilityProxy { yieldMs = getConfig().backgroundMs, previewChars = FOREGROUND_PREVIEW_CHARS ): Promise { + this.assertActive() const crashed = this.getCrashedCompletionResult(conversationId, sessionId) if (crashed) { return { @@ -1358,6 +1565,7 @@ class BackgroundExecUtilityProxy { sessionId: string, previewChars = FOREGROUND_PREVIEW_CHARS ): Promise { + this.assertActive() const crashed = this.getCrashedCompletionResult(conversationId, sessionId) if (crashed) { return crashed @@ -1379,6 +1587,7 @@ class BackgroundExecUtilityProxy { eof = false, beforeMutation?: () => void ): Promise { + this.assertActive() this.assertTrackedSessionOwner(conversationId, sessionId) if ( this.getCrashedSession(conversationId, sessionId) || @@ -1395,6 +1604,7 @@ class BackgroundExecUtilityProxy { sessionId: string, beforeMutation?: () => void ): Promise { + this.assertActive() this.assertTrackedSessionOwner(conversationId, sessionId) if (this.getCrashedSession(conversationId, sessionId)) return if (this.getCompletedSession(conversationId, sessionId)) return @@ -1407,6 +1617,7 @@ class BackgroundExecUtilityProxy { sessionId: string, beforeMutation?: () => void ): Promise { + this.assertActive() this.assertTrackedSessionOwner(conversationId, sessionId) if (this.getCrashedSession(conversationId, sessionId)) return const completed = this.getCompletedSession(conversationId, sessionId) @@ -1433,6 +1644,7 @@ class BackgroundExecUtilityProxy { sessionId: string, beforeMutation?: () => void ): Promise { + this.assertActive() this.assertTrackedSessionOwner(conversationId, sessionId) if (this.getCrashedSession(conversationId, sessionId)) { beforeMutation?.() @@ -1452,6 +1664,7 @@ class BackgroundExecUtilityProxy { } async cleanupConversation(conversationId: string): Promise { + this.assertActive() try { await this.request('cleanupConversation', [conversationId]) } finally { @@ -1474,49 +1687,68 @@ class BackgroundExecUtilityProxy { } } - async shutdown(): Promise { - this.shuttingDown = true + stop(): void { + this.lifetime.abort(new DOMException('Background exec owner is stopping', 'AbortError')) + this.stopCompletedSessionReconciliation() + } + + shutdown(): Promise { + if (this.shutdownPromise) return this.shutdownPromise + this.stop() + this.shutdownPromise = this.performShutdown() + return this.shutdownPromise + } + + private async performShutdown(): Promise { + await this.hostReady?.catch(() => undefined) + const host = this.host try { - if (this.host) { - await this.request('shutdown', []) - } + if (host) await this.postRequest(host, 'shutdown', []) } finally { - this.stopCompletedSessionReconciliation() - this.host?.kill() - this.host = null - this.hostReady = null - this.rejectPendingRequests(new Error('Background exec utility process shut down.')) - this.activeSessions.clear() - this.completedSessions.clear() - this.crashedSessions.clear() + try { + await host?.kill() + await this.retirement + } finally { + this.host = null + this.rejectPendingRequests(new Error('Background exec utility process shut down.')) + while (this.operations.size) await Promise.allSettled(this.operations) + this.activeSessions.clear() + this.completedSessions.clear() + this.crashedSessions.clear() + } } } - private async request( + private request( method: BackgroundExecRpcMethod, args: unknown[], lifecycle: { beforePost?: () => Promise; onPosted?: () => void } = {} ): Promise { - const host = await this.ensureHost() - await lifecycle.beforePost?.() - const id = `exec_rpc_${++this.requestId}` - - return await new Promise((resolve, reject) => { - this.pendingRequests.set(id, { - resolve: (value) => resolve(value as T), - reject - }) - - const payload: BackgroundExecRpcRequest = { - type: 'background-exec:request', - id, - method, - args - } + return this.trackOperation(async () => { + this.assertActive() + const host = await this.ensureHost() + this.assertActive() + await lifecycle.beforePost?.() + this.assertActive() + if (this.host !== host) throw new Error('Background exec helper changed before dispatch') + const result = await this.postRequest(host, method, args, lifecycle.onPosted) + this.assertActive() + return result + }) + } + private postRequest( + host: BackgroundExecHost, + method: BackgroundExecRpcMethod, + args: unknown[], + onPosted?: () => void + ): Promise { + const id = `exec_rpc_${++this.requestId}` + return new Promise((resolve, reject) => { + this.pendingRequests.set(id, { resolve: (value) => resolve(value as T), reject }) try { - host.postMessage(payload) - lifecycle.onPosted?.() + host.postMessage({ type: 'background-exec:request', id, method, args }) + onPosted?.() } catch (error) { this.pendingRequests.delete(id) reject(error) @@ -1524,84 +1756,46 @@ class BackgroundExecUtilityProxy { }) } - private async ensureHost(): Promise { - if (this.host) { - return this.host - } - if (this.hostReady) { - return await this.hostReady - } - - this.shuttingDown = false - this.hostReady = this.startHost() + private async ensureHost(): Promise { + this.assertActive() + if (this.host) return this.host + if (this.hostReady) return this.hostReady + const spawning = this.startHost() + this.hostReady = spawning try { - return await this.hostReady + return await spawning } finally { - this.hostReady = null + if (this.hostReady === spawning) this.hostReady = null } } - private async startHost(): Promise { - const { app, utilityProcess } = await import('electron') - const modulePath = this.resolveUtilityHostEntryPoint(app.getAppPath()) - const host = utilityProcess.fork(modulePath, ['--deepchat-exec-utility-host'], { - serviceName: 'DeepChat Exec Utility', - stdio: 'ignore', - env: { - ...process.env, - DEEPCHAT_EXEC_UTILITY_HOST: '1' - } + private async startHost(): Promise { + await this.retirement + this.assertActive() + const host = await this.spawnHost(this.lifetime.signal) + if (this.lifetime.signal.aborted) { + await host.kill() + this.assertActive() + } + this.host = host + host.onMessage((message) => { + if (this.host === host) this.handleHostMessage(message) }) - - host.on('message', (message) => this.handleHostMessage(message)) - host.on('exit', (code) => this.handleHostExit(code)) - host.on('error', (type, location) => { - logger.error('[BackgroundExecProxy] Utility process error:', { type, location }) + host.onExit((code) => { + if (this.host !== host) return + this.retirement = host.kill() + void this.retirement.catch((error) => { + this.stop() + logger.error('[BackgroundExecProxy] Helper retirement failed:', error) + }) + this.handleHostExit(code) }) - - return await new Promise((resolve, reject) => { - let settled = false - const settle = (callback: () => void) => { - if (settled) { - return - } - settled = true - host.off('spawn', onSpawn) - host.off('exit', onExit) - callback() - } - const onSpawn = () => { - settle(() => { - this.host = host - this.scheduleCompletedSessionReconciliation() - resolve(host) - }) - } - const onExit = (code: number) => { - settle(() => { - reject(new Error(`Background exec utility process exited before spawn: ${code}`)) - }) - } - - host.once('spawn', onSpawn) - host.once('exit', onExit) + host.onError((type, location) => { + if (this.host === host) + logger.error('[BackgroundExecProxy] Utility process error:', { type, location }) }) - } - - private resolveUtilityHostEntryPoint(appPath?: string): string { - const modulePath = fileURLToPath(import.meta.url) - const candidates = [ - ...(appPath - ? [ - path.join(appPath, 'out/main/backgroundExecUtilityHost.js'), - path.join(appPath, 'backgroundExecUtilityHost.js') - ] - : []), - path.resolve(path.dirname(modulePath), 'backgroundExecUtilityHost.js'), - path.resolve(path.dirname(modulePath), '../backgroundExecUtilityHost.js'), - path.resolve(process.cwd(), 'out/main/backgroundExecUtilityHost.js') - ] - return candidates.find((candidate) => fs.existsSync(candidate)) ?? candidates[0] + this.scheduleCompletedSessionReconciliation() + return host } private handleHostMessage(message: unknown): void { @@ -1630,7 +1824,7 @@ class BackgroundExecUtilityProxy { private handleHostExit(code: number): void { const error = new Error(`Background exec utility process exited with code ${code}.`) - if (!this.shuttingDown) { + if (!this.lifetime.signal.aborted) { for (const session of this.activeSessions.values()) { this.crashedSessions.set(session.sessionId, { ...session, @@ -1692,6 +1886,7 @@ class BackgroundExecUtilityProxy { sessionId: string, result: PollResult | LogResult ): void { + this.assertActive() const session = this.activeSessions.get(sessionId) if (!session || session.conversationId !== conversationId) { return @@ -1708,6 +1903,7 @@ class BackgroundExecUtilityProxy { sessionId: string, result: SessionCompletionResult | PollResult | LogResult ): void { + this.assertActive() const session = this.activeSessions.get(sessionId) if (!session || session.conversationId !== conversationId || result.status === 'running') { return @@ -1730,7 +1926,7 @@ class BackgroundExecUtilityProxy { this.completedSessionReconciliationTimer || this.completedSessions.size === 0 || !this.host || - this.shuttingDown + this.lifetime.signal.aborted ) { return } @@ -1828,5 +2024,3 @@ class BackgroundExecUtilityProxy { return `Background exec utility process exited before session ${session.sessionId} completed. The command may have been terminated: ${session.command}` } } - -export const backgroundExecSessionManager = new BackgroundExecUtilityProxy() diff --git a/src/main/agent/shared/process/childProcessRegistry.ts b/src/main/agent/shared/process/childProcessRegistry.ts index 92bd9afa5a..0840fa18a8 100644 --- a/src/main/agent/shared/process/childProcessRegistry.ts +++ b/src/main/agent/shared/process/childProcessRegistry.ts @@ -1,4 +1,4 @@ -import { spawn } from 'child_process' +import { spawn, type ChildProcess } from 'child_process' import { createHash } from 'crypto' import fs from 'fs' import os from 'os' @@ -20,12 +20,14 @@ export interface ChildProcessLaunchRecord { cwd?: string recordedAt: number startedAtMs?: number + nativeIdentity?: string } export interface ObservedProcessIdentity { alive: boolean commandLine?: string startedAtMs?: number + nativeIdentity?: string } export type ChildProcessAttester = ( @@ -41,6 +43,7 @@ export interface ReapStaleChildProcessesResult { } export interface ReapStaleChildProcessesOptions { + ownerPid?: number attester?: ChildProcessAttester excludeRecordIds?: ReadonlySet maxRecordAgeMs?: number @@ -50,7 +53,7 @@ export interface ChildProcessRegistryOptions { rootDir?: string now?: () => number isAlive?: (pid: number) => boolean - observe?: (pid: number) => Promise + observe?: (pid: number, record?: ChildProcessLaunchRecord) => Promise terminate?: (pid: number) => Promise log?: (message: string, ...args: unknown[]) => void } @@ -155,10 +158,31 @@ async function observeWindows(pid: number): Promise { } } -async function defaultObserve(pid: number): Promise { +function isNativeIdentity(value: unknown): value is string { + return typeof value === 'string' && /^darwin:[\dA-Fa-f-]{36}:[1-9]\d{0,19}$/.test(value) +} + +export async function observeProcessIdentity( + pid: number, + record?: ChildProcessLaunchRecord +): Promise { if (!defaultIsAlive(pid)) { return { alive: false } } + const executable = process.env.DEEPCHAT_HARNESS_EXECUTABLE + if (process.platform === 'darwin' && executable && (!record || record.nativeIdentity)) { + const result = await runAndCapture(executable, ['--process-identity', String(pid)]) + if (result.code === 0) { + try { + const value = JSON.parse(result.stdout) + if (value.pid === pid && isNativeIdentity(value.nativeIdentity)) + return { alive: true, nativeIdentity: value.nativeIdentity } + } catch { + // Missing native identity cannot authorize releasing an execution gate. + } + } + return { alive: defaultIsAlive(pid) } + } return process.platform === 'win32' ? await observeWindows(pid) : await observePosix(pid) } @@ -169,6 +193,7 @@ export function defaultChildProcessAttester( if (!observed.alive) { return false } + if (record.nativeIdentity !== undefined) return record.nativeIdentity === observed.nativeIdentity if ( !Number.isFinite(record.startedAtMs) || !Number.isFinite(observed.startedAtMs) || @@ -197,6 +222,7 @@ function isLaunchRecord(value: unknown): value is ChildProcessLaunchRecord { Number.isSafeInteger(record.ownerPid) && Array.isArray(record.commandLine) && record.commandLine.every((segment) => typeof segment === 'string') && + (record.nativeIdentity === undefined || isNativeIdentity(record.nativeIdentity)) && Number.isFinite(record.recordedAt) ) } @@ -212,10 +238,10 @@ export class ChildProcessRegistry { private resolvedRootDir?: string private readonly now: () => number private readonly isAlive: (pid: number) => boolean - private readonly observe: (pid: number) => Promise - private readonly terminate: (pid: number) => Promise + private readonly observe: NonNullable + private readonly terminate: (record: ChildProcessLaunchRecord) => Promise private readonly log: (message: string, ...args: unknown[]) => void - private readonly reapedSubsystems = new Set() + private readonly onceReaps = new Map | null>() private readonly inflightReaps = new Map>() private readonly pendingRecords = new Map() @@ -223,22 +249,33 @@ export class ChildProcessRegistry { this.configuredRootDir = options.rootDir this.now = options.now ?? Date.now this.isAlive = options.isAlive ?? defaultIsAlive - this.observe = options.observe ?? defaultObserve - this.terminate = - options.terminate ?? ((pid: number) => terminateProcessTreeByPid(pid, { graceMs: 2000 })) + this.observe = options.observe ?? observeProcessIdentity + this.terminate = (record) => + options.terminate + ? options.terminate(record.pid) + : terminateProcessTreeByPid(record.pid, { + graceMs: 2000, + nativeIdentity: record.nativeIdentity + }) this.log = options.log ?? ((message, ...args) => console.warn(message, ...args)) } - async record(entry: { - subsystem: ChildProcessSubsystem - recordId: string - pid: number - commandLine: string[] - cwd?: string - }): Promise { + async record( + entry: { + subsystem: ChildProcessSubsystem + recordId: string + pid: number + commandLine: string[] + cwd?: string + }, + child?: Readonly> + ): Promise { if (!Number.isSafeInteger(entry.pid) || entry.pid <= 0 || !entry.recordId) { - return + return 'failed' } + const isCurrentChild = () => + !child || (child.pid === entry.pid && child.exitCode === null && child.signalCode === null) + if (!isCurrentChild()) return 'failed' const key = `${entry.subsystem}:${entry.recordId}` let record: ChildProcessLaunchRecord | undefined try { @@ -254,20 +291,32 @@ export class ChildProcessRegistry { } this.pendingRecords.set(key, record) this.writeRecord(record) - const observed = await this.observe(entry.pid) + // PID-only callers retain legacy attestation; native identity needs a live owned child. + const observed = await this.observe(entry.pid, child ? undefined : record) + // A reaped child may have released its PID while inspection was pending. + if (!isCurrentChild()) return 'failed' + if (this.pendingRecords.get(key) === record && !observed.alive) { + this.clear(entry.subsystem, entry.recordId) + return 'exited' + } // An exit or a newer launch may have cleared/replaced this record while ps ran. if ( this.pendingRecords.get(key) === record && observed.alive && - typeof observed.startedAtMs === 'number' && - Number.isFinite(observed.startedAtMs) && - observed.startedAtMs <= record.recordedAt + (isNativeIdentity(observed.nativeIdentity) || + (typeof observed.startedAtMs === 'number' && + Number.isFinite(observed.startedAtMs) && + observed.startedAtMs <= record.recordedAt)) ) { record.startedAtMs = observed.startedAtMs + record.nativeIdentity = observed.nativeIdentity this.writeRecord(record) + return observed } + return 'failed' } catch (error) { this.log(`[ChildProcessRegistry] Failed to record launch ${entry.recordId}:`, error) + return 'failed' } finally { if (this.pendingRecords.get(key) === record) this.pendingRecords.delete(key) } @@ -287,12 +336,15 @@ export class ChildProcessRegistry { } } - list(subsystem: ChildProcessSubsystem): ChildProcessLaunchRecord[] { + list(subsystem: ChildProcessSubsystem, ownerPid?: number): ChildProcessLaunchRecord[] { + if (ownerPid !== undefined && (!Number.isSafeInteger(ownerPid) || ownerPid <= 0)) + throw new Error('Invalid child process owner PID') const subsystemDir = path.join(this.rootDir, subsystem) let entries: fs.Dirent[] try { entries = fs.readdirSync(subsystemDir, { withFileTypes: true }) - } catch { + } catch (error) { + if (ownerPid !== undefined && (error as NodeJS.ErrnoException).code !== 'ENOENT') throw error return [] } @@ -305,11 +357,18 @@ export class ChildProcessRegistry { try { const parsed: unknown = JSON.parse(fs.readFileSync(filePath, 'utf-8')) if (isLaunchRecord(parsed)) { + if (ownerPid !== undefined) { + if (parsed.subsystem !== subsystem || parsed.version !== RECORD_VERSION) + throw new Error('Invalid owned child process record') + if (parsed.ownerPid !== ownerPid) continue + } records.push(parsed) } else { + if (ownerPid !== undefined) throw new Error('Invalid owned child process record') fs.unlinkSync(filePath) } - } catch { + } catch (error) { + if (ownerPid !== undefined) throw error try { fs.unlinkSync(filePath) } catch { @@ -324,12 +383,16 @@ export class ChildProcessRegistry { subsystem: ChildProcessSubsystem, options: ReapStaleChildProcessesOptions = {} ): Promise { - const inflight = this.inflightReaps.get(subsystem) - if (inflight) { - return await inflight + const previous = this.inflightReaps.get(subsystem) + const snapshot = { + ...options, + excludeRecordIds: options.excludeRecordIds ? new Set(options.excludeRecordIds) : undefined } - const reapPromise = this.reapStaleRecords(subsystem, options).finally(() => { - this.inflightReaps.delete(subsystem) + const reapPromise = (async () => { + if (previous) await previous.catch(() => undefined) + return await this.reapStaleRecords(subsystem, snapshot) + })().finally(() => { + if (this.inflightReaps.get(subsystem) === reapPromise) this.inflightReaps.delete(subsystem) }) this.inflightReaps.set(subsystem, reapPromise) return await reapPromise @@ -337,15 +400,14 @@ export class ChildProcessRegistry { async reapStaleOnce( subsystem: ChildProcessSubsystem, - options: ReapStaleChildProcessesOptions = {} + options: Omit = {} ): Promise { - const inflight = this.inflightReaps.get(subsystem) - if (inflight) return await inflight - if (this.reapedSubsystems.has(subsystem)) { - return null - } - this.reapedSubsystems.add(subsystem) - return await this.reapStale(subsystem, options) + if (this.onceReaps.has(subsystem)) return await (this.onceReaps.get(subsystem) ?? null) + const recovery = this.reapStale(subsystem, options).finally(() => { + this.onceReaps.set(subsystem, null) + }) + this.onceReaps.set(subsystem, recovery) + return await recovery } private async reapStaleRecords( @@ -361,12 +423,16 @@ export class ChildProcessRegistry { const attester = options.attester ?? defaultChildProcessAttester const maxRecordAgeMs = options.maxRecordAgeMs ?? DEFAULT_MAX_RECORD_AGE_MS - for (const record of this.list(subsystem)) { + for (const record of this.list(subsystem, options.ownerPid)) { if (options.excludeRecordIds?.has(record.recordId)) { result.skipped.push(record.recordId) continue } - if (this.now() - record.recordedAt > maxRecordAgeMs) { + if ( + options.ownerPid === undefined && + record.nativeIdentity === undefined && + this.now() - record.recordedAt > maxRecordAgeMs + ) { this.clear(subsystem, record.recordId) result.cleared.push(record.recordId) continue @@ -375,7 +441,7 @@ export class ChildProcessRegistry { result.refused.push(record.recordId) continue } - if (!this.isAlive(record.pid)) { + if (!this.isAlive(record.pid) && record.nativeIdentity === undefined) { this.clear(subsystem, record.recordId) result.cleared.push(record.recordId) continue @@ -385,20 +451,40 @@ export class ChildProcessRegistry { continue } - const observed = await this.observe(record.pid) - if (!observed.alive) { + const observed = await this.observe(record.pid, record) + if ( + record.nativeIdentity !== undefined && + isNativeIdentity(observed.nativeIdentity) && + record.nativeIdentity.slice(0, 43).toLowerCase() !== + observed.nativeIdentity.slice(0, 43).toLowerCase() + ) { + this.clear(subsystem, record.recordId) + result.cleared.push(record.recordId) + continue + } + const nativeRootGone = !observed.alive && record.nativeIdentity !== undefined + if (!observed.alive && !nativeRootGone) { this.clear(subsystem, record.recordId) result.cleared.push(record.recordId) continue } let attested = false - if (!Number.isFinite(record.startedAtMs) || !Number.isFinite(observed.startedAtMs)) { + if ( + !nativeRootGone && + (record.nativeIdentity !== undefined + ? !isNativeIdentity(observed.nativeIdentity) + : !Number.isFinite(record.startedAtMs) || !Number.isFinite(observed.startedAtMs)) + ) { result.refused.push(record.recordId) continue } try { - attested = record.startedAtMs === observed.startedAtMs && attester(record, observed) + attested = nativeRootGone + ? options.attester === undefined || attester(record, observed) + : (record.nativeIdentity !== undefined + ? record.nativeIdentity === observed.nativeIdentity + : record.startedAtMs === observed.startedAtMs) && attester(record, observed) } catch (error) { this.log(`[ChildProcessRegistry] Attester failed for ${record.recordId}:`, error) } @@ -408,15 +494,14 @@ export class ChildProcessRegistry { this.log( `[ChildProcessRegistry] Refusing unattested cleanup of pid ${record.pid} (${record.recordId})` ) - if (observed.commandLine !== undefined && observed.startedAtMs !== undefined) { - // Identity was fully checked and did not match: the recorded process - // is gone and the pid now belongs to an unrelated process. + // A reused native root PID cannot prove that its escaped descendants have retired. + if (record.nativeIdentity === undefined && record.startedAtMs !== observed.startedAtMs) { this.clear(subsystem, record.recordId) } continue } - const terminated = await this.terminate(record.pid).catch((error) => { + const terminated = await this.terminate(record).catch((error) => { this.log(`[ChildProcessRegistry] Failed to terminate pid ${record.pid}:`, error) return false }) diff --git a/src/main/agent/shared/process/processTree.ts b/src/main/agent/shared/process/processTree.ts index ec79934397..abb7652512 100644 --- a/src/main/agent/shared/process/processTree.ts +++ b/src/main/agent/shared/process/processTree.ts @@ -7,8 +7,13 @@ function hasExited(child: ChildProcess): boolean { return child.exitCode !== null || child.signalCode !== null } -function waitForClose(child: ChildProcess, timeoutMs: number): Promise { - if (hasExited(child)) { +export function waitForClose(child: ChildProcess, timeoutMs: number): Promise { + if ( + hasExited(child) && + (child.stdio ?? [child.stdin, child.stdout, child.stderr]).every( + (stream) => !stream || stream.closed + ) + ) { return Promise.resolve(true) } @@ -117,23 +122,31 @@ async function signalDescendantsRecursively( } } -async function signalProcessTree(pid: number, signal: 'SIGTERM' | 'SIGKILL'): Promise { +async function signalProcessTree( + pid: number, + signal: 'SIGTERM' | 'SIGKILL', + ownedGroup = false +): Promise { if (process.platform === 'win32') { const args = ['/PID', `${pid}`, '/T', '/F'] await spawnAndWait('taskkill', args) - return + return false } const childSignal = signal === 'SIGKILL' ? '-KILL' : '-TERM' try { process.kill(-pid, signal) + return true } catch { + // Never fall back to a potentially reused root PID after acquiring its group. + if (ownedGroup) return true await signalDescendantsRecursively(pid, childSignal) try { process.kill(pid, signal) } catch { // Process may have already exited. } + return false } } @@ -142,7 +155,7 @@ function isProcessAlive(pid: number): boolean { process.kill(pid, 0) return true } catch (error) { - return (error as NodeJS.ErrnoException).code === 'EPERM' + return (error as NodeJS.ErrnoException).code !== 'ESRCH' } } @@ -162,35 +175,62 @@ export async function terminateProcessTreeByPid( pid: number, options: { graceMs?: number + nativeIdentity?: string + ownedGroup?: boolean } = {} ): Promise { if (!Number.isSafeInteger(pid) || pid <= 0) { throw new Error(`Cannot terminate invalid process id: ${pid}`) } + if (options.nativeIdentity !== undefined) { + const executable = process.env.DEEPCHAT_HARNESS_EXECUTABLE + if (process.platform !== 'darwin' || !executable) return false + const output = await spawnAndCapture(executable, [ + options.ownedGroup === false ? '--terminate-owner' : '--terminate-tree', + `${pid}`, + options.nativeIdentity, + `${Math.min(2000, Math.max(0, options.graceMs ?? 2000))}` + ]) + try { + return JSON.parse(output).retired === true + } catch { + return false + } + } + if (options.ownedGroup === false) return false if (!isProcessAlive(pid)) { - return true + return process.platform === 'win32' || !isProcessAlive(-pid) } const graceMs = Math.max(0, options.graceMs ?? 2000) - await signalProcessTree(pid, 'SIGTERM') - if (await waitForPidExit(pid, graceMs)) { + const grouped = await signalProcessTree(pid, 'SIGTERM') + const target = grouped ? -pid : pid + if (await waitForPidExit(target, graceMs)) { return true } - await signalProcessTree(pid, 'SIGKILL') - return await waitForPidExit(pid, FORCE_KILL_SETTLE_MS) + await signalProcessTree(pid, 'SIGKILL', grouped) + return await waitForPidExit(target, FORCE_KILL_SETTLE_MS) } export async function terminateProcessTree( child: ChildProcess, options: { graceMs?: number + nativeIdentity?: string } = {} ): Promise { const graceMs = Math.max(0, options.graceMs ?? 2000) + if (options.nativeIdentity !== undefined) { + if (!child.pid) return false + const retired = await terminateProcessTreeByPid(child.pid, options) + return retired && (await waitForClose(child, FORCE_KILL_SETTLE_MS)) + } + if (hasExited(child)) { - return true + if (process.platform !== 'win32' && child.pid && isProcessAlive(-child.pid)) return false + return await waitForClose(child, graceMs) } const pid = child.pid @@ -203,11 +243,19 @@ export async function terminateProcessTree( return await waitForClose(child, graceMs) } - await signalProcessTree(pid, 'SIGTERM') - if (await waitForClose(child, graceMs)) { + const grouped = await signalProcessTree(pid, 'SIGTERM') + const settled = await Promise.all([ + waitForClose(child, graceMs), + grouped ? waitForPidExit(-pid, graceMs) : Promise.resolve(true) + ]) + if (settled.every(Boolean)) { return true } - await signalProcessTree(pid, 'SIGKILL') - return await waitForClose(child, FORCE_KILL_SETTLE_MS) + await signalProcessTree(pid, 'SIGKILL', grouped) + const forced = await Promise.all([ + waitForClose(child, FORCE_KILL_SETTLE_MS), + grouped ? waitForPidExit(-pid, FORCE_KILL_SETTLE_MS) : Promise.resolve(true) + ]) + return forced.every(Boolean) } diff --git a/src/main/agent/shared/process/shellEnvHelper.ts b/src/main/agent/shared/process/shellEnvHelper.ts index b4907aef84..09af6bd48f 100644 --- a/src/main/agent/shared/process/shellEnvHelper.ts +++ b/src/main/agent/shared/process/shellEnvHelper.ts @@ -33,6 +33,7 @@ const POSIX_SHELL_FALLBACKS: Partial> = { const DEFAULT_POSIX_SHELL_FALLBACKS = ['/bin/sh', '/usr/bin/sh'] let cachedShellEnv: Record | null = null +let loadingShellEnv: Promise> | null = null const TIMEOUT_MS = 8000 @@ -359,6 +360,8 @@ export async function resolveShellBootstrapEnv(): Promise return await new Promise>((resolve, reject) => { const child = spawn(shell, args, { + // An interactive probe must not take its caller's controlling terminal. + detached: true, stdio: ['ignore', 'pipe', 'pipe'], env: { ...process.env, @@ -369,10 +372,11 @@ export async function resolveShellBootstrapEnv(): Promise let stdout = '' let stderr = '' let timeoutId: NodeJS.Timeout | null = null + let terminationError: Error | undefined timeoutId = setTimeout(() => { - child.kill() - reject(new Error(`Shell environment command timed out after ${TIMEOUT_MS}ms`)) + terminationError = new Error(`Shell environment command timed out after ${TIMEOUT_MS}ms`) + child.kill('SIGKILL') }, TIMEOUT_MS) child.stdout?.on('data', (data: Buffer | string) => { @@ -384,16 +388,21 @@ export async function resolveShellBootstrapEnv(): Promise }) child.on('error', (error) => { - if (timeoutId) { - clearTimeout(timeoutId) + terminationError ??= error + if (!child.pid) { + if (timeoutId) clearTimeout(timeoutId) + reject(error) } - reject(error) }) - child.on('exit', (code, signal) => { + child.on('close', (code, signal) => { if (timeoutId) { clearTimeout(timeoutId) } + if (terminationError) { + reject(terminationError) + return + } if (code !== 0 && signal === null) { reject( @@ -438,32 +447,41 @@ export async function getShellEnvironment(): Promise> { logger.info('[ACP] Using cached shell environment variables') return cachedShellEnv } + if (loadingShellEnv) return await loadingShellEnv logger.info('[ACP] Fetching shell environment variables (this may take a moment)...') - try { - const shellEnv = await resolveShellBootstrapEnv() - const normalizedEnv = normalizeShellEnvironment(shellEnv) - - cachedShellEnv = normalizedEnv - - logger.info('[ACP] Shell environment variables fetched and cached:', { - pathLength: normalizedEnv.PATH?.length || normalizedEnv.Path?.length || 0, - hasNvm: !!normalizedEnv.NVM_DIR, - hasFnm: !!normalizedEnv.FNM_DIR, - hasVolta: !!normalizedEnv.VOLTA_HOME, - hasN: !!normalizedEnv.N_PREFIX, - envVarCount: Object.keys(normalizedEnv).length - }) + const operation: Promise> = resolveShellBootstrapEnv() + .then((shellEnv) => { + const normalizedEnv = normalizeShellEnvironment(shellEnv) - return normalizedEnv - } catch (error) { - console.warn('[ACP] Failed to get shell environment variables:', error) - return normalizeShellEnvironment(toStringEnvEntries(process.env), process.env) + if (loadingShellEnv === operation) cachedShellEnv = normalizedEnv + + logger.info('[ACP] Shell environment variables fetched:', { + pathLength: normalizedEnv.PATH?.length || normalizedEnv.Path?.length || 0, + hasNvm: !!normalizedEnv.NVM_DIR, + hasFnm: !!normalizedEnv.FNM_DIR, + hasVolta: !!normalizedEnv.VOLTA_HOME, + hasN: !!normalizedEnv.N_PREFIX, + envVarCount: Object.keys(normalizedEnv).length + }) + + return normalizedEnv + }) + .catch((error) => { + console.warn('[ACP] Failed to get shell environment variables:', error) + return normalizeShellEnvironment(toStringEnvEntries(process.env), process.env) + }) + loadingShellEnv = operation + try { + return await operation + } finally { + if (loadingShellEnv === operation) loadingShellEnv = null } } export function clearShellEnvironmentCache(): void { cachedShellEnv = null + loadingShellEnv = null logger.info('[ACP] Shell environment cache cleared') } diff --git a/src/main/app/backgroundExecHost.ts b/src/main/app/backgroundExecHost.ts new file mode 100644 index 0000000000..28e7139856 --- /dev/null +++ b/src/main/app/backgroundExecHost.ts @@ -0,0 +1,128 @@ +import path from 'node:path' +import { utilityProcess } from 'electron' +import type { BackgroundExecHost } from '@/agent/shared/process/backgroundExecSessionManager' +import { + childProcessRegistry, + observeProcessIdentity +} from '@/agent/shared/process/childProcessRegistry' +import { terminateProcessTreeByPid } from '@/agent/shared/process/processTree' + +export async function spawnBackgroundExecHost( + entry: string, + signal: AbortSignal +): Promise { + if (!path.isAbsolute(entry)) throw new Error('Background exec helper entry must be absolute') + signal.throwIfAborted() + const host = utilityProcess.fork(entry, ['--deepchat-exec-utility-host'], { + serviceName: 'DeepChat Exec Utility', + stdio: 'ignore', + env: { ...process.env, DEEPCHAT_EXEC_UTILITY_HOST: '1' } + }) + const closed = Promise.withResolvers() + let exitCode: number | undefined + let ownerPid: number | undefined + let ownerIdentity: string | undefined + let retirement: Promise | undefined + host.once('exit', (code) => { + exitCode = code + closed.resolve() + }) + const kill = (): Promise => { + retirement ??= Promise.resolve().then(async () => { + try { + if (exitCode === undefined) host.kill() + } finally { + await closed.promise + } + if (ownerPid === undefined) return + if ( + ownerIdentity && + !(await terminateProcessTreeByPid(ownerPid, { + nativeIdentity: ownerIdentity, + ownedGroup: false, + graceMs: 2000 + })) + ) + throw new Error('Background exec helper descendants did not retire') + const recovery = await childProcessRegistry.reapStale('background-exec', { ownerPid }) + if ( + recovery.refused.length || + recovery.skipped.length || + childProcessRegistry.list('background-exec', ownerPid).length + ) + throw new Error('Owned background execution did not retire') + }) + return retirement + } + return new Promise((resolve, reject) => { + let settled = false + const settle = (callback: () => void) => { + if (settled) return + settled = true + host.off('spawn', onSpawn) + host.off('exit', onExit) + host.off('error', onError) + signal.removeEventListener('abort', onAbort) + callback() + } + const fail = (error: unknown) => + settle(() => { + void kill().then(() => reject(error), reject) + }) + const onSpawn = async () => { + if (!Number.isSafeInteger(host.pid) || !host.pid || host.pid <= 0) { + fail(new Error('Background exec helper has no owned PID')) + return + } + ownerPid = host.pid + if (process.platform === 'darwin') { + try { + const observed = await observeProcessIdentity(ownerPid) + if (settled) return + if (!observed.alive || !observed.nativeIdentity || exitCode !== undefined) { + fail(new Error('Background exec helper has no native identity')) + return + } + ownerIdentity = observed.nativeIdentity + } catch (error) { + fail(error) + return + } + } + settle(() => + resolve({ + get pid() { + return ownerPid + }, + postMessage(message) { + host.postMessage(message) + }, + kill, + onMessage(listener) { + host.on('message', listener) + }, + onExit(listener) { + if (exitCode === undefined) host.once('exit', listener) + else { + const code = exitCode + queueMicrotask(() => listener(code)) + } + }, + onError(listener) { + host.on('error', listener) + } + }) + ) + } + const onExit = (code: number) => + fail(new Error(`Background exec helper exited before spawn: ${code}`)) + const onError = (type: string, location: string) => + fail(new Error(`Background exec helper error: ${type} at ${location}`)) + const onAbort = () => fail(signal.reason) + host.once('spawn', onSpawn) + host.once('exit', onExit) + host.once('error', onError) + signal.addEventListener('abort', onAbort, { once: true }) + if (signal.aborted) onAbort() + }) +} diff --git a/src/main/app/codeModeHost.ts b/src/main/app/codeModeHost.ts new file mode 100644 index 0000000000..4c53b20037 --- /dev/null +++ b/src/main/app/codeModeHost.ts @@ -0,0 +1,54 @@ +import path from 'node:path' +import { app, utilityProcess } from 'electron' +import type { CodeModeHost } from '@/tool/codeMode/runCodeRuntimeManager' + +export async function spawnCodeModeHost(entry: string, signal: AbortSignal): Promise { + if (!path.isAbsolute(entry)) throw new Error('Code Mode helper entry must be absolute') + signal.throwIfAborted() + const env: Record = { DEEPCHAT_CODE_MODE_HOST: '1' } + for (const name of ['LANG', 'LC_ALL', 'TMPDIR', 'TEMP', 'TMP', 'SystemRoot']) { + const value = process.env[name] + if (value) env[name] = value + } + const host = utilityProcess.fork(entry, ['--deepchat-code-mode-host'], { + serviceName: 'DeepChat Code Mode Cell', + stdio: 'ignore', + env, + execArgv: ['--max-old-space-size=64'], + allowLoadingUnsignedLibraries: false + }) + if (signal.aborted) { + const closed = new Promise((resolve) => host.once('exit', () => resolve())) + try { + host.kill() + } finally { + await closed + } + signal.throwIfAborted() + } + const events: NodeJS.EventEmitter = host + return { + get pid() { + return host.pid + }, + postMessage(message) { + host.postMessage(message) + }, + kill() { + host.kill() + }, + on(event, listener) { + events.on(event, listener) + }, + off(event, listener) { + events.off(event, listener) + }, + async getRssBytes(requestSignal) { + requestSignal.throwIfAborted() + if (!host.pid) return null + const metric = app.getAppMetrics().find((entry) => entry.pid === host.pid) + requestSignal.throwIfAborted() + return metric ? metric.memory.workingSetSize * 1024 : null + } + } +} diff --git a/src/main/app/composition.ts b/src/main/app/composition.ts index 39c7fffe2b..530290db27 100644 --- a/src/main/app/composition.ts +++ b/src/main/app/composition.ts @@ -1,11 +1,36 @@ -import { SyncReplicaStore } from '@/sync/replica/store' -import { SyncReplicaEndpoint } from '@/sync/replica/endpoint' -import { AutomaticSync } from '@/sync/replica/automatic' -import { SyncPeerService } from '../sync/peer' -import { createSyncPeerRoutes } from '../sync/peer/routes' -import { NowledgeMemConnections } from '@/nowledgeMem' -import { NOWLEDGE_PLUGIN_ID } from '@shared/types/nowledgeMemPlugin' +import path from 'node:path' +import { pathToFileURL } from 'node:url' +import { + app, + ipcMain, + nativeImage, + nativeTheme, + net, + powerMonitor, + safeStorage, + shell, + webContents as electronWebContents +} from 'electron' +import { optimizer } from '@electron-toolkit/utils' import logger from '@shared/logger' +import { DEEPCHAT_EVENT_CHANNEL } from '@shared/contracts/channels' +import { + approvalClosedEvent, + approvalRequestedEvent, + createDeepchatEventEnvelope, + type DeepchatEventName +} from '@shared/contracts/events' +import { + configGetAcpRegistryIconMarkupRoute, + sessionsGetWidgetSnapshotRoute, + sessionsOpenDesktopRoute +} from '@shared/contracts/routes' +import { NativeConfigurationSnapshotSchema } from '@shared/harness/configuration' +import { + EpisodeRegistry, + TimeoutNotificationScheduler, + systemNotificationClock +} from '@shared/notifications' import { mainLogger, reportMainStartupComponentFailure, @@ -17,72 +42,41 @@ import { normalizeMainLogRunStopReason, type MainLogRunStopReason, type MainLogShutdownReason, - type MainLogStartupComponent, - type MainLogStartupComponentFailureCategory, type SafeLogError } from '@/logging/mainLogEvents' -import { projectEnvironmentsChangedEvent } from '@shared/contracts/events/project.events' +import type { RunJournalObservation } from '@/agent/deepchat/runtime/types' +import type { LiveDelegationLifecycleObservation } from '@/orchestration/liveDelegationService' +import { isAcpRegistryIconUrl } from '@/agent/acp/catalog/acpRegistryConstants' import { - approvalClosedEvent, - approvalRequestedEvent, - liveDelegationChangedEvent, - sessionsTapeInspectorHeadChangedEvent, - sessionsUpdatedEvent -} from '@shared/contracts/events' -import path from 'path' -import { pathToFileURL } from 'node:url' -import { DialogService } from '../desktop/dialog' -import { safeStorage, app, ipcMain, webContents as electronWebContents } from 'electron' -import { DEEPCHAT_EVENT_CHANNEL } from '@shared/contracts/channels' -import { createDeepchatEventEnvelope, type DeepchatEventName } from '@shared/contracts/events' -import { optimizer } from '@electron-toolkit/utils' + DEFAULT_AGENT_INVOCATION_CAPACITY, + DEFAULT_AGENT_INVOCATION_MAX_PENDING +} from '@/agent/invocationAdmission' +import { CoreRuntimeConnection, createCoreProfileNative } from '../harness/core' +import { createCoreConfigurationNative } from '../harness/configuration' +import { CoreDesktopNative } from '../harness/desktop' +import { CoreBrowserNative } from '../harness/browser' +import { CoreHelperNative } from '../harness/helpers' +import { HarnessRuntime } from '../harness/runtime' +import type { CoreKernelNative } from '../../harness/core/kernel' import { WindowPresenter } from '../desktop/window' -import { PluginSettingsWindow } from '../desktop/pluginSettingsWindow' +import { TabPresenter } from '../desktop/tab' import { ShortcutPresenter } from '../desktop/shortcut' -import type { FileServicePort } from '@shared/types/file' -import type { ToolchainKind } from '@shared/types/toolchains' -import type { WorkspaceServicePort } from '@shared/types/workspace' -import type { AssistantMessageBlock } from '@shared/types/agent-interface' -import { projectFinalAssistantAnswer } from '@shared/lib/assistantDeliverySegments' -import type { SkillMetadataSnapshotPort, SkillServicePort } from '@shared/types/skill' -import type { SkillSyncServicePort } from '@shared/types/skillSync' -import type { IConversationExporter } from '../exporter/interface' -import type { - IShortcutPresenter, - IWindowPresenter, - IYoBrowserPresenter -} from '@shared/types/desktop' -import type { DialogServicePort } from '@shared/types/dialog' -import type { KnowledgeServicePort } from '@shared/types/knowledge' -import { ProviderRuntime } from '../provider' -import { AgentInvocationAdmission } from '@/agent/invocationAdmission' -import { ProviderImportService } from '../provider/providerImportService' -import { ProviderDatabase } from '../provider/data/database' -import { createProviderRoutes } from '../provider/routes' -import { ProviderSettings } from '../provider/settings' -import type { SettingsStore } from '../config/settingsStore' -import type { SecretStore } from '../config/secretStore' -import { providerDbLoader } from '../provider/providerDbLoader' -import { AcpProvider } from '../provider/providers/acpProvider' -import { proxyConfig, ProxyMode } from '../platform/proxy' +import { NotificationService } from '../desktop/notification' +import { TrayPresenter } from '../desktop/tray' +import { FloatingButtonPresenter } from '../desktop/floatingButton' +import { DialogService } from '../desktop/dialog' +import { YoBrowserPresenter } from '../desktop/browser/YoBrowserPresenter' +import { ComputerUsePreviewPresenter } from '../desktop/computerUse/ComputerUsePreviewPresenter' +import { AgentPreviewCoordinator } from '../desktop/preview/AgentPreviewCoordinator' +import { PluginSettingsWindow } from '../desktop/pluginSettingsWindow' +import { DesktopRemoteAuthWindows } from '../desktop/remoteAuthWindows' +import { DesktopConfigurationHost } from '../desktop/configurationHost' +import { DesktopConfigurationProjection } from '../desktop/configurationProjection' +import { DesktopImageActions } from '../desktop/imageActions' import { DeviceService } from '../device' import { UpgradeService } from '../upgrade' -import { UpdateSettings } from '../upgrade/settings' -import { FileService } from '../file' -import { getShellEnvironment } from '@/agent/shared/process/shellEnvHelper' -import { mergeDetectionEnv, noteNodeDemandFromMcp, ToolchainService } from '@/toolchains' -import { ToolchainResolutionError } from '@/toolchains/errors' -import { createToolchainRoutes } from '@/toolchains/routes' -import { AttachmentCapabilityRouter } from '@/ocr/attachmentCapabilityRouter' -import { OcrRuntimeService } from '@/ocr/ocrRuntimeService' -import { OcrSettings } from '@/ocr/ocrSettings' -import { createOcrRoutes } from '@/ocr/routes' -import { McpService } from '../mcp' -import { ImportMode, SyncService, type SyncImportDatabasePort } from '../sync' -import { SyncSettings } from '../sync/settings' import { DeeplinkService } from '../deeplink' import { createDeeplinkActions } from '../deeplink/actions' -import { NotificationService } from '../desktop/notification' import { AggregatedWindowNotificationDiagnostics, ElectronWindowNotificationTargets, @@ -90,234 +84,39 @@ import { createNotificationRoutes, type SemanticNotificationPublisher } from '../notifications' -import { DesktopSettings } from '../desktop/settings' -import { FontSettings } from '../desktop/fontSettings' -import { TabPresenter } from '../desktop/tab' -import { DesktopSessionBinding } from '@/desktop/sessionBinding' -import { TrayPresenter } from '../desktop/tray' -import { OAuthService } from '../provider/auth' -import { FloatingButtonPresenter } from '../desktop/floatingButton' -import { YoBrowserPresenter } from '../desktop/browser/YoBrowserPresenter' -import { ComputerUsePreviewPresenter } from '@/desktop/computerUse/ComputerUsePreviewPresenter' -import { AgentPreviewCoordinator } from '@/desktop/preview/AgentPreviewCoordinator' -import { KnowledgeService } from '../knowledge' -import { WorkspaceService } from '../workspace' -import { FileWatcherService } from '../platform/fileWatcher' -import { LoggingService } from './logging' -import { RendererPerformanceLogService } from './rendererPerformanceLogService' -import type { PrivacySettings } from './privacy' -import type { ProxySettings } from '@/platform/proxySettings' -import type { McpSettings } from '@/mcp/settings' -import type { McpAppSandboxRegistry } from '@/mcp/apps/sandboxRegistry' -import type { AcpCatalogSettings } from '@/agent/acp/catalog/settings' -import { ToolService } from '../tool' -import { createToolRoutes } from '../tool/routes' -import { createSkillRoutes } from '../skill/routes' -import { createMcpRoutes } from '../mcp/routes' -import { createRemoteRoutes } from '../remote/routes' -import { createSchedulerRoutes } from '../scheduler/routes' -import { createMemoryRoutes } from '../memory/routes' import { createDesktopRoutes } from '../desktop/routes' -import { createFileRoutes } from '../file/routes' -import { createKnowledgeRoutes } from '../knowledge/routes' -import { KnowledgeSettings } from '@/knowledge/settings' -import { PromptSettings } from '@/agent/promptSettings' -import { AgentSettings } from '@/agent/settings' -import { AgentLifecycleGate } from '@/agent/lifecycleGate' -import { SessionDeletionGate } from '@/session/deletionGate' -import { emitAcpAgentModelsChanged, emitAgentCatalogChanged } from '@/app/agentEvents' -import { emitModelsChanged } from '@/provider/eventPublishers' -import { createWorkspaceRoutes } from '../workspace/routes' import { createDeviceRoutes } from '../device/routes' -import { createOnboardingRoutes } from '../onboarding/routes' import { createUpgradeRoutes } from '../upgrade/routes' -import { createSyncRoutes } from '../sync/routes' -import { SyncHostService } from '../sync/host' -import { createSyncHostRoutes } from '../sync/host/routes' -import { createPlatformRoutes } from '../platform/routes' -import { createHookRoutes } from '../hook/routes' -import { createAppSettingsRoutes } from './settingsRoutes' -import { createAppRoutes } from './routes' +import { createRouteRegistry, type RouteContext } from '../routes/routeRegistry' +import type { DeepchatRouteName } from '@shared/contracts/routes' +import type { McpAppSandboxPort, McpAppDesktopPort } from '../mcp/apps/sandboxRegistry' +import { SafeStorageOcrCacheKeyProvider } from '../ocr/ocrCacheKeyProvider' +import { credentialCrypto, secureCredentialCrypto } from './credentialCrypto' +import { ProxyConfig, ProxyMode } from '../platform/proxy' +import { isInsecureTlsAllowed } from '../lib/insecureTls' +import { createGitHubCopilotAuthorization } from '../provider/auth' +import { spawnBackgroundExecHost } from './backgroundExecHost' +import { spawnCodeModeHost } from './codeModeHost' +import { spawnSchedulerHost } from './schedulerHost' +import { spawnFileWatcherHost } from './fileWatcherHost' +import { listFileOpenApps, openFileWithApp } from '../workspace/openInApp' +import { CliLauncherService, resolveBundledCliDirectory } from '../cli' +import { RendererPerformanceLogService } from './rendererPerformanceLogService' import { registerClipboardIpc } from './clipboardIpc' -import { createRendererIpcAuthorizer } from './rendererIpcSecurity' -import { ApprovalBroker, createApprovalRoutes } from '@/approval' -import { - CommandPermissionService, - FilePermissionService, - SettingsPermissionService, - ToolPermissionBroker -} from '../tool/permission' -import type { AgentToolDependencies } from '../tool/runtimePorts' - -import { ConversationExporterService } from '../exporter' -import { createExporterRoutes } from '../exporter/routes' -import { SkillService } from '../skill' -import type { SkillSessionStatePort } from '../skill' -import { SkillSettings } from '../skill/settings' -import { SkillSyncService } from '../skill/sync' -import { SkillExecutionAuthorityResolver } from '../skill/skillExecutionAuthority' -import { HookService } from '../hook' -import { HookSettings } from '../hook/config' -import { SchedulerService, createCronJobRunSessionStarter } from '../scheduler' -import { AgentManager } from '@/agent/manager/agentManager' -import { createDeepChatAgentBackend } from '@/agent/manager/deepChatAgentBackend' -import { createDirectAcpAgentBackend } from '@/agent/manager/directAcpAgentBackend' -import { - TOOL_SURFACE_PRODUCTION_ROLLOUT_POLICY_V1, - ToolSurfaceRolloutOwner -} from '@/agent/deepchat/runtime/toolSurfaceRollout' -import { AppSessionService } from '@/agent/shared/appSessionService' -import { createSessionData } from '@/session/data' -import { MemoryDatabase } from '@/memory/data/database' -import { toAppSessionId } from '@/agent/shared/agentSessionIds' -import { resolveAssistantModelSelection } from '@/agent/shared/assistantModelSelection' -import { AgentUnavailableError } from '@/agent/shared/agentCatalogCodec' -import { resolveAcpAgentAlias } from '@shared/utils/acpAgentAlias' -import { SessionQuery } from '@/session/query' -import { SessionAssignmentPolicy } from '@/session/assignmentPolicy' -import { sanitizeGenerationSettings } from '@/agent/deepchat/runtime/generationSettings' -import { SessionAssignment } from '@/session/assignment' -import { SessionDeletion } from '@/session/deletion' -import { SessionTranscriptMutations } from '@/session/transcriptMutations' -import { SessionTurn } from '@/session/turn' -import { SessionLifecycle } from '@/session/lifecycle' -import { createDeepChatAgentHarness, type DeepChatAgentHarness } from '@/agent/deepchat/harness' -import type { RunJournalObservation } from '@/agent/deepchat/runtime/types' -import { AcpAgentRuntime } from '@/agent/acp/instance' -import { createAcpRuntimeOwner } from '@/agent/acp/createRuntimeOwner' -import { createAcpRoutes } from '@/agent/acp/routes' -import { AcpSessionPersistence } from '@/agent/acp/runtime' -import type { - MemoryIngestionDrainOutcome, - MemoryIngestionObserver -} from '@/agent/deepchat/memory/memoryIngestionObserver' -import { MemoryService, isSafeAgentId, type MemoryServicePort } from '../memory' -import { createMemoryVectorStorePaths, MemoryVectorStore } from '../memory/infra/memoryVectorStore' -import { ProjectService } from '../project' -import { ProjectDatabase } from '@/project/data/database' -import { SettingsDatabase } from '@/settings/data/database' -import { SchedulerDatabase } from '@/scheduler/data/database' -import { AppDatabase } from '@/app/data/database' -import { createOrchestrationRoutes } from '@/orchestration/routes' -import { OrchestrationCapabilityResolver } from '@/orchestration/capability' -import { LiveDelegationDatabase } from '@/orchestration/data/database' -import { LiveDelegationRepository } from '@/orchestration/liveDelegationRepository' -import { - LiveDelegationService, - type LiveDelegationLifecycleObservation -} from '@/orchestration/liveDelegationService' -import { LiveDelegationSafetyCoordinator } from '@/orchestration/liveDelegationSafety' -import { LiveDelegationConsentAuthority } from '@/orchestration/liveDelegationConsent' -import { TaskContractService } from '@/tape/application/taskContractService' -import { TaskEvaluationService } from '@/tape/application/taskEvaluationService' -import { TapeInspectorHeadWatcher } from '@/tape/application/traceInspectorHeadWatcher' -import { createProjectRoutes } from '../project/routes' -import { RemoteService } from '../remote' -import type { RemoteServiceLike } from '../remote/ports' -import { PluginService, type PluginServicePort } from '../plugin' -import { createPluginRoutes } from '../plugin/routes' -import { PluginRuntimeSupervisor } from '../plugin/runtimeSupervisor' -import { AgentRepository } from '../agent/repository' -import { AgentDatabase } from '@/agent/data/database' -import { DeepChatDefaults } from '../agent/deepchat/defaults' -import { CommandShellService } from '@/agent/shared/process/commandShellService' -import { AgentTraceSettings } from '../agent/traceSettings' -import type { MainDatabase } from '../data/mainDatabase' -import { - DatabaseSecurityService, - type DatabaseSecurityMigrationDatabasePort -} from './databaseSecurity' -import { - normalizeDeepChatSubagentSlots, - resolveDeepChatSubagentCapability -} from '@shared/lib/deepchatSubagents' -import { DEFAULT_DISABLED_AGENT_TOOLS } from '@shared/agentTools' -import { composeSubagentAuthority } from '@/session/subagentAuthority' -import type { - AcpAsLlmProviderPermissionPort, - AcpAsLlmProviderSessionControlPort, - AcpProviderAdminPort, - ProviderCatalogPort -} from '../provider/ports' -import type { SessionPermissionPort, SessionUiPort } from '../session/contracts' -import { - isStartupWorkloadCancellation, - scheduleObservedStartupTask, - StartupWorkloadCoordinator -} from '../app/startupWorkloadCoordinator' -import type { StartupWorkloadTaskContext } from '../app/startupWorkloadCoordinator' -import { LegacyChatImportService } from './startupMigrations/legacyChatImportService' -import { UsageStatsService } from '../session/usageStatsService' -import type { SessionDataMigrationSQLitePort } from './startupMigrations/sessionDataMigrations' -import { SessionHistorySearch } from '@/session/sessionHistorySearch' -import { SessionTranslation } from '@/session/sessionTranslation' -import { createSessionRoutes } from '@/session/routes' -import { createAgentRoutes } from '@/agent/routes' -import { createPromptRoutes } from '@/agent/promptRoutes' -import { createSkillExecutionAuthorityTapePort } from '@/tape/application/capabilityAdapters' -import { AgentSessionExportService } from '../exporter/agentSessionExporter' -import { createInMemoryServerFactory } from '../mcp/inMemoryServers/builder' -import { - createRouteDispatcher, - dispatchDeepchatRoute, - registerDeepchatRoutes, - type RouteDispatcher -} from '@/routes' -import { createNodeScheduler } from '@/routes/scheduler' +import { createRendererIpcAuthorizer, isRendererWindowCurrent } from './rendererIpcSecurity' +import { registerDeepchatRoutes } from './routeIpc' import { coordinateApplicationDataReset } from './applicationDataReset' -import { - AgentCliCommandAccess, - AgentCliTokenAuthority, - ArtifactSpool, - CliAudioTranscriptionService, - CliAuditLog, - CliComputeService, - CliLauncherService, - CliMutationGuard, - CliOcrService, - ProgrammaticToolDispatcher, - ProgrammaticToolParentRegistry, - CliRequestPolicy, - CliRunService, - CliServer, - CliSkillService, - createArtifactRoutes, - createCliComputeRoutes, - createCliMcpAdminRoutes, - createCliProviderModelAdminRoutes, - createCliRoutes, - resolveBundledCliDirectory -} from '@/cli' -import { CliRequestError } from '@/cli/errors' -import { AcpRegistryMigrationService } from '@/agent/acp/catalog/acpRegistryMigrationService' -import { AcpAuthService } from '@/agent/acp/auth/acpAuthService' -import { backgroundExecSessionManager } from '@/agent/shared/process/backgroundExecSessionManager' -import { - runBuiltinMcpAllowlistCompatibilityMigration, - runDisabledAgentToolCapabilityCleanupMigration, - runMainlineNormalizationMigration -} from './startupMigrations/sessionDataMigrations' -import { activateAppOnMac } from '@/lib/activateApp' -import { SessionRuntimeEvents } from '@/session/runtimeEvents' -import { TypedEventHub } from '@/events/typedEventHub' -import { SessionEventRouter } from '@/events/sessionEventRouter' -import { createMemoryProviderBindings } from './memoryProviderBindings' -import { createSessionPermissionPort } from './sessionPermissionAdapter' import { MainShutdownCoordinator, type MainShutdownActionClaim } from './mainShutdownCoordinator' -import { elapsedMonotonicMs, readMonotonicNow } from '@/lib/monotonicTime' -import { - EpisodeRegistry, - TimeoutNotificationScheduler, - systemNotificationClock -} from '@shared/notifications' - -type ApplicationDatabaseMaintenancePort = SyncImportDatabasePort & - DatabaseSecurityMigrationDatabasePort +import { activateAppOnMac } from '../lib/activateApp' +import { awaitWithAbort } from '../lib/awaitWithAbort' +import { elapsedMonotonicMs, readMonotonicNow } from '../lib/monotonicTime' export interface MainProcessControl { + readonly mcpAppSandbox: McpAppSandboxPort + resolveWorkspacePreviewRequest(url: string): Promise focusPrimaryWindow(): void handleDeepLink(url: string): Promise - clearPermissionCaches(): void + clearPermissionCaches(): Promise confirmShutdown(): Promise cancelShutdown(): void hasMainWindows(): boolean @@ -488,3481 +287,1040 @@ function emitLiveDelegationObservation(observation: LiveDelegationLifecycleObser } } -function createLivePort(resolve: () => T): T { - return new Proxy({} as T, { - get(_target, property) { - const target = resolve() - const value = Reflect.get(target, property, target) - return typeof value === 'function' ? value.bind(target) : value - } - }) -} - export async function createMainProcessControl(dependencies: { - previousAppVersion?: string - settingsStore: SettingsStore - secretStore: SecretStore - privacySettings: PrivacySettings - proxySettings: ProxySettings - mcpSettings: McpSettings - mcpAppSandboxRegistry: McpAppSandboxRegistry - acpCatalogSettings: AcpCatalogSettings - database: MainDatabase - settingsDatabase: SettingsDatabase - providerDatabase: ProviderDatabase - agentDatabase: AgentDatabase - databaseSecurityService: DatabaseSecurityService - startupWorkloadCoordinator: StartupWorkloadCoordinator + mcpAppDesktop: McpAppDesktopPort startupRunId: string requestUpdateInstall: (installAction: () => void) => Promise onWindowCreated: (isMainWindow: boolean) => void splash: import('./splashWindow').SplashWindow bindControl: (control: MainProcessControl) => void -}) { - const databaseSecurityService = dependencies.databaseSecurityService - const startupWorkloadCoordinator = dependencies.startupWorkloadCoordinator - const mainDatabase = dependencies.database - const fileWatcherService = new FileWatcherService() - let windowPresenter: IWindowPresenter - let providerSettings: ProviderSettings - let acpProviderAdminPort: AcpProviderAdminPort - let exporter: IConversationExporter - let deviceService: DeviceService - let upgradeService: UpgradeService - let shortcutPresenter: IShortcutPresenter - let fileService: FileServicePort - let ocrRuntimeService: OcrRuntimeService - let ocrSettings: OcrSettings - let mcpService: McpService - let syncService: SyncService - let syncPeerService: SyncPeerService - let syncHostService: SyncHostService - let syncReplica: SyncReplicaStore | undefined - let automaticSync: AutomaticSync | undefined - const syncActiveSessions = new Set() - let deeplinkService: DeeplinkService - let notificationService: NotificationService - let tabPresenter: TabPresenter - let trayPresenter: TrayPresenter - let oauthService: OAuthService - let floatingButtonPresenter: FloatingButtonPresenter - let knowledgeService: KnowledgeServicePort - let workspaceService: WorkspaceServicePort - let toolService: ToolService - let deepChatAgentHarness: DeepChatAgentHarness - let yoBrowserPresenter: IYoBrowserPresenter - let computerUsePreviewPresenter: ComputerUsePreviewPresenter - let agentPreviewCoordinator: AgentPreviewCoordinator - let dialogService: DialogServicePort - let skillService: SkillServicePort & SkillMetadataSnapshotPort - let skillSyncService: SkillSyncServicePort - let sessionQuery: SessionQuery - let desktopSessionBinding: DesktopSessionBinding - let sessionAssignmentPolicy: SessionAssignmentPolicy - let sessionAssignment: SessionAssignment - let sessionTurn: SessionTurn - let sessionLifecycle: SessionLifecycle - let sessionDeletion: SessionDeletion - let sessionPermissionPort: SessionPermissionPort - let agentManager: AgentManager - let acpAgentRuntime: AcpAgentRuntime - let memoryService: MemoryServicePort - let memoryIngestionObserver: MemoryIngestionObserver - let projectService: ProjectService - let remoteService: RemoteServiceLike - let pluginService: PluginServicePort - let hookService: HookService - let cronJobs: SchedulerService - let commandPermissionService: CommandPermissionService - let filePermissionService: FilePermissionService - let settingsPermissionService: SettingsPermissionService - let approvalBroker: ApprovalBroker - let toolPermissionBroker: ToolPermissionBroker - let legacyChatImportService: LegacyChatImportService - let usageStatsService: UsageStatsService - let appSessionService: AppSessionService - let sessionDataMigrationSQLite: SessionDataMigrationSQLitePort - let sessionHistorySearch: SessionHistorySearch - let agentSessionExportService: AgentSessionExportService - let sessionTranslation: SessionTranslation - let liveDelegationService: LiveDelegationService - let acpAsLlmProviderSessionControl: AcpAsLlmProviderSessionControlPort - let acpAsLlmProviderPermission: AcpAsLlmProviderPermissionPort - let routeDispatcher: RouteDispatcher | undefined - let cliComputeService: CliComputeService - let cliAudioTranscriptionService: CliAudioTranscriptionService - let cliOcrService: CliOcrService - let cliSkillService: CliSkillService - let cliMutationGuard: CliMutationGuard - let cliRequestPolicy: CliRequestPolicy - let cliRunService: CliRunService - let hasInitialized = false - let databaseMaintenanceState: 'running' | 'maintenance' | 'failed' = 'running' +}): Promise { let appLifecycleState: 'starting' | 'running' | 'stopping' | 'stopped' = 'starting' + const isStopping = () => appLifecycleState === 'stopping' || appLifecycleState === 'stopped' + let nativeSettings: DesktopConfigurationProjection + let floatingButtonPresenter: FloatingButtonPresenter + let windowPresenter: WindowPresenter + let tabPresenter: TabPresenter + let core: CoreRuntimeConnection | undefined + const proxyConfig = new ProxyConfig((error) => core?.invalidateNative(error)) + let nativeDesktop: CoreDesktopNative | undefined + let nativeBrowser: CoreBrowserNative | undefined + let nativeHelpers: CoreHelperNative | undefined + let cleanup: (() => Promise) | undefined let shutdownStepFailures = 0 - let pluginInitializationPromise: Promise | null = null - let skillInitializationPromise: Promise | null = null - let skillSyncScanPromise: Promise | null = null - - windowPresenter = new WindowPresenter( - { - getCloseToQuit: () => dependencies.settingsStore.get('closeToQuit') ?? false, - getContentProtectionEnabled: () => - dependencies.settingsStore.get('contentProtectionEnabled') ?? false - }, - () => { - restartApplication().catch((error) => logger.error('Application restart failed:', error)) - }, - dependencies.onWindowCreated, - startupWorkloadCoordinator - ) - // No CLI sessions can exist before AppSessionService is ready, so startup events retain the - // renderer compatibility path. The installed resolver below fails closed for missing sessions. - let resolveSessionRunId = (_sessionId: string): string | null | undefined => null - let resolveBoundRendererIds = (_sessionId: string): readonly number[] => [] - const typedEventHub = new TypedEventHub({ - renderer: { - broadcast: (envelope) => windowPresenter.sendToAllWindows(DEEPCHAT_EVENT_CHANNEL, envelope), - send: (webContentsId, envelope) => - (windowPresenter as WindowPresenter).sendToWebContents( - webContentsId, - DEEPCHAT_EVENT_CHANNEL, - envelope - ) - }, - log: logger - }) - const sessionEventRouter = new SessionEventRouter({ - hub: typedEventHub, - resolveSessionRunId: (sessionId) => resolveSessionRunId(sessionId), - getBoundRendererIds: (sessionId) => resolveBoundRendererIds(sessionId) - }) - const agentCliTokenAuthority = new AgentCliTokenAuthority() - const toolSurfaceRollout = new ToolSurfaceRolloutOwner(TOOL_SURFACE_PRODUCTION_ROLLOUT_POLICY_V1) - let programmaticToolDispatcher: ProgrammaticToolDispatcher - const artifactSpool = new ArtifactSpool({ - directory: path.join(app.getPath('userData'), 'local-control', 'artifacts'), - consumeAgentBytes: (tokenId, bytes) => agentCliTokenAuthority.consumeBytes(tokenId, bytes), - log: logger - }) - const cliAuditLog = new CliAuditLog({ - directory: path.join(app.getPath('userData'), 'local-control') - }) - const cliServer = new CliServer({ - userDataPath: app.getPath('userData'), - appVersion: app.getVersion(), - dispatch: async (method, input, caller, signal) => { - if (cliAudioTranscriptionService?.handlesRpc(method)) { - assertRouteAllowedDuringDatabaseMaintenance(method) - return await cliAudioTranscriptionService.dispatchRpc(method, input, caller, signal) - } - if (cliOcrService?.handlesRpc(method)) { - assertRouteAllowedDuringDatabaseMaintenance(method) - return await cliOcrService.dispatchRpc(method, input, caller, signal) - } - if (!routeDispatcher) throw new Error('CLI route dispatcher is not ready') - signal.throwIfAborted() - const output = await dispatchDeepchatRoute(routeDispatcher, method, input, { caller }) - signal.throwIfAborted() - return output - }, - dispatchProgrammaticTool: async ( - method, - input, - caller, - operation, - signal, - takeSettlementOwnership + const widgetRoutesReady = Promise.withResolvers() + void widgetRoutesReady.promise.catch(() => {}) + const publishDeepchatEvent = (name: DeepchatEventName, payload: unknown): void => { + windowPresenter.sendToAllWindows( + DEEPCHAT_EVENT_CHANNEL, + createDeepchatEventEnvelope(name, payload) + ) + } + const admissionDiagnostics = { + observationsEnabled: () => mainLogger.isOutputEnabled(), + observe: ( + observation: import('@shared/harness/admission').AgentInvocationAdmissionObservation ) => { - assertRouteAllowedDuringDatabaseMaintenance(method) - return await programmaticToolDispatcher.dispatch( - method, - input, - caller, - operation, - signal, - takeSettlementOwnership - ) - }, - completeProgrammaticToolPreDispatchFailure: (method, operation, error) => { - programmaticToolDispatcher.completePreDispatchFailure(method, operation, error) - }, - dispatchStream: async (method, input, caller, requestId, signal, emit) => { - if (cliRunService?.handlesStream(method)) { - assertRouteAllowedDuringDatabaseMaintenance(method) - return await cliRunService.dispatchStream(method, input, caller, signal, emit) - } - if (!cliComputeService) throw new Error('CLI compute service is not ready') - assertRouteAllowedDuringDatabaseMaintenance(method) - return await cliComputeService.dispatchStream(method, input, caller, requestId, signal, emit) - }, - dispatchUpload: async (method, input, upload, caller, signal) => { - assertRouteAllowedDuringDatabaseMaintenance(method) - if (cliAudioTranscriptionService?.handlesUpload(method)) { - return await cliAudioTranscriptionService.dispatchUpload( - method, - input, - upload, - caller, - signal - ) - } - if (cliOcrService?.handlesUpload(method)) { - return await cliOcrService.dispatchUpload(method, input, upload, caller, signal) - } - if (cliSkillService?.handlesUpload(method)) { - return await cliSkillService.dispatchUpload(method, input, upload, caller, signal) + switch (observation.type) { + case 'queued': + mainLogger.emit('agent.admission.queued', observation) + break + case 'granted': + mainLogger.emit('agent.admission.granted', observation) + break + case 'released': + mainLogger.emit('agent.admission.released', observation) + break + case 'rejected': + mainLogger.emit('agent.admission.rejected', observation) + break + case 'closed': + mainLogger.emit('agent.admission.closed', observation) + break } - throw new Error(`CLI upload service is not ready for ${method}`) - }, - authorize: async (input) => { - if (!cliRequestPolicy) throw new Error('CLI request policy is not ready') - return await cliRequestPolicy.authorize(input) - }, - beginAgentRequest: (token) => agentCliTokenAuthority.beginRequest(token), - artifactSpool, - log: logger - }) - const semanticNotificationScheduler = new TimeoutNotificationScheduler() - const semanticNotificationEpisodes = new EpisodeRegistry( - systemNotificationClock, - semanticNotificationScheduler - ) - let handleSemanticRendererUnavailable = (_webContentsId: number): void => undefined - let handleApprovalRendererUnavailable = (_webContentsId: number): void => undefined - const semanticNotificationTargets = new ElectronWindowNotificationTargets( - windowPresenter, - () => tabPresenter, - (webContentsId) => { - handleSemanticRendererUnavailable(webContentsId) - handleApprovalRendererUnavailable(webContentsId) } + } + const harnessDirectory = app.isPackaged + ? path.join(process.resourcesPath, 'app.asar.unpacked', 'harness') + : path.join(app.getAppPath(), 'out', 'harness') + process.env.DEEPCHAT_HARNESS_EXECUTABLE = path.join( + harnessDirectory, + `deepchat-harness-${process.platform}-${process.arch}${process.platform === 'win32' ? '.exe' : ''}` ) - const semanticNotificationDiagnostics = new AggregatedWindowNotificationDiagnostics({ - scheduler: semanticNotificationScheduler, - write: (event) => logger.warn('[NotificationRouter] delivery diagnostic', event) - }) - const semanticNotificationRouter = new WindowNotificationRouter({ - clock: systemNotificationClock, - scheduler: semanticNotificationScheduler, - episodes: semanticNotificationEpisodes, - targets: semanticNotificationTargets, - diagnostics: semanticNotificationDiagnostics + const harnessRuntime = await HarnessRuntime.start({ + directory: harnessDirectory, + electronExecutable: process.execPath, + capacity: DEFAULT_AGENT_INVOCATION_CAPACITY, + maxPending: DEFAULT_AGENT_INVOCATION_MAX_PENDING, + observe: admissionDiagnostics.observe }) - handleSemanticRendererUnavailable = (webContentsId) => { - void semanticNotificationRouter - .availabilityChanged({ unavailableWebContentsIds: [webContentsId] }) - .catch((error) => { - logger.warn('[NotificationRouter] renderer invalidation failed', error) - }) - } - const semanticNotifications: SemanticNotificationPublisher = { - occur: (intent) => { - void semanticNotificationRouter.occur(intent).catch((error) => { - logger.warn('[NotificationRouter] occurrence failed', error) - }) - }, - recover: (intent) => { - void semanticNotificationRouter.recover(intent).catch((error) => { - logger.warn('[NotificationRouter] recovery failed', error) - }) - } - } - const publishDeepchatEvent = (name: DeepchatEventName, payload: unknown): void => { - sessionEventRouter.publish(name, payload) - if (name === 'sessions.status.changed') { - const state = payload as { sessionId: string; status: string } - if (state.status === 'generating' || state.status === 'running') - syncActiveSessions.add(state.sessionId) - else { - syncActiveSessions.delete(state.sessionId) - syncReplica?.wake() - } - } - } - dependencies.mcpAppSandboxRegistry.setConsentPublisher((windowId, payload) => { - windowPresenter.sendToWindow( - windowId, - DEEPCHAT_EVENT_CHANNEL, - createDeepchatEventEnvelope('mcp.app.consent.request', payload) + try { + const desktopConfigurationHost = new DesktopConfigurationHost( + app.getPath('userData'), + setMainLoggingEnabled, + restartApplication ) - }) - const unsubscribeStartupWorkload = startupWorkloadCoordinator.subscribe((payload) => { - publishDeepchatEvent('startup.workload.changed', payload) - }) - providerSettings = new ProviderSettings( - dependencies.settingsStore, - dependencies.privacySettings, - dependencies.providerDatabase, - publishDeepchatEvent, - dependencies.previousAppVersion - ) - - const memoryDatabase = new MemoryDatabase(mainDatabase) - const sessionData = createSessionData( - mainDatabase, - () => memoryDatabase.ingestionProjectionTable, - { - publishPendingInputsChanged: (sessionId) => - publishDeepchatEvent('sessions.pendingInputs.changed', { - sessionId, - version: Date.now() + const profileNative = createCoreProfileNative( + { + ...credentialCrypto, + getBackend: async () => + process.platform === 'linux' ? safeStorage.getSelectedStorageBackend() : undefined + }, + { + setLoggingEnabled: async (enabled) => setMainLoggingEnabled(enabled), + showDatabaseUnlockProgress: async (payload, options) => + dependencies.splash.showDatabaseUnlockProgress(payload, options), + requestDatabaseUnlock: (payload) => dependencies.splash.requestDatabaseUnlock(payload), + requestDatabaseRecovery: (payload) => dependencies.splash.requestDatabaseRecovery(payload), + quit: async () => app.quit() + }, + (observation) => mainLogger.emit('database.initialization.terminal', observation) + ) + const configurationNative = createCoreConfigurationNative({ + desktop: { + systemLocale: app.getLocale(), + readTheme: async () => ({ + theme: nativeTheme.themeSource, + isDark: nativeTheme.shouldUseDarkColors }), - publishMessagesChanged: (sessionId, messages) => - publishDeepchatEvent('sessions.messages.changed', { - sessionId, - messages, - version: Date.now() - }) - } - ) - const tapeInspectorHeadWatcher = new TapeInspectorHeadWatcher({ - readHead: (sessionId) => sessionData.tapeStore.getTapeInspectorHead(sessionId), - emit: (webContentsId, pulse) => { - typedEventHub.publish(sessionsTapeInspectorHeadChangedEvent.name, pulse, { - kind: 'renderer', - webContentsId - }) - }, - watchRendererDestroyed: (webContentsId, listener) => { - const target = electronWebContents.fromId(webContentsId) - if (!target || target.isDestroyed()) { - queueMicrotask(listener) - return () => {} - } - target.once('destroyed', listener) - return () => target.removeListener('destroyed', listener) - }, - onError: (error, sessionId) => { - logger.warn('[TapeInspectorHeadWatcher] Failed to read committed head', { - sessionId, - error - }) - } - }) - const programmaticToolParents = new ProgrammaticToolParentRegistry({ - tokenAuthority: agentCliTokenAuthority, - executionJournal: sessionData.programmaticExecutionJournal - }) - programmaticToolDispatcher = new ProgrammaticToolDispatcher({ - parents: programmaticToolParents, - executeChild: async (input) => await toolService.callProgrammaticToolChild(input), - authorizeChild: async ({ - caller, - grant, - childOrdinal, - entry, - arguments: childArguments, - permission, - signal - }) => { - const requestId = permission.requestId?.trim() - if (!requestId) { - throw new CliRequestError( - 'unavailable', - 'Programmatic Tool permission request is unavailable', - { httpStatus: 503 } - ) - } - await cliMutationGuard.authorize({ - operation: 'tool.call', - effect: permission.permissionType === 'read' ? 'read' : 'destructive', - principal: caller.principal, - connectionId: caller.connectionId, - clientRequestId: `${grant.operation.providerToolCallId}:${childOrdinal}`, - arguments: { - target: entry.target.providerVisibleName, - arguments: childArguments + applyTheme: async (theme) => { + nativeTheme.themeSource = theme + return nativeTheme.shouldUseDarkColors }, - displayData: { - target: entry.target.providerVisibleName, - ...(typeof permission.argumentsPreview === 'string' - ? { argumentsPreview: permission.argumentsPreview } - : {}) + refreshLanguage: async (locale) => { + floatingButtonPresenter.refreshLanguage(locale) + await tabPresenter.refreshLanguage(locale) }, - signal, - timeoutMs: grant.quotas.maxDurationMs - }) - if (!toolPermissionBroker.approve(requestId, grant.operation.sessionId)) { - throw new CliRequestError( - 'unavailable', - 'Programmatic Tool permission request is no longer active', - { httpStatus: 503 } - ) - } - }, - cancelChildPermission: (requestId, sessionId) => { - toolPermissionBroker.cancel(requestId, sessionId) - } - }) - const taskContractService = new TaskContractService( - () => sessionData.database.deepchatContractStore - ) - const taskEvaluationService = new TaskEvaluationService( - () => sessionData.database.deepchatContractStore - ) - const liveDelegationRepository = new LiveDelegationRepository( - new LiveDelegationDatabase(mainDatabase), - taskContractService, - taskEvaluationService - ) - const sessionRuntimeEvents = new SessionRuntimeEvents() - const projectDatabase = new ProjectDatabase(mainDatabase) - const agentDatabase = dependencies.agentDatabase - const settingsDatabase = dependencies.settingsDatabase - const providerDatabase = dependencies.providerDatabase - const schedulerDatabase = new SchedulerDatabase(mainDatabase) - const appDatabase = new AppDatabase(mainDatabase) - const agentRepository = new AgentRepository(agentDatabase, sessionData.database, memoryDatabase) - const agentLifecycle = new AgentLifecycleGate() - const sessionDeletionGate = new SessionDeletionGate() - const promptSettings = new PromptSettings(dependencies.settingsStore, { - publishCustomPromptsChanged: (prompts) => - publishDeepchatEvent('config.customPrompts.changed', { - prompts, - version: Date.now() - }), - publishSystemPromptsChanged: (state) => - publishDeepchatEvent('config.systemPrompts.changed', { - ...state, - version: Date.now() - }) - }) - appSessionService = new AppSessionService(projectDatabase, sessionData.database, () => - projectService.notifyEnvironmentProjectionChanged() - ) - resolveSessionRunId = (sessionId) => { - const visited = new Set() - let currentSessionId: string | null = sessionId - while (currentSessionId && visited.size < 32 && !visited.has(currentSessionId)) { - visited.add(currentSessionId) - const session = appSessionService.get(currentSessionId) - if (!session) return undefined - if (session.metadata?.source === 'cli_run') return session.id - currentSessionId = session.parentSessionId ?? null - } - return currentSessionId ? undefined : null - } - sessionDataMigrationSQLite = { - get appSettingsTable() { - return settingsDatabase.appSettingsTable - }, - getDatabase: () => sessionData.database.getDatabase(), - get newSessionsTable() { - return sessionData.database.newSessionsTable - }, - get newSessionActiveSkillsTable() { - return sessionData.database.newSessionActiveSkillsTable - }, - get newSessionDisabledAgentToolsTable() { - return sessionData.database.newSessionDisabledAgentToolsTable - }, - get deepchatSearchDocumentsTable() { - return sessionData.database.deepchatSearchDocumentsTable - }, - get deepchatUserMessagesTable() { - return sessionData.database.deepchatUserMessagesTable - }, - get deepchatUserMessageFilesTable() { - return sessionData.database.deepchatUserMessageFilesTable - }, - get deepchatUserMessageLinksTable() { - return sessionData.database.deepchatUserMessageLinksTable - }, - get deepchatAssistantBlocksTable() { - return sessionData.database.deepchatAssistantBlocksTable - } - } - legacyChatImportService = new LegacyChatImportService( - appDatabase, - sessionData.database, - projectDatabase, - memoryDatabase, - sessionData.tapeStore, - undefined, - () => projectService.notifyEnvironmentProjectionChanged() - ) - usageStatsService = new UsageStatsService( - sessionData.database, - providerSettings, - dependencies.settingsStore, - sessionData.tapeStore - ) - const desktopSettings = new DesktopSettings( - dependencies.settingsStore, - { - refreshLanguage: () => { - floatingButtonPresenter.refreshLanguage() - void tabPresenter.refreshLanguage() - }, - refreshTheme: () => floatingButtonPresenter.refreshTheme() - }, - publishDeepchatEvent - ) - const fontSettings = new FontSettings(dependencies.settingsStore, publishDeepchatEvent) - const skillSettings = new SkillSettings(dependencies.settingsStore) - const traceSettings = new AgentTraceSettings(dependencies.settingsStore) - - const acpSessionPersistence = new AcpSessionPersistence( - agentDatabase, - sessionData.database, - projectDatabase, - () => projectService.notifyEnvironmentProjectionChanged() - ) - let providerRuntime!: ProviderRuntime - oauthService = new OAuthService( - { - getProviderById: (providerId) => providerSettings.getProviderById(providerId), - setProviderById: (providerId, provider) => - providerRuntime.setProviderById(providerId, provider) - }, - publishDeepchatEvent - ) - const agentSettings = new AgentSettings( - dependencies.settingsStore, - agentRepository, - dependencies.acpCatalogSettings, - () => dependencies.privacySettings.isEnabled(), - app.getPath('userData'), - { - getModelConfig: (modelId, providerId) => providerSettings.getModelConfig(modelId, providerId), - setAcpProviderEnabled: (enabled) => { - const provider = providerSettings.getProviderById('acp') - if (provider && provider.enable !== enabled) { - providerRuntime.updateProviderAtomic('acp', { enable: enabled }) - } + refreshTheme: (isDark) => floatingButtonPresenter.refreshTheme(isDark), + readLaunchAtLogin: async () => app.getLoginItemSettings().openAtLogin, + setLaunchAtLogin: async (enabled) => { + app.setLoginItemSettings({ openAtLogin: enabled }) + return app.getLoginItemSettings().openAtLogin + }, + applyContentProtection: async (enabled) => { + ;(windowPresenter as WindowPresenter).applyContentProtection(enabled) + desktopConfigurationHost.requestRestart() + }, + applyFloatingButtonEnabled: (enabled) => floatingButtonPresenter.setEnabled(enabled) }, - clearAcpProviderModels: () => - providerRuntime.replaceProviderCatalogFromSource('acp', [], 'acp'), - clearAcpProviderModelStatus: () => providerSettings.clearProviderModelStatusCache('acp'), - refreshAcpProviderAgents: async (agentIds) => { - const provider = providerRuntime.getProviderInstance('acp') - if (!(provider instanceof AcpProvider)) { - throw new Error('ACP provider is not initialized.') - } - await provider.refreshAgents(agentIds) - } - }, - { - publishCatalogChanged: (agentIds) => - emitAgentCatalogChanged(agentSettings, publishDeepchatEvent, agentIds), - publishAcpModelsChanged: () => { - emitModelsChanged(publishDeepchatEvent, 'acp') - emitAcpAgentModelsChanged(publishDeepchatEvent) + publishEvent: publishDeepchatEvent, + logging: desktopConfigurationHost, + enumerateFonts: () => desktopConfigurationHost.listSystemFonts(), + publishNativeSnapshot: (snapshot) => nativeSettings.apply(snapshot) + }) + const connection = await CoreRuntimeConnection.connect({ + runtime: harnessRuntime, + temporaryDirectory: app.getPath('temp'), + native: async (method, args) => { + if (method === 'configuration') return await configurationNative(method, args) + if (method === 'desktop' && nativeDesktop) return await nativeDesktop.invoke(args) + if (method === 'browser' && nativeBrowser) return await nativeBrowser.invoke(args) + if (method === 'helpers' && nativeHelpers) return await nativeHelpers.invoke(args) + return await profileNative(method, args) }, - publishSessionsUpdated: () => - publishDeepchatEvent('sessions.updated', { - sessionIds: [], - reason: 'list-refreshed' - }) - }, - (agentId) => memoryService.cleanupDeletedAgentResources(agentId), - async (agentId) => { - await ensureSkillServicesInitialized() - await skillService.cleanupAgentSkills(agentId) - }, - (agentId) => memoryService.onAgentMemoryMaintenanceConfigChanged(agentId), - skillSettings, - agentLifecycle - ) - const acpRuntimeOwner = createAcpRuntimeOwner({ - providerConfig: providerSettings, - agentSettings, - mcpSettings: dependencies.mcpSettings, - sessionPersistence: acpSessionPersistence, - titles: { - get: (sessionId) => appSessionService.get(sessionId)?.title, - apply: (sessionId, expected, title) => { - const session = appSessionService.get(sessionId) - if (!session || session.title !== expected) return false - appSessionService.update(sessionId, { title }) - publishDeepchatEvent('sessions.updated', { sessionIds: [sessionId], reason: 'updated' }) - return true - } - }, - publishEvent: publishDeepchatEvent, - registry: { - getNpmRegistry: () => mcpService.getNpmRegistry(), - getUvRegistry: () => mcpService.getUvRegistry() - } - }) - const acpAuthService = new AcpAuthService({ - owner: acpRuntimeOwner, - agentSettings, - sendToRenderer: (webContentsId, name, payload) => { - const target = electronWebContents.fromId(webContentsId) - if (!target || target.isDestroyed()) return - target.send(DEEPCHAT_EVENT_CHANNEL, createDeepchatEventEnvelope(name, payload)) - }, - onRendererDestroyed: (webContentsId, callback) => { - const target = electronWebContents.fromId(webContentsId) - if (!target || target.isDestroyed()) { - queueMicrotask(callback) - return () => {} + onFailure: (error) => { + if (appLifecycleState === 'starting' || appLifecycleState === 'stopped') return + reportNativeMainError('Core generation failed:', error) + void stopForCleanup().then( + () => app.exit(1), + (retirementError) => { + reportNativeMainError('Core failure retirement failed:', retirementError) + app.exit(1) + } + ) } - target.once('destroyed', callback) - return () => target.removeListener('destroyed', callback) - } - }) - providerRuntime = new ProviderRuntime( - providerSettings, - desktopSettings, - agentSettings, - dependencies.mcpSettings, - acpRuntimeOwner, - acpSessionPersistence, - publishDeepchatEvent - ) - cliComputeService = new CliComputeService({ - providerSettings, - providerRuntime, - artifactSpool, - mediaCacheDirectory: path.join(app.getPath('userData'), 'images'), - log: logger - }) - cliAudioTranscriptionService = new CliAudioTranscriptionService({ - providerSettings, - providerRuntime, - artifactSpool, - log: logger - }) - const agentDefaults = new DeepChatDefaults({ - settings: dependencies.settingsStore, - publishSettingChanged: (key, value) => - publishDeepchatEvent('settings.changed', { - changedKeys: [key], - version: Date.now(), - values: { [key]: value } - }) - }) - const commandShellService = new CommandShellService({ settings: dependencies.settingsStore }) - const unsubscribeProviderDbCatalog = providerDbLoader.subscribeCatalogChanges((change) => { - if (change.reason === 'updated') { - providerRuntime.handleProviderDbUpdated() - } - }) - acpProviderAdminPort = providerRuntime - acpAsLlmProviderSessionControl = providerRuntime - acpAsLlmProviderPermission = providerRuntime - const commandPermissionHandler = new CommandPermissionService() - const resolveCliDirectory = () => - resolveBundledCliDirectory({ + }) + core = connection + await connection.openProfile({ + userDataDirectory: app.getPath('userData'), + homeDirectory: app.getPath('home'), + currentAppVersion: app.getVersion(), + systemLocale: app.getLocale(), appPath: app.getAppPath(), - resourcesPath: process.resourcesPath, isPackaged: app.isPackaged }) - const cliLauncherService = new CliLauncherService({ - homeDirectory: app.getPath('home'), - userDataDirectory: app.getPath('userData'), - environmentPath: process.env.PATH, - shell: process.env.SHELL, - localAppDataDirectory: process.env.LOCALAPPDATA, - resolveCliDirectory - }) - const agentCliCommandAccess = new AgentCliCommandAccess({ - tokenAuthority: agentCliTokenAuthority, - commandPermission: commandPermissionHandler, - resolveCliDirectory - }) - commandPermissionService = commandPermissionHandler - filePermissionService = new FilePermissionService() - settingsPermissionService = new SettingsPermissionService() - approvalBroker = new ApprovalBroker({ log: logger }) - toolPermissionBroker = new ToolPermissionBroker({ approvalBroker }) - cliMutationGuard = new CliMutationGuard(approvalBroker, { - getTarget: async () => { - const focused = await semanticNotificationTargets.getFocusedTarget() - const target = - focused?.kind === 'main' - ? focused - : (await semanticNotificationTargets.getExistingTargets()).find( - (candidate) => candidate.kind === 'main' - ) - return target ? { windowId: target.windowId, webContentsId: target.webContentsId } : null - }, - present: async (target, payload) => { - const readyTarget = await semanticNotificationTargets.getTargetForWindow(target.windowId) - if ( - !readyTarget || - readyTarget.kind !== 'main' || - readyTarget.webContentsId !== target.webContentsId - ) { - return false + nativeSettings = new DesktopConfigurationProjection( + NativeConfigurationSnapshotSchema.parse( + await connection.configure({ operation: 'nativeSnapshot' }) + ), + () => nativeTheme.shouldUseDarkColors, + async (bounds) => { + await connection.configure({ operation: 'floatingBounds', bounds }) } - windowPresenter.show(target.windowId, true) - return await semanticNotificationTargets.sendDeepchatEvent( - readyTarget, - approvalRequestedEvent.name, - payload - ) - }, - close: async (target, payload) => { - const readyTarget = await semanticNotificationTargets.getTargetByWebContents( - target.webContentsId - ) - if (!readyTarget || readyTarget.kind !== 'main' || readyTarget.windowId !== target.windowId) { - return + ) + windowPresenter = new WindowPresenter( + { + getCloseToQuit: () => nativeSettings.getCloseToQuit(), + getContentProtectionEnabled: () => nativeSettings.getContentProtectionEnabled() + }, + dependencies.onWindowCreated, + async (action) => { + if (!isStopping()) await connection.host.observeSettingsWindow(action) } - await semanticNotificationTargets.sendDeepchatEvent( - readyTarget, - approvalClosedEvent.name, - payload - ) - } - }) - handleApprovalRendererUnavailable = (webContentsId) => { - cliMutationGuard.cancelRenderer(webContentsId) - } - cliRequestPolicy = new CliRequestPolicy({ - mutationGuard: cliMutationGuard, - audit: (record) => cliAuditLog.record(record) - }) - const liveDelegationConsent = new LiveDelegationConsentAuthority() - deviceService = new DeviceService() - const loggingService = new LoggingService( - dependencies.settingsStore, - () => { - restartApplication().catch((error) => logger.error('Application restart failed:', error)) - }, - publishDeepchatEvent, - setMainLoggingEnabled - ) - const rendererPerformanceLogService = new RendererPerformanceLogService( - dependencies.settingsStore - ) - projectService = new ProjectService( - projectDatabase, - sessionData.database, - deviceService, - dependencies.settingsStore, - (projectPath, version) => - publishDeepchatEvent('config.defaultProjectPath.changed', { - path: projectPath, - version - }), - (action, environmentPath, version) => - publishDeepchatEvent(projectEnvironmentsChangedEvent.name, { - action, - path: environmentPath, - version - }) - ) - const nowledgeMemConnections = new NowledgeMemConnections( - dependencies.settingsStore, - dependencies.secretStore, - dependencies.mcpSettings - ) - dependencies.mcpSettings.setPluginBindingResolver(NOWLEDGE_PLUGIN_ID, (config) => - nowledgeMemConnections.getMcpBindings(config) - ) - exporter = new ConversationExporterService({ - nowledgeMemConnections, - sqlitePresenter: sessionData.database, - settings: dependencies.settingsStore - }) - const updateSettings = new UpdateSettings(dependencies.settingsStore) - upgradeService = new UpgradeService( - updateSettings, - () => dependencies.privacySettings.isEnabled(), - dependencies.requestUpdateInstall, - publishDeepchatEvent, - (observation) => - mainLogger.emit('app.update.operation.failed', { - operation: observation.operation, - error: { category: observation.errorCategory } - }) - ) - shortcutPresenter = new ShortcutPresenter(desktopSettings, windowPresenter, publishDeepchatEvent) - fileService = new FileService(dependencies.settingsStore) - ocrSettings = new OcrSettings(dependencies.settingsStore, publishDeepchatEvent) - const toolchainHomeDir = app.getPath('home') - const toolchainService = ToolchainService.initialize({ - appPath: app.getAppPath(), - userDataDir: app.getPath('userData'), - env: mergeDetectionEnv(process.env, toolchainHomeDir, process.platform), - allowProbe: () => !dependencies.privacySettings.isEnabled(), - onProgress: (progress) => - publishDeepchatEvent('toolchains.progress', { ...progress, version: Date.now() }), - onMissing: (missing) => - publishDeepchatEvent('toolchains.missing', { missing, version: Date.now() }), - onStateChanged: (kind?: ToolchainKind) => { - publishDeepchatEvent('toolchains.changed', { version: Date.now() }) - ocrRuntimeService?.refreshAvailability(kind) - const state = ToolchainService.getInstance().getState() - if (state.node.source === 'unconfigured' && state.uv.source === 'unconfigured') { - return + ) + const semanticNotificationScheduler = new TimeoutNotificationScheduler() + const semanticNotificationEpisodes = new EpisodeRegistry( + systemNotificationClock, + semanticNotificationScheduler + ) + let handleSemanticRendererUnavailable = (_webContentsId: number): void => undefined + const semanticNotificationTargets = new ElectronWindowNotificationTargets( + windowPresenter, + () => tabPresenter, + (webContentsId) => { + handleSemanticRendererUnavailable(webContentsId) } - void mcpService?.retryUnstartedEnabledServers().catch((error) => { - logger.warn('[ToolchainService] Failed to retry MCP servers after PATH refresh', error) - }) + ) + const semanticNotificationDiagnostics = new AggregatedWindowNotificationDiagnostics({ + scheduler: semanticNotificationScheduler, + write: (event) => logger.warn('[NotificationRouter] delivery diagnostic', event) + }) + const semanticNotificationRouter = new WindowNotificationRouter({ + clock: systemNotificationClock, + scheduler: semanticNotificationScheduler, + episodes: semanticNotificationEpisodes, + targets: semanticNotificationTargets, + diagnostics: semanticNotificationDiagnostics + }) + handleSemanticRendererUnavailable = (webContentsId) => { + void semanticNotificationRouter + .availabilityChanged({ unavailableWebContentsIds: [webContentsId] }) + .catch((error) => { + logger.warn('[NotificationRouter] renderer invalidation failed', error) + }) } - }) - if (process.platform !== 'win32') { - void getShellEnvironment() - .then((shellEnv) => { - toolchainService.updateDetectionEnv( - mergeDetectionEnv(shellEnv, toolchainHomeDir, process.platform) - ) - }) - .catch((error) => { - logger.warn('[ToolchainService] Failed to refresh login-shell PATH', error) - }) - } - ocrRuntimeService = new OcrRuntimeService({ - appPath: app.getAppPath(), - isPackaged: app.isPackaged, - nodeRuntimePath: null, - resolveNode: () => { - const resolved = toolchainService.resolve('node', { purpose: 'ocr' }) - if (!resolved.version) { - throw new ToolchainResolutionError( - 'node', - 'version_mismatch', - 'OCR Node version is unavailable' - ) - } - return { executable: resolved.node, version: resolved.version } - }, - tempBaseDir: app.getPath('temp'), - userDataDir: app.getPath('userData') - }) - cliOcrService = new CliOcrService({ - appVersion: app.getVersion(), - ocrRuntime: ocrRuntimeService, - artifactSpool, - log: logger - }) - const attachmentRouter = new AttachmentCapabilityRouter({ - extraction: ocrRuntimeService, - getAutomaticOcrEnabled: () => ocrSettings.getAutomaticExtractionEnabled(), - getBackendPreference: () => ocrSettings.getBackend(), - getMaxFileSize: () => dependencies.settingsStore.get('maxFileSize') ?? 30 * 1024 * 1024, - onDiagnostic: (event) => { - if (traceSettings.isEnabled()) { - logger.info('[OCR] attachment representation resolved', event) + const semanticNotifications: SemanticNotificationPublisher = { + occur: (intent) => { + void semanticNotificationRouter.occur(intent).catch((error) => { + logger.warn('[NotificationRouter] occurrence failed', error) + }) + }, + recover: (intent) => { + void semanticNotificationRouter.recover(intent).catch((error) => { + logger.warn('[NotificationRouter] recovery failed', error) + }) } } - }) - const syncSettings = new SyncSettings( - dependencies.settingsStore, - dependencies.secretStore, - publishDeepchatEvent - ) - const hookSettings = new HookSettings(dependencies.settingsStore) - const knowledgeSettings = new KnowledgeSettings( - dependencies.settingsStore, - dependencies.mcpSettings - ) - syncService = new SyncService( - syncSettings, - mainDatabase, - settingsDatabase, - providerDatabase, - publishDeepchatEvent - ) - const syncTokenStorage = { - protectToken: (token: string) => { - if ( - !safeStorage.isEncryptionAvailable() || - (process.platform === 'linux' && safeStorage.getSelectedStorageBackend() === 'basic_text') - ) { - throw new Error('sync.error.safeStorageUnavailable') - } - return safeStorage.encryptString(token).toString('base64') - }, - revealToken: (wrapped: string) => safeStorage.decryptString(Buffer.from(wrapped, 'base64')) - } - syncPeerService = new SyncPeerService({ - directory: path.join(app.getPath('userData'), 'sync-peer'), - replicaId: () => syncReplica!.replicaId, - ...syncTokenStorage, - isLocalDatabaseEncrypted: () => Boolean(mainDatabase.getDatabasePassword()), - importSnapshot: (filePath, mode) => { - if (databaseMaintenanceState !== 'running') throw new Error('sync.tunnel.error.busy') - return runDatabaseMaintenance((database) => - syncService.importBackupFile(filePath, mode, database) - ) + const dialogService = new DialogService(publishDeepchatEvent) + const getDialogParent = () => { + const parent = windowPresenter.mainWindow + if (isStopping() || !parent || parent.isDestroyed()) + throw new Error('Native dialog owner is unavailable') + return parent } - }) - const syncAppliedSessions = new Set() - const hasPendingSyncSession = (id: string): boolean => - Boolean( - mainDatabase - .getDatabase() - .prepare( - "SELECT 1 FROM deepchat_pending_inputs WHERE session_id=? AND state != 'consumed' LIMIT 1" - ) - .get(id) + const deviceService = new DeviceService((options) => + dialogService.showOpenDialog(getDialogParent(), options) ) - syncReplica = new SyncReplicaStore({ - database: () => mainDatabase.getDatabase(), - directory: path.join(app.getPath('userData'), 'sync-replica'), - canApply: (kind, id) => - databaseMaintenanceState === 'running' && - (kind === 'session' - ? !syncActiveSessions.has(id) && - !hasPendingSyncSession(id) && - (syncAppliedSessions.has(id) || !sessionDeletionGate.hasActiveOperations(id)) - : syncActiveSessions.size === 0 && - !sessionDeletionGate.hasActiveOperationsOutside(syncAppliedSessions)), - guard: async (units, operation) => { - const ids = [ - ...new Set(units.filter((unit) => unit.kind === 'session').map((unit) => unit.id)) - ].sort() - const apply = async (index: number): Promise> => { - if (index === ids.length) return operation() - const id = ids[index] - if ( - syncActiveSessions.has(id) || - hasPendingSyncSession(id) || - sessionDeletionGate.hasActiveOperations(id) + const rendererPerformanceLogService = new RendererPerformanceLogService( + () => nativeSettings.getLoggingEnabled(), + app.getPath('userData') + ) + const resolveProjectDirectory = (name: 'documents' | 'home'): string | null => { + try { + return app.getPath(name) + } catch (error) { + console.warn( + `[ProjectService] Failed to resolve ${name === 'documents' ? 'Documents' : 'Home'} path:`, + error ) - return false - return sessionDeletionGate.runWithSessionDeletion(id, async () => { - if (syncActiveSessions.has(id)) return false - await deepChatAgentHarness.cleanupSession(id) - await acpAgentRuntime.cleanupSession(toAppSessionId(id)) - syncAppliedSessions.add(id) - try { - return await apply(index + 1) - } finally { - syncAppliedSessions.delete(id) - } - }) - } - return apply(0) - }, - applied: (kind, id, deleted) => { - if (kind === 'session') - publishDeepchatEvent('sessions.updated', { - sessionIds: [id], - reason: deleted ? 'deleted' : 'synced' - }) - if (kind === 'provider') { - providerSettings.invalidateSyncCaches() - providerRuntime.handleProviderAtomicUpdate({ - operation: 'remove', - providerId: id, - requiresRebuild: true - }) - const provider = providerSettings.getProviderById(id) - if (provider) - providerRuntime.handleProviderAtomicUpdate({ - operation: 'add', - providerId: id, - provider, - requiresRebuild: false - }) - publishDeepchatEvent('providers.changed', { - reason: 'providers', - providerIds: [id], - version: Date.now() - }) - publishDeepchatEvent('models.changed', { - reason: 'runtime-refresh', - providerId: id, - version: Date.now() - }) - } - if (kind === 'setting' && id === 'customPrompts') promptSettings.invalidateSyncCache() - if (kind === 'setting') { - const key = ( - [ - 'autoCompactionEnabled', - 'autoCompactionTriggerThreshold', - 'autoCompactionRetainRecentPairs', - 'copyWithCotEnabled' - ] as const - ).find((value) => value === id) - if (key) { - const values = { - autoCompactionEnabled: agentDefaults.getAutoCompactionEnabled(), - autoCompactionTriggerThreshold: agentDefaults.getAutoCompactionTriggerThreshold(), - autoCompactionRetainRecentPairs: agentDefaults.getAutoCompactionRetainRecentPairs(), - copyWithCotEnabled: desktopSettings.getCopyWithCotEnabled() - } - publishDeepchatEvent('settings.changed', { - changedKeys: [key], - version: Date.now(), - values: { [key]: values[key] } - }) - } - } - const refresh = async () => { - if (kind === 'agent') - await emitAgentCatalogChanged(agentSettings, publishDeepchatEvent, [id]) - if (kind === 'mcp') { - mcpService.handleConfigChanged() - publishDeepchatEvent('mcp.config.changed', { - mcpServers: await dependencies.mcpSettings.getMcpServers(), - mcpEnabled: await dependencies.mcpSettings.getMcpEnabled(), - version: Date.now() - }) - deepChatAgentHarness.refreshToolRegistry() - } - if (kind === 'setting' && id === 'customPrompts') - publishDeepchatEvent('config.customPrompts.changed', { - prompts: await promptSettings.getCustomPrompts(), - version: Date.now() - }) - if (kind === 'setting' && (id === 'systemPrompts' || id === 'default_system_prompt')) - await promptSettings.publishSystemPromptState() + return null } - void refresh().catch((error) => - logger.warn('[Sync] Could not refresh imported settings', error) - ) - publishDeepchatEvent('sync.device.changed', { version: Date.now() }) } - }) - const replicaEndpoint = new SyncReplicaEndpoint({ - store: syncReplica, - directory: path.join(app.getPath('userData'), 'sync-host'), - hostId: () => syncHostService.getHostId(), - available: () => - databaseMaintenanceState === 'running' && - !syncService.isBackupInProgress() && - !mainDatabase.getDatabasePassword() - }) - automaticSync = new AutomaticSync({ - store: syncReplica, - directory: path.join(app.getPath('userData'), 'sync-peer'), - connection: () => syncPeerService.connection(), - available: () => - databaseMaintenanceState === 'running' && - !syncService.isBackupInProgress() && - !mainDatabase.getDatabasePassword(), - isEncrypted: () => Boolean(mainDatabase.getDatabasePassword()), - changed: () => publishDeepchatEvent('sync.device.changed', { version: Date.now() }) - }) - syncPeerService.automatic = automaticSync - syncHostService = new SyncHostService({ - changed: () => publishDeepchatEvent('sync.device.changed', { version: Date.now() }), - replica: replicaEndpoint, - resolveCloudflared: () => toolchainService.resolve('cloudflared').cloudflared, - ...syncTokenStorage, - - createBackup: () => { - if (databaseMaintenanceState !== 'running') throw new Error('sync.tunnel.error.busy') - return syncService.createHostBackup() - }, - getFolderPath: () => syncSettings.getFolderPath(), - getUserDataPath: () => app.getPath('userData'), - getAppVersion: () => app.getVersion(), - logger - }) - notificationService = new NotificationService(desktopSettings, publishDeepchatEvent) - trayPresenter = new TrayPresenter(desktopSettings, windowPresenter) - dialogService = new DialogService(publishDeepchatEvent) - agentPreviewCoordinator = new AgentPreviewCoordinator() - yoBrowserPresenter = new YoBrowserPresenter( - windowPresenter, - publishDeepchatEvent, - agentPreviewCoordinator - ) - computerUsePreviewPresenter = new ComputerUsePreviewPresenter( - windowPresenter, - agentPreviewCoordinator - ) - - // Define the storage root for built-in knowledge databases. - const dbDir = path.join(app.getPath('userData'), 'app_db') - knowledgeService = new KnowledgeService({ - config: knowledgeSettings, - storageRoot: dbDir, - files: fileService, - dialog: dialogService, - embeddings: providerRuntime, - events: { - publishFileUpdated: (file) => - publishDeepchatEvent('knowledge.file.updated', { ...file, version: Date.now() }), - publishFileProgress: (fileId, progress) => - publishDeepchatEvent('knowledge.file.progress', { - fileId, - ...progress, - version: Date.now() + const upgradeService = new UpgradeService( + { getChannel: () => nativeSettings.getChannel() }, + () => nativeSettings.isPrivacyModeEnabled(), + dependencies.requestUpdateInstall, + publishDeepchatEvent, + (observation) => + mainLogger.emit('app.update.operation.failed', { + operation: observation.operation, + error: { category: observation.errorCategory } }) - } - }) - const pluginRuntimeSupervisor = new PluginRuntimeSupervisor() - mcpService = new McpService( - providerSettings, - agentSettings, - promptSettings, - dependencies.mcpSettings, - dependencies.privacySettings, - createInMemoryServerFactory({ - sqlitePresenter: sessionData.database, - sessions: appSessionService, - transcript: sessionData.transcript, - settings: sessionData.settings, - locale: desktopSettings, - promptSettings, - knowledgeSettings, - knowledgeService: knowledgeService - }), - providerRuntime, - () => deepChatAgentHarness.refreshToolRegistry(), - semanticNotifications, - publishDeepchatEvent, - (data, options) => deviceService.cacheImage(data, options), - pluginRuntimeSupervisor, - computerUsePreviewPresenter, - { - registry: dependencies.mcpAppSandboxRegistry, - permissionBroker: toolPermissionBroker, - getPermissionMode: async (conversationId) => - sessionData.settings.get(conversationId)?.permission_mode ?? 'default', - validateSource: (input) => { - const message = sessionData.database.deepchatMessagesTable.get(input.messageId) - return Boolean( - message?.session_id === input.conversationId && - sessionData.database.deepchatAssistantBlocksTable.matchesMcpAppSource( - input.messageId, - input.blockId, - input.descriptor, - input.toolInput - ) - ) - }, - persistModelContext: (messageId, blockId, descriptor, toolInput, context) => - sessionData.database.deepchatAssistantBlocksTable.updateMcpAppModelContext( - messageId, - blockId, - descriptor, - toolInput, - context - ) - }, - () => reportMainStartupComponentFailure(dependencies.startupRunId, 'mcp', 'unknown') - ) - await noteNodeDemandFromMcp(dependencies.mcpSettings, toolchainService).catch((error) => { - logger.warn('[ToolchainService] Failed to note MCP Node demand', error) - }) - const deeplinkActions = createDeeplinkActions({ - window: windowPresenter, - config: providerSettings, - mcp: mcpService, - notifications: semanticNotifications - }) - deeplinkService = new DeeplinkService( - deeplinkActions.desktop, - deeplinkActions.mcp, - deeplinkActions.provider - ) - - // Initialize generic Workspace presenter (for all Agent modes) - workspaceService = new WorkspaceService(fileService, fileWatcherService, { - publishInvalidated: (event) => publishDeepchatEvent('workspace.invalidated', event), - publishWatchStatusChanged: (event) => - publishDeepchatEvent('workspace.watch.status.changed', event) - }) - - const skillSessionStatePort: SkillSessionStatePort = { - hasNewSession: async (conversationId) => Boolean(await sessionQuery.getSession(conversationId)), - getPersistedNewSessionSkills: (conversationId) => - sessionData.database.newSessionsTable.getActiveSkills(conversationId), - setPersistedNewSessionSkills: (conversationId, skills) => { - sessionData.database.newSessionsTable.updateActiveSkills(conversationId, skills) - projectDatabase.newEnvironmentsTable.syncForSession(conversationId) - projectService.notifyEnvironmentProjectionChanged() - }, - repairImportedLegacySessionSkills: async (conversationId) => { - return await legacyChatImportService.repairImportedLegacySessionSkills(conversationId) - } - } - skillService = new SkillService( - skillSettings, - skillSessionStatePort, - fileWatcherService, - publishDeepchatEvent, - { - isDeepChatAgent: async (agentId) => - (await agentSettings.getAgent(agentId))?.type === 'deepchat', - listDeepChatAgents: async () => - (await agentSettings.listAgents()) - .filter((agent) => agent.type === 'deepchat') - .map((agent) => ({ - id: agent.id, - enabledSkillNames: agent.config?.enabledSkillNames, - protected: agent.protected - })), - getSessionAgentId: async (sessionId) => - (await sessionQuery.getSession(sessionId))?.agentId ?? null, - getSessionProjectDir: async (sessionId) => - (await sessionQuery.getSession(sessionId))?.projectDir ?? null, - listSessions: async () => - (await sessionQuery.listSessions({ includeSubagents: true })).map((session) => ({ - id: session.id, - agentId: session.agentId - })) - } - ) - cliSkillService = new CliSkillService({ - skills: skillService, - agentExists: async (agentId) => (await agentSettings.getAgent(agentId))?.type === 'deepchat', - recordSettingsActivity: (input) => { - void settingsDatabase.recordSettingsActivity(input).catch((error) => { - logger.warn('[SettingsActivity] Failed to record CLI Skill activity:', error) - }) - }, - log: logger - }) + ) + const desktopImageActions = new DesktopImageActions(app.getPath('userData'), (options) => + dialogService.showSaveDialog(getDialogParent(), options) + ) + const shortcutPresenter = new ShortcutPresenter( + nativeSettings, + windowPresenter, + publishDeepchatEvent + ) + const notificationService = new NotificationService(nativeSettings, publishDeepchatEvent) + const trayPresenter = new TrayPresenter(nativeSettings, windowPresenter) + const agentPreviewCoordinator = new AgentPreviewCoordinator() + const yoBrowserPresenter = new YoBrowserPresenter( + windowPresenter, + publishDeepchatEvent, + agentPreviewCoordinator + ) + const computerUsePreviewPresenter = new ComputerUsePreviewPresenter( + windowPresenter, + agentPreviewCoordinator + ) - const agentInvocationAdmission = new AgentInvocationAdmission(undefined, undefined, { - observationsEnabled: () => mainLogger.isOutputEnabled(), - observe: (observation) => { - switch (observation.type) { - case 'queued': - mainLogger.emit('agent.admission.queued', observation) - break - case 'granted': - mainLogger.emit('agent.admission.granted', observation) - break - case 'released': - mainLogger.emit('agent.admission.released', observation) - break - case 'rejected': - mainLogger.emit('agent.admission.rejected', observation) - break - case 'closed': - mainLogger.emit('agent.admission.closed', observation) - break - } - } - }) - const resolveConversationExecutionAuthorityNow = (conversationId: string) => { - const session = appSessionService.get(conversationId) - if (!session) return null - const resolveAgentConfig = (agentId: string) => { - const config = agentRepository.resolveDeepChatAgentConfig(agentId) - return { - ...config, - disabledAgentTools: Array.isArray(config.disabledAgentTools) - ? config.disabledAgentTools - : [...DEFAULT_DISABLED_AGENT_TOOLS] - } - } - const agentType = agentRepository.getAgentType(session.agentId) - const agentConfig = resolveAgentConfig(session.agentId) - const persistedDisabledAgentTools = appSessionService.getDisabledAgentTools(session.id) - let authority = composeSubagentAuthority({ - disabledAgentTools: persistedDisabledAgentTools, - enabledMcpServerIds: agentConfig.enabledMcpServerIds + const deeplinkActions = createDeeplinkActions({ + window: windowPresenter, + hasProvider: (providerId) => connection.host.hasProvider(providerId), + notifications: semanticNotifications }) - if (session.sessionKind === 'subagent') { - const parentSessionId = session.parentSessionId?.trim() - const parent = parentSessionId ? appSessionService.get(parentSessionId) : null - if (!parent || parent.sessionKind !== 'regular') { - throw new Error(`Subagent Session ${session.id} has no resolvable parent tool policy.`) - } - authority = composeSubagentAuthority( - { disabledAgentTools: persistedDisabledAgentTools }, - { disabledAgentTools: appSessionService.getDisabledAgentTools(parent.id) }, - resolveAgentConfig(parent.agentId), - agentConfig - ) - } - return { - sessionId: session.id, - agentId: session.agentId, - projectDir: session.projectDir ?? null, - sessionKind: session.sessionKind, - disabledAgentTools: authority.disabledAgentTools, - enabledMcpServerIds: authority.enabledMcpServerIds, - subagentCapability: resolveDeepChatSubagentCapability({ - agentType, - sessionKind: session.sessionKind, - agentPolicyEnabled: agentConfig.subagentEnabled === true, - slots: normalizeDeepChatSubagentSlots(agentConfig.subagents) - }) - } - } - const agentToolDependencies: AgentToolDependencies = { - readSession: (callerSessionId, input) => - sessionData.sessionReferences.read(callerSessionId, input), - agentInvocationAdmission, - sessions: { - resolveConversationWorkdir: async (conversationId) => { - try { - const session = await sessionQuery.getSession(conversationId) - const normalized = session?.projectDir?.trim() - if (normalized) { - return normalized - } - } catch (error) { - console.warn('[Main] Failed to resolve new session workdir:', { - conversationId, - error - }) - } - - return null - }, - resolveConversationExecutionAuthorityNow, - resolveConversationExecutionAuthority: async (conversationId) => - resolveConversationExecutionAuthorityNow(conversationId), - resolveConversationSessionInfo: async (conversationId) => { - const session = await sessionQuery.getSession(conversationId) - if (!session) { - return null - } - - const agent = await agentSettings.getAgent(session.agentId) - const agentType = await agentSettings.getAgentType(session.agentId) - const permissionMode = await sessionAssignment.getPermissionMode(session.id) - const generationSettings = await sessionAssignment.getSessionGenerationSettings(session.id) - const disabledAgentTools = await sessionAssignment.getSessionDisabledAgentTools(session.id) - const activeSkills = await skillService.getActiveSkills(session.id) - const agentConfig = await agentSettings.resolveDeepChatAgentConfig(session.agentId) - const availableSubagentSlots = normalizeDeepChatSubagentSlots(agentConfig.subagents) - const subagentCapability = resolveDeepChatSubagentCapability({ - agentType, - sessionKind: session.sessionKind, - agentPolicyEnabled: agentConfig.subagentEnabled === true, - slots: availableSubagentSlots - }) - - return { - sessionId: session.id, - agentId: session.agentId, - agentName: agent?.name?.trim() || session.agentId, - agentType, - providerId: session.providerId, - modelId: session.modelId, - projectDir: session.projectDir ?? null, - permissionMode, - orchestrationPolicy: session.orchestrationPolicy, - toolModeOverride: session.toolModeOverride ?? null, - generationSettings, - disabledAgentTools, - activeSkills, - sessionKind: session.sessionKind, - parentSessionId: session.parentSessionId ?? null, - subagentMeta: session.subagentMeta ?? null, - subagentCapability, - status: session.status - } - } - }, - tape: { - searchTape: async (conversationId, query, options) => { - return await sessionQuery.searchTape(conversationId, query, options) - }, - getTapeContext: async (conversationId, entryIds, options) => { - return await sessionQuery.getTapeContext(conversationId, entryIds, options) - } - }, - memory: { - isMemoryEnabled: (agentId) => memoryService.isEnabled(agentId), - rememberMemory: async (agentId, input, sourceSession, model, beforeMutation) => - memoryService.rememberMemory( - { - kind: input.kind, - category: input.category, - content: input.content, - importance: input.importance - }, - { agentId, sourceSession }, - model, - beforeMutation - ), - recallMemory: async (agentId, query, scopeContext) => { - const items = await memoryService.recall(agentId, query, undefined, scopeContext) - return items.map((item) => ({ - id: item.id, - kind: item.kind, - content: item.content - })) - }, - forgetMemory: async (agentId, memoryId, beforeMutation) => - await memoryService.forgetMemory(agentId, memoryId, beforeMutation) - }, - cronJobs: { - listCronJobs: async () => await cronJobs.list(), - upsertCronJob: async (input, beforeMutation) => - (await cronJobs.upsert(input, beforeMutation)).job, - deleteCronJob: async (id, beforeMutation) => { - await cronJobs.delete(id, beforeMutation) - }, - toggleCronJob: async (id, enabled, beforeMutation) => - (await cronJobs.toggle(id, enabled, beforeMutation)).job, - runCronJobNow: async (id, beforeMutation) => (await cronJobs.runNow(id, beforeMutation)).run, - listCronJobRuns: async (jobId, limit) => cronJobs.listRuns(jobId, limit), - previewCronSchedule: async (input) => cronJobs.previewSchedule(input) - }, - subagents: { - createSubagentSession: async (input) => { - const created = await sessionLifecycle.createSubagentSession(input) - return await agentToolDependencies.sessions.resolveConversationSessionInfo(created.id) - }, - linkSubagentTape: async (input) => await sessionAssignment.linkSubagentTape(input), - sendConversationMessage: async (conversationId, content) => { - await sessionTurn.sendMessage(conversationId, content) - }, - cancelConversation: async (conversationId) => { - await sessionTurn.cancelGeneration(conversationId) - }, - subscribeSessionRuntimeUpdates: (listener) => sessionRuntimeEvents.subscribe(listener) - }, - liveDelegation: createLivePort(() => liveDelegationService), - skills: skillService, - skillExecutionAuthority: new SkillExecutionAuthorityResolver({ - tape: createSkillExecutionAuthorityTapePort(sessionData.tapeStore), - environments: skillService - }), - browser: yoBrowserPresenter.toolHandler, - files: { - getMimeType: (filePath) => fileService.getMimeType(filePath), - prepareFileCompletely: (absPath, typeInfo, contentType) => - fileService.prepareFileCompletely(absPath, typeInfo, contentType) - }, - provider: { - executeWithRateLimit: (providerId, options) => - providerRuntime.executeWithRateLimit(providerId, options), - generateCompletionStandalone: ( - providerId, - messages, - modelId, - temperature, - maxTokens, - options - ) => - providerRuntime.generateCompletionStandalone( - providerId, - messages, - modelId, - temperature, - maxTokens, - options - ), - generateImageStandalone: (providerId, prompt, modelId, imageOptions, options) => - providerRuntime.generateImageStandalone(providerId, prompt, modelId, imageOptions, options) - }, - cacheImage: (data, options) => deviceService.cacheImage(data, options), - desktop: { - createSettingsWindow: () => windowPresenter.createSettingsWindow(), - sendToWindow: (windowId, channel, ...args) => - windowPresenter.sendToWindow(windowId, channel, ...args), - sendSettingsNavigation: (windowId, navigation) => - windowPresenter.sendSettingsNavigation(windowId, navigation) - }, - permissions: { - getApprovedFilePaths: (conversationId, requiredPermission, provisionalLeaseId) => - filePermissionService.getApprovedPaths( - conversationId, - requiredPermission, - provisionalLeaseId - ), - consumeSettingsApproval: (conversationId, toolName, provisionalLeaseId) => - settingsPermissionService.consumeApproval(conversationId, toolName, provisionalLeaseId) - } - } - - // Initialize the merged MCP and built-in Tool service. - toolService = new ToolService({ - mcpService: mcpService, - providerSettings: providerSettings, - settings: dependencies.settingsStore, - agentSettings, - skillSettings, - desktopSettings, - commandPermissionHandler, - commandEnvironment: agentCliCommandAccess, - permissionBroker: toolPermissionBroker, - liveDelegationConsent, - agentTools: agentToolDependencies, - effectObserver: { - beforeToolAuthorization: async (observation, signal) => { - const permissionMode = await liveDelegationService.beforeToolAuthorization( - observation, - signal - ) - return permissionMode ? { permissionMode } : null - }, - beforeToolExecution: async (observation, signal) => { - await liveDelegationService.beforeToolExecution(observation, signal) - } - } - }) - - // Plugin activation is a shared startup barrier for Skill migration and MCP startup. - const pluginSettingsWindow = new PluginSettingsWindow() - pluginService = new PluginService({ - nowledgeMem: nowledgeMemConnections, - exportNowledgeSession: (input) => agentSessionExportService.submitToNowledgeMem(input), - contextTape: sessionData.tapeStore, - mcpSettings: dependencies.mcpSettings, - mcpService: mcpService, - skillService: skillService, - settingsWindow: pluginSettingsWindow, - runtimeSupervisor: pluginRuntimeSupervisor - }) - - // Initialize Skill Sync service - skillSyncService = new SkillSyncService(skillService, skillSettings, publishDeepchatEvent) - - hookService = new HookService(hookSettings, { - getSession: (sessionId) => sessionQuery.getSession(sessionId) - }) - const providerCatalogPort: ProviderCatalogPort = { - getProviderModels: (providerId) => providerSettings.getProviderModels(providerId), - getCustomModels: (providerId) => providerSettings.getCustomModels(providerId), - getAgentType: async (agentId) => await agentSettings.getAgentType(agentId) - } - const sessionUiPort: SessionUiPort = { - refreshSessionUi: () => { - try { - void floatingButtonPresenter.refreshWidgetState() - } catch (error) { - console.warn('[Main] Failed to refresh floating widget state:', error) - } - } - } - sessionPermissionPort = createSessionPermissionPort({ - agentCliTokenAuthority, - commandPermissionService, - filePermissionService, - settingsPermissionService, - toolPermissionBroker - }) - // Initialize agent memory layer (opt-in per agent; vectors stored separately from knowledge base) - const memoryDbDir = path.join(dbDir, 'AgentMemory') - MemoryVectorStore.recoverQuarantinedStores(memoryDbDir) - const memoryVectorDbPaths = (agentId: string) => - createMemoryVectorStorePaths(memoryDbDir, agentId) - memoryService = new MemoryService({ - repository: createLivePort(() => memoryDatabase.agentMemoryTable), - directiveRepository: createLivePort(() => memoryDatabase.agentMemoryDirectiveTable), - auditRepository: createLivePort(() => memoryDatabase.agentMemoryAuditTable), - resolveAgentConfig: (agentId) => agentRepository.resolveDeepChatAgentConfig(agentId), - resolveAgentDefaultModel: (agentId) => { - const config = agentRepository.resolveDeepChatAgentConfig(agentId) - const model = config.assistantModel ?? config.defaultModelPreset - return model?.providerId && model?.modelId - ? { providerId: model.providerId, modelId: model.modelId } - : null - }, - // Management memory APIs only read/write real DeepChat agents. - isManagedAgent: (agentId) => agentRepository.getDeepChatAgentConfig(agentId) !== null, - listManagedAgentConfigs: () => agentRepository.listResolvedDeepChatAgentConfigs(), - listManagedMemoryAgentIds: () => - agentRepository - .listAgents({ agentType: 'deepchat', enabled: true }) - .map((agent) => agent.id) - .filter( - (agentId) => agentRepository.resolveDeepChatAgentConfig(agentId).memoryEnabled === true - ), - ...createMemoryProviderBindings(providerRuntime), - createVectorStore: (agentId, embedding, dimensions) => { - if (!isSafeAgentId(agentId)) { - throw new Error(`[Memory] refusing to open vector store for unsafe agentId: ${agentId}`) - } - return MemoryVectorStore.create(memoryVectorDbPaths(agentId), dimensions, embedding) - }, - resetVectorStore: async (agentId) => { - if (!isSafeAgentId(agentId)) { - throw new Error(`[Memory] refusing to reset vector store for unsafe agentId: ${agentId}`) - } - MemoryVectorStore.destroyFiles(memoryVectorDbPaths(agentId)) - }, - markVectorStoreQuarantined: (agentId) => { - if (!isSafeAgentId(agentId)) { - throw new Error( - `[Memory] refusing to quarantine vector store for unsafe agentId: ${agentId}` - ) - } - MemoryVectorStore.markQuarantined(memoryVectorDbPaths(agentId)) - }, - onMemoryChanged: (agentId, reason, context) => - publishDeepchatEvent('memory.updated', { - agentId, - reason, - version: Date.now(), - ...(typeof context?.memoryId === 'string' ? { memoryId: context.memoryId } : {}), - ...(typeof context?.directiveId === 'string' ? { directiveId: context.directiveId } : {}), - ...(typeof context?.sessionId === 'string' ? { sessionId: context.sessionId } : {}), - ...(context?.createdIds?.length ? { createdIds: context.createdIds } : {}) - }) - }) - agentSettings.start() + const deeplinkService = new DeeplinkService( + deeplinkActions.desktop, + deeplinkActions.mcp, + deeplinkActions.provider + ) - // Initialize new agent architecture presenters - deepChatAgentHarness = createDeepChatAgentHarness({ - providerRuntime, - providerSettings, - agentSettings, - database: sessionData.database, - sessionData, - toolService, - hookObserver: hookService, - pluginContext: pluginService.contextHooks, - onSessionCompleted: (sessionId) => { - const session = appSessionService.get(sessionId) - if (session?.sessionKind !== 'regular' || resolveSessionRunId(sessionId) !== null) return - void notificationService.showSessionCompletion(session).catch((error) => { - logger.warn('[Notification] Failed to notify session completion', { sessionId, error }) - }) - }, - publishEvent: publishDeepchatEvent, - publishSessionUpdate: (update) => { - sessionRuntimeEvents.publish(update) - if (update.kind === 'status') { - if (update.status === 'generating') syncActiveSessions.add(update.sessionId) - else { - syncActiveSessions.delete(update.sessionId) - syncReplica?.wake() - } - } - if (update.kind === 'status' && (update.status === 'idle' || update.status === 'error')) { - void yoBrowserPresenter.releaseInactivePreview(update.sessionId).catch((error) => { - logger.warn('[YoBrowser] Failed to release inactive preview', { - sessionId: update.sessionId, - error - }) - }) + const pluginSettingsWindow = new PluginSettingsWindow() + const remoteAuthWindows = new DesktopRemoteAuthWindows((event) => + nativeDesktop!.publishRemoteAuthWindow(event) + ) + tabPresenter = new TabPresenter( + windowPresenter, + () => deeplinkService.processStartupUrl(), + (parent, options) => dialogService.showSaveDialog(parent, options) + ) + ;(windowPresenter as WindowPresenter).bindTabPresenter(tabPresenter) + floatingButtonPresenter = new FloatingButtonPresenter( + nativeSettings, + windowPresenter as WindowPresenter, + tabPresenter, + { + getSnapshot: async (context, assertActive, signal) => + sessionsGetWidgetSnapshotRoute.output.parse( + await invokeWidgetRoute( + sessionsGetWidgetSnapshotRoute.name, + {}, + context, + assertActive, + signal + ) + ).snapshot, + getRegistryIconMarkup: async (agentId, iconUrl, context, assertActive, signal) => + configGetAcpRegistryIconMarkupRoute.output.parse( + await invokeWidgetRoute( + configGetAcpRegistryIconMarkupRoute.name, + { agentId, iconUrl }, + context, + assertActive, + signal + ) + ).markup, + openSession: async (sessionId, context, assertActive, signal) => + sessionsOpenDesktopRoute.output.parse( + await invokeWidgetRoute( + sessionsOpenDesktopRoute.name, + { sessionId }, + context, + assertActive, + signal + ) + ).opened } - }, - providerCatalogPort, - sessionPermissionPort, - acpAsLlmProviderPermission: acpAsLlmProviderPermission, - sessionUiPort, - memoryPort: memoryService, - getMemoryIngestionProjection: () => memoryDatabase.ingestionProjectionTable, - cacheImage: (data, options) => deviceService.cacheImage(data, options), - runJournalObserver: emitRunJournalObservation, - skillService: skillService, - skillSettings, - traceSettings, - commandShell: commandShellService, - promptSettings, - attachmentRouter, - interactionContinuationAdmission: { - resume: async (sessionId, signal) => - await liveDelegationService.beforeInteractionContinuation(sessionId, signal), - suspend: (sessionId) => liveDelegationService.suspendInteractionContinuation(sessionId) - }, - taskContractContext: { - prepare: (sessionId) => liveDelegationService.prepareTaskContractContext(sessionId) - }, - toolSurfaceRunMode: toolSurfaceRollout, - agentCliTokenAuthority, - programmaticToolParents - }) - const sessionTranscriptMutations = new SessionTranscriptMutations({ - transcript: sessionData.transcript, - settings: sessionData.settings, - pendingInputs: sessionData.pendingInputs, - runtime: deepChatAgentHarness, - runInTransaction: (operation) => sessionData.database.getDatabase().transaction(operation)() - }) - memoryIngestionObserver = deepChatAgentHarness.memoryIngestionObserver - acpAgentRuntime = new AcpAgentRuntime( - acpRuntimeOwner, - (input) => deepChatAgentHarness.createAcpAgentInstanceDependencies(input), - sessionData.pendingInputs - ) - agentManager = new AgentManager(agentRepository, appSessionService, { - deepchat: createDeepChatAgentBackend({ - port: deepChatAgentHarness, - runtime: deepChatAgentHarness.deepChatRuntime, - transcript: sessionData.transcript, - tape: sessionData.tape - }), - acp: createDirectAcpAgentBackend({ - runtime: acpAgentRuntime, - sessionState: deepChatAgentHarness, - transcript: sessionData.transcript, - tape: sessionData.tape, - deleteDurableSession: async (sessionId) => { - await acpSessionPersistence.deleteSessions(sessionId) + ) + async function invokeWidgetRoute( + routeName: + | 'sessions.getWidgetSnapshot' + | 'sessions.openDesktop' + | 'config.getAcpRegistryIconMarkup', + input: unknown, + context: RouteContext, + assertActive: () => void, + signal: AbortSignal + ): Promise { + assertActive() + const owner = await awaitWithAbort(widgetRoutesReady.promise, signal) + assertActive() + return await owner.invokeRoute(routeName, input, context, assertActive) + } + const kernelNative: CoreKernelNative = { + workspaceDesktop: { + revealFile: async (filePath) => shell.showItemInFolder(filePath), + openFile: (filePath) => shell.openPath(filePath), + listFileOpenApps, + openFileWithApp }, - resolveInput: async (sessionId, descriptor) => { - const session = appSessionService.get(sessionId) - if (!session || resolveAcpAgentAlias(session.agentId) !== descriptor.id) { - throw new AgentUnavailableError(descriptor.id, 'invalid-config', 'acp') - } - const agent = (await agentSettings.getAcpAgents()).find( - (candidate) => candidate.id === descriptor.id && candidate.source === descriptor.source - ) - if (!agent || !agent.command.trim()) { - throw new AgentUnavailableError(descriptor.id, 'invalid-config', 'acp') - } - return { - sessionId: toAppSessionId(session.id), - descriptor, - agent, - scope: session.sessionKind === 'subagent' ? 'subagent' : 'regular', - workdir: session.projectDir?.trim() ?? '' - } - } - }) - }) - sessionQuery = new SessionQuery({ - sessions: appSessionService, - runtime: { - getAgentKind: (agentId) => agentManager.resolveBackend(agentId).kind, - snapshot: async (sessionId, options) => - await agentManager.resolveSessionHandle(toAppSessionId(sessionId)).handle.snapshot(options), - snapshotIfHydrated: async (sessionId) => - await agentManager.snapshotIfHydrated(toAppSessionId(sessionId)), - waitForFirstTurnReady: async (sessionId, options) => - await agentManager - .resolveSessionHandle(toAppSessionId(sessionId)) - .handle.waitForFirstTurnReady(options) - }, - transcript: sessionData.transcript, - tape: sessionData.tape, - messages: createLivePort(() => sessionData.database.deepchatMessagesTable), - searchResults: createLivePort(() => sessionData.database.deepchatMessageSearchResultsTable), - traces: createLivePort(() => sessionData.database.deepchatMessageTracesTable), - usesAgentTitle: (sessionId) => - !!acpRuntimeOwner.peek()?.sessionController.getSession(toAppSessionId(sessionId))?.extensions - ?.sessionTitle, - onTitleRenamed: async (sessionId, agentId) => { - if (agentManager.resolveBackend(agentId).kind === 'acp') - await acpRuntimeOwner - .getOrCreate() - .sessionController.markTitleManual(toAppSessionId(sessionId), agentId) - }, - titles: providerRuntime, - agentConfig: { - getAssistantModel: async (agentId) => { - const selection = await resolveAssistantModelSelection( - { - agentManager: agentManager, - agentSettings - }, - agentId, - '', - '' - ) - return selection.providerId && selection.modelId ? selection : null - } - }, - events: { - publish: (payload) => publishDeepchatEvent('sessions.updated', payload) - }, - ui: sessionUiPort - }) - desktopSessionBinding = new DesktopSessionBinding(sessionQuery) - resolveBoundRendererIds = (sessionId) => - desktopSessionBinding.getWebContentsIdsForSession(sessionId) - tabPresenter = new TabPresenter(windowPresenter, desktopSessionBinding, () => - deeplinkService.processStartupUrl() - ) - ;(windowPresenter as WindowPresenter).bindTabPresenter(tabPresenter) - floatingButtonPresenter = new FloatingButtonPresenter( - agentSettings, - desktopSettings, - sessionQuery, - desktopSessionBinding, - windowPresenter as WindowPresenter, - tabPresenter - ) - sessionAssignmentPolicy = new SessionAssignmentPolicy( - { - resolveAgent: (agentId) => { - const descriptor = agentManager.resolveBackend(agentId).descriptor - return { id: descriptor.id, kind: descriptor.kind } - } - }, - { - getDefaultModel: () => agentSettings.getDefaultModel(), - getDefaultProjectPath: () => projectService.getDefaultProjectPath(), - resolveDeepChatAgentConfig: async (agentId) => - await agentSettings.resolveDeepChatAgentConfig(agentId), - isModelAvailable: (providerId, modelId) => - providerSettings.getProviderById(providerId)?.enable === true && - providerSettings.isKnownModel(providerId, modelId) && - providerSettings.getModelStatus(providerId, modelId) - } - ) - const clearNewAgentSessionSkills = skillService.clearNewAgentSessionSkills - if (!clearNewAgentSessionSkills) { - throw new Error('Skill presenter must provide session skill cleanup.') - } - sessionDeletion = new SessionDeletion({ - sessions: appSessionService, - gate: sessionDeletionGate, - orchestration: { - prepareSessionDeletion: async (sessionId) => - await liveDelegationService.prepareSessionDeletion(sessionId) - }, - runtime: { - cleanupSessionBackends: async (sessionId) => - await agentManager.cleanupSessionBackends(sessionId), - destroySessionBrowser: async (sessionId) => - await yoBrowserPresenter.destroySessionBrowser(sessionId) - }, - state: deepChatAgentHarness, - permissions: sessionPermissionPort, - skills: { - clearNewAgentSessionSkills: async (sessionId) => - await clearNewAgentSessionSkills.call(skillService, sessionId), - completeNewAgentSessionSkillsDeletion: (sessionId) => - skillService.completeNewAgentSessionSkillsDeletion(sessionId) - } - }) - sessionAssignment = new SessionAssignment({ - sessions: appSessionService, - runtime: { - getSessionAgentKind: (sessionId) => - agentManager.resolveSessionBackend(sessionId).descriptor.kind, - resolveSession: (sessionId) => agentManager.resolveSessionHandle(sessionId), - resolveTransferSource: (sessionId) => agentManager.resolveTransferSource(sessionId), - resolveDeepChatTransferTarget: (agentId) => - agentManager.resolveDeepChatTransferTarget(agentId), - resolveSubagentFacet: (sessionId) => agentManager.resolveSubagentFacet(sessionId) - }, - policy: sessionAssignmentPolicy, - projection: sessionQuery, - deletion: sessionDeletion, - environment: { - syncPath: (projectDir) => { - projectDatabase.newEnvironmentsTable.syncPath(projectDir) - projectService.notifyEnvironmentProjectionChanged() - } - }, - acp: acpAsLlmProviderSessionControl, - agentLifecycle - }) - sessionTurn = new SessionTurn({ - sessions: appSessionService, - runtime: { - resolveSession: (sessionId) => { - const { handle } = agentManager.resolveSessionHandle(sessionId) - const turn = { - pending: handle.pending, - toolInteractions: handle.toolInteractions, - send: (input: Parameters[0]) => handle.send(input), - cancel: () => handle.cancel(), - snapshot: () => handle.snapshot() - } - return handle.kind === 'deepchat' - ? { - ...turn, - kind: handle.kind, - compaction: { - getSnapshot: () => handle.deepchat.getCompactionSnapshot(), - compact: () => handle.deepchat.compact() - }, - getContextOccupancy: () => handle.deepchat.getContextOccupancy(), - isPendingQueueResumeAvailable: () => handle.deepchat.isPendingQueueResumeAvailable(), - resumePendingQueue: () => handle.deepchat.resumePendingQueue(), - retryPendingQueueInput: (itemId) => handle.deepchat.retryPendingQueueInput(itemId) - } - : { ...turn, kind: handle.kind } - } - }, - transcript: { - hasMessages: (sessionId) => sessionData.transcript.hasMessages(sessionId), - clearMessages: (sessionId) => sessionTranscriptMutations.clearMessages(sessionId), - prepareRetryMessage: (sessionId, messageId) => - sessionTranscriptMutations.prepareRetryMessage(sessionId, messageId), - commitRetryMessage: (sessionId, sourceOrderSeq) => - sessionTranscriptMutations.commitRetryMessage(sessionId, sourceOrderSeq), - deleteMessage: (sessionId, messageId) => - sessionTranscriptMutations.deleteMessage(sessionId, messageId), - editUserMessage: (sessionId, messageId, text, inlineItems) => - sessionTranscriptMutations.editUserMessage(sessionId, messageId, text, inlineItems) - }, - workdir: sessionAssignment, - projection: sessionQuery - }) - sessionLifecycle = new SessionLifecycle({ - onRendererDestroyed: (webContentsId, callback) => { - const target = electronWebContents.fromId(webContentsId) - if (!target || target.isDestroyed()) { - queueMicrotask(callback) - return () => {} - } - target.once('destroyed', callback) - return () => target.removeListener('destroyed', callback) - }, - forkAcpSession: async (sourceId, targetId, messageId) => { - const message = sessionData.transcript.getMessage(messageId) - if ( - !message || - message.sessionId !== sourceId || - message.role !== 'assistant' || - message.status !== 'sent' - ) - throw new Error('Fork requires a completed ACP assistant message') - const metadata = JSON.parse(message.metadata) as { acp?: { turnId?: string } } - const blocks = JSON.parse(message.content) as AssistantMessageBlock[] - const anchors = blocks.flatMap((block) => { - try { - const meta = JSON.parse(String(block.extra?.providerOptionsJson ?? '{}')) - return typeof meta.acp?.turnId === 'string' ? [meta.acp.turnId] : [] - } catch { - return [] - } - }) - const turnId = anchors.at(-1) ?? metadata.acp?.turnId - if (!turnId) throw new Error('This message has no verified remote turn anchor') - const result = await acpAgentRuntime.forkSession( - toAppSessionId(sourceId), - toAppSessionId(targetId), - turnId, - messageId - ) - sessionData.transcript.importAcpHistory( - targetId, - result.agentId, - result.remoteSessionId, - result.history - ) - await acpRuntimeOwner - .getOrCreate() - .sessionController.completeFork(toAppSessionId(sourceId), messageId) - }, - sessions: appSessionService, - runtime: { - resolveSession: (sessionId) => { - const { handle } = agentManager.resolveSessionHandle(sessionId) - return { - kind: handle.kind, - initialize: (config) => handle.lifecycle.initialize(config), - isInitialized: () => handle.lifecycle.isInitialized(), - snapshot: () => handle.snapshot(), - getGenerationSettings: () => handle.settings.getGenerationSettings(), - setPermissionMode: (mode) => handle.settings.setPermissionMode(mode), - close: () => handle.close() - } - } - }, - transcript: { - hasMessages: (sessionId) => sessionData.transcript.hasMessages(sessionId), - forkSessionFromMessage: (sourceSessionId, targetSessionId, targetMessageId) => - sessionTranscriptMutations.forkSessionFromMessage( - sourceSessionId, - targetSessionId, - targetMessageId - ) - }, - skills: { - setActiveSkills: async (sessionId, activeSkills) => { - await skillService.setActiveSkills(sessionId, activeSkills) - } - }, - assignmentPolicy: sessionAssignmentPolicy, - workdir: sessionAssignment, - initialTurn: sessionTurn, - projection: sessionQuery, - desktop: desktopSessionBinding, - deletion: sessionDeletion, - deletionGate: sessionDeletionGate, - permissions: sessionPermissionPort, - agentLifecycle - }) - cliRunService = new CliRunService({ - lifecycle: sessionLifecycle, - turn: sessionTurn, - projection: sessionQuery, - sessions: appSessionService, - getPendingAssistantMessages: (runId) => - sessionData.transcript.getPendingAssistantMessages(runId), - hasWaitingDescendantInteraction: (runId) => - liveDelegationRepository - .listActiveTurns() - .some( - ({ delegation, turn }) => - (turn.status === 'waiting_permission' || turn.status === 'waiting_question') && - resolveSessionRunId(delegation.parentSessionId) === runId - ), - eventHub: typedEventHub, - log: logger - }) - sessionHistorySearch = new SessionHistorySearch(sessionData.database, appSessionService) - agentSessionExportService = new AgentSessionExportService({ - nowledgeMemConnections, - agentManager: agentManager, - appSessionService, - transcript: sessionData.transcript, - providerSettings: providerSettings - }) - sessionTranslation = new SessionTranslation({ - agentManager: agentManager, - agentSettings, - providerRuntime: providerRuntime - }) - const orchestrationCapabilityResolver = new OrchestrationCapabilityResolver({ - sessions: agentToolDependencies.sessions, - agents: agentSettings - }) - const liveDelegationSafety = new LiveDelegationSafetyCoordinator({ - sessions: agentToolDependencies.sessions, - assignmentPolicy: sessionAssignmentPolicy, - assignment: sessionAssignment, - permissions: sessionPermissionPort, - executionSnapshots: deepChatAgentHarness - }) - const createLiveDelegationService = (): LiveDelegationService => - new LiveDelegationService({ - repository: liveDelegationRepository, - admission: agentInvocationAdmission, - deletionGate: sessionDeletionGate, - safety: liveDelegationSafety, - consent: liveDelegationConsent, - sessions: { - ...agentToolDependencies.sessions, - ...agentToolDependencies.subagents, - resolveSubagentExecutionSnapshot: async (input) => { - const assignment = await sessionAssignmentPolicy.resolveSubagentAssignment(input) - const isAcp = agentManager.resolveBackend(assignment.agentId).descriptor.kind === 'acp' - return { - providerId: assignment.providerId, - modelId: assignment.modelId, - toolModeOverride: assignment.toolModeOverride, - generationSettings: isAcp - ? null - : await sanitizeGenerationSettings( - providerSettings, - promptSettings, - assignment.providerId, - assignment.modelId, - assignment.generationSettings ?? {} + approvalPresentation: { + getTarget: async () => { + const focused = await semanticNotificationTargets.getFocusedTarget() + const target = + focused?.kind === 'main' + ? focused + : (await semanticNotificationTargets.getExistingTargets()).find( + (candidate) => candidate.kind === 'main' ) - } + return target ? { windowId: target.windowId, webContentsId: target.webContentsId } : null }, - findDelegationChild: async (parentSessionId, delegationId) => { - const matches = appSessionService - .list({ includeSubagents: true, parentSessionId }) - .filter( - (session) => session.subagentMeta?.liveDelegation?.delegationId === delegationId - ) - if (matches.length > 1) { - throw new Error(`Multiple child Sessions share live delegation ${delegationId}.`) + present: async (target, payload) => { + if (!('windowId' in target)) return false + const readyTarget = await semanticNotificationTargets.getTargetForWindow(target.windowId) + if ( + !readyTarget || + readyTarget.kind !== 'main' || + readyTarget.webContentsId !== target.webContentsId + ) { + return false } - return matches[0] - ? await agentToolDependencies.sessions.resolveConversationSessionInfo(matches[0].id) - : null + windowPresenter.show(target.windowId, true) + return await semanticNotificationTargets.sendDeepchatEvent( + readyTarget, + approvalRequestedEvent.name, + payload + ) }, - getAssistantResult: async (sessionId, messageId) => { - const messages = sessionData.database.deepchatMessagesTable - const identity = messageId - ? messages.getAssistantIdentity(messageId) - : messages.getLatestAssistantIdentity(sessionId) - if (!identity || identity.session_id !== sessionId) return null - - const resultRows = - sessionData.database.deepchatAssistantBlocksTable.listResultProjectionByMessageId( - identity.id - ) - if (resultRows.length > 0) { - const answer = projectFinalAssistantAnswer( - resultRows.map( - (row): AssistantMessageBlock => ({ - type: row.block_type, - status: row.status, - timestamp: row.updated_at, - content: row.text_content ?? undefined - }) - ) - ) - return answer - ? { - messageId: identity.id, - answerMarkdown: answer, - updatedAt: identity.updated_at - } - : null - } - - const legacyMessage = await sessionQuery.getMessage(identity.id) - if (!legacyMessage || legacyMessage.sessionId !== sessionId) return null - try { - const parsed = JSON.parse(legacyMessage.content) as AssistantMessageBlock[] | string - const answer = Array.isArray(parsed) - ? projectFinalAssistantAnswer(parsed) - : typeof parsed === 'string' - ? parsed.trim() - : '' - return answer - ? { messageId: identity.id, answerMarkdown: answer, updatedAt: identity.updated_at } - : null - } catch { - const answer = legacyMessage.content.trim() - return answer - ? { messageId: identity.id, answerMarkdown: answer, updatedAt: identity.updated_at } - : null + close: async (target, payload) => { + if (!('windowId' in target)) return + const readyTarget = await semanticNotificationTargets.getTargetByWebContents( + target.webContentsId + ) + if ( + !readyTarget || + readyTarget.kind !== 'main' || + readyTarget.windowId !== target.windowId + ) { + return } + await semanticNotificationTargets.sendDeepchatEvent( + readyTarget, + approvalClosedEvent.name, + payload + ) } }, - observe: emitLiveDelegationObservation, - observationsEnabled: () => mainLogger.isOutputEnabled(), - onChanged: (parentSessionId, delegationId) => { - sessionQuery.notify({ sessionIds: [parentSessionId], reason: 'updated' }) - try { - publishDeepchatEvent(liveDelegationChangedEvent.name, { - schemaVersion: 1, - parentSessionId, - delegation: liveDelegationService.getSummary(parentSessionId, delegationId) - }) - } catch (error) { - logger.warn('[LiveDelegationService] Failed to publish delegation projection', { error }) - } - } - }) - liveDelegationService = createLiveDelegationService() - remoteService = new RemoteService({ - settings: dependencies.settingsStore, - catalog: { - getAgentType: (agentId) => agentSettings.getAgentType(agentId), - listAgents: async () => - (await agentSettings.listAgents()) - .filter((agent) => agent.enabled !== false) - .map((agent) => ({ - agentId: agent.id, - agentName: agent.name || agent.id, - agentType: agent.type, - source: agent.source - })), - listModelProviders: async () => { - const enabledProviders = providerSettings.getEnabledProviders() - const enabledModelGroups = await providerSettings.getAllEnabledModels() - const providerNameById = new Map( - enabledProviders.map((provider) => [provider.id, provider.name]) + assertSyncTokenStorageAvailable: () => { + if ( + process.platform === 'linux' && + safeStorage.getSelectedStorageBackend() === 'basic_text' ) - return enabledModelGroups - .filter((group) => providerNameById.has(group.providerId) && group.models.length > 0) - .map((group) => ({ - providerId: group.providerId, - providerName: providerNameById.get(group.providerId) ?? group.providerId, - models: group.models.map((model) => ({ - modelId: model.id, - modelName: model.name || model.id - })) - })) - } - }, - workspace: { - getDefaultProjectPath: () => projectService.getDefaultProjectPath(), - prepareFile: (filePath, type) => fileService.prepareFile(filePath, type) - }, - lifecycle: sessionLifecycle, - turn: { - sendMessage: (...args) => sessionTurn.sendMessage(...args), - respondToolInteraction: (...args) => sessionTurn.respondToolInteraction(...args), - cancelGeneration: (sessionId) => sessionTurn.cancelGeneration(sessionId) - }, - assignment: sessionAssignment, - projection: sessionQuery, - desktop: { - openSession: (sessionId) => openRemoteSession(sessionId) - }, - notifications: { - showNotification: (options) => notificationService.showNotification(options) - } - }) - cronJobs = new SchedulerService({ - database: schedulerDatabase, - agentSettings, - sessionEvents: sessionRuntimeEvents, - runSessionStarter: createCronJobRunSessionStarter({ - lifecycle: sessionLifecycle, - turn: sessionTurn, - agentCatalog: agentSettings - }), - remoteDeliveryPort: remoteService - }) - - desktopSettings.initializeTheme() - - function setupTray() { - console.info('setupTray', !!trayPresenter) - trayPresenter.init() - } - - function scheduleMainStartupTask( - startupRunId: string, - task: Omit[0], 'runId'>, - errorMessage: string, - failure: { - component: MainLogStartupComponent - category: MainLogStartupComponentFailureCategory - } - ): void { - scheduleObservedStartupTask({ - coordinator: startupWorkloadCoordinator, - startupRunId, - task, - onFailure: (failedStartupRunId, error) => { - reportMainStartupComponentFailure(failedStartupRunId, failure.component, failure.category) - reportNativeMainError(errorMessage, error) - } - }) - } - - function init(mainRunId: string) { - if (hasInitialized) { - console.info('[Startup][Main] Main startup skipped because startup already ran') - return - } - - liveDelegationService.start() - hasInitialized = true - - const providers = providerSettings.getProviders() - console.info(`[Startup][Main] Main startup begin providers=${providers.length}`) - scheduleMainStartupTask( - mainRunId, - { - id: 'main:floating-button', - target: 'main', - phase: 'deferred', - resource: 'io', - labelKey: 'startup.main.floatingButton', - run: async () => { - await initializeFloatingButton() - } + throw new Error('sync.error.safeStorageUnavailable') }, - 'Failed to schedule floating button initialization:', - { component: 'floating_widget', category: 'resource' } - ) - - scheduleMainStartupTask( - mainRunId, - { - id: 'main:skills-sync-scan', - target: 'main', - phase: 'background', - resource: 'cpu', - labelKey: 'startup.main.skillsSyncScan', - run: async (taskContext) => { - await taskContext.yield() - await initializeSkillSyncScan(taskContext.signal) - } - }, - 'Failed to schedule SkillSyncService background scan:', - { component: 'skill_sync', category: 'unknown' } - ) - - scheduleMainStartupTask( - mainRunId, - { - id: 'main:mcp-init', - target: 'main', - phase: 'background', - resource: 'io', - labelKey: 'startup.main.mcpInit', - run: async (taskContext) => { - await taskContext.yield() - await initializeMcp() - } - }, - 'Failed to schedule MCP initialization:', - { component: 'mcp', category: 'unknown' } - ) - - scheduleMainStartupTask( - mainRunId, - { - id: 'main:remote-runtime', - target: 'main', - phase: 'background', - resource: 'io', - labelKey: 'startup.main.remoteRuntime', - run: async (taskContext) => { - await taskContext.yield() - await initializeRemoteControl() - } + semanticNotifications, + secureCrypto: secureCredentialCrypto, + getProxySnapshot: () => proxyConfig.getProxySnapshot(), + openExternal: (url) => shell.openExternal(url), + openPath: (filePath) => shell.openPath(filePath), + selectDirectory: () => deviceService.selectDirectory(), + cacheImage: (data, options) => deviceService.cacheImage(data, options), + initializeFloatingButton, + ocrCacheKeyProvider: new SafeStorageOcrCacheKeyProvider( + path.join(app.getPath('userData'), 'ocr', 'cache-key.json') + ), + agentDesktop: { + tempDirectory: app.getPath('temp'), + profileTempDirectory: path.join(app.getPath('userData'), 'temp'), + readImageSize: async (filePath, signal) => { + signal?.throwIfAborted() + const size = nativeImage.createFromPath(filePath).getSize() + signal?.throwIfAborted() + return { width: size.width, height: size.height } + }, + createSettingsWindow: () => windowPresenter.createSettingsWindow(), + sendSettingsNavigation: async (windowId, navigation) => + windowPresenter.sendSettingsNavigation(windowId, navigation) }, - 'Failed to schedule remote runtime initialization:', - { component: 'remote_runtime', category: 'unknown' } - ) - - void startupWorkloadCoordinator - .whenIdle('main', async () => { - await startupWorkloadCoordinator.scheduleTask({ - id: 'main:provider-warmup-idle', - target: 'main', - phase: 'background', - resource: 'io', - labelKey: 'startup.main.provider.warmup', - visibleId: 'main.provider.warmup', - dedupeKey: 'main.provider.warmup:idle', - runId: mainRunId, - run: async (taskContext) => { - await initializeIdleProviderWarmup(taskContext) - } - }) - }) - .catch((error) => { - if (isStartupWorkloadCancellation(error)) return - console.error('Failed to schedule idle provider warmup:', error) - }) - } - - async function initializeFloatingButton() { - try { - await floatingButtonPresenter.initialize() - logger.info('FloatingButtonPresenter initialized successfully') - } catch (error) { - reportMainStartupComponentFailure(dependencies.startupRunId, 'floating_widget', 'resource') - console.error('Failed to initialize FloatingButtonPresenter:', error) - } - } - - async function initializeSkills(): Promise { - if (!skillSettings.isEnabled()) { - logger.info('SkillService disabled by config') - return - } - await ensureSkillServicesInitialized() - } - - async function ensureSkillServicesInitialized(): Promise { - if (appLifecycleState === 'stopping' || appLifecycleState === 'stopped') { - throw new Error(`Cannot initialize Skill services while app is ${appLifecycleState}`) - } - if (!skillInitializationPromise) { - const initialization = (async () => { - await initializePlugins() - await skillService.initialize() - logger.info('SkillService initialized') - await skillSyncService.initialize() - })() - skillInitializationPromise = initialization - void initialization.catch(() => { - if (skillInitializationPromise === initialization) { - skillInitializationPromise = null - } - }) - } - await skillInitializationPromise - } - - async function initializePlugins(): Promise { - if (!pluginInitializationPromise) { - const initialization = pluginService.initialize() - pluginInitializationPromise = initialization - void initialization.catch(() => { - if (pluginInitializationPromise === initialization) { - pluginInitializationPromise = null - } - }) - } - try { - await pluginInitializationPromise - } catch (error) { - reportMainStartupComponentFailure(dependencies.startupRunId, 'plugin_host', 'unknown') - console.error('[PluginHost] Failed to initialize plugins:', error) - } - } - - async function initializeSkillSyncScan(signal?: AbortSignal): Promise { - if (signal?.aborted || appLifecycleState === 'stopping' || appLifecycleState === 'stopped') { - return - } - if (!skillSyncScanPromise) { - const scan = (async () => { - try { - if (!skillSettings.isEnabled() || signal?.aborted) return - await ensureSkillServicesInitialized() - if (signal?.aborted) return - await skillSyncService.scanAndDetectNewDiscoveries() - logger.info('SkillSyncService background scan completed') - } catch (error) { - reportMainStartupComponentFailure(dependencies.startupRunId, 'skill_sync', 'unknown') - console.error('Failed to run SkillSyncService background scan:', error) - } - })() - skillSyncScanPromise = scan - void scan.finally(() => { - if (skillSyncScanPromise === scan) { - skillSyncScanPromise = null - } - }) - } - await skillSyncScanPromise - } - - async function initializeMcp() { - await initializePlugins() - - try { - await proxyConfig.whenReady() - await mcpService.initialize() - } catch (error) { - reportMainStartupComponentFailure(dependencies.startupRunId, 'mcp', 'unknown') - console.error('Failed to initialize McpService:', error) - return - } - try { - await pluginRuntimeSupervisor.reconcileAll() - } catch (error) { - reportMainStartupComponentFailure(dependencies.startupRunId, 'plugin_runtime', 'unknown') - console.error('[PluginHost] Failed to reconcile eager plugin runtimes:', error) - } - - try { - deepChatAgentHarness.refreshToolRegistry() - deeplinkService.processPendingMcpInstall() - } catch (error) { - reportMainStartupComponentFailure(dependencies.startupRunId, 'mcp_integration', 'unknown') - console.error('Failed to finish MCP startup integration:', error) - } - } - - async function initializeRemoteControl() { - try { - await remoteService.initialize() - } catch (error) { - reportMainStartupComponentFailure(dependencies.startupRunId, 'remote_runtime', 'unknown') - console.error('RemoteService.initialize failed:', error) - } - } - - async function initializeIdleProviderWarmup(taskContext: StartupWorkloadTaskContext) { - const enabledProviders = providerSettings - .getEnabledProviders() - .map((provider) => provider.id) - .filter((providerId, index, ids) => ids.indexOf(providerId) === index) - - if (enabledProviders.length === 0) { - taskContext.reportProgress(1) - return - } - - console.info( - `[Startup][Main] startup.provider.warmup.deferred begin providers=${enabledProviders.length}` - ) - - for (const [index, providerId] of enabledProviders.entries()) { - if (taskContext.signal.aborted) { - const error = new Error(`Provider warmup aborted for ${providerId}`) - error.name = 'AbortError' - throw error - } - - const providerModels = providerSettings.getProviderModels(providerId) - const customModels = providerSettings.getCustomModels(providerId) - providerSettings.getDbProviderModels(providerId) - providerSettings.getBatchModelStatus(providerId, [ - ...providerModels.map((model) => model.id), - ...customModels.map((model) => model.id) - ]) - - taskContext.reportProgress((index + 1) / enabledProviders.length) - await taskContext.yield() - } - - console.info( - `[Startup][Main] startup.provider.warmup.deferred done providers=${enabledProviders.length}` - ) - } - - async function destroy(): Promise { - await runDestroyStep('agentCliTokenAuthority.clear', () => agentCliTokenAuthority.clear()) - await runDestroyStep('cliServer.stop', () => cliServer.stop()) - await runDestroyStep('automaticSync.close', () => automaticSync!.close()) - await runDestroyStep('syncPeerService.stop', () => syncPeerService.stop()) - await runDestroyStep('syncHostService.stop', () => syncHostService.stop()) - await runDestroyStep('replicaEndpoint.close', () => replicaEndpoint.close()) - await runDestroyStep('syncReplica.close', () => syncReplica?.close()) - await runDestroyStep('tapeInspectorHeadWatcher.close', () => tapeInspectorHeadWatcher.close()) - await runDestroyStep('typedEventHub.close', () => typedEventHub.close()) - await runDestroyStep('cliMutationGuard.clear', () => cliMutationGuard.clear()) - await runDestroyStep('cliAuditLog.close', () => cliAuditLog.close()) - await runDestroyStep('artifactSpool.close', () => artifactSpool.close()) - await runDestroyStep('providerCatalog.unsubscribe', () => unsubscribeProviderDbCatalog()) - await runDestroyStep('liveDelegationService.stop', () => liveDelegationService.stop()) - await runDestroyStep('agentInvocationAdmission.close', () => agentInvocationAdmission.close()) - await runDestroyStep('agentInvocationAdmission.flushObservations', () => - agentInvocationAdmission.flushObservations() - ) - await runDestroyStep('cronJobs.destroy', () => cronJobs.destroy()) - await runDestroyStep('remoteService.destroy', () => remoteService.destroy()) - await runDestroyStep('hookService.stop', () => hookService.stop()) - await runDestroyStep('sessionRuntimes.suspend', () => suspendSessionRuntimes()) - await runDestroyStep('toolService.shutdownCodeRuntime', () => toolService.shutdownCodeRuntime()) - const pendingSkillInitialization = skillInitializationPromise - if (pendingSkillInitialization) { - await runDestroyStep('skillInitialization.drain', async () => { - await pendingSkillInitialization - }) - } - const pendingPluginInitialization = pluginInitializationPromise - if (pendingPluginInitialization) { - await runDestroyStep('pluginInitialization.drain', async () => { - await pendingPluginInitialization - }) - } - const pendingSkillSyncScan = skillSyncScanPromise - if (pendingSkillSyncScan) { - await runDestroyStep('skillSyncScan.drain', async () => { - await pendingSkillSyncScan - }) - } - await runDestroyStep('pluginService.shutdown', () => pluginService.shutdown()) - await runDestroyStep('mcpApps.clear', () => dependencies.mcpAppSandboxRegistry.clear()) - await runDestroyStep('mcpService.shutdown', () => mcpService.shutdown()) - await runDestroyStep('semanticNotificationRouter.dispose', () => - semanticNotificationRouter.dispose() - ) - await runDestroyStep('semanticNotificationTargets.dispose', () => - semanticNotificationTargets.dispose() - ) - await runDestroyStep('semanticNotificationEpisodes.dispose', () => - semanticNotificationEpisodes.dispose() - ) - await runDestroyStep('semanticNotificationDiagnostics.dispose', () => - semanticNotificationDiagnostics.dispose() - ) - await runDestroyStep('computerUsePreviewPresenter.shutdown', () => - computerUsePreviewPresenter.shutdown() - ) - await runDestroyStep('yoBrowserPresenter.shutdown', () => yoBrowserPresenter.shutdown()) - await runDestroyStep('agentPreviewCoordinator.shutdown', () => - agentPreviewCoordinator.shutdown() - ) - await runDestroyStep('floatingButtonPresenter.destroy', () => floatingButtonPresenter.destroy()) - await runDestroyStep('windowPresenter.destroyFloatingChatWindow', () => - windowPresenter.destroyFloatingChatWindow() - ) - await runDestroyStep('tabPresenter.destroy', () => tabPresenter.destroy()) - await runDestroyStep('windowPresenter.destroyWindows', () => { - windowPresenter.closeSettingsWindow() - for (const window of windowPresenter.getAllWindows()) { - if (!window.isDestroyed()) window.destroy() - } - }) - await runDestroyStep('workspaceService.destroy', () => workspaceService.destroy()) - await runDestroyStep('skillSyncService.destroy', () => skillSyncService.destroy()) - await runDestroyStep('skillService.destroy', () => skillService.destroy()) - await runDestroyStep('fileWatcherService.destroy', () => fileWatcherService.destroy()) - await runDestroyStep('backgroundExecSessionManager.shutdown', () => - backgroundExecSessionManager.shutdown() - ) - // Fence new ingestion synchronously, then let Memory disposal abort provider-bound work before - // awaiting the existing chains. This avoids both late SQLite writes and shutdown deadlocks. - const memoryIngestionDrain = (() => { - try { - return memoryIngestionObserver.drainAndFence().then( - (outcome) => ({ outcome }) as const, - (error) => ({ error }) as const - ) - } catch (error) { - return Promise.resolve({ error } as const) - } - })() - await runDestroyStep('memoryService.dispose', () => memoryService.dispose()) - let memoryIngestionDrainOutcome: MemoryIngestionDrainOutcome | undefined - await runDestroyStep('memoryIngestionObserver.drainAndFence', async () => { - const result = await memoryIngestionDrain - if ('error' in result) throw result.error - memoryIngestionDrainOutcome = result.outcome - }) - if (memoryIngestionDrainOutcome?.timedOut) { - logger.warn( - `[Main] Memory ingestion drain timed out with ${memoryIngestionDrainOutcome.pendingSessions.length} pending session(s); late writes remain fenced.` - ) - } - await runDestroyStep('knowledgeService.destroy', () => knowledgeService.destroy()) - await runDestroyStep('ocrRuntimeService.close', () => ocrRuntimeService.close()) - await runDestroyStep('providerRuntime.shutdown', () => providerRuntime.shutdown()) - await runDestroyStep('acpRuntime.shutdown', () => acpRuntimeOwner.shutdown()) - await runDestroyStep('mainDatabase.close', () => mainDatabase.close()) - await runDestroyStep('shortcutPresenter.destroy', () => shortcutPresenter.destroy()) - await runDestroyStep('notificationService.clearAllNotifications', () => - notificationService.clearAllNotifications() - ) - await runDestroyStep('trayPresenter.destroy', () => trayPresenter.destroy()) - } - - async function runDestroyStep(stepName: string, step: () => void | Promise): Promise { - const startedAt = readMonotonicNow() - logger.info(`[Main] destroy.${stepName} begin`) - try { - await step() - const durationMs = elapsedMonotonicMs(startedAt) - logger.info( - durationMs === undefined - ? `[Main] destroy.${stepName} done` - : `[Main] destroy.${stepName} done durationMs=${durationMs.toFixed(1)}` - ) - } catch (error) { - shutdownStepFailures += 1 - const durationMs = elapsedMonotonicMs(startedAt) - logger.warn( - durationMs === undefined - ? `[Main] destroy.${stepName} failed` - : `[Main] destroy.${stepName} failed durationMs=${durationMs.toFixed(1)}`, - error - ) - } - } - - function registerRoutes(): void { - const providerQueryScheduler = createNodeScheduler() - const providerRoutes = createProviderRoutes({ - providerSettings, - providerRuntime, - acpProviderAdminPort, - providerImportService: new ProviderImportService({ - getProviders: () => providerSettings.getProviders(), - getDefaultProviders: () => providerSettings.getDefaultProviders(), - addCustomModel: (providerId, model) => { - const added = providerSettings.addCustomModel(providerId, model) - providerRuntime.invalidateProviderCatalogFromSync(providerId) - return added + pluginSettingsWindow, + remoteAuthWindows, + remoteSessionWindow: { + select: async () => { + if (isStopping()) return null + const chatWindows = windowPresenter + .getAllWindows() + .filter( + (window) => + !window.isDestroyed() && + !window.webContents.isDestroyed() && + tabPresenter.getWindowType(window.id) === 'chat' + ) + const focusedWindow = windowPresenter.getFocusedWindow() + const targetWindow = + chatWindows.find((window) => window.id === focusedWindow?.id) ?? chatWindows[0] + const windowId = + targetWindow?.id ?? (await windowPresenter.createAppWindow({ initialRoute: 'chat' })) + if (isStopping()) return null + const window = windowPresenter.getAllWindows().find((window) => window.id === windowId) + if ( + !window || + window.isDestroyed() || + window.webContents.isDestroyed() || + tabPresenter.getWindowType(window.id) !== 'chat' + ) + return null + return { windowId: window.id, webContentsId: window.webContents.id } }, - updateProvidersBatch: (batchUpdate) => providerRuntime.updateProvidersBatch(batchUpdate) - }), - oauthService, - scheduler: providerQueryScheduler, - recordSettingsActivity: (input) => settingsDatabase.recordSettingsActivity(input) - }) - const toolRoutes = createToolRoutes(toolService) - const pluginRoutes = createPluginRoutes(pluginService, pluginSettingsWindow) - const skillRoutes = createSkillRoutes({ - skillService, - skillSyncService, - skillSettings, - ensureInitialized: ensureSkillServicesInitialized, - assertSessionActiveSkillsMutable: async (conversationId) => { - const state = await deepChatAgentHarness.getSessionState(conversationId) - if (state?.status === 'generating') { - throw new Error('Cannot change Session Skills while the session is generating.') + present: async (target) => { + if (isStopping()) return false + const window = windowPresenter + .getAllWindows() + .find((window) => window.id === target.windowId) + if ( + !window || + window.isDestroyed() || + window.webContents.isDestroyed() || + window.webContents.id !== target.webContentsId || + tabPresenter.getWindowType(window.id) !== 'chat' + ) + return false + windowPresenter.show(window.id, true) + return true } }, - recordSettingsActivity: (input) => settingsDatabase.recordSettingsActivity(input) - }) - const mcpRoutes = createMcpRoutes({ - mcpService, - mcpAppHost: (() => { - if (!mcpService.appHost) { - throw new Error('MCP Apps host is not configured') + showNotification: (options) => notificationService.showNotification(options), + mcpPreview: computerUsePreviewPresenter, + mcpAppDesktop: dependencies.mcpAppDesktop, + publishMcpConsent: (windowId, payload) => + windowPresenter.sendToWindow( + windowId, + DEEPCHAT_EVENT_CHANNEL, + createDeepchatEventEnvelope('mcp.app.consent.request', payload) + ), + sessionUiPort: { + refreshSessionUi: () => { + void floatingButtonPresenter.refreshWidgetState() } - return mcpService.appHost - })(), - isSettingsWindow: (windowId) => - windowId != null && windowPresenter.getSettingsWindowId() === windowId, - recordSettingsActivity: (input) => settingsDatabase.recordSettingsActivity(input) - }) - const remoteRoutes = createRemoteRoutes(remoteService) - const schedulerRoutes = createSchedulerRoutes(cronJobs) - const memoryRoutes = createMemoryRoutes({ - memoryService, - getAgentType: (agentId) => agentSettings.getAgentType(agentId), - getTapeInspection: () => sessionData.tapeStore, - getAuditEntries: () => memoryDatabase.agentMemoryAuditTable - }) - const desktopRoutes = createDesktopRoutes({ - ownsDraftSession: (sessionId, webContentsId) => - sessionLifecycle.ownsDraftSession(sessionId, webContentsId), - windowPresenter, - shortcutPresenter, - browserPresenter: yoBrowserPresenter, - computerUsePreviewPresenter, - desktopSessionBinding, - tabPresenter, - dialogService, - settings: desktopSettings, - setFloatingButtonEnabled: (enabled) => floatingButtonPresenter.setEnabled(enabled), - recordActivity: (input) => { - void settingsDatabase.recordSettingsActivity(input).catch((error) => { - console.warn('[SettingsActivity] Failed to record settings activity:', error) - }) - } - }) - const fileRoutes = createFileRoutes(fileService) - const ocrRoutes = createOcrRoutes({ runtime: ocrRuntimeService }) - const toolchainRoutes = createToolchainRoutes({ - service: toolchainService, - pickPath: () => deviceService.selectFiles({ multiple: false }) - }) - const knowledgeRoutes = createKnowledgeRoutes({ - service: knowledgeService, - settings: knowledgeSettings, - applyConfigChange: async () => { - mcpService.handleConfigChanged() - await knowledgeService.syncConfigChanges() }, - recordActivity: (input) => { - void settingsDatabase.recordSettingsActivity(input).catch((error) => { - console.warn('[SettingsActivity] Failed to record settings activity:', error) - }) - } - }) - const workspaceRoutes = createWorkspaceRoutes(workspaceService) - const orchestrationRoutes = createOrchestrationRoutes({ - resolveCapability: (target) => - 'sessionId' in target - ? orchestrationCapabilityResolver.resolveSession(target.sessionId) - : orchestrationCapabilityResolver.resolveDraft(target.agentId), - getPolicy: (sessionId) => sessionAssignment.getOrchestrationPolicy(sessionId), - setPolicy: (sessionId, policy) => - sessionAssignment.updateOrchestrationPolicy(sessionId, policy), - liveDelegations: createLivePort(() => liveDelegationService) - }) - const projectRoutes = createProjectRoutes({ - projectService, - publishEnvironmentsChanged: (action, environmentPath, version) => { - publishDeepchatEvent(projectEnvironmentsChangedEvent.name, { - action, - path: environmentPath, - version - }) - } - }) - const sessionRoutes = createSessionRoutes({ - lifecycle: sessionLifecycle, - projection: sessionQuery, - desktop: desktopSessionBinding, - turn: sessionTurn, - assignment: sessionAssignment, - permission: sessionPermissionPort, - agentSettings, - scheduler: createNodeScheduler(), - historySearch: sessionHistorySearch, - exportService: agentSessionExportService, - translation: sessionTranslation, - usageStats: usageStatsService, - tapeInspectorHeadWatcher, - sessionReferences: sessionData.sessionReferences - }) - const agentRoutes = createAgentRoutes({ - agentSettings, - recordActivity: (input) => { - void settingsDatabase.recordSettingsActivity(input).catch((error) => { - console.warn('[SettingsActivity] Failed to record settings activity:', error) - }) + mcpReadinessChanged: (ready) => deeplinkService.observeMcpReadiness(ready), + reportStartupFailure: (component, category, failure) => { + reportMainStartupComponentFailure( + failure?.startupRunId ?? dependencies.startupRunId, + component, + category + ) + if (failure) reportNativeMainError(failure.message, failure.error) }, - reconcileScheduler: async () => { - await cronJobs.reconcileScheduler('agent-change') - } - }) - const promptRoutes = createPromptRoutes({ - settings: promptSettings, - recordActivity: (input) => { - void settingsDatabase.recordSettingsActivity(input).catch((error) => { - console.warn('[SettingsActivity] Failed to record settings activity:', error) - }) - } - }) - const acpRoutes = createAcpRoutes({ - auth: acpAuthService, - owner: acpRuntimeOwner, - runtime: acpAgentRuntime, - isMainWindowContext: (caller) => - windowPresenter.mainWindow?.webContents.id === caller.webContentsId, - importHistory: async (sessionId) => { - const localId = toAppSessionId(sessionId) - const instance = acpAgentRuntime.getHydrated(localId) - if (!instance) throw new Error('ACP session is not initialized') - await instance.runIdleOperation(async () => { - const session = acpRuntimeOwner.getOrCreate().sessionController.getSession(localId) - const snapshot = - session && - (await acpRuntimeOwner - .getOrCreate() - .sessionController.getExtensions(localId, session.agentId)) - if (!session || !snapshot?.history) throw new Error('Read ACP history before importing') - sessionData.transcript.importAcpHistory( - sessionId, - session.agentId, - session.sessionId, - snapshot.history + onSessionInactive: (sessionId) => { + void yoBrowserPresenter + .releaseInactivePreview(sessionId) + .catch((error) => + logger.warn('[YoBrowser] Failed to release inactive preview', { sessionId, error }) ) - publishDeepchatEvent('sessions.messages.changed', { - sessionId, - messages: sessionData.transcript.getMessages(sessionId), - version: Date.now() - }) - publishDeepchatEvent('sessions.updated', { sessionIds: [sessionId], reason: 'updated' }) - }) - } - }) - const deviceRoutes = createDeviceRoutes({ - device: deviceService, - restartApplication, - resetDataByType: (resetType) => resetApplicationData(resetType) - }) - const onboardingRoutes = createOnboardingRoutes(dependencies.settingsStore) - const upgradeRoutes = createUpgradeRoutes({ upgrade: upgradeService, settings: updateSettings }) - const exporterRoutes = createExporterRoutes(exporter) - const syncRoutes = createSyncRoutes({ - sync: syncService, - settings: syncSettings, - importFromSync: (backupFileName, importMode) => - runDatabaseMaintenance((database) => - syncService.importFromSync(backupFileName, importMode ?? ImportMode.INCREMENT, database) - ), - pullLatestBackupFromCloud: (importMode) => pullLatestBackupFromCloud(importMode), - recordActivity: (input) => { - void settingsDatabase.recordSettingsActivity(input).catch((error) => { - console.warn('[SettingsActivity] Failed to record settings activity:', error) - }) - } - }) - const syncHostRoutes = createSyncHostRoutes({ host: syncHostService }) - const platformRoutes = createPlatformRoutes({ - proxySettings: dependencies.proxySettings, - applyProxyMode: (mode) => { - proxyConfig.setProxyMode(mode as ProxyMode) - void proxyConfig.resolveProxy().then((resolved) => { - if (resolved) (providerRuntime as ProviderRuntime).handleProxyResolved() - }) }, - applyCustomProxyUrl: (url) => { - proxyConfig.setCustomProxyUrl(url) - if (proxyConfig.getProxyMode() === ProxyMode.CUSTOM) void proxyConfig.resolveProxy() - } - }) - const hookRoutes = createHookRoutes({ service: hookService }) - const notificationRoutes = createNotificationRoutes({ - rendererReady: async (webContentsId) => { - const ready = await semanticNotificationTargets.markRendererReady(webContentsId) - if (ready) { - await semanticNotificationRouter.availabilityChanged() + renderer: { + broadcast: (envelope) => windowPresenter.sendToAllWindows(DEEPCHAT_EVENT_CHANNEL, envelope), + send: (id, envelope) => + windowPresenter.sendToWebContents(id, DEEPCHAT_EVENT_CHANNEL, envelope) + }, + onRendererDestroyed: (id, listener) => { + const target = electronWebContents.fromId(id) + if (!target || target.isDestroyed()) { + queueMicrotask(listener) + return () => {} } - return ready + target.once('destroyed', listener) + return () => target.removeListener('destroyed', listener) }, - acknowledgePresentation: (episodeId, webContentsId) => - semanticNotificationRouter.acknowledgePresentation(episodeId, { webContentsId }) - }) - const appSettingsRoutes = createAppSettingsRoutes({ - settings: dependencies.settingsStore, - agentDefaults, - privacy: dependencies.privacySettings, - traceSettings, - desktopSettings, - fonts: fontSettings, - applyContentProtection: (enabled) => - (windowPresenter as WindowPresenter).applyContentProtection(enabled), - logging: loggingService, - ocr: ocrSettings, - commandShell: commandShellService, - publishEvent: publishDeepchatEvent, - recordActivity: (input) => { - void settingsDatabase.recordSettingsActivity(input).catch((error) => { - console.warn('[SettingsActivity] Failed to record settings activity:', error) - }) + sendToRenderer: (id, name, payload) => { + const target = electronWebContents.fromId(id) + if (target && !target.isDestroyed()) + target.send(DEEPCHAT_EVENT_CHANNEL, createDeepchatEventEnvelope(name, payload)) }, - listActivities: (limit) => settingsDatabase.listSettingsActivity(limit) - }) - const appRoutes = createAppRoutes({ - logging: loggingService, - rendererPerformance: rendererPerformanceLogService, - isMainWindowContext: (caller) => - windowPresenter.mainWindow?.webContents.id === caller.webContentsId, - agentSettings, - projects: projectService, - databaseSecurity: databaseSecurityService, - database: mainDatabase, - startupSession: sessionQuery, - desktopSession: desktopSessionBinding, - startup: startupWorkloadCoordinator, - ensureDefaultWorkspace: () => projectService.ensureDefaultWorkspace(), - enableDatabaseEncryption: (password) => - runDatabaseMaintenance((database) => - databaseSecurityService.enableEncryption({ password, database, providerSettings }) - ), - changeDatabasePassword: (currentPassword, newPassword) => - runDatabaseMaintenance((database) => - databaseSecurityService.changePassword({ - currentPassword, - newPassword, - database, - providerSettings - }) - ), - disableDatabaseEncryption: (currentPassword) => - runDatabaseMaintenance((database) => - databaseSecurityService.disableEncryption({ - currentPassword, - database, - providerSettings - }) - ), - recordActivity: (input) => { - void settingsDatabase.recordSettingsActivity(input).catch((error) => { - console.warn('[SettingsActivity] Failed to record settings activity:', error) - }) + destroySessionBrowser: (id) => yoBrowserPresenter.destroySessionBrowser(id), + observeDelegation: emitLiveDelegationObservation, + runJournalObserver: emitRunJournalObservation, + notifySessionCompletion: async (session) => { + await notificationService.showSessionCompletion(session) }, - publishSessionsUpdated: (sessionIds) => { - publishDeepchatEvent(sessionsUpdatedEvent.name, { sessionIds, reason: 'created' }) + providerCrypto: credentialCrypto, + fetchRegistryIcon: async (url, signal) => { + if (!isAcpRegistryIconUrl(url)) throw new Error('Unsupported ACP registry icon URL') + signal.throwIfAborted() + const response = await net.fetch(url, { signal }) + if (!response.ok) { + throw new Error(`Failed to fetch icon: ${response.status} ${response.statusText}`) + } + const text = await response.text() + signal.throwIfAborted() + if (text.length > 1024 * 1024) throw new Error('ACP registry icon exceeds the size limit') + return text }, - splash: dependencies.splash - }) - const cliRoutes = createCliRoutes({ - appVersion: app.getVersion(), - getStatus: () => cliServer.getStatus(), + configurationHost: { + pause: async () => desktopConfigurationHost.pause(), + resume: async () => desktopConfigurationHost.resume(), + stopWrites: async () => nativeSettings.stopWrites(), + drainWrites: () => nativeSettings.drainWrites(), + startWrites: async () => nativeSettings.startWrites() + }, + isSettingsWindow: async (id) => id != null && windowPresenter.getSettingsWindowId() === id, + isMainWindowContext: async (caller) => + windowPresenter.mainWindow?.webContents.id === caller.webContentsId, + rendererPerformance: rendererPerformanceLogService, + splash: dependencies.splash, + images: desktopImageActions, + pickToolchainPath: () => deviceService.selectFiles({ multiple: false }), hasTrustedRenderer: async () => (await semanticNotificationTargets.getExistingTargets()).some( (target) => target.kind === 'main' - ) - }) - const approvalRoutes = createApprovalRoutes({ - resolve: (input, caller) => cliMutationGuard.resolve(input, caller) - }) - const artifactRoutes = createArtifactRoutes(artifactSpool) - const cliComputeRoutes = createCliComputeRoutes(cliComputeService) - const cliProviderModelAdminRoutes = createCliProviderModelAdminRoutes({ - providerSettings, - providerRuntime, - scheduler: providerQueryScheduler, - recordSettingsActivity: (input) => { - void settingsDatabase.recordSettingsActivity(input).catch((error) => { - console.warn('[SettingsActivity] Failed to record CLI provider activity:', error) - }) - } - }) - const cliSkillRoutes = cliSkillService.createRoutes() - const cliMcpAdminRoutes = createCliMcpAdminRoutes({ - mcp: mcpService, - recordSettingsActivity: (input) => { - void settingsDatabase.recordSettingsActivity(input).catch((error) => { - console.warn('[SettingsActivity] Failed to record CLI MCP activity:', error) - }) - }, - log: logger - }) - const cliRunRoutes = cliRunService.createRoutes() - routeDispatcher = createRouteDispatcher({ - appDatabaseMaintenance: { - assertRouteAllowed: (routeName) => assertRouteAllowedDuringDatabaseMaintenance(routeName) - }, - routeMaps: [ - providerRoutes, - toolRoutes, - pluginRoutes, - skillRoutes, - mcpRoutes, - remoteRoutes, - schedulerRoutes, - memoryRoutes, - desktopRoutes, - fileRoutes, - ocrRoutes, - toolchainRoutes, - knowledgeRoutes, - workspaceRoutes, - orchestrationRoutes, - projectRoutes, - sessionRoutes, - agentRoutes, - promptRoutes, - acpRoutes, - deviceRoutes, - onboardingRoutes, - upgradeRoutes, - exporterRoutes, - syncRoutes, - syncHostRoutes, - createSyncPeerRoutes(syncPeerService), - platformRoutes, - hookRoutes, - notificationRoutes, - appSettingsRoutes, - appRoutes, - approvalRoutes, - cliRoutes, - artifactRoutes, - cliComputeRoutes, - cliProviderModelAdminRoutes, - cliSkillRoutes, - cliMcpAdminRoutes, - cliRunRoutes - ], - settingsWindow: windowPresenter, - startupWorkloadCoordinator - }) - const authorizeRendererIpc = createRendererIpcAuthorizer({ - rendererDirectoryUrl: pathToFileURL(path.join(__dirname, '../renderer/')).href, - developmentServerUrl: app.isPackaged ? undefined : process.env.ELECTRON_RENDERER_URL, - pluginSettingsWindow - }) - registerDeepchatRoutes(ipcMain, routeDispatcher, authorizeRendererIpc) - registerClipboardIpc(ipcMain, authorizeRendererIpc) - } - - function setupApplicationListeners(): void { - app.on('accessibility-support-changed', (_event, enabled) => { - publishDeepchatEvent('appRuntime.accessibilityChanged', { enabled }) - }) - - app.on('browser-window-created', (_, window) => { - optimizer.watchWindowShortcuts(window) - }) - - app.on('activate', () => { - if (appLifecycleState !== 'running') return - if (windowPresenter.restoreMainWindowHiddenByClose()) { - return - } - - if (windowPresenter.getAllWindows().length === 0) { - void windowPresenter.createAppWindow({ initialRoute: 'chat' }) - } - }) - - app.on('did-resign-active', () => { - setTimeout(() => { - if (app.isHidden()) { - windowPresenter.clearMainWindowHiddenByClose() - } - }, 0) - }) - - app.on('browser-window-focus', () => { - if (appLifecycleState === 'stopping' || appLifecycleState === 'stopped') return - shortcutPresenter.registerShortcuts() - upgradeService.handleAppFocus() - void semanticNotificationRouter.availabilityChanged().catch((error) => { - logger.warn('[NotificationRouter] focus reconciliation failed', error) - }) - }) - - app.on('browser-window-blur', () => { - setTimeout(() => { - const isAnyWindowFocused = windowPresenter - .getAllWindows() - .some((window) => !window.isDestroyed() && window.isFocused()) - - if (!isAnyWindowFocused) { - shortcutPresenter.unregisterShortcuts() - } - }, 50) - }) - } - - async function runAcpRegistryMigration(): Promise { - const service = new AcpRegistryMigrationService( - dependencies.settingsStore, - agentSettings, - agentDatabase - ) - try { - await service.runIfNeeded() - } catch (error) { - reportMainStartupComponentFailure( - dependencies.startupRunId, - 'acp_registry_migration', - 'persistence' - ) - console.error('Failed to migrate ACP registry references:', error) - } - - try { - await service.compensateEnabledRegistryAgentInstalls() - } catch (error) { - reportMainStartupComponentFailure( - dependencies.startupRunId, - 'acp_install_compensation', - 'persistence' - ) - console.error('Failed to compensate ACP install states:', error) - } - } - - function scheduleBackgroundWork(startupRunId: string): void { - scheduleMainStartupTask( - startupRunId, - { - id: 'main:legacy-import', - target: 'main', - phase: 'background', - resource: 'io', - labelKey: 'startup.main.legacyImport', - run: async () => legacyChatImportService.start(false) + ), + applyProxy: async (mode, url, resolve) => { + proxyConfig.setCustomProxyUrl(url) + proxyConfig.setProxyMode(mode as ProxyMode) + return resolve ? await proxyConfig.resolveProxy() : false }, - 'Failed to start legacy import task:', - { component: 'legacy_import', category: 'persistence' } - ) - - scheduleMainStartupTask( - startupRunId, - { - id: 'main:usage-stats-backfill', - target: 'main', - phase: 'background', - resource: 'io', - labelKey: 'startup.main.usageStatsBackfill', - run: async (taskContext) => usageStatsService.startBackfill(taskContext) + copilotAuthorization: createGitHubCopilotAuthorization(), + sessionPresentation: { + focusBrowser: async (sessionId, caller) => + !isStopping() && + isRendererWindowCurrent(caller) && + caller.windowId != null && + yoBrowserPresenter.focusSessionBrowser(sessionId, caller.windowId), + setComputerUsePreview: async (sessionId, mode, caller) => + !isStopping() && isRendererWindowCurrent(caller) && caller.windowId != null + ? await computerUsePreviewPresenter.setPreviewMode(sessionId, mode, caller.windowId) + : { updated: false, surface: 'none' }, + dismissComputerUsePreview: async (sessionId, runId, caller) => + !isStopping() && + isRendererWindowCurrent(caller) && + computerUsePreviewPresenter.dismissPreview(sessionId, runId) + } + } + nativeDesktop = new CoreDesktopNative( + kernelNative, + (event) => connection.deliverDesktopEvent(event), + async () => { + const results = await Promise.allSettled([ + remoteAuthWindows.closeAll(), + pluginSettingsWindow.closeAll(), + proxyConfig.close() + ]) + const errors = results.flatMap((result) => + result.status === 'rejected' ? [result.reason] : [] + ) + if (errors.length) + throw new AggregateError(errors, 'Native desktop resources did not retire') }, - 'Failed to start usage stats backfill:', - { component: 'usage_stats_backfill', category: 'persistence' } + (error) => connection.invalidateNative(error) ) - - scheduleMainStartupTask( - startupRunId, - { - id: 'main:sqlite-mainline-normalization', - target: 'main', - phase: 'background', - resource: 'io', - labelKey: 'startup.main.sqliteMainlineNormalization', - run: async (taskContext) => - runMainlineNormalizationMigration( - { sqlitePresenter: sessionDataMigrationSQLite }, - taskContext - ) + nativeBrowser = new CoreBrowserNative( + yoBrowserPresenter.toolHandler, + (event) => connection.deliverBrowserEvent(event), + async () => { + computerUsePreviewPresenter.shutdown() + await yoBrowserPresenter.shutdown() + await agentPreviewCoordinator.shutdown() }, - 'Failed to start normalization backfill:', - { component: 'sqlite_mainline_normalization', category: 'persistence' } + (error) => connection.invalidateNative(error) ) - - scheduleMainStartupTask( - startupRunId, + nativeHelpers = new CoreHelperNative( { - id: 'main:disabled-agent-tool-capability-cleanup', - target: 'main', - phase: 'background', - resource: 'io', - labelKey: 'startup.main.disabledSearchToolCleanup', - run: async (taskContext) => - runDisabledAgentToolCapabilityCleanupMigration( - { - sqlitePresenter: sessionDataMigrationSQLite, - agentSettings - }, - taskContext + exec: (signal) => + spawnBackgroundExecHost( + path.join(app.getAppPath(), 'out/main/backgroundExecUtilityHost.js'), + signal + ), + code: (signal) => + spawnCodeModeHost(path.join(app.getAppPath(), 'out/main/codeModeUtilityHost.js'), signal), + scheduler: spawnSchedulerHost, + watcher: (kind, signal) => + spawnFileWatcherHost( + path.join(app.getAppPath(), 'out/main/fileWatcherUtilityHost.js'), + kind, + signal ) }, - 'Failed to start disabled agent tool capability cleanup:', - { component: 'disabled_agent_tool_capability_cleanup', category: 'persistence' } + (events) => connection.deliverNativeEvents(events), + (error) => connection.invalidateNative(error) ) - - scheduleMainStartupTask( - startupRunId, - { - id: 'main:toolchain-gc', - target: 'main', - phase: 'background', - resource: 'io', - labelKey: 'startup.main.toolchainGc', - run: async (taskContext) => { - await taskContext.yield() - toolchainService.gcUnreachableTrees() - } - }, - 'Failed to collect unused toolchain trees:', - { component: 'toolchain_gc', category: 'resource' } - ) - } - - const shutdownCoordinator = new MainShutdownCoordinator( - async () => { - shutdownStepFailures = 0 - appLifecycleState = 'stopping' - windowPresenter.setApplicationQuitting(true) - startupWorkloadCoordinator.cancelTarget('main') - await runDestroyStep('acpAuth.shutdown', () => acpAuthService.shutdown()) + const onSystemResume = () => { + if (appLifecycleState !== 'running') return + void connection.host + .systemResume() + .catch((error) => logger.warn('[Scheduler] System resume failed', error)) + } + const onSystemThemeUpdated = () => { + if (appLifecycleState !== 'running') return + void connection + .observeSystemTheme() + .catch((error) => logger.warn('[Settings] System theme observation failed', error)) + } + powerMonitor.on('resume', onSystemResume) + nativeTheme.on('updated', onSystemThemeUpdated) + async function initializeFloatingButton() { try { - await destroy() - } finally { - unsubscribeStartupWorkload() - appLifecycleState = 'stopped' - } - return shutdownStepFailures === 0 ? 'completed' : 'failed' - }, - { - started: (reason) => mainLogger.emit('app.shutdown.started', { reason }), - terminal: (observation) => { - if (observation.outcome === 'completed') { - mainLogger.emit('app.shutdown.terminal', { - outcome: 'completed', - ...(observation.durationMs === undefined ? {} : { durationMs: observation.durationMs }) - }) - return + await floatingButtonPresenter.initialize() + logger.info('FloatingButtonPresenter initialized successfully') + } catch (error) { + reportMainStartupComponentFailure(dependencies.startupRunId, 'floating_widget', 'resource') + console.error('Failed to initialize FloatingButtonPresenter:', error) + } + } + + async function destroy(): Promise { + const httpRetirement = proxyConfig.close() + void httpRetirement.catch(() => {}) + nativeTheme.off('updated', onSystemThemeUpdated) + powerMonitor.off('resume', onSystemResume) + dialogService.destroy() + desktopImageActions.stop() + nativeSettings.stopWrites() + await nativeSettings.drainWrites() + const failures: unknown[] = [] + const retire = async (run: () => Promise) => { + try { + await run() + } catch (error) { + failures.push(error) } - mainLogger.emit('app.shutdown.terminal', { - outcome: 'failed', - ...(observation.durationMs === undefined ? {} : { durationMs: observation.durationMs }), - error: { category: 'unknown' } - }) - }, - actionFailed: (observation) => { - mainLogger.emit('app.shutdown.action.failed', { - reason: observation.reason, - ...(observation.durationMs === undefined ? {} : { durationMs: observation.durationMs }), - error: classifyMainLogError(observation.error) - }) } + await retire(() => connection.beginShutdown()) + if (failures.length === 0) { + if (process.platform === 'darwin') await retire(() => dialogService.drainNativeDialogs()) + floatingButtonPresenter.destroy() + windowPresenter.closeSettingsWindow() + for (const window of windowPresenter.getAllWindows()) { + if (!window.isDestroyed()) window.destroy() + } + await retire(() => dialogService.drainNativeDialogs()) + } + await retire(() => connection.stop()) + await retire(() => nativeHelpers!.close()) + await retire(() => nativeBrowser!.close()) + await retire(() => nativeDesktop!.close()) + await retire(() => connection.closeAfterHostExit()) + if (failures.length) + throw new AggregateError(failures, 'Core and Native owners did not retire') + await runDestroyStep('semanticNotificationRouter.dispose', () => + semanticNotificationRouter.dispose() + ) + await runDestroyStep('semanticNotificationTargets.dispose', () => + semanticNotificationTargets.dispose() + ) + await runDestroyStep('semanticNotificationEpisodes.dispose', () => + semanticNotificationEpisodes.dispose() + ) + await runDestroyStep('semanticNotificationDiagnostics.dispose', () => + semanticNotificationDiagnostics.dispose() + ) + await runDestroyStep('floatingButtonPresenter.destroy', () => + floatingButtonPresenter.destroy() + ) + await runDestroyStep('windowPresenter.destroyFloatingChatWindow', () => + windowPresenter.destroyFloatingChatWindow() + ) + await runDestroyStep('tabPresenter.destroy', () => tabPresenter.destroy()) + await runDestroyStep('windowPresenter.destroyWindows', () => { + windowPresenter.closeSettingsWindow() + for (const window of windowPresenter.getAllWindows()) { + if (!window.isDestroyed()) window.destroy() + } + }) + await runDestroyStep('shortcutPresenter.destroy', () => shortcutPresenter.destroy()) + await runDestroyStep('notificationService.clearAllNotifications', () => + notificationService.clearAllNotifications() + ) + await runDestroyStep('trayPresenter.destroy', () => trayPresenter.destroy()) } - ) - - function stop(reason: MainLogShutdownReason): Promise { - return shutdownCoordinator.request(reason) - } - - function stopForCleanup(): Promise { - return shutdownCoordinator.cleanup() - } - - function assertRouteAllowedDuringDatabaseMaintenance(routeName: string): void { - if (appLifecycleState === 'stopping' || appLifecycleState === 'stopped') { - throw new Error(`App lifecycle is ${appLifecycleState}`) - } - if (databaseMaintenanceState === 'running') return - if (routeName === 'sync.startBackup') throw new Error('sync.tunnel.error.busy') - if ( - routeName.startsWith('chat.') || - routeName.startsWith('sessions.') || - routeName.startsWith('memory.') || - routeName.startsWith('orchestration.') || - routeName.startsWith('remoteControl.') || - routeName.startsWith('cronJobs.') - ) { - throw new Error(`App database maintenance is ${databaseMaintenanceState}`) - } - } - - async function runDatabaseMaintenance( - operation: (database: ApplicationDatabaseMaintenancePort) => Promise - ): Promise { - if (databaseMaintenanceState !== 'running') { - throw new Error(`App database maintenance is ${databaseMaintenanceState}`) - } - if (syncService.isBackupInProgress()) throw new Error('sync.tunnel.error.busy') - databaseMaintenanceState = 'maintenance' - replicaEndpoint.stop() - await automaticSync?.pause() - startupWorkloadCoordinator.cancelTarget('main') - memoryService.stopBackgroundMaintenance() - let operationResult: T | undefined - let operationError: unknown - try { - await liveDelegationService.stop() - await cronJobs.stop() - await remoteService.destroy() - await hookService.stop() - const drain = await memoryIngestionObserver.drainAndFence() - if (drain.timedOut) { - throw new Error( - `Memory ingestion did not drain for sessions: ${drain.pendingSessions.join(', ')}` + async function runDestroyStep( + stepName: string, + step: () => void | Promise + ): Promise { + const startedAt = readMonotonicNow() + logger.info(`[Main] destroy.${stepName} begin`) + try { + await step() + const durationMs = elapsedMonotonicMs(startedAt) + logger.info( + durationMs === undefined + ? `[Main] destroy.${stepName} done` + : `[Main] destroy.${stepName} done durationMs=${durationMs.toFixed(1)}` ) - } - const pendingMaintenanceAgents = await memoryService.drainBackgroundMaintenance() - if (pendingMaintenanceAgents.length > 0) { - throw new Error( - `Memory maintenance did not drain for agents: ${pendingMaintenanceAgents.join(', ')}` + } catch (error) { + shutdownStepFailures += 1 + const durationMs = elapsedMonotonicMs(startedAt) + logger.warn( + durationMs === undefined + ? `[Main] destroy.${stepName} failed` + : `[Main] destroy.${stepName} failed durationMs=${durationMs.toFixed(1)}`, + error ) } - await suspendSessionRuntimes() - operationResult = await operation({ - getDatabasePath: () => mainDatabase.getDatabasePath(), - checkpointAndClose: () => { - const database = mainDatabase.getDatabase() - if (database.open) { - database.pragma('wal_checkpoint(TRUNCATE)') + } + + function registerRoutes(): void { + const desktopRoutes = createDesktopRoutes({ + windowPresenter, + shortcutPresenter, + browserPresenter: yoBrowserPresenter, + tabPresenter, + dialogService + }) + const deviceRoutes = createDeviceRoutes({ + device: deviceService, + restartApplication, + resetDataByType: (resetType) => resetApplicationData(resetType) + }) + const upgradeRoutes = createUpgradeRoutes({ + upgrade: upgradeService + }) + const notificationRoutes = createNotificationRoutes({ + rendererReady: async (webContentsId) => { + const ready = await semanticNotificationTargets.markRendererReady(webContentsId) + if (ready) { + await semanticNotificationRouter.availabilityChanged() } - mainDatabase.close() + return ready }, - close: () => mainDatabase.close(), - reopen: () => reopenApplicationDatabase(), - reopenWithPassword: (password) => { - mainDatabase.reopenWithPassword(password) - }, - isOpen: () => mainDatabase.getDatabase().open, - importLegacyChatDb: (sourceDbPath, mode) => - legacyChatImportService.importFromSourceDb(sourceDbPath, mode) + acknowledgePresentation: (episodeId, webContentsId) => + semanticNotificationRouter.acknowledgePresentation(episodeId, { webContentsId }) + }) + const nativeRoutes = createRouteRegistry([ + desktopRoutes, + deviceRoutes, + upgradeRoutes, + notificationRoutes + ]) + const authorizeRendererIpc = createRendererIpcAuthorizer({ + rendererDirectoryUrl: pathToFileURL(path.join(__dirname, '../renderer/')).href, + developmentServerUrl: app.isPackaged ? undefined : process.env.ELECTRON_RENDERER_URL, + pluginSettingsWindow }) - } catch (error) { - operationError = error + registerDeepchatRoutes( + ipcMain, + async (name, input, context, assertCallerActive) => { + assertCallerActive() + if (isStopping()) throw new Error('Application is stopping') + const handler = nativeRoutes.get(name as DeepchatRouteName) + return handler + ? await handler(input, context) + : await connection.invokeRoute(name, input, context, assertCallerActive) + }, + authorizeRendererIpc + ) + registerClipboardIpc(ipcMain, authorizeRendererIpc) } + function setupApplicationListeners(): void { + app.on('accessibility-support-changed', (_event, enabled) => { + publishDeepchatEvent('appRuntime.accessibilityChanged', { enabled }) + }) - try { - if (!mainDatabase.getDatabase().open) { - reopenApplicationDatabase() - } - deepChatAgentHarness.reconcileAfterDatabaseReopen() - memoryIngestionObserver.resumeIngestion() - memoryService.startBackgroundMaintenance() - hookService.start() - cronJobs.start() - await remoteService.initialize() - liveDelegationService = createLiveDelegationService() - liveDelegationService.start() - const startupRunId = startupWorkloadCoordinator.createRun('main') - scheduleBackgroundWork(startupRunId) - databaseMaintenanceState = 'running' - syncReplica?.wake() - await automaticSync?.start() - } catch (error) { - databaseMaintenanceState = 'failed' - await stopForCleanup() - throw error - } + app.on('browser-window-created', (_, window) => { + optimizer.watchWindowShortcuts(window) + }) - if (operationError) throw operationError - return operationResult as T - } + app.on('activate', () => { + if (appLifecycleState !== 'running') return + if (windowPresenter.restoreMainWindowHiddenByClose()) { + return + } - function reopenApplicationDatabase(): void { - mainDatabase.reopen() - } + if (windowPresenter.getAllWindows().length === 0) { + void windowPresenter.createAppWindow({ initialRoute: 'chat' }) + } + }) - async function suspendSessionRuntimes(): Promise { - const results = await Promise.allSettled( - appSessionService.list({ includeSubagents: true }).map(async (session) => { - const sessionId = toAppSessionId(session.id) - await Promise.all([ - deepChatAgentHarness.cleanupSession(sessionId), - acpAgentRuntime.cleanupSession(sessionId) - ]) + app.on('did-resign-active', () => { + setTimeout(() => { + if (app.isHidden()) { + windowPresenter.clearMainWindowHiddenByClose() + } + }, 0) }) - ) - const failure = results.find( - (result): result is PromiseRejectedResult => result.status === 'rejected' - ) - if (failure) throw failure.reason - } - async function pullLatestBackupFromCloud( - importMode: 'increment' | 'overwrite' = ImportMode.INCREMENT - ) { - const download = await syncService.downloadLatestBackupFromCloud() - if (!download.success || !download.fileName) return download - const backupFileName = download.fileName - const result = await runDatabaseMaintenance((database) => - syncService.importFromSync(backupFileName, importMode, database) - ) - return { ...result, fileName: backupFileName } - } + app.on('browser-window-focus', () => { + if (appLifecycleState === 'stopping' || appLifecycleState === 'stopped') return + shortcutPresenter.registerShortcuts() + upgradeService.handleAppFocus() + void semanticNotificationRouter.availabilityChanged().catch((error) => { + logger.warn('[NotificationRouter] focus reconciliation failed', error) + }) + }) - async function resetApplicationData( - resetType: 'chat' | 'knowledge' | 'config' | 'all' - ): Promise { - let actionClaim: MainShutdownActionClaim | undefined - try { - await coordinateApplicationDataReset(resetType, { - cliLauncher: cliLauncherService, - logger, - stop: async () => { - actionClaim = await stop('data_reset') - if (!actionClaim) throw new Error('Application shutdown is already owned') - }, - resetDataByType: (type) => { - if (!actionClaim) throw new Error('Application shutdown claim is unavailable') - return actionClaim.run(() => deviceService.resetDataByType(type)) - } + app.on('browser-window-blur', () => { + setTimeout(() => { + const isAnyWindowFocused = windowPresenter + .getAllWindows() + .some((window) => !window.isDestroyed() && window.isFocused()) + + if (!isAnyWindowFocused) { + shortcutPresenter.unregisterShortcuts() + } + }, 50) }) - } catch (error) { - actionClaim?.abandon() - throw error } - } - async function restartApplication(): Promise { - const actionClaim = await stop('restart') - if (!actionClaim) throw new Error('Application shutdown is already owned') - await actionClaim.run(() => deviceService.restartApp()) - } - - async function openRemoteSession(sessionId: string): Promise { - const chatWindows = windowPresenter - .getAllWindows() - .filter((window) => tabPresenter.getWindowType(window.id) === 'chat') - const focusedWindow = windowPresenter.getFocusedWindow() - const targetWindow = - focusedWindow && chatWindows.some((window) => window.id === focusedWindow.id) - ? focusedWindow - : chatWindows[0] + const shutdownCoordinator = new MainShutdownCoordinator( + async () => { + shutdownStepFailures = 0 + appLifecycleState = 'stopping' + widgetRoutesReady.reject(new Error('Core business routes are stopping')) + desktopConfigurationHost.close() + deeplinkService.observeMcpReadiness(false) + windowPresenter.setApplicationQuitting(true) + try { + await destroy() + } finally { + appLifecycleState = 'stopped' + } + return shutdownStepFailures === 0 ? 'completed' : 'failed' + }, + { + started: (reason) => mainLogger.emit('app.shutdown.started', { reason }), + terminal: (observation) => { + if (observation.outcome === 'completed') { + mainLogger.emit('app.shutdown.terminal', { + outcome: 'completed', + ...(observation.durationMs === undefined + ? {} + : { durationMs: observation.durationMs }) + }) + return + } + mainLogger.emit('app.shutdown.terminal', { + outcome: 'failed', + ...(observation.durationMs === undefined ? {} : { durationMs: observation.durationMs }), + error: { category: 'unknown' } + }) + }, + actionFailed: (observation) => { + mainLogger.emit('app.shutdown.action.failed', { + reason: observation.reason, + ...(observation.durationMs === undefined ? {} : { durationMs: observation.durationMs }), + error: classifyMainLogError(observation.error) + }) + } + } + ) - if (targetWindow) { - await desktopSessionBinding.activate(targetWindow.webContents.id, sessionId) - windowPresenter.show(targetWindow.id, true) - return true + let resetLauncher: CliLauncherService | undefined + function getResetLauncher(): CliLauncherService { + if (appLifecycleState !== 'stopped') + throw new Error('Core must retire before launcher cleanup') + return (resetLauncher ??= new CliLauncherService({ + homeDirectory: app.getPath('home'), + userDataDirectory: app.getPath('userData'), + environmentPath: process.env.PATH, + shell: process.env.SHELL, + localAppDataDirectory: process.env.LOCALAPPDATA, + resolveCliDirectory: () => + resolveBundledCliDirectory({ + appPath: app.getAppPath(), + resourcesPath: process.resourcesPath, + isPackaged: app.isPackaged + }) + })) + } + function stop(reason: MainLogShutdownReason): Promise { + return shutdownCoordinator.request(reason) } - const createdWindowId = await windowPresenter.createAppWindow({ initialRoute: 'chat' }) - const createdWindow = windowPresenter - .getAllWindows() - .find((window) => window.id === createdWindowId) - if (!createdWindow) return false - - await desktopSessionBinding.activate(createdWindow.webContents.id, sessionId) - windowPresenter.show(createdWindow.id, true) - return true - } + function stopForCleanup(): Promise { + return shutdownCoordinator.cleanup() + } - const control: MainProcessControl = { - focusPrimaryWindow: () => { - const targetWindow = windowPresenter.getAllWindows()[0] - if (!targetWindow || targetWindow.isDestroyed()) { - return + async function resetApplicationData( + resetType: 'chat' | 'knowledge' | 'config' | 'all' + ): Promise { + let actionClaim: MainShutdownActionClaim | undefined + try { + await coordinateApplicationDataReset(resetType, { + cliLauncher: { + getStatus: () => getResetLauncher().getStatus(), + removeOwnedLauncher: () => getResetLauncher().removeOwnedLauncher() + }, + logger, + stop: async () => { + actionClaim = await stop('data_reset') + if (!actionClaim) throw new Error('Application shutdown is already owned') + }, + resetDataByType: (type) => { + if (!actionClaim) throw new Error('Application shutdown claim is unavailable') + return actionClaim.run(() => deviceService.resetDataByType(type)) + } + }) + } catch (error) { + actionClaim?.abandon() + throw error } + } - if (targetWindow.isMinimized()) { - targetWindow.restore() - } - targetWindow.show() - targetWindow.focus() - activateAppOnMac() - }, - handleDeepLink: async (url) => await deeplinkService.handleDeepLink(url), - clearPermissionCaches: () => { - commandPermissionService.clearAll() - filePermissionService.clearAll() - settingsPermissionService.clearAll() - approvalBroker.clear() - dependencies.mcpAppSandboxRegistry.clear() - }, - confirmShutdown: async () => await knowledgeService.confirmShutdown(), - cancelShutdown: () => windowPresenter.setApplicationQuitting(false), - hasMainWindows: () => windowPresenter.getAllWindows().length > 0, - stop, - stopForCleanup - } + async function restartApplication(): Promise { + const actionClaim = await stop('restart') + if (!actionClaim) throw new Error('Application shutdown is already owned') + await actionClaim.run(() => deviceService.restartApp()) + } - dependencies.bindControl(control) - registerRoutes() - deeplinkService.init() - setupApplicationListeners() - await runAcpRegistryMigration() - await runBuiltinMcpAllowlistCompatibilityMigration({ - sqlitePresenter: sessionDataMigrationSQLite, - agentSettings - }) - // Migration must finish before any renderer or background runtime can read a manual Agent scope. - // A failed migration is startup-fatal; continuing would persist an empty active-Skill selection. - await initializeSkills() - await agentSettings.retryPendingDeletedAgentSkillCleanup() + const control: MainProcessControl = { + mcpAppSandbox: connection.mcpAppSandbox, + resolveWorkspacePreviewRequest: (url) => connection.host.resolveWorkspacePreviewRequest(url), + focusPrimaryWindow: () => { + const targetWindow = windowPresenter.getAllWindows()[0] + if (!targetWindow || targetWindow.isDestroyed()) { + return + } - if (windowPresenter.getAllWindows().length === 0) { - const windowId = await windowPresenter.createAppWindow({ initialRoute: 'chat' }) - if (!windowId) { - throw new Error('Failed to create initial app window') + if (targetWindow.isMinimized()) { + targetWindow.restore() + } + targetWindow.show() + targetWindow.focus() + activateAppOnMac() + }, + handleDeepLink: async (url) => await deeplinkService.handleDeepLink(url), + clearPermissionCaches: async () => { + if (!isStopping()) await connection.host.clearPermissionCaches() + }, + confirmShutdown: async () => { + const request = await connection.host.getShutdownConfirmation() + return request === null || (await dialogService.showDialog(request)) === 'confirm' + }, + cancelShutdown: () => windowPresenter.setApplicationQuitting(false), + hasMainWindows: () => windowPresenter.getAllWindows().length > 0, + stop, + stopForCleanup + } + + cleanup = stopForCleanup + dependencies.bindControl(control) + registerRoutes() + deeplinkService.init() + setupApplicationListeners() + const resourcesDirectory = app.isPackaged + ? path.join(process.resourcesPath, 'app.asar.unpacked') + : app.getAppPath() + await connection.configure({ operation: 'initialize' }) + await connection.assembleKernel({ + temporaryDirectory: app.getPath('temp'), + appDataDirectory: app.getPath('appData'), + documentsDirectory: resolveProjectDirectory('documents'), + projectHomeDirectory: resolveProjectDirectory('home'), + resourcesPath: process.resourcesPath, + workingDirectory: process.cwd(), + builtinSkillsDirectories: app.isPackaged + ? [ + path.join(resourcesDirectory, 'resources', 'skills'), + path.join(process.resourcesPath, 'resources', 'skills'), + path.join(process.resourcesPath, 'skills') + ] + : [path.join(resourcesDirectory, 'resources', 'skills')], + providerCatalogPath: path.join(resourcesDirectory, 'resources', 'model-db', 'providers.json'), + allowInsecureTls: isInsecureTlsAllowed() + }) + await connection.startKernel() + widgetRoutesReady.resolve(connection) + if (windowPresenter.getAllWindows().length === 0) { + const windowId = await windowPresenter.createAppWindow({ initialRoute: 'chat' }) + if (!windowId) throw new Error('Failed to create initial app window') } - } - - shortcutPresenter.registerShortcuts() - setupTray() - cronJobs.start() - memoryService.startBackgroundMaintenance() - appLifecycleState = 'running' - try { - await artifactSpool.initialize() - await cliServer.start() - } catch (error) { - reportMainStartupComponentFailure(dependencies.startupRunId, 'cli_control', 'unknown') - logger.error('[CLI] Failed to start local control server', error) - } - try { - await syncHostService.startIfEnabled() - } catch (error) { - reportMainStartupComponentFailure(dependencies.startupRunId, 'sync_host', 'unknown') - logger.error('[SyncHost] Failed to start host mode', error) - } - try { - await automaticSync?.start() + shortcutPresenter.registerShortcuts() + trayPresenter.init() + appLifecycleState = 'running' + onSystemThemeUpdated() + void floatingButtonPresenter.refreshWidgetState() + return control } catch (error) { - logger.error('[SyncReplica] Failed to start automatic sync', error) - } - if (cliServer.getStatus().running) { - try { - await cliLauncherService.ensureInstalled() - } catch (error) { - reportMainStartupComponentFailure(dependencies.startupRunId, 'cli_launcher', 'unknown') - logger.warn('[CLI] Failed to install or refresh the command launcher', error) + if (cleanup) await cleanup() + else { + const results = await Promise.allSettled([ + proxyConfig.close(), + nativeHelpers?.close(), + nativeBrowser?.close(), + nativeDesktop?.close() + ]) + await harnessRuntime.stop() + await core?.closeAfterHostExit() + const failures = results.flatMap((result) => + result.status === 'rejected' ? [result.reason] : [] + ) + if (failures.length) + throw new AggregateError([error, ...failures], 'Core startup did not retire') } + throw error } - init(dependencies.startupRunId) - scheduleBackgroundWork(dependencies.startupRunId) - return control } diff --git a/src/main/app/credentialCrypto.ts b/src/main/app/credentialCrypto.ts new file mode 100644 index 0000000000..159d2e465b --- /dev/null +++ b/src/main/app/credentialCrypto.ts @@ -0,0 +1,22 @@ +import { safeStorage } from 'electron' +import type { CredentialCryptoPort } from '@/config/secretStore' + +export const credentialCrypto: CredentialCryptoPort = { + isAvailable: () => safeStorage.isAsyncEncryptionAvailable(), + encrypt: async (value) => (await safeStorage.encryptStringAsync(value)).toString('base64'), + decrypt: async (wrapped) => + (await safeStorage.decryptStringAsync(Buffer.from(wrapped, 'base64'))).result +} + +export const secureCredentialCrypto: CredentialCryptoPort = { + ...credentialCrypto, + isAvailable: async () => { + if (!(await credentialCrypto.isAvailable())) return false + if (process.platform !== 'linux') return true + try { + return safeStorage.getSelectedStorageBackend() !== 'basic_text' + } catch { + return false + } + } +} diff --git a/src/main/app/databaseInitializer.ts b/src/main/app/databaseInitializer.ts index 9e40e0846a..d414f31725 100644 --- a/src/main/app/databaseInitializer.ts +++ b/src/main/app/databaseInitializer.ts @@ -1,8 +1,6 @@ import logger from '@shared/logger' import type { DatabaseSchemaDiagnosis, DatabaseSchemaIssue } from '@shared/types/databaseSchema' import type { DatabaseRepairReason } from '@shared/notifications' -import { app } from 'electron' -import path from 'path' import { isDestructiveDatabaseError, repairSQLiteDatabaseFile, @@ -16,7 +14,7 @@ import { elapsedMonotonicMs, readMonotonicNow, type MonotonicClock } from '@/lib type DatabaseInitializerOptions = { password?: string - dbPath?: string + dbPath: string observe?: (observation: DatabaseInitializationObservation) => void | Promise now?: MonotonicClock } @@ -65,13 +63,11 @@ export class DatabaseInitializer implements IDatabaseInitializer { private readonly observe: (observation: DatabaseInitializationObservation) => void | Promise private readonly now?: MonotonicClock - constructor(options?: DatabaseInitializerOptions) { - // Initialize database path - const dbDir = path.join(app.getPath('userData'), 'app_db') - this.dbPath = options?.dbPath ?? path.join(dbDir, 'agent.db') - this.password = options?.password - this.observe = options?.observe ?? (() => undefined) - this.now = options?.now + constructor(options: DatabaseInitializerOptions) { + this.dbPath = options.dbPath + this.password = options.password + this.observe = options.observe ?? (() => undefined) + this.now = options.now } /** diff --git a/src/main/app/databaseRequestGate.ts b/src/main/app/databaseRequestGate.ts new file mode 100644 index 0000000000..d62a4aeba9 --- /dev/null +++ b/src/main/app/databaseRequestGate.ts @@ -0,0 +1,165 @@ +import type { DeepchatRouteName } from '@shared/contracts/routes' + +const PRESENTATION_ROUTES = new Set([ + 'window.getCurrentState', + 'window.getRuntimeIdentity', + 'window.minimizeCurrent', + 'window.toggleMaximizeCurrent', + 'window.closeCurrent', + 'window.closeFloatingCurrent', + 'window.closeSettings', + 'device.getInfo', + 'device.getAppVersion', + 'databaseSecurity.getStatus', + 'approvals.resolve', + 'cli.status', + 'cli.version', + 'cli.capabilities' +] satisfies DeepchatRouteName[]) + +const MAINTENANCE_ROUTES = new Set([ + 'sync.import', + 'sync.pullFromCloud', + 'databaseSecurity.enable', + 'databaseSecurity.changePassword', + 'databaseSecurity.disable', + 'databaseSecurity.repairSchema' +] satisfies DeepchatRouteName[]) + +const SHUTDOWN_ROUTES = new Set([ + 'device.restartApp', + 'device.resetDataByType' +] satisfies DeepchatRouteName[]) + +export class DatabaseRequestGate { + private currentState: 'running' | 'maintenance' | 'failed' | 'stopping' = 'running' + private activeRequests = 0 + private drainPromise: Promise | null = null + private onDrained: (() => void) | null = null + private maintenanceCompletion: Promise | null = null + + constructor(private readonly assertActive: () => void) {} + + get state(): 'running' | 'maintenance' | 'failed' | 'stopping' { + return this.currentState + } + + assertRouteAllowed(method: string): void { + this.assertActive() + if (this.currentState === 'stopping') throw new Error('App database owner is stopping') + if (this.currentState === 'running' || PRESENTATION_ROUTES.has(method)) return + if (method === 'sync.startBackup') throw new Error('sync.tunnel.error.busy') + throw new Error(`App database maintenance is ${this.currentState}`) + } + + async runRequest(method: string, run: () => Promise): Promise { + this.assertRouteAllowed(method) + if ( + PRESENTATION_ROUTES.has(method) || + MAINTENANCE_ROUTES.has(method) || + SHUTDOWN_ROUTES.has(method) + ) + return await run() + return await this.trackRequest(run) + } + + async runAuthorization(method: string, run: () => Promise): Promise { + this.assertRouteAllowed(method) + if (PRESENTATION_ROUTES.has(method)) return await run() + return await this.trackRequest(run) + } + + private async trackRequest(run: () => Promise): Promise { + this.activeRequests++ + try { + return await run() + } finally { + if (--this.activeRequests === 0) { + const onDrained = this.onDrained + this.onDrained = null + onDrained?.() + } + } + } + + beginMaintenance(): void { + this.assertActive() + if (this.currentState !== 'running') + throw new Error(`App database maintenance is ${this.currentState}`) + this.currentState = 'maintenance' + } + + async runMaintenance(run: () => Promise): Promise { + if (this.maintenanceCompletion) throw new Error('Database maintenance is still completing') + this.beginMaintenance() + const pending = Promise.resolve().then(run) + const finished = pending.then( + () => undefined, + () => undefined + ) + this.maintenanceCompletion = finished + try { + return await pending + } finally { + if (this.maintenanceCompletion === finished) this.maintenanceCompletion = null + } + } + + beginShutdown(): void { + this.currentState = 'stopping' + } + + async drain(): Promise { + if (this.currentState !== 'maintenance') throw new Error('Database maintenance is not active') + await this.drainRequests() + } + + async drainForShutdown(): Promise { + if (this.currentState !== 'stopping') throw new Error('Database shutdown is not active') + let timeout: NodeJS.Timeout | undefined + try { + await Promise.race([ + Promise.all([this.drainRequests(), this.maintenanceCompletion]), + new Promise((_, reject) => { + timeout = setTimeout( + () => reject(new Error('Timed out waiting for database shutdown (30000ms)')), + 30_000 + ) + }) + ]) + } finally { + clearTimeout(timeout) + } + } + + private async drainRequests(): Promise { + if (this.activeRequests === 0) return + if (!this.drainPromise) { + const pending = new Promise((resolve, reject) => { + const onDrained = () => { + clearTimeout(timeout) + resolve() + } + const timeout = setTimeout(() => { + if (this.onDrained === onDrained) this.onDrained = null + reject(new Error('Timed out waiting for database requests to drain (30000ms)')) + }, 30_000) + this.onDrained = onDrained + }).finally(() => { + if (this.drainPromise === pending) this.drainPromise = null + }) + this.drainPromise = pending + } + await this.drainPromise + } + + resume(): void { + if (this.currentState !== 'maintenance' || this.drainPromise) + throw new Error('Database maintenance cannot resume') + this.currentState = 'running' + } + + fail(): void { + this.currentState = 'failed' + } +} diff --git a/src/main/app/databaseSecurity.ts b/src/main/app/databaseSecurity.ts index ef253153cb..376a516139 100644 --- a/src/main/app/databaseSecurity.ts +++ b/src/main/app/databaseSecurity.ts @@ -1,6 +1,5 @@ import type { ProviderSettingsPort } from '@/provider/settings' -import { app, safeStorage } from 'electron' -import ElectronStore from 'electron-store' +import type { CredentialCryptoPort } from '@/config/secretStore' import fs from 'fs' import path from 'path' import Database from 'better-sqlite3-multiple-ciphers' @@ -16,7 +15,7 @@ import { import { shouldExcludeFromSqliteCopy } from '@/data/sqliteCopyExclusions' import { orderSqliteTablesForCopy } from '@/data/sqliteCopyOrder' -type DatabaseSecurityMetadata = { +export type DatabaseSecurityMetadata = { version: 1 enabled: boolean cipher: 'sqlcipher' @@ -27,6 +26,15 @@ type DatabaseSecurityMetadata = { lastMigrationDirection?: 'enable' | 'change-password' | 'disable' } +export interface DatabaseSecurityCryptoPort extends CredentialCryptoPort { + getBackend(): Promise +} + +export interface DatabaseSecurityMetadataStore { + get(key: 'metadata'): DatabaseSecurityMetadata | undefined + set(key: 'metadata', value: DatabaseSecurityMetadata): void +} + type UnlockRequest = { reason: DatabaseUnlockReason safeStorageAvailable: boolean @@ -48,7 +56,7 @@ export interface DatabaseSecurityMigrationDatabasePort { isOpen(): boolean } -const DEFAULT_METADATA: DatabaseSecurityMetadata = { +export const DEFAULT_DATABASE_SECURITY_METADATA: DatabaseSecurityMetadata = { version: 1, enabled: false, cipher: 'sqlcipher', @@ -98,70 +106,103 @@ function isFtsMaintenanceSchemaObject(row: SqliteSchemaRow & { tbl_name?: string } export class DatabaseSecurityService { - private readonly store: ElectronStore<{ metadata: DatabaseSecurityMetadata }> + private readonly store: DatabaseSecurityMetadataStore private readonly dbPath: string private migrationInProgress = false - - constructor(options?: { dbPath?: string }) { - const dbDir = path.join(app.getPath('userData'), 'app_db') - this.dbPath = options?.dbPath ?? path.join(dbDir, 'agent.db') - this.store = new ElectronStore<{ metadata: DatabaseSecurityMetadata }>({ - name: 'database-security', - defaults: { - metadata: DEFAULT_METADATA - } - }) + private generation = 0 + private stopped = false + + constructor( + private readonly options: { + dbPath: string + metadata: DatabaseSecurityMetadataStore + crypto: DatabaseSecurityCryptoPort + } + ) { + this.dbPath = options.dbPath + this.store = options.metadata + if (this.isMigrationInProgress(this.dbPath)) + throw new Error('Database migration is already in progress') this.recoverInterruptedMigrationFiles() } + stop(): void { + this.stopped = true + this.generation++ + } + + private captureOperation(): () => void { + const generation = this.generation + const check = () => { + if (this.stopped || generation !== this.generation) + throw new Error('Database security operation interrupted') + } + check() + return check + } + getDatabasePath(): string { return this.dbPath } - private rememberPasswordAfterUnlock( + private async rememberPasswordAfterUnlock( password: string, metadata: DatabaseSecurityMetadata, - safeStorageAvailable: boolean - ): void { + safeStorageAvailable: boolean, + check: () => void + ): Promise { if (!safeStorageAvailable) { return } + const wrappedPassword = await this.options.crypto.encrypt(password) + check() + const safeStorageBackend = await this.getSafeStorageBackend() + check() this.persistMetadata({ ...metadata, passwordStorage: 'safeStorage', - wrappedPassword: this.wrapPassword(password), - safeStorageBackend: this.getSafeStorageBackend() + wrappedPassword, + safeStorageBackend }) } - persistRecoveredEncryptionMetadata(password: string): void { - this.assertPassword(password) - const safeStorageAvailable = this.isSafeStorageAvailable() - const metadata = this.getMetadata() - this.persistMetadata({ - version: 1, - enabled: true, - cipher: 'sqlcipher', - passwordStorage: safeStorageAvailable ? 'safeStorage' : 'manual', - wrappedPassword: safeStorageAvailable ? this.wrapPassword(password) : undefined, - safeStorageBackend: this.getSafeStorageBackend(), - lastMigrationAt: metadata.lastMigrationAt, - lastMigrationDirection: metadata.lastMigrationDirection + async persistRecoveredEncryptionMetadata(password: string): Promise { + await this.runMigration(async (check) => { + this.assertPassword(password) + const metadata = this.getMetadata() + const recovered = await this.prepareMetadata(password, { + version: 1, + enabled: true, + cipher: 'sqlcipher', + passwordStorage: 'manual', + lastMigrationAt: metadata.lastMigrationAt, + lastMigrationDirection: metadata.lastMigrationDirection + }) + check() + this.persistMetadata(recovered) }) } clearEncryptionMetadata(): void { - this.persistMetadata({ ...DEFAULT_METADATA }) + this.captureOperation() + if (this.isMigrationInProgress(this.dbPath)) + throw new Error('Database migration is already in progress') + this.generation++ + this.persistMetadata({ ...DEFAULT_DATABASE_SECURITY_METADATA }) } - getStatus(): DatabaseSecurityStatus { + async getStatus(): Promise { + const check = this.captureOperation() + const safeStorageAvailable = await this.isSafeStorageAvailable() + check() + const safeStorageBackend = await this.getSafeStorageBackend() + check() const metadata = this.getMetadata() - const safeStorageAvailable = this.isSafeStorageAvailable() return { enabled: metadata.enabled, cipher: 'sqlcipher', safeStorageAvailable, - safeStorageBackend: this.getSafeStorageBackend(), + safeStorageBackend, passwordStorage: metadata.passwordStorage, manualUnlockRequired: metadata.enabled && @@ -174,12 +215,16 @@ export class DatabaseSecurityService { } async resolveStartupPassword(unlockProvider: UnlockProvider): Promise { + if (this.isMigrationInProgress(this.dbPath)) + throw new Error('Database migration is already in progress') + const check = this.captureOperation() const metadata = this.getMetadata() if (!metadata.enabled) { return undefined } - const safeStorageAvailable = this.isSafeStorageAvailable() + const safeStorageAvailable = await this.isSafeStorageAvailable() + check() let safeStorageUnlockFailed = false if ( safeStorageAvailable && @@ -188,10 +233,12 @@ export class DatabaseSecurityService { ) { let password: string | undefined try { - password = this.unwrapPassword(metadata.wrappedPassword) + password = await this.options.crypto.decrypt(metadata.wrappedPassword) + check() this.validatePassword(password) return password } catch (error) { + check() if (password !== undefined && isDecryptedDatabaseCorruptionError(error)) { return password } @@ -211,25 +258,42 @@ export class DatabaseSecurityService { while (true) { const password = await unlockProvider({ reason, safeStorageAvailable }) + check() if (password === null) { - app.quit() throw new Error('Database unlock canceled') } try { this.validatePassword(password) - this.rememberPasswordAfterUnlock(password, metadata, safeStorageAvailable) - return password } catch (error) { if (isDecryptedDatabaseCorruptionError(error)) { - this.rememberPasswordAfterUnlock(password, metadata, safeStorageAvailable) + await this.rememberPasswordAfterUnlock(password, metadata, safeStorageAvailable, check) + check() return password } if (error instanceof OrphanWalDatabaseError) { throw error } reason = 'invalid' + continue } + await this.rememberPasswordAfterUnlock(password, metadata, safeStorageAvailable, check) + check() + return password + } + } + + private async runMigration(operation: (check: () => void) => Promise): Promise { + this.captureOperation() + this.acquireMigrationLock(this.dbPath) + this.generation++ + this.migrationInProgress = true + const check = this.captureOperation() + try { + return await operation(check) + } finally { + this.migrationInProgress = false + this.releaseMigrationLock(this.dbPath) } } @@ -238,20 +302,27 @@ export class DatabaseSecurityService { database: DatabaseSecurityMigrationDatabasePort providerSettings: ProviderEncryptionCleanupPort }): Promise { - this.assertPassword(input.password) - const metadata = this.getMetadata() - if (metadata.enabled) { - throw new Error('Database encryption is already enabled') - } - - this.cleanupLegacyProviderJson(input.providerSettings) - await this.migrateDatabase({ - database: input.database, - sourcePassword: undefined, - targetPassword: input.password, - direction: 'enable' + await this.runMigration(async (check) => { + this.assertPassword(input.password) + if (this.getMetadata().enabled) throw new Error('Database encryption is already enabled') + const metadata = await this.prepareMetadata(input.password, { + version: 1, + enabled: true, + cipher: 'sqlcipher', + passwordStorage: 'manual', + lastMigrationAt: Date.now(), + lastMigrationDirection: 'enable' + }) + check() + this.cleanupLegacyProviderJson(input.providerSettings) + this.migrateDatabase({ + database: input.database, + sourcePassword: undefined, + targetPassword: input.password, + direction: 'enable', + metadata + }) }) - this.persistUnlockedMetadata(input.password, 'enable') return this.getStatus() } @@ -261,22 +332,31 @@ export class DatabaseSecurityService { database: DatabaseSecurityMigrationDatabasePort providerSettings: ProviderEncryptionCleanupPort }): Promise { - this.assertEnabled() - this.assertPassword(input.currentPassword) - this.assertPassword(input.newPassword) - if (input.currentPassword === input.newPassword) { - throw new Error('New SQLite password must differ from the current password') - } - this.validatePassword(input.currentPassword) - - this.cleanupLegacyProviderJson(input.providerSettings) - await this.migrateDatabase({ - database: input.database, - sourcePassword: input.currentPassword, - targetPassword: input.newPassword, - direction: 'change-password' + await this.runMigration(async (check) => { + this.assertEnabled() + this.assertPassword(input.currentPassword) + this.assertPassword(input.newPassword) + if (input.currentPassword === input.newPassword) + throw new Error('New SQLite password must differ from the current password') + this.validatePassword(input.currentPassword) + const metadata = await this.prepareMetadata(input.newPassword, { + version: 1, + enabled: true, + cipher: 'sqlcipher', + passwordStorage: 'manual', + lastMigrationAt: Date.now(), + lastMigrationDirection: 'change-password' + }) + check() + this.cleanupLegacyProviderJson(input.providerSettings) + this.migrateDatabase({ + database: input.database, + sourcePassword: input.currentPassword, + targetPassword: input.newPassword, + direction: 'change-password', + metadata + }) }) - this.persistUnlockedMetadata(input.newPassword, 'change-password') return this.getStatus() } @@ -285,20 +365,22 @@ export class DatabaseSecurityService { database: DatabaseSecurityMigrationDatabasePort providerSettings: ProviderEncryptionCleanupPort }): Promise { - this.assertEnabled() - this.assertPassword(input.currentPassword) - this.validatePassword(input.currentPassword) - - await this.migrateDatabase({ - database: input.database, - sourcePassword: input.currentPassword, - targetPassword: undefined, - direction: 'disable' - }) - this.persistMetadata({ - ...DEFAULT_METADATA, - lastMigrationAt: Date.now(), - lastMigrationDirection: 'disable' + await this.runMigration(async (check) => { + this.assertEnabled() + this.assertPassword(input.currentPassword) + this.validatePassword(input.currentPassword) + check() + this.migrateDatabase({ + database: input.database, + sourcePassword: input.currentPassword, + targetPassword: undefined, + direction: 'disable', + metadata: { + ...DEFAULT_DATABASE_SECURITY_METADATA, + lastMigrationAt: Date.now(), + lastMigrationDirection: 'disable' + } + }) }) return this.getStatus() } @@ -312,15 +394,17 @@ export class DatabaseSecurityService { } } - private async migrateDatabase(input: { + private migrateDatabase(input: { database: DatabaseSecurityMigrationDatabasePort sourcePassword: string | undefined targetPassword: string | undefined direction: MigrationDirection - }): Promise { + metadata: DatabaseSecurityMetadata + }): void { const dbPath = input.database.getDatabasePath() - this.acquireMigrationLock(dbPath) - this.migrationInProgress = true + if (getMigrationLockPath(dbPath) !== getMigrationLockPath(this.dbPath)) + throw new Error('Database security owner path mismatch') + const previousMetadata = this.getMetadata() const tempPath = this.getTempPath(dbPath) const rollbackPath = this.getRollbackPath(dbPath) @@ -339,10 +423,12 @@ export class DatabaseSecurityService { try { input.database.reopenWithPassword(input.targetPassword) + this.persistMetadata(input.metadata) } catch (error) { input.database.close() this.restoreRollbackDatabase(dbPath, rollbackPath) input.database.reopenWithPassword(input.sourcePassword) + this.persistMetadata(previousMetadata) throw error } @@ -362,9 +448,6 @@ export class DatabaseSecurityService { } } throw error - } finally { - this.migrationInProgress = false - this.releaseMigrationLock(dbPath) } } @@ -620,25 +703,30 @@ export class DatabaseSecurityService { return counts } - private persistUnlockedMetadata(password: string, direction: MigrationDirection): void { - const safeStorageAvailable = this.isSafeStorageAvailable() - this.persistMetadata({ - version: 1, - enabled: true, - cipher: 'sqlcipher', - passwordStorage: safeStorageAvailable ? 'safeStorage' : 'manual', - wrappedPassword: safeStorageAvailable ? this.wrapPassword(password) : undefined, - safeStorageBackend: this.getSafeStorageBackend(), - lastMigrationAt: Date.now(), - lastMigrationDirection: direction - }) + private async prepareMetadata( + password: string, + metadata: DatabaseSecurityMetadata + ): Promise { + const check = this.captureOperation() + const available = await this.isSafeStorageAvailable() + check() + const backend = await this.getSafeStorageBackend() + check() + const wrapped = available ? await this.options.crypto.encrypt(password) : undefined + check() + return { + ...metadata, + passwordStorage: available ? 'safeStorage' : 'manual', + wrappedPassword: wrapped, + safeStorageBackend: backend + } } private getMetadata(): DatabaseSecurityMetadata { const raw = this.store.get('metadata') return { - ...DEFAULT_METADATA, - ...clone(raw ?? DEFAULT_METADATA), + ...DEFAULT_DATABASE_SECURITY_METADATA, + ...clone(raw ?? DEFAULT_DATABASE_SECURITY_METADATA), cipher: 'sqlcipher', version: 1 } @@ -652,25 +740,17 @@ export class DatabaseSecurityService { providerSettings.cleanupLegacyProviderJsonForDatabaseEncryption() } - private wrapPassword(password: string): string { - return Buffer.from(safeStorage.encryptString(password)).toString('base64') - } - - private unwrapPassword(wrappedPassword: string): string { - return safeStorage.decryptString(Buffer.from(wrappedPassword, 'base64')) - } - - private isSafeStorageAvailable(): boolean { + private async isSafeStorageAvailable(): Promise { try { - return safeStorage.isEncryptionAvailable() + return await this.options.crypto.isAvailable() } catch { return false } } - private getSafeStorageBackend(): string | undefined { + private async getSafeStorageBackend(): Promise { try { - return process.platform === 'linux' ? safeStorage.getSelectedStorageBackend() : undefined + return await this.options.crypto.getBackend() } catch { return undefined } diff --git a/src/main/app/databaseStartup.ts b/src/main/app/databaseStartup.ts index a61c49ee7b..d7fdaf58fb 100644 --- a/src/main/app/databaseStartup.ts +++ b/src/main/app/databaseStartup.ts @@ -1,7 +1,11 @@ import logger from '@shared/logger' -import { app } from 'electron' import type { MainDatabase } from '@/data/mainDatabase' -import type { DatabaseStartupFailureKind } from '@shared/contracts/databaseSecurity' +import type { + DatabaseStartupFailureKind, + DatabaseUnlockReason, + DatabaseRecoveryRequestPayload, + DatabaseRecoveryChoice +} from '@shared/contracts/databaseSecurity' import { allocateQuarantineDirectory, classifyDatabaseStartupFailure, @@ -11,11 +15,21 @@ import { } from '@/data/databaseStartupRecovery' import { DatabaseInitializer, type DatabaseInitializationObservation } from './databaseInitializer' import type { DatabaseSecurityService } from './databaseSecurity' -import type { SplashWindow } from './splashWindow' + +export interface DatabaseStartupPresentation { + requestDatabaseUnlock(payload: { + reason: DatabaseUnlockReason + safeStorageAvailable: boolean + }): Promise + requestDatabaseRecovery( + payload: Omit + ): Promise + quit(): Promise +} export async function initializeMainDatabaseWithRecovery(input: { security: DatabaseSecurityService - splash: SplashWindow + splash: DatabaseStartupPresentation observe?: (observation: DatabaseInitializationObservation) => void | Promise }): Promise { const dbPath = input.security.getDatabasePath() @@ -42,9 +56,11 @@ export async function initializeMainDatabaseWithRecovery(input: { while (true) { if (!passwordResolved) { try { - password = await input.security.resolveStartupPassword((request) => - input.splash.requestDatabaseUnlock(request) - ) + password = await input.security.resolveStartupPassword(async (request) => { + const value = await input.splash.requestDatabaseUnlock(request) + if (value === null) await input.splash.quit() + return value + }) passwordResolved = true } catch (error) { const kind = classifyDatabaseStartupFailure({ error, dbPath, password }) @@ -64,7 +80,7 @@ export async function initializeMainDatabaseWithRecovery(input: { }) if (!choice) { - app.quit() + await input.splash.quit() throw new Error('Database recovery canceled') } @@ -75,7 +91,7 @@ export async function initializeMainDatabaseWithRecovery(input: { if (isDecryptedDatabaseCorruptionError(error)) { password = choice.password passwordResolved = true - input.security.persistRecoveredEncryptionMetadata(choice.password) + await input.security.persistRecoveredEncryptionMetadata(choice.password) pending = { ...pending, kind: 'true-corruption', @@ -99,7 +115,7 @@ export async function initializeMainDatabaseWithRecovery(input: { password = choice.password passwordResolved = true - input.security.persistRecoveredEncryptionMetadata(choice.password) + await input.security.persistRecoveredEncryptionMetadata(choice.password) pending = undefined continue } diff --git a/src/main/app/fileWatcherHost.ts b/src/main/app/fileWatcherHost.ts new file mode 100644 index 0000000000..cba6dac104 --- /dev/null +++ b/src/main/app/fileWatcherHost.ts @@ -0,0 +1,83 @@ +import path from 'node:path' +import { utilityProcess } from 'electron' +import type { FileWatcherHost } from '@/platform/fileWatcher/watcherHostClient' +import type { WatcherHostKind } from '@/platform/fileWatcher/watcherTypes' + +export async function spawnFileWatcherHost( + entry: string, + kind: WatcherHostKind, + signal: AbortSignal +): Promise { + if (!path.isAbsolute(entry)) throw new Error('File watcher helper entry must be absolute') + signal.throwIfAborted() + const host = utilityProcess.fork(entry, ['--deepchat-file-watcher-host'], { + serviceName: `DeepChat ${kind === 'git' ? 'Git' : 'Content'} File Watcher`, + stdio: 'ignore', + env: { + ...process.env, + DEEPCHAT_FILE_WATCHER_HOST: '1', + DEEPCHAT_FILE_WATCHER_HOST_KIND: kind + } + }) + const closed = Promise.withResolvers() + let exited = false + let termination: Promise | undefined + host.once('exit', () => { + exited = true + closed.resolve() + }) + const terminate = (): Promise => { + termination ??= (async () => { + try { + if (!exited) host.kill() + } finally { + await closed.promise + } + })() + return termination + } + return await new Promise((resolve, reject) => { + let settled = false + const settle = (callback: () => void) => { + if (settled) return + settled = true + host.off('spawn', onSpawn) + host.off('exit', onExit) + host.off('error', onError) + signal.removeEventListener('abort', onAbort) + callback() + } + const fail = (error: unknown) => + settle(() => { + void terminate().then(() => reject(error), reject) + }) + const onSpawn = () => + settle(() => + resolve({ + postMessage(message) { + host.postMessage(message) + }, + onMessage(listener) { + host.on('message', listener) + }, + onExit(listener) { + host.on('exit', listener) + }, + onError(listener) { + host.on('error', listener) + }, + terminate + }) + ) + const onExit = (code: number) => + fail(new Error(`File watcher helper exited before spawn: ${code}`)) + const onError = (type: string, location: string) => + fail(new Error(`File watcher helper error: ${type} at ${location}`)) + const onAbort = () => fail(signal.reason) + host.once('spawn', onSpawn) + host.once('exit', onExit) + host.once('error', onError) + signal.addEventListener('abort', onAbort, { once: true }) + if (signal.aborted) onAbort() + }) +} diff --git a/src/main/app/logging.ts b/src/main/app/logging.ts index 3d837a7881..f0b2a1f09c 100644 --- a/src/main/app/logging.ts +++ b/src/main/app/logging.ts @@ -1,38 +1,36 @@ -import { app, shell } from 'electron' -import fs from 'fs' -import path from 'path' import type { SettingsStore } from '@/config/settingsStore' import type { DeepchatEventPublisher } from '@shared/contracts/events' +export interface LoggingHost { + applyLogging(enabled: boolean): Promise + openLoggingFolder(): Promise +} + export class LoggingService { constructor( private readonly settings: SettingsStore, - private readonly restartApp: () => void, + private readonly host: LoggingHost, private readonly publishEvent: DeepchatEventPublisher, - private readonly setMainPersistenceEnabled: (enabled: boolean) => void + private readonly committed: () => void | Promise ) {} getEnabled(): boolean { return this.settings.get('loggingEnabled') ?? false } - setEnabled(enabled: boolean): void { + async setEnabled(enabled: boolean): Promise { const value = Boolean(enabled) this.settings.set('loggingEnabled', value) - this.setMainPersistenceEnabled(value) + await this.committed() + await this.host.applyLogging(value) this.publishEvent('settings.changed', { changedKeys: ['loggingEnabled'], version: Date.now(), values: { loggingEnabled: value } }) - setTimeout(() => this.restartApp(), 1000) } async openFolder(): Promise { - const loggingFolderPath = path.join(app.getPath('userData'), 'logs') - if (!fs.existsSync(loggingFolderPath)) { - fs.mkdirSync(loggingFolderPath, { recursive: true }) - } - await shell.openPath(loggingFolderPath) + await this.host.openLoggingFolder() } } diff --git a/src/main/app/mainProcess.ts b/src/main/app/mainProcess.ts index 41f7e2d639..bfbe55a911 100644 --- a/src/main/app/mainProcess.ts +++ b/src/main/app/mainProcess.ts @@ -1,125 +1,37 @@ import { electronApp } from '@electron-toolkit/utils' -import { app } from 'electron' -import { createSettingsStore } from '@/config/settingsStore' -import { SecretStore } from '@/config/secretStore' -import { DatabaseSecurityService } from './databaseSecurity' -import { proxyConfig } from '@/platform/proxy' -import type { StartupWorkloadCoordinator } from '@/app/startupWorkloadCoordinator' import { createMainProcessControl, type MainProcessControl } from './composition' -import { initializeMainDatabaseWithRecovery } from './databaseStartup' import { registerProtocols } from './protocols' -import { McpAppSandboxRegistry } from '@/mcp/apps/sandboxRegistry' import { SplashWindow } from './splashWindow' -import { PrivacySettings } from './privacy' -import { ProxySettings } from '@/platform/proxySettings' -import { McpSettings } from '@/mcp/settings' -import { AcpCatalogSettings } from '@/agent/acp/catalog/settings' -import { SettingsDatabase } from '@/settings/data/database' -import { migrateConfigStorage } from '@/config/migration' -import { ProviderDatabase } from '@/provider/data/database' -import { McpDatabase } from '@/mcp/data/database' -import { AgentDatabase } from '@/agent/data/database' -import { mainLogger, setMainLoggingEnabled } from '@/logging' export type { MainProcessControl } from './composition' export async function startMainProcess( - startupWorkloadCoordinator: StartupWorkloadCoordinator, startupRunId: string, - requestUpdateInstall: (installAction: () => void) => Promise + requestUpdateInstall: (installAction: () => void) => Promise, + signal: AbortSignal ): Promise { + signal.throwIfAborted() const splashWindow = new SplashWindow() let mainProcess: MainProcessControl | undefined - let database: Awaited> | undefined - - await splashWindow.create() - + const cancelStartup = () => splashWindow.cancelDatabaseStartup() + signal.addEventListener('abort', cancelStartup, { once: true }) try { + await splashWindow.create() + signal.throwIfAborted() electronApp.setAppUserModelId('com.wefonk.deepchat') - const settingsStore = createSettingsStore() - setMainLoggingEnabled(settingsStore.get('loggingEnabled') ?? false) - const secretStore = new SecretStore(settingsStore) - const privacySettings = new PrivacySettings(settingsStore) - const proxySettings = new ProxySettings(settingsStore) - const mcpSettings = new McpSettings(secretStore) - const mcpAppSandboxRegistry = new McpAppSandboxRegistry() - const acpCatalogSettings = new AcpCatalogSettings({ mcpSettings }) - const databaseSecurityService = new DatabaseSecurityService() - const securityStatus = databaseSecurityService.getStatus() - splashWindow.showDatabaseUnlockProgress( + const mcpAppDesktop = await registerProtocols( { - active: securityStatus.enabled, - safeStorageAvailable: securityStatus.safeStorageAvailable + getProtocolPolicy: async (instanceId) => + (await mainProcess?.mcpAppSandbox.getProtocolPolicy(instanceId)) ?? null, + requestBrowserPermission: async (...args) => + (await mainProcess?.mcpAppSandbox.requestBrowserPermission(...args)) ?? false }, - { skipDelay: securityStatus.enabled } + async (url) => (await mainProcess?.resolveWorkspacePreviewRequest(url)) ?? null ) - database = await initializeMainDatabaseWithRecovery({ - security: databaseSecurityService, - splash: splashWindow, - observe: (observation) => { - const context = { - ...(observation.durationMs === undefined ? {} : { durationMs: observation.durationMs }), - repairAttempted: observation.repairAttempted, - schemaDiagnosis: observation.schemaDiagnosis, - repairableIssueCount: observation.repairableIssueCount, - manualIssueCount: observation.manualIssueCount - } - if (observation.outcome === 'failed') { - mainLogger.emit('database.initialization.terminal', { - ...context, - outcome: 'failed', - error: observation.error - }) - } else { - mainLogger.emit('database.initialization.terminal', { - ...context, - outcome: 'completed' - }) - } - } - }) - splashWindow.showDatabaseUnlockProgress({ - active: false, - safeStorageAvailable: databaseSecurityService.getStatus().safeStorageAvailable - }) - const settingsDatabase = new SettingsDatabase(database) - const providerDatabase = new ProviderDatabase(database) - const mcpDatabase = new McpDatabase(database) - const agentDatabase = new AgentDatabase(database) - const configMigration = migrateConfigStorage({ - database: settingsDatabase, - providerDatabase, - mcpDatabase, - agentDatabase, - settings: settingsStore, - mcpSettings: mcpSettings.getMigrationSnapshot(), - acpCatalog: acpCatalogSettings.getMigrationSnapshot(), - userDataPath: app.getPath('userData') - }) - settingsStore.attachDatabase(settingsDatabase) - mcpSettings.connectDatabase(mcpDatabase) - acpCatalogSettings.connectDatabase(agentDatabase) - if (configMigration.appVersionChanged) { - mcpSettings.onUpgrade(configMigration.previousAppVersion) - } - proxyConfig.initFromConfig(proxySettings.getMode(), proxySettings.getCustomUrl()) - await registerProtocols(mcpAppSandboxRegistry) + signal.throwIfAborted() mainProcess = await createMainProcessControl({ - previousAppVersion: configMigration.previousAppVersion, - settingsStore, - secretStore, - privacySettings, - proxySettings, - mcpSettings, - mcpAppSandboxRegistry, - acpCatalogSettings, - database, - settingsDatabase, - providerDatabase, - agentDatabase, - databaseSecurityService, - startupWorkloadCoordinator, + mcpAppDesktop, startupRunId, requestUpdateInstall, onWindowCreated: (isMainWindow) => splashWindow.handleWindowCreated(isMainWindow), @@ -133,11 +45,9 @@ export async function startMainProcess( return mainProcess } catch (error) { await splashWindow.close() - if (mainProcess) { - await mainProcess.stopForCleanup() - } else { - database?.close() - } + await mainProcess?.stopForCleanup() throw error + } finally { + signal.removeEventListener('abort', cancelStartup) } } diff --git a/src/main/app/profileStartup.ts b/src/main/app/profileStartup.ts new file mode 100644 index 0000000000..194fd11c88 --- /dev/null +++ b/src/main/app/profileStartup.ts @@ -0,0 +1,185 @@ +import Conf from 'conf' +import Database from 'better-sqlite3-multiple-ciphers' +import fs from 'node:fs' +import path from 'node:path' +import { createSettingsStore } from '@/config/settingsStore' +import type { StoreLike } from '@/config/storeLike' +import { SecretStore } from '@/config/secretStore' +import { McpSettings, createMcpSettingsDefaults } from '@/mcp/settings' +import { AcpCatalogSettings, createAcpCatalogDefaults } from '@/agent/acp/catalog/settings' +import { SettingsDatabase } from '@/settings/data/database' +import { ProviderDatabase } from '@/provider/data/database' +import { McpDatabase } from '@/mcp/data/database' +import { AgentDatabase } from '@/agent/data/database' +import { migrateConfigStorage } from '@/config/migration' +import { PluginToolPolicyStore, type StoredToolPolicy } from '@/plugin/toolPolicyStore' +import type { MainDatabase } from '@/data/mainDatabase' +import type { DatabaseUnlockProgressPayload } from '@shared/contracts/databaseSecurity' +import { PrivacySettings } from './privacy' +import { ProxySettings } from '@/platform/proxySettings' +import { + DatabaseSecurityService, + DEFAULT_DATABASE_SECURITY_METADATA, + type DatabaseSecurityMetadata, + type DatabaseSecurityCryptoPort +} from './databaseSecurity' +import { + initializeMainDatabaseWithRecovery, + type DatabaseStartupPresentation +} from './databaseStartup' +import type { DatabaseInitializationObservation } from './databaseInitializer' + +export interface CoreProfileHost extends DatabaseStartupPresentation { + setLoggingEnabled(enabled: boolean): Promise + showDatabaseUnlockProgress( + payload: DatabaseUnlockProgressPayload, + options?: { skipDelay?: boolean } + ): Promise +} + +export async function initializeCoreProfile(options: { + userDataDirectory: string + homeDirectory: string + currentAppVersion: string + crypto: DatabaseSecurityCryptoPort + host: CoreProfileHost + onOwnershipChanged?: (owned: boolean) => void + observe?: (observation: DatabaseInitializationObservation) => void | Promise +}) { + if (!path.isAbsolute(options.userDataDirectory) || !path.isAbsolute(options.homeDirectory)) { + throw new Error('Profile directories must be absolute') + } + fs.mkdirSync(options.userDataDirectory, { recursive: true }) + const ownership = new Database(path.join(options.userDataDirectory, '.core-owner.lock'), { + timeout: 0 + }) + try { + ownership.exec('BEGIN EXCLUSIVE') + } catch (error) { + ownership.close() + if ( + error instanceof Error && + 'code' in error && + (error.code === 'SQLITE_BUSY' || error.code === 'SQLITE_LOCKED') + ) { + const unavailable = new Error('Core profile already has an owner') + unavailable.name = 'CoreProfileOwnershipUnavailableError' + throw unavailable + } + throw error + } + let database: MainDatabase | undefined + let secretStore: SecretStore | undefined + let databaseSecurityService: DatabaseSecurityService | undefined + const closeProfile = () => { + databaseSecurityService?.stop() + secretStore?.pause() + database?.close() + if (ownership.open) ownership.close() + options.onOwnershipChanged?.(false) + } + try { + options.onOwnershipChanged?.(true) + const { userDataDirectory, homeDirectory, currentAppVersion } = options + const settingsStore = createSettingsStore({ + userDataDirectory, + homeDirectory, + currentAppVersion + }) + await options.host.setLoggingEnabled(settingsStore.get('loggingEnabled') ?? false) + secretStore = new SecretStore(settingsStore, options.crypto) + const privacySettings = new PrivacySettings(settingsStore) + const proxySettings = new ProxySettings(settingsStore) + const mcpSettings = new McpSettings( + new Conf({ + configName: 'mcp-settings', + cwd: userDataDirectory, + projectVersion: currentAppVersion, + defaults: createMcpSettingsDefaults() + }) as unknown as StoreLike>, + secretStore + ) + const acpCatalogSettings = new AcpCatalogSettings({ + mcpSettings, + store: new Conf({ + configName: 'acp_agents', + cwd: userDataDirectory, + projectVersion: currentAppVersion, + defaults: createAcpCatalogDefaults() + }) as unknown as StoreLike> + }) + databaseSecurityService = new DatabaseSecurityService({ + dbPath: path.join(userDataDirectory, 'app_db', 'agent.db'), + metadata: new Conf<{ metadata: DatabaseSecurityMetadata }>({ + configName: 'database-security', + cwd: userDataDirectory, + projectVersion: currentAppVersion, + defaults: { metadata: { ...DEFAULT_DATABASE_SECURITY_METADATA } } + }), + crypto: options.crypto + }) + const securityStatus = await databaseSecurityService.getStatus() + await options.host.showDatabaseUnlockProgress( + { + active: securityStatus.enabled, + safeStorageAvailable: securityStatus.safeStorageAvailable + }, + { skipDelay: securityStatus.enabled } + ) + database = await initializeMainDatabaseWithRecovery({ + security: databaseSecurityService, + splash: options.host, + observe: options.observe + }) + await options.host.showDatabaseUnlockProgress({ + active: false, + safeStorageAvailable: (await databaseSecurityService.getStatus()).safeStorageAvailable + }) + const settingsDatabase = new SettingsDatabase(database) + const providerDatabase = new ProviderDatabase(database) + const mcpDatabase = new McpDatabase(database) + const agentDatabase = new AgentDatabase(database) + const migration = migrateConfigStorage({ + database: settingsDatabase, + providerDatabase, + mcpDatabase, + agentDatabase, + settings: settingsStore, + mcpSettings: mcpSettings.getMigrationSnapshot(), + acpCatalog: acpCatalogSettings.getMigrationSnapshot(), + userDataPath: userDataDirectory, + currentAppVersion + }) + settingsStore.attachDatabase(settingsDatabase) + mcpSettings.connectDatabase(mcpDatabase) + acpCatalogSettings.connectDatabase(agentDatabase) + if (migration.appVersionChanged) mcpSettings.onUpgrade(migration.previousAppVersion) + return { + closeProfile, + currentAppVersion, + previousAppVersion: migration.previousAppVersion, + settingsStore, + secretStore, + privacySettings, + proxySettings, + mcpSettings, + acpCatalogSettings, + database, + settingsDatabase, + providerDatabase, + agentDatabase, + databaseSecurityService, + pluginToolPolicies: new PluginToolPolicyStore( + new Conf<{ policies: StoredToolPolicy[] }>({ + configName: 'plugin-tool-policies', + cwd: userDataDirectory, + projectVersion: currentAppVersion, + defaults: { policies: [] } + }) + ) + } + } catch (error) { + closeProfile() + throw error + } +} diff --git a/src/main/app/protocols.ts b/src/main/app/protocols.ts index 7d379fca1f..413a94c617 100644 --- a/src/main/app/protocols.ts +++ b/src/main/app/protocols.ts @@ -9,12 +9,28 @@ import path from 'path' import fs, { promises as fsp, Stats } from 'fs' import { Readable } from 'stream' import { is } from '@electron-toolkit/utils' -import { - resolveWorkspacePreviewRequest, - WORKSPACE_PREVIEW_PROTOCOL -} from '@/workspace/workspacePreviewProtocol' +import { WORKSPACE_PREVIEW_PROTOCOL } from '@shared/types/workspace' import { registerMcpAppProtocol } from '@/mcp/apps/sandboxProtocol' -import type { McpAppSandboxRegistry } from '@/mcp/apps/sandboxRegistry' +import type { McpAppSandboxPort, McpAppDesktopPort } from '@/mcp/apps/sandboxRegistry' + +let workspacePreviewSchemeRegistered = false + +export function registerWorkspacePreviewSchemes(): void { + if (workspacePreviewSchemeRegistered) return + protocol.registerSchemesAsPrivileged([ + { + scheme: WORKSPACE_PREVIEW_PROTOCOL, + privileges: { + standard: true, + secure: true, + supportFetchAPI: true, + corsEnabled: true, + stream: true + } + } + ]) + workspacePreviewSchemeRegistered = true +} const workspacePreviewMimeCache = new Map() @@ -147,8 +163,9 @@ const findDeepCdnResourcesDir = async (candidates: string[]): Promise => } export async function registerProtocols( - mcpAppSandboxRegistry: McpAppSandboxRegistry -): Promise { + mcpAppSandbox: McpAppSandboxPort, + resolveWorkspacePreview: (url: string) => Promise +): Promise { logger.info('registerProtocols: Registering application protocols') // Register 'deepcdn' protocol for loading built-in resources (simulating CDN) @@ -248,7 +265,7 @@ export async function registerProtocols( }) protocol.handle(WORKSPACE_PREVIEW_PROTOCOL, async (request) => { - const fullPath = resolveWorkspacePreviewRequest(request.url) + const fullPath = await resolveWorkspacePreview(request.url).catch(() => null) if (!fullPath) { return new Response('Forbidden', { status: 403, @@ -298,7 +315,8 @@ export async function registerProtocols( } }) - registerMcpAppProtocol(mcpAppSandboxRegistry) + const mcpAppDesktop = registerMcpAppProtocol(mcpAppSandbox) logger.info('registerProtocols: Application protocols registered successfully') + return mcpAppDesktop } diff --git a/src/main/app/rendererIpcSecurity.ts b/src/main/app/rendererIpcSecurity.ts index 1848a21365..32521979b6 100644 --- a/src/main/app/rendererIpcSecurity.ts +++ b/src/main/app/rendererIpcSecurity.ts @@ -1,9 +1,16 @@ -import type { IpcMainInvokeEvent } from 'electron' +import { BrowserWindow, webContents, type IpcMainInvokeEvent } from 'electron' import { createAppDocumentMatcher } from '@shared/rendererDocument' -import type { PluginSettingsWindowPort } from '@/plugin' +import type { RendererRouteCaller } from '@/routes/routeRegistry' export type AuthorizeRendererIpc = (event: IpcMainInvokeEvent, routeName?: string) => void +export function isRendererWindowCurrent(caller: RendererRouteCaller): boolean { + const contents = webContents.fromId(caller.webContentsId) + if (!contents || contents.isDestroyed()) return false + const window = BrowserWindow.fromWebContents(contents) + return (window?.id ?? null) === caller.windowId && (!window || !window.isDestroyed()) +} + const PLUGIN_SETTINGS_ROUTES = new Set([ 'plugins.get', 'plugins.enable', @@ -14,7 +21,7 @@ const PLUGIN_SETTINGS_ROUTES = new Set([ export function createRendererIpcAuthorizer(options: { rendererDirectoryUrl: string developmentServerUrl?: string - pluginSettingsWindow: Pick + pluginSettingsWindow: { getPluginIdForWebContents(webContentsId: number): string | null } }): AuthorizeRendererIpc { const matchDocument = createAppDocumentMatcher( options.rendererDirectoryUrl, @@ -39,7 +46,7 @@ export function createRendererIpcAuthorizer(options: { } if ( senderFrame.url.startsWith('file:') && - options.pluginSettingsWindow.getPluginIdForWebContents(sender.id) !== null && + typeof options.pluginSettingsWindow.getPluginIdForWebContents(sender.id) === 'string' && routeName !== undefined && PLUGIN_SETTINGS_ROUTES.has(routeName) ) { diff --git a/src/main/app/rendererPerformanceLogService.ts b/src/main/app/rendererPerformanceLogService.ts index 3c4d793243..727edcd97f 100644 --- a/src/main/app/rendererPerformanceLogService.ts +++ b/src/main/app/rendererPerformanceLogService.ts @@ -1,7 +1,5 @@ -import { app } from 'electron' import fs from 'node:fs/promises' import path from 'node:path' -import type { SettingsStore } from '@/config/settingsStore' import { RendererPerformanceRecordSchema, type RendererPerformanceRecord @@ -26,17 +24,20 @@ export class RendererPerformanceLogService { private writeQueue: Promise = Promise.resolve(true) constructor( - private readonly settings: Pick, - private readonly getUserDataPath: () => string = () => app.getPath('userData'), + private readonly isLoggingEnabled: () => boolean, + private readonly userDataDirectory: string, private readonly fsImpl: RendererPerformanceLogFs = fs, private readonly now: () => number = Date.now, private readonly onWriteError: () => void = () => { console.warn('[RendererPerformance] Failed to persist diagnostic record') } - ) {} + ) { + if (!path.isAbsolute(userDataDirectory)) + throw new Error('Diagnostic profile directory must be absolute') + } async record(rawRecord: unknown): Promise { - if (!this.settings.get('loggingEnabled')) { + if (!this.isLoggingEnabled()) { return false } @@ -50,7 +51,7 @@ export class RendererPerformanceLogService { recordedAt: this.now() } const line = `${JSON.stringify(record)}\n` - const logDirectory = path.join(this.getUserDataPath(), 'logs') + const logDirectory = path.join(this.userDataDirectory, 'logs') const logPath = path.join(logDirectory, 'renderer-performance.ndjson') const write = async (): Promise => { diff --git a/src/main/app/routeIpc.ts b/src/main/app/routeIpc.ts new file mode 100644 index 0000000000..9a8498c585 --- /dev/null +++ b/src/main/app/routeIpc.ts @@ -0,0 +1,33 @@ +import { BrowserWindow, type IpcMain, type IpcMainInvokeEvent } from 'electron' +import { DEEPCHAT_ROUTE_INVOKE_CHANNEL } from '@shared/contracts/channels' +import { createRendererRouteContext, type RouteContext } from '@/routes/routeRegistry' +import type { AuthorizeRendererIpc } from './rendererIpcSecurity' + +export function registerDeepchatRoutes( + ipcMain: IpcMain, + invoke: ( + routeName: string, + input: unknown, + context: RouteContext, + assertCallerActive: () => void + ) => Promise, + authorize: AuthorizeRendererIpc +): void { + ipcMain.removeHandler(DEEPCHAT_ROUTE_INVOKE_CHANNEL) + ipcMain.handle( + DEEPCHAT_ROUTE_INVOKE_CHANNEL, + async (event: IpcMainInvokeEvent, routeName: string, rawInput: unknown) => { + const assertCallerActive = () => authorize(event, routeName) + assertCallerActive() + return await invoke( + routeName, + rawInput, + createRendererRouteContext( + event.sender.id, + BrowserWindow.fromWebContents(event.sender)?.id ?? null + ), + assertCallerActive + ) + } + ) +} diff --git a/src/main/app/routes.ts b/src/main/app/routes.ts index 348368e370..576864a8fc 100644 --- a/src/main/app/routes.ts +++ b/src/main/app/routes.ts @@ -1,4 +1,3 @@ -import { app } from 'electron' import type { DatabaseRepairReport } from '@shared/types/databaseSchema' import type { AgentSettingsPort } from '@/agent/settings' import { @@ -35,9 +34,10 @@ import type { RendererPerformanceLogService } from './rendererPerformanceLogServ import type { SplashWindow } from './splashWindow' export function createAppRoutes(deps: { + isPackaged: boolean logging: Pick rendererPerformance: Pick - isMainWindowContext(caller: RendererRouteCaller): boolean + isMainWindowContext(caller: RendererRouteCaller): Promise agentSettings: Pick projects: Pick databaseSecurity: Pick @@ -86,7 +86,7 @@ export function createAppRoutes(deps: { async (rawInput) => { databaseSecurityGetStatusRoute.input.parse(rawInput) return databaseSecurityGetStatusRoute.output.parse({ - status: deps.databaseSecurity.getStatus() + status: await deps.databaseSecurity.getStatus() }) } ], @@ -142,7 +142,7 @@ export function createAppRoutes(deps: { async (rawInput, context) => { const record = performanceRecordRendererRoute.input.parse(rawInput) const caller = requireRendererCaller(context) - if (!deps.isMainWindowContext(caller)) { + if (!(await deps.isMainWindowContext(caller))) { return performanceRecordRendererRoute.output.parse({ accepted: false }) } const accepted = await deps.rendererPerformance.record(record) @@ -204,7 +204,7 @@ export function createAppRoutes(deps: { debugShowSplashScenarioRoute.name, async (rawInput) => { const input = debugShowSplashScenarioRoute.input.parse(rawInput) - if (!import.meta.env.DEV || app.isPackaged) { + if (!import.meta.env.DEV || deps.isPackaged) { return debugShowSplashScenarioRoute.output.parse({ shown: false }) } await deps.splash.showDebugScenario(input.mode) @@ -215,7 +215,7 @@ export function createAppRoutes(deps: { debugCloseSplashScenarioRoute.name, async (rawInput) => { debugCloseSplashScenarioRoute.input.parse(rawInput) - if (!import.meta.env.DEV || app.isPackaged) { + if (!import.meta.env.DEV || deps.isPackaged) { return debugCloseSplashScenarioRoute.output.parse({ closed: false }) } return debugCloseSplashScenarioRoute.output.parse({ @@ -227,7 +227,7 @@ export function createAppRoutes(deps: { debugCreateMockChatSessionRoute.name, async (rawInput) => { debugCreateMockChatSessionRoute.input.parse(rawInput) - if (!import.meta.env.DEV || app.isPackaged) { + if (!import.meta.env.DEV || deps.isPackaged) { return debugCreateMockChatSessionRoute.output.parse({ created: false, sessionId: null, diff --git a/src/main/app/schedulerHost.ts b/src/main/app/schedulerHost.ts new file mode 100644 index 0000000000..eff1b85c7c --- /dev/null +++ b/src/main/app/schedulerHost.ts @@ -0,0 +1,86 @@ +import fs from 'node:fs' +import path from 'node:path' +import { fileURLToPath } from 'node:url' +import { app, utilityProcess } from 'electron' +import type { SchedulerHost } from '@/scheduler/schedulerProcessManager' + +export async function spawnSchedulerHost(signal: AbortSignal): Promise { + signal.throwIfAborted() + const modulePath = fileURLToPath(import.meta.url) + const appPath = app.getAppPath() + const candidates = [ + path.join(appPath, 'out/main/schedulerUtilityHost.js'), + path.join(appPath, 'schedulerUtilityHost.js'), + path.resolve(path.dirname(modulePath), 'schedulerUtilityHost.js'), + path.resolve(path.dirname(modulePath), '../schedulerUtilityHost.js'), + path.resolve(process.cwd(), 'out/main/schedulerUtilityHost.js') + ] + const entry = candidates.find((candidate) => fs.existsSync(candidate)) ?? candidates[0] + const host = utilityProcess.fork(entry, ['--deepchat-cron-scheduler-host'], { + serviceName: 'DeepChat Cron Jobs Scheduler', + stdio: 'ignore', + env: { ...process.env, DEEPCHAT_CRON_SCHEDULER_HOST: '1' } + }) + + return await new Promise((resolve, reject) => { + let settled = false + const settle = (callback: () => void) => { + if (settled) return + settled = true + host.off('spawn', onSpawn) + host.off('exit', onExit) + host.off('error', onError) + signal.removeEventListener('abort', onAbort) + callback() + } + const fail = (error: unknown, exited = false) => + settle(() => { + if (exited) { + reject(error) + return + } + const closed = new Promise((resolve) => host.once('exit', () => resolve())) + void (async () => { + try { + host.kill() + } finally { + await closed + } + })().then(() => reject(error), reject) + }) + const onSpawn = () => + settle(() => + resolve({ + get pid() { + return host.pid + }, + postMessage(command) { + host.postMessage(command) + }, + kill() { + host.kill() + }, + onMessage(listener) { + host.on('message', listener) + }, + onExit(listener) { + host.on('exit', listener) + }, + onError(listener) { + host.on('error', listener) + } + }) + ) + const onExit = (code: number) => + fail(new Error(`Cron scheduler utility exited before spawn: ${code}`), true) + const onError = (type: string, location: string) => + fail(new Error(`Scheduler utility process error: ${type} at ${location}`)) + const onAbort = () => fail(new Error('Scheduler utility spawn canceled.')) + + host.once('spawn', onSpawn) + host.once('exit', onExit) + host.once('error', onError) + signal.addEventListener('abort', onAbort, { once: true }) + if (signal.aborted) onAbort() + }) +} diff --git a/src/main/app/settingsRoutes.ts b/src/main/app/settingsRoutes.ts index e0a2c9cb2c..f39a716d2d 100644 --- a/src/main/app/settingsRoutes.ts +++ b/src/main/app/settingsRoutes.ts @@ -1,5 +1,15 @@ import { - SETTINGS_KEYS, + configGetUpdateChannelRoute, + configSetUpdateChannelRoute, + configGetLanguageRoute, + configSetLanguageRoute, + configGetThemeRoute, + configSetThemeRoute, + configGetFloatingButtonRoute, + configSetFloatingButtonRoute, + configGetShortcutKeysRoute, + configSetShortcutKeysRoute, + configResetShortcutKeysRoute, configGetEntriesRoute, configUpdateEntriesRoute, settingsActivityListRoute, @@ -13,18 +23,9 @@ import { settingsUpdateRoute, type ConfigEntryKey, type ConfigEntryValues, - type SettingsActivityInput, - type SettingsChange, - type SettingsKey, - type SettingsSnapshotValues + type SettingsActivityInput } from '@shared/contracts/routes' -import type { DeepChatDefaults } from '@/agent/deepchat/defaults' -import type { AgentTraceSettingsPort } from '@/agent/traceSettings' -import type { PrivacySettingsPort } from './privacy' -import type { DesktopSettings } from '@/desktop/settings' -import type { FontSettings } from '@/desktop/fontSettings' -import type { LoggingService } from './logging' -import type { OcrSettingsPort } from '@/ocr/ocrSettings' +import type { AppSettingsService } from './settingsService' import type { SettingsStore } from '@/config/settingsStore' import type { CommandShellService } from '@/agent/shared/process/commandShellService' import type { DeepchatEventPublisher } from '@shared/contracts/events' @@ -32,16 +33,25 @@ import { createRouteMap, type DeepchatRouteMap } from '@/routes/routeRegistry' export function createAppSettingsRoutes(deps: { settings: Pick - agentDefaults: DeepChatDefaults - privacy: PrivacySettingsPort - traceSettings: AgentTraceSettingsPort - desktopSettings: DesktopSettings - fonts: FontSettings - logging: LoggingService - ocr: OcrSettingsPort + appSettings: Pick< + AppSettingsService, + | 'getSnapshot' + | 'update' + | 'listSystemFonts' + | 'getLanguage' + | 'setLanguage' + | 'getTheme' + | 'setTheme' + | 'getFloatingButtonEnabled' + | 'setFloatingButtonEnabled' + | 'getShortcutKeys' + | 'setShortcutKeys' + | 'resetShortcutKeys' + | 'getUpdateChannel' + | 'setUpdateChannel' + > commandShell: Pick publishEvent: DeepchatEventPublisher - applyContentProtection(enabled: boolean): void recordActivity(input: SettingsActivityInput): void listActivities(limit?: number): Promise }): DeepchatRouteMap { @@ -70,130 +80,110 @@ export function createAppSettingsRoutes(deps: { read('input_enabledMcpTools') return values } - const readSnapshot = (): SettingsSnapshotValues => ({ - fontSizeLevel: deps.desktopSettings.getFontSizeLevel(), - fontFamily: deps.fonts.getFontFamily(), - codeFontFamily: deps.fonts.getCodeFontFamily(), - autoScrollEnabled: deps.desktopSettings.getAutoScrollEnabled(), - autoCompactionEnabled: deps.agentDefaults.getAutoCompactionEnabled(), - autoCompactionTriggerThreshold: deps.agentDefaults.getAutoCompactionTriggerThreshold(), - autoCompactionRetainRecentPairs: deps.agentDefaults.getAutoCompactionRetainRecentPairs(), - contentProtectionEnabled: deps.desktopSettings.getContentProtectionEnabled(), - privacyModeEnabled: deps.privacy.isEnabled(), - notificationsEnabled: deps.desktopSettings.getNotificationsEnabled(), - launchAtLoginEnabled: deps.desktopSettings.getLaunchAtLoginEnabled(), - traceDebugEnabled: deps.traceSettings.isEnabled(), - copyWithCotEnabled: deps.desktopSettings.getCopyWithCotEnabled(), - loggingEnabled: deps.logging.getEnabled(), - ocrAutoExtractForNonVisionModels: deps.ocr.getAutomaticExtractionEnabled(), - ocrBackend: deps.ocr.getBackend() - }) - const pickSnapshot = ( - snapshot: SettingsSnapshotValues, - keys?: SettingsKey[] - ): Partial => { - const result: Partial = {} - for (const key of keys?.length ? keys : SETTINGS_KEYS) { - ;(result as Record)[key] = snapshot[key] - } - return result - } - const applyChange = (change: SettingsChange): void => { - switch (change.key) { - case 'fontSizeLevel': - deps.desktopSettings.setFontSizeLevel(change.value) - return - case 'fontFamily': - deps.fonts.setFontFamily(change.value) - return - case 'codeFontFamily': - deps.fonts.setCodeFontFamily(change.value) - return - case 'autoScrollEnabled': - deps.desktopSettings.setAutoScrollEnabled(change.value) - return - case 'autoCompactionEnabled': - deps.agentDefaults.setAutoCompactionEnabled(change.value) - return - case 'autoCompactionTriggerThreshold': - deps.agentDefaults.setAutoCompactionTriggerThreshold(change.value) - return - case 'autoCompactionRetainRecentPairs': - deps.agentDefaults.setAutoCompactionRetainRecentPairs(change.value) - return - case 'contentProtectionEnabled': - deps.desktopSettings.setContentProtectionEnabled(change.value) - deps.applyContentProtection(change.value) - return - case 'privacyModeEnabled': - deps.privacy.setEnabled(change.value) - return - case 'notificationsEnabled': - deps.desktopSettings.setNotificationsEnabled(change.value) - return - case 'launchAtLoginEnabled': - deps.desktopSettings.setLaunchAtLoginEnabled(change.value) - return - case 'traceDebugEnabled': - deps.traceSettings.setEnabled(change.value) - return - case 'copyWithCotEnabled': - deps.desktopSettings.setCopyWithCotEnabled(change.value) - return - case 'loggingEnabled': - deps.logging.setEnabled(change.value) - return - case 'ocrAutoExtractForNonVisionModels': - deps.ocr.setAutomaticExtractionEnabled(change.value) - return - case 'ocrBackend': - deps.ocr.setBackend(change.value) - } - } - const recordChange = (change: SettingsChange): void => { - deps.recordActivity({ - category: - change.key === 'privacyModeEnabled' - ? 'privacy' - : change.key === 'fontSizeLevel' || - change.key === 'fontFamily' || - change.key === 'codeFontFamily' || - change.key === 'contentProtectionEnabled' - ? 'appearance' - : 'system', - action: - typeof change.value === 'boolean' ? (change.value ? 'enabled' : 'disabled') : 'updated', - targetType: 'setting', - targetId: change.key, - targetLabel: change.key, - routeName: - change.key === 'privacyModeEnabled' - ? 'settings-database' - : change.key === 'ocrAutoExtractForNonVisionModels' || change.key === 'ocrBackend' - ? 'settings-ocr' - : 'settings-common', - summaryKey: 'settings.controlCenter.activity.settingUpdated', - summaryParams: { key: change.key } - }) - } - const getSnapshot = (keys?: SettingsKey[]) => ({ - version: Date.now(), - values: pickSnapshot(readSnapshot(), keys) - }) - const updateSnapshot = (changes: SettingsChange[]) => { - for (const change of changes) { - applyChange(change) - recordChange(change) - } - const changedKeys = changes.map((change) => change.key) - return { - version: Date.now(), - changedKeys, - values: pickSnapshot(readSnapshot(), changedKeys) - } - } - return createRouteMap([ + [ + configGetUpdateChannelRoute.name, + async (rawInput) => { + configGetUpdateChannelRoute.input.parse(rawInput) + return configGetUpdateChannelRoute.output.parse({ + channel: await deps.appSettings.getUpdateChannel() + }) + } + ], + [ + configSetUpdateChannelRoute.name, + async (rawInput) => { + const input = configSetUpdateChannelRoute.input.parse(rawInput) + return configSetUpdateChannelRoute.output.parse({ + channel: await deps.appSettings.setUpdateChannel(input.channel) + }) + } + ], + [ + configGetLanguageRoute.name, + async (rawInput) => { + configGetLanguageRoute.input.parse(rawInput) + return configGetLanguageRoute.output.parse(await deps.appSettings.getLanguage()) + } + ], + [ + configSetLanguageRoute.name, + async (rawInput) => { + const input = configSetLanguageRoute.input.parse(rawInput) + return configSetLanguageRoute.output.parse( + await deps.appSettings.setLanguage(input.language) + ) + } + ], + [ + configGetThemeRoute.name, + async (rawInput) => { + configGetThemeRoute.input.parse(rawInput) + return configGetThemeRoute.output.parse(await deps.appSettings.getTheme()) + } + ], + [ + configSetThemeRoute.name, + async (rawInput) => { + const input = configSetThemeRoute.input.parse(rawInput) + return configSetThemeRoute.output.parse(await deps.appSettings.setTheme(input.theme)) + } + ], + [ + configGetFloatingButtonRoute.name, + async (rawInput) => { + configGetFloatingButtonRoute.input.parse(rawInput) + return configGetFloatingButtonRoute.output.parse({ + enabled: await deps.appSettings.getFloatingButtonEnabled() + }) + } + ], + [ + configSetFloatingButtonRoute.name, + async (rawInput) => { + const input = configSetFloatingButtonRoute.input.parse(rawInput) + return configSetFloatingButtonRoute.output.parse({ + enabled: await deps.appSettings.setFloatingButtonEnabled(input.enabled) + }) + } + ], + [ + configGetShortcutKeysRoute.name, + async (rawInput) => { + configGetShortcutKeysRoute.input.parse(rawInput) + return configGetShortcutKeysRoute.output.parse({ + shortcuts: await deps.appSettings.getShortcutKeys() + }) + } + ], + [ + configSetShortcutKeysRoute.name, + async (rawInput) => { + const input = configSetShortcutKeysRoute.input.parse(rawInput) + return configSetShortcutKeysRoute.output.parse({ + shortcuts: await deps.appSettings.setShortcutKeys(input.shortcuts) + }) + } + ], + [ + configResetShortcutKeysRoute.name, + async (rawInput) => { + configResetShortcutKeysRoute.input.parse(rawInput) + const shortcuts = await deps.appSettings.resetShortcutKeys() + deps.recordActivity({ + category: 'shortcut', + action: 'reset', + targetType: 'shortcut', + targetLabel: 'Shortcuts', + routeName: 'settings-shortcut', + summaryKey: 'settings.controlCenter.activity.settingUpdated', + summaryParams: { key: 'shortcuts' } + }) + return configResetShortcutKeysRoute.output.parse({ + shortcuts + }) + } + ], [ configGetEntriesRoute.name, async (rawInput) => { @@ -220,14 +210,14 @@ export function createAppSettingsRoutes(deps: { settingsGetSnapshotRoute.name, async (rawInput) => { const input = settingsGetSnapshotRoute.input.parse(rawInput) - return settingsGetSnapshotRoute.output.parse(getSnapshot(input.keys)) + return settingsGetSnapshotRoute.output.parse(await deps.appSettings.getSnapshot(input.keys)) } ], [ settingsGetPublicRoute.name, async (rawInput) => { const input = settingsGetPublicRoute.input.parse(rawInput) - return settingsGetPublicRoute.output.parse(getSnapshot(input.keys)) + return settingsGetPublicRoute.output.parse(await deps.appSettings.getSnapshot(input.keys)) } ], [ @@ -235,7 +225,7 @@ export function createAppSettingsRoutes(deps: { async (rawInput) => { settingsListSystemFontsRoute.input.parse(rawInput) return settingsListSystemFontsRoute.output.parse({ - fonts: await deps.fonts.getSystemFonts() + fonts: await deps.appSettings.listSystemFonts() }) } ], @@ -286,14 +276,14 @@ export function createAppSettingsRoutes(deps: { settingsUpdateRoute.name, async (rawInput) => { const input = settingsUpdateRoute.input.parse(rawInput) - return settingsUpdateRoute.output.parse(updateSnapshot(input.changes)) + return settingsUpdateRoute.output.parse(await deps.appSettings.update(input.changes)) } ], [ settingsUpdatePublicRoute.name, async (rawInput) => { const input = settingsUpdatePublicRoute.input.parse(rawInput) - return settingsUpdatePublicRoute.output.parse(updateSnapshot(input.changes)) + return settingsUpdatePublicRoute.output.parse(await deps.appSettings.update(input.changes)) } ], [ diff --git a/src/main/app/settingsService.ts b/src/main/app/settingsService.ts new file mode 100644 index 0000000000..34dd9249e5 --- /dev/null +++ b/src/main/app/settingsService.ts @@ -0,0 +1,421 @@ +import { + SETTINGS_KEYS, + configSetLanguageRoute, + configSetThemeRoute, + settingsGetSnapshotRoute, + settingsUpdateRoute, + type DeepchatRouteOutput, + type SettingsActivityInput, + type SettingsChange, + type SettingsKey, + type SettingsSnapshotValues +} from '@shared/contracts/routes' +import { + ChatSettingMutationSchema, + type ApplyChatSettingSuccess, + type ChatSettingMutation +} from '@shared/types/chatSettings' +import type { DeepchatEventPublisher } from '@shared/contracts/events' +import type { DeepChatDefaults } from '@/agent/deepchat/defaults' +import type { AgentTraceSettingsPort } from '@/agent/traceSettings' +import type { PrivacySettingsPort } from './privacy' +import type { DesktopSettings } from '@/desktop/settings' +import type { FontSettings } from '@/desktop/fontSettings' +import type { LoggingService } from './logging' +import type { OcrSettingsPort } from '@/ocr/ocrSettings' +import { getLocaleDirection } from '@shared/locales' +import type { NativeConfigurationSnapshot } from '@shared/types/nativeConfiguration' +import type { FloatingButtonBounds } from '@shared/types/floating-widget' +import type { ShortcutKeySetting } from '@shared/types/desktop' +import type { UpdateSettings } from '@/upgrade/settings' +import { defaultShortcutKey } from '@/desktop/shortcutKeySettings' + +export interface AppSettingsServiceDependencies { + agentDefaults: DeepChatDefaults + privacy: PrivacySettingsPort + traceSettings: AgentTraceSettingsPort + desktopSettings: DesktopSettings + fonts: FontSettings + logging: LoggingService + ocr: OcrSettingsPort + recordActivity(input: SettingsActivityInput): void + publishEvent: DeepchatEventPublisher + nextVersion(): number + assertActive(): void + ownerId: string + updateSettings: UpdateSettings + publishNativeSnapshot(snapshot: NativeConfigurationSnapshot): void | Promise +} + +export class AppSettingsService { + private accepting = true + private pendingCommands = 0 + private tail: Promise = Promise.resolve() + private systemThemeObservation: Promise | undefined + private nativeVersion = 0 + + constructor(private readonly deps: AppSettingsServiceDependencies) {} + + private async enqueue(run: () => T | Promise, systemObservation = false): Promise { + this.deps.assertActive() + if (!this.accepting) throw new Error('Settings owner is stopped') + if (this.pendingCommands >= 64 && !systemObservation) + throw new Error('Settings command queue is full') + this.pendingCommands++ + const result = this.tail + .then(() => { + this.deps.assertActive() + if (!this.accepting) throw new Error('Settings owner is stopped') + return run() + }) + .finally(() => this.pendingCommands--) + this.tail = result.then( + () => undefined, + () => undefined + ) + return await result + } + + async stop(): Promise { + this.accepting = false + let timeout: NodeJS.Timeout | undefined + try { + await Promise.race([ + this.tail, + new Promise((_, reject) => { + timeout = setTimeout( + () => reject(new Error('Settings owner did not drain (30000ms)')), + 30_000 + ) + }) + ]) + } finally { + clearTimeout(timeout) + } + } + + start(): void { + if (this.pendingCommands > 0) throw new Error('Settings owner is still draining') + this.accepting = true + } + + readNativeSnapshot(): NativeConfigurationSnapshot { + const settings = this.deps.desktopSettings + const shortcuts = settings.getShortcutKeys() + const bounds = settings.getFloatingButtonBounds() + return structuredClone({ + ownerId: this.deps.ownerId, + version: ++this.nativeVersion, + language: settings.getLanguage(), + notificationsEnabled: settings.getNotificationsEnabled(), + closeToQuit: settings.getCloseToQuit(), + contentProtectionEnabled: settings.getContentProtectionEnabled(), + shortcutKeys: Object.fromEntries( + Object.keys(defaultShortcutKey).map((key) => [ + key, + shortcuts[key as keyof ShortcutKeySetting] + ]) + ) as ShortcutKeySetting, + floatingButtonEnabled: settings.getFloatingButtonEnabled(), + floatingButtonBounds: bounds ? { x: bounds.x, y: bounds.y, dockSide: bounds.dockSide } : null, + loggingEnabled: this.deps.logging.getEnabled(), + privacyModeEnabled: this.deps.privacy.isEnabled(), + updateChannel: this.deps.updateSettings.getChannel() + }) + } + + async publishNativeSnapshot(): Promise { + await this.deps.publishNativeSnapshot(this.readNativeSnapshot()) + } + + async getFloatingButtonEnabled(): Promise { + return await this.enqueue(() => this.deps.desktopSettings.getFloatingButtonEnabled()) + } + + async setFloatingButtonEnabled(enabled: boolean): Promise { + return await this.enqueue(async () => { + await this.deps.desktopSettings.setFloatingButtonEnabled(enabled) + return this.deps.desktopSettings.getFloatingButtonEnabled() + }) + } + + async setFloatingButtonBounds(bounds: FloatingButtonBounds): Promise { + if ( + !Number.isFinite(bounds.x) || + !Number.isFinite(bounds.y) || + !['left', 'right'].includes(bounds.dockSide) + ) + throw new Error('Invalid floating bounds') + const value = { x: bounds.x, y: bounds.y, dockSide: bounds.dockSide } + await this.enqueue(() => this.deps.desktopSettings.setFloatingButtonBounds(value)) + } + + async getShortcutKeys(): Promise { + return await this.enqueue(() => this.deps.desktopSettings.getShortcutKeys()) + } + + async setShortcutKeys(shortcuts: ShortcutKeySetting): Promise { + return await this.enqueue(async () => { + await this.deps.desktopSettings.setShortcutKeys(shortcuts) + return this.deps.desktopSettings.getShortcutKeys() + }) + } + + async resetShortcutKeys(): Promise { + return await this.enqueue(async () => { + await this.deps.desktopSettings.resetShortcutKeys() + return this.deps.desktopSettings.getShortcutKeys() + }) + } + + async getUpdateChannel(): Promise<'stable' | 'beta'> { + return await this.enqueue(() => this.deps.updateSettings.getChannel()) + } + + async setUpdateChannel(channel: 'stable' | 'beta'): Promise<'stable' | 'beta'> { + return await this.enqueue(async () => { + this.deps.updateSettings.setChannel(channel) + await this.publishNativeSnapshot() + return this.deps.updateSettings.getChannel() + }) + } + + async initializeDesktop(): Promise { + await this.enqueue(() => this.deps.desktopSettings.initialize()) + } + + async listSystemFonts(): Promise { + return await this.enqueue(() => this.deps.fonts.getSystemFonts()) + } + + async openLoggingFolder(): Promise { + await this.enqueue(() => this.deps.logging.openFolder()) + } + + async observeSystemTheme(): Promise { + this.deps.assertActive() + if (!this.accepting) throw new Error('Settings owner is stopped') + if (this.systemThemeObservation) return await this.systemThemeObservation + // Reserve the single coalesced observation so a full command queue cannot lose an OS change. + const pending = this.enqueue(async () => { + this.systemThemeObservation = undefined + await this.deps.desktopSettings.observeSystemTheme() + }, true) + this.systemThemeObservation = pending + const clear = () => { + if (this.systemThemeObservation === pending) this.systemThemeObservation = undefined + } + void pending.then(clear, clear) + await pending + } + + private readLanguage(): DeepchatRouteOutput<'config.getLanguage'> { + const locale = this.deps.desktopSettings.getLanguage() + return { + requestedLanguage: this.deps.desktopSettings.getRequestedLanguage(), + locale, + direction: getLocaleDirection(locale) + } + } + + async getLanguage(): Promise> { + return await this.enqueue(() => this.readLanguage()) + } + + async setLanguage(language: string): Promise> { + const input = configSetLanguageRoute.input.parse({ language }) + return await this.enqueue(async () => { + await this.deps.desktopSettings.setLanguage(input.language) + return this.readLanguage() + }) + } + + private async readTheme(): Promise> { + return { + theme: this.deps.desktopSettings.getTheme(), + isDark: await this.deps.desktopSettings.getCurrentThemeIsDark() + } + } + + async getTheme(): Promise> { + return await this.enqueue(() => this.readTheme()) + } + + async setTheme( + theme: 'dark' | 'light' | 'system' + ): Promise> { + const input = configSetThemeRoute.input.parse({ theme }) + return await this.enqueue(async () => ({ + theme: input.theme, + isDark: await this.deps.desktopSettings.setTheme(input.theme) + })) + } + + private readonly readSnapshot = async ( + keys?: SettingsKey[] + ): Promise> => { + const values: Partial = { + fontSizeLevel: this.deps.desktopSettings.getFontSizeLevel(), + fontFamily: this.deps.fonts.getFontFamily(), + codeFontFamily: this.deps.fonts.getCodeFontFamily(), + autoScrollEnabled: this.deps.desktopSettings.getAutoScrollEnabled(), + autoCompactionEnabled: this.deps.agentDefaults.getAutoCompactionEnabled(), + autoCompactionTriggerThreshold: this.deps.agentDefaults.getAutoCompactionTriggerThreshold(), + autoCompactionRetainRecentPairs: this.deps.agentDefaults.getAutoCompactionRetainRecentPairs(), + contentProtectionEnabled: this.deps.desktopSettings.getContentProtectionEnabled(), + privacyModeEnabled: this.deps.privacy.isEnabled(), + notificationsEnabled: this.deps.desktopSettings.getNotificationsEnabled(), + traceDebugEnabled: this.deps.traceSettings.isEnabled(), + copyWithCotEnabled: this.deps.desktopSettings.getCopyWithCotEnabled(), + loggingEnabled: this.deps.logging.getEnabled(), + ocrAutoExtractForNonVisionModels: this.deps.ocr.getAutomaticExtractionEnabled(), + ocrBackend: this.deps.ocr.getBackend() + } + if (!keys?.length || keys.includes('launchAtLoginEnabled')) { + values.launchAtLoginEnabled = await this.deps.desktopSettings.getLaunchAtLoginEnabled() + } + return this.pickSnapshot(values, keys) + } + private readonly pickSnapshot = ( + snapshot: Partial, + keys?: SettingsKey[] + ): Partial => { + const result: Partial = {} + for (const key of keys?.length ? keys : SETTINGS_KEYS) { + ;(result as Record)[key] = + snapshot[key] + } + return result + } + private readonly applyChange = (change: SettingsChange): void | Promise => { + switch (change.key) { + case 'fontSizeLevel': + return this.deps.desktopSettings.setFontSizeLevel(change.value) + case 'fontFamily': + this.deps.fonts.setFontFamily(change.value) + return + case 'codeFontFamily': + this.deps.fonts.setCodeFontFamily(change.value) + return + case 'autoScrollEnabled': + return this.deps.desktopSettings.setAutoScrollEnabled(change.value) + case 'autoCompactionEnabled': + this.deps.agentDefaults.setAutoCompactionEnabled(change.value) + return + case 'autoCompactionTriggerThreshold': + this.deps.agentDefaults.setAutoCompactionTriggerThreshold(change.value) + return + case 'autoCompactionRetainRecentPairs': + this.deps.agentDefaults.setAutoCompactionRetainRecentPairs(change.value) + return + case 'contentProtectionEnabled': + return this.deps.desktopSettings.setContentProtectionEnabled(change.value) + case 'privacyModeEnabled': + this.deps.privacy.setEnabled(change.value) + return this.publishNativeSnapshot() + case 'notificationsEnabled': + return this.deps.desktopSettings.setNotificationsEnabled(change.value) + case 'launchAtLoginEnabled': + return this.deps.desktopSettings.setLaunchAtLoginEnabled(change.value) + case 'traceDebugEnabled': + this.deps.traceSettings.setEnabled(change.value) + return + case 'copyWithCotEnabled': + return this.deps.desktopSettings.setCopyWithCotEnabled(change.value) + case 'loggingEnabled': + return this.deps.logging.setEnabled(change.value) + case 'ocrAutoExtractForNonVisionModels': + this.deps.ocr.setAutomaticExtractionEnabled(change.value) + return + case 'ocrBackend': + this.deps.ocr.setBackend(change.value) + } + } + private readonly recordChange = (change: SettingsChange): void => { + this.deps.recordActivity({ + category: + change.key === 'privacyModeEnabled' + ? 'privacy' + : change.key === 'fontSizeLevel' || + change.key === 'fontFamily' || + change.key === 'codeFontFamily' || + change.key === 'contentProtectionEnabled' + ? 'appearance' + : 'system', + action: + typeof change.value === 'boolean' ? (change.value ? 'enabled' : 'disabled') : 'updated', + targetType: 'setting', + targetId: change.key, + targetLabel: change.key, + routeName: + change.key === 'privacyModeEnabled' + ? 'settings-database' + : change.key === 'ocrAutoExtractForNonVisionModels' || change.key === 'ocrBackend' + ? 'settings-ocr' + : 'settings-common', + summaryKey: 'settings.controlCenter.activity.settingUpdated', + summaryParams: { key: change.key } + }) + } + + async getSnapshot(keys?: SettingsKey[]): Promise> { + const input = settingsGetSnapshotRoute.input.parse({ keys }) + return await this.enqueue(async () => { + const version = this.deps.nextVersion() + return { version, values: await this.readSnapshot(input.keys) } + }) + } + + async update(changes: SettingsChange[]): Promise> { + const input = settingsUpdateRoute.input.parse({ changes }) + return await this.enqueue(async () => { + for (const change of input.changes) { + const pending = this.applyChange(change) + if (pending) await pending + this.recordChange(change) + } + const changedKeys = input.changes.map((change) => change.key) + const values = await this.readSnapshot(changedKeys) + return { version: this.deps.nextVersion(), changedKeys, values } + }) + } + + async publishSnapshot(): Promise { + await this.enqueue(async () => { + await this.publishNativeSnapshot() + const version = this.deps.nextVersion() + this.deps.publishEvent('settings.changed', { + version, + changedKeys: [...SETTINGS_KEYS], + values: await this.readSnapshot() + }) + }) + } + + async applyDisplayChange(raw: ChatSettingMutation): Promise { + const change = ChatSettingMutationSchema.parse(raw) + return await this.enqueue(async () => { + const settings = this.deps.desktopSettings + let previousValue: ApplyChatSettingSuccess['previousValue'] + switch (change.id) { + case 'copyWithCotEnabled': + previousValue = settings.getCopyWithCotEnabled() + await settings.setCopyWithCotEnabled(change.value) + break + case 'fontSizeLevel': + previousValue = settings.getFontSizeLevel() + await settings.setFontSizeLevel(change.value) + break + case 'language': + previousValue = settings.getRequestedLanguage() + await settings.setLanguage(change.value) + break + case 'theme': + previousValue = settings.getTheme() + await settings.setTheme(change.value) + break + } + return { ok: true, id: change.id, value: change.value, previousValue, appliedAt: Date.now() } + }) + } +} diff --git a/src/main/app/splashWindow.ts b/src/main/app/splashWindow.ts index 99519a961f..7ce1ddacac 100644 --- a/src/main/app/splashWindow.ts +++ b/src/main/app/splashWindow.ts @@ -49,6 +49,7 @@ export class SplashWindow { private splashLoadPromise: Promise | null = null private splashShowDelayTimer: ReturnType | null = null private debugMode: SplashDebugMode | null = null + private databaseStartupCanceled = false constructor() { this.setupDatabaseUnlockListeners() } @@ -164,6 +165,7 @@ export class SplashWindow { reason: DatabaseUnlockReason safeStorageAvailable: boolean }): Promise { + if (this.databaseStartupCanceled) return null this.unlockRequest?.resolve(null) const requestId = `database-unlock-${Date.now()}-${Math.random().toString(36).slice(2)}` @@ -183,6 +185,7 @@ export class SplashWindow { async requestDatabaseRecovery( payload: Omit ): Promise { + if (this.databaseStartupCanceled) return null this.recoveryRequest?.resolve(null) const requestId = `database-recovery-${Date.now()}-${Math.random().toString(36).slice(2)}` @@ -210,6 +213,14 @@ export class SplashWindow { this.emitDebugMode() } + cancelDatabaseStartup(): void { + this.databaseStartupCanceled = true + this.unlockRequest?.resolve(null) + this.unlockRequest = null + this.recoveryRequest?.resolve(null) + this.recoveryRequest = null + } + async closeDebugScenario(): Promise { if (!this.debugMode) { return false diff --git a/src/main/app/startupMigrations/legacyChatImportService.ts b/src/main/app/startupMigrations/legacyChatImportService.ts index 881beb7eff..7cf3358987 100644 --- a/src/main/app/startupMigrations/legacyChatImportService.ts +++ b/src/main/app/startupMigrations/legacyChatImportService.ts @@ -1,4 +1,3 @@ -import { app } from 'electron' import path from 'path' import fs from 'fs' import Database from 'better-sqlite3-multiple-ciphers' @@ -46,7 +45,7 @@ export class LegacyChatImportService { projectDatabase: ProjectDatabase, memoryDatabase: MemoryDatabase, tapeFacts: TranscriptTapePort, - sourceDbPath?: string, + sourceDbPath: string, notifyEnvironmentProjectionChanged: () => void = () => undefined ) { this.appDatabase = appDatabase @@ -54,7 +53,8 @@ export class LegacyChatImportService { this.projectDatabase = projectDatabase this.memoryDatabase = memoryDatabase this.messageStore = new SessionTranscript(this.sessionDatabase, tapeFacts) - this.sourceDbPath = sourceDbPath ?? path.join(app.getPath('userData'), 'app_db', 'chat.db') + if (!path.isAbsolute(sourceDbPath)) throw new Error('Legacy database path must be absolute') + this.sourceDbPath = sourceDbPath this.notifyEnvironmentProjectionChanged = notifyEnvironmentProjectionChanged } diff --git a/src/main/app/startupWorkloadCoordinator/index.ts b/src/main/app/startupWorkloadCoordinator/index.ts index bf86e7d1d8..2dc688b51e 100644 --- a/src/main/app/startupWorkloadCoordinator/index.ts +++ b/src/main/app/startupWorkloadCoordinator/index.ts @@ -74,6 +74,8 @@ type StartupTaskRecord = { resolve: (value: T | PromiseLike) => void reject: (reason?: unknown) => void promise: Promise + finished: Promise + finish: () => void } const PHASE_PRIORITY: Record = { @@ -133,7 +135,8 @@ export class StartupWorkloadCoordinator { const tasks = [...runState.tasks] for (const task of tasks) { - if (task.state === 'pending') { + const pending = task.state === 'pending' + if (pending) { this.removePendingTask(task.internalKey) } @@ -144,6 +147,8 @@ export class StartupWorkloadCoordinator { task.reject(this.createAbortError(task.id)) } + if (pending) this.finishTask(task, 'cancelled') + if (!task.visibleId) { runState.tasks.delete(task) } @@ -153,6 +158,29 @@ export class StartupWorkloadCoordinator { this.runs.delete(target) } + async cancelAndDrainTarget(target: StartupWorkloadTarget): Promise { + this.cancelTarget(target) + const pending = [...this.inFlightByDedupeKey.values()] + .filter((task) => task.target === target) + .map((task) => task.finished) + if (pending.length === 0) return + + let timeout: NodeJS.Timeout | undefined + try { + await Promise.race([ + Promise.all(pending), + new Promise((_, reject) => { + timeout = setTimeout( + () => reject(new Error(`Startup workload target "${target}" did not drain (30000ms)`)), + 30_000 + ) + }) + ]) + } finally { + clearTimeout(timeout) + } + } + replayTarget(target: StartupWorkloadTarget): void { if (!this.runs.has(target)) { this.ensureRun(target) @@ -194,6 +222,10 @@ export class StartupWorkloadCoordinator { resolveTask = resolve rejectTask = reject }) + let finish!: () => void + const finished = new Promise((resolve) => { + finish = resolve + }) const now = Date.now() const task: StartupTaskRecord = { @@ -213,7 +245,9 @@ export class StartupWorkloadCoordinator { run: options.run, resolve: resolveTask, reject: rejectTask, - promise + promise, + finished, + finish } runState.tasks.add(task as StartupTaskRecord) @@ -370,6 +404,7 @@ export class StartupWorkloadCoordinator { task.state = finalState task.updatedAt = Date.now() this.inFlightByDedupeKey.delete(task.dedupeKey) + task.finish() const runState = this.runs.get(task.target) if (!runState || runState.runId !== task.runId) { diff --git a/src/main/appMain.ts b/src/main/appMain.ts index dc0b1d372c..c7950c939e 100644 --- a/src/main/appMain.ts +++ b/src/main/appMain.ts @@ -1,6 +1,6 @@ import { app, dialog } from 'electron' -import { StartupWorkloadCoordinator } from './app/startupWorkloadCoordinator' -import { registerWorkspacePreviewSchemes } from './workspace/workspacePreviewProtocol' +import { randomUUID } from 'node:crypto' +import { registerWorkspacePreviewSchemes } from './app/protocols' import { registerMcpAppScheme } from './mcp/apps/sandboxProtocol' import { findDeepLinkArg, @@ -48,6 +48,8 @@ export function startApp(): void { registerMcpAppScheme() let mainProcess: MainProcessControl | undefined + let startupCompletion: Promise | undefined + const startupController = new AbortController() let allowQuit = false let shutdownPromise: Promise | undefined let shutdownReason: MainLogShutdownReason = 'app_quit' @@ -130,8 +132,7 @@ export function startApp(): void { }) } - const startupWorkloadCoordinator = new StartupWorkloadCoordinator() - const mainStartupRunId = startupWorkloadCoordinator.createRun('main') + const mainStartupRunId = randomUUID() const startupStartedAt = readMonotonicNow() mainLogger.emit('app.startup.started', { startupRunId: mainStartupRunId, @@ -148,9 +149,10 @@ export function startApp(): void { throw new Error('Cannot install update while application shutdown is already in progress') } - activeMainProcess.clearPermissionCaches() let actionClaim: MainShutdownActionClaim | undefined shutdownPromise = (async () => { + await startupCompletion + await activeMainProcess.clearPermissionCaches() actionClaim = await activeMainProcess.stop('update_install') if (!actionClaim) { throw new Error('Application shutdown is already owned by another action') @@ -169,13 +171,13 @@ export function startApp(): void { } } - app.whenReady().then(async () => { + startupCompletion = app.whenReady().then(async () => { ensureRegularAppOnMac() try { mainProcess = await startMainProcess( - startupWorkloadCoordinator, mainStartupRunId, - requestUpdateInstall + requestUpdateInstall, + startupController.signal ) const durationMs = elapsedMonotonicMs(startupStartedAt) mainLogger.emit('app.startup.terminal', { @@ -184,7 +186,8 @@ export function startApp(): void { ...(durationMs === undefined ? {} : { durationMs }) }) } catch (error) { - reportNativeMainError('main: Application startup failed:', error) + if (!startupController.signal.aborted) + reportNativeMainError('main: Application startup failed:', error) const durationMs = elapsedMonotonicMs(startupStartedAt) mainLogger.emit('app.startup.terminal', { startupRunId: mainStartupRunId, @@ -192,27 +195,30 @@ export function startApp(): void { ...(durationMs === undefined ? {} : { durationMs }), error: classifyMainLogError(error) }) - dialog.showErrorBox( - 'Application startup failed', - error instanceof Error ? error.message : String(error) - ) + if (!startupController.signal.aborted) + dialog.showErrorBox( + 'Application startup failed', + error instanceof Error ? error.message : String(error) + ) allowQuit = true app.quit() } }) app.on('before-quit', (event) => { - mainProcess?.clearPermissionCaches() if (allowQuit) { return } event.preventDefault() + if (!mainProcess) startupController.abort() if (shutdownPromise) { return } shutdownPromise = (async () => { + // The main window can be ready while the splash transition is still pending. + await startupCompletion const activeMainProcess = mainProcess if (!activeMainProcess) { allowQuit = true @@ -220,6 +226,7 @@ export function startApp(): void { return } + await activeMainProcess.clearPermissionCaches() const confirmed = await activeMainProcess.confirmShutdown() if (!confirmed) { activeMainProcess.cancelShutdown() @@ -256,12 +263,18 @@ export function startApp(): void { shutdownPromise = undefined shutdownReason = 'app_quit' } - })() + })().catch((error) => { + reportNativeMainError('main: Application shutdown preflight failed:', error) + shutdownPromise = undefined + shutdownReason = 'app_quit' + }) }) // Handle window-all-closed event app.on('window-all-closed', () => { - mainProcess?.clearPermissionCaches() + void mainProcess?.clearPermissionCaches().catch((error) => { + reportNativeMainError('main: Failed to clear permission caches:', error) + }) if (!mainProcess) return if (!mainProcess.hasMainWindows()) { diff --git a/src/main/cli/agentCommandAccess.ts b/src/main/cli/agentCommandAccess.ts index 2ad8234f9f..c3bcaf54f4 100644 --- a/src/main/cli/agentCommandAccess.ts +++ b/src/main/cli/agentCommandAccess.ts @@ -84,16 +84,16 @@ export function resolveBundledCliDirectory( }> ): string | null { const platform = input.platform ?? process.platform - const directory = input.isPackaged - ? path.join(input.resourcesPath, 'app.asar.unpacked', 'cli') - : path.join(input.appPath, 'out', 'cli') - const launcher = path.join(directory, platform === 'win32' ? 'deepchat.cmd' : 'deepchat') - if (input.isFile) return input.isFile(launcher) ? directory : null - try { - return fs.statSync(launcher).isFile() ? directory : null - } catch { - return null + const directories = input.isPackaged + ? [path.join(input.resourcesPath, 'app.asar.unpacked', 'cli')] + : [path.join(input.appPath, 'out', 'cli'), path.join(input.appPath, 'cli')] + for (const directory of directories) { + const launcher = path.join(directory, platform === 'win32' ? 'deepchat.cmd' : 'deepchat') + try { + if (input.isFile ? input.isFile(launcher) : fs.statSync(launcher).isFile()) return directory + } catch {} } + return null } export class AgentCliCommandAccess { diff --git a/src/main/cli/mutationGuard.ts b/src/main/cli/mutationGuard.ts index 591f5ce35c..1d1c9aff08 100644 --- a/src/main/cli/mutationGuard.ts +++ b/src/main/cli/mutationGuard.ts @@ -9,17 +9,20 @@ import { CliRequestError } from './errors' const CLI_APPROVAL_DOMAIN = 'cli-mutation' const DEFAULT_APPROVAL_TIMEOUT_MS = 2 * 60_000 -export type CliApprovalTarget = Readonly<{ - windowId: number - webContentsId: number -}> +export type CliApprovalTarget = + | Readonly<{ + windowId: number + webContentsId: number + }> + | Readonly<{ host: 'terminal' }> export type CliApprovalPresentationPort = Readonly<{ getTarget(): Promise present( target: CliApprovalTarget, - payload: DeepchatEventPayload<'approvals.requested'> - ): Promise + payload: DeepchatEventPayload<'approvals.requested'>, + signal?: AbortSignal + ): Promise close(target: CliApprovalTarget, payload: DeepchatEventPayload<'approvals.closed'>): Promise }> @@ -75,7 +78,7 @@ export class CliMutationGuard { const target = await this.presentation.getTarget() throwIfSignalAborted(input.signal) if (!target) { - throw new CliRequestError('unavailable', 'No trusted renderer is available for approval', { + throw new CliRequestError('unavailable', 'No trusted approval surface is available', { httpStatus: 503, retriable: true }) @@ -85,6 +88,8 @@ export class CliMutationGuard { const scopeKey = `cli:${input.connectionId}` let approvalRequestId: string | undefined let closeReason: DeepchatEventPayload<'approvals.closed'>['reason'] = 'cancelled' + const presentationLifetime = new AbortController() + let presentation: Promise | undefined try { const pending = this.approvals.create( @@ -97,7 +102,7 @@ export class CliMutationGuard { arguments: input.arguments, ...(input.displayData !== undefined ? { redactedDisplayData: input.displayData } : {}), metadata: { - targetWebContentsId: target.webContentsId, + ...('webContentsId' in target ? { targetWebContentsId: target.webContentsId } : {}), clientRequestId: input.clientRequestId } }, @@ -109,23 +114,36 @@ export class CliMutationGuard { ) approvalRequestId = pending.requestId this.pendingTargets.set(pending.requestId, { scopeKey, target }) + const decisionPromise = this.approvals.wait(pending.requestId, input.signal) - let presented: boolean + let presented: boolean | 'approved' | 'denied' try { - presented = await this.presentation.present(target, { - requestId: pending.requestId, - operation: input.operation, - effect: input.effect, - principal: input.principal, - expiresAt: pending.expiresAt, - ...(pending.redactedDisplayData !== undefined - ? { displayData: pending.redactedDisplayData } - : {}) - }) + presentation = this.presentation.present( + target, + { + requestId: pending.requestId, + operation: input.operation, + effect: input.effect, + principal: input.principal, + expiresAt: pending.expiresAt, + ...(pending.redactedDisplayData !== undefined + ? { displayData: pending.redactedDisplayData } + : {}) + }, + AbortSignal.any([input.signal, presentationLifetime.signal]) + ) + presented = await Promise.race([presentation, decisionPromise.then(() => true as const)]) } catch { presented = false } - if (!presented) { + throwIfSignalAborted(input.signal) + if (typeof presented === 'string' && 'host' in target) { + this.approvals.resolve({ + requestId: pending.requestId, + scopeKey, + decision: presented + }) + } else if (presented !== true) { closeReason = 'unavailable' this.approvals.resolve({ requestId: pending.requestId, @@ -138,7 +156,7 @@ export class CliMutationGuard { }) } - const decision = await this.approvals.wait(pending.requestId, input.signal) + const decision = await decisionPromise closeReason = decisionCloseReason(decision) if (decision.allowed) { return { approvalRequestId: pending.requestId } @@ -182,6 +200,8 @@ export class CliMutationGuard { } throw error } finally { + presentationLifetime.abort() + await presentation?.catch(() => undefined) if (approvalRequestId) { this.pendingTargets.delete(approvalRequestId) this.rendererUnavailableRequests.delete(approvalRequestId) @@ -197,7 +217,12 @@ export class CliMutationGuard { caller: RendererRouteCaller ): boolean { const pending = this.pendingTargets.get(input.requestId) - if (!pending || pending.target.webContentsId !== caller.webContentsId) return false + if ( + !pending || + !('webContentsId' in pending.target) || + pending.target.webContentsId !== caller.webContentsId + ) + return false return this.approvals.resolve({ requestId: input.requestId, scopeKey: pending.scopeKey, @@ -207,7 +232,8 @@ export class CliMutationGuard { cancelRenderer(webContentsId: number): void { for (const [requestId, pending] of this.pendingTargets) { - if (pending.target.webContentsId !== webContentsId) continue + if (!('webContentsId' in pending.target) || pending.target.webContentsId !== webContentsId) + continue this.rendererUnavailableRequests.add(requestId) this.approvals.resolve({ requestId, diff --git a/src/main/cli/runService.ts b/src/main/cli/runService.ts index f6a7b653bb..1b4cef4f54 100644 --- a/src/main/cli/runService.ts +++ b/src/main/cli/runService.ts @@ -79,6 +79,7 @@ export type CliRunServiceOptions = Readonly<{ sessions: RunSessionStorePort getPendingAssistantMessages(runId: string): ChatMessageRecord[] hasWaitingDescendantInteraction(runId: string): boolean + readSubscriptionSnapshot(read: () => Promise): Promise eventHub: TypedEventHub now?: () => number log?: Pick @@ -390,7 +391,9 @@ export class CliRunService { let lastCursor = subscription.initialCursor try { - const currentRun = await this.buildSnapshot(input.runId, input.messageLimit) + const currentRun = await this.options.readSubscriptionSnapshot(() => + this.buildSnapshot(input.runId, input.messageLimit) + ) const terminalAtSubscribe = currentRun.status !== 'generating' if (subscription.recoveryReason) { const snapshot = runsSnapshotEvent.payload.parse({ diff --git a/src/main/config/migration.ts b/src/main/config/migration.ts index bae46ed380..35364e086a 100644 --- a/src/main/config/migration.ts +++ b/src/main/config/migration.ts @@ -1,5 +1,4 @@ -import { app } from 'electron' -import ElectronStore from 'electron-store' +import Conf from 'conf' import path from 'node:path' import type { Prompt, SystemPrompt } from '@shared/types/prompt' import type { MCPServerConfig } from '@shared/types/mcp' @@ -44,9 +43,9 @@ export function migrateConfigStorage(options: { mcpSettings: Record acpCatalog: { enabled: boolean; sharedMcpSelections: string[] } userDataPath: string - currentAppVersion?: string + currentAppVersion: string }): ConfigMigrationResult { - const currentAppVersion = options.currentAppVersion ?? app.getVersion() + const currentAppVersion = options.currentAppVersion const previousAppVersion = options.settings.get('appVersion') migrateBusinessConfigToSqlite(options) @@ -81,9 +80,10 @@ function migrateBusinessConfigToSqlite(options: Parameters({ - name: storeName, + const store = new Conf<{ models: MODEL_META[]; custom_models: MODEL_META[] }>({ + configName: storeName, cwd: path.join(options.userDataPath, PROVIDER_MODELS_DIR), + projectVersion: options.currentAppVersion, defaults: { models: [], custom_models: [] } }) providerSettings.replaceProviderModels(provider.id, 'provider', store.get('models', [])) @@ -95,7 +95,7 @@ function migrateBusinessConfigToSqlite(options: Parameters({ - name: 'custom_prompts', + const customPromptsStore = new Conf<{ prompts: Prompt[] }>({ + configName: 'custom_prompts', + cwd: options.userDataPath, + projectVersion: options.currentAppVersion, defaults: { prompts: [] } }) appSettingsTable.setAppSetting('customPrompts', customPromptsStore.get('prompts', []), true) customPromptsStore.set('prompts', []) - const systemPromptsStore = new ElectronStore<{ prompts: SystemPrompt[] }>({ - name: 'system_prompts', + const systemPromptsStore = new Conf<{ prompts: SystemPrompt[] }>({ + configName: 'system_prompts', + cwd: options.userDataPath, + projectVersion: options.currentAppVersion, defaults: { prompts: [ { @@ -186,8 +190,10 @@ function migrateSensitiveConfigToSqlite(options: Parameters({ - name: 'knowledge-configs', + const knowledgeStore = new Conf<{ knowledgeConfigs: BuiltinKnowledgeConfig[] }>({ + configName: 'knowledge-configs', + cwd: options.userDataPath, + projectVersion: options.currentAppVersion, defaults: { knowledgeConfigs: [] } }) appSettingsTable.setAppSetting( @@ -216,9 +222,13 @@ function migrateAppStartupState(options: Parameters appSettingsTable.markConfigMigrationApplied(APP_STARTUP_STATE_MIGRATION_ID) } -function readLegacyModelConfigs(): Record { - const store = new ElectronStore>({ - name: 'model-config' +function readLegacyModelConfigs( + options: Parameters[0] +): Record { + const store = new Conf>({ + configName: 'model-config', + cwd: options.userDataPath, + projectVersion: options.currentAppVersion }) const snapshot = store.store const meta = snapshot[LEGACY_MODEL_CONFIG_META_KEY] as LegacyModelConfigMeta | undefined diff --git a/src/main/config/secretStore.ts b/src/main/config/secretStore.ts index d91664d609..c1a8aacb92 100644 --- a/src/main/config/secretStore.ts +++ b/src/main/config/secretStore.ts @@ -1,46 +1,102 @@ -import { safeStorage } from 'electron' import type { SettingsStore } from './settingsStore' +export interface CredentialCryptoPort { + isAvailable(): Promise + encrypt(value: string): Promise + decrypt(wrapped: string): Promise +} + export class SecretStore { - constructor(private readonly settings: SettingsStore) {} + private generation = 0 + private paused = false + + constructor( + private readonly settings: SettingsStore, + private readonly crypto: CredentialCryptoPort + ) {} + + pause(): void { + this.paused = true + this.generation++ + } - isAvailable(): boolean { + resume(): void { + this.generation++ + this.paused = false + } + + captureOperation(): () => void { + const generation = this.generation + const check = () => { + if (this.paused || generation !== this.generation) + throw new Error('Credential operation interrupted by maintenance or shutdown') + } + check() + return check + } + + async isAvailable(): Promise { + const check = this.captureOperation() + let available = false try { - return safeStorage.isEncryptionAvailable() + available = await this.crypto.isAvailable() } catch { - return false + available = false } + check() + return available } - get(key: string): string { + async get(key: string): Promise { + const check = this.captureOperation() const wrapped = this.settings.get(key) if (!wrapped) { return '' } + let value = '' try { - return safeStorage.decryptString(Buffer.from(wrapped, 'base64')) + value = await this.crypto.decrypt(wrapped) } catch (error) { console.error(`[SecretStore] Failed to decrypt ${key}:`, error) - return '' } + check() + if (this.getWrapped(key) !== wrapped) throw new Error('The saved credential changed') + return value } getWrapped(key: string): string | undefined { return this.settings.get(key) } - wrap(value: string): string { - if (!this.isAvailable()) { + async unwrap(wrapped: string): Promise { + const check = this.captureOperation() + try { + return await this.crypto.decrypt(wrapped) + } finally { + check() + } + } + + async wrap(value: string): Promise { + const check = this.captureOperation() + if (!(await this.isAvailable())) { throw new Error('sync.error.safeStorageUnavailable') } - return Buffer.from(safeStorage.encryptString(value)).toString('base64') + check() + try { + return await this.crypto.encrypt(value) + } finally { + check() + } } setWrapped(key: string, wrapped: string): void { + this.captureOperation() this.settings.set(key, wrapped) } restoreWrapped(key: string, wrapped: string | undefined): void { + this.captureOperation() if (wrapped === undefined) { this.settings.delete(key) return diff --git a/src/main/config/settingsStore.ts b/src/main/config/settingsStore.ts index 9fd4cc0634..35bb1fc0f6 100644 --- a/src/main/config/settingsStore.ts +++ b/src/main/config/settingsStore.ts @@ -1,46 +1,8 @@ +import Conf from 'conf' +import path from 'node:path' import type { SettingsDatabase } from '@/settings/data/database' import { AppSettingsDbBackedStore } from '@/settings/appSettingsDbStore' import type { StoreLike } from '@/config/storeLike' -import ElectronStore from 'electron-store' -import path from 'node:path' -import { app } from 'electron' - -export function createSettingsStore(): SettingsStore { - const userDataPath = app.getPath('userData') - return new SettingsStore( - new ElectronStore>({ - name: 'app-settings', - defaults: { - language: 'system', - providers: [], - closeToQuit: false, - proxyMode: 'system', - customProxyUrl: '', - searchPreviewEnabled: true, - contentProtectionEnabled: false, - privacyModeEnabled: false, - syncEnabled: false, - syncFolderPath: path.join(userDataPath, 'sync'), - lastSyncTime: 0, - copyWithCotEnabled: true, - autoCompactionEnabled: true, - autoCompactionTriggerThreshold: 80, - autoCompactionRetainRecentPairs: 2, - loggingEnabled: false, - floatingButtonEnabled: false, - fontFamily: '', - codeFontFamily: '', - default_system_prompt: '', - skillsPath: path.join(app.getPath('home'), '.deepchat', 'skills'), - enableSkills: true, - skillDraftSuggestionsEnabled: false, - appVersion: app.getVersion(), - hooksNotifications: { hooks: [] }, - agentCommandShell: { preference: 'auto' } - } - }) as unknown as StoreLike> - ) -} export class SettingsStore implements StoreLike> { private activeStore: StoreLike> @@ -102,3 +64,49 @@ export class SettingsStore implements StoreLike> { this.legacyStore.delete(key) } } + +export function createSettingsStore(options: { + userDataDirectory: string + homeDirectory: string + currentAppVersion: string +}): SettingsStore { + for (const directory of [options.userDataDirectory, options.homeDirectory]) { + if (!path.isAbsolute(directory)) throw new Error('Settings directories must be absolute') + } + const userDataPath = options.userDataDirectory + return new SettingsStore( + new Conf>({ + configName: 'app-settings', + cwd: userDataPath, + projectVersion: options.currentAppVersion, + defaults: { + language: 'system', + providers: [], + closeToQuit: false, + proxyMode: 'system', + customProxyUrl: '', + searchPreviewEnabled: true, + contentProtectionEnabled: false, + privacyModeEnabled: false, + syncEnabled: false, + syncFolderPath: path.join(userDataPath, 'sync'), + lastSyncTime: 0, + copyWithCotEnabled: true, + autoCompactionEnabled: true, + autoCompactionTriggerThreshold: 80, + autoCompactionRetainRecentPairs: 2, + loggingEnabled: false, + floatingButtonEnabled: false, + fontFamily: '', + codeFontFamily: '', + default_system_prompt: '', + skillsPath: path.join(options.homeDirectory, '.deepchat', 'skills'), + enableSkills: true, + skillDraftSuggestionsEnabled: false, + appVersion: options.currentAppVersion, + hooksNotifications: { hooks: [] }, + agentCommandShell: { preference: 'auto' } + } + }) as unknown as StoreLike> + ) +} diff --git a/src/main/deeplink/actions.ts b/src/main/deeplink/actions.ts index d18447f229..46fc5eb1a2 100644 --- a/src/main/deeplink/actions.ts +++ b/src/main/deeplink/actions.ts @@ -1,6 +1,4 @@ -import type { ProviderSettingsPort } from '@/provider/settings' import { BrowserWindow } from 'electron' -import type { McpServicePort } from '@shared/types/mcp' import type { IWindowPresenter } from '@shared/types/desktop' import type { ProviderInstallPreview } from '@shared/providerDeeplink' import type { ProviderDeeplinkFailureReason } from '@shared/notifications' @@ -17,8 +15,7 @@ import type { type DeeplinkActionsDeps = { window: IWindowPresenter - config: Pick - mcp: Pick + hasProvider(providerId: string): Promise notifications: SemanticNotificationPublisher } @@ -72,14 +69,7 @@ class DesktopActions implements DeeplinkDesktopPort { } class McpInstallActions implements DeeplinkMcpInstallPort { - constructor( - private readonly windowPresenter: IWindowPresenter, - private readonly mcpService: Pick - ) {} - - isReady(): boolean { - return this.mcpService.isReady() - } + constructor(private readonly windowPresenter: IWindowPresenter) {} async requestInstall(mcpConfig: string): Promise { const targetWindow = await resolveChatWindow(this.windowPresenter) @@ -94,12 +84,12 @@ class McpInstallActions implements DeeplinkMcpInstallPort { class ProviderInstallActions implements DeeplinkProviderInstallPort { constructor( private readonly windowPresenter: IWindowPresenter, - private readonly providerSettings: Pick, + private readonly providerExists: DeeplinkActionsDeps['hasProvider'], private readonly notifications: SemanticNotificationPublisher ) {} - hasProvider(providerId: string): boolean { - return Boolean(this.providerSettings.getProviderById(providerId)) + hasProvider(providerId: string): Promise { + return this.providerExists(providerId) } async requestInstall(preview: ProviderInstallPreview): Promise { @@ -129,6 +119,6 @@ class ProviderInstallActions implements DeeplinkProviderInstallPort { export const createDeeplinkActions = (deps: DeeplinkActionsDeps) => ({ desktop: new DesktopActions(deps.window), - mcp: new McpInstallActions(deps.window, deps.mcp), - provider: new ProviderInstallActions(deps.window, deps.config, deps.notifications) + mcp: new McpInstallActions(deps.window), + provider: new ProviderInstallActions(deps.window, deps.hasProvider, deps.notifications) }) diff --git a/src/main/deeplink/index.ts b/src/main/deeplink/index.ts index 277c4d56ad..e43a9356a0 100644 --- a/src/main/deeplink/index.ts +++ b/src/main/deeplink/index.ts @@ -56,6 +56,7 @@ class ProviderDeeplinkError extends Error { export class DeeplinkService { private startupUrl: string | null = null private pendingMcpInstallUrl: string | null = null + private mcpReady = false constructor( private readonly desktop: DeeplinkDesktopPort, @@ -92,8 +93,9 @@ export class DeeplinkService { this.startupUrl = null } - processPendingMcpInstall(): void { - if (!this.pendingMcpInstallUrl) { + observeMcpReadiness(ready: boolean): void { + this.mcpReady = ready + if (!ready || !this.pendingMcpInstallUrl) { return } @@ -101,8 +103,9 @@ export class DeeplinkService { 'Processing pending MCP install URL:', this.redactDeepLinkUrlForLog(this.pendingMcpInstallUrl) ) - void this.handleDeepLink(this.pendingMcpInstallUrl) + const pendingUrl = this.pendingMcpInstallUrl this.pendingMcpInstallUrl = null + void this.handleDeepLink(pendingUrl) } async handleDeepLink(url: string): Promise { @@ -122,7 +125,7 @@ export class DeeplinkService { logger.info('Parsed deeplink - command:', command, 'subCommand:', subCommand) - if (command === 'mcp' && subCommand === 'install' && !this.mcpInstall.isReady()) { + if (command === 'mcp' && subCommand === 'install' && !this.mcpReady) { logger.info('MCP not ready yet, saving MCP install URL for later') this.pendingMcpInstallUrl = url return @@ -363,7 +366,7 @@ export class DeeplinkService { let preview: ProviderInstallPreview try { - preview = this.parseProviderInstallParams(params) + preview = await this.parseProviderInstallParams(params) } catch (error) { const isExpectedRejection = error instanceof ProviderDeeplinkError const reason = isExpectedRejection ? error.reason : ('invalid-payload' as const) @@ -387,7 +390,9 @@ export class DeeplinkService { } } - private parseProviderInstallParams(params: URLSearchParams): ProviderInstallPreview { + private async parseProviderInstallParams( + params: URLSearchParams + ): Promise { const version = params.get('v') if (version !== PROVIDER_INSTALL_VERSION) { throw new ProviderDeeplinkError( @@ -417,7 +422,7 @@ export class DeeplinkService { ) } - if (!this.providerInstall.hasProvider(id)) { + if (!(await this.providerInstall.hasProvider(id))) { throw new ProviderDeeplinkError('provider-not-found', `Unknown provider id: ${id}`) } diff --git a/src/main/deeplink/ports.ts b/src/main/deeplink/ports.ts index ea3b213589..38dd25e689 100644 --- a/src/main/deeplink/ports.ts +++ b/src/main/deeplink/ports.ts @@ -14,12 +14,11 @@ export interface DeeplinkDesktopPort { } export interface DeeplinkMcpInstallPort { - isReady(): boolean requestInstall(mcpConfig: string): Promise } export interface DeeplinkProviderInstallPort { - hasProvider(providerId: string): boolean + hasProvider(providerId: string): Promise requestInstall(preview: ProviderInstallPreview): Promise reportFailure(reason: ProviderDeeplinkFailureReason): void } diff --git a/src/main/desktop/browser/BrowserTab.ts b/src/main/desktop/browser/BrowserTab.ts index ad447da10b..9b670c7fe2 100644 --- a/src/main/desktop/browser/BrowserTab.ts +++ b/src/main/desktop/browser/BrowserTab.ts @@ -72,14 +72,17 @@ export class BrowserTab { async navigateUntilDomReady( url: string, timeoutMs: number = 30000, - beforeDispatch?: () => void, + beforeDispatch?: () => unknown, onDispatched?: () => void, signal?: AbortSignal ): Promise { signal?.throwIfAborted() this.ensureAvailable() - beforeDispatch?.() + const authorization = beforeDispatch?.() + if (authorization && typeof (authorization as PromiseLike).then === 'function') + await authorization signal?.throwIfAborted() + this.ensureAvailable() this.beginMainFrameNavigation(url) const loadPromise = this.webContents.loadURL(url) @@ -123,7 +126,7 @@ export class BrowserTab { async sendCdpCommand( method: string, params?: Record, - beforeDispatch?: () => void, + beforeDispatch?: () => unknown, onDispatched?: () => void, signal?: AbortSignal ): Promise { @@ -136,8 +139,11 @@ export class BrowserTab { const session = await awaitWithAbort(this.ensureSession(), signal) signal?.throwIfAborted() - beforeDispatch?.() + const authorization = beforeDispatch?.() + if (authorization && typeof (authorization as PromiseLike).then === 'function') + await authorization signal?.throwIfAborted() + this.ensureAvailable() const responsePromise = session.sendCommand(method, params ?? {}) onDispatched?.() const response = await awaitWithAbort(responsePromise, signal) diff --git a/src/main/desktop/browser/DownloadManager.ts b/src/main/desktop/browser/DownloadManager.ts deleted file mode 100644 index 82fa4ecfe9..0000000000 --- a/src/main/desktop/browser/DownloadManager.ts +++ /dev/null @@ -1,218 +0,0 @@ -import { - session as electronSession, - type DownloadItem, - type Session, - type WebContents -} from 'electron' -import { nanoid } from 'nanoid' -import type { DownloadInfo } from '@shared/types/browser' - -type PendingDownloadRequest = { - url: string - savePath?: string - resolve: (info: DownloadInfo) => void - reject: (error: unknown) => void - timer: NodeJS.Timeout - requestedAt: number -} - -export class DownloadManager { - private downloads = new Map() - private pendingRequests = new Map() - private sessionHandlers = new Map< - Session, - (event: Electron.Event, item: DownloadItem, webContents: WebContents) => void - >() - - constructor() { - this.ensureSessionListener(electronSession.defaultSession) - } - - async downloadFile( - url: string, - savePath?: string, - webContents?: WebContents - ): Promise { - const electronSessionRef = webContents?.session ?? electronSession.defaultSession - this.ensureSessionListener(electronSessionRef) - - return await new Promise((resolve, reject) => { - let pendingRequest: PendingDownloadRequest | null = null - - const timer = setTimeout(() => { - if (pendingRequest) { - this.removePendingRequest(electronSessionRef, pendingRequest) - } - reject(new Error('Download did not start in time')) - }, 10_000) - - pendingRequest = { - url, - savePath, - resolve, - reject, - timer, - requestedAt: Date.now() - } - - if (!pendingRequest) { - clearTimeout(timer) - reject(new Error('Failed to create download request')) - return - } - - this.enqueuePendingRequest(electronSessionRef, pendingRequest) - - try { - electronSessionRef.downloadURL(url) - } catch (error) { - clearTimeout(timer) - if (pendingRequest) { - this.removePendingRequest(electronSessionRef, pendingRequest) - } - reject(error) - } - }) - } - - listDownloads(): DownloadInfo[] { - return Array.from(this.downloads.values()) - } - - getDownload(downloadId: string): DownloadInfo | undefined { - return this.downloads.get(downloadId) - } - - destroy(): void { - for (const [session, handler] of this.sessionHandlers.entries()) { - session.removeListener('will-download', handler) - } - this.sessionHandlers.clear() - - for (const requests of this.pendingRequests.values()) { - requests.forEach((pending) => clearTimeout(pending.timer)) - } - this.pendingRequests.clear() - } - - private ensureSessionListener(session: Session): void { - if (this.sessionHandlers.has(session)) { - return - } - - const handler = (_event: Electron.Event, item: DownloadItem) => - this.handleWillDownload(session, item) - - session.on('will-download', handler) - this.sessionHandlers.set(session, handler) - } - - private enqueuePendingRequest(session: Session, pendingRequest: PendingDownloadRequest): void { - const queue = this.pendingRequests.get(session) ?? [] - queue.push(pendingRequest) - this.pendingRequests.set(session, queue) - } - - private removePendingRequest(session: Session, pendingRequest: PendingDownloadRequest): void { - const queue = this.pendingRequests.get(session) - if (!queue) return - - const index = queue.indexOf(pendingRequest) - if (index !== -1) { - queue.splice(index, 1) - } - - if (queue.length === 0) { - this.pendingRequests.delete(session) - } - } - - private handleWillDownload(session: Session, item: DownloadItem): void { - const queue = this.pendingRequests.get(session) - if (!queue || queue.length === 0) { - return - } - - const itemUrl = item.getURL() - const now = Date.now() - const exactMatchIndex = queue.findIndex((pending) => pending.url === itemUrl) - const fallbackIndex = - exactMatchIndex !== -1 - ? exactMatchIndex - : queue.findIndex((pending) => now - pending.requestedAt < 15_000) - - if (fallbackIndex === -1) { - return - } - - const [pendingRequest] = queue.splice(fallbackIndex, 1) - if (queue.length === 0) { - this.pendingRequests.delete(session) - } - - clearTimeout(pendingRequest.timer) - this.startTrackingDownload(item, pendingRequest) - } - - private startTrackingDownload(item: DownloadItem, pendingRequest: PendingDownloadRequest): void { - const id = nanoid(10) - const info: DownloadInfo = { - id, - url: item.getURL(), - filePath: pendingRequest.savePath, - mimeType: item.getMimeType(), - receivedBytes: 0, - totalBytes: item.getTotalBytes(), - status: 'pending' - } - - if (pendingRequest.savePath) { - item.setSavePath(pendingRequest.savePath) - } - - this.downloads.set(id, info) - - const finalizeDownload = (status: DownloadInfo['status'], error?: string) => { - const existing = this.downloads.get(id) - if (!existing) { - pendingRequest.reject(new Error('Download info missing on completion')) - return - } - - const updated: DownloadInfo = { - ...existing, - receivedBytes: item.getReceivedBytes(), - totalBytes: item.getTotalBytes(), - filePath: item.getSavePath(), - status - } - - if (error) { - updated.error = error - } else { - delete updated.error - } - - this.downloads.set(id, updated) - pendingRequest.resolve({ ...updated }) - } - - item.on('updated', (_updateEvent, state) => { - const existing = this.downloads.get(id) - if (!existing) return - existing.receivedBytes = item.getReceivedBytes() - existing.totalBytes = item.getTotalBytes() - existing.status = state === 'interrupted' ? 'failed' : 'in-progress' - this.downloads.set(id, { ...existing }) - }) - - item.once('done', (_doneEvent, state) => { - if (state === 'completed') { - finalizeDownload('completed') - return - } - - finalizeDownload('failed', state) - }) - } -} diff --git a/src/main/desktop/browser/YoBrowserPresenter.ts b/src/main/desktop/browser/YoBrowserPresenter.ts index 292f4e32e3..ec917efd1f 100644 --- a/src/main/desktop/browser/YoBrowserPresenter.ts +++ b/src/main/desktop/browser/YoBrowserPresenter.ts @@ -20,7 +20,6 @@ import { type YoBrowserActivityRect, type YoBrowserStatus } from '@shared/types/browser' -import type { DownloadInfo } from '@shared/types/browser' import type { IWindowPresenter, IYoBrowserPresenter } from '@shared/types/desktop' import { AgentPreviewCoordinator, @@ -30,7 +29,7 @@ import { import { BrowserTab as BrowserPage } from './BrowserTab' import { BrowserProfileImportService } from './BrowserProfileImportService' import { CDPManager } from './CDPManager' -import { DownloadManager } from './DownloadManager' +import { closeWebContents } from '../closeWebContents' import { ScreenshotManager } from './ScreenshotManager' import { clearYoBrowserSessionData, @@ -75,6 +74,7 @@ type SessionBrowserState = { previewBurstUntil: number targetDispatchObserved: boolean createdEventPublished: boolean + closing?: Promise } type HostWindowListeners = { @@ -98,12 +98,12 @@ export class YoBrowserPresenter implements IYoBrowserPresenter { private readonly hostWindowListeners = new Map() private readonly cdpManager = new CDPManager() private readonly screenshotManager = new ScreenshotManager(this.cdpManager) - private readonly downloadManager = new DownloadManager() private readonly profileImportService = new BrowserProfileImportService( getYoBrowserSession, getYoBrowserUnpartitionedCookies ) private browserDataMutationActive = false + private closing: Promise | undefined private readonly windowPresenter: IWindowPresenter private readonly previewCoordinator: AgentPreviewCoordinator private readonly unregisterPreviewHandler: () => void @@ -133,7 +133,7 @@ export class YoBrowserPresenter implements IYoBrowserPresenter { hostWindowId?: number, activitySource?: YoBrowserActivitySource, agentRunId?: string, - beforeDispatch?: () => void, + beforeDispatch?: () => unknown, signal?: AbortSignal ): Promise { signal?.throwIfAborted() @@ -156,6 +156,7 @@ export class YoBrowserPresenter implements IYoBrowserPresenter { if (activitySource !== 'agent' && state.previewHost) { await this.releasePreviewHost(state) } + if (this.closing || state.closing) throw new Error('Native browser is stopping') const projectDispatch = () => { state.targetDispatchObserved = true const publishCreated = () => { @@ -273,6 +274,7 @@ export class YoBrowserPresenter implements IYoBrowserPresenter { this.previewCoordinator.hide(this.nativeTargetRef(state)) await this.releasePreviewHost(state) + if (this.closing || state.closing) return false this.detachOtherSessionBrowsers(hostWindowId, sessionId) @@ -457,28 +459,19 @@ export class YoBrowserPresenter implements IYoBrowserPresenter { this.previewCoordinator.releaseClaim({ source: 'browser', sessionId, runId }) } - async destroySessionBrowser(sessionId: string): Promise { + destroySessionBrowser(sessionId: string): Promise { const state = this.sessionBrowsers.get(sessionId) - if (!state) { - return - } - - this.previewCoordinator.releaseClaim(this.nativeTargetRef(state)) - await this.releasePreviewHost(state) - await this.detachSessionBrowser(sessionId) - state.page.destroy() - state.overlay.destroy() - this.sessionBrowsers.delete(sessionId) - - if (!state.view.webContents.isDestroyed()) { - try { - state.view.webContents.close() - } catch { - // Ignore view shutdown failures. - } - } - - this.emitWindowClosed(sessionId) + if (!state) return Promise.resolve() + if (state.closing) return state.closing + state.closing = Promise.resolve().then(async () => { + this.previewCoordinator.releaseClaim(this.nativeTargetRef(state)) + await this.releasePreviewHost(state) + await this.detachSessionBrowser(sessionId) + state.page.destroy() + await closeWebContents(state.view.webContents) + this.handleDestroyedContents(state) + }) + return state.closing } async goBack(sessionId: string): Promise { @@ -560,7 +553,7 @@ export class YoBrowserPresenter implements IYoBrowserPresenter { params?: Record, activitySource?: YoBrowserActivitySource, agentRunId?: string, - beforeDispatch?: () => void, + beforeDispatch?: () => unknown, signal?: AbortSignal ): Promise { const state = this.sessionBrowsers.get(sessionId) @@ -602,14 +595,6 @@ export class YoBrowserPresenter implements IYoBrowserPresenter { return await state.page.sendCdpCommand(method, params, beforeDispatch, projectDispatch, signal) } - async startDownload(url: string, savePath?: string): Promise { - const state = this.findPreferredSessionState() - if (!state || state.page.contents.isDestroyed()) { - throw new Error('No active session browser available') - } - return await this.downloadManager.downloadFile(url, savePath, state.page.contents) - } - async clearSandboxData(): Promise { await this.runBrowserDataMutation(async () => { await clearYoBrowserSessionData() @@ -641,16 +626,26 @@ export class YoBrowserPresenter implements IYoBrowserPresenter { }) } - async shutdown(): Promise { - for (const sessionId of Array.from(this.sessionBrowsers.keys())) { - await this.destroySessionBrowser(sessionId) - } - this.unregisterPreviewHandler() + shutdown(): Promise { + if (this.closing) return this.closing + this.closing = Promise.resolve().then(async () => { + const results = await Promise.allSettled( + [...this.sessionBrowsers.keys()].map((sessionId) => this.destroySessionBrowser(sessionId)) + ) + this.unregisterPreviewHandler() + const errors = results.flatMap((result) => + result.status === 'rejected' ? [result.reason] : [] + ) + if (errors.length) throw new AggregateError(errors, 'Native browser pages did not retire') + }) + return this.closing } private ensureSessionBrowserState(sessionId: string): SessionBrowserState { + if (this.closing) throw new Error('Native browser is stopping') const existing = this.sessionBrowsers.get(sessionId) if (existing) { + if (existing.closing) throw new Error('Native browser is stopping') return existing } @@ -724,6 +719,7 @@ export class YoBrowserPresenter implements IYoBrowserPresenter { } private async runBrowserDataMutation(mutation: () => Promise): Promise { + if (this.closing) throw new Error('Native browser is stopping') if (this.browserDataMutationActive) { throw new Error('browser_data_mutation_in_progress') } @@ -840,13 +836,13 @@ export class YoBrowserPresenter implements IYoBrowserPresenter { ) contents.on('destroyed', () => { - this.handleDestroyedContents(sessionId) + this.handleDestroyedContents(state) }) } - private handleDestroyedContents(sessionId: string): void { - const state = this.sessionBrowsers.get(sessionId) - if (!state) { + private handleDestroyedContents(state: SessionBrowserState): void { + const { sessionId } = state + if (this.sessionBrowsers.get(sessionId) !== state) { return } @@ -987,20 +983,6 @@ export class YoBrowserPresenter implements IYoBrowserPresenter { return null } - private findPreferredSessionState(): SessionBrowserState | null { - const states = [...this.sessionBrowsers.values()] - if (states.length === 0) { - return null - } - - const visibleState = states.find((state) => state.visible) - if (visibleState) { - return visibleState - } - - return states.sort((left, right) => right.updatedAt - left.updatedAt)[0] ?? null - } - private resolveHostWindowId(preferredWindowId?: number): number | null { if (preferredWindowId != null) { const preferredWindow = BrowserWindow.fromId(preferredWindowId) @@ -1505,6 +1487,7 @@ export class YoBrowserPresenter implements IYoBrowserPresenter { } private ensurePreviewHost(state: SessionBrowserState): boolean { + if (this.closing || state.closing) return false if (state.previewHost && !state.previewHost.isDestroyed()) { return true } diff --git a/src/main/desktop/browser/YoBrowserToolHandler.ts b/src/main/desktop/browser/YoBrowserToolHandler.ts index 2d4ff02487..97a7700101 100644 --- a/src/main/desktop/browser/YoBrowserToolHandler.ts +++ b/src/main/desktop/browser/YoBrowserToolHandler.ts @@ -25,7 +25,7 @@ export class YoBrowserToolHandler { args: Record, conversationId?: string, runId?: string, - beforeInvoke?: (normalizedArguments: Record) => void, + beforeInvoke?: (normalizedArguments: Record) => unknown, signal?: AbortSignal ): Promise { try { diff --git a/src/main/desktop/browser/yoBrowserSession.ts b/src/main/desktop/browser/yoBrowserSession.ts index da50a6666c..4addca3288 100644 --- a/src/main/desktop/browser/yoBrowserSession.ts +++ b/src/main/desktop/browser/yoBrowserSession.ts @@ -1,4 +1,5 @@ import { session, WebContentsView, type CookiesSetDetails, type Session } from 'electron' +import { closeWebContents } from '../closeWebContents' export const YO_BROWSER_PARTITION = 'persist:yo-browser' @@ -99,8 +100,11 @@ export async function getYoBrowserUnpartitionedCookies(): Promise !cookie.partitionKey && !cookie.partitionKeyOpaque) .map(toElectronCookie) } finally { - if (debugSession.isAttached()) debugSession.detach() - view.webContents.close() + try { + if (debugSession.isAttached()) debugSession.detach() + } finally { + await closeWebContents(view.webContents) + } } } diff --git a/src/main/desktop/closeWebContents.ts b/src/main/desktop/closeWebContents.ts new file mode 100644 index 0000000000..b341f24730 --- /dev/null +++ b/src/main/desktop/closeWebContents.ts @@ -0,0 +1,14 @@ +import type { WebContents } from 'electron' + +export async function closeWebContents(contents: WebContents): Promise { + if (contents.isDestroyed()) return + await new Promise((resolve, reject) => { + contents.once('destroyed', resolve) + try { + contents.close({ waitForBeforeUnload: false }) + } catch (error) { + contents.removeListener('destroyed', resolve) + reject(error) + } + }) +} diff --git a/src/main/desktop/configurationHost.ts b/src/main/desktop/configurationHost.ts new file mode 100644 index 0000000000..41b9120a45 --- /dev/null +++ b/src/main/desktop/configurationHost.ts @@ -0,0 +1,91 @@ +import { mkdir } from 'node:fs/promises' +import { isAbsolute, join } from 'node:path' +import { shell } from 'electron' +import fontList from 'font-list' +import { reportNativeMainError } from '@/logging' + +export class DesktopConfigurationHost { + private state: 'active' | 'paused' | 'closed' = 'paused' + private restartPending = false + private restartTimer: NodeJS.Timeout | undefined + + constructor( + private readonly userDataDirectory: string, + private readonly setLoggingPersistence: (enabled: boolean) => void, + private readonly restartApplication: () => Promise + ) { + if (!isAbsolute(userDataDirectory)) + throw new Error('Desktop profile directory must be absolute') + } + + async listSystemFonts(): Promise { + this.assertOpen() + return await fontList.getFonts() + } + + async openLoggingFolder(): Promise { + this.assertOpen() + const directory = join(this.userDataDirectory, 'logs') + await mkdir(directory, { recursive: true }) + this.assertOpen() + const error = await shell.openPath(directory) + if (error) throw new Error(`Unable to open logging folder: ${error}`) + } + + async applyLogging(enabled: boolean): Promise { + this.assertOpen() + this.setLoggingPersistence(enabled) + this.requestRestart() + } + + requestRestart(): void { + this.assertOpen() + this.restartPending = true + this.scheduleRestart() + } + + pause(): void { + if (this.state === 'closed') return + this.state = 'paused' + this.clearRestartTimer() + } + + resume(): void { + this.assertOpen() + this.state = 'active' + this.scheduleRestart() + } + + close(): void { + this.state = 'closed' + this.restartPending = false + this.clearRestartTimer() + } + + private assertOpen(): void { + if (this.state === 'closed') throw new Error('Desktop configuration host is closed') + } + + private clearRestartTimer(): void { + clearTimeout(this.restartTimer) + this.restartTimer = undefined + } + + private scheduleRestart(): void { + this.clearRestartTimer() + if (this.state !== 'active' || !this.restartPending) return + this.restartTimer = setTimeout(() => { + this.restartTimer = undefined + if (this.state !== 'active' || !this.restartPending) return + this.restartPending = false + try { + // The shutdown coordinator owns a fired restart; awaiting it during close would deadlock. + void this.restartApplication().catch((error) => { + reportNativeMainError('Application restart failed:', error) + }) + } catch (error) { + reportNativeMainError('Application restart failed:', error) + } + }, 1000) + } +} diff --git a/src/main/desktop/configurationProjection.ts b/src/main/desktop/configurationProjection.ts new file mode 100644 index 0000000000..2360e63e22 --- /dev/null +++ b/src/main/desktop/configurationProjection.ts @@ -0,0 +1,82 @@ +import type { NativeConfigurationSnapshot } from '@shared/types/nativeConfiguration' +import type { FloatingButtonBounds } from '@shared/types/floating-widget' + +export class DesktopConfigurationProjection { + private snapshot: NativeConfigurationSnapshot + private acceptingWrites = true + private readonly pendingWrites = new Set>() + + constructor( + initial: NativeConfigurationSnapshot, + private readonly readNativeDarkMode: () => boolean, + private readonly writeBounds: (bounds: FloatingButtonBounds) => Promise + ) { + this.snapshot = structuredClone(initial) + } + + apply(snapshot: NativeConfigurationSnapshot): void { + if ( + !Number.isSafeInteger(snapshot.version) || + snapshot.ownerId !== this.snapshot.ownerId || + snapshot.version <= this.snapshot.version + ) + return + this.snapshot = structuredClone(snapshot) + } + + getLanguage() { + return this.snapshot.language + } + getNotificationsEnabled() { + return this.snapshot.notificationsEnabled + } + getCloseToQuit() { + return this.snapshot.closeToQuit + } + getContentProtectionEnabled() { + return this.snapshot.contentProtectionEnabled + } + getShortcutKeys() { + return structuredClone(this.snapshot.shortcutKeys) + } + getFloatingButtonEnabled() { + return this.snapshot.floatingButtonEnabled + } + getFloatingButtonBounds() { + return structuredClone(this.snapshot.floatingButtonBounds) + } + getLoggingEnabled() { + return this.snapshot.loggingEnabled + } + isPrivacyModeEnabled() { + return this.snapshot.privacyModeEnabled + } + getChannel() { + return this.snapshot.updateChannel + } + async getCurrentThemeIsDark(): Promise { + return this.readNativeDarkMode() + } + + async setFloatingButtonBounds(bounds: FloatingButtonBounds): Promise { + if (!this.acceptingWrites) throw new Error('Native configuration writes are stopped') + const pending = this.writeBounds(structuredClone(bounds)) + this.pendingWrites.add(pending) + try { + await pending + } finally { + this.pendingWrites.delete(pending) + } + } + + stopWrites(): void { + this.acceptingWrites = false + } + startWrites(): void { + if (this.pendingWrites.size) throw new Error('Native configuration writes are still draining') + this.acceptingWrites = true + } + async drainWrites(): Promise { + await Promise.allSettled(this.pendingWrites) + } +} diff --git a/src/main/desktop/contextMenu.ts b/src/main/desktop/contextMenu.ts index 851fc7dbb1..093bbd4c75 100644 --- a/src/main/desktop/contextMenu.ts +++ b/src/main/desktop/contextMenu.ts @@ -1,5 +1,6 @@ import logger from '@shared/logger' -import { BrowserWindow, Menu, MenuItemConstructorOptions, WebContents, dialog, net } from 'electron' +import { BrowserWindow, Menu, MenuItemConstructorOptions, WebContents, net } from 'electron' +import type { DialogService } from './dialog' import path from 'path' import sharp from 'sharp' import type { DeepchatEventPublisher } from '@shared/contracts/events' @@ -7,6 +8,7 @@ import type { DeepchatEventPublisher } from '@shared/contracts/events' interface ContextMenuOptions { webContents: WebContents publishEvent: DeepchatEventPublisher + showSaveDialog: DialogService['showSaveDialog'] shouldShowMenu?: (event: Electron.Event, params: Electron.ContextMenuParams) => boolean labels?: Record prepend?: ( @@ -119,7 +121,10 @@ export default function contextMenu(options: ContextMenuOptions): () => void { } // 打开保存对话框 - const { canceled, filePath } = await dialog.showSaveDialog({ + const parent = BrowserWindow.fromWebContents(options.webContents) + if (!parent || parent.isDestroyed()) + throw new Error('Native dialog owner is unavailable') + const { canceled, filePath } = await options.showSaveDialog(parent, { defaultPath: fileName, filters: [ { name: '图片', extensions: ['png', 'jpg', 'jpeg', 'gif', 'webp'] }, diff --git a/src/main/desktop/dialog.ts b/src/main/desktop/dialog.ts index b05a48da5e..ddc1dd8cff 100644 --- a/src/main/desktop/dialog.ts +++ b/src/main/desktop/dialog.ts @@ -1,4 +1,6 @@ import logger from '@shared/logger' +import { dialog, type BrowserWindow } from 'electron' +import { CORE_NATIVE_CALLS } from '@shared/harness/core' /** * Message dialog implemented via the renderer process * The dialog is displayed on the current default window content. If it is in the background, it will automatically switch to the foreground. @@ -14,6 +16,8 @@ import type { DeepchatEventPublisher } from '@shared/contracts/events' import { nanoid } from 'nanoid' export class DialogService implements DialogServicePort { + private stopped = false + private readonly nativeDialogs = new Map, BrowserWindow>() private pendingDialogs = new Map< string, { @@ -24,12 +28,51 @@ export class DialogService implements DialogServicePort { constructor(private readonly publishEvent: DeepchatEventPublisher) {} + showOpenDialog(parent: BrowserWindow, options: Electron.OpenDialogOptions) { + return this.showNativeDialog(parent, () => dialog.showOpenDialog(parent, options)) + } + + showSaveDialog(parent: BrowserWindow, options: Electron.SaveDialogOptions) { + return this.showNativeDialog(parent, () => dialog.showSaveDialog(parent, options)) + } + + private async showNativeDialog(parent: BrowserWindow, open: () => Promise): Promise { + if (this.stopped) throw new Error('Dialog service is stopping') + if (this.nativeDialogs.size >= CORE_NATIVE_CALLS) + throw new Error('Native dialog limit exceeded') + // One physical picker keeps queued requests under the application's shutdown fence. + const previous = [...this.nativeDialogs.keys()].at(-1) + const pending = Promise.resolve(previous) + .catch(() => {}) + .then(() => { + if (this.stopped || parent.isDestroyed()) + throw new Error('Native dialog owner is unavailable') + return open() + }) + this.nativeDialogs.set(pending, parent) + try { + return await pending + } finally { + this.nativeDialogs.delete(pending) + } + } + + async drainNativeDialogs(): Promise { + if (!this.stopped) throw new Error('Dialog service must stop before retirement') + if (!this.nativeDialogs.size) return + if (process.platform === 'darwin') + for (const parent of new Set(this.nativeDialogs.values())) + if (!parent.isDestroyed()) parent.hide() + await Promise.allSettled(this.nativeDialogs.keys()) + } + /** * show dialog in the default active window * @param request Dialog Parameters * @returns Promise click result */ async showDialog(request: DialogRequestParams): Promise { + if (this.stopped) throw new Error('Dialog service is stopping') if (!request.title) { throw new Error('Dialog title is required') } @@ -90,4 +133,11 @@ export class DialogService implements DialogServicePort { pendingDialog?.reject(new Error(`Dialog with id ${id} was cancelled`)) } } + + destroy(): void { + this.stopped = true + for (const pending of this.pendingDialogs.values()) + pending.reject(new Error('Dialog service is stopping')) + this.pendingDialogs.clear() + } } diff --git a/src/main/desktop/floatingButton/index.ts b/src/main/desktop/floatingButton/index.ts index 59eb91936f..1146ad2d5b 100644 --- a/src/main/desktop/floatingButton/index.ts +++ b/src/main/desktop/floatingButton/index.ts @@ -2,7 +2,6 @@ import logger from '@shared/logger' import { FloatingButtonWindow } from './FloatingButtonWindow' import { FloatingButtonConfig, FloatingButtonState, DEFAULT_FLOATING_BUTTON_CONFIG } from './types' import { - buildFloatingWidgetSnapshot, getPeekedCollapsedBounds, getWidgetSizeForSnapshot, repositionWidgetForResize, @@ -11,17 +10,16 @@ import { type WidgetRect } from './layout' import type { FloatingWidgetSnapshot } from '@shared/types/floating-widget' -import type { Agent, SessionWithState } from '@shared/types/agent-interface' -import { listAvailableAgents } from '@/agent/shared/availableAgentCatalog' import { BrowserWindow, ipcMain, Menu, app, screen } from 'electron' import { FLOATING_BUTTON_EVENTS } from '@/events' -import type { AgentSettingsPort } from '@/agent/settings' import { FLOATING_BUTTON_AVAILABLE } from '@shared/featureFlags' -import type { SessionQuery } from '@/session/query' -import type { DesktopSessionBinding } from '@/desktop/sessionBinding' +import { createRendererRouteContext, type RouteContext } from '@/routes/routeRegistry' +import { createAppDocumentMatcher } from '@shared/rendererDocument' +import path from 'node:path' +import { pathToFileURL } from 'node:url' import type { WindowPresenter } from '../window' import type { TabPresenter } from '../tab' -import type { DesktopSettings } from '../settings' +import type { DesktopConfigurationProjection } from '../configurationProjection' const EMPTY_SNAPSHOT: FloatingWidgetSnapshot = { expanded: false, @@ -35,6 +33,27 @@ const COLLAPSE_REVEAL_LOCK_MS = WIDGET_LAYOUT_ANIMATION_DURATION_MS + 120 const COLLAPSED_WIDGET_INACTIVE_OPACITY = 0.5 const ACTIVE_WIDGET_OPACITY = 1 +export interface FloatingWidgetBusinessPort { + getSnapshot( + context: RouteContext, + assertActive: () => void, + signal: AbortSignal + ): Promise + getRegistryIconMarkup( + agentId: string, + iconUrl: string, + context: RouteContext, + assertActive: () => void, + signal: AbortSignal + ): Promise + openSession( + sessionId: string, + context: RouteContext, + assertActive: () => void, + signal: AbortSignal + ): Promise +} + type DragRuntimeState = { startX: number startY: number @@ -64,10 +83,12 @@ const isNonEmptyString = (payload: unknown): payload is string => export class FloatingButtonPresenter { private floatingWindow: FloatingButtonWindow | null = null private config: FloatingButtonConfig - private readonly agentSettings: Pick< - AgentSettingsPort, - 'getAcpRegistryIconMarkup' | 'listAgents' | 'getAcpEnabled' - > + private lifetime: AbortController | undefined + private refreshing: { signal: AbortSignal; dirty: boolean; promise: Promise } | undefined + private readonly matchDocument = createAppDocumentMatcher( + pathToFileURL(path.join(__dirname, '../renderer/')).href, + app.isPackaged ? undefined : process.env.ELECTRON_RENDERER_URL + ) private snapshot: FloatingWidgetSnapshot = { ...EMPTY_SNAPSHOT } private layoutAnimationTimer: ReturnType | null = null private collapseRevealTimer: ReturnType | null = null @@ -77,24 +98,18 @@ export class FloatingButtonPresenter { private pendingLayoutSync = false constructor( - agentSettings: Pick< - AgentSettingsPort, - 'getAcpRegistryIconMarkup' | 'listAgents' | 'getAcpEnabled' - >, private readonly settings: Pick< - DesktopSettings, + DesktopConfigurationProjection, | 'getFloatingButtonEnabled' | 'getFloatingButtonBounds' | 'setFloatingButtonBounds' | 'getLanguage' | 'getCurrentThemeIsDark' >, - private readonly sessionQuery: SessionQuery, - private readonly desktopSessionBinding: DesktopSessionBinding, private readonly windowPresenter: WindowPresenter, - private readonly tabPresenter: TabPresenter + private readonly tabPresenter: TabPresenter, + private readonly business: FloatingWidgetBusinessPort ) { - this.agentSettings = agentSettings this.config = { ...DEFAULT_FLOATING_BUTTON_CONFIG } @@ -128,6 +143,7 @@ export class FloatingButtonPresenter { } public destroy(): void { + this.lifetime?.abort(new Error('Floating widget closed')) this.config.enabled = false this.snapshot = { ...EMPTY_SNAPSHOT } this.isDragging = false @@ -166,10 +182,10 @@ export class FloatingButtonPresenter { this.config.enabled = true if (this.floatingWindow) { - await this.refreshWidgetState() + const window = this.floatingWindow this.refreshLanguage() await this.refreshTheme() - this.floatingWindow.show() + if (this.floatingWindow === window) this.showAfterRefresh() return } @@ -197,18 +213,41 @@ export class FloatingButtonPresenter { return this.floatingWindow?.getState() || null } - public async refreshWidgetState(): Promise { - try { - const [sessions, agents] = await Promise.all([this.loadSessions(), this.loadAgents()]) - this.snapshot = buildFloatingWidgetSnapshot(sessions, agents, this.snapshot.expanded) - this.applyWindowLayout() - this.pushSnapshotToRenderer() - } catch (error) { - console.error('Failed to refresh floating widget state:', error) - } + public refreshWidgetState(): Promise { + if (!this.floatingWindow?.exists() || !this.lifetime || this.lifetime.signal.aborted) + return Promise.resolve() + const { signal } = this.lifetime + if (this.refreshing?.signal === signal) { + this.refreshing.dirty = true + return this.refreshing.promise + } + const refresh = { signal, dirty: true, promise: Promise.resolve() } + this.refreshing = refresh + refresh.promise = (async () => { + try { + while (refresh.dirty && !signal.aborted) { + refresh.dirty = false + const caller = this.widgetCaller() + const snapshot = await this.business.getSnapshot( + caller.context, + caller.assertActive, + signal + ) + caller.assertActive() + this.snapshot = { ...snapshot, expanded: this.snapshot.expanded } + this.applyWindowLayout() + this.pushSnapshotToRenderer() + } + } catch (error) { + if (!signal.aborted) console.error('Failed to refresh floating widget state:', error) + } finally { + if (this.refreshing === refresh) this.refreshing = undefined + } + })() + return refresh.promise } - public refreshLanguage(): void { + public refreshLanguage(locale = this.settings.getLanguage()): void { if (!this.floatingWindow?.exists()) { return } @@ -218,13 +257,10 @@ export class FloatingButtonPresenter { return } - buttonWindow.webContents.send( - FLOATING_BUTTON_EVENTS.LANGUAGE_CHANGED, - this.settings.getLanguage() - ) + buttonWindow.webContents.send(FLOATING_BUTTON_EVENTS.LANGUAGE_CHANGED, locale) } - public async refreshTheme(): Promise { + public async refreshTheme(isDark?: boolean): Promise { if (!this.floatingWindow?.exists()) { return } @@ -234,22 +270,43 @@ export class FloatingButtonPresenter { return } - buttonWindow.webContents.send(FLOATING_BUTTON_EVENTS.THEME_CHANGED, await this.resolveTheme()) + const theme = isDark === undefined ? await this.resolveTheme() : isDark ? 'dark' : 'light' + if (this.floatingWindow?.getWindow() !== buttonWindow || buttonWindow.isDestroyed()) return + buttonWindow.webContents.send(FLOATING_BUTTON_EVENTS.THEME_CHANGED, theme) } private async createFloatingWindow(): Promise { this.registerIpcHandlers() if (!this.floatingWindow) { + this.lifetime = new AbortController() const persistedBounds = this.settings.getFloatingButtonBounds() this.floatingWindow = new FloatingButtonWindow(this.config, persistedBounds) - await this.floatingWindow.create() + const window = this.floatingWindow + await window.create() + if (this.floatingWindow !== window) return } - await this.refreshWidgetState() + const window = this.floatingWindow this.refreshLanguage() await this.refreshTheme() - this.floatingWindow.show() + if (this.floatingWindow === window) this.showAfterRefresh() + } + + private showAfterRefresh(): void { + const window = this.floatingWindow + const lifetime = this.lifetime + if (!window || !lifetime || lifetime.signal.aborted) return + // Native startup must acknowledge before a Core snapshot can complete. + void this.refreshWidgetState() + .then(() => { + if (this.floatingWindow === window && !lifetime.signal.aborted && window.exists()) { + window.show() + } + }) + .catch((error) => { + if (!lifetime.signal.aborted) console.error('Failed to show floating widget:', error) + }) } private registerIpcHandlers(): void { @@ -269,33 +326,43 @@ export class FloatingButtonPresenter { let dragState: DragRuntimeState | null = null - ipcMain.handle(FLOATING_BUTTON_EVENTS.SNAPSHOT_REQUEST, async () => { + ipcMain.handle(FLOATING_BUTTON_EVENTS.SNAPSHOT_REQUEST, async (event) => { + this.widgetCaller(event).assertActive() if (this.snapshot.sessions.length === 0 && !this.snapshot.expanded) { - await this.refreshWidgetState() + void this.refreshWidgetState() } return this.snapshot }) - ipcMain.handle(FLOATING_BUTTON_EVENTS.LANGUAGE_REQUEST, async () => { + ipcMain.handle(FLOATING_BUTTON_EVENTS.LANGUAGE_REQUEST, async (event) => { + this.widgetCaller(event).assertActive() return this.settings.getLanguage() }) - ipcMain.handle(FLOATING_BUTTON_EVENTS.THEME_REQUEST, async () => { + ipcMain.handle(FLOATING_BUTTON_EVENTS.THEME_REQUEST, async (event) => { + this.widgetCaller(event).assertActive() return await this.resolveTheme() }) ipcMain.handle( FLOATING_BUTTON_EVENTS.ACP_REGISTRY_ICON_REQUEST, - async (_event, payload: { agentId?: string; iconUrl?: string }) => { - const agentId = payload?.agentId?.trim() - const iconUrl = payload?.iconUrl?.trim() + async (event, payload: { agentId?: string; iconUrl?: string }) => { + const caller = this.widgetCaller(event) + const agentId = typeof payload?.agentId === 'string' ? payload.agentId.trim() : '' + const iconUrl = typeof payload?.iconUrl === 'string' ? payload.iconUrl.trim() : '' if (!agentId || !iconUrl) { return '' } try { - return (await this.agentSettings.getAcpRegistryIconMarkup(agentId, iconUrl)) ?? '' + return await this.business.getRegistryIconMarkup( + agentId, + iconUrl, + caller.context, + caller.assertActive, + caller.signal + ) } catch (error) { console.warn('Failed to resolve floating ACP registry icon markup:', error) return '' @@ -303,35 +370,42 @@ export class FloatingButtonPresenter { } ) - ipcMain.on(FLOATING_BUTTON_EVENTS.CLICKED, () => { + ipcMain.on(FLOATING_BUTTON_EVENTS.CLICKED, (event) => { + if (!this.isWidgetSender(event)) return this.toggleExpanded() }) - ipcMain.on(FLOATING_BUTTON_EVENTS.RIGHT_CLICKED, () => { + ipcMain.on(FLOATING_BUTTON_EVENTS.RIGHT_CLICKED, (event) => { + if (!this.isWidgetSender(event)) return this.showContextMenu() }) ipcMain.on(FLOATING_BUTTON_EVENTS.HOVER_STATE_CHANGED, (_event, hovering: boolean) => { + if (!this.isWidgetSender(_event)) return this.setHovering(Boolean(hovering)) }) - ipcMain.on(FLOATING_BUTTON_EVENTS.TOGGLE_EXPANDED, () => { + ipcMain.on(FLOATING_BUTTON_EVENTS.TOGGLE_EXPANDED, (event) => { + if (!this.isWidgetSender(event)) return this.toggleExpanded() }) ipcMain.on(FLOATING_BUTTON_EVENTS.SET_EXPANDED, (_event, expanded: boolean) => { + if (!this.isWidgetSender(_event)) return this.setExpanded(Boolean(expanded)) }) ipcMain.on(FLOATING_BUTTON_EVENTS.OPEN_SESSION, (_event, sessionId: unknown) => { + if (!this.isWidgetSender(_event)) return if (!isNonEmptyString(sessionId)) { return } - void this.openSession(sessionId.trim()) + void this.openSession(sessionId.trim(), this.widgetCaller(_event)) }) - ipcMain.on(FLOATING_BUTTON_EVENTS.DRAG_START, (_event, payload: unknown) => { + ipcMain.on(FLOATING_BUTTON_EVENTS.DRAG_START, (event, payload: unknown) => { + if (!this.isWidgetSender(event)) return if (!isPointPayload(payload)) { return } @@ -362,7 +436,8 @@ export class FloatingButtonPresenter { } }) - ipcMain.on(FLOATING_BUTTON_EVENTS.DRAG_MOVE, (_event, payload: unknown) => { + ipcMain.on(FLOATING_BUTTON_EVENTS.DRAG_MOVE, (event, payload: unknown) => { + if (!this.isWidgetSender(event)) return if (!isPointPayload(payload)) { return } @@ -382,7 +457,8 @@ export class FloatingButtonPresenter { }) }) - ipcMain.on(FLOATING_BUTTON_EVENTS.DRAG_END, () => { + ipcMain.on(FLOATING_BUTTON_EVENTS.DRAG_END, (event) => { + if (!this.isWidgetSender(event)) return if (!dragState || !this.floatingWindow?.exists()) { this.isDragging = false dragState = null @@ -418,6 +494,46 @@ export class FloatingButtonPresenter { }) } + private isWidgetSender(event: Electron.IpcMainEvent | Electron.IpcMainInvokeEvent): boolean { + const window = this.floatingWindow?.getWindow() + if (!window || window.isDestroyed()) return false + return ( + event.sender.id === window.webContents.id && + event.senderFrame === window.webContents.mainFrame && + !event.sender.isDestroyed() && + event.senderFrame?.url === window.webContents.getURL() && + this.matchDocument(event.senderFrame.url) === 'floating' + ) + } + + private widgetCaller(event?: Electron.IpcMainEvent | Electron.IpcMainInvokeEvent) { + const window = this.floatingWindow?.getWindow() + const signal = this.lifetime?.signal + if (!window || !signal) throw new Error('Floating widget is unavailable') + const frame = event?.senderFrame ?? window.webContents.mainFrame + const url = frame.url + const assertActive = () => { + signal.throwIfAborted() + if ( + this.floatingWindow?.getWindow() !== window || + window.isDestroyed() || + window.webContents.isDestroyed() || + window.webContents.mainFrame !== frame || + frame.url !== url || + window.webContents.getURL() !== url || + this.matchDocument(url) !== 'floating' || + (event && !this.isWidgetSender(event)) + ) + throw new Error('Floating widget caller retired') + } + assertActive() + return { + context: createRendererRouteContext(window.webContents.id, window.id), + assertActive, + signal + } + } + private setExpanded(expanded: boolean): void { if (this.snapshot.expanded === expanded) { return @@ -571,11 +687,15 @@ export class FloatingButtonPresenter { private persistFloatingBounds(snapped: WidgetRect & { dockSide: FloatingWidgetDockSide }): void { try { - this.settings.setFloatingButtonBounds({ - x: snapped.x, - y: snapped.y, - dockSide: snapped.dockSide - }) + void this.settings + .setFloatingButtonBounds({ + x: snapped.x, + y: snapped.y, + dockSide: snapped.dockSide + }) + .catch((error) => { + console.error('Failed to persist floating button bounds:', error) + }) } catch (error) { console.error('Failed to persist floating button bounds:', error) } @@ -631,30 +751,29 @@ export class FloatingButtonPresenter { } private async resolveTheme(): Promise<'dark' | 'light'> { - const isDark = this.settings.getCurrentThemeIsDark() + const isDark = await this.settings.getCurrentThemeIsDark() return isDark ? 'dark' : 'light' } - private async loadSessions(): Promise { - return await this.sessionQuery.listSessions() - } - - private async loadAgents(): Promise { - return await listAvailableAgents(this.agentSettings) - } - - private async openSession(sessionId: string): Promise { + private async openSession( + sessionId: string, + caller: ReturnType + ): Promise { try { - const targetWindow = await this.resolveChatWindow() - if (!targetWindow || targetWindow.isDestroyed()) { - return + if ( + await this.business.openSession( + sessionId, + caller.context, + caller.assertActive, + caller.signal + ) + ) { + caller.assertActive() + this.setExpanded(false) } - - await this.desktopSessionBinding.activate(targetWindow.webContents.id, sessionId) - this.windowPresenter.show(targetWindow.id, true) - this.setExpanded(false) } catch (error) { - console.error('Failed to open session from floating widget:', error) + if (!caller.signal.aborted) + console.error('Failed to open session from floating widget:', error) } } diff --git a/src/main/desktop/floatingButton/layout.ts b/src/main/desktop/floatingButton/layout.ts index 313535a30c..810ca0704b 100644 --- a/src/main/desktop/floatingButton/layout.ts +++ b/src/main/desktop/floatingButton/layout.ts @@ -1,11 +1,4 @@ -import type { Agent, SessionWithState } from '@shared/types/agent-interface' -import type { - FloatingWidgetDockSide, - FloatingWidgetSessionAgent, - FloatingWidgetSessionItem, - FloatingWidgetSessionStatus, - FloatingWidgetSnapshot -} from '@shared/types/floating-widget' +import type { FloatingWidgetDockSide, FloatingWidgetSnapshot } from '@shared/types/floating-widget' export type { FloatingWidgetDockSide } @@ -28,90 +21,6 @@ export const FLOATING_WIDGET_LAYOUT = { maxVisibleSessions: 6 } as const -function mapSessionStatus(status: SessionWithState['status']): FloatingWidgetSessionStatus { - switch (status) { - case 'generating': - return 'in_progress' - case 'error': - return 'error' - case 'idle': - default: - return 'done' - } -} - -function getStatusPriority(status: FloatingWidgetSessionStatus): number { - switch (status) { - case 'in_progress': - return 0 - case 'error': - return 1 - case 'done': - default: - return 2 - } -} - -function mapSessionAgent( - session: SessionWithState, - agentsById: Map -): FloatingWidgetSessionAgent { - const matchedAgent = agentsById.get(session.agentId) - if (matchedAgent) { - return matchedAgent - } - - return { - id: session.agentId, - name: session.agentId, - type: session.agentId === 'deepchat' ? 'deepchat' : 'acp' - } -} - -export function buildFloatingWidgetSnapshot( - sessions: SessionWithState[], - agents: Agent[], - expanded: boolean -): FloatingWidgetSnapshot { - const agentsById = new Map( - agents.map((agent) => [ - agent.id, - { - id: agent.id, - name: agent.name, - type: agent.type, - icon: agent.icon, - avatar: agent.avatar - } - ]) - ) - - const mappedSessions: FloatingWidgetSessionItem[] = sessions - .filter((session) => !session.isDraft) - .map((session) => ({ - id: session.id, - title: session.title.trim(), - status: mapSessionStatus(session.status), - updatedAt: session.updatedAt, - agent: mapSessionAgent(session, agentsById) - })) - .sort((left, right) => { - const statusDiff = getStatusPriority(left.status) - getStatusPriority(right.status) - if (statusDiff !== 0) { - return statusDiff - } - return right.updatedAt - left.updatedAt - }) - - const activeCount = mappedSessions.filter((session) => session.status === 'in_progress').length - - return { - expanded, - activeCount, - sessions: mappedSessions - } -} - export function inferDockSide(bounds: WidgetRect, workArea: WidgetRect): FloatingWidgetDockSide { const widgetCenterX = bounds.x + bounds.width / 2 const workAreaCenterX = workArea.x + workArea.width / 2 diff --git a/src/main/desktop/fontSettings.ts b/src/main/desktop/fontSettings.ts index 1b8bb0da16..d11421f52b 100644 --- a/src/main/desktop/fontSettings.ts +++ b/src/main/desktop/fontSettings.ts @@ -1,4 +1,3 @@ -import fontList from 'font-list' import type { SettingsStore } from '@/config/settingsStore' import type { DeepchatEventPublisher } from '@shared/contracts/events' @@ -25,7 +24,8 @@ export class FontSettings { constructor( private readonly settings: SettingsStore, - private readonly publishEvent: DeepchatEventPublisher + private readonly publishEvent: DeepchatEventPublisher, + private readonly enumerateFonts: () => Promise ) {} getFontFamily(): string { @@ -48,7 +48,7 @@ export class FontSettings { if (this.systemFontsCache) return this.systemFontsCache try { - const detected = await fontList.getFonts() + const detected = await this.enumerateFonts() const seen = new Set() this.systemFontsCache = detected.flatMap((font) => { const name = normalizeFontNameValue(font) @@ -58,7 +58,7 @@ export class FontSettings { return [name] }) } catch (error) { - console.warn('Failed to detect system fonts with font-list:', error) + console.warn('Failed to detect system fonts:', error) this.systemFontsCache = [] } return this.systemFontsCache diff --git a/src/main/desktop/imageActions.ts b/src/main/desktop/imageActions.ts new file mode 100644 index 0000000000..e6912a4e68 --- /dev/null +++ b/src/main/desktop/imageActions.ts @@ -0,0 +1,275 @@ +import { clipboard, nativeImage, net } from 'electron' +import fs from 'node:fs/promises' +import path from 'node:path' + +type SaveImageInput = { + source: string + mimeType?: string + suggestedName?: string +} + +type ResolvedImageData = { + data: Buffer + mimeType: string +} + +const IMAGE_SAVE_EXTENSIONS = ['png', 'jpg', 'jpeg', 'webp', 'gif', 'bmp', 'svg', 'avif', 'ico'] +const INVALID_FILE_NAME_CHARS = new Set(['<', '>', ':', '"', '/', '\\', '|', '?', '*']) + +function normalizeImageMimeType(mimeType?: string): string | null { + const normalized = mimeType?.split(';')[0]?.trim().toLowerCase() + if (!normalized?.startsWith('image/')) { + return null + } + return normalized +} + +function getImageExtensionFromMimeType(mimeType?: string): string { + const normalized = normalizeImageMimeType(mimeType) + if (!normalized) { + return 'png' + } + + switch (normalized) { + case 'image/jpeg': + case 'image/jpg': + return 'jpg' + case 'image/svg+xml': + return 'svg' + case 'image/x-icon': + case 'image/vnd.microsoft.icon': + return 'ico' + default: + return normalized.replace('image/', '').replace(/[^a-z0-9]/g, '') || 'png' + } +} + +function inferImageMimeTypeFromPath(filePath: string): string | null { + const extension = path.extname(filePath).toLowerCase() + switch (extension) { + case '.png': + return 'image/png' + case '.jpg': + case '.jpeg': + return 'image/jpeg' + case '.gif': + return 'image/gif' + case '.webp': + return 'image/webp' + case '.bmp': + return 'image/bmp' + case '.svg': + return 'image/svg+xml' + case '.avif': + return 'image/avif' + case '.ico': + return 'image/x-icon' + default: + return null + } +} + +function sanitizeFileName(fileName?: string): string | null { + const sanitized = fileName + ?.split('') + .map((char) => (INVALID_FILE_NAME_CHARS.has(char) || char.charCodeAt(0) < 32 ? '_' : char)) + .join('') + .replace(/\s+/g, ' ') + .replace(/[. ]+$/g, '') + .trim() + + return sanitized || null +} + +function formatImageTimestamp(date: Date): string { + const pad = (value: number) => value.toString().padStart(2, '0') + return `${date.getFullYear()}${pad(date.getMonth() + 1)}${pad(date.getDate())}-${pad( + date.getHours() + )}${pad(date.getMinutes())}${pad(date.getSeconds())}` +} + +function buildDefaultImageName(mimeType: string, suggestedName?: string): string { + const extension = getImageExtensionFromMimeType(mimeType) + const sanitizedName = sanitizeFileName(suggestedName) + if (sanitizedName) { + return path.extname(sanitizedName) ? sanitizedName : `${sanitizedName}.${extension}` + } + + return `deepchat-image-${formatImageTimestamp(new Date())}.${extension}` +} + +export class DesktopImageActions { + private readonly shutdown = new AbortController() + + constructor( + private readonly userDataPath: string, + private readonly showSaveDialog: ( + options: Electron.SaveDialogOptions + ) => Promise + ) { + if (!path.isAbsolute(userDataPath)) throw new Error('Image profile directory must be absolute') + } + + async saveImage(file: SaveImageInput): Promise<{ canceled: boolean; path?: string }> { + const image = await this.resolveImageData(file) + this.shutdown.signal.throwIfAborted() + const defaultPath = buildDefaultImageName(image.mimeType, file.suggestedName) + + const preferredExtension = getImageExtensionFromMimeType(image.mimeType) + const extensions = Array.from(new Set([preferredExtension, ...IMAGE_SAVE_EXTENSIONS])) + const { canceled, filePath } = await this.showSaveDialog({ + defaultPath, + filters: [ + { name: 'Images', extensions }, + { name: 'All Files', extensions: ['*'] } + ] + }) + this.shutdown.signal.throwIfAborted() + + if (canceled || !filePath) { + return { canceled: true } + } + + // A granted write must finish once started, including during shutdown. + await fs.writeFile(filePath, image.data) + return { canceled: false, path: filePath } + } + + async copyImage(file: SaveImageInput): Promise<{ copied: boolean }> { + const image = await this.resolveImageData(file) + this.shutdown.signal.throwIfAborted() + const clipboardImage = this.createClipboardImage(image) + clipboard.writeImage(clipboardImage) + return { copied: true } + } + + stop(): void { + this.shutdown.abort(new Error('Native image actions are stopping')) + } + + private async resolveImageData(file: SaveImageInput): Promise { + this.shutdown.signal.throwIfAborted() + const source = file.source.trim() + if (source.startsWith('data:image/')) { + return this.resolveDataUrlImage(source) + } + + if (source.startsWith('imgcache://')) { + return this.resolveCachedImage(source) + } + + if (source.startsWith('http://') || source.startsWith('https://')) { + return this.resolveRemoteImage(source, file.mimeType) + } + + return this.resolveRawBase64Image(source, file.mimeType) + } + + private resolveDataUrlImage(source: string): ResolvedImageData { + const match = source.match(/^data:(image\/[a-zA-Z0-9.+-]+);base64,(.*)$/s) + const mimeType = normalizeImageMimeType(match?.[1]) + const base64Data = match?.[2]?.replace(/\s/g, '') + + if (!mimeType || !base64Data) { + throw new Error('Invalid image data URL') + } + + const data = Buffer.from(base64Data, 'base64') + if (data.length === 0) { + throw new Error('Invalid image data URL') + } + + return { data, mimeType } + } + + private async resolveCachedImage(source: string): Promise { + const cacheDir = path.join(this.userDataPath, 'images') + const rawCachePath = source.slice('imgcache://'.length) + const cachePath = this.safeDecodePath(rawCachePath) + const fullPath = path.resolve(cacheDir, cachePath) + const relativePath = path.relative(cacheDir, fullPath) + + if (!relativePath || relativePath.startsWith('..') || path.isAbsolute(relativePath)) { + throw new Error('Invalid cached image path') + } + + const mimeType = inferImageMimeTypeFromPath(fullPath) + if (!mimeType) { + throw new Error('Invalid cached image type') + } + + return { + data: await fs.readFile(fullPath, { signal: this.shutdown.signal }), + mimeType + } + } + + private async resolveRemoteImage( + source: string, + fallbackMimeType?: string + ): Promise { + const url = new URL(source) + if (url.protocol !== 'http:' && url.protocol !== 'https:') { + throw new Error('Unsupported image URL') + } + + const response = await net.fetch(url.toString(), { signal: this.shutdown.signal }) + if (!response.ok) { + throw new Error(`Failed to download image: ${response.status}`) + } + + const responseMimeType = normalizeImageMimeType( + response.headers.get('content-type') ?? undefined + ) + const mimeType = + responseMimeType || + normalizeImageMimeType(fallbackMimeType) || + inferImageMimeTypeFromPath(url.pathname) + + if (!mimeType) { + throw new Error('Remote URL is not an image') + } + + return { + data: Buffer.from(await response.arrayBuffer()), + mimeType + } + } + + private resolveRawBase64Image(source: string, fallbackMimeType?: string): ResolvedImageData { + const mimeType = normalizeImageMimeType(fallbackMimeType) + if (!mimeType) { + throw new Error('Raw image data requires an image MIME type') + } + + const data = Buffer.from(source.replace(/\s/g, ''), 'base64') + if (data.length === 0) { + throw new Error('Invalid raw image data') + } + + return { data, mimeType } + } + + private createClipboardImage(image: ResolvedImageData): Electron.NativeImage { + const imageFromBuffer = nativeImage.createFromBuffer(image.data) + if (!imageFromBuffer.isEmpty()) { + return imageFromBuffer + } + + const dataUrl = `data:${image.mimeType};base64,${image.data.toString('base64')}` + const imageFromDataUrl = nativeImage.createFromDataURL(dataUrl) + if (!imageFromDataUrl.isEmpty()) { + return imageFromDataUrl + } + + throw new Error('Image data cannot be copied to clipboard') + } + + private safeDecodePath(filePath: string): string { + try { + return decodeURIComponent(filePath) + } catch { + return filePath + } + } +} diff --git a/src/main/desktop/notification.ts b/src/main/desktop/notification.ts index 9616d51f6e..fddd5eb65d 100644 --- a/src/main/desktop/notification.ts +++ b/src/main/desktop/notification.ts @@ -1,7 +1,7 @@ import { BrowserWindow, nativeImage, Notification, NotificationConstructorOptions } from 'electron' import icon from '../../../resources/icon.png?asset' import type { DeepchatEventPublisher } from '@shared/contracts/events' -import type { DesktopSettings } from './settings' +import type { DesktopConfigurationProjection } from './configurationProjection' const notificationMessages = import.meta.glob<{ generationComplete: string }>( '../../renderer/src/i18n/*/chat.json', @@ -12,7 +12,10 @@ export class NotificationService { private notifications = new Map() constructor( - private readonly settings: Pick, + private readonly settings: Pick< + DesktopConfigurationProjection, + 'getNotificationsEnabled' | 'getLanguage' + >, private readonly publishEvent: DeepchatEventPublisher ) {} diff --git a/src/main/desktop/pluginSettingsWindow.ts b/src/main/desktop/pluginSettingsWindow.ts index fd2c045d6c..98fb04af85 100644 --- a/src/main/desktop/pluginSettingsWindow.ts +++ b/src/main/desktop/pluginSettingsWindow.ts @@ -4,15 +4,33 @@ import { pathToFileURL } from 'node:url' import type { PluginSettingsWindowPort } from '@/plugin' export class PluginSettingsWindow implements PluginSettingsWindowPort { - private readonly windows = new Map() + private readonly windows = new Map< + string, + { + window: BrowserWindow + webContentsId: number + closing: boolean + closed: Promise + loading: Promise + finishClose: () => void + } + >() private readonly pluginIdByWebContentsId = new Map() + private accepting = true async open(input: { pluginId: string; title: string; entry: string }): Promise { + if (!this.accepting) throw new Error('Plugin settings windows are closed') const existing = this.windows.get(input.pluginId) - if (existing && !existing.isDestroyed()) { - existing.show() - existing.focus() - return + if (existing) { + if (existing.closing) { + await existing.closed + return this.open(input) + } + if (!existing.window.isDestroyed()) { + existing.window.show() + existing.window.focus() + return existing.loading + } } const settingsWindow = new BrowserWindow({ @@ -32,7 +50,19 @@ export class PluginSettingsWindow implements PluginSettingsWindowPort { const webContentsId = settingsWindow.webContents.id const entryPath = pathToFileURL(input.entry).pathname - this.windows.set(input.pluginId, settingsWindow) + let finishClose!: () => void + const closed = new Promise((resolve) => { + finishClose = resolve + }) + const record = { + window: settingsWindow, + webContentsId, + closing: false, + closed, + loading: Promise.resolve(), + finishClose + } + this.windows.set(input.pluginId, record) this.pluginIdByWebContentsId.set(webContentsId, input.pluginId) settingsWindow.webContents.setWindowOpenHandler(() => ({ action: 'deny' })) settingsWindow.webContents.on('will-navigate', (event, url) => { @@ -42,39 +72,60 @@ export class PluginSettingsWindow implements PluginSettingsWindowPort { } }) settingsWindow.on('ready-to-show', () => { - if (!settingsWindow.isDestroyed()) { + if (!record.closing && !settingsWindow.isDestroyed()) { settingsWindow.show() } }) + settingsWindow.on('close', () => { + record.closing = true + }) settingsWindow.on('closed', () => { this.pluginIdByWebContentsId.delete(webContentsId) - if (this.windows.get(input.pluginId) === settingsWindow) { + if (this.windows.get(input.pluginId) === record) { this.windows.delete(input.pluginId) } + finishClose() }) + settingsWindow.webContents.on('will-prevent-unload', (event) => event.preventDefault()) - await settingsWindow.loadFile(input.entry, { - query: { - pluginId: input.pluginId + record.loading = (async () => { + try { + await settingsWindow.loadFile(input.entry, { + query: { pluginId: input.pluginId } + }) + } catch (error) { + if (record.closing || settingsWindow.isDestroyed()) { + await closed + return + } + await this.close(input.pluginId) + throw error } - }) + })() + await record.loading } getPluginIdForWebContents(webContentsId: number): string | null { return this.pluginIdByWebContentsId.get(webContentsId) ?? null } - close(pluginId: string): void { - const settingsWindow = this.windows.get(pluginId) - if (settingsWindow && !settingsWindow.isDestroyed()) { - settingsWindow.close() + async close(pluginId: string): Promise { + const record = this.windows.get(pluginId) + if (!record) return + if (!record.closing) { + record.closing = true + if (!record.window.isDestroyed()) record.window.destroy() + else { + this.pluginIdByWebContentsId.delete(record.webContentsId) + this.windows.delete(pluginId) + record.finishClose() + } } - this.windows.delete(pluginId) + await record.closed } - closeAll(): void { - for (const pluginId of Array.from(this.windows.keys())) { - this.close(pluginId) - } + async closeAll(): Promise { + this.accepting = false + await Promise.all(Array.from(this.windows.keys(), (pluginId) => this.close(pluginId))) } } diff --git a/src/main/desktop/remoteAuthWindows.ts b/src/main/desktop/remoteAuthWindows.ts new file mode 100644 index 0000000000..e37cef0bfd --- /dev/null +++ b/src/main/desktop/remoteAuthWindows.ts @@ -0,0 +1,113 @@ +import { BrowserWindow } from 'electron' +import { resolveFeishuAuthDomains } from '@/remote/channels/feishu/feishuAuth' +import type { + RemoteAuthWindowEvent, + RemoteAuthWindowInput, + RemoteAuthWindowPort +} from '@/remote/ports' + +export class DesktopRemoteAuthWindows implements RemoteAuthWindowPort { + private readonly windows = new Map< + string, + { window: BrowserWindow; url: string; closed: Promise; closing: boolean } + >() + + constructor(private readonly publish: (event: RemoteAuthWindowEvent) => void) {} + + async open(input: RemoteAuthWindowInput): Promise { + if (input.kind === 'feishu' && !this.isAllowedFeishuNavigation(input.url, input)) + throw new Error('Invalid Feishu authorization URL') + const existing = this.windows.get(input.id) + if (existing) { + if (existing.url !== input.url) throw new Error('Auth window identity is already in use') + existing.window.show() + existing.window.focus() + return + } + const parent = BrowserWindow.getFocusedWindow() ?? BrowserWindow.getAllWindows()[0] + const window = new BrowserWindow({ + width: input.kind === 'feishu' ? 480 : 420, + height: 760, + minWidth: input.kind === 'feishu' ? 420 : 380, + minHeight: 680, + autoHideMenuBar: true, + title: input.kind === 'feishu' ? 'Feishu / Lark Authorization' : 'WeChat iLink Login', + ...(parent ? { parent } : {}), + webPreferences: { nodeIntegration: false, contextIsolation: true, webSecurity: true } + }) + const closed = Promise.withResolvers() + const entry = { window, url: input.url, closed: closed.promise, closing: false } + this.windows.set(input.id, entry) + window.on('close', () => { + entry.closing = true + }) + window.webContents.on('will-prevent-unload', (event) => event.preventDefault()) + const allowed = (url: string) => + input.kind === 'weixin-ilink' || this.isAllowedFeishuNavigation(url, input) + if (input.kind === 'feishu') { + window.webContents.on('will-navigate', (event, url) => { + if (!allowed(url)) event.preventDefault() + }) + } + const navigate = (url: string) => { + void window.loadURL(url).catch(() => { + // Closing a loading window can reject navigation before the native closed event. + if (entry.closing || window.isDestroyed()) return + if (this.windows.get(input.id) === entry) this.publish({ id: input.id, type: 'error' }) + }) + } + window.webContents.setWindowOpenHandler(({ url }) => { + if (allowed(url)) navigate(url) + return { action: 'deny' } + }) + window.once('closed', () => { + closed.resolve() + if (this.windows.get(input.id) !== entry) return + this.windows.delete(input.id) + this.publish({ id: input.id, type: 'closed' }) + }) + try { + navigate(input.url) + window.show() + window.focus() + } catch (error) { + await this.close(input.id) + throw error + } + } + + async close(id: string): Promise { + const entry = this.windows.get(id) + if (!entry) return + entry.closing = true + // An auth page must not retain an owned window with a beforeunload handler after cancellation. + if (!entry.window.isDestroyed()) entry.window.destroy() + await entry.closed + } + + async closeAll(): Promise { + await Promise.all(Array.from(this.windows.keys(), (id) => this.close(id))) + } + + private isAllowedFeishuNavigation( + url: string, + input: Extract + ): boolean { + try { + const parsed = new URL(url) + const redirect = new URL(input.redirectUri) + const domains = resolveFeishuAuthDomains(input.brand) + return ( + (parsed.protocol === 'https:' && + [domains.accountsBaseUrl, domains.openBaseUrl].some( + (base) => parsed.host === new URL(base).host + )) || + (parsed.protocol === redirect.protocol && + parsed.host === redirect.host && + parsed.pathname === redirect.pathname) + ) + } catch { + return false + } + } +} diff --git a/src/main/desktop/routes.ts b/src/main/desktop/routes.ts index 70c69ec56b..92bc163c8f 100644 --- a/src/main/desktop/routes.ts +++ b/src/main/desktop/routes.ts @@ -5,15 +5,11 @@ import type { IShortcutPresenter, ITabPresenter, IWindowPresenter, - IComputerUsePreviewPresenter, IYoBrowserPresenter } from '@shared/types/desktop' import type { DialogServicePort } from '@shared/types/dialog' -import { getLocaleDirection } from '@shared/locales' -import type { DesktopSettings } from './settings' import { browserAttachCurrentWindowRoute, - browserFocusContentRoute, browserApplyImportRoute, browserClearSandboxDataRoute, browserDismissPreviewRoute, @@ -28,17 +24,6 @@ import { browserScanImportSourcesRoute, browserSetPreviewModeRoute, browserUpdateCurrentWindowBoundsRoute, - computerUseDismissPreviewRoute, - computerUseSetPreviewModeRoute, - configGetFloatingButtonRoute, - configGetLanguageRoute, - configGetShortcutKeysRoute, - configGetThemeRoute, - configResetShortcutKeysRoute, - configSetFloatingButtonRoute, - configSetLanguageRoute, - configSetShortcutKeysRoute, - configSetThemeRoute, dialogErrorRoute, dialogRespondRoute, shortcutDestroyRoute, @@ -60,8 +45,7 @@ import { windowRequeuePendingSettingsProviderInstallRoute, windowResumeGuidedOnboardingRoute, windowStartGuidedOnboardingRoute, - windowToggleMaximizeCurrentRoute, - type SettingsActivityInput + windowToggleMaximizeCurrentRoute } from '@shared/contracts/routes' import { DEV_EVENTS } from '../events' import { @@ -70,29 +54,15 @@ import { type DeepchatRouteMap, type RouteContext } from '@/routes/routeRegistry' -import type { DesktopSessionBinding } from '@/desktop/sessionBinding' export function createDesktopRoutes(deps: { - ownsDraftSession(sessionId: string, webContentsId: number): boolean windowPresenter: IWindowPresenter shortcutPresenter: IShortcutPresenter browserPresenter: IYoBrowserPresenter - computerUsePreviewPresenter: IComputerUsePreviewPresenter - desktopSessionBinding: Pick tabPresenter: ITabPresenter dialogService: DialogServicePort - settings: DesktopSettings - setFloatingButtonEnabled(enabled: boolean): void - recordActivity(input: SettingsActivityInput): void }): DeepchatRouteMap { - const { - windowPresenter, - shortcutPresenter, - browserPresenter, - computerUsePreviewPresenter, - tabPresenter, - dialogService - } = deps + const { windowPresenter, shortcutPresenter, browserPresenter, tabPresenter, dialogService } = deps const readWindowState = (context: RouteContext) => { const caller = requireRendererCaller(context) const window = caller.windowId == null ? null : BrowserWindow.fromId(caller.windowId) @@ -107,108 +77,8 @@ export function createDesktopRoutes(deps: { } const readBrowserStatus = async (sessionId: string) => await browserPresenter.getBrowserStatus(sessionId) - const readLanguage = () => { - const locale = deps.settings.getLanguage() - return { - requestedLanguage: deps.settings.getRequestedLanguage(), - locale, - direction: getLocaleDirection(locale) - } - } - const readTheme = () => ({ - theme: deps.settings.getTheme(), - isDark: deps.settings.getCurrentThemeIsDark() - }) return createRouteMap([ - [ - configGetLanguageRoute.name, - async (rawInput) => { - configGetLanguageRoute.input.parse(rawInput) - return configGetLanguageRoute.output.parse(readLanguage()) - } - ], - [ - configSetLanguageRoute.name, - async (rawInput) => { - const input = configSetLanguageRoute.input.parse(rawInput) - deps.settings.setLanguage(input.language) - return configSetLanguageRoute.output.parse(readLanguage()) - } - ], - [ - configGetThemeRoute.name, - async (rawInput) => { - configGetThemeRoute.input.parse(rawInput) - return configGetThemeRoute.output.parse(readTheme()) - } - ], - [ - configSetThemeRoute.name, - async (rawInput) => { - const input = configSetThemeRoute.input.parse(rawInput) - deps.settings.setTheme(input.theme) - return configSetThemeRoute.output.parse(readTheme()) - } - ], - [ - configGetFloatingButtonRoute.name, - async (rawInput) => { - configGetFloatingButtonRoute.input.parse(rawInput) - return configGetFloatingButtonRoute.output.parse({ - enabled: deps.settings.getFloatingButtonEnabled() - }) - } - ], - [ - configSetFloatingButtonRoute.name, - async (rawInput) => { - const input = configSetFloatingButtonRoute.input.parse(rawInput) - deps.settings.setFloatingButtonEnabled(input.enabled) - deps.setFloatingButtonEnabled(input.enabled) - return configSetFloatingButtonRoute.output.parse({ - enabled: deps.settings.getFloatingButtonEnabled() - }) - } - ], - [ - configGetShortcutKeysRoute.name, - async (rawInput) => { - configGetShortcutKeysRoute.input.parse(rawInput) - return configGetShortcutKeysRoute.output.parse({ - shortcuts: deps.settings.getShortcutKeys() - }) - } - ], - [ - configSetShortcutKeysRoute.name, - async (rawInput) => { - const input = configSetShortcutKeysRoute.input.parse(rawInput) - deps.settings.setShortcutKeys(input.shortcuts) - return configSetShortcutKeysRoute.output.parse({ - shortcuts: deps.settings.getShortcutKeys() - }) - } - ], - [ - configResetShortcutKeysRoute.name, - async (rawInput) => { - configResetShortcutKeysRoute.input.parse(rawInput) - deps.settings.resetShortcutKeys() - deps.recordActivity({ - category: 'shortcut', - action: 'reset', - targetType: 'shortcut', - targetLabel: 'Shortcuts', - routeName: 'settings-shortcut', - summaryKey: 'settings.controlCenter.activity.settingUpdated', - summaryParams: { key: 'shortcuts' } - }) - return configResetShortcutKeysRoute.output.parse({ - shortcuts: deps.settings.getShortcutKeys() - }) - } - ], [ shortcutRegisterRoute.name, async (rawInput) => { @@ -402,22 +272,6 @@ export function createDesktopRoutes(deps: { }) } ], - [ - browserFocusContentRoute.name, - async (rawInput, context) => { - const input = browserFocusContentRoute.input.parse(rawInput) - const caller = requireRendererCaller(context) - const active = - deps.desktopSessionBinding.getActiveId(caller.webContentsId) === input.sessionId || - deps.ownsDraftSession(input.sessionId, caller.webContentsId) - return browserFocusContentRoute.output.parse({ - focused: - caller.windowId != null && - active && - browserPresenter.focusSessionBrowser(input.sessionId, caller.windowId) - }) - } - ], [ browserAttachCurrentWindowRoute.name, async (rawInput, context) => { @@ -480,43 +334,6 @@ export function createDesktopRoutes(deps: { }) } ], - [ - computerUseSetPreviewModeRoute.name, - async (rawInput, context) => { - const input = computerUseSetPreviewModeRoute.input.parse(rawInput) - const caller = requireRendererCaller(context) - if ( - caller.windowId == null || - (input.mode !== 'stopped' && - deps.desktopSessionBinding.getActiveId(caller.webContentsId) !== input.sessionId) - ) { - return computerUseSetPreviewModeRoute.output.parse({ - updated: false, - surface: 'none' - }) - } - return computerUseSetPreviewModeRoute.output.parse( - await computerUsePreviewPresenter.setPreviewMode( - input.sessionId, - input.mode, - caller.windowId - ) - ) - } - ], - [ - computerUseDismissPreviewRoute.name, - async (rawInput, context) => { - const input = computerUseDismissPreviewRoute.input.parse(rawInput) - const caller = requireRendererCaller(context) - const active = - deps.desktopSessionBinding.getActiveId(caller.webContentsId) === input.sessionId - return computerUseDismissPreviewRoute.output.parse({ - dismissed: - active && computerUsePreviewPresenter.dismissPreview(input.sessionId, input.runId) - }) - } - ], [ browserDestroyRoute.name, async (rawInput) => { diff --git a/src/main/desktop/sessionBinding.ts b/src/main/desktop/sessionBinding.ts index de0dab33cc..c98ec53ca2 100644 --- a/src/main/desktop/sessionBinding.ts +++ b/src/main/desktop/sessionBinding.ts @@ -12,15 +12,35 @@ export interface DesktopSessionProjectionPort { export class DesktopSessionBinding { private readonly bindings = new Map() + private readonly watches = new Map() - constructor(private readonly projection: DesktopSessionProjectionPort) {} + constructor( + private readonly projection: DesktopSessionProjectionPort, + private readonly onRendererDestroyed: ( + webContentsId: number, + listener: () => void + ) => () => void + ) {} bind(webContentsId: number, sessionId: string): void { this.bindings.set(webContentsId, sessionId) + if (this.watches.has(webContentsId)) return + const watch = { dispose() {} } + this.watches.set(webContentsId, watch) + try { + watch.dispose = this.onRendererDestroyed(webContentsId, () => this.unbind(webContentsId)) + if (this.watches.get(webContentsId) !== watch) watch.dispose() + } catch (error) { + this.unbind(webContentsId) + throw error + } } unbind(webContentsId: number): void { this.bindings.delete(webContentsId) + const watch = this.watches.get(webContentsId) + this.watches.delete(webContentsId) + watch?.dispose() } getActiveId(webContentsId: number): string | null { @@ -35,6 +55,7 @@ export class DesktopSessionBinding { async activate(webContentsId: number, sessionId: string): Promise { this.bind(webContentsId, sessionId) + if (this.getActiveId(webContentsId) !== sessionId) return this.projection.notify({ sessionIds: [sessionId], reason: 'activated', @@ -58,6 +79,7 @@ export class DesktopSessionBinding { if (!sessionId) return null const session = await this.projection.getSession(sessionId) + if (this.getActiveId(webContentsId) !== sessionId) return null if (!session) this.unbind(webContentsId) return session } diff --git a/src/main/desktop/settings.ts b/src/main/desktop/settings.ts index 832d817bd4..14e1905abb 100644 --- a/src/main/desktop/settings.ts +++ b/src/main/desktop/settings.ts @@ -2,7 +2,6 @@ import type { SettingsStore } from '@/config/settingsStore' import type { DeepchatEventPublisher } from '@shared/contracts/events' import type { ShortcutKeySetting } from '@shared/types/desktop' import { defaultShortcutKey } from './shortcutKeySettings' -import { app, nativeTheme } from 'electron' import type { FloatingButtonBounds } from '@shared/types/floating-widget' import { getLocaleDirection, @@ -12,14 +11,24 @@ import { type SupportedLocale } from '@shared/locales' +export interface DesktopSettingsHost { + readonly systemLocale: string + readTheme(): Promise<{ theme: 'dark' | 'light' | 'system'; isDark: boolean }> + applyTheme(theme: 'dark' | 'light' | 'system'): Promise + refreshLanguage(locale: SupportedLocale): Promise + refreshTheme(isDark: boolean): Promise + readLaunchAtLogin(): Promise + setLaunchAtLogin(enabled: boolean): Promise + applyContentProtection(enabled: boolean): Promise + applyFloatingButtonEnabled(enabled: boolean): Promise +} + export class DesktopSettings { constructor( private readonly settings: SettingsStore, - private readonly effects: { - refreshLanguage(): void - refreshTheme(): Promise - }, - private readonly publishEvent: DeepchatEventPublisher + private readonly host: DesktopSettingsHost, + private readonly publishEvent: DeepchatEventPublisher, + private readonly committed: () => void | Promise ) {} getRequestedLanguage(): RequestedLocale { @@ -28,52 +37,57 @@ export class DesktopSettings { getLanguage(): SupportedLocale { const language = this.getRequestedLanguage() - return resolveSupportedLocale(language === 'system' ? app.getLocale() : language) + return resolveSupportedLocale(language === 'system' ? this.host.systemLocale : language) } - setLanguage(language: string): void { + async setLanguage(language: string): Promise { const requestedLanguage = resolveRequestedLocale(language) this.settings.set('language', requestedLanguage) + await this.committed() const locale = this.getLanguage() + await this.host.refreshLanguage(locale) this.publishEvent('config.language.changed', { requestedLanguage, locale, direction: getLocaleDirection(locale), version: Date.now() }) - this.effects.refreshLanguage() - } - - initializeTheme(): void { - nativeTheme.themeSource = this.getTheme() - nativeTheme.on('updated', () => { - if (nativeTheme.themeSource !== 'system') return - this.publishEvent('config.systemTheme.changed', { - isDark: nativeTheme.shouldUseDarkColors, - version: Date.now() - }) - void this.effects.refreshTheme() + } + + async initialize(): Promise { + await this.host.applyTheme(this.getTheme()) + await this.host.refreshLanguage(this.getLanguage()) + } + + async observeSystemTheme(): Promise { + const observed = await this.host.readTheme() + if (this.getTheme() !== 'system' || observed.theme !== 'system') return + await this.host.refreshTheme(observed.isDark) + this.publishEvent('config.systemTheme.changed', { + isDark: observed.isDark, + version: Date.now() }) } - setTheme(theme: 'dark' | 'light' | 'system'): boolean { - nativeTheme.themeSource = theme + async setTheme(theme: 'dark' | 'light' | 'system'): Promise { + const isDark = await this.host.applyTheme(theme) this.settings.set('appTheme', theme) + await this.committed() + await this.host.refreshTheme(isDark) this.publishEvent('config.theme.changed', { theme, - isDark: nativeTheme.shouldUseDarkColors, + isDark, version: Date.now() }) - void this.effects.refreshTheme() - return nativeTheme.shouldUseDarkColors + return isDark } getTheme(): 'dark' | 'light' | 'system' { return this.settings.get<'dark' | 'light' | 'system'>('appTheme') || 'system' } - getCurrentThemeIsDark(): boolean { - return nativeTheme.shouldUseDarkColors + async getCurrentThemeIsDark(): Promise { + return (await this.host.readTheme()).isDark } getNotificationsEnabled(): boolean { @@ -84,8 +98,8 @@ export class DesktopSettings { return this.settings.get('fontSizeLevel') ?? 1 } - setFontSizeLevel(level: number): void { - this.setSetting('fontSizeLevel', level) + async setFontSizeLevel(level: number): Promise { + await this.setSetting('fontSizeLevel', level) } getAutoScrollEnabled(): boolean { @@ -96,12 +110,13 @@ export class DesktopSettings { return this.settings.get('copyWithCotEnabled') ?? true } - setCopyWithCotEnabled(enabled: boolean): void { - this.setSetting('copyWithCotEnabled', Boolean(enabled)) + async setCopyWithCotEnabled(enabled: boolean): Promise { + await this.setSetting('copyWithCotEnabled', Boolean(enabled)) } - setAutoScrollEnabled(enabled: boolean): void { + async setAutoScrollEnabled(enabled: boolean): Promise { this.settings.set('autoScrollEnabled', Boolean(enabled)) + await this.committed() this.publishEvent('settings.changed', { changedKeys: ['autoScrollEnabled'], version: Date.now(), @@ -109,9 +124,10 @@ export class DesktopSettings { }) } - setNotificationsEnabled(enabled: boolean): void { + async setNotificationsEnabled(enabled: boolean): Promise { const value = Boolean(enabled) this.settings.set('notificationsEnabled', value) + await this.committed() this.publishEvent('settings.changed', { changedKeys: ['notificationsEnabled'], version: Date.now(), @@ -119,16 +135,16 @@ export class DesktopSettings { }) } - getLaunchAtLoginEnabled(): boolean { - return app.getLoginItemSettings().openAtLogin + async getLaunchAtLoginEnabled(): Promise { + return await this.host.readLaunchAtLogin() } - setLaunchAtLoginEnabled(enabled: boolean): void { - app.setLoginItemSettings({ openAtLogin: Boolean(enabled) }) + async setLaunchAtLoginEnabled(enabled: boolean): Promise { + const observed = await this.host.setLaunchAtLogin(Boolean(enabled)) this.publishEvent('settings.changed', { changedKeys: ['launchAtLoginEnabled'], version: Date.now(), - values: { launchAtLoginEnabled: this.getLaunchAtLoginEnabled() } + values: { launchAtLoginEnabled: observed } }) } @@ -140,9 +156,11 @@ export class DesktopSettings { return this.settings.get('contentProtectionEnabled') ?? false } - setContentProtectionEnabled(enabled: boolean): void { + async setContentProtectionEnabled(enabled: boolean): Promise { const value = Boolean(enabled) this.settings.set('contentProtectionEnabled', value) + await this.committed() + await this.host.applyContentProtection(value) this.publishEvent('settings.changed', { changedKeys: ['contentProtectionEnabled'], version: Date.now(), @@ -154,9 +172,11 @@ export class DesktopSettings { return this.settings.get('floatingButtonEnabled') ?? false } - setFloatingButtonEnabled(enabled: boolean): void { + async setFloatingButtonEnabled(enabled: boolean): Promise { const value = Boolean(enabled) this.settings.set('floatingButtonEnabled', value) + await this.committed() + await this.host.applyFloatingButtonEnabled(value) this.publishEvent('config.floatingButton.changed', { enabled: value, version: Date.now() @@ -176,8 +196,9 @@ export class DesktopSettings { return value } - setFloatingButtonBounds(bounds: FloatingButtonBounds): void { + async setFloatingButtonBounds(bounds: FloatingButtonBounds): Promise { this.settings.set('floatingButtonBounds', bounds) + await this.committed() } getShortcutKeys(): ShortcutKeySetting { @@ -187,13 +208,15 @@ export class DesktopSettings { } } - setShortcutKeys(shortcuts: ShortcutKeySetting): void { + async setShortcutKeys(shortcuts: ShortcutKeySetting): Promise { this.settings.set('shortcutKey', shortcuts) + await this.committed() this.publishShortcutKeysChanged() } - resetShortcutKeys(): void { + async resetShortcutKeys(): Promise { this.settings.set('shortcutKey', { ...defaultShortcutKey }) + await this.committed() this.publishShortcutKeysChanged() } @@ -204,8 +227,12 @@ export class DesktopSettings { }) } - private setSetting(key: 'fontSizeLevel' | 'copyWithCotEnabled', value: number | boolean): void { + private async setSetting( + key: 'fontSizeLevel' | 'copyWithCotEnabled', + value: number | boolean + ): Promise { this.settings.set(key, value) + await this.committed() this.publishEvent('settings.changed', { changedKeys: [key], version: Date.now(), diff --git a/src/main/desktop/shortcut.ts b/src/main/desktop/shortcut.ts index 2a89d83393..ecd9041add 100644 --- a/src/main/desktop/shortcut.ts +++ b/src/main/desktop/shortcut.ts @@ -8,13 +8,13 @@ import type { IWindowPresenter, ShortcutKeySetting } from '@shared/types/desktop' -import type { DesktopSettings } from './settings' +import type { DesktopConfigurationProjection } from './configurationProjection' import { getContextMenuLabels, type TranslationMap } from '@shared/i18n' import { is } from '@electron-toolkit/utils' import type { DeepchatEventPublisher } from '@shared/contracts/events' export class ShortcutPresenter implements IShortcutPresenter { - private settings: Pick + private settings: Pick private windowPresenter: IWindowPresenter private shortcutKeys: ShortcutKeySetting = { ...defaultShortcutKey @@ -25,7 +25,7 @@ export class ShortcutPresenter implements IShortcutPresenter { * @param shortKey 可选的自定义快捷键设置 */ constructor( - settings: Pick, + settings: Pick, windowPresenter: IWindowPresenter, private readonly publishEvent: DeepchatEventPublisher ) { diff --git a/src/main/desktop/tab.ts b/src/main/desktop/tab.ts index da03822101..39e9af31b7 100644 --- a/src/main/desktop/tab.ts +++ b/src/main/desktop/tab.ts @@ -25,10 +25,6 @@ import { getYoBrowserSession } from './browser/yoBrowserSession' import { DEEPCHAT_EVENT_CHANNEL } from '@shared/contracts/channels' import { createDeepchatEventEnvelope } from '@shared/contracts/events' -export interface TabDesktopSessionBindingPort { - unbind(webContentsId: number): void -} - export class TabPresenter implements ITabPresenter { // 全局标签页实例存储 private tabs: Map = new Map() @@ -50,6 +46,7 @@ export class TabPresenter implements ITabPresenter { private windowTypes: Map = new Map() private chromeHeights: Map = new Map() + private contextMenuLocale: string | undefined private static readonly DEFAULT_CHROME_HEIGHT = 60 private static readonly DEFAULT_WINDOW_TYPE: 'chat' | 'browser' = 'chat' @@ -57,8 +54,8 @@ export class TabPresenter implements ITabPresenter { constructor( windowPresenter: IWindowPresenter, - private readonly desktopSessionBinding: TabDesktopSessionBindingPort, - private readonly onFirstContentLoaded: () => void + private readonly onFirstContentLoaded: () => void, + private readonly showSaveDialog: import('./dialog').DialogService['showSaveDialog'] ) { this.windowPresenter = windowPresenter // 注入窗口管理器 } @@ -97,13 +94,13 @@ export class TabPresenter implements ITabPresenter { } }) } - views?.forEach((viewId) => this.desktopSessionBinding.unbind(viewId)) this.windowTabs.delete(windowId) this.windowTypes.delete(windowId) this.chromeHeights.delete(windowId) } - async refreshLanguage(): Promise { + async refreshLanguage(locale?: string): Promise { + if (locale) this.contextMenuLocale = locale for (const [tabId] of this.tabWindowMap.entries()) { await this.setupTabContextMenu(tabId) } @@ -278,8 +275,6 @@ export class TabPresenter implements ITabPresenter { this.detachViewFromWindow(window, view) } - this.desktopSessionBinding.unbind(view.webContents.id) - // 移除事件监听 this.removeWebContentsListeners(view.webContents) @@ -730,11 +725,12 @@ export class TabPresenter implements ITabPresenter { this.tabContextMenuDisposers.delete(tabId) } - const lang = app.getLocale() + const lang = this.contextMenuLocale ?? app.getLocale() const labels = await getContextMenuLabels(lang) const disposer = contextMenu({ webContents: view.webContents, + showSaveDialog: this.showSaveDialog, publishEvent: (name, payload) => { void this.windowPresenter .sendToWebContents( diff --git a/src/main/desktop/tray.ts b/src/main/desktop/tray.ts index 6a4c018a3f..e036bd663c 100644 --- a/src/main/desktop/tray.ts +++ b/src/main/desktop/tray.ts @@ -2,14 +2,14 @@ import { Tray, Menu, app, nativeImage, NativeImage } from 'electron' import * as path from 'path' import { getContextMenuLabels } from '@shared/i18n' import type { IWindowPresenter } from '@shared/types/desktop' -import type { DesktopSettings } from './settings' +import type { DesktopConfigurationProjection } from './configurationProjection' export class TrayPresenter { private tray: Tray | null = null private iconPath: string constructor( - private readonly settings: Pick, + private readonly settings: Pick, private readonly windowPresenter: IWindowPresenter ) { this.iconPath = path.join(app.getAppPath(), 'resources') diff --git a/src/main/desktop/window/index.ts b/src/main/desktop/window/index.ts index 8dd0ea4fef..a1989dc1d8 100644 --- a/src/main/desktop/window/index.ts +++ b/src/main/desktop/window/index.ts @@ -14,7 +14,7 @@ import icon from '../../../../resources/icon.png?asset' // App icon (macOS/Linux import iconWin from '../../../../resources/icon.ico?asset' // App icon (Windows) import { is } from '@electron-toolkit/utils' // Electron utilities import type { IWindowPresenter } from '@shared/types/desktop' -import type { DesktopSettings } from '../settings' +import type { DesktopConfigurationProjection } from '../configurationProjection' import { resolveSettingsNavigationPath, type SettingsNavigationPayload @@ -25,7 +25,6 @@ import windowStateManager from 'electron-window-state' // Window state manager import { TabPresenter } from '../tab' import { FloatingChatWindow } from './FloatingChatWindow' // Floating chat window import type { ProviderInstallPreview } from '@shared/providerDeeplink' -import { StartupWorkloadCoordinator } from '../../app/startupWorkloadCoordinator' import { openExternalUrl } from '@/lib/externalUrl' import { activateAppOnMac } from '@/lib/activateApp' import { DEEPCHAT_EVENT_CHANNEL } from '@shared/contracts/channels' @@ -43,7 +42,10 @@ type PendingSettingsMessage = { export class WindowPresenter implements IWindowPresenter { // Map managing all BrowserWindow instances, key is window ID windows: Map - private settings: Pick + private settings: Pick< + DesktopConfigurationProjection, + 'getCloseToQuit' | 'getContentProtectionEnabled' + > // Exit flag indicating if app is in the process of quitting (set by 'before-quit' hook) private isQuitting: boolean = false // Current focused window ID (internal record) @@ -57,8 +59,6 @@ export class WindowPresenter implements IWindowPresenter { private settingsWindowReady = false private pendingSettingsMessages: PendingSettingsMessage[] = [] private pendingSettingsProviderInstalls: ProviderInstallPreview[] = [] - private readonly startupWorkloadCoordinator?: StartupWorkloadCoordinator - private readonly restartApp: () => void private tabPresenter!: TabPresenter // Serializes async tab lookups/deliveries per window so consecutive broadcasts // cannot reorder against each other when a delayed `getTab` resolves out of turn. @@ -82,23 +82,24 @@ export class WindowPresenter implements IWindowPresenter { } constructor( - settings: Pick, - restartApp: () => void, + settings: Pick< + DesktopConfigurationProjection, + 'getCloseToQuit' | 'getContentProtectionEnabled' + >, private readonly onWindowCreated: (isMainWindow: boolean) => void, - startupWorkloadCoordinator?: StartupWorkloadCoordinator + private readonly settingsWindowLifecycle?: ( + action: 'opened' | 'closed' | 'ready' + ) => Promise ) { this.windows = new Map() this.settings = settings - this.restartApp = restartApp - this.startupWorkloadCoordinator = startupWorkloadCoordinator } applyContentProtection(enabled: boolean): void { - logger.info(`Content protection setting changed to ${enabled}, restarting application.`) + logger.info(`Content protection setting changed to ${enabled}.`) this.windows.forEach((window) => { if (!window.isDestroyed()) this.updateContentProtection(window, enabled) }) - setTimeout(() => this.restartApp(), 1000) } bindTabPresenter(tabPresenter: TabPresenter): void { @@ -1370,7 +1371,7 @@ export class WindowPresenter implements IWindowPresenter { this.settingsWindow = settingsWindow this.resetSettingsWindowState() - this.startupWorkloadCoordinator?.createRun('settings') + await this.settingsWindowLifecycle?.('opened') const windowId = settingsWindow.id if (navigation) { @@ -1414,7 +1415,9 @@ export class WindowPresenter implements IWindowPresenter { logger.info(`Settings window ${windowId} closed.`) // Unmanage window state when window is closed settingsWindowState.unmanage() - this.startupWorkloadCoordinator?.cancelTarget('settings') + void this.settingsWindowLifecycle?.('closed').catch((error) => + logger.warn('[Window] Settings workload retirement failed', error) + ) this.settingsWindow = null this.resetSettingsWindowState(true) }) @@ -1540,7 +1543,9 @@ export class WindowPresenter implements IWindowPresenter { console.info( `[Startup][Settings][Main] window.notifySettingsReady windowId=${this.settingsWindow.id}` ) - this.startupWorkloadCoordinator?.replayTarget('settings') + void this.settingsWindowLifecycle?.('ready').catch((error) => + logger.warn('[Window] Settings workload replay failed', error) + ) this.flushPendingSettingsMessages() } diff --git a/src/main/device/index.ts b/src/main/device/index.ts index c5058f954a..5a5d5ae3a3 100644 --- a/src/main/device/index.ts +++ b/src/main/device/index.ts @@ -11,14 +11,12 @@ import { cacheImage, type CacheImageOptions } from '@/platform/imageCache' const execAsync = promisify(exec) export class DeviceService implements DeviceServicePort { - static getDefaultHeaders(): Record { - const version = app.getVersion() - return { - 'HTTP-Referer': 'https://deepchatai.cn', - 'X-Title': 'DeepChat', - 'User-Agent': `DeepChat/${version}` - } - } + constructor( + private readonly showOpenDialog: ( + options: Electron.OpenDialogOptions + ) => Promise + ) {} + async getAppVersion(): Promise { return app.getVersion() } @@ -312,7 +310,7 @@ export class DeviceService implements DeviceServicePort { * @returns 返回所选目录的路径,如果用户取消则返回null */ async selectDirectory(): Promise<{ canceled: boolean; filePaths: string[] }> { - return dialog.showOpenDialog({ + return this.showOpenDialog({ properties: ['openDirectory', 'createDirectory'] }) } @@ -330,7 +328,7 @@ export class DeviceService implements DeviceServicePort { if (options?.multiple) { properties.push('multiSelections') } - return dialog.showOpenDialog({ + return this.showOpenDialog({ properties, filters: options?.filters }) diff --git a/src/main/exporter/agentSessionExporter.ts b/src/main/exporter/agentSessionExporter.ts index 6fce83f981..4487d576c3 100644 --- a/src/main/exporter/agentSessionExporter.ts +++ b/src/main/exporter/agentSessionExporter.ts @@ -40,7 +40,7 @@ export class AgentSessionExportService { ) {} async submitToNowledgeMem(input: NowledgeExportInput): Promise<{ threadId: string }> { - const connection = this.dependencies.nowledgeMemConnections?.getExportConfig() + const connection = await this.dependencies.nowledgeMemConnections?.getExportConfig() if (!connection || connection.baseUrl !== input.apiBaseUrl) { throw new Error('The Nowledge export destination changed; confirm it again') } diff --git a/src/main/exporter/index.ts b/src/main/exporter/index.ts index f72feb6283..f10cba1633 100644 --- a/src/main/exporter/index.ts +++ b/src/main/exporter/index.ts @@ -88,7 +88,7 @@ export class ConversationExporterService implements IConversationExporter { // Capture the destination before loading/serializing the conversation. let connection: NowledgeMemConfig | undefined try { - connection = this.deps.nowledgeMemConnections?.getExportConfig() + connection = await this.deps.nowledgeMemConnections?.getExportConfig() } catch (error) { return { success: false, @@ -128,7 +128,7 @@ export class ConversationExporterService implements IConversationExporter { const state = await this.deps.nowledgeMemConnections.getState() const connection = state.connection if (!connection || config) throw new Error('Verify connections in the Nowledge Mem plugin') - const resolved = this.deps.nowledgeMemConnections.getExportConfig() + const resolved = await this.deps.nowledgeMemConnections.getExportConfig() await this.deps.nowledgeMemConnections.verify( connection, resolved.apiKey ?? '', diff --git a/src/main/file/index.ts b/src/main/file/index.ts index 011816291a..ba7bdf7163 100644 --- a/src/main/file/index.ts +++ b/src/main/file/index.ts @@ -1,12 +1,11 @@ import logger from '@shared/logger' -import { app, clipboard, dialog, nativeImage, net } from 'electron' import fs from 'fs/promises' import path from 'path' import { BaseFileAdapter } from './adapters/BaseFileAdapter' import { FileAdapterConstructor } from './adapters/FileAdapterConstructor' import type { SettingsStore } from '@/config/settingsStore' -import type { FileOperation, FileServicePort } from '@shared/types/file' +import type { FileServicePort } from '@shared/types/file' import { detectMimeType, getMimeTypeAdapterMap } from './mime' import type { MessageFile } from '@shared/chat' import { estimateTokenCount } from 'tokenx' @@ -17,102 +16,6 @@ import { DirectoryAdapter } from './adapters/DirectoryAdapter' import { UnsupportFileAdapter } from './adapters/UnsupportFileAdapter' import { FileValidationService, FileValidationResult, IFileValidationService } from './validation' -type SaveImageInput = { - source: string - mimeType?: string - suggestedName?: string -} - -type ResolvedImageData = { - data: Buffer - mimeType: string -} - -const IMAGE_SAVE_EXTENSIONS = ['png', 'jpg', 'jpeg', 'webp', 'gif', 'bmp', 'svg', 'avif', 'ico'] -const INVALID_FILE_NAME_CHARS = new Set(['<', '>', ':', '"', '/', '\\', '|', '?', '*']) - -function normalizeImageMimeType(mimeType?: string): string | null { - const normalized = mimeType?.split(';')[0]?.trim().toLowerCase() - if (!normalized?.startsWith('image/')) { - return null - } - return normalized -} - -function getImageExtensionFromMimeType(mimeType?: string): string { - const normalized = normalizeImageMimeType(mimeType) - if (!normalized) { - return 'png' - } - - switch (normalized) { - case 'image/jpeg': - case 'image/jpg': - return 'jpg' - case 'image/svg+xml': - return 'svg' - case 'image/x-icon': - case 'image/vnd.microsoft.icon': - return 'ico' - default: - return normalized.replace('image/', '').replace(/[^a-z0-9]/g, '') || 'png' - } -} - -function inferImageMimeTypeFromPath(filePath: string): string | null { - const extension = path.extname(filePath).toLowerCase() - switch (extension) { - case '.png': - return 'image/png' - case '.jpg': - case '.jpeg': - return 'image/jpeg' - case '.gif': - return 'image/gif' - case '.webp': - return 'image/webp' - case '.bmp': - return 'image/bmp' - case '.svg': - return 'image/svg+xml' - case '.avif': - return 'image/avif' - case '.ico': - return 'image/x-icon' - default: - return null - } -} - -function sanitizeFileName(fileName?: string): string | null { - const sanitized = fileName - ?.split('') - .map((char) => (INVALID_FILE_NAME_CHARS.has(char) || char.charCodeAt(0) < 32 ? '_' : char)) - .join('') - .replace(/\s+/g, ' ') - .replace(/[. ]+$/g, '') - .trim() - - return sanitized || null -} - -function formatImageTimestamp(date: Date): string { - const pad = (value: number) => value.toString().padStart(2, '0') - return `${date.getFullYear()}${pad(date.getMonth() + 1)}${pad(date.getDate())}-${pad( - date.getHours() - )}${pad(date.getMinutes())}${pad(date.getSeconds())}` -} - -function buildDefaultImageName(mimeType: string, suggestedName?: string): string { - const extension = getImageExtensionFromMimeType(mimeType) - const sanitizedName = sanitizeFileName(suggestedName) - if (sanitizedName) { - return path.extname(sanitizedName) ? sanitizedName : `${sanitizedName}.${extension}` - } - - return `deepchat-image-${formatImageTimestamp(new Date())}.${extension}` -} - export class FileService implements FileServicePort { private userDataPath: string private settings: Pick @@ -125,21 +28,15 @@ export class FileService implements FileServicePort { constructor( settings: Pick, + userDataDirectory: string, fileValidationService?: IFileValidationService ) { - this.userDataPath = app.getPath('userData') + if (!path.isAbsolute(userDataDirectory)) + throw new Error('File profile directory must be absolute') + this.userDataPath = userDataDirectory this.tempDir = path.join(this.userDataPath, 'temp') this.settings = settings this.fileValidationService = fileValidationService || new FileValidationService() - // Ensure temp directory exists - try { - const mkdirResult = fs.mkdir(this.tempDir, { recursive: true }) - if (mkdirResult && typeof mkdirResult.catch === 'function') { - mkdirResult.catch(console.error) - } - } catch (error) { - console.error('Failed to create temp directory:', error) - } } async getMimeType(filePath: string): Promise { @@ -151,18 +48,7 @@ export class FileService implements FileServicePort { return fs.readFile(fullPath, 'utf-8') } - async writeFile(operation: FileOperation): Promise { - const fullPath = path.join(this.userDataPath, operation.path) - await fs.mkdir(path.dirname(fullPath), { recursive: true }) - await fs.writeFile(fullPath, operation.content || '', 'utf-8') - } - - async deleteFile(relativePath: string): Promise { - const fullPath = path.join(this.userDataPath, relativePath) - await fs.unlink(fullPath) - } - - async createFileAdapter(filePath: string, typeInfo?: string): Promise { + private async createFileAdapter(filePath: string, typeInfo?: string): Promise { // Use the refined getMimeType method // Prioritize provided typeInfo if available const mimeType = typeInfo ?? (await this.getMimeType(filePath)) @@ -300,24 +186,6 @@ export class FileService implements FileServicePort { return UnsupportFileAdapter } - async writeTemp(file: { name: string; content: string | Buffer | ArrayBuffer }): Promise { - const ext = path.extname(file.name) - const tempName = `${nanoid()}${ext || '.tmp'}` // Add .tmp extension if original name has none - const tempPath = path.join(this.tempDir, tempName) - // Check if content is binary (Buffer or ArrayBuffer) or string - if (typeof file.content === 'string') { - await fs.writeFile(tempPath, file.content, 'utf-8') - } else if (Buffer.isBuffer(file.content)) { - // If it's already a Buffer, write it directly - await fs.writeFile(tempPath, file.content) - } else { - // Otherwise, assume it's ArrayBuffer and convert to Buffer - await fs.writeFile(tempPath, Buffer.from(file.content)) - } - - return tempPath - } - async writeImageBase64(file: { name: string; content: string }): Promise { // Check if it's base64 format image data if (!file.content.startsWith('data:image/')) { @@ -341,166 +209,12 @@ export class FileService implements FileServicePort { const tempName = `${nanoid()}${ext}` const tempPath = path.join(this.tempDir, tempName) - // Write file + await fs.mkdir(this.tempDir, { recursive: true }) await fs.writeFile(tempPath, binaryData) return tempPath } - async saveImage(file: SaveImageInput): Promise<{ canceled: boolean; path?: string }> { - const image = await this.resolveImageData(file) - const defaultPath = buildDefaultImageName(image.mimeType, file.suggestedName) - - const preferredExtension = getImageExtensionFromMimeType(image.mimeType) - const extensions = Array.from(new Set([preferredExtension, ...IMAGE_SAVE_EXTENSIONS])) - const { canceled, filePath } = await dialog.showSaveDialog({ - defaultPath, - filters: [ - { name: 'Images', extensions }, - { name: 'All Files', extensions: ['*'] } - ] - }) - - if (canceled || !filePath) { - return { canceled: true } - } - - await fs.writeFile(filePath, image.data) - return { canceled: false, path: filePath } - } - - async copyImage(file: SaveImageInput): Promise<{ copied: boolean }> { - const image = await this.resolveImageData(file) - const clipboardImage = this.createClipboardImage(image) - clipboard.writeImage(clipboardImage) - return { copied: true } - } - - private async resolveImageData(file: SaveImageInput): Promise { - const source = file.source.trim() - if (source.startsWith('data:image/')) { - return this.resolveDataUrlImage(source) - } - - if (source.startsWith('imgcache://')) { - return this.resolveCachedImage(source) - } - - if (source.startsWith('http://') || source.startsWith('https://')) { - return this.resolveRemoteImage(source, file.mimeType) - } - - return this.resolveRawBase64Image(source, file.mimeType) - } - - private resolveDataUrlImage(source: string): ResolvedImageData { - const match = source.match(/^data:(image\/[a-zA-Z0-9.+-]+);base64,(.*)$/s) - const mimeType = normalizeImageMimeType(match?.[1]) - const base64Data = match?.[2]?.replace(/\s/g, '') - - if (!mimeType || !base64Data) { - throw new Error('Invalid image data URL') - } - - const data = Buffer.from(base64Data, 'base64') - if (data.length === 0) { - throw new Error('Invalid image data URL') - } - - return { data, mimeType } - } - - private async resolveCachedImage(source: string): Promise { - const cacheDir = path.join(app.getPath('userData'), 'images') - const rawCachePath = source.slice('imgcache://'.length) - const cachePath = this.safeDecodePath(rawCachePath) - const fullPath = path.resolve(cacheDir, cachePath) - const relativePath = path.relative(cacheDir, fullPath) - - if (!relativePath || relativePath.startsWith('..') || path.isAbsolute(relativePath)) { - throw new Error('Invalid cached image path') - } - - const mimeType = inferImageMimeTypeFromPath(fullPath) - if (!mimeType) { - throw new Error('Invalid cached image type') - } - - return { - data: await fs.readFile(fullPath), - mimeType - } - } - - private async resolveRemoteImage( - source: string, - fallbackMimeType?: string - ): Promise { - const url = new URL(source) - if (url.protocol !== 'http:' && url.protocol !== 'https:') { - throw new Error('Unsupported image URL') - } - - const response = await net.fetch(url.toString()) - if (!response.ok) { - throw new Error(`Failed to download image: ${response.status}`) - } - - const responseMimeType = normalizeImageMimeType( - response.headers.get('content-type') ?? undefined - ) - const mimeType = - responseMimeType || - normalizeImageMimeType(fallbackMimeType) || - inferImageMimeTypeFromPath(url.pathname) - - if (!mimeType) { - throw new Error('Remote URL is not an image') - } - - return { - data: Buffer.from(await response.arrayBuffer()), - mimeType - } - } - - private resolveRawBase64Image(source: string, fallbackMimeType?: string): ResolvedImageData { - const mimeType = normalizeImageMimeType(fallbackMimeType) - if (!mimeType) { - throw new Error('Raw image data requires an image MIME type') - } - - const data = Buffer.from(source.replace(/\s/g, ''), 'base64') - if (data.length === 0) { - throw new Error('Invalid raw image data') - } - - return { data, mimeType } - } - - private createClipboardImage(image: ResolvedImageData): Electron.NativeImage { - const imageFromBuffer = nativeImage.createFromBuffer(image.data) - if (!imageFromBuffer.isEmpty()) { - return imageFromBuffer - } - - const dataUrl = `data:${image.mimeType};base64,${image.data.toString('base64')}` - const imageFromDataUrl = nativeImage.createFromDataURL(dataUrl) - if (!imageFromDataUrl.isEmpty()) { - return imageFromDataUrl - } - - throw new Error('Image data cannot be copied to clipboard') - } - - private safeDecodePath(filePath: string): string { - try { - return decodeURIComponent(filePath) - } catch { - return filePath - } - } - async isDirectory(absPath: string): Promise { try { const fullPath = path.join(absPath) diff --git a/src/main/file/routes.ts b/src/main/file/routes.ts index 4d2d0c0a0f..9993ab6adf 100644 --- a/src/main/file/routes.ts +++ b/src/main/file/routes.ts @@ -1,4 +1,5 @@ import type { FileServicePort } from '@shared/types/file' +import type { DesktopImageActions } from '@/desktop/imageActions' import { fileCopyImageRoute, fileGetMimeTypeRoute, @@ -11,7 +12,10 @@ import { } from '@shared/contracts/routes' import { createRouteMap, type DeepchatRouteMap } from '@/routes/routeRegistry' -export function createFileRoutes(fileService: FileServicePort): DeepchatRouteMap { +export function createFileRoutes( + fileService: FileServicePort, + images: Pick +): DeepchatRouteMap { return createRouteMap([ [ fileGetMimeTypeRoute.name, @@ -69,14 +73,14 @@ export function createFileRoutes(fileService: FileServicePort): DeepchatRouteMap fileSaveImageRoute.name, async (rawInput) => { const input = fileSaveImageRoute.input.parse(rawInput) - return fileSaveImageRoute.output.parse(await fileService.saveImage(input)) + return fileSaveImageRoute.output.parse(await images.saveImage(input)) } ], [ fileCopyImageRoute.name, async (rawInput) => { const input = fileCopyImageRoute.input.parse(rawInput) - return fileCopyImageRoute.output.parse(await fileService.copyImage(input)) + return fileCopyImageRoute.output.parse(await images.copyImage(input)) } ] ]) diff --git a/src/main/harness/admission.ts b/src/main/harness/admission.ts new file mode 100644 index 0000000000..d6640198bf --- /dev/null +++ b/src/main/harness/admission.ts @@ -0,0 +1,169 @@ +import { randomUUID } from 'node:crypto' +import { z } from 'zod' +import { + AdmissionSnapshotSchema, + DEFAULT_AGENT_INVOCATION_CAPACITY, + DEFAULT_AGENT_INVOCATION_MAX_PENDING, + AgentInvocationAdmissionAbortedError, + AgentInvocationAdmissionClosedError, + AgentInvocationAdmissionQueueFullError, + normalizeAdmissionOwnerId, + normalizeAdmissionOwnerLimit, + snapshotAdmissionCorrelation, + type AgentInvocationAdmissionDiagnosticsOptions, + type AgentInvocationAdmissionOptions, + type AgentInvocationAdmissionPort, + type AgentInvocationPermit, + type InternalAgentInvocationPermit +} from '@shared/harness/admission' +import { StatefulAgentInvocationLease } from '@shared/harness/lease' +import type { HarnessRuntime } from './runtime' + +export class RutisAgentInvocationAdmission implements AgentInvocationAdmissionPort { + private closed: AgentInvocationAdmissionClosedError | undefined + private closePromise: Promise | undefined + private readonly releases = new Set>() + + private constructor( + private readonly runtime: Pick, + private readonly capacity: number, + private readonly maxPending: number, + private readonly diagnostics: AgentInvocationAdmissionDiagnosticsOptions + ) {} + + static create(options: { + runtime: Pick + capacity?: number + maxPending?: number + diagnostics?: AgentInvocationAdmissionDiagnosticsOptions + }): RutisAgentInvocationAdmission { + const capacity = options.capacity ?? DEFAULT_AGENT_INVOCATION_CAPACITY + const maxPending = options.maxPending ?? DEFAULT_AGENT_INVOCATION_MAX_PENDING + if ( + !Number.isSafeInteger(capacity) || + capacity < 1 || + !Number.isSafeInteger(maxPending) || + maxPending < 0 + ) { + throw new Error('Invalid admission limits') + } + const diagnostics = options.diagnostics ?? {} + return new RutisAgentInvocationAdmission(options.runtime, capacity, maxPending, diagnostics) + } + + get identity() { + return this.runtime.identity + } + + async acquire(options: AgentInvocationAdmissionOptions): Promise { + if (this.closed) throw this.closed + const ownerId = normalizeAdmissionOwnerId(options.ownerId) + const maxActiveForOwner = normalizeAdmissionOwnerLimit(options.maxActiveForOwner, this.capacity) + const id = randomUUID() + let released = false + const releaseRemote = (reason: 'permit_released' | 'lease_suspended' | 'lease_released') => { + if (released) return + released = true + const release = this.runtime + .request('release', [id, reason, this.observationsEnabled()]) + .catch(() => undefined) + .finally(() => this.releases.delete(release)) + this.releases.add(release) + } + const request = this.runtime.request('acquire', [ + id, + { + ownerId, + maxActiveForOwner, + aborted: options.signal?.aborted === true, + correlation: snapshotAdmissionCorrelation(options.correlation) + }, + this.observationsEnabled() + ]) + const cancel = () => releaseRemote('permit_released') + options.signal?.addEventListener('abort', cancel, { once: true }) + if (options.signal?.aborted) cancel() + try { + z.literal(id).parse(await request) + } catch (error) { + // Cancellation can discard a successful remote reply; identity is known before acquire. + releaseRemote('permit_released') + throw options.signal?.aborted + ? new AgentInvocationAdmissionAbortedError() + : this.admissionError(error) + } finally { + options.signal?.removeEventListener('abort', cancel) + } + const permit: InternalAgentInvocationPermit = { + release: () => permit.releaseWithReason('permit_released'), + releaseWithReason: (reason) => { + releaseRemote(reason) + } + } + if (options.signal?.aborted || this.closed) { + permit.release() + throw this.closed ?? new AgentInvocationAdmissionAbortedError() + } + return permit + } + + createLease(options: AgentInvocationAdmissionOptions) { + return new StatefulAgentInvocationLease(this, options) + } + + async run(options: AgentInvocationAdmissionOptions, task: () => Promise): Promise { + const permit = await this.acquire(options) + try { + if (options.signal?.aborted) throw new AgentInvocationAdmissionAbortedError() + return await task() + } finally { + permit.release() + } + } + + close(reason = 'Agent invocation admission is closed.'): Promise { + if (this.closePromise) return this.closePromise + this.closed = new AgentInvocationAdmissionClosedError(reason) + this.closePromise = (async () => { + await Promise.all(this.releases) + await this.runtime.request('close', [reason, this.observationsEnabled()]) + })() + return this.closePromise + } + + async flushObservations(): Promise { + await Promise.all(this.releases) + await this.runtime.request('flushObservations', []) + } + + async snapshot() { + await Promise.all(this.releases) + const value = z + .object({ admission: AdmissionSnapshotSchema }) + .strict() + .parse(await this.runtime.request('snapshot', [])) + return value.admission + } + + private observationsEnabled(): boolean { + try { + return this.diagnostics.observationsEnabled?.() ?? this.diagnostics.observe !== undefined + } catch { + return false + } + } + + private admissionError(value: unknown): Error { + if (!(value instanceof Error)) return new Error('Admission request failed') + switch (value.name) { + case 'AgentInvocationAdmissionAbortedError': + return new AgentInvocationAdmissionAbortedError() + case 'AgentInvocationAdmissionClosedError': + return new AgentInvocationAdmissionClosedError(value.message) + case 'AgentInvocationAdmissionQueueFullError': + return new AgentInvocationAdmissionQueueFullError(this.maxPending) + default: + return value + } + } +} diff --git a/src/main/harness/browser.ts b/src/main/harness/browser.ts new file mode 100644 index 0000000000..f582049341 --- /dev/null +++ b/src/main/harness/browser.ts @@ -0,0 +1,113 @@ +import { z } from 'zod' +import type { AgentBrowserToolPort } from '@/tool/runtimePorts' +import { isYoBrowserUnavailableError } from '@/tool/browser/errors' +import { + CoreBrowserCommandSchema, + CoreBrowserAuthorizationSchema, + coreBrowserError, + type CoreBrowserEvent, + type CoreBrowserOutcome +} from '@shared/harness/browser' +import { CORE_NATIVE_CALLS } from '@shared/harness/core' + +export class CoreBrowserNative { + private readonly calls = new Map< + string, + { controller: AbortController; operation: Promise } + >() + private closing: Promise | undefined + private failure: Error | undefined + + constructor( + private readonly browser: AgentBrowserToolPort, + private readonly deliver: (event: CoreBrowserEvent) => Promise, + // Includes physical Browser closure and any effects that outlived a canceled client wait. + private readonly retire: () => Promise, + private readonly failed: (error: Error) => void + ) {} + + async invoke(input: unknown): Promise { + const [command] = z.tuple([CoreBrowserCommandSchema]).parse(input) + switch (command.operation) { + case 'open': { + if (this.failure || this.closing) + throw this.failure ?? new Error('Native browser is stopping') + if (this.calls.has(command.id)) throw new Error('Native browser call already exists') + if (this.calls.size >= CORE_NATIVE_CALLS - 2) + throw new Error('Native browser call limit exceeded') + const call = { controller: new AbortController(), operation: Promise.resolve() } + this.calls.set(command.id, call) + call.operation = Promise.resolve().then(async () => { + let outcome: CoreBrowserOutcome + try { + call.controller.signal.throwIfAborted() + const value = await this.browser.callTool( + command.toolName, + command.args, + command.conversationId, + command.runId, + command.authorize + ? async (args) => { + call.controller.signal.throwIfAborted() + const result = CoreBrowserAuthorizationSchema.parse( + await this.deliver({ type: 'browser.authorize', id: command.id, args }) + ) + call.controller.signal.throwIfAborted() + if (!result.ok) { + const error = new Error(result.error.message) + error.name = result.error.name + throw error + } + } + : undefined, + call.controller.signal + ) + call.controller.signal.throwIfAborted() + outcome = { ok: true, value } + } catch (error) { + outcome = { + ok: false, + error: coreBrowserError(error), + ...(isYoBrowserUnavailableError(error) ? { unavailable: error.payload } : {}) + } + } + try { + await this.deliver({ type: 'browser.completed', id: command.id, outcome }) + this.calls.delete(command.id) + } catch (error) { + this.fail(error) + } + }) + return { accepted: true } + } + case 'cancel': { + const call = this.calls.get(command.id) + if (!call) return undefined + call.controller.abort(new Error('Native browser call canceled')) + await call.operation + return undefined + } + case 'close': + return await this.close() + } + } + + close(): Promise { + if (this.closing) return this.closing + const calls = [...this.calls.values()] + for (const call of calls) call.controller.abort(new Error('Native browser is stopping')) + this.closing = (async () => { + // Native closure also releases a target that ignored its caller's cancellation. + await this.retire() + await Promise.allSettled(calls.map((call) => call.operation)) + this.calls.clear() + })() + return this.closing + } + + private fail(input: unknown): void { + if (this.failure) return + this.failure = input instanceof Error ? input : new Error('Native browser delivery failed') + this.failed(this.failure) + } +} diff --git a/src/main/harness/configuration.ts b/src/main/harness/configuration.ts new file mode 100644 index 0000000000..76a9a60a28 --- /dev/null +++ b/src/main/harness/configuration.ts @@ -0,0 +1,49 @@ +import { z } from 'zod' +import type { DesktopSettingsHost } from '@/desktop/settings' +import type { LoggingHost } from '@/app/logging' +import type { NativeConfigurationSnapshot } from '@shared/types/nativeConfiguration' +import type { DeepchatEventPublisher } from '@shared/contracts/events' +import { CoreConfigurationNativeSchema } from '@shared/harness/configuration' +import type { CoreNativeMethod } from '@shared/harness/core' + +export function createCoreConfigurationNative(options: { + desktop: DesktopSettingsHost + logging: LoggingHost + enumerateFonts(): Promise + publishNativeSnapshot(snapshot: NativeConfigurationSnapshot): void | Promise + publishEvent: DeepchatEventPublisher +}) { + return async (method: CoreNativeMethod, args: unknown): Promise => { + if (method !== 'configuration') throw new Error('Unsupported Native configuration capability') + const [command] = z.tuple([CoreConfigurationNativeSchema]).parse(args) + switch (command.operation) { + case 'readTheme': + return await options.desktop.readTheme() + case 'applyTheme': + return await options.desktop.applyTheme(command.theme) + case 'refreshLanguage': + return await options.desktop.refreshLanguage(command.locale) + case 'refreshTheme': + return await options.desktop.refreshTheme(command.isDark) + case 'readLaunchAtLogin': + return await options.desktop.readLaunchAtLogin() + case 'setLaunchAtLogin': + return await options.desktop.setLaunchAtLogin(command.enabled) + case 'applyContentProtection': + return await options.desktop.applyContentProtection(command.enabled) + case 'applyFloatingButtonEnabled': + return await options.desktop.applyFloatingButtonEnabled(command.enabled) + case 'applyLogging': + return await options.logging.applyLogging(command.enabled) + case 'openLoggingFolder': + return await options.logging.openLoggingFolder() + case 'enumerateFonts': + return await options.enumerateFonts() + case 'snapshot': + return await options.publishNativeSnapshot(command.snapshot) + case 'events': + for (const event of command.events) options.publishEvent(event.name, event.payload) + return + } + } +} diff --git a/src/main/harness/core.ts b/src/main/harness/core.ts new file mode 100644 index 0000000000..b3de5e2795 --- /dev/null +++ b/src/main/harness/core.ts @@ -0,0 +1,692 @@ +import { + CoreConfigurationCommandSchema, + type CoreConfigurationCommand +} from '@shared/harness/configuration' +import { rm } from 'node:fs/promises' +import { deserialize, serialize } from 'node:v8' +import { z } from 'zod' +import { CoreHelperEventsSchema, type CoreHelperEvent } from '@shared/harness/helpers' +import { CoreBrowserEventSchema, type CoreBrowserEvent } from '@shared/harness/browser' +import { CoreDesktopEventSchema, type CoreDesktopEvent } from '@shared/harness/desktop' +import type { HarnessRuntime } from './runtime' +import type { RouteContext } from '@/routes/routeRegistry' +import type { McpAppSandboxPort } from '@/mcp/apps/sandboxRegistry' +import type { CoreProfileHost } from '@/app/profileStartup' +import type { DatabaseSecurityCryptoPort } from '@/app/databaseSecurity' +import { DATABASE_STARTUP_FAILURE_KINDS } from '@shared/contracts/databaseSecurity' +import { CorePayloadStore } from '../../harness/core/payload' +import { + CORE_ORDINARY_CALLS, + CORE_ROUTE_QUEUED, + CORE_CONTROL_CALLS, + CORE_NATIVE_CALLS, + CORE_NATIVE_QUEUED, + CORE_VALUE_BYTES, + isCoreNativeDrain, + CoreStatusSchema, + CoreKernelBootstrapSchema, + CoreRouteInvocationSchema, + CoreMcpAppProtocolCommandSchema, + CoreMcpAppProtocolPolicySchema, + CoreHostQuerySchema, + CoreHostEventSchema, + CoreShutdownConfirmationSchema, + CoreProfileObservationSchema, + CoreFailureComponentSchema, + isCoreControlRoute, + type CoreKernelBootstrap, + type CoreStatus, + type CoreMethod, + type CoreFrame, + type CoreNativeMethod, + type CorePayload, + type CoreRequest +} from '@shared/harness/core' + +type NativeHandler = (method: CoreNativeMethod, args: unknown) => Promise + +export function createCoreProfileNative( + crypto: DatabaseSecurityCryptoPort, + host: CoreProfileHost, + observe?: (observation: z.infer) => void +): NativeHandler { + return async (method, input) => { + switch (method) { + case 'crypto.available': + z.tuple([]).parse(input) + return await crypto.isAvailable() + case 'crypto.backend': + z.tuple([]).parse(input) + return await crypto.getBackend() + case 'crypto.encrypt': + return await crypto.encrypt(...z.tuple([z.string()]).parse(input)) + case 'crypto.decrypt': + return await crypto.decrypt(...z.tuple([z.string()]).parse(input)) + case 'profile.logging': + return await host.setLoggingEnabled(...z.tuple([z.boolean()]).parse(input)) + case 'profile.progress': + return await host.showDatabaseUnlockProgress( + ...z + .tuple([ + z.object({ active: z.boolean(), safeStorageAvailable: z.boolean() }).strict(), + z.object({ skipDelay: z.boolean().optional() }).strict().optional() + ]) + .parse(input) + ) + case 'profile.unlock': + return await host.requestDatabaseUnlock( + ...z + .tuple([ + z + .object({ + reason: z.enum([ + 'manual-required', + 'safe-storage-unavailable', + 'system-key-missing', + 'invalid' + ]), + safeStorageAvailable: z.boolean() + }) + .strict() + ]) + .parse(input) + ) + case 'profile.recovery': + return await host.requestDatabaseRecovery( + ...z + .tuple([ + z + .object({ + kind: z.enum(DATABASE_STARTUP_FAILURE_KINDS), + preservedPath: z.string(), + invalidPassword: z.boolean().optional(), + quarantineFailed: z.boolean().optional() + }) + .strict() + ]) + .parse(input) + ) + case 'profile.quit': + z.tuple([]).parse(input) + return await host.quit() + case 'profile.observation': { + const [observation] = z.tuple([CoreProfileObservationSchema]).parse(input) + observe?.(observation) + return undefined + } + default: + throw new Error('Unsupported Native profile capability') + } + } +} + +export class CoreRuntimeConnection { + private readonly ready = Promise.withResolvers() + private readonly pending = new Map< + number, + { method: CoreMethod; resolve(value: unknown): void; reject(error: Error): void } + >() + private readonly nativeCalls = new Set>() + private readonly nativeIds = new Set() + private nextNativeId = 1 + private identity: CoreStatus | undefined + private send!: (request: CoreRequest) => void + private nextId = 0 + private failure: Error | undefined + private ordinary = 0 + private routeControl = 0 + private readonly routeQueues: Array< + Array<{ + method: 'invokeRoute' | 'invokeControlRoute' | 'mcpAppProtocol' | 'hostQuery' + snapshot: Uint8Array + assertCallerActive(): void + resolve(value: unknown): void + reject(error: Error): void + }> + > = [[], []] + private routeQueued = 0 + private routeQueuedBytes = 0 + private control = 0 + private statusPending = false + private themeObservationPending = false + private nativeEventsPending = false + private nativeEventTail: Promise = Promise.resolve() + private nativeEventQueued = 0 + private nativeEventBytes = 0 + private themeObservation: Promise | undefined + private themeObservationDirty = false + private stopped = false + private stopPromise: Promise | undefined + private activating = false + + private constructor( + private readonly runtime: Pick, + readonly payloads: CorePayloadStore, + private readonly native: NativeHandler, + private readonly onFailure?: (error: Error) => void + ) {} + + static async connect(options: { + runtime: Pick + temporaryDirectory: string + native: NativeHandler + onFailure?(error: Error): void + }): Promise { + const payloads = await CorePayloadStore.create(options.temporaryDirectory) + const connection = new CoreRuntimeConnection( + options.runtime, + payloads, + options.native, + options.onFailure + ) + try { + connection.send = options.runtime.connectCore(payloads.config, { + message: (frame) => connection.receive(frame), + failed: (error) => connection.fail(error) + }) + const timeout = setTimeout(() => connection.fail(new Error('Core startup timed out')), 15_000) + timeout.unref() + try { + await connection.ready.promise + } finally { + clearTimeout(timeout) + } + return connection + } catch (error) { + if (error instanceof Error && error.name === 'CoreGenerationAlreadyAdoptedError') { + await payloads.close() + await rm(payloads.config.directory, { recursive: true, force: true }) + throw error + } + await options.runtime.stop() + await connection.closeAfterHostExit() + throw error + } + } + + async openProfile(options: { + userDataDirectory: string + homeDirectory: string + currentAppVersion: string + systemLocale: string + appPath: string + isPackaged: boolean + }): Promise { + return CoreStatusSchema.parse(await this.request('openProfile', options)) + } + + async configure(command: CoreConfigurationCommand): Promise { + return await this.request('configure', CoreConfigurationCommandSchema.parse(command)) + } + + async assembleKernel(input: CoreKernelBootstrap): Promise { + return CoreStatusSchema.parse( + await this.request('assembleKernel', CoreKernelBootstrapSchema.parse(input)) + ) + } + + async startKernel(): Promise { + if (this.activating) throw new Error('Core Kernel activation is pending') + this.activating = true + try { + const status = CoreStatusSchema.parse(await this.request('startKernel', undefined)) + if (status.phase !== 'running' || !status.businessReady) + throw new Error('Core Kernel did not acknowledge business readiness') + this.activating = false + this.pumpRoutes() + return status + } catch (error) { + this.activating = false + this.rejectQueuedRoutes(error instanceof Error ? error : new Error('Core activation failed')) + throw error + } + } + + readonly mcpAppSandbox: McpAppSandboxPort = { + getProtocolPolicy: async (instanceId) => + CoreMcpAppProtocolPolicySchema.parse( + await this.enqueueBusinessCall( + 'mcpAppProtocol', + CoreMcpAppProtocolCommandSchema.parse({ operation: 'policy', instanceId }) + ) + ), + requestBrowserPermission: async (instanceId, context, kinds) => + z.boolean().parse( + await this.enqueueBusinessCall( + 'mcpAppProtocol', + CoreMcpAppProtocolCommandSchema.parse({ + operation: 'permission', + instanceId, + context, + kinds + }) + ) + ) + } + + readonly host = { + observeSettingsWindow: async (action: 'opened' | 'closed' | 'ready'): Promise => { + await this.deliverNative(CoreHostEventSchema.parse({ type: 'host.settingsWindow', action })) + }, + systemResume: async (): Promise => { + await this.deliverNative(CoreHostEventSchema.parse({ type: 'host.systemResume' })) + }, + resolveWorkspacePreviewRequest: async (url: string): Promise => + z + .string() + .nullable() + .parse( + await this.enqueueBusinessCall( + 'hostQuery', + CoreHostQuerySchema.parse({ operation: 'workspacePreview', url }) + ) + ), + hasProvider: async (providerId: string): Promise => + z + .boolean() + .parse( + await this.enqueueBusinessCall( + 'hostQuery', + CoreHostQuerySchema.parse({ operation: 'hasProvider', providerId }) + ) + ), + getShutdownConfirmation: async () => { + return CoreShutdownConfirmationSchema.parse( + await this.deliverNative(CoreHostEventSchema.parse({ type: 'host.shutdownConfirmation' })) + ) + }, + clearPermissionCaches: async (): Promise => { + await this.deliverNative(CoreHostEventSchema.parse({ type: 'host.clearPermissions' })) + } + } + + async invokeRoute( + routeName: string, + input: unknown, + context: RouteContext, + assertCallerActive: () => void + ): Promise { + assertCallerActive() + return await this.enqueueBusinessCall( + isCoreControlRoute(routeName) ? 'invokeControlRoute' : 'invokeRoute', + CoreRouteInvocationSchema.parse({ routeName, input, context }), + assertCallerActive + ) + } + + private async enqueueBusinessCall( + method: 'invokeRoute' | 'invokeControlRoute' | 'mcpAppProtocol' | 'hostQuery', + input: unknown, + assertCallerActive: () => void = () => {} + ): Promise { + assertCallerActive() + if (this.failure || this.stopped || this.stopPromise) + throw this.failure ?? new Error('Core is stopping') + const snapshot = serialize(input) + if (snapshot.length > CORE_VALUE_BYTES) throw new Error('Core route queue exceeded') + const control = method === 'invokeControlRoute' + // Only unsent work may yield capacity to cancellation or a human answer. + while ( + control && + this.routeQueues[0].length && + (this.routeQueued >= CORE_ROUTE_QUEUED || + this.routeQueuedBytes + snapshot.length > CORE_VALUE_BYTES) + ) { + const displaced = this.routeQueues[0].pop()! + this.routeQueued-- + this.routeQueuedBytes -= displaced.snapshot.length + displaced.reject(new Error('Core route queue yielded to a control request')) + } + if ( + this.routeQueued >= CORE_ROUTE_QUEUED || + this.routeQueuedBytes + snapshot.length > CORE_VALUE_BYTES + ) + throw new Error('Core route queue exceeded') + return await new Promise((resolve, reject) => { + this.routeQueues[control ? 1 : 0].push({ + method, + snapshot, + assertCallerActive, + resolve, + reject + }) + this.routeQueued++ + this.routeQueuedBytes += snapshot.length + this.pumpRoutes() + }) + } + + private pumpRoutes(): void { + if (this.failure || this.stopped || this.stopPromise || this.activating) return + for (const lane of [1, 0]) { + const queue = this.routeQueues[lane] + while (queue.length && (lane ? this.routeControl < 1 : this.ordinary < CORE_ORDINARY_CALLS)) { + const request = queue.shift()! + this.routeQueued-- + this.routeQueuedBytes -= request.snapshot.length + void this.request( + request.method, + deserialize(request.snapshot), + request.assertCallerActive + ).then(request.resolve, request.reject) + } + } + } + + private rejectQueuedRoutes(error: Error): void { + for (const queue of this.routeQueues) + for (const request of queue.splice(0)) request.reject(error) + this.routeQueued = 0 + this.routeQueuedBytes = 0 + } + + async deliverDesktopEvent(event: CoreDesktopEvent): Promise { + await this.deliverNative(CoreDesktopEventSchema.parse(event)) + } + + async deliverNativeEvents(events: CoreHelperEvent[]): Promise { + await this.deliverNative(CoreHelperEventsSchema.parse(events)) + } + + async deliverBrowserEvent(event: CoreBrowserEvent): Promise { + return await this.deliverNative(CoreBrowserEventSchema.parse(event)) + } + + private async deliverNative(event: unknown): Promise { + try { + const encoded = serialize(event) + if ( + this.nativeEventQueued >= CORE_NATIVE_QUEUED || + this.nativeEventBytes + encoded.length > CORE_VALUE_BYTES + ) + throw new Error('Core Native event queue exceeded') + const snapshot: unknown = deserialize(encoded) + this.nativeEventQueued++ + this.nativeEventBytes += encoded.length + const operation = this.nativeEventTail.then(async () => { + try { + return await this.request('nativeEvents', snapshot) + } finally { + this.nativeEventQueued-- + this.nativeEventBytes -= encoded.length + } + }) + this.nativeEventTail = operation.then( + () => undefined, + () => undefined + ) + return await operation + } catch (error) { + if (!CoreHostEventSchema.safeParse(event).success) + this.invalidateNative( + error instanceof Error ? error : new Error('Core Native event delivery failed') + ) + throw error + } + } + + invalidateNative(error: Error): void { + if (!this.failure) this.fail(error) + void this.stop().catch(() => {}) + } + + observeSystemTheme(): Promise { + if (this.stopPromise || this.stopped || this.failure) + return Promise.reject(this.failure ?? new Error('Core is stopping')) + this.themeObservationDirty = true + if (this.themeObservation) return this.themeObservation + this.themeObservation = (async () => { + try { + while (this.themeObservationDirty && !this.stopPromise) { + this.themeObservationDirty = false + await this.request('observeSystemTheme', undefined) + } + } finally { + this.themeObservation = undefined + this.themeObservationDirty = false + } + })() + return this.themeObservation + } + + async status(): Promise { + return CoreStatusSchema.parse( + await this.observe(this.request('status', undefined), 5_000, 'CoreUnavailableError') + ) + } + + async beginShutdown(): Promise { + this.rejectQueuedRoutes(new Error('Core is stopping')) + const status = CoreStatusSchema.parse(await this.request('beginShutdown', undefined)) + if (status.phase !== 'stopping' && status.phase !== 'stopped') + throw new Error('Core did not acknowledge its shutdown fence') + } + + stop(): Promise { + if (this.stopPromise) return this.stopPromise + this.rejectQueuedRoutes(new Error('Core is stopping')) + const stopped = (async () => { + const status = CoreStatusSchema.parse(await this.request('stop', undefined)) + if (status.phase !== 'stopped') throw new Error('Core did not acknowledge profile closure') + this.stopped = true + this.rejectPending(new Error('Core stopped before the operation outcome was received')) + await Promise.allSettled(this.nativeCalls) + await this.payloads.close() + await rm(this.payloads.config.directory, { recursive: true, force: true }) + return status + })() + this.stopPromise = this.observe(stopped, 30_000, 'CoreOperationOutcomeUnknownError') + return this.stopPromise + } + + async closeAfterHostExit(): Promise { + await this.runtime.stop() + this.stopped = true + this.rejectQueuedRoutes(new Error('Core generation is stopped')) + this.rejectPending(new Error('Core stopped before the operation outcome was received')) + await Promise.allSettled(this.nativeCalls) + await this.payloads.close() + await rm(this.payloads.config.directory, { recursive: true, force: true }) + } + + private async request( + method: CoreMethod, + args: unknown, + assertCallerActive?: () => void + ): Promise { + const retirement = method === 'stop' || method === 'beginShutdown' || method === 'nativeEvents' + if ((this.failure && !retirement) || this.stopped) + throw this.failure ?? new Error('Core generation is stopped') + const control = method === 'status' || method === 'stop' || method === 'beginShutdown' + const observation = method === 'observeSystemTheme' + const nativeEvent = method === 'nativeEvents' + const routeControl = method === 'invokeControlRoute' + if (!control && !nativeEvent && this.stopPromise) throw new Error('Core is stopping') + if (method === 'status' && this.statusPending) throw new Error('Core status request is pending') + if (observation && this.themeObservationPending) + throw new Error('Core theme observation is pending') + if (nativeEvent && this.nativeEventsPending) + throw new Error('Core Native event delivery is pending') + if ( + !observation && + !nativeEvent && + (control + ? this.control >= CORE_CONTROL_CALLS + : routeControl + ? this.routeControl >= 1 + : this.ordinary >= CORE_ORDINARY_CALLS) + ) + throw new Error('Core request limit exceeded') + if (nativeEvent) this.nativeEventsPending = true + else if (observation) this.themeObservationPending = true + else if (control) this.control++ + else if (routeControl) this.routeControl++ + else this.ordinary++ + if (method === 'status') this.statusPending = true + let ref: CorePayload | undefined + try { + assertCallerActive?.() + ref = await this.payloads.encode(args) + assertCallerActive?.() + if ((this.failure && !retirement) || this.stopped) + throw this.failure ?? new Error('Core generation is stopped') + if (!control && !nativeEvent && this.stopPromise) throw new Error('Core is stopping') + const id = ++this.nextId + return await new Promise((resolve, reject) => { + this.pending.set(id, { method, resolve, reject }) + try { + this.send({ type: 'coreCall', id, method, args: ref! }) + } catch (error) { + this.fail(error instanceof Error ? error : new Error('Core write failed')) + } + }) + } finally { + if (nativeEvent) this.nativeEventsPending = false + else if (observation) this.themeObservationPending = false + else if (control) this.control-- + else if (routeControl) this.routeControl-- + else this.ordinary-- + if (method === 'status') this.statusPending = false + if (ref && !this.failure) + await this.payloads.release(ref).catch((error: unknown) => { + this.fail(error instanceof Error ? error : new Error('Core payload retirement failed')) + throw error + }) + this.pumpRoutes() + } + } + + private receive(frame: CoreFrame): void { + if (this.stopped) return + if (frame.type === 'coreStopped') { + this.fail(new Error(frame.message)) + return + } + if (frame.type === 'coreReady') { + if ( + this.identity || + frame.runtime.generation !== this.payloads.config.generation || + frame.runtime.modules !== process.versions.modules + ) { + this.fail(new Error('Core runtime generation or ABI mismatch')) + return + } + this.identity = frame.runtime + this.ready.resolve(frame.runtime) + return + } + if (frame.type === 'coreNativeCall') { + if ( + this.stopped || + frame.generation !== this.payloads.config.generation || + frame.id < this.nextNativeId || + frame.id - this.nextNativeId >= CORE_NATIVE_CALLS || + this.nativeIds.has(frame.id) || + this.nativeCalls.size >= CORE_NATIVE_CALLS + ) { + this.fail(new Error('Invalid Core Native request')) + return + } + this.nativeIds.add(frame.id) + while (this.nativeIds.delete(this.nextNativeId)) this.nextNativeId++ + const task = this.runNative(frame) + this.nativeCalls.add(task) + void task.then( + () => this.nativeCalls.delete(task), + () => this.nativeCalls.delete(task) + ) + return + } + const request = this.pending.get(frame.id) + if (!request) { + if (this.failure) return + this.fail(new Error('Unknown Core reply')) + return + } + if ('error' in frame && frame.error.name === 'HarnessTransportError') { + this.fail(new Error(frame.error.message)) + return + } + this.pending.delete(frame.id) + if ('error' in frame) { + const error = new Error(frame.error.message) + error.name = frame.error.name + request.reject(error) + } else void this.payloads.decode(frame.value).then(request.resolve, request.reject) + } + + private async runNative(frame: Extract): Promise { + try { + const args = await this.payloads.decode(frame.args) + if (this.stopped) return + const drain = isCoreNativeDrain(frame.method, args) + if ((this.failure || this.stopPromise) && !drain) + throw new Error('Core Native admission is stopping') + let result: unknown + if (frame.method === 'core.failure') { + const [component] = z.tuple([CoreFailureComponentSchema]).parse(args) + const failure = new Error(`Core ${component} owner failed`) + failure.name = 'CoreOwnerFailureError' + this.invalidateNative(failure) + } else result = await this.native(frame.method, args) + if (this.failure && !drain) { + const error = new Error('Core Native outcome belongs to a retired generation') + error.name = 'CoreNativeOperationOutcomeUnknownError' + throw error + } + const value = await this.payloads.encode(result) + this.send({ type: 'coreNativeReply', id: frame.id, generation: frame.generation, value }) + } catch (error) { + if (!this.stopped) { + try { + this.send({ + type: 'coreNativeReply', + id: frame.id, + generation: frame.generation, + error: { + name: error instanceof Error ? error.name.slice(0, 256) : 'CoreNativeError', + message: + error instanceof Error ? error.message.slice(0, 8192) : 'Core Native effect failed' + } + }) + } catch { + this.fail(new Error('Core Native reply was lost')) + } + } + } + } + + private fail(error: Error): void { + if (this.stopped) return + if (!this.failure) { + this.failure = error + this.ready.reject(error) + this.onFailure?.(error) + } + this.rejectPending(this.failure) + this.rejectQueuedRoutes(this.failure) + } + + private rejectPending(error: Error): void { + for (const request of this.pending.values()) { + const failure = new Error(error.message) + failure.name = + request.method === 'status' ? 'CoreUnavailableError' : 'CoreOperationOutcomeUnknownError' + request.reject(failure) + } + this.pending.clear() + } + + private observe(operation: Promise, milliseconds: number, name: string): Promise { + let timer: NodeJS.Timeout | undefined + const deadline = new Promise((_, reject) => { + timer = setTimeout(() => { + const error = new Error('Core reply deadline elapsed; owner retirement is not confirmed') + error.name = name + reject(error) + }, milliseconds) + timer.unref() + }) + return Promise.race([operation, deadline]).finally(() => clearTimeout(timer)) + } +} diff --git a/src/main/harness/desktop.ts b/src/main/harness/desktop.ts new file mode 100644 index 0000000000..51e71e24de --- /dev/null +++ b/src/main/harness/desktop.ts @@ -0,0 +1,179 @@ +import { z } from 'zod' +import type { CoreKernelNative } from '../../harness/core/kernel' +import { + CoreDesktopArguments, + CoreDesktopCommandSchema, + type CoreDesktopEvent, + type CoreDesktopMethod +} from '@shared/harness/desktop' +import { CORE_NATIVE_CALLS } from '@shared/harness/core' + +type Watch = { active: boolean; dispose(): void; delivery?: Promise } + +export class CoreDesktopNative { + private readonly calls = new Map< + string, + { controller: AbortController; operation: Promise } + >() + private readonly watches = new Map() + private readonly canceledCalls = new Set() + private closing: Promise | undefined + + constructor( + private readonly native: CoreKernelNative, + private readonly deliver: (event: CoreDesktopEvent) => Promise, + private readonly retire: () => Promise, + private readonly failed: (error: Error) => void + ) {} + + async invoke(input: unknown): Promise { + const [command] = z.tuple([CoreDesktopCommandSchema]).parse(input) + if (this.closing && command.operation !== 'close' && command.operation !== 'retire') + throw new Error('Native desktop is stopping') + switch (command.operation) { + case 'capabilities': + return { + preview: Boolean(this.native.mcpPreview), + journal: Boolean(this.native.runJournalObserver) + } + case 'call': { + if (this.calls.has(command.id)) throw new Error('Native desktop identity already exists') + if (this.calls.size >= CORE_NATIVE_CALLS) + throw new Error('Native desktop call limit exceeded') + const args = CoreDesktopArguments[command.method].parse(command.args) + const controller = new AbortController() + const entry = { controller, operation: Promise.resolve(undefined) } + this.calls.set(command.id, entry) + if (this.canceledCalls.delete(command.id)) + controller.abort(new Error('Native desktop call canceled')) + entry.operation = Promise.resolve().then(async () => { + controller.signal.throwIfAborted() + const result = await this.call(command.method, args, controller.signal) + controller.signal.throwIfAborted() + return result + }) + try { + return await entry.operation + } finally { + this.calls.delete(command.id) + } + } + case 'watch': { + if (this.watches.has(command.id)) throw new Error('Native renderer watch already exists') + if (this.watches.size >= 64) throw new Error('Native renderer watch limit exceeded') + const entry: Watch = { active: true, dispose() {} } + this.watches.set(command.id, entry) + try { + entry.dispose = this.native.onRendererDestroyed(command.webContentsId, () => { + if (!entry.active || entry.delivery) return + entry.delivery = this.deliver({ type: 'desktop.rendererDestroyed', id: command.id }) + void entry.delivery.catch((error) => this.failed(error)) + }) + } catch (error) { + this.watches.delete(command.id) + throw error + } + return undefined + } + case 'retire': { + if (this.closing) return await this.closing + for (const id of command.settledCalls) this.canceledCalls.delete(id) + const calls = command.calls.map((id) => { + const call = this.calls.get(id) + if (!call) this.remember(this.canceledCalls, id) + else call.controller.abort(new Error('Native desktop call canceled')) + return call?.operation + }) + const watches = command.watches.map(async (id) => { + const watch = this.watches.get(id) + if (watch) { + watch.active = false + watch.dispose() + await watch.delivery?.catch(() => {}) + this.watches.delete(id) + } + }) + const results = await Promise.allSettled([...calls, ...watches]) + // A canceled operation's business error does not invalidate physical retirement. + for (const result of results.slice(calls.length)) + if (result.status === 'rejected') throw result.reason + return undefined + } + case 'close': + return await this.close() + } + } + + publishRemoteAuthWindow( + event: Extract['event'] + ): void { + void this.deliver({ type: 'desktop.remoteAuthWindow', event }).catch((error) => + this.failed(error) + ) + } + + close(): Promise { + if (this.closing) return this.closing + for (const call of this.calls.values()) + call.controller.abort(new Error('Native desktop is stopping')) + this.closing = (async () => { + const errors: unknown[] = [] + for (const watch of this.watches.values()) { + try { + watch.active = false + watch.dispose() + } catch (error) { + errors.push(error) + } + } + try { + await this.retire() + } catch (error) { + errors.push(error) + } + await Promise.allSettled([...this.calls.values()].map((call) => call.operation)) + await Promise.allSettled([...this.watches.values()].map((watch) => watch.delivery)) + if (errors.length) throw new AggregateError(errors, 'Native desktop did not retire') + this.watches.clear() + this.canceledCalls.clear() + })() + return this.closing + } + + private remember(set: Set, id: string): void { + if (set.has(id)) return + if (set.size >= 80) { + const error = new Error('Native desktop retirement identities exceeded') + this.failed(error) + throw error + } + set.add(id) + } + + private async call( + method: CoreDesktopMethod, + args: unknown[], + signal: AbortSignal + ): Promise { + if (method.startsWith('copilotAuthorization.')) args[1] = signal + else if ( + method === 'fetchRegistryIcon' || + method === 'agentDesktop.readImageSize' || + method === 'approvalPresentation.present' + ) + args.push(signal) + if (method === 'cacheImage') args[1] = { ...(args[1] as object | undefined), signal } + const parts = method.split('.') + let owner: object = this.native + for (const part of parts.slice(0, -1)) { + const next: unknown = Reflect.get(owner, part) + if (!next || typeof next !== 'object') + throw new Error('Native desktop capability is unavailable') + owner = next + } + const target: unknown = Reflect.get(owner, parts.at(-1)!) + if (method === 'mcpPreview.shouldCaptureAfterClick' && target === undefined) return false + if (typeof target !== 'function') throw new Error('Native desktop capability is unavailable') + return await Reflect.apply(target, owner, args) + } +} diff --git a/src/main/harness/helpers.ts b/src/main/harness/helpers.ts new file mode 100644 index 0000000000..5e4aa1884b --- /dev/null +++ b/src/main/harness/helpers.ts @@ -0,0 +1,351 @@ +import { deserialize, serialize } from 'node:v8' +import { z } from 'zod' +import type { + BackgroundExecHost, + BackgroundExecRpcRequest +} from '@/agent/shared/process/backgroundExecSessionManager' +import type { CodeModeHost } from '@/tool/codeMode/runCodeRuntimeManager' +import type { SchedulerHost } from '@/scheduler/schedulerProcessManager' +import type { SpawnFileWatcherHost } from '@/platform/fileWatcher/watcherHostClient' +import type { FileWatcherRpcRequest } from '@/platform/fileWatcher/watcherTypes' +import type { RunCodeParentMessage } from '@shared/codeModeProtocol' +import type { SchedulerCommand } from '@shared/cronJobs' +import { + CORE_HELPER_BATCH, + CORE_HELPER_QUEUE, + CORE_HELPER_QUEUE_BYTES, + CoreHelperCommandSchema, + parseCoreHelperMessage, + type CoreHelperEvent, + type CoreHelperKind +} from '@shared/harness/helpers' + +type Factories = { + exec(signal: AbortSignal): Promise + code(signal: AbortSignal): Promise + scheduler(signal: AbortSignal): Promise + watcher: SpawnFileWatcherHost +} + +type Host = { + pid(): number | undefined + postMessage(message: unknown): void + onMessage(listener: (message: unknown) => void): void + onExit(listener: (code: number) => void): void + onError(listener: (kind: string, location: string) => void): void + stop(): void | Promise + rss?(signal: AbortSignal): Promise +} +type Helper = { + id: string + kind: CoreHelperKind + controller: AbortController + opening: Promise + host?: Host + exited: ReturnType> + exitReceipt?: Promise + startReceipt?: Promise + startupFailed: boolean + retired?: Promise + dead: boolean + termination?: Promise + reads: Set> +} +type PendingEvent = { + value: CoreHelperEvent + bytes: number + receipt: ReturnType> +} + +export class CoreHelperNative { + private readonly helpers = new Map() + private readonly events: PendingEvent[] = [] + private sequence = 0 + private bytes = 0 + private sending = false + private closed = false + private closing: Promise | undefined + private failure: Error | undefined + + constructor( + private readonly factories: Factories, + private readonly deliver: (events: CoreHelperEvent[]) => Promise, + private readonly failed: (error: Error) => void + ) {} + + async invoke(args: unknown): Promise { + const [command] = z.tuple([CoreHelperCommandSchema]).parse(args) + switch (command.operation) { + case 'open': { + if (this.closed || this.closing || this.failure) + throw this.failure ?? new Error('Native helpers are stopping') + if (this.helpers.has(command.id)) throw new Error('Native helper identity already used') + const helper: Helper = { + id: command.id, + kind: command.kind, + controller: new AbortController(), + opening: Promise.resolve(), + exited: Promise.withResolvers(), + dead: false, + startupFailed: false, + reads: new Set() + } + this.helpers.set(helper.id, helper) + helper.opening = Promise.resolve().then(async () => { + try { + helper.controller.signal.throwIfAborted() + helper.host = await this.spawn(helper.kind, helper.controller.signal) + const host = helper.host + host.onMessage((message) => { + if (!helper.dead) + void this.enqueue(helper, { type: 'message', message }).catch(() => {}) + }) + host.onError((kind, location) => { + if (!helper.dead) + void this.enqueue(helper, { type: 'error', kind, location }).catch(() => {}) + }) + host.onExit((code) => { + if (helper.dead) return + helper.dead = true + helper.exited.resolve() + helper.exitReceipt = this.enqueue(helper, { type: 'exit', code }) + void helper.exitReceipt.catch(() => {}) + queueMicrotask(() => { + void this.completeRetirement(helper) + }) + }) + if (helper.controller.signal.aborted) { + await this.stopHost(helper) + helper.controller.signal.throwIfAborted() + } + helper.startReceipt = this.enqueue(helper, { type: 'opened' }) + void helper.startReceipt.catch(() => {}) + } catch (error) { + helper.startupFailed = true + helper.startReceipt = this.enqueue(helper, { + type: 'openFailed', + name: error instanceof Error ? error.name.slice(0, 256) : 'NativeHelperStartupError', + message: + error instanceof Error + ? error.message.slice(0, 8192) + : 'Native helper startup failed' + }) + void helper.startReceipt.catch(() => {}) + queueMicrotask(() => { + void this.completeRetirement(helper) + }) + } + }) + return { accepted: true } + } + case 'send': { + if (this.failure || this.closed) + throw this.failure ?? new Error('Native helpers are closed') + const helper = this.require(command.id) + await helper.opening + if (helper.dead || helper.termination || helper.startupFailed) + throw new Error('Native helper is retired') + helper.host!.postMessage(parseCoreHelperMessage(helper.kind, command.message)) + return undefined + } + case 'terminate': { + const results = await Promise.allSettled( + command.ids.map(async (id) => { + const helper = this.helpers.get(id) + if (helper) await this.terminate(helper) + }) + ) + for (const result of results) if (result.status === 'rejected') throw result.reason + return undefined + } + case 'rss': { + const helper = this.helpers.get(command.id) + if (!helper || helper.dead) return null + if (helper.kind !== 'code') + throw new Error('RSS is only available for an owned Code Mode helper') + const read = Promise.resolve().then(async () => { + await helper.opening + if (helper.startupFailed) throw new Error('Native helper startup failed') + helper.controller.signal.throwIfAborted() + const value = await helper.host!.rss!(helper.controller.signal) + helper.controller.signal.throwIfAborted() + return value + }) + helper.reads.add(read) + try { + return await read + } finally { + helper.reads.delete(read) + } + } + case 'close': + await this.close() + return undefined + } + } + + close(): Promise { + if (this.closing) return this.closing + this.closed = true + const helpers = [...this.helpers.values()] + for (const helper of helpers) helper.controller.abort(new Error('Native helpers are stopping')) + this.closing = (async () => { + const results = await Promise.allSettled(helpers.map((helper) => this.terminate(helper))) + for (const result of results) if (result.status === 'rejected') throw result.reason + this.helpers.clear() + })() + return this.closing + } + + private require(id: string): Helper { + const helper = this.helpers.get(id) + if (!helper) throw new Error('Unknown Native helper') + return helper + } + + private terminate(helper: Helper): Promise { + helper.controller.abort(new Error('Native helper startup canceled')) + helper.termination ??= (async () => { + await helper.opening.catch(() => undefined) + await Promise.allSettled(helper.reads) + if (helper.host) await this.stopHost(helper) + await helper.startReceipt?.catch(() => {}) + })() + return helper.termination + } + + private stopHost(helper: Helper): Promise { + helper.retired ??= (async () => { + try { + if (!helper.dead || helper.kind === 'exec') await helper.host!.stop() + } catch (error) { + if (helper.kind === 'exec') throw error + } + await helper.exited.promise + await helper.exitReceipt?.catch(() => {}) + })() + return helper.retired + } + + private async completeRetirement(helper: Helper): Promise { + try { + await helper.opening + if (helper.host) await this.stopHost(helper) + await helper.startReceipt?.catch(() => {}) + this.helpers.delete(helper.id) + } catch (error) { + this.fail(error) + } + } + + private enqueue(helper: Helper, event: CoreHelperEvent['event']): Promise { + if (this.failure) return Promise.reject(this.failure) + let encoded: Buffer + try { + encoded = serialize(event) + } catch (error) { + return Promise.reject(this.fail(error)) + } + if ( + this.events.length >= CORE_HELPER_QUEUE || + this.bytes + encoded.length > CORE_HELPER_QUEUE_BYTES + ) { + const error = this.fail(new Error('Native helper event queue exceeded')) + return Promise.reject(error) + } + const pending: PendingEvent = { + value: { + id: helper.id, + sequence: ++this.sequence, + pid: helper.host?.pid() ?? null, + event: deserialize(encoded) + }, + bytes: encoded.length, + receipt: Promise.withResolvers() + } + this.events.push(pending) + this.bytes += pending.bytes + if (!this.sending) { + this.sending = true + queueMicrotask(() => { + void this.sendEvents() + }) + } + return pending.receipt.promise + } + + private async sendEvents(): Promise { + try { + while (this.events.length && !this.failure) { + const batch = this.events.slice(0, CORE_HELPER_BATCH) + await this.deliver(batch.map((value) => value.value)) + if (this.failure) throw this.failure + this.events.splice(0, batch.length) + for (const value of batch) { + this.bytes -= value.bytes + value.receipt.resolve() + } + } + } catch (error) { + this.fail(error) + } finally { + this.sending = false + } + } + + private fail(value: unknown): Error { + if (this.failure) return this.failure + const error = value instanceof Error ? value : new Error('Native helper event delivery failed') + this.failure = error + for (const event of this.events.splice(0)) event.receipt.reject(error) + this.bytes = 0 + this.failed(error) + return error + } + + private async spawn(kind: CoreHelperKind, signal: AbortSignal): Promise { + if (kind === 'content' || kind === 'git') { + const host = await this.factories.watcher(kind, signal) + return { + pid: () => undefined, + postMessage: (message) => host.postMessage(message as FileWatcherRpcRequest), + onMessage: (listener) => host.onMessage(listener), + onExit: (listener) => host.onExit(listener), + onError: (listener) => host.onError(listener), + stop: () => host.terminate() + } + } + if (kind === 'code') { + const host = await this.factories.code(signal) + return { + pid: () => host.pid, + postMessage: (message) => host.postMessage(message as RunCodeParentMessage), + onMessage: (listener) => host.on('message', listener), + onExit: (listener) => host.on('exit', listener), + onError: (listener) => host.on('error', listener), + stop: () => host.kill(), + rss: (requestSignal) => host.getRssBytes(requestSignal) + } + } + if (kind === 'exec') { + const host = await this.factories.exec(signal) + return { + pid: () => host.pid, + postMessage: (message) => host.postMessage(message as BackgroundExecRpcRequest), + onMessage: (listener) => host.onMessage(listener), + onExit: (listener) => host.onExit(listener), + onError: (listener) => host.onError(listener), + stop: () => host.kill() + } + } + const host = await this.factories.scheduler(signal) + return { + pid: () => host.pid, + postMessage: (message) => host.postMessage(message as SchedulerCommand), + onMessage: (listener) => host.onMessage(listener), + onExit: (listener) => host.onExit(listener), + onError: (listener) => host.onError(listener), + stop: () => host.kill() + } + } +} diff --git a/src/main/harness/memory-vector.ts b/src/main/harness/memory-vector.ts new file mode 100644 index 0000000000..efed58c262 --- /dev/null +++ b/src/main/harness/memory-vector.ts @@ -0,0 +1,352 @@ +import { randomUUID } from 'node:crypto' +import fs from 'node:fs' +import { endianness } from 'node:os' +import { z } from 'zod' +import { AsyncSemaphore } from '@/lib/asyncSemaphore' +import { withSoftDeadline } from '@/memory/core/asyncDeadline' +import type { IMemoryVectorStore, MemoryVectorStoreFactoryPort } from '@/memory/ports' +import type { MemoryVectorRecord, MemoryVectorQueryOptions } from '@/memory/types' +import { + createMemoryVectorStorePaths, + persistMemoryVectorQuarantine +} from '@/memory/infra/memoryVectorStorePaths' +import { + isDuckDbFatalError, + MemoryVectorStoreQuarantineRequiredError, + MemoryVectorStoreTerminalRecoveryError +} from '@/memory/infra/vectorStoreErrors' +import { + encodeMemoryVector, + MEMORY_VECTOR_CHUNK_BYTES, + MEMORY_VECTOR_INITIAL_CHUNK_BYTES, + MEMORY_VECTOR_UPLOAD_BYTES, + MemoryVectorCommandSchema, + type MemoryVectorCommand, + type MemoryVectorConfig +} from '@shared/harness/memory-vector' +import { isSafeAgentId } from '@shared/types/agent-memory' +import type { HarnessRuntime } from './runtime' + +const matches = z + .array( + z.object({ memoryId: z.string().min(1).max(1024), distance: z.number().finite() }).strict() + ) + .max(800) + +export class RutisMemoryVectorStores implements MemoryVectorStoreFactoryPort { + private readonly config: MemoryVectorConfig + private readonly stores = new Map() + private readonly barriers = new Map>() + private readonly uploads = new AsyncSemaphore(1) + private startup: Promise | undefined + private stopped = false + + constructor( + readonly runtime: Pick, + config: Omit + ) { + this.config = { ...config, generation: randomUUID() } + } + + async createVectorStore( + agentId: string, + embedding: { providerId: string; modelId: string }, + dimensions: number + ): Promise { + this.assertAgent(agentId) + await this.barriers.get(agentId) + if (this.stopped) throw new Error('Memory vector stores are stopping') + if ([...this.stores.values()].some((store) => store.agentId === agentId)) { + throw new Error('Memory vector store already has a local owner') + } + const store = new RemoteMemoryVectorStore(this, agentId, randomUUID(), dimensions) + const opening = MemoryVectorCommandSchema.parse({ + method: 'open', + resource: store.id, + agentId, + embedding, + dimensions, + generation: this.config.generation, + operation: randomUUID() + }) + this.stores.set(store.id, store) + try { + await this.start() + store.assertUsable() + const grant = z + .object({ resource: z.literal(store.id), usable: z.boolean() }) + .strict() + .parse(await this.request(opening)) + if (!grant.usable) store.revoke() + return store + } catch (error) { + await store.close().catch(() => undefined) + throw error + } + } + + async resetVectorStore(agentId: string): Promise { + this.assertAgent(agentId) + await this.barriers.get(agentId) + if (!this.startup) { + const paths = createMemoryVectorStorePaths(this.config.memoryDbDir, agentId) + if ( + !fs.existsSync(`${paths.current}.lease`) && + !Object.values(paths).some((file) => fs.existsSync(file) || fs.existsSync(`${file}.wal`)) + ) + return + } + for (const store of this.stores.values()) if (store.agentId === agentId) store.revoke() + await this.start() + await this.request({ method: 'reset', agentId }) + for (const [id, store] of this.stores) if (store.agentId === agentId) this.stores.delete(id) + } + + markVectorStoreQuarantined(agentId: string): void { + this.assertAgent(agentId) + for (const store of this.stores.values()) if (store.agentId === agentId) store.revoke() + // Append-only intent survives a dead worker; only the locked native owner consumes it. + try { + persistMemoryVectorQuarantine(createMemoryVectorStorePaths(this.config.memoryDbDir, agentId)) + } finally { + const previous = this.barriers.get(agentId) + const barrier = (async () => { + await previous + await this.start() + await this.request({ method: 'quarantine', agentId }) + })() + this.barriers.set(agentId, barrier) + void barrier.catch(() => undefined) + } + } + + async dispose(): Promise { + this.stopped = true + const drain = (async () => { + await Promise.allSettled([...this.stores.values()].map((store) => store.close(true))) + await Promise.allSettled(this.barriers.values()) + })() + // The composition owner stops the host next; unresolved native work stays owned until death. + const result = await withSoftDeadline(drain, 5_000) + if (result.timedOut) + console.warn('[Memory] Vector disposal timed out; host shutdown will fence native work') + } + + async close(store: RemoteMemoryVectorStore, preserveLease: boolean): Promise { + try { + await this.startup + if (this.startup) await this.request({ method: 'close', resource: store.id, preserveLease }) + await this.barriers.get(store.agentId) + } finally { + this.stores.delete(store.id) + } + } + + async upsert(store: RemoteMemoryVectorStore, records: MemoryVectorRecord[]): Promise { + store.assertUsable() + if (!records.length) return + if (endianness() !== 'LE') throw new Error('Memory vector uploads require a little-endian host') + const bytes = records.length * store.dimensions * 4 + if (records.length > 50 || bytes > MEMORY_VECTOR_UPLOAD_BYTES) + throw new Error('Memory vector upload exceeds batch limit') + for (const record of records) { + if (record.embedding.length !== store.dimensions) + throw new Error('Invalid memory vector upload values') + for (let index = 0; index < record.embedding.length; index++) { + const value = record.embedding[index] + if (!Number.isFinite(value) || !Number.isFinite(Math.fround(value))) + throw new Error('Invalid memory vector upload values') + } + } + if (this.uploads.activeCount + this.uploads.pendingCount >= 24) + throw new Error('Memory vector upload queue limit exceeded') + await this.uploads.run(async () => { + store.assertUsable() + const upload = randomUUID() + const chunkAt = (offset: number, limit = MEMORY_VECTOR_CHUNK_BYTES) => { + const chunk = Buffer.allocUnsafe(Math.min(limit, bytes - offset)) + const values = new Float32Array(chunk.buffer, chunk.byteOffset, chunk.length / 4) + let sourceOffset = offset / 4 + for (let local = 0; local < values.length; ) { + const source = records[Math.floor(sourceOffset / store.dimensions)].embedding + const start = sourceOffset % store.dimensions + const count = Math.min(source.length - start, values.length - local) + if (start === 0 && count === source.length) values.set(source, local) + else { + for (let index = 0; index < count; index++) + values[local + index] = source[start + index] + } + local += count + sourceOffset += count + } + return chunk + } + try { + let offset: number + { + const first = chunkAt(0, MEMORY_VECTOR_INITIAL_CHUNK_BYTES) + offset = first.length + await this.request({ + method: 'begin', + resource: store.id, + upload, + memoryIds: records.map((record) => record.memoryId), + bytes, + data: first + }) + } + while (offset < bytes) { + store.assertUsable() + const chunk = chunkAt(offset) + if (offset + chunk.length === bytes) { + await this.request({ + method: 'commit', + resource: store.id, + upload, + offset, + data: chunk + }) + return + } + z.literal(offset + chunk.length).parse( + await this.request({ + method: 'chunk', + resource: store.id, + upload, + offset, + data: chunk + }) + ) + offset += chunk.length + } + store.assertUsable() + await this.request({ method: 'commit', resource: store.id, upload }) + } catch (error) { + await this.request({ method: 'abort', resource: store.id, upload }).catch(() => undefined) + throw error + } + }) + } + + async request(command: MemoryCommand): Promise { + try { + return await this.runtime.requestMemory({ + ...command, + generation: this.config.generation, + operation: randomUUID() + } as MemoryVectorCommand) + } catch (error) { + if (!this.runtime.memoryAvailable) + throw new MemoryVectorStoreQuarantineRequiredError( + 'Memory vector runtime is unavailable; restart DeepChat', + { cause: error } + ) + if (error instanceof Error) { + if (error.name === 'MemoryVectorStoreTerminalRecoveryError') { + throw new MemoryVectorStoreTerminalRecoveryError(error.message, { + fatal: isDuckDbFatalError(error) + }) + } + if ( + ['MemoryVectorStoreQuarantineRequiredError', 'MemoryVectorStorePostCommitError'].includes( + error.name + ) + ) { + throw new MemoryVectorStoreQuarantineRequiredError(error.message) + } + } + throw error + } + } + + private start(): Promise { + return (this.startup ??= this.runtime.startMemory(this.config).catch((error) => { + throw new MemoryVectorStoreQuarantineRequiredError( + 'Memory vector runtime failed to start; restart DeepChat', + { cause: error } + ) + })) + } + + private assertAgent(agentId: string): void { + if (!isSafeAgentId(agentId)) throw new Error('Invalid memory vector Agent identity') + } +} + +type MemoryCommand = MemoryVectorCommand extends infer T + ? T extends MemoryVectorCommand + ? Omit + : never + : never + +class RemoteMemoryVectorStore implements IMemoryVectorStore { + private usable = true + private closing: Promise | undefined + + constructor( + private readonly owner: RutisMemoryVectorStores, + readonly agentId: string, + readonly id: string, + readonly dimensions: number + ) {} + + isUsable(): boolean { + return this.usable && this.owner.runtime.memoryAvailable + } + revoke(): void { + this.usable = false + } + assertUsable(): void { + if (!this.owner.runtime.memoryAvailable) + throw new MemoryVectorStoreQuarantineRequiredError( + 'Memory vector runtime is unavailable; restart DeepChat' + ) + if (!this.isUsable()) throw new Error('Memory vector resource is unavailable') + } + + upsert(records: MemoryVectorRecord[]): Promise { + return this.owner.upsert(this, records) + } + + async query(embedding: number[], options: MemoryVectorQueryOptions) { + this.assertUsable() + return matches.parse( + await this.owner.request({ + method: 'query', + resource: this.id, + vector: encodeMemoryVector(embedding, this.dimensions).toString('base64'), + topK: options.topK + }) + ) + } + + async queryByMemoryId(memoryId: string, options: MemoryVectorQueryOptions) { + this.assertUsable() + return matches.parse( + await this.owner.request({ + method: 'queryByMemoryId', + resource: this.id, + memoryId, + topK: options.topK + }) + ) + } + + async deleteByMemoryIds(memoryIds: string[]): Promise { + this.assertUsable() + await this.owner.request({ method: 'delete', resource: this.id, memoryIds }) + } + + async listMemoryIds(afterId: string | null, limit: number): Promise { + this.assertUsable() + return z + .array(z.string().min(1).max(1024)) + .max(512) + .parse(await this.owner.request({ method: 'list', resource: this.id, afterId, limit })) + } + + close(preserveLease = false): Promise { + this.revoke() + return (this.closing ??= this.owner.close(this, preserveLease)) + } +} diff --git a/src/main/harness/runtime.ts b/src/main/harness/runtime.ts new file mode 100644 index 0000000000..61c3d4b2a8 --- /dev/null +++ b/src/main/harness/runtime.ts @@ -0,0 +1,497 @@ +import { spawn, type ChildProcessWithoutNullStreams } from 'node:child_process' +import { createRequire } from 'node:module' +import path from 'node:path' +import { z } from 'zod' +import { + CoreFrameSchema, + type CoreFrame, + type CoreRequest, + type CoreCarrierConfig +} from '@shared/harness/core' +import { AdmissionObservationSchema } from '@shared/harness/admission' +import type { AgentInvocationAdmissionObservation } from '@shared/harness/admission' +import { + isMemoryVectorControl, + MEMORY_VECTOR_BINARY_FLAG, + MEMORY_VECTOR_FRAME_BYTES, + MEMORY_VECTOR_FRAME_FLAG, + MEMORY_VECTOR_PENDING, + MemoryVectorCommandSchema, + MemoryVectorConfigSchema, + type MemoryVectorCommand, + type MemoryVectorConfig +} from '@shared/harness/memory-vector' + +const MAX_FRAME_BYTES = 64 * 1024 +const MAX_BUFFER_BYTES = 1024 * 1024 +const MAX_REQUESTS = 1024 +const memoryIdentitySchema = z + .object({ + generation: z.string().uuid(), + pid: z.number().int().positive(), + node: z.string().max(64), + modules: z.string().max(32) + }) + .strict() +const frameSchema = z.union([ + CoreFrameSchema, + z + .object({ + type: z.literal('memoryReady'), + protocol: z.literal(1), + runtime: memoryIdentitySchema + }) + .strict(), + z.object({ type: z.literal('memoryStopped'), message: z.string().max(8192) }).strict(), + z + .object({ type: z.literal('memoryReply'), id: z.number().int().positive(), value: z.unknown() }) + .strict(), + z + .object({ + type: z.literal('memoryReply'), + id: z.number().int().positive(), + error: z.object({ name: z.string().max(256), message: z.string().max(8192) }).strict() + }) + .strict(), + z + .object({ + type: z.literal('ready'), + protocol: z.literal(2), + hostPid: z.number().int().positive(), + owner: z.literal('rust') + }) + .strict(), + z + .object({ type: z.literal('reply'), id: z.number().int().positive(), value: z.unknown() }) + .strict(), + z + .object({ + type: z.literal('reply'), + id: z.number().int().positive(), + error: z + .object({ + name: z.string().max(256), + message: z.string().max(8192) + }) + .strict() + }) + .strict(), + z + .object({ + type: z.literal('observation'), + sequence: z.number().int().positive().max(Number.MAX_SAFE_INTEGER), + value: z.unknown() + }) + .strict() +]) + +type Method = 'acquire' | 'release' | 'snapshot' | 'close' | 'flushObservations' +type RuntimeInfo = Extract, { type: 'ready' }> +interface Pending { + resolve: (value: unknown) => void + reject: (error: Error) => void + cleanup: () => void +} + +export class HarnessRuntime { + private readonly child: ChildProcessWithoutNullStreams + private readonly ready = Promise.withResolvers() + private readonly exited = Promise.withResolvers() + private readonly pending = new Map() + private buffer = Buffer.alloc(0) + private nextId = 0 + private nextObservation = 1 + private observationDrops = 0 + private readonly observations = new Map() + private failure: Error | undefined + private stopping = false + private stopPromise: Promise | undefined + private info: RuntimeInfo | undefined + private memoryConfig: MemoryVectorConfig | undefined + private readonly memoryReady = Promise.withResolvers>() + private readonly memoryPending = new Map() + private nextMemoryId = 0 + private memoryFailure: Error | undefined + private memoryInfo: z.infer | undefined + private memorySentBytes = 0 + private memoryReceivedBytes = 0 + private memoryPeakPending = 0 + private core: { message(frame: CoreFrame): void; failed(error: Error): void } | undefined + + private constructor( + directory: string, + electronExecutable: string, + capacity: number, + maxPending: number, + private readonly observe: (value: AgentInvocationAdmissionObservation) => void | Promise + ) { + const host = path.join( + directory, + `deepchat-harness-${process.platform}-${process.arch}${process.platform === 'win32' ? '.exe' : ''}` + ) + this.child = spawn( + host, + [ + electronExecutable, + path.dirname( + createRequire(path.join(directory, 'core.mjs')).resolve( + '@arcships/rutis-runtime/package.json' + ) + ), + path.join(directory, 'core.mjs'), + String(capacity), + String(maxPending) + ], + { stdio: ['pipe', 'pipe', 'pipe'], windowsHide: true } + ) + this.child.stderr.resume() + this.child.on('error', () => + this.fail(new Error('Cannot start Rutis host. Run pnpm harness:build.')) + ) + this.child.on('close', (code, signal) => { + this.fail( + new Error( + `Rutis host exited (${signal ?? code ?? 'unknown'}). Restart DeepChat to restore admission.` + ) + ) + this.exited.resolve() + }) + this.child.stdout.on('data', (bytes: Buffer) => { + try { + this.read(bytes) + } catch { + this.fail(new Error('Invalid Rutis host response')) + } + }) + this.child.stdout.on('error', () => this.fail(new Error('Rutis host output failed'))) + this.child.stdin.on('error', () => this.fail(new Error('Rutis host input failed'))) + } + + static async start(options: { + directory: string + electronExecutable: string + capacity: number + maxPending: number + observe: (value: AgentInvocationAdmissionObservation) => void | Promise + }): Promise { + const runtime = new HarnessRuntime( + options.directory, + options.electronExecutable, + options.capacity, + options.maxPending, + options.observe + ) + const timer = setTimeout( + () => runtime.fail(new Error('Rutis admission startup timed out')), + 15_000 + ) + timer.unref() + try { + await runtime.ready.promise + return runtime + } catch (error) { + await runtime.stop().catch(() => undefined) + throw error + } finally { + clearTimeout(timer) + } + } + + get identity(): RuntimeInfo | undefined { + return this.info + } + + get memoryIdentity() { + return this.memoryInfo + } + + connectCore( + config: CoreCarrierConfig, + listener: { message(frame: CoreFrame): void; failed(error: Error): void } + ): (request: CoreRequest) => void { + if (this.core) { + const error = new Error('Core generation cannot be adopted twice') + error.name = 'CoreGenerationAlreadyAdoptedError' + throw error + } + if (this.failure || this.stopping) throw this.failure ?? new Error('Rutis host is stopping') + this.core = listener + const send = (request: CoreRequest): void => { + if (this.failure || this.stopping) throw this.failure ?? new Error('Rutis host is stopping') + this.child.stdin.write(this.packet(request, MAX_FRAME_BYTES, 0)) + } + send({ type: 'coreStart', config }) + return send + } + + get memoryAvailable(): boolean { + return !!this.memoryInfo && !this.memoryFailure && !this.failure && !this.stopping + } + + get memoryTransport() { + return { + sentBytes: this.memorySentBytes, + receivedBytes: this.memoryReceivedBytes, + pending: this.memoryPending.size, + peakPending: this.memoryPeakPending + } + } + + async startMemory(config: MemoryVectorConfig): Promise { + if (this.memoryFailure) throw this.memoryFailure + if (this.failure) throw this.failure + if (!this.memoryConfig) { + this.memoryConfig = MemoryVectorConfigSchema.parse(config) + this.send({ type: 'memoryStart', config: this.memoryConfig }) + } else if (this.memoryConfig.generation !== config.generation) { + throw new Error('Memory vector generation cannot change') + } + const timer = setTimeout( + () => this.failMemory(new Error('Memory vector runtime startup timed out')), + 15_000 + ) + timer.unref() + try { + await this.memoryReady.promise + } finally { + clearTimeout(timer) + } + } + + requestMemory(input: MemoryVectorCommand): Promise { + if (this.failure || this.memoryFailure) + return Promise.reject(this.failure ?? this.memoryFailure) + if (this.stopping || !this.memoryInfo) + return Promise.reject(new Error('Memory vector runtime is unavailable')) + let command: MemoryVectorCommand + let packet: Buffer + const id = this.nextMemoryId + 1 + try { + command = MemoryVectorCommandSchema.parse(input) + if (command.generation !== this.memoryInfo.generation) + throw new Error('Memory vector generation changed') + const control = isMemoryVectorControl(command.method) + if ( + this.memoryPending.size >= (control ? MEMORY_VECTOR_PENDING : MEMORY_VECTOR_PENDING - 8) + ) { + throw new Error('Memory vector operation limit exceeded') + } + const data = 'data' in command ? command.data : undefined + packet = this.packet( + { type: 'memoryCall', id, command: data ? { ...command, data: undefined } : command }, + control ? MAX_FRAME_BYTES : MEMORY_VECTOR_FRAME_BYTES, + control ? 0 : MAX_FRAME_BYTES + 4, + true, + data + ) + } catch (error) { + return Promise.reject(error) + } + this.nextMemoryId = id + return new Promise((resolve, reject) => { + // Native deadlines belong to Memory; retain ownership until settlement or confirmed death. + this.memoryPending.set(id, { resolve, reject, cleanup: () => {} }) + this.memorySentBytes += packet.length + this.memoryPeakPending = Math.max(this.memoryPeakPending, this.memoryPending.size) + this.child.stdin.write(packet) + }) + } + + request(method: Method, args: unknown[]): Promise { + if (this.failure) return Promise.reject(this.failure) + if (this.stopping) return Promise.reject(new Error('Rutis admission is stopping')) + if (this.pending.size >= MAX_REQUESTS) { + this.fail(new Error('Rutis admission request limit exceeded')) + return Promise.reject(this.failure) + } + const id = ++this.nextId + return new Promise((resolve, reject) => { + const timer = + method === 'acquire' + ? undefined + : setTimeout(() => this.fail(new Error(`Rutis admission ${method} timed out`)), 10_000) + timer?.unref() + this.pending.set(id, { + resolve, + reject, + cleanup: () => { + clearTimeout(timer) + } + }) + this.send({ type: 'call', id, method, args }) + }) + } + + async stop(): Promise { + if (this.stopPromise) return this.stopPromise + this.stopping = true + this.child.stdin.end() + this.stopPromise = (async () => { + const timer = setTimeout(() => this.child.kill('SIGKILL'), 12_000) + timer.unref() + try { + await this.exited.promise + } finally { + clearTimeout(timer) + } + })() + return this.stopPromise + } + + private send(value: unknown): void { + if (this.failure) return + try { + this.child.stdin.write(this.packet(value, MAX_FRAME_BYTES, 0)) + } catch { + this.fail(new Error('Rutis admission transport write failed')) + } + } + + private packet( + value: unknown, + maxFrame: number, + reserve: number, + vector = false, + data?: Uint8Array + ): Buffer { + const json = Buffer.from(JSON.stringify(value)) + const length = json.length + (data ? 4 + data.byteLength : 0) + const request = value as { type?: string; method?: string } + const coreData = + this.core && + ((vector && reserve > 0) || (request.type === 'call' && request.method === 'acquire')) + const reserved = coreData ? Math.max(reserve, MAX_BUFFER_BYTES / 2) : reserve + if ( + length > maxFrame || + this.child.stdin.writableLength + 4 + length > MAX_BUFFER_BYTES - reserved + ) { + throw new Error('Rutis transport limit exceeded') + } + const packet = Buffer.allocUnsafe(4 + length) + packet.writeUInt32BE( + length + (vector ? MEMORY_VECTOR_FRAME_FLAG : 0) + (data ? MEMORY_VECTOR_BINARY_FLAG : 0) + ) + if (data) { + packet.writeUInt32BE(json.length, 4) + json.copy(packet, 8) + packet.set(data, 8 + json.length) + } else json.copy(packet, 4) + return packet + } + + private read(bytes: Buffer): void { + if (this.buffer.length + bytes.length > MAX_BUFFER_BYTES) + throw new Error('Host buffer exceeded') + this.buffer = Buffer.concat([this.buffer, bytes]) + while (this.buffer.length >= 4) { + const header = this.buffer.readUInt32BE(0) + const vector = header >= MEMORY_VECTOR_FRAME_FLAG + const length = vector ? header - MEMORY_VECTOR_FRAME_FLAG : header + if (length === 0 || length > (vector ? MEMORY_VECTOR_FRAME_BYTES : MAX_FRAME_BYTES)) + throw new Error('Invalid frame length') + if (this.buffer.length < length + 4) return + const frame = frameSchema.parse( + JSON.parse(this.buffer.subarray(4, 4 + length).toString('utf8')) + ) + this.buffer = this.buffer.subarray(4 + length) + if (frame.type === 'memoryReply') this.memoryReceivedBytes += length + 4 + if (vector !== (frame.type === 'memoryReply')) throw new Error('Invalid frame kind') + if (frame.type !== 'memoryReply' && length > MAX_FRAME_BYTES) + throw new Error('Invalid control frame length') + if ( + frame.type === 'coreReady' || + frame.type === 'coreStopped' || + frame.type === 'coreReply' || + frame.type === 'coreNativeCall' + ) { + if (!this.core) throw new Error('Unexpected Core response') + this.core.message(frame) + } else if (frame.type === 'memoryReady') { + if ( + !this.memoryConfig || + frame.runtime.generation !== this.memoryConfig.generation || + this.memoryInfo + ) { + throw new Error('Invalid memory generation') + } + this.memoryInfo = frame.runtime + this.memoryReady.resolve(frame.runtime) + } else if (frame.type === 'memoryStopped') { + this.failMemory(new Error(frame.message)) + } else if (frame.type === 'ready') { + if (this.info) throw new Error('Admission generation changed') + this.info = frame + this.ready.resolve(frame) + } else if (frame.type === 'observation') { + if (frame.sequence < this.nextObservation) continue + const parsed = AdmissionObservationSchema.safeParse(frame.value) + this.observations.set(frame.sequence, parsed.success ? parsed.data : null) + if (this.observations.size > 1024) { + this.observationDrops += this.observations.size + this.observations.clear() + this.nextObservation = frame.sequence + 1 + } + while (this.observations.has(this.nextObservation)) { + const value = this.observations.get(this.nextObservation++) + this.observations.delete(this.nextObservation - 1) + if (!value) { + this.observationDrops += 1 + continue + } + try { + const observed = this.observe( + value.type === 'closed' + ? { + ...value, + observationsDropped: value.observationsDropped + this.observationDrops + } + : value + ) + if (observed) void observed.catch(() => undefined) + } catch { + /* Observers cannot alter admission. */ + } + } + } else { + const requests = frame.type === 'memoryReply' ? this.memoryPending : this.pending + const pending = requests.get(frame.id) + if (frame.type === 'memoryReply' && this.memoryFailure) continue + if (!pending) throw new Error('Unknown harness reply') + requests.delete(frame.id) + pending.cleanup() + if ('error' in frame) { + const error = new Error(frame.error.message) + error.name = frame.error.name + pending.reject(error) + } else pending.resolve(frame.value) + } + } + } + + private fail(error: Error): void { + if (this.failure) return + this.failure = error + this.ready.reject(error) + this.failMemory(error) + this.core?.failed(error) + for (const pending of this.pending.values()) { + pending.cleanup() + pending.reject(error) + } + this.pending.clear() + this.child.stdin.destroy() + if (!this.stopping && this.child.exitCode === null && this.child.pid) { + const timer = setTimeout(() => this.child.kill('SIGKILL'), 12_000) + timer.unref() + this.child.once('exit', () => clearTimeout(timer)) + } + } + + private failMemory(error: Error): void { + if (this.memoryFailure) return + this.memoryFailure = error + if (this.memoryConfig) this.memoryReady.reject(error) + for (const pending of this.memoryPending.values()) pending.reject(error) + this.memoryPending.clear() + } +} diff --git a/src/main/hook/index.ts b/src/main/hook/index.ts index 9ad94623d6..c5b85e2699 100644 --- a/src/main/hook/index.ts +++ b/src/main/hook/index.ts @@ -1,5 +1,4 @@ import { spawn, type ChildProcess } from 'child_process' -import { app } from 'electron' import fs from 'fs' import type { HookCommandItem, @@ -199,14 +198,16 @@ const projectHookEvent = (event: HookEvent, time: string): HookEventProjection = export class HookService implements HookObserver { private accepting = true - private readonly activeChildren = new Set() + private retired = false + private readonly activeChildren = new Map>() /** Per-session delivery chain: emission order within a session, no coupling across sessions. */ private readonly sessionChains = new Map>() private snapshot: HookConfigSnapshot | null = null constructor( private readonly settings: HookSettingsPort, - private readonly query: HookQueryPort + private readonly query: HookQueryPort, + private readonly appVersion: string ) {} getConfigSnapshot(): HooksNotificationsSettings { @@ -215,6 +216,7 @@ export class HookService implements HookObserver { /** Store write and subscription refresh happen together so the index is never behind the config. */ updateConfig(config: HooksNotificationsSettings): HooksNotificationsSettings { + if (this.retired) throw new Error('Hooks capability is retired') const stored = this.settings.setHooksNotificationsConfig(config) this.snapshot = buildConfigSnapshot(stored) return stored @@ -226,20 +228,42 @@ export class HookService implements HookObserver { } start(): void { + if (this.retired) throw new Error('Hooks capability is retired') + if (this.activeChildren.size > 0 || this.sessionChains.size > 0) { + throw new Error('Hook work must drain before restarting') + } this.accepting = true this.refreshSubscriptions() } + dispose(): Promise { + this.retired = true + return this.stop() + } + async stop(): Promise { this.accepting = false - for (const child of this.activeChildren) { + for (const child of this.activeChildren.keys()) { try { child.kill('SIGKILL') } catch { // Process may already be closing. } } - await Promise.allSettled(Array.from(this.sessionChains.values())) + const drain = Promise.allSettled([ + ...this.sessionChains.values(), + ...this.activeChildren.values() + ]) + await new Promise((resolve, reject) => { + const timeout = setTimeout( + () => reject(new Error('Hook work did not drain before shutdown')), + COMMAND_TIMEOUT_MS + ) + void drain.then(() => { + clearTimeout(timeout) + resolve() + }) + }) } isObserved(event: HookEventName): boolean { @@ -268,6 +292,9 @@ export class HookService implements HookObserver { } async testHookCommand(hookId: string): Promise { + if (!this.accepting) { + return { success: false, durationMs: 0, error: 'Hook commands are stopped' } + } const hook = this.getConfigSnapshot().hooks.find((item) => item.id === hookId) if (!hook) { return { @@ -291,7 +318,7 @@ export class HookService implements HookObserver { event, time: new Date().toISOString(), isTest: true, - app: { version: app.getVersion(), platform: process.platform }, + app: { version: this.appVersion, platform: process.platform }, session: { conversationId: undefined, agentId: null, workdir: null }, user: { messageId: undefined, promptPreview: 'Test message' }, tool: null, @@ -303,6 +330,7 @@ export class HookService implements HookObserver { } private readSnapshot(): HookConfigSnapshot { + if (this.retired) throw new Error('Hooks capability is retired') this.snapshot ??= buildConfigSnapshot(this.settings.getHooksNotificationsConfig()) return this.snapshot } @@ -406,7 +434,7 @@ export class HookService implements HookObserver { time: projection.time, isTest: false, app: { - version: app.getVersion(), + version: this.appVersion, platform: process.platform }, session: { @@ -489,7 +517,13 @@ export class HookService implements HookObserver { env, windowsHide: true }) - this.activeChildren.add(child) + let releaseChild: () => void + this.activeChildren.set( + child, + new Promise((resolve) => { + releaseChild = resolve + }) + ) const finalize = (result: HookCommandResult) => { if (finished) { @@ -525,7 +559,6 @@ export class HookService implements HookObserver { }, COMMAND_TIMEOUT_MS) child.on('error', (error) => { - this.activeChildren.delete(child) clearTimeout(timeout) finalize({ success: false, @@ -545,6 +578,7 @@ export class HookService implements HookObserver { child.on('close', (code) => { this.activeChildren.delete(child) + releaseChild() clearTimeout(timeout) finalize({ success: !timedOut && code === 0, diff --git a/src/main/index.ts b/src/main/index.ts index d493ff2cf5..d2eb244ad1 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -1,3 +1,6 @@ +import { app } from 'electron' +import { setLegacyConsoleDebugEnabled } from '@shared/logger' import { startApp } from './appMain' +setLegacyConsoleDebugEnabled(!app.isPackaged) startApp() diff --git a/src/main/knowledge/database/knowledgeDatabase.ts b/src/main/knowledge/database/knowledgeDatabase.ts index 5e94ae7147..54db5619ad 100644 --- a/src/main/knowledge/database/knowledgeDatabase.ts +++ b/src/main/knowledge/database/knowledgeDatabase.ts @@ -15,12 +15,6 @@ import type { import type { KnowledgeDatabasePort } from '../ports' import { nanoid } from 'nanoid' -import { app } from 'electron' - -const runtimeBasePath = path - .join(app.getAppPath(), 'runtime') - .replace('app.asar', 'app.asar.unpacked') -const extensionDir = path.join(runtimeBasePath, 'duckdb', 'extensions') const extensionSuffix = '.duckdb_extension' // 数据库版本常量 @@ -72,7 +66,12 @@ export class KnowledgeDatabase implements KnowledgeDatabasePort { private readonly chunkTable = 'chunk' private readonly metadataTable = 'metadata' - constructor(dbPath: string) { + constructor( + dbPath: string, + private readonly extensionDirectory: string + ) { + if (!path.isAbsolute(extensionDirectory)) + throw new Error('Knowledge extension directory must be absolute') this.dbPath = dbPath } @@ -673,7 +672,7 @@ export class KnowledgeDatabase implements KnowledgeDatabasePort { const conn = await ins.connect() // load vss - const extensionPath = path.join(extensionDir, `vss${extensionSuffix}`) + const extensionPath = path.join(this.extensionDirectory, `vss${extensionSuffix}`) if (fs.existsSync(extensionPath)) { const escapedPath = extensionPath.replace(/\\/g, '\\\\') logger.info(`[DuckDB] LOAD VSS extension from ${escapedPath}`) @@ -699,7 +698,7 @@ export class KnowledgeDatabase implements KnowledgeDatabasePort { name: string, afterRun?: () => Promise ): Promise { - const extensionPath = path.join(extensionDir, `${name}${extensionSuffix}`) + const extensionPath = path.join(this.extensionDirectory, `${name}${extensionSuffix}`) if (fs.existsSync(extensionPath)) { const escapedPath = extensionPath.replace(/\\/g, '\\\\') logger.info(`[DuckDB] LOAD ${name} extension from ${escapedPath}`) diff --git a/src/main/knowledge/index.ts b/src/main/knowledge/index.ts index 8f3aeac8ec..653a88d4c9 100644 --- a/src/main/knowledge/index.ts +++ b/src/main/knowledge/index.ts @@ -22,6 +22,7 @@ import { } from '@/lib/textsplitters' import { isBuiltinKnowledgeSupported } from './support' import type { KnowledgeEventPublisher, KnowledgeServiceDeps } from './ports' +import type { DialogRequestParams } from '@shared/types/dialog' function diffKnowledgeConfigs( previous: BuiltinKnowledgeConfig[], @@ -44,6 +45,7 @@ export class KnowledgeService implements KnowledgeServicePort { * 知识库存储目录 */ private readonly storageDir + private readonly extensionDirectory: string private readonly configPort: KnowledgeServiceDeps['config'] @@ -51,7 +53,6 @@ export class KnowledgeService implements KnowledgeServicePort { * File presenter for validation operations */ private readonly filePort: KnowledgeServiceDeps['files'] - private readonly dialogPort: KnowledgeServiceDeps['dialog'] private readonly embeddingPort: KnowledgeServiceDeps['embeddings'] private readonly events: KnowledgeEventPublisher @@ -72,10 +73,10 @@ export class KnowledgeService implements KnowledgeServicePort { logger.info('[RAG] Initializing Built-in Knowledge Service') this.configPort = deps.config this.filePort = deps.files - this.dialogPort = deps.dialog this.embeddingPort = deps.embeddings this.events = deps.events this.storageDir = path.join(deps.storageRoot, 'KnowledgeBase') + this.extensionDirectory = deps.extensionDirectory this.taskQueue = new KnowledgeTaskQueue() this.knowledgeBases = new Map() this.knowledgeBaseInitializations = new Map() @@ -303,12 +304,12 @@ export class KnowledgeService implements KnowledgeServicePort { ): Promise => { const dbPath = path.join(this.storageDir, id) if (fs.existsSync(dbPath)) { - const db = new KnowledgeDatabase(dbPath) + const db = new KnowledgeDatabase(dbPath, this.extensionDirectory) await db.open() return db } // 如果数据库不存在,则初始化 - const db = new KnowledgeDatabase(dbPath) + const db = new KnowledgeDatabase(dbPath, this.extensionDirectory) await db.initialize(dimensions, { metric: getMetric(normalized) }) @@ -367,15 +368,9 @@ export class KnowledgeService implements KnowledgeServicePort { this.knowledgeBaseInitializations.clear() } - /** - * @returns return true if user confirmed to destroy knowledge, otherwise false - */ - async confirmShutdown(): Promise { - const status = this.taskQueue.getStatus() - if (status.totalTasks === 0) { - return true - } - const choice = await this.dialogPort.showDialog({ + getShutdownConfirmation(): DialogRequestParams | null { + if (this.taskQueue.getStatus().totalTasks === 0) return null + return { title: 'settings.knowledgeBase.dialog.beforequit.title', description: 'settings.knowledgeBase.dialog.beforequit.description', icon: DIALOG_WARN, @@ -385,8 +380,7 @@ export class KnowledgeService implements KnowledgeServicePort { ], timeout: 10000, i18n: true - }) - return choice === 'confirm' + } } async destroy(): Promise { diff --git a/src/main/knowledge/ports.ts b/src/main/knowledge/ports.ts index 825416a770..3b011208cf 100644 --- a/src/main/knowledge/ports.ts +++ b/src/main/knowledge/ports.ts @@ -83,17 +83,6 @@ export interface KnowledgeEmbeddingPort { ): Promise } -export interface KnowledgeDialogPort { - showDialog(request: { - title: string - description?: string - i18n?: boolean - icon?: { icon: string; class: string } - buttons?: Array<{ key: string; label: string; default?: boolean }> - timeout?: number - }): Promise -} - export interface KnowledgeEventPublisher { publishFileUpdated(file: KnowledgeFileMessage): void publishFileProgress( @@ -105,8 +94,8 @@ export interface KnowledgeEventPublisher { export type KnowledgeServiceDeps = { config: KnowledgeConfigPort storageRoot: string + extensionDirectory: string files: KnowledgeFilePort - dialog: KnowledgeDialogPort embeddings: KnowledgeEmbeddingPort events: KnowledgeEventPublisher } diff --git a/src/main/lib/runtimeHelper.ts b/src/main/lib/runtimeHelper.ts index a54c837613..551ad5b069 100644 --- a/src/main/lib/runtimeHelper.ts +++ b/src/main/lib/runtimeHelper.ts @@ -1,115 +1,35 @@ -import logger from '@shared/logger' -import { app } from 'electron' -import * as path from 'path' +export function expandRuntimePath(inputPath: string, homeDirectory: string): string { + let expandedPath = inputPath -/** - * Utilities for environment paths and platform-specific installation locations. - */ -export class RuntimeHelper { - private static instance: RuntimeHelper | null = null - - private constructor() { - // Private constructor to enforce singleton pattern - } - - /** - * Get the singleton instance of RuntimeHelper - */ - public static getInstance(): RuntimeHelper { - if (!RuntimeHelper.instance) { - RuntimeHelper.instance = new RuntimeHelper() - } - return RuntimeHelper.instance - } - - /** - * Expand various symbols and variables in paths - * @param inputPath Input path that may contain ~ or environment variables - * @returns Expanded path - */ - public expandPath(inputPath: string): string { - let expandedPath = inputPath - - // Handle ~ symbol (user home directory) - if (expandedPath.startsWith('~/') || expandedPath === '~') { - const homeDir = app.getPath('home') - expandedPath = expandedPath.replace('~', homeDir) - } - - // Handle environment variable expansion - expandedPath = expandedPath.replace(/\$\{([^}]+)\}/g, (match, varName) => { - return process.env[varName] || match - }) - - // Handle simple $VAR format (without braces) - expandedPath = expandedPath.replace(/\$([A-Z_][A-Z0-9_]*)/g, (match, varName) => { - return process.env[varName] || match - }) - - return expandedPath - } - - /** - * Get system-specific default paths - * @param homeDir User home directory - * @returns Array of default system paths - */ - public getDefaultPaths(homeDir: string): string[] { - if (process.platform === 'darwin') { - return [ - '/bin', - '/usr/bin', - '/usr/local/bin', - '/usr/local/sbin', - '/opt/homebrew/bin', - '/opt/homebrew/sbin', - '/usr/local/opt/node/bin', - '/opt/local/bin', - `${homeDir}/.cargo/bin` - ] - } else if (process.platform === 'linux') { - return ['/bin', '/usr/bin', '/usr/local/bin', `${homeDir}/.cargo/bin`] - } else { - // Windows - return [`${homeDir}\\.cargo\\bin`, `${homeDir}\\.local\\bin`] - } + if (expandedPath.startsWith('~/') || expandedPath === '~') { + expandedPath = expandedPath.replace('~', homeDirectory) } - /** - * Check if the application is installed in a Windows system directory - * System directories include Program Files and Program Files (x86) - * @returns true if installed in system directory, false otherwise - */ - public isInstalledInSystemDirectory(): boolean { - if (process.platform !== 'win32') { - return false - } - - const appPath = app.getAppPath() - const normalizedPath = appPath.toLowerCase() + expandedPath = expandedPath.replace(/\$\{([^}]+)\}/g, (match, varName) => { + return process.env[varName] || match + }) - // Check if app is installed in Program Files or Program Files (x86) - const isSystemDir = - normalizedPath.includes('program files') || normalizedPath.includes('program files (x86)') - - if (isSystemDir) { - logger.info('[RuntimeHelper] Application is installed in system directory:', appPath) - } + return expandedPath.replace(/\$([A-Z_][A-Z0-9_]*)/g, (match, varName) => { + return process.env[varName] || match + }) +} - return isSystemDir +export function getDefaultRuntimePaths(homeDirectory: string): string[] { + if (process.platform === 'darwin') { + return [ + '/bin', + '/usr/bin', + '/usr/local/bin', + '/usr/local/sbin', + '/opt/homebrew/bin', + '/opt/homebrew/sbin', + '/usr/local/opt/node/bin', + '/opt/local/bin', + `${homeDirectory}/.cargo/bin` + ] } - - /** - * Get user npm prefix path for Windows - * Returns the path where npm should install global packages when app is in system directory - * @returns User npm prefix path or null if not applicable - */ - public getUserNpmPrefix(): string | null { - if (process.platform !== 'win32') { - return null - } - - const appDataPath = app.getPath('appData') - return path.join(appDataPath, 'npm') + if (process.platform === 'linux') { + return ['/bin', '/usr/bin', '/usr/local/bin', `${homeDirectory}/.cargo/bin`] } + return [`${homeDirectory}\\.cargo\\bin`, `${homeDirectory}\\.local\\bin`] } diff --git a/src/main/mcp/apps/appHost.ts b/src/main/mcp/apps/appHost.ts index 88ca7bac4d..fb5c290327 100644 --- a/src/main/mcp/apps/appHost.ts +++ b/src/main/mcp/apps/appHost.ts @@ -1,5 +1,4 @@ import { createHash } from 'node:crypto' -import { shell } from 'electron' import type { PermissionMode } from '@shared/types/agent-interface' import type { MCPContentItem, @@ -24,11 +23,13 @@ import type { ToolPermissionBroker } from '@/tool/permission' import type { McpAppSandboxInstance, McpAppSandboxRegistry, - McpAppRouteContext + McpAppRouteContext, + McpAppDesktopPort } from './sandboxRegistry' import { MCP_APP_SCHEME } from './sandboxRegistry' import { assertBoundedMcpJson } from '../schemaValidation' -import { resolvePluginToolPolicy } from '@/plugin/toolPolicyStore' +import { awaitWithAbort } from '@/lib/awaitWithAbort' +import type { PluginToolPolicyStore } from '@/plugin/toolPolicyStore' const MCP_APP_RESOURCE_MIME_TYPE = 'text/html;profile=mcp-app' const MAX_APP_HTML_BYTES = 2 * 1024 * 1024 @@ -188,10 +189,12 @@ type BoundServer = { export class McpAppHost implements McpAppHostPort { constructor( private readonly deps: { + toolPolicies: Pick settings: McpSettings serverManager: ServerManager permissionBroker: ToolPermissionBroker registry: McpAppSandboxRegistry + desktop: McpAppDesktopPort ensureServerRunning(serverName: string): Promise getPermissionMode(conversationId: string): Promise validateSource(input: { @@ -229,59 +232,75 @@ export class McpAppHost implements McpAppHostPort { if (!this.deps.validateSource(input)) { throw new Error('The MCP App source no longer matches the saved tool result') } - const bound = await this.resolveBoundServer(input.descriptor) - const tools = await bound.client.listTools() - await this.assertBoundServerCurrent(input.descriptor, bound) - const tool = tools.find((entry) => entry.name === input.descriptor.toolName) - if (!tool || getToolUiResourceUri(tool) !== input.descriptor.resourceUri) { - throw new Error('MCP App tool declaration no longer matches the saved result') - } + const creationId = this.deps.registry.reserveCreation(context, input.descriptor.serverId) + const signal = this.deps.registry.getCreationSignal(creationId) + try { + await awaitWithAbort(this.deps.desktop.observeOwner(context), signal) + this.deps.registry.assertCreationCurrent(creationId) + if (!this.deps.validateSource(input)) { + throw new Error('The MCP App source no longer matches the saved tool result') + } + const bound = await awaitWithAbort(this.resolveBoundServer(input.descriptor), signal) + const tools = await awaitWithAbort(bound.client.listTools({ signal }), signal) + await awaitWithAbort(this.assertBoundServerCurrent(input.descriptor, bound), signal) + const tool = tools.find((entry) => entry.name === input.descriptor.toolName) + if (!tool || getToolUiResourceUri(tool) !== input.descriptor.resourceUri) { + throw new Error('MCP App tool declaration no longer matches the saved result') + } - const resources = await bound.client.readResourceContents(input.descriptor.resourceUri) - await this.assertBoundServerCurrent(input.descriptor, bound) - const matching = resources.filter((entry) => entry.uri === input.descriptor.resourceUri) - if (matching.length !== 1) { - throw new Error('MCP App resource must return exactly one matching content item') - } - const resource = matching[0] - if (resource.mimeType !== MCP_APP_RESOURCE_MIME_TYPE) { - throw new Error(`Unsupported MCP App resource MIME type: ${resource.mimeType ?? 'missing'}`) - } + const resources = await awaitWithAbort( + bound.client.readResourceContents(input.descriptor.resourceUri, signal), + signal + ) + await awaitWithAbort(this.assertBoundServerCurrent(input.descriptor, bound), signal) + const matching = resources.filter((entry) => entry.uri === input.descriptor.resourceUri) + if (matching.length !== 1) { + throw new Error('MCP App resource must return exactly one matching content item') + } + const resource = matching[0] + if (resource.mimeType !== MCP_APP_RESOURCE_MIME_TYPE) { + throw new Error(`Unsupported MCP App resource MIME type: ${resource.mimeType ?? 'missing'}`) + } - const html = decodeResourceHtml(resource) - const uiMeta = - readUiMeta(resource) ?? - (await this.findListedResourceUiMeta(bound.client, input.descriptor.resourceUri)) - await this.assertBoundServerCurrent(input.descriptor, bound) - if (!this.deps.validateSource(input)) { - throw new Error('The MCP App source no longer matches the saved tool result') - } - const instance = this.deps.registry.create({ - context, - conversationId: input.conversationId, - messageId: input.messageId, - blockId: input.blockId, - descriptor: { ...input.descriptor, serverName: bound.serverName }, - toolInput: input.toolInput, - html, - csp: normalizeCsp(uiMeta?.csp), - permissions: normalizePermissions(uiMeta?.permissions), - prefersBorder: typeof uiMeta?.prefersBorder === 'boolean' ? uiMeta.prefersBorder : undefined, - advisoryDomain: normalizeAdvisoryDomain(uiMeta?.domain), - validateLive: () => this.deps.validateSource(input) - }) + const html = decodeResourceHtml(resource) + const uiMeta = + readUiMeta(resource) ?? + (await this.findListedResourceUiMeta(bound.client, input.descriptor.resourceUri, signal)) + await awaitWithAbort(this.assertBoundServerCurrent(input.descriptor, bound), signal) + if (!this.deps.validateSource(input)) { + throw new Error('The MCP App source no longer matches the saved tool result') + } + const instance = this.deps.registry.create({ + creationId, + context, + conversationId: input.conversationId, + messageId: input.messageId, + blockId: input.blockId, + descriptor: { ...input.descriptor, serverName: bound.serverName }, + toolInput: input.toolInput, + html, + csp: normalizeCsp(uiMeta?.csp), + permissions: normalizePermissions(uiMeta?.permissions), + prefersBorder: + typeof uiMeta?.prefersBorder === 'boolean' ? uiMeta.prefersBorder : undefined, + advisoryDomain: normalizeAdvisoryDomain(uiMeta?.domain), + validateLive: () => this.deps.validateSource(input) + }) - return { - instanceId: instance.instanceId, - sandboxUrl: `${MCP_APP_SCHEME}://${instance.instanceId}/sandbox.html`, - html: instance.html, - sandbox: 'allow-scripts allow-same-origin', - tool, - csp: instance.csp, - permissions: instance.permissions, - prefersBorder: instance.prefersBorder, - advisoryDomain: instance.advisoryDomain, - expiresAt: instance.expiresAt + return { + instanceId: instance.instanceId, + sandboxUrl: `${MCP_APP_SCHEME}://${instance.instanceId}/sandbox.html`, + html: instance.html, + sandbox: 'allow-scripts allow-same-origin', + tool, + csp: instance.csp, + permissions: instance.permissions, + prefersBorder: instance.prefersBorder, + advisoryDomain: instance.advisoryDomain, + expiresAt: instance.expiresAt + } + } finally { + this.deps.registry.cancelCreation(creationId) } } @@ -539,7 +558,7 @@ export class McpAppHost implements McpAppHostPort { } this.assertLiveInstance(instanceId, context) await this.assertDescriptorCurrent(instance.descriptor) - await shell.openExternal(normalizedUrl) + await this.deps.desktop.openExternal(normalizedUrl, context) return true } @@ -700,18 +719,19 @@ export class McpAppHost implements McpAppHostPort { } private isToolAllowedByPluginPolicy(serverName: string, toolName: string): boolean { - const policy = resolvePluginToolPolicy(serverName, toolName) + const policy = this.deps.toolPolicies.resolve(serverName, toolName) return !policy.managed || policy.decision === 'allow' || policy.decision === 'ask' } private async findListedResourceUiMeta( client: BoundServer['client'], - resourceUri: string + resourceUri: string, + signal: AbortSignal ): Promise | undefined> { try { let cursor: string | undefined for (let page = 0; page < MAX_RESOURCE_LIST_PAGES; page += 1) { - const result = await client.listResourcesPage(cursor) + const result = await awaitWithAbort(client.listResourcesPage(cursor, signal), signal) const resource = result.resources.find((entry) => entry.uri === resourceUri) if (resource) { return readUiMeta(resource as Resource) @@ -723,6 +743,7 @@ export class McpAppHost implements McpAppHostPort { } return undefined } catch { + signal.throwIfAborted() return undefined } } diff --git a/src/main/mcp/apps/sandboxProtocol.ts b/src/main/mcp/apps/sandboxProtocol.ts index 4169786528..9739c4dfae 100644 --- a/src/main/mcp/apps/sandboxProtocol.ts +++ b/src/main/mcp/apps/sandboxProtocol.ts @@ -1,6 +1,11 @@ -import { protocol } from 'electron' -import type { McpAppCsp, McpAppPermissions } from '@shared/types/mcp' -import { MCP_APP_SCHEME, type McpAppSandboxRegistry } from './sandboxRegistry' +import { protocol, session, webContents, BrowserWindow, shell, type Session } from 'electron' +import type { McpAppConsentKind, McpAppCsp, McpAppPermissions } from '@shared/types/mcp' +import { + MCP_APP_SCHEME, + type McpAppSandboxPort, + type McpAppRouteContext, + type McpAppDesktopPort +} from './sandboxRegistry' let schemeRegistered = false @@ -195,8 +200,8 @@ export function registerMcpAppScheme(): void { schemeRegistered = true } -export function registerMcpAppProtocol(registry: McpAppSandboxRegistry): void { - protocol.handle(MCP_APP_SCHEME, (request) => { +export function registerMcpAppProtocol(registry: McpAppSandboxPort): McpAppDesktopPort { + protocol.handle(MCP_APP_SCHEME, async (request) => { let instanceId = '' try { const url = new URL(request.url) @@ -211,8 +216,14 @@ export function registerMcpAppProtocol(registry: McpAppSandboxRegistry): void { }) } - const instance = registry.getForProtocol(instanceId) - if (!instance) { + let policy: Awaited> = null + try { + policy = await registry.getProtocolPolicy(instanceId) + if (policy) requireNativeOwner(policy.context) + } catch { + policy = null + } + if (!policy) { return new Response('MCP App instance expired', { status: 410, headers: { @@ -225,12 +236,159 @@ export function registerMcpAppProtocol(registry: McpAppSandboxRegistry): void { return new Response(createSandboxProxyHtml(), { headers: { 'Cache-Control': 'no-store', - 'Content-Security-Policy': buildMcpAppContentSecurityPolicy(instance.csp), + 'Content-Security-Policy': buildMcpAppContentSecurityPolicy(policy.csp), 'Content-Type': 'text/html; charset=utf-8', - 'Permissions-Policy': buildMcpAppPermissionsPolicy(instance.permissions), + 'Permissions-Policy': buildMcpAppPermissionsPolicy(policy.permissions), 'X-Content-Type-Options': 'nosniff' } }) }) - registry.configureDefaultSessionPermissions() + configureMcpAppSessionPermissions(registry) + return { + observeOwner: async (context) => { + requireNativeOwner(context) + }, + openExternal: async (url, context) => { + requireNativeOwner(context) + await shell.openExternal(url) + } + } +} + +type PermissionRequestHandler = NonNullable[0]> +type PermissionCheckHandler = NonNullable[0]> +type ElectronPermission = + | Parameters[1] + | Parameters[1] + +const isFirstPartyRendererUrl = (value: string): boolean => { + try { + const url = new URL(value) + if (url.protocol === 'file:') { + return true + } + return ( + (url.protocol === 'http:' || url.protocol === 'https:') && + (url.hostname === 'localhost' || url.hostname === '127.0.0.1') + ) + } catch { + return false + } +} + +const parseMcpAppInstanceId = (value: string | undefined): string | null => { + if (!value) { + return null + } + try { + const url = new URL(value) + return url.protocol === `${MCP_APP_SCHEME}:` && url.hostname ? url.hostname : null + } catch { + return null + } +} + +const resolvePermissionKinds = ( + permission: ElectronPermission, + mediaTypes: string[] | undefined +): McpAppConsentKind[] => { + if (permission === 'geolocation') { + return ['geolocation'] + } + if (permission === 'clipboard-sanitized-write') { + return ['clipboard-write'] + } + if (permission !== 'media') { + return [] + } + const kinds: McpAppConsentKind[] = [] + if (mediaTypes?.includes('video')) { + kinds.push('camera') + } + if (mediaTypes?.includes('audio')) { + kinds.push('microphone') + } + return kinds +} + +const configuredSessions = new WeakMap() + +const requireNativeOwner = (context: McpAppRouteContext) => { + const owner = webContents.fromId(context.webContentsId) + if ( + !owner || + owner.isDestroyed() || + (BrowserWindow.fromWebContents(owner)?.id ?? null) !== context.windowId + ) + throw new Error('MCP App owner is unavailable') + return owner +} + +export function configureMcpAppSessionPermissions( + registry: McpAppSandboxPort, + target: Session = session.defaultSession +): void { + if (configuredSessions.get(target) === registry) return + configuredSessions.set(target, registry) + target.setPermissionRequestHandler((owner, permission, callback, details) => { + const requestingUrl = details.requestingUrl || owner.getURL() + const securityOrigin = + 'securityOrigin' in details && typeof details.securityOrigin === 'string' + ? details.securityOrigin + : undefined + const instanceId = parseMcpAppInstanceId(requestingUrl) ?? parseMcpAppInstanceId(securityOrigin) + if (!instanceId) { + const ownerUrl = owner.getURL() + callback(!isFirstPartyRendererUrl(ownerUrl) || isFirstPartyRendererUrl(requestingUrl)) + return + } + + const mediaTypes = + 'mediaTypes' in details && Array.isArray(details.mediaTypes) + ? details.mediaTypes.map(String) + : undefined + const kinds = resolvePermissionKinds(permission, mediaTypes) + if (kinds.length === 0) { + callback(false) + return + } + const context = { + webContentsId: owner.id, + windowId: BrowserWindow.fromWebContents(owner)?.id ?? null + } + let settled = false + const complete = (allowed: boolean): void => { + if (settled) return + settled = true + callback(allowed) + } + void registry + .requestBrowserPermission(instanceId, context, kinds) + .then((approved) => { + try { + requireNativeOwner(context) + complete(approved) + } catch { + complete(false) + } + }) + .catch(() => complete(false)) + }) + + target.setPermissionCheckHandler((owner, _permission, requestingOrigin, details) => { + const requestingUrl = + requestingOrigin || details.requestingUrl || details.securityOrigin || owner?.getURL() || '' + const instanceId = + parseMcpAppInstanceId(requestingUrl) ?? + parseMcpAppInstanceId(details.securityOrigin) ?? + parseMcpAppInstanceId(details.requestingUrl) + if (!instanceId) { + const ownerUrl = owner?.getURL() + return ( + !ownerUrl || !isFirstPartyRendererUrl(ownerUrl) || isFirstPartyRendererUrl(requestingUrl) + ) + } + // MCP App browser permissions are request-scoped; there is no standing grant to check. + return false + }) } diff --git a/src/main/mcp/apps/sandboxRegistry.ts b/src/main/mcp/apps/sandboxRegistry.ts index 5c7450cff2..5ed211277e 100644 --- a/src/main/mcp/apps/sandboxRegistry.ts +++ b/src/main/mcp/apps/sandboxRegistry.ts @@ -1,5 +1,4 @@ import { randomBytes, randomUUID } from 'node:crypto' -import { session, webContents, type Session } from 'electron' import type { McpAppConsentKind, McpAppConsentRequestPayload, @@ -14,12 +13,6 @@ const MAX_PENDING_CONSENTS = 64 const MAX_SANDBOX_INSTANCES = 64 const MAX_SANDBOX_INSTANCES_PER_WEB_CONTENTS = 32 -type PermissionRequestHandler = NonNullable[0]> -type PermissionCheckHandler = NonNullable[0]> -type ElectronPermission = - | Parameters[1] - | Parameters[1] - export const MCP_APP_SCHEME = 'mcp-app' export type McpAppRouteContext = { @@ -27,6 +20,16 @@ export type McpAppRouteContext = { windowId: number | null } +export type McpAppDesktopPort = { + observeOwner(context: McpAppRouteContext): Promise + openExternal(url: string, context: McpAppRouteContext): Promise +} + +export type McpAppSandboxPort = Pick< + McpAppSandboxRegistry, + 'getProtocolPolicy' | 'requestBrowserPermission' +> + export type McpAppSandboxInstance = { instanceId: string webContentsId: number @@ -62,56 +65,6 @@ type ConsentPublisher = ( payload: { request: McpAppConsentRequestPayload; version: number } ) => void -const isFirstPartyRendererUrl = (value: string): boolean => { - try { - const url = new URL(value) - if (url.protocol === 'file:') { - return true - } - return ( - (url.protocol === 'http:' || url.protocol === 'https:') && - (url.hostname === 'localhost' || url.hostname === '127.0.0.1') - ) - } catch { - return false - } -} - -const parseMcpAppInstanceId = (value: string | undefined): string | null => { - if (!value) { - return null - } - try { - const url = new URL(value) - return url.protocol === `${MCP_APP_SCHEME}:` && url.hostname ? url.hostname : null - } catch { - return null - } -} - -const resolvePermissionKinds = ( - permission: ElectronPermission, - mediaTypes: string[] | undefined -): McpAppConsentKind[] => { - if (permission === 'geolocation') { - return ['geolocation'] - } - if (permission === 'clipboard-sanitized-write') { - return ['clipboard-write'] - } - if (permission !== 'media') { - return [] - } - const kinds: McpAppConsentKind[] = [] - if (mediaTypes?.includes('video')) { - kinds.push('camera') - } - if (mediaTypes?.includes('audio')) { - kinds.push('microphone') - } - return kinds -} - const hasDeclaredPermission = ( permissions: McpAppPermissions | undefined, kind: McpAppConsentKind @@ -131,18 +84,120 @@ const hasDeclaredPermission = ( } export class McpAppSandboxRegistry { + private closed = false + private readonly owners = new Map void>() private readonly instances = new Map() private readonly liveValidators = new Map boolean>() private readonly pendingConsents = new Map() - private readonly observedWebContents = new Set() + private readonly creations = new Map< + string, + { context: McpAppRouteContext; serverId: string; controller: AbortController } + >() private publishConsent?: ConsentPublisher - private configuredSession: Session | null = null + + constructor( + private readonly onRendererDestroyed: ( + webContentsId: number, + listener: () => void + ) => () => void + ) {} setConsentPublisher(publisher: ConsentPublisher): void { this.publishConsent = publisher } + reserveCreation(context: McpAppRouteContext, serverId: string): string { + if (this.closed) throw new Error('MCP App sandbox registry is closed') + this.pruneExpired() + const owned = + Array.from(this.instances.values()).filter( + (instance) => instance.webContentsId === context.webContentsId + ).length + + Array.from(this.creations.values()).filter( + (creation) => creation.context.webContentsId === context.webContentsId + ).length + if ( + this.instances.size + this.creations.size >= MAX_SANDBOX_INSTANCES || + owned >= MAX_SANDBOX_INSTANCES_PER_WEB_CONTENTS + ) + throw new Error('MCP App sandbox instance limit reached') + const id = randomUUID() + this.creations.set(id, { context: { ...context }, serverId, controller: new AbortController() }) + try { + if (!this.owners.has(context.webContentsId)) { + let destroyed = false + const unsubscribe = this.onRendererDestroyed(context.webContentsId, () => { + destroyed = true + this.revokeByWebContents(context.webContentsId) + }) + if (destroyed) unsubscribe() + else this.owners.set(context.webContentsId, unsubscribe) + } + this.assertCreationCurrent(id) + } catch (error) { + this.cancelCreation(id) + throw error + } + return id + } + + assertCreationCurrent(creationId: string): void { + if (!this.creations.has(creationId)) { + throw new Error('MCP App view creation is no longer active') + } + } + + cancelCreation(creationId: string): void { + const creation = this.creations.get(creationId) + this.creations.delete(creationId) + creation?.controller.abort(new Error('MCP App view creation is no longer active')) + if (creation) this.releaseUnusedOwner(creation.context.webContentsId) + } + + getCreationSignal(creationId: string): AbortSignal { + this.assertCreationCurrent(creationId) + return this.creations.get(creationId)!.controller.signal + } + + async getProtocolPolicy(instanceId: string): Promise<{ + context: McpAppRouteContext + csp?: McpAppCsp + permissions?: McpAppPermissions + } | null> { + const instance = this.getForProtocol(instanceId) + if (!instance) return null + return structuredClone({ + context: { webContentsId: instance.webContentsId, windowId: instance.windowId }, + csp: instance.csp, + permissions: instance.permissions + }) + } + + async requestBrowserPermission( + instanceId: string, + context: McpAppRouteContext, + kinds: readonly McpAppConsentKind[] + ): Promise { + const instance = this.getForProtocol(instanceId) + if ( + !instance || + instance.webContentsId !== context.webContentsId || + instance.windowId !== context.windowId || + kinds.length === 0 || + kinds.length > 4 || + kinds.some((kind) => !hasDeclaredPermission(instance.permissions, kind)) + ) + return false + const decisions = await Promise.all( + kinds.map((kind) => + this.requestConsent(instance, { kind, title: kind, detail: instance.descriptor.toolName }) + ) + ) + return decisions.every(Boolean) && this.getForProtocol(instanceId) === instance + } + create(input: { + creationId: string context: McpAppRouteContext conversationId: string messageId: string @@ -156,6 +211,14 @@ export class McpAppSandboxRegistry { advisoryDomain?: string validateLive(): boolean }): McpAppSandboxInstance { + this.assertCreationCurrent(input.creationId) + const creation = this.creations.get(input.creationId)! + if ( + creation.context.webContentsId !== input.context.webContentsId || + creation.context.windowId !== input.context.windowId || + creation.serverId !== input.descriptor.serverId + ) + throw new Error('MCP App creation binding changed') this.pruneExpired() const ownerInstanceCount = Array.from(this.instances.values()).filter( (instance) => instance.webContentsId === input.context.webContentsId @@ -184,20 +247,10 @@ export class McpAppSandboxRegistry { expiresAt: Date.now() + DEFAULT_INSTANCE_TTL_MS, toolAccessSuspended: false } + this.creations.delete(input.creationId) this.instances.set(instanceId, instance) this.liveValidators.set(instanceId, input.validateLive) - if (!this.observedWebContents.has(input.context.webContentsId)) { - const owner = webContents.fromId(input.context.webContentsId) - if (owner) { - this.observedWebContents.add(input.context.webContentsId) - owner.once('destroyed', () => { - this.revokeByWebContents(input.context.webContentsId) - this.observedWebContents.delete(input.context.webContentsId) - }) - } - } - return instance } @@ -244,9 +297,13 @@ export class McpAppSandboxRegistry { this.deletePendingConsent(pending.requestId) } } + this.releaseUnusedOwner(instance.webContentsId) } revokeByWebContents(webContentsId: number): void { + for (const [id, creation] of this.creations) { + if (creation.context.webContentsId === webContentsId) this.cancelCreation(id) + } for (const instance of this.instances.values()) { if (instance.webContentsId === webContentsId) { this.revoke(instance.instanceId) @@ -255,6 +312,9 @@ export class McpAppSandboxRegistry { } revokeByServer(serverId: string): void { + for (const [id, creation] of this.creations) { + if (creation.serverId === serverId) this.cancelCreation(id) + } for (const instance of this.instances.values()) { if (instance.descriptor.serverId === serverId) { this.revoke(instance.instanceId) @@ -263,11 +323,17 @@ export class McpAppSandboxRegistry { } clear(): void { + for (const id of this.creations.keys()) this.cancelCreation(id) for (const instanceId of this.instances.keys()) { this.revoke(instanceId) } } + close(): void { + this.closed = true + this.clear() + } + async requestConsent( instance: McpAppSandboxInstance, input: { @@ -368,72 +434,6 @@ export class McpAppSandboxRegistry { return true } - configureDefaultSessionPermissions(target: Session = session.defaultSession): void { - if (this.configuredSession === target) { - return - } - this.configuredSession = target - - target.setPermissionRequestHandler((owner, permission, callback, details) => { - const requestingUrl = details.requestingUrl || owner.getURL() - const securityOrigin = - 'securityOrigin' in details && typeof details.securityOrigin === 'string' - ? details.securityOrigin - : undefined - const instanceId = - parseMcpAppInstanceId(requestingUrl) ?? parseMcpAppInstanceId(securityOrigin) - if (!instanceId) { - const ownerUrl = owner.getURL() - callback(!isFirstPartyRendererUrl(ownerUrl) || isFirstPartyRendererUrl(requestingUrl)) - return - } - - const instance = this.getForProtocol(instanceId) - const mediaTypes = - 'mediaTypes' in details && Array.isArray(details.mediaTypes) - ? details.mediaTypes.map(String) - : undefined - const kinds = resolvePermissionKinds(permission, mediaTypes) - if ( - !instance || - instance.webContentsId !== owner.id || - kinds.length === 0 || - kinds.some((kind) => !hasDeclaredPermission(instance.permissions, kind)) - ) { - callback(false) - return - } - void Promise.all( - kinds.map((kind) => - this.requestConsent(instance, { - kind, - title: kind, - detail: instance.descriptor.toolName - }) - ) - ) - .then((decisions) => callback(decisions.every(Boolean))) - .catch(() => callback(false)) - }) - - target.setPermissionCheckHandler((owner, _permission, requestingOrigin, details) => { - const requestingUrl = - requestingOrigin || details.requestingUrl || details.securityOrigin || owner?.getURL() || '' - const instanceId = - parseMcpAppInstanceId(requestingUrl) ?? - parseMcpAppInstanceId(details.securityOrigin) ?? - parseMcpAppInstanceId(details.requestingUrl) - if (!instanceId) { - const ownerUrl = owner?.getURL() - return ( - !ownerUrl || !isFirstPartyRendererUrl(ownerUrl) || isFirstPartyRendererUrl(requestingUrl) - ) - } - // MCP App browser permissions are request-scoped; there is no standing grant to check. - return false - }) - } - private pruneExpired(): void { const now = Date.now() for (const instance of this.instances.values()) { @@ -451,4 +451,17 @@ export class McpAppSandboxRegistry { clearTimeout(pending.timeout) this.pendingConsents.delete(requestId) } + + private releaseUnusedOwner(webContentsId: number): void { + if ( + Array.from(this.creations.values()).some( + (item) => item.context.webContentsId === webContentsId + ) || + Array.from(this.instances.values()).some((item) => item.webContentsId === webContentsId) + ) + return + const unsubscribe = this.owners.get(webContentsId) + this.owners.delete(webContentsId) + unsubscribe?.() + } } diff --git a/src/main/mcp/enterpriseIdentityManager.ts b/src/main/mcp/enterpriseIdentityManager.ts index d34c2e6237..3e3b9339b5 100644 --- a/src/main/mcp/enterpriseIdentityManager.ts +++ b/src/main/mcp/enterpriseIdentityManager.ts @@ -1,5 +1,4 @@ import { createHash, createPublicKey, randomBytes, verify as verifySignature } from 'node:crypto' -import { shell } from 'electron' import { CrossAppAccessProvider, discoverAndRequestJwtAuthGrant, @@ -20,7 +19,7 @@ import { import { MCP_OAUTH_CALLBACK_TIMEOUT_MS, MCP_OAUTH_REDIRECT_PORT } from './oauthConstants' import type { McpSettings } from './settings' import { - McpOAuthCredentialStore, + type McpOAuthCredentialStore, type McpEnterpriseIdentityCredential } from './oauthCredentialStore' @@ -43,6 +42,7 @@ type PendingEnterpriseFlow = { metadata: OpenIdMetadata callbackSession: OAuthLoopbackCallbackSession flowPromise?: Promise + assertActive(): void } type JwtHeader = { @@ -238,13 +238,55 @@ const statusError = (error: unknown): string => { export class McpEnterpriseIdentityManager { private readonly pendingFlows = new Map() private readonly statuses = new Map() + private stopped = false + private readonly generations = new Map() + + private invalidate(id?: string): void { + if (id) { + this.generations.set(id, (this.generations.get(id) ?? 0) + 1) + this.cancelPendingFlow(id) + this.statuses.delete(id) + } + } + + private captureOperation(id?: string): () => void { + const generation = id ? (this.generations.get(id) ?? 0) : 0 + const check = () => { + if (this.stopped || (id && generation !== (this.generations.get(id) ?? 0))) + throw new Error('MCP enterprise identity operation interrupted') + } + check() + return check + } + + private captureProfile(profile: McpEnterpriseIdentityProfile): () => void { + const check = this.captureOperation(profile.id) + const assertCurrent = () => { + check() + if (!sameProfileBinding(this.requireProfile(profile.id), profile)) + throw new Error('MCP enterprise identity profile changed') + } + assertCurrent() + return assertCurrent + } + + stop(): void { + this.stopped = true + for (const id of this.pendingFlows.keys()) this.cancelPendingFlow(id) + this.statuses.clear() + this.generations.clear() + } constructor( private readonly settings: McpSettings, private readonly store: McpOAuthCredentialStore, - private readonly publishEvent: DeepchatEventPublisher + private readonly publishEvent: DeepchatEventPublisher, + private readonly openExternal: (url: string) => Promise, + private readonly onProfileInvalidated?: ( + profileId: string, + assertCurrent: () => void + ) => Promise ) {} - listProfiles(): McpEnterpriseIdentityProfile[] { return this.settings.getEnterpriseIdentityProfiles().flatMap((profile) => { try { @@ -254,196 +296,273 @@ export class McpEnterpriseIdentityManager { } }) } - - saveProfile(input: McpEnterpriseIdentityProfile): McpEnterpriseIdentityProfile { + async saveProfile(input: McpEnterpriseIdentityProfile): Promise { const profile = this.normalizeProfile(input) - const profiles = this.listProfiles() - const existing = profiles.find((item) => item.id === profile.id) - if (existing && !sameProfileBinding(existing, profile)) { + const existing = this.listProfiles().find((item) => item.id === profile.id) + const bindingChanged = Boolean(existing && !sameProfileBinding(existing, profile)) + if (bindingChanged) { + this.invalidate(profile.id) this.cancelPendingFlow(profile.id) - this.store.clearEnterpriseProfileCredentials(profile.id) - this.statuses.delete(profile.id) } - const next = [...profiles.filter((item) => item.id !== profile.id), profile].sort((a, b) => - a.label.localeCompare(b.label) - ) - this.settings.setEnterpriseIdentityProfiles(next) - return profile + const assertCurrent = this.captureOperation(profile.id) + try { + if (bindingChanged) { + assertCurrent() + await this.store.clearEnterpriseProfileCredentials(profile.id) + } + assertCurrent() + const profiles = this.listProfiles() + const current = profiles.find((item) => item.id === profile.id) + if ( + bindingChanged && + (!current || !sameProfileBinding(existing!, current) || existing!.label !== current.label) + ) + throw new Error('MCP enterprise identity profile changed') + const next = [...profiles.filter((item) => item.id !== profile.id), profile].sort( + (left, right) => left.label.localeCompare(right.label) + ) + assertCurrent() + this.settings.setEnterpriseIdentityProfiles(next) + if (bindingChanged) { + this.statuses.delete(profile.id) + await this.onProfileInvalidated?.(profile.id, assertCurrent) + } + return profile + } finally { + assertCurrent() + } } - removeProfile(profileId: string): void { - this.cancelPendingFlow(profileId) - const next = this.listProfiles().filter((profile) => profile.id !== profileId) - this.settings.setEnterpriseIdentityProfiles(next) - this.store.clearEnterpriseProfileCredentials(profileId) - this.statuses.delete(profileId) + async removeProfile(profileId: string): Promise { + this.invalidate(profileId) + const assertCurrent = this.captureOperation(profileId) + try { + this.cancelPendingFlow(profileId) + assertCurrent() + await this.store.clearEnterpriseProfileCredentials(profileId) + assertCurrent() + assertCurrent() + this.settings.setEnterpriseIdentityProfiles( + this.listProfiles().filter((profile) => profile.id !== profileId) + ) + this.statuses.delete(profileId) + await this.onProfileInvalidated?.(profileId, assertCurrent) + } finally { + assertCurrent() + } } - - setClientSecret(profileId: string, secret: string): McpEnterpriseIdentityStatus { - const profile = this.requireProfile(profileId) - if (profile.clientAuthentication !== 'client_secret') { - throw new Error('Enterprise profile is not configured for client secret authentication') - } - if (!secret || secret.length > 8192) { - throw new Error('Enterprise identity client secret is invalid') - } - this.store.saveEnterpriseIdentityClientSecret( - profileKey('enterprise_identity_client_secret', profile), - { - profileId: profile.id, - issuer: profile.issuer, - clientId: profile.clientId, - secret + async setClientSecret(profileId: string, secret: string): Promise { + this.invalidate(profileId) + const assertCurrent = this.captureProfile(this.requireProfile(profileId)) + try { + const profile = this.requireProfile(profileId) + if (profile.clientAuthentication !== 'client_secret') { + throw new Error('Enterprise profile is not configured for client secret authentication') } - ) - return this.getStatus(profileId) + if (!secret || secret.length > 8192) { + throw new Error('Enterprise identity client secret is invalid') + } + assertCurrent() + await this.store.saveEnterpriseIdentityClientSecret( + profileKey('enterprise_identity_client_secret', profile), + { + profileId: profile.id, + issuer: profile.issuer, + clientId: profile.clientId, + secret + } + ) + assertCurrent() + return await this.getStatus(profileId) + } finally { + assertCurrent() + } } - - getStatus(profileId: string): McpEnterpriseIdentityStatus { - const profile = this.requireProfile(profileId) - const clientSecretConfigured = this.hasClientSecret(profile) - const pending = this.pendingFlows.get(profileId) - if (pending) { + async getStatus(profileId: string): Promise { + const assertCurrent = this.captureProfile(this.requireProfile(profileId)) + try { + const profile = this.requireProfile(profileId) + const clientSecretConfigured = await this.hasClientSecret(profile) + assertCurrent() + const pending = this.pendingFlows.get(profileId) + if (pending) { + return { + profileId, + state: 'authenticating', + authenticated: false, + persistent: await this.store.isPersistent(), + clientSecretConfigured + } + } + const credential = await this.loadIdentity(profile) + assertCurrent() + if (credential && (credential.expiresAt > Date.now() || Boolean(credential.refreshToken))) { + return { + profileId, + state: 'authenticated', + authenticated: true, + persistent: await this.store.isPersistent(), + clientSecretConfigured, + subjectLabel: credential.subjectLabel, + updatedAt: credential.updatedAt + } + } + const existing = this.statuses.get(profileId) + if (existing?.state === 'error') { + return existing + } return { profileId, - state: 'authenticating', + state: 'signed_out', authenticated: false, - persistent: this.store.isPersistent(), + persistent: await this.store.isPersistent(), clientSecretConfigured } - } - - const credential = this.loadIdentity(profile) - if (credential && (credential.expiresAt > Date.now() || Boolean(credential.refreshToken))) { - return { - profileId, - state: 'authenticated', - authenticated: true, - persistent: this.store.isPersistent(), - clientSecretConfigured, - subjectLabel: credential.subjectLabel, - updatedAt: credential.updatedAt - } - } - - const existing = this.statuses.get(profileId) - if (existing?.state === 'error') { - return existing - } - - return { - profileId, - state: 'signed_out', - authenticated: false, - persistent: this.store.isPersistent(), - clientSecretConfigured + } finally { + assertCurrent() } } - async startAuth(profileId: string): Promise { - const profile = this.requireProfile(profileId) - this.cancelPendingFlow(profileId) - let flow: PendingEnterpriseFlow | null = null - + this.invalidate(profileId) + const assertCurrent = this.captureProfile(this.requireProfile(profileId)) try { - const metadata = await this.discoverMetadata(profile) - const state = encodeBase64Url(randomBytes(24)) - const nonce = encodeBase64Url(randomBytes(24)) - const pkce = createPkcePair() - const callbackPath = `/mcp/enterprise/callback/${encodeBase64Url(randomBytes(12))}` - const callbackSession = await startOAuthLoopbackCallbackSession({ - expectedState: state, - path: callbackPath, - preferredPort: MCP_OAUTH_REDIRECT_PORT, - redirectHost: 'localhost', - timeoutMs: MCP_OAUTH_CALLBACK_TIMEOUT_MS, - invalidCallbackMessage: 'Invalid enterprise identity callback', - validateParameters: (parameters) => { - validateAuthorizationResponseIssuer({ - iss: parameters.get('iss') || undefined, - expectedIssuer: metadata.issuer, - issParameterSupported: metadata.authorization_response_iss_parameter_supported === true - }) + const profile = this.requireProfile(profileId) + this.cancelPendingFlow(profileId) + let flow: PendingEnterpriseFlow | null = null + let callbackSession: OAuthLoopbackCallbackSession | null = null + try { + const metadata = await this.discoverMetadata(profile) + assertCurrent() + const state = encodeBase64Url(randomBytes(24)) + const nonce = encodeBase64Url(randomBytes(24)) + const pkce = createPkcePair() + const callbackPath = `/mcp/enterprise/callback/${encodeBase64Url(randomBytes(12))}` + callbackSession = await startOAuthLoopbackCallbackSession({ + expectedState: state, + path: callbackPath, + preferredPort: MCP_OAUTH_REDIRECT_PORT, + redirectHost: 'localhost', + timeoutMs: MCP_OAUTH_CALLBACK_TIMEOUT_MS, + invalidCallbackMessage: 'Invalid enterprise identity callback', + validateParameters: (parameters) => { + validateAuthorizationResponseIssuer({ + iss: parameters.get('iss') || undefined, + expectedIssuer: metadata.issuer, + issParameterSupported: + metadata.authorization_response_iss_parameter_supported === true + }) + } + }) + assertCurrent() + const authorizationUrl = new URL(metadata.authorization_endpoint) + authorizationUrl.searchParams.set('client_id', profile.clientId) + authorizationUrl.searchParams.set('response_type', 'code') + authorizationUrl.searchParams.set('redirect_uri', callbackSession.redirectUri) + authorizationUrl.searchParams.set('scope', normalizeScopes(profile.scopes).join(' ')) + authorizationUrl.searchParams.set('state', state) + authorizationUrl.searchParams.set('nonce', nonce) + authorizationUrl.searchParams.set('code_challenge', pkce.challenge) + authorizationUrl.searchParams.set('code_challenge_method', 'S256') + flow = { + profile, + state, + nonce, + codeVerifier: pkce.verifier, + redirectUri: callbackSession.redirectUri, + metadata, + callbackSession, + assertActive: assertCurrent } - }) - const authorizationUrl = new URL(metadata.authorization_endpoint) - authorizationUrl.searchParams.set('client_id', profile.clientId) - authorizationUrl.searchParams.set('response_type', 'code') - authorizationUrl.searchParams.set('redirect_uri', callbackSession.redirectUri) - authorizationUrl.searchParams.set('scope', normalizeScopes(profile.scopes).join(' ')) - authorizationUrl.searchParams.set('state', state) - authorizationUrl.searchParams.set('nonce', nonce) - authorizationUrl.searchParams.set('code_challenge', pkce.challenge) - authorizationUrl.searchParams.set('code_challenge_method', 'S256') - - flow = { - profile, - state, - nonce, - codeVerifier: pkce.verifier, - redirectUri: callbackSession.redirectUri, - metadata, - callbackSession + assertCurrent() + this.pendingFlows.set(profileId, flow) + await this.setStatus( + { + profileId, + state: 'authenticating', + authenticated: false, + persistent: await this.store.isPersistent() + }, + assertCurrent + ) + assertCurrent() + flow.flowPromise = callbackSession + .waitForCallback() + .then(async (callback) => await this.finishAuth(flow!, callback.code)) + .catch(async (error) => await this.failAuth(flow!, error)) + assertCurrent() + await this.openExternal(authorizationUrl.toString()) + assertCurrent() + return await this.getStatus(profileId) + } catch (error) { + callbackSession?.close() + assertCurrent() + if (flow && this.pendingFlows.get(flow.profile.id) === flow) + this.cancelPendingFlow(flow.profile.id) + await this.setStatus( + { + profileId, + state: 'error', + authenticated: false, + persistent: await this.store.isPersistent(), + error: statusError(error) + }, + assertCurrent + ) + assertCurrent() + return await this.getStatus(profileId) } - this.pendingFlows.set(profileId, flow) - this.setStatus({ - profileId, - state: 'authenticating', - authenticated: false, - persistent: this.store.isPersistent() - }) - flow.flowPromise = callbackSession - .waitForCallback() - .then((callback) => this.finishAuth(flow!, callback.code)) - .catch((error) => this.failAuth(flow!, error)) - - await shell.openExternal(authorizationUrl.toString()) - return this.getStatus(profileId) - } catch (error) { - if (flow) { - this.cancelPendingFlow(flow.profile.id) - } - this.setStatus({ - profileId, - state: 'error', - authenticated: false, - persistent: this.store.isPersistent(), - error: statusError(error) - }) - return this.getStatus(profileId) + } finally { + assertCurrent() } } - async completeAuthFromCallbackUrl( profileId: string, callbackUrl: string ): Promise { - const flow = this.pendingFlows.get(profileId) - if (!flow) { - return this.getStatus(profileId) - } - const resolution = flow.callbackSession.resolveCallbackUrl(callbackUrl) - if (resolution.kind === 'not-found') { - this.failAuth(flow, new Error('Enterprise identity callback URL is invalid')) - return this.getStatus(profileId) + const assertCurrent = this.captureProfile(this.requireProfile(profileId)) + try { + const flow = this.pendingFlows.get(profileId) + if (!flow) { + return await this.getStatus(profileId) + } + const resolution = flow.callbackSession.resolveCallbackUrl(callbackUrl) + if (resolution.kind === 'not-found') { + await this.failAuth(flow, new Error('Enterprise identity callback URL is invalid')) + assertCurrent() + return await this.getStatus(profileId) + } + await flow.flowPromise + assertCurrent() + return await this.getStatus(profileId) + } finally { + assertCurrent() } - await flow.flowPromise - return this.getStatus(profileId) } - - logout(profileId: string): McpEnterpriseIdentityStatus { - const profile = this.requireProfile(profileId) - this.cancelPendingFlow(profileId) - this.store.clearEntry(profileKey('enterprise_identity', profile)) - this.setStatus({ - profileId, - state: 'signed_out', - authenticated: false, - persistent: this.store.isPersistent() - }) - return this.getStatus(profileId) + async logout(profileId: string): Promise { + this.invalidate(profileId) + const assertCurrent = this.captureProfile(this.requireProfile(profileId)) + try { + const profile = this.requireProfile(profileId) + this.cancelPendingFlow(profileId) + assertCurrent() + await this.store.clearEntry(profileKey('enterprise_identity', profile)) + assertCurrent() + await this.setStatus( + { + profileId, + state: 'signed_out', + authenticated: false, + persistent: await this.store.isPersistent() + }, + assertCurrent + ) + assertCurrent() + await this.onProfileInvalidated?.(profileId, assertCurrent) + return await this.getStatus(profileId) + } finally { + assertCurrent() + } } - async createCrossAppProvider( config: MCPServerConfig, targetClientSecret: string, @@ -455,25 +574,30 @@ export class McpEnterpriseIdentityManager { throw new Error('Enterprise authorization profile and target client ID are required') } const profile = this.requireProfile(profileId) - + this.captureProfile(profile) return new CrossAppAccessProvider({ clientId: targetClientId, clientSecret: targetClientSecret, clientName: 'DeepChat', expectedIssuer, assertion: async (context) => { + const assertProfileCurrent = this.captureProfile(profile) + assertProfileCurrent() const identity = await this.getValidIdentity(profile) + assertProfileCurrent() const idpClientSecret = profile.clientAuthentication === 'client_secret' - ? this.store.loadEnterpriseIdentityClientSecret( + ? (await this.store.loadEnterpriseIdentityClientSecret( profileKey('enterprise_identity_client_secret', profile) - ) || undefined + )) || undefined : undefined + assertProfileCurrent() if (profile.clientAuthentication === 'client_secret' && !idpClientSecret) { throw new Error('Enterprise identity client secret is not configured') } const fetchFn: FetchLike = (input, init) => context.fetchFn(input, { ...init, redirect: 'error' }) + assertProfileCurrent() const grant = await discoverAndRequestJwtAuthGrant({ idpUrl: profile.issuer, audience: context.authorizationServerUrl, @@ -484,56 +608,79 @@ export class McpEnterpriseIdentityManager { scope: context.scope, fetchFn }) + assertProfileCurrent() return grant.jwtAuthGrant } }) } - private async finishAuth(flow: PendingEnterpriseFlow, code: string): Promise { + if (this.stopped || this.pendingFlows.get(flow.profile.id) !== flow) return + const assertCurrent = flow.assertActive try { - if (this.pendingFlows.get(flow.profile.id) !== flow) { - return - } const credential = await this.exchangeAuthorizationCode(flow, code) + assertCurrent() if ( this.pendingFlows.get(flow.profile.id) !== flow || !sameProfileBinding(this.requireProfile(flow.profile.id), flow.profile) - ) { + ) return - } - const existing = this.loadIdentity(flow.profile) - if (existing && existing.subject !== credential.subject) { + const existing = await this.loadIdentity(flow.profile) + assertCurrent() + if (existing && existing.subject !== credential.subject) throw new Error('A different enterprise subject is already signed in; sign out first') - } - this.store.saveEnterpriseIdentity(profileKey('enterprise_identity', flow.profile), credential) + assertCurrent() + await this.store.saveEnterpriseIdentity( + profileKey('enterprise_identity', flow.profile), + credential + ) + assertCurrent() + const persistent = await this.store.isPersistent() + assertCurrent() + if (this.pendingFlows.get(flow.profile.id) !== flow) return this.pendingFlows.delete(flow.profile.id) flow.callbackSession.close() - this.setStatus({ - profileId: flow.profile.id, - state: 'authenticated', - authenticated: true, - persistent: this.store.isPersistent(), - subjectLabel: credential.subjectLabel, - updatedAt: Date.now() - }) + await this.setStatus( + { + profileId: flow.profile.id, + state: 'authenticated', + authenticated: true, + persistent, + subjectLabel: credential.subjectLabel, + updatedAt: Date.now() + }, + assertCurrent + ) + assertCurrent() } catch (error) { - this.failAuth(flow, error) + await this.failAuth(flow, error) + assertCurrent() } } - private failAuth(flow: PendingEnterpriseFlow, error: unknown): void { - if (this.pendingFlows.get(flow.profile.id) !== flow) { - return + private async failAuth(flow: PendingEnterpriseFlow, error: unknown): Promise { + if (this.stopped || this.pendingFlows.get(flow.profile.id) !== flow) return + const assertCurrent = flow.assertActive + try { + const persistent = await this.store.isPersistent() + assertCurrent() + if (this.pendingFlows.get(flow.profile.id) !== flow) return + this.pendingFlows.delete(flow.profile.id) + flow.callbackSession.close() + await this.setStatus( + { + profileId: flow.profile.id, + state: 'error', + authenticated: false, + persistent, + error: statusError(error) + }, + assertCurrent + ) + assertCurrent() + } catch (failure) { + if (this.stopped || this.pendingFlows.get(flow.profile.id) !== flow) return + throw failure } - this.pendingFlows.delete(flow.profile.id) - flow.callbackSession.close() - this.setStatus({ - profileId: flow.profile.id, - state: 'error', - authenticated: false, - persistent: this.store.isPersistent(), - error: statusError(error) - }) } private cancelPendingFlow(profileId: string): void { @@ -544,25 +691,25 @@ export class McpEnterpriseIdentityManager { this.pendingFlows.delete(profileId) flow.callbackSession.close() } - - private setStatus( + private async setStatus( status: Omit & { clientSecretConfigured?: boolean - } - ): void { + }, + assertCurrent: () => void + ): Promise { const profile = this.listProfiles().find((item) => item.id === status.profileId) const next = { ...status, - clientSecretConfigured: profile ? this.hasClientSecret(profile) : false, + clientSecretConfigured: profile ? await this.hasClientSecret(profile) : false, updatedAt: status.updatedAt || Date.now() } + assertCurrent() this.statuses.set(status.profileId, next) this.publishEvent('mcp.enterprise.auth.changed', { status: next, version: Date.now() }) } - private requireProfile(profileId: string): McpEnterpriseIdentityProfile { const profile = this.listProfiles().find((item) => item.id === profileId) if (!profile) { @@ -570,7 +717,6 @@ export class McpEnterpriseIdentityManager { } return profile } - private normalizeProfile(input: McpEnterpriseIdentityProfile): McpEnterpriseIdentityProfile { const profile = { id: input.id.trim(), @@ -591,18 +737,16 @@ export class McpEnterpriseIdentityManager { } return profile } - - private hasClientSecret(profile: McpEnterpriseIdentityProfile): boolean { + private async hasClientSecret(profile: McpEnterpriseIdentityProfile): Promise { if (profile.clientAuthentication !== 'client_secret') { return false } return Boolean( - this.store.loadEnterpriseIdentityClientSecret( + await this.store.loadEnterpriseIdentityClientSecret( profileKey('enterprise_identity_client_secret', profile) ) ) } - private async discoverMetadata(profile: McpEnterpriseIdentityProfile): Promise { const issuer = new URL(profile.issuer) const issuerPath = issuer.pathname === '/' ? '' : issuer.pathname.replace(/\/$/, '') @@ -644,11 +788,13 @@ export class McpEnterpriseIdentityManager { } return metadata as OpenIdMetadata } - private async exchangeAuthorizationCode( flow: PendingEnterpriseFlow, code: string ): Promise> { + const assertCurrent = flow.assertActive + assertCurrent() + const parameters = new URLSearchParams({ grant_type: 'authorization_code', code, @@ -660,7 +806,8 @@ export class McpEnterpriseIdentityManager { Accept: 'application/json', 'Content-Type': 'application/x-www-form-urlencoded' }) - this.applyIdpClientAuthentication(flow.profile, flow.metadata, parameters, headers) + await this.applyIdpClientAuthentication(flow.profile, flow.metadata, parameters, headers) + assertCurrent() const response = await fetch(flow.metadata.token_endpoint, { method: 'POST', headers, @@ -668,10 +815,12 @@ export class McpEnterpriseIdentityManager { redirect: 'error', signal: AbortSignal.timeout(OIDC_REQUEST_TIMEOUT_MS) }) + assertCurrent() if (!response.ok) { throw new Error(`Enterprise identity token exchange failed (${response.status})`) } const tokens = await readIdentityTokenResponse(response, 'Enterprise identity token response') + assertCurrent() if (!tokens.idToken) { throw new Error('Enterprise identity response did not include an ID token') } @@ -681,6 +830,7 @@ export class McpEnterpriseIdentityManager { flow.metadata, flow.nonce ) + assertCurrent() return { profileId: flow.profile.id, issuer: flow.profile.issuer, @@ -697,99 +847,113 @@ export class McpEnterpriseIdentityManager { scope: tokens.scope } } - - private applyIdpClientAuthentication( + private async applyIdpClientAuthentication( profile: McpEnterpriseIdentityProfile, metadata: OpenIdMetadata, parameters: URLSearchParams, headers: Headers - ): void { - if (profile.clientAuthentication === 'none') { - return - } - const secret = this.store.loadEnterpriseIdentityClientSecret( - profileKey('enterprise_identity_client_secret', profile) - ) - if (!secret) { - throw new Error('Enterprise identity client secret is not configured') - } - const methods = metadata.token_endpoint_auth_methods_supported || ['client_secret_basic'] - if (methods.includes('client_secret_basic')) { - headers.set( - 'Authorization', - `Basic ${Buffer.from( - `${encodeFormComponent(profile.clientId)}:${encodeFormComponent(secret)}` - ).toString('base64')}` + ): Promise { + const assertCurrent = this.captureProfile(profile) + try { + if (profile.clientAuthentication === 'none') { + return + } + const secret = await this.store.loadEnterpriseIdentityClientSecret( + profileKey('enterprise_identity_client_secret', profile) ) - return - } - if (methods.includes('client_secret_post')) { - parameters.set('client_secret', secret) - return + assertCurrent() + if (!secret) { + throw new Error('Enterprise identity client secret is not configured') + } + const methods = metadata.token_endpoint_auth_methods_supported || ['client_secret_basic'] + if (methods.includes('client_secret_basic')) { + headers.set( + 'Authorization', + `Basic ${Buffer.from(`${encodeFormComponent(profile.clientId)}:${encodeFormComponent(secret)}`).toString('base64')}` + ) + return + } + if (methods.includes('client_secret_post')) { + parameters.set('client_secret', secret) + return + } + throw new Error('Enterprise identity provider does not support the configured client auth') + } finally { + assertCurrent() } - throw new Error('Enterprise identity provider does not support the configured client auth') } - private async getValidIdentity( profile: McpEnterpriseIdentityProfile ): Promise { - const current = this.loadIdentity(profile) - if (!current) { - throw new Error('Enterprise identity sign-in is required') - } - if (current.expiresAt > Date.now() + 60_000) { - return current - } - if (!current.refreshToken) { - throw new Error('Enterprise identity sign-in has expired') + const assertCurrent = this.captureProfile(profile) + try { + const current = await this.loadIdentity(profile) + assertCurrent() + if (!current) { + throw new Error('Enterprise identity sign-in is required') + } + if (current.expiresAt > Date.now() + 60000) { + return current + } + if (!current.refreshToken) { + throw new Error('Enterprise identity sign-in has expired') + } + const metadata = await this.discoverMetadata(profile) + assertCurrent() + const parameters = new URLSearchParams({ + grant_type: 'refresh_token', + refresh_token: current.refreshToken, + client_id: profile.clientId + }) + const headers = new Headers({ + Accept: 'application/json', + 'Content-Type': 'application/x-www-form-urlencoded' + }) + await this.applyIdpClientAuthentication(profile, metadata, parameters, headers) + assertCurrent() + const response = await fetch(metadata.token_endpoint, { + method: 'POST', + headers, + body: parameters, + redirect: 'error', + signal: AbortSignal.timeout(OIDC_REQUEST_TIMEOUT_MS) + }) + assertCurrent() + if (!response.ok) { + throw new Error(`Enterprise identity refresh failed (${response.status})`) + } + const tokens = await readIdentityTokenResponse( + response, + 'Enterprise identity refresh response' + ) + assertCurrent() + const idToken = tokens.idToken || current.idToken + const payload = await this.verifyIdToken(idToken, profile, metadata) + assertCurrent() + if (payload.sub !== current.subject) { + throw new Error('Enterprise identity subject changed during refresh') + } + assertCurrent() + return await this.store.saveEnterpriseIdentity(profileKey('enterprise_identity', profile), { + ...current, + idToken, + accessToken: tokens.accessToken || current.accessToken, + refreshToken: tokens.refreshToken || current.refreshToken, + expiresAt: Math.min( + (payload.exp || Math.floor(Date.now() / 1000) + 300) * 1000, + Date.now() + tokens.expiresInSeconds * 1000 + ), + scope: tokens.scope || current.scope + }) + } finally { + assertCurrent() } - - const metadata = await this.discoverMetadata(profile) - const parameters = new URLSearchParams({ - grant_type: 'refresh_token', - refresh_token: current.refreshToken, - client_id: profile.clientId - }) - const headers = new Headers({ - Accept: 'application/json', - 'Content-Type': 'application/x-www-form-urlencoded' - }) - this.applyIdpClientAuthentication(profile, metadata, parameters, headers) - const response = await fetch(metadata.token_endpoint, { - method: 'POST', - headers, - body: parameters, - redirect: 'error', - signal: AbortSignal.timeout(OIDC_REQUEST_TIMEOUT_MS) - }) - if (!response.ok) { - throw new Error(`Enterprise identity refresh failed (${response.status})`) - } - const tokens = await readIdentityTokenResponse(response, 'Enterprise identity refresh response') - const idToken = tokens.idToken || current.idToken - const payload = await this.verifyIdToken(idToken, profile, metadata) - if (payload.sub !== current.subject) { - throw new Error('Enterprise identity subject changed during refresh') - } - return this.store.saveEnterpriseIdentity(profileKey('enterprise_identity', profile), { - ...current, - idToken, - accessToken: tokens.accessToken || current.accessToken, - refreshToken: tokens.refreshToken || current.refreshToken, - expiresAt: Math.min( - (payload.exp || Math.floor(Date.now() / 1000) + 300) * 1000, - Date.now() + tokens.expiresInSeconds * 1000 - ), - scope: tokens.scope || current.scope - }) } - - private loadIdentity( + private async loadIdentity( profile: McpEnterpriseIdentityProfile - ): McpEnterpriseIdentityCredential | null { - return this.store.loadEnterpriseIdentity(profileKey('enterprise_identity', profile)) + ): Promise { + return await this.store.loadEnterpriseIdentity(profileKey('enterprise_identity', profile)) } - private async verifyIdToken( token: string, profile: McpEnterpriseIdentityProfile, @@ -834,11 +998,11 @@ export class McpEnterpriseIdentityManager { (payload.iat != null && (typeof payload.iat !== 'number' || !Number.isSafeInteger(payload.iat) || - payload.iat * 1000 > now + 60_000)) || + payload.iat * 1000 > now + 60000)) || (payload.nbf != null && (typeof payload.nbf !== 'number' || !Number.isSafeInteger(payload.nbf) || - payload.nbf * 1000 > now + 60_000)) || + payload.nbf * 1000 > now + 60000)) || (expectedNonce != null && payload.nonce !== expectedNonce) ) { throw new Error('Enterprise identity ID token claims are invalid') @@ -852,7 +1016,6 @@ export class McpEnterpriseIdentityManager { ) { throw new Error('Enterprise identity ID token authorized party is invalid') } - const jwksResponse = await fetch(metadata.jwks_uri, { headers: { Accept: 'application/json' }, redirect: 'error', @@ -861,10 +1024,9 @@ export class McpEnterpriseIdentityManager { if (!jwksResponse.ok) { throw new Error(`Enterprise identity JWKS request failed (${jwksResponse.status})`) } - const jwks = await readBoundedJson<{ keys?: Array> }>( - jwksResponse, - 'Enterprise identity JWKS' - ) + const jwks = await readBoundedJson<{ + keys?: Array> + }>(jwksResponse, 'Enterprise identity JWKS') if (!Array.isArray(jwks.keys) || jwks.keys.length > MAX_JWKS_KEYS) { throw new Error('Enterprise identity JWKS is invalid') } diff --git a/src/main/mcp/index.ts b/src/main/mcp/index.ts index 3ed248e506..a6b72e1391 100644 --- a/src/main/mcp/index.ts +++ b/src/main/mcp/index.ts @@ -1,3 +1,4 @@ +import type { McpOAuthCredentialStore } from './oauthCredentialStore' import type { ProviderSettingsPort } from '@/provider/settings' import logger from '@shared/logger' import { performance } from 'node:perf_hooks' @@ -35,6 +36,7 @@ import { import type { ToolCallImagePreview } from '@shared/types/core/mcp' import type { ProviderRuntimePort } from '@shared/types/provider' import { ServerManager } from './serverManager' +import type { McpClientRuntime } from './mcpClient' import type { McpClient as RuntimeMcpClient } from './mcpClient' import { ToolManager, type ComputerUsePreviewObserver } from './toolManager' import { McpRouterManager } from './mcprouterManager' @@ -54,8 +56,9 @@ import type { SemanticNotificationPublisher } from '@/notifications' import { McpSettings } from './settings' import type { PermissionMode } from '@shared/types/agent-interface' import type { ToolPermissionBroker } from '@/tool/permission' -import type { McpAppSandboxRegistry } from './apps/sandboxRegistry' +import type { McpAppSandboxRegistry, McpAppDesktopPort } from './apps/sandboxRegistry' import { McpAppHost } from './apps/appHost' +import type { PluginToolPolicyStore } from '@/plugin/toolPolicyStore' import { hasMcpIdentityBearingChange } from './serverIdentity' import type { CacheImageOptions } from '@/platform/imageCache' import { awaitWithAbort } from '@/lib/awaitWithAbort' @@ -170,11 +173,17 @@ export class McpService implements McpServicePort { onRegistryChanged: () => void, semanticNotifications: SemanticNotificationPublisher, private readonly publishEvent: DeepchatEventPublisher, + toolPolicies: Pick, + runtime: Pick & { + getProxyUrl(): Promise + }, + oauth: { store: McpOAuthCredentialStore; openExternal(url: string): Promise }, cacheImage?: (data: string, options?: CacheImageOptions) => Promise, pluginRuntimeSupervisor?: PluginRuntimeSupervisor, computerUsePreviewObserver?: ComputerUsePreviewObserver, mcpApps?: { registry: McpAppSandboxRegistry + desktop: McpAppDesktopPort permissionBroker: ToolPermissionBroker getPermissionMode(conversationId: string): Promise validateSource(input: { @@ -209,9 +218,18 @@ export class McpService implements McpServicePort { this.pluginRuntimeSupervisor = pluginRuntimeSupervisor ?? new PluginRuntimeSupervisor() this.mcpAppSandboxRegistry = mcpApps?.registry this.mcpOAuthManager = new McpOAuthManager( - undefined, + oauth.store, + oauth.openExternal, this.publishEvent, - (serverName) => this.restartServerAfterAuthentication(serverName), + async (serverId, action, assertCurrent) => { + const { name } = await this.requireServerById(serverId) + assertCurrent() + if (action === 'restart') await this.restartServerAfterAuthentication(name, assertCurrent) + else if (this.pluginRuntimeSupervisor.ownsServer(name)) + await this.pluginRuntimeSupervisor.requestExternalStop(name, assertCurrent) + else if (this.serverManager.isServerActive(name)) await this.stopServer(name) + assertCurrent() + }, this.mcpSettings, (serverId) => { this.revokeMcpAppsByServer(serverId) @@ -223,6 +241,9 @@ export class McpService implements McpServicePort { this.privacy, inMemoryServerFactory, { + homeDirectory: runtime.homeDirectory, + appVersion: runtime.appVersion, + toolchains: runtime.toolchains, sampling: this, resolveMcpBindings: (config) => this.mcpSettings.getMcpVariableBindings(config), elicitation: this, @@ -232,12 +253,14 @@ export class McpService implements McpServicePort { () => this.handleRegistryChanged(), semanticNotifications, this.publishEvent, + runtime.getProxyUrl, this.mcpOAuthManager ) this.toolManager = new ToolManager( agentSettings, this.mcpSettings, this.serverManager, + toolPolicies, semanticNotifications, this.publishEvent, { @@ -255,10 +278,12 @@ export class McpService implements McpServicePort { ) this.appHost = mcpApps ? new McpAppHost({ + toolPolicies, settings: this.mcpSettings, serverManager: this.serverManager, permissionBroker: mcpApps.permissionBroker, registry: mcpApps.registry, + desktop: mcpApps.desktop, ensureServerRunning: async (serverName) => { if (this.pluginRuntimeSupervisor.ownsServer(serverName)) { await this.pluginRuntimeSupervisor.ensureRunning(serverName, 'external') @@ -424,6 +449,7 @@ export class McpService implements McpServicePort { return this.shutdownPromise } + this.mcpOAuthManager.stop() this.shutdownPromise = this.shutdownRunningClients() try { await this.shutdownPromise @@ -539,12 +565,21 @@ export class McpService implements McpServicePort { } } - private async restartServerAfterAuthentication(serverName: string): Promise { + private async restartServerAfterAuthentication( + serverName: string, + assertCurrent: () => void + ): Promise { try { - if (await this.pluginRuntimeSupervisor.restartIfRunning(serverName, 'authentication')) { - return - } + assertCurrent() + const restarted = await this.pluginRuntimeSupervisor.restartIfRunning( + serverName, + 'authentication', + assertCurrent + ) + assertCurrent() + if (restarted) return const servers = await this.mcpSettings.getMcpServers() + assertCurrent() const serverConfig = servers[serverName] if (this.isPluginOwnedServerConfig(serverConfig)) { console.warn( @@ -552,13 +587,21 @@ export class McpService implements McpServicePort { ) return } - if (!serverConfig?.enabled) { - return - } + if (this.serverManager.isServerActive(serverName)) await this.stopServer(serverName) + assertCurrent() + if (!serverConfig?.enabled) return const connectResult = await this.serverManager.startServer(serverName, { - onBackgroundConnected: () => this.emitServerStarted(serverName) + onBackgroundConnected: () => { + try { + assertCurrent() + } catch { + return + } + this.emitServerStarted(serverName) + } }) + assertCurrent() if (connectResult === 'connected') { this.emitServerStarted(serverName) } @@ -685,7 +728,7 @@ export class McpService implements McpServicePort { updatedServerCount > 0 ? servers : undefined ) for (const serverId of invalidatedServerIds) { - this.mcpOAuthManager.clearServerCredentials(serverId) + await this.mcpOAuthManager.clearServerCredentials(serverId) } logger.info(`Synchronized Authorization for ${updatedServerCount} mcprouter servers`) } @@ -869,7 +912,7 @@ export class McpService implements McpServicePort { previousConfig?.serverId && updatedConfig?.configGeneration !== previousConfig.configGeneration ) { - this.mcpOAuthManager.clearServerCredentials(previousConfig.serverId) + await this.mcpOAuthManager.clearServerCredentials(previousConfig.serverId) } // If server was previously running, restart it to apply new configuration @@ -907,7 +950,7 @@ export class McpService implements McpServicePort { const servers = await this.mcpSettings.getMcpServers() const persistedServerId = servers[serverName]?.serverId if (persistedServerId) { - this.mcpOAuthManager.clearServerCredentials(persistedServerId) + await this.mcpOAuthManager.clearServerCredentials(persistedServerId) } await this.mcpSettings.removeMcpServer(serverName) } @@ -1036,7 +1079,7 @@ export class McpService implements McpServicePort { binding: McpCredentialBinding, credential: McpCredentialInput ): Promise { - const { name, config } = await this.requireCurrentCredentialBinding(binding) + const { config } = await this.requireCurrentCredentialBinding(binding) const expectedKind: McpCredentialKind = config.authorization?.mode === 'private_key_jwt' ? 'private_key' @@ -1052,8 +1095,7 @@ export class McpService implements McpServicePort { ) { throw new Error('MCP private key algorithm does not match the server configuration') } - const status = this.mcpOAuthManager.setCredential(binding, credential) - await this.restartServerAfterAuthentication(name) + const status = await this.mcpOAuthManager.setCredential(binding, credential) return status } @@ -1061,12 +1103,8 @@ export class McpService implements McpServicePort { binding: McpCredentialBinding, kind: McpCredentialKind ): Promise { - const { name } = await this.requireCurrentCredentialBinding(binding) - const status = this.mcpOAuthManager.removeCredential(binding, kind) - if (this.serverManager.isServerRunning(name)) { - await this.stopServer(name) - } - return status + await this.requireCurrentCredentialBinding(binding) + return this.mcpOAuthManager.removeCredential(binding, kind) } listMcpEnterpriseProfiles(): Promise { @@ -1076,7 +1114,7 @@ export class McpService implements McpServicePort { saveMcpEnterpriseProfile( profile: McpEnterpriseIdentityProfile ): Promise { - return Promise.resolve(this.mcpOAuthManager.saveEnterpriseProfile(profile)) + return this.mcpOAuthManager.saveEnterpriseProfile(profile) } async removeMcpEnterpriseProfile(profileId: string): Promise { @@ -1086,13 +1124,13 @@ export class McpService implements McpServicePort { continue } if (config.serverId) { - this.mcpOAuthManager.clearServerCredentials(config.serverId) + await this.mcpOAuthManager.clearServerCredentials(config.serverId) } await this.updateMcpServer(serverName, { authorization: { mode: 'interactive' } }) } - this.mcpOAuthManager.removeEnterpriseProfile(profileId) + await this.mcpOAuthManager.removeEnterpriseProfile(profileId) } setMcpEnterpriseProfileClientSecret( @@ -1118,7 +1156,7 @@ export class McpService implements McpServicePort { } logoutMcpEnterpriseProfile(profileId: string): Promise { - return Promise.resolve(this.mcpOAuthManager.logoutEnterpriseProfile(profileId)) + return this.mcpOAuthManager.logoutEnterpriseProfile(profileId) } private async requireServerById( @@ -1152,19 +1190,19 @@ export class McpService implements McpServicePort { async getServerDiagnostics(serverId: string): Promise { const { name: serverName, config } = await this.requireServerById(serverId) - const auth = this.mcpOAuthManager.getStatus(serverName, config) + const auth = await this.mcpOAuthManager.getStatus(serverName, config) const client = this.serverManager.getClient(serverName) const lastError = this.serverManager.getServerLastError(serverName)?.slice(0, 2048) if (client) { return { - ...client.getDiagnostics(auth), + ...(await client.getDiagnostics(auth)), ...(lastError ? { lastError } : {}) } } let authorizationExtensions: string[] = [] try { - authorizationExtensions = this.mcpOAuthManager.getUsableAuthorizationExtensions(config) + authorizationExtensions = await this.mcpOAuthManager.getUsableAuthorizationExtensions(config) } catch { authorizationExtensions = [] } diff --git a/src/main/mcp/mcpClient.ts b/src/main/mcp/mcpClient.ts index 177e08cc9c..d6253410c8 100644 --- a/src/main/mcp/mcpClient.ts +++ b/src/main/mcp/mcpClient.ts @@ -37,11 +37,9 @@ import type { import type { DeepchatEventPublisher } from '@shared/contracts/events' import { randomUUID } from 'node:crypto' import path from 'node:path' -import { app } from 'electron' -// import { NO_PROXY, proxyConfig } from '@/platform/proxy' import type { InMemoryServerFactory } from './inMemoryServers/builder' -import { RuntimeHelper } from '@/lib/runtimeHelper' -import { ToolchainService } from '@/toolchains' +import { expandRuntimePath, getDefaultRuntimePaths } from '@/lib/runtimeHelper' +import type { ToolchainService } from '@/toolchains/service' import { getPathEntriesFromEnv, setPathEntriesOnEnv } from '@/agent/shared/process/shellEnvHelper' import { terminateProcessTreeByPid } from '@/agent/shared/process/processTree' import { childProcessRegistry } from '@/agent/shared/process/childProcessRegistry' @@ -195,7 +193,10 @@ interface ServerStatusChangedOptions { } export type McpClientRuntime = { - resolveMcpBindings?(config: Partial): Record + readonly homeDirectory: string + readonly appVersion: string + toolchains: Pick + resolveMcpBindings?(config: Partial): Promise> sampling: Pick elicitation: Pick completion: Pick @@ -304,8 +305,6 @@ export class McpClient { private readonly inMemoryServerFactory?: InMemoryServerFactory private readonly runtime: McpClientRuntime private readonly onRegistryChanged: () => void - private readonly runtimeHelper = RuntimeHelper.getInstance() - private readonly toolchainService: ToolchainService private probe: McpServerDiagnostics['probe'] = { outcome: 'not-run' } constructor( @@ -319,6 +318,9 @@ export class McpClient { onRegistryChanged: () => void, private readonly publishEvent: DeepchatEventPublisher ) { + if (!path.isAbsolute(runtime.homeDirectory)) { + throw new Error('MCP home directory must be absolute') + } this.serverName = serverName this.serverConfig = serverConfig this.npmRegistry = npmRegistry @@ -327,7 +329,6 @@ export class McpClient { this.inMemoryServerFactory = inMemoryServerFactory this.runtime = runtime this.onRegistryChanged = onRegistryChanged - this.toolchainService = ToolchainService.getInstance() } private emitServerStatusChanged( @@ -357,16 +358,16 @@ export class McpClient { command: string, args: string[] ): { command: string; args: string[] } { - return this.toolchainService.rewriteCommand(command, args) + return this.runtime.toolchains.rewriteCommand(command, args) } public expandPath(inputPath: string): string { - return this.runtimeHelper.expandPath(inputPath) + return expandRuntimePath(inputPath, this.runtime.homeDirectory) } public get nodeRuntimePath(): string | null { try { - return this.toolchainService.resolve('node').rootDir + return this.runtime.toolchains.resolve('node').rootDir } catch { return null } @@ -374,7 +375,7 @@ export class McpClient { public get uvRuntimePath(): string | null { try { - return this.toolchainService.resolve('uv').rootDir + return this.runtime.toolchains.resolve('uv').rootDir } catch { return null } @@ -389,11 +390,7 @@ export class McpClient { ): void { setPathEntriesOnEnv( env, - [ - this.toolchainService.resolvedBinDirs(), - inheritedPaths, - this.runtimeHelper.getDefaultPaths(homeDir) - ], + [this.runtime.toolchains.resolvedBinDirs(), inheritedPaths, getDefaultRuntimePaths(homeDir)], { includeDefaultPaths: false } ) } @@ -510,8 +507,12 @@ export class McpClient { this.serverConfig.ownerPluginId.startsWith('user.') const privateBindings = userPlugin || this.serverConfig.ownerPluginId === NOWLEDGE_PLUGIN_ID const bindingEnvironment = privateBindings - ? (this.runtime.resolveMcpBindings?.(this.serverConfig as Partial) ?? {}) + ? ((await this.runtime.resolveMcpBindings?.( + this.serverConfig as Partial + )) ?? {}) : process.env + if (this.connectAborted || attempt !== this.startupAttempt) + throw new McpConnectionCancelledError(this.serverName) const resolveBinding = (value: string) => resolveMcpEnvironmentBinding( value, @@ -551,6 +552,9 @@ export class McpClient { this.serverConfig as Partial ) + if (this.connectAborted || attempt !== this.startupAttempt) + throw new McpConnectionCancelledError(this.serverName) + if (this.serverConfig.type === 'inmemory') { const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair() const _args = Array.isArray(this.serverConfig.args) ? this.serverConfig.args : [] @@ -569,10 +573,10 @@ export class McpClient { let args = (this.serverConfig.args as string[]).map(resolveBinding) // Handle path expansion (including ~ and environment variables) - command = this.runtimeHelper.expandPath(command) - args = args.map((arg) => this.runtimeHelper.expandPath(arg)) + command = this.expandPath(command) + args = args.map((arg) => this.expandPath(arg)) - const HOME_DIR = app.getPath('home') + const homeDirectory = this.runtime.homeDirectory // Define allowed environment variables whitelist const allowedEnvVars = [ @@ -595,7 +599,7 @@ export class McpClient { const env: Record = {} // Handle command and argument replacement - const processedCommand = this.toolchainService.rewriteCommand(command, args) + const processedCommand = this.processCommandWithArgs(command, args) command = processedCommand.command args = processedCommand.args @@ -603,7 +607,7 @@ export class McpClient { if (this.serverConfig.inheritEnv === 'minimal') { Object.assign(env, createMinimalProcessEnvironment(process.env, process.platform)) - this.addRuntimePathsToEnvironment(env, HOME_DIR) + this.addRuntimePathsToEnvironment(env, homeDirectory) } else if (isNodeCommand) { // Node.js/UV commands use whitelist processing if (process.env) { @@ -623,7 +627,7 @@ export class McpClient { } }) - this.addRuntimePathsToEnvironment(env, HOME_DIR, existingPaths) + this.addRuntimePathsToEnvironment(env, homeDirectory, existingPaths) } } else { // 非 Node.js/UV 命令,保留所有系统环境变量,只补充 PATH @@ -642,7 +646,7 @@ export class McpClient { existingPaths.push(env.Path) } - this.addRuntimePathsToEnvironment(env, HOME_DIR, existingPaths) + this.addRuntimePathsToEnvironment(env, homeDirectory, existingPaths) } // 添加自定义环境变量 @@ -730,11 +734,13 @@ export class McpClient { // 创建 MCP 客户端 this.probe = { outcome: 'not-run' } const authorizationExtensions = - this.mcpOAuthManager?.getUsableAuthorizationExtensions( + (await this.mcpOAuthManager?.getUsableAuthorizationExtensions( this.serverConfig as Partial - ) ?? [] + )) ?? [] + if (this.connectAborted || attempt !== this.startupAttempt) + throw new McpConnectionCancelledError(this.serverName) this.client = new Client( - { name: 'DeepChat', version: app.getVersion() }, + { name: 'DeepChat', version: this.runtime.appVersion }, { capabilities: { sampling: {}, @@ -823,6 +829,9 @@ export class McpClient { // 等待连接完成或硬超时 await Promise.race([connectPromise, timeoutPromise]) + if (this.connectAborted || attempt !== this.startupAttempt) + throw new McpConnectionCancelledError(this.serverName) + // 清除超时 if (this.connectionTimeout) { clearTimeout(this.connectionTimeout) @@ -845,6 +854,7 @@ export class McpClient { this.emitServerStatusChanged('connected', { phase, attempt }) } catch (error) { + if (attempt !== this.startupAttempt) throw new McpConnectionCancelledError(this.serverName) // 清除超时 if (this.connectionTimeout) { clearTimeout(this.connectionTimeout) @@ -1867,15 +1877,22 @@ export class McpClient { return content } - async readResourceContents(resourceUri: string): Promise { + async readResourceContents(resourceUri: string, signal?: AbortSignal): Promise { try { - await this.ensureConnectedForRequest() + await this.ensureConnectedForRequest(signal) + signal?.throwIfAborted() if (!this.client) { throw new Error(`MCP client ${this.serverName} not initialized`) } - const rawResource = await this.client.readResource({ uri: resourceUri }) + const rawResource = await awaitWithAbort( + signal + ? this.client.readResource({ uri: resourceUri }, { signal }) + : this.client.readResource({ uri: resourceUri }), + signal + ) + signal?.throwIfAborted() this.assertControlResult(rawResource, `resource ${resourceUri}`) return rawResource.contents.map((content) => ({ uri: content.uri, @@ -1924,7 +1941,7 @@ export class McpClient { ) } - getDiagnostics(auth: McpServerAuthStatus): McpServerDiagnostics { + async getDiagnostics(auth: McpServerAuthStatus): Promise { const client = this.client const capabilities = client?.getServerCapabilities() const serverVersion = client?.getServerVersion() @@ -1932,9 +1949,9 @@ export class McpClient { let authorizationExtensions: string[] = [] try { authorizationExtensions = - this.mcpOAuthManager?.getUsableAuthorizationExtensions( + (await this.mcpOAuthManager?.getUsableAuthorizationExtensions( this.serverConfig as Partial - ) ?? [] + )) ?? [] } catch { authorizationExtensions = [] } diff --git a/src/main/mcp/mcpOAuthManager.ts b/src/main/mcp/mcpOAuthManager.ts index 2187b90745..2a5ea2087e 100644 --- a/src/main/mcp/mcpOAuthManager.ts +++ b/src/main/mcp/mcpOAuthManager.ts @@ -34,7 +34,7 @@ import { MCP_OAUTH_REDIRECT_PORT } from './oauthConstants' import { - McpOAuthCredentialStore, + type McpOAuthCredentialStore, normalizeMcpOAuthIssuer, type McpOAuthCredentialEntry } from './oauthCredentialStore' @@ -51,6 +51,7 @@ type PendingMcpOAuthFlow = { provider: DeepChatMcpOAuthProvider callbackSession: OAuthLoopbackCallbackSession flowPromise?: Promise + assertActive(): void } const OAUTH_AUTH_ERROR_PATTERNS = [ @@ -257,538 +258,722 @@ export class McpOAuthManager { private readonly statuses = new Map() private readonly pendingFlows = new Map() private readonly enterpriseIdentity: McpEnterpriseIdentityManager | null + private stopped = false + private readonly generations = new Map() + + private invalidate(id?: string): void { + if (id) { + this.generations.set(id, (this.generations.get(id) ?? 0) + 1) + this.cancelPendingFlow(id) + this.statuses.delete(id) + } + } + + private captureOperation(id?: string): () => void { + const generation = id ? (this.generations.get(id) ?? 0) : 0 + const check = () => { + if (this.stopped || (id && generation !== (this.generations.get(id) ?? 0))) + throw new Error('MCP OAuth operation interrupted') + } + check() + return check + } + + stop(): void { + this.stopped = true + for (const id of this.pendingFlows.keys()) this.cancelPendingFlow(id) + this.statuses.clear() + this.generations.clear() + this.enterpriseIdentity?.stop() + this.store.stop() + } constructor( - store = new McpOAuthCredentialStore(), + store: McpOAuthCredentialStore, + private readonly openExternal: (url: string) => Promise, private readonly publishEvent: DeepchatEventPublisher, - private readonly onAuthenticated?: (serverName: string) => void | Promise, + private readonly onRuntimeChange?: ( + serverId: string, + action: 'restart' | 'stop', + assertCurrent: () => void + ) => void | Promise, settings?: McpSettings, private readonly onServerBindingChanged?: (serverId: string) => void ) { this.store = store this.settings = settings this.enterpriseIdentity = settings - ? new McpEnterpriseIdentityManager(settings, store, publishEvent) + ? new McpEnterpriseIdentityManager( + settings, + store, + publishEvent, + openExternal, + async (profileId, assertCurrent) => { + const servers = await settings.getMcpServers() + assertCurrent() + for (const config of Object.values(servers)) { + if (config.serverId && config.authorization?.identityProfileId === profileId) { + const assertServerCurrent = this.captureOperation(config.serverId) + await this.applyRuntimeChange(config.serverId, 'stop', () => { + assertCurrent() + assertServerCurrent() + }) + } + } + } + ) : null } - - getStatus(serverName: string, config?: Partial | null): McpServerAuthStatus { - if (!config || !isRemoteOAuthCapable(config)) { - const authenticated = hasAuthorizationHeader(config) - const mode = config ? getAuthorizationMode(config) : undefined - return { - serverName, - serverId: config?.serverId, - state: authenticated ? 'authenticated' : mode === 'none' ? 'none' : 'unsupported', - authenticated, - storage: this.store.getStorageState(), - persistent: this.store.isPersistent(), - mode + async getStatus( + serverName: string, + config?: Partial | null + ): Promise { + const assertCurrent = this.captureOperation(config?.serverId) + try { + if (!config || !isRemoteOAuthCapable(config)) { + const authenticated = hasAuthorizationHeader(config) + const mode = config ? getAuthorizationMode(config) : undefined + return { + serverName, + serverId: config?.serverId, + state: authenticated ? 'authenticated' : mode === 'none' ? 'none' : 'unsupported', + authenticated, + ...(await this.getStorageSnapshot()), + mode + } } - } - - const binding = requireServerBinding(config as Partial) - const mode = getAuthorizationMode(config) - const pending = this.pendingFlows.get(binding.serverId) - if (pending) { - return this.buildStatus(serverName, binding, mode, { - state: 'authenticating', - authenticated: false - }) - } - - if (mode === 'interactive') { - const found = this.findInteractiveCredential(serverName, config, binding) - if (found?.entry.tokens?.access_token) { - return this.buildStatus(serverName, binding, mode, { - state: 'authenticated', - authenticated: true, - updatedAt: found.entry.updatedAt + const binding = requireServerBinding(config as Partial) + const mode = getAuthorizationMode(config) + const pending = this.pendingFlows.get(binding.serverId) + if (pending) { + return await this.buildStatus(serverName, binding, mode, { + state: 'authenticating', + authenticated: false }) } - } else { - const credential = this.getSelectedCredentialStatus(binding, mode) - if (!credential.configured) { - return this.buildStatus(serverName, binding, mode, { - state: 'required', - authenticated: false, - credential - }) + if (mode === 'interactive') { + const found = await this.findInteractiveCredential(serverName, config, binding) + assertCurrent() + if (found?.entry.tokens?.access_token) { + return await this.buildStatus(serverName, binding, mode, { + state: 'authenticated', + authenticated: true, + updatedAt: found.entry.updatedAt + }) + } + } else { + const credential = await this.getSelectedCredentialStatus(binding, mode) + assertCurrent() + if (!credential.configured) { + return await this.buildStatus(serverName, binding, mode, { + state: 'required', + authenticated: false, + credential + }) + } + const existing = this.statuses.get(binding.serverId) + if (existing?.state === 'authenticated') { + return { ...existing, serverName, credential } + } + if (!existing || existing.state !== 'error') { + return await this.buildStatus(serverName, binding, mode, { + state: 'none', + authenticated: false, + credential + }) + } } const existing = this.statuses.get(binding.serverId) - if (existing?.state === 'authenticated') { - return { ...existing, serverName, credential } + if (existing?.state === 'required' || existing?.state === 'error') { + return { ...existing, serverName } } - if (!existing || existing.state !== 'error') { - return this.buildStatus(serverName, binding, mode, { - state: 'none', - authenticated: false, - credential - }) - } - } - - const existing = this.statuses.get(binding.serverId) - if (existing?.state === 'required' || existing?.state === 'error') { - return { ...existing, serverName } + return await this.buildStatus(serverName, binding, mode, { + state: 'none', + authenticated: false + }) + } finally { + assertCurrent() } - - return this.buildStatus(serverName, binding, mode, { - state: 'none', - authenticated: false - }) } - async createRuntimeProvider( serverName: string, config: Partial ): Promise { - if (!isRemoteOAuthCapable(config)) { - return undefined - } - const completeConfig = config as MCPServerConfig - const binding = requireServerBinding(completeConfig) - const mode = getAuthorizationMode(completeConfig) - - if (mode === 'interactive') { - const found = this.findInteractiveCredential(serverName, completeConfig, binding) - if (!found?.entry.tokens?.access_token) { + const assertCurrent = this.captureOperation(config.serverId) + try { + if (!isRemoteOAuthCapable(config)) { return undefined } + const completeConfig = config as MCPServerConfig + const binding = requireServerBinding(completeConfig) + const mode = getAuthorizationMode(completeConfig) + if (mode === 'interactive') { + const found = await this.findInteractiveCredential(serverName, completeConfig, binding) + assertCurrent() + if (!found?.entry.tokens?.access_token) { + return undefined + } + assertCurrent() + const info = await discoverOAuthServerInfo(binding.endpoint) + assertCurrent() + assertConfiguredDiscovery(completeConfig, info) + if (!this.isInteractiveCredentialCurrent(found.entry, info)) { + assertCurrent() + await this.store.clearEntry(found.key) + assertCurrent() + return undefined + } + const provider = this.createInteractiveProvider(completeConfig, binding, { + interactive: false, + assertActive: assertCurrent, + initialCredentialKey: found.key + }) + await this.finalizeInteractiveBinding( + completeConfig, + provider, + info, + found.entry.clientInformation?.client_id + ) + assertCurrent() + return provider + } + if ( + !completeConfig.authorization?.authorizationServerIssuer || + !completeConfig.authorization.protectedResourceUrl + ) { + throw new Error('MCP machine authorization requires an issuer and protected resource') + } + assertCurrent() const info = await discoverOAuthServerInfo(binding.endpoint) - assertConfiguredDiscovery(completeConfig, info) - if (!this.isInteractiveCredentialCurrent(found.entry, info)) { - this.store.clearEntry(found.key) - return undefined + assertCurrent() + const issuer = assertConfiguredDiscovery(completeConfig, info) + const discoveredBinding: McpCredentialBinding = { + ...binding, + authorizationServerIssuer: issuer, + protectedResourceUrl: normalizeUrlIdentifier( + info.resourceMetadata?.resource || binding.protectedResourceUrl! + ) } - const provider = this.createInteractiveProvider(completeConfig, binding, { - interactive: false, - initialCredentialKey: found.key + const provider = await this.createMachineProvider(completeConfig, discoveredBinding, info) + assertCurrent() + const result = await auth(provider, { + serverUrl: binding.endpoint, + scope: completeConfig.authorization?.scopes?.join(' ') }) - await this.finalizeInteractiveBinding( - completeConfig, - provider, - info, - found.entry.clientInformation?.client_id + assertCurrent() + if (result !== 'AUTHORIZED') { + throw new Error('MCP machine authorization did not produce an access token') + } + this.setStatus( + await this.buildStatus(serverName, discoveredBinding, mode, { + state: 'authenticated', + authenticated: true, + credential: await this.getSelectedCredentialStatus(discoveredBinding, mode) + }), + assertCurrent ) + assertCurrent() return provider + } finally { + assertCurrent() } - - if ( - !completeConfig.authorization?.authorizationServerIssuer || - !completeConfig.authorization.protectedResourceUrl - ) { - throw new Error('MCP machine authorization requires an issuer and protected resource') - } - const info = await discoverOAuthServerInfo(binding.endpoint) - const issuer = assertConfiguredDiscovery(completeConfig, info) - const discoveredBinding: McpCredentialBinding = { - ...binding, - authorizationServerIssuer: issuer, - protectedResourceUrl: normalizeUrlIdentifier( - info.resourceMetadata?.resource || binding.protectedResourceUrl! - ) - } - const provider = await this.createMachineProvider(completeConfig, discoveredBinding, info) - const result = await auth(provider, { - serverUrl: binding.endpoint, - scope: completeConfig.authorization?.scopes?.join(' ') - }) - if (result !== 'AUTHORIZED') { - throw new Error('MCP machine authorization did not produce an access token') - } - this.setStatus( - this.buildStatus(serverName, discoveredBinding, mode, { - state: 'authenticated', - authenticated: true, - credential: this.getSelectedCredentialStatus(discoveredBinding, mode) - }) - ) - return provider } - - handleConnectionError( + async handleConnectionError( serverName: string, config: Partial, error: unknown - ): boolean { - if (!isRemoteOAuthCapable(config) || !isOAuthError(error)) { - return false + ): Promise { + const assertCurrent = this.captureOperation(config?.serverId) + try { + if (!isRemoteOAuthCapable(config) || !isOAuthError(error)) { + return false + } + const binding = requireServerBinding(config) + const mode = getAuthorizationMode(config) + this.setStatus( + await this.buildStatus(serverName, binding, mode, { + state: mode === 'interactive' ? 'required' : 'error', + authenticated: false, + error: sanitizeError(error), + ...(mode === 'interactive' + ? {} + : { credential: await this.getSelectedCredentialStatus(binding, mode) }) + }), + assertCurrent + ) + assertCurrent() + return true + } finally { + assertCurrent() } - const binding = requireServerBinding(config) - const mode = getAuthorizationMode(config) - this.setStatus( - this.buildStatus(serverName, binding, mode, { - state: mode === 'interactive' ? 'required' : 'error', - authenticated: false, - error: sanitizeError(error), - ...(mode === 'interactive' - ? {} - : { credential: this.getSelectedCredentialStatus(binding, mode) }) - }) - ) - return true } - async startAuth( serverName: string, config: Partial ): Promise { - if (!isRemoteOAuthCapable(config) || !config.baseUrl) { - throw new Error('MCP server does not support OAuth authentication') - } - const completeConfig = config as MCPServerConfig - const binding = requireServerBinding(completeConfig) - const mode = getAuthorizationMode(completeConfig) - if (mode !== 'interactive') { - try { - await this.createRuntimeProvider(serverName, completeConfig) - } catch (error) { - this.setStatus( - this.buildStatus(serverName, binding, mode, { - state: 'error', - authenticated: false, - error: sanitizeError(error), - credential: this.getSelectedCredentialStatus(binding, mode) - }) - ) - return this.getStatus(serverName, config) + this.invalidate(config.serverId) + const assertCurrent = this.captureOperation(config.serverId) + try { + if (!isRemoteOAuthCapable(config) || !config.baseUrl) { + throw new Error('MCP server does not support OAuth authentication') } - await Promise.resolve(this.onAuthenticated?.(serverName)).catch((error) => { - logger.warn( - '[MCP OAuth] Failed to restart server after machine authentication:', - sanitizeError(error) - ) + const completeConfig = config as MCPServerConfig + const binding = requireServerBinding(completeConfig) + const mode = getAuthorizationMode(completeConfig) + if (mode !== 'interactive') { + try { + await this.createRuntimeProvider(serverName, completeConfig) + assertCurrent() + } catch (error) { + this.setStatus( + await this.buildStatus(serverName, binding, mode, { + state: 'error', + authenticated: false, + error: sanitizeError(error), + credential: await this.getSelectedCredentialStatus(binding, mode) + }), + assertCurrent + ) + assertCurrent() + return await this.getStatus(serverName, config) + } + assertCurrent() + await this.applyRuntimeChange(binding.serverId, 'restart', assertCurrent).catch((error) => { + logger.warn( + '[MCP OAuth] Failed to restart server after machine authentication:', + sanitizeError(error) + ) + }) + assertCurrent() + return await this.getStatus(serverName, config) + } + this.cancelPendingFlow(binding.serverId) + const state = createState() + let existing = await this.findInteractiveCredential(serverName, completeConfig, binding) + assertCurrent() + if (existing?.entry.discoveryState) { + try { + assertCurrent() + const info = await discoverOAuthServerInfo(binding.endpoint) + assertCurrent() + assertConfiguredDiscovery(completeConfig, info) + if (!this.isInteractiveCredentialCurrent(existing.entry, info)) { + assertCurrent() + await this.store.clearEntry(existing.key) + assertCurrent() + existing = null + } + } catch (error) { + this.setStatus( + await this.buildStatus(serverName, binding, mode, { + state: 'error', + authenticated: false, + error: sanitizeError(error) + }), + assertCurrent + ) + assertCurrent() + return await this.getStatus(serverName, config) + } + } + const client = existing?.entry.clientInformation + const dynamicClient = + client && client.client_id !== completeConfig.authorization?.clientMetadataUrl + let callbackPath = + completeConfig.authorization?.clientMetadataUrl && !dynamicClient + ? MCP_OAUTH_REDIRECT_PATH + : `${MCP_OAUTH_REDIRECT_PATH}/${encodeURIComponent(binding.serverId)}` + let registeredRedirect: URL | null | undefined + if (dynamicClient) { + registeredRedirect = + 'redirect_uris' in client && Array.isArray(client.redirect_uris) + ? client.redirect_uris + .map((uri) => URL.parse(uri)) + .find( + (url) => + url?.protocol === 'http:' && + url.hostname === 'localhost' && + !url.username && + !url.password && + !url.search && + !url.hash && + (url.pathname === MCP_OAUTH_REDIRECT_PATH || + url.pathname.startsWith(`${MCP_OAUTH_REDIRECT_PATH}/`)) + ) + : undefined + if (registeredRedirect) { + callbackPath = registeredRedirect.pathname + } + } + let provider!: DeepChatMcpOAuthProvider + const callbackSession = await startOAuthLoopbackCallbackSession({ + expectedState: state, + path: callbackPath, + preferredPort: Number(registeredRedirect?.port) || MCP_OAUTH_REDIRECT_PORT, + redirectHost: 'localhost', + timeoutMs: MCP_OAUTH_CALLBACK_TIMEOUT_MS, + invalidCallbackMessage: 'Invalid MCP OAuth callback', + validateParameters: (parameters) => { + const validation = provider.getCallbackValidation() + validateAuthorizationResponseIssuer({ + iss: parameters.get('iss') || undefined, + expectedIssuer: validation.issuer, + issParameterSupported: validation.issParameterSupported + }) + } }) - return this.getStatus(serverName, config) - } - - this.cancelPendingFlow(binding.serverId) - const state = createState() - let existing = this.findInteractiveCredential(serverName, completeConfig, binding) - if (existing?.entry.discoveryState) { try { - const info = await discoverOAuthServerInfo(binding.endpoint) - assertConfiguredDiscovery(completeConfig, info) - if (!this.isInteractiveCredentialCurrent(existing.entry, info)) { - this.store.clearEntry(existing.key) - existing = null + assertCurrent() + if ( + existing && + dynamicClient && + registeredRedirect?.href !== new URL(callbackSession.redirectUri).href + ) { + assertCurrent() + await this.store.clearEntryScope(existing.key, 'client') + assertCurrent() + await this.store.clearEntryScope(existing.key, 'tokens') + assertCurrent() } + provider = this.createInteractiveProvider(completeConfig, binding, { + redirectUrl: callbackSession.redirectUri, + state, + interactive: true, + assertActive: assertCurrent, + initialCredentialKey: existing?.key + }) } catch (error) { + callbackSession.close() + assertCurrent() this.setStatus( - this.buildStatus(serverName, binding, mode, { + await this.buildStatus(serverName, binding, mode, { state: 'error', authenticated: false, error: sanitizeError(error) - }) + }), + assertCurrent ) - return this.getStatus(serverName, config) + assertCurrent() + return await this.getStatus(serverName, config) } - } - const client = existing?.entry.clientInformation - const dynamicClient = - client && client.client_id !== completeConfig.authorization?.clientMetadataUrl - let callbackPath = - completeConfig.authorization?.clientMetadataUrl && !dynamicClient - ? MCP_OAUTH_REDIRECT_PATH - : `${MCP_OAUTH_REDIRECT_PATH}/${encodeURIComponent(binding.serverId)}` - let registeredRedirect: URL | null | undefined - if (dynamicClient) { - registeredRedirect = - 'redirect_uris' in client && Array.isArray(client.redirect_uris) - ? client.redirect_uris - .map((uri) => URL.parse(uri)) - .find( - (url) => - url?.protocol === 'http:' && - url.hostname === 'localhost' && - !url.username && - !url.password && - !url.search && - !url.hash && - (url.pathname === MCP_OAUTH_REDIRECT_PATH || - url.pathname.startsWith(`${MCP_OAUTH_REDIRECT_PATH}/`)) - ) - : undefined - if (registeredRedirect) { - callbackPath = registeredRedirect.pathname + const flow: PendingMcpOAuthFlow = { + serverId: binding.serverId, + serverName, + serverUrl: config.baseUrl, + initialBinding: binding, + binding, + provider, + callbackSession, + assertActive: assertCurrent } - } - let provider!: DeepChatMcpOAuthProvider - const callbackSession = await startOAuthLoopbackCallbackSession({ - expectedState: state, - path: callbackPath, - preferredPort: Number(registeredRedirect?.port) || MCP_OAUTH_REDIRECT_PORT, - redirectHost: 'localhost', - timeoutMs: MCP_OAUTH_CALLBACK_TIMEOUT_MS, - invalidCallbackMessage: 'Invalid MCP OAuth callback', - validateParameters: (parameters) => { - const discovery = provider.discoveryState() - validateAuthorizationResponseIssuer({ - iss: parameters.get('iss') || undefined, - expectedIssuer: - discovery?.authorizationServerMetadata?.issuer || discovery?.authorizationServerUrl, - issParameterSupported: - discovery?.authorizationServerMetadata - ?.authorization_response_iss_parameter_supported === true + assertCurrent() + this.pendingFlows.set(binding.serverId, flow) + this.setStatus( + await this.buildStatus(serverName, binding, mode, { + state: 'authenticating', + authenticated: false + }), + assertCurrent + ) + assertCurrent() + try { + assertCurrent() + const result = await auth(provider, { + serverUrl: config.baseUrl, + scope: completeConfig.authorization?.scopes?.join(' ') }) + assertCurrent() + const discovery = await provider.discoveryState() + assertCurrent() + if (!discovery) { + throw new Error('MCP OAuth discovery state is unavailable') + } + flow.binding = await this.finalizeInteractiveBinding(completeConfig, provider, discovery) + assertCurrent() + if (result === 'AUTHORIZED') { + await this.finishAuthenticatedFlow(flow) + assertCurrent() + return await this.getStatus( + serverName, + await this.getCurrentConfig(serverName, completeConfig) + ) + } + flow.flowPromise = callbackSession + .waitForCallback() + .then(async (callback) => await this.finishAuthFlow(flow, callback.code, callback.iss)) + .catch(async (error) => await this.failAuthFlow(flow, error)) + return await this.getStatus( + serverName, + await this.getCurrentConfig(serverName, completeConfig) + ) + } catch (error) { + await this.failAuthFlow(flow, error) + assertCurrent() + return await this.getStatus( + serverName, + await this.getCurrentConfig(serverName, completeConfig) + ) } - }) + } finally { + assertCurrent() + } + } + async completeAuthFromCallbackUrl( + serverName: string, + config: Partial, + callbackUrl: string + ): Promise { + const assertCurrent = this.captureOperation(config.serverId) try { + const binding = requireServerBinding(config) + const flow = this.pendingFlows.get(binding.serverId) + if (!flow) { + const status = await this.getStatus(serverName, config) + assertCurrent() + if (status.authenticated) { + return status + } + this.setStatus( + await this.buildStatus(serverName, binding, getAuthorizationMode(config), { + state: 'error', + authenticated: false, + error: 'MCP OAuth authentication is not pending' + }), + assertCurrent + ) + assertCurrent() + return await this.getStatus(serverName, config) + } + const callbackCredentialKey = createMcpCredentialKey('interactive_oauth', binding) if ( - existing && - dynamicClient && - registeredRedirect?.href !== new URL(callbackSession.redirectUri).href + callbackCredentialKey !== + createMcpCredentialKey('interactive_oauth', flow.initialBinding) && + callbackCredentialKey !== createMcpCredentialKey('interactive_oauth', flow.binding) ) { - this.store.clearEntryScope(existing.key, 'client') - this.store.clearEntryScope(existing.key, 'tokens') - } - provider = this.createInteractiveProvider(completeConfig, binding, { - redirectUrl: callbackSession.redirectUri, - state, - interactive: true, - initialCredentialKey: existing?.key - }) - } catch (error) { - callbackSession.close() - this.setStatus( - this.buildStatus(serverName, binding, mode, { - state: 'error', - authenticated: false, - error: sanitizeError(error) - }) - ) - return this.getStatus(serverName, config) - } - const flow: PendingMcpOAuthFlow = { - serverId: binding.serverId, - serverName, - serverUrl: config.baseUrl, - initialBinding: binding, - binding, - provider, - callbackSession - } - this.pendingFlows.set(binding.serverId, flow) - this.setStatus( - this.buildStatus(serverName, binding, mode, { - state: 'authenticating', - authenticated: false - }) - ) - - try { - const result = await auth(provider, { - serverUrl: config.baseUrl, - scope: completeConfig.authorization?.scopes?.join(' ') - }) - const discovery = provider.discoveryState() - if (!discovery) { - throw new Error('MCP OAuth discovery state is unavailable') + await this.failAuthFlow( + flow, + new Error('MCP OAuth server binding changed during authentication') + ) + assertCurrent() + return await this.getStatus(serverName, config) } - flow.binding = await this.finalizeInteractiveBinding(completeConfig, provider, discovery) - if (result === 'AUTHORIZED') { - this.finishAuthenticatedFlow(flow) - return this.getStatus(serverName, await this.getCurrentConfig(serverName, completeConfig)) + const resolution = flow.callbackSession.resolveCallbackUrl(callbackUrl) + if (resolution.kind === 'not-found') { + await this.failAuthFlow(flow, new Error('MCP OAuth callback URL is invalid')) + assertCurrent() + return await this.getStatus(serverName, config) } - - flow.flowPromise = callbackSession - .waitForCallback() - .then((callback) => this.finishAuthFlow(flow, callback.code, callback.iss)) - .catch((error) => this.failAuthFlow(flow, error)) - return this.getStatus(serverName, await this.getCurrentConfig(serverName, completeConfig)) - } catch (error) { - this.failAuthFlow(flow, error) - return this.getStatus(serverName, await this.getCurrentConfig(serverName, completeConfig)) + await flow.flowPromise + assertCurrent() + return await this.getStatus(serverName, config) + } finally { + assertCurrent() } } - - async completeAuthFromCallbackUrl( + async logout( serverName: string, - config: Partial, - callbackUrl: string + config?: Partial | null ): Promise { - const binding = requireServerBinding(config) - const flow = this.pendingFlows.get(binding.serverId) - if (!flow) { - const status = this.getStatus(serverName, config) - if (status.authenticated) { - return status + this.invalidate(config?.serverId) + const assertCurrent = this.captureOperation(config?.serverId) + try { + if (!config) { + return { + serverName, + state: 'unsupported', + authenticated: false, + ...(await this.getStorageSnapshot()) + } + } + const binding = requireServerBinding(config) + this.cancelPendingFlow(binding.serverId) + const interactive = await this.findInteractiveCredential(serverName, config, binding) + assertCurrent() + if (interactive) { + assertCurrent() + await this.store.clearEntry(interactive.key) + assertCurrent() } this.setStatus( - this.buildStatus(serverName, binding, getAuthorizationMode(config), { - state: 'error', - authenticated: false, - error: 'MCP OAuth authentication is not pending' - }) + await this.buildStatus(serverName, binding, getAuthorizationMode(config), { + state: 'none', + authenticated: false + }), + assertCurrent ) - return this.getStatus(serverName, config) - } - const callbackCredentialKey = createMcpCredentialKey('interactive_oauth', binding) - if ( - callbackCredentialKey !== createMcpCredentialKey('interactive_oauth', flow.initialBinding) && - callbackCredentialKey !== createMcpCredentialKey('interactive_oauth', flow.binding) - ) { - this.failAuthFlow(flow, new Error('MCP OAuth server binding changed during authentication')) - return this.getStatus(serverName, config) + assertCurrent() + const status = await this.getStatus(serverName, config) + await this.applyRuntimeChange(binding.serverId, 'stop', assertCurrent) + return status + } finally { + assertCurrent() } - - const resolution = flow.callbackSession.resolveCallbackUrl(callbackUrl) - if (resolution.kind === 'not-found') { - this.failAuthFlow(flow, new Error('MCP OAuth callback URL is invalid')) - return this.getStatus(serverName, config) - } - - await flow.flowPromise - return this.getStatus(serverName, config) } - - logout(serverName: string, config?: Partial | null): McpServerAuthStatus { - if (!config) { - return { - serverName, - state: 'unsupported', - authenticated: false, - storage: this.store.getStorageState(), - persistent: this.store.isPersistent() - } - } - const binding = requireServerBinding(config) - this.cancelPendingFlow(binding.serverId) - const interactive = this.findInteractiveCredential(serverName, config, binding) - if (interactive) { - this.store.clearEntry(interactive.key) + async getCredentialStatuses(config: MCPServerConfig): Promise { + const assertCurrent = this.captureOperation(config.serverId) + try { + const binding = requireServerBinding(config) + return await Promise.all( + (['client_secret', 'private_key', 'enterprise_resource_secret'] as const).map((kind) => + this.getCredentialStatus(binding, kind) + ) + ) + } finally { + assertCurrent() } - this.setStatus( - this.buildStatus(serverName, binding, getAuthorizationMode(config), { - state: 'none', - authenticated: false - }) - ) - return this.getStatus(serverName, config) - } - - getCredentialStatuses(config: MCPServerConfig): McpCredentialStatus[] { - const binding = requireServerBinding(config) - return (['client_secret', 'private_key', 'enterprise_resource_secret'] as const).map((kind) => - this.getCredentialStatus(binding, kind) - ) } - - setCredential( + async setCredential( binding: McpCredentialBinding, credential: McpCredentialInput - ): McpCredentialStatus { - if (credential.kind === 'client_secret') { - if (!credential.secret || credential.secret.length > 8192) { - throw new Error('MCP client secret is invalid') - } - this.store.saveClientSecret( - createMcpCredentialKey('client_secret', binding), - credential.secret, - binding - ) - } else if (credential.kind === 'enterprise_resource_secret') { - if (!credential.secret || credential.secret.length > 8192) { - throw new Error('MCP enterprise resource secret is invalid') - } - this.store.saveEnterpriseResourceSecret( - createMcpCredentialKey('enterprise_resource_secret', binding), - credential.secret, - binding - ) - } else { - if (!credential.privateKey || credential.privateKey.length > 64 * 1024) { - throw new Error('MCP private key is invalid') + ): Promise { + this.invalidate(binding.serverId) + const assertCurrent = this.captureOperation(binding.serverId) + try { + if (credential.kind === 'client_secret') { + if (!credential.secret || credential.secret.length > 8192) { + throw new Error('MCP client secret is invalid') + } + assertCurrent() + await this.store.saveClientSecret( + createMcpCredentialKey('client_secret', binding), + credential.secret, + binding + ) + assertCurrent() + } else if (credential.kind === 'enterprise_resource_secret') { + if (!credential.secret || credential.secret.length > 8192) { + throw new Error('MCP enterprise resource secret is invalid') + } + assertCurrent() + await this.store.saveEnterpriseResourceSecret( + createMcpCredentialKey('enterprise_resource_secret', binding), + credential.secret, + binding + ) + assertCurrent() + } else { + if (!credential.privateKey || credential.privateKey.length > 64 * 1024) { + throw new Error('MCP private key is invalid') + } + const fingerprint = createFingerprint(credential.privateKey, credential.algorithm) + assertCurrent() + await this.store.savePrivateKey(createMcpCredentialKey('private_key', binding), { + privateKey: credential.privateKey, + algorithm: credential.algorithm, + fingerprint, + binding + }) + assertCurrent() } - const fingerprint = createFingerprint(credential.privateKey, credential.algorithm) - this.store.savePrivateKey(createMcpCredentialKey('private_key', binding), { - privateKey: credential.privateKey, - algorithm: credential.algorithm, - fingerprint, - binding - }) + const status = await this.getCredentialStatus(binding, credential.kind) + await this.applyRuntimeChange(binding.serverId, 'restart', assertCurrent) + return status + } finally { + assertCurrent() } - return this.getCredentialStatus(binding, credential.kind) } - - removeCredential(binding: McpCredentialBinding, kind: McpCredentialKind): McpCredentialStatus { - this.store.clearEntry(createMcpCredentialKey(kind, binding)) - return this.getCredentialStatus(binding, kind) + async removeCredential( + binding: McpCredentialBinding, + kind: McpCredentialKind + ): Promise { + this.invalidate(binding.serverId) + const assertCurrent = this.captureOperation(binding.serverId) + try { + assertCurrent() + await this.store.clearEntry(createMcpCredentialKey(kind, binding)) + assertCurrent() + const status = await this.getCredentialStatus(binding, kind) + await this.applyRuntimeChange(binding.serverId, 'stop', assertCurrent) + return status + } finally { + assertCurrent() + } } - - clearServerCredentials(serverId: string): void { - this.cancelPendingFlow(serverId) - this.store.clearServerCredentials(serverId) - this.statuses.delete(serverId) + async clearServerCredentials(serverId: string): Promise { + this.invalidate(serverId) + const assertCurrent = this.captureOperation(serverId) + try { + this.cancelPendingFlow(serverId) + assertCurrent() + await this.store.clearServerCredentials(serverId) + assertCurrent() + this.statuses.delete(serverId) + } finally { + assertCurrent() + } } - listEnterpriseProfiles(): McpEnterpriseIdentityProfile[] { return this.requireEnterpriseIdentity().listProfiles() } - - saveEnterpriseProfile(profile: McpEnterpriseIdentityProfile): McpEnterpriseIdentityProfile { - return this.requireEnterpriseIdentity().saveProfile(profile) + async saveEnterpriseProfile( + profile: McpEnterpriseIdentityProfile + ): Promise { + return await this.requireEnterpriseIdentity().saveProfile(profile) } - - removeEnterpriseProfile(profileId: string): void { - this.requireEnterpriseIdentity().removeProfile(profileId) + async removeEnterpriseProfile(profileId: string): Promise { + await this.requireEnterpriseIdentity().removeProfile(profileId) } - - setEnterpriseProfileClientSecret(profileId: string, secret: string): McpEnterpriseIdentityStatus { - return this.requireEnterpriseIdentity().setClientSecret(profileId, secret) + async setEnterpriseProfileClientSecret( + profileId: string, + secret: string + ): Promise { + return await this.requireEnterpriseIdentity().setClientSecret(profileId, secret) } - - getEnterpriseProfileStatus(profileId: string): McpEnterpriseIdentityStatus { - return this.requireEnterpriseIdentity().getStatus(profileId) + async getEnterpriseProfileStatus(profileId: string): Promise { + return await this.requireEnterpriseIdentity().getStatus(profileId) } - - startEnterpriseProfileAuth(profileId: string): Promise { - return this.requireEnterpriseIdentity().startAuth(profileId) + async startEnterpriseProfileAuth(profileId: string): Promise { + return await this.requireEnterpriseIdentity().startAuth(profileId) } - - completeEnterpriseProfileAuthFromCallbackUrl( + async completeEnterpriseProfileAuthFromCallbackUrl( profileId: string, callbackUrl: string ): Promise { - return this.requireEnterpriseIdentity().completeAuthFromCallbackUrl(profileId, callbackUrl) + return await this.requireEnterpriseIdentity().completeAuthFromCallbackUrl( + profileId, + callbackUrl + ) } - - logoutEnterpriseProfile(profileId: string): McpEnterpriseIdentityStatus { - return this.requireEnterpriseIdentity().logout(profileId) + async logoutEnterpriseProfile(profileId: string): Promise { + return await this.requireEnterpriseIdentity().logout(profileId) } - - getUsableAuthorizationExtensions(config: Partial): string[] { - const mode = getAuthorizationMode(config) - if ( - !isRemoteOAuthCapable(config) || - (mode !== 'client_credentials' && mode !== 'private_key_jwt' && mode !== 'cross_app_access') - ) { - return [] - } - - const binding = requireServerBinding(config) - if ( - (mode === 'client_credentials' || mode === 'private_key_jwt') && - this.getSelectedCredentialStatus(binding, mode).configured - ) { - return [AUTH_EXTENSION_CLIENT_CREDENTIALS] - } - if (mode === 'cross_app_access' && this.getSelectedCredentialStatus(binding, mode).configured) { - const profileId = config.authorization?.identityProfileId - if (profileId) { - try { - if (this.enterpriseIdentity?.getStatus(profileId).authenticated) { - return [AUTH_EXTENSION_ENTERPRISE] + async getUsableAuthorizationExtensions(config: Partial): Promise { + const assertCurrent = this.captureOperation(config.serverId) + try { + const mode = getAuthorizationMode(config) + if ( + !isRemoteOAuthCapable(config) || + (mode !== 'client_credentials' && mode !== 'private_key_jwt' && mode !== 'cross_app_access') + ) { + return [] + } + const binding = requireServerBinding(config) + if ( + (mode === 'client_credentials' || mode === 'private_key_jwt') && + (await this.getSelectedCredentialStatus(binding, mode)).configured + ) { + return [AUTH_EXTENSION_CLIENT_CREDENTIALS] + } + if ( + mode === 'cross_app_access' && + (await this.getSelectedCredentialStatus(binding, mode)).configured + ) { + const profileId = config.authorization?.identityProfileId + if (profileId) { + try { + if ((await this.enterpriseIdentity?.getStatus(profileId))?.authenticated) { + return [AUTH_EXTENSION_ENTERPRISE] + } + } catch { + return [] } - } catch { - return [] } } + return [] + } finally { + assertCurrent() } - return [] } - private async getCurrentConfig( serverName: string, fallback: MCPServerConfig @@ -803,7 +988,6 @@ export class McpOAuthManager { fallback ) } - private isInteractiveCredentialCurrent( entry: McpOAuthCredentialEntry, info: OAuthServerInfo @@ -819,7 +1003,6 @@ export class McpOAuthManager { if (!storedIssuer || normalizeUrlIdentifier(storedIssuer) !== liveIssuer) { return false } - const liveResource = info.resourceMetadata?.resource const storedResource = entry.binding?.protectedResourceUrl || entry.discoveryState?.resourceMetadata?.resource @@ -832,83 +1015,58 @@ export class McpOAuthManager { return false } } - private async finalizeInteractiveBinding( config: MCPServerConfig, provider: DeepChatMcpOAuthProvider, info: OAuthServerInfo, clientIdHint?: string ): Promise { - const startingBinding = requireServerBinding(config) - const issuer = assertConfiguredDiscovery(config, info) - const providerBinding = provider.getBinding() - const resourceValue = - info.resourceMetadata?.resource || - providerBinding.protectedResourceUrl || - config.authorization?.protectedResourceUrl - const protectedResourceUrl = resourceValue ? normalizeUrlIdentifier(resourceValue) : undefined - const clientId = - clientIdHint || providerBinding.clientId || config.authorization?.clientId || undefined - - let finalBinding: McpCredentialBinding = { - ...startingBinding, - authorizationServerIssuer: issuer, - protectedResourceUrl, - clientId - } - if (this.settings) { - const servers = await this.settings.getMcpServers() - const currentMatch = Object.entries(servers).find( - ([, current]) => current.serverId === startingBinding.serverId - ) - if (!currentMatch) { - throw new Error('MCP server was removed during OAuth discovery') - } - const [currentServerName, currentConfig] = currentMatch - const currentBinding = requireServerBinding(currentConfig) - if ( - currentBinding.configGeneration !== startingBinding.configGeneration || - currentBinding.bindingHash !== startingBinding.bindingHash || - normalizeUrlIdentifier(currentBinding.endpoint) !== - normalizeUrlIdentifier(startingBinding.endpoint) - ) { - throw new Error('MCP server binding changed during OAuth discovery') - } - - const nextAuthorization = { - ...(currentConfig.authorization || { mode: 'interactive' as const }), + const assertCurrent = this.captureOperation(config.serverId) + try { + const startingBinding = requireServerBinding(config) + const issuer = assertConfiguredDiscovery(config, info) + const providerBinding = provider.getBinding() + const resourceValue = + info.resourceMetadata?.resource || + providerBinding.protectedResourceUrl || + config.authorization?.protectedResourceUrl + const protectedResourceUrl = resourceValue ? normalizeUrlIdentifier(resourceValue) : undefined + const clientId = + clientIdHint || providerBinding.clientId || config.authorization?.clientId || undefined + let finalBinding: McpCredentialBinding = { + ...startingBinding, authorizationServerIssuer: issuer, protectedResourceUrl, clientId } - const needsUpdate = - currentConfig.authorization?.authorizationServerIssuer !== issuer || - currentConfig.authorization?.protectedResourceUrl !== protectedResourceUrl || - currentConfig.authorization?.clientId !== clientId - if (needsUpdate) { - await this.settings.updateMcpServer(currentServerName, { - authorization: nextAuthorization - }) - const updatedConfig = (await this.settings.getMcpServers())[currentServerName] - if (!updatedConfig || updatedConfig.serverId !== startingBinding.serverId) { - throw new Error('MCP server binding could not be finalized after OAuth discovery') - } - finalBinding = requireServerBinding(updatedConfig) - this.onServerBindingChanged?.(startingBinding.serverId) - } else { - finalBinding = currentBinding + if (this.settings) { + assertCurrent() + finalBinding = requireServerBinding( + this.settings.finalizeOAuthBinding(startingBinding, { + ...(config.authorization || { mode: 'interactive' as const }), + authorizationServerIssuer: issuer, + protectedResourceUrl, + clientId + }) + ) + if ( + finalBinding.configGeneration !== startingBinding.configGeneration || + finalBinding.bindingHash !== startingBinding.bindingHash + ) + this.onServerBindingChanged?.(startingBinding.serverId) } + await provider.rebind({ + ...finalBinding, + authorizationServerIssuer: issuer, + protectedResourceUrl, + clientId + }) + assertCurrent() + return provider.getBinding() + } finally { + assertCurrent() } - - provider.rebind({ - ...finalBinding, - authorizationServerIssuer: issuer, - protectedResourceUrl, - clientId - }) - return provider.getBinding() } - private createInteractiveProvider( config: MCPServerConfig, binding: McpCredentialBinding, @@ -917,10 +1075,13 @@ export class McpOAuthManager { state?: string interactive: boolean initialCredentialKey?: string + assertActive(): void } ): DeepChatMcpOAuthProvider { return new DeepChatMcpOAuthProvider({ store: this.store, + openExternal: this.openExternal, + assertActive: options.assertActive, binding, credentialKey: (nextBinding) => createMcpCredentialKey('interactive_oauth', nextBinding), initialCredentialKey: options.initialCredentialKey, @@ -931,167 +1092,186 @@ export class McpOAuthManager { scopes: config.authorization?.scopes }) } - - private findInteractiveCredential( + private async findInteractiveCredential( serverName: string, config: Partial, binding: McpCredentialBinding ) { - const current = this.store.findInteractiveCredential(binding) - if (current) { - return current - } - - const legacyKey = createHash('sha256') - .update(`${serverName}\n${config.type || ''}\n${config.baseUrl || ''}`) - .digest('hex') - const legacy = this.store.load(legacyKey) - if (!legacy) { - return null - } - const issuer = - legacy.discoveryState?.authorizationServerMetadata?.issuer || - legacy.discoveryState?.authorizationServerUrl - if ( - !issuer || - (binding.authorizationServerIssuer && - normalizeMcpOAuthIssuer(binding.authorizationServerIssuer) !== - normalizeMcpOAuthIssuer(issuer)) - ) { - this.store.clearEntry(legacyKey) - return null - } - const migratedBinding: McpCredentialBinding = { - ...binding, - authorizationServerIssuer: normalizeMcpOAuthIssuer(issuer), - protectedResourceUrl: - legacy.discoveryState?.resourceMetadata?.resource || binding.protectedResourceUrl + const assertCurrent = this.captureOperation(binding.serverId) + try { + const current = await this.store.findInteractiveCredential(binding) + assertCurrent() + if (current) { + return current + } + const legacyKey = createHash('sha256') + .update(`${serverName}\n${config.type || ''}\n${config.baseUrl || ''}`) + .digest('hex') + const legacy = await this.store.load(legacyKey) + assertCurrent() + if (!legacy) { + return null + } + const issuer = + legacy.discoveryState?.authorizationServerMetadata?.issuer || + legacy.discoveryState?.authorizationServerUrl + if ( + !issuer || + (binding.authorizationServerIssuer && + normalizeMcpOAuthIssuer(binding.authorizationServerIssuer) !== + normalizeMcpOAuthIssuer(issuer)) + ) { + assertCurrent() + await this.store.clearEntry(legacyKey) + assertCurrent() + return null + } + const migratedBinding: McpCredentialBinding = { + ...binding, + authorizationServerIssuer: normalizeMcpOAuthIssuer(issuer), + protectedResourceUrl: + legacy.discoveryState?.resourceMetadata?.resource || binding.protectedResourceUrl + } + const nextKey = createMcpCredentialKey('interactive_oauth', migratedBinding) + assertCurrent() + return await this.store.moveInteractiveEntry(legacyKey, nextKey, migratedBinding) + } finally { + assertCurrent() } - const nextKey = createMcpCredentialKey('interactive_oauth', migratedBinding) - this.store.saveEntry(nextKey, { - ...legacy, - binding: migratedBinding - }) - this.store.clearEntry(legacyKey) - return { key: nextKey, entry: this.store.load(nextKey)! } } - private async createMachineProvider( config: MCPServerConfig, binding: McpCredentialBinding, info: OAuthServerInfo ): Promise { - const mode = getAuthorizationMode(config) - const clientId = config.authorization?.clientId - if (!clientId) { - throw new Error('MCP machine authorization client ID is required') - } - const methods = supportedTokenAuthMethods(info.authorizationServerMetadata) - const issuer = discoveryIssuer(info) - const scope = config.authorization?.scopes?.join(' ') - - if (mode === 'client_credentials') { - if (!methods.includes('client_secret_basic')) { - throw new Error('MCP authorization server does not support client_secret_basic') - } - const credential = this.store.loadClientSecret( - createMcpCredentialKey('client_secret', binding) - ) - if (!credential) { - throw new Error('MCP client secret is not configured') - } - return bindProviderToProtectedResource( - new ClientCredentialsProvider({ - clientId, - clientSecret: credential.secret, - clientName: 'DeepChat', - scope, - expectedIssuer: issuer - }), - binding - ) - } - - if (mode === 'private_key_jwt') { - if (!methods.includes('private_key_jwt')) { - throw new Error('MCP authorization server does not support private_key_jwt') - } - const credential = this.store.loadPrivateKey(createMcpCredentialKey('private_key', binding)) - if (!credential) { - throw new Error('MCP private key is not configured') + const assertCurrent = this.captureOperation(binding.serverId) + try { + const mode = getAuthorizationMode(config) + const clientId = config.authorization?.clientId + if (!clientId) { + throw new Error('MCP machine authorization client ID is required') } - return bindProviderToProtectedResource( - new PrivateKeyJwtProvider({ - clientId, - privateKey: credential.privateKey, - algorithm: credential.algorithm, - clientName: 'DeepChat', - scope, - expectedIssuer: issuer - }), - binding - ) - } - - if (mode === 'cross_app_access') { - if (!methods.includes('client_secret_basic')) { - throw new Error('MCP authorization server does not support client_secret_basic') + const methods = supportedTokenAuthMethods(info.authorizationServerMetadata) + const issuer = discoveryIssuer(info) + const scope = config.authorization?.scopes?.join(' ') + if (mode === 'client_credentials') { + if (!methods.includes('client_secret_basic')) { + throw new Error('MCP authorization server does not support client_secret_basic') + } + const credential = await this.store.loadClientSecret( + createMcpCredentialKey('client_secret', binding) + ) + assertCurrent() + if (!credential) { + throw new Error('MCP client secret is not configured') + } + return bindProviderToProtectedResource( + new ClientCredentialsProvider({ + clientId, + clientSecret: credential.secret, + clientName: 'DeepChat', + scope, + expectedIssuer: issuer + }), + binding + ) } - const profiles = ( - info.authorizationServerMetadata as AuthorizationServerMetadata & { - authorization_grant_profiles_supported?: string[] + if (mode === 'private_key_jwt') { + if (!methods.includes('private_key_jwt')) { + throw new Error('MCP authorization server does not support private_key_jwt') + } + const credential = await this.store.loadPrivateKey( + createMcpCredentialKey('private_key', binding) + ) + assertCurrent() + if (!credential) { + throw new Error('MCP private key is not configured') } - ).authorization_grant_profiles_supported - if (!profiles?.includes('urn:ietf:params:oauth:grant-profile:id-jag')) { - throw new Error('MCP authorization server does not support the ID-JAG grant profile') + return bindProviderToProtectedResource( + new PrivateKeyJwtProvider({ + clientId, + privateKey: credential.privateKey, + algorithm: credential.algorithm, + clientName: 'DeepChat', + scope, + expectedIssuer: issuer + }), + binding + ) } - const credential = this.store.loadEnterpriseResourceSecret( - createMcpCredentialKey('enterprise_resource_secret', binding) - ) - if (!credential) { - throw new Error('MCP enterprise resource client secret is not configured') + if (mode === 'cross_app_access') { + if (!methods.includes('client_secret_basic')) { + throw new Error('MCP authorization server does not support client_secret_basic') + } + const profiles = ( + info.authorizationServerMetadata as AuthorizationServerMetadata & { + authorization_grant_profiles_supported?: string[] + } + ).authorization_grant_profiles_supported + if (!profiles?.includes('urn:ietf:params:oauth:grant-profile:id-jag')) { + throw new Error('MCP authorization server does not support the ID-JAG grant profile') + } + const credential = await this.store.loadEnterpriseResourceSecret( + createMcpCredentialKey('enterprise_resource_secret', binding) + ) + assertCurrent() + if (!credential) { + throw new Error('MCP enterprise resource client secret is not configured') + } + return bindProviderToProtectedResource( + await this.requireEnterpriseIdentity().createCrossAppProvider( + config, + credential.secret, + issuer + ), + binding + ) } - return bindProviderToProtectedResource( - await this.requireEnterpriseIdentity().createCrossAppProvider( - config, - credential.secret, - issuer - ), - binding - ) + throw new Error(`Unsupported MCP authorization mode: ${mode}`) + } finally { + assertCurrent() } - - throw new Error(`Unsupported MCP authorization mode: ${mode}`) } - - private getCredentialStatus( + private async getCredentialStatus( binding: McpCredentialBinding, kind: McpCredentialKind - ): McpCredentialStatus { - const status = this.store.getCredentialRecordStatus(createMcpCredentialKey(kind, binding), kind) - return { - serverId: binding.serverId, - kind, - configured: status.configured, - persistent: this.store.isPersistent(), - updatedAt: status.updatedAt, - fingerprint: status.fingerprint + ): Promise { + const assertCurrent = this.captureOperation(binding.serverId) + try { + const status = await this.store.getCredentialRecordStatus( + createMcpCredentialKey(kind, binding), + kind + ) + assertCurrent() + return { + serverId: binding.serverId, + kind, + configured: status.configured, + persistent: await this.store.isPersistent(), + updatedAt: status.updatedAt, + fingerprint: status.fingerprint + } + } finally { + assertCurrent() } } - - private getSelectedCredentialStatus( + private async getSelectedCredentialStatus( binding: McpCredentialBinding, mode: McpAuthorizationMode - ): McpCredentialStatus { - const kind: McpCredentialKind = - mode === 'private_key_jwt' - ? 'private_key' - : mode === 'cross_app_access' - ? 'enterprise_resource_secret' - : 'client_secret' - return this.getCredentialStatus(binding, kind) + ): Promise { + const assertCurrent = this.captureOperation(binding.serverId) + try { + const kind: McpCredentialKind = + mode === 'private_key_jwt' + ? 'private_key' + : mode === 'cross_app_access' + ? 'enterprise_resource_secret' + : 'client_secret' + return await this.getCredentialStatus(binding, kind) + } finally { + assertCurrent() + } } - private async finishAuthFlow( flow: PendingMcpOAuthFlow, code: string, @@ -1103,56 +1283,74 @@ export class McpOAuthManager { authorizationCode: code, iss }) - this.finishAuthenticatedFlow(flow) + await this.finishAuthenticatedFlow(flow) } catch (error) { - this.failAuthFlow(flow, error) + await this.failAuthFlow(flow, error) } } - - private finishAuthenticatedFlow(flow: PendingMcpOAuthFlow): void { - if (this.pendingFlows.get(flow.serverId) !== flow) { - return - } + private async finishAuthenticatedFlow(flow: PendingMcpOAuthFlow): Promise { + if (this.stopped || this.pendingFlows.get(flow.serverId) !== flow) return + const assertCurrent = flow.assertActive + const entry = await this.store.findInteractiveCredential(flow.binding) + assertCurrent() + const storage = await this.getStorageSnapshot() + assertCurrent() + if (this.pendingFlows.get(flow.serverId) !== flow) return this.pendingFlows.delete(flow.serverId) flow.callbackSession.close() - const entry = this.store.findInteractiveCredential(flow.binding) const current = this.statuses.get(flow.serverId) - this.setStatus({ - serverName: flow.serverName, - serverId: flow.serverId, - state: 'authenticated', - authenticated: true, - updatedAt: entry?.entry.updatedAt || Date.now(), - storage: this.store.getStorageState(), - persistent: this.store.isPersistent(), - mode: 'interactive', - ...(current?.credential ? { credential: current.credential } : {}) - }) - - void Promise.resolve(this.onAuthenticated?.(flow.serverName)).catch((error) => { - logger.warn( - '[MCP OAuth] Failed to restart server after authentication:', - sanitizeError(error) - ) - }) + this.setStatus( + { + serverName: flow.serverName, + serverId: flow.serverId, + state: 'authenticated', + authenticated: true, + updatedAt: entry?.entry.updatedAt || Date.now(), + ...storage, + mode: 'interactive', + ...(current?.credential ? { credential: current.credential } : {}) + }, + assertCurrent + ) + void Promise.resolve() + .then(() => { + assertCurrent() + return this.applyRuntimeChange(flow.serverId, 'restart', assertCurrent) + }) + .catch((error) => { + if (!this.stopped) + logger.warn( + '[MCP OAuth] Failed to restart server after authentication:', + sanitizeError(error) + ) + }) } - private failAuthFlow(flow: PendingMcpOAuthFlow, error: unknown): void { - if (this.pendingFlows.get(flow.serverId) !== flow) { - return + private async failAuthFlow(flow: PendingMcpOAuthFlow, error: unknown): Promise { + if (this.stopped || this.pendingFlows.get(flow.serverId) !== flow) return + const assertCurrent = flow.assertActive + try { + const storage = await this.getStorageSnapshot() + assertCurrent() + if (this.pendingFlows.get(flow.serverId) !== flow) return + this.pendingFlows.delete(flow.serverId) + flow.callbackSession.close() + this.setStatus( + { + serverName: flow.serverName, + serverId: flow.serverId, + state: 'error', + authenticated: false, + error: sanitizeError(error), + ...storage, + mode: 'interactive' + }, + assertCurrent + ) + } catch (failure) { + if (this.stopped || this.pendingFlows.get(flow.serverId) !== flow) return + throw failure } - this.pendingFlows.delete(flow.serverId) - flow.callbackSession.close() - this.setStatus({ - serverName: flow.serverName, - serverId: flow.serverId, - state: 'error', - authenticated: false, - error: sanitizeError(error), - storage: this.store.getStorageState(), - persistent: this.store.isPersistent(), - mode: 'interactive' - }) } private cancelPendingFlow(serverId: string): void { @@ -1163,25 +1361,38 @@ export class McpOAuthManager { pending.callbackSession.close() this.pendingFlows.delete(serverId) } - - private buildStatus( + private async buildStatus( serverName: string, binding: McpCredentialBinding, mode: McpAuthorizationMode, state: Pick & Partial> - ): McpServerAuthStatus { + ): Promise { return { serverName, serverId: binding.serverId, ...state, - storage: this.store.getStorageState(), - persistent: this.store.isPersistent(), + ...(await this.getStorageSnapshot()), mode } } + private async applyRuntimeChange( + serverId: string, + action: 'restart' | 'stop', + assertCurrent: () => void + ): Promise { + assertCurrent() + await this.onRuntimeChange?.(serverId, action, assertCurrent) + assertCurrent() + } - private setStatus(status: McpServerAuthStatus): void { + private async getStorageSnapshot() { + const storage = await this.store.getStorageState() + return { storage, persistent: storage === 'safeStorage' } + } + + private setStatus(status: McpServerAuthStatus, assertCurrent: () => void): void { + assertCurrent() if (status.serverId) { this.statuses.set(status.serverId, status) } @@ -1191,17 +1402,14 @@ export class McpOAuthManager { version: Date.now() }) } - private requireEnterpriseIdentity(): McpEnterpriseIdentityManager { if (!this.enterpriseIdentity) { throw new Error('MCP enterprise identity support is not configured') } return this.enterpriseIdentity } - private getDefaultRedirectUri(): string { return `http://localhost:${MCP_OAUTH_REDIRECT_PORT}${MCP_OAUTH_REDIRECT_PATH}` } } - export { isRemoteOAuthCapable, resolveOAuthLoopbackCallbackUrl } diff --git a/src/main/mcp/mcpOAuthProvider.ts b/src/main/mcp/mcpOAuthProvider.ts index 4307f138e3..c3788bc0fb 100644 --- a/src/main/mcp/mcpOAuthProvider.ts +++ b/src/main/mcp/mcpOAuthProvider.ts @@ -1,4 +1,3 @@ -import { shell } from 'electron' import type { OAuthClientInformationContext, OAuthClientMetadata, @@ -13,6 +12,8 @@ import { normalizeMcpOAuthIssuer, type McpOAuthCredentialStore } from './oauthCr export type DeepChatMcpOAuthProviderOptions = { store: McpOAuthCredentialStore + openExternal(url: string): Promise + assertActive(): void binding: McpCredentialBinding credentialKey(binding: McpCredentialBinding): string initialCredentialKey?: string @@ -40,6 +41,12 @@ export class DeepChatMcpOAuthProvider implements OAuthClientProvider { private readonly interactive: boolean private activeBinding: McpCredentialBinding private activeCredentialKey: string + private operations: Promise = Promise.resolve() + private pending = 0 + private pendingNormal = 0 + private callbackValidation: { issuer?: string; issParameterSupported: boolean } = { + issParameterSupported: false + } constructor(private readonly options: DeepChatMcpOAuthProviderOptions) { this.store = options.store @@ -74,158 +81,189 @@ export class DeepChatMcpOAuthProvider implements OAuthClientProvider { clientInformation( context?: OAuthClientInformationContext - ): StoredOAuthClientInformation | undefined { - return this.loadForContext(context)?.clientInformation + ): Promise { + return this.perform(async () => (await this.loadForContext(context))?.clientInformation) } saveClientInformation( clientInformation: StoredOAuthClientInformation, context?: OAuthClientInformationContext - ): void { - this.activateContext(context) - this.moveActiveEntry({ - ...this.activeBinding, - clientId: clientInformation.client_id - }) - this.store.saveEntry(this.activeCredentialKey, { - clientInformation, - binding: this.activeBinding + ): Promise { + return this.perform(async () => { + await this.activateContext(context) + await this.moveActiveEntry({ ...this.activeBinding, clientId: clientInformation.client_id }) + this.options.assertActive() + await this.store.saveEntry(this.activeCredentialKey, { + clientInformation, + binding: this.activeBinding + }) }) } - tokens(context?: OAuthClientInformationContext): StoredOAuthTokens | undefined { - return this.loadForContext(context)?.tokens + tokens(context?: OAuthClientInformationContext): Promise { + return this.perform(async () => (await this.loadForContext(context))?.tokens) } - saveTokens(tokens: StoredOAuthTokens, context?: OAuthClientInformationContext): void { - this.activateContext(context) - this.store.saveEntry(this.activeCredentialKey, { - tokens, - binding: this.activeBinding + saveTokens(tokens: StoredOAuthTokens, context?: OAuthClientInformationContext): Promise { + return this.perform(async () => { + await this.activateContext(context) + this.options.assertActive() + await this.store.saveEntry(this.activeCredentialKey, { tokens, binding: this.activeBinding }) }) } - async redirectToAuthorization(authorizationUrl: URL): Promise { - if (!this.interactive) { - throw new Error('MCP OAuth authentication is required') - } - if ( - authorizationUrl.protocol !== 'https:' && - !(authorizationUrl.protocol === 'http:' && isLoopbackHost(authorizationUrl.hostname)) - ) { - throw new Error('MCP OAuth authorization URL must use HTTPS or loopback HTTP') - } + redirectToAuthorization(authorizationUrl: URL): Promise { + return this.perform(async () => { + if (!this.interactive) throw new Error('MCP OAuth authentication is required') + if ( + authorizationUrl.protocol !== 'https:' && + !(authorizationUrl.protocol === 'http:' && isLoopbackHost(authorizationUrl.hostname)) + ) + throw new Error('MCP OAuth authorization URL must use HTTPS or loopback HTTP') + await this.options.openExternal(authorizationUrl.toString()) + }) + } - await shell.openExternal(authorizationUrl.toString()) + saveCodeVerifier(codeVerifier: string): Promise { + return this.perform(async () => { + this.options.assertActive() + await this.store.saveEntry(this.activeCredentialKey, { + codeVerifier, + binding: this.activeBinding + }) + }) } - saveCodeVerifier(codeVerifier: string): void { - this.store.saveEntry(this.activeCredentialKey, { - codeVerifier, - binding: this.activeBinding + codeVerifier(): Promise { + return this.perform(async () => { + const verifier = (await this.store.load(this.activeCredentialKey))?.codeVerifier + if (!verifier) throw new Error('MCP OAuth code verifier is unavailable') + return verifier }) } - codeVerifier(): string { - const codeVerifier = this.store.load(this.activeCredentialKey)?.codeVerifier - if (!codeVerifier) { - throw new Error('MCP OAuth code verifier is unavailable') - } - return codeVerifier + invalidateCredentials( + scope: 'all' | 'client' | 'tokens' | 'verifier' | 'discovery' + ): Promise { + return this.perform(async () => { + await this.store.clearEntryScope(this.activeCredentialKey, scope) + }, true) } - invalidateCredentials(scope: 'all' | 'client' | 'tokens' | 'verifier' | 'discovery'): void { - this.store.clearEntryScope(this.activeCredentialKey, scope) + saveDiscoveryState(state: OAuthDiscoveryState): Promise { + return this.perform(async () => { + await this.moveActiveEntry({ + ...this.activeBinding, + authorizationServerIssuer: discoveryIssuer(state), + protectedResourceUrl: discoveryResource(state, this.activeBinding.protectedResourceUrl) + }) + this.options.assertActive() + await this.store.saveEntry(this.activeCredentialKey, { + discoveryState: state, + binding: this.activeBinding + }) + this.options.assertActive() + this.rememberCallbackValidation(state) + }) } - saveDiscoveryState(state: OAuthDiscoveryState): void { - const nextBinding: McpCredentialBinding = { - ...this.activeBinding, - authorizationServerIssuer: discoveryIssuer(state), - protectedResourceUrl: discoveryResource(state, this.activeBinding.protectedResourceUrl) - } - this.moveActiveEntry(nextBinding) - this.store.saveEntry(this.activeCredentialKey, { - discoveryState: state, - binding: this.activeBinding + discoveryState(): Promise { + return this.perform(async () => { + const state = (await this.store.load(this.activeCredentialKey))?.discoveryState + this.options.assertActive() + this.rememberCallbackValidation(state) + return state }) } - discoveryState(): OAuthDiscoveryState | undefined { - return this.store.load(this.activeCredentialKey)?.discoveryState + getCallbackValidation(): { issuer?: string; issParameterSupported: boolean } { + this.options.assertActive() + return { ...this.callbackValidation } + } + + private rememberCallbackValidation(state?: OAuthDiscoveryState): void { + this.callbackValidation = { + issuer: state?.authorizationServerMetadata?.issuer || state?.authorizationServerUrl, + issParameterSupported: + state?.authorizationServerMetadata?.authorization_response_iss_parameter_supported === true + } } getBinding(): McpCredentialBinding { + this.options.assertActive() return { ...this.activeBinding } } - rebind(binding: McpCredentialBinding): void { - this.moveActiveEntry(binding) + rebind(binding: McpCredentialBinding): Promise { + return this.perform(() => this.moveActiveEntry(binding)) } - private loadForContext(context?: OAuthClientInformationContext) { - if (!context) { - return this.store.load(this.activeCredentialKey) || undefined - } + private async perform(operation: () => Promise, control = false): Promise { + this.options.assertActive() + if (this.pending >= 32 || (!control && this.pendingNormal >= 24)) + return Promise.reject(new Error('MCP OAuth provider operation queue is full')) + this.pending++ + if (!control) this.pendingNormal++ + const result = this.operations.then(async () => { + this.options.assertActive() + const value = await operation() + this.options.assertActive() + return value + }) + this.operations = result.then( + () => undefined, + () => undefined + ) + return result.finally(() => { + this.pending-- + if (!control) this.pendingNormal-- + this.options.assertActive() + }) + } + private async loadForContext(context?: OAuthClientInformationContext) { + if (!context) + return this.store.load(this.activeCredentialKey).then((value) => value || undefined) const contextIssuer = normalizeMcpOAuthIssuer(context.issuer) - const contextualBinding = { - ...this.activeBinding, - authorizationServerIssuer: contextIssuer - } - const contextualKey = this.options.credentialKey(contextualBinding) - const contextual = this.store.load(contextualKey) + const binding = { ...this.activeBinding, authorizationServerIssuer: contextIssuer } + const key = this.options.credentialKey(binding) + const contextual = await this.store.load(key) + this.options.assertActive() if (contextual) { - this.activeBinding = contextualBinding - this.activeCredentialKey = contextualKey + this.activeBinding = binding + this.activeCredentialKey = key return contextual } - - const active = this.store.load(this.activeCredentialKey) - const storedIssuer = + const active = await this.store.load(this.activeCredentialKey) + this.options.assertActive() + const issuer = active?.binding?.authorizationServerIssuer || active?.discoveryState?.authorizationServerMetadata?.issuer || active?.discoveryState?.authorizationServerUrl || active?.tokens?.issuer || active?.clientInformation?.issuer - if (active && storedIssuer && normalizeMcpOAuthIssuer(storedIssuer) === contextIssuer) { - this.moveActiveEntry(contextualBinding) - return this.store.load(this.activeCredentialKey) || undefined + if (active && issuer && normalizeMcpOAuthIssuer(issuer) === contextIssuer) { + await this.moveActiveEntry(binding) + return (await this.store.load(this.activeCredentialKey)) || undefined } - return undefined } - private activateContext(context?: OAuthClientInformationContext): void { - if (!context) { - return - } - const contextIssuer = normalizeMcpOAuthIssuer(context.issuer) - if (this.activeBinding.authorizationServerIssuer === contextIssuer) { - return - } - this.moveActiveEntry({ - ...this.activeBinding, - authorizationServerIssuer: contextIssuer - }) + private async activateContext(context?: OAuthClientInformationContext): Promise { + this.options.assertActive() + if (!context) return + const issuer = normalizeMcpOAuthIssuer(context.issuer) + if (this.activeBinding.authorizationServerIssuer !== issuer) + await this.moveActiveEntry({ ...this.activeBinding, authorizationServerIssuer: issuer }) } - private moveActiveEntry(nextBinding: McpCredentialBinding): void { - const nextKey = this.options.credentialKey(nextBinding) - if (nextKey === this.activeCredentialKey) { - this.activeBinding = nextBinding - return - } - - const current = this.store.load(this.activeCredentialKey) - if (current) { - this.store.saveEntry(nextKey, { - ...current, - binding: nextBinding - }) - this.store.clearEntry(this.activeCredentialKey) - } - this.activeBinding = nextBinding - this.activeCredentialKey = nextKey + private async moveActiveEntry(binding: McpCredentialBinding): Promise { + this.options.assertActive() + const key = this.options.credentialKey(binding) + if (key !== this.activeCredentialKey) + await this.store.moveInteractiveEntry(this.activeCredentialKey, key, binding) + this.options.assertActive() + this.activeBinding = binding + this.activeCredentialKey = key } } diff --git a/src/main/mcp/oauthCredentialStore.ts b/src/main/mcp/oauthCredentialStore.ts index b377bdfab4..b458446e2d 100644 --- a/src/main/mcp/oauthCredentialStore.ts +++ b/src/main/mcp/oauthCredentialStore.ts @@ -1,7 +1,7 @@ import * as fs from 'node:fs' import * as path from 'node:path' import { randomBytes } from 'node:crypto' -import { app, safeStorage } from 'electron' +import type { CredentialCryptoPort } from '@/config/secretStore' import type { OAuthDiscoveryState, StoredOAuthClientInformation, @@ -153,108 +153,149 @@ const isCredentialBinding = (value: unknown): value is McpCredentialBinding => { ) } -const isLinuxBasicTextBackend = (): boolean => { - if (process.platform !== 'linux') { - return false - } - - try { - return safeStorage.getSelectedStorageBackend() === 'basic_text' - } catch { - return true - } -} +const MAX_PENDING_MUTATIONS = 32 +const MAX_NORMAL_MUTATIONS = 24 export class McpOAuthCredentialStore { private readonly filePath: string + private readonly crypto: CredentialCryptoPort private loaded = false private loadFailed = false + private loading: Promise | null = null + private mutations: Promise = Promise.resolve() + private pendingMutations = 0 + private pendingNormalMutations = 0 + private generation = 0 + private stopped = false private records: McpCredentialData = {} - constructor(filePath?: string) { - this.filePath = filePath || path.join(app.getPath('userData'), 'mcp-oauth', 'credentials.json') + constructor(options: { filePath: string; crypto: CredentialCryptoPort }) { + this.filePath = options.filePath + this.crypto = options.crypto + } + + stop(): void { + this.stopped = true + this.generation++ + this.records = {} + } + + private captureOperation(): () => void { + const generation = this.generation + const check = () => { + if (this.stopped || generation !== this.generation) + throw new Error('MCP credential operation interrupted') + } + check() + return check } - getStorageState(): McpOAuthCredentialStorage { + async getStorageState(): Promise { + const check = this.captureOperation() try { - if (!safeStorage.isEncryptionAvailable() || isLinuxBasicTextBackend()) { - return 'memory' - } - return 'safeStorage' + const available = await this.crypto.isAvailable() + check() + return available ? 'safeStorage' : 'memory' } catch { + check() return 'memory' } } - isPersistent(): boolean { - return this.getStorageState() === 'safeStorage' + async isPersistent(): Promise { + return (await this.getStorageState()) === 'safeStorage' } - load(key: string): McpOAuthCredentialEntry | null { - const record = this.loadRecord(key) - return record?.credentialClass === 'interactive_oauth' ? record.value : null + load(key: string): Promise { + return this.read((records) => { + const record = records[key] + return record?.credentialClass === 'interactive_oauth' ? record.value : null + }) } - saveEntry(key: string, entry: Partial): McpOAuthCredentialEntry { - const current = this.load(key) - const next: McpOAuthCredentialEntry = { - ...(current || { updatedAt: Date.now() }), - ...entry, - updatedAt: Date.now() - } - this.saveRecord(key, { - credentialClass: 'interactive_oauth', - value: next + saveEntry( + key: string, + entry: Partial + ): Promise { + return this.write((records) => { + const record = records[key] + const current = record?.credentialClass === 'interactive_oauth' ? record.value : undefined + const next = { ...current, ...entry, updatedAt: Date.now() } + return { + records: this.withRecord(records, key, { + credentialClass: 'interactive_oauth', + value: next + }), + value: next + } }) - return next } - findInteractiveCredential( + moveInteractiveEntry( + fromKey: string, + toKey: string, binding: McpCredentialBinding - ): { key: string; entry: McpOAuthCredentialEntry } | null { - this.ensureLoaded() - const matches = Object.entries(this.records).filter(([, record]) => { - if (record.credentialClass !== 'interactive_oauth' || !record.value.binding) { - return false + ): Promise<{ key: string; entry: McpOAuthCredentialEntry } | null> { + return this.write((records) => { + const source = records[fromKey] + if (source?.credentialClass !== 'interactive_oauth') return { records, value: null } + const target = records[toKey] + const entry = { + ...(target?.credentialClass === 'interactive_oauth' ? target.value : {}), + ...source.value, + binding, + updatedAt: Date.now() } - const stored = record.value.binding - return ( - stored.serverId === binding.serverId && - stored.configGeneration === binding.configGeneration && - stored.bindingHash === binding.bindingHash && - stored.endpoint === binding.endpoint && - (binding.protectedResourceUrl === undefined || - stored.protectedResourceUrl === binding.protectedResourceUrl) && - (binding.authorizationServerIssuer === undefined || - (stored.authorizationServerIssuer !== undefined && - normalizeMcpOAuthIssuer(stored.authorizationServerIssuer) === - normalizeMcpOAuthIssuer(binding.authorizationServerIssuer))) && - (binding.clientId === undefined || stored.clientId === binding.clientId) - ) + const next = this.withRecord(records, toKey, { + credentialClass: 'interactive_oauth', + value: entry + }) + if (fromKey !== toKey) delete next[fromKey] + return { records: next, value: { key: toKey, entry } } + }) + } + + findInteractiveCredential( + binding: McpCredentialBinding + ): Promise<{ key: string; entry: McpOAuthCredentialEntry } | null> { + return this.read((records) => { + const matches = Object.entries(records).filter(([, record]) => { + if (record.credentialClass !== 'interactive_oauth' || !record.value.binding) return false + const stored = record.value.binding + return ( + stored.serverId === binding.serverId && + stored.configGeneration === binding.configGeneration && + stored.bindingHash === binding.bindingHash && + stored.endpoint === binding.endpoint && + (binding.protectedResourceUrl === undefined || + stored.protectedResourceUrl === binding.protectedResourceUrl) && + (binding.authorizationServerIssuer === undefined || + (stored.authorizationServerIssuer !== undefined && + normalizeMcpOAuthIssuer(stored.authorizationServerIssuer) === + normalizeMcpOAuthIssuer(binding.authorizationServerIssuer))) && + (binding.clientId === undefined || stored.clientId === binding.clientId) + ) + }) + if (matches.length !== 1) return null + const [key, record] = matches[0] + return { key, entry: (record as InteractiveCredentialRecord).value } }) - if (matches.length !== 1) { - return null - } - const [key, record] = matches[0] - return { - key, - entry: (record as InteractiveCredentialRecord).value - } } saveClientSecret( key: string, secret: string, binding: McpCredentialBinding - ): McpSecretCredential { + ): Promise { const value = { secret, binding, updatedAt: Date.now() } - this.saveRecord(key, { credentialClass: 'client_secret', value }) - return value + return this.saveRecord(key, { credentialClass: 'client_secret', value }) } - loadClientSecret(key: string): McpSecretCredential | null { - const record = this.loadRecord(key) - return record?.credentialClass === 'client_secret' ? record.value : null + loadClientSecret(key: string): Promise { + return this.read((records) => { + const record = records[key] + return record?.credentialClass === 'client_secret' ? record.value : null + }) } savePrivateKey( @@ -265,50 +306,47 @@ export class McpOAuthCredentialStore { fingerprint: string binding: McpCredentialBinding } - ): McpPrivateKeyCredential { + ): Promise { const value = { ...input, updatedAt: Date.now() } - this.saveRecord(key, { credentialClass: 'private_key', value }) - return value + return this.saveRecord(key, { credentialClass: 'private_key', value }) } - loadPrivateKey(key: string): McpPrivateKeyCredential | null { - const record = this.loadRecord(key) - return record?.credentialClass === 'private_key' ? record.value : null + loadPrivateKey(key: string): Promise { + return this.read((records) => { + const record = records[key] + return record?.credentialClass === 'private_key' ? record.value : null + }) } saveEnterpriseResourceSecret( key: string, secret: string, binding: McpCredentialBinding - ): McpSecretCredential { + ): Promise { const value = { secret, binding, updatedAt: Date.now() } - this.saveRecord(key, { - credentialClass: 'enterprise_resource_secret', - value - }) - return value + return this.saveRecord(key, { credentialClass: 'enterprise_resource_secret', value }) } - loadEnterpriseResourceSecret(key: string): McpSecretCredential | null { - const record = this.loadRecord(key) - return record?.credentialClass === 'enterprise_resource_secret' ? record.value : null + loadEnterpriseResourceSecret(key: string): Promise { + return this.read((records) => { + const record = records[key] + return record?.credentialClass === 'enterprise_resource_secret' ? record.value : null + }) } saveEnterpriseIdentity( key: string, - value: Omit - ): McpEnterpriseIdentityCredential { - const next = { ...value, updatedAt: Date.now() } - this.saveRecord(key, { - credentialClass: 'enterprise_identity', - value: next - }) - return next + input: Omit + ): Promise { + const value = { ...input, updatedAt: Date.now() } + return this.saveRecord(key, { credentialClass: 'enterprise_identity', value }) } - loadEnterpriseIdentity(key: string): McpEnterpriseIdentityCredential | null { - const record = this.loadRecord(key) - return record?.credentialClass === 'enterprise_identity' ? record.value : null + loadEnterpriseIdentity(key: string): Promise { + return this.read((records) => { + const record = records[key] + return record?.credentialClass === 'enterprise_identity' ? record.value : null + }) } saveEnterpriseIdentityClientSecret( @@ -319,214 +357,241 @@ export class McpOAuthCredentialStore { clientId: string secret: string } - ): void { - this.saveRecord(key, { + ): Promise { + return this.saveRecord(key, { credentialClass: 'enterprise_identity_client_secret', value: { ...value, updatedAt: Date.now() } - }) + }).then(() => undefined) } - loadEnterpriseIdentityClientSecret(key: string): string | null { - const record = this.loadRecord(key) - return record?.credentialClass === 'enterprise_identity_client_secret' - ? record.value.secret - : null + loadEnterpriseIdentityClientSecret(key: string): Promise { + return this.read((records) => { + const record = records[key] + return record?.credentialClass === 'enterprise_identity_client_secret' + ? record.value.secret + : null + }) } getCredentialRecordStatus( key: string, kind: McpCredentialKind - ): { configured: boolean; updatedAt?: number; fingerprint?: string } { - const record = this.loadRecord(key) - if (!record || record.credentialClass !== kind) { - return { configured: false } - } - - return { - configured: true, - updatedAt: record.value.updatedAt, - ...(record.credentialClass === 'private_key' ? { fingerprint: record.value.fingerprint } : {}) - } + ): Promise<{ configured: boolean; updatedAt?: number; fingerprint?: string }> { + return this.read((records) => { + const record = records[key] + if (!record || record.credentialClass !== kind) return { configured: false } + return { + configured: true, + updatedAt: record.value.updatedAt, + ...(record.credentialClass === 'private_key' + ? { fingerprint: record.value.fingerprint } + : {}) + } + }) } - clearEntry(key: string): void { - this.ensureLoaded() - this.assertWritable() - if (!this.records[key]) { - return - } - delete this.records[key] - this.persist() + clearEntry(key: string): Promise { + return this.write((records) => { + if (!records[key]) return { records, value: undefined } + const next = { ...records } + delete next[key] + return { records: next, value: undefined } + }, true) } clearEntryScope( key: string, scope: 'all' | 'client' | 'tokens' | 'verifier' | 'discovery' - ): void { - if (scope === 'all') { - this.clearEntry(key) - return - } - - const current = this.load(key) - if (!current) { - return - } - - const next: McpOAuthCredentialEntry = { ...current, updatedAt: Date.now() } - if (scope === 'client') { - delete next.clientInformation - } else if (scope === 'tokens') { - delete next.tokens - } else if (scope === 'verifier') { - delete next.codeVerifier - } else if (scope === 'discovery') { - delete next.discoveryState - } - - this.saveRecord(key, { - credentialClass: 'interactive_oauth', - value: next - }) + ): Promise { + if (scope === 'all') return this.clearEntry(key) + return this.write((records) => { + const record = records[key] + if (record?.credentialClass !== 'interactive_oauth') return { records, value: undefined } + const value = { ...record.value, updatedAt: Date.now() } + if (scope === 'client') delete value.clientInformation + else if (scope === 'tokens') delete value.tokens + else if (scope === 'verifier') delete value.codeVerifier + else if (scope === 'discovery') delete value.discoveryState + return { + records: this.withRecord(records, key, { credentialClass: 'interactive_oauth', value }), + value: undefined + } + }, true) } - clearServerCredentials(serverId: string): void { - this.ensureLoaded() - this.assertWritable() - let changed = false - for (const [key, record] of Object.entries(this.records)) { - if ( - (record.credentialClass === 'interactive_oauth' && - record.value.binding?.serverId === serverId) || - (record.credentialClass === 'client_secret' && - record.value.binding.serverId === serverId) || - (record.credentialClass === 'private_key' && record.value.binding.serverId === serverId) || - (record.credentialClass === 'enterprise_resource_secret' && - record.value.binding.serverId === serverId) - ) { - delete this.records[key] - changed = true + clearServerCredentials(serverId: string): Promise { + return this.write((records) => { + const next = { ...records } + for (const [key, record] of Object.entries(records)) { + if ( + (record.credentialClass === 'interactive_oauth' && + record.value.binding?.serverId === serverId) || + ((record.credentialClass === 'client_secret' || + record.credentialClass === 'private_key' || + record.credentialClass === 'enterprise_resource_secret') && + record.value.binding.serverId === serverId) + ) + delete next[key] } - } - if (changed) { - this.persist() - } + return { + records: Object.keys(next).length === Object.keys(records).length ? records : next, + value: undefined + } + }, true) } - clearEnterpriseProfileCredentials(profileId: string): void { - this.ensureLoaded() - this.assertWritable() - let changed = false - for (const [key, record] of Object.entries(this.records)) { - if ( - (record.credentialClass === 'enterprise_identity' || - record.credentialClass === 'enterprise_identity_client_secret') && - record.value.profileId === profileId - ) { - delete this.records[key] - changed = true + clearEnterpriseProfileCredentials(profileId: string): Promise { + return this.write((records) => { + const next = { ...records } + for (const [key, record] of Object.entries(records)) { + if ( + (record.credentialClass === 'enterprise_identity' || + record.credentialClass === 'enterprise_identity_client_secret') && + record.value.profileId === profileId + ) + delete next[key] } - } - if (changed) { - this.persist() - } + return { + records: Object.keys(next).length === Object.keys(records).length ? records : next, + value: undefined + } + }, true) + } + + private async read(select: (records: McpCredentialData) => T): Promise { + const check = this.captureOperation() + await this.ensureLoaded() + await this.mutations + check() + return select(this.records) } - private loadRecord(key: string): StoredCredentialRecord | null { - this.ensureLoaded() - return this.records[key] || null + private saveRecord( + key: string, + record: T + ): Promise { + return this.write((records) => ({ + records: this.withRecord(records, key, record), + value: record.value + })) } - private saveRecord(key: string, record: StoredCredentialRecord): void { - this.ensureLoaded() - this.assertWritable() - if (!isBoundedString(key, MAX_CREDENTIAL_KEY_BYTES)) { + private withRecord( + records: McpCredentialData, + key: string, + record: StoredCredentialRecord + ): McpCredentialData { + if (!isBoundedString(key, MAX_CREDENTIAL_KEY_BYTES)) throw new Error('MCP credential key is invalid') - } const normalized = this.normalizeRecord(record) - if (!normalized) { - throw new Error('MCP credential record is invalid') - } - const next = { ...this.records, [key]: normalized } + if (!normalized) throw new Error('MCP credential record is invalid') + return { ...records, [key]: normalized } + } + + private async write( + mutate: (records: McpCredentialData) => { records: McpCredentialData; value: T }, + control = false + ): Promise { + const check = this.captureOperation() if ( - Object.keys(next).length > MAX_CREDENTIAL_RECORDS || - Buffer.byteLength(JSON.stringify(next), 'utf8') > MAX_CREDENTIAL_PAYLOAD_BYTES - ) { - throw new Error('MCP credential store limit exceeded') - } - this.records = next - this.persist() + this.pendingMutations >= MAX_PENDING_MUTATIONS || + (!control && this.pendingNormalMutations >= MAX_NORMAL_MUTATIONS) + ) + return Promise.reject(new Error('MCP credential mutation queue is full')) + this.pendingMutations++ + if (!control) this.pendingNormalMutations++ + const result = this.mutations.then(async () => { + check() + await this.ensureLoaded() + check() + this.assertWritable() + const next = mutate(this.records) + if (next.records !== this.records) { + if ( + Object.keys(next.records).length > MAX_CREDENTIAL_RECORDS || + Buffer.byteLength(JSON.stringify(next.records), 'utf8') > MAX_CREDENTIAL_PAYLOAD_BYTES + ) + throw new Error('MCP credential store limit exceeded') + await this.persist(next.records, check) + check() + this.records = next.records + } + return next.value + }) + this.mutations = result.then( + () => undefined, + () => undefined + ) + return result.finally(() => { + this.pendingMutations-- + if (!control) this.pendingNormalMutations-- + check() + }) } - private ensureLoaded(): void { - if (this.loaded) { - return - } - this.loaded = true + private ensureLoaded(): Promise { + this.captureOperation() + if (this.loaded) return Promise.resolve() + if (this.loading) return this.loading + const loading = this.loadFile().finally(() => { + if (this.loading === loading) this.loading = null + }) + this.loading = loading + return loading + } + private async loadFile(): Promise { + const check = this.captureOperation() try { - if (!fs.existsSync(this.filePath)) { - return - } - - if (fs.statSync(this.filePath).size > MAX_CREDENTIAL_FILE_BYTES) { + if (!fs.existsSync(this.filePath)) return + if (fs.statSync(this.filePath).size > MAX_CREDENTIAL_FILE_BYTES) throw new Error('MCP credential file is oversized') - } - const serializedEnvelope = fs.readFileSync(this.filePath, 'utf-8') - if (Buffer.byteLength(serializedEnvelope, 'utf8') > MAX_CREDENTIAL_FILE_BYTES) { + const serialized = fs.readFileSync(this.filePath, 'utf8') + if (Buffer.byteLength(serialized, 'utf8') > MAX_CREDENTIAL_FILE_BYTES) throw new Error('MCP credential file is oversized') - } - const envelope = JSON.parse(serializedEnvelope) as + const envelope = JSON.parse(serialized) as | StoredCredentialEnvelopeV1 | StoredCredentialEnvelopeV2 | undefined - if (!envelope) { - return - } - + if (!envelope) return if (envelope.version === 2 && envelope.storage === 'safeStorage') { if ( !isBoundedString(envelope.wrapped, MAX_CREDENTIAL_FILE_BYTES) || !isTimestamp(envelope.updatedAt) - ) { + ) throw new Error('MCP credential envelope is invalid') - } - if (!this.isPersistent()) { + const persistent = await this.isPersistent() + check() + if (!persistent) { fs.unlinkSync(this.filePath) return } - const raw = safeStorage.decryptString(Buffer.from(envelope.wrapped, 'base64')) - if (Buffer.byteLength(raw, 'utf8') > MAX_CREDENTIAL_PAYLOAD_BYTES) { + const raw = await this.crypto.decrypt(envelope.wrapped) + check() + if (Buffer.byteLength(raw, 'utf8') > MAX_CREDENTIAL_PAYLOAD_BYTES) throw new Error('MCP credential payload is oversized') - } - this.records = this.normalizeRecords(JSON.parse(raw) as McpCredentialData) + this.records = this.normalizeRecords(JSON.parse(raw)) return } - - if (envelope.version !== 1) { - return - } - - if (envelope.storage === 'safeStorage' && !this.isPersistent()) { - fs.unlinkSync(this.filePath) - return - } - - const legacyEntries = - envelope.storage === 'safeStorage' - ? (() => { - if (!isBoundedString(envelope.wrapped, MAX_CREDENTIAL_FILE_BYTES)) { - throw new Error('Legacy MCP credential envelope is invalid') - } - const raw = safeStorage.decryptString(Buffer.from(envelope.wrapped, 'base64')) - if (Buffer.byteLength(raw, 'utf8') > MAX_CREDENTIAL_PAYLOAD_BYTES) { - throw new Error('Legacy MCP credential payload is oversized') - } - return JSON.parse(raw) as LegacyCredentialData - })() - : envelope.entries - this.records = Object.fromEntries( - Object.entries(legacyEntries) + if (envelope.version !== 1) return + const persistent = await this.isPersistent() + check() + let entries: LegacyCredentialData + if (envelope.storage === 'safeStorage') { + if (!persistent) { + fs.unlinkSync(this.filePath) + return + } + if (!isBoundedString(envelope.wrapped, MAX_CREDENTIAL_FILE_BYTES)) + throw new Error('Legacy MCP credential envelope is invalid') + const raw = await this.crypto.decrypt(envelope.wrapped) + check() + if (Buffer.byteLength(raw, 'utf8') > MAX_CREDENTIAL_PAYLOAD_BYTES) + throw new Error('Legacy MCP credential payload is oversized') + entries = JSON.parse(raw) + } else entries = envelope.entries + const records: McpCredentialData = Object.fromEntries( + Object.entries(entries) .slice(0, MAX_CREDENTIAL_RECORDS) .flatMap(([key, value]) => { const normalized = this.normalizeInteractiveEntry(value) @@ -538,56 +603,61 @@ export class McpOAuthCredentialStore { credentialClass: 'interactive_oauth', value: normalized } satisfies InteractiveCredentialRecord - ] as const + ] ] : [] }) ) - - if (this.isPersistent()) { - this.persist() - } else if (envelope.storage === 'file') { - fs.unlinkSync(this.filePath) - } + if (persistent) await this.persist(records, check) + else if (envelope.storage === 'file') fs.unlinkSync(this.filePath) + check() + this.records = records } catch { + check() this.records = {} this.loadFailed = true + } finally { + if (!this.stopped) this.loaded = true } } private assertWritable(): void { - if (this.loadFailed) { - throw new Error('MCP credential store is unavailable') - } + if (this.loadFailed) throw new Error('MCP credential store is unavailable') } - private persist(): void { - if (!this.isPersistent()) { + private async persist(records: McpCredentialData, check: () => void): Promise { + const persistent = await this.isPersistent() + check() + if (!persistent) { try { - if (fs.existsSync(this.filePath)) { - fs.unlinkSync(this.filePath) - } - } catch { - // Credentials remain memory-only when secure persistence is unavailable. - } + if (fs.existsSync(this.filePath)) fs.unlinkSync(this.filePath) + } catch {} return } - - fs.mkdirSync(path.dirname(this.filePath), { recursive: true, mode: 0o700 }) + const wrapped = await this.crypto.encrypt(JSON.stringify(this.normalizeRecords(records))) + check() + if (!isBoundedString(wrapped, MAX_CREDENTIAL_FILE_BYTES)) + throw new Error('MCP credential envelope is invalid') const envelope: StoredCredentialEnvelopeV2 = { version: 2, storage: 'safeStorage', - wrapped: safeStorage - .encryptString(JSON.stringify(this.normalizeRecords(this.records))) - .toString('base64'), + wrapped, updatedAt: Date.now() } - const temporaryPath = `${this.filePath}.tmp-${process.pid}-${randomBytes(6).toString('hex')}` - fs.writeFileSync(temporaryPath, JSON.stringify(envelope), { - encoding: 'utf-8', - mode: 0o600 - }) - fs.renameSync(temporaryPath, this.filePath) + const serialized = JSON.stringify(envelope) + if (Buffer.byteLength(serialized, 'utf8') > MAX_CREDENTIAL_FILE_BYTES) + throw new Error('MCP credential file is oversized') + fs.mkdirSync(path.dirname(this.filePath), { recursive: true, mode: 0o700 }) + const temporaryPath = + this.filePath + '.tmp-' + process.pid + '-' + randomBytes(6).toString('hex') + try { + fs.writeFileSync(temporaryPath, serialized, { encoding: 'utf8', mode: 0o600 }) + fs.renameSync(temporaryPath, this.filePath) + } finally { + try { + fs.rmSync(temporaryPath, { force: true }) + } catch {} + } } private normalizeRecords(records: unknown): McpCredentialData { diff --git a/src/main/mcp/routes.ts b/src/main/mcp/routes.ts index 2b0d9ad0a0..6f2e5a8071 100644 --- a/src/main/mcp/routes.ts +++ b/src/main/mcp/routes.ts @@ -81,13 +81,13 @@ const assertAppRouteInput = (rawInput: unknown): void => { export function createMcpRoutes(deps: { mcpService: McpServicePort mcpAppHost: McpAppHostPort - isSettingsWindow(windowId: number | null): boolean + isSettingsWindow(windowId: number | null): Promise recordSettingsActivity(input: SettingsActivityInput): Promise }): DeepchatRouteMap { const { mcpService } = deps - const appContext = (context: RouteContext) => { + const appContext = async (context: RouteContext) => { const caller = requireRendererCaller(context) - if (caller.windowId === null || deps.isSettingsWindow(caller.windowId)) { + if (caller.windowId === null || (await deps.isSettingsWindow(caller.windowId))) { throw new Error('MCP Apps are restricted to conversation windows') } return { @@ -95,9 +95,9 @@ export function createMcpRoutes(deps: { windowId: caller.windowId } } - const assertSettingsWindow = (context: RouteContext): void => { + const assertSettingsWindow = async (context: RouteContext): Promise => { const caller = requireRendererCaller(context) - if (!deps.isSettingsWindow(caller.windowId)) { + if (!(await deps.isSettingsWindow(caller.windowId))) { throw new Error('MCP credential changes are restricted to the settings window') } } @@ -310,7 +310,7 @@ export function createMcpRoutes(deps: { [ mcpStartServerAuthRoute.name, async (rawInput, context) => { - assertSettingsWindow(context) + await assertSettingsWindow(context) const input = mcpStartServerAuthRoute.input.parse(rawInput) return mcpStartServerAuthRoute.output.parse({ status: await mcpService.startMcpServerAuth(input.serverId) @@ -320,7 +320,7 @@ export function createMcpRoutes(deps: { [ mcpCompleteServerAuthFromCallbackUrlRoute.name, async (rawInput, context) => { - assertSettingsWindow(context) + await assertSettingsWindow(context) const input = mcpCompleteServerAuthFromCallbackUrlRoute.input.parse(rawInput) return mcpCompleteServerAuthFromCallbackUrlRoute.output.parse({ status: await mcpService.completeMcpServerAuthFromCallbackUrl( @@ -333,7 +333,7 @@ export function createMcpRoutes(deps: { [ mcpLogoutServerAuthRoute.name, async (rawInput, context) => { - assertSettingsWindow(context) + await assertSettingsWindow(context) const input = mcpLogoutServerAuthRoute.input.parse(rawInput) return mcpLogoutServerAuthRoute.output.parse({ status: await mcpService.logoutMcpServerAuth(input.serverId) @@ -343,7 +343,7 @@ export function createMcpRoutes(deps: { [ mcpCredentialsGetStatusRoute.name, async (rawInput, context) => { - assertSettingsWindow(context) + await assertSettingsWindow(context) const input = mcpCredentialsGetStatusRoute.input.parse(rawInput) return mcpCredentialsGetStatusRoute.output.parse({ credentials: await mcpService.getMcpCredentialStatus(input.serverId) @@ -353,7 +353,7 @@ export function createMcpRoutes(deps: { [ mcpCredentialsSetRoute.name, async (rawInput, context) => { - assertSettingsWindow(context) + await assertSettingsWindow(context) const input = mcpCredentialsSetRoute.input.parse(rawInput) return mcpCredentialsSetRoute.output.parse({ status: await mcpService.setMcpCredential(input.binding, input.credential) @@ -363,7 +363,7 @@ export function createMcpRoutes(deps: { [ mcpCredentialsRemoveRoute.name, async (rawInput, context) => { - assertSettingsWindow(context) + await assertSettingsWindow(context) const input = mcpCredentialsRemoveRoute.input.parse(rawInput) return mcpCredentialsRemoveRoute.output.parse({ status: await mcpService.removeMcpCredential(input.binding, input.kind) @@ -373,7 +373,7 @@ export function createMcpRoutes(deps: { [ mcpEnterpriseProfilesListRoute.name, async (rawInput, context) => { - assertSettingsWindow(context) + await assertSettingsWindow(context) mcpEnterpriseProfilesListRoute.input.parse(rawInput) return mcpEnterpriseProfilesListRoute.output.parse({ profiles: await mcpService.listMcpEnterpriseProfiles() @@ -383,7 +383,7 @@ export function createMcpRoutes(deps: { [ mcpEnterpriseProfilesSaveRoute.name, async (rawInput, context) => { - assertSettingsWindow(context) + await assertSettingsWindow(context) const input = mcpEnterpriseProfilesSaveRoute.input.parse(rawInput) return mcpEnterpriseProfilesSaveRoute.output.parse({ profile: await mcpService.saveMcpEnterpriseProfile(input.profile) @@ -393,7 +393,7 @@ export function createMcpRoutes(deps: { [ mcpEnterpriseProfilesRemoveRoute.name, async (rawInput, context) => { - assertSettingsWindow(context) + await assertSettingsWindow(context) const input = mcpEnterpriseProfilesRemoveRoute.input.parse(rawInput) await mcpService.removeMcpEnterpriseProfile(input.profileId) return mcpEnterpriseProfilesRemoveRoute.output.parse({ removed: true }) @@ -402,7 +402,7 @@ export function createMcpRoutes(deps: { [ mcpEnterpriseProfilesSetClientSecretRoute.name, async (rawInput, context) => { - assertSettingsWindow(context) + await assertSettingsWindow(context) const input = mcpEnterpriseProfilesSetClientSecretRoute.input.parse(rawInput) return mcpEnterpriseProfilesSetClientSecretRoute.output.parse({ status: await mcpService.setMcpEnterpriseProfileClientSecret( @@ -415,7 +415,7 @@ export function createMcpRoutes(deps: { [ mcpEnterpriseProfilesGetStatusRoute.name, async (rawInput, context) => { - assertSettingsWindow(context) + await assertSettingsWindow(context) const input = mcpEnterpriseProfilesGetStatusRoute.input.parse(rawInput) return mcpEnterpriseProfilesGetStatusRoute.output.parse({ status: await mcpService.getMcpEnterpriseProfileStatus(input.profileId) @@ -425,7 +425,7 @@ export function createMcpRoutes(deps: { [ mcpEnterpriseProfilesStartAuthRoute.name, async (rawInput, context) => { - assertSettingsWindow(context) + await assertSettingsWindow(context) const input = mcpEnterpriseProfilesStartAuthRoute.input.parse(rawInput) return mcpEnterpriseProfilesStartAuthRoute.output.parse({ status: await mcpService.startMcpEnterpriseProfileAuth(input.profileId) @@ -435,7 +435,7 @@ export function createMcpRoutes(deps: { [ mcpEnterpriseProfilesCompleteAuthRoute.name, async (rawInput, context) => { - assertSettingsWindow(context) + await assertSettingsWindow(context) const input = mcpEnterpriseProfilesCompleteAuthRoute.input.parse(rawInput) return mcpEnterpriseProfilesCompleteAuthRoute.output.parse({ status: await mcpService.completeMcpEnterpriseProfileAuthFromCallbackUrl( @@ -448,7 +448,7 @@ export function createMcpRoutes(deps: { [ mcpEnterpriseProfilesLogoutRoute.name, async (rawInput, context) => { - assertSettingsWindow(context) + await assertSettingsWindow(context) const input = mcpEnterpriseProfilesLogoutRoute.input.parse(rawInput) return mcpEnterpriseProfilesLogoutRoute.output.parse({ status: await mcpService.logoutMcpEnterpriseProfile(input.profileId) @@ -476,7 +476,7 @@ export function createMcpRoutes(deps: { [ mcpSubmitSamplingDecisionRoute.name, async (rawInput, context) => { - appContext(context) + await appContext(context) const input = mcpSubmitSamplingDecisionRoute.input.parse(rawInput) await mcpService.submitSamplingDecision(input.decision) return mcpSubmitSamplingDecisionRoute.output.parse({ submitted: true }) @@ -485,7 +485,7 @@ export function createMcpRoutes(deps: { [ mcpCancelSamplingRequestRoute.name, async (rawInput, context) => { - appContext(context) + await appContext(context) const input = mcpCancelSamplingRequestRoute.input.parse(rawInput) await mcpService.cancelSamplingRequest(input.requestId, input.reason) return mcpCancelSamplingRequestRoute.output.parse({ cancelled: true }) @@ -494,7 +494,7 @@ export function createMcpRoutes(deps: { [ mcpSubmitElicitationDecisionRoute.name, async (rawInput, context) => { - appContext(context) + await appContext(context) const input = mcpSubmitElicitationDecisionRoute.input.parse(rawInput) await mcpService.submitElicitationDecision(input.decision) return mcpSubmitElicitationDecisionRoute.output.parse({ submitted: true }) @@ -503,7 +503,7 @@ export function createMcpRoutes(deps: { [ mcpCancelElicitationRequestRoute.name, async (rawInput, context) => { - appContext(context) + await appContext(context) const input = mcpCancelElicitationRequestRoute.input.parse(rawInput) await mcpService.cancelElicitationRequest(input.requestId, input.reason) return mcpCancelElicitationRequestRoute.output.parse({ cancelled: true }) @@ -515,7 +515,7 @@ export function createMcpRoutes(deps: { assertAppRouteInput(rawInput) const input = mcpAppsPrepareViewRoute.input.parse(rawInput) return mcpAppsPrepareViewRoute.output.parse({ - view: await deps.mcpAppHost.prepareView(input, appContext(context)) + view: await deps.mcpAppHost.prepareView(input, await appContext(context)) }) } ], @@ -524,7 +524,7 @@ export function createMcpRoutes(deps: { async (rawInput, context) => { assertAppRouteInput(rawInput) const input = mcpAppsReleaseViewRoute.input.parse(rawInput) - await deps.mcpAppHost.releaseView(input.instanceId, appContext(context)) + await deps.mcpAppHost.releaseView(input.instanceId, await appContext(context)) return mcpAppsReleaseViewRoute.output.parse({ released: true }) } ], @@ -538,7 +538,7 @@ export function createMcpRoutes(deps: { input.instanceId, input.name, input.arguments, - appContext(context) + await appContext(context) ) }) } @@ -549,7 +549,7 @@ export function createMcpRoutes(deps: { assertAppRouteInput(rawInput) const input = mcpAppsListToolsRoute.input.parse(rawInput) return mcpAppsListToolsRoute.output.parse( - await deps.mcpAppHost.listTools(input.instanceId, input.cursor, appContext(context)) + await deps.mcpAppHost.listTools(input.instanceId, input.cursor, await appContext(context)) ) } ], @@ -559,7 +559,7 @@ export function createMcpRoutes(deps: { assertAppRouteInput(rawInput) const input = mcpAppsReadResourceRoute.input.parse(rawInput) return mcpAppsReadResourceRoute.output.parse( - await deps.mcpAppHost.readResource(input.instanceId, input.uri, appContext(context)) + await deps.mcpAppHost.readResource(input.instanceId, input.uri, await appContext(context)) ) } ], @@ -569,7 +569,11 @@ export function createMcpRoutes(deps: { assertAppRouteInput(rawInput) const input = mcpAppsListResourcesRoute.input.parse(rawInput) return mcpAppsListResourcesRoute.output.parse( - await deps.mcpAppHost.listResources(input.instanceId, input.cursor, appContext(context)) + await deps.mcpAppHost.listResources( + input.instanceId, + input.cursor, + await appContext(context) + ) ) } ], @@ -582,7 +586,7 @@ export function createMcpRoutes(deps: { await deps.mcpAppHost.listResourceTemplates( input.instanceId, input.cursor, - appContext(context) + await appContext(context) ) ) } @@ -593,7 +597,11 @@ export function createMcpRoutes(deps: { assertAppRouteInput(rawInput) const input = mcpAppsListPromptsRoute.input.parse(rawInput) return mcpAppsListPromptsRoute.output.parse( - await deps.mcpAppHost.listPrompts(input.instanceId, input.cursor, appContext(context)) + await deps.mcpAppHost.listPrompts( + input.instanceId, + input.cursor, + await appContext(context) + ) ) } ], @@ -603,7 +611,11 @@ export function createMcpRoutes(deps: { assertAppRouteInput(rawInput) const input = mcpAppsOpenLinkRoute.input.parse(rawInput) return mcpAppsOpenLinkRoute.output.parse({ - opened: await deps.mcpAppHost.openLink(input.instanceId, input.url, appContext(context)) + opened: await deps.mcpAppHost.openLink( + input.instanceId, + input.url, + await appContext(context) + ) }) } ], @@ -616,7 +628,7 @@ export function createMcpRoutes(deps: { approved: await deps.mcpAppHost.authorizeMessage( input.instanceId, input.text, - appContext(context) + await appContext(context) ) }) } @@ -633,7 +645,7 @@ export function createMcpRoutes(deps: { content: input.content, structuredContent: input.structuredContent }, - appContext(context) + await appContext(context) ) ) } @@ -643,7 +655,7 @@ export function createMcpRoutes(deps: { async (rawInput, context) => { assertAppRouteInput(rawInput) const input = mcpAppsRetryToolAccessRoute.input.parse(rawInput) - await deps.mcpAppHost.retryToolAccess(input.instanceId, appContext(context)) + await deps.mcpAppHost.retryToolAccess(input.instanceId, await appContext(context)) return mcpAppsRetryToolAccessRoute.output.parse({ retried: true }) } ], @@ -652,7 +664,11 @@ export function createMcpRoutes(deps: { async (rawInput, context) => { assertAppRouteInput(rawInput) const input = mcpAppsSubmitConsentRoute.input.parse(rawInput) - await deps.mcpAppHost.submitConsent(input.requestId, input.approved, appContext(context)) + await deps.mcpAppHost.submitConsent( + input.requestId, + input.approved, + await appContext(context) + ) return mcpAppsSubmitConsentRoute.output.parse({ submitted: true }) } ], diff --git a/src/main/mcp/serverManager.ts b/src/main/mcp/serverManager.ts index 1291ddf3f3..ee1f8a969f 100644 --- a/src/main/mcp/serverManager.ts +++ b/src/main/mcp/serverManager.ts @@ -7,7 +7,6 @@ import { type McpConnectResult } from './mcpClient' import axios from 'axios' -import { proxyConfig } from '@/platform/proxy' import type { DeepchatEventPublisher } from '@shared/contracts/events' import type { SemanticNotificationPublisher } from '@/notifications' import type { McpOAuthManager } from './mcpOAuthManager' @@ -52,6 +51,7 @@ export class ServerManager { onRegistryChanged: () => void, private readonly semanticNotifications: SemanticNotificationPublisher, private readonly publishEvent: DeepchatEventPublisher, + private readonly getProxyUrl: () => Promise, mcpOAuthManager?: McpOAuthManager ) { this.mcpSettings = mcpSettings @@ -133,7 +133,7 @@ export class ServerManager { const testPackage = 'tiny-runtime-injector' // Get proxy configuration - const proxyUrl = proxyConfig.getProxyUrl() + const proxyUrl = await this.getProxyUrl() const proxyOptions = (() => { if (!proxyUrl) return {} const u = new URL(proxyUrl) @@ -377,6 +377,7 @@ export class ServerManager { serverConfig, connectResult, connectionCompletion, + epoch, options ) if (connectResult === 'connected') { @@ -399,7 +400,10 @@ export class ServerManager { // user restarts or stops it. A later start replaces this client through the normal path. this.setServerLastError(name, error) const authHandled = - this.mcpOAuthManager?.handleConnectionError(name, serverConfig, error) ?? false + (await this.mcpOAuthManager + ?.handleConnectionError(name, serverConfig, error) + .catch(() => false)) ?? false + if (this.isStaleStart(name, epoch)) return 'stopped' if (!authHandled && !this.isPluginOwnedServerConfig(serverConfig)) { this.notifyConnectionFailure(name) @@ -417,6 +421,7 @@ export class ServerManager { serverConfig: MCPServerConfig, connectResult: McpConnectResult, connectionCompletion: Promise | null, + epoch: number, options: { onBackgroundConnected?: () => void } = {} ): void { if (connectResult !== 'soft-timeout-released') { @@ -435,7 +440,7 @@ export class ServerManager { options.onBackgroundConnected?.() this.onRegistryChanged() }) - .catch((error) => { + .catch(async (error) => { if (error instanceof McpConnectionCancelledError) { return } @@ -446,7 +451,10 @@ export class ServerManager { this.setServerLastError(name, error) const authHandled = - this.mcpOAuthManager?.handleConnectionError(name, serverConfig, error) ?? false + (await this.mcpOAuthManager + ?.handleConnectionError(name, serverConfig, error) + .catch(() => false)) ?? false + if (this.isStaleStart(name, epoch) || this.clients.get(name) !== client) return if (!authHandled && !this.isPluginOwnedServerConfig(serverConfig)) { this.notifyConnectionFailure(name) diff --git a/src/main/mcp/settings.ts b/src/main/mcp/settings.ts index 5bdcb7831b..0f8a471824 100644 --- a/src/main/mcp/settings.ts +++ b/src/main/mcp/settings.ts @@ -1,9 +1,10 @@ import logger from '@shared/logger' -import type { MCPServerConfig, McpEnterpriseIdentityProfile } from '@shared/types/mcp' +import type { + MCPServerConfig, + McpCredentialBinding, + McpEnterpriseIdentityProfile +} from '@shared/types/mcp' import type { BuiltinKnowledgeConfig } from '@shared/types/knowledge' -import ElectronStore from 'electron-store' -// app is used in DEFAULT_INMEMORY_SERVERS but removed buildInFileSystem -// import { app } from 'electron' import { compare } from 'compare-versions' import { isBuiltinKnowledgeSupported } from '../knowledge/support' import type { StoreLike } from '../config/storeLike' @@ -393,39 +394,68 @@ export const SYSTEM_INMEM_MCP_SERVERS: Record = { // custom-prompts-server has been removed, now provides prompt functionality through config data source } +function cloneServerConfig(config: MCPServerConfig): MCPServerConfig { + const cloneFn = ( + globalThis as typeof globalThis & { + structuredClone?: (value: MCPServerConfig) => MCPServerConfig + } + ).structuredClone + + if (typeof cloneFn === 'function') { + return cloneFn(config) + } + + return JSON.parse(JSON.stringify(config)) as MCPServerConfig +} + +function buildDefaultServerConfigs(): Record { + const enabledServers = new Set(DEFAULT_ENABLED_SERVER_NAMES) + + return Object.fromEntries( + Object.entries(DEFAULT_MCP_SERVERS.mcpServers).map(([name, config]) => [ + name, + { + ...cloneServerConfig(config as unknown as MCPServerConfig), + enabled: enabledServers.has(name) + } + ]) + ) +} + +export function createMcpSettingsDefaults(): IMcpSettings { + return { + mcpServers: buildDefaultServerConfigs(), + mcpEnabled: DEFAULT_MCP_SERVERS.mcpEnabled, + autoDetectNpmRegistry: true, + npmRegistryCache: undefined, + customNpmRegistry: undefined, + removedBuiltInServers: [] + } +} + export class McpSettings { private mcpStore: StoreLike> private mcpDatabase?: McpDatabase private pluginBindingResolvers = new Map< string, - (config: Partial) => Record + (config: Partial) => Promise> >() setPluginBindingResolver( pluginId: string, - resolve: (config: Partial) => Record + resolve: (config: Partial) => Promise> ): void { this.pluginBindingResolvers.set(pluginId, resolve) } constructor( + store: StoreLike, private readonly variableSecrets?: Pick< SecretStore, - 'get' | 'getWrapped' | 'wrap' | 'setWrapped' | 'restoreWrapped' + 'get' | 'getWrapped' | 'wrap' | 'setWrapped' | 'restoreWrapped' | 'captureOperation' > ) { - // Initialize MCP settings storage - this.mcpStore = new ElectronStore({ - name: 'mcp-settings', - defaults: { - mcpServers: this.buildDefaultServerConfigs(), - mcpEnabled: DEFAULT_MCP_SERVERS.mcpEnabled, - autoDetectNpmRegistry: true, - npmRegistryCache: undefined, - customNpmRegistry: undefined, - removedBuiltInServers: [] - } - }) + this.mcpStore = store } getMigrationSnapshot(): Record { @@ -459,20 +489,6 @@ export class McpSettings { return [...DEFAULT_ENABLED_SERVER_NAMES] } - private buildDefaultServerConfigs(): Record { - const enabledServers = new Set(this.getDefaultEnabledServerNames()) - - return Object.fromEntries( - Object.entries(DEFAULT_MCP_SERVERS.mcpServers).map(([name, config]) => [ - name, - { - ...this.cloneServerConfig(config as unknown as MCPServerConfig), - enabled: enabledServers.has(name) - } - ]) - ) - } - private resolveLegacyEnabledServers(): Set { const enabled = new Set() const oldDefaultServer = this.mcpStore.get('defaultServer') @@ -499,7 +515,7 @@ export class McpSettings { legacyKeysPresent: boolean, defaultEnabledServers: Set ): MCPServerConfig { - const cloned = this.cloneServerConfig(config) as MCPServerConfig & { + const cloned = cloneServerConfig(config) as MCPServerConfig & { autoApprove?: unknown } delete cloned.autoApprove @@ -599,20 +615,6 @@ export class McpSettings { this.setRemovedBuiltInServers(removed) } - private cloneServerConfig(config: MCPServerConfig): MCPServerConfig { - const cloneFn = ( - globalThis as typeof globalThis & { - structuredClone?: (value: MCPServerConfig) => MCPServerConfig - } - ).structuredClone - - if (typeof cloneFn === 'function') { - return cloneFn(config) - } - - return JSON.parse(JSON.stringify(config)) as MCPServerConfig - } - private ensureUniqueServerIdentities( servers: Record ): Record { @@ -695,10 +697,7 @@ export class McpSettings { // Get MCP server configuration async getMcpServers(): Promise> { const storedServers = this.removeDeprecatedBuiltInServers( - this.mcpStore.get>( - 'mcpServers', - this.buildDefaultServerConfigs() - ) + this.mcpStore.get>('mcpServers', buildDefaultServerConfigs()) ) const legacyEnabledServers = this.resolveLegacyEnabledServers() const legacyKeysPresent = @@ -739,7 +738,7 @@ export class McpSettings { updatedServers[serverName] = this.normalizeServerConfig( serverName, { - ...this.cloneServerConfig(serverConfig as MCPServerConfig), + ...cloneServerConfig(serverConfig as MCPServerConfig), enabled: defaultEnabledServers.has(serverName) }, new Set(), @@ -954,7 +953,7 @@ export class McpSettings { // 添加MCP服务器 async addMcpServer(name: string, config: MCPServerConfig): Promise { const mcpServers = await this.getMcpServers() - const hostOwnedConfig = this.cloneServerConfig(config) + const hostOwnedConfig = cloneServerConfig(config) delete hostOwnedConfig.serverId delete hostOwnedConfig.configGeneration delete hostOwnedConfig.bindingHash @@ -1064,11 +1063,17 @@ export class McpSettings { await this.setMcpServers(mcpServers) } - getMcpVariableBindings(config: Partial): Record { + async getMcpVariableBindings(config: Partial): Promise> { + const check = this.variableSecrets?.captureOperation() const resolve = config.ownerPluginId && this.pluginBindingResolvers.get(config.ownerPluginId) - if (resolve) return resolve(config) + if (resolve) { + const bindings = await resolve(config) + check?.() + return bindings + } if (!config.serverId) return {} - const raw = this.variableSecrets?.get(`mcpVariableBindings.${config.serverId}`) + const raw = await this.variableSecrets?.get(`mcpVariableBindings.${config.serverId}`) + check?.() if (!raw) return {} try { const stored = JSON.parse(raw) @@ -1089,7 +1094,10 @@ export class McpSettings { } } - setMcpVariableBindings(config: MCPServerConfig, values: Record): void { + async setMcpVariableBindings( + config: MCPServerConfig, + values: Record + ): Promise { if (!config.serverId || !this.variableSecrets) throw new Error('MCP credential storage is unavailable') if ( @@ -1099,12 +1107,14 @@ export class McpSettings { ) ) throw new Error('Invalid MCP variable binding') - const wrapped = this.variableSecrets.wrap( + const check = this.variableSecrets.captureOperation() + const wrapped = await this.variableSecrets.wrap( JSON.stringify({ scope: mcpVariableBindingScope(config), - values: { ...this.getMcpVariableBindings(config), ...values } + values: { ...(await this.getMcpVariableBindings(config)), ...values } }) ) + check() this.variableSecrets.setWrapped(`mcpVariableBindings.${config.serverId}`, wrapped) } @@ -1130,6 +1140,33 @@ export class McpSettings { await this.setMcpServers(mcpServers) } + finalizeOAuthBinding( + expected: McpCredentialBinding, + authorization: NonNullable + ): MCPServerConfig { + const servers = this.mcpStore.get>('mcpServers') ?? {} + const match = Object.entries(servers).find( + ([, config]) => config.serverId === expected.serverId + ) + if (!match) throw new Error('MCP server was removed during OAuth discovery') + const [name, current] = match + if ( + current.configGeneration !== expected.configGeneration || + current.bindingHash !== expected.bindingHash || + current.baseUrl !== expected.endpoint + ) + throw new Error('MCP server binding changed during OAuth discovery') + const merged = { ...current, authorization: sanitizeMcpAuthorizationConfig(authorization) } + const next = { ...merged, ...normalizeMcpServerIdentity(merged, current) } + if ( + next.bindingHash === current.bindingHash && + next.configGeneration === current.configGeneration + ) + return current + this.mcpStore.set('mcpServers', { ...servers, [name]: next }) + return next + } + /** * Batch import MCP servers from external source (like ModelScope) * @param servers - Array of MCP server configs to import diff --git a/src/main/mcp/toolManager.ts b/src/main/mcp/toolManager.ts index 2c739666ad..cb0008a446 100644 --- a/src/main/mcp/toolManager.ts +++ b/src/main/mcp/toolManager.ts @@ -19,7 +19,7 @@ import type { AgentSettingsPort } from '@/agent/settings' import { ServerManager } from './serverManager' import { McpClient } from './mcpClient' import { jsonrepair } from 'jsonrepair' -import { getExplicitlyDeniedPluginTools, resolvePluginToolPolicy } from '@/plugin/toolPolicyStore' +import type { PluginToolPolicyStore } from '@/plugin/toolPolicyStore' import type { DeepchatEventPublisher } from '@shared/contracts/events' import type { SemanticNotificationPublisher } from '@/notifications' import { awaitWithAbort } from '@/lib/awaitWithAbort' @@ -109,7 +109,7 @@ export type ComputerUsePreviewCall = { } export type ComputerUsePreviewObserver = { - shouldCaptureAfterClick?(call: ComputerUsePreviewCall): boolean + shouldCaptureAfterClick?(call: ComputerUsePreviewCall): boolean | Promise started(call: ComputerUsePreviewCall): void completed(call: ComputerUsePreviewCall, result: MCPToolResponse): void failed(call: ComputerUsePreviewCall, error: unknown): void @@ -338,6 +338,7 @@ export class ToolManager { agentSettings: Pick, mcpSettings: McpSettings, serverManager: ServerManager, + private readonly toolPolicies: Pick, private readonly semanticNotifications: SemanticNotificationPublisher, private readonly publishEvent: DeepchatEventPublisher, private readonly pluginOwnership: PluginMcpOwnershipPort = NO_PLUGIN_OWNERSHIP, @@ -695,7 +696,7 @@ export class ToolManager { } private filterExplicitlyDeniedTools(serverName: string, tools: readonly Tool[]): readonly Tool[] { - const deniedTools = new Set(getExplicitlyDeniedPluginTools(serverName)) + const deniedTools = new Set(this.toolPolicies.deniedTools(serverName)) return deniedTools.size === 0 ? tools : tools.filter((tool) => !deniedTools.has(tool.name)) } @@ -1006,7 +1007,7 @@ export class ToolManager { access?.throwPreDispatchErrors ) } - const pluginPolicy = resolvePluginToolPolicy(toolServerName, originalName) + const pluginPolicy = this.toolPolicies.resolve(toolServerName, originalName) if ( pluginPolicy.managed && pluginPolicy.decision !== 'allow' && @@ -1143,7 +1144,7 @@ export class ToolManager { version: Date.now() }) - this.scheduleComputerUsePreviewAfterClick({ + void this.scheduleComputerUsePreviewAfterClick({ client: targetClient, toolCall, toolName: originalName, @@ -1151,6 +1152,8 @@ export class ToolManager { runId: access?.runId, response, signal: access?.signal + }).catch((error) => { + logger.warn('[ToolManager] Computer Use preview scheduling failed', { error }) }) } if (dispatchCommitted && access?.registerOutcomeProjection) { @@ -1321,7 +1324,7 @@ export class ToolManager { } } - private scheduleComputerUsePreviewAfterClick(input: { + private async scheduleComputerUsePreviewAfterClick(input: { client: McpClient toolCall: MCPToolCall toolName: string @@ -1329,11 +1332,11 @@ export class ToolManager { runId?: string response: MCPToolResponse signal?: AbortSignal - }): void { + }): Promise { if ( input.toolName !== 'click' || input.response.isError === true || - resolvePluginToolPolicy(input.client.serverName, 'get_window_state').decision !== 'allow' || + this.toolPolicies.resolve(input.client.serverName, 'get_window_state').decision !== 'allow' || !this.computerUsePreviewObserver?.shouldCaptureAfterClick ) { return @@ -1348,14 +1351,18 @@ export class ToolManager { let shouldCapture = false try { - shouldCapture = this.computerUsePreviewObserver.shouldCaptureAfterClick(clickCall) + shouldCapture = await this.computerUsePreviewObserver.shouldCaptureAfterClick(clickCall) } catch (error) { logger.warn('[ToolManager] Computer Use preview eligibility check failed', { toolCallId: clickCall.toolCallId, error: error instanceof Error ? error.message : String(error) }) } - if (!shouldCapture) { + if ( + !shouldCapture || + input.signal?.aborted || + this.toolPolicies.resolve(input.client.serverName, 'get_window_state').decision !== 'allow' + ) { return } @@ -1389,6 +1396,9 @@ export class ToolManager { this.getCatalogTool(client.serverName, 'get_window_state'), signal ) + signal?.throwIfAborted() + if (this.toolPolicies.resolve(client.serverName, 'get_window_state').decision !== 'allow') + throw new Error('Computer Use preview read is no longer allowed') const result = signal ? await client.callTool('get_window_state', call.args, { signal }) : await client.callTool('get_window_state', call.args) diff --git a/src/main/memory/context.ts b/src/main/memory/context.ts index 0ccd68e924..10cdbb82f1 100644 --- a/src/main/memory/context.ts +++ b/src/main/memory/context.ts @@ -57,6 +57,7 @@ export function isUniqueConstraintError(error: unknown): boolean { } export class MemoryRuntimeContext { + private readonly ownerId = nanoid() private disposed = false private paused = false private readonly readEpochByAgent = new Map() @@ -110,6 +111,7 @@ export class MemoryRuntimeContext { captureOperationFence(agentId: string): MemoryOperationFence { return { + ownerId: this.ownerId, agentId, generation: this.getOrCreateExecutionState(agentId).generation } @@ -119,6 +121,7 @@ export class MemoryRuntimeContext { return ( !this.disposed && !this.paused && + fence.ownerId === this.ownerId && (this.executionStateByAgent.get(fence.agentId)?.generation ?? 0) === fence.generation ) } diff --git a/src/main/memory/core/executionIdentity.ts b/src/main/memory/core/executionIdentity.ts index 578f73ef5d..35589074fa 100644 --- a/src/main/memory/core/executionIdentity.ts +++ b/src/main/memory/core/executionIdentity.ts @@ -3,6 +3,7 @@ import type { DeepChatAgentConfig } from '@shared/types/agent-interface' import type { MemoryModelRef } from '../domain/types' export interface MemoryExecutionToken { + ownerId: string agentId: string generation: number } diff --git a/src/main/memory/infra/legacyV1Reader.ts b/src/main/memory/infra/legacyV1Reader.ts index 64e6a3bc28..2d6868af36 100644 --- a/src/main/memory/infra/legacyV1Reader.ts +++ b/src/main/memory/infra/legacyV1Reader.ts @@ -1,7 +1,12 @@ import { DuckDBConnection, DuckDBInstance } from '@duckdb/node-api' import type { MemoryVectorRecord } from '../types' -import { escapeDuckDbSqlPath, LegacyVssUnavailableError, loadLegacyVss } from './legacyVssLoader' +import { + escapeDuckDbSqlPath, + LegacyVssUnavailableError, + loadLegacyVss, + type MemoryVectorHost +} from './legacyVssLoader' import { readSafeMemoryVectorRowCount } from './memoryVectorStoreFormat' const LEGACY_SCHEMA = 'legacy' @@ -68,14 +73,15 @@ export class LegacyV1Reader { static async open( legacyPath: string, expectedDimensions: number, - fence: MigrationAbandonFence + fence: MigrationAbandonFence, + host: MemoryVectorHost ): Promise { const dbInstance = await DuckDBInstance.create(':memory:') fence.markProgress() const connection = await dbInstance.connect() fence.markProgress() try { - await loadLegacyVss(connection, legacyPath, fence) + await loadLegacyVss(connection, legacyPath, host, fence) } catch (error) { if (!(error instanceof LegacyVssUnavailableError)) throw error fence.assertActive() diff --git a/src/main/memory/infra/legacyVssLoader.ts b/src/main/memory/infra/legacyVssLoader.ts index 056d66284a..ef3d2047e1 100644 --- a/src/main/memory/infra/legacyVssLoader.ts +++ b/src/main/memory/infra/legacyVssLoader.ts @@ -1,4 +1,4 @@ -import logger from '@shared/logger' +import logger from 'node:console' import { createHash, randomUUID } from 'node:crypto' import fs from 'node:fs' import path from 'node:path' @@ -6,7 +6,12 @@ import { promisify } from 'node:util' import { gunzip } from 'node:zlib' import type { DuckDBConnection } from '@duckdb/node-api' -import { app } from 'electron' + +export interface MemoryVectorHost { + appPath: string + userDataPath: string + isPackaged: boolean +} const extensionSuffix = '.duckdb_extension' const VSS_EXTENSION_NAME = `vss${extensionSuffix}` @@ -88,9 +93,13 @@ async function inflatePackagedVssExtension( return targetPath } -async function materializePackagedVssExtension(assetPath: string, dbPath: string): Promise { +async function materializePackagedVssExtension( + assetPath: string, + dbPath: string, + host: MemoryVectorHost +): Promise { const resolvedAssetPath = path.resolve(assetPath) - const materializationRoot = path.resolve(app.getPath('userData') || path.dirname(dbPath)) + const materializationRoot = path.resolve(host.userDataPath || path.dirname(dbPath)) const cacheKey = materializationCacheKey(resolvedAssetPath, materializationRoot) const existing = PACKAGED_VSS_MATERIALIZATION_PROMISES.get(cacheKey) if (existing) { @@ -99,7 +108,7 @@ async function materializePackagedVssExtension(assetPath: string, dbPath: string if (PACKAGED_VSS_MATERIALIZATION_PROMISES.get(cacheKey) === existing) { PACKAGED_VSS_MATERIALIZATION_PROMISES.delete(cacheKey) } else { - return materializePackagedVssExtension(resolvedAssetPath, dbPath) + return materializePackagedVssExtension(resolvedAssetPath, dbPath, host) } } @@ -119,10 +128,11 @@ async function materializePackagedVssExtension(assetPath: string, dbPath: string async function resolveLegacyVssPath( dbPath: string, + host: MemoryVectorHost, fence?: LegacyVssLoadFence ): Promise<{ extensionPath: string; source: string } | null> { const runtimeBasePath = path - .join(app.getAppPath(), 'runtime') + .join(host.appPath, 'runtime') .replace('app.asar', 'app.asar.unpacked') const extensionDir = path.join(runtimeBasePath, 'duckdb', 'extensions') const extensionPath = path.join(extensionDir, VSS_EXTENSION_NAME) @@ -133,7 +143,7 @@ async function resolveLegacyVssPath( if (fs.existsSync(packagedAssetPath)) { let materializedPath: string try { - materializedPath = await materializePackagedVssExtension(packagedAssetPath, dbPath) + materializedPath = await materializePackagedVssExtension(packagedAssetPath, dbPath, host) } catch (error) { const message = `[MemoryVectorStore] packaged VSS extension failed to materialize from ${packagedAssetPath}: ${String(error)}` if (fence) { @@ -148,7 +158,7 @@ async function resolveLegacyVssPath( const message = `[MemoryVectorStore] bundled VSS extension missing at ${extensionPath} or ${packagedAssetPath}. Run installRuntime:duckdb:vss before packaging.` if (fence) throw new LegacyVssUnavailableError(message) - if (app.isPackaged) { + if (host.isPackaged) { logger.error(`${message} Vector recall disabled until a valid bundled extension ships.`) throw new Error(message) } @@ -159,9 +169,10 @@ async function resolveLegacyVssPath( export async function loadLegacyVss( connection: Pick, dbPath: string, + host: MemoryVectorHost, fence?: LegacyVssLoadFence ): Promise { - const resolved = await resolveLegacyVssPath(dbPath, fence) + const resolved = await resolveLegacyVssPath(dbPath, host, fence) fence?.assertActive() if (resolved) { try { @@ -169,7 +180,7 @@ export async function loadLegacyVss( return } catch (error) { const message = `[MemoryVectorStore] ${resolved.source} VSS extension failed to load from ${resolved.extensionPath}: ${String(error)}` - if (app.isPackaged || fence) { + if (host.isPackaged || fence) { logger.error(`${message}. Vector recall disabled until a valid bundled extension ships.`) throw error } diff --git a/src/main/memory/infra/memoryVectorStore.ts b/src/main/memory/infra/memoryVectorStore.ts index bbe38f2a83..e22e34314a 100644 --- a/src/main/memory/infra/memoryVectorStore.ts +++ b/src/main/memory/infra/memoryVectorStore.ts @@ -1,11 +1,25 @@ -import logger from '@shared/logger' +import logger from 'node:console' import fs from 'node:fs' import path from 'node:path' +import { endianness } from 'node:os' +import duckdb from '@duckdb/node-bindings' +import { isSafeAgentId } from '@shared/types/agent-memory' -import { DuckDBConnection, DuckDBInstance, arrayValue } from '@duckdb/node-api' +import { + ARRAY, + FLOAT, + INTEGER, + VARCHAR, + type DuckDBAppender, + DuckDBArrayType, + DuckDBConnection, + DuckDBDataChunk, + DuckDBInstance, + arrayValue +} from '@duckdb/node-api' import type { MemoryPerfObserver } from '../ports' -import { V1_PRESERVE_IDLE_TIMEOUT_MS } from '../runtimeConstants' +import { REINDEX_BATCH_SIZE, V1_PRESERVE_IDLE_TIMEOUT_MS } from '../runtimeConstants' import type { IMemoryVectorStore, MemoryVectorMatch, @@ -13,7 +27,13 @@ import type { MemoryVectorRecord } from '../types' import { LegacyV1Reader, MigrationAbandonFence } from './legacyV1Reader' -import { LegacyVssUnavailableError } from './legacyVssLoader' +import { LegacyVssUnavailableError, type MemoryVectorHost } from './legacyVssLoader' +import { + createMemoryVectorStorePaths, + persistMemoryVectorQuarantine, + type MemoryVectorStorePaths +} from './memoryVectorStorePaths' +export { createMemoryVectorStorePaths, type MemoryVectorStorePaths } from './memoryVectorStorePaths' import { assertMemoryVectorDimensions, createMemoryVectorStoreV2FormatPlan, @@ -30,6 +50,12 @@ import { const QUARANTINE_FILE_SUFFIX = '.v2.duckdb.quarantine' +export interface PackedMemoryVectors { + memoryIds: string[] + dimensions: number + bytes: Uint8Array +} + interface EmbeddingIdentity { providerId: string modelId: string @@ -59,26 +85,6 @@ type LegacyPreserveOutcome = | { status: 'failed'; error: unknown } | { status: 'timed-out' } -export interface MemoryVectorStorePaths { - current: string - staging: string - quarantine: string - legacy: string -} - -export function createMemoryVectorStorePaths( - memoryDbDir: string, - agentId: string -): MemoryVectorStorePaths { - const current = path.join(memoryDbDir, `${agentId}.v2.duckdb`) - return { - current, - staging: `${current}.migrating`, - quarantine: `${current}.quarantine`, - legacy: path.join(memoryDbDir, `${agentId}.duckdb`) - } -} - function filesWithWal(filePath: string): string[] { return [filePath, `${filePath}.wal`] } @@ -151,6 +157,7 @@ export class MemoryVectorStore implements IMemoryVectorStore { private readonly vectorTable = MEMORY_VECTOR_STORE_TABLES.vector private readonly metaTable = MEMORY_VECTOR_STORE_TABLES.meta private usable = true + private insertChunk: DuckDBDataChunk | undefined private constructor( private readonly dbPath: string, @@ -162,6 +169,7 @@ export class MemoryVectorStore implements IMemoryVectorStore { paths: MemoryVectorStorePaths, dimensions: number, embedding: EmbeddingIdentity, + host: MemoryVectorHost, metric: 'cosine' | 'l2sq' | 'ip' = 'cosine', perfObserver?: MemoryPerfObserver ): Promise { @@ -212,7 +220,14 @@ export class MemoryVectorStore implements IMemoryVectorStore { } if (fs.existsSync(paths.legacy)) { - return MemoryVectorStore.migrateLegacyV1(paths, dimensions, embedding, metric, perfObserver) + return MemoryVectorStore.migrateLegacyV1( + paths, + dimensions, + embedding, + host, + metric, + perfObserver + ) } if (fs.existsSync(`${paths.current}.wal`)) { @@ -245,7 +260,7 @@ export class MemoryVectorStore implements IMemoryVectorStore { for (const entry of entries) { if (!entry.isFile() || !entry.name.endsWith(QUARANTINE_FILE_SUFFIX)) continue const agentId = entry.name.slice(0, -QUARANTINE_FILE_SUFFIX.length) - if (!agentId) continue + if (!isSafeAgentId(agentId)) continue const paths = createMemoryVectorStorePaths(memoryDbDir, agentId) if (path.basename(paths.quarantine) !== entry.name) continue try { @@ -268,17 +283,13 @@ export class MemoryVectorStore implements IMemoryVectorStore { ...filesWithWal(paths.current), ...filesWithWal(paths.staging), ...filesWithWal(paths.legacy), + `${paths.current}.lease`, ...(includeQuarantine ? [paths.quarantine] : []) ]) } static markQuarantined(paths: MemoryVectorStorePaths): void { - fs.mkdirSync(path.dirname(paths.quarantine), { recursive: true }) - try { - fs.writeFileSync(paths.quarantine, '', { flag: 'wx' }) - } catch (error) { - if ((error as NodeJS.ErrnoException).code !== 'EEXIST') throw error - } + persistMemoryVectorQuarantine(paths) } private static async openCurrent( @@ -309,6 +320,7 @@ export class MemoryVectorStore implements IMemoryVectorStore { paths: MemoryVectorStorePaths, dimensions: number, embedding: EmbeddingIdentity, + host: MemoryVectorHost, metric: 'cosine' | 'l2sq' | 'ip', perfObserver?: MemoryPerfObserver ): Promise { @@ -334,6 +346,7 @@ export class MemoryVectorStore implements IMemoryVectorStore { paths, dimensions, embedding, + host, metric, perfObserver, fence @@ -369,13 +382,14 @@ export class MemoryVectorStore implements IMemoryVectorStore { paths: MemoryVectorStorePaths, dimensions: number, embedding: EmbeddingIdentity, + host: MemoryVectorHost, metric: 'cosine' | 'l2sq' | 'ip', perfObserver: MemoryPerfObserver | undefined, fence: MigrationAbandonFence ): Promise { let reader: LegacyV1Reader try { - reader = await LegacyV1Reader.open(paths.legacy, dimensions, fence) + reader = await LegacyV1Reader.open(paths.legacy, dimensions, fence, host) } catch (error) { if (!(error instanceof LegacyVssUnavailableError)) throw error fence.assertActive() @@ -685,6 +699,26 @@ export class MemoryVectorStore implements IMemoryVectorStore { return this.upsertRecords(records) } + // The native upload owner retains these bytes until the transaction settles. + async upsertPacked(batch: PackedMemoryVectors): Promise { + assertMemoryVectorDimensions(batch.dimensions) + const expectedBytes = batch.memoryIds.length * batch.dimensions * 4 + if ( + endianness() !== 'LE' || + !(batch.bytes instanceof Uint8Array) || + !(batch.bytes.buffer instanceof ArrayBuffer) || + !Number.isSafeInteger(expectedBytes) || + batch.bytes.byteLength !== expectedBytes + ) + throw new Error('Invalid packed memory vector shape') + const values = new DataView(batch.bytes.buffer, batch.bytes.byteOffset, batch.bytes.byteLength) + for (let offset = 0; offset < expectedBytes; offset += 4) { + if (!Number.isFinite(values.getFloat32(offset, true))) + throw new Error('Memory vector contains a non-finite value') + } + return this.upsertRecords(batch) + } + private async beginMigration(fence: MigrationAbandonFence): Promise { fence.assertActive() await this.connection.run('BEGIN TRANSACTION;') @@ -705,7 +739,8 @@ export class MemoryVectorStore implements IMemoryVectorStore { this.perfObserver?.increment('duckDbStatements') await this.connection.run( `INSERT INTO ${this.vectorTable} (memory_id, embedding) VALUES ${insertPlaceholders};`, - insertParams + insertParams, + records.flatMap((record) => [VARCHAR, ARRAY(FLOAT, record.embedding.length)]) ) fence.markProgress() } @@ -717,31 +752,73 @@ export class MemoryVectorStore implements IMemoryVectorStore { } private async upsertRecords( - records: MemoryVectorRecord[], + records: MemoryVectorRecord[] | PackedMemoryVectors, fence?: MigrationAbandonFence ): Promise { - if (!records.length) return + const memoryIds = Array.isArray(records) + ? records.map((record) => record.memoryId) + : records.memoryIds + if (!memoryIds.length) return fence?.assertActive() await this.connection.run('BEGIN TRANSACTION;') fence?.assertActive() + let appender: DuckDBAppender | undefined + let appenderClosed = false try { - const deletePlaceholders = records.map(() => '?').join(', ') + const deletePlaceholders = memoryIds.map(() => '?').join(', ') this.perfObserver?.increment('duckDbStatements') await this.connection.run( `DELETE FROM ${this.vectorTable} WHERE memory_id IN (${deletePlaceholders});`, - records.map((record) => record.memoryId) + memoryIds ) fence?.assertActive() - const insertPlaceholders = records.map(() => '(?, ?::FLOAT[])').join(', ') - const insertParams = records.flatMap((record) => [ - record.memoryId, - arrayValue(Array.from(record.embedding)) - ]) this.perfObserver?.increment('duckDbStatements') - await this.connection.run( - `INSERT INTO ${this.vectorTable} (memory_id, embedding) VALUES ${insertPlaceholders};`, - insertParams + appender = await this.connection.createAppender(this.vectorTable) + const vectorType = appender.columnType(1) as DuckDBArrayType + if ( + Array.isArray(records) + ? records.some((record) => record.embedding.length !== vectorType.length) + : !(vectorType instanceof DuckDBArrayType) || + vectorType.valueType !== FLOAT || + records.dimensions !== vectorType.length ) + throw new Error('Memory vector dimensions do not match') + // Reuse native column buffers instead of allocating one native value per vector component. + const chunk = (this.insertChunk ??= DuckDBDataChunk.create([VARCHAR, vectorType])) + for (let offset = 0; offset < memoryIds.length; offset += REINDEX_BATCH_SIZE) { + const ids = memoryIds.slice(offset, offset + REINDEX_BATCH_SIZE) + chunk.reset() + chunk.rowCount = ids.length + chunk.setColumnValues(0, ids) + if (Array.isArray(records)) { + chunk.setColumnValues( + 1, + records.slice(offset, offset + ids.length).map((record) => arrayValue(record.embedding)) + ) + } else { + const parent = duckdb.data_chunk_get_vector(chunk.chunk, 1) + const child = duckdb.array_vector_get_child(parent) + // The checked FLOAT array and chunk row count bound both native buffers. + for (const [vector, count] of [ + [parent, ids.length], + [child, ids.length * vectorType.length] + ] as const) { + duckdb.vector_ensure_validity_writable(vector) + const valid = new Uint8Array(Math.ceil(count / 64) * 8).fill(255) + duckdb.copy_data_to_vector_validity(vector, 0, valid.buffer, 0, valid.byteLength) + } + duckdb.copy_data_to_vector( + child, + 0, + records.bytes.buffer, + records.bytes.byteOffset + offset * vectorType.length * 4, + ids.length * vectorType.length * 4 + ) + } + appender.appendDataChunk(chunk) + } + appenderClosed = true + appender.closeSync() fence?.assertActive() await this.connection.run('COMMIT;') fence?.assertActive() @@ -751,6 +828,16 @@ export class MemoryVectorStore implements IMemoryVectorStore { this.usable = false throw error } + if (appender && !appenderClosed) { + try { + appender.closeSync() + } catch (closeError) { + if (isDuckDbFatalError(closeError)) { + this.usable = false + throw closeError + } + } + } await this.connection.run('ROLLBACK;').catch(() => undefined) throw error } @@ -778,10 +865,11 @@ export class MemoryVectorStore implements IMemoryVectorStore { LIMIT ?; ` this.perfObserver?.increment('duckDbStatements') - const reader = await this.connection.runAndReadAll(sql, [ - arrayValue(Array.from(embedding)), - options.topK - ]) + const reader = await this.connection.runAndReadAll( + sql, + [arrayValue(Array.from(embedding)), options.topK], + [ARRAY(FLOAT, embedding.length), INTEGER] + ) const rows = reader.getRowObjectsJson() this.perfObserver?.increment('materializedRows', rows.length) return rows.map((row: Record) => ({ @@ -883,6 +971,7 @@ export class MemoryVectorStore implements IMemoryVectorStore { this.connection.closeSync() this.connectionOpen = false } catch (error) { + if (isDuckDbFatalError(error)) throw error errors.push(error) } } @@ -900,10 +989,8 @@ export class MemoryVectorStore implements IMemoryVectorStore { } async close(): Promise { - try { - this.closeStrict() - } catch (error) { - logger.warn(String(error)) - } + this.usable = false + this.closeStrict() + this.insertChunk = undefined } } diff --git a/src/main/memory/infra/memoryVectorStorePaths.ts b/src/main/memory/infra/memoryVectorStorePaths.ts new file mode 100644 index 0000000000..8cda738475 --- /dev/null +++ b/src/main/memory/infra/memoryVectorStorePaths.ts @@ -0,0 +1,34 @@ +import path from 'node:path' +import fs from 'node:fs' + +export const MEMORY_VECTOR_OWNER_FILE = '.vector-owner.duckdb' +export const MEMORY_VECTOR_LEASE_SUFFIX = '.v2.duckdb.lease' + +export interface MemoryVectorStorePaths { + current: string + staging: string + quarantine: string + legacy: string +} + +export function persistMemoryVectorQuarantine(paths: MemoryVectorStorePaths): void { + fs.mkdirSync(path.dirname(paths.quarantine), { recursive: true }) + try { + fs.writeFileSync(paths.quarantine, '', { flag: 'wx' }) + } catch (error) { + if ((error as NodeJS.ErrnoException).code !== 'EEXIST') throw error + } +} + +export function createMemoryVectorStorePaths( + memoryDbDir: string, + agentId: string +): MemoryVectorStorePaths { + const current = path.join(memoryDbDir, `${agentId}.v2.duckdb`) + return { + current, + staging: `${current}.migrating`, + quarantine: `${current}.quarantine`, + legacy: path.join(memoryDbDir, `${agentId}.duckdb`) + } +} diff --git a/src/main/memory/infra/vectorStoreManager.ts b/src/main/memory/infra/vectorStoreManager.ts index fd98abf3db..adce986686 100644 --- a/src/main/memory/infra/vectorStoreManager.ts +++ b/src/main/memory/infra/vectorStoreManager.ts @@ -472,6 +472,10 @@ export class VectorStoreManager implements VectorStoreRetrievalPort { this.vectorMutationLocks.set(agentId, settled) try { return await run + } catch (error) { + if (error instanceof MemoryVectorStoreQuarantineRequiredError || isDuckDbFatalError(error)) + this.quarantineAgent(agentId, error) + throw error } finally { if (this.vectorMutationLocks.get(agentId) === settled) { this.vectorMutationLocks.delete(agentId) @@ -584,7 +588,7 @@ export class VectorStoreManager implements VectorStoreRetrievalPort { } return result } catch (error) { - if (isDuckDbFatalError(error)) { + if (isDuckDbFatalError(error) || error instanceof MemoryVectorStoreQuarantineRequiredError) { if (this.quarantineAgent(agentId, error, state)) { throw new VectorStoreLeaseUnavailableError( 'quarantined', diff --git a/src/main/nowledgeMem/index.ts b/src/main/nowledgeMem/index.ts index fd392b4e1e..e7a36f703a 100644 --- a/src/main/nowledgeMem/index.ts +++ b/src/main/nowledgeMem/index.ts @@ -67,7 +67,10 @@ export class NowledgeMemConnections { constructor( private readonly settings: SettingsStore, - private readonly secrets: Pick, + private readonly secrets: Pick< + SecretStore, + 'get' | 'wrap' | 'setWrapped' | 'restoreWrapped' | 'captureOperation' + >, private readonly mcpSettings: Pick ) {} @@ -92,8 +95,8 @@ export class NowledgeMemConnections { } } - private key(connection: StoredConnection): string { - const key = connection.credentialId ? this.secrets.get(connection.credentialId) : '' + private async key(connection: StoredConnection): Promise { + const key = connection.credentialId ? await this.secrets.get(connection.credentialId) : '' if (connection.credentialId && !key) throw new Error('The saved credential is unavailable; enter it again') return key @@ -172,13 +175,16 @@ export class NowledgeMemConnections { return result } - getExportConfig(): NowledgeMemConfig { + async getExportConfig(): Promise { + const check = this.secrets.captureOperation() const state = this.read() const connection = state.connection if (!connection) throw new Error('Verify a connection in the Nowledge Mem plugin') + const apiKey = await this.key(connection) + check() return { baseUrl: connection.apiBaseUrl, - apiKey: this.key(connection), + apiKey, timeout: connection.timeout } } @@ -194,6 +200,7 @@ export class NowledgeMemConnections { clear(): void { if (this.busy) throw new Error('A connection update is in progress') + this.secrets.captureOperation() for (const key of Object.keys(this.settings.store)) { if (isNowledgeMachineLocalSetting(key)) this.settings.delete(key) } @@ -209,7 +216,8 @@ export class NowledgeMemConnections { } } - getMcpBindings(config: Partial): Record { + async getMcpBindings(config: Partial): Promise> { + const check = this.secrets.captureOperation() if (config.ownerPluginId !== NOWLEDGE_PLUGIN_ID) return {} const connection = this.read().connection if ( @@ -217,13 +225,16 @@ export class NowledgeMemConnections { config.baseUrl === connection.mcpUrl && config.sourceId === NOWLEDGE_PLUGIN_ID ) { - return { [KEY_VARIABLE]: this.key(connection) } + const apiKey = await this.key(connection) + check() + return { [KEY_VARIABLE]: apiKey } } throw new Error('The Nowledge MCP destination no longer matches its saved connection') } async save(rawInput: unknown): Promise { const input = NowledgeConnectionInputSchema.parse(rawInput) + const check = this.secrets.captureOperation() if (this.busy) throw new Error('A connection update is in progress') this.busy = true try { @@ -254,7 +265,8 @@ export class NowledgeMemConnections { // Reuse only credentials bound to these exact destinations, never ambient nmem settings. const suppliedKey = apiKey const savedCredential = state.credentials?.[this.credentialId(endpoints)] - apiKey ||= savedCredential ? this.secrets.get(savedCredential) : '' + apiKey ||= savedCredential ? await this.secrets.get(savedCredential) : '' + check() try { await this.verify(endpoints, apiKey, input.timeout) } catch (error) { @@ -269,10 +281,11 @@ export class NowledgeMemConnections { endpoints = resolveMemEndpoints({ ...input, apiBaseUrl: `${endpoints.baseUrl}/remote-api` }) if (!suppliedKey) { const resolvedCredential = state.credentials?.[this.credentialId(endpoints)] - apiKey = resolvedCredential ? this.secrets.get(resolvedCredential) : '' + apiKey = resolvedCredential ? await this.secrets.get(resolvedCredential) : '' if (!apiKey && savedCredential) throw new Error('Enter a credential for the resolved API and MCP destinations') } + check() await this.verify(endpoints, apiKey, input.timeout) } if ( @@ -285,14 +298,16 @@ export class NowledgeMemConnections { const destinationKey = this.credentialId(endpoints) const previousCredentialId = state.credentials?.[destinationKey] const reuse = Boolean( - previousCredentialId && this.secrets.get(previousCredentialId) === apiKey + previousCredentialId && (await this.secrets.get(previousCredentialId)) === apiKey ) const credentialId = apiKey ? reuse ? previousCredentialId : `${destinationKey}.${randomUUID()}` : undefined - if (credentialId && !reuse) this.secrets.setWrapped(credentialId, this.secrets.wrap(apiKey)) + const wrapped = credentialId && !reuse ? await this.secrets.wrap(apiKey) : undefined + check() + if (credentialId && wrapped !== undefined) this.secrets.setWrapped(credentialId, wrapped) const connection: StoredConnection = { ...endpoints, timeout: input.timeout, @@ -357,13 +372,16 @@ export class NowledgeMemConnections { apiKey: string, timeout: number ): Promise { + const check = this.secrets.captureOperation() await this.readJson(`${endpoints.apiBaseUrl}/health`, apiKey, timeout, 'Health check') + check() await this.readJson( `${endpoints.apiBaseUrl}/memories?limit=1`, apiKey, timeout, 'REST authentication' ) + check() const client = new Client({ name: 'DeepChat', version: '1.0.0' }) const deadline = AbortSignal.timeout(timeout) const transport = new StreamableHTTPClientTransport(new URL(endpoints.mcpUrl), { @@ -377,7 +395,9 @@ export class NowledgeMemConnections { }) try { await client.connect(transport, { timeout, signal: deadline }) + check() const catalog = await client.listTools({}, { timeout, signal: deadline }) + check() const name = ['read_context_bundle', 'read_working_memory'].find((candidate) => catalog.tools.some((tool) => tool.name === candidate) ) @@ -387,6 +407,7 @@ export class NowledgeMemConnections { undefined, { timeout, signal: deadline } ) + check() if (result.isError) throw new Error('Context tool failed') } catch { throw new Error( diff --git a/src/main/ocr/ocrRuntimeService.ts b/src/main/ocr/ocrRuntimeService.ts index 04a41a5182..99556a4813 100644 --- a/src/main/ocr/ocrRuntimeService.ts +++ b/src/main/ocr/ocrRuntimeService.ts @@ -16,12 +16,13 @@ import { } from './imageTextExtractionService' import { LightOcrProcessHost, type LightOcrProcessHostStatus } from './lightOcrProcessHost' import { OcrArtifactStore, type OcrArtifactStoreStats } from './ocrArtifactStore' -import { SafeStorageOcrCacheKeyProvider } from './ocrCacheKeyProvider' +import type { OcrCacheKeyProvider } from './ocrCacheKeyProvider' import { OcrExtractionScheduler } from './ocrExtractionScheduler' import { OcrRuntimeAssetResolver, type OcrRuntimeAvailability } from './ocrRuntimeAssetResolver' import { OcrSourceSnapshotBudget } from './ocrSourceSnapshotBudget' export interface OcrRuntimeServiceOptions { + cacheKeyProvider: OcrCacheKeyProvider appPath: string isPackaged: boolean nodeRuntimePath: string | null @@ -208,7 +209,7 @@ export class OcrRuntimeService { }) store = new OcrArtifactStore({ dbPath: path.join(cacheDir, 'ocr-cache.db'), - keyProvider: new SafeStorageOcrCacheKeyProvider(path.join(cacheDir, 'cache-key.json')) + keyProvider: this.options.cacheKeyProvider }) scheduler = new OcrExtractionScheduler() const snapshotBudget = new OcrSourceSnapshotBudget() diff --git a/src/main/platform/fetchDispatcher.ts b/src/main/platform/fetchDispatcher.ts new file mode 100644 index 0000000000..222b73552d --- /dev/null +++ b/src/main/platform/fetchDispatcher.ts @@ -0,0 +1,35 @@ +import { Agent, EnvHttpProxyAgent } from 'undici' + +// Model cancellation owns slow first-token and response-body deadlines. +export const FETCH_DISPATCHER_TIMEOUTS = { + headersTimeout: 0, + bodyTimeout: 0 +} as const + +export function createGlobalFetchDispatcher(proxy?: { + httpProxy: string + httpsProxy: string + noProxy: string +}): Agent | EnvHttpProxyAgent { + return proxy + ? new EnvHttpProxyAgent({ ...proxy, ...FETCH_DISPATCHER_TIMEOUTS }) + : new Agent(FETCH_DISPATCHER_TIMEOUTS) +} + +export function applyProxyEnvironment(proxyUrl: string | null, noProxy: string): void { + for (const key of [ + 'http_proxy', + 'https_proxy', + 'HTTP_PROXY', + 'HTTPS_PROXY', + 'GRPC_PROXY', + 'grpc_proxy' + ]) { + if (proxyUrl === null) delete process.env[key] + else process.env[key] = proxyUrl + } + for (const key of ['no_proxy', 'NO_PROXY']) { + if (proxyUrl === null) delete process.env[key] + else process.env[key] = noProxy + } +} diff --git a/src/main/platform/fileWatcher/watcherHostClient.ts b/src/main/platform/fileWatcher/watcherHostClient.ts index 1697bddd03..62fff5dc02 100644 --- a/src/main/platform/fileWatcher/watcherHostClient.ts +++ b/src/main/platform/fileWatcher/watcherHostClient.ts @@ -1,7 +1,3 @@ -import fs from 'fs' -import path from 'path' -import { fileURLToPath } from 'url' -import type { UtilityProcess } from 'electron' import type { FileWatcherHostEvent, FileWatcherRpcMethod, @@ -13,6 +9,19 @@ import type { WatchStatusListener } from './watcherTypes' +export interface FileWatcherHost { + postMessage(message: FileWatcherRpcRequest): void + onMessage(listener: (message: unknown) => void): void + onExit(listener: (code: number) => void): void + onError(listener: (type: string, location: string) => void): void + terminate(): Promise +} + +export type SpawnFileWatcherHost = ( + kind: WatcherHostKind, + signal: AbortSignal +) => Promise + type PendingRequest = { resolve(value: unknown): void reject(error: Error): void @@ -23,8 +32,10 @@ const RESTART_DELAY_MS = 800 const RPC_TIMEOUT_MS = 15000 export class WatcherHostClient { - private host: UtilityProcess | null = null - private hostReady: Promise | null = null + private host: FileWatcherHost | null = null + private hostReady: Promise | null = null + private pendingSpawn: AbortController | undefined + private stopping: Promise | undefined private requestId = 0 private restartAttempts = 0 private restartTimer: NodeJS.Timeout | null = null @@ -34,7 +45,10 @@ export class WatcherHostClient { private readonly batchListeners = new Set() private readonly statusListeners = new Set() - constructor(private readonly hostKind: WatcherHostKind) {} + constructor( + private readonly hostKind: WatcherHostKind, + private readonly spawnHost: SpawnFileWatcherHost + ) {} onBatch(listener: WatchBatchListener): () => void { this.batchListeners.add(listener) @@ -54,6 +68,7 @@ export class WatcherHostClient { this.activeRequests.set(request.id, request) try { await this.request('watch', [request]) + if (this.shuttingDown) throw new Error('File watcher is stopped.') } catch (error) { this.activeRequests.delete(request.id) throw error @@ -68,29 +83,34 @@ export class WatcherHostClient { await this.request('unwatch', [watchId]).catch(() => {}) } - async shutdown(): Promise { + shutdown(): Promise { + if (this.stopping) return this.stopping this.shuttingDown = true - + this.pendingSpawn?.abort(new Error('File watcher startup canceled by shutdown.')) if (this.restartTimer) { clearTimeout(this.restartTimer) this.restartTimer = null } - - try { - if (this.host) { - await this.request('shutdown', []) + this.stopping = (async () => { + await this.hostReady?.catch(() => undefined) + const host = this.host + try { + if (host) await this.request('shutdown', []) + } finally { + await host?.terminate() + this.host = null + this.activeRequests.clear() + this.rejectPendingRequests(new Error('File watcher process shut down.')) } - } finally { - this.host?.kill() - this.host = null - this.hostReady = null - this.activeRequests.clear() - this.rejectPendingRequests(new Error('File watcher utility process shut down.')) - } + })() + return this.stopping } private async request(method: FileWatcherRpcMethod, args: unknown[]): Promise { - const host = await this.ensureHost() + if (this.shuttingDown && method !== 'shutdown') throw new Error('File watcher is stopped.') + const host = this.host ?? (await this.ensureHost()) + if (host !== this.host || (this.shuttingDown && method !== 'shutdown')) + throw new Error('File watcher generation ended.') const id = `watcher_rpc_${this.hostKind}_${++this.requestId}` return await new Promise((resolve, reject) => { @@ -127,7 +147,8 @@ export class WatcherHostClient { }) } - private async ensureHost(): Promise { + private async ensureHost(): Promise { + if (this.shuttingDown) throw new Error('File watcher is stopped.') if (this.host) { return this.host } @@ -136,83 +157,45 @@ export class WatcherHostClient { return await this.hostReady } - this.shuttingDown = false - this.hostReady = this.startHost() + const opening = Promise.resolve().then(() => this.startHost()) + this.hostReady = opening try { - return await this.hostReady + return await opening } finally { - this.hostReady = null + if (this.hostReady === opening) this.hostReady = null } } - private async startHost(): Promise { - const { app, utilityProcess } = await import('electron') - const modulePath = this.resolveUtilityHostEntryPoint(app.getAppPath()) - const serviceLabel = this.hostKind === 'git' ? 'Git' : 'Content' - const host = utilityProcess.fork(modulePath, ['--deepchat-file-watcher-host'], { - serviceName: `DeepChat ${serviceLabel} File Watcher`, - stdio: 'ignore', - env: { - ...process.env, - DEEPCHAT_FILE_WATCHER_HOST: '1', - DEEPCHAT_FILE_WATCHER_HOST_KIND: this.hostKind + private async startHost(): Promise { + if (this.shuttingDown) throw new Error('File watcher is stopped.') + const startup = new AbortController() + this.pendingSpawn = startup + try { + const host = await this.spawnHost(this.hostKind, startup.signal) + if (this.shuttingDown || startup.signal.aborted) { + await host.terminate() + throw new Error('File watcher startup was superseded by shutdown.') } - }) - - host.on('message', (message) => this.handleHostMessage(message)) - host.on('exit', (code) => this.handleHostExit(code)) - host.on('error', (type, location) => { - console.error('[FileWatcherClient] Utility process error:', { - hostKind: this.hostKind, - type, - location + this.host = host + this.restartAttempts = 0 + host.onMessage((message) => { + if (this.host === host) this.handleHostMessage(message) }) - }) - - return await new Promise((resolve, reject) => { - let settled = false - const settle = (callback: () => void) => { - if (settled) { - return - } - settled = true - host.off('spawn', onSpawn) - host.off('exit', onExit) - callback() - } - const onSpawn = () => { - settle(() => { - this.host = host - this.restartAttempts = 0 - resolve(host) - }) - } - const onExit = (code: number) => { - settle(() => { - reject(new Error(`File watcher utility process exited before spawn: ${code}`)) - }) - } - - host.once('spawn', onSpawn) - host.once('exit', onExit) - }) - } - - private resolveUtilityHostEntryPoint(appPath?: string): string { - const modulePath = fileURLToPath(import.meta.url) - const candidates = [ - ...(appPath - ? [ - path.join(appPath, 'out/main/fileWatcherUtilityHost.js'), - path.join(appPath, 'fileWatcherUtilityHost.js') - ] - : []), - path.resolve(path.dirname(modulePath), 'fileWatcherUtilityHost.js'), - path.resolve(path.dirname(modulePath), '../fileWatcherUtilityHost.js'), - path.resolve(process.cwd(), 'out/main/fileWatcherUtilityHost.js') - ] - - return candidates.find((candidate) => fs.existsSync(candidate)) ?? candidates[0] + host.onExit((code) => { + if (this.host === host) this.handleHostExit(code) + }) + host.onError((type, location) => { + if (this.host === host && !this.shuttingDown) + console.error('[FileWatcherClient] Helper process error:', { + hostKind: this.hostKind, + type, + location + }) + }) + return host + } finally { + if (this.pendingSpawn === startup) this.pendingSpawn = undefined + } } private handleHostMessage(message: unknown): void { @@ -225,6 +208,7 @@ export class WatcherHostClient { this.handleRpcResponse(response) return } + if (this.shuttingDown) return const hostEvent = message as FileWatcherHostEvent if (hostEvent.type === 'file-watcher:event-batch') { @@ -290,7 +274,7 @@ export class WatcherHostClient { } private scheduleRestart(): void { - if (this.restartTimer || this.restartAttempts >= MAX_RESTART_ATTEMPTS) { + if (this.shuttingDown || this.restartTimer || this.restartAttempts >= MAX_RESTART_ATTEMPTS) { return } diff --git a/src/main/platform/fileWatcher/watcherPool.ts b/src/main/platform/fileWatcher/watcherPool.ts index 8f1a3cf12b..464a4fa090 100644 --- a/src/main/platform/fileWatcher/watcherPool.ts +++ b/src/main/platform/fileWatcher/watcherPool.ts @@ -1,5 +1,5 @@ import path from 'path' -import { WatcherHostClient } from './watcherHostClient' +import type { WatcherHostClient } from './watcherHostClient' import type { WatchBatchListener, WatcherEvent, @@ -10,6 +10,11 @@ import type { WatchStatusListener } from './watcherTypes' +type WatcherClient = Pick< + WatcherHostClient, + 'watch' | 'unwatch' | 'shutdown' | 'onBatch' | 'onStatus' +> + type WatcherPoolEntry = { key: string request: WatchRequest @@ -85,12 +90,12 @@ export class WatcherPool { private sequence = 0 private readonly entriesByKey = new Map() private readonly entriesByWatchId = new Map() - private readonly contentClient: WatcherHostClient - private readonly gitClient: WatcherHostClient + private readonly contentClient: WatcherClient + private readonly gitClient: WatcherClient - constructor(clients?: { content?: WatcherHostClient; git?: WatcherHostClient }) { - this.contentClient = clients?.content ?? new WatcherHostClient('content') - this.gitClient = clients?.git ?? new WatcherHostClient('git') + constructor(clients: { content: WatcherClient; git: WatcherClient }) { + this.contentClient = clients.content + this.gitClient = clients.git this.contentClient.onBatch((batch) => this.handleBatch(batch)) this.gitClient.onBatch((batch) => this.handleBatch(batch)) this.contentClient.onStatus((status) => this.handleStatus(status)) @@ -152,7 +157,11 @@ export class WatcherPool { async destroy(): Promise { this.entriesByKey.clear() this.entriesByWatchId.clear() - await Promise.all([this.contentClient.shutdown(), this.gitClient.shutdown()]) + const results = await Promise.allSettled([ + this.contentClient.shutdown(), + this.gitClient.shutdown() + ]) + for (const result of results) if (result.status === 'rejected') throw result.reason } private async unwatch( @@ -201,7 +210,7 @@ export class WatcherPool { } } - private getClient(request: WatchRequest): WatcherHostClient { + private getClient(request: WatchRequest): WatcherClient { return request.hostKind === 'git' ? this.gitClient : this.contentClient } diff --git a/src/main/platform/fileWatcher/watcherService.ts b/src/main/platform/fileWatcher/watcherService.ts index 9593312c8a..e13b6c0ec8 100644 --- a/src/main/platform/fileWatcher/watcherService.ts +++ b/src/main/platform/fileWatcher/watcherService.ts @@ -2,14 +2,13 @@ import { WatcherPool } from './watcherPool' import type { IFileWatcherService, WatchBatchListener, - WatcherHostKind, WatchHandle, WatchRequest, WatchStatusListener } from './watcherTypes' export class FileWatcherService implements IFileWatcherService { - constructor(private readonly pool = new WatcherPool()) {} + constructor(private readonly pool: WatcherPool) {} async watch( request: WatchRequest, @@ -23,11 +22,3 @@ export class FileWatcherService implements IFileWatcherService { await this.pool.destroy() } } - -export function createWatcherRequestId( - kind: WatcherHostKind, - purpose: string, - rootPath: string -): string { - return `${kind}:${purpose}:${rootPath}` -} diff --git a/src/main/platform/fileWatcher/watcherTypes.ts b/src/main/platform/fileWatcher/watcherTypes.ts index 4fbc4d21f4..f5c6a2651d 100644 --- a/src/main/platform/fileWatcher/watcherTypes.ts +++ b/src/main/platform/fileWatcher/watcherTypes.ts @@ -1,5 +1,13 @@ export type WatcherHostKind = 'content' | 'git' +export function createWatcherRequestId( + kind: WatcherHostKind, + purpose: string, + rootPath: string +): string { + return `${kind}:${purpose}:${rootPath}` +} + export type WatchPurpose = 'workspace-content' | 'workspace-git' | 'skills' export type WatchEventType = 'create' | 'update' | 'delete' | 'overflow' | 'root-deleted' diff --git a/src/main/platform/imageCache.ts b/src/main/platform/imageCache.ts index bfcfdc254c..afb2017eb7 100644 --- a/src/main/platform/imageCache.ts +++ b/src/main/platform/imageCache.ts @@ -2,7 +2,6 @@ import fs from 'node:fs' import path from 'node:path' import dns from 'node:dns/promises' import { BlockList, isIP, type LookupFunction } from 'node:net' -import { app } from 'electron' import { nanoid } from 'nanoid' import axios, { type AxiosRequestConfig } from 'axios' @@ -35,6 +34,14 @@ const IMAGE_MIME_BY_EXTENSION: Record = { '.webp': 'image/webp' } +function getCacheDirectory(): string { + const userDataDirectory = process.env.DEEPCHAT_USER_DATA_DIR + if (!userDataDirectory || !path.isAbsolute(userDataDirectory)) { + throw new Error('Image cache requires an absolute bootstrap profile directory') + } + return path.join(userDataDirectory, 'images') +} + const IMAGE_EXTENSION_BY_MIME: Record = Object.fromEntries( Object.entries(IMAGE_MIME_BY_EXTENSION).map(([extension, mimeType]) => [ mimeType, @@ -295,7 +302,7 @@ export async function cacheImage( options.signal?.throwIfAborted() if (imageData.trim().toLowerCase().startsWith(IMGCACHE_URL_PREFIX)) return imageData.trim() - const cacheDir = path.join(app.getPath('userData'), 'images') + const cacheDir = getCacheDirectory() if (!fs.existsSync(cacheDir)) fs.mkdirSync(cacheDir, { recursive: true }) const fileName = `img_${Date.now()}_${nanoid(8)}` @@ -332,7 +339,7 @@ export async function resolveCachedImageDataUrl( throw new Error('Unsupported cached image reference') } - const cacheDir = path.join(app.getPath('userData'), 'images') + const cacheDir = getCacheDirectory() const cachePath = safeDecodePath(normalizedSource.slice(IMGCACHE_URL_PREFIX.length)) const fullPath = path.resolve(cacheDir, cachePath) if (!isPathInsideRoot(cacheDir, fullPath)) { diff --git a/src/main/platform/proxy.ts b/src/main/platform/proxy.ts index 741a8fd0e9..dfa08b8388 100644 --- a/src/main/platform/proxy.ts +++ b/src/main/platform/proxy.ts @@ -1,6 +1,10 @@ import logger from '@shared/logger' import { session } from 'electron' -import { Agent, EnvHttpProxyAgent, setGlobalDispatcher } from 'undici' +import { setGlobalDispatcher } from 'undici' +import { applyProxyEnvironment, createGlobalFetchDispatcher } from './fetchDispatcher' +import type { CoreProxySnapshot } from '@shared/harness/desktop' +export { FETCH_DISPATCHER_TIMEOUTS, createGlobalFetchDispatcher } from './fetchDispatcher' +type Dispatcher = ReturnType // 先简单处理,用系统代理 export enum ProxyMode { @@ -12,33 +16,9 @@ export const NO_PROXY = 'localhost, 127.0.0.1, ::1, 192.168.*.*, 10.*.*.*, *.local, host.docker.internal' // const NO_PROXY = '' -// undici's default Agent uses headersTimeout 300s. 0 disables it so slow first-token -// providers (local Ollama) are bounded by the model timeout instead. -export const FETCH_DISPATCHER_TIMEOUTS = { - headersTimeout: 0, - bodyTimeout: 0 -} as const - -export function createGlobalFetchDispatcher(proxy?: { - httpProxy: string - httpsProxy: string - noProxy: string -}): Agent | EnvHttpProxyAgent { - if (proxy) { - return new EnvHttpProxyAgent({ - httpProxy: proxy.httpProxy, - httpsProxy: proxy.httpsProxy, - noProxy: proxy.noProxy, - ...FETCH_DISPATCHER_TIMEOUTS - }) - } - return new Agent(FETCH_DISPATCHER_TIMEOUTS) -} - // Merge app defaults with the process inherited no_proxy snapshot, not live env. // resolve can write NO_PROXY then later clear it; live env would lose the original. function mergeNoProxy(defaultNoProxy: string, inheritedNoProxy: string): string { - logger.info('systemNoProxy', inheritedNoProxy) if (!inheritedNoProxy) { return defaultNoProxy } @@ -53,14 +33,53 @@ function mergeNoProxy(defaultNoProxy: string, inheritedNoProxy: string): string } export class ProxyConfig { + private version = 0 + private noProxy = '' private proxyUrl: string | null = null private mode: ProxyMode = ProxyMode.SYSTEM private customProxyUrl: string = '' private resolutionPromise: Promise = Promise.resolve(true) - private dispatcherInstalled = false + private dispatcher: Dispatcher | undefined + private readonly retiring = new Map>() + private closing: Promise | undefined + private failure: Error | undefined private readonly inheritedNoProxy = process.env.no_proxy || process.env.NO_PROXY || '' - resolveProxy(): Promise { + constructor( + private readonly failed: (error: Error) => void = (error) => + logger.warn('[Proxy] HTTP dispatcher retirement failed', error) + ) {} + + private assertActive(): void { + if (this.failure) throw this.failure + if (this.closing) throw new Error('Native HTTP is stopping') + } + + private replaceDispatcher(next: Dispatcher): void { + this.assertActive() + const previous = this.dispatcher + setGlobalDispatcher(next) + this.dispatcher = next + if (previous) { + const retirement = previous + .close() + .catch(async () => await previous.destroy()) + .then(() => { + this.retiring.delete(previous) + }) + this.retiring.set(previous, retirement) + void retirement.catch((error) => { + this.failure = + error instanceof Error + ? error + : new Error('Native HTTP retirement failed', { cause: error }) + if (!this.closing) this.failed(this.failure) + }) + } + } + + async resolveProxy(): Promise { + this.assertActive() const mode = this.mode const customProxyUrl = this.customProxyUrl const resolution = this.resolutionPromise.then( @@ -72,19 +91,23 @@ export class ProxyConfig { } private ensureTimeoutDispatcher(): void { - if (this.dispatcherInstalled) { + this.assertActive() + if (this.dispatcher) { return } - setGlobalDispatcher(createGlobalFetchDispatcher()) - this.dispatcherInstalled = true + this.replaceDispatcher(createGlobalFetchDispatcher()) } - whenReady(): Promise { - return this.resolutionPromise + async whenReady(): Promise { + this.assertActive() + const result = await this.resolutionPromise + this.assertActive() + return result } private async resolveProxyNow(mode: ProxyMode, customProxyUrl: string): Promise { try { + this.assertActive() this.ensureTimeoutDispatcher() // 根据不同的代理模式设置 if (mode === ProxyMode.NONE) { @@ -92,22 +115,22 @@ export class ProxyConfig { logger.info('clear proxy') return false } else if (mode === ProxyMode.CUSTOM && customProxyUrl) { - logger.info('proxy url', customProxyUrl) await this.setCustomProxy(customProxyUrl) return false } // 系统代理模式 await session.defaultSession.setProxy({ mode: 'system' }) + this.assertActive() const proxyString = await session.defaultSession.resolveProxy('https://www.google.com') + this.assertActive() const [protocol, address] = proxyString.split(';')[0].split(' ') - logger.info('proxy url', protocol, address) const resolvedProxyUrl = protocol === 'PROXY' && address?.trim() ? `http://${address.trim()}` : null if (resolvedProxyUrl) { const mergedNoProxy = mergeNoProxy(NO_PROXY, this.inheritedNoProxy) - setGlobalDispatcher( + this.replaceDispatcher( createGlobalFetchDispatcher({ httpProxy: resolvedProxyUrl, httpsProxy: resolvedProxyUrl, @@ -116,12 +139,15 @@ export class ProxyConfig { ) this.commitResolvedProxy(resolvedProxyUrl, mergedNoProxy) } else { - setGlobalDispatcher(createGlobalFetchDispatcher()) + this.replaceDispatcher(createGlobalFetchDispatcher()) this.proxyUrl = null - this.clearProxyEnv() + this.noProxy = '' + this.version++ + applyProxyEnvironment(null, '') } return true } catch (error) { + if (this.closing || this.failure) throw error console.error('Failed to resolve proxy:', error) // Leave the last good dispatcher in place. Recreating it here leaks the // previous pool and can drop a working proxy after a later resolve fails. @@ -129,40 +155,28 @@ export class ProxyConfig { } } - private clearProxyEnv(): void { - delete process.env.http_proxy - delete process.env.https_proxy - delete process.env.HTTP_PROXY - delete process.env.HTTPS_PROXY - delete process.env.GRPC_PROXY - delete process.env.grpc_proxy - delete process.env.no_proxy - delete process.env.NO_PROXY - } - private async clearProxy(): Promise { await session.defaultSession.setProxy({ mode: 'direct' }) + this.assertActive() + this.replaceDispatcher(createGlobalFetchDispatcher()) this.proxyUrl = null - this.clearProxyEnv() - setGlobalDispatcher(createGlobalFetchDispatcher()) + this.noProxy = '' + this.version++ + applyProxyEnvironment(null, '') } private commitResolvedProxy(proxyUrl: string, mergedNoProxy: string): void { this.proxyUrl = proxyUrl - process.env.http_proxy = proxyUrl - process.env.https_proxy = proxyUrl - process.env.HTTP_PROXY = proxyUrl - process.env.HTTPS_PROXY = proxyUrl - process.env.GRPC_PROXY = proxyUrl - process.env.grpc_proxy = proxyUrl - process.env.no_proxy = mergedNoProxy - process.env.NO_PROXY = mergedNoProxy + this.noProxy = mergedNoProxy + this.version++ + applyProxyEnvironment(proxyUrl, mergedNoProxy) } private async setCustomProxy(proxyUrl: string): Promise { await session.defaultSession.setProxy({ proxyRules: proxyUrl }) + this.assertActive() const mergedNoProxy = mergeNoProxy(NO_PROXY, this.inheritedNoProxy) - setGlobalDispatcher( + this.replaceDispatcher( createGlobalFetchDispatcher({ httpProxy: proxyUrl, httpsProxy: proxyUrl, @@ -198,8 +212,8 @@ export class ProxyConfig { } return true - } catch (error) { - console.error('Invalid proxy URL:', error) + } catch { + console.error('Invalid proxy URL') return false } } @@ -208,11 +222,17 @@ export class ProxyConfig { return this.proxyUrl } + async getProxySnapshot(): Promise { + await this.whenReady() + return { version: this.version, proxyUrl: this.proxyUrl, noProxy: this.noProxy } + } + getProxyMode(): ProxyMode { return this.mode } setProxyMode(mode: ProxyMode): void { + this.assertActive() this.mode = mode } @@ -221,16 +241,18 @@ export class ProxyConfig { } setCustomProxyUrl(url: string): void { + this.assertActive() // 只设置有效的URL,否则保留原有值 if (this.isValidProxyUrl(url) || url.trim() === '') { this.customProxyUrl = url } else { - console.warn('Invalid proxy URL format:', url) + console.warn('Invalid proxy URL format') } } // 从配置初始化代理设置 initFromConfig(mode: string, customUrl: string): void { + this.assertActive() this.mode = mode === ProxyMode.NONE ? ProxyMode.NONE @@ -247,7 +269,31 @@ export class ProxyConfig { } } this.ensureTimeoutDispatcher() - void this.resolveProxy() + void this.resolveProxy().catch((error) => { + if (!this.closing && !this.failure) this.failed(error) + }) + } + + close(): Promise { + if (this.closing) return this.closing + const dispatchers = [...this.retiring.keys(), ...(this.dispatcher ? [this.dispatcher] : [])] + const retirements = [...this.retiring.values()] + const resolution = this.resolutionPromise.catch(() => {}) + this.closing = Promise.resolve().then(async () => { + const results = await Promise.allSettled([ + ...dispatchers.map( + async (dispatcher) => await dispatcher.destroy(new Error('Native HTTP is stopping')) + ), + ...retirements + ]) + await resolution + const errors = results.flatMap((result) => + result.status === 'rejected' ? [result.reason] : [] + ) + if (errors.length) throw new AggregateError(errors, 'Native HTTP did not retire') + this.retiring.clear() + this.dispatcher = undefined + }) + return this.closing } } -export const proxyConfig = new ProxyConfig() diff --git a/src/main/platform/routes.ts b/src/main/platform/routes.ts index cb8477d7e0..420d3c7d4c 100644 --- a/src/main/platform/routes.ts +++ b/src/main/platform/routes.ts @@ -8,8 +8,8 @@ import type { ProxySettings, ProxySettingMode } from './proxySettings' export function createPlatformRoutes(deps: { proxySettings: ProxySettings - applyProxyMode(mode: ProxySettingMode): void - applyCustomProxyUrl(url: string): void + applyProxyMode(mode: ProxySettingMode): void | Promise + applyCustomProxyUrl(url: string): void | Promise }): ReadonlyMap Promise> { const read = () => ({ mode: deps.proxySettings.getMode(), @@ -28,7 +28,7 @@ export function createPlatformRoutes(deps: { async (rawInput: unknown) => { const input = configSetProxyModeRoute.input.parse(rawInput) deps.proxySettings.setMode(input.mode) - deps.applyProxyMode(input.mode) + await deps.applyProxyMode(input.mode) return configSetProxyModeRoute.output.parse(read()) } ], @@ -37,7 +37,7 @@ export function createPlatformRoutes(deps: { async (rawInput: unknown) => { const input = configSetCustomProxyUrlRoute.input.parse(rawInput) deps.proxySettings.setCustomUrl(input.url) - deps.applyCustomProxyUrl(input.url) + await deps.applyCustomProxyUrl(input.url) return configSetCustomProxyUrlRoute.output.parse(read()) } ] diff --git a/src/main/plugin/index.ts b/src/main/plugin/index.ts index 510f0c94a6..2f0aa4e8de 100644 --- a/src/main/plugin/index.ts +++ b/src/main/plugin/index.ts @@ -5,8 +5,6 @@ import { NOWLEDGE_MCP_SERVER_ID } from '@shared/types/nowledgeMemPlugin' import { normalizeMemUrl, type NowledgeMemConnections } from '@/nowledgeMem' -import { ToolchainService } from '@/toolchains' -import { createMinimalProcessEnvironment } from '@/mcp/processEnvironment' import { UserPlugins, type UserPluginRecord } from './userPlugins' import { UserPluginHooks } from './userPluginHooks' import type { @@ -20,13 +18,12 @@ import type { UserPluginInstallInput, PreparedUserPlugin } from '@shared/types/userPlugin' -import { app, shell } from 'electron' import { execFile } from 'node:child_process' import { createHash } from 'node:crypto' import fs from 'node:fs' import path from 'node:path' import { promisify } from 'node:util' -import ElectronStore from 'electron-store' +import Conf from 'conf' import { unzipSync } from 'fflate' import type { SkillServicePort } from '@shared/types/skill' import type { McpServicePort, MCPServerConfig } from '@shared/types/mcp' @@ -42,7 +39,7 @@ import type { RuntimeDependencyRecord } from '@shared/types/plugin' import { CUA_PLUGIN_ID, OFFICIAL_PLUGIN_SOURCE } from '@shared/types/plugin' -import { registerPluginToolPolicy, unregisterPluginToolPolicies } from './toolPolicyStore' +import type { PluginToolPolicyStore } from './toolPolicyStore' import type { PluginRuntimeSafetyStore, PluginRuntimeSentinel, @@ -85,12 +82,13 @@ type SkillContributionPort = Pick< export interface PluginSettingsWindowPort { open(input: { pluginId: string; title: string; entry: string }): Promise - close(pluginId: string): void - closeAll(): void - getPluginIdForWebContents(webContentsId: number): string | null + close(pluginId: string): Promise + closeAll(): Promise + getPluginIdForWebContents(webContentsId: number): string | null | Promise } type PluginServiceDeps = { + toolPolicies: Pick nowledgeMem?: NowledgeMemConnections exportNowledgeSession?: (input: NowledgeExportInput) => Promise<{ threadId: string }> contextTape?: TapeAnchorWriter & TapeNonContextEntryReader & TapeIncarnationReader @@ -111,12 +109,19 @@ type PluginServiceDeps = { | 'retryRuntime' > skillService: SkillContributionPort - settingsWindow: PluginSettingsWindowPort + settingsWindow: Pick + openExternal(url: string): Promise + openPath(filePath: string): Promise + hookEnvironment(): Record + profileDirectory: string + homeDirectory: string + workingDirectory: string + appVersion: string platform?: NodeJS.Platform arch?: NodeJS.Architecture - appPath?: string - isPackaged?: boolean - resourcesPath?: string + appPath: string + isPackaged: boolean + resourcesPath: string } type ResolvedOfficialPlugin = { @@ -165,6 +170,7 @@ export interface PluginServicePort { } export class PluginService implements PluginServicePort { + private readonly toolPolicies: PluginServiceDeps['toolPolicies'] readonly contextHooks?: UserPluginHooks private readonly nowledgeMem?: NowledgeMemConnections private readonly exportNowledgeSession?: PluginServiceDeps['exportNowledgeSession'] @@ -172,25 +178,20 @@ export class PluginService implements PluginServicePort { private readonly mcpSettings: McpSettings private readonly mcpService: PluginServiceDeps['mcpService'] private readonly skillService: SkillContributionPort - private readonly settingsWindow: PluginSettingsWindowPort + private readonly settingsWindow: PluginServiceDeps['settingsWindow'] + private readonly openExternal: PluginServiceDeps['openExternal'] + private readonly openPath: PluginServiceDeps['openPath'] + private readonly profileDirectory: string + private readonly homeDirectory: string + private readonly workingDirectory: string + private readonly appVersion: string private readonly runtimeSupervisor: PluginServiceDeps['runtimeSupervisor'] private readonly platform: NodeJS.Platform private readonly arch: NodeJS.Architecture private readonly appPath: string private readonly isPackaged: boolean private readonly resourcesPath: string - private readonly store = new ElectronStore({ - name: 'plugin-settings', - defaults: { - userInstallations: [], - userPluginPending: null, - installations: [], - resources: [], - runtimes: [], - migrations: {}, - runtimeSentinels: {} - } - }) + private readonly store: Conf private readonly runtimeSafetyStore: PluginRuntimeSafetyStore = { read: (key) => this.getRuntimeSentinelMap()[key], write: (key, sentinel) => { @@ -213,25 +214,52 @@ export class PluginService implements PluginServicePort { private readonly activationErrors = new Map() constructor(deps: PluginServiceDeps) { + for (const directory of [ + deps.profileDirectory, + deps.homeDirectory, + deps.appPath, + deps.workingDirectory + ]) { + if (!path.isAbsolute(directory)) throw new Error('Plugin directories must be absolute') + } + if (deps.resourcesPath && !path.isAbsolute(deps.resourcesPath)) + throw new Error('Plugin resources directory must be absolute') + this.profileDirectory = deps.profileDirectory + this.homeDirectory = deps.homeDirectory + this.workingDirectory = deps.workingDirectory + this.appVersion = deps.appVersion + this.store = new Conf({ + configName: 'plugin-settings', + cwd: this.profileDirectory, + projectVersion: this.appVersion, + defaults: { + userInstallations: [], + userPluginPending: null, + installations: [], + resources: [], + runtimes: [], + migrations: {}, + runtimeSentinels: {} + } + }) + this.toolPolicies = deps.toolPolicies this.mcpSettings = deps.mcpSettings this.mcpService = deps.mcpService this.skillService = deps.skillService this.nowledgeMem = deps.nowledgeMem this.exportNowledgeSession = deps.exportNowledgeSession this.settingsWindow = deps.settingsWindow + this.openExternal = deps.openExternal + this.openPath = deps.openPath this.runtimeSupervisor = deps.runtimeSupervisor this.platform = deps.platform ?? process.platform this.arch = deps.arch ?? process.arch - this.appPath = deps.appPath ?? app.getAppPath() - this.isPackaged = deps.isPackaged ?? app.isPackaged - this.resourcesPath = deps.resourcesPath ?? process.resourcesPath ?? '' + this.appPath = deps.appPath + this.isPackaged = deps.isPackaged + this.resourcesPath = deps.resourcesPath this.runtimeSupervisor.attachSafetyStore(this.runtimeSafetyStore) if (deps.contextTape) { - this.contextHooks = new UserPluginHooks(deps.contextTape, () => - ToolchainService.getInstance().prependResolvedToEnv( - createMinimalProcessEnvironment(process.env, process.platform) - ) - ) + this.contextHooks = new UserPluginHooks(deps.contextTape, deps.hookEnvironment) this.userPlugins = new UserPlugins({ root: this.getPluginInstallRoot(), hooks: this.contextHooks, @@ -345,7 +373,7 @@ export class PluginService implements PluginServicePort { // A forced quit can leave the separately persisted policy behind. Clear // it before removing the legacy server; activation re-registers the // current policy later when installation intent remains enabled. - unregisterPluginToolPolicies(CUA_PLUGIN_ID) + this.toolPolicies.unregister(CUA_PLUGIN_ID) await this.mcpSettings.removeMcpServer('cua-driver') } @@ -356,13 +384,14 @@ export class PluginService implements PluginServicePort { } async shutdown(): Promise { + await this.contextHooks?.stop() this.userPlugins?.shutdown() const pluginIds = new Set(this.getInstallations().map((installation) => installation.pluginId)) for (const pluginId of pluginIds) { - unregisterPluginToolPolicies(pluginId) + this.toolPolicies.unregister(pluginId) } - this.settingsWindow.closeAll() + await this.settingsWindow.closeAll() } async listPlugins(): Promise { @@ -521,7 +550,7 @@ export class PluginService implements PluginServicePort { await this.openRuntimeGuide(pluginId) return { ok: true } case 'runtime.openProject': - await shell.openExternal('https://github.com/trycua/cua') + await this.openExternal('https://github.com/trycua/cua') return { ok: true } case 'runtime.uninstallHelper': return { @@ -651,8 +680,8 @@ export class PluginService implements PluginServicePort { } await this.skillService.unregisterPluginSkillsByOwner(pluginId) - unregisterPluginToolPolicies(pluginId) - this.settingsWindow.close(pluginId) + this.toolPolicies.unregister(pluginId) + await this.settingsWindow.close(pluginId) this.removeResourceRecordsByOwner(pluginId) if (runtimeStopError) { throw runtimeStopError @@ -907,7 +936,7 @@ export class PluginService implements PluginServicePort { private registerToolPolicies(plugin: ResolvedOfficialPlugin): void { for (const policy of plugin.manifest.toolPolicies ?? []) { - registerPluginToolPolicy({ + this.toolPolicies.register({ pluginId: plugin.manifest.id, serverId: policy.serverId, tools: policy.tools, @@ -1329,7 +1358,7 @@ export class PluginService implements PluginServicePort { permissions.accessibility === 'granted' ? MACOS_SCREEN_CAPTURE_SETTINGS : MACOS_ACCESSIBILITY_SETTINGS - await shell.openExternal(settingsUrl) + await this.openExternal(settingsUrl) return } @@ -1337,7 +1366,7 @@ export class PluginService implements PluginServicePort { try { const runtime = await this.refreshRuntime(pluginId) if (runtime.helperAppPath) { - const openError = await shell.openPath(runtime.helperAppPath) + const openError = await this.openPath(runtime.helperAppPath) if (!openError) { return } @@ -1366,7 +1395,7 @@ export class PluginService implements PluginServicePort { } throw new Error(`Plugin ${pluginId} does not declare a runtime guide URL`) } - await shell.openExternal(guideUrl) + await this.openExternal(guideUrl) } private async loadOfficialPlugins(): Promise { @@ -1430,7 +1459,7 @@ export class PluginService implements PluginServicePort { const sourceRoots = this.isPackaged ? [this.getPluginInstallRoot()] : [ - path.join(process.cwd(), 'plugins'), + path.join(this.workingDirectory, 'plugins'), path.join(this.appPath, 'plugins'), this.getPluginInstallRoot() ] @@ -1479,7 +1508,7 @@ export class PluginService implements PluginServicePort { const packageRoots = this.isPackaged ? resourceRoots : [ - path.join(process.cwd(), 'build', 'bundled-plugins'), + path.join(this.workingDirectory, 'build', 'bundled-plugins'), path.join(this.appPath, 'build', 'bundled-plugins'), path.join(this.appPath, 'plugins'), ...resourceRoots @@ -1966,7 +1995,7 @@ export class PluginService implements PluginServicePort { } private getPluginInstallRoot(): string { - return path.join(app.getPath('userData'), PLUGIN_INSTALL_DIRECTORY) + return path.join(this.profileDirectory, PLUGIN_INSTALL_DIRECTORY) } private getInstalledPluginRoot(pluginId: string): string { @@ -2263,7 +2292,7 @@ export class PluginService implements PluginServicePort { return candidate.slice('PATH:'.length) } if (candidate.startsWith('~/')) { - return path.join(app.getPath('home'), candidate.slice(2)) + return path.join(this.homeDirectory, candidate.slice(2)) } return candidate } @@ -2338,7 +2367,7 @@ export class PluginService implements PluginServicePort { private hydrateManifestPlaceholders(manifest: DeepChatPluginManifest): DeepChatPluginManifest { return JSON.parse( JSON.stringify(manifest) - .replaceAll('${app.version}', app.getVersion()) + .replaceAll('${app.version}', this.appVersion) .replaceAll('${arch}', this.arch) .replaceAll('${target.platform}', this.platform) .replaceAll( @@ -2349,7 +2378,7 @@ export class PluginService implements PluginServicePort { } private getReleaseTag(): string { - const version = app.getVersion() + const version = this.appVersion return version.startsWith('v') ? version : `v${version}` } diff --git a/src/main/plugin/routes.ts b/src/main/plugin/routes.ts index d1adad7685..5a1579a5bc 100644 --- a/src/main/plugin/routes.ts +++ b/src/main/plugin/routes.ts @@ -18,11 +18,16 @@ export function createPluginRoutes( pluginService: PluginServicePort, settingsWindow: PluginSettingsWindowPort ): DeepchatRouteMap { - const assertPluginSettingsCallerOwns = (context: RouteContext, pluginId: string): void => { + const assertPluginSettingsCallerOwns = async ( + context: RouteContext, + pluginId: string + ): Promise => { if (context.caller.kind !== 'renderer') { return } - const ownerPluginId = settingsWindow.getPluginIdForWebContents(context.caller.webContentsId) + const ownerPluginId = await settingsWindow.getPluginIdForWebContents( + context.caller.webContentsId + ) if (ownerPluginId != null && ownerPluginId !== pluginId) { throw new Error( `Plugin settings window for "${ownerPluginId}" cannot control plugin "${pluginId}"` @@ -95,7 +100,7 @@ export function createPluginRoutes( pluginsGetRoute.name, async (rawInput, context) => { const input = pluginsGetRoute.input.parse(rawInput) - assertPluginSettingsCallerOwns(context, input.pluginId) + await assertPluginSettingsCallerOwns(context, input.pluginId) return pluginsGetRoute.output.parse({ plugin: await pluginService.getPlugin(input.pluginId) }) @@ -105,7 +110,7 @@ export function createPluginRoutes( pluginsEnableRoute.name, async (rawInput, context) => { const input = pluginsEnableRoute.input.parse(rawInput) - assertPluginSettingsCallerOwns(context, input.pluginId) + await assertPluginSettingsCallerOwns(context, input.pluginId) return pluginsEnableRoute.output.parse({ result: await pluginService.enablePlugin(input.pluginId) }) @@ -115,7 +120,7 @@ export function createPluginRoutes( pluginsDisableRoute.name, async (rawInput, context) => { const input = pluginsDisableRoute.input.parse(rawInput) - assertPluginSettingsCallerOwns(context, input.pluginId) + await assertPluginSettingsCallerOwns(context, input.pluginId) return pluginsDisableRoute.output.parse({ result: await pluginService.disablePlugin(input.pluginId) }) @@ -125,7 +130,7 @@ export function createPluginRoutes( pluginsInvokeActionRoute.name, async (rawInput, context) => { const input = pluginsInvokeActionRoute.input.parse(rawInput) - assertPluginSettingsCallerOwns(context, input.pluginId) + await assertPluginSettingsCallerOwns(context, input.pluginId) return pluginsInvokeActionRoute.output.parse({ result: await pluginService.invokeAction(input.pluginId, input.actionId, input.payload) }) diff --git a/src/main/plugin/runtimeSupervisor.ts b/src/main/plugin/runtimeSupervisor.ts index 1f1f1b2482..e7a58a4aa4 100644 --- a/src/main/plugin/runtimeSupervisor.ts +++ b/src/main/plugin/runtimeSupervisor.ts @@ -421,18 +421,27 @@ export class PluginRuntimeSupervisor { ) } - async ensureRunning(serverName: string, reason: PluginRuntimeStartReason): Promise { + async ensureRunning( + serverName: string, + reason: PluginRuntimeStartReason, + assertCurrent?: () => void + ): Promise { + assertCurrent?.() if (this.probePromises.has(serverName)) { await this.waitForRuntimeProbe(serverName, true) } - await this.ensureRunningAuthorized(serverName, reason, false) + assertCurrent?.() + await this.ensureRunningAuthorized(serverName, reason, false, assertCurrent) + assertCurrent?.() } private async ensureRunningAuthorized( serverName: string, reason: PluginRuntimeStartReason, - allowQuarantinedFingerprint: boolean + allowQuarantinedFingerprint: boolean, + assertCurrent?: () => void ): Promise { + assertCurrent?.() let entry = this.getStartableEntry(serverName) const processPort = this.getProcessPort() if (!processPort.isReady()) { @@ -442,11 +451,14 @@ export class PluginRuntimeSupervisor { const stopping = this.stopPromises.get(serverName) if (stopping) { await stopping + assertCurrent?.() entry = this.getStartableEntry(serverName) } const existingStart = this.startPromises.get(serverName) if (existingStart) { - return await existingStart + await existingStart + assertCurrent?.() + return } if (entry.quarantine && processPort.isActive(serverName)) { const error = new PluginRuntimeQuarantinedError(serverName, entry.quarantine) @@ -468,10 +480,11 @@ export class PluginRuntimeSupervisor { throw error } - const startPromise = this.startEntry(entry, reason, allowQuarantinedFingerprint) + const startPromise = this.startEntry(entry, reason, allowQuarantinedFingerprint, assertCurrent) this.startPromises.set(serverName, startPromise) try { await startPromise + assertCurrent?.() } finally { if (this.startPromises.get(serverName) === startPromise) { this.startPromises.delete(serverName) @@ -489,11 +502,13 @@ export class PluginRuntimeSupervisor { await this.ensureRunning(serverName, 'external') } - async requestExternalStop(serverName: string): Promise { + async requestExternalStop(serverName: string, assertCurrent?: () => void): Promise { + assertCurrent?.() if (!this.entries.has(serverName)) { throw new Error(`Plugin runtime server "${serverName}" is not registered`) } - await this.stopServer(serverName) + await this.stopServer(serverName, 'normal', assertCurrent) + assertCurrent?.() } async testRuntime(pluginId: string, serverName: string): Promise { @@ -540,8 +555,10 @@ export class PluginRuntimeSupervisor { async restartIfRunning( serverName: string, - reason: Extract + reason: Extract, + assertCurrent?: () => void ): Promise { + assertCurrent?.() const processPort = this.processPort const entry = this.entries.get(serverName) if (!entry) { @@ -555,12 +572,14 @@ export class PluginRuntimeSupervisor { (!processPort.isActive(serverName) && !this.startPromises.has(serverName)) ) { if (entry.registration.startMode === 'eager') { - await this.ensureRunning(serverName, reason) + await this.ensureRunning(serverName, reason, assertCurrent) } return true } - await this.stopServer(serverName) - await this.ensureRunning(serverName, reason) + await this.stopServer(serverName, 'normal', assertCurrent) + assertCurrent?.() + await this.ensureRunning(serverName, reason, assertCurrent) + assertCurrent?.() return true } @@ -617,8 +636,10 @@ export class PluginRuntimeSupervisor { private async startEntry( entry: RuntimeEntry, reason: PluginRuntimeStartReason, - allowQuarantinedFingerprint: boolean + allowQuarantinedFingerprint: boolean, + assertCurrent?: () => void ): Promise { + assertCurrent?.() const processPort = this.getProcessPort() const { registration } = entry entry.state = 'starting' @@ -628,9 +649,16 @@ export class PluginRuntimeSupervisor { const initialFingerprint = registration.adapter ? await this.verifyLaunchArtifacts(entry) : undefined + assertCurrent?.() if (initialFingerprint) { - await this.authorizeGuardedSpawn(entry, initialFingerprint, allowQuarantinedFingerprint) + await this.authorizeGuardedSpawn( + entry, + initialFingerprint, + allowQuarantinedFingerprint, + assertCurrent + ) } + assertCurrent?.() if (registration.adapter) { entry.adapterCleanupRequired = true } @@ -643,6 +671,7 @@ export class PluginRuntimeSupervisor { } : undefined ) + assertCurrent?.() if (entry.retiring || this.shuttingDown) { throw new Error( `Plugin runtime server "${registration.serverName}" was stopped during startup` @@ -652,9 +681,16 @@ export class PluginRuntimeSupervisor { // boundaries. Re-verify the complete launch artifact contract here so a // replacement during daemon startup cannot become the proxy executable. const proxyFingerprint = await this.verifyLaunchArtifacts(entry) + assertCurrent?.() if (proxyFingerprint && !initialFingerprint) { - await this.authorizeGuardedSpawn(entry, proxyFingerprint, allowQuarantinedFingerprint) + await this.authorizeGuardedSpawn( + entry, + proxyFingerprint, + allowQuarantinedFingerprint, + assertCurrent + ) } + assertCurrent?.() if ( initialFingerprint && proxyFingerprint && @@ -666,6 +702,7 @@ export class PluginRuntimeSupervisor { ) } await processPort.start(registration.serverName, launch?.configOverride) + assertCurrent?.() if (!processPort.isRunning(registration.serverName)) { throw new Error( `Plugin runtime server "${registration.serverName}" did not reach running state` @@ -718,8 +755,10 @@ export class PluginRuntimeSupervisor { private async stopServer( serverName: string, - mode: 'normal' | 'shutdown' = 'normal' + mode: 'normal' | 'shutdown' = 'normal', + assertCurrent?: () => void ): Promise { + assertCurrent?.() const entry = this.entries.get(serverName) if (!entry) { return @@ -730,13 +769,16 @@ export class PluginRuntimeSupervisor { await this.escalateStopForShutdown(entry, existingStop) return } - return await existingStop + await existingStop + assertCurrent?.() + return } - const stopPromise = this.stopEntry(entry, mode) + const stopPromise = this.stopEntry(entry, mode, assertCurrent) this.stopPromises.set(serverName, stopPromise) try { await stopPromise + assertCurrent?.() } finally { if (this.stopPromises.get(serverName) === stopPromise) { this.stopPromises.delete(serverName) @@ -744,12 +786,18 @@ export class PluginRuntimeSupervisor { } } - private async stopEntry(entry: RuntimeEntry, mode: 'normal' | 'shutdown'): Promise { + private async stopEntry( + entry: RuntimeEntry, + mode: 'normal' | 'shutdown', + assertCurrent?: () => void + ): Promise { + assertCurrent?.() const { registration } = entry const startPromise = this.startPromises.get(registration.serverName) if (startPromise) { await startPromise.catch(() => undefined) } + assertCurrent?.() const processPort = this.processPort const errors: unknown[] = [] @@ -794,8 +842,10 @@ export class PluginRuntimeSupervisor { private async authorizeGuardedSpawn( entry: RuntimeEntry, fingerprint: PluginRuntimeFingerprint, - allowQuarantinedFingerprint: boolean + allowQuarantinedFingerprint: boolean, + assertCurrent?: () => void ): Promise { + assertCurrent?.() const { registration } = entry const expectedRuntimeId = registration.runtimeId ?? registration.serverName if ( @@ -827,6 +877,7 @@ export class PluginRuntimeSupervisor { } await recoverStaleLaunch.call(registration.adapter, previous.launchContext) } + assertCurrent?.() const sentinel: PluginRuntimeSentinel = { schemaVersion: 1, diff --git a/src/main/plugin/toolPolicyStore.ts b/src/main/plugin/toolPolicyStore.ts index f41a163d6e..8b217f34ff 100644 --- a/src/main/plugin/toolPolicyStore.ts +++ b/src/main/plugin/toolPolicyStore.ts @@ -1,85 +1,63 @@ -import ElectronStore from 'electron-store' import type { PluginToolPolicyDecision } from '@shared/types/plugin' -type StoredToolPolicy = { +export type StoredToolPolicy = { pluginId: string serverId: string tools: Record enabled: boolean } -type ToolPolicySettings = { - policies: StoredToolPolicy[] -} - export type PluginToolPolicyLookup = { managed: boolean decision: PluginToolPolicyDecision | null } -const store = new ElectronStore({ - name: 'plugin-tool-policies', - defaults: { - policies: [] +export class PluginToolPolicyStore { + constructor( + private readonly store: { + get(key: 'policies'): StoredToolPolicy[] | undefined + set(key: 'policies', policies: StoredToolPolicy[]): void + } + ) {} + + register(policy: StoredToolPolicy): void { + const policies = this.store.get('policies') ?? [] + const filtered = policies.filter( + (item) => !(item.pluginId === policy.pluginId && item.serverId === policy.serverId) + ) + this.store.set('policies', [...filtered, policy]) } -}) - -export function registerPluginToolPolicy(policy: StoredToolPolicy): void { - const policies = store.get('policies') ?? [] - const filtered = policies.filter( - (item) => !(item.pluginId === policy.pluginId && item.serverId === policy.serverId) - ) - store.set('policies', [...filtered, policy]) -} - -export function unregisterPluginToolPolicies(pluginId: string): void { - const policies = store.get('policies') ?? [] - store.set( - 'policies', - policies.filter((policy) => policy.pluginId !== pluginId) - ) -} -export function getPluginToolPolicy( - serverId: string, - toolName: string -): PluginToolPolicyDecision | null { - return resolvePluginToolPolicy(serverId, toolName).decision -} - -function getEnabledServerPolicies(serverId: string): StoredToolPolicy[] { - const policies = store.get('policies') ?? [] - return policies.filter((policy) => policy.enabled && policy.serverId === serverId) -} - -export function getExplicitlyDeniedPluginTools(serverId: string): string[] { - const matches = getEnabledServerPolicies(serverId) - if (matches.length !== 1) { - return [] + unregister(pluginId: string): void { + const policies = this.store.get('policies') ?? [] + this.store.set( + 'policies', + policies.filter((policy) => policy.pluginId !== pluginId) + ) } - return Object.entries(matches[0].tools) - .filter(([, decision]) => decision === 'deny') - .map(([toolName]) => toolName) -} -export function resolvePluginToolPolicy( - serverId: string, - toolName: string -): PluginToolPolicyLookup { - const matches = getEnabledServerPolicies(serverId) - if (matches.length === 0) { - return { managed: false, decision: null } + deniedTools(serverId: string): string[] { + const matches = this.getEnabledServerPolicies(serverId) + if (matches.length !== 1) return [] + return Object.entries(matches[0].tools) + .filter(([, decision]) => decision === 'deny') + .map(([toolName]) => toolName) } - // More than one enabled owner for a server is ambiguous. Treat it like an - // undeclared tool so stale policy records cannot accidentally widen access. - if (matches.length !== 1) { - return { managed: true, decision: null } + resolve(serverId: string, toolName: string): PluginToolPolicyLookup { + const matches = this.getEnabledServerPolicies(serverId) + if (matches.length === 0) return { managed: false, decision: null } + // Ambiguous owners retain a closed policy, including undeclared tools. + if (matches.length !== 1) return { managed: true, decision: null } + const decision = matches[0].tools[toolName] + return { + managed: true, + decision: decision === 'allow' || decision === 'ask' || decision === 'deny' ? decision : null + } } - const decision = matches[0].tools[toolName] - return { - managed: true, - decision: decision === 'allow' || decision === 'ask' || decision === 'deny' ? decision : null + private getEnabledServerPolicies(serverId: string): StoredToolPolicy[] { + const policies = this.store.get('policies') ?? [] + return policies.filter((policy) => policy.enabled && policy.serverId === serverId) } } diff --git a/src/main/plugin/userPluginHooks.ts b/src/main/plugin/userPluginHooks.ts index b51720358e..9a1f0cebe5 100644 --- a/src/main/plugin/userPluginHooks.ts +++ b/src/main/plugin/userPluginHooks.ts @@ -52,8 +52,38 @@ export class UserPluginHooks implements PluginContextPort { private queue: Promise = Promise.resolve() private activeRuns = new Map() private updating = false + private accepting = true + private controller = new AbortController() + private draining?: Promise + + start(): void { + if (this.draining) throw new Error('Plugin hook work must drain before restarting') + if (this.accepting) return + this.controller = new AbortController() + this.accepting = true + } + + async stop(): Promise { + this.accepting = false + this.controller.abort() + const drain = (this.draining ??= this.queue) + void drain.then(() => { + if (this.draining === drain) this.draining = undefined + }) + await new Promise((resolve, reject) => { + const timeout = setTimeout( + () => reject(new Error('Plugin hook work did not drain before shutdown')), + 30_000 + ) + void drain.then(() => { + clearTimeout(timeout) + resolve() + }) + }) + } beginRun(sessionId: string): void { + if (!this.accepting) throw new Error('Plugin hook owner is stopped') if (this.updating) throw new Error('A plugin update is being applied; retry after it completes') this.activeRuns.set(sessionId, (this.activeRuns.get(sessionId) ?? 0) + 1) } @@ -65,6 +95,7 @@ export class UserPluginHooks implements PluginContextPort { } beginUpdate(): () => void { + if (!this.accepting) throw new Error('Plugin hook owner is stopped') if (this.updating || this.activeRuns.size) throw new Error('Finish or stop active DeepChat turns before updating a plugin') this.updating = true @@ -150,15 +181,19 @@ export class UserPluginHooks implements PluginContextPort { } async accept(input: PluginContextInput): Promise { + if (!this.accepting) throw new Error('Plugin hook owner is stopped') if (!this.hasHooks()) return + const lifetime = this.controller.signal const incarnationId = this.tape.getTapeIncarnationId(input.sessionId) const operation = this.queue.then(async () => { + if (lifetime.aborted) return if (this.tape.getTapeIncarnationId(input.sessionId) !== incarnationId) return const deadline = AbortSignal.timeout(10000) - const signal = input.signal ? AbortSignal.any([deadline, input.signal]) : deadline + const signal = AbortSignal.any([deadline, lifetime, ...(input.signal ? [input.signal] : [])]) const promptHash = createHash('sha256').update(input.prompt).digest('hex') const history = this.history(input.sessionId) for (const owner of this.owners.values()) { + if (lifetime.aborted) return const previous = [...history.values()].filter( (item) => item.pluginId === owner.pluginId && item.digest === owner.digest ) @@ -390,6 +425,7 @@ export class UserPluginHooks implements PluginContextPort { } async retry(pluginId: string, invocationId: string): Promise { + if (!this.accepting) throw new Error('Plugin hook owner is stopped') const invocation = [...this.sessions.keys()] .map((sessionId) => this.history(sessionId).get(invocationId)) .find(Boolean) @@ -409,7 +445,9 @@ export class UserPluginHooks implements PluginContextPort { const incarnationId = this.tape.getTapeIncarnationId(sessionId) const expectedInput = JSON.stringify(invocation.input) const expectedStatus = invocation.status + const lifetime = this.controller.signal const operation = this.queue.then(() => { + lifetime.throwIfAborted() if (this.tape.getTapeIncarnationId(sessionId) !== incarnationId) throw new Error('Hook retry is unavailable after the session was cleared') const current = this.history(sessionId).get(invocationId) @@ -431,7 +469,7 @@ export class UserPluginHooks implements PluginContextPort { current.event, `${current.boundaryId}:retry:${randomUUID()}`, current.event === 'SessionStart' ? current.boundaryId.split(':')[0] : undefined, - AbortSignal.timeout(10000), + AbortSignal.any([AbortSignal.timeout(10000), lifetime]), current.hookId ) }) @@ -469,6 +507,7 @@ async function runContextHook( DEEPCHAT_PLUGIN_ID: owner.pluginId } }) + const closed = new Promise((resolve) => child.once('close', () => resolve())) const out: Buffer[] = [] let outBytes = 0 let errBytes = 0 @@ -485,7 +524,10 @@ async function runContextHook( /* The group already exited. */ } } - if (error) void terminateProcessTree(child, { graceMs: 100 }).finally(() => reject(error)) + if (error) + void Promise.allSettled([terminateProcessTree(child, { graceMs: 100 }), closed]).then(() => + reject(error) + ) else resolve(Buffer.concat(out).toString('utf8')) } const abort = () => finish(new Error('Hook cancelled')) diff --git a/src/main/plugin/userPlugins.ts b/src/main/plugin/userPlugins.ts index 2d6e1d1032..863911be62 100644 --- a/src/main/plugin/userPlugins.ts +++ b/src/main/plugin/userPlugins.ts @@ -517,7 +517,7 @@ export class UserPlugins { record.package.mcpServers.map(async (server) => { const key = `${record.pluginId}.${server.name}` const config = configs[key] - const bindings = config ? this.deps.mcpSettings.getMcpVariableBindings(config) : {} + const bindings = config ? await this.deps.mcpSettings.getMcpVariableBindings(config) : {} setup[key] = server.requiredVariables.filter((name) => !bindings[name]) const state = this.deps.supervisor.getState(key) return { @@ -577,7 +577,7 @@ export class UserPlugins { throw new Error('MCP configuration is unavailable; enable the plugin first') if (Object.keys(values).some((name) => !declared.requiredVariables.includes(name))) throw new Error('Undeclared MCP variable') - this.deps.mcpSettings.setMcpVariableBindings(current, values) + await this.deps.mcpSettings.setMcpVariableBindings(current, values) await this.deps.supervisor.unregisterPlugin(pluginId) if (current.serverId) this.deps.mcpService.revokeMcpAppsByServer?.(current.serverId) if (record.enabled) await this.activate(record) diff --git a/src/main/project/index.ts b/src/main/project/index.ts index 7cda5664ff..5cd164f40c 100644 --- a/src/main/project/index.ts +++ b/src/main/project/index.ts @@ -1,4 +1,3 @@ -import { app, shell } from 'electron' import fs from 'fs' import path from 'path' import type { DeviceServicePort } from '@shared/types/device' @@ -14,20 +13,29 @@ import type { SettingsStore } from '@/config/settingsStore' const PROJECT_SNAPSHOT_VERSION_SETTINGS_KEY = 'projectSnapshotVersion' +export type ProjectHost = Pick & { + readonly tempDirectory: string + readonly userDataDirectory: string + readonly appDataDirectory: string + readonly documentsDirectory: string | null + readonly homeDirectory: string | null + openPath(path: string): Promise +} + export class ProjectService { private sqlitePresenter: ProjectDatabase private sessionDatabase: SessionDatabase - private deviceService: DeviceServicePort private settings: SettingsStore private readonly tempRoot: string private readonly userDataWorkspacesRoot: string private readonly appDataRoot: string + private readonly defaultWorkspaceCandidates: string[] private snapshotVersion: number constructor( sqlitePresenter: ProjectDatabase, sessionDatabase: SessionDatabase, - deviceService: DeviceServicePort, + private readonly host: ProjectHost, settings: SettingsStore, private readonly publishDefaultProjectPathChanged: ( path: string | null, @@ -41,11 +49,21 @@ export class ProjectService { ) { this.sqlitePresenter = sqlitePresenter this.sessionDatabase = sessionDatabase - this.deviceService = deviceService this.settings = settings - this.tempRoot = path.resolve(app.getPath('temp')) - this.userDataWorkspacesRoot = path.resolve(path.join(app.getPath('userData'), 'workspaces')) - this.appDataRoot = path.resolve(app.getPath('appData')) + const resolveDirectory = (directory: string): string => { + if (!path.isAbsolute(directory)) { + throw new Error('Project host directories must be absolute') + } + return path.resolve(directory) + } + this.tempRoot = resolveDirectory(host.tempDirectory) + this.userDataWorkspacesRoot = path.join(resolveDirectory(host.userDataDirectory), 'workspaces') + this.appDataRoot = resolveDirectory(host.appDataDirectory) + this.defaultWorkspaceCandidates = this.normalizeUniqueEnvironmentPaths( + [host.documentsDirectory, host.homeDirectory, this.userDataWorkspacesRoot] + .filter((directory): directory is string => directory !== null) + .map((directory) => path.join(resolveDirectory(directory), 'DeepChat')) + ) this.snapshotVersion = this.readSnapshotVersion() } @@ -226,7 +244,7 @@ export class ProjectService { return } - const errorMessage = await shell.openPath(normalizedPath) + const errorMessage = await this.host.openPath(normalizedPath) if (errorMessage) { throw new Error(errorMessage) } @@ -243,7 +261,7 @@ export class ProjectService { } async selectDirectory(): Promise<{ path: string | null; version: number }> { - const result = await this.deviceService.selectDirectory() + const result = await this.host.selectDirectory() if (result.canceled || result.filePaths.length === 0) { return { path: null, version: this.snapshotVersion } } @@ -263,7 +281,7 @@ export class ProjectService { } async ensureDefaultWorkspace(): Promise { - const candidates = this.getDefaultWorkspaceCandidates() + const candidates = this.defaultWorkspaceCandidates const currentDefault = this.getDefaultProjectPath() const currentDefaultIsBuiltin = Boolean( currentDefault && this.isDefaultWorkspaceCandidate(currentDefault, candidates) @@ -303,7 +321,7 @@ export class ProjectService { getDefaultChatWorkspacePath(): string | null { const projectPath = this.getDefaultProjectPath() - let candidates = this.getDefaultWorkspaceCandidates() + let candidates = this.defaultWorkspaceCandidates if (projectPath && this.isDefaultWorkspaceCandidate(projectPath, candidates)) { candidates = [projectPath] } @@ -319,7 +337,7 @@ export class ProjectService { } ensureChatWorkspace(): { path: string; version: number } { - const candidates = this.getDefaultWorkspaceCandidates() + const candidates = this.defaultWorkspaceCandidates const currentDefault = this.getDefaultProjectPath() const currentDefaultIsBuiltin = Boolean( currentDefault && this.isDefaultWorkspaceCandidate(currentDefault, candidates) @@ -446,28 +464,6 @@ export class ProjectService { return normalizedPaths } - private getDefaultWorkspaceCandidates(): string[] { - const candidates: string[] = [] - const addCandidate = (basePath: string) => { - candidates.push(path.resolve(path.join(basePath, 'DeepChat'))) - } - - try { - addCandidate(app.getPath('documents')) - } catch (error) { - console.warn('[ProjectService] Failed to resolve Documents path:', error) - } - - try { - addCandidate(app.getPath('home')) - } catch (error) { - console.warn('[ProjectService] Failed to resolve Home path:', error) - } - - candidates.push(path.resolve(path.join(this.userDataWorkspacesRoot, 'DeepChat'))) - return this.normalizeUniqueEnvironmentPaths(candidates) - } - private isDefaultWorkspaceCandidate(workspacePath: string, candidates: string[]): boolean { const normalizedPath = path.resolve(workspacePath) return candidates.some((candidate) => path.resolve(candidate) === normalizedPath) diff --git a/src/main/provider/auth/githubCopilotDeviceFlow.ts b/src/main/provider/auth/githubCopilotDeviceFlow.ts index 30f41ae214..b20f4906d5 100644 --- a/src/main/provider/auth/githubCopilotDeviceFlow.ts +++ b/src/main/provider/auth/githubCopilotDeviceFlow.ts @@ -1,162 +1,80 @@ import { BrowserWindow, clipboard, shell } from 'electron' -import { exec } from 'child_process' import logger from '@shared/logger' import { z } from 'zod' -import { createProviderHttpErrorFromResponse, type ProviderHttpError } from '../providerFailure' +import { setTimeout as delay } from 'node:timers/promises' +import { readAuthResponse } from './responseBody' const GITHUB_DEVICE_URL = 'https://github.com/login/device' const GITHUB_COPILOT_DEVICE_FLOW_CLIENT_ID = 'Iv1.b507a08c87ecfe98' -const GITHUB_TOKEN_PATTERN = /^[\x21-\x7e]+$/ export interface DeviceFlowConfig { clientId: string scope: string } -export interface DeviceCodeResponse { - device_code: string - user_code: string - verification_uri: string - expires_in: number - interval: number -} - -export interface AccessTokenResponse { - access_token?: string - token_type?: string - scope?: string - error?: string - error_description?: string -} - -export interface CopilotTokenResponse { - token: string - expires_at: number - refresh_in?: number -} - -// Copilot chat uses VS Code integration metadata independently of the DeepChat app version. -export const GITHUB_COPILOT_CHAT_HEADERS = { - 'User-Agent': 'GitHubCopilotChat/0.44.0', - 'Editor-Version': 'vscode/1.115.0', - 'Editor-Plugin-Version': 'copilot-chat/0.44.0', - 'Copilot-Integration-Id': 'vscode-chat', - 'X-GitHub-Api-Version': '2026-08-01' -} - -const CopilotTokenSchema = z.object({ - token: z.string().regex(GITHUB_TOKEN_PATTERN), - expires_at: z.number().positive().finite(), - refresh_in: z.number().nonnegative().finite().optional() +const seconds = z + .number() + .int() + .positive() + .max(Math.floor(2 ** 31 / 1000)) +const DeviceCodeSchema = z.object({ + device_code: z.string().min(1).max(4096), + user_code: z + .string() + .min(1) + .max(128) + .regex(/^[A-Za-z0-9-]+$/), + verification_uri: z.literal(GITHUB_DEVICE_URL), + expires_in: seconds, + interval: seconds +}) +type DeviceCodeResponse = z.infer +const AccessTokenSchema = z.object({ + access_token: z + .string() + .regex(/^[\x21-\x7e]+$/) + .optional(), + error: z.string().optional(), + error_description: z.string().optional(), + interval: seconds.optional() }) - -export async function createGitHubCopilotHttpError( - response: Response, - phase: 'token' | 'chat', - secrets: string[] -): Promise { - const body = await response.json().catch(() => null) - const sanitize = (value: unknown): string => { - if (typeof value !== 'string') return '' - let text = value - for (const secret of secrets) { - if (secret) text = text.replaceAll(secret, '[redacted]') - } - return text - .replace(/(?:gh[opusr]_|github_pat_)[A-Za-z0-9_]+/g, '[redacted]') - .replace(/Bearer\s+[^\s"',}]+/gi, 'Bearer [redacted]') - .replace(/(?:access_token|refresh_token|api_key)["\s:=]+[^"'\s,}]+/gi, '[redacted]') - .replace(/[\r\n\0]/g, ' ') - .slice(0, 500) - } - const serverMessage = sanitize( - body?.error?.message ?? body?.error_details?.message ?? body?.message - ) - const serverCode = sanitize(body?.error?.code ?? body?.error_details?.code ?? body?.code) - const requestId = sanitize( - response.headers.get('x-github-request-id') ?? response.headers.get('x-request-id') - ) - const details = [ - serverMessage, - /^[A-Za-z0-9._:-]{1,128}$/.test(serverCode) ? `code=${serverCode}` : '', - /^[A-Za-z0-9._:-]{1,128}$/.test(requestId) ? `request_id=${requestId}` : '' - ].filter(Boolean) - const description = phase === 'token' ? 'token exchange failed' : 'API error' - return createProviderHttpErrorFromResponse( - `GitHub Copilot ${description}: ${response.status} ${sanitize(response.statusText)}` + - (details.length ? ` - ${details.join('; ')}` : ''), - response, - `github_copilot_${phase}_http_error` - ) -} - -export async function getGitHubCopilotToken( - oauthToken: string, - signal?: AbortSignal -): Promise { - signal?.throwIfAborted() - if (typeof oauthToken !== 'string' || !GITHUB_TOKEN_PATTERN.test(oauthToken)) { - throw new Error('No GitHub OAuth token available. Please sign in to GitHub Copilot.') - } - try { - const response = await fetch('https://api.github.com/copilot_internal/v2/token', { - headers: { - Authorization: `token ${oauthToken}`, - Accept: 'application/json', - 'User-Agent': GITHUB_COPILOT_CHAT_HEADERS['User-Agent'], - 'X-GitHub-Api-Version': '2025-04-01' - }, - ...(signal ? { signal } : {}) - }) - if (!response.ok) { - throw await createGitHubCopilotHttpError(response, 'token', [oauthToken]) - } - const result = CopilotTokenSchema.safeParse(await response.json().catch(() => null)) - if (!result.success) { - throw new Error('Invalid GitHub Copilot token response') - } - return result.data - } catch (error) { - signal?.throwIfAborted() - throw error - } -} export class GitHubCopilotDeviceFlow { - private config: DeviceFlowConfig - private pollingInterval: NodeJS.Timeout | null = null - - constructor(config: DeviceFlowConfig) { - this.config = config - } + constructor(private readonly config: DeviceFlowConfig) {} /** * Start Device Flow authentication process */ - async startDeviceFlow(): Promise { + async startDeviceFlow(signal: AbortSignal): Promise { + signal.throwIfAborted() + const expiration = new AbortController() + const active = AbortSignal.any([signal, expiration.signal]) + let timer: NodeJS.Timeout | undefined try { - // Step 1: Request device code - const deviceCodeResponse = await this.requestDeviceCode() - - // Step 2: Show user code and open browser - await this.showUserCodeAndOpenBrowser(deviceCodeResponse) - - // Step 3: Poll for access token - const accessToken = await this.pollForAccessToken(deviceCodeResponse) - - return accessToken + const deviceCodeResponse = await this.requestDeviceCode(active) + active.throwIfAborted() + const receivedAt = Date.now() + timer = setTimeout( + () => expiration.abort(new Error('Device code expired')), + deviceCodeResponse.expires_in * 1000 + ) + await this.showUserCodeAndOpenBrowser(deviceCodeResponse, active) + active.throwIfAborted() + return await this.pollForAccessToken(deviceCodeResponse, active, receivedAt) } catch (error) { - console.error('[GitHub Copilot] Device flow failed:', error) + active.throwIfAborted() throw new Error( `Device flow authentication failed: ${error instanceof Error ? error.message : 'Unknown error'}` ) + } finally { + clearTimeout(timer) } } /** * Step 1: Request device code */ - private async requestDeviceCode(): Promise { + private async requestDeviceCode(signal: AbortSignal): Promise { const url = 'https://github.com/login/device/code' const body = { client_id: this.config.clientId, @@ -164,15 +82,19 @@ export class GitHubCopilotDeviceFlow { } try { - const response = await fetch(url, { - method: 'POST', - headers: { - Accept: 'application/json', - 'Content-Type': 'application/json', - 'User-Agent': 'DeepChat/1.0.0' - }, - body: JSON.stringify(body) - }) + const response = await readAuthResponse( + await fetch(url, { + method: 'POST', + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json', + 'User-Agent': 'DeepChat/1.0.0' + }, + body: JSON.stringify(body), + signal + }), + signal + ) if (!response.ok) { const errorText = await response.text().catch(() => '') @@ -181,10 +103,11 @@ export class GitHubCopilotDeviceFlow { ) } - const data = (await response.json()) as DeviceCodeResponse - return data + const parsed = DeviceCodeSchema.safeParse(await response.json()) + if (!parsed.success) throw new Error('Invalid GitHub device-code response') + return parsed.data } catch (error) { - console.warn(error) + signal.throwIfAborted() throw error } } @@ -192,26 +115,84 @@ export class GitHubCopilotDeviceFlow { /** * Step 2: Show user code and open browser */ - private async showUserCodeAndOpenBrowser(deviceCodeResponse: DeviceCodeResponse): Promise { - return new Promise((resolve) => { - // Create a window to display user code - const instructionWindow = new BrowserWindow({ - width: 340, - height: 320, - show: false, - webPreferences: { - nodeIntegration: false, - contextIsolation: true - }, - autoHideMenuBar: true, - title: 'GitHub Copilot Device Authentication', - resizable: false, - minimizable: false, - maximizable: false + private async showUserCodeAndOpenBrowser( + deviceCodeResponse: DeviceCodeResponse, + signal: AbortSignal + ): Promise { + signal.throwIfAborted() + const result = Promise.withResolvers() + void result.promise.catch(() => {}) + const closed = Promise.withResolvers() + const effects = new Set>() + let active = true + let opening: Promise | undefined + // Create a window to display user code + const instructionWindow = new BrowserWindow({ + width: 340, + height: 320, + show: false, + webPreferences: { + nodeIntegration: false, + contextIsolation: true, + webSecurity: true + }, + autoHideMenuBar: true, + title: 'GitHub Copilot Device Authentication', + resizable: false, + minimizable: false, + maximizable: false + }) + instructionWindow.once('closed', () => { + closed.resolve() + result.resolve() + }) + const abort = () => result.reject(signal.reason) + signal.addEventListener('abort', abort, { once: true }) + const run = (effect: () => Promise): Promise => { + const operation = Promise.resolve().then(() => { + if (active && !signal.aborted && !instructionWindow.isDestroyed()) return effect() + return undefined + }) + effects.add(operation) + void operation.then( + () => effects.delete(operation), + () => effects.delete(operation) + ) + return operation + } + // Destroyed frames can lose command replies; physical closure retires their local effects. + const runWindow = (effect: () => Promise) => + run(async () => await Promise.race([effect(), closed.promise])) + const openBrowser = () => { + if (!active || signal.aborted || instructionWindow.isDestroyed() || opening) return + opening = run(async () => { + try { + await shell.openExternal(GITHUB_DEVICE_URL) + } catch { + if (active && !signal.aborted && !instructionWindow.isDestroyed()) + await runWindow( + async () => + await instructionWindow.webContents.executeJavaScript( + `alert('Cannot automatically open browser. Please manually visit: ${GITHUB_DEVICE_URL}');` + ) + ).catch(() => {}) + } }) + .then(() => undefined) + .finally(() => { + opening = undefined + }) + void opening.catch(() => {}) + } + const closeWindow = async () => { + try { + if (!instructionWindow.isDestroyed()) instructionWindow.destroy() + } finally { + await closed.promise + } + } - // 创建HTML内容 - const htmlContent = ` + const htmlContent = ` @@ -321,7 +302,7 @@ export class GitHubCopilotDeviceFlow { - - - `) - - // Trigger callback handling - this.handleServerCallback(code, null) - } else if (error) { - // Error page - res.writeHead(400, { 'Content-Type': 'text/html; charset=utf-8' }) - res.end(` - - - - - Authorization Failed - - - -

❌ Authorization Failed

-

An error occurred during authorization: ${error}

-

You can close this window and try again.

- - - `) - - this.handleServerCallback(null, error) - } else { - res.writeHead(400, { 'Content-Type': 'text/plain' }) - res.end('Invalid callback request') - } - } else { - res.writeHead(404, { 'Content-Type': 'text/plain' }) - res.end('Not found') - } - }) - - this.callbackServer.listen(this.callbackPort, 'localhost', () => { - logger.info(`OAuth callback server started on http://localhost:${this.callbackPort}`) - resolve() - }) - - this.callbackServer.on('error', (error) => { - console.error('Callback server error:', error) - reject(error) - }) - }) - } - - /** - * Stop callback server - */ - private stopCallbackServer(): void { - if (this.callbackServer) { - this.callbackServer.close() - this.callbackServer = null - logger.info('OAuth callback server stopped') - } - } - - // Callback handling resolve and reject functions - private callbackResolve: ((value: string) => void) | null = null - private callbackReject: ((reason?: Error) => void) | null = null - - /** - * Handle server callback - */ - private handleServerCallback(code: string | null, error: string | null): void { - if (error) { - console.error('OAuth server callback error:', error) - this.callbackReject?.(new Error(`OAuth authorization failed: ${error}`)) - } else if (code) { - logger.info('OAuth server callback success, received authorization code') - this.callbackResolve?.(code) - } - - // Clean up callback functions - this.callbackResolve = null - this.callbackReject = null - } - - /** - * Start OAuth flow - */ - private async startOAuthFlow(config: OAuthConfig): Promise { - return new Promise((resolve, reject) => { - // Save callback functions - this.callbackResolve = resolve - this.callbackReject = reject - - // Create authorization window - this.authWindow = new BrowserWindow({ - width: 500, - height: 700, - show: false, - webPreferences: { - nodeIntegration: false, - contextIsolation: true - }, - autoHideMenuBar: true, - title: 'GitHub Authorization Login' - }) - - // Build authorization URL - const authUrl = this.buildAuthUrl(config) - logger.info('Opening OAuth URL:', authUrl) - - this.authWindow.webContents.setWindowOpenHandler(() => ({ action: 'deny' })) - - // Load authorization page - this.authWindow.loadURL(authUrl) - this.authWindow.show() - - // Handle window close - this.authWindow.on('closed', () => { - this.authWindow = null - if (this.callbackReject) { - this.callbackReject(new Error('User cancelled login')) - this.callbackReject = null - this.callbackResolve = null - } - }) - - // Handle loading errors - this.authWindow.webContents.on('did-fail-load', (_event, errorCode, errorDescription) => { - console.error('OAuth page load failed:', errorCode, errorDescription) - this.closeAuthWindow() - if (this.callbackReject) { - this.callbackReject(new Error(`Failed to load authorization page: ${errorDescription}`)) - this.callbackReject = null - this.callbackResolve = null - } - }) - - // Monitor page navigation to check if callback page is reached - this.authWindow.webContents.on('did-navigate', (_event, navigationUrl) => { - logger.info('OAuth window navigated to:', navigationUrl) - // If navigated to our callback page, authorization flow is complete - if (navigationUrl.includes('deepchatai.cn/auth/github/callback')) { - // Close authorization window as callback server handles remaining logic - setTimeout(() => { - this.closeAuthWindow() - }, 2000) // Close after 2 seconds to let user see success page - } - }) - }) - } - - /** - * Build authorization URL - */ - private buildAuthUrl(config: OAuthConfig): string { - const params = new URLSearchParams({ - client_id: config.clientId, - redirect_uri: config.redirectUri, - response_type: config.responseType, - scope: config.scope - }) - - return `${config.authUrl}?${params.toString()}` - } - - /** - * Exchange authorization code for access token - */ - private async exchangeCodeForToken(code: string, config: OAuthConfig): Promise { - const response = await fetch('https://github.com/login/oauth/access_token', { - method: 'POST', - headers: { - Accept: 'application/json', - 'Content-Type': 'application/json', - 'User-Agent': 'DeepChat/1.0.0' - }, - body: JSON.stringify({ - client_id: config.clientId, - client_secret: config.clientSecret || process.env.GITHUB_CLIENT_SECRET, - code: code, - redirect_uri: config.redirectUri - }) - }) - - if (!response.ok) { - throw new Error(`Token exchange failed: ${response.status} ${response.statusText}`) - } - - const data = (await response.json()) as { - access_token: string - error?: string - error_description?: string - } - - if (data.error) { - throw new Error(`Token exchange error: ${data.error_description || data.error}`) - } - - return data.access_token - } - - /** - * Close authorization window - */ - private closeAuthWindow(): void { - if (this.authWindow && !this.authWindow.isDestroyed()) { - this.authWindow.close() - this.authWindow = null - } - } -} - -// GitHub Copilot OAuth configuration -export const GITHUB_COPILOT_OAUTH_CONFIG: OAuthConfig = { - authUrl: 'https://github.com/login/oauth/authorize', - redirectUri: - import.meta.env.VITE_GITHUB_REDIRECT_URI || 'https://deepchatai.cn/auth/github/callback', - clientId: import.meta.env.VITE_GITHUB_CLIENT_ID, - clientSecret: import.meta.env.VITE_GITHUB_CLIENT_SECRET, - scope: 'read:user read:org', - responseType: 'code' } diff --git a/src/main/provider/auth/openaiCodex/credentialStore.ts b/src/main/provider/auth/openaiCodex/credentialStore.ts index ded41e6104..36f4216eec 100644 --- a/src/main/provider/auth/openaiCodex/credentialStore.ts +++ b/src/main/provider/auth/openaiCodex/credentialStore.ts @@ -1,7 +1,7 @@ import * as fs from 'fs' import * as path from 'path' import { randomBytes } from 'node:crypto' -import { app, safeStorage } from 'electron' +import type { CredentialCryptoPort } from '@/config/secretStore' export type OpenAICodexCredentialStorage = 'safeStorage' | 'file' | 'none' @@ -41,19 +41,50 @@ function toErrorMessage(error: unknown): string { return error instanceof Error ? error.message : String(error) } +const MAX_CREDENTIAL_FILE_BYTES = 16 * 1024 * 1024 +const MAX_CREDENTIAL_PAYLOAD_BYTES = 8 * 1024 * 1024 + export class OpenAICodexCredentialStore { private readonly filePath: string + private readonly crypto: CredentialCryptoPort private lastLoadError: string | null = null + private stopped = false + private generation = 0 + private revision = 0 + private operations: Promise = Promise.resolve() + private pending = 0 + private pendingNormal = 0 + private loading: { revision: number; promise: Promise } | null = null + + constructor(options: { filePath: string; crypto: CredentialCryptoPort }) { + this.filePath = options.filePath + this.crypto = options.crypto + } + + stop(): void { + this.stopped = true + this.generation++ + this.loading = null + } - constructor(filePath?: string) { - this.filePath = - filePath || path.join(app.getPath('userData'), 'openai-codex-auth', 'credentials.json') + private captureOperation(): () => void { + const generation = this.generation + const check = () => { + if (this.stopped || generation !== this.generation) + throw new Error('OpenAI Codex credential operation interrupted') + } + check() + return check } - getStorageState(): OpenAICodexCredentialStorage { + async getStorageState(): Promise { + const check = this.captureOperation() try { - return safeStorage.isEncryptionAvailable() ? 'safeStorage' : 'file' + const available = await this.crypto.isAvailable() + check() + return available ? 'safeStorage' : 'file' } catch (error) { + check() console.warn( '[OpenAICodexCredentialStore] Encryption availability check failed, using file storage:', error @@ -66,8 +97,28 @@ export class OpenAICodexCredentialStore { return this.lastLoadError } - load(): OpenAICodexTokenSet | null { - const result = this.readEnvelope() + async load(): Promise { + const check = this.captureOperation() + const revision = this.revision + await this.operations + check() + const assertReadCurrent = () => { + check() + if (revision !== this.revision) throw new Error('OpenAI Codex credential read superseded') + } + assertReadCurrent() + let loading = this.loading + if (!loading || loading.revision !== revision) { + loading = { revision, promise: this.readEnvelope(assertReadCurrent) } + this.loading = loading + void loading.promise + .finally(() => { + if (this.loading === loading) this.loading = null + }) + .catch(() => undefined) + } + const result = await loading.promise + assertReadCurrent() if (result.state === 'corrupt' || result.state === 'undecryptable') { this.lastLoadError = result.reason console.warn( @@ -75,98 +126,128 @@ export class OpenAICodexCredentialStore { ) return null } - this.lastLoadError = null return result.state === 'ok' ? result.tokens : null } - save(tokens: OpenAICodexTokenSet): void { - fs.mkdirSync(path.dirname(this.filePath), { recursive: true, mode: 0o700 }) - const normalized = this.normalizeTokens(tokens) - if (!normalized) { - throw new Error('Invalid OpenAI Codex token payload') - } - - const now = Date.now() - const envelope: StoredCredentialEnvelope = - this.getStorageState() === 'safeStorage' - ? { - version: 1, - storage: 'safeStorage', - wrapped: safeStorage.encryptString(JSON.stringify(normalized)).toString('base64'), - updatedAt: now - } - : { - version: 1, - storage: 'file', - tokens: normalized, - updatedAt: now - } - - if (this.readEnvelope().state === 'corrupt') { - try { - fs.copyFileSync(this.filePath, `${this.filePath}.corrupt`) - } catch (error) { - console.warn( - '[OpenAICodexCredentialStore] Failed to back up corrupted credential file:', - error - ) + save(tokens: OpenAICodexTokenSet): Promise { + return this.write(async (check) => { + const normalized = this.normalizeTokens(tokens) + if (!normalized) throw new Error('Invalid OpenAI Codex token payload') + const payload = JSON.stringify(normalized) + if (Buffer.byteLength(payload, 'utf8') > MAX_CREDENTIAL_PAYLOAD_BYTES) + throw new Error('OpenAI Codex token payload is oversized') + const storage = await this.getStorageState() + check() + const now = Date.now() + const envelope: StoredCredentialEnvelope = + storage === 'safeStorage' + ? { version: 1, storage, wrapped: await this.crypto.encrypt(payload), updatedAt: now } + : { version: 1, storage: 'file', tokens: normalized, updatedAt: now } + check() + if ( + envelope.storage === 'safeStorage' && + (typeof envelope.wrapped !== 'string' || !envelope.wrapped) + ) + throw new Error('Credential encryption returned an invalid envelope') + const serialized = JSON.stringify(envelope, null, 2) + if (Buffer.byteLength(serialized, 'utf8') > MAX_CREDENTIAL_FILE_BYTES) + throw new Error('OpenAI Codex credential envelope is oversized') + const previous = await this.readEnvelope(check) + check() + fs.mkdirSync(path.dirname(this.filePath), { recursive: true, mode: 0o700 }) + if (previous.state === 'corrupt') { + try { + fs.copyFileSync(this.filePath, `${this.filePath}.corrupt`) + } catch (error) { + console.warn( + '[OpenAICodexCredentialStore] Failed to back up corrupted credential file:', + error + ) + } } - } - - const temporaryPath = `${this.filePath}.tmp-${process.pid}-${randomBytes(6).toString('hex')}` - try { - const fd = fs.openSync(temporaryPath, 'w', 0o600) + const temporaryPath = `${this.filePath}.tmp-${process.pid}-${randomBytes(6).toString('hex')}` try { - fs.writeFileSync(fd, JSON.stringify(envelope, null, 2), 'utf-8') - fs.fsyncSync(fd) + const fd = fs.openSync(temporaryPath, 'w', 0o600) + try { + fs.writeFileSync(fd, serialized, 'utf-8') + fs.fsyncSync(fd) + } finally { + fs.closeSync(fd) + } + fs.renameSync(temporaryPath, this.filePath) } finally { - fs.closeSync(fd) - } - fs.renameSync(temporaryPath, this.filePath) - } finally { - try { - fs.rmSync(temporaryPath, { force: true }) - } catch (error) { - console.warn( - '[OpenAICodexCredentialStore] Failed to remove temporary credential file:', - error - ) + try { + fs.rmSync(temporaryPath, { force: true }) + } catch (error) { + console.warn( + '[OpenAICodexCredentialStore] Failed to remove temporary credential file:', + error + ) + } } - } - this.lastLoadError = null + this.lastLoadError = null + }) } - clear(): void { - const directory = path.dirname(this.filePath) - const artifacts = [this.filePath, `${this.filePath}.corrupt`, `${this.filePath}.tmp`] - try { - const prefix = `${path.basename(this.filePath)}.tmp-` - for (const entry of fs.readdirSync(directory)) { - if (entry.startsWith(prefix)) { - artifacts.push(path.join(directory, entry)) + clear(): Promise { + return this.write(async (check) => { + check() + const directory = path.dirname(this.filePath) + const artifacts = [this.filePath, `${this.filePath}.corrupt`, `${this.filePath}.tmp`] + const errors: unknown[] = [] + try { + const prefix = `${path.basename(this.filePath)}.tmp-` + for (const entry of fs.readdirSync(directory)) { + if (entry.startsWith(prefix)) artifacts.push(path.join(directory, entry)) } + } catch (error) { + if ((error as NodeJS.ErrnoException).code !== 'ENOENT') errors.push(error) } - } catch (error) { - if ((error as NodeJS.ErrnoException).code !== 'ENOENT') { - console.warn('[OpenAICodexCredentialStore] Failed to list credential directory:', error) + for (const artifact of artifacts) { + try { + fs.rmSync(artifact, { force: true }) + } catch (error) { + errors.push(error) + } } - } + if (errors.length) throw new AggregateError(errors, 'OpenAI Codex credential cleanup failed') + this.lastLoadError = null + }, true) + } - for (const artifact of artifacts) { - try { - fs.rmSync(artifact, { force: true }) - } catch (error) { - console.warn('[OpenAICodexCredentialStore] Failed to remove', artifact, error) - } - } - this.lastLoadError = null + private async write( + operation: (check: () => void) => Promise, + control = false + ): Promise { + const check = this.captureOperation() + if (this.pending >= 32 || (!control && this.pendingNormal >= 24)) + throw new Error('OpenAI Codex credential mutation queue is full') + this.pending++ + if (!control) this.pendingNormal++ + this.revision++ + const result = this.operations.then(async () => { + check() + await operation(check) + check() + }) + this.operations = result.catch(() => undefined) + return result.finally(() => { + this.pending-- + if (!control) this.pendingNormal-- + check() + }) } - private readEnvelope(): EnvelopeReadResult { + private async readEnvelope(check: () => void): Promise { let raw: string try { + check() + if (fs.statSync(this.filePath).size > MAX_CREDENTIAL_FILE_BYTES) + return { state: 'corrupt', reason: 'credential file is oversized' } raw = fs.readFileSync(this.filePath, 'utf-8') + if (Buffer.byteLength(raw, 'utf8') > MAX_CREDENTIAL_FILE_BYTES) + return { state: 'corrupt', reason: 'credential file is oversized' } } catch (error) { if ((error as NodeJS.ErrnoException).code === 'ENOENT') { return { state: 'missing' } @@ -187,6 +268,11 @@ export class OpenAICodexCredentialStore { if (envelope.storage === 'file') { const tokens = this.normalizeTokens(envelope.tokens) + if ( + tokens && + Buffer.byteLength(JSON.stringify(tokens), 'utf8') > MAX_CREDENTIAL_PAYLOAD_BYTES + ) + return { state: 'corrupt', reason: 'credential payload is oversized' } return tokens ? { state: 'ok', tokens } : { state: 'corrupt', reason: 'credential file holds an invalid token payload' } @@ -194,8 +280,14 @@ export class OpenAICodexCredentialStore { let decrypted: string try { - decrypted = safeStorage.decryptString(Buffer.from(envelope.wrapped, 'base64')) + if (typeof envelope.wrapped !== 'string' || !envelope.wrapped) + return { state: 'corrupt', reason: 'credential envelope is invalid' } + decrypted = await this.crypto.decrypt(envelope.wrapped) + check() + if (Buffer.byteLength(decrypted, 'utf8') > MAX_CREDENTIAL_PAYLOAD_BYTES) + return { state: 'corrupt', reason: 'credential payload is oversized' } } catch (error) { + check() return { state: 'undecryptable', reason: `credential decryption failed: ${toErrorMessage(error)}` diff --git a/src/main/provider/auth/openaiCodex/index.ts b/src/main/provider/auth/openaiCodex/index.ts index c14ba4973b..8c328f2715 100644 --- a/src/main/provider/auth/openaiCodex/index.ts +++ b/src/main/provider/auth/openaiCodex/index.ts @@ -1,5 +1,5 @@ import logger from '@shared/logger' -import { shell } from 'electron' +import { readAuthResponse } from '../responseBody' import { URL } from 'url' import type { DeepchatEventPublisher } from '@shared/contracts/events' import { @@ -33,6 +33,7 @@ export type OpenAICodexBackendAuth = { } type PendingBrowserFlow = { + generation: number state: string codeVerifier: string redirectUri: string @@ -148,62 +149,136 @@ export class OpenAICodexAuth { private pendingBrowserFlow: PendingBrowserFlow | null = null private refreshPromise: Promise | null = null private lastError: string | null = null + private generation = 0 + private stopped = false + private loginPending = false + private publication = 0 + private readonly requests = new Set() constructor( - store = new OpenAICodexCredentialStore(), - private readonly publishEvent: DeepchatEventPublisher + store: OpenAICodexCredentialStore, + private readonly publishEvent: DeepchatEventPublisher, + private readonly openExternal: (url: string) => Promise ) { this.store = store } - getStatus(): OpenAICodexAuthStatus { - if (isOpenAICodexDisabled()) { - return this.withStorage({ - state: 'disabled', - authenticated: false - }) - } + private assertGeneration(generation: number): void { + if (this.stopped || generation !== this.generation) + throw new DOMException('OpenAI Codex authentication operation interrupted', 'AbortError') + } - if (this.pendingBrowserFlow && !this.pendingBrowserFlow.cancelled) { - return this.withStorage({ - state: 'pending-browser', - authenticated: false, - ...(this.lastError ? { error: this.lastError } : {}) - }) + captureOperation(): () => void { + const generation = this.generation + this.assertGeneration(generation) + return () => this.assertGeneration(generation) + } + + private invalidate(): void { + this.generation++ + this.refreshPromise = null + for (const request of this.requests) request.abort() + } + + stop(): void { + this.stopped = true + this.invalidate() + this.cancelPendingFlow() + this.store.stop() + } + + private cancelPendingFlow(): void { + this.loginPending = false + if (this.pendingBrowserFlow) { + this.pendingBrowserFlow.cancelled = true + this.pendingBrowserFlow.callbackSession.close() + this.pendingBrowserFlow = null } + } - const tokens = this.store.load() - if (tokens) { - return this.statusFromTokens(tokens) + async getStatus(): Promise { + for (let attempt = 0; attempt < 2; attempt++) { + const generation = this.generation + const check = this.captureOperation() + try { + const storage = await this.store.getStorageState() + check() + let status: Omit + if (isOpenAICodexDisabled()) status = { state: 'disabled', authenticated: false } + else if (this.pendingBrowserFlow && !this.pendingBrowserFlow.cancelled) + status = { + state: 'pending-browser', + authenticated: false, + ...(this.lastError ? { error: this.lastError } : {}) + } + else { + const tokens = await this.store.load() + check() + if (tokens) + status = { + state: 'authenticated', + authenticated: true, + accountId: maskAccountId(tokens.accountId), + accountLabel: tokens.accountLabel, + planType: tokens.planType, + expiresAt: tokens.expiresAt + } + else { + const error = this.store.getLoadError() ?? this.lastError + status = { + state: error ? 'error' : 'signed-out', + authenticated: false, + ...(error ? { error } : {}) + } + } + } + check() + return { ...status, storage } + } catch (error) { + if (attempt === 0 && !this.stopped && generation !== this.generation) continue + throw error + } } + throw new DOMException('Provider auth status changed during read', 'AbortError') + } - const statusError = this.store.getLoadError() ?? this.lastError - return this.withStorage({ - state: statusError ? 'error' : 'signed-out', - authenticated: false, - ...(statusError ? { error: statusError } : {}) - }) + async cancelLogin(): Promise { + if (this.loginPending) { + this.invalidate() + this.cancelPendingFlow() + } + const check = this.captureOperation() + try { + await this.publishStatusChanged(check) + return await this.getStatus() + } finally { + check() + } } async startBrowserLogin(): Promise { this.assertEnabled() - this.cancelLogin() + this.invalidate() + this.cancelPendingFlow() + this.loginPending = true this.lastError = null - - const state = createOpenAICodexState() - const pkce = createOpenAICodexPkcePair() + const initialGeneration = this.generation + let flow: PendingBrowserFlow | null = null + const check = () => this.assertGeneration(flow?.generation ?? initialGeneration) let callbackSession: OAuthLoopbackCallbackSession | null = null - try { - const configuredRedirect = new URL(OPENAI_CODEX_REDIRECT_URI) + const state = createOpenAICodexState() + const pkce = createOpenAICodexPkcePair() + const redirect = new URL(OPENAI_CODEX_REDIRECT_URI) callbackSession = await startOAuthLoopbackCallbackSession({ expectedState: state, - path: configuredRedirect.pathname || OPENAI_CODEX_REDIRECT_PATH, - preferredPort: Number(configuredRedirect.port) || OPENAI_CODEX_REDIRECT_PORT, - redirectHost: configuredRedirect.hostname || 'localhost', + path: redirect.pathname || OPENAI_CODEX_REDIRECT_PATH, + preferredPort: Number(redirect.port) || OPENAI_CODEX_REDIRECT_PORT, + redirectHost: redirect.hostname || 'localhost', timeoutMs: OPENAI_CODEX_BROWSER_TIMEOUT_MS, invalidCallbackMessage: 'Invalid OpenAI Codex OAuth callback' }) + check() const authUrl = new URL(OPENAI_CODEX_AUTHORIZE_URL) authUrl.searchParams.set('client_id', OPENAI_CODEX_CLIENT_ID) authUrl.searchParams.set('response_type', 'code') @@ -214,8 +289,8 @@ export class OpenAICodexAuth { authUrl.searchParams.set('code_challenge_method', 'S256') authUrl.searchParams.set('codex_cli_simplified_flow', 'true') authUrl.searchParams.set('id_token_add_organizations', 'true') - - const flow: PendingBrowserFlow = { + flow = { + generation: this.generation, state, codeVerifier: pkce.codeVerifier, redirectUri: callbackSession.redirectUri, @@ -224,238 +299,263 @@ export class OpenAICodexAuth { } this.pendingBrowserFlow = flow flow.flowPromise = this.completeBrowserLogin(flow, callbackSession.waitForCallback()) - await shell.openExternal(authUrl.toString()) - this.publishStatusChanged() - return this.getStatus() + await this.openExternal(authUrl.toString()) + check() + await this.publishStatusChanged(check) + return await this.getStatus() } catch (error) { - this.lastError = sanitizeError(error) - this.pendingBrowserFlow = null callbackSession?.close() - this.publishStatusChanged() - return this.getStatus() + check() + if (this.pendingBrowserFlow === flow) this.cancelPendingFlow() + this.loginPending = false + this.lastError = sanitizeError(error) + await this.publishStatusChanged(check) + return await this.getStatus() + } finally { + check() } } async completeBrowserLoginFromCallbackUrl(callbackUrl: string): Promise { this.assertEnabled() const flow = this.pendingBrowserFlow - if (!flow || flow.cancelled) { - this.lastError = 'OpenAI Codex browser login is not pending' - this.publishStatusChanged() - return this.getStatus() - } - - const resolution = flow.callbackSession.resolveCallbackUrl(callbackUrl) - if (resolution.kind === 'not-found') { - this.lastError = 'OpenAI Codex callback URL is invalid' - this.publishStatusChanged() - return this.getStatus() - } - - await flow.flowPromise - return this.getStatus() - } - - cancelLogin(): OpenAICodexAuthStatus { - if (this.pendingBrowserFlow) { - this.pendingBrowserFlow.cancelled = true - this.pendingBrowserFlow.callbackSession.close() - this.pendingBrowserFlow = null + const initialGeneration = this.generation + const check = () => this.assertGeneration(flow?.generation ?? initialGeneration) + try { + check() + if (!flow || flow.cancelled) { + this.lastError = 'OpenAI Codex browser login is not pending' + await this.publishStatusChanged(check) + return await this.getStatus() + } + const resolution = flow.callbackSession.resolveCallbackUrl(callbackUrl) + if (resolution.kind === 'not-found') { + this.lastError = 'OpenAI Codex callback URL is invalid' + await this.publishStatusChanged(check) + return await this.getStatus() + } + await flow.flowPromise + check() + return await this.getStatus() + } finally { + check() } - this.publishStatusChanged() - return this.getStatus() } async logout(): Promise { - const tokens = this.store.load() - this.cancelLogin() - this.store.clear() - this.lastError = null - - const tokenToRevoke = tokens?.refreshToken || tokens?.accessToken - if (tokenToRevoke) { - try { - await this.postForm(OPENAI_CODEX_REVOKE_URL, { - token: tokenToRevoke, - client_id: OPENAI_CODEX_CLIENT_ID - }) - } catch (error) { - logger.warn('[OpenAI Codex] Token revoke failed:', sanitizeError(error)) + this.invalidate() + this.cancelPendingFlow() + const check = this.captureOperation() + try { + const tokens = await this.store.load() + check() + await this.store.clear() + check() + this.lastError = null + const token = tokens?.refreshToken || tokens?.accessToken + if (token) { + try { + await this.postForm( + OPENAI_CODEX_REVOKE_URL, + { token, client_id: OPENAI_CODEX_CLIENT_ID }, + check + ) + } catch (error) { + check() + logger.warn('[OpenAI Codex] Token revoke failed:', sanitizeError(error)) + } } + check() + await this.publishStatusChanged(check) + return await this.getStatus() + } finally { + check() } - - this.publishStatusChanged() - return this.getStatus() } async getAccessToken(): Promise { - const auth = await this.getBackendAuth() - return auth.accessToken + const check = this.captureOperation() + try { + return (await this.getBackendAuth()).accessToken + } finally { + check() + } } async forceRefreshAccessToken(): Promise { - const auth = await this.forceRefreshBackendAuth() - return auth.accessToken + const check = this.captureOperation() + try { + return (await this.forceRefreshBackendAuth()).accessToken + } finally { + check() + } } async getBackendAuth(): Promise { - this.assertEnabled() - - if (OPENAI_CODEX_ACCESS_TOKEN_ENV) { - return { accessToken: OPENAI_CODEX_ACCESS_TOKEN_ENV } - } - - const tokens = this.store.load() - if (!tokens) { - const loadError = this.store.getLoadError() - if (loadError) throw new Error(loadError) - throw new ProviderHttpError('OpenAI Codex sign-in is required', { - statusCode: 401, - code: 'openai_codex_sign_in_required', - retryable: false - }) - } - - const current = - tokens.expiresAt > Date.now() + OPENAI_CODEX_TOKEN_REFRESH_SKEW_MS - ? tokens - : await this.refreshAccessToken(tokens) - return { - accessToken: current.accessToken, - accountId: current.accountId + const check = this.captureOperation() + try { + this.assertEnabled() + if (OPENAI_CODEX_ACCESS_TOKEN_ENV) return { accessToken: OPENAI_CODEX_ACCESS_TOKEN_ENV } + const tokens = await this.store.load() + check() + if (!tokens) { + const error = this.store.getLoadError() + if (error) throw new Error(error) + throw new ProviderHttpError('OpenAI Codex sign-in is required', { + statusCode: 401, + code: 'openai_codex_sign_in_required', + retryable: false + }) + } + const current = + tokens.expiresAt > Date.now() + OPENAI_CODEX_TOKEN_REFRESH_SKEW_MS + ? tokens + : await this.refreshAccessToken(tokens, check) + check() + return { accessToken: current.accessToken, accountId: current.accountId } + } finally { + check() } } async forceRefreshBackendAuth(): Promise { - this.assertEnabled() - const tokens = this.store.load() - if (!tokens?.refreshToken) { - const loadError = this.store.getLoadError() - if (loadError) throw new Error(loadError) - throw new ProviderHttpError('OpenAI Codex refresh token is unavailable', { - statusCode: 401, - code: 'openai_codex_sign_in_required', - retryable: false - }) - } - - const refreshed = await this.refreshAccessToken(tokens, true) - return { - accessToken: refreshed.accessToken, - accountId: refreshed.accountId - } - } - - private withStorage(status: Omit): OpenAICodexAuthStatus { - return { - ...status, - storage: this.store.getStorageState() + const check = this.captureOperation() + try { + this.assertEnabled() + const tokens = await this.store.load() + check() + if (!tokens?.refreshToken) { + const error = this.store.getLoadError() + if (error) throw new Error(error) + throw new ProviderHttpError('OpenAI Codex refresh token is unavailable', { + statusCode: 401, + code: 'openai_codex_sign_in_required', + retryable: false + }) + } + const refreshed = await this.refreshAccessToken(tokens, check, true) + check() + return { accessToken: refreshed.accessToken, accountId: refreshed.accountId } + } finally { + check() } } - private statusFromTokens(tokens: OpenAICodexTokenSet): OpenAICodexAuthStatus { - return this.withStorage({ - state: 'authenticated', - authenticated: true, - accountId: maskAccountId(tokens.accountId), - accountLabel: tokens.accountLabel, - planType: tokens.planType, - expiresAt: tokens.expiresAt - }) - } - private assertEnabled(): void { - if (isOpenAICodexDisabled()) { - throw new Error('OpenAI Codex provider is disabled by environment') - } + if (isOpenAICodexDisabled()) throw new Error('OpenAI Codex provider is disabled by environment') } private async completeBrowserLogin( flow: PendingBrowserFlow, - callbackPromise: Promise<{ code: string }> + callback: Promise<{ code: string }> ): Promise { + const check = () => this.assertGeneration(flow.generation) try { - const { code } = await callbackPromise - if (flow.cancelled || this.pendingBrowserFlow !== flow) { - return - } - - const tokens = await this.exchangeAuthorizationCode(code, flow.codeVerifier, flow.redirectUri) - if (flow.cancelled || this.pendingBrowserFlow !== flow) { - return - } - - this.store.save(tokens) + const { code } = await callback + if (flow.cancelled || this.pendingBrowserFlow !== flow) return + check() + const tokens = await this.exchangeAuthorizationCode( + code, + flow.codeVerifier, + flow.redirectUri, + check + ) + check() + if (this.pendingBrowserFlow !== flow) return + this.invalidate() + flow.generation = this.generation + await this.store.save(tokens) + check() this.lastError = null this.pendingBrowserFlow = null + this.loginPending = false flow.callbackSession.close() - this.publishStatusChanged() + await this.publishStatusChanged(check) } catch (error) { - if (flow.cancelled || this.pendingBrowserFlow !== flow) { + if ( + this.stopped || + flow.cancelled || + this.pendingBrowserFlow !== flow || + flow.generation !== this.generation + ) return - } - this.lastError = sanitizeError(error) this.pendingBrowserFlow = null + this.loginPending = false flow.callbackSession.close() - this.publishStatusChanged() + try { + await this.publishStatusChanged(check) + } catch (publishError) { + if (!this.stopped && flow.generation === this.generation) + logger.warn('[OpenAI Codex] Failed to publish auth status:', sanitizeError(publishError)) + } } } private async exchangeAuthorizationCode( code: string, - codeVerifier: string, - redirectUri: string + verifier: string, + redirectUri: string, + check: () => void ): Promise { - const payload = await this.postForm(OPENAI_CODEX_TOKEN_URL, { - grant_type: 'authorization_code', - client_id: OPENAI_CODEX_CLIENT_ID, - code, - redirect_uri: redirectUri, - code_verifier: codeVerifier - }) - + check() + const payload = await this.postForm( + OPENAI_CODEX_TOKEN_URL, + { + grant_type: 'authorization_code', + client_id: OPENAI_CODEX_CLIENT_ID, + code, + redirect_uri: redirectUri, + code_verifier: verifier + }, + check + ) + check() return this.parseTokenResponse(payload) } private async refreshAccessToken( - currentTokens: OpenAICodexTokenSet, + tokens: OpenAICodexTokenSet, + check: () => void, force = false ): Promise { - if (!force && currentTokens.expiresAt > Date.now() + OPENAI_CODEX_TOKEN_REFRESH_SKEW_MS) { - return currentTokens - } - - if (this.refreshPromise) { - return this.refreshPromise - } - - this.refreshPromise = this.refreshAccessTokenOnce(currentTokens).finally(() => { - this.refreshPromise = null + check() + if (!force && tokens.expiresAt > Date.now() + OPENAI_CODEX_TOKEN_REFRESH_SKEW_MS) return tokens + if (this.refreshPromise) return this.refreshPromise + const pending = this.refreshAccessTokenOnce(tokens, check).finally(() => { + if (this.refreshPromise === pending) this.refreshPromise = null }) - return this.refreshPromise + this.refreshPromise = pending + return pending } private async refreshAccessTokenOnce( - currentTokens: OpenAICodexTokenSet + tokens: OpenAICodexTokenSet, + check: () => void ): Promise { - if (!currentTokens.refreshToken) { + check() + if (!tokens.refreshToken) throw new ProviderHttpError('OpenAI Codex refresh token is unavailable', { statusCode: 401, code: 'openai_codex_sign_in_required', retryable: false }) - } - - const payload = await this.postForm(OPENAI_CODEX_TOKEN_URL, { - grant_type: 'refresh_token', - client_id: OPENAI_CODEX_CLIENT_ID, - refresh_token: currentTokens.refreshToken - }) - const refreshed = this.parseTokenResponse(payload, currentTokens) - this.store.save(refreshed) + const payload = await this.postForm( + OPENAI_CODEX_TOKEN_URL, + { + grant_type: 'refresh_token', + client_id: OPENAI_CODEX_CLIENT_ID, + refresh_token: tokens.refreshToken + }, + check + ) + check() + const refreshed = this.parseTokenResponse(payload, tokens) + await this.store.save(refreshed) + check() this.lastError = null - this.publishStatusChanged() + await this.publishStatusChanged(check) + check() return refreshed } @@ -496,26 +596,21 @@ export class OpenAICodexAuth { private async postForm( url: string, - params: Record + params: Record, + check: () => void ): Promise { + check() const body = new URLSearchParams() - Object.entries(params).forEach(([key, value]) => { - if (value) { - body.set(key, value) - } - }) - + for (const [key, value] of Object.entries(params)) if (value) body.set(key, value) const response = await this.fetchWithTimeout(url, { method: 'POST', - headers: { - Accept: 'application/json', - 'Content-Type': 'application/x-www-form-urlencoded' - }, + headers: { Accept: 'application/json', 'Content-Type': 'application/x-www-form-urlencoded' }, body }) - + check() if (!response.ok) { const errorBody = await readErrorBody(response) + check() let requiresSignIn = false if (params.grant_type === 'refresh_token') { try { @@ -532,43 +627,64 @@ export class OpenAICodexAuth { } ) } - - return (await response.json()) as TokenResponse + const payload = (await response.json()) as TokenResponse + check() + return payload } - private async fetchWithTimeout(url: string, init: RequestInit): Promise { + private async fetchWithTimeout( + url: string, + init: RequestInit, + externalSignal?: AbortSignal + ): Promise { + const check = this.captureOperation() + if (this.requests.size >= 32) throw new Error('OpenAI Codex auth request limit exceeded') const controller = new AbortController() - const timeout = setTimeout(() => controller.abort(), OPENAI_CODEX_AUTH_REQUEST_TIMEOUT_MS) + this.requests.add(controller) + const timer = setTimeout(() => controller.abort(), OPENAI_CODEX_AUTH_REQUEST_TIMEOUT_MS) + const abort = () => controller.abort(externalSignal?.reason) + if (externalSignal?.aborted) abort() + else externalSignal?.addEventListener('abort', abort, { once: true }) try { - return await fetch(url, { - ...init, - signal: controller.signal - }) + controller.signal.throwIfAborted() + const response = await fetch(url, { ...init, signal: controller.signal }) + check() + const result = await readAuthResponse(response, controller.signal) + check() + return result } catch (error) { + check() if (controller.signal.aborted) { + if (externalSignal?.aborted) + throw new DOMException('OpenAI Codex OAuth login cancelled', 'AbortError') throw new Error( `OpenAI Codex auth request timed out after ${OPENAI_CODEX_AUTH_REQUEST_TIMEOUT_MS}ms` ) } throw error } finally { - clearTimeout(timeout) + clearTimeout(timer) + externalSignal?.removeEventListener('abort', abort) + this.requests.delete(controller) } } - private publishStatusChanged(): void { - this.publishEvent('oauth.openaiCodex.statusChanged', { - status: this.getStatus(), - version: Date.now() - }) + private async publishStatusChanged(check: () => void): Promise { + check() + const publication = ++this.publication + const status = await this.getStatus() + check() + if (publication !== this.publication) return + this.publishEvent('oauth.openaiCodex.statusChanged', { status, version: Date.now() }) } } - export function initializeGlobalOpenAICodexAuth( - publishEvent: DeepchatEventPublisher + publishEvent: DeepchatEventPublisher, + store: OpenAICodexCredentialStore, + openExternal: (url: string) => Promise ): OpenAICodexAuth { if (!globalOpenAICodexAuth) { - globalOpenAICodexAuth = new OpenAICodexAuth(undefined, publishEvent) + globalOpenAICodexAuth = new OpenAICodexAuth(store, publishEvent, openExternal) } return globalOpenAICodexAuth } diff --git a/src/main/provider/auth/responseBody.ts b/src/main/provider/auth/responseBody.ts new file mode 100644 index 0000000000..abcb1bf9bc --- /dev/null +++ b/src/main/provider/auth/responseBody.ts @@ -0,0 +1,53 @@ +const MAX_AUTH_RESPONSE_BYTES = 8 * 1024 * 1024 + +export async function readAuthResponse(response: Response, signal: AbortSignal): Promise { + if (signal.aborted) { + await response.body?.cancel(signal.reason) + signal.throwIfAborted() + } + if (Number(response.headers.get('content-length')) > MAX_AUTH_RESPONSE_BYTES) { + await response.body?.cancel() + throw new Error('Provider auth response exceeds the size limit') + } + if (!response.body) return response + const reader = response.body.getReader() + let cancellation: Promise | undefined + let complete = false + const cancel = (reason?: unknown) => (cancellation ??= reader.cancel(reason)) + const abort = () => { + void cancel(signal.reason).catch(() => undefined) + } + signal.addEventListener('abort', abort, { once: true }) + const chunks: Uint8Array[] = [] + let size = 0 + try { + while (true) { + signal.throwIfAborted() + const { done, value } = await reader.read() + signal.throwIfAborted() + if (done) { + complete = true + break + } + size += value.byteLength + if (size > MAX_AUTH_RESPONSE_BYTES) { + await cancel() + throw new Error('Provider auth response exceeds the size limit') + } + chunks.push(value) + } + return new Response(Buffer.concat(chunks), { + status: response.status, + statusText: response.statusText, + headers: response.headers + }) + } finally { + signal.removeEventListener('abort', abort) + try { + if (!complete) await cancel(signal.aborted ? signal.reason : undefined) + else await cancellation + } finally { + reader.releaseLock() + } + } +} diff --git a/src/main/provider/auth/xaiGrok/credentialStore.ts b/src/main/provider/auth/xaiGrok/credentialStore.ts index f7676e2ecd..70226b1f77 100644 --- a/src/main/provider/auth/xaiGrok/credentialStore.ts +++ b/src/main/provider/auth/xaiGrok/credentialStore.ts @@ -1,7 +1,7 @@ import * as fs from 'fs' import * as path from 'path' import { randomBytes } from 'node:crypto' -import { app, safeStorage } from 'electron' +import type { CredentialCryptoPort } from '@/config/secretStore' export type XaiGrokCredentialStorage = 'safeStorage' | 'file' | 'none' @@ -42,19 +42,50 @@ function toErrorMessage(error: unknown): string { return error instanceof Error ? error.message : String(error) } +const MAX_CREDENTIAL_FILE_BYTES = 16 * 1024 * 1024 +const MAX_CREDENTIAL_PAYLOAD_BYTES = 8 * 1024 * 1024 + export class XaiGrokCredentialStore { private readonly filePath: string + private readonly crypto: CredentialCryptoPort private lastLoadError: string | null = null + private stopped = false + private generation = 0 + private revision = 0 + private operations: Promise = Promise.resolve() + private pending = 0 + private pendingNormal = 0 + private loading: { revision: number; promise: Promise } | null = null + + constructor(options: { filePath: string; crypto: CredentialCryptoPort }) { + this.filePath = options.filePath + this.crypto = options.crypto + } + + stop(): void { + this.stopped = true + this.generation++ + this.loading = null + } - constructor(filePath?: string) { - this.filePath = - filePath || path.join(app.getPath('userData'), 'xai-grok-auth', 'credentials.json') + private captureOperation(): () => void { + const generation = this.generation + const check = () => { + if (this.stopped || generation !== this.generation) + throw new Error('xAI Grok credential operation interrupted') + } + check() + return check } - getStorageState(): XaiGrokCredentialStorage { + async getStorageState(): Promise { + const check = this.captureOperation() try { - return safeStorage.isEncryptionAvailable() ? 'safeStorage' : 'file' + const available = await this.crypto.isAvailable() + check() + return available ? 'safeStorage' : 'file' } catch (error) { + check() console.warn( '[XaiGrokCredentialStore] Encryption availability check failed, using file storage:', error @@ -67,99 +98,155 @@ export class XaiGrokCredentialStore { return this.lastLoadError } - load(): XaiGrokTokenSet | null { - const result = this.readEnvelope() + async load(): Promise { + const check = this.captureOperation() + const revision = this.revision + await this.operations + check() + const assertReadCurrent = () => { + check() + if (revision !== this.revision) throw new Error('xAI Grok credential read superseded') + } + assertReadCurrent() + let loading = this.loading + if (!loading || loading.revision !== revision) { + loading = { revision, promise: this.readEnvelope(assertReadCurrent) } + this.loading = loading + void loading.promise + .finally(() => { + if (this.loading === loading) this.loading = null + }) + .catch(() => undefined) + } + const result = await loading.promise + assertReadCurrent() if (result.state === 'corrupt' || result.state === 'undecryptable') { this.lastLoadError = result.reason console.warn(`[XaiGrokCredentialStore] Ignoring unreadable credential file: ${result.reason}`) return null } - this.lastLoadError = null return result.state === 'ok' ? result.tokens : null } - save(tokens: XaiGrokTokenSet): void { - fs.mkdirSync(path.dirname(this.filePath), { recursive: true, mode: 0o700 }) - const normalized = this.normalizeTokens(tokens) - if (!normalized) { - throw new Error('Invalid xAI Grok token payload') - } - - const now = Date.now() - const envelope: StoredCredentialEnvelope = - this.getStorageState() === 'safeStorage' - ? { - version: 1, - storage: 'safeStorage', - wrapped: safeStorage.encryptString(JSON.stringify(normalized)).toString('base64'), - updatedAt: now - } - : { - version: 1, - storage: 'file', - tokens: normalized, - updatedAt: now - } - - if (this.readEnvelope().state === 'corrupt') { - try { - fs.copyFileSync(this.filePath, `${this.filePath}.corrupt`) - } catch (error) { - console.warn('[XaiGrokCredentialStore] Failed to back up corrupted credential file:', error) + save(tokens: XaiGrokTokenSet): Promise { + return this.write(async (check) => { + const normalized = this.normalizeTokens(tokens) + if (!normalized) throw new Error('Invalid xAI Grok token payload') + const payload = JSON.stringify(normalized) + if (Buffer.byteLength(payload, 'utf8') > MAX_CREDENTIAL_PAYLOAD_BYTES) + throw new Error('xAI Grok token payload is oversized') + const storage = await this.getStorageState() + check() + const now = Date.now() + const envelope: StoredCredentialEnvelope = + storage === 'safeStorage' + ? { version: 1, storage, wrapped: await this.crypto.encrypt(payload), updatedAt: now } + : { version: 1, storage: 'file', tokens: normalized, updatedAt: now } + check() + if ( + envelope.storage === 'safeStorage' && + (typeof envelope.wrapped !== 'string' || !envelope.wrapped) + ) + throw new Error('Credential encryption returned an invalid envelope') + const serialized = JSON.stringify(envelope, null, 2) + if (Buffer.byteLength(serialized, 'utf8') > MAX_CREDENTIAL_FILE_BYTES) + throw new Error('xAI Grok credential envelope is oversized') + const previous = await this.readEnvelope(check) + check() + fs.mkdirSync(path.dirname(this.filePath), { recursive: true, mode: 0o700 }) + if (previous.state === 'corrupt') { + try { + fs.copyFileSync(this.filePath, `${this.filePath}.corrupt`) + } catch (error) { + console.warn( + '[XaiGrokCredentialStore] Failed to back up corrupted credential file:', + error + ) + } } - } - - const temporaryPath = `${this.filePath}.tmp-${process.pid}-${randomBytes(6).toString('hex')}` - try { - const fd = fs.openSync(temporaryPath, 'w', 0o600) + const temporaryPath = `${this.filePath}.tmp-${process.pid}-${randomBytes(6).toString('hex')}` try { - fs.writeFileSync(fd, JSON.stringify(envelope, null, 2), 'utf-8') - fs.fsyncSync(fd) + const fd = fs.openSync(temporaryPath, 'w', 0o600) + try { + fs.writeFileSync(fd, serialized, 'utf-8') + fs.fsyncSync(fd) + } finally { + fs.closeSync(fd) + } + fs.renameSync(temporaryPath, this.filePath) } finally { - fs.closeSync(fd) - } - fs.renameSync(temporaryPath, this.filePath) - } finally { - try { - fs.rmSync(temporaryPath, { force: true }) - } catch (error) { - console.warn('[XaiGrokCredentialStore] Failed to remove temporary credential file:', error) + try { + fs.rmSync(temporaryPath, { force: true }) + } catch (error) { + console.warn( + '[XaiGrokCredentialStore] Failed to remove temporary credential file:', + error + ) + } } - } - this.lastLoadError = null + this.lastLoadError = null + }) } - clear(): void { - const directory = path.dirname(this.filePath) - const artifacts = [this.filePath, `${this.filePath}.corrupt`, `${this.filePath}.tmp`] - try { - const prefix = `${path.basename(this.filePath)}.tmp-` - for (const entry of fs.readdirSync(directory)) { - if (entry.startsWith(prefix)) { - artifacts.push(path.join(directory, entry)) + clear(): Promise { + return this.write(async (check) => { + check() + const directory = path.dirname(this.filePath) + const artifacts = [this.filePath, `${this.filePath}.corrupt`, `${this.filePath}.tmp`] + const errors: unknown[] = [] + try { + const prefix = `${path.basename(this.filePath)}.tmp-` + for (const entry of fs.readdirSync(directory)) { + if (entry.startsWith(prefix)) artifacts.push(path.join(directory, entry)) } + } catch (error) { + if ((error as NodeJS.ErrnoException).code !== 'ENOENT') errors.push(error) } - } catch (error) { - if ((error as NodeJS.ErrnoException).code !== 'ENOENT') { - console.warn('[XaiGrokCredentialStore] Failed to list credential directory:', error) + for (const artifact of artifacts) { + try { + fs.rmSync(artifact, { force: true }) + } catch (error) { + errors.push(error) + } } - } + if (errors.length) throw new AggregateError(errors, 'xAI Grok credential cleanup failed') + this.lastLoadError = null + }, true) + } - for (const artifact of artifacts) { - try { - fs.rmSync(artifact, { force: true }) - } catch (error) { - console.warn('[XaiGrokCredentialStore] Failed to remove', artifact, error) - } - } - this.lastLoadError = null + private async write( + operation: (check: () => void) => Promise, + control = false + ): Promise { + const check = this.captureOperation() + if (this.pending >= 32 || (!control && this.pendingNormal >= 24)) + throw new Error('xAI Grok credential mutation queue is full') + this.pending++ + if (!control) this.pendingNormal++ + this.revision++ + const result = this.operations.then(async () => { + check() + await operation(check) + check() + }) + this.operations = result.catch(() => undefined) + return result.finally(() => { + this.pending-- + if (!control) this.pendingNormal-- + check() + }) } - private readEnvelope(): EnvelopeReadResult { + private async readEnvelope(check: () => void): Promise { let raw: string try { + check() + if (fs.statSync(this.filePath).size > MAX_CREDENTIAL_FILE_BYTES) + return { state: 'corrupt', reason: 'credential file is oversized' } raw = fs.readFileSync(this.filePath, 'utf-8') + if (Buffer.byteLength(raw, 'utf8') > MAX_CREDENTIAL_FILE_BYTES) + return { state: 'corrupt', reason: 'credential file is oversized' } } catch (error) { if ((error as NodeJS.ErrnoException).code === 'ENOENT') { return { state: 'missing' } @@ -180,6 +267,11 @@ export class XaiGrokCredentialStore { if (envelope.storage === 'file') { const tokens = this.normalizeTokens(envelope.tokens) + if ( + tokens && + Buffer.byteLength(JSON.stringify(tokens), 'utf8') > MAX_CREDENTIAL_PAYLOAD_BYTES + ) + return { state: 'corrupt', reason: 'credential payload is oversized' } return tokens ? { state: 'ok', tokens } : { state: 'corrupt', reason: 'credential file holds an invalid token payload' } @@ -187,8 +279,14 @@ export class XaiGrokCredentialStore { let decrypted: string try { - decrypted = safeStorage.decryptString(Buffer.from(envelope.wrapped, 'base64')) + if (typeof envelope.wrapped !== 'string' || !envelope.wrapped) + return { state: 'corrupt', reason: 'credential envelope is invalid' } + decrypted = await this.crypto.decrypt(envelope.wrapped) + check() + if (Buffer.byteLength(decrypted, 'utf8') > MAX_CREDENTIAL_PAYLOAD_BYTES) + return { state: 'corrupt', reason: 'credential payload is oversized' } } catch (error) { + check() return { state: 'undecryptable', reason: `credential decryption failed: ${toErrorMessage(error)}` diff --git a/src/main/provider/auth/xaiGrok/index.ts b/src/main/provider/auth/xaiGrok/index.ts index 6e1dda4fe9..e5e602f7ad 100644 --- a/src/main/provider/auth/xaiGrok/index.ts +++ b/src/main/provider/auth/xaiGrok/index.ts @@ -1,5 +1,5 @@ import logger from '@shared/logger' -import { shell } from 'electron' +import { readAuthResponse } from '../responseBody' import type { DeepchatEventPublisher } from '@shared/contracts/events' import type { XaiGrokAuthStatus } from '@shared/types/xai-grok' import { @@ -37,6 +37,7 @@ type DeviceCodeResponse = { } type PendingDeviceFlow = { + generation: number cancelled: boolean abortController: AbortController userCode: string @@ -136,220 +137,274 @@ export class XaiGrokAuth { private pendingDeviceFlow: PendingDeviceFlow | null = null private refreshPromise: Promise | null = null private lastError: string | null = null + private generation = 0 + private stopped = false + private loginPending = false + private publication = 0 + private readonly requests = new Set() private cachedDiscovery: DiscoveryEndpoints | null = null + private pendingLoginController: AbortController | null = null constructor( - store = new XaiGrokCredentialStore(), - private readonly publishEvent: DeepchatEventPublisher + store: XaiGrokCredentialStore, + private readonly publishEvent: DeepchatEventPublisher, + private readonly openExternal: (url: string) => Promise ) { this.store = store } - getStatus(): XaiGrokAuthStatus { - if (isXaiGrokOAuthDisabled()) { - return this.withStorage({ - state: 'disabled', - authenticated: false - }) - } - - if (this.pendingDeviceFlow && !this.pendingDeviceFlow.cancelled) { - return this.withStorage({ - state: 'pending-device', - authenticated: false, - userCode: this.pendingDeviceFlow.userCode, - verificationUri: this.pendingDeviceFlow.verificationUri, - verificationUriComplete: this.pendingDeviceFlow.verificationUriComplete, - ...(this.lastError ? { error: this.lastError } : {}) - }) - } + private assertGeneration(generation: number): void { + if (this.stopped || generation !== this.generation) + throw new DOMException('xAI Grok authentication operation interrupted', 'AbortError') + } - const tokens = this.store.load() - if (tokens) { - return this.statusFromTokens(tokens) - } + captureOperation(): () => void { + const generation = this.generation + this.assertGeneration(generation) + return () => this.assertGeneration(generation) + } - const statusError = this.store.getLoadError() ?? this.lastError - return this.withStorage({ - state: statusError ? 'error' : 'signed-out', - authenticated: false, - ...(statusError ? { error: statusError } : {}) - }) + private invalidate(): void { + this.generation++ + this.refreshPromise = null + for (const request of this.requests) request.abort() } - isAuthenticated(): boolean { - return this.getStatus().authenticated + stop(): void { + this.stopped = true + this.invalidate() + this.cancelPendingFlow() + this.store.stop() } - /** - * Returns a non-expired cached access token without network I/O. - * Prefer ensureAccessToken() when a network refresh is acceptable. - */ - peekAccessToken(): string | null { - if (XAI_GROK_ACCESS_TOKEN_ENV) { - return XAI_GROK_ACCESS_TOKEN_ENV + private cancelPendingFlow(): void { + this.loginPending = false + this.pendingLoginController?.abort() + this.pendingLoginController = null + if (this.pendingDeviceFlow) { + this.pendingDeviceFlow.cancelled = true + this.pendingDeviceFlow.abortController.abort() + this.pendingDeviceFlow = null } + } - const tokens = this.store.load() - if (!tokens) { - return null + async getStatus(): Promise { + for (let attempt = 0; attempt < 2; attempt++) { + const generation = this.generation + const check = this.captureOperation() + try { + const storage = await this.store.getStorageState() + check() + let status: Omit + if (isXaiGrokOAuthDisabled()) status = { state: 'disabled', authenticated: false } + else if (this.pendingDeviceFlow && !this.pendingDeviceFlow.cancelled) + status = { + state: 'pending-device', + authenticated: false, + userCode: this.pendingDeviceFlow.userCode, + verificationUri: this.pendingDeviceFlow.verificationUri, + verificationUriComplete: this.pendingDeviceFlow.verificationUriComplete, + ...(this.lastError ? { error: this.lastError } : {}) + } + else { + const tokens = await this.store.load() + check() + if (tokens) + status = { + state: 'authenticated', + authenticated: true, + accountId: maskAccountId(tokens.accountId), + accountLabel: tokens.accountLabel, + + expiresAt: tokens.expiresAt + } + else { + const error = this.store.getLoadError() ?? this.lastError + status = { + state: error ? 'error' : 'signed-out', + authenticated: false, + ...(error ? { error } : {}) + } + } + } + check() + return { ...status, storage } + } catch (error) { + if (attempt === 0 && !this.stopped && generation !== this.generation) continue + throw error + } } + throw new DOMException('Provider auth status changed during read', 'AbortError') + } - if (tokens.expiresAt > Date.now() + XAI_GROK_TOKEN_REFRESH_SKEW_MS) { - return tokens.accessToken + async cancelLogin(): Promise { + if (this.loginPending) { + this.invalidate() + this.cancelPendingFlow() + } + const check = this.captureOperation() + try { + await this.publishStatusChanged(check) + return await this.getStatus() + } finally { + check() } + } - // Still return near-expiry tokens for sync header paths; ensureAccessToken - // will refresh on the next async entry point. Never return an expired token, - // so API-key fallback remains available if refresh fails. - return tokens.expiresAt > Date.now() ? tokens.accessToken : null + async isAuthenticated(): Promise { + const check = this.captureOperation() + try { + return (await this.getStatus()).authenticated + } finally { + check() + } } async startDeviceLogin(): Promise { this.assertEnabled() - this.cancelLogin() + this.invalidate() + this.cancelPendingFlow() + this.loginPending = true this.lastError = null - - const abortController = new AbortController() - + const initialGeneration = this.generation + const controller = new AbortController() + this.pendingLoginController = controller + let flow: PendingDeviceFlow | null = null + const check = () => this.assertGeneration(flow?.generation ?? initialGeneration) try { - const discovery = await this.fetchDiscovery(abortController.signal) + const discovery = await this.fetchDiscovery(check, controller.signal) + check() const deviceCode = await this.requestDeviceCode( discovery.deviceAuthorizationEndpoint, - abortController.signal + check, + controller.signal ) - - const flow: PendingDeviceFlow = { + check() + flow = { + generation: this.generation, cancelled: false, - abortController, + abortController: controller, userCode: deviceCode.userCode, verificationUri: deviceCode.verificationUri, verificationUriComplete: deviceCode.verificationUriComplete } this.pendingDeviceFlow = flow - - const browserUrl = deviceCode.verificationUriComplete || deviceCode.verificationUri try { - await shell.openExternal(browserUrl) + await this.openExternal(deviceCode.verificationUriComplete || deviceCode.verificationUri) } catch (error) { + check() logger.warn('[xAI Grok OAuth] Failed to open browser:', sanitizeError(error)) } - + check() flow.flowPromise = this.completeDeviceLogin(flow, discovery, deviceCode) - this.publishStatusChanged() - return this.getStatus() + await this.publishStatusChanged(check) + return await this.getStatus() } catch (error) { + controller.abort() + check() + if (this.pendingLoginController === controller) this.cancelPendingFlow() this.lastError = sanitizeError(error) - this.pendingDeviceFlow = null - this.publishStatusChanged() - return this.getStatus() - } - } - - cancelLogin(): XaiGrokAuthStatus { - if (this.pendingDeviceFlow) { - this.pendingDeviceFlow.cancelled = true - this.pendingDeviceFlow.abortController.abort() - this.pendingDeviceFlow = null + await this.publishStatusChanged(check) + return await this.getStatus() + } finally { + check() } - this.publishStatusChanged() - return this.getStatus() } async logout(): Promise { - const tokens = this.store.load() - this.cancelLogin() - this.store.clear() - this.lastError = null - - if (tokens?.refreshToken || tokens?.accessToken) { - try { - const discovery = await this.fetchDiscovery() - if (discovery.revocationEndpoint) { - await this.postForm( - discovery.revocationEndpoint, - { - token: tokens.refreshToken || tokens.accessToken, - client_id: XAI_GROK_OAUTH_CLIENT_ID - }, - false - ) + this.invalidate() + this.cancelPendingFlow() + const check = this.captureOperation() + try { + const tokens = await this.store.load() + check() + await this.store.clear() + check() + this.lastError = null + const token = tokens?.refreshToken || tokens?.accessToken + if (token) { + try { + const discovery = await this.fetchDiscovery(check) + check() + if (discovery.revocationEndpoint) + await this.postForm( + discovery.revocationEndpoint, + { + token, + client_id: XAI_GROK_OAUTH_CLIENT_ID + }, + check, + false + ) + } catch (error) { + check() + logger.warn('[xAI Grok OAuth] Token revoke failed:', sanitizeError(error)) } - } catch (error) { - logger.warn('[xAI Grok OAuth] Token revoke failed:', sanitizeError(error)) } + check() + await this.publishStatusChanged(check) + return await this.getStatus() + } finally { + check() } - - this.publishStatusChanged() - return this.getStatus() } async ensureAccessToken(): Promise { - if (isXaiGrokOAuthDisabled()) { - return null - } - - if (XAI_GROK_ACCESS_TOKEN_ENV) { - return XAI_GROK_ACCESS_TOKEN_ENV - } - - const tokens = this.store.load() - if (!tokens) { - return null - } - + const check = this.captureOperation() try { - const current = - tokens.expiresAt > Date.now() + XAI_GROK_TOKEN_REFRESH_SKEW_MS - ? tokens - : await this.refreshAccessToken(tokens) - return current.accessToken - } catch (error) { - this.lastError = sanitizeError(error) - this.publishStatusChanged() - throw error + if (isXaiGrokOAuthDisabled()) return null + if (XAI_GROK_ACCESS_TOKEN_ENV) return XAI_GROK_ACCESS_TOKEN_ENV + const tokens = await this.store.load() + check() + if (!tokens) return null + try { + const current = + tokens.expiresAt > Date.now() + XAI_GROK_TOKEN_REFRESH_SKEW_MS + ? tokens + : await this.refreshAccessToken(tokens, check) + check() + return current.accessToken + } catch (error) { + check() + this.lastError = sanitizeError(error) + await this.publishStatusChanged(check) + throw error + } + } finally { + check() } } async getAccessToken(): Promise { - const token = await this.ensureAccessToken() - if (!token) { - throw new Error(this.store.getLoadError() ?? 'xAI Grok OAuth sign-in is required') + const check = this.captureOperation() + try { + const token = await this.ensureAccessToken() + check() + if (!token) throw new Error(this.store.getLoadError() ?? 'xAI Grok OAuth sign-in is required') + return token + } finally { + check() } - return token } async forceRefreshAccessToken(): Promise { - this.assertEnabled() - const tokens = this.store.load() - if (!tokens?.refreshToken) { - throw new Error(this.store.getLoadError() ?? 'xAI Grok OAuth refresh token is unavailable') - } - const refreshed = await this.refreshAccessToken(tokens, true) - return refreshed.accessToken - } - - private withStorage(status: Omit): XaiGrokAuthStatus { - return { - ...status, - storage: this.store.getStorageState() + const check = this.captureOperation() + try { + this.assertEnabled() + const tokens = await this.store.load() + check() + if (!tokens?.refreshToken) + throw new Error(this.store.getLoadError() ?? 'xAI Grok OAuth refresh token is unavailable') + const refreshed = await this.refreshAccessToken(tokens, check, true) + check() + return refreshed.accessToken + } finally { + check() } } - private statusFromTokens(tokens: XaiGrokTokenSet): XaiGrokAuthStatus { - return this.withStorage({ - state: 'authenticated', - authenticated: true, - accountId: maskAccountId(tokens.accountId), - accountLabel: tokens.accountLabel, - expiresAt: tokens.expiresAt - }) - } - private assertEnabled(): void { - if (isXaiGrokOAuthDisabled()) { - throw new Error('xAI Grok OAuth is disabled by environment') - } + if (isXaiGrokOAuthDisabled()) throw new Error('xAI Grok OAuth is disabled by environment') } private async completeDeviceLogin( @@ -357,36 +412,106 @@ export class XaiGrokAuth { discovery: DiscoveryEndpoints, deviceCode: DeviceCodeResponse ): Promise { + const check = () => this.assertGeneration(flow.generation) try { + check() const tokens = await this.pollDeviceCodeToken( discovery.tokenEndpoint, deviceCode, + check, flow.abortController.signal ) - if (flow.cancelled || this.pendingDeviceFlow !== flow) { - return - } - + if (flow.cancelled || this.pendingDeviceFlow !== flow) return + check() const tokenSet = this.parseTokenResponse(tokens, undefined, { tokenEndpoint: discovery.tokenEndpoint, deviceAuthorizationEndpoint: discovery.deviceAuthorizationEndpoint }) - this.store.save(tokenSet) + this.invalidate() + flow.generation = this.generation + await this.store.save(tokenSet) + check() this.lastError = null this.pendingDeviceFlow = null - this.publishStatusChanged() + this.pendingLoginController = null + this.loginPending = false + await this.publishStatusChanged(check) } catch (error) { - if (flow.cancelled || this.pendingDeviceFlow !== flow) { + if ( + this.stopped || + flow.cancelled || + this.pendingDeviceFlow !== flow || + flow.generation !== this.generation + ) return - } - this.lastError = sanitizeError(error) this.pendingDeviceFlow = null - this.publishStatusChanged() + this.pendingLoginController = null + this.loginPending = false + try { + await this.publishStatusChanged(check) + } catch (publishError) { + if (!this.stopped && flow.generation === this.generation) + logger.warn( + '[xAI Grok OAuth] Failed to publish auth status:', + sanitizeError(publishError) + ) + } } } - private async fetchDiscovery(signal?: AbortSignal): Promise { + private async refreshAccessToken( + tokens: XaiGrokTokenSet, + check: () => void, + force = false + ): Promise { + check() + if (!force && tokens.expiresAt > Date.now() + XAI_GROK_TOKEN_REFRESH_SKEW_MS) return tokens + if (this.refreshPromise) return this.refreshPromise + const pending = this.refreshAccessTokenOnce(tokens, check).finally(() => { + if (this.refreshPromise === pending) this.refreshPromise = null + }) + this.refreshPromise = pending + return pending + } + + private async refreshAccessTokenOnce( + tokens: XaiGrokTokenSet, + check: () => void + ): Promise { + check() + if (!tokens.refreshToken) throw new Error('xAI Grok OAuth refresh token is unavailable') + let tokenEndpoint = tokens.tokenEndpoint + if (!tokenEndpoint || !isTrustedXaiOAuthEndpoint(tokenEndpoint)) + tokenEndpoint = (await this.fetchDiscovery(check)).tokenEndpoint + check() + const payload = await this.postForm( + tokenEndpoint, + { + grant_type: 'refresh_token', + client_id: XAI_GROK_OAUTH_CLIENT_ID, + refresh_token: tokens.refreshToken + }, + check + ) + check() + const refreshed = this.parseTokenResponse(payload, tokens, { + tokenEndpoint, + deviceAuthorizationEndpoint: tokens.deviceAuthorizationEndpoint + }) + await this.store.save(refreshed) + check() + this.lastError = null + await this.publishStatusChanged(check) + check() + return refreshed + } + + private async fetchDiscovery( + check: () => void, + signal?: AbortSignal + ): Promise { + check() if (this.cachedDiscovery) { return this.cachedDiscovery } @@ -403,11 +528,13 @@ export class XaiGrokAuth { signal ) + check() if (!response.ok) { throw new Error(`xAI OAuth discovery failed: ${await readErrorBody(response)}`) } const payload = (await response.json()) as TokenResponse + check() const deviceAuthorizationEndpoint = toStringValue(payload.device_authorization_endpoint) const tokenEndpoint = toStringValue(payload.token_endpoint) if (!deviceAuthorizationEndpoint || !tokenEndpoint) { @@ -435,8 +562,10 @@ export class XaiGrokAuth { private async requestDeviceCode( deviceAuthorizationEndpoint: string, + check: () => void, signal?: AbortSignal ): Promise { + check() const response = await this.fetchWithTimeout( requireTrustedEndpoint(deviceAuthorizationEndpoint, 'device authorization endpoint'), { @@ -454,11 +583,13 @@ export class XaiGrokAuth { signal ) + check() if (!response.ok) { throw new Error(`xAI device code request failed: ${await readErrorBody(response)}`) } const payload = (await response.json()) as TokenResponse + check() const deviceCode = toStringValue(payload.device_code) const userCode = toStringValue(payload.user_code) const verificationUri = toStringValue(payload.verification_uri) @@ -490,12 +621,15 @@ export class XaiGrokAuth { private async pollDeviceCodeToken( tokenEndpoint: string, deviceCode: DeviceCodeResponse, + check: () => void, signal?: AbortSignal ): Promise { + check() const deadlineMs = Date.now() + deviceCode.expiresInMs let intervalMs = deviceCode.intervalMs while (Date.now() < deadlineMs) { + check() if (signal?.aborted) { throw new Error('xAI Grok OAuth login cancelled') } @@ -525,6 +659,7 @@ export class XaiGrokAuth { payload = {} } + check() if (response.ok) { return payload } @@ -557,50 +692,6 @@ export class XaiGrokAuth { throw new Error('xAI device authorization timed out') } - private async refreshAccessToken( - currentTokens: XaiGrokTokenSet, - force = false - ): Promise { - if (!force && currentTokens.expiresAt > Date.now() + XAI_GROK_TOKEN_REFRESH_SKEW_MS) { - return currentTokens - } - - if (this.refreshPromise) { - return this.refreshPromise - } - - this.refreshPromise = this.refreshAccessTokenOnce(currentTokens).finally(() => { - this.refreshPromise = null - }) - return this.refreshPromise - } - - private async refreshAccessTokenOnce(currentTokens: XaiGrokTokenSet): Promise { - if (!currentTokens.refreshToken) { - throw new Error('xAI Grok OAuth refresh token is unavailable') - } - - let tokenEndpoint = currentTokens.tokenEndpoint - if (!tokenEndpoint || !isTrustedXaiOAuthEndpoint(tokenEndpoint)) { - tokenEndpoint = (await this.fetchDiscovery()).tokenEndpoint - } - - const payload = await this.postForm(tokenEndpoint, { - grant_type: 'refresh_token', - client_id: XAI_GROK_OAUTH_CLIENT_ID, - refresh_token: currentTokens.refreshToken - }) - - const refreshed = this.parseTokenResponse(payload, currentTokens, { - tokenEndpoint, - deviceAuthorizationEndpoint: currentTokens.deviceAuthorizationEndpoint - }) - this.store.save(refreshed) - this.lastError = null - this.publishStatusChanged() - return refreshed - } - private parseTokenResponse( payload: TokenResponse, previous?: XaiGrokTokenSet, @@ -642,8 +733,10 @@ export class XaiGrokAuth { private async postForm( url: string, params: Record, + check: () => void, requireOk = true ): Promise { + check() const response = await this.fetchWithTimeout(url, { method: 'POST', headers: { @@ -653,53 +746,16 @@ export class XaiGrokAuth { }, body: toFormBody(params) }) - + check() if (!response.ok) { - if (!requireOk) { - return {} - } - throw new Error(await readErrorBody(response)) - } - - return (await response.json()) as TokenResponse - } - - private async fetchWithTimeout( - url: string, - init: RequestInit, - externalSignal?: AbortSignal - ): Promise { - const controller = new AbortController() - const timeout = setTimeout(() => controller.abort(), XAI_GROK_AUTH_REQUEST_TIMEOUT_MS) - const onExternalAbort = () => controller.abort(externalSignal?.reason) - - if (externalSignal) { - if (externalSignal.aborted) { - controller.abort(externalSignal.reason) - } else { - externalSignal.addEventListener('abort', onExternalAbort, { once: true }) - } - } - - try { - return await fetch(url, { - ...init, - signal: controller.signal - }) - } catch (error) { - if (controller.signal.aborted) { - if (externalSignal?.aborted) { - throw new Error('xAI Grok OAuth login cancelled') - } - throw new Error( - `xAI Grok OAuth request timed out after ${XAI_GROK_AUTH_REQUEST_TIMEOUT_MS}ms` - ) - } - throw error - } finally { - clearTimeout(timeout) - externalSignal?.removeEventListener('abort', onExternalAbort) + if (!requireOk) return {} + const error = await readErrorBody(response) + check() + throw new Error(error) } + const payload = (await response.json()) as TokenResponse + check() + return payload } private async sleep(ms: number, signal?: AbortSignal): Promise { @@ -726,17 +782,59 @@ export class XaiGrokAuth { }) } - private publishStatusChanged(): void { - this.publishEvent('oauth.xaiGrok.statusChanged', { - status: this.getStatus(), - version: Date.now() - }) + private async fetchWithTimeout( + url: string, + init: RequestInit, + externalSignal?: AbortSignal + ): Promise { + const check = this.captureOperation() + if (this.requests.size >= 32) throw new Error('xAI Grok auth request limit exceeded') + const controller = new AbortController() + this.requests.add(controller) + const timer = setTimeout(() => controller.abort(), XAI_GROK_AUTH_REQUEST_TIMEOUT_MS) + const abort = () => controller.abort(externalSignal?.reason) + if (externalSignal?.aborted) abort() + else externalSignal?.addEventListener('abort', abort, { once: true }) + try { + controller.signal.throwIfAborted() + const response = await fetch(url, { ...init, signal: controller.signal }) + check() + const result = await readAuthResponse(response, controller.signal) + check() + return result + } catch (error) { + check() + if (controller.signal.aborted) { + if (externalSignal?.aborted) + throw new DOMException('xAI Grok OAuth login cancelled', 'AbortError') + throw new Error( + `xAI Grok auth request timed out after ${XAI_GROK_AUTH_REQUEST_TIMEOUT_MS}ms` + ) + } + throw error + } finally { + clearTimeout(timer) + externalSignal?.removeEventListener('abort', abort) + this.requests.delete(controller) + } } -} -export function initializeGlobalXaiGrokAuth(publishEvent: DeepchatEventPublisher): XaiGrokAuth { + private async publishStatusChanged(check: () => void): Promise { + check() + const publication = ++this.publication + const status = await this.getStatus() + check() + if (publication !== this.publication) return + this.publishEvent('oauth.xaiGrok.statusChanged', { status, version: Date.now() }) + } +} +export function initializeGlobalXaiGrokAuth( + publishEvent: DeepchatEventPublisher, + store: XaiGrokCredentialStore, + openExternal: (url: string) => Promise +): XaiGrokAuth { if (!globalXaiGrokAuth) { - globalXaiGrokAuth = new XaiGrokAuth(undefined, publishEvent) + globalXaiGrokAuth = new XaiGrokAuth(store, publishEvent, openExternal) } return globalXaiGrokAuth } @@ -749,6 +847,7 @@ export function getGlobalXaiGrokAuth(): XaiGrokAuth { } export function resetGlobalXaiGrokAuthForTests(): void { + globalXaiGrokAuth?.stop() globalXaiGrokAuth = null } diff --git a/src/main/provider/baseProvider.ts b/src/main/provider/baseProvider.ts index 2698d080b9..1d66dfd99a 100644 --- a/src/main/provider/baseProvider.ts +++ b/src/main/provider/baseProvider.ts @@ -10,12 +10,11 @@ import type { KeyStatus, LLM_EMBEDDING_ATTRS } from '@shared/types/provider' -import { DeviceService } from '../device' import { jsonrepair } from 'jsonrepair' import logger from '@shared/logger' import { resolveRequestTraceContext, type ProviderRequestTracePayload } from './requestTrace' import { normalizeToolInputSchema } from './aiSdk/toolMapper' -import type { ProviderLocalePort } from './ports' +import type { ProviderEnvironmentPort } from './ports' import { buildProviderTraceHeaders, fetchWithProviderHeaders } from './providerHeaders' export const AUDIO_TRANSCRIPTION_NOT_SUPPORTED_ERROR = 'audio-transcription-not-supported' @@ -50,23 +49,20 @@ export abstract class BaseLLMProvider { protected customModels: MODEL_META[] = [] protected isInitialized: boolean = false protected providerSettings: ProviderSettingsPort - private readonly locale: ProviderLocalePort + protected readonly environment: ProviderEnvironmentPort private modelFetchPromise: Promise | null = null - protected defaultHeaders: Record = { - 'HTTP-Referer': 'https://deepchatai.cn', - 'X-Title': 'DeepChat' - } + protected defaultHeaders: Record constructor( provider: LLM_PROVIDER, providerSettings: ProviderSettingsPort, - locale: ProviderLocalePort + locale: ProviderEnvironmentPort ) { this.provider = provider this.providerSettings = providerSettings - this.locale = locale - this.defaultHeaders = DeviceService.getDefaultHeaders() + this.environment = locale + this.defaultHeaders = { ...locale.defaultHeaders } // Initialize models and customModels from cached config data this.loadCachedModels() @@ -369,7 +365,7 @@ export abstract class BaseLLMProvider { * @returns 格式化的提示词 */ protected getFunctionCallWrapPrompt(tools: MCPToolDefinition[]): string { - const locale = this.locale.getLanguage() || 'zh-CN' + const locale = this.environment.getLanguage() || 'zh-CN' return `你具备调用外部工具的能力来协助解决用户的问题 ==== diff --git a/src/main/provider/catalog/providerCatalogService.ts b/src/main/provider/catalog/providerCatalogService.ts index 27ce24760f..618c262d98 100644 --- a/src/main/provider/catalog/providerCatalogService.ts +++ b/src/main/provider/catalog/providerCatalogService.ts @@ -55,6 +55,9 @@ export interface ProviderCatalogServiceDeps { source: ProviderCatalogSourcePort publishEvent: DeepchatEventPublisher now?: () => number + runRefresh?: ( + operation: () => Promise + ) => Promise } type CatalogDiff = { @@ -276,11 +279,14 @@ export class ProviderCatalogService { // A configuration change invalidates the instance that owns an older in-flight fetch; start // a refresh for the new instance instead of joining a promise that answers with stale facts. - const refreshPromise = this.performRefresh(providerId, reason, generation).finally(() => { - if (this.inFlight.get(providerId)?.promise === refreshPromise) { - this.inFlight.delete(providerId) + const performRefresh = () => this.performRefresh(providerId, reason, generation) + const refreshPromise = (this.deps.runRefresh?.(performRefresh) ?? performRefresh()).finally( + () => { + if (this.inFlight.get(providerId)?.promise === refreshPromise) { + this.inFlight.delete(providerId) + } } - }) + ) this.inFlight.set(providerId, { generation, reason, promise: refreshPromise }) return refreshPromise } diff --git a/src/main/provider/index.ts b/src/main/provider/index.ts index ee3b14a27e..66b2d74145 100644 --- a/src/main/provider/index.ts +++ b/src/main/provider/index.ts @@ -1,4 +1,6 @@ import type { ProviderSettingsPort } from '@/provider/settings' +import { createHmac, randomBytes } from 'node:crypto' +import type { GitHubCopilotSignInTarget } from '@shared/types/oauth' import logger from '@shared/logger' import type { LLMResponse } from '@shared/types/provider' import type { ChatMessage } from '@shared/types/core/chat-message' @@ -42,7 +44,7 @@ import type { AcpAsLlmProviderPermissionPort, AcpAsLlmProviderSessionControlPort, AcpProviderAdminPort, - ProviderLocalePort + ProviderEnvironmentPort } from './ports' import { BaseLLMProvider, @@ -69,7 +71,10 @@ import type { ProviderCatalogRefreshResult, ProviderCatalogSnapshot } from '@shared/types/providerCatalog' -import { ProviderCatalogService } from './catalog/providerCatalogService' +import { + ProviderCatalogService, + type ProviderCatalogServiceDeps +} from './catalog/providerCatalogService' const createAbortError = (): Error => { if (typeof DOMException !== 'undefined') { @@ -157,15 +162,17 @@ export class ProviderRuntime private readonly acpRuntimeOwner: AcpRuntimeOwner private readonly acpSessionPersistence: AcpSessionPersistence private shutdownPromise?: Promise + private copilotSignInKey = randomBytes(32) constructor( providerSettings: ProviderSettingsPort, - locale: ProviderLocalePort, + locale: ProviderEnvironmentPort, agentSettings: Pick, mcpSettings: McpSettings, acpRuntimeOwner: AcpRuntimeOwner, acpSessionPersistence: AcpSessionPersistence, - private readonly publishEvent: DeepchatEventPublisher + private readonly publishEvent: DeepchatEventPublisher, + runCatalogRefresh?: ProviderCatalogServiceDeps['runRefresh'] ) { this.providerSettings = providerSettings this.rateLimitManager = new RateLimitManager( @@ -182,7 +189,8 @@ export class ProviderRuntime reloadModels: (providerId) => this.getExistingProviderInstance(providerId)?.reloadModelsFromSettings() }, - publishEvent: this.publishEvent + publishEvent: this.publishEvent, + runRefresh: runCatalogRefresh }) this.providerInstanceManager = new ProviderInstanceManager({ providerSettings, @@ -247,6 +255,53 @@ export class ProviderRuntime } } + private copilotConfigurationIdentity(provider: LLM_PROVIDER): string { + return createHmac('sha256', this.copilotSignInKey) + .update( + JSON.stringify([ + provider.id, + provider.apiType, + provider.apiKey, + provider.copilotClientId?.trim() || '' + ]) + ) + .digest('hex') + } + + async getGitHubCopilotSignInTarget(providerId: string): Promise { + if (this.shutdownPromise) throw new Error('Provider runtime is stopped') + const provider = this.providerSettings.getProviderById(providerId) + if (!provider) throw new Error(`Provider ${providerId} not found`) + if (provider.apiType !== 'github-copilot') + throw new Error('Provider is not configured for GitHub Copilot') + return { + providerId, + clientId: provider.copilotClientId, + configurationId: this.copilotConfigurationIdentity(provider) + } + } + + async commitGitHubCopilotCredential( + target: GitHubCopilotSignInTarget, + apiKey: string + ): Promise { + if (this.shutdownPromise) throw new Error('Provider runtime is stopped') + if (typeof apiKey !== 'string' || !/^[\x21-\x7e]+$/.test(apiKey)) + throw new Error('Invalid GitHub OAuth token') + const provider = this.providerSettings.getProviderById(target.providerId) + if ( + !provider || + provider.apiType !== 'github-copilot' || + this.copilotConfigurationIdentity(provider) !== target.configurationId + ) + throw new Error('GitHub Copilot configuration changed during sign-in. Please try again.') + this.setProviderById(target.providerId, { ...provider, apiKey }) + } + + async invalidateGitHubCopilotSignIns(): Promise { + this.copilotSignInKey = randomBytes(32) + } + updateProviderAtomic(providerId: string, updates: Partial): boolean { if (!this.providerSettings.getProviderById(providerId)) { return false diff --git a/src/main/provider/managers/providerInstanceManager.ts b/src/main/provider/managers/providerInstanceManager.ts index 6b67232387..228b9e8932 100644 --- a/src/main/provider/managers/providerInstanceManager.ts +++ b/src/main/provider/managers/providerInstanceManager.ts @@ -14,7 +14,7 @@ import { RateLimitManager } from './rateLimitManager' import { StreamState } from '../types' import type { AcpRuntimeOwner } from '@/agent/acp/client' import { resolveAiSdkProviderDefinition } from '../providerRegistry' -import type { ProviderLocalePort } from '../ports' +import type { ProviderEnvironmentPort } from '../ports' import type { AgentSettingsPort } from '@/agent/settings' import type { ProviderSettingsPort } from '@/provider/settings' import type { DeepchatEventPublisher } from '@shared/contracts/events' @@ -22,7 +22,7 @@ import type { ProviderCatalogWriterPort } from '../catalog/providerCatalogServic interface ProviderInstanceManagerOptions { providerSettings: ProviderSettingsPort - locale: ProviderLocalePort + locale: ProviderEnvironmentPort agentSettings: Pick activeStreams: Map rateLimitManager: RateLimitManager diff --git a/src/main/provider/modelConfig.ts b/src/main/provider/modelConfig.ts index 423da79d59..bfe08ee7fe 100644 --- a/src/main/provider/modelConfig.ts +++ b/src/main/provider/modelConfig.ts @@ -14,7 +14,6 @@ import { } from '@shared/modelConfigDefaults' import { applyMoonshotKimiReasoningTemperaturePolicy } from '@shared/modelRequestPolicy' import { resolveVideoGenerationCompatType } from '@shared/videoGenerationSettings' -import ElectronStore from 'electron-store' import { hasAnthropicReasoningToggle, getReasoningEffortDefault, @@ -90,17 +89,10 @@ const normalizeVerbosityValue = ( } export class ModelConfigHelper { - private modelConfigStore: StoreLike> private memoryCache: Map = new Map() private cacheInitialized: boolean = false - constructor( - store: StoreLike> = new ElectronStore>({ - name: 'model-config' - }) - ) { - this.modelConfigStore = store - } + constructor(private readonly modelConfigStore: StoreLike>) {} /** * Infer model type from provider model data diff --git a/src/main/provider/openaiCodexAdapter.ts b/src/main/provider/openaiCodexAdapter.ts index 93d1e4b448..6ca77ad020 100644 --- a/src/main/provider/openaiCodexAdapter.ts +++ b/src/main/provider/openaiCodexAdapter.ts @@ -198,8 +198,13 @@ export function createOpenAICodexFetch( throw new Error('OpenAI Codex provider is disabled by environment') } + const signal = init?.signal ?? (url instanceof Request ? url.signal : undefined) + signal?.throwIfAborted() const auth = getGlobalOpenAICodexAuth() + const check = auth.captureOperation() const backendAuth = await auth.getBackendAuth() + check() + signal?.throwIfAborted() const responsesRequest = isResponsesRequest(url) const accept = responsesRequest ? 'text/event-stream' : 'application/json' const nextInit: RequestInit = { @@ -209,11 +214,16 @@ export function createOpenAICodexFetch( } let response = await baseFetch(url, nextInit) + signal?.throwIfAborted() if (response.status !== 401) { return normalizeOpenAICodexErrorResponse(response) } + check() + signal?.throwIfAborted() const refreshedAuth = await auth.forceRefreshBackendAuth() + check() + signal?.throwIfAborted() response = await baseFetch(url, { ...nextInit, headers: applyCodexHeaders(init?.headers, defaultHeaders, refreshedAuth, accept) diff --git a/src/main/provider/ports.ts b/src/main/provider/ports.ts index 4725100968..3b8e33928e 100644 --- a/src/main/provider/ports.ts +++ b/src/main/provider/ports.ts @@ -11,8 +11,10 @@ export interface ProviderCatalogPort { getAgentType(agentId: string): Promise<'deepchat' | 'acp' | null> } -export interface ProviderLocalePort { +export interface ProviderEnvironmentPort { getLanguage(): string + defaultHeaders: Readonly> + readonly allowInsecureTls: boolean } export interface AcpAsLlmProviderSessionControlPort { diff --git a/src/main/provider/providerDbLoader.ts b/src/main/provider/providerDbLoader.ts index d94bc2d50c..ddb2b536f9 100644 --- a/src/main/provider/providerDbLoader.ts +++ b/src/main/provider/providerDbLoader.ts @@ -1,6 +1,5 @@ import fs from 'fs' import path from 'path' -import { app } from 'electron' import { ProviderAggregate, ProviderEntry, @@ -76,19 +75,42 @@ export type ProviderDbCatalogChange = export type ProviderDbCatalogListener = (change: ProviderDbCatalogChange) => void +export type ProviderDbBootstrap = { + userDataDirectory: string + builtInCatalogPath: string +} + export class ProviderDbLoader { private cache: ProviderAggregate | null = null - private userDataDir: string - private cacheDir: string - private cacheFilePath: string - private metaFilePath: string + private bootstrap: ProviderDbBootstrap | null = null + private cacheDir = '' + private cacheFilePath = '' + private metaFilePath = '' + private stopped = false + private refreshController: AbortController | null = null private refreshPromise: Promise | null = null private privacyModeResolver: () => boolean = () => false private readonly catalogListeners = new Set() - constructor() { - this.userDataDir = process.env.DEEPCHAT_E2E_USER_DATA_DIR?.trim() || app.getPath('userData') - this.cacheDir = path.join(this.userDataDir, 'provider-db') + configure(bootstrap: ProviderDbBootstrap): void { + if (this.stopped) throw new Error('Provider catalog is stopping') + if ( + !path.isAbsolute(bootstrap.userDataDirectory) || + !path.isAbsolute(bootstrap.builtInCatalogPath) + ) { + throw new Error('Provider catalog paths must be absolute') + } + if (this.bootstrap) { + if ( + this.bootstrap.userDataDirectory !== bootstrap.userDataDirectory || + this.bootstrap.builtInCatalogPath !== bootstrap.builtInCatalogPath + ) { + throw new Error('Provider catalog is already configured for another profile') + } + return + } + this.bootstrap = { ...bootstrap } + this.cacheDir = path.join(bootstrap.userDataDirectory, 'provider-db') this.cacheFilePath = path.join(this.cacheDir, 'providers.json') this.metaFilePath = path.join(this.cacheDir, 'meta.json') @@ -97,6 +119,15 @@ export class ProviderDbLoader { } catch {} } + stop(): void { + this.stopped = true + this.refreshController?.abort() + } + + async drain(): Promise { + await this.refreshPromise + } + setPrivacyModeResolver(resolver?: () => boolean): void { this.privacyModeResolver = resolver ?? (() => false) } @@ -106,8 +137,10 @@ export class ProviderDbLoader { return () => this.catalogListeners.delete(listener) } - // Public: initialize on app start (non-blocking refresh) + // Load the initial catalog; the startup task owner schedules automatic refresh. async initialize(): Promise { + if (!this.bootstrap) throw new Error('Provider catalog paths are required') + if (this.stopped) return // Load from cache or built-in this.cache = this.loadFromCache() ?? this.loadFromBuiltIn() if (this.cache) { @@ -116,25 +149,11 @@ export class ProviderDbLoader { providersCount: Object.keys(this.cache.providers || {}).length }) } - - if (this.isAutomaticRefreshBlocked()) { - return - } - - // Always refresh once in the background on startup to pick up upstream updates. - void this.refreshIfNeeded(true, { automatic: true }) - .then((result) => { - if (result.status === 'error') { - console.warn('[ProviderDbLoader] Startup refresh failed:', result.message) - } - }) - .catch((error) => { - console.warn('[ProviderDbLoader] Startup refresh failed:', error) - }) } getDb(): ProviderAggregate | null { if (this.cache) return this.cache + if (!this.bootstrap || this.stopped) return null // Lazy try again if not initialized yet this.cache = this.loadFromCache() ?? this.loadFromBuiltIn() return this.cache @@ -172,7 +191,8 @@ export class ProviderDbLoader { private loadFromBuiltIn(): ProviderAggregate | null { try { - const helperPath = path.join(app.getAppPath(), 'resources', 'model-db', 'providers.json') + const helperPath = this.bootstrap?.builtInCatalogPath + if (!helperPath) return null if (!fs.existsSync(helperPath)) return null const raw = fs.readFileSync(helperPath, 'utf-8') const parsed = JSON.parse(raw) @@ -184,6 +204,7 @@ export class ProviderDbLoader { } private readMeta(): MetaFile | null { + if (!this.bootstrap || this.stopped) return null try { if (!fs.existsSync(this.metaFilePath)) return null const raw = fs.readFileSync(this.metaFilePath, 'utf-8') @@ -223,8 +244,11 @@ export class ProviderDbLoader { force = false, options: { automatic?: boolean + signal?: AbortSignal } = {} ): Promise { + if (!this.bootstrap) throw new Error('Provider catalog paths are required') + if (this.stopped || options.signal?.aborted) return this.createResult('skipped') const meta = this.readMeta() if (options.automatic && this.isAutomaticRefreshBlocked()) { return this.createResult('skipped', meta) @@ -244,9 +268,11 @@ export class ProviderDbLoader { return this.createResult('skipped', meta) } - this.refreshPromise = this.fetchAndUpdate(url, meta || undefined).finally(() => { - this.refreshPromise = null - }) + this.refreshPromise = this.fetchAndUpdate(url, meta || undefined, options.signal).finally( + () => { + this.refreshPromise = null + } + ) return this.refreshPromise } @@ -288,8 +314,14 @@ export class ProviderDbLoader { } } - private async fetchAndUpdate(url: string, prevMeta?: MetaFile): Promise { + private async fetchAndUpdate( + url: string, + prevMeta?: MetaFile, + signal?: AbortSignal + ): Promise { const controller = new AbortController() + this.refreshController = controller + const requestSignal = signal ? AbortSignal.any([signal, controller.signal]) : controller.signal const timeout = setTimeout(() => controller.abort(), 15000) try { const headers: Record = {} @@ -298,7 +330,8 @@ export class ProviderDbLoader { headers['If-None-Match'] = prevMeta.etag } - const res = await fetch(url, { headers, signal: controller.signal }) + const res = await fetch(url, { headers, signal: requestSignal }) + if (this.stopped || signal?.aborted) return this.createResult('skipped', prevMeta) const now = this.now() if (res.status === 304 && prevMeta) { @@ -319,6 +352,7 @@ export class ProviderDbLoader { } const text = await readResponseTextWithLimit(res, MAX_PROVIDER_DB_PAYLOAD_BYTES) + if (this.stopped || signal?.aborted) return this.createResult('skipped', prevMeta) if (text === null) { const meta = this.createAttemptMeta(prevMeta, url, now) if (meta) this.writeMeta(meta) @@ -362,12 +396,14 @@ export class ProviderDbLoader { }) return this.createResult('updated', meta) } catch (error) { + if (this.stopped || signal?.aborted) return this.createResult('skipped', prevMeta) const meta = this.createAttemptMeta(prevMeta, url, this.now()) if (meta) this.writeMeta(meta) const message = error instanceof Error ? error.message : 'Unknown provider DB refresh error' return this.createResult('error', meta, message) } finally { clearTimeout(timeout) + if (this.refreshController === controller) this.refreshController = null } } diff --git a/src/main/provider/providerHeaders.ts b/src/main/provider/providerHeaders.ts index 1f9acbcb8c..1bf9d25a1b 100644 --- a/src/main/provider/providerHeaders.ts +++ b/src/main/provider/providerHeaders.ts @@ -18,6 +18,14 @@ const CROSS_ORIGIN_SENSITIVE_HEADERS = [ export const MASKED_PROVIDER_CUSTOM_HEADER_VALUE = '***MASKED***' +export function buildDefaultProviderHeaders(appVersion: string): Record { + return { + 'HTTP-Referer': 'https://deepchatai.cn', + 'X-Title': 'DeepChat', + 'User-Agent': `DeepChat/${appVersion}` + } +} + type ProviderFetch = (input: string | URL | Request, init?: RequestInit) => Promise const getRequestUrl = (input: string | URL | Request): URL | null => { diff --git a/src/main/provider/providerModelHelper.ts b/src/main/provider/providerModelHelper.ts index 74d16f0e90..134c81521e 100644 --- a/src/main/provider/providerModelHelper.ts +++ b/src/main/provider/providerModelHelper.ts @@ -2,8 +2,6 @@ import logger from '@shared/logger' import type { MODEL_META, ModelRouteConfig } from '@shared/types/provider' import { isNewApiEndpointType, ModelType, resolveNewApiModelTypeFromMetadata } from '@shared/model' import { resolveVideoGenerationCompatType } from '@shared/videoGenerationSettings' -import ElectronStore from 'electron-store' -import path from 'path' import type { StoreLike } from '@/config/storeLike' import type { DeepchatEventPublisher } from '@shared/contracts/events' import { emitModelsChanged } from './eventPublishers' @@ -14,7 +12,6 @@ export interface IModelStore { custom_models: MODEL_META[] } -export const PROVIDER_MODELS_DIR = 'provider_models' const PROVIDER_MODEL_CACHE_TTL_MS = 250 type ProviderModelRouteSource = Pick< @@ -27,7 +24,7 @@ type ModelStatusUpdater = (providerId: string, modelId: string, enabled: boolean type ModelStatusRemover = (providerId: string, modelId: string) => void interface ProviderModelHelperOptions { - userDataPath: string + createStore: (providerId: string) => ProviderModelStore setModelStatus: ModelStatusUpdater deleteModelStatus: ModelStatusRemover publishEvent: DeepchatEventPublisher @@ -53,12 +50,11 @@ function isNonChatModelType(type: ModelType | undefined): type is ModelType { } export class ProviderModelHelper { - private readonly userDataPath: string + private readonly createStore: (providerId: string) => ProviderModelStore private readonly setModelStatus: ModelStatusUpdater private readonly deleteModelStatus: ModelStatusRemover private readonly publishEvent: DeepchatEventPublisher private readonly stores: Map = new Map() - private storeFactory: ((providerId: string) => ProviderModelStore) | null = null private readonly providerModelsCache = new Map< string, { @@ -69,47 +65,15 @@ export class ProviderModelHelper { >() constructor(options: ProviderModelHelperOptions) { - this.userDataPath = options.userDataPath + this.createStore = options.createStore this.setModelStatus = options.setModelStatus this.deleteModelStatus = options.deleteModelStatus this.publishEvent = options.publishEvent } - private getStoreName(providerId: string): string { - const safeProviderId = encodeURIComponent(providerId).replace(/\*/g, '%2A') - return `models_${safeProviderId}` - } - - setStoreFactory(factory: (providerId: string) => ProviderModelStore): void { - this.storeFactory = factory - this.stores.clear() - this.invalidateAllProviderModelsCache() - } - getProviderModelStore(providerId: string): ProviderModelStore { if (!this.stores.has(providerId)) { - if (this.storeFactory) { - this.stores.set(providerId, this.storeFactory(providerId)) - return this.stores.get(providerId)! - } - - const storeName = this.getStoreName(providerId) - const storePath = path.join(this.userDataPath, PROVIDER_MODELS_DIR) - logger.info( - `[ProviderModelHelper] getProviderModelStore: creating isolated store "${storeName}" at "${storePath}" for provider "${providerId}"` - ) - const store = new ElectronStore({ - name: storeName, - cwd: storePath, - defaults: { - models: [], - custom_models: [] - } - }) - this.stores.set(providerId, store as unknown as ProviderModelStore) - logger.info( - `[ProviderModelHelper] getProviderModelStore: store "${storeName}" created and cached for provider "${providerId}"` - ) + this.stores.set(providerId, this.createStore(providerId)) } return this.stores.get(providerId)! } diff --git a/src/main/provider/providers/acpProvider.ts b/src/main/provider/providers/acpProvider.ts index 00dfa43715..265a6cbb48 100644 --- a/src/main/provider/providers/acpProvider.ts +++ b/src/main/provider/providers/acpProvider.ts @@ -11,7 +11,7 @@ import { SUMMARY_TITLES_PROMPT, type ProviderGenerateTextOptions } from '../baseProvider' -import type { ProviderLocalePort } from '../ports' +import type { ProviderEnvironmentPort } from '../ports' import type { AgentSettingsPort } from '@/agent/settings' import type { ChatMessage } from '@shared/types/core/chat-message' import type { LLMResponse } from '@shared/types/provider' @@ -158,7 +158,7 @@ export class AcpProvider extends BaseLLMProvider { constructor( provider: LLM_PROVIDER, providerSettings: ProviderSettingsPort, - locale: ProviderLocalePort, + locale: ProviderEnvironmentPort, agentSettings: Pick, runtimeOwner: AcpRuntimeOwner, private readonly publishEvent: DeepchatEventPublisher, diff --git a/src/main/provider/providers/aiSdkProvider.ts b/src/main/provider/providers/aiSdkProvider.ts index 1b0ec0e4e9..404b43d4de 100644 --- a/src/main/provider/providers/aiSdkProvider.ts +++ b/src/main/provider/providers/aiSdkProvider.ts @@ -34,7 +34,7 @@ import { SUMMARY_TITLES_PROMPT, type ProviderGenerateTextOptions } from '../baseProvider' -import type { ProviderLocalePort } from '../ports' +import type { ProviderEnvironmentPort } from '../ports' import { runAiSdkCoreStream, runAiSdkDimensions, @@ -44,7 +44,7 @@ import { } from '../aiSdk' import type { AiSdkProviderKind } from '../aiSdk/providerFactory' import { normalizeAzureBaseUrl, normalizeGeminiBaseUrl } from '../aiSdk/providerFactory' -import { shouldUseXaiGrokOAuthFetch } from '../xaiGrokAuthAdapter' +import { createXaiGrokFetch, shouldUseXaiGrokOAuthFetch } from '../xaiGrokAuthAdapter' import { getGlobalXaiGrokAuth } from '../../provider/auth/xaiGrok' import { isTrustedXaiApiEndpoint } from '../../provider/auth/xaiGrok/constants' import { @@ -249,7 +249,7 @@ export class AiSdkProvider extends BaseLLMProvider { constructor( provider: LLM_PROVIDER, providerSettings: ProviderSettingsPort, - locale: ProviderLocalePort + locale: ProviderEnvironmentPort ) { super(provider, providerSettings, locale) const definition = resolveAiSdkProviderDefinition(provider) @@ -600,16 +600,6 @@ export class AiSdkProvider extends BaseLLMProvider { base.baseUrl = 'https://api.fireworks.ai/inference/v1' } - if (shouldUseXaiGrokOAuthFetch(base)) { - const oauthToken = getGlobalXaiGrokAuth().peekAccessToken() - if (oauthToken) { - return { - ...base, - oauthToken - } - } - } - return base } @@ -917,19 +907,10 @@ export class AiSdkProvider extends BaseLLMProvider { } try { - // Refresh Grok OAuth tokens before model/list requests that use sync headers. const runtimeProvider = this.getRuntimeProvider(resolvedDecision) const useGrokOAuth = shouldUseXaiGrokOAuthFetch(runtimeProvider) && isTrustedXaiApiEndpoint(url) - if (useGrokOAuth) { - await getGlobalXaiGrokAuth() - .ensureAccessToken() - .catch(() => null) - } - const refreshedRuntimeProvider = this.getRuntimeProvider(resolvedDecision) - const requestRuntimeProvider = useGrokOAuth - ? refreshedRuntimeProvider - : { ...refreshedRuntimeProvider, oauthToken: undefined } + const requestRuntimeProvider = { ...runtimeProvider, oauthToken: undefined } const requestHeaders = this.getRequestHeaders( resolvedDecision, requestRuntimeProvider, @@ -944,7 +925,14 @@ export class AiSdkProvider extends BaseLLMProvider { } requestHeaders[name] = value }) - const response = await this.fetchProvider(url, { + controller.signal.throwIfAborted() + const request = useGrokOAuth + ? createXaiGrokFetch(runtimeProvider, {}, (input, options) => + this.fetchProvider(input, options) + ) + : (input: string | URL | Request, options?: RequestInit) => + this.fetchProvider(input, options) + const response = await request(url, { ...init, headers: requestHeaders, signal: controller.signal @@ -2672,11 +2660,11 @@ export class AiSdkProvider extends BaseLLMProvider { } } - private validateCredentials(strategy: AiSdkCredentialStrategy): string | null { + private async validateCredentials(strategy: AiSdkCredentialStrategy): Promise { switch (strategy) { case 'api-key': if (shouldUseXaiGrokOAuthFetch(this.provider)) { - if (this.provider.apiKey || getGlobalXaiGrokAuth().isAuthenticated()) { + if (this.provider.apiKey || (await getGlobalXaiGrokAuth().isAuthenticated())) { return null } return 'Missing API key or xAI Grok OAuth sign-in' @@ -2730,7 +2718,7 @@ export class AiSdkProvider extends BaseLLMProvider { } } case 'generate-text': { - const credentialError = this.validateCredentials( + const credentialError = await this.validateCredentials( this.definition.credentialStrategy ?? 'none' ) if (credentialError) { diff --git a/src/main/provider/providers/githubCopilotProvider.ts b/src/main/provider/providers/githubCopilotProvider.ts index eaa0073b9d..8f2a6e8b69 100644 --- a/src/main/provider/providers/githubCopilotProvider.ts +++ b/src/main/provider/providers/githubCopilotProvider.ts @@ -16,12 +16,12 @@ import { type ProviderGenerateTextOptions } from '../baseProvider' import { normalizeToolInputSchema } from '../aiSdk/toolMapper' -import type { ProviderLocalePort } from '../ports' +import type { ProviderEnvironmentPort } from '../ports' import { createGitHubCopilotHttpError, getGitHubCopilotToken, GITHUB_COPILOT_CHAT_HEADERS -} from '../../provider/auth/githubCopilotDeviceFlow' +} from '../../provider/auth/githubCopilotToken' export class GithubCopilotProvider extends BaseLLMProvider { private copilotToken: string | null = null @@ -31,7 +31,7 @@ export class GithubCopilotProvider extends BaseLLMProvider { constructor( provider: LLM_PROVIDER, providerSettings: ProviderSettingsPort, - locale: ProviderLocalePort + locale: ProviderEnvironmentPort ) { super(provider, providerSettings, locale) this.init() diff --git a/src/main/provider/providers/jevProvider.ts b/src/main/provider/providers/jevProvider.ts index a2075b04dd..b8a12472f8 100644 --- a/src/main/provider/providers/jevProvider.ts +++ b/src/main/provider/providers/jevProvider.ts @@ -12,7 +12,7 @@ import type { ProviderStreamOptions } from '@shared/types/provider' import { BaseLLMProvider, type ProviderGenerateTextOptions } from '../baseProvider' -import type { ProviderLocalePort } from '../ports' +import type { ProviderEnvironmentPort } from '../ports' import { createProviderHttpErrorFromResponse, ProviderHttpError } from '../providerFailure' /** @@ -164,7 +164,7 @@ export class JevProvider extends BaseLLMProvider { constructor( provider: LLM_PROVIDER, providerSettings: ProviderSettingsPort, - locale: ProviderLocalePort + locale: ProviderEnvironmentPort ) { super(provider, providerSettings, locale) this.init() diff --git a/src/main/provider/providers/ollamaProvider.ts b/src/main/provider/providers/ollamaProvider.ts index 530762034b..f7bb6d1436 100644 --- a/src/main/provider/providers/ollamaProvider.ts +++ b/src/main/provider/providers/ollamaProvider.ts @@ -19,7 +19,7 @@ import { SUMMARY_TITLES_PROMPT, type ProviderGenerateTextOptions } from '../baseProvider' -import type { ProviderLocalePort } from '../ports' +import type { ProviderEnvironmentPort } from '../ports' import { execFile } from 'node:child_process' import { Ollama, ShowResponse } from 'ollama' import { @@ -30,7 +30,6 @@ import { type AiSdkRuntimeContext } from '../aiSdk' import { normalizeOllamaOpenAIBaseUrl, normalizeOllamaSdkHost } from '../aiSdk/providerFactory' -import { isInsecureTlsAllowed } from '@/lib/insecureTls' import { buildResolvedCapabilitySnapshot, resolveCapabilityIdentity } from '../capabilityIdentity' const OLLAMA_LIST_TIMEOUT_MS = 5000 @@ -48,7 +47,7 @@ export class OllamaProvider extends BaseLLMProvider { constructor( provider: LLM_PROVIDER, providerSettings: ProviderSettingsPort, - locale: ProviderLocalePort + locale: ProviderEnvironmentPort ) { super(provider, providerSettings, locale) this.ollama = this.createOllamaClient() @@ -768,7 +767,7 @@ export class OllamaProvider extends BaseLLMProvider { try { const stream = await this.ollama.pull({ model: modelName, - insecure: isInsecureTlsAllowed(), + insecure: this.environment.allowInsecureTls, stream: true }) diff --git a/src/main/provider/providers/voiceAIProvider.ts b/src/main/provider/providers/voiceAIProvider.ts index b275ce61f9..8ac4035330 100644 --- a/src/main/provider/providers/voiceAIProvider.ts +++ b/src/main/provider/providers/voiceAIProvider.ts @@ -15,7 +15,7 @@ import { createProviderHttpErrorFromResponse, extractProviderFailureMetadata } from '../providerFailure' -import type { ProviderLocalePort } from '../ports' +import type { ProviderEnvironmentPort } from '../ports' const DEFAULT_BASE_URL = 'https://dev.voice.ai' const DEFAULT_AUDIO_FORMAT = 'mp3' @@ -62,7 +62,7 @@ export class VoiceAIProvider extends BaseLLMProvider { constructor( provider: LLM_PROVIDER, providerSettings: ProviderSettingsPort, - locale: ProviderLocalePort + locale: ProviderEnvironmentPort ) { super(provider, providerSettings, locale) this.init() diff --git a/src/main/provider/settings.ts b/src/main/provider/settings.ts index 2e21dc6788..14043553bf 100644 --- a/src/main/provider/settings.ts +++ b/src/main/provider/settings.ts @@ -24,19 +24,19 @@ import { resolveModelVision } from '@shared/modelConfigDefaults' import { DEFAULT_PROVIDERS } from '@/provider/defaults' -import path from 'path' -import { app } from 'electron' -import fs from 'fs' import { compare } from 'compare-versions' import { ModelConfigHelper } from '@/provider/modelConfig' -import { providerDbLoader, type ProviderDbRefreshResult } from '@/provider/providerDbLoader' +import { + providerDbLoader, + type ProviderDbRefreshResult, + type ProviderDbBootstrap +} from '@/provider/providerDbLoader' import { ProviderAggregate, type ProviderModel } from '@shared/types/model-db' import { modelCapabilities } from '@/provider/modelCapabilities' import { ProviderHelper } from '@/provider/providerHelper' import { ModelStatusHelper } from '@/provider/modelStatusHelper' import { ProviderModelHelper, - PROVIDER_MODELS_DIR, type ProviderModelRouteMetadata } from '@/provider/providerModelHelper' import { stripDerivedProviderModelFields } from '@/provider/providerModelFacts' @@ -353,8 +353,6 @@ export type ProviderModelResolutionPort = Pick< > export class ProviderSettings implements ProviderSettingsPort { - private userDataPath: string - private currentAppVersion: string private modelConfigHelper: ModelConfigHelper // Model configuration helper private providerHelper: ProviderHelper private modelStatusHelper: ModelStatusHelper @@ -367,12 +365,12 @@ export class ProviderSettings implements ProviderSettingsPort { private readonly privacy: PrivacySettingsPort, private readonly database: ProviderDatabase, private readonly publishEvent: DeepchatEventPublisher, + currentAppVersion: string, + catalogBootstrap: ProviderDbBootstrap, previousAppVersion?: string ) { this.appSettings = settings this.store = new ProviderDbStore(settings, () => this.database.settingsTable) - this.userDataPath = app.getPath('userData') - this.currentAppVersion = app.getVersion() this.providerHelper = new ProviderHelper({ store: this.store, setSetting: this.setSetting.bind(this), @@ -392,14 +390,12 @@ export class ProviderSettings implements ProviderSettingsPort { ) this.providerModelHelper = new ProviderModelHelper({ - userDataPath: this.userDataPath, + createStore: (providerId) => + new ProviderModelDbStore(providerId, () => this.database.settingsTable), setModelStatus: this.modelStatusHelper.setModelStatus.bind(this.modelStatusHelper), deleteModelStatus: this.modelStatusHelper.deleteModelStatus.bind(this.modelStatusHelper), publishEvent: this.publishEvent }) - this.providerModelHelper.setStoreFactory( - (providerId) => new ProviderModelDbStore(providerId, () => this.database.settingsTable) - ) this.providerHelper.setCleanupHooks({ deleteProviderModelStatuses: this.modelStatusHelper.deleteProviderModelStatuses.bind( this.modelStatusHelper @@ -409,10 +405,6 @@ export class ProviderSettings implements ProviderSettingsPort { ) }) - // Initialize built-in ACP agents on first run or version upgrade - // Initialize provider models directory - this.initProviderModelsDir() - // 初始化 Provider DB(外部聚合 JSON,本地内置为兜底) providerDbLoader.subscribeCatalogChanges((change) => { const reason = change.reason === 'loaded' ? 'provider-db-loaded' : 'provider-db-updated' @@ -420,11 +412,12 @@ export class ProviderSettings implements ProviderSettingsPort { this.publishEvent('models.changed', { reason, version: Date.now() }) }) providerDbLoader.setPrivacyModeResolver(() => this.privacy.isEnabled()) + providerDbLoader.configure(catalogBootstrap) providerDbLoader.initialize().catch((error) => { console.warn('[ProviderSettings] Failed to initialize provider DB:', error) }) - if (previousAppVersion !== this.currentAppVersion) { + if (previousAppVersion !== currentAppVersion) { this.migrateConfigData(previousAppVersion) } @@ -460,13 +453,6 @@ export class ProviderSettings implements ProviderSettingsPort { return legacyProviders.length } - private initProviderModelsDir(): void { - const modelsDir = path.join(this.userDataPath, PROVIDER_MODELS_DIR) - if (!fs.existsSync(modelsDir)) { - fs.mkdirSync(modelsDir, { recursive: true }) - } - } - // 提供聚合 Provider DB(只读)给渲染层/其他模块 getProviderDb(): ProviderAggregate | null { return providerDbLoader.getDb() diff --git a/src/main/provider/xaiGrokAuthAdapter.ts b/src/main/provider/xaiGrokAuthAdapter.ts index 847e1aec72..2ebb43ef0c 100644 --- a/src/main/provider/xaiGrokAuthAdapter.ts +++ b/src/main/provider/xaiGrokAuthAdapter.ts @@ -34,8 +34,17 @@ export function createXaiGrokFetch( throw new Error('Refusing to send xAI credentials to an untrusted API endpoint') } + const signal = init?.signal ?? (url instanceof Request ? url.signal : undefined) + signal?.throwIfAborted() const auth = getGlobalXaiGrokAuth() - const oauthToken = await auth.ensureAccessToken().catch(() => null) + const check = auth.captureOperation() + const oauthToken = await auth.ensureAccessToken().catch((error) => { + signal?.throwIfAborted() + if (error instanceof Error && error.name === 'AbortError') throw error + return null + }) + check() + signal?.throwIfAborted() const accessToken = oauthToken || provider.apiKey?.trim() || '' if (!accessToken) { throw new Error('Grok requires xAI OAuth sign-in or an API key') @@ -47,13 +56,18 @@ export function createXaiGrokFetch( } let response = await underlyingFetch(url, nextInit) + signal?.throwIfAborted() if (response.status !== 401 || !oauthToken) { return response } // OAuth token may have been revoked mid-session; force refresh once. try { + check() + signal?.throwIfAborted() const refreshed = await auth.forceRefreshAccessToken() + check() + signal?.throwIfAborted() if (!refreshed || refreshed === accessToken) { return response } @@ -61,7 +75,10 @@ export function createXaiGrokFetch( ...nextInit, headers: applyBearerHeaders(init?.headers, defaultHeaders, refreshed) }) - } catch { + } catch (error) { + check() + signal?.throwIfAborted() + if (error instanceof Error && error.name === 'AbortError') throw error return response } } diff --git a/src/main/remote/channels/weixinIlink/weixinIlinkClient.ts b/src/main/remote/channels/weixinIlink/weixinIlinkClient.ts index 326a09812f..ac45686a67 100644 --- a/src/main/remote/channels/weixinIlink/weixinIlinkClient.ts +++ b/src/main/remote/channels/weixinIlink/weixinIlinkClient.ts @@ -1,5 +1,6 @@ import { createCipheriv, createHash, randomBytes, randomUUID } from 'node:crypto' import fs from 'node:fs/promises' +import { setTimeout as delay } from 'node:timers/promises' type WeixinIlinkQrCodeResponse = { qrcode?: string @@ -247,18 +248,21 @@ const normalizeHttpError = async (response: Response): Promise => { const withTimeout = async ( timeoutMs: number | undefined, - operation: (signal?: AbortSignal) => Promise + operation: (signal?: AbortSignal) => Promise, + parentSignal?: AbortSignal ): Promise => { const normalizedTimeoutMs = typeof timeoutMs === 'number' && Number.isFinite(timeoutMs) && timeoutMs > 0 ? timeoutMs : 0 if (!normalizedTimeoutMs) { - return await operation() + return await operation(parentSignal) } const controller = new AbortController() const timer = setTimeout(() => controller.abort(), normalizedTimeoutMs) try { - return await operation(controller.signal) + return await operation( + parentSignal ? AbortSignal.any([parentSignal, controller.signal]) : controller.signal + ) } finally { clearTimeout(timer) } @@ -267,20 +271,25 @@ const withTimeout = async ( const fetchGetJson = async ( baseUrl: string, endpoint: string, - timeoutMs?: number + timeoutMs?: number, + parentSignal?: AbortSignal ): Promise => { - return await withTimeout(timeoutMs, async (signal) => { - const response = await fetch(new URL(endpoint, ensureTrailingSlash(baseUrl)).toString(), { - method: 'GET', - ...(signal ? { signal } : {}) - }) + return await withTimeout( + timeoutMs, + async (signal) => { + const response = await fetch(new URL(endpoint, ensureTrailingSlash(baseUrl)).toString(), { + method: 'GET', + ...(signal ? { signal } : {}) + }) - if (!response.ok) { - throw new WeixinIlinkApiError(await normalizeHttpError(response), response.status) - } + if (!response.ok) { + throw new WeixinIlinkApiError(await normalizeHttpError(response), response.status) + } - return (await response.json()) as T - }) + return (await response.json()) as T + }, + parentSignal + ) } const fetchPostJson = async (params: { @@ -334,7 +343,9 @@ export class WeixinIlinkClient { static async startLogin(input?: { sessionKey?: string force?: boolean + signal?: AbortSignal }): Promise { + input?.signal?.throwIfAborted() const sessionKey = input?.sessionKey?.trim() || randomUUID() purgeExpiredLogins() @@ -350,8 +361,10 @@ export class WeixinIlinkClient { const response = await fetchGetJson( WEIXIN_ILINK_FIXED_BASE_URL, `ilink/bot/get_bot_qrcode?bot_type=${encodeURIComponent(WEIXIN_ILINK_DEFAULT_BOT_TYPE)}`, - WEIXIN_ILINK_REQUEST_TIMEOUT_MS + WEIXIN_ILINK_REQUEST_TIMEOUT_MS, + input?.signal ) + input?.signal?.throwIfAborted() const qrcode = response.qrcode?.trim() || '' const loginUrl = response.qrcode_img_content?.trim() || '' if (!qrcode || !loginUrl) { @@ -376,7 +389,25 @@ export class WeixinIlinkClient { static async waitForLogin(input: { sessionKey: string timeoutMs?: number + signal?: AbortSignal + }): Promise { + try { + return await this.pollForLogin(input) + } finally { + if (input.signal?.aborted) activeLogins.delete(input.sessionKey) + } + } + + static cancelLogin(sessionKey: string): void { + activeLogins.delete(sessionKey) + } + + private static async pollForLogin(input: { + sessionKey: string + timeoutMs?: number + signal?: AbortSignal }): Promise { + input.signal?.throwIfAborted() purgeExpiredLogins() const login = activeLogins.get(input.sessionKey) if (!login || !isFreshLogin(login)) { @@ -394,14 +425,17 @@ export class WeixinIlinkClient { const deadline = Date.now() + timeoutMs while (Date.now() < deadline) { + input.signal?.throwIfAborted() let response: WeixinIlinkQrStatusResponse try { response = await fetchGetJson( login.currentBaseUrl, `ilink/bot/get_qrcode_status?qrcode=${encodeURIComponent(login.qrcode)}`, - WEIXIN_ILINK_QR_POLL_TIMEOUT_MS + WEIXIN_ILINK_QR_POLL_TIMEOUT_MS, + input.signal ) } catch (error) { + input.signal?.throwIfAborted() if (error instanceof Error && error.name === 'AbortError') { response = { status: 'wait' @@ -411,6 +445,7 @@ export class WeixinIlinkClient { } } + input.signal?.throwIfAborted() switch (response.status) { case 'wait': case 'scaned': @@ -453,7 +488,7 @@ export class WeixinIlinkClient { break } - await new Promise((resolve) => setTimeout(resolve, 1_000)) + await delay(1_000, undefined, { signal: input.signal }) } activeLogins.delete(input.sessionKey) diff --git a/src/main/remote/conversation/runner.ts b/src/main/remote/conversation/runner.ts index 044f60119b..6e8c07e55d 100644 --- a/src/main/remote/conversation/runner.ts +++ b/src/main/remote/conversation/runner.ts @@ -1,4 +1,3 @@ -import { app } from 'electron' import crypto from 'node:crypto' import fs from 'node:fs/promises' import path from 'node:path' @@ -149,8 +148,7 @@ const safeDecodePath = (value: string): string => { } } -const resolveCachedImagePath = (source: string): string => { - const cacheDir = path.join(app.getPath('userData'), 'images') +const resolveCachedImagePath = (source: string, cacheDir: string): string => { const cachePath = safeDecodePath(source.slice(IMGCACHE_URL_PREFIX.length)) const fullPath = path.resolve(cacheDir, cachePath) const relativePath = path.relative(cacheDir, fullPath) @@ -172,7 +170,8 @@ const decodeBase64Image = (value: string, label: string): Buffer => { const resolveGeneratedImageContent = async ( source: string, - fallbackMimeType: string + fallbackMimeType: string, + cacheDirectory: string ): Promise<{ data: Buffer mimeType: string @@ -191,7 +190,7 @@ const resolveGeneratedImageContent = async ( } if (normalizedSource.startsWith(IMGCACHE_URL_PREFIX)) { - const imagePath = resolveCachedImagePath(normalizedSource) + const imagePath = resolveCachedImagePath(normalizedSource, cacheDirectory) return { data: await fs.readFile(imagePath), mimeType: @@ -424,6 +423,7 @@ export type RemoteOpenSessionResult = } type RemoteConversationRunnerDeps = { + profileDirectory: string catalog: RemoteCatalogPort workspace: RemoteWorkspacePort lifecycle: RemoteSessionLifecyclePort @@ -442,7 +442,10 @@ export class RemoteConversationRunner { constructor( private readonly deps: RemoteConversationRunnerDeps, private readonly bindingStore: RemoteBindingStore - ) {} + ) { + if (!path.isAbsolute(deps.profileDirectory)) + throw new Error('Remote profile directory must be absolute') + } async createNewSession( endpointKey: string, @@ -805,8 +808,8 @@ export class RemoteConversationRunner { return currentSession?.agentId ?? (await this.deps.resolveDefaultAgentId()) } - private getGlobalDefaultWorkdir(): string | null { - const projectDir = this.deps.workspace.getDefaultProjectPath() + private async getGlobalDefaultWorkdir(): Promise { + const projectDir = await this.deps.workspace.getDefaultProjectPath() const normalized = projectDir?.trim() return normalized ? normalized : null } @@ -882,7 +885,7 @@ export class RemoteConversationRunner { return path.join(workspace, REMOTE_ASSET_ROOT) } - return path.join(app.getPath('userData'), REMOTE_GENERATED_ASSET_ROOT) + return path.join(this.deps.profileDirectory, REMOTE_GENERATED_ASSET_ROOT) } private async prepareRemoteAttachments( @@ -1221,7 +1224,11 @@ export class RemoteConversationRunner { const assets: RemoteGeneratedImageAsset[] = [] for (const [candidateIndex, candidate] of imageCandidates.entries()) { try { - const imageContent = await resolveGeneratedImageContent(candidate.data, candidate.mimeType) + const imageContent = await resolveGeneratedImageContent( + candidate.data, + candidate.mimeType, + path.join(this.deps.profileDirectory, 'images') + ) const filenameBase = sanitizePathSegment( candidate.filenameBase, `generated-${candidateIndex + 1}` diff --git a/src/main/remote/index.ts b/src/main/remote/index.ts index 9161a7f520..a113c276c3 100644 --- a/src/main/remote/index.ts +++ b/src/main/remote/index.ts @@ -1,6 +1,7 @@ -import { BrowserWindow } from 'electron' import { randomBytes } from 'node:crypto' import * as http from 'node:http' +import { setTimeout as delay } from 'node:timers/promises' +import { finished } from 'node:stream/promises' import logger from '@shared/logger' import type { ChannelSettingsMap, @@ -57,7 +58,7 @@ import { import type { ChannelAdapterConfig } from './runtime/types' import { resolveAcpAgentAlias } from '@shared/utils/acpAgentAlias' import { REMOTE_CONTROL_ERROR_MESSAGES } from '@shared/contracts/remoteControlErrors' -import type { RemoteServiceDeps } from './ports' +import type { RemoteAuthWindowEvent, RemoteServiceDeps } from './ports' import { RemoteBindingStore } from './binding/store' import { RemoteConversationRunner } from './conversation/runner' import { TelegramClient } from './channels/telegram/telegramClient' @@ -75,7 +76,6 @@ import { fetchFeishuOAuthUserInfo, pollFeishuPersonalAgentRegistration, readFeishuRegistrationString, - resolveFeishuAuthDomains, startFeishuPersonalAgentRegistration } from './channels/feishu/feishuAuth' import { WeixinIlinkClient } from './channels/weixinIlink/weixinIlinkClient' @@ -129,7 +129,9 @@ type FeishuAuthSessionState = { authUrl: string expiresAt: number server: http.Server | null - window: BrowserWindow | null + startup: Promise | null + cleanup: Promise | null + callbacks: Set> cleanupTimer: NodeJS.Timeout | null abortController: AbortController resolve: (result: FeishuAuthResult) => void @@ -220,8 +222,11 @@ export class RemoteService { private runtimeOperation: Promise = Promise.resolve() private readonly feishuAuthSessions = new Map() private readonly feishuInstallSessions = new Map() - private weixinIlinkLoginWindow: BrowserWindow | null = null - private weixinIlinkLoginWindowUrl: string | null = null + private weixinIlinkLoginWindowId: string | null = null + private authController = new AbortController() + private readonly authTasks = new Set>() + private stopping: Promise | null = null + private readonly weixinLoginSessions = new Set() private readonly weixinIlinkLoginWaits = new Map>() private readonly deliveryErrorNotifiedAt = new Map() private readonly deliveryErrorNotificationInFlight = new Set() @@ -236,6 +241,12 @@ export class RemoteService { } async initialize(): Promise { + await this.stopping + if (this.authController.signal.aborted) { + if (this.authTasks.size) throw new Error('Remote authorization is still draining') + this.authController = new AbortController() + } + this.stopping = null await this.enqueueRuntimeOperation(async () => { await Promise.all([ this.rebuildTelegramRuntime(), @@ -248,17 +259,48 @@ export class RemoteService { } async destroy(): Promise { - await this.enqueueRuntimeOperation(async () => { - await this.channelManager.unregisterAll() - }) - for (const sessionKey of Array.from(this.feishuAuthSessions.keys())) { - await this.cancelFeishuAuth(sessionKey) + if (this.stopping) return await this.stopping + this.authController.abort() + this.stopping = this.stopRuntime() + await this.stopping + } + + private async stopRuntime(): Promise { + for (const session of this.feishuAuthSessions.values()) { + this.completeFeishuAuthSession( + session, + createFeishuAuthResult({ + authorized: false, + messageKey: 'settings.remote.feishu.authCancelled' + }) + ) } - for (const sessionKey of Array.from(this.feishuInstallSessions.keys())) { - await this.cancelFeishuInstall(sessionKey) + for (const session of this.feishuInstallSessions.values()) { + this.completeFeishuInstallSession( + session, + createFeishuInstallResult({ + installed: false, + messageKey: 'settings.remote.feishu.installCancelled' + }) + ) + } + try { + await this.enqueueRuntimeOperation(async () => { + await this.channelManager.unregisterAll() + }) + } finally { + const closed = await Promise.allSettled([ + ...Array.from(this.feishuAuthSessions.keys(), (key) => this.cancelFeishuAuth(key)), + ...Array.from(this.feishuInstallSessions.keys(), (key) => this.cancelFeishuInstall(key)), + this.closeWeixinIlinkLoginWindow() + ]) + while (this.authTasks.size) await Promise.allSettled(Array.from(this.authTasks)) + for (const sessionKey of this.weixinLoginSessions) WeixinIlinkClient.cancelLogin(sessionKey) + this.weixinLoginSessions.clear() + this.weixinIlinkLoginWaits.clear() + const failure = closed.find((result) => result.status === 'rejected') + if (failure?.status === 'rejected') throw failure.reason } - this.weixinIlinkLoginWaits.clear() - this.closeWeixinIlinkLoginWindow() } buildTelegramSettingsSnapshot(): TelegramRemoteSettings { @@ -666,10 +708,22 @@ export class RemoteService { } async startFeishuAuth(input: FeishuAuthStartInput = {}): Promise { + const signal = this.authController.signal + signal.throwIfAborted() + return await this.retainAuthTask( + this.enqueueRuntimeOperation(() => this.beginFeishuAuth(input, signal)) + ) + } + + private async beginFeishuAuth( + input: FeishuAuthStartInput, + signal: AbortSignal + ): Promise { this.pruneExpiredFeishuAuthSessions() for (const existingSessionKey of Array.from(this.feishuAuthSessions.keys())) { await this.cancelFeishuAuth(existingSessionKey) } + signal.throwIfAborted() const currentConfig = this.bindingStore.getFeishuConfig() const brand = input.brand === 'lark' ? 'lark' : currentConfig.brand @@ -709,7 +763,9 @@ export class RemoteService { authUrl, expiresAt, server: null, - window: null, + startup: null, + cleanup: null, + callbacks: new Set(), cleanupTimer: null, abortController: new AbortController(), resolve: resolveResult, @@ -720,21 +776,40 @@ export class RemoteService { this.feishuAuthSessions.set(sessionKey, session) try { - session.server = await this.startFeishuAuthCallbackServer(session) - session.cleanupTimer = setTimeout(() => { - this.completeFeishuAuthSession( - session, - createFeishuAuthResult({ - authorized: false, - messageKey: 'settings.remote.feishu.authTimeout' - }) - ) - this.feishuAuthSessions.delete(session.sessionKey) - }, FEISHU_AUTH_SESSION_TTL_MS) - this.openFeishuAuthWindow(session) + session.startup = (async () => { + session.server = await this.startFeishuAuthCallbackServer(session) + if (session.completed || signal.aborted) return + session.cleanupTimer = setTimeout(() => { + this.completeFeishuAuthSession( + session, + createFeishuAuthResult({ + authorized: false, + messageKey: 'settings.remote.feishu.authTimeout' + }) + ) + this.feishuAuthSessions.delete(session.sessionKey) + }, FEISHU_AUTH_SESSION_TTL_MS) + await this.deps.authWindows.open({ + id: `feishu:${session.sessionKey}`, + kind: 'feishu', + url: session.authUrl, + brand: session.brand, + redirectUri: session.redirectUri + }) + })() + await session.startup + signal.throwIfAborted() + if (session.completed) throw new Error('Feishu authorization was cancelled') } catch (error) { this.feishuAuthSessions.delete(sessionKey) - this.cleanupFeishuAuthSession(session) + this.completeFeishuAuthSession( + session, + createFeishuAuthResult({ + authorized: false, + messageKey: 'settings.remote.feishu.authFailed' + }) + ) + await this.cleanupFeishuAuthSession(session) throw error } @@ -802,9 +877,22 @@ export class RemoteService { }) ) this.feishuAuthSessions.delete(session.sessionKey) + await this.cleanupFeishuAuthSession(session) } async startFeishuInstall(input: FeishuInstallStartInput = {}): Promise { + const signal = this.authController.signal + signal.throwIfAborted() + return await this.retainAuthTask( + this.enqueueRuntimeOperation(() => this.beginFeishuInstall(input, signal)) + ) + } + + private async beginFeishuInstall( + input: FeishuInstallStartInput, + signal: AbortSignal + ): Promise { + signal.throwIfAborted() this.pruneExpiredFeishuInstallSessions() for (const existingSessionKey of Array.from(this.feishuInstallSessions.keys())) { await this.cancelFeishuInstall(existingSessionKey) @@ -813,7 +901,10 @@ export class RemoteService { const requestedBrand = input.brand === 'lark' ? 'lark' : this.bindingStore.getFeishuConfig().brand const abortController = new AbortController() - const registration = await startFeishuPersonalAgentRegistration(abortController.signal) + const registration = await startFeishuPersonalAgentRegistration( + AbortSignal.any([signal, abortController.signal]) + ) + signal.throwIfAborted() const sessionKey = randomBytes(16).toString('hex') const expiresAt = Date.now() + registration.expireInSec * 1000 const intervalMs = Math.max(registration.intervalSec * 1000, 3_000) @@ -882,7 +973,7 @@ export class RemoteService { if (!session.polling) { session.polling = true - void this.pollFeishuInstallUntilComplete(session) + void this.retainAuthTask(this.pollFeishuInstallUntilComplete(session)).catch(() => undefined) } const timeoutMs = Math.min( input.timeoutMs ?? FEISHU_INSTALL_DEFAULT_WAIT_TIMEOUT_MS, @@ -1125,10 +1216,26 @@ export class RemoteService { } async startWeixinIlinkLogin(input?: { force?: boolean }): Promise { + const signal = this.authController.signal + signal.throwIfAborted() + return await this.retainAuthTask( + this.enqueueRuntimeOperation(() => this.beginWeixinIlinkLogin(input, signal)) + ) + } + + private async beginWeixinIlinkLogin( + input: { force?: boolean } | undefined, + signal: AbortSignal + ): Promise { + signal.throwIfAborted() const result = await WeixinIlinkClient.startLogin({ - force: input?.force + force: input?.force, + signal }) - this.openWeixinIlinkLoginWindow(result.loginUrl) + this.weixinLoginSessions.add(result.sessionKey) + if (signal.aborted) WeixinIlinkClient.cancelLogin(result.sessionKey) + signal.throwIfAborted() + await this.openWeixinIlinkLoginWindow(result.sessionKey, result.loginUrl, signal) return { sessionKey: result.sessionKey, loginUrl: result.loginUrl, @@ -1141,6 +1248,8 @@ export class RemoteService { sessionKey: string timeoutMs?: number }): Promise { + const signal = this.authController.signal + signal.throwIfAborted() const sessionKey = input.sessionKey.trim() if (!sessionKey) { return { @@ -1156,11 +1265,15 @@ export class RemoteService { } const waitPromise = (async () => { + const windowId = this.weixinIlinkLoginWindowId const result = await WeixinIlinkClient.waitForLogin({ ...input, - sessionKey + sessionKey, + signal }) - this.closeWeixinIlinkLoginWindow() + signal.throwIfAborted() + if (windowId === this.weixinIlinkLoginWindowId) await this.closeWeixinIlinkLoginWindow() + signal.throwIfAborted() if (!result.connected || !result.accountId || !result.ownerUserId || !result.botToken) { return { connected: false, @@ -1195,12 +1308,14 @@ export class RemoteService { messageKey: result.messageKey } })().finally(() => { + this.weixinLoginSessions.delete(sessionKey) if (this.weixinIlinkLoginWaits.get(sessionKey) === waitPromise) { this.weixinIlinkLoginWaits.delete(sessionKey) } }) this.weixinIlinkLoginWaits.set(sessionKey, waitPromise) + this.retainAuthTask(waitPromise) return await waitPromise } @@ -2048,7 +2163,7 @@ export class RemoteService { const error = readFeishuRegistrationString(data, 'error') if (error) { if (error === 'authorization_pending' || error === 'slow_down') { - await this.delayFeishuInstallPoll(session.intervalMs) + await this.delayFeishuInstallPoll(session.intervalMs, session.abortController.signal) continue } @@ -2137,7 +2252,7 @@ export class RemoteService { return } - await this.delayFeishuInstallPoll(session.intervalMs) + await this.delayFeishuInstallPoll(session.intervalMs, session.abortController.signal) } if (!session.completed) { @@ -2151,8 +2266,12 @@ export class RemoteService { } } - private delayFeishuInstallPoll(ms: number): Promise { - return new Promise((resolve) => setTimeout(resolve, ms)) + private async delayFeishuInstallPoll(ms: number, signal: AbortSignal): Promise { + try { + await delay(ms, undefined, { signal }) + } catch (error) { + if (!signal.aborted) throw error + } } private completeFeishuInstallSession( @@ -2221,8 +2340,22 @@ export class RemoteService { } const server = http.createServer((request, response) => { - void this.handleFeishuAuthCallback(session, request, response) + const pending = (async () => { + try { + await this.handleFeishuAuthCallback(session, request, response) + } finally { + if (!response.writableEnded) response.end() + await finished(response, { cleanup: true }).catch(() => undefined) + } + })() + session.callbacks.add(pending) + const remove = () => session.callbacks.delete(pending) + void pending.then(remove, remove) + void this.retainAuthTask(pending).catch(() => { + if (!response.writableEnded) response.end() + }) }) + session.server = server await new Promise((resolve, reject) => { server.once('error', reject) @@ -2383,11 +2516,17 @@ export class RemoteService { } session.completed = true - this.cleanupFeishuAuthSession(session) - session.resolve(result) + void this.cleanupFeishuAuthSession(session).then( + () => session.resolve(result), + (error) => { + logger.warn('[RemoteService] Failed to close Feishu authorization.', { error }) + session.resolve(result) + } + ) } - private cleanupFeishuAuthSession(session: FeishuAuthSessionState): void { + private cleanupFeishuAuthSession(session: FeishuAuthSessionState): Promise { + if (session.cleanup) return session.cleanup if (session.cleanupTimer) { clearTimeout(session.cleanupTimer) session.cleanupTimer = null @@ -2397,15 +2536,33 @@ export class RemoteService { session.abortController.abort() } - if (session.server) { - session.server.close() - session.server = null - } - - if (session.window && !session.window.isDestroyed()) { - session.window.close() - } - session.window = null + session.cleanup = this.retainAuthTask( + (async () => { + await session.startup?.catch(() => undefined) + const server = session.server + session.server = null + const closed = server + ? new Promise((resolve, reject) => { + server.close((error) => { + if (error && (error as NodeJS.ErrnoException).code !== 'ERR_SERVER_NOT_RUNNING') + reject(error) + else resolve() + }) + }) + : Promise.resolve() + const callbacks = (async () => { + while (session.callbacks.size) await Promise.allSettled(Array.from(session.callbacks)) + // Partial HTTP headers are not active callbacks and must not retain the loopback server. + server?.closeAllConnections() + })() + await Promise.all([ + closed, + callbacks, + this.deps.authWindows.close(`feishu:${session.sessionKey}`) + ]) + })() + ) + return session.cleanup } private pruneExpiredFeishuAuthSessions(): void { @@ -2426,140 +2583,54 @@ export class RemoteService { } } - private isAllowedFeishuAuthNavigation( - url: string, - brand: 'feishu' | 'lark', - redirectUri: string - ): boolean { - try { - const parsed = new URL(url) - const redirect = new URL(redirectUri) - const domains = resolveFeishuAuthDomains(brand) - const accountsHost = new URL(domains.accountsBaseUrl).host - const openHost = new URL(domains.openBaseUrl).host - const isAuthHost = - parsed.protocol === 'https:' && (parsed.host === accountsHost || parsed.host === openHost) - const isRedirectHost = - parsed.protocol === redirect.protocol && - parsed.host === redirect.host && - parsed.pathname === redirect.pathname - return isAuthHost || isRedirectHost - } catch { - return false - } - } - - private openFeishuAuthWindow(session: FeishuAuthSessionState): void { - const parentWindow = BrowserWindow.getFocusedWindow() ?? BrowserWindow.getAllWindows()[0] - const loginWindow = new BrowserWindow({ - width: 480, - height: 760, - minWidth: 420, - minHeight: 680, - autoHideMenuBar: true, - title: 'Feishu / Lark Authorization', - ...(parentWindow ? { parent: parentWindow } : {}), - webPreferences: { - nodeIntegration: false, - contextIsolation: true, - webSecurity: true - } - }) - - loginWindow.webContents.on('will-navigate', (event, url) => { - if (!this.isAllowedFeishuAuthNavigation(url, session.brand, session.redirectUri)) { - event.preventDefault() - } - }) - loginWindow.webContents.setWindowOpenHandler(({ url }) => { - if (this.isAllowedFeishuAuthNavigation(url, session.brand, session.redirectUri)) { - void loginWindow.loadURL(url) - } - return { action: 'deny' } - }) - loginWindow.on('closed', () => { - if (session.window === loginWindow && !session.completed) { - this.completeFeishuAuthSession( - session, - createFeishuAuthResult({ - authorized: false, - messageKey: 'settings.remote.feishu.authCancelled' - }) - ) - } - if (session.window === loginWindow) { - session.window = null - } - }) - - void loginWindow.loadURL(session.authUrl) - loginWindow.show() - loginWindow.focus() - session.window = loginWindow - } - - private openWeixinIlinkLoginWindow(loginUrl: string | null | undefined): void { - const normalizedLoginUrl = loginUrl?.trim() - if (!normalizedLoginUrl) { - return + handleAuthWindowEvent(event: RemoteAuthWindowEvent): void { + if (event.id.startsWith('feishu:')) { + const session = this.feishuAuthSessions.get(event.id.slice('feishu:'.length)) + if (!session || session.completed) return + this.completeFeishuAuthSession( + session, + createFeishuAuthResult({ + authorized: false, + messageKey: + event.type === 'closed' + ? 'settings.remote.feishu.authCancelled' + : 'settings.remote.feishu.authFailed' + }) + ) + } else if (event.id === this.weixinIlinkLoginWindowId && event.type === 'closed') { + this.weixinIlinkLoginWindowId = null } + } - if ( - this.weixinIlinkLoginWindow && - !this.weixinIlinkLoginWindow.isDestroyed() && - this.weixinIlinkLoginWindowUrl === normalizedLoginUrl - ) { - this.weixinIlinkLoginWindow.show() - this.weixinIlinkLoginWindow.focus() - return + private async openWeixinIlinkLoginWindow( + sessionKey: string, + loginUrl: string | null | undefined, + signal: AbortSignal + ): Promise { + const url = loginUrl?.trim() + if (!url) return + const id = `weixin-ilink:${sessionKey}` + if (this.weixinIlinkLoginWindowId !== id) await this.closeWeixinIlinkLoginWindow() + this.weixinIlinkLoginWindowId = id + await this.deps.authWindows.open({ id, kind: 'weixin-ilink', url }) + if (signal.aborted || this.weixinIlinkLoginWindowId !== id) { + await this.deps.authWindows.close(id) + signal.throwIfAborted() + throw new Error('Weixin authorization was superseded') } - - this.closeWeixinIlinkLoginWindow() - - const parentWindow = BrowserWindow.getFocusedWindow() ?? BrowserWindow.getAllWindows()[0] - const loginWindow = new BrowserWindow({ - width: 420, - height: 760, - minWidth: 380, - minHeight: 680, - autoHideMenuBar: true, - title: 'WeChat iLink Login', - ...(parentWindow ? { parent: parentWindow } : {}), - webPreferences: { - nodeIntegration: false, - contextIsolation: true, - webSecurity: true - } - }) - - loginWindow.webContents.setWindowOpenHandler(({ url }) => { - void loginWindow.loadURL(url) - return { action: 'deny' } - }) - loginWindow.on('closed', () => { - if (this.weixinIlinkLoginWindow === loginWindow) { - this.weixinIlinkLoginWindow = null - this.weixinIlinkLoginWindowUrl = null - } - }) - - void loginWindow.loadURL(normalizedLoginUrl) - loginWindow.show() - loginWindow.focus() - this.weixinIlinkLoginWindow = loginWindow - this.weixinIlinkLoginWindowUrl = normalizedLoginUrl } - private closeWeixinIlinkLoginWindow(): void { - if (!this.weixinIlinkLoginWindow || this.weixinIlinkLoginWindow.isDestroyed()) { - this.weixinIlinkLoginWindow = null - this.weixinIlinkLoginWindowUrl = null - return - } + private async closeWeixinIlinkLoginWindow(): Promise { + const id = this.weixinIlinkLoginWindowId + this.weixinIlinkLoginWindowId = null + if (id) await this.deps.authWindows.close(id) + } - this.weixinIlinkLoginWindow.close() - this.weixinIlinkLoginWindow = null - this.weixinIlinkLoginWindowUrl = null + private retainAuthTask(pending: Promise): Promise { + this.authTasks.add(pending) + const remove = () => this.authTasks.delete(pending) + void pending.then(remove, remove) + return pending } private notifyChannelDeliveryError( @@ -2605,6 +2676,7 @@ export class RemoteService { private createConversationRunner(channel: RemoteChannel): RemoteConversationRunner { return new RemoteConversationRunner( { + profileDirectory: this.deps.profileDirectory, catalog: this.deps.catalog, workspace: this.deps.workspace, lifecycle: this.deps.lifecycle, diff --git a/src/main/remote/ports.ts b/src/main/remote/ports.ts index 1b551fedd5..baac28ed4c 100644 --- a/src/main/remote/ports.ts +++ b/src/main/remote/ports.ts @@ -56,7 +56,7 @@ export interface RemoteCatalogPort { } export interface RemoteWorkspacePort { - getDefaultProjectPath(): string | null + getDefaultProjectPath(): Promise prepareFile(filePath: string, type?: string): Promise } @@ -64,6 +64,16 @@ export interface RemoteDesktopPort { openSession(sessionId: string): Promise } +export interface RemoteSessionWindowTarget { + windowId: number + webContentsId: number +} + +export interface RemoteSessionWindowPort { + select(): Promise + present(target: RemoteSessionWindowTarget): Promise +} + export interface RemoteNotificationPort { showNotification(options: { id: string @@ -73,7 +83,20 @@ export interface RemoteNotificationPort { }): Promise } +export type RemoteAuthWindowInput = + | { id: string; kind: 'feishu'; url: string; brand: 'feishu' | 'lark'; redirectUri: string } + | { id: string; kind: 'weixin-ilink'; url: string } + +export type RemoteAuthWindowEvent = { id: string; type: 'closed' | 'error' } + +export interface RemoteAuthWindowPort { + open(input: RemoteAuthWindowInput): Promise + close(id: string): Promise +} + export interface RemoteServiceDeps { + profileDirectory: string + authWindows: RemoteAuthWindowPort settings: Pick catalog: RemoteCatalogPort workspace: RemoteWorkspacePort @@ -91,6 +114,7 @@ export interface RemoteRuntimeLifecycle { } export interface RemoteServiceLike extends RemoteServicePort, RemoteRuntimeLifecycle { + handleAuthWindowEvent(event: RemoteAuthWindowEvent): void deliverCronJobResult(input: { job: CronJob run: CronJobRun diff --git a/src/main/remote/runtime/adapter.ts b/src/main/remote/runtime/adapter.ts index 4b2e26a498..d89902b88d 100644 --- a/src/main/remote/runtime/adapter.ts +++ b/src/main/remote/runtime/adapter.ts @@ -1,16 +1,11 @@ -import path from 'node:path' import { EventEmitter } from 'node:events' -import { net } from 'electron' import logger from '@shared/logger' import { - CHANNEL_MAX_FILE_SIZE_BYTES, type ChannelAdapterConfig, type ChannelLogEntry, type ChannelLogLevel, type ChannelStatusSnapshot, - type FileAttachment, type IChannelAdapter, - type ImageAttachment, type SendMessageOptions } from './types' @@ -196,79 +191,4 @@ export abstract class ChannelAdapter extends EventEmitter implements IChannelAda this.emit('log', entry) } - protected async downloadImageAsBase64(url: string): Promise { - const response = await this.fetchBinaryAttachment(url) - if (!response) { - return null - } - - return { - data: response.data, - media_type: response.mediaType - } - } - - protected async downloadFileAsBase64( - url: string, - filename: string - ): Promise { - const response = await this.fetchBinaryAttachment(url) - if (!response) { - return null - } - - return { - filename: filename.trim() || path.basename(new URL(url).pathname) || 'attachment', - data: response.data, - media_type: response.mediaType, - size: response.size - } - } - - private async fetchBinaryAttachment(url: string): Promise<{ - data: string - mediaType: string - size: number - } | null> { - const response = await net.fetch(url) - if (!response.ok) { - this.emitLog('warn', 'Failed to download channel attachment.', { - url, - status: response.status - }) - return null - } - - const contentLength = response.headers.get('content-length') - const declaredSize = - typeof contentLength === 'string' && contentLength.trim() - ? Number.parseInt(contentLength, 10) - : Number.NaN - - if (Number.isFinite(declaredSize) && declaredSize > CHANNEL_MAX_FILE_SIZE_BYTES) { - this.emitLog('warn', 'Skipped oversized channel attachment.', { - url, - size: declaredSize, - limit: CHANNEL_MAX_FILE_SIZE_BYTES - }) - return null - } - - const arrayBuffer = await response.arrayBuffer() - const buffer = Buffer.from(arrayBuffer) - if (buffer.byteLength > CHANNEL_MAX_FILE_SIZE_BYTES) { - this.emitLog('warn', 'Skipped oversized channel attachment.', { - url, - size: buffer.byteLength, - limit: CHANNEL_MAX_FILE_SIZE_BYTES - }) - return null - } - - return { - data: buffer.toString('base64'), - mediaType: response.headers.get('content-type')?.trim() || 'application/octet-stream', - size: buffer.byteLength - } - } } diff --git a/src/main/remote/runtime/types.ts b/src/main/remote/runtime/types.ts index 2f388ec65e..29f4428a8d 100644 --- a/src/main/remote/runtime/types.ts +++ b/src/main/remote/runtime/types.ts @@ -2,7 +2,6 @@ import { EventEmitter } from 'node:events' import { z } from 'zod' import type { RemoteRuntimeState } from '@shared/types/remote' -export const CHANNEL_MAX_FILE_SIZE_BYTES = 20 * 1024 * 1024 export const CHANNEL_PLUGIN_SCHEMA_VERSION = 1 export const CHANNEL_PLUGIN_API_VERSION = 1 diff --git a/src/main/routes/index.ts b/src/main/routes/index.ts index 8ecdd8cea9..4c4176b4e1 100644 --- a/src/main/routes/index.ts +++ b/src/main/routes/index.ts @@ -1,6 +1,4 @@ -import { BrowserWindow, type IpcMain, type IpcMainInvokeEvent } from 'electron' -import type { IWindowPresenter } from '@shared/types/desktop' -import { DEEPCHAT_ROUTE_INVOKE_CHANNEL } from '@shared/contracts/channels' +import { randomUUID } from 'node:crypto' import { hasDeepchatRouteContract, mcpGetClientsRoute, @@ -14,36 +12,31 @@ import { sessionsListLightweightRoute, skillsListMetadataRoute } from '@shared/contracts/routes' -import { - createRendererRouteContext, - createRouteRegistry, - type DeepchatRouteMap, - type RouteContext -} from './routeRegistry' +import { createRouteRegistry, type DeepchatRouteMap, type RouteContext } from './routeRegistry' import type { StartupWorkloadCoordinator } from '@/app/startupWorkloadCoordinator' -import type { AuthorizeRendererIpc } from '@/app/rendererIpcSecurity' export type RouteDispatcher = { appDatabaseMaintenance: MainKernelAppDatabaseMaintenancePort routeRegistry: DeepchatRouteMap - settingsWindow: Pick + isSettingsWindow(windowId: number): Promise startupWorkloadCoordinator: StartupWorkloadCoordinator } export interface MainKernelAppDatabaseMaintenancePort { assertRouteAllowed(routeName: string): void + runRequest(routeName: string, run: () => Promise): Promise } export function createRouteDispatcher(deps: { appDatabaseMaintenance: MainKernelAppDatabaseMaintenancePort routeMaps: readonly DeepchatRouteMap[] - settingsWindow: Pick + isSettingsWindow(windowId: number): Promise startupWorkloadCoordinator: StartupWorkloadCoordinator }): RouteDispatcher { return { appDatabaseMaintenance: deps.appDatabaseMaintenance, routeRegistry: createRouteRegistry(deps.routeMaps), - settingsWindow: deps.settingsWindow, + isSettingsWindow: deps.isSettingsWindow, startupWorkloadCoordinator: deps.startupWorkloadCoordinator } } @@ -63,22 +56,38 @@ type StartupTrackedRouteTask = { resource: 'cpu' | 'io' labelKey: string id: string - dedupeKey?: string } -function isSettingsWindowContext(dispatcher: RouteDispatcher, context: RouteContext): boolean { +const startupTrackedRoutes = new Set([ + providersListSummariesRoute.name, + modelsGetProviderCatalogRoute.name, + providersListOllamaModelsRoute.name, + providersListOllamaRunningModelsRoute.name, + sessionsListLightweightRoute.name, + skillsListMetadataRoute.name, + mcpGetServersRoute.name, + mcpGetEnabledRoute.name, + mcpGetClientsRoute.name, + mcpGetNpmRegistryStatusRoute.name +]) + +async function isSettingsWindowContext( + dispatcher: RouteDispatcher, + context: RouteContext +): Promise { if (context.caller.kind !== 'renderer' || context.caller.windowId == null) { return false } - return dispatcher.settingsWindow.getSettingsWindowId() === context.caller.windowId + return await dispatcher.isSettingsWindow(context.caller.windowId) } -function resolveTrackedRouteTask( +async function resolveTrackedRouteTask( dispatcher: RouteDispatcher, routeName: string, context: RouteContext -): StartupTrackedRouteTask | null { - const isSettings = isSettingsWindowContext(dispatcher, context) +): Promise { + if (!startupTrackedRoutes.has(routeName)) return null + const isSettings = await isSettingsWindowContext(dispatcher, context) if (routeName === providersListSummariesRoute.name && isSettings) { return { @@ -87,8 +96,7 @@ function resolveTrackedRouteTask( phase: 'interactive', resource: 'io', labelKey: 'startup.settings.providers.summary', - id: 'settings.providers.summary:route', - dedupeKey: 'settings.providers.summary:route' + id: 'settings.providers.summary:route' } } @@ -136,8 +144,7 @@ function resolveTrackedRouteTask( phase: 'interactive', resource: 'io', labelKey: 'startup.main.session.firstPage', - id: 'main.session.firstPage:route', - dedupeKey: 'main.session.firstPage:route' + id: 'main.session.firstPage:route' } } @@ -179,7 +186,8 @@ async function runTrackedRouteTask( action: () => Promise ): Promise { const coordinator = dispatcher.startupWorkloadCoordinator - const trackedTask = resolveTrackedRouteTask(dispatcher, routeName, context) + const trackedTask = await resolveTrackedRouteTask(dispatcher, routeName, context) + dispatcher.appDatabaseMaintenance.assertRouteAllowed(routeName) if (!trackedTask) { return await action() } @@ -191,10 +199,12 @@ async function runTrackedRouteTask( resource: trackedTask.resource, labelKey: trackedTask.labelKey, visibleId: trackedTask.visibleId, - dedupeKey: trackedTask.dedupeKey, + // Startup progress must not share results across route arguments or callers. + dedupeKey: randomUUID(), runId: coordinator.getRunId(trackedTask.target), - run: async () => { - return await action() + run: async ({ signal }) => { + signal.throwIfAborted() + return await dispatcher.appDatabaseMaintenance.runRequest(routeName, action) } }) } @@ -205,40 +215,21 @@ export async function dispatchDeepchatRoute( rawInput: unknown, context: RouteContext ): Promise { - dispatcher.appDatabaseMaintenance.assertRouteAllowed(routeName) if (!hasDeepchatRouteContract(routeName)) { throw new Error(`Unknown deepchat route: ${routeName}`) } const registeredRoute = dispatcher.routeRegistry.get(routeName) if (registeredRoute) { - return await runTrackedRouteTask(dispatcher, routeName, context, async () => { - return await registeredRoute(rawInput, context) - }) - } - - throw new Error(`Unhandled deepchat route: ${routeName}`) -} - -export function registerDeepchatRoutes( - ipcMain: IpcMain, - dispatcher: RouteDispatcher, - authorize: AuthorizeRendererIpc -): void { - ipcMain.removeHandler(DEEPCHAT_ROUTE_INVOKE_CHANNEL) - ipcMain.handle( - DEEPCHAT_ROUTE_INVOKE_CHANNEL, - async (event: IpcMainInvokeEvent, routeName: string, rawInput: unknown) => { - authorize(event, routeName) - return await dispatchDeepchatRoute( + return await dispatcher.appDatabaseMaintenance.runRequest(routeName, () => + runTrackedRouteTask( dispatcher, routeName, - rawInput, - createRendererRouteContext( - event.sender.id, - BrowserWindow.fromWebContents(event.sender)?.id ?? null - ) + context, + async () => await registeredRoute(rawInput, context) ) - } - ) + ) + } + + throw new Error(`Unhandled deepchat route: ${routeName}`) } diff --git a/src/main/scheduler/data/database.ts b/src/main/scheduler/data/database.ts index 8988f73749..113d29018c 100644 --- a/src/main/scheduler/data/database.ts +++ b/src/main/scheduler/data/database.ts @@ -3,26 +3,13 @@ import { CronJobDeliveriesTable } from './tables/cronJobDeliveries' import { CronJobsTable } from './tables/cronJobs' import { CronJobRunsTable } from './tables/cronJobRuns' -interface SchedulerDatabaseConnectionProvider extends DatabaseConnectionProvider { - getDatabasePath(): string - getDatabasePassword(): string | undefined -} - export class SchedulerDatabase { - constructor(private readonly connection: SchedulerDatabaseConnectionProvider) {} + constructor(private readonly connection: DatabaseConnectionProvider) {} getDatabase() { return this.connection.getDatabase() } - getDatabasePath(): string { - return this.connection.getDatabasePath() - } - - getDatabasePassword(): string | undefined { - return this.connection.getDatabasePassword() - } - get cronJobsTable(): CronJobsTable { return new CronJobsTable(this.getDatabase()) } diff --git a/src/main/scheduler/index.ts b/src/main/scheduler/index.ts index 4d330d8912..09753829b0 100644 --- a/src/main/scheduler/index.ts +++ b/src/main/scheduler/index.ts @@ -1,4 +1,3 @@ -import type { PowerMonitor } from 'electron' import { CRON_JOBS_DEFAULT_DELIVERY as DEFAULT_DELIVERY, CRON_JOBS_DEFAULT_RUNTIME as DEFAULT_RUNTIME, @@ -21,10 +20,10 @@ import { CronJobDeliveryRouter, type CronJobRemoteDeliveryPort } from './deliver import { CronJobsRepository } from './repository' import { CronJobRunExecutor, type CronJobRunSessionStarter } from './runExecutor' import { CronJobRuntimeResolver } from './runtimeResolver' -import { +import type { SchedulerProcessManager, - type SchedulerProcessManagerDeps, - type SchedulerRunDueEvent + SchedulerProcessManagerDeps, + SchedulerRunDueEvent } from './schedulerProcessManager' export type CronJobsUpsertInput = z.input @@ -38,14 +37,12 @@ export interface SchedulerServiceDeps { remoteDeliveryPort: CronJobRemoteDeliveryPort sessionEvents: SessionRuntimeEventPort agentSettings: Pick - schedulerManager?: SchedulerProcessManager scheduleService?: CronExpressionService runtimeResolver?: CronJobRuntimeResolver deliveryRouter?: CronJobDeliveryRouter - createSchedulerManager?: ( + createSchedulerManager: ( deps: Omit ) => SchedulerProcessManager - powerMonitor?: Pick } export class SchedulerService { @@ -56,10 +53,7 @@ export class SchedulerService { private readonly runtimeResolver: CronJobRuntimeResolver private readonly runExecutor: CronJobRunExecutor private started = false - private powerMonitor: Pick | null = null - private readonly resumeHandler = () => { - void this.reconcileScheduler('system-resume') - } + private lifecycleGeneration = 0 constructor(deps: SchedulerServiceDeps) { this.repository = new CronJobsRepository(deps.database) @@ -74,18 +68,13 @@ export class SchedulerService { deps.sessionEvents ) const managerDeps: Omit = { - dbPath: deps.database.getDatabasePath(), - dbPassword: deps.database.getDatabasePassword(), getSnapshot: () => this.repository.getSchedulerSnapshot(), + queueDueRuns: () => this.repository.queueDueRuns(), onRunDue: async (event) => { await this.processDueRun(event) } } - this.schedulerManager = - deps.schedulerManager ?? - deps.createSchedulerManager?.(managerDeps) ?? - new SchedulerProcessManager(managerDeps) - this.powerMonitor = deps.powerMonitor ?? null + this.schedulerManager = deps.createSchedulerManager(managerDeps) } start(): void { @@ -93,20 +82,25 @@ export class SchedulerService { return } this.started = true + this.lifecycleGeneration++ this.failStaleRunningRuns() - void this.attachPowerMonitor() void this.reconcileScheduler('startup') } async stop(): Promise { this.started = false - if (this.powerMonitor) { - this.powerMonitor.off('resume', this.resumeHandler) - this.powerMonitor = null - } + this.lifecycleGeneration++ await this.schedulerManager.stop('app-quit') } + async handleSystemResume(): Promise { + if (!this.started) return + const generation = this.lifecycleGeneration + await this.reconcileStoredSchedules(Date.now(), generation) + if (!this.started || generation !== this.lifecycleGeneration) return + await this.schedulerManager.reconcile('system-resume') + } + async destroy(): Promise { await this.stop() this.runExecutor.dispose() @@ -250,23 +244,6 @@ export class SchedulerService { return this.scheduleService.preview(input.cronExpr, input.timezone, input.count, input.from) } - private async attachPowerMonitor(): Promise { - if (this.powerMonitor) { - this.powerMonitor.off('resume', this.resumeHandler) - this.powerMonitor.on('resume', this.resumeHandler) - return - } - - try { - const { powerMonitor } = await import('electron') - this.powerMonitor = powerMonitor - powerMonitor.off('resume', this.resumeHandler) - powerMonitor.on('resume', this.resumeHandler) - } catch (error) { - console.warn('[CronJobs] Failed to attach power monitor resume handler:', error) - } - } - private failStaleRunningRuns(): void { const failedCount = this.repository.markRunningRunsFailed( 'Cron job runner stopped before completion.' @@ -406,9 +383,14 @@ export class SchedulerService { } } - private async reconcileStoredSchedules(now: number): Promise { + private async reconcileStoredSchedules(now: number, resumeGeneration?: number): Promise { for (const job of this.repository.listJobs()) { const state = await this.computeJobState(job, now, false) + if ( + resumeGeneration !== undefined && + (!this.started || resumeGeneration !== this.lifecycleGeneration) + ) + return if ( job.enabled === state.enabled && job.status === state.status && diff --git a/src/main/scheduler/repository.ts b/src/main/scheduler/repository.ts index 99626dc7e5..dd92fd6d2a 100644 --- a/src/main/scheduler/repository.ts +++ b/src/main/scheduler/repository.ts @@ -16,6 +16,8 @@ import type { SchedulerDatabase } from './data/database' import type { CronJobDeliveryRow } from './data/tables/cronJobDeliveries' import type { CronJobRow } from './data/tables/cronJobs' import type { CronJobRunRow } from './data/tables/cronJobRuns' +import { randomUUID } from 'node:crypto' +import { CronExpressionService } from './cronExpressionService' export type CronJobUpsertInput = z.input & { now?: number @@ -26,7 +28,16 @@ export interface CronJobsSchedulerSnapshot { nextRunAt: number | null } +export interface SchedulerRunDueEvent { + jobId: string + runId: string + scheduledAt: number + reason: CronJobRunReason +} + export class CronJobsRepository { + private readonly scheduleService = new CronExpressionService() + constructor(private readonly database: SchedulerDatabase) {} listJobs(): CronJob[] { @@ -108,6 +119,59 @@ export class CronJobsRepository { } } + queueDueRuns(now = Date.now()): SchedulerRunDueEvent[] { + const db = this.database.getDatabase() + return db.transaction(() => { + const rows = db + .prepare( + `SELECT id, cron_expr, timezone, next_run_at, misfire_policy, max_catch_up_runs + FROM cron_jobs + WHERE enabled = 1 AND status = 'ready' AND agent_id IS NOT NULL + AND task_prompt != '' AND next_run_at IS NOT NULL AND next_run_at <= ? + ORDER BY next_run_at ASC, id ASC + LIMIT 100` + ) + .all(now) as Pick< + CronJobRow, + 'id' | 'cron_expr' | 'timezone' | 'next_run_at' | 'misfire_policy' | 'max_catch_up_runs' + >[] + return rows.flatMap((row) => { + const reconciliation = this.scheduleService.reconcileDueRun( + { + cronExpr: row.cron_expr, + timezone: row.timezone, + misfirePolicy: row.misfire_policy, + maxCatchUpRuns: row.max_catch_up_runs + }, + row.next_run_at!, + now + ) + const events: SchedulerRunDueEvent[] = [] + for (const scheduledAt of reconciliation.scheduledAts) { + const runId = randomUUID() + const result = db + .prepare( + `INSERT OR IGNORE INTO cron_job_runs ( + id, job_id, scheduled_at, queued_at, started_at, completed_at, + status, reason, error, created_at, updated_at + ) VALUES (?, ?, ?, ?, NULL, NULL, 'queued', 'scheduled', NULL, ?, ?)` + ) + .run(runId, row.id, scheduledAt, now, now, now) + if (result.changes > 0) + events.push({ jobId: row.id, runId, scheduledAt, reason: 'scheduled' }) + } + const updated = db + .prepare( + `UPDATE cron_jobs SET next_run_at = ?, schedule_error = ?, updated_at = ? + WHERE id = ? AND next_run_at = ?` + ) + .run(reconciliation.nextRunAt, reconciliation.error, now, row.id, row.next_run_at) + if (updated.changes !== 1) throw new Error('Cron schedule changed during due-run claim.') + return events + }) + })() + } + queueRun(input: { jobId: string scheduledAt: number diff --git a/src/main/scheduler/schedulerProcessManager.ts b/src/main/scheduler/schedulerProcessManager.ts index 79ef4663fb..fa7a672ec6 100644 --- a/src/main/scheduler/schedulerProcessManager.ts +++ b/src/main/scheduler/schedulerProcessManager.ts @@ -1,36 +1,24 @@ -import fs from 'fs' -import path from 'path' -import { fileURLToPath } from 'url' -import type { UtilityProcess } from 'electron' import type { CronJobsSchedulerStatus, SchedulerCommand, SchedulerEvent } from '@shared/cronJobs' -import type { CronJobsSchedulerSnapshot } from './repository' - -type SchedulerUtilityProcess = Pick & { - on(event: 'message', listener: (message: unknown) => void): SchedulerUtilityProcess - on(event: 'exit', listener: (code: number) => void): SchedulerUtilityProcess - on(event: 'error', listener: (type: string, location: string) => void): SchedulerUtilityProcess - once(event: 'spawn', listener: () => void): SchedulerUtilityProcess - once(event: 'exit', listener: (code: number) => void): SchedulerUtilityProcess - off(event: 'spawn', listener: () => void): SchedulerUtilityProcess - off(event: 'exit', listener: (code: number) => void): SchedulerUtilityProcess -} - -export interface SchedulerRunDueEvent { - jobId: string - runId: string - scheduledAt: number - reason: 'scheduled' | 'manual' +import type { CronJobsSchedulerSnapshot, SchedulerRunDueEvent } from './repository' +export type { SchedulerRunDueEvent } from './repository' + +export interface SchedulerHost { + readonly pid: number | undefined + postMessage(command: SchedulerCommand): void + kill(): void + onMessage(listener: (message: unknown) => void): void + onExit(listener: (code: number) => void): void + onError(listener: (type: string, location: string) => void): void } export interface SchedulerProcessManagerDeps { - dbPath: string - dbPassword?: string getSnapshot: () => CronJobsSchedulerSnapshot + queueDueRuns: () => SchedulerRunDueEvent[] onRunDue: (event: SchedulerRunDueEvent) => Promise | void idleShutdownMs?: number maxRestartAttempts?: number restartDelayMs?: number - spawnHost?: () => Promise + spawnHost: (signal: AbortSignal) => Promise } const DEFAULT_IDLE_SHUTDOWN_MS = 30_000 @@ -38,14 +26,17 @@ const DEFAULT_RESTART_DELAY_MS = 1_000 const DEFAULT_MAX_RESTART_ATTEMPTS = 5 export class SchedulerProcessManager { - private host: SchedulerUtilityProcess | null = null - private hostReady: Promise | null = null + private host: SchedulerHost | null = null + private hostReady: Promise | null = null + private pendingSpawn: AbortController | null = null private readonly idleShutdownMs: number private readonly restartDelayMs: number private readonly maxRestartAttempts: number private idleStopTimer: NodeJS.Timeout | null = null private restartTimer: NodeJS.Timeout | null = null private shuttingDown = false + private lastScanRequestId = 0 + private hostEpoch = 0 private status: CronJobsSchedulerStatus = { state: 'stopped', pid: null, @@ -94,6 +85,8 @@ export class SchedulerProcessManager { this.cancelIdleStop() const host = await this.ensureHost() + if (this.host !== host || this.shuttingDown) + throw new Error('Scheduler utility spawn was superseded.') host.postMessage({ type: 'RECONCILE', reason, @@ -111,8 +104,11 @@ export class SchedulerProcessManager { async stop(reason = 'manual'): Promise { this.shuttingDown = true + this.hostEpoch++ this.cancelIdleStop() this.cancelRestart() + this.pendingSpawn?.abort() + this.pendingSpawn = null const host = this.host this.host = null @@ -138,7 +134,7 @@ export class SchedulerProcessManager { return this.getStatus() } - private async ensureHost(): Promise { + private async ensureHost(): Promise { if (this.host) { return this.host } @@ -148,23 +144,33 @@ export class SchedulerProcessManager { this.shuttingDown = false this.updateStatus({ state: 'starting', lastError: null }) - this.hostReady = this.spawnHost() + const epoch = ++this.hostEpoch + const pendingSpawn = new AbortController() + this.pendingSpawn = pendingSpawn + const ready = this.spawnHost(epoch, pendingSpawn.signal) + this.hostReady = ready try { - return await this.hostReady + return await ready } catch (error) { const message = error instanceof Error ? error.message : String(error) - this.updateStatus({ state: 'error', pid: null, lastError: message }) + if (this.hostEpoch === epoch) + this.updateStatus({ state: 'error', pid: null, lastError: message }) throw error } finally { - this.hostReady = null + if (this.hostReady === ready) this.hostReady = null + if (this.pendingSpawn === pendingSpawn) this.pendingSpawn = null } } - private async spawnHost(): Promise { - const host = this.deps.spawnHost ? await this.deps.spawnHost() : await this.spawnDefaultHost() - host.on('message', (message) => this.handleHostMessage(host, message)) - host.on('exit', (code) => this.handleHostExit(host, code)) - host.on('error', (type, location) => { + private async spawnHost(epoch: number, signal: AbortSignal): Promise { + const host = await this.deps.spawnHost(signal) + if (this.hostEpoch !== epoch || this.shuttingDown) { + host.kill() + throw new Error('Scheduler utility spawn was superseded.') + } + host.onMessage((message) => this.handleHostMessage(host, message)) + host.onExit((code) => this.handleHostExit(host, code)) + host.onError((type, location) => { if (this.host !== host) { return } @@ -174,6 +180,7 @@ export class SchedulerProcessManager { }) this.host = host + this.lastScanRequestId = 0 this.updateStatus({ state: 'running', pid: host.pid ?? null, @@ -186,64 +193,8 @@ export class SchedulerProcessManager { return host } - private async spawnDefaultHost(): Promise { - const { app, utilityProcess } = await import('electron') - const modulePath = this.resolveUtilityHostEntryPoint(app.getAppPath()) - const host = utilityProcess.fork(modulePath, ['--deepchat-cron-scheduler-host'], { - serviceName: 'DeepChat Cron Jobs Scheduler', - stdio: 'ignore', - env: { - ...process.env, - DEEPCHAT_CRON_SCHEDULER_HOST: '1', - DEEPCHAT_CRON_SCHEDULER_DB_PATH: this.deps.dbPath, - ...(this.deps.dbPassword - ? { DEEPCHAT_CRON_SCHEDULER_DB_PASSWORD: this.deps.dbPassword } - : {}) - } - }) as SchedulerUtilityProcess - - return await new Promise((resolve, reject) => { - let settled = false - const settle = (callback: () => void) => { - if (settled) { - return - } - settled = true - host.off('spawn', onSpawn) - host.off('exit', onExit) - callback() - } - const onSpawn = () => { - settle(() => resolve(host)) - } - const onExit = (code: number) => { - settle(() => reject(new Error(`Cron scheduler utility exited before spawn: ${code}`))) - } - - host.once('spawn', onSpawn) - host.once('exit', onExit) - }) - } - - private resolveUtilityHostEntryPoint(appPath?: string): string { - const modulePath = fileURLToPath(import.meta.url) - const candidates = [ - ...(appPath - ? [ - path.join(appPath, 'out/main/schedulerUtilityHost.js'), - path.join(appPath, 'schedulerUtilityHost.js') - ] - : []), - path.resolve(path.dirname(modulePath), 'schedulerUtilityHost.js'), - path.resolve(path.dirname(modulePath), '../schedulerUtilityHost.js'), - path.resolve(process.cwd(), 'out/main/schedulerUtilityHost.js') - ] - - return candidates.find((candidate) => fs.existsSync(candidate)) ?? candidates[0] - } - - private handleHostMessage(host: SchedulerUtilityProcess, message: unknown): void { - if (this.host !== host) { + private handleHostMessage(host: SchedulerHost, message: unknown): void { + if (this.host !== host || this.shuttingDown) { return } const event = unwrapSchedulerEvent(message) @@ -260,44 +211,38 @@ export class SchedulerProcessManager { lastError: null }) return - case 'HEARTBEAT': + case 'HEARTBEAT': { + const snapshot = this.deps.getSnapshot() this.updateStatus({ state: 'running', - enabledJobCount: event.enabledJobCount, - nextRunAt: event.nextRunAt, + ...snapshot, lastHeartbeatAt: event.now, lastError: null }) - if (event.enabledJobCount === 0) { + if (snapshot.enabledJobCount === 0) { this.scheduleIdleStop() - } + } else this.cancelIdleStop() return - case 'IDLE': + } + case 'IDLE': { + const snapshot = this.deps.getSnapshot() this.updateStatus({ - state: 'idle', - enabledJobCount: event.enabledJobCount, - nextRunAt: event.nextRunAt, + state: snapshot.enabledJobCount === 0 ? 'idle' : 'running', + ...snapshot, lastHeartbeatAt: event.now }) - this.scheduleIdleStop() + if (snapshot.enabledJobCount === 0) this.scheduleIdleStop() + else this.cancelIdleStop() return - case 'RUN_DUE': - console.info('[CronJobs] Scheduler reported due run:', { - jobId: event.jobId, - runId: event.runId, - scheduledAt: event.scheduledAt, - reason: event.reason - }) - void Promise.resolve( - this.deps.onRunDue({ - jobId: event.jobId, - runId: event.runId, - scheduledAt: event.scheduledAt, - reason: event.reason - }) - ).catch((error) => { - console.error('[CronJobs] Failed to process due run:', error) - }) + } + case 'SCAN_DUE': + if (event.requestId !== this.lastScanRequestId + 1) { + this.updateStatus({ state: 'error', lastError: 'Invalid scheduler scan sequence.' }) + host.kill() + return + } + this.lastScanRequestId = event.requestId + this.scanDueRuns(host, event.requestId) return case 'ERROR': console.error('[CronJobs] Scheduler utility error:', event.message) @@ -306,11 +251,45 @@ export class SchedulerProcessManager { lastError: event.message, lastHeartbeatAt: event.now }) + if (event.fatal) host.kill() return } } - private handleHostExit(host: SchedulerUtilityProcess, code: number): void { + private scanDueRuns(host: SchedulerHost, requestId: number): void { + let error: string | undefined + try { + const events = this.deps.queueDueRuns() + for (const event of events) { + // Dispatch committed runs at the owner even if the timer reply is lost. + try { + void Promise.resolve(this.deps.onRunDue(event)).catch((error) => { + console.error('[CronJobs] Failed to process due run:', error) + }) + } catch (error) { + console.error('[CronJobs] Failed to process due run:', error) + } + } + } catch (cause) { + error = (cause instanceof Error ? cause.message : String(cause)).slice(0, 4096) + this.updateStatus({ state: 'error', lastError: error }) + } + if (this.host !== host || this.shuttingDown) return + try { + host.postMessage({ + type: 'SCAN_RESULT', + requestId, + ...this.deps.getSnapshot(), + ...(error !== undefined ? { error } : {}), + now: Date.now() + } satisfies SchedulerCommand) + } catch (cause) { + console.error('[CronJobs] Failed to reply to scheduler scan:', cause) + host.kill() + } + } + + private handleHostExit(host: SchedulerHost, code: number): void { if (this.host !== host) { return } @@ -403,16 +382,36 @@ function unwrapSchedulerEvent(message: unknown): SchedulerEvent | null { return null } - const type = (payload as { type?: unknown }).type - if ( - type === 'READY' || - type === 'HEARTBEAT' || - type === 'RUN_DUE' || - type === 'IDLE' || - type === 'ERROR' - ) { - return payload as SchedulerEvent + const event = payload as Record + const nonnegativeInteger = (value: unknown): value is number => + typeof value === 'number' && Number.isSafeInteger(value) && value >= 0 + if (!nonnegativeInteger(event.now)) return null + switch (event.type) { + case 'READY': + if (event.pid !== null && (!nonnegativeInteger(event.pid) || event.pid === 0)) return null + break + case 'HEARTBEAT': + case 'IDLE': + if ( + !nonnegativeInteger(event.enabledJobCount) || + (event.nextRunAt !== null && !nonnegativeInteger(event.nextRunAt)) + ) + return null + break + case 'SCAN_DUE': + if (!nonnegativeInteger(event.requestId) || event.requestId === 0) return null + break + case 'ERROR': + if ( + typeof event.message !== 'string' || + event.message.length > 4096 || + (event.fatal !== undefined && typeof event.fatal !== 'boolean') || + (event.stack !== undefined && typeof event.stack !== 'string') + ) + return null + break + default: + return null } - - return null + return event as unknown as SchedulerEvent } diff --git a/src/main/scheduler/schedulerUtilityHost.ts b/src/main/scheduler/schedulerUtilityHost.ts index a14f2a88ce..def72beb22 100644 --- a/src/main/scheduler/schedulerUtilityHost.ts +++ b/src/main/scheduler/schedulerUtilityHost.ts @@ -1,18 +1,8 @@ -import { randomUUID } from 'node:crypto' -import type Database from 'better-sqlite3-multiple-ciphers' -import { openSQLiteDatabase } from '@/data/databaseConnection' -import type { - CronJobMisfirePolicy, - CronJobRunReason, - SchedulerCommand, - SchedulerEvent -} from '@shared/cronJobs' -import { CronExpressionService } from './cronExpressionService' +import type { SchedulerCommand, SchedulerEvent } from '@shared/cronJobs' const SCHEDULER_HOST_ARG = '--deepchat-cron-scheduler-host' const HEARTBEAT_INTERVAL_MS = 5_000 const MAX_SCAN_DELAY_MS = 30_000 -const DUE_SCAN_LIMIT = 100 type ParentPort = { postMessage(message: unknown): void @@ -20,430 +10,145 @@ type ParentPort = { start?(): void } -type ParentPortMessageEvent = { - data?: unknown -} - -interface DueCronJobRow { - id: string - cron_expr: string - timezone: string - next_run_at: number - misfire_policy: CronJobMisfirePolicy - max_catch_up_runs: number | null -} - -interface SchedulerSnapshot { - enabledJobCount: number - nextRunAt: number | null -} - -function getParentPort(): ParentPort | null { - const maybeProcess = process as NodeJS.Process & { - parentPort?: ParentPort - } - return maybeProcess.parentPort ?? null -} +type SchedulerSnapshot = { enabledJobCount: number; nextRunAt: number | null } -function isSchedulerHostRequest(): boolean { +function isSchedulerCommand(message: unknown): message is SchedulerCommand { + if (!message || typeof message !== 'object' || !('type' in message)) return false + if (message.type === 'START' || message.type === 'STOP' || message.type === 'RECONCILE') + return true + if (message.type !== 'SCAN_RESULT') return false + const result = message as Extract return ( - process.env.DEEPCHAT_CRON_SCHEDULER_HOST === '1' || process.argv.includes(SCHEDULER_HOST_ARG) + Number.isSafeInteger(result.requestId) && + result.requestId > 0 && + Number.isSafeInteger(result.enabledJobCount) && + result.enabledJobCount >= 0 && + (result.nextRunAt === null || + (Number.isSafeInteger(result.nextRunAt) && result.nextRunAt >= 0)) && + (result.error === undefined || typeof result.error === 'string') ) } -function getParentPortMessagePayload(message: unknown): unknown { - if (isSchedulerCommand(message)) { - return message - } - - if (message && typeof message === 'object' && 'data' in message) { - return (message as ParentPortMessageEvent).data - } - - return message -} - -function isSchedulerCommand(message: unknown): message is SchedulerCommand { - if (!message || typeof message !== 'object') { - return false - } - - const type = (message as { type?: unknown }).type - return type === 'START' || type === 'RECONCILE' || type === 'RUN_NOW' || type === 'STOP' -} - -function serializeError(error: unknown): { message: string; stack?: string } { - if (error instanceof Error) { - return { - message: error.message, - stack: error.stack - } - } - return { message: String(error) } -} - export class CronJobsSchedulerUtilityHost { - private db: Database.Database | null = null private heartbeatTimer: NodeJS.Timeout | null = null private scanTimer: NodeJS.Timeout | null = null + private replyTimer: NodeJS.Timeout | null = null private shuttingDown = false - private readonly scheduleService = new CronExpressionService() + private requestId = 0 + private pendingRequestId: number | null = null + private reconcileRequested = false + private snapshot: SchedulerSnapshot = { enabledJobCount: 0, nextRunAt: null } - constructor( - private readonly options: { - dbPath: string - dbPassword?: string - postMessage: (message: SchedulerEvent) => void - } - ) {} + constructor(private readonly options: { postMessage: (message: SchedulerEvent) => void }) {} start(): void { - if (this.shuttingDown) { - return - } - try { - this.openDatabase() - this.send({ - type: 'READY', - pid: process.pid || null, - now: Date.now() - }) - this.startHeartbeat() - this.scanDueRuns() - } catch (error) { - this.reportError(error) - } + if (this.shuttingDown || this.heartbeatTimer) return + this.send({ type: 'READY', pid: process.pid || null, now: Date.now() }) + this.heartbeatTimer = setInterval(() => this.sendHeartbeat(), HEARTBEAT_INTERVAL_MS) + this.reconcile() } reconcile(): void { - try { - this.openDatabase() - this.scanDueRuns() - } catch (error) { - this.reportError(error) + if (this.shuttingDown) return + this.clearScanTimer() + if (this.pendingRequestId !== null) { + this.reconcileRequested = true + return } - } - - runNow(jobId: string, now = Date.now()): void { - try { - this.openDatabase() - const db = this.requireDatabase() - const job = db.prepare('SELECT id FROM cron_jobs WHERE id = ?').get(jobId) as - | { id: string } - | undefined - if (!job) { - return - } - - const run = this.queueRun(job.id, now, 'manual') - if (!run) { - return - } + const requestId = ++this.requestId + this.pendingRequestId = requestId + this.replyTimer = setTimeout(() => { + this.shutdown() this.send({ - type: 'RUN_DUE', - jobId: job.id, - runId: run.runId, - scheduledAt: now, - reason: 'manual', + type: 'ERROR', + message: 'Scheduler Store scan timed out.', + fatal: true, now: Date.now() }) - this.scheduleNextScan() - } catch (error) { - this.reportError(error) - } + }, MAX_SCAN_DELAY_MS) + this.send({ type: 'SCAN_DUE', requestId, now: Date.now() }) } - shutdown(): void { - this.shuttingDown = true - this.clearHeartbeat() - this.clearScanTimer() - if (this.db) { - try { - this.db.close() - } catch {} - this.db = null - } - } - - private openDatabase(): void { - if (this.db) { - return - } - this.db = openSQLiteDatabase(this.options.dbPath, this.options.dbPassword) - } - - private requireDatabase(): Database.Database { - if (!this.db) { - throw new Error('Cron scheduler database is not open.') - } - return this.db - } - - private startHeartbeat(): void { - this.clearHeartbeat() + applyScanResult(result: Extract): void { + if (this.shuttingDown || result.requestId !== this.pendingRequestId) return + if (this.replyTimer) clearTimeout(this.replyTimer) + this.replyTimer = null + this.pendingRequestId = null + this.snapshot = { enabledJobCount: result.enabledJobCount, nextRunAt: result.nextRunAt } this.sendHeartbeat() - this.heartbeatTimer = setInterval(() => { - this.sendHeartbeat() - }, HEARTBEAT_INTERVAL_MS) - } - - private clearHeartbeat(): void { - if (!this.heartbeatTimer) { + if (result.error !== undefined) { + this.reconcileRequested = false + this.send({ type: 'ERROR', message: result.error, now: Date.now() }) + this.scheduleScan(MAX_SCAN_DELAY_MS) return } - clearInterval(this.heartbeatTimer) - this.heartbeatTimer = null - } - - private scanDueRuns(): void { - if (this.shuttingDown) { + if (this.reconcileRequested) { + this.reconcileRequested = false + this.reconcile() return } - - try { - const db = this.requireDatabase() - const now = Date.now() - const dueRows = db - .prepare( - `SELECT id, - cron_expr, - timezone, - next_run_at, - misfire_policy, - max_catch_up_runs - FROM cron_jobs - WHERE enabled = 1 - AND status = 'ready' - AND agent_id IS NOT NULL - AND task_prompt != '' - AND next_run_at IS NOT NULL - AND next_run_at <= ? - ORDER BY next_run_at ASC, id ASC - LIMIT ?` - ) - .all(now, DUE_SCAN_LIMIT) as DueCronJobRow[] - - const dueEvents = db.transaction((rows: DueCronJobRow[]) => - rows.flatMap((row) => { - const reconciliation = this.scheduleService.reconcileDueRun( - { - cronExpr: row.cron_expr, - timezone: row.timezone, - misfirePolicy: row.misfire_policy, - maxCatchUpRuns: row.max_catch_up_runs - }, - row.next_run_at, - now - ) - - if (reconciliation.error) { - db.prepare( - `UPDATE cron_jobs - SET next_run_at = NULL, - schedule_error = ?, - updated_at = ? - WHERE id = ?` - ).run(reconciliation.error, now, row.id) - return [] - } - - const events = reconciliation.scheduledAts.flatMap((scheduledAt) => { - const run = this.queueRun(row.id, scheduledAt, 'scheduled', true) - return run - ? [ - { - type: 'RUN_DUE' as const, - jobId: row.id, - runId: run.runId, - scheduledAt, - reason: 'scheduled' as const, - now: Date.now() - } - ] - : [] - }) - - db.prepare( - `UPDATE cron_jobs - SET next_run_at = ?, - schedule_error = NULL, - updated_at = ? - WHERE id = ? - AND next_run_at = ?` - ).run(reconciliation.nextRunAt, now, row.id, row.next_run_at) - - return events - }) - )(dueRows) - - for (const event of dueEvents) { - this.send(event) - } - this.sendHeartbeat() - } catch (error) { - this.reportError(error) - } finally { - this.scheduleNextScan() + if (this.snapshot.enabledJobCount === 0) { + this.send({ type: 'IDLE', ...this.snapshot, now: Date.now() }) + return } + this.scheduleScan( + this.snapshot.nextRunAt === null + ? MAX_SCAN_DELAY_MS + : Math.min(Math.max(this.snapshot.nextRunAt - Date.now(), 0), MAX_SCAN_DELAY_MS) + ) } - private queueRun( - jobId: string, - scheduledAt: number, - reason: CronJobRunReason, - ignoreDuplicate = false - ): { runId: string } | null { - const db = this.requireDatabase() - const now = Date.now() - const runId = randomUUID() - const statement = ignoreDuplicate - ? `INSERT OR IGNORE INTO cron_job_runs ( - id, - job_id, - scheduled_at, - queued_at, - started_at, - completed_at, - status, - reason, - error, - created_at, - updated_at - ) - VALUES (?, ?, ?, ?, NULL, NULL, 'queued', ?, NULL, ?, ?)` - : `INSERT INTO cron_job_runs ( - id, - job_id, - scheduled_at, - queued_at, - started_at, - completed_at, - status, - reason, - error, - created_at, - updated_at - ) - VALUES (?, ?, ?, ?, NULL, NULL, 'queued', ?, NULL, ?, ?)` - - const result = db.prepare(statement).run(runId, jobId, scheduledAt, now, reason, now, now) - - return result.changes > 0 ? { runId } : null + shutdown(): void { + this.shuttingDown = true + if (this.heartbeatTimer) clearInterval(this.heartbeatTimer) + if (this.replyTimer) clearTimeout(this.replyTimer) + this.heartbeatTimer = null + this.replyTimer = null + this.pendingRequestId = null + this.clearScanTimer() } - private scheduleNextScan(): void { - if (this.shuttingDown) { - return - } - + private scheduleScan(delay: number): void { this.clearScanTimer() - const snapshot = this.readSnapshot() - if (snapshot.enabledJobCount === 0) { - this.send({ - type: 'IDLE', - enabledJobCount: snapshot.enabledJobCount, - nextRunAt: snapshot.nextRunAt, - now: Date.now() - }) - return - } - - const delay = - snapshot.nextRunAt === null - ? MAX_SCAN_DELAY_MS - : Math.min(Math.max(snapshot.nextRunAt - Date.now(), 0), MAX_SCAN_DELAY_MS) this.scanTimer = setTimeout(() => { this.scanTimer = null - this.scanDueRuns() + this.reconcile() }, delay) } private clearScanTimer(): void { - if (!this.scanTimer) { - return - } - clearTimeout(this.scanTimer) + if (this.scanTimer) clearTimeout(this.scanTimer) this.scanTimer = null } private sendHeartbeat(): void { - try { - const snapshot = this.readSnapshot() - this.send({ - type: 'HEARTBEAT', - enabledJobCount: snapshot.enabledJobCount, - nextRunAt: snapshot.nextRunAt, - now: Date.now() - }) - } catch (error) { - this.reportError(error) - } - } - - private readSnapshot(): SchedulerSnapshot { - const db = this.requireDatabase() - const countRow = db - .prepare('SELECT COUNT(*) AS count FROM cron_jobs WHERE enabled = 1') - .get() as { count: number } | undefined - const nextRow = db - .prepare( - `SELECT MIN(next_run_at) AS next_run_at - FROM cron_jobs - WHERE enabled = 1 - AND next_run_at IS NOT NULL` - ) - .get() as { next_run_at: number | null } | undefined - - return { - enabledJobCount: countRow?.count ?? 0, - nextRunAt: nextRow?.next_run_at ?? null - } + this.send({ type: 'HEARTBEAT', ...this.snapshot, now: Date.now() }) } private send(message: SchedulerEvent): void { this.options.postMessage(message) } - - private reportError(error: unknown): void { - const serialized = serializeError(error) - this.send({ - type: 'ERROR', - message: serialized.message, - stack: serialized.stack, - now: Date.now() - }) - } } export function runSchedulerUtilityHostIfRequested(): boolean { - if (!isSchedulerHostRequest()) { + if ( + process.env.DEEPCHAT_CRON_SCHEDULER_HOST !== '1' && + !process.argv.includes(SCHEDULER_HOST_ARG) + ) return false - } - - const parentPort = getParentPort() - if (!parentPort) { - throw new Error('Cron scheduler utility host started without a parent port.') - } - - const dbPath = process.env.DEEPCHAT_CRON_SCHEDULER_DB_PATH - if (!dbPath) { - throw new Error('Cron scheduler utility host started without a database path.') - } - + const parentPort = (process as NodeJS.Process & { parentPort?: ParentPort }).parentPort + if (!parentPort) throw new Error('Cron scheduler utility host started without a parent port.') const host = new CronJobsSchedulerUtilityHost({ - dbPath, - dbPassword: process.env.DEEPCHAT_CRON_SCHEDULER_DB_PASSWORD, postMessage: (message) => parentPort.postMessage(message) }) const keepAliveIntervalId = setInterval(() => {}, 2 ** 31 - 1) parentPort.start?.() - parentPort.on('message', (message) => { - const command = getParentPortMessagePayload(message) - if (!isSchedulerCommand(command)) { - return - } - + const command = + message && typeof message === 'object' && 'data' in message + ? (message as { data: unknown }).data + : message + if (!isSchedulerCommand(command)) return switch (command.type) { case 'START': host.start() @@ -451,8 +156,8 @@ export function runSchedulerUtilityHostIfRequested(): boolean { case 'RECONCILE': host.reconcile() return - case 'RUN_NOW': - host.runNow(command.jobId, command.now) + case 'SCAN_RESULT': + host.applyScanResult(command) return case 'STOP': clearInterval(keepAliveIntervalId) @@ -460,11 +165,9 @@ export function runSchedulerUtilityHostIfRequested(): boolean { process.exit(0) } }) - process.once('beforeExit', () => { clearInterval(keepAliveIntervalId) host.shutdown() }) - return true } diff --git a/src/main/session/data/index.ts b/src/main/session/data/index.ts index d7c75ce380..4060cf329e 100644 --- a/src/main/session/data/index.ts +++ b/src/main/session/data/index.ts @@ -53,6 +53,8 @@ export function createSessionDataFromDatabase( }) const transcript = new SessionTranscript(database, tapeStore, tapeStore, tapeStore) const pendingInputStore = new SessionPendingInputStore(database) + const settings = new SessionSettingsStore(database, tapeStore) + const pendingInputs = new SessionPendingInputs(pendingInputStore, transcript, events) const ensureTape = (sessionId: string) => sessionTape.ensureSessionTapeReady(sessionId, transcript) const toTapeAnchor = (row: DeepChatTapeEntryRow) => ({ @@ -119,7 +121,7 @@ export function createSessionDataFromDatabase( return { database, - settings: new SessionSettingsStore(database, tapeStore), + settings, transcript, tape, tapeStore, @@ -127,7 +129,14 @@ export function createSessionDataFromDatabase( tape.getTapeInfo(sessionId) ), programmaticExecutionJournal, - pendingInputs: new SessionPendingInputs(pendingInputStore, transcript, events) + pendingInputs, + clearTranscript(sessionId: string): void { + database.getDatabase().transaction(() => { + pendingInputs.deleteBySession(sessionId) + transcript.deleteBySession(sessionId) + settings.resetTape(sessionId) + })() + } } } diff --git a/src/main/session/routes.ts b/src/main/session/routes.ts index 7a56ebe8f4..c70fb34a02 100644 --- a/src/main/session/routes.ts +++ b/src/main/session/routes.ts @@ -1,4 +1,7 @@ import { + browserFocusContentRoute, + computerUseSetPreviewModeRoute, + computerUseDismissPreviewRoute, chatCancelSubmissionRoute, chatDismissToolInteractionRoute, chatRespondToolInteractionRoute, @@ -6,6 +9,8 @@ import { chatSteerActiveTurnRoute, chatStopStreamRoute, sessionsActivateRoute, + sessionsGetWidgetSnapshotRoute, + sessionsOpenDesktopRoute, sessionsClearMessagesRoute, sessionsCompactRoute, sessionsConvertPendingInputToSteerRoute, @@ -70,7 +75,9 @@ import { sessionsTranslateTextRoute, sessionsUpdateDisabledAgentToolsRoute, sessionsUpdateGenerationSettingsRoute, - sessionsUpdateQueuedInputRoute + sessionsUpdateQueuedInputRoute, + type DeepchatRouteInput, + type DeepchatRouteOutput } from '@shared/contracts/routes' import type { SessionPermissionPort } from '@/session/contracts' import type { UsageStatsService } from '@/session/usageStatsService' @@ -80,11 +87,13 @@ import type { TapeInspectorHeadWatcher } from '@/tape/application/traceInspector import { createRouteMap, requireRendererCaller, - type DeepchatRouteMap + type DeepchatRouteMap, + type RendererRouteCaller } from '@/routes/routeRegistry' import type { Scheduler } from '@/routes/scheduler' import type { SessionAgentAssignmentPort, SessionLifecyclePort, SessionTurnPort } from './contracts' import type { SessionQuery } from './query' +import type { DesktopSessionBinding } from '@/desktop/sessionBinding' import { SessionService, type SessionServiceDesktopPort, @@ -96,6 +105,7 @@ import type { SessionTranslation } from './sessionTranslation' import type { AgentSettingsPort } from '@/agent/settings' import { SubmissionCancellationRegistry } from './submissionCancellationRegistry' import type { SessionReferences } from './data/sessionReferences' +import { buildFloatingWidgetSnapshot } from './widgetSnapshot' export type SessionRouteProjectionPort = SessionServiceProjectionPort & ChatServiceProjectionPort & @@ -121,7 +131,21 @@ export type SessionRouteProjectionPort = SessionServiceProjectionPort & export function createSessionRoutes(deps: { lifecycle: SessionLifecyclePort projection: SessionRouteProjectionPort - desktop: SessionServiceDesktopPort + desktop: SessionServiceDesktopPort & Pick + openDesktopSession(sessionId: string): Promise + presentation: { + focusBrowser(sessionId: string, caller: RendererRouteCaller): Promise + setComputerUsePreview( + sessionId: string, + mode: DeepchatRouteInput<'computerUse.setPreviewMode'>['mode'], + caller: RendererRouteCaller + ): Promise> + dismissComputerUsePreview( + sessionId: string, + runId: string, + caller: RendererRouteCaller + ): Promise + } turn: SessionTurnPort assignment: SessionAgentAssignmentPort permission: Pick @@ -163,6 +187,84 @@ export function createSessionRoutes(deps: { } return createRouteMap([ + [ + sessionsGetWidgetSnapshotRoute.name, + async (input, context) => { + sessionsGetWidgetSnapshotRoute.input.parse(input) + requireRendererCaller(context) + const [sessions, agents] = await Promise.all([ + deps.projection.listSessions(), + listAvailableAgents(deps.agentSettings) + ]) + return sessionsGetWidgetSnapshotRoute.output.parse({ + snapshot: buildFloatingWidgetSnapshot(sessions, agents, false) + }) + } + ], + [ + sessionsOpenDesktopRoute.name, + async (rawInput, context) => { + const input = sessionsOpenDesktopRoute.input.parse(rawInput) + requireRendererCaller(context) + if (!(await deps.projection.getSession(input.sessionId))) return { opened: false } + return sessionsOpenDesktopRoute.output.parse({ + opened: await deps.openDesktopSession(input.sessionId) + }) + } + ], + [ + browserFocusContentRoute.name, + async (rawInput, context) => { + const input = browserFocusContentRoute.input.parse(rawInput) + const caller = requireRendererCaller(context) + const active = + deps.desktop.getActiveId(caller.webContentsId) === input.sessionId || + deps.lifecycle.ownsDraftSession(input.sessionId, caller.webContentsId) + return browserFocusContentRoute.output.parse({ + focused: + caller.windowId != null && + active && + (await deps.presentation.focusBrowser(input.sessionId, caller)) + }) + } + ], + [ + computerUseSetPreviewModeRoute.name, + async (rawInput, context) => { + const input = computerUseSetPreviewModeRoute.input.parse(rawInput) + const caller = requireRendererCaller(context) + if ( + caller.windowId == null || + (input.mode !== 'stopped' && + deps.desktop.getActiveId(caller.webContentsId) !== input.sessionId) + ) { + return computerUseSetPreviewModeRoute.output.parse({ + updated: false, + surface: 'none' + }) + } + return computerUseSetPreviewModeRoute.output.parse( + await deps.presentation.setComputerUsePreview(input.sessionId, input.mode, caller) + ) + } + ], + [ + computerUseDismissPreviewRoute.name, + async (rawInput, context) => { + const input = computerUseDismissPreviewRoute.input.parse(rawInput) + const caller = requireRendererCaller(context) + const active = deps.desktop.getActiveId(caller.webContentsId) === input.sessionId + return computerUseDismissPreviewRoute.output.parse({ + dismissed: + active && + (await deps.presentation.dismissComputerUsePreview( + input.sessionId, + input.runId, + caller + )) + }) + } + ], [ sessionsCreateDraftRoute.name, async (rawInput, context) => { diff --git a/src/main/session/transcriptMutations.ts b/src/main/session/transcriptMutations.ts index 678430a04e..ba778bf1dc 100644 --- a/src/main/session/transcriptMutations.ts +++ b/src/main/session/transcriptMutations.ts @@ -4,32 +4,28 @@ import type { UserMessageInlineItem } from '@shared/types/agent-interface' import { isSendMessageInputEmpty } from '@/agent/shared/agentSessionNormalization' -import type { SessionPendingInputs } from './data/pendingInputs' -import type { SessionSettingsStore } from './data/settings' import type { SessionTranscript } from './data/transcript' import { buildEditedUserContent, extractUserMessageInput } from './data/userMessageContent' import { parseMessageMetadata } from './usageStats' export interface SessionTranscriptRuntimePort { prepareClearMessages(sessionId: string): Promise - finishClearMessages(sessionId: string): void + finishClearMessages(sessionId: string): Promise prepareRetry( sessionId: string, options?: { allowRestartHeldQueue?: boolean } ): Promise<{ projectDir: string | null }> - assertNoActivePendingInputs(sessionId: string): void + assertNoActivePendingInputs(sessionId: string): Promise cancelForTranscriptMutation(sessionId: string): Promise invalidateTranscriptFrom(sessionId: string, orderSeq: number): void - finishTranscriptTruncate(sessionId: string): void + finishTranscriptTruncate(sessionId: string): Promise resetForkTarget(sessionId: string, clonedMemoryCursorOrderSeq: number): void } export interface SessionTranscriptMutationDependencies { transcript: SessionTranscript - settings: SessionSettingsStore - pendingInputs: SessionPendingInputs runtime: SessionTranscriptRuntimePort - runInTransaction(operation: () => T): T + clearTranscript(sessionId: string): void } export class SessionTranscriptMutations { @@ -37,12 +33,8 @@ export class SessionTranscriptMutations { async clearMessages(sessionId: string): Promise { await this.dependencies.runtime.prepareClearMessages(sessionId) - this.dependencies.runInTransaction(() => { - this.dependencies.pendingInputs.deleteBySession(sessionId) - this.dependencies.transcript.deleteBySession(sessionId) - this.dependencies.settings.resetTape(sessionId) - }) - this.dependencies.runtime.finishClearMessages(sessionId) + this.dependencies.clearTranscript(sessionId) + await this.dependencies.runtime.finishClearMessages(sessionId) } async prepareRetryMessage( @@ -96,20 +88,18 @@ export class SessionTranscriptMutations { } commitRetryMessage(sessionId: string, orderSeq: number): void { - this.dependencies.runInTransaction(() => { - this.dependencies.transcript.deleteFromOrderSeq(sessionId, orderSeq) - }) + this.dependencies.transcript.deleteFromOrderSeq(sessionId, orderSeq) this.dependencies.runtime.invalidateTranscriptFrom(sessionId, orderSeq) } async deleteMessage(sessionId: string, messageId: string): Promise { - this.dependencies.runtime.assertNoActivePendingInputs(sessionId) + await this.dependencies.runtime.assertNoActivePendingInputs(sessionId) const target = this.requireMessage(sessionId, messageId) await this.dependencies.runtime.cancelForTranscriptMutation(sessionId) this.dependencies.runtime.invalidateTranscriptFrom(sessionId, target.orderSeq) this.dependencies.transcript.deleteFromOrderSeq(sessionId, target.orderSeq) - this.dependencies.runtime.finishTranscriptTruncate(sessionId) + await this.dependencies.runtime.finishTranscriptTruncate(sessionId) } async editUserMessage( @@ -118,7 +108,7 @@ export class SessionTranscriptMutations { text: string, inlineItems?: UserMessageInlineItem[] ): Promise { - this.dependencies.runtime.assertNoActivePendingInputs(sessionId) + await this.dependencies.runtime.assertNoActivePendingInputs(sessionId) const target = this.requireMessage(sessionId, messageId) if (target.role !== 'user') throw new Error('Only user messages can be edited.') diff --git a/src/main/session/widgetSnapshot.ts b/src/main/session/widgetSnapshot.ts new file mode 100644 index 0000000000..2259d572f6 --- /dev/null +++ b/src/main/session/widgetSnapshot.ts @@ -0,0 +1,91 @@ +import type { Agent, SessionWithState } from '@shared/types/agent-interface' +import type { + FloatingWidgetSessionAgent, + FloatingWidgetSessionItem, + FloatingWidgetSessionStatus, + FloatingWidgetSnapshot +} from '@shared/types/floating-widget' + +function mapSessionStatus(status: SessionWithState['status']): FloatingWidgetSessionStatus { + switch (status) { + case 'generating': + return 'in_progress' + case 'error': + return 'error' + case 'idle': + default: + return 'done' + } +} + +function getStatusPriority(status: FloatingWidgetSessionStatus): number { + switch (status) { + case 'in_progress': + return 0 + case 'error': + return 1 + case 'done': + default: + return 2 + } +} + +function mapSessionAgent( + session: SessionWithState, + agentsById: Map +): FloatingWidgetSessionAgent { + const matchedAgent = agentsById.get(session.agentId) + if (matchedAgent) { + return matchedAgent + } + + return { + id: session.agentId, + name: session.agentId, + type: session.agentId === 'deepchat' ? 'deepchat' : 'acp' + } +} + +export function buildFloatingWidgetSnapshot( + sessions: SessionWithState[], + agents: Agent[], + expanded: boolean +): FloatingWidgetSnapshot { + const agentsById = new Map( + agents.map((agent) => [ + agent.id, + { + id: agent.id, + name: agent.name, + type: agent.type, + icon: agent.icon, + avatar: agent.avatar + } + ]) + ) + + const mappedSessions: FloatingWidgetSessionItem[] = sessions + .filter((session) => !session.isDraft) + .map((session) => ({ + id: session.id, + title: session.title.trim(), + status: mapSessionStatus(session.status), + updatedAt: session.updatedAt, + agent: mapSessionAgent(session, agentsById) + })) + .sort((left, right) => { + const statusDiff = getStatusPriority(left.status) - getStatusPriority(right.status) + if (statusDiff !== 0) { + return statusDiff + } + return right.updatedAt - left.updatedAt + }) + + const activeCount = mappedSessions.filter((session) => session.status === 'in_progress').length + + return { + expanded, + activeCount, + sessions: mappedSessions + } +} diff --git a/src/main/skill/agentSkillImportService.ts b/src/main/skill/agentSkillImportService.ts index 692bd768cf..d3a3ad86dd 100644 --- a/src/main/skill/agentSkillImportService.ts +++ b/src/main/skill/agentSkillImportService.ts @@ -1,7 +1,6 @@ import * as fs from 'node:fs' import * as path from 'node:path' import { createHash, randomUUID } from 'node:crypto' -import { app } from 'electron' import type { AgentSkillImportPreview, AgentSkillImportPreviewItem, @@ -23,6 +22,7 @@ import { isFilenameSafe } from './sync/security' const IMPORTABLE_SKILL_NAME_PATTERN = /^[a-z0-9][a-z0-9._-]*$/ export interface AgentSkillImportServiceDependencies { + tempDirectory: string skills: Pick external: Pick } @@ -313,7 +313,7 @@ export class AgentSkillImportService { private async materializeCanonicalSkill(item: ResolvedImportItem): Promise { if (!item.canonicalSkill) throw new Error('External Skill could not be converted.') const root = await fs.promises.mkdtemp( - path.join(app.getPath('temp'), `deepchat-agent-skill-import-${randomUUID()}-`) + path.join(this.dependencies.tempDirectory, `deepchat-agent-skill-import-${randomUUID()}-`) ) try { await fs.promises.writeFile( diff --git a/src/main/skill/index.ts b/src/main/skill/index.ts index 7761317be0..6a72028b03 100644 --- a/src/main/skill/index.ts +++ b/src/main/skill/index.ts @@ -1,4 +1,3 @@ -import { app, shell } from 'electron' import path from 'path' import fs from 'fs' import { execFile } from 'node:child_process' @@ -15,7 +14,7 @@ import { type WatcherEventBatch, type WatcherStatus, type WatchHandle -} from '@/platform/fileWatcher' +} from '@/platform/fileWatcher/watcherTypes' import { SkillServicePort, SkillMetadata, @@ -354,6 +353,14 @@ function sanitizeSkillExtensionConfig(input: unknown): SkillExtensionConfig { } } +export interface SkillHostPort { + homeDirectory: string + tempDirectory: string + userDataDirectory: string + builtinSkillsDirectories: readonly string[] + openSkillsDirectory(directory: string): Promise +} + /** * SkillService manages the skills system. * @@ -397,19 +404,20 @@ export class SkillService implements SkillServicePort { private readonly sessionStatePort: SkillSessionStatePort, private readonly watcherService: IFileWatcherService, private readonly publishEvent: DeepchatEventPublisher, + private readonly host: SkillHostPort, private readonly agentScopePort?: SkillAgentScopePort ) { // Skills directory: ~/.deepchat/skills/ this.skillsDir = this.resolveSkillsDir() this.sidecarDir = path.join(this.skillsDir, SKILL_CONFIG.SIDECAR_DIR) - this.draftsRoot = path.join(app.getPath('temp'), SKILL_CONFIG.DRAFT_ROOT_DIR) + this.draftsRoot = path.join(this.host.tempDirectory, SKILL_CONFIG.DRAFT_ROOT_DIR) this.ensureSkillsDir() } private resolveSkillsDir(): string { const configuredPath = this.settings.getPath() const normalized = configuredPath?.trim() - const homePath = app.getPath('home') + const homePath = this.host.homeDirectory const homeDir = homePath ? path.resolve(homePath) : path.resolve('.') const fallbackDir = path.join(homeDir, '.deepchat', 'skills') const resolved = normalized ? path.resolve(normalized) : fallbackDir @@ -3307,7 +3315,7 @@ export class SkillService implements SkillServicePort { } private resolveBuiltinSkillsDir(): string | null { - const candidates = this.getBuiltinSkillsDirCandidates() + const candidates = this.host.builtinSkillsDirectories for (const candidate of candidates) { if (fs.existsSync(candidate)) { return candidate @@ -3316,17 +3324,6 @@ export class SkillService implements SkillServicePort { return null } - private getBuiltinSkillsDirCandidates(): string[] { - if (!app.isPackaged) { - return [path.join(app.getAppPath(), 'resources', 'skills')] - } - return [ - path.join(process.resourcesPath, 'app.asar.unpacked', 'resources', 'skills'), - path.join(process.resourcesPath, 'resources', 'skills'), - path.join(process.resourcesPath, 'skills') - ] - } - /** * Install a skill from a folder path */ @@ -3413,7 +3410,7 @@ export class SkillService implements SkillServicePort { return { success: false, error: 'Zip file not found', errorCode: 'not_found' } } - const tempDir = fs.mkdtempSync(path.join(app.getPath('temp'), 'deepchat-skill-')) + const tempDir = fs.mkdtempSync(path.join(this.host.tempDirectory, 'deepchat-skill-')) try { await extractSkillArchive(zipPath, tempDir, { maxArchiveBytes: SKILL_CONFIG.ZIP_MAX_SIZE @@ -3459,7 +3456,7 @@ export class SkillService implements SkillServicePort { ): Promise { const normalizedAgentId = await this.requireAgentScope(agentId) const finishOperation = this.beginAgentScopeOperation(normalizedAgentId) - const tempZipPath = path.join(app.getPath('temp'), `deepchat-skill-${randomUUID()}.zip`) + const tempZipPath = path.join(this.host.tempDirectory, `deepchat-skill-${randomUUID()}.zip`) try { await downloadSkillArchive(url, tempZipPath, { maxBytes: SKILL_CONFIG.ZIP_MAX_SIZE, @@ -4268,7 +4265,7 @@ export class SkillService implements SkillServicePort { private backupExistingSkill(skillName: string): string { const sourceDir = path.join(this.skillsDir, skillName) const timestamp = new Date().toISOString().replace(/[:.]/g, '-') - const backupRoot = path.join(app.getPath('home'), '.deepchat', 'backups', 'skill-installs') + const backupRoot = path.join(this.host.homeDirectory, '.deepchat', 'backups', 'skill-installs') fs.mkdirSync(backupRoot, { recursive: true }) const backupDir = path.join(backupRoot, `${skillName}-${timestamp}-${randomUUID()}`) fs.renameSync(sourceDir, backupDir) @@ -4347,7 +4344,7 @@ export class SkillService implements SkillServicePort { } private async cloneGitSkillRepo(repoUrl: string): Promise { - const operationRoot = path.join(app.getPath('home'), '.deepchat', 'tmp', 'skill-installs') + const operationRoot = path.join(this.host.homeDirectory, '.deepchat', 'tmp', 'skill-installs') fs.mkdirSync(operationRoot, { recursive: true }) const cloneDir = path.join(operationRoot, `${Date.now()}-${randomUUID()}`) try { @@ -4857,7 +4854,7 @@ export class SkillService implements SkillServicePort { } const skillDir = path.resolve(metadata.skillRoot) - const backupRoot = path.join(app.getPath('home'), '.deepchat', 'backups', 'skill-deletes') + const backupRoot = path.join(this.host.homeDirectory, '.deepchat', 'backups', 'skill-deletes') fs.mkdirSync(backupRoot, { recursive: true }) const backupDir = path.join(backupRoot, `${name}-${Date.now()}-${randomUUID()}`) const sessions = this.agentScopePort ? await this.agentScopePort.listSessions() : [] @@ -5343,7 +5340,7 @@ export class SkillService implements SkillServicePort { const normalizedAgentId = await this.requireAgentScope(agentId) const finishOperation = this.beginAgentScopeOperation(normalizedAgentId) try { - await shell.openPath(this.skillsDir) + await this.host.openSkillsDirectory(this.skillsDir) } finally { finishOperation() } @@ -5379,7 +5376,7 @@ export class SkillService implements SkillServicePort { } const ownerPluginId = this.getStoredManagementState().skills[name]?.ownerPluginId const userPluginRoot = path.join( - app.getPath('userData'), + this.host.userDataDirectory, PLUGIN_INSTALL_DIRECTORY, USER_PLUGIN_INSTALL_DIRECTORY ) diff --git a/src/main/skill/routes.ts b/src/main/skill/routes.ts index 2157705fb7..07b9c0a4cc 100644 --- a/src/main/skill/routes.ts +++ b/src/main/skill/routes.ts @@ -47,6 +47,7 @@ import { import { createRouteMap, type DeepchatRouteMap } from '@/routes/routeRegistry' export function createSkillRoutes(deps: { + tempDirectory: string skillService: SkillServicePort skillSyncService: SkillSyncServicePort skillSettings: SkillSettingsPort @@ -56,6 +57,7 @@ export function createSkillRoutes(deps: { }): DeepchatRouteMap { const { skillService, skillSyncService } = deps const agentSkillImportService = new AgentSkillImportService({ + tempDirectory: deps.tempDirectory, skills: skillService, external: skillSyncService }) diff --git a/src/main/skill/settings.ts b/src/main/skill/settings.ts index 3e4d1bbfaa..9a8253a1e0 100644 --- a/src/main/skill/settings.ts +++ b/src/main/skill/settings.ts @@ -1,4 +1,3 @@ -import { app } from 'electron' import path from 'path' import type { SettingsStore } from '@/config/settingsStore' import type { ScanCache } from '@shared/types/skillSync' @@ -27,7 +26,10 @@ export interface SkillSettingsPort { } export class SkillSettings implements SkillSettingsPort { - constructor(private readonly store: SettingsStore) {} + constructor( + private readonly store: SettingsStore, + private readonly homeDirectory: string + ) {} isEnabled(): boolean { return this.store.get('enableSkills') ?? true @@ -43,7 +45,7 @@ export class SkillSettings implements SkillSettingsPort { getPath(): string { return ( - this.store.get('skillsPath') || path.join(app.getPath('home'), '.deepchat', 'skills') + this.store.get('skillsPath') || path.join(this.homeDirectory, '.deepchat', 'skills') ) } diff --git a/src/main/skill/skillExecutionService.ts b/src/main/skill/skillExecutionService.ts index 93c289b3bb..82f2e26d82 100644 --- a/src/main/skill/skillExecutionService.ts +++ b/src/main/skill/skillExecutionService.ts @@ -12,7 +12,7 @@ import { type SkillRuntimePolicy, type SkillScriptRuntime } from '@shared/types/skill' -import { backgroundExecSessionManager } from '@/agent/shared/process/backgroundExecSessionManager' +import type { BackgroundExecPort } from '@/agent/shared/process/backgroundExecSessionManager' import { mergeCommandEnvironment } from '@/agent/shared/process/shellEnvHelper' import { createUtf8OutputDecoderPair, @@ -56,6 +56,7 @@ export interface SkillRunOptions { } interface SkillExecutionServiceOptions { + backgroundExec: BackgroundExecPort resolveConversationWorkdir?: (conversationId: string) => Promise } @@ -98,9 +99,11 @@ interface ForegroundExecutionResult { } export class SkillExecutionService { + private readonly backgroundExec: BackgroundExecPort private readonly resolveConversationWorkdir?: (conversationId: string) => Promise - constructor(options: SkillExecutionServiceOptions = {}) { + constructor(options: SkillExecutionServiceOptions) { + this.backgroundExec = options.backgroundExec this.resolveConversationWorkdir = options.resolveConversationWorkdir } @@ -153,7 +156,7 @@ export class SkillExecutionService { const displayCommand = plan.shellCommand ?? this.formatDirectInvocation(plan.command, plan.args) ownershipTransferred = true - const result = await backgroundExecSessionManager.start( + const result = await this.backgroundExec.start( options.conversationId, displayCommand, plan.cwd, @@ -182,14 +185,14 @@ export class SkillExecutionService { if (input.stdin !== undefined) { try { - await backgroundExecSessionManager.write( + await this.backgroundExec.write( options.conversationId, result.sessionId, input.stdin, true ) } catch (error) { - await backgroundExecSessionManager + await this.backgroundExec .remove(options.conversationId, result.sessionId) .catch((cleanupError) => { logger.warn('[SkillExecutionService] Failed to remove background session', { diff --git a/src/main/sync/host/index.ts b/src/main/sync/host/index.ts index befdaa7cd9..d2b2b43058 100644 --- a/src/main/sync/host/index.ts +++ b/src/main/sync/host/index.ts @@ -40,8 +40,8 @@ export interface SyncHostServiceDeps { changed?: () => void replica?: SyncReplicaEndpoint resolveCloudflared?: () => string - protectToken?: (token: string) => string - revealToken?: (wrapped: string) => string + protectToken?: (token: string) => Promise + revealToken?: (wrapped: string) => Promise createBackup: () => Promise getFolderPath: () => string getUserDataPath: () => string @@ -73,6 +73,7 @@ export class SyncHostService { private preparationError: string | null = null private lastPreparedAt = 0 private lifecycle: Promise = Promise.resolve() + private generation = 0 /** Identity minted before the first load, so every caller sees the same value. */ private pendingHostId: string | null = null @@ -164,9 +165,11 @@ export class SyncHostService { tunnel?: SyncTunnelConfig & { token?: string } } = {} ): Promise { + const generation = this.generation await this.serialize(async () => { await this.state.load() if (enabled) { + this.assertGeneration(generation) if (!options.consent) throw new Error('sync.tunnel.error.consentRequired') const port = options.port ?? this.state.snapshot().port if (!Number.isInteger(port) || port < 1 || port > 65535) { @@ -198,7 +201,8 @@ export class SyncHostService { } if (options.tunnel?.token?.trim()) { if (!this.deps.protectToken) throw new Error('sync.tunnel.error.credentialsUnavailable') - wrappedToken = this.deps.protectToken(options.tunnel.token.trim()) + wrappedToken = await this.deps.protectToken(options.tunnel.token.trim()) + this.assertGeneration(generation) } if (config.mode === 'named' && !wrappedToken) throw new Error('sync.tunnel.error.tokenRequired') @@ -209,8 +213,9 @@ export class SyncHostService { }) // Start before persisting so a failed bind never leaves host mode marked enabled without a // listener, and roll the listener back if the flag itself cannot be written. - await this.startInternal(port) + await this.startInternal(port, generation) try { + this.assertGeneration(generation) await this.state.update((state) => { state.allowWrites = options.bidirectional === true if (!state.allowWrites) for (const device of state.devices) device.writable = false @@ -235,15 +240,18 @@ export class SyncHostService { } async start(): Promise { - await this.serialize(() => this.startInternal()) + const generation = this.generation + await this.serialize(() => this.startInternal(undefined, generation)) } async stop(): Promise { + this.generation++ await this.serialize(() => this.stopInternal()) } /** Starts the endpoint only when host mode is enabled; safe to call unconditionally at boot. */ async startIfEnabled(): Promise { + const generation = this.generation await this.tunnel?.initialize() await this.initialize() await this.serialize(async () => { @@ -255,7 +263,7 @@ export class SyncHostService { }) return } - await this.startInternal(state.port) + await this.startInternal(state.port, generation) }) } @@ -382,25 +390,36 @@ export class SyncHostService { return this.endpoint.getAuditEntries() } - private async startInternal(port = this.state.snapshot().port): Promise { + private assertGeneration(generation: number): void { + if (generation !== this.generation) throw new Error('sync.tunnel.error.busy') + } + + private async startInternal( + port = this.state.snapshot().port, + generation = this.generation + ): Promise { + this.assertGeneration(generation) if (this.endpoint.isRunning()) { return } this.startError = null try { await this.endpoint.start({ port }) + this.assertGeneration(generation) const state = this.state.snapshot() if (state.tunnel.mode !== 'external') { if (!this.tunnel) throw new Error('sync.tunnel.error.tunnelUnavailable') let token: string | undefined if (state.tunnel.mode === 'named' && state.wrappedTunnelToken) { try { - token = this.deps.revealToken?.(state.wrappedTunnelToken) + token = await this.deps.revealToken?.(state.wrappedTunnelToken) } catch { throw new Error('sync.tunnel.error.credentialsUnavailable') } } + this.assertGeneration(generation) await this.tunnel.start(state.tunnel, port, token) + this.assertGeneration(generation) } } catch (error) { this.startError = diff --git a/src/main/sync/index.ts b/src/main/sync/index.ts index d3828b56af..89cf48171b 100644 --- a/src/main/sync/index.ts +++ b/src/main/sync/index.ts @@ -1,5 +1,4 @@ import { SYNC_PORTABLE_SETTINGS } from '@shared/types/syncPortableSettings' -import { app, shell } from 'electron' import { isNowledgeMachineLocalSetting } from '@shared/types/nowledgeMemPlugin' import path from 'path' import fs from 'fs' @@ -122,22 +121,38 @@ export interface SyncImportResult { importedSessions?: number } +export interface SyncHost { + profileDirectory: string + temporaryDirectory: string + openPath(directory: string): Promise +} + export class SyncService { private isBackingUp = false private currentBackupStatus: BackupStatus = 'idle' - private readonly APP_SETTINGS_PATH = path.join(app.getPath('userData'), 'app-settings.json') - private readonly CUSTOM_PROMPTS_PATH = path.join(app.getPath('userData'), 'custom_prompts.json') - private readonly SYSTEM_PROMPTS_PATH = path.join(app.getPath('userData'), 'system_prompts.json') - private readonly MCP_SETTINGS_PATH = path.join(app.getPath('userData'), 'mcp-settings.json') - private readonly DB_PATH = path.join(app.getPath('userData'), 'app_db', 'agent.db') + private readonly APP_SETTINGS_PATH: string + private readonly CUSTOM_PROMPTS_PATH: string + private readonly SYSTEM_PROMPTS_PATH: string + private readonly MCP_SETTINGS_PATH: string + private readonly DB_PATH: string constructor( private readonly settings: SyncSettings, private readonly database: SyncDatabasePort, private readonly settingsDatabase: SettingsDatabase, private readonly providerDatabase: ProviderDatabase, - private readonly publishEvent: DeepchatEventPublisher - ) {} + private readonly publishEvent: DeepchatEventPublisher, + private readonly host: SyncHost + ) { + if (!path.isAbsolute(host.profileDirectory) || !path.isAbsolute(host.temporaryDirectory)) { + throw new Error('Sync runtime directories must be absolute') + } + this.APP_SETTINGS_PATH = path.join(host.profileDirectory, 'app-settings.json') + this.CUSTOM_PROMPTS_PATH = path.join(host.profileDirectory, 'custom_prompts.json') + this.SYSTEM_PROMPTS_PATH = path.join(host.profileDirectory, 'system_prompts.json') + this.MCP_SETTINGS_PATH = path.join(host.profileDirectory, 'mcp-settings.json') + this.DB_PATH = path.join(host.profileDirectory, 'app_db', 'agent.db') + } public async checkSyncFolder(): Promise<{ exists: boolean; path: string }> { const syncFolderPath = this.settings.getFolderPath() @@ -150,7 +165,8 @@ export class SyncService { if (!exists) { fs.mkdirSync(syncFolderPath, { recursive: true }) } - shell.openPath(syncFolderPath) + const error = await this.host.openPath(syncFolderPath) + if (error) throw new Error(error) } public isBackupInProgress(): boolean { @@ -164,8 +180,8 @@ export class SyncService { // === Cloud sync (S3-compatible) === - private buildCloudService(): CloudStorageService { - const resolved = this.settings.getResolvedCloudConfig() + private async buildCloudService(): Promise { + const resolved = await this.settings.getResolvedCloudConfig() if (!resolved) { throw new Error('sync.error.cloudNotConfigured') } @@ -206,7 +222,7 @@ export class SyncService { public async testCloudConnection(): Promise { try { - const service = this.buildCloudService() + const service = await this.buildCloudService() await service.testConnection() return { success: true, message: 'sync.success.cloudConnected' } } catch (error) { @@ -217,7 +233,7 @@ export class SyncService { public async uploadLatestBackupToCloud(): Promise { try { - const service = this.buildCloudService() + const service = await this.buildCloudService() const backups = (await this.listBackups()).filter(({ fileName }) => BACKUP_FILE_NAME_REGEX.test(fileName) ) @@ -251,7 +267,7 @@ export class SyncService { public async downloadLatestBackupFromCloud(): Promise { try { - const service = this.buildCloudService() + const service = await this.buildCloudService() const { path: syncFolderPath } = await this.checkSyncFolder() const backupsDir = this.getBackupsDirectory(syncFolderPath) const fileName = await service.downloadLatest(backupsDir) @@ -356,7 +372,7 @@ export class SyncService { version: Date.now() }) - const extractionDir = path.join(app.getPath('temp'), `deepchat-backup-${Date.now()}`) + const extractionDir = path.join(this.host.temporaryDirectory, `deepchat-backup-${Date.now()}`) fs.mkdirSync(extractionDir, { recursive: true }) const tempCurrentFiles: Record = { @@ -577,7 +593,7 @@ export class SyncService { const active = this.database.getDatabase() if (!active.prepare("SELECT 1 FROM sqlite_master WHERE name='_sync_state'").get()) return const temporary = await fs.promises.mkdtemp( - path.join(app.getPath('temp'), 'deepchat-sync-backup-') + path.join(this.host.temporaryDirectory, 'deepchat-sync-backup-') ) const file = path.join(temporary, 'agent.db') try { @@ -1069,7 +1085,10 @@ export class SyncService { } private async validateBackupArchive(backupZipPath: string): Promise { - const extractionDir = path.join(app.getPath('temp'), `deepchat-backup-validate-${Date.now()}`) + const extractionDir = path.join( + this.host.temporaryDirectory, + `deepchat-backup-validate-${Date.now()}` + ) fs.mkdirSync(extractionDir, { recursive: true }) try { @@ -1168,7 +1187,7 @@ export class SyncService { } private createTempBackup(originalPath: string, name: string): string | null { - const tempPath = path.join(app.getPath('temp'), `${name}.${Date.now()}.bak`) + const tempPath = path.join(this.host.temporaryDirectory, `${name}.${Date.now()}.bak`) try { this.copyFile(originalPath, tempPath) } catch (error) { diff --git a/src/main/sync/peer/index.ts b/src/main/sync/peer/index.ts index 293155e026..bcfa73a204 100644 --- a/src/main/sync/peer/index.ts +++ b/src/main/sync/peer/index.ts @@ -36,8 +36,8 @@ type Pairing = z.infer export interface SyncPeerDeps { directory: string replicaId?(): string - protectToken(token: string): string - revealToken(wrapped: string): string + protectToken(token: string): Promise + revealToken(wrapped: string): Promise isLocalDatabaseEncrypted(): boolean importSnapshot(filePath: string, mode: 'increment' | 'overwrite'): Promise fetch?: typeof fetch @@ -57,6 +57,7 @@ export class SyncPeerService { private pairingBusy = false private stopping = false private cancelled = false + private credentialGeneration = 0 private progress: Pick = { phase: 'idle', received: 0, @@ -81,7 +82,8 @@ export class SyncPeerService { } } - private async writePrivate(name: string, value: unknown): Promise { + private async writePrivate(name: string, value: unknown, signal?: AbortSignal): Promise { + signal?.throwIfAborted() await mkdir(this.deps.directory, { recursive: true, mode: 0o700 }) const temporary = this.file(`${name}.${randomBytes(6).toString('hex')}.tmp`) try { @@ -92,6 +94,7 @@ export class SyncPeerService { } finally { await file.close() } + signal?.throwIfAborted() await rename(temporary, this.file(name)) } finally { await rm(temporary, { force: true }) @@ -99,13 +102,22 @@ export class SyncPeerService { } async connection(): Promise { + const generation = this.credentialGeneration + const check = () => { + if (this.stopping || this.pairingBusy || generation !== this.credentialGeneration) + fail('credentialsUnavailable') + } + check() const pairing = await this.readPairing() + check() if (!pairing) return null if (!pairing.bidirectional) throw new Error('sync.tunnel.error.writeConsentRequired') + const token = await this.deps.revealToken(pairing.wrappedToken) + check() return { hostUrl: pairing.hostUrl, hostId: pairing.hostId, - token: this.deps.revealToken(pairing.wrappedToken) + token } } @@ -163,10 +175,13 @@ export class SyncPeerService { bidirectional?: boolean }): Promise { this.pairingBusy = true + this.credentialGeneration++ this.controller = new AbortController() + const signal = this.controller.signal this.progress = { phase: 'pairing', received: 0, total: 0, error: null } try { await this.automatic?.setEnabled(false) + signal.throwIfAborted() let url: URL try { url = new URL(input.hostUrl) @@ -186,8 +201,9 @@ export class SyncPeerService { const replicaId = input.bidirectional ? this.deps.replicaId?.() : undefined if (input.bidirectional && !replicaId) fail('automaticUnsupported') // Fail before consuming a one-time code when the OS cannot protect a token. - this.deps.protectToken('availability-check') - await this.checkHost(hostUrl, input.hostId, this.controller.signal) + await this.deps.protectToken('availability-check') + signal.throwIfAborted() + await this.checkHost(hostUrl, input.hostId, signal) const response = SyncHostPairResponseSchema.parse( await this.json(hostUrl + SYNC_HOST_PAIR_PATH, { method: 'POST', @@ -198,18 +214,25 @@ export class SyncPeerService { bidirectional: input.bidirectional, replicaId }), - signal: this.controller.signal + signal }) ) - await this.writePrivate('pairing.json', { - hostUrl, - hostId: input.hostId, - deviceId: response.deviceId, - deviceName: response.deviceName, - wrappedToken: this.deps.protectToken(response.token), - lastSuccessAt: null, - bidirectional: input.bidirectional === true - } satisfies Pairing) + signal.throwIfAborted() + const wrappedToken = await this.deps.protectToken(response.token) + signal.throwIfAborted() + await this.writePrivate( + 'pairing.json', + { + hostUrl, + hostId: input.hostId, + deviceId: response.deviceId, + deviceName: response.deviceName, + wrappedToken, + lastSuccessAt: null, + bidirectional: input.bidirectional === true + } satisfies Pairing, + signal + ) await this.clearPartial() this.progress.phase = 'idle' } catch (error) { @@ -227,6 +250,7 @@ export class SyncPeerService { await this.automatic?.setEnabled(false) this.assertIdle() this.pairingBusy = true + this.credentialGeneration++ try { await rm(this.file('pairing.json'), { force: true }) await this.clearPartial() @@ -242,6 +266,7 @@ export class SyncPeerService { if ((await this.readPairing())?.bidirectional) fail('fullBackupIncompatible') if (this.automatic?.status().enabled) fail('busy') if (mode === 'overwrite' && !confirmOverwrite) fail('overwriteConfirmation') + this.assertIdle() this.cancelled = false this.controller = new AbortController() this.progress = { phase: 'downloading', received: 0, total: 0, error: null } @@ -267,6 +292,7 @@ export class SyncPeerService { async stop(): Promise { this.stopping = true + this.credentialGeneration++ if (this.progress.phase !== 'importing') { this.cancelled = true this.controller?.abort() @@ -348,11 +374,12 @@ export class SyncPeerService { await this.checkHost(pairing.hostUrl, pairing.hostId, signal) let token: string try { - token = this.deps.revealToken(pairing.wrappedToken) + token = await this.deps.revealToken(pairing.wrappedToken) } catch { fail('credentialsUnavailable') } if (!token) fail('credentialsUnavailable') + signal.throwIfAborted() const headers = { authorization: `Bearer ${token}` } let status = SyncHostStatusSchema.parse( await this.json(pairing.hostUrl + SYNC_HOST_STATUS_PATH, { headers, signal }) diff --git a/src/main/sync/routes.ts b/src/main/sync/routes.ts index 6c362100b6..c986f9debe 100644 --- a/src/main/sync/routes.ts +++ b/src/main/sync/routes.ts @@ -125,7 +125,9 @@ export function createSyncRoutes(deps: { syncGetCloudConfigRoute.name, async (rawInput) => { syncGetCloudConfigRoute.input.parse(rawInput) - return syncGetCloudConfigRoute.output.parse({ config: deps.settings.getCloudConfig() }) + return syncGetCloudConfigRoute.output.parse({ + config: await deps.settings.getCloudConfig() + }) } ], [ @@ -133,7 +135,7 @@ export function createSyncRoutes(deps: { async (rawInput) => { const input = syncSetCloudConfigRoute.input.parse(rawInput) return syncSetCloudConfigRoute.output.parse({ - config: deps.settings.setCloudConfig(input.config) + config: await deps.settings.setCloudConfig(input.config) }) } ], diff --git a/src/main/sync/settings.ts b/src/main/sync/settings.ts index 649b13df8b..706e5963a4 100644 --- a/src/main/sync/settings.ts +++ b/src/main/sync/settings.ts @@ -1,4 +1,3 @@ -import { app } from 'electron' import path from 'node:path' import type { SecretStore } from '@/config/secretStore' import type { SettingsStore } from '@/config/settingsStore' @@ -14,11 +13,18 @@ const CLOUD_SYNC_BASE_KEY = 'cloudSyncConfig' const CLOUD_SYNC_SECRET_KEY = 'cloudSyncSecret' export class SyncSettings { + private cloudMutation: Promise = Promise.resolve() + private readonly defaultFolderPath: string + constructor( private readonly settings: SettingsStore, private readonly secrets: SecretStore, - private readonly publishEvent: DeepchatEventPublisher - ) {} + private readonly publishEvent: DeepchatEventPublisher, + homeDirectory: string + ) { + if (!path.isAbsolute(homeDirectory)) throw new Error('Sync home directory must be absolute') + this.defaultFolderPath = path.join(homeDirectory, 'DeepchatSync') + } getEnabled(): boolean { return this.settings.get('syncEnabled') ?? false @@ -30,9 +36,7 @@ export class SyncSettings { } getFolderPath(): string { - return ( - this.settings.get('syncFolderPath') || path.join(app.getPath('home'), 'DeepchatSync') - ) + return this.settings.get('syncFolderPath') || this.defaultFolderPath } setFolderPath(folderPath: string): void { @@ -48,16 +52,36 @@ export class SyncSettings { this.settings.set('lastSyncTime', time) } - getCloudConfig(): CloudSyncConfigView { + async getCloudConfig(): Promise { + const check = this.secrets.captureOperation() const base = this.getCloudBase() + const [secret, safeStorageAvailable] = await Promise.all([ + this.secrets.get(CLOUD_SYNC_SECRET_KEY), + this.secrets.isAvailable() + ]) + check() return { ...base, - hasSecret: Boolean(this.secrets.get(CLOUD_SYNC_SECRET_KEY)), - safeStorageAvailable: this.secrets.isAvailable() + hasSecret: Boolean(secret), + safeStorageAvailable } } - setCloudConfig(config: CloudSyncConfigInput): CloudSyncConfigView { + setCloudConfig(config: CloudSyncConfigInput): Promise { + const check = this.secrets.captureOperation() + const operation = this.cloudMutation.then(() => this.writeCloudConfig(config, check)) + this.cloudMutation = operation.then( + () => undefined, + () => undefined + ) + return operation + } + + private async writeCloudConfig( + config: CloudSyncConfigInput, + check: () => void + ): Promise { + check() const current = this.getCloudBase() const next: CloudSyncConfigBase = { enabled: config.enabled ?? current.enabled, @@ -69,9 +93,14 @@ export class SyncSettings { } const currentWrappedSecret = this.secrets.getWrapped(CLOUD_SYNC_SECRET_KEY) const nextWrappedSecret = config.secretAccessKey?.length - ? this.secrets.wrap(config.secretAccessKey) + ? await this.secrets.wrap(config.secretAccessKey) : undefined + const hasSecret = + nextWrappedSecret !== undefined || Boolean(await this.secrets.get(CLOUD_SYNC_SECRET_KEY)) + const safeStorageAvailable = + nextWrappedSecret !== undefined || (await this.secrets.isAvailable()) + check() let secretWritten = false try { if (nextWrappedSecret !== undefined) { @@ -86,12 +115,14 @@ export class SyncSettings { throw error } - return this.getCloudConfig() + return { ...next, hasSecret, safeStorageAvailable } } - getResolvedCloudConfig(): ResolvedCloudSyncConfig | null { + async getResolvedCloudConfig(): Promise { + const check = this.secrets.captureOperation() const base = this.getCloudBase() - const secretAccessKey = this.secrets.get(CLOUD_SYNC_SECRET_KEY) + const secretAccessKey = await this.secrets.get(CLOUD_SYNC_SECRET_KEY) + check() if (!base.endpoint || !base.bucket || !base.accessKeyId || !secretAccessKey) { return null } diff --git a/src/main/tool/agentTools/agentBashHandler.ts b/src/main/tool/agentTools/agentBashHandler.ts index 993b878976..656bac57c1 100644 --- a/src/main/tool/agentTools/agentBashHandler.ts +++ b/src/main/tool/agentTools/agentBashHandler.ts @@ -5,7 +5,7 @@ import os from 'os' import { z } from 'zod' import logger from '@shared/logger' import { - backgroundExecSessionManager, + type BackgroundExecPort, getBackgroundExecConfig } from '@/agent/shared/process/backgroundExecSessionManager' import { terminateProcessTree } from '@/agent/shared/process/processTree' @@ -126,6 +126,7 @@ export class AgentBashHandler { constructor( allowedDirectories: string[], commandPermissionHandler: CommandPermissionService, + private readonly backgroundExec: BackgroundExecPort, private readonly commandEnvironment?: AgentCommandEnvironmentPort ) { if (allowedDirectories.length === 0) { @@ -358,7 +359,7 @@ export class AgentBashHandler { options.signal?.throwIfAborted() } - const session = await backgroundExecSessionManager.start(conversationId, command, cwd, { + const session = await this.backgroundExec.start(conversationId, command, cwd, { commandShell: options.commandShell, timeout, env: options.env, @@ -372,7 +373,7 @@ export class AgentBashHandler { let removal: Promise | undefined const removeSession = (): Promise => { - removal ??= backgroundExecSessionManager.remove(conversationId, session.sessionId) + removal ??= this.backgroundExec.remove(conversationId, session.sessionId) return removal } const signal = options.programmatic === true ? options.signal : undefined @@ -405,12 +406,7 @@ export class AgentBashHandler { } try { - await backgroundExecSessionManager.write( - conversationId, - session.sessionId, - options.stdin ?? '', - true - ) + await this.backgroundExec.write(conversationId, session.sessionId, options.stdin ?? '', true) } catch (error) { await removeSession().catch((cause) => { logger.warn('[AgentBashHandler] Failed to cleanup a session after stdin delivery failed', { @@ -427,7 +423,7 @@ export class AgentBashHandler { try { const yielded = options.stdin === undefined && options.programmatic !== true - ? await backgroundExecSessionManager.waitForCompletionOrYield( + ? await this.backgroundExec.waitForCompletionOrYield( conversationId, session.sessionId, options.yieldMs ?? getBackgroundExecConfig().backgroundMs, @@ -437,7 +433,7 @@ export class AgentBashHandler { kind: 'completed' as const, result: await (abortObserved && signal ? Promise.race([ - backgroundExecSessionManager.getCompletionResult( + this.backgroundExec.getCompletionResult( conversationId, session.sessionId, options.outputPreviewChars ?? COMMAND_PREVIEW_CHARS @@ -448,7 +444,7 @@ export class AgentBashHandler { throw new DOMException('Aborted', 'AbortError') }) ]) - : backgroundExecSessionManager.getCompletionResult( + : this.backgroundExec.getCompletionResult( conversationId, session.sessionId, options.outputPreviewChars ?? COMMAND_PREVIEW_CHARS @@ -756,22 +752,17 @@ export class AgentBashHandler { timeoutMs: timeout ?? COMMAND_DEFAULT_TIMEOUT_MS, background: true }) - const result = await backgroundExecSessionManager.start( - conversationId, - prepared.command, - spawnCwd, - { - commandShell: options.commandShell, - timeout: timeout ?? COMMAND_DEFAULT_TIMEOUT_MS, - env: prepared.env, - outputPrefix: options.outputPrefix, - previewChars: options.outputPreviewChars ?? COMMAND_PREVIEW_CHARS, - offloadThresholdChars: Math.min( - COMMAND_OFFLOAD_THRESHOLD, - options.outputPreviewChars ?? COMMAND_PREVIEW_CHARS - ) - } - ) + const result = await this.backgroundExec.start(conversationId, prepared.command, spawnCwd, { + commandShell: options.commandShell, + timeout: timeout ?? COMMAND_DEFAULT_TIMEOUT_MS, + env: prepared.env, + outputPrefix: options.outputPrefix, + previewChars: options.outputPreviewChars ?? COMMAND_PREVIEW_CHARS, + offloadThresholdChars: Math.min( + COMMAND_OFFLOAD_THRESHOLD, + options.outputPreviewChars ?? COMMAND_PREVIEW_CHARS + ) + }) return { output: { status: 'running', sessionId: result.sessionId } diff --git a/src/main/tool/agentTools/agentToolManager.ts b/src/main/tool/agentTools/agentToolManager.ts index 35dd819e57..893fb08668 100644 --- a/src/main/tool/agentTools/agentToolManager.ts +++ b/src/main/tool/agentTools/agentToolManager.ts @@ -12,7 +12,6 @@ import { toDeepChatJsonSchema } from '@shared/lib/zodJsonSchema' import { z } from 'zod' import fs from 'fs' import path from 'path' -import { app, nativeImage } from 'electron' import logger from '@shared/logger' import { awaitWithAbort } from '@/lib/awaitWithAbort' import type { ChatMessage } from '@shared/types/core/chat-message' @@ -532,6 +531,13 @@ export class AgentToolManager { this.commandPermissionHandler = options.commandPermissionHandler this.commandEnvironment = options.commandEnvironment this.dependencies = options.dependencies + for (const directory of [ + this.dependencies.desktop.tempDirectory, + this.dependencies.desktop.profileTempDirectory + ]) { + if (!path.isAbsolute(directory)) + throw new Error('Agent Tool host directories must be absolute') + } this.liveDelegationTool = this.dependencies.liveDelegation ? new LiveDelegationAgentTool(this.dependencies.liveDelegation) : null @@ -556,6 +562,7 @@ export class AgentToolManager { this.bashHandler = new AgentBashHandler( [this.agentWorkspacePath], this.commandPermissionHandler, + this.dependencies.backgroundExec, this.commandEnvironment ) } @@ -579,6 +586,7 @@ export class AgentToolManager { this.bashHandler = new AgentBashHandler( [effectiveWorkspacePath], this.commandPermissionHandler, + this.dependencies.backgroundExec, this.commandEnvironment ) } else { @@ -1333,8 +1341,7 @@ export class AgentToolManager { throw new Error('process tool requires a conversation ID') } - const { backgroundExecSessionManager } = - await import('@/agent/shared/process/backgroundExecSessionManager') + const backgroundExec = this.dependencies.backgroundExec const validationResult = this.fileSystemSchemas.process.safeParse(args) if (!validationResult.success) { @@ -1351,7 +1358,7 @@ export class AgentToolManager { switch (action) { case 'list': { - const sessions = await backgroundExecSessionManager.list(conversationId) + const sessions = await backgroundExec.list(conversationId) return { content: JSON.stringify({ status: 'ok', sessions }, null, 2) } @@ -1362,7 +1369,7 @@ export class AgentToolManager { throw new Error('sessionId is required for poll action') } const outputLimits = await this.resolveOutputLimitsForConversation(conversationId) - const result = await backgroundExecSessionManager.poll( + const result = await backgroundExec.poll( conversationId, sessionId, outputLimits.commandOutputInlineChars @@ -1376,12 +1383,7 @@ export class AgentToolManager { if (!sessionId) { throw new Error('sessionId is required for log action') } - const result = await backgroundExecSessionManager.log( - conversationId, - sessionId, - offset, - limit - ) + const result = await backgroundExec.log(conversationId, sessionId, offset, limit) return { content: JSON.stringify(result, null, 2) } @@ -1391,13 +1393,7 @@ export class AgentToolManager { if (!sessionId) { throw new Error('sessionId is required for write action') } - await backgroundExecSessionManager.write( - conversationId, - sessionId, - data ?? '', - eof, - commitMutation - ) + await backgroundExec.write(conversationId, sessionId, data ?? '', eof, commitMutation) return { content: JSON.stringify({ status: 'ok', sessionId }) } @@ -1407,7 +1403,7 @@ export class AgentToolManager { if (!sessionId) { throw new Error('sessionId is required for kill action') } - await backgroundExecSessionManager.kill(conversationId, sessionId, commitMutation) + await backgroundExec.kill(conversationId, sessionId, commitMutation) return { content: JSON.stringify({ status: 'ok', sessionId }) } @@ -1417,7 +1413,7 @@ export class AgentToolManager { if (!sessionId) { throw new Error('sessionId is required for clear action') } - await backgroundExecSessionManager.clear(conversationId, sessionId, commitMutation) + await backgroundExec.clear(conversationId, sessionId, commitMutation) return { content: JSON.stringify({ status: 'ok', sessionId }) } @@ -1427,7 +1423,7 @@ export class AgentToolManager { if (!sessionId) { throw new Error('sessionId is required for remove action') } - await backgroundExecSessionManager.remove(conversationId, sessionId, commitMutation) + await backgroundExec.remove(conversationId, sessionId, commitMutation) return { content: JSON.stringify({ status: 'ok', sessionId }) } @@ -1493,6 +1489,7 @@ export class AgentToolManager { const bashHandler = new AgentBashHandler( allowedDirectories, this.commandPermissionHandler, + this.dependencies.backgroundExec, this.commandEnvironment ) const execArgs = parsedArgs as { @@ -2288,8 +2285,8 @@ export class AgentToolManager { if (includeRuntimeRoots) { addPath(conversationId ? resolveSessionDir(conversationId) : null) - addPath(app.getPath('temp')) - addPath(path.join(app.getPath('userData'), 'temp')) + addPath(this.dependencies.desktop.tempDirectory) + addPath(this.dependencies.desktop.profileTempDirectory) } if (conversationId) { @@ -2472,17 +2469,24 @@ export class AgentToolManager { return mimeType.startsWith('image/') } - private buildImageMetadataBlock(filePath: string, mimeType: string, fileSize: number): string { + private async buildImageMetadataBlock( + filePath: string, + mimeType: string, + fileSize: number, + signal?: AbortSignal + ): Promise { + throwIfAbortRequested(signal) let width: number | null = null let height: number | null = null try { - const image = nativeImage.createFromPath(filePath) - const size = image.getSize() + const size = await this.dependencies.desktop.readImageSize(filePath, signal) + throwIfAbortRequested(signal) if (size.width > 0 && size.height > 0) { width = size.width height = size.height } } catch (error) { + throwIfAbortRequested(signal) logger.warn('[AgentToolManager] Failed to read image dimensions', { filePath, error }) } @@ -2506,7 +2510,13 @@ export class AgentToolManager { throwIfAbortRequested(signal) const fileBuffer = await fs.promises.readFile(filePath) throwIfAbortRequested(signal) - const metadata = this.buildImageMetadataBlock(filePath, mimeType, fileBuffer.length) + const metadata = await this.buildImageMetadataBlock( + filePath, + mimeType, + fileBuffer.length, + signal + ) + throwIfAbortRequested(signal) const dataUrl = `data:${mimeType};base64,${fileBuffer.toString('base64')}` let previewData: string | undefined let dispatchCommitFailed = false @@ -2801,7 +2811,11 @@ export class AgentToolManager { } private getDefaultAgentWorkspacePath(): string { - const tempDir = path.join(app.getPath('temp'), 'deepchat-agent', 'workspaces') + const tempDir = path.join( + this.dependencies.desktop.tempDirectory, + 'deepchat-agent', + 'workspaces' + ) try { fs.mkdirSync(tempDir, { recursive: true }) } catch (error) { @@ -2809,7 +2823,7 @@ export class AgentToolManager { '[AgentToolManager] Failed to create default workspace, using system temp:', error ) - return app.getPath('temp') + return this.dependencies.desktop.tempDirectory } return tempDir } @@ -2867,8 +2881,6 @@ export class AgentToolManager { skillService: this.getSkillService(), windowRuntime: { createSettingsWindow: () => this.dependencies.desktop.createSettingsWindow(), - sendToWindow: (windowId, channel, ...args) => - this.dependencies.desktop.sendToWindow(windowId, channel, ...args), sendSettingsNavigation: (windowId, navigation) => this.dependencies.desktop.sendSettingsNavigation(windowId, navigation) } @@ -2880,6 +2892,7 @@ export class AgentToolManager { private getSkillExecutionService(): SkillExecutionService { if (!this.skillExecutionService) { this.skillExecutionService = new SkillExecutionService({ + backgroundExec: this.dependencies.backgroundExec, resolveConversationWorkdir: (conversationId) => this.getWorkdirForConversation(conversationId) }) diff --git a/src/main/tool/agentTools/chatSettingsTools.ts b/src/main/tool/agentTools/chatSettingsTools.ts index fef7ce8186..6848eb4fed 100644 --- a/src/main/tool/agentTools/chatSettingsTools.ts +++ b/src/main/tool/agentTools/chatSettingsTools.ts @@ -2,8 +2,6 @@ import { z } from 'zod' import { toDeepChatJsonSchema } from '@shared/lib/zodJsonSchema' import type { ApplyChatSettingResult, - ChatSettingValue, - ChatLanguage, OpenChatSettingsResult, OpenChatSettingsSection } from '@shared/types/chatSettings' @@ -144,7 +142,7 @@ export class ChatSettingsToolHandler { desktopSettings: AgentDisplaySettingsPort skillSettings: SkillSettingsPort skillService: SkillServicePort - windowRuntime: AgentDesktopToolPort + windowRuntime: Pick } ) {} @@ -166,21 +164,6 @@ export class ChatSettingsToolHandler { return null } - private getCurrentValue(key: string): ChatSettingValue | undefined { - switch (key) { - case 'copyWithCotEnabled': - return this.options.desktopSettings.getCopyWithCotEnabled() - case 'language': - return this.options.desktopSettings.getRequestedLanguage() as ChatLanguage - case 'theme': - return this.options.desktopSettings.getTheme() - case 'fontSizeLevel': - return this.options.desktopSettings.getFontSizeLevel() - default: - return undefined - } - } - async toggle( raw: unknown, conversationId?: string, @@ -198,16 +181,9 @@ export class ChatSettingsToolHandler { } const { setting, enabled } = parsed.data - const previousValue = this.getCurrentValue(setting) beforeMutation?.(parsed.data) try { - switch (setting) { - case 'copyWithCotEnabled': - this.options.desktopSettings.setCopyWithCotEnabled(enabled) - break - default: - return buildError('unknown_setting', `Unsupported toggle: ${setting}`) - } + return await this.options.desktopSettings.applyDisplayChange({ id: setting, value: enabled }) } catch (error) { return buildError( 'apply_failed', @@ -215,14 +191,6 @@ export class ChatSettingsToolHandler { error instanceof Error ? error.message : String(error) ) } - - return { - ok: true, - id: setting, - value: enabled, - previousValue, - appliedAt: Date.now() - } } async setLanguage( @@ -246,10 +214,12 @@ export class ChatSettingsToolHandler { } const { language } = parsed.data - const previousValue = this.getCurrentValue('language') beforeMutation?.(parsed.data) try { - this.options.desktopSettings.setLanguage(language) + return await this.options.desktopSettings.applyDisplayChange({ + id: 'language', + value: language + }) } catch (error) { return buildError( 'apply_failed', @@ -257,14 +227,6 @@ export class ChatSettingsToolHandler { error instanceof Error ? error.message : String(error) ) } - - return { - ok: true, - id: 'language', - value: language, - previousValue, - appliedAt: Date.now() - } } async setTheme( @@ -284,10 +246,9 @@ export class ChatSettingsToolHandler { } const { theme } = parsed.data - const previousValue = this.getCurrentValue('theme') beforeMutation?.(parsed.data) try { - this.options.desktopSettings.setTheme(theme) + return await this.options.desktopSettings.applyDisplayChange({ id: 'theme', value: theme }) } catch (error) { return buildError( 'apply_failed', @@ -295,14 +256,6 @@ export class ChatSettingsToolHandler { error instanceof Error ? error.message : String(error) ) } - - return { - ok: true, - id: 'theme', - value: theme, - previousValue, - appliedAt: Date.now() - } } async setFontSize( @@ -326,10 +279,12 @@ export class ChatSettingsToolHandler { } const { level } = parsed.data - const previousValue = this.getCurrentValue('fontSizeLevel') beforeMutation?.(parsed.data) try { - this.options.desktopSettings.setFontSizeLevel(level) + return await this.options.desktopSettings.applyDisplayChange({ + id: 'fontSizeLevel', + value: level + }) } catch (error) { return buildError( 'apply_failed', @@ -337,14 +292,6 @@ export class ChatSettingsToolHandler { error instanceof Error ? error.message : String(error) ) } - - return { - ok: true, - id: 'fontSizeLevel', - value: level, - previousValue, - appliedAt: Date.now() - } } async open( @@ -388,10 +335,26 @@ export class ChatSettingsToolHandler { } if (routeName) { - this.options.windowRuntime.sendSettingsNavigation(windowId, { - routeName, - section: normalizedSection - }) + try { + const delivered = await this.options.windowRuntime.sendSettingsNavigation(windowId, { + routeName, + section: normalizedSection + }) + if (!delivered) { + return { + ok: false, + errorCode: 'open_failed', + message: 'Settings navigation was not delivered.' + } + } + } catch (error) { + return { + ok: false, + errorCode: 'open_failed', + message: 'Failed to navigate settings window.', + details: error instanceof Error ? error.message : String(error) + } + } } return { diff --git a/src/main/tool/codeMode/runCodeRuntimeManager.ts b/src/main/tool/codeMode/runCodeRuntimeManager.ts index ecc527fd83..35304e5821 100644 --- a/src/main/tool/codeMode/runCodeRuntimeManager.ts +++ b/src/main/tool/codeMode/runCodeRuntimeManager.ts @@ -1,8 +1,4 @@ -import fs from 'node:fs' -import path from 'node:path' -import { fileURLToPath } from 'node:url' import { randomUUID } from 'node:crypto' -import type { UtilityProcess } from 'electron' import type { MCPToolDefinition, MCPToolResponse, ToolDispatchCommitInput } from '@shared/types/mcp' import type { ToolCallOptions } from '@shared/types/tool' import { @@ -20,13 +16,25 @@ import { MAX_EXECUTION_JOURNAL_NESTED_CHILDREN } from '@/tape/domain/executionJo import { awaitWithAbort } from '@/lib/awaitWithAbort' import { normalizeCodexToolName } from './toolModeTools' -type CodeModeUtilityProcess = Pick & { - on(event: 'message', listener: (message: unknown) => void): CodeModeUtilityProcess - on(event: 'exit', listener: (code: number) => void): CodeModeUtilityProcess - on(event: 'error', listener: (type: string, location: string) => void): CodeModeUtilityProcess - off(event: 'message', listener: (message: unknown) => void): CodeModeUtilityProcess - off(event: 'exit', listener: (code: number) => void): CodeModeUtilityProcess - off(event: 'error', listener: (type: string, location: string) => void): CodeModeUtilityProcess +type CodeModeHostEvents = { + message: [message: unknown] + exit: [code: number] + error: [type: string, location: string] +} + +export interface CodeModeHost { + readonly pid: number | undefined + postMessage(message: RunCodeParentMessage): void + kill(): void + on( + event: T, + listener: (...args: CodeModeHostEvents[T]) => void + ): void + off( + event: T, + listener: (...args: CodeModeHostEvents[T]) => void + ): void + getRssBytes(signal: AbortSignal): Promise } export interface RunCodeNestedExecutionInput { @@ -42,7 +50,7 @@ export interface RunCodeRuntimeManagerOptions { executeNested( input: RunCodeNestedExecutionInput ): Promise<{ content: unknown; rawData: MCPToolResponse }> - spawnHost?: () => Promise + spawnHost(signal: AbortSignal): Promise } export interface RunCodeExecutionInput { @@ -107,7 +115,7 @@ type ActiveCell = { runId?: string outerToolCallId: string frontend: RunCodeFrontend - host: CodeModeUtilityProcess + host: CodeModeHost state: CellState bindings: Map< string, @@ -136,6 +144,7 @@ type ActiveCell = { startupRssBytes: number | null heartbeatTimer: NodeJS.Timeout | null rssTimer: NodeJS.Timeout | null + rssInspection: Promise | null executionDeadlineTimer: NodeJS.Timeout | null yieldLeaseTimer: NodeJS.Timeout | null forceKillTimer: NodeJS.Timeout | null @@ -252,12 +261,22 @@ export class RunCodeRuntimeManager { private readonly cellsById = new Map() private readonly cellsBySession = new Map() private readonly storesBySession = new Map>() - private shuttingDown = false + private readonly lifetime = new AbortController() + private readonly physicalCells = new Set() + private readonly operations = new Set>() + private readonly pendingStarts = new Map< + string, + { + controller: AbortController + completion: Promise<{ result: Promise }> + } + >() + private shutdownPromise: Promise | null = null constructor(private readonly managerOptions: RunCodeRuntimeManagerOptions) {} async execute(input: RunCodeExecutionInput): Promise { - if (this.shuttingDown) throw new Error('Code mode runtime is shutting down.') + if (this.lifetime.signal.aborted) throw new Error('Code mode runtime is shutting down.') input.options.signal?.throwIfAborted() assertCompleteJournalCapabilities(input.options) if (Buffer.byteLength(input.source, 'utf8') > RUN_CODE_SOURCE_MAX_BYTES) { @@ -277,11 +296,14 @@ export class RunCodeRuntimeManager { this.clearCellLease(existing) this.bindAbortSignal(existing, input.options.signal) existing.waiter = createDeferred() - void this.retryPermissionCall(existing) + void this.trackOperation(this.retryPermissionCall(existing)) return await existing.waiter.promise } throw new Error(`Session ${input.sessionId} already has an active code cell.`) } + if (this.pendingStarts.has(input.sessionId)) { + throw new Error(`Session ${input.sessionId} already has a code cell starting.`) + } const cellId = randomUUID() @@ -318,50 +340,74 @@ export class RunCodeRuntimeManager { }) } - input.options.commitDispatch?.(input.outerDispatch) - const host = await this.spawnReadyHost(input.options.signal) - if (input.options.signal?.aborted) { - try { - host.kill() - } catch {} - input.options.signal.throwIfAborted() - } - - const cell = this.createCell({ - id: cellId, - sessionId: input.sessionId, - runId: input.runId, - outerToolCallId: input.toolCallId, - frontend: input.frontend, - host, - bindings, - capabilityHash: canonicalCatalog.fullCatalogHash, - yieldTimeMs: input.yieldTimeMs ?? 10_000, - maxOutputTokens: input.maxOutputTokens ?? 10_000, - timeoutMs: input.timeoutMs ?? RUN_CODE_DEFAULT_TIMEOUT_MS, - options: input.options, - committedDispatchToolCallId: input.toolCallId - }) - this.cellsById.set(cell.id, cell) - this.cellsBySession.set(cell.sessionId, cell) - this.attachCell(cell) - cell.state = 'running' - this.post(cell, { - type: 'START', - version: RUN_CODE_PROTOCOL_VERSION, - cellId, - frontend: input.frontend, - source: input.source, - bindings: [...bindings.values()].map(({ binding }) => binding), - store: structuredClone(this.storesBySession.get(input.sessionId) ?? {}) + const controller = new AbortController() + const signal = AbortSignal.any([ + this.lifetime.signal, + controller.signal, + ...(input.options.signal ? [input.options.signal] : []) + ]) + const completion = this.trackOperation( + Promise.resolve().then(async () => { + signal.throwIfAborted() + input.options.commitDispatch?.(input.outerDispatch) + const host = await this.spawnReadyHost(signal) + if (signal.aborted) { + host.kill() + signal.throwIfAborted() + } + const cell = this.createCell({ + id: cellId, + sessionId: input.sessionId, + runId: input.runId, + outerToolCallId: input.toolCallId, + frontend: input.frontend, + host, + bindings, + capabilityHash: canonicalCatalog.fullCatalogHash, + yieldTimeMs: input.yieldTimeMs ?? 10_000, + maxOutputTokens: input.maxOutputTokens ?? 10_000, + timeoutMs: input.timeoutMs ?? RUN_CODE_DEFAULT_TIMEOUT_MS, + options: input.options, + committedDispatchToolCallId: input.toolCallId + }) + const waiter = cell.waiter + if (!waiter) throw new Error('Code cell lost its initial result waiter.') + void waiter.promise.catch(() => undefined) + this.physicalCells.add(cell) + this.cellsById.set(cell.id, cell) + this.cellsBySession.set(cell.sessionId, cell) + try { + signal.throwIfAborted() + this.attachCell(cell) + signal.throwIfAborted() + cell.state = 'running' + this.post(cell, { + type: 'START', + version: RUN_CODE_PROTOCOL_VERSION, + cellId, + frontend: input.frontend, + source: input.source, + bindings: [...bindings.values()].map(({ binding }) => binding), + store: structuredClone(this.storesBySession.get(input.sessionId) ?? {}) + }) + this.scheduleYield(cell) + } catch (error) { + this.failAndCleanup(cell, toError(error)) + throw error + } + return { result: waiter.promise } + }) + ).finally(() => { + if (this.pendingStarts.get(input.sessionId)?.completion === completion) + this.pendingStarts.delete(input.sessionId) }) - this.scheduleYield(cell) - const waiter = cell.waiter - if (!waiter) throw new Error('Code cell lost its initial result waiter.') - return await waiter.promise + this.pendingStarts.set(input.sessionId, { controller, completion }) + const prepared = await awaitWithAbort(completion, signal) + return await prepared.result } async wait(input: RunCodeWaitInput): Promise { + this.lifetime.signal.throwIfAborted() input.options.signal?.throwIfAborted() assertCompleteJournalCapabilities(input.options) const cell = this.cellsById.get(input.cellId) @@ -388,7 +434,7 @@ export class RunCodeRuntimeManager { this.clearCellLease(cell) this.bindAbortSignal(cell, input.options.signal) cell.waiter = createDeferred() - void this.retryPermissionCall(cell) + void this.trackOperation(this.retryPermissionCall(cell)) return await cell.waiter.promise } @@ -442,32 +488,51 @@ export class RunCodeRuntimeManager { } cancelSession(sessionId: string, reason = 'Session closed.'): void { + this.pendingStarts.get(sessionId)?.controller.abort(new Error(reason)) const cell = this.cellsBySession.get(sessionId) if (cell) this.failAndCleanup(cell, new Error(reason)) this.storesBySession.delete(sessionId) } - async shutdown(): Promise { - this.shuttingDown = true - const cells = [...this.cellsById.values()] + stop(): void { + if (this.lifetime.signal.aborted) return + this.lifetime.abort(new DOMException('Code mode runtime shut down.', 'AbortError')) + for (const cell of this.cellsById.values()) + this.failAndCleanup(cell, new Error('Code mode runtime shut down.')) + } + + shutdown(): Promise { + if (this.shutdownPromise) return this.shutdownPromise + this.stop() + this.shutdownPromise = this.performShutdown() + return this.shutdownPromise + } + + private async performShutdown(): Promise { await Promise.all( - cells.map(async (cell) => { - this.failAndCleanup(cell, new Error('Code mode runtime shut down.')) - await this.waitForExit(cell, STOP_GRACE_MS + 100) - }) + [...this.physicalCells].map((cell) => this.waitForExit(cell, STOP_GRACE_MS + 100)) ) + while (this.operations.size) await Promise.allSettled(this.operations) this.cellsById.clear() this.cellsBySession.clear() this.storesBySession.clear() } + private trackOperation(promise: Promise): Promise { + const tracked = promise.finally(() => { + this.operations.delete(tracked) + }) + this.operations.add(tracked) + return tracked + } + private createCell(input: { id: string sessionId: string runId?: string outerToolCallId: string frontend: RunCodeFrontend - host: CodeModeUtilityProcess + host: CodeModeHost bindings: ActiveCell['bindings'] yieldTimeMs: number maxOutputTokens: number @@ -496,6 +561,7 @@ export class RunCodeRuntimeManager { startupRssBytes: null, heartbeatTimer: null, rssTimer: null, + rssInspection: null, executionDeadlineTimer: null, yieldLeaseTimer: null, forceKillTimer: null, @@ -620,7 +686,7 @@ export class RunCodeRuntimeManager { private drainPendingNestedCalls(cell: ActiveCell): void { const pending = cell.pendingNestedMessages.splice(0) - for (const message of pending) void this.handleNestedCall(cell, message) + for (const message of pending) void this.trackOperation(this.handleNestedCall(cell, message)) } private flushPendingHostSettlement(cell: ActiveCell): void { @@ -793,7 +859,7 @@ export class RunCodeRuntimeManager { cell.lastHeartbeatAt = Date.now() return case 'NESTED_CALL': - void this.handleNestedCall(cell, message) + void this.trackOperation(this.handleNestedCall(cell, message)) return case 'YIELDED': if (cell.frontend !== 'codex') { @@ -963,6 +1029,7 @@ export class RunCodeRuntimeManager { } private detachCell(cell: ActiveCell): void { + this.physicalCells.delete(cell) if (cell.forceKillTimer) clearTimeout(cell.forceKillTimer) cell.forceKillTimer = null cell.host.off('message', cell.onMessage) @@ -1045,45 +1112,49 @@ export class RunCodeRuntimeManager { } private inspectRss(cell: ActiveCell): void { - if (!cell.host.pid) return - void import('electron') - .then(({ app }) => { - if (cell.state === 'stopping') return - const metric = app.getAppMetrics().find((entry) => entry.pid === cell.host.pid) - const rssBytes = metric ? metric.memory.workingSetSize * 1024 : null - if (rssBytes === null) return - cell.startupRssBytes ??= rssBytes - const softLimit = Math.max(RSS_SOFT_MIN_BYTES, cell.startupRssBytes + RSS_SOFT_DELTA_BYTES) - if (rssBytes > RSS_HARD_BYTES) { - this.failAndCleanup(cell, new Error('Code cell exceeded the 512 MiB RSS limit.')) - } else if (rssBytes > softLimit) { - console.warn('[RunCodeRuntime] Code cell exceeded the RSS soft ceiling.', { - cellId: cell.id, - rssBytes, - softLimit - }) - } - }) - .catch((error) => { - if (cell.state !== 'stopping') { - console.warn('[RunCodeRuntime] Failed to inspect utility process RSS.', error) - } - }) + const signal = cell.runtimeAbortController.signal + if (signal.aborted || cell.rssInspection) return + const inspection = this.trackOperation( + Promise.resolve() + .then(async () => { + signal.throwIfAborted() + const rssBytes = await cell.host.getRssBytes(signal) + if (signal.aborted || rssBytes === null) return + if (!Number.isFinite(rssBytes) || rssBytes < 0) + throw new Error('Invalid Code Mode RSS sample') + cell.startupRssBytes ??= rssBytes + const softLimit = Math.max( + RSS_SOFT_MIN_BYTES, + cell.startupRssBytes + RSS_SOFT_DELTA_BYTES + ) + if (rssBytes > RSS_HARD_BYTES) { + this.failAndCleanup(cell, new Error('Code cell exceeded the 512 MiB RSS limit.')) + } else if (rssBytes > softLimit) { + console.warn('[RunCodeRuntime] Code cell exceeded the RSS soft ceiling.', { + cellId: cell.id, + rssBytes, + softLimit + }) + } + }) + .catch((error) => { + if (!signal.aborted) + console.warn('[RunCodeRuntime] Failed to inspect utility process RSS.', error) + }) + ).finally(() => { + if (cell.rssInspection === inspection) cell.rssInspection = null + }) + cell.rssInspection = inspection } - private async spawnReadyHost(signal?: AbortSignal): Promise { - signal?.throwIfAborted() - const spawning = this.managerOptions.spawnHost - ? this.managerOptions.spawnHost() - : this.spawnDefaultHost() - let host: CodeModeUtilityProcess - try { - host = await awaitWithAbort(spawning, signal) - } catch (error) { - void spawning.then((abandonedHost) => abandonedHost.kill()).catch(() => undefined) - throw error + private async spawnReadyHost(signal: AbortSignal): Promise { + signal.throwIfAborted() + const host = await this.managerOptions.spawnHost(signal) + if (signal.aborted) { + host.kill() + signal.throwIfAborted() } - return await new Promise((resolve, reject) => { + return await new Promise((resolve, reject) => { let settled = false const settle = (callback: () => void) => { if (settled) return @@ -1108,9 +1179,12 @@ export class RunCodeRuntimeManager { const onExit = (code: number) => settle(() => reject(new Error(`Code mode utility exited before ready: ${code}`))) const onError = (type: string, location: string) => - settle(() => + settle(() => { + try { + host.kill() + } catch {} reject(new Error(`Code mode utility failed before ready: ${type} at ${location}`)) - ) + }) const onAbort = () => settle(() => { try { @@ -1134,41 +1208,6 @@ export class RunCodeRuntimeManager { }) } - private async spawnDefaultHost(): Promise { - const { app, utilityProcess } = await import('electron') - const modulePath = this.resolveUtilityHostEntryPoint(app.getAppPath()) - const env: Record = { - DEEPCHAT_CODE_MODE_HOST: '1' - } - for (const name of ['LANG', 'LC_ALL', 'TMPDIR', 'TEMP', 'TMP', 'SystemRoot']) { - const value = process.env[name] - if (value) env[name] = value - } - return utilityProcess.fork(modulePath, ['--deepchat-code-mode-host'], { - serviceName: 'DeepChat Code Mode Cell', - stdio: 'ignore', - env, - execArgv: ['--max-old-space-size=64'], - allowLoadingUnsignedLibraries: false - }) as CodeModeUtilityProcess - } - - private resolveUtilityHostEntryPoint(appPath?: string): string { - const modulePath = fileURLToPath(import.meta.url) - const candidates = [ - ...(appPath - ? [ - path.join(appPath, 'out/main/codeModeUtilityHost.js'), - path.join(appPath, 'codeModeUtilityHost.js') - ] - : []), - path.resolve(path.dirname(modulePath), 'codeModeUtilityHost.js'), - path.resolve(path.dirname(modulePath), '../../codeModeUtilityHost.js'), - path.resolve(process.cwd(), 'out/main/codeModeUtilityHost.js') - ] - return candidates.find((candidate) => fs.existsSync(candidate)) ?? candidates[0] - } - private async waitForExit(cell: ActiveCell, timeoutMs: number): Promise { if (cell.exited) return await new Promise((resolve) => { diff --git a/src/main/tool/index.ts b/src/main/tool/index.ts index f0629b81db..c12f64163d 100644 --- a/src/main/tool/index.ts +++ b/src/main/tool/index.ts @@ -114,7 +114,7 @@ type MainProcessToolPreCheckOptions = Pick< > import type { AgentCommandEnvironmentPort } from './agentTools/agentBashHandler' import type { ToolEffectObserver } from './effectObserver' -import { resolvePluginToolPolicy } from '@/plugin/toolPolicyStore' +import type { PluginToolPolicyStore } from '@/plugin/toolPolicyStore' import { composeSubagentAuthority } from '@/session/subagentAuthority' import type { LiveDelegationConsentIssuer } from '@/orchestration/liveDelegationConsent' import { parseChildAgentResultEnvelopeText } from '@shared/orchestration/resultSafety' @@ -122,7 +122,10 @@ import { ExecutionContractDispatchError, assertExecutionContractAllowsDispatch } from '@/tape/domain/executionContract' -import { RunCodeRuntimeManager } from './codeMode/runCodeRuntimeManager' +import { + RunCodeRuntimeManager, + type RunCodeRuntimeManagerOptions +} from './codeMode/runCodeRuntimeManager' import { APPLY_PATCH_TOOL_NAME, CODE_MODE_EXEC_TOOL_NAME, @@ -151,6 +154,8 @@ type McpToolPort = Pick< > interface ToolServiceOptions { + pluginToolPolicies: Pick + spawnCodeModeHost: RunCodeRuntimeManagerOptions['spawnHost'] mcpService: McpToolPort providerSettings: Pick settings: Pick @@ -279,6 +284,7 @@ export class ToolService implements ToolServicePort { this.mapper = new ToolMapper() this.conversationMappers = new Map() this.runCodeRuntime = new RunCodeRuntimeManager({ + spawnHost: options.spawnCodeModeHost, executeNested: async (input) => await this.callTool( { @@ -650,6 +656,10 @@ export class ToolService implements ToolServicePort { return [...codeEntryTools, ...codeModeDirectTools] } + stopCodeRuntime(): void { + this.runCodeRuntime.stop() + } + async shutdownCodeRuntime(): Promise { await this.runCodeRuntime.shutdown() } @@ -2066,7 +2076,7 @@ export class ToolService implements ToolServicePort { if (!serverName) { return true } - const policy = resolvePluginToolPolicy( + const policy = this.options.pluginToolPolicies.resolve( serverName, definition.raw?.name ?? definition.function.name ) @@ -2096,7 +2106,7 @@ export class ToolService implements ToolServicePort { } const policy = definition - ? resolvePluginToolPolicy( + ? this.options.pluginToolPolicies.resolve( definition.server.name, definition.raw?.name ?? definition.function.name ) diff --git a/src/main/tool/runtimePorts.ts b/src/main/tool/runtimePorts.ts index e9d29536e2..6163783a7b 100644 --- a/src/main/tool/runtimePorts.ts +++ b/src/main/tool/runtimePorts.ts @@ -2,6 +2,7 @@ import type { FileServicePort } from '@shared/types/file' import type { ProviderRuntimePort } from '@shared/types/provider' import type { MCPToolDefinition } from '@shared/types/mcp' import type { SettingsNavigationPayload } from '@shared/settingsNavigation' +import type { ApplyChatSettingSuccess, ChatSettingMutation } from '@shared/types/chatSettings' import type { DeepChatSubagentMeta, DeepChatSubagentCapability, @@ -44,6 +45,7 @@ import type { cronJobsUpsertInputSchema } from '@shared/contracts/routes/cronJob import type { z } from 'zod' import type { LiveDelegationConsentReceipt } from '@/orchestration/liveDelegationConsent' import type { ReadSessionInput } from '@shared/sessionReferences' +import type { BackgroundExecPort } from '@/agent/shared/process/backgroundExecSessionManager' export type AgentToolCronJobUpsertInput = z.input @@ -221,7 +223,7 @@ export interface AgentBrowserToolPort { args: Record, conversationId?: string, runId?: string, - beforeInvoke?: (normalizedArguments: Record) => void, + beforeInvoke?: (normalizedArguments: Record) => unknown, signal?: AbortSignal ): Promise } @@ -234,20 +236,15 @@ export type AgentProviderToolPort = Pick< > export interface AgentDesktopToolPort { + readonly tempDirectory: string + readonly profileTempDirectory: string + readImageSize(filePath: string, signal?: AbortSignal): Promise<{ width: number; height: number }> createSettingsWindow(): Promise - sendToWindow(windowId: number, channel: string, ...args: unknown[]): boolean - sendSettingsNavigation(windowId: number, navigation: SettingsNavigationPayload): boolean + sendSettingsNavigation(windowId: number, navigation: SettingsNavigationPayload): Promise } export interface AgentDisplaySettingsPort { - getCopyWithCotEnabled(): boolean - setCopyWithCotEnabled(enabled: boolean): void - getRequestedLanguage(): string - setLanguage(language: string): void - getTheme(): 'system' | 'light' | 'dark' - setTheme(theme: 'system' | 'light' | 'dark'): void - getFontSizeLevel(): number - setFontSizeLevel(level: number): void + applyDisplayChange(change: ChatSettingMutation): Promise } export interface AgentToolPermissionPort { @@ -264,6 +261,7 @@ export interface AgentToolPermissionPort { } export interface AgentToolDependencies { + backgroundExec: BackgroundExecPort sessions: AgentToolSessionPort tape: AgentTapeToolPort memory: AgentMemoryToolPort diff --git a/src/main/upgrade/index.ts b/src/main/upgrade/index.ts index 8fbf101c67..663fb8889c 100644 --- a/src/main/upgrade/index.ts +++ b/src/main/upgrade/index.ts @@ -169,7 +169,7 @@ export class UpgradeService { } constructor( - private readonly settings: UpdateSettings, + private readonly settings: Pick, private readonly isPrivacyModeEnabled: () => boolean, requestUpdateInstall: (installAction: () => void) => Promise, private readonly publishEvent: DeepchatEventPublisher, diff --git a/src/main/upgrade/routes.ts b/src/main/upgrade/routes.ts index b0651be599..86b0ba105e 100644 --- a/src/main/upgrade/routes.ts +++ b/src/main/upgrade/routes.ts @@ -1,9 +1,6 @@ import { app } from 'electron' import type { UpgradeService } from './index' -import type { UpdateSettings } from './settings' import { - configGetUpdateChannelRoute, - configSetUpdateChannelRoute, upgradeCheckRoute, upgradeClearMockRoute, upgradeGetStatusRoute, @@ -25,25 +22,9 @@ export function createUpgradeRoutes(deps: { | 'clearMockUpdate' | 'restartToUpdate' > - settings: UpdateSettings }): DeepchatRouteMap { const { upgrade } = deps return createRouteMap([ - [ - configGetUpdateChannelRoute.name, - async (rawInput) => { - configGetUpdateChannelRoute.input.parse(rawInput) - return configGetUpdateChannelRoute.output.parse({ channel: deps.settings.getChannel() }) - } - ], - [ - configSetUpdateChannelRoute.name, - async (rawInput) => { - const input = configSetUpdateChannelRoute.input.parse(rawInput) - deps.settings.setChannel(input.channel) - return configSetUpdateChannelRoute.output.parse({ channel: deps.settings.getChannel() }) - } - ], [ upgradeGetStatusRoute.name, async (rawInput) => { diff --git a/src/main/upgrade/settings.ts b/src/main/upgrade/settings.ts index 0f88c236ae..18533f2aab 100644 --- a/src/main/upgrade/settings.ts +++ b/src/main/upgrade/settings.ts @@ -1,15 +1,17 @@ -import { app } from 'electron' import type { SettingsStore } from '@/config/settingsStore' export class UpdateSettings { - constructor(private readonly settings: SettingsStore) {} + constructor( + private readonly settings: SettingsStore, + private readonly appVersion: string + ) {} getChannel(): 'stable' | 'beta' { const raw = this.settings.get('updateChannel') if (raw === 'stable' || raw === 'beta') { return raw } - const channel = /-(?:alpha|beta|rc|canary)(?:[.-]\d+)?$/i.test(app.getVersion()) + const channel = /-(?:alpha|beta|rc|canary)(?:[.-]\d+)?$/i.test(this.appVersion) ? 'beta' : 'stable' this.settings.set('updateChannel', channel) diff --git a/src/main/workspace/index.ts b/src/main/workspace/index.ts index 9e5e995e36..cace5b9aac 100644 --- a/src/main/workspace/index.ts +++ b/src/main/workspace/index.ts @@ -3,7 +3,6 @@ import fs from 'fs' import { execFile } from 'child_process' import { fileURLToPath } from 'url' import { promisify } from 'util' -import { shell } from 'electron' import { createWatcherRequestId, type IFileWatcherService, @@ -12,13 +11,13 @@ import { type WatchHandle } from '@/platform/fileWatcher' import { readDirectoryShallow } from './directoryReader' -import { listFileOpenApps, openFileWithApp } from './openInApp' import { searchWorkspaceFiles } from './workspaceFileSearch' import { createWorkspacePreviewFileUrl, createWorkspacePreviewUrl, registerWorkspacePreviewFile, registerWorkspacePreviewRoot, + resolveWorkspacePreviewRequest, unregisterWorkspacePreviewFile, unregisterWorkspacePreviewRoot } from './workspacePreviewProtocol' @@ -38,7 +37,7 @@ import type { WorkspaceWatchStatusEvent, WorkspaceLinkedFileResolution } from '@shared/types/workspace' -import type { WorkspaceEventPublisher, WorkspaceFilePort } from './ports' +import type { WorkspaceDesktopPort, WorkspaceEventPublisher, WorkspaceFilePort } from './ports' const execFileAsync = promisify(execFile) @@ -100,7 +99,7 @@ const getInvalidationPriority = (kind: WorkspaceInvalidationKind): number => { /** * Workspace lifecycle contract: - * - Main process owns workspace invalidation production. + * - The Workspace owner produces invalidations. * - Content watcher emits `fs`, git metadata watcher emits `git`. * - Renderer consumes invalidation events and decides whether to run a full or git-only refresh. * - `registerWorkspace` remains a pure security boundary; `watchWorkspace` controls watcher lifetime. @@ -111,17 +110,22 @@ export class WorkspaceService implements WorkspaceServicePort { private readonly fileService: WorkspaceFilePort private readonly watcherService: IFileWatcherService private readonly watchRuntimes = new Map() + private readonly desktopWork = new Set>() + private stopped = false + private closing: Promise | undefined constructor( fileService: WorkspaceFilePort, watcherService: IFileWatcherService, - private readonly events: WorkspaceEventPublisher + private readonly events: WorkspaceEventPublisher, + private readonly desktop: WorkspaceDesktopPort ) { this.fileService = fileService this.watcherService = watcherService } async registerWorkspace(workspacePath: string): Promise { + if (this.stopped) throw new Error('Workspace is stopped.') const normalized = path.resolve(workspacePath) this.allowedPaths.add(normalized) registerWorkspacePreviewRoot(normalized) @@ -190,15 +194,40 @@ export class WorkspaceService implements WorkspaceServicePort { await this.disposeRuntime(runtime) } - async destroy(): Promise { - const runtimes = Array.from(this.watchRuntimes.values()) - this.watchRuntimes.clear() - await Promise.allSettled(runtimes.map((runtime) => this.disposeRuntime(runtime))) + destroy(): Promise { + if (this.closing) return this.closing + this.stopped = true + this.closing = (async () => { + const runtimes = Array.from(this.watchRuntimes.values()) + this.watchRuntimes.clear() + await Promise.allSettled([ + ...runtimes.map((runtime) => this.disposeRuntime(runtime)), + ...this.desktopWork + ]) + for (const exactPath of this.allowedExactPaths) unregisterWorkspacePreviewFile(exactPath) + this.allowedExactPaths.clear() + for (const root of this.allowedPaths) unregisterWorkspacePreviewRoot(root) + this.allowedPaths.clear() + })() + return this.closing + } - for (const exactPath of this.allowedExactPaths) { - unregisterWorkspacePreviewFile(exactPath) + async resolvePreviewRequest(url: string): Promise { + return this.stopped ? null : resolveWorkspacePreviewRequest(url) + } + + private async useDesktop(execute: () => Promise): Promise { + if (this.stopped) throw new Error('Workspace is stopped.') + const operation = Promise.resolve().then(() => { + if (this.stopped) throw new Error('Workspace is stopped.') + return execute() + }) + this.desktopWork.add(operation) + try { + return await operation + } finally { + this.desktopWork.delete(operation) } - this.allowedExactPaths.clear() } private async createContentWatcher(workspacePath: string): Promise { @@ -490,6 +519,7 @@ export class WorkspaceService implements WorkspaceServicePort { * Uses realpathSync when possible and falls back to resolved paths for deleted files. */ private isPathAllowed(targetPath: string): boolean { + if (this.stopped) return false const normalizedTarget = this.normalizePathForAccess(targetPath) const targetWithSep = normalizedTarget.endsWith(path.sep) ? normalizedTarget @@ -785,7 +815,7 @@ export class WorkspaceService implements WorkspaceServicePort { const normalizedPath = path.resolve(filePath) try { - shell.showItemInFolder(normalizedPath) + await this.useDesktop(() => this.desktop.revealFile(normalizedPath)) } catch (error) { console.error(`[Workspace] Failed to reveal path: ${normalizedPath}`, error) } @@ -798,7 +828,7 @@ export class WorkspaceService implements WorkspaceServicePort { } const normalizedPath = path.resolve(filePath) - const errorMessage = await shell.openPath(normalizedPath) + const errorMessage = await this.useDesktop(() => this.desktop.openFile(normalizedPath)) if (errorMessage) { throw new Error(errorMessage) @@ -811,7 +841,7 @@ export class WorkspaceService implements WorkspaceServicePort { return [] } - return listFileOpenApps(path.resolve(filePath)) + return await this.useDesktop(() => this.desktop.listFileOpenApps(path.resolve(filePath))) } async openFileWithApp(filePath: string, appId: string): Promise { @@ -821,7 +851,7 @@ export class WorkspaceService implements WorkspaceServicePort { } const normalizedPath = path.resolve(filePath) - await openFileWithApp(normalizedPath, appId) + await this.useDesktop(() => this.desktop.openFileWithApp(normalizedPath, appId)) } async resolveMarkdownLinkedFile( @@ -878,6 +908,7 @@ export class WorkspaceService implements WorkspaceServicePort { 'origin' ) const normalizedPreparedPath = this.normalizePathForAccess(preparedFile.path) + if (!this.isPathAllowed(filePath)) return null const workspaceRoot = this.getWorkspaceRootForPath(normalizedPreparedPath) const kind = this.resolvePreviewKind(preparedFile.mimeType, normalizedPreparedPath) diff --git a/src/main/workspace/ports.ts b/src/main/workspace/ports.ts index 2509ed6134..4d368902bc 100644 --- a/src/main/workspace/ports.ts +++ b/src/main/workspace/ports.ts @@ -1,4 +1,15 @@ -import type { WorkspaceInvalidationEvent, WorkspaceWatchStatusEvent } from '@shared/types/workspace' +import type { + WorkspaceFileOpenApp, + WorkspaceInvalidationEvent, + WorkspaceWatchStatusEvent +} from '@shared/types/workspace' + +export interface WorkspaceDesktopPort { + revealFile(filePath: string): Promise + openFile(filePath: string): Promise + listFileOpenApps(filePath: string): Promise + openFileWithApp(filePath: string, appId: string): Promise +} export interface WorkspaceFilePort { prepareFileCompletely( diff --git a/src/main/workspace/workspacePreviewProtocol.ts b/src/main/workspace/workspacePreviewProtocol.ts index 683985298f..d9cb119abd 100644 --- a/src/main/workspace/workspacePreviewProtocol.ts +++ b/src/main/workspace/workspacePreviewProtocol.ts @@ -1,9 +1,10 @@ import { createHash } from 'crypto' import fs from 'fs' import path from 'path' -import { protocol } from 'electron' -export const WORKSPACE_PREVIEW_PROTOCOL = 'workspace-preview' +import { WORKSPACE_PREVIEW_PROTOCOL } from '@shared/types/workspace' + +export { WORKSPACE_PREVIEW_PROTOCOL } /** * Bidirectional map for managing path-to-id mappings @@ -42,8 +43,6 @@ class BidirectionalMap { const workspaceRegistry = new BidirectionalMap() const fileRegistry = new BidirectionalMap() -let schemesRegistered = false - function normalizePathForAccess(targetPath: string): string { try { return path.normalize(fs.realpathSync(targetPath)) @@ -101,27 +100,6 @@ function buildPreviewUrl(id: string, ...pathComponents: string[]): string { return `${WORKSPACE_PREVIEW_PROTOCOL}://${id}/${encodedPath}` } -export function registerWorkspacePreviewSchemes(): void { - if (schemesRegistered) { - return - } - - protocol.registerSchemesAsPrivileged([ - { - scheme: WORKSPACE_PREVIEW_PROTOCOL, - privileges: { - standard: true, - secure: true, - supportFetchAPI: true, - corsEnabled: true, - stream: true - } - } - ]) - - schemesRegistered = true -} - export function registerWorkspacePreviewRoot(workspacePath: string): void { const normalizedRoot = normalizePathForAccess(workspacePath) getOrCreateWorkspaceId(normalizedRoot) @@ -227,5 +205,4 @@ export function resolveWorkspacePreviewRequest(requestUrl: string): string | nul export function resetWorkspacePreviewProtocolState(): void { workspaceRegistry.clear() fileRegistry.clear() - schemesRegistered = false } diff --git a/src/renderer/api/SettingsClient.ts b/src/renderer/api/SettingsClient.ts index a2237bf249..adf8105fcf 100644 --- a/src/renderer/api/SettingsClient.ts +++ b/src/renderer/api/SettingsClient.ts @@ -1,8 +1,10 @@ import type { DeepchatBridge } from '@shared/contracts/bridge' +import { RevisionSchema } from '@shared/contracts/common' import type { AgentCommandShellConfig } from '@shared/commandShell' import { settingsChangedEvent, settingsCommandShellChangedEvent } from '@shared/contracts/events' import type { SettingsNavigationPayload } from '@shared/settingsNavigation' import { + SETTINGS_KEYS, configGetEntriesRoute, configUpdateEntriesRoute, settingsCheckCommandShellRoute, @@ -16,6 +18,7 @@ import { type ConfigEntryChange, type ConfigEntryKey, type ConfigEntryValues, + type DeepchatRouteOutput, type SettingsChange, type SettingsKey, type SettingsSnapshotValues @@ -46,9 +49,27 @@ const normalizeSettingsNavigationPayload = ( } export function createSettingsClient(bridge: DeepchatBridge = getDeepchatBridge()) { - async function getSnapshot(keys?: SettingsKey[]): Promise> { - const result = await bridge.invoke(settingsGetSnapshotRoute.name, { keys }) - return result.values + const observedVersions = new Map() + + function acceptSnapshot(snapshot: { + version: number + values: Partial + }): Partial { + const version = RevisionSchema.parse(snapshot.version) + const accepted: Partial = {} + for (const key of SETTINGS_KEYS) { + if (!Object.hasOwn(snapshot.values, key)) continue + if (version < (observedVersions.get(key) ?? -1)) continue + observedVersions.set(key, version) + Object.assign(accepted, { [key]: snapshot.values[key] }) + } + return accepted + } + + async function getSnapshot( + keys?: SettingsKey[] + ): Promise> { + return await bridge.invoke(settingsGetSnapshotRoute.name, { keys }) } async function getSystemFonts(): Promise { @@ -115,7 +136,9 @@ export function createSettingsClient(bridge: DeepchatBridge = getDeepchatBridge( values: Partial }) => void ) { - return bridge.on(settingsChangedEvent.name, listener) + return bridge.on(settingsChangedEvent.name, (payload) => + listener({ ...payload, values: acceptSnapshot(payload) }) + ) } function onCommandShellChanged( @@ -126,6 +149,7 @@ export function createSettingsClient(bridge: DeepchatBridge = getDeepchatBridge( return { getSnapshot, + acceptSnapshot, getSystemFonts, getCommandShell, updateCommandShell, diff --git a/src/renderer/settings/components/MemoryInlinePanel.vue b/src/renderer/settings/components/MemoryInlinePanel.vue index f895971ba2..960b096c06 100644 --- a/src/renderer/settings/components/MemoryInlinePanel.vue +++ b/src/renderer/settings/components/MemoryInlinePanel.vue @@ -816,11 +816,9 @@ function handleKeydown(event: KeyboardEvent): void { } } -watch( - () => [props.agentId, props.memory?.id, props.mode], - () => seed(), - { immediate: true } -) +watch([() => props.agentId, () => props.memory?.id, () => props.mode], () => seed(), { + immediate: true +}) watch(busy, (value) => emit('busy', value), { immediate: true, flush: 'sync' }) watch(dirty, (value) => emit('dirty', value), { immediate: true, flush: 'sync' }) watch(sourceOpen, () => void loadSource()) diff --git a/src/renderer/settings/components/OcrSettings.vue b/src/renderer/settings/components/OcrSettings.vue index b20037149f..63e02dd6fb 100644 --- a/src/renderer/settings/components/OcrSettings.vue +++ b/src/renderer/settings/components/OcrSettings.vue @@ -295,6 +295,7 @@ import { Switch } from '@shadcn/components/ui/switch' import SettingsPageShell from './control-center/SettingsPageShell.vue' import SettingsSectionCard from './control-center/SettingsSectionCard.vue' import { notifyRenderer } from '@renderer-notifications/rendererNotificationPort' +import type { SettingsSnapshotValues } from '@shared/contracts/routes' type OcrBackend = 'auto' | 'cpu' @@ -402,9 +403,20 @@ onMounted(() => { onBeforeUnmount(() => { disposed = true + unsubscribeSettings() pausePolling() }) +const unsubscribeSettings = settingsClient.onChanged(({ values }) => { + if (!disposed) applySettingsValues(values) +}) + +function applySettingsValues(values: Partial): void { + if (typeof values.ocrAutoExtractForNonVisionModels === 'boolean') + automaticExtractionEnabled.value = values.ocrAutoExtractForNonVisionModels + if (values.ocrBackend !== undefined) backend.value = values.ocrBackend +} + watch(pollingAllowed, (allowed) => { pausePolling() if (allowed) activatePolling() @@ -415,12 +427,12 @@ async function loadSettings(): Promise { settingsOperationPending.value = true try { - const values = await settingsClient.getSnapshot([ + const snapshot = await settingsClient.getSnapshot([ 'ocrAutoExtractForNonVisionModels', 'ocrBackend' ]) - automaticExtractionEnabled.value = values.ocrAutoExtractForNonVisionModels ?? true - backend.value = values.ocrBackend ?? 'auto' + if (disposed) return + applySettingsValues(settingsClient.acceptSnapshot(snapshot)) settingsReady.value = true notifyRenderer({ kind: 'success', @@ -428,6 +440,7 @@ async function loadSettings(): Promise { title: t('common.saved') }) } catch (error) { + if (disposed) return settingsReady.value = false console.error('[OcrSettings] Failed to load settings', error) notifyRenderer({ @@ -445,13 +458,15 @@ async function updateAutomaticExtraction(value: boolean): Promise { settingsOperationPending.value = true try { const result = await settingsClient.update([{ key: 'ocrAutoExtractForNonVisionModels', value }]) - automaticExtractionEnabled.value = result.values.ocrAutoExtractForNonVisionModels ?? value + if (disposed) return + applySettingsValues(settingsClient.acceptSnapshot(result)) notifyRenderer({ kind: 'success', code: 'settings.ocr.autoExtractUpdated', title: t('common.saved') }) } catch (error) { + if (disposed) return console.error('[OcrSettings] Failed to update automatic extraction', error) notifyRenderer({ kind: 'error', @@ -468,13 +483,15 @@ async function updateBackend(value: AcceptableValue): Promise { settingsOperationPending.value = true try { const result = await settingsClient.update([{ key: 'ocrBackend', value }]) - backend.value = result.values.ocrBackend ?? value + if (disposed) return + applySettingsValues(settingsClient.acceptSnapshot(result)) notifyRenderer({ kind: 'success', code: 'settings.ocr.backendUpdated', title: t('common.saved') }) } catch (error) { + if (disposed) return console.error('[OcrSettings] Failed to update backend', error) notifyRenderer({ kind: 'error', diff --git a/src/renderer/src/stores/uiSettingsStore.ts b/src/renderer/src/stores/uiSettingsStore.ts index fd6be9d70c..e663c3e247 100644 --- a/src/renderer/src/stores/uiSettingsStore.ts +++ b/src/renderer/src/stores/uiSettingsStore.ts @@ -114,7 +114,7 @@ export const useUiSettingsStore = defineStore('uiSettings', () => { } const result = await settingsClient.update(changes) - applySettingsValues(result.values) + applySettingsValues(settingsClient.acceptSnapshot(result)) } const loadSettings = async () => { @@ -125,7 +125,7 @@ export const useUiSettingsStore = defineStore('uiSettings', () => { const nextLoadPromise = (async () => { const snapshot = await settingsClient.getSnapshot() - applySettingsValues(snapshot) + applySettingsValues(settingsClient.acceptSnapshot(snapshot)) })() settingsLoadPromise = nextLoadPromise @@ -328,8 +328,6 @@ export const useUiSettingsStore = defineStore('uiSettings', () => { value: nextValue } ]) - - launchAtLoginEnabled.value = nextValue } const setLoggingEnabled = async (enabled: boolean) => { diff --git a/src/shared/contracts/events/settings.events.ts b/src/shared/contracts/events/settings.events.ts index 4b78644a02..ca06912c89 100644 --- a/src/shared/contracts/events/settings.events.ts +++ b/src/shared/contracts/events/settings.events.ts @@ -1,6 +1,6 @@ import { z } from 'zod' import { AgentCommandShellConfigSchema } from '../../commandShell' -import { TimestampMsSchema, defineEventContract } from '../common' +import { RevisionSchema, TimestampMsSchema, defineEventContract } from '../common' import { SettingsKeySchema, SettingsSnapshotValuesSchema } from '../routes/settings.routes' const SettingsRouteNameSchema = z.enum([ @@ -38,7 +38,7 @@ export const settingsChangedEvent = defineEventContract({ name: 'settings.changed', payload: z.object({ changedKeys: z.array(SettingsKeySchema).min(1), - version: TimestampMsSchema, + version: RevisionSchema, values: SettingsSnapshotValuesSchema.partial() }) }) diff --git a/src/shared/contracts/routes.ts b/src/shared/contracts/routes.ts index 0ed8fbc145..ae292afab1 100644 --- a/src/shared/contracts/routes.ts +++ b/src/shared/contracts/routes.ts @@ -490,6 +490,8 @@ import { } from './routes/runs.routes' import { sessionsActivateRoute, + sessionsGetWidgetSnapshotRoute, + sessionsOpenDesktopRoute, sessionsSearchReferenceCandidatesRoute, sessionsResolveReferenceRoute, sessionsClearMessagesRoute, @@ -1017,6 +1019,8 @@ const DEEPCHAT_ROUTE_CATALOG_PART_4 = { [sessionsRestoreRoute.name]: sessionsRestoreRoute, [sessionsListMessagesPageRoute.name]: sessionsListMessagesPageRoute, [sessionsListRoute.name]: sessionsListRoute, + [sessionsGetWidgetSnapshotRoute.name]: sessionsGetWidgetSnapshotRoute, + [sessionsOpenDesktopRoute.name]: sessionsOpenDesktopRoute, [sessionsListLightweightRoute.name]: sessionsListLightweightRoute, [sessionsGetLightweightByIdsRoute.name]: sessionsGetLightweightByIdsRoute, [sessionsActivateRoute.name]: sessionsActivateRoute, diff --git a/src/shared/contracts/routes/mcp.routes.ts b/src/shared/contracts/routes/mcp.routes.ts index 2bf0aecd8e..d708fd9e9c 100644 --- a/src/shared/contracts/routes/mcp.routes.ts +++ b/src/shared/contracts/routes/mcp.routes.ts @@ -649,13 +649,13 @@ const McpAppDescriptorSchema: z.ZodType = z.object({ resourceUri: z.string().startsWith('ui://').max(4096), resourceMimeType: z.literal('text/html;profile=mcp-app') }) -const McpAppCspSchema = z.object({ +export const McpAppCspSchema = z.object({ connectDomains: z.array(z.string().max(2048)).max(64).optional(), resourceDomains: z.array(z.string().max(2048)).max(64).optional(), frameDomains: z.array(z.string().max(2048)).max(64).optional(), baseUriDomains: z.array(z.string().max(2048)).max(64).optional() }) -const McpAppPermissionsSchema = z.object({ +export const McpAppPermissionsSchema = z.object({ camera: z.object({}).optional(), microphone: z.object({}).optional(), geolocation: z.object({}).optional(), diff --git a/src/shared/contracts/routes/sessions.routes.ts b/src/shared/contracts/routes/sessions.routes.ts index 7e978b5ade..fb44db7449 100644 --- a/src/shared/contracts/routes/sessions.routes.ts +++ b/src/shared/contracts/routes/sessions.routes.ts @@ -36,6 +36,7 @@ import { } from '@shared/types/execution-journal-audit' import { AttachmentFallbackPolicySchema, + AgentBootstrapItemSchema, AttachmentPreparationSummarySchema, SessionListItemSchema, SessionPageCursorSchema, @@ -58,6 +59,38 @@ import { defineRouteContract } from '../common' import type { RouteContract } from '../common' + +export const sessionsGetWidgetSnapshotRoute = defineRouteContract({ + name: 'sessions.getWidgetSnapshot', + input: z.object({}), + output: z.object({ + snapshot: z.object({ + expanded: z.boolean(), + activeCount: z.number().int().nonnegative(), + sessions: z.array( + z.object({ + id: EntityIdSchema, + title: z.string(), + status: z.enum(['in_progress', 'done', 'error']), + updatedAt: z.number().finite(), + agent: AgentBootstrapItemSchema.pick({ + id: true, + name: true, + type: true, + icon: true, + avatar: true + }) + }) + ) + }) + }) +}) + +export const sessionsOpenDesktopRoute = defineRouteContract({ + name: 'sessions.openDesktop', + input: z.object({ sessionId: EntityIdSchema }), + output: z.object({ opened: z.boolean() }) +}) import { AcpConfigStateSchema, UsageDashboardDataSchema } from '../domainSchemas' import { PROGRAMMATIC_TOOL_BATCH_MAX_STEPS } from './tools.routes' import { AcpAuthChallengeSchema } from './acp-auth.routes' diff --git a/src/shared/contracts/routes/settings.routes.ts b/src/shared/contracts/routes/settings.routes.ts index 0cd4f6a304..1ff318c3ad 100644 --- a/src/shared/contracts/routes/settings.routes.ts +++ b/src/shared/contracts/routes/settings.routes.ts @@ -1,6 +1,6 @@ import { z } from 'zod' import { AgentCommandShellConfigSchema, GitBashAvailabilitySchema } from '../../commandShell' -import { TimestampMsSchema, defineRouteContract } from '../common' +import { RevisionSchema, TimestampMsSchema, defineRouteContract } from '../common' export const SETTINGS_KEYS = [ 'fontSizeLevel', @@ -117,7 +117,7 @@ export const settingsGetSnapshotRoute = defineRouteContract({ }) .default({}), output: z.object({ - version: TimestampMsSchema, + version: RevisionSchema, values: SettingsSnapshotValuesSchema.partial() }) }) @@ -166,7 +166,7 @@ export const settingsUpdateRoute = defineRouteContract({ changes: z.array(SettingsChangeSchema).min(1) }), output: z.object({ - version: TimestampMsSchema, + version: RevisionSchema, changedKeys: z.array(SettingsKeySchema).min(1), values: SettingsSnapshotValuesSchema.partial() }) diff --git a/src/shared/cronJobs.ts b/src/shared/cronJobs.ts index 5d6827caa6..50d29170e9 100644 --- a/src/shared/cronJobs.ts +++ b/src/shared/cronJobs.ts @@ -187,8 +187,11 @@ export type SchedulerCommand = now: number } | { - type: 'RUN_NOW' - jobId: string + type: 'SCAN_RESULT' + requestId: number + enabledJobCount: number + nextRunAt: number | null + error?: string now: number } | { @@ -210,11 +213,8 @@ export type SchedulerEvent = now: number } | { - type: 'RUN_DUE' - jobId: string - runId: string - scheduledAt: number - reason: CronJobRunReason + type: 'SCAN_DUE' + requestId: number now: number } | { @@ -227,5 +227,6 @@ export type SchedulerEvent = type: 'ERROR' message: string stack?: string + fatal?: boolean now: number } diff --git a/src/shared/harness/admission.ts b/src/shared/harness/admission.ts new file mode 100644 index 0000000000..32141fc0b5 --- /dev/null +++ b/src/shared/harness/admission.ts @@ -0,0 +1,258 @@ +import { z } from 'zod' +import { types as utilTypes } from 'node:util' +import type { NumberDistribution } from '../../main/lib/boundedNumberRing' + +export const DEFAULT_AGENT_INVOCATION_CAPACITY = 6 +export const DEFAULT_AGENT_INVOCATION_MAX_PENDING = 256 +const MAX_AGENT_INVOCATION_IDENTIFIER_LENGTH = 256 + +export interface AgentInvocationPermit { + release(): void +} + +export type AgentInvocationLeaseState = 'suspended' | 'acquiring' | 'active' | 'released' + +export interface AgentInvocationLease { + readonly state: AgentInvocationLeaseState + resume(options?: { signal?: AbortSignal }): Promise + suspend(): void + release(): void +} + +export interface AgentInvocationAdmissionOptions { + ownerId: string + maxActiveForOwner?: number + signal?: AbortSignal + correlation?: AgentInvocationAdmissionCorrelation +} + +export interface AgentInvocationAdmissionCorrelation { + kind: 'live_delegation' + parentSessionId: string + delegationId: string + turnId: string +} + +export interface AgentInvocationAdmissionSnapshot { + capacity: number + active: number + pending: number + pendingOwners: number + closed: boolean + activeHighWater: number + pendingHighWater: number + granted: number + rejected: number + observationsDropped: number + waitMs: NumberDistribution + holdMs: NumberDistribution +} + +type AgentInvocationAdmissionState = Pick< + AgentInvocationAdmissionSnapshot, + 'capacity' | 'active' | 'pending' +> + +export type AgentInvocationAdmissionObservation = + | ({ + type: 'queued' + acquisitionSeq: number + } & AgentInvocationAdmissionCorrelation & + AgentInvocationAdmissionState) + | ({ + type: 'granted' + acquisitionSeq: number + waitMs?: number + } & AgentInvocationAdmissionCorrelation & + AgentInvocationAdmissionState) + | ({ + type: 'released' + acquisitionSeq: number + holdMs?: number + reason: 'permit_released' | 'lease_suspended' | 'lease_released' + active: number + pending: number + } & AgentInvocationAdmissionCorrelation) + | ({ + type: 'rejected' + acquisitionSeq: number + waitMs?: number + reason: 'queue_full' | 'aborted' | 'closed' + } & AgentInvocationAdmissionCorrelation & + AgentInvocationAdmissionState) + | ({ + type: 'closed' + } & Omit) + +export interface AgentInvocationAdmissionDiagnosticsOptions { + observe?: (observation: AgentInvocationAdmissionObservation) => void | Promise + observationsEnabled?: () => boolean +} + +export interface AgentInvocationAdmissionPort { + acquire(options: AgentInvocationAdmissionOptions): Promise + createLease(options: AgentInvocationAdmissionOptions): AgentInvocationLease + run(options: AgentInvocationAdmissionOptions, task: () => Promise): Promise +} + +export class AgentInvocationAdmissionClosedError extends Error { + constructor(message = 'Agent invocation admission is closed.') { + super(message) + this.name = 'AgentInvocationAdmissionClosedError' + } +} + +export class AgentInvocationAdmissionAbortedError extends Error { + constructor() { + super('Agent invocation admission was cancelled while queued.') + this.name = 'AgentInvocationAdmissionAbortedError' + } +} + +export class AgentInvocationAdmissionQueueFullError extends Error { + constructor(maxPending: number) { + super(`Agent invocation admission queue is full (${maxPending} pending).`) + this.name = 'AgentInvocationAdmissionQueueFullError' + } +} + +export interface InternalAgentInvocationPermit extends AgentInvocationPermit { + releaseWithReason(reason: 'permit_released' | 'lease_suspended' | 'lease_released'): void +} + +export function snapshotAdmissionCorrelation( + correlation: AgentInvocationAdmissionCorrelation | undefined +): AgentInvocationAdmissionCorrelation | undefined { + if (!correlation) return undefined + try { + if (typeof correlation !== 'object' || utilTypes.isProxy(correlation)) return undefined + const prototype = Object.getPrototypeOf(correlation) + if (prototype !== Object.prototype && prototype !== null) return undefined + const kind = Object.getOwnPropertyDescriptor(correlation, 'kind') + const parentSessionId = Object.getOwnPropertyDescriptor(correlation, 'parentSessionId') + const delegationId = Object.getOwnPropertyDescriptor(correlation, 'delegationId') + const turnId = Object.getOwnPropertyDescriptor(correlation, 'turnId') + if ( + !kind || + !('value' in kind) || + kind.value !== 'live_delegation' || + !parentSessionId || + !('value' in parentSessionId) || + !isBoundedAdmissionIdentifier(parentSessionId.value) || + !delegationId || + !('value' in delegationId) || + !isBoundedAdmissionIdentifier(delegationId.value) || + !turnId || + !('value' in turnId) || + !isBoundedAdmissionIdentifier(turnId.value) + ) { + return undefined + } + return { + kind: kind.value, + parentSessionId: parentSessionId.value, + delegationId: delegationId.value, + turnId: turnId.value + } + } catch { + return undefined + } +} + +export function normalizeAdmissionOwnerId(ownerId: string): string { + const normalized = ownerId.trim() + if (!normalized || normalized.length > MAX_AGENT_INVOCATION_IDENTIFIER_LENGTH) { + throw new Error( + `Agent invocation ownerId must contain 1 to ${MAX_AGENT_INVOCATION_IDENTIFIER_LENGTH} characters.` + ) + } + return normalized +} + +function isBoundedAdmissionIdentifier(value: unknown): value is string { + return ( + typeof value === 'string' && + value.length > 0 && + value.length <= MAX_AGENT_INVOCATION_IDENTIFIER_LENGTH + ) +} + +export function normalizeAdmissionOwnerLimit( + requested: number | undefined, + capacity: number +): number { + if (requested === undefined) { + return capacity + } + if (!Number.isSafeInteger(requested) || requested < 1) { + throw new Error('Agent invocation owner limit must be a positive safe integer.') + } + return Math.min(requested, capacity) +} + +const CountSchema = z.number().int().nonnegative().max(Number.MAX_SAFE_INTEGER) +const DurationSchema = z.number().finite().nonnegative() +const DistributionSchema = z + .object({ + samples: CountSchema, + p50: DurationSchema.nullable(), + p95: DurationSchema.nullable(), + max: DurationSchema.nullable() + }) + .strict() +const stateShape = { capacity: CountSchema, active: CountSchema, pending: CountSchema } +const correlationShape = { + kind: z.literal('live_delegation'), + parentSessionId: z.string().min(1).max(256), + delegationId: z.string().min(1).max(256), + turnId: z.string().min(1).max(256), + acquisitionSeq: CountSchema +} +const diagnosticShape = { + activeHighWater: CountSchema, + pendingHighWater: CountSchema, + granted: CountSchema, + rejected: CountSchema, + observationsDropped: CountSchema, + waitMs: DistributionSchema, + holdMs: DistributionSchema +} +export const AdmissionSnapshotSchema = z + .object({ + ...stateShape, + ...diagnosticShape, + pendingOwners: CountSchema, + closed: z.boolean() + }) + .strict() +export const AdmissionObservationSchema = z.discriminatedUnion('type', [ + z.object({ type: z.literal('queued'), ...correlationShape, ...stateShape }).strict(), + z + .object({ + type: z.literal('granted'), + ...correlationShape, + ...stateShape, + waitMs: DurationSchema.optional() + }) + .strict(), + z + .object({ + type: z.literal('released'), + ...correlationShape, + active: CountSchema, + pending: CountSchema, + holdMs: DurationSchema.optional(), + reason: z.enum(['permit_released', 'lease_suspended', 'lease_released']) + }) + .strict(), + z + .object({ + type: z.literal('rejected'), + ...correlationShape, + ...stateShape, + waitMs: DurationSchema.optional(), + reason: z.enum(['queue_full', 'aborted', 'closed']) + }) + .strict(), + z.object({ type: z.literal('closed'), ...stateShape, ...diagnosticShape }).strict() +]) diff --git a/src/shared/harness/browser.ts b/src/shared/harness/browser.ts new file mode 100644 index 0000000000..c89545198f --- /dev/null +++ b/src/shared/harness/browser.ts @@ -0,0 +1,71 @@ +import { z } from 'zod' +import { YoBrowserStatusSchema } from '../contracts/domainSchemas' + +const id = z.string().uuid() +const error = z.object({ name: z.string().max(256), message: z.string().max(8192) }).strict() +export const CoreBrowserCommandSchema = z.discriminatedUnion('operation', [ + z + .object({ + operation: z.literal('open'), + id, + toolName: z.enum(['get_browser_status', 'load_url', 'cdp_send']), + args: z.record(z.string(), z.unknown()), + conversationId: z.string().optional(), + runId: z.string().optional(), + authorize: z.boolean() + }) + .strict(), + z.object({ operation: z.literal('cancel'), id }).strict(), + z.object({ operation: z.literal('close') }).strict() +]) +export type CoreBrowserCommand = z.infer + +const unavailable = z + .object({ + ok: z.literal(false), + error: z + .object({ + code: z.literal('yobrowser_unavailable'), + message: z.string(), + recoverable: z.literal(true), + sessionId: z.string(), + method: z.string(), + browserStatus: YoBrowserStatusSchema.nullable(), + suggestedNextActions: z.array(z.string()) + }) + .strict() + }) + .strict() + +export const CoreBrowserOutcomeSchema = z.discriminatedUnion('ok', [ + z.object({ ok: z.literal(true), value: z.string() }).strict(), + z.object({ ok: z.literal(false), error, unavailable: unavailable.optional() }).strict() +]) +export type CoreBrowserOutcome = z.infer +export const CoreBrowserEventSchema = z.discriminatedUnion('type', [ + z + .object({ + type: z.literal('browser.authorize'), + id, + args: z.record(z.string(), z.unknown()) + }) + .strict(), + z.object({ type: z.literal('browser.completed'), id, outcome: CoreBrowserOutcomeSchema }).strict() +]) +export type CoreBrowserEvent = z.infer +export const CoreBrowserAuthorizationSchema = z.discriminatedUnion('ok', [ + z.object({ ok: z.literal(true) }).strict(), + z.object({ ok: z.literal(false), error }).strict() +]) +export type CoreBrowserAuthorization = z.infer + +export function isCoreBrowserDrain(input: unknown): boolean { + const parsed = z.tuple([CoreBrowserCommandSchema]).safeParse(input) + return parsed.success && parsed.data[0].operation !== 'open' +} + +export function coreBrowserError(input: unknown): z.infer { + return input instanceof Error + ? { name: input.name.slice(0, 256), message: input.message.slice(0, 8192) } + : { name: 'BrowserOperationError', message: 'Native browser operation failed' } +} diff --git a/src/shared/harness/configuration.ts b/src/shared/harness/configuration.ts new file mode 100644 index 0000000000..4aadf38f38 --- /dev/null +++ b/src/shared/harness/configuration.ts @@ -0,0 +1,86 @@ +import { z } from 'zod' +import { settingsGetSnapshotRoute, settingsUpdateRoute } from '../contracts/routes' +import { createDeepchatEventEnvelope, type DeepchatEventEnvelope } from '../contracts/events' +import { SUPPORTED_LOCALES } from '../locales' +import type { NativeConfigurationSnapshot } from '../types/nativeConfiguration' + +export const CoreConfigurationCommandSchema = z.discriminatedUnion('operation', [ + z.object({ operation: z.literal('read'), input: settingsGetSnapshotRoute.input }).strict(), + z.object({ operation: z.literal('update'), input: settingsUpdateRoute.input }).strict(), + z.object({ operation: z.literal('nativeSnapshot') }).strict(), + z + .object({ + operation: z.literal('floatingBounds'), + bounds: z + .object({ + x: z.number().finite(), + y: z.number().finite(), + dockSide: z.enum(['left', 'right']) + }) + .strict() + }) + .strict(), + z.object({ operation: z.literal('initialize') }).strict() +]) +export type CoreConfigurationCommand = z.infer + +const theme = z.enum(['dark', 'light', 'system']) +export const NativeConfigurationSnapshotSchema: z.ZodType = z + .object({ + ownerId: z.string().uuid(), + version: z.number().int().positive().max(Number.MAX_SAFE_INTEGER), + language: z.enum(SUPPORTED_LOCALES), + notificationsEnabled: z.boolean(), + closeToQuit: z.boolean(), + contentProtectionEnabled: z.boolean(), + shortcutKeys: z.record(z.string().max(256), z.string().max(256)), + floatingButtonEnabled: z.boolean(), + floatingButtonBounds: z + .object({ + x: z.number().finite(), + y: z.number().finite(), + dockSide: z.enum(['left', 'right']) + }) + .strict() + .nullable(), + loggingEnabled: z.boolean(), + privacyModeEnabled: z.boolean(), + updateChannel: z.enum(['stable', 'beta']) + }) + .strict() + +const event = z + .object({ + name: z.enum([ + 'settings.changed', + 'config.language.changed', + 'config.theme.changed', + 'config.systemTheme.changed', + 'config.floatingButton.changed', + 'config.shortcutKeys.changed' + ]), + payload: z.unknown() + }) + .strict() + .transform( + (value): DeepchatEventEnvelope => createDeepchatEventEnvelope(value.name, value.payload) + ) + +export const CoreConfigurationNativeSchema = z.discriminatedUnion('operation', [ + z.object({ operation: z.literal('readTheme') }).strict(), + z.object({ operation: z.literal('applyTheme'), theme }).strict(), + z.object({ operation: z.literal('refreshLanguage'), locale: z.enum(SUPPORTED_LOCALES) }).strict(), + z.object({ operation: z.literal('refreshTheme'), isDark: z.boolean() }).strict(), + z.object({ operation: z.literal('readLaunchAtLogin') }).strict(), + z.object({ operation: z.literal('setLaunchAtLogin'), enabled: z.boolean() }).strict(), + z.object({ operation: z.literal('applyContentProtection'), enabled: z.boolean() }).strict(), + z.object({ operation: z.literal('applyFloatingButtonEnabled'), enabled: z.boolean() }).strict(), + z.object({ operation: z.literal('applyLogging'), enabled: z.boolean() }).strict(), + z.object({ operation: z.literal('openLoggingFolder') }).strict(), + z.object({ operation: z.literal('enumerateFonts') }).strict(), + z + .object({ operation: z.literal('snapshot'), snapshot: NativeConfigurationSnapshotSchema }) + .strict(), + z.object({ operation: z.literal('events'), events: z.array(event).max(64) }).strict() +]) +export type CoreConfigurationNative = z.infer diff --git a/src/shared/harness/control.ts b/src/shared/harness/control.ts new file mode 100644 index 0000000000..25a79c2fa6 --- /dev/null +++ b/src/shared/harness/control.ts @@ -0,0 +1,391 @@ +import { z } from 'zod' + +export const HARNESS_CONTROL_METADATA_BYTES = 16 * 1024 * 1024 +export const HARNESS_CONTROL_PENDING = 1024 + +const id = z.string().min(1) +const status = z.enum(['idle', 'generating', 'error']) +const scope = { scopeId: id } +const operation = { ...scope, operationId: id } +const run = { ...scope, runId: id } + +export const HarnessControlOwnerSchema = z.discriminatedUnion('kind', [ + z.object({ kind: z.literal('operation'), id }).strict(), + z.object({ kind: z.literal('run'), id }).strict(), + z.object({ kind: z.literal('orphan') }).strict() +]) +export type HarnessControlOwner = z.infer + +const interactionOrigin = z.enum([ + 'pre-check-permission', + 'question', + 'post-call-permission', + 'skill-draft-confirmation', + 'acp-permission' +]) +export const HarnessPendingInteractionSchema = z + .object({ + messageId: id, + toolCallId: id, + origin: interactionOrigin, + order: z.number().int().nonnegative().max(Number.MAX_SAFE_INTEGER) + }) + .strict() +export type HarnessPendingInteraction = z.infer + +export const HarnessToolBatchSchema = z + .object({ + id, + callOrder: z.array(id), + invokedCallIds: z.array(id), + committedResultCallIds: z.array(id), + pendingInteractionCallIds: z.array(id) + }) + .strict() +export type HarnessToolBatch = z.infer + +export const HarnessInteractionUpdateSchema = z + .object({ + interactions: z.array(HarnessPendingInteractionSchema), + batch: HarnessToolBatchSchema.nullable().optional(), + reconcile: z.boolean().optional() + }) + .strict() +export type HarnessInteractionUpdate = z.infer + +const inputSource = z.enum(['send', 'queue', 'steer']) +const inputWakeReason = z.enum(['completed', 'enqueue', 'manual']) +const inputDisposition = z.enum([ + 'consume', + 'block', + 'release-for-mutation', + 'release-before-user-fact', + 'release-after-rollback' +]) +const claimEffectAction = z.union([z.enum(['user-fact', 'rollback']), inputDisposition]) +const inputEffectOutcome = z.enum(['applied', 'rejected', 'unknown']) +export const HarnessInputSnapshotSchema = z + .object({ + queue: z.array( + z.object({ id, state: z.enum(['pending', 'blocked', 'retry_required']) }).strict() + ), + steerHead: z + .object({ id, state: z.enum(['pending', 'blocked']) }) + .strict() + .nullable(), + hasClaimed: z.boolean(), + hasBlocking: z.boolean(), + awaitingFollowUp: z.boolean() + }) + .strict() +export type HarnessInputSnapshot = z.infer + +export const HarnessBeginInputResultSchema = z + .object({ + action: z.enum(['pending', 'claim', 'prepare', 'steer', 'busy', 'blocked']), + claimId: id.optional(), + effectId: id.optional(), + inputId: id.optional(), + source: inputSource.optional(), + drainLeaseId: id.optional(), + consumeBeforeProviderStream: z.boolean(), + mergeItemId: id.optional(), + wakeReason: z.literal('enqueue').optional() + }) + .strict() +export type HarnessBeginInputResult = z.infer + +export const HarnessQueryInputResultSchema = z + .object({ + canDrain: z.boolean(), + hasRestartHeld: z.boolean(), + onlyRestartHeld: z.boolean() + }) + .strict() +export type HarnessQueryInputResult = z.infer + +export const HarnessClaimEffectGrantSchema = z + .object({ effectId: id, action: claimEffectAction }) + .strict() +export type HarnessClaimEffectGrant = z.infer +export const HarnessEndInputResultSchema = z + .object({ released: z.boolean(), nextWake: inputWakeReason.nullable() }) + .strict() + +const inputClaim = z + .object({ + id, + inputId: id.nullable(), + source: inputSource, + disposition: inputDisposition.nullable(), + outcome: inputEffectOutcome.nullable(), + consumeBeforeProviderStream: z.boolean(), + effect: z + .object({ + id, + action: z.union([z.literal('claim'), claimEffectAction]), + outcome: inputEffectOutcome.nullable() + }) + .strict() + .nullable() + }) + .strict() + +export const HarnessControlLeaseTargetSchema = z.discriminatedUnion('kind', [ + z.object({ kind: z.literal('interaction'), messageId: id, toolCallId: id }).strict(), + z.object({ kind: z.literal('resume'), messageId: id }).strict(), + z + .object({ + kind: z.literal('deferred'), + messageId: id, + toolCallId: id, + parentLeaseId: id, + owner: HarnessControlOwnerSchema + }) + .strict() +]) +export type HarnessControlLeaseTarget = z.infer +export interface HarnessControlLease { + readonly id: string + readonly target: HarnessControlLeaseTarget +} +const acceptanceLane = z.enum(['send', 'steer']) +export type HarnessAcceptanceLane = z.infer + +export const HarnessPermissionDecisionSchema = z.enum(['callback', 'direct', 'conflict', 'spent']) +const permission = { requestId: id, messageId: id, toolCallId: z.string() } + +export const HarnessControlCommandSchema = z.discriminatedUnion('method', [ + z + .object({ + method: z.literal('inputRecovery'), + sessions: z.array(z.object({ sessionId: id, heldInputIds: z.array(id) }).strict()) + }) + .strict(), + z + .object({ + method: z.literal('queryInput'), + ...scope, + snapshot: HarnessInputSnapshotSchema, + reason: inputWakeReason.optional() + }) + .strict(), + z + .object({ + method: z.literal('beginInput'), + ...scope, + requestId: id, + intent: z.enum(['enqueue', 'drain', 'prepare', 'steer']), + source: inputSource, + reason: inputWakeReason.optional(), + itemId: id.optional(), + snapshot: HarnessInputSnapshotSchema + }) + .strict(), + z + .object({ + method: z.literal('ackInputEffect'), + ...scope, + claimId: id.optional(), + effectId: id, + outcome: inputEffectOutcome, + inputId: id.optional(), + messageId: id.optional() + }) + .strict(), + z + .object({ + method: z.literal('prepareClaimEffect'), + ...scope, + owner: HarnessControlOwnerSchema, + claimId: id, + effectId: id, + action: claimEffectAction + }) + .strict(), + z + .object({ + method: z.literal('inputHold'), + ...scope, + action: z.enum(['release-item', 'resume']), + itemId: id.optional(), + queueIds: z.array(id).optional(), + headId: id.optional() + }) + .strict(), + z.object({ method: z.literal('endInput'), ...scope, drainLeaseId: id }).strict(), + z.object({ method: z.literal('open'), sessionId: id, ...scope }).strict(), + z.object({ method: z.literal('prepareOperation'), ...operation }).strict(), + z.object({ method: z.literal('prepareRun'), ...operation, runId: id, messageId: id }).strict(), + z + .object({ method: z.literal('commit'), ...scope, transitionId: id, aborted: z.boolean() }) + .strict(), + z.object({ method: z.literal('clearOperation'), ...operation, runId: id.nullable() }).strict(), + z.object({ method: z.literal('clearRun'), ...run }).strict(), + z.object({ method: z.literal('abort'), ...scope }).strict(), + z.object({ method: z.literal('operationAborted'), ...operation }).strict(), + z + .object({ + method: z.literal('status'), + ...scope, + owner: HarnessControlOwnerSchema, + status, + interactions: HarnessInteractionUpdateSchema.optional() + }) + .strict(), + z + .object({ + method: z.literal('updateInteractions'), + ...scope, + owner: HarnessControlOwnerSchema, + update: HarnessInteractionUpdateSchema + }) + .strict(), + z + .object({ + method: z.literal('advanceBatch'), + ...scope, + owner: HarnessControlOwnerSchema, + invokedCallId: id.optional(), + committedResultCallId: id.optional() + }) + .strict(), + z + .object({ + method: z.literal('interactionOrigin'), + ...scope, + owner: HarnessControlOwnerSchema, + messageId: id, + toolCallId: id, + origin: interactionOrigin + }) + .strict(), + z + .object({ + method: z.literal('checkInteraction'), + ...scope, + owner: HarnessControlOwnerSchema, + messageId: id, + toolCallId: id, + leaseId: id + }) + .strict(), + z.object({ method: z.literal('ready'), ...run }).strict(), + z.object({ method: z.literal('clearReady'), ...scope }).strict(), + z.object({ method: z.literal('abortDeferred'), ...scope }).strict(), + z + .object({ + method: z.literal('registerPermission'), + ...scope, + ...permission, + permissionId: id, + runId: id + }) + .strict(), + z + .object({ + method: z.literal('takePermission'), + ...scope, + ...permission, + permissionId: id.nullable(), + runId: id.nullable(), + leaseId: id, + granted: z.boolean(), + resolutionId: id + }) + .strict(), + z + .object({ + method: z.literal('acquireLease'), + ...scope, + leaseId: id, + target: HarnessControlLeaseTargetSchema + }) + .strict(), + z + .object({ + method: z.literal('releaseLease'), + ...scope, + leaseId: id, + target: HarnessControlLeaseTargetSchema + }) + .strict(), + z + .object({ + method: z.literal('parkInteraction'), + ...scope, + leaseId: id, + messageId: id, + toolCallId: id + }) + .strict(), + z + .object({ method: z.literal('clearSession'), sessionId: id, originScopeId: id.nullable() }) + .strict(), + z + .object({ method: z.literal('queueAcceptance'), ...scope, leaseId: id, lane: acceptanceLane }) + .strict(), + z + .object({ method: z.literal('releaseAcceptance'), ...scope, leaseId: id, lane: acceptanceLane }) + .strict(), + z.object({ method: z.literal('retire'), ...scope }).strict(), + z.object({ method: z.literal('release'), ...scope }).strict() +]) +export type HarnessControlCommand = z.infer + +const scopeSnapshot = z + .object({ + id, + sessionId: id, + current: z.boolean(), + status, + operation: z.object({ id, aborted: z.boolean() }).strict().nullable(), + run: z.object({ id, messageId: id, operationId: id }).strict().nullable(), + firstTurnReady: z.boolean(), + drainLeaseId: id.nullable(), + permissions: z.array(z.object({ requestId: id, id }).strict()), + deferred: z.array(z.object({ toolCallId: id, id }).strict()), + interactions: z.array(HarnessPendingInteractionSchema), + batch: HarnessToolBatchSchema.nullable(), + input: z + .object({ + activeSteerId: id.nullable(), + activeClaimId: id.nullable(), + claims: z.array(inputClaim), + restartHeldIds: z.array(id), + manuallyResumedIds: z.array(id), + wakeReason: inputWakeReason.nullable() + }) + .strict() + }) + .strict() +export type HarnessControlScope = z.infer + +export const HarnessControlReceiptSchema = z + .object({ + protocol: z.literal(1), + revision: z.number().int().nonnegative().max(Number.MAX_SAFE_INTEGER), + outcome: z.enum(['accepted', 'stale', 'cancelled']), + scope: scopeSnapshot.nullable(), + result: z.unknown(), + fences: z.array( + z.object({ kind: z.enum(['operation', 'run', 'scope', 'permission']), id }).strict() + ), + grants: z.array(z.object({ scopeId: id, leaseId: id }).strict()) + }) + .strict() +export type HarnessControlReceipt = z.infer + +export interface HarnessControlPort { + readonly closed: boolean + send( + command: HarnessControlCommand, + receive?: (receipt: HarnessControlReceipt) => void + ): Promise + transition( + command: Extract, + receive: (receipt: HarnessControlReceipt) => boolean + ): Promise + onClose(listener: () => void): () => void + onGrant(scopeId: string, listener: (leaseId: string) => void): () => void +} diff --git a/src/shared/harness/core.ts b/src/shared/harness/core.ts new file mode 100644 index 0000000000..e12102b244 --- /dev/null +++ b/src/shared/harness/core.ts @@ -0,0 +1,299 @@ +import { z } from 'zod' +import { CoreHelperEventsSchema, isCoreHelperDrain } from './helpers' +import { CoreBrowserEventSchema, isCoreBrowserDrain } from './browser' +import { CoreDesktopEventSchema, CoreWindowTargetSchema, isCoreDesktopDrain } from './desktop' +import { McpAppCspSchema, McpAppPermissionsSchema } from '../contracts/routes/mcp.routes' +import { hasDeepchatRouteContract, type DeepchatRouteName } from '../contracts/routes' +import { databaseRepairReasonSchema } from '../notifications/semanticNotification' + +export const CORE_INLINE_BYTES = 16 * 1024 +export const CORE_VALUE_BYTES = 64 * 1024 * 1024 +export const CORE_CARRIER_FILES = 4 +export const CORE_CALLS = 4 +export const CORE_ORDINARY_CALLS = CORE_CALLS - 1 +export const CORE_ROUTE_QUEUED = 64 +export const CORE_CONTROL_CALLS = 2 +export const CORE_NATIVE_CALLS = 16 +export const CORE_NATIVE_QUEUED = 64 +export const CORE_PRESENTATION_QUEUED = 256 + +export const CoreNativeMethodSchema = z.enum([ + 'crypto.available', + 'crypto.encrypt', + 'crypto.decrypt', + 'crypto.backend', + 'profile.logging', + 'profile.progress', + 'profile.unlock', + 'profile.recovery', + 'profile.quit', + 'profile.observation', + 'core.failure', + 'configuration', + 'helpers', + 'browser', + 'desktop' +]) +export type CoreNativeMethod = z.infer + +export const CoreFailureComponentSchema = z.enum([ + 'helpers', + 'browser', + 'desktop', + 'http', + 'control' +]) + +const profileObservation = z.object({ + durationMs: z.number().finite().nonnegative().optional(), + repairAttempted: z.boolean(), + schemaDiagnosis: z.enum(['completed', 'unavailable', 'not_completed']), + repairableIssueCount: z.number().int().nonnegative(), + manualIssueCount: z.number().int().nonnegative() +}) +export const CoreProfileObservationSchema = z.discriminatedUnion('outcome', [ + profileObservation.extend({ outcome: z.literal('completed') }).strict(), + profileObservation + .extend({ + outcome: z.literal('failed'), + error: z.union([ + z.object({ category: z.enum(['integrity', 'persistence']) }).strict(), + z.object({ category: z.literal('schema'), reason: databaseRepairReasonSchema }).strict() + ]) + }) + .strict() +]) + +export const CoreHostEventSchema = z.discriminatedUnion('type', [ + z.object({ type: z.literal('host.clearPermissions') }).strict(), + z.object({ type: z.literal('host.shutdownConfirmation') }).strict(), + z.object({ type: z.literal('host.systemResume') }).strict(), + z + .object({ + type: z.literal('host.settingsWindow'), + action: z.enum(['opened', 'closed', 'ready']) + }) + .strict() +]) +export const CoreShutdownConfirmationSchema = z + .object({ + title: z.string(), + description: z.string().optional(), + i18n: z.boolean().optional(), + icon: z.object({ icon: z.string(), class: z.string() }).strict().optional(), + buttons: z + .array( + z.object({ key: z.string(), label: z.string(), default: z.boolean().optional() }).strict() + ) + .optional(), + timeout: z.number().finite().optional() + }) + .strict() + .nullable() +export const CoreNativeEventSchema = z.union([ + CoreHelperEventsSchema, + CoreBrowserEventSchema, + CoreDesktopEventSchema, + CoreHostEventSchema +]) + +export function isCoreNativeDrain(method: CoreNativeMethod, args: unknown): boolean { + return ( + (method === 'core.failure' && z.tuple([CoreFailureComponentSchema]).safeParse(args).success) || + (method === 'helpers' && isCoreHelperDrain(args)) || + (method === 'browser' && isCoreBrowserDrain(args)) || + (method === 'desktop' && isCoreDesktopDrain(args)) + ) +} + +export const CoreCarrierConfigSchema = z + .object({ + generation: z.string().uuid(), + directory: z.string().min(1).max(8192), + key: z.string().regex(/^[A-Za-z0-9+/]{43}=$/) + }) + .strict() +export type CoreCarrierConfig = z.infer + +export const CorePayloadSchema = z.union([ + z.object({ inline: z.string().max(4 * Math.ceil(CORE_INLINE_BYTES / 3)) }).strict(), + z + .object({ + operation: z.string().uuid(), + sender: z.enum(['main', 'core']), + bytes: z + .number() + .int() + .min(CORE_INLINE_BYTES + 1) + .max(CORE_VALUE_BYTES) + }) + .strict() +]) +export type CorePayload = z.infer + +export const CoreMethodSchema = z.enum([ + 'openProfile', + 'assembleKernel', + 'startKernel', + 'invokeRoute', + 'invokeControlRoute', + 'mcpAppProtocol', + 'hostQuery', + 'configure', + 'observeSystemTheme', + 'nativeEvents', + 'status', + 'beginShutdown', + 'stop' +]) +export type CoreMethod = z.infer + +export const CoreHostQuerySchema = z.discriminatedUnion('operation', [ + z.object({ operation: z.literal('workspacePreview'), url: z.string().max(32768) }).strict(), + z.object({ operation: z.literal('hasProvider'), providerId: z.string().min(1).max(256) }).strict() +]) + +const mcpAppContext = CoreWindowTargetSchema.extend({ + windowId: z.number().int().positive().nullable() +}) +const mcpAppInstanceId = z.string().min(1).max(128) +export const CoreMcpAppProtocolCommandSchema = z.discriminatedUnion('operation', [ + z.object({ operation: z.literal('policy'), instanceId: mcpAppInstanceId }).strict(), + z + .object({ + operation: z.literal('permission'), + instanceId: mcpAppInstanceId, + context: mcpAppContext, + kinds: z + .array(z.enum(['camera', 'microphone', 'geolocation', 'clipboard-write'])) + .min(1) + .max(4) + }) + .strict() +]) +export const CoreMcpAppProtocolPolicySchema = z + .object({ + context: mcpAppContext, + csp: McpAppCspSchema.optional(), + permissions: McpAppPermissionsSchema.optional() + }) + .strict() + .nullable() + +export const CoreStatusSchema = z + .object({ + generation: z.string().uuid(), + pid: z.number().int().positive(), + node: z.string().max(64), + modules: z.string().max(32), + phase: z.enum([ + 'idle', + 'opening', + 'profile-open', + 'assembling', + 'kernel-assembled', + 'starting', + 'running', + 'stopping', + 'stopped' + ]), + businessReady: z.boolean() + }) + .strict() +export type CoreStatus = z.infer + +const callerId = z.number().int().positive().safe() +export const CoreRouteInvocationSchema = z + .object({ + routeName: z.string().max(256).refine(hasDeepchatRouteContract), + input: z.unknown(), + context: z + .object({ + caller: z + .object({ + kind: z.literal('renderer'), + webContentsId: callerId, + windowId: callerId.nullable() + }) + .strict() + }) + .strict() + }) + .strict() + +const controlRoutes = new Set([ + 'approvals.resolve', + 'chat.cancelSubmission', + 'chat.stopStream', + 'chat.respondToolInteraction', + 'chat.dismissToolInteraction', + 'runs.cancel', + 'oauth.openaiCodex.cancelLogin', + 'oauth.xaiGrok.cancelLogin', + 'acpAuth.cancel', + 'acp.elicitation.respond', + 'mcp.submitSamplingDecision', + 'mcp.cancelSamplingRequest', + 'mcp.submitElicitationDecision', + 'mcp.cancelElicitationRequest', + 'mcp.apps.submitConsent', + 'remoteControl.cancelFeishuAuth', + 'remoteControl.cancelFeishuInstall', + 'syncPeer.cancel', + 'toolchains.cancelInstall' +] satisfies DeepchatRouteName[]) + +export function isCoreControlRoute(routeName: string): boolean { + return controlRoutes.has(routeName) +} + +const directory = z.string().min(1).max(32768) +export const CoreKernelBootstrapSchema = z + .object({ + temporaryDirectory: directory, + appDataDirectory: directory, + documentsDirectory: directory.nullable(), + projectHomeDirectory: directory.nullable(), + resourcesPath: directory, + workingDirectory: directory, + builtinSkillsDirectories: z.array(directory).max(64), + providerCatalogPath: directory, + allowInsecureTls: z.boolean() + }) + .strict() +export type CoreKernelBootstrap = z.infer + +const error = z.object({ name: z.string().max(256), message: z.string().max(8192) }).strict() +export const CoreFrameSchema = z.union([ + z.object({ type: z.literal('coreReady'), runtime: CoreStatusSchema }).strict(), + z.object({ type: z.literal('coreStopped'), message: z.string().max(8192) }).strict(), + z + .object({ + type: z.literal('coreReply'), + id: z.number().int().positive(), + value: CorePayloadSchema + }) + .strict(), + z.object({ type: z.literal('coreReply'), id: z.number().int().positive(), error }).strict(), + z + .object({ + type: z.literal('coreNativeCall'), + id: z.number().int().positive(), + generation: z.string().uuid(), + method: CoreNativeMethodSchema, + args: CorePayloadSchema + }) + .strict() +]) +export type CoreFrame = z.infer + +export type CoreRequest = + | { type: 'coreStart'; config: CoreCarrierConfig } + | { type: 'coreCall'; id: number; method: CoreMethod; args: CorePayload } + | { type: 'coreNativeReply'; id: number; generation: string; value: CorePayload } + | { + type: 'coreNativeReply' + id: number + generation: string + error: { name: string; message: string } + } diff --git a/src/shared/harness/desktop.ts b/src/shared/harness/desktop.ts new file mode 100644 index 0000000000..9636345ace --- /dev/null +++ b/src/shared/harness/desktop.ts @@ -0,0 +1,238 @@ +import { z } from 'zod' +import { + createDeepchatEventEnvelope, + hasDeepchatEventContract, + approvalRequestedEvent, + approvalClosedEvent, + mcpAppConsentRequestEvent +} from '../contracts/events' +import { semanticNotificationIntentSchema } from '../notifications/semanticNotification' +import { SPLASH_DEBUG_MODES } from '../contracts/splash' +import { + RendererPerformanceRecordSchema, + fileSaveImageRoute, + fileCopyImageRoute, + computerUseSetPreviewModeRoute +} from '../contracts/routes' + +const text = z.string() +const empty = z.tuple([]) +const record = z.record(text, z.unknown()) +export const CoreProxySnapshotSchema = z + .object({ + version: z.number().int().nonnegative().max(Number.MAX_SAFE_INTEGER), + proxyUrl: text.nullable(), + noProxy: text + }) + .strict() +export type CoreProxySnapshot = z.infer +export const CoreWindowTargetSchema = z + .object({ windowId: z.number().int().positive(), webContentsId: z.number().int().positive() }) + .strict() +export const CoreApprovalTargetSchema = z.union([ + CoreWindowTargetSchema, + z.object({ host: z.literal('terminal') }).strict() +]) +const context = CoreWindowTargetSchema.extend({ windowId: z.number().int().positive().nullable() }) +const rendererCaller = context.extend({ kind: z.literal('renderer') }) +const envelope = z + .object({ name: text.refine(hasDeepchatEventContract), payload: z.unknown() }) + .strict() + .transform((value) => { + if (!hasDeepchatEventContract(value.name)) throw new Error('Unknown Core event') + return createDeepchatEventEnvelope(value.name, value.payload) + }) +const preview = z + .object({ + conversationId: text, + runId: text, + toolCallId: text, + toolName: text, + args: record, + source: z.object({ serverName: text, ownerPluginId: text.optional() }).strict() + }) + .strict() + +export const CoreDesktopArguments = { + 'configurationHost.pause': empty, + 'configurationHost.resume': empty, + 'configurationHost.stopWrites': empty, + 'configurationHost.drainWrites': empty, + 'configurationHost.startWrites': empty, + isSettingsWindow: z.tuple([z.number().int().positive().nullable()]), + isMainWindowContext: z.tuple([rendererCaller]), + 'sessionPresentation.focusBrowser': z.tuple([text, rendererCaller]), + 'sessionPresentation.setComputerUsePreview': z.tuple([ + text, + computerUseSetPreviewModeRoute.input.shape.mode, + rendererCaller + ]), + 'sessionPresentation.dismissComputerUsePreview': z.tuple([text, text, rendererCaller]), + 'rendererPerformance.record': z.tuple([RendererPerformanceRecordSchema]), + 'splash.showDebugScenario': z.tuple([z.enum(SPLASH_DEBUG_MODES)]), + 'splash.closeDebugScenario': empty, + 'images.saveImage': z.tuple([fileSaveImageRoute.input]), + 'images.copyImage': z.tuple([fileCopyImageRoute.input]), + pickToolchainPath: empty, + hasTrustedRenderer: empty, + applyProxy: z.tuple([z.enum(['system', 'none', 'custom']), text, z.boolean()]), + 'copilotAuthorization.browser': z.tuple([text.optional()]), + 'copilotAuthorization.device': z.tuple([text.optional()]), + 'workspaceDesktop.revealFile': z.tuple([text]), + 'workspaceDesktop.openFile': z.tuple([text]), + 'workspaceDesktop.listFileOpenApps': z.tuple([text]), + 'workspaceDesktop.openFileWithApp': z.tuple([text, text]), + 'approvalPresentation.getTarget': empty, + 'approvalPresentation.present': z.tuple([ + CoreApprovalTargetSchema, + approvalRequestedEvent.payload + ]), + 'approvalPresentation.close': z.tuple([CoreApprovalTargetSchema, approvalClosedEvent.payload]), + 'ocrCacheKeyProvider.loadOrCreateKey': empty, + initializeFloatingButton: empty, + assertSyncTokenStorageAvailable: empty, + 'semanticNotifications.occur': z.tuple([semanticNotificationIntentSchema]), + 'semanticNotifications.recover': z.tuple([semanticNotificationIntentSchema]), + 'secureCrypto.isAvailable': empty, + 'secureCrypto.encrypt': z.tuple([text]), + 'secureCrypto.decrypt': z.tuple([text]), + 'providerCrypto.isAvailable': empty, + 'providerCrypto.encrypt': z.tuple([text]), + 'providerCrypto.decrypt': z.tuple([text]), + getProxySnapshot: empty, + openExternal: z.tuple([text]), + openPath: z.tuple([text]), + selectDirectory: empty, + cacheImage: z.tuple([ + text, + z.object({ allowPrivateNetwork: z.boolean().optional() }).strict().optional() + ]), + 'agentDesktop.readImageSize': z.tuple([text]), + 'agentDesktop.createSettingsWindow': empty, + 'agentDesktop.sendSettingsNavigation': z.tuple([ + z.number().int().positive(), + z + .object({ + routeName: text, + params: z.record(text, text).optional(), + section: text.optional() + }) + .strict() + ]), + 'pluginSettingsWindow.open': z.tuple([ + z.object({ pluginId: text, title: text, entry: text }).strict() + ]), + 'pluginSettingsWindow.close': z.tuple([text]), + 'pluginSettingsWindow.closeAll': empty, + 'pluginSettingsWindow.getPluginIdForWebContents': z.tuple([z.number().int().positive()]), + 'remoteAuthWindows.open': z.tuple([ + z.discriminatedUnion('kind', [ + z + .object({ + id: text, + kind: z.literal('feishu'), + url: text, + brand: z.enum(['feishu', 'lark']), + redirectUri: text + }) + .strict(), + z.object({ id: text, kind: z.literal('weixin-ilink'), url: text }).strict() + ]) + ]), + 'remoteAuthWindows.close': z.tuple([text]), + 'remoteAuthWindows.closeAll': empty, + 'remoteSessionWindow.select': empty, + 'remoteSessionWindow.present': z.tuple([CoreWindowTargetSchema]), + showNotification: z.tuple([ + z.object({ id: text, title: text, body: text, silent: z.boolean().optional() }).strict() + ]), + 'mcpPreview.shouldCaptureAfterClick': z.tuple([preview]), + 'mcpPreview.started': z.tuple([preview]), + 'mcpPreview.completed': z.tuple([preview, record]), + 'mcpPreview.failed': z.tuple([preview, z.unknown()]), + 'sessionUiPort.refreshSessionUi': empty, + mcpReadinessChanged: z.tuple([z.boolean()]), + reportStartupFailure: z.tuple([ + text, + text, + z.object({ message: text, error: z.unknown(), startupRunId: text }).strict().optional() + ]), + onSessionInactive: z.tuple([text]), + 'renderer.broadcast': z.tuple([envelope]), + 'renderer.send': z.tuple([z.number().int().positive(), envelope]), + sendToRenderer: z + .tuple([z.number().int().positive(), text.refine(hasDeepchatEventContract), z.unknown()]) + .transform(([id, name, payload]) => { + if (!hasDeepchatEventContract(name)) throw new Error('Unknown Core event') + return [id, name, createDeepchatEventEnvelope(name, payload).payload] + }), + destroySessionBrowser: z.tuple([text]), + observeDelegation: z.tuple([record]), + runJournalObserver: z.tuple([record]), + notifySessionCompletion: z.tuple([z.object({ id: text, title: text }).strict()]), + fetchRegistryIcon: z.tuple([text]), + 'mcpAppDesktop.observeOwner': z.tuple([context]), + 'mcpAppDesktop.openExternal': z.tuple([text, context]), + publishMcpConsent: z.tuple([z.number().int().positive(), mcpAppConsentRequestEvent.payload]) +} as const +export type CoreDesktopMethod = keyof typeof CoreDesktopArguments +const method = z.enum( + Object.keys(CoreDesktopArguments) as [CoreDesktopMethod, ...CoreDesktopMethod[]] +) +const id = z.string().uuid() +export const CoreDesktopCommandSchema = z.discriminatedUnion('operation', [ + z.object({ operation: z.literal('call'), id, method, args: z.array(z.unknown()) }).strict(), + z + .object({ + operation: z.literal('retire'), + calls: z.array(id).max(32), + watches: z.array(id).max(32), + settledCalls: z.array(id).max(32) + }) + .strict(), + z + .object({ operation: z.literal('watch'), id, webContentsId: z.number().int().positive() }) + .strict(), + z.object({ operation: z.literal('capabilities') }).strict(), + z.object({ operation: z.literal('close') }).strict() +]) +export type CoreDesktopCommand = z.infer +export const CoreDesktopEventSchema = z.discriminatedUnion('type', [ + z.object({ type: z.literal('desktop.rendererDestroyed'), id }).strict(), + z + .object({ + type: z.literal('desktop.remoteAuthWindow'), + event: z.object({ id: text, type: z.enum(['closed', 'error']) }).strict() + }) + .strict() +]) +export type CoreDesktopEvent = z.infer +export const CoreDesktopCapabilitiesSchema = z + .object({ preview: z.boolean(), journal: z.boolean() }) + .strict() + +const retirement = new Set([ + 'configurationHost.pause', + 'configurationHost.stopWrites', + 'configurationHost.drainWrites', + 'remoteAuthWindows.closeAll', + 'approvalPresentation.close', + 'pluginSettingsWindow.close', + 'pluginSettingsWindow.closeAll', + 'remoteAuthWindows.close', + 'destroySessionBrowser', + 'onSessionInactive', + 'semanticNotifications.recover' +]) +export function isCoreDesktopDrain(input: unknown): boolean { + const parsed = z.tuple([CoreDesktopCommandSchema]).safeParse(input) + if (!parsed.success) return false + const [command] = parsed.data + return ( + command.operation === 'retire' || + command.operation === 'close' || + (command.operation === 'call' && + (retirement.has(command.method) || + (command.method === 'mcpReadinessChanged' && command.args[0] === false))) + ) +} diff --git a/src/shared/harness/helpers.ts b/src/shared/harness/helpers.ts new file mode 100644 index 0000000000..08c41273b5 --- /dev/null +++ b/src/shared/harness/helpers.ts @@ -0,0 +1,152 @@ +import { z } from 'zod' +import { RUN_CODE_PROTOCOL_VERSION } from '../codeModeProtocol' + +export const CORE_HELPER_QUEUE = 64 +export const CORE_HELPER_QUEUE_BYTES = 64 * 1024 * 1024 +export const CORE_HELPER_BATCH = 32 + +export const CoreHelperKindSchema = z.enum(['exec', 'code', 'scheduler', 'content', 'git']) +export type CoreHelperKind = z.infer +const id = z.string().uuid() + +export const CoreHelperCommandSchema = z.discriminatedUnion('operation', [ + z.object({ operation: z.literal('open'), id, kind: CoreHelperKindSchema }).strict(), + z.object({ operation: z.literal('send'), id, message: z.unknown() }).strict(), + z + .object({ operation: z.literal('terminate'), ids: z.array(id).min(1).max(CORE_HELPER_BATCH) }) + .strict(), + z.object({ operation: z.literal('rss'), id }).strict(), + z.object({ operation: z.literal('close') }).strict() +]) +export type CoreHelperCommand = z.infer + +const helperEvent = z.discriminatedUnion('type', [ + z.object({ type: z.literal('opened') }).strict(), + z + .object({ + type: z.literal('openFailed'), + name: z.string().max(256), + message: z.string().max(8192) + }) + .strict(), + z.object({ type: z.literal('message'), message: z.unknown() }).strict(), + z.object({ type: z.literal('exit'), code: z.number().int() }).strict(), + z + .object({ type: z.literal('error'), kind: z.string().max(256), location: z.string().max(8192) }) + .strict() +]) +export const CoreHelperEventSchema = z + .object({ + id, + sequence: z.number().int().positive().max(Number.MAX_SAFE_INTEGER), + pid: z.number().int().positive().nullable(), + event: helperEvent + }) + .strict() +export type CoreHelperEvent = z.infer +export const CoreHelperEventsSchema = z.array(CoreHelperEventSchema).min(1).max(CORE_HELPER_BATCH) + +export const CoreHelperAdmissionSchema = z.object({ accepted: z.literal(true) }).strict() + +const rpc = z + .object({ + type: z.literal('background-exec:request'), + id: z.string().min(1), + method: z.enum([ + 'start', + 'list', + 'poll', + 'log', + 'waitForCompletionOrYield', + 'getCompletionResult', + 'write', + 'kill', + 'clear', + 'remove', + 'cleanupConversation', + 'shutdown' + ]), + args: z.array(z.unknown()) + }) + .strict() +const watcherRpc = rpc.extend({ + type: z.literal('file-watcher:request'), + method: z.enum(['watch', 'unwatch', 'shutdown']) +}) +const codeBase = z.object({ version: z.literal(RUN_CODE_PROTOCOL_VERSION), cellId: z.string() }) +const code = z.discriminatedUnion('type', [ + codeBase + .extend({ + type: z.literal('START'), + frontend: z.enum(['codex', 'function']), + source: z.string(), + bindings: z.array( + z + .object({ + id: z.string(), + name: z.string(), + description: z.string(), + execution: z.enum(['parallel', 'sequential']) + }) + .strict() + ), + store: z.record(z.string(), z.unknown()) + }) + .strict(), + codeBase + .extend({ + type: z.literal('NESTED_RESULT'), + callId: z.string(), + ok: z.boolean(), + result: z.unknown().optional(), + error: z.string().optional() + }) + .strict(), + codeBase.extend({ type: z.literal('RESUME') }).strict(), + codeBase.extend({ type: z.literal('STOP'), reason: z.string() }).strict() +]) +const scheduler = z.discriminatedUnion('type', [ + z.object({ type: z.literal('START'), now: z.number().finite() }).strict(), + z.object({ type: z.literal('RECONCILE'), now: z.number().finite(), reason: z.string() }).strict(), + z + .object({ + type: z.literal('SCAN_RESULT'), + now: z.number().finite(), + requestId: z.number().int().positive(), + enabledJobCount: z.number().int().nonnegative(), + nextRunAt: z.number().finite().nullable(), + error: z.string().optional() + }) + .strict(), + z.object({ type: z.literal('STOP'), now: z.number().finite(), reason: z.string() }).strict() +]) + +export function parseCoreHelperMessage(kind: CoreHelperKind, value: unknown) { + switch (kind) { + case 'exec': + return rpc.parse(value) + case 'code': + return code.parse(value) + case 'scheduler': + return scheduler.parse(value) + case 'content': + case 'git': + return watcherRpc.parse(value) + } +} + +export function isCoreHelperDrain(input: unknown): boolean { + const parsed = z.tuple([CoreHelperCommandSchema]).safeParse(input) + if (!parsed.success) return false + const [command] = parsed.data + if (command.operation === 'close' || command.operation === 'terminate') return true + if (command.operation !== 'send') return false + const message = command.message as { type?: string; method?: string } | null + return ( + message?.type === 'STOP' || + (message?.type === 'file-watcher:request' && + ['shutdown', 'unwatch'].includes(message.method ?? '')) || + (message?.type === 'background-exec:request' && + ['shutdown', 'kill', 'clear', 'remove', 'cleanupConversation'].includes(message.method ?? '')) + ) +} diff --git a/src/shared/harness/lease.ts b/src/shared/harness/lease.ts new file mode 100644 index 0000000000..9379b12b40 --- /dev/null +++ b/src/shared/harness/lease.ts @@ -0,0 +1,123 @@ +import { + AgentInvocationAdmissionAbortedError, + type AgentInvocationAdmissionPort, + type AgentInvocationAdmissionOptions, + type AgentInvocationLease, + type AgentInvocationLeaseState, + type InternalAgentInvocationPermit +} from '@shared/harness/admission' + +export class StatefulAgentInvocationLease implements AgentInvocationLease { + private currentState: AgentInvocationLeaseState = 'suspended' + private permit: InternalAgentInvocationPermit | null = null + private acquisition: + | { + controller: AbortController + promise: Promise + cleanupSignals: () => void + } + | undefined + + constructor( + private readonly admission: Pick, + private readonly options: AgentInvocationAdmissionOptions + ) {} + + get state(): AgentInvocationLeaseState { + return this.currentState + } + + resume(options?: { signal?: AbortSignal }): Promise { + if (this.currentState === 'released') { + return Promise.reject(new AgentInvocationAdmissionAbortedError()) + } + if (this.currentState === 'active') return Promise.resolve() + if (this.acquisition) { + if (this.currentState === 'acquiring') return this.acquisition.promise + return this.acquisition.promise + .catch(() => undefined) + .then(async () => await this.resume(options)) + } + + const controller = new AbortController() + const cleanupSignals = forwardAbortSignals([this.options.signal, options?.signal], controller) + this.currentState = 'acquiring' + const promise = this.admission + .acquire({ ...this.options, signal: controller.signal }) + .then((permit) => { + const internalPermit = permit as InternalAgentInvocationPermit + if ( + controller.signal.aborted || + this.currentState !== 'acquiring' || + this.acquisition?.controller !== controller + ) { + internalPermit.releaseWithReason( + this.currentState === 'released' ? 'lease_released' : 'lease_suspended' + ) + throw new AgentInvocationAdmissionAbortedError() + } + this.permit = internalPermit + this.currentState = 'active' + }) + .finally(() => { + if (this.acquisition?.controller !== controller) return + this.acquisition.cleanupSignals() + this.acquisition = undefined + if (this.currentState === 'acquiring') this.currentState = 'suspended' + }) + this.acquisition = { controller, promise, cleanupSignals } + return promise + } + + suspend(): void { + if (this.currentState === 'released' || this.currentState === 'suspended') return + if (this.currentState === 'active') { + this.releaseActivePermit('lease_suspended', 'suspended') + return + } + this.currentState = 'suspended' + this.acquisition?.controller.abort('Agent invocation lease suspended.') + } + + release(): void { + if (this.currentState === 'released') return + if (this.currentState === 'active') { + this.releaseActivePermit('lease_released', 'released') + return + } + this.currentState = 'released' + this.acquisition?.controller.abort('Agent invocation lease released.') + } + + private releaseActivePermit( + reason: 'lease_suspended' | 'lease_released', + nextState: 'suspended' | 'released' + ): void { + if (!this.permit) throw new Error('Active Agent invocation lease has no permit.') + this.permit.releaseWithReason(reason) + this.permit = null + this.currentState = nextState + } +} + +function forwardAbortSignals( + signals: Array, + target: AbortController +): () => void { + const listeners: Array<{ signal: AbortSignal; listener: () => void }> = [] + for (const signal of signals) { + if (!signal) continue + const listener = () => target.abort(signal.reason) + if (signal.aborted) { + listener() + break + } + signal.addEventListener('abort', listener, { once: true }) + listeners.push({ signal, listener }) + } + return () => { + for (const { signal, listener } of listeners) { + signal.removeEventListener('abort', listener) + } + } +} diff --git a/src/shared/harness/memory-vector.ts b/src/shared/harness/memory-vector.ts new file mode 100644 index 0000000000..c7c28bc196 --- /dev/null +++ b/src/shared/harness/memory-vector.ts @@ -0,0 +1,177 @@ +import { z } from 'zod' +import { AGENT_MEMORY_AGENT_ID_PATTERN } from '../types/agent-memory' + +export const MEMORY_VECTOR_FRAME_BYTES = 256 * 1024 +export const MEMORY_VECTOR_FRAME_FLAG = 0x80000000 +export const MEMORY_VECTOR_BINARY_FLAG = 0x40000000 +export const MEMORY_VECTOR_UPLOAD_BYTES = 16 * 1024 * 1024 +export const MEMORY_VECTOR_INITIAL_CHUNK_BYTES = 64 * 1024 +export const MEMORY_VECTOR_CHUNK_BYTES = 240 * 1024 +export const MEMORY_VECTOR_PENDING = 32 +export const MEMORY_VECTOR_NATIVE_PART_BYTES = 8 * 1024 * 1024 + +const identity = z.string().uuid() +const memoryId = z.string().min(1).max(1024) +const agentId = z.string().regex(AGENT_MEMORY_AGENT_ID_PATTERN) +const dimensions = z + .number() + .int() + .positive() + .max(MEMORY_VECTOR_UPLOAD_BYTES / 4) +const topK = z.number().int().min(1).max(800) +const base64 = z + .string() + .refine( + (value) => + value.length <= 240 * 1024 && + value.length % 4 === 0 && + Buffer.from(value, 'base64').toString('base64') === value, + 'Expected canonical base64' + ) +const resource = { generation: identity, operation: identity, resource: identity } +const chunk = z + .custom((value) => value instanceof Uint8Array) + .refine((value) => value.byteLength > 0 && value.byteLength <= MEMORY_VECTOR_CHUNK_BYTES) + +export const MemoryVectorConfigSchema = z + .object({ + generation: identity, + memoryDbDir: z.string().min(1).max(32768), + host: z + .object({ + appPath: z.string().min(1).max(32768), + userDataPath: z.string().min(1).max(32768), + isPackaged: z.boolean() + }) + .strict() + }) + .strict() + +const commands = [ + z + .object({ + ...resource, + method: z.literal('open'), + agentId, + dimensions, + embedding: z + .object({ providerId: z.string().min(1).max(1024), modelId: z.string().min(1).max(1024) }) + .strict() + }) + .strict(), + z + .object({ ...resource, method: z.literal('close'), preserveLease: z.boolean().optional() }) + .strict(), + z + .object({ ...resource, method: z.literal('query'), vector: base64.max(240 * 1024), topK }) + .strict(), + z.object({ ...resource, method: z.literal('queryByMemoryId'), memoryId, topK }).strict(), + z + .object({ ...resource, method: z.literal('delete'), memoryIds: z.array(memoryId).max(512) }) + .strict(), + z + .object({ + ...resource, + method: z.literal('list'), + afterId: memoryId.nullable(), + limit: z.number().int().min(1).max(512) + }) + .strict(), + z + .object({ generation: identity, operation: identity, method: z.literal('reset'), agentId }) + .strict(), + z + .object({ generation: identity, operation: identity, method: z.literal('quarantine'), agentId }) + .strict() +] as const + +export const MemoryVectorCommandSchema = z.discriminatedUnion('method', [ + ...commands, + z + .object({ + ...resource, + method: z.literal('begin'), + upload: identity, + memoryIds: z.array(memoryId).min(1).max(50), + bytes: z.number().int().positive().max(MEMORY_VECTOR_UPLOAD_BYTES), + data: chunk + .refine((value) => value.byteLength <= MEMORY_VECTOR_INITIAL_CHUNK_BYTES) + .optional() + }) + .strict(), + z + .object({ + ...resource, + method: z.literal('chunk'), + upload: identity, + offset: z.number().int().nonnegative(), + data: chunk + }) + .strict(), + z + .object({ + ...resource, + method: z.literal('commit'), + upload: identity, + offset: z.number().int().nonnegative().optional(), + data: chunk.optional() + }) + .strict(), + z.object({ ...resource, method: z.literal('abort'), upload: identity }).strict() +]) + +export const MemoryVectorNativeCommandSchema = z.discriminatedUnion('method', [ + ...commands, + z.object({ ...resource, method: z.literal('abort'), upload: identity }).strict(), + z + .object({ + ...resource, + method: z.literal('upsert'), + upload: identity, + bytes: z.number().int().positive().max(MEMORY_VECTOR_UPLOAD_BYTES), + offset: z.number().int().nonnegative().max(MEMORY_VECTOR_UPLOAD_BYTES), + memoryIds: z.array(memoryId).min(1).max(50), + data: z + .custom((value) => value instanceof Uint8Array) + .refine( + (value) => value.byteLength > 0 && value.byteLength <= MEMORY_VECTOR_NATIVE_PART_BYTES + ) + }) + .strict() +]) +export type MemoryVectorNativeCommand = z.infer + +export type MemoryVectorConfig = z.infer +export type MemoryVectorCommand = z.infer +export const isMemoryVectorControl = ( + method: MemoryVectorCommand['method'] | MemoryVectorNativeCommand['method'] +) => ['close', 'abort', 'reset', 'quarantine'].includes(method) + +export function encodeMemoryVector(values: readonly number[], dimensions: number): Buffer { + if (values.length !== dimensions) throw new Error('Memory vector dimensions do not match') + const bytes = Buffer.allocUnsafe(dimensions * 4) + for (let index = 0; index < dimensions; index++) { + const value = values[index] + if (!Number.isFinite(value) || !Number.isFinite(Math.fround(value))) { + throw new Error('Memory vector contains a non-finite Float32 value') + } + bytes.writeFloatLE(value, index * 4) + } + return bytes +} + +export function decodeMemoryVector(bytes: Buffer, dimensions: number): number[] { + if (bytes.length !== dimensions * 4) throw new Error('Memory vector byte length does not match') + const data = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength) + const vector: number[] = [] + for (let index = 0; index < dimensions; index++) { + const value = data.getFloat32(index * 4, true) + if (!Number.isFinite(value)) + throw new Error('Memory vector contains a non-finite Float32 value') + const integer = value | 0 + // Keep integer-only vectors compact through the native binding's element conversion. + if (value === integer && !Object.is(value, -0)) vector.push(integer) + else vector.push(value) + } + return vector +} diff --git a/src/shared/logger.ts b/src/shared/logger.ts index 3dc6e7fe65..abf59fa2fe 100644 --- a/src/shared/logger.ts +++ b/src/shared/logger.ts @@ -1,5 +1,3 @@ -import { is } from '@electron-toolkit/utils' - // Legacy Main diagnostics remain console-only until their owners are migrated or removed. Persisted // diagnostics must go through the typed Main logger; arbitrary values must never reach a file sink. export const originalConsole = { @@ -11,8 +9,14 @@ export const originalConsole = { trace: console.trace.bind(console) } +let debugEnabled = false + +export function setLegacyConsoleDebugEnabled(enabled: boolean): void { + debugEnabled = enabled +} + const debug = (...params: unknown[]): void => { - if (is.dev) originalConsole.debug(...params) + if (debugEnabled) originalConsole.debug(...params) } const logger = { diff --git a/src/shared/rendererDocument.ts b/src/shared/rendererDocument.ts index 7acc81fcb8..7bf222a1bc 100644 --- a/src/shared/rendererDocument.ts +++ b/src/shared/rendererDocument.ts @@ -1,4 +1,4 @@ -type AppDocument = 'main' | 'settings' | 'splash' +type AppDocument = 'main' | 'settings' | 'splash' | 'floating' function documentUrl(value: string): string | null { try { @@ -21,7 +21,8 @@ export function createAppDocumentMatcher( const entries = [ ['index.html', 'main'], ['settings/index.html', 'settings'], - ['splash/index.html', 'splash'] + ['splash/index.html', 'splash'], + ['floating/index.html', 'floating'] ] as const for (const [entry, kind] of entries) { documents.set(new URL(entry, rendererDirectoryUrl).href, kind) @@ -39,6 +40,7 @@ export function createAppDocumentMatcher( documents.set(new URL(`/${entry}`, base).href, kind) } documents.set(new URL('/splash/', base).href, 'splash') + documents.set(new URL('/floating/', base).href, 'floating') } return (url) => documents.get(documentUrl(url) ?? '') ?? null diff --git a/src/shared/types/browser.ts b/src/shared/types/browser.ts index 6b61476d85..96e4d2e47a 100644 --- a/src/shared/types/browser.ts +++ b/src/shared/types/browser.ts @@ -84,17 +84,6 @@ export interface ScreenshotOptions { } } -export interface DownloadInfo { - id: string - url: string - filePath?: string - mimeType?: string - receivedBytes: number - totalBytes: number - status: 'pending' | 'in-progress' | 'completed' | 'failed' - error?: string -} - export type YoBrowserActivityKind = 'navigation' | 'vision' | 'pointer' | 'scroll' | 'keyboard' export type YoBrowserActivityAction = diff --git a/src/shared/types/chatSettings.ts b/src/shared/types/chatSettings.ts index a878067301..67e70a9c91 100644 --- a/src/shared/types/chatSettings.ts +++ b/src/shared/types/chatSettings.ts @@ -1,4 +1,14 @@ -import type { RequestedLocale } from '../locales' +import { z } from 'zod' +import { REQUESTED_LOCALES, type RequestedLocale } from '../locales' + +export const ChatSettingMutationSchema = z.discriminatedUnion('id', [ + z.strictObject({ id: z.literal('copyWithCotEnabled'), value: z.boolean() }), + z.strictObject({ id: z.literal('fontSizeLevel'), value: z.number().int().min(0).max(4) }), + z.strictObject({ id: z.literal('language'), value: z.enum(REQUESTED_LOCALES) }), + z.strictObject({ id: z.literal('theme'), value: z.enum(['dark', 'light', 'system']) }) +]) + +export type ChatSettingMutation = z.infer export type ChatLanguage = RequestedLocale diff --git a/src/shared/types/desktop.ts b/src/shared/types/desktop.ts index 2d77c34101..33c211029e 100644 --- a/src/shared/types/desktop.ts +++ b/src/shared/types/desktop.ts @@ -6,7 +6,6 @@ import type { BrowserPageInfo, BrowserPreviewMode, BrowserPreviewModeResult, - DownloadInfo, ScreenshotOptions, YoBrowserStatus } from './browser' @@ -143,7 +142,6 @@ export interface IYoBrowserPresenter { }> captureScreenshot(sessionId: string, options?: ScreenshotOptions): Promise getBrowserPage(sessionId: string): Promise - startDownload(url: string, savePath?: string): Promise clearSandboxData(): Promise scanImportSources(): Promise previewImport(profileId: string): Promise diff --git a/src/shared/types/file.ts b/src/shared/types/file.ts index e2261f6c0d..eecdc91c67 100644 --- a/src/shared/types/file.ts +++ b/src/shared/types/file.ts @@ -1,11 +1,6 @@ import type { MessageFile } from '@shared/chat' import type { FileValidationResult } from './knowledge' -export type FileOperation = { - path: string - content?: string -} - export interface FileMetaData { fileName: string fileSize: number @@ -34,9 +29,6 @@ export interface FileItem { export interface FileServicePort { readFile(relativePath: string): Promise - writeFile(operation: FileOperation): Promise - deleteFile(relativePath: string): Promise - createFileAdapter(filePath: string, typeInfo?: string): Promise prepareFile(absPath: string, typeInfo?: string): Promise prepareFileCompletely( absPath: string, @@ -44,20 +36,9 @@ export interface FileServicePort { contentType?: null | 'origin' | 'llm-friendly' ): Promise prepareDirectory(absPath: string): Promise - writeTemp(file: { name: string; content: string | Buffer | ArrayBuffer }): Promise isDirectory(absPath: string): Promise getMimeType(filePath: string): Promise writeImageBase64(file: { name: string; content: string }): Promise - saveImage(file: { - source: string - mimeType?: string - suggestedName?: string - }): Promise<{ canceled: boolean; path?: string }> - copyImage(file: { - source: string - mimeType?: string - suggestedName?: string - }): Promise<{ copied: boolean }> validateFileForKnowledgeBase(filePath: string): Promise getSupportedExtensions(): string[] } diff --git a/src/shared/types/knowledge.ts b/src/shared/types/knowledge.ts index a0b53d3dcf..d26a30521d 100644 --- a/src/shared/types/knowledge.ts +++ b/src/shared/types/knowledge.ts @@ -1,3 +1,5 @@ +import type { DialogRequestParams } from './dialog' + export type KnowledgeFileMetadata = { size: number totalChunks: number @@ -105,7 +107,7 @@ export interface KnowledgeServicePort { getTaskQueueStatus(): Promise pauseAllRunningTasks(id: string): Promise resumeAllPausedTasks(id: string): Promise - confirmShutdown(): Promise + getShutdownConfirmation(): DialogRequestParams | null destroy(): Promise getSupportedLanguages(): Promise getSeparatorsForLanguage(language: string): Promise diff --git a/src/shared/types/nativeConfiguration.ts b/src/shared/types/nativeConfiguration.ts new file mode 100644 index 0000000000..dff02b9de2 --- /dev/null +++ b/src/shared/types/nativeConfiguration.ts @@ -0,0 +1,18 @@ +import type { SupportedLocale } from '@shared/locales' +import type { ShortcutKeySetting } from './desktop' +import type { FloatingButtonBounds } from './floating-widget' + +export type NativeConfigurationSnapshot = { + ownerId: string + version: number + language: SupportedLocale + notificationsEnabled: boolean + closeToQuit: boolean + contentProtectionEnabled: boolean + shortcutKeys: ShortcutKeySetting + floatingButtonEnabled: boolean + floatingButtonBounds: FloatingButtonBounds | null + loggingEnabled: boolean + privacyModeEnabled: boolean + updateChannel: 'stable' | 'beta' +} diff --git a/src/shared/types/oauth.ts b/src/shared/types/oauth.ts index 21258436c8..e84241da98 100644 --- a/src/shared/types/oauth.ts +++ b/src/shared/types/oauth.ts @@ -1,17 +1,19 @@ import type { OpenAICodexAuthStatus } from './openai-codex' import type { XaiGrokAuthStatus } from './xai-grok' -export interface OAuthConfig { - authUrl: string - redirectUri: string - clientId: string - clientSecret?: string - scope: string - responseType: string +export interface GitHubCopilotSignInTarget { + providerId: string + clientId?: string + configurationId: string +} + +export interface GitHubCopilotCredentialOwner { + getGitHubCopilotSignInTarget(providerId: string): Promise + commitGitHubCopilotCredential(target: GitHubCopilotSignInTarget, apiKey: string): Promise + invalidateGitHubCopilotSignIns(): Promise } export interface OAuthServicePort { - startOAuthLogin(providerId: string, config: OAuthConfig): Promise startGitHubCopilotLogin(providerId: string): Promise startGitHubCopilotDeviceFlowLogin(providerId: string): Promise getOpenAICodexStatus(): Promise @@ -24,3 +26,10 @@ export interface OAuthServicePort { cancelXaiGrokLogin(): Promise logoutXaiGrok(): Promise } + +export type ProviderAccountAuthPort = Omit< + OAuthServicePort, + 'startGitHubCopilotLogin' | 'startGitHubCopilotDeviceFlowLogin' +> & { + stop(): Promise +} diff --git a/src/shared/types/workspace.ts b/src/shared/types/workspace.ts index b65597a3f9..a79ae55c53 100644 --- a/src/shared/types/workspace.ts +++ b/src/shared/types/workspace.ts @@ -5,6 +5,8 @@ import type { WorkspaceFileOpenAppKind } from '@shared/workspace/fileOpenApps' +export const WORKSPACE_PREVIEW_PROTOCOL = 'workspace-preview' + export type SidePanelTab = 'workspace' | 'browser' | 'mcp-app' | 'tape-inspector' export type WorkspaceNavSection = 'files' | 'git' | 'subagents' diff --git a/test/e2e/README.md b/test/e2e/README.md index 7a4922350d..9c33873558 100644 --- a/test/e2e/README.md +++ b/test/e2e/README.md @@ -20,15 +20,27 @@ The CI subset runs launch, Settings navigation, and Skill sync, using the same p Skill sync asserts that scanning does not fall back from the bundled Worker. The Windows ARM64 workflow also runs this spec against the packaged executable to cover loading from `app.asar`. +All six native package jobs also launch the packaged app for default Core startup, Core owner +failure/profile reopen, quit during pending database unlock and acknowledged settings persistence. +They reuse four cases from the launch and settings snapshot specs with isolated profiles, no retries +and resource measurement disabled. Linux uses +Xvfb with the existing packaged launcher. These checks do not require distribution signing or +verify Native keyboard focus. + ## Commands -Use Node >=20.19 and pnpm >=10.11. Build before running: +Use Node >=24.22.1 <25, pnpm >=10.34.5 <11, and Rust >=1.88 with Cargo. Build before running: ```bash pnpm run build pnpm run e2e:smoke ``` +Native keyboard-focus checks require an interactive, unlocked desktop. A locked macOS session +can still answer renderer/CDP requests while `BrowserWindow.isFocused()` remains false. The +browser spec waits for actual Native window focus before checking keyboard-entry admission; +renderer readiness or the application's last focused-window identity is insufficient. + Run the smaller CI subset: ```bash diff --git a/test/e2e/fixtures/electronApp.ts b/test/e2e/fixtures/electronApp.ts index 17d327a07b..bb21c81da2 100644 --- a/test/e2e/fixtures/electronApp.ts +++ b/test/e2e/fixtures/electronApp.ts @@ -17,6 +17,8 @@ import { import { arch, homedir, tmpdir } from 'node:os' import { dirname, join, resolve } from 'node:path' import { fileURLToPath } from 'node:url' +import { performance } from 'node:perf_hooks' +import { measureResources } from '../helpers/resources' import { GUIDED_ONBOARDING_REQUIRED_STEP_IDS, GUIDED_ONBOARDING_STEP_IDS, @@ -63,7 +65,7 @@ const isMainAppWindow = async (page: Page): Promise => { return title === 'DeepChat' && !url.includes('/renderer/') } -const waitForMainAppWindow = async (electronApp: ElectronApplication): Promise => { +export const waitForMainAppWindow = async (electronApp: ElectronApplication): Promise => { const deadline = Date.now() + 60_000 while (Date.now() < deadline) { @@ -90,11 +92,14 @@ export type ElectronAppInstance = { } type ElectronFixtures = { + privacyModeEnabled: boolean app: ElectronAppInstance - launchApp: () => Promise + launchApp: (options?: { + prepareUserData?: (userDataDir: string) => void + }) => Promise } -const resolvePackagedExecutable = (): string => { +export const resolvePackagedExecutable = (): string => { if (process.env.DEEPCHAT_E2E_EXECUTABLE_PATH) { return resolve(process.env.DEEPCHAT_E2E_EXECUTABLE_PATH) } @@ -180,7 +185,7 @@ const readMainProcessLogs = (): string => { return files.map((filePath) => `== ${filePath} ==\n${readTextFileTail(filePath)}`).join('\n\n') } -const seedE2eUserDataDir = (userDataDir: string): void => { +const seedE2eUserDataDir = (userDataDir: string, privacyModeEnabled: boolean): void => { mkdirSync(userDataDir, { recursive: true }) const appSettingsPath = join(userDataDir, 'app-settings.json') @@ -196,6 +201,8 @@ const seedE2eUserDataDir = (userDataDir: string): void => { { init_complete: true, loggingEnabled: true, + privacyModeEnabled, + skillsPath: join(userDataDir, 'skills'), guidedOnboardingState: { version: GUIDED_ONBOARDING_VERSION, status: 'completed', @@ -243,7 +250,8 @@ const ensureLaunchTargetExists = (): void => { } export const test = base.extend({ - launchApp: async ({}, use, testInfo) => { + privacyModeEnabled: [false, { option: true }], + launchApp: async ({ privacyModeEnabled }, use, testInfo) => { ensureLaunchTargetExists() const consoleLogs: string[] = [] @@ -255,7 +263,7 @@ export const test = base.extend({ const userDataDir = originalE2eUserDataDir ?? mkdtempSync(join(tmpdir(), 'deepchat-e2e-user-data-')) const ownsUserDataDir = !originalE2eUserDataDir - seedE2eUserDataDir(userDataDir) + seedE2eUserDataDir(userDataDir, privacyModeEnabled) process.env.DEEPCHAT_E2E_USER_DATA_DIR = userDataDir const attachPageListeners = (page: Page, label: string) => { @@ -274,11 +282,18 @@ export const test = base.extend({ }) } - const launchApp = async (): Promise => { + const launchApp: ElectronFixtures['launchApp'] = async (options) => { + if (options?.prepareUserData) { + if (!ownsUserDataDir || launchCount !== 0) { + throw new Error('Profile preparation requires an owned profile before its first launch') + } + options.prepareUserData(userDataDir) + } launchCount += 1 const currentLaunch = launchCount const packaged = process.env.DEEPCHAT_E2E_APP_MODE === 'packaged' + const startedAt = performance.now() const electronApp = await electron.launch({ ...(packaged ? { @@ -295,8 +310,10 @@ export const test = base.extend({ }, timeout: 120_000 }) + const appProcess = electronApp.process() let closeOutcomePromise: Promise<'graceful' | 'forced'> | undefined + let resources: Awaited> const app: ElectronAppInstance = { electronApp, page: undefined as unknown as Page, @@ -308,6 +325,16 @@ export const test = base.extend({ if (closeOutcomePromise) return closeOutcomePromise launchedApps.delete(app) closeOutcomePromise = (async () => { + let measurementFailure: unknown + try { + await resources?.finish(testInfo, currentLaunch) + } catch (error) { + measurementFailure = error + } + if (appProcess.exitCode === 0) { + if (measurementFailure) throw measurementFailure + return 'graceful' + } const closePromise = electronApp.close() const closedGracefully = await Promise.race([ closePromise.then( @@ -317,9 +344,13 @@ export const test = base.extend({ delay(APP_CLOSE_TIMEOUT_MS).then(() => false) ]) - if (closedGracefully) return 'graceful' - electronApp.process().kill('SIGKILL') + if (closedGracefully) { + if (measurementFailure) throw measurementFailure + return 'graceful' + } + appProcess.kill('SIGKILL') await Promise.race([closePromise.catch(() => undefined), delay(1_000)]) + if (measurementFailure) throw measurementFailure return 'forced' })() return closeOutcomePromise @@ -333,10 +364,12 @@ export const test = base.extend({ }) try { + resources = await measureResources(electronApp, startedAt) const page = await waitForMainAppWindow(electronApp) attachPageListeners(page, `main-${currentLaunch}`) await page.waitForLoadState('domcontentloaded') app.page = page + await resources?.ready(page) return app } catch (error) { await app.close() @@ -348,8 +381,13 @@ export const test = base.extend({ await use(launchApp) } finally { const pendingApps = [...launchedApps].reverse() + let closeFailure: unknown for (const app of pendingApps) { - await app.close() + try { + await app.close() + } catch (error) { + closeFailure ??= error + } } await attachDiagnostics(testInfo, consoleLogs, pageErrors) @@ -363,6 +401,7 @@ export const test = base.extend({ if (ownsUserDataDir) { rmSync(userDataDir, { recursive: true, force: true }) } + if (closeFailure) throw closeFailure } }, diff --git a/test/e2e/helpers/chat.ts b/test/e2e/helpers/chat.ts index 9b2c46a200..adb0677f2a 100644 --- a/test/e2e/helpers/chat.ts +++ b/test/e2e/helpers/chat.ts @@ -28,7 +28,9 @@ export async function selectAgent(page: Page, agentId = 'deepchat'): Promise { @@ -41,7 +43,9 @@ export async function createNewChat(page: Page): Promise { await collapsedButton.click() } - await expect(page.getByTestId('chat-input-editor')).toBeVisible({ timeout: 30_000 }) + await expect(page.getByTestId('new-thread-page').getByTestId('chat-input-editor')).toBeVisible({ + timeout: 30_000 + }) } export async function selectModel(page: Page, modelId: string, providerId?: string): Promise { diff --git a/test/e2e/helpers/httpsFixture.ts b/test/e2e/helpers/httpsFixture.ts new file mode 100644 index 0000000000..145c7df143 --- /dev/null +++ b/test/e2e/helpers/httpsFixture.ts @@ -0,0 +1,101 @@ +import { once } from 'node:events' +import { mkdtempSync, rmSync, writeFileSync } from 'node:fs' +import { createServer, type RequestListener } from 'node:http' +import { createServer as createHttpsServer } from 'node:https' +import { connect, type Socket } from 'node:net' +import { tmpdir } from 'node:os' +import { join } from 'node:path' + +export async function startHttpsFixture(hosts: string[], handler: RequestListener) { + const directory = mkdtempSync(join(tmpdir(), 'deepchat-https-fixture-')) + const certificate = join(directory, 'fixture.pem') + writeFileSync(certificate, HTTPS_FIXTURE_CERTIFICATE) + const envKeys = [ + 'NODE_EXTRA_CA_CERTS', + 'NODE_TLS_REJECT_UNAUTHORIZED', + 'NO_PROXY', + 'no_proxy' + ] as const + const inherited = Object.fromEntries(envKeys.map((key) => [key, process.env[key]])) + process.env.NODE_EXTRA_CA_CERTS = certificate + process.env.NODE_TLS_REJECT_UNAUTHORIZED = '1' + process.env.NO_PROXY = '' + process.env.no_proxy = '' + const sockets = new Set() + const target = createHttpsServer( + { key: HTTPS_FIXTURE_KEY, cert: HTTPS_FIXTURE_CERTIFICATE }, + handler + ) + const proxy = createServer((_request, response) => response.writeHead(404).end()) + for (const server of [target, proxy]) + server.on('connection', (socket) => { + sockets.add(socket) + socket.once('close', () => sockets.delete(socket)) + }) + proxy.on('connect', (request, client, head) => { + if (!hosts.some((host) => request.url === `${host}:443`)) { + client.destroy() + return + } + const upstream = connect((target.address() as { port: number }).port, '127.0.0.1', () => { + client.write('HTTP/1.1 200 Connection Established\r\n\r\n') + upstream.write(head) + upstream.pipe(client) + client.pipe(upstream) + }) + sockets.add(upstream) + upstream.once('close', () => { + sockets.delete(upstream) + client.destroy() + }) + client.once('close', () => upstream.destroy()) + client.on('error', () => upstream.destroy()) + upstream.on('error', () => client.destroy()) + }) + const close = async () => { + for (const socket of sockets) socket.destroy() + try { + await Promise.all( + [target, proxy].map( + (server) => new Promise((resolve) => server.close(() => resolve())) + ) + ) + } finally { + for (const key of envKeys) { + if (inherited[key] === undefined) delete process.env[key] + else process.env[key] = inherited[key] + } + rmSync(directory, { recursive: true, force: true }) + } + } + try { + for (const server of [target, proxy]) await once(server.listen(0, '127.0.0.1'), 'listening') + return { proxyUrl: `http://127.0.0.1:${(proxy.address() as { port: number }).port}`, close } + } catch (error) { + await close() + throw error + } +} + +// Public test certificate; trust is scoped to the disposable app processes. +const HTTPS_FIXTURE_CERTIFICATE = `-----BEGIN CERTIFICATE----- +MIICLTCCAdKgAwIBAgIJAP4V/cL19n3EMAoGCCqGSM49BAMCMCExHzAdBgNVBAMM +FkRlZXBDaGF0IEhUVFBTIGZpeHR1cmUwIBcNMjYxMDMwMjAwODA5WhgPMjEyNjEw +MDYyMDA4MDlaMCExHzAdBgNVBAMMFkRlZXBDaGF0IEhUVFBTIGZpeHR1cmUwWTAT +BgcqhkjOPQIBBggqhkjOPQMBBwNCAATcBwNMi0XCCLfN+/uKzbpnaSh2+AsG/XoV +HUYIX7vtpjBuRc8hSuMyJc2CFT7WbV9cVndF660k8168ccOFnfkoo4HwMIHtMA8G +A1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgKEMBMGA1UdJQQMMAoGCCsGAQUF +BwMBMIG0BgNVHREEgawwgamCDmFwaS5naXRodWIuY29tgg9hdXRoLm9wZW5haS5j +b22CCWF1dGgueC5haYIZcmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbYIbY2RuLmFn +ZW50Y2xpZW50cHJvdG9jb2wuY29tghpzeW5jLXRva2VuLWZpeHR1cmUuaW52YWxp +ZIIVaWxpbmthaS53ZWl4aW4ucXEuY29tghBhcGkudGVsZWdyYW0ub3JnMAoGCCqG +SM49BAMCA0kAMEYCIQDa3pgQZX5CKY/xMJIBp9poCfTjzQh3Uu3eVeT9HHihbAIh +APmhijn9Ge9W2JNilPs/nlpPW6yqwel+GFVhQZiuO3hl +-----END CERTIFICATE----- +` +const HTTPS_FIXTURE_KEY = `-----BEGIN EC PRIVATE KEY----- +MHcCAQEEIFYXNXboDpmNI524QvRBpIEqwdwgPQswYye5tt36yEFjoAoGCCqGSM49 +AwEHoUQDQgAE3AcDTItFwgi3zfv7is26Z2kodvgLBv16FR1GCF+77aYwbkXPIUrj +MiXNghU+1m1fXFZ3ReutJPNevHHDhZ35KA== +-----END EC PRIVATE KEY----- +` diff --git a/test/e2e/helpers/resources.ts b/test/e2e/helpers/resources.ts new file mode 100644 index 0000000000..48c52b2132 --- /dev/null +++ b/test/e2e/helpers/resources.ts @@ -0,0 +1,138 @@ +import { execFile } from 'node:child_process' +import { writeFile } from 'node:fs/promises' +import { performance } from 'node:perf_hooks' +import { promisify } from 'node:util' +import type { ElectronApplication, Page, TestInfo } from '@playwright/test' + +const execute = promisify(execFile) +const SAMPLE_INTERVAL_MS = 500 +const MAX_SAMPLES = 1200 + +// Use the public rusage_info_v0 layout from the macOS SDK; RSS alone double-counts shared pages. +const MAC_PROCESS_SNAPSHOT = String.raw` +import ctypes, json, os, subprocess, sys +class Usage(ctypes.Structure): + _fields_ = [('uuid', ctypes.c_uint8 * 16)] + [(name, ctypes.c_uint64) for name in + ['user', 'system', 'idle', 'interrupt', 'pageins', 'wired', 'resident', 'footprint', 'start', 'exit']] +assert ctypes.sizeof(Usage) == 96 +class Timebase(ctypes.Structure): + _fields_ = [('numer', ctypes.c_uint32), ('denom', ctypes.c_uint32)] +timebase = Timebase() +system = ctypes.CDLL('/usr/lib/libSystem.B.dylib') +assert system.mach_timebase_info(ctypes.byref(timebase)) == 0 and timebase.denom > 0 +seconds_per_tick = timebase.numer / timebase.denom / 1e9 +lib = ctypes.CDLL('/usr/lib/libproc.dylib', use_errno=True) +lib.proc_pid_rusage.argtypes = [ctypes.c_int, ctypes.c_int, ctypes.c_void_p] +lib.proc_pid_rusage.restype = ctypes.c_int +probe = Usage() +assert lib.proc_pid_rusage(os.getpid(), 0, ctypes.byref(probe)) == 0 and probe.footprint > 0 +root = int(sys.argv[1]) +rows = {} +for line in subprocess.check_output(['ps', '-axo', 'pid=,ppid=,comm='], text=True).splitlines(): + fields = line.split(None, 2) + if len(fields) == 3: + rows[int(fields[0])] = (int(fields[1]), os.path.basename(fields[2])) +owned = {root} +while True: + expanded = owned | {pid for pid, (parent, _) in rows.items() if parent in owned} + if expanded == owned: break + owned = expanded +result = [] +for pid in sorted(owned): + usage = Usage() + if pid in rows and lib.proc_pid_rusage(pid, 0, ctypes.byref(usage)) == 0: + result.append(dict(pid=pid, parent=rows[pid][0], name=rows[pid][1], + identity=str(usage.start), rssBytes=usage.resident, + footprintBytes=usage.footprint, cpuSeconds=(usage.user + usage.system) * seconds_per_tick)) +assert any(row['pid'] == root for row in result), 'Application process is unavailable' +print(json.dumps(result)) +` + +type ProcessSample = { + pid: number + parent: number + name: string + identity: string + rssBytes: number + footprintBytes: number + cpuSeconds: number +} + +export async function measureResources(app: ElectronApplication, startedAt: number) { + if (process.env.DEEPCHAT_E2E_MEASURE_RESOURCES !== '1') return undefined + if (process.platform !== 'darwin') { + throw new Error('E2E resource measurement currently requires macOS and Python 3') + } + const pid = app.process().pid + if (!pid) throw new Error('Application process has no PID') + const histogram = await app.evaluateHandle(() => { + const { monitorEventLoopDelay } = process.getBuiltinModule( + 'node:perf_hooks' + ) as typeof import('node:perf_hooks') + const histogram = monitorEventLoopDelay({ resolution: 10 }) + histogram.enable() + return histogram + }) + const samples: Array<{ elapsedMs: number; processes: ProcessSample[] }> = [] + let failure: unknown + let uiReadyMs: number | undefined + let stopped = false + let pending: Promise | undefined + const takeSample = () => { + if (pending || stopped) return pending + pending = (async () => { + if (samples.length >= MAX_SAMPLES) throw new Error('Resource sample limit exceeded') + const { stdout } = await execute('python3', ['-c', MAC_PROCESS_SNAPSHOT, String(pid)], { + timeout: 5000, + maxBuffer: 1024 * 1024 + }) + samples.push({ elapsedMs: performance.now() - startedAt, processes: JSON.parse(stdout) }) + })() + .catch((error) => { + failure = error + }) + .finally(() => { + pending = undefined + }) + return pending + } + await takeSample() + const timer = setInterval(takeSample, SAMPLE_INTERVAL_MS) + timer.unref() + return { + async ready(page: Page) { + await page.getByTestId('app-root').waitFor({ state: 'visible', timeout: 60_000 }) + await page.getByTestId('window-sidebar').waitFor({ state: 'visible', timeout: 30_000 }) + uiReadyMs = performance.now() - startedAt + }, + async finish(testInfo: TestInfo, launch: number) { + clearInterval(timer) + await pending + await takeSample() + stopped = true + const eventLoop = await histogram.evaluate(async (histogram) => { + // Keep a Node task alive while the inspector settles its awaited result. + await new Promise((resolve) => setImmediate(resolve)) + histogram.disable() + return { p95Ms: histogram.percentile(95) / 1e6, p99Ms: histogram.percentile(99) / 1e6 } + }) + await histogram.dispose() + if (failure) throw failure + const report = { + schema: 1, + platform: process.platform, + architecture: process.arch, + sampleIntervalMs: SAMPLE_INTERVAL_MS, + uiReadyMs, + eventLoop, + samples + } + const output = testInfo.outputPath(`resources-${launch}.json`) + await writeFile(output, JSON.stringify(report, null, 2)) + await testInfo.attach(`resources-${launch}`, { + path: output, + contentType: 'application/json' + }) + } + } +} diff --git a/test/e2e/specs/01-launch.smoke.spec.ts b/test/e2e/specs/01-launch.smoke.spec.ts index 5164ee5178..378f162889 100644 --- a/test/e2e/specs/01-launch.smoke.spec.ts +++ b/test/e2e/specs/01-launch.smoke.spec.ts @@ -1,8 +1,17 @@ import { mkdir, readFile, rm, writeFile } from 'node:fs/promises' -import { test, expect } from '../fixtures/electronApp' +import { + test, + expect, + resolvePackagedExecutable, + waitForMainAppWindow +} from '../fixtures/electronApp' +import { _electron, type ElectronApplication } from '@playwright/test' import { waitForAppReady } from '../helpers/wait' -import { existsSync, readFileSync } from 'node:fs' +import { openSettings } from '../helpers/settings' +import { existsSync, readFileSync, writeFileSync } from 'node:fs' import { join } from 'node:path' +import type { ChildProcess } from 'node:child_process' +import { setTimeout as delay } from 'node:timers/promises' type JsonObject = Record @@ -34,6 +43,137 @@ const LIFECYCLE_EVENTS = new Set([ 'app.shutdown.terminal' ]) +test('quit cancels a pending database unlock and preserves the profile @smoke', async ({ + launchApp +}, testInfo) => { + const password = 'disposable-startup-quit-password' + const first = await launchApp() + expect(first.ownsUserDataDir).toBe(true) + await waitForAppReady(first.page) + await first.page.evaluate(() => + window.deepchat.invoke('settings.update', { + changes: [ + { key: 'copyWithCotEnabled', value: false }, + { key: 'privacyModeEnabled', value: true } + ] + }) + ) + await first.page.evaluate( + (password) => window.deepchat.invoke('databaseSecurity.enable', { password }), + password + ) + expect(await first.close()).toBe('graceful') + const metadataFile = join(first.userDataDir, 'database-security.json') + const state = JSON.parse(readFileSync(metadataFile, 'utf8')) + state.metadata.passwordStorage = 'manual' + delete state.metadata.wrappedPassword + writeFileSync(metadataFile, JSON.stringify(state)) + const errorsFile = join(first.userDataDir, 'startup-error-dialogs.json') + writeFileSync(errorsFile, '[]') + const owned: { app: ElectronApplication; child: ChildProcess }[] = [] + const launch = async () => { + const app = await _electron.launch({ + ...(process.env.DEEPCHAT_E2E_APP_MODE === 'packaged' + ? { executablePath: resolvePackagedExecutable(), args: [] } + : { args: ['.'] }), + cwd: process.cwd(), + env: { ...process.env, DEEPCHAT_E2E_USER_DATA_DIR: first.userDataDir } + }) + owned.push({ app, child: app.process() }) + await app.evaluate(({ dialog }, file) => { + const fs = process.getBuiltinModule('node:fs') as typeof import('node:fs') + dialog.showErrorBox = (title) => { + const calls = JSON.parse(fs.readFileSync(file, 'utf8')) + fs.writeFileSync(file, JSON.stringify([...calls, title])) + } + }, errorsFile) + const findSplash = () => app.windows().find((page) => page.url().includes('/splash/')) + await expect.poll(() => Boolean(findSplash())).toBe(true) + return { app, splash: findSplash()! } + } + try { + const { app: waiting, splash } = await launch() + await expect(splash.locator('input[type="password"]')).toBeVisible() + const child = waiting.process() + const quitAt = Date.now() + await waiting.evaluate(({ app }) => app.quit()) + await expect.poll(() => child.exitCode, { timeout: 10_000 }).not.toBeNull() + expect(child.exitCode).toBe(0) + await testInfo.attach('pending-unlock-quit', { + body: JSON.stringify({ quitMs: Date.now() - quitAt, exitCode: child.exitCode }), + contentType: 'application/json' + }) + + const { app: reopened, splash: unlock } = await launch() + await unlock.locator('input[type="password"]').fill(password) + await unlock.getByRole('button', { name: 'Unlock', exact: true }).click() + const page = await waitForMainAppWindow(reopened) + await waitForAppReady(page) + const snapshot = await page.evaluate(() => + window.deepchat.invoke('settings.getSnapshot', { keys: ['copyWithCotEnabled'] }) + ) + expect(snapshot.values.copyWithCotEnabled).toBe(false) + const reopenedChild = reopened.process() + await reopened.evaluate(({ app }) => app.quit()) + await expect.poll(() => reopenedChild.exitCode, { timeout: 10_000 }).not.toBeNull() + expect(reopenedChild.exitCode).toBe(0) + expect(JSON.parse(readFileSync(errorsFile, 'utf8'))).toEqual([]) + } finally { + for (const { app, child } of owned.reverse()) { + if (child.exitCode !== null || child.signalCode !== null) continue + const splash = app.windows().find((page) => page.url().includes('/splash/')) + await splash + ?.getByRole('button', { name: 'Quit', exact: true }) + .click({ timeout: 1000 }) + .catch(() => {}) + const closed = app.close().catch(() => {}) + await Promise.race([closed, delay(1000)]) + if (child.exitCode === null && child.signalCode === null) child.kill('SIGKILL') + } + } +}) + +test('Core owner failure ends the app generation and permits profile reopen @smoke', async ({ + launchApp +}) => { + const active = await launchApp() + expect(active.ownsUserDataDir).toBe(true) + await waitForAppReady(active.page) + const appProcess = active.electronApp.process() + const settings = await openSettings(active) + const settingsWindow = await active.electronApp.browserWindow(settings) + const rendererId = await settingsWindow.evaluate((window) => window.webContents.id) + await active.electronApp.evaluate(({ webContents }, rendererId) => { + const target = webContents.fromId(rendererId) + if (!target) throw new Error('Owned settings renderer is missing') + const original = webContents.fromId + webContents.fromId = (id) => { + if (id === target.id) throw new Error('Native renderer registration was refused') + return original.call(webContents, id) + } + }, rendererId) + const pending = settings + .evaluate(() => + window.deepchat.invoke('sessions.create', { + agentId: 'deepchat', + providerId: 'openai', + modelId: 'gpt-4o-mini', + message: '' + }) + ) + .catch(() => undefined) + await expect.poll(() => appProcess.exitCode).toBe(1) + await pending + + const reopened = await launchApp() + await waitForAppReady(reopened.page) + const sessions = await reopened.page.evaluate(() => + window.deepchat.invoke('sessions.listLightweight', { limit: 10 }) + ) + expect(sessions.items.length).toBeLessThanOrEqual(1) + expect(await reopened.close()).toBe('graceful') +}) + test('启动应用 @smoke', async ({ app }, testInfo) => { test.skip( !app.ownsUserDataDir, diff --git a/test/e2e/specs/07-floating-ipc-boundary.smoke.spec.ts b/test/e2e/specs/07-floating-ipc-boundary.smoke.spec.ts index 4143449db0..205ae88220 100644 --- a/test/e2e/specs/07-floating-ipc-boundary.smoke.spec.ts +++ b/test/e2e/specs/07-floating-ipc-boundary.smoke.spec.ts @@ -1,6 +1,8 @@ -import { test, expect, type Page } from '../fixtures/electronApp' +import { test, expect } from '../fixtures/electronApp' import { waitForAppReady } from '../helpers/wait' -import type { ElectronApplication } from '@playwright/test' +import type { ElectronApplication, Page } from '@playwright/test' + +test.use({ privacyModeEnabled: true }) const isFloatingWindow = async (page: Page): Promise => { const url = page.url() @@ -51,6 +53,7 @@ const waitForFloatingWindow = async ( } test('floating window IPC boundary uses scoped preload API @smoke', async ({ app }) => { + expect(app.ownsUserDataDir).toBe(true) await waitForAppReady(app.page) const mainWindow = await app.electronApp.browserWindow(app.page) expect(await mainWindow.evaluate((window) => window.isVisible())).toBe(true) @@ -59,8 +62,19 @@ test('floating window IPC boundary uses scoped preload API @smoke', async ({ app const original = await app.page.evaluate(async () => { return await window.deepchat.invoke('config.getFloatingButton', {}) }) + let sessionId: string | undefined try { + sessionId = await app.page.evaluate(async () => { + await window.deepchat.invoke('config.setFloatingButton', { enabled: false }) + const { session } = await window.deepchat.invoke('sessions.create', { + agentId: 'deepchat', + providerId: 'openai', + modelId: 'gpt-4o-mini', + message: '' + }) + return session.id + }) await app.page.evaluate(async () => { await window.deepchat.invoke('config.setFloatingButton', { enabled: true }) }) @@ -141,7 +155,38 @@ test('floating window IPC boundary uses scoped preload API @smoke', async ({ app sessions: expect.any(Array) }) expect(boundary.updateCount).toBe(0) + + await expect + .poll(() => + floatingPage.evaluate(async () => { + const snapshot = await window.floatingButtonAPI.getSnapshot() + return snapshot.sessions.map((session) => session.id) + }) + ) + .toContain(sessionId) + await floatingPage.evaluate((sessionId) => { + window.floatingButtonAPI.setExpanded(true) + window.floatingButtonAPI.openSession(sessionId) + }, sessionId) + await expect + .poll(() => + app.page.evaluate(async () => { + const { session } = await window.deepchat.invoke('sessions.getActive', {}) + return session?.id + }) + ) + .toBe(sessionId) + await expect + .poll(() => + floatingPage.evaluate(async () => (await window.floatingButtonAPI.getSnapshot()).expanded) + ) + .toBe(false) } finally { + if (sessionId) { + await app.page.evaluate(async (sessionId) => { + await window.deepchat.invoke('sessions.delete', { sessionId }) + }, sessionId) + } await app.page .evaluate(async (enabled) => { await window.deepchat.invoke('config.setFloatingButton', { enabled }) diff --git a/test/e2e/specs/08-browser-route.smoke.spec.ts b/test/e2e/specs/08-browser-route.smoke.spec.ts index a704c87681..1bcc37e7ae 100644 --- a/test/e2e/specs/08-browser-route.smoke.spec.ts +++ b/test/e2e/specs/08-browser-route.smoke.spec.ts @@ -1,13 +1,15 @@ import { test, expect } from '../fixtures/electronApp' import { createSmokeToken } from '../helpers/testData' import { waitForAppReady } from '../helpers/wait' +import { openSettings } from '../helpers/settings' +import { createHash } from 'node:crypto' +import { createServer } from 'node:http' +import type { Socket } from 'node:net' test('browser typed routes load status and destroy without legacy IPC @smoke', async ({ app }) => { await waitForAppReady(app.page) - const titlePrefix = `DeepChat Browser ${createSmokeToken('route').toLowerCase()}` - - const result = await app.page.evaluate( + const browser = await app.page.evaluateHandle( async ({ titlePrefix }) => { const created = await window.deepchat.invoke('sessions.create', { agentId: 'deepchat', @@ -16,22 +18,16 @@ test('browser typed routes load status and destroy without legacy IPC @smoke', a modelId: 'gpt-4o-mini' }) const sessionId = created.session.id + await window.deepchat.invoke('sessions.activate', { sessionId }) const title = `${titlePrefix} ${sessionId}` const url = `data:text/html;charset=utf-8,${encodeURIComponent( `${title}

${sessionId}

` )}` const events: Array<{ reason: string; initialized: boolean }> = [] const unsubscribe = window.deepchat.on('browser.status.changed', (payload) => { - if (payload.sessionId !== sessionId) { - return - } - - events.push({ - reason: payload.reason, - initialized: Boolean(payload.status?.initialized) - }) + if (payload.sessionId === sessionId) + events.push({ reason: payload.reason, initialized: Boolean(payload.status?.initialized) }) }) - try { const loaded = await window.deepchat.invoke('browser.loadUrl', { sessionId, @@ -39,45 +35,227 @@ test('browser typed routes load status and destroy without legacy IPC @smoke', a timeoutMs: 15_000 }) let afterLoad = await window.deepchat.invoke('browser.getStatus', { sessionId }) - for (let index = 0; index < 20 && afterLoad.status.page?.status !== 'ready'; index += 1) { await new Promise((resolve) => setTimeout(resolve, 250)) afterLoad = await window.deepchat.invoke('browser.getStatus', { sessionId }) } - - await window.deepchat.invoke('browser.destroy', { sessionId }) - const afterDestroy = await window.deepchat.invoke('browser.getStatus', { sessionId }) - - await new Promise((resolve) => setTimeout(resolve, 300)) - - return { - afterDestroy, - afterLoad, - events, - loaded, - title, - url - } - } finally { + await window.deepchat.invoke('browser.attachCurrentWindow', { sessionId }) + await window.deepchat.invoke('browser.updateCurrentWindowBounds', { + sessionId, + bounds: { x: 20, y: 80, width: 400, height: 300 }, + visible: true + }) + return { sessionId, title, url, loaded, afterLoad, events, unsubscribe } + } catch (error) { unsubscribe() await window.deepchat.invoke('browser.destroy', { sessionId }).catch(() => undefined) await window.deepchat.invoke('sessions.delete', { sessionId }).catch(() => undefined) + throw error } }, { titlePrefix } ) + try { + await app.page.evaluate(() => window.deepchat.invoke('window.focusMain', {})) + const nativeWindow = await app.electronApp.browserWindow(app.page) + await app.electronApp.evaluate(({ app }) => { + if (process.platform === 'darwin') app.show() + app.focus({ steal: true }) + }) + await nativeWindow.evaluate((window) => { + window.show() + window.focus() + }) + await expect + .poll(() => nativeWindow.evaluate((window) => window.isVisible() && window.isFocused())) + .toBe(true) + const result = await browser.evaluate(async (browser) => { + const { sessionId } = browser + const focused = await window.deepchat.invoke('browser.focusContent', { sessionId }) + await window.deepchat.invoke('sessions.deactivate', {}) + const inactiveFocus = await window.deepchat.invoke('browser.focusContent', { sessionId }) + await window.deepchat.invoke('browser.destroy', { sessionId }) + const afterDestroy = await window.deepchat.invoke('browser.getStatus', { sessionId }) + await new Promise((resolve) => setTimeout(resolve, 300)) + return { + afterDestroy, + afterLoad: browser.afterLoad, + events: browser.events, + loaded: browser.loaded, + title: browser.title, + url: browser.url, + focused, + inactiveFocus + } + }) + expect(result.focused).toEqual({ focused: true }) + expect(result.inactiveFocus).toEqual({ focused: false }) + expect(result.loaded.status.initialized).toBe(true) + expect(result.loaded.status.page?.url).toBe(result.url) + expect(result.afterLoad.status.initialized).toBe(true) + expect(result.afterLoad.status.page?.status).toBe('ready') + expect(result.afterLoad.status.page?.title).toBe(result.title) + expect(result.afterDestroy.status).toMatchObject({ + initialized: false, + page: null, + visible: false + }) + expect(result.events.map((event) => event.reason)).toEqual( + expect.arrayContaining(['created', 'updated', 'closed']) + ) + } finally { + await browser.evaluate(async ({ sessionId, unsubscribe }) => { + unsubscribe() + await window.deepchat.invoke('browser.destroy', { sessionId }).catch(() => undefined) + await window.deepchat.invoke('sessions.delete', { sessionId }).catch(() => undefined) + }) + await browser.dispose() + } +}) - expect(result.loaded.status.initialized).toBe(true) - expect(result.loaded.status.page?.url).toBe(result.url) - expect(result.afterLoad.status.initialized).toBe(true) - expect(result.afterLoad.status.page?.status).toBe('ready') - expect(result.afterLoad.status.page?.title).toBe(result.title) - expect(result.afterDestroy.status).toMatchObject({ - initialized: false, - page: null, - visible: false +test('Browser pages retire held HTTP and WebSocket work on destroy, quit and Core loss @smoke', async ({ + launchApp +}) => { + const started = new Set() + const closed = new Set() + const sockets = new Set() + const server = createServer((request, response) => { + const route = request.url ?? '' + if (route.startsWith('/page/')) { + const mode = route.split('/').at(-1) + response.writeHead(200, { 'content-type': 'text/html' }).end(` + Owned Browser ${mode}Browser retirement`) + return + } + if (!route.startsWith('/headers/') && !route.startsWith('/body/')) { + response.writeHead(404).end() + return + } + started.add(route) + response.once('close', () => closed.add(route)) + if (route.startsWith('/body/')) { + response.writeHead(200, { 'content-type': 'text/plain' }) + response.write('held response body') + } }) - expect(result.events.map((event) => event.reason)).toEqual( - expect.arrayContaining(['created', 'updated', 'closed']) - ) + server.on('connection', (socket) => { + sockets.add(socket) + socket.once('close', () => sockets.delete(socket)) + }) + server.on('upgrade', (request, socket) => { + const key = request.headers['sec-websocket-key'] + if (!key) { + socket.destroy() + return + } + const accept = createHash('sha1') + .update(key + '258EAFA5-E914-47DA-95CA-C5AB0DC85B11') + .digest('base64') + socket.write( + `HTTP/1.1 101 Switching Protocols\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Accept: ${accept}\r\n\r\n` + ) + const route = request.url ?? '' + started.add(route) + socket.once('close', () => closed.add(route)) + socket.on('error', () => {}) + socket.on('data', (frame: Buffer) => { + if ((frame[0] & 0x0f) === 8) socket.end(Buffer.from([0x88, 0])) + }) + }) + await new Promise((resolve) => server.listen(0, '127.0.0.1', resolve)) + const origin = `http://127.0.0.1:${(server.address() as { port: number }).port}` + let active: Awaited> | undefined + try { + for (const mode of ['destroy', 'quit', 'failure'] as const) { + active = await launchApp() + expect(active.ownsUserDataDir).toBe(true) + await waitForAppReady(active.page) + const appProcess = active.electronApp.process() + const settings = mode === 'failure' ? await openSettings(active) : undefined + const { session } = await active.page.evaluate(() => + window.deepchat.invoke('sessions.create', { + agentId: 'deepchat', + message: '', + providerId: 'openai', + modelId: 'gpt-4o-mini' + }) + ) + const loaded = await active.page.evaluate( + (input) => window.deepchat.invoke('browser.loadUrl', input), + { sessionId: session.id, url: `${origin}/page/${mode}`, timeoutMs: 15_000 } + ) + expect(loaded.status.initialized).toBe(true) + const routes = [`/headers/${mode}`, `/body/${mode}`, `/socket/${mode}`] + await expect.poll(() => routes.every((route) => started.has(route))).toBe(true) + expect(routes.some((route) => closed.has(route))).toBe(false) + if (mode === 'destroy') { + await active.page.evaluate( + (sessionId) => window.deepchat.invoke('browser.destroy', { sessionId }), + session.id + ) + expect( + await active.page.evaluate( + (sessionId) => window.deepchat.invoke('browser.getStatus', { sessionId }), + session.id + ) + ).toMatchObject({ status: { initialized: false } }) + expect(appProcess.exitCode).toBeNull() + } else if (settings) { + await active.electronApp.evaluate(({ webContents }, url) => { + const target = webContents + .getAllWebContents() + .find((contents) => contents.getURL() === url) + if (!target) throw new Error('Owned settings renderer is missing') + const original = webContents.fromId + webContents.fromId = (id) => { + if (id === target.id) throw new Error('Native renderer registration was refused') + return original.call(webContents, id) + } + }, settings.url()) + const pending = settings + .evaluate(() => + window.deepchat.invoke('sessions.create', { + agentId: 'deepchat', + providerId: 'openai', + modelId: 'gpt-4o-mini', + message: '' + }) + ) + .catch(() => undefined) + await expect.poll(() => appProcess.exitCode).toBe(1) + await pending + } else expect(await active.close()).toBe('graceful') + await expect.poll(() => routes.every((route) => closed.has(route))).toBe(true) + await test.info().attach(`browser-retirement-${mode}`, { + body: JSON.stringify({ mode, closed: routes.filter((route) => closed.has(route)) }), + contentType: 'application/json' + }) + if (mode === 'destroy') expect(await active.close()).toBe('graceful') + active = await launchApp() + await waitForAppReady(active.page) + expect( + await active.page.evaluate( + (sessionId) => window.deepchat.invoke('browser.getStatus', { sessionId }), + session.id + ) + ).toMatchObject({ status: { initialized: false } }) + expect( + ( + await active.page.evaluate(() => window.deepchat.invoke('sessions.listLightweight', {})) + ).items.some((item) => item.id === session.id) + ).toBe(true) + expect(await active.close()).toBe('graceful') + } + } finally { + await active?.close() + for (const socket of sockets) socket.destroy() + await new Promise((resolve) => server.close(() => resolve())) + } }) diff --git a/test/e2e/specs/16-skills-readonly-route.smoke.spec.ts b/test/e2e/specs/16-skills-readonly-route.smoke.spec.ts index 72bc826f6f..dad6462fe3 100644 --- a/test/e2e/specs/16-skills-readonly-route.smoke.spec.ts +++ b/test/e2e/specs/16-skills-readonly-route.smoke.spec.ts @@ -1,4 +1,5 @@ import { test, expect } from '../fixtures/electronApp' +import { join } from 'node:path' import { waitForAppReady } from '../helpers/wait' test('Plugins Skills exposes read-only skill routes through typed bridge @smoke', async ({ @@ -109,7 +110,8 @@ test('Plugins Skills exposes read-only skill routes through typed bridge @smoke' }) expect(typeof snapshot.directoryPath).toBe('string') - expect(snapshot.skillCount).toBeGreaterThanOrEqual(0) + if (app.ownsUserDataDir) expect(snapshot.directoryPath).toBe(join(app.userDataDir, 'skills')) + expect(snapshot.skillCount).toBeGreaterThan(0) expect(snapshot.allSkillCount).toBeGreaterThanOrEqual(snapshot.skillCount) expect(snapshot.allSkillShapeValid).toBe(true) diff --git a/test/e2e/specs/18-provider-readonly-route.smoke.spec.ts b/test/e2e/specs/18-provider-readonly-route.smoke.spec.ts index 99c98ca00f..05ac79b8e9 100644 --- a/test/e2e/specs/18-provider-readonly-route.smoke.spec.ts +++ b/test/e2e/specs/18-provider-readonly-route.smoke.spec.ts @@ -14,7 +14,7 @@ test('provider settings read-only routes expose provider and model snapshots @sm }) if ((await settingsPage.locator('[data-provider-id]').count()) === 0) { - await settingsPage.getByTestId('disabled-providers-toggle').click() + await settingsPage.getByTestId('provider-browse-all').click() } await expect diff --git a/test/e2e/specs/25-window-provider-deeplink-preview.smoke.spec.ts b/test/e2e/specs/25-window-provider-deeplink-preview.smoke.spec.ts index c03022c5a2..c1e68d553d 100644 --- a/test/e2e/specs/25-window-provider-deeplink-preview.smoke.spec.ts +++ b/test/e2e/specs/25-window-provider-deeplink-preview.smoke.spec.ts @@ -8,7 +8,7 @@ test('settings consumes pending provider install preview without applying it @sm await waitForAppReady(app.page) const preview = { - kind: 'custom', + kind: 'custom' as const, name: 'DeepChat E2E Provider Preview', type: 'openai-compatible', baseUrl: 'https://e2e-provider.example.com/v1', diff --git a/test/e2e/specs/27-hooks-notification-command.smoke.spec.ts b/test/e2e/specs/27-hooks-notification-command.smoke.spec.ts index 5c1ca2b8fe..a063e8a732 100644 --- a/test/e2e/specs/27-hooks-notification-command.smoke.spec.ts +++ b/test/e2e/specs/27-hooks-notification-command.smoke.spec.ts @@ -1,40 +1,53 @@ +import { createServer } from 'node:http' +import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs' +import { join } from 'node:path' +import type { + HookEventPayload, + HooksNotificationsSettings +} from '../../../src/shared/hooksNotifications' import { test, expect } from '../fixtures/electronApp' import { openSettings, openSettingsTab } from '../helpers/settings' import { waitForAppReady } from '../helpers/wait' -type HookConfig = { - hooks: Array<{ - id: string - name: string - enabled: boolean - command: string - events: string[] - }> +test.use({ privacyModeEnabled: true }) + +const prepareHookProfile = (profile: string): void => { + const workspace = join(profile, 'hook-workspace') + mkdirSync(workspace) + const file = join(profile, 'app-settings.json') + const settings = JSON.parse(readFileSync(file, 'utf8')) + settings.defaultProjectPath = workspace + writeFileSync(file, JSON.stringify(settings)) } -test('notification hooks can save test command and restore config @smoke', async ({ app }) => { +test('notification hooks can save test command and restore config @smoke', async ({ + launchApp +}) => { + let app = await launchApp({ prepareUserData: prepareHookProfile }) await waitForAppReady(app.page) + const expectedVersion = await app.electronApp.evaluate(({ app }) => app.getVersion()) - const settingsPage = await openSettings(app) + let settingsPage = await openSettings(app) const hookId = `deepchat-e2e-hook-${Date.now()}` - let originalConfig: HookConfig | null = null + let originalConfig: HooksNotificationsSettings | null = null try { originalConfig = await settingsPage.evaluate(async () => { const result = (await window.deepchat.invoke('config.getHooksNotifications', {})) as { - config: HookConfig + config: HooksNotificationsSettings } return result.config }) - const tempConfig: HookConfig = { + const tempConfig: HooksNotificationsSettings = { hooks: [ ...(originalConfig?.hooks ?? []).filter((hook) => hook.id !== hookId), { id: hookId, name: 'DeepChat E2E Hook', enabled: true, - command: 'node -e "console.log(\'deepchat-hook-e2e-ok\')"', + command: + "node -e \"let raw='';process.stdin.on('data',c=>raw+=c);process.stdin.on('end',()=>console.log('deepchat-hook-e2e-ok '+JSON.parse(raw).app.version))\"", events: ['SessionStart'] } ] @@ -46,7 +59,7 @@ test('notification hooks can save test command and restore config @smoke', async }, { config: tempConfig } )) as { - config?: HookConfig + config?: HooksNotificationsSettings } expect(savedConfig.config?.hooks.some((hook) => hook.id === hookId)).toBe(true) @@ -75,7 +88,16 @@ test('notification hooks can save test command and restore config @smoke', async expect(testResult.result?.success).toBe(true) expect(testResult.result?.exitCode).toBe(0) expect(typeof testResult.result?.durationMs).toBe('number') - expect(String(testResult.result?.stdout ?? '')).toContain('deepchat-hook-e2e-ok') + expect(String(testResult.result?.stdout ?? '')).toContain( + `deepchat-hook-e2e-ok ${expectedVersion}` + ) + expect(await app.close()).toBe('graceful') + app = await launchApp() + await waitForAppReady(app.page) + expect( + await app.page.evaluate(() => window.deepchat.invoke('config.getHooksNotifications', {})) + ).toEqual({ config: tempConfig }) + settingsPage = await openSettings(app) } finally { if (originalConfig) { await settingsPage @@ -89,3 +111,207 @@ test('notification hooks can save test command and restore config @smoke', async } } }) + +const processIsAlive = (pid: number): boolean => { + try { + process.kill(pid, 0) + return true + } catch (error) { + if ((error as NodeJS.ErrnoException).code === 'ESRCH') return false + throw error + } +} + +test('hook shutdown retains native child lifetime until stdio closes @smoke', async ({ + launchApp +}) => { + let active = await launchApp({ prepareUserData: prepareHookProfile }) + await waitForAppReady(active.page) + const profile = active.userDataDir + const releaseFile = join(profile, 'hook-release') + const pidFile = join(profile, 'hook-child.json') + const payloadFile = join(profile, 'hook-payload.json') + writeFileSync( + join(profile, 'hook-lifecycle.cjs'), + ` +const fs = require('node:fs') +const path = require('node:path') +let raw = '' +process.stdin.on('data', chunk => { raw += chunk }) +process.stdin.on('end', () => { + fs.writeFileSync(path.join(__dirname, 'hook-payload.json'), raw) + fs.writeFileSync(path.join(__dirname, 'hook-child.json'), JSON.stringify({ pid: process.pid, parent: process.ppid })) + setInterval(() => { + if (fs.existsSync(path.join(__dirname, 'hook-release'))) process.exit(0) + }, 25) +}) +` + ) + const server = createServer(async (request, response) => { + if (request.url !== '/v1/chat/completions') { + response.writeHead(404).end() + return + } + let body = '' + for await (const chunk of request) body += chunk + const result = { id: 'hook-fixture', model: 'hook-fixture', created: 1 } + if (!JSON.parse(body).stream) { + response.writeHead(200, { 'Content-Type': 'application/json' }).end( + JSON.stringify({ + ...result, + object: 'chat.completion', + choices: [ + { + index: 0, + message: { role: 'assistant', content: 'Hook fixture' }, + finish_reason: 'stop' + } + ] + }) + ) + return + } + response + .writeHead(200, { 'Content-Type': 'text/event-stream' }) + .end( + `data: ${JSON.stringify({ ...result, object: 'chat.completion.chunk', choices: [{ index: 0, delta: { role: 'assistant', content: 'HOOK_COMPLETE' }, finish_reason: 'stop' }] })}\n\ndata: [DONE]\n\n` + ) + }) + await new Promise((resolve) => server.listen(0, '127.0.0.1', resolve)) + const { port } = server.address() as { port: number } + let childPid: number | undefined + let closing: Promise<'graceful' | 'forced'> | undefined + try { + const sessionId = await active.page.evaluate( + async ({ baseUrl, projectDir }) => { + const providerId = `custom-${crypto.randomUUID()}` + await window.deepchat.invoke('providers.add', { + provider: { + id: providerId, + name: 'Hook fixture', + apiType: 'openai-completions', + baseUrl, + apiKey: 'fixture-key', + enable: true, + custom: true + } + }) + await window.deepchat.invoke('models.addCustom', { + providerId, + model: { + id: 'hook-fixture', + name: 'Hook fixture', + enabled: true, + vision: false, + functionCall: false, + reasoning: false, + contextLength: 32000, + maxTokens: 8000 + } + }) + await window.deepchat.invoke('models.setStatus', { + providerId, + modelId: 'hook-fixture', + enabled: true + }) + const { config } = await window.deepchat.invoke('config.resolveDeepChatAgentConfig', { + agentId: 'deepchat' + }) + await window.deepchat.invoke('config.updateDeepChatAgent', { + agentId: 'deepchat', + updates: { + config: { + ...config, + memoryEnabled: false, + enabledMcpServerIds: [], + enabledSkillNames: [] + } + } + }) + await window.deepchat.invoke('config.setHooksNotifications', { + config: { + hooks: [ + { + id: 'held-hook', + name: 'Held hook', + enabled: true, + command: 'node hook-lifecycle.cjs && node -e "process.exit(0)"', + events: ['SessionStart'] + } + ] + } + }) + const { session } = await window.deepchat.invoke('sessions.create', { + agentId: 'deepchat', + providerId, + modelId: 'hook-fixture', + message: 'Run the owned hook.', + projectDir + }) + return session.id + }, + { baseUrl: `http://127.0.0.1:${port}/v1`, projectDir: profile } + ) + await expect.poll(() => existsSync(pidFile)).toBe(true) + const child = JSON.parse(readFileSync(pidFile, 'utf8')) as { pid: number; parent: number } + childPid = child.pid + expect(child.pid).toBeGreaterThan(0) + expect(child.parent).toBeGreaterThan(0) + await expect + .poll(async () => + JSON.stringify( + await active.page.evaluate( + (sessionId) => + window.deepchat.invoke('sessions.listMessagesPage', { sessionId, limit: 100 }), + sessionId + ) + ) + ) + .toContain('HOOK_COMPLETE') + const savedPayload = readFileSync(payloadFile, 'utf8') + const payload = JSON.parse(savedPayload) as HookEventPayload + const version = await active.electronApp.evaluate(({ app }) => app.getVersion()) + expect(payload).toMatchObject({ + payloadVersion: 1, + event: 'SessionStart', + isTest: false, + app: { version, platform: process.platform }, + session: { conversationId: sessionId, workdir: profile } + }) + + let closed = false + closing = active.close().then((outcome) => { + closed = true + return outcome + }) + await expect + .poll(() => processIsAlive(child.parent), { timeout: 5000, intervals: [25, 50, 100] }) + .toBe(false) + expect(processIsAlive(child.pid)).toBe(true) + expect(closed).toBe(false) + expect(active.electronApp.process().exitCode).toBeNull() + writeFileSync(releaseFile, 'release') + expect(await closing).toBe('graceful') + await expect.poll(() => processIsAlive(child.pid)).toBe(false) + + active = await launchApp() + await waitForAppReady(active.page) + expect(readFileSync(payloadFile, 'utf8')).toBe(savedPayload) + expect(await active.close()).toBe('graceful') + } finally { + writeFileSync(releaseFile, 'release') + let cleanupError: unknown + if (childPid !== undefined && processIsAlive(childPid)) { + try { + await expect.poll(() => processIsAlive(childPid!), { timeout: 5000 }).toBe(false) + } catch (error) { + process.kill(childPid, 'SIGKILL') + cleanupError = error + } + } + if (closing) await closing + server.closeAllConnections() + await new Promise((resolve) => server.close(() => resolve())) + if (cleanupError) throw cleanupError + } +}) diff --git a/test/e2e/specs/30-workspace-watcher-events.smoke.spec.ts b/test/e2e/specs/30-workspace-watcher-events.smoke.spec.ts index 31419f1276..e5d184ce54 100644 --- a/test/e2e/specs/30-workspace-watcher-events.smoke.spec.ts +++ b/test/e2e/specs/30-workspace-watcher-events.smoke.spec.ts @@ -151,4 +151,5 @@ test('workspace watcher emits file and git invalidations through the typed bridg .catch(() => undefined) rmSync(workspacePath, { recursive: true, force: true }) } + expect(await app.close()).toBe('graceful') }) diff --git a/test/e2e/specs/33-provider-custom-headers.smoke.spec.ts b/test/e2e/specs/33-provider-custom-headers.smoke.spec.ts index 1d0ab895c7..77f53f3343 100644 --- a/test/e2e/specs/33-provider-custom-headers.smoke.spec.ts +++ b/test/e2e/specs/33-provider-custom-headers.smoke.spec.ts @@ -13,7 +13,7 @@ test('provider headers use a JSON editor above the model list @smoke', async ({ const providerRow = settingsPage.locator('[data-provider-id="openai"]').first() if (!(await providerRow.isVisible())) { - await settingsPage.getByTestId('disabled-providers-toggle').click() + await settingsPage.getByTestId('provider-browse-all').click() } await selectProvider(settingsPage, 'openai') diff --git a/test/e2e/specs/34-user-plugin-install.smoke.spec.ts b/test/e2e/specs/34-user-plugin-install.smoke.spec.ts index c2bac4f47d..244761c557 100644 --- a/test/e2e/specs/34-user-plugin-install.smoke.spec.ts +++ b/test/e2e/specs/34-user-plugin-install.smoke.spec.ts @@ -7,6 +7,61 @@ import { waitForAppReady } from '../helpers/wait' const require = createRequire(import.meta.url) +test('official settings windows close on disable and shutdown despite beforeunload @smoke', async ({ + launchApp +}) => { + let active = await launchApp() + await waitForAppReady(active.page) + const pluginId = 'com.deepchat.plugins.feishu' + const openSettings = async () => { + const { result } = await active.page.evaluate( + (pluginId) => + window.deepchat.invoke('plugins.invokeAction', { pluginId, actionId: 'settings.open' }), + pluginId + ) + expect(result.ok, result.error).toBe(true) + const settings = active.electronApp + .windows() + .find((page) => page.url().includes('/settings/index.html?pluginId=')) + expect(settings).toBeDefined() + expect( + await settings!.evaluate(() => ({ node: typeof process, require: typeof require })) + ).toEqual({ node: 'undefined', require: 'undefined' }) + await settings!.evaluate(() => { + window.addEventListener('beforeunload', (event) => { + event.preventDefault() + event.returnValue = '' + }) + }) + return settings! + } + const settings = await openSettings() + const { result } = await active.page.evaluate( + (pluginId) => window.deepchat.invoke('plugins.disable', { pluginId }), + pluginId + ) + expect(result.ok, result.error).toBe(true) + await expect.poll(() => settings.isClosed()).toBe(true) + expect( + await active.electronApp.evaluate( + ({ BrowserWindow }) => + BrowserWindow.getAllWindows().filter((window) => + window.webContents.getURL().includes('/settings/index.html?pluginId=') + ).length + ) + ).toBe(0) + expect(await active.close()).toBe('graceful') + active = await launchApp() + await waitForAppReady(active.page) + const { plugin } = await active.page.evaluate( + (pluginId) => window.deepchat.invoke('plugins.get', { pluginId }), + pluginId + ) + expect(plugin?.enabled).toBe(false) + await openSettings() + expect(await active.close()).toBe('graceful') +}) + test('ZIP review installs a disabled user plugin and imports a working MCP server @smoke', async ({ app }, testInfo) => { diff --git a/test/e2e/specs/36-chat-streaming.smoke.spec.ts b/test/e2e/specs/36-chat-streaming.smoke.spec.ts index d79c471983..439916764d 100644 --- a/test/e2e/specs/36-chat-streaming.smoke.spec.ts +++ b/test/e2e/specs/36-chat-streaming.smoke.spec.ts @@ -2,12 +2,14 @@ import { createServer } from 'node:http' import { writeFileSync } from 'node:fs' import { join } from 'node:path' import { test, expect } from '../fixtures/electronApp' -import { selectAgent, selectModel, sendMessage } from '../helpers/chat' +import { getActiveSessionId, selectAgent, selectModel, sendMessage } from '../helpers/chat' import { waitForAppReady, waitForGenerationDone } from '../helpers/wait' test('local streaming preserves an editable composer and completes the response @smoke', async ({ - app + app, + launchApp }) => { + expect(app.ownsUserDataDir).toBe(true) let releaseResponse = () => {} const released = new Promise((resolve) => { releaseResponse = resolve @@ -157,6 +159,37 @@ test('local streaming preserves an editable composer and completes the response await expect(app.page.getByTestId('chat-message-user')).toHaveCount(1) } expect(app.pageErrors).toEqual([]) + const sessionId = await getActiveSessionId(app.page) + const beforeClear = await app.page.evaluate( + (sessionId) => window.deepchat.invoke('sessions.listMessagesPage', { sessionId, limit: 10 }), + sessionId + ) + expect(beforeClear.messages).toHaveLength(2) + await shell.getByRole('button', { name: 'More', exact: true }).click() + await app.page.getByRole('menuitem', { name: 'Clear Messages', exact: true }).click() + await app.page.getByRole('button', { name: 'Clear', exact: true }).click() + await expect(app.page.getByTestId('chat-message-assistant')).toHaveCount(0) + expect( + ( + await app.page.evaluate( + (sessionId) => window.deepchat.invoke('sessions.listPendingInputs', { sessionId }), + sessionId + ) + ).items + ).toEqual([]) + expect(await app.close()).toBe('graceful') + const reopened = await launchApp() + await waitForAppReady(reopened.page) + expect( + ( + await reopened.page.evaluate( + (sessionId) => + window.deepchat.invoke('sessions.listMessagesPage', { sessionId, limit: 10 }), + sessionId + ) + ).messages + ).toEqual([]) + expect(await reopened.close()).toBe('graceful') } finally { releaseResponse() server.closeAllConnections() diff --git a/test/e2e/specs/37-session-references.smoke.spec.ts b/test/e2e/specs/37-session-references.smoke.spec.ts index e3bca59779..d4fe92ccb8 100644 --- a/test/e2e/specs/37-session-references.smoke.spec.ts +++ b/test/e2e/specs/37-session-references.smoke.spec.ts @@ -14,7 +14,7 @@ import { waitForAppReady } from '../helpers/wait' test('session references survive drafts and retrieve evidence without injecting history @smoke', async ({ app -}) => { +}, testInfo) => { let sourceId = '' let sawReader = false let referenceRequest = '' @@ -201,7 +201,7 @@ test('session references survive drafts and retrieve evidence without injecting await editor.fill('@Launch') const candidate = app.page.getByRole('option', { name: /Launch research/ }) await expect(candidate).toBeVisible() - const artifacts = resolve('.amp/in/artifacts') + const artifacts = testInfo.outputPath('composer-artifacts') mkdirSync(artifacts, { recursive: true }) await app.page.screenshot({ path: resolve(artifacts, 'composer-experience-candidates.png') }) await candidate.click() @@ -223,6 +223,8 @@ test('session references survive drafts and retrieve evidence without injecting await chip.getByRole('button', { name: /Delete|删除/ }).focus() await app.page.keyboard.press('Space') await expect(chip).toHaveCount(0) + const sidebarToggle = app.page.getByTestId('window-sidebar-toggle') + if ((await sidebarToggle.getAttribute('aria-expanded')) === 'false') await sidebarToggle.click() const sourceRow = app.page .locator(`[data-testid="sidebar-session-item"][data-session-id="${sourceId}"]`) .first() @@ -387,6 +389,18 @@ test('session references survive drafts and retrieve evidence without injecting ).toHaveCount(0) // Restore the legacy inline representation without changing its backing uploaded material. + await expect + .poll(async () => { + const messageId = await sentAttachment.getAttribute('data-message-id') + return app.page.evaluate( + async ({ sessionId, messageId }) => { + const { messages } = await window.deepchat.invoke('sessions.restore', { sessionId }) + return messages.some((message) => message.id === messageId) + }, + { sessionId: workspaceSessionId, messageId } + ) + }) + .toBe(true) const attachmentMessageId = await sentAttachment.getAttribute('data-message-id') await app.page.evaluate( async ({ sessionId, messageId }) => { diff --git a/test/e2e/specs/39-composer-paste.smoke.spec.ts b/test/e2e/specs/39-composer-paste.smoke.spec.ts index 5a158a9938..7c37df4466 100644 --- a/test/e2e/specs/39-composer-paste.smoke.spec.ts +++ b/test/e2e/specs/39-composer-paste.smoke.spec.ts @@ -155,6 +155,8 @@ test('unfocused composer accepts text, links, images and files once @smoke', asy }) return session.id }) + const sidebarToggle = app.page.getByTestId('window-sidebar-toggle') + if ((await sidebarToggle.getAttribute('aria-expanded')) === 'false') await sidebarToggle.click() await app.page .locator(`[data-testid="sidebar-session-item"][data-session-id="${sessionId}"]`) .click() diff --git a/test/e2e/specs/42-provider-connection-save.smoke.spec.ts b/test/e2e/specs/42-provider-connection-save.smoke.spec.ts index 35d1475da0..2037f95e47 100644 --- a/test/e2e/specs/42-provider-connection-save.smoke.spec.ts +++ b/test/e2e/specs/42-provider-connection-save.smoke.spec.ts @@ -3,6 +3,7 @@ import { test, expect } from '../fixtures/electronApp' import { openSettings, openSettingsTab, selectProvider } from '../helpers/settings' import { waitForAppReady } from '../helpers/wait' import { DEEPCHAT_ROUTE_INVOKE_CHANNEL } from '../../../src/shared/contracts/channels' +import { ModelType } from '../../../src/shared/model' test('connection saving does not depend on the fixed probe model @smoke', async ({ app @@ -49,34 +50,37 @@ test('connection saving does not depend on the fixed probe model @smoke', async const baseUrl = `http://127.0.0.1:${address.port}/v1` try { await waitForAppReady(app.page) - const baseline = await app.page.evaluate(async (url) => { - const { provider } = await window.deepchat.invoke('providers.update', { - providerId: 'alibaba-token-plan-cn', - updates: { apiKey: 'old-fixture-key', baseUrl: url, enable: false } - }) - await window.deepchat.invoke('models.saveCustom', { - providerId: provider.id, - config: { - maxTokens: 2048, - contextLength: 8192, - vision: false, - functionCall: false, - reasoning: false, - type: 'chat', - isUserDefined: true - }, - model: { - id: 'custom-text-fixture', - name: 'Custom text fixture', - type: 'chat', - enabled: true - } - }) - return await window.deepchat.invoke('providers.validateDraft', { - provider: { ...provider, apiKey: 'replacement-fixture-key' }, - loadModels: false - }) - }, baseUrl) + const baseline = await app.page.evaluate( + async ({ url, modelType }) => { + const { provider } = await window.deepchat.invoke('providers.update', { + providerId: 'alibaba-token-plan-cn', + updates: { apiKey: 'old-fixture-key', baseUrl: url, enable: false } + }) + await window.deepchat.invoke('models.saveCustom', { + providerId: provider.id, + config: { + maxTokens: 2048, + contextLength: 8192, + vision: false, + functionCall: false, + reasoning: false, + type: modelType, + isUserDefined: true + }, + model: { + id: 'custom-text-fixture', + name: 'Custom text fixture', + type: modelType, + enabled: true + } + }) + return await window.deepchat.invoke('providers.validateDraft', { + provider: { ...provider, apiKey: 'replacement-fixture-key' }, + loadModels: false + }) + }, + { url: baseUrl, modelType: ModelType.Chat } + ) expect(baseline).toMatchObject({ isOk: false, errorMsg: 'AccessDenied.Unpurchased' }) const page = await openSettings(app) @@ -235,10 +239,10 @@ test('connection saving does not depend on the fixed probe model @smoke', async await page.locator('#modelName').fill('Unsaved custom fixture') await page .getByRole('dialog') - .getByRole('button', { name: /^(保存配置|Save Configuration)$/ }) + .getByRole('button', { name: /^(保存配置|Save configuration)$/ }) .click() await expect(page.getByRole('dialog').getByRole('alert')).toContainText( - /保存失败|Failed to save/ + /保存失败|Could not save/ ) await expect(page.locator('#modelId')).toHaveValue('unsaved-custom-fixture') await page.screenshot({ path: testInfo.outputPath('custom-model-save-failed.png') }) diff --git a/test/e2e/specs/44-admission-delegation.smoke.spec.ts b/test/e2e/specs/44-admission-delegation.smoke.spec.ts new file mode 100644 index 0000000000..67109f1e49 --- /dev/null +++ b/test/e2e/specs/44-admission-delegation.smoke.spec.ts @@ -0,0 +1,340 @@ +import { createServer } from 'node:http' +import { readFileSync } from 'node:fs' +import { join } from 'node:path' +import Database from 'better-sqlite3-multiple-ciphers' +import { test, expect } from '../fixtures/electronApp' +import { waitForAppReady } from '../helpers/wait' +import type { LiveDelegationSummary } from '../../../src/shared/orchestration/liveDelegation' +import type { ModelType } from '../../../src/shared/model' + +test.use({ privacyModeEnabled: true }) + +test('delegation admission queues, cancels and releases execution slots @smoke', async ({ + launchApp +}, testInfo) => { + const memoryRows = Number(process.env.DEEPCHAT_E2E_MEMORY_LOAD_ROWS ?? 0) + expect([0, 1000, 10000, 50000]).toContain(memoryRows) + let embeddingBatches = 0 + const started = new Set() + const releases = new Map void>() + const server = createServer(async (request, response) => { + if (request.url === '/v1/models') { + response + .writeHead(200, { 'Content-Type': 'application/json' }) + .end(JSON.stringify({ data: [{ id: 'admission-fixture' }] })) + return + } + if (!['/v1/chat/completions', '/v1/embeddings'].includes(request.url ?? '')) { + response.writeHead(404).end() + return + } + let source = '' + for await (const chunk of request) source += chunk + const body = JSON.parse(source) + if (request.url === '/v1/embeddings') { + const inputs = Array.isArray(body.input) ? body.input : [body.input] + embeddingBatches++ + response.writeHead(200, { 'Content-Type': 'application/json' }).end( + JSON.stringify({ + object: 'list', + model: body.model, + data: inputs.map((_: string, index: number) => ({ + object: 'embedding', + index, + embedding: Array.from({ length: 1536 }, (_, component) => (component === 0 ? 1 : 0)) + })), + usage: { prompt_tokens: inputs.length, total_tokens: inputs.length } + }) + ) + return + } + if (!body.stream) { + response.writeHead(200, { 'Content-Type': 'application/json' }).end( + JSON.stringify({ + id: 'title', + object: 'chat.completion', + created: 1, + model: 'admission-fixture', + choices: [ + { + index: 0, + message: { role: 'assistant', content: 'Admission verification' }, + finish_reason: 'stop' + } + ] + }) + ) + return + } + response.writeHead(200, { 'Content-Type': 'text/event-stream' }) + const emit = (delta: unknown, finishReason: string | null = null) => + response.write( + `data: ${JSON.stringify({ + id: 'admission-fixture', + object: 'chat.completion.chunk', + created: 1, + model: 'admission-fixture', + choices: [{ index: 0, delta, finish_reason: finishReason }] + })}\n\n` + ) + const userText = body.messages + .filter((message: { role: string }) => message.role === 'user') + .map((message: { content: unknown }) => JSON.stringify(message.content)) + .join('\n') + const child = /CHILD_ADMISSION_(\d)/.exec(userText) + if (child) { + const index = Number(child[1]) + started.add(index) + await new Promise((resolve) => { + releases.set(index, resolve) + response.once('close', resolve) + }) + if (response.destroyed) return + emit({ content: `CHILD_${index}_DONE` }, 'stop') + } else if (!body.messages.some((message: { role: string }) => message.role === 'tool')) { + emit( + { + role: 'assistant', + tool_calls: Array.from({ length: 4 }, (_, slot) => { + const index = slot + (userText.includes('PARENT_ADMISSION_B') ? 4 : 0) + return { + index, + id: `spawn-${index}`, + type: 'function', + function: { + name: 'deepchat_subagents', + arguments: JSON.stringify({ + operation: 'spawn', + slotId: 'explorer', + title: `Admission verification ${index}`, + prompt: `CHILD_ADMISSION_${index}: Reply with CHILD_${index}_DONE without using tools.` + }) + } + } + }) + }, + 'tool_calls' + ) + } else emit({ content: 'Delegation requests accepted.' }, 'stop') + response.end('data: [DONE]\n\n') + }) + await new Promise((resolve) => server.listen(0, '127.0.0.1', resolve)) + const { port } = server.address() as { port: number } + try { + let app = await launchApp() + await waitForAppReady(app.page) + const providerId = await app.page.evaluate( + async ({ baseUrl, memoryRows }) => { + const providerId = `custom-${crypto.randomUUID()}` + await window.deepchat.invoke('providers.add', { + provider: { + id: providerId, + name: 'Admission fixture', + apiType: 'openai-completions', + baseUrl, + apiKey: 'fixture-key', + enable: true, + custom: true + } + }) + await window.deepchat.invoke('models.addCustom', { + providerId, + model: { + id: 'admission-fixture', + name: 'Admission fixture', + enabled: true, + vision: false, + functionCall: true, + reasoning: false, + contextLength: 32000, + maxTokens: 8000 + } + }) + await window.deepchat.invoke('models.setStatus', { + providerId, + modelId: 'admission-fixture', + enabled: true + }) + if (memoryRows) + await window.deepchat.invoke('models.addCustom', { + providerId, + model: { + id: 'load-embedding', + name: 'Load embedding', + type: 'embedding' as ModelType, + enabled: true, + vision: false, + functionCall: false, + reasoning: false, + contextLength: 8192, + maxTokens: 8192 + } + }) + const { config } = await window.deepchat.invoke('config.resolveDeepChatAgentConfig', { + agentId: 'deepchat' + }) + await window.deepchat.invoke('config.updateDeepChatAgent', { + agentId: 'deepchat', + updates: { + config: { + ...config, + ...(memoryRows + ? { + memoryEnabled: true, + memoryEmbedding: { providerId, modelId: 'load-embedding' } + } + : {}), + subagentEnabled: true, + permissionMode: 'full_access', + enabledSkillNames: [], + disabledAgentTools: [] + } + } + }) + return providerId + }, + { baseUrl: `http://127.0.0.1:${port}/v1`, memoryRows } + ) + if (memoryRows) { + expect(app.ownsUserDataDir).toBe(true) + expect(await app.close()).toBe('graceful') + const database = new Database(join(app.userDataDir, 'app_db', 'agent.db')) + try { + const insert = database.prepare(`INSERT INTO agent_memory + (id, agent_id, kind, content, created_at, status, embedding_state, lifecycle_state) + VALUES (?, 'deepchat', 'semantic', ?, ?, 'fts_only', 'fts_only', 'active')`) + database.transaction(() => { + const now = Date.now() + for (let index = 0; index < memoryRows; index++) + insert.run(`load-${index}`, `Concurrent native indexing fact ${index}.`, now) + })() + } finally { + database.close() + } + app = await launchApp() + await waitForAppReady(app.page) + await app.page.evaluate(() => + window.deepchat.invoke('memory.reindex', { agentId: 'deepchat' }) + ) + } + const firstParent = await app.page.evaluate( + async ({ providerId, projectDir }) => { + const { session } = await window.deepchat.invoke('sessions.create', { + agentId: 'deepchat', + message: 'PARENT_ADMISSION_A: Delegate four isolated admission verification tasks.', + providerId, + modelId: 'admission-fixture', + projectDir, + permissionMode: 'full_access', + orchestrationPolicy: 'proactive' + }) + return { parentSessionId: session.id, providerId } + }, + { providerId, projectDir: app.userDataDir } + ) + await expect.poll(() => started.size).toBe(4) + if (memoryRows) { + expect(embeddingBatches).toBeGreaterThan(0) + const { status } = await app.page.evaluate(() => + window.deepchat.invoke('memory.getStatus', { agentId: 'deepchat' }) + ) + expect(status.pendingEmbedding).toBeGreaterThan(0) + } + const secondParentId = await app.page.evaluate( + async ({ providerId, projectDir }) => { + const { session } = await window.deepchat.invoke('sessions.create', { + agentId: 'deepchat', + message: 'PARENT_ADMISSION_B: Delegate four isolated admission verification tasks.', + providerId, + modelId: 'admission-fixture', + projectDir, + permissionMode: 'full_access', + orchestrationPolicy: 'proactive' + }) + return session.id + }, + { providerId: firstParent.providerId, projectDir: app.userDataDir } + ) + const list = (parentSessionId: string) => + app.page.evaluate(async (parentSessionId) => { + const { delegations } = await window.deepchat.invoke('orchestration.liveDelegation.list', { + parentSessionId, + limit: 100 + }) + return delegations + }, parentSessionId) as Promise + const listAll = async () => [ + ...(await list(firstParent.parentSessionId)), + ...(await list(secondParentId)) + ] + await expect.poll(async () => (await listAll()).length).toBe(8) + await expect.poll(() => started.size).toBe(6) + const queued = (await listAll()).find( + (delegation) => delegation.title === 'Admission verification 7' + )! + expect(queued.status).toBe('queued') + await app.page.evaluate( + async ({ parentSessionId, delegationId }) => { + await window.deepchat.invoke('orchestration.liveDelegation.interrupt', { + parentSessionId, + delegationId + }) + }, + { parentSessionId: secondParentId, delegationId: queued.id } + ) + expect(started.has(7)).toBe(false) + releases.get(0)!() + await expect.poll(() => started.has(6)).toBe(true) + for (const release of releases.values()) release() + await expect + .poll(async () => (await listAll()).map((delegation) => delegation.status).sort()) + .toEqual(['idle', 'idle', 'idle', 'idle', 'idle', 'idle', 'idle', 'interrupted']) + const final = await listAll() + expect(final.find((delegation) => delegation.id === queued.id)?.status).toBe('interrupted') + expect([...started].sort()).toEqual([0, 1, 2, 3, 4, 5, 6]) + if (memoryRows) + await expect + .poll( + async () => + ( + await app.page.evaluate(() => + window.deepchat.invoke('memory.getStatus', { agentId: 'deepchat' }) + ) + ).status.lastReindex?.outcome, + { timeout: 180_000 } + ) + .toBe('completed') + expect(app.pageErrors).toEqual([]) + expect(await app.close()).toBe('graceful') + const records = readFileSync(join(app.userDataDir, 'logs', 'main.jsonl'), 'utf8') + .trim() + .split('\n') + .map((line) => JSON.parse(line)) + const admission = records.filter((record) => record.event.startsWith('agent.admission.')) + expect(admission.some((record) => record.event === 'agent.admission.queued')).toBe(true) + expect(admission.some((record) => record.event === 'agent.admission.rejected')).toBe(true) + expect(admission.at(-1)).toMatchObject({ event: 'agent.admission.closed' }) + await testInfo.attach('admission-comparison.json', { + body: Buffer.from( + JSON.stringify( + { + completed: 7, + interrupted: 1, + peakStartedBeforeRelease: 6, + started: [...started].sort(), + memoryRows, + embeddingBatches, + events: admission.map((record) => record.event) + }, + null, + 2 + ) + ), + contentType: 'application/json' + }) + } finally { + for (const release of releases.values()) release() + server.closeAllConnections() + await new Promise((resolve) => server.close(() => resolve())) + } +}) diff --git a/test/e2e/specs/45-memory-vector-runtime.smoke.spec.ts b/test/e2e/specs/45-memory-vector-runtime.smoke.spec.ts new file mode 100644 index 0000000000..b1044fea5d --- /dev/null +++ b/test/e2e/specs/45-memory-vector-runtime.smoke.spec.ts @@ -0,0 +1,248 @@ +import { createServer } from 'node:http' +import { existsSync, readFileSync, writeFileSync } from 'node:fs' +import { join } from 'node:path' +import Database from 'better-sqlite3-multiple-ciphers' +import { DuckDBInstance } from '@duckdb/node-api' +import { test, expect } from '../fixtures/electronApp' +import { waitForAppReady } from '../helpers/wait' +import type { ModelType } from '../../../src/shared/model' + +test.use({ privacyModeEnabled: true }) + +test('native Memory indexing, vector-only recall, deletion and clear survive relaunch @smoke', async ({ + launchApp +}, testInfo) => { + const rows = Number(process.env.DEEPCHAT_E2E_MEMORY_ROWS ?? 1000) + expect([1000, 10000, 50000]).toContain(rows) + const embeddingRequests: number[] = [] + const server = createServer(async (request, response) => { + if (request.url === '/v1/models') { + response + .writeHead(200, { 'Content-Type': 'application/json' }) + .end(JSON.stringify({ data: [{ id: 'fixture-embedding' }] })) + return + } + if (request.url !== '/v1/embeddings') { + response.writeHead(404).end() + return + } + let source = '' + for await (const chunk of request) source += chunk + const body = JSON.parse(source) + const inputs: string[] = Array.isArray(body.input) ? body.input : [body.input] + embeddingRequests.push(inputs.length) + response.writeHead(200, { 'Content-Type': 'application/json' }).end( + JSON.stringify({ + object: 'list', + model: body.model, + data: inputs.map((input, index) => ({ + object: 'embedding', + index, + embedding: Array.from({ length: 1536 }, (_, component) => + component === (input.includes('cobalt-lark') || input === 'quartzneedle' ? 0 : 1) + ? 1 + : Math.fround(Math.sin(component + 1) * 0.001) + ) + })), + usage: { prompt_tokens: inputs.length * 4, total_tokens: inputs.length * 4 } + }) + ) + }) + await new Promise((resolve) => server.listen(0, '127.0.0.1', resolve)) + const address = server.address() as { port: number } + try { + const initial = await launchApp() + expect(initial.ownsUserDataDir).toBe(true) + await waitForAppReady(initial.page) + await initial.page.evaluate(async (baseUrl) => { + const providerId = `custom-${crypto.randomUUID()}` + await window.deepchat.invoke('providers.add', { + provider: { + id: providerId, + name: 'Memory vector fixture', + apiType: 'openai-completions', + baseUrl, + apiKey: 'fixture-key', + enable: true, + custom: true + } + }) + await window.deepchat.invoke('models.addCustom', { + providerId, + model: { + id: 'fixture-embedding', + name: 'Fixture Embedding', + type: 'embedding' as ModelType, + enabled: true, + vision: false, + functionCall: false, + reasoning: false, + contextLength: 8192, + maxTokens: 8192 + } + }) + await window.deepchat.invoke('config.updateDeepChatAgent', { + agentId: 'deepchat', + updates: { + config: { + memoryEnabled: true, + memoryEmbedding: { providerId, modelId: 'fixture-embedding' } + } + } + }) + }, `http://127.0.0.1:${address.port}/v1`) + expect(await initial.close()).toBe('graceful') + + const securityPath = join(initial.userDataDir, 'database-security.json') + if (existsSync(securityPath)) { + expect(JSON.parse(readFileSync(securityPath, 'utf8')).metadata?.enabled).not.toBe(true) + } + // Seed canonical rows only while the disposable application's writer is closed. + const db = new Database(join(initial.userDataDir, 'app_db', 'agent.db')) + try { + const insert = db.prepare(`INSERT INTO agent_memory + (id, agent_id, kind, content, created_at, status, embedding_state, lifecycle_state) + VALUES (?, 'deepchat', 'semantic', ?, ?, 'fts_only', 'fts_only', 'active')`) + db.transaction(() => { + const now = Date.now() + insert.run('native-anchor', 'The project codename is cobalt-lark.', now) + for (let index = 1; index < rows; index++) { + insert.run(`native-filler-${index}`, `Baseline fact ${index} about slow rivers.`, now) + } + })() + } finally { + db.close() + } + + const app = await launchApp() + await waitForAppReady(app.page) + await app.page.evaluate(() => window.deepchat.invoke('memory.reindex', { agentId: 'deepchat' })) + await expect + .poll( + async () => { + const { status } = await app.page.evaluate(() => + window.deepchat.invoke('memory.getStatus', { agentId: 'deepchat' }) + ) + return { pending: status.pendingEmbedding, outcome: status.lastReindex?.outcome } + }, + // Reserve a minute for recall, the fixed idle window and relaunch within the test limit. + { timeout: rows === 50000 ? testInfo.timeout - 60_000 : 120_000 } + ) + .toEqual({ pending: 0, outcome: 'completed' }) + + const timings: number[] = [] + for (let sample = 0; sample < 12; sample++) { + const startedAt = performance.now() + const result = await app.page.evaluate(() => + window.deepchat.invoke('memory.search', { + agentId: 'deepchat', + query: 'quartzneedle', + limit: 20 + }) + ) + timings.push(performance.now() - startedAt) + expect(result.results).toEqual([ + expect.objectContaining({ + id: 'native-anchor', + sources: expect.objectContaining({ vec: true }) + }) + ]) + } + const { health } = await app.page.evaluate(() => + window.deepchat.invoke('memory.getHealth', { agentId: 'deepchat' }) + ) + expect(health.runtime.agent.retrieval.search.vectorCandidates).toBeGreaterThan(0) + expect(health.runtime.process.vector.openStores).toBeGreaterThan(0) + expect(embeddingRequests.some((count) => count > 1)).toBe(true) + + const deletion = await app.page.evaluate(() => + window.deepchat.invoke('memory.delete', { agentId: 'deepchat', memoryId: 'native-anchor' }) + ) + expect(deletion.action).toBe('applied') + expect( + await app.page.evaluate(() => + window.deepchat.invoke('memory.search', { + agentId: 'deepchat', + query: 'quartzneedle', + limit: 20 + }) + ) + ).toEqual({ results: [] }) + // Measure settled footprint without forcing GC or slowing normal E2E runs. + if (process.env.DEEPCHAT_E2E_MEASURE_RESOURCES === '1') + await new Promise((resolve) => setTimeout(resolve, 30_000)) + expect(await app.close()).toBe('graceful') + + const instance = await DuckDBInstance.create( + join(app.userDataDir, 'app_db', 'AgentMemory', 'deepchat.v2.duckdb') + ) + const connection = await instance.connect() + try { + const result = await connection.runAndReadAll( + 'SELECT count(*)::INTEGER AS total FROM memory_vector' + ) + expect(result.getRowObjectsJson()).toEqual([{ total: rows - 1 }]) + } finally { + connection.closeSync() + instance.closeSync() + } + + const reopened = await launchApp() + await waitForAppReady(reopened.page) + expect( + await reopened.page.evaluate(() => + window.deepchat.invoke('memory.search', { + agentId: 'deepchat', + query: 'quartzneedle', + limit: 20 + }) + ) + ).toEqual({ results: [] }) + const { status: beforeClear } = await reopened.page.evaluate(() => + window.deepchat.invoke('memory.getStatus', { agentId: 'deepchat' }) + ) + expect(beforeClear.total).toBeGreaterThanOrEqual(rows - 1) + const cleared = await reopened.page.evaluate(() => + window.deepchat.invoke('memory.clear', { agentId: 'deepchat' }) + ) + expect(cleared.cleanupPendingRestart).toBe(false) + expect(cleared.removed).toBeGreaterThanOrEqual(beforeClear.total) + expect( + await reopened.page.evaluate(() => + window.deepchat.invoke('memory.getStatus', { agentId: 'deepchat' }) + ) + ).toMatchObject({ status: { total: 0, pendingEmbedding: 0 } }) + expect(await reopened.close()).toBe('graceful') + const clearedDb = new Database(join(reopened.userDataDir, 'app_db', 'agent.db'), { + readonly: true + }) + try { + expect( + clearedDb + .prepare("SELECT count(*) AS total FROM agent_memory WHERE agent_id = 'deepchat'") + .get() + ).toEqual({ total: 0 }) + } finally { + clearedDb.close() + } + const evidence = testInfo.outputPath('native-memory-baseline.json') + writeFileSync( + evidence, + JSON.stringify({ + rows, + dimensions: 1536, + embeddingShape: 'dense-f32-sine-noise', + timingsMs: timings, + embeddingRequests, + beforeClear + }) + ) + await testInfo.attach('native-memory-baseline.json', { + path: evidence, + contentType: 'application/json' + }) + } finally { + server.closeAllConnections() + await new Promise((resolve) => server.close(() => resolve())) + } +}) diff --git a/test/e2e/specs/46-memory-runtime-lifecycle.smoke.spec.ts b/test/e2e/specs/46-memory-runtime-lifecycle.smoke.spec.ts new file mode 100644 index 0000000000..1eb359fbb6 --- /dev/null +++ b/test/e2e/specs/46-memory-runtime-lifecycle.smoke.spec.ts @@ -0,0 +1,330 @@ +import { execFileSync } from 'node:child_process' +import { createServer } from 'node:http' +import { test, expect } from '../fixtures/electronApp' +import type { ModelType } from '../../../src/shared/model' +import { waitForAppReady } from '../helpers/wait' + +test.use({ privacyModeEnabled: true }) + +function processes(): Array<{ pid: number; parent: number; command: string }> { + if (process.platform === 'win32') { + const rows = JSON.parse( + execFileSync( + 'powershell.exe', + [ + '-NoProfile', + '-NonInteractive', + '-Command', + 'Get-CimInstance Win32_Process | Select-Object ProcessId,ParentProcessId,Name | ConvertTo-Json -Compress' + ], + { encoding: 'utf8' } + ) + ) + return rows.map((row: { ProcessId: number; ParentProcessId: number; Name: string }) => ({ + pid: row.ProcessId, + parent: row.ParentProcessId, + command: row.Name + })) + } + return execFileSync('ps', ['-axo', 'pid=,ppid=,command='], { encoding: 'utf8' }) + .split('\n') + .flatMap((line) => { + const match = /^\s*(\d+)\s+(\d+)\s+(.+)$/.exec(line) + return match ? [{ pid: Number(match[1]), parent: Number(match[2]), command: match[3] }] : [] + }) +} + +test('Memory evicts idle stores, changes models and survives native worker loss @smoke', async ({ + launchApp +}) => { + const server = createServer(async (request, response) => { + if (request.url === '/v1/models') { + response.writeHead(200, { 'Content-Type': 'application/json' }).end( + JSON.stringify({ + data: ['fixture-embedding-16', 'fixture-embedding-32', 'fixture-chat'].map((id) => ({ + id + })) + }) + ) + return + } + let source = '' + for await (const chunk of request) source += chunk + const body = source ? JSON.parse(source) : {} + if (request.url === '/v1/embeddings') { + const inputs: string[] = Array.isArray(body.input) ? body.input : [body.input] + response.writeHead(200, { 'Content-Type': 'application/json' }).end( + JSON.stringify({ + object: 'list', + model: body.model, + data: inputs.map((input, index) => ({ + object: 'embedding', + index, + embedding: Array.from( + { length: body.model.endsWith('-32') ? 32 : 16 }, + (_, component) => + component === (input.includes('cobalt-lark') || input === 'quartzneedle' ? 0 : 1) + ? 1 + : 0 + ) + })), + usage: { prompt_tokens: inputs.length * 4, total_tokens: inputs.length * 4 } + }) + ) + return + } + if (request.url !== '/v1/chat/completions') { + response.writeHead(404).end() + return + } + if (!body.stream) { + response.writeHead(200, { 'Content-Type': 'application/json' }).end( + JSON.stringify({ + id: 'fixture', + object: 'chat.completion', + created: 1, + model: body.model, + choices: [ + { + index: 0, + message: { role: 'assistant', content: 'Memory fixture' }, + finish_reason: 'stop' + } + ] + }) + ) + return + } + response.writeHead(200, { 'Content-Type': 'text/event-stream' }) + response.write( + `data: ${JSON.stringify({ + id: 'fixture', + object: 'chat.completion.chunk', + created: 1, + model: body.model, + choices: [ + { + index: 0, + delta: { role: 'assistant', content: 'MEMORY_RUNTIME_STILL_READY' }, + finish_reason: 'stop' + } + ] + })}\n\n` + ) + response.end('data: [DONE]\n\n') + }) + await new Promise((resolve) => server.listen(0, '127.0.0.1', resolve)) + const { port } = server.address() as { port: number } + try { + const initial = await launchApp() + expect(initial.ownsUserDataDir).toBe(true) + await waitForAppReady(initial.page) + const setup = await initial.page.evaluate(async (baseUrl) => { + const providerId = `custom-${crypto.randomUUID()}` + await window.deepchat.invoke('providers.add', { + provider: { + id: providerId, + name: 'Memory lifecycle fixture', + apiType: 'openai-completions', + baseUrl, + apiKey: 'fixture-key', + enable: true, + custom: true + } + }) + for (const modelId of ['fixture-embedding-16', 'fixture-embedding-32', 'fixture-chat']) { + await window.deepchat.invoke('models.addCustom', { + providerId, + model: { + id: modelId, + name: modelId, + type: (modelId === 'fixture-chat' ? 'chat' : 'embedding') as ModelType, + enabled: true, + vision: false, + functionCall: false, + reasoning: false, + contextLength: 8192, + maxTokens: 8192 + } + }) + await window.deepchat.invoke('models.setStatus', { providerId, modelId, enabled: true }) + } + const agents: string[] = [] + const memories: Record = {} + for (let index = 0; index < 9; index++) { + const { agent } = await window.deepchat.invoke('config.createDeepChatAgent', { + name: `Memory lifecycle ${index}` + }) + agents.push(agent.id) + await window.deepchat.invoke('config.updateDeepChatAgent', { + agentId: agent.id, + updates: { + config: { + memoryEnabled: true + } + } + }) + const { result } = await window.deepchat.invoke('memory.add', { + agentId: agent.id, + kind: 'semantic', + content: 'The project codename is cobalt-lark.' + }) + if (result.action !== 'created' || !result.memoryId) + throw new Error(`Memory fixture was not created: ${JSON.stringify(result)}`) + memories[agent.id] = result.memoryId + await window.deepchat.invoke('config.updateDeepChatAgent', { + agentId: agent.id, + updates: { + config: { + memoryEnabled: false, + memoryEmbedding: { providerId, modelId: 'fixture-embedding-16' } + } + } + }) + } + return { providerId, agents, memories } + }, `http://127.0.0.1:${port}/v1`) + expect(await initial.close()).toBe('graceful') + + const app = await launchApp() + await waitForAppReady(app.page) + const host = processes().find( + (row) => + row.parent === app.electronApp.process().pid && row.command.includes('deepchat-harness-') + )! + expect(host).toBeDefined() + const before = processes() + .filter((row) => row.parent === host.pid) + .map((row) => row.pid) + expect(before.length).toBeGreaterThan(0) + const recall = (agentId: string, query = 'quartzneedle') => + app.page.evaluate( + ({ agentId, query }) => + window.deepchat.invoke('memory.search', { agentId, query, limit: 5 }), + { agentId, query } + ) + for (const agentId of setup.agents) { + await app.page.evaluate(async (agentId) => { + await window.deepchat.invoke('config.updateDeepChatAgent', { + agentId, + updates: { config: { memoryEnabled: true } } + }) + await window.deepchat.invoke('memory.reindex', { agentId }) + }, agentId) + await expect + .poll(async () => + (await recall(agentId)).results.some( + (row) => row.id === setup.memories[agentId] && row.sources?.vec + ) + ) + .toBe(true) + } + const lastAgent = setup.agents.at(-1)! + await expect + .poll( + async () => + ( + await app.page.evaluate( + (agentId) => window.deepchat.invoke('memory.getHealth', { agentId }), + lastAgent + ) + ).health.runtime.process.vector.openStores + ) + .toBeLessThanOrEqual(8) + await app.page.evaluate( + async ({ agentId, providerId }) => { + await window.deepchat.invoke('config.updateDeepChatAgent', { + agentId, + updates: { + config: { + memoryEmbedding: { providerId, modelId: 'fixture-embedding-32' } + } + } + }) + await window.deepchat.invoke('memory.reindex', { agentId }) + }, + { agentId: lastAgent, providerId: setup.providerId } + ) + await expect + .poll(async () => (await recall(lastAgent)).results.some((row) => row.sources?.vec)) + .toBe(true) + const worker = processes().filter((row) => row.parent === host.pid && !before.includes(row.pid)) + expect(worker).toHaveLength(1) + expect((await recall(lastAgent, 'cobalt')).results.some((row) => row.sources?.fts)).toBe(true) + process.kill(worker[0].pid, 'SIGKILL') + await expect + .poll(async () => (await recall(lastAgent, 'cobalt')).results.some((row) => row.sources?.fts)) + .toBe(true) + expect(() => process.kill(host.pid, 0)).not.toThrow() + for (const pid of before) expect(() => process.kill(pid, 0)).not.toThrow() + expect( + processes() + .filter((row) => row.parent === host.pid) + .map((row) => row.pid) + ).toEqual(before) + const clearedAgent = setup.agents[0] + const cleared = await app.page.evaluate( + (agentId) => window.deepchat.invoke('memory.clear', { agentId }), + clearedAgent + ) + expect(cleared.removed).toBeGreaterThanOrEqual(1) + expect(cleared.cleanupPendingRestart).toBe(true) + expect((await recall(clearedAgent, 'cobalt')).results).toEqual([]) + const { session } = await app.page.evaluate( + ({ providerId, projectDir }) => + window.deepchat.invoke('sessions.create', { + agentId: 'deepchat', + providerId, + modelId: 'fixture-chat', + projectDir, + message: 'Reply after the vector worker stops.' + }), + { providerId: setup.providerId, projectDir: app.userDataDir } + ) + await expect + .poll(async () => + JSON.stringify( + await app.page.evaluate( + (sessionId) => window.deepchat.invoke('sessions.restore', { sessionId, limit: 30 }), + session.id + ) + ) + ) + .toContain('MEMORY_RUNTIME_STILL_READY') + expect(await app.close()).toBe('graceful') + const reopened = await launchApp() + await waitForAppReady(reopened.page) + expect( + ( + await reopened.page.evaluate( + (agentId) => + window.deepchat.invoke('memory.search', { agentId, query: 'cobalt', limit: 5 }), + clearedAgent + ) + ).results + ).toEqual([]) + await reopened.page.evaluate( + (agentId) => window.deepchat.invoke('memory.reindex', { agentId }), + lastAgent + ) + await expect + .poll(async () => + ( + await reopened.page.evaluate( + (agentId) => + window.deepchat.invoke('memory.search', { + agentId, + query: 'quartzneedle', + limit: 5 + }), + lastAgent + ) + ).results.some((row) => row.id === setup.memories[lastAgent] && row.sources?.vec) + ) + .toBe(true) + expect(await reopened.close()).toBe('graceful') + } finally { + server.closeAllConnections() + await new Promise((resolve) => server.close(() => resolve())) + } +}) diff --git a/test/e2e/specs/47-scheduler-store.smoke.spec.ts b/test/e2e/specs/47-scheduler-store.smoke.spec.ts new file mode 100644 index 0000000000..106b611f17 --- /dev/null +++ b/test/e2e/specs/47-scheduler-store.smoke.spec.ts @@ -0,0 +1,213 @@ +import { createServer } from 'node:http' +import { test, expect } from '../fixtures/electronApp' +import { waitForAppReady } from '../helpers/wait' + +test.use({ privacyModeEnabled: true }) + +test('scheduler manual and timed runs settle once and survive restart @smoke', async ({ + launchApp +}, testInfo) => { + let streamedRequests = 0 + const server = createServer(async (request, response) => { + if (request.url === '/v1/models') { + response + .writeHead(200, { 'Content-Type': 'application/json' }) + .end(JSON.stringify({ data: [{ id: 'scheduler-fixture' }] })) + return + } + if (request.url !== '/v1/chat/completions') { + response.writeHead(404).end() + return + } + let source = '' + for await (const chunk of request) source += chunk + const body = JSON.parse(source) + const message = 'SCHEDULER_FIXTURE_DONE' + if (!body.stream) { + response.writeHead(200, { 'Content-Type': 'application/json' }).end( + JSON.stringify({ + id: 'scheduler-fixture', + object: 'chat.completion', + created: 1, + model: 'scheduler-fixture', + choices: [ + { index: 0, message: { role: 'assistant', content: message }, finish_reason: 'stop' } + ] + }) + ) + return + } + streamedRequests++ + response.writeHead(200, { 'Content-Type': 'text/event-stream' }).end( + `data: ${JSON.stringify({ + id: 'scheduler-fixture', + object: 'chat.completion.chunk', + created: 1, + model: 'scheduler-fixture', + choices: [ + { index: 0, delta: { role: 'assistant', content: message }, finish_reason: 'stop' } + ] + })}\n\ndata: [DONE]\n\n` + ) + }) + await new Promise((resolve) => server.listen(0, '127.0.0.1', resolve)) + const { port } = server.address() as { port: number } + try { + const initial = await launchApp() + expect(initial.ownsUserDataDir).toBe(true) + await waitForAppReady(initial.page) + const { schedulerStatus: unused } = await initial.page.evaluate(() => + window.deepchat.invoke('cronJobs.getSchedulerStatus', {}) + ) + expect(unused.pid).toBeNull() + await initial.page.evaluate(async (baseUrl) => { + const providerId = `custom-${crypto.randomUUID()}` + await window.deepchat.invoke('providers.add', { + provider: { + id: providerId, + name: 'Scheduler fixture', + apiType: 'openai-completions', + baseUrl, + apiKey: 'fixture-key', + enable: true, + custom: true + } + }) + await window.deepchat.invoke('models.addCustom', { + providerId, + model: { + id: 'scheduler-fixture', + name: 'Scheduler fixture', + enabled: true, + vision: false, + functionCall: false, + reasoning: false, + contextLength: 32000, + maxTokens: 8000 + } + }) + await window.deepchat.invoke('models.setStatus', { + providerId, + modelId: 'scheduler-fixture', + enabled: true + }) + await window.deepchat.invoke('config.updateDeepChatAgent', { + agentId: 'deepchat', + updates: { + config: { + defaultModelPreset: { providerId, modelId: 'scheduler-fixture' }, + memoryEnabled: false, + subagentEnabled: false, + permissionMode: 'full_access', + disabledAgentTools: [], + enabledSkillNames: [] + } + } + }) + }, `http://127.0.0.1:${port}/v1`) + expect(await initial.close()).toBe('graceful') + + const active = await launchApp() + await waitForAppReady(active.page) + const { job } = await active.page.evaluate(() => + window.deepchat.invoke('cronJobs.upsert', { + name: 'Scheduler ownership fixture', + enabled: true, + cronExpr: '0 0 1 1 *', + timezone: 'UTC', + agentId: 'deepchat', + taskPrompt: 'Reply with SCHEDULER_FIXTURE_DONE without using tools.', + runtime: { maxDurationMs: 30_000, maxTurns: 2, concurrencyPolicy: 'queue' } + }) + ) + expect( + await active.electronApp.evaluate(({ powerMonitor }) => ({ + listeners: powerMonitor.listenerCount('resume'), + delivered: [powerMonitor.emit('resume'), powerMonitor.emit('resume')] + })) + ).toEqual({ listeners: 1, delivered: [true, true] }) + const { run: manual } = await active.page.evaluate( + (id) => window.deepchat.invoke('cronJobs.runNow', { id }), + job.id + ) + await expect + .poll( + async () => + ( + await active.page.evaluate( + (runId) => window.deepchat.invoke('cronJobs.getRun', { runId }), + manual.id + ) + ).run.status + ) + .toBe('completed') + await active.page.evaluate( + (job) => window.deepchat.invoke('cronJobs.upsert', { ...job, cronExpr: '*/2 * * * * *' }), + job + ) + await expect + .poll(async () => { + const { runs } = await active.page.evaluate( + (jobId) => window.deepchat.invoke('cronJobs.listRuns', { jobId, limit: 20 }), + job.id + ) + return runs.some((run) => run.reason === 'scheduled' && run.status === 'completed') + }) + .toBe(true) + await active.page.evaluate( + (job) => window.deepchat.invoke('cronJobs.upsert', { ...job, cronExpr: '0 0 1 1 *' }), + job + ) + await expect + .poll(async () => { + const { runs } = await active.page.evaluate( + (jobId) => window.deepchat.invoke('cronJobs.listRuns', { jobId, limit: 20 }), + job.id + ) + return runs.every((run) => run.status === 'completed') + }) + .toBe(true) + const { runs } = await active.page.evaluate( + (jobId) => window.deepchat.invoke('cronJobs.listRuns', { jobId, limit: 20 }), + job.id + ) + expect(runs.find((run) => run.id === manual.id)?.reason).toBe('manual') + const scheduled = runs.filter((run) => run.reason === 'scheduled') + expect(new Set(scheduled.map((run) => run.scheduledAt)).size).toBe(scheduled.length) + expect(runs.every((run) => run.sessionId && run.outputMessageId)).toBe(true) + expect(streamedRequests).toBe(runs.length) + if (process.env.DEEPCHAT_E2E_MEASURE_RESOURCES === '1') + await new Promise((resolve) => setTimeout(resolve, 30_000)) + expect(await active.close()).toBe('graceful') + + const reopened = await launchApp() + await waitForAppReady(reopened.page) + const persisted = await reopened.page.evaluate( + (jobId) => window.deepchat.invoke('cronJobs.listRuns', { jobId, limit: 20 }), + job.id + ) + expect(persisted.runs).toEqual(runs) + expect(streamedRequests).toBe(runs.length) + await reopened.page.evaluate( + (id) => window.deepchat.invoke('cronJobs.toggle', { id, enabled: false }), + job.id + ) + const { schedulerStatus: stopped } = await reopened.page.evaluate(() => + window.deepchat.invoke('cronJobs.restartScheduler', {}) + ) + expect(stopped.pid).toBeNull() + expect(stopped.enabledJobCount).toBe(0) + await reopened.page.evaluate((id) => window.deepchat.invoke('cronJobs.delete', { id }), job.id) + expect( + (await reopened.page.evaluate(() => window.deepchat.invoke('cronJobs.list', {}))).jobs + ).toEqual([]) + expect(await reopened.close()).toBe('graceful') + await testInfo.attach('scheduler-outcomes.json', { + body: Buffer.from(JSON.stringify({ runs, streamedRequests })), + contentType: 'application/json' + }) + } finally { + server.closeAllConnections() + await new Promise((resolve) => server.close(() => resolve())) + } +}) diff --git a/test/e2e/specs/48-credential-crypto.smoke.spec.ts b/test/e2e/specs/48-credential-crypto.smoke.spec.ts new file mode 100644 index 0000000000..dcc179a9a0 --- /dev/null +++ b/test/e2e/specs/48-credential-crypto.smoke.spec.ts @@ -0,0 +1,88 @@ +import { readFileSync } from 'node:fs' +import { join } from 'node:path' +import { test, expect } from '../fixtures/electronApp' +import { waitForAppReady } from '../helpers/wait' + +test.use({ privacyModeEnabled: true }) + +test('native async crypto reads legacy ciphertext @smoke', async ({ app }, testInfo) => { + expect(app.ownsUserDataDir).toBe(true) + await waitForAppReady(app.page) + const compatibility = await app.electronApp.evaluate(async ({ safeStorage }) => { + const plaintext = 'credential-compatibility-fixture' + const legacy = safeStorage.encryptString(plaintext) + const available = await safeStorage.isAsyncEncryptionAvailable() + const legacyRead = await safeStorage.decryptStringAsync(legacy) + const current = await safeStorage.encryptStringAsync(plaintext) + const currentRead = await safeStorage.decryptStringAsync(current) + return { + available, + legacyMatches: legacyRead.result === plaintext, + currentMatches: currentRead.result === plaintext, + shouldReEncrypt: legacyRead.shouldReEncrypt, + electron: process.versions.electron + } + }) + expect(compatibility.available).toBe(true) + expect(compatibility.legacyMatches).toBe(true) + expect(compatibility.currentMatches).toBe(true) + expect(typeof compatibility.shouldReEncrypt).toBe('boolean') + await testInfo.attach('native-crypto-compatibility.json', { + body: Buffer.from(JSON.stringify(compatibility)), + contentType: 'application/json' + }) +}) + +test('cloud credentials stay wrapped across acknowledged updates and relaunch @smoke', async ({ + launchApp +}) => { + const secret = 'disposable-cloud-credential-fixture' + const initial = await launchApp() + expect(initial.ownsUserDataDir).toBe(true) + await waitForAppReady(initial.page) + const { config: empty } = await initial.page.evaluate(() => + window.deepchat.invoke('sync.getCloudConfig', {}) + ) + expect(empty.safeStorageAvailable).toBe(true) + expect(empty.hasSecret).toBe(false) + expect(await initial.close()).toBe('graceful') + + const active = await launchApp() + await waitForAppReady(active.page) + const { config: configured } = await active.page.evaluate( + (secret) => + window.deepchat.invoke('sync.setCloudConfig', { + config: { + enabled: false, + endpoint: 'http://127.0.0.1:1', + bucket: 'credential-fixture', + accessKeyId: 'fixture-access-key', + secretAccessKey: secret + } + }), + secret + ) + expect(configured).toMatchObject({ hasSecret: true, safeStorageAvailable: true }) + expect(JSON.stringify(configured)).not.toContain(secret) + const settingsPath = join(active.userDataDir, 'app-settings.json') + const raw = readFileSync(settingsPath, 'utf8') + expect(raw).not.toContain(secret) + const wrapped = JSON.parse(raw).cloudSyncSecret + expect(typeof wrapped).toBe('string') + expect(wrapped.length).toBeGreaterThan(0) + const { config: updated } = await active.page.evaluate(() => + window.deepchat.invoke('sync.setCloudConfig', { config: { region: 'us-west-2' } }) + ) + expect(updated).toMatchObject({ hasSecret: true, region: 'us-west-2' }) + expect(JSON.parse(readFileSync(settingsPath, 'utf8')).cloudSyncSecret).toBe(wrapped) + expect(await active.close()).toBe('graceful') + + const reopened = await launchApp() + await waitForAppReady(reopened.page) + const { config: persisted } = await reopened.page.evaluate(() => + window.deepchat.invoke('sync.getCloudConfig', {}) + ) + expect(persisted).toEqual(updated) + expect(JSON.parse(readFileSync(settingsPath, 'utf8')).cloudSyncSecret).toBe(wrapped) + expect(await reopened.close()).toBe('graceful') +}) diff --git a/test/e2e/specs/49-settings-snapshot.smoke.spec.ts b/test/e2e/specs/49-settings-snapshot.smoke.spec.ts new file mode 100644 index 0000000000..43cacf92c8 --- /dev/null +++ b/test/e2e/specs/49-settings-snapshot.smoke.spec.ts @@ -0,0 +1,109 @@ +import type { Page } from '@playwright/test' +import { test, expect } from '../fixtures/electronApp' +import { openSettings, openSettingsTab } from '../helpers/settings' +import { waitForAppReady } from '../helpers/wait' + +async function changeSettings(page: Page, enabled: boolean) { + return await page.evaluate(async (enabled) => { + const events: Array<{ version: number; values: { autoScrollEnabled?: boolean } }> = [] + let received!: () => void + const changed = new Promise((resolve) => { + received = resolve + }) + const unsubscribe = window.deepchat.on('settings.changed', (payload) => { + if (!payload.changedKeys.includes('autoScrollEnabled')) return + events.push(payload) + if (events.length === 2) received() + }) + try { + const first = await window.deepchat.invoke('settings.getSnapshot', { + keys: ['autoScrollEnabled'] + }) + const second = await window.deepchat.invoke('settings.getSnapshot', { + keys: ['autoScrollEnabled'] + }) + const initial = await window.deepchat.invoke('settings.update', { + changes: [{ key: 'autoScrollEnabled', value: !enabled }] + }) + const final = await window.deepchat.invoke('settings.update', { + changes: [{ key: 'autoScrollEnabled', value: enabled }] + }) + await changed + return { first, second, initial, final, events } + } finally { + unsubscribe() + } + }, enabled) +} + +test.use({ privacyModeEnabled: true }) + +test('settings revisions survive clock rollback and persist across relaunch @smoke', async ({ + launchApp +}) => { + const active = await launchApp() + expect(active.ownsUserDataDir).toBe(true) + await waitForAppReady(active.page) + const settingsPage = await openSettings(active) + await openSettingsTab(settingsPage, 'settings-tab-general') + const autoScroll = settingsPage.locator('#auto-scroll-switch') + await expect(autoScroll).toHaveAttribute('aria-checked', 'true') + + // Control only the disposable Main process clock; restore it before shutdown. + const fixedNow = await active.electronApp.evaluate(() => { + const original = Date.now + const state = globalThis as typeof globalThis & { restoreSettingsSmokeClock?: () => void } + state.restoreSettingsSmokeClock = () => { + Date.now = original + delete state.restoreSettingsSmokeClock + } + const now = original() + Date.now = () => now + return now + }) + try { + const frozen = await changeSettings(active.page, true) + const revisions = [ + frozen.first.version, + frozen.second.version, + frozen.events[0].version, + frozen.initial.version, + frozen.events[1].version, + frozen.final.version + ] + for (let i = 1; i < revisions.length; i++) + expect(revisions[i]).toBeGreaterThan(revisions[i - 1]) + expect(frozen.initial.values.autoScrollEnabled).toBe(false) + expect(frozen.final.values.autoScrollEnabled).toBe(true) + + await active.electronApp.evaluate((_electron, now) => { + Date.now = () => now - 30_000 + }, fixedNow) + const rolledBack = await changeSettings(active.page, false) + expect(rolledBack.first.version).toBeGreaterThan(frozen.final.version) + expect(rolledBack.final.version).toBeGreaterThan(rolledBack.events[1].version) + expect(rolledBack.final.values.autoScrollEnabled).toBe(false) + await expect(autoScroll).toHaveAttribute('aria-checked', 'false') + } finally { + await active.electronApp.evaluate(() => { + ;( + globalThis as typeof globalThis & { restoreSettingsSmokeClock?: () => void } + ).restoreSettingsSmokeClock?.() + }) + } + expect(await active.close()).toBe('graceful') + + const reopened = await launchApp() + await waitForAppReady(reopened.page) + const { values } = await reopened.page.evaluate(() => + window.deepchat.invoke('settings.getSnapshot', { keys: ['autoScrollEnabled'] }) + ) + expect(values.autoScrollEnabled).toBe(false) + const reopenedSettings = await openSettings(reopened) + await openSettingsTab(reopenedSettings, 'settings-tab-general') + await expect(reopenedSettings.locator('#auto-scroll-switch')).toHaveAttribute( + 'aria-checked', + 'false' + ) + expect(await reopened.close()).toBe('graceful') +}) diff --git a/test/e2e/specs/50-sync-token.smoke.spec.ts b/test/e2e/specs/50-sync-token.smoke.spec.ts new file mode 100644 index 0000000000..41cb9b978b --- /dev/null +++ b/test/e2e/specs/50-sync-token.smoke.spec.ts @@ -0,0 +1,95 @@ +import { readFileSync, writeFileSync } from 'node:fs' +import { join } from 'node:path' +import { test, expect } from '../fixtures/electronApp' +import { startHttpsFixture } from '../helpers/httpsFixture' +import { waitForAppReady } from '../helpers/wait' +import { + SYNC_HOST_PROTOCOL_NAME, + SYNC_HOST_PROTOCOL_VERSION +} from '../../../src/shared/contracts/syncHost' + +const host = 'sync-token-fixture.invalid' +const hostUrl = `https://${host}` +const hostId = 'sync-token-fixture' +const token = 'disposable-sync-token-credential-fixture' + +test.use({ privacyModeEnabled: true }) + +test('peer credentials stay wrapped and read legacy ciphertext after relaunch @smoke', async ({ + launchApp +}) => { + const authorized: string[] = [] + const https = await startHttpsFixture([host], (request, response) => { + const reply = (status: number, body: object) => + response.writeHead(status, { 'content-type': 'application/json' }).end(JSON.stringify(body)) + if (request.url === '/sync/v1/handshake' && request.method === 'GET') { + reply(200, { + protocol: SYNC_HOST_PROTOCOL_NAME, + protocolVersion: SYNC_HOST_PROTOCOL_VERSION, + hostId, + appVersion: 'fixture', + capabilities: ['snapshot'], + encryption: { payload: 'none', transport: 'tls' } + }) + } else if (request.url === '/sync/v1/pair' && request.method === 'POST') { + reply(200, { deviceId: 'fixture-device', deviceName: 'Fixture device', token }) + } else if (request.headers.authorization !== `Bearer ${token}`) { + reply(401, {}) + } else { + authorized.push(request.url ?? '') + if (request.url === '/sync/v1/status') reply(200, { serverTime: Date.now(), snapshot: null }) + else reply(404, {}) + } + }) + let active: Awaited> | undefined + try { + active = await launchApp() + expect(active.ownsUserDataDir).toBe(true) + await waitForAppReady(active.page) + await active.page.evaluate(async (url) => { + await window.deepchat.invoke('config.setCustomProxyUrl', { url }) + await window.deepchat.invoke('config.setProxyMode', { mode: 'custom' }) + }, https.proxyUrl) + const file = join(active.userDataDir, 'sync-peer', 'pairing.json') + const status = await active.page.evaluate( + (input) => window.deepchat.invoke('syncPeer.pair', input), + { hostUrl, hostId, code: 'fixture-code', deviceName: 'Fixture device' } + ) + expect(status.paired).toBe(true) + expect(JSON.stringify(status)).not.toContain(token) + const current = readFileSync(file, 'utf8') + expect(current).not.toContain(token) + expect(typeof JSON.parse(current).wrappedToken).toBe('string') + const legacy = await active.electronApp.evaluate( + ({ safeStorage }, value) => safeStorage.encryptString(value).toString('base64'), + token + ) + expect(await active.close()).toBe('graceful') + const pairing = JSON.parse(readFileSync(file, 'utf8')) + writeFileSync(file, JSON.stringify({ ...pairing, wrappedToken: legacy })) + active = await launchApp() + await waitForAppReady(active.page) + await active.page.evaluate(() => window.deepchat.invoke('syncPeer.pull', { mode: 'increment' })) + await expect + .poll(() => active!.page.evaluate(() => window.deepchat.invoke('syncPeer.getStatus', {}))) + .toMatchObject({ + paired: true, + phase: 'failed', + error: 'sync.tunnel.error.noSnapshot' + }) + expect(authorized).toContain('/sync/v1/status') + expect(authorized).toContain('/sync/v1/prepare') + expect(JSON.parse(readFileSync(file, 'utf8')).wrappedToken).toBe(legacy) + const forgotten = await active.page.evaluate(() => + window.deepchat.invoke('syncPeer.forget', {}) + ) + expect(forgotten.paired).toBe(false) + expect(await active.close()).toBe('graceful') + } finally { + try { + await active?.close() + } finally { + await https.close() + } + } +}) diff --git a/test/e2e/specs/51-database-security.smoke.spec.ts b/test/e2e/specs/51-database-security.smoke.spec.ts new file mode 100644 index 0000000000..483606403c --- /dev/null +++ b/test/e2e/specs/51-database-security.smoke.spec.ts @@ -0,0 +1,102 @@ +import { readFileSync, writeFileSync } from 'node:fs' +import { join } from 'node:path' +import { test, expect } from '../fixtures/electronApp' +import { waitForAppReady } from '../helpers/wait' + +test.use({ privacyModeEnabled: true }) + +test('database encryption preserves settings through legacy unlock rekey and disable @smoke', async ({ + launchApp +}) => { + const firstPassword = 'disposable-database-password-one' + const nextPassword = 'disposable-database-password-two' + const active = await launchApp() + expect(active.ownsUserDataDir).toBe(true) + await waitForAppReady(active.page) + await active.page.evaluate(() => + window.deepchat.invoke('settings.update', { + changes: [{ key: 'copyWithCotEnabled', value: false }] + }) + ) + const { status: enabled } = await active.page.evaluate( + (password) => window.deepchat.invoke('databaseSecurity.enable', { password }), + firstPassword + ) + expect(enabled).toMatchObject({ + enabled: true, + passwordStorage: 'safeStorage', + manualUnlockRequired: false + }) + const file = join(active.userDataDir, 'database-security.json') + const databaseFile = join(active.userDataDir, 'app_db', 'agent.db') + const raw = readFileSync(file, 'utf8') + expect(raw).not.toContain(firstPassword) + expect(readFileSync(databaseFile).subarray(0, 16).toString()).not.toBe('SQLite format 3\u0000') + const legacy = await active.electronApp.evaluate( + ({ safeStorage }, value) => safeStorage.encryptString(value).toString('base64'), + firstPassword + ) + const state = JSON.parse(raw) + writeFileSync( + file, + JSON.stringify({ ...state, metadata: { ...state.metadata, wrappedPassword: legacy } }) + ) + expect(await active.close()).toBe('graceful') + + const reopened = await launchApp() + await waitForAppReady(reopened.page) + expect( + ( + await reopened.page.evaluate(() => + window.deepchat.invoke('settings.getSnapshot', { keys: ['copyWithCotEnabled'] }) + ) + ).values.copyWithCotEnabled + ).toBe(false) + expect(JSON.parse(readFileSync(file, 'utf8')).metadata.wrappedPassword).toBe(legacy) + await expect( + reopened.page.evaluate( + (newPassword) => + window.deepchat.invoke('databaseSecurity.changePassword', { + currentPassword: 'wrong-fixture-password', + newPassword + }), + nextPassword + ) + ).rejects.toThrow() + const { status: changed } = await reopened.page.evaluate( + ({ currentPassword, newPassword }) => + window.deepchat.invoke('databaseSecurity.changePassword', { currentPassword, newPassword }), + { currentPassword: firstPassword, newPassword: nextPassword } + ) + expect(changed.enabled).toBe(true) + expect(readFileSync(file, 'utf8')).not.toContain(nextPassword) + expect(await reopened.close()).toBe('graceful') + + const changedApp = await launchApp() + await waitForAppReady(changedApp.page) + expect( + ( + await changedApp.page.evaluate(() => + window.deepchat.invoke('settings.getSnapshot', { keys: ['copyWithCotEnabled'] }) + ) + ).values.copyWithCotEnabled + ).toBe(false) + const { status: disabled } = await changedApp.page.evaluate( + (currentPassword) => window.deepchat.invoke('databaseSecurity.disable', { currentPassword }), + nextPassword + ) + expect(disabled).toMatchObject({ enabled: false, passwordStorage: 'none' }) + expect(readFileSync(databaseFile).subarray(0, 16).toString()).toBe('SQLite format 3\u0000') + expect(await changedApp.close()).toBe('graceful') + + const plain = await launchApp() + await waitForAppReady(plain.page) + expect( + ( + await plain.page.evaluate(() => + window.deepchat.invoke('settings.getSnapshot', { keys: ['copyWithCotEnabled'] }) + ) + ).values.copyWithCotEnabled + ).toBe(false) + expect(await plain.close()).toBe('graceful') +}) diff --git a/test/e2e/specs/52-mcp-oauth-credentials.smoke.spec.ts b/test/e2e/specs/52-mcp-oauth-credentials.smoke.spec.ts new file mode 100644 index 0000000000..6ed3794be5 --- /dev/null +++ b/test/e2e/specs/52-mcp-oauth-credentials.smoke.spec.ts @@ -0,0 +1,118 @@ +import { existsSync, readFileSync, writeFileSync } from 'node:fs' +import { join } from 'node:path' +import { test, expect } from '../fixtures/electronApp' +import { openSettings } from '../helpers/settings' +import { waitForAppReady } from '../helpers/wait' + +test.use({ privacyModeEnabled: true }) + +test('MCP machine credentials preserve native ciphertext and settings-window ownership @smoke', async ({ + launchApp +}) => { + const active = await launchApp() + expect(active.ownsUserDataDir).toBe(true) + await waitForAppReady(active.page) + const settings = await openSettings(active) + const binding = await settings.evaluate(async () => { + await window.deepchat.invoke('mcp.addServer', { + serverName: 'native-credential-fixture', + config: { + type: 'http', + baseUrl: 'http://127.0.0.1:1/mcp', + enabled: false, + command: '', + args: [], + env: {}, + descriptions: 'Disposable credential fixture', + icons: '', + authorization: { + mode: 'client_credentials', + clientId: 'fixture-client', + authorizationServerIssuer: 'https://auth.example', + protectedResourceUrl: 'https://resource.example/mcp' + } + } + }) + const { servers } = await window.deepchat.invoke('mcp.getServers', {}) + const config = servers['native-credential-fixture'] + if (!config?.serverId || !config.configGeneration || !config.bindingHash) + throw new Error('Missing fixture server identity') + return { + serverId: config.serverId, + configGeneration: config.configGeneration, + bindingHash: config.bindingHash, + endpoint: config.baseUrl!, + clientId: config.authorization!.clientId, + authorizationServerIssuer: config.authorization!.authorizationServerIssuer, + protectedResourceUrl: config.authorization!.protectedResourceUrl + } + }) + const secret = 'disposable-mcp-client-secret' + await expect( + active.page.evaluate( + ({ binding, secret }) => + window.deepchat.invoke('mcp.credentials.set', { + binding, + credential: { kind: 'client_secret', secret } + }), + { binding, secret } + ) + ).rejects.toThrow('settings window') + const { status } = await settings.evaluate( + ({ binding, secret }) => + window.deepchat.invoke('mcp.credentials.set', { + binding, + credential: { kind: 'client_secret', secret } + }), + { binding, secret } + ) + expect(status).toMatchObject({ configured: true, persistent: true, kind: 'client_secret' }) + const file = join(active.userDataDir, 'mcp-oauth', 'credentials.json') + expect(existsSync(file)).toBe(true) + const raw = readFileSync(file, 'utf8') + expect(raw).not.toContain(secret) + const envelope = JSON.parse(raw) + expect(envelope).toMatchObject({ version: 2, storage: 'safeStorage' }) + const legacy = await active.electronApp.evaluate( + ({ safeStorage }, wrapped) => + safeStorage + .encryptString(safeStorage.decryptString(Buffer.from(wrapped, 'base64'))) + .toString('base64'), + envelope.wrapped + ) + expect(await active.close()).toBe('graceful') + const legacyFile = JSON.stringify({ ...envelope, wrapped: legacy }) + writeFileSync(file, legacyFile) + + const reopened = await launchApp() + await waitForAppReady(reopened.page) + const reopenedSettings = await openSettings(reopened) + const saved = await reopenedSettings.evaluate( + (serverId) => window.deepchat.invoke('mcp.credentials.getStatus', { serverId }), + binding.serverId + ) + expect(saved.credentials.find((credential) => credential.kind === 'client_secret')).toMatchObject( + { configured: true, persistent: true } + ) + expect(JSON.stringify(saved)).not.toContain(secret) + expect(readFileSync(file, 'utf8')).toBe(legacyFile) + const removed = await reopenedSettings.evaluate( + (binding) => + window.deepchat.invoke('mcp.credentials.remove', { binding, kind: 'client_secret' }), + binding + ) + expect(removed.status).toMatchObject({ configured: false, persistent: true }) + expect(await reopened.close()).toBe('graceful') + + const final = await launchApp() + await waitForAppReady(final.page) + const finalSettings = await openSettings(final) + const after = await finalSettings.evaluate( + (serverId) => window.deepchat.invoke('mcp.credentials.getStatus', { serverId }), + binding.serverId + ) + expect( + after.credentials.find((credential) => credential.kind === 'client_secret')?.configured + ).toBe(false) + expect(await final.close()).toBe('graceful') +}) diff --git a/test/e2e/specs/53-provider-oauth-credentials.smoke.spec.ts b/test/e2e/specs/53-provider-oauth-credentials.smoke.spec.ts new file mode 100644 index 0000000000..ddd2ef5c44 --- /dev/null +++ b/test/e2e/specs/53-provider-oauth-credentials.smoke.spec.ts @@ -0,0 +1,177 @@ +import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs' +import { dirname, join } from 'node:path' +import { test, expect } from '../fixtures/electronApp' +import { openSettings } from '../helpers/settings' +import { waitForAppReady } from '../helpers/wait' +import { startHttpsFixture } from '../helpers/httpsFixture' + +test.use({ privacyModeEnabled: true }) + +test('Provider OAuth preserves legacy private files and durable logout @smoke', async ({ + launchApp +}) => { + const requests: Array<{ + host: string | undefined + path: string | undefined + method: string | undefined + token: string | null + clientId: string | null + }> = [] + const https = await startHttpsFixture(['auth.openai.com', 'auth.x.ai'], (request, response) => { + let body = '' + request.setEncoding('utf8') + request.on('data', (chunk) => (body += chunk)) + request.on('end', () => { + const form = new URLSearchParams(body) + requests.push({ + host: request.headers.host, + path: request.url, + method: request.method, + token: form.get('token'), + clientId: form.get('client_id') + }) + if ( + request.headers.host === 'auth.x.ai' && + request.url === '/.well-known/openid-configuration' && + request.method === 'GET' + ) { + response.writeHead(200, { 'content-type': 'application/json' }).end( + JSON.stringify({ + device_authorization_endpoint: 'https://auth.x.ai/device', + token_endpoint: 'https://auth.x.ai/token', + revocation_endpoint: 'https://auth.x.ai/oauth/revoke' + }) + ) + } else if (request.url === '/oauth/revoke' && request.method === 'POST') { + response.writeHead(200).end() + } else { + response.writeHead(404).end() + } + }) + }) + let cleanupApp: Awaited> | undefined + try { + const first = await launchApp() + cleanupApp = first + expect(first.ownsUserDataDir).toBe(true) + await waitForAppReady(first.page) + await first.page.evaluate(async (url) => { + await window.deepchat.invoke('config.setCustomProxyUrl', { url }) + await window.deepchat.invoke('config.setProxyMode', { mode: 'custom' }) + }, https.proxyUrl) + const fixtures = ['openai-codex-auth', 'xai-grok-auth'].map((directory) => ({ + file: join(first.userDataDir, directory, 'credentials.json'), + tokens: { + accessToken: `disposable-${directory}-access`, + refreshToken: `disposable-${directory}-refresh`, + tokenType: 'Bearer', + expiresAt: Date.now() + 24 * 60 * 60 * 1000, + accountId: 'fixture-account-12345678', + accountLabel: 'Fixture account', + updatedAt: 42 + } + })) + const wrapped = await first.electronApp.evaluate( + ({ safeStorage }, tokens) => + tokens.map((value) => safeStorage.encryptString(JSON.stringify(value)).toString('base64')), + fixtures.map((fixture) => fixture.tokens) + ) + expect(await first.close()).toBe('graceful') + for (const fixture of fixtures) { + mkdirSync(dirname(fixture.file), { recursive: true, mode: 0o700 }) + writeFileSync( + fixture.file, + JSON.stringify({ version: 1, storage: 'file', tokens: fixture.tokens, updatedAt: 42 }), + { mode: 0o600 } + ) + } + + for (const storage of ['file', 'safeStorage'] as const) { + if (storage === 'safeStorage') + fixtures.forEach((fixture, index) => { + writeFileSync( + fixture.file, + JSON.stringify({ version: 1, storage, wrapped: wrapped[index], updatedAt: 42 }), + { mode: 0o600 } + ) + }) + const before = fixtures.map((fixture) => readFileSync(fixture.file, 'utf8')) + const active = await launchApp() + cleanupApp = active + await waitForAppReady(active.page) + const settings = await openSettings(active) + const statuses = await settings.evaluate(async () => [ + (await window.deepchat.invoke('oauth.openaiCodex.getStatus', {})).status, + (await window.deepchat.invoke('oauth.xaiGrok.getStatus', {})).status + ]) + for (const status of statuses) { + expect(status).toMatchObject({ + state: 'authenticated', + authenticated: true, + accountLabel: 'Fixture account' + }) + expect(JSON.stringify(status)).not.toContain('disposable-') + } + await settings.evaluate(async () => { + await window.deepchat.invoke('oauth.openaiCodex.cancelLogin', {}) + await window.deepchat.invoke('oauth.xaiGrok.cancelLogin', {}) + }) + expect(fixtures.map((fixture) => readFileSync(fixture.file, 'utf8'))).toEqual(before) + expect(requests).toEqual([]) + if (storage === 'safeStorage') { + const loggedOut = await settings.evaluate(async () => [ + (await window.deepchat.invoke('oauth.openaiCodex.logout', {})).status, + (await window.deepchat.invoke('oauth.xaiGrok.logout', {})).status + ]) + expect( + loggedOut.every((status) => status.state === 'signed-out' && !status.authenticated) + ).toBe(true) + expect(requests.filter((request) => request.path === '/oauth/revoke')).toEqual([ + { + host: 'auth.openai.com', + path: '/oauth/revoke', + method: 'POST', + token: fixtures[0].tokens.refreshToken, + clientId: expect.stringMatching(/\S/) + }, + { + host: 'auth.x.ai', + path: '/oauth/revoke', + method: 'POST', + token: fixtures[1].tokens.refreshToken, + clientId: expect.stringMatching(/\S/) + } + ]) + expect(requests.filter((request) => request.method === 'GET')).toEqual([ + { + host: 'auth.x.ai', + path: '/.well-known/openid-configuration', + method: 'GET', + token: null, + clientId: null + } + ]) + expect(fixtures.every((fixture) => !existsSync(fixture.file))).toBe(true) + } + expect(await active.close()).toBe('graceful') + } + const final = await launchApp() + cleanupApp = final + await waitForAppReady(final.page) + const settings = await openSettings(final) + const statuses = await settings.evaluate(async () => [ + (await window.deepchat.invoke('oauth.openaiCodex.getStatus', {})).status, + (await window.deepchat.invoke('oauth.xaiGrok.getStatus', {})).status + ]) + expect(statuses.every((status) => status.state === 'signed-out' && !status.authenticated)).toBe( + true + ) + expect(await final.close()).toBe('graceful') + } finally { + try { + await cleanupApp?.close() + } finally { + await https.close() + } + } +}) diff --git a/test/e2e/specs/54-database-maintenance.smoke.spec.ts b/test/e2e/specs/54-database-maintenance.smoke.spec.ts new file mode 100644 index 0000000000..af07db1fab --- /dev/null +++ b/test/e2e/specs/54-database-maintenance.smoke.spec.ts @@ -0,0 +1,384 @@ +import { join, resolve } from 'node:path' +import { execFile } from 'node:child_process' +import { promisify } from 'node:util' +import type { ElectronApplication } from '@playwright/test' +import { test, expect } from '../fixtures/electronApp' +import { waitForAppReady } from '../helpers/wait' + +test.use({ privacyModeEnabled: true }) + +async function runCli(userDataDir: string, args: string[]) { + const env: NodeJS.ProcessEnv = { ...process.env, DEEPCHAT_E2E_USER_DATA_DIR: userDataDir } + delete env.DEEPCHAT_CLI_AGENT_TOKEN + return await promisify(execFile)( + process.execPath, + [resolve('out/cli/deepchat.mjs'), ...args, '--json'], + { env, timeout: 30_000 } + ) +} + +async function holdEncryption( + app: ElectronApplication, + heldValue: string, + maintenancePassword: string +) { + await app.evaluate( + ({ safeStorage }, { heldValue, maintenancePassword }) => { + const encrypt = safeStorage.encryptStringAsync + let release!: () => void + const gate = new Promise((resolve) => { + release = resolve + }) + const fixture = { + entered: false, + events: [] as string[], + release, + restore: () => { + safeStorage.encryptStringAsync = encrypt + } + } + Object.assign(globalThis, { __maintenanceFixture: fixture }) + safeStorage.encryptStringAsync = async (value) => { + if (value === heldValue) { + fixture.entered = true + await gate + fixture.events.push('pending-request-released') + } else if (value === maintenancePassword) fixture.events.push('maintenance-dispatched') + return encrypt.call(safeStorage, value) + } + }, + { heldValue, maintenancePassword } + ) +} + +async function releaseEncryption(app: ElectronApplication) { + await app.evaluate(() => { + const fixture = ( + globalThis as unknown as { + __maintenanceFixture: { release(): void; restore(): void } + } + ).__maintenanceFixture + fixture.restore() + fixture.release() + }) +} + +test('database maintenance restores sessions through incremental and overwrite imports @smoke', async ({ + launchApp +}) => { + const active = await launchApp() + expect(active.ownsUserDataDir).toBe(true) + await waitForAppReady(active.page) + const listProviders = async () => { + const { stdout } = await runCli(active.userDataDir, ['provider', 'list']) + const response = JSON.parse(stdout) + expect(response).toMatchObject({ ok: true, result: { providers: expect.any(Array) } }) + return response.result.providers.map((provider: { id: string }) => provider.id) + } + const providers = await listProviders() + await active.page.evaluate( + (folderPath) => + window.deepchat.invoke('config.updateSyncSettings', { enabled: true, folderPath }), + join(active.userDataDir, 'maintenance-backups') + ) + const created = await active.page.evaluate(() => + window.deepchat.invoke('sessions.create', { + agentId: 'deepchat', + providerId: 'openai', + modelId: 'gpt-5.6-luna', + message: '', + projectDir: null + }) + ) + const sessionId = created.session.id + const before = await active.page.evaluate( + (sessionId) => window.deepchat.invoke('sessions.restore', { sessionId, limit: 30 }), + sessionId + ) + expect(before.session?.id).toBe(sessionId) + const { backup } = await active.page.evaluate(() => + window.deepchat.invoke('sync.startBackup', {}) + ) + expect(backup).not.toBeNull() + await active.page.evaluate( + (sessionId) => window.deepchat.invoke('sessions.delete', { sessionId }), + sessionId + ) + const missing = await active.page.evaluate( + (sessionId) => window.deepchat.invoke('sessions.restore', { sessionId, limit: 30 }), + sessionId + ) + expect(missing.session).toBeNull() + const invalid = await active.page.evaluate(() => + window.deepchat.invoke('sync.import', { backupFile: '../not-a-backup.zip', mode: 'increment' }) + ) + expect(invalid.result.success).toBe(false) + const restored = await active.page.evaluate( + (backupFile) => window.deepchat.invoke('sync.import', { backupFile, mode: 'increment' }), + backup!.fileName + ) + expect(restored.result.success).toBe(true) + const increment = await active.page.evaluate( + (sessionId) => window.deepchat.invoke('sessions.restore', { sessionId, limit: 30 }), + sessionId + ) + expect(increment.session?.id).toBe(sessionId) + expect(increment.session?.providerId).toBe(before.session?.providerId) + const extra = await active.page.evaluate(() => + window.deepchat.invoke('sessions.create', { + agentId: 'deepchat', + providerId: 'openai', + modelId: 'gpt-5.6-luna', + message: '', + projectDir: null + }) + ) + const overwritten = await active.page.evaluate( + (backupFile) => window.deepchat.invoke('sync.import', { backupFile, mode: 'overwrite' }), + backup!.fileName + ) + expect(overwritten.result.success).toBe(true) + const removed = await active.page.evaluate( + (sessionId) => window.deepchat.invoke('sessions.restore', { sessionId, limit: 30 }), + extra.session.id + ) + expect(removed.session).toBeNull() + const { report } = await active.page.evaluate(() => + window.deepchat.invoke('databaseSecurity.repairSchema', {}) + ) + expect(report.remainingIssues).toEqual([]) + expect(await active.close()).toBe('graceful') + + const reopened = await launchApp() + await waitForAppReady(reopened.page) + const persisted = await reopened.page.evaluate( + (sessionId) => window.deepchat.invoke('sessions.restore', { sessionId, limit: 30 }), + sessionId + ) + expect(persisted.session?.id).toBe(sessionId) + expect(persisted.session?.providerId).toBe(before.session?.providerId) + expect(await listProviders()).toEqual(providers) + expect(await reopened.close()).toBe('graceful') +}) + +test('database repair and import cannot enter while encryption is preparing @smoke', async ({ + launchApp +}) => { + const active = await launchApp() + expect(active.ownsUserDataDir).toBe(true) + await waitForAppReady(active.page) + const password = 'disposable-maintenance-password' + await holdEncryption(active.electronApp, password, password) + const enabled = active.page.evaluate( + (password) => window.deepchat.invoke('databaseSecurity.enable', { password }), + password + ) + void enabled.catch(() => undefined) + try { + await expect + .poll(() => + active.electronApp.evaluate( + () => + (globalThis as unknown as { __maintenanceFixture: { entered: boolean } }) + .__maintenanceFixture.entered + ) + ) + .toBe(true) + await expect( + active.page.evaluate(() => window.deepchat.invoke('databaseSecurity.repairSchema', {})) + ).rejects.toThrow('App database maintenance is maintenance') + await expect( + active.page.evaluate(() => + window.deepchat.invoke('sync.import', { backupFile: 'unavailable.zip', mode: 'increment' }) + ) + ).rejects.toThrow('App database maintenance is maintenance') + await expect( + active.page.evaluate(() => window.deepchat.invoke('config.setCustomPrompts', { prompts: [] })) + ).rejects.toThrow('App database maintenance is maintenance') + await expect( + active.page.evaluate(() => + window.deepchat.invoke('providers.add', { + provider: { + id: 'custom-maintenance-blocked', + name: 'Blocked', + apiType: 'openai-completions', + apiKey: 'must-not-persist', + baseUrl: 'http://127.0.0.1:1/v1', + enable: false, + custom: true + } + }) + ) + ).rejects.toThrow('App database maintenance is maintenance') + expect( + (await active.page.evaluate(() => window.deepchat.invoke('window.getCurrentState', {}))).state + .exists + ).toBe(true) + expect( + (await active.page.evaluate(() => window.deepchat.invoke('databaseSecurity.getStatus', {}))) + .status.migrationInProgress + ).toBe(true) + expect( + JSON.parse((await runCli(active.userDataDir, ['system', 'status'])).stdout) + ).toMatchObject({ ok: true, result: { running: true } }) + await expect(runCli(active.userDataDir, ['provider', 'list'])).rejects.toMatchObject({ + stdout: expect.stringMatching(/"ok"\s*:\s*false/) + }) + } finally { + await releaseEncryption(active.electronApp) + } + expect((await enabled).status.enabled).toBe(true) + expect( + (await active.page.evaluate(() => window.deepchat.invoke('databaseSecurity.repairSchema', {}))) + .report.remainingIssues + ).toEqual([]) + expect(await active.close()).toBe('graceful') +}) + +test('maintenance drains a credential request delayed in native encryption @smoke', async ({ + launchApp +}) => { + const active = await launchApp() + expect(active.ownsUserDataDir).toBe(true) + await waitForAppReady(active.page) + const secret = 'disposable-pending-cloud-secret' + const password = 'disposable-request-drain-password' + await holdEncryption(active.electronApp, secret, password) + const saving = active.page.evaluate( + (secretAccessKey) => + window.deepchat.invoke('sync.setCloudConfig', { + config: { + enabled: false, + endpoint: 'http://127.0.0.1:1', + bucket: 'fixture', + accessKeyId: 'fixture', + secretAccessKey + } + }), + secret + ) + const rejected = expect(saving).rejects.toThrow() + await expect + .poll(() => + active.electronApp.evaluate( + () => + (globalThis as unknown as { __maintenanceFixture: { entered: boolean } }) + .__maintenanceFixture.entered + ) + ) + .toBe(true) + const enabled = active.page.evaluate( + (password) => window.deepchat.invoke('databaseSecurity.enable', { password }), + password + ) + void enabled.catch(() => undefined) + try { + await expect + .poll(async () => + active.page.evaluate(async () => { + try { + await window.deepchat.invoke('config.listCustomPrompts', {}) + return false + } catch (error) { + return String(error).includes('App database maintenance is maintenance') + } + }) + ) + .toBe(true) + } finally { + // Release the request first; keep the observer installed until maintenance dispatches. + await active.electronApp.evaluate(() => + ( + globalThis as unknown as { __maintenanceFixture: { release(): void } } + ).__maintenanceFixture.release() + ) + } + await rejected + try { + expect((await enabled).status.enabled).toBe(true) + expect( + await active.electronApp.evaluate( + () => + (globalThis as unknown as { __maintenanceFixture: { events: string[] } }) + .__maintenanceFixture.events + ) + ).toEqual(['pending-request-released', 'maintenance-dispatched']) + expect( + (await active.page.evaluate(() => window.deepchat.invoke('sync.getCloudConfig', {}))).config + .hasSecret + ).toBe(false) + } finally { + await releaseEncryption(active.electronApp) + } + expect(await active.close()).toBe('graceful') +}) + +for (const kind of ['credential', 'maintenance'] as const) { + test(`shutdown drains pending native ${kind} work before closing the database @smoke`, async ({ + launchApp + }) => { + const active = await launchApp() + const appProcess = active.electronApp.process() + expect(active.ownsUserDataDir).toBe(true) + await waitForAppReady(active.page) + const value = `disposable-shutdown-${kind}` + await holdEncryption(active.electronApp, value, value) + const pending = active.page + .evaluate( + async ({ kind, value }) => { + if (kind === 'maintenance') { + await window.deepchat.invoke('databaseSecurity.enable', { password: value }) + } else { + await window.deepchat.invoke('sync.setCloudConfig', { + config: { + enabled: false, + endpoint: 'http://127.0.0.1:1', + bucket: 'fixture', + accessKeyId: 'fixture', + secretAccessKey: value + } + }) + } + }, + { kind, value } + ) + .then( + () => true, + () => false + ) + try { + await expect + .poll(() => + active.electronApp.evaluate( + () => + (globalThis as unknown as { __maintenanceFixture: { entered: boolean } }) + .__maintenanceFixture.entered + ) + ) + .toBe(true) + const windowClosed = active.page.waitForEvent('close') + await active.electronApp.evaluate(({ app }) => { + setImmediate(() => app.quit()) + }) + await windowClosed + await releaseEncryption(active.electronApp) + await expect.poll(() => appProcess.exitCode).toBe(0) + expect(await active.close()).toBe('graceful') + expect(await pending).toBe(false) + } finally { + await releaseEncryption(active.electronApp).catch(() => undefined) + } + + const reopened = await launchApp() + await waitForAppReady(reopened.page) + expect( + (await reopened.page.evaluate(() => window.deepchat.invoke('databaseSecurity.getStatus', {}))) + .status.enabled + ).toBe(false) + expect( + (await reopened.page.evaluate(() => window.deepchat.invoke('sync.getCloudConfig', {}))).config + .hasSecret + ).toBe(false) + expect(await reopened.close()).toBe('graceful') + }) +} diff --git a/test/e2e/specs/55-provider-model-store.smoke.spec.ts b/test/e2e/specs/55-provider-model-store.smoke.spec.ts new file mode 100644 index 0000000000..17304ab224 --- /dev/null +++ b/test/e2e/specs/55-provider-model-store.smoke.spec.ts @@ -0,0 +1,81 @@ +import { existsSync, readFileSync } from 'node:fs' +import { join } from 'node:path' +import { test, expect } from '../fixtures/electronApp' +import { waitForAppReady } from '../helpers/wait' +import type { ModelType } from '../../../src/shared/model' + +test.use({ privacyModeEnabled: true }) + +test('model facts and user overrides persist without rewriting legacy JSON @smoke', async ({ + launchApp +}) => { + const active = await launchApp() + expect(active.ownsUserDataDir).toBe(true) + await waitForAppReady(active.page) + const legacyFiles = [ + join(active.userDataDir, 'model-config.json'), + join(active.userDataDir, 'provider_models', 'models_openai.json') + ] + const readLegacyFiles = () => + legacyFiles.map((file) => (existsSync(file) ? readFileSync(file, 'utf8') : null)) + const legacyContents = readLegacyFiles() + const selection = { providerId: 'openai', modelId: 'store-fixture-model' } + await active.page.evaluate(async ({ providerId, modelId }) => { + await window.deepchat.invoke('models.saveCustom', { + providerId, + model: { id: modelId, name: 'Store fixture model', type: 'chat' as ModelType, enabled: true }, + config: { + maxTokens: 12000, + contextLength: 64000, + temperature: 0.4, + vision: false, + functionCall: true, + reasoning: false, + type: 'chat' as ModelType, + isUserDefined: true + } + }) + }, selection) + expect(await active.close()).toBe('graceful') + expect(readLegacyFiles()).toEqual(legacyContents) + + const reopened = await launchApp() + await waitForAppReady(reopened.page) + const persisted = await reopened.page.evaluate( + async (selection) => ({ + ...(await window.deepchat.invoke('models.getConfig', selection)), + ...(await window.deepchat.invoke('models.hasUserConfig', selection)), + ...(await window.deepchat.invoke('models.getProviderCatalog', { + providerId: selection.providerId + })) + }), + selection + ) + expect(persisted).toMatchObject({ + hasConfig: true, + config: { maxTokens: 12000, contextLength: 64000, temperature: 0.4, isUserDefined: true }, + catalog: { customModels: [expect.objectContaining({ id: selection.modelId })] } + }) + await reopened.page.evaluate(async (selection) => { + await window.deepchat.invoke('models.resetConfig', selection) + await window.deepchat.invoke('models.removeCustom', selection) + }, selection) + expect(await reopened.close()).toBe('graceful') + + const removed = await launchApp() + await waitForAppReady(removed.page) + const final = await removed.page.evaluate( + async (selection) => ({ + ...(await window.deepchat.invoke('models.hasUserConfig', selection)), + ...(await window.deepchat.invoke('models.getProviderCatalog', { + providerId: selection.providerId + })) + }), + selection + ) + expect(final.hasConfig).toBe(false) + expect(final.catalog.customModels).not.toContainEqual( + expect.objectContaining({ id: selection.modelId }) + ) + expect(readLegacyFiles()).toEqual(legacyContents) +}) diff --git a/test/e2e/specs/56-mcp-acp-settings-store.smoke.spec.ts b/test/e2e/specs/56-mcp-acp-settings-store.smoke.spec.ts new file mode 100644 index 0000000000..7c6e5c5e99 --- /dev/null +++ b/test/e2e/specs/56-mcp-acp-settings-store.smoke.spec.ts @@ -0,0 +1,99 @@ +import { readFileSync } from 'node:fs' +import { join } from 'node:path' +import { test, expect } from '../fixtures/electronApp' +import { waitForAppReady } from '../helpers/wait' + +test.use({ privacyModeEnabled: true }) + +test('MCP and ACP settings persist through their database owners across reopen @smoke', async ({ + launchApp +}) => { + const active = await launchApp() + expect(active.ownsUserDataDir).toBe(true) + await waitForAppReady(active.page) + const legacyFiles = ['mcp-settings.json', 'acp_agents.json'].map((file) => + join(active.userDataDir, file) + ) + const readLegacy = () => legacyFiles.map((file) => readFileSync(file, 'utf8')) + const legacy = readLegacy() + const serverName = 'settings-store-fixture' + const agentId = 'settings-store-agent' + await active.page.evaluate( + async ({ serverName, agentId }) => { + await window.deepchat.invoke('mcp.addServer', { + serverName, + config: { + command: 'unused-settings-fixture', + args: [], + env: {}, + descriptions: 'Settings store fixture', + icons: '', + disable: false, + enabled: false, + type: 'stdio' + } + }) + await window.deepchat.invoke('config.addManualAcpAgent', { + id: agentId, + name: 'Settings fixture', + command: 'unused-agent-fixture', + enabled: false + }) + await window.deepchat.invoke('config.updateManualAcpAgent', { + agentId, + updates: { name: 'Updated settings fixture', env: { FIXTURE_VALUE: 'persisted' } } + }) + await window.deepchat.invoke('config.setAcpSharedMcpSelections', { + selections: [serverName] + }) + await window.deepchat.invoke('config.setAcpEnabled', { enabled: true }) + }, + { serverName, agentId } + ) + expect(await active.close()).toBe('graceful') + expect(readLegacy()).toEqual(legacy) + + const reopened = await launchApp() + await waitForAppReady(reopened.page) + const persisted = await reopened.page.evaluate(async () => ({ + ...(await window.deepchat.invoke('mcp.getServers', {})), + ...(await window.deepchat.invoke('config.listManualAcpAgents', {})), + ...(await window.deepchat.invoke('config.getAcpSharedMcpSelections', {})), + enabled: (await window.deepchat.invoke('config.getAcpState', {})).enabled + })) + expect(persisted.enabled).toBe(true) + expect(persisted.servers[serverName]).toMatchObject({ enabled: false, type: 'stdio' }) + expect(persisted.selections).toEqual([serverName]) + expect(persisted.agents).toContainEqual( + expect.objectContaining({ + id: agentId, + name: 'Updated settings fixture', + env: { FIXTURE_VALUE: 'persisted' }, + enabled: false + }) + ) + await reopened.page.evaluate( + async ({ serverName, agentId }) => { + await window.deepchat.invoke('config.setAcpSharedMcpSelections', { selections: [] }) + await window.deepchat.invoke('config.removeManualAcpAgent', { agentId }) + await window.deepchat.invoke('mcp.removeServer', { serverName }) + await window.deepchat.invoke('config.setAcpEnabled', { enabled: false }) + }, + { serverName, agentId } + ) + expect(await reopened.close()).toBe('graceful') + + const removed = await launchApp() + await waitForAppReady(removed.page) + const final = await removed.page.evaluate(async () => ({ + ...(await window.deepchat.invoke('mcp.getServers', {})), + ...(await window.deepchat.invoke('config.listManualAcpAgents', {})), + ...(await window.deepchat.invoke('config.getAcpSharedMcpSelections', {})), + enabled: (await window.deepchat.invoke('config.getAcpState', {})).enabled + })) + expect(final.enabled).toBe(false) + expect(final.servers).not.toHaveProperty(serverName) + expect(final.agents).not.toContainEqual(expect.objectContaining({ id: agentId })) + expect(final.selections).toEqual([]) + expect(readLegacy()).toEqual(legacy) +}) diff --git a/test/e2e/specs/57-provider-bootstrap.smoke.spec.ts b/test/e2e/specs/57-provider-bootstrap.smoke.spec.ts new file mode 100644 index 0000000000..e2e268d0d7 --- /dev/null +++ b/test/e2e/specs/57-provider-bootstrap.smoke.spec.ts @@ -0,0 +1,124 @@ +import { createServer, type ServerResponse } from 'node:http' +import { startHttpsFixture } from '../helpers/httpsFixture' +import { existsSync, readFileSync } from 'node:fs' +import { join } from 'node:path' +import { test, expect } from '../fixtures/electronApp' +import { waitForAppReady } from '../helpers/wait' + +test.use({ privacyModeEnabled: true }) + +test('Provider catalog and request metadata are ready from bootstrap @smoke', async ({ app }) => { + const requests: Array<{ userAgent?: string; title?: string; referer?: string }> = [] + const server = createServer(async (request, response) => { + requests.push({ + userAgent: request.headers['user-agent'], + title: request.headers['x-title']?.toString(), + referer: request.headers['http-referer']?.toString() + }) + let body = '' + for await (const chunk of request) body += chunk + response.writeHead(200, { 'Content-Type': 'application/json' }) + if (request.url?.endsWith('/models')) { + response.end(JSON.stringify({ data: [{ id: 'fixture-model', object: 'model' }] })) + } else { + response.end( + JSON.stringify({ + id: 'bootstrap-fixture', + object: 'chat.completion', + created: 1, + model: JSON.parse(body).model, + choices: [ + { index: 0, message: { role: 'assistant', content: 'OK' }, finish_reason: 'stop' } + ] + }) + ) + } + }) + await new Promise((resolve) => server.listen(0, '127.0.0.1', resolve)) + const { port } = server.address() as { port: number } + try { + await waitForAppReady(app.page) + const { catalog } = await app.page.evaluate(() => + window.deepchat.invoke('models.getProviderCatalog', { providerId: 'openai' }) + ) + expect(catalog.dbProviderModels.length).toBeGreaterThan(0) + const result = await app.page.evaluate( + (baseUrl) => + window.deepchat.invoke('providers.validateDraft', { + provider: { + id: 'bootstrap-fixture', + name: 'Bootstrap fixture', + apiType: 'openai-compatible', + apiKey: 'disposable-bootstrap-key', + baseUrl, + enable: false, + custom: true + }, + loadModels: false + }), + `http://127.0.0.1:${port}/v1` + ) + expect(result.isOk).toBe(true) + const version = await app.electronApp.evaluate(({ app }) => app.getVersion()) + expect(requests.length).toBeGreaterThan(0) + for (const request of requests) { + expect(request).toEqual({ + userAgent: `DeepChat/${version}`, + title: 'DeepChat', + referer: 'https://deepchatai.cn' + }) + } + } finally { + server.closeAllConnections() + await new Promise((resolve) => server.close(() => resolve())) + } +}) + +test('shutdown cancels and drains catalog refresh without accepting a late response @smoke', async ({ + launchApp +}) => { + const responses: ServerResponse[] = [] + const https = await startHttpsFixture(['raw.githubusercontent.com'], (request, response) => { + if (request.url?.includes('/PublicProviderConf/')) responses.push(response) + else response.writeHead(404).end() + }) + let active: Awaited> | undefined + let refresh: Promise | undefined + try { + active = await launchApp() + expect(active.ownsUserDataDir).toBe(true) + await waitForAppReady(active.page) + await active.page.evaluate(async (url) => { + await window.deepchat.invoke('config.setCustomProxyUrl', { url }) + await window.deepchat.invoke('config.setProxyMode', { mode: 'custom' }) + }, https.proxyUrl) + const profile = active.userDataDir + const files = ['providers.json', 'meta.json'].map((name) => join(profile, 'provider-db', name)) + const readCache = () => + files.map((file) => (existsSync(file) ? readFileSync(file, 'utf8') : null)) + const original = readCache() + refresh = active.page + .evaluate(() => window.deepchat.invoke('config.refreshProviderDb', { force: true })) + .catch(() => null) + await expect.poll(() => responses.length).toBe(1) + expect(responses[0].destroyed).toBe(false) + expect(await active.close()).toBe('graceful') + await refresh + await expect.poll(() => responses[0].destroyed).toBe(true) + expect(readCache()).toEqual(original) + active = await launchApp() + await waitForAppReady(active.page) + const { catalog } = await active.page.evaluate(() => + window.deepchat.invoke('models.getProviderCatalog', { providerId: 'openai' }) + ) + expect(catalog.dbProviderModels.length).toBeGreaterThan(0) + expect(await active.close()).toBe('graceful') + } finally { + try { + await active?.close() + await refresh + } finally { + await https.close() + } + } +}) diff --git a/test/e2e/specs/58-acp-registry-bootstrap.smoke.spec.ts b/test/e2e/specs/58-acp-registry-bootstrap.smoke.spec.ts new file mode 100644 index 0000000000..0547cf1252 --- /dev/null +++ b/test/e2e/specs/58-acp-registry-bootstrap.smoke.spec.ts @@ -0,0 +1,156 @@ +import { existsSync, readFileSync } from 'node:fs' +import type { ServerResponse } from 'node:http' +import { join } from 'node:path' +import { test, expect } from '../fixtures/electronApp' +import { startHttpsFixture } from '../helpers/httpsFixture' +import { waitForAppReady } from '../helpers/wait' + +test.use({ privacyModeEnabled: true }) + +const registryHost = 'cdn.agentclientprotocol.com' +const registryPath = '/registry/v1/latest/registry.json' + +test('ACP registry refresh persists catalog and sanitized icons without legacy writes @smoke', async ({ + launchApp +}) => { + const agentId = 'rutis-registry-fixture' + const iconUrl = `https://${registryHost}/registry/v1/latest/${agentId}.svg` + const svg = + '' + const https = await startHttpsFixture([registryHost], (request, response) => { + if (request.url !== registryPath) { + response.writeHead(404).end() + return + } + response.writeHead(200, { 'content-type': 'application/json' }).end( + JSON.stringify({ + version: 'rutis-registry-fixture-v1', + agents: [ + { + id: agentId, + name: 'Registry fixture', + version: '1.0.0', + icon: iconUrl, + distribution: { npx: { package: '@deepchat/registry-fixture@1.0.0' } } + } + ] + }) + ) + }) + let active: Awaited> | undefined + try { + active = await launchApp() + expect(active.ownsUserDataDir).toBe(true) + await waitForAppReady(active.page) + const legacyFile = join(active.userDataDir, 'acp_agents.json') + const readLegacy = () => (existsSync(legacyFile) ? readFileSync(legacyFile, 'utf8') : null) + const legacy = readLegacy() + await active.page.evaluate(async (url) => { + await window.deepchat.invoke('config.setCustomProxyUrl', { url }) + await window.deepchat.invoke('config.setProxyMode', { mode: 'custom' }) + }, https.proxyUrl) + await active.electronApp.evaluate( + ({ net }, { iconUrl, svg }) => { + const original = net.fetch + net.fetch = async (input, init) => + String(input) === iconUrl ? new Response(svg) : await original.call(net, input, init) + }, + { iconUrl, svg } + ) + const { agents } = await active.page.evaluate(() => + window.deepchat.invoke('config.refreshAcpRegistry', { force: true }) + ) + expect(agents).toEqual([ + expect.objectContaining({ id: agentId, version: '1.0.0', enabled: false }) + ]) + const { markup } = await active.page.evaluate( + ({ agentId, iconUrl }) => + window.deepchat.invoke('config.getAcpRegistryIconMarkup', { agentId, iconUrl }), + { agentId, iconUrl } + ) + expect(markup).toContain(' + window.deepchat.invoke('config.listAcpRegistryAgents', {}) + ) + expect(saved.agents).toEqual([ + expect.objectContaining({ id: agentId, version: '1.0.0', enabled: false }) + ]) + const reopened = await active.page.evaluate( + ({ agentId, iconUrl }) => + window.deepchat.invoke('config.getAcpRegistryIconMarkup', { agentId, iconUrl }), + { agentId, iconUrl } + ) + expect(reopened.markup).toContain(' { + const responses: ServerResponse[] = [] + const https = await startHttpsFixture([registryHost], (request, response) => { + if (request.url === registryPath) responses.push(response) + else response.writeHead(404).end() + }) + let active: Awaited> | undefined + let refresh: Promise | undefined + try { + active = await launchApp() + expect(active.ownsUserDataDir).toBe(true) + await waitForAppReady(active.page) + await active.page.evaluate(async (url) => { + await window.deepchat.invoke('config.setCustomProxyUrl', { url }) + await window.deepchat.invoke('config.setProxyMode', { mode: 'custom' }) + }, https.proxyUrl) + const profile = active.userDataDir + const files = ['registry.json', 'meta.json'].map((file) => join(profile, 'acp-registry', file)) + const readCache = () => + files.map((file) => (existsSync(file) ? readFileSync(file, 'utf8') : null)) + const original = readCache() + refresh = active.page + .evaluate(() => window.deepchat.invoke('config.refreshAcpRegistry', { force: true })) + .catch(() => null) + await expect.poll(() => responses.length).toBe(1) + expect(responses[0].destroyed).toBe(false) + expect(await active.close()).toBe('graceful') + await refresh + await expect.poll(() => responses[0].destroyed).toBe(true) + expect(readCache()).toEqual(original) + active = await launchApp() + await waitForAppReady(active.page) + const { agents } = await active.page.evaluate(() => + window.deepchat.invoke('config.listAgents', { agentType: 'acp' }) + ) + expect(agents.some((agent) => agent.id === 'late-registry-fixture')).toBe(false) + expect(await active.close()).toBe('graceful') + } finally { + try { + await active?.close() + await refresh + } finally { + await https.close() + } + } +}) diff --git a/test/e2e/specs/59-acp-installation.smoke.spec.ts b/test/e2e/specs/59-acp-installation.smoke.spec.ts new file mode 100644 index 0000000000..7da90482af --- /dev/null +++ b/test/e2e/specs/59-acp-installation.smoke.spec.ts @@ -0,0 +1,225 @@ +import { createServer, type ServerResponse } from 'node:http' +import { existsSync, readFileSync, readdirSync } from 'node:fs' +import { dirname } from 'node:path' +import { zipSync } from 'fflate' +import { test, expect } from '../fixtures/electronApp' +import { startHttpsFixture } from '../helpers/httpsFixture' +import { waitForAppReady } from '../helpers/wait' + +test.use({ privacyModeEnabled: true }) + +function registry(agentId: string, archiveUrl: string) { + const platform = process.platform === 'win32' ? 'windows' : process.platform + const key = `${platform}-${process.arch === 'arm64' ? 'aarch64' : 'x86_64'}` + return { + version: agentId, + agents: [ + { + id: agentId, + name: 'Installation fixture', + version: '1.0.0', + distribution: { binary: { [key]: { archive: archiveUrl, cmd: './fixture-agent' } } } + } + ] + } +} + +test('ACP binary installation and repair persist through the owned cache across reopen @smoke', async ({ + launchApp +}) => { + let downloads = 0 + const archive = zipSync({ 'fixture-agent': Buffer.from('owned-installation-fixture') }) + const server = createServer((_request, response) => { + downloads++ + response.writeHead(200, { 'content-type': 'application/zip' }).end(archive) + }) + await new Promise((resolve) => server.listen(0, '127.0.0.1', resolve)) + const archiveUrl = `http://127.0.0.1:${(server.address() as { port: number }).port}/fixture.zip` + const agentId = 'owned-installation-fixture' + const https = await startHttpsFixture(['cdn.agentclientprotocol.com'], (request, response) => { + if (request.url !== '/registry/v1/latest/registry.json') { + response.writeHead(404).end() + return + } + response + .writeHead(200, { 'content-type': 'application/json' }) + .end(JSON.stringify(registry(agentId, archiveUrl))) + }) + let active: Awaited> | undefined + try { + active = await launchApp() + expect(active.ownsUserDataDir).toBe(true) + await waitForAppReady(active.page) + await active.page.evaluate(async (url) => { + await window.deepchat.invoke('config.setCustomProxyUrl', { url }) + await window.deepchat.invoke('config.setProxyMode', { mode: 'custom' }) + await window.deepchat.invoke('config.refreshAcpRegistry', { force: true }) + }, https.proxyUrl) + const { installState } = await active.page.evaluate( + (agentId) => window.deepchat.invoke('config.ensureAcpAgentInstalled', { agentId }), + agentId + ) + expect(installState).toMatchObject({ + status: 'installed', + version: '1.0.0', + distributionType: 'binary' + }) + const binary = `${installState.installDir}/fixture-agent` + expect(readFileSync(binary, 'utf8')).toBe('owned-installation-fixture') + const { installState: repaired } = await active.page.evaluate( + (agentId) => window.deepchat.invoke('config.repairAcpAgent', { agentId }), + agentId + ) + expect(repaired.status).toBe('installed') + expect(readFileSync(binary, 'utf8')).toBe('owned-installation-fixture') + expect(downloads).toBe(2) + expect(await active.close()).toBe('graceful') + active = await launchApp() + await waitForAppReady(active.page) + const { agents } = await active.page.evaluate(() => + window.deepchat.invoke('config.listAcpRegistryAgents', {}) + ) + expect(agents.find((agent) => agent.id === agentId)?.installState?.status).toBe('installed') + const existing = await active.page.evaluate( + (agentId) => window.deepchat.invoke('config.ensureAcpAgentInstalled', { agentId }), + agentId + ) + expect(existing.installState).toMatchObject({ + installDir: installState.installDir, + status: 'installed' + }) + expect(downloads).toBe(2) + expect(await active.close()).toBe('graceful') + } finally { + try { + await active?.close() + } finally { + server.closeAllConnections() + await new Promise((resolve) => server.close(() => resolve())) + await https.close() + } + } +}) + +for (const action of ['shutdown', 'maintenance'] as const) { + test(`${action} drains a held ACP repair without replacing the acknowledged binary @smoke`, async ({ + launchApp + }) => { + const archive = zipSync({ 'fixture-agent': Buffer.from('acknowledged-binary') }) + const badArchive = zipSync({ 'wrong-agent': Buffer.from('invalid-replacement') }) + let mode: 'install' | 'hold' | 'bad' = 'install' + const held: ServerResponse[] = [] + const server = createServer((_request, response) => { + response.writeHead(200, { 'content-type': 'application/zip' }) + if (mode === 'hold') { + held.push(response) + response.write(archive.subarray(0, 8)) + } else response.end(mode === 'bad' ? badArchive : archive) + }) + await new Promise((resolve) => server.listen(0, '127.0.0.1', resolve)) + const archiveUrl = `http://127.0.0.1:${(server.address() as { port: number }).port}/fixture.zip` + const agentId = 'held-installation-fixture' + const https = await startHttpsFixture(['cdn.agentclientprotocol.com'], (request, response) => { + if (request.url !== '/registry/v1/latest/registry.json') { + response.writeHead(404).end() + return + } + response + .writeHead(200, { 'content-type': 'application/json' }) + .end(JSON.stringify(registry(agentId, archiveUrl))) + }) + let active: Awaited> | undefined + let repair: Promise | undefined + try { + active = await launchApp() + expect(active.ownsUserDataDir).toBe(true) + await waitForAppReady(active.page) + await active.page.evaluate(async (url) => { + await window.deepchat.invoke('config.setCustomProxyUrl', { url }) + await window.deepchat.invoke('config.setProxyMode', { mode: 'custom' }) + await window.deepchat.invoke('config.refreshAcpRegistry', { force: true }) + }, https.proxyUrl) + const { installState: original } = await active.page.evaluate( + (agentId) => window.deepchat.invoke('config.ensureAcpAgentInstalled', { agentId }), + agentId + ) + expect(original.status).toBe('installed') + let acknowledged = original + const binary = `${original.installDir}/fixture-agent` + mode = 'hold' + repair = active.page + .evaluate( + (agentId) => window.deepchat.invoke('config.repairAcpAgent', { agentId }), + agentId + ) + .then( + () => 'resolved', + () => 'rejected' + ) + await expect.poll(() => held.length).toBe(1) + expect(held[0].destroyed).toBe(false) + expect(readFileSync(binary, 'utf8')).toBe('acknowledged-binary') + if (action === 'shutdown') expect(await active.close()).toBe('graceful') + else + await expect( + active.page.evaluate(() => + window.deepchat.invoke('databaseSecurity.enable', { + password: 'owned-installation-maintenance-password' + }) + ) + ).resolves.toMatchObject({ status: { enabled: true } }) + await expect.poll(() => held[0].destroyed).toBe(true) + expect(await repair).toBe('rejected') + expect(readFileSync(binary, 'utf8')).toBe('acknowledged-binary') + expect(readdirSync(dirname(original.installDir!))).toEqual(['1.0.0']) + if (action === 'maintenance') { + mode = 'bad' + const { installState: failed } = await active.page.evaluate( + (agentId) => window.deepchat.invoke('config.repairAcpAgent', { agentId }), + agentId + ) + expect(failed.status).toBe('error') + expect(failed.error).toContain('Installed archive does not contain ./fixture-agent') + expect(readFileSync(binary, 'utf8')).toBe('acknowledged-binary') + mode = 'install' + const { installState: recovered } = await active.page.evaluate( + (agentId) => window.deepchat.invoke('config.repairAcpAgent', { agentId }), + agentId + ) + expect(recovered.status).toBe('installed') + acknowledged = recovered + } + expect(await active.close()).toBe('graceful') + active = await launchApp() + await waitForAppReady(active.page) + const { agents } = await active.page.evaluate(() => + window.deepchat.invoke('config.listAcpRegistryAgents', {}) + ) + expect(agents.find((agent) => agent.id === agentId)?.installState).toEqual(acknowledged) + expect(readFileSync(binary, 'utf8')).toBe('acknowledged-binary') + if (action === 'maintenance') { + await active.page.evaluate( + (agentId) => window.deepchat.invoke('config.uninstallAcpRegistryAgent', { agentId }), + agentId + ) + expect(existsSync(original.installDir!)).toBe(false) + const uninstalled = await active.page.evaluate(() => + window.deepchat.invoke('config.listAcpRegistryAgents', {}) + ) + expect(uninstalled.agents.find((agent) => agent.id === agentId)?.installState?.status).toBe( + 'not_installed' + ) + } + expect(await active.close()).toBe('graceful') + } finally { + try { + await active?.close() + await repair + } finally { + server.closeAllConnections() + await new Promise((resolve) => server.close(() => resolve())) + await https.close() + } + } + }) +} diff --git a/test/e2e/specs/60-background-execution.smoke.spec.ts b/test/e2e/specs/60-background-execution.smoke.spec.ts new file mode 100644 index 0000000000..1123bf66c6 --- /dev/null +++ b/test/e2e/specs/60-background-execution.smoke.spec.ts @@ -0,0 +1,576 @@ +import { createServer } from 'node:http' +import { execFileSync } from 'node:child_process' +import { existsSync, mkdirSync, readdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs' +import { join } from 'node:path' +import { test, expect } from '../fixtures/electronApp' +import { waitForAppReady } from '../helpers/wait' + +test.use({ privacyModeEnabled: true }) + +for (const action of [ + 'complete', + 'environment', + 'shutdown', + 'held-start', + 'helper-loss', + 'registration-failure', + 'exec-transition', + 'group-shutdown', + 'group-helper-loss', + 'root-exit', + 'escaped-root-exit', + 'registration-loss' +] as const) { + test(`background exec retains process feedback and ${action} ownership @smoke`, async ({ + launchApp + }, testInfo) => { + const rootExits = action === 'root-exit' || action === 'escaped-root-exit' + const hasDescendant = action === 'group-shutdown' || action === 'group-helper-loss' || rootExits + test.skip( + action === 'exec-transition' && process.platform !== 'darwin', + 'macOS native identity' + ) + test.skip(hasDescendant && process.platform === 'win32', 'POSIX process group') + test.skip( + (action === 'group-helper-loss' || rootExits) && process.platform !== 'darwin', + 'macOS identity-bound recovery' + ) + test.skip( + action === 'registration-loss' && process.platform !== 'darwin', + 'macOS execution gate' + ) + let command = '' + let firstResult = '' + let processResult = '' + let environmentStarted = false + let releaseEnvironment!: () => void + const environmentReady = new Promise((resolve) => { + releaseEnvironment = resolve + }) + let releasePoll!: () => void + const pollReady = new Promise((resolve) => { + releasePoll = resolve + }) + const finalText = `BACKGROUND_${action.toUpperCase()}_VERIFIED` + const server = createServer(async (request, response) => { + if (request.url === '/v1/models') { + response + .writeHead(200, { 'Content-Type': 'application/json' }) + .end(JSON.stringify({ data: [{ id: 'exec-fixture' }] })) + return + } + if (request.url !== '/v1/chat/completions') { + response.writeHead(404).end() + return + } + let source = '' + for await (const chunk of request) source += chunk + const body = JSON.parse(source) + if (!body.stream) { + response.writeHead(200, { 'Content-Type': 'application/json' }).end( + JSON.stringify({ + id: 'title', + object: 'chat.completion', + created: 1, + model: 'exec-fixture', + choices: [ + { + index: 0, + message: { role: 'assistant', content: 'Execution verification' }, + finish_reason: 'stop' + } + ] + }) + ) + return + } + const results = body.messages.filter((message: { role: string }) => message.role === 'tool') + const toolCall = (name: string, args: unknown) => ({ + role: 'assistant', + tool_calls: [ + { + index: 0, + id: `owned-${name}${results.length > 1 ? `-${results.length}` : ''}`, + type: 'function', + function: { name, arguments: JSON.stringify(args) } + } + ] + }) + let delta: unknown + let finishReason = 'tool_calls' + if (!results.length) { + delta = toolCall('exec', { + command, + description: 'Verify owned background execution', + background: true + }) + } else if ( + (action === 'complete' || action === 'environment' || rootExits) && + results.length === 1 + ) { + firstResult = JSON.stringify(results) + await pollReady + const sessionId = /bg_[A-Za-z0-9_-]{12}/.exec(firstResult)?.[0] + delta = toolCall('process', { action: 'poll', sessionId }) + } else if ( + (rootExits || (action === 'environment' && !environmentStarted)) && + JSON.parse(results.at(-1).content).status === 'running' + ) { + const sessionId = /bg_[A-Za-z0-9_-]{12}/.exec(firstResult)?.[0] + delta = toolCall('process', { action: 'poll', sessionId }) + } else if (action === 'environment' && !environmentStarted) { + processResult = JSON.stringify(results) + await environmentReady + environmentStarted = true + delta = toolCall('exec', { + command, + description: 'Verify cleared command environment', + background: false, + yieldMs: 30_000 + }) + } else { + if (!firstResult) firstResult = JSON.stringify(results) + processResult = JSON.stringify(results) + delta = { content: finalText } + finishReason = 'stop' + } + if (response.destroyed) return + response.writeHead(200, { 'Content-Type': 'text/event-stream' }) + response.write( + `data: ${JSON.stringify({ + id: 'exec-fixture', + object: 'chat.completion.chunk', + created: 1, + model: 'exec-fixture', + choices: [{ index: 0, delta, finish_reason: finishReason }] + })}\n\n` + ) + response.end('data: [DONE]\n\n') + }) + await new Promise((resolve) => server.listen(0, '127.0.0.1', resolve)) + const { port } = server.address() as { port: number } + let releaseCommand: (() => void) | undefined + let drainReleasedCommand: (() => Promise) | undefined + let restoreRegistry: (() => void) | undefined + let releaseObservation: (() => void) | undefined + try { + const active = await launchApp() + expect(active.ownsUserDataDir).toBe(true) + await waitForAppReady(active.page) + const proxyUrl = `http://127.0.0.1:${port}` + if (action === 'environment') { + await active.page.evaluate(async (url) => { + await window.deepchat.invoke('config.setCustomProxyUrl', { url }) + await window.deepchat.invoke('config.setProxyMode', { mode: 'custom' }) + }, proxyUrl) + } + if (action === 'registration-failure') { + const registryRoot = join(active.userDataDir, 'child-processes') + const registryDirectory = join(registryRoot, 'background-exec') + mkdirSync(registryRoot, { recursive: true }) + expect(existsSync(registryDirectory)).toBe(false) + writeFileSync(registryDirectory, 'unavailable registry directory') + restoreRegistry = () => { + rmSync(registryDirectory, { force: true }) + mkdirSync(registryDirectory) + restoreRegistry = undefined + } + } + const observationFile = join(active.userDataDir, 'held-exec-observation') + let observerDirectory: string | undefined + if (action === 'registration-loss') { + observerDirectory = join(active.userDataDir, 'exec-observer') + mkdirSync(observerDirectory) + const executable = + (await active.electronApp.evaluate(() => process.env.DEEPCHAT_HARNESS_EXECUTABLE)) ?? + `${process.cwd()}/out/harness/deepchat-harness-${process.platform}-${process.arch}` + const release = join(observerDirectory, 'release') + releaseObservation = () => writeFileSync(release, 'release') + const quote = (value: string) => `'${value.replaceAll("'", "'\\''")}'` + const script = `#!/bin/sh\nif [ "$1" = "--exec-gate" ]; then while [ ! -f ${quote(release)} ]; do /bin/sleep 0.02; done; exec ${quote(executable)} "$@"; fi\nif [ "$1" = "--process-identity" ]; then target="$2"; binary=${quote(executable)}; elif [ "$1" = "-ww" ]; then target="$3"; binary=/bin/ps; else exec ${quote(executable)} "$@"; fi\nprintf '%s %s' "$target" "$$" > ${quote(observationFile)}\nwhile [ ! -f ${quote(release)} ]; do /bin/sleep 0.02; done\nexec "$binary" "$@"\n` + for (const name of ['ps', 'harness']) + writeFileSync(join(observerDirectory, name), script, { mode: 0o755 }) + } + if ( + action === 'held-start' || + action === 'helper-loss' || + action === 'group-helper-loss' || + action === 'exec-transition' || + action === 'registration-loss' + ) { + await active.electronApp.evaluate( + ({ utilityProcess }, { holdStart, observerDirectory }) => { + const originalFork = utilityProcess.fork + const fixture = { + entered: false, + pid: undefined as number | undefined, + crash: () => {} + } + utilityProcess.fork = (entry, args, options) => { + if (entry.endsWith('backgroundExecUtilityHost.js') && observerDirectory) { + options = { + ...options, + env: { + ...process.env, + ...options?.env, + PATH: `${observerDirectory}:${process.env.PATH ?? ''}`, + DEEPCHAT_HARNESS_EXECUTABLE: `${observerDirectory}/harness` + } + } + } + const host = originalFork.call(utilityProcess, entry, args, options) + if (entry.endsWith('backgroundExecUtilityHost.js')) { + fixture.crash = () => { + host.kill() + } + if (!holdStart) return host + const once = host.once.bind(host) as ( + event: string, + listener: (...values: unknown[]) => void + ) => typeof host + host.once = ((event: string, listener: (...values: unknown[]) => void) => { + if (event === 'spawn') + return once(event, () => { + fixture.entered = true + fixture.pid = host.pid + }) + return once(event, listener) + }) as typeof host.once + } + return host + } + Object.assign(globalThis, { __backgroundSpawnFixture: fixture }) + }, + { holdStart: action === 'held-start', observerDirectory } + ) + } + const startedFile = join(active.userDataDir, 'owned-command.pid') + const releaseFile = join(active.userDataDir, 'owned-command.release') + releaseCommand = () => writeFileSync(releaseFile, 'release') + const doneFile = join(active.userDataDir, 'owned-command.done') + const execFile = join(active.userDataDir, 'owned-command.exec') + const descendantFile = join(active.userDataDir, 'owned-command.descendant') + const rootExitFile = join(active.userDataDir, 'owned-command.root-exit') + const environmentFile = join(active.userDataDir, 'owned-command.environment') + let script = `const fs = require('node:fs'); fs.writeFileSync(${JSON.stringify(startedFile)}, String(process.pid)); const timer = setInterval(() => { if (fs.existsSync(${JSON.stringify(releaseFile)})) { clearInterval(timer); process.stdout.write('BACKGROUND_EXEC_DONE'); fs.writeFileSync(${JSON.stringify(doneFile)}, 'done') } }, 20)` + if (action === 'environment') + script = `require('node:fs').writeFileSync(${JSON.stringify(environmentFile)}, JSON.stringify({ proxy: process.env.HTTP_PROXY ?? null, utility: process.env.DEEPCHAT_EXEC_UTILITY_HOST ?? null })); ${script}` + if (hasDescendant) { + const descendant = `process.on('SIGTERM', () => {}); const fs = require('node:fs'); const timer = setInterval(() => { if (fs.existsSync(${JSON.stringify(releaseFile)})) { clearInterval(timer); fs.writeFileSync(${JSON.stringify(doneFile)}, 'done'); process.exit(0) } }, 20); process.stdout.write('ready')` + script = `const fs = require('node:fs'); const child = require('node:child_process').spawn(process.execPath, ['-e', ${JSON.stringify(descendant)}], { detached: ${action === 'escaped-root-exit'}, stdio: ['ignore', 'pipe', 'ignore'] }); child.stdout.once('data', () => { fs.writeFileSync(${JSON.stringify(descendantFile)}, String(child.pid)); fs.writeFileSync(${JSON.stringify(startedFile)}, String(process.pid)) }); setInterval(() => { if (fs.existsSync(${JSON.stringify(rootExitFile)})) { process.stdout.write('ROOT_EXITED', () => process.exit(0)) } else if (fs.existsSync(${JSON.stringify(releaseFile)})) process.exit(0) }, 20)` + } + const executable = + process.platform === 'win32' + ? `"${process.execPath}"` + : `'${process.execPath.replaceAll("'", "'\\''")}'` + command = `${executable} -e "eval(Buffer.from('${Buffer.from(script).toString('base64')}', 'base64').toString())"` + if (hasDescendant || action === 'registration-loss') command = `exec ${command}` + if (action === 'helper-loss' && process.platform !== 'win32') command += '; wait' + if (action === 'exec-transition') { + const inner = `while [ ! -f '${execFile.replaceAll("'", "'\\''")}' ]; do sleep 0.02; done; exec ${command}` + command = `exec /bin/sh -c '${inner.replaceAll("'", "'\\''")}'` + } + const sessionId = await active.page.evaluate( + async ({ baseUrl, projectDir }) => { + const providerId = `custom-${crypto.randomUUID()}` + await window.deepchat.invoke('providers.add', { + provider: { + id: providerId, + name: 'Execution fixture', + apiType: 'openai-completions', + baseUrl, + apiKey: 'fixture-key', + enable: true, + custom: true + } + }) + await window.deepchat.invoke('models.addCustom', { + providerId, + model: { + id: 'exec-fixture', + name: 'Execution fixture', + enabled: true, + vision: false, + functionCall: true, + reasoning: false, + contextLength: 32000, + maxTokens: 8000 + } + }) + await window.deepchat.invoke('models.setStatus', { + providerId, + modelId: 'exec-fixture', + enabled: true + }) + const { config } = await window.deepchat.invoke('config.resolveDeepChatAgentConfig', { + agentId: 'deepchat' + }) + await window.deepchat.invoke('config.updateDeepChatAgent', { + agentId: 'deepchat', + updates: { + config: { + ...config, + permissionMode: 'full_access', + disabledAgentTools: [], + enabledSkillNames: [], + memoryEnabled: false + } + } + }) + const { session } = await window.deepchat.invoke('sessions.create', { + agentId: 'deepchat', + message: 'Verify the owned background command and its process feedback.', + providerId, + modelId: 'exec-fixture', + projectDir, + permissionMode: 'full_access', + toolModeOverride: 'agent' + }) + return session.id + }, + { baseUrl: `http://127.0.0.1:${port}/v1`, projectDir: active.userDataDir } + ) + if (action === 'registration-loss') { + await expect.poll(() => existsSync(observationFile)).toBe(true) + const [rootPid, observerPid] = readFileSync(observationFile, 'utf8').split(' ').map(Number) + expect(rootPid).toBeGreaterThan(0) + expect(observerPid).toBeGreaterThan(0) + const alive = (pid: number) => { + try { + process.kill(pid, 0) + return true + } catch (error) { + if ((error as NodeJS.ErrnoException).code === 'ESRCH') return false + throw error + } + } + drainReleasedCommand = async () => { + await expect.poll(() => alive(rootPid)).toBe(false) + await expect.poll(() => alive(observerPid)).toBe(false) + } + await active.electronApp.evaluate(() => + ( + globalThis as unknown as { __backgroundSpawnFixture: { crash(): void } } + ).__backgroundSpawnFixture.crash() + ) + await expect.poll(() => alive(rootPid)).toBe(false) + await expect.poll(() => alive(observerPid)).toBe(false) + releaseObservation?.() + expect(existsSync(startedFile)).toBe(false) + expect(existsSync(doneFile)).toBe(false) + const exitCode = active.electronApp.process().exitCode + if (exitCode === null) expect(await active.close()).toBe('graceful') + else expect([0, 1]).toContain(exitCode) + const reopened = await launchApp() + await waitForAppReady(reopened.page) + const persisted = await reopened.page.evaluate( + (sessionId) => + window.deepchat.invoke('sessions.listMessagesPage', { sessionId, limit: 100 }), + sessionId + ) + expect(JSON.stringify(persisted)).toContain('Verify the owned background command') + expect(existsSync(startedFile)).toBe(false) + expect(await reopened.close()).toBe('graceful') + return + } + if (action === 'held-start') { + await expect + .poll(() => + active.electronApp.evaluate( + () => + (globalThis as unknown as { __backgroundSpawnFixture: { entered: boolean } }) + .__backgroundSpawnFixture.entered + ) + ) + .toBe(true) + const helperPid = await active.electronApp.evaluate( + () => + (globalThis as unknown as { __backgroundSpawnFixture: { pid?: number } }) + .__backgroundSpawnFixture.pid + ) + expect(helperPid).toBeGreaterThan(0) + expect(existsSync(startedFile)).toBe(false) + expect(await active.close()).toBe('graceful') + await expect + .poll(() => { + try { + process.kill(helperPid!, 0) + return true + } catch (error) { + if ((error as NodeJS.ErrnoException).code === 'ESRCH') return false + throw error + } + }) + .toBe(false) + expect(existsSync(startedFile)).toBe(false) + const reopened = await launchApp() + await waitForAppReady(reopened.page) + const { messages } = await reopened.page.evaluate( + (sessionId) => + window.deepchat.invoke('sessions.listMessagesPage', { sessionId, limit: 100 }), + sessionId + ) + expect(JSON.stringify(messages)).toContain('Verify the owned background command') + expect(JSON.stringify(messages)).not.toContain(finalText) + expect(await reopened.close()).toBe('graceful') + return + } + if (action === 'registration-failure') { + await expect.poll(() => firstResult).not.toBe('') + expect(firstResult).toContain('could not be registered') + expect(firstResult).not.toMatch(/bg_[A-Za-z0-9_-]{12}/) + restoreRegistry?.() + await expect + .poll(async () => + JSON.stringify( + await active.page.evaluate( + (sessionId) => + window.deepchat.invoke('sessions.listMessagesPage', { sessionId, limit: 100 }), + sessionId + ) + ) + ) + .toContain(finalText) + expect(await active.close()).toBe('graceful') + const reopened = await launchApp() + await waitForAppReady(reopened.page) + const persisted = await reopened.page.evaluate( + (sessionId) => + window.deepchat.invoke('sessions.listMessagesPage', { sessionId, limit: 100 }), + sessionId + ) + expect(JSON.stringify(persisted)).toContain('could not be registered') + expect(await reopened.close()).toBe('graceful') + return + } + let registeredPid: number | undefined + if (action === 'exec-transition') { + await expect.poll(() => firstResult).toMatch(/bg_[A-Za-z0-9_-]{12}/) + const registry = join(active.userDataDir, 'child-processes', 'background-exec') + const record = JSON.parse( + readFileSync( + join(registry, readdirSync(registry).find((name) => name.endsWith('.json'))!), + 'utf8' + ) + ) + registeredPid = record.pid + await testInfo.attach('exec-transition-registry.json', { + body: JSON.stringify(record), + contentType: 'application/json' + }) + writeFileSync(execFile, 'exec') + } + await expect.poll(() => existsSync(startedFile)).toBe(true) + const pid = Number(readFileSync(startedFile, 'utf8')) + if (action === 'exec-transition') expect(pid).toBe(registeredPid) + const alive = (targetPid = pid) => { + try { + process.kill(targetPid, 0) + return true + } catch (error) { + if ((error as NodeJS.ErrnoException).code === 'ESRCH') return false + throw error + } + } + const descendantPid = hasDescendant ? Number(readFileSync(descendantFile, 'utf8')) : undefined + if (descendantPid) { + expect(alive(descendantPid)).toBe(true) + drainReleasedCommand = async () => { + await expect.poll(() => alive(descendantPid)).toBe(false) + await expect.poll(alive).toBe(false) + } + } + expect(alive()).toBe(true) + await expect.poll(() => firstResult).toMatch(/bg_[A-Za-z0-9_-]{12}/) + if ( + action === 'helper-loss' || + action === 'exec-transition' || + action === 'group-helper-loss' + ) { + await active.electronApp.evaluate(() => + ( + globalThis as unknown as { __backgroundSpawnFixture: { crash(): void } } + ).__backgroundSpawnFixture.crash() + ) + await expect.poll(alive).toBe(false) + if (descendantPid) await expect.poll(() => alive(descendantPid)).toBe(false) + expect(existsSync(doneFile)).toBe(false) + } + if (action === 'complete' || action === 'environment') { + if (action === 'environment') { + expect(JSON.parse(readFileSync(environmentFile, 'utf8')).proxy).toBe(proxyUrl) + } + writeFileSync(releaseFile, 'release') + await expect.poll(() => existsSync(doneFile)).toBe(true) + await expect.poll(alive).toBe(false) + releasePoll() + await expect.poll(() => processResult).toContain('BACKGROUND_EXEC_DONE') + if (action === 'environment') { + await active.page.evaluate(() => + window.deepchat.invoke('config.setProxyMode', { mode: 'none' }) + ) + releaseEnvironment() + await expect + .poll(() => JSON.parse(readFileSync(environmentFile, 'utf8'))) + .toEqual({ proxy: null, utility: null }) + } + } + if (rootExits) { + if (action === 'escaped-root-exit') { + const group = (target: number) => + Number( + execFileSync('/bin/ps', ['-o', 'pgid=', '-p', String(target)], { + encoding: 'utf8', + timeout: 5000 + }).trim() + ) + expect(group(pid)).toBe(pid) + expect(group(descendantPid!)).toBe(descendantPid) + expect(descendantPid).not.toBe(pid) + } + writeFileSync(rootExitFile, 'exit') + await expect.poll(alive).toBe(false) + await expect.poll(() => alive(descendantPid)).toBe(false) + expect(existsSync(doneFile)).toBe(false) + releasePoll() + await expect.poll(() => processResult).toContain('ROOT_EXITED') + const polled = JSON.parse(JSON.parse(processResult).at(-1).content) + expect(polled).toMatchObject({ status: 'done', exitCode: 0 }) + } + const messages = () => + active.page.evaluate( + (sessionId) => + window.deepchat.invoke('sessions.listMessagesPage', { sessionId, limit: 100 }), + sessionId + ) + await expect.poll(async () => JSON.stringify(await messages())).toContain(finalText) + if (action === 'environment') { + expect(JSON.parse(processResult).at(-1).content).toContain('BACKGROUND_EXEC_DONE') + await expect.poll(() => alive(Number(readFileSync(startedFile, 'utf8')))).toBe(false) + } + expect(await active.close()).toBe('graceful') + await expect.poll(alive).toBe(false) + if (descendantPid) await expect.poll(() => alive(descendantPid)).toBe(false) + if (action === 'shutdown') expect(existsSync(doneFile)).toBe(false) + if (hasDescendant) expect(existsSync(doneFile)).toBe(false) + const reopened = await launchApp() + await waitForAppReady(reopened.page) + const persisted = await reopened.page.evaluate( + (sessionId) => + window.deepchat.invoke('sessions.listMessagesPage', { sessionId, limit: 100 }), + sessionId + ) + expect(JSON.stringify(persisted)).toContain(finalText) + expect(await reopened.close()).toBe('graceful') + } finally { + restoreRegistry?.() + releaseObservation?.() + releaseCommand?.() + await drainReleasedCommand?.() + releasePoll() + releaseEnvironment() + server.closeAllConnections() + await new Promise((resolve) => server.close(() => resolve())) + } + }) +} diff --git a/test/e2e/specs/61-code-mode-runtime.smoke.spec.ts b/test/e2e/specs/61-code-mode-runtime.smoke.spec.ts new file mode 100644 index 0000000000..10cf644142 --- /dev/null +++ b/test/e2e/specs/61-code-mode-runtime.smoke.spec.ts @@ -0,0 +1,244 @@ +import { createServer } from 'node:http' +import { test, expect } from '../fixtures/electronApp' +import { waitForAppReady } from '../helpers/wait' + +test.use({ privacyModeEnabled: true }) + +for (const action of ['complete', 'held-ready'] as const) { + test(`Code Mode preserves nested execution and ${action} ownership @smoke`, async ({ + launchApp + }) => { + const executable = + process.platform === 'win32' + ? `"${process.execPath}"` + : `'${process.execPath.replaceAll("'", "'\\''")}'` + const command = `${executable} -e "process.stdout.write('NESTED_CODE_DONE')"` + const firstCode = `const result = await tools.exec({ command: ${JSON.stringify(command)}, description: 'Verify nested command execution' }); const earlierLocal = 'cell-local'; console.log('CODE_MODE_ONE'); return result` + const secondCode = `return { earlierLocalType: typeof earlierLocal, processType: typeof process, requireType: typeof require }` + let toolResults: string[] = [] + const server = createServer(async (request, response) => { + if (request.url === '/v1/models') { + response + .writeHead(200, { 'Content-Type': 'application/json' }) + .end(JSON.stringify({ data: [{ id: 'code-fixture' }] })) + return + } + if (request.url !== '/v1/chat/completions') { + response.writeHead(404).end() + return + } + let source = '' + for await (const chunk of request) source += chunk + const body = JSON.parse(source) + if (!body.stream) { + response.writeHead(200, { 'Content-Type': 'application/json' }).end( + JSON.stringify({ + id: 'title', + object: 'chat.completion', + created: 1, + model: 'code-fixture', + choices: [ + { + index: 0, + message: { role: 'assistant', content: 'Code Mode verification' }, + finish_reason: 'stop' + } + ] + }) + ) + return + } + const results = body.messages.filter((message: { role: string }) => message.role === 'tool') + toolResults = results.map((message: { content: unknown }) => JSON.stringify(message.content)) + const complete = results.length >= 2 + const delta = complete + ? { content: 'CODE_MODE_VERIFIED' } + : { + role: 'assistant', + tool_calls: [ + { + index: 0, + id: `owned-code-${results.length}`, + type: 'function', + function: { + name: 'run_code', + arguments: JSON.stringify({ + code: results.length ? secondCode : firstCode, + description: 'Verify isolated code execution' + }) + } + } + ] + } + response.writeHead(200, { 'Content-Type': 'text/event-stream' }) + response.write( + `data: ${JSON.stringify({ + id: 'code-fixture', + object: 'chat.completion.chunk', + created: 1, + model: 'code-fixture', + choices: [{ index: 0, delta, finish_reason: complete ? 'stop' : 'tool_calls' }] + })}\n\n` + ) + response.end('data: [DONE]\n\n') + }) + await new Promise((resolve) => server.listen(0, '127.0.0.1', resolve)) + const { port } = server.address() as { port: number } + try { + const active = await launchApp() + expect(active.ownsUserDataDir).toBe(true) + await waitForAppReady(active.page) + if (action === 'held-ready') { + await active.electronApp.evaluate(({ utilityProcess }) => { + const fork = utilityProcess.fork + const fixture = { entered: false, pid: undefined as number | undefined } + utilityProcess.fork = (entry, args, options) => { + const host = fork.call(utilityProcess, entry, args, options) + if (entry.endsWith('codeModeUtilityHost.js')) { + const on = host.on.bind(host) as ( + event: string, + listener: (...values: unknown[]) => void + ) => typeof host + host.on = ((event: string, listener: (...values: unknown[]) => void) => { + if (event === 'message') + return on(event, (message) => { + if ((message as { type?: string })?.type === 'READY') { + fixture.entered = true + fixture.pid = host.pid + } else listener(message) + }) + return on(event, listener) + }) as typeof host.on + } + return host + } + Object.assign(globalThis, { __codeModeReadyFixture: fixture }) + }) + } + const sessionId = await active.page.evaluate( + async ({ baseUrl, projectDir }) => { + const providerId = `custom-${crypto.randomUUID()}` + await window.deepchat.invoke('providers.add', { + provider: { + id: providerId, + name: 'Code fixture', + apiType: 'openai-completions', + baseUrl, + apiKey: 'fixture-key', + enable: true, + custom: true + } + }) + await window.deepchat.invoke('models.addCustom', { + providerId, + model: { + id: 'code-fixture', + name: 'Code fixture', + enabled: true, + vision: false, + functionCall: true, + reasoning: false, + contextLength: 32000, + maxTokens: 8000 + } + }) + await window.deepchat.invoke('models.setStatus', { + providerId, + modelId: 'code-fixture', + enabled: true + }) + const { config } = await window.deepchat.invoke('config.resolveDeepChatAgentConfig', { + agentId: 'deepchat' + }) + await window.deepchat.invoke('config.updateDeepChatAgent', { + agentId: 'deepchat', + updates: { + config: { + ...config, + permissionMode: 'full_access', + disabledAgentTools: [], + enabledSkillNames: [], + memoryEnabled: false + } + } + }) + const { session } = await window.deepchat.invoke('sessions.create', { + agentId: 'deepchat', + message: 'Verify Code Mode nested execution and isolated session state.', + providerId, + modelId: 'code-fixture', + projectDir, + permissionMode: 'full_access', + toolModeOverride: 'code' + }) + return session.id + }, + { baseUrl: `http://127.0.0.1:${port}/v1`, projectDir: active.userDataDir } + ) + if (action === 'held-ready') { + await expect + .poll(() => + active.electronApp.evaluate( + () => + (globalThis as unknown as { __codeModeReadyFixture: { entered: boolean } }) + .__codeModeReadyFixture.entered + ) + ) + .toBe(true) + const pid = await active.electronApp.evaluate( + () => + (globalThis as unknown as { __codeModeReadyFixture: { pid?: number } }) + .__codeModeReadyFixture.pid + ) + expect(pid).toBeGreaterThan(0) + expect(await active.close()).toBe('graceful') + await expect + .poll(() => { + try { + process.kill(pid!, 0) + return true + } catch (error) { + if ((error as NodeJS.ErrnoException).code === 'ESRCH') return false + throw error + } + }) + .toBe(false) + expect(toolResults).toHaveLength(0) + } else { + await expect.poll(() => toolResults.length).toBe(2) + expect(toolResults[0]).toContain('NESTED_CODE_DONE') + expect(JSON.parse(JSON.parse(toolResults[1]))).toEqual({ + earlierLocalType: 'undefined', + processType: 'undefined', + requireType: 'undefined' + }) + await expect + .poll(async () => + JSON.stringify( + await active.page.evaluate( + (sessionId) => + window.deepchat.invoke('sessions.listMessagesPage', { sessionId, limit: 100 }), + sessionId + ) + ) + ) + .toContain('CODE_MODE_VERIFIED') + expect(await active.close()).toBe('graceful') + } + const reopened = await launchApp() + await waitForAppReady(reopened.page) + const { messages } = await reopened.page.evaluate( + (sessionId) => + window.deepchat.invoke('sessions.listMessagesPage', { sessionId, limit: 100 }), + sessionId + ) + expect(JSON.stringify(messages)).toContain( + action === 'complete' ? 'CODE_MODE_VERIFIED' : 'Verify Code Mode nested execution' + ) + expect(await reopened.close()).toBe('graceful') + } finally { + server.closeAllConnections() + await new Promise((resolve) => server.close(() => resolve())) + } + }) +} diff --git a/test/e2e/specs/62-agent-tool-host.smoke.spec.ts b/test/e2e/specs/62-agent-tool-host.smoke.spec.ts new file mode 100644 index 0000000000..5f5ac6ab51 --- /dev/null +++ b/test/e2e/specs/62-agent-tool-host.smoke.spec.ts @@ -0,0 +1,197 @@ +import { createServer } from 'node:http' +import { mkdirSync, readFileSync, writeFileSync } from 'node:fs' +import { join } from 'node:path' +import { test, expect } from '../fixtures/electronApp' +import { waitForAppReady } from '../helpers/wait' + +test.use({ privacyModeEnabled: true }) + +test('Agent tools preserve file access and settings navigation across reopen @smoke', async ({ + launchApp +}) => { + const image = readFileSync(new URL('../../../resources/icon.png', import.meta.url)) + const resolution = `${image.readUInt32BE(16)}x${image.readUInt32BE(20)}` + let imagePath = '' + let toolResults: string[] = [] + const server = createServer(async (request, response) => { + if (request.url === '/v1/models') { + response + .writeHead(200, { 'Content-Type': 'application/json' }) + .end(JSON.stringify({ data: [{ id: 'host-fixture' }] })) + return + } + if (request.url !== '/v1/chat/completions') { + response.writeHead(404).end() + return + } + let source = '' + for await (const chunk of request) source += chunk + const body = JSON.parse(source) + if (!body.stream) { + response.writeHead(200, { 'Content-Type': 'application/json' }).end( + JSON.stringify({ + id: 'title', + object: 'chat.completion', + created: 1, + model: 'host-fixture', + choices: [ + { + index: 0, + message: { role: 'assistant', content: 'Agent host verification' }, + finish_reason: 'stop' + } + ] + }) + ) + return + } + const results = body.messages.filter((message: { role: string }) => message.role === 'tool') + toolResults = results.map((message: { content: unknown }) => JSON.stringify(message.content)) + const complete = results.length >= 3 + const openingSettings = results.length === 2 + const delta = complete + ? { content: 'AGENT_HOST_VERIFIED' } + : { + role: 'assistant', + tool_calls: [ + { + index: 0, + id: `host-read-${results.length}`, + type: 'function', + function: { + name: openingSettings ? 'deepchat_settings_open' : 'read', + arguments: JSON.stringify( + openingSettings + ? { section: 'mcp' } + : { path: results.length ? 'workspace.txt' : imagePath } + ) + } + } + ] + } + response.writeHead(200, { 'Content-Type': 'text/event-stream' }) + response.write( + `data: ${JSON.stringify({ + id: 'host-fixture', + object: 'chat.completion.chunk', + created: 1, + model: 'host-fixture', + choices: [{ index: 0, delta, finish_reason: complete ? 'stop' : 'tool_calls' }] + })}\n\n` + ) + response.end('data: [DONE]\n\n') + }) + await new Promise((resolve) => server.listen(0, '127.0.0.1', resolve)) + const { port } = server.address() as { port: number } + try { + const active = await launchApp() + expect(active.ownsUserDataDir).toBe(true) + await waitForAppReady(active.page) + const projectDir = join(active.userDataDir, 'workspace') + const temporary = join(active.userDataDir, 'temp') + mkdirSync(projectDir, { recursive: true }) + mkdirSync(temporary, { recursive: true }) + imagePath = join(temporary, 'host-image.png') + writeFileSync(imagePath, image) + writeFileSync(join(projectDir, 'workspace.txt'), 'WORKSPACE_HOST_READ_VERIFIED') + const sessionId = await active.page.evaluate( + async ({ baseUrl, projectDir }) => { + const providerId = `custom-${crypto.randomUUID()}` + await window.deepchat.invoke('providers.add', { + provider: { + id: providerId, + name: 'Agent host fixture', + apiType: 'openai-completions', + baseUrl, + apiKey: 'fixture-key', + enable: true, + custom: true + } + }) + await window.deepchat.invoke('models.addCustom', { + providerId, + model: { + id: 'host-fixture', + name: 'Agent host fixture', + enabled: true, + vision: false, + functionCall: true, + reasoning: false, + contextLength: 32000, + maxTokens: 8000 + } + }) + await window.deepchat.invoke('models.setStatus', { + providerId, + modelId: 'host-fixture', + enabled: true + }) + const { config } = await window.deepchat.invoke('config.resolveDeepChatAgentConfig', { + agentId: 'deepchat' + }) + await window.deepchat.invoke('config.updateDeepChatAgent', { + agentId: 'deepchat', + updates: { + config: { + ...config, + permissionMode: 'full_access', + disabledAgentTools: [], + enabledSkillNames: [], + memoryEnabled: false + } + } + }) + await window.deepchat.invoke('skills.setAssignments', { + agentId: 'deepchat', + skillNames: ['deepchat-settings'] + }) + const { session } = await window.deepchat.invoke('sessions.create', { + agentId: 'deepchat', + message: + 'Verify image metadata and workspace file access, then open DeepChat MCP settings.', + providerId, + modelId: 'host-fixture', + projectDir, + permissionMode: 'full_access', + toolModeOverride: 'agent', + activeSkills: ['deepchat-settings'] + }) + return session.id + }, + { baseUrl: `http://127.0.0.1:${port}/v1`, projectDir } + ) + await expect.poll(() => toolResults.length).toBe(3) + expect(toolResults[0]).toContain('[Image Metadata]') + expect(toolResults[0]).toContain(`resolution: ${resolution}`) + expect(toolResults[0]).toContain('neither the current session model nor the agent vision model') + expect(toolResults[1]).toContain('WORKSPACE_HOST_READ_VERIFIED') + expect(toolResults[2]).toContain('settings-mcp') + await expect + .poll(() => active.electronApp.windows().some((page) => page.url().endsWith('#/mcp'))) + .toBe(true) + await expect + .poll(async () => + JSON.stringify( + await active.page.evaluate( + (sessionId) => + window.deepchat.invoke('sessions.listMessagesPage', { sessionId, limit: 100 }), + sessionId + ) + ) + ) + .toContain('AGENT_HOST_VERIFIED') + expect(await active.close()).toBe('graceful') + const reopened = await launchApp() + await waitForAppReady(reopened.page) + const { messages } = await reopened.page.evaluate( + (sessionId) => window.deepchat.invoke('sessions.listMessagesPage', { sessionId, limit: 100 }), + sessionId + ) + expect(JSON.stringify(messages)).toContain('AGENT_HOST_VERIFIED') + expect(JSON.stringify(messages)).toContain('WORKSPACE_HOST_READ_VERIFIED') + expect(await reopened.close()).toBe('graceful') + } finally { + server.closeAllConnections() + await new Promise((resolve) => server.close(() => resolve())) + } +}) diff --git a/test/e2e/specs/63-plugin-tool-policy.smoke.spec.ts b/test/e2e/specs/63-plugin-tool-policy.smoke.spec.ts new file mode 100644 index 0000000000..a57ef6be87 --- /dev/null +++ b/test/e2e/specs/63-plugin-tool-policy.smoke.spec.ts @@ -0,0 +1,141 @@ +import { createServer } from 'node:http' +import { existsSync, readFileSync, writeFileSync } from 'node:fs' +import { join } from 'node:path' +import { test, expect } from '../fixtures/electronApp' +import { openSettings } from '../helpers/settings' +import { waitForAppReady } from '../helpers/wait' + +test.use({ privacyModeEnabled: true }) + +test('plugin policy preserves persisted decisions, ambiguity and disabled policies @smoke', async ({ + launchApp +}) => { + const calls: string[] = [] + const server = createServer(async (request, response) => { + if (request.method !== 'POST') { + response.writeHead(405).end() + return + } + let body = '' + for await (const chunk of request) body += chunk + const message = JSON.parse(body) + if (message.id === undefined) { + response.writeHead(202).end() + return + } + let result: unknown = {} + if (message.method === 'initialize') { + result = { + protocolVersion: message.params.protocolVersion, + capabilities: { tools: {} }, + serverInfo: { name: 'policy-fixture', version: '1.0.0' } + } + } else if (message.method === 'tools/list') { + result = { + tools: ['allowed', 'ask', 'blocked'].map((name) => ({ + name, + description: `Policy fixture ${name}`, + inputSchema: { type: 'object', properties: {} } + })) + } + } else if (message.method === 'tools/call') { + calls.push(message.params.name) + result = { content: [{ type: 'text', text: `POLICY_EXECUTED_${message.params.name}` }] } + } + response + .writeHead(200, { 'Content-Type': 'application/json' }) + .end(JSON.stringify({ jsonrpc: '2.0', id: message.id, result })) + }) + await new Promise((resolve) => server.listen(0, '127.0.0.1', resolve)) + const { port } = server.address() as { port: number } + const serverName = 'policy-owner-fixture' + const policy = { + pluginId: 'com.deepchat.policy-e2e', + serverId: serverName, + tools: { allowed: 'allow', ask: 'ask', blocked: 'deny' }, + enabled: true + } + try { + const bootstrap = await launchApp() + expect(bootstrap.ownsUserDataDir).toBe(true) + await waitForAppReady(bootstrap.page) + const settings = await openSettings(bootstrap) + await settings.evaluate( + async ({ serverName, baseUrl }) => { + await window.deepchat.invoke('mcp.setEnabled', { enabled: true }) + await window.deepchat.invoke('mcp.addServer', { + serverName, + config: { + type: 'http', + baseUrl, + enabled: true, + command: '', + args: [], + env: {}, + descriptions: 'Disposable policy fixture', + icons: '' + } + }) + }, + { serverName, baseUrl: `http://127.0.0.1:${port}/mcp` } + ) + const file = join(bootstrap.userDataDir, 'plugin-tool-policies.json') + expect(await bootstrap.close()).toBe('graceful') + for (const phase of ['single', 'ambiguous', 'disabled'] as const) { + const policies = + phase === 'ambiguous' + ? [policy, { ...policy, pluginId: 'com.deepchat.policy-e2e-other' }] + : [{ ...policy, enabled: phase !== 'disabled' }] + const previous = existsSync(file) ? JSON.parse(readFileSync(file, 'utf8')) : {} + writeFileSync(file, JSON.stringify({ ...previous, policies }, null, 2)) + const active = await launchApp() + await waitForAppReady(active.page) + const controls = await openSettings(active) + await controls.evaluate( + (serverName) => window.deepchat.invoke('mcp.startServer', { serverName }), + serverName + ) + await expect + .poll(async () => + controls.evaluate(async (serverName) => { + const { tools } = await window.deepchat.invoke('mcp.listToolDefinitions', {}) + return tools.filter((tool) => tool.server.name === serverName).length + }, serverName) + ) + .toBe(phase === 'single' ? 2 : 3) + const { tools } = await controls.evaluate(() => + window.deepchat.invoke('mcp.listToolDefinitions', {}) + ) + const definitions = tools.filter((tool) => tool.server.name === serverName) + const allowed = definitions.find((tool) => tool.raw?.name === 'allowed')! + expect(allowed).toBeTruthy() + const name = + phase === 'disabled' + ? definitions.find((tool) => tool.raw?.name === 'blocked')!.function.name + : allowed.function.name + const result = await controls.evaluate( + ({ name, id }) => + window.deepchat.invoke('mcp.callTool', { + request: { id, type: 'function', function: { name, arguments: '{}' } } + }), + { name, id: `policy-${phase}` } + ) + if (phase === 'ambiguous') { + expect(result.rawData.isError).toBe(true) + expect(result.content).toContain('not declared') + expect(calls).toEqual(['allowed']) + } else { + expect(result.rawData.isError).not.toBe(true) + expect(result.content).toContain( + phase === 'single' ? 'POLICY_EXECUTED_allowed' : 'POLICY_EXECUTED_blocked' + ) + } + expect(JSON.parse(readFileSync(file, 'utf8')).policies).toEqual(policies) + expect(await active.close()).toBe('graceful') + } + expect(calls).toEqual(['allowed', 'blocked']) + } finally { + server.closeAllConnections() + await new Promise((resolve) => server.close(() => resolve())) + } +}) diff --git a/test/e2e/specs/64-mcp-runtime.smoke.spec.ts b/test/e2e/specs/64-mcp-runtime.smoke.spec.ts new file mode 100644 index 0000000000..d5cb054e3e --- /dev/null +++ b/test/e2e/specs/64-mcp-runtime.smoke.spec.ts @@ -0,0 +1,175 @@ +import { mkdirSync, readFileSync, realpathSync, writeFileSync } from 'node:fs' +import { dirname, join } from 'node:path' +import { test, expect } from '../fixtures/electronApp' +import { openSettings } from '../helpers/settings' +import { waitForAppReady } from '../helpers/wait' + +test.use({ privacyModeEnabled: true }) + +test('MCP stdio preserves host identity, toolchain, environment and child cleanup @smoke', async ({ + launchApp +}) => { + const first = await launchApp() + expect(first.ownsUserDataDir).toBe(true) + await waitForAppReady(first.page) + const workspace = join(first.userDataDir, 'mcp-workspace') + const firstPath = join(workspace, 'first-path') + mkdirSync(firstPath, { recursive: true }) + const script = join(workspace, 'server.cjs') + const journal = join(workspace, 'journal.jsonl') + writeFileSync( + script, + `const fs = require('node:fs') +const readline = require('node:readline') +const record = (entry) => fs.appendFileSync(process.argv[2], JSON.stringify(entry) + '\\n') +const snapshot = () => ({ pid: process.pid, cwd: process.cwd(), executable: process.execPath, + homeArgument: process.argv[3], declared: process.env.MCP_DECLARED, + hostSecret: process.env.DEEPCHAT_MCP_HOST_SECRET ?? null, + path: process.env.PATH ?? process.env.Path ?? process.env.path }) +readline.createInterface({ input: process.stdin }).on('line', (line) => { + const message = JSON.parse(line) + if (message.id === undefined) return + let result = {} + if (message.method === 'initialize') { + record({ event: 'initialize', client: message.params.clientInfo, ...snapshot() }) + result = { protocolVersion: message.params.protocolVersion, capabilities: { tools: {} }, + serverInfo: { name: 'runtime-fixture', version: '1.0.0' } } + } else if (message.method === 'tools/list') { + result = { tools: [{ name: 'inspect_runtime', description: 'Inspect the owned runtime', + inputSchema: { type: 'object', properties: {} } }] } + } else if (message.method === 'tools/call') { + record({ event: 'call', ...snapshot() }) + result = { content: [{ type: 'text', text: JSON.stringify(snapshot()) }] } + } else if (message.method === 'prompts/list') result = { prompts: [] } + else if (message.method === 'resources/list') result = { resources: [] } + else if (message.method === 'resources/templates/list') result = { resourceTemplates: [] } + process.stdout.write(JSON.stringify({ jsonrpc: '2.0', id: message.id, result }) + '\\n') +}) +process.stdin.on('end', () => process.exit(0)) +` + ) + const expectedHost = await first.electronApp.evaluate(({ app }) => ({ + home: app.getPath('home'), + version: app.getVersion() + })) + await first.electronApp.evaluate(() => { + process.env.DEEPCHAT_MCP_HOST_SECRET = 'fixture-host-only' + }) + const serverName = 'runtime-host-fixture' + const controls = await openSettings(first) + await controls.evaluate( + async ({ node, serverName, script, journal, workspace, firstPath }) => { + await window.deepchat.invoke('toolchains.setSource', { + kind: 'node', + selection: { source: 'custom', customPath: node } + }) + await window.deepchat.invoke('mcp.setEnabled', { enabled: true }) + await window.deepchat.invoke('mcp.addServer', { + serverName, + config: { + type: 'stdio', + enabled: true, + command: 'node', + args: [script, journal, '~/mcp-runtime-marker'], + cwd: workspace, + inheritEnv: 'minimal', + env: { MCP_DECLARED: 'owned-value', PATH: firstPath }, + descriptions: 'Disposable runtime fixture', + icons: '' + } + }) + }, + { node: process.execPath, serverName, script, journal, workspace, firstPath } + ) + const alive = (pid: number) => { + try { + process.kill(pid, 0) + return true + } catch (error) { + if ((error as NodeJS.ErrnoException).code === 'ESRCH') return false + throw error + } + } + const records = () => + readFileSync(journal, 'utf8') + .trim() + .split('\n') + .map((line) => JSON.parse(line)) as Array<{ + event: string + pid: number + client: { name: string; version: string } + executable: string + homeArgument: string + cwd: string + declared: string + hostSecret: string | null + path: string + }> + for (const phase of ['first', 'reopen'] as const) { + const active = phase === 'first' ? first : await launchApp() + await waitForAppReady(active.page) + await active.electronApp.evaluate(() => { + process.env.DEEPCHAT_MCP_HOST_SECRET = 'fixture-host-only' + }) + const settings = phase === 'first' ? controls : await openSettings(active) + await settings.evaluate( + (serverName) => window.deepchat.invoke('mcp.startServer', { serverName }), + serverName + ) + const { tools } = await settings.evaluate(() => + window.deepchat.invoke('mcp.listToolDefinitions', {}) + ) + const tool = tools.find((entry) => entry.server.name === serverName)! + expect(tool).toBeTruthy() + const result = await settings.evaluate( + (name) => + window.deepchat.invoke('mcp.callTool', { + request: { + id: 'inspect-runtime', + type: 'function', + function: { name, arguments: '{}' } + } + }), + tool.function.name + ) + expect(result.rawData.isError).not.toBe(true) + const current = records() + .filter((entry) => entry.event === 'call') + .at(-1)! + expect(current).toMatchObject({ + executable: realpathSync(process.execPath), + homeArgument: `${expectedHost.home}/mcp-runtime-marker`, + cwd: realpathSync(workspace), + declared: 'owned-value', + hostSecret: null + }) + expect(current.path.startsWith(firstPath)).toBe(true) + expect(current.path).toContain(dirname(process.execPath)) + expect(result.content).toContain('mcp-runtime-marker') + expect( + records() + .filter((entry) => entry.event === 'initialize') + .at(-1)?.client + ).toEqual({ + name: 'DeepChat', + version: expectedHost.version + }) + expect(records().every((entry) => entry.hostSecret === null)).toBe(true) + if (phase === 'first') { + await settings.evaluate( + (serverName) => window.deepchat.invoke('mcp.stopServer', { serverName }), + serverName + ) + await expect.poll(() => alive(current.pid)).toBe(false) + await settings.evaluate( + (serverName) => window.deepchat.invoke('mcp.startServer', { serverName }), + serverName + ) + } + const pids = records().map((entry) => entry.pid) + expect(await active.close()).toBe('graceful') + for (const pid of new Set(pids)) await expect.poll(() => alive(pid)).toBe(false) + expect(active.pageErrors).toEqual([]) + } + expect(records().filter((entry) => entry.event === 'call')).toHaveLength(2) +}) diff --git a/test/e2e/specs/65-mcp-app-host.smoke.spec.ts b/test/e2e/specs/65-mcp-app-host.smoke.spec.ts new file mode 100644 index 0000000000..f369536825 --- /dev/null +++ b/test/e2e/specs/65-mcp-app-host.smoke.spec.ts @@ -0,0 +1,370 @@ +import { createServer } from 'node:http' +import type { McpAppDescriptor } from '../../../src/shared/types/mcp' +import type { AssistantMessageBlock } from '../../../src/shared/types/agent-interface' +import { test, expect } from '../fixtures/electronApp' +import { openSettings } from '../helpers/settings' +import { waitForAppReady } from '../helpers/wait' + +test.use({ privacyModeEnabled: true }) + +test('MCP Apps preserve saved sources, native sandbox policy, consent and reopen @smoke', async ({ + launchApp +}) => { + const serverName = 'app-host-fixture' + const resourceUri = 'ui://fixture/index.html' + let renderName = '' + const calls: string[] = [] + const server = createServer(async (request, response) => { + let source = '' + for await (const chunk of request) source += chunk + const body = source ? JSON.parse(source) : {} + if (request.url === '/mcp') { + if (body.id === undefined) { + response.writeHead(202).end() + return + } + let result: unknown = {} + if (body.method === 'initialize') + result = { + protocolVersion: body.params.protocolVersion, + capabilities: { tools: {}, resources: {} }, + serverInfo: { name: serverName, version: '1.0.0' } + } + else if (body.method === 'tools/list') + result = { + tools: ['render_app', 'app_action'].map((name) => ({ + name, + description: `App fixture ${name}`, + inputSchema: { type: 'object', properties: {} }, + annotations: { readOnlyHint: true }, + _meta: { + ui: { + ...(name === 'render_app' ? { resourceUri } : {}), + visibility: name === 'render_app' ? ['model', 'app'] : ['app'] + } + } + })) + } + else if (body.method === 'tools/call') { + calls.push(body.params.name) + result = { + content: [{ type: 'text', text: `APP_RESULT_${body.params.name}` }], + structuredContent: { value: 42 } + } + } else if (body.method === 'resources/read') + result = { + contents: [ + { + uri: resourceUri, + mimeType: 'text/html;profile=mcp-app', + text: '
APP_HOST_CONTENT
', + _meta: { + ui: { + permissions: { clipboardWrite: {} }, + csp: { connectDomains: ['https://api.example.com'] } + } + } + } + ] + } + else if (body.method === 'resources/list') result = { resources: [] } + else if (body.method === 'resources/templates/list') result = { resourceTemplates: [] } + response + .writeHead(200, { 'Content-Type': 'application/json' }) + .end(JSON.stringify({ jsonrpc: '2.0', id: body.id, result })) + return + } + if (request.url === '/v1/models') { + response + .writeHead(200, { 'Content-Type': 'application/json' }) + .end(JSON.stringify({ data: [{ id: 'app-fixture' }] })) + return + } + if (request.url !== '/v1/chat/completions') { + response.writeHead(404).end() + return + } + if (!body.stream) { + response.writeHead(200, { 'Content-Type': 'application/json' }).end( + JSON.stringify({ + id: 'title', + object: 'chat.completion', + created: 1, + model: 'app-fixture', + choices: [ + { + index: 0, + message: { role: 'assistant', content: 'App fixture' }, + finish_reason: 'stop' + } + ] + }) + ) + return + } + const complete = body.messages.some((entry: { role: string }) => entry.role === 'tool') + const delta = complete + ? { content: 'APP_HOST_COMPLETE' } + : { + role: 'assistant', + tool_calls: [ + { + index: 0, + id: 'render-app', + type: 'function', + function: { name: renderName, arguments: '{}' } + } + ] + } + response.writeHead(200, { 'Content-Type': 'text/event-stream' }) + response.write( + `data: ${JSON.stringify({ id: 'app-fixture', object: 'chat.completion.chunk', created: 1, model: 'app-fixture', choices: [{ index: 0, delta, finish_reason: complete ? 'stop' : 'tool_calls' }] })}\n\n` + ) + response.end('data: [DONE]\n\n') + }) + await new Promise((resolve) => server.listen(0, '127.0.0.1', resolve)) + const { port } = server.address() as { port: number } + try { + const first = await launchApp() + expect(first.ownsUserDataDir).toBe(true) + await waitForAppReady(first.page) + const settings = await openSettings(first) + const configured = await settings.evaluate( + async ({ baseUrl, serverName }) => { + await window.deepchat.invoke('mcp.setEnabled', { enabled: true }) + await window.deepchat.invoke('mcp.addServer', { + serverName, + config: { + type: 'http', + baseUrl: `${baseUrl}/mcp`, + enabled: true, + command: '', + args: [], + env: {}, + descriptions: 'Owned App fixture', + icons: '' + } + }) + await window.deepchat.invoke('mcp.startServer', { serverName }) + const { servers } = await window.deepchat.invoke('mcp.getServers', {}) + const { tools } = await window.deepchat.invoke('mcp.listToolDefinitions', {}) + return { + serverId: servers[serverName].serverId!, + name: tools.find( + (tool) => tool.server.name === serverName && tool.raw?.name === 'render_app' + )!.function.name + } + }, + { baseUrl: `http://127.0.0.1:${port}`, serverName } + ) + renderName = configured.name + const sessionId = await first.page.evaluate( + async ({ baseUrl, serverName }) => { + const providerId = `custom-${crypto.randomUUID()}` + await window.deepchat.invoke('providers.add', { + provider: { + id: providerId, + name: 'App fixture', + apiType: 'openai-completions', + baseUrl, + apiKey: 'fixture-key', + enable: true, + custom: true + } + }) + await window.deepchat.invoke('models.addCustom', { + providerId, + model: { + id: 'app-fixture', + name: 'App fixture', + enabled: true, + vision: false, + functionCall: true, + reasoning: false, + contextLength: 32000, + maxTokens: 8000 + } + }) + await window.deepchat.invoke('models.setStatus', { + providerId, + modelId: 'app-fixture', + enabled: true + }) + const { config } = await window.deepchat.invoke('config.resolveDeepChatAgentConfig', { + agentId: 'deepchat' + }) + await window.deepchat.invoke('config.updateDeepChatAgent', { + agentId: 'deepchat', + updates: { + config: { + ...config, + permissionMode: 'full_access', + disabledAgentTools: [], + enabledSkillNames: [], + enabledMcpServerIds: [serverName], + memoryEnabled: false + } + } + }) + const { session } = await window.deepchat.invoke('sessions.create', { + agentId: 'deepchat', + message: 'Render the App fixture.', + providerId, + modelId: 'app-fixture', + permissionMode: 'full_access', + toolModeOverride: 'agent' + }) + return session.id + }, + { baseUrl: `http://127.0.0.1:${port}/v1`, serverName } + ) + await expect + .poll(async () => + JSON.stringify( + await first.page.evaluate( + (sessionId) => + window.deepchat.invoke('sessions.listMessagesPage', { sessionId, limit: 100 }), + sessionId + ) + ) + ) + .toContain('APP_HOST_COMPLETE') + const saved = await first.page.evaluate(async (sessionId) => { + const { messages } = await window.deepchat.invoke('sessions.listMessagesPage', { + sessionId, + limit: 100 + }) + for (const message of messages) { + if (message.role !== 'assistant') continue + const blocks = JSON.parse(message.content) as AssistantMessageBlock[] + const block = blocks.find((block) => block.tool_call?.mcpResult?.app) + if (block) + return { + messageId: message.id, + blockId: block.id || block.tool_call?.id || '', + descriptor: block.tool_call!.mcpResult!.app!, + toolInput: JSON.parse(block.tool_call!.params ?? '{}') as Record + } + } + throw new Error(`Persisted App result missing: ${JSON.stringify(messages).slice(0, 16384)}`) + }, sessionId) + const sourceInput = { + ...saved, + conversationId: sessionId, + descriptor: saved.descriptor as McpAppDescriptor + } + for (const phase of ['first', 'reopen'] as const) { + const active = phase === 'first' ? first : await launchApp() + await waitForAppReady(active.page) + await active.electronApp.evaluate(({ shell }) => { + const links: string[] = [] + shell.openExternal = async (url) => { + links.push(url) + } + Object.assign(globalThis, { __appHostLinks: links }) + }) + const { view } = await active.page.evaluate( + (input) => window.deepchat.invoke('mcp.apps.prepareView', input), + sourceInput + ) + expect(view.html).toContain('APP_HOST_CONTENT') + const policy = await active.electronApp.evaluate(async ({ net }, url) => { + const response = await net.fetch(url) + return { + status: response.status, + csp: response.headers.get('content-security-policy'), + permissions: response.headers.get('permissions-policy') + } + }, view.sandboxUrl) + expect(policy.status).toBe(200) + expect(policy.csp).toContain("default-src 'none'") + expect(policy.csp).toContain('connect-src https://api.example.com') + expect(policy.permissions).toContain('camera=()') + expect(policy.permissions).toContain('clipboard-write=(self)') + const { call } = await active.page.evaluate( + (instanceId) => + window.deepchat.invoke('mcp.apps.callTool', { + instanceId, + name: 'app_action', + arguments: {} + }), + view.instanceId + ) + expect(call.result.content).toEqual([{ type: 'text', text: 'APP_RESULT_app_action' }]) + if (phase === 'first') { + await expect( + settings.evaluate( + (input) => window.deepchat.invoke('mcp.apps.prepareView', input), + sourceInput + ) + ).rejects.toThrow('conversation windows') + for (const approved of [false, true]) { + await active.page.evaluate((instanceId) => { + Object.assign(window, { + __appHostPending: window.deepchat.invoke('mcp.apps.openLink', { + instanceId, + url: 'https://example.com/fixture' + }) + }) + }, view.instanceId) + const dialog = active.page.getByRole('dialog') + await expect(dialog).toBeVisible() + await dialog + .getByRole('button', { + name: approved ? /Allow once|允许一次|允許一次|仅此一次|僅此一次/ : /Deny|拒绝|拒絕/ + }) + .click() + expect( + await active.page.evaluate( + async () => + await (window as unknown as { __appHostPending: Promise }).__appHostPending + ) + ).toEqual({ opened: approved }) + } + expect( + await active.electronApp.evaluate( + () => (globalThis as unknown as { __appHostLinks: string[] }).__appHostLinks + ) + ).toEqual(['https://example.com/fixture']) + await active.page.evaluate((instanceId) => { + Object.assign(window, { + __appHostPending: window.deepchat + .invoke('mcp.apps.authorizeMessage', { instanceId, text: 'Owned pending consent' }) + .catch(() => undefined) + }) + }, view.instanceId) + await expect(active.page.getByRole('dialog')).toBeVisible() + } else { + await active.page.evaluate( + ({ sessionId, messageId }) => + window.deepchat.invoke('sessions.deleteMessage', { sessionId, messageId }), + { sessionId, messageId: saved.messageId } + ) + await expect( + active.page.evaluate( + (instanceId) => window.deepchat.invoke('mcp.apps.listTools', { instanceId }), + view.instanceId + ) + ).rejects.toThrow('unavailable') + expect( + await active.electronApp.evaluate( + async ({ net }, url) => (await net.fetch(url)).status, + view.sandboxUrl + ) + ).toBe(410) + await expect( + active.page.evaluate( + (input) => window.deepchat.invoke('mcp.apps.prepareView', input), + sourceInput + ) + ).rejects.toThrow('source no longer matches') + } + expect(await active.close()).toBe('graceful') + expect(active.pageErrors).toEqual([]) + } + expect(calls).toEqual(['render_app', 'app_action', 'app_action']) + } finally { + server.closeAllConnections() + await new Promise((resolve) => server.close(() => resolve())) + } +}) diff --git a/test/e2e/specs/66-project-host.smoke.spec.ts b/test/e2e/specs/66-project-host.smoke.spec.ts new file mode 100644 index 0000000000..a9c116f643 --- /dev/null +++ b/test/e2e/specs/66-project-host.smoke.spec.ts @@ -0,0 +1,212 @@ +import { existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from 'node:fs' +import { join } from 'node:path' +import { test, expect } from '../fixtures/electronApp' +import { waitForAppReady } from '../helpers/wait' + +test.use({ privacyModeEnabled: true }) + +test('native pickers retire queued requests and close with their app generation @smoke', async ({ + launchApp +}) => { + test.skip(process.platform !== 'darwin', 'Exercises actual macOS native sheet retirement.') + for (const kind of ['directory', 'image'] as const) { + const active = await launchApp() + await waitForAppReady(active.page) + await active.electronApp.evaluate(({ dialog }, kind) => { + const state = { opened: 0, settled: 0 } + Reflect.set(globalThis, '__nativePickerFixture', state) + const method = kind === 'directory' ? 'showOpenDialog' : 'showSaveDialog' + const original = dialog[method] + Reflect.set(dialog, method, (...args: unknown[]) => { + state.opened++ + return Reflect.apply(original, dialog, args).finally(() => { + state.settled++ + }) + }) + }, kind) + const pending = active.page + .evaluate(async (kind) => { + if (kind === 'directory') + await Promise.all([ + window.deepchat.invoke('project.selectDirectory', {}), + window.deepchat.invoke('project.selectDirectory', {}) + ]) + else + await window.deepchat.invoke('file.saveImage', { + source: 'data:image/png;base64,aW1hZ2U=' + }) + }, kind) + .catch(() => undefined) + await expect + .poll(() => + active.electronApp.evaluate(() => { + const state = Reflect.get(globalThis, '__nativePickerFixture') as { + opened: number + settled: number + } + return state.opened > 0 && state.settled === 0 + }) + ) + .toBe(true) + expect(await active.close()).toBe('graceful') + await pending + } +}) + +test('project host preserves workspace, native operations and durable versions @smoke', async ({ + launchApp +}) => { + let initialWorkspace = '' + let active = await launchApp({ + prepareUserData: (profile) => { + initialWorkspace = join(profile, 'initial-workspace') + mkdirSync(initialWorkspace) + const settingsFile = join(profile, 'app-settings.json') + const settings = JSON.parse(readFileSync(settingsFile, 'utf8')) + settings.defaultProjectPath = initialWorkspace + writeFileSync(settingsFile, JSON.stringify(settings)) + } + }) + await waitForAppReady(active.page) + const profile = active.userDataDir + const snapshot = () => + active.page.evaluate(() => window.deepchat.invoke('project.getSnapshot', {})) + expect((await snapshot()).defaultProjectPath).toBe(initialWorkspace) + + const managedWorkspace = join(profile, 'workspaces', 'DeepChat') + await active.page.evaluate( + (path) => window.deepchat.invoke('config.setDefaultProjectPath', { path }), + managedWorkspace + ) + // Other live renderer/bootstrap callers may prepare this directory first. + const prepared = await active.page.evaluate(() => + window.deepchat.invoke('project.ensureChatWorkspace', {}) + ) + expect(prepared.path).toBe(managedWorkspace) + expect(statSync(managedWorkspace).isDirectory()).toBe(true) + expect(await snapshot()).toMatchObject({ + version: prepared.version, + defaultProjectPath: managedWorkspace, + defaultChatWorkspacePath: managedWorkspace + }) + + const firstPath = join(profile, 'selected-first') + const secondPath = join(profile, 'selected-second') + mkdirSync(firstPath) + mkdirSync(secondPath) + await active.electronApp.evaluate( + ({ dialog, shell }, paths) => { + const selections = [ + { canceled: true, filePaths: [] }, + { canceled: false, filePaths: [paths.firstPath] }, + { canceled: false, filePaths: [paths.secondPath] } + ] + const calls: { kind: string; path?: string }[] = [] + Reflect.set(globalThis, '__projectHostCalls', calls) + dialog.showOpenDialog = (async () => { + calls.push({ kind: 'selectDirectory' }) + const result = selections.shift() + if (!result) throw new Error('Unexpected native directory selection') + return result + }) as typeof dialog.showOpenDialog + shell.openPath = async (path) => { + calls.push({ kind: 'openPath', path }) + return path === paths.secondPath ? 'Fixture native open failed' : '' + } + }, + { firstPath, secondPath } + ) + + const canceled = await active.page.evaluate(() => + window.deepchat.invoke('project.selectDirectory', {}) + ) + expect(canceled).toEqual({ path: null, version: prepared.version }) + expect((await snapshot()).version).toBe(prepared.version) + for (const path of [firstPath, secondPath]) { + const selected = await active.page.evaluate(() => + window.deepchat.invoke('project.selectDirectory', {}) + ) + expect(selected.path).toBe(path) + expect((await snapshot()).version).toBe(selected.version) + } + await active.page.evaluate( + (path) => window.deepchat.invoke('project.openDirectory', { path: ` ${path} ` }), + firstPath + ) + await expect( + active.page.evaluate( + (path) => window.deepchat.invoke('project.openDirectory', { path }), + secondPath + ) + ).rejects.toThrow('Fixture native open failed') + expect( + await active.electronApp.evaluate(() => Reflect.get(globalThis, '__projectHostCalls')) + ).toEqual([ + { kind: 'selectDirectory' }, + { kind: 'selectDirectory' }, + { kind: 'selectDirectory' }, + { kind: 'openPath', path: firstPath }, + { kind: 'openPath', path: secondPath } + ]) + + await active.page.evaluate( + (paths) => window.deepchat.invoke('project.reorderEnvironments', { paths }), + [firstPath, secondPath] + ) + expect((await snapshot()).environments.slice(0, 2).map((item) => item.path)).toEqual([ + firstPath, + secondPath + ]) + await active.page.evaluate( + (path) => window.deepchat.invoke('project.archiveEnvironment', { path }), + secondPath + ) + expect((await snapshot()).archivedEnvironments.map((item) => item.path)).toContain(secondPath) + await active.page.evaluate( + (path) => window.deepchat.invoke('project.restoreEnvironment', { path }), + secondPath + ) + + const { session } = await active.page.evaluate( + (projectDir) => + window.deepchat.invoke('sessions.create', { + agentId: 'deepchat', + providerId: 'openai', + modelId: 'gpt-5.6-luna', + message: '', + projectDir + }), + firstPath + ) + await active.page.evaluate( + (path) => window.deepchat.invoke('config.setDefaultProjectPath', { path }), + firstPath + ) + const removed = await active.page.evaluate( + (path) => window.deepchat.invoke('project.removeEnvironment', { path }), + firstPath + ) + expect(removed.clearedSessionIds).toContain(session.id) + const expected = await snapshot() + expect(expected.defaultProjectPath).toBeNull() + expect(expected.removedEnvironments.map((item) => item.path)).toContain(firstPath) + expect(expected.environments.map((item) => item.path)).not.toContain(firstPath) + expect(existsSync(firstPath)).toBe(true) + const restored = await active.page.evaluate( + (sessionId) => window.deepchat.invoke('sessions.restore', { sessionId, limit: 30 }), + session.id + ) + expect(restored.session?.projectDir).toBeNull() + + expect(await active.close()).toBe('graceful') + active = await launchApp() + await waitForAppReady(active.page) + expect(active.userDataDir).toBe(profile) + expect(await snapshot()).toEqual(expected) + const afterRestart = await active.page.evaluate( + (sessionId) => window.deepchat.invoke('sessions.restore', { sessionId, limit: 30 }), + session.id + ) + expect(afterRestart.session?.projectDir).toBeNull() + expect(await active.close()).toBe('graceful') +}) diff --git a/test/e2e/specs/67-settings-file-owner.smoke.spec.ts b/test/e2e/specs/67-settings-file-owner.smoke.spec.ts new file mode 100644 index 0000000000..ec17e7972b --- /dev/null +++ b/test/e2e/specs/67-settings-file-owner.smoke.spec.ts @@ -0,0 +1,102 @@ +import { mkdirSync, readFileSync, writeFileSync } from 'node:fs' +import { join } from 'node:path' +import { test, expect } from '../fixtures/electronApp' +import { waitForAppReady } from '../helpers/wait' + +test.use({ privacyModeEnabled: true }) + +test('legacy settings file and database values survive owner bootstrap and relaunch @smoke', async ({ + launchApp +}) => { + const extension = { source: 'legacy-fixture', nested: { retained: [1, 2, 3] } } + let active = await launchApp({ + prepareUserData: (profile) => { + const file = join(profile, 'app-settings.json') + const settings = JSON.parse(readFileSync(file, 'utf8')) + const workspace = join(profile, 'workspace') + mkdirSync(workspace) + Object.assign(settings, { + defaultProjectPath: workspace, + appVersion: '0.9.0', + language: 'en-US', + copyWithCotEnabled: true, + extensionFixture: extension + }) + writeFileSync(file, JSON.stringify(settings)) + } + }) + await waitForAppReady(active.page) + const profile = active.userDataDir + const file = join(profile, 'app-settings.json') + const readSettings = () => JSON.parse(readFileSync(file, 'utf8')) + expect(readSettings().extensionFixture).toEqual(extension) + expect( + await active.page.evaluate(() => window.deepchat.invoke('config.getLanguage', {})) + ).toMatchObject({ requestedLanguage: 'en-US', locale: 'en-US' }) + const version = await active.electronApp.evaluate(({ app }) => app.getVersion()) + expect(readSettings().appVersion).toBe(version) + + const changed = await active.page.evaluate(async () => { + const settings = await window.deepchat.invoke('settings.update', { + changes: [ + { key: 'autoScrollEnabled', value: false }, + { key: 'fontSizeLevel', value: 2 }, + { key: 'copyWithCotEnabled', value: false } + ] + }) + const entries = await window.deepchat.invoke('config.updateEntries', { + changes: [{ key: 'maxFileSize', value: 20 * 1024 * 1024 }] + }) + const language = await window.deepchat.invoke('config.setLanguage', { language: 'zh-CN' }) + const cloud = await window.deepchat.invoke('sync.setCloudConfig', { + config: { + enabled: false, + endpoint: 'http://127.0.0.1:1', + bucket: 'settings-owner-fixture', + accessKeyId: 'fixture-key', + secretAccessKey: 'owned-profile-cloud-fixture' + } + }) + return { settings, entries, language, cloud } + }) + expect(changed.settings.values).toEqual({ + autoScrollEnabled: false, + fontSizeLevel: 2, + copyWithCotEnabled: false + }) + expect(changed.entries.values).toEqual({ maxFileSize: 20 * 1024 * 1024 }) + expect(changed.language).toMatchObject({ requestedLanguage: 'zh-CN', locale: 'zh-CN' }) + expect(changed.cloud.config).toMatchObject({ hasSecret: true, safeStorageAvailable: true }) + expect(readFileSync(file, 'utf8')).not.toContain('owned-profile-cloud-fixture') + const wrappedSecret = readSettings().cloudSyncSecret + expect(typeof wrappedSecret).toBe('string') + expect(readSettings()).toMatchObject({ + extensionFixture: extension, + autoScrollEnabled: false, + fontSizeLevel: 2, + language: 'zh-CN', + maxFileSize: 20 * 1024 * 1024, + copyWithCotEnabled: true + }) + + expect(await active.close()).toBe('graceful') + active = await launchApp() + await waitForAppReady(active.page) + expect(active.userDataDir).toBe(profile) + const restored = await active.page.evaluate(async () => ({ + settings: await window.deepchat.invoke('settings.getSnapshot', { + keys: ['autoScrollEnabled', 'fontSizeLevel', 'copyWithCotEnabled'] + }), + entries: await window.deepchat.invoke('config.getEntries', { keys: ['maxFileSize'] }), + language: await window.deepchat.invoke('config.getLanguage', {}), + cloud: await window.deepchat.invoke('sync.getCloudConfig', {}) + })) + expect(restored.settings.values).toEqual(changed.settings.values) + expect(restored.entries.values).toEqual(changed.entries.values) + expect(restored.language).toEqual(changed.language) + expect(restored.cloud.config).toMatchObject({ hasSecret: true, safeStorageAvailable: true }) + expect(readSettings().cloudSyncSecret).toBe(wrappedSecret) + expect(readSettings().extensionFixture).toEqual(extension) + expect(readSettings().copyWithCotEnabled).toBe(true) + expect(await active.close()).toBe('graceful') +}) diff --git a/test/e2e/specs/68-desktop-settings-host.smoke.spec.ts b/test/e2e/specs/68-desktop-settings-host.smoke.spec.ts new file mode 100644 index 0000000000..d0a36cc399 --- /dev/null +++ b/test/e2e/specs/68-desktop-settings-host.smoke.spec.ts @@ -0,0 +1,109 @@ +import { readFileSync, writeFileSync } from 'node:fs' +import { join } from 'node:path' +import { test, expect } from '../fixtures/electronApp' +import { openSettings } from '../helpers/settings' +import { waitForAppReady } from '../helpers/wait' + +test.use({ privacyModeEnabled: true }) + +test('desktop appearance and native login observations survive settings owner relaunch @smoke', async ({ + launchApp +}) => { + let active = await launchApp({ + prepareUserData: (profile) => { + const file = join(profile, 'app-settings.json') + const stored = JSON.parse(readFileSync(file, 'utf8')) + writeFileSync(file, JSON.stringify({ ...stored, launchAtLoginEnabled: true })) + } + }) + await waitForAppReady(active.page) + expect(active.ownsUserDataDir).toBe(true) + const settingsPage = await openSettings(active) + + // Keep OS login writes inside this disposable process fixture. + await active.electronApp.evaluate(({ app }) => { + const get = app.getLoginItemSettings + const set = app.setLoginItemSettings + const observed = get.call(app) + const writes: boolean[] = [] + app.getLoginItemSettings = () => ({ ...observed, openAtLogin: false }) + app.setLoginItemSettings = (options) => { + writes.push(Boolean(options.openAtLogin)) + } + const state = globalThis as typeof globalThis & { + restoreDesktopSettingsFixture?: () => boolean[] + } + state.restoreDesktopSettingsFixture = () => { + app.getLoginItemSettings = get + app.setLoginItemSettings = set + delete state.restoreDesktopSettingsFixture + return writes + } + }) + try { + const login = await active.page.evaluate(async () => ({ + before: await window.deepchat.invoke('settings.getSnapshot', { + keys: ['launchAtLoginEnabled'] + }), + changed: await window.deepchat.invoke('settings.update', { + changes: [{ key: 'launchAtLoginEnabled', value: true }] + }) + })) + expect(login.before.values).toEqual({ launchAtLoginEnabled: false }) + expect(login.changed.values).toEqual({ launchAtLoginEnabled: false }) + + expect( + await active.page.evaluate(() => window.deepchat.invoke('config.setTheme', { theme: 'dark' })) + ).toEqual({ theme: 'dark', isDark: true }) + await expect(active.page.locator('html')).toHaveClass(/dark/) + await expect(settingsPage.locator('html')).toHaveClass(/dark/) + expect( + await active.page.evaluate(() => + window.deepchat.invoke('config.setLanguage', { language: 'fa' }) + ) + ).toEqual({ requestedLanguage: 'fa-IR', locale: 'fa-IR', direction: 'rtl' }) + await expect(active.page.locator('html')).toHaveAttribute('dir', 'rtl') + await expect(settingsPage.locator('html')).toHaveAttribute('dir', 'rtl') + + const restored = await settingsPage.evaluate(async () => ({ + theme: await window.deepchat.invoke('config.setTheme', { theme: 'light' }), + language: await window.deepchat.invoke('config.setLanguage', { language: 'en-US' }) + })) + expect(restored.theme).toEqual({ theme: 'light', isDark: false }) + expect(restored.language).toEqual({ + requestedLanguage: 'en-US', + locale: 'en-US', + direction: 'auto' + }) + await expect(active.page.locator('html')).not.toHaveClass(/dark/) + await expect(settingsPage.locator('html')).not.toHaveClass(/dark/) + await expect(active.page.locator('html')).toHaveAttribute('dir', 'auto') + await expect(settingsPage.locator('html')).toHaveAttribute('dir', 'auto') + } finally { + const writes = await active.electronApp.evaluate(() => + ( + globalThis as typeof globalThis & { + restoreDesktopSettingsFixture?: () => boolean[] + } + ).restoreDesktopSettingsFixture?.() + ) + expect(writes).toEqual([true]) + } + const stored = JSON.parse(readFileSync(join(active.userDataDir, 'app-settings.json'), 'utf8')) + expect(stored).toMatchObject({ language: 'en-US', appTheme: 'light', launchAtLoginEnabled: true }) + expect(await active.close()).toBe('graceful') + + active = await launchApp() + await waitForAppReady(active.page) + const reopened = await active.page.evaluate(async () => ({ + theme: await window.deepchat.invoke('config.getTheme', {}), + language: await window.deepchat.invoke('config.getLanguage', {}) + })) + expect(reopened.theme).toEqual({ theme: 'light', isDark: false }) + expect(reopened.language).toEqual({ + requestedLanguage: 'en-US', + locale: 'en-US', + direction: 'auto' + }) + expect(await active.close()).toBe('graceful') +}) diff --git a/test/e2e/specs/69-font-logging-host.smoke.spec.ts b/test/e2e/specs/69-font-logging-host.smoke.spec.ts new file mode 100644 index 0000000000..c71a49d4e8 --- /dev/null +++ b/test/e2e/specs/69-font-logging-host.smoke.spec.ts @@ -0,0 +1,182 @@ +import { existsSync, readFileSync } from 'node:fs' +import { join } from 'node:path' +import { test, expect } from '../fixtures/electronApp' +import { waitForAppReady } from '../helpers/wait' + +type NativeConfigurationFixture = { + opened: string[] + protectedValues: boolean[] + relaunches: number + exits: number + finish(): void +} + +test.use({ privacyModeEnabled: true }) + +test('native font, folder and configuration restart capabilities preserve the profile @smoke', async ({ + launchApp +}) => { + let active = await launchApp() + await waitForAppReady(active.page) + expect(active.ownsUserDataDir).toBe(true) + const profile = active.userDataDir + const restartProcess = active.electronApp.process() + const catalogs = await active.page.evaluate(() => + Promise.all([ + window.deepchat.invoke('settings.listSystemFonts', {}), + window.deepchat.invoke('settings.listSystemFonts', {}) + ]) + ) + expect(catalogs[0].fonts.length).toBeGreaterThan(0) + expect(catalogs[1]).toEqual(catalogs[0]) + const family = catalogs[0].fonts.find((name) => /^[A-Za-z][A-Za-z0-9-]*$/.test(name)) + expect(family).toBeTruthy() + const selected = await active.page.evaluate( + (family) => + window.deepchat.invoke('settings.update', { + changes: [ + { key: 'fontFamily', value: ` ${family.toLowerCase()}; ` }, + { key: 'codeFontFamily', value: family } + ] + }), + family! + ) + expect(selected.values).toEqual({ fontFamily: family, codeFontFamily: family }) + + // Observe the real shutdown at the native relaunch/exit boundary without spawning another app. + await active.electronApp.evaluate(({ app, shell, BrowserWindow }) => { + const relaunch = app.relaunch + const exit = app.exit + const quit = app.quit + const openPath = shell.openPath + const windows = BrowserWindow.getAllWindows().map((window) => ({ + window, + protect: window.setContentProtection + })) + const fixture = { + opened: [] as string[], + protectedValues: [] as boolean[], + relaunches: 0, + exits: 0, + finish: () => { + app.relaunch = relaunch + app.exit = exit + app.quit = quit + shell.openPath = openPath + for (const { window, protect } of windows) window.setContentProtection = protect + exit.call(app, 0) + } + } + app.relaunch = () => { + fixture.relaunches++ + } + app.exit = () => { + fixture.exits++ + } + app.quit = () => {} + shell.openPath = async (path) => { + fixture.opened.push(path) + return '' + } + for (const { window, protect } of windows) { + window.setContentProtection = function ( + this: InstanceType, + enabled: boolean + ) { + protect.call(this, enabled) + fixture.protectedValues.push(this.isContentProtected()) + } + } + ;( + globalThis as typeof globalThis & { nativeConfigurationFixture?: NativeConfigurationFixture } + ).nativeConfigurationFixture = fixture + }) + expect( + await active.page.evaluate(() => window.deepchat.invoke('config.openLoggingFolder', {})) + ).toEqual({ opened: true }) + expect(existsSync(join(profile, 'logs'))).toBe(true) + expect( + await active.page.evaluate(() => + window.deepchat.invoke('settings.update', { + changes: [ + { key: 'contentProtectionEnabled', value: true }, + { key: 'loggingEnabled', value: true } + ] + }) + ) + ).toMatchObject({ values: { contentProtectionEnabled: true, loggingEnabled: true } }) + + await expect + .poll(() => + active.electronApp.evaluate( + () => + ( + globalThis as typeof globalThis & { + nativeConfigurationFixture: NativeConfigurationFixture + } + ).nativeConfigurationFixture.exits + ) + ) + .toBe(1) + const native = await active.electronApp.evaluate(() => { + const fixture = ( + globalThis as typeof globalThis & { nativeConfigurationFixture: NativeConfigurationFixture } + ).nativeConfigurationFixture + return { + opened: fixture.opened, + protectedValues: fixture.protectedValues, + relaunches: fixture.relaunches + } + }) + expect(native.opened).toEqual([join(profile, 'logs')]) + expect(native.protectedValues.length).toBeGreaterThan(0) + expect(native.protectedValues.every(Boolean)).toBe(true) + expect(native.relaunches).toBe(1) + expect(JSON.parse(readFileSync(join(profile, 'app-settings.json'), 'utf8'))).toMatchObject({ + fontFamily: family, + codeFontFamily: family, + contentProtectionEnabled: true, + loggingEnabled: true + }) + expect(readFileSync(join(profile, 'logs', 'main.jsonl'), 'utf8')).toContain( + 'app.shutdown.terminal' + ) + await active.electronApp + .evaluate(() => + ( + globalThis as typeof globalThis & { nativeConfigurationFixture: NativeConfigurationFixture } + ).nativeConfigurationFixture.finish() + ) + .catch((error) => { + if (!(error instanceof Error) || !error.message.includes('closed')) throw error + }) + await expect.poll(() => restartProcess.exitCode).toBe(0) + expect(await active.close()).toBe('graceful') + + active = await launchApp() + await waitForAppReady(active.page) + expect( + ( + await active.page.evaluate(() => + window.deepchat.invoke('settings.getSnapshot', { + keys: ['fontFamily', 'codeFontFamily', 'contentProtectionEnabled', 'loggingEnabled'] + }) + ) + ).values + ).toEqual({ + fontFamily: family, + codeFontFamily: family, + contentProtectionEnabled: true, + loggingEnabled: true + }) + const identity = await active.page.evaluate(() => + window.deepchat.invoke('window.getRuntimeIdentity', {}) + ) + expect( + await active.electronApp.evaluate( + ({ BrowserWindow }, id) => BrowserWindow.fromId(id)!.isContentProtected(), + identity.windowId! + ) + ).toBe(true) + expect(await active.close()).toBe('graceful') +}) diff --git a/test/e2e/specs/70-file-image-host.smoke.spec.ts b/test/e2e/specs/70-file-image-host.smoke.spec.ts new file mode 100644 index 0000000000..7eca532aa8 --- /dev/null +++ b/test/e2e/specs/70-file-image-host.smoke.spec.ts @@ -0,0 +1,270 @@ +import { mkdirSync, readFileSync, writeFileSync } from 'node:fs' +import { createServer } from 'node:http' +import { dirname, join } from 'node:path' +import sharp from 'sharp' +import { test, expect } from '../fixtures/electronApp' +import { waitForAppReady } from '../helpers/wait' +import { openSettings } from '../helpers/settings' + +type ImageActionFixture = { + target?: string + dialogs: string[] + copied: { width: number; height: number }[] + restore(): void +} + +test.use({ privacyModeEnabled: true }) + +test('held native image downloads retire on quit and Core loss @smoke', async ({ launchApp }) => { + const started = new Set() + const closed = new Set() + const server = createServer((request, response) => { + const route = request.url ?? '' + started.add(route) + response.once('close', () => closed.add(route)) + if (route.endsWith('/body.png')) { + response.writeHead(200, { 'content-type': 'image/png' }) + response.write(Buffer.from('partial image body')) + } + }) + await new Promise((resolve) => server.listen(0, '127.0.0.1', resolve)) + const address = server.address() + if (!address || typeof address === 'string') throw new Error('Image fixture did not listen') + let active: Awaited> | undefined + try { + for (const mode of ['quit', 'failure'] as const) { + active = await launchApp() + expect(active.ownsUserDataDir).toBe(true) + await waitForAppReady(active.page) + const appProcess = active.electronApp.process() + const settings = mode === 'failure' ? await openSettings(active) : undefined + const routes = [`/${mode}/headers.png`, `/${mode}/body.png`] + if (mode === 'quit') routes.push(`/${mode}/headers-2.png`) + const pending = active.page + .evaluate( + async (urls) => { + await Promise.allSettled( + urls.map((source, index) => + index === 0 + ? window.deepchat.invoke('file.saveImage', { source }) + : window.deepchat.invoke('file.copyImage', { source }) + ) + ) + }, + routes.map((route) => `http://127.0.0.1:${address.port}${route}`) + ) + .catch(() => undefined) + await expect.poll(() => routes.every((route) => started.has(route))).toBe(true) + if (settings) { + await active.electronApp.evaluate(({ webContents }, url) => { + const target = webContents + .getAllWebContents() + .find((contents) => contents.getURL() === url) + if (!target) throw new Error('Owned settings renderer is missing') + const original = webContents.fromId + webContents.fromId = (id) => { + if (id === target.id) throw new Error('Native renderer registration was refused') + return original.call(webContents, id) + } + }, settings.url()) + const creation = settings + .evaluate(() => + window.deepchat.invoke('sessions.create', { + agentId: 'deepchat', + providerId: 'openai', + modelId: 'gpt-4o-mini', + message: '' + }) + ) + .catch(() => undefined) + await expect.poll(() => appProcess.exitCode).toBe(1) + await creation + } else expect(await active.close()).toBe('graceful') + await pending + await expect.poll(() => routes.every((route) => closed.has(route))).toBe(true) + + active = await launchApp() + await waitForAppReady(active.page) + await active.electronApp.evaluate(({ dialog }) => { + dialog.showSaveDialog = async () => ({ canceled: true, filePath: '' }) + }) + expect( + await active.page.evaluate(() => + window.deepchat.invoke('file.saveImage', { source: 'data:image/png;base64,aW1hZ2U=' }) + ) + ).toEqual({ canceled: true }) + expect(await active.close()).toBe('graceful') + } + } finally { + await active?.close() + server.closeAllConnections() + await new Promise((resolve, reject) => + server.close((error) => (error ? reject(error) : resolve())) + ) + } +}) + +test('file preparation and native image actions preserve bytes and profile bounds @smoke', async ({ + launchApp +}) => { + const png = await sharp({ + create: { width: 32, height: 24, channels: 4, background: '#2c8651' } + }) + .png() + .toBuffer() + const dataUrl = `data:image/png;base64,${png.toString('base64')}` + const server = createServer((_request, response) => { + response.writeHead(200, { 'content-type': 'image/png' }) + response.end(png) + }) + await new Promise((resolve) => server.listen(0, '127.0.0.1', resolve)) + const address = server.address() + if (!address || typeof address === 'string') throw new Error('Image fixture did not listen') + let active: Awaited> | undefined + try { + active = await launchApp({ + prepareUserData(profile) { + mkdirSync(join(profile, 'images'), { recursive: true }) + writeFileSync(join(profile, 'images', 'cached image.png'), png) + writeFileSync(join(profile, 'note.txt'), 'file preparation fixture') + } + }) + await waitForAppReady(active.page) + expect(active.ownsUserDataDir).toBe(true) + const profile = active.userDataDir + const prepared = await active.page.evaluate( + ({ textPath, imagePath, dataUrl }) => + Promise.all([ + window.deepchat.invoke('file.prepareFile', { path: textPath }), + window.deepchat.invoke('file.prepareFile', { path: imagePath }), + window.deepchat.invoke('file.readFile', { path: 'note.txt' }), + window.deepchat.invoke('file.writeImageBase64', { + name: '../../outside.png', + content: dataUrl + }) + ]), + { + textPath: join(profile, 'note.txt'), + imagePath: join(profile, 'images', 'cached image.png'), + dataUrl + } + ) + expect(prepared[0].file.content).toContain('file preparation fixture') + expect(prepared[1].file.mimeType).toBe('image/png') + expect(prepared[1].file.thumbnail).toMatch(/^data:image\/jpeg;base64,/) + expect(prepared[2]).toEqual({ content: 'file preparation fixture' }) + expect(dirname(prepared[3].path)).toBe(join(profile, 'temp')) + expect(readFileSync(prepared[3].path)).toEqual(png) + await expect( + active.page.evaluate(() => + window.deepchat.invoke('file.readFile', { path: '../outside.txt' }) + ) + ).rejects.toThrow('File path escapes user data directory') + + // Keep real native decoding and filesystem writes while owning dialog and clipboard effects. + await active.electronApp.evaluate(({ clipboard, dialog }) => { + const save = dialog.showSaveDialog + const copy = clipboard.writeImage + const fixture: ImageActionFixture = { + dialogs: [], + copied: [], + restore() { + dialog.showSaveDialog = save + clipboard.writeImage = copy + } + } + dialog.showSaveDialog = async (...args: unknown[]) => { + const options = args.at(-1) as Electron.SaveDialogOptions + fixture.dialogs.push(options.defaultPath ?? '') + return { canceled: !fixture.target, filePath: fixture.target ?? '' } + } + clipboard.writeImage = (image) => { + fixture.copied.push(image.getSize()) + } + ;( + globalThis as typeof globalThis & { imageActionFixture: ImageActionFixture } + ).imageActionFixture = fixture + }) + const sources = [ + { source: dataUrl }, + { source: png.toString('base64'), mimeType: 'image/png' }, + { source: 'imgcache://cached%20image.png' }, + { source: `http://127.0.0.1:${address.port}/remote.png` } + ] + for (const [index, source] of sources.entries()) { + const target = join(profile, `saved-${index}.png`) + await active.electronApp.evaluate((_, target) => { + ;( + globalThis as typeof globalThis & { imageActionFixture: ImageActionFixture } + ).imageActionFixture.target = target + }, target) + expect( + await active.page.evaluate((input) => window.deepchat.invoke('file.saveImage', input), { + ...source, + suggestedName: 'saved:image' + }) + ).toEqual({ canceled: false, path: target }) + expect(readFileSync(target)).toEqual(png) + expect( + await active.page.evaluate( + (input) => window.deepchat.invoke('file.copyImage', input), + source + ) + ).toEqual({ copied: true }) + } + await active.electronApp.evaluate(() => { + ;( + globalThis as typeof globalThis & { imageActionFixture: ImageActionFixture } + ).imageActionFixture.target = undefined + }) + expect( + await active.page.evaluate( + (source) => window.deepchat.invoke('file.saveImage', { source }), + dataUrl + ) + ).toEqual({ canceled: true }) + await expect( + active.page.evaluate(() => + window.deepchat.invoke('file.copyImage', { + source: 'bm90IGFuIGltYWdl', + mimeType: 'image/png' + }) + ) + ).rejects.toThrow('Image data cannot be copied to clipboard') + const observed = await active.electronApp.evaluate(() => { + const fixture = (globalThis as typeof globalThis & { imageActionFixture: ImageActionFixture }) + .imageActionFixture + return { dialogs: fixture.dialogs, copied: fixture.copied } + }) + expect(observed.dialogs.slice(0, 4)).toEqual(Array(4).fill('saved_image.png')) + expect(observed.copied).toEqual(Array(4).fill({ width: 32, height: 24 })) + await active.electronApp.evaluate(() => { + ;( + globalThis as typeof globalThis & { imageActionFixture: ImageActionFixture } + ).imageActionFixture.restore() + }) + expect(await active.close()).toBe('graceful') + active = await launchApp() + await waitForAppReady(active.page) + expect( + await active.page.evaluate(() => + window.deepchat.invoke('file.readFile', { path: 'note.txt' }) + ) + ).toEqual({ content: 'file preparation fixture' }) + expect(await active.close()).toBe('graceful') + } finally { + if (active) { + await active.electronApp + .evaluate(() => { + ;( + globalThis as typeof globalThis & { imageActionFixture?: ImageActionFixture } + ).imageActionFixture?.restore() + }) + .catch(() => {}) + await active.close() + } + await new Promise((resolve, reject) => + server.close((error) => (error ? reject(error) : resolve())) + ) + } +}) diff --git a/test/e2e/specs/71-storage-host-roots.smoke.spec.ts b/test/e2e/specs/71-storage-host-roots.smoke.spec.ts new file mode 100644 index 0000000000..a9062784c2 --- /dev/null +++ b/test/e2e/specs/71-storage-host-roots.smoke.spec.ts @@ -0,0 +1,154 @@ +import { execFileSync } from 'node:child_process' +import { existsSync, mkdirSync, readFileSync } from 'node:fs' +import { createRequire } from 'node:module' +import { join } from 'node:path' +import { test, expect } from '../fixtures/electronApp' +import { waitForAppReady } from '../helpers/wait' + +test.use({ privacyModeEnabled: true }) + +test('sync folders and Knowledge databases retain their owned paths across relaunch @smoke', async ({ + launchApp +}) => { + const require = createRequire(import.meta.url) + let sourceBytes!: Buffer + let active = await launchApp({ + prepareUserData(profile) { + mkdirSync(join(profile, 'app_db'), { recursive: true }) + const source = join(profile, 'app_db', 'chat.db') + execFileSync( + require('electron'), + [ + '-e', + ` + const Database = require(process.argv[1]) + const db = new Database(process.argv[2]) + db.exec("CREATE TABLE conversations (conv_id TEXT PRIMARY KEY, title TEXT); INSERT INTO conversations VALUES ('storage-root-fixture', 'Imported storage fixture')") + db.close() + `, + require.resolve('better-sqlite3-multiple-ciphers'), + source + ], + { + env: { ...process.env, ELECTRON_RUN_AS_NODE: '1' }, + timeout: 30_000 + } + ) + sourceBytes = readFileSync(source) + } + }) + expect(active.ownsUserDataDir).toBe(true) + await waitForAppReady(active.page) + const profile = active.userDataDir + const legacySessionId = 'legacy-session-storage-root-fixture' + await expect + .poll( + async () => + ( + await active.page.evaluate( + (sessionId) => window.deepchat.invoke('sessions.restore', { sessionId, limit: 30 }), + legacySessionId + ) + ).session?.title + ) + .toBe('Imported storage fixture') + const backupDirectory = join(profile, 'storage-backups') + await active.page.evaluate( + (folderPath) => + window.deepchat.invoke('config.updateSyncSettings', { enabled: true, folderPath }), + backupDirectory + ) + await active.electronApp.evaluate(({ shell }) => { + const openPath = shell.openPath + Object.assign(globalThis, { + storageFolderFixture: { + opened: [] as string[], + restore: () => { + shell.openPath = openPath + } + } + }) + shell.openPath = async (path) => { + ;( + globalThis as typeof globalThis & { storageFolderFixture: { opened: string[] } } + ).storageFolderFixture.opened.push(path) + return '' + } + }) + try { + expect(await active.page.evaluate(() => window.deepchat.invoke('sync.openFolder', {}))).toEqual( + { + opened: true + } + ) + expect(existsSync(backupDirectory)).toBe(true) + expect( + await active.electronApp.evaluate( + () => + (globalThis as typeof globalThis & { storageFolderFixture: { opened: string[] } }) + .storageFolderFixture.opened + ) + ).toEqual([backupDirectory]) + } finally { + await active.electronApp.evaluate(() => { + ;( + globalThis as typeof globalThis & { storageFolderFixture: { restore(): void } } + ).storageFolderFixture.restore() + }) + } + + const supported = await active.page.evaluate(() => + window.deepchat.invoke('knowledge.isSupported', {}) + ) + const knowledgeBaseId = 'storage-root-fixture' + if (supported.supported) { + await active.page.evaluate( + (id) => + window.deepchat.invoke('config.setKnowledgeConfigs', { + configs: [ + { + id, + description: 'Storage root fixture', + embedding: { providerId: 'openai', modelId: 'text-embedding-3-small' }, + dimensions: 3, + normalized: false, + fragmentsNumber: 2, + enabled: true + } + ] + }), + knowledgeBaseId + ) + expect( + await active.page.evaluate( + (knowledgeBaseId) => window.deepchat.invoke('knowledge.listFiles', { knowledgeBaseId }), + knowledgeBaseId + ) + ).toEqual({ files: [] }) + expect(existsSync(join(profile, 'app_db', 'KnowledgeBase', knowledgeBaseId))).toBe(true) + } + expect(await active.close()).toBe('graceful') + expect(readFileSync(join(profile, 'app_db', 'chat.db'))).toEqual(sourceBytes) + active = await launchApp() + await waitForAppReady(active.page) + expect( + ( + await active.page.evaluate( + (sessionId) => window.deepchat.invoke('sessions.restore', { sessionId, limit: 30 }), + legacySessionId + ) + ).session?.title + ).toBe('Imported storage fixture') + expect( + await active.page.evaluate(() => window.deepchat.invoke('config.getSyncSettings', {})) + ).toMatchObject({ enabled: true, folderPath: backupDirectory }) + if (supported.supported) { + expect( + await active.page.evaluate( + (knowledgeBaseId) => window.deepchat.invoke('knowledge.listFiles', { knowledgeBaseId }), + knowledgeBaseId + ) + ).toEqual({ files: [] }) + } + expect(await active.close()).toBe('graceful') +}) diff --git a/test/e2e/specs/72-native-configuration-projection.smoke.spec.ts b/test/e2e/specs/72-native-configuration-projection.smoke.spec.ts new file mode 100644 index 0000000000..d2771d2749 --- /dev/null +++ b/test/e2e/specs/72-native-configuration-projection.smoke.spec.ts @@ -0,0 +1,145 @@ +import { readFileSync } from 'node:fs' +import { join, resolve } from 'node:path' +import { test, expect } from '../fixtures/electronApp' +import { waitForAppReady } from '../helpers/wait' + +type NativeReadFixture = { + shortcuts: string[] + channels: (string | null)[] + restore(): void +} + +test.use({ privacyModeEnabled: true }) + +test('native settings readers observe acknowledged changes and persisted floating bounds @smoke', async ({ + launchApp +}) => { + let active = await launchApp() + await waitForAppReady(active.page) + expect(active.ownsUserDataDir).toBe(true) + const profile = active.userDataDir + await active.electronApp.evaluate(({ globalShortcut }, packageJson) => { + const require = process.getBuiltinModule('node:module').createRequire(packageJson) + const { autoUpdater } = require('electron-updater') as typeof import('electron-updater') + const register = globalShortcut.register + const check = autoUpdater.checkForUpdates + const fixture: NativeReadFixture = { + shortcuts: [], + channels: [], + restore() { + globalShortcut.register = register + autoUpdater.checkForUpdates = check + } + } + globalShortcut.register = (accelerator) => { + fixture.shortcuts.push(accelerator) + return true + } + autoUpdater.checkForUpdates = async () => { + fixture.channels.push(autoUpdater.channel) + return null + } + ;( + globalThis as typeof globalThis & { nativeReadFixture: NativeReadFixture } + ).nativeReadFixture = fixture + }, resolve('package.json')) + try { + const shortcuts = ( + await active.page.evaluate(() => window.deepchat.invoke('config.getShortcutKeys', {})) + ).shortcuts + const accelerator = 'CommandOrControl+Shift+F12' + await active.page.evaluate( + (shortcuts) => window.deepchat.invoke('config.setShortcutKeys', { shortcuts }), + { ...shortcuts, ShowHideWindow: accelerator } + ) + await active.page.evaluate(() => window.deepchat.invoke('shortcut.register', {})) + for (const channel of ['stable', 'beta'] as const) { + expect( + await active.page.evaluate( + (channel) => window.deepchat.invoke('config.setUpdateChannel', { channel }), + channel + ) + ).toEqual({ channel }) + await active.page.evaluate(() => + window.deepchat.invoke('upgrade.check', { type: 'manualCheck' }) + ) + } + const reads = await active.electronApp.evaluate(() => { + const fixture = (globalThis as typeof globalThis & { nativeReadFixture: NativeReadFixture }) + .nativeReadFixture + return { shortcuts: fixture.shortcuts, channels: fixture.channels } + }) + expect(reads.shortcuts).toContain(accelerator) + expect(reads.channels).toEqual(['latest', 'beta']) + + await active.page.evaluate(() => + window.deepchat.invoke('config.setFloatingButton', { enabled: true }) + ) + await expect + .poll(() => active!.electronApp.windows().some((page) => page.url().includes('/floating/'))) + .toBe(true) + const widget = active.electronApp.windows().find((page) => page.url().includes('/floating/'))! + await widget.waitForFunction(() => + Boolean((window as unknown as { floatingButtonAPI?: unknown }).floatingButtonAPI) + ) + await widget.evaluate(() => { + const api = ( + window as unknown as { + floatingButtonAPI: { + onDragStart(x: number, y: number): void + onDragMove(x: number, y: number): void + onDragEnd(x: number, y: number): void + } + } + ).floatingButtonAPI + api.onDragStart(100, 100) + api.onDragMove(140, 170) + api.onDragEnd(140, 170) + }) + const readSettings = () => JSON.parse(readFileSync(join(profile, 'app-settings.json'), 'utf8')) + await expect.poll(() => readSettings().floatingButtonBounds?.y).toEqual(expect.any(Number)) + const bounds = readSettings().floatingButtonBounds + expect(Number.isFinite(bounds.x)).toBe(true) + expect(['left', 'right']).toContain(bounds.dockSide) + await active.page.evaluate(() => + window.deepchat.invoke('config.setFloatingButton', { enabled: false }) + ) + await active.page.evaluate(() => + window.deepchat.invoke('config.setFloatingButton', { enabled: true }) + ) + await expect + .poll(() => active!.electronApp.windows().some((page) => page.url().includes('/floating/'))) + .toBe(true) + // The reopened native window reads the committed resting position from the private projection. + await expect + .poll(() => + active!.electronApp.evaluate( + ({ BrowserWindow }) => + BrowserWindow.getAllWindows() + .find((window) => window.webContents.getURL().includes('/floating/')) + ?.getBounds().y + ) + ) + .toBe(bounds.y) + await active.page.evaluate(() => + window.deepchat.invoke('config.setFloatingButton', { enabled: false }) + ) + } finally { + await active.electronApp.evaluate(() => + ( + globalThis as typeof globalThis & { nativeReadFixture: NativeReadFixture } + ).nativeReadFixture.restore() + ) + } + expect(await active.close()).toBe('graceful') + active = await launchApp() + await waitForAppReady(active.page) + expect( + await active.page.evaluate(() => window.deepchat.invoke('config.getUpdateChannel', {})) + ).toEqual({ channel: 'beta' }) + expect( + (await active.page.evaluate(() => window.deepchat.invoke('config.getShortcutKeys', {}))) + .shortcuts.ShowHideWindow + ).toBe('CommandOrControl+Shift+F12') + expect(await active.close()).toBe('graceful') +}) diff --git a/test/e2e/specs/73-remote-native-auth.smoke.spec.ts b/test/e2e/specs/73-remote-native-auth.smoke.spec.ts new file mode 100644 index 0000000000..0a58fb17d6 --- /dev/null +++ b/test/e2e/specs/73-remote-native-auth.smoke.spec.ts @@ -0,0 +1,310 @@ +import { createServer } from 'node:net' +import type { ServerResponse } from 'node:http' +import { test, expect } from '../fixtures/electronApp' +import { waitForAppReady } from '../helpers/wait' +import { openSettings } from '../helpers/settings' +import { startHttpsFixture } from '../helpers/httpsFixture' + +type AuthFixture = { windows: number[]; restore(): void } + +test.use({ privacyModeEnabled: true }) + +test('remote login windows preserve nonce checks, native close and account relaunch @smoke', async ({ + launchApp +}) => { + const https = await startHttpsFixture(['ilinkai.weixin.qq.com'], (request, response) => { + const body = request.url?.startsWith('/ilink/bot/get_bot_qrcode') + ? { qrcode: 'fixture-qr', qrcode_img_content: 'https://liteapp.weixin.qq.com/fixture' } + : request.url?.startsWith('/ilink/bot/get_qrcode_status') + ? { + status: 'confirmed', + ilink_bot_id: 'native-fixture-bot', + ilink_user_id: 'native-fixture-owner', + bot_token: 'disposable-native-token' + } + : undefined + response + .writeHead(body ? 200 : 404, { 'content-type': 'application/json' }) + .end(JSON.stringify(body ?? {})) + }) + let active = await launchApp().catch(async (error) => { + await https.close() + throw error + }) + try { + expect(active.ownsUserDataDir).toBe(true) + await waitForAppReady(active.page) + await active.page.evaluate(async (url) => { + await window.deepchat.invoke('config.setCustomProxyUrl', { url }) + await window.deepchat.invoke('config.setProxyMode', { mode: 'custom' }) + }, https.proxyUrl) + const server = createServer() + await new Promise((resolve) => server.listen(0, '127.0.0.1', resolve)) + const address = server.address() + if (!address || typeof address === 'string') throw new Error('No callback port') + await new Promise((resolve) => server.close(() => resolve())) + const redirectUri = `http://127.0.0.1:${address.port}/remote/feishu/auth/callback` + await active.electronApp.evaluate(({ BrowserWindow }) => { + const originalLoad = BrowserWindow.prototype.loadURL + const fixture: AuthFixture = { + windows: [], + restore() { + BrowserWindow.prototype.loadURL = originalLoad + } + } + BrowserWindow.prototype.loadURL = function (url, options) { + if ( + url.startsWith('https://accounts.feishu.cn/') || + url.startsWith('https://liteapp.weixin.qq.com/') + ) { + fixture.windows.push(this.id) + return originalLoad.call(this, 'data:text/html,Remote auth fixture') + } + return originalLoad.call(this, url, options) + } + ;(globalThis as typeof globalThis & { remoteAuthFixture: AuthFixture }).remoteAuthFixture = + fixture + }) + try { + const startFeishu = () => + active.page.evaluate( + (redirectUri) => + window.deepchat.invoke('remoteControl.startFeishuAuth', { + appId: 'native-fixture', + appSecret: 'disposable-native-secret', + redirectUri + }), + redirectUri + ) + const first = await startFeishu() + const nodeAvailable = await active.electronApp.evaluate(({ BrowserWindow }) => { + const fixture = (globalThis as typeof globalThis & { remoteAuthFixture: AuthFixture }) + .remoteAuthFixture + return BrowserWindow.fromId(fixture.windows.at(-1)!)!.webContents.executeJavaScript( + "typeof require !== 'undefined' || typeof process !== 'undefined'" + ) + }) + expect(nodeAvailable).toBe(false) + const waiting = active.page.evaluate( + (sessionKey) => window.deepchat.invoke('remoteControl.waitForFeishuAuth', { sessionKey }), + first.session.sessionKey + ) + const callback = await fetch(`${redirectUri}?code=fixture-code&state=incorrect-nonce`) + expect(callback.status).toBe(400) + await callback.text() + expect((await waiting).result).toMatchObject({ + authorized: false, + messageKey: 'settings.remote.feishu.authStateMismatch' + }) + + const second = await startFeishu() + await active.electronApp.evaluate(({ BrowserWindow }) => { + const fixture = (globalThis as typeof globalThis & { remoteAuthFixture: AuthFixture }) + .remoteAuthFixture + BrowserWindow.fromId(fixture.windows.at(-1)!)!.close() + }) + expect( + ( + await active.page.evaluate( + (sessionKey) => + window.deepchat.invoke('remoteControl.waitForFeishuAuth', { sessionKey }), + second.session.sessionKey + ) + ).result + ).toMatchObject({ authorized: false, messageKey: 'settings.remote.feishu.authCancelled' }) + + const login = await active.page.evaluate(() => + window.deepchat.invoke('remoteControl.startWeixinIlinkLogin', {}) + ) + expect( + ( + await active.page.evaluate( + (sessionKey) => + window.deepchat.invoke('remoteControl.waitForWeixinIlinkLogin', { sessionKey }), + login.session.sessionKey + ) + ).result + ).toMatchObject({ + connected: true, + account: { accountId: 'native-fixture-bot', ownerUserId: 'native-fixture-owner' } + }) + await expect + .poll(() => + active.electronApp.evaluate(({ BrowserWindow }) => { + const fixture = (globalThis as typeof globalThis & { remoteAuthFixture: AuthFixture }) + .remoteAuthFixture + return fixture.windows.some((id) => BrowserWindow.fromId(id) !== null) + }) + ) + .toBe(false) + } finally { + await active.electronApp.evaluate(() => + ( + globalThis as typeof globalThis & { remoteAuthFixture: AuthFixture } + ).remoteAuthFixture.restore() + ) + } + expect(await active.close()).toBe('graceful') + active = await launchApp() + await waitForAppReady(active.page) + const settings = await active.page.evaluate(() => + window.deepchat.invoke('remoteControl.getChannelSettings', { channel: 'weixin-ilink' }) + ) + expect(settings.settings).toMatchObject({ + remoteEnabled: false, + accounts: [{ accountId: 'native-fixture-bot', ownerUserId: 'native-fixture-owner' }] + }) + expect(await active.close()).toBe('graceful') + } finally { + try { + await active.close() + } finally { + await https.close() + } + } +}) + +test('remote open activates an existing or newly created chat window @smoke', async ({ + launchApp +}) => { + const messages: string[] = [] + const replies: string[] = [] + let held: ServerResponse | undefined + let updateId = 0 + const flush = () => { + if (!held || held.destroyed || !messages.length) return + const response = held + held = undefined + response.writeHead(200, { 'content-type': 'application/json' }).end( + JSON.stringify({ + ok: true, + result: messages.splice(0).map((text) => ({ + update_id: ++updateId, + message: { + message_id: updateId, + chat: { id: 4242, type: 'private' }, + from: { id: 4242 }, + text + } + })) + }) + ) + } + const https = await startHttpsFixture(['api.telegram.org'], async (request, response) => { + const method = request.url?.slice('/botdisposable-window-fixture/'.length) + const reply = (result: unknown) => + response + .writeHead(200, { 'content-type': 'application/json' }) + .end(JSON.stringify({ ok: true, result })) + if (method === 'getMe') reply({ id: 1, username: 'deepchat_fixture_bot' }) + else if (method === 'setMyCommands') reply(true) + else if (method === 'sendMessage') { + let body = '' + for await (const chunk of request) body += chunk + replies.push(JSON.parse(body).text) + reply({ message_id: replies.length }) + } else if (method === 'getUpdates') { + held = response + response.once('close', () => { + if (held === response) held = undefined + }) + flush() + } else response.writeHead(404).end() + }) + const active = await launchApp().catch(async (error) => { + await https.close() + throw error + }) + try { + await waitForAppReady(active.page) + await active.page.evaluate(async (url) => { + await window.deepchat.invoke('config.setCustomProxyUrl', { url }) + await window.deepchat.invoke('config.setProxyMode', { mode: 'custom' }) + }, https.proxyUrl) + const settingsPage = await openSettings(active) + const settings = { + botToken: 'disposable-window-fixture', + remoteEnabled: true, + defaultAgentId: '', + defaultWorkdir: '' + } + const send = async (text: string, expected: string) => { + const previous = replies.length + messages.push(text) + flush() + await expect.poll(() => replies.slice(previous).join('\n')).toContain(expected) + } + try { + await settingsPage.evaluate(() => + window.deepchat.invoke('config.updateEntries', { + changes: [ + { key: 'defaultModel', value: { providerId: 'openai', modelId: 'gpt-4o-mini' } } + ] + }) + ) + await settingsPage.evaluate( + (settings) => + window.deepchat.invoke('remoteControl.saveChannelSettings', { + channel: 'telegram', + settings + }), + settings + ) + const pairing = await settingsPage.evaluate(() => + window.deepchat.invoke('remoteControl.createChannelPairCode', { channel: 'telegram' }) + ) + await send(`/pair ${pairing.code}`, 'Pairing complete.') + await send('/open', 'No bound session.') + await send('/new Remote window fixture', 'Started a new session:') + await send('/open', 'Opened on desktop:') + const first = await active.page.evaluate(() => + window.deepchat.invoke('sessions.getActive', {}) + ) + expect(first.session?.title).toBe('Remote window fixture') + const nativeWindow = await active.electronApp.browserWindow(active.page) + await nativeWindow.evaluate((window) => window.destroy()) + + await send('/open', 'Opened on desktop:') + await expect + .poll( + () => + active.electronApp + .windows() + .filter((page) => page.url().includes('/renderer/index.html')).length + ) + .toBe(1) + const page = active.electronApp + .windows() + .find((page) => page.url().includes('/renderer/index.html'))! + await waitForAppReady(page) + await expect + .poll(async () => { + const { session } = await page.evaluate(() => + window.deepchat.invoke('sessions.getActive', {}) + ) + return session?.id + }) + .toBe(first.session!.id) + expect( + (await settingsPage.evaluate(() => window.deepchat.invoke('sessions.getActive', {}))) + .session + ).toBeNull() + } finally { + await settingsPage.evaluate( + (settings) => + window.deepchat.invoke('remoteControl.saveChannelSettings', { + channel: 'telegram', + settings: { ...settings, remoteEnabled: false } + }), + settings + ) + } + expect(await active.close()).toBe('graceful') + } finally { + try { + await active.close() + } finally { + await https.close() + } + } +}) diff --git a/test/e2e/specs/74-provider-oauth-owner.smoke.spec.ts b/test/e2e/specs/74-provider-oauth-owner.smoke.spec.ts new file mode 100644 index 0000000000..1fe3adef0f --- /dev/null +++ b/test/e2e/specs/74-provider-oauth-owner.smoke.spec.ts @@ -0,0 +1,301 @@ +import { test, expect } from '../fixtures/electronApp' +import { waitForAppReady } from '../helpers/wait' +import { startHttpsFixture } from '../helpers/httpsFixture' +import { readFileSync } from 'node:fs' +import { createServer, type ServerResponse } from 'node:http' +import type { Socket } from 'node:net' +import { join } from 'node:path' + +type CopilotFixture = { + mode: 'accept' | 'present' + presented: boolean + oauthRequests: number +} + +test.use({ privacyModeEnabled: true }) + +test('Copilot sign-in preserves credentials and drains native authorization at quit @smoke', async ({ + launchApp +}) => { + const heldValidation: ServerResponse[] = [] + const authorizations: Array = [] + let holdValidation = false + const respond = (response: ServerResponse) => + response.writeHead(200, { 'content-type': 'application/json' }).end( + JSON.stringify({ + token: 'disposable-copilot-session', + expires_at: Math.floor(Date.now() / 1000) + 3600 + }) + ) + const releaseValidation = () => { + holdValidation = false + for (const response of heldValidation.splice(0)) if (!response.destroyed) respond(response) + } + const https = await startHttpsFixture(['api.github.com'], (request, response) => { + if (request.url !== '/copilot_internal/v2/token') { + response.writeHead(404).end() + return + } + authorizations.push(request.headers.authorization) + if (holdValidation) heldValidation.push(response) + else respond(response) + }) + let cleanupApp: Awaited> | undefined + try { + let active = await launchApp() + cleanupApp = active + expect(active.ownsUserDataDir).toBe(true) + await waitForAppReady(active.page) + await active.page.evaluate(async (url) => { + await window.deepchat.invoke('config.setCustomProxyUrl', { url }) + await window.deepchat.invoke('config.setProxyMode', { mode: 'custom' }) + }, https.proxyUrl) + const provider = ( + await active.page.evaluate(() => window.deepchat.invoke('providers.list', {})) + ).providers.find((entry) => entry.apiType === 'github-copilot') + if (!provider) throw new Error('Missing built-in GitHub Copilot provider') + await active.page.evaluate( + (provider) => + window.deepchat.invoke('providers.setById', { providerId: provider.id, provider }), + { ...provider, apiKey: 'saved-disposable-token', enable: false } + ) + + const retirementReceipt = join(active.userDataDir, 'copilot-native-retirement.json') + await active.electronApp.evaluate(({ BrowserWindow, clipboard, shell }, receiptPath) => { + const originalFetch = globalThis.fetch + const originalLoad = BrowserWindow.prototype.loadURL + let windowClosed = false + const fixture: CopilotFixture = { + mode: 'accept', + presented: false, + oauthRequests: 0 + } + const publishRetirement = () => { + const fs = process.getBuiltinModule('node:fs') as typeof import('node:fs') + fs.writeFileSync( + receiptPath, + JSON.stringify({ closed: windowClosed, oauthRequests: fixture.oauthRequests }) + ) + } + BrowserWindow.prototype.loadURL = function (url, options) { + const loading = originalLoad.call(this, url, options) + if (url.startsWith('data:') && url.includes('GitHub%20Copilot%20Device%20Authentication')) { + if (fixture.mode === 'present') { + fixture.presented = true + this.once('closed', () => { + windowClosed = true + publishRetirement() + }) + } else + void loading.then(() => { + if (!this.isDestroyed()) this.close() + }) + } + return loading + } + clipboard.writeText = () => {} + shell.openExternal = async () => {} + globalThis.fetch = async (input, init) => { + const url = String(input) + if (url === 'https://github.com/login/device/code') + return Response.json({ + device_code: 'disposable-device', + user_code: 'TEST-CODE', + verification_uri: 'https://github.com/login/device', + expires_in: 900, + interval: 1 + }) + if (url === 'https://github.com/login/oauth/access_token') { + fixture.oauthRequests++ + if (fixture.mode === 'present') publishRetirement() + return Response.json({ access_token: 'new-disposable-token' }) + } + return await originalFetch(input, init) + } + ;(globalThis as typeof globalThis & { copilotFixture: CopilotFixture }).copilotFixture = + fixture + }, retirementReceipt) + let pending: Promise | undefined + try { + expect( + await active.page.evaluate( + (providerId) => + window.deepchat.invoke('oauth.githubCopilot.startDeviceFlowLogin', { providerId }), + provider.id + ) + ).toEqual({ success: true }) + const readProvider = async () => + ( + await active.page.evaluate(() => window.deepchat.invoke('providers.list', {})) + ).providers.find((entry) => entry.id === provider.id)! + expect((await readProvider()).apiKey).toBe('new-disposable-token') + expect(authorizations).toEqual(['token new-disposable-token']) + + holdValidation = true + const login = active.page.evaluate( + (providerId) => + window.deepchat.invoke('oauth.githubCopilot.startDeviceFlowLogin', { providerId }), + provider.id + ) + const stale = expect(login).rejects.toThrow('configuration changed') + await expect.poll(() => heldValidation.length).toBe(1) + const current = await readProvider() + await active.page.evaluate( + (provider) => + window.deepchat.invoke('providers.setById', { providerId: provider.id, provider }), + { ...current, name: 'Current Copilot', apiKey: 'newer-disposable-token' } + ) + releaseValidation() + await stale + expect(await readProvider()).toMatchObject({ + name: 'Current Copilot', + apiKey: 'newer-disposable-token', + enable: false + }) + await active.electronApp.evaluate(() => { + const fixture = (globalThis as typeof globalThis & { copilotFixture: CopilotFixture }) + .copilotFixture + fixture.mode = 'present' + fixture.presented = false + }) + pending = active.page + .evaluate( + (providerId) => + window.deepchat.invoke('oauth.githubCopilot.startDeviceFlowLogin', { providerId }), + provider.id + ) + .catch(() => undefined) + await expect + .poll(() => + active.electronApp.evaluate( + () => + (globalThis as typeof globalThis & { copilotFixture: CopilotFixture }).copilotFixture + .presented + ) + ) + .toBe(true) + } finally { + releaseValidation() + expect(await active.close()).toBe('graceful') + await pending + } + const retired = JSON.parse(readFileSync(retirementReceipt, 'utf8')) + expect(retired).toEqual({ closed: true, oauthRequests: 2 }) + await test.info().attach('copilot-native-retirement', { + body: JSON.stringify(retired), + contentType: 'application/json' + }) + active = await launchApp() + cleanupApp = active + await waitForAppReady(active.page) + expect( + ( + await active.page.evaluate(() => window.deepchat.invoke('providers.list', {})) + ).providers.find((entry) => entry.id === provider.id) + ).toMatchObject({ name: 'Current Copilot', apiKey: 'newer-disposable-token', enable: false }) + expect(await active.close()).toBe('graceful') + } finally { + releaseValidation() + try { + await cleanupApp?.close() + } finally { + await https.close() + } + } +}) + +test('Native authorization HTTP drains current and retired proxy pools at quit @smoke', async ({ + launchApp +}) => { + const sockets = new Set() + const requests: ServerResponse[] = [] + const server = createServer((request, response) => { + if (request.url === '/native-code') requests.push(response) + else response.writeHead(404).end() + }) + server.on('connection', (socket) => { + sockets.add(socket) + socket.once('close', () => sockets.delete(socket)) + }) + await new Promise((resolve) => server.listen(0, '127.0.0.1', resolve)) + const origin = `http://127.0.0.1:${(server.address() as { port: number }).port}` + let active: Awaited> | undefined + const pending: Promise[] = [] + try { + active = await launchApp() + expect(active.ownsUserDataDir).toBe(true) + await waitForAppReady(active.page) + const provider = ( + await active.page.evaluate(() => window.deepchat.invoke('providers.list', {})) + ).providers.find((entry) => entry.apiType === 'github-copilot') + if (!provider) throw new Error('Missing built-in GitHub Copilot provider') + await active.page.evaluate( + async (provider) => { + await window.deepchat.invoke('config.setProxyMode', { mode: 'none' }) + await window.deepchat.invoke('providers.setById', { providerId: provider.id, provider }) + }, + { ...provider, apiKey: 'unchanged-native-http-token', enable: false } + ) + await active.electronApp.evaluate((_electron, origin) => { + const original = globalThis.fetch + globalThis.fetch = async (input, init) => { + if (String(input) === 'https://github.com/login/device/code') { + // Ignore caller abort so retirement must reach the actual transport. + return await original(`${origin}/native-code`, { ...init, signal: undefined }) + } + return await original(input, init) + } + }, origin) + pending.push( + active.page + .evaluate( + (providerId) => + window.deepchat.invoke('oauth.githubCopilot.startDeviceFlowLogin', { providerId }), + provider.id + ) + .catch(() => undefined) + ) + await expect.poll(() => requests.length).toBe(1) + await active.page.evaluate(async (url) => { + await window.deepchat.invoke('config.setCustomProxyUrl', { url }) + await window.deepchat.invoke('config.setProxyMode', { mode: 'custom' }) + }, origin) + pending.push( + active.page + .evaluate( + (providerId) => + window.deepchat.invoke('oauth.githubCopilot.startDeviceFlowLogin', { providerId }), + provider.id + ) + .catch(() => undefined) + ) + await expect.poll(() => requests.length).toBe(2) + const disconnected = requests.map( + (response) => new Promise((resolve) => response.once('close', resolve)) + ) + expect(await active.close()).toBe('graceful') + await Promise.all(disconnected) + await Promise.all(pending) + expect(requests.every((response) => response.destroyed)).toBe(true) + await test.info().attach('native-http-retirement', { + body: JSON.stringify({ + requests: requests.length, + disconnected: requests.every((response) => response.destroyed) + }), + contentType: 'application/json' + }) + active = await launchApp() + await waitForAppReady(active.page) + expect( + ( + await active.page.evaluate(() => window.deepchat.invoke('providers.list', {})) + ).providers.find((entry) => entry.id === provider.id) + ).toMatchObject({ apiKey: 'unchanged-native-http-token', enable: false }) + expect(await active.close()).toBe('graceful') + } finally { + for (const socket of sockets) socket.destroy() + await active?.close() + await Promise.all(pending) + await new Promise((resolve) => server.close(() => resolve())) + } +}) diff --git a/test/e2e/specs/75-ocr-cache-key.smoke.spec.ts b/test/e2e/specs/75-ocr-cache-key.smoke.spec.ts new file mode 100644 index 0000000000..ffbe5cfd9a --- /dev/null +++ b/test/e2e/specs/75-ocr-cache-key.smoke.spec.ts @@ -0,0 +1,64 @@ +import { existsSync, readFileSync } from 'node:fs' +import { join } from 'node:path' +import { test, expect } from '../fixtures/electronApp' +import { openSettings } from '../helpers/settings' +import { waitForAppReady } from '../helpers/wait' + +test.use({ privacyModeEnabled: true }) + +test('OCR cache keeps native key ownership and encryption across relaunch @smoke', async ({ + launchApp +}, testInfo) => { + let active = await launchApp() + expect(active.ownsUserDataDir).toBe(true) + const clearCache = async () => { + await waitForAppReady(active.page) + const settings = await openSettings(active) + return settings.evaluate(async (node) => { + await window.deepchat.invoke('toolchains.setSource', { + kind: 'node', + selection: { source: 'custom', customPath: node } + }) + const status = await window.deepchat.invoke('ocr.getRuntimeStatus', {}) + if (status.availability.status !== 'available') + throw new Error(`OCR runtime is unavailable: ${status.availability.reason}`) + const { cache } = await window.deepchat.invoke('ocr.clearCache', {}) + return cache + }, process.execPath) + } + const first = await clearCache() + const nativeEncryptionAvailable = await active.electronApp.evaluate(({ safeStorage }) => + safeStorage.isEncryptionAvailable() + ) + if (process.platform !== 'linux' && nativeEncryptionAvailable) + expect(first.mode).toBe('persistent') + expect(first.entryCount).toBe(0) + const keyPath = join(active.userDataDir, 'ocr', 'cache-key.json') + const databasePath = join(active.userDataDir, 'ocr', 'ocr-cache.db') + let wrappedKey: Buffer | null = null + if (first.mode === 'persistent') { + expect(existsSync(keyPath)).toBe(true) + expect( + readFileSync(databasePath).subarray(0, 16).equals(Buffer.from('SQLite format 3\0')) + ).toBe(false) + wrappedKey = readFileSync(keyPath) + } else { + expect(first.persistenceUnavailableReason).toBe('safe_storage_unavailable') + expect(existsSync(databasePath)).toBe(false) + } + expect(await active.close()).toBe('graceful') + active = await launchApp() + const second = await clearCache() + expect(second.mode).toBe(first.mode) + expect(second.entryCount).toBe(0) + if (wrappedKey) expect(readFileSync(keyPath).equals(wrappedKey)).toBe(true) + expect(await active.close()).toBe('graceful') + await testInfo.attach('ocr-cache-ownership.json', { + body: JSON.stringify({ + mode: first.mode, + relaunchMode: second.mode, + nativeEncryptionAvailable + }), + contentType: 'application/json' + }) +}) diff --git a/test/e2e/specs/76-deeplink-owner.smoke.spec.ts b/test/e2e/specs/76-deeplink-owner.smoke.spec.ts new file mode 100644 index 0000000000..2702650dfe --- /dev/null +++ b/test/e2e/specs/76-deeplink-owner.smoke.spec.ts @@ -0,0 +1,51 @@ +import { test, expect } from '../fixtures/electronApp' +import { waitForAppReady } from '../helpers/wait' + +test.use({ privacyModeEnabled: true }) + +test('OS provider deeplinks observe existing providers and preserve saved credentials @smoke', async ({ + app +}) => { + await waitForAppReady(app.page) + const provider = await app.page.evaluate(async () => { + const id = `custom-${crypto.randomUUID()}` + return ( + await window.deepchat.invoke('providers.add', { + provider: { + id, + name: 'Deep link owner fixture', + apiType: 'openai-completions', + baseUrl: 'https://saved.example.com/v1', + apiKey: 'saved-only-fixture-key', + enable: false, + custom: true, + customModels: [] + } + }) + ).provider + }) + const baseUrl = 'https://incoming.example.com/v1' + const payload = Buffer.from( + JSON.stringify({ id: provider.id, baseUrl, apiKey: 'incoming-only-fixture-key' }) + ).toString('base64') + const link = `deepchat://provider/install?v=1&data=${encodeURIComponent(payload)}` + await app.electronApp.evaluate(({ app }, link) => { + app.emit('open-url', { preventDefault() {}, defaultPrevented: false }, link) + }, link) + await expect + .poll(() => + app.electronApp.windows().some((page) => page.url().includes('/settings/index.html')) + ) + .toBe(true) + const settings = app.electronApp + .windows() + .find((page) => page.url().includes('/settings/index.html'))! + await expect(settings.getByTestId('settings-provider-page')).toBeVisible() + await expect(settings.getByText(baseUrl, { exact: true })).toBeVisible() + const saved = ( + await app.page.evaluate(() => window.deepchat.invoke('providers.list', {})) + ).providers.find((item) => item.id === provider.id) + expect(saved?.apiKey).toBe('saved-only-fixture-key') + expect(saved?.baseUrl).toBe('https://saved.example.com/v1') + expect(app.pageErrors).toEqual([]) +}) diff --git a/test/e2e/specs/77-cli-approval.smoke.spec.ts b/test/e2e/specs/77-cli-approval.smoke.spec.ts new file mode 100644 index 0000000000..ba9308cc28 --- /dev/null +++ b/test/e2e/specs/77-cli-approval.smoke.spec.ts @@ -0,0 +1,108 @@ +import { execFile } from 'node:child_process' +import { resolve } from 'node:path' +import { promisify } from 'node:util' +import { LocalControlRpcResponseSchema } from '../../../src/shared/contracts/localControl' +import { settingsGetSnapshotRoute } from '../../../src/shared/contracts/routes/settings.routes' +import { test, expect } from '../fixtures/electronApp' +import { waitForAppReady } from '../helpers/wait' +import { openSettings } from '../helpers/settings' + +const execute = promisify(execFile) +test.use({ privacyModeEnabled: true }) + +test('CLI security changes require renderer approval and persist across reopen @smoke', async ({ + launchApp +}) => { + let active = await launchApp() + const invoke = async (...args: string[]) => { + const environment: NodeJS.ProcessEnv = { + ...process.env, + DEEPCHAT_E2E_USER_DATA_DIR: active.userDataDir + } + delete environment.DEEPCHAT_CLI_AGENT_TOKEN + let stdout: string + try { + const result = await execute( + process.execPath, + [resolve('out/cli/deepchat.mjs'), ...args, '--json'], + { env: environment, timeout: 60_000 } + ) + stdout = result.stdout + } catch (error) { + if (!error || typeof error !== 'object' || !('stdout' in error)) throw error + if (typeof error.stdout !== 'string') throw error + stdout = error.stdout + } + return LocalControlRpcResponseSchema.parse(JSON.parse(stdout)) + } + const readLogging = async () => { + const reply = await invoke('settings', 'get', '--keys', 'loggingEnabled') + expect(reply.ok).toBe(true) + if (!reply.ok) throw new Error(reply.error.message) + return settingsGetSnapshotRoute.output.parse(reply.result).values.loggingEnabled + } + + await waitForAppReady(active.page) + await expect.poll(async () => await invoke('system', 'version')).toMatchObject({ ok: true }) + const initial = await readLogging() + expect(typeof initial).toBe('boolean') + const change = (value = !initial) => { + const pending = invoke('settings', 'set', '--key', 'loggingEnabled', '--value', String(value)) + void pending.catch(() => undefined) + return pending + } + const dialog = active.page.getByRole('dialog').filter({ hasText: 'settings.updatePublic' }) + + const denied = change() + await dialog.getByRole('button', { name: 'Deny', exact: true }).click() + await expect(denied).resolves.toMatchObject({ ok: false, error: { code: 'approval_denied' } }) + expect(await readLogging()).toBe(initial) + + const approved = change() + await dialog.getByRole('button', { name: 'Allow Once', exact: true }).click() + await expect(approved).resolves.toMatchObject({ ok: true }) + expect(await readLogging()).toBe(!initial) + expect(await active.close()).toBe('graceful') + + active = await launchApp() + await waitForAppReady(active.page) + await expect.poll(async () => await invoke('system', 'version')).toMatchObject({ ok: true }) + expect(await readLogging()).toBe(!initial) + + const settings = await openSettings(active) + const interrupted = change(initial).then( + (result) => result.ok, + () => false + ) + const approval = active.page.getByRole('dialog').filter({ hasText: 'settings.updatePublic' }) + await expect(approval).toBeVisible() + const appProcess = active.electronApp.process() + await active.electronApp.evaluate(({ webContents }, url) => { + const target = webContents.getAllWebContents().find((contents) => contents.getURL() === url) + if (!target) throw new Error('Owned settings renderer is missing') + const original = webContents.fromId + webContents.fromId = (id) => { + if (id === target.id) throw new Error('Native renderer registration was refused') + return original.call(webContents, id) + } + }, settings.url()) + const creation = settings + .evaluate(() => + window.deepchat.invoke('sessions.create', { + agentId: 'deepchat', + providerId: 'openai', + modelId: 'gpt-4o-mini', + message: '' + }) + ) + .catch(() => undefined) + await expect.poll(() => appProcess.exitCode).toBe(1) + await creation + expect(await interrupted).toBe(false) + + active = await launchApp() + await waitForAppReady(active.page) + await expect.poll(async () => await invoke('system', 'version')).toMatchObject({ ok: true }) + expect(await readLogging()).toBe(!initial) + expect(await active.close()).toBe('graceful') +}) diff --git a/test/e2e/specs/78-harness-control.smoke.spec.ts b/test/e2e/specs/78-harness-control.smoke.spec.ts new file mode 100644 index 0000000000..973c6b120c --- /dev/null +++ b/test/e2e/specs/78-harness-control.smoke.spec.ts @@ -0,0 +1,487 @@ +import { createServer } from 'node:http' +import { mkdirSync } from 'node:fs' +import { join } from 'node:path' +import Database from 'better-sqlite3-multiple-ciphers' +import type { Page } from '@playwright/test' +import type { AssistantMessageBlock } from '../../../src/shared/types/agent-interface' +import { test, expect, type ElectronAppInstance } from '../fixtures/electronApp' +import { waitForAppReady } from '../helpers/wait' + +test.use({ privacyModeEnabled: true }) + +const MODEL = 'harness-control-fixture' +const PROVIDER = 'custom-harness-control-fixture' +const QUESTIONS = 'HARNESS_CONTROL_QUESTIONS' +const ACTIVE = 'HARNESS_CONTROL_ACTIVE' +const PROMOTED = 'HARNESS_CONTROL_PROMOTED' +const TAIL = 'HARNESS_CONTROL_TAIL' +const RESTORED_A = 'HARNESS_CONTROL_RESTORED_A' +const RESTORED_B = 'HARNESS_CONTROL_RESTORED_B' +const BARRIER = 'HARNESS_CONTROL_BARRIER' +const FIRST = 'control-question-first' +const SECOND = 'control-question-second' + +async function startModel() { + const requests: Array<{ marker: string; toolIds: string[]; toolText: string }> = [] + const cancelled = new Set() + const errors: string[] = [] + const gates = new Map([ + [ACTIVE, Promise.withResolvers()], + [TAIL, Promise.withResolvers()] + ]) + const server = createServer(async (request, response) => { + try { + if (request.url === '/v1/models') { + response + .writeHead(200, { 'Content-Type': 'application/json' }) + .end(JSON.stringify({ data: [{ id: MODEL }] })) + return + } + if (request.url !== '/v1/chat/completions') { + response.writeHead(404).end() + return + } + let source = '' + for await (const chunk of request) source += chunk + const body = JSON.parse(source) as { + stream?: boolean + messages: Array<{ role: string; content: unknown; tool_call_id?: string }> + } + if (!body.stream) { + response.writeHead(200, { 'Content-Type': 'application/json' }).end( + JSON.stringify({ + id: 'control-title', + object: 'chat.completion', + created: 1, + model: MODEL, + choices: [ + { + index: 0, + message: { role: 'assistant', content: 'Harness control' }, + finish_reason: 'stop' + } + ] + }) + ) + return + } + const lastUser = JSON.stringify( + body.messages.filter((message) => message.role === 'user').at(-1)?.content + ) + const marker = [QUESTIONS, ACTIVE, PROMOTED, TAIL, RESTORED_A, RESTORED_B, BARRIER].find( + (value) => lastUser?.includes(value) + ) + if (!marker) throw new Error(`Unexpected fixture request: ${lastUser}`) + const toolResults = body.messages.filter((message) => message.role === 'tool') + requests.push({ + marker, + toolIds: toolResults.map((message) => message.tool_call_id ?? ''), + toolText: JSON.stringify(toolResults.map((message) => message.content)) + }) + response.writeHead(200, { 'Content-Type': 'text/event-stream' }) + const emit = (delta: unknown, finishReason: string | null = null) => + response.write( + `data: ${JSON.stringify({ + id: `control-${marker}`, + object: 'chat.completion.chunk', + created: 1, + model: MODEL, + choices: [{ index: 0, delta, finish_reason: finishReason }] + })}\n\n` + ) + if (marker === QUESTIONS && toolResults.length === 0) { + emit( + { + role: 'assistant', + tool_calls: [FIRST, SECOND].map((id, index) => ({ + index, + id, + type: 'function', + function: { + name: 'deepchat_question', + arguments: JSON.stringify({ + question: index === 0 ? 'Choose the first answer.' : 'Choose the second answer.', + options: [{ label: index === 0 ? 'FIRST_ANSWER' : 'SECOND_ANSWER' }], + custom: false + }) + } + })) + }, + 'tool_calls' + ) + } else { + emit({ role: 'assistant', content: `${marker}_STARTED` }) + const gate = gates.get(marker) + if (gate) { + response.once('close', () => { + if (!response.writableEnded) cancelled.add(marker) + gate.resolve() + }) + await gate.promise + } + if (response.destroyed) return + emit({ content: ` ${marker}_DONE` }, 'stop') + } + response.end('data: [DONE]\n\n') + } catch (error) { + errors.push(String(error)) + if (!response.destroyed) { + if (!response.headersSent) response.writeHead(500) + response.end() + } + } + }) + await new Promise((resolve) => server.listen(0, '127.0.0.1', resolve)) + const { port } = server.address() as { port: number } + const release = () => { + for (const gate of gates.values()) gate.resolve() + } + return { + baseUrl: `http://127.0.0.1:${port}/v1`, + requests, + cancelled, + errors, + release, + async close() { + release() + server.closeAllConnections() + await new Promise((resolve) => server.close(() => resolve())) + } + } +} + +async function configure(app: ElectronAppInstance, baseUrl: string) { + expect(app.ownsUserDataDir).toBe(true) + await waitForAppReady(app.page) + await app.page.evaluate( + async ({ baseUrl, providerId, modelId }) => { + await window.deepchat.invoke('providers.add', { + provider: { + id: providerId, + name: 'Harness control fixture', + apiType: 'openai-completions', + baseUrl, + apiKey: 'fixture-key', + enable: true, + custom: true + } + }) + await window.deepchat.invoke('models.addCustom', { + providerId, + model: { + id: modelId, + name: 'Harness control fixture', + enabled: true, + vision: false, + functionCall: true, + reasoning: false, + contextLength: 32000, + maxTokens: 8000 + } + }) + await window.deepchat.invoke('models.setStatus', { providerId, modelId, enabled: true }) + const { config } = await window.deepchat.invoke('config.resolveDeepChatAgentConfig', { + agentId: 'deepchat' + }) + await window.deepchat.invoke('config.updateDeepChatAgent', { + agentId: 'deepchat', + updates: { + config: { + ...config, + memoryEnabled: false, + subagentEnabled: false, + permissionMode: 'full_access', + enabledSkillNames: [], + disabledAgentTools: [] + } + } + }) + }, + { baseUrl, providerId: PROVIDER, modelId: MODEL } + ) +} + +async function createSession(app: ElectronAppInstance, message: string) { + return await app.page.evaluate( + async ({ message, projectDir, providerId, modelId }) => { + const { session } = await window.deepchat.invoke('sessions.create', { + agentId: 'deepchat', + message, + projectDir, + providerId, + modelId, + permissionMode: 'full_access', + toolModeOverride: 'agent' + }) + return session.id + }, + { + message, + projectDir: join(app.userDataDir, 'control-workspace'), + providerId: PROVIDER, + modelId: MODEL + } + ) +} + +const restore = (page: Page, sessionId: string) => + page.evaluate( + (sessionId) => window.deepchat.invoke('sessions.restore', { sessionId, limit: 100 }), + sessionId + ) +const pending = (page: Page, sessionId: string) => + page.evaluate( + (sessionId) => window.deepchat.invoke('sessions.listPendingInputs', { sessionId }), + sessionId + ) +const blocks = (content: string) => JSON.parse(content) as AssistantMessageBlock[] +const questionActions = (content: string) => + blocks(content).filter( + (block) => block.type === 'action' && block.action_type === 'question_request' + ) +const pendingQuestions = (content: string) => + questionActions(content).filter( + (block) => block.status === 'pending' && block.extra?.needsUserAction !== false + ) + +function storedFacts(userDataDir: string, sessionId: string) { + const db = new Database(join(userDataDir, 'app_db', 'agent.db'), { readonly: true }) + try { + return { + messages: db + .prepare( + 'SELECT role, content, metadata FROM deepchat_messages WHERE session_id = ? ORDER BY order_seq' + ) + .all(sessionId) as Array<{ role: string; content: string; metadata: string }>, + pending: db + .prepare( + "SELECT state, payload_json FROM deepchat_pending_inputs WHERE session_id = ? AND state != 'consumed'" + ) + .all(sessionId) as Array<{ state: string; payload_json: string }> + } + } finally { + db.close() + } +} + +test('ordered questions resume once through public interaction routes @smoke', async ({ + launchApp +}) => { + const model = await startModel() + let app: ElectronAppInstance | undefined + try { + app = await launchApp({ + prepareUserData: (directory) => + mkdirSync(join(directory, 'control-workspace'), { recursive: true }) + }) + await configure(app, model.baseUrl) + const sessionId = await createSession(app, QUESTIONS) + const page = app.page + await expect + .poll(async () => + (await restore(page, sessionId)).messages + .filter((message) => message.role === 'assistant') + .flatMap((message) => + pendingQuestions(message.content).map((block) => block.tool_call?.id) + ) + ) + .toEqual([FIRST, SECOND]) + const snapshot = await restore(page, sessionId) + const messageId = snapshot.messages.find( + (message) => message.role === 'assistant' && pendingQuestions(message.content).length === 2 + )!.id + const reply = (toolCallId: string, optionLabel: string) => + page.evaluate( + async (input) => { + try { + return { value: await window.deepchat.invoke('chat.respondToolInteraction', input) } + } catch (error) { + return { error: String(error) } + } + }, + { + sessionId, + messageId, + toolCallId, + response: { kind: 'question_option' as const, optionLabel } + } + ) + expect(await reply(SECOND, 'SECOND_ANSWER')).toMatchObject({ + error: expect.stringMatching(/out of order/i) + }) + const firstReplies = await Promise.all([ + reply(FIRST, 'FIRST_ANSWER'), + reply(FIRST, 'FIRST_ANSWER') + ]) + expect(firstReplies.some((result) => result.value?.accepted)).toBe(true) + expect(firstReplies.every((result) => result.value?.resumed !== true)).toBe(true) + expect(model.requests).toHaveLength(1) + expect( + (await restore(page, sessionId)).messages + .filter((message) => message.role === 'assistant') + .flatMap((message) => pendingQuestions(message.content).map((block) => block.tool_call?.id)) + ).toEqual([SECOND]) + expect(await reply(SECOND, 'SECOND_ANSWER')).toMatchObject({ + value: { accepted: true, resumed: true } + }) + await expect.poll(async () => (await restore(page, sessionId)).session?.status).toBe('idle') + const finished = await restore(page, sessionId) + expect( + questionActions(finished.messages.find((message) => message.id === messageId)!.content).map( + (block) => block.extra?.answerText + ) + ).toEqual(['FIRST_ANSWER', 'SECOND_ANSWER']) + expect(model.requests.map((request) => request.toolIds)).toEqual([[], [FIRST, SECOND]]) + expect(model.requests[1].toolText).toContain('FIRST_ANSWER') + expect(model.requests[1].toolText).toContain('SECOND_ANSWER') + expect(model.errors).toEqual([]) + expect(app.pageErrors).toEqual([]) + expect(await app.close()).toBe('graceful') + } finally { + model.release() + try { + await app?.close() + } finally { + await model.close() + } + } +}) + +test('Steer cancellation and queue recovery preserve each user fact once @smoke', async ({ + launchApp +}) => { + const model = await startModel() + let app: ElectronAppInstance | undefined + try { + app = await launchApp({ + prepareUserData: (directory) => + mkdirSync(join(directory, 'control-workspace'), { recursive: true }) + }) + await configure(app, model.baseUrl) + const sessionId = await createSession(app, ACTIVE) + await expect.poll(() => model.requests.map((request) => request.marker)).toEqual([ACTIVE]) + await expect + .poll(async () => + (await restore(app!.page, sessionId)).messages + .filter((message) => message.role === 'assistant') + .flatMap((message) => blocks(message.content)) + .some((block) => block.content?.includes(`${ACTIVE}_STARTED`)) + ) + .toBe(true) + const queued = await app.page.evaluate( + async ({ sessionId, promoted, tail }) => { + const first = await window.deepchat.invoke('sessions.queuePendingInput', { + sessionId, + content: promoted + }) + const second = await window.deepchat.invoke('sessions.queuePendingInput', { + sessionId, + content: tail + }) + const result = await window.deepchat.invoke('sessions.steerPendingInput', { + sessionId, + itemId: first.item.id + }) + return { promoted: result.item, tail: second.item } + }, + { sessionId, promoted: PROMOTED, tail: TAIL } + ) + expect(queued.promoted.mode).toBe('steer') + expect(queued.tail.state).toBe('pending') + expect( + await app.page.evaluate( + (sessionId) => window.deepchat.invoke('chat.stopStream', { sessionId }), + sessionId + ) + ).toEqual({ stopped: true }) + await expect.poll(() => model.cancelled.has(ACTIVE)).toBe(true) + await expect + .poll(() => model.requests.map((request) => request.marker)) + .toEqual([ACTIVE, PROMOTED, TAIL]) + await app.page.evaluate( + async ({ sessionId, first, second }) => { + await window.deepchat.invoke('sessions.queuePendingInput', { sessionId, content: first }) + await window.deepchat.invoke('sessions.queuePendingInput', { sessionId, content: second }) + }, + { sessionId, first: RESTORED_A, second: RESTORED_B } + ) + expect((await pending(app.page, sessionId)).items.map((item) => item.payload.text)).toEqual([ + RESTORED_A, + RESTORED_B + ]) + expect(await app.close()).toBe('graceful') + const interrupted = storedFacts(app.userDataDir, sessionId) + expect( + interrupted.pending + .filter((row) => [RESTORED_A, RESTORED_B].includes(JSON.parse(row.payload_json).text)) + .map((row) => row.state) + ).toEqual(['pending', 'pending']) + app = await launchApp() + await waitForAppReady(app.page) + const barrierId = await createSession(app, BARRIER) + await expect + .poll(async () => (await restore(app!.page, barrierId)).session?.status) + .toBe('idle') + expect(model.requests.map((request) => request.marker)).toEqual([ + ACTIVE, + PROMOTED, + TAIL, + BARRIER + ]) + const recovered = await pending(app.page, sessionId) + expect(recovered.resumeAvailable).toBe(true) + expect(recovered.items.map((item) => ({ text: item.payload.text, state: item.state }))).toEqual( + [ + { text: RESTORED_A, state: 'pending' }, + { text: RESTORED_B, state: 'pending' } + ] + ) + expect( + await app.page.evaluate( + (sessionId) => window.deepchat.invoke('sessions.resumePendingQueue', { sessionId }), + sessionId + ) + ).toEqual({ started: true }) + await expect.poll(async () => (await pending(app!.page, sessionId)).items).toEqual([]) + await expect + .poll(async () => (await restore(app!.page, sessionId)).session?.status) + .toBe('idle') + expect(model.requests.map((request) => request.marker)).toEqual([ + ACTIVE, + PROMOTED, + TAIL, + BARRIER, + RESTORED_A, + RESTORED_B + ]) + expect(model.errors).toEqual([]) + expect(app.pageErrors).toEqual([]) + const messages = (await restore(app.page, sessionId)).messages + expect( + messages + .filter((message) => message.role === 'user') + .map((message) => JSON.parse(message.content).text) + ).toEqual([ACTIVE, PROMOTED, TAIL, RESTORED_A, RESTORED_B]) + expect(await app.close()).toBe('graceful') + const persisted = storedFacts(app.userDataDir, sessionId) + expect(persisted.pending).toEqual([]) + expect( + persisted.messages + .filter((message) => message.role === 'user') + .map((message) => JSON.parse(message.content).text) + ).toEqual([ACTIVE, PROMOTED, TAIL, RESTORED_A, RESTORED_B]) + expect( + persisted.messages.some( + (message) => + message.role === 'assistant' && JSON.parse(message.metadata).runOutcome === 'aborted' + ) + ).toBe(true) + } finally { + model.release() + try { + await app?.close() + } finally { + await model.close() + } + } +}) diff --git a/test/fixtures/harness/control-bridge.ts b/test/fixtures/harness/control-bridge.ts new file mode 100644 index 0000000000..b6cf4ae6d8 --- /dev/null +++ b/test/fixtures/harness/control-bridge.ts @@ -0,0 +1,44 @@ +import { definePlugin } from '@arcships/rutis' +import { CorePayloadStore } from '../../../src/harness/core/payload' +import type { CoreControlService } from '../../../src/harness/core/control' +import { CoreCarrierConfigSchema, type CorePayload } from '../../../src/shared/harness/core' +import type { HarnessControlCommand } from '../../../src/shared/harness/control' + +export default definePlugin({ + name: 'control-test-bridge', + inject: ['deepchatCoreNative'], + provides: { + deepchatCore: { identity: 'async', invokeRoute: 'async', status: 'async', stop: 'async' } + }, + async apply(ctx, input) { + const config = CoreCarrierConfigSchema.parse(input) + const payloads = new CorePayloadStore(config, 'core') + const native = ctx.use('deepchatCoreNative') + let stopped = false + const status = () => ({ + generation: config.generation, + pid: process.pid, + node: process.versions.node, + modules: process.versions.modules, + phase: stopped ? 'stopped' : 'idle', + businessReady: false + }) + ctx.effect(async () => await payloads.close()) + ctx.provide('deepchatCore', { + identity: status, + status: async () => await payloads.encode(status()), + async invokeRoute(ref: CorePayload) { + const { input } = (await payloads.decode(ref)) as { + input: { sequence: number; command: HarnessControlCommand } + } + return await payloads.encode( + await native.control(config.generation, input.sequence, input.command) + ) + }, + async stop() { + stopped = true + return await payloads.encode(status()) + } + }) + } +}) diff --git a/test/fixtures/harness/core-admission.ts b/test/fixtures/harness/core-admission.ts new file mode 100644 index 0000000000..276dacc4e3 --- /dev/null +++ b/test/fixtures/harness/core-admission.ts @@ -0,0 +1,84 @@ +import { randomUUID } from 'node:crypto' +import { definePlugin } from '@arcships/rutis' +import { createCoreAdmission, type CoreAdmissionService } from '../../../src/harness/core/admission' +import { CorePayloadStore } from '../../../src/harness/core/payload' +import { CoreCarrierConfigSchema, type CorePayload } from '../../../src/shared/harness/core' + +export default definePlugin({ + name: 'core-admission-contract', + inject: ['deepchatCoreNative'], + provides: { + deepchatCore: { identity: 'async', configure: 'async', status: 'async', stop: 'async' } + }, + async apply(ctx, input) { + const config = CoreCarrierConfigSchema.parse(input) + const payloads = new CorePayloadStore(config, 'core') + const service = ctx.use< + CoreAdmissionService & { + invoke(generation: string, method: string, args: CorePayload): Promise + } + >('deepchatCoreNative') + const admission = await createCoreAdmission(service, config.generation, { + observationsEnabled: () => true + }) + let stopped = false + const status = () => ({ + generation: config.generation, + pid: process.pid, + node: process.versions.node, + modules: process.versions.modules, + phase: stopped ? 'stopped' : 'idle', + businessReady: false + }) + ctx.effect(async () => { + await admission.close() + await payloads.close() + }) + ctx.provide('deepchatCore', { + identity: status, + status: async () => await payloads.encode(status()), + async configure(ref: CorePayload) { + await payloads.decode(ref) + try { + await service.admission(randomUUID(), 0, 'limits', []) + throw new Error('Foreign generation was admitted') + } catch (error) { + if (!(error instanceof Error) || !error.message.includes('generation is unavailable')) + throw error + } + const controller = new AbortController() + const cancelled = admission.acquire({ + ownerId: 'cancelled-core', + signal: controller.signal + }) + controller.abort() + try { + await cancelled + throw new Error('Cancelled Core acquisition was granted') + } catch (error) { + if (!(error instanceof Error) || error.name !== 'AgentInvocationAdmissionAbortedError') + throw error + } + const permit = await admission.acquire({ ownerId: 'core-work' }) + try { + await payloads.decode( + await service.invoke( + config.generation, + 'profile.logging', + await payloads.encode([true]) + ) + ) + } finally { + permit.release() + await admission.flushObservations() + } + return await payloads.encode(await admission.snapshot()) + }, + async stop() { + await admission.close() + stopped = true + return await payloads.encode(status()) + } + }) + } +}) diff --git a/test/fixtures/harness/core-control.ts b/test/fixtures/harness/core-control.ts new file mode 100644 index 0000000000..a72f647f97 --- /dev/null +++ b/test/fixtures/harness/core-control.ts @@ -0,0 +1,548 @@ +import assert from 'node:assert/strict' +import { definePlugin } from '@arcships/rutis' +import { POSIX_COMMAND_SHELL } from '../../helpers/commandShell' +import { CoreHarnessControl, type CoreControlService } from '../../../src/harness/core/control' +import { CorePayloadStore } from '../../../src/harness/core/payload' +import { DeepChatAgentRuntime } from '../../../src/main/agent/deepchat/instance/deepChatAgentRuntime' +import { SessionStatusPublisher } from '../../../src/main/agent/deepchat/runtime/sessionStatusPublisher' +import { RunLifecycleCoordinator } from '../../../src/main/agent/deepchat/runtime/runLifecycleCoordinator' +import { ProviderPermissionCoordinator } from '../../../src/main/agent/deepchat/runtime/providerPermissionCoordinator' +import { createLoopRun } from '../../../src/main/agent/deepchat/loop/loopRun' +import { toAppSessionId } from '../../../src/main/agent/shared/agentSessionIds' +import { CoreCarrierConfigSchema, type CorePayload } from '../../../src/shared/harness/core' +import type { DeepChatExecutionContract } from '../../../src/shared/types/execution-contract' + +export default definePlugin({ + name: 'core-control-contract', + inject: ['deepchatCoreNative'], + provides: { + deepchatCore: { identity: 'async', configure: 'async', status: 'async', stop: 'async' } + }, + async apply(ctx, input) { + const config = CoreCarrierConfigSchema.parse(input) + const payloads = new CorePayloadStore(config, 'core') + const service = ctx.use('deepchatCoreNative') + let queuedAcceptance: (() => void) | undefined + let failed = false + const control = new CoreHarnessControl( + { + async control(generation, sequence, command) { + const receipt = await service.control(generation, sequence, command) + if (command.method === 'queueAcceptance') { + const queued = queuedAcceptance + queuedAcceptance = undefined + queued?.() + } + return receipt + } + }, + config.generation, + () => { + failed = true + } + ) + const runtime = new DeepChatAgentRuntime(control) + let stopped = false + const status = () => ({ + generation: config.generation, + pid: process.pid, + node: process.versions.node, + modules: process.versions.modules, + phase: stopped ? 'stopped' : 'idle', + businessReady: false + }) + ctx.effect(async () => { + control.stop() + await payloads.close() + }) + ctx.provide('deepchatCore', { + identity: status, + status: async () => await payloads.encode(status()), + async configure(ref: CorePayload) { + await payloads.decode(ref) + await assert.rejects( + service.control('foreign', 1, { + method: 'open', + sessionId: 'foreign', + scopeId: 'foreign' + }), + /generation is unavailable/ + ) + await control.send({ method: 'inputRecovery', sessions: [] }) + const sessionId = toAppSessionId('session') + const scope = runtime.getOrHydrateScope(sessionId) + assert.equal(scope.isCurrent(), false) + await scope.instance.controlReady() + assert.equal(scope.isCurrent(), true) + scope.instance.setRuntimeState({ + status: 'idle', + providerId: 'openai', + modelId: 'model', + permissionMode: 'full_access' + }) + const events: string[] = [] + const publisher = new SessionStatusPublisher({ + publishEvent: (name) => { + events.push(name) + }, + publishSessionUpdate: () => {}, + sessionUiPort: { refreshSessionUi() {} } + }) + const first = await scope.instance.ensureOperationController() + const owner = scope.instance.operationOwner(first) + const makeRun = ( + abortController: AbortController, + runId: string, + runSessionId = sessionId + ) => + createLoopRun({ + runId, + sessionId: runSessionId, + messageId: 'message', + abortController, + messages: [], + streamState: {}, + resources: { + toolDefinitions: [], + activeSkillNames: [], + commandShell: POSIX_COMMAND_SHELL, + toolMode: { mode: 'agent', source: 'fallback' } + } + }) + const run = makeRun(first, 'run') + assert.equal(await publisher.transition(scope, 'generating', owner), true) + assert.equal(events.length, 2) + await scope.instance.registerActiveGeneration(run) + assert.equal(await scope.instance.ensureOperationController(), first) + assert.equal(scope.instance.getActiveGeneration(), run) + assert.equal(await scope.instance.markFirstTurnReady(run.runId), true) + assert.equal(await scope.instance.waitForFirstTurnReady({ timeoutMs: 0 }), true) + const lifecycle = new RunLifecycleCoordinator({ + runtime, + statusPublisher: publisher, + transcript: { getMessage: () => null, getMessages: () => [], setMessageError() {} }, + pendingInputWakeup: { drain: async () => false }, + terminalObserver: { observeTerminal() {} }, + messageProjection: { refresh() {} }, + programmaticAuthority: { revokeConversation() {} } + }) + const nativePermissions: Array<{ requestId: string; granted: boolean }> = [] + const decisions: boolean[] = [] + const lateEntered = Promise.withResolvers() + const lateReleased = Promise.withResolvers() + let lateCommitted = false + const permissions = new ProviderPermissionCoordinator({ + runLifecycle: lifecycle, + messageStore: { + getMessage: () => null, + updateAssistantContent() {}, + setMessageError() {} + }, + messageProjection: { refresh() {} }, + publishEvent() {}, + permissionPort: { + async resolveAgentPermission(requestId, granted) { + nativePermissions.push({ requestId, granted }) + if (requestId === 'late') { + lateEntered.resolve() + await lateReleased.promise + } + } + } + }) + const permission = (requestId: string) => ({ + requestId, + providerId: 'acp', + permissionType: 'command' as const + }) + await permissions.register( + scope, + run, + permission('request'), + { callId: 'tool' }, + (granted) => { + decisions.push(granted) + } + ) + await scope.instance.replacePendingInteractions([ + { messageId: 'message', toolCallId: 'tool', origin: 'acp-permission', order: 0 } + ]) + const approval = await scope.instance.acquireInteraction('message', 'tool') + assert.ok(approval && approval !== 'parked') + const answer = { + sessionId, + messageId: 'message', + toolCallId: 'tool', + requestId: 'request', + permissionType: 'command' as const, + granted: true, + ownerRun: run, + interactionLeaseId: approval.id, + signal: first.signal + } + await permissions.resolve(answer) + assert.deepEqual(decisions, [true]) + assert.equal(scope.instance.hasActiveProviderPermission('request'), false) + await permissions.resolve(answer) + assert.equal(nativePermissions.length, 1) + await permissions.resolve({ ...answer, requestId: 'direct', granted: false }) + assert.deepEqual(nativePermissions.at(-1), { requestId: 'direct', granted: false }) + await permissions.register( + scope, + run, + permission('request'), + { callId: 'new-tool' }, + (granted) => { + decisions.push(granted) + } + ) + await permissions.resolve(answer) + assert.equal(nativePermissions.length, 2) + assert.equal(scope.instance.hasActiveProviderPermission('request'), true) + await scope.instance.unlockInteraction(approval) + await permissions.register(scope, run, permission('empty-tool'), {}, (granted) => { + decisions.push(granted) + }) + await permissions.register(scope, run, permission('late'), { callId: 'late-tool' }, () => { + lateCommitted = true + }) + await scope.instance.replacePendingInteractions([ + { messageId: 'message', toolCallId: 'late-tool', origin: 'acp-permission', order: 0 } + ]) + const lateApproval = await scope.instance.acquireInteraction('message', 'late-tool') + assert.ok(lateApproval && lateApproval !== 'parked') + const resolving = permissions.resolve({ + ...answer, + requestId: 'late', + toolCallId: 'late-tool', + interactionLeaseId: lateApproval.id + }) + await lateEntered.promise + scope.instance.requestGenerationAbort() + assert.equal(first.signal.aborted, true) + lateReleased.resolve() + await resolving + await scope.instance.unlockInteraction(lateApproval) + assert.equal(lateCommitted, false) + assert.equal(scope.instance.hasActiveProviderPermission('request'), false) + assert.equal(scope.instance.hasActiveProviderPermission('empty-tool'), false) + assert.deepEqual(decisions, [true, false, false]) + const next = await scope.instance.ensureOperationController() + assert.notEqual(next, first) + assert.equal(next.signal.aborted, false) + assert.equal(scope.instance.getActiveGeneration(), undefined) + let settled = 0 + assert.equal( + await scope.instance.clearAbortController(first, () => { + settled++ + }), + false + ) + assert.equal(settled, 0) + assert.equal(await scope.instance.clearActiveGeneration(run.runId), false) + assert.equal(await publisher.transition(scope, 'error', owner), false) + assert.equal(events.length, 2) + assert.equal(scope.state()?.status, 'generating') + const reuseId = toAppSessionId('reuse-session') + const reuse = runtime.getOrHydrateScope(reuseId) + await reuse.instance.controlReady() + reuse.instance.setRuntimeState({ + status: 'idle', + providerId: 'openai', + modelId: 'model', + permissionMode: 'full_access' + }) + const shared = await reuse.instance.ensureOperationController() + const sharedOwner = reuse.instance.operationOwner(shared) + const older = makeRun(shared, 'reuse-a', reuseId) + const newer = makeRun(shared, 'reuse-b', reuseId) + await reuse.instance.registerActiveGeneration(older) + await reuse.instance.registerActiveGeneration(newer) + let cleanup = 0 + assert.equal( + await reuse.instance.clearAbortController( + shared, + () => { + cleanup++ + }, + { operationId: sharedOwner.id, runId: older.runId } + ), + false + ) + assert.equal(reuse.instance.isActiveRun(newer.runId), true) + assert.equal(await reuse.instance.transitionControlStatus(sharedOwner, 'error'), undefined) + await reuse.instance.clearActiveGeneration(newer.runId) + assert.equal( + await reuse.instance.clearAbortController( + shared, + () => { + cleanup++ + }, + { operationId: sharedOwner.id, runId: older.runId } + ), + false + ) + assert.equal(cleanup, 0) + await reuse.instance.clearAbortController( + shared, + () => { + cleanup++ + }, + { operationId: sharedOwner.id, runId: newer.runId } + ) + assert.equal(cleanup, 1) + const contract: DeepChatExecutionContract = { + schemaVersion: 1, + hashVersion: 1, + request: { sessionId: reuseId, messageId: 'owned', runId: newer.runId, requestSeq: 1 }, + ceilings: { tools: [], workspace: { kind: 'runtime_default' }, maxSubagentDepth: 0 }, + dynamicControlSnapshot: { + permissionMode: 'full_access', + requestAdmitted: true, + cancellationRequested: false + }, + provenance: { + promptSections: [], + providerId: 'openai', + modelId: 'model', + promptHash: 'prompt', + effectiveGenerationConfigHash: 'config', + providerVisibleToolDefinitionsHash: 'tools', + internalExecutionPolicyHash: 'policy', + assemblerVersion: '1', + taskContractRef: null + }, + contractHash: 'contract' + } + const latestOwner = { kind: 'run' as const, id: newer.runId } + await reuse.instance.replacePendingToolBatch( + [ + { messageId: 'owned', toolCallId: 'tail', origin: 'question', order: 1 }, + { messageId: 'owned', toolCallId: 'owned', origin: 'pre-check-permission', order: 0 } + ], + { + callOrder: ['owned', 'tail'], + invokedCallIds: [], + committedResultCallIds: [], + pendingInteractionCallIds: ['owned', 'tail'], + executionContract: contract + }, + latestOwner + ) + assert.equal(reuse.instance.getFirstPendingInteraction()?.toolCallId, 'owned') + assert.equal(reuse.instance.getPendingToolBatchState()?.executionContract, contract) + const headLease = await reuse.instance.acquireInteraction('owned', 'owned') + const tailLease = await reuse.instance.acquireInteraction('owned', 'tail') + assert.ok(headLease && headLease !== 'parked' && tailLease && tailLease !== 'parked') + assert.equal(await reuse.instance.checkPendingInteraction(headLease, latestOwner), true) + assert.equal(await reuse.instance.checkPendingInteraction(tailLease, latestOwner), false) + await reuse.instance.advancePendingToolBatch({ invokedCallId: 'owned' }, latestOwner) + await reuse.instance.advancePendingToolBatch( + { invokedCallId: 'owned', committedResultCallId: 'owned' }, + latestOwner + ) + assert.deepEqual(reuse.instance.getPendingToolBatchState()?.invokedCallIds, ['owned']) + await reuse.instance.transitionPendingInteractionOrigin( + 'owned', + 'owned', + 'post-call-permission', + latestOwner + ) + await reuse.instance.replacePendingInteractions( + [ + { messageId: 'owned', toolCallId: 'owned', origin: 'pre-check-permission', order: 99 }, + { messageId: 'owned', toolCallId: 'tail', origin: 'question', order: 0 } + ], + latestOwner, + { reconcile: true } + ) + assert.deepEqual(reuse.instance.getFirstPendingInteraction(), { + messageId: 'owned', + toolCallId: 'owned', + origin: 'post-call-permission', + order: 0 + }) + await lifecycle.applyProcessResultStatus( + reuseId, + { status: 'paused', pendingInteractions: [] }, + older.runId + ) + assert.equal(reuse.instance.hasPendingInteractions(), true) + assert.equal(reuse.instance.getPendingToolBatchState()?.executionContract, contract) + await lifecycle.applyProcessResultStatus(reuseId, { status: 'completed' }, newer.runId) + assert.equal(reuse.instance.hasPendingInteractions(), false) + assert.equal(reuse.instance.getPendingToolBatchState(), undefined) + await reuse.instance.unlockInteraction(headLease) + await reuse.instance.unlockInteraction(tailLease) + const retiringRun = makeRun(next, 'retiring-run') + await scope.instance.registerActiveGeneration(retiringRun) + runtime.evict(sessionId) + const replacement = runtime.getOrHydrateScope(sessionId) + await replacement.instance.controlReady() + assert.equal(scope.isCurrent(), false) + assert.equal(replacement.isCurrent(), true) + assert.equal(next.signal.aborted, true) + assert.equal(await scope.instance.clearActiveGeneration(retiringRun.runId), true) + assert.equal(replacement.isCurrent(), true) + const pending = replacement.instance.ensureOperationController() + replacement.instance.requestGenerationAbort() + const cancelled = await pending + assert.equal(cancelled.signal.aborted, true) + const active = await replacement.instance.ensureOperationController() + await replacement.instance.replacePendingInteractions([ + { + messageId: 'deferred-message', + toolCallId: 'deferred-tool', + origin: 'pre-check-permission', + order: 0 + } + ]) + const deferredParent = await replacement.instance.acquireInteraction( + 'deferred-message', + 'deferred-tool' + ) + assert.ok(deferredParent && deferredParent !== 'parked') + const deferred = await replacement.instance.registerDeferredToolAbortController( + 'deferred-message', + 'deferred-tool', + deferredParent, + replacement.instance.operationOwner(active) + ) + assert.equal(replacement.instance.hasDeferredToolAbortController('deferred-tool'), true) + await assert.rejects( + replacement.instance.registerDeferredToolAbortController( + 'deferred-message', + 'deferred-tool', + deferredParent, + replacement.instance.operationOwner(active) + ) + ) + assert.equal(deferred.signal.aborted, false) + await replacement.instance.unlockInteraction(deferredParent) + assert.equal(deferred.signal.aborted, true) + const nextParent = await replacement.instance.acquireInteraction( + 'deferred-message', + 'deferred-tool' + ) + assert.ok(nextParent && nextParent !== 'parked') + const nextDeferred = await replacement.instance.registerDeferredToolAbortController( + 'deferred-message', + 'deferred-tool', + nextParent, + replacement.instance.operationOwner(active) + ) + assert.equal(await replacement.instance.clearDeferredToolAbortController(deferred), false) + assert.equal(nextDeferred.signal.aborted, false) + active.abort() + assert.equal(nextDeferred.signal.aborted, true) + await replacement.instance.clearDeferredToolAbortController(nextDeferred) + await replacement.instance.unlockInteraction(nextParent) + assert.equal( + await replacement.instance.clearAbortController(active, () => { + settled++ + }), + true + ) + assert.equal(settled, 1) + + const leaseSession = toAppSessionId('lease-session') + const origin = runtime.getOrHydrateScope(leaseSession) + await origin.instance.controlReady() + const interaction = await origin.instance.acquireInteraction('approval', 'tool') + assert.ok(interaction && interaction !== 'parked') + assert.equal(await origin.instance.acquireInteraction('approval', 'tool'), null) + const resume = await origin.instance.tryBeginResume('approval') + assert.ok(resume) + assert.equal(await origin.instance.tryBeginResume('approval'), null) + await origin.instance.finishResume(resume) + const inputSelection = { + intent: 'drain' as const, + source: 'queue' as const, + reason: 'manual' as const, + snapshot: { + queue: [{ id: 'queued', state: 'pending' as const }], + steerHead: null, + hasClaimed: false, + hasBlocking: false, + awaitingFollowUp: false + } + } + const drain = await origin.instance.beginInput(inputSelection) + assert.equal(drain.action, 'claim') + assert.ok(drain.claimId && drain.effectId && drain.drainLeaseId) + assert.equal(origin.instance.isPendingQueueDraining(), true) + assert.equal((await origin.instance.beginInput(inputSelection)).action, 'busy') + const activeAcceptance = await origin.instance.acquireAttachmentAcceptance('send') + const queued = Promise.withResolvers() + queuedAcceptance = () => queued.resolve() + const cancellation = new AbortController() + const cancelledAcceptance = origin.instance.acquireAttachmentAcceptance( + 'send', + cancellation.signal + ) + const abortedAcceptance = assert.rejects(cancelledAcceptance, { name: 'AbortError' }) + await queued.promise + cancellation.abort() + await abortedAcceptance + runtime.evict(leaseSession) + let successor = runtime.getOrHydrateScope(leaseSession) + await successor.instance.controlReady() + await origin.instance.parkInteraction(interaction) + assert.equal(await successor.instance.acquireInteraction('approval', 'tool'), 'parked') + assert.equal((await successor.instance.beginInput(inputSelection)).action, 'busy') + assert.equal((await successor.instance.endInput(drain.drainLeaseId)).released, false) + await origin.instance.ackInputEffect({ + claimId: drain.claimId, + effectId: drain.effectId, + outcome: 'rejected' + }) + assert.equal((await origin.instance.endInput(drain.drainLeaseId)).released, true) + const successorQueued = Promise.withResolvers() + queuedAcceptance = () => successorQueued.resolve() + let successorEntered = false + const waiting = successor.instance.acquireAttachmentAcceptance('send').then((release) => { + successorEntered = true + return release + }) + await successorQueued.promise + assert.equal(successorEntered, false) + const independent = await successor.instance.acquireAttachmentAcceptance('steer') + await independent() + await activeAcceptance() + const releaseSuccessor = await waiting + assert.equal(successorEntered, true) + await releaseSuccessor() + runtime.evict(leaseSession) + await runtime.clearSessionControl(leaseSession, successor.instance) + await assert.rejects(origin.instance.parkInteraction(interaction)) + await origin.instance.unlockInteraction(interaction) + successor = runtime.getOrHydrateScope(leaseSession) + await successor.instance.controlReady() + const successorInteraction = await successor.instance.acquireInteraction('approval', 'tool') + assert.ok(successorInteraction && successorInteraction !== 'parked') + await successor.instance.unlockInteraction(successorInteraction) + + const heldAtStop = await successor.instance.acquireAttachmentAcceptance('send') + const queuedAtStop = Promise.withResolvers() + queuedAcceptance = () => queuedAtStop.resolve() + const stoppedWaiter = assert.rejects(successor.instance.acquireAttachmentAcceptance('send')) + await queuedAtStop.promise + + const final = await replacement.instance.ensureOperationController() + control.stop() + await stoppedWaiter + await assert.rejects(heldAtStop()) + assert.equal(final.signal.aborted, true) + assert.equal(replacement.isCurrent(), false) + await assert.rejects(replacement.instance.ensureOperationController()) + assert.equal(failed, false) + return await payloads.encode({ passed: true }) + }, + async stop() { + control.stop() + stopped = true + return await payloads.encode(status()) + } + }) + } +}) diff --git a/test/fixtures/harness/core-memory.ts b/test/fixtures/harness/core-memory.ts new file mode 100644 index 0000000000..a948c0e87e --- /dev/null +++ b/test/fixtures/harness/core-memory.ts @@ -0,0 +1,113 @@ +import { randomUUID } from 'node:crypto' +import path from 'node:path' +import { definePlugin } from '@arcships/rutis' +import { CoreMemoryRuntime, type CoreMemoryService } from '../../../src/harness/core/memory' +import { CorePayloadStore } from '../../../src/harness/core/payload' +import { RutisMemoryVectorStores } from '../../../src/main/harness/memory-vector' +import { CoreCarrierConfigSchema, type CorePayload } from '../../../src/shared/harness/core' + +export default definePlugin({ + name: 'core-memory-contract', + inject: ['deepchatCoreNative'], + provides: { + deepchatCore: { + identity: 'async', + openProfile: 'async', + configure: 'async', + status: 'async', + stop: 'async' + } + }, + async apply(ctx, input) { + const config = CoreCarrierConfigSchema.parse(input) + const payloads = new CorePayloadStore(config, 'core') + const service = ctx.use('deepchatCoreNative') + let memoryIdentity: unknown + const memory = new CoreMemoryRuntime( + ctx, + { + async memory(...args) { + const value = await service.memory(...args) + if (args[2] === 'start') memoryIdentity = value + return value + } + }, + config.generation + ) + let stores: RutisMemoryVectorStores | undefined + let stopped = false + const status = () => ({ + generation: config.generation, + pid: process.pid, + node: process.versions.node, + modules: process.versions.modules, + phase: stopped ? 'stopped' : 'idle', + businessReady: false + }) + const stop = async () => { + await stores?.dispose() + await memory.retire() + stopped = true + } + ctx.effect(async () => { + await stop() + await payloads.close() + }) + ctx.provide('deepchatCore', { + identity: status, + status: async () => await payloads.encode(status()), + async openProfile(ref: CorePayload) { + const bootstrap = (await payloads.decode(ref)) as { + userDataDirectory: string + appPath: string + } + stores = new RutisMemoryVectorStores(memory, { + memoryDbDir: path.join(bootstrap.userDataDirectory, 'memory'), + host: { + appPath: bootstrap.appPath, + userDataPath: bootstrap.userDataDirectory, + isPackaged: false + } + }) + return await payloads.encode(status()) + }, + async configure(ref: CorePayload) { + const { operation } = (await payloads.decode(ref)) as { operation: string } + if (!stores) throw new Error('Memory has not been configured') + if (operation === 'initialize') { + const store = await stores.createVectorStore( + randomUUID(), + { providerId: 'fixture', modelId: 'fixture' }, + 2 + ) + await store.close() + return await payloads.encode(memoryIdentity) + } + if (operation === 'nativeSnapshot') { + const store = await stores.createVectorStore( + 'core-data', + { providerId: 'fixture', modelId: 'fixture' }, + 2048 + ) + const records = Array.from({ length: 45 }, (_, row) => ({ + memoryId: `row-${String(row).padStart(2, '0')}`, + embedding: Array.from({ length: 2048 }, (_, column) => + column === row ? 1 : column === 2047 ? -0.25 : 0 + ) + })) + await store.upsert(records) + const before = await store.listMemoryIds(null, 50) + await store.deleteByMemoryIds(['row-00']) + const after = await store.listMemoryIds(null, 50) + await store.close() + return await payloads.encode({ before, after }) + } + return await payloads.encode({ available: memory.memoryAvailable }) + }, + async stop() { + await stop() + return await payloads.encode(status()) + } + }) + } +}) diff --git a/test/helpers/memoryStore.ts b/test/helpers/memoryStore.ts new file mode 100644 index 0000000000..1d41038ce1 --- /dev/null +++ b/test/helpers/memoryStore.ts @@ -0,0 +1,26 @@ +import type { StoreLike } from '../../src/main/config/storeLike' + +export function createMemoryStore = Record>( + initial: TStore = {} as TStore +): StoreLike { + const data: Record = structuredClone(initial) + return { + get store() { + return { ...data } as TStore + }, + get(key: string, defaultValue?: TValue): TValue { + return (data[key] === undefined ? defaultValue : data[key]) as TValue + }, + set(keyOrValues: string | Record, value?: unknown) { + if (typeof keyOrValues === 'string') data[keyOrValues] = value + else Object.assign(data, keyOrValues) + }, + delete(key: string) { + delete data[key] + }, + has: (key) => Object.hasOwn(data, key), + clear() { + for (const key of Object.keys(data)) delete data[key] + } + } +} diff --git a/test/helpers/pluginToolPolicies.ts b/test/helpers/pluginToolPolicies.ts new file mode 100644 index 0000000000..dce14400d3 --- /dev/null +++ b/test/helpers/pluginToolPolicies.ts @@ -0,0 +1,11 @@ +import { PluginToolPolicyStore, type StoredToolPolicy } from '../../src/main/plugin/toolPolicyStore' + +export function createPluginToolPolicies(): PluginToolPolicyStore { + let policies: StoredToolPolicy[] = [] + return new PluginToolPolicyStore({ + get: () => structuredClone(policies), + set: (_key, next) => { + policies = structuredClone(next) + } + }) +} diff --git a/test/helpers/providerEnvironment.ts b/test/helpers/providerEnvironment.ts new file mode 100644 index 0000000000..fd1e9c1a1c --- /dev/null +++ b/test/helpers/providerEnvironment.ts @@ -0,0 +1,8 @@ +import { buildDefaultProviderHeaders } from '../../src/main/provider/providerHeaders' +import type { ProviderEnvironmentPort } from '../../src/main/provider/ports' + +export const providerTestEnvironment: ProviderEnvironmentPort = { + getLanguage: () => 'en-US', + defaultHeaders: Object.freeze(buildDefaultProviderHeaders('0.0.0-test')), + allowInsecureTls: false +} diff --git a/test/main/agent/acp/catalog/acpRegistryService.test.ts b/test/main/agent/acp/catalog/acpRegistryService.test.ts index 9b408ae3a6..64b4e2f15c 100644 --- a/test/main/agent/acp/catalog/acpRegistryService.test.ts +++ b/test/main/agent/acp/catalog/acpRegistryService.test.ts @@ -2,10 +2,12 @@ import * as fs from 'node:fs' import * as os from 'node:os' import * as path from 'node:path' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' +import { + AcpRegistryService, + type AcpRegistryServiceOptions +} from '@/agent/acp/catalog/acpRegistryService' -const mockGetPath = vi.fn() -const mockGetAppPath = vi.fn() -const mockNetFetch = vi.fn() +const mockFetchIcon = vi.fn() vi.mock('fs', async () => { const actual = await vi.importActual('node:fs') @@ -16,16 +18,6 @@ vi.mock('fs', async () => { } }) -vi.mock('electron', () => ({ - app: { - getPath: mockGetPath, - getAppPath: mockGetAppPath - }, - net: { - fetch: mockNetFetch - } -})) - type RegistryManifestFixture = { version: string agents: Array<{ @@ -54,10 +46,13 @@ describe('AcpRegistryService', () => { let appRoot: string let userDataRoot: string - const importService = async () => { - const mod = await import('@/agent/acp/catalog/acpRegistryService') - return mod.AcpRegistryService - } + const createService = (options: Partial = {}) => + new AcpRegistryService({ + userDataDirectory: userDataRoot, + builtInDirectory: path.join(appRoot, 'resources', 'acp-registry'), + fetchIcon: mockFetchIcon, + ...options + }) const writeBuiltInIcon = (agentId: string, markup: string) => { const iconDir = path.join(appRoot, 'resources', 'acp-registry', 'icons') @@ -95,14 +90,7 @@ describe('AcpRegistryService', () => { fs.mkdirSync(appRoot, { recursive: true }) fs.mkdirSync(userDataRoot, { recursive: true }) - mockGetPath.mockImplementation((name: string) => { - if (name === 'userData') { - return userDataRoot - } - return userDataRoot - }) - mockGetAppPath.mockReturnValue(appRoot) - mockNetFetch.mockReset() + mockFetchIcon.mockReset() vi.unstubAllGlobals() }) @@ -118,8 +106,7 @@ describe('AcpRegistryService', () => { '' ) - const AcpRegistryService = await importService() - const service = new AcpRegistryService() + const service = createService() const markup = await service.getIconMarkup( 'claude-acp', @@ -128,7 +115,7 @@ describe('AcpRegistryService', () => { expect(markup).toContain('focusable="false"') expect(markup).toContain('color="currentColor"') - expect(mockNetFetch).not.toHaveBeenCalled() + expect(mockFetchIcon).not.toHaveBeenCalled() }) it('skips the automatic registry refresh when privacy mode is enabled', async () => { @@ -138,12 +125,12 @@ describe('AcpRegistryService', () => { const globalFetch = vi.fn() vi.stubGlobal('fetch', globalFetch) - const AcpRegistryService = await importService() - const service = new AcpRegistryService({ + const service = createService({ isPrivacyModeEnabled: () => true }) - await service.initialize() + service.initialize() + await service.refresh(false, { automatic: true }) expect(globalFetch).not.toHaveBeenCalled() expect(service.listAgents()).toHaveLength(1) @@ -171,8 +158,7 @@ describe('AcpRegistryService', () => { ] }) - const AcpRegistryService = await importService() - const service = new AcpRegistryService({ + const service = createService({ isPrivacyModeEnabled: () => true }) @@ -207,8 +193,7 @@ describe('AcpRegistryService', () => { ] }) - const AcpRegistryService = await importService() - const service = new AcpRegistryService({ + const service = createService({ isPrivacyModeEnabled: () => true }) @@ -229,21 +214,15 @@ describe('AcpRegistryService', () => { }) vi.stubGlobal('fetch', globalFetch) - mockNetFetch.mockResolvedValue({ - ok: true, - text: vi - .fn() - .mockResolvedValue( - '' - ) - }) + mockFetchIcon.mockResolvedValue( + '' + ) const staleIconDir = path.join(userDataRoot, 'acp-registry', 'icons') fs.mkdirSync(staleIconDir, { recursive: true }) fs.writeFileSync(path.join(staleIconDir, 'obsolete.svg'), '', 'utf-8') - const AcpRegistryService = await importService() - const service = new AcpRegistryService() + const service = createService() await service.refresh(true) @@ -252,7 +231,7 @@ describe('AcpRegistryService', () => { const markup = await service.getIconMarkup('claude-acp', manifest.agents[0].icon) expect(markup).toContain('currentColor') - expect(mockNetFetch).toHaveBeenCalledTimes(1) + expect(mockFetchIcon).toHaveBeenCalledTimes(1) }) it('keeps manual refresh available while privacy mode is enabled', async () => { @@ -265,24 +244,18 @@ describe('AcpRegistryService', () => { }) vi.stubGlobal('fetch', globalFetch) - mockNetFetch.mockResolvedValue({ - ok: true, - text: vi - .fn() - .mockResolvedValue( - '' - ) - }) + mockFetchIcon.mockResolvedValue( + '' + ) - const AcpRegistryService = await importService() - const service = new AcpRegistryService({ + const service = createService({ isPrivacyModeEnabled: () => true }) await service.refresh(true) expect(globalFetch).toHaveBeenCalledTimes(1) - expect(mockNetFetch).toHaveBeenCalledTimes(1) + expect(mockFetchIcon).toHaveBeenCalledTimes(1) }) it('preserves existing cached icon when refreshing a new icon fails', async () => { @@ -298,14 +271,9 @@ describe('AcpRegistryService', () => { const oldMarkup = '' fs.writeFileSync(path.join(iconDir, 'claude-acp.svg'), oldMarkup, 'utf-8') - mockNetFetch.mockResolvedValue({ - ok: false, - status: 503, - statusText: 'Service Unavailable' - }) + mockFetchIcon.mockRejectedValue(new Error('Service Unavailable')) - const AcpRegistryService = await importService() - const service = new AcpRegistryService() + const service = createService() await service.refresh(true) @@ -320,23 +288,125 @@ describe('AcpRegistryService', () => { version: '1', agents: [createManifest('claude-acp').agents[0], createManifest('claude-acp').agents[0]] } - const emptyAppRoot = path.join(tempRoot, 'empty-app-root') - const emptyCwd = path.join(tempRoot, 'empty-cwd') - fs.mkdirSync(emptyAppRoot, { recursive: true }) - fs.mkdirSync(emptyCwd, { recursive: true }) - mockGetAppPath.mockReturnValue(emptyAppRoot) - vi.spyOn(process, 'cwd').mockReturnValue(emptyCwd) const globalFetch = vi.fn().mockResolvedValue({ ok: true, text: vi.fn().mockResolvedValue(JSON.stringify(duplicateManifest)) }) vi.stubGlobal('fetch', globalFetch) - const AcpRegistryService = await importService() - const service = new AcpRegistryService() + const service = createService() await expect(service.refresh(true)).rejects.toThrow( '[ACP Registry] No registry snapshot is available.' ) }) + + it('loads the local catalog without starting detached network work', async () => { + const manifest = createManifest() + delete manifest.agents[0].icon + writeBuiltInManifest(manifest) + const fetch = vi.fn().mockResolvedValue(new Response(JSON.stringify(manifest))) + vi.stubGlobal('fetch', fetch) + const service = createService() + + service.initialize() + expect(service.listAgents()[0].id).toBe('claude-acp') + expect(fetch).not.toHaveBeenCalled() + await service.refresh(false, { automatic: true }) + expect(fetch).toHaveBeenCalledOnce() + }) + + it.each(['shutdown', 'task cancellation'])( + 'drains a held refresh and rejects late writes after %s', + async (reason) => { + const initial = createManifest('initial') + writeBuiltInManifest(initial) + const service = createService() + service.initialize() + const cache = path.join(userDataRoot, 'acp-registry', 'registry.json') + const meta = path.join(userDataRoot, 'acp-registry', 'meta.json') + fs.writeFileSync(cache, JSON.stringify(initial)) + fs.writeFileSync(meta, JSON.stringify({ version: '1', lastUpdated: 1 })) + const original = [cache, meta].map((file) => fs.readFileSync(file, 'utf8')) + let release!: (response: Response) => void + let requestSignal!: AbortSignal + const fetch = vi.fn((_url: string, options: { signal: AbortSignal }) => { + requestSignal = options.signal + return new Promise((resolve) => { + release = resolve + }) + }) + vi.stubGlobal('fetch', fetch) + const caller = new AbortController() + const refresh = service.refresh(true, { signal: caller.signal }) + const rejected = expect(refresh).rejects.toMatchObject({ name: 'AbortError' }) + const joined = expect(service.refresh(true)).rejects.toMatchObject({ name: 'AbortError' }) + expect(fetch).toHaveBeenCalledOnce() + if (reason === 'shutdown') service.stop() + else caller.abort() + expect(requestSignal.aborted).toBe(true) + let drained = false + const drain = service.drain().then(() => { + drained = true + }) + await Promise.resolve() + expect(drained).toBe(false) + release(new Response(JSON.stringify(createManifest('late')))) + await Promise.all([rejected, joined, drain]) + expect([cache, meta].map((file) => fs.readFileSync(file, 'utf8'))).toEqual(original) + expect(service.listAgents()[0].id).toBe('initial') + + if (reason === 'task cancellation') { + const next = createManifest('fresh') + delete next.agents[0].icon + fetch.mockResolvedValue(new Response(JSON.stringify(next))) + expect((await service.refresh(true))[0].id).toBe('fresh') + } else { + await expect(service.refresh(true)).rejects.toMatchObject({ name: 'AbortError' }) + expect(fetch).toHaveBeenCalledOnce() + } + } + ) + + it.each(['manifest body', 'icon body'])( + 'fences a late %s and preserves the committed catalog on shutdown', + async (phase) => { + const initial = createManifest('initial') + writeBuiltInManifest(initial) + const service = createService() + service.initialize() + const cache = path.join(userDataRoot, 'acp-registry', 'registry.json') + fs.writeFileSync(cache, JSON.stringify(initial)) + const original = fs.readFileSync(cache, 'utf8') + let release!: (text: string) => void + let entered!: () => void + const started = new Promise((resolve) => { + entered = resolve + }) + const held = () => { + entered() + return new Promise((resolve) => { + release = resolve + }) + } + const next = createManifest('late') + vi.stubGlobal( + 'fetch', + vi.fn().mockResolvedValue({ + ok: true, + text: phase === 'manifest body' ? held : async () => JSON.stringify(next) + }) + ) + mockFetchIcon.mockImplementation(held) + const rejected = expect(service.refresh(true)).rejects.toMatchObject({ name: 'AbortError' }) + await started + service.stop() + release(phase === 'manifest body' ? JSON.stringify(next) : '') + await Promise.all([rejected, service.drain()]) + expect(fs.readFileSync(cache, 'utf8')).toBe(original) + expect(fs.existsSync(path.join(userDataRoot, 'acp-registry', 'icons', 'late.svg'))).toBe( + false + ) + } + ) }) diff --git a/test/main/agent/acp/catalog/settings.test.ts b/test/main/agent/acp/catalog/settings.test.ts index 97f8da6d64..5e1a1180f6 100644 --- a/test/main/agent/acp/catalog/settings.test.ts +++ b/test/main/agent/acp/catalog/settings.test.ts @@ -1,44 +1,21 @@ import { beforeEach, describe, expect, it, vi } from 'vitest' -const mockStores = vi.hoisted(() => new Map>()) - -const clone = (value: T): T => JSON.parse(JSON.stringify(value)) as T - -vi.mock('electron-store', () => ({ - default: class MockElectronStore { - private readonly data: Record - - constructor(options: { name: string; defaults?: Record }) { - if (!mockStores.has(options.name)) { - mockStores.set(options.name, clone(options.defaults ?? {})) - } - this.data = mockStores.get(options.name)! - } - - get(key: string) { - return this.data[key] - } - - set(key: string, value: any) { - this.data[key] = value - } - - delete(key: string) { - delete this.data[key] - } - } -})) - -vi.mock('@/mcp/settings', () => ({ - McpSettings: class MockMcpSettings { - async getMcpServers() { - return { - github: { type: 'stdio' }, - memory: { type: 'inmemory' } - } - } - } -})) +import { createMemoryStore } from '../../../../helpers/memoryStore' + +const mockStores = new Map>() +const github = { + command: 'fixture', + args: [], + env: {}, + descriptions: '', + icons: '', + disable: false, + enabled: true, + type: 'stdio' as const +} +const mcpSettings = { + getMcpServers: async () => ({ github, memory: { ...github, type: 'inmemory' as const } }) +} describe('AcpCatalogSettings registry-first migration', () => { beforeEach(() => { @@ -88,9 +65,12 @@ describe('AcpCatalogSettings registry-first migration', () => { ] }) - const { AcpCatalogSettings } = await import('@/agent/acp/catalog/settings') - const { McpSettings } = await import('@/mcp/settings') - const helper = new AcpCatalogSettings({ mcpSettings: new McpSettings() }) + const { AcpCatalogSettings, createAcpCatalogDefaults } = + await import('@/agent/acp/catalog/settings') + const helper = new AcpCatalogSettings({ + mcpSettings, + store: createMemoryStore(mockStores.get('acp_agents') ?? createAcpCatalogDefaults()) + }) expect(helper.getAgentState('kimi')).toEqual( expect.objectContaining({ @@ -113,15 +93,23 @@ describe('AcpCatalogSettings registry-first migration', () => { expect(helper.getSharedMcpSelections()).toEqual(['github']) }) - it('persists registry env overrides and validates shared MCP selections', async () => { - const { AcpCatalogSettings } = await import('@/agent/acp/catalog/settings') - const { McpSettings } = await import('@/mcp/settings') - const helper = new AcpCatalogSettings({ mcpSettings: new McpSettings() }) - - helper.setAgentEnvOverride('claude-code-acp', { - ANTHROPIC_AUTH_TOKEN: 'token', - EMPTY_LINE: '' + it('normalizes legacy registry env overrides and validates shared MCP selections', async () => { + mockStores.set('acp_agents', { + version: '4', + registryStates: { + 'claude-code-acp': { + agentId: 'claude-code-acp', + envOverride: { ANTHROPIC_AUTH_TOKEN: 'token', EMPTY_LINE: '' } + } + } + }) + const { AcpCatalogSettings, createAcpCatalogDefaults } = + await import('@/agent/acp/catalog/settings') + const helper = new AcpCatalogSettings({ + mcpSettings, + store: createMemoryStore(mockStores.get('acp_agents') ?? createAcpCatalogDefaults()) }) + await helper.setAgentMcpSelections('claude-code-acp', true, ['github', 'memory']) expect(helper.getAgentState('claude-acp')).toEqual( @@ -167,9 +155,12 @@ describe('AcpCatalogSettings registry-first migration', () => { } }) - const { AcpCatalogSettings } = await import('@/agent/acp/catalog/settings') - const { McpSettings } = await import('@/mcp/settings') - const helper = new AcpCatalogSettings({ mcpSettings: new McpSettings() }) + const { AcpCatalogSettings, createAcpCatalogDefaults } = + await import('@/agent/acp/catalog/settings') + const helper = new AcpCatalogSettings({ + mcpSettings, + store: createMemoryStore(mockStores.get('acp_agents') ?? createAcpCatalogDefaults()) + }) expect(helper.getSharedMcpSelections()).toEqual(['github', 'legacy-extra']) expect(helper.getAgentState('claude-acp')).toEqual( diff --git a/test/main/agent/acp/launch/acpLaunchSpecService.test.ts b/test/main/agent/acp/launch/acpLaunchSpecService.test.ts index a27a048b94..0b849e9831 100644 --- a/test/main/agent/acp/launch/acpLaunchSpecService.test.ts +++ b/test/main/agent/acp/launch/acpLaunchSpecService.test.ts @@ -42,6 +42,200 @@ describe('AcpLaunchSpecService', () => { return new AcpLaunchSpecService(dir) } + const binaryAgent = () => ({ + id: 'owned-agent', + name: 'Owned agent', + version: '1.0.0', + distribution: { + binary: { + [getCurrentPlatformKey()]: { + archive: 'https://example.com/owned-agent.zip', + cmd: './owned-agent' + } + } + }, + source: 'registry' as const, + enabled: false + }) + + const deferred = () => { + let resolve!: (value: T) => void + const promise = new Promise((settle) => { + resolve = settle + }) + return { promise, resolve } + } + + it('coalesces explicit and implicit installs and serializes a queued repair', async () => { + const service = createService() + const agent = binaryAgent() + const body = deferred() + const entered = deferred() + const archive = zipSync({ 'owned-agent': Buffer.from('binary') }) + const fetchMock = vi + .fn() + .mockImplementationOnce(async () => ({ + ok: true, + arrayBuffer: () => { + entered.resolve() + return body.promise + } + })) + .mockImplementation(async () => new Response(archive)) + vi.stubGlobal('fetch', fetchMock) + + const first = service.ensureRegistryAgentInstalled(agent, null) + await entered.promise + const second = service.ensureRegistryAgentInstalled(agent, null) + const launch = service.resolveRegistryLaunchSpec(agent, null) + const repair = service.ensureRegistryAgentInstalled(agent, null, { repair: true }) + expect(fetchMock).toHaveBeenCalledTimes(1) + body.resolve(archive) + const [installed, shared, resolved, repaired] = await Promise.all([ + first, + second, + launch, + repair + ]) + expect(shared).toEqual(installed) + expect(resolved.command).toBe(path.join(installed.installDir!, 'owned-agent')) + expect(repaired.status).toBe('installed') + expect(fetchMock).toHaveBeenCalledTimes(2) + }) + + it.each(['headers', 'body'])( + 'drains actual canceled %s and permits a fresh owner operation', + async (phase) => { + const service = createService() + const agent = binaryAgent() + const entered = deferred() + const release = deferred() + const archive = zipSync({ 'owned-agent': Buffer.from('binary') }) + vi.stubGlobal( + 'fetch', + vi.fn(async () => { + if (phase === 'headers') { + entered.resolve() + await release.promise + } + return { + ok: true, + arrayBuffer: async () => { + if (phase === 'body') { + entered.resolve() + await release.promise + } + return archive + } + } + }) + ) + const rejected = expect( + service.ensureRegistryAgentInstalled(agent, null) + ).rejects.toMatchObject({ name: 'AbortError' }) + await entered.promise + service.cancelPendingInstallations() + let drained = false + const drain = service.drain().then(() => { + drained = true + }) + await Promise.resolve() + expect(drained).toBe(false) + release.resolve() + await Promise.all([rejected, drain]) + expect(fs.existsSync(path.join(tempDirs[0], 'agents', agent.id, agent.version))).toBe(false) + expect(fs.readdirSync(path.join(tempDirs[0], 'agents', agent.id))).toEqual([]) + const state = await service.ensureRegistryAgentInstalled(agent, null) + expect(state.status).toBe('installed') + } + ) + + it('rejects new work after stop and waits for an uncancelable body before cleanup', async () => { + const service = createService() + const agent = binaryAgent() + const entered = deferred() + const body = deferred() + vi.stubGlobal( + 'fetch', + vi.fn(async () => ({ + ok: true, + arrayBuffer: () => { + entered.resolve() + return body.promise + } + })) + ) + const rejected = expect( + service.ensureRegistryAgentInstalled(agent, null) + ).rejects.toMatchObject({ name: 'AbortError' }) + await entered.promise + service.stop() + let drained = false + const drain = service.drain().then(() => { + drained = true + }) + await expect(service.ensureRegistryAgentInstalled(agent, null)).rejects.toMatchObject({ + name: 'AbortError' + }) + expect(drained).toBe(false) + body.resolve(zipSync({ 'owned-agent': Buffer.from('late') })) + await Promise.all([rejected, drain]) + expect(fs.readdirSync(path.join(tempDirs[0], 'agents', agent.id))).toEqual([]) + }) + + it('keeps the acknowledged binary when a staged repair fails validation', async () => { + const service = createService() + const agent = binaryAgent() + vi.stubGlobal( + 'fetch', + vi.fn(async () => new Response(zipSync({ 'owned-agent': Buffer.from('original') }))) + ) + const original = await service.ensureRegistryAgentInstalled(agent, null) + vi.stubGlobal( + 'fetch', + vi.fn(async () => new Response(zipSync({ 'wrong-agent': Buffer.from('bad') }))) + ) + const repaired = await service.ensureRegistryAgentInstalled(agent, original, { repair: true }) + expect(repaired.status).toBe('error') + expect(fs.readFileSync(path.join(original.installDir!, 'owned-agent'), 'utf8')).toBe('original') + expect(fs.readdirSync(path.dirname(original.installDir!))).toEqual([agent.version]) + fs.renameSync(original.installDir!, `${original.installDir}.prev`) + const restored = await service.ensureRegistryAgentInstalled(agent, original) + expect(restored.status).toBe('installed') + expect(fs.readFileSync(path.join(restored.installDir!, 'owned-agent'), 'utf8')).toBe('original') + }) + + it('prevents canceled installs and queued repairs from recreating an uninstalled binary', async () => { + const service = createService() + const agent = binaryAgent() + const entered = deferred() + const body = deferred() + vi.stubGlobal( + 'fetch', + vi.fn(async () => ({ + ok: true, + arrayBuffer: () => { + entered.resolve() + return body.promise + } + })) + ) + const install = expect(service.ensureRegistryAgentInstalled(agent, null)).rejects.toMatchObject( + { name: 'AbortError' } + ) + await entered.promise + const repair = expect( + service.ensureRegistryAgentInstalled(agent, null, { repair: true }) + ).rejects.toMatchObject({ name: 'AbortError' }) + const uninstall = service.uninstallRegistryAgent(agent, null) + await expect(service.ensureRegistryAgentInstalled(agent, null)).rejects.toThrow( + 'uninstall is in progress' + ) + body.resolve(zipSync({ 'owned-agent': Buffer.from('late') })) + await Promise.all([install, repair, uninstall, service.drain()]) + expect(fs.existsSync(path.join(tempDirs[0], 'agents', agent.id))).toBe(false) + }) + it('prefers binary over npx and uvx for preview generation', () => { const service = createService() const platformKey = getCurrentPlatformKey() @@ -230,8 +424,9 @@ describe('AcpLaunchSpecService', () => { }) expect(timeoutSpy).toHaveBeenCalledWith(120_000) expect(fetchMock).toHaveBeenCalledWith('https://example.com/codex.zip', { - signal: timeoutSignal + signal: expect.any(AbortSignal) }) + expect(fetchMock.mock.calls[0][1]?.signal?.aborted).toBe(true) }) it('uninstalls binary registry agents by removing the install directory', async () => { diff --git a/test/main/agent/acp/runtime/acpProcessManager.test.ts b/test/main/agent/acp/runtime/acpProcessManager.test.ts index 57d971b84a..a1cd71f1d8 100644 --- a/test/main/agent/acp/runtime/acpProcessManager.test.ts +++ b/test/main/agent/acp/runtime/acpProcessManager.test.ts @@ -19,13 +19,6 @@ const childProcessRegistryMock = vi.hoisted(() => ({ reapStaleOnce: vi.fn().mockResolvedValue(null) })) -vi.mock('electron', () => ({ - app: { - getVersion: vi.fn(() => '0.0.0-test'), - getPath: vi.fn(() => '/tmp') - } -})) - vi.mock('cross-spawn', () => ({ default: vi.fn() })) @@ -80,8 +73,29 @@ describe('parseLoadSessionCapability', () => { describe('AcpProcessManager config cache fallback', () => { const normalizePathValue = (value: string) => value.replace(/\\/g, '/') + it.each(['homeDirectory', 'temporaryDirectory'])( + 'rejects a relative bootstrap %s before acquiring process resources', + (key) => { + expect( + () => + new AcpProcessManager({ + bootstrap: { + appVersion: '0.0.0-test', + homeDirectory: '/tmp', + temporaryDirectory: '/tmp', + [key]: 'relative-profile' + }, + publishEvent: publishDeepchatEventMock, + providerId: 'acp', + resolveLaunchSpec: vi.fn() + }) + ).toThrow('ACP bootstrap directories must be absolute') + } + ) + const createManager = () => new AcpProcessManager({ + bootstrap: { appVersion: '0.0.0-test', homeDirectory: '/tmp', temporaryDirectory: '/tmp' }, publishEvent: publishDeepchatEventMock, providerId: 'acp', resolveLaunchSpec: vi.fn().mockResolvedValue({ @@ -123,10 +137,7 @@ describe('AcpProcessManager config cache fallback', () => { ] }) - const createProcessHandle = ( - child: MockSpawnedChild, - state: 'warmup' | 'bound' = 'warmup' - ) => { + const createProcessHandle = (child: MockSpawnedChild, state: 'warmup' | 'bound' = 'warmup') => { Object.defineProperty(child, 'pid', { value: undefined }) return { providerId: 'acp', @@ -224,9 +235,9 @@ describe('AcpProcessManager config cache fallback', () => { const second = manager.createAuthChallenge(handle as any, { origin: 'settings_probe' }) await manager.prepareTerminalAuthentication(first.id, 'browser-login') - await expect( - manager.prepareTerminalAuthentication(second.id, 'browser-login') - ).rejects.toThrow('already running for this agent and workdir') + await expect(manager.prepareTerminalAuthentication(second.id, 'browser-login')).rejects.toThrow( + 'already running for this agent and workdir' + ) manager.abandonAuthentication(first.id) await expect( @@ -338,6 +349,7 @@ describe('AcpProcessManager config cache fallback', () => { it('does not expose terminal methods as supported when the capability is disabled', () => { const manager = new AcpProcessManager({ + bootstrap: { appVersion: '0.0.0-test', homeDirectory: '/tmp', temporaryDirectory: '/tmp' }, publishEvent: publishDeepchatEventMock, providerId: 'acp', resolveLaunchSpec: vi.fn().mockResolvedValue({ @@ -364,6 +376,7 @@ describe('AcpProcessManager config cache fallback', () => { it('rejects terminal authentication when the capability is disabled', async () => { const manager = new AcpProcessManager({ + bootstrap: { appVersion: '0.0.0-test', homeDirectory: '/tmp', temporaryDirectory: '/tmp' }, publishEvent: publishDeepchatEventMock, providerId: 'acp', resolveLaunchSpec: vi.fn(), @@ -699,6 +712,7 @@ describe('AcpProcessManager config cache fallback', () => { cwd: '/tmp/workspace' } const manager = new AcpProcessManager({ + bootstrap: { appVersion: '0.0.0-test', homeDirectory: '/tmp', temporaryDirectory: '/tmp' }, publishEvent: publishDeepchatEventMock, providerId: 'acp', resolveLaunchSpec: vi.fn().mockResolvedValue(launchSpec), @@ -713,14 +727,6 @@ describe('AcpProcessManager config cache fallback', () => { vi.spyOn(shellEnvHelper, 'getShellEnvironment').mockResolvedValue({ PATH: '/shell/bin' }) - vi.spyOn((manager as any).runtimeHelper, 'expandPath').mockImplementation( - (value: string) => value - ) - vi.spyOn((manager as any).runtimeHelper, 'getDefaultPaths').mockReturnValue(['/default/bin']) - vi.spyOn((manager as any).runtimeHelper, 'isInstalledInSystemDirectory').mockReturnValue( - false - ) - vi.spyOn((manager as any).runtimeHelper, 'getUserNpmPrefix').mockReturnValue(null) existsSpy = vi.spyOn(fs, 'existsSync').mockReturnValue(true) statSpy = vi.spyOn(fs, 'statSync').mockReturnValue({ isDirectory: () => true @@ -780,9 +786,6 @@ describe('AcpProcessManager config cache fallback', () => { vi.spyOn(shellEnvHelper, 'getShellEnvironment').mockResolvedValue({ PATH: '/shell/bin' }) - vi.spyOn((manager as any).runtimeHelper, 'expandPath').mockImplementation( - (value: string) => value - ) try { await expect( @@ -1091,6 +1094,7 @@ describe('AcpProcessManager child process launch records', () => { const createManager = () => new AcpProcessManager({ + bootstrap: { appVersion: '0.0.0-test', homeDirectory: '/tmp', temporaryDirectory: '/tmp' }, publishEvent: publishDeepchatEventMock, providerId: 'acp', resolveLaunchSpec: vi.fn() @@ -1113,9 +1117,12 @@ describe('AcpProcessManager child process launch records', () => { it('waits for orphan recovery before spawning an agent', async () => { let finishRecovery!: () => void - childProcessRegistryMock.reapStaleOnce.mockImplementationOnce(() => new Promise((resolve) => { - finishRecovery = resolve - })) + childProcessRegistryMock.reapStaleOnce.mockImplementationOnce( + () => + new Promise((resolve) => { + finishRecovery = resolve + }) + ) const manager = createManager() const child = new MockSpawnedChild() vi.mocked(spawn).mockReturnValue(child as never) diff --git a/test/main/agent/acp/runtime/acpProcessManagerCapabilities.test.ts b/test/main/agent/acp/runtime/acpProcessManagerCapabilities.test.ts index bbc3409737..72b30d8c24 100644 --- a/test/main/agent/acp/runtime/acpProcessManagerCapabilities.test.ts +++ b/test/main/agent/acp/runtime/acpProcessManagerCapabilities.test.ts @@ -29,13 +29,6 @@ const sdkMock = vi.hoisted(() => ({ } })) -vi.mock('electron', () => ({ - app: { - getVersion: vi.fn(() => '0.0.0-test'), - getPath: vi.fn(() => '/tmp') - } -})) - const childProcessRegistryMock = vi.hoisted(() => ({ record: vi.fn(), clear: vi.fn(), @@ -64,6 +57,7 @@ describe('AcpProcessManager initialized capabilities', () => { sdkMock.initialize.mockClear() const { AcpProcessManager } = await import('@/agent/acp/runtime/acpProcessManager') const manager = new AcpProcessManager({ + bootstrap: { appVersion: '0.0.0-test', homeDirectory: '/tmp', temporaryDirectory: '/tmp' }, publishEvent: vi.fn(), providerId: 'acp', resolveLaunchSpec: vi.fn(), @@ -122,7 +116,9 @@ describe('AcpProcessManager initialized capabilities', () => { expect(handle.supportsSessionClose).toBe(true) expect(handle.supportsSessionFork).toBe(true) expect(handle.authMethods).toEqual([{ id: 'terminal', name: 'Terminal', type: 'terminal' }]) - const clientCapabilities = sdkMock.initialize.mock.calls.at(-1)?.[0].clientCapabilities + const initialize = sdkMock.initialize.mock.calls.at(-1)?.[0] + expect(initialize.clientInfo).toEqual({ name: 'DeepChat', version: '0.0.0-test' }) + const clientCapabilities = initialize.clientCapabilities if (terminalAuthAvailable) expect(clientCapabilities.auth).toEqual({ terminal: true }) else expect(clientCapabilities.auth).toBeUndefined() const received = vi.fn() diff --git a/test/main/agent/acp/runtime/acpSessionPersistence.test.ts b/test/main/agent/acp/runtime/acpSessionPersistence.test.ts index d9a1297dec..9c38dfaf8a 100644 --- a/test/main/agent/acp/runtime/acpSessionPersistence.test.ts +++ b/test/main/agent/acp/runtime/acpSessionPersistence.test.ts @@ -1,6 +1,5 @@ import * as path from 'path' import * as fs from 'fs' -import { app } from 'electron' import { beforeEach, describe, expect, it, vi } from 'vitest' import { AcpSessionPersistence } from '@/agent/acp/runtime' import type { AcpSessionEntity } from '@shared/types/acp' @@ -13,12 +12,6 @@ const createProjectDatabase = () => ({ } }) -vi.mock('electron', () => ({ - app: { - getPath: vi.fn(() => '/home/tester') - } -})) - describe('AcpSessionPersistence remote session sync', () => { beforeEach(() => { const usableDirectories = new Set([process.cwd(), path.dirname(process.cwd()), '/home/tester']) @@ -42,10 +35,18 @@ describe('AcpSessionPersistence remote session sync', () => { database as never, database as never, projectDatabase as never, + '/home/tester', notifyEnvironmentProjectionChanged ) - await persistence.saveSessionData('conversation-1', 'agent-1', 'remote-1' as never, null, 'idle', null) + await persistence.saveSessionData( + 'conversation-1', + 'agent-1', + 'remote-1' as never, + null, + 'idle', + null + ) expect(projectDatabase.newEnvironmentsTable.syncPath).toHaveBeenCalledWith('/work/project') expect(notifyEnvironmentProjectionChanged).toHaveBeenCalledTimes(1) @@ -54,19 +55,17 @@ describe('AcpSessionPersistence remote session sync', () => { it('falls back from missing persisted workdirs to the default workdir', () => { const homeDir = process.cwd() const missingDir = path.join(homeDir, 'missing-workdir-for-acp-test') - vi.mocked(app.getPath).mockReturnValue(homeDir) const database = {} as MainDatabase const persistence = new AcpSessionPersistence( database as never, database as never, - createProjectDatabase() as never + createProjectDatabase() as never, + homeDir ) expect(persistence.isWorkdirUsable(missingDir)).toBe(false) expect(persistence.resolveWorkdir(missingDir)).toBe(homeDir) - - vi.mocked(app.getPath).mockReturnValue('/home/tester') }) it('imports remote sessions once and updates the existing link on later syncs', async () => { @@ -112,7 +111,8 @@ describe('AcpSessionPersistence remote session sync', () => { const persistence = new AcpSessionPersistence( sqlitePresenter as never, sqlitePresenter as never, - createProjectDatabase() as never + createProjectDatabase() as never, + '/home/tester' ) const input = { agentId: 'agent-1', @@ -226,7 +226,8 @@ describe('AcpSessionPersistence remote session sync', () => { const persistence = new AcpSessionPersistence( sqlitePresenter as never, sqlitePresenter as never, - createProjectDatabase() as never + createProjectDatabase() as never, + '/home/tester' ) const input = { agentId: 'agent-1', @@ -296,7 +297,8 @@ describe('AcpSessionPersistence remote session sync', () => { const persistence = new AcpSessionPersistence( sqlitePresenter as never, sqlitePresenter as never, - createProjectDatabase() as never + createProjectDatabase() as never, + '/home/tester' ) const result = await persistence.syncRemoteSessions({ @@ -348,7 +350,8 @@ describe('AcpSessionPersistence remote session sync', () => { const persistence = new AcpSessionPersistence( sqlitePresenter as never, sqlitePresenter as never, - createProjectDatabase() as never + createProjectDatabase() as never, + '/home/tester' ) await expect( @@ -435,7 +438,8 @@ describe('AcpSessionPersistence remote session sync', () => { const persistence = new AcpSessionPersistence( sqlitePresenter as never, sqlitePresenter as never, - createProjectDatabase() as never + createProjectDatabase() as never, + '/home/tester' ) const firstMerge = persistence.mergeMetadata('conv-1', 'agent-1', { first: true }) @@ -455,7 +459,6 @@ describe('AcpSessionPersistence remote session sync', () => { it('keeps remote cwd metadata while using a local fallback for missing imported workdirs', async () => { const fallbackDir = process.cwd() const remoteMissingDir = path.join(fallbackDir, 'remote-missing') - vi.mocked(app.getPath).mockReturnValue(fallbackDir) const sqlitePresenter = { getAcpSessionByAgentAndSessionId: vi.fn(async () => null), @@ -465,45 +468,42 @@ describe('AcpSessionPersistence remote session sync', () => { const persistence = new AcpSessionPersistence( sqlitePresenter as never, sqlitePresenter as never, - createProjectDatabase() as never + createProjectDatabase() as never, + fallbackDir ) - try { - await persistence.syncRemoteSessions({ - agentId: 'agent-1', - agentName: 'Agent One', - providerId: 'acp', - workdir: remoteMissingDir, - sessions: [ - { - sessionId: 'remote-1', - cwd: remoteMissingDir, - title: 'Remote title' - } - ] - }) + await persistence.syncRemoteSessions({ + agentId: 'agent-1', + agentName: 'Agent One', + providerId: 'acp', + workdir: remoteMissingDir, + sessions: [ + { + sessionId: 'remote-1', + cwd: remoteMissingDir, + title: 'Remote title' + } + ] + }) - expect(sqlitePresenter.createConversation).toHaveBeenCalledWith( - 'Remote title', - expect.objectContaining({ - agentWorkspacePath: fallbackDir, - acpWorkdirMap: { 'agent-1': fallbackDir } - }) - ) - expect(sqlitePresenter.upsertAcpSession).toHaveBeenCalledWith( - 'conv-imported', - 'agent-1', - expect.objectContaining({ - workdir: fallbackDir, - metadata: expect.objectContaining({ - remoteSession: expect.objectContaining({ - cwd: remoteMissingDir - }) + expect(sqlitePresenter.createConversation).toHaveBeenCalledWith( + 'Remote title', + expect.objectContaining({ + agentWorkspacePath: fallbackDir, + acpWorkdirMap: { 'agent-1': fallbackDir } + }) + ) + expect(sqlitePresenter.upsertAcpSession).toHaveBeenCalledWith( + 'conv-imported', + 'agent-1', + expect.objectContaining({ + workdir: fallbackDir, + metadata: expect.objectContaining({ + remoteSession: expect.objectContaining({ + cwd: remoteMissingDir }) }) - ) - } finally { - vi.mocked(app.getPath).mockReturnValue('/home/tester') - } + }) + ) }) }) diff --git a/test/main/agent/acp/runtime/acpTerminalManager.test.ts b/test/main/agent/acp/runtime/acpTerminalManager.test.ts index e415623a14..f5746b63b6 100644 --- a/test/main/agent/acp/runtime/acpTerminalManager.test.ts +++ b/test/main/agent/acp/runtime/acpTerminalManager.test.ts @@ -8,12 +8,6 @@ vi.mock('node-pty', () => ({ spawn: vi.fn() })) -vi.mock('electron', () => ({ - app: { - getPath: vi.fn((name: string) => (name === 'temp' ? '/tmp' : '/tmp')) - } -})) - describe('AcpTerminalManager', () => { const createPty = () => ({ onData: vi.fn(), @@ -28,7 +22,7 @@ describe('AcpTerminalManager', () => { }) it('uses the provided cwd when one is supplied', async () => { - const manager = new AcpTerminalManager() + const manager = new AcpTerminalManager('/tmp') await manager.createTerminal({ sessionId: 'session-1', @@ -46,27 +40,30 @@ describe('AcpTerminalManager', () => { }) it('falls back to a controlled temp directory when cwd is missing', async () => { - const manager = new AcpTerminalManager() + const manager = new AcpTerminalManager('/tmp/acp-owned-temp') await manager.createTerminal({ sessionId: 'session-1', command: 'pwd' }) - expect(fs.mkdirSync).toHaveBeenCalledWith(path.normalize('/tmp/deepchat-acp/terminals'), { - recursive: true - }) + expect(fs.mkdirSync).toHaveBeenCalledWith( + path.normalize('/tmp/acp-owned-temp/deepchat-acp/terminals'), + { + recursive: true + } + ) expect(spawn).toHaveBeenCalledWith( 'pwd', [], expect.objectContaining({ - cwd: expect.stringContaining(path.normalize('/tmp/deepchat-acp/terminals')) + cwd: expect.stringContaining(path.normalize('/tmp/acp-owned-temp/deepchat-acp/terminals')) }) ) }) it('passes command arguments directly without shell concatenation', async () => { - const manager = new AcpTerminalManager() + const manager = new AcpTerminalManager('/tmp') await manager.createTerminal({ sessionId: 'session-1', @@ -87,7 +84,7 @@ describe('AcpTerminalManager', () => { it('retains the latest terminal output when outputByteLimit is exceeded', async () => { const pty = createPty() vi.mocked(spawn).mockReturnValue(pty as never) - const manager = new AcpTerminalManager() + const manager = new AcpTerminalManager('/tmp') const response = await manager.createTerminal({ sessionId: 'session-1', diff --git a/test/main/agent/deepchat/harness/deepChatAgentHarness.test.ts b/test/main/agent/deepchat/harness/deepChatAgentHarness.test.ts index 28b974fe4f..9063950730 100644 --- a/test/main/agent/deepchat/harness/deepChatAgentHarness.test.ts +++ b/test/main/agent/deepchat/harness/deepChatAgentHarness.test.ts @@ -1,3 +1,5 @@ +import { useHarnessControl } from '../helpers/control' +import { MAX_PENDING_INPUTS } from '@shared/pendingInput' import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest' import { createHash } from 'node:crypto' import fs from 'fs/promises' @@ -85,6 +87,7 @@ import { DeepChatLoopRunner } from '@/agent/deepchat/runtime/deepChatLoopRunner' vi.mock('nanoid', () => ({ nanoid: vi.fn(() => 'mock-msg-id') })) +const control = useHarnessControl() const publishDeepchatEvent = vi.fn() const UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i @@ -193,13 +196,23 @@ function getRuntimeState(agent: DeepChatAgentHarness, sessionId: string): DeepCh return state } -function setRuntimeStatus( +async function setRuntimeStatus( agent: DeepChatAgentHarness, sessionId: string, status: DeepChatSessionState['status'] -): void { +): Promise { const instance = agent.deepChatRuntime.getOrHydrate(toAppSessionId(sessionId)) - instance.setRuntimeState({ ...getRuntimeState(agent, sessionId), status }) + await instance.controlReady() + const run = instance.getActiveGeneration() + const controller = instance.getAbortController() + await instance.transitionControlStatus( + run + ? { kind: 'run', id: run.runId } + : controller + ? instance.operationOwner(controller) + : { kind: 'orphan' }, + status + ) } function deferred() { @@ -1140,6 +1153,7 @@ function createRuntimeDependencies( attachmentRouter: { prepare: ReturnType } } { return { + control: control(), publishEvent: publishDeepchatEvent, publishSessionUpdate: vi.fn(), providerCatalogPort: { @@ -1205,7 +1219,11 @@ function createRuntimeDependencies( function createMemoryRuntimePort(): MemoryRuntimePort { return { isEnabled: vi.fn(() => false), - captureExecutionToken: vi.fn((agentId: string) => ({ agentId, generation: 0 })), + captureExecutionToken: vi.fn((agentId: string) => ({ + ownerId: 'memory-test-owner', + agentId, + generation: 0 + })), canContinueExecution: vi.fn(() => true), getInjectionTokenBudget: vi.fn(() => 1_200), buildInjection: vi.fn().mockResolvedValue(null), @@ -1392,7 +1410,8 @@ describe('DeepChatAgentHarness', () => { ...port, isEnabled, captureExecutionToken: - port.captureExecutionToken ?? vi.fn((agentId: string) => ({ agentId, generation: 0 })), + port.captureExecutionToken ?? + vi.fn((agentId: string) => ({ ownerId: 'memory-test-owner', agentId, generation: 0 })), canContinueExecution: port.canContinueExecution ?? vi.fn( @@ -1595,6 +1614,7 @@ describe('DeepChatAgentHarness', () => { ): void => { agent = createDeepChatAgentHarness({ ...runtimeDependencies, + control: control(), providerRuntime: llmProvider, providerSettings, agentSettings: providerSettings, @@ -1724,10 +1744,8 @@ describe('DeepChatAgentHarness', () => { }) transcriptMutations = new SessionTranscriptMutations({ transcript: sessionData.transcript, - settings: sessionData.settings, - pendingInputs: sessionData.pendingInputs, runtime: agent, - runInTransaction: (operation) => sessionData.database.getDatabase().transaction(operation)() + clearTranscript: sessionData.clearTranscript }) }) @@ -1766,10 +1784,13 @@ describe('DeepChatAgentHarness', () => { const processing = agent.processMessage('s1', privateContent) await activeSkillsStarted.promise + await control.flush() await vi.advanceTimersByTimeAsync(PRE_STREAM_STUCK_WARN_MS - 1) expect(stuckWarnings()).toHaveLength(0) + await control.flush() await vi.advanceTimersByTimeAsync(1) expect(stuckWarnings()).toHaveLength(1) + await control.flush() await vi.advanceTimersByTimeAsync(PRE_STREAM_STUCK_ESCALATION_MS - PRE_STREAM_STUCK_WARN_MS) expect(stuckWarnings()).toHaveLength(2) expect(stuckWarnings()[0]).toContain('step=active-skills') @@ -1779,6 +1800,7 @@ describe('DeepChatAgentHarness', () => { await agent.cancelGeneration('s1') pending.resolve([]) await processing + await control.flush() await vi.advanceTimersByTimeAsync(PRE_STREAM_STUCK_ESCALATION_MS) expect(stuckWarnings()).toHaveLength(2) } finally { @@ -1799,6 +1821,7 @@ describe('DeepChatAgentHarness', () => { ) await agent.processMessage('s1', 'rejected step') + await control.flush() await vi.advanceTimersByTimeAsync(PRE_STREAM_STUCK_ESCALATION_MS) expect(stuckWarnings()).toHaveLength(0) expect(warn.mock.calls.map(([message]) => String(message)).join('\n')).not.toContain( @@ -1832,6 +1855,7 @@ describe('DeepChatAgentHarness', () => { await vi.waitFor(() => expect(providerSettings.getDefaultSystemPrompt).toHaveBeenCalledTimes(2) ) + await control.flush() await vi.advanceTimersByTimeAsync(PRE_STREAM_STUCK_WARN_MS) expect(stuckWarnings()).toEqual([ expect.stringContaining('message= step=generation-settings') @@ -1853,6 +1877,7 @@ describe('DeepChatAgentHarness', () => { callsBeforeResume ) ) + await control.flush() await vi.advanceTimersByTimeAsync(PRE_STREAM_STUCK_WARN_MS) expect(stuckWarnings()).toEqual([ expect.stringContaining('message=m1 step=generation-settings') @@ -1893,6 +1918,7 @@ describe('DeepChatAgentHarness', () => { await agent.initSession('s1', { providerId: 'openai', modelId: 'gpt-4' }) const processing = agent.processMessage('s1', 'Hello') await providerStarted.promise + await control.flush() await vi.advanceTimersByTimeAsync(PRE_STREAM_STUCK_ESCALATION_MS) expect( stuckWarnings().filter((message) => message.includes('step=pre-stream-provider-start')) @@ -1927,6 +1953,7 @@ describe('DeepChatAgentHarness', () => { await agent.initSession('s1', { providerId: 'openai', modelId: 'gpt-4' }) const processing = agent.processMessage('s1', 'Hello') await vi.waitFor(() => expect(llmProvider.executeWithRateLimit).toHaveBeenCalledTimes(1)) + await control.flush() await vi.advanceTimersByTimeAsync(PRE_STREAM_STUCK_ESCALATION_MS) expect( stuckWarnings().filter((message) => message.includes('step=pre-stream-provider-start')) @@ -1949,6 +1976,7 @@ describe('DeepChatAgentHarness', () => { try { await agent.initSession('s1', { providerId: 'openai', modelId: 'gpt-4' }) await agent.processMessage('s1', 'Hello') + await control.flush() await vi.advanceTimersByTimeAsync(PRE_STREAM_STUCK_ESCALATION_MS) expect( stuckWarnings().filter((message) => message.includes('step=pre-stream-provider-start')) @@ -1980,10 +2008,12 @@ describe('DeepChatAgentHarness', () => { const processing = agent.processMessage('s1', 'Hello') await vi.waitFor(() => expect(processStream).toHaveBeenCalledOnce()) await agent.cancelGeneration('s1') + await control.flush() await vi.advanceTimersByTimeAsync(PRE_STREAM_STUCK_ESCALATION_MS) providerAdmission.resolve() await processing + await control.flush() await vi.advanceTimersByTimeAsync(PRE_STREAM_STUCK_ESCALATION_MS) expect( @@ -3842,7 +3872,7 @@ describe('DeepChatAgentHarness', () => { 'tool_b', 'tool_a' ]) - expect(eventOrder).toEqual(['provider-entered', 'provider-complete']) + expect(eventOrder.slice(0, 2)).toEqual(['provider-entered', 'provider-complete']) await vi.waitFor(() => expect(eventOrder).toEqual(['provider-entered', 'provider-complete', 'shadow-universe']) ) @@ -5428,6 +5458,7 @@ describe('DeepChatAgentHarness', () => { const processing = agent.processMessage('s1', 'Hello') await vi.waitFor(() => expect(buildInjection).toHaveBeenCalledTimes(1)) + await control.flush() await vi.advanceTimersByTimeAsync(MEMORY_INJECTION_TIMEOUT_MS) await processing @@ -6091,8 +6122,47 @@ describe('DeepChatAgentHarness', () => { state: 'claimed' }) + const releaseAcknowledged = deferred() + const continuePromotion = deferred() + let preparationClaimId: string | undefined + const sendControl = runtimeDependencies.control.send.bind(runtimeDependencies.control) + vi.spyOn(runtimeDependencies.control, 'send').mockImplementation(async (command, receive) => { + if (command.method === 'prepareClaimEffect' && command.action === 'release-for-mutation') { + preparationClaimId = command.claimId + } + const receipt = await sendControl(command, receive) + if ( + command.method === 'ackInputEffect' && + preparationClaimId && + command.claimId === preparationClaimId && + command.outcome === 'applied' + ) { + releaseAcknowledged.resolve() + await continuePromotion.promise + } + return receipt + }) preflightDone.resolve() - await steerPromise + try { + await releaseAcknowledged.promise + expect(sqlitePresenter.deepchatPendingInputsTable.get(firstQueued.id)).toMatchObject({ + mode: 'queue', + state: 'pending' + }) + await agent.updateQueuedInput('s1', 'steer-preflight-second', { + text: 'Must remain second', + files: [] + }) + await control.flush() + expect(processStream).toHaveBeenCalledOnce() + expect(sqlitePresenter.deepchatPendingInputsTable.get(firstQueued.id)).toMatchObject({ + mode: 'queue', + state: 'pending' + }) + } finally { + continuePromotion.resolve() + await steerPromise + } await vi.waitFor(() => expect(processStream).toHaveBeenCalledTimes(2)) const secondRun = (processStream as ReturnType).mock.calls[1][0] @@ -7542,6 +7612,8 @@ describe('DeepChatAgentHarness', () => { modelId: 'gpt-4', permissionMode: 'default' }) + await replacement.controlReady() + await replacement.transitionControlStatus({ kind: 'orphan' }, 'generating') replacement.replaceRuntimeActivatedSkills(['replacement-skill']) replacement.setToolProfileCache({ profile: 'general', @@ -10228,6 +10300,7 @@ describe('DeepChatAgentHarness', () => { modelId: 'gpt-4', permissionMode: 'full_access' }) + await setRuntimeStatus(agent, 's1', 'generating') await expect(agent.setSessionModel('s1', 'anthropic', 'claude-3-5-sonnet')).rejects.toThrow( 'Cannot switch model while session is generating.' @@ -10392,7 +10465,7 @@ describe('DeepChatAgentHarness', () => { const instance = agent.deepChatRuntime.getHydrated(toAppSessionId('s1')) await expect(agent.getSessionState('s1')).resolves.toMatchObject({ status: 'generating' }) expect(instance?.getPendingInteractions()).toHaveLength(1) - setRuntimeStatus(agent, 's1', 'generating') + await setRuntimeStatus(agent, 's1', 'generating') await agent.cancelGeneration('s1') @@ -10506,12 +10579,14 @@ describe('DeepChatAgentHarness', () => { }) it('ignores stale run completion after a newer turn starts', async () => { + const firstRunStarted = deferred() let resolveFirstRun: ((value: any) => void) | null = null ;(processStream as ReturnType) .mockImplementationOnce( () => new Promise((resolve) => { resolveFirstRun = resolve + firstRunStarted.resolve() }) ) .mockResolvedValueOnce({ @@ -10533,7 +10608,7 @@ describe('DeepChatAgentHarness', () => { await agent.initSession('s1', { providerId: 'openai', modelId: 'gpt-4' }) const firstProcess = agent.processMessage('s1', 'First') - await new Promise((resolve) => setTimeout(resolve, 10)) + await firstRunStarted.promise await agent.cancelGeneration('s1') await agent.processMessage('s1', 'Second') @@ -10660,7 +10735,7 @@ describe('DeepChatAgentHarness', () => { await agent.initSession('s1', { providerId: 'openai', modelId: 'gpt-4' }) const cancelSpy = vi.spyOn(agent, 'cancelGeneration').mockResolvedValue(undefined) const controller = new AbortController() - agent.deepChatRuntime.getOrHydrate(toAppSessionId('s1')).registerActiveGeneration( + await agent.deepChatRuntime.getOrHydrate(toAppSessionId('s1')).registerActiveGeneration( createLoopRun({ runId: 'run-1', sessionId: toAppSessionId('s1'), @@ -10800,7 +10875,7 @@ describe('DeepChatAgentHarness', () => { it('preflights a normal send before accepting it into a busy session queue', async () => { await agent.initSession('s1', { providerId: 'openai', modelId: 'gpt-4' }) - setRuntimeStatus(agent, 's1', 'generating') + await setRuntimeStatus(agent, 's1', 'generating') const summary = { status: 'needs_user_action' as const, issues: [{ attachmentIndex: 0, reason: 'ocr_empty' as const }], @@ -10828,7 +10903,7 @@ describe('DeepChatAgentHarness', () => { it('preserves send order while an earlier attachment preflight is still running', async () => { await agent.initSession('s1', { providerId: 'openai', modelId: 'gpt-4' }) - setRuntimeStatus(agent, 's1', 'generating') + await setRuntimeStatus(agent, 's1', 'generating') const firstStarted = deferred() const releaseFirst = deferred() const prepare = vi.fn(async ({ content }) => { @@ -10874,7 +10949,7 @@ describe('DeepChatAgentHarness', () => { it('removes a cancelled send waiter without letting later sends bypass the active preflight', async () => { await agent.initSession('s1', { providerId: 'openai', modelId: 'gpt-4' }) - setRuntimeStatus(agent, 's1', 'generating') + await setRuntimeStatus(agent, 's1', 'generating') const firstStarted = deferred() const releaseFirst = deferred() const prepare = vi.fn(async ({ content }) => { @@ -11086,7 +11161,7 @@ describe('DeepChatAgentHarness', () => { } sqlitePresenter.deepchatMessagesTable.getBySession.mockReturnValue([interactionRow]) const errorSpy = vi.spyOn(console, 'error').mockImplementation(() => undefined) - setRuntimeStatus(agent, 's1', 'generating') + await setRuntimeStatus(agent, 's1', 'generating') try { const claimed = await agent.queuePendingInput('s1', 'Retry after interaction') @@ -11102,8 +11177,10 @@ describe('DeepChatAgentHarness', () => { expect(processStream).not.toHaveBeenCalled() sqlitePresenter.deepchatMessagesTable.getBySession.mockReturnValue([]) - agent.deepChatRuntime.getHydrated(toAppSessionId('s1'))?.replacePendingInteractions([]) - setRuntimeStatus(agent, 's1', 'idle') + await agent.deepChatRuntime + .getHydrated(toAppSessionId('s1')) + ?.replacePendingInteractions([]) + await setRuntimeStatus(agent, 's1', 'idle') sqlitePresenter.deepchatPendingInputsTable.update.mockClear() await expect(agent.retryPendingQueueInput('s1', claimed.id)).resolves.toMatchObject({ accepted: true @@ -11145,7 +11222,7 @@ describe('DeepChatAgentHarness', () => { ]) } ]) - setRuntimeStatus(agent, 's1', 'generating') + await setRuntimeStatus(agent, 's1', 'generating') const firstPreparationStarted = deferred() const releaseFirstPreparation = deferred() runtimeDependencies.attachmentRouter.prepare = vi.fn(async ({ content }) => { @@ -11177,7 +11254,7 @@ describe('DeepChatAgentHarness', () => { await vi.waitFor(() => expect(processStream).toHaveBeenCalledOnce()) }) - it('keeps a pre-user-fact runtime failure pending until an explicit retry', async () => { + it('keeps a failed attachment recheck retryable until explicitly retried', async () => { await agent.initSession('s1', { providerId: 'openai', modelId: 'gpt-4' }) const ready = { status: 'ready' as const, issues: [], suggestedActions: [] } const prepare = vi @@ -11194,10 +11271,11 @@ describe('DeepChatAgentHarness', () => { }) expect(queued.state).toBe('pending') await agent.updateQueuedInput('s1', queued.id, queued.payload) + await vi.waitFor(() => expect(prepare).toHaveBeenCalledOnce()) await vi.waitFor(async () => { expect(await agent.listPendingInputs('s1')).toEqual([ - expect.objectContaining({ id: queued.id, state: 'pending' }) + expect.objectContaining({ id: queued.id, state: 'retry_required' }) ]) }) expect(prepare).toHaveBeenCalledOnce() @@ -11215,6 +11293,94 @@ describe('DeepChatAgentHarness', () => { } }) + it('rejects a full waiting lane before preparing another Send attachment', async () => { + await agent.initSession('s1', { providerId: 'openai', modelId: 'gpt-4' }) + let id = 0 + vi.mocked(nanoid).mockImplementation(() => `capacity-${++id}`) + for (let index = 0; index < MAX_PENDING_INPUTS; index++) { + sessionData.pendingInputs.queuePendingInput('s1', { text: `Waiting ${index}`, files: [] }) + } + runtimeDependencies.attachmentRouter.prepare.mockClear() + + await expect( + agent.send('s1', { content: { text: 'At capacity', files: [] }, queue: { source: 'send' } }) + ).rejects.toThrow('Pending input limit reached for this session.') + + expect(runtimeDependencies.attachmentRouter.prepare).not.toHaveBeenCalled() + expect(await agent.listPendingInputs('s1')).toHaveLength(MAX_PENDING_INPUTS) + expect(processStream).not.toHaveBeenCalled() + }) + + it.each(['publication', 'immediate-drain'] as const)( + 'keeps a durable Queue retry accepted after a %s failure', + async (failureAt) => { + await agent.initSession('s1', { providerId: 'openai', modelId: 'gpt-4' }) + let id = 0 + vi.mocked(nanoid).mockImplementation(() => `retry-${++id}`) + const queued = sessionData.pendingInputs.queuePendingInput('s1', { + text: 'Retry me', + files: [] + }) + sessionData.pendingInputs.claimQueuedInput('s1', queued.id) + sessionData.pendingInputs.releaseClaimedQueueInputForRetry('s1', queued.id) + const retry = sessionData.pendingInputs.retryReleasedQueueInput.bind( + sessionData.pendingInputs + ) + const failure = new Error('retry notification unavailable') + vi.spyOn(sessionData.pendingInputs, 'retryReleasedQueueInput').mockImplementationOnce( + (...args) => { + const record = retry(...args) + if (failureAt === 'publication') throw failure + vi.spyOn(sessionData.pendingInputs, 'listPendingInputs').mockImplementationOnce(() => { + throw failure + }) + return record + } + ) + + await expect(agent.retryPendingQueueInput('s1', queued.id)).resolves.toEqual({ + accepted: true, + started: failureAt === 'publication' + }) + + await vi.waitFor(() => expect(processStream).toHaveBeenCalledOnce()) + await vi.waitFor(async () => expect(await agent.listPendingInputs('s1')).toEqual([])) + expect(logger.error).toHaveBeenCalledWith( + expect.stringContaining( + failureAt === 'publication' + ? 'retry pending queue publication failed' + : 'retry pending queue drain failed' + ), + { name: 'Error' } + ) + } + ) + + it('preserves the attachment failure when its claimed Queue release also fails', async () => { + await agent.initSession('s1', { providerId: 'openai', modelId: 'gpt-4' }) + const queued = sessionData.pendingInputs.queuePendingInput('s1', { + text: 'Steer me', + files: [] + }) + const preparationError = new Error('attachment runtime unavailable') + runtimeDependencies.attachmentRouter.prepare.mockRejectedValueOnce(preparationError) + vi.spyOn( + sessionData.pendingInputs, + 'releaseClaimedQueueInputForRetry' + ).mockImplementationOnce(() => { + throw new Error('claim release unavailable') + }) + + await expect(agent.steerPendingInput('s1', queued.id)).rejects.toBe(preparationError) + + expect(sessionData.pendingInputs.getInput('s1', queued.id)?.state).toBe('claimed') + expect(processStream).not.toHaveBeenCalled() + expect(logger.error).toHaveBeenCalledWith( + expect.stringContaining('failed to release pending input after admission failure'), + { name: 'Error' } + ) + }) + it('rolls back a user fact when compaction fails after the fact is appended', async () => { await agent.initSession('s1', { providerId: 'openai', modelId: 'gpt-4' }) sqlitePresenter.deepchatSessionsTable.updateMemoryCursorOrderSeq('s1', 8) @@ -11534,6 +11700,7 @@ describe('DeepChatAgentHarness', () => { }) const restartedAgent = createDeepChatAgentHarness({ ...runtimeDependencies, + control: control(), providerRuntime: llmProvider, providerSettings, agentSettings: providerSettings, @@ -14049,6 +14216,7 @@ describe('DeepChatAgentHarness', () => { }) const compaction = agent.compactSession('s1') + const rejected = expect(compaction).rejects.toMatchObject({ name: 'AbortError' }) await vi.waitFor(() => expect(prepareForManualCompaction).toHaveBeenCalledTimes(1)) const sessionId = toAppSessionId('s1') @@ -14060,6 +14228,8 @@ describe('DeepChatAgentHarness', () => { modelId: 'gpt-4', permissionMode: 'default' }) + await replacement.controlReady() + await replacement.transitionControlStatus({ kind: 'orphan' }, 'generating') replacement.setToolProfileCache({ profile: 'general', fingerprint: 'replacement tool fingerprint', @@ -14072,9 +14242,7 @@ describe('DeepChatAgentHarness', () => { }) preparation.resolve(null) - await expect(compaction).rejects.toMatchObject({ - name: 'StaleDeepChatAgentInstanceError' - }) + await rejected expect(replacement.getRuntimeState()?.status).toBe('generating') expect(replacement.getToolProfileCache()?.fingerprint).toBe('replacement tool fingerprint') @@ -14112,6 +14280,7 @@ describe('DeepChatAgentHarness', () => { const sessionId = toAppSessionId('s1') const staleInstance = agent.deepChatRuntime.getOrHydrate(sessionId) const compaction = agent.compactSession('s1') + const rejected = expect(compaction).rejects.toMatchObject({ name: 'AbortError' }) await vi.waitFor(() => expect(applyCompaction).toHaveBeenCalledTimes(1)) expect(staleInstance.getCompactionState()?.status).toBe('compacting') @@ -14123,6 +14292,8 @@ describe('DeepChatAgentHarness', () => { modelId: 'gpt-4', permissionMode: 'default' }) + await replacement.controlReady() + await replacement.transitionControlStatus({ kind: 'orphan' }, 'generating') replacement.setCompactionState({ status: 'compacted', cursorOrderSeq: 17, @@ -14138,9 +14309,7 @@ describe('DeepChatAgentHarness', () => { } }) - await expect(compaction).rejects.toMatchObject({ - name: 'StaleDeepChatAgentInstanceError' - }) + await rejected expect(replacement.getCompactionState()).toEqual({ status: 'compacted', cursorOrderSeq: 17, @@ -14196,7 +14365,7 @@ describe('DeepChatAgentHarness', () => { providerId: 'openai', modelId: 'gpt-4' }) - setRuntimeStatus(agent, 's1', 'generating') + await setRuntimeStatus(agent, 's1', 'generating') await expect(agent.compactSession('s1')).rejects.toThrow( 'Manual compaction is only available when the session is idle.' @@ -14598,18 +14767,20 @@ describe('DeepChatAgentHarness', () => { return row } - const registerActiveInteractionRun = ( + const registerActiveInteractionRun = async ( messageId: string, blocks: AssistantMessageBlock[], - runId = `run-${messageId}` + runId = `run-${messageId}`, + sessionId = 's1' ) => { - const instance = agent.deepChatRuntime.getOrHydrate(toAppSessionId('s1')) - const abortController = new AbortController() + const instance = agent.deepChatRuntime.getOrHydrate(toAppSessionId(sessionId)) + await instance.controlReady() + const abortController = await instance.ensureOperationController() const streamState = createState() streamState.blocks = structuredClone(blocks) const run = createLoopRun({ runId, - sessionId: toAppSessionId('s1'), + sessionId: toAppSessionId(sessionId), messageId, abortController, messages: [], @@ -14621,8 +14792,8 @@ describe('DeepChatAgentHarness', () => { toolMode: { mode: 'agent', source: 'fallback' } } }) - instance.registerActiveGeneration(run) - setRuntimeStatus(agent, 's1', 'generating') + await instance.registerActiveGeneration(run) + await setRuntimeStatus(agent, sessionId, 'generating') return { instance, run, abortController, streamState } } @@ -15642,7 +15813,7 @@ describe('DeepChatAgentHarness', () => { } ] }) - const { instance, abortController } = registerActiveInteractionRun('new-message', []) + const { instance, abortController } = await registerActiveInteractionRun('new-message', []) const interaction = agent.respondToolInteraction('s1', 'm1', 'tc1', { kind: 'question_option', @@ -17163,7 +17334,7 @@ describe('DeepChatAgentHarness', () => { } ) const instance = agent.deepChatRuntime.getHydrated(toAppSessionId('s1')) - instance?.replacePendingToolBatch( + await instance?.replacePendingToolBatch( [ { messageId: 'm1', @@ -17262,59 +17433,62 @@ describe('DeepChatAgentHarness', () => { ] }) - const { instance } = registerActiveInteractionRun( + const { instance } = await registerActiveInteractionRun( 'm1', JSON.parse(row.content) as AssistantMessageBlock[] ) - instance.registerActiveProviderPermission({ - requestId: 'acp-req-1', - messageId: 'm1', - toolCallId: 'tc1', - providerId: 'acp', - permissionType: 'command', - resolve: async (granted: boolean) => { - await llmProvider.resolveAgentPermission('acp-req-1', granted) - sqlitePresenter.deepchatMessagesTable.updateContent( - 'm1', - JSON.stringify([ - { - type: 'tool_call', - status: 'pending', - timestamp: 1, - tool_call: { - id: 'tc1', - name: 'Terminal', - params: '{"command":"dir"}', - response: '' - } - }, - { - type: 'action', - action_type: 'tool_call_permission', - status: 'granted', - timestamp: 2, - content: 'components.messageBlockPermissionRequest.description.command', - tool_call: { id: 'tc1', name: 'Terminal', params: '{"command":"dir"}' }, - extra: { - needsUserAction: false, - permissionType: 'command', - grantedPermissions: 'command', - providerId: 'acp', - permissionRequestId: 'acp-req-1', - permissionRequest: JSON.stringify({ + await instance.registerActiveProviderPermission( + { + requestId: 'acp-req-1', + messageId: 'm1', + toolCallId: 'tc1', + providerId: 'acp', + permissionType: 'command', + resolve: async (granted: boolean) => { + await llmProvider.resolveAgentPermission('acp-req-1', granted) + sqlitePresenter.deepchatMessagesTable.updateContent( + 'm1', + JSON.stringify([ + { + type: 'tool_call', + status: 'pending', + timestamp: 1, + tool_call: { + id: 'tc1', + name: 'Terminal', + params: '{"command":"dir"}', + response: '' + } + }, + { + type: 'action', + action_type: 'tool_call_permission', + status: 'granted', + timestamp: 2, + content: 'components.messageBlockPermissionRequest.description.command', + tool_call: { id: 'tc1', name: 'Terminal', params: '{"command":"dir"}' }, + extra: { + needsUserAction: false, permissionType: 'command', - description: 'components.messageBlockPermissionRequest.description.command', - toolName: 'Terminal', + grantedPermissions: 'command', providerId: 'acp', - requestId: 'acp-req-1', - command: 'dir' - }) + permissionRequestId: 'acp-req-1', + permissionRequest: JSON.stringify({ + permissionType: 'command', + description: 'components.messageBlockPermissionRequest.description.command', + toolName: 'Terminal', + providerId: 'acp', + requestId: 'acp-req-1', + command: 'dir' + }) + } } - } - ]) - ) - } - }) + ]) + ) + } + }, + instance.getActiveGeneration()!.runId + ) const result = await agent.respondToolInteraction('s1', 'm1', 'tc1', { kind: 'permission', @@ -17337,24 +17511,41 @@ describe('DeepChatAgentHarness', () => { it('cancels only the target session live ACP permission resolvers', async () => { const resolve = vi.fn().mockResolvedValue(undefined) - const first = agent.deepChatRuntime.getOrHydrate(toAppSessionId('s1')) - const second = agent.deepChatRuntime.getOrHydrate(toAppSessionId('s2')) - first.registerActiveProviderPermission({ - requestId: 'acp-req-1', - messageId: 'm1', - toolCallId: 'tc1', - providerId: 'acp', - permissionType: 'command', - resolve - }) - second.registerActiveProviderPermission({ - requestId: 'acp-req-2', - messageId: 'm2', - toolCallId: 'tc2', - providerId: 'acp', - permissionType: 'command', - resolve: vi.fn().mockResolvedValue(undefined) - }) + await agent.initSession('s1', { providerId: 'acp', modelId: 'claude-code-acp' }) + await agent.initSession('s2', { providerId: 'acp', modelId: 'claude-code-acp' }) + const { instance: first, run: firstRun } = await registerActiveInteractionRun( + 'm1', + [], + 'run-1' + ) + const { instance: second, run: secondRun } = await registerActiveInteractionRun( + 'm2', + [], + 'run-2', + 's2' + ) + await first.registerActiveProviderPermission( + { + requestId: 'acp-req-1', + messageId: 'm1', + toolCallId: 'tc1', + providerId: 'acp', + permissionType: 'command', + resolve + }, + firstRun.runId + ) + await second.registerActiveProviderPermission( + { + requestId: 'acp-req-2', + messageId: 'm2', + toolCallId: 'tc2', + providerId: 'acp', + permissionType: 'command', + resolve: vi.fn().mockResolvedValue(undefined) + }, + secondRun.runId + ) await agent.cancelGeneration('s1') await Promise.resolve() @@ -17398,7 +17589,7 @@ describe('DeepChatAgentHarness', () => { } ] }) - const { instance, streamState } = registerActiveInteractionRun( + const { instance, streamState } = await registerActiveInteractionRun( 'm1', JSON.parse(row.content) as AssistantMessageBlock[] ) @@ -17470,7 +17661,7 @@ describe('DeepChatAgentHarness', () => { } ] }) - const { instance, abortController } = registerActiveInteractionRun( + const { instance, abortController } = await registerActiveInteractionRun( 'm1', JSON.parse(row.content) as AssistantMessageBlock[] ) @@ -17533,7 +17724,7 @@ describe('DeepChatAgentHarness', () => { } ] }) - const { instance, abortController } = registerActiveInteractionRun( + const { instance, abortController } = await registerActiveInteractionRun( 'new-message', [], 'new-run' @@ -17709,20 +17900,23 @@ describe('DeepChatAgentHarness', () => { } ] }) - const { instance, abortController } = registerActiveInteractionRun( + const { instance, abortController } = await registerActiveInteractionRun( 'new-message', [], 'new-run' ) const resolveNewPermission = vi.fn().mockResolvedValue(undefined) - instance.registerActiveProviderPermission({ - requestId: 'acp-shared-req', - messageId: 'new-message', - toolCallId: 'tc-new', - providerId: 'acp', - permissionType: 'command', - resolve: resolveNewPermission - }) + await instance.registerActiveProviderPermission( + { + requestId: 'acp-shared-req', + messageId: 'new-message', + toolCallId: 'tc-new', + providerId: 'acp', + permissionType: 'command', + resolve: resolveNewPermission + }, + instance.getActiveGeneration()!.runId + ) await expect( agent.respondToolInteraction('s1', 'm1', 'tc-old', { @@ -17757,7 +17951,7 @@ describe('DeepChatAgentHarness', () => { it('terminalizes a stale unowned ACP permission as provider_error', async () => { await agent.initSession('s1', { providerId: 'acp', modelId: 'claude-code-acp' }) - setRuntimeStatus(agent, 's1', 'generating') + await setRuntimeStatus(agent, 's1', 'generating') llmProvider.resolveAgentPermission.mockRejectedValueOnce( new Error('Unknown ACP permission request: acp-stale-req') ) @@ -17838,7 +18032,7 @@ describe('DeepChatAgentHarness', () => { commandSignature: 'posix:npm test', shellProfile: 'posix' }) - const { abortController } = registerActiveInteractionRun( + const { abortController } = await registerActiveInteractionRun( 'm1', JSON.parse(row.content) as AssistantMessageBlock[] ) @@ -18007,6 +18201,8 @@ describe('DeepChatAgentHarness', () => { modelId: 'gpt-4', permissionMode: 'auto_approve' }) + await instance.controlReady() + await instance.transitionControlStatus({ kind: 'orphan' }, 'generating') const dismissed = await agent.dismissToolInteraction('s1', 'm1', 'tc1') @@ -18260,6 +18456,7 @@ describe('DeepChatAgentHarness', () => { } ) + await control.flush() await vi.advanceTimersByTimeAsync(30_000) await expect(resultPromise).resolves.toEqual( expect.objectContaining({ @@ -18748,6 +18945,7 @@ describe('DeepChatAgentHarness', () => { }) agent = createDeepChatAgentHarness({ ...runtimeDependencies, + control: control(), sessionPermissionPort, database: sqlitePresenter, sessionData, @@ -18789,7 +18987,12 @@ describe('DeepChatAgentHarness', () => { }) ) expect(executeDeferredToolCallSpy).toHaveBeenCalledWith( - 's1', + expect.objectContaining({ + scope: expect.objectContaining({ sessionId: 's1' }), + lease: expect.objectContaining({ + target: { kind: 'interaction', messageId: 'm1', toolCallId: 'tc1' } + }) + }), 'm1', expect.objectContaining({ id: 'tc1', name: 'exec' }), expect.any(Function), @@ -19165,7 +19368,7 @@ describe('DeepChatAgentHarness', () => { const executionPromise = approvePendingTool('m1', 'tc-subagent') - await new Promise((resolve) => setTimeout(resolve, 0)) + await vi.waitFor(() => expect(capturedSignal).toBeDefined()) expect(capturedSignal).toBeDefined() expect(capturedSignal?.aborted).toBe(false) @@ -19229,7 +19432,9 @@ describe('DeepChatAgentHarness', () => { })) ) } - agent.deepChatRuntime.getHydrated(toAppSessionId('s1'))?.replacePendingInteractions([]) + await agent.deepChatRuntime + .getHydrated(toAppSessionId('s1')) + ?.replacePendingInteractions([]) toolResult.resolve({ content: 'done', rawData: { content: 'done', isError: false } diff --git a/test/main/agent/deepchat/helpers/control.ts b/test/main/agent/deepchat/helpers/control.ts new file mode 100644 index 0000000000..5e1fadb1d3 --- /dev/null +++ b/test/main/agent/deepchat/helpers/control.ts @@ -0,0 +1,133 @@ +import { builtinModules } from 'node:module' +import { tmpdir } from 'node:os' +import { setImmediate } from 'node:timers/promises' +import { afterAll, afterEach, beforeAll, vi } from 'vitest' +import { HarnessRuntime } from '@/harness/runtime' +import { CoreRuntimeConnection } from '@/harness/core' +import { CoreHarnessControl } from '../../../../../src/harness/core/control' +import type { HarnessControlPort } from '@shared/harness/control' + +export function useHarnessControl(): (() => HarnessControlPort) & { flush(): Promise } { + let directory: string | undefined + let harness: string | undefined + const owners: Array<{ flush(): Promise; close(): Promise }> = [] + beforeAll(async () => { + const fs = await vi.importActual('node:fs') + const path = await vi.importActual('node:path') + directory = fs.mkdtempSync(path.join(tmpdir(), 'deepchat-control-tests-')) + harness = path.join(directory, 'harness') + fs.cpSync(path.resolve('out/harness'), harness, { recursive: true }) + const { build } = await vi.importActual('vite') + await build({ + configFile: false, + publicDir: false, + logLevel: 'silent', + resolve: { + conditions: ['node'], + mainFields: ['module', 'main'], + alias: { '@shared': path.resolve('src/shared') } + }, + build: { + target: 'node24', + outDir: harness, + emptyOutDir: false, + copyPublicDir: false, + lib: { + entry: path.resolve('test/fixtures/harness/control-bridge.ts'), + formats: ['es'], + fileName: () => 'core.mjs' + }, + rollupOptions: { external: [...builtinModules, /^node:/] } + } + }) + }) + afterEach(async () => { + for (const owner of owners.splice(0)) await owner.close() + }) + afterAll(async () => { + if (directory) { + const fs = await vi.importActual('node:fs') + fs.rmSync(directory, { recursive: true, force: true }) + } + }) + const create = (): HarnessControlPort => { + if (!harness || !directory) throw new Error('Control test fixture has not started') + let runtime: HarnessRuntime | undefined + let connection: CoreRuntimeConnection | undefined + let startup: Promise | undefined + let requests = 0 + let pending = 0 + const start = () => + (startup ??= (async () => { + runtime = await HarnessRuntime.start({ + directory: harness!, + electronExecutable: process.execPath, + capacity: 1, + maxPending: 4, + observe() {} + }) + connection = await CoreRuntimeConnection.connect({ + runtime, + temporaryDirectory: directory!, + native: async () => { + throw new Error('Unexpected Native operation') + } + }) + })()) + const control = new CoreHarnessControl( + { + async control(_generation, sequence, command) { + requests++ + pending++ + try { + await start() + return await connection!.invokeRoute( + 'settings.getSnapshot', + { sequence, command }, + { caller: { kind: 'renderer', webContentsId: 1, windowId: null } }, + () => {} + ) + } finally { + pending-- + } + } + }, + 'test', + () => {} + ) + const flush = async () => { + if (control.closed || (!startup && pending === 0)) return + // Finish real RPC work before advancing the application's virtual clock. + for (;;) { + await control.send({ + method: 'clearSession', + sessionId: 'fixture-flush', + originScopeId: null + }) + const previous = requests + await setImmediate() + if (pending === 0 && requests === previous) return + } + } + owners.push({ + flush, + async close() { + await flush() + control.stop() + if (startup) { + try { + await startup + } catch {} + await runtime?.stop() + await connection?.closeAfterHostExit() + } + } + }) + return control + } + return Object.assign(create, { + async flush() { + for (const owner of owners) await owner.flush() + } + }) +} diff --git a/test/main/agent/deepchat/instance/deepChatAgentRuntime.test.ts b/test/main/agent/deepchat/instance/deepChatAgentRuntime.test.ts index af685166eb..ac9de6a983 100644 --- a/test/main/agent/deepchat/instance/deepChatAgentRuntime.test.ts +++ b/test/main/agent/deepchat/instance/deepChatAgentRuntime.test.ts @@ -7,6 +7,9 @@ import { toAppSessionId } from '@/agent/shared/agentSessionIds' import { TOOL_EXECUTION, type MCPToolDefinition } from '@shared/types/core/mcp' import { createLoopRun } from '@/agent/deepchat/loop/loopRun' import { POSIX_COMMAND_SHELL } from '../../../../helpers/commandShell' +import { useHarnessControl } from '../helpers/control' + +const control = useHarnessControl() const TOOL_DEFINITION: MCPToolDefinition = { execution: TOOL_EXECUTION.read.parallel, @@ -43,13 +46,42 @@ function createRun( } describe('DeepChatAgentRuntime', () => { - it('hydrates one stable instance per app session', () => { - const runtime = new DeepChatAgentRuntime() + it('aborts hydrated generations and deferred work without evicting or hydrating sessions', async () => { + const runtime = new DeepChatAgentRuntime(control()) + const sessionId = toAppSessionId('running') + const instance = runtime.getOrHydrate(sessionId) + await instance.controlReady() + const controller = await instance.ensureOperationController() + const run = createRun(sessionId, 'run', 'message', controller) + await instance.registerActiveGeneration(run) + await instance.replacePendingInteractions( + [{ messageId: 'message', toolCallId: 'tool', origin: 'pre-check-permission', order: 0 }], + { kind: 'run', id: run.runId } + ) + const lease = await instance.acquireInteraction('message', 'tool') + if (!lease || lease === 'parked') throw new Error('Expected interaction lease') + const deferred = await instance.registerDeferredToolAbortController('message', 'tool', lease, { + kind: 'run', + id: run.runId + }) + runtime.abortGenerations() + expect(controller.signal.aborted).toBe(true) + expect(deferred.signal.aborted).toBe(true) + expect(runtime.getHydrated(sessionId)).toBe(instance) + expect(instance.getActiveGeneration()).toBe(run) + expect(runtime.getHydrated(toAppSessionId('missing'))).toBeUndefined() + }) + + it('hydrates one stable instance per app session', async () => { + const runtime = new DeepChatAgentRuntime(control()) const sessionId = toAppSessionId('session') const first = runtime.getOrHydrate(sessionId) + await first.controlReady() const second = runtime.getOrHydrate(sessionId) + await second.controlReady() const other = runtime.getOrHydrate(toAppSessionId('other')) + await other.controlReady() expect(first).toBe(second) expect(other).not.toBe(first) @@ -57,13 +89,14 @@ describe('DeepChatAgentRuntime', () => { expect(other.sessionId).toBe(toAppSessionId('other')) }) - it('creates a minimal current scope over the registered instance state', () => { - const runtime = new DeepChatAgentRuntime() + it('creates a minimal current scope over the registered instance state', async () => { + const runtime = new DeepChatAgentRuntime(control()) const sessionId = toAppSessionId('session') expect(runtime.getHydratedScope(sessionId)).toBeUndefined() const scope = runtime.getOrHydrateScope(sessionId) + await scope.instance.controlReady() scope.instance.setRuntimeState({ status: 'idle', providerId: 'openai', @@ -81,11 +114,12 @@ describe('DeepChatAgentRuntime', () => { expect(runtime.scopeFor(sessionId, scope.instance)).toBe(scope) }) - it('fences evicted and mismatched scopes with the stable stale-instance identity', () => { - const runtime = new DeepChatAgentRuntime() + it('fences evicted and mismatched scopes with the stable stale-instance identity', async () => { + const runtime = new DeepChatAgentRuntime(control()) const sessionId = toAppSessionId('session') const otherSessionId = toAppSessionId('other') const instance = runtime.getOrHydrate(sessionId) + await instance.controlReady() const currentScope = runtime.scopeFor(sessionId, instance) const mismatchedScope = runtime.scopeFor(otherSessionId, instance) @@ -96,6 +130,7 @@ describe('DeepChatAgentRuntime', () => { runtime.evict(sessionId) const replacementScope = runtime.getOrHydrateScope(sessionId) + await replacementScope.instance.controlReady() expect(currentScope.isCurrent()).toBe(false) expect(replacementScope.isCurrent()).toBe(true) @@ -107,40 +142,45 @@ describe('DeepChatAgentRuntime', () => { } }) - it('exposes runtime state only, with no orchestration surface on the instance', () => { - const runtime = new DeepChatAgentRuntime() + it('exposes runtime state only, with no orchestration surface on the instance', async () => { + const runtime = new DeepChatAgentRuntime(control()) const instance = runtime.getOrHydrate(toAppSessionId('session')) + await instance.controlReady() for (const orchestration of ['send', 'cancel', 'snapshot', 'close']) { expect(instance).not.toHaveProperty(orchestration) } }) - it('replaces an evicted instance on the next hydration', () => { - const runtime = new DeepChatAgentRuntime() + it('replaces an evicted instance on the next hydration', async () => { + const runtime = new DeepChatAgentRuntime(control()) const sessionId = toAppSessionId('session') const instance = runtime.getOrHydrate(sessionId) + await instance.controlReady() expect(runtime.evict(sessionId)).toBe(true) expect(runtime.evict(sessionId)).toBe(false) expect(runtime.getOrHydrate(sessionId)).not.toBe(instance) }) - it('reads only already hydrated instances without creating a shell', () => { - const runtime = new DeepChatAgentRuntime() + it('reads only already hydrated instances without creating a shell', async () => { + const runtime = new DeepChatAgentRuntime(control()) const sessionId = toAppSessionId('session') expect(runtime.getHydrated(sessionId)).toBeUndefined() const instance = runtime.getOrHydrate(sessionId) + await instance.controlReady() expect(runtime.getHydrated(sessionId)).toBe(instance) runtime.evict(sessionId) expect(runtime.getHydrated(sessionId)).toBeUndefined() }) it('isolates identity, settings, status, project and readiness by session', async () => { - const runtime = new DeepChatAgentRuntime() + const runtime = new DeepChatAgentRuntime(control()) const first = runtime.getOrHydrate(toAppSessionId('first')) + await first.controlReady() const second = runtime.getOrHydrate(toAppSessionId('second')) + await second.controlReady() first.setAgentId('agent-a') first.setProjectDir('/workspace/a') @@ -158,6 +198,7 @@ describe('DeepChatAgentRuntime', () => { permissionMode: 'default' }) + await first.transitionControlStatus({ kind: 'orphan' }, 'generating') expect(first.getAgentId()).toBe('agent-a') expect(first.hasProjectDir()).toBe(true) expect(first.getProjectDir()).toBe('/workspace/a') @@ -171,14 +212,17 @@ describe('DeepChatAgentRuntime', () => { const canceledWait = first.waitForFirstTurnReady() first.clearFirstTurnReady() await expect(canceledWait).resolves.toBe(false) - first.markFirstTurnReady() + const controller = await first.ensureOperationController() + await first.registerActiveGeneration(createRun('first', 'ready-run', 'message', controller)) + await first.markFirstTurnReady('ready-run') await expect(first.waitForFirstTurnReady()).resolves.toBe(true) }) - it('keeps provider context observations runtime-local and model-scoped', () => { - const runtime = new DeepChatAgentRuntime() + it('keeps provider context observations runtime-local and model-scoped', async () => { + const runtime = new DeepChatAgentRuntime(control()) const sessionId = toAppSessionId('session') const instance = runtime.getOrHydrate(sessionId) + await instance.controlReady() instance.recordContextWindowObservation({ providerId: 'new-api', @@ -237,205 +281,36 @@ describe('DeepChatAgentRuntime', () => { expect(instance.getContextWindowObservation('openai', 'other-model')).toBeUndefined() }) - it('reuses an owned preparation controller for the active generation', () => { - const runtime = new DeepChatAgentRuntime() - const instance = runtime.getOrHydrate(toAppSessionId('session')) - const controller = new AbortController() - - instance.setAbortController(controller) - expect(instance.getAbortSignal()).toBe(controller.signal) - - const run = createRun('session', 'run-1', 'message-1', controller) - const generation = instance.registerActiveGeneration(run) - expect(generation).toBe(run) - expect(controller.signal.aborted).toBe(false) - expect(instance.getActiveGeneration()).toBe(generation) - expect(instance.clearActiveGeneration('stale-run')).toBe(false) - expect(instance.isActiveRun('run-1')).toBe(true) - expect(instance.clearActiveGeneration('run-1')).toBe(true) - expect(instance.getActiveGeneration()).toBeUndefined() - expect(instance.getAbortController()).toBeUndefined() - }) - - it('aborts the previous active generation before replacing it', () => { - const runtime = new DeepChatAgentRuntime() - const instance = runtime.getOrHydrate(toAppSessionId('session')) - const firstController = new AbortController() - const replacementController = new AbortController() - const replacement = createRun('session', 'run-2', 'message-2', replacementController) - - instance.registerActiveGeneration(createRun('session', 'run-1', 'message-1', firstController)) - instance.registerActiveGeneration(replacement) - - expect(firstController.signal.aborted).toBe(true) - expect(replacementController.signal.aborted).toBe(false) - expect(instance.getActiveGeneration()).toBe(replacement) - expect(instance.getAbortController()).toBe(replacementController) - }) - - it('aborts an owned preparation controller when a different active run replaces it', () => { - const runtime = new DeepChatAgentRuntime() - const instance = runtime.getOrHydrate(toAppSessionId('session')) - const preparationController = new AbortController() - const activeController = new AbortController() - const run = createRun('session', 'run-1', 'message-1', activeController) - - instance.setAbortController(preparationController) - instance.registerActiveGeneration(run) - - expect(preparationController.signal.aborted).toBe(true) - expect(activeController.signal.aborted).toBe(false) - expect(instance.getActiveGeneration()).toBe(run) - expect(instance.getAbortController()).toBe(activeController) - }) - - it('aborts pre-stream work immediately but retains an active run until settlement', () => { - const runtime = new DeepChatAgentRuntime() + it('aborts pre-stream work immediately but retains an active run until settlement', async () => { + const runtime = new DeepChatAgentRuntime(control()) const first = runtime.getOrHydrate(toAppSessionId('first')) + await first.controlReady() const second = runtime.getOrHydrate(toAppSessionId('second')) - const preStreamController = new AbortController() + await second.controlReady() + const preStreamController = await first.ensureOperationController() const activeController = new AbortController() - first.setAbortController(preStreamController) first.requestGenerationAbort() expect(preStreamController.signal.aborted).toBe(true) - expect(first.getAbortController()).toBeUndefined() + await vi.waitFor(() => expect(first.getAbortController()).toBeUndefined()) - first.registerActiveGeneration(createRun('first', 'run-1', 'message-1', activeController)) + await first.registerActiveGeneration(createRun('first', 'run-1', 'message-1', activeController)) first.requestGenerationAbort() expect(activeController.signal.aborted).toBe(true) expect(first.getActiveGeneration()?.runId).toBe('run-1') expect(second.getAbortSignal()).toBeUndefined() first.abortAndClearGeneration() - expect(first.getActiveGeneration()).toBeUndefined() + await vi.waitFor(() => expect(first.getActiveGeneration()).toBeUndefined()) expect(first.getAbortController()).toBeUndefined() }) - it('isolates pending drain and steer merge state by instance', () => { - const runtime = new DeepChatAgentRuntime() - const first = runtime.getOrHydrate(toAppSessionId('first')) - const second = runtime.getOrHydrate(toAppSessionId('second')) - - first.setActiveSteerPendingInputId('steer-1') - const drainLease = first.tryAcquirePendingQueueDrain() - - expect(first.getActiveSteerPendingInputId()).toBe('steer-1') - expect(drainLease).not.toBeNull() - expect(first.tryAcquirePendingQueueDrain()).toBeNull() - expect(first.isPendingQueueDraining()).toBe(true) - expect(second.getActiveSteerPendingInputId()).toBeUndefined() - expect(second.isPendingQueueDraining()).toBe(false) - expect(first.clearActiveSteerPendingInputId('stale-steer')).toBe(false) - expect(first.clearActiveSteerPendingInputId('steer-1')).toBe(true) - expect(first.releasePendingQueueDrain(Symbol('not-owner'))).toBe(false) - expect(first.isPendingQueueDraining()).toBe(true) - - first.clearOwnedState() - expect(first.isPendingQueueDraining()).toBe(false) - }) - - it('owns ordered interactions and per-session response guards', () => { - const runtime = new DeepChatAgentRuntime() - const first = runtime.getOrHydrate(toAppSessionId('first')) - const second = runtime.getOrHydrate(toAppSessionId('second')) - - first.replacePendingToolBatch( - [ - { - messageId: 'message', - toolCallId: 'tool-1', - origin: 'pre-check-permission', - order: 0 - }, - { - messageId: 'message', - toolCallId: 'tool-2', - origin: 'pre-check-permission', - order: 1 - } - ], - { - callOrder: ['tool-1', 'tool-2'], - invokedCallIds: [], - committedResultCallIds: [], - pendingInteractionCallIds: ['tool-1', 'tool-2'] - } - ) - - expect(first.getFirstPendingInteraction()).toEqual({ - messageId: 'message', - toolCallId: 'tool-1', - origin: 'pre-check-permission', - order: 0 - }) - expect(first.hasPendingInteractions()).toBe(true) - expect(first.getPendingToolBatchState()).toEqual({ - callOrder: ['tool-1', 'tool-2'], - invokedCallIds: [], - committedResultCallIds: [], - pendingInteractionCallIds: ['tool-1', 'tool-2'] - }) - expect( - first.transitionPendingInteractionOrigin('message', 'tool-1', 'post-call-permission') - ).toBe(true) - expect(first.getFirstPendingInteraction()?.origin).toBe('post-call-permission') - expect(second.hasPendingInteractions()).toBe(false) - expect(first.tryLockInteraction('message', 'tool-1')).toBe(true) - expect(first.tryLockInteraction('message', 'tool-1')).toBe(false) - expect(second.tryLockInteraction('message', 'tool-1')).toBe(true) - expect(first.tryBeginResume('message')).toBe(true) - expect(first.tryBeginResume('message')).toBe(false) - - first.unlockInteraction('message', 'tool-1') - first.finishResume('message') - expect(first.tryLockInteraction('message', 'tool-1')).toBe(true) - expect(first.tryBeginResume('message')).toBe(true) - - first.replacePendingInteractions([ - { messageId: 'message', toolCallId: 'acp-tool', origin: 'acp-permission', order: 0 } - ]) - expect(first.hasPendingInteractions()).toBe(true) - expect(first.getPendingToolBatchState()).toBeUndefined() - }) - - it('owns deferred tool cancellation and live provider permissions', () => { - const runtime = new DeepChatAgentRuntime() - const first = runtime.getOrHydrate(toAppSessionId('first')) - const second = runtime.getOrHydrate(toAppSessionId('second')) - const staleController = first.registerDeferredToolAbortController('tool') - const currentController = first.registerDeferredToolAbortController('tool') - const resolve = vi.fn().mockResolvedValue(undefined) - - first.registerActiveProviderPermission({ - requestId: 'request', - messageId: 'message', - toolCallId: 'tool', - providerId: 'acp', - permissionType: 'command', - resolve - }) - - expect(staleController.signal.aborted).toBe(true) - expect(currentController.signal.aborted).toBe(false) - expect(first.clearDeferredToolAbortController('tool', staleController)).toBe(false) - expect(first.hasDeferredToolAbortController('tool')).toBe(true) - expect(first.hasActiveProviderPermission('request')).toBe(true) - expect(second.hasActiveProviderPermission('request')).toBe(false) - expect(first.takeActiveProviderPermissions()).toEqual([ - expect.objectContaining({ requestId: 'request', resolve }) - ]) - - first.abortDeferredToolCalls() - expect(currentController.signal.aborted).toBe(true) - expect(first.hasDeferredToolAbortController('tool')).toBe(false) - expect(first.hasActiveProviderPermission('request')).toBe(false) - }) - - it('owns isolated runtime skill selections and tool caches', () => { - const runtime = new DeepChatAgentRuntime() + it('owns isolated runtime skill selections and tool caches', async () => { + const runtime = new DeepChatAgentRuntime(control()) const first = runtime.getOrHydrate(toAppSessionId('first')) + await first.controlReady() const second = runtime.getOrHydrate(toAppSessionId('second')) + await second.controlReady() first.replaceRuntimeActivatedSkills(['skill-b', 'skill-a', 'skill-a', ' ']) first.setToolProfileCache({ @@ -453,11 +328,13 @@ describe('DeepChatAgentRuntime', () => { expect(first.getToolProfileCache()).toBeUndefined() }) - it('owns isolated compaction projections and stable legacy memory handles', () => { - const runtime = new DeepChatAgentRuntime() + it('owns isolated compaction projections and stable legacy memory handles', async () => { + const runtime = new DeepChatAgentRuntime(control()) const sessionId = toAppSessionId('first') const first = runtime.getOrHydrate(sessionId) + await first.controlReady() const second = runtime.getOrHydrate(toAppSessionId('second')) + await second.controlReady() first.setCompactionState({ status: 'compacting', @@ -478,6 +355,7 @@ describe('DeepChatAgentRuntime', () => { runtime.evict(sessionId) const replacement = runtime.getOrHydrate(sessionId) + await replacement.controlReady() expect(replacement.getMemorySessionHandle()).not.toBe(first.getMemorySessionHandle()) expect(replacement.getMemorySessionHandle().sessionId).toBe(sessionId) @@ -485,11 +363,13 @@ describe('DeepChatAgentRuntime', () => { expect(first.getCompactionState()).toBeUndefined() }) - it('invalidates tool revisions and clears only the owning instance lifecycle', () => { - const runtime = new DeepChatAgentRuntime() + it('invalidates tool revisions and clears only the owning instance lifecycle', async () => { + const runtime = new DeepChatAgentRuntime(control()) const sessionId = toAppSessionId('session') const staleInstance = runtime.getOrHydrate(sessionId) + await staleInstance.controlReady() const other = runtime.getOrHydrate(toAppSessionId('other')) + await other.controlReady() for (const instance of [staleInstance, other]) { instance.setToolProfileCache({ @@ -507,27 +387,11 @@ describe('DeepChatAgentRuntime', () => { runtime.evict(sessionId) const currentInstance = runtime.getOrHydrate(sessionId) + await currentInstance.controlReady() currentInstance.replaceRuntimeActivatedSkills(['current-skill']) staleInstance.clearOwnedState() expect(currentInstance.getRuntimeActivatedSkills()).toEqual(['current-skill']) expect(staleInstance.getRuntimeActivatedSkills()).toEqual([]) }) - - it('does not let a stale drain completion clear a rehydrated instance', () => { - const runtime = new DeepChatAgentRuntime() - const sessionId = toAppSessionId('session') - const staleInstance = runtime.getOrHydrate(sessionId) - const staleLease = staleInstance.tryAcquirePendingQueueDrain() - if (!staleLease) throw new Error('Expected stale instance to acquire the drain') - - runtime.evict(sessionId) - const currentInstance = runtime.getOrHydrate(sessionId) - const currentLease = currentInstance.tryAcquirePendingQueueDrain() - if (!currentLease) throw new Error('Expected current instance to acquire the drain') - staleInstance.releasePendingQueueDrain(staleLease) - - expect(currentInstance.isPendingQueueDraining()).toBe(true) - expect(currentInstance.releasePendingQueueDrain(currentLease)).toBe(true) - }) }) diff --git a/test/main/agent/deepchat/memory/memoryRuntimeCoordinator.test.ts b/test/main/agent/deepchat/memory/memoryRuntimeCoordinator.test.ts index bc0c7812b4..7ad39b9f9c 100644 --- a/test/main/agent/deepchat/memory/memoryRuntimeCoordinator.test.ts +++ b/test/main/agent/deepchat/memory/memoryRuntimeCoordinator.test.ts @@ -1,5 +1,8 @@ +import { useHarnessControl } from '../helpers/control' import { afterEach, describe, expect, it, vi } from 'vitest' +const control = useHarnessControl() + import { MEMORY_INJECTION_TIMEOUT_MS, MemoryRuntimeCoordinator @@ -84,7 +87,7 @@ function createHarness() { let cursor = 0 let rows = [createRecord('u1', 1, 'Remember Redis.')] let tapeRows = rows.map(toTapeRow) - const registry = new DeepChatAgentRuntime() + const registry = new DeepChatAgentRuntime(control()) const instance = registry.getOrHydrate(toAppSessionId('s1')) instance.setRuntimeState({ status: 'idle', @@ -97,6 +100,7 @@ function createHarness() { const port = { isEnabled, captureExecutionToken: vi.fn((agentId: string) => ({ + ownerId: 'memory-test-owner', agentId, generation: executionGenerations.get(agentId) ?? 0 })), diff --git a/test/main/agent/deepchat/runtime/compactionRuntimeCoordinator.test.ts b/test/main/agent/deepchat/runtime/compactionRuntimeCoordinator.test.ts index 9bfeb8a932..0565d2a293 100644 --- a/test/main/agent/deepchat/runtime/compactionRuntimeCoordinator.test.ts +++ b/test/main/agent/deepchat/runtime/compactionRuntimeCoordinator.test.ts @@ -1,3 +1,4 @@ +import { useHarnessControl } from '../helpers/control' import type { ProviderModelResolutionPort } from '@/provider/settings' import { DeepChatAgentRuntime } from '@/agent/deepchat/instance/deepChatAgentRuntime' import type { DeepChatAgentInstance } from '@/agent/deepchat/instance/deepChatAgentInstance' @@ -15,6 +16,8 @@ import { ModelType } from '@shared/model' import { afterEach, describe, expect, it, vi } from 'vitest' import { POSIX_COMMAND_SHELL } from '../../../../helpers/commandShell' +const control = useHarnessControl() + const SESSION_ID = 'session' function createRuntimeState( @@ -119,16 +122,17 @@ type PrepareManualCompactionInput = Parameters< CompactionRuntimeCoordinatorDependencies['compactionService']['prepareForManualCompaction'] >[0] -function createHarness(options?: { +async function createHarness(options?: { hydrate?: boolean sessionExists?: boolean state?: DeepChatSessionState }) { - const runtime = new DeepChatAgentRuntime() + const runtime = new DeepChatAgentRuntime(control()) const shouldHydrate = options?.hydrate !== false const initialInstance = shouldHydrate ? runtime.getOrHydrate(toAppSessionId(SESSION_ID)) : undefined + if (initialInstance) await initialInstance.controlReady() if (initialInstance && options?.state !== undefined) { initialInstance.setRuntimeState(options.state) } else if (initialInstance) { @@ -191,31 +195,18 @@ function createHarness(options?: { const scopeFor = vi.fn((sessionId: string, instance: DeepChatAgentInstance) => runtime.scopeFor(toAppSessionId(sessionId), instance) ) - const transitionStatus = vi.fn( - ( - scope: ReturnType, - status: DeepChatSessionState['status'] - ): boolean => { - if (!scope.isCurrent()) return false - const state = scope.state() - if (!state) return false - state.status = status - return true - } - ) - const ensureOperationController = vi.fn((scope: ReturnType) => { + const transitionStatus = vi.fn(async (scope: ReturnType, status: DeepChatSessionState['status'], owner: Parameters[0]) => Boolean(await scope.instance.transitionControlStatus(owner, status))) + const ensureOperationController = vi.fn(async (scope: ReturnType) => { scope.assertCurrent() - const controller = new AbortController() - scope.instance.setAbortController(controller) - return controller + return await scope.instance.ensureOperationController() }) const canSettleOperation = vi.fn( (scope: ReturnType, controller: AbortController) => scope.isCurrent() && scope.instance.getAbortController() === controller ) const clearOperationController = vi.fn( - (scope: ReturnType, controller?: AbortController) => - scope.isCurrent() && scope.instance.clearAbortController(controller) + async (scope: ReturnType, controller?: AbortController) => + await scope.instance.clearAbortController(controller) ) const runLifecycle: CompactionRuntimeCoordinatorDependencies['runLifecycle'] = { canSettleOperation, @@ -345,7 +336,7 @@ describe('CompactionRuntimeCoordinator', () => { }) it('does not hydrate an instance when neither runtime nor persisted session facts exist', async () => { - const { coordinator, getInstance, runtime } = createHarness({ hydrate: false }) + const { coordinator, getInstance, runtime } = await createHarness({ hydrate: false }) await expect(coordinator.getSnapshot(SESSION_ID)).rejects.toThrow( `Session ${SESSION_ID} not found` @@ -356,7 +347,7 @@ describe('CompactionRuntimeCoordinator', () => { }) it('keeps an in-flight state authoritative and otherwise refreshes from persisted summary', async () => { - const { coordinator, initialInstance, setSummaryState } = createHarness() + const { coordinator, initialInstance, setSummaryState } = await createHarness() setSummaryState({ summaryText: 'Persisted summary', summaryCursorOrderSeq: 7, @@ -393,7 +384,7 @@ describe('CompactionRuntimeCoordinator', () => { }) it('projects a cursor-only reconstruction boundary as compacted', async () => { - const { coordinator, setSummaryState } = createHarness() + const { coordinator, setSummaryState } = await createHarness() setSummaryState({ summaryText: null, summaryCursorOrderSeq: 7, @@ -411,7 +402,7 @@ describe('CompactionRuntimeCoordinator', () => { }) it('derives boundary-only reason and anchor identity from the latest Tape anchor', async () => { - const { coordinator, setReconstructionAnchor, setSummaryState } = createHarness() + const { coordinator, setReconstructionAnchor, setSummaryState } = await createHarness() setSummaryState({ summaryText: null, summaryCursorOrderSeq: 7, @@ -451,7 +442,7 @@ describe('CompactionRuntimeCoordinator', () => { }) it('uses a per-session sequence across same-millisecond events and runtime replacement', async () => { - const { coordinator, publishedEvents, runtime } = createHarness() + const { coordinator, publishedEvents, runtime } = await createHarness() vi.spyOn(Date, 'now').mockReturnValue(100) coordinator.emit(SESSION_ID, coordinator.idleState()) @@ -464,6 +455,7 @@ describe('CompactionRuntimeCoordinator', () => { runtime.evict(toAppSessionId(SESSION_ID)) const replacement = runtime.getOrHydrate(toAppSessionId(SESSION_ID)) + await replacement.controlReady() replacement.setRuntimeState(createRuntimeState()) coordinator.emit(SESSION_ID, coordinator.idleState(), replacement) @@ -473,8 +465,8 @@ describe('CompactionRuntimeCoordinator', () => { await expect(coordinator.getSnapshot(SESSION_ID)).resolves.toMatchObject({ emitSeq: 3 }) }) - it('takes event boundary reasons only from the durable Tape anchor', () => { - const { coordinator, publishedEvents, setReconstructionAnchor } = createHarness() + it('takes event boundary reasons only from the durable Tape anchor', async () => { + const { coordinator, publishedEvents, setReconstructionAnchor } = await createHarness() setReconstructionAnchor({ entryId: 42, name: 'compaction/context_pressure', @@ -501,7 +493,7 @@ describe('CompactionRuntimeCoordinator', () => { }) it('restarts process-local sequencing while preserving the durable anchor identity', async () => { - const { coordinator, deps, setReconstructionAnchor, setSummaryState } = createHarness() + const { coordinator, deps, setReconstructionAnchor, setSummaryState } = await createHarness() setSummaryState({ summaryText: null, summaryCursorOrderSeq: 7, @@ -545,7 +537,7 @@ describe('CompactionRuntimeCoordinator', () => { toolResolver, transitionStatus, providerSettings - } = createHarness() + } = await createHarness() await expect(coordinator.compact(SESSION_ID)).resolves.toEqual({ compacted: false, @@ -604,7 +596,7 @@ describe('CompactionRuntimeCoordinator', () => { messageStore, prepareForManualCompaction, publishedEvents - } = createHarness() + } = await createHarness() const intent = createIntent() prepareForManualCompaction.mockResolvedValueOnce(intent) @@ -642,7 +634,7 @@ describe('CompactionRuntimeCoordinator', () => { }) it('finishes compaction projection after a post-commit hook dispatch rejects', async () => { - const f = createHarness() + const f = await createHarness() f.prepareForManualCompaction.mockResolvedValueOnce(createIntent()) f.deps.pluginContext = { hasHooks: () => true, @@ -665,7 +657,7 @@ describe('CompactionRuntimeCoordinator', () => { }) it('persists each observed summary call against the compaction marker identity', async () => { - const { applyCompaction, coordinator, messageStore } = createHarness() + const { applyCompaction, coordinator, messageStore } = await createHarness() const intent = createIntent() applyCompaction.mockImplementationOnce(async (_intent, _signal, observeModelCall) => { observeModelCall?.({ @@ -703,7 +695,7 @@ describe('CompactionRuntimeCoordinator', () => { }) it('removes the local marker when another compaction attempt wins the anchor CAS', async () => { - const { applyCompaction, coordinator, messageStore } = createHarness() + const { applyCompaction, coordinator, messageStore } = await createHarness() const intent = createIntent() applyCompaction.mockResolvedValueOnce({ outcome: 'summarized', @@ -725,7 +717,7 @@ describe('CompactionRuntimeCoordinator', () => { it('finalizes a committed marker without mutating a replacement runtime', async () => { const { applyCompaction, coordinator, initialInstance, messageStore, publishedEvents, runtime } = - createHarness() + await createHarness() const completion = createDeferred<{ outcome: 'summarized' anchorCommitted: true @@ -741,6 +733,7 @@ describe('CompactionRuntimeCoordinator', () => { await vi.waitFor(() => expect(applyCompaction).toHaveBeenCalledOnce()) runtime.evict(toAppSessionId(SESSION_ID)) const replacement = runtime.getOrHydrate(toAppSessionId(SESSION_ID)) + await replacement.controlReady() replacement.setRuntimeState(createRuntimeState()) completion.resolve({ outcome: 'summarized', @@ -776,7 +769,7 @@ describe('CompactionRuntimeCoordinator', () => { messageStore, setReconstructionAnchor, setSummaryState - } = createHarness() + } = await createHarness() const intent = createIntent() const boundaryState = { summaryText: null, @@ -833,7 +826,7 @@ describe('CompactionRuntimeCoordinator', () => { messageStore, publishedEvents, runtime - } = createHarness() + } = await createHarness() const completion = createDeferred() const failure = new Error('provider failed') applyCompaction.mockImplementationOnce(async () => await completion.promise) @@ -842,6 +835,7 @@ describe('CompactionRuntimeCoordinator', () => { await vi.waitFor(() => expect(applyCompaction).toHaveBeenCalledOnce()) runtime.evict(toAppSessionId(SESSION_ID)) const replacement = runtime.getOrHydrate(toAppSessionId(SESSION_ID)) + await replacement.controlReady() replacement.setRuntimeState(createRuntimeState()) completion.reject(failure) @@ -861,21 +855,19 @@ describe('CompactionRuntimeCoordinator', () => { it('does not settle idle after the operation controller loses ownership', async () => { const { - canSettleOperation, coordinator, initialInstance, prepareForManualCompaction, transitionStatus - } = createHarness() + } = await createHarness() prepareForManualCompaction.mockImplementationOnce(async () => { - initialInstance?.setAbortController(new AbortController()) + await initialInstance?.ensureOperationController() return null }) - canSettleOperation.mockReturnValueOnce(false) - await coordinator.compact(SESSION_ID) + await expect(coordinator.compact(SESSION_ID)).rejects.toMatchObject({ name: 'AbortError' }) - expect(transitionStatus.mock.calls.map(([, status]) => status)).toEqual(['generating']) + expect(transitionStatus.mock.calls.map(([, status]) => status)).toEqual(['generating', 'idle']) expect(initialInstance?.getRuntimeState()?.status).toBe('generating') }) @@ -886,7 +878,7 @@ describe('CompactionRuntimeCoordinator', () => { initialInstance, messageStore, publishedEvents - } = createHarness() + } = await createHarness() const controller = new AbortController() controller.abort() applyCompaction.mockRejectedValueOnce(new Error('late failure')) @@ -912,7 +904,7 @@ describe('CompactionRuntimeCoordinator', () => { }) it('restores the previous projection without masking a non-abort failure', async () => { - const { applyCompaction, coordinator, initialInstance, messageStore } = createHarness() + const { applyCompaction, coordinator, initialInstance, messageStore } = await createHarness() const failure = new Error('compaction failed') applyCompaction.mockRejectedValueOnce(failure) @@ -936,7 +928,7 @@ describe('CompactionRuntimeCoordinator', () => { }) it('retains a summary error while publishing the committed fallback boundary', async () => { - const { applyCompaction, coordinator, messageStore, initialInstance } = createHarness() + const { applyCompaction, coordinator, messageStore, initialInstance } = await createHarness() const summaryState = { summaryText: null, summaryCursorOrderSeq: 5, summaryUpdatedAt: null } applyCompaction.mockResolvedValueOnce({ outcome: 'boundary_only', diff --git a/test/main/agent/deepchat/runtime/contextOccupancyCoordinator.test.ts b/test/main/agent/deepchat/runtime/contextOccupancyCoordinator.test.ts index 8a1cd3adb1..ac606da1b4 100644 --- a/test/main/agent/deepchat/runtime/contextOccupancyCoordinator.test.ts +++ b/test/main/agent/deepchat/runtime/contextOccupancyCoordinator.test.ts @@ -99,6 +99,7 @@ function setup( } = {} ) { const instance = { + controlReady: async () => {}, getContextWindowObservation: vi.fn(() => { const providerContextLimitTokens = overrides.providerContextLimitTokens const providerPromptLimitTokens = overrides.providerPromptLimitTokens diff --git a/test/main/agent/deepchat/runtime/deferredToolExecutor.test.ts b/test/main/agent/deepchat/runtime/deferredToolExecutor.test.ts index ef785d9213..3e26969dc1 100644 --- a/test/main/agent/deepchat/runtime/deferredToolExecutor.test.ts +++ b/test/main/agent/deepchat/runtime/deferredToolExecutor.test.ts @@ -26,6 +26,11 @@ import { const SESSION_ID = 'session-1' const MESSAGE_ID = 'message-1' +const EXECUTION_CONTEXT = { + scope: { sessionId: SESSION_ID }, + lease: { id: 'interaction', target: { kind: 'interaction', messageId: MESSAGE_ID, toolCallId: 'call-1' } }, + owner: { kind: 'operation', id: 'operation' } +} as unknown as Parameters[0] const TOOL_CALL = { id: 'call-1', name: 'write_file', @@ -168,9 +173,8 @@ function createHarness( }, cacheImage: vi.fn(async (data: string) => data), runLifecycle: { - registerDeferredToolController: vi.fn(() => abortController), - clearDeferredToolController: vi.fn(), - getAbortSignal: vi.fn(() => undefined) + registerDeferredToolController: vi.fn(async () => abortController), + clearDeferredToolController: vi.fn(async () => true) }, sessionSettings: { resolveProjectDir: vi.fn(() => '/workspace') }, sessionState: { @@ -208,7 +212,7 @@ function createHarness( dependencies, execute: (onToolCallStarted?: () => void) => executor.execute( - SESSION_ID, + EXECUTION_CONTEXT, MESSAGE_ID, toolCall, onToolCallStarted, @@ -457,7 +461,7 @@ describe('DeferredToolExecutor Execution Journal', () => { const executionContract = buildContract() await executor.execute( - SESSION_ID, + EXECUTION_CONTEXT, MESSAGE_ID, TOOL_CALL, undefined, @@ -501,7 +505,7 @@ describe('DeferredToolExecutor Execution Journal', () => { try { await expect( harness.executor.execute( - SESSION_ID, + EXECUTION_CONTEXT, MESSAGE_ID, toolCall, undefined, @@ -547,7 +551,7 @@ describe('DeferredToolExecutor Execution Journal', () => { const { dependencies, executor } = createHarness() await executor.execute( - SESSION_ID, + EXECUTION_CONTEXT, MESSAGE_ID, TOOL_CALL, undefined, @@ -572,7 +576,7 @@ describe('DeferredToolExecutor Execution Journal', () => { const permissionLease = { kind: 'file' as const, leaseId: 'file-lease-1' } await executor.execute( - SESSION_ID, + EXECUTION_CONTEXT, MESSAGE_ID, TOOL_CALL, undefined, @@ -595,7 +599,7 @@ describe('DeferredToolExecutor Execution Journal', () => { await expect( executor.execute( - SESSION_ID, + EXECUTION_CONTEXT, MESSAGE_ID, TOOL_CALL, undefined, @@ -612,7 +616,7 @@ describe('DeferredToolExecutor Execution Journal', () => { it('fails closed when deferred Agent filesystem execution lacks a stored shell profile', async () => { const { dependencies, executionJournal, executor } = createHarness() - await expect(executor.execute(SESSION_ID, MESSAGE_ID, TOOL_CALL)).resolves.toMatchObject({ + await expect(executor.execute(EXECUTION_CONTEXT, MESSAGE_ID, TOOL_CALL)).resolves.toMatchObject({ responseText: 'Deferred file execution is missing its shell profile.', isError: true, invoked: false @@ -633,7 +637,7 @@ describe('DeferredToolExecutor Execution Journal', () => { const { server_name: _serverName, ...unboundToolCall } = TOOL_CALL await expect( - executor.execute(SESSION_ID, MESSAGE_ID, unboundToolCall, undefined, undefined, 'posix') + executor.execute(EXECUTION_CONTEXT, MESSAGE_ID, unboundToolCall, undefined, undefined, 'posix') ).resolves.toMatchObject({ responseText: 'Deferred tool execution is missing its server identity.', isError: true, @@ -659,7 +663,7 @@ describe('DeferredToolExecutor Execution Journal', () => { } ] as never) - await executor.execute(SESSION_ID, MESSAGE_ID, { + await executor.execute(EXECUTION_CONTEXT, MESSAGE_ID, { ...TOOL_CALL, name: 'echo', server_name: 'mcp-server' @@ -675,7 +679,7 @@ describe('DeferredToolExecutor Execution Journal', () => { await expect( executor.execute( - SESSION_ID, + EXECUTION_CONTEXT, MESSAGE_ID, { ...TOOL_CALL, name: 'echo', server_name: 'mcp-server' }, undefined, diff --git a/test/main/agent/deepchat/runtime/messageProjectionService.test.ts b/test/main/agent/deepchat/runtime/messageProjectionService.test.ts index 5d681c4abd..a1ea10af3b 100644 --- a/test/main/agent/deepchat/runtime/messageProjectionService.test.ts +++ b/test/main/agent/deepchat/runtime/messageProjectionService.test.ts @@ -1,3 +1,4 @@ +import { useHarnessControl } from '../helpers/control' import { describe, expect, it, vi } from 'vitest' import { DeepChatAgentRuntime } from '@/agent/deepchat/instance/deepChatAgentRuntime' import { createLoopRun } from '@/agent/deepchat/loop/loopRun' @@ -8,6 +9,8 @@ import { } from '@/agent/deepchat/runtime/messageProjectionService' import { POSIX_COMMAND_SHELL } from '../../../../helpers/commandShell' +const control = useHarnessControl() + const SESSION_ID = 'session' const MESSAGE_ID = 'message' @@ -17,7 +20,7 @@ function assistantMessage(content: string) { function createHarness(message: unknown = assistantMessage('[]')) { const order: string[] = [] - const runtime = new DeepChatAgentRuntime() + const runtime = new DeepChatAgentRuntime(control()) const getMessage = vi.fn(() => { order.push('transcript.getMessage') return message as never @@ -62,10 +65,11 @@ describe('MessageProjectionService', () => { warn.mockRestore() }) - it('uses the active run id only for the message that run owns', () => { + it('uses the active run id only for the message that run owns', async () => { const { deps, runtime, service } = createHarness() const scope = runtime.getOrHydrateScope(toAppSessionId(SESSION_ID)) - scope.instance.registerActiveGeneration( + await scope.instance.controlReady() + await scope.instance.registerActiveGeneration( createLoopRun({ runId: 'run-1', sessionId: toAppSessionId(SESSION_ID), diff --git a/test/main/agent/deepchat/runtime/pendingInputAdmissionCoordinator.test.ts b/test/main/agent/deepchat/runtime/pendingInputAdmissionCoordinator.test.ts deleted file mode 100644 index 05b4b2628f..0000000000 --- a/test/main/agent/deepchat/runtime/pendingInputAdmissionCoordinator.test.ts +++ /dev/null @@ -1,682 +0,0 @@ -import type { - ChatMessageRecord, - DeepChatSessionState, - PendingSessionInputRecord, - SendMessageInput -} from '@shared/types/agent-interface' -import logger from '@shared/logger' -import { describe, expect, it, vi } from 'vitest' -import { - PendingInputAdmissionCoordinator, - type PendingInputAdmissionCoordinatorPorts, - type PendingInputAdmissionPumpPort, - type PendingInputAdmissionStorePort -} from '@/agent/deepchat/runtime/pendingInputAdmissionCoordinator' -import type { - ClaimedInputDisposition, - ClaimedPendingInputHandle -} from '@/agent/deepchat/runtime/pendingInputContracts' - -const SESSION_ID = 'session' - -function createInput( - id = 'input', - mode: PendingSessionInputRecord['mode'] = 'queue' -): PendingSessionInputRecord { - return { - id, - sessionId: SESSION_ID, - mode, - state: 'pending', - payload: { text: 'Promote me', files: [] }, - messageIds: [], - assistantMessageId: null, - blocking: null, - queueOrder: mode === 'queue' ? 1 : null, - claimedAt: null, - consumedAt: null, - createdAt: 1, - updatedAt: 1 - } -} - -function createScopeRegistry( - instance: unknown -): PendingInputAdmissionCoordinatorPorts['registry'] { - return { - getOrHydrateScope: vi.fn(), - getHydratedScope: vi.fn((sessionId: string) => ({ sessionId, instance })), - scopeFor: vi.fn() - } as unknown as PendingInputAdmissionCoordinatorPorts['registry'] -} - -function createHarness(onDrain?: (harness: HarnessState) => void) { - const state: DeepChatSessionState = { - status: 'idle', - providerId: 'openai', - modelId: 'gpt-5', - permissionMode: 'full_access' - } - const harness: HarnessState = { - activeSteerInputId: undefined, - draining: false, - input: createInput(), - preStreamController: undefined - } - const replaceInput = ( - patch: Partial - ): PendingSessionInputRecord => { - if (!harness.input) throw new Error('Pending input not found') - harness.input = { - ...harness.input, - ...patch, - updatedAt: harness.input.updatedAt + 1 - } - return harness.input - } - const createUserMessage = (id: string, content: SendMessageInput): ChatMessageRecord => ({ - id, - sessionId: SESSION_ID, - orderSeq: 2, - role: 'user', - content: JSON.stringify(content), - status: 'pending', - isContextEdge: 0, - metadata: JSON.stringify({ - inputReceipt: { - mode: 'steer', - readAt: null - } - }), - createdAt: 1, - updatedAt: 1 - }) - - let disposition: ClaimedInputDisposition | null = null - const claim: ClaimedPendingInputHandle = { - id: 'input', - source: 'queue', - messageIds: [], - assistantMessageId: null, - get disposition() { - return disposition - }, - settle(nextDisposition) { - if (disposition) throw new Error('Claim already settled') - disposition = nextDisposition - switch (nextDisposition.kind) { - case 'consume': - replaceInput({ state: 'consumed', consumedAt: 2 }) - return null - case 'block': - return replaceInput({ - state: 'blocked', - claimedAt: null, - blocking: nextDisposition.attachmentPreparation - }) - case 'release-before-user-fact': - case 'release-after-rollback': - return replaceInput({ state: 'pending', claimedAt: null }) - case 'release-for-mutation': - return replaceInput({ state: 'pending', claimedAt: null }) - } - } - } - - const pendingInputs: PendingInputAdmissionStorePort = { - degradeBlockedInput: vi.fn(() => replaceInput({ state: 'pending', blocking: null })), - deletePendingInput: vi.fn(() => { - harness.input = null - }), - getInput: vi.fn((_sessionId, itemId) => - harness.input?.id === itemId ? harness.input : null - ), - getNextQueueInput: vi.fn(() => - harness.input?.mode === 'queue' && - harness.input.state !== 'claimed' && - harness.input.state !== 'consumed' - ? harness.input - : null - ), - hasActiveInputs: vi.fn(() => harness.input !== null), - hasBlockingInput: vi.fn(() => harness.input?.state === 'blocked'), - hasClaimedInput: vi.fn(() => harness.input?.state === 'claimed'), - isAtCapacity: vi.fn(() => false), - listPendingInputs: vi.fn(() => - harness.input && harness.input.state !== 'claimed' && harness.input.state !== 'consumed' - ? [harness.input] - : [] - ), - moveQueuedInput: vi.fn(() => (harness.input ? [harness.input] : [])), - acceptSteerMessage: vi.fn((_sessionId, input) => { - const message = createUserMessage('direct-steer-message', input) - harness.input = { - ...createInput('direct-steer', 'steer'), - payload: input, - messageIds: [message.id] - } - return { pendingInput: harness.input, message } - }), - promoteQueuedInputToSteerMessage: vi.fn(() => { - const message = createUserMessage('promoted-steer-message', harness.input!.payload) - return { - pendingInput: replaceInput({ - mode: 'steer', - queueOrder: null, - messageIds: [message.id] - }), - message - } - }), - queuePendingInput: vi.fn((_sessionId, input, options) => { - harness.input = { - ...createInput(), - payload: input, - state: options?.state ?? 'pending' - } - return harness.input - }), - retryReleasedQueueInput: vi.fn(() => { - if (harness.input?.state !== 'retry_required') { - throw new Error('Pending queue item is not waiting for retry.') - } - return replaceInput({ state: 'pending' }) - }), - retryBlockedInput: vi.fn(() => replaceInput({ state: 'pending', blocking: null })), - updateQueuedInput: vi.fn((_sessionId, _itemId, input) => replaceInput({ payload: input })) - } - - const pump: PendingInputAdmissionPumpPort = { - shouldClaimImmediately: vi.fn(() => false), - startAcceptedInput: vi.fn(), - schedule: vi.fn(), - hasInteractionBlocker: vi.fn(() => false), - canDrain: vi.fn(() => true), - drain: vi.fn(async () => { - onDrain?.(harness) - return false - }), - claimQueuedInputForPreparation: vi.fn(() => { - replaceInput({ state: 'claimed', claimedAt: 2 }) - return claim - }), - releaseRestartHoldForInput: vi.fn(), - releaseRestartHoldForSession: vi.fn(() => true), - hasRestartHeldQueueInputs: vi.fn(() => true), - hasOnlyRestartHeldQueueInputs: vi.fn(() => false) - } - - const ports: PendingInputAdmissionCoordinatorPorts = { - providerSettings: { - getModelConfig: vi.fn(() => undefined) - }, - pendingInputs, - pump, - transcript: { - getMessage: vi.fn((messageId: string) => - messageId === 'assistant' - ? ({ - id: messageId, - sessionId: SESSION_ID, - role: 'assistant' - } as ChatMessageRecord) - : null - ) - }, - attachmentRouter: { - prepare: vi.fn(async ({ content }) => ({ - content, - summary: { status: 'ready', issues: [], suggestedActions: [] } - })) - }, - sessionState: { get: vi.fn(async () => state) }, - registry: createScopeRegistry({ - clearActiveSteerPendingInputId: (expectedItemId?: string) => { - if ( - !harness.activeSteerInputId || - (expectedItemId && harness.activeSteerInputId !== expectedItemId) - ) { - return false - } - harness.activeSteerInputId = undefined - return true - }, - getAbortController: () => harness.preStreamController, - getActiveGeneration: () => undefined, - getActiveSteerPendingInputId: () => harness.activeSteerInputId, - getPreStreamTranscriptAnchorId: () => undefined, - isPendingQueueDraining: () => harness.draining, - setActiveSteerPendingInputId: (itemId: string) => { - harness.activeSteerInputId = itemId - } - }), - sessionSettings: { resolveProjectDir: vi.fn(() => null) } - } - - return { - coordinator: new PendingInputAdmissionCoordinator(ports), - harness, - pendingInputs, - ports, - pump - } -} - -interface HarnessState { - activeSteerInputId: string | undefined - draining: boolean - input: PendingSessionInputRecord | null - preStreamController: AbortController | undefined -} - -function createDeferred() { - let resolve!: (value: T) => void - const promise = new Promise((resolvePromise) => { - resolve = resolvePromise - }) - return { promise, resolve } -} - -describe('PendingInputAdmissionCoordinator', () => { - it('lists pending Steer inputs without scheduling execution', () => { - const test = createHarness() - test.harness.input = createInput('steer', 'steer') - - expect(test.coordinator.list(SESSION_ID)).toEqual([test.harness.input]) - expect(test.pump.schedule).not.toHaveBeenCalled() - }) - - it('releases the restart hold and requests one manual Queue drain', async () => { - const test = createHarness() - vi.mocked(test.pump.drain).mockResolvedValueOnce(true) - - await expect(test.coordinator.resumePendingQueue(SESSION_ID)).resolves.toBe(true) - - expect(test.pump.releaseRestartHoldForSession).toHaveBeenCalledWith(SESSION_ID) - expect(test.pump.drain).toHaveBeenCalledWith(SESSION_ID, 'manual') - }) - - it('reports resume availability from the restart hold owner', () => { - const test = createHarness() - - expect(test.coordinator.isPendingQueueResumeAvailable(SESSION_ID)).toBe(true) - expect(test.pump.hasRestartHeldQueueInputs).toHaveBeenCalledWith(SESSION_ID) - }) - - it('does not drain when no restart hold was released', async () => { - const test = createHarness() - vi.mocked(test.pump.releaseRestartHoldForSession).mockReturnValueOnce(false) - - await expect(test.coordinator.resumePendingQueue(SESSION_ID)).resolves.toBe(false) - - expect(test.pump.drain).not.toHaveBeenCalled() - }) - - it('atomically accepts an explicit retry and requests a manual drain', async () => { - const test = createHarness() - test.harness.input = { - ...test.harness.input!, - state: 'retry_required' - } - vi.mocked(test.pump.hasRestartHeldQueueInputs).mockReturnValue(false) - vi.mocked(test.pump.drain).mockResolvedValueOnce(true) - - await expect(test.coordinator.retryPendingQueueInput(SESSION_ID, 'input')).resolves.toEqual({ - accepted: true, - started: true - }) - - expect(test.pendingInputs.retryReleasedQueueInput).toHaveBeenCalledWith(SESSION_ID, 'input') - expect(test.pump.drain).toHaveBeenCalledWith(SESSION_ID, 'manual') - expect(test.harness.input?.state).toBe('pending') - - await expect(test.coordinator.retryPendingQueueInput(SESSION_ID, 'input')).resolves.toEqual({ - accepted: false, - started: false - }) - expect(test.pendingInputs.retryReleasedQueueInput).toHaveBeenCalledOnce() - }) - - it('accepts retry for a non-head Queue item and leaves dispatch ordering to the pump', async () => { - const test = createHarness() - test.harness.input = { - ...test.harness.input!, - state: 'retry_required', - queueOrder: 2 - } - const earlier = createInput('earlier') - vi.mocked(test.pendingInputs.listPendingInputs).mockReturnValueOnce([ - earlier, - test.harness.input - ]) - - await expect(test.coordinator.retryPendingQueueInput(SESSION_ID, 'input')).resolves.toEqual({ - accepted: true, - started: false - }) - - expect(test.pendingInputs.retryReleasedQueueInput).toHaveBeenCalledWith(SESSION_ID, 'input') - expect(test.pump.drain).toHaveBeenCalledWith(SESSION_ID, 'manual') - expect(test.pump.schedule).toHaveBeenCalledWith(SESSION_ID, 'enqueue') - expect(test.harness.input?.state).toBe('pending') - }) - - it('keeps an accepted retry successful when the immediate drain fails', async () => { - const test = createHarness() - test.harness.input = { - ...test.harness.input!, - state: 'retry_required' - } - vi.mocked(test.pump.drain).mockRejectedValueOnce(new Error('state unavailable')) - const errorSpy = vi.spyOn(logger, 'error').mockImplementation(() => undefined) - - await expect(test.coordinator.retryPendingQueueInput(SESSION_ID, 'input')).resolves.toEqual({ - accepted: true, - started: false - }) - - expect(test.harness.input.state).toBe('pending') - expect(test.pump.schedule).toHaveBeenCalledWith(SESSION_ID, 'enqueue') - expect(errorSpy).toHaveBeenCalledOnce() - errorSpy.mockRestore() - }) - - it('continues an accepted retry when publication fails after persistence', async () => { - const test = createHarness() - test.harness.input = { - ...test.harness.input!, - state: 'retry_required' - } - vi.mocked(test.pendingInputs.retryReleasedQueueInput).mockImplementationOnce(() => { - test.harness.input = { - ...test.harness.input!, - state: 'pending' - } - throw new Error('publication unavailable') - }) - vi.mocked(test.pump.drain).mockResolvedValueOnce(true) - const errorSpy = vi.spyOn(logger, 'error').mockImplementation(() => undefined) - - await expect(test.coordinator.retryPendingQueueInput(SESSION_ID, 'input')).resolves.toEqual({ - accepted: true, - started: true - }) - - expect(test.pump.drain).toHaveBeenCalledWith(SESSION_ID, 'manual') - expect(errorSpy).toHaveBeenCalledOnce() - errorSpy.mockRestore() - }) - - it('allows retry mutation only when every active input is a restart-held Queue draft', () => { - const test = createHarness() - - expect(() => test.coordinator.assertNoActiveInputs(SESSION_ID)).toThrow( - 'Please clear the waiting lane before mutating chat history.' - ) - vi.mocked(test.pump.hasOnlyRestartHeldQueueInputs).mockReturnValueOnce(true) - expect(() => - test.coordinator.assertNoActiveInputs(SESSION_ID, { allowRestartHeldQueue: true }) - ).not.toThrow() - }) - - it('accepts steer before the assistant stream starts and ends preparation', async () => { - const test = createHarness() - test.harness.preStreamController = new AbortController() - test.harness.input = { - ...test.harness.input!, - state: 'claimed', - claimedAt: 1 - } - - await expect(test.coordinator.steerActiveTurn(SESSION_ID, 'Steer now')).resolves.toMatchObject({ - userMessage: { id: 'direct-steer-message', status: 'pending' } - }) - expect(test.pendingInputs.acceptSteerMessage).toHaveBeenCalledWith( - SESSION_ID, - { text: 'Steer now', files: [] }, - { mergeItemId: null, preStreamAnchorMessageId: null } - ) - expect(test.harness.preStreamController.signal).toMatchObject({ - aborted: true, - reason: 'pending_input' - }) - }) - - it('rejects a send before attachment preparation when the lane is at capacity', async () => { - const test = createHarness() - vi.mocked(test.pendingInputs.isAtCapacity).mockReturnValueOnce(true) - - await expect( - test.coordinator.sendQueuedMessage( - SESSION_ID, - { text: 'At capacity', files: [] }, - { source: 'send' } - ) - ).rejects.toThrow('Pending input limit reached for this session.') - - expect(test.ports.attachmentRouter.prepare).not.toHaveBeenCalled() - expect(test.pendingInputs.queuePendingInput).not.toHaveBeenCalled() - }) - - it('returns attachment user action without queueing the send', async () => { - const test = createHarness() - vi.mocked(test.ports.attachmentRouter.prepare).mockResolvedValueOnce({ - content: { text: 'Needs OCR', files: [] }, - summary: { - status: 'needs_user_action', - issues: [{ attachmentIndex: 0, reason: 'ocr_failed' }], - suggestedActions: ['retry'] - } - }) - - await expect( - test.coordinator.sendQueuedMessage( - SESSION_ID, - { text: 'Needs OCR', files: [] }, - { source: 'send' } - ) - ).resolves.toMatchObject({ - requestId: null, - messageId: null, - attachmentPreparation: { status: 'needs_user_action' } - }) - - expect(test.pendingInputs.queuePendingInput).not.toHaveBeenCalled() - }) - - it('does not queue a send aborted after attachment preparation', async () => { - const test = createHarness() - const controller = new AbortController() - vi.mocked(test.ports.attachmentRouter.prepare).mockImplementationOnce(async ({ content }) => { - controller.abort() - return { - content, - summary: { status: 'ready', issues: [], suggestedActions: [] } - } - }) - - await expect( - test.coordinator.sendQueuedMessage( - SESSION_ID, - { text: 'Abort me', files: [] }, - { source: 'send' }, - { signal: controller.signal } - ) - ).rejects.toMatchObject({ name: 'AbortError' }) - - expect(test.pendingInputs.queuePendingInput).not.toHaveBeenCalled() - }) - - it('serializes overlapping send preparation through durable queue admission', async () => { - const test = createHarness() - const firstPreparation = createDeferred<{ - content: SendMessageInput - summary: { status: 'ready'; issues: []; suggestedActions: [] } - }>() - const prepare = vi.mocked(test.ports.attachmentRouter.prepare) - prepare - .mockImplementationOnce(async () => await firstPreparation.promise) - .mockImplementationOnce(async ({ content }) => ({ - content, - summary: { status: 'ready', issues: [], suggestedActions: [] } - })) - - const firstSend = test.coordinator.sendQueuedMessage( - SESSION_ID, - { text: 'First', files: [] }, - { source: 'send' } - ) - const secondSend = test.coordinator.sendQueuedMessage( - SESSION_ID, - { text: 'Second', files: [] }, - { source: 'send' } - ) - - await vi.waitFor(() => expect(prepare).toHaveBeenCalledOnce()) - expect(test.pendingInputs.queuePendingInput).not.toHaveBeenCalled() - - firstPreparation.resolve({ - content: { text: 'First', files: [] }, - summary: { status: 'ready', issues: [], suggestedActions: [] } - }) - await Promise.all([firstSend, secondSend]) - - expect(prepare).toHaveBeenCalledTimes(2) - expect(test.pendingInputs.queuePendingInput).toHaveBeenCalledTimes(2) - expect(prepare.mock.invocationCallOrder[1]).toBeGreaterThan( - vi.mocked(test.pendingInputs.queuePendingInput).mock.invocationCallOrder[0] - ) - expect( - vi.mocked(test.pendingInputs.queuePendingInput).mock.calls.map(([, input]) => input.text) - ).toEqual(['First', 'Second']) - }) - - it('keeps a promoted steer when another drain has already claimed it', async () => { - const test = createHarness((harness) => { - if (!harness.input) throw new Error('Pending input not found') - harness.input = { - ...harness.input, - state: 'claimed', - claimedAt: 3 - } - }) - - await expect(test.coordinator.steerPendingInput(SESSION_ID, 'input')).resolves.toMatchObject({ - id: 'input', - mode: 'steer' - }) - - expect(test.harness.input).toMatchObject({ mode: 'steer', state: 'claimed' }) - }) - - it('keeps a promoted steer behind another in-flight drain', async () => { - const test = createHarness((harness) => { - harness.draining = true - }) - - await expect(test.coordinator.steerPendingInput(SESSION_ID, 'input')).resolves.toMatchObject({ - id: 'input', - mode: 'steer' - }) - - expect(test.harness.input).toMatchObject({ mode: 'steer', state: 'pending' }) - }) - - it('accepts a direct steer that completed before its drain result was observed', async () => { - const test = createHarness((harness) => { - if (!harness.input) throw new Error('Pending input not found') - harness.input = { - ...harness.input, - state: 'consumed', - consumedAt: 3 - } - }) - - await expect( - test.coordinator.steerActiveTurn(SESSION_ID, { - text: 'Steer now', - files: [] - } satisfies SendMessageInput) - ).resolves.toMatchObject({ - requestId: null, - messageId: null, - userMessage: { - id: 'direct-steer-message', - status: 'pending' - }, - attachmentPreparation: { status: 'ready' } - }) - - expect(test.pendingInputs.deletePendingInput).not.toHaveBeenCalled() - }) - - it('keeps a promoted steer durable when its drain is deferred', async () => { - const test = createHarness() - - await expect(test.coordinator.steerPendingInput(SESSION_ID, 'input')).resolves.toMatchObject({ - id: 'input', - mode: 'steer', - state: 'pending' - }) - - expect(test.pump.schedule).toHaveBeenCalledWith(SESSION_ID, 'enqueue') - expect(test.harness.input).toMatchObject({ mode: 'steer', state: 'pending' }) - }) - - it('classifies a missing hydrated steer owner as a stale runtime instance', async () => { - const test = createHarness() - vi.mocked(test.ports.registry.getHydratedScope).mockReturnValue(undefined) - - await expect(test.coordinator.steerActiveTurn(SESSION_ID, 'Steer now')).rejects.toMatchObject({ - name: 'StaleDeepChatAgentInstanceError' - }) - - expect(test.pendingInputs.acceptSteerMessage).not.toHaveBeenCalled() - }) - - it('returns the durable blocked record without a nullable settlement branch', async () => { - const test = createHarness() - vi.mocked(test.ports.attachmentRouter.prepare).mockResolvedValueOnce({ - content: { text: 'Promote me', files: [] }, - summary: { - status: 'needs_user_action', - issues: [{ attachmentIndex: 0, reason: 'ocr_failed' }], - suggestedActions: ['retry'] - } - }) - - await expect(test.coordinator.steerPendingInput(SESSION_ID, 'input')).resolves.toMatchObject({ - id: 'input', - state: 'blocked', - blocking: { status: 'needs_user_action' } - }) - - expect(test.harness.input?.state).toBe('blocked') - }) - - it('does not mask an attachment preparation error when claim release also fails', async () => { - const test = createHarness() - const preparationError = new Error('preparation failed') - const releaseError = new Error('release failed') - const claim: ClaimedPendingInputHandle = { - id: 'input', - source: 'queue', - disposition: null, - settle: vi.fn(() => { - throw releaseError - }) - } - vi.mocked(test.pump.claimQueuedInputForPreparation).mockReturnValueOnce(claim) - vi.mocked(test.ports.attachmentRouter.prepare).mockRejectedValueOnce(preparationError) - const logError = vi.spyOn(logger, 'error').mockImplementation(() => undefined) - - await expect(test.coordinator.steerPendingInput(SESSION_ID, 'input')).rejects.toBe( - preparationError - ) - - expect(logError).toHaveBeenCalledWith( - expect.stringContaining('failed to release pending input after admission failure'), - { name: 'Error' } - ) - }) -}) diff --git a/test/main/agent/deepchat/runtime/pendingInputPump.test.ts b/test/main/agent/deepchat/runtime/pendingInputPump.test.ts index 0d495fad23..a7289a8abb 100644 --- a/test/main/agent/deepchat/runtime/pendingInputPump.test.ts +++ b/test/main/agent/deepchat/runtime/pendingInputPump.test.ts @@ -1,3 +1,4 @@ +import { useHarnessControl } from '../helpers/control' import logger from '@shared/logger' import type { AssistantMessageBlock, @@ -20,6 +21,8 @@ import type { TurnCompletion } from '@/agent/deepchat/runtime/pendingInputContracts' +const control = useHarnessControl() + const SESSION_ID = 'session' function createState(status: DeepChatSessionState['status'] = 'idle'): DeepChatSessionState { @@ -136,7 +139,17 @@ function createPendingInputStore(initialRecords: PendingSessionInputRecord[]) { replaceRecord(itemId, { state: 'consumed', consumedAt: 2 }) } + let inputSequence = 0 const store: PendingInputPumpStorePort = { + queuePendingInput: vi.fn((sessionId, input, options) => { + const id = `new-${++inputSequence}` + const record = { ...createInput(id, 'queue', Math.max(0, ...Array.from(records.values()).map((row) => row.queueOrder ?? 0)) + 1), + sessionId, state: options?.state ?? 'pending', payload: typeof input === 'string' ? { text: input, files: [] } : input } + records.set(id, record) + return record + }), + acceptSteerMessage: vi.fn(() => { throw new Error('Unexpected visible steer acceptance') }), + promoteQueuedInputToSteerMessage: vi.fn(() => { throw new Error('Unexpected steer promotion') }), blockClaimedInput: vi.fn( (sessionId: string, itemId: string, blocking: AttachmentPreparationSummary) => { const record = requireRecord(sessionId, itemId) @@ -203,26 +216,33 @@ function createPendingInputStore(initialRecords: PendingSessionInputRecord[]) { return { consumeQueue, records, store, claim } } -function completion( +async function completion( context: PendingInputTurnContext, disposition: ClaimedInputDisposition -): TurnCompletion { - context.claimedInput?.settle(disposition) +): Promise { + await context.claimedInput.settle(disposition) return { messageStart: { requestId: 'request', messageId: 'message' }, claimedInputDisposition: disposition } } -function createHarness( +async function createHarness( inputs: PendingSessionInputRecord[], start?: PendingInputPumpPorts['turnStarter']['start'] ) { - const runtime = new DeepChatAgentRuntime() + const controlPort = control() + const runtime = new DeepChatAgentRuntime(controlPort) const scope = runtime.getOrHydrateScope(toAppSessionId(SESSION_ID)) + await scope.instance.controlReady() scope.instance.setRuntimeState(createState()) const pendingInputs = createPendingInputStore(inputs) - const hasPendingInteractions = vi.fn(() => false) + const hasPendingInteractions = vi.fn(async (sessionId: string, entries: Array<{ interaction: { messageId: string; toolCallId: string; origin: 'question'; order: number } }>) => { + const current = runtime.getHydratedScope(toAppSessionId(sessionId)) + if (!current) return entries.length > 0 + await current.instance.replacePendingInteractions(entries.map(({ interaction }) => interaction), undefined, { reconcile: true }) + return current.instance.hasPendingInteractions() + }) const messages: ChatMessageRecord[] = [] const turnStarter: PendingInputPumpPorts['turnStarter'] = { start: @@ -232,6 +252,7 @@ function createHarness( ) } const ports: PendingInputPumpPorts = { + control: controlPort, pendingInputs: pendingInputs.store, transcript: { getMessages: vi.fn(() => messages) }, runLifecycle: { @@ -240,20 +261,17 @@ function createHarness( reconcilePendingInteractions: hasPendingInteractions }, turnStarter, - sessionState: { - get: vi.fn(async (sessionId: string) => - runtime.getHydratedScope(toAppSessionId(sessionId))?.state() ?? null - ) - }, sessionSettings: { resolveProjectDir: vi.fn(() => '/workspace') } } + const pump = new PendingInputPump(ports) + await pump.replaceRestartedQueueInputs([]) return { hasPendingInteractions, messages, pendingInputs, ports, - pump: new PendingInputPump(ports), + pump, runtime, scope, turnStarter @@ -277,17 +295,17 @@ describe('PendingInputPump', () => { it('keeps a restarted Queue head held across automatic wakeups until manual resume', async () => { const queue = createInput('queue', 'queue') - const test = createHarness([queue]) - test.pump.holdRestartedQueueInputs([queue.id]) + const test = await createHarness([queue]) + await test.pump.replaceRestartedQueueInputs([{ sessionId: SESSION_ID, heldInputIds: [queue.id] }]) await expect(test.pump.drain(SESSION_ID, 'enqueue')).resolves.toBe(false) await expect(test.pump.drain(SESSION_ID, 'completed')).resolves.toBe(false) expect(test.pendingInputs.store.claimQueuedInput).not.toHaveBeenCalled() - expect(test.pump.hasRestartHeldQueueInputs(SESSION_ID)).toBe(true) - expect(test.pump.hasOnlyRestartHeldQueueInputs(SESSION_ID)).toBe(true) + expect(await test.pump.hasRestartHeldQueueInputs(SESSION_ID)).toBe(true) + expect(await test.pump.hasOnlyRestartHeldQueueInputs(SESSION_ID)).toBe(true) - expect(test.pump.releaseRestartHoldForSession(SESSION_ID)).toBe(true) - expect(test.pump.hasRestartHeldQueueInputs(SESSION_ID)).toBe(false) + expect(await test.pump.releaseRestartHoldForSession(SESSION_ID)).toBe(true) + expect(await test.pump.hasRestartHeldQueueInputs(SESSION_ID)).toBe(false) await expect(test.pump.drain(SESSION_ID, 'manual')).resolves.toBe(true) expect(test.pendingInputs.store.claimQueuedInput).toHaveBeenCalledWith(SESSION_ID, queue.id) }) @@ -295,16 +313,16 @@ describe('PendingInputPump', () => { it('replaces obsolete restart holds after the database is reopened', async () => { const obsolete = createInput('obsolete', 'queue', 1) const restored = createInput('restored', 'queue', 2) - const test = createHarness([obsolete, restored]) - test.pump.holdRestartedQueueInputs([obsolete.id]) + const test = await createHarness([obsolete, restored]) + await test.pump.replaceRestartedQueueInputs([{ sessionId: SESSION_ID, heldInputIds: [obsolete.id] }]) - test.pump.replaceRestartedQueueInputs([restored.id]) + await test.pump.replaceRestartedQueueInputs([{ sessionId: SESSION_ID, heldInputIds: [restored.id] }]) await expect(test.pump.drain(SESSION_ID, 'completed')).resolves.toBe(true) await vi.waitFor(() => expect(test.scope.instance.isPendingQueueDraining()).toBe(false)) expect(test.pendingInputs.records.has(obsolete.id)).toBe(false) expect(test.pendingInputs.records.get(restored.id)?.state).toBe('pending') - expect(test.pump.hasRestartHeldQueueInputs(SESSION_ID)).toBe(true) + expect(await test.pump.hasRestartHeldQueueInputs(SESSION_ID)).toBe(true) }) it('keeps a restart-held tail behind a retry-required Queue head', async () => { @@ -313,11 +331,11 @@ describe('PendingInputPump', () => { state: 'retry_required' as const } const heldTail = createInput('held-tail', 'queue', 2) - const test = createHarness([released, heldTail]) - test.pump.holdRestartedQueueInputs([heldTail.id]) + const test = await createHarness([released, heldTail]) + await test.pump.replaceRestartedQueueInputs([{ sessionId: SESSION_ID, heldInputIds: [heldTail.id] }]) - expect(test.pump.hasRestartHeldQueueInputs(SESSION_ID)).toBe(false) - expect(test.pump.releaseRestartHoldForSession(SESSION_ID)).toBe(false) + expect(await test.pump.hasRestartHeldQueueInputs(SESSION_ID)).toBe(false) + expect(await test.pump.releaseRestartHoldForSession(SESSION_ID)).toBe(false) await expect(test.pump.drain(SESSION_ID, 'completed')).resolves.toBe(false) test.pendingInputs.records.set(released.id, { ...released, state: 'pending' }) @@ -327,7 +345,7 @@ describe('PendingInputPump', () => { expect(test.turnStarter.start).toHaveBeenCalledOnce() expect(test.pendingInputs.records.has(released.id)).toBe(false) expect(test.pendingInputs.records.get(heldTail.id)?.state).toBe('pending') - expect(test.pump.hasRestartHeldQueueInputs(SESSION_ID)).toBe(true) + expect(await test.pump.hasRestartHeldQueueInputs(SESSION_ID)).toBe(true) }) it('keeps manual Queue consumption semantics after an attachment-resolution wake', async () => { @@ -346,9 +364,9 @@ describe('PendingInputPump', () => { } ) const queue = createInput('queue', 'queue') - const test = createHarness([queue], start) - test.pump.holdRestartedQueueInputs([queue.id]) - expect(test.pump.releaseRestartHoldForSession(SESSION_ID)).toBe(true) + const test = await createHarness([queue], start) + await test.pump.replaceRestartedQueueInputs([{ sessionId: SESSION_ID, heldInputIds: [queue.id] }]) + expect(await test.pump.releaseRestartHoldForSession(SESSION_ID)).toBe(true) await expect(test.pump.drain(SESSION_ID, 'manual')).resolves.toBe(true) await vi.waitFor(() => expect(test.pendingInputs.records.get(queue.id)?.state).toBe('blocked')) @@ -367,22 +385,25 @@ describe('PendingInputPump', () => { await vi.waitFor(() => expect(test.pendingInputs.records.has(queue.id)).toBe(false)) }) - it('lets an explicit composer Send start without releasing a restarted Queue hold', () => { + it('lets an explicit composer Send start without releasing a restarted Queue hold', async () => { const queue = createInput('queue', 'queue') - const test = createHarness([queue]) - test.pump.holdRestartedQueueInputs([queue.id]) + const test = await createHarness([queue]) + await test.pump.replaceRestartedQueueInputs([{ sessionId: SESSION_ID, heldInputIds: [queue.id] }]) - expect(test.pump.shouldClaimImmediately(SESSION_ID, 'idle', 'send')).toBe(true) - expect(test.pump.shouldClaimImmediately(SESSION_ID, 'idle', 'queue')).toBe(false) - expect(test.pump.hasOnlyRestartHeldQueueInputs(SESSION_ID)).toBe(true) + const sent = await test.pump.enqueue(SESSION_ID, 'send', 'send', '/workspace') + expect(sent.state).toBe('claimed') + await vi.waitFor(() => expect(test.scope.instance.isPendingQueueDraining()).toBe(false)) + const queued = await test.pump.enqueue(SESSION_ID, 'queue', 'queue', '/workspace') + expect(queued.state).toBe('pending') + expect(await test.pump.hasRestartHeldQueueInputs(SESSION_ID)).toBe(true) }) - it('does not let new input bypass a retry-required Queue head', () => { + it('does not let new input bypass a retry-required Queue head', async () => { const retryRequired = { ...createInput('retry', 'queue'), state: 'retry_required' as const } - const test = createHarness([retryRequired]) + const test = await createHarness([retryRequired]) const followUpBlock: AssistantMessageBlock = { type: 'action', action_type: 'question_request', @@ -422,8 +443,9 @@ describe('PendingInputPump', () => { } ) - expect(test.pump.shouldClaimImmediately(SESSION_ID, 'idle', 'send')).toBe(false) - expect(test.pump.shouldClaimImmediately(SESSION_ID, 'idle', 'queue')).toBe(false) + expect((await test.pump.enqueue(SESSION_ID, 'send', 'send', '/workspace')).state).toBe('pending') + expect((await test.pump.enqueue(SESSION_ID, 'queue', 'queue', '/workspace')).state).toBe('pending') + expect(test.turnStarter.start).not.toHaveBeenCalled() }) it('claims steer input before an older queued input', async () => { @@ -435,7 +457,7 @@ describe('PendingInputPump', () => { return await deferred.promise } ) - const harness = createHarness( + const harness = await createHarness( [createInput('queue', 'queue', 1), createInput('steer', 'steer')], start ) @@ -447,9 +469,12 @@ describe('PendingInputPump', () => { expect(harness.pendingInputs.records.get('steer')?.state).toBe('claimed') expect(harness.pendingInputs.records.get('queue')?.state).toBe('pending') - harness.hasPendingInteractions.mockReturnValue(true) + harness.messages.push({ id: 'approval', sessionId: SESSION_ID, orderSeq: 1, role: 'assistant', + content: JSON.stringify([{ type: 'action', action_type: 'question_request', status: 'pending', timestamp: 1, + tool_call: { id: 'tool', name: 'ask_question', params: '{}' }, extra: { needsUserAction: true } }]), + status: 'pending', isContextEdge: 0, metadata: '{}', createdAt: 1, updatedAt: 1 }) const disposition = { kind: 'consume' } as const - executionContext?.claimedInput?.settle(disposition) + await executionContext?.claimedInput.settle(disposition) deferred.resolve({ messageStart: { requestId: 'request', messageId: 'message' }, claimedInputDisposition: disposition @@ -457,39 +482,6 @@ describe('PendingInputPump', () => { await vi.waitFor(() => expect(harness.scope.instance.isPendingQueueDraining()).toBe(false)) }) - it('propagates a rejected session-state read and releases the drain lease', async () => { - const harness = createHarness([createInput('queue', 'queue')]) - const failure = new Error('session state unavailable') - vi.mocked(harness.ports.sessionState.get).mockRejectedValueOnce(failure) - - await expect(harness.pump.drain(SESSION_ID, 'completed')).rejects.toBe(failure) - - expect(harness.scope.instance.isPendingQueueDraining()).toBe(false) - expect(harness.pendingInputs.store.claimQueuedInput).not.toHaveBeenCalled() - expect(harness.turnStarter.start).not.toHaveBeenCalled() - }) - - it('acquires the single-flight lease before the first async state read', async () => { - const harness = createHarness([createInput('queue', 'queue')]) - const stateRead = createDeferred() - vi.mocked(harness.ports.sessionState.get).mockImplementation( - async () => await stateRead.promise - ) - - const firstDrain = harness.pump.drain(SESSION_ID, 'enqueue') - const concurrentDrain = harness.pump.drain(SESSION_ID, 'enqueue') - - await expect(concurrentDrain).resolves.toBe(false) - expect(harness.ports.sessionState.get).toHaveBeenCalledOnce() - expect(harness.pendingInputs.store.claimQueuedInput).not.toHaveBeenCalled() - - stateRead.resolve(createState()) - await expect(firstDrain).resolves.toBe(true) - await vi.waitFor(() => expect(harness.scope.instance.isPendingQueueDraining()).toBe(false)) - - expect(harness.turnStarter.start).toHaveBeenCalledOnce() - expect(harness.pendingInputs.store.claimQueuedInput).toHaveBeenCalledOnce() - }) it('keeps one drain in flight per session', async () => { const deferred = createDeferred() @@ -500,14 +492,14 @@ describe('PendingInputPump', () => { return await deferred.promise } ) - const harness = createHarness([createInput('queue', 'queue')], start) + const harness = await createHarness([createInput('queue', 'queue')], start) await expect(harness.pump.drain(SESSION_ID, 'enqueue')).resolves.toBe(true) await expect(harness.pump.drain(SESSION_ID, 'enqueue')).resolves.toBe(false) expect(start).toHaveBeenCalledOnce() const disposition = { kind: 'consume' } as const - executionContext?.claimedInput?.settle(disposition) + await executionContext?.claimedInput.settle(disposition) deferred.resolve({ messageStart: { requestId: 'request', messageId: 'message' }, claimedInputDisposition: disposition @@ -528,12 +520,13 @@ describe('PendingInputPump', () => { .mockImplementationOnce(async (_sessionId, _content, context) => completion(context, { kind: 'consume' }) ) - const harness = createHarness([createInput('retry', 'queue')], start) + const harness = await createHarness([createInput('retry', 'queue')], start) await expect(harness.pump.drain(SESSION_ID, 'enqueue')).resolves.toBe(true) - const disposition = { kind: 'release-after-rollback' } as const - firstContext?.claimedInput?.settle(disposition) - harness.pump.schedule(SESSION_ID, 'enqueue') + const disposition = { kind: 'release-before-user-fact' } as const + await firstContext?.claimedInput.settle(disposition) + const later = await harness.pump.enqueue(SESSION_ID, 'Later', 'queue', '/workspace') + expect(later.state).toBe('pending') expect(start).toHaveBeenCalledOnce() firstTurn.resolve({ @@ -544,6 +537,7 @@ describe('PendingInputPump', () => { await vi.waitFor(() => expect(harness.scope.instance.isPendingQueueDraining()).toBe(false)) expect(start).toHaveBeenCalledOnce() expect(harness.pendingInputs.records.get('retry')?.state).toBe('retry_required') + expect(harness.pendingInputs.records.get(later.id)?.state).toBe('pending') }) it('does not replay an enqueue accepted while the active turn is still generating', async () => { @@ -555,17 +549,18 @@ describe('PendingInputPump', () => { return await firstTurn.promise } ) - const harness = createHarness([createInput('active', 'queue')], start) + const harness = await createHarness([createInput('active', 'queue')], start) await expect(harness.pump.drain(SESSION_ID, 'enqueue')).resolves.toBe(true) const disposition = { kind: 'consume' } as const - firstContext?.claimedInput?.settle(disposition) + await firstContext?.claimedInput.settle(disposition) + await harness.scope.instance.transitionControlStatus({ kind: 'orphan' }, 'generating') harness.scope.instance.setRuntimeState(createState('generating')) - harness.pendingInputs.records.set('queued', createInput('queued', 'queue', 2)) - - harness.pump.schedule(SESSION_ID, 'enqueue') + const queued = await harness.pump.enqueue(SESSION_ID, 'Queued', 'queue', '/workspace') + expect(queued.state).toBe('pending') expect(start).toHaveBeenCalledOnce() + await harness.scope.instance.transitionControlStatus({ kind: 'orphan' }, 'error') harness.scope.instance.setRuntimeState(createState('error')) firstTurn.resolve({ messageStart: { requestId: 'request', messageId: 'message' }, @@ -574,88 +569,12 @@ describe('PendingInputPump', () => { await vi.waitFor(() => expect(harness.scope.instance.isPendingQueueDraining()).toBe(false)) expect(start).toHaveBeenCalledOnce() - expect(harness.pendingInputs.records.get('queued')?.state).toBe('pending') - }) - - it('starts a send deferred behind a draining question follow-up', async () => { - const harness = createHarness([createInput('follow-up', 'queue')]) - const followUpBlock: AssistantMessageBlock = { - type: 'action', - action_type: 'question_request', - status: 'success', - timestamp: 2, - content: 'Answer in chat', - extra: { - needsUserAction: false, - questionResolution: 'replied', - questionFollowUpPending: true - } - } - harness.messages.push( - { - id: 'user-before-question', - sessionId: SESSION_ID, - orderSeq: 1, - role: 'user', - content: 'Ask me', - status: 'sent', - isContextEdge: 0, - metadata: '{}', - createdAt: 1, - updatedAt: 1 - }, - { - id: 'question', - sessionId: SESSION_ID, - orderSeq: 2, - role: 'assistant', - content: JSON.stringify([followUpBlock]), - status: 'sent', - isContextEdge: 0, - metadata: '{}', - createdAt: 2, - updatedAt: 2 - } - ) - const overlapLease = harness.scope.instance.tryAcquirePendingQueueDrain() - if (!overlapLease) throw new Error('Expected the overlap drain lease') - - expect(harness.pump.shouldClaimImmediately(SESSION_ID, 'idle', 'send')).toBe(false) - expect(harness.turnStarter.start).not.toHaveBeenCalled() - expect(harness.ports.transcript.getMessages).toHaveBeenCalledOnce() - - harness.messages.push({ - id: 'accepted-follow-up', - sessionId: SESSION_ID, - orderSeq: 3, - role: 'user', - content: 'My answer', - status: 'sent', - isContextEdge: 0, - metadata: '{}', - createdAt: 3, - updatedAt: 3 - }) - expect(harness.scope.instance.releasePendingQueueDrain(overlapLease)).toBe(true) - - await expect(harness.pump.drain(SESSION_ID, 'completed')).resolves.toBe(true) - await vi.waitFor(() => expect(harness.scope.instance.isPendingQueueDraining()).toBe(false)) - - expect(harness.turnStarter.start).toHaveBeenCalledOnce() - expect(harness.pendingInputs.records.has('follow-up')).toBe(false) + expect(harness.pendingInputs.records.get(queued.id)?.state).toBe('pending') }) - it('does not scan the transcript when a queue-origin input cannot drain from status', () => { - const harness = createHarness([createInput('queue', 'queue')]) - - expect(harness.pump.shouldClaimImmediately(SESSION_ID, 'generating', 'queue')).toBe(false) - - expect(harness.ports.transcript.getMessages).not.toHaveBeenCalled() - expect(harness.hasPendingInteractions).not.toHaveBeenCalled() - }) it('releases a durable claim when claim publication throws', async () => { - const harness = createHarness([createInput('queue', 'queue')]) + const harness = await createHarness([createInput('queue', 'queue')]) vi.mocked(harness.pendingInputs.store.claimQueuedInput).mockImplementationOnce( (sessionId, itemId) => { harness.pendingInputs.claim(sessionId, itemId, 'queue') @@ -676,7 +595,7 @@ describe('PendingInputPump', () => { }) it('releases a claim when publication replaces the originating instance', async () => { - const harness = createHarness([createInput('queue', 'queue')]) + const harness = await createHarness([createInput('queue', 'queue')]) const staleInstance = harness.scope.instance vi.mocked(harness.pendingInputs.store.claimQueuedInput).mockImplementationOnce( (sessionId, itemId) => { @@ -700,55 +619,8 @@ describe('PendingInputPump', () => { expect(staleInstance.isPendingQueueDraining()).toBe(false) }) - it('does not clear a newer active steer marker while adopting a claim', async () => { - const harness = createHarness([createInput('steer', 'steer')]) - harness.scope.instance.setActiveSteerPendingInputId('newer-steer') - await expect(harness.pump.drain(SESSION_ID, 'enqueue')).resolves.toBe(true) - await vi.waitFor(() => expect(harness.scope.instance.isPendingQueueDraining()).toBe(false)) - expect(harness.scope.instance.getActiveSteerPendingInputId()).toBe('newer-steer') - }) - - it.each(['queue', 'steer'] as const)('uses the %s consume transition', async (mode) => { - const harness = createHarness([createInput(mode, mode)]) - - await expect(harness.pump.drain(SESSION_ID, 'enqueue')).resolves.toBe(true) - await vi.waitFor(() => expect(harness.scope.instance.isPendingQueueDraining()).toBe(false)) - - if (mode === 'queue') { - expect(harness.pendingInputs.store.consumeQueuedInput).toHaveBeenCalledWith(SESSION_ID, mode) - expect(harness.pendingInputs.store.consumeSteerInput).not.toHaveBeenCalled() - } else { - expect(harness.pendingInputs.store.consumeSteerInput).toHaveBeenCalledWith(SESSION_ID, mode) - expect(harness.pendingInputs.store.consumeQueuedInput).not.toHaveBeenCalled() - } - }) - - it.each(['queue', 'steer'] as const)('uses the %s release transition', async (mode) => { - const start: PendingInputPumpPorts['turnStarter']['start'] = vi.fn( - async (_sessionId, _content, context) => - completion(context, { kind: 'release-after-rollback' }) - ) - const harness = createHarness([createInput(mode, mode)], start) - - await expect(harness.pump.drain(SESSION_ID, 'enqueue')).resolves.toBe(true) - await vi.waitFor(() => expect(harness.scope.instance.isPendingQueueDraining()).toBe(false)) - - if (mode === 'queue') { - expect(harness.pendingInputs.store.releaseClaimedQueueInputForRetry).toHaveBeenCalledWith( - SESSION_ID, - mode - ) - expect(harness.pendingInputs.store.releaseClaimedInput).not.toHaveBeenCalled() - } else { - expect(harness.pendingInputs.store.releaseClaimedInput).toHaveBeenCalledWith(SESSION_ID, mode) - expect(harness.pendingInputs.store.releaseClaimedQueueInputForRetry).not.toHaveBeenCalled() - } - expect(harness.pendingInputs.records.get(mode)?.state).toBe( - mode === 'queue' ? 'retry_required' : 'pending' - ) - }) it('blocks a claimed head without draining later inputs', async () => { const attachmentPreparation: AttachmentPreparationSummary = { @@ -760,7 +632,7 @@ describe('PendingInputPump', () => { async (_sessionId, _content, context) => completion(context, { kind: 'block', attachmentPreparation }) ) - const harness = createHarness( + const harness = await createHarness( [createInput('blocked', 'queue', 1), createInput('later', 'queue', 2)], start ) @@ -776,12 +648,12 @@ describe('PendingInputPump', () => { expect(harness.pendingInputs.records.get('later')?.state).toBe('pending') }) - it('leaves a released input at the head for explicit retry', async () => { + it('keeps an unstarted input at the head for explicit retry', async () => { const start: PendingInputPumpPorts['turnStarter']['start'] = vi.fn( async (_sessionId, _content, context) => - completion(context, { kind: 'release-after-rollback' }) + completion(context, { kind: 'release-before-user-fact' }) ) - const harness = createHarness( + const harness = await createHarness( [createInput('retry', 'queue', 1), createInput('later', 'queue', 2)], start ) @@ -795,7 +667,7 @@ describe('PendingInputPump', () => { }) it('continues with the next input after consuming the current head', async () => { - const harness = createHarness([ + const harness = await createHarness([ createInput('first', 'queue', 1), createInput('second', 'queue', 2) ]) @@ -809,7 +681,7 @@ describe('PendingInputPump', () => { expect(harness.scope.instance.isPendingQueueDraining()).toBe(false) }) - it('cleans only the originating instance after replacement', async () => { + it('retains the originating drain until its replaced Instance turn settles', async () => { const deferred = createDeferred() let executionContext: PendingInputTurnContext | undefined const start: PendingInputPumpPorts['turnStarter']['start'] = vi.fn( @@ -818,18 +690,18 @@ describe('PendingInputPump', () => { return await deferred.promise } ) - const harness = createHarness([createInput('queue', 'queue')], start) + const harness = await createHarness([createInput('queue', 'queue')], start) await expect(harness.pump.drain(SESSION_ID, 'enqueue')).resolves.toBe(true) const staleInstance = harness.scope.instance harness.runtime.evict(toAppSessionId(SESSION_ID)) const replacement = harness.runtime.getOrHydrateScope(toAppSessionId(SESSION_ID)) replacement.instance.setRuntimeState(createState()) - const replacementDrainLease = replacement.instance.tryAcquirePendingQueueDrain() - if (!replacementDrainLease) throw new Error('Expected replacement drain lease') + await replacement.instance.controlReady() + await expect(harness.pump.drain(SESSION_ID, 'enqueue')).resolves.toBe(false) const disposition = { kind: 'consume' } as const - executionContext?.claimedInput?.settle(disposition) + await executionContext?.claimedInput.settle(disposition) deferred.resolve({ messageStart: { requestId: 'request', messageId: 'message' }, claimedInputDisposition: disposition @@ -837,22 +709,36 @@ describe('PendingInputPump', () => { await vi.waitFor(() => expect(staleInstance.isPendingQueueDraining()).toBe(false)) expect(staleInstance.isPendingQueueDraining()).toBe(false) + await control.flush() + const replacementTurn = createDeferred() + let replacementContext: PendingInputTurnContext | undefined + vi.mocked(harness.turnStarter.start).mockImplementationOnce(async (_sessionId, _content, context) => { + replacementContext = context + return await replacementTurn.promise + }) + harness.pendingInputs.records.set('replacement', createInput('replacement', 'queue')) + await expect(harness.pump.drain(SESSION_ID, 'enqueue')).resolves.toBe(true) expect(replacement.instance.isPendingQueueDraining()).toBe(true) - expect(replacement.instance.releasePendingQueueDrain(replacementDrainLease)).toBe(true) + await replacementContext!.claimedInput.settle(disposition) + replacementTurn.resolve({ + messageStart: { requestId: 'replacement-run', messageId: 'replacement-message' }, + claimedInputDisposition: disposition + }) + await vi.waitFor(() => expect(replacement.instance.isPendingQueueDraining()).toBe(false)) }) it('reports inconsistent turn completion without applying a second transition', async () => { const start: PendingInputPumpPorts['turnStarter']['start'] = vi.fn( async (_sessionId, _content, context) => { const settled = { kind: 'consume' } as const - context.claimedInput?.settle(settled) + await context.claimedInput.settle(settled) return { messageStart: { requestId: 'request', messageId: 'message' }, claimedInputDisposition: { kind: 'release-after-rollback' } } } ) - const harness = createHarness([createInput('queue', 'queue')], start) + const harness = await createHarness([createInput('queue', 'queue')], start) const error = vi.spyOn(logger, 'error').mockImplementation(() => undefined) await expect(harness.pump.drain(SESSION_ID, 'enqueue')).resolves.toBe(true) @@ -874,7 +760,7 @@ describe('PendingInputPump', () => { const start: PendingInputPumpPorts['turnStarter']['start'] = vi.fn(async () => { throw new Error('turn failed before reporting settlement') }) - const harness = createHarness( + const harness = await createHarness( [createInput('failed', 'queue', 1), createInput('later', 'queue', 2)], start ) @@ -891,7 +777,7 @@ describe('PendingInputPump', () => { }) it('does not repeat a durable consume when publication throws afterward', async () => { - const harness = createHarness([createInput('queue', 'queue')]) + const harness = await createHarness([createInput('queue', 'queue')]) vi.mocked(harness.pendingInputs.store.consumeQueuedInput).mockImplementationOnce( (sessionId, itemId) => { harness.pendingInputs.consumeQueue(sessionId, itemId) @@ -908,9 +794,9 @@ describe('PendingInputPump', () => { expect(harness.pendingInputs.store.releaseClaimedQueueInput).not.toHaveBeenCalled() }) - it('preserves the primary settlement error when durable verification also fails', () => { - const harness = createHarness([createInput('queue', 'queue')]) - const claim = harness.pump.claimQueuedInputForPreparation(SESSION_ID, 'queue') + it('preserves the primary settlement error when durable verification also fails', async () => { + const harness = await createHarness([createInput('queue', 'queue')]) + const claim = await harness.pump.claimQueuedInputForPreparation(SESSION_ID, 'queue') const primaryError = new Error('publication failed') const verificationError = new Error('verification failed') const getInput = vi.mocked(harness.pendingInputs.store.getInput).getMockImplementation() @@ -933,7 +819,7 @@ describe('PendingInputPump', () => { ) const warn = vi.spyOn(logger, 'warn').mockImplementation(() => undefined) - expect(() => + await expect( claim.settle({ kind: 'block', attachmentPreparation: { @@ -942,10 +828,10 @@ describe('PendingInputPump', () => { suggestedActions: ['retry'] } }) - ).toThrow(primaryError) + ).rejects.toBe(primaryError) expect(warn).toHaveBeenCalledWith( - expect.stringContaining('failed to verify pending input settlement'), + expect.stringContaining('failed to verify input effect'), { name: 'Error' } ) expect(claim.disposition).toEqual({ diff --git a/test/main/agent/deepchat/runtime/process.test.ts b/test/main/agent/deepchat/runtime/process.test.ts index 73387dbba8..bc980a7512 100644 --- a/test/main/agent/deepchat/runtime/process.test.ts +++ b/test/main/agent/deepchat/runtime/process.test.ts @@ -2780,7 +2780,9 @@ describe('processStream', () => { }) }) - it('flushes ACP provider permission blocks immediately and keeps live permission updates mutable', async () => { + it('awaits ACP permission registration before advancing the stream and keeps updates mutable', async () => { + const registered = Promise.withResolvers() + let streamAdvanced = false let releaseStream: (() => void) | null = null let commitDecision: ((granted: boolean) => void) | null = null const coreStream = vi.fn(async function* () { @@ -2819,6 +2821,7 @@ describe('processStream', () => { metadata: { rememberable: false } } } as LLMCoreStreamEvent + streamAdvanced = true await new Promise((resolve) => { releaseStream = resolve }) @@ -2826,8 +2829,9 @@ describe('processStream', () => { }) as unknown as ProcessParams['coreStream'] const onStreamingProviderPermission = vi.fn( - (_permission, _tool, resolvePermission: (granted: boolean) => void) => { + async (_permission, _tool, resolvePermission: (granted: boolean) => void) => { commitDecision = resolvePermission + await registered.promise } ) const params = createParams({ @@ -2843,6 +2847,10 @@ describe('processStream', () => { await Promise.resolve() expect(onStreamingProviderPermission).toHaveBeenCalledTimes(1) + expect(streamAdvanced).toBe(false) + registered.resolve() + await vi.advanceTimersByTimeAsync(1) + expect(streamAdvanced).toBe(true) expect(messageStore.updateAssistantContent).toHaveBeenCalled() const pendingBlocks = (messageStore.updateAssistantContent as ReturnType).mock diff --git a/test/main/agent/deepchat/runtime/promptAssemblyService.test.ts b/test/main/agent/deepchat/runtime/promptAssemblyService.test.ts index 5adcfbb63f..a2eed62d0a 100644 --- a/test/main/agent/deepchat/runtime/promptAssemblyService.test.ts +++ b/test/main/agent/deepchat/runtime/promptAssemblyService.test.ts @@ -1,3 +1,4 @@ +import { useHarnessControl } from '../helpers/control' import { describe, expect, it, vi } from 'vitest' import { DeepChatAgentRuntime } from '@/agent/deepchat/instance/deepChatAgentRuntime' import { toAppSessionId } from '@/agent/shared/agentSessionIds' @@ -7,6 +8,8 @@ import { } from '@/agent/deepchat/runtime/promptAssemblyService' import { POSIX_COMMAND_SHELL } from '../../../../helpers/commandShell' +const control = useHarnessControl() + const SESSION_ID = 'session' const buildSystemPromptWithSkills = vi.hoisted(() => @@ -36,7 +39,7 @@ function createHarness( memoryContent: string | null = 'recalled memory', directiveContent: string | null = null ) { - const runtime = new DeepChatAgentRuntime() + const runtime = new DeepChatAgentRuntime(control()) const contribute = vi.fn(async () => ({ memory: { content: memoryContent, @@ -109,7 +112,8 @@ describe('PromptAssemblyService', () => { it('binds the base assembler to the expected instance and copies its inputs', async () => { const { runtime, service } = createHarness() buildSystemPromptWithSkills.mockClear() - const instance = runtime.getOrHydrate(toAppSessionId('other')) + const instance = runtime.getOrHydrate(toAppSessionId(SESSION_ID)) + await instance.controlReady() const toolDefinitions = [] as never[] const activeSkillNames = ['skill-a'] const sessionActiveSkillNames = ['session-skill'] diff --git a/test/main/agent/deepchat/runtime/runLifecycleCoordinator.test.ts b/test/main/agent/deepchat/runtime/runLifecycleCoordinator.test.ts index d9699e3fa1..b74794d646 100644 --- a/test/main/agent/deepchat/runtime/runLifecycleCoordinator.test.ts +++ b/test/main/agent/deepchat/runtime/runLifecycleCoordinator.test.ts @@ -1,3 +1,4 @@ +import { useHarnessControl } from '../helpers/control' import logger from '@shared/logger' import type { AssistantMessageBlock, @@ -19,6 +20,8 @@ import { import * as toolSurface from '@/agent/deepchat/runtime/toolSurface' import { POSIX_COMMAND_SHELL } from '../../../../helpers/commandShell' +const control = useHarnessControl() + const SESSION_ID = 'session' function createState(status: DeepChatSessionState['status'] = 'idle'): DeepChatSessionState { @@ -84,7 +87,8 @@ function createMessage( } function createHarness(initialMessages: ChatMessageRecord[] = []) { - const runtime = new DeepChatAgentRuntime() + const harnessControl = control() + const runtime = new DeepChatAgentRuntime(harnessControl) const messages = [...initialMessages] const statusPorts: SessionStatusPublisherPorts = { publishEvent: vi.fn(), @@ -117,6 +121,7 @@ function createHarness(initialMessages: ChatMessageRecord[] = []) { }) return { + control: harnessControl, coordinator, emitMessageRefresh, messages, @@ -139,23 +144,99 @@ describe('RunLifecycleCoordinator', () => { vi.restoreAllMocks() }) + it('rebuilds current durable interactions across owner rotation and readiness waits', async () => { + const { + coordinator, + control: port, + messages + } = createHarness([createMessage('old-message', [createPendingAction('old-tool')])]) + const scope = coordinator.getOrCreateScope(SESSION_ID) + await scope.instance.controlReady() + const oldRun = createRun(SESSION_ID, 'old-run', 'old-message') + await coordinator.registerRun(scope, oldRun) + const entered = Promise.withResolvers() + const release = Promise.withResolvers() + const send = port.send.bind(port) + let held = false + const gate = vi.spyOn(port, 'send').mockImplementation(async (command, receive) => { + if (command.method === 'updateInteractions' && !held) { + held = true + entered.resolve() + await release.promise + } + return await send(command, receive) + }) + try { + const observation = coordinator.refreshPendingInteractions(SESSION_ID) + await entered.promise + messages.splice( + 0, + messages.length, + createMessage('new-message', [createPendingAction('new-tool')]) + ) + oldRun.abortController.abort() + await coordinator.ensureOperationController(scope) + release.resolve() + expect(await observation).toBe(true) + expect(scope.instance.getPendingInteractions()).toMatchObject([ + { messageId: 'new-message', toolCallId: 'new-tool' } + ]) + expect( + await scope.instance.replacePendingInteractions( + [ + { + messageId: 'old-message', + toolCallId: 'old-tool', + origin: 'pre-check-permission', + order: 0 + } + ], + { kind: 'run', id: 'old-run' } + ) + ).toBe(false) + expect(scope.instance.getPendingInteractions()).toMatchObject([ + { messageId: 'new-message', toolCallId: 'new-tool' } + ]) + } finally { + release.resolve() + gate.mockRestore() + } + + const readyEntered = Promise.withResolvers() + const ready = Promise.withResolvers() + vi.spyOn(scope.instance, 'controlReady').mockImplementationOnce(async () => { + readyEntered.resolve() + await ready.promise + }) + messages.splice(0, messages.length) + const observation = coordinator.reconcilePendingInteractions(SESSION_ID, []) + await readyEntered.promise + messages.push(createMessage('ready-message', [createPendingAction('ready-tool')])) + ready.resolve() + expect(await observation).toBe(true) + expect(scope.instance.getPendingInteractions()).toMatchObject([ + { messageId: 'ready-message', toolCallId: 'ready-tool' } + ]) + }) + it('replaces an aborted lingering run with a fresh operation controller', async () => { const { coordinator } = createHarness() const scope = coordinator.getOrCreateScope(SESSION_ID) + await scope.instance.controlReady() const oldController = new AbortController() const permissionResolve = vi.fn().mockResolvedValue(undefined) - coordinator.registerRun(scope, createRun(SESSION_ID, 'run-old', 'message-old', oldController)) - scope.instance.registerActiveProviderPermission({ + await coordinator.registerRun(scope, createRun(SESSION_ID, 'run-old', 'message-old', oldController)) + await scope.instance.registerActiveProviderPermission({ requestId: 'permission-old', messageId: 'message-old', toolCallId: 'tool-old', providerId: 'acp', permissionType: 'write', resolve: permissionResolve - }) + }, 'run-old') oldController.abort() - const replacement = coordinator.ensureOperationController(scope) + const replacement = await coordinator.ensureOperationController(scope) await flushPromises() expect(replacement).not.toBe(oldController) @@ -166,17 +247,17 @@ describe('RunLifecycleCoordinator', () => { expect(permissionResolve).toHaveBeenCalledWith(false) }) - it('requires the exact controller owner before clearing operation state', () => { + it('requires the exact controller owner before clearing operation state', async () => { const { coordinator } = createHarness() const scope = coordinator.getOrCreateScope(SESSION_ID) - const owned = coordinator.ensureOperationController(scope) - const replacement = new AbortController() - scope.instance.setAbortController(replacement) + await scope.instance.controlReady() + const owned = await coordinator.ensureOperationController(scope) + const replacement = await scope.instance.ensureOperationController() - expect(coordinator.clearOperationController(scope)).toBe(false) - expect(coordinator.clearOperationController(scope, owned)).toBe(false) + expect(await coordinator.clearOperationController(scope)).toBe(false) + expect(await coordinator.clearOperationController(scope, owned)).toBe(false) expect(scope.instance.getAbortController()).toBe(replacement) - expect(coordinator.clearOperationController(scope, replacement)).toBe(true) + expect(await coordinator.clearOperationController(scope, replacement)).toBe(true) expect(scope.instance.getAbortController()).toBeUndefined() }) @@ -184,24 +265,26 @@ describe('RunLifecycleCoordinator', () => { const { coordinator, runtime } = createHarness() const sessionId = toAppSessionId(SESSION_ID) const staleScope = coordinator.getOrCreateScope(SESSION_ID) + await staleScope.instance.controlReady() const staleRun = createRun(SESSION_ID, 'shared-run', 'stale-message') const stalePermissionResolve = vi.fn().mockResolvedValue(undefined) - coordinator.registerRun(staleScope, staleRun) - staleScope.instance.registerActiveProviderPermission({ + await coordinator.registerRun(staleScope, staleRun) + await staleScope.instance.registerActiveProviderPermission({ requestId: 'stale-permission', messageId: 'stale-message', toolCallId: 'stale-tool', providerId: 'acp', permissionType: 'write', resolve: stalePermissionResolve - }) + }, 'shared-run') runtime.evict(sessionId) const currentScope = coordinator.getOrCreateScope(SESSION_ID) + await currentScope.instance.controlReady() const currentRun = createRun(SESSION_ID, 'shared-run', 'current-message') - coordinator.registerRun(currentScope, currentRun) + await coordinator.registerRun(currentScope, currentRun) - expect(coordinator.clearRun(staleScope, staleRun.runId)).toBe(true) + expect(await coordinator.clearRun(staleScope, staleRun.runId)).toBe(true) await flushPromises() expect(staleScope.instance.getActiveGeneration()).toBeUndefined() @@ -209,12 +292,13 @@ describe('RunLifecycleCoordinator', () => { expect(stalePermissionResolve).toHaveBeenCalledOnce() }) - it('rejects a run whose session does not match its scope', () => { + it('rejects a run whose session does not match its scope', async () => { const { coordinator } = createHarness() const scope = coordinator.getOrCreateScope(SESSION_ID) + await scope.instance.controlReady() const mismatchedRun = createRun('other', 'run-other', 'message-other') - expect(() => coordinator.registerRun(scope, mismatchedRun)).toThrow( + await expect(coordinator.registerRun(scope, mismatchedRun)).rejects.toThrow( 'Loop run run-other belongs to session other, not session' ) expect(scope.instance.getActiveGeneration()).toBeUndefined() @@ -223,26 +307,27 @@ describe('RunLifecycleCoordinator', () => { it('keeps run, message and first-turn-ready fences distinct', async () => { const { coordinator } = createHarness() const scope = coordinator.getOrCreateScope(SESSION_ID) + await scope.instance.controlReady() const run = createRun(SESSION_ID, 'run-1', 'message-1') - coordinator.registerRun(scope, run) + await coordinator.registerRun(scope, run) expect(coordinator.isRunCurrentForScope(scope, 'run-1')).toBe(true) expect(coordinator.isMessageAssociatedWithRun(run, 'message-1')).toBe(true) expect(coordinator.isMessageAssociatedWithRun(run, 'message-2')).toBe(false) expect(coordinator.resolveStreamRequestId(SESSION_ID, 'message-1')).toBe('run-1') expect(coordinator.resolveStreamRequestId(SESSION_ID, 'message-2')).toBe('message-2') - expect(coordinator.markFirstTurnReady(scope, 'run-other')).toBe(false) + expect(await coordinator.markFirstTurnReady(scope, 'run-other')).toBe(false) const ready = scope.instance.waitForFirstTurnReady({ timeoutMs: 100 }) - expect(coordinator.markFirstTurnReady(scope, 'run-1')).toBe(true) + expect(await coordinator.markFirstTurnReady(scope, 'run-1')).toBe(true) await expect(ready).resolves.toBe(true) coordinator.clearFirstTurnReady(SESSION_ID) run.abortController.abort() - expect(coordinator.markFirstTurnReady(scope, 'run-1')).toBe(false) + expect(await coordinator.markFirstTurnReady(scope, 'run-1')).toBe(false) }) - it('does not hydrate an evicted session while clearing first-turn readiness', () => { + it('does not hydrate an evicted session while clearing first-turn readiness', async () => { const { coordinator, runtime } = createHarness() const sessionId = toAppSessionId(SESSION_ID) coordinator.getOrCreateScope(SESSION_ID) @@ -253,19 +338,21 @@ describe('RunLifecycleCoordinator', () => { expect(runtime.getHydrated(sessionId)).toBeUndefined() }) - it('observes a stale terminal result without overwriting the current run status', () => { + it('observes a stale terminal result without overwriting the current run status', async () => { const { coordinator, terminalObserver } = createHarness() const scope = coordinator.getOrCreateScope(SESSION_ID) + await scope.instance.controlReady() scope.instance.setRuntimeState(createState('generating')) + await scope.instance.transitionControlStatus({ kind: 'orphan' }, 'generating') const staleRun = createRun(SESSION_ID, 'run-stale', 'message-stale') const currentRun = createRun(SESSION_ID, 'run-current', 'message-current') - coordinator.registerRun(scope, staleRun) - coordinator.registerRun(scope, currentRun) + await coordinator.registerRun(scope, staleRun) + await coordinator.registerRun(scope, currentRun) vi.mocked(terminalObserver.observeTerminal).mockImplementation(() => { expect(scope.state()?.status).toBe('generating') }) - coordinator.applyProcessResultStatus( + await coordinator.applyProcessResultStatus( SESSION_ID, { status: 'completed', stopReason: 'complete' }, staleRun.runId @@ -274,7 +361,7 @@ describe('RunLifecycleCoordinator', () => { expect(terminalObserver.observeTerminal).toHaveBeenCalledOnce() expect(scope.state()?.status).toBe('generating') - coordinator.applyProcessResultStatus( + await coordinator.applyProcessResultStatus( SESSION_ID, { status: 'completed', stopReason: 'complete' }, currentRun.runId @@ -284,14 +371,16 @@ describe('RunLifecycleCoordinator', () => { expect(scope.state()?.status).toBe('idle') }) - it('does not let an aborted turn overwrite a replacement operation status', () => { + it('does not let an aborted turn overwrite a replacement operation status', async () => { const message = createMessage('message-old', []) const { coordinator, terminalObserver, transcript } = createHarness([message]) const scope = coordinator.getOrCreateScope(SESSION_ID) + await scope.instance.controlReady() scope.instance.setRuntimeState(createState('generating')) - const replacement = coordinator.ensureOperationController(scope) + await scope.instance.transitionControlStatus({ kind: 'orphan' }, 'generating') + const replacement = await coordinator.ensureOperationController(scope) - coordinator.settleAbortedTurn(SESSION_ID, message.id, 'run-old', '{}') + await coordinator.settleAbortedTurn(SESSION_ID, message.id, 'run-old', '{}') expect(scope.state()?.status).toBe('generating') expect(scope.instance.getAbortController()).toBe(replacement) @@ -310,9 +399,11 @@ describe('RunLifecycleCoordinator', () => { message ]) const scope = coordinator.getOrCreateScope(SESSION_ID) + await scope.instance.controlReady() scope.instance.setRuntimeState(createState('generating')) + await scope.instance.transitionControlStatus({ kind: 'orphan' }, 'generating') - expect(coordinator.refreshPendingInteractions(SESSION_ID)).toBe(true) + expect(await coordinator.refreshPendingInteractions(SESSION_ID)).toBe(true) await coordinator.cancel(SESSION_ID) expect(scope.instance.getPendingInteractions()).toEqual([]) @@ -346,9 +437,10 @@ describe('RunLifecycleCoordinator', () => { expect(revoke).toHaveBeenCalledWith(SESSION_ID) }) - it('revokes deferred dispatches and live grants when current scope operations are cancelled', () => { + it('revokes deferred dispatches and live grants when current scope operations are cancelled', async () => { const { coordinator, revokeConversation } = createHarness() const scope = coordinator.getOrCreateScope(SESSION_ID) + await scope.instance.controlReady() const revoke = vi.spyOn(toolSurface, 'revokeToolSurfaceDeferredDispatchesForSession') coordinator.cancelScopeOperations(scope) @@ -360,7 +452,9 @@ describe('RunLifecycleCoordinator', () => { it('settles a generating session that nothing owns when it is stopped', async () => { const { coordinator, pendingInputWakeup, terminalObserver } = createHarness() const scope = coordinator.getOrCreateScope(SESSION_ID) + await scope.instance.controlReady() scope.instance.setRuntimeState(createState('generating')) + await scope.instance.transitionControlStatus({ kind: 'orphan' }, 'generating') vi.spyOn(toolSurface, 'revokeToolSurfaceDeferredDispatchesForSession') await coordinator.cancel(SESSION_ID) @@ -371,16 +465,18 @@ describe('RunLifecycleCoordinator', () => { expect(pendingInputWakeup.drain).toHaveBeenCalledWith(SESSION_ID, 'completed') }) - it('declines to settle while the transcript still holds another approval', () => { + it('declines to settle while the transcript still holds another approval', async () => { const message = createMessage('message-pending', [createPendingAction('tool-1')]) const { coordinator, terminalObserver } = createHarness([message]) const scope = coordinator.getOrCreateScope(SESSION_ID) + await scope.instance.controlReady() scope.instance.setRuntimeState(createState('generating')) + await scope.instance.transitionControlStatus({ kind: 'orphan' }, 'generating') // The instance queue is stale relative to the transcript: the caller closed one entry, and // another message's approval is still open. - scope.instance.replacePendingInteractions([]) + await scope.instance.replacePendingInteractions([]) - expect(coordinator.settleOrphanedInteraction(SESSION_ID, null)).toBe(false) + expect(await coordinator.settleOrphanedInteraction(SESSION_ID, null)).toBe(false) expect(scope.state()?.status).toBe('generating') expect(terminalObserver.observeTerminal).not.toHaveBeenCalled() }) @@ -388,7 +484,9 @@ describe('RunLifecycleCoordinator', () => { it('leaves an error session untouched when it is stopped', async () => { const { coordinator, pendingInputWakeup, terminalObserver } = createHarness() const scope = coordinator.getOrCreateScope(SESSION_ID) + await scope.instance.controlReady() scope.instance.setRuntimeState(createState('error')) + await scope.instance.transitionControlStatus({ kind: 'orphan' }, 'error') vi.spyOn(toolSurface, 'revokeToolSurfaceDeferredDispatchesForSession') await coordinator.cancel(SESSION_ID) @@ -401,18 +499,20 @@ describe('RunLifecycleCoordinator', () => { expect(pendingInputWakeup.drain).not.toHaveBeenCalled() }) - it('leaves a generating session alone while a run still owns it', () => { + it('leaves a generating session alone while a run still owns it', async () => { const { coordinator, terminalObserver } = createHarness() const scope = coordinator.getOrCreateScope(SESSION_ID) + await scope.instance.controlReady() scope.instance.setRuntimeState(createState('generating')) - coordinator.registerRun(scope, createRun(SESSION_ID, 'run-live', 'message-live')) + await scope.instance.transitionControlStatus({ kind: 'orphan' }, 'generating') + await coordinator.registerRun(scope, createRun(SESSION_ID, 'run-live', 'message-live')) - expect(coordinator.settleOrphanedInteraction(SESSION_ID, null)).toBe(false) + expect(await coordinator.settleOrphanedInteraction(SESSION_ID, null)).toBe(false) expect(scope.state()?.status).toBe('generating') expect(terminalObserver.observeTerminal).not.toHaveBeenCalled() }) - it('preserves pending interaction order across assistant messages', () => { + it('preserves pending interaction order across assistant messages', async () => { const first = createMessage('message-1', [ createPendingAction('tool-1'), createPendingAction('tool-2') @@ -420,8 +520,9 @@ describe('RunLifecycleCoordinator', () => { const second = createMessage('message-2', [createPendingAction('tool-3')], 2) const { coordinator } = createHarness([first, second]) const scope = coordinator.getOrCreateScope(SESSION_ID) + await scope.instance.controlReady() - expect(coordinator.refreshPendingInteractions(SESSION_ID)).toBe(true) + expect(await coordinator.refreshPendingInteractions(SESSION_ID)).toBe(true) expect(scope.instance.getPendingInteractions()).toEqual([ expect.objectContaining({ messageId: 'message-1', toolCallId: 'tool-1', order: 0 }), expect.objectContaining({ messageId: 'message-1', toolCallId: 'tool-2', order: 1 }), @@ -445,9 +546,11 @@ describe('RunLifecycleCoordinator', () => { const { coordinator, emitMessageRefresh, pendingInputWakeup, terminalObserver, transcript } = createHarness([first, second]) const scope = coordinator.getOrCreateScope(SESSION_ID) + await scope.instance.controlReady() scope.instance.setRuntimeState(createState('generating')) + await scope.instance.transitionControlStatus({ kind: 'orphan' }, 'generating') - expect(coordinator.refreshPendingInteractions(SESSION_ID)).toBe(true) + expect(await coordinator.refreshPendingInteractions(SESSION_ID)).toBe(true) await coordinator.cancel(SESSION_ID) expect(scope.instance.getPendingInteractions()).toEqual([]) @@ -467,18 +570,20 @@ describe('RunLifecycleCoordinator', () => { it('cancels provider permissions exactly once and ignores already-stale requests', async () => { const { coordinator } = createHarness() const scope = coordinator.getOrCreateScope(SESSION_ID) + await scope.instance.controlReady() + await coordinator.registerRun(scope, createRun(SESSION_ID, 'run-1', 'message-1')) const resolve = vi .fn() .mockRejectedValue(new Error('Unknown ACP permission request: permission-1')) const warn = vi.spyOn(logger, 'warn').mockImplementation(() => undefined) - scope.instance.registerActiveProviderPermission({ + await scope.instance.registerActiveProviderPermission({ requestId: 'permission-1', messageId: 'message-1', toolCallId: 'tool-1', providerId: 'acp', permissionType: 'write', resolve - }) + }, 'run-1') await coordinator.cancel(SESSION_ID) await coordinator.cancel(SESSION_ID) @@ -492,23 +597,25 @@ describe('RunLifecycleCoordinator', () => { it('reports genuine provider permission cancellation failures', async () => { const { coordinator } = createHarness() const scope = coordinator.getOrCreateScope(SESSION_ID) + await scope.instance.controlReady() const failure = new Error('permission bridge failed') + await coordinator.registerRun(scope, createRun(SESSION_ID, 'run-1', 'message-1')) const resolve = vi.fn().mockRejectedValue(failure) const warn = vi.spyOn(logger, 'warn').mockImplementation(() => undefined) - scope.instance.registerActiveProviderPermission({ + await scope.instance.registerActiveProviderPermission({ requestId: 'permission-1', messageId: 'message-1', toolCallId: 'tool-1', providerId: 'acp', permissionType: 'write', resolve - }) + }, 'run-1') await coordinator.cancel(SESSION_ID) await flushPromises() expect(warn).toHaveBeenCalledWith( - '[DeepChatAgent] Failed to cancel ACP permission request permission-1:', + '[DeepChatAgent] Failed to cancel ACP permission:', { name: 'Error' } ) }) diff --git a/test/main/agent/deepchat/runtime/sessionIdentityService.test.ts b/test/main/agent/deepchat/runtime/sessionIdentityService.test.ts index 3d0fe04439..fe0fba91c9 100644 --- a/test/main/agent/deepchat/runtime/sessionIdentityService.test.ts +++ b/test/main/agent/deepchat/runtime/sessionIdentityService.test.ts @@ -1,3 +1,4 @@ +import { useHarnessControl } from '../helpers/control' import { describe, expect, it, vi } from 'vitest' import { DeepChatAgentRuntime } from '@/agent/deepchat/instance/deepChatAgentRuntime' import { toAppSessionId } from '@/agent/shared/agentSessionIds' @@ -6,10 +7,12 @@ import { type SessionIdentityServiceDependencies } from '@/agent/deepchat/runtime/sessionIdentityService' +const control = useHarnessControl() + const SESSION_ID = 'session' function createHarness(row?: { agent_id?: string; session_kind?: string }) { - const runtime = new DeepChatAgentRuntime() + const runtime = new DeepChatAgentRuntime(control()) const get = vi.fn(() => row) const deps = { registry: runtime, diff --git a/test/main/agent/deepchat/runtime/sessionLifecycleCoordinator.test.ts b/test/main/agent/deepchat/runtime/sessionLifecycleCoordinator.test.ts index 82a898a9ec..f81203fded 100644 --- a/test/main/agent/deepchat/runtime/sessionLifecycleCoordinator.test.ts +++ b/test/main/agent/deepchat/runtime/sessionLifecycleCoordinator.test.ts @@ -1,3 +1,4 @@ +import { useHarnessControl } from '../helpers/control' import { describe, expect, it, vi } from 'vitest' import { DeepChatAgentRuntime } from '@/agent/deepchat/instance/deepChatAgentRuntime' import { toAppSessionId } from '@/agent/shared/agentSessionIds' @@ -6,6 +7,8 @@ import { type SessionLifecycleCoordinatorDependencies } from '@/agent/deepchat/runtime/sessionLifecycleCoordinator' +const control = useHarnessControl() + const SESSION_ID = 'session' function createHarness() { @@ -16,10 +19,16 @@ function createHarness() { order.push(label) return result as TResult } - const runtime = new DeepChatAgentRuntime() + const runtime = new DeepChatAgentRuntime(control()) + const clearSessionControl = runtime.clearSessionControl.bind(runtime) + vi.spyOn(runtime, 'clearSessionControl').mockImplementation(async (...args) => { + await clearSessionControl(...args) + order.push('runtime.clearSessionControl') + }) const cancel = vi.fn(async () => undefined) const deps = { registry: runtime, + fenceInputEffects: vi.fn(record('fenceInputEffects')), providerSettings: {} as SessionLifecycleCoordinatorDependencies['providerSettings'], promptSettings: { getDefaultSystemPrompt: vi.fn(async () => '') }, sessionStore: { create: vi.fn(), delete: vi.fn(record('sessionStore.delete')) }, @@ -45,7 +54,6 @@ function createHarness() { cancelScopeOperations: vi.fn(record('runLifecycle.cancelScopeOperations')), scopeFor: vi.fn() }, - interactionParking: { clearSession: vi.fn(record('interactionParking.clearSession')) }, toolSurfaceDiagnostics: { clear: vi.fn(record('toolSurfaceDiagnostics.clear')) }, toolSurfaceCanaryDiagnostics: { clearSession: vi.fn(record('toolSurfaceCanaryDiagnostics.clearSession')) @@ -122,6 +130,7 @@ describe('SessionLifecycleCoordinator', () => { await coordinator.destroy(SESSION_ID) expect(order).toEqual([ + 'fenceInputEffects', 'memory.beginSessionDestroy', 'runLifecycle.cancelScopeOperations', 'runLifecycle.clearFirstTurnReady', @@ -132,7 +141,7 @@ describe('SessionLifecycleCoordinator', () => { 'programmaticToolParents.releaseSession', 'pruningFeedback.clear', 'toolSurfaceCanaryDiagnostics.clearSession', - 'interactionParking.clearSession', + 'runtime.clearSessionControl', 'toolSurfaceDiagnostics.clear', 'memory.finishSessionDestroy', 'toolService.clearConversationToolMapping' @@ -147,6 +156,7 @@ describe('SessionLifecycleCoordinator', () => { await coordinator.destroy(SESSION_ID) expect(order).toEqual([ + 'fenceInputEffects', 'memory.beginSessionDestroy', 'runLifecycle.clearFirstTurnReady', 'pendingInputs.deleteBySession', @@ -156,7 +166,7 @@ describe('SessionLifecycleCoordinator', () => { 'programmaticToolParents.releaseSession', 'pruningFeedback.clear', 'toolSurfaceCanaryDiagnostics.clearSession', - 'interactionParking.clearSession', + 'runtime.clearSessionControl', 'memory.finishSessionDestroy', 'toolService.clearConversationToolMapping' ]) diff --git a/test/main/agent/deepchat/runtime/sessionSettingsCoordinator.test.ts b/test/main/agent/deepchat/runtime/sessionSettingsCoordinator.test.ts index b4fa252f5e..e327c621d3 100644 --- a/test/main/agent/deepchat/runtime/sessionSettingsCoordinator.test.ts +++ b/test/main/agent/deepchat/runtime/sessionSettingsCoordinator.test.ts @@ -1,3 +1,4 @@ +import { useHarnessControl } from '../helpers/control' import { DeepChatAgentRuntime } from '@/agent/deepchat/instance/deepChatAgentRuntime' import { toAppSessionId } from '@/agent/shared/agentSessionIds' import { SessionSettingsCoordinator } from '@/agent/deepchat/runtime/sessionSettingsCoordinator' @@ -6,6 +7,8 @@ import { ModelType } from '@shared/model' import type { SessionGenerationSettings } from '@shared/types/agent-interface' import { describe, expect, it, vi } from 'vitest' +const control = useHarnessControl() + const SESSION_ID = 'session' const APP_SESSION_ID = toAppSessionId(SESSION_ID) const BASE_SETTINGS: SessionGenerationSettings = { @@ -67,9 +70,10 @@ function createProviderSettings(): ProviderModelResolutionPort { } } -function createHarness() { - const runtime = new DeepChatAgentRuntime() +async function createHarness() { + const runtime = new DeepChatAgentRuntime(control()) const instance = runtime.getOrHydrate(APP_SESSION_ID) + await instance.controlReady() instance.setRuntimeState({ status: 'idle', providerId: 'openai', @@ -121,9 +125,11 @@ function createHarness() { revalidateActiveSkillsForAgent, runtime, sessionStore, - replaceInstance() { + async replaceInstance() { runtime.evict(APP_SESSION_ID) const replacement = runtime.getOrHydrate(APP_SESSION_ID) + await replacement.controlReady() + await replacement.transitionControlStatus({ kind: 'orphan' }, 'generating') replacement.setRuntimeState({ status: 'generating', providerId: 'replacement-provider', @@ -141,13 +147,13 @@ function createHarness() { describe('SessionSettingsCoordinator', () => { it('does not persist or mutate a model switch after instance replacement', async () => { - const harness = createHarness() + const harness = await createHarness() const prompt = deferred() harness.getDefaultSystemPrompt.mockReturnValueOnce(prompt.promise) const update = harness.coordinator.setModel(SESSION_ID, 'anthropic', 'claude-3-5-sonnet') await vi.waitFor(() => expect(harness.getDefaultSystemPrompt).toHaveBeenCalledOnce()) - const replacement = harness.replaceInstance() + const replacement = await harness.replaceInstance() prompt.resolve('Target default prompt') await expect(update).rejects.toMatchObject({ name: 'StaleDeepChatAgentInstanceError' }) @@ -162,12 +168,13 @@ describe('SessionSettingsCoordinator', () => { }) it('rechecks generating status before committing a model switch', async () => { - const harness = createHarness() + const harness = await createHarness() const prompt = deferred() harness.getDefaultSystemPrompt.mockReturnValueOnce(prompt.promise) const update = harness.coordinator.setModel(SESSION_ID, 'anthropic', 'claude-3-5-sonnet') await vi.waitFor(() => expect(harness.getDefaultSystemPrompt).toHaveBeenCalledOnce()) + await harness.instance.transitionControlStatus({ kind: 'orphan' }, 'generating') harness.instance.setRuntimeState({ status: 'generating', providerId: 'openai', @@ -182,7 +189,7 @@ describe('SessionSettingsCoordinator', () => { }) it('does not publish agent context after replacement during reassignment fencing', async () => { - const harness = createHarness() + const harness = await createHarness() const reassignment = deferred() harness.beginSessionAgentReassignment.mockReturnValueOnce(reassignment.promise) @@ -195,7 +202,7 @@ describe('SessionSettingsCoordinator', () => { await vi.waitFor(() => expect(harness.beginSessionAgentReassignment).toHaveBeenCalledWith(SESSION_ID) ) - const replacement = harness.replaceInstance() + const replacement = await harness.replaceInstance() reassignment.resolve() await expect(update).rejects.toMatchObject({ name: 'StaleDeepChatAgentInstanceError' }) @@ -206,24 +213,34 @@ describe('SessionSettingsCoordinator', () => { }) it('does not persist generation settings after instance replacement', async () => { - const harness = createHarness() + const harness = await createHarness() const update = harness.coordinator.updateGenerationSettings(SESSION_ID, { systemPrompt: 'Stale prompt' }) - const replacement = harness.replaceInstance() + const rejected = expect(update).rejects.toMatchObject({ name: 'StaleDeepChatAgentInstanceError' }) + const replacement = await harness.replaceInstance() - await expect(update).rejects.toMatchObject({ name: 'StaleDeepChatAgentInstanceError' }) + await rejected expect(harness.sessionStore.updateGenerationSettings).not.toHaveBeenCalled() expect(replacement.getGenerationSettings()?.systemPrompt).toBe('Replacement prompt') }) it('does not apply settings sanitized for a model that changed in flight', async () => { - const harness = createHarness() - + const harness = await createHarness() + + const started = deferred() + const proceed = deferred() + const getSettings = harness.coordinator.getEffectiveGenerationSettings.bind(harness.coordinator) + vi.spyOn(harness.coordinator, 'getEffectiveGenerationSettings').mockImplementationOnce(async (...args) => { + started.resolve() + await proceed.promise + return await getSettings(...args) + }) const update = harness.coordinator.updateGenerationSettings(SESSION_ID, { systemPrompt: 'Old-model prompt' }) + await started.promise harness.instance.setRuntimeState({ status: 'idle', providerId: 'anthropic', @@ -231,6 +248,7 @@ describe('SessionSettingsCoordinator', () => { permissionMode: 'default' }) + proceed.resolve() await expect(update).rejects.toThrow( `Session ${SESSION_ID} model changed while generation settings were updating.` ) @@ -238,7 +256,7 @@ describe('SessionSettingsCoordinator', () => { }) it('clears stale provider limits only when the configured context window changes', async () => { - const harness = createHarness() + const harness = await createHarness() harness.instance.recordContextWindowObservation({ providerId: 'openai', modelId: 'gpt-4', @@ -264,7 +282,7 @@ describe('SessionSettingsCoordinator', () => { }) it('atomically applies the model and generation snapshot for an idle turn', async () => { - const harness = createHarness() + const harness = await createHarness() const generationSettings = { ...BASE_SETTINGS, systemPrompt: 'Frozen child prompt', @@ -298,7 +316,7 @@ describe('SessionSettingsCoordinator', () => { { reasoningEffort: undefined, thinkingBudget: 4096 }, { reasoningEffort: 'none' as const, thinkingBudget: undefined } ])('preserves snapshot reasoning controls without re-inheriting defaults: %j', async (controls) => { - const harness = createHarness() + const harness = await createHarness() vi.mocked(harness.providerSettings.getModelConfig).mockReturnValue({ reasoning: true, reasoningEffort: 'low', @@ -331,7 +349,8 @@ describe('SessionSettingsCoordinator', () => { }) it('refuses to apply a turn snapshot after generation has started', async () => { - const harness = createHarness() + const harness = await createHarness() + await harness.instance.transitionControlStatus({ kind: 'orphan' }, 'generating') harness.instance.setRuntimeState({ status: 'generating', providerId: 'openai', @@ -350,7 +369,7 @@ describe('SessionSettingsCoordinator', () => { }) it('does not apply a turn snapshot after the runtime instance is replaced', async () => { - const harness = createHarness() + const harness = await createHarness() const prompt = deferred() harness.getDefaultSystemPrompt.mockReturnValueOnce(prompt.promise) @@ -360,7 +379,7 @@ describe('SessionSettingsCoordinator', () => { generationSettings: { ...BASE_SETTINGS, systemPrompt: '' } }) await vi.waitFor(() => expect(harness.getDefaultSystemPrompt).toHaveBeenCalledOnce()) - const replacement = harness.replaceInstance() + const replacement = await harness.replaceInstance() prompt.resolve('Target default prompt') await expect(update).rejects.toMatchObject({ name: 'StaleDeepChatAgentInstanceError' }) @@ -369,7 +388,7 @@ describe('SessionSettingsCoordinator', () => { }) it('preserves a permission downgrade while a turn snapshot is being sanitized', async () => { - const harness = createHarness() + const harness = await createHarness() const prompt = deferred() harness.getDefaultSystemPrompt.mockReturnValueOnce(prompt.promise) harness.instance.getRuntimeState()!.permissionMode = 'full_access' diff --git a/test/main/agent/deepchat/runtime/sessionStateResolver.test.ts b/test/main/agent/deepchat/runtime/sessionStateResolver.test.ts index 3c4e3da749..4b0e771d5c 100644 --- a/test/main/agent/deepchat/runtime/sessionStateResolver.test.ts +++ b/test/main/agent/deepchat/runtime/sessionStateResolver.test.ts @@ -1,3 +1,4 @@ +import { useHarnessControl } from '../helpers/control' import { describe, expect, it, vi } from 'vitest' import { DeepChatAgentRuntime } from '@/agent/deepchat/instance/deepChatAgentRuntime' import { toAppSessionId } from '@/agent/shared/agentSessionIds' @@ -6,6 +7,8 @@ import { type SessionStateResolverDependencies } from '@/agent/deepchat/runtime/sessionStateResolver' +const control = useHarnessControl() + const SESSION_ID = 'session' const PERSISTED_ROW = { @@ -16,7 +19,7 @@ const PERSISTED_ROW = { function createHarness(options: { persisted?: unknown; hasPendingInteractions?: boolean } = {}) { const order: string[] = [] - const runtime = new DeepChatAgentRuntime() + const runtime = new DeepChatAgentRuntime(control()) const getAgentId = vi.fn(() => { order.push('identity.getAgentId') return 'deepchat' diff --git a/test/main/agent/deepchat/runtime/sessionStatusPublisher.test.ts b/test/main/agent/deepchat/runtime/sessionStatusPublisher.test.ts index cff8ce9dde..4ed428e421 100644 --- a/test/main/agent/deepchat/runtime/sessionStatusPublisher.test.ts +++ b/test/main/agent/deepchat/runtime/sessionStatusPublisher.test.ts @@ -1,3 +1,4 @@ +import { useHarnessControl } from '../helpers/control' import { describe, expect, it, vi } from 'vitest' import { DeepChatAgentRuntime } from '@/agent/deepchat/instance/deepChatAgentRuntime' import { toAppSessionId } from '@/agent/shared/agentSessionIds' @@ -6,6 +7,8 @@ import { type SessionStatusPublisherPorts } from '@/agent/deepchat/runtime/sessionStatusPublisher' +const control = useHarnessControl() + function createPublisher() { const ports: SessionStatusPublisherPorts = { publishEvent: vi.fn(), @@ -16,9 +19,10 @@ function createPublisher() { } describe('SessionStatusPublisher', () => { - it('updates current state and publishes the four projections in order', () => { - const runtime = new DeepChatAgentRuntime() + it('updates current state and publishes the four projections in order', async () => { + const runtime = new DeepChatAgentRuntime(control()) const scope = runtime.getOrHydrateScope(toAppSessionId('session')) + await scope.instance.controlReady() scope.instance.setRuntimeState({ status: 'idle', providerId: 'openai', @@ -27,7 +31,7 @@ describe('SessionStatusPublisher', () => { }) const { ports, publisher } = createPublisher() - expect(publisher.transition(scope, 'generating')).toBe(true) + expect(await publisher.transition(scope, 'generating', { kind: 'orphan' })).toBe(true) expect(scope.state()?.status).toBe('generating') expect(ports.publishEvent).toHaveBeenNthCalledWith(1, 'sessions.status.changed', { @@ -55,28 +59,30 @@ describe('SessionStatusPublisher', () => { expect(order).toEqual([...order].sort((left, right) => left - right)) }) - it('does not publish when state is missing and treats an unchanged status as success', () => { - const runtime = new DeepChatAgentRuntime() + it('does not publish when state is missing and treats an unchanged status as success', async () => { + const runtime = new DeepChatAgentRuntime(control()) const scope = runtime.getOrHydrateScope(toAppSessionId('session')) + await scope.instance.controlReady() const { ports, publisher } = createPublisher() - expect(publisher.transition(scope, 'idle')).toBe(false) + expect(await publisher.transition(scope, 'idle', { kind: 'orphan' })).toBe(false) scope.instance.setRuntimeState({ status: 'idle', providerId: 'openai', modelId: 'gpt-5', permissionMode: 'full_access' }) - expect(publisher.transition(scope, 'idle')).toBe(true) + expect(await publisher.transition(scope, 'idle', { kind: 'orphan' })).toBe(true) expect(ports.publishEvent).not.toHaveBeenCalled() expect(ports.publishSessionUpdate).not.toHaveBeenCalled() expect(ports.sessionUiPort.refreshSessionUi).not.toHaveBeenCalled() }) - it('publishes terminal usage even when the projected status is already unchanged', () => { - const runtime = new DeepChatAgentRuntime() + it('publishes terminal usage even when the projected status is already unchanged', async () => { + const runtime = new DeepChatAgentRuntime(control()) const scope = runtime.getOrHydrateScope(toAppSessionId('session')) + await scope.instance.controlReady() scope.instance.setRuntimeState({ status: 'idle', providerId: 'openai', @@ -85,7 +91,7 @@ describe('SessionStatusPublisher', () => { }) const { ports, publisher } = createPublisher() - expect(publisher.transition(scope, 'idle', { totalTokens: 12 })).toBe(true) + expect(await publisher.transition(scope, 'idle', { kind: 'orphan' }, { totalTokens: 12 })).toBe(true) expect(ports.publishSessionUpdate).toHaveBeenCalledWith({ sessionId: 'session', @@ -98,10 +104,11 @@ describe('SessionStatusPublisher', () => { expect(ports.sessionUiPort.refreshSessionUi).not.toHaveBeenCalled() }) - it('fences stale and mismatched scopes before mutating any instance', () => { - const runtime = new DeepChatAgentRuntime() + it('fences stale and mismatched scopes before mutating any instance', async () => { + const runtime = new DeepChatAgentRuntime(control()) const sessionId = toAppSessionId('session') const staleScope = runtime.getOrHydrateScope(sessionId) + await staleScope.instance.controlReady() staleScope.instance.setRuntimeState({ status: 'idle', providerId: 'openai', @@ -110,6 +117,7 @@ describe('SessionStatusPublisher', () => { }) runtime.evict(sessionId) const currentScope = runtime.getOrHydrateScope(sessionId) + await currentScope.instance.controlReady() currentScope.instance.setRuntimeState({ status: 'idle', providerId: 'openai', @@ -117,6 +125,7 @@ describe('SessionStatusPublisher', () => { permissionMode: 'full_access' }) const otherScope = runtime.getOrHydrateScope(toAppSessionId('other')) + await otherScope.instance.controlReady() otherScope.instance.setRuntimeState({ status: 'idle', providerId: 'openai', @@ -126,8 +135,8 @@ describe('SessionStatusPublisher', () => { const mismatchedScope = runtime.scopeFor(sessionId, otherScope.instance) const { ports, publisher } = createPublisher() - expect(publisher.transition(staleScope, 'error')).toBe(false) - expect(publisher.transition(mismatchedScope, 'error')).toBe(false) + expect(await publisher.transition(staleScope, 'error', { kind: 'orphan' })).toBe(false) + expect(await publisher.transition(mismatchedScope, 'error', { kind: 'orphan' })).toBe(false) expect(staleScope.state()?.status).toBe('idle') expect(currentScope.state()?.status).toBe('idle') diff --git a/test/main/agent/deepchat/runtime/toolResolver.test.ts b/test/main/agent/deepchat/runtime/toolResolver.test.ts index 034f07ee9f..bb6f754ebd 100644 --- a/test/main/agent/deepchat/runtime/toolResolver.test.ts +++ b/test/main/agent/deepchat/runtime/toolResolver.test.ts @@ -17,6 +17,7 @@ import { const createResourceInstance = (agentId = 'deepchat') => { let cached: { profile: 'general'; fingerprint: string; tools: [] } | undefined return { + controlReady: async () => {}, getAgentId: vi.fn(() => agentId), getRuntimeState: vi.fn(() => ({ status: 'idle', diff --git a/test/main/agent/deepchat/runtime/toolRuntimeBindings.test.ts b/test/main/agent/deepchat/runtime/toolRuntimeBindings.test.ts index 857e45a055..8d53fa6a1a 100644 --- a/test/main/agent/deepchat/runtime/toolRuntimeBindings.test.ts +++ b/test/main/agent/deepchat/runtime/toolRuntimeBindings.test.ts @@ -1,3 +1,4 @@ +import { useHarnessControl } from '../helpers/control' import { describe, expect, it, vi } from 'vitest' import type { ChatMessage } from '@shared/types/core/chat-message' import { DeepChatAgentRuntime } from '@/agent/deepchat/instance/deepChatAgentRuntime' @@ -10,6 +11,8 @@ import { } from '@/agent/deepchat/runtime/toolRuntimeBindings' import { JevPruningFeedback, JEV_PRUNING_TIGHTENED_DROP_BELOW } from '@/agent/deepchat/runtime/jevPruningFeedback' +const control = useHarnessControl() + const normalizeToolResultContent = vi.hoisted(() => vi.fn(async () => [{ type: 'text', text: 'ok' }])) const reviewAutoApproveToolPermission = vi.hoisted(() => vi.fn(async () => ({ decision: 'ask_user' })) @@ -27,7 +30,7 @@ vi.mock('@/agent/deepchat/runtime/toolPermissionReviewer', async (importOriginal const SESSION_ID = 'session' function createHarness(persisted?: { provider_id?: string; model_id?: string }) { - const runtime = new DeepChatAgentRuntime() + const runtime = new DeepChatAgentRuntime(control()) const abortSignal = new AbortController().signal const deps = { providerSettings: {}, diff --git a/test/main/agent/deepchat/runtime/toolSurfaceDiagnostics.test.ts b/test/main/agent/deepchat/runtime/toolSurfaceDiagnostics.test.ts index a96587e959..c9a375a643 100644 --- a/test/main/agent/deepchat/runtime/toolSurfaceDiagnostics.test.ts +++ b/test/main/agent/deepchat/runtime/toolSurfaceDiagnostics.test.ts @@ -1,3 +1,4 @@ +import { useHarnessControl } from '../helpers/control' import { describe, expect, it } from 'vitest' import type { ChatMessage } from '@shared/types/core/chat-message' import type { MCPToolDefinition } from '@shared/types/core/mcp' @@ -13,6 +14,8 @@ import { ToolSurfaceShadowDiagnosticsRegistry } from '@/agent/deepchat/runtime/toolSurfaceDiagnostics' +const control = useHarnessControl() + function agentTool(name: string, options: { description?: string } = {}): MCPToolDefinition { return { type: 'function', @@ -229,7 +232,7 @@ describe('Tool Surface shadow diagnostics selection', () => { describe('Tool Surface shadow diagnostics registry', () => { it('isolates provider/model/profile lineages and invalidates evicted recorders', () => { const registry = new ToolSurfaceShadowDiagnosticsRegistry({ maxLineagesPerInstance: 1 }) - const instance = new DeepChatAgentInstance(toAppSessionId('session-1')) + const instance = new DeepChatAgentInstance(toAppSessionId('session-1'), control()) const definitions = [agentTool('read')] const firstScope = { sessionId: 'session-1', @@ -268,8 +271,8 @@ describe('Tool Surface shadow diagnostics registry', () => { it('does not share a lineage between replacement Session instances', () => { const registry = new ToolSurfaceShadowDiagnosticsRegistry() - const firstInstance = new DeepChatAgentInstance(toAppSessionId('session-1')) - const replacement = new DeepChatAgentInstance(toAppSessionId('session-1')) + const firstInstance = new DeepChatAgentInstance(toAppSessionId('session-1'), control()) + const replacement = new DeepChatAgentInstance(toAppSessionId('session-1'), control()) const scope = { sessionId: 'session-1', providerId: 'provider-1', @@ -290,7 +293,7 @@ describe('Tool Surface shadow diagnostics registry', () => { it('allows a pending universe to settle after its Run finishes', async () => { const registry = new ToolSurfaceShadowDiagnosticsRegistry() - const instance = new DeepChatAgentInstance(toAppSessionId('session-1')) + const instance = new DeepChatAgentInstance(toAppSessionId('session-1'), control()) const scope = { sessionId: 'session-1', providerId: 'provider-1', @@ -330,7 +333,7 @@ describe('Tool Surface shadow diagnostics registry', () => { it('commits deferred measurements in Run order when universes resolve in reverse', async () => { const registry = new ToolSurfaceShadowDiagnosticsRegistry() - const instance = new DeepChatAgentInstance(toAppSessionId('session-1')) + const instance = new DeepChatAgentInstance(toAppSessionId('session-1'), control()) const scope = { sessionId: 'session-1', providerId: 'provider-1', @@ -381,7 +384,7 @@ describe('Tool Surface shadow diagnostics registry', () => { it('cancels and invalidates deferred Runs when their Session instance is cleared', async () => { const registry = new ToolSurfaceShadowDiagnosticsRegistry() - const instance = new DeepChatAgentInstance(toAppSessionId('session-1')) + const instance = new DeepChatAgentInstance(toAppSessionId('session-1'), control()) const scope = { sessionId: 'session-1', providerId: 'provider-1', @@ -416,7 +419,7 @@ describe('Tool Surface shadow diagnostics registry', () => { it('cancels pending Runs without erasing settled lineage diagnostics', async () => { const registry = new ToolSurfaceShadowDiagnosticsRegistry() - const instance = new DeepChatAgentInstance(toAppSessionId('session-1')) + const instance = new DeepChatAgentInstance(toAppSessionId('session-1'), control()) const scope = { sessionId: 'session-1', providerId: 'provider-1', @@ -457,7 +460,7 @@ describe('Tool Surface shadow diagnostics registry', () => { it('owns and cancels a scheduled Run before its universe source starts', async () => { const registry = new ToolSurfaceShadowDiagnosticsRegistry() - const instance = new DeepChatAgentInstance(toAppSessionId('session-1')) + const instance = new DeepChatAgentInstance(toAppSessionId('session-1'), control()) const scope = { sessionId: 'session-1', providerId: 'provider-1', @@ -484,7 +487,7 @@ describe('Tool Surface shadow diagnostics registry', () => { it('detaches deferred diagnostics from mutable provider inputs before scheduling', async () => { const registry = new ToolSurfaceShadowDiagnosticsRegistry() - const instance = new DeepChatAgentInstance(toAppSessionId('session-1')) + const instance = new DeepChatAgentInstance(toAppSessionId('session-1'), control()) const scope = { sessionId: 'session-1', providerId: 'provider-1', @@ -528,7 +531,7 @@ describe('Tool Surface shadow diagnostics registry', () => { it('rechecks Session currency after a deferred universe resolves', async () => { const registry = new ToolSurfaceShadowDiagnosticsRegistry() - const instance = new DeepChatAgentInstance(toAppSessionId('session-1')) + const instance = new DeepChatAgentInstance(toAppSessionId('session-1'), control()) const scope = { sessionId: 'session-1', providerId: 'provider-1', @@ -558,7 +561,7 @@ describe('Tool Surface shadow diagnostics registry', () => { it('does not let a stalled lineage delay another provider lineage', async () => { const registry = new ToolSurfaceShadowDiagnosticsRegistry() - const instance = new DeepChatAgentInstance(toAppSessionId('session-1')) + const instance = new DeepChatAgentInstance(toAppSessionId('session-1'), control()) const firstScope = { sessionId: 'session-1', providerId: 'provider-1', @@ -603,7 +606,7 @@ describe('Tool Surface shadow diagnostics registry', () => { it('cancels a resolved Run while it waits behind the same lineage tail', async () => { const registry = new ToolSurfaceShadowDiagnosticsRegistry() - const instance = new DeepChatAgentInstance(toAppSessionId('session-1')) + const instance = new DeepChatAgentInstance(toAppSessionId('session-1'), control()) const scope = { sessionId: 'session-1', providerId: 'provider-1', @@ -643,7 +646,7 @@ describe('Tool Surface shadow diagnostics registry', () => { it('admits at most sixteen scheduled Runs and releases capacity after cancellation', async () => { const registry = new ToolSurfaceShadowDiagnosticsRegistry() - const instance = new DeepChatAgentInstance(toAppSessionId('session-1')) + const instance = new DeepChatAgentInstance(toAppSessionId('session-1'), control()) const scope = { sessionId: 'session-1', providerId: 'provider-1', @@ -696,7 +699,7 @@ describe('Tool Surface shadow diagnostics registry', () => { vi.useFakeTimers() try { const registry = new ToolSurfaceShadowDiagnosticsRegistry() - const instance = new DeepChatAgentInstance(toAppSessionId('session-1')) + const instance = new DeepChatAgentInstance(toAppSessionId('session-1'), control()) const scope = { sessionId: 'session-1', providerId: 'provider-1', @@ -733,7 +736,7 @@ describe('Tool Surface shadow diagnostics registry', () => { it('contains synchronous and asynchronous universe source failures', async () => { const registry = new ToolSurfaceShadowDiagnosticsRegistry() - const instance = new DeepChatAgentInstance(toAppSessionId('session-1')) + const instance = new DeepChatAgentInstance(toAppSessionId('session-1'), control()) const baseScope = { sessionId: 'session-1', providerId: 'provider-1', diff --git a/test/main/agent/deepchat/runtime/toolSurfaceSelection.test.ts b/test/main/agent/deepchat/runtime/toolSurfaceSelection.test.ts index a35eec1502..27a9fd7c88 100644 --- a/test/main/agent/deepchat/runtime/toolSurfaceSelection.test.ts +++ b/test/main/agent/deepchat/runtime/toolSurfaceSelection.test.ts @@ -1,3 +1,4 @@ +import { useHarnessControl } from '../helpers/control' import { describe, expect, it } from 'vitest' import { DeepChatAgentInstance } from '@/agent/deepchat/instance/deepChatAgentInstance' import { toAppSessionId } from '@/agent/shared/agentSessionIds' @@ -14,6 +15,8 @@ import { ToolSurfaceRolloutOwner } from '@/agent/deepchat/runtime/toolSurfaceRollout' +const control = useHarnessControl() + describe('Tool Surface adapter selection', () => { const policy = createAutomaticToolSurfaceSelectionPolicy(256) @@ -120,8 +123,8 @@ describe('Tool Surface adapter selection', () => { it('keeps bounded process-live adapter history isolated by instance and lineage', () => { const history = new ToolSurfaceAdapterHistory({ maxLineagesPerInstance: 2 }) - const firstInstance = new DeepChatAgentInstance(toAppSessionId('session-1')) - const secondInstance = new DeepChatAgentInstance(toAppSessionId('session-1')) + const firstInstance = new DeepChatAgentInstance(toAppSessionId('session-1'), control()) + const secondInstance = new DeepChatAgentInstance(toAppSessionId('session-1'), control()) const scope = (modelId: string) => ({ sessionId: 'session-1', providerId: 'provider-1', diff --git a/test/main/agent/deepchat/runtime/transcriptMutationCoordinator.test.ts b/test/main/agent/deepchat/runtime/transcriptMutationCoordinator.test.ts index 06472adf70..fdf9394322 100644 --- a/test/main/agent/deepchat/runtime/transcriptMutationCoordinator.test.ts +++ b/test/main/agent/deepchat/runtime/transcriptMutationCoordinator.test.ts @@ -1,3 +1,4 @@ +import { useHarnessControl } from '../helpers/control' import { describe, expect, it, vi } from 'vitest' import { DeepChatAgentRuntime } from '@/agent/deepchat/instance/deepChatAgentRuntime' import { toAppSessionId } from '@/agent/shared/agentSessionIds' @@ -6,6 +7,8 @@ import { type TranscriptMutationCoordinatorDependencies } from '@/agent/deepchat/runtime/transcriptMutationCoordinator' +const control = useHarnessControl() + const SESSION_ID = 'session' const IDLE_STATE = { @@ -17,13 +20,16 @@ const IDLE_STATE = { function createHarness(state: unknown = IDLE_STATE) { const order: string[] = [] - const runtime = new DeepChatAgentRuntime() + const runtime = new DeepChatAgentRuntime(control()) const record = (label: string) => () => { order.push(label) } const deps = { registry: runtime, - sessionState: { get: vi.fn(async () => state) }, + sessionState: { get: vi.fn(async () => { + await runtime.getOrHydrate(toAppSessionId(SESSION_ID)).controlReady() + return state + }) }, sessionSettings: { resolveProjectDir: vi.fn(() => '/workspace') }, admission: { assertNoActiveInputs: vi.fn(record('admission.assertNoActiveInputs')) }, compaction: { reset: vi.fn(record('compaction.reset')), invalidateIfNeeded: vi.fn() }, @@ -77,14 +83,15 @@ describe('TranscriptMutationCoordinator', () => { expect(order).toEqual([]) }) - it('resets pending interactions, compaction, and status when clearing finishes', () => { + it('resets pending interactions, compaction, and status when clearing finishes', async () => { const { coordinator, order, runtime } = createHarness() const instance = runtime.getOrHydrate(toAppSessionId(SESSION_ID)) - instance.replacePendingInteractions([ - { messageId: 'm1', toolCallId: 'tc1', origin: 'permission', order: 0 } + await instance.controlReady() + await instance.replacePendingInteractions([ + { messageId: 'm1', toolCallId: 'tc1', origin: 'pre-check-permission', order: 0 } ]) - coordinator.finishClearMessages(SESSION_ID) + await coordinator.finishClearMessages(SESSION_ID) expect(instance.hasPendingInteractions()).toBe(false) expect(order).toEqual(['compaction.reset', 'runLifecycle.transitionStatus']) @@ -146,10 +153,10 @@ describe('TranscriptMutationCoordinator', () => { }) }) - it('refreshes pending interactions before projecting idle after a truncate', () => { + it('refreshes pending interactions before projecting idle after a truncate', async () => { const { coordinator, order } = createHarness() - coordinator.finishTranscriptTruncate(SESSION_ID) + await coordinator.finishTranscriptTruncate(SESSION_ID) expect(order).toEqual([ 'runLifecycle.refreshPendingInteractions', @@ -157,7 +164,7 @@ describe('TranscriptMutationCoordinator', () => { ]) }) - it('invalidates compaction and memory for the same order sequence', () => { + it('invalidates compaction and memory for the same order sequence', async () => { const { coordinator, deps } = createHarness() coordinator.invalidateTranscriptFrom(SESSION_ID, 7) @@ -170,7 +177,7 @@ describe('TranscriptMutationCoordinator', () => { expect(deps.memory.invalidateFromOrderSeq).toHaveBeenCalledWith(SESSION_ID, 7) }) - it('resets compaction and seeds the extracted cloned prefix on the fork target', () => { + it('resets compaction and seeds the extracted cloned prefix on the fork target', async () => { const { coordinator, deps, runtime } = createHarness() coordinator.resetForkTarget('target', 12) diff --git a/test/main/agent/invocationAdmission.test.ts b/test/main/agent/invocationAdmission.test.ts index 8576939ed7..52877f464a 100644 --- a/test/main/agent/invocationAdmission.test.ts +++ b/test/main/agent/invocationAdmission.test.ts @@ -1,574 +1,79 @@ import { describe, expect, it, vi } from 'vitest' import { - AgentInvocationAdmission, - AgentInvocationAdmissionAbortedError, - AgentInvocationAdmissionClosedError, - AgentInvocationAdmissionQueueFullError, - type AgentInvocationAdmissionObservation -} from '@/agent/invocationAdmission' -import { BoundedNumberRing } from '@/lib/boundedNumberRing' + normalizeAdmissionOwnerId, + normalizeAdmissionOwnerLimit, + snapshotAdmissionCorrelation +} from '@shared/harness/admission' +import { RutisAgentInvocationAdmission } from '@/harness/admission' -const CORRELATION = { +const correlation = { kind: 'live_delegation' as const, - parentSessionId: 'parent_1', - delegationId: 'delegation_1', - turnId: 'turn_1' + parentSessionId: 'parent', + delegationId: 'delegation', + turnId: 'turn' } -describe('AgentInvocationAdmission', () => { - it('retains recent diagnostic samples in insertion order after wrap-around', () => { - const samples = new BoundedNumberRing(3) - for (const value of [1, 2, 3, 4]) samples.push(value) - - expect(samples.snapshot()).toEqual([2, 3, 4]) - }) - - it('reserves process-wide headroom beyond one workflow owner', () => { - expect(new AgentInvocationAdmission().snapshot()).toMatchObject({ - capacity: 6, - active: 0, - pending: 0 - }) - }) - - it('reports invalid acquire options through the promised rejection contract', async () => { - const admission = new AgentInvocationAdmission() - let acquirePromise!: Promise - +describe('invocation admission client boundary', () => { + it('validates owner limits and rejects invalid acquisition through its Promise', async () => { + const request = vi.fn() + const admission = RutisAgentInvocationAdmission.create({ + runtime: { identity: undefined, request } + }) + expect(normalizeAdmissionOwnerId(' \ufeffowner\ufeff ')).toBe('owner') + expect(normalizeAdmissionOwnerLimit(20, 6)).toBe(6) + expect(() => normalizeAdmissionOwnerId('😀'.repeat(129))).toThrow(/256 characters/) + for (const limit of [0, -1, 1.5, Number.MAX_SAFE_INTEGER + 1]) { + await expect( + admission.acquire({ ownerId: 'owner', maxActiveForOwner: limit }) + ).rejects.toThrow(/positive safe integer/) + } + let acquisition!: Promise expect(() => { - acquirePromise = admission.acquire({ ownerId: ' ' }) + acquisition = admission.acquire({ ownerId: ' ' }) }).not.toThrow() - await expect(acquirePromise).rejects.toThrow('ownerId must contain 1 to 256 characters') - }) - - it('enforces capacity and schedules queued owners round-robin', async () => { - const admission = new AgentInvocationAdmission(1, 10) - const first = await admission.acquire({ ownerId: 'owner-a' }) - const order: string[] = [] - const queued = [ - admission.run({ ownerId: 'owner-a' }, async () => { - order.push('a1') - }), - admission.run({ ownerId: 'owner-a' }, async () => { - order.push('a2') - }), - admission.run({ ownerId: 'owner-b' }, async () => { - order.push('b1') - }), - admission.run({ ownerId: 'owner-b' }, async () => { - order.push('b2') - }) - ] - - expect(admission.snapshot()).toMatchObject({ - active: 1, - pending: 4, - pendingOwners: 2 - }) - first.release() - await Promise.all(queued) - - expect(order).toEqual(['a1', 'b1', 'a2', 'b2']) - expect(admission.snapshot()).toMatchObject({ active: 0, pending: 0 }) - }) - - it('enforces owner limits while allowing other owners to use global headroom', async () => { - const admission = new AgentInvocationAdmission(3, 10) - const first = await admission.acquire({ ownerId: 'workflow-a', maxActiveForOwner: 2 }) - const second = await admission.acquire({ ownerId: 'workflow-a', maxActiveForOwner: 2 }) - const queued = admission.acquire({ ownerId: 'workflow-a', maxActiveForOwner: 2 }) - const other = await admission.acquire({ - ownerId: 'orchestrator-b', - maxActiveForOwner: 5 - }) - - expect(admission.snapshot()).toMatchObject({ - capacity: 3, - active: 3, - pending: 1, - pendingOwners: 1 - }) - - other.release() - await Promise.resolve() - expect(admission.snapshot()).toMatchObject({ active: 2, pending: 1 }) - - first.release() - const queuedPermit = await queued - expect(admission.snapshot()).toMatchObject({ active: 2, pending: 0 }) - - second.release() - queuedPermit.release() - expect(admission.snapshot()).toMatchObject({ active: 0, pending: 0 }) - }) - - it('removes an aborted waiter without consuming the next permit', async () => { - const admission = new AgentInvocationAdmission(1, 10) - const first = await admission.acquire({ ownerId: 'active' }) - const controller = new AbortController() - const aborted = admission.acquire({ - ownerId: 'cancelled', - signal: controller.signal - }) - const next = admission.acquire({ ownerId: 'next' }) - - controller.abort() - await expect(aborted).rejects.toBeInstanceOf(AgentInvocationAdmissionAbortedError) - expect(admission.snapshot().pending).toBe(1) - - first.release() - const nextPermit = await next - expect(admission.snapshot()).toMatchObject({ active: 1, pending: 0 }) - nextPermit.release() - }) - - it('bounds queued work and rejects new work after close', async () => { - const admission = new AgentInvocationAdmission(1, 1) - const active = await admission.acquire({ ownerId: 'active' }) - const queued = admission.acquire({ ownerId: 'queued' }) - - await expect(admission.acquire({ ownerId: 'overflow' })).rejects.toBeInstanceOf( - AgentInvocationAdmissionQueueFullError - ) - const queuedRejection = expect(queued).rejects.toBeInstanceOf( - AgentInvocationAdmissionClosedError - ) - admission.close('Application is shutting down.') - await queuedRejection - await expect(admission.acquire({ ownerId: 'late' })).rejects.toThrow( - 'Application is shutting down.' - ) - - active.release() - active.release() - expect(admission.snapshot()).toMatchObject({ - active: 0, - pending: 0, - closed: true - }) - }) - - it('does not enter a task if its signal aborts before the resolved permit is observed', async () => { - const admission = new AgentInvocationAdmission(1, 1) - const controller = new AbortController() - const task = vi.fn(async () => undefined) - - const execution = admission.run( - { - ownerId: 'owner', - signal: controller.signal - }, - task - ) - controller.abort() - - await expect(execution).rejects.toBeInstanceOf(AgentInvocationAdmissionAbortedError) - expect(task).not.toHaveBeenCalled() - expect(admission.snapshot().active).toBe(0) - }) - - it('suspends and fairly reacquires a state-aware lease', async () => { - const admission = new AgentInvocationAdmission(1, 10) - const lease = admission.createLease({ ownerId: 'child-a' }) - - expect(lease.state).toBe('suspended') - await lease.resume() - expect(lease.state).toBe('active') - expect(admission.snapshot()).toMatchObject({ active: 1, pending: 0 }) - - const other = admission.acquire({ ownerId: 'child-b' }) - lease.suspend() - const otherPermit = await other - expect(lease.state).toBe('suspended') - expect(admission.snapshot()).toMatchObject({ active: 1, pending: 0 }) - - const resumed = lease.resume() - expect(admission.snapshot()).toMatchObject({ active: 1, pending: 1 }) - otherPermit.release() - await resumed - expect(lease.state).toBe('active') - expect(admission.snapshot()).toMatchObject({ active: 1, pending: 0 }) - - lease.release() - lease.release() - expect(lease.state).toBe('released') - expect(admission.snapshot()).toMatchObject({ active: 0, pending: 0 }) - }) - - it('keeps an active lease retryable when an accounting invariant rejects release', async () => { - const admission = new AgentInvocationAdmission(1, 1) - const lease = admission.createLease({ ownerId: 'owner' }) - await lease.resume() - const accounting = admission as unknown as { - active: number - activeByOwner: Map - } - - accounting.active = 0 - expect(() => lease.suspend()).toThrow('permit accounting underflow') - expect(lease.state).toBe('active') - accounting.active = 1 - lease.suspend() - expect(lease.state).toBe('suspended') - - await lease.resume() - accounting.activeByOwner.delete('owner') - expect(() => lease.release()).toThrow('owner permit accounting underflow') - expect(lease.state).toBe('active') - accounting.activeByOwner.set('owner', 1) - lease.release() - expect(lease.state).toBe('released') - expect(admission.snapshot().active).toBe(0) - }) - - it('cancels a queued lease when it is suspended or its owner is aborted', async () => { - const admission = new AgentInvocationAdmission(1, 10) - const active = await admission.acquire({ ownerId: 'active' }) - const suspendedLease = admission.createLease({ ownerId: 'suspended' }) - const suspendedResume = suspendedLease.resume() - - suspendedLease.suspend() - const retriedResume = suspendedLease.resume() - await expect(suspendedResume).rejects.toBeInstanceOf(AgentInvocationAdmissionAbortedError) - expect(admission.snapshot()).toMatchObject({ active: 1, pending: 1 }) - - active.release() - await retriedResume - expect(suspendedLease.state).toBe('active') - suspendedLease.suspend() - - const controller = new AbortController() - const abortedLease = admission.createLease({ ownerId: 'aborted', signal: controller.signal }) - const abortedResume = abortedLease.resume() - controller.abort() - await expect(abortedResume).rejects.toBeInstanceOf(AgentInvocationAdmissionAbortedError) - expect(abortedLease.state).toBe('suspended') - abortedLease.release() - - expect(admission.snapshot()).toMatchObject({ active: 0, pending: 0 }) - }) - - it('observes correlated queue wait and active hold intervals with bounded summaries', async () => { - let now = 0 - const observations: AgentInvocationAdmissionObservation[] = [] - const admission = new AgentInvocationAdmission(1, 10, { - now: () => now, - observe: (observation) => observations.push(observation) - }) - - const first = await admission.acquire({ ownerId: 'parent', correlation: CORRELATION }) - now = 5 - const secondPromise = admission.acquire({ ownerId: 'parent', correlation: CORRELATION }) - now = 10 - first.release() - const second = await secondPromise - now = 22 - second.release() - admission.close() - await admission.flushObservations() - - expect(observations.map(({ type }) => type)).toEqual([ - 'granted', - 'queued', - 'released', - 'granted', - 'released', - 'closed' - ]) - expect(observations[0]).toMatchObject({ acquisitionSeq: 1, waitMs: 0 }) - expect(observations[2]).toMatchObject({ - acquisitionSeq: 1, - holdMs: 10, - reason: 'permit_released' - }) - expect(observations[3]).toMatchObject({ acquisitionSeq: 2, waitMs: 5 }) - expect(observations[4]).toMatchObject({ acquisitionSeq: 2, holdMs: 12 }) - expect(observations[5]).toMatchObject({ - activeHighWater: 1, - pendingHighWater: 1, - granted: 2, - rejected: 0, - observationsDropped: 0, - waitMs: { samples: 2, p50: 0, p95: 5, max: 5 }, - holdMs: { samples: 2, p50: 10, p95: 12, max: 12 } - }) + await expect(acquisition).rejects.toThrow(/ownerId must contain/) + expect(request).not.toHaveBeenCalled() }) - it('segments lease hold timing across suspension and reacquisition', async () => { - let now = 0 - const observations: AgentInvocationAdmissionObservation[] = [] - const admission = new AgentInvocationAdmission(1, 10, { - now: () => now, - observe: (observation) => observations.push(observation) - }) - const lease = admission.createLease({ ownerId: 'parent', correlation: CORRELATION }) - - await lease.resume() - now = 4 - lease.suspend() - now = 6 - await lease.resume() - now = 10 - lease.release() - await admission.flushObservations() - + it('snapshots bounded correlations without evaluating accessors or Proxy traps', () => { + const getter = vi.fn(() => 'hostile') + const hostile = Object.defineProperty({ ...correlation }, 'parentSessionId', { get: getter }) + const trap = vi.fn() + expect(snapshotAdmissionCorrelation(hostile)).toBeUndefined() expect( - observations - .filter((observation) => observation.type === 'released') - .map((observation) => ({ - reason: observation.reason, - holdMs: observation.holdMs, - acquisitionSeq: observation.acquisitionSeq - })) - ).toEqual([ - { reason: 'lease_suspended', holdMs: 4, acquisitionSeq: 1 }, - { reason: 'lease_released', holdMs: 4, acquisitionSeq: 2 } - ]) + snapshotAdmissionCorrelation(new Proxy(correlation, { getOwnPropertyDescriptor: trap })) + ).toBeUndefined() + expect(getter).not.toHaveBeenCalled() + expect(trap).not.toHaveBeenCalled() + for (const key of ['parentSessionId', 'delegationId', 'turnId']) { + expect( + snapshotAdmissionCorrelation({ ...correlation, [key]: 'x'.repeat(257) }) + ).toBeUndefined() + } + const snapshot = snapshotAdmissionCorrelation(correlation) + expect(snapshot).toEqual(correlation) + expect(snapshot).not.toBe(correlation) }) - it('contains observation failures and classifies correlated rejections', async () => { - const observations: AgentInvocationAdmissionObservation[] = [] - const admission = new AgentInvocationAdmission(1, 1, { - observe: (observation) => { - observations.push(observation) - if (observation.type === 'queued') throw new Error('observer failed') - } + it('releases a known acquisition once when cancellation precedes a late grant', async () => { + const grant = Promise.withResolvers() + const request = vi.fn((method: string, _args: unknown[]) => + method === 'acquire' ? grant.promise : Promise.resolve(true) + ) + const admission = RutisAgentInvocationAdmission.create({ + runtime: { identity: undefined, request } }) - const active = await admission.acquire({ ownerId: 'active', correlation: CORRELATION }) const controller = new AbortController() - const queued = admission.acquire({ - ownerId: 'queued', - correlation: CORRELATION, - signal: controller.signal - }) - - await expect( - admission.acquire({ ownerId: 'overflow', correlation: CORRELATION }) - ).rejects.toBeInstanceOf(AgentInvocationAdmissionQueueFullError) + const acquisition = admission.acquire({ ownerId: 'owner', signal: controller.signal }) + const acquisitionId = request.mock.calls[0][1][0] controller.abort() - await expect(queued).rejects.toBeInstanceOf(AgentInvocationAdmissionAbortedError) - active.release() - await admission.flushObservations() - - expect( - observations - .filter((observation) => observation.type === 'rejected') - .map((observation) => observation.reason) - ).toEqual(['queue_full', 'aborted']) - expect( - observations.find( - (observation) => observation.type === 'rejected' && observation.reason === 'queue_full' - ) - ).toMatchObject({ waitMs: 0 }) - expect(admission.snapshot()).toMatchObject({ active: 0, pending: 0, rejected: 2 }) - }) - - it('includes an abandoned queued interval in wait diagnostics', async () => { - let now = 0 - const admission = new AgentInvocationAdmission(1, 1, { now: () => now }) - const active = await admission.acquire({ ownerId: 'active' }) - const controller = new AbortController() - now = 5 - const queued = admission.acquire({ ownerId: 'queued', signal: controller.signal }) - - now = 105 - controller.abort() - await expect(queued).rejects.toBeInstanceOf(AgentInvocationAdmissionAbortedError) - - expect(admission.snapshot().waitMs).toEqual({ samples: 2, p50: 0, p95: 100, max: 100 }) - active.release() - }) - - it('does not run hostile correlation accessors or Proxy traps', async () => { - const observe = vi.fn() - const admission = new AgentInvocationAdmission(1, 1, { observe }) - let getterCalls = 0 - const correlation = { ...CORRELATION } - Object.defineProperty(correlation, 'parentSessionId', { - get() { - getterCalls += 1 - admission.close('Accessor side effect') - return 'hostile_parent' - } - }) - - const permit = await admission.acquire({ ownerId: 'owner', correlation }) - expect(getterCalls).toBe(0) - expect(admission.snapshot().active).toBe(1) - permit.release() - - let proxyTrapCalls = 0 - const proxyCorrelation = new Proxy(CORRELATION, { - getOwnPropertyDescriptor() { - proxyTrapCalls += 1 - return undefined - } - }) - const proxyPermit = await admission.acquire({ - ownerId: 'owner', - correlation: proxyCorrelation - }) - expect(proxyTrapCalls).toBe(0) - proxyPermit.release() - await admission.flushObservations() - - expect(admission.snapshot().active).toBe(0) - expect(observe).not.toHaveBeenCalled() - }) - - it.each(['parentSessionId', 'delegationId', 'turnId'] as const)( - 'drops correlation snapshots with an oversized %s', - async (field) => { - const observe = vi.fn() - const admission = new AgentInvocationAdmission(1, 1, { observe }) - const permit = await admission.acquire({ - ownerId: 'owner', - correlation: { ...CORRELATION, [field]: 'x'.repeat(257) } - }) - - permit.release() - await admission.flushObservations() - - expect(observe).not.toHaveBeenCalled() - } - ) - - it('dispatches permits before draining potentially slow observations', async () => { - const observe = vi.fn() - const admission = new AgentInvocationAdmission(1, 1, { observe }) - const first = await admission.acquire({ ownerId: 'first', correlation: CORRELATION }) - const queued = admission.acquire({ ownerId: 'second', correlation: CORRELATION }) - - first.release() - const second = await queued - - expect(observe).not.toHaveBeenCalled() - second.release() - await admission.flushObservations() - expect(observe).toHaveBeenCalled() - }) - - it('skips observation queue work while diagnostics output is disabled', async () => { - let observationsEnabled = false - let now = 0 - const observe = vi.fn() - const admission = new AgentInvocationAdmission(1, 1, { - observe, - observationsEnabled: () => observationsEnabled, - now: () => now - }) - - const disabledPermit = await admission.acquire({ - ownerId: 'disabled', - correlation: CORRELATION - }) - now = 10 - disabledPermit.release() - await admission.flushObservations() - expect(observe).not.toHaveBeenCalled() - expect(admission.snapshot()).toMatchObject({ - waitMs: { samples: 1, p50: 0, p95: 0, max: 0 }, - holdMs: { samples: 1, p50: 10, p95: 10, max: 10 } + grant.resolve(acquisitionId) + await expect(acquisition).rejects.toMatchObject({ + name: 'AgentInvocationAdmissionAbortedError' }) - - observationsEnabled = true - const enabledPermit = await admission.acquire({ ownerId: 'enabled', correlation: CORRELATION }) - enabledPermit.release() await admission.flushObservations() - expect(observe.mock.calls.map(([observation]) => observation.type)).toEqual([ - 'granted', - 'released' + expect(request.mock.calls.filter(([method]) => method === 'release')).toEqual([ + ['release', [acquisitionId, 'permit_released', false]] ]) }) - - it('omits intervals with invalid or backward clock endpoints', async () => { - const measure = async (clockValues: number[]) => { - const observations: AgentInvocationAdmissionObservation[] = [] - let clockIndex = 0 - const admission = new AgentInvocationAdmission(1, 1, { - now: () => clockValues[clockIndex++], - observe: (observation) => observations.push(observation) - }) - const permit = await admission.acquire({ ownerId: 'owner', correlation: CORRELATION }) - permit.release() - await admission.flushObservations() - return { observations, snapshot: admission.snapshot() } - } - - const invalidStart = await measure([Number.NaN, 100, 110]) - expect(invalidStart.snapshot.waitMs.samples).toBe(0) - expect(invalidStart.snapshot.holdMs).toEqual({ samples: 1, p50: 10, p95: 10, max: 10 }) - expect(invalidStart.observations[0]).not.toHaveProperty('waitMs') - - const invalidGrant = await measure([100, Number.NaN, 110]) - expect(invalidGrant.snapshot.waitMs.samples).toBe(0) - expect(invalidGrant.snapshot.holdMs.samples).toBe(0) - expect(invalidGrant.observations[0]).not.toHaveProperty('waitMs') - expect(invalidGrant.observations[1]).not.toHaveProperty('holdMs') - - const invalidRelease = await measure([100, 100, Number.NaN]) - expect(invalidRelease.snapshot.waitMs.samples).toBe(1) - expect(invalidRelease.snapshot.holdMs.samples).toBe(0) - expect(invalidRelease.observations[0]).toMatchObject({ waitMs: 0 }) - expect(invalidRelease.observations[1]).not.toHaveProperty('holdMs') - - const rollback = await measure([100, 90, 80]) - expect(rollback.snapshot.waitMs.samples).toBe(0) - expect(rollback.snapshot.holdMs.samples).toBe(0) - expect(rollback.observations[0]).not.toHaveProperty('waitMs') - expect(rollback.observations[1]).not.toHaveProperty('holdMs') - }) - - it('reports correlated close rejections once before its terminal summary', async () => { - const observations: AgentInvocationAdmissionObservation[] = [] - const admission = new AgentInvocationAdmission(1, 2, { - observe: (observation) => observations.push(observation) - }) - const active = await admission.acquire({ ownerId: 'active', correlation: CORRELATION }) - const firstQueued = admission.acquire({ ownerId: 'first', correlation: CORRELATION }) - const secondQueued = admission.acquire({ ownerId: 'second', correlation: CORRELATION }) - - admission.close() - await expect(firstQueued).rejects.toBeInstanceOf(AgentInvocationAdmissionClosedError) - await expect(secondQueued).rejects.toBeInstanceOf(AgentInvocationAdmissionClosedError) - await admission.flushObservations() - - expect( - observations - .filter((observation) => observation.type === 'rejected') - .map((observation) => observation.reason) - ).toEqual(['closed', 'closed']) - expect(observations.at(-1)).toMatchObject({ - type: 'closed', - active: 1, - pending: 0, - rejected: 2 - }) - - active.release() - await admission.flushObservations() - expect(admission.snapshot()).toMatchObject({ active: 0, pending: 0, rejected: 2 }) - }) - - it('bounds pending observations and reports discarded diagnostics', async () => { - const observations: AgentInvocationAdmissionObservation[] = [] - const admission = new AgentInvocationAdmission(1, 1, { - observe: (observation) => observations.push(observation) - }) - - for (let iteration = 0; iteration < 300; iteration += 1) { - const permit = await admission.acquire({ ownerId: 'owner', correlation: CORRELATION }) - permit.release() - } - admission.close() - await admission.flushObservations() - - expect(observations).toHaveLength(512) - expect(admission.snapshot().observationsDropped).toBe(89) - expect(admission.snapshot()).toMatchObject({ - waitMs: { samples: 256 }, - holdMs: { samples: 256 } - }) - expect(observations.at(-1)).toMatchObject({ type: 'closed', observationsDropped: 89 }) - }) }) diff --git a/test/main/agent/manager/deepChatAgentBackend.test.ts b/test/main/agent/manager/deepChatAgentBackend.test.ts index c0f288cbba..36bd877a1e 100644 --- a/test/main/agent/manager/deepChatAgentBackend.test.ts +++ b/test/main/agent/manager/deepChatAgentBackend.test.ts @@ -2,7 +2,10 @@ import { describe, expect, it, vi } from 'vitest' import type { DeepChatAgentBackendPort } from '@/agent/manager/deepChatAgentBackend' import { DeepChatAgentRuntime } from '@/agent/deepchat/instance/deepChatAgentRuntime' import { toAppSessionId } from '@/agent/shared/agentSessionIds' -import { createDeepChatAgentBackendFixture } from './deepChatAgentBackendFixture' +import { + createBackendControl, + createDeepChatAgentBackendFixture +} from './deepChatAgentBackendFixture' const createPort = (): DeepChatAgentBackendPort => { const port: DeepChatAgentBackendPort = { @@ -122,7 +125,7 @@ describe('DeepChatAgentBackend', () => { it('releases its own runtime instance when durable teardown fails', async () => { const port = createPort() - const runtime = new DeepChatAgentRuntime() + const runtime = new DeepChatAgentRuntime(createBackendControl()) const backend = createDeepChatAgentBackendFixture(port, runtime) const sessionId = toAppSessionId('session') const handle = backend.open(sessionId) @@ -137,7 +140,7 @@ describe('DeepChatAgentBackend', () => { it('leaves a replacement runtime instance alone when a stale handle closes', async () => { const port = createPort() - const runtime = new DeepChatAgentRuntime() + const runtime = new DeepChatAgentRuntime(createBackendControl()) const backend = createDeepChatAgentBackendFixture(port, runtime) const sessionId = toAppSessionId('session') const handle = backend.open(sessionId) diff --git a/test/main/agent/manager/deepChatAgentBackendFixture.ts b/test/main/agent/manager/deepChatAgentBackendFixture.ts index f556381724..5bc8558f8d 100644 --- a/test/main/agent/manager/deepChatAgentBackendFixture.ts +++ b/test/main/agent/manager/deepChatAgentBackendFixture.ts @@ -1,3 +1,4 @@ +import { useHarnessControl } from '../deepchat/helpers/control' import { DeepChatAgentRuntime } from '@/agent/deepchat/instance/deepChatAgentRuntime' import { createDeepChatAgentBackend, @@ -5,6 +6,8 @@ import { } from '@/agent/manager/deepChatAgentBackend' import type { SessionTapePort, SessionTranscriptReadPort } from '@/session/data/contracts' +export const createBackendControl = useHarnessControl() + export function createDeepChatAgentBackendFixture( port: DeepChatAgentBackendPort, providedRuntime?: DeepChatAgentRuntime, @@ -24,7 +27,7 @@ export function createDeepChatAgentBackendFixture( } } ) { - const runtime = providedRuntime ?? new DeepChatAgentRuntime() + const runtime = providedRuntime ?? new DeepChatAgentRuntime(createBackendControl()) return createDeepChatAgentBackend({ port, runtime, ...data }) } diff --git a/test/main/agent/settings.test.ts b/test/main/agent/settings.test.ts index 403e0cb701..ac0152af1f 100644 --- a/test/main/agent/settings.test.ts +++ b/test/main/agent/settings.test.ts @@ -4,6 +4,7 @@ import { AgentLifecycleGate } from '@/agent/lifecycleGate' import { BUILTIN_DEEPCHAT_AGENT_ID } from '@/agent/repository' import { CRON_JOB_AGENT_TOOL_NAME } from '@shared/agentTools' import type { CreateDeepChatAgentInput } from '@shared/types/agent-interface' +import type { AcpAgentInstallState } from '@shared/types/acp' const createModelSelection = (providerId: string, modelId: string) => ({ providerId, modelId }) @@ -18,6 +19,68 @@ const createDeferred = () => { } describe('AgentSettings ACP availability', () => { + it.each(['registry', 'provider'])( + 'drains the pending %s operation and fences database/events after registry stop', + async (phase) => { + const entered = createDeferred() + const previous = [{ id: 'agent-1', name: 'Agent', version: '1' }] + const updated = [{ ...previous[0], version: '2' }] + const pending = createDeferred() + const syncRegistryAgentsToRepository = vi.fn() + const notifyAcpAgentsChanged = vi.fn() + const listAcpRegistryAgents = vi.fn(async () => updated) + const registry = { + isStopped: false, + listAgents: () => previous, + refresh: vi.fn(async () => { + if (phase === 'registry') { + entered.resolve() + return pending.promise + } + return updated + }), + stop: () => { + registry.isStopped = true + }, + drain: vi.fn(async () => undefined) + } + const settings = Object.assign(Object.create(AgentSettings.prototype), { + registry, + launchSpecs: { + stop: vi.fn(), + cancelPendingInstallations: vi.fn(), + drain: vi.fn(async () => undefined) + }, + provider: { + refreshAcpProviderAgents: vi.fn(async () => { + entered.resolve() + await pending.promise + }) + }, + syncRegistryAgentsToRepository, + listAcpRegistryAgents, + notifyAcpAgentsChanged + }) as AgentSettings + const rejected = expect(settings.refreshAcpRegistry(true)).rejects.toMatchObject({ + name: 'AbortError' + }) + await entered.promise + const writes = syncRegistryAgentsToRepository.mock.calls.length + settings.stop() + let drained = false + const drain = settings.drain().then(() => { + drained = true + }) + await Promise.resolve() + expect(drained).toBe(false) + pending.resolve(updated) + await Promise.all([rejected, drain]) + expect(syncRegistryAgentsToRepository).toHaveBeenCalledTimes(writes) + expect(listAcpRegistryAgents).not.toHaveBeenCalled() + expect(notifyAcpAgentsChanged).not.toHaveBeenCalled() + } + ) + it('restores the previous state when disabling cannot stop active runtimes', async () => { const runtimeError = new Error('runtime busy') const setGlobalEnabled = vi.fn().mockReturnValue(true) @@ -113,6 +176,260 @@ describe('AgentSettings ACP registry uninstall', () => { }) }) +describe('AgentSettings ACP installation ownership', () => { + const createOwner = () => { + const agent = { id: 'owned-agent', name: 'Owned agent', version: '1', distribution: {} } + const previous: AcpAgentInstallState = { + status: 'installed', + version: '1', + distributionType: 'binary', + installDir: '/managed/agent', + installedAt: 1, + lastCheckedAt: 1, + error: null + } + let state: AcpAgentInstallState | null = structuredClone(previous) + let allowed = true + const registry = { + isStopped: false, + getAgent: () => agent, + stop: () => { + registry.isStopped = true + }, + drain: async () => undefined + } + const launchSpecs = { + selectRegistryDistribution: () => ({ type: 'binary' }), + ensureRegistryAgentInstalled: vi.fn(async () => structuredClone(previous)), + cancelPendingInstallations: vi.fn(), + stop: vi.fn(), + drain: async () => undefined + } + const provider = { + refreshAcpProviderAgents: vi.fn(async () => undefined), + clearAcpProviderModelStatus: vi.fn() + } + const repository = { + getAgentInstallState: () => (state === null ? null : structuredClone(state)), + setAgentInstallState: vi.fn((_id: string, value: AcpAgentInstallState | null) => { + state = structuredClone(value) + return true + }), + setAgentEnabled: vi.fn(), + hasAgentSessions: () => false, + clearRegistryAcpAgentInstallation: vi.fn((_id: string, value: AcpAgentInstallState) => { + state = value + return true + }) + } + const settings = Object.assign(Object.create(AgentSettings.prototype), { + registry, + repository, + provider, + launchSpecs, + assertAcpOperationAllowed: () => { + if (!allowed) throw new Error('maintenance') + }, + getRegistryAgentOrThrow: () => structuredClone(agent), + notifyAcpAgentsChanged: vi.fn() + }) as AgentSettings + return { + settings, + registry, + repository, + provider, + launchSpecs, + agent, + previous, + readState: () => state, + setAllowed: (value: boolean) => { + allowed = value + } + } + } + + it.each(['installer', 'provider'])( + 'drains a canceled %s continuation and restores acknowledged state', + async (phase) => { + const owner = createOwner() + const entered = createDeferred() + const release = createDeferred() + if (phase === 'installer') { + owner.launchSpecs.ensureRegistryAgentInstalled.mockImplementation(async () => { + entered.resolve() + await release.promise + return { ...owner.previous, installedAt: 2 } + }) + } else { + owner.provider.refreshAcpProviderAgents.mockImplementation(async () => { + entered.resolve() + await release.promise + }) + } + const rejected = expect(owner.settings.repairAcpAgent(owner.agent.id)).rejects.toMatchObject({ + name: 'AbortError' + }) + await entered.promise + expect(owner.readState()?.status).toBe('installing') + owner.settings.stop() + let drained = false + const drain = owner.settings.drain().then(() => { + drained = true + }) + await Promise.resolve() + expect(drained).toBe(false) + release.resolve() + await Promise.all([rejected, drain]) + expect(owner.readState()).toEqual(owner.previous) + await expect(owner.settings.ensureAcpAgentInstalled(owner.agent.id)).rejects.toMatchObject({ + name: 'AbortError' + }) + expect(owner.readState()).toEqual(owner.previous) + } + ) + + it('coalesces installation and recovers after maintenance without publishing late state', async () => { + const owner = createOwner() + const entered = createDeferred() + const release = createDeferred() + owner.launchSpecs.ensureRegistryAgentInstalled.mockImplementation(async () => { + entered.resolve() + await release.promise + return { ...owner.previous, installedAt: 2 } + }) + const first = expect( + owner.settings.ensureAcpAgentInstalled(owner.agent.id) + ).rejects.toMatchObject({ name: 'AbortError' }) + const second = expect( + owner.settings.ensureAcpAgentInstalled(owner.agent.id) + ).rejects.toMatchObject({ name: 'AbortError' }) + await entered.promise + expect(owner.launchSpecs.ensureRegistryAgentInstalled).toHaveBeenCalledOnce() + owner.setAllowed(false) + const drain = owner.settings.cancelAndDrainAcpWork() + release.resolve() + await Promise.all([first, second, drain]) + expect(owner.readState()).toEqual(owner.previous) + await expect(owner.settings.ensureAcpAgentInstalled(owner.agent.id)).rejects.toThrow( + 'maintenance' + ) + owner.setAllowed(true) + await expect(owner.settings.ensureAcpAgentInstalled(owner.agent.id)).resolves.toMatchObject({ + status: 'installed', + installedAt: 2 + }) + await owner.settings.drain() + }) + + it('holds uninstall admission while canceled installation continuations are settling', async () => { + const owner = createOwner() + const entered = createDeferred() + const release = createDeferred() + owner.launchSpecs.ensureRegistryAgentInstalled.mockImplementation(async () => { + entered.resolve() + await release.promise + return owner.previous + }) + Object.assign(owner.launchSpecs, { uninstallRegistryAgent: vi.fn(async () => undefined) }) + const canceled = expect( + owner.settings.ensureAcpAgentInstalled(owner.agent.id) + ).rejects.toMatchObject({ name: 'AbortError' }) + await entered.promise + const uninstall = owner.settings.uninstallAcpRegistryAgent(owner.agent.id) + await expect(owner.settings.ensureAcpAgentInstalled(owner.agent.id)).rejects.toThrow( + 'uninstall is in progress' + ) + let drained = false + const drain = owner.settings.drain().then(() => { + drained = true + }) + expect(drained).toBe(false) + release.resolve() + await Promise.all([canceled, uninstall, drain]) + expect(owner.readState()?.status).toBe('not_installed') + }) + + it('acknowledges enable promptly while retaining background install and process ownership', async () => { + const owner = createOwner() + const entered = createDeferred() + const installed = createDeferred() + const refreshed = createDeferred() + owner.launchSpecs.ensureRegistryAgentInstalled.mockImplementation(async () => { + entered.resolve() + await installed.promise + return owner.previous + }) + owner.provider.refreshAcpProviderAgents.mockImplementation(async () => { + await refreshed.promise + }) + await owner.settings.setAcpAgentEnabled(owner.agent.id, true) + await entered.promise + owner.settings.stop() + let drained = false + const drain = owner.settings.drain().then(() => { + drained = true + }) + installed.resolve() + await Promise.resolve() + expect(drained).toBe(false) + refreshed.resolve() + await drain + expect(owner.readState()).toEqual(owner.previous) + }) + + it('rejects an obsolete descriptor without acknowledging its installation result', async () => { + const owner = createOwner() + const entered = createDeferred() + const release = createDeferred() + owner.launchSpecs.ensureRegistryAgentInstalled.mockImplementation(async () => { + entered.resolve() + await release.promise + return { ...owner.previous, installedAt: 2 } + }) + const rejected = expect( + owner.settings.ensureAcpAgentInstalled(owner.agent.id) + ).rejects.toMatchObject({ name: 'AbortError' }) + await entered.promise + owner.agent.version = '2' + release.resolve() + await rejected + expect(owner.readState()).toEqual(owner.previous) + await owner.settings.drain() + }) + + it('fences the repository continuation of implicit binary launch preparation', async () => { + const owner = createOwner() + const entered = createDeferred() + const release = createDeferred() + Object.assign(owner.repository, { getManualAcpAgent: () => null }) + Object.assign(owner.launchSpecs, { + resolveRegistryLaunchSpec: async () => { + entered.resolve() + await release.promise + return { + agentId: owner.agent.id, + source: 'registry', + distributionType: 'binary', + command: '/managed/agent/binary', + args: [], + env: {}, + installDir: '/managed/agent' + } + } + }) + const rejected = expect( + owner.settings.resolveAcpLaunchSpec(owner.agent.id) + ).rejects.toMatchObject({ name: 'AbortError' }) + await entered.promise + owner.settings.stop() + const drain = owner.settings.drain() + release.resolve() + await Promise.all([rejected, drain]) + expect(owner.repository.setAgentInstallState).not.toHaveBeenCalled() + expect(owner.readState()).toEqual(owner.previous) + }) +}) + describe('AgentSettings migrations', () => { it('repairs legacy selections before materializing version 3 configs', () => { const sequence: string[] = [] @@ -713,6 +1030,8 @@ describe('AgentSettings ACP notifications', () => { it('publishes catalog, model, and session changes before refreshing processes', async () => { const sequence: string[] = [] const settings = Object.assign(Object.create(AgentSettings.prototype), { + registry: { isStopped: false }, + assertAcpOperationAllowed: () => undefined, provider: { clearAcpProviderModelStatus: vi.fn(() => sequence.push('cache-clear')), refreshAcpProviderAgents: vi.fn(async () => sequence.push('process-refresh')) @@ -773,6 +1092,7 @@ describe('AgentSettings ACP notifications', () => { const refreshed = [{ ...previous[0], description: 'After' }, previous[1]] const refreshAcpProviderAgents = vi.fn(async () => undefined) const settings = Object.assign(Object.create(AgentSettings.prototype), { + launchSpecs: { cancelPendingInstallations: vi.fn() }, registry: { listAgents: vi.fn(() => previous), refresh: vi.fn(async () => refreshed) diff --git a/test/main/agent/shared/process/backgroundExecService.test.ts b/test/main/agent/shared/process/backgroundExecService.test.ts new file mode 100644 index 0000000000..6620b9a066 --- /dev/null +++ b/test/main/agent/shared/process/backgroundExecService.test.ts @@ -0,0 +1,209 @@ +import { EventEmitter } from 'node:events' +import { describe, expect, it, vi } from 'vitest' +import { + BackgroundExecService, + type BackgroundExecRpcRequest +} from '@/agent/shared/process/backgroundExecSessionManager' +import { ResolvedCommandShellSchema } from '@shared/commandShell' + +const cleanup = vi.hoisted(() => vi.fn(async () => undefined)) +vi.mock('@/skill/skillExecutionPackageTree', () => ({ + cleanupOwnedSkillExecutionPackageTree: cleanup +})) + +const deferred = () => { + let resolve!: (value: T) => void + const promise = new Promise((settle) => { + resolve = settle + }) + return { promise, resolve } +} + +function helper() { + const host = Object.assign(new EventEmitter(), { + pid: 123, + kill: vi.fn(async () => {}), + postMessage: vi.fn<(request: BackgroundExecRpcRequest) => void>(), + onMessage(listener: (message: unknown) => void) { + host.on('message', listener) + }, + onExit(listener: (code: number) => void) { + host.on('exit', listener) + }, + onError(listener: (type: string, location: string) => void) { + host.on('error', listener) + } + }) + host.postMessage.mockImplementation((request) => + queueMicrotask(() => + host.emit('message', { + type: 'background-exec:response', + id: request.id, + ok: true, + data: request.method === 'start' ? { sessionId: request.id, status: 'running' } : undefined + }) + ) + ) + return host +} + +const options = { + commandShell: ResolvedCommandShellSchema.parse({ + profile: 'posix', + dialect: 'posix', + pathStyle: 'native', + executable: '/bin/sh', + args: ['-c'], + displayName: 'sh' + }) +} +const ownedTree = { + schemaVersion: 1 as const, + rootPath: '/managed/package', + ownershipToken: 'owned', + packageHash: 'hash', + files: [] +} + +describe('Background execution ownership', () => { + it('captures command overrides and environment deletion across a reused helper', async () => { + const host = helper() + const owner = new BackgroundExecService(async () => host) + vi.stubEnv('DEEPCHAT_COMMAND_ENV', 'core-value') + try { + await owner.start('a', 'first', '/workspace', { + ...options, + env: { DEEPCHAT_COMMAND_OVERRIDE: 'command-value' } + }) + delete process.env.DEEPCHAT_COMMAND_ENV + await owner.start('b', 'second', '/workspace', options) + const starts = host.postMessage.mock.calls + .map(([request]) => request) + .filter((request) => request.method === 'start') + expect((starts[0].args[3] as { env: Record }).env).toMatchObject({ + DEEPCHAT_COMMAND_ENV: 'core-value', + DEEPCHAT_COMMAND_OVERRIDE: 'command-value' + }) + expect((starts[1].args[3] as { env: Record }).env).not.toHaveProperty( + 'DEEPCHAT_COMMAND_ENV' + ) + } finally { + vi.unstubAllEnvs() + await owner.shutdown() + } + }) + + it('shares pending helper startup and rejects terminal restart', async () => { + const ready = deferred>() + const spawn = vi.fn(() => ready.promise) + const owner = new BackgroundExecService(spawn) + const first = owner.start('a', 'first', '/workspace', options) + const second = owner.start('b', 'second', '/workspace', options) + await vi.waitFor(() => expect(spawn).toHaveBeenCalledOnce()) + const host = helper() + ready.resolve(host) + const sessions = await Promise.all([first, second]) + expect(sessions[0].sessionId).not.toBe(sessions[1].sessionId) + const retirement = deferred() + host.kill.mockImplementationOnce(() => retirement.promise) + let stopped = false + const stopping = owner.shutdown().then(() => { + stopped = true + }) + await vi.waitFor(() => expect(host.kill).toHaveBeenCalledOnce()) + expect(stopped).toBe(false) + retirement.resolve() + await stopping + await expect(owner.start('c', 'late', '/workspace', options)).rejects.toMatchObject({ + name: 'AbortError' + }) + await expect(owner.list('a')).rejects.toMatchObject({ name: 'AbortError' }) + expect(spawn).toHaveBeenCalledOnce() + }) + + it('waits for a late uncancelable spawn and package cleanup without posting ownership', async () => { + const ready = deferred>() + const cleaned = deferred() + cleanup.mockImplementationOnce(async () => { + await cleaned.promise + }) + let signal: AbortSignal | undefined + const owner = new BackgroundExecService(async (value) => { + signal = value + return ready.promise + }) + const rejected = expect( + owner.start('a', 'late', '/workspace', { + ...options, + ownedSkillExecutionPackageTree: ownedTree + }) + ).rejects.toMatchObject({ name: 'AbortError' }) + await vi.waitFor(() => expect(signal).toBeDefined()) + let stopped = false + const stop = owner.shutdown().then(() => { + stopped = true + }) + expect(signal?.aborted).toBe(true) + expect(stopped).toBe(false) + const host = helper() + ready.resolve(host) + await vi.waitFor(() => expect(cleanup).toHaveBeenCalled()) + expect(stopped).toBe(false) + expect(host.kill).toHaveBeenCalledOnce() + expect(host.postMessage).not.toHaveBeenCalled() + cleaned.resolve() + await Promise.all([rejected, stop]) + }) + + it('drains a held authority callback and fences dispatch after shutdown', async () => { + const host = helper() + const entered = deferred(), + release = deferred() + const owner = new BackgroundExecService(async () => host) + const rejected = expect( + owner.start('a', 'guarded', '/workspace', options, async () => { + entered.resolve() + await release.promise + }) + ).rejects.toMatchObject({ name: 'AbortError' }) + await entered.promise + let stopped = false + const stop = owner.shutdown().then(() => { + stopped = true + }) + await vi.waitFor(() => expect(host.kill).toHaveBeenCalledOnce()) + expect(stopped).toBe(false) + expect(host.postMessage.mock.calls.map(([request]) => request.method)).toEqual(['shutdown']) + release.resolve() + await Promise.all([rejected, stop]) + }) + + it('ignores an obsolete host exit after recovery starts a replacement', async () => { + const old = helper(), + current = helper() + const spawn = vi.fn().mockResolvedValueOnce(old).mockResolvedValueOnce(current) + const owner = new BackgroundExecService(spawn) + await owner.start('a', 'first', '/workspace', options) + const retirement = deferred() + old.kill.mockImplementationOnce(() => retirement.promise) + old.emit('exit', 1) + const opening = owner.start('a', 'second', '/workspace', options) + await Promise.resolve() + expect(spawn).toHaveBeenCalledOnce() + retirement.resolve() + const next = await opening + old.emit('exit', 2) + old.emit('message', { + type: 'background-exec:response', + id: next.sessionId, + ok: true, + data: 'obsolete' + }) + await owner.kill('a', next.sessionId) + expect(current.postMessage.mock.calls.map(([request]) => request.method)).toEqual([ + 'start', + 'kill' + ]) + await owner.shutdown() + }) +}) diff --git a/test/main/agent/shared/process/backgroundExecSessionManager.test.ts b/test/main/agent/shared/process/backgroundExecSessionManager.test.ts index aa4aaead6e..9053b5e868 100644 --- a/test/main/agent/shared/process/backgroundExecSessionManager.test.ts +++ b/test/main/agent/shared/process/backgroundExecSessionManager.test.ts @@ -1,17 +1,17 @@ -import { EventEmitter } from 'events' +import { EventEmitter, once } from 'events' import type { ChildProcess } from 'child_process' import { spawn } from 'child_process' import fs from 'fs' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' const { - mockUtilityProcessFork, + mockSpawnHost, mockAssertPackageTree, mockCleanupPackageTree, mockTerminateProcessTree, registryMock } = vi.hoisted(() => ({ - mockUtilityProcessFork: vi.fn(), + mockSpawnHost: vi.fn(), mockAssertPackageTree: vi.fn(), mockCleanupPackageTree: vi.fn(), mockTerminateProcessTree: vi.fn(), @@ -26,22 +26,6 @@ vi.mock('child_process', () => ({ spawn: vi.fn() })) -vi.mock('electron', () => ({ - app: { - getAppPath: vi.fn(() => '/mock/app'), - getPath: vi.fn((name: string) => (name === 'userData' ? '/mock/userData' : '/mock/home')) - }, - utilityProcess: { - fork: mockUtilityProcessFork - } -})) - -vi.mock('@electron-toolkit/utils', () => ({ - is: { - dev: false - } -})) - vi.mock('@shared/logger', () => ({ default: { info: vi.fn(), @@ -55,7 +39,8 @@ vi.mock('@/skill/skillExecutionPackageTree', () => ({ cleanupOwnedSkillExecutionPackageTree: mockCleanupPackageTree })) -vi.mock('@/agent/shared/process/processTree', () => ({ +vi.mock('@/agent/shared/process/processTree', async (importOriginal) => ({ + ...(await importOriginal()), terminateProcessTree: mockTerminateProcessTree })) @@ -65,8 +50,9 @@ vi.mock('@/agent/shared/process/childProcessRegistry', () => ({ import { BackgroundExecSessionManager, - backgroundExecSessionManager + BackgroundExecService } from '@/agent/shared/process/backgroundExecSessionManager' +import type { ObservedProcessIdentity } from '@/agent/shared/process/childProcessRegistry' import { CMD_COMMAND_SHELL, POSIX_COMMAND_SHELL, @@ -87,12 +73,24 @@ class MockChildProcess extends EventEmitter { destroyed: false } pid = 321 + spawnargs: string[] = [] + exitCode: number | null = null + signalCode: NodeJS.Signals | null = null unref = vi.fn() } class MockUtilityProcess extends EventEmitter { + onMessage(listener: (message: unknown) => void): void { + this.on('message', listener) + } + onExit(listener: (code: number) => void): void { + this.on('exit', listener) + } + onError(listener: (type: string, location: string) => void): void { + this.on('error', listener) + } postMessage = vi.fn() - kill = vi.fn() + kill = vi.fn(async () => {}) } function mockStats(kind: 'file' | 'directory'): fs.Stats { @@ -132,15 +130,19 @@ describe('BackgroundExecSessionManager', () => { beforeEach(() => { manager = new BackgroundExecSessionManager() clearInterval((manager as never).cleanupIntervalId) - mockUtilityProcessFork.mockReset() - mockAssertPackageTree.mockResolvedValue(undefined) - mockCleanupPackageTree.mockResolvedValue(undefined) - mockTerminateProcessTree.mockResolvedValue(true) - registryMock.record.mockReset() + vi.mocked(spawn).mockReset() + mockSpawnHost.mockReset() + mockAssertPackageTree.mockReset().mockResolvedValue(undefined) + mockCleanupPackageTree.mockReset().mockResolvedValue(undefined) + mockTerminateProcessTree.mockReset().mockResolvedValue(true) + registryMock.record.mockReset().mockResolvedValue({ alive: true }) registryMock.clear.mockReset() vi.spyOn(fs, 'existsSync').mockReturnValue(true) vi.spyOn(fs, 'statSync').mockImplementation((candidate) => - String(candidate).includes('workspace') ? mockStats('directory') : mockStats('file') + String(candidate).includes('workspace') || + normalizedPath(candidate) === normalizedPath(process.cwd()) + ? mockStats('directory') + : mockStats('file') ) vi.spyOn(fs, 'accessSync').mockReturnValue(undefined) }) @@ -254,6 +256,8 @@ describe('BackgroundExecSessionManager', () => { it('terminates a background process when its output byte limit is exceeded', async () => { const child = new MockChildProcess() vi.mocked(spawn).mockReturnValue(child as never) + const nativeIdentity = 'darwin:00000000-0000-0000-0000-000000000000:42' + registryMock.record.mockResolvedValueOnce({ alive: true, nativeIdentity }) mockTerminateProcessTree.mockImplementationOnce(async () => { child.emit('close', 0, null) return true @@ -267,7 +271,12 @@ describe('BackgroundExecSessionManager', () => { child.stdout.emit('data', Buffer.from('12345678')) child.stdout.emit('data', Buffer.from('discarded')) - await vi.waitFor(() => expect(mockTerminateProcessTree).toHaveBeenCalledOnce()) + await vi.waitFor(() => + expect(mockTerminateProcessTree).toHaveBeenCalledExactlyOnceWith(child, { + graceMs: 2000, + nativeIdentity + }) + ) const result = await manager.getCompletionResult('conv-1', started.sessionId, 500) expect(result.status).toBe('killed') @@ -353,15 +362,201 @@ describe('BackgroundExecSessionManager', () => { timeout: 0 }) - expect(registryMock.record).toHaveBeenCalledWith({ - subsystem: 'background-exec', - recordId: started.sessionId, - pid: child.pid, - commandLine: expect.arrayContaining([PLATFORM_COMMAND_SHELL.executable]), - cwd: expect.stringMatching(/[\\/]workspace$/) + expect(registryMock.record).toHaveBeenCalledWith( + { + subsystem: 'background-exec', + recordId: started.sessionId, + pid: child.pid, + commandLine: expect.arrayContaining([PLATFORM_COMMAND_SHELL.executable]), + cwd: expect.stringMatching(/[\\/]workspace$/) + }, + child + ) + }) + + it('waits for registration and refuses an unregistered live command until retirement', async () => { + const child = new MockChildProcess() + vi.mocked(spawn).mockReturnValue(child as never) + const registration = Promise.withResolvers() + registryMock.record.mockReturnValueOnce(registration.promise) + const retirement = Promise.withResolvers() + mockTerminateProcessTree.mockReturnValueOnce(retirement.promise) + const descriptor = ownedPackageTree() + const starting = manager.start('conv-1', 'echo test', '/workspace', { + commandShell: PLATFORM_COMMAND_SHELL, + timeout: 0, + ownedSkillExecutionPackageTree: descriptor + }) + const settled = vi.fn() + void starting.then(settled, settled) + await vi.waitFor(() => expect(registryMock.record).toHaveBeenCalledOnce()) + expect(settled).not.toHaveBeenCalled() + registration.resolve('failed') + await vi.waitFor(() => expect(mockTerminateProcessTree).toHaveBeenCalledOnce()) + expect(settled).not.toHaveBeenCalled() + expect(mockCleanupPackageTree).not.toHaveBeenCalled() + child.signalCode = 'SIGTERM' + child.emit('close', null, 'SIGTERM') + expect(registryMock.clear).not.toHaveBeenCalled() + expect(mockCleanupPackageTree).not.toHaveBeenCalled() + expect(settled).not.toHaveBeenCalled() + retirement.resolve(false) + await expect(starting).rejects.toThrow('did not retire') + await expect(manager.shutdown()).rejects.toThrow('did not retire') + expect(mockCleanupPackageTree).not.toHaveBeenCalled() + child.signalCode = 'SIGKILL' + child.emit('close', null, 'SIGKILL') + await expect(manager.shutdown()).rejects.toThrow('did not retire') + await expect( + manager.start('conv-1', 'echo test', '/workspace', { + commandShell: PLATFORM_COMMAND_SHELL, + timeout: 0 + }) + ).rejects.toThrow('stopping') + }) + + it('cleans a rejected launch but preserves commands that close before registration', async () => { + const actual = await vi.importActual('child_process') + const processTree = await vi.importActual( + '@/agent/shared/process/processTree' + ) + vi.mocked(spawn).mockImplementation(actual.spawn) + mockTerminateProcessTree.mockImplementationOnce(processTree.terminateProcessTree) + let child: ChildProcess | undefined + registryMock.record.mockImplementationOnce(async () => { + child = vi.mocked(spawn).mock.results[0].value as ChildProcess + expect(child.pid).toBeGreaterThan(0) + return 'failed' + }) + try { + await expect( + manager.start('conv-1', 'owned command', process.cwd(), { + commandShell: PLATFORM_COMMAND_SHELL, + timeout: 0, + directInvocation: { + executable: process.execPath, + args: ['-e', 'setInterval(() => {}, 1000)'] + } + }) + ).rejects.toThrow('could not be registered') + expect(child?.exitCode !== null || child?.signalCode !== null).toBe(true) + expect(() => process.kill(child!.pid!, 0)).toThrow(expect.objectContaining({ code: 'ESRCH' })) + } finally { + if (child?.exitCode === null && child.signalCode === null) child.kill('SIGKILL') + } + expect(manager.list('conv-1')).toEqual([]) + + const quick = new MockChildProcess() + vi.mocked(spawn).mockReturnValueOnce(quick as never) + registryMock.record.mockImplementationOnce(async () => { + setImmediate(() => { + quick.exitCode = 0 + quick.stdout.emit('data', 'quick command') + quick.emit('close', 0, null) + }) + return 'exited' + }) + const started = await manager.start('conv-1', 'echo test', '/workspace', { + commandShell: PLATFORM_COMMAND_SHELL, + timeout: 0 + }) + await expect(manager.getCompletionResult('conv-1', started.sessionId)).resolves.toMatchObject({ + status: 'done', + output: 'quick command', + exitCode: 0 }) }) + it.skipIf(process.platform !== 'darwin')( + 'gates real exec and environment until registration and retires on EOF or oversized grants', + async () => { + const actual = await vi.importActual('child_process') + const originalExecutable = process.env.DEEPCHAT_HARNESS_EXECUTABLE + const originalNativeValue = process.env.DEEPCHAT_NATIVE_ENV_ONLY + process.env.DEEPCHAT_HARNESS_EXECUTABLE = `${process.cwd()}/out/harness/deepchat-harness-${process.platform}-${process.arch}` + process.env.DEEPCHAT_NATIVE_ENV_ONLY = 'bootstrap-only' + vi.mocked(spawn).mockImplementation(actual.spawn) + try { + for (const action of ['release', 'eof', 'oversize']) { + const registration = Promise.withResolvers< + ObservedProcessIdentity | 'exited' | 'failed' + >() + registryMock.record.mockClear().mockReturnValueOnce(registration.promise) + vi.mocked(spawn).mockClear() + const starting = manager.start('conv-1', 'owned gated command', process.cwd(), { + commandShell: PLATFORM_COMMAND_SHELL, + timeout: 0, + env: { DEEPCHAT_GATE_VALUE: 'released-environment' }, + directInvocation: { + executable: process.execPath, + args: [ + '-e', + `let input = ''; process.stdin.on('data', (data) => { input += data }); process.stdin.on('end', () => { process.stdout.write(JSON.stringify({ pid: process.pid, env: process.env.DEEPCHAT_GATE_VALUE, inherited: process.env.DEEPCHAT_NATIVE_ENV_ONLY ?? null, input }), () => process.exit(0)) })` + ] + } + }) + void starting.catch(() => {}) + let child: ChildProcess | undefined + try { + await vi.waitFor(() => expect(registryMock.record).toHaveBeenCalledOnce()) + child = vi.mocked(spawn).mock.results[0].value as ChildProcess + const gate = child.stdio[3]! + expect(vi.mocked(spawn).mock.calls[0][2]?.env?.DEEPCHAT_GATE_VALUE).toBeUndefined() + child.stdin!.write('preserved input') + if (action === 'release') { + registration.resolve({ + alive: true, + nativeIdentity: 'darwin:00000000-0000-0000-0000-000000000000:42' + }) + const started = await starting + await vi.waitFor(() => expect(gate.closed).toBe(true)) + expect(child.exitCode).toBeNull() + child.stdin!.end() + const completed = await manager.getCompletionResult('conv-1', started.sessionId) + expect(JSON.parse(completed.output)).toEqual({ + pid: child.pid, + env: 'released-environment', + inherited: null, + input: 'preserved input' + }) + await manager.remove('conv-1', started.sessionId) + } else { + const closed = once(child, 'close', { signal: AbortSignal.timeout(2000) }) + if (action === 'eof') { + mockTerminateProcessTree.mockClear() + await manager.kill('conv-1', manager.list('conv-1')[0].sessionId) + expect(mockTerminateProcessTree).not.toHaveBeenCalled() + } else if ('write' in gate) { + const header = Buffer.alloc(4) + header.writeUInt32BE(2 * 1024 * 1024 + 1) + gate.write(header) + } + await closed + registration.resolve('exited') + await expect(starting).rejects.toThrow('launch stopped') + expect(manager.list('conv-1')).toEqual([]) + } + } finally { + const closed = + child && child.exitCode === null && child.signalCode === null + ? once(child, 'close') + : Promise.resolve() + child?.stdin?.end() + child?.stdio[3]?.destroy() + registration.resolve('exited') + await starting.catch(() => {}) + await closed + } + } + } finally { + if (originalExecutable === undefined) delete process.env.DEEPCHAT_HARNESS_EXECUTABLE + else process.env.DEEPCHAT_HARNESS_EXECUTABLE = originalExecutable + if (originalNativeValue === undefined) delete process.env.DEEPCHAT_NATIVE_ENV_ONLY + else process.env.DEEPCHAT_NATIVE_ENV_ONLY = originalNativeValue + } + } + ) + it('clears the registry record when the session process closes', async () => { const child = new MockChildProcess() vi.mocked(spawn).mockReturnValue(child as never) @@ -416,6 +611,58 @@ describe('BackgroundExecSessionManager', () => { }) }) + it('keeps native completion and ownership behind the physical group receipt', async () => { + const descriptor = ownedPackageTree() + for (const retired of [true, false]) { + const child = new MockChildProcess() + vi.mocked(spawn).mockReturnValueOnce(child as never) + registryMock.record.mockResolvedValueOnce({ + alive: true, + nativeIdentity: 'darwin:00000000-0000-0000-0000-000000000000:42' + }) + const physical = Promise.withResolvers() + mockTerminateProcessTree.mockReturnValueOnce(physical.promise) + const started = await manager.start('conv-1', 'echo complete', '/workspace', { + commandShell: PLATFORM_COMMAND_SHELL, + timeout: 0, + ownedSkillExecutionPackageTree: descriptor + }) + const cleared = registryMock.clear.mock.calls.length + const cleaned = mockCleanupPackageTree.mock.calls.length + child.stdout.emit('data', 'complete') + child.exitCode = 0 + child.emit('exit', 0, null) + child.emit('close', 0, null) + const completion = manager.getCompletionResult('conv-1', started.sessionId) + const settled = vi.fn() + void completion.then(settled, settled) + await Promise.resolve() + expect(settled).not.toHaveBeenCalled() + expect( + manager.list('conv-1').find((item) => item.sessionId === started.sessionId)?.status + ).toBe('running') + expect(registryMock.clear).toHaveBeenCalledTimes(cleared) + expect(mockCleanupPackageTree).toHaveBeenCalledTimes(cleaned) + physical.resolve(retired) + if (retired) { + await expect(completion).resolves.toMatchObject({ + status: 'done', + exitCode: 0, + output: 'complete' + }) + expect(registryMock.clear).toHaveBeenCalledTimes(cleared + 1) + expect(mockCleanupPackageTree).toHaveBeenCalledTimes(cleaned + 1) + } else { + await expect(completion).rejects.toThrow('did not retire') + child.emit('close', 0, null) + await expect(manager.poll('conv-1', started.sessionId)).rejects.toThrow('did not retire') + expect(registryMock.clear).toHaveBeenCalledTimes(cleared) + expect(mockCleanupPackageTree).toHaveBeenCalledTimes(cleaned) + await expect(manager.shutdown()).rejects.toThrow('did not retire') + } + } + }) + it('clears the close watchdog when another path finalizes the session', async () => { vi.useFakeTimers() const child = new MockChildProcess() @@ -429,7 +676,7 @@ describe('BackgroundExecSessionManager', () => { child.emit('exit', 0, null) expect(vi.getTimerCount()).toBe(1) - await manager.kill('conv-1', started.sessionId) + await expect(manager.kill('conv-1', started.sessionId)).rejects.toThrow('did not retire') expect(vi.getTimerCount()).toBe(0) }) @@ -551,7 +798,7 @@ describe('BackgroundExecSessionManager', () => { setSession(session) mockTerminateProcessTree.mockResolvedValueOnce(false) - await manager.kill('conv-1', 'bg_123') + await expect(manager.kill('conv-1', 'bg_123')).rejects.toThrow('did not retire') expect(mockCleanupPackageTree).not.toHaveBeenCalled() expect(session.ownedSkillExecutionPackageTree).toBeUndefined() @@ -561,7 +808,7 @@ describe('BackgroundExecSessionManager', () => { expect(session.child.unref).toHaveBeenCalledOnce() }) - it('merges the prepared env on top of process env when starting a session', async () => { + it('uses the granted environment without inheriting utility variables', async () => { const child = new MockChildProcess() vi.mocked(spawn).mockReturnValue(child as never) process.env.BASELINE_FLAG = 'baseline' @@ -586,12 +833,12 @@ describe('BackgroundExecSessionManager', () => { expect.objectContaining({ cwd: expect.stringMatching(/[\\/]workspace$/), env: expect.objectContaining({ - BASELINE_FLAG: 'baseline', PATH: '/prepared/bin:/usr/local/bin', CUSTOM_FLAG: '1' }) }) ) + expect(vi.mocked(spawn).mock.calls[0][2]?.env?.BASELINE_FLAG).toBeUndefined() } finally { delete process.env.BASELINE_FLAG } @@ -883,57 +1130,22 @@ describe('BackgroundExecSessionManager', () => { }) describe('backgroundExecSessionManager utility proxy', () => { - const resetProxyState = () => { - const proxy = backgroundExecSessionManager as any - proxy.stopCompletedSessionReconciliation() - proxy.host = null - proxy.hostReady = null - proxy.shuttingDown = false - proxy.activeSessions.clear() - proxy.completedSessions.clear() - proxy.crashedSessions.clear() - proxy.pendingRequests.clear() - } + let backgroundExecSessionManager: BackgroundExecService beforeEach(() => { - mockUtilityProcessFork.mockReset() - mockAssertPackageTree.mockResolvedValue(undefined) - mockCleanupPackageTree.mockResolvedValue(undefined) - mockTerminateProcessTree.mockResolvedValue(true) - resetProxyState() + mockSpawnHost.mockReset() + mockAssertPackageTree.mockReset().mockResolvedValue(undefined) + mockCleanupPackageTree.mockReset().mockResolvedValue(undefined) + mockTerminateProcessTree.mockReset().mockResolvedValue(true) + backgroundExecSessionManager = new BackgroundExecService(mockSpawnHost) }) afterEach(() => { - resetProxyState() + ;(backgroundExecSessionManager as any).stopCompletedSessionReconciliation() vi.useRealTimers() vi.restoreAllMocks() }) - it('forks the dedicated entrypoint for the utility host', async () => { - const host = new MockUtilityProcess() - mockUtilityProcessFork.mockReturnValue(host) - - const startPromise = (backgroundExecSessionManager as any).startHost() - await vi.waitFor(() => { - expect(mockUtilityProcessFork).toHaveBeenCalled() - }) - host.emit('spawn') - - await expect(startPromise).resolves.toBe(host) - expect(mockUtilityProcessFork).toHaveBeenCalledWith( - expect.stringMatching( - /[\\/]mock[\\/]app[\\/]out[\\/]main[\\/]backgroundExecUtilityHost\.js$/ - ), - ['--deepchat-exec-utility-host'], - expect.objectContaining({ - serviceName: 'DeepChat Exec Utility', - env: expect.objectContaining({ - DEEPCHAT_EXEC_UTILITY_HOST: '1' - }) - }) - ) - }) - it('cleans an owned tree when utility ownership was never posted', async () => { const proxy = backgroundExecSessionManager as any const descriptor = ownedPackageTree() @@ -1056,7 +1268,7 @@ describe('backgroundExecSessionManager utility proxy', () => { timedOut: false } }) - expect(mockUtilityProcessFork).not.toHaveBeenCalled() + expect(mockSpawnHost).not.toHaveBeenCalled() }) it('keeps completed sessions visible when the utility host cannot list them', async () => { @@ -1093,7 +1305,7 @@ describe('backgroundExecSessionManager utility proxy', () => { ]) expect(request).toHaveBeenCalledOnce() expect(request).toHaveBeenCalledWith('poll', ['conv-1', 'bg_completed']) - expect(mockUtilityProcessFork).not.toHaveBeenCalled() + expect(mockSpawnHost).not.toHaveBeenCalled() }) it('refreshes completed-session fallback metadata after clearing output', async () => { @@ -1174,7 +1386,7 @@ describe('backgroundExecSessionManager utility proxy', () => { await backgroundExecSessionManager.remove('conv-1', 'bg_crashed') expect(proxy.crashedSessions.has('bg_crashed')).toBe(false) - expect(mockUtilityProcessFork).not.toHaveBeenCalled() + expect(mockSpawnHost).not.toHaveBeenCalled() }) it('rejects unknown proxy mutations before committing dispatch', async () => { @@ -1185,7 +1397,7 @@ describe('backgroundExecSessionManager utility proxy', () => { ).rejects.toThrow('Session missing not found') expect(beforeMutation).not.toHaveBeenCalled() - expect(mockUtilityProcessFork).not.toHaveBeenCalled() + expect(mockSpawnHost).not.toHaveBeenCalled() }) it('commits tracked proxy writes before the utility-host request', async () => { @@ -1331,10 +1543,10 @@ describe('backgroundExecSessionManager utility proxy', () => { }) const firstHost = new MockUtilityProcess() const secondHost = new MockUtilityProcess() - mockUtilityProcessFork.mockReturnValueOnce(firstHost).mockReturnValueOnce(secondHost) + mockSpawnHost.mockReturnValueOnce(firstHost).mockReturnValueOnce(secondHost) const firstStart = proxy.startHost() - await vi.waitFor(() => expect(mockUtilityProcessFork).toHaveBeenCalledTimes(1)) + await vi.waitFor(() => expect(mockSpawnHost).toHaveBeenCalledTimes(1)) firstHost.emit('spawn') await firstStart expect(proxy.completedSessionReconciliationTimer).not.toBeNull() @@ -1344,7 +1556,7 @@ describe('backgroundExecSessionManager utility proxy', () => { expect(proxy.completedSessionReconciliationTimer).toBeNull() const secondStart = proxy.startHost() - await vi.waitFor(() => expect(mockUtilityProcessFork).toHaveBeenCalledTimes(2)) + await vi.waitFor(() => expect(mockSpawnHost).toHaveBeenCalledTimes(2)) secondHost.emit('spawn') await secondStart diff --git a/test/main/agent/shared/process/childProcessRegistry.test.ts b/test/main/agent/shared/process/childProcessRegistry.test.ts index cce71aa8bc..77fedc381d 100644 --- a/test/main/agent/shared/process/childProcessRegistry.test.ts +++ b/test/main/agent/shared/process/childProcessRegistry.test.ts @@ -45,7 +45,7 @@ describe('ChildProcessRegistry', () => { let rootDir: string let alivePids: Set let observations: Map - let terminate: ReturnType + let terminate: ReturnType Promise>> let registry: ChildProcessRegistry const writeRecord = (subsystem: string, record: ChildProcessLaunchRecord) => { @@ -65,7 +65,7 @@ describe('ChildProcessRegistry', () => { rootDir = fs.mkdtempSync(path.join(os.tmpdir(), 'child-process-registry-')) alivePids = new Set() observations = new Map() - terminate = vi.fn().mockResolvedValue(true) + terminate = vi.fn<(pid: number) => Promise>().mockResolvedValue(true) registry = new ChildProcessRegistry({ rootDir, now: () => 1_060_000, @@ -82,13 +82,14 @@ describe('ChildProcessRegistry', () => { it('persists the observed process start time independently of the write time', async () => { observations.set(4321, { alive: true, startedAtMs: 998_000 }) - await registry.record({ + const recorded = await registry.record({ subsystem: 'background-exec', recordId: 'bg_session/1', pid: 4321, commandLine: ['/bin/zsh', '-c', 'npm run dev'], cwd: '/tmp/work' }) + expect(recorded).toEqual({ alive: true, startedAtMs: 998_000 }) const records = registry.list('background-exec') expect(records).toHaveLength(1) @@ -103,6 +104,24 @@ describe('ChildProcessRegistry', () => { }) }) + it('reports registration failure for unavailable storage or process identity', async () => { + const entry = { + subsystem: 'background-exec' as const, + recordId: 'unregistered-command', + pid: 4321, + commandLine: ['command'] + } + observations.set(entry.pid, { alive: true }) + expect(await registry.record(entry)).toBe('failed') + expect(registry.list(entry.subsystem)).toMatchObject([{ pid: entry.pid }]) + observations.set(entry.pid, { alive: false }) + expect(await registry.record(entry)).toBe('exited') + expect(registry.list(entry.subsystem)).toEqual([]) + fs.rmSync(rootDir, { recursive: true }) + fs.writeFileSync(rootDir, 'not a directory') + expect(await registry.record(entry)).toBe('failed') + }) + it('clears records and tolerates missing files', () => { registry.record({ subsystem: 'mcp-stdio', @@ -165,7 +184,7 @@ describe('ChildProcessRegistry', () => { expect(recordFileCount('background-exec')).toBe(1) }) - it('refuses to kill a reused pid whose command line does not match', async () => { + it('retains uncertain legacy ownership when argv changes without a different birth time', async () => { const record = makeRecord() writeRecord('background-exec', record) alivePids.add(record.pid) @@ -179,7 +198,60 @@ describe('ChildProcessRegistry', () => { expect(result.refused).toEqual(['bg_test']) expect(terminate).not.toHaveBeenCalled() - // The recorded process is demonstrably gone; the stale record is dropped. + expect(recordFileCount('background-exec')).toBe(1) + }) + + it('recovers a native identity across exec but refuses reused or unobservable identities', async () => { + const identity = 'darwin:11111111-1111-4111-8111-111111111111:12345' + const record = makeRecord({ nativeIdentity: identity, startedAtMs: undefined }) + alivePids.add(record.pid) + const nextBoot = identity.replace( + '11111111-1111-4111-8111-111111111111', + '22222222-2222-4222-8222-222222222222' + ) + for (const observedIdentity of [undefined, `${identity}6`, nextBoot, identity]) { + writeRecord('background-exec', record) + observations.set(record.pid, { + alive: true, + nativeIdentity: observedIdentity, + commandLine: '/different/executable' + }) + const result = await registry.reapStale('background-exec', { ownerPid: record.ownerPid }) + if (observedIdentity === identity) { + expect(result.reaped).toEqual([record.recordId]) + expect(terminate).toHaveBeenCalledExactlyOnceWith(record.pid) + } else if (observedIdentity === nextBoot) { + expect(result.cleared).toEqual([record.recordId]) + expect(terminate).not.toHaveBeenCalled() + expect(recordFileCount('background-exec')).toBe(0) + } else { + expect(result.refused).toEqual([record.recordId]) + expect(terminate).not.toHaveBeenCalled() + expect(recordFileCount('background-exec')).toBe(1) + } + } + }) + + it('retains native ownership after root loss until group retirement succeeds', async () => { + const record = makeRecord({ + nativeIdentity: 'darwin:11111111-1111-4111-8111-111111111111:12345', + startedAtMs: undefined, + recordedAt: -14 * 24 * 60 * 60 * 1000 + }) + writeRecord('background-exec', record) + terminate.mockResolvedValueOnce(false) + const failed = await registry.reapStale('background-exec') + expect(failed.refused).toEqual([record.recordId]) + expect(failed.cleared).toEqual([]) + expect(recordFileCount('background-exec')).toBe(1) + const refused = await registry.reapStale('background-exec', { + ownerPid: record.ownerPid, + attester: () => false + }) + expect(refused.refused).toEqual([record.recordId]) + expect(terminate).toHaveBeenCalledTimes(1) + const retired = await registry.reapStale('background-exec', { ownerPid: record.ownerPid }) + expect(retired.reaped).toEqual([record.recordId]) expect(recordFileCount('background-exec')).toBe(0) }) @@ -228,7 +300,7 @@ describe('ChildProcessRegistry', () => { expect(recordFileCount('background-exec')).toBe(1) }) - it.each(['clear', 'replace'] as const)( + it.each(['clear', 'replace', 'exit'] as const)( 'does not restore an old record when observation completes after %s', async (action) => { let resolveObservation!: (identity: ObservedProcessIdentity) => void @@ -248,18 +320,27 @@ describe('ChildProcessRegistry', () => { pid: 4321, commandLine: ['node'] } - const pending = delayedRegistry.record(entry) + const child = { pid: entry.pid, exitCode: null as number | null, signalCode: null } + const pending = delayedRegistry.record(entry, child) if (action === 'clear') { delayedRegistry.clear(entry.subsystem, entry.recordId) - } else { + } else if (action === 'replace') { await delayedRegistry.record({ ...entry, pid: 4322 }) + } else { + child.exitCode = 0 } resolveObservation({ alive: true, startedAtMs: 998_000 }) await pending const records = delayedRegistry.list('mcp-stdio') if (action === 'clear') expect(records).toEqual([]) - else expect(records).toMatchObject([{ pid: 4322, startedAtMs: 999_000 }]) + else if (action === 'replace') + expect(records).toMatchObject([{ pid: 4322, startedAtMs: 999_000 }]) + else { + expect(records).toHaveLength(1) + expect(records[0].startedAtMs).toBeUndefined() + expect(records[0].nativeIdentity).toBeUndefined() + } } ) @@ -407,12 +488,60 @@ describe('ChildProcessRegistry', () => { expect(terminate).toHaveBeenCalledOnce() }) + it('serializes scoped retirement without sharing owners or expiring live records', async () => { + const entered = Promise.withResolvers() + const released = Promise.withResolvers() + for (const [pid, ownerPid] of [ + [4321, 1001], + [4322, 1002], + [4323, 1003] + ]) { + writeRecord('background-exec', makeRecord({ pid, ownerPid, recordId: `job-${pid}` })) + alivePids.add(pid) + observations.set(pid, { + alive: true, + startedAtMs: 998_000, + commandLine: '/bin/zsh -c npm run dev' + }) + } + terminate.mockImplementationOnce(async () => { + entered.resolve() + await released.promise + return true + }) + const first = registry.reapStale('background-exec', { ownerPid: 1001, maxRecordAgeMs: 1 }) + const second = registry.reapStale('background-exec', { ownerPid: 1002, maxRecordAgeMs: 1 }) + try { + await entered.promise + expect(terminate).toHaveBeenCalledExactlyOnceWith(4321) + } finally { + released.resolve() + } + expect((await first).reaped).toEqual(['job-4321']) + expect((await second).reaped).toEqual(['job-4322']) + expect(terminate.mock.calls.map(([pid]) => pid)).toEqual([4321, 4322]) + expect(registry.list('background-exec', 1003).map((record) => record.pid)).toEqual([4323]) + }) + + it('refuses an owned retirement receipt when records cannot be read', async () => { + const directory = path.join(rootDir, 'background-exec') + fs.mkdirSync(directory) + const corrupted = path.join(directory, 'corrupt.json') + fs.writeFileSync(corrupted, '{') + await expect(registry.reapStale('background-exec', { ownerPid: 1001 })).rejects.toThrow() + expect(fs.existsSync(corrupted)).toBe(true) + expect(terminate).not.toHaveBeenCalled() + }) + it('resolves the default root from DEEPCHAT_USER_DATA_DIR at first use', async () => { const userDataDir = fs.mkdtempSync(path.join(os.tmpdir(), 'registry-userdata-')) const previous = process.env.DEEPCHAT_USER_DATA_DIR process.env.DEEPCHAT_USER_DATA_DIR = userDataDir try { - const defaultRegistry = new ChildProcessRegistry({ log: () => {} }) + const defaultRegistry = new ChildProcessRegistry({ + observe: async () => ({ alive: true, startedAtMs: Date.now() - 1000 }), + log: () => {} + }) await defaultRegistry.record({ subsystem: 'mcp-stdio', recordId: 'srv', diff --git a/test/main/agent/shared/process/processTree.test.ts b/test/main/agent/shared/process/processTree.test.ts index 50fd5e6548..9cb7a44932 100644 --- a/test/main/agent/shared/process/processTree.test.ts +++ b/test/main/agent/shared/process/processTree.test.ts @@ -1,6 +1,11 @@ -import { EventEmitter } from 'events' +import { EventEmitter, once } from 'events' +import fs from 'node:fs' +import os from 'node:os' +import path from 'node:path' +import { createInterface } from 'node:readline' import { afterEach, describe, expect, it, vi } from 'vitest' +vi.unmock('fs') vi.mock('child_process', () => ({ spawn: vi.fn() })) @@ -42,6 +47,7 @@ describe('terminateProcessTree', () => { afterEach(() => { vi.useRealTimers() vi.restoreAllMocks() + vi.unstubAllEnvs() if (originalPlatform) { Object.defineProperty(process, 'platform', originalPlatform) } @@ -80,25 +86,37 @@ describe('terminateProcessTree', () => { value: 'linux' }) - process.kill = vi.fn(((_pid: number, signal?: NodeJS.Signals) => { - if (_pid === -654 && signal === 'SIGKILL') { + let groupAlive = true + process.kill = vi.fn(((_pid: number, signal?: NodeJS.Signals | number) => { + if (_pid === 654 && signal === 0 && target.signalCode !== null) + throw Object.assign(new Error('root exited'), { code: 'ESRCH' }) + if (signal === 'SIGTERM') { queueMicrotask(() => { - target.signalCode = 'SIGKILL' - target.emit('close', null, 'SIGKILL') + target.signalCode = 'SIGTERM' + target.emit('close', null, 'SIGTERM') }) } + if (_pid === -654 && signal === 'SIGKILL') { + groupAlive = false + } + if (!groupAlive) throw Object.assign(new Error('group exited'), { code: 'ESRCH' }) return true }) as typeof process.kill) const target = new MockChildProcess(654) const termination = terminateProcessTree(target as never, { graceMs: 10 }) + await vi.advanceTimersByTimeAsync(0) + await expect(terminateProcessTree(target as never, { graceMs: 10 })).resolves.toBe(false) + await expect(terminateProcessTreeByPid(654, { graceMs: 10 })).resolves.toBe(false) await vi.advanceTimersByTimeAsync(10) await expect(termination).resolves.toBe(true) expect(spawn).not.toHaveBeenCalled() - expect(process.kill).toHaveBeenNthCalledWith(1, -654, 'SIGTERM') - expect(process.kill).toHaveBeenNthCalledWith(2, -654, 'SIGKILL') + expect(vi.mocked(process.kill).mock.calls.filter(([, signal]) => signal !== 0)).toEqual([ + [-654, 'SIGTERM'], + [-654, 'SIGKILL'] + ]) }) it('falls back to recursively killing descendants when Unix process-group signaling fails', async () => { @@ -190,7 +208,7 @@ describe('terminateProcessTree', () => { }) let alive = true process.kill = vi.fn(((pid: number, signal?: NodeJS.Signals | number) => { - if (pid === 888 && signal === 0) { + if ((pid === 888 || pid === -888) && signal === 0) { if (!alive) { const error = new Error('missing') as NodeJS.ErrnoException error.code = 'ESRCH' @@ -208,9 +226,105 @@ describe('terminateProcessTree', () => { await expect(terminateProcessTreeByPid(888, { graceMs: 10 })).resolves.toBe(true) expect(process.kill).toHaveBeenNthCalledWith(1, 888, 0) expect(process.kill).toHaveBeenNthCalledWith(2, -888, 'SIGTERM') - expect(process.kill).toHaveBeenNthCalledWith(3, 888, 0) + expect(process.kill).toHaveBeenNthCalledWith(3, -888, 0) }) + it('requires the native recovery receipt without falling back to PID signals', async () => { + Object.defineProperty(process, 'platform', { configurable: true, value: 'darwin' }) + vi.stubEnv('DEEPCHAT_HARNESS_EXECUTABLE', '/owned/deepchat-harness') + process.kill = vi.fn() + const nativeIdentity = 'darwin:00000000-0000-0000-0000-000000000000:42' + for (const [output, retired] of [ + ['{"retired":true}', true], + ['{"retired":false}', false], + ['invalid receipt', false] + ] as const) { + vi.mocked(spawn).mockImplementationOnce(() => new MockSpawnedProcess(output) as never) + await expect(terminateProcessTreeByPid(888, { nativeIdentity, graceMs: 10 })).resolves.toBe( + retired + ) + } + expect(spawn).toHaveBeenLastCalledWith( + '/owned/deepchat-harness', + ['--terminate-tree', '888', nativeIdentity, '10'], + { stdio: ['ignore', 'pipe', 'ignore'] } + ) + vi.mocked(spawn).mockImplementationOnce( + () => new MockSpawnedProcess('{"retired":true}') as never + ) + await expect( + terminateProcessTreeByPid(888, { nativeIdentity, ownedGroup: false, graceMs: 10 }) + ).resolves.toBe(true) + expect(spawn).toHaveBeenLastCalledWith( + '/owned/deepchat-harness', + ['--terminate-owner', '888', nativeIdentity, '10'], + { stdio: ['ignore', 'pipe', 'ignore'] } + ) + await expect(terminateProcessTreeByPid(888, { ownedGroup: false })).resolves.toBe(false) + const child = new MockChildProcess(888) + vi.mocked(spawn).mockImplementationOnce( + () => new MockSpawnedProcess('{"retired":true}') as never + ) + const termination = terminateProcessTree(child as never, { nativeIdentity }) + const returned = vi.fn() + void termination.then(returned) + await vi.waitFor(() => expect(child.listenerCount('close')).toBe(1)) + expect(returned).not.toHaveBeenCalled() + child.exitCode = 0 + child.emit('close', 0, null) + await expect(termination).resolves.toBe(true) + vi.stubEnv('DEEPCHAT_HARNESS_EXECUTABLE', '') + await expect(terminateProcessTreeByPid(888, { nativeIdentity })).resolves.toBe(false) + expect(spawn).toHaveBeenCalledTimes(5) + expect(process.kill).not.toHaveBeenCalled() + }) + + it.skipIf(process.platform === 'win32')( + 'retires a TERM-resistant group member after the root closes, through both APIs', + async () => { + const actual = await vi.importActual('child_process') + for (const byPid of [false, true]) { + const directory = fs.mkdtempSync(path.join(os.tmpdir(), 'deepchat-exec-group-')) + const release = path.join(directory, 'release') + const descendant = `process.on('SIGTERM', () => {}); const timer = setInterval(() => { if (require('node:fs').existsSync(${JSON.stringify(release)})) { clearInterval(timer); process.exit(0) } }, 20); process.stdout.write('ready')` + const script = `const child = require('node:child_process').spawn(process.execPath, ['-e', ${JSON.stringify(descendant)}], { stdio: ['ignore', 'pipe', 'ignore'] }); child.stdout.once('data', () => process.stdout.write(String(child.pid) + '\\n')); setInterval(() => { if (require('node:fs').existsSync(${JSON.stringify(release)}) && (child.exitCode !== null || child.signalCode !== null)) process.exit(0) }, 20)` + const root = actual.spawn(process.execPath, ['-e', script], { + detached: true, + stdio: ['ignore', 'pipe', 'ignore'] + }) + const lines = createInterface({ input: root.stdout! }) + let descendantPid: number | undefined + const alive = () => { + try { + process.kill(descendantPid!, 0) + return true + } catch (error) { + if ((error as NodeJS.ErrnoException).code === 'ESRCH') return false + throw error + } + } + try { + descendantPid = Number((await once(lines, 'line'))[0]) + expect(descendantPid).toBeGreaterThan(0) + const result = byPid + ? await terminateProcessTreeByPid(root.pid!, { graceMs: 30 }) + : await terminateProcessTree(root, { graceMs: 30 }) + expect(result).toBe(true) + expect(root.exitCode !== null || root.signalCode !== null).toBe(true) + expect(alive()).toBe(false) + } finally { + fs.writeFileSync(release, 'release') + if (descendantPid) await vi.waitFor(() => expect(alive()).toBe(false)) + await vi.waitFor(() => + expect(root.exitCode !== null || root.signalCode !== null).toBe(true) + ) + lines.close() + fs.rmSync(directory, { recursive: true }) + } + } + } + ) + it('rejects invalid orphan process ids', async () => { await expect(terminateProcessTreeByPid(0)).rejects.toThrow('invalid process id') expect(spawn).not.toHaveBeenCalled() diff --git a/test/main/agent/shared/process/shellEnvHelper.test.ts b/test/main/agent/shared/process/shellEnvHelper.test.ts index 5fbeba9c9a..a493d68887 100644 --- a/test/main/agent/shared/process/shellEnvHelper.test.ts +++ b/test/main/agent/shared/process/shellEnvHelper.test.ts @@ -27,6 +27,7 @@ import { class MockStream extends EventEmitter {} class MockChild extends EventEmitter { + pid: number | undefined = 41001 stdout = new MockStream() stderr = new MockStream() kill = vi.fn(() => true) @@ -59,6 +60,7 @@ describe('shellEnvHelper', () => { }) afterEach(() => { + vi.useRealTimers() vi.restoreAllMocks() clearShellEnvironmentCache() process.env = { ...originalEnv } @@ -75,7 +77,7 @@ describe('shellEnvHelper', () => { process.env.SHELL = '/bin/zsh' process.env.PATH = '/usr/bin:/bin' - vi.mocked(spawn).mockImplementation((shell, args) => { + vi.mocked(spawn).mockImplementation((_shell, args) => { const child = new MockChild() const command = String(args?.[3] ?? '') const { start, end } = getMarkers(command) @@ -92,6 +94,7 @@ describe('shellEnvHelper', () => { ].join('\n') ) child.emit('exit', 0, null) + child.emit('close', 0, null) }) return child as never @@ -121,6 +124,7 @@ describe('shellEnvHelper', () => { vi.mocked(spawn) .mockImplementationOnce(() => { const child = new MockChild() + child.pid = undefined queueMicrotask(() => { child.emit('error', new Error('boom')) }) @@ -139,6 +143,7 @@ describe('shellEnvHelper', () => { ) ) child.emit('exit', 0, null) + child.emit('close', 0, null) }) return child as never @@ -231,6 +236,7 @@ describe('shellEnvHelper', () => { queueMicrotask(() => { child.stdout.emit('data', [start, 'PATH=/fallback/bin', end].join('\n')) child.emit('exit', 0, null) + child.emit('close', 0, null) }) expect(shell).toBe('/bin/sh') @@ -268,4 +274,29 @@ describe('shellEnvHelper', () => { ]) ) }) + + it('shares one shell and waits for physical close after a timeout', async () => { + vi.useFakeTimers() + Object.defineProperty(process, 'platform', { configurable: true, value: 'darwin' }) + process.env.SHELL = '/bin/zsh' + process.env.PATH = '/usr/bin:/bin' + const child = new MockChild() + vi.mocked(spawn).mockReturnValue(child as never) + let completed = 0 + const reads = [getShellEnvironment(), getShellEnvironment()].map(async (read) => { + const value = await read + completed++ + return value + }) + expect(spawn).toHaveBeenCalledOnce() + await vi.advanceTimersByTimeAsync(8000) + expect(child.kill).toHaveBeenCalledWith('SIGKILL') + expect(completed).toBe(0) + child.emit('exit', null, 'SIGKILL') + await Promise.resolve() + expect(completed).toBe(0) + child.emit('close', null, 'SIGKILL') + for (const value of await Promise.all(reads)) expect(value.PATH).toContain('/usr/bin') + expect(completed).toBe(2) + }) }) diff --git a/test/main/app/backgroundExecHost.test.ts b/test/main/app/backgroundExecHost.test.ts new file mode 100644 index 0000000000..fa43c520ce --- /dev/null +++ b/test/main/app/backgroundExecHost.test.ts @@ -0,0 +1,228 @@ +import { EventEmitter } from 'node:events' +import path from 'node:path' +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' +import { spawnBackgroundExecHost } from '@/app/backgroundExecHost' +import type { BackgroundExecRpcRequest } from '@/agent/shared/process/backgroundExecSessionManager' + +const mocks = vi.hoisted(() => ({ + fork: vi.fn(), + reap: vi.fn(), + list: vi.fn(), + observe: vi.fn(), + terminate: vi.fn() +})) +vi.mock('electron', () => ({ utilityProcess: { fork: mocks.fork } })) +vi.mock('@/agent/shared/process/childProcessRegistry', () => ({ + childProcessRegistry: { reapStale: mocks.reap, list: mocks.list }, + observeProcessIdentity: mocks.observe +})) +vi.mock('@/agent/shared/process/processTree', () => ({ + terminateProcessTreeByPid: mocks.terminate +})) + +describe('Background exec native launch', () => { + const entry = path.resolve('out/main/backgroundExecUtilityHost.js') + const originalPlatform = Object.getOwnPropertyDescriptor(process, 'platform') + beforeEach(() => { + mocks.fork.mockReset() + mocks.reap.mockReset().mockResolvedValue({ refused: [], skipped: [], reaped: [], cleared: [] }) + mocks.list.mockReset().mockReturnValue([]) + mocks.observe.mockReset().mockResolvedValue({ + alive: true, + nativeIdentity: 'darwin:11111111-1111-4111-8111-111111111111:12345' + }) + mocks.terminate.mockReset().mockResolvedValue(true) + }) + afterEach(() => { + vi.unstubAllEnvs() + if (originalPlatform) Object.defineProperty(process, 'platform', originalPlatform) + }) + + function nativeHost() { + const host = Object.assign(new EventEmitter(), { + pid: 123, + postMessage: vi.fn(), + kill: vi.fn() + }) + host.kill.mockImplementation(() => { + host.emit('exit', 0) + return true + }) + mocks.fork.mockReturnValue(host) + return host + } + + it('launches the approved entry and exposes a plain command/event/lifecycle channel', async () => { + const host = nativeHost() + const pending = spawnBackgroundExecHost(entry, new AbortController().signal) + host.emit('spawn') + const channel = await pending + expect(channel).not.toBe(host) + expect(channel.pid).toBe(123) + expect(mocks.fork).toHaveBeenCalledWith( + entry, + ['--deepchat-exec-utility-host'], + expect.objectContaining({ + serviceName: 'DeepChat Exec Utility', + env: expect.objectContaining({ DEEPCHAT_EXEC_UTILITY_HOST: '1' }) + }) + ) + const message = vi.fn(), + exit = vi.fn(), + error = vi.fn() + channel.onMessage(message) + channel.onExit(exit) + channel.onError(error) + const request: BackgroundExecRpcRequest = { + type: 'background-exec:request', + id: '1', + method: 'list', + args: ['conversation'] + } + channel.postMessage(request) + host.emit('message', { type: 'background-exec:response', id: '1', ok: true, data: [] }) + host.emit('error', 'crash', 'helper') + expect(host.postMessage).toHaveBeenCalledWith(request) + expect(message).toHaveBeenCalledWith({ + type: 'background-exec:response', + id: '1', + ok: true, + data: [] + }) + expect(error).toHaveBeenCalledWith('crash', 'helper') + await expect(channel.kill()).resolves.toBeUndefined() + expect(exit).toHaveBeenCalledWith(0) + expect(mocks.reap).toHaveBeenCalledWith('background-exec', { ownerPid: 123 }) + const lateExit = vi.fn() + channel.onExit(lateExit) + await Promise.resolve() + expect(lateExit).toHaveBeenCalledExactlyOnceWith(0) + }) + + it('rejects relative paths and already canceled launches before forking', async () => { + const canceled = AbortSignal.abort() + await expect(spawnBackgroundExecHost(entry, canceled)).rejects.toMatchObject({ + name: 'AbortError' + }) + await expect( + spawnBackgroundExecHost('relative.js', new AbortController().signal) + ).rejects.toThrow('must be absolute') + expect(mocks.fork).not.toHaveBeenCalled() + }) + + it('waits for physical exit and refuses incomplete owned-command cleanup', async () => { + const host = nativeHost() + host.kill.mockImplementation(() => true) + const recovery = Promise.withResolvers<{ refused: string[]; skipped: string[] }>() + mocks.reap.mockReturnValueOnce(recovery.promise) + const pending = spawnBackgroundExecHost(entry, new AbortController().signal) + host.emit('spawn') + const channel = await pending + const retirement = channel.kill() + const rejected = expect(retirement).rejects.toThrow('Owned background execution did not retire') + expect(channel.kill()).toBe(retirement) + await vi.waitFor(() => expect(host.kill).toHaveBeenCalledOnce()) + expect(mocks.reap).not.toHaveBeenCalled() + host.emit('exit', 0) + await vi.waitFor(() => + expect(mocks.reap).toHaveBeenCalledWith('background-exec', { ownerPid: 123 }) + ) + recovery.resolve({ refused: ['job'], skipped: [] }) + await rejected + expect(channel.pid).toBe(123) + expect(host.kill).toHaveBeenCalledOnce() + }) + + it('attests native readiness and refuses an incomplete descendant retirement receipt', async () => { + Object.defineProperty(process, 'platform', { configurable: true, value: 'darwin' }) + vi.stubEnv('DEEPCHAT_HARNESS_EXECUTABLE', '/owned/deepchat-harness') + const host = nativeHost() + const observation = Promise.withResolvers<{ alive: boolean; nativeIdentity: string }>() + const descendants = Promise.withResolvers() + mocks.observe.mockReturnValueOnce(observation.promise) + mocks.terminate.mockReturnValueOnce(descendants.promise) + const pending = spawnBackgroundExecHost(entry, new AbortController().signal) + const ready = vi.fn() + void pending.then(ready) + host.emit('spawn') + expect(mocks.observe).toHaveBeenCalledExactlyOnceWith(123) + await Promise.resolve() + expect(ready).not.toHaveBeenCalled() + const nativeIdentity = 'darwin:11111111-1111-4111-8111-111111111111:12345' + observation.resolve({ alive: true, nativeIdentity }) + const channel = await pending + const retirement = channel.kill() + const refused = expect(retirement).rejects.toThrow('helper descendants did not retire') + await vi.waitFor(() => + expect(mocks.terminate).toHaveBeenCalledExactlyOnceWith(123, { + nativeIdentity, + ownedGroup: false, + graceMs: 2000 + }) + ) + expect(mocks.reap).not.toHaveBeenCalled() + descendants.resolve(false) + await refused + expect(channel.kill()).toBe(retirement) + expect(mocks.reap).not.toHaveBeenCalled() + }) + + it('does not expose a native channel after failed or canceled identity observation', async () => { + Object.defineProperty(process, 'platform', { configurable: true, value: 'darwin' }) + vi.stubEnv('DEEPCHAT_HARNESS_EXECUTABLE', '/owned/deepchat-harness') + for (const canceled of [false, true]) { + const host = nativeHost() + const observation = Promise.withResolvers<{ alive: boolean }>() + mocks.observe.mockReturnValueOnce(observation.promise) + const owner = new AbortController() + const pending = spawnBackgroundExecHost(entry, owner.signal) + const rejected = expect(pending).rejects.toThrow() + host.emit('spawn') + if (canceled) owner.abort() + observation.resolve({ alive: true }) + await rejected + expect(host.kill).toHaveBeenCalledOnce() + expect(host.postMessage).not.toHaveBeenCalled() + expect(mocks.terminate).not.toHaveBeenCalled() + } + }) + + it.each(['pending', 'during-fork'])( + 'kills a %s canceled launch and detaches startup listeners', + async (phase) => { + const host = nativeHost() + const owner = new AbortController() + if (phase === 'during-fork') + mocks.fork.mockImplementationOnce(() => { + owner.abort() + return host + }) + const rejected = expect(spawnBackgroundExecHost(entry, owner.signal)).rejects.toMatchObject({ + name: 'AbortError' + }) + owner.abort() + await rejected + expect(host.kill).toHaveBeenCalledOnce() + for (const event of ['spawn', 'exit', 'error']) expect(host.listenerCount(event)).toBe(0) + host.emit('spawn') + expect(host.postMessage).not.toHaveBeenCalled() + } + ) + + it.each(['exit', 'error'])( + 'rejects a native %s before readiness and releases startup listeners', + async (event) => { + const host = nativeHost() + const rejected = expect( + spawnBackgroundExecHost(entry, new AbortController().signal) + ).rejects.toThrow( + event === 'exit' ? 'exited before spawn: 1' : 'helper error: crash at startup' + ) + if (event === 'exit') host.emit('exit', 1) + else host.emit('error', 'crash', 'startup') + await rejected + expect(host.kill).toHaveBeenCalledTimes(event === 'exit' ? 0 : 1) + for (const name of ['spawn', 'exit', 'error']) expect(host.listenerCount(name)).toBe(0) + } + ) +}) diff --git a/test/main/app/codeModeHost.test.ts b/test/main/app/codeModeHost.test.ts new file mode 100644 index 0000000000..d508fb8ae5 --- /dev/null +++ b/test/main/app/codeModeHost.test.ts @@ -0,0 +1,139 @@ +import { EventEmitter } from 'node:events' +import path from 'node:path' +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' +import { spawnCodeModeHost } from '@/app/codeModeHost' +import { RUN_CODE_PROTOCOL_VERSION, type RunCodeParentMessage } from '@shared/codeModeProtocol' + +const mocks = vi.hoisted(() => ({ fork: vi.fn(), metrics: vi.fn() })) +vi.mock('electron', () => ({ + utilityProcess: { fork: mocks.fork }, + app: { getAppMetrics: mocks.metrics } +})) + +describe('Code Mode native capability', () => { + const entry = path.resolve('out/main/codeModeUtilityHost.js') + beforeEach(() => { + mocks.fork.mockReset() + mocks.metrics.mockReset() + }) + afterEach(() => vi.unstubAllEnvs()) + const nativeHost = () => { + const host = Object.assign(new EventEmitter(), { + pid: 123 as number | undefined, + postMessage: vi.fn(), + kill: vi.fn() + }) + mocks.fork.mockReturnValue(host) + return host + } + + it('preserves restricted environment, heap and library policy behind a plain channel', async () => { + vi.stubEnv('DEEPCHAT_CODE_MODE_SECRET', 'fixture-secret') + const native = nativeHost() + const host = await spawnCodeModeHost(entry, new AbortController().signal) + expect(host).not.toBe(native) + const [actualEntry, args, options] = mocks.fork.mock.calls[0] + expect(actualEntry).toBe(entry) + expect(args).toEqual(['--deepchat-code-mode-host']) + expect(options.serviceName).toBe('DeepChat Code Mode Cell') + expect(options.execArgv).toEqual(['--max-old-space-size=64']) + expect(options.allowLoadingUnsignedLibraries).toBe(false) + expect(options.stdio).toBe('ignore') + const allowed = [ + 'DEEPCHAT_CODE_MODE_HOST', + 'LANG', + 'LC_ALL', + 'TMPDIR', + 'TEMP', + 'TMP', + 'SystemRoot' + ] + expect(Object.keys(options.env).every((name) => allowed.includes(name))).toBe(true) + expect(options.env.DEEPCHAT_CODE_MODE_HOST).toBe('1') + expect(host.pid).toBe(123) + const message = vi.fn(), + exit = vi.fn(), + error = vi.fn() + expect(host.on('message', message)).toBeUndefined() + host.on('exit', exit) + host.on('error', error) + const stop: RunCodeParentMessage = { + type: 'STOP', + version: RUN_CODE_PROTOCOL_VERSION, + cellId: 'cell', + reason: 'done' + } + host.postMessage(stop) + expect(native.postMessage).toHaveBeenCalledWith(stop) + native.emit('message', { type: 'READY' }) + native.emit('exit', 0) + native.emit('error', 'crash', 'cell') + expect(message).toHaveBeenCalledWith({ type: 'READY' }) + expect(exit).toHaveBeenCalledWith(0) + expect(error).toHaveBeenCalledWith('crash', 'cell') + expect(host.off('message', message)).toBeUndefined() + expect(native.listenerCount('message')).toBe(0) + expect(host.kill()).toBeUndefined() + expect(native.kill).toHaveBeenCalledOnce() + }) + + it('rejects canceled and relative launch requests before creating a process', async () => { + await expect(spawnCodeModeHost(entry, AbortSignal.abort())).rejects.toMatchObject({ + name: 'AbortError' + }) + await expect(spawnCodeModeHost('relative.js', new AbortController().signal)).rejects.toThrow( + 'must be absolute' + ) + expect(mocks.fork).not.toHaveBeenCalled() + }) + + it('compensates cancellation during native fork', async () => { + const native = nativeHost(), + owner = new AbortController() + mocks.fork.mockImplementation(() => { + owner.abort() + return native + }) + let settled = false + const spawning = spawnCodeModeHost(entry, owner.signal).finally(() => { + settled = true + }) + const rejected = expect(spawning).rejects.toMatchObject({ + name: 'AbortError' + }) + expect(native.kill).toHaveBeenCalledOnce() + await Promise.resolve() + expect(settled).toBe(false) + native.emit('exit', 0) + await rejected + }) + + it('returns only the owned helper RSS in bytes and respects canceled reads', async () => { + const native = nativeHost() + const host = await spawnCodeModeHost(entry, new AbortController().signal) + mocks.metrics.mockReturnValue([ + { pid: 999, memory: { workingSetSize: 999999 } }, + { pid: 123, memory: { workingSetSize: 1024 } } + ]) + await expect(host.getRssBytes(new AbortController().signal)).resolves.toBe(1024 * 1024) + mocks.metrics.mockClear() + await expect(host.getRssBytes(AbortSignal.abort())).rejects.toMatchObject({ + name: 'AbortError' + }) + expect(mocks.metrics).not.toHaveBeenCalled() + native.pid = undefined + await expect(host.getRssBytes(new AbortController().signal)).resolves.toBeNull() + expect(mocks.metrics).not.toHaveBeenCalled() + }) + + it('rejects a read canceled during native sampling without returning a stale metric', async () => { + nativeHost() + const host = await spawnCodeModeHost(entry, new AbortController().signal) + const owner = new AbortController() + mocks.metrics.mockImplementation(() => { + owner.abort() + return [{ pid: 123, memory: { workingSetSize: 1024 } }] + }) + await expect(host.getRssBytes(owner.signal)).rejects.toMatchObject({ name: 'AbortError' }) + }) +}) diff --git a/test/main/app/compositionBoundaries.test.ts b/test/main/app/compositionBoundaries.test.ts index fcd611bb09..08c89273e9 100644 --- a/test/main/app/compositionBoundaries.test.ts +++ b/test/main/app/compositionBoundaries.test.ts @@ -1,32 +1,34 @@ import path from 'node:path' import { describe, expect, it, vi } from 'vitest' +async function readComposition(): Promise { + const { readFileSync } = await vi.importActual('node:fs') + return [ + 'src/main/app/composition.ts', + 'src/harness/core/data.ts', + 'src/harness/core/runtime.ts', + 'src/harness/core/controller.ts', + 'src/harness/core/maintenance.ts' + ] + .map((file) => readFileSync(path.resolve(process.cwd(), file), 'utf8')) + .join('\n') +} + describe('session boundary composition', () => { it('does not load NativeKit from the application startup workload', async () => { - const { readFileSync } = await vi.importActual('node:fs') - const compositionSource = readFileSync( - path.resolve(process.cwd(), 'src/main/app/composition.ts'), - 'utf8' - ) + const compositionSource = await readComposition() expect(compositionSource).not.toContain('main:yo-browser') expect(compositionSource).not.toContain('yoBrowserPresenter.initialize()') }) it('reuses one default LegacyChatImportService across startup and skill repair', async () => { - const { readFileSync } = await vi.importActual('node:fs') - const compositionSource = readFileSync( - path.resolve(process.cwd(), 'src/main/app/composition.ts'), - 'utf8' - ) + const compositionSource = await readComposition() expect(compositionSource.match(/new LegacyChatImportService\(/g)).toHaveLength(1) expect(compositionSource).toMatch( /new LegacyChatImportService\([^)]*memoryDatabase,\s*sessionData\.tapeStore/ ) - expect(compositionSource).toMatch( - /sessionData\.tapeStore,\s*undefined,\s*\(\) => projectService\.notifyEnvironmentProjectionChanged\(\)/ - ) expect(compositionSource).toContain( 'legacyChatImportService.repairImportedLegacySessionSkills(conversationId)' ) @@ -34,38 +36,15 @@ describe('session boundary composition', () => { }) it('notifies the project snapshot owner after skill persistence refreshes environments', async () => { - const { readFileSync } = await vi.importActual('node:fs') - const compositionSource = readFileSync( - path.resolve(process.cwd(), 'src/main/app/composition.ts'), - 'utf8' - ) + const compositionSource = await readComposition() expect(compositionSource).toContain( 'projectDatabase.newEnvironmentsTable.syncForSession(conversationId)\n projectService.notifyEnvironmentProjectionChanged()' ) }) - it('keeps hooks notifications on one instance that owns every configuration write', async () => { - const { readFileSync } = await vi.importActual('node:fs') - const compositionSource = readFileSync( - path.resolve(process.cwd(), 'src/main/app/composition.ts'), - 'utf8' - ) - - expect(compositionSource.match(/new HookService\(/g)).toHaveLength(1) - expect(compositionSource).toContain( - 'getSession: (sessionId) => sessionQuery.getSession(sessionId)' - ) - expect(compositionSource).toContain('createHookRoutes({ service: hookService })') - expect(compositionSource).not.toContain('settings: hookSettings') - }) - it('constructs Scheduler once after Remote with complete execution dependencies', async () => { - const { readFileSync } = await vi.importActual('node:fs') - const compositionSource = readFileSync( - path.resolve(process.cwd(), 'src/main/app/composition.ts'), - 'utf8' - ) + const compositionSource = await readComposition() expect(compositionSource.match(/new SchedulerService\(/g)).toHaveLength(1) expect(compositionSource.indexOf('new RemoteService(')).toBeLessThan( @@ -79,43 +58,40 @@ describe('session boundary composition', () => { it('finishes config migration before connecting module settings', async () => { const { readFileSync } = await vi.importActual('node:fs') - const mainProcessSource = readFileSync( - path.resolve(process.cwd(), 'src/main/app/mainProcess.ts'), + const profileSource = readFileSync( + path.resolve(process.cwd(), 'src/main/app/profileStartup.ts'), 'utf8' ) - const migration = mainProcessSource.indexOf('migrateConfigStorage({') - const settingsConnection = mainProcessSource.indexOf('settingsStore.attachDatabase(') - const mcpConnection = mainProcessSource.indexOf('mcpSettings.connectDatabase(') - const providerCreation = mainProcessSource.indexOf('previousAppVersion:') + const migration = profileSource.indexOf('migrateConfigStorage({') + const settingsConnection = profileSource.indexOf('settingsStore.attachDatabase(') + const mcpConnection = profileSource.indexOf('mcpSettings.connectDatabase(') + const providerCreation = profileSource.indexOf('previousAppVersion:') expect(migration).toBeGreaterThanOrEqual(0) expect(migration).toBeLessThan(settingsConnection) expect(settingsConnection).toBeLessThan(mcpConnection) expect(mcpConnection).toBeLessThan(providerCreation) - expect(mainProcessSource).not.toContain('providerSettings.attachDatabase(') + expect(profileSource).not.toContain('providerSettings.attachDatabase(') }) it('applies the persisted logging gate before database initialization can fail', async () => { const { readFileSync } = await vi.importActual('node:fs') - const mainProcessSource = readFileSync( - path.resolve(process.cwd(), 'src/main/app/mainProcess.ts'), + const profileSource = readFileSync( + path.resolve(process.cwd(), 'src/main/app/profileStartup.ts'), 'utf8' ) - const loggingGate = mainProcessSource.indexOf('setMainLoggingEnabled(') - const databaseInitialization = mainProcessSource.indexOf('initializeMainDatabaseWithRecovery(') + const loggingGate = profileSource.indexOf('.setLoggingEnabled(') + const databaseInitialization = profileSource.indexOf('initializeMainDatabaseWithRecovery(') expect(loggingGate).toBeGreaterThanOrEqual(0) expect(loggingGate).toBeLessThan(databaseInitialization) - expect(mainProcessSource.match(/setMainLoggingEnabled\(/g)).toHaveLength(1) + expect(profileSource.match(/\.setLoggingEnabled\(/g)).toHaveLength(1) }) it('has no late Provider runtime connection or ready fallback', async () => { const { readFileSync } = await vi.importActual('node:fs') - const compositionSource = readFileSync( - path.resolve(process.cwd(), 'src/main/app/composition.ts'), - 'utf8' - ) + const compositionSource = await readComposition() const providerSettingsSource = readFileSync( path.resolve(process.cwd(), 'src/main/provider/settings.ts'), 'utf8' @@ -126,112 +102,14 @@ describe('session boundary composition', () => { expect(providerSettingsSource).not.toContain('runtimeEffects') }) - it('owns one idempotent shutdown path and rejects routes after shutdown starts', async () => { - const { readFileSync } = await vi.importActual('node:fs') - const compositionSource = readFileSync( - path.resolve(process.cwd(), 'src/main/app/composition.ts'), - 'utf8' - ) - const shutdownSource = readFileSync( - path.resolve(process.cwd(), 'src/main/app/mainShutdownCoordinator.ts'), - 'utf8' - ) - - expect(shutdownSource).toContain('if (!this.teardownPromise)') - expect(shutdownSource).toContain('if (this.actionClaim)') - expect(compositionSource).toContain("appLifecycleState = 'stopping'") - expect(compositionSource).toContain("appLifecycleState = 'stopped'") - expect(compositionSource).toContain('throw new Error(`App lifecycle is ${appLifecycleState}`)') - }) - - it('stops entry points and sessions before owned infrastructure', async () => { - const { readFileSync } = await vi.importActual('node:fs') - const compositionSource = readFileSync( - path.resolve(process.cwd(), 'src/main/app/composition.ts'), - 'utf8' - ) - const destroyStart = compositionSource.indexOf('async function destroy(): Promise') - const destroyEnd = compositionSource.indexOf('async function runDestroyStep', destroyStart) - expect(destroyStart).toBeGreaterThanOrEqual(0) - expect(destroyEnd).toBeGreaterThan(destroyStart) - const destroySource = compositionSource.slice(destroyStart, destroyEnd) - const destroyStepIndex = (step: string): number => { - const index = destroySource.indexOf(`'${step}'`) - expect(index, `Missing destroy step: ${step}`).toBeGreaterThanOrEqual(0) - return index - } - - expect(destroyStepIndex('remoteService.destroy')).toBeLessThan( - destroyStepIndex('sessionRuntimes.suspend') - ) - expect(destroyStepIndex('liveDelegationService.stop')).toBeLessThan( - destroyStepIndex('agentInvocationAdmission.close') - ) - expect(destroyStepIndex('agentInvocationAdmission.close')).toBeLessThan( - destroyStepIndex('agentInvocationAdmission.flushObservations') - ) - expect(destroyStepIndex('agentInvocationAdmission.flushObservations')).toBeLessThan( - destroyStepIndex('cronJobs.destroy') - ) - expect(destroyStepIndex('sessionRuntimes.suspend')).toBeLessThan( - destroyStepIndex('skillInitialization.drain') - ) - expect(destroyStepIndex('skillInitialization.drain')).toBeLessThan( - destroyStepIndex('pluginService.shutdown') - ) - expect(destroyStepIndex('skillSyncScan.drain')).toBeLessThan( - destroyStepIndex('skillSyncService.destroy') - ) - expect(destroySource).toContain("'yoBrowserPresenter.shutdown'") - expect(destroySource).toContain("'tabPresenter.destroy'") - expect(destroySource).toContain("'backgroundExecSessionManager.shutdown'") - expect(destroyStepIndex('windowPresenter.destroyWindows')).toBeLessThan( - destroyStepIndex('mainDatabase.close') - ) - }) - it('does not detach startup workload tasks without an observation owner', async () => { - const { readFileSync } = await vi.importActual('node:fs') - const compositionSource = readFileSync( - path.resolve(process.cwd(), 'src/main/app/composition.ts'), - 'utf8' - ) + const compositionSource = await readComposition() expect(compositionSource).not.toContain('void startupWorkloadCoordinator.scheduleTask(') }) - it('finishes migrations and Skill initialization before the initial window', async () => { - const { readFileSync } = await vi.importActual('node:fs') - const compositionSource = readFileSync( - path.resolve(process.cwd(), 'src/main/app/composition.ts'), - 'utf8' - ) - const startupStart = compositionSource.lastIndexOf('dependencies.bindControl(control)') - const startupSource = compositionSource.slice(startupStart) - - expect(startupSource.indexOf('await runAcpRegistryMigration()')).toBeLessThan( - startupSource.indexOf('init(dependencies.startupRunId)') - ) - expect( - startupSource.indexOf('await runBuiltinMcpAllowlistCompatibilityMigration(') - ).toBeLessThan(startupSource.indexOf('await initializeSkills()')) - expect(startupSource.indexOf('await initializeSkills()')).toBeLessThan( - startupSource.indexOf("createAppWindow({ initialRoute: 'chat' })") - ) - expect( - startupSource.indexOf('await agentSettings.retryPendingDeletedAgentSkillCleanup()') - ).toBeLessThan(startupSource.indexOf("createAppWindow({ initialRoute: 'chat' })")) - expect(startupSource.indexOf("createAppWindow({ initialRoute: 'chat' })")).toBeLessThan( - startupSource.indexOf('init(dependencies.startupRunId)') - ) - }) - it('does not let app activation create a chat window during startup migration', async () => { - const { readFileSync } = await vi.importActual('node:fs') - const compositionSource = readFileSync( - path.resolve(process.cwd(), 'src/main/app/composition.ts'), - 'utf8' - ) + const compositionSource = await readComposition() const activateStart = compositionSource.indexOf("app.on('activate'") const activateEnd = compositionSource.indexOf("app.on('did-resign-active'", activateStart) const activateSource = compositionSource.slice(activateStart, activateEnd) @@ -241,11 +119,7 @@ describe('session boundary composition', () => { }) it('registers Plugin contributions before Skill migration through one startup barrier', async () => { - const { readFileSync } = await vi.importActual('node:fs') - const compositionSource = readFileSync( - path.resolve(process.cwd(), 'src/main/app/composition.ts'), - 'utf8' - ) + const compositionSource = await readComposition() const skillsStart = compositionSource.indexOf('async function initializeSkills()') const pluginsStart = compositionSource.indexOf('async function initializePlugins()') const syncStart = compositionSource.indexOf('async function initializeSkillSyncScan()') @@ -277,10 +151,7 @@ describe('session boundary composition', () => { path.resolve(process.cwd(), 'src/main/device/routes.ts'), 'utf8' ) - const compositionSource = readFileSync( - path.resolve(process.cwd(), 'src/main/app/composition.ts'), - 'utf8' - ) + const compositionSource = await readComposition() const restartStart = compositionSource.indexOf('async function restartApplication()') const restartEnd = compositionSource.indexOf('\n }', restartStart) expect(restartStart).toBeGreaterThanOrEqual(0) @@ -310,8 +181,8 @@ describe('session boundary composition', () => { path.resolve(process.cwd(), 'src/main/tool/runtimePorts.ts'), 'utf8' ) - const compositionSource = readFileSync( - path.resolve(process.cwd(), 'src/main/app/composition.ts'), + const coreDataSource = readFileSync( + path.resolve(process.cwd(), 'src/harness/core/data.ts'), 'utf8' ) @@ -320,7 +191,7 @@ describe('session boundary composition', () => { expect(schedulerSource).not.toContain("from '@/agent/") expect(toolPortSource).toContain("from '@/session/runtimeEvents'") expect(toolPortSource).not.toContain('DeepChatInternalSessionUpdate') - expect(compositionSource).toContain('const sessionRuntimeEvents = new SessionRuntimeEvents()') + expect(coreDataSource).toContain('const sessionRuntimeEvents = new SessionRuntimeEvents()') }) it('keeps Remote behind App-owned catalog, workspace, Session, and desktop ports', async () => { diff --git a/test/main/app/credentialCrypto.test.ts b/test/main/app/credentialCrypto.test.ts new file mode 100644 index 0000000000..bfa72f1656 --- /dev/null +++ b/test/main/app/credentialCrypto.test.ts @@ -0,0 +1,38 @@ +import { afterEach, expect, it, vi } from 'vitest' +import { credentialCrypto, secureCredentialCrypto } from '@/app/credentialCrypto' + +const native = vi.hoisted(() => ({ + isAsyncEncryptionAvailable: vi.fn(async () => true), + encryptStringAsync: vi.fn(async (value: string) => Buffer.from(`wrapped:${value}`)), + decryptStringAsync: vi.fn(async (value: Buffer) => ({ + result: value.toString().slice(8), + shouldReEncrypt: false + })), + getSelectedStorageBackend: vi.fn(() => 'basic_text') +})) +vi.mock('electron', () => ({ safeStorage: native })) +const platform = Object.getOwnPropertyDescriptor(process, 'platform')! + +afterEach(() => { + Object.defineProperty(process, 'platform', platform) + vi.restoreAllMocks() +}) + +it('uses acknowledged native ciphertext without changing its encoding', async () => { + const wrapped = await credentialCrypto.encrypt('fixture') + expect(wrapped).toBe(Buffer.from('wrapped:fixture').toString('base64')) + expect(await credentialCrypto.decrypt(wrapped)).toBe('fixture') +}) + +it.each(['basic_text', 'kwallet5', 'unavailable', 'error'])( + 'enforces the Linux secure backend policy for %s', + async (backend) => { + Object.defineProperty(process, 'platform', { configurable: true, value: 'linux' }) + native.isAsyncEncryptionAvailable.mockResolvedValue(backend !== 'unavailable') + native.getSelectedStorageBackend.mockImplementation(() => { + if (backend === 'error') throw new Error('backend unavailable') + return backend + }) + expect(await secureCredentialCrypto.isAvailable()).toBe(backend === 'kwallet5') + } +) diff --git a/test/main/app/databaseInitializer.test.ts b/test/main/app/databaseInitializer.test.ts index dbd8a02ec0..5a41b81866 100644 --- a/test/main/app/databaseInitializer.test.ts +++ b/test/main/app/databaseInitializer.test.ts @@ -1,5 +1,6 @@ import { beforeEach, describe, expect, it, vi } from 'vitest' import { MAX_MAIN_LOG_DURATION_MS } from '../../../src/main/logging/mainLogEvents' +import type { DatabaseInitializationObservation } from '@/app/databaseInitializer' const startupSchemaCatalog = [ { @@ -34,7 +35,9 @@ async function createInitializerWithMocks(input: { isDestructiveDatabaseError?: ReturnType classifySchemaError?: ReturnType getStartupSchemaCatalog?: ReturnType - observe?: ReturnType + observe?: ReturnType< + typeof vi.fn<(observation: DatabaseInitializationObservation) => void | Promise> + > now?: () => number }) { const repairSQLiteDatabaseFile = input.repairSQLiteDatabaseFile ?? vi.fn() @@ -43,13 +46,10 @@ async function createInitializerWithMocks(input: { const classifySchemaError = input.classifySchemaError ?? vi.fn().mockReturnValue(null) const getStartupSchemaCatalog = input.getStartupSchemaCatalog ?? vi.fn().mockReturnValue(startupSchemaCatalog) - const observe = input.observe ?? vi.fn() + const observe = + input.observe ?? + vi.fn<(observation: DatabaseInitializationObservation) => void | Promise>() - vi.doMock('electron', () => ({ - app: { - getPath: vi.fn().mockReturnValue('C:/Users/test/AppData/Roaming/DeepChat') - } - })) vi.doMock('@/data/mainDatabase', () => ({ MainDatabase: input.MainDatabase, repairSQLiteDatabaseFile, diff --git a/test/main/app/databaseRequestGate.test.ts b/test/main/app/databaseRequestGate.test.ts new file mode 100644 index 0000000000..5fa2b91c1e --- /dev/null +++ b/test/main/app/databaseRequestGate.test.ts @@ -0,0 +1,368 @@ +import { afterEach, describe, expect, it, vi } from 'vitest' +import { DatabaseRequestGate } from '@/app/databaseRequestGate' +import { createCoreMaintenance } from '../../../src/harness/core/maintenance' +import { SecretStore } from '@/config/secretStore' + +const createSecrets = () => + new SecretStore({} as never, { + isAvailable: async () => true, + encrypt: async (value) => value, + decrypt: async (value) => value + }) + +const deferred = () => { + let resolve!: (value: T) => void + let reject!: (error: unknown) => void + const promise = new Promise((done, fail) => { + resolve = done + reject = fail + }) + return { promise, resolve, reject } +} + +afterEach(() => vi.useRealTimers()) + +describe('database request ownership', () => { + it('waits for both session backends when one cleanup fails', async () => { + const active = Promise.withResolvers() + const failure = new Error('DeepChat backend cleanup failed') + const owner = createCoreMaintenance({ + profile: {}, + providers: {}, + settings: {}, + data: { appSessionService: { list: () => [{ id: 'session-1' }] } }, + runtime: { + execution: { + deepChatAgentHarness: { + cleanupSession: async () => { + throw failure + } + }, + acpAgentRuntime: { cleanupSession: async () => await active.promise } + } + } + } as unknown as Parameters[0]) + let settled = false + const rejected = expect( + owner.suspendSessionRuntimes().finally(() => { + settled = true + }) + ).rejects.toBe(failure) + await new Promise((resolve) => setImmediate(resolve)) + expect(settled).toBe(false) + active.resolve() + await rejected + }) + + it('fences an uncertain Native pause and waits for failed-owner cleanup', async () => { + const gate = new DatabaseRequestGate(() => undefined) + const secrets = createSecrets() + const previousCredential = secrets.captureOperation() + const failure = new Error('Native pause outcome is unknown') + const cleanup = Promise.withResolvers() + const entered = Promise.withResolvers() + const owner = createCoreMaintenance({ + profile: { secretStore: secrets }, + data: {}, + providers: {}, + settings: {}, + runtime: { + execution: {}, + mcpApps: { clear: vi.fn() }, + syncService: { isBackupInProgress: () => false } + }, + databaseRequests: gate, + startupWorkloadCoordinator: {}, + native: { + configurationHost: { + pause: async () => { + throw failure + } + } + }, + isStopping: () => false, + stopForCleanup: async () => { + entered.resolve() + await cleanup.promise + } + } as unknown as Parameters[0]) + let settled = false + const rejected = expect( + owner.run({ kind: 'repair-schema' }).finally(() => { + settled = true + }) + ).rejects.toBe(failure) + await entered.promise + expect(gate.state).toBe('failed') + expect(previousCredential).toThrow('interrupted') + expect(() => secrets.captureOperation()).toThrow('interrupted') + expect(settled).toBe(false) + await expect(gate.runRequest('sessions.list', async () => [])).rejects.toThrow('failed') + cleanup.resolve() + await rejected + }) + + it.each([false, true])( + 'keeps credentials fenced until preparation recovery acknowledges (stopping=%s)', + async (stopping) => { + const gate = new DatabaseRequestGate(() => undefined) + const secrets = createSecrets() + const previousCredential = secrets.captureOperation() + const failure = new Error('Maintenance preparation was canceled') + const resuming = Promise.withResolvers() + const released = Promise.withResolvers() + let stopped = false + const owner = createCoreMaintenance({ + profile: { secretStore: secrets }, + data: {}, + providers: { + providerRuntime: { + invalidateGitHubCopilotSignIns: async () => { + throw failure + } + }, + agentSettings: { notifyAgentCatalogChanged: vi.fn() } + }, + settings: {}, + runtime: { + execution: {}, + mcpApps: { clear() {} }, + syncService: { isBackupInProgress: () => false } + }, + databaseRequests: gate, + startupWorkloadCoordinator: {}, + native: { + configurationHost: { + pause: async () => {}, + resume: async () => { + resuming.resolve() + await released.promise + } + } + }, + isStopping: () => stopped, + stopForCleanup: vi.fn() + } as unknown as Parameters[0]) + const maintenance = owner.run({ kind: 'repair-schema' }) + const rejected = stopping + ? expect(maintenance).rejects.toBeInstanceOf(AggregateError) + : expect(maintenance).rejects.toBe(failure) + await resuming.promise + try { + expect(gate.state).toBe('maintenance') + expect(previousCredential).toThrow('interrupted') + expect(() => secrets.captureOperation()).toThrow('interrupted') + if (stopping) { + stopped = true + gate.beginShutdown() + } + } finally { + released.resolve() + } + await rejected + expect(gate.state).toBe(stopping ? 'stopping' : 'running') + expect(previousCredential).toThrow('interrupted') + if (stopping) expect(() => secrets.captureOperation()).toThrow('interrupted') + else expect(() => secrets.captureOperation()).not.toThrow() + } + ) + + it('drains an admitted request while rejecting new ordinary work', async () => { + const gate = new DatabaseRequestGate(() => undefined) + const result = deferred() + const request = gate.runRequest('providers.add', () => result.promise) + gate.beginMaintenance() + let drained = false + const drain = gate.drain().then(() => { + drained = true + }) + await Promise.resolve() + expect(drained).toBe(false) + for (const method of [ + 'config.setCustomPrompts', + 'mcp.getServers', + 'models.invoke', + 'ocr.extractUpload', + 'future.write' + ]) { + const action = vi.fn(async () => undefined) + await expect(gate.runRequest(method, action)).rejects.toThrow('maintenance is maintenance') + expect(action).not.toHaveBeenCalled() + } + result.resolve('committed') + expect(await request).toBe('committed') + await drain + gate.resume() + expect(await gate.runRequest('providers.add', async () => 'next')).toBe('next') + }) + + it('releases failed requests without hiding the original error', async () => { + const gate = new DatabaseRequestGate(() => undefined) + const error = new Error('owner failure') + await expect( + gate.runRequest('config.setCustomPrompts', async () => { + throw error + }) + ).rejects.toBe(error) + gate.beginMaintenance() + await gate.drain() + gate.resume() + }) + + it('does not include the maintenance command or native presentation in its own drain', async () => { + const gate = new DatabaseRequestGate(() => undefined) + await expect( + gate.runRequest('databaseSecurity.enable', async () => { + gate.beginMaintenance() + const native = deferred() + const presentation = gate.runRequest('window.getCurrentState', () => native.promise) + await gate.drain() + await expect( + gate.runRequest('databaseSecurity.repairSchema', async () => undefined) + ).rejects.toThrow('maintenance is maintenance') + expect(await gate.runRequest('approvals.resolve', async () => 'resolved')).toBe('resolved') + native.resolve() + await presentation + gate.resume() + return 'acknowledged' + }) + ).resolves.toBe('acknowledged') + }) + + it('bounds drain waiting and includes an old pending request in a later attempt', async () => { + vi.useFakeTimers() + const gate = new DatabaseRequestGate(() => undefined) + const result = deferred() + const request = gate.runRequest('providers.add', () => result.promise) + gate.beginMaintenance() + const timeout = expect(gate.drain()).rejects.toThrow('30000ms') + await vi.advanceTimersByTimeAsync(30_000) + await timeout + expect(vi.getTimerCount()).toBe(0) + gate.resume() + gate.beginMaintenance() + let drained = false + const next = gate.drain().then(() => { + drained = true + }) + await Promise.resolve() + expect(drained).toBe(false) + result.resolve() + await request + await next + expect(vi.getTimerCount()).toBe(0) + gate.resume() + }) + + it('keeps failed maintenance closed and still honors the application lifecycle', async () => { + let active = true + const gate = new DatabaseRequestGate(() => { + if (!active) throw new Error('Application stopped') + }) + gate.beginMaintenance() + gate.fail() + await expect(gate.runRequest('providers.add', async () => undefined)).rejects.toThrow( + 'maintenance is failed' + ) + expect(await gate.runRequest('databaseSecurity.getStatus', async () => 'failed')).toBe('failed') + expect(() => gate.resume()).toThrow('cannot resume') + active = false + await expect(gate.runRequest('window.getCurrentState', async () => undefined)).rejects.toThrow( + 'Application stopped' + ) + }) + + it('drains requests and actual maintenance completion during shutdown', async () => { + const gate = new DatabaseRequestGate(() => undefined) + const normal = deferred() + const owner = deferred() + const request = gate.runRequest('providers.add', () => normal.promise) + const maintenance = gate.runMaintenance(() => owner.promise) + await Promise.resolve() + gate.beginShutdown() + let drained = false + const drain = gate.drainForShutdown().then(() => { + drained = true + }) + for (const method of ['providers.add', 'window.getCurrentState', 'device.restartApp']) { + await expect(gate.runRequest(method, async () => undefined)).rejects.toThrow('stopping') + } + normal.resolve() + await request + await new Promise((resolve) => setImmediate(resolve)) + expect(drained).toBe(false) + const error = new Error('interrupted maintenance') + const rejected = expect(maintenance).rejects.toBe(error) + owner.reject(error) + await rejected + await drain + expect(gate.state).toBe('stopping') + }) + + it.each(['device.restartApp', 'device.resetDataByType'])( + 'does not wait for the %s request that owns shutdown', + async (method) => { + const gate = new DatabaseRequestGate(() => undefined) + await gate.runRequest(method, async () => { + gate.beginShutdown() + await gate.drainForShutdown() + }) + } + ) + + it('includes lifecycle authorization before its shutdown dispatch', async () => { + const gate = new DatabaseRequestGate(() => undefined) + const release = deferred() + const authorization = gate.runAuthorization('device.resetDataByType', () => release.promise) + gate.beginShutdown() + let drained = false + const drain = gate.drainForShutdown().then(() => { + drained = true + }) + await Promise.resolve() + expect(drained).toBe(false) + release.resolve() + await authorization + await drain + }) + + it('bounds shutdown waiting without losing an unfinished maintenance owner', async () => { + vi.useFakeTimers() + const gate = new DatabaseRequestGate(() => undefined) + const release = deferred() + const maintenance = gate.runMaintenance(() => release.promise) + gate.beginShutdown() + const timeout = expect(gate.drainForShutdown()).rejects.toThrow('30000ms') + await vi.advanceTimersByTimeAsync(30_000) + await timeout + expect(vi.getTimerCount()).toBe(0) + let drained = false + const retry = gate.drainForShutdown().then(() => { + drained = true + }) + await vi.advanceTimersByTimeAsync(0) + expect(drained).toBe(false) + release.resolve() + await maintenance + await retry + expect(vi.getTimerCount()).toBe(0) + expect(() => gate.resume()).toThrow('cannot resume') + }) + + it('retains exclusive maintenance ownership after ordinary admission resumes', async () => { + const gate = new DatabaseRequestGate(() => undefined) + const entered = deferred() + const release = deferred() + const maintenance = gate.runMaintenance(async () => { + gate.resume() + entered.resolve() + await release.promise + }) + await entered.promise + await expect(gate.runMaintenance(async () => undefined)).rejects.toThrow('still completing') + expect(await gate.runRequest('providers.add', async () => 'ready')).toBe('ready') + release.resolve() + await maintenance + await gate.runMaintenance(async () => gate.resume()) + }) +}) diff --git a/test/main/app/databaseSecurity.test.ts b/test/main/app/databaseSecurity.test.ts index 4f988cb8a6..793a3610cf 100644 --- a/test/main/app/databaseSecurity.test.ts +++ b/test/main/app/databaseSecurity.test.ts @@ -1,5 +1,6 @@ import { beforeEach, describe, expect, it, vi } from 'vitest' import { shouldExcludeFromSqliteCopy } from '@/data/sqliteCopyExclusions' +import type { DatabaseSecurityMetadata } from '@/app/databaseSecurity' const mocks = vi.hoisted(() => { const stores = new Map>() @@ -9,10 +10,6 @@ const mocks = vi.hoisted(() => { decryptString: vi.fn((value: Buffer) => value.toString('utf8').replace(/^wrapped:/, '')), getSelectedStorageBackend: vi.fn(() => 'basic_text') } - const app = { - getPath: vi.fn(() => '/tmp/deepchat-test'), - quit: vi.fn() - } const openSQLiteDatabase = vi.fn(() => ({ prepare: vi.fn(() => ({ get: vi.fn(() => ({ name: 'schema_versions' })) @@ -23,42 +20,34 @@ const mocks = vi.hoisted(() => { return { stores, safeStorage, - app, openSQLiteDatabase } }) -vi.mock('electron', () => ({ - app: mocks.app, - safeStorage: mocks.safeStorage +vi.mock('../../../src/main/data/databaseConnection', () => ({ + openSQLiteDatabase: mocks.openSQLiteDatabase })) -vi.mock('electron-store', () => ({ - default: class MockElectronStore { - private readonly name: string - - constructor(options: { name: string; defaults?: Record }) { - this.name = options.name - if (!mocks.stores.has(this.name)) { - mocks.stores.set(this.name, structuredClone(options.defaults ?? {})) +function fixtureOptions() { + return { + dbPath: '/tmp/deepchat-test/agent.db', + metadata: { + get: () => + mocks.stores.get('database-security')?.metadata as DatabaseSecurityMetadata | undefined, + set: (_key: 'metadata', metadata: DatabaseSecurityMetadata) => { + mocks.stores.set('database-security', { metadata }) } - } - - get(key: string) { - return mocks.stores.get(this.name)?.[key] - } - - set(key: string, value: unknown) { - const store = mocks.stores.get(this.name) ?? {} - store[key] = value - mocks.stores.set(this.name, store) + }, + crypto: { + isAvailable: async () => mocks.safeStorage.isEncryptionAvailable(), + encrypt: async (value: string) => mocks.safeStorage.encryptString(value).toString('base64'), + decrypt: async (wrapped: string) => + mocks.safeStorage.decryptString(Buffer.from(wrapped, 'base64')), + getBackend: async () => + process.platform === 'linux' ? mocks.safeStorage.getSelectedStorageBackend() : undefined } } -})) - -vi.mock('../../../src/main/data/databaseConnection', () => ({ - openSQLiteDatabase: mocks.openSQLiteDatabase -})) +} const enabledMetadata = (overrides: Record = {}) => ({ version: 1, @@ -74,7 +63,7 @@ async function listMigratableTableNames( foreignKeys: Record = {} ): Promise { const { DatabaseSecurityService } = await import('@/app/databaseSecurity') - const presenter = new DatabaseSecurityService({ dbPath: '/tmp/deepchat-test/agent.db' }) + const presenter = new DatabaseSecurityService(fixtureOptions()) const db = { prepare: vi.fn((sql: string) => { const tableName = sql.match(/foreign_key_list\("([^"]+)"\)/u)?.[1] @@ -105,8 +94,6 @@ describe('DatabaseSecurityService', () => { value.toString('utf8').replace(/^wrapped:/, '') ) mocks.safeStorage.getSelectedStorageBackend.mockReturnValue('basic_text') - mocks.app.getPath.mockReturnValue('/tmp/deepchat-test') - mocks.app.quit.mockReset() mocks.openSQLiteDatabase.mockReset() mocks.openSQLiteDatabase.mockImplementation(() => ({ prepare: vi.fn(() => ({ @@ -126,9 +113,9 @@ describe('DatabaseSecurityService', () => { }) const { DatabaseSecurityService } = await import('@/app/databaseSecurity') - const presenter = new DatabaseSecurityService({ dbPath: '/tmp/deepchat-test/agent.db' }) + const presenter = new DatabaseSecurityService(fixtureOptions()) - expect(presenter.getStatus()).toMatchObject({ + expect(await presenter.getStatus()).toMatchObject({ enabled: true, safeStorageAvailable: false, passwordStorage: 'manual', @@ -142,7 +129,7 @@ describe('DatabaseSecurityService', () => { }) const { DatabaseSecurityService } = await import('@/app/databaseSecurity') - const presenter = new DatabaseSecurityService({ dbPath: '/tmp/deepchat-test/agent.db' }) + const presenter = new DatabaseSecurityService(fixtureOptions()) const unlockProvider = vi.fn() await expect(presenter.resolveStartupPassword(unlockProvider)).resolves.toBe('secret') @@ -159,7 +146,7 @@ describe('DatabaseSecurityService', () => { }) const { DatabaseSecurityService } = await import('@/app/databaseSecurity') - const presenter = new DatabaseSecurityService({ dbPath: '/tmp/deepchat-test/agent.db' }) + const presenter = new DatabaseSecurityService(fixtureOptions()) const requests: unknown[] = [] const password = await presenter.resolveStartupPassword(async (request) => { @@ -179,9 +166,9 @@ describe('DatabaseSecurityService', () => { it('persists recovered encryption metadata without forging a migration', async () => { mocks.safeStorage.isEncryptionAvailable.mockReturnValue(false) const { DatabaseSecurityService } = await import('@/app/databaseSecurity') - const presenter = new DatabaseSecurityService({ dbPath: '/tmp/deepchat-test/agent.db' }) + const presenter = new DatabaseSecurityService(fixtureOptions()) - presenter.persistRecoveredEncryptionMetadata('recovered-secret') + await presenter.persistRecoveredEncryptionMetadata('recovered-secret') expect(mocks.stores.get('database-security')?.metadata).toMatchObject({ version: 1, @@ -202,7 +189,7 @@ describe('DatabaseSecurityService', () => { }) const { DatabaseSecurityService } = await import('@/app/databaseSecurity') - const presenter = new DatabaseSecurityService({ dbPath: '/tmp/deepchat-test/agent.db' }) + const presenter = new DatabaseSecurityService(fixtureOptions()) await expect(presenter.resolveStartupPassword(async () => 'secret')).resolves.toBe('secret') }) @@ -219,7 +206,7 @@ describe('DatabaseSecurityService', () => { }) const { DatabaseSecurityService } = await import('@/app/databaseSecurity') - const presenter = new DatabaseSecurityService({ dbPath: '/tmp/deepchat-test/agent.db' }) + const presenter = new DatabaseSecurityService(fixtureOptions()) const requests: unknown[] = [] await expect( @@ -247,14 +234,14 @@ describe('DatabaseSecurityService', () => { }) const { DatabaseSecurityService } = await import('@/app/databaseSecurity') - const presenter = new DatabaseSecurityService({ dbPath: '/tmp/deepchat-test/agent.db' }) + const presenter = new DatabaseSecurityService(fixtureOptions()) await expect(presenter.resolveStartupPassword(async () => 'secret')).rejects.toBeInstanceOf( OrphanWalDatabaseError ) }) - it('quits when startup unlock is canceled', async () => { + it('rejects when startup unlock is canceled', async () => { mocks.stores.set('database-security', { metadata: enabledMetadata({ passwordStorage: 'manual', @@ -263,20 +250,78 @@ describe('DatabaseSecurityService', () => { }) const { DatabaseSecurityService } = await import('@/app/databaseSecurity') - const presenter = new DatabaseSecurityService({ dbPath: '/tmp/deepchat-test/agent.db' }) + const presenter = new DatabaseSecurityService(fixtureOptions()) await expect(presenter.resolveStartupPassword(async () => null)).rejects.toThrow( 'Database unlock canceled' ) - expect(mocks.app.quit).toHaveBeenCalled() + }) + + it('discards a password decrypted after the owner stops', async () => { + mocks.stores.set('database-security', { metadata: enabledMetadata() }) + const options = fixtureOptions() + let finish!: (password: string) => void + options.crypto.decrypt = vi.fn( + () => + new Promise((resolve) => { + finish = resolve + }) + ) + const { DatabaseSecurityService } = await import('@/app/databaseSecurity') + const service = new DatabaseSecurityService(options) + const unlock = vi.fn() + const result = service.resolveStartupPassword(unlock) + const rejected = expect(result).rejects.toThrow('Database security operation interrupted') + await vi.waitFor(() => expect(options.crypto.decrypt).toHaveBeenCalled()) + service.stop() + finish('secret') + await rejected + expect(mocks.openSQLiteDatabase).not.toHaveBeenCalled() + expect(unlock).not.toHaveBeenCalled() + }) + + it('discards a manual password wrapped after the owner stops', async () => { + const original = enabledMetadata({ passwordStorage: 'manual', wrappedPassword: undefined }) + mocks.stores.set('database-security', { metadata: original }) + const options = fixtureOptions() + let finish!: (wrapped: string) => void + options.crypto.encrypt = vi.fn( + () => + new Promise((resolve) => { + finish = resolve + }) + ) + const { DatabaseSecurityService } = await import('@/app/databaseSecurity') + const service = new DatabaseSecurityService(options) + const result = service.resolveStartupPassword(async () => 'secret') + const rejected = expect(result).rejects.toThrow('Database security operation interrupted') + await vi.waitFor(() => expect(options.crypto.encrypt).toHaveBeenCalled()) + service.stop() + finish('late-wrapped-secret') + await rejected + expect(mocks.stores.get('database-security')?.metadata).toEqual(original) + }) + + it('propagates native wrapping failure without retrying a valid manual password', async () => { + const original = enabledMetadata({ passwordStorage: 'manual', wrappedPassword: undefined }) + mocks.stores.set('database-security', { metadata: original }) + mocks.safeStorage.encryptString.mockImplementationOnce(() => { + throw new Error('vault locked') + }) + const { DatabaseSecurityService } = await import('@/app/databaseSecurity') + const service = new DatabaseSecurityService(fixtureOptions()) + const unlock = vi.fn(async () => 'secret') + await expect(service.resolveStartupPassword(unlock)).rejects.toThrow('vault locked') + expect(unlock).toHaveBeenCalledTimes(1) + expect(mocks.stores.get('database-security')?.metadata).toEqual(original) }) it('cleans legacy provider JSON before enabling encryption', async () => { const { DatabaseSecurityService } = await import('@/app/databaseSecurity') - const presenter = new DatabaseSecurityService({ dbPath: '/tmp/deepchat-test/agent.db' }) + const presenter = new DatabaseSecurityService(fixtureOptions()) const migrateDatabase = vi - .spyOn(presenter as unknown as { migrateDatabase: () => Promise }, 'migrateDatabase') - .mockResolvedValue(undefined) + .spyOn(presenter as unknown as { migrateDatabase: () => void }, 'migrateDatabase') + .mockReturnValue(undefined) const cleanupLegacyProviderJsonForDatabaseEncryption = vi.fn(() => 1) await presenter.enableEncryption({ @@ -293,7 +338,7 @@ describe('DatabaseSecurityService', () => { it('qualifies CREATE TABLE IF NOT EXISTS for the migration target schema', async () => { const { DatabaseSecurityService } = await import('@/app/databaseSecurity') - const presenter = new DatabaseSecurityService({ dbPath: '/tmp/deepchat-test/agent.db' }) + const presenter = new DatabaseSecurityService(fixtureOptions()) expect( ( diff --git a/test/main/app/databaseSecurityNative.test.ts b/test/main/app/databaseSecurityNative.test.ts new file mode 100644 index 0000000000..b2e97bbc38 --- /dev/null +++ b/test/main/app/databaseSecurityNative.test.ts @@ -0,0 +1,240 @@ +import { afterEach, expect, it, vi } from 'vitest' +import { mkdtempSync, readFileSync, rmSync, writeFileSync, existsSync } from 'node:fs' +import { tmpdir } from 'node:os' +import { join } from 'node:path' +import { describeIfNativeSqlite } from '../nativeSqliteHarness' +import { openSQLiteDatabase } from '@/data/databaseConnection' +import { + DatabaseSecurityService, + DEFAULT_DATABASE_SECURITY_METADATA, + type DatabaseSecurityMetadata +} from '@/app/databaseSecurity' + +vi.unmock('fs') +vi.unmock('node:fs') +vi.unmock('path') +vi.unmock('node:path') + +describeIfNativeSqlite('Database security SQLCipher commit', () => { + const cleanups: Array<() => void> = [] + afterEach(() => { + for (const cleanup of cleanups.reverse()) cleanup() + cleanups.length = 0 + }) + + function fixture(password?: string) { + const directory = mkdtempSync(join(tmpdir(), 'deepchat-database-security-')) + const dbPath = join(directory, 'agent.db') + const metadataPath = join(directory, 'database-security.json') + const initial: DatabaseSecurityMetadata = password + ? { version: 1, enabled: true, cipher: 'sqlcipher', passwordStorage: 'manual' } + : { ...DEFAULT_DATABASE_SECURITY_METADATA } + writeFileSync(metadataPath, JSON.stringify(initial)) + const metadata = { + get: (_key: 'metadata') => + JSON.parse(readFileSync(metadataPath, 'utf8')) as DatabaseSecurityMetadata, + set: vi.fn((_key: 'metadata', value: DatabaseSecurityMetadata) => { + writeFileSync(metadataPath, JSON.stringify(value)) + }) + } + const crypto = { + isAvailable: vi.fn(async () => true), + encrypt: vi.fn(async (value: string) => Buffer.from(value).toString('base64')), + decrypt: vi.fn(async (wrapped: string) => Buffer.from(wrapped, 'base64').toString()), + getBackend: vi.fn(async (): Promise => undefined) + } + let db = openSQLiteDatabase(dbPath, password) + db.exec(` + CREATE TABLE agent_memory (id TEXT PRIMARY KEY, content TEXT NOT NULL); + INSERT INTO agent_memory VALUES ('claim', 'durable memory'); + CREATE TABLE deepchat_tape_entries (id TEXT PRIMARY KEY, parent_id TEXT); + INSERT INTO deepchat_tape_entries VALUES ('root', NULL), ('child', 'root'); + CREATE TABLE app_settings (key TEXT PRIMARY KEY, value_json TEXT NOT NULL); + INSERT INTO app_settings VALUES ('copyWithCotEnabled', 'false'); + `) + const database = { + getDatabasePath: () => dbPath, + checkpointAndClose: vi.fn(() => { + db.pragma('wal_checkpoint(TRUNCATE)') + db.close() + }), + close: () => { + if (db.open) db.close() + }, + reopenWithPassword: vi.fn((key?: string) => { + db = openSQLiteDatabase(dbPath, key) + }), + isOpen: () => db.open + } + const providerSettings = { cleanupLegacyProviderJsonForDatabaseEncryption: vi.fn(() => 0) } + const service = new DatabaseSecurityService({ dbPath, metadata, crypto }) + cleanups.push(() => { + service.stop() + database.close() + rmSync(directory, { recursive: true, force: true }) + }) + return { + service, + metadata, + crypto, + database, + providerSettings, + dbPath, + metadataPath, + initial, + assertData(key?: string) { + const check = openSQLiteDatabase(dbPath, key) + try { + expect(check.pragma('quick_check', { simple: true })).toBe('ok') + expect(check.prepare('SELECT * FROM agent_memory').all()).toEqual([ + { id: 'claim', content: 'durable memory' } + ]) + expect(check.prepare('SELECT * FROM deepchat_tape_entries ORDER BY id').all()).toEqual([ + { id: 'child', parent_id: 'root' }, + { id: 'root', parent_id: null } + ]) + expect(check.prepare('SELECT * FROM app_settings').get()).toEqual({ + key: 'copyWithCotEnabled', + value_json: 'false' + }) + } finally { + check.close() + } + expect(existsSync(`${dbPath}.migration-tmp`)).toBe(false) + expect(existsSync(`${dbPath}.migration-rollback`)).toBe(false) + } + } + } + + it('preserves durable rows across enable, rekey, manual unlock and disable', async () => { + const f = fixture() + expect(await f.service.enableEncryption({ password: 'first', ...f })).toMatchObject({ + enabled: true, + migrationInProgress: false + }) + f.assertData('first') + expect(() => openSQLiteDatabase(f.dbPath)).toThrow() + await expect( + f.service.changePassword({ currentPassword: 'wrong', newPassword: 'next', ...f }) + ).rejects.toThrow() + f.assertData('first') + expect( + await f.service.changePassword({ currentPassword: 'first', newPassword: 'next', ...f }) + ).toMatchObject({ enabled: true, migrationInProgress: false }) + f.assertData('next') + expect(() => openSQLiteDatabase(f.dbPath, 'first')).toThrow() + f.crypto.isAvailable.mockResolvedValue(false) + const manual = await f.service.changePassword({ + currentPassword: 'next', + newPassword: 'manual', + ...f + }) + expect(manual).toMatchObject({ + enabled: true, + passwordStorage: 'manual', + manualUnlockRequired: true + }) + expect(f.metadata.get('metadata').wrappedPassword).toBeUndefined() + await expect(f.service.resolveStartupPassword(async () => 'manual')).resolves.toBe('manual') + expect(await f.service.disableEncryption({ currentPassword: 'manual', ...f })).toMatchObject({ + enabled: false, + passwordStorage: 'none', + migrationInProgress: false + }) + f.assertData() + }) + + it('keeps the original writer and metadata when native wrapping fails', async () => { + const f = fixture() + f.crypto.encrypt.mockRejectedValueOnce(new Error('vault locked')) + await expect(f.service.enableEncryption({ password: 'next', ...f })).rejects.toThrow( + 'vault locked' + ) + expect(f.database.checkpointAndClose).not.toHaveBeenCalled() + expect(f.providerSettings.cleanupLegacyProviderJsonForDatabaseEncryption).not.toHaveBeenCalled() + expect(f.metadata.get('metadata')).toEqual(f.initial) + f.assertData() + await f.service.enableEncryption({ password: 'next', ...f }) + f.assertData('next') + }) + + it.each(['enable', 'change-password', 'disable'] as const)( + 'rolls back database and metadata together when %s metadata persistence fails', + async (direction) => { + const sourceKey = direction === 'enable' ? undefined : 'original' + const f = fixture(sourceKey) + f.metadata.set.mockImplementationOnce((_key, value) => { + writeFileSync(f.metadataPath, JSON.stringify(value)) + throw new Error('metadata write failed') + }) + const result = + direction === 'enable' + ? f.service.enableEncryption({ password: 'next', ...f }) + : direction === 'change-password' + ? f.service.changePassword({ currentPassword: 'original', newPassword: 'next', ...f }) + : f.service.disableEncryption({ currentPassword: 'original', ...f }) + await expect(result).rejects.toThrow('metadata write failed') + expect(f.database.isOpen()).toBe(true) + expect(f.metadata.get('metadata')).toEqual(f.initial) + f.assertData(sourceKey) + expect(() => openSQLiteDatabase(f.dbPath, 'next')).toThrow() + } + ) + + it('restores the original encrypted writer after target reopen fails', async () => { + const f = fixture('original') + f.database.reopenWithPassword.mockImplementationOnce(() => { + throw new Error('reopen failed') + }) + await expect( + f.service.changePassword({ currentPassword: 'original', newPassword: 'next', ...f }) + ).rejects.toThrow('reopen failed') + expect(f.database.isOpen()).toBe(true) + expect(f.metadata.get('metadata')).toEqual(f.initial) + f.assertData('original') + expect(() => openSQLiteDatabase(f.dbPath, 'next')).toThrow() + }) + + it('locks the profile through pending crypto and discards stopped results before mutation', async () => { + const f = fixture() + let finish!: (wrapped: string) => void + f.crypto.encrypt.mockImplementationOnce( + () => + new Promise((resolve) => { + finish = resolve + }) + ) + const result = f.service.enableEncryption({ password: 'next', ...f }) + const rejected = expect(result).rejects.toThrow('Database security operation interrupted') + await vi.waitFor(() => expect(f.crypto.encrypt).toHaveBeenCalled()) + await expect(f.service.enableEncryption({ password: 'other', ...f })).rejects.toThrow( + 'already in progress' + ) + await expect(f.service.persistRecoveredEncryptionMetadata('other')).rejects.toThrow( + 'already in progress' + ) + expect( + () => + new DatabaseSecurityService({ dbPath: f.dbPath, metadata: f.metadata, crypto: f.crypto }) + ).toThrow('already in progress') + f.service.stop() + finish('late-ciphertext') + await rejected + expect(f.database.checkpointAndClose).not.toHaveBeenCalled() + expect(f.metadata.get('metadata')).toEqual(f.initial) + f.assertData() + const replacement = new DatabaseSecurityService({ + dbPath: f.dbPath, + metadata: f.metadata, + crypto: f.crypto + }) + try { + expect(await replacement.getStatus()).toMatchObject({ + enabled: false, + migrationInProgress: false + }) + } finally { + replacement.stop() + } + }) +}) diff --git a/test/main/app/databaseStartup.test.ts b/test/main/app/databaseStartup.test.ts index bfd87084a9..bd23c0550f 100644 --- a/test/main/app/databaseStartup.test.ts +++ b/test/main/app/databaseStartup.test.ts @@ -1,4 +1,6 @@ import { beforeEach, describe, expect, it, vi } from 'vitest' +import type { DatabaseSecurityService } from '@/app/databaseSecurity' +import type { DatabaseStartupPresentation } from '@/app/databaseStartup' const mocks = vi.hoisted(() => ({ appQuit: vi.fn(), @@ -9,12 +11,6 @@ const mocks = vi.hoisted(() => ({ DatabaseInitializer: vi.fn() })) -vi.mock('electron', () => ({ - app: { - quit: mocks.appQuit - } -})) - vi.mock('@/data/databaseStartupRecovery', async (importOriginal) => { const actual = await importOriginal() return { @@ -58,14 +54,19 @@ describe('initializeMainDatabaseWithRecovery', () => { return { security: { getDatabasePath: () => '/tmp/deepchat-test/app_db/agent.db', - resolveStartupPassword: vi.fn(async () => overrides?.password), + resolveStartupPassword: vi.fn( + async () => overrides?.password + ), validatePassword: vi.fn(), persistRecoveredEncryptionMetadata: vi.fn(), clearEncryptionMetadata: vi.fn() }, splash: { - requestDatabaseUnlock: vi.fn(), - requestDatabaseRecovery: vi.fn(async () => recovery.shift() ?? null) + quit: mocks.appQuit, + requestDatabaseUnlock: vi.fn(), + requestDatabaseRecovery: vi.fn( + async () => recovery.shift() ?? null + ) } } } @@ -82,6 +83,29 @@ describe('initializeMainDatabaseWithRecovery', () => { expect(mocks.quarantine).not.toHaveBeenCalled() }) + it('quits from the desktop unlock adapter when unlock is canceled', async () => { + const { initializeMainDatabaseWithRecovery } = await import('@/app/databaseStartup') + const ports = createPorts() + ports.splash.requestDatabaseUnlock.mockResolvedValue(null) + ports.security.resolveStartupPassword.mockImplementation(async (unlock) => { + const password = await unlock({ reason: 'manual-required', safeStorageAvailable: true }) + if (password === null) throw new Error('Database unlock canceled') + return password + }) + const quit = Promise.withResolvers() + mocks.appQuit.mockReturnValue(quit.promise) + let settled = false + const request = initializeMainDatabaseWithRecovery(ports as never).catch((error) => { + settled = true + return error + }) + await vi.waitFor(() => expect(mocks.appQuit).toHaveBeenCalledTimes(1)) + expect(settled).toBe(false) + quit.resolve() + await expect(request).resolves.toMatchObject({ message: 'Database unlock canceled' }) + expect(mocks.initialize).not.toHaveBeenCalled() + }) + it('quarantines a damaged database only after the user chooses start empty', async () => { const database = { id: 'fresh' } mocks.initialize diff --git a/test/main/app/logging.test.ts b/test/main/app/logging.test.ts index 64073e2850..93adfb49c1 100644 --- a/test/main/app/logging.test.ts +++ b/test/main/app/logging.test.ts @@ -1,5 +1,5 @@ import { inspect } from 'node:util' -import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' +import { afterEach, describe, expect, it, vi } from 'vitest' import { originalConsole } from '@shared/logger' import { LoggingService } from '@/app/logging' import { @@ -14,32 +14,66 @@ import { } from '@/app/startupWorkloadCoordinator' describe('LoggingService', () => { - beforeEach(() => vi.useFakeTimers()) - afterEach(() => vi.useRealTimers()) - it.each([false, true])( - 'applies the %s persistence gate before publishing and scheduling restart', - (enabled) => { + 'awaits the native persistence gate before publishing %s', + async (enabled) => { const operations: string[] = [] const settings = { get: vi.fn(() => false), set: vi.fn(() => operations.push('settings')) } - const restart = vi.fn(() => operations.push('restart')) const publish = vi.fn(() => operations.push('publish')) - const setPersistence = vi.fn(() => operations.push('persistence')) - const service = new LoggingService(settings as never, restart, publish, setPersistence) - - service.setEnabled(enabled) - - expect(setPersistence).toHaveBeenCalledWith(enabled) - expect(operations).toEqual(['settings', 'persistence', 'publish']) - vi.advanceTimersByTime(999) - expect(restart).not.toHaveBeenCalled() - vi.advanceTimersByTime(1) - expect(restart).toHaveBeenCalledOnce() + let finish!: () => void + const pending = new Promise((resolve) => { + finish = resolve + }) + const host = { applyLogging: vi.fn(() => pending), openLoggingFolder: vi.fn(async () => {}) } + const service = new LoggingService(settings as never, host, publish, vi.fn()) + const changed = service.setEnabled(enabled) + await vi.waitFor(() => expect(host.applyLogging).toHaveBeenCalledWith(enabled)) + expect(operations).toEqual(['settings']) + finish() + await changed + expect(operations).toEqual(['settings', 'publish']) } ) + + it('does not acknowledge a failed native logging change or hide a folder error', async () => { + const publish = vi.fn() + const host = { + applyLogging: vi.fn(async () => { + throw new Error('native gate unavailable') + }), + openLoggingFolder: vi.fn(async () => { + throw new Error('folder unavailable') + }) + } + const service = new LoggingService({ set: vi.fn() } as never, host, publish, vi.fn()) + await expect(service.setEnabled(true)).rejects.toThrow('native gate unavailable') + expect(publish).not.toHaveBeenCalled() + await expect(service.openFolder()).rejects.toThrow('folder unavailable') + }) +}) + +it('controls legacy console debug output through an explicit host flag', async () => { + const legacy = await vi.importActual('@shared/logger') + const debug = vi.spyOn(legacy.originalConsole, 'debug').mockImplementation(() => undefined) + try { + legacy.setLegacyConsoleDebugEnabled(false) + legacy.default.debug('quiet') + expect(debug).not.toHaveBeenCalled() + legacy.setLegacyConsoleDebugEnabled(true) + legacy.default.debug('debug') + legacy.default.verbose('verbose') + legacy.default.silly('silly') + expect(debug.mock.calls).toEqual([['debug'], ['verbose'], ['silly']]) + expect(legacy.default.info).toBe(legacy.originalConsole.info) + expect(legacy.default.warn).toBe(legacy.originalConsole.warn) + expect(legacy.default.error).toBe(legacy.originalConsole.error) + } finally { + legacy.setLegacyConsoleDebugEnabled(false) + debug.mockRestore() + } }) describe('Main startup diagnostics', () => { diff --git a/test/main/app/profileStartupNative.test.ts b/test/main/app/profileStartupNative.test.ts new file mode 100644 index 0000000000..b9b0648da6 --- /dev/null +++ b/test/main/app/profileStartupNative.test.ts @@ -0,0 +1,181 @@ +import { afterEach, expect, it, vi } from 'vitest' +import { mkdtempSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs' +import { tmpdir } from 'node:os' +import { join } from 'node:path' +import { spawn } from 'node:child_process' +import { once } from 'node:events' +import { createRequire } from 'node:module' +import { describeIfNativeSqlite } from '../nativeSqliteHarness' +import { initializeCoreProfile } from '@/app/profileStartup' + +vi.unmock('fs') +vi.unmock('node:fs') +vi.unmock('path') +vi.unmock('node:path') +vi.unmock('child_process') +vi.unmock('node:child_process') + +describeIfNativeSqlite('Core profile ownership', () => { + const cleanups: Array<() => void | Promise> = [] + afterEach(async () => { + for (const cleanup of cleanups.reverse()) await cleanup() + cleanups.length = 0 + }) + + function fixture() { + const directory = mkdtempSync(join(tmpdir(), 'deepchat-core-profile-')) + const profile = join(directory, 'profile') + const home = join(directory, 'home') + mkdirSync(profile) + mkdirSync(home) + cleanups.push(() => rmSync(directory, { recursive: true, force: true })) + const host = { + setLoggingEnabled: vi.fn(async (_enabled: boolean) => {}), + showDatabaseUnlockProgress: vi.fn(async () => {}), + requestDatabaseUnlock: vi.fn(async () => null), + requestDatabaseRecovery: vi.fn(async () => null), + quit: vi.fn(async () => {}) + } + const unavailable = async (): Promise => { + throw new Error('Unexpected crypto operation') + } + return { + directory, + options: { + userDataDirectory: profile, + homeDirectory: home, + currentAppVersion: '1.1.4', + host, + crypto: { + isAvailable: async () => false, + encrypt: unavailable, + decrypt: unavailable, + getBackend: async () => undefined + } + } + } + } + + async function open(options: Parameters[0]) { + const profile = await initializeCoreProfile(options) + cleanups.push(() => profile.closeProfile()) + return profile + } + + it('retains ownership through pending startup and maintenance database closure', async () => { + const { options } = fixture() + const gate = Promise.withResolvers() + options.host.setLoggingEnabled.mockImplementationOnce(() => gate.promise) + const pending = initializeCoreProfile(options) + try { + await vi.waitFor(() => expect(options.host.setLoggingEnabled).toHaveBeenCalledOnce()) + const settings = join(options.userDataDirectory, 'app-settings.json') + const before = readFileSync(settings) + await expect(initializeCoreProfile(options)).rejects.toMatchObject({ + name: 'CoreProfileOwnershipUnavailableError' + }) + expect(options.host.setLoggingEnabled).toHaveBeenCalledOnce() + expect(readFileSync(settings)).toEqual(before) + } finally { + gate.resolve() + const profile = await pending + cleanups.push(() => profile.closeProfile()) + } + const profile = await pending + profile.database.close() + await expect(initializeCoreProfile(options)).rejects.toMatchObject({ + name: 'CoreProfileOwnershipUnavailableError' + }) + profile.database.reopen() + profile.closeProfile() + await open(options) + }) + + it('retains ownership when primary database close fails', async () => { + const { options } = fixture() + const ownership: boolean[] = [] + const profile = await open({ ...options, onOwnershipChanged: (owned) => ownership.push(owned) }) + const close = vi.spyOn(profile.database, 'close').mockImplementationOnce(() => { + throw new Error('Database close failed') + }) + expect(() => profile.closeProfile()).toThrow('Database close failed') + expect(ownership).toEqual([true]) + await expect(initializeCoreProfile(options)).rejects.toMatchObject({ + name: 'CoreProfileOwnershipUnavailableError' + }) + close.mockRestore() + profile.closeProfile() + expect(ownership).toEqual([true, false]) + await open(options) + }) + + it('releases ownership after failed initialization', async () => { + const { options } = fixture() + const legacy = join(options.userDataDirectory, 'model-config.json') + writeFileSync(legacy, '{invalid') + await expect(initializeCoreProfile(options)).rejects.toThrow() + writeFileSync(legacy, '{}') + await open(options) + }) + + it('fences delayed credential work before releasing ownership', async () => { + const { options } = fixture() + options.crypto.isAvailable = async () => true + const profile = await open(options) + const wrapped = Promise.withResolvers() + const encrypt = vi.spyOn(options.crypto, 'encrypt').mockReturnValueOnce(wrapped.promise) + const pending = profile.secretStore.wrap('credential-fixture') + await vi.waitFor(() => expect(encrypt).toHaveBeenCalledOnce()) + profile.closeProfile() + wrapped.resolve('late-wrapped-fixture') + await expect(pending).rejects.toThrow('interrupted') + expect(() => profile.secretStore.setWrapped('cloudSyncSecret', 'late-wrapped-fixture')).toThrow( + 'interrupted' + ) + encrypt.mockRestore() + await open(options) + }) + + it('rejects a live process owner and permits recovery only after its exit', async () => { + const { directory, options } = fixture() + const script = join(directory, 'owner.cjs') + const require = createRequire(join(process.cwd(), 'package.json')) + writeFileSync( + script, + `const Database = require(process.argv[2]) +const owner = new Database(process.argv[3], { timeout: 0 }) +owner.exec('BEGIN EXCLUSIVE') +process.stdout.write('owned\\n') +setInterval(() => {}, 1000) +` + ) + const child = spawn( + process.execPath, + [ + script, + require.resolve('better-sqlite3-multiple-ciphers'), + join(options.userDataDirectory, '.core-owner.lock') + ], + { env: { ...process.env, ELECTRON_RUN_AS_NODE: '1' }, stdio: ['pipe', 'pipe', 'pipe'] } + ) + const closed = once(child, 'close') + cleanups.push(async () => { + if (child.exitCode === null && child.signalCode === null) child.kill('SIGKILL') + await closed + }) + const ready = await Promise.race([ + once(child.stdout, 'data').then(([data]) => data.toString()), + closed.then(() => { + throw new Error('Owner process exited before taking ownership') + }) + ]) + expect(ready).toBe('owned\n') + await expect(initializeCoreProfile(options)).rejects.toMatchObject({ + name: 'CoreProfileOwnershipUnavailableError' + }) + expect(options.host.setLoggingEnabled).not.toHaveBeenCalled() + child.kill('SIGKILL') + await closed + await open(options) + }) +}) diff --git a/test/main/app/rendererIpcSecurity.test.ts b/test/main/app/rendererIpcSecurity.test.ts index b9b5e1edbf..483727c9df 100644 --- a/test/main/app/rendererIpcSecurity.test.ts +++ b/test/main/app/rendererIpcSecurity.test.ts @@ -1,15 +1,23 @@ import path from 'node:path' import { pathToFileURL } from 'node:url' import { describe, expect, it, vi } from 'vitest' -import { clipboard, nativeImage, type IpcMain, type IpcMainInvokeEvent } from 'electron' -import { createRendererIpcAuthorizer } from '@/app/rendererIpcSecurity' +import { + BrowserWindow, + clipboard, + nativeImage, + webContents, + type IpcMain, + type IpcMainInvokeEvent +} from 'electron' +import { createRendererIpcAuthorizer, isRendererWindowCurrent } from '@/app/rendererIpcSecurity' import { registerClipboardIpc } from '@/app/clipboardIpc' -import { registerDeepchatRoutes, type RouteDispatcher } from '@/routes' +import { registerDeepchatRoutes } from '@/app/routeIpc' import { CLIPBOARD_IPC_CHANNELS } from '@shared/clipboardChannels' import { DEEPCHAT_ROUTE_INVOKE_CHANNEL } from '@shared/contracts/channels' vi.mock('electron', () => ({ BrowserWindow: { fromWebContents: vi.fn() }, + webContents: { fromId: vi.fn() }, clipboard: { writeText: vi.fn(), writeImage: vi.fn(), readText: vi.fn() }, nativeImage: { createFromDataURL: vi.fn() } })) @@ -37,6 +45,25 @@ const authorize = createRendererIpcAuthorizer({ }) describe('native renderer document boundary', () => { + it('revalidates the current native renderer and window before presentation', () => { + const contents = { isDestroyed: () => false } + const window = { id: 7, isDestroyed: () => false } + vi.mocked(webContents.fromId).mockReturnValue(contents as never) + vi.mocked(BrowserWindow.fromWebContents).mockReturnValue(window as never) + const caller = { kind: 'renderer', webContentsId: 42, windowId: 7 } as const + expect(isRendererWindowCurrent(caller)).toBe(true) + expect(isRendererWindowCurrent({ ...caller, windowId: 8 })).toBe(false) + expect(isRendererWindowCurrent({ ...caller, windowId: null })).toBe(false) + window.isDestroyed = () => true + expect(isRendererWindowCurrent(caller)).toBe(false) + vi.mocked(BrowserWindow.fromWebContents).mockReturnValue(null) + expect(isRendererWindowCurrent({ ...caller, windowId: null })).toBe(true) + contents.isDestroyed = () => true + expect(isRendererWindowCurrent({ ...caller, windowId: null })).toBe(false) + vi.mocked(webContents.fromId).mockReturnValue(undefined) + expect(isRendererWindowCurrent(caller)).toBe(false) + }) + it.each([ appUrl, `${appUrl}?window=2#/chat`, @@ -61,6 +88,8 @@ describe('native renderer document boundary', () => { 'http://user@localhost:5173/index.html', `${appUrl}/preview.html`, new URL('preview.html', rendererDirectoryUrl).href, + new URL('floating/index.html', rendererDirectoryUrl).href, + 'http://localhost:5173/floating/', 'file:///tmp/index.html', 'not a URL' ])('rejects unrelated documents despite the app preload: %s', (url) => { @@ -77,10 +106,10 @@ describe('native renderer document boundary', () => { it('rejects a same-origin child, missing frame, destroyed sender, and stale document', () => { const child = eventFor() - child.senderFrame = { url: appUrl } as IpcMainInvokeEvent['senderFrame'] + Object.defineProperty(child, 'senderFrame', { value: { url: appUrl } }) expect(() => authorize(child)).toThrow('top-level') const missing = eventFor() - missing.senderFrame = null + Object.defineProperty(missing, 'senderFrame', { value: null }) expect(() => authorize(missing)).toThrow('top-level') const destroyed = eventFor() destroyed.sender.isDestroyed = () => true @@ -116,6 +145,17 @@ describe('native renderer document boundary', () => { Object.defineProperty(event.sender, 'id', { value: 43 }) expect(() => authorize(event, 'plugins.get')).toThrow('Native IPC') expect(() => authorize(eventFor('https://example.com/'), 'plugins.get')).toThrow('Native IPC') + const lookup = vi.fn() + const invalidRegistry = createRendererIpcAuthorizer({ + rendererDirectoryUrl, + pluginSettingsWindow: { getPluginIdForWebContents: lookup } + }) + for (const invalid of [Promise.resolve(null), {}, undefined, 42]) { + lookup.mockReturnValue(invalid) + expect(() => invalidRegistry(eventFor('file:///tmp/foreign.html'), 'plugins.get')).toThrow( + 'Native IPC' + ) + } }) it('authorizes before route dispatch or any clipboard side effect', async () => { @@ -132,13 +172,7 @@ describe('native renderer document boundary', () => { vi.spyOn(clipboard, 'readText'), vi.spyOn(nativeImage, 'createFromDataURL') ] - registerDeepchatRoutes( - ipcMain, - { - appDatabaseMaintenance: { assertRouteAllowed: dispatch } - } as unknown as RouteDispatcher, - authorize - ) + registerDeepchatRoutes(ipcMain, dispatch, authorize) registerClipboardIpc(ipcMain, authorize) const untrusted = eventFor('file:///tmp/preview.html') await expect( @@ -151,4 +185,33 @@ describe('native renderer document boundary', () => { for (const spy of clipboardSpies) expect(spy).not.toHaveBeenCalled() vi.restoreAllMocks() }) + + it('derives the forwarded caller from the authorized native sender', async () => { + let invoke: (...args: unknown[]) => unknown = () => undefined + const ipcMain = { + removeHandler: vi.fn(), + handle: (_channel: string, handler: typeof invoke) => { + invoke = handler + } + } as unknown as IpcMain + const dispatch = vi.fn().mockResolvedValue({ language: 'en-US' }) + vi.mocked(BrowserWindow.fromWebContents).mockReturnValue({ id: 7 } as Electron.BrowserWindow) + registerDeepchatRoutes(ipcMain, dispatch, authorize) + const input = { caller: { kind: 'internal', component: 'migration' } } + const event = eventFor() + await expect(invoke(event, 'config.getLanguage', input)).resolves.toEqual({ + language: 'en-US' + }) + expect(dispatch).toHaveBeenCalledExactlyOnceWith( + 'config.getLanguage', + input, + { caller: { kind: 'renderer', webContentsId: 42, windowId: 7 } }, + expect.any(Function) + ) + const assertCallerActive = dispatch.mock.calls[0][3] as () => void + expect(assertCallerActive).not.toThrow() + vi.spyOn(event.sender, 'getURL').mockReturnValue('file:///tmp/preview.html') + expect(assertCallerActive).toThrow('Native IPC requires the current app document') + vi.restoreAllMocks() + }) }) diff --git a/test/main/app/rendererPerformanceLogService.test.ts b/test/main/app/rendererPerformanceLogService.test.ts index bad0c68d60..514db01d18 100644 --- a/test/main/app/rendererPerformanceLogService.test.ts +++ b/test/main/app/rendererPerformanceLogService.test.ts @@ -23,11 +23,7 @@ const validRecord = { describe('RendererPerformanceLogService', () => { it('does not create a diagnostics file when local logging is disabled', async () => { const fs = createFs() - const service = new RendererPerformanceLogService( - { get: vi.fn(() => false) } as never, - () => '/user-data', - fs - ) + const service = new RendererPerformanceLogService(() => false, '/user-data', fs) await expect(service.record(validRecord)).resolves.toBe(false) expect(fs.appendFile).not.toHaveBeenCalled() @@ -36,8 +32,8 @@ describe('RendererPerformanceLogService', () => { it('persists a sanitized, timestamped NDJSON record', async () => { const fs = createFs() const service = new RendererPerformanceLogService( - { get: vi.fn(() => true) } as never, - () => '/user-data', + () => true, + '/user-data', fs, () => 1234 ) @@ -54,11 +50,7 @@ describe('RendererPerformanceLogService', () => { it('rejects unknown fields before they reach the local log', async () => { const fs = createFs() - const service = new RendererPerformanceLogService( - { get: vi.fn(() => true) } as never, - () => '/user-data', - fs - ) + const service = new RendererPerformanceLogService(() => true, '/user-data', fs) await expect( service.record({ ...validRecord, sessionId: 'sensitive-session-id' }) @@ -83,8 +75,8 @@ describe('RendererPerformanceLogService', () => { writeOrder.push('second') }) const service = new RendererPerformanceLogService( - { get: vi.fn(() => true) } as never, - () => '/user-data', + () => true, + '/user-data', createFs({ appendFile }) ) @@ -99,11 +91,7 @@ describe('RendererPerformanceLogService', () => { it('rotates the bounded record file and retains only one previous generation', async () => { const fs = createFs({ stat: vi.fn().mockResolvedValue({ size: 10 * 1024 * 1024 }) }) - const service = new RendererPerformanceLogService( - { get: vi.fn(() => true) } as never, - () => '/user-data', - fs - ) + const service = new RendererPerformanceLogService(() => true, '/user-data', fs) await expect(service.record(validRecord)).resolves.toBe(true) @@ -117,8 +105,8 @@ describe('RendererPerformanceLogService', () => { it('contains write failures without rejecting the renderer route', async () => { const onWriteError = vi.fn() const service = new RendererPerformanceLogService( - { get: vi.fn(() => true) } as never, - () => '/user-data', + () => true, + '/user-data', createFs({ appendFile: vi.fn().mockRejectedValue(new Error('disk unavailable')) }), Date.now, onWriteError diff --git a/test/main/app/routes.test.ts b/test/main/app/routes.test.ts index 2dceecdf1a..52c6e396bd 100644 --- a/test/main/app/routes.test.ts +++ b/test/main/app/routes.test.ts @@ -1,6 +1,5 @@ import { describe, expect, it, vi } from 'vitest' import { createRendererRouteContext } from '@/routes/routeRegistry' -import { app } from 'electron' import { debugCloseSplashScenarioRoute, debugShowSplashScenarioRoute, @@ -20,9 +19,10 @@ const validRecord = { function createRoutes(overrides: Partial[0]> = {}) { return createAppRoutes({ + isPackaged: false, logging: { openFolder: vi.fn() }, rendererPerformance: { record: vi.fn().mockResolvedValue(true) }, - isMainWindowContext: vi.fn(() => true), + isMainWindowContext: vi.fn(async () => true), agentSettings: { listAgents: vi.fn(), getAcpEnabled: vi.fn() @@ -57,24 +57,18 @@ describe('app debug splash routes', () => { showDebugScenario: vi.fn(), closeDebugScenario: vi.fn() } - const isPackaged = vi.spyOn(app, 'isPackaged', 'get').mockReturnValue(true) - - try { - const routes = createRoutes({ splash }) - const show = routes.get(debugShowSplashScenarioRoute.name) - const close = routes.get(debugCloseSplashScenarioRoute.name) + const routes = createRoutes({ splash, isPackaged: true }) + const show = routes.get(debugShowSplashScenarioRoute.name) + const close = routes.get(debugCloseSplashScenarioRoute.name) - await expect(show?.({ mode: 'unlock' }, createRendererRouteContext(1, 1))).resolves.toEqual({ - shown: false - }) - await expect(close?.({}, createRendererRouteContext(1, 1))).resolves.toEqual({ - closed: false - }) - expect(splash.showDebugScenario).not.toHaveBeenCalled() - expect(splash.closeDebugScenario).not.toHaveBeenCalled() - } finally { - isPackaged.mockRestore() - } + await expect(show?.({ mode: 'unlock' }, createRendererRouteContext(1, 1))).resolves.toEqual({ + shown: false + }) + await expect(close?.({}, createRendererRouteContext(1, 1))).resolves.toEqual({ + closed: false + }) + expect(splash.showDebugScenario).not.toHaveBeenCalled() + expect(splash.closeDebugScenario).not.toHaveBeenCalled() }) it('validates mode input and delegates allowed Splash previews', async () => { @@ -112,7 +106,7 @@ describe('app performance diagnostics route', () => { const rendererPerformance = { record: vi.fn().mockResolvedValue(true) } const routes = createRoutes({ rendererPerformance, - isMainWindowContext: vi.fn(() => false) + isMainWindowContext: vi.fn(async () => false) }) const handler = routes.get(performanceRecordRendererRoute.name) diff --git a/test/main/app/schedulerHost.test.ts b/test/main/app/schedulerHost.test.ts new file mode 100644 index 0000000000..431e3090a9 --- /dev/null +++ b/test/main/app/schedulerHost.test.ts @@ -0,0 +1,134 @@ +import { EventEmitter } from 'node:events' +import { beforeEach, describe, expect, it, vi } from 'vitest' +import { spawnSchedulerHost } from '@/app/schedulerHost' +import { SchedulerProcessManager } from '@/scheduler/schedulerProcessManager' +import type { SchedulerCommand } from '@shared/cronJobs' + +const mocks = vi.hoisted(() => ({ fork: vi.fn() })) +vi.mock('electron', () => ({ + app: { getAppPath: () => process.cwd() }, + utilityProcess: { fork: mocks.fork } +})) + +describe('Scheduler native launch', () => { + beforeEach(() => { + mocks.fork.mockReset() + }) + + function nativeHost() { + const host = Object.assign(new EventEmitter(), { + pid: 123, + postMessage: vi.fn<(command: SchedulerCommand) => void>(), + kill: vi.fn<() => boolean>() + }) + host.kill.mockImplementation(() => { + host.emit('exit', 0) + return true + }) + mocks.fork.mockReturnValue(host) + return host + } + + function manager() { + return new SchedulerProcessManager({ + getSnapshot: () => ({ enabledJobCount: 1, nextRunAt: 100 }), + queueDueRuns: () => [], + onRunDue: vi.fn(), + spawnHost: spawnSchedulerHost + }) + } + + it('exposes command, event and lifecycle capabilities after native spawn', async () => { + const host = nativeHost() + const pending = spawnSchedulerHost(new AbortController().signal) + host.emit('spawn') + const channel = await pending + expect(channel).not.toBe(host) + expect(channel.pid).toBe(123) + expect(host.listenerCount('spawn')).toBe(0) + expect(host.listenerCount('exit')).toBe(0) + expect(host.listenerCount('error')).toBe(0) + const message = vi.fn(), + exit = vi.fn(), + error = vi.fn() + expect(channel.onMessage(message)).toBeUndefined() + expect(channel.onExit(exit)).toBeUndefined() + expect(channel.onError(error)).toBeUndefined() + host.emit('message', { type: 'READY', pid: 123, now: 100 }) + host.emit('error', 'crash', 'scheduler') + expect(message).toHaveBeenCalledWith({ type: 'READY', pid: 123, now: 100 }) + expect(error).toHaveBeenCalledWith('crash', 'scheduler') + channel.postMessage({ type: 'START', now: 100 }) + expect(host.postMessage).toHaveBeenCalledWith({ type: 'START', now: 100 }) + expect(channel.kill()).toBeUndefined() + expect(exit).toHaveBeenCalledWith(0) + }) + + it('does not fork an already canceled launch', async () => { + const abort = new AbortController() + abort.abort() + await expect(spawnSchedulerHost(abort.signal)).rejects.toMatchObject({ name: 'AbortError' }) + expect(mocks.fork).not.toHaveBeenCalled() + }) + + it('kills a pending native child when its manager stops and ignores a late spawn', async () => { + const host = nativeHost() + const owner = manager() + const pending = owner.start() + const rejected = expect(pending).rejects.toThrow('Scheduler utility spawn canceled') + await owner.stop() + await rejected + expect(host.kill).toHaveBeenCalledTimes(1) + expect(host.postMessage).not.toHaveBeenCalled() + expect(owner.getStatus()).toMatchObject({ state: 'stopped', pid: null, lastError: null }) + for (const event of ['spawn', 'exit', 'error']) expect(host.listenerCount(event)).toBe(0) + host.emit('spawn') + expect(owner.getStatus()).toMatchObject({ state: 'stopped', pid: null }) + }) + + it('cancels a signal aborted while the native fork returns', async () => { + const host = nativeHost() + const abort = new AbortController() + mocks.fork.mockImplementationOnce(() => { + abort.abort() + return host + }) + await expect(spawnSchedulerHost(abort.signal)).rejects.toThrow( + 'Scheduler utility spawn canceled' + ) + expect(host.kill).toHaveBeenCalledTimes(1) + for (const event of ['spawn', 'exit', 'error']) expect(host.listenerCount(event)).toBe(0) + }) + + it.each(['exit', 'error'] as const)( + 'rejects a native %s before spawn without leaving the child alive', + async (event) => { + const host = nativeHost() + const pending = spawnSchedulerHost(new AbortController().signal) + const rejected = expect(pending).rejects.toThrow( + event === 'exit' ? 'exited before spawn: 1' : 'process error: crash at startup' + ) + if (event === 'exit') host.emit('exit', 1) + else host.emit('error', 'crash', 'startup') + await rejected + expect(host.kill).toHaveBeenCalledTimes(event === 'exit' ? 0 : 1) + for (const name of ['spawn', 'exit', 'error']) expect(host.listenerCount(name)).toBe(0) + host.emit('spawn') + } + ) + + it('does not send reconciliation after stop supersedes a completed native spawn', async () => { + const host = nativeHost() + const owner = manager() + host.postMessage.mockImplementation((command) => { + if (command.type === 'START') void owner.stop() + }) + const pending = owner.start() + const rejected = expect(pending).rejects.toThrow('spawn was superseded') + host.emit('spawn') + await rejected + expect(host.postMessage.mock.calls.map(([command]) => command.type)).toEqual(['START', 'STOP']) + expect(host.kill).toHaveBeenCalledTimes(1) + expect(owner.getStatus()).toMatchObject({ state: 'stopped', pid: null }) + }) +}) diff --git a/test/main/app/settingsService.test.ts b/test/main/app/settingsService.test.ts new file mode 100644 index 0000000000..6d5b448042 --- /dev/null +++ b/test/main/app/settingsService.test.ts @@ -0,0 +1,444 @@ +import { describe, expect, it, vi } from 'vitest' +import { AppSettingsService } from '@/app/settingsService' +import { DesktopSettings } from '@/desktop/settings' +import { FontSettings } from '@/desktop/fontSettings' +import { LoggingService } from '@/app/logging' +import { UpdateSettings } from '@/upgrade/settings' +import { SETTINGS_KEYS } from '@shared/contracts/routes' + +function deferred() { + let resolve!: (value: T) => void + const promise = new Promise((done) => { + resolve = done + }) + return { promise, resolve } +} + +function setup() { + const stored = new Map([['cloudSyncSecret', 'wrapped-secret']]) + const storage = { + get: (key: string) => stored.get(key), + set: vi.fn((key: string, value: unknown) => { + stored.set(key, value) + }) + } + const publishEvent = vi.fn() + let theme: 'system' | 'dark' | 'light' = 'system' + const host = { + systemLocale: 'en-US', + readTheme: vi.fn(async () => ({ + theme, + isDark: theme === 'dark' + })), + applyTheme: vi.fn(async (value: 'system' | 'dark' | 'light') => { + theme = value + return theme === 'dark' + }), + refreshLanguage: vi.fn(async (_locale: string): Promise => undefined), + refreshTheme: vi.fn(async (_isDark: boolean): Promise => undefined), + readLaunchAtLogin: vi.fn(async () => false), + setLaunchAtLogin: vi.fn(async (_enabled: boolean) => false), + applyContentProtection: vi.fn(async (_enabled: boolean): Promise => undefined), + applyFloatingButtonEnabled: vi.fn(async (_enabled: boolean): Promise => undefined) + } + let owner!: AppSettingsService + const nativeSnapshots = vi.fn() + const committed = () => owner.publishNativeSnapshot() + const desktopSettings = new DesktopSettings(storage as never, host, publishEvent, committed) + const enumerateFonts = vi.fn(async (): Promise => []) + const loggingHost = { + applyLogging: vi.fn(async (_enabled: boolean): Promise => undefined), + openLoggingFolder: vi.fn(async (): Promise => undefined) + } + let version = 0 + let active = true + owner = new AppSettingsService({ + ownerId: 'settings-owner', + updateSettings: new UpdateSettings(storage as never, '1.1.4-beta.7'), + publishNativeSnapshot: nativeSnapshots, + desktopSettings, + fonts: new FontSettings(storage as never, publishEvent, enumerateFonts), + agentDefaults: { + getAutoCompactionEnabled: () => true, + getAutoCompactionTriggerThreshold: () => 80, + getAutoCompactionRetainRecentPairs: () => 2 + } as never, + privacy: { isEnabled: () => true } as never, + traceSettings: { isEnabled: () => false } as never, + logging: new LoggingService(storage as never, loggingHost, publishEvent, committed), + ocr: { getAutomaticExtractionEnabled: () => true, getBackend: () => 'auto' } as never, + recordActivity: vi.fn(), + publishEvent, + nextVersion: () => ++version, + assertActive: () => { + if (!active) throw new Error('owner unavailable') + } + }) + return { + owner, + nativeSnapshots, + host, + loggingHost, + enumerateFonts, + stored, + storage, + publishEvent, + fence: () => { + active = false + } + } +} + +describe('AppSettingsService', () => { + it('serializes floating presentation effects before acknowledging each change', async () => { + const { owner, host, stored } = setup() + const pending = Promise.withResolvers() + host.applyFloatingButtonEnabled.mockReturnValueOnce(pending.promise) + const first = owner.setFloatingButtonEnabled(true) + await vi.waitFor(() => expect(host.applyFloatingButtonEnabled).toHaveBeenCalledOnce()) + const second = owner.setFloatingButtonEnabled(false) + await Promise.resolve() + expect(stored.get('floatingButtonEnabled')).toBe(true) + pending.resolve() + expect(await first).toBe(true) + expect(await second).toBe(false) + expect(host.applyFloatingButtonEnabled.mock.calls).toEqual([[true], [false]]) + await owner.stop() + }) + + it('does not finish stopping while native floating presentation is pending', async () => { + const { owner, host } = setup() + const pending = Promise.withResolvers() + host.applyFloatingButtonEnabled.mockReturnValueOnce(pending.promise) + const enabling = owner.setFloatingButtonEnabled(true) + await vi.waitFor(() => expect(host.applyFloatingButtonEnabled).toHaveBeenCalledOnce()) + let stopped = false + const stopping = owner.stop().then(() => { + stopped = true + }) + await Promise.resolve() + expect(stopped).toBe(false) + await expect(owner.setFloatingButtonEnabled(false)).rejects.toThrow('stopped') + pending.resolve() + await enabling + await stopping + expect(stopped).toBe(true) + }) + + it('awaits a Native projection receipt before publishing or releasing ownership', async () => { + const { owner, nativeSnapshots, publishEvent, stored } = setup() + const receipt = Promise.withResolvers() + nativeSnapshots.mockReturnValueOnce(receipt.promise) + const writing = owner.update([{ key: 'notificationsEnabled', value: false }]) + await vi.waitFor(() => expect(nativeSnapshots).toHaveBeenCalledOnce()) + expect(stored.get('notificationsEnabled')).toBe(false) + expect(publishEvent).not.toHaveBeenCalled() + let stopped = false + const stopping = owner.stop().then(() => { + stopped = true + }) + await Promise.resolve() + expect(stopped).toBe(false) + receipt.resolve() + await writing + await stopping + expect(stopped).toBe(true) + expect(publishEvent).toHaveBeenCalledOnce() + }) + + it('publishes allowlisted native state at commit before ordinary settings events', async () => { + const { owner, nativeSnapshots, publishEvent, stored } = setup() + stored.set('shortcutKey', { privateToken: 'unexpected-secret' }) + stored.set('floatingButtonBounds', { + x: 1, + y: 2, + dockSide: 'left', + secret: 'unexpected-secret' + }) + await owner.update([{ key: 'notificationsEnabled', value: false }]) + const committed = nativeSnapshots.mock.calls.at(-1)![0] + expect(committed).toMatchObject({ + ownerId: 'settings-owner', + notificationsEnabled: false, + updateChannel: 'beta' + }) + expect(JSON.stringify(committed)).not.toContain('wrapped-secret') + expect(JSON.stringify(committed)).not.toContain('unexpected-secret') + expect(nativeSnapshots.mock.invocationCallOrder[0]).toBeLessThan( + publishEvent.mock.invocationCallOrder[0] + ) + await owner.setFloatingButtonBounds({ x: 20, y: 30, dockSide: 'right' }) + expect(nativeSnapshots.mock.calls.at(-1)![0].floatingButtonBounds).toEqual( + stored.get('floatingButtonBounds') + ) + expect(await owner.setUpdateChannel('stable')).toBe('stable') + expect(nativeSnapshots.mock.calls.at(-1)![0].updateChannel).toBe('stable') + await expect(owner.setFloatingButtonBounds({ x: NaN, y: 0, dockSide: 'left' })).rejects.toThrow( + 'Invalid floating bounds' + ) + await owner.stop() + await expect(owner.setFloatingButtonBounds({ x: 0, y: 0, dockSide: 'left' })).rejects.toThrow( + 'stopped' + ) + }) + + it('returns an allowlisted serializable snapshot and normalized mutation values', async () => { + const { owner, publishEvent } = setup() + const snapshot = await owner.getSnapshot() + expect(Object.keys(snapshot.values).sort()).toEqual([...SETTINGS_KEYS].sort()) + expect(JSON.stringify(snapshot)).not.toContain('wrapped-secret') + expect(structuredClone(snapshot)).toEqual(snapshot) + + const updated = await owner.update([{ key: 'fontFamily', value: ' Fixture; Font ' }]) + expect(updated).toMatchObject({ + changedKeys: ['fontFamily'], + values: { fontFamily: 'Fixture Font' } + }) + expect(updated.version).toBeGreaterThan(snapshot.version) + expect((await owner.getSnapshot(['fontFamily'])).values).toEqual(updated.values) + await owner.publishSnapshot() + expect(publishEvent).toHaveBeenLastCalledWith('settings.changed', { + changedKeys: [...SETTINGS_KEYS], + version: expect.any(Number), + values: (await owner.getSnapshot()).values + }) + }) + + it('returns the native owner previous value only after the mutation succeeds', async () => { + const { owner, stored } = setup() + expect( + await owner.applyDisplayChange({ id: 'copyWithCotEnabled', value: false }) + ).toMatchObject({ + ok: true, + id: 'copyWithCotEnabled', + previousValue: true, + value: false + }) + expect(stored.get('copyWithCotEnabled')).toBe(false) + expect(await owner.applyDisplayChange({ id: 'copyWithCotEnabled', value: true })).toMatchObject( + { + previousValue: false, + value: true + } + ) + }) + + it('rejects invalid and fenced commands before native writes', async () => { + const { owner, storage, fence } = setup() + await expect( + owner.applyDisplayChange({ id: 'fontSizeLevel', value: 5 } as never) + ).rejects.toThrow() + await expect( + owner.applyDisplayChange({ id: 'theme', value: 'light', extra: true } as never) + ).rejects.toThrow() + fence() + await expect( + owner.applyDisplayChange({ id: 'copyWithCotEnabled', value: false }) + ).rejects.toThrow('owner unavailable') + await expect(owner.update([{ key: 'fontSizeLevel', value: 2 }])).rejects.toThrow( + 'owner unavailable' + ) + await expect(owner.getSnapshot()).rejects.toThrow('owner unavailable') + await expect(owner.publishSnapshot()).rejects.toThrow('owner unavailable') + expect(storage.set).not.toHaveBeenCalled() + }) + + it('does not acknowledge or publish a failed persistent mutation', async () => { + const { owner, storage, publishEvent } = setup() + storage.set.mockImplementationOnce(() => { + throw new Error('storage unavailable') + }) + await expect( + owner.applyDisplayChange({ id: 'copyWithCotEnabled', value: false }) + ).rejects.toThrow('storage unavailable') + expect(publishEvent).not.toHaveBeenCalled() + expect((await owner.getSnapshot(['copyWithCotEnabled'])).values.copyWithCotEnabled).toBe(true) + }) + + it('orders native acknowledgements, display receipts and following snapshots together', async () => { + const { owner, host, publishEvent } = setup() + const refreshing = deferred() + host.refreshTheme.mockReturnValueOnce(refreshing.promise) + const first = owner.setTheme('dark') + await vi.waitFor(() => expect(host.refreshTheme).toHaveBeenCalledWith(true)) + const display = owner.applyDisplayChange({ id: 'theme', value: 'light' }) + const snapshot = owner.getTheme() + expect(publishEvent).not.toHaveBeenCalled() + refreshing.resolve() + expect(await first).toEqual({ theme: 'dark', isDark: true }) + expect(await display).toMatchObject({ previousValue: 'dark', value: 'light' }) + expect(await snapshot).toEqual({ theme: 'light', isDark: false }) + }) + + it('awaits normalized language refresh and recovers after a failed native command', async () => { + const { owner, host, publishEvent, stored } = setup() + const refreshing = deferred() + host.refreshLanguage.mockReturnValueOnce(refreshing.promise) + const changed = owner.setLanguage('fa') + await vi.waitFor(() => expect(host.refreshLanguage).toHaveBeenCalledWith('fa-IR')) + expect(publishEvent).not.toHaveBeenCalled() + refreshing.resolve() + expect(await changed).toEqual({ requestedLanguage: 'fa-IR', locale: 'fa-IR', direction: 'rtl' }) + publishEvent.mockClear() + host.applyTheme.mockRejectedValueOnce(new Error('native unavailable')) + await expect(owner.setTheme('dark')).rejects.toThrow('native unavailable') + expect(stored.has('appTheme')).toBe(false) + expect(publishEvent).not.toHaveBeenCalled() + expect(await owner.setTheme('light')).toEqual({ theme: 'light', isDark: false }) + }) + + it('queries login state only when selected and returns the observed OS value', async () => { + const { owner, host, stored, publishEvent } = setup() + await owner.getSnapshot(['autoScrollEnabled']) + await owner.update([{ key: 'fontSizeLevel', value: 2 }]) + expect(host.readLaunchAtLogin).not.toHaveBeenCalled() + expect((await owner.getSnapshot(['launchAtLoginEnabled'])).values).toEqual({ + launchAtLoginEnabled: false + }) + expect((await owner.update([{ key: 'launchAtLoginEnabled', value: true }])).values).toEqual({ + launchAtLoginEnabled: false + }) + expect(host.setLaunchAtLogin).toHaveBeenCalledWith(true) + expect(stored.has('launchAtLoginEnabled')).toBe(false) + expect(publishEvent).toHaveBeenLastCalledWith( + 'settings.changed', + expect.objectContaining({ values: { launchAtLoginEnabled: false } }) + ) + }) + + it('fences queued commands and drains active host work before restart', async () => { + const { owner, host, stored } = setup() + const refreshing = deferred() + host.refreshTheme.mockReturnValueOnce(refreshing.promise) + const started = owner.setTheme('dark') + await vi.waitFor(() => expect(host.refreshTheme).toHaveBeenCalled()) + const queued = owner.update([{ key: 'fontSizeLevel', value: 3 }]) + const queuedRejected = expect(queued).rejects.toThrow('Settings owner is stopped') + let drained = false + const stopping = owner.stop().then(() => { + drained = true + }) + await expect(owner.getTheme()).rejects.toThrow('Settings owner is stopped') + await expect(owner.observeSystemTheme()).rejects.toThrow('Settings owner is stopped') + expect(() => owner.start()).toThrow('still draining') + expect(drained).toBe(false) + refreshing.resolve() + await started + await queuedRejected + await stopping + expect(stored.has('fontSizeLevel')).toBe(false) + owner.start() + expect((await owner.update([{ key: 'fontSizeLevel', value: 2 }])).values).toEqual({ + fontSizeLevel: 2 + }) + }) + + it('does not release or restart an owner whose native drain timed out', async () => { + const { owner, host } = setup() + const refreshing = deferred() + host.refreshTheme.mockReturnValueOnce(refreshing.promise) + const started = owner.setTheme('dark') + await vi.waitFor(() => expect(host.refreshTheme).toHaveBeenCalled()) + vi.useFakeTimers() + try { + const stopped = expect(owner.stop()).rejects.toThrow('did not drain') + await vi.advanceTimersByTimeAsync(30_000) + await stopped + expect(() => owner.start()).toThrow('still draining') + refreshing.resolve() + await started + await owner.stop() + owner.start() + expect(await owner.getTheme()).toEqual({ theme: 'dark', isDark: true }) + } finally { + vi.useRealTimers() + } + }) + + it('bounds pending commands while a native observation is blocked', async () => { + const { owner, host } = setup() + const login = deferred() + host.readLaunchAtLogin.mockReturnValueOnce(login.promise) + const snapshot = owner.getSnapshot() + await vi.waitFor(() => expect(host.readLaunchAtLogin).toHaveBeenCalled()) + const changes = Array.from({ length: 63 }, (_, index) => + owner.update([{ key: 'fontSizeLevel', value: (index % 4) + 1 }]) + ) + await expect(owner.update([{ key: 'fontSizeLevel', value: 1 }])).rejects.toThrow( + 'queue is full' + ) + const observations = Array.from({ length: 100 }, () => owner.observeSystemTheme()) + login.resolve(false) + await snapshot + await Promise.all(changes) + await Promise.all(observations) + expect(host.readTheme).toHaveBeenCalledTimes(1) + expect((await owner.getSnapshot(['fontSizeLevel'])).values).toEqual({ fontSizeLevel: 3 }) + }) + + it('coalesces pending system events and observes a newer change after active native work', async () => { + const { owner, host, publishEvent } = setup() + const observed = deferred<{ theme: 'system'; isDark: boolean }>() + host.readTheme.mockReturnValueOnce(observed.promise) + host.readTheme.mockResolvedValue({ theme: 'system', isDark: true }) + const first = owner.observeSystemTheme() + await vi.waitFor(() => expect(host.readTheme).toHaveBeenCalledTimes(1)) + const following = Array.from({ length: 100 }, () => owner.observeSystemTheme()) + observed.resolve({ theme: 'system', isDark: false }) + await first + await Promise.all(following) + expect(host.readTheme).toHaveBeenCalledTimes(2) + const colors = publishEvent.mock.calls + .filter(([name]) => name === 'config.systemTheme.changed') + .map(([, payload]) => payload.isDark) + expect(colors).toEqual([false, true]) + await owner.setTheme('light') + publishEvent.mockClear() + await owner.observeSystemTheme() + expect(publishEvent).not.toHaveBeenCalled() + }) + + it.each(['contentProtectionEnabled', 'loggingEnabled'] as const)( + 'awaits native %s completion before its event and receipt', + async (key) => { + const { owner, host, loggingHost, publishEvent } = setup() + const effect = + key === 'contentProtectionEnabled' ? host.applyContentProtection : loggingHost.applyLogging + const finishing = deferred() + effect.mockReturnValueOnce(finishing.promise) + const changed = owner.update([{ key, value: true }]) + await vi.waitFor(() => expect(effect).toHaveBeenCalledWith(true)) + expect(publishEvent).not.toHaveBeenCalled() + finishing.resolve() + expect((await changed).values).toEqual({ [key]: true }) + expect(publishEvent).toHaveBeenCalledWith( + 'settings.changed', + expect.objectContaining({ values: { [key]: true } }) + ) + } + ) + + it('drains actual font enumeration and fences queued folder requests before stopping', async () => { + const { owner, enumerateFonts, loggingHost } = setup() + const enumerated = deferred() + enumerateFonts.mockReturnValueOnce(enumerated.promise) + const fonts = owner.listSystemFonts() + await vi.waitFor(() => expect(enumerateFonts).toHaveBeenCalledOnce()) + const folder = expect(owner.openLoggingFolder()).rejects.toThrow('Settings owner is stopped') + let drained = false + const stopping = owner.stop().then(() => { + drained = true + }) + expect(drained).toBe(false) + enumerated.resolve(['Inter Regular', 'Inter Bold', 'Menlo']) + expect(await fonts).toEqual(['Inter', 'Menlo']) + await folder + await stopping + expect(loggingHost.openLoggingFolder).not.toHaveBeenCalled() + owner.start() + expect(await owner.listSystemFonts()).toEqual(['Inter', 'Menlo']) + expect(enumerateFonts).toHaveBeenCalledOnce() + await owner.openLoggingFolder() + expect(loggingHost.openLoggingFolder).toHaveBeenCalledOnce() + }) +}) diff --git a/test/main/app/splashWindow.display.test.ts b/test/main/app/splashWindow.display.test.ts index 85ede69321..b1804f3f18 100644 --- a/test/main/app/splashWindow.display.test.ts +++ b/test/main/app/splashWindow.display.test.ts @@ -158,7 +158,7 @@ describe('SplashWindow display gating', () => { mockIpcMain.on.mockClear() splashLoadMocks.loadURL = undefined splashLoadMocks.loadFile = undefined - delete process.env.ELECTRON_RENDERER_URL + vi.stubEnv('ELECTRON_RENDERER_URL', undefined) }) afterEach(async () => { @@ -169,9 +169,32 @@ describe('SplashWindow display gating', () => { manager = null } vi.useRealTimers() + vi.unstubAllEnvs() createdWindows.length = 0 }) + it.each(['unlock', 'recovery'] as const)( + 'cancels pending and future database %s requests during startup quit', + async (kind) => { + const { SplashWindow } = await import('../../../src/main/app/splashWindow') + manager = new SplashWindow() + const request = () => + kind === 'unlock' + ? manager!.requestDatabaseUnlock({ + reason: 'manual-required', + safeStorageAvailable: true + }) + : manager!.requestDatabaseRecovery({ + kind: 'true-corruption', + preservedPath: '/tmp/agent.db.corrupt' + }) + const pending = request() + manager.cancelDatabaseStartup() + await expect(pending).resolves.toBeNull() + await expect(request()).resolves.toBeNull() + } + ) + it('creates a transparent splash window canvas', async () => { const { SplashWindow } = await import('../../../src/main/app/splashWindow') @@ -407,7 +430,7 @@ describe('SplashWindow display gating', () => { }) it('falls back to an inline splash renderer when the dev page is unavailable', async () => { - process.env.ELECTRON_RENDERER_URL = 'http://localhost:5173' + vi.stubEnv('ELECTRON_RENDERER_URL', 'http://localhost:5173') splashLoadMocks.loadURL = vi.fn(async (url: string) => { if (url.startsWith('data:text/html')) { return @@ -535,7 +558,7 @@ describe('SplashWindow display gating', () => { }) it('stops splash renderer fallback quietly after the hidden splash is suppressed', async () => { - process.env.ELECTRON_RENDERER_URL = 'http://localhost:5173' + vi.stubEnv('ELECTRON_RENDERER_URL', 'http://localhost:5173') const errorSpy = vi.spyOn(console, 'error').mockImplementation(() => {}) const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {}) splashLoadMocks.loadURL = vi.fn(async () => { diff --git a/test/main/app/startupWorkloadCoordinator.test.ts b/test/main/app/startupWorkloadCoordinator.test.ts index 5b2b6c8f76..a84b07119e 100644 --- a/test/main/app/startupWorkloadCoordinator.test.ts +++ b/test/main/app/startupWorkloadCoordinator.test.ts @@ -279,6 +279,99 @@ describe('StartupWorkloadCoordinator', () => { expect(onFailure).not.toHaveBeenCalled() }) + it('drains actual callbacks across canceled runs without waiting for another target', async () => { + const { StartupWorkloadCoordinator } = await import('@/app/startupWorkloadCoordinator') + const coordinator = new StartupWorkloadCoordinator() + const first = createDeferred() + const next = createDeferred() + const settings = createDeferred() + const schedule = (target: 'main' | 'settings', result: Promise) => + coordinator.scheduleTask({ + id: 'owner', + target, + phase: 'background', + resource: 'io', + labelKey: 'startup.test', + run: async () => await result + }) + const original = schedule('main', first.promise) + const originalRejected = expect(original).rejects.toMatchObject({ name: 'AbortError' }) + coordinator.createRun('main') + await originalRejected + const current = schedule('main', next.promise) + const currentRejected = expect(current).rejects.toMatchObject({ name: 'AbortError' }) + const unrelated = schedule('settings', settings.promise) + + let drained = false + const drain = coordinator.cancelAndDrainTarget('main').then(() => { + drained = true + }) + await currentRejected + next.resolve() + await new Promise((resolve) => setImmediate(resolve)) + expect(drained).toBe(false) + first.reject(new Error('late callback failure')) + await drain + expect(drained).toBe(true) + settings.resolve() + await unrelated + expect(coordinator.isIdle()).toBe(true) + }) + + it('cancels queued work without executing it while an active callback drains', async () => { + const { StartupWorkloadCoordinator } = await import('@/app/startupWorkloadCoordinator') + const coordinator = new StartupWorkloadCoordinator() + const release = createDeferred() + const schedule = (id: string, run: () => Promise) => + coordinator.scheduleTask({ + id, + target: 'main', + phase: 'background', + resource: 'cpu', + labelKey: 'startup.test', + run + }) + const active = schedule('active', () => release.promise) + const runQueued = vi.fn(async () => undefined) + const queued = schedule('queued', runQueued) + const rejected = [active, queued].map((task) => + expect(task).rejects.toMatchObject({ name: 'AbortError' }) + ) + const drain = coordinator.cancelAndDrainTarget('main') + await Promise.all(rejected) + release.resolve() + await drain + expect(runQueued).not.toHaveBeenCalled() + expect(coordinator.isIdle()).toBe(true) + }) + + it('bounds drain failure and keeps an old callback available to a later drain', async () => { + vi.useFakeTimers() + const { StartupWorkloadCoordinator } = await import('@/app/startupWorkloadCoordinator') + const coordinator = new StartupWorkloadCoordinator() + const release = createDeferred() + const task = coordinator.scheduleTask({ + id: 'held', + target: 'main', + phase: 'background', + resource: 'io', + labelKey: 'startup.test', + run: () => release.promise + }) + const rejected = expect(task).rejects.toMatchObject({ name: 'AbortError' }) + const timeout = expect(coordinator.cancelAndDrainTarget('main')).rejects.toThrow('30000ms') + await rejected + await vi.advanceTimersByTimeAsync(30_000) + await timeout + expect(vi.getTimerCount()).toBe(0) + expect(coordinator.isIdle()).toBe(false) + const retry = coordinator.cancelAndDrainTarget('main') + release.resolve() + await retry + expect(vi.getTimerCount()).toBe(0) + expect(coordinator.isIdle()).toBe(true) + }) + it('does not starve interactive io work behind occupied background lanes', async () => { const { StartupWorkloadCoordinator } = await import('@/app/startupWorkloadCoordinator') const coordinator = new StartupWorkloadCoordinator() diff --git a/test/main/cli/discovery.test.ts b/test/main/cli/discovery.test.ts index 39f7847563..06821b5914 100644 --- a/test/main/cli/discovery.test.ts +++ b/test/main/cli/discovery.test.ts @@ -50,6 +50,11 @@ describe('CLI descriptor discovery', () => { homeDirectory: '/unused' }) ).toBe(path.resolve('./profile')) + expect( + resolveCliUserDataPath({ + env: { DEEPCHAT_USER_DATA_DIR: './host-profile', DEEPCHAT_E2E_USER_DATA_DIR: './unused' } + }) + ).toBe(path.resolve('./host-profile')) }) it('fails closed when an Agent token variable is present but invalid', () => { diff --git a/test/main/cli/mutationGuard.test.ts b/test/main/cli/mutationGuard.test.ts index 64749335b2..f19a80cb1d 100644 --- a/test/main/cli/mutationGuard.test.ts +++ b/test/main/cli/mutationGuard.test.ts @@ -47,6 +47,71 @@ async function waitForRequests(requests: unknown[], count: number): Promise { + it.each(['approved', 'denied'] as const)( + 'settles a trusted terminal decision as %s without renderer authority', + async (decision) => { + const harness = createHarness() + const entered = Promise.withResolvers() + const answer = Promise.withResolvers<'approved' | 'denied'>() + vi.mocked(harness.presentation.getTarget).mockResolvedValue({ host: 'terminal' }) + vi.mocked(harness.presentation.present).mockImplementation(async (_target, payload) => { + entered.resolve(payload.requestId) + return await answer.promise + }) + const pending = harness.authorize() + const outcome = pending.then( + () => 'approved', + (error: CliRequestError) => error.code + ) + const requestId = await entered.promise + expect(harness.guard.resolve({ requestId, decision: 'approved' }, rendererCaller(70))).toBe( + false + ) + harness.guard.cancelRenderer(70) + answer.resolve(decision) + expect(await outcome).toBe(decision === 'approved' ? 'approved' : 'approval_denied') + expect(harness.close).toHaveBeenCalledWith( + { host: 'terminal' }, + { requestId, reason: decision } + ) + } + ) + + it('cancels an awaiting terminal presentation when its CLI caller disconnects', async () => { + const harness = createHarness() + const entered = Promise.withResolvers() + const retiring = Promise.withResolvers() + const retired = Promise.withResolvers() + vi.mocked(harness.presentation.getTarget).mockResolvedValue({ host: 'terminal' }) + vi.mocked(harness.presentation.present).mockImplementation( + async (_target, _payload, signal) => { + entered.resolve(signal!) + await new Promise((resolve) => { + signal!.addEventListener( + 'abort', + () => { + retiring.resolve() + resolve() + }, + { once: true } + ) + }) + await retired.promise + throw signal!.reason + } + ) + const controller = new AbortController() + const pending = harness.authorize(controller.signal) + const rejection = expect(pending).rejects.toMatchObject({ code: 'cancelled' }) + const signal = await entered.promise + controller.abort() + await retiring.promise + expect(harness.close).not.toHaveBeenCalled() + retired.resolve() + await rejection + expect(signal.aborted).toBe(true) + }) + it('resumes only the exact request resolved by its target renderer', async () => { const harness = createHarness() const pending = harness.authorize() diff --git a/test/main/cli/runService.test.ts b/test/main/cli/runService.test.ts index 066fe351f4..7a30cb5acd 100644 --- a/test/main/cli/runService.test.ts +++ b/test/main/cli/runService.test.ts @@ -16,6 +16,7 @@ import type { import { DEFAULT_ORCHESTRATION_POLICY } from '@shared/orchestration/policy' import { LOCAL_CONTROL_MAX_JSON_RESPONSE_BYTES } from '@shared/contracts/localControl' import { CliRunService, type CliRunServiceOptions } from '@/cli/runService' +import { DatabaseRequestGate } from '@/app/databaseRequestGate' import { CliRequestError } from '@/cli/errors' import { TypedEventHub, type TypedEventRecord } from '@/events/typedEventHub' import type { CliRouteCaller, RouteContext } from '@/routes/routeRegistry' @@ -47,6 +48,7 @@ const baseSession: SessionWithState = { sessionKind: 'regular', parentSessionId: null, subagentMeta: null, + toolModeOverride: null, orchestrationPolicy: DEFAULT_ORCHESTRATION_POLICY, createdAt: 100, updatedAt: 101, @@ -145,6 +147,7 @@ function createHarness( storedSession?: SessionRecord | null messages?: ChatMessageRecord[] hasWaitingDescendantInteraction?: boolean + readSubscriptionSnapshot?: CliRunServiceOptions['readSubscriptionSnapshot'] } = {} ): { service: CliRunService @@ -195,6 +198,8 @@ function createHarness( projection, sessions, getPendingAssistantMessages, + readSubscriptionSnapshot: + overrides.readSubscriptionSnapshot ?? (async (read) => await read()), hasWaitingDescendantInteraction: vi.fn( () => overrides.hasWaitingDescendantInteraction ?? false ), @@ -1025,6 +1030,53 @@ describe('CliRunService', () => { expect(turn.cancelGeneration).not.toHaveBeenCalled() }) + it('drains only the initial database snapshot while the event watcher stays connected', async () => { + const gate = new DatabaseRequestGate(() => undefined) + const { service, projection } = createHarness({ + readSubscriptionSnapshot: (read) => gate.runRequest('events.subscribe', read) + }) + let release!: (session: SessionWithState) => void + vi.spyOn(projection, 'getSession').mockImplementationOnce( + () => + new Promise((resolve) => { + release = resolve + }) + ) + let ready!: () => void + const snapshotReady = new Promise((resolve) => { + ready = resolve + }) + const controller = new AbortController() + let finished = false + const result = service + .dispatchStream( + eventsSubscribeRoute.name, + { runId: 'run-1' }, + humanCaller, + controller.signal, + async (event) => { + if (event === 'runs.snapshot') ready() + } + ) + .finally(() => { + finished = true + }) + gate.beginMaintenance() + let drained = false + const drain = gate.drain().then(() => { + drained = true + }) + await Promise.resolve() + expect(drained).toBe(false) + release(baseSession) + await snapshotReady + await drain + expect(finished).toBe(false) + controller.abort() + await result + gate.resume() + }) + it('rejects renderer callers before exposing run existence', async () => { const { service } = createHarness() diff --git a/test/main/config/migration.test.ts b/test/main/config/migration.test.ts index 0ebc795103..063430151e 100644 --- a/test/main/config/migration.test.ts +++ b/test/main/config/migration.test.ts @@ -13,21 +13,21 @@ import { migrateConfigStorage } from '@/config/migration' import { RAW_PROVIDER_MODEL_FACTS_MIGRATION_ID } from '@/provider/providerModelFacts' import { USER_MODEL_CONFIG_MIGRATION_ID } from '@/provider/userModelConfig' -const electronStores = vi.hoisted(() => new Map>()) +const legacyStores = vi.hoisted(() => new Map>()) -vi.mock('electron-store', () => ({ - default: class MockElectronStore { +vi.mock('conf', () => ({ + default: class MockConf { private readonly key: string - constructor(options: { name: string; cwd?: string; defaults?: Record }) { - this.key = `${options.cwd ?? ''}/${options.name}` - if (!electronStores.has(this.key)) { - electronStores.set(this.key, { ...(options.defaults ?? {}) }) + constructor(options: { configName: string; cwd?: string; defaults?: Record }) { + this.key = `${options.cwd ?? ''}/${options.configName}` + if (!legacyStores.has(this.key)) { + legacyStores.set(this.key, { ...(options.defaults ?? {}) }) } } get store(): Record { - return electronStores.get(this.key)! + return legacyStores.get(this.key)! } get(key: string, defaultValue?: unknown): unknown { @@ -41,7 +41,7 @@ vi.mock('electron-store', () => ({ })) describe('config storage migration', () => { - beforeEach(() => electronStores.clear()) + beforeEach(() => legacyStores.clear()) it('moves legacy storage before modules connect to sqlite', () => { const legacyStore = createStore({ @@ -53,11 +53,11 @@ describe('config storage migration', () => { hooksNotifications: { enabled: true } }) const settings = new SettingsStore(legacyStore) - electronStores.set('/user-data/provider_models/models_openai', { + legacyStores.set('/user-data/provider_models/models_openai', { models: [{ id: 'gpt-4', providerId: 'openai' }], custom_models: [{ id: 'custom', providerId: 'openai' }] }) - electronStores.set('/model-config', { + legacyStores.set('/user-data/model-config', { __meta__: { userConfigKeys: 'malformed' }, 'openai_-_gpt-4': { id: 'gpt-4', @@ -66,9 +66,9 @@ describe('config storage migration', () => { config: { isUserDefined: true } } }) - electronStores.set('/custom_prompts', { prompts: [{ id: 'custom' }] }) - electronStores.set('/system_prompts', { prompts: [{ id: 'system' }] }) - electronStores.set('/knowledge-configs', { knowledgeConfigs: [{ id: 'knowledge' }] }) + legacyStores.set('/user-data/custom_prompts', { prompts: [{ id: 'custom' }] }) + legacyStores.set('/user-data/system_prompts', { prompts: [{ id: 'system' }] }) + legacyStores.set('/user-data/knowledge-configs', { knowledgeConfigs: [{ id: 'knowledge' }] }) const tables = createAppSettingsTable() const providerTables = createProviderSettingsTable() const mcpTables = createMcpSettingsTable() @@ -129,9 +129,9 @@ describe('config storage migration', () => { expect(agentTables.deleteAgentSetting).toHaveBeenCalledWith('sqlite-mainline-normalization-v1') expect(legacyStore.get('hooksNotifications')).toBeUndefined() expect(legacyStore.get('appVersion')).toBe('1.0.0') - expect(electronStores.get('/custom_prompts')?.prompts).toEqual([]) - expect(electronStores.get('/system_prompts')?.prompts).toEqual([]) - expect(electronStores.get('/knowledge-configs')?.knowledgeConfigs).toEqual([]) + expect(legacyStores.get('/user-data/custom_prompts')?.prompts).toEqual([]) + expect(legacyStores.get('/user-data/system_prompts')?.prompts).toEqual([]) + expect(legacyStores.get('/user-data/knowledge-configs')?.knowledgeConfigs).toEqual([]) }) }) diff --git a/test/main/config/secretStore.test.ts b/test/main/config/secretStore.test.ts new file mode 100644 index 0000000000..e127a775d9 --- /dev/null +++ b/test/main/config/secretStore.test.ts @@ -0,0 +1,152 @@ +import { describe, expect, it, vi } from 'vitest' +import { tmpdir } from 'node:os' +import path from 'node:path' +import { SecretStore } from '@/config/secretStore' +import { createHeadlessNative } from '../../../src/headless/native' + +function fixture() { + const values = new Map([['credential', 'wrapped:previous']]) + const settings = { + get: (key: string) => values.get(key), + set: vi.fn((key: string, value: string) => values.set(key, value)), + delete: (key: string) => values.delete(key) + } + const crypto = { + isAvailable: vi.fn(async () => true), + encrypt: vi.fn(async (value: string) => `wrapped:${value}`), + decrypt: vi.fn(async (wrapped: string) => wrapped.slice('wrapped:'.length)) + } + return { values, settings, crypto, secrets: new SecretStore(settings as never, crypto) } +} + +describe('SecretStore lifecycle', () => { + it('authenticates headless credentials across restart and retires the original key owner', async () => { + const createNative = (masterKey: Uint8Array) => + createHeadlessNative({ + profileDirectory: path.join(tmpdir(), 'deepchat-headless-credential-contract'), + temporaryDirectory: tmpdir(), + masterKey, + publishEvent() {} + }) + const suppliedKey = Buffer.alloc(32, 7) + const first = createNative(suppliedKey) + suppliedKey.fill(0) + const restarted = createNative(Buffer.alloc(32, 7)) + const wrongKey = createNative(Buffer.alloc(32, 8)) + const { settings, values } = fixture() + const secrets = new SecretStore(settings as never, first.crypto) + const restoredSecrets = new SecretStore(settings as never, restarted.crypto) + try { + const plaintext = 'headless-token-🔐\u0000' + const wrapped = await secrets.wrap(plaintext) + expect(wrapped).not.toContain(plaintext) + expect(await secrets.wrap(plaintext)).not.toBe(wrapped) + secrets.setWrapped('credential', wrapped) + expect(await secrets.get('credential')).toBe(plaintext) + expect(await restoredSecrets.get('credential')).toBe(plaintext) + expect(await first.crypto.getBackend()).toBe('headless-aes-256-gcm') + await expect(wrongKey.crypto.decrypt(wrapped)).rejects.toThrow('authentication failed') + + const parts = wrapped.split(':') + const changed = Buffer.from(parts[2], 'base64url') + changed[changed.length - 1] ^= 1 + parts[2] = changed.toString('base64url') + await expect(restoredSecrets.unwrap(parts.join(':'))).rejects.toThrow('authentication failed') + await expect(restarted.crypto.decrypt('electron-wrapped-secret')).rejects.toThrow( + 'Unsupported headless credential envelope' + ) + expect(values.get('credential')).toBe(wrapped) + + const closing = first.close() + expect(first.close()).toBe(closing) + await closing + expect(await first.crypto.isAvailable()).toBe(false) + await expect(first.crypto.encrypt(plaintext)).rejects.toThrow('stopped') + await expect(first.crypto.decrypt(wrapped)).rejects.toThrow('stopped') + await expect(first.kernel.configurationHost.resume()).rejects.toThrow('stopped') + await expect(first.kernel.ocrCacheKeyProvider.loadOrCreateKey()).rejects.toThrow('stopped') + expect(await restoredSecrets.get('credential')).toBe(plaintext) + } finally { + await Promise.all([first.close(), restarted.close(), wrongKey.close()]) + } + }) + + it.each([false, true])( + 'fences opaque unwrap results across maintenance (native failure=%s)', + async (failed) => { + const f = fixture() + const pending = Promise.withResolvers() + f.crypto.decrypt.mockReturnValueOnce(pending.promise) + const result = f.secrets.unwrap('wrapped:opaque') + const rejected = expect(result).rejects.toThrow('interrupted') + f.secrets.pause() + f.secrets.resume() + if (failed) pending.reject(new Error('native unavailable')) + else pending.resolve('opaque') + await rejected + expect(f.settings.set).not.toHaveBeenCalled() + } + ) + + it('does not cache or rewrite opaque credentials after a native unwrap failure', async () => { + const f = fixture() + expect(await f.secrets.unwrap('wrapped:opaque')).toBe('opaque') + f.crypto.decrypt.mockRejectedValueOnce(new Error('native unavailable')) + await expect(f.secrets.unwrap('wrapped:opaque')).rejects.toThrow('native unavailable') + expect(f.settings.set).not.toHaveBeenCalled() + }) + it.each(['encrypt', 'decrypt'] as const)( + 'rejects a pending %s after maintenance resumes, including native failure', + async (operation) => { + for (const failed of [false, true]) { + const f = fixture() + const pending = Promise.withResolvers() + f.crypto[operation].mockImplementationOnce(() => pending.promise) + const result = + operation === 'encrypt' ? f.secrets.wrap('next') : f.secrets.get('credential') + const rejected = expect(result).rejects.toThrow('interrupted') + await vi.waitFor(() => expect(f.crypto[operation]).toHaveBeenCalledOnce()) + f.secrets.pause() + await expect(f.secrets.get('credential')).rejects.toThrow('interrupted') + expect(() => f.secrets.setWrapped('credential', 'unexpected')).toThrow('interrupted') + f.secrets.resume() + if (failed) pending.reject(new Error('Native crypto unavailable')) + else pending.resolve(operation === 'encrypt' ? 'wrapped:next' : 'previous') + await rejected + expect(f.values.get('credential')).toBe('wrapped:previous') + expect(await f.secrets.get('credential')).toBe('previous') + } + } + ) + + it('does not release a credential replaced or removed while decrypting', async () => { + const f = fixture() + const pending = Promise.withResolvers() + f.crypto.decrypt.mockReturnValueOnce(pending.promise) + const read = f.secrets.get('credential') + f.secrets.restoreWrapped('credential', undefined) + const rejected = expect(read).rejects.toThrow('credential changed') + pending.resolve('previous') + await rejected + expect(await f.secrets.get('credential')).toBe('') + }) + + it('keeps existing wrapped data when encryption is unavailable or fails', async () => { + const f = fixture() + f.crypto.isAvailable.mockResolvedValueOnce(false) + await expect(f.secrets.wrap('next')).rejects.toThrow('safeStorageUnavailable') + f.crypto.encrypt.mockRejectedValueOnce(new Error('Native encryption failed')) + await expect(f.secrets.wrap('next')).rejects.toThrow('Native encryption failed') + expect(f.settings.set).not.toHaveBeenCalled() + expect(f.values.get('credential')).toBe('wrapped:previous') + }) + + it('does not cache plaintext or rewrite credentials when native decryption fails', async () => { + const f = fixture() + expect(await f.secrets.get('credential')).toBe('previous') + f.crypto.decrypt.mockRejectedValueOnce(new Error('Native decryption unavailable')) + expect(await f.secrets.get('credential')).toBe('') + expect(await f.secrets.get('credential')).toBe('previous') + expect(f.settings.set).not.toHaveBeenCalled() + }) +}) diff --git a/test/main/config/settingsStore.test.ts b/test/main/config/settingsStore.test.ts new file mode 100644 index 0000000000..a846072b9f --- /dev/null +++ b/test/main/config/settingsStore.test.ts @@ -0,0 +1,106 @@ +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' +import { existsSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs' +import { tmpdir } from 'node:os' +import path from 'node:path' +import Database from 'better-sqlite3-multiple-ciphers' +import { createSettingsStore } from '@/config/settingsStore' +import { SettingsDatabase } from '@/settings/data/database' + +vi.unmock('fs') + +describe('application settings file owner', () => { + let profile: string + let db: InstanceType | undefined + const options = () => ({ + userDataDirectory: profile, + homeDirectory: path.join(profile, 'home-fact'), + currentAppVersion: '1.2.3-beta.4' + }) + const readFile = () => JSON.parse(readFileSync(path.join(profile, 'app-settings.json'), 'utf8')) + + beforeEach(() => { + profile = mkdtempSync(path.join(tmpdir(), 'deepchat-settings-owner-')) + }) + afterEach(() => { + db?.close() + db = undefined + rmSync(profile, { recursive: true, force: true }) + }) + + it('uses the legacy filename and bootstrap defaults without native directory lookup', () => { + const store = createSettingsStore(options()) + + expect(store.path).toBe(path.join(profile, 'app-settings.json')) + expect(store.get('language')).toBe('system') + expect(store.get('appVersion')).toBe('1.2.3-beta.4') + expect(store.get('skillsPath')).toBe(path.join(profile, 'home-fact', '.deepchat', 'skills')) + expect(store.get('syncFolderPath')).toBe(path.join(profile, 'sync')) + expect(store.get('privacyModeEnabled')).toBe(false) + expect(existsSync(options().homeDirectory)).toBe(false) + }) + + it('preserves existing values and extension data through dot-path mutations and reopen', () => { + writeFileSync( + path.join(profile, 'app-settings.json'), + JSON.stringify({ + appVersion: '0.9.0', + language: 'en-US', + extension: { enabled: true, retained: ['legacy'] } + }) + ) + const store = createSettingsStore(options()) + expect(store.get('appVersion')).toBe('0.9.0') + expect(store.get('language')).toBe('en-US') + store.set({ language: 'zh-CN', 'extension.nested.mode': 'custom' }) + expect(store.has('extension.nested.mode')).toBe(true) + expect(readFile().extension).toEqual({ + enabled: true, + retained: ['legacy'], + nested: { mode: 'custom' } + }) + store.delete('extension.nested.mode') + const reopened = createSettingsStore(options()) + expect(reopened.has('extension.nested.mode')).toBe(false) + expect(reopened.get('extension.retained')).toEqual(['legacy']) + expect(reopened.get('language')).toBe('zh-CN') + }) + + it('rejects corrupt legacy data without replacing it with defaults', () => { + const file = path.join(profile, 'app-settings.json') + writeFileSync(file, '{broken legacy data') + expect(() => createSettingsStore(options())).toThrow() + expect(readFileSync(file, 'utf8')).toBe('{broken legacy data') + }) + + it.each(['userDataDirectory', 'homeDirectory'] as const)('rejects a relative %s', (key) => { + expect(() => createSettingsStore({ ...options(), [key]: 'relative' })).toThrow( + 'Settings directories must be absolute' + ) + expect(existsSync(path.join(profile, 'app-settings.json'))).toBe(false) + }) + + it('retains one file adapter while portable and sensitive settings use the attached database', () => { + const store = createSettingsStore(options()) + store.set('defaultModel', { providerId: 'legacy', modelId: 'old' }) + db = new Database(':memory:') + const database = new SettingsDatabase({ getDatabase: () => db! }) + database.appSettingsTable.createTable() + store.attachDatabase(database) + expect(store.get('defaultModel')).toEqual({ providerId: 'legacy', modelId: 'old' }) + store.set('defaultModel', { providerId: 'current', modelId: 'new' }) + store.set('hooksNotifications', { hooks: [{ id: 'database-only' }] }) + store.set('language', 'en-US') + + expect(database.appSettingsTable.getAppSetting('defaultModel')).toEqual({ + providerId: 'current', + modelId: 'new' + }) + expect(database.appSettingsTable.getAppSetting('hooksNotifications')).toEqual({ + hooks: [{ id: 'database-only' }] + }) + expect(readFile().hooksNotifications).toEqual({ hooks: [] }) + expect(readFile().defaultModel).toEqual({ providerId: 'legacy', modelId: 'old' }) + expect(readFile().language).toBe('en-US') + expect(() => store.attachDatabase(database)).toThrow('Settings database is already attached') + }) +}) diff --git a/test/main/deeplink/deeplinkService.test.ts b/test/main/deeplink/deeplinkService.test.ts index 6b2c007e90..ab98ef3f31 100644 --- a/test/main/deeplink/deeplinkService.test.ts +++ b/test/main/deeplink/deeplinkService.test.ts @@ -19,12 +19,7 @@ const presenterMock = vi.hoisted(() => ({ getAllWindows: vi.fn().mockReturnValue([]), getFocusedWindow: vi.fn().mockReturnValue(null) }, - providerSettings: { - getProviderById: vi.fn() - }, - mcpService: { - isReady: vi.fn().mockReturnValue(true) - }, + providerExists: vi.fn(), semanticNotifications: { occur: vi.fn(), recover: vi.fn() @@ -42,16 +37,17 @@ describe('DeeplinkService', () => { const createProviderInstallBase64 = (payload: Record) => Buffer.from(JSON.stringify(payload)).toString('base64') - const createDeeplinkService = async () => { + const createDeeplinkService = async (mcpReady = true) => { const { createDeeplinkActions } = await import('@/deeplink/actions') const { DeeplinkService } = await import('@/deeplink') const actions = createDeeplinkActions({ window: presenterMock.windowPresenter as any, - config: presenterMock.providerSettings as any, - mcp: presenterMock.mcpService as any, + hasProvider: presenterMock.providerExists, notifications: presenterMock.semanticNotifications }) - return new DeeplinkService(actions.desktop, actions.mcp, actions.provider) + const service = new DeeplinkService(actions.desktop, actions.mcp, actions.provider) + service.observeMcpReadiness(mcpReady) + return service } beforeEach(async () => { @@ -65,21 +61,9 @@ describe('DeeplinkService', () => { presenterMock.windowPresenter.setPendingSettingsProviderInstall.mockReset() presenterMock.windowPresenter.getAllWindows.mockReturnValue([]) presenterMock.windowPresenter.getFocusedWindow.mockReturnValue(null) - presenterMock.mcpService.isReady.mockReturnValue(true) - presenterMock.providerSettings.getProviderById.mockImplementation((providerId: string) => { - if (providerId === 'openai') { - return { - id: 'openai', - name: 'OpenAI', - apiType: 'openai', - apiKey: '', - baseUrl: 'https://api.openai.com/v1', - enable: false - } - } - - return undefined - }) + presenterMock.providerExists.mockImplementation( + async (providerId: string) => providerId === 'openai' + ) }) afterEach(() => { @@ -235,20 +219,63 @@ describe('DeeplinkService', () => { } } const url = `deepchat:mcp/install?code=${Buffer.from(JSON.stringify(payload)).toString('base64')}` - presenterMock.mcpService.isReady.mockReturnValue(false) + deeplinkService.observeMcpReadiness(false) await deeplinkService.handleDeepLink(url) expect((deeplinkService as any).pendingMcpInstallUrl).toBe(url) expect(presenterMock.windowPresenter.createSettingsWindow).not.toHaveBeenCalled() - presenterMock.mcpService.isReady.mockReturnValue(true) - deeplinkService.processPendingMcpInstall() + deeplinkService.observeMcpReadiness(true) expect(handleDeepLink).toHaveBeenNthCalledWith(2, url) expect((deeplinkService as any).pendingMcpInstallUrl).toBeNull() }) + it('awaits provider existence before allowing the native settings preview', async () => { + const service = await createDeeplinkService() + const query = Promise.withResolvers() + presenterMock.providerExists.mockReturnValue(query.promise) + const payload = createProviderInstallBase64({ + id: 'openai', + baseUrl: 'https://proxy.example.com/v1', + apiKey: 'incoming-fixture' + }) + const request = service.handleDeepLink(`deepchat://provider/install?v=1&data=${payload}`) + expect(presenterMock.windowPresenter.createSettingsWindow).not.toHaveBeenCalled() + query.resolve(false) + await request + expect(presenterMock.windowPresenter.createSettingsWindow).not.toHaveBeenCalled() + expect(presenterMock.semanticNotifications.occur).toHaveBeenCalledWith({ + code: 'providerDeeplink.failed', + reason: 'provider-not-found' + }) + }) + + it('releases only the latest pending MCP preview once for an observed startup', async () => { + const { DeeplinkService } = await import('@/deeplink') + const requestInstall = vi.fn(async (_mcpConfig: string) => true) + const service = new DeeplinkService( + { requestStart: async () => true }, + { requestInstall }, + { hasProvider: async () => false, requestInstall: async () => true, reportFailure: () => {} } + ) + for (const name of ['first', 'latest']) { + const payload = Buffer.from( + JSON.stringify({ mcpServers: { [name]: { command: 'node', args: [] } } }) + ).toString('base64') + await service.handleDeepLink(`deepchat://mcp/install?code=${payload}`) + } + service.observeMcpReadiness(false) + expect(requestInstall).not.toHaveBeenCalled() + service.observeMcpReadiness(true) + await vi.waitFor(() => expect(requestInstall).toHaveBeenCalledOnce()) + expect(Object.keys(JSON.parse(requestInstall.mock.calls[0][0]).mcpServers)).toEqual(['latest']) + service.observeMcpReadiness(true) + await Promise.resolve() + expect(requestInstall).toHaveBeenCalledOnce() + }) + it('routes built-in provider imports to settings and stores the preview for replay', async () => { const deeplinkService = await createDeeplinkService() const payload = { diff --git a/test/main/desktop/browser/BrowserProfileImportService.test.ts b/test/main/desktop/browser/BrowserProfileImportService.test.ts index 9d2805d050..163553fa55 100644 --- a/test/main/desktop/browser/BrowserProfileImportService.test.ts +++ b/test/main/desktop/browser/BrowserProfileImportService.test.ts @@ -15,8 +15,8 @@ afterEach(async () => { }) const createCookie = (details: CookiesSetDetails): Cookie => ({ - name: details.name, - value: details.value, + name: details.name ?? '', + value: details.value ?? '', domain: details.domain ?? new URL(details.url).hostname, hostOnly: details.domain === undefined, path: details.path ?? '/', diff --git a/test/main/desktop/browser/BrowserTab.test.ts b/test/main/desktop/browser/BrowserTab.test.ts index 4460c85a6f..f8210ea69b 100644 --- a/test/main/desktop/browser/BrowserTab.test.ts +++ b/test/main/desktop/browser/BrowserTab.test.ts @@ -16,7 +16,7 @@ class MockWebContents extends EventEmitter { isAttached: vi.fn(() => false), detach: vi.fn(), attach: vi.fn(), - sendCommand: vi.fn(async () => ({})) + sendCommand: vi.fn(async (_method: string, _params?: unknown): Promise => ({})) } session = {} navigationHistory = { @@ -101,9 +101,9 @@ describe('BrowserTab', () => { const createTab = () => { const webContents = new MockWebContents() const cdpManager = { - createSession: vi.fn(async () => undefined), + createSession: vi.fn(async (): Promise => undefined), getDOM: vi.fn(async () => ''), - evaluateScript: vi.fn(async () => 1) + evaluateScript: vi.fn(async (): Promise => 1) } const screenshotManager = { captureScreenshot: vi.fn(async () => 'image-data') @@ -318,6 +318,54 @@ describe('BrowserTab', () => { expect(order).toEqual(['commit', 'target']) }) + it('waits for remote authorization and rechecks cancellation and target lifetime', async () => { + for (const operation of ['navigation', 'cdp'] as const) { + for (const terminal of ['approved', 'denied', 'canceled', 'destroyed'] as const) { + const { tab, webContents } = createTab() + const permission = Promise.withResolvers() + const entered = Promise.withResolvers() + const controller = new AbortController() + const authorize = () => { + entered.resolve() + return permission.promise + } + const result = + operation === 'navigation' + ? tab.navigateUntilDomReady( + 'https://example.com', + 30000, + authorize, + undefined, + controller.signal + ) + : tab.sendCdpCommand('Page.reload', {}, authorize, undefined, controller.signal) + const settled = result.then( + () => undefined, + (error: unknown) => error + ) + await entered.promise + expect(webContents.loadURL).not.toHaveBeenCalled() + expect(webContents.debugger.sendCommand).not.toHaveBeenCalled() + if (terminal === 'canceled') controller.abort(new Error('run canceled')) + if (terminal === 'destroyed') webContents.destroyed = true + if (terminal === 'denied') permission.reject(new Error('dispatch denied')) + else permission.resolve() + if (terminal === 'approved') { + await Promise.resolve() + if (operation === 'navigation') { + expect(webContents.loadURL).toHaveBeenCalledOnce() + webContents.finishLoad() + } else expect(webContents.debugger.sendCommand).toHaveBeenCalledOnce() + expect(await settled).toBeUndefined() + } else { + expect(await settled).toBeInstanceOf(Error) + expect(webContents.loadURL).not.toHaveBeenCalled() + expect(webContents.debugger.sendCommand).not.toHaveBeenCalled() + } + } + } + }) + it('does not commit when CDP readiness or session validation fails', async () => { const { tab, webContents, cdpManager } = createTab() const beforeDispatch = vi.fn() diff --git a/test/main/desktop/browser/YoBrowserPresenter.test.ts b/test/main/desktop/browser/YoBrowserPresenter.test.ts index 9ddd0c96ce..f25c061f3c 100644 --- a/test/main/desktop/browser/YoBrowserPresenter.test.ts +++ b/test/main/desktop/browser/YoBrowserPresenter.test.ts @@ -9,7 +9,7 @@ const overlayHideMock = vi.fn() const overlayDestroyMock = vi.fn() const nativeInitializeMock = vi.fn(async () => false) const nativeIsAvailableMock = vi.fn(() => false) -const nativePrepareMock = vi.fn(() => 'renderer-canvas') +const nativePrepareMock = vi.fn((_target: Record) => 'renderer-canvas') const nativePresentMock = vi.fn(() => true) const nativeHideMock = vi.fn() const nativeRemoveTargetMock = vi.fn() @@ -247,12 +247,6 @@ describe('YoBrowserPresenter', () => { } })) - vi.doMock('@/desktop/browser/DownloadManager', () => ({ - DownloadManager: class { - downloadFile = vi.fn() - } - })) - vi.doMock('@/desktop/browser/yoBrowserSession', () => ({ getYoBrowserSession: () => ({}), getYoBrowserUnpartitionedCookies: vi.fn(async () => []), @@ -1417,4 +1411,38 @@ describe('YoBrowserPresenter', () => { await presenter.destroySessionBrowser('session-a') expect(overlayDestroyMock).toHaveBeenCalled() }) + + it('awaits physical page closure and retains failed retirement', async () => { + const { presenter, windows, getSessionWebContents } = await setupPresenter() + windows.set(1, new MockBrowserWindow(1)) + for (const session of ['held', 'failed']) { + const loading = presenter.loadUrl(session, 'https://example.com') + getSessionWebContents(session)!.emitDomReady() + await loading + } + const held = getSessionWebContents('held')! + const failed = getSessionWebContents('failed')! + held.close.mockImplementation(() => {}) + failed.close.mockImplementation(() => { + throw new Error('Native close refused') + }) + let settled = false + const stopping = presenter.shutdown() + const rejected = expect(stopping).rejects.toThrow('Native browser pages did not retire') + void stopping.finally(() => (settled = true)).catch(() => {}) + expect(presenter.shutdown()).toBe(stopping) + await vi.advanceTimersByTimeAsync(0) + expect(held.close).toHaveBeenCalledWith({ waitForBeforeUnload: false }) + expect(settled).toBe(false) + await expect(presenter.loadUrl('late', 'https://example.com')).rejects.toThrow('stopping') + expect(getSessionWebContents('late')).toBeNull() + held.destroyed = true + held.emit('destroyed') + await rejected + expect(await presenter.getBrowserStatus('held')).toMatchObject({ initialized: false }) + expect(await presenter.getBrowserStatus('failed')).toMatchObject({ initialized: true }) + expect(presenter.shutdown()).toBe(stopping) + failed.destroyed = true + failed.emit('destroyed') + }) }) diff --git a/test/main/desktop/browser/yoBrowserSession.test.ts b/test/main/desktop/browser/yoBrowserSession.test.ts new file mode 100644 index 0000000000..2f6482e7e0 --- /dev/null +++ b/test/main/desktop/browser/yoBrowserSession.test.ts @@ -0,0 +1,86 @@ +import { expect, it, vi } from 'vitest' +import { getYoBrowserUnpartitionedCookies } from '@/desktop/browser/yoBrowserSession' + +const fixture = vi.hoisted(() => ({ + failDetach: false, + closeRequested: () => {}, + finishClose: () => {} +})) + +vi.mock('electron', async () => { + const { EventEmitter } = await import('node:events') + return { + session: { + fromPartition: () => ({ + setPermissionRequestHandler() {}, + setUserAgent() {}, + webRequest: { onBeforeSendHeaders() {} } + }) + }, + WebContentsView: class { + webContents = new (class extends EventEmitter { + destroyed = false + debugger = { + attach() {}, + isAttached: () => true, + detach() { + if (fixture.failDetach) throw new Error('Native detach refused') + }, + sendCommand: async () => ({ + cookies: [ + { name: 'plain', value: 'fixture', domain: 'example.test', path: '/', session: true }, + { name: 'partitioned', partitionKey: { topLevelSite: 'https://example.test' } } + ] + }) + } + isDestroyed() { + return this.destroyed + } + close() { + fixture.closeRequested() + } + })() + constructor() { + fixture.finishClose = () => { + this.webContents.destroyed = true + this.webContents.emit('destroyed') + } + } + } + } +}) + +it.each([false, true])( + 'joins temporary cookie-view destruction when detach fails: %s', + async (failDetach) => { + const entered = Promise.withResolvers() + fixture.failDetach = failDetach + fixture.closeRequested = () => entered.resolve() + let settled = false + const reading = getYoBrowserUnpartitionedCookies().then( + (cookies) => { + settled = true + return { cookies } + }, + (error: unknown) => { + settled = true + return { error } + } + ) + try { + await entered.promise + await Promise.resolve() + expect(settled).toBe(false) + fixture.finishClose() + const result = await reading + if (failDetach) expect(result).toMatchObject({ error: new Error('Native detach refused') }) + else + expect(result).toMatchObject({ + cookies: [{ name: 'plain', value: 'fixture', url: 'http://example.test/' }] + }) + } finally { + fixture.finishClose() + await reading + } + } +) diff --git a/test/main/desktop/configurationHost.test.ts b/test/main/desktop/configurationHost.test.ts new file mode 100644 index 0000000000..6318cdf456 --- /dev/null +++ b/test/main/desktop/configurationHost.test.ts @@ -0,0 +1,129 @@ +import { mkdtempSync, existsSync, rmSync } from 'node:fs' +import { tmpdir } from 'node:os' +import { join } from 'node:path' +import { shell } from 'electron' +import fontList from 'font-list' +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' +import { DesktopConfigurationHost } from '@/desktop/configurationHost' +import { reportNativeMainError } from '@/logging' + +vi.unmock('fs') +vi.mock('font-list', () => ({ default: { getFonts: vi.fn() } })) +vi.mock('@/logging', () => ({ reportNativeMainError: vi.fn() })) + +describe('DesktopConfigurationHost', () => { + let directory: string + const hosts: DesktopConfigurationHost[] = [] + const setup = () => { + const persistence = vi.fn() + const restart = vi.fn(async (): Promise => undefined) + const host = new DesktopConfigurationHost(directory, persistence, restart) + hosts.push(host) + return { host, persistence, restart } + } + + beforeEach(() => { + vi.clearAllMocks() + vi.useFakeTimers() + directory = mkdtempSync(join(tmpdir(), 'deepchat-desktop-configuration-')) + vi.mocked(shell.openPath).mockResolvedValue('') + }) + + afterEach(() => { + for (const host of hosts.splice(0)) host.close() + vi.useRealTimers() + rmSync(directory, { recursive: true, force: true }) + }) + + it('opens only the private profile logging directory and propagates native failures', async () => { + const { host } = setup() + await host.openLoggingFolder() + expect(existsSync(join(directory, 'logs'))).toBe(true) + expect(shell.openPath).toHaveBeenCalledWith(join(directory, 'logs')) + vi.mocked(shell.openPath).mockResolvedValueOnce('permission denied') + await expect(host.openLoggingFolder()).rejects.toThrow('permission denied') + }) + + it('delegates font enumeration to the existing native implementation', async () => { + const { host } = setup() + vi.mocked(fontList.getFonts).mockResolvedValueOnce(['Inter Regular', 'Menlo']) + await expect(host.listSystemFonts()).resolves.toEqual(['Inter Regular', 'Menlo']) + expect(fontList.getFonts).toHaveBeenCalledTimes(1) + }) + + it('does not launch a folder after close while its directory creation is pending', async () => { + const { host } = setup() + const opening = host.openLoggingFolder() + host.close() + await expect(opening).rejects.toThrow('host is closed') + expect(shell.openPath).not.toHaveBeenCalled() + }) + + it('retains one logging/content-protection restart across startup and maintenance pauses', async () => { + const { host, restart, persistence } = setup() + await host.applyLogging(true) + await vi.advanceTimersByTimeAsync(2000) + expect(restart).not.toHaveBeenCalled() + host.resume() + await vi.advanceTimersByTimeAsync(800) + await host.applyLogging(false) + host.requestRestart() + host.pause() + await vi.advanceTimersByTimeAsync(2000) + expect(restart).not.toHaveBeenCalled() + host.resume() + await vi.advanceTimersByTimeAsync(999) + expect(restart).not.toHaveBeenCalled() + await vi.advanceTimersByTimeAsync(1) + expect(restart).toHaveBeenCalledTimes(1) + expect(persistence.mock.calls).toEqual([[true], [false]]) + }) + + it('cancels a pending restart and fences native requests when closed', async () => { + const { host, restart } = setup() + host.resume() + await host.applyLogging(true) + host.close() + await vi.advanceTimersByTimeAsync(2000) + expect(restart).not.toHaveBeenCalled() + await expect(host.applyLogging(false)).rejects.toThrow('host is closed') + await expect(host.listSystemFonts()).rejects.toThrow('host is closed') + await expect(host.openLoggingFolder()).rejects.toThrow('host is closed') + expect(() => host.requestRestart()).toThrow('host is closed') + expect(() => host.resume()).toThrow('host is closed') + }) + + it('transfers a fired restart to the shutdown coordinator without waiting on itself', async () => { + const { host, restart } = setup() + let finish!: () => void + restart.mockImplementationOnce( + () => + new Promise((resolve) => { + finish = resolve + }) + ) + host.resume() + await host.applyLogging(true) + await vi.advanceTimersByTimeAsync(1000) + expect(restart).toHaveBeenCalledTimes(1) + host.close() + finish() + await Promise.resolve() + expect(restart).toHaveBeenCalledTimes(1) + }) + + it('reports failed restart authority without replaying it', async () => { + const { host, restart } = setup() + const failure = new Error('shutdown did not drain') + restart.mockRejectedValueOnce(failure) + host.resume() + await host.applyLogging(true) + await vi.advanceTimersByTimeAsync(2000) + expect(reportNativeMainError).toHaveBeenCalledWith('Application restart failed:', failure) + expect(restart).toHaveBeenCalledTimes(1) + }) + + it('requires an absolute private profile directory', () => { + expect(() => new DesktopConfigurationHost('relative', vi.fn(), vi.fn())).toThrow('absolute') + }) +}) diff --git a/test/main/desktop/configurationProjection.test.ts b/test/main/desktop/configurationProjection.test.ts new file mode 100644 index 0000000000..dbbbd7ee86 --- /dev/null +++ b/test/main/desktop/configurationProjection.test.ts @@ -0,0 +1,95 @@ +import { describe, expect, it, vi } from 'vitest' +import { DesktopConfigurationProjection } from '@/desktop/configurationProjection' +import { defaultShortcutKey } from '@/desktop/shortcutKeySettings' +import type { NativeConfigurationSnapshot } from '@shared/types/nativeConfiguration' + +const snapshot = (): NativeConfigurationSnapshot => ({ + ownerId: 'core-one', + version: 1, + language: 'en-US', + notificationsEnabled: true, + closeToQuit: false, + contentProtectionEnabled: false, + shortcutKeys: { ...defaultShortcutKey }, + floatingButtonEnabled: false, + floatingButtonBounds: null, + loggingEnabled: false, + privacyModeEnabled: true, + updateChannel: 'stable' +}) + +describe('DesktopConfigurationProjection', () => { + it('rejects old, foreign and invalid revisions without reversing committed fields', () => { + const initial = snapshot() + const view = new DesktopConfigurationProjection( + initial, + () => false, + async () => {} + ) + view.apply({ ...initial, version: 3, loggingEnabled: true }) + for (const change of [ + { version: 2 }, + { version: Number.NaN }, + { version: 4, ownerId: 'obsolete-core' } + ]) + view.apply({ ...initial, ...change, loggingEnabled: false }) + expect(view.getLoggingEnabled()).toBe(true) + expect(view.isPrivacyModeEnabled()).toBe(true) + }) + + it('does not share mutable shortcut or bounds objects with callers', () => { + const initial = { + ...snapshot(), + floatingButtonBounds: { x: 1, y: 2, dockSide: 'left' as const } + } + const view = new DesktopConfigurationProjection( + initial, + () => false, + async () => {} + ) + initial.floatingButtonBounds.x = 99 + const read = view.getFloatingButtonBounds()! + read.x = 88 + expect(view.getFloatingButtonBounds()?.x).toBe(1) + expect(view.getShortcutKeys()).not.toBe(view.getShortcutKeys()) + }) + + it('waits for acknowledged writes and drains them after admission closes', async () => { + const pending = Promise.withResolvers() + const write = vi.fn(() => pending.promise) + const view = new DesktopConfigurationProjection(snapshot(), () => true, write) + const bounds = { x: 10, y: 20, dockSide: 'right' as const } + const writing = view.setFloatingButtonBounds(bounds) + expect(view.getFloatingButtonBounds()).toBeNull() + view.stopWrites() + expect(() => view.startWrites()).toThrow('still draining') + await expect(view.setFloatingButtonBounds(bounds)).rejects.toThrow('stopped') + let drained = false + const draining = view.drainWrites().then(() => { + drained = true + }) + await Promise.resolve() + expect(drained).toBe(false) + pending.resolve() + await Promise.all([writing, draining]) + view.startWrites() + expect(await view.getCurrentThemeIsDark()).toBe(true) + }) + + it('propagates failed persistence without installing optimistic bounds or leaking work', async () => { + const view = new DesktopConfigurationProjection( + snapshot(), + () => false, + async () => { + throw new Error('storage unavailable') + } + ) + await expect(view.setFloatingButtonBounds({ x: 1, y: 2, dockSide: 'left' })).rejects.toThrow( + 'storage unavailable' + ) + expect(view.getFloatingButtonBounds()).toBeNull() + view.stopWrites() + await view.drainWrites() + expect(() => view.startWrites()).not.toThrow() + }) +}) diff --git a/test/main/desktop/dialogService.test.ts b/test/main/desktop/dialogService.test.ts index 48eafda1bd..dbb4f7b696 100644 --- a/test/main/desktop/dialogService.test.ts +++ b/test/main/desktop/dialogService.test.ts @@ -1,12 +1,19 @@ import { beforeEach, describe, expect, it, vi } from 'vitest' +import { dialog } from 'electron' +import { CORE_NATIVE_CALLS } from '@shared/harness/core' const publishEventMock = vi.hoisted(() => vi.fn()) +vi.mock('electron', () => ({ + dialog: { showOpenDialog: vi.fn(), showSaveDialog: vi.fn() } +})) + import { DialogService } from '@/desktop/dialog' describe('DialogService', () => { beforeEach(() => { publishEventMock.mockReset() + vi.mocked(dialog.showOpenDialog).mockReset() }) it('publishes dialog requests through the typed deepchat event channel only', async () => { @@ -41,4 +48,61 @@ describe('DialogService', () => { await expect(responsePromise).resolves.toBe('ok') }) + + it('rejects pending confirmations and refuses new dialogs after shutdown', async () => { + const presenter = new DialogService(publishEventMock) + const pending = presenter.showDialog({ title: 'Confirm shutdown' }) + const rejected = expect(pending).rejects.toThrow('Dialog service is stopping') + const { id } = publishEventMock.mock.calls[0][1] as { id: string } + + presenter.destroy() + await rejected + await presenter.handleDialogResponse({ id, button: 'confirm' }) + await expect(presenter.showDialog({ title: 'Late confirmation' })).rejects.toThrow( + 'Dialog service is stopping' + ) + expect(publishEventMock).toHaveBeenCalledOnce() + }) + + it('queues bounded native pickers and waits for physical closure before retiring', async () => { + const presenter = new DialogService(publishEventMock) + const parent = { isDestroyed: () => false, hide() {} } as Electron.BrowserWindow + const first = Promise.withResolvers() + const held = Promise.withResolvers() + vi.mocked(dialog.showOpenDialog) + .mockReturnValueOnce(first.promise) + .mockResolvedValueOnce({ canceled: false, filePaths: ['/chosen'] }) + .mockReturnValueOnce(held.promise) + const initial = presenter.showOpenDialog(parent, {}) + const queued = presenter.showOpenDialog(parent, {}) + await vi.waitFor(() => expect(dialog.showOpenDialog).toHaveBeenCalledOnce()) + first.resolve({ canceled: true, filePaths: [] }) + await expect(initial).resolves.toEqual({ canceled: true, filePaths: [] }) + await expect(queued).resolves.toEqual({ canceled: false, filePaths: ['/chosen'] }) + + const accepted = Array.from({ length: CORE_NATIVE_CALLS }, () => + presenter.showOpenDialog(parent, {}).then( + (result) => result, + () => null + ) + ) + await vi.waitFor(() => expect(dialog.showOpenDialog).toHaveBeenCalledTimes(3)) + await expect(presenter.showOpenDialog(parent, {})).rejects.toThrow( + 'Native dialog limit exceeded' + ) + presenter.destroy() + let retired = false + const closing = presenter.drainNativeDialogs().then(() => { + retired = true + }) + await Promise.resolve() + expect(retired).toBe(false) + held.resolve({ canceled: true, filePaths: [] }) + await closing + expect(await Promise.all(accepted)).toEqual([ + { canceled: true, filePaths: [] }, + ...Array(CORE_NATIVE_CALLS - 1).fill(null) + ]) + expect(dialog.showOpenDialog).toHaveBeenCalledTimes(3) + }) }) diff --git a/test/main/desktop/floatingButton/index.test.ts b/test/main/desktop/floatingButton/index.test.ts index 579513c15c..bcac717c7b 100644 --- a/test/main/desktop/floatingButton/index.test.ts +++ b/test/main/desktop/floatingButton/index.test.ts @@ -2,6 +2,8 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' import { FLOATING_BUTTON_EVENTS } from '../../../../src/main/events' import { getCollapsedWidgetSize, getExpandedWidgetSize } from '@/desktop/floatingButton/layout' import type { SessionWithState } from '../../../../src/shared/types/agent-interface' +import { buildFloatingWidgetSnapshot } from '@/session/widgetSnapshot' +import type { FloatingWidgetSnapshot } from '@shared/types/floating-widget' const { electronState, @@ -79,6 +81,7 @@ const { return { electronState: { + frame: { url: 'http://localhost:5173/floating/' }, workArea, eventHandlers, invokeHandlers, @@ -147,13 +150,18 @@ vi.mock('@/desktop/floatingButton/FloatingButtonWindow', () => ({ public setDockSide = vi.fn((dockSide: 'left' | 'right') => { floatingWindowState.dockSide = dockSide }) - public getWindow = vi.fn(() => ({ + private window = { + id: 3, isDestroyed: () => false, webContents: { id: 1, + mainFrame: electronState.frame, + isDestroyed: () => false, + getURL: () => electronState.frame.url, send: webContentsSendMock } - })) + } + public getWindow = vi.fn(() => this.window) constructor() { floatingWindowState.instance = this as unknown as typeof floatingWindowState.instance @@ -161,7 +169,7 @@ vi.mock('@/desktop/floatingButton/FloatingButtonWindow', () => ({ } })) -import { FloatingButtonPresenter } from '@/desktop/floatingButton' +import { FloatingButtonPresenter, type FloatingWidgetBusinessPort } from '@/desktop/floatingButton' describe('FloatingButtonPresenter drag layout sync', () => { let floatingPresenter: FloatingButtonPresenter | null = null @@ -176,20 +184,27 @@ describe('FloatingButtonPresenter drag layout sync', () => { const createDesktopSettings = () => ({ getFloatingButtonEnabled: vi.fn(() => true), getFloatingButtonBounds: vi.fn(() => null), - setFloatingButtonBounds: vi.fn(), - getLanguage: vi.fn(() => 'zh-CN'), - getCurrentThemeIsDark: vi.fn(() => false) + setFloatingButtonBounds: vi.fn(async () => {}), + getLanguage: vi.fn(() => 'zh-CN' as const), + getCurrentThemeIsDark: vi.fn(async () => false) }) const createFloatingPresenter = ( providerSettings = createProviderSettings(), - settings = createDesktopSettings() + settings = createDesktopSettings(), + business: FloatingWidgetBusinessPort = { + getSnapshot: async () => + buildFloatingWidgetSnapshot( + await getSessionListMock(), + await providerSettings.listAgents(), + false + ), + getRegistryIconMarkup: async () => '', + openSession: async () => true + } ) => new FloatingButtonPresenter( - providerSettings, settings, - { listSessions: getSessionListMock } as any, - { activate: vi.fn() } as any, { mainWindow: null, windows: new Map(), @@ -198,7 +213,8 @@ describe('FloatingButtonPresenter drag layout sync', () => { createAppWindow: vi.fn(async () => null), show: vi.fn() } as any, - { getWindowType: vi.fn(() => 'chat') } as any + { getWindowType: vi.fn(() => 'chat') } as any, + business ) const emitEvent = async (channel: string, payload?: unknown) => { @@ -207,10 +223,14 @@ describe('FloatingButtonPresenter drag layout sync', () => { throw new Error(`Missing IPC handler for ${channel}`) } - return await handler({}, payload) + return await handler( + { sender: { id: 1, isDestroyed: () => false }, senderFrame: electronState.frame }, + payload + ) } beforeEach(() => { + vi.stubEnv('ELECTRON_RENDERER_URL', 'http://localhost:5173') vi.useFakeTimers() electronState.reset() floatingWindowState.reset() @@ -226,11 +246,68 @@ describe('FloatingButtonPresenter drag layout sync', () => { floatingPresenter = null await vi.runOnlyPendingTimersAsync() vi.useRealTimers() + vi.unstubAllEnvs() + }) + + it('acknowledges Native startup before a Core read and ignores a retired window reply', async () => { + const held = Promise.withResolvers() + const fresh: FloatingWidgetSnapshot = { expanded: false, activeCount: 2, sessions: [] } + const business = { + getSnapshot: vi + .fn() + .mockImplementationOnce(() => held.promise) + .mockResolvedValue(fresh), + getRegistryIconMarkup: vi.fn(async () => ''), + openSession: vi.fn(async () => true) + } + floatingPresenter = createFloatingPresenter(undefined, undefined, business) + await floatingPresenter.initialize() + const retired = floatingWindowState.instance! + expect(retired.show).not.toHaveBeenCalled() + const oldRead = floatingPresenter.refreshWidgetState() + floatingPresenter.destroy() + await floatingPresenter.enable() + await floatingPresenter.refreshWidgetState() + expect(floatingWindowState.instance?.show).toHaveBeenCalledOnce() + held.resolve({ expanded: false, activeCount: 7, sessions: [] }) + await oldRead + expect(retired.show).not.toHaveBeenCalled() + const snapshot = await electronState.invokeHandlers.get( + FLOATING_BUTTON_EVENTS.SNAPSHOT_REQUEST + )!({ sender: { id: 1, isDestroyed: () => false }, senderFrame: electronState.frame }) + expect(snapshot).toEqual(fresh) + const before = business.getSnapshot.mock.calls.length + await expect( + electronState.invokeHandlers.get(FLOATING_BUTTON_EVENTS.SNAPSHOT_REQUEST)!({ + sender: { id: 2, isDestroyed: () => false }, + senderFrame: electronState.frame + }) + ).rejects.toThrow('caller retired') + expect(business.getSnapshot).toHaveBeenCalledTimes(before) + }) + + it('applies acknowledged host appearance without reentering the pending core command', async () => { + const settings = createDesktopSettings() + floatingPresenter = createFloatingPresenter(createProviderSettings(), settings) + await floatingPresenter.initialize() + settings.getLanguage.mockClear() + settings.getCurrentThemeIsDark.mockClear() + settings.getCurrentThemeIsDark.mockRejectedValue(new Error('core command is awaiting host')) + floatingPresenter.refreshLanguage('fa-IR') + await floatingPresenter.refreshTheme(true) + expect(webContentsSendMock).toHaveBeenCalledWith( + FLOATING_BUTTON_EVENTS.LANGUAGE_CHANGED, + 'fa-IR' + ) + expect(webContentsSendMock).toHaveBeenCalledWith(FLOATING_BUTTON_EVENTS.THEME_CHANGED, 'dark') + expect(settings.getLanguage).not.toHaveBeenCalled() + expect(settings.getCurrentThemeIsDark).not.toHaveBeenCalled() }) it('keeps the collapsed size stable when dragging interrupts collapse animation', async () => { floatingPresenter = createFloatingPresenter() await floatingPresenter.initialize() + await floatingPresenter.refreshWidgetState() expect(floatingWindowState.bounds).toMatchObject({ x: @@ -305,6 +382,23 @@ describe('FloatingButtonPresenter drag layout sync', () => { }) }) + it('rejects drag writes from another window or a nested frame', async () => { + const settings = createDesktopSettings() + floatingPresenter = createFloatingPresenter(createProviderSettings(), settings) + await floatingPresenter.initialize() + const before = { ...floatingWindowState.bounds } + for (const event of [ + { sender: { id: 999 }, senderFrame: electronState.frame }, + { sender: { id: 1 }, senderFrame: {} } + ]) { + electronState.eventHandlers.get(FLOATING_BUTTON_EVENTS.DRAG_START)!(event, { x: 100, y: 100 }) + electronState.eventHandlers.get(FLOATING_BUTTON_EVENTS.DRAG_MOVE)!(event, { x: 200, y: 200 }) + electronState.eventHandlers.get(FLOATING_BUTTON_EVENTS.DRAG_END)!(event) + } + expect(settings.setFloatingButtonBounds).not.toHaveBeenCalled() + expect(floatingWindowState.bounds).toEqual(before) + }) + it('loads all regular sessions without restricting the agent id', async () => { floatingPresenter = createFloatingPresenter() await floatingPresenter.initialize() @@ -327,6 +421,9 @@ describe('FloatingButtonPresenter drag layout sync', () => { projectDir: null, isPinned: false, isDraft: false, + sessionKind: 'regular', + orchestrationPolicy: 'explicit', + toolModeOverride: null, createdAt: 1, updatedAt: 10, status: 'idle', @@ -340,6 +437,9 @@ describe('FloatingButtonPresenter drag layout sync', () => { projectDir: null, isPinned: false, isDraft: false, + sessionKind: 'regular', + orchestrationPolicy: 'explicit', + toolModeOverride: null, createdAt: 2, updatedAt: 11, status: 'generating', @@ -369,6 +469,7 @@ describe('FloatingButtonPresenter drag layout sync', () => { it('reveals the collapsed widget on hover and peeks it again on mouse leave', async () => { floatingPresenter = createFloatingPresenter() await floatingPresenter.initialize() + await floatingPresenter.refreshWidgetState() expect(floatingWindowState.bounds.x).toBe( electronState.workArea.x + electronState.workArea.width - getCollapsedWidgetSize(0).width / 2 diff --git a/test/main/desktop/floatingButton/layout.test.ts b/test/main/desktop/floatingButton/layout.test.ts index 5c975ee6b5..2bc26e95d3 100644 --- a/test/main/desktop/floatingButton/layout.test.ts +++ b/test/main/desktop/floatingButton/layout.test.ts @@ -1,6 +1,6 @@ +import { buildFloatingWidgetSnapshot } from '@/session/widgetSnapshot' import { describe, expect, it } from 'vitest' import { - buildFloatingWidgetSnapshot, getPeekedCollapsedBounds, getCollapsedWidgetSize, getExpandedWidgetSize, @@ -19,6 +19,9 @@ describe('floating widget layout helpers', () => { projectDir: null, isPinned: false, isDraft: false, + sessionKind: 'regular', + orchestrationPolicy: 'explicit', + toolModeOverride: null, createdAt: 1, updatedAt: 10, status: 'idle', @@ -32,6 +35,9 @@ describe('floating widget layout helpers', () => { projectDir: null, isPinned: false, isDraft: false, + sessionKind: 'regular', + orchestrationPolicy: 'explicit', + toolModeOverride: null, createdAt: 1, updatedAt: 9, status: 'generating', @@ -45,6 +51,9 @@ describe('floating widget layout helpers', () => { projectDir: null, isPinned: false, isDraft: false, + sessionKind: 'regular', + orchestrationPolicy: 'explicit', + toolModeOverride: null, createdAt: 1, updatedAt: 99, status: 'generating', diff --git a/test/main/desktop/fontSettings.test.ts b/test/main/desktop/fontSettings.test.ts index 3aaa09da71..5d67af9df5 100644 --- a/test/main/desktop/fontSettings.test.ts +++ b/test/main/desktop/fontSettings.test.ts @@ -1,10 +1,7 @@ -import fontList from 'font-list' import { beforeEach, describe, expect, it, vi } from 'vitest' import { FontSettings } from '@/desktop/fontSettings' -vi.mock('font-list', () => ({ default: { getFonts: vi.fn() } })) - -const getFontsMock = vi.mocked(fontList.getFonts) +const getFontsMock = vi.fn<() => Promise>() const createSettings = () => { const values = new Map() @@ -19,7 +16,7 @@ describe('FontSettings', () => { it('normalizes and caches system fonts', async () => { getFontsMock.mockResolvedValue(['Inter Regular', 'Inter Bold', 'Menlo']) - const fonts = new FontSettings(createSettings() as never, vi.fn()) + const fonts = new FontSettings(createSettings() as never, vi.fn(), getFontsMock) const detected = await fonts.getSystemFonts() const cached = await fonts.getSystemFonts() @@ -31,7 +28,7 @@ describe('FontSettings', () => { it('returns an empty array when font detection fails', async () => { getFontsMock.mockRejectedValue(new Error('failed to load')) - const fonts = new FontSettings(createSettings() as never, vi.fn()) + const fonts = new FontSettings(createSettings() as never, vi.fn(), getFontsMock) await expect(fonts.getSystemFonts()).resolves.toEqual([]) }) diff --git a/test/main/desktop/imageActions.test.ts b/test/main/desktop/imageActions.test.ts new file mode 100644 index 0000000000..92357ea30e --- /dev/null +++ b/test/main/desktop/imageActions.test.ts @@ -0,0 +1,141 @@ +import { existsSync, mkdtempSync, readFileSync, rmSync, writeFileSync, mkdirSync } from 'node:fs' +import fs from 'node:fs/promises' +import { tmpdir } from 'node:os' +import { join } from 'node:path' +import { clipboard, dialog, nativeImage, net } from 'electron' +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' +import { DesktopImageActions } from '@/desktop/imageActions' + +vi.unmock('fs') +vi.mock('electron', () => ({ + clipboard: { writeImage: vi.fn() }, + dialog: { showSaveDialog: vi.fn() }, + nativeImage: { createFromBuffer: vi.fn(), createFromDataURL: vi.fn() }, + net: { fetch: vi.fn() } +})) + +describe('DesktopImageActions', () => { + let directory: string + let images: DesktopImageActions + const bytes = Buffer.from('image fixture') + const source = `data:image/png;base64,${bytes.toString('base64')}` + + beforeEach(() => { + vi.resetAllMocks() + directory = mkdtempSync(join(tmpdir(), 'deepchat-native-images-')) + images = new DesktopImageActions(directory, (options) => dialog.showSaveDialog(options)) + }) + + afterEach(() => { + images.stop() + vi.restoreAllMocks() + rmSync(directory, { force: true, recursive: true }) + }) + + it('writes the original bytes only to the path granted by the save dialog', async () => { + const target = join(directory, 'chosen.png') + vi.mocked(dialog.showSaveDialog).mockResolvedValueOnce({ canceled: true, filePath: '' }) + await expect(images.saveImage({ source })).resolves.toEqual({ canceled: true }) + expect(existsSync(target)).toBe(false) + + vi.mocked(dialog.showSaveDialog).mockResolvedValueOnce({ canceled: false, filePath: target }) + const started = Promise.withResolvers() + const release = Promise.withResolvers() + const writeFile = fs.writeFile + const write = vi.spyOn(fs, 'writeFile').mockImplementationOnce(async (...args) => { + started.resolve() + await release.promise + await writeFile(...args) + }) + const saving = images.saveImage({ source, suggestedName: 'unsafe:name' }) + try { + await started.promise + images.stop() + release.resolve() + await expect(saving).resolves.toEqual({ canceled: false, path: target }) + } finally { + release.resolve() + await saving.catch(() => {}) + write.mockRestore() + } + expect(readFileSync(target)).toEqual(bytes) + expect(vi.mocked(dialog.showSaveDialog).mock.calls[1][0]).toMatchObject({ + defaultPath: 'unsafe_name.png' + }) + }) + + it('reads cached images inside the owned image directory and rejects path escapes', async () => { + mkdirSync(join(directory, 'images')) + writeFileSync(join(directory, 'images', 'cached image.png'), bytes) + const target = join(directory, 'selected.png') + vi.mocked(dialog.showSaveDialog).mockResolvedValueOnce({ canceled: false, filePath: target }) + await images.saveImage({ source: 'imgcache://cached%20image.png' }) + expect(readFileSync(target)).toEqual(bytes) + vi.mocked(dialog.showSaveDialog).mockClear() + + for (const source of ['imgcache://../outside.png', 'imgcache://%2e%2e/outside.png']) { + await expect(images.saveImage({ source })).rejects.toThrow('Invalid cached image path') + } + expect(dialog.showSaveDialog).not.toHaveBeenCalled() + }) + + it('propagates native download errors before opening a dialog or writing the clipboard', async () => { + vi.mocked(net.fetch).mockRejectedValueOnce(new Error('network unavailable')) + await expect(images.copyImage({ source: 'https://example.test/image.png' })).rejects.toThrow( + 'network unavailable' + ) + vi.mocked(net.fetch).mockResolvedValueOnce(new Response('denied', { status: 403 })) + await expect(images.saveImage({ source: 'https://example.test/image.png' })).rejects.toThrow( + 'Failed to download image: 403' + ) + expect(dialog.showSaveDialog).not.toHaveBeenCalled() + expect(clipboard.writeImage).not.toHaveBeenCalled() + }) + + it('does not write the clipboard when native image decoding fails', async () => { + const empty = { isEmpty: () => true } as Electron.NativeImage + vi.mocked(nativeImage.createFromBuffer).mockReturnValue(empty) + vi.mocked(nativeImage.createFromDataURL).mockReturnValue(empty) + await expect(images.copyImage({ source })).rejects.toThrow( + 'Image data cannot be copied to clipboard' + ) + expect(clipboard.writeImage).not.toHaveBeenCalled() + }) + + it('aborts downloads and denies late image effects after shutdown', async () => { + const requested = Promise.withResolvers() + const reading = Promise.withResolvers() + const body = Promise.withResolvers() + const response = new Response(null, { headers: { 'content-type': 'image/png' } }) + vi.spyOn(response, 'arrayBuffer').mockImplementation(() => { + reading.resolve() + return body.promise + }) + vi.mocked(net.fetch) + .mockImplementationOnce( + (_url, options) => + new Promise((_resolve, reject) => { + requested.resolve(options?.signal) + options?.signal?.addEventListener('abort', () => reject(options.signal?.reason), { + once: true + }) + }) + ) + .mockResolvedValueOnce(response) + const saving = expect( + images.saveImage({ source: 'https://example.test/headers.png' }) + ).rejects.toThrow('Native image actions are stopping') + const copying = expect( + images.copyImage({ source: 'https://example.test/body.png' }) + ).rejects.toThrow('Native image actions are stopping') + const signal = await requested.promise + await reading.promise + images.stop() + expect(signal?.aborted).toBe(true) + body.resolve(Uint8Array.from(bytes).buffer) + await Promise.all([saving, copying]) + await expect(images.copyImage({ source })).rejects.toThrow('Native image actions are stopping') + expect(dialog.showSaveDialog).not.toHaveBeenCalled() + expect(clipboard.writeImage).not.toHaveBeenCalled() + }) +}) diff --git a/test/main/desktop/pluginSettingsWindow.test.ts b/test/main/desktop/pluginSettingsWindow.test.ts index ba9f980d36..a7e5fc68e7 100644 --- a/test/main/desktop/pluginSettingsWindow.test.ts +++ b/test/main/desktop/pluginSettingsWindow.test.ts @@ -14,13 +14,13 @@ type FakeWindow = { on: ReturnType show: ReturnType focus: ReturnType - close: ReturnType + destroy: ReturnType loadFile: ReturnType handlers: Map webContentsHandlers: Map } -function installBrowserWindowMock(): FakeWindow[] { +function installBrowserWindowMock(loadFile: () => Promise = async () => {}): FakeWindow[] { const created: FakeWindow[] = [] vi.mocked(BrowserWindow).mockImplementation(function () { const handlers = new Map() @@ -35,8 +35,8 @@ function installBrowserWindowMock(): FakeWindow[] { on: vi.fn((event: string, cb: Handler) => handlers.set(event, cb)), show: vi.fn(), focus: vi.fn(), - close: vi.fn(), - loadFile: vi.fn().mockResolvedValue(undefined), + destroy: vi.fn(), + loadFile: vi.fn(loadFile), handlers, webContentsHandlers } @@ -49,19 +49,62 @@ function installBrowserWindowMock(): FakeWindow[] { const input = { pluginId: 'p1', title: 'P1', entry: '/plugins/p1/settings.html' } describe('PluginSettingsWindow', () => { - it('keeps the reopened window record when a stale closed event fires late', async () => { + it('waits for physical closure before acknowledging close or reopening', async () => { const created = installBrowserWindowMock() const settingsWindow = new PluginSettingsWindow() await settingsWindow.open(input) - settingsWindow.close(input.pluginId) - await settingsWindow.open(input) + let settled = false + const closing = settingsWindow.close(input.pluginId).then(() => { + settled = true + }) + const reopening = settingsWindow.open(input) + await Promise.resolve() + expect(settled).toBe(false) + expect(created).toHaveLength(1) + expect(settingsWindow.getPluginIdForWebContents(created[0].webContents.id)).toBe('p1') + created[0].handlers.get('closed')?.() + await closing + await reopening + expect(settingsWindow.getPluginIdForWebContents(created[0].webContents.id)).toBeNull() created[0].handlers.get('closed')?.() - settingsWindow.close(input.pluginId) - expect(created[1].close).toHaveBeenCalled() + const closingAgain = settingsWindow.close(input.pluginId) + expect(created[1].destroy).toHaveBeenCalled() expect(settingsWindow.getPluginIdForWebContents(created[1].webContents.id)).toBe('p1') + created[1].handlers.get('closed')?.() + await closingAgain + }) + + it('drains a failed load before propagating its error', async () => { + const created = installBrowserWindowMock(async () => { + throw new Error('Invalid settings file') + }) + const settingsWindow = new PluginSettingsWindow() + const opening = settingsWindow.open(input) + const rejected = expect(opening).rejects.toThrow('Invalid settings file') + await Promise.resolve() + expect(created[0].destroy).toHaveBeenCalled() + created[0].handlers.get('closed')?.() + await rejected + }) + + it('settles cancellation when closing a window rejects its pending load', async () => { + let rejectLoad!: (error: Error) => void + const created = installBrowserWindowMock( + () => + new Promise((_resolve, reject) => { + rejectLoad = reject + }) + ) + const settingsWindow = new PluginSettingsWindow() + const opening = settingsWindow.open(input) + const closing = settingsWindow.close(input.pluginId) + rejectLoad(new Error('ERR_FAILED (-2)')) + created[0].handlers.get('closed')?.() + await opening + await closing }) it('restricts navigation to the file entry', async () => { @@ -84,4 +127,17 @@ describe('PluginSettingsWindow', () => { expect(isAllowed('file:///plugins/other/settings.html')).toBe(false) expect(isAllowed('https://example.com/')).toBe(false) }) + + it('fences reopen requests while closing all owned windows', async () => { + const created = installBrowserWindowMock() + const windows = new PluginSettingsWindow() + await windows.open(input) + const closing = windows.close(input.pluginId) + const reopening = windows.open(input) + const rejected = expect(reopening).rejects.toThrow('windows are closed') + const shutdown = windows.closeAll() + created[0].handlers.get('closed')?.() + await Promise.all([closing, shutdown, rejected]) + expect(created).toHaveLength(1) + }) }) diff --git a/test/main/desktop/remoteAuthWindows.test.ts b/test/main/desktop/remoteAuthWindows.test.ts new file mode 100644 index 0000000000..6084339742 --- /dev/null +++ b/test/main/desktop/remoteAuthWindows.test.ts @@ -0,0 +1,92 @@ +import { EventEmitter } from 'node:events' +import { BrowserWindow } from 'electron' +import { afterEach, describe, expect, it, vi } from 'vitest' +import { DesktopRemoteAuthWindows } from '@/desktop/remoteAuthWindows' + +const input = { + id: 'feishu:one', + kind: 'feishu' as const, + url: 'https://accounts.feishu.cn/open-apis/authen/v1/authorize', + brand: 'feishu' as const, + redirectUri: 'http://127.0.0.1:9911/callback' +} + +const fixture = () => { + const events = new EventEmitter() + const webContents = Object.assign(new EventEmitter(), { setWindowOpenHandler: vi.fn() }) + const window = Object.assign(events, { + webContents, + loadURL: vi.fn(async (_url: string) => {}), + show: vi.fn(), + focus: vi.fn(), + isDestroyed: vi.fn(() => false), + destroy: vi.fn(() => { + events.emit('closed') + }) + }) + Object.assign(BrowserWindow, { + getFocusedWindow: vi.fn(() => null), + getAllWindows: vi.fn(() => []) + }) + vi.mocked(BrowserWindow).mockImplementation(function () { + return window as unknown as BrowserWindow + }) + const publish = vi.fn() + return { window, publish, host: new DesktopRemoteAuthWindows(publish) } +} + +afterEach(() => vi.restoreAllMocks()) + +describe('DesktopRemoteAuthWindows', () => { + it('preserves native isolation, brand/redirect navigation and popup denial', async () => { + const { host, window } = fixture() + await expect(host.open({ ...input, url: 'https://untrusted.invalid/' })).rejects.toThrow( + 'Invalid' + ) + await host.open(input) + expect(BrowserWindow).toHaveBeenLastCalledWith( + expect.objectContaining({ + webPreferences: { nodeIntegration: false, contextIsolation: true, webSecurity: true } + }) + ) + const preventDefault = vi.fn() + window.webContents.emit('will-navigate', { preventDefault }, 'https://untrusted.invalid/') + expect(preventDefault).toHaveBeenCalledOnce() + preventDefault.mockClear() + window.webContents.emit('will-navigate', { preventDefault }, `${input.redirectUri}?code=one`) + expect(preventDefault).not.toHaveBeenCalled() + const popup = window.webContents.setWindowOpenHandler.mock.calls[0][0] + expect(popup({ url: 'https://accounts.larksuite.com/' })).toEqual({ action: 'deny' }) + expect(window.loadURL).toHaveBeenCalledTimes(1) + expect(popup({ url: 'https://open.feishu.cn/auth' })).toEqual({ action: 'deny' }) + expect(window.loadURL).toHaveBeenLastCalledWith('https://open.feishu.cn/auth') + await host.closeAll() + }) + + it('acknowledges physical close and ignores a late navigation failure from the closed window', async () => { + const { host, window, publish } = fixture() + const loading = Promise.withResolvers() + window.loadURL.mockReturnValueOnce(loading.promise) + window.destroy.mockImplementation(() => {}) + await host.open(input) + let closed = false + const closing = host.close(input.id).then(() => { + closed = true + }) + loading.reject(Object.assign(new Error('navigation cancelled'), { code: 'ERR_FAILED' })) + await Promise.resolve() + expect(closed).toBe(false) + expect(publish).not.toHaveBeenCalled() + window.emit('closed') + await closing + expect(publish.mock.calls).toEqual([[{ id: input.id, type: 'closed' }]]) + }) + + it('reports a navigation failure while the auth window is still active', async () => { + const { host, window, publish } = fixture() + window.loadURL.mockRejectedValueOnce(new Error('connection refused')) + await host.open(input) + await vi.waitFor(() => expect(publish).toHaveBeenCalledWith({ id: input.id, type: 'error' })) + await host.closeAll() + }) +}) diff --git a/test/main/desktop/sessionBinding.test.ts b/test/main/desktop/sessionBinding.test.ts index d6dedb8bee..91761d4cb2 100644 --- a/test/main/desktop/sessionBinding.test.ts +++ b/test/main/desktop/sessionBinding.test.ts @@ -1,5 +1,6 @@ import { describe, expect, it, vi } from 'vitest' import { DesktopSessionBinding } from '@/desktop/sessionBinding' +import type { SessionWithState } from '@shared/types/agent-interface' describe('DesktopSessionBinding', () => { it('owns independent renderer bindings and clears missing sessions', async () => { @@ -9,7 +10,15 @@ describe('DesktopSessionBinding', () => { ), notify: vi.fn() } - const binding = new DesktopSessionBinding(projection) + const callbacks = new Map void>() + const disposed = vi.fn() + const binding = new DesktopSessionBinding(projection, (id, listener) => { + callbacks.set(id, listener) + return () => { + callbacks.delete(id) + disposed(id) + } + }) await binding.activate(1, 'missing') await binding.activate(2, 'available') @@ -20,6 +29,7 @@ describe('DesktopSessionBinding', () => { await expect(binding.getActive(1)).resolves.toBeNull() expect(binding.getActiveId(1)).toBeNull() + expect(disposed).toHaveBeenCalledWith(1) await binding.deactivate(2) expect(binding.getActiveId(2)).toBeNull() @@ -30,5 +40,43 @@ describe('DesktopSessionBinding', () => { activeSessionId: null, webContentsId: 2 }) + callbacks.get(3)!() + expect(binding.getWebContentsIdsForSession('available')).toEqual([]) + expect(callbacks.size).toBe(0) + expect(disposed).toHaveBeenCalledTimes(3) + }) + + it('preserves a new binding when an old missing-session lookup finishes', async () => { + const missing = Promise.withResolvers() + const dispose = vi.fn() + const binding = new DesktopSessionBinding( + { getSession: () => missing.promise, notify() {} }, + () => dispose + ) + binding.bind(1, 'old') + const reading = binding.getActive(1) + binding.bind(1, 'new') + missing.resolve(null) + await expect(reading).resolves.toBeNull() + expect(binding.getActiveId(1)).toBe('new') + expect(dispose).not.toHaveBeenCalled() + binding.unbind(1) + expect(dispose).toHaveBeenCalledOnce() + }) + + it('does not activate a renderer destroyed during native registration', async () => { + const dispose = vi.fn() + const notify = vi.fn() + const binding = new DesktopSessionBinding( + { getSession: async () => null, notify }, + (_id, listener) => { + listener() + return dispose + } + ) + await binding.activate(1, 'session') + expect(binding.getActiveId(1)).toBeNull() + expect(notify).not.toHaveBeenCalled() + expect(dispose).toHaveBeenCalledOnce() }) }) diff --git a/test/main/desktop/settings.test.ts b/test/main/desktop/settings.test.ts index 4b38cd56f9..792bc441ab 100644 --- a/test/main/desktop/settings.test.ts +++ b/test/main/desktop/settings.test.ts @@ -1,38 +1,36 @@ import { beforeEach, describe, expect, it, vi } from 'vitest' const publishDeepchatEventMock = vi.hoisted(() => vi.fn()) -const getLocaleMock = vi.hoisted(() => vi.fn(() => 'en-US')) - -vi.mock('electron', () => ({ - app: { - getLocale: getLocaleMock, - getLoginItemSettings: vi.fn(() => ({ openAtLogin: false })), - setLoginItemSettings: vi.fn() - } -})) - import { DesktopSettings } from '@/desktop/settings' describe('DesktopSettings', () => { beforeEach(() => { vi.clearAllMocks() - getLocaleMock.mockReturnValue('en-US') + host.systemLocale = 'en-US' }) - const effects = { - refreshLanguage: vi.fn(), + const host = { + systemLocale: 'en-US', + readTheme: vi.fn(async () => ({ theme: 'system' as const, isDark: false })), + applyTheme: vi.fn(async () => false), + readLaunchAtLogin: vi.fn(async () => false), + setLaunchAtLogin: vi.fn(async () => false), + applyContentProtection: vi.fn(async () => undefined), + applyFloatingButtonEnabled: vi.fn(async () => undefined), + refreshLanguage: vi.fn(async () => undefined), refreshTheme: vi.fn(async () => undefined) } - it('publishes the font size change after persisting it', () => { + it('publishes the font size change after persisting it', async () => { const settings = { set: vi.fn() } const desktopSettings = new DesktopSettings( settings as never, - effects, - publishDeepchatEventMock + host, + publishDeepchatEventMock, + vi.fn() ) - desktopSettings.setFontSizeLevel(4) + await desktopSettings.setFontSizeLevel(4) expect(settings.set).toHaveBeenCalledWith('fontSizeLevel', 4) expect(publishDeepchatEventMock).toHaveBeenCalledWith('settings.changed', { @@ -42,20 +40,21 @@ describe('DesktopSettings', () => { }) }) - it('owns copy with reasoning settings', () => { + it('owns copy with reasoning settings', async () => { const settings = { get: vi.fn(() => undefined), set: vi.fn() } const desktopSettings = new DesktopSettings( settings as never, - effects, - publishDeepchatEventMock + host, + publishDeepchatEventMock, + vi.fn() ) expect(desktopSettings.getCopyWithCotEnabled()).toBe(true) - desktopSettings.setCopyWithCotEnabled(false) + await desktopSettings.setCopyWithCotEnabled(false) expect(settings.set).toHaveBeenCalledWith('copyWithCotEnabled', false) expect(publishDeepchatEventMock).toHaveBeenCalledWith('settings.changed', { @@ -70,11 +69,12 @@ describe('DesktopSettings', () => { get: vi.fn(() => 'system'), set: vi.fn() } - getLocaleMock.mockReturnValue('zh-Hant-HK') + host.systemLocale = 'zh-Hant-HK' const desktopSettings = new DesktopSettings( settings as never, - effects, - publishDeepchatEventMock + host, + publishDeepchatEventMock, + vi.fn() ) expect(desktopSettings.getLanguage()).toBe('zh-HK') @@ -84,18 +84,19 @@ describe('DesktopSettings', () => { expect(desktopSettings.getLanguage()).toBe('fr-FR') }) - it('publishes a normalized locale and direction once when changing language', () => { + it('publishes a normalized locale and direction once when changing language', async () => { const settings = { get: vi.fn(() => 'fa-IR'), set: vi.fn() } const desktopSettings = new DesktopSettings( settings as never, - effects, - publishDeepchatEventMock + host, + publishDeepchatEventMock, + vi.fn() ) - desktopSettings.setLanguage('fa') + await desktopSettings.setLanguage('fa') expect(settings.set).toHaveBeenCalledWith('language', 'fa-IR') expect(publishDeepchatEventMock).toHaveBeenCalledWith('config.language.changed', { diff --git a/test/main/desktop/window.test.ts b/test/main/desktop/window.test.ts index 3040d73f49..1b69c8da78 100644 --- a/test/main/desktop/window.test.ts +++ b/test/main/desktop/window.test.ts @@ -35,7 +35,6 @@ describe('WindowPresenter', () => { { getContentProtectionEnabled: vi.fn(() => false) } as any, - vi.fn(), vi.fn() ) @@ -80,7 +79,6 @@ describe('WindowPresenter', () => { { getContentProtectionEnabled: vi.fn(() => false) } as any, - vi.fn(), vi.fn() ) @@ -113,7 +111,6 @@ describe('WindowPresenter', () => { { getContentProtectionEnabled: vi.fn(() => false) } as any, - vi.fn(), vi.fn() ) @@ -150,7 +147,6 @@ describe('WindowPresenter', () => { { getContentProtectionEnabled: vi.fn(() => false) } as any, - vi.fn(), vi.fn() ) @@ -215,7 +211,6 @@ describe('WindowPresenter', () => { getContentProtectionEnabled: vi.fn(() => false), getCloseToQuit: vi.fn(() => false) } as any, - vi.fn(), onWindowCreated ) const tabPresenter = { @@ -310,7 +305,6 @@ describe('WindowPresenter', () => { getContentProtectionEnabled: vi.fn(() => false), getCloseToQuit: vi.fn(() => true) } as any, - vi.fn(), vi.fn() ) presenter.bindTabPresenter({ @@ -342,7 +336,6 @@ describe('WindowPresenter', () => { { getContentProtectionEnabled: vi.fn(() => false) } as any, - vi.fn(), vi.fn() ) @@ -362,7 +355,6 @@ describe('WindowPresenter', () => { { getContentProtectionEnabled: vi.fn(() => false) } as any, - vi.fn(), vi.fn() ) @@ -382,7 +374,7 @@ describe('WindowPresenter', () => { id: 'tab-1', webContents: { id: 21, isDestroyed: vi.fn(() => false), send } } - let pendingFirstLookup: ((value: unknown) => void) | null = null + let pendingFirstLookup!: (value: unknown) => void let getTabCalls = 0 const tabPresenter = { getWindowTabsData: vi.fn(async () => [{ id: 'tab-1' }]), @@ -403,7 +395,7 @@ describe('WindowPresenter', () => { const second = presenter.sendToAllWindows('test-msg', 'second') await new Promise((resolve) => setTimeout(resolve, 0)) - pendingFirstLookup?.(tab) + pendingFirstLookup(tab) await Promise.all([first, second]) // Main webContents of both broadcasts first (synchronous), then tab diff --git a/test/main/device/deviceService.test.ts b/test/main/device/deviceService.test.ts index a3ad18592c..ecbf7a0a12 100644 --- a/test/main/device/deviceService.test.ts +++ b/test/main/device/deviceService.test.ts @@ -7,6 +7,8 @@ const { appRelaunchMock, appExitMock } = vi.hoisted(() => ({ appExitMock: vi.fn() })) +const selectDialog = async () => ({ canceled: true, filePaths: [] }) + vi.mock('electron', () => ({ app: { getVersion: vi.fn(() => '0.2.3'), @@ -35,26 +37,10 @@ describe('DeviceService', () => { appExitMock.mockClear() }) - describe('getDefaultHeaders', () => { - it('should include User-Agent header with DeepChat/ prefix', () => { - const headers = DeviceService.getDefaultHeaders() - - expect(headers).toHaveProperty('User-Agent') - expect(headers['User-Agent']).toMatch(/^DeepChat\//) - }) - - it('should include HTTP-Referer and X-Title headers', () => { - const headers = DeviceService.getDefaultHeaders() - - expect(headers['HTTP-Referer']).toBe('https://deepchatai.cn') - expect(headers['X-Title']).toBe('DeepChat') - }) - }) - describe('restartAppWithDelay', () => { it('relaunches the process after data reset', async () => { vi.useFakeTimers() - const presenter = new DeviceService() + const presenter = new DeviceService(selectDialog) const restartPromise = ( presenter as unknown as { restartAppWithDelay: () => Promise } @@ -71,7 +57,7 @@ describe('DeviceService', () => { it('only removes data after the App owner has stopped runtime resources', async () => { vi.useFakeTimers() vi.spyOn(fs, 'existsSync').mockReturnValue(false) - const presenter = new DeviceService() + const presenter = new DeviceService(selectDialog) const resetPromise = presenter.resetDataByType('all') await vi.advanceTimersByTimeAsync(1000) @@ -88,7 +74,7 @@ describe('DeviceService', () => { appRelaunchMock.mockImplementationOnce(() => { throw failure }) - const presenter = new DeviceService() + const presenter = new DeviceService(selectDialog) const resetPromise = presenter.resetDataByType('all') const rejection = expect(resetPromise).rejects.toBe(failure) diff --git a/test/main/file/fileService.readFile.test.ts b/test/main/file/fileService.readFile.test.ts index c12436a475..c56b246ad7 100644 --- a/test/main/file/fileService.readFile.test.ts +++ b/test/main/file/fileService.readFile.test.ts @@ -3,12 +3,6 @@ import fs from 'fs/promises' import path from 'path' import { FileService } from '../../../src/main/file' -vi.mock('electron', () => ({ - app: { - getPath: vi.fn((name: string) => (name === 'userData' ? '/mock/user/data' : '/mock')) - } -})) - vi.mock('fs/promises', () => ({ default: { mkdir: vi.fn().mockResolvedValue(undefined), @@ -34,7 +28,8 @@ describe('FileService.readFile', () => { beforeEach(() => { vi.clearAllMocks() - vi.mocked(fs.realpath).mockImplementation(async (targetPath: string) => { + vi.mocked(fs.realpath).mockImplementation(async (inputPath) => { + const targetPath = inputPath.toString() if (targetPath === '/mock/user/data') { return '/mock/user/data' } @@ -50,7 +45,7 @@ describe('FileService.readFile', () => { it('reads relative files from the user data directory', async () => { vi.mocked(fs.readFile).mockResolvedValue('hello world' as never) - const presenter = new FileService(mockSettings) + const presenter = new FileService(mockSettings, '/mock/user/data') const content = await presenter.readFile('notes/today.md') @@ -62,7 +57,7 @@ describe('FileService.readFile', () => { }) it('rejects absolute paths', async () => { - const presenter = new FileService(mockSettings) + const presenter = new FileService(mockSettings, '/mock/user/data') await expect(presenter.readFile('/etc/passwd')).rejects.toThrow( 'Absolute paths are not allowed' @@ -71,7 +66,7 @@ describe('FileService.readFile', () => { }) it('rejects paths that escape the user data directory', async () => { - const presenter = new FileService(mockSettings) + const presenter = new FileService(mockSettings, '/mock/user/data') await expect(presenter.readFile('../outside.txt')).rejects.toThrow( 'File path escapes user data directory' diff --git a/test/main/file/fileService.test.ts b/test/main/file/fileService.test.ts index 54d822dc2a..17c9028608 100644 --- a/test/main/file/fileService.test.ts +++ b/test/main/file/fileService.test.ts @@ -5,12 +5,6 @@ import { FileValidationResult, IFileValidationService } from '../../../src/main/ // Mock all external dependencies const mockSettings = { get: vi.fn() } -vi.mock('electron', () => ({ - app: { - getPath: vi.fn().mockReturnValue('/mock/user/data') - } -})) - vi.mock('fs/promises', () => ({ default: { mkdir: vi.fn().mockResolvedValue(undefined), @@ -25,7 +19,8 @@ vi.mock('path', () => ({ default: { join: vi.fn((...args) => args.join('/')), extname: vi.fn(), - dirname: vi.fn() + dirname: vi.fn(), + isAbsolute: vi.fn(() => true) } })) @@ -58,7 +53,7 @@ describe('FileService Integration with FileValidationService', () => { } // Create FileService with mocked service - fileService = new FileService(mockSettings, mockFileValidationService) + fileService = new FileService(mockSettings, '/mock/user/data', mockFileValidationService) }) describe('validateFileForKnowledgeBase', () => { diff --git a/test/main/harness/admissionRuntime.test.ts b/test/main/harness/admissionRuntime.test.ts new file mode 100644 index 0000000000..5dcd01beaf --- /dev/null +++ b/test/main/harness/admissionRuntime.test.ts @@ -0,0 +1,235 @@ +import { spawn } from 'node:child_process' +import { tmpdir } from 'node:os' +import { createRequire } from 'node:module' +import path from 'node:path' +import { afterEach, describe, expect, it, vi } from 'vitest' +import { RutisAgentInvocationAdmission } from '@/agent/invocationAdmission' +import type { AgentInvocationAdmissionObservation } from '@/agent/invocationAdmission' +import { HarnessRuntime } from '@/harness/runtime' + +const { cpSync, mkdtempSync, rmSync } = await vi.importActual('node:fs') +const require = createRequire(import.meta.url) +const active = new Map() +const directories = new Set() +const create = async ( + capacity = 1, + maxPending = 4, + observations?: AgentInvocationAdmissionObservation[], + directory = path.resolve('out/harness') +) => { + const runtime = await HarnessRuntime.start({ + directory, + electronExecutable: require('electron'), + capacity, + maxPending, + observe: (value) => observations?.push(value) + }) + const admission = RutisAgentInvocationAdmission.create({ + runtime, + capacity, + maxPending, + diagnostics: { observe: (value) => observations?.push(value) } + }) + active.set(admission, runtime) + return admission +} + +afterEach(async () => { + for (const [admission, runtime] of active) { + await admission.close().catch(() => undefined) + await admission.flushObservations().catch(() => undefined) + await runtime.stop() + } + active.clear() + for (const directory of directories) rmSync(directory, { recursive: true, force: true }) + directories.clear() +}) + +describe('application Rutis admission runtime', () => { + it('preserves owner fairness, queue cancellation and permit accounting through the real host', async () => { + const directory = mkdtempSync(path.join(tmpdir(), 'deepchat-harness-closure-')) + directories.add(directory) + cpSync(path.resolve('out/harness'), directory, { recursive: true }) + const admission = await create(1, 4, undefined, directory) + await expect( + admission.acquire({ ownerId: 'pre-aborted', signal: AbortSignal.abort() }) + ).rejects.toMatchObject({ name: 'AgentInvocationAdmissionAbortedError' }) + const first = await admission.acquire({ ownerId: 'a' }) + const cancelled = new AbortController() + const rejection = expect( + admission.acquire({ ownerId: 'cancelled', signal: cancelled.signal }) + ).rejects.toMatchObject({ name: 'AgentInvocationAdmissionAbortedError' }) + const order: string[] = [] + const a = admission.run({ ownerId: 'a' }, async () => { + order.push('a') + }) + const b = admission.run({ ownerId: 'b' }, async () => { + order.push('b') + }) + await expect.poll(async () => (await admission.snapshot()).pending).toBe(3) + cancelled.abort() + await rejection + first.release() + first.release() + await Promise.all([a, b]) + expect(order).toEqual(['a', 'b']) + expect(await admission.snapshot()).toMatchObject({ + active: 0, + pending: 0, + granted: 3, + rejected: 2 + }) + }) + + it('preserves owner headroom and lease suspend/resume ordering with correlated diagnostics', async () => { + const observations: AgentInvocationAdmissionObservation[] = [] + const admission = await create(2, 4, observations) + const lease = admission.createLease({ + ownerId: 'a', + maxActiveForOwner: 1, + correlation: { + kind: 'live_delegation', + parentSessionId: 'parent', + delegationId: 'delegation', + turnId: 'turn' + } + }) + await lease.resume() + const next = admission.acquire({ ownerId: 'a', maxActiveForOwner: 1 }) + const other = await admission.acquire({ ownerId: 'b' }) + expect(await admission.snapshot()).toMatchObject({ active: 2, pending: 1 }) + lease.suspend() + expect(lease.state).toBe('suspended') + const permit = await next + const resume = lease.resume() + await expect.poll(async () => (await admission.snapshot()).pending).toBe(1) + permit.release() + await resume + expect(lease.state).toBe('active') + lease.suspend() + const cancelledResume = lease.resume() + lease.release() + await expect(cancelledResume).rejects.toMatchObject({ + name: 'AgentInvocationAdmissionAbortedError' + }) + other.release() + await admission.flushObservations() + expect(await admission.snapshot()).toMatchObject({ active: 0, pending: 0 }) + expect(observations).toContainEqual( + expect.objectContaining({ type: 'released', reason: 'lease_suspended', turnId: 'turn' }) + ) + const identity = admission.identity! + expect(identity.hostPid).not.toBe(process.pid) + expect(identity).toMatchObject({ protocol: 2, owner: 'rust' }) + await admission.close() + expect(observations.at(-1)).toMatchObject({ type: 'closed', active: 0, pending: 0 }) + await active.get(admission)!.stop() + expect(() => process.kill(identity.hostPid, 0)).toThrow() + }) + + it('isolates asynchronous diagnostic failures from admission outcomes', async () => { + const runtime = await HarnessRuntime.start({ + directory: path.resolve('out/harness'), + electronExecutable: require('electron'), + capacity: 4, + maxPending: 128, + observe: async () => { + throw new Error('Observer unavailable') + } + }) + const admission = RutisAgentInvocationAdmission.create({ + runtime, + capacity: 4, + maxPending: 128, + diagnostics: { + observe: async () => { + throw new Error('Observer unavailable') + } + } + }) + active.set(admission, runtime) + const permit = await admission.acquire({ + ownerId: 'diagnostics', + correlation: { + kind: 'live_delegation', + parentSessionId: 'parent', + delegationId: 'delegation', + turnId: 'turn' + } + }) + permit.release() + await admission.flushObservations() + expect(await admission.snapshot()).toMatchObject({ active: 0, pending: 0 }) + }) + + it.each([ + { name: 'oversized admission', header: 64 * 1024 + 1, body: Buffer.alloc(0) }, + { name: 'reserved admission bit', header: 0x40000000 + 1, body: Buffer.alloc(0) }, + { name: 'invalid binary metadata', header: 0xc0000000 + 4, body: Buffer.alloc(4) }, + { name: 'oversized memory', header: 0x80000000 + 256 * 1024 + 1, body: Buffer.alloc(0) } + ])('rejects $name framing and tears down the owned runtime', async ({ header: value, body }) => { + const directory = path.resolve('out/harness') + const child = spawn( + path.join( + directory, + `deepchat-harness-${process.platform}-${process.arch}${process.platform === 'win32' ? '.exe' : ''}` + ), + [ + require('electron'), + path.join(directory, 'node_modules', '@arcships', 'rutis-runtime'), + path.join(directory, 'core.mjs'), + '1', + '4' + ], + { stdio: ['pipe', 'pipe', 'pipe'], windowsHide: true } + ) + child.stderr.resume() + const exited = new Promise((resolve, reject) => { + child.once('close', resolve) + child.once('error', reject) + }) + let forced = false + const timer = setTimeout(() => { + forced = true + child.kill('SIGKILL') + }, 5000) + try { + const ready = await new Promise<{ hostPid: number }>((resolve, reject) => { + let bytes = Buffer.alloc(0) + child.once('error', reject) + child.stdout.on('data', (chunk: Buffer) => { + bytes = Buffer.concat([bytes, chunk]) + if (bytes.length >= 4 && bytes.length >= 4 + bytes.readUInt32BE(0)) { + resolve(JSON.parse(bytes.subarray(4, 4 + bytes.readUInt32BE(0)).toString('utf8'))) + } + }) + child.once('close', () => reject(new Error('Host exited before ready'))) + }) + const header = Buffer.alloc(4) + header.writeUInt32BE(value) + child.stdin.write(Buffer.concat([header, body])) + expect(await exited).not.toBe(0) + expect(forced).toBe(false) + expect(() => process.kill(ready.hostPid, 0)).toThrow() + } finally { + clearTimeout(timer) + child.stdin.destroy() + if (child.exitCode === null) child.kill('SIGKILL') + await exited + } + }) + + it('fails closed when its runtime dies and never starts a replacement owner', async () => { + const admission = await create() + await admission.acquire({ ownerId: 'active' }) + const queued = expect(admission.acquire({ ownerId: 'queued' })).rejects.toThrow() + await expect.poll(async () => (await admission.snapshot()).pending).toBe(1) + const identity = admission.identity! + process.kill(identity.hostPid, 'SIGKILL') + await queued + await expect(admission.acquire({ ownerId: 'late' })).rejects.toThrow(/Rutis host/) + expect(admission.identity).toEqual(identity) + await active.get(admission)!.stop() + expect(() => process.kill(identity.hostPid, 0)).toThrow() + }) +}) diff --git a/test/main/harness/coreAdmissionRuntime.test.ts b/test/main/harness/coreAdmissionRuntime.test.ts new file mode 100644 index 0000000000..5f15166ef5 --- /dev/null +++ b/test/main/harness/coreAdmissionRuntime.test.ts @@ -0,0 +1,141 @@ +import type { AgentInvocationAdmissionObservation } from '@shared/harness/admission' +import { execFileSync } from 'node:child_process' +import { mkdtempSync, cpSync, rmSync } from 'node:fs' +import { tmpdir } from 'node:os' +import path from 'node:path' +import { randomUUID } from 'node:crypto' +import { afterEach, expect, it, vi } from 'vitest' +import { HarnessRuntime } from '@/harness/runtime' +import { CoreRuntimeConnection } from '@/harness/core' + +vi.unmock('fs') +vi.unmock('node:fs') +vi.unmock('path') +vi.unmock('node:path') +vi.unmock('child_process') +vi.unmock('node:child_process') + +const cleanups: Array<() => unknown | Promise> = [] +afterEach(async () => { + for (const cleanup of cleanups.reverse()) await cleanup() + cleanups.length = 0 +}) + +async function fixture() { + const directory = mkdtempSync(path.join(tmpdir(), 'deepchat-core-admission-')) + cleanups.push(() => rmSync(directory, { recursive: true, force: true })) + const harness = path.join(directory, 'harness') + cpSync(path.resolve('out/harness'), harness, { recursive: true }) + const { build } = await vi.importActual('vite') + await build({ + configFile: false, + publicDir: false, + logLevel: 'silent', + resolve: { + conditions: ['node'], + mainFields: ['module', 'main'], + alias: { '@': path.resolve('src/main'), '@shared': path.resolve('src/shared') } + }, + build: { + target: 'node24', + outDir: harness, + emptyOutDir: false, + copyPublicDir: false, + lib: { + entry: path.resolve('test/fixtures/harness/core-admission.ts'), + formats: ['es'], + fileName: () => 'core.mjs' + }, + rollupOptions: { external: [/^node:/] } + } + }) + const observations: AgentInvocationAdmissionObservation[] = [] + const runtime = await HarnessRuntime.start({ + directory: harness, + electronExecutable: process.execPath, + capacity: 1, + maxPending: 4, + observe(value) { + observations.push(value) + } + }) + cleanups.push(() => runtime.stop()) + const entered = Promise.withResolvers() + const release = Promise.withResolvers() + const connection = await CoreRuntimeConnection.connect({ + runtime, + temporaryDirectory: directory, + native: async (method, args) => { + expect(method).toBe('profile.logging') + expect(args).toEqual([true]) + entered.resolve() + await release.promise + } + }) + cleanups.push(() => connection.closeAfterHostExit()) + cleanups.push(() => release.resolve()) + const identity = await connection.status() + const parent = Number( + (process.platform === 'win32' + ? execFileSync( + 'powershell.exe', + [ + '-NoProfile', + '-NonInteractive', + '-Command', + `(Get-CimInstance Win32_Process -Filter 'ProcessId = ${identity.pid}').ParentProcessId` + ], + { encoding: 'utf8' } + ) + : execFileSync('ps', ['-o', 'ppid=', '-p', String(identity.pid)], { encoding: 'utf8' }) + ).trim() + ) + expect(parent).toBe(runtime.identity?.hostPid) + return { runtime, connection, entered, release, pid: identity.pid, observations } +} + +it('shares one quota with Main while preserving Core cancellation and command ordering', async () => { + const f = await fixture() + const main = randomUUID() + await f.runtime.request('acquire', [main, { ownerId: 'main', maxActiveForOwner: 1 }, true]) + const working = f.connection.configure({ operation: 'read', input: {} }) + await expect + .poll(async () => await f.runtime.request('snapshot', [])) + .toMatchObject({ + admission: { active: 1, pending: 1, rejected: 1 } + }) + await f.runtime.request('release', [main, 'permit_released', true]) + await f.entered.promise + await expect(f.runtime.request('snapshot', [])).resolves.toMatchObject({ + admission: { active: 1, pending: 0 } + }) + f.release.resolve() + await expect(working).resolves.toMatchObject({ active: 0, pending: 0, closed: false }) + await expect(f.connection.stop()).resolves.toMatchObject({ phase: 'stopped' }) + await expect(f.runtime.request('snapshot', [])).resolves.toMatchObject({ + admission: { closed: true } + }) + expect(f.observations.some((value) => value.type === 'closed')).toBe(true) +}) + +it('fences the shared quota after physical Core loss with a permit and Native effect pending', async () => { + const f = await fixture() + const working = f.connection.configure({ operation: 'read', input: {} }).catch((error) => error) + await f.entered.promise + const queued = f.runtime + .request('acquire', [randomUUID(), { ownerId: 'waiting-main' }, true]) + .catch((error) => error) + await expect + .poll(async () => await f.runtime.request('snapshot', [])) + .toMatchObject({ + admission: { active: 1, pending: 1 } + }) + process.kill(f.pid, 'SIGKILL') + await expect(working).resolves.toMatchObject({ name: 'CoreOperationOutcomeUnknownError' }) + await expect(queued).resolves.toMatchObject({ name: 'AgentInvocationAdmissionClosedError' }) + await expect(f.runtime.request('snapshot', [])).resolves.toMatchObject({ + admission: { closed: true, pending: 0 } + }) + expect(f.observations.some((value) => value.type === 'closed')).toBe(true) + f.release.resolve() +}) diff --git a/test/main/harness/coreBrowser.test.ts b/test/main/harness/coreBrowser.test.ts new file mode 100644 index 0000000000..bd233efac8 --- /dev/null +++ b/test/main/harness/coreBrowser.test.ts @@ -0,0 +1,205 @@ +import { expect, it, vi } from 'vitest' +import { mkdtempSync, rmSync } from 'node:fs' +import { tmpdir } from 'node:os' +import path from 'node:path' +import { randomUUID } from 'node:crypto' +import { CoreNativeBrowser } from '../../../src/harness/core/browser' +import { CorePayloadStore } from '../../../src/harness/core/payload' +import { CoreBrowserNative } from '@/harness/browser' +import { CoreRuntimeConnection } from '@/harness/core' +import type { HarnessRuntime } from '@/harness/runtime' +import type { CoreStatus } from '@shared/harness/core' +import type { CoreBrowserCommand } from '@shared/harness/browser' +import type { AgentBrowserToolPort } from '@/tool/runtimePorts' +import { YoBrowserUnavailableError, buildYoBrowserUnavailablePayload } from '@/tool/browser/errors' + +vi.unmock('fs') +vi.unmock('node:fs') +vi.unmock('path') +vi.unmock('node:path') + +it('commits normalized Browser dispatch in Core and preserves recoverable business errors', async () => { + const order: string[] = [] + const failures = vi.fn() + const nativeBrowser: AgentBrowserToolPort = { + getToolDefinitions: () => [], + callTool: async (tool, _args, session, _run, authorize, signal) => { + signal?.throwIfAborted() + if (tool === 'get_browser_status') + throw new YoBrowserUnavailableError(buildYoBrowserUnavailablePayload(session!, '', null)) + await authorize?.({ method: 'Page.reload', params: { ignoreCache: true } }) + signal?.throwIfAborted() + order.push('dispatch') + return 'completed' + } + } + const retired = vi.fn(async () => {}) + const native = new CoreBrowserNative( + nativeBrowser, + async (event) => await core.accept(event), + retired, + failures + ) + const core = new CoreNativeBrowser(async (command) => await native.invoke([command]), failures) + const permission = Promise.withResolvers() + const entered = Promise.withResolvers() + const approved = core.callTool('cdp_send', { params: '{}' }, 'session', 'run', async (args) => { + expect(args).toEqual({ method: 'Page.reload', params: { ignoreCache: true } }) + order.push('authorize') + entered.resolve() + await permission.promise + }) + await entered.promise + expect(order).toEqual(['authorize']) + permission.resolve() + expect(await approved).toBe('completed') + expect(order).toEqual(['authorize', 'dispatch']) + const denied = new Error('journal unavailable') + await expect( + core.callTool('load_url', {}, 'session', 'run', () => { + throw denied + }) + ).rejects.toBe(denied) + expect(order).toEqual(['authorize', 'dispatch']) + await expect(core.callTool('get_browser_status', {}, 'session')).rejects.toMatchObject({ + name: 'YoBrowserUnavailableError', + payload: buildYoBrowserUnavailablePayload('session', '', null) + }) + expect(await core.callTool('cdp_send', {}, 'session')).toBe('completed') + expect(failures).not.toHaveBeenCalled() + await core.close() + expect(retired).toHaveBeenCalledOnce() +}) + +it('cancels late admission without dispatch and waits for actual Native retirement', async () => { + const admission = Promise.withResolvers() + const admitted = Promise.withResolvers() + const physicalClosure = Promise.withResolvers() + const retired = vi.fn(async () => await physicalClosure.promise) + const dispatch = vi.fn() + const failures = vi.fn() + const native = new CoreBrowserNative( + { + getToolDefinitions: () => [], + callTool: async (_tool, _args, _session, _run, authorize, signal) => { + await authorize?.({ url: 'https://example.com' }) + signal?.throwIfAborted() + dispatch() + return 'completed' + } + }, + async (event) => { + if (event.type === 'browser.authorize') await admission.promise + return await core.accept(event) + }, + retired, + failures + ) + const invoke = async (command: CoreBrowserCommand) => { + const result = await native.invoke([command]) + if (command.operation === 'open') { + admitted.resolve() + await admission.promise + } + return result + } + const core = new CoreNativeBrowser(invoke, failures) + const controller = new AbortController() + const authorize = vi.fn() + const result = core.callTool('load_url', {}, 'session', 'run', authorize, controller.signal) + const canceled = expect(result).rejects.toThrow('run canceled') + await admitted.promise + controller.abort(new Error('run canceled')) + admission.resolve() + await canceled + expect(authorize).not.toHaveBeenCalled() + expect(dispatch).not.toHaveBeenCalled() + let closed = false + const closing = core.close().then(() => { + closed = true + }) + await vi.waitFor(() => expect(retired).toHaveBeenCalledOnce()) + expect(closed).toBe(false) + physicalClosure.resolve() + await closing + expect(failures).not.toHaveBeenCalled() +}) + +it('serializes encrypted helper and Browser events without fencing a denied authorization', async () => { + const directory = mkdtempSync(path.join(tmpdir(), 'rutis-native-events-')) + const permission = Promise.withResolvers() + const entered = Promise.withResolvers() + const received: unknown[] = [] + let peer!: CorePayloadStore + const runtime: Pick = { + stop: async () => {}, + connectCore(config, listener) { + peer = new CorePayloadStore(config, 'core') + const status: CoreStatus = { + generation: config.generation, + pid: process.pid, + node: process.versions.node, + modules: process.versions.modules, + phase: 'idle', + businessReady: false + } + listener.message({ type: 'coreReady', runtime: status }) + return (request) => { + if (request.type !== 'coreCall') throw new Error('Unexpected Core request') + void (async () => { + const event = await peer.decode(request.args) + let result: unknown = status + if (request.method === 'nativeEvents') { + received.push(event) + if (received.length === 1) { + entered.resolve() + await permission.promise + result = { ok: false, error: { name: 'Error', message: 'dispatch denied' } } + } else result = undefined + } + listener.message({ + type: 'coreReply', + id: request.id, + value: await peer.encode(result) + }) + })().catch(listener.failed) + } + } + } + const connection = await CoreRuntimeConnection.connect({ + runtime, + temporaryDirectory: directory, + native: async () => { + throw new Error('Unexpected Native effect') + } + }) + try { + const authorization = connection.deliverBrowserEvent({ + type: 'browser.authorize', + id: randomUUID(), + args: { source: Buffer.alloc(32 * 1024, 3) } + }) + await entered.promise + const bytes = Buffer.alloc(32 * 1024, 7) + const helper = connection.deliverNativeEvents([ + { + id: randomUUID(), + sequence: 1, + pid: null, + event: { type: 'message', message: bytes } + } + ]) + bytes.fill(0) + expect(received).toHaveLength(1) + permission.resolve() + await expect(authorization).resolves.toMatchObject({ ok: false }) + await helper + expect(received[1]).toMatchObject([{ event: { message: Buffer.alloc(32 * 1024, 7) } }]) + await expect(connection.status()).resolves.toMatchObject({ phase: 'idle' }) + } finally { + permission.resolve() + await connection.closeAfterHostExit() + await peer.close() + rmSync(directory, { recursive: true, force: true }) + } +}) diff --git a/test/main/harness/coreControl.test.ts b/test/main/harness/coreControl.test.ts new file mode 100644 index 0000000000..9aec243003 --- /dev/null +++ b/test/main/harness/coreControl.test.ts @@ -0,0 +1,83 @@ +import { afterEach, expect, it, vi } from 'vitest' +import { CoreHarnessControl } from '../../../src/harness/core/control' +import type { HarnessControlCommand } from '@shared/harness/control' + +afterEach(() => vi.useRealTimers()) + +function receipt(revision: number, result: unknown = null) { + return { protocol: 1, revision, outcome: 'accepted', scope: null, result, fences: [], grants: [] } +} + +it('applies local fences and acknowledges a transition before submitting its successor', async () => { + const prepared = Promise.withResolvers() + const committed = Promise.withResolvers() + const calls: Array<{ sequence: number; command: HarnessControlCommand }> = [] + const control = new CoreHarnessControl( + { + async control(_generation, sequence, command) { + calls.push({ sequence, command }) + return sequence === 1 + ? await prepared.promise + : sequence === 2 + ? await committed.promise + : receipt(sequence) + } + }, + 'generation', + () => { + throw new Error('Unexpected control failure') + } + ) + const fences = vi.fn(() => true) + const first = control.transition( + { method: 'prepareOperation', scopeId: 'scope', operationId: 'operation' }, + fences + ) + const successor = control.send({ method: 'clearReady', scopeId: 'scope' }) + await vi.waitFor(() => expect(calls).toHaveLength(1)) + prepared.resolve(receipt(1, { transitionId: 'operation' })) + await vi.waitFor(() => expect(calls).toHaveLength(2)) + expect(fences).toHaveBeenCalledOnce() + expect(calls[1]).toEqual({ + sequence: 2, + command: { + method: 'commit', + scopeId: 'scope', + transitionId: 'operation', + aborted: true + } + }) + committed.resolve(receipt(2)) + await first + await successor + expect(calls.map(({ sequence }) => sequence)).toEqual([1, 2, 3]) + expect(fences).toHaveBeenCalledTimes(2) + control.stop() +}) + +it('fences unknown outcomes without replaying queued work or applying a late receipt', async () => { + vi.useFakeTimers() + const late = Promise.withResolvers() + const service = { control: vi.fn(() => late.promise) } + const failOwner = vi.fn() + const closed = vi.fn() + const apply = vi.fn() + const control = new CoreHarnessControl(service, 'generation', failOwner) + control.onClose(closed) + const first = control.send({ method: 'open', sessionId: 'session', scopeId: 'scope' }, apply) + const successor = control.send({ method: 'clearReady', scopeId: 'scope' }, apply) + const rejected = Promise.all([ + expect(first).rejects.toThrow('timed out'), + expect(successor).rejects.toThrow('timed out') + ]) + await vi.advanceTimersByTimeAsync(10_000) + await rejected + late.resolve(receipt(1)) + await Promise.resolve() + expect(control.closed).toBe(true) + expect(closed).toHaveBeenCalledOnce() + expect(failOwner).toHaveBeenCalledOnce() + expect(service.control).toHaveBeenCalledOnce() + expect(apply).not.toHaveBeenCalled() + await expect(control.send({ method: 'release', scopeId: 'scope' })).rejects.toThrow('timed out') +}) diff --git a/test/main/harness/coreControlRuntime.test.ts b/test/main/harness/coreControlRuntime.test.ts new file mode 100644 index 0000000000..de160bc801 --- /dev/null +++ b/test/main/harness/coreControlRuntime.test.ts @@ -0,0 +1,68 @@ +import { mkdtempSync, cpSync, rmSync } from 'node:fs' +import { tmpdir } from 'node:os' +import { builtinModules } from 'node:module' +import path from 'node:path' +import { expect, it, vi } from 'vitest' +import { HarnessRuntime } from '@/harness/runtime' +import { CoreRuntimeConnection } from '@/harness/core' + +vi.unmock('fs') +vi.unmock('node:fs') +vi.unmock('path') +vi.unmock('node:path') +vi.unmock('child_process') +vi.unmock('node:child_process') + +it('binds Rust control grants to Core generation and local effect handles', async () => { + const directory = mkdtempSync(path.join(tmpdir(), 'deepchat-core-control-')) + let runtime: HarnessRuntime | undefined + let connection: CoreRuntimeConnection | undefined + try { + const harness = path.join(directory, 'harness') + cpSync(path.resolve('out/harness'), harness, { recursive: true }) + const { build } = await vi.importActual('vite') + await build({ + configFile: false, + publicDir: false, + logLevel: 'silent', + resolve: { + conditions: ['node'], + mainFields: ['module', 'main'], + alias: { '@': path.resolve('src/main'), '@shared': path.resolve('src/shared') } + }, + build: { + target: 'node24', + outDir: harness, + emptyOutDir: false, + copyPublicDir: false, + lib: { + entry: path.resolve('test/fixtures/harness/core-control.ts'), + formats: ['es'], + fileName: () => 'core.mjs' + }, + rollupOptions: { external: [...builtinModules, /^node:/] } + } + }) + runtime = await HarnessRuntime.start({ + directory: harness, + electronExecutable: process.execPath, + capacity: 1, + maxPending: 4, + observe() {} + }) + connection = await CoreRuntimeConnection.connect({ + runtime, + temporaryDirectory: directory, + native: async () => { + throw new Error('Unexpected Native operation') + } + }) + await expect(connection.configure({ operation: 'read', input: {} })).resolves.toEqual({ + passed: true + }) + } finally { + await runtime?.stop() + await connection?.closeAfterHostExit() + rmSync(directory, { recursive: true, force: true }) + } +}) diff --git a/test/main/harness/coreDesktop.test.ts b/test/main/harness/coreDesktop.test.ts new file mode 100644 index 0000000000..bd5ebaeb04 --- /dev/null +++ b/test/main/harness/coreDesktop.test.ts @@ -0,0 +1,264 @@ +import { expect, it, vi } from 'vitest' +import { CoreNativeDesktop } from '../../../src/harness/core/desktop' +import type { CoreKernelNative } from '../../../src/harness/core/kernel' +import { CoreDesktopNative } from '@/harness/desktop' +import { createDeepchatEventEnvelope, type DeepchatEventEnvelope } from '@shared/contracts/events' +import { CORE_NATIVE_QUEUED, CORE_PRESENTATION_QUEUED } from '@shared/harness/core' + +vi.unmock('path') +vi.unmock('node:path') + +it('delivers the latest projections without displacing business events', async () => { + const entered = Promise.withResolvers() + const released = Promise.withResolvers() + const businessDelivered = Promise.withResolvers() + const delivered: DeepchatEventEnvelope[] = [] + const failed = vi.fn() + const native = new CoreDesktopNative( + { + mcpReadinessChanged() {}, + renderer: { + broadcast: async (event) => { + delivered.push(event) + if (delivered.length === 1) { + entered.resolve() + await released.promise + } + if (event.name === 'appRuntime.accessibilityChanged') businessDelivered.resolve() + } + } + } as unknown as CoreKernelNative, + async () => {}, + async () => {}, + failed + ) + const core = new CoreNativeDesktop( + (command) => native.invoke([command]), + () => {}, + failed + ) + const ports = await core.createPorts({ temporaryDirectory: '/tmp', profileDirectory: '/profile' }) + const snapshot = (run: string) => + createDeepchatEventEnvelope('startup.workload.changed', { + startupRunId: run, + target: 'settings', + tasks: [] + }) + const delegation = (parent: string, id: string, revision: number) => + createDeepchatEventEnvelope('orchestration.liveDelegation.changed', { + schemaVersion: 1, + parentSessionId: parent, + delegation: { + schemaVersion: 1, + id, + parentSessionId: parent, + childSessionId: null, + slotId: 'explorer', + targetAgentId: 'deepchat', + title: 'Owned delegation', + status: 'running', + lastTurnSeq: 1, + createdAt: 0, + updatedAt: revision, + revision, + summaryPreview: null, + errorPreview: null + } + }) + try { + ports.renderer.broadcast(snapshot('first')) + await entered.promise + for (let i = 0; i < CORE_NATIVE_QUEUED * 2; i++) { + ports.renderer.broadcast(snapshot(String(i))) + ports.renderer.broadcast(delegation('parent:a', 'child', i)) + ports.renderer.broadcast(delegation('parent', 'a:child', i)) + } + ports.renderer.broadcast(delegation('parent:a', 'child', 0)) + ports.renderer.broadcast( + createDeepchatEventEnvelope('appRuntime.accessibilityChanged', { enabled: true }) + ) + released.resolve() + await businessDelivered.promise + await ports.mcpReadinessChanged(false) + expect(delivered.filter((event) => event.name === 'startup.workload.changed')).toEqual([ + snapshot('first'), + snapshot(String(CORE_NATIVE_QUEUED * 2 - 1)) + ]) + expect( + delivered.filter((event) => event.name === 'appRuntime.accessibilityChanged') + ).toHaveLength(1) + expect( + delivered.filter((event) => event.name === 'orchestration.liveDelegation.changed') + ).toEqual([ + delegation('parent:a', 'child', CORE_NATIVE_QUEUED * 2 - 1), + delegation('parent', 'a:child', CORE_NATIVE_QUEUED * 2 - 1) + ]) + expect(failed).not.toHaveBeenCalled() + for (let i = 0; i < CORE_PRESENTATION_QUEUED; i++) + ports.renderer.broadcast( + createDeepchatEventEnvelope('appRuntime.accessibilityChanged', { enabled: Boolean(i % 2) }) + ) + expect(() => ports.renderer.broadcast(snapshot('over-capacity'))).toThrow( + 'Core presentation queue exceeded' + ) + expect(failed).toHaveBeenCalledOnce() + } finally { + released.resolve() + await core.close() + } +}) + +it('cancels reordered Native calls and releases completed cancellation identities', async () => { + const failed = vi.fn() + const read = vi.fn(async () => ({ width: 10, height: 20 })) + const native = new CoreDesktopNative( + { agentDesktop: { readImageSize: read } } as unknown as CoreKernelNative, + async () => {}, + async () => {}, + failed + ) + let held: + | { + position: 'admission' | 'reply' + entered: ReturnType> + release: ReturnType> + } + | undefined + const core = new CoreNativeDesktop( + async (command) => { + if (command.operation !== 'call' || !held) return await native.invoke([command]) + const gate = held + if (gate.position === 'admission') { + gate.entered.resolve() + await gate.release.promise + } + const result = await native.invoke([command]) + if (gate.position === 'reply') { + gate.entered.resolve() + await gate.release.promise + } + return result + }, + () => {}, + failed + ) + const ports = await core.createPorts({ temporaryDirectory: '/tmp', profileDirectory: '/profile' }) + try { + for (let i = 0; i < 84; i++) { + held = { + position: i === 0 ? 'admission' : 'reply', + entered: Promise.withResolvers(), + release: Promise.withResolvers() + } + const signal = new AbortController() + const canceled = new Error(`canceled ${i}`) + const call = ports.agentDesktop.readImageSize('/image', signal.signal) + const rejected = expect(call).rejects.toBe(canceled) + await held.entered.promise + signal.abort(canceled) + await Promise.resolve() + held.release.resolve() + await rejected + } + held = undefined + expect(read).toHaveBeenCalledTimes(83) + expect(await ports.agentDesktop.readImageSize('/image')).toEqual({ width: 10, height: 20 }) + expect(failed).not.toHaveBeenCalled() + } finally { + held?.release.resolve() + await core.close() + } +}) + +it('owns renderer subscriptions until registration and waits for physical Native closure', async () => { + const callbacks = new Map void>() + const disposed = vi.fn() + const failed = vi.fn() + const release = Promise.withResolvers() + const registration = Promise.withResolvers() + let pluginOwner: unknown = 'plugin-a' + let remoteTarget: unknown = { windowId: 1, webContentsId: 7 } + let presented: unknown = true + let focused: unknown = true + let previewResult: unknown = { updated: true, surface: 'renderer-canvas' } + const native = new CoreDesktopNative( + { + pluginSettingsWindow: { getPluginIdForWebContents: async () => pluginOwner }, + remoteSessionWindow: { + select: async () => remoteTarget, + present: async () => presented + }, + sessionPresentation: { + focusBrowser: async () => focused, + setComputerUsePreview: async () => previewResult, + dismissComputerUsePreview: async () => true + }, + onRendererDestroyed: (id, listener) => { + callbacks.set(id, listener) + return () => { + callbacks.delete(id) + disposed() + } + } + } as unknown as CoreKernelNative, + async (event) => core.accept(event), + async () => await release.promise, + failed + ) + const core = new CoreNativeDesktop( + async (command) => { + if (command.operation === 'watch') await registration.promise + return await native.invoke([command]) + }, + () => {}, + failed + ) + const ports = await core.createPorts({ temporaryDirectory: '/tmp', profileDirectory: '/profile' }) + expect(await ports.pluginSettingsWindow.getPluginIdForWebContents(7)).toBe('plugin-a') + pluginOwner = { id: 'invalid Native owner' } + await expect(ports.pluginSettingsWindow.getPluginIdForWebContents(7)).rejects.toThrow() + expect(await ports.remoteSessionWindow.select()).toEqual(remoteTarget) + expect(await ports.remoteSessionWindow.present({ windowId: 1, webContentsId: 7 })).toBe(true) + presented = { shown: true } + await expect( + ports.remoteSessionWindow.present({ windowId: 1, webContentsId: 7 }) + ).rejects.toThrow() + remoteTarget = null + expect(await ports.remoteSessionWindow.select()).toBeNull() + remoteTarget = { windowId: 1, webContentsId: '7' } + await expect(ports.remoteSessionWindow.select()).rejects.toThrow() + const caller = { kind: 'renderer', webContentsId: 7, windowId: 1 } as const + expect(await ports.sessionPresentation.focusBrowser('session', caller)).toBe(true) + focused = { focused: true } + await expect(ports.sessionPresentation.focusBrowser('session', caller)).rejects.toThrow() + expect( + await ports.sessionPresentation.setComputerUsePreview('session', 'eligible', caller) + ).toEqual({ updated: true, surface: 'renderer-canvas' }) + previewResult = { updated: true, surface: 'unknown' } + await expect( + ports.sessionPresentation.setComputerUsePreview('session', 'eligible', caller) + ).rejects.toThrow() + const first = vi.fn() + const second = vi.fn() + const stopFirst = ports.onRendererDestroyed(7, first) + const stopSecond = ports.onRendererDestroyed(7, second) + stopFirst() + registration.resolve() + await vi.waitFor(() => expect(callbacks.has(7)).toBe(true)) + callbacks.get(7)!() + expect(first).not.toHaveBeenCalled() + expect(second).toHaveBeenCalledOnce() + stopSecond() + await vi.waitFor(() => expect(disposed).toHaveBeenCalledOnce()) + let closed = false + const closing = core.close().then(() => { + closed = true + }) + await Promise.resolve() + expect(closed).toBe(false) + await expect(ports.getProxySnapshot()).rejects.toThrow('stopping') + release.resolve() + await closing + expect(callbacks.size).toBe(0) + expect(failed).not.toHaveBeenCalled() +}) diff --git a/test/main/harness/coreHelpers.test.ts b/test/main/harness/coreHelpers.test.ts new file mode 100644 index 0000000000..597e36e2b7 --- /dev/null +++ b/test/main/harness/coreHelpers.test.ts @@ -0,0 +1,346 @@ +import { EventEmitter } from 'node:events' +import { setImmediate as nextTurn } from 'node:timers/promises' +import { deserialize, serialize } from 'node:v8' +import { describe, expect, it, vi } from 'vitest' +import { CoreNativeHelpers } from '../../../src/harness/core/helpers' +import { CoreHelperNative } from '@/harness/helpers' +import { + CORE_HELPER_QUEUE, + type CoreHelperCommand, + type CoreHelperKind +} from '@shared/harness/helpers' +import type { BackgroundExecHost } from '@/agent/shared/process/backgroundExecSessionManager' +import type { CodeModeHost } from '@/tool/codeMode/runCodeRuntimeManager' + +const crossed = (value: T): T => deserialize(serialize(value)) + +function nativeHost(earlyReady = false) { + const events = new EventEmitter() + const closed = Promise.withResolvers() + let exited = false + events.once('exit', () => { + exited = true + closed.resolve() + }) + const host = { + pid: 41001, + postMessage: vi.fn<(message: unknown) => void>(), + kill: vi.fn(async () => {}), + onMessage: (listener) => { + events.on('message', listener) + }, + onExit: (listener) => { + events.on('exit', listener) + }, + onError: (listener) => { + events.on('error', listener) + } + } satisfies BackgroundExecHost + const rss = vi.fn(async (_signal: AbortSignal): Promise => 1024) + const code: CodeModeHost = { + ...host, + on: (event, listener) => { + events.on(event, listener) + if (earlyReady && event === 'message') + events.emit('message', { type: 'READY', version: 1, pid: host.pid }) + }, + off: (event, listener) => { + events.off(event, listener) + }, + getRssBytes: rss + } + return { host, code, events, rss, closed, isExited: () => exited } +} + +function fixture( + intercept?: ( + command: CoreHelperCommand, + invoke: (command: CoreHelperCommand) => Promise + ) => Promise +) { + const native = nativeHost(true) + const failures = vi.fn() + const retireExec = vi.fn(async (_ownerPid: number | undefined) => {}) + let core: CoreNativeHelpers + const main = new CoreHelperNative( + { + exec: async () => ({ + ...native.host, + kill: async () => { + if (!native.isExited()) await native.host.kill() + await native.closed.promise + await retireExec(native.host.pid) + } + }), + code: async () => native.code, + scheduler: async () => native.host, + watcher: async () => ({ + ...native.host, + terminate: async () => { + native.host.kill() + await native.closed.promise + } + }) + }, + async (events) => { + core.accept(crossed(events)) + }, + failures + ) + const commands: CoreHelperCommand[] = [] + const invoke = async (command: CoreHelperCommand) => { + commands.push(crossed(command)) + return crossed(await main.invoke([crossed(command)])) + } + core = new CoreNativeHelpers( + (command) => (intercept ? intercept(command, invoke) : invoke(command)), + failures + ) + return { ...native, main, core, failures, retireExec, commands, invoke } +} + +describe('Core Native helper transport', () => { + it('retains unexpected Exec loss until attested descendant recovery completes', async () => { + const f = fixture() + const descendants = Promise.withResolvers() + f.retireExec.mockImplementationOnce(async () => await descendants.promise) + const host = await f.core.spawnBackgroundExecHost(new AbortController().signal) + const exited = vi.fn() + host.onExit(exited) + f.events.emit('exit', 1) + await vi.waitFor(() => expect(f.retireExec).toHaveBeenCalledWith(41001)) + await vi.waitFor(() => expect(exited).toHaveBeenCalledWith(1)) + let retired = false + const closing = f.core.close().then(() => { + retired = true + }) + await nextTurn() + expect(retired).toBe(false) + descendants.resolve() + await closing + expect(f.host.kill).not.toHaveBeenCalled() + expect(f.failures).not.toHaveBeenCalled() + }) + + it('coalesces concurrent retirement into bounded batches', async () => { + const physical: ReturnType[] = [] + const failures = vi.fn() + const commands: CoreHelperCommand[] = [] + let core: CoreNativeHelpers + const unexpected = async (): Promise => { + throw new Error('Unexpected helper kind') + } + const main = new CoreHelperNative( + { + exec: unexpected, + scheduler: unexpected, + watcher: unexpected, + code: async () => { + const native = nativeHost() + native.host.kill.mockImplementation(async () => { + queueMicrotask(() => native.events.emit('exit', 0)) + }) + physical.push(native) + return native.code + } + }, + async (events) => { + core.accept(crossed(events)) + }, + failures + ) + core = new CoreNativeHelpers(async (command) => { + commands.push(crossed(command)) + return crossed(await main.invoke([crossed(command)])) + }, failures) + const hosts = await Promise.all( + Array.from({ length: 33 }, () => core.spawnCodeModeHost(new AbortController().signal)) + ) + const exited = vi.fn() + for (const host of hosts) { + host.on('exit', exited) + host.kill() + } + await vi.waitFor(() => expect(exited).toHaveBeenCalledTimes(33)) + await core.close() + const batches = commands.filter((command) => command.operation === 'terminate') + expect(batches.length).toBeLessThanOrEqual(2) + expect(batches.length).toBeGreaterThan(0) + expect(batches.every((command) => command.ids.length <= 32)).toBe(true) + expect(physical.every((native) => native.host.kill.mock.calls.length === 1)).toBe(true) + expect(failures).not.toHaveBeenCalled() + }) + + it('preserves each fixed helper protocol, callback order and physical retirement', async () => { + for (const kind of ['exec', 'code', 'scheduler', 'content', 'git'] as const) { + const f = fixture() + const signal = new AbortController().signal + const received = vi.fn() + let post: (value: unknown) => void + if (kind === 'code') { + const host = await f.core.spawnCodeModeHost(signal) + host.on('message', received) + host.on('exit', () => {}) + await vi.waitFor(() => + expect(received).toHaveBeenCalledWith({ type: 'READY', version: 1, pid: 41001 }) + ) + received.mockClear() + post = (value) => host.postMessage(value as Parameters[0]) + await expect(host.getRssBytes(signal)).resolves.toBe(1024) + } else { + const host = + kind === 'exec' + ? await f.core.spawnBackgroundExecHost(signal) + : kind === 'scheduler' + ? await f.core.spawnSchedulerHost(signal) + : await f.core.spawnFileWatcherHost(kind, signal) + host.onMessage(received) + host.onExit(() => {}) + post = (value) => host.postMessage(value as never) + } + const messages: Record = { + exec: { type: 'background-exec:request', id: 'request', method: 'list', args: ['session'] }, + code: { + type: 'START', + version: 1, + cellId: 'cell', + frontend: 'function', + source: 'return 1', + bindings: [], + store: {} + }, + scheduler: { type: 'START', now: 1 }, + content: { type: 'file-watcher:request', id: 'watch', method: 'unwatch', args: ['root'] }, + git: { type: 'file-watcher:request', id: 'watch', method: 'unwatch', args: ['root'] } + } + const message = crossed(messages[kind]) + post(message) + Object.assign(message as object, { type: 'mutated-after-post' }) + await vi.waitFor(() => expect(f.host.postMessage).toHaveBeenCalledWith(messages[kind])) + f.events.emit('message', { chunk: Buffer.from('first') }) + f.events.emit('message', { chunk: Buffer.from('second') }) + await vi.waitFor(() => expect(received).toHaveBeenCalledTimes(2)) + expect(received.mock.calls.map(([value]) => value.chunk.toString())).toEqual([ + 'first', + 'second' + ]) + let retired = false + const closing = f.core.close().then(() => { + retired = true + }) + await vi.waitFor(() => expect(f.host.kill).toHaveBeenCalledOnce()) + expect(retired).toBe(false) + f.events.emit('exit', 0) + await closing + await expect(f.core.spawnBackgroundExecHost(signal)).rejects.toThrow('stopping') + expect(f.failures).not.toHaveBeenCalled() + } + }) + + it('compensates canceled startup after an uncancelable launcher returns its actual host', async () => { + const f = fixture() + const opened = Promise.withResolvers() + const descendants = Promise.withResolvers() + const retireExec = vi.fn(async (_ownerPid: number | undefined) => await descendants.promise) + let core: CoreNativeHelpers + const main = new CoreHelperNative( + { + exec: async () => await opened.promise, + code: async () => f.code, + scheduler: async () => f.host, + watcher: async () => { + throw new Error('Unexpected watcher') + } + }, + async (events) => { + core.accept(crossed(events)) + }, + f.failures + ) + core = new CoreNativeHelpers( + async (command) => await main.invoke([crossed(command)]), + f.failures + ) + const owner = new AbortController() + const spawning = Promise.allSettled([core.spawnBackgroundExecHost(owner.signal)]) + await Promise.resolve() + owner.abort() + opened.resolve({ + ...f.host, + kill: async () => { + if (!f.isExited()) await f.host.kill() + await f.closed.promise + await retireExec(f.host.pid) + } + }) + await vi.waitFor(() => expect(f.host.kill).toHaveBeenCalledOnce()) + let retired = false + const closing = core.close().then(() => { + retired = true + }) + await Promise.resolve() + expect(retired).toBe(false) + f.events.emit('exit', 0) + await vi.waitFor(() => expect(retireExec).toHaveBeenCalledWith(41001)) + expect(retired).toBe(false) + descendants.resolve() + expect((await spawning)[0].status).toBe('rejected') + await closing + expect(f.failures).not.toHaveBeenCalled() + }) + + it('drains an owned RSS observation before retiring the helper', async () => { + const f = fixture() + const receipt = Promise.withResolvers() + const entered = Promise.withResolvers() + f.rss.mockImplementationOnce(async () => { + entered.resolve() + return await receipt.promise + }) + const host = await f.core.spawnCodeModeHost(new AbortController().signal) + const exited = vi.fn() + host.on('exit', exited) + const reading = Promise.allSettled([host.getRssBytes(new AbortController().signal)]) + await entered.promise + const closing = f.core.close() + await Promise.resolve() + expect(f.host.kill).not.toHaveBeenCalled() + receipt.resolve(2048) + await vi.waitFor(() => expect(f.host.kill).toHaveBeenCalledOnce()) + f.events.emit('exit', 0) + await closing + expect(exited).toHaveBeenCalledOnce() + expect((await reading)[0].status).toBe('rejected') + expect(f.failures).not.toHaveBeenCalled() + }) + + it('fences overflow without replaying queued commands or accepting arbitrary helper methods', async () => { + const posted = Promise.withResolvers() + const f = fixture(async (command, invoke) => { + if (command.operation === 'send') await posted.promise + return await invoke(command) + }) + const host = await f.core.spawnBackgroundExecHost(new AbortController().signal) + const command = { + type: 'background-exec:request' as const, + id: 'request', + method: 'list' as const, + args: ['session'] + } + const id = f.commands[0].operation === 'open' ? f.commands[0].id : '' + await expect( + f.main.invoke([{ operation: 'send', id, message: { ...command, method: 'constructor' } }]) + ).rejects.toThrow() + await expect(f.main.invoke([{ operation: 'rss', id, pid: 999 }])).rejects.toThrow() + for (let i = 0; i < CORE_HELPER_QUEUE; i++) host.postMessage(command) + expect(() => host.postMessage(command)).toThrow('queue exceeded') + posted.resolve() + expect(() => host.postMessage(command)).toThrow('queue exceeded') + expect(f.failures).toHaveBeenCalledOnce() + expect(f.host.postMessage).not.toHaveBeenCalled() + const closing = Promise.allSettled([f.main.close()]) + await vi.waitFor(() => expect(f.host.kill).toHaveBeenCalledOnce()) + f.events.emit('exit', 0) + await closing + }) +}) diff --git a/test/main/harness/coreMemoryRuntime.test.ts b/test/main/harness/coreMemoryRuntime.test.ts new file mode 100644 index 0000000000..0db02a930b --- /dev/null +++ b/test/main/harness/coreMemoryRuntime.test.ts @@ -0,0 +1,201 @@ +import { execFileSync } from 'node:child_process' +import { randomUUID } from 'node:crypto' +import { cpSync, mkdtempSync, rmSync } from 'node:fs' +import { tmpdir } from 'node:os' +import path from 'node:path' +import { DuckDBInstance } from '@duckdb/node-api' +import { afterEach, expect, it, vi } from 'vitest' +import { CoreRuntimeConnection } from '@/harness/core' +import { HarnessRuntime } from '@/harness/runtime' + +vi.unmock('fs') +vi.unmock('node:fs') +vi.unmock('path') +vi.unmock('node:path') +vi.unmock('child_process') +vi.unmock('node:child_process') + +const cleanups: Array<() => unknown | Promise> = [] +afterEach(async () => { + for (const cleanup of cleanups.reverse()) await cleanup() + cleanups.length = 0 +}) + +function assertChild(pid: number, parent: number | undefined) { + const actual = Number( + (process.platform === 'win32' + ? execFileSync( + 'powershell.exe', + [ + '-NoProfile', + '-NonInteractive', + '-Command', + `(Get-CimInstance Win32_Process -Filter 'ProcessId = ${pid}').ParentProcessId` + ], + { encoding: 'utf8' } + ) + : execFileSync('ps', ['-o', 'ppid=', '-p', String(pid)], { encoding: 'utf8' }) + ).trim() + ) + expect(actual).toBe(parent) +} + +function alive(pid: number) { + try { + process.kill(pid, 0) + return true + } catch { + return false + } +} + +async function fixture() { + const directory = mkdtempSync(path.join(tmpdir(), 'deepchat-core-memory-')) + cleanups.push(() => rmSync(directory, { recursive: true, force: true })) + const harness = path.join(directory, 'harness') + cpSync(path.resolve('out/harness'), harness, { recursive: true }) + const { build } = await vi.importActual('vite') + await build({ + configFile: false, + publicDir: false, + logLevel: 'silent', + resolve: { + conditions: ['node'], + mainFields: ['module', 'main'], + alias: { '@': path.resolve('src/main'), '@shared': path.resolve('src/shared') } + }, + build: { + target: 'node24', + outDir: harness, + emptyOutDir: false, + copyPublicDir: false, + lib: { + entry: path.resolve('test/fixtures/harness/core-memory.ts'), + formats: ['es'], + fileName: () => 'core.mjs' + }, + rollupOptions: { external: [/^node:/] } + } + }) + const runtime = await HarnessRuntime.start({ + directory: harness, + electronExecutable: process.execPath, + capacity: 1, + maxPending: 4, + observe() {} + }) + cleanups.push(() => runtime.stop()) + const connection = await CoreRuntimeConnection.connect({ + runtime, + temporaryDirectory: directory, + native: async () => { + throw new Error('Unexpected Native operation') + } + }) + cleanups.push(() => connection.closeAfterHostExit()) + const core = await connection.status() + assertChild(core.pid, runtime.identity?.hostPid) + const memoryDbDir = path.join(directory, 'memory') + await connection.openProfile({ + userDataDirectory: directory, + homeDirectory: directory, + currentAppVersion: '1.1.4', + systemLocale: 'en-US', + appPath: path.resolve('.'), + isPackaged: false + }) + const command = (action: string) => + connection.configure( + action === 'start' + ? { operation: 'initialize' } + : action === 'write' + ? { operation: 'nativeSnapshot' } + : { operation: 'read', input: {} } + ) + const start = async () => { + const identity = (await command('start')) as { pid: number; generation: string } + assertChild(identity.pid, runtime.identity?.hostPid) + expect(identity.pid).not.toBe(core.pid) + return identity + } + return { runtime, connection, memoryDbDir, core, start, command } +} + +it('preserves packed data through Core and releases the native owner before stop returns', async () => { + const f = await fixture() + const memory = await f.start() + expect((await f.start()).pid).toBe(memory.pid) + const result = (await f.command('write')) as { before: string[]; after: string[] } + expect(result.before).toEqual( + Array.from({ length: 45 }, (_, i) => `row-${String(i).padStart(2, '0')}`) + ) + expect(result.after).toEqual(result.before.slice(1)) + await expect( + f.runtime.startMemory({ + generation: randomUUID(), + memoryDbDir: f.memoryDbDir, + host: { appPath: path.resolve('.'), userDataPath: f.memoryDbDir, isPackaged: false } + }) + ).rejects.toThrow('owned by Core') + await f.connection.stop() + expect(alive(memory.pid)).toBe(false) + const instance = await DuckDBInstance.create(path.join(f.memoryDbDir, 'core-data.v2.duckdb')) + try { + const connection = await instance.connect() + try { + const values = await connection.runAndReadAll( + "SELECT embedding[45] AS anchor, embedding[2048] AS fraction, list_sum(embedding)::FLOAT AS total FROM memory_vector WHERE memory_id = 'row-44'" + ) + expect(values.getRowObjectsJson()).toEqual([{ anchor: 1, fraction: -0.25, total: 0.75 }]) + } finally { + connection.closeSync() + } + } finally { + instance.closeSync() + } +}) + +it('preserves a Main-owned Memory generation when Core is denied and stopped', async () => { + const f = await fixture() + const generation = randomUUID() + await f.runtime.startMemory({ + generation, + memoryDbDir: f.memoryDbDir, + host: { appPath: path.resolve('.'), userDataPath: f.memoryDbDir, isPackaged: false } + }) + await expect(f.start()).rejects.toMatchObject({ + name: 'MemoryVectorStoreQuarantineRequiredError' + }) + await f.connection.stop() + expect(f.runtime.memoryAvailable).toBe(true) + await expect( + f.runtime.requestMemory({ + method: 'reset', + generation, + operation: randomUUID(), + agentId: 'after-core-stop' + }) + ).resolves.toBeNull() + await expect(f.runtime.request('snapshot', [])).resolves.toHaveProperty('admission') +}) + +it('reaps Core-owned Memory after physical Core loss without restarting it', async () => { + const f = await fixture() + const memory = await f.start() + process.kill(f.core.pid, 'SIGKILL') + await expect.poll(() => alive(memory.pid)).toBe(false) + await expect(f.connection.status()).rejects.toThrow('Core runtime stopped') + await expect(f.runtime.request('snapshot', [])).resolves.toHaveProperty('admission') +}) + +it('marks physical Memory loss unavailable while preserving the Rust control path', async () => { + const f = await fixture() + const memory = await f.start() + process.kill(memory.pid, 'SIGKILL') + await expect.poll(async () => await f.command('available')).toEqual({ available: false }) + await expect(f.start()).rejects.toMatchObject({ + name: 'MemoryVectorStoreQuarantineRequiredError' + }) + await expect(f.runtime.request('snapshot', [])).resolves.toHaveProperty('admission') + await f.connection.stop() +}) diff --git a/test/main/harness/coreNative.test.ts b/test/main/harness/coreNative.test.ts new file mode 100644 index 0000000000..f7af545299 --- /dev/null +++ b/test/main/harness/coreNative.test.ts @@ -0,0 +1,72 @@ +import { mkdtempSync, rmSync } from 'node:fs' +import { tmpdir } from 'node:os' +import path from 'node:path' +import { randomUUID } from 'node:crypto' +import { expect, it, vi } from 'vitest' +import { CoreNativeCalls } from '../../../src/harness/core/native' +import { CorePayloadStore } from '../../../src/harness/core/payload' + +vi.unmock('fs') +vi.unmock('node:fs') +vi.unmock('path') +vi.unmock('node:path') + +it('reserves Native retirement capacity and drains real receipts while fencing queued work', async () => { + const directory = mkdtempSync(path.join(tmpdir(), 'rutis-native-window-')) + const main = await CorePayloadStore.create(directory) + const core = new CorePayloadStore(main.config, 'core') + const calls: Array<{ args: unknown; receipt: ReturnType> }> = + [] + let stopping = false + const native = new CoreNativeCalls( + core, + async (_method, payload) => { + const args = await main.decode(payload) + const receipt = Promise.withResolvers() + calls.push({ args, receipt }) + await receipt.promise + return await main.encode(undefined) + }, + () => stopping + ) + try { + const ordinary = Array.from({ length: 14 }, () => native.call('crypto.available', [])) + await vi.waitFor(() => expect(calls).toHaveLength(14)) + const command = { + operation: 'send', + id: randomUUID(), + message: { type: 'START', source: 'original' } + } + const queued = native.call('helpers', [command]) + command.message.source = 'changed-after-call' + const controls = [ + native.call('helpers', [{ operation: 'close' }]), + native.call('browser', [{ operation: 'cancel', id: randomUUID() }]) + ] + await vi.waitFor(() => expect(calls).toHaveLength(16)) + calls[0].receipt.resolve() + await vi.waitFor(() => expect(calls).toHaveLength(17)) + expect(calls[16].args).toMatchObject([{ message: { source: 'original' } }]) + const rejected = expect(native.call('crypto.available', [])).rejects.toThrow('stopping') + stopping = true + calls[14].receipt.resolve() + calls[15].receipt.resolve() + await rejected + let drained = false + const drain = native.drain().then(() => { + drained = true + }) + await Promise.resolve() + expect(drained).toBe(false) + for (const call of calls) call.receipt.resolve() + await Promise.all([...ordinary, queued, ...controls]) + await drain + expect(calls).toHaveLength(17) + } finally { + for (const call of calls) call.receipt.resolve() + await native.drain() + await core.close() + await main.close() + rmSync(directory, { recursive: true, force: true }) + } +}) diff --git a/test/main/harness/corePayload.test.ts b/test/main/harness/corePayload.test.ts new file mode 100644 index 0000000000..68e3f4020a --- /dev/null +++ b/test/main/harness/corePayload.test.ts @@ -0,0 +1,79 @@ +import { mkdtemp, readdir, readFile, rm, writeFile } from 'node:fs/promises' +import { tmpdir } from 'node:os' +import path from 'node:path' +import { afterEach, expect, it, vi } from 'vitest' +import { randomUUID } from 'node:crypto' +import { CorePayloadStore } from '../../../src/harness/core/payload' + +vi.unmock('fs') +vi.unmock('node:fs') +vi.unmock('path') +vi.unmock('node:path') + +const stores: CorePayloadStore[] = [] +const roots: string[] = [] +async function pair() { + const root = await mkdtemp(path.join(tmpdir(), 'core-payload-test-')) + roots.push(root) + const main = await CorePayloadStore.create(root) + const core = new CorePayloadStore(main.config, 'core') + stores.push(main, core) + return { main, core } +} +afterEach(async () => { + await Promise.all(stores.splice(0).map((store) => store.close())) + await Promise.all(roots.splice(0).map((root) => rm(root, { recursive: true, force: true }))) +}) + +it('preserves IPC values inline and through encrypted large carriers', async () => { + const { main, core } = await pair() + const value = { + missing: undefined, + date: new Date('0000-01-01T00:00:00Z'), + integer: 9n, + map: new Map([['x', new Set([1])]]) + } + expect(await core.decode(await main.encode(value))).toEqual(value) + const large = { secret: 'private-payload', bytes: Buffer.alloc(3 * 1024 * 1024, 37) } + const ref = await main.encode(large) + const files = await readdir(main.config.directory) + expect(files).toHaveLength(1) + expect( + (await readFile(path.join(main.config.directory, files[0]))).includes(Buffer.from(large.secret)) + ).toBe(false) + expect(await core.decode(ref)).toEqual(large) + await expect(core.decode(ref)).rejects.toThrow() +}) + +it('authenticates carrier generation, direction and metadata before consuming it', async () => { + const { main, core } = await pair() + const ref = await main.encode(Buffer.alloc(32 * 1024, 41)) + if ('inline' in ref) throw new Error('Expected carrier') + await expect(main.decode(ref)).rejects.toThrow('direction') + await expect(core.decode({ ...ref, bytes: ref.bytes + 1 })).rejects.toThrow('size') + const foreign = new CorePayloadStore({ ...main.config, generation: randomUUID() }, 'core') + stores.push(foreign) + await expect(foreign.decode(ref)).rejects.toThrow() + const [file] = await readdir(main.config.directory) + const location = path.join(main.config.directory, file) + const original = await readFile(location) + const changed = Buffer.from(original) + changed[changed.length - 1] ^= 1 + await writeFile(location, changed) + await expect(core.decode(ref)).rejects.toThrow() + await writeFile(location, original) + expect(await core.decode(ref)).toEqual(Buffer.alloc(32 * 1024, 41)) +}) + +it('bounds retained files and reclaims consumed carriers without a timing retry', async () => { + const { main, core } = await pair() + const refs = await Promise.all( + Array.from({ length: 4 }, () => main.encode(Buffer.alloc(32 * 1024))) + ) + await expect(main.encode(Buffer.alloc(32 * 1024))).rejects.toThrow('quota') + await core.decode(refs[0]) + await main.encode(Buffer.alloc(32 * 1024)) + await main.close() + expect(await readdir(main.config.directory)).toHaveLength(0) + await expect(main.encode(undefined)).rejects.toThrow('closed') +}) diff --git a/test/main/harness/coreRuntime.test.ts b/test/main/harness/coreRuntime.test.ts new file mode 100644 index 0000000000..37d7a6332a --- /dev/null +++ b/test/main/harness/coreRuntime.test.ts @@ -0,0 +1,1281 @@ +import { createCoreConfigurationNative } from '@/harness/configuration' +import type { NativeConfigurationSnapshot } from '@shared/types/nativeConfiguration' +import { execFileSync } from 'node:child_process' +import { mkdtempSync, mkdirSync, rmSync, statSync, cpSync, existsSync } from 'node:fs' +import { tmpdir } from 'node:os' +import path from 'node:path' +import { randomUUID } from 'node:crypto' +import { EventEmitter } from 'node:events' +import { createServer, type ServerResponse } from 'node:http' +import { connect, type Socket, type AddressInfo } from 'node:net' +import { afterEach, expect, it, vi } from 'vitest' +import { describeIfNativeSqlite } from '../nativeSqliteHarness' +import { HarnessRuntime } from '@/harness/runtime' +import { CoreRuntimeConnection, createCoreProfileNative } from '@/harness/core' +import { initializeCoreProfile } from '@/app/profileStartup' +import { CoreDesktopNative } from '@/harness/desktop' +import { CoreBrowserNative } from '@/harness/browser' +import type { CoreKernelNative } from '../../../src/harness/core/kernel' +import { createRendererRouteContext, type RouteContext } from '@/routes/routeRegistry' +import { CORE_ORDINARY_CALLS, CORE_ROUTE_QUEUED } from '@shared/harness/core' +import { configGetEntriesRoute } from '@shared/contracts/routes' +import { CoreHelperNative } from '@/harness/helpers' +import { FileWatcherHost } from '@/platform/fileWatcher/watcherHost' +import type { FileWatcherHost as FileWatcherPort } from '@/platform/fileWatcher/watcherHostClient' +import type { WatchRequest } from '@/platform/fileWatcher/watcherTypes' +import { CronJobsSchedulerUtilityHost } from '@/scheduler/schedulerUtilityHost' +import type { SchedulerHost } from '@/scheduler/schedulerProcessManager' + +vi.unmock('fs') +vi.unmock('node:fs') +vi.unmock('path') +vi.unmock('node:path') +vi.unmock('child_process') +vi.unmock('node:child_process') +vi.unmock('node:http') +vi.unmock('node:net') + +describeIfNativeSqlite('Rutis Core profile ownership', () => { + const cleanups: Array<() => void | Promise> = [] + afterEach(async () => { + vi.useRealTimers() + for (const cleanup of cleanups.reverse()) await cleanup() + cleanups.length = 0 + }) + + async function fixture( + options: { + available?: () => Promise + detached?: boolean + publishSnapshot?: (snapshot: NativeConfigurationSnapshot) => Promise + refreshTheme?: (isDark: boolean) => Promise + readTheme?: () => Promise<{ theme: 'dark' | 'light' | 'system'; isDark: boolean }> + transport?: (runtime: HarnessRuntime) => Pick + retireDesktop?: () => Promise + retireBrowser?: () => Promise + isSettingsWindow?: (windowId: number | null) => Promise + resumeConfiguration?: () => Promise + assertCallerActive?: (context: RouteContext) => void + sessionPresentation?: CoreKernelNative['sessionPresentation'] + sessionWindow?: CoreKernelNative['remoteSessionWindow'] + applyProxy?: CoreKernelNative['applyProxy'] + getProxySnapshot?: CoreKernelNative['getProxySnapshot'] + onFailure?: (error: Error) => void + onRendererDestroyed?: CoreKernelNative['onRendererDestroyed'] + } = {} + ) { + const directory = mkdtempSync(path.join(tmpdir(), 'rutis-core-test-')) + const userDataDirectory = path.join(directory, 'profile') + mkdirSync(userDataDirectory) + cleanups.push(() => rmSync(directory, { recursive: true, force: true })) + const harness = options.detached ? path.join(directory, 'harness') : path.resolve('out/harness') + if (options.detached) cpSync(path.resolve('out/harness'), harness, { recursive: true }) + const runtime = await HarnessRuntime.start({ + directory: harness, + electronExecutable: process.execPath, + capacity: 1, + maxPending: 4, + observe() {} + }) + cleanups.push(() => runtime.stop()) + const unexpected = async (): Promise => { + throw new Error('Unexpected Native operation') + } + const host = { + setLoggingEnabled: vi.fn(async () => {}), + showDatabaseUnlockProgress: vi.fn(async () => {}), + requestDatabaseUnlock: vi.fn(async () => null), + requestDatabaseRecovery: vi.fn(async () => null), + quit: vi.fn(async () => {}) + } + const crypto = { + isAvailable: options.available ?? (async () => false), + encrypt: unexpected, + decrypt: unexpected, + getBackend: async () => undefined + } + const nativeSnapshots: NativeConfigurationSnapshot[] = [] + const publishEvent = vi.fn() + const desktop = { + systemLocale: 'en-US', + readTheme: options.readTheme ?? (async () => ({ theme: 'system' as const, isDark: false })), + applyTheme: async () => false, + refreshLanguage: async () => {}, + refreshTheme: async (isDark) => { + await options.refreshTheme?.(isDark) + }, + readLaunchAtLogin: async () => false, + setLaunchAtLogin: async (enabled: boolean) => enabled, + applyContentProtection: async () => {}, + applyFloatingButtonEnabled: async () => {} + } + const profileNative = createCoreProfileNative(crypto, host) + const configurationNative = createCoreConfigurationNative({ + desktop, + logging: { applyLogging: async () => {}, openLoggingFolder: async () => {} }, + enumerateFonts: async () => ['Fixture Sans'], + publishNativeSnapshot: async (snapshot) => { + if (options.publishSnapshot) await options.publishSnapshot(snapshot) + nativeSnapshots.push(snapshot) + }, + publishEvent + }) + const kernelNative: CoreKernelNative = { + configurationHost: { + pause: async () => {}, + resume: options.resumeConfiguration ?? (async () => {}), + stopWrites: async () => {}, + drainWrites: async () => {}, + startWrites: async () => {} + }, + isSettingsWindow: options.isSettingsWindow ?? (async () => false), + isMainWindowContext: async () => false, + rendererPerformance: { record: async () => false }, + splash: { showDebugScenario: unexpected, closeDebugScenario: async () => false }, + images: { saveImage: unexpected, copyImage: unexpected }, + pickToolchainPath: unexpected, + hasTrustedRenderer: async () => false, + applyProxy: options.applyProxy ?? (async () => false), + copilotAuthorization: { browser: unexpected, device: unexpected }, + sessionPresentation: options.sessionPresentation ?? { + focusBrowser: unexpected, + setComputerUsePreview: unexpected, + dismissComputerUsePreview: unexpected + }, + workspaceDesktop: { + revealFile: unexpected, + openFile: unexpected, + listFileOpenApps: unexpected, + openFileWithApp: unexpected + }, + approvalPresentation: { + getTarget: async () => null, + present: unexpected, + close: async () => {} + }, + ocrCacheKeyProvider: { loadOrCreateKey: unexpected }, + initializeFloatingButton: async () => {}, + assertSyncTokenStorageAvailable: unexpected, + semanticNotifications: { occur() {}, recover() {} }, + secureCrypto: crypto, + providerCrypto: crypto, + getProxySnapshot: + options.getProxySnapshot ?? (async () => ({ version: 0, proxyUrl: null, noProxy: '' })), + openExternal: unexpected, + openPath: unexpected, + selectDirectory: unexpected, + cacheImage: unexpected, + agentDesktop: { + tempDirectory: directory, + profileTempDirectory: path.join(userDataDirectory, 'temp'), + readImageSize: unexpected, + createSettingsWindow: async () => null, + sendSettingsNavigation: async () => false + }, + pluginSettingsWindow: { + open: unexpected, + close: async () => {}, + closeAll: async () => {}, + getPluginIdForWebContents: async () => null + }, + remoteAuthWindows: { open: unexpected, close: async () => {}, closeAll: async () => {} }, + remoteSessionWindow: options.sessionWindow ?? { select: unexpected, present: unexpected }, + showNotification: unexpected, + mcpPreview: undefined, + sessionUiPort: { refreshSessionUi: async () => {} }, + mcpReadinessChanged() {}, + reportStartupFailure() {}, + onSessionInactive() {}, + renderer: { + broadcast: (envelope) => publishEvent(envelope.name, envelope.payload), + send: (_id, envelope) => publishEvent(envelope.name, envelope.payload) + }, + onRendererDestroyed: options.onRendererDestroyed ?? (() => () => {}), + sendToRenderer: (_id, name, payload) => publishEvent(name, payload), + destroySessionBrowser: async () => {}, + observeDelegation() {}, + runJournalObserver: undefined, + notifySessionCompletion: unexpected, + fetchRegistryIcon: unexpected, + mcpAppDesktop: { observeOwner: unexpected, openExternal: unexpected }, + publishMcpConsent() {} + } + let connection: CoreRuntimeConnection + // shortcut: Native owners run in-process here; verify utility-process bindings at G5 cutover. + const nativeHelpers = new CoreHelperNative( + { + exec: unexpected, + code: unexpected, + watcher: async () => { + const events = new EventEmitter() + const owner = new FileWatcherHost({ + postMessage: (message) => events.emit('message', message) + }) + return { + postMessage: (message) => { + const operation = + message.method === 'watch' + ? owner.watch(message.args[0] as WatchRequest) + : message.method === 'unwatch' + ? owner.unwatch(message.args[0] as string) + : owner.shutdown() + void operation.then( + (data) => + events.emit('message', { + type: 'file-watcher:response', + id: message.id, + ok: true, + data + }), + (error: Error) => + events.emit('message', { + type: 'file-watcher:response', + id: message.id, + ok: false, + error: { message: error.message } + }) + ) + }, + onMessage: (listener) => { + events.on('message', listener) + }, + onExit: (listener) => { + events.on('exit', listener) + }, + onError: (listener) => { + events.on('error', listener) + }, + terminate: async () => { + await owner.shutdown() + events.emit('exit', 0) + } + } satisfies FileWatcherPort + }, + scheduler: async () => { + const events = new EventEmitter() + const owner = new CronJobsSchedulerUtilityHost({ + postMessage: (message) => events.emit('message', message) + }) + return { + pid: process.pid, + postMessage: (message) => { + if (message.type === 'START') owner.start() + else if (message.type === 'RECONCILE') owner.reconcile() + else if (message.type === 'SCAN_RESULT') owner.applyScanResult(message) + else owner.shutdown() + }, + kill: () => { + owner.shutdown() + events.emit('exit', 0) + }, + onMessage: (listener) => { + events.on('message', listener) + }, + onExit: (listener) => { + events.on('exit', listener) + }, + onError: (listener) => { + events.on('error', listener) + } + } satisfies SchedulerHost + } + }, + async (events) => await connection.deliverNativeEvents(events), + (error) => connection.invalidateNative(error) + ) + const nativeDesktop = new CoreDesktopNative( + kernelNative, + async (event) => await connection.deliverDesktopEvent(event), + options.retireDesktop ?? (async () => {}), + (error) => { + throw error + } + ) + const nativeBrowser = new CoreBrowserNative( + { + getToolDefinitions: () => [], + callTool: async () => { + throw new Error('Unexpected Agent Browser call') + } + }, + async (event) => await connection.deliverBrowserEvent(event), + options.retireBrowser ?? (async () => {}), + (error) => connection.invalidateNative(error) + ) + connection = await CoreRuntimeConnection.connect({ + onFailure: options.onFailure, + runtime: options.transport?.(runtime) ?? runtime, + temporaryDirectory: directory, + native: (method, args) => + method === 'desktop' + ? nativeDesktop.invoke(args) + : method === 'helpers' + ? nativeHelpers.invoke(args) + : method === 'browser' + ? nativeBrowser.invoke(args) + : method === 'configuration' + ? configurationNative(method, args) + : profileNative(method, args) + }) + cleanups.push(async () => { + await connection.closeAfterHostExit() + await nativeHelpers.close().catch(() => {}) + await nativeBrowser.close().catch(() => {}) + }) + const profileOptions = { + userDataDirectory, + homeDirectory: directory, + currentAppVersion: '1.1.4', + systemLocale: 'en-US', + appPath: path.resolve('.'), + isPackaged: false + } + return { + runtime, + connection, + directory, + options: profileOptions, + host, + crypto, + nativeSnapshots, + publishEvent, + invokeRoute: async (routeName: string, input: unknown, context: RouteContext) => + await connection.invokeRoute(routeName, input, context, () => + options.assertCallerActive?.(context) + ), + assembleKernel: async () => + await connection.assembleKernel({ + temporaryDirectory: directory, + appDataDirectory: directory, + documentsDirectory: null, + projectHomeDirectory: null, + resourcesPath: path.resolve('resources'), + workingDirectory: directory, + builtinSkillsDirectories: [], + providerCatalogPath: path.resolve('resources/model-db/providers.json'), + allowInsecureTls: false + }) + } + } + + it( + 'runs SDK routes with bounded queues, reserved control and acknowledged retirement', + { timeout: 30_000 }, + async () => { + const entered = Promise.withResolvers() + const retired = Promise.withResolvers() + const queriesEntered = Promise.withResolvers() + const queriesReleased = Promise.withResolvers() + let holdQueries = false + let settingsQueries = 0 + let queuedCallerActive = true + const focusBrowser = vi.fn(async () => true) + const setComputerUsePreview = vi.fn< + CoreKernelNative['sessionPresentation']['setComputerUsePreview'] + >(async (_id, mode) => ({ + updated: true, + surface: mode === 'stopped' ? 'none' : 'renderer-canvas' + })) + const dismissComputerUsePreview = vi.fn(async () => true) + const selectSessionWindow = vi.fn(async () => ({ windowId: 99, webContentsId: 77 })) + const presentSessionWindow = vi.fn(async () => true) + const f = await fixture({ + detached: true, + sessionWindow: { select: selectSessionWindow, present: presentSessionWindow }, + sessionPresentation: { focusBrowser, setComputerUsePreview, dismissComputerUsePreview }, + assertCallerActive: (context) => { + if ( + context.caller.kind === 'renderer' && + context.caller.webContentsId === 43 && + !queuedCallerActive + ) + throw new Error('Renderer document retired') + }, + isSettingsWindow: async () => { + if (holdQueries) { + if (++settingsQueries === CORE_ORDINARY_CALLS) queriesEntered.resolve() + await queriesReleased.promise + } + return false + }, + retireDesktop: async () => { + entered.resolve() + await retired.promise + } + }) + try { + await f.connection.openProfile(f.options) + await f.connection.configure({ operation: 'initialize' }) + const status = await f.assembleKernel() + expect(status).toMatchObject({ phase: 'kernel-assembled', businessReady: false }) + expect(status.pid).not.toBe(process.pid) + await expect( + f.invokeRoute('settings.getSnapshot', {}, createRendererRouteContext(42, 99)) + ).rejects.toThrow('Core business routes are unavailable') + await expect(f.connection.mcpAppSandbox.getProtocolPolicy('missing')).rejects.toThrow( + 'Core MCP App protocol is unavailable' + ) + await expect(f.connection.host.hasProvider('missing')).rejects.toThrow( + 'Core host queries are unavailable' + ) + await expect(f.connection.host.getShutdownConfirmation()).rejects.toThrow( + 'Core host control is unavailable' + ) + await expect(f.connection.startKernel()).resolves.toMatchObject({ + phase: 'running', + businessReady: true + }) + f.publishEvent.mockClear() + await f.connection.host.observeSettingsWindow('opened') + await vi.waitFor(() => + expect(f.publishEvent).toHaveBeenCalledWith( + 'startup.workload.changed', + expect.objectContaining({ target: 'settings' }) + ) + ) + await f.connection.host.observeSettingsWindow('ready') + await f.connection.host.systemResume() + await f.connection.host.observeSettingsWindow('closed') + await expect(f.connection.mcpAppSandbox.getProtocolPolicy('missing')).resolves.toBeNull() + await expect(f.connection.host.hasProvider('missing')).resolves.toBe(false) + await expect( + f.connection.host.resolveWorkspacePreviewRequest('workspace-preview://missing/index.html') + ).resolves.toBeNull() + await expect( + f.connection.mcpAppSandbox.requestBrowserPermission( + 'missing', + { webContentsId: 42, windowId: 99 }, + ['camera'] + ) + ).resolves.toBe(false) + f.publishEvent.mockClear() + await f.invokeRoute( + 'settings.update', + { + changes: [{ key: 'notificationsEnabled', value: false }] + }, + createRendererRouteContext(42, 99) + ) + await vi.waitFor(() => + expect(f.publishEvent).toHaveBeenCalledWith( + 'settings.changed', + expect.objectContaining({ values: { notificationsEnabled: false } }) + ) + ) + const caller = createRendererRouteContext(42, 99) + await expect( + f.invokeRoute('config.setLanguage', { language: 'zh-CN' }, caller) + ).resolves.toMatchObject({ requestedLanguage: 'zh-CN', locale: 'zh-CN' }) + await expect(f.invokeRoute('config.getLanguage', {}, caller)).resolves.toMatchObject({ + locale: 'zh-CN' + }) + await f.invokeRoute('config.setTheme', { theme: 'dark' }, caller) + await expect(f.invokeRoute('config.getTheme', {}, caller)).resolves.toMatchObject({ + theme: 'dark' + }) + await expect( + f.invokeRoute('config.setUpdateChannel', { channel: 'beta' }, caller) + ).resolves.toEqual({ channel: 'beta' }) + await expect(f.invokeRoute('config.getUpdateChannel', {}, caller)).resolves.toEqual({ + channel: 'beta' + }) + const { sessionId } = (await f.invokeRoute( + 'sessions.createDraft', + { agentId: 'deepchat', projectDir: null }, + caller + )) as { sessionId: string } + await expect(f.invokeRoute('browser.focusContent', { sessionId }, caller)).resolves.toEqual( + { focused: true } + ) + expect(focusBrowser).toHaveBeenCalledWith(sessionId, caller.caller) + await expect( + f.invokeRoute('browser.focusContent', { sessionId }, createRendererRouteContext(43, 99)) + ).resolves.toEqual({ focused: false }) + expect(focusBrowser).toHaveBeenCalledOnce() + await f.invokeRoute('sessions.activate', { sessionId }, caller) + await expect( + f.invokeRoute('computerUse.setPreviewMode', { sessionId, mode: 'eligible' }, caller) + ).resolves.toEqual({ updated: true, surface: 'renderer-canvas' }) + await expect( + f.invokeRoute('computerUse.dismissPreview', { sessionId, runId: 'run-1' }, caller) + ).resolves.toEqual({ dismissed: true }) + expect(dismissComputerUsePreview).toHaveBeenCalledWith(sessionId, 'run-1', caller.caller) + await f.invokeRoute('sessions.deactivate', {}, caller) + await expect( + f.invokeRoute('computerUse.setPreviewMode', { sessionId, mode: 'eligible' }, caller) + ).resolves.toEqual({ updated: false, surface: 'none' }) + await expect( + f.invokeRoute('computerUse.setPreviewMode', { sessionId, mode: 'stopped' }, caller) + ).resolves.toEqual({ updated: true, surface: 'none' }) + expect(setComputerUsePreview).toHaveBeenCalledTimes(2) + const created = (await f.invokeRoute( + 'sessions.create', + { + agentId: 'deepchat', + providerId: 'openai', + modelId: 'gpt-4o-mini', + message: '' + }, + caller + )) as { session: { id: string } } + const widget = (await f.invokeRoute('sessions.getWidgetSnapshot', {}, caller)) as { + snapshot: { sessions: { id: string }[] } + } + expect(widget.snapshot.sessions.map((session) => session.id)).toEqual([created.session.id]) + await expect( + f.invokeRoute('sessions.openDesktop', { sessionId: 'missing' }, caller) + ).resolves.toEqual({ opened: false }) + expect(selectSessionWindow).not.toHaveBeenCalled() + await expect( + f.invokeRoute('sessions.openDesktop', { sessionId: created.session.id }, caller) + ).resolves.toEqual({ opened: true }) + expect(presentSessionWindow).toHaveBeenCalledWith({ windowId: 99, webContentsId: 77 }) + await expect( + f.invokeRoute('sessions.getActive', {}, createRendererRouteContext(77, 99)) + ).resolves.toMatchObject({ session: { id: created.session.id } }) + await expect( + f.invokeRoute( + 'settings.getSnapshot', + {}, + { + caller: { kind: 'internal', component: 'migration' } + } + ) + ).rejects.toThrow() + await expect(f.invokeRoute('window.getCurrentState', {}, caller)).rejects.toThrow( + 'Unhandled deepchat route' + ) + holdQueries = true + const ordinary = Array.from({ length: CORE_ORDINARY_CALLS }, () => + f.invokeRoute('providers.listSummaries', {}, caller) + ) + const ordinaryOutcomes = Promise.allSettled(ordinary) + await queriesEntered.promise + await expect(f.connection.host.getShutdownConfirmation()).resolves.toBeNull() + await expect(f.connection.host.clearPermissionCaches()).resolves.toBeUndefined() + const prompt = 'x'.repeat(3 * 1024 * 1024) + const input = { changes: [{ key: 'default_system_prompt', value: prompt }] } + const queued = [ + f.invokeRoute('config.updateEntries', input, caller), + f.invokeRoute( + 'config.updateEntries', + { changes: [{ key: 'default_system_prompt', value: 'retired caller' }] }, + createRendererRouteContext(43, 100) + ), + f.connection.mcpAppSandbox.getProtocolPolicy('missing'), + ...Array.from({ length: CORE_ROUTE_QUEUED - 3 }, () => + f.invokeRoute('config.getEntries', { keys: ['init_complete'] }, caller) + ) + ] + const queuedOutcomes = Promise.allSettled(queued) + input.changes[0].value = 'mutated after admission' + queuedCallerActive = false + await expect(f.invokeRoute('config.getEntries', {}, caller)).rejects.toThrow( + 'Core route queue exceeded' + ) + await expect( + f.invokeRoute( + 'approvals.resolve', + { requestId: 'unknown_approval_id', decision: 'denied' }, + caller + ) + ).resolves.toEqual({ accepted: false }) + expect(settingsQueries).toBe(CORE_ORDINARY_CALLS) + await expect(f.connection.status()).resolves.toMatchObject({ businessReady: true }) + queriesReleased.resolve() + expect((await ordinaryOutcomes).every((result) => result.status === 'fulfilled')).toBe(true) + const outcomes = await queuedOutcomes + expect(outcomes[2]).toEqual({ status: 'fulfilled', value: null }) + expect(outcomes.filter((result) => result.status === 'rejected')).toHaveLength(2) + expect(outcomes[1]).toMatchObject({ + status: 'rejected', + reason: { message: 'Renderer document retired' } + }) + expect(outcomes.at(-1)).toMatchObject({ + status: 'rejected', + reason: { message: 'Core route queue yielded to a control request' } + }) + const output = configGetEntriesRoute.output.parse( + await f.invokeRoute('config.getEntries', { keys: ['default_system_prompt'] }, caller) + ) + expect(output.values.default_system_prompt === prompt).toBe(true) + const stopping = f.connection.stop() + const observed = stopping.then( + () => 'stopped', + () => 'failed' + ) + await entered.promise + await expect( + initializeCoreProfile({ ...f.options, host: f.host, crypto: f.crypto }) + ).rejects.toMatchObject({ name: 'CoreProfileOwnershipUnavailableError' }) + retired.resolve() + expect(await observed).toBe('stopped') + const reopened = await initializeCoreProfile({ + ...f.options, + host: f.host, + crypto: f.crypto + }) + expect(reopened.settingsStore.get('notificationsEnabled')).toBe(false) + reopened.closeProfile() + } finally { + queriesReleased.resolve() + retired.resolve() + } + } + ) + + it( + 'fences pending Kernel activation until its accepted Native effect settles', + { timeout: 30_000 }, + async () => { + const entered = Promise.withResolvers() + const released = Promise.withResolvers() + const f = await fixture({ + resumeConfiguration: async () => { + entered.resolve() + await released.promise + } + }) + try { + await f.connection.openProfile(f.options) + await f.connection.configure({ operation: 'initialize' }) + await f.assembleKernel() + const activation = Promise.allSettled([f.connection.startKernel()]) + await entered.promise + await expect(f.connection.status()).resolves.toMatchObject({ + phase: 'starting', + businessReady: false + }) + const queued = Promise.allSettled([ + f.invokeRoute('providers.list', {}, createRendererRouteContext(42, 99)) + ]) + await f.connection.beginShutdown() + expect((await queued)[0]).toMatchObject({ + status: 'rejected', + reason: { message: 'Core is stopping' } + }) + const stopping = f.connection.stop() + await expect.poll(async () => (await f.connection.status()).phase).toBe('stopping') + await expect( + initializeCoreProfile({ ...f.options, host: f.host, crypto: f.crypto }) + ).rejects.toMatchObject({ name: 'CoreProfileOwnershipUnavailableError' }) + released.resolve() + expect((await activation)[0].status).toBe('rejected') + await expect(stopping).resolves.toMatchObject({ phase: 'stopped', businessReady: false }) + expect(existsSync(path.join(f.options.userDataDirectory, 'local-control.json'))).toBe(false) + const reopened = await initializeCoreProfile({ + ...f.options, + host: f.host, + crypto: f.crypto + }) + reopened.closeProfile() + } finally { + released.resolve() + } + } + ) + + it( + 'applies proxy changes to SDK HTTP and retires current and draining connections', + { timeout: 30_000 }, + async () => { + const sockets = new Set() + const servers: ReturnType[] = [] + cleanups.push(async () => { + for (const socket of sockets) socket.destroy() + await Promise.all( + servers.map((server) => new Promise((resolve) => server.close(() => resolve()))) + ) + }) + let hold: ((response: ServerResponse) => void) | undefined + const respond = (response: ServerResponse) => { + response.writeHead(200, { 'content-type': 'application/json' }) + response.end(JSON.stringify({ data: [{ id: 'core-http-model', object: 'model' }] })) + } + const target = createServer((_request, response) => + hold ? hold(response) : respond(response) + ) + const listen = async (server: ReturnType) => { + servers.push(server) + server.on('connection', (socket) => { + sockets.add(socket) + socket.once('close', () => sockets.delete(socket)) + }) + await new Promise((resolve) => server.listen(0, '127.0.0.1', resolve)) + return (server.address() as AddressInfo).port + } + const targetPort = await listen(target) + const authorizations: Array<{ proxy: number; value: string | undefined }> = [] + const proxyUrl = async (proxy: number) => { + const server = createServer((request, response) => { + authorizations.push({ proxy, value: request.headers['proxy-authorization'] }) + if (hold) hold(response) + else respond(response) + }) + server.on('connect', (request, client, head) => { + authorizations.push({ proxy, value: request.headers['proxy-authorization'] }) + const upstream = connect(targetPort, '127.0.0.1', () => { + client.write('HTTP/1.1 200 Connection Established\r\n\r\n') + upstream.write(head) + upstream.pipe(client) + client.pipe(upstream) + }) + client.on('error', () => upstream.destroy()) + upstream.on('error', () => client.destroy()) + client.once('close', () => upstream.destroy()) + }) + return `http://user%40example:pass%3Aword@127.0.0.1:${await listen(server)}` + } + const primary = await proxyUrl(1) + const secondary = await proxyUrl(2) + let snapshot = { version: 0, proxyUrl: null as string | null, noProxy: '' } + const f = await fixture({ + getProxySnapshot: async () => snapshot, + applyProxy: async (mode, url, resolve) => { + if (resolve) + snapshot = { + version: snapshot.version + 1, + proxyUrl: mode === 'custom' ? url : null, + noProxy: 'localhost,127.0.0.1' + } + return mode === 'system' + } + }) + await f.connection.openProfile(f.options) + await f.connection.configure({ operation: 'initialize' }) + await f.assembleKernel() + await f.connection.startKernel() + const route = (name: string, input: unknown) => + f.invokeRoute(name, input, createRendererRouteContext(42, 99)) + const refresh = (providerId: string) => route('providers.refreshModels', { providerId }) + for (const [id, host] of [ + ['proxied', 'provider.invalid'], + ['bypassed', '127.0.0.1'] + ]) { + await route('providers.add', { + provider: { + id, + name: id, + apiType: 'openai', + apiKey: 'fixture-key', + baseUrl: `http://${host}:${targetPort}/v1`, + enable: false, + custom: true + } + }) + } + await route('config.setCustomProxyUrl', { url: primary }) + await route('config.setProxyMode', { mode: 'custom' }) + await expect(refresh('proxied')).resolves.toEqual({ refreshed: true }) + expect(authorizations).toContainEqual({ + proxy: 1, + value: `Basic ${Buffer.from('user@example:pass:word').toString('base64')}` + }) + const proxyRequests = authorizations.length + await expect(refresh('bypassed')).resolves.toEqual({ refreshed: true }) + expect(authorizations).toHaveLength(proxyRequests) + + const entered = Promise.withResolvers() + const responses: ServerResponse[] = [] + hold = (response) => { + responses.push(response) + entered.resolve() + } + const pending = refresh('proxied') + await entered.promise + await route('config.setCustomProxyUrl', { url: secondary }) + hold = undefined + responses.forEach(respond) + await expect(pending).resolves.toEqual({ refreshed: true }) + await expect(refresh('proxied')).resolves.toEqual({ refreshed: true }) + expect(authorizations.some((entry) => entry.proxy === 2)).toBe(true) + await route('config.setProxyMode', { mode: 'none' }) + const beforeDirect = authorizations.length + await expect(refresh('bypassed')).resolves.toEqual({ refreshed: true }) + expect(authorizations).toHaveLength(beforeDirect) + + await route('config.setProxyMode', { mode: 'custom' }) + responses.length = 0 + hold = (response) => { + responses.push(response) + } + const retiredRequest = refresh('proxied').catch((error: unknown) => error) + await vi.waitFor(() => expect(responses).toHaveLength(1)) + await route('config.setCustomProxyUrl', { url: primary }) + const currentRequest = refresh('bypassed').catch((error: unknown) => error) + await vi.waitFor(() => expect(responses).toHaveLength(2)) + const closed = responses.map( + (response) => new Promise((resolve) => response.once('close', resolve)) + ) + await expect(f.connection.stop()).resolves.toMatchObject({ phase: 'stopped' }) + await Promise.all(closed) + expect(await retiredRequest).toBeInstanceOf(Error) + expect(await currentRequest).toBeInstanceOf(Error) + const reopened = await initializeCoreProfile({ ...f.options, host: f.host, crypto: f.crypto }) + reopened.closeProfile() + } + ) + + it( + 'holds the actual writer in Core while Memory and admission remain mounted', + { timeout: 30_000 }, + async () => { + const f = await fixture({ detached: true }) + const status = await f.connection.openProfile(f.options) + expect(status).toMatchObject({ phase: 'profile-open', businessReady: false }) + expect(status.pid).not.toBe(process.pid) + expect(f.host.setLoggingEnabled).toHaveBeenCalledOnce() + await f.connection.configure({ operation: 'initialize' }) + await expect( + f.connection.configure({ + operation: 'update', + input: { + changes: [ + { key: 'notificationsEnabled', value: false }, + { key: 'fontSizeLevel', value: 4 } + ] + } + }) + ).resolves.toMatchObject({ values: { notificationsEnabled: false, fontSizeLevel: 4 } }) + expect(f.nativeSnapshots.at(-1)).toMatchObject({ + ownerId: f.connection.payloads.config.generation, + notificationsEnabled: false + }) + const bounds = { x: 20, y: 40, dockSide: 'left' as const } + await f.connection.configure({ operation: 'floatingBounds', bounds }) + expect(f.nativeSnapshots.at(-1)?.floatingButtonBounds).toEqual(bounds) + expect(f.publishEvent).toHaveBeenCalledWith( + 'settings.changed', + expect.objectContaining({ + changedKeys: ['notificationsEnabled'], + values: { notificationsEnabled: false } + }) + ) + await expect( + f.connection.configure({ + operation: 'read', + input: { + keys: ['notificationsEnabled', 'fontSizeLevel'] + } + }) + ).resolves.toMatchObject({ values: { notificationsEnabled: false, fontSizeLevel: 4 } }) + expect( + statSync(path.join(f.options.userDataDirectory, 'app_db', 'agent.db')).size + ).toBeGreaterThan(0) + await expect( + initializeCoreProfile({ ...f.options, host: f.host, crypto: f.crypto }) + ).rejects.toMatchObject({ name: 'CoreProfileOwnershipUnavailableError' }) + await f.runtime.startMemory({ + generation: randomUUID(), + memoryDbDir: path.join(f.directory, 'memory'), + host: { appPath: process.cwd(), userDataPath: f.directory, isPackaged: false } + }) + expect(await f.connection.status()).toMatchObject({ pid: status.pid, phase: 'profile-open' }) + await expect(f.runtime.request('snapshot', [])).resolves.toHaveProperty('admission') + await expect(f.connection.stop()).resolves.toMatchObject({ phase: 'stopped' }) + expect(f.runtime.memoryAvailable).toBe(true) + const reopened = await initializeCoreProfile({ ...f.options, host: f.host, crypto: f.crypto }) + expect(reopened.settingsStore.get('notificationsEnabled')).toBe(false) + expect(reopened.settingsStore.get('fontSizeLevel')).toBe(4) + expect(reopened.settingsStore.get('floatingButtonBounds')).toEqual(bounds) + reopened.closeProfile() + } + ) + + it( + 'keeps ownership until a pending Native startup effect actually settles', + { timeout: 30_000 }, + async () => { + const entered = Promise.withResolvers() + const release = Promise.withResolvers() + const f = await fixture({ + available: async () => { + entered.resolve() + return await release.promise + } + }) + const opening = f.connection.openProfile(f.options) + const rejected = expect(opening).rejects.toThrow() + try { + await entered.promise + const stopping = f.connection.stop() + await expect.poll(async () => (await f.connection.status()).phase).toBe('stopping') + await expect( + initializeCoreProfile({ ...f.options, host: f.host, crypto: f.crypto }) + ).rejects.toMatchObject({ name: 'CoreProfileOwnershipUnavailableError' }) + release.resolve(false) + await rejected + await expect(stopping).resolves.toMatchObject({ phase: 'stopped' }) + const reopened = await initializeCoreProfile({ + ...f.options, + host: f.host, + crypto: f.crypto + }) + reopened.closeProfile() + } finally { + release.resolve(false) + } + } + ) + + it( + 'keeps the profile until the Browser owner retires without Agent Browser calls', + { timeout: 30_000 }, + async () => { + const entered = Promise.withResolvers() + const release = Promise.withResolvers() + const f = await fixture({ + retireBrowser: async () => { + entered.resolve() + await release.promise + } + }) + try { + await f.connection.openProfile(f.options) + await f.connection.configure({ operation: 'initialize' }) + await f.assembleKernel() + const stopping = f.connection.stop() + await entered.promise + await expect( + initializeCoreProfile({ ...f.options, host: f.host, crypto: f.crypto }) + ).rejects.toMatchObject({ name: 'CoreProfileOwnershipUnavailableError' }) + release.resolve() + await expect(stopping).resolves.toMatchObject({ phase: 'stopped' }) + const reopened = await initializeCoreProfile({ + ...f.options, + host: f.host, + crypto: f.crypto + }) + reopened.closeProfile() + } finally { + release.resolve() + } + } + ) + + it( + 'drains a real Native configuration receipt before retiring the profile', + { timeout: 30_000 }, + async () => { + const entered = Promise.withResolvers() + const release = Promise.withResolvers() + const f = await fixture({ + publishSnapshot: async () => { + entered.resolve() + await release.promise + } + }) + await f.connection.openProfile(f.options) + const writing = f.connection.configure({ + operation: 'update', + input: { + changes: [{ key: 'notificationsEnabled', value: false }] + } + }) + const settled = Promise.allSettled([writing]) + try { + await entered.promise + expect(f.publishEvent).not.toHaveBeenCalled() + const stopping = f.connection.stop() + await expect.poll(async () => (await f.connection.status()).phase).toBe('stopping') + await expect( + initializeCoreProfile({ ...f.options, host: f.host, crypto: f.crypto }) + ).rejects.toMatchObject({ name: 'CoreProfileOwnershipUnavailableError' }) + release.resolve() + await settled + await expect(stopping).resolves.toMatchObject({ phase: 'stopped' }) + const reopened = await initializeCoreProfile({ + ...f.options, + host: f.host, + crypto: f.crypto + }) + expect(reopened.settingsStore.get('notificationsEnabled')).toBe(false) + reopened.closeProfile() + } finally { + release.resolve() + } + } + ) + + it( + 'coalesces native observations and reserves shutdown with ordinary calls saturated', + { timeout: 30_000 }, + async () => { + const pause = () => ({ + entered: Promise.withResolvers(), + release: Promise.withResolvers() + }) + let receipt = pause() + let isDark = false + const refreshTheme = vi.fn(async (_isDark: boolean) => { + const current = receipt + current.entered.resolve() + await current.release.promise + }) + const f = await fixture({ + refreshTheme, + readTheme: async () => ({ theme: 'system', isDark }) + }) + await f.connection.openProfile(f.options) + try { + const observing = f.connection.observeSystemTheme() + const observed = Promise.allSettled([observing]) + await receipt.entered.promise + const ordinary = Array.from({ length: CORE_ORDINARY_CALLS }, () => + f.connection.configure({ operation: 'read', input: { keys: ['fontFamily'] } }) + ) + const settled = Promise.allSettled(ordinary) + expect(f.connection.observeSystemTheme()).toBe(observing) + isDark = true + await expect(f.connection.status()).resolves.toMatchObject({ phase: 'profile-open' }) + receipt.release.resolve() + await expect(observing).resolves.toBeUndefined() + await observed + await settled + expect(refreshTheme).toHaveBeenLastCalledWith(true) + expect(f.publishEvent).toHaveBeenLastCalledWith( + 'config.systemTheme.changed', + expect.objectContaining({ isDark: true }) + ) + + receipt = pause() + const finalObservation = Promise.allSettled([f.connection.observeSystemTheme()]) + await receipt.entered.promise + const queued = Promise.allSettled( + Array.from({ length: CORE_ORDINARY_CALLS }, () => + f.connection.configure({ operation: 'read', input: { keys: ['fontFamily'] } }) + ) + ) + const stopping = f.connection.stop() + await expect.poll(async () => (await f.connection.status()).phase).toBe('stopping') + await expect( + initializeCoreProfile({ ...f.options, host: f.host, crypto: f.crypto }) + ).rejects.toMatchObject({ name: 'CoreProfileOwnershipUnavailableError' }) + receipt.release.resolve() + await finalObservation + await queued + await expect(stopping).resolves.toMatchObject({ phase: 'stopped' }) + const reopened = await initializeCoreProfile({ + ...f.options, + host: f.host, + crypto: f.crypto + }) + reopened.closeProfile() + } finally { + receipt.release.resolve() + } + } + ) + + it( + 'fences an unknown helper event through its reserved lane while ordinary work is saturated', + { timeout: 30_000 }, + async () => { + const entered = Promise.withResolvers() + const receipt = Promise.withResolvers() + const f = await fixture({ + refreshTheme: async () => { + entered.resolve() + await receipt.promise + } + }) + await f.connection.openProfile(f.options) + const observation = Promise.allSettled([f.connection.observeSystemTheme()]) + await entered.promise + const pending = Promise.allSettled( + Array.from({ length: CORE_ORDINARY_CALLS }, () => + f.connection.configure({ operation: 'read', input: { keys: ['fontFamily'] } }) + ) + ) + try { + await expect( + f.connection.deliverNativeEvents([ + { + id: randomUUID(), + sequence: 1, + pid: null, + event: { type: 'message', message: Buffer.alloc(32 * 1024) } + } + ]) + ).rejects.toThrow(/Unknown or out-of-order Native helper event|Core helpers owner failed/) + await expect(f.connection.status()).rejects.toThrow() + await expect( + initializeCoreProfile({ ...f.options, host: f.host, crypto: f.crypto }) + ).rejects.toMatchObject({ name: 'CoreProfileOwnershipUnavailableError' }) + } finally { + receipt.resolve() + await observation + await pending + await expect(f.connection.stop()).resolves.toMatchObject({ phase: 'stopped' }) + await f.connection.closeAfterHostExit() + } + const reopened = await initializeCoreProfile({ ...f.options, host: f.host, crypto: f.crypto }) + reopened.closeProfile() + } + ) + + it( + 'keeps an unacknowledged stop unknown until actual retirement', + { timeout: 30_000 }, + async () => { + const dropped = Promise.withResolvers() + let stopId = 0 + const f = await fixture({ + transport: (runtime) => ({ + stop: () => runtime.stop(), + connectCore: (config, listener) => { + const send = runtime.connectCore(config, { + ...listener, + message: (frame) => { + if (frame.type === 'coreReply' && frame.id === stopId) { + dropped.resolve() + return + } + if (frame.type === 'coreStopped') dropped.reject(new Error(frame.message)) + listener.message(frame) + } + }) + return (request) => { + if (request.type === 'coreCall' && request.method === 'stop') stopId = request.id + send(request) + } + } + }) + }) + await f.connection.openProfile(f.options) + vi.useFakeTimers({ toFake: ['setTimeout', 'clearTimeout'] }) + try { + const stopped = expect(f.connection.stop()).rejects.toMatchObject({ + name: 'CoreOperationOutcomeUnknownError' + }) + await dropped.promise + await vi.advanceTimersByTimeAsync(30_000) + await stopped + } finally { + vi.useRealTimers() + } + expect(existsSync(f.connection.payloads.config.directory)).toBe(true) + await expect(f.connection.status()).resolves.toMatchObject({ phase: 'stopped' }) + } + ) + + it( + 'reports an internal Core failure before physical Native retirement releases the writer', + { timeout: 30_000 }, + async () => { + const failure = Promise.withResolvers() + const closing = Promise.withResolvers() + const release = Promise.withResolvers() + const onFailure = vi.fn((error: Error) => failure.resolve(error)) + const f = await fixture({ + onFailure, + onRendererDestroyed: () => { + throw new Error('Native renderer registration was refused') + }, + retireDesktop: async () => { + closing.resolve() + await release.promise + } + }) + await f.connection.openProfile(f.options) + await f.connection.configure({ operation: 'initialize' }) + await f.assembleKernel() + await f.connection.startKernel() + const mutation = Promise.allSettled([ + f.invokeRoute( + 'sessions.create', + { + agentId: 'deepchat', + providerId: 'openai', + modelId: 'gpt-4o-mini', + message: '' + }, + createRendererRouteContext(42, 99) + ) + ]) + try { + await expect(failure.promise).resolves.toMatchObject({ + name: 'CoreOwnerFailureError', + message: 'Core desktop owner failed' + }) + await closing.promise + expect(onFailure).toHaveBeenCalledOnce() + await expect(f.connection.beginShutdown()).resolves.toBeUndefined() + await expect( + initializeCoreProfile({ ...f.options, host: f.host, crypto: f.crypto }) + ).rejects.toMatchObject({ name: 'CoreProfileOwnershipUnavailableError' }) + } finally { + release.resolve() + await mutation + await f.connection.stop().catch(() => {}) + await f.connection.closeAfterHostExit() + } + const reopened = await initializeCoreProfile({ ...f.options, host: f.host, crypto: f.crypto }) + reopened.closeProfile() + } + ) + + it( + 'releases a crashed Core owner without silently adopting a replacement', + { timeout: 30_000 }, + async () => { + let send!: ReturnType + const onFailure = vi.fn() + const f = await fixture({ + onFailure, + transport: (runtime) => ({ + stop: () => runtime.stop(), + connectCore: (config, listener) => { + send = runtime.connectCore(config, listener) + return send + } + }) + }) + const status = await f.connection.openProfile(f.options) + const parent = Number( + (process.platform === 'win32' + ? execFileSync( + 'powershell.exe', + [ + '-NoProfile', + '-NonInteractive', + '-Command', + `(Get-CimInstance Win32_Process -Filter 'ProcessId = ${status.pid}').ParentProcessId` + ], + { encoding: 'utf8' } + ) + : execFileSync('ps', ['-o', 'ppid=', '-p', String(status.pid)], { encoding: 'utf8' }) + ).trim() + ) + expect(parent).toBe(f.runtime.identity?.hostPid) + process.kill(status.pid, 'SIGKILL') + await expect + .poll(async () => { + try { + await f.connection.status() + return false + } catch { + return true + } + }) + .toBe(true) + await expect( + CoreRuntimeConnection.connect({ + runtime: f.runtime, + temporaryDirectory: f.directory, + native: createCoreProfileNative(f.crypto, f.host) + }) + ).rejects.toMatchObject({ name: 'CoreGenerationAlreadyAdoptedError' }) + expect(onFailure).toHaveBeenCalledOnce() + send({ + type: 'coreCall', + id: 999, + method: 'status', + args: await f.connection.payloads.encode(undefined) + }) + await expect(f.runtime.request('snapshot', [])).resolves.toHaveProperty('admission') + const reopened = await initializeCoreProfile({ ...f.options, host: f.host, crypto: f.crypto }) + reopened.closeProfile() + } + ) +}) diff --git a/test/main/harness/memoryCapability.test.ts b/test/main/harness/memoryCapability.test.ts new file mode 100644 index 0000000000..a857665228 --- /dev/null +++ b/test/main/harness/memoryCapability.test.ts @@ -0,0 +1,74 @@ +import { load } from '@arcships/rutis/testing' +import { expect, it } from 'vitest' +import memoryPlugin, { type MemoryCapability } from '../../../src/harness/plugins/memory' +import { + createFakeRepository, + enabledConfig, + FakeDirectiveRepository, + FakeVectorStore +} from '../memory/support/memoryFakes' + +it('Memory unload fences admitted extraction and cannot reopen its retired attachment', async () => { + const plugin = await load(memoryPlugin, { strict: false }) + const capability = plugin.service('deepchatMemoryCapability') + const repository = createFakeRepository() + const request = Promise.withResolvers() + const requested = Promise.withResolvers() + const fenced = Promise.withResolvers() + const deps = { + repository, + directiveRepository: new FakeDirectiveRepository(), + resolveAgentConfig: () => enabledConfig, + executeWithRateLimit: async () => {}, + getEmbeddings: async () => [], + getDimensions: async () => ({ data: { dimensions: 4, normalized: false } }), + generateText: async () => { + requested.resolve() + return await request.promise + }, + createVectorStore: async () => new FakeVectorStore(), + resetVectorStore: async () => {}, + markVectorStoreQuarantined() {} + } + const service = capability.open(deps) + const token = service.captureExecutionToken('a') + const extraction = service.extractAndStore({ + agentId: 'a', + spanText: 'User: I prefer Redis', + model: { providerId: 'p', modelId: 'm' } + }) + capability.bindIngestion({ + afterTurnSettled() {}, + afterCompactionApplyReturned() {}, + resumeIngestion() {}, + async drainAndFence() { + fenced.resolve() + await extraction + return { timedOut: false, pendingSessions: [] } + } + }) + try { + await requested.promise + expect(service.canContinueExecution(token)).toBe(true) + const unloading = plugin.unload() + await fenced.promise + expect(service.canContinueExecution(token)).toBe(false) + request.resolve('[{"kind":"semantic","content":"user prefers Redis","importance":0.8}]') + expect(await extraction).toMatchObject({ ok: false }) + await unloading + expect(repository.countByAgent('a')).toBe(0) + expect(() => capability.open(deps)).toThrow('cannot reopen') + expect(() => capability.start()).toThrow('unavailable') + const replacement = await load(memoryPlugin, { strict: false }) + try { + const next = replacement.service('deepchatMemoryCapability').open(deps) + expect(next.canContinueExecution(token)).toBe(false) + expect(next.canContinueExecution(next.captureExecutionToken('a'))).toBe(true) + } finally { + await replacement.unload() + } + } finally { + request.resolve('[]') + await plugin.unload() + } +}) diff --git a/test/main/harness/memoryVectorOwner.test.ts b/test/main/harness/memoryVectorOwner.test.ts new file mode 100644 index 0000000000..989524da99 --- /dev/null +++ b/test/main/harness/memoryVectorOwner.test.ts @@ -0,0 +1,341 @@ +import { randomUUID } from 'node:crypto' +import fs from 'node:fs' +import os from 'node:os' +import path from 'node:path' +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' +import { MemoryVectorOwner } from '../../../src/harness/memory-vector' +import { MemoryVectorStore } from '@/memory/infra/memoryVectorStore' +import { MEMORY_VECTOR_OWNER_FILE } from '@/memory/infra/memoryVectorStorePaths' +import { + decodeMemoryVector, + encodeMemoryVector, + MemoryVectorCommandSchema, + type MemoryVectorNativeCommand, + type MemoryVectorConfig +} from '@shared/harness/memory-vector' + +const actualFs = await vi.importActual('node:fs') +let directory: string +let config: MemoryVectorConfig +let owner: MemoryVectorOwner +let resource: string +type Command = MemoryVectorNativeCommand extends infer T + ? T extends MemoryVectorNativeCommand + ? Omit & { resource?: string } + : never + : never +const command = (input: Command) => + owner.command({ + resource, + ...input, + generation: config.generation, + operation: randomUUID() + }) +const open = (agentId = 'agent-a', id = resource) => + command({ + method: 'open', + resource: id, + agentId, + dimensions: 2, + embedding: { providerId: 'fixture', modelId: 'fixture' } + }) +const upsert = (vectors: number[][]) => + command({ + method: 'upsert', + upload: randomUUID(), + bytes: vectors.length * 8, + offset: 0, + memoryIds: vectors.map((_, i) => `m${i}`), + data: Buffer.concat(vectors.map((vector) => encodeMemoryVector(vector, 2))) + }) + +beforeEach(() => { + for (const method of [ + 'existsSync', + 'readdirSync', + 'mkdirSync', + 'mkdtempSync', + 'rmSync', + 'renameSync', + 'writeFileSync' + ] as const) { + vi.mocked(fs[method]).mockImplementation(actualFs[method] as never) + } + directory = fs.mkdtempSync(path.join(os.tmpdir(), 'deepchat-vector-owner-')) + config = { + generation: randomUUID(), + memoryDbDir: directory, + host: { appPath: path.resolve('.'), userDataPath: directory, isPackaged: false } + } + resource = randomUUID() + owner = new MemoryVectorOwner(config) +}) + +afterEach(async () => { + await owner.dispose() + vi.restoreAllMocks() + actualFs.rmSync(directory, { recursive: true, force: true }) +}) + +describe('native Memory vector ownership', () => { + it('orders mutations behind all native parts and aborts only the known upload', async () => { + await open() + const upload = randomUUID() + const part = { method: 'upsert' as const, upload, memoryIds: ['m0'], bytes: 8 } + await command({ ...part, offset: 0, data: Buffer.alloc(4) }) + const deleting = command({ method: 'delete', memoryIds: ['m0'] }) + let deleted = false + void deleting.then(() => { + deleted = true + }) + await new Promise((resolve) => setImmediate(resolve)) + expect(deleted).toBe(false) + await command({ method: 'abort', upload: randomUUID() }) + await expect(upsert([[1, 0]])).rejects.toThrow('busy') + await expect(command({ ...part, offset: 0, data: Buffer.alloc(4) })).rejects.toThrow('busy') + await command({ + ...part, + memoryIds: [...part.memoryIds], + offset: 4, + data: encodeMemoryVector([1], 1) + }) + await deleting + expect(await command({ method: 'list', afterId: null, limit: 10 })).toEqual([]) + await command({ ...part, offset: 0, data: Buffer.alloc(4) }) + await command({ method: 'abort', upload }) + await upsert([[1, 0]]) + await command({ ...part, offset: 0, data: Buffer.alloc(4) }) + await command({ method: 'close' }) + resource = randomUUID() + await open() + expect( + await command({ + method: 'query', + vector: encodeMemoryVector([1, 0], 2).toString('base64'), + topK: 1 + }) + ).toEqual([{ memoryId: 'm0', distance: 0 }]) + }) + + it('publishes one complete validated batch and preserves vector-only recall across close', async () => { + await open() + await expect( + command({ + method: 'upsert', + upload: randomUUID(), + bytes: 8, + offset: 0, + memoryIds: ['m0'], + data: new Uint8Array(new SharedArrayBuffer(8)) + }) + ).rejects.toThrow('shape') + await expect( + command({ + method: 'upsert', + upload: randomUUID(), + bytes: 8, + offset: 0, + memoryIds: ['m0', 'm1'], + data: encodeMemoryVector([1, 0], 2) + }) + ).rejects.toThrow('shape') + expect(await command({ method: 'list', afterId: null, limit: 512 })).toEqual([]) + const invalid = Buffer.alloc(16) + invalid.writeFloatLE(Infinity) + await expect( + command({ + method: 'upsert', + upload: randomUUID(), + bytes: 16, + offset: 0, + memoryIds: ['m0', 'm1'], + data: invalid + }) + ).rejects.toThrow('non-finite') + expect(await command({ method: 'list', afterId: null, limit: 512 })).toEqual([]) + await upsert([ + [1, 0], + [0, 1] + ]) + const query = { + method: 'query', + vector: encodeMemoryVector([1, 0], 2).toString('base64'), + topK: 2 + } as const + expect(await command(query)).toEqual([ + { memoryId: 'm0', distance: 0 }, + { memoryId: 'm1', distance: 1 } + ]) + await command({ method: 'close' }) + resource = randomUUID() + await open() + expect(await command(query)).toEqual([ + { memoryId: 'm0', distance: 0 }, + { memoryId: 'm1', distance: 1 } + ]) + expect(await command({ method: 'queryByMemoryId', memoryId: 'm0', topK: 2 })).toEqual([ + { memoryId: 'm1', distance: 1 } + ]) + await command({ method: 'delete', memoryIds: ['m0'] }) + expect(await command({ method: 'list', afterId: null, limit: 512 })).toEqual(['m1']) + await expect( + owner.command({ + generation: randomUUID(), + operation: randomUUID(), + resource, + method: 'close' + }) + ).rejects.toThrow('generation') + await expect(command({ method: 'delete', memoryIds: Array(513).fill('m1') })).rejects.toThrow() + await expect(command({ ...query, resource: randomUUID() })).rejects.toThrow('unavailable') + }) + + it('retains a late native open until revocation has closed the returned handle', async () => { + const gate = Promise.withResolvers() + const create = MemoryVectorStore.create.bind(MemoryVectorStore) + vi.spyOn(MemoryVectorStore, 'create').mockImplementationOnce(async (...args) => { + await gate.promise + return create(...args) + }) + const opening = expect(open()).rejects.toThrow('revoked') + const closing = command({ method: 'close' }) + let closed = false + void closing.then(() => { + closed = true + }) + await new Promise((resolve) => setImmediate(resolve)) + expect(closed).toBe(false) + await expect(open('agent-a', randomUUID())).rejects.toThrow('owner') + gate.resolve() + await Promise.all([opening, closing]) + resource = randomUUID() + await open() + }) + + it('supports more than eight agents while limiting active native opens to two', async () => { + const gate = Promise.withResolvers() + const create = MemoryVectorStore.create.bind(MemoryVectorStore) + let active = 0 + let peak = 0 + vi.spyOn(MemoryVectorStore, 'create').mockImplementation(async (...args) => { + active++ + peak = Math.max(active, peak) + try { + await gate.promise + return await create(...args) + } finally { + active-- + } + }) + const opens = Array.from({ length: 9 }, (_, index) => open(`agent-${index}`, randomUUID())) + const completed = Promise.all(opens) + void completed.catch(() => undefined) + try { + await expect.poll(() => active).toBe(2) + } finally { + gate.resolve() + } + await completed + expect(peak).toBe(2) + }) + + it('waits for an executing write before reset can remove files', async () => { + await open() + const gate = Promise.withResolvers() + const started = Promise.withResolvers() + const upsert = MemoryVectorStore.prototype.upsertPacked + vi.spyOn(MemoryVectorStore.prototype, 'upsertPacked').mockImplementation( + async function (records) { + started.resolve() + await gate.promise + return upsert.call(this, records) + } + ) + const commit = command({ + method: 'upsert', + upload: randomUUID(), + bytes: 8, + offset: 0, + memoryIds: ['m0'], + data: encodeMemoryVector([1, 0], 2) + }) + await started.promise + const reset = owner.command({ + method: 'reset', + generation: config.generation, + operation: randomUUID(), + agentId: 'agent-a' + }) + let removed = false + void reset.then(() => { + removed = true + }) + await new Promise((resolve) => setImmediate(resolve)) + expect(removed).toBe(false) + expect(actualFs.existsSync(path.join(directory, 'agent-a.v2.duckdb'))).toBe(true) + await expect(command({ method: 'queryByMemoryId', memoryId: 'm0', topK: 1 })).rejects.toThrow( + 'unavailable' + ) + gate.resolve() + await Promise.all([commit, reset]) + expect( + actualFs.readdirSync(directory).filter((name) => !name.startsWith(MEMORY_VECTOR_OWNER_FILE)) + ).toEqual([]) + expect(actualFs.existsSync(path.join(directory, MEMORY_VECTOR_OWNER_FILE))).toBe(true) + }) + + it('uses finite Float32 values with explicit little-endian byte order', () => { + const values = [0, -0, 1 / 3, -3.4028234663852886e38, 1.401298464324817e-45] + const bytes = encodeMemoryVector(values, values.length) + expect(decodeMemoryVector(bytes, values.length)).toEqual(values.map(Math.fround)) + expect(encodeMemoryVector([1], 1).toString('hex')).toBe('0000803f') + expect(() => encodeMemoryVector([1e39], 1)).toThrow('non-finite') + expect(() => encodeMemoryVector([NaN], 1)).toThrow('non-finite') + expect(() => decodeMemoryVector(Buffer.alloc(3), 1)).toThrow('length') + }) + + it('accepts canonical base64 and rejects malformed or oversized wire data', () => { + const accepts = (data: string) => + MemoryVectorCommandSchema.safeParse({ + method: 'query', + resource, + generation: config.generation, + operation: randomUUID(), + topK: 1, + vector: data + }).success + for (const data of ['', 'AA==', 'AAA=', 'AAAA', Buffer.alloc(65536).toString('base64')]) + expect(accepts(data)).toBe(true) + for (const data of ['AB==', 'AAB=', 'A===', 'AA=A', 'AA==\n', '____', 'A'.repeat(262144)]) + expect(accepts(data)).toBe(false) + }) + + it('accepts only bounded application upload bytes', () => { + const accepts = (data: unknown) => + MemoryVectorCommandSchema.safeParse({ + method: 'chunk', + resource, + generation: config.generation, + operation: randomUUID(), + upload: randomUUID(), + offset: 0, + data + }).success + for (const data of [Buffer.alloc(1), new Uint8Array(240 * 1024)]) + expect(accepts(data)).toBe(true) + for (const data of ['', 'AAAA', [0, 0, 0], new Uint8Array(0), Buffer.alloc(240 * 1024 + 1)]) + expect(accepts(data)).toBe(false) + expect( + MemoryVectorCommandSchema.safeParse({ + method: 'begin', + resource, + generation: config.generation, + operation: randomUUID(), + upload: randomUUID(), + memoryIds: ['m0'], + bytes: 65540, + data: Buffer.alloc(65537) + }).success + ).toBe(false) + }) +}) diff --git a/test/main/harness/memoryVectorRuntime.test.ts b/test/main/harness/memoryVectorRuntime.test.ts new file mode 100644 index 0000000000..7cab8f5a49 --- /dev/null +++ b/test/main/harness/memoryVectorRuntime.test.ts @@ -0,0 +1,437 @@ +import { randomUUID } from 'node:crypto' +import { DuckDBInstance } from '@duckdb/node-api' +import { createRequire } from 'node:module' +import { execFileSync } from 'node:child_process' +import fs from 'node:fs' +import os from 'node:os' +import path from 'node:path' +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' +import { parse } from 'yaml' +import { HarnessRuntime } from '@/harness/runtime' +import { RutisAgentInvocationAdmission } from '@/harness/admission' +import { RutisMemoryVectorStores } from '@/harness/memory-vector' +import { MEMORY_VECTOR_OWNER_FILE } from '@/memory/infra/memoryVectorStorePaths' +import { + encodeMemoryVector, + MEMORY_VECTOR_CHUNK_BYTES, + MEMORY_VECTOR_INITIAL_CHUNK_BYTES, + MEMORY_VECTOR_UPLOAD_BYTES, + type MemoryVectorCommand +} from '@shared/harness/memory-vector' + +const { mkdtempSync, mkdirSync, rmSync, readdirSync, existsSync, writeFileSync, readFileSync } = + await vi.importActual('node:fs') +const require = createRequire(import.meta.url) +const builderRequire = createRequire(require.resolve('electron-builder')) +const { getFileMatchers, copyFiles } = builderRequire('app-builder-lib/out/fileMatcher.js') +const active: Array<{ runtime: HarnessRuntime; stores: RutisMemoryVectorStores; root: string }> = [] + +async function create(memoryDbDir?: string) { + const root = mkdtempSync(path.join(os.tmpdir(), 'deepchat-memory-runtime-')) + const resources = + process.platform === 'darwin' + ? path.join(root, 'DeepChat.app', 'Contents', 'Resources') + : path.join(root, 'resources') + const directory = path.join(resources, 'app.asar.unpacked', 'harness') + const config = parse(readFileSync(path.resolve('electron-builder.yml'), 'utf8')) + const matchers = getFileMatchers( + { + extraResources: config.extraResources.filter((resource: { from?: string }) => + resource.from?.startsWith('./out/harness/') + ) + }, + 'extraResources', + resources, + { + defaultSrc: path.resolve('.'), + globalOutDir: path.resolve('dist'), + customBuildOptions: {}, + macroExpander: (value: string) => value + } + ) + await copyFiles(matchers) + const { validateHarnessDuckDbNativePackage } = await import('../../../scripts/afterPack.js') + await validateHarnessDuckDbNativePackage({ + appOutDir: root, + electronPlatformName: process.platform, + arch: process.arch + }) + expect( + existsSync( + path.join( + directory, + 'node_modules', + '@duckdb', + `node-bindings-${process.platform}-${process.arch}` + ) + ) + ).toBe(false) + const runtime = await HarnessRuntime.start({ + directory, + electronExecutable: require('electron'), + capacity: 1, + maxPending: 4, + observe: () => {} + }) + const stores = new RutisMemoryVectorStores(runtime, { + memoryDbDir: memoryDbDir ?? path.join(root, 'memory'), + host: { appPath: path.resolve('.'), userDataPath: root, isPackaged: false } + }) + active.push({ runtime, stores, root }) + const admission = RutisAgentInvocationAdmission.create({ runtime, capacity: 1, maxPending: 4 }) + return { runtime, stores, root, admission } +} + +beforeEach(() => { + vi.mocked(fs.existsSync).mockImplementation(existsSync) + vi.mocked(fs.mkdirSync).mockImplementation(mkdirSync) + vi.mocked(fs.writeFileSync).mockImplementation(writeFileSync) +}) + +afterEach(async () => { + for (const { runtime, stores } of active) { + await stores.dispose() + await runtime.stop() + } + for (const { root } of active) rmSync(root, { recursive: true, force: true }) + active.length = 0 + vi.restoreAllMocks() +}) + +describe('Rutis native Memory runtime', () => { + it('publishes a valid batch larger than one internal native message part', async () => { + const { runtime, stores, root } = await create() + const dimensions = 49152 + const store = await stores.createVectorStore( + 'wide', + { providerId: 'fixture', modelId: 'fixture' }, + dimensions + ) + const records = Array.from({ length: 45 }, (_, row) => ({ + memoryId: `wide-${String(row).padStart(2, '0')}`, + embedding: Array.from({ length: dimensions }, (_, column) => + column === row ? 1 : column === dimensions - 1 ? -0.25 : 0 + ) + })) + await store.upsert(records) + expect(await store.listMemoryIds(null, 50)).toEqual(records.map((record) => record.memoryId)) + await store.close() + const instance = await DuckDBInstance.create(path.join(root, 'memory', 'wide.v2.duckdb')) + try { + const connection = await instance.connect() + try { + const result = await connection.runAndReadAll( + "SELECT embedding[45] AS anchor, embedding[49152] AS fraction, list_sum(embedding)::FLOAT AS total FROM memory_vector WHERE memory_id = 'wide-44'" + ) + expect(result.getRowObjectsJson()).toEqual([{ anchor: 1, fraction: -0.25, total: 0.75 }]) + } finally { + connection.closeSync() + } + } finally { + instance.closeSync() + } + const reopened = await stores.createVectorStore( + 'wide', + { providerId: 'fixture', modelId: 'fixture' }, + dimensions + ) + expect(await reopened.listMemoryIds(null, 50)).toHaveLength(45) + expect(runtime.memoryTransport.pending).toBe(0) + }) + + it('keeps staged uploads atomic and releases staging after abort, failure and close', async () => { + const { runtime, root } = await create() + const generation = randomUUID() + const resource = randomUUID() + await runtime.startMemory({ + generation, + memoryDbDir: path.join(root, 'memory'), + host: { appPath: path.resolve('.'), userDataPath: root, isPackaged: false } + }) + type Command = MemoryVectorCommand extends infer T + ? T extends MemoryVectorCommand + ? Omit + : never + : never + const send = (command: Command) => + runtime.requestMemory({ + ...command, + generation, + resource, + operation: randomUUID() + } as MemoryVectorCommand) + const open = () => + send({ + method: 'open', + agentId: 'agent', + dimensions: 2, + embedding: { providerId: 'fixture', modelId: 'fixture' } + }) + const begin = (upload: string, data?: Uint8Array) => + send({ method: 'begin', upload, memoryIds: ['memory'], bytes: 8, data }) + await open() + let upload = randomUUID() + await begin(upload, Buffer.alloc(4)) + await expect(send({ method: 'commit', upload })).rejects.toThrow('incomplete') + expect(await send({ method: 'list', afterId: null, limit: 10 })).toEqual([]) + await send({ method: 'abort', upload }) + upload = randomUUID() + const invalid = Buffer.alloc(8) + invalid.writeFloatLE(Infinity) + await begin(upload, invalid) + await expect(send({ method: 'commit', upload })).rejects.toThrow('non-finite') + expect(await send({ method: 'list', afterId: null, limit: 10 })).toEqual([]) + upload = randomUUID() + const chunk = Buffer.alloc(MEMORY_VECTOR_CHUNK_BYTES) + await send({ + method: 'begin', + upload, + memoryIds: ['memory'], + bytes: MEMORY_VECTOR_UPLOAD_BYTES, + data: Buffer.alloc(MEMORY_VECTOR_INITIAL_CHUNK_BYTES) + }) + let offset = MEMORY_VECTOR_INITIAL_CHUNK_BYTES + for (; offset + chunk.length < MEMORY_VECTOR_UPLOAD_BYTES; offset += chunk.length) + await send({ method: 'chunk', upload, offset, data: chunk }) + await expect(send({ method: 'commit', upload, offset, data: chunk })).rejects.toThrow('shape') + await begin(randomUUID()) + await send({ method: 'close' }) + await open() + upload = randomUUID() + await begin(upload, encodeMemoryVector([1, 0], 2)) + await send({ method: 'commit', upload }) + expect(await send({ method: 'list', afterId: null, limit: 10 })).toEqual(['memory']) + await send({ method: 'close' }) + }) + + it.each(['quarantine', 'corrupt-open', 'marker-failure'])( + 'fences native files by terminating only the vector process after %s', + async (failure) => { + const { runtime, stores, admission, root } = await create() + const identity = runtime.identity + const store = await stores.createVectorStore( + 'agent', + { providerId: 'fixture', modelId: 'fixture' }, + 2 + ) + await store.upsert([{ memoryId: 'existing', embedding: [1, 0] }]) + const worker = runtime.memoryIdentity! + const file = path.join(root, 'memory', 'agent.v2.duckdb') + if (failure === 'quarantine') { + stores.markVectorStoreQuarantined('agent') + expect(existsSync(`${file}.quarantine`)).toBe(true) + } else if (failure === 'marker-failure') { + vi.mocked(fs.writeFileSync).mockImplementationOnce(() => { + throw new Error('Quarantine intent cannot be persisted') + }) + expect(() => stores.markVectorStoreQuarantined('agent')).toThrow('cannot be persisted') + } else { + await store.close() + writeFileSync(file, 'invalid native database') + await expect( + stores.createVectorStore('agent', { providerId: 'fixture', modelId: 'fixture' }, 2) + ).rejects.toThrow() + } + await vi.waitFor(() => { + expect(runtime.memoryAvailable).toBe(false) + expect(() => process.kill(worker.pid, 0)).toThrow() + }) + expect(existsSync(`${file}.lease`)).toBe(true) + expect(existsSync(`${file}.quarantine`)).toBe(true) + const retained = readFileSync(file) + await expect(stores.resetVectorStore('agent')).rejects.toThrow() + await expect( + stores.createVectorStore('agent', { providerId: 'fixture', modelId: 'fixture' }, 2) + ).rejects.toThrow() + expect(readFileSync(file)).toEqual(retained) + await admission.run({ ownerId: 'after-native-quarantine' }, async () => {}) + expect(runtime.identity).toEqual(identity) + expect(runtime.memoryIdentity?.pid).toBe(worker.pid) + } + ) + it('rejects oversized results without abandoning the request or admission owner', async () => { + const { runtime, stores, admission } = await create() + const store = await stores.createVectorStore( + 'large-ids', + { providerId: 'fixture', modelId: 'fixture' }, + 2 + ) + for (let offset = 0; offset < 800; offset += 50) { + await store.upsert( + Array.from({ length: 50 }, (_, index) => ({ + memoryId: `${'x'.repeat(1000)}${offset + index}`, + embedding: [1, 0] + })) + ) + } + await expect(store.query([1, 0], { topK: 800 })).rejects.toMatchObject({ + name: 'MemoryVectorResponseLimitError' + }) + expect(runtime.memoryTransport.pending).toBe(0) + expect(await store.query([1, 0], { topK: 1 })).toHaveLength(1) + await admission.run({ ownerId: 'after-large-result' }, async () => {}) + }) + it('fences another file owner before quarantine recovery or reset can touch live stores', async () => { + const first = await create() + const directory = path.join(first.root, 'memory') + const store = await first.stores.createVectorStore( + 'agent', + { providerId: 'fixture', modelId: 'fixture' }, + 2 + ) + await store.upsert([{ memoryId: 'existing', embedding: [1, 0] }]) + writeFileSync(path.join(directory, 'agent.v2.duckdb.quarantine'), '') + writeFileSync(path.join(directory, '.vector-owner.v2.duckdb.quarantine'), '') + const second = await create(directory) + await expect( + second.stores.createVectorStore('agent', { providerId: 'fixture', modelId: 'fixture' }, 2) + ).rejects.toMatchObject({ name: 'MemoryVectorOwnershipUnavailableError' }) + await expect(second.stores.resetVectorStore('agent')).rejects.toThrow('ownership') + expect(await store.listMemoryIds(null, 512)).toEqual(['existing']) + expect(existsSync(path.join(directory, 'agent.v2.duckdb.quarantine'))).toBe(true) + await first.stores.dispose() + await first.runtime.stop() + const replacement = await create(directory) + const recovered = await replacement.stores.createVectorStore( + 'agent', + { providerId: 'fixture', modelId: 'fixture' }, + 2 + ) + expect(recovered.isUsable()).toBe(false) + await recovered.close() + await replacement.stores.resetVectorStore('agent') + const rebuilt = await replacement.stores.createVectorStore( + 'agent', + { providerId: 'fixture', modelId: 'fixture' }, + 2 + ) + expect(await rebuilt.listMemoryIds(null, 512)).toEqual([]) + expect(existsSync(path.join(directory, 'agent.v2.duckdb.quarantine'))).toBe(false) + expect(existsSync(path.join(directory, MEMORY_VECTOR_OWNER_FILE))).toBe(true) + }) + it('lazily owns the real native store in a separate process using the standalone closure', async () => { + const { runtime, stores, root } = await create() + const identity = runtime.identity! + expect(runtime.memoryIdentity).toBeUndefined() + await stores.resetVectorStore('unused') + expect(runtime.memoryIdentity).toBeUndefined() + const store = await stores.createVectorStore( + 'agent', + { providerId: 'fixture', modelId: 'fixture' }, + 2 + ) + const worker = runtime.memoryIdentity! + expect([process.pid, identity.hostPid]).not.toContain(worker.pid) + const expectedVersions = JSON.parse( + execFileSync(require('electron'), ['-p', 'JSON.stringify(process.versions)'], { + encoding: 'utf8', + env: { ...process.env, ELECTRON_RUN_AS_NODE: '1', NODE_OPTIONS: '', NODE_PATH: '' } + }) + ) + expect(worker.modules).toBe(expectedVersions.modules) + expect(worker.node).toBe(expectedVersions.node) + await store.upsert([ + { memoryId: 'anchor', embedding: [1, 0] }, + { memoryId: 'other', embedding: [0, 1] } + ]) + expect(await store.query([1, 0], { topK: 2 })).toEqual([ + { memoryId: 'anchor', distance: 0 }, + { memoryId: 'other', distance: 1 } + ]) + await store.deleteByMemoryIds(['anchor']) + await store.close() + const reopened = await stores.createVectorStore( + 'agent', + { providerId: 'fixture', modelId: 'fixture' }, + 2 + ) + expect(await reopened.listMemoryIds(null, 512)).toEqual(['other']) + await reopened.close() + const mismatched = await stores.createVectorStore( + 'agent', + { providerId: 'fixture', modelId: 'changed' }, + 3 + ) + expect(mismatched.isUsable()).toBe(false) + await expect(mismatched.query([1, 0, 0], { topK: 1 })).rejects.toThrow('unavailable') + await mismatched.close() + const original = await stores.createVectorStore( + 'agent', + { providerId: 'fixture', modelId: 'fixture' }, + 2 + ) + expect(await original.listMemoryIds(null, 512)).toEqual(['other']) + await original.close() + await stores.resetVectorStore('agent') + expect( + readdirSync(path.join(root, 'memory')).filter( + (name) => !name.startsWith(MEMORY_VECTOR_OWNER_FILE) + ) + ).toEqual([]) + const precision = await stores.createVectorStore( + 'precision', + { providerId: 'fixture', modelId: 'fixture' }, + 5 + ) + const values = [0, -0, 1 / 3, -3.4028234663852886e38, 1.401298464324817e-45] + await precision.upsert([{ memoryId: 'float32', embedding: values }]) + await precision.close() + const instance = await DuckDBInstance.create(path.join(root, 'memory', 'precision.v2.duckdb')) + try { + const connection = await instance.connect() + try { + const rows = await connection.runAndReadAll('SELECT embedding FROM memory_vector') + expect(rows.getRowObjectsJson()).toEqual([{ embedding: values.map(Math.fround) }]) + } finally { + connection.closeSync() + } + } finally { + instance.closeSync() + } + expect(runtime.identity).toEqual(identity) + expect(runtime.memoryIdentity).toEqual(worker) + await stores.dispose() + await runtime.stop() + expect(() => process.kill(worker.pid, 0)).toThrow() + expect(() => process.kill(identity.hostPid, 0)).toThrow() + }) + + it('serves admission while chunked vector batches run and isolates native worker death', async () => { + const { runtime, stores, admission, root } = await create() + const permit = await admission.acquire({ ownerId: 'parent' }) + const queued = admission.acquire({ ownerId: 'other' }) + void queued.catch(() => undefined) + await expect.poll(async () => (await admission.snapshot()).pending).toBe(1) + const store = await stores.createVectorStore( + 'agent', + { providerId: 'fixture', modelId: 'fixture' }, + 1536 + ) + expect(await admission.snapshot()).toMatchObject({ active: 1, pending: 1 }) + const identity = runtime.identity! + const worker = runtime.memoryIdentity! + const batch = Array.from({ length: 50 }, (_, row) => ({ + memoryId: randomUUID(), + embedding: Array.from({ length: 1536 }, (_, index) => (index === row ? 1 : 0)) + })) + const uploading = store.upsert(batch) + await expect.poll(async () => (await admission.snapshot()).pending).toBe(1) + permit.release() + ;(await queued).release() + await uploading + expect(await store.listMemoryIds(null, 512)).toHaveLength(50) + process.kill(worker.pid, 'SIGKILL') + await expect.poll(() => store.isUsable()).toBe(false) + expect(existsSync(path.join(root, 'memory', 'agent.v2.duckdb.lease'))).toBe(true) + expect(() => process.kill(worker.pid, 0)).toThrow() + await expect(store.query(batch[0].embedding, { topK: 1 })).rejects.toThrow() + await expect( + stores.createVectorStore('after-crash', { providerId: 'fixture', modelId: 'fixture' }, 2) + ).rejects.toThrow() + await admission.run({ ownerId: 'after-crash' }, async () => {}) + expect(await admission.snapshot()).toMatchObject({ active: 0, pending: 0 }) + expect(runtime.identity).toEqual(identity) + expect(runtime.memoryIdentity).toEqual(worker) + expect(() => process.kill(identity.hostPid, 0)).not.toThrow() + }) +}) diff --git a/test/main/harness/runtimeCodec.test.ts b/test/main/harness/runtimeCodec.test.ts new file mode 100644 index 0000000000..08e944a06e --- /dev/null +++ b/test/main/harness/runtimeCodec.test.ts @@ -0,0 +1,51 @@ +import { createRequire } from 'node:module' +import path from 'node:path' +import { pathToFileURL } from 'node:url' +import { describe, expect, it } from 'vitest' + +const require = createRequire(import.meta.url) +const runtimeRoot = path.dirname(require.resolve('@arcships/rutis-runtime/package.json')) +const { base64Decode } = (await import( + pathToFileURL(path.join(runtimeRoot, 'src/values.mjs')).href +)) as { base64Decode(text: string): Uint8Array } + +describe('Rutis byte value contract', () => { + it('decodes exact independent byte buffers across pooled and native-part sizes', () => { + for (const size of [0, 1, 2, 3, 63, 4095, 4096, 8192, 307200, 8 * 1024 * 1024]) { + const source = Buffer.allocUnsafe(size) + for (let index = 0; index < size; index++) source[index] = index % 256 + const encoded = source.toString('base64') + const decoded = base64Decode(encoded) + const second = base64Decode(encoded) + expect(decoded.constructor).toBe(Uint8Array) + expect(Buffer.from(decoded).equals(source)).toBe(true) + expect(decoded.byteOffset).toBe(0) + expect(decoded.buffer).toBeInstanceOf(ArrayBuffer) + expect(decoded.buffer.byteLength).toBe(size) + expect(decoded.buffer).not.toBe(second.buffer) + decoded.fill(0) + expect(Buffer.from(second).equals(source)).toBe(true) + } + }) + + it('rejects malformed padding, unused bits, whitespace and non-base64 characters', () => { + for (const encoded of [ + 'A', + 'AA', + 'AAA', + 'A===', + 'AAAA=', + 'A A=', + 'AA==\n', + 'AB==', + 'AAB=', + '____', + 'é===', + '\0AAA', + '====', + 'AA=A' + ]) { + expect(() => base64Decode(encoded)).toThrow(/expected base64/) + } + }) +}) diff --git a/test/main/hook/hookService.test.ts b/test/main/hook/hookService.test.ts index c44f8b3142..cfe98d10e9 100644 --- a/test/main/hook/hookService.test.ts +++ b/test/main/hook/hookService.test.ts @@ -1,4 +1,6 @@ import { EventEmitter } from 'node:events' +import { load } from '@arcships/rutis/testing' +import hooksPlugin, { type HooksCapability } from '../../../src/harness/plugins/hooks' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' import type { HookCommandItem, @@ -215,7 +217,8 @@ const createHarness = (hooks: HookCommandItem[] = []) => { const getSession = vi.fn().mockResolvedValue(null) const service = new HookService( { getHooksNotificationsConfig, setHooksNotificationsConfig }, - { getSession } + { getSession }, + '9.8.7-hook-fixture' ) return { service, @@ -297,7 +300,8 @@ describe('HookService subscription index', () => { getHooksNotificationsConfig: () => stored, setHooksNotificationsConfig: (next) => next }, - { getSession: vi.fn().mockResolvedValue(null) } + { getSession: vi.fn().mockResolvedValue(null) }, + '9.8.7-hook-fixture' ) const snapshot = service.getConfigSnapshot() @@ -403,6 +407,71 @@ describe('HookService subscription index', () => { }) describe('HookService delivery ordering', () => { + it('plugin retirement drains children and enrichment without reviving stale references', async () => { + const plugin = await load(hooksPlugin, { strict: false }) + const capability = plugin.service('deepchatHooksCapability') + const config = { hooks: [enabledHook('a', ['PostToolUse'])] } + const setConfig = vi.fn((next: HooksNotificationsSettings) => next) + const settings = { + getHooksNotificationsConfig: () => config, + setHooksNotificationsConfig: setConfig + } + const enrichment = Promise.withResolvers() + const entered = Promise.withResolvers() + const query = { + getSession: async () => { + entered.resolve() + return await enrichment.promise + } + } + const service = capability.open(settings, query, 'hook-plugin-fixture') + const child = new FakeChild() + spawnMock.mockImplementationOnce(() => child) + let unloading: Promise | undefined + try { + await service.stop() + service.start() + expect(service.isObserved('PostToolUse')).toBe(true) + const command = service.testHookCommand('a') + service.notify({ event: 'PostToolUse', session: { sessionId: 'held' }, tool: {} }) + await entered.promise + let drained = false + unloading = plugin.unload().then(() => { + drained = true + }) + await vi.waitFor(() => expect(service.isObserved('PostToolUse')).toBe(false)) + expect(() => service.start()).toThrow('retired') + expect(() => service.updateConfig(config)).toThrow('retired') + expect(() => service.getConfigSnapshot()).toThrow('retired') + expect(() => capability.open(settings, query, 'stale')).toThrow('cannot reopen') + expect((await service.testHookCommand('a')).success).toBe(false) + enrichment.resolve(null) + await Promise.resolve() + expect(drained).toBe(false) + child.emit('close', null) + await command + await unloading + expect(setConfig).not.toHaveBeenCalled() + expect(spawnMock).toHaveBeenCalledTimes(1) + const replacement = await load(hooksPlugin, { strict: false }) + try { + expect( + replacement + .service('deepchatHooksCapability') + .open(settings, query, 'replacement') + .isObserved('PostToolUse') + ).toBe(true) + expect(() => service.start()).toThrow('retired') + } finally { + await replacement.unload() + } + } finally { + enrichment.resolve(null) + child.emit('close', null) + await (unloading ?? plugin.unload()) + } + }) + it('keeps one session in emission order even when enrichment is slow', async () => { const { service, getSession } = createHarness([enabledHook('a', ['PostToolUse'])]) let releaseSession: (value: null) => void = () => undefined @@ -537,22 +606,132 @@ describe('HookService delivery ordering', () => { const result = await pending expect(result.error).toBe('spawn ENOENT') expect(result.stderr).toBe('Authorization: ***REDACTED***\n') + let drained = false + const stopped = service.stop().then(() => { + drained = true + }) + await flush() + expect(drained).toBe(false) + child.emit('close', null) + await stopped }) - it('kills running hook commands on stop', async () => { + it('keeps stop pending until a killed hook child actually closes', async () => { const { service } = createHarness([enabledHook('a', ['PostToolUse'])]) const child = new FakeChild() spawnMock.mockImplementationOnce(() => child) service.notify(toolEvent('session-1', 'call-1')) await flush() - await service.stop() + let drained = false + const stopped = service.stop().then(() => { + drained = true + }) + await flush() expect(child.kill).toHaveBeenCalledWith('SIGKILL') + expect(drained).toBe(false) + await expect(service.testHookCommand('a')).resolves.toMatchObject({ + success: false, + error: 'Hook commands are stopped' + }) + expect(spawnMock).toHaveBeenCalledTimes(1) + child.emit('close', null) + await stopped + expect(drained).toBe(true) + }) + + it('retains native lifetime after command diagnostics time out', async () => { + vi.useFakeTimers() + try { + const { service } = createHarness([enabledHook('a', ['SessionStart'])]) + const child = new FakeChild() + spawnMock.mockImplementationOnce(() => child) + + const command = service.testHookCommand('a') + await vi.advanceTimersByTimeAsync(30_000) + await expect(command).resolves.toMatchObject({ error: 'Command timed out' }) + let drained = false + const stopped = service.stop().then(() => { + drained = true + }) + await vi.advanceTimersByTimeAsync(0) + expect(drained).toBe(false) + child.emit('close', null) + await stopped + expect(drained).toBe(true) + } finally { + vi.useRealTimers() + } + }) + + it('rejects stop and restart while native child closure remains uncertain', async () => { + vi.useFakeTimers() + try { + const { service } = createHarness([enabledHook('a', ['SessionStart'])]) + const child = new FakeChild() + spawnMock.mockImplementationOnce(() => child) + const command = service.testHookCommand('a') + const stopped = expect(service.stop()).rejects.toThrow( + 'Hook work did not drain before shutdown' + ) + await vi.advanceTimersByTimeAsync(30_000) + await stopped + expect(() => service.start()).toThrow('Hook work must drain before restarting') + child.emit('close', null) + await command + service.start() + expect(service.isObserved('SessionStart')).toBe(true) + } finally { + vi.useRealTimers() + } + }) + + it('retains pending enrichment after a drain deadline without dispatching it later', async () => { + vi.useFakeTimers() + try { + const { service, getSession } = createHarness([enabledHook('a', ['PostToolUse'])]) + let release: (value: null) => void = () => undefined + getSession.mockReturnValueOnce( + new Promise((resolve) => { + release = resolve + }) + ) + service.notify({ + event: 'PostToolUse', + session: { sessionId: 'held' }, + tool: { callId: 'late' } + }) + await vi.advanceTimersByTimeAsync(0) + const stopped = expect(service.stop()).rejects.toThrow( + 'Hook work did not drain before shutdown' + ) + await vi.advanceTimersByTimeAsync(30_000) + await stopped + expect(() => service.start()).toThrow('Hook work must drain before restarting') + release(null) + await vi.advanceTimersByTimeAsync(0) + expect(spawnMock).not.toHaveBeenCalled() + service.start() + } finally { + vi.useRealTimers() + } }) }) describe('HookService payload projection', () => { + it('uses the supplied application version for explicit test commands', async () => { + const { service } = createHarness([enabledHook('a', ['SessionStart'])]) + + await service.testHookCommand('a') + + expect(spawnedPayloads()[0]).toMatchObject({ + payloadVersion: 1, + isTest: true, + app: { version: '9.8.7-hook-fixture', platform: process.platform } + }) + }) + it('truncates tool previews before any clone runs', async () => { const { service } = createHarness([enabledHook('a', ['PostToolUse'])]) const clone = vi.spyOn(globalThis, 'structuredClone') @@ -617,7 +796,7 @@ describe('HookService payload projection', () => { event: 'SessionEnd', time: undefined, isTest: false, - app: { version: expect.any(String), platform: process.platform }, + app: { version: '9.8.7-hook-fixture', platform: process.platform }, session: { conversationId: 'session-1', agentId: 'deepchat', diff --git a/test/main/knowledge/knowledgeService.test.ts b/test/main/knowledge/knowledgeService.test.ts index bd8175afba..fabdba6901 100644 --- a/test/main/knowledge/knowledgeService.test.ts +++ b/test/main/knowledge/knowledgeService.test.ts @@ -5,6 +5,8 @@ import { KnowledgeDatabase } from '../../../src/main/knowledge/database/knowledg import { KnowledgeBase } from '../../../src/main/knowledge/knowledgeBase' import fs from 'fs' +const queueStatus = vi.hoisted(() => vi.fn(() => ({ totalTasks: 0 }))) + // Mock all external dependencies vi.mock('electron', () => ({ app: { @@ -61,7 +63,7 @@ vi.mock('../../../src/main/knowledge/knowledgeBase', () => ({ vi.mock('../../../src/main/knowledge/taskQueue', () => ({ KnowledgeTaskQueue: vi.fn().mockImplementation(function KnowledgeTaskQueue() { return { - getStatus: vi.fn().mockReturnValue({ totalTasks: 0 }) + getStatus: queueStatus } }) })) @@ -91,10 +93,6 @@ const mockFileService = { getSupportedExtensions: vi.fn() } as any -const mockDialogPresenter = { - showDialog: vi.fn() -} as any - const mockProviderRuntime = { getEmbeddings: vi.fn() } as any @@ -123,6 +121,7 @@ describe('KnowledgeService Validation Methods', () => { beforeEach(() => { vi.clearAllMocks() + queueStatus.mockReturnValue({ totalTasks: 0 }) ;( KnowledgeDatabase as unknown as { mockImplementation: (factory: () => unknown) => void @@ -157,13 +156,25 @@ describe('KnowledgeService Validation Methods', () => { knowledgeService = new KnowledgeService({ config: mockProviderSettings, storageRoot: mockDbDir, + extensionDirectory: '/mock/runtime/duckdb/extensions', files: mockFileService, - dialog: mockDialogPresenter, embeddings: mockProviderRuntime, events: mockEvents }) }) + it('returns shutdown confirmation data before Native presentation', async () => { + expect(knowledgeService.getShutdownConfirmation()).toBeNull() + queueStatus.mockReturnValue({ totalTasks: 1 }) + const request = knowledgeService.getShutdownConfirmation() + expect(request).toMatchObject({ + title: 'settings.knowledgeBase.dialog.beforequit.title', + timeout: 10000, + i18n: true + }) + expect(request?.buttons?.map((button) => button.key)).toEqual(['cancel', 'confirm']) + }) + describe('validateFile', () => { it('should successfully validate a supported file', async () => { const mockFilePath = '/path/to/test.txt' diff --git a/test/main/mcp/enterpriseIdentityManager.test.ts b/test/main/mcp/enterpriseIdentityManager.test.ts index 63f5618d4c..4a915c655a 100644 --- a/test/main/mcp/enterpriseIdentityManager.test.ts +++ b/test/main/mcp/enterpriseIdentityManager.test.ts @@ -28,7 +28,7 @@ type TestableManager = { verifyIdToken: ( token: string, profile: McpEnterpriseIdentityProfile, - metadata: typeof metadata, + discoveredMetadata: typeof metadata, expectedNonce?: string ) => Promise<{ sub?: string; exp?: number }> getValidIdentity: ( @@ -38,24 +38,29 @@ type TestableManager = { const createManager = (credential: McpEnterpriseIdentityCredential) => { const saveEnterpriseIdentity = vi.fn( - ( - _key: string, - value: Omit - ): McpEnterpriseIdentityCredential => ({ ...value, updatedAt: Date.now() }) + async (_key: string, value: Omit) => ({ + ...value, + updatedAt: Date.now() + }) ) const store = { - loadEnterpriseIdentity: vi.fn(() => credential), + loadEnterpriseIdentity: vi.fn(async () => credential), saveEnterpriseIdentity, - loadEnterpriseIdentityClientSecret: vi.fn(() => null) + loadEnterpriseIdentityClientSecret: vi.fn(async () => null) } as unknown as McpOAuthCredentialStore const settings = { getEnterpriseIdentityProfiles: vi.fn(() => [profile]) } as unknown as McpSettings - const manager = new McpEnterpriseIdentityManager(settings, store, vi.fn()) + const manager = new McpEnterpriseIdentityManager( + settings, + store, + vi.fn(), + vi.fn(async () => undefined) + ) const testable = manager as unknown as TestableManager testable.discoverMetadata = vi.fn().mockResolvedValue(metadata) - return { saveEnterpriseIdentity, testable } + return { saveEnterpriseIdentity, testable, manager, store, settings } } describe('McpEnterpriseIdentityManager refresh', () => { @@ -142,4 +147,98 @@ describe('McpEnterpriseIdentityManager refresh', () => { expect(testable.verifyIdToken).toHaveBeenCalledWith(credential.idToken, profile, metadata) expect(saveEnterpriseIdentity).not.toHaveBeenCalled() }) + + it.each(['stop', 'replace'] as const)( + 'rejects a pending identity read after %s', + async (action) => { + const credential: McpEnterpriseIdentityCredential = { + profileId: profile.id, + issuer: profile.issuer, + clientId: profile.clientId, + subject: 'user-1', + idToken: 'stored-token', + expiresAt: now + 600_000, + updatedAt: now + } + const { manager, store, settings, saveEnterpriseIdentity } = createManager(credential) + let finish!: (value: McpEnterpriseIdentityCredential) => void + let entered!: () => void + const reading = new Promise((resolve) => { + entered = resolve + }) + vi.mocked(store.loadEnterpriseIdentity).mockImplementationOnce(() => { + entered() + return new Promise((resolve) => { + finish = resolve + }) + }) + const pending = manager.getStatus(profile.id) + const rejected = expect(pending).rejects.toThrow( + action === 'stop' ? 'interrupted' : 'profile changed' + ) + await reading + if (action === 'stop') manager.stop() + else + vi.mocked(settings.getEnterpriseIdentityProfiles).mockReturnValue([ + { ...profile, clientId: 'replacement' } + ]) + finish(credential) + await rejected + expect(saveEnterpriseIdentity).not.toHaveBeenCalled() + } + ) + it.each(['another profile', 'same profile'] as const)( + 'retains edits to %s while clearing an old binding', + async (change) => { + let profiles = [profile] + let finish!: () => void + let entered!: () => void + const clearing = new Promise((resolve) => { + entered = resolve + }) + const store = { + clearEnterpriseProfileCredentials: vi.fn(() => { + entered() + return new Promise((resolve) => { + finish = resolve + }) + }) + } as unknown as McpOAuthCredentialStore + const settings = { + getEnterpriseIdentityProfiles: () => profiles, + setEnterpriseIdentityProfiles: (next: McpEnterpriseIdentityProfile[]) => { + profiles = next + } + } as unknown as McpSettings + const manager = new McpEnterpriseIdentityManager( + settings, + store, + vi.fn(), + vi.fn(async () => undefined) + ) + const pending = manager.saveProfile({ ...profile, clientId: 'replacement' }) + const result = + change === 'same profile' + ? expect(pending).rejects.toThrow('profile changed') + : expect(pending).resolves.toMatchObject({ clientId: 'replacement' }) + await clearing + await manager.saveProfile({ + ...profile, + id: change === 'same profile' ? profile.id : 'second', + label: 'Latest edit' + }) + finish() + await result + expect(manager.listProfiles()).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + id: change === 'same profile' ? profile.id : 'second', + label: 'Latest edit', + clientId: profile.clientId + }) + ]) + ) + manager.stop() + } + ) }) diff --git a/test/main/mcp/mcpAppHost.test.ts b/test/main/mcp/mcpAppHost.test.ts index 3ef13a2db1..3d91a5ab99 100644 --- a/test/main/mcp/mcpAppHost.test.ts +++ b/test/main/mcp/mcpAppHost.test.ts @@ -1,5 +1,7 @@ +import { createPluginToolPolicies } from '../../helpers/pluginToolPolicies' import { describe, expect, it, vi } from 'vitest' import { McpAppHost } from '@/mcp/apps/appHost' +import { McpAppSandboxRegistry } from '@/mcp/apps/sandboxRegistry' import type { McpAppDescriptor, Tool } from '@shared/types/mcp' const context = { webContentsId: 7, windowId: 11 } @@ -25,7 +27,7 @@ const tool: Tool = { } } -const createHarness = () => { +const createHarness = (registryOverride?: McpAppSandboxRegistry) => { let currentConfig = { serverId: descriptor.serverId, configGeneration: descriptor.configGeneration, @@ -91,6 +93,10 @@ const createHarness = () => { toolAccessSuspended: false } const registry = { + reserveCreation: vi.fn(() => 'creation-id'), + getCreationSignal: vi.fn(() => new AbortController().signal), + assertCreationCurrent: vi.fn(), + cancelCreation: vi.fn(), create: vi.fn((input: Record) => ({ ...instance, ...input })), assertOwned: vi.fn(() => instance), revoke: vi.fn(), @@ -101,7 +107,12 @@ const createHarness = () => { requestAppDecision: vi.fn().mockResolvedValue({ allowed: true }) } const validateSource = vi.fn(() => true) + const desktop = { + observeOwner: vi.fn(async (): Promise => undefined), + openExternal: vi.fn(async (): Promise => undefined) + } const host = new McpAppHost({ + toolPolicies: createPluginToolPolicies(), settings: { getMcpServers } as never, @@ -109,7 +120,8 @@ const createHarness = () => { getClient: vi.fn(() => client) } as never, permissionBroker: permissionBroker as never, - registry: registry as never, + registry: registryOverride ?? (registry as never), + desktop, ensureServerRunning: vi.fn(), getPermissionMode: vi.fn().mockResolvedValue('default'), validateSource, @@ -119,6 +131,7 @@ const createHarness = () => { return { client, host, + desktop, permissionBroker, registry, validateSource, @@ -129,6 +142,81 @@ const createHarness = () => { } describe('MCP App host', () => { + it('does not publish a view after its owner closes during resource loading', async () => { + const registry = new McpAppSandboxRegistry(() => () => {}) + const { host, client } = createHarness(registry) + let release!: (resources: unknown[]) => void + client.readResourceContents.mockReturnValueOnce( + new Promise((resolve) => { + release = resolve + }) + ) + const pending = host.prepareView( + { + descriptor, + conversationId: 'conversation-id', + messageId: 'message-id', + blockId: 'block-id', + toolInput: {} + }, + context + ) + await vi.waitFor(() => expect(client.readResourceContents).toHaveBeenCalled()) + registry.revokeByWebContents(context.webContentsId) + release([ + { + uri: descriptor.resourceUri, + mimeType: descriptor.resourceMimeType, + text: '
Late
', + _meta: { ui: {} } + } + ]) + await expect(pending).rejects.toThrow('no longer active') + }) + + it('fences a delayed desktop observation when maintenance clears creation', async () => { + const registry = new McpAppSandboxRegistry(() => () => {}) + const { host, desktop, client } = createHarness(registry) + let release!: () => void + desktop.observeOwner.mockReturnValueOnce( + new Promise((resolve) => { + release = resolve + }) + ) + const pending = host.prepareView( + { + descriptor, + conversationId: 'conversation-id', + messageId: 'message-id', + blockId: 'block-id', + toolInput: {} + }, + context + ) + registry.clear() + release() + await expect(pending).rejects.toThrow('no longer active') + expect(client.readResourceContents).not.toHaveBeenCalled() + }) + + it('opens links only after consent and current source validation through the desktop port', async () => { + const { host, registry, desktop, validateSource } = createHarness() + registry.requestConsent.mockResolvedValueOnce(false) + expect(await host.openLink('instance-id', 'https://example.com', context)).toBe(false) + expect(desktop.openExternal).not.toHaveBeenCalled() + expect(await host.openLink('instance-id', 'https://example.com', context)).toBe(true) + expect(desktop.openExternal).toHaveBeenCalledWith('https://example.com/', context) + desktop.openExternal.mockClear() + registry.requestConsent.mockImplementationOnce(async () => { + validateSource.mockReturnValue(false) + return true + }) + await expect(host.openLink('instance-id', 'https://example.com', context)).rejects.toThrow( + 'source no longer matches' + ) + expect(desktop.openExternal).not.toHaveBeenCalled() + }) + it('hydrates an exact saved App binding and finds metadata on later resource pages', async () => { const { client, host, registry } = createHarness() @@ -143,8 +231,12 @@ describe('MCP App host', () => { context ) - expect(client.listResourcesPage).toHaveBeenNthCalledWith(1, undefined) - expect(client.listResourcesPage).toHaveBeenNthCalledWith(2, 'second-page') + expect(client.listResourcesPage).toHaveBeenNthCalledWith(1, undefined, expect.any(AbortSignal)) + expect(client.listResourcesPage).toHaveBeenNthCalledWith( + 2, + 'second-page', + expect.any(AbortSignal) + ) expect(registry.create).toHaveBeenCalledWith( expect.objectContaining({ descriptor, diff --git a/test/main/mcp/mcpAppSandboxProtocol.test.ts b/test/main/mcp/mcpAppSandboxProtocol.test.ts index 210dc570a9..ffe61b1285 100644 --- a/test/main/mcp/mcpAppSandboxProtocol.test.ts +++ b/test/main/mcp/mcpAppSandboxProtocol.test.ts @@ -1,10 +1,134 @@ -import { describe, expect, it } from 'vitest' +import { beforeEach, describe, expect, it, vi } from 'vitest' +import { EventEmitter } from 'node:events' +import { McpAppSandboxRegistry } from '@/mcp/apps/sandboxRegistry' +import type { McpAppDescriptor } from '@shared/types/mcp' import { buildMcpAppContentSecurityPolicy, + configureMcpAppSessionPermissions, + registerMcpAppProtocol, buildMcpAppPermissionsPolicy } from '@/mcp/apps/sandboxProtocol' +const native = vi.hoisted(() => ({ + fromId: vi.fn(), + fromWebContents: vi.fn(), + handle: vi.fn(), + openExternal: vi.fn(), + session: { setPermissionRequestHandler: vi.fn(), setPermissionCheckHandler: vi.fn() } +})) +vi.mock('electron', () => ({ + protocol: { handle: native.handle }, + session: { defaultSession: native.session }, + webContents: { fromId: native.fromId }, + BrowserWindow: { fromWebContents: native.fromWebContents }, + shell: { openExternal: native.openExternal } +})) + +beforeEach(() => { + vi.clearAllMocks() + const owner = Object.assign(new EventEmitter(), { id: 7, isDestroyed: () => false }) + native.fromId.mockReturnValue(owner) + native.fromWebContents.mockReturnValue({ id: 11 }) +}) + +const descriptor: McpAppDescriptor = { + schemaVersion: 1, + serverId: 'server-id', + configGeneration: 1, + bindingHash: 'binding-hash', + serverName: 'apps-server', + toolName: 'render', + resourceUri: 'ui://render/index.html', + resourceMimeType: 'text/html;profile=mcp-app' +} + +const createInstance = ( + registry: McpAppSandboxRegistry, + validateLive: () => boolean = () => true +) => + registry.create({ + creationId: registry.reserveCreation({ webContentsId: 7, windowId: 11 }, descriptor.serverId), + context: { webContentsId: 7, windowId: 11 }, + conversationId: 'conversation-id', + messageId: 'message-id', + blockId: 'block-id', + descriptor, + toolInput: {}, + html: '
App
', + permissions: { camera: {} }, + validateLive + }) + describe('MCP App sandbox policy', () => { + it('settles a native permission callback once when its disposed caller throws', async () => { + const registry = new McpAppSandboxRegistry(() => () => {}) + const instance = createInstance(registry) + const setRequest = vi.fn() + configureMcpAppSessionPermissions(registry, { + setPermissionRequestHandler: setRequest, + setPermissionCheckHandler: vi.fn() + } as never) + const callback = vi.fn(() => { + throw new Error('Native caller disposed') + }) + setRequest.mock.calls[0][0]( + { id: 7, getURL: () => 'file:///deepchat/index.html' }, + 'media', + callback, + { + requestingUrl: `mcp-app://${instance.instanceId}/sandbox.html`, + mediaTypes: ['audio'], + isMainFrame: false + } + ) + await new Promise((resolve) => setImmediate(resolve)) + expect(callback).toHaveBeenCalledExactlyOnceWith(false) + }) + + it('revokes pending creation and views on native owner destruction', async () => { + const owner = Object.assign(new EventEmitter(), { id: 7, isDestroyed: vi.fn(() => false) }) + native.fromId.mockReturnValue(owner) + const registry = new McpAppSandboxRegistry((_id, listener) => { + owner.once('destroyed', listener) + return () => owner.removeListener('destroyed', listener) + }) + const desktop = registerMcpAppProtocol(registry) + const context = { webContentsId: 7, windowId: 11 } + const creationId = registry.reserveCreation(context, descriptor.serverId) + await desktop.observeOwner(context) + const instance = createInstance(registry) + const handler = native.handle.mock.calls.at(-1)![1] + const response = await handler(new Request(`mcp-app://${instance.instanceId}/sandbox.html`)) + expect(response.status).toBe(200) + await desktop.openExternal('https://example.com/', context) + expect(native.openExternal).toHaveBeenCalledTimes(1) + owner.isDestroyed.mockReturnValue(true) + owner.emit('destroyed') + expect(registry.getForProtocol(instance.instanceId)).toBeNull() + expect(() => registry.assertCreationCurrent(creationId)).toThrow('no longer active') + await expect(desktop.observeOwner(context)).rejects.toThrow('owner is unavailable') + await expect(desktop.openExternal('https://example.com/', context)).rejects.toThrow( + 'owner is unavailable' + ) + expect(native.openExternal).toHaveBeenCalledTimes(1) + }) + + it('refuses a delayed protocol policy after the native owner is gone', async () => { + const registry = new McpAppSandboxRegistry(() => () => {}) + registerMcpAppProtocol(registry) + const handler = native.handle.mock.calls.at(-1)![1] + let release!: (policy: Awaited>) => void + vi.spyOn(registry, 'getProtocolPolicy').mockReturnValueOnce( + new Promise((resolve) => { + release = resolve + }) + ) + const pending = handler(new Request('mcp-app://fixture/sandbox.html')) + native.fromId.mockReturnValue(null) + release({ context: { webContentsId: 7, windowId: 11 } }) + expect((await pending).status).toBe(410) + }) + it('keeps network access deny-by-default and admits only declared origins', () => { const policy = buildMcpAppContentSecurityPolicy({ connectDomains: ['https://api.example.com'], @@ -58,4 +182,192 @@ describe('MCP App sandbox policy', () => { 'camera=(), microphone=(), geolocation=(), clipboard-write=()' ) }) + it('preserves default-session permissions while isolating App frames', async () => { + const registry = new McpAppSandboxRegistry(() => () => {}) + const instance = createInstance(registry) + let permissionRequestHandler: + | (( + owner: { id: number; getURL(): string }, + permission: string, + callback: (allowed: boolean) => void, + details: { + requestingUrl: string + mediaTypes?: string[] + isMainFrame: boolean + } + ) => void) + | undefined + let permissionCheckHandler: + | (( + owner: { id: number; getURL(): string } | null, + permission: string, + requestingOrigin: string, + details: { securityOrigin: string; requestingUrl: string } + ) => boolean) + | undefined + const targetSession = { + setPermissionRequestHandler: vi.fn( + (handler: NonNullable) => { + permissionRequestHandler = handler + } + ), + setPermissionCheckHandler: vi.fn((handler: NonNullable) => { + permissionCheckHandler = handler + }) + } + configureMcpAppSessionPermissions(registry, targetSession as never) + + const firstPartyDecision = vi.fn() + permissionRequestHandler!( + { id: 1, getURL: () => 'file:///deepchat/index.html' }, + 'media', + firstPartyDecision, + { + requestingUrl: 'file:///deepchat/index.html', + mediaTypes: ['audio'], + isMainFrame: true + } + ) + expect(firstPartyDecision).toHaveBeenCalledWith(true) + + for (const mediaTypes of [['video'], ['audio', 'video']]) { + const firstPartyCameraDecision = vi.fn() + permissionRequestHandler!( + { id: 1, getURL: () => 'file:///deepchat/index.html' }, + 'media', + firstPartyCameraDecision, + { + requestingUrl: 'file:///deepchat/index.html', + mediaTypes, + isMainFrame: true + } + ) + expect(firstPartyCameraDecision).toHaveBeenCalledWith(true) + } + expect( + permissionCheckHandler!( + { id: 1, getURL: () => 'file:///deepchat/index.html' }, + 'notifications', + 'file:///deepchat/index.html', + { + securityOrigin: 'file:///deepchat/index.html', + requestingUrl: 'file:///deepchat/index.html' + } + ) + ).toBe(true) + + const browserDecision = vi.fn() + permissionRequestHandler!( + { id: 2, getURL: () => 'https://example.com' }, + 'notifications', + browserDecision, + { + requestingUrl: 'https://example.com', + isMainFrame: true + } + ) + expect(browserDecision).toHaveBeenCalledWith(true) + expect( + permissionCheckHandler!( + { id: 2, getURL: () => 'https://example.com' }, + 'notifications', + 'https://example.com', + { + securityOrigin: 'https://example.com', + requestingUrl: 'https://example.com' + } + ) + ).toBe(true) + + const nestedRemoteDecision = vi.fn() + permissionRequestHandler!( + { id: 1, getURL: () => 'file:///deepchat/index.html' }, + 'notifications', + nestedRemoteDecision, + { + requestingUrl: 'https://example.com', + isMainFrame: false + } + ) + expect(nestedRemoteDecision).toHaveBeenCalledWith(false) + expect( + permissionCheckHandler!( + { id: 1, getURL: () => 'file:///deepchat/index.html' }, + 'notifications', + 'https://example.com', + { + securityOrigin: 'https://example.com', + requestingUrl: 'https://example.com' + } + ) + ).toBe(false) + + const consentRequestIds: string[] = [] + registry.setConsentPublisher((_windowId, payload) => { + consentRequestIds.push(payload.request.requestId) + }) + const undeclaredMicrophoneDecision = vi.fn() + permissionRequestHandler!( + { + id: 7, + getURL: () => `mcp-app://${instance.instanceId}/sandbox.html` + }, + 'media', + undeclaredMicrophoneDecision, + { + requestingUrl: `mcp-app://${instance.instanceId}/sandbox.html`, + mediaTypes: ['audio'], + isMainFrame: false + } + ) + await vi.waitFor(() => expect(undeclaredMicrophoneDecision).toHaveBeenCalledWith(false)) + expect(consentRequestIds).toHaveLength(0) + + const appDecision = vi.fn() + permissionRequestHandler!( + { + id: 7, + getURL: () => `mcp-app://${instance.instanceId}/sandbox.html` + }, + 'media', + appDecision, + { + requestingUrl: `mcp-app://${instance.instanceId}/sandbox.html`, + mediaTypes: ['video'], + isMainFrame: false + } + ) + await vi.waitFor(() => expect(consentRequestIds).toHaveLength(1)) + expect( + registry.submitConsent(consentRequestIds[0], true, { + webContentsId: 7, + windowId: 11 + }) + ).toBe(true) + await vi.waitFor(() => expect(appDecision).toHaveBeenCalledWith(true)) + + const repeatedDecision = vi.fn() + permissionRequestHandler!( + { + id: 7, + getURL: () => `mcp-app://${instance.instanceId}/sandbox.html` + }, + 'media', + repeatedDecision, + { + requestingUrl: `mcp-app://${instance.instanceId}/sandbox.html`, + mediaTypes: ['video'], + isMainFrame: false + } + ) + await vi.waitFor(() => expect(consentRequestIds).toHaveLength(2)) + expect(repeatedDecision).not.toHaveBeenCalled() + expect( + registry.submitConsent(consentRequestIds[1], true, { + webContentsId: 7, + windowId: 11 + }) + ).toBe(true) + await vi.waitFor(() => expect(repeatedDecision).toHaveBeenCalledWith(true)) + }) }) diff --git a/test/main/mcp/mcpAppSandboxRegistry.test.ts b/test/main/mcp/mcpAppSandboxRegistry.test.ts index 84c5ea4c94..246b25fabb 100644 --- a/test/main/mcp/mcpAppSandboxRegistry.test.ts +++ b/test/main/mcp/mcpAppSandboxRegistry.test.ts @@ -15,9 +15,13 @@ const descriptor: McpAppDescriptor = { const createInstance = ( registry: McpAppSandboxRegistry, - validateLive: () => boolean = () => true + validateLive: () => boolean = () => true, + creationId?: string ) => registry.create({ + creationId: + creationId ?? + registry.reserveCreation({ webContentsId: 7, windowId: 11 }, descriptor.serverId), context: { webContentsId: 7, windowId: 11 }, conversationId: 'conversation-id', messageId: 'message-id', @@ -30,8 +34,109 @@ const createInstance = ( }) describe('MCP App sandbox registry', () => { + it('owns one renderer lifetime through creation, consent and retirement', async () => { + let destroy!: () => void + const unsubscribe = vi.fn() + const observe = vi.fn((_id: number, listener: () => void) => { + destroy = listener + return unsubscribe + }) + const registry = new McpAppSandboxRegistry(observe) + registry.setConsentPublisher(() => {}) + const context = { webContentsId: 7, windowId: 11 } + const creation = registry.reserveCreation(context, descriptor.serverId) + const instance = createInstance(registry) + expect(observe).toHaveBeenCalledOnce() + registry.cancelCreation(creation) + expect(unsubscribe).not.toHaveBeenCalled() + const pending = registry.requestBrowserPermission(instance.instanceId, context, ['camera']) + destroy() + expect(await pending).toBe(false) + expect(registry.getForProtocol(instance.instanceId)).toBeNull() + expect(unsubscribe).toHaveBeenCalledOnce() + registry.close() + expect(() => registry.reserveCreation(context, descriptor.serverId)).toThrow( + 'registry is closed' + ) + + const retired = new McpAppSandboxRegistry((_id, listener) => { + listener() + return unsubscribe + }) + expect(() => retired.reserveCreation(context, descriptor.serverId)).toThrow('no longer active') + expect(unsubscribe).toHaveBeenCalledTimes(2) + }) + + it('counts pending creation against both limits and releases canceled work', () => { + const registry = new McpAppSandboxRegistry(() => () => {}) + const first = Array.from({ length: 32 }, () => + registry.reserveCreation({ webContentsId: 7, windowId: 11 }, descriptor.serverId) + ) + expect(() => + registry.reserveCreation({ webContentsId: 7, windowId: 11 }, descriptor.serverId) + ).toThrow('instance limit') + const instance = createInstance(registry, () => true, first.pop()!) + const second = Array.from({ length: 32 }, () => + registry.reserveCreation({ webContentsId: 8, windowId: 12 }, descriptor.serverId) + ) + expect(() => + registry.reserveCreation({ webContentsId: 9, windowId: 13 }, descriptor.serverId) + ).toThrow('instance limit') + registry.cancelCreation(second.pop()!) + const replacement = registry.reserveCreation( + { webContentsId: 9, windowId: 13 }, + descriptor.serverId + ) + registry.revokeByWebContents(7) + expect(registry.getForProtocol(instance.instanceId)).toBeNull() + for (const id of first) + expect(() => registry.assertCreationCurrent(id)).toThrow('no longer active') + expect(() => registry.assertCreationCurrent(replacement)).not.toThrow() + registry.clear() + }) + + it.each(['owner', 'server', 'maintenance'] as const)( + 'invalidates pending creation on %s revocation', + (reason) => { + const registry = new McpAppSandboxRegistry(() => () => {}) + const id = registry.reserveCreation({ webContentsId: 7, windowId: 11 }, descriptor.serverId) + const signal = registry.getCreationSignal(id) + if (reason === 'owner') registry.revokeByWebContents(7) + else if (reason === 'server') registry.revokeByServer(descriptor.serverId) + else registry.clear() + expect(signal.aborted).toBe(true) + expect(() => createInstance(registry, () => true, id)).toThrow('no longer active') + } + ) + + it('isolates protocol policy and rejects approval revoked before native dispatch', async () => { + const registry = new McpAppSandboxRegistry(() => () => {}) + const instance = createInstance(registry) + const policy = await registry.getProtocolPolicy(instance.instanceId) + policy!.permissions!.camera = undefined + expect(instance.permissions?.camera).toEqual({}) + let requestId = '' + registry.setConsentPublisher((_windowId, payload) => { + requestId = payload.request.requestId + }) + const context = { webContentsId: 7, windowId: 11 } + expect( + await registry.requestBrowserPermission(instance.instanceId, { ...context, windowId: 12 }, [ + 'camera' + ]) + ).toBe(false) + expect( + await registry.requestBrowserPermission(instance.instanceId, context, ['tool-call']) + ).toBe(false) + const decision = registry.requestBrowserPermission(instance.instanceId, context, ['camera']) + expect(requestId).not.toBe('') + expect(registry.submitConsent(requestId, true, context)).toBe(true) + registry.revoke(instance.instanceId) + expect(await decision).toBe(false) + }) + it('binds instances to the creating WebContents and revokes them', () => { - const registry = new McpAppSandboxRegistry() + const registry = new McpAppSandboxRegistry(() => () => {}) const instance = createInstance(registry) expect(registry.assertOwned(instance.instanceId, { webContentsId: 7, windowId: 11 })).toBe( @@ -46,7 +151,7 @@ describe('MCP App sandbox registry', () => { }) it('revokes every instance bound to a reconfigured server', () => { - const registry = new McpAppSandboxRegistry() + const registry = new McpAppSandboxRegistry(() => () => {}) const instance = createInstance(registry) registry.revokeByServer(descriptor.serverId) @@ -55,7 +160,7 @@ describe('MCP App sandbox registry', () => { }) it('rejects browser permission approval after the live App binding is lost', async () => { - const registry = new McpAppSandboxRegistry() + const registry = new McpAppSandboxRegistry(() => () => {}) let isLive = true const instance = createInstance(registry, () => isLive) let consentRequestId = '' @@ -86,193 +191,4 @@ describe('MCP App sandbox registry', () => { await expect(decision).resolves.toBe(false) expect(registry.getForProtocol(instance.instanceId)).toBeNull() }) - - it('preserves default-session permissions while isolating App frames', async () => { - const registry = new McpAppSandboxRegistry() - const instance = createInstance(registry) - let permissionRequestHandler: - | (( - owner: { id: number; getURL(): string }, - permission: string, - callback: (allowed: boolean) => void, - details: { - requestingUrl: string - mediaTypes?: string[] - isMainFrame: boolean - } - ) => void) - | undefined - let permissionCheckHandler: - | (( - owner: { id: number; getURL(): string } | null, - permission: string, - requestingOrigin: string, - details: { securityOrigin: string; requestingUrl: string } - ) => boolean) - | undefined - const targetSession = { - setPermissionRequestHandler: vi.fn( - (handler: NonNullable) => { - permissionRequestHandler = handler - } - ), - setPermissionCheckHandler: vi.fn((handler: NonNullable) => { - permissionCheckHandler = handler - }) - } - registry.configureDefaultSessionPermissions(targetSession as never) - - const firstPartyDecision = vi.fn() - permissionRequestHandler!( - { id: 1, getURL: () => 'file:///deepchat/index.html' }, - 'media', - firstPartyDecision, - { - requestingUrl: 'file:///deepchat/index.html', - mediaTypes: ['audio'], - isMainFrame: true - } - ) - expect(firstPartyDecision).toHaveBeenCalledWith(true) - - for (const mediaTypes of [['video'], ['audio', 'video']]) { - const firstPartyCameraDecision = vi.fn() - permissionRequestHandler!( - { id: 1, getURL: () => 'file:///deepchat/index.html' }, - 'media', - firstPartyCameraDecision, - { - requestingUrl: 'file:///deepchat/index.html', - mediaTypes, - isMainFrame: true - } - ) - expect(firstPartyCameraDecision).toHaveBeenCalledWith(true) - } - expect( - permissionCheckHandler!( - { id: 1, getURL: () => 'file:///deepchat/index.html' }, - 'notifications', - 'file:///deepchat/index.html', - { - securityOrigin: 'file:///deepchat/index.html', - requestingUrl: 'file:///deepchat/index.html' - } - ) - ).toBe(true) - - const browserDecision = vi.fn() - permissionRequestHandler!( - { id: 2, getURL: () => 'https://example.com' }, - 'notifications', - browserDecision, - { - requestingUrl: 'https://example.com', - isMainFrame: true - } - ) - expect(browserDecision).toHaveBeenCalledWith(true) - expect( - permissionCheckHandler!( - { id: 2, getURL: () => 'https://example.com' }, - 'notifications', - 'https://example.com', - { - securityOrigin: 'https://example.com', - requestingUrl: 'https://example.com' - } - ) - ).toBe(true) - - const nestedRemoteDecision = vi.fn() - permissionRequestHandler!( - { id: 1, getURL: () => 'file:///deepchat/index.html' }, - 'notifications', - nestedRemoteDecision, - { - requestingUrl: 'https://example.com', - isMainFrame: false - } - ) - expect(nestedRemoteDecision).toHaveBeenCalledWith(false) - expect( - permissionCheckHandler!( - { id: 1, getURL: () => 'file:///deepchat/index.html' }, - 'notifications', - 'https://example.com', - { - securityOrigin: 'https://example.com', - requestingUrl: 'https://example.com' - } - ) - ).toBe(false) - - const consentRequestIds: string[] = [] - registry.setConsentPublisher((_windowId, payload) => { - consentRequestIds.push(payload.request.requestId) - }) - const undeclaredMicrophoneDecision = vi.fn() - permissionRequestHandler!( - { - id: 7, - getURL: () => `mcp-app://${instance.instanceId}/sandbox.html` - }, - 'media', - undeclaredMicrophoneDecision, - { - requestingUrl: `mcp-app://${instance.instanceId}/sandbox.html`, - mediaTypes: ['audio'], - isMainFrame: false - } - ) - expect(undeclaredMicrophoneDecision).toHaveBeenCalledWith(false) - expect(consentRequestIds).toHaveLength(0) - - const appDecision = vi.fn() - permissionRequestHandler!( - { - id: 7, - getURL: () => `mcp-app://${instance.instanceId}/sandbox.html` - }, - 'media', - appDecision, - { - requestingUrl: `mcp-app://${instance.instanceId}/sandbox.html`, - mediaTypes: ['video'], - isMainFrame: false - } - ) - await vi.waitFor(() => expect(consentRequestIds).toHaveLength(1)) - expect( - registry.submitConsent(consentRequestIds[0], true, { - webContentsId: 7, - windowId: 11 - }) - ).toBe(true) - await vi.waitFor(() => expect(appDecision).toHaveBeenCalledWith(true)) - - const repeatedDecision = vi.fn() - permissionRequestHandler!( - { - id: 7, - getURL: () => `mcp-app://${instance.instanceId}/sandbox.html` - }, - 'media', - repeatedDecision, - { - requestingUrl: `mcp-app://${instance.instanceId}/sandbox.html`, - mediaTypes: ['video'], - isMainFrame: false - } - ) - await vi.waitFor(() => expect(consentRequestIds).toHaveLength(2)) - expect(repeatedDecision).not.toHaveBeenCalled() - expect( - registry.submitConsent(consentRequestIds[1], true, { - webContentsId: 7, - windowId: 11 - }) - ).toBe(true) - await vi.waitFor(() => expect(repeatedDecision).toHaveBeenCalledWith(true)) - }) }) diff --git a/test/main/mcp/mcpClient.test.ts b/test/main/mcp/mcpClient.test.ts index 6b7e89d987..fe4e5468dd 100644 --- a/test/main/mcp/mcpClient.test.ts +++ b/test/main/mcp/mcpClient.test.ts @@ -1,6 +1,5 @@ import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest' -import { McpClient } from '../../../src/main/mcp/mcpClient' -import { ToolchainService } from '../../../src/main/toolchains/service' +import { McpClient, type McpClientRuntime } from '../../../src/main/mcp/mcpClient' import { ToolchainResolutionError } from '../../../src/main/toolchains/errors' import path from 'path' import fs from 'fs' @@ -24,18 +23,6 @@ vi.mock('@/agent/shared/process/childProcessRegistry', () => ({ childProcessRegistry: childProcessRegistryMock })) -// Mock electron modules -vi.mock('electron', () => ({ - app: { - getPath: vi.fn((pathType: string) => { - if (pathType === 'home') return '/mock/home' - return '/mock/app' - }), - getAppPath: vi.fn(() => '/mock/app'), - getVersion: vi.fn(() => '1.0.0') - } -})) - // Mock fs module vi.mock('fs', () => ({ existsSync: fsExistsSyncMock, @@ -63,6 +50,10 @@ const mockGenerateCompletionStandalone = presenterMocks.generateCompletionStanda const mockGetProviderModels = presenterMocks.getProviderModels const mockGetCustomModels = presenterMocks.getCustomModels +let toolchains: McpClientRuntime['toolchains'] +let homeDirectory = '/mock/home' +let appVersion = '1.0.0' + function createMcpClient( serverName: string, serverConfig: Record, @@ -80,6 +71,9 @@ function createMcpClient( mcpOAuthManager, inMemoryServerFactory, { + homeDirectory, + appVersion, + toolchains, resolveMcpBindings, sampling: { handleSamplingRequest: mockHandleSamplingRequest, @@ -192,7 +186,7 @@ describe('McpClient Runtime Command Processing Tests', () => { vi.clearAllMocks() mockFsExistsSync.mockReset() mockFsExistsSync.mockReturnValue(false) - vi.spyOn(ToolchainService, 'getInstance').mockReturnValue({ + toolchains = { rewriteCommand: (command: string, args: string[]) => ({ command, args }), resolvedBinDirs: () => [], prependResolvedToEnv: (env: Record) => env, @@ -203,7 +197,9 @@ describe('McpClient Runtime Command Processing Tests', () => { `${kind} toolchain is not configured` ) } - } as never) + } as never + homeDirectory = '/mock/home' + appVersion = '1.0.0' mockHandleSamplingRequest.mockReset() mockCancelSamplingRequest.mockReset() @@ -239,7 +235,6 @@ describe('McpClient Runtime Command Processing Tests', () => { }) afterEach(() => { - ToolchainService.resetForTests() vi.unstubAllEnvs() vi.clearAllMocks() }) @@ -308,12 +303,12 @@ describe('McpClient Runtime Command Processing Tests', () => { process.platform === 'win32' ? path.join(uvRuntimePath, 'uvx.exe') : path.join(uvRuntimePath, 'uvx') - vi.spyOn(ToolchainService, 'getInstance').mockReturnValue({ + toolchains = { rewriteCommand: () => ({ command: expectedUvxPath, args: ['osm-mcp-server'] }), resolvedBinDirs: () => [], prependResolvedToEnv: (env: Record) => env, resolve: () => ({ rootDir: uvRuntimePath }) - } as never) + } as never const client = createMcpClient('osm-mcp-server', { type: 'stdio', @@ -411,15 +406,37 @@ describe('McpClient Runtime Command Processing Tests', () => { }) describe('Runtime Path Detection', () => { + it('uses the supplied home and application identity without a native default', async () => { + homeDirectory = '/owned/runtime/home' + appVersion = '9.8.7' + const client = createMcpClient('host-inputs', { + type: 'http', + baseUrl: 'http://127.0.0.1:12345/mcp' + }) + + expect(client.expandPath('~/server.cjs')).toBe('/owned/runtime/home/server.cjs') + await client.connect() + expect(Client).toHaveBeenCalledWith( + { name: 'DeepChat', version: '9.8.7' }, + expect.any(Object) + ) + await client.disconnect() + + homeDirectory = 'relative/home' + expect(() => createMcpClient('invalid', { type: 'stdio' })).toThrow( + 'MCP home directory must be absolute' + ) + }) + it('should expose resolved toolchain roots from the service', () => { - vi.spyOn(ToolchainService, 'getInstance').mockReturnValue({ + toolchains = { rewriteCommand: (command: string, args: string[]) => ({ command, args }), resolvedBinDirs: () => [], prependResolvedToEnv: (env: Record) => env, resolve: (kind: 'node' | 'uv') => ({ rootDir: kind === 'uv' ? '/runtime/uv' : '/runtime/node' }) - } as never) + } as never const client = createMcpClient('test', { type: 'stdio' }) @@ -435,6 +452,40 @@ describe('McpClient Runtime Command Processing Tests', () => { }) }) + it('cancels an owned resource read while an uncooperative SDK result is pending', async () => { + let release!: (value: { contents: unknown[] }) => void + const sdk = { + ...createSdkToolClient([], 'modern'), + readResource: vi.fn( + () => + new Promise<{ contents: unknown[] }>((resolve) => { + release = resolve + }) + ) + } + vi.mocked(Client).mockImplementationOnce(function Client() { + return sdk as never + }) + const client = createMcpClient('resource-owner', { + type: 'http', + baseUrl: 'http://127.0.0.1:12345/mcp' + }) + await client.connect() + const controller = new AbortController() + const reading = client.readResourceContents('ui://fixture/index.html', controller.signal) + const rejected = expect(reading).rejects.toThrow('Resource owner closed') + await vi.waitFor(() => expect(sdk.readResource).toHaveBeenCalled()) + expect(sdk.readResource).toHaveBeenCalledWith( + { uri: 'ui://fixture/index.html' }, + { signal: controller.signal } + ) + controller.abort(new Error('Resource owner closed')) + await rejected + release({ contents: [{ uri: 'ui://fixture/index.html', text: 'late' }] }) + await new Promise((resolve) => setImmediate(resolve)) + await client.disconnect() + }) + describe('Environment Variable Processing', () => { it('does not disclose host environment values to user-owned MCP servers', async () => { vi.stubEnv('DEEPCHAT_TEST_HOST_SECRET', 'host-only-secret') @@ -473,7 +524,7 @@ describe('McpClient Runtime Command Processing Tests', () => { null, undefined, undefined, - () => ({ TOKEN: value }) + async () => ({ TOKEN: value }) ) await expect(client.connect()).rejects.toThrow(/header/i) } @@ -489,9 +540,17 @@ describe('McpClient Runtime Command Processing Tests', () => { environmentVariables: ['DEEPCHAT_TEST_HOST_SECRET'], env: { TOKEN: '${DEEPCHAT_TEST_HOST_SECRET}' } } - const client = createMcpClient('reviewed', config, null, null, undefined, undefined, () => ({ - DEEPCHAT_TEST_HOST_SECRET: 'explicit-secret' - })) + const client = createMcpClient( + 'reviewed', + config, + null, + null, + undefined, + undefined, + async () => ({ + DEEPCHAT_TEST_HOST_SECRET: 'explicit-secret' + }) + ) await client.connect() expect(vi.mocked(StdioClientTransport).mock.calls.at(-1)?.[0]?.env?.TOKEN).toBe( 'explicit-secret' @@ -499,6 +558,35 @@ describe('McpClient Runtime Command Processing Tests', () => { expect(config.env.TOKEN).toBe('${DEEPCHAT_TEST_HOST_SECRET}') }) + it('does not create a transport when cancelled during credential resolution', async () => { + const bindings = Promise.withResolvers>() + const client = createMcpClient( + 'reviewed', + { + ownerPluginId: 'user.reviewed', + type: 'stdio', + command: 'node', + args: ['server.js'], + environmentVariables: ['TOKEN'], + env: { TOKEN: '${TOKEN}' } + }, + null, + null, + undefined, + undefined, + () => bindings.promise + ) + const completion = expect(client.connect({ waitForConnection: true })).rejects.toThrow( + 'cancelled' + ) + await client.disconnect() + bindings.resolve({ TOKEN: 'private-key' }) + await completion + expect(StdioClientTransport).not.toHaveBeenCalled() + expect(client.getLifecycleStatus()).toBe('stopped') + expect(client.isActive()).toBe(false) + }) + it('should set npm registry environment variables', () => { const client = createMcpClient('test', { type: 'stdio' }, 'https://registry.npmmirror.com') @@ -603,12 +691,12 @@ describe('McpClient Runtime Command Processing Tests', () => { } return env }) - vi.spyOn(ToolchainService, 'getInstance').mockReturnValue({ + toolchains = { rewriteCommand: (command: string, args: string[]) => ({ command, args }), resolvedBinDirs: () => [toolchainBin], prependResolvedToEnv, resolve: () => ({ rootDir: '/managed/node', binDir: toolchainBin }) - } as never) + } as never const client = createMcpClient('test', { type: 'stdio', diff --git a/test/main/mcp/mcpOAuthFlow.test.ts b/test/main/mcp/mcpOAuthFlow.test.ts index aba0e6ee29..5030f8d72c 100644 --- a/test/main/mcp/mcpOAuthFlow.test.ts +++ b/test/main/mcp/mcpOAuthFlow.test.ts @@ -15,14 +15,12 @@ import { McpOAuthManager } from '@/mcp/mcpOAuthManager' import { DeepChatMcpOAuthProvider } from '@/mcp/mcpOAuthProvider' import { McpOAuthCredentialStore } from '@/mcp/oauthCredentialStore' import { MCP_OAUTH_REDIRECT_PATH, MCP_OAUTH_REDIRECT_PORT } from '@/mcp/oauthConstants' - const endpoint = 'https://mcp.example/mcp' const identity = { serverId: '11111111-1111-4111-8111-111111111111', configGeneration: 1, bindingHash: 'a'.repeat(64) } - describe('MCP interactive OAuth', () => { let manager: McpOAuthManager let store: McpOAuthCredentialStore @@ -31,7 +29,6 @@ describe('MCP interactive OAuth', () => { let registrations: OAuthClientMetadata[] let tokenRequests: URLSearchParams[] let reservedServers: Server[] - beforeEach(() => { registrations = [] tokenRequests = [] @@ -40,9 +37,8 @@ describe('MCP interactive OAuth', () => { vi.mocked(safeStorage.isEncryptionAvailable).mockReturnValue(false) vi.mocked(shell.openExternal).mockResolvedValue(undefined) }) - afterEach(async () => { - manager?.logout('example', config) + await manager?.logout('example', config) await Promise.all( reservedServers .filter((server) => server.listening) @@ -50,7 +46,6 @@ describe('MCP interactive OAuth', () => { ) vi.unstubAllGlobals() }) - async function registeredCallbackUri(occupied = false): Promise { const server = createServer().listen(0, '127.0.0.1') reservedServers.push(server) @@ -61,7 +56,6 @@ describe('MCP interactive OAuth', () => { } return `http://localhost:${port}/mcp/oauth/callback/legacy-random-path` } - function setup(issuer: string, clientMetadataUrl?: string): void { config = { ...identity, @@ -114,15 +108,24 @@ describe('MCP interactive OAuth', () => { throw new Error(`Unexpected OAuth request: ${url}`) }) ) - store = new McpOAuthCredentialStore('/tmp/deepchat-oauth-flow/credentials.json') - manager = new McpOAuthManager(store, vi.fn()) + store = new McpOAuthCredentialStore({ + filePath: '/tmp/deepchat-oauth-flow/credentials.json', + crypto: { + isAvailable: async () => false, + encrypt: async () => { + throw new Error('Unexpected native encryption') + }, + decrypt: async () => { + throw new Error('Unexpected native decryption') + } + } + }) + manager = new McpOAuthManager(store, shell.openExternal, vi.fn()) } - function latestAuthorization(): URL { const calls = vi.mocked(shell.openExternal).mock.calls return new URL(calls[calls.length - 1][0]) } - function completeAuthorization( parameters: Record = { code: 'authorization-code' } ) { @@ -135,9 +138,8 @@ describe('MCP interactive OAuth', () => { } return manager.completeAuthFromCallbackUrl('example', config, callback.toString()) } - - function seedClient(redirectUris?: string[], clientId = 'legacy-client'): void { - store.saveEntry('legacy-credential', { + async function seedClient(redirectUris?: string[], clientId = 'legacy-client'): Promise { + await store.saveEntry('legacy-credential', { binding: { ...identity, endpoint, @@ -159,7 +161,6 @@ describe('MCP interactive OAuth', () => { } }) } - it.each(['https://auth.example', 'https://auth.example/'])( 'exchanges and refreshes tokens for issuer %s', async (issuer) => { @@ -167,7 +168,6 @@ describe('MCP interactive OAuth', () => { expect((await manager.startAuth('example', config)).state).toBe('authenticating') const authorization = latestAuthorization() expect((await completeAuthorization()).state).toBe('authenticated') - const verifier = tokenRequests[0].get('code_verifier')! expect(createHash('sha256').update(verifier).digest('base64url')).toBe( authorization.searchParams.get('code_challenge') @@ -192,13 +192,11 @@ describe('MCP interactive OAuth', () => { ).toBeUndefined() } ) - it('reuses the registered callback after denial with fresh state and PKCE', async () => { setup('https://auth.example') await manager.startAuth('example', config) const first = latestAuthorization() expect((await completeAuthorization({ error: 'access_denied' })).state).toBe('error') - expect((await manager.startAuth('example', config)).state).toBe('authenticating') const second = latestAuthorization() expect(new URL(second.searchParams.get('redirect_uri')!).pathname).toBe( @@ -212,21 +210,22 @@ describe('MCP interactive OAuth', () => { expect((await completeAuthorization()).state).toBe('authenticated') expect(registrations).toHaveLength(1) }) - it.each(['bound', 'legacy'])( 'preserves a %s registration with a raw stored issuer and normalized config', async (storage) => { setup('https://auth.example') const registeredUri = await registeredCallbackUri() - seedClient([registeredUri]) - store.clearEntryScope('legacy-credential', 'tokens') + await seedClient([registeredUri]) + await store.clearEntryScope('legacy-credential', 'tokens') config.authorization!.authorizationServerIssuer = 'https://auth.example/' if (storage === 'legacy') { const key = createHash('sha256').update(`example\nhttp\n${endpoint}`).digest('hex') - store.saveEntry(key, { ...store.load('legacy-credential'), binding: undefined }) - store.clearEntry('legacy-credential') + await store.saveEntry(key, { + ...(await store.load('legacy-credential')), + binding: undefined + }) + await store.clearEntry('legacy-credential') } - expect((await manager.startAuth('example', config)).state).toBe('authenticating') const authorization = latestAuthorization() expect(new URL(authorization.searchParams.get('redirect_uri')!).pathname).toBe( @@ -235,11 +234,13 @@ describe('MCP interactive OAuth', () => { expect(authorization.searchParams.get('client_id')).toBe('legacy-client') expect((await completeAuthorization()).state).toBe('authenticated') expect(registrations).toHaveLength(0) - const stored = store.findInteractiveCredential({ - ...identity, - endpoint, - authorizationServerIssuer: 'https://auth.example/' - })?.entry + const stored = ( + await store.findInteractiveCredential({ + ...identity, + endpoint, + authorizationServerIssuer: 'https://auth.example/' + }) + )?.entry expect(stored?.binding?.authorizationServerIssuer).toBe('https://auth.example/') expect(stored?.discoveryState?.authorizationServerMetadata?.issuer).toBe( 'https://auth.example' @@ -247,15 +248,13 @@ describe('MCP interactive OAuth', () => { expect(stored?.clientInformation?.issuer).toBe('https://auth.example') } ) - it.each([false, true])( 'handles a registered callback port with occupied=%s', async (occupied) => { setup('https://auth.example') const registeredUri = await registeredCallbackUri(occupied) - seedClient([registeredUri]) - store.clearEntryScope('legacy-credential', 'tokens') - + await seedClient([registeredUri]) + await store.clearEntryScope('legacy-credential', 'tokens') expect((await manager.startAuth('example', config)).state).toBe('authenticating') const authorization = latestAuthorization() const redirect = new URL(authorization.searchParams.get('redirect_uri')!) @@ -272,27 +271,29 @@ describe('MCP interactive OAuth', () => { expect((await completeAuthorization()).state).toBe('authenticated') } ) - - it.each(['tokens', 'clientInformation'])('ignores a malformed stored issuer in %s', (scope) => { - setup('https://auth.example') - store.saveEntry( - 'malformed-credential', - scope === 'tokens' - ? { tokens: { access_token: 'stale-token', issuer: 'not-a-url' } } - : { clientInformation: { client_id: 'stale-client', issuer: 'not-a-url' } } - ) - const provider = new DeepChatMcpOAuthProvider({ - store, - binding: { ...identity, endpoint }, - initialCredentialKey: 'malformed-credential', - credentialKey: (binding) => JSON.stringify(binding), - redirectUrl: 'http://localhost:1456/mcp/oauth/callback' - }) - - expect(provider.clientInformation({ issuer: 'https://auth.example' })).toBeUndefined() - expect(provider.tokens({ issuer: 'https://auth.example' })).toBeUndefined() - }) - + it.each(['tokens', 'clientInformation'])( + 'ignores a malformed stored issuer in %s', + async (scope) => { + setup('https://auth.example') + await store.saveEntry( + 'malformed-credential', + scope === 'tokens' + ? { tokens: { access_token: 'stale-token', token_type: 'Bearer', issuer: 'not-a-url' } } + : { clientInformation: { client_id: 'stale-client', issuer: 'not-a-url' } } + ) + const provider = new DeepChatMcpOAuthProvider({ + openExternal: shell.openExternal, + assertActive: () => undefined, + store, + binding: { ...identity, endpoint }, + initialCredentialKey: 'malformed-credential', + credentialKey: (binding) => JSON.stringify(binding), + redirectUrl: 'http://localhost:1456/mcp/oauth/callback' + }) + expect(await provider.clientInformation({ issuer: 'https://auth.example' })).toBeUndefined() + expect(await provider.tokens({ issuer: 'https://auth.example' })).toBeUndefined() + } + ) it.each([ { redirectUris: undefined }, { redirectUris: [] }, @@ -306,8 +307,7 @@ describe('MCP interactive OAuth', () => { 're-registers a client without a usable local callback: $redirectUris', async ({ redirectUris }) => { setup('https://auth.example') - seedClient(redirectUris) - + await seedClient(redirectUris) expect((await manager.startAuth('example', config)).state).toBe('authenticating') expect(registrations).toHaveLength(1) expect(tokenRequests).toHaveLength(0) @@ -319,20 +319,17 @@ describe('MCP interactive OAuth', () => { expect((await completeAuthorization()).state).toBe('authenticated') } ) - it('keeps an existing dynamic registration when a client metadata URL is configured', async () => { setup('https://auth.example', 'https://client.example/deepchat.json') const registeredUri = await registeredCallbackUri() - seedClient([registeredUri]) - store.clearEntryScope('legacy-credential', 'tokens') - + await seedClient([registeredUri]) + await store.clearEntryScope('legacy-credential', 'tokens') expect((await manager.startAuth('example', config)).state).toBe('authenticating') expect(latestAuthorization().searchParams.get('client_id')).toBe('legacy-client') expect(latestAuthorization().searchParams.get('redirect_uri')).toBe(registeredUri) expect((await completeAuthorization()).state).toBe('authenticated') expect(registrations).toHaveLength(0) }) - it.each([false, true])( 'uses a published callback for a URL-based client with existing=%s', async (existing) => { @@ -343,8 +340,8 @@ describe('MCP interactive OAuth', () => { } setup('https://auth.example', clientMetadataUrl) if (existing) { - seedClient(undefined, clientMetadataUrl) - store.clearEntryScope('legacy-credential', 'tokens') + await seedClient(undefined, clientMetadataUrl) + await store.clearEntryScope('legacy-credential', 'tokens') } vi.mocked(shell.openExternal).mockImplementation(async (url) => { const authorization = new URL(url) @@ -354,7 +351,6 @@ describe('MCP interactive OAuth', () => { redirect.port = new URL(publishedMetadata.redirect_uris[0]).port expect(publishedMetadata.redirect_uris).toContain(redirect.href) }) - expect((await manager.startAuth('example', config)).state).toBe('authenticating') expect((await completeAuthorization()).state).toBe('authenticated') expect(tokenRequests[0].get('redirect_uri')).toBe( @@ -363,4 +359,27 @@ describe('MCP interactive OAuth', () => { expect(registrations).toHaveLength(0) } ) + + it('rejects a delayed browser acknowledgement after logout without restoring credentials', async () => { + setup('https://auth.example') + let acknowledge!: () => void + let opened!: () => void + const opening = new Promise((resolve) => { + opened = resolve + }) + vi.mocked(shell.openExternal).mockImplementationOnce(() => { + opened() + return new Promise((resolve) => { + acknowledge = resolve + }) + }) + const pending = manager.startAuth('example', config) + const rejected = expect(pending).rejects.toThrow('interrupted') + await opening + expect((await manager.logout('example', config)).authenticated).toBe(false) + acknowledge() + await rejected + expect((await manager.getStatus('example', config)).authenticated).toBe(false) + expect(tokenRequests).toHaveLength(0) + }) }) diff --git a/test/main/mcp/mcpOAuthManager.test.ts b/test/main/mcp/mcpOAuthManager.test.ts index 0dec815fd9..4a1e51d1df 100644 --- a/test/main/mcp/mcpOAuthManager.test.ts +++ b/test/main/mcp/mcpOAuthManager.test.ts @@ -1,11 +1,9 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' - const publishDeepchatEventMock = vi.hoisted(() => vi.fn()) const discoverOAuthServerInfoMock = vi.hoisted(() => vi.fn()) const authMock = vi.hoisted(() => vi.fn()) const clientCredentialsProviderMock = vi.hoisted(() => vi.fn()) const privateKeyJwtProviderMock = vi.hoisted(() => vi.fn()) - vi.mock('@modelcontextprotocol/client', async (importOriginal) => ({ ...(await importOriginal()), auth: authMock, @@ -21,77 +19,79 @@ vi.mock('@modelcontextprotocol/client', async (importOriginal) => ({ } } })) - +import type { OAuthDiscoveryState } from '@modelcontextprotocol/client' import { McpOAuthManager } from '@/mcp/mcpOAuthManager' import { McpEnterpriseIdentityManager } from '@/mcp/enterpriseIdentityManager' -import type { McpOAuthCredentialStore } from '@/mcp/oauthCredentialStore' +import type { McpOAuthCredentialEntry, McpOAuthCredentialStore } from '@/mcp/oauthCredentialStore' import type { McpSettings } from '@/mcp/settings' - const createStore = (entry: unknown): McpOAuthCredentialStore => ({ - getStorageState: vi.fn(() => 'file'), - isPersistent: vi.fn(() => true), - load: vi.fn(() => entry), - findInteractiveCredential: vi.fn(() => null), - saveEntry: vi.fn(), - clearEntry: vi.fn(), - clearEntryScope: vi.fn(), - loadClientSecret: vi.fn(() => null), - loadPrivateKey: vi.fn(() => null), - loadEnterpriseResourceSecret: vi.fn(() => null), - getCredentialRecordStatus: vi.fn(() => ({ configured: false })) + getStorageState: vi.fn(async () => 'safeStorage'), + isPersistent: vi.fn(async () => true), + load: vi.fn(async () => entry), + findInteractiveCredential: vi.fn(async () => null), + saveEntry: vi.fn(async () => undefined), + moveInteractiveEntry: vi.fn(async (_from: string, key: string) => + entry ? { key, entry: entry as McpOAuthCredentialEntry } : null + ), + stop: vi.fn(), + clearEntry: vi.fn(async () => undefined), + clearEntryScope: vi.fn(async () => undefined), + clearServerCredentials: vi.fn(async () => undefined), + loadClientSecret: vi.fn(async () => null), + loadPrivateKey: vi.fn(async () => null), + loadEnterpriseResourceSecret: vi.fn(async () => null), + getCredentialRecordStatus: vi.fn(async () => ({ configured: false })) }) as unknown as McpOAuthCredentialStore - const serverIdentity = { serverId: '11111111-1111-4111-8111-111111111111', configGeneration: 1, bindingHash: 'a'.repeat(64) } - describe('McpOAuthManager', () => { beforeEach(() => { vi.clearAllMocks() }) - afterEach(() => { vi.restoreAllMocks() }) - it('keeps authenticated credentials ahead of stale non-pending errors', async () => { const manager = new McpOAuthManager( createStore({ tokens: { - access_token: 'access-token' + access_token: 'access-token', + token_type: 'Bearer' }, discoveryState: { authorizationServerUrl: 'https://auth.example' }, updatedAt: 123 }), + vi.fn(async () => undefined), publishDeepchatEventMock ) const config = { - type: 'http', + type: 'http' as const, baseUrl: 'https://mcp.linear.app/mcp', ...serverIdentity } - - manager.handleConnectionError('linear', config, new Error('401 unauthorized')) - + await manager.handleConnectionError('linear', config, new Error('401 unauthorized')) const status = await manager.completeAuthFromCallbackUrl( 'linear', config, 'http://localhost:3333/callback?code=used&state=used' ) - expect(status.state).toBe('authenticated') expect(status.authenticated).toBe(true) expect(status.error).toBeUndefined() expect(publishDeepchatEventMock).toHaveBeenCalledTimes(1) }) - it('accepts callback input bound to the pre-discovery OAuth configuration', async () => { - const manager = new McpOAuthManager(createStore(null), publishDeepchatEventMock) + const manager = new McpOAuthManager( + createStore(null), + vi.fn(async () => undefined), + publishDeepchatEventMock + ) const initialBinding = { ...serverIdentity, endpoint: 'https://mcp.example/mcp' @@ -103,7 +103,9 @@ describe('McpOAuthManager', () => { url: 'http://localhost/callback?code=code&state=state' })) const failAuthFlow = vi.spyOn( - manager as unknown as { failAuthFlow: (...args: unknown[]) => void }, + manager as unknown as { + failAuthFlow: (...args: unknown[]) => void + }, 'failAuthFlow' ) const internals = manager as unknown as { @@ -129,49 +131,49 @@ describe('McpOAuthManager', () => { }, flowPromise: Promise.resolve() }) - await manager.completeAuthFromCallbackUrl( 'example', { - type: 'http', + type: 'http' as const, baseUrl: initialBinding.endpoint, authorization: { mode: 'interactive' }, ...serverIdentity }, 'http://localhost/callback?code=code&state=state' ) - expect(resolveCallbackUrl).toHaveBeenCalledOnce() expect(failAuthFlow).not.toHaveBeenCalled() }) - - it('classifies HTTP status shaped OAuth failures', () => { + it('classifies HTTP status shaped OAuth failures', async () => { const config = { - type: 'http', + type: 'http' as const, baseUrl: 'https://mcp.linear.app/mcp', ...serverIdentity } as const const errors = [{ status: 401 }, { httpStatus: 401 }, { response: { status: '401' } }] - for (const error of errors) { - const manager = new McpOAuthManager(createStore(null), publishDeepchatEventMock) - - expect(manager.handleConnectionError('linear', config, error)).toBe(true) - expect(manager.getStatus('linear', config).state).toBe('required') + const manager = new McpOAuthManager( + createStore(null), + vi.fn(async () => undefined), + publishDeepchatEventMock + ) + expect(await manager.handleConnectionError('linear', config, error)).toBe(true) + expect((await manager.getStatus('linear', config)).state).toBe('required') } }) - - it('ignores stale authenticated flows after a newer auth attempt starts', () => { + it('ignores stale authenticated flows after a newer auth attempt starts', async () => { const closeStaleSession = vi.fn() const closeActiveSession = vi.fn() const onAuthenticated = vi.fn() const manager = new McpOAuthManager( createStore({ tokens: { - access_token: 'access-token' + access_token: 'access-token', + token_type: 'Bearer' }, updatedAt: 123 }), + vi.fn(async () => undefined), publishDeepchatEventMock, onAuthenticated ) @@ -196,16 +198,13 @@ describe('McpOAuthManager', () => { pendingFlows: Map finishAuthenticatedFlow: (flow: unknown) => void } - managerInternals.pendingFlows.set('linear', activeFlow) - managerInternals.finishAuthenticatedFlow(staleFlow) - + await managerInternals.finishAuthenticatedFlow(staleFlow) expect(closeStaleSession).not.toHaveBeenCalled() expect(closeActiveSession).not.toHaveBeenCalled() expect(onAuthenticated).not.toHaveBeenCalled() expect(publishDeepchatEventMock).not.toHaveBeenCalled() }) - it('rejects an interactive credential when live discovery changes its issuer', async () => { discoverOAuthServerInfoMock.mockResolvedValue({ authorizationServerUrl: 'https://auth.new.example/', @@ -217,7 +216,7 @@ describe('McpOAuthManager', () => { } }) const staleEntry = { - tokens: { access_token: 'stale-token' }, + tokens: { access_token: 'stale-token', token_type: 'Bearer' }, binding: { ...serverIdentity, endpoint: 'https://mcp.example/mcp', @@ -227,15 +226,18 @@ describe('McpOAuthManager', () => { updatedAt: 123 } const store = createStore(null) - vi.mocked(store.findInteractiveCredential).mockReturnValue({ + vi.mocked(store.findInteractiveCredential).mockResolvedValue({ key: 'stale-key', entry: staleEntry }) - const manager = new McpOAuthManager(store, publishDeepchatEventMock) - + const manager = new McpOAuthManager( + store, + vi.fn(async () => undefined), + publishDeepchatEventMock + ) await expect( manager.createRuntimeProvider('example', { - type: 'http', + type: 'http' as const, baseUrl: 'https://mcp.example/mcp', authorization: { mode: 'interactive' }, ...serverIdentity @@ -243,12 +245,14 @@ describe('McpOAuthManager', () => { ).resolves.toBeUndefined() expect(store.clearEntry).toHaveBeenCalledWith('stale-key') }) - it('finalizes the persisted server binding from interactive discovery', async () => { - const discovery = { + const discovery: OAuthDiscoveryState = { authorizationServerUrl: 'https://auth.example/', authorizationServerMetadata: { - issuer: 'https://auth.example/' + issuer: 'https://auth.example/', + authorization_endpoint: 'https://auth.example/authorize', + token_endpoint: 'https://auth.example/token', + response_types_supported: ['code'] }, resourceMetadata: { resource: 'https://mcp.example/mcp' @@ -256,7 +260,7 @@ describe('McpOAuthManager', () => { } discoverOAuthServerInfoMock.mockResolvedValue(discovery) const entry = { - tokens: { access_token: 'access-token' }, + tokens: { access_token: 'access-token', token_type: 'Bearer' }, clientInformation: { client_id: 'dynamic-client' }, discoveryState: discovery, binding: { @@ -269,7 +273,7 @@ describe('McpOAuthManager', () => { updatedAt: 123 } const store = createStore(entry) - vi.mocked(store.findInteractiveCredential).mockReturnValue({ + vi.mocked(store.findInteractiveCredential).mockResolvedValue({ key: 'old-key', entry }) @@ -287,50 +291,48 @@ describe('McpOAuthManager', () => { } const settings = { getMcpServers: vi.fn(async () => ({ example: currentConfig })), - updateMcpServer: vi.fn(async (_serverName: string, update: Record) => { + finalizeOAuthBinding: vi.fn((_expected: unknown, authorization: unknown) => { currentConfig = { ...currentConfig, - ...update, + authorization, configGeneration: 2, bindingHash: 'b'.repeat(64) } as typeof currentConfig + return currentConfig }) } const bindingChanged = vi.fn() const manager = new McpOAuthManager( store, + vi.fn(async () => undefined), publishDeepchatEventMock, undefined, settings as never, bindingChanged ) - await expect(manager.createRuntimeProvider('example', currentConfig)).resolves.toBeDefined() - - expect(settings.updateMcpServer).toHaveBeenCalledWith('example', { - authorization: { + expect(settings.finalizeOAuthBinding).toHaveBeenCalledWith( + expect.objectContaining(serverIdentity), + { mode: 'interactive', authorizationServerIssuer: 'https://auth.example/', protectedResourceUrl: 'https://mcp.example/mcp', clientId: 'dynamic-client' } - }) + ) expect(bindingChanged).toHaveBeenCalledOnce() - expect(store.saveEntry).toHaveBeenCalledWith( + expect(store.moveInteractiveEntry).toHaveBeenCalledWith( + 'old-key', expect.not.stringMatching(/^old-key$/), expect.objectContaining({ - binding: expect.objectContaining({ - configGeneration: 2, - bindingHash: 'b'.repeat(64), - authorizationServerIssuer: 'https://auth.example/', - protectedResourceUrl: 'https://mcp.example/mcp', - clientId: 'dynamic-client' - }) + configGeneration: 2, + bindingHash: 'b'.repeat(64), + authorizationServerIssuer: 'https://auth.example/', + protectedResourceUrl: 'https://mcp.example/mcp', + clientId: 'dynamic-client' }) ) - expect(store.clearEntry).toHaveBeenCalledWith('old-key') }) - it('binds client credentials to the configured issuer, resource, and server', async () => { const endpoint = 'https://mcp.example/mcp' const issuer = 'https://auth.example/' @@ -354,12 +356,12 @@ describe('McpOAuthManager', () => { ...serverIdentity } const store = createStore(null) - vi.mocked(store.loadClientSecret).mockReturnValue({ + vi.mocked(store.loadClientSecret).mockResolvedValue({ secret: 'client-secret', binding, updatedAt: 1 }) - vi.mocked(store.getCredentialRecordStatus).mockReturnValue({ + vi.mocked(store.getCredentialRecordStatus).mockResolvedValue({ configured: true, updatedAt: 1 }) @@ -372,10 +374,12 @@ describe('McpOAuthManager', () => { resourceMetadata: { resource: endpoint } }) authMock.mockResolvedValue('AUTHORIZED') - const manager = new McpOAuthManager(store, publishDeepchatEventMock) - + const manager = new McpOAuthManager( + store, + vi.fn(async () => undefined), + publishDeepchatEventMock + ) const provider = await manager.createRuntimeProvider('example', config) - expect(clientCredentialsProviderMock).toHaveBeenCalledWith( expect.objectContaining({ clientId: 'machine-client', @@ -404,7 +408,6 @@ describe('McpOAuthManager', () => { new URL(endpoint) ) }) - it('preserves successful machine authentication when the server restart fails', async () => { const endpoint = 'https://mcp.example/mcp' const issuer = 'https://auth.example/' @@ -427,12 +430,12 @@ describe('McpOAuthManager', () => { ...serverIdentity } const store = createStore(null) - vi.mocked(store.loadClientSecret).mockReturnValue({ + vi.mocked(store.loadClientSecret).mockResolvedValue({ secret: 'client-secret', binding, updatedAt: 1 }) - vi.mocked(store.getCredentialRecordStatus).mockReturnValue({ + vi.mocked(store.getCredentialRecordStatus).mockResolvedValue({ configured: true, updatedAt: 1 }) @@ -446,17 +449,23 @@ describe('McpOAuthManager', () => { }) authMock.mockResolvedValue('AUTHORIZED') const onAuthenticated = vi.fn().mockRejectedValue(new Error('restart failed')) - const manager = new McpOAuthManager(store, publishDeepchatEventMock, onAuthenticated) - + const manager = new McpOAuthManager( + store, + vi.fn(async () => undefined), + publishDeepchatEventMock, + onAuthenticated + ) const status = await manager.startAuth('example', config) - - expect(onAuthenticated).toHaveBeenCalledWith('example') + expect(onAuthenticated).toHaveBeenCalledWith( + serverIdentity.serverId, + 'restart', + expect.any(Function) + ) expect(status).toMatchObject({ state: 'authenticated', authenticated: true }) }) - it('fails closed before selecting a private-key provider with incompatible discovery', async () => { const endpoint = 'https://mcp.example/mcp' const issuer = 'https://auth.example/' @@ -479,14 +488,14 @@ describe('McpOAuthManager', () => { ...serverIdentity } const store = createStore(null) - vi.mocked(store.loadPrivateKey).mockReturnValue({ + vi.mocked(store.loadPrivateKey).mockResolvedValue({ privateKey: 'private-key', algorithm: 'RS256', fingerprint: 'fingerprint', binding, updatedAt: 1 }) - vi.mocked(store.getCredentialRecordStatus).mockReturnValue({ + vi.mocked(store.getCredentialRecordStatus).mockResolvedValue({ configured: true, updatedAt: 1, fingerprint: 'fingerprint' @@ -501,14 +510,16 @@ describe('McpOAuthManager', () => { } discoverOAuthServerInfoMock.mockResolvedValue(discovery) authMock.mockResolvedValue('AUTHORIZED') - const manager = new McpOAuthManager(store, publishDeepchatEventMock) - + const manager = new McpOAuthManager( + store, + vi.fn(async () => undefined), + publishDeepchatEventMock + ) await expect(manager.createRuntimeProvider('example', config)).rejects.toThrow( 'does not support private_key_jwt' ) expect(privateKeyJwtProviderMock).not.toHaveBeenCalled() expect(authMock).not.toHaveBeenCalled() - discoverOAuthServerInfoMock.mockResolvedValue({ ...discovery, authorizationServerMetadata: { @@ -517,7 +528,6 @@ describe('McpOAuthManager', () => { } }) const provider = await manager.createRuntimeProvider('example', config) - expect(privateKeyJwtProviderMock).toHaveBeenCalledWith( expect.objectContaining({ clientId: 'machine-client', @@ -528,7 +538,6 @@ describe('McpOAuthManager', () => { provider ) }) - it('requires ID-JAG discovery before binding cross-app access credentials', async () => { const endpoint = 'https://mcp.example/mcp' const issuer = 'https://auth.example/' @@ -552,12 +561,12 @@ describe('McpOAuthManager', () => { ...serverIdentity } const store = createStore(null) - vi.mocked(store.loadEnterpriseResourceSecret).mockReturnValue({ + vi.mocked(store.loadEnterpriseResourceSecret).mockResolvedValue({ secret: 'target-secret', binding, updatedAt: 1 }) - vi.mocked(store.getCredentialRecordStatus).mockReturnValue({ + vi.mocked(store.getCredentialRecordStatus).mockResolvedValue({ configured: true, updatedAt: 1 }) @@ -578,17 +587,16 @@ describe('McpOAuthManager', () => { ) const manager = new McpOAuthManager( store, + vi.fn(async () => undefined), publishDeepchatEventMock, undefined, {} as McpSettings ) - await expect(manager.createRuntimeProvider('example', config)).rejects.toThrow( 'does not support the ID-JAG grant profile' ) expect(createCrossAppProvider).not.toHaveBeenCalled() expect(store.loadEnterpriseResourceSecret).not.toHaveBeenCalled() - discoverOAuthServerInfoMock.mockResolvedValue({ ...discovery, authorizationServerMetadata: { @@ -597,11 +605,47 @@ describe('McpOAuthManager', () => { } }) const provider = await manager.createRuntimeProvider('example', config) - expect(createCrossAppProvider).toHaveBeenCalledWith(config, 'target-secret', issuer) expect(authMock).toHaveBeenCalledWith(provider, { serverUrl: endpoint, scope: undefined }) }) + + it('rejects a delayed runtime stop after the server binding is invalidated', async () => { + const store = createStore(null) + let finish!: () => void + let entered!: () => void + const stopping = new Promise((resolve) => { + entered = resolve + }) + const effects: string[] = [] + const onRuntimeChange = vi.fn( + async (_id: string, action: 'restart' | 'stop', assertCurrent: () => void) => { + if (action === 'stop') { + entered() + await new Promise((resolve) => { + finish = resolve + }) + } + assertCurrent() + effects.push(action) + } + ) + const manager = new McpOAuthManager( + store, + vi.fn(async () => undefined), + publishDeepchatEventMock, + onRuntimeChange + ) + const binding = { ...serverIdentity, endpoint: 'https://mcp.example/mcp' } + const removing = manager.removeCredential(binding, 'client_secret') + const rejected = expect(removing).rejects.toThrow('interrupted') + await stopping + await manager.clearServerCredentials(binding.serverId) + finish() + await rejected + expect(effects).toEqual([]) + expect(store.clearEntry).toHaveBeenCalledOnce() + }) }) diff --git a/test/main/mcp/mcpService.test.ts b/test/main/mcp/mcpService.test.ts index a4f1e7beb0..47b18545f0 100644 --- a/test/main/mcp/mcpService.test.ts +++ b/test/main/mcp/mcpService.test.ts @@ -1,3 +1,5 @@ +import { createPluginToolPolicies } from '../../helpers/pluginToolPolicies' +import type { McpToolDefinitionsSnapshot, MCPServerConfig } from '@shared/types/mcp' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' const serverManagerMocks = vi.hoisted(() => ({ @@ -19,7 +21,9 @@ const serverManagerMocks = vi.hoisted(() => ({ const toolManagerMocks = vi.hoisted(() => ({ getAllToolDefinitions: vi.fn().mockResolvedValue([]), - snapshotCachedToolDefinitions: vi.fn(() => ({ state: 'uninitialized' as const })), + snapshotCachedToolDefinitions: vi.fn( + (): McpToolDefinitionsSnapshot => ({ state: 'uninitialized' }) + ), getRunningClients: vi.fn().mockResolvedValue([]), invalidateRegistry: vi.fn(), callTool: vi.fn() @@ -81,6 +85,7 @@ vi.mock('@/agent/shared/process/childProcessRegistry', () => ({ })) import { McpService } from '../../../src/main/mcp' +import { McpOAuthCredentialStore } from '@/mcp/oauthCredentialStore' import { ToolManager } from '../../../src/main/mcp/toolManager' import type { CacheImageOptions } from '../../../src/main/platform/imageCache' @@ -110,18 +115,53 @@ const createMcpService = ( providerSettings, { getCustomPrompts: vi.fn().mockResolvedValue([]) }, providerSettings, - { isEnabled: () => providerSettings.privacyModeEnabled === true }, + { + isEnabled: () => providerSettings.privacyModeEnabled === true, + setEnabled: async () => undefined + }, vi.fn() as never, {} as never, onRegistryChanged, semanticNotificationsMock, publishDeepchatEventMock, + createPluginToolPolicies(), + { + homeDirectory: '/mock/home', + appVersion: '1.0.0', + toolchains: { + rewriteCommand: (command, args) => ({ command, args }), + resolvedBinDirs: () => [], + resolve: () => { + throw new Error('Toolchain is not configured') + } + }, + getProxyUrl: async () => null + }, + { + store: new McpOAuthCredentialStore({ + filePath: '/tmp/deepchat-mcp-service/credentials.json', + crypto: { + isAvailable: async () => false, + encrypt: async () => { + throw new Error('Unexpected encryption') + }, + decrypt: async () => { + throw new Error('Unexpected decryption') + } + } + }), + openExternal: vi.fn(async () => undefined) + }, cacheImage, undefined, undefined, mcpApps ? ({ registry: mcpApps.registry, + desktop: { + observeOwner: vi.fn(async () => undefined), + openExternal: vi.fn(async () => undefined) + }, permissionBroker: {}, getPermissionMode: vi.fn(), validateSource: vi.fn(), @@ -439,12 +479,28 @@ describe('McpService', () => { it('returns a typed duplicate result without mutating settings', async () => { const providerSettings = createProviderSettings(true, false, { - existing: { type: 'stdio', command: 'node' } + existing: { + type: 'stdio', + command: 'node', + args: [], + env: {}, + descriptions: '', + icons: '', + enabled: false + } }) const presenter = createMcpService(providerSettings) await expect( - presenter.addMcpServer('existing', { type: 'stdio', command: 'node' }) + presenter.addMcpServer('existing', { + type: 'stdio', + command: 'node', + args: [], + env: {}, + descriptions: '', + icons: '', + enabled: false + }) ).resolves.toEqual({ status: 'duplicate' }) expect(providerSettings.addMcpServer).not.toHaveBeenCalled() @@ -454,7 +510,15 @@ describe('McpService', () => { it('persists a new server and returns an added result', async () => { const providerSettings = createProviderSettings(true) const presenter = createMcpService(providerSettings) - const config = { type: 'stdio', command: 'node' } as const + const config: MCPServerConfig = { + type: 'stdio', + command: 'node', + args: [], + env: {}, + descriptions: '', + icons: '', + enabled: false + } await expect(presenter.addMcpServer('new-server', config)).resolves.toEqual({ status: 'added' @@ -471,7 +535,15 @@ describe('McpService', () => { servers[name] = config }) const presenter = createMcpService(providerSettings) - const config = { type: 'stdio', command: 'node' } as const + const config: MCPServerConfig = { + type: 'stdio', + command: 'node', + args: [], + env: {}, + descriptions: '', + icons: '', + enabled: false + } await expect( Promise.all([ diff --git a/test/main/mcp/oauthCredentialStore.test.ts b/test/main/mcp/oauthCredentialStore.test.ts index fcd340f6b9..d07d6a870e 100644 --- a/test/main/mcp/oauthCredentialStore.test.ts +++ b/test/main/mcp/oauthCredentialStore.test.ts @@ -1,204 +1,292 @@ -import * as fs from 'fs' -import { beforeEach, describe, expect, it, vi } from 'vitest' -import { safeStorage } from 'electron' +import * as fs from 'node:fs' +import { tmpdir } from 'node:os' +import { join } from 'node:path' +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' import { McpOAuthCredentialStore } from '@/mcp/oauthCredentialStore' +vi.mock('node:fs', async (importOriginal) => ({ + ...(await importOriginal()) +})) + +const binding = { + serverId: '71e8637a-1889-4749-b04e-f5a79adefb06', + configGeneration: 1, + bindingHash: 'binding', + endpoint: 'https://mcp.example.com/mcp' +} + +const deferred = () => { + let resolve!: (value: T) => void + const promise = new Promise((done) => { + resolve = done + }) + return { promise, resolve } +} + +const unwrap = (wrapped: string) => Buffer.from(wrapped, 'base64').toString('utf8') + describe('McpOAuthCredentialStore', () => { - let savedContent = '' + let directory: string + let filePath: string + let crypto: { + isAvailable: ReturnType Promise>> + encrypt: ReturnType Promise>> + decrypt: ReturnType Promise>> + } + let store: McpOAuthCredentialStore beforeEach(() => { - savedContent = '' - vi.mocked(fs.existsSync).mockImplementation(() => Boolean(savedContent)) - vi.mocked(fs.readFileSync).mockImplementation(() => savedContent) - vi.mocked(fs.statSync).mockImplementation( - () => ({ size: Buffer.byteLength(savedContent, 'utf8') }) as fs.Stats - ) - vi.mocked(fs.writeFileSync).mockImplementation((_, data) => { - savedContent = String(data) - }) - vi.mocked(fs.mkdirSync).mockImplementation(() => undefined as unknown as string) - vi.mocked(safeStorage.isEncryptionAvailable).mockReturnValue(false) - vi.mocked(safeStorage.encryptString).mockImplementation((value) => - Buffer.from(`wrapped:${value.split('').reverse().join('')}`, 'utf8') - ) - vi.mocked(safeStorage.decryptString).mockImplementation((value) => - value - .toString('utf8') - .replace(/^wrapped:/, '') - .split('') - .reverse() - .join('') - ) + directory = fs.mkdtempSync(join(tmpdir(), 'deepchat-oauth-')) + filePath = join(directory, 'credentials.json') + crypto = { + isAvailable: vi.fn(async () => true), + encrypt: vi.fn(async (value: string) => Buffer.from(value).toString('base64')), + decrypt: vi.fn(async (wrapped: string) => unwrap(wrapped)) + } + store = new McpOAuthCredentialStore({ filePath, crypto }) }) - it('removes only the requested credential scope', () => { - const store = new McpOAuthCredentialStore('/tmp/deepchat-mcp-oauth/credentials.json') + afterEach(() => { + store.stop() + vi.restoreAllMocks() + fs.rmSync(directory, { recursive: true, force: true }) + }) - store.saveEntry('linear', { - tokens: { - access_token: 'access-token' - }, - clientInformation: { - client_id: 'client-id' - }, + it('removes only the requested credential scope', async () => { + await store.saveEntry('linear', { + tokens: { access_token: 'access-token', token_type: 'Bearer' }, + clientInformation: { client_id: 'client-id' }, codeVerifier: 'verifier', - discoveryState: { - authorizationServerUrl: 'https://mcp.linear.app' - } + discoveryState: { authorizationServerUrl: 'https://mcp.linear.app' } }) - - store.clearEntryScope('linear', 'tokens') - - const entry = store.load('linear') - expect(entry?.tokens).toBeUndefined() - expect(entry?.clientInformation?.client_id).toBe('client-id') - expect(entry?.codeVerifier).toBe('verifier') - expect(entry?.discoveryState?.authorizationServerUrl).toBe('https://mcp.linear.app') + await store.clearEntryScope('linear', 'tokens') + expect(await store.load('linear')).toMatchObject({ + clientInformation: { client_id: 'client-id' }, + codeVerifier: 'verifier', + discoveryState: { authorizationServerUrl: 'https://mcp.linear.app' } + }) + expect((await store.load('linear'))?.tokens).toBeUndefined() }) - it('keeps credentials memory-only when secure storage is unavailable', () => { - const store = new McpOAuthCredentialStore('/tmp/deepchat-mcp-oauth/credentials.json') - const binding = { - serverId: '71e8637a-1889-4749-b04e-f5a79adefb06', - configGeneration: 1, - bindingHash: 'binding', - endpoint: 'https://mcp.example.com/mcp' - } - - store.saveClientSecret('secret-key', 'not-plaintext-on-disk', binding) - - expect(store.loadClientSecret('secret-key')).toMatchObject({ + it('keeps credentials memory-only when secure storage is unavailable', async () => { + crypto.isAvailable.mockResolvedValue(false) + await store.saveClientSecret('secret-key', 'not-plaintext-on-disk', binding) + expect(await store.loadClientSecret('secret-key')).toMatchObject({ secret: 'not-plaintext-on-disk', binding }) - expect(fs.writeFileSync).not.toHaveBeenCalled() - expect(store.getStorageState()).toBe('memory') + expect(fs.existsSync(filePath)).toBe(false) + expect(await store.getStorageState()).toBe('memory') }) - it('persists only a safeStorage envelope when encryption is available', () => { - vi.mocked(safeStorage.isEncryptionAvailable).mockReturnValue(true) - const store = new McpOAuthCredentialStore('/tmp/deepchat-mcp-oauth/credentials.json') - const binding = { - serverId: '71e8637a-1889-4749-b04e-f5a79adefb06', - configGeneration: 1, - bindingHash: 'binding', - endpoint: 'https://mcp.example.com/mcp' - } - - store.saveClientSecret('secret-key', 'protected-value', binding) - - const envelope = JSON.parse(savedContent) - expect(envelope).toMatchObject({ version: 2, storage: 'safeStorage' }) - expect(envelope).not.toHaveProperty('entries') - expect(savedContent).not.toContain('protected-value') - expect(fs.renameSync).toHaveBeenCalledOnce() + it('persists only a safeStorage envelope and reads v2 without rewriting it', async () => { + await store.saveClientSecret('secret-key', 'protected-value', binding) + const serialized = fs.readFileSync(filePath, 'utf8') + expect(JSON.parse(serialized)).toMatchObject({ version: 2, storage: 'safeStorage' }) + expect(JSON.parse(serialized)).not.toHaveProperty('entries') + expect(serialized).not.toContain('protected-value') + const reloaded = new McpOAuthCredentialStore({ filePath, crypto }) + expect(await reloaded.loadClientSecret('secret-key')).toMatchObject({ + secret: 'protected-value' + }) + expect(fs.readFileSync(filePath, 'utf8')).toBe(serialized) + expect(crypto.encrypt).toHaveBeenCalledOnce() + reloaded.stop() }) - it('removes a stale envelope when secure storage becomes unavailable at runtime', () => { - vi.mocked(safeStorage.isEncryptionAvailable).mockReturnValue(true) - const filePath = '/tmp/deepchat-mcp-oauth/credentials.json' - const store = new McpOAuthCredentialStore(filePath) - const binding = { - serverId: '71e8637a-1889-4749-b04e-f5a79adefb06', - configGeneration: 1, - bindingHash: 'binding', - endpoint: 'https://mcp.example.com/mcp' - } - - store.saveClientSecret('secret-key', 'protected-value', binding) - vi.mocked(safeStorage.isEncryptionAvailable).mockReturnValue(false) - store.clearEntry('secret-key') - - expect(fs.unlinkSync).toHaveBeenCalledWith(filePath) - expect(store.loadClientSecret('secret-key')).toBeNull() + it('removes a stale envelope when secure storage becomes unavailable at runtime', async () => { + await store.saveClientSecret('secret-key', 'protected-value', binding) + crypto.isAvailable.mockResolvedValue(false) + await store.clearEntry('secret-key') + expect(fs.existsSync(filePath)).toBe(false) + expect(await store.loadClientSecret('secret-key')).toBeNull() }) - it('loads a legacy plaintext envelope into memory and removes the file', () => { - savedContent = JSON.stringify({ - version: 1, - storage: 'file', - entries: { - legacy: { - tokens: { access_token: 'legacy-token' }, - updatedAt: 42 - } - }, - updatedAt: 42 - }) - const store = new McpOAuthCredentialStore('/tmp/deepchat-mcp-oauth/credentials.json') - - expect(store.load('legacy')?.tokens?.access_token).toBe('legacy-token') - expect(fs.unlinkSync).toHaveBeenCalledWith('/tmp/deepchat-mcp-oauth/credentials.json') - expect(fs.writeFileSync).not.toHaveBeenCalled() + it.each([false, true])('loads a legacy envelope with secure storage=%s', async (secure) => { + crypto.isAvailable.mockResolvedValue(secure) + const entries = { legacy: { tokens: { access_token: 'legacy-token' }, updatedAt: 42 } } + fs.writeFileSync( + filePath, + JSON.stringify( + secure + ? { + version: 1, + storage: 'safeStorage', + wrapped: await crypto.encrypt(JSON.stringify(entries)), + updatedAt: 42 + } + : { version: 1, storage: 'file', entries, updatedAt: 42 } + ) + ) + expect((await store.load('legacy'))?.tokens?.access_token).toBe('legacy-token') + if (secure) expect(JSON.parse(fs.readFileSync(filePath, 'utf8')).version).toBe(2) + else expect(fs.existsSync(filePath)).toBe(false) }) - it('preserves an unreadable envelope and refuses to overwrite stored credentials', () => { - vi.mocked(safeStorage.isEncryptionAvailable).mockReturnValue(true) - vi.mocked(safeStorage.decryptString).mockImplementationOnce(() => { - throw new Error('cannot decrypt') - }) - savedContent = JSON.stringify({ + it('preserves an unreadable envelope and refuses to overwrite stored credentials', async () => { + crypto.decrypt.mockRejectedValueOnce(new Error('cannot decrypt')) + const serialized = JSON.stringify({ version: 2, storage: 'safeStorage', wrapped: 'encrypted', updatedAt: 42 }) - const store = new McpOAuthCredentialStore('/tmp/deepchat-mcp-oauth/credentials.json') - - expect(store.load('stale')).toBeNull() - expect(safeStorage.decryptString).toHaveBeenCalledOnce() - expect(fs.unlinkSync).not.toHaveBeenCalled() - expect(() => - store.saveClientSecret('replacement', 'new-secret', { - serverId: '71e8637a-1889-4749-b04e-f5a79adefb06', - configGeneration: 1, - bindingHash: 'binding', - endpoint: 'https://mcp.example.com/mcp' - }) - ).toThrow('MCP credential store is unavailable') - expect(fs.writeFileSync).not.toHaveBeenCalled() + fs.writeFileSync(filePath, serialized) + expect(await store.load('stale')).toBeNull() + await expect(store.saveClientSecret('replacement', 'new-secret', binding)).rejects.toThrow( + 'unavailable' + ) + expect(crypto.decrypt).toHaveBeenCalledOnce() + expect(fs.readFileSync(filePath, 'utf8')).toBe(serialized) }) - it('matches interactive credentials by every finalized binding field', () => { - const store = new McpOAuthCredentialStore('/tmp/deepchat-mcp-oauth/credentials.json') - const baseBinding = { - serverId: '71e8637a-1889-4749-b04e-f5a79adefb06', - configGeneration: 1, - bindingHash: 'binding', - endpoint: 'https://mcp.example.com/mcp', - protectedResourceUrl: 'https://mcp.example.com/mcp' - } - store.saveEntry('issuer-a', { - tokens: { access_token: 'token-a' }, - binding: { - ...baseBinding, - authorizationServerIssuer: 'https://auth-a.example.com/', - clientId: 'client-a' - } - }) - store.saveEntry('issuer-b', { - tokens: { access_token: 'token-b' }, - binding: { - ...baseBinding, - authorizationServerIssuer: 'https://auth-b.example.com/', - clientId: 'client-b' - } - }) - + it('matches interactive credentials by every finalized binding field', async () => { + const base = { ...binding, protectedResourceUrl: binding.endpoint } + for (const id of ['a', 'b']) + await store.saveEntry(`issuer-${id}`, { + tokens: { access_token: `token-${id}`, token_type: 'Bearer' }, + binding: { + ...base, + authorizationServerIssuer: `https://auth-${id}.example/`, + clientId: `client-${id}` + } + }) expect( - store.findInteractiveCredential({ - ...baseBinding, - authorizationServerIssuer: 'https://auth-a.example.com/', - clientId: 'client-a' - })?.key + ( + await store.findInteractiveCredential({ + ...base, + authorizationServerIssuer: 'https://auth-a.example/', + clientId: 'client-a' + }) + )?.key ).toBe('issuer-a') expect( - store.findInteractiveCredential({ - ...baseBinding, - authorizationServerIssuer: 'https://auth-a.example.com/', + await store.findInteractiveCredential({ + ...base, + authorizationServerIssuer: 'https://auth-a.example/', clientId: 'client-b' }) ).toBeNull() - expect(store.findInteractiveCredential(baseBinding)).toBeNull() + expect(await store.findInteractiveCredential(base)).toBeNull() + }) + + it('serializes partial updates and shares a pending initial decrypt', async () => { + await store.saveEntry('entry', { codeVerifier: 'initial' }) + const raw = unwrap(JSON.parse(fs.readFileSync(filePath, 'utf8')).wrapped) + const gate = deferred() + const entered = deferred() + crypto.decrypt.mockImplementationOnce(() => { + entered.resolve() + return gate.promise + }) + const reloaded = new McpOAuthCredentialStore({ filePath, crypto }) + const reads = [reloaded.load('entry'), reloaded.load('entry')] + await entered.promise + expect(crypto.decrypt).toHaveBeenCalledOnce() + gate.resolve(raw) + expect(await Promise.all(reads)).toEqual([ + expect.objectContaining({ codeVerifier: 'initial' }), + expect.objectContaining({ codeVerifier: 'initial' }) + ]) + await Promise.all([ + reloaded.saveEntry('entry', { tokens: { access_token: 'new', token_type: 'Bearer' } }), + reloaded.saveEntry('entry', { clientInformation: { client_id: 'client' } }) + ]) + expect(await reloaded.load('entry')).toMatchObject({ + codeVerifier: 'initial', + tokens: { access_token: 'new' }, + clientInformation: { client_id: 'client' } + }) + reloaded.stop() + }) + + it.each(['encrypt', 'rename'] as const)( + 'preserves RAM and disk after %s failure and recovers', + async (failure) => { + await store.saveClientSecret('key', 'original', binding) + const serialized = fs.readFileSync(filePath, 'utf8') + if (failure === 'encrypt') crypto.encrypt.mockRejectedValueOnce(new Error('native failure')) + else + vi.spyOn(fs, 'renameSync').mockImplementationOnce(() => { + throw new Error('rename failure') + }) + await expect(store.saveClientSecret('key', 'replacement', binding)).rejects.toThrow('failure') + expect((await store.loadClientSecret('key'))?.secret).toBe('original') + expect(fs.readFileSync(filePath, 'utf8')).toBe(serialized) + expect(fs.readdirSync(directory)).toEqual(['credentials.json']) + await store.saveClientSecret('key', 'recovered', binding) + expect((await store.loadClientSecret('key'))?.secret).toBe('recovered') + } + ) + + it('moves an interactive binding atomically without losing either entry on failure', async () => { + await store.saveEntry('from', { codeVerifier: 'source' }) + await store.saveEntry('to', { codeVerifier: 'target' }) + const serialized = fs.readFileSync(filePath, 'utf8') + crypto.encrypt.mockRejectedValueOnce(new Error('native failure')) + await expect(store.moveInteractiveEntry('from', 'to', binding)).rejects.toThrow( + 'native failure' + ) + expect(fs.readFileSync(filePath, 'utf8')).toBe(serialized) + expect((await store.load('from'))?.codeVerifier).toBe('source') + await store.moveInteractiveEntry('from', 'to', binding) + expect(await store.load('from')).toBeNull() + expect(await store.load('to')).toMatchObject({ + codeVerifier: 'source', + binding + }) + }) + + it.each(['encrypt', 'decrypt'] as const)( + 'rejects a delayed %s result after stop', + async (operation) => { + await store.saveEntry('entry', { codeVerifier: 'original' }) + const serialized = fs.readFileSync(filePath, 'utf8') + const gate = deferred() + const entered = deferred() + crypto[operation].mockImplementationOnce(() => { + entered.resolve() + return gate.promise + }) + const owner = + operation === 'decrypt' ? new McpOAuthCredentialStore({ filePath, crypto }) : store + const pending = + operation === 'decrypt' + ? owner.load('entry') + : owner.saveEntry('entry', { codeVerifier: 'late' }) + const result = expect(pending).rejects.toThrow('interrupted') + await entered.promise + owner.stop() + gate.resolve( + operation === 'decrypt' ? unwrap(JSON.parse(serialized).wrapped) : 'late-wrapped' + ) + await result + await expect(owner.load('entry')).rejects.toThrow('interrupted') + expect(fs.readFileSync(filePath, 'utf8')).toBe(serialized) + } + ) + + it('bounds pending writes while reserving control capacity and stops the entire queue', async () => { + const gate = deferred() + const entered = deferred() + crypto.encrypt.mockImplementationOnce(() => { + entered.resolve() + return gate.promise + }) + const normal = Array.from({ length: 24 }, (_, i) => + store.saveEntry(`key-${i}`, { codeVerifier: 'pending' }) + ) + await entered.promise + await expect(store.saveEntry('overflow', { codeVerifier: 'rejected' })).rejects.toThrow( + 'queue is full' + ) + const controls = Array.from({ length: 8 }, () => store.clearEntry('key-0')) + const result = Promise.allSettled([...normal, ...controls]) + await expect(store.clearEntry('overflow')).rejects.toThrow('queue is full') + store.stop() + gate.resolve('late') + expect((await result).every((item) => item.status === 'rejected')).toBe(true) + expect(fs.existsSync(filePath)).toBe(false) + expect(crypto.encrypt).toHaveBeenCalledOnce() }) }) diff --git a/test/main/mcp/routeAuthority.test.ts b/test/main/mcp/routeAuthority.test.ts new file mode 100644 index 0000000000..137c95be27 --- /dev/null +++ b/test/main/mcp/routeAuthority.test.ts @@ -0,0 +1,63 @@ +import { describe, expect, it, vi } from 'vitest' +import { createMcpRoutes } from '@/mcp/routes' +import { createRendererRouteContext } from '@/routes/routeRegistry' + +describe('MCP native window authority', () => { + it.each([true, false])( + 'awaits settings authority before authentication (%s)', + async (allowed) => { + const authority = Promise.withResolvers() + const status = { serverName: 'fixture', state: 'authenticating', authenticated: false } + const startMcpServerAuth = vi.fn().mockResolvedValue(status) + const isSettingsWindow = vi.fn(() => authority.promise) + const routes = createMcpRoutes({ + mcpService: { startMcpServerAuth } as never, + mcpAppHost: {} as never, + isSettingsWindow, + recordSettingsActivity: vi.fn() + }) + const serverId = '264552d7-7744-4092-afde-b8d690e1899c' + const pending = routes.get('mcp.startServerAuth')!( + { serverId }, + createRendererRouteContext(42, 7) + ) + const outcome = allowed + ? expect(pending).resolves.toEqual({ status }) + : expect(pending).rejects.toThrow('restricted to the settings window') + expect(isSettingsWindow).toHaveBeenCalledExactlyOnceWith(7) + expect(startMcpServerAuth).not.toHaveBeenCalled() + authority.resolve(allowed) + await outcome + expect(startMcpServerAuth).toHaveBeenCalledTimes(allowed ? 1 : 0) + if (allowed) expect(startMcpServerAuth).toHaveBeenCalledWith(serverId) + } + ) + + it.each([true, false])( + 'awaits conversation authority before app access (%s)', + async (settings) => { + const authority = Promise.withResolvers() + const releaseView = vi.fn().mockResolvedValue(undefined) + const routes = createMcpRoutes({ + mcpService: {} as never, + mcpAppHost: { releaseView } as never, + isSettingsWindow: () => authority.promise, + recordSettingsActivity: vi.fn() + }) + const instanceId = 'fixture-instance-identity' + const pending = routes.get('mcp.apps.releaseView')!( + { instanceId }, + createRendererRouteContext(42, 7) + ) + const outcome = settings + ? expect(pending).rejects.toThrow('restricted to conversation windows') + : expect(pending).resolves.toEqual({ released: true }) + expect(releaseView).not.toHaveBeenCalled() + authority.resolve(settings) + await outcome + expect(releaseView).toHaveBeenCalledTimes(settings ? 0 : 1) + if (!settings) + expect(releaseView).toHaveBeenCalledWith(instanceId, { webContentsId: 42, windowId: 7 }) + } + ) +}) diff --git a/test/main/mcp/serverManager.test.ts b/test/main/mcp/serverManager.test.ts index 5e5fd819aa..f44c21d0b7 100644 --- a/test/main/mcp/serverManager.test.ts +++ b/test/main/mcp/serverManager.test.ts @@ -1,4 +1,5 @@ import { beforeEach, describe, expect, it, vi } from 'vitest' +import axios from 'axios' const publishDeepchatEventMock = vi.hoisted(() => vi.fn()) const semanticNotificationsMock = vi.hoisted(() => ({ @@ -21,12 +22,6 @@ const clientMocks = vi.hoisted(() => ({ } })) -vi.mock('@/platform/proxy', () => ({ - proxyConfig: { - getProxyUrl: vi.fn(() => '') - } -})) - vi.mock('@/mcp/mcpClient', () => ({ McpClient: vi.fn().mockImplementation(function McpClient(_name, serverConfig) { return { @@ -42,6 +37,7 @@ vi.mock('@/mcp/mcpClient', () => ({ McpConnectionCancelledError: clientMocks.McpConnectionCancelledError })) +import type { McpOAuthManager } from '@/mcp/mcpOAuthManager' import { ServerManager } from '@/mcp/serverManager' import { McpClient, McpConnectionCancelledError } from '@/mcp/mcpClient' @@ -74,18 +70,63 @@ describe('ServerManager notifications and plugin isolation', () => { } } - function createManager(providerSettings: ReturnType) { + function createManager( + providerSettings: ReturnType, + oauth?: McpOAuthManager, + getProxyUrl: () => Promise = async () => null + ) { return new ServerManager( providerSettings as never, - { isEnabled: () => false }, + { isEnabled: () => false, setEnabled: async () => undefined }, vi.fn() as never, {} as never, vi.fn(), semanticNotificationsMock, - publishDeepchatEventMock + publishDeepchatEventMock, + getProxyUrl, + oauth ) } + it('awaits the current native proxy before each registry batch', async () => { + const settings = { + ...createProviderSettings({}), + getCustomNpmRegistry: () => null, + setNpmRegistryCache: vi.fn() + } + const proxy = Promise.withResolvers() + const getProxyUrl = vi.fn<() => Promise>() + getProxyUrl.mockReturnValueOnce(proxy.promise).mockResolvedValue(null) + const manager = createManager(settings, undefined, getProxyUrl) + const get = vi.spyOn(axios, 'get').mockResolvedValue({ status: 200 }) + try { + const pending = manager.testNpmRegistrySpeed(false) + expect(get).not.toHaveBeenCalled() + proxy.resolve('http://fixture:credential@127.0.0.1:4567') + await pending + expect(get).toHaveBeenCalledTimes(3) + for (const [, options] of get.mock.calls) { + expect(options?.proxy).toEqual({ + host: '127.0.0.1', + port: 4567, + auth: { username: 'fixture', password: 'credential' } + }) + } + get.mockClear() + await manager.testNpmRegistrySpeed(false) + expect(get).toHaveBeenCalledTimes(3) + for (const [, options] of get.mock.calls) expect(options?.proxy).toBeUndefined() + get.mockClear() + settings.setNpmRegistryCache.mockClear() + getProxyUrl.mockRejectedValueOnce(new Error('Native proxy unavailable')) + await expect(manager.testNpmRegistrySpeed(false)).rejects.toThrow('Native proxy unavailable') + expect(get).not.toHaveBeenCalled() + expect(settings.setNpmRegistryCache).not.toHaveBeenCalled() + } finally { + get.mockRestore() + } + }) + it('suppresses semantic connection occurrences for plugin-owned MCP servers', async () => { const providerSettings = createProviderSettings({ plugin: { @@ -519,4 +560,62 @@ describe('ServerManager notifications and plugin isolation', () => { expect(vi.mocked(McpClient).mock.calls[0][1]).toMatchObject({ command: 'plugin-command' }) expect(vi.mocked(McpClient).mock.calls[1][1]).toMatchObject({ command: 'regular-command' }) }) + + it.each(['startup', 'background'] as const)( + 'awaits OAuth classification for %s connection failures', + async (phase) => { + const providerSettings = createProviderSettings({ + regular: { + type: 'http', + baseUrl: 'https://mcp.example/mcp', + command: '', + args: [], + env: {} + } + }) + let classify!: (handled: boolean) => void + let entered!: () => void + const classifying = new Promise((resolve) => { + entered = resolve + }) + const handleConnectionError = vi.fn(() => { + entered() + return new Promise((resolve) => { + classify = resolve + }) + }) + const manager = createManager(providerSettings, { + handleConnectionError + } as unknown as McpOAuthManager) + const error = new Error('connect failed') + let rejectBackground!: (error: Error) => void + if (phase === 'background') { + clientMocks.getConnectionCompletion.mockReturnValue( + new Promise((_resolve, reject) => { + rejectBackground = reject + }) + ) + clientMocks.connect.mockResolvedValueOnce('soft-timeout-released') + } else clientMocks.connect.mockRejectedValueOnce(error) + const started = manager.startServer('regular') + const result = + phase === 'startup' + ? expect(started).rejects.toBe(error) + : expect(started).resolves.toBe('soft-timeout-released') + if (phase === 'background') { + await result + rejectBackground(error) + } + await classifying + expect(semanticNotificationsMock.occur).not.toHaveBeenCalled() + classify(false) + await result + await vi.waitFor(() => + expect(semanticNotificationsMock.occur).toHaveBeenCalledWith({ + code: 'mcp.connectionFailed', + serverName: 'regular' + }) + ) + } + ) }) diff --git a/test/main/mcp/settings.test.ts b/test/main/mcp/settings.test.ts index 878da6b20e..abb5426ac8 100644 --- a/test/main/mcp/settings.test.ts +++ b/test/main/mcp/settings.test.ts @@ -1,6 +1,6 @@ -import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' +import { afterEach, describe, expect, it, vi } from 'vitest' -const mockStores = vi.hoisted(() => new Map>()) +import { createMemoryStore } from '../../helpers/memoryStore' const clone = (value: T): T => { const cloneFn = (globalThis as typeof globalThis & { structuredClone?: (value: T) => T }) @@ -13,39 +13,6 @@ const clone = (value: T): T => { return JSON.parse(JSON.stringify(value)) as T } -vi.mock('electron-store', () => ({ - default: class MockElectronStore { - private readonly data: Record - - constructor(options: { name: string; defaults?: Record }) { - if (!mockStores.has(options.name)) { - mockStores.set(options.name, clone(options.defaults ?? {})) - } - this.data = mockStores.get(options.name)! - } - - get(key: string) { - return this.data[key] - } - - set(key: string | Record, value?: any) { - if (typeof key === 'string') { - this.data[key] = value - } else { - Object.assign(this.data, key) - } - } - - delete(key: string) { - delete this.data[key] - } - - has(key: string) { - return key in this.data - } - } -})) - const originalPlatform = Object.getOwnPropertyDescriptor(process, 'platform') const setPlatform = (platform: string) => { @@ -75,10 +42,6 @@ const createKnowledgeConfig = (id: string, description = id) => ({ }) describe('McpSettings', () => { - beforeEach(() => { - mockStores.clear() - }) - afterEach(() => { vi.clearAllMocks() if (originalPlatform) { @@ -87,8 +50,8 @@ describe('McpSettings', () => { }) it('honors an empty legacy enabled set when legacy keys are present', async () => { - const { McpSettings } = await loadHelper('darwin') - const helper = new McpSettings() + const { McpSettings, createMcpSettingsDefaults } = await loadHelper('darwin') + const helper = new McpSettings(createMemoryStore(createMcpSettingsDefaults())) const mcpStore = (helper as any).mcpStore const searchConfig = { ...mcpStore.get('mcpServers').bochaSearch } @@ -105,8 +68,8 @@ describe('McpSettings', () => { }) it('retires persisted in-memory artifacts without removing external servers', async () => { - const { McpSettings } = await loadHelper('darwin') - const helper = new McpSettings() + const { McpSettings, createMcpSettingsDefaults } = await loadHelper('darwin') + const helper = new McpSettings(createMemoryStore(createMcpSettingsDefaults())) const store = (helper as any).mcpStore expect(store.get('mcpServers').Artifacts).toBeUndefined() store.set('mcpServers', { @@ -124,14 +87,16 @@ describe('McpSettings', () => { }) it('updates Router credentials through one fallback store write', async () => { - const { McpSettings } = await loadHelper('darwin') - const helper = new McpSettings() + const { McpSettings, createMcpSettingsDefaults } = await loadHelper('darwin') + const helper = new McpSettings(createMemoryStore(createMcpSettingsDefaults())) const servers = { router: { command: '', args: [], env: {}, type: 'http' as const, + descriptions: '', + icons: '', enabled: true, customHeaders: { Authorization: 'Bearer router-key' @@ -154,8 +119,8 @@ describe('McpSettings', () => { }) it("adds the disabled McDonald's MCP server with a token placeholder for existing users", async () => { - const { McpSettings } = await loadHelper('darwin') - const helper = new McpSettings() + const { McpSettings, createMcpSettingsDefaults } = await loadHelper('darwin') + const helper = new McpSettings(createMemoryStore(createMcpSettingsDefaults())) const mcpStore = (helper as any).mcpStore mcpStore.set('mcpServers', {}) @@ -173,8 +138,8 @@ describe('McpSettings', () => { }) it('adds the disabled You.com search MCP server (keyless free profile) for existing users', async () => { - const { McpSettings } = await loadHelper('darwin') - const helper = new McpSettings() + const { McpSettings, createMcpSettingsDefaults } = await loadHelper('darwin') + const helper = new McpSettings(createMemoryStore(createMcpSettingsDefaults())) const mcpStore = (helper as any).mcpStore mcpStore.set('mcpServers', {}) @@ -189,8 +154,8 @@ describe('McpSettings', () => { }) it('adds the disabled Serply search MCP server with a key placeholder for existing users', async () => { - const { McpSettings } = await loadHelper('darwin') - const helper = new McpSettings() + const { McpSettings, createMcpSettingsDefaults } = await loadHelper('darwin') + const helper = new McpSettings(createMemoryStore(createMcpSettingsDefaults())) const mcpStore = (helper as any).mcpStore mcpStore.set('mcpServers', {}) @@ -208,8 +173,8 @@ describe('McpSettings', () => { }) it('adds the disabled Webz.io news search MCP server with a token placeholder for existing users', async () => { - const { McpSettings } = await loadHelper('darwin') - const helper = new McpSettings() + const { McpSettings, createMcpSettingsDefaults } = await loadHelper('darwin') + const helper = new McpSettings(createMemoryStore(createMcpSettingsDefaults())) const mcpStore = (helper as any).mcpStore mcpStore.set('mcpServers', {}) @@ -227,8 +192,8 @@ describe('McpSettings', () => { }) it('adds the disabled SkyAccess empty leg MCP server (keyless) for existing users', async () => { - const { McpSettings } = await loadHelper('darwin') - const helper = new McpSettings() + const { McpSettings, createMcpSettingsDefaults } = await loadHelper('darwin') + const helper = new McpSettings(createMemoryStore(createMcpSettingsDefaults())) const mcpStore = (helper as any).mcpStore mcpStore.set('mcpServers', {}) @@ -243,8 +208,8 @@ describe('McpSettings', () => { }) it('adds the disabled Cohesivity backend infrastructure MCP server (keyless) for existing users', async () => { - const { McpSettings } = await loadHelper('darwin') - const helper = new McpSettings() + const { McpSettings, createMcpSettingsDefaults } = await loadHelper('darwin') + const helper = new McpSettings(createMemoryStore(createMcpSettingsDefaults())) const mcpStore = (helper as any).mcpStore mcpStore.set('mcpServers', {}) @@ -259,8 +224,8 @@ describe('McpSettings', () => { }) it('adds the disabled Keenable search MCP server (keyless) for existing users', async () => { - const { McpSettings } = await loadHelper('darwin') - const helper = new McpSettings() + const { McpSettings, createMcpSettingsDefaults } = await loadHelper('darwin') + const helper = new McpSettings(createMemoryStore(createMcpSettingsDefaults())) const mcpStore = (helper as any).mcpStore mcpStore.set('mcpServers', {}) @@ -278,8 +243,8 @@ describe('McpSettings', () => { }) it('adds the disabled Bowmark MCP server for existing users', async () => { - const { McpSettings } = await loadHelper('darwin') - const helper = new McpSettings() + const { McpSettings, createMcpSettingsDefaults } = await loadHelper('darwin') + const helper = new McpSettings(createMemoryStore(createMcpSettingsDefaults())) const mcpStore = (helper as any).mcpStore mcpStore.set('mcpServers', {}) @@ -297,8 +262,8 @@ describe('McpSettings', () => { }) it('adds the disabled EvoLink MCP server (OAuth sign-in) for existing users', async () => { - const { McpSettings } = await loadHelper('darwin') - const helper = new McpSettings() + const { McpSettings, createMcpSettingsDefaults } = await loadHelper('darwin') + const helper = new McpSettings(createMemoryStore(createMcpSettingsDefaults())) const mcpStore = (helper as any).mcpStore mcpStore.set('mcpServers', {}) @@ -314,8 +279,8 @@ describe('McpSettings', () => { }) it('adds the disabled deed.page static hosting MCP server (keyless) for existing users', async () => { - const { McpSettings } = await loadHelper('darwin') - const helper = new McpSettings() + const { McpSettings, createMcpSettingsDefaults } = await loadHelper('darwin') + const helper = new McpSettings(createMemoryStore(createMcpSettingsDefaults())) const mcpStore = (helper as any).mcpStore mcpStore.set('mcpServers', {}) @@ -331,8 +296,8 @@ describe('McpSettings', () => { }) it('does not recreate the Apple built-in server after the user removed it', async () => { - const { McpSettings } = await loadHelper('darwin') - const helper = new McpSettings() + const { McpSettings, createMcpSettingsDefaults } = await loadHelper('darwin') + const helper = new McpSettings(createMemoryStore(createMcpSettingsDefaults())) const mcpStore = (helper as any).mcpStore mcpStore.set('mcpServers', {}) @@ -344,8 +309,8 @@ describe('McpSettings', () => { }) it('removes the unpublished Computer Use demo MCP server config', async () => { - const { McpSettings } = await loadHelper('darwin') - const helper = new McpSettings() + const { McpSettings, createMcpSettingsDefaults } = await loadHelper('darwin') + const helper = new McpSettings(createMemoryStore(createMcpSettingsDefaults())) const mcpStore = (helper as any).mcpStore const legacyServer = { command: '/Applications/DeepChat Computer Use.app/Contents/MacOS/cua-driver', @@ -384,8 +349,8 @@ describe('McpSettings', () => { }) it('migrates legacy builtin knowledge configs out of MCP env', async () => { - const { McpSettings } = await loadHelper('win32') - const helper = new McpSettings() + const { McpSettings, createMcpSettingsDefaults } = await loadHelper('win32') + const helper = new McpSettings(createMemoryStore(createMcpSettingsDefaults())) const mcpStore = (helper as any).mcpStore const legacyConfig = createKnowledgeConfig('legacy-knowledge', 'Legacy config') const realConfig = createKnowledgeConfig('real-knowledge', 'Real config') @@ -406,8 +371,8 @@ describe('McpSettings', () => { }) it('keeps existing knowledge configs when legacy env has the same id', async () => { - const { McpSettings } = await loadHelper('win32') - const helper = new McpSettings() + const { McpSettings, createMcpSettingsDefaults } = await loadHelper('win32') + const helper = new McpSettings(createMemoryStore(createMcpSettingsDefaults())) const mcpStore = (helper as any).mcpStore const realConfig = createKnowledgeConfig('same-id', 'Real config') const legacyConfig = createKnowledgeConfig('same-id', 'Legacy config') @@ -428,8 +393,8 @@ describe('McpSettings', () => { }) it('persists batch imported MCP servers', async () => { - const { McpSettings } = await loadHelper('darwin') - const helper = new McpSettings() + const { McpSettings, createMcpSettingsDefaults } = await loadHelper('darwin') + const helper = new McpSettings(createMemoryStore(createMcpSettingsDefaults())) const result = await helper.batchImportMcpServers([ { @@ -454,4 +419,92 @@ describe('McpSettings', () => { }) ) }) + + it('finalizes OAuth against the current binding while retaining unrelated settings', async () => { + const { McpSettings, createMcpSettingsDefaults } = await loadHelper('darwin') + const helper = new McpSettings(createMemoryStore(createMcpSettingsDefaults())) + await helper.addMcpServer('oauth', { + type: 'http', + baseUrl: 'https://mcp.example/mcp', + command: '', + args: [], + env: {}, + descriptions: '', + icons: '', + enabled: false + }) + const initial = (await helper.getMcpServers()).oauth + const expected = { + serverId: initial.serverId!, + configGeneration: initial.configGeneration!, + bindingHash: initial.bindingHash!, + endpoint: initial.baseUrl! + } + await helper.updateMcpServer('oauth', { descriptions: 'Current description' }) + const authorization = { + mode: 'interactive' as const, + authorizationServerIssuer: 'https://auth.example/', + protectedResourceUrl: expected.endpoint, + clientId: 'client' + } + const finalized = helper.finalizeOAuthBinding(expected, authorization) + expect(finalized).toMatchObject({ + descriptions: 'Current description', + authorization, + configGeneration: expected.configGeneration + 1 + }) + expect((await helper.getMcpServers()).oauth).toEqual(finalized) + const set = vi.spyOn((helper as any).mcpStore, 'set') + expect( + helper.finalizeOAuthBinding( + { + ...expected, + configGeneration: finalized.configGeneration!, + bindingHash: finalized.bindingHash! + }, + authorization + ) + ).toEqual(finalized) + expect(set).not.toHaveBeenCalled() + }) + + it.each(['replace', 'remove', 'write failure'] as const)( + 'preserves owner state when OAuth finalization encounters %s', + async (change) => { + const { McpSettings, createMcpSettingsDefaults } = await loadHelper('darwin') + const helper = new McpSettings(createMemoryStore(createMcpSettingsDefaults())) + await helper.addMcpServer('oauth', { + type: 'http', + baseUrl: 'https://mcp.example/mcp', + command: '', + args: [], + env: {}, + descriptions: '', + icons: '', + enabled: false + }) + const initial = (await helper.getMcpServers()).oauth + const expected = { + serverId: initial.serverId!, + configGeneration: initial.configGeneration!, + bindingHash: initial.bindingHash!, + endpoint: initial.baseUrl! + } + if (change === 'replace') + await helper.updateMcpServer('oauth', { baseUrl: 'https://replacement.example/mcp' }) + if (change === 'remove') await helper.removeMcpServer('oauth') + const store = (helper as any).mcpStore + const before = clone(store.get('mcpServers')) + if (change === 'write failure') + vi.spyOn(store, 'set').mockImplementationOnce(() => { + throw new Error('write failure') + }) + expect(() => + helper.finalizeOAuthBinding(expected, { mode: 'interactive', clientId: 'discovered' }) + ).toThrow( + change === 'replace' ? 'binding changed' : change === 'remove' ? 'removed' : 'write failure' + ) + expect(store.get('mcpServers')).toEqual(before) + } + ) }) diff --git a/test/main/mcp/toolManager.test.ts b/test/main/mcp/toolManager.test.ts index 43603ad144..c1e4843868 100644 --- a/test/main/mcp/toolManager.test.ts +++ b/test/main/mcp/toolManager.test.ts @@ -1,3 +1,4 @@ +import { createPluginToolPolicies } from '../../helpers/pluginToolPolicies' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' import { CUA_PLUGIN_ID } from '@shared/types/plugin' @@ -5,10 +6,10 @@ import { ToolManager, type ComputerUsePreviewObserver } from '@/mcp/toolManager' import { McpPreDispatchError } from '@/mcp/errors' import { validateAndCloneMcpTool } from '@/mcp/schemaValidation' import type { PluginRuntimeStartReason } from '@/plugin/runtimeSupervisor' -import * as toolPolicyStore from '@/plugin/toolPolicyStore' +import type { DeepchatEventPublisher } from '@shared/contracts/events' +const toolPolicies = createPluginToolPolicies() const TOOL_POLICY_PLUGIN_ID = 'com.deepchat.plugins.permission-test' -const { registerPluginToolPolicy, unregisterPluginToolPolicies } = toolPolicyStore const semanticNotifications = { occur: vi.fn(), recover: vi.fn() @@ -39,13 +40,13 @@ describe('ToolManager', () => { }) afterEach(() => { - unregisterPluginToolPolicies(TOOL_POLICY_PLUGIN_ID) + toolPolicies.unregister(TOOL_POLICY_PLUGIN_ID) warnSpy.mockRestore() }) function createClient( serverName: string, - tools = [ + tools: unknown[] = [ { name: 'echo', description: 'Echo tool', @@ -120,7 +121,7 @@ describe('ToolManager', () => { }> ensureRunning?: (serverName: string, reason: PluginRuntimeStartReason) => Promise unavailableServers?: Set - publishEvent?: ReturnType + publishEvent?: DeepchatEventPublisher } = {}, computerUsePreviewObserver?: ComputerUsePreviewObserver, resolveCachedImageDataUrl?: (source: string, signal?: AbortSignal) => Promise @@ -129,6 +130,7 @@ describe('ToolManager', () => { providerSettings as never, providerSettings as never, serverManager as never, + toolPolicies, semanticNotifications, runtime.publishEvent ?? vi.fn(), { @@ -523,7 +525,7 @@ describe('ToolManager', () => { inputSchema: { type: 'object', properties: {}, required: [] } } ]) - registerPluginToolPolicy({ + toolPolicies.register({ pluginId: TOOL_POLICY_PLUGIN_ID, serverId: serverName, tools: { known_tool: 'allow' }, @@ -551,7 +553,7 @@ describe('ToolManager', () => { it('does not advertise explicitly denied plugin catalog tools', async () => { const serverName = 'closed-policy-server' - registerPluginToolPolicy({ + toolPolicies.register({ pluginId: TOOL_POLICY_PLUGIN_ID, serverId: serverName, tools: { @@ -2401,14 +2403,12 @@ describe('ToolManager', () => { expect(observer.failed).not.toHaveBeenCalled() }) - it('refreshes PiP after a trusted click without exposing or awaiting the private snapshot', async () => { + it('refreshes PiP after a trusted click without awaiting Native eligibility or the snapshot', async () => { let snapshotPolicy: 'allow' | 'ask' = 'allow' - vi.spyOn(toolPolicyStore, 'resolvePluginToolPolicy').mockImplementation( - (_serverId, toolName) => ({ - managed: toolName === 'get_window_state', - decision: toolName === 'get_window_state' ? snapshotPolicy : null - }) - ) + vi.spyOn(toolPolicies, 'resolve').mockImplementation((_serverId, toolName) => ({ + managed: toolName === 'get_window_state', + decision: toolName === 'get_window_state' ? snapshotPolicy : null + })) const tools = [ { name: 'click', @@ -2453,8 +2453,11 @@ describe('ToolManager', () => { return privateSnapshots[privateSnapshotIndex++].promise }) const publishEvent = vi.fn() + const eligibility = deferred() const observer = { - shouldCaptureAfterClick: vi.fn(() => true), + shouldCaptureAfterClick: vi + .fn((): boolean | Promise => true) + .mockImplementationOnce(() => eligibility.promise), started: vi.fn(), completed: vi.fn(), failed: vi.fn() @@ -2464,6 +2467,7 @@ describe('ToolManager', () => { providerSettings as never, providerSettings as never, createServerManager([client]) as never, + toolPolicies, semanticNotifications, publishEvent, { @@ -2520,6 +2524,8 @@ describe('ToolManager', () => { toolDefinition: expect.objectContaining({ name: 'click' }) }) ) + expect(client.callTool).toHaveBeenCalledTimes(1) + eligibility.resolve(true) await vi.waitFor(() => expect(client.callTool).toHaveBeenCalledTimes(2)) expect(client.callTool).toHaveBeenNthCalledWith(2, 'get_window_state', { pid: 12, @@ -2624,15 +2630,35 @@ describe('ToolManager', () => { expect(client.callTool).toHaveBeenCalledTimes(5) expect(observer.shouldCaptureAfterClick).toHaveBeenCalledTimes(2) expect(observer.started).toHaveBeenCalledTimes(2) + + snapshotPolicy = 'allow' + const delayedEligibility = deferred() + observer.shouldCaptureAfterClick.mockImplementationOnce(() => delayedEligibility.promise) + await manager.callTool( + { + id: 'cua-click-4', + type: 'function', + function: { + name: 'click', + arguments: '{"pid":12,"window_id":34,"x":50,"y":60}' + }, + conversationId: 'session-1' + }, + { runId: 'run-1' } + ) + snapshotPolicy = 'ask' + delayedEligibility.resolve(true) + await Promise.resolve() + expect(client.callTool).toHaveBeenCalledTimes(6) + expect(observer.shouldCaptureAfterClick).toHaveBeenCalledTimes(3) + expect(observer.started).toHaveBeenCalledTimes(2) }) it('blocks a private PiP snapshot when the live schema differs from the catalog', async () => { - vi.spyOn(toolPolicyStore, 'resolvePluginToolPolicy').mockImplementation( - (_serverId, toolName) => ({ - managed: toolName === 'get_window_state', - decision: toolName === 'get_window_state' ? 'allow' : null - }) - ) + vi.spyOn(toolPolicies, 'resolve').mockImplementation((_serverId, toolName) => ({ + managed: toolName === 'get_window_state', + decision: toolName === 'get_window_state' ? 'allow' : null + })) const liveTools = [ { name: 'click', diff --git a/test/main/memory/legacyV1Reader.test.ts b/test/main/memory/legacyV1Reader.test.ts index ec305ffe85..4735f4e5be 100644 --- a/test/main/memory/legacyV1Reader.test.ts +++ b/test/main/memory/legacyV1Reader.test.ts @@ -1,3 +1,4 @@ +import { nativeVectorHost } from './nativeVectorHost' import { afterEach, describe, expect, it, vi } from 'vitest' const duckDbMocks = vi.hoisted(() => ({ create: vi.fn() })) @@ -49,7 +50,7 @@ describe('LegacyV1Reader', () => { [{ provider: 'provider-a', model: 'model-a', dim: 2 }] ]) const fence = new MigrationAbandonFence() - const reader = await LegacyV1Reader.open('/tmp/legacy.duckdb', 2, fence) + const reader = await LegacyV1Reader.open('/tmp/legacy.duckdb', 2, fence, nativeVectorHost) await expect(reader.readEmbeddingIdentity(fence)).resolves.toEqual({ providerId: 'provider-a', @@ -86,7 +87,7 @@ describe('LegacyV1Reader', () => { ])('returns null for $name', async ({ responses }) => { setupReader(responses) const fence = new MigrationAbandonFence() - const reader = await LegacyV1Reader.open('/tmp/legacy.duckdb', 2, fence) + const reader = await LegacyV1Reader.open('/tmp/legacy.duckdb', 2, fence, nativeVectorHost) await expect(reader.readEmbeddingIdentity(fence)).resolves.toBeNull() }) @@ -95,7 +96,7 @@ describe('LegacyV1Reader', () => { const { connection } = setupReader([]) connection.runAndReadAll.mockRejectedValueOnce(new Error('INTERNAL metadata failure')) const fence = new MigrationAbandonFence() - const reader = await LegacyV1Reader.open('/tmp/legacy.duckdb', 2, fence) + const reader = await LegacyV1Reader.open('/tmp/legacy.duckdb', 2, fence, nativeVectorHost) await expect(reader.readEmbeddingIdentity(fence)).rejects.toThrow('INTERNAL metadata failure') expect(connection.closeSync).not.toHaveBeenCalled() @@ -113,7 +114,7 @@ describe('LegacyV1Reader', () => { const fence = new MigrationAbandonFence() const legacyPath = "C:\\AgentMemory\\O'Brien.duckdb" - const reader = await LegacyV1Reader.open(legacyPath, 2, fence) + const reader = await LegacyV1Reader.open(legacyPath, 2, fence, nativeVectorHost) await expect(reader.countRows(fence)).resolves.toBe(3) await expect(reader.readPage(null, fence)).resolves.toEqual([ { memoryId: 'm1', embedding: [0.1, 0.2] }, @@ -125,7 +126,7 @@ describe('LegacyV1Reader', () => { reader.closeBeforeFileMutation(fence) expect(duckDbMocks.create).toHaveBeenCalledWith(':memory:') - expect(vssMocks.load).toHaveBeenCalledWith(connection, legacyPath, fence) + expect(vssMocks.load).toHaveBeenCalledWith(connection, legacyPath, nativeVectorHost, fence) expect(connection.run).toHaveBeenCalledWith( "ATTACH 'C:\\\\AgentMemory\\\\O''Brien.duckdb' AS legacy (READ_ONLY);" ) @@ -143,7 +144,7 @@ describe('LegacyV1Reader', () => { [{ memory_id: 'm1', embedding: [0.1, 0.2] }] ]) const fence = new MigrationAbandonFence() - const reader = await LegacyV1Reader.open('/tmp/legacy.duckdb', 2, fence) + const reader = await LegacyV1Reader.open('/tmp/legacy.duckdb', 2, fence, nativeVectorHost) await expect(reader.readPage(null, fence)).rejects.toThrow('invalid legacy v1 embedding') await expect(reader.readPage('m1', fence)).rejects.toThrow('not strictly keyset ordered') @@ -168,7 +169,7 @@ describe('LegacyV1Reader', () => { } duckDbMocks.create.mockResolvedValue(instance) const fence = new MigrationAbandonFence() - const reader = await LegacyV1Reader.open('/tmp/legacy.duckdb', 2, fence) + const reader = await LegacyV1Reader.open('/tmp/legacy.duckdb', 2, fence, nativeVectorHost) const pending = reader.readPage(null, fence) fence.abandon() @@ -187,7 +188,7 @@ describe('LegacyV1Reader', () => { throw new Error('close failed') }) const fence = new MigrationAbandonFence() - const reader = await LegacyV1Reader.open('/tmp/legacy.duckdb', 2, fence) + const reader = await LegacyV1Reader.open('/tmp/legacy.duckdb', 2, fence, nativeVectorHost) expect(() => reader.closeBeforeFileMutation(fence)).toThrow('close failed') expect(instance.closeSync).not.toHaveBeenCalled() diff --git a/test/main/memory/memoryRuntimeContext.test.ts b/test/main/memory/memoryRuntimeContext.test.ts index 499660cc60..7c31275a59 100644 --- a/test/main/memory/memoryRuntimeContext.test.ts +++ b/test/main/memory/memoryRuntimeContext.test.ts @@ -242,8 +242,9 @@ describe('MemoryRuntimeContext execution epoch', () => { providerControl: { abortAgent: vi.fn(), abortAll: vi.fn() } }) - expect(ctx.captureOperationFence('agent-a')).toEqual({ agentId: 'agent-a', generation: 0 }) - expect(ctx.captureOperationFence('agent-a')).toEqual({ agentId: 'agent-a', generation: 0 }) + const token = ctx.captureOperationFence('agent-a') + expect(token).toEqual({ ownerId: expect.any(String), agentId: 'agent-a', generation: 0 }) + expect(ctx.captureOperationFence('agent-a')).toEqual(token) expect(resolveAgentConfig).not.toHaveBeenCalled() expect(ctx.listObservedExecutionAgentIds()).toEqual([]) }) diff --git a/test/main/memory/memoryVectorStore.test.ts b/test/main/memory/memoryVectorStore.test.ts index 15abb11f36..c8c7d3902a 100644 --- a/test/main/memory/memoryVectorStore.test.ts +++ b/test/main/memory/memoryVectorStore.test.ts @@ -1,3 +1,4 @@ +import { nativeVectorHost } from './nativeVectorHost' import { afterEach, describe, expect, it, vi } from 'vitest' const duckDbMocks = vi.hoisted(() => ({ @@ -5,12 +6,19 @@ const duckDbMocks = vi.hoisted(() => ({ })) vi.mock('@duckdb/node-api', () => ({ + ARRAY: (type: string, size: number) => ({ type, size }), + FLOAT: 'FLOAT', + INTEGER: 'INTEGER', + VARCHAR: 'VARCHAR', DuckDBInstance: { create: duckDbMocks.create }, DuckDBConnection: class {}, + DuckDBDataChunk: { + create: () => ({ reset() {}, rowCount: 0, setColumnValues() {} }) + }, arrayValue: (values: number[]) => values })) -import logger from '@shared/logger' +import logger from 'node:console' import { MigrationAbandonFence } from '@/memory/infra/legacyV1Reader' import { loadLegacyVss } from '@/memory/infra/legacyVssLoader' import { @@ -72,6 +80,14 @@ interface ListableStore { function makeStore(onRun: (sql: string) => void = () => {}) { const calls: string[] = [] const connection = { + createAppender: vi.fn(async () => ({ + columnType: () => ({ length: 2 }), + appendDataChunk: () => { + calls.push('INSERT') + onRun('INSERT') + }, + closeSync() {} + })), run: vi.fn(async (sql: string) => { calls.push(sql.trim().split(/[\s;]/)[0].toUpperCase()) onRun(sql) @@ -88,6 +104,45 @@ const records: MemoryVectorRecord[] = [{ memoryId: 'm1', embedding: [0.1, 0.2] } const EMB = { providerId: 'p', modelId: 'm' } describe('MemoryVectorStore v2 format contract', () => { + it('stops native cleanup immediately after a fatal connection close', async () => { + const instance = { closeSync: vi.fn() } + const store = Object.assign(Object.create(MemoryVectorStore.prototype), { + connection: { + closeSync: () => { + throw new Error('INTERNAL Error: invalidated database') + } + }, + dbInstance: instance, + connectionOpen: true, + instanceOpen: true, + usable: true + }) as MemoryVectorStore + await expect(store.close()).rejects.toThrow('INTERNAL Error') + expect(instance.closeSync).not.toHaveBeenCalled() + expect(store.isUsable()).toBe(false) + }) + it('rejects a failed native close so the owner cannot acknowledge file disposal', async () => { + const connection = { + closeSync: vi.fn<() => void>(() => { + throw new Error('native connection is still open') + }) + } + const instance = { closeSync: vi.fn() } + const store = Object.assign(Object.create(MemoryVectorStore.prototype), { + connection, + dbInstance: instance, + connectionOpen: true, + instanceOpen: true, + usable: true + }) as MemoryVectorStore + await expect(store.close()).rejects.toThrow('native connection is still open') + expect(store.isUsable()).toBe(false) + expect(instance.closeSync).toHaveBeenCalledOnce() + connection.closeSync.mockImplementation(() => {}) + await store.close() + expect(connection.closeSync).toHaveBeenCalledTimes(2) + expect(instance.closeSync).toHaveBeenCalledOnce() + }) it('defines the schema and metadata consumed by native crash fixtures', () => { const plan = createMemoryVectorStoreV2FormatPlan(2, EMB) @@ -518,7 +573,7 @@ function makeVssLoadableStore( return { dbPath, connection, - loadVss: () => loadLegacyVss(connection, dbPath) + loadVss: () => loadLegacyVss(connection, dbPath, nativeVectorHost) } satisfies VssLoadableStore } @@ -600,7 +655,7 @@ describe('MemoryVectorStore v2 staged publish', () => { const { sql } = mockV2DuckDbLifecycle(paths) try { - const store = await MemoryVectorStore.create(paths, 2, EMB) + const store = await MemoryVectorStore.create(paths, 2, EMB, nativeVectorHost) expect(fs.existsSync(paths.current)).toBe(true) expect(fs.existsSync(paths.staging)).toBe(false) @@ -624,7 +679,9 @@ describe('MemoryVectorStore v2 staged publish', () => { }) try { - const failure = await MemoryVectorStore.create(paths, 2, EMB).catch((error) => error) + const failure = await MemoryVectorStore.create(paths, 2, EMB, nativeVectorHost).catch( + (error) => error + ) expect(failure).toBeInstanceOf(MemoryVectorStoreTerminalRecoveryError) expect(failure).toMatchObject({ @@ -651,7 +708,9 @@ describe('MemoryVectorStore v2 staged publish', () => { }) try { - const failure = await MemoryVectorStore.create(paths, 2, EMB).catch((error) => error) + const failure = await MemoryVectorStore.create(paths, 2, EMB, nativeVectorHost).catch( + (error) => error + ) expect(failure).toBeInstanceOf(MemoryVectorStoreTerminalRecoveryError) expect(failure).toMatchObject({ @@ -685,7 +744,7 @@ describe('MemoryVectorStore v2 staged publish', () => { mockV2DuckDbLifecycle(paths) try { - const store = await MemoryVectorStore.create(paths, 2, EMB) + const store = await MemoryVectorStore.create(paths, 2, EMB, nativeVectorHost) expect(fs.existsSync(paths.legacy)).toBe(false) expect(fs.existsSync(`${paths.legacy}.wal`)).toBe(false) expect(fs.existsSync(paths.current)).toBe(true) @@ -709,7 +768,7 @@ describe('MemoryVectorStore v2 staged publish', () => { const { legacySql } = mockV2DuckDbLifecycle(paths, { legacyRows }) try { - const store = await MemoryVectorStore.create(paths, 2, EMB) + const store = await MemoryVectorStore.create(paths, 2, EMB, nativeVectorHost) expect(store.isUsable()).toBe(true) expect(fs.existsSync(paths.current)).toBe(true) expect(fs.existsSync(paths.legacy)).toBe(false) @@ -756,7 +815,7 @@ describe('MemoryVectorStore v2 staged publish', () => { }) try { - const store = await MemoryVectorStore.create(paths, 2, EMB) + const store = await MemoryVectorStore.create(paths, 2, EMB, nativeVectorHost) expect(store.isUsable()).toBe(true) expect(fs.existsSync(paths.current)).toBe(true) @@ -780,7 +839,7 @@ describe('MemoryVectorStore v2 staged publish', () => { }) try { - const store = await MemoryVectorStore.create(paths, 2, EMB) + const store = await MemoryVectorStore.create(paths, 2, EMB, nativeVectorHost) expect(store.isUsable()).toBe(true) expect(fs.existsSync(paths.legacy)).toBe(false) @@ -801,9 +860,11 @@ describe('MemoryVectorStore v2 staged publish', () => { }) try { - await expect(MemoryVectorStore.create(paths, 2, EMB)).rejects.toMatchObject({ - name: 'MemoryVectorStoreQuarantineRequiredError' - }) + await expect(MemoryVectorStore.create(paths, 2, EMB, nativeVectorHost)).rejects.toMatchObject( + { + name: 'MemoryVectorStoreQuarantineRequiredError' + } + ) expect(fs.existsSync(paths.legacy)).toBe(true) expect(fs.existsSync(paths.current)).toBe(false) expect(connections.every((connection) => !connection.closeSync.mock.calls.length)).toBe(true) @@ -832,7 +893,7 @@ describe('MemoryVectorStore v2 staged publish', () => { }) try { - const store = await MemoryVectorStore.create(paths, 2, EMB) + const store = await MemoryVectorStore.create(paths, 2, EMB, nativeVectorHost) expect(store.isUsable()).toBe(true) expect(fs.existsSync(paths.legacy)).toBe(false) @@ -855,9 +916,11 @@ describe('MemoryVectorStore v2 staged publish', () => { }) try { - await expect(MemoryVectorStore.create(paths, 2, EMB)).rejects.toMatchObject({ - name: 'MemoryVectorStoreQuarantineRequiredError' - }) + await expect(MemoryVectorStore.create(paths, 2, EMB, nativeVectorHost)).rejects.toMatchObject( + { + name: 'MemoryVectorStoreQuarantineRequiredError' + } + ) expect(fs.existsSync(paths.legacy)).toBe(true) expect(fs.existsSync(paths.staging)).toBe(true) expect(fs.existsSync(paths.current)).toBe(false) @@ -880,9 +943,11 @@ describe('MemoryVectorStore v2 staged publish', () => { }) try { - await expect(MemoryVectorStore.create(paths, 2, EMB)).rejects.toMatchObject({ - name: 'MemoryVectorStoreQuarantineRequiredError' - }) + await expect(MemoryVectorStore.create(paths, 2, EMB, nativeVectorHost)).rejects.toMatchObject( + { + name: 'MemoryVectorStoreQuarantineRequiredError' + } + ) expect(fs.existsSync(paths.current)).toBe(false) expect(fs.existsSync(paths.staging)).toBe(true) expect(fs.existsSync(paths.legacy)).toBe(true) @@ -912,7 +977,7 @@ describe('MemoryVectorStore v2 staged publish', () => { }) try { - const pending = MemoryVectorStore.create(paths, 2, EMB) + const pending = MemoryVectorStore.create(paths, 2, EMB, nativeVectorHost) const rejection = expect(pending).rejects.toMatchObject({ name: 'MemoryVectorStoreQuarantineRequiredError' }) @@ -966,7 +1031,7 @@ describe('MemoryVectorStore v2 staged publish', () => { }) try { - const pending = MemoryVectorStore.create(paths, 2, EMB) + const pending = MemoryVectorStore.create(paths, 2, EMB, nativeVectorHost) for (let pageIndex = 0; pageIndex < pageGates.length; pageIndex += 1) { await pageStarts[pageIndex].promise await vi.advanceTimersByTimeAsync(50_000) @@ -995,7 +1060,7 @@ describe('MemoryVectorStore v2 staged publish', () => { mockV2DuckDbLifecycle(paths) try { - const store = await MemoryVectorStore.create(paths, 2, EMB) + const store = await MemoryVectorStore.create(paths, 2, EMB, nativeVectorHost) expect(fs.existsSync(paths.current)).toBe(true) expect(fs.existsSync(paths.staging)).toBe(false) expect(fs.existsSync(`${paths.staging}.wal`)).toBe(false) @@ -1017,7 +1082,7 @@ describe('MemoryVectorStore v2 staged publish', () => { }) try { - const store = await MemoryVectorStore.create(paths, 2, EMB) + const store = await MemoryVectorStore.create(paths, 2, EMB, nativeVectorHost) expect(store.isUsable()).toBe(true) expect(fs.existsSync(paths.current)).toBe(true) expect(fs.existsSync(paths.staging)).toBe(false) @@ -1049,7 +1114,7 @@ describe('MemoryVectorStore v2 staged publish', () => { }) try { - const store = await MemoryVectorStore.create(paths, 2, EMB) + const store = await MemoryVectorStore.create(paths, 2, EMB, nativeVectorHost) expect(fs.existsSync(paths.current)).toBe(true) expect(fs.existsSync(paths.quarantine)).toBe(false) expect(fs.existsSync(paths.legacy)).toBe(false) @@ -1067,17 +1132,19 @@ describe('MemoryVectorStore v2 staged publish', () => { mockV2DuckDbLifecycle(paths, { leaveStagingWal: true }) try { - await expect(MemoryVectorStore.create(paths, 2, EMB)).rejects.toMatchObject({ - name: 'MemoryVectorStoreTerminalRecoveryError', - message: expect.stringContaining('staged v2 WAL remains') - }) + await expect(MemoryVectorStore.create(paths, 2, EMB, nativeVectorHost)).rejects.toMatchObject( + { + name: 'MemoryVectorStoreTerminalRecoveryError', + message: expect.stringContaining('staged v2 WAL remains') + } + ) expect(fs.existsSync(paths.quarantine)).toBe(false) expect(fs.existsSync(paths.current)).toBe(false) MemoryVectorStore.markQuarantined(paths) duckDbMocks.create.mockReset() mockV2DuckDbLifecycle(paths) - const store = await MemoryVectorStore.create(paths, 2, EMB) + const store = await MemoryVectorStore.create(paths, 2, EMB, nativeVectorHost) expect(store.isUsable()).toBe(true) expect(fs.existsSync(paths.current)).toBe(true) expect(fs.existsSync(paths.quarantine)).toBe(false) @@ -1101,9 +1168,11 @@ describe('MemoryVectorStore v2 staged publish', () => { }) try { - await expect(MemoryVectorStore.create(paths, 2, EMB)).rejects.toMatchObject({ - name: 'MemoryVectorStoreTerminalRecoveryError' - }) + await expect(MemoryVectorStore.create(paths, 2, EMB, nativeVectorHost)).rejects.toMatchObject( + { + name: 'MemoryVectorStoreTerminalRecoveryError' + } + ) expect(fs.existsSync(paths.current)).toBe(false) expect(fs.existsSync(paths.quarantine)).toBe(true) expect(duckDbMocks.create).not.toHaveBeenCalled() @@ -1142,9 +1211,11 @@ describe('MemoryVectorStore v2 staged publish', () => { }) try { - await expect(MemoryVectorStore.create(paths, 2, EMB)).rejects.toMatchObject({ - name: 'MemoryVectorStoreTerminalRecoveryError' - }) + await expect(MemoryVectorStore.create(paths, 2, EMB, nativeVectorHost)).rejects.toMatchObject( + { + name: 'MemoryVectorStoreTerminalRecoveryError' + } + ) expect(duckDbMocks.create).not.toHaveBeenCalled() } finally { actualFs.rmSync(root, { recursive: true, force: true }) @@ -1159,7 +1230,7 @@ describe('MemoryVectorStore v2 staged publish', () => { mockV2DuckDbLifecycle(paths) try { - const store = await MemoryVectorStore.create(paths, 2, EMB) + const store = await MemoryVectorStore.create(paths, 2, EMB, nativeVectorHost) expect(fs.existsSync(`${paths.current}.wal`)).toBe(false) expect(fs.existsSync(paths.current)).toBe(true) await store.close() @@ -1184,7 +1255,7 @@ describe('MemoryVectorStore v2 staged publish', () => { }) try { - const store = await MemoryVectorStore.create(paths, 2, EMB) + const store = await MemoryVectorStore.create(paths, 2, EMB, nativeVectorHost) expect(store.isUsable()).toBe(true) expect(fs.readFileSync(paths.current, 'utf8')).toBe('v2') expect(fs.readFileSync(`${paths.current}.wal`, 'utf8')).toBe('safe-wal') @@ -1202,9 +1273,9 @@ describe('MemoryVectorStore v2 staged publish', () => { mockV2DuckDbLifecycle(paths, { failFinalOpen: true }) try { - await expect(MemoryVectorStore.create(paths, 2, EMB)).rejects.toBeInstanceOf( - MemoryVectorStorePostCommitError - ) + await expect( + MemoryVectorStore.create(paths, 2, EMB, nativeVectorHost) + ).rejects.toBeInstanceOf(MemoryVectorStorePostCommitError) expect(fs.existsSync(paths.current)).toBe(true) expect(fs.existsSync(paths.staging)).toBe(false) } finally { @@ -1222,10 +1293,12 @@ describe('MemoryVectorStore v2 staged publish', () => { }) try { - await expect(MemoryVectorStore.create(paths, 2, EMB)).rejects.toMatchObject({ - name: 'MemoryVectorStoreTerminalRecoveryError', - fatal: true - }) + await expect(MemoryVectorStore.create(paths, 2, EMB, nativeVectorHost)).rejects.toMatchObject( + { + name: 'MemoryVectorStoreTerminalRecoveryError', + fatal: true + } + ) expect(connections).toHaveLength(1) expect(connections[0].closeSync).not.toHaveBeenCalled() expect(fs.existsSync(paths.current)).toBe(true) @@ -1288,9 +1361,11 @@ describe('MemoryVectorStore v2 staged publish', () => { }) try { - await expect(MemoryVectorStore.create(paths, 2, EMB)).rejects.toMatchObject({ - name: 'MemoryVectorStorePostCommitError' - }) + await expect(MemoryVectorStore.create(paths, 2, EMB, nativeVectorHost)).rejects.toMatchObject( + { + name: 'MemoryVectorStorePostCommitError' + } + ) expect(fs.existsSync(paths.current)).toBe(true) expect(fs.existsSync(paths.staging)).toBe(false) expect(fs.existsSync(paths.legacy)).toBe(true) @@ -1307,7 +1382,9 @@ describe('MemoryVectorStore v2 staged publish', () => { mockV2DuckDbLifecycle(paths, { leaveStagingWal: true }) try { - await expect(MemoryVectorStore.create(paths, 2, EMB)).rejects.toThrow('staged v2 WAL remains') + await expect(MemoryVectorStore.create(paths, 2, EMB, nativeVectorHost)).rejects.toThrow( + 'staged v2 WAL remains' + ) expect(fs.existsSync(paths.current)).toBe(false) expect(fs.existsSync(paths.staging)).toBe(false) expect(fs.existsSync(`${paths.staging}.wal`)).toBe(false) @@ -1342,9 +1419,9 @@ describe('MemoryVectorStore v2 staged publish', () => { it('rejects invalid dimensions before interpolating them into schema SQL', async () => { const paths = createMemoryVectorStorePaths('/tmp/AgentMemory', 'agent-a') - await expect(MemoryVectorStore.create(paths, Number.NaN, EMB)).rejects.toThrow( - 'invalid vector dimensions' - ) + await expect( + MemoryVectorStore.create(paths, Number.NaN, EMB, nativeVectorHost) + ).rejects.toThrow('invalid vector dimensions') expect(duckDbMocks.create).not.toHaveBeenCalled() }) @@ -1534,10 +1611,16 @@ describe('Legacy VSS loading', () => { const secondFence = new MigrationAbandonFence() try { - const first = loadLegacyVss(firstConnection, path.join(userDataDir, 'a.duckdb'), firstFence) + const first = loadLegacyVss( + firstConnection, + path.join(userDataDir, 'a.duckdb'), + nativeVectorHost, + firstFence + ) const second = loadLegacyVss( secondConnection, path.join(userDataDir, 'b.duckdb'), + nativeVectorHost, secondFence ) await readStarted @@ -1675,7 +1758,7 @@ describe('Legacy VSS loading', () => { try { await expect( - loadLegacyVss(connection, path.join(userDataDir, 'agent.duckdb'), fence) + loadLegacyVss(connection, path.join(userDataDir, 'agent.duckdb'), nativeVectorHost, fence) ).rejects.toMatchObject({ name: 'LegacyVssUnavailableError' }) expect(connection.run).not.toHaveBeenCalled() } finally { @@ -1719,7 +1802,7 @@ describe('Legacy VSS loading', () => { }) } const fence = new MigrationAbandonFence() - const pendingLoad = loadLegacyVss(connection, '/tmp/legacy.duckdb', fence) + const pendingLoad = loadLegacyVss(connection, '/tmp/legacy.duckdb', nativeVectorHost, fence) const rejection = expect(pendingLoad).rejects.toMatchObject({ name: 'LegacyV1MigrationAbandonedError' }) @@ -1756,7 +1839,9 @@ describe('Legacy VSS loading', () => { const connection = { run: vi.fn(async () => undefined as never) } const fence = new MigrationAbandonFence() - await expect(loadLegacyVss(connection, '/tmp/legacy.duckdb', fence)).rejects.toMatchObject({ + await expect( + loadLegacyVss(connection, '/tmp/legacy.duckdb', nativeVectorHost, fence) + ).rejects.toMatchObject({ name: 'LegacyVssUnavailableError' }) expect(connection.run).not.toHaveBeenCalled() diff --git a/test/main/memory/memoryVectorStoreV2Native.test.ts b/test/main/memory/memoryVectorStoreV2Native.test.ts index a5b32302d4..09b74015c4 100644 --- a/test/main/memory/memoryVectorStoreV2Native.test.ts +++ b/test/main/memory/memoryVectorStoreV2Native.test.ts @@ -1,3 +1,4 @@ +import { nativeVectorHost } from './nativeVectorHost' import { DuckDBInstance, arrayValue } from '@duckdb/node-api' import { app } from 'electron' import { spawn } from 'node:child_process' @@ -187,6 +188,87 @@ afterEach(() => { }) describe('MemoryVectorStore v2 native integration', () => { + it('persists packed Float32 batches with offsets, reuse and atomic rejection', async () => { + const { paths } = createPaths('deepchat-vector-packed-') + const store = await MemoryVectorStore.create(paths, 2, EMBEDDING, nativeVectorHost) + const packed = (ids: string[], values: number[], dimensions = 2) => { + const bytes = Buffer.alloc(values.length * 4 + 3).subarray(3) + values.forEach((value, index) => bytes.writeFloatLE(value, index * 4)) + return { memoryIds: ids, dimensions, bytes } + } + try { + await store.upsertPacked( + packed( + Array.from({ length: 101 }, (_, index) => `row-${index}`), + Array.from({ length: 101 }, (_, index) => [index + 0.25, -index - 0.5]).flat() + ) + ) + await expect(store.upsertPacked(packed(['row-0'], [1]))).rejects.toThrow('shape') + await expect(store.upsertPacked(packed(['row-0'], [1], 1))).rejects.toThrow('dimensions') + for (const invalid of [NaN, Infinity, -Infinity]) { + await expect(store.upsertPacked(packed(['row-0'], [1, invalid]))).rejects.toThrow( + 'non-finite' + ) + } + await expect( + store.upsertPacked( + packed( + Array.from({ length: 51 }, (_, index) => + index === 0 || index === 50 ? 'row-0' : `new-${index}` + ), + Array.from({ length: 51 }, () => [1, 0]).flat() + ) + ) + ).rejects.toThrow(/constraint|duplicate key/i) + await store.upsert([{ memoryId: 'row-1', embedding: [0.25, 0.75] }]) + await store.upsertPacked(packed(['row-0'], [-0, -3.4028234663852886e38])) + await store.upsertPacked(packed(['row-2'], [1.401298464324817e-45, -0.25])) + } finally { + await store.close() + } + const { rows } = await inspectV2(paths) + expect(rows).toHaveLength(101) + const embeddingFor = (id: string) => rows.find((row) => row.memory_id === id)?.embedding + expect(embeddingFor('row-0')).toEqual([-0, -3.4028234663852886e38]) + expect(embeddingFor('row-1')).toEqual([0.25, 0.75]) + expect(embeddingFor('row-2')).toEqual([1.401298464324817e-45, -0.25]) + expect(embeddingFor('row-50')).toEqual([50.25, -50.5]) + expect(embeddingFor('row-100')).toEqual([100.25, -100.5]) + expect(rows.some((row) => String(row.memory_id).startsWith('new-'))).toBe(false) + }) + it('keeps multi-chunk upserts atomic and reuses a smaller final batch safely', async () => { + const { paths } = createPaths('deepchat-vector-append-') + const store = await MemoryVectorStore.create(paths, 2, EMBEDDING, nativeVectorHost) + try { + await store.upsert( + Array.from({ length: 101 }, (_, index) => ({ + memoryId: `row-${index}`, + embedding: [index + 1, 1] + })) + ) + await expect(store.upsert([{ memoryId: 'row-0', embedding: [1] }])).rejects.toThrow( + 'dimensions' + ) + await expect( + store.upsert( + Array.from({ length: 51 }, (_, index) => ({ + memoryId: index === 50 ? 'row-0' : index === 0 ? 'row-0' : `new-${index}`, + embedding: [1, 0] + })) + ) + ).rejects.toThrow(/constraint|duplicate key/i) + await store.upsert([{ memoryId: 'row-0', embedding: [0.25, 0.75] }]) + } finally { + await store.close() + } + const { rows } = await inspectV2(paths) + expect(rows).toHaveLength(101) + expect(rows.find((row) => row.memory_id === 'row-0')).toEqual({ + memory_id: 'row-0', + embedding: [0.25, 0.75] + }) + expect(rows.some((row) => String(row.memory_id).startsWith('new-'))).toBe(false) + }) itWithVss('preserves a genuine WAL-free HNSW v1 store without VSS on the v2 path', async () => { if (!VSS_AVAILABLE) { throw new Error(`Required bundled VSS extension is missing at ${VSS_EXTENSION}`) @@ -194,7 +276,7 @@ describe('MemoryVectorStore v2 native integration', () => { const { paths } = createPaths('deepchat-memory-native-preserve-') await createLegacyV1(paths) - const store = await MemoryVectorStore.create(paths, 2, EMBEDDING) + const store = await MemoryVectorStore.create(paths, 2, EMBEDDING, nativeVectorHost) expect(store.isUsable()).toBe(true) await expect(store.query([1, 51], { topK: 1 })).resolves.toMatchObject([{ memoryId: 'm001' }]) await store.close() @@ -209,7 +291,7 @@ describe('MemoryVectorStore v2 native integration', () => { expect(snapshot.rows[50]).toMatchObject({ memory_id: 'm051', embedding: [51, 1] }) vi.mocked(app.getAppPath).mockReturnValue(path.join(PROJECT_ROOT, 'missing-runtime-root')) - const reopened = await MemoryVectorStore.create(paths, 2, EMBEDDING) + const reopened = await MemoryVectorStore.create(paths, 2, EMBEDDING, nativeVectorHost) await expect(reopened.query([51, 1], { topK: 1 })).resolves.toMatchObject([ { memoryId: 'm051' } ]) @@ -224,7 +306,7 @@ describe('MemoryVectorStore v2 native integration', () => { await createLegacyV1(paths, 1) fs.renameSync(paths.legacy, paths.current) - const store = await MemoryVectorStore.create(paths, 2, EMBEDDING) + const store = await MemoryVectorStore.create(paths, 2, EMBEDDING, nativeVectorHost) expect(store.isUsable()).toBe(false) expect(fs.existsSync(paths.current)).toBe(true) @@ -237,7 +319,7 @@ describe('MemoryVectorStore v2 native integration', () => { fs.writeFileSync(paths.legacy, 'not-a-duckdb-database') fs.writeFileSync(`${paths.legacy}.wal`, 'not-a-duckdb-wal') - const store = await MemoryVectorStore.create(paths, 2, EMBEDDING) + const store = await MemoryVectorStore.create(paths, 2, EMBEDDING, nativeVectorHost) expect(store.isUsable()).toBe(true) await expect(store.query([1, 0], { topK: 5 })).resolves.toEqual([]) await store.close() @@ -257,7 +339,7 @@ describe('MemoryVectorStore v2 native crash recovery', () => { expect(fs.existsSync(paths.current)).toBe(true) expect(fs.existsSync(`${paths.current}.wal`)).toBe(true) - const store = await MemoryVectorStore.create(paths, 2, EMBEDDING) + const store = await MemoryVectorStore.create(paths, 2, EMBEDDING, nativeVectorHost) await expect(store.query([1, 0], { topK: 1 })).resolves.toMatchObject([ { memoryId: 'crash-row' } ]) @@ -280,7 +362,7 @@ describe('MemoryVectorStore v2 native crash recovery', () => { const { paths } = createPaths(`deepchat-memory-native-${mode}-`) await runCrashWorker(mode, paths) - const store = await MemoryVectorStore.create(paths, 2, EMBEDDING) + const store = await MemoryVectorStore.create(paths, 2, EMBEDDING, nativeVectorHost) expect(store.isUsable()).toBe(true) const matches = await store.query([1, 0], { topK: 1 }) if (mode === 'rename-after') { @@ -336,7 +418,7 @@ describe('MemoryVectorStore v2 native crash recovery', () => { 'keeps committed v2 authoritative when legacy cleanup returns EBUSY', async () => { const { root, paths } = createPaths('deepchat-memory-native-windows-cleanup-') - const published = await MemoryVectorStore.create(paths, 2, EMBEDDING) + const published = await MemoryVectorStore.create(paths, 2, EMBEDDING, nativeVectorHost) await published.upsert([{ memoryId: 'current-row', embedding: [1, 0] }]) await published.close() await createLegacyV1(paths, 1) @@ -354,7 +436,7 @@ describe('MemoryVectorStore v2 native crash recovery', () => { try { await waitForReady(child) - const reopened = await MemoryVectorStore.create(paths, 2, EMBEDDING) + const reopened = await MemoryVectorStore.create(paths, 2, EMBEDDING, nativeVectorHost) await expect(reopened.query([1, 0], { topK: 1 })).resolves.toMatchObject([ { memoryId: 'current-row' } ]) diff --git a/test/main/memory/nativeVectorHost.ts b/test/main/memory/nativeVectorHost.ts new file mode 100644 index 0000000000..59489346c0 --- /dev/null +++ b/test/main/memory/nativeVectorHost.ts @@ -0,0 +1,14 @@ +import { app } from 'electron' +import type { MemoryVectorHost } from '@/memory/infra/legacyVssLoader' + +export const nativeVectorHost: MemoryVectorHost = { + get appPath() { + return app.getAppPath() + }, + get userDataPath() { + return app.getPath('userData') + }, + get isPackaged() { + return app.isPackaged + } +} diff --git a/test/main/nativeSqliteHarness.ts b/test/main/nativeSqliteHarness.ts index 3542b7b3f9..a837f380f4 100644 --- a/test/main/nativeSqliteHarness.ts +++ b/test/main/nativeSqliteHarness.ts @@ -21,7 +21,7 @@ if (Database) { export function nativeSqliteDescribeIf( dependenciesAvailable = true, dependencyUnavailableReason = 'native SQLite test dependency is unavailable' -): typeof describe { +): typeof describe | typeof describe.skip { if (nativeSqliteAvailable && dependenciesAvailable) return describe if (!requireNativeSqlite) return describe.skip const reason = nativeSqliteAvailable ? dependencyUnavailableReason : nativeSqliteUnavailableReason @@ -36,7 +36,7 @@ export function nativeSqliteDescribeIf( export function nativeSqliteItIf( dependenciesAvailable = true, dependencyUnavailableReason = 'native SQLite test dependency is unavailable' -): typeof it { +): typeof it | typeof it.skip { if (nativeSqliteAvailable && dependenciesAvailable) return it if (!requireNativeSqlite) return it.skip const reason = nativeSqliteAvailable ? dependencyUnavailableReason : nativeSqliteUnavailableReason diff --git a/test/main/nowledgeMem/connections.test.ts b/test/main/nowledgeMem/connections.test.ts index addedfb7fc..3f4f5b6136 100644 --- a/test/main/nowledgeMem/connections.test.ts +++ b/test/main/nowledgeMem/connections.test.ts @@ -90,8 +90,9 @@ function setup(initial: Record = {}) { }) } const secrets = { - get: (key: string) => encrypted.get(key) ?? '', - wrap: vi.fn((value: string) => `encrypted:${value}`), + captureOperation: () => () => {}, + get: async (key: string) => encrypted.get(key) ?? '', + wrap: vi.fn(async (value: string) => `encrypted:${value}`), setWrapped: (key: string, wrapped: string) => { encrypted.set(key, wrapped.slice('encrypted:'.length)) }, @@ -136,7 +137,7 @@ describe('Nowledge connection contract', () => { expect(state.connection?.baseUrl).toBe('https://mem.example.test') expect(JSON.stringify([...data])).not.toContain('remote-key') expect(JSON.stringify(state)).not.toContain('remote-key') - expect(create().getExportConfig()).toMatchObject({ + expect(await create().getExportConfig()).toMatchObject({ baseUrl: 'https://mem.example.test', apiKey: 'remote-key' }) @@ -146,7 +147,7 @@ describe('Nowledge connection contract', () => { ownerPluginId: NOWLEDGE_PLUGIN_ID, sourceId: NOWLEDGE_PLUGIN_ID } - expect(service.getMcpBindings(config)).toEqual({ NOWLEDGE_MEM_API_KEY: 'remote-key' }) + expect(await service.getMcpBindings(config)).toEqual({ NOWLEDGE_MEM_API_KEY: 'remote-key' }) expect(requests.some((r) => r.path === '/mcp/' && r.key === 'remote-key')).toBe(true) }) @@ -183,7 +184,7 @@ describe('Nowledge connection contract', () => { credentials: saved.credentials }) expect((await service.getState()).connection?.baseUrl).toBe('https://mem.example.test') - expect(service.getExportConfig().apiKey).toBe('remote-key') + expect((await service.getExportConfig()).apiKey).toBe('remote-key') await service.save({ ...remote(), apiKey: '' }) expect(data.get('nowledgeMemPlugin')).not.toHaveProperty('connections') expect([...encrypted.values()]).toEqual(['local-key', 'remote-key']) @@ -197,7 +198,7 @@ describe('Nowledge connection contract', () => { requests = [] await expect(service.save({ ...remote(), apiKey: 'wrong-key' })).rejects.toThrow('HTTP 401') expect([...data]).toEqual(before) - expect(service.getExportConfig().apiKey).toBe('remote-key') + expect((await service.getExportConfig()).apiKey).toBe('remote-key') expect(requests).toHaveLength(1) }) @@ -222,11 +223,11 @@ describe('Nowledge connection contract', () => { const next = { ...remote('https://other.example.test'), apiKey: '' } await expect(service.save(next)).rejects.toThrow('Confirm replacing') await service.save({ ...next, replace: true }) - expect(service.getExportConfig().apiKey).toBe('') + expect((await service.getExportConfig()).apiKey).toBe('') await service.save({ ...next, replace: true, apiKey: 'other-key' }) - expect(() => service.getMcpBindings(old)).toThrow('no longer matches') + await expect(service.getMcpBindings(old)).rejects.toThrow('no longer matches') await service.save({ ...remote(), replace: true, apiKey: '' }) - expect(service.getExportConfig().apiKey).toBe('remote-key') + expect((await service.getExportConfig()).apiKey).toBe('remote-key') }) it('imports legacy export credentials only on explicit save and clears plaintext after encryption', async () => { @@ -240,7 +241,7 @@ describe('Nowledge connection contract', () => { await service.save({ ...local(), legacySource: 'export' }) expect(secrets.wrap).toHaveBeenCalledWith('legacy-key') expect(data.get('nowledgeMemConfig')).not.toHaveProperty('apiKey') - expect(service.getExportConfig().apiKey).toBe('legacy-key') + expect((await service.getExportConfig()).apiKey).toBe('legacy-key') }) it('does not lose the previous credential when persisting a rotation fails', async () => { @@ -253,10 +254,24 @@ describe('Nowledge connection contract', () => { await expect(service.save({ ...remote(), apiKey: 'replacement-key' })).rejects.toThrow( 'Disk full' ) - expect(service.getExportConfig().apiKey).toBe('remote-key') + expect((await service.getExportConfig()).apiKey).toBe('remote-key') expect([...encrypted.values()]).toEqual(['remote-key']) }) + it('keeps connection replacement and clearing excluded while native wrapping is pending', async () => { + const { service, secrets, data } = setup() + const pending = Promise.withResolvers() + secrets.wrap.mockReturnValueOnce(pending.promise) + const save = service.save({ ...local(), apiKey: 'private-key' }) + await vi.waitFor(() => expect(secrets.wrap).toHaveBeenCalledOnce()) + expect(data.has('nowledgeMemPlugin')).toBe(false) + expect(() => service.clear()).toThrow('update is in progress') + await expect(service.save(local())).rejects.toThrow('update is in progress') + pending.resolve('encrypted:private-key') + await save + expect((await service.getExportConfig()).apiKey).toBe('private-key') + }) + it('reuses unchanged keys, removes replaced keys and explicitly clears retained destinations', async () => { routeRemoteToFixture() const { service, encrypted } = setup() @@ -294,7 +309,7 @@ describe('Nowledge connection contract', () => { }) requests = [] await service.save({ ...remote(), replace: true, apiKey: '' }) - expect(service.getExportConfig().apiKey).toBe('remote-key') + expect((await service.getExportConfig()).apiKey).toBe('remote-key') expect( requests .filter((request) => request.path.startsWith('/remote-api')) diff --git a/test/main/ocr/ocrRuntimeService.test.ts b/test/main/ocr/ocrRuntimeService.test.ts index e4d86b9200..b7940eaa6d 100644 --- a/test/main/ocr/ocrRuntimeService.test.ts +++ b/test/main/ocr/ocrRuntimeService.test.ts @@ -5,6 +5,7 @@ import { OcrRuntimeService } from '@/ocr/ocrRuntimeService' function createUnsupportedService() { return new OcrRuntimeService({ + cacheKeyProvider: { loadOrCreateKey: async () => null }, appPath: '/application', isPackaged: true, nodeRuntimePath: null, @@ -47,6 +48,7 @@ describe('OcrRuntimeService', () => { } as never) const service = new OcrRuntimeService({ + cacheKeyProvider: { loadOrCreateKey: async () => null }, appPath: '/application', isPackaged: true, nodeRuntimePath: null, @@ -79,6 +81,7 @@ describe('OcrRuntimeService', () => { } as never) const service = new OcrRuntimeService({ + cacheKeyProvider: { loadOrCreateKey: async () => null }, appPath: '/application', isPackaged: true, nodeRuntimePath: null, @@ -113,6 +116,7 @@ describe('OcrRuntimeService', () => { } as never) const service = new OcrRuntimeService({ + cacheKeyProvider: { loadOrCreateKey: async () => null }, appPath: '/application', isPackaged: true, nodeRuntimePath: null, diff --git a/test/main/orchestration/liveDelegationService.test.ts b/test/main/orchestration/liveDelegationService.test.ts index ecf05004fb..2b8172b8bc 100644 --- a/test/main/orchestration/liveDelegationService.test.ts +++ b/test/main/orchestration/liveDelegationService.test.ts @@ -1,5 +1,7 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' -import { AgentInvocationAdmission } from '@/agent/invocationAdmission' +import path from 'node:path' +import { HarnessRuntime } from '@/harness/runtime' +import { RutisAgentInvocationAdmission } from '@/harness/admission' import { TOOL_EXECUTION } from '@shared/types/mcp' import { LIVE_DELEGATION_MAX_ACTIVE_PER_PARENT, @@ -78,13 +80,26 @@ describeIfSqlite('LiveDelegationService', () => { let service: InstanceType let harness: ReturnType let deletionGate: SessionDeletionGate - let admission: AgentInvocationAdmission + let admission: RutisAgentInvocationAdmission let consentAuthority: LiveDelegationConsentAuthority let observations: LiveDelegationLifecycleObservation[] let dispatchObservation: (observation: LiveDelegationLifecycleObservation) => void | Promise let monotonicNow: number - beforeEach(() => { + const admissionRuntimes: HarnessRuntime[] = [] + async function createAdmission(capacity = 2, maxPending = 10) { + const runtime = await HarnessRuntime.start({ + directory: path.resolve('out/harness'), + electronExecutable: process.execPath, + capacity, + maxPending, + observe: () => {} + }) + admissionRuntimes.push(runtime) + return RutisAgentInvocationAdmission.create({ runtime, capacity, maxPending }) + } + + beforeEach(async () => { db = new DatabaseCtor(':memory:') db.exec(` PRAGMA foreign_keys = ON; @@ -107,7 +122,7 @@ describeIfSqlite('LiveDelegationService', () => { ) harness = createSessionHarness(db) deletionGate = new SessionDeletionGate() - admission = new AgentInvocationAdmission(2, 10) + admission = await createAdmission(2, 10) consentAuthority = new LiveDelegationConsentAuthority() observations = [] dispatchObservation = (observation) => observations.push(observation) @@ -127,6 +142,7 @@ describeIfSqlite('LiveDelegationService', () => { afterEach(async () => { await service?.stop() + await Promise.all(admissionRuntimes.splice(0).map((runtime) => runtime.stop())) db?.close() db = null vi.restoreAllMocks() @@ -462,7 +478,7 @@ describeIfSqlite('LiveDelegationService', () => { const childSessionId = repository.require(created.delegation.id).childSessionId! expect(repository.requireTurn(turnId).status).toBe('queued') expect(harness.sessions.sendConversationMessage).not.toHaveBeenCalled() - expect(admission.snapshot().active).toBe(0) + expect((await admission.snapshot()).active).toBe(0) expect(repository.listEvents('parent')).toEqual([]) expect(() => service.prepareTaskContractContext(childSessionId)).toThrow( /no admitted live-delegation runtime/u @@ -470,7 +486,7 @@ describeIfSqlite('LiveDelegationService', () => { await service.stop() harness.sessions.sendConversationMessage.mockClear() - admission = new AgentInvocationAdmission(2, 10) + admission = await createAdmission(2, 10) service = new LiveDelegationServiceCtor({ repository, sessions: harness.sessions, @@ -791,7 +807,7 @@ describeIfSqlite('LiveDelegationService', () => { sessions: harness.sessions, safety: harness.safety, consent: consentAuthority, - admission: new AgentInvocationAdmission(2, 10), + admission: await createAdmission(2, 10), deletionGate }) service.start() @@ -1029,7 +1045,7 @@ describeIfSqlite('LiveDelegationService', () => { it('freezes generation settings while applying live permission and workspace safety', async () => { await service.stop() - admission = new AgentInvocationAdmission(1, 10) + admission = await createAdmission(1, 10) const blocker = await admission.acquire({ ownerId: 'other-session' }) service = new LiveDelegationServiceCtor({ repository, @@ -1395,7 +1411,7 @@ describeIfSqlite('LiveDelegationService', () => { it('rejects revoked waiting work before it queues for another permit', async () => { await service.stop() - admission = new AgentInvocationAdmission(1, 10) + admission = await createAdmission(1, 10) service = new LiveDelegationServiceCtor({ repository, sessions: harness.sessions, @@ -1431,7 +1447,9 @@ describeIfSqlite('LiveDelegationService', () => { } } }) - await vi.waitFor(() => expect(admission.snapshot()).toMatchObject({ active: 0, pending: 0 })) + await vi.waitFor(async () => + expect(await admission.snapshot()).toMatchObject({ active: 0, pending: 0 }) + ) const blocker = await admission.acquire({ ownerId: 'other-session' }) harness.parent.subagentCapability = { available: false, @@ -1439,7 +1457,7 @@ describeIfSqlite('LiveDelegationService', () => { } await expect(service.beforeInteractionContinuation(childId)).rejects.toThrow('unavailable') - expect(admission.snapshot()).toMatchObject({ active: 1, pending: 0 }) + expect(await admission.snapshot()).toMatchObject({ active: 1, pending: 0 }) await vi.waitFor(() => expect(repository.require(spawned.delegation.id).status).toBe('interrupted') ) @@ -1538,7 +1556,7 @@ describeIfSqlite('LiveDelegationService', () => { it('releases a waiting child permit and reacquires before continuation', async () => { await service.stop() - admission = new AgentInvocationAdmission(1, 10) + admission = await createAdmission(1, 10) service = new LiveDelegationServiceCtor({ repository, sessions: harness.sessions, @@ -1558,7 +1576,7 @@ describeIfSqlite('LiveDelegationService', () => { expect(repository.listTurns(first.delegation.id, 1)[0]?.status).toBe('running') ) const firstChildId = repository.require(first.delegation.id).childSessionId! - expect(admission.snapshot()).toMatchObject({ active: 1, pending: 0 }) + expect(await admission.snapshot()).toMatchObject({ active: 1, pending: 0 }) harness.publish({ sessionId: firstChildId, kind: 'status', @@ -1582,7 +1600,7 @@ describeIfSqlite('LiveDelegationService', () => { } } }) - expect(admission.snapshot()).toMatchObject({ active: 0, pending: 0 }) + expect(await admission.snapshot()).toMatchObject({ active: 0, pending: 0 }) const second = await service.spawn('parent', { slotId: 'reviewer', @@ -1594,7 +1612,7 @@ describeIfSqlite('LiveDelegationService', () => { ) const secondChildId = repository.require(second.delegation.id).childSessionId! const continuation = service.beforeInteractionContinuation(firstChildId) - await vi.waitFor(() => expect(admission.snapshot().pending).toBe(1)) + await vi.waitFor(async () => expect((await admission.snapshot()).pending).toBe(1)) harness.publishAnswer(secondChildId, 'Second result.', 220) harness.publish({ @@ -1604,7 +1622,7 @@ describeIfSqlite('LiveDelegationService', () => { status: 'idle' }) await expect(continuation).resolves.toBe(true) - expect(admission.snapshot()).toMatchObject({ active: 1, pending: 0 }) + expect(await admission.snapshot()).toMatchObject({ active: 1, pending: 0 }) harness.publish({ sessionId: firstChildId, @@ -1615,7 +1633,7 @@ describeIfSqlite('LiveDelegationService', () => { expect(repository.requireTurn(first.turns[0]!.id).status).toBe('running') harness.publishAnswer(firstChildId, 'First result.', 240) harness.publish({ sessionId: firstChildId, kind: 'status', updatedAt: 241, status: 'idle' }) - await vi.waitFor(() => expect(admission.snapshot().active).toBe(0)) + await vi.waitFor(async () => expect((await admission.snapshot()).active).toBe(0)) }) it('keeps a recovered turn live when waiting suspends a queued lease', async () => { @@ -1634,7 +1652,7 @@ describeIfSqlite('LiveDelegationService', () => { harness.addChild('child-recovered-wait', created.delegation.id, 'generating') repository.bindChild(created.delegation.id, 'child-recovered-wait', 110) repository.markTurnStarted(created.turn.id, 120) - admission = new AgentInvocationAdmission(1, 10) + admission = await createAdmission(1, 10) const blocker = await admission.acquire({ ownerId: 'other-session' }) service = new LiveDelegationServiceCtor({ repository, @@ -1645,7 +1663,7 @@ describeIfSqlite('LiveDelegationService', () => { deletionGate }) service.start() - await vi.waitFor(() => expect(admission.snapshot().pending).toBe(1)) + await vi.waitFor(async () => expect((await admission.snapshot()).pending).toBe(1)) harness.publish({ sessionId: 'child-recovered-wait', @@ -1663,7 +1681,7 @@ describeIfSqlite('LiveDelegationService', () => { } } }) - await vi.waitFor(() => expect(admission.snapshot().pending).toBe(0)) + await vi.waitFor(async () => expect((await admission.snapshot()).pending).toBe(0)) expect(repository.requireTurn(created.turn.id).status).toBe('waiting_question') blocker.release() @@ -1902,7 +1920,7 @@ describeIfSqlite('LiveDelegationService', () => { sessions: harness.sessions, safety: harness.safety, consent: consentAuthority, - admission: new AgentInvocationAdmission(2, 10), + admission: await createAdmission(2, 10), deletionGate, onChanged: (_parentSessionId, delegationId) => { publishedStatuses.push(repository.require(delegationId).status) @@ -2261,7 +2279,7 @@ describeIfSqlite('LiveDelegationService', () => { sessions: harness.sessions, safety: harness.safety, consent: consentAuthority, - admission: new AgentInvocationAdmission(2, 10), + admission: await createAdmission(2, 10), deletionGate, observe: (observation) => dispatchObservation(observation), now: () => monotonicNow @@ -2335,7 +2353,7 @@ describeIfSqlite('LiveDelegationService', () => { sessions: harness.sessions, safety: harness.safety, consent: consentAuthority, - admission: new AgentInvocationAdmission(2, 10), + admission: await createAdmission(2, 10), deletionGate, observe: (observation) => dispatchObservation(observation), now: () => monotonicNow @@ -2392,7 +2410,7 @@ describeIfSqlite('LiveDelegationService', () => { sessions: harness.sessions, safety: harness.safety, consent: consentAuthority, - admission: new AgentInvocationAdmission(2, 10), + admission: await createAdmission(2, 10), deletionGate }) service.start() @@ -2436,7 +2454,7 @@ describeIfSqlite('LiveDelegationService', () => { sessions: harness.sessions, safety: harness.safety, consent: consentAuthority, - admission: new AgentInvocationAdmission(2, 10), + admission: await createAdmission(2, 10), deletionGate, observe: (observation) => dispatchObservation(observation), now: () => monotonicNow @@ -2487,7 +2505,7 @@ describeIfSqlite('LiveDelegationService', () => { sessions: harness.sessions, safety: harness.safety, consent: consentAuthority, - admission: new AgentInvocationAdmission(2, 10), + admission: await createAdmission(2, 10), deletionGate }) service.start() @@ -2537,7 +2555,7 @@ describeIfSqlite('LiveDelegationService', () => { sessions: harness.sessions, safety: harness.safety, consent: consentAuthority, - admission: new AgentInvocationAdmission(2, 10), + admission: await createAdmission(2, 10), deletionGate, observe: (observation) => dispatchObservation(observation), now: () => monotonicNow @@ -2651,7 +2669,7 @@ describeIfSqlite('LiveDelegationService', () => { sessions: harness.sessions, safety: harness.safety, consent: consentAuthority, - admission: new AgentInvocationAdmission(2, 10), + admission: await createAdmission(2, 10), deletionGate, onChanged }) @@ -2700,7 +2718,7 @@ describeIfSqlite('LiveDelegationService', () => { sessions: harness.sessions, safety: harness.safety, consent: consentAuthority, - admission: new AgentInvocationAdmission(2, 10), + admission: await createAdmission(2, 10), deletionGate }) service.start() @@ -2766,7 +2784,7 @@ describeIfSqlite('LiveDelegationService', () => { sessions: harness.sessions, safety: harness.safety, consent: consentAuthority, - admission: new AgentInvocationAdmission(2, 10), + admission: await createAdmission(2, 10), deletionGate }) service.start() @@ -2833,7 +2851,7 @@ describeIfSqlite('LiveDelegationService', () => { sessions: harness.sessions, safety: harness.safety, consent: consentAuthority, - admission: new AgentInvocationAdmission(2, 10), + admission: await createAdmission(2, 10), deletionGate }) diff --git a/test/main/performance/memory/clearScale.perf.ts b/test/main/performance/memory/clearScale.perf.ts index 55205ac00f..c57b05d073 100644 --- a/test/main/performance/memory/clearScale.perf.ts +++ b/test/main/performance/memory/clearScale.perf.ts @@ -5,6 +5,7 @@ import { expect } from 'vitest' import { AgentMemoryTable } from '@/memory/data/tables/agentMemory' import { describeIfNativeSqlite, requireDatabase } from '../../nativeSqliteHarness' +import { assertTimingBudget } from './timing' const CLEAR_CORPUS_SIZE = 10_000 const CLEAR_BATCH_SIZE = 256 @@ -64,7 +65,7 @@ describeIfNativeSqlite('Agent Memory clear scale', () => { })}` ) expect(batchCount).toBe(Math.ceil(CLEAR_CORPUS_SIZE / CLEAR_BATCH_SIZE)) - expect(maxBatchMs).toBeLessThanOrEqual(CLEAR_MAX_BATCH_MS) + assertTimingBudget('clear-10k-max-batch', maxBatchMs, CLEAR_MAX_BATCH_MS) expect(job.removed).toBe(CLEAR_CORPUS_SIZE) expect(table.countByAgent('clear-agent')).toBe(0) expect( diff --git a/test/main/performance/memory/recallScale.perf.ts b/test/main/performance/memory/recallScale.perf.ts index 6297f65848..04ad57b13f 100644 --- a/test/main/performance/memory/recallScale.perf.ts +++ b/test/main/performance/memory/recallScale.perf.ts @@ -5,7 +5,7 @@ import { AgentMemoryTable } from '@/memory/data/tables/agentMemory' import { buildMemoryFixture } from './fixtures' import { createMemoryPerfObserver } from './performanceObserver' import { describeIfNativeSqlite, requireDatabase } from '../../nativeSqliteHarness' -import { measurePairedPerformance, reportPerformance } from './timing' +import { assertTimingBudget, measurePairedPerformance, reportPerformance } from './timing' const RECALL_MAX_10K_TO_50K_INDEXED_GROWTH = 4 const RECALL_MAX_50K_OVER_LEGACY_RATIO = 1.25 @@ -130,8 +130,12 @@ describeIfNativeSqlite('Agent Memory #28 recall scale', () => { ) // The 10k legacy median can jitter independently on shared runners. Bound indexed scaling // directly, then use the 50k pair only to catch material overhead against the legacy path. - expect(indexedGrowth).toBeLessThanOrEqual(RECALL_MAX_10K_TO_50K_INDEXED_GROWTH) - expect(largeScaleRatio).toBeLessThanOrEqual(RECALL_MAX_50K_OVER_LEGACY_RATIO) + assertTimingBudget( + 'recall-indexed-growth', + indexedGrowth, + RECALL_MAX_10K_TO_50K_INDEXED_GROWTH + ) + assertTimingBudget('recall-over-legacy', largeScaleRatio, RECALL_MAX_50K_OVER_LEGACY_RATIO) } finally { db.close() } diff --git a/test/main/performance/memory/tapeScale.perf.ts b/test/main/performance/memory/tapeScale.perf.ts index 2a101ba128..2d4110d2fc 100644 --- a/test/main/performance/memory/tapeScale.perf.ts +++ b/test/main/performance/memory/tapeScale.perf.ts @@ -6,7 +6,7 @@ import { DeepChatTapeEntriesTable } from '@/tape/infrastructure/sqlite/tapeEntry import { createMemoryPerfObserver } from './performanceObserver' import { describeIfNativeSqlite, requireDatabase } from '../../nativeSqliteHarness' -import { measurePerformance, reportPerformance } from './timing' +import { assertTimingBudget, measurePerformance, reportPerformance } from './timing' function messagePayload(sessionId: string, index: number): string { const id = `message-${index.toString().padStart(6, '0')}` @@ -124,7 +124,7 @@ describeIfNativeSqlite('Agent Memory #28 Tape scale', () => { const largest = reports.get(100_000) expect(largest).toBeDefined() - expect(largest!.range).toBeLessThanOrEqual(largest!.full * 0.2) + assertTimingBudget('tape-range-100k', largest!.range, largest!.full * 0.2) } finally { db.close() } diff --git a/test/main/performance/memory/timing.test.ts b/test/main/performance/memory/timing.test.ts new file mode 100644 index 0000000000..d039a75110 --- /dev/null +++ b/test/main/performance/memory/timing.test.ts @@ -0,0 +1,37 @@ +import { afterEach, beforeEach, expect, it, vi } from 'vitest' +import { assertTimingBudget } from './timing' + +beforeEach(() => { + vi.spyOn(console, 'info').mockImplementation(() => {}) + vi.spyOn(console, 'warn').mockImplementation(() => {}) +}) + +afterEach(() => { + vi.unstubAllEnvs() + vi.restoreAllMocks() +}) + +it.each([undefined, '0', 'true'])( + 'enforces timing budgets unless report mode is explicit (%s)', + (mode) => { + vi.stubEnv('DEEPCHAT_MEMORY_PERF_REPORT_ONLY', mode) + expect(() => assertTimingBudget('write-overhead', -1, 5)).not.toThrow() + expect(() => assertTimingBudget('write-overhead', 6.94, 5)).toThrow() + } +) + +it('reports shared-runner overruns without failing the timing budget', () => { + vi.stubEnv('DEEPCHAT_MEMORY_PERF_REPORT_ONLY', '1') + expect(() => assertTimingBudget('write-overhead', 6.94, 5)).not.toThrow() + expect(console.warn).toHaveBeenCalledWith(expect.stringContaining('memory-perf-budget-exceeded')) +}) + +it('rejects invalid measurements and budgets in both modes', () => { + for (const mode of [undefined, '1']) { + vi.stubEnv('DEEPCHAT_MEMORY_PERF_REPORT_ONLY', mode) + for (const invalid of [NaN, Infinity, -Infinity]) { + expect(() => assertTimingBudget('write-overhead', invalid, 5)).toThrow() + expect(() => assertTimingBudget('write-overhead', 1, invalid)).toThrow() + } + } +}) diff --git a/test/main/performance/memory/timing.ts b/test/main/performance/memory/timing.ts index 6d0df10e10..14f048b133 100644 --- a/test/main/performance/memory/timing.ts +++ b/test/main/performance/memory/timing.ts @@ -1,4 +1,5 @@ import { performance } from 'node:perf_hooks' +import { expect } from 'vitest' import { summarizeDurations } from './performanceObserver' @@ -80,3 +81,17 @@ export function measurePairedPerformance( export function reportPerformance(report: PerformanceReport): void { console.info(`[memory-perf] ${JSON.stringify(report)}`) } + +export function assertTimingBudget(scenario: string, actual: number, maximum: number): void { + expect(Number.isFinite(actual), `${scenario}: measured timing must be finite`).toBe(true) + expect(Number.isFinite(maximum), `${scenario}: timing budget must be finite`).toBe(true) + const reportOnly = process.env.DEEPCHAT_MEMORY_PERF_REPORT_ONLY === '1' + const exceeded = actual > maximum + const report = { scenario, actual, maximum, exceeded, reportOnly } + console.info(`[memory-perf-budget] ${JSON.stringify(report)}`) + if (reportOnly) { + if (exceeded) console.warn(`[memory-perf-budget-exceeded] ${JSON.stringify(report)}`) + return + } + expect(actual, scenario).toBeLessThanOrEqual(maximum) +} diff --git a/test/main/performance/memory/vectorTransportScale.perf.ts b/test/main/performance/memory/vectorTransportScale.perf.ts new file mode 100644 index 0000000000..227083bab5 --- /dev/null +++ b/test/main/performance/memory/vectorTransportScale.perf.ts @@ -0,0 +1,164 @@ +import { createRequire } from 'node:module' +import fs from 'node:fs' +import os from 'node:os' +import path from 'node:path' +import { afterEach, beforeEach, expect, it, vi } from 'vitest' +import { HarnessRuntime } from '@/harness/runtime' +import { RutisMemoryVectorStores } from '@/harness/memory-vector' +import { MemoryVectorStore, createMemoryVectorStorePaths } from '@/memory/infra/memoryVectorStore' +import type { IMemoryVectorStore } from '@/memory/ports' +import { assertTimingBudget } from './timing' + +const actualFs = await vi.importActual('node:fs') +const require = createRequire(import.meta.url) +const DIMENSIONS = 1536 +const embedding = { providerId: 'fixture', modelId: 'fixture' } +const percentile = (values: number[], fraction: number) => + [...values].sort((a, b) => a - b)[Math.ceil(values.length * fraction) - 1] +const mean = (values: number[]) => values.reduce((sum, value) => sum + value, 0) / values.length +const timed = async (task: () => Promise) => { + const start = performance.now() + await task() + return performance.now() - start +} +let directory: string +let runtime: HarnessRuntime | undefined +let stores: RutisMemoryVectorStores | undefined +let native: IMemoryVectorStore | undefined + +beforeEach(() => { + for (const method of [ + 'existsSync', + 'readdirSync', + 'mkdirSync', + 'mkdtempSync', + 'rmSync', + 'renameSync', + 'writeFileSync' + ] as const) { + vi.mocked(fs[method]).mockImplementation(actualFs[method] as never) + } + directory = fs.mkdtempSync(path.join(os.tmpdir(), 'deepchat-vector-transport-perf-')) +}) + +afterEach(async () => { + await native?.close() + await stores?.dispose() + await runtime?.stop() + native = undefined + stores = undefined + runtime = undefined + actualFs.rmSync(directory, { recursive: true, force: true }) +}) + +it.each([1000, 10000, 50000])( + 'bounds actual vector transport overhead at %i rows', + async (rows) => { + const host = { appPath: path.resolve('.'), userDataPath: directory, isPackaged: false } + const firstOpens: number[] = [] + let remote!: IMemoryVectorStore + for (let sample = 0; sample < 5; sample++) { + runtime = await HarnessRuntime.start({ + directory: path.resolve('out/harness'), + electronExecutable: require('electron'), + capacity: 1, + maxPending: 4, + observe: () => {} + }) + stores = new RutisMemoryVectorStores(runtime, { + memoryDbDir: path.join(directory, `remote-${sample}`), + host + }) + const openedAt = performance.now() + remote = await stores.createVectorStore('agent', embedding, DIMENSIONS) + firstOpens.push(performance.now() - openedAt) + if (sample < 4) { + await remote.close() + await stores.dispose() + await runtime.stop() + } + } + const runningRuntime = runtime! + native = await MemoryVectorStore.create( + createMemoryVectorStorePaths(path.join(directory, 'native'), 'agent'), + DIMENSIONS, + embedding, + host + ) + const baseline = native + const nativeWrites: number[] = [] + const remoteWrites: number[] = [] + for (let offset = 0; offset < rows; offset += 50) { + const records = Array.from({ length: Math.min(50, rows - offset) }, (_, index) => ({ + memoryId: `memory-${String(offset + index).padStart(8, '0')}`, + embedding: Array.from({ length: DIMENSIONS }, (_, component) => + component === (offset + index) % DIMENSIONS ? 1 : 0 + ) + })) + const pair = [() => baseline.upsert(records), () => remote.upsert(records)] + const times: number[] = [] + for (const index of offset % 100 === 0 ? [0, 1] : [1, 0]) + times[index] = await timed(pair[index]) + nativeWrites.push(times[0]) + remoteWrites.push(times[1]) + } + const vector = Array.from({ length: DIMENSIONS }, (_, index) => (index === 0 ? 1 : 0)) + const query = (store: IMemoryVectorStore) => store.query(vector, { topK: 20 }) + await query(baseline) + await query(remote) + const traffic = runningRuntime.memoryTransport + const nativeQueries: number[] = [] + const remoteQueries: number[] = [] + for (let sample = 0; sample < 30; sample++) { + for (const index of sample % 2 ? [1, 0] : [0, 1]) { + const target = index === 0 ? nativeQueries : remoteQueries + target.push(await timed(() => query(index === 0 ? baseline : remote))) + } + } + const nativeQueryP95 = percentile(nativeQueries, 0.95) + const remoteQueryP95 = percentile(remoteQueries, 0.95) + const nativeWriteMean = mean(nativeWrites) + const remoteWriteMean = mean(remoteWrites) + const report = { + rows, + dimensions: DIMENSIONS, + firstOpens, + firstOpenMedianMs: percentile(firstOpens, 0.5), + firstOpenP95Ms: percentile(firstOpens, 0.95), + nativeQueryP95, + remoteQueryP95, + nativeWriteMean, + remoteWriteMean, + queryTrafficBytes: + runningRuntime.memoryTransport.sentBytes + + runningRuntime.memoryTransport.receivedBytes - + traffic.sentBytes - + traffic.receivedBytes, + pending: runningRuntime.memoryTransport.pending, + peakPending: runningRuntime.memoryTransport.peakPending, + nativeNode: process.versions.node, + remoteNode: runningRuntime.memoryIdentity?.node, + nativeModules: process.versions.modules, + remoteModules: runningRuntime.memoryIdentity?.modules + } + process.stdout.write(`[memory-vector-transport] ${JSON.stringify(report)}\n`) + expect(report.nativeNode, 'Use pnpm test:main:memory-perf for the Electron runtime').toBe( + report.remoteNode + ) + expect(report.nativeModules).toBe(report.remoteModules) + expect(report.pending).toBe(0) + assertTimingBudget( + `vector-query-overhead-${rows}`, + remoteQueryP95 - nativeQueryP95, + Math.max(5, nativeQueryP95 * 0.2) + ) + assertTimingBudget( + `vector-write-overhead-${rows}`, + remoteWriteMean - nativeWriteMean, + Math.max(5, nativeWriteMean * 0.25) + ) + assertTimingBudget(`vector-first-open-median-${rows}`, report.firstOpenMedianMs, 500) + assertTimingBudget(`vector-first-open-p95-${rows}`, report.firstOpenP95Ms, 1000) + }, + 120_000 +) diff --git a/test/main/platform/fileWatcher/watcherHostClient.test.ts b/test/main/platform/fileWatcher/watcherHostClient.test.ts new file mode 100644 index 0000000000..717d27a3ce --- /dev/null +++ b/test/main/platform/fileWatcher/watcherHostClient.test.ts @@ -0,0 +1,175 @@ +import { EventEmitter } from 'node:events' +import path from 'node:path' +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' +import { spawnFileWatcherHost } from '@/app/fileWatcherHost' +import { WatcherHostClient, type FileWatcherHost } from '@/platform/fileWatcher/watcherHostClient' +import type { FileWatcherRpcRequest, WatchRequest } from '@/platform/fileWatcher/watcherTypes' + +const mocks = vi.hoisted(() => ({ fork: vi.fn() })) +vi.mock('electron', () => ({ utilityProcess: { fork: mocks.fork } })) + +const request: WatchRequest = { + id: 'workspace', + rootPath: path.resolve('workspace'), + hostKind: 'content', + purpose: 'workspace-content', + recursive: true +} + +function child() { + const host = Object.assign(new EventEmitter(), { + postMessage: vi.fn((message: FileWatcherRpcRequest) => { + queueMicrotask(() => + host.emit('message', { + type: 'file-watcher:response', + id: message.id, + ok: true, + data: undefined + }) + ) + }), + kill: vi.fn(() => true) + }) + return host +} + +describe('File watcher helper ownership', () => { + beforeEach(() => { + mocks.fork.mockReset() + }) + afterEach(() => vi.useRealTimers()) + + const client = () => + new WatcherHostClient('content', (kind, signal) => + spawnFileWatcherHost(path.resolve('out/main/fileWatcherUtilityHost.js'), kind, signal) + ) + + it('waits for actual helper exit after shutdown acknowledgement and fences further work', async () => { + const native = child() + mocks.fork.mockReturnValue(native) + const owner = client() + const batch = vi.fn() + owner.onBatch(batch) + const watching = owner.watch(request) + await Promise.resolve() + native.emit('spawn') + await watching + let stopped = false + const closing = owner.shutdown().then(() => { + stopped = true + }) + await vi.waitFor(() => expect(native.kill).toHaveBeenCalledOnce()) + expect(stopped).toBe(false) + native.emit('message', { type: 'file-watcher:event-batch', batch: { watchId: request.id } }) + expect(batch).not.toHaveBeenCalled() + await expect(owner.watch({ ...request, id: 'late' })).rejects.toThrow('stopped') + native.emit('exit', 0) + await closing + await owner.shutdown() + expect(mocks.fork).toHaveBeenCalledOnce() + }) + + it('keeps canceled startup owned until its native process exits', async () => { + const native = child() + mocks.fork.mockReturnValue(native) + const owner = client() + const watching = Promise.allSettled([owner.watch(request)]) + await Promise.resolve() + let stopped = false + const closing = owner.shutdown().then(() => { + stopped = true + }) + expect(native.kill).toHaveBeenCalledOnce() + await Promise.resolve() + expect(stopped).toBe(false) + native.emit('spawn') + expect(native.postMessage).not.toHaveBeenCalled() + native.emit('exit', 0) + expect((await watching)[0].status).toBe('rejected') + await closing + await expect(owner.watch(request)).rejects.toThrow('stopped') + expect(mocks.fork).toHaveBeenCalledOnce() + }) + + it('retains startup ownership when native fork synchronously triggers shutdown', async () => { + const native = child() + const owner = client() + let closing: Promise | undefined + mocks.fork.mockImplementation(() => { + closing = owner.shutdown() + return native + }) + const watching = Promise.allSettled([owner.watch(request)]) + await Promise.resolve() + expect(native.kill).toHaveBeenCalledOnce() + let stopped = false + void closing!.then(() => { + stopped = true + }) + await Promise.resolve() + expect(stopped).toBe(false) + native.emit('exit', 0) + await closing + expect((await watching)[0].status).toBe('rejected') + expect(native.postMessage).not.toHaveBeenCalled() + }) + + it('drains a late host even when its launcher ignores cancellation', async () => { + const opening = Promise.withResolvers() + const exit = Promise.withResolvers() + const host: FileWatcherHost = { + postMessage: vi.fn(), + onMessage: vi.fn(), + onExit: vi.fn(), + onError: vi.fn(), + terminate: vi.fn(() => exit.promise) + } + const owner = new WatcherHostClient('git', async () => await opening.promise) + const watching = Promise.allSettled([owner.watch({ ...request, hostKind: 'git' })]) + await Promise.resolve() + let stopped = false + const closing = owner.shutdown().then(() => { + stopped = true + }) + opening.resolve(host) + await vi.waitFor(() => expect(host.terminate).toHaveBeenCalledOnce()) + expect(stopped).toBe(false) + expect(host.postMessage).not.toHaveBeenCalled() + exit.resolve() + expect((await watching)[0].status).toBe('rejected') + await closing + }) + + it('replays active watches after helper loss and ignores retired-host events', async () => { + vi.useFakeTimers() + const hosts = [child(), child()] + mocks.fork.mockImplementation(() => { + const native = hosts[mocks.fork.mock.calls.length - 1] + queueMicrotask(() => native.emit('spawn')) + return native + }) + const owner = client() + const batches = vi.fn() + owner.onBatch(batches) + const watching = owner.watch(request) + await vi.advanceTimersByTimeAsync(0) + await watching + hosts[0].emit('exit', 1) + await vi.advanceTimersByTimeAsync(800) + expect(hosts[1].postMessage).toHaveBeenCalledWith( + expect.objectContaining({ method: 'watch', args: [request] }) + ) + const batch = { watchId: request.id } + hosts[0].emit('message', { type: 'file-watcher:event-batch', batch }) + hosts[0].emit('exit', 1) + expect(batches).not.toHaveBeenCalled() + hosts[1].emit('message', { type: 'file-watcher:event-batch', batch }) + expect(batches).toHaveBeenCalledWith(batch) + const closing = owner.shutdown() + await vi.advanceTimersByTimeAsync(0) + hosts[1].emit('exit', 0) + await closing + await vi.advanceTimersByTimeAsync(1600) + expect(mocks.fork).toHaveBeenCalledTimes(2) + }) +}) diff --git a/test/main/platform/fileWatcher/watcherPool.test.ts b/test/main/platform/fileWatcher/watcherPool.test.ts index f73e68d6e7..9772fcbc60 100644 --- a/test/main/platform/fileWatcher/watcherPool.test.ts +++ b/test/main/platform/fileWatcher/watcherPool.test.ts @@ -5,7 +5,7 @@ import type { WatchBatchListener, WatcherEventBatch, WatchRequest, - WatchStatus, + WatcherStatus, WatchStatusListener } from '../../../../src/main/platform/fileWatcher' @@ -51,8 +51,8 @@ function createPoolWithClients() { const git = new FakeWatcherHostClient() return { pool: new WatcherPool({ - content: content as never, - git: git as never + content, + git }), content, git @@ -72,6 +72,22 @@ function createRequest(overrides: Partial = {}): WatchRequest { } describe('WatcherPool', () => { + it('drains both helper owners before reporting a shutdown failure', async () => { + const { pool, content, git } = createPoolWithClients() + const closed = Promise.withResolvers() + content.shutdown.mockRejectedValueOnce(new Error('content shutdown failed')) + git.shutdown.mockReturnValueOnce(closed.promise) + let settled = false + const result = pool.destroy().finally(() => { + settled = true + }) + const rejected = expect(result).rejects.toThrow('content shutdown failed') + await Promise.resolve() + expect(settled).toBe(false) + closed.resolve() + await rejected + }) + it('deduplicates identical requests and keeps the backend alive until the last handle closes', async () => { const { pool, content } = createPoolWithClients() const firstListener = vi.fn() diff --git a/test/main/platform/imageCache.test.ts b/test/main/platform/imageCache.test.ts index 3c20e356c0..acd23aa96a 100644 --- a/test/main/platform/imageCache.test.ts +++ b/test/main/platform/imageCache.test.ts @@ -10,14 +10,9 @@ vi.unmock('node:fs/promises') vi.unmock('path') vi.unmock('node:path') -const electronMock = vi.hoisted(() => ({ userDataPath: '' })) +const profile = { userDataPath: '' } const axiosMock = vi.hoisted(() => vi.fn()) -vi.mock('electron', () => ({ - app: { - getPath: vi.fn(() => electronMock.userDataPath) - } -})) vi.mock('axios', () => ({ default: axiosMock })) import { cacheImage, resolveCachedImageDataUrl } from '@/platform/imageCache' @@ -29,9 +24,10 @@ describe('imageCache', () => { beforeEach(async () => { axiosMock.mockReset() vi.spyOn(console, 'error').mockImplementation(() => {}) - electronMock.userDataPath = await fs.mkdtemp(path.join(os.tmpdir(), 'deepchat-image-cache-')) - tempDirectories.push(electronMock.userDataPath) - await fs.mkdir(path.join(electronMock.userDataPath, 'images')) + profile.userDataPath = await fs.mkdtemp(path.join(os.tmpdir(), 'deepchat-image-cache-')) + vi.stubEnv('DEEPCHAT_USER_DATA_DIR', profile.userDataPath) + tempDirectories.push(profile.userDataPath) + await fs.mkdir(path.join(profile.userDataPath, 'images')) }) afterEach(async () => { @@ -41,6 +37,18 @@ describe('imageCache', () => { .map((directory) => fs.rm(directory, { recursive: true, force: true })) ) vi.mocked(console.error).mockRestore() + vi.unstubAllEnvs() + }) + + it('requires an absolute bootstrap profile before accessing cached files', async () => { + vi.stubEnv('DEEPCHAT_USER_DATA_DIR', undefined) + await expect(resolveCachedImageDataUrl('imgcache://fixture.png')).rejects.toThrow( + 'absolute bootstrap profile' + ) + vi.stubEnv('DEEPCHAT_USER_DATA_DIR', 'relative-profile') + await expect(cacheImage('data:image/png;base64,AA==')).rejects.toThrow( + 'absolute bootstrap profile' + ) }) it('preserves distinct valid images whose base64 differs only by case', async () => { @@ -112,7 +120,7 @@ describe('imageCache', () => { ) await expect(cacheImage(sourceUrl, { allowPrivateNetwork: false })).resolves.toBe(sourceUrl) - await expect(fs.readdir(path.join(electronMock.userDataPath, 'images'))).resolves.toEqual([]) + await expect(fs.readdir(path.join(profile.userDataPath, 'images'))).resolves.toEqual([]) }) it.each(['http', 'HTTP'])( @@ -138,9 +146,7 @@ describe('imageCache', () => { }) ) await expect( - fs.readFile( - path.join(electronMock.userDataPath, 'images', cached.slice('imgcache://'.length)) - ) + fs.readFile(path.join(profile.userDataPath, 'images', cached.slice('imgcache://'.length))) ).resolves.toEqual(Buffer.from('0000001866747970617669660000000061766966', 'hex')) } ) @@ -154,7 +160,7 @@ describe('imageCache', () => { }) await expect(cacheImage(sourceUrl, { allowPrivateNetwork: true })).resolves.toBe(sourceUrl) - await expect(fs.readdir(path.join(electronMock.userDataPath, 'images'))).resolves.toEqual([]) + await expect(fs.readdir(path.join(profile.userDataPath, 'images'))).resolves.toEqual([]) }) it('does not cache HTTP responses above the image size limit', async () => { @@ -166,7 +172,7 @@ describe('imageCache', () => { }) await expect(cacheImage(sourceUrl, { allowPrivateNetwork: true })).resolves.toBe(sourceUrl) - await expect(fs.readdir(path.join(electronMock.userDataPath, 'images'))).resolves.toEqual([]) + await expect(fs.readdir(path.join(profile.userDataPath, 'images'))).resolves.toEqual([]) }) it('propagates caller cancellation to the active HTTP request', async () => { @@ -196,14 +202,12 @@ describe('imageCache', () => { expect(cached).toMatch(/^imgcache:\/\/.+\.png$/) await expect( - fs.readFile( - path.join(electronMock.userDataPath, 'images', cached.slice('imgcache://'.length)) - ) + fs.readFile(path.join(profile.userDataPath, 'images', cached.slice('imgcache://'.length))) ).resolves.toEqual(Buffer.from('image')) }) it('resolves a cached image to a MIME-correct data URL', async () => { - await fs.writeFile(path.join(electronMock.userDataPath, 'images', 'generated.png'), 'image') + await fs.writeFile(path.join(profile.userDataPath, 'images', 'generated.png'), 'image') await expect(resolveCachedImageDataUrl('imgcache://generated.png')).resolves.toBe( 'data:image/png;base64,aW1hZ2U=' @@ -211,7 +215,7 @@ describe('imageCache', () => { }) it('resolves cached images with an uppercase scheme', async () => { - await fs.writeFile(path.join(electronMock.userDataPath, 'images', 'generated.png'), 'image') + await fs.writeFile(path.join(profile.userDataPath, 'images', 'generated.png'), 'image') await expect(resolveCachedImageDataUrl('IMGCACHE://generated.png')).resolves.toBe( 'data:image/png;base64,aW1hZ2U=' @@ -225,13 +229,9 @@ describe('imageCache', () => { }) it('rejects symbolic links that escape the image cache root', async () => { - const outsidePath = path.join(electronMock.userDataPath, 'outside.png') + const outsidePath = path.join(profile.userDataPath, 'outside.png') await fs.writeFile(outsidePath, 'image') - await fs.symlink( - outsidePath, - path.join(electronMock.userDataPath, 'images', 'outside.png'), - 'file' - ) + await fs.symlink(outsidePath, path.join(profile.userDataPath, 'images', 'outside.png'), 'file') await expect(resolveCachedImageDataUrl('imgcache://outside.png')).rejects.toThrow( 'Cached image reference is not a regular file' @@ -246,7 +246,7 @@ describe('imageCache', () => { it('rejects cached images above the MCP image input limit', async () => { await fs.writeFile( - path.join(electronMock.userDataPath, 'images', 'oversized.png'), + path.join(profile.userDataPath, 'images', 'oversized.png'), Buffer.alloc(8 * 1024 * 1024 + 1) ) @@ -256,7 +256,7 @@ describe('imageCache', () => { }) it('rejects unsupported cached image types', async () => { - await fs.writeFile(path.join(electronMock.userDataPath, 'images', 'generated.txt'), 'image') + await fs.writeFile(path.join(profile.userDataPath, 'images', 'generated.txt'), 'image') await expect(resolveCachedImageDataUrl('imgcache://generated.txt')).rejects.toThrow( 'Unsupported cached image type' diff --git a/test/main/platform/proxy.test.ts b/test/main/platform/proxy.test.ts index 047046a0a8..13a136ac76 100644 --- a/test/main/platform/proxy.test.ts +++ b/test/main/platform/proxy.test.ts @@ -1,4 +1,6 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' +import { createServer } from 'node:http' +import type { Socket } from 'node:net' const electronMocks = vi.hoisted(() => ({ setProxy: vi.fn(), @@ -6,11 +8,11 @@ const electronMocks = vi.hoisted(() => ({ })) const { Agent, EnvHttpProxyAgent, setGlobalDispatcher } = vi.hoisted(() => ({ - Agent: vi.fn(function Agent() { - return {} + Agent: vi.fn(function Agent(_options: unknown) { + return { close: vi.fn(async () => undefined), destroy: vi.fn(async () => undefined) } }), - EnvHttpProxyAgent: vi.fn(function EnvHttpProxyAgent() { - return {} + EnvHttpProxyAgent: vi.fn(function EnvHttpProxyAgent(_options: unknown) { + return { close: vi.fn(async () => undefined), destroy: vi.fn(async () => undefined) } }), setGlobalDispatcher: vi.fn() })) @@ -54,7 +56,7 @@ describe('ProxyConfig readiness', () => { config.initFromConfig(ProxyMode.SYSTEM, '') let settled = false - void config.whenReady().then(() => { + void config.getProxySnapshot().then(() => { settled = true }) await Promise.resolve() @@ -62,6 +64,11 @@ describe('ProxyConfig readiness', () => { expect(settled).toBe(false) finishResolution('DIRECT') await expect(config.whenReady()).resolves.toBe(true) + await expect(config.getProxySnapshot()).resolves.toEqual({ + version: 1, + proxyUrl: null, + noProxy: '' + }) expect(electronMocks.resolveProxy).toHaveBeenCalledWith('https://www.google.com') }) @@ -90,6 +97,109 @@ describe('ProxyConfig readiness', () => { expect(config.getProxyUrl()).toBeNull() expect(electronMocks.setProxy).toHaveBeenLastCalledWith({ mode: 'direct' }) }) + + it('joins draining pools and accepted native resolution while fencing late effects', async () => { + electronMocks.resolveProxy.mockResolvedValue('DIRECT') + const config = new ProxyConfig() + await config.resolveProxy() + const previous = setGlobalDispatcher.mock.calls.at(-1)![0] + const drain = Promise.withResolvers() + previous.close.mockReturnValueOnce(drain.promise) + await config.resolveProxy() + const current = setGlobalDispatcher.mock.calls.at(-1)![0] + const native = Promise.withResolvers() + electronMocks.resolveProxy.mockReturnValueOnce(native.promise) + const pending = config.resolveProxy() + const rejected = expect(pending).rejects.toThrow('Native HTTP is stopping') + await vi.waitFor(() => expect(electronMocks.resolveProxy).toHaveBeenCalledTimes(3)) + const queued = expect(config.resolveProxy()).rejects.toThrow('Native HTTP is stopping') + const installations = setGlobalDispatcher.mock.calls.length + const nativeCalls = electronMocks.setProxy.mock.calls.length + const closing = config.close() + expect(config.close()).toBe(closing) + let closed = false + void closing.then(() => { + closed = true + }) + await vi.waitFor(() => { + expect(previous.destroy).toHaveBeenCalledOnce() + expect(current.destroy).toHaveBeenCalledOnce() + }) + expect(closed).toBe(false) + native.resolve('PROXY late.example:8080') + await Promise.all([rejected, queued]) + expect(setGlobalDispatcher).toHaveBeenCalledTimes(installations) + expect(electronMocks.setProxy).toHaveBeenCalledTimes(nativeCalls) + expect(closed).toBe(false) + drain.resolve() + await closing + await expect(config.getProxySnapshot()).rejects.toThrow('Native HTTP is stopping') + expect(() => config.setProxyMode(ProxyMode.NONE)).toThrow('Native HTTP is stopping') + }) + + it('retains a failed transport receipt and invalidates its owner', async () => { + electronMocks.resolveProxy.mockResolvedValue('DIRECT') + const failed = vi.fn() + const config = new ProxyConfig(failed) + await config.resolveProxy() + const previous = setGlobalDispatcher.mock.calls.at(-1)![0] + const failure = new Error('transport did not close') + previous.close.mockRejectedValue(failure) + previous.destroy.mockRejectedValue(failure) + await config.resolveProxy().catch(() => {}) + await vi.waitFor(() => expect(failed).toHaveBeenCalledExactlyOnceWith(failure)) + await expect(config.getProxySnapshot()).rejects.toBe(failure) + const closing = config.close() + await expect(closing).rejects.toThrow('Native HTTP did not retire') + expect(config.close()).toBe(closing) + }) + + it( + 'aborts a real held request after graceful Agent retirement has started', + { timeout: 15_000 }, + async () => { + const actual = await vi.importActual('undici') + const sockets = new Set() + const entered = Promise.withResolvers() + const server = createServer(() => entered.resolve()) + server.on('connection', (socket) => { + sockets.add(socket) + socket.once('close', () => sockets.delete(socket)) + }) + await new Promise((resolve) => server.listen(0, '127.0.0.1', resolve)) + const agent = new actual.Agent({ headersTimeout: 0, bodyTimeout: 0 }) + let settled = false + let graceful: Promise | undefined + const request = actual + .fetch(`http://127.0.0.1:${(server.address() as { port: number }).port}`, { + dispatcher: agent + }) + .catch((error: unknown) => error) + .finally(() => { + settled = true + }) + try { + await entered.promise + graceful = agent.close() + await agent.destroy(new Error('owned shutdown')) + await vi.waitFor( + () => { + expect(settled).toBe(true) + expect(sockets.size).toBe(0) + }, + { timeout: 5000 } + ) + expect(await request).toBeInstanceOf(Error) + await graceful + } finally { + for (const socket of sockets) socket.destroy() + await agent.destroy() + await request + await graceful + await new Promise((resolve) => server.close(() => resolve())) + } + } + ) }) const PROXY_ENV_KEYS = [ @@ -230,6 +340,9 @@ describe('process-wide fetch dispatcher', () => { .mockRejectedValueOnce(new Error('resolve failed')) const config = new ProxyConfig() await config.resolveProxy() + const snapshot = await config.getProxySnapshot() + expect(snapshot).toMatchObject({ version: 1, proxyUrl: 'http://127.0.0.1:7890' }) + expect(snapshot.noProxy).toContain('localhost') vi.clearAllMocks() electronMocks.setProxy.mockResolvedValue(undefined) @@ -239,6 +352,7 @@ describe('process-wide fetch dispatcher', () => { expect(Agent).not.toHaveBeenCalled() expect(setGlobalDispatcher).not.toHaveBeenCalled() expect(config.getProxyUrl()).toBe('http://127.0.0.1:7890') + await expect(config.getProxySnapshot()).resolves.toEqual(snapshot) }) it('preserves inherited NO_PROXY after a DIRECT to proxy transition', async () => { @@ -268,6 +382,11 @@ describe('process-wide fetch dispatcher', () => { await expect(config.resolveProxy()).resolves.toBe(true) expect(config.getProxyUrl()).toBeNull() + await expect(config.getProxySnapshot()).resolves.toEqual({ + version: 2, + proxyUrl: null, + noProxy: '' + }) expect(process.env.http_proxy).toBeUndefined() expect(process.env.https_proxy).toBeUndefined() expect(process.env.HTTP_PROXY).toBeUndefined() diff --git a/test/main/plugin/pluginRoutes.test.ts b/test/main/plugin/pluginRoutes.test.ts index a6e481ad03..9cd363183b 100644 --- a/test/main/plugin/pluginRoutes.test.ts +++ b/test/main/plugin/pluginRoutes.test.ts @@ -20,9 +20,9 @@ function setup(ownerPluginId: string | null) { } const settingsWindow: PluginSettingsWindowPort = { open: async () => {}, - close: () => {}, - closeAll: () => {}, - getPluginIdForWebContents: () => ownerPluginId + close: async () => {}, + closeAll: async () => {}, + getPluginIdForWebContents: async () => ownerPluginId } const routes = createPluginRoutes(pluginService as unknown as PluginServicePort, settingsWindow) return { pluginService, routes } diff --git a/test/main/plugin/pluginService.test.ts b/test/main/plugin/pluginService.test.ts index fe6ac0aa0f..9dcc188aeb 100644 --- a/test/main/plugin/pluginService.test.ts +++ b/test/main/plugin/pluginService.test.ts @@ -1,3 +1,4 @@ +import { createPluginToolPolicies } from '../../helpers/pluginToolPolicies' import { createHash } from 'node:crypto' import fs from 'node:fs' import { chmod, mkdir, mkdtemp, readFile, rm, writeFile } from 'node:fs/promises' @@ -7,8 +8,8 @@ import { app, BrowserWindow, shell } from 'electron' import { zipSync } from 'fflate' import { afterEach, describe, expect, it, vi } from 'vitest' -vi.mock('electron-store', () => ({ - default: class MockElectronStore { +vi.mock('conf', () => ({ + default: class MockConf { private data: Record constructor(options?: { defaults?: Record }) { @@ -104,12 +105,21 @@ const createPluginService = async ( registerPluginSkill: vi.fn().mockResolvedValue(undefined), unregisterPluginSkillsByOwner: vi.fn().mockResolvedValue(undefined) } + const toolPolicies = createPluginToolPolicies() const presenter = new PluginService({ + workingDirectory: process.cwd(), + profileDirectory: app.getPath('userData'), + homeDirectory: app.getPath('home'), + appVersion: app.getVersion(), + openExternal: (url: string) => shell.openExternal(url), + openPath: (filePath: string) => shell.openPath(filePath), + hookEnvironment: () => ({}), + toolPolicies, platform, arch: options.arch, appPath: options.appPath ?? process.cwd(), - isPackaged: options.isPackaged, - resourcesPath: options.resourcesPath, + isPackaged: options.isPackaged ?? false, + resourcesPath: options.resourcesPath ? path.resolve(options.resourcesPath) : '', nowledgeMem: options.nowledgeMem, mcpSettings, mcpService, @@ -119,6 +129,7 @@ const createPluginService = async ( } as any) return Object.assign(presenter, { __mocks: { + toolPolicies, mcpServers, mcpSettings, mcpService, @@ -1132,11 +1143,12 @@ describe('PluginService', () => { await writeFile(workspaceManifestPath, `${JSON.stringify(manifest, null, 2)}\n`) const presenter = await createPluginService('darwin', fixture.appPath) - const { getPluginToolPolicy } = await import('@/plugin/toolPolicyStore') const enabled = await presenter.enablePlugin(fixture.pluginId) expect(enabled.ok).toBe(true) - expect(getPluginToolPolicy('fixture-tools', 'fixture_tool')).toBe('ask') + expect(presenter.__mocks.toolPolicies.resolve('fixture-tools', 'fixture_tool').decision).toBe( + 'ask' + ) const rejectedManifest = { ...manifest, @@ -1159,7 +1171,9 @@ describe('PluginService', () => { expect((presenter as any).store.get('resources')).toEqual([]) expect((presenter as any).store.get('runtimes')).toEqual([]) expect(servers['fixture-tools']).toBeUndefined() - expect(getPluginToolPolicy('fixture-tools', 'fixture_tool')).toBeNull() + expect( + presenter.__mocks.toolPolicies.resolve('fixture-tools', 'fixture_tool').decision + ).toBeNull() }) it('loads official packages only from resources roots in packaged mode', async () => { @@ -1607,8 +1621,6 @@ describe('PluginService', () => { it('preserves an explicit legacy CUA disable in installation intent before cleanup', async () => { const presenter = await createPluginService('darwin') - const { getPluginToolPolicy, registerPluginToolPolicy } = - await import('@/plugin/toolPolicyStore') const now = Date.now() ;(presenter as any).store.set('installations', [ { @@ -1632,7 +1644,7 @@ describe('PluginService', () => { sourceId: 'com.deepchat.plugins.cua', ownerPluginId: 'com.deepchat.plugins.cua' } - registerPluginToolPolicy({ + presenter.__mocks.toolPolicies.register({ pluginId: 'com.deepchat.plugins.cua', serverId: 'cua-driver', tools: { click: 'ask' }, @@ -1646,7 +1658,7 @@ describe('PluginService', () => { enabled: false }) expect(presenter.__mocks.mcpSettings.removeMcpServer).toHaveBeenCalledWith('cua-driver') - expect(getPluginToolPolicy('cua-driver', 'click')).toBeNull() + expect(presenter.__mocks.toolPolicies.resolve('cua-driver', 'click').decision).toBeNull() }) it('does not reinterpret the supervised CUA record as a legacy disable signal', async () => { @@ -1705,7 +1717,7 @@ describe('PluginService', () => { const activatePluginImplementation = (presenter as any).activatePlugin.bind(presenter) const activatePlugin = vi .spyOn(presenter as any, 'activatePlugin') - .mockImplementation(async (pluginId: string) => { + .mockImplementation(async (pluginId) => { if (pluginId === 'com.deepchat.plugins.cua') { return } diff --git a/test/main/plugin/runtimeSupervisor.test.ts b/test/main/plugin/runtimeSupervisor.test.ts index 556cd294f4..511a33a2e3 100644 --- a/test/main/plugin/runtimeSupervisor.test.ts +++ b/test/main/plugin/runtimeSupervisor.test.ts @@ -1042,4 +1042,108 @@ describe('PluginRuntimeSupervisor', () => { normalStop.resolve() await disable }) + it.each(['launch-check', 'adapter-start', 'process-start'] as const)( + 'cleans a canceled authentication restart during %s', + async (stage) => { + const supervisor = new PluginRuntimeSupervisor() + const { port, running, active } = createProcessPort() + const entered = deferred() + const finish = deferred() + const safety = createSafetyStore() + const guard = { + verify: vi.fn(async () => { + if (stage === 'launch-check') { + entered.resolve() + await finish.promise + } + return runtimeFingerprint('a') + }) + } + const adapter = { + start: vi.fn(async () => { + if (stage === 'adapter-start') { + entered.resolve() + await finish.promise + } + }), + stop: vi.fn(async () => undefined) + } + if (stage === 'process-start') + vi.mocked(port.start).mockImplementation(async (name) => { + active.add(name) + entered.resolve() + await finish.promise + running.add(name) + }) + supervisor.attachProcessPort(port) + supervisor.attachSafetyStore(safety.store) + register(supervisor, 'fixture', 'eager', adapter, guard) + let current = true + const pending = supervisor.restartIfRunning('fixture', 'authentication', () => { + if (!current) throw new Error('authentication interrupted') + }) + const rejected = expect(pending).rejects.toThrow('authentication interrupted') + await entered.promise + current = false + finish.resolve() + await rejected + expect(running.size).toBe(0) + expect(active.size).toBe(0) + expect(safety.values.size).toBe(0) + if (stage !== 'process-start') expect(port.start).not.toHaveBeenCalled() + } + ) + + it('does not restart a plugin when authentication is canceled during its stop', async () => { + const supervisor = new PluginRuntimeSupervisor() + const { port } = createProcessPort() + supervisor.attachProcessPort(port) + register(supervisor, 'fixture') + await supervisor.ensureRunning('fixture', 'external') + const entered = deferred() + const finish = deferred() + vi.mocked(port.stop).mockImplementationOnce(async () => { + entered.resolve() + await finish.promise + }) + let current = true + const pending = supervisor.restartIfRunning('fixture', 'authentication', () => { + if (!current) throw new Error('authentication interrupted') + }) + const rejected = expect(pending).rejects.toThrow('authentication interrupted') + await entered.promise + current = false + finish.resolve() + await rejected + expect(port.start).toHaveBeenCalledOnce() + }) + + it('rejects a superseded stop waiting for another plugin start without stopping its process', async () => { + const supervisor = new PluginRuntimeSupervisor() + const { port, running, active } = createProcessPort() + const entered = deferred() + const finish = deferred() + vi.mocked(port.start).mockImplementation(async (name) => { + active.add(name) + entered.resolve() + await finish.promise + running.add(name) + }) + supervisor.attachProcessPort(port) + register(supervisor, 'fixture') + const started = supervisor.ensureRunning('fixture', 'external') + await entered.promise + let current = true + const stopped = supervisor.requestExternalStop('fixture', () => { + if (!current) throw new Error('authentication interrupted') + }) + const rejected = expect(stopped).rejects.toThrow('authentication interrupted') + current = false + finish.resolve() + await started + await rejected + expect(running.has('fixture')).toBe(true) + expect(port.stop).not.toHaveBeenCalled() + await supervisor.requestExternalStop('fixture') + }) }) diff --git a/test/main/plugin/toolPolicyStore.test.ts b/test/main/plugin/toolPolicyStore.test.ts new file mode 100644 index 0000000000..d214009757 --- /dev/null +++ b/test/main/plugin/toolPolicyStore.test.ts @@ -0,0 +1,63 @@ +import { describe, expect, it } from 'vitest' +import { PluginToolPolicyStore, type StoredToolPolicy } from '@/plugin/toolPolicyStore' +import { createPluginToolPolicies } from '../../helpers/pluginToolPolicies' + +const policy: StoredToolPolicy = { + pluginId: 'fixture-owner', + serverId: 'fixture-server', + tools: { allowed: 'allow', prompted: 'ask', blocked: 'deny' }, + enabled: true +} + +describe('PluginToolPolicyStore', () => { + it('preserves decisions and closes undeclared or ambiguous owned tools', () => { + const owner = createPluginToolPolicies() + expect(owner.resolve('fixture-server', 'allowed')).toEqual({ managed: false, decision: null }) + owner.register(policy) + for (const [tool, decision] of Object.entries(policy.tools)) + expect(owner.resolve(policy.serverId, tool)).toEqual({ managed: true, decision }) + expect(owner.resolve(policy.serverId, 'undeclared')).toEqual({ managed: true, decision: null }) + expect(owner.deniedTools(policy.serverId)).toEqual(['blocked']) + owner.register({ ...policy, pluginId: 'another-owner' }) + expect(owner.resolve(policy.serverId, 'allowed')).toEqual({ managed: true, decision: null }) + expect(owner.deniedTools(policy.serverId)).toEqual([]) + owner.register({ ...policy, pluginId: 'another-owner', enabled: false }) + expect(owner.resolve(policy.serverId, 'allowed').decision).toBe('allow') + }) + + it('replaces one owner/server pair and removes only the requested owner', () => { + const owner = createPluginToolPolicies() + owner.register(policy) + owner.register({ ...policy, serverId: 'another-server' }) + owner.register({ ...policy, pluginId: 'another-owner', serverId: 'unrelated-server' }) + owner.register({ ...policy, tools: { allowed: 'deny' } }) + expect(owner.resolve(policy.serverId, 'allowed').decision).toBe('deny') + owner.unregister(policy.pluginId) + expect(owner.resolve(policy.serverId, 'allowed').managed).toBe(false) + expect(owner.resolve('another-server', 'allowed').managed).toBe(false) + expect(owner.resolve('unrelated-server', 'allowed').decision).toBe('allow') + }) + + it('keeps disabled policies unmanaged and isolates separate profile owners', () => { + const owner = createPluginToolPolicies() + owner.register({ ...policy, enabled: false }) + expect(owner.resolve(policy.serverId, 'blocked')).toEqual({ managed: false, decision: null }) + owner.register(policy) + expect(createPluginToolPolicies().resolve(policy.serverId, 'blocked').managed).toBe(false) + }) + + it('does not acknowledge failed persistence or retain an independent decision cache', () => { + let policies = [policy] + const owner = new PluginToolPolicyStore({ + get: () => structuredClone(policies), + set: () => { + throw new Error('Policy persistence failed') + } + }) + expect(() => owner.register({ ...policy, tools: { allowed: 'deny' } })).toThrow('persistence') + expect(() => owner.unregister(policy.pluginId)).toThrow('persistence') + expect(owner.resolve(policy.serverId, 'allowed').decision).toBe('allow') + policies = [{ ...policy, tools: { allowed: 'ask' } }] + expect(owner.resolve(policy.serverId, 'allowed').decision).toBe('ask') + }) +}) diff --git a/test/main/plugin/userPluginHookLifecycle.test.ts b/test/main/plugin/userPluginHookLifecycle.test.ts new file mode 100644 index 0000000000..34c80aadda --- /dev/null +++ b/test/main/plugin/userPluginHookLifecycle.test.ts @@ -0,0 +1,84 @@ +import { EventEmitter } from 'node:events' +import { PassThrough } from 'node:stream' +import { spawn } from 'node:child_process' +import { afterEach, expect, it, vi } from 'vitest' +import { terminateProcessTree } from '@/agent/shared/process/processTree' +import { UserPluginHooks } from '@/plugin/userPluginHooks' +import type { DeepChatTapeEntryRow, TapeAnchorAppendInput } from '@/tape/domain/entry' + +vi.mock('node:child_process', () => ({ spawn: vi.fn() })) +vi.mock('@/agent/shared/process/processTree', () => ({ terminateProcessTree: vi.fn() })) + +afterEach(() => vi.restoreAllMocks()) + +it.each([false, true])( + 'keeps Tape owned until actual child closure when termination rejects=%s', + async (terminationFails) => { + const child = Object.assign(new EventEmitter(), { + stdin: new PassThrough(), + stdout: new PassThrough(), + stderr: new PassThrough() + }) + vi.mocked(spawn).mockReturnValue(child as unknown as ReturnType) + if (terminationFails) + vi.mocked(terminateProcessTree).mockRejectedValue(new Error('Kill failed')) + else vi.mocked(terminateProcessTree).mockResolvedValue(true) + const rows: DeepChatTapeEntryRow[] = [] + const host = new UserPluginHooks({ + getTapeIncarnationId: () => 'incarnation', + getBySession: () => rows, + appendAnchor: (input: TapeAnchorAppendInput) => { + const row: DeepChatTapeEntryRow = { + session_id: input.sessionId, + entry_id: rows.length + 1, + kind: 'anchor', + name: input.name, + source_type: 'runtime_event', + source_id: input.source?.id ?? null, + source_seq: input.source?.seq ?? null, + provenance_key: input.provenanceKey ?? null, + payload_json: JSON.stringify({ state: input.state }), + meta_json: '{}', + created_at: 1 + } + rows.push(row) + return row + } + }) + host.register({ + pluginId: 'user.closure', + digest: 'a'.repeat(64), + root: '/plugin', + data: '/plugin/data', + hooks: [{ id: 'input', event: 'UserPromptSubmit', command: 'fixture', timeout: 5 }] + }) + const accepted = host.accept({ + sessionId: 's', + messageId: 'm', + prompt: 'test', + model: 'test', + cwd: null + }) + await vi.waitFor(() => expect(spawn).toHaveBeenCalled()) + let released = false + const stopping = host.stop().then(() => { + released = true + }) + try { + await vi.waitFor(() => expect(terminateProcessTree).toHaveBeenCalled()) + await Promise.resolve() + expect(released).toBe(false) + expect(() => host.start()).toThrow('must drain') + child.emit('close', null, 'SIGKILL') + await Promise.all([accepted, stopping]) + expect(host.diagnostics('user.closure')).toMatchObject([{ status: 'failed' }]) + expect(() => host.beginRun('s')).toThrow('stopped') + host.start() + expect(() => host.beginRun('s')).not.toThrow() + host.endRun('s') + } finally { + child.emit('close', null, 'SIGKILL') + await host.stop() + } + } +) diff --git a/test/main/plugin/userPluginLifecycle.test.ts b/test/main/plugin/userPluginLifecycle.test.ts index 587248ee65..4834f27c6d 100644 --- a/test/main/plugin/userPluginLifecycle.test.ts +++ b/test/main/plugin/userPluginLifecycle.test.ts @@ -6,12 +6,12 @@ import { unzipSync, zipSync } from 'fflate' import { afterEach, expect, it, vi } from 'vitest' import { UserPlugins, type UserPluginRecord, type UserPluginStore } from '@/plugin/userPlugins' import { UserPluginHooks } from '@/plugin/userPluginHooks' -import { McpSettings } from '@/mcp/settings' +import { McpSettings, createMcpSettingsDefaults } from '@/mcp/settings' import { mcpVariableBindingScope } from '@/mcp/environmentBindings' import { SecretStore } from '@/config/secretStore' -import { safeStorage } from 'electron' import type { TapeAnchorAppendInput, DeepChatTapeEntryRow } from '@/tape/domain/entry' import { USER_PLUGIN_INSTALL_DIRECTORY } from '@shared/pluginPaths' +import { createMemoryStore } from '../../helpers/memoryStore' vi.unmock('fs') vi.unmock('node:fs') @@ -90,13 +90,19 @@ function fixture() { appendAnchor: (input: TapeAnchorAppendInput) => DeepChatTapeEntryRow }) const wrappedSecrets = new Map() - vi.spyOn(safeStorage, 'isEncryptionAvailable').mockReturnValue(true) - const secrets = new SecretStore({ - get: (key: string) => wrappedSecrets.get(key), - set: (key: string, value: string) => wrappedSecrets.set(key, value), - delete: (key: string) => wrappedSecrets.delete(key) - } as never) - const mcpSettings = new McpSettings(secrets) + const secrets = new SecretStore( + { + get: (key: string) => wrappedSecrets.get(key), + set: (key: string, value: string) => wrappedSecrets.set(key, value), + delete: (key: string) => wrappedSecrets.delete(key) + } as never, + { + isAvailable: async () => true, + encrypt: async (value) => Buffer.from(value).toString('base64'), + decrypt: async (wrapped) => Buffer.from(wrapped, 'base64').toString() + } + ) + const mcpSettings = new McpSettings(createMemoryStore(createMcpSettingsDefaults()), secrets) const skills = new Map() const supervisor = { registerServer: vi.fn(), @@ -213,12 +219,12 @@ it.each([ ok: false, error: 'Invalid MCP variable binding' }) - expect(f.mcpSettings.getMcpVariableBindings(first)).toEqual({}) + expect(await f.mcpSettings.getMcpVariableBindings(first)).toEqual({}) // The fixture mocks safeStorage and connections; this checks storage boundaries, not OS encryption. const token = randomUUID() expect((await f.service.configureMcp(id, key, { [variable]: token })).ok).toBe(true) - expect(f.mcpSettings.getMcpVariableBindings(first)).toEqual({ [variable]: token }) + expect(await f.mcpSettings.getMcpVariableBindings(first)).toEqual({ [variable]: token }) expect(JSON.stringify(f.store.read())).not.toContain(token) expect(JSON.stringify(await f.service.get(id))).not.toContain(token) expect(JSON.stringify(await f.mcpSettings.getMcpServers())).not.toContain(token) @@ -241,7 +247,7 @@ it.each([ const current = (await f.mcpSettings.getMcpServers())[key] expect(current.serverId).toBe(first.serverId) expect(current.customHeaders).toEqual({ Authorization: 'Bearer ${' + variable + '}' }) - expect(f.mcpSettings.getMcpVariableBindings(current)).toEqual({ [variable]: token }) + expect(await f.mcpSettings.getMcpVariableBindings(current)).toEqual({ [variable]: token }) const secretKey = `mcpVariableBindings.${current.serverId}` const savedSecret = f.wrappedSecrets.get(secretKey) @@ -274,10 +280,10 @@ it.each([ expect(mcpVariableBindingScope({ ...updated, env: current.env })).toBe( mcpVariableBindingScope(current) ) - expect(f.mcpSettings.getMcpVariableBindings(updated)).toEqual({}) + expect(await f.mcpSettings.getMcpVariableBindings(updated)).toEqual({}) expect((await updatedService.get(id)).userPlugin?.setup[key]).toEqual([variable]) expect((await updatedService.configureMcp(id, key, { [variable]: token })).ok).toBe(true) - expect(f.mcpSettings.getMcpVariableBindings(updated)).toEqual({ [variable]: token }) + expect(await f.mcpSettings.getMcpVariableBindings(updated)).toEqual({ [variable]: token }) expect((await updatedService.get(id)).userPlugin?.setup[key]).toEqual([]) expect((await updatedService.uninstall(id)).ok).toBe(true) expect(await f.mcpSettings.getMcpServers()).toEqual(unrelated) @@ -321,7 +327,7 @@ it('installs disabled, preserves host MCP identities across enable cycles, and r const current = (await f.mcpSettings.getMcpServers())[key] expect(current.serverId).toBe(first.serverId) expect(current.customHeaders).toEqual({ Authorization: 'Bearer ${EXAMPLE_PLUGIN_TOKEN}' }) - expect(f.mcpSettings.getMcpVariableBindings(current)).toEqual({ + expect(await f.mcpSettings.getMcpVariableBindings(current)).toEqual({ EXAMPLE_PLUGIN_TOKEN: 'private-token' }) expect(JSON.stringify(f.mcpSettings.getMigrationSnapshot())).not.toContain('private-token') @@ -329,7 +335,7 @@ it('installs disabled, preserves host MCP identities across enable cycles, and r expect( (await f.service.configureMcp(id, key, { EXAMPLE_PLUGIN_TOKEN: 'rotated-token' })).ok ).toBe(true) - expect(f.mcpSettings.getMcpVariableBindings(current)).toEqual({ + expect(await f.mcpSettings.getMcpVariableBindings(current)).toEqual({ EXAMPLE_PLUGIN_TOKEN: 'rotated-token' }) const unrelated = Object.entries(await f.mcpSettings.getMcpServers()).find( @@ -382,7 +388,9 @@ it('blocks active-turn updates and rolls failed publication back with the previo customHeaders: { Authorization: 'Bearer ${EXAMPLE_PLUGIN_TOKEN}' } }) expect( - f.mcpSettings.getMcpVariableBindings((await f.mcpSettings.getMcpServers())[`${id}.remote`]) + await f.mcpSettings.getMcpVariableBindings( + (await f.mcpSettings.getMcpServers())[`${id}.remote`] + ) ).toEqual({ EXAMPLE_PLUGIN_TOKEN: 'kept-secret' }) expect(f.store.pending()).toBeNull() const success = await f.service.install({ @@ -422,7 +430,9 @@ it('restores an interrupted update and lets a concurrent disable cancel publicat customHeaders: { Authorization: 'Bearer ${EXAMPLE_PLUGIN_TOKEN}' } }) expect( - f.mcpSettings.getMcpVariableBindings((await f.mcpSettings.getMcpServers())[`${id}.remote`]) + await f.mcpSettings.getMcpVariableBindings( + (await f.mcpSettings.getMcpServers())[`${id}.remote`] + ) ).toEqual({ EXAMPLE_PLUGIN_TOKEN: 'recovered-secret' }) f.manifest('https://new.example/mcp') const update = await recovered.inspect({ kind: 'directory', path: f.source }, randomUUID()) @@ -488,7 +498,7 @@ it.each(['publication', 'cleanup', 'rollback cleanup'])( expect(f.skills.size).toBe(0) const restored = (await f.mcpSettings.getMcpServers())[`${id}.remote`] expect(restored).toMatchObject({ enabled: false, baseUrl: 'https://old.example/mcp' }) - expect(f.mcpSettings.getMcpVariableBindings(restored)).toEqual({ + expect(await f.mcpSettings.getMcpVariableBindings(restored)).toEqual({ EXAMPLE_PLUGIN_TOKEN: 'rollback-secret' }) failing.mockReset() @@ -559,7 +569,7 @@ it('installs Sem disabled and retains its explicit repository binding through en env: { SEM_REPO: '${SEM_REPO}', SEM_NO_NETWORK: '1', SEM_NO_TELEMETRY: '1' }, environmentVariables: ['SEM_REPO'] }) - expect(f.mcpSettings.getMcpVariableBindings(config)).toEqual({ SEM_REPO: repository }) + expect(await f.mcpSettings.getMcpVariableBindings(config)).toEqual({ SEM_REPO: repository }) expect((await f.service.disable(id)).ok).toBe(true) expect((await f.mcpSettings.getMcpServers())[key].enabled).toBe(false) expect((await f.service.uninstall(id)).ok).toBe(true) @@ -622,7 +632,7 @@ it('installs the anonymous Parallel ZIP without changing existing MCP settings', }, environmentVariables: [] }) - expect(f.mcpSettings.getMcpVariableBindings(enabled[key])).toEqual({}) + expect(await f.mcpSettings.getMcpVariableBindings(enabled[key])).toEqual({}) for (const [name, config] of Object.entries(existing)) expect(enabled[name]).toEqual(config) expect((await f.service.disable(id)).ok).toBe(true) expect((await f.mcpSettings.getMcpServers())[key].enabled).toBe(false) diff --git a/test/main/plugin/userPlugins.test.ts b/test/main/plugin/userPlugins.test.ts index 5011d43d5f..4ac24cfb8a 100644 --- a/test/main/plugin/userPlugins.test.ts +++ b/test/main/plugin/userPlugins.test.ts @@ -219,6 +219,65 @@ describe('portable plugin package contract', () => { }) describe('reviewed context hook execution', () => { + it('keeps Tape owned after a drain deadline and fences queued work before resuming', async () => { + const root = directory() + write( + root, + 'hook.cjs', + `console.log(JSON.stringify({hookSpecificOutput:{hookEventName:'UserPromptSubmit',additionalContext:'ready'}}))` + ) + const history = tape() + const host = new UserPluginHooks(history) + let release!: () => void + let entered!: () => void + const gate = new Promise((resolve) => { + release = resolve + }) + const admitted = new Promise((resolve) => { + entered = resolve + }) + host.register({ + pluginId: 'user.drain', + digest: 'a'.repeat(64), + root, + data: root, + hooks: [commandHook('UserPromptSubmit')], + verify: async () => { + entered() + await gate + } + }) + const input = { sessionId: 's', messageId: 'first', prompt: 'First', model: 'test', cwd: root } + const active = host.accept(input) + await admitted + const queued = host.accept({ ...input, messageId: 'queued' }) + vi.useFakeTimers() + const stopping = host.stop() + const timedOut = expect(stopping).rejects.toThrow('did not drain') + try { + expect(() => host.start()).toThrow('must drain') + expect(() => host.beginRun('s')).toThrow('stopped') + await expect(host.accept(input)).rejects.toThrow('stopped') + await expect(host.retry('user.drain', 'unknown')).rejects.toThrow('stopped') + await vi.advanceTimersByTimeAsync(30_000) + await timedOut + expect(() => host.start()).toThrow('must drain') + release() + await Promise.all([active, queued]) + vi.useRealTimers() + expect(host.diagnostics('user.drain').map((entry) => entry.status)).toEqual(['failed']) + const rows = history.rows.length + host.start() + await host.accept({ ...input, messageId: 'resumed' }) + expect(history.rows.length).toBeGreaterThan(rows) + expect(host.getContext('s', 'resumed').map((entry) => entry.content)).toEqual(['ready']) + } finally { + release() + vi.useRealTimers() + await host.stop() + } + }) + it('reuses only the matching prompt and restarts hooks after the Tape is cleared', async () => { const root = fixture() write( diff --git a/test/main/project/projectService.test.ts b/test/main/project/projectService.test.ts index 5d4e1fd8c4..8bc7a580cc 100644 --- a/test/main/project/projectService.test.ts +++ b/test/main/project/projectService.test.ts @@ -1,33 +1,9 @@ import { beforeEach, describe, expect, it, vi } from 'vitest' -const { getPathMock, openPathMock, existsSyncMock, mkdirSyncMock, statSyncMock } = vi.hoisted( - () => ({ - getPathMock: vi.fn((name: string) => { - if (name === 'temp') { - return '/system/temp' - } - if (name === 'userData') { - return '/mock/userData' - } - if (name === 'appData') { - return '/mock/appData' - } - return `/mock/${name}` - }), - openPathMock: vi.fn(), - existsSyncMock: vi.fn(), - mkdirSyncMock: vi.fn(), - statSyncMock: vi.fn() - }) -) - -vi.mock('electron', () => ({ - app: { - getPath: getPathMock - }, - shell: { - openPath: openPathMock - } +const { existsSyncMock, mkdirSyncMock, statSyncMock } = vi.hoisted(() => ({ + existsSyncMock: vi.fn(), + mkdirSyncMock: vi.fn(), + statSyncMock: vi.fn() })) vi.mock('fs', () => ({ @@ -38,7 +14,7 @@ vi.mock('fs', () => ({ } })) -import { ProjectService } from '@/project' +import { ProjectService, type ProjectHost } from '@/project' function createMockSqlitePresenter() { return { @@ -70,10 +46,16 @@ function createMockSqlitePresenter() { } as any } -function createMockDeviceService() { +function createMockHost() { return { - selectDirectory: vi.fn().mockResolvedValue({ canceled: true, filePaths: [] }) - } as any + tempDirectory: '/system/temp', + userDataDirectory: '/mock/userData', + appDataDirectory: '/mock/appData', + documentsDirectory: '/mock/documents', + homeDirectory: '/mock/home', + selectDirectory: vi.fn().mockResolvedValue({ canceled: true, filePaths: [] }), + openPath: vi.fn().mockResolvedValue('') + } satisfies ProjectHost } function createMockSettingsStore(defaultProjectPath: string | null = null) { @@ -86,7 +68,7 @@ function createMockSettingsStore(defaultProjectPath: string | null = null) { describe('ProjectService', () => { let sqlitePresenter: ReturnType - let deviceService: ReturnType + let host: ReturnType let presenter: ProjectService beforeEach(() => { @@ -95,16 +77,66 @@ describe('ProjectService', () => { mkdirSyncMock.mockReturnValue(undefined) statSyncMock.mockReturnValue({ isDirectory: () => true }) sqlitePresenter = createMockSqlitePresenter() - deviceService = createMockDeviceService() + host = createMockHost() presenter = new ProjectService( sqlitePresenter, sqlitePresenter, - deviceService, + host, createMockSettingsStore(), vi.fn() ) }) + it.each([ + 'tempDirectory', + 'userDataDirectory', + 'appDataDirectory', + 'documentsDirectory', + 'homeDirectory' + ] as const)( + 'rejects a relative %s instead of resolving it against the working directory', + (key) => { + expect( + () => + new ProjectService( + sqlitePresenter, + sqlitePresenter, + { ...host, [key]: 'relative-directory' }, + createMockSettingsStore(), + vi.fn() + ) + ).toThrow('Project host directories must be absolute') + expect(mkdirSyncMock).not.toHaveBeenCalled() + } + ) + + it('uses the profile workspace when Documents and Home are unavailable', async () => { + const service = new ProjectService( + sqlitePresenter, + sqlitePresenter, + { ...host, documentsDirectory: null, homeDirectory: null }, + createMockSettingsStore(), + vi.fn() + ) + + await expect(service.ensureDefaultWorkspace()).resolves.toBe( + '/mock/userData/workspaces/DeepChat' + ) + expect(mkdirSyncMock).toHaveBeenCalledExactlyOnceWith('/mock/userData/workspaces/DeepChat', { + recursive: true + }) + }) + + it('keeps captured workspace roots stable across snapshots', async () => { + host.documentsDirectory = '/changed/documents' + host.homeDirectory = '/changed/home' + host.userDataDirectory = '/changed/userData' + + const snapshot = await presenter.getSnapshot() + expect(snapshot.defaultChatWorkspacePath).toBe('/mock/documents/DeepChat') + expect(presenter.ensureChatWorkspace().path).toBe('/mock/documents/DeepChat') + }) + it('lazily prepares Chats without changing a custom default workspace', () => { const directories = new Set() statSyncMock.mockImplementation((path) => { @@ -117,7 +149,7 @@ describe('ProjectService', () => { const service = new ProjectService( sqlitePresenter, sqlitePresenter, - deviceService, + host, settings, vi.fn(), publish @@ -154,13 +186,7 @@ describe('ProjectService', () => { }) mkdirSyncMock.mockImplementation((path) => directories.add(path)) const settings = createMockSettingsStore(configuredPath) - const service = new ProjectService( - sqlitePresenter, - sqlitePresenter, - deviceService, - settings, - vi.fn() - ) + const service = new ProjectService(sqlitePresenter, sqlitePresenter, host, settings, vi.fn()) expect(service.getDefaultChatWorkspacePath()).toBeNull() expect(service.ensureChatWorkspace().path).toBe(configuredPath) @@ -186,7 +212,7 @@ describe('ProjectService', () => { const service = new ProjectService( sqlitePresenter, sqlitePresenter, - deviceService, + host, settings, publishDefault ) @@ -226,13 +252,7 @@ describe('ProjectService', () => { updated_at: 40 } ]) - presenter = new ProjectService( - sqlitePresenter, - sqlitePresenter, - deviceService, - settingsStore, - vi.fn() - ) + presenter = new ProjectService(sqlitePresenter, sqlitePresenter, host, settingsStore, vi.fn()) const initial = await presenter.getSnapshot() await presenter.restoreEnvironment('/work/archived') @@ -254,7 +274,7 @@ describe('ProjectService', () => { const first = new ProjectService( sqlitePresenter, sqlitePresenter, - deviceService, + host, settingsStore, vi.fn() ) @@ -265,7 +285,7 @@ describe('ProjectService', () => { const reconstructed = new ProjectService( sqlitePresenter, sqlitePresenter, - deviceService, + host, settingsStore, vi.fn() ) @@ -281,7 +301,7 @@ describe('ProjectService', () => { const reconstructed = new ProjectService( sqlitePresenter, sqlitePresenter, - deviceService, + host, settingsStore, vi.fn() ) @@ -294,7 +314,7 @@ describe('ProjectService', () => { presenter = new ProjectService( sqlitePresenter, sqlitePresenter, - deviceService, + host, createMockSettingsStore(), publishDefaultProjectPathChanged ) @@ -310,7 +330,7 @@ describe('ProjectService', () => { presenter = new ProjectService( sqlitePresenter, sqlitePresenter, - deviceService, + host, createMockSettingsStore(), vi.fn(), publishEnvironmentsChanged @@ -327,13 +347,7 @@ describe('ProjectService', () => { describe('ensureDefaultWorkspace', () => { it('creates and registers the Documents default workspace for first-run users', async () => { const settingsStore = createMockSettingsStore() - presenter = new ProjectService( - sqlitePresenter, - sqlitePresenter, - deviceService, - settingsStore, - vi.fn() - ) + presenter = new ProjectService(sqlitePresenter, sqlitePresenter, host, settingsStore, vi.fn()) await expect(presenter.ensureDefaultWorkspace()).resolves.toBe('/mock/documents/DeepChat') @@ -353,13 +367,7 @@ describe('ProjectService', () => { it('recreates and registers the built-in workspace when it is already the default', async () => { const settingsStore = createMockSettingsStore('/mock/documents/DeepChat') - presenter = new ProjectService( - sqlitePresenter, - sqlitePresenter, - deviceService, - settingsStore, - vi.fn() - ) + presenter = new ProjectService(sqlitePresenter, sqlitePresenter, host, settingsStore, vi.fn()) await expect(presenter.ensureDefaultWorkspace()).resolves.toBe('/mock/documents/DeepChat') @@ -376,13 +384,7 @@ describe('ProjectService', () => { it('does not migrate users with a custom default project path', async () => { const settingsStore = createMockSettingsStore('/work/custom') - presenter = new ProjectService( - sqlitePresenter, - sqlitePresenter, - deviceService, - settingsStore, - vi.fn() - ) + presenter = new ProjectService(sqlitePresenter, sqlitePresenter, host, settingsStore, vi.fn()) await expect(presenter.ensureDefaultWorkspace()).resolves.toBeNull() @@ -395,13 +397,7 @@ describe('ProjectService', () => { sqlitePresenter.newProjectsTable.getAll.mockReturnValue([ { path: '/work/app', name: 'app', icon: null, last_accessed_at: 1000 } ]) - presenter = new ProjectService( - sqlitePresenter, - sqlitePresenter, - deviceService, - settingsStore, - vi.fn() - ) + presenter = new ProjectService(sqlitePresenter, sqlitePresenter, host, settingsStore, vi.fn()) await expect(presenter.ensureDefaultWorkspace()).resolves.toBeNull() @@ -421,13 +417,7 @@ describe('ProjectService', () => { updated_at: 1000 } ]) - presenter = new ProjectService( - sqlitePresenter, - sqlitePresenter, - deviceService, - settingsStore, - vi.fn() - ) + presenter = new ProjectService(sqlitePresenter, sqlitePresenter, host, settingsStore, vi.fn()) await expect(presenter.ensureDefaultWorkspace()).resolves.toBeNull() @@ -443,13 +433,7 @@ describe('ProjectService', () => { throw new Error('documents denied') } }) - presenter = new ProjectService( - sqlitePresenter, - sqlitePresenter, - deviceService, - settingsStore, - vi.fn() - ) + presenter = new ProjectService(sqlitePresenter, sqlitePresenter, host, settingsStore, vi.fn()) try { await expect(presenter.ensureDefaultWorkspace()).resolves.toBe('/mock/home/DeepChat') @@ -783,15 +767,15 @@ describe('ProjectService', () => { describe('openDirectory', () => { it('opens the directory with the system shell', async () => { - openPathMock.mockResolvedValue('') + host.openPath.mockResolvedValue('') await presenter.openDirectory('/work/hello-world') - expect(openPathMock).toHaveBeenCalledWith('/work/hello-world') + expect(host.openPath).toHaveBeenCalledWith('/work/hello-world') }) it('throws when the shell reports an error', async () => { - openPathMock.mockResolvedValue('failed to open') + host.openPath.mockResolvedValue('failed to open') await expect(presenter.openDirectory('/work/hello-world')).rejects.toThrow('failed to open') }) @@ -809,8 +793,18 @@ describe('ProjectService', () => { }) describe('selectDirectory', () => { + it('does not mutate projects or the version when native selection fails', async () => { + host.selectDirectory.mockRejectedValue(new Error('native dialog unavailable')) + + await expect(presenter.selectDirectory()).rejects.toThrow('native dialog unavailable') + + expect(sqlitePresenter.getDatabase).not.toHaveBeenCalled() + expect(sqlitePresenter.newProjectsTable.upsert).not.toHaveBeenCalled() + expect(presenter.getSnapshotVersion()).toBe(0) + }) + it('returns null when user cancels', async () => { - deviceService.selectDirectory.mockResolvedValue({ canceled: true, filePaths: [] }) + host.selectDirectory.mockResolvedValue({ canceled: true, filePaths: [] }) const result = await presenter.selectDirectory() @@ -818,7 +812,7 @@ describe('ProjectService', () => { }) it('returns null when no path selected', async () => { - deviceService.selectDirectory.mockResolvedValue({ canceled: false, filePaths: [] }) + host.selectDirectory.mockResolvedValue({ canceled: false, filePaths: [] }) const result = await presenter.selectDirectory() @@ -826,7 +820,7 @@ describe('ProjectService', () => { }) it('registers a new directory at the top of the active order', async () => { - deviceService.selectDirectory.mockResolvedValue({ + host.selectDirectory.mockResolvedValue({ canceled: false, filePaths: ['/Users/test/my-project'] }) @@ -846,7 +840,7 @@ describe('ProjectService', () => { }) it('keeps the persisted order when selecting an already active directory', async () => { - deviceService.selectDirectory.mockResolvedValue({ + host.selectDirectory.mockResolvedValue({ canceled: false, filePaths: ['/work/existing'] }) @@ -859,7 +853,7 @@ describe('ProjectService', () => { }) it('reactivates an archived directory at the top of the active order', async () => { - deviceService.selectDirectory.mockResolvedValue({ + host.selectDirectory.mockResolvedValue({ canceled: false, filePaths: ['/work/archived'] }) @@ -872,7 +866,7 @@ describe('ProjectService', () => { }) it('keeps concurrent selections on distinct snapshot versions', async () => { - deviceService.selectDirectory + host.selectDirectory .mockResolvedValueOnce({ canceled: false, filePaths: ['/work/first'] }) .mockResolvedValueOnce({ canceled: false, filePaths: ['/work/second'] }) diff --git a/test/main/provider/aiSdkProviderFactory.test.ts b/test/main/provider/aiSdkProviderFactory.test.ts index 9459188f6c..a43decc3a0 100644 --- a/test/main/provider/aiSdkProviderFactory.test.ts +++ b/test/main/provider/aiSdkProviderFactory.test.ts @@ -2,6 +2,7 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' import { generateImage, generateText, streamText } from 'ai' const codexAuthState = vi.hoisted(() => ({ + captureOperation: () => () => undefined, getBackendAuth: vi.fn(), forceRefreshBackendAuth: vi.fn() })) diff --git a/test/main/provider/aihubmixProvider.test.ts b/test/main/provider/aihubmixProvider.test.ts index 51555881c7..12230b1345 100644 --- a/test/main/provider/aihubmixProvider.test.ts +++ b/test/main/provider/aihubmixProvider.test.ts @@ -1,3 +1,4 @@ +import { providerTestEnvironment } from '../../helpers/providerEnvironment' import type { ProviderSettingsPort } from '@/provider/settings' import { beforeEach, describe, expect, it, vi } from 'vitest' import type { LLM_PROVIDER, ModelConfig } from '@shared/types/provider' @@ -62,7 +63,11 @@ describe('AihubmixProvider AI SDK runtime headers', () => { }) it('preserves the DeepChat APP-Code header in AI SDK mode', async () => { - const provider = new AiSdkProvider(createProvider(), createProviderSettings()) + const provider = new AiSdkProvider( + createProvider(), + createProviderSettings(), + providerTestEnvironment + ) ;(provider as any).isInitialized = true for await (const _event of provider.coreStream( @@ -92,7 +97,11 @@ describe('AihubmixProvider AI SDK runtime headers', () => { }) it('treats Seedance models as video generation even when metadata is still chat', async () => { - const provider = new AiSdkProvider(createProvider(), createProviderSettings()) + const provider = new AiSdkProvider( + createProvider(), + createProviderSettings(), + providerTestEnvironment + ) ;(provider as any).isInitialized = true const modelConfig = { diff --git a/test/main/provider/anthropicProvider.test.ts b/test/main/provider/anthropicProvider.test.ts index 9d0caaef68..e689af13ff 100644 --- a/test/main/provider/anthropicProvider.test.ts +++ b/test/main/provider/anthropicProvider.test.ts @@ -1,3 +1,4 @@ +import { providerTestEnvironment } from '../../helpers/providerEnvironment' import type { ProviderSettingsPort } from '@/provider/settings' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' import type { LLM_PROVIDER } from '@shared/types/provider' @@ -98,7 +99,8 @@ describe('AiSdkProvider anthropic', () => { createProvider({ apiKey: '' }), - createProviderSettings() + createProviderSettings(), + providerTestEnvironment ) await expect(provider.check()).resolves.toEqual({ @@ -114,7 +116,8 @@ describe('AiSdkProvider anthropic', () => { createProvider({ apiKey: '' }), - createProviderSettings() + createProviderSettings(), + providerTestEnvironment ) ;(provider as any).isInitialized = true @@ -135,7 +138,11 @@ describe('AiSdkProvider anthropic', () => { }) it('passes system prompts through the AI SDK text path', async () => { - const provider = new AiSdkProvider(createProvider(), createProviderSettings()) + const provider = new AiSdkProvider( + createProvider(), + createProviderSettings(), + providerTestEnvironment + ) ;(provider as any).isInitialized = true const signal = new AbortController().signal @@ -161,7 +168,11 @@ describe('AiSdkProvider anthropic', () => { }) it('preserves the legacy positional system prompt argument', async () => { - const provider = new AiSdkProvider(createProvider(), createProviderSettings()) + const provider = new AiSdkProvider( + createProvider(), + createProviderSettings(), + providerTestEnvironment + ) ;(provider as any).isInitialized = true await provider.generateText('hi', 'claude-sonnet-4-5-20250929', 0.2, 32, 'Legacy system prompt') @@ -182,7 +193,11 @@ describe('AiSdkProvider anthropic', () => { }) it('reads model identities without duplicating catalog capability state', async () => { - const provider = new AiSdkProvider(createProvider(), createProviderSettings()) + const provider = new AiSdkProvider( + createProvider(), + createProviderSettings(), + providerTestEnvironment + ) const models = await (provider as any).fetchProviderModels() expect(models).toEqual([ @@ -211,7 +226,8 @@ describe('AiSdkProvider anthropic', () => { name: 'Custom Anthropic', custom: true }), - createProviderSettings() + createProviderSettings(), + providerTestEnvironment ) const models = await provider.fetchModels() @@ -251,7 +267,8 @@ describe('AiSdkProvider anthropic', () => { name: 'Custom Anthropic', custom: true }), - createProviderSettings() + createProviderSettings(), + providerTestEnvironment ) await expect(provider.fetchCatalog()).rejects.toThrow('Invalid API key') diff --git a/test/main/provider/apimartProvider.test.ts b/test/main/provider/apimartProvider.test.ts index 9fb6b31cf9..0a0c439dba 100644 --- a/test/main/provider/apimartProvider.test.ts +++ b/test/main/provider/apimartProvider.test.ts @@ -1,3 +1,4 @@ +import { providerTestEnvironment } from '../../helpers/providerEnvironment' import type { ProviderSettingsPort } from '@/provider/settings' import { ApiEndpointType, ModelType } from '@shared/model' import type { LLM_PROVIDER, ModelConfig } from '@shared/types/provider' @@ -93,6 +94,8 @@ const createProviderSettings = (): ProviderSettingsPort => const createProviderInstance = (providerSettings = createProviderSettings()) => new ApimartProvider(createProvider(), providerSettings, { + allowInsecureTls: providerTestEnvironment.allowInsecureTls, + defaultHeaders: providerTestEnvironment.defaultHeaders, getLanguage: vi.fn().mockReturnValue('en-US') }) @@ -149,7 +152,11 @@ describe('ApimartProvider', () => { const manager = new ProviderInstanceManager({ providerSettings: createProviderSettings(), - locale: { getLanguage: () => 'en-US' }, + locale: { + allowInsecureTls: providerTestEnvironment.allowInsecureTls, + defaultHeaders: providerTestEnvironment.defaultHeaders, + getLanguage: () => 'en-US' + }, agentSettings: { getAcpEnabled: vi.fn().mockReturnValue(false), getAcpAgents: vi.fn().mockReturnValue([]) diff --git a/test/main/provider/auth/openaiCodex.test.ts b/test/main/provider/auth/openaiCodex.test.ts index 788e019c35..b8aa678ead 100644 --- a/test/main/provider/auth/openaiCodex.test.ts +++ b/test/main/provider/auth/openaiCodex.test.ts @@ -6,11 +6,15 @@ import { OpenAICodexAuth } from '@/provider/auth/openaiCodex' import { OpenAICodexCredentialStore } from '@/provider/auth/openaiCodex/credentialStore' import { createOpenAICodexPkcePair } from '@/provider/auth/openaiCodex/pkce' import { extractProviderFailureMetadata } from '@/provider/providerFailure' +const crypto = { + isAvailable: async () => false, + encrypt: async (value: string) => Buffer.from(value).toString('base64'), + decrypt: async (wrapped: string) => Buffer.from(wrapped, 'base64').toString('utf8') +} const { startOAuthLoopbackCallbackSessionMock } = vi.hoisted(() => ({ startOAuthLoopbackCallbackSessionMock: vi.fn() })) - vi.mock('@/provider/auth/oauthLoopbackCallback', async (importOriginal) => { const actual = await importOriginal() return { @@ -18,14 +22,17 @@ vi.mock('@/provider/auth/oauthLoopbackCallback', async (importOriginal) => { startOAuthLoopbackCallbackSession: startOAuthLoopbackCallbackSessionMock } }) - describe('OpenAI Codex auth', () => { let tempDir: string let files: Map let fdPaths: Map - beforeEach(() => { files = new Map() + vi.mocked(fs.statSync).mockImplementation((file) => { + const raw = files.get(String(file)) + if (raw === undefined) throw Object.assign(new Error('ENOENT'), { code: 'ENOENT' }) + return { size: Buffer.byteLength(raw, 'utf8') } as fs.Stats + }) fdPaths = new Map() let nextFd = 1 tempDir = `/tmp/deepchat-codex-auth-${Date.now()}` @@ -72,7 +79,6 @@ describe('OpenAI Codex auth', () => { vi.mocked(shell.openExternal).mockClear() delete process.env.DEEPCHAT_OPENAI_CODEX_DISABLED }) - afterEach(() => { vi.useRealTimers() vi.unstubAllGlobals() @@ -80,36 +86,30 @@ describe('OpenAI Codex auth', () => { delete process.env.OPENAI_CODEX_REDIRECT_PORT delete process.env.OPENAI_CODEX_REDIRECT_URI }) - it('creates URL-safe PKCE verifier and challenge values', () => { const pair = createOpenAICodexPkcePair() - expect(pair.codeVerifier).toMatch(/^[A-Za-z0-9_-]+$/) expect(pair.codeChallenge).toMatch(/^[A-Za-z0-9_-]+$/) expect(pair.codeVerifier).not.toBe(pair.codeChallenge) }) - it('falls back when the redirect port env value is not a TCP port', async () => { vi.resetModules() process.env.OPENAI_CODEX_REDIRECT_PORT = '1455.5' const decimalPortConstants = await import('@/provider/auth/openaiCodex/constants') expect(decimalPortConstants.OPENAI_CODEX_REDIRECT_PORT).toBe(1455) - vi.resetModules() process.env.OPENAI_CODEX_REDIRECT_PORT = '70000' const outOfRangePortConstants = await import('@/provider/auth/openaiCodex/constants') expect(outOfRangePortConstants.OPENAI_CODEX_REDIRECT_PORT).toBe(1455) - vi.resetModules() process.env.OPENAI_CODEX_REDIRECT_PORT = '65535' const validPortConstants = await import('@/provider/auth/openaiCodex/constants') expect(validPortConstants.OPENAI_CODEX_REDIRECT_PORT).toBe(65535) }) - - it('stores Codex credentials outside provider records', () => { + it('stores Codex credentials outside provider records', async () => { const credentialPath = path.join(tempDir, 'credentials.json') - const store = new OpenAICodexCredentialStore(credentialPath) - store.save({ + const store = new OpenAICodexCredentialStore({ filePath: credentialPath, crypto }) + await store.save({ accessToken: 'access-token', refreshToken: 'refresh-token', tokenType: 'Bearer', @@ -118,7 +118,6 @@ describe('OpenAI Codex auth', () => { accountLabel: 'user@example.com', updatedAt: Date.now() }) - expect(fs.mkdirSync).toHaveBeenCalledWith(path.dirname(credentialPath), { recursive: true, mode: 0o700 @@ -128,14 +127,16 @@ describe('OpenAI Codex auth', () => { expect(fs.openSync).toHaveBeenCalledWith(temporaryPath, 'w', 0o600) expect(fs.fsyncSync).toHaveBeenCalled() expect(fs.renameSync).toHaveBeenCalledWith(temporaryPath, credentialPath) - expect(store.load()?.accessToken).toBe('access-token') - store.clear() - expect(store.load()).toBeNull() + expect((await store.load())?.accessToken).toBe('access-token') + await store.clear() + expect(await store.load()).toBeNull() }) - it('returns full backend auth while keeping status account IDs masked', async () => { - const store = new OpenAICodexCredentialStore(path.join(tempDir, 'credentials.json')) - store.save({ + const store = new OpenAICodexCredentialStore({ + filePath: path.join(tempDir, 'credentials.json'), + crypto + }) + await store.save({ accessToken: 'access-token', refreshToken: 'refresh-token', tokenType: 'Bearer', @@ -144,28 +145,27 @@ describe('OpenAI Codex auth', () => { accountLabel: 'user@example.com', updatedAt: Date.now() }) - - const auth = new OpenAICodexAuth(store, vi.fn()) + const auth = new OpenAICodexAuth(store, vi.fn(), shell.openExternal) const backendAuth = await auth.getBackendAuth() - const status = auth.getStatus() - + const status = await auth.getStatus() expect(backendAuth).toEqual({ accessToken: 'access-token', accountId: 'account-123456789' }) expect(status.accountId).toBe('acco...6789') }) - it('refreshes expired access tokens with single-flight coordination', async () => { - const store = new OpenAICodexCredentialStore(path.join(tempDir, 'credentials.json')) - store.save({ + const store = new OpenAICodexCredentialStore({ + filePath: path.join(tempDir, 'credentials.json'), + crypto + }) + await store.save({ accessToken: 'old-token', refreshToken: 'refresh-token', tokenType: 'Bearer', expiresAt: Date.now() - 1000, updatedAt: Date.now() }) - const fetchMock = vi.fn().mockResolvedValue( new Response( JSON.stringify({ @@ -183,22 +183,22 @@ describe('OpenAI Codex auth', () => { ) ) vi.stubGlobal('fetch', fetchMock) - - const auth = new OpenAICodexAuth(store, vi.fn()) + const auth = new OpenAICodexAuth(store, vi.fn(), shell.openExternal) const [first, second] = await Promise.all([auth.getAccessToken(), auth.getAccessToken()]) - expect(first).toBe('new-token') expect(second).toBe('new-token') expect(fetchMock).toHaveBeenCalledTimes(1) expect(fetchMock.mock.calls[0][1]?.signal).toBeInstanceOf(AbortSignal) - expect(store.load()?.refreshToken).toBe('new-refresh-token') + expect((await store.load())?.refreshToken).toBe('new-refresh-token') }) - it.each([1, 100])( 'requires sign-in after a rejected refresh token with %i message repeats', async (messageRepeats) => { - const store = new OpenAICodexCredentialStore(path.join(tempDir, 'credentials.json')) - store.save({ + const store = new OpenAICodexCredentialStore({ + filePath: path.join(tempDir, 'credentials.json'), + crypto + }) + await store.save({ accessToken: 'expired-token', refreshToken: 'rejected-refresh-token', tokenType: 'Bearer', @@ -217,20 +217,21 @@ describe('OpenAI Codex auth', () => { ) ) vi.stubGlobal('fetch', fetchMock) - const auth = new OpenAICodexAuth(store, vi.fn()) - + const auth = new OpenAICodexAuth(store, vi.fn(), shell.openExternal) await expect(auth.getBackendAuth()).rejects.toMatchObject({ message: 'OpenAI Codex sign-in has expired. Sign in again.', failure: { statusCode: 400, code: 'openai_codex_sign_in_required', retryable: false } }) expect(fetchMock).toHaveBeenCalledOnce() - expect(store.load()?.refreshToken).toBe('rejected-refresh-token') + expect((await store.load())?.refreshToken).toBe('rejected-refresh-token') } ) - it('limits OAuth diagnostic messages after parsing the complete error response', async () => { - const store = new OpenAICodexCredentialStore(path.join(tempDir, 'credentials.json')) - store.save({ + const store = new OpenAICodexCredentialStore({ + filePath: path.join(tempDir, 'credentials.json'), + crypto + }) + await store.save({ accessToken: 'expired-token', refreshToken: 'refresh-token', tokenType: 'Bearer', @@ -241,14 +242,12 @@ describe('OpenAI Codex auth', () => { 'fetch', vi.fn().mockResolvedValue(new Response('x'.repeat(2000), { status: 500 })) ) - const auth = new OpenAICodexAuth(store, vi.fn()) - + const auth = new OpenAICodexAuth(store, vi.fn(), shell.openExternal) const failure = await auth.getBackendAuth().catch((error) => error) expect(failure).toBeInstanceOf(Error) expect(failure.message).toHaveLength(1000) expect(failure.failure).toEqual({ statusCode: 500 }) }) - it('opens browser login externally and completes from a pasted callback URL', async () => { startOAuthLoopbackCallbackSessionMock.mockImplementationOnce( async (options: { expectedState: string; path: string }) => { @@ -256,11 +255,12 @@ describe('OpenAI Codex auth', () => { const redirectUri = `http://localhost:43123${callbackPath}` let resolveCallback!: (value: { code: string }) => void let rejectCallback!: (error: Error) => void - const callbackPromise = new Promise<{ code: string }>((resolve, reject) => { + const callbackPromise = new Promise<{ + code: string + }>((resolve, reject) => { resolveCallback = resolve rejectCallback = reject }) - return { redirectUri, waitForCallback: vi.fn(() => callbackPromise), @@ -273,7 +273,6 @@ describe('OpenAI Codex auth', () => { rejectCallback(error) return { kind: 'failure' as const, error, url: callbackUrl.toString() } } - const result = { kind: 'success' as const, code, @@ -287,8 +286,10 @@ describe('OpenAI Codex auth', () => { } } ) - - const store = new OpenAICodexCredentialStore(path.join(tempDir, 'browser.json')) + const store = new OpenAICodexCredentialStore({ + filePath: path.join(tempDir, 'browser.json'), + crypto + }) const fetchMock = vi.fn().mockResolvedValue( new Response( JSON.stringify({ @@ -306,64 +307,51 @@ describe('OpenAI Codex auth', () => { ) ) vi.stubGlobal('fetch', fetchMock) - - const auth = new OpenAICodexAuth(store, vi.fn()) - + const auth = new OpenAICodexAuth(store, vi.fn(), shell.openExternal) const status = await auth.startBrowserLogin() const authUrl = new URL(vi.mocked(shell.openExternal).mock.calls[0][0]) const redirectUri = authUrl.searchParams.get('redirect_uri')! const state = authUrl.searchParams.get('state')! - expect(status.state).toBe('pending-browser') - expect(store.load()).toBeNull() + expect(await store.load()).toBeNull() expect(shell.openExternal).toHaveBeenCalledWith( expect.stringContaining('https://auth.openai.com/oauth/authorize') ) - const callbackUrl = new URL(redirectUri) callbackUrl.searchParams.set('code', 'browser-code') callbackUrl.searchParams.set('state', state) await auth.completeBrowserLoginFromCallbackUrl(callbackUrl.toString()) - - await vi.waitFor(() => expect(store.load()?.accessToken).toBe('browser-token')) - expect(auth.getStatus().state).toBe('authenticated') + await vi.waitFor(async () => expect((await store.load())?.accessToken).toBe('browser-token')) + expect((await auth.getStatus()).state).toBe('authenticated') }) - it('honors the environment kill switch', async () => { process.env.DEEPCHAT_OPENAI_CODEX_DISABLED = 'true' const auth = new OpenAICodexAuth( - new OpenAICodexCredentialStore(path.join(tempDir, 'credentials.json')), - vi.fn() + new OpenAICodexCredentialStore({ filePath: path.join(tempDir, 'credentials.json'), crypto }), + vi.fn(), + shell.openExternal ) - - expect(auth.getStatus().state).toBe('disabled') + expect((await auth.getStatus()).state).toBe('disabled') await expect(auth.getAccessToken()).rejects.toThrow('disabled') }) - it('prefers the credential-store load error over a stale login error', async () => { const credentialPath = path.join(tempDir, 'credentials.json') - const store = new OpenAICodexCredentialStore(credentialPath) - const auth = new OpenAICodexAuth(store, vi.fn()) - + const store = new OpenAICodexCredentialStore({ filePath: credentialPath, crypto }) + const auth = new OpenAICodexAuth(store, vi.fn(), shell.openExternal) await auth.completeBrowserLoginFromCallbackUrl('https://example.com/callback') - files.set(credentialPath, '{broken') - const status = auth.getStatus() - + const status = await auth.getStatus() expect(status.state).toBe('error') expect(status.error).toContain('not valid JSON') }) - it.each(['getBackendAuth', 'forceRefreshBackendAuth'] as const)( 'keeps credential-store load errors separate from sign-in errors in %s', async (method) => { vi.spyOn(console, 'warn').mockImplementation(() => {}) const credentialPath = path.join(tempDir, 'credentials.json') - const store = new OpenAICodexCredentialStore(credentialPath) - const auth = new OpenAICodexAuth(store, vi.fn()) - + const store = new OpenAICodexCredentialStore({ filePath: credentialPath, crypto }) + const auth = new OpenAICodexAuth(store, vi.fn(), shell.openExternal) files.set(credentialPath, '{broken') - const failure = await auth[method]().catch((error) => error) expect(failure).toBeInstanceOf(Error) expect(failure.message).toMatch(/not valid JSON/) @@ -373,4 +361,66 @@ describe('OpenAI Codex auth', () => { expect(files.get(credentialPath)).toBe('{broken') } ) + it('keeps a newly signed-in account after an older refresh resolves late', async () => { + const store = new OpenAICodexCredentialStore({ + filePath: path.join(tempDir, 'account.json'), + crypto + }) + await store.save({ + accessToken: 'old-account', + refreshToken: 'old-refresh', + tokenType: 'Bearer', + expiresAt: 0, + updatedAt: 42 + }) + let refreshEntered!: () => void + let finishRefresh!: (response: Response) => void + const entered = new Promise((resolve) => { + refreshEntered = resolve + }) + let finishCallback!: (value: { code: string }) => void + const callback = new Promise<{ code: string }>((resolve) => { + finishCallback = resolve + }) + startOAuthLoopbackCallbackSessionMock.mockResolvedValueOnce({ + redirectUri: 'http://localhost:43123/auth/callback', + waitForCallback: () => callback, + resolveCallbackUrl: () => { + finishCallback({ code: 'new-account-code' }) + return { kind: 'success', code: 'new-account-code' } + }, + close: vi.fn() + }) + vi.stubGlobal( + 'fetch', + vi.fn(async (_input, init) => { + if (new URLSearchParams(String(init?.body)).get('grant_type') === 'refresh_token') { + refreshEntered() + return new Promise((resolve) => { + finishRefresh = resolve + }) + } + return Response.json({ + access_token: 'new-account', + refresh_token: 'new-refresh', + token_type: 'Bearer', + expires_in: 86400 + }) + }) + ) + const auth = new OpenAICodexAuth(store, vi.fn(), shell.openExternal) + const refreshing = auth.forceRefreshAccessToken() + const rejected = expect(refreshing).rejects.toMatchObject({ name: 'AbortError' }) + await entered + await auth.startBrowserLogin() + await auth.completeBrowserLoginFromCallbackUrl( + 'http://localhost:43123/auth/callback?code=new-account-code' + ) + expect((await store.load())?.accessToken).toBe('new-account') + finishRefresh(Response.json({ access_token: 'late-old-account', expires_in: 86400 })) + await rejected + expect((await store.load())?.accessToken).toBe('new-account') + expect((await auth.getStatus()).authenticated).toBe(true) + auth.stop() + }) }) diff --git a/test/main/provider/auth/providerOAuthCredentials.test.ts b/test/main/provider/auth/providerOAuthCredentials.test.ts new file mode 100644 index 0000000000..1b0e676a92 --- /dev/null +++ b/test/main/provider/auth/providerOAuthCredentials.test.ts @@ -0,0 +1,334 @@ +import * as fs from 'node:fs' +import { join } from 'node:path' +import { tmpdir } from 'node:os' +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' +import type { CredentialCryptoPort } from '@/config/secretStore' +import { OpenAICodexCredentialStore } from '@/provider/auth/openaiCodex/credentialStore' +import { XaiGrokCredentialStore } from '@/provider/auth/xaiGrok/credentialStore' +import { OpenAICodexAuth } from '@/provider/auth/openaiCodex' +import { XaiGrokAuth } from '@/provider/auth/xaiGrok' + +vi.mock('node:fs', async (importOriginal) => ({ + ...(await importOriginal()) +})) + +const deferred = () => { + let resolve!: (value: T) => void + const promise = new Promise((done) => { + resolve = done + }) + return { promise, resolve } +} + +const providers = [ + { + name: 'Codex', + create: (options: { filePath: string; crypto: CredentialCryptoPort }) => { + const store = new OpenAICodexCredentialStore(options) + return { + store, + auth: new OpenAICodexAuth( + store, + vi.fn(), + vi.fn(async () => undefined) + ) + } + } + }, + { + name: 'Grok', + create: (options: { filePath: string; crypto: CredentialCryptoPort }) => { + const store = new XaiGrokCredentialStore(options) + return { + store, + auth: new XaiGrokAuth( + store, + vi.fn(), + vi.fn(async () => undefined) + ) + } + } + } +] +const tokens = (accessToken = 'original') => ({ + accessToken, + refreshToken: 'fixture-refresh', + tokenType: 'Bearer', + expiresAt: 0, + updatedAt: 42 +}) + +for (const provider of providers) + describe(`${provider.name} private credential ownership`, () => { + let directory: string + let filePath: string + let crypto: { + isAvailable: ReturnType Promise>> + encrypt: ReturnType Promise>> + decrypt: ReturnType Promise>> + } + let owner: ReturnType + + beforeEach(() => { + directory = fs.mkdtempSync(join(tmpdir(), 'deepchat-provider-oauth-')) + filePath = join(directory, 'credentials.json') + crypto = { + isAvailable: vi.fn(async () => true), + encrypt: vi.fn(async (value: string) => Buffer.from(value).toString('base64')), + decrypt: vi.fn(async (wrapped: string) => Buffer.from(wrapped, 'base64').toString('utf8')) + } + owner = provider.create({ filePath, crypto }) + }) + + afterEach(() => { + owner.auth.stop() + vi.unstubAllGlobals() + vi.restoreAllMocks() + fs.rmSync(directory, { recursive: true, force: true }) + }) + + it('distinguishes a missing file from an unreadable credential', async () => { + const warn = vi.spyOn(console, 'warn').mockImplementation(() => undefined) + expect(await owner.store.load()).toBeNull() + expect(owner.store.getLoadError()).toBeNull() + expect(warn).not.toHaveBeenCalled() + await owner.store.save(tokens()) + const before = fs.readFileSync(filePath, 'utf8') + crypto.decrypt.mockRejectedValue(new Error('keyring locked')) + expect(await owner.store.load()).toBeNull() + expect(owner.store.getLoadError()).toContain('decryption failed') + expect(fs.readFileSync(filePath, 'utf8')).toBe(before) + await owner.store.save(tokens('replacement')) + expect(fs.existsSync(`${filePath}.corrupt`)).toBe(false) + crypto.decrypt.mockImplementation(async (wrapped) => + Buffer.from(wrapped, 'base64').toString('utf8') + ) + expect((await owner.store.load())?.accessToken).toBe('replacement') + expect(owner.store.getLoadError()).toBeNull() + }) + + it('rejects oversized payloads before native wrapping', async () => { + await owner.store.save(tokens()) + const before = fs.readFileSync(filePath, 'utf8') + crypto.encrypt.mockClear() + await expect(owner.store.save(tokens('x'.repeat(8 * 1024 * 1024)))).rejects.toThrow( + 'oversized' + ) + expect(crypto.encrypt).not.toHaveBeenCalled() + expect(fs.readFileSync(filePath, 'utf8')).toBe(before) + }) + + it('keeps encrypted reads byte-identical and preserves the file fallback policy', async () => { + await owner.store.save(tokens()) + const before = fs.readFileSync(filePath, 'utf8') + expect(await owner.store.load()).toMatchObject(tokens()) + expect(fs.readFileSync(filePath, 'utf8')).toBe(before) + expect(before).not.toContain('fixture-refresh') + expect(fs.statSync(filePath).mode & 0o777).toBe(0o600) + crypto.isAvailable.mockResolvedValue(false) + await owner.store.save(tokens('file-fallback')) + expect(fs.readdirSync(directory)).toEqual(['credentials.json']) + expect(JSON.parse(fs.readFileSync(filePath, 'utf8'))).toMatchObject({ + version: 1, + storage: 'file', + tokens: { accessToken: 'file-fallback' } + }) + }) + + it.each(['encrypt', 'fsync', 'rename'] as const)( + 'preserves the previous file after %s failure and recovers', + async (failure) => { + await owner.store.save(tokens()) + const before = fs.readFileSync(filePath, 'utf8') + if (failure === 'encrypt') crypto.encrypt.mockRejectedValueOnce(new Error('native failure')) + if (failure === 'fsync') + vi.spyOn(fs, 'fsyncSync').mockImplementationOnce(() => { + throw new Error('flush failure') + }) + if (failure === 'rename') + vi.spyOn(fs, 'renameSync').mockImplementationOnce(() => { + throw new Error('rename failure') + }) + await expect(owner.store.save(tokens('replacement'))).rejects.toThrow('failure') + expect(fs.readFileSync(filePath, 'utf8')).toBe(before) + expect(fs.readdirSync(directory)).toEqual(['credentials.json']) + expect((await owner.store.load())?.accessToken).toBe('original') + await owner.store.save(tokens('recovered')) + expect((await owner.store.load())?.accessToken).toBe('recovered') + } + ) + + it('backs up corrupt contents and removes all private artifacts through clear', async () => { + fs.writeFileSync(filePath, '{broken') + expect(await owner.store.load()).toBeNull() + expect(owner.store.getLoadError()).toContain('not valid JSON') + await owner.store.save(tokens()) + expect(owner.store.getLoadError()).toBeNull() + expect(fs.readFileSync(`${filePath}.corrupt`, 'utf8')).toBe('{broken') + fs.writeFileSync(`${filePath}.tmp-stale`, 'stale') + fs.writeFileSync(`${filePath}.tmp`, 'legacy') + fs.writeFileSync(join(directory, 'unrelated.txt'), 'unrelated') + await owner.store.clear() + expect(fs.readdirSync(directory)).toEqual(['unrelated.txt']) + }) + + it('coalesces pending decrypts and rejects both results after durable removal', async () => { + await owner.store.save(tokens()) + const raw = Buffer.from( + JSON.parse(fs.readFileSync(filePath, 'utf8')).wrapped, + 'base64' + ).toString('utf8') + const entered = deferred() + const gate = deferred() + crypto.decrypt.mockImplementationOnce(() => { + entered.resolve() + return gate.promise + }) + const reads = [owner.store.load(), owner.store.load()] + const results = Promise.allSettled(reads) + await entered.promise + expect(crypto.decrypt).toHaveBeenCalledOnce() + await owner.store.clear() + gate.resolve(raw) + expect((await results).every((item) => item.status === 'rejected')).toBe(true) + expect(await owner.store.load()).toBeNull() + expect(fs.existsSync(filePath)).toBe(false) + }) + + it('serializes save/clear/save without exposing a superseded file', async () => { + await Promise.all([ + owner.store.save(tokens('first')), + owner.store.clear(), + owner.store.save(tokens('last')) + ]) + expect((await owner.store.load())?.accessToken).toBe('last') + const reopened = provider.create({ filePath, crypto }) + expect((await reopened.store.load())?.accessToken).toBe('last') + reopened.auth.stop() + }) + + it('reserves control capacity and stops pending native writes and queued mutations', async () => { + const entered = deferred() + const gate = deferred() + crypto.encrypt.mockImplementationOnce(() => { + entered.resolve() + return gate.promise + }) + const normal = Array.from({ length: 24 }, () => owner.store.save(tokens())) + await entered.promise + await expect(owner.store.save(tokens('overflow'))).rejects.toThrow('queue is full') + const control = Array.from({ length: 8 }, () => owner.store.clear()) + const results = Promise.allSettled([...normal, ...control]) + await expect(owner.store.clear()).rejects.toThrow('queue is full') + owner.auth.stop() + gate.resolve('late') + expect((await results).every((item) => item.status === 'rejected')).toBe(true) + expect(fs.existsSync(filePath)).toBe(false) + expect(crypto.encrypt).toHaveBeenCalledOnce() + }) + + it('still removes known private artifacts when directory enumeration fails', async () => { + await owner.store.save(tokens()) + fs.writeFileSync(`${filePath}.corrupt`, 'backup') + fs.writeFileSync(`${filePath}.tmp`, 'temporary') + vi.spyOn(fs, 'readdirSync').mockImplementationOnce(() => { + throw new Error('directory denied') + }) + await expect(owner.store.clear()).rejects.toThrow('cleanup failed') + expect(fs.readdirSync(directory)).toEqual([]) + }) + + it('does not acknowledge logout while credential deletion fails', async () => { + await owner.store.save(tokens()) + fs.writeFileSync(`${filePath}.corrupt`, 'backup') + fs.writeFileSync(`${filePath}.tmp`, 'temporary') + const remove = fs.rmSync + vi.spyOn(fs, 'rmSync').mockImplementation((path, options) => { + if (String(path) === filePath) throw new Error('denied') + return remove(path, options) + }) + await expect(owner.auth.logout()).rejects.toThrow('cleanup failed') + expect((await owner.auth.getStatus()).authenticated).toBe(true) + expect(fs.existsSync(filePath)).toBe(true) + expect(fs.readdirSync(directory)).toEqual(['credentials.json']) + }) + + it('waits for a late refresh wrap before logout removes the final file', async () => { + await owner.store.save(tokens()) + const entered = deferred() + const gate = deferred() + crypto.encrypt.mockImplementationOnce(() => { + entered.resolve() + return gate.promise + }) + vi.stubGlobal( + 'fetch', + vi.fn(async (input) => + String(input).includes('openid-configuration') + ? Response.json({ + device_authorization_endpoint: 'https://auth.x.ai/device', + token_endpoint: 'https://auth.x.ai/token', + revocation_endpoint: 'https://auth.x.ai/revoke' + }) + : Response.json({ access_token: 'refreshed', expires_in: 86400 }) + ) + ) + const refreshing = owner.auth.forceRefreshAccessToken() + const rejected = expect(refreshing).rejects.toMatchObject({ name: 'AbortError' }) + await entered.promise + const loggedOut = owner.auth.logout() + gate.resolve(Buffer.from(JSON.stringify(tokens('refreshed'))).toString('base64')) + await rejected + expect((await loggedOut).authenticated).toBe(false) + expect(fs.existsSync(filePath)).toBe(false) + }) + + it('aborts a refresh response body on stop without changing credentials', async () => { + await owner.store.save(tokens()) + const before = fs.readFileSync(filePath, 'utf8') + const entered = deferred() + vi.stubGlobal( + 'fetch', + vi.fn(async (input) => { + if (String(input).includes('openid-configuration')) + return Response.json({ + device_authorization_endpoint: 'https://auth.x.ai/device', + token_endpoint: 'https://auth.x.ai/token' + }) + entered.resolve() + return new Response(new ReadableStream()) + }) + ) + const pending = owner.auth.forceRefreshAccessToken() + const rejected = expect(pending).rejects.toMatchObject({ name: 'AbortError' }) + await entered.promise + owner.auth.stop() + await rejected + expect(fs.readFileSync(filePath, 'utf8')).toBe(before) + }) + + it.each(['declared', 'streamed'] as const)( + 'rejects oversized %s auth data before a credential write', + async (size) => { + await owner.store.save(tokens()) + const before = fs.readFileSync(filePath, 'utf8') + vi.stubGlobal( + 'fetch', + vi.fn(async (input) => + String(input).includes('openid-configuration') + ? Response.json({ + device_authorization_endpoint: 'https://auth.x.ai/device', + token_endpoint: 'https://auth.x.ai/token' + }) + : size === 'declared' + ? new Response('oversized', { + headers: { 'content-length': String(8 * 1024 * 1024 + 1) } + }) + : new Response(new Uint8Array(8 * 1024 * 1024 + 1)) + ) + ) + await expect(owner.auth.forceRefreshAccessToken()).rejects.toThrow('size limit') + expect(fs.readFileSync(filePath, 'utf8')).toBe(before) + } + ) + }) diff --git a/test/main/provider/auth/xaiGrok.test.ts b/test/main/provider/auth/xaiGrok.test.ts index bcf080b1fd..b3c674b942 100644 --- a/test/main/provider/auth/xaiGrok.test.ts +++ b/test/main/provider/auth/xaiGrok.test.ts @@ -10,6 +10,11 @@ import { isTrustedXaiOAuthEndpoint } from '@/provider/auth/xaiGrok/constants' import { createXaiGrokFetch, shouldUseXaiGrokOAuthFetch } from '@/provider/xaiGrokAuthAdapter' +const crypto = { + isAvailable: async () => false, + encrypt: async (value: string) => Buffer.from(value).toString('base64'), + decrypt: async (wrapped: string) => Buffer.from(wrapped, 'base64').toString('utf8') +} function jsonResponse(value: unknown, init?: ResponseInit): Response { return new Response(JSON.stringify(value), { @@ -18,14 +23,17 @@ function jsonResponse(value: unknown, init?: ResponseInit): Response { ...init }) } - describe('xAI Grok OAuth', () => { let tempDir: string let files: Map let fdPaths: Map - beforeEach(() => { files = new Map() + vi.mocked(fs.statSync).mockImplementation((file) => { + const raw = files.get(String(file)) + if (raw === undefined) throw Object.assign(new Error('ENOENT'), { code: 'ENOENT' }) + return { size: Buffer.byteLength(raw, 'utf8') } as fs.Stats + }) fdPaths = new Map() let nextFd = 1 tempDir = `/tmp/deepchat-xai-grok-auth-${Date.now()}` @@ -73,7 +81,6 @@ describe('xAI Grok OAuth', () => { delete process.env.XAI_GROK_ACCESS_TOKEN resetGlobalXaiGrokAuthForTests() }) - afterEach(() => { vi.useRealTimers() vi.unstubAllGlobals() @@ -81,7 +88,6 @@ describe('xAI Grok OAuth', () => { delete process.env.XAI_GROK_ACCESS_TOKEN resetGlobalXaiGrokAuthForTests() }) - it('accepts only trusted xAI OAuth endpoints', () => { expect(isTrustedXaiOAuthEndpoint('https://auth.x.ai/oauth2/token')).toBe(true) expect(isTrustedXaiOAuthEndpoint('https://accounts.x.ai/oauth2/token')).toBe(true) @@ -91,7 +97,6 @@ describe('xAI Grok OAuth', () => { expect(isTrustedXaiApiEndpoint('https://api.x.ai/v1/chat/completions')).toBe(true) expect(isTrustedXaiApiEndpoint('https://api.x.ai.evil.test/v1')).toBe(false) }) - it('enables OAuth injection only for the built-in Grok provider on xAI endpoints', () => { const provider = { id: 'grok', @@ -101,7 +106,6 @@ describe('xAI Grok OAuth', () => { baseUrl: 'https://api.x.ai/v1', enable: true } - expect(shouldUseXaiGrokOAuthFetch(provider)).toBe(true) expect( shouldUseXaiGrokOAuthFetch({ @@ -116,7 +120,6 @@ describe('xAI Grok OAuth', () => { }) ).toBe(false) }) - it('refuses to send credentials when a wrapped request targets an untrusted endpoint', async () => { const underlyingFetch = vi.fn() const fetcher = createXaiGrokFetch( @@ -131,27 +134,24 @@ describe('xAI Grok OAuth', () => { {}, underlyingFetch ) - await expect(fetcher('https://attacker.example.com/v1/models')).rejects.toThrow( 'untrusted API endpoint' ) expect(underlyingFetch).not.toHaveBeenCalled() }) - - it('stores Grok OAuth credentials outside provider records', () => { + it('stores Grok OAuth credentials outside provider records', async () => { const credentialPath = path.join(tempDir, 'credentials.json') - const store = new XaiGrokCredentialStore(credentialPath) - store.save({ + const store = new XaiGrokCredentialStore({ filePath: credentialPath, crypto }) + await store.save({ accessToken: 'access-token', refreshToken: 'refresh-token', tokenType: 'Bearer', - expiresAt: Date.now() + 3600_000, + expiresAt: Date.now() + 3600000, accountId: 'account-id', accountLabel: 'user@example.com', tokenEndpoint: 'https://auth.x.ai/oauth2/token', updatedAt: Date.now() }) - expect(fs.mkdirSync).toHaveBeenCalledWith(path.dirname(credentialPath), { recursive: true, mode: 0o700 @@ -161,18 +161,15 @@ describe('xAI Grok OAuth', () => { expect(fs.openSync).toHaveBeenCalledWith(temporaryPath, 'w', 0o600) expect(fs.fsyncSync).toHaveBeenCalled() expect(fs.renameSync).toHaveBeenCalledWith(temporaryPath, credentialPath) - - const loaded = store.load() + const loaded = await store.load() expect(loaded?.accessToken).toBe('access-token') expect(loaded?.refreshToken).toBe('refresh-token') expect(loaded?.accountLabel).toBe('user@example.com') }) - it('completes device-code login and exposes authenticated status', async () => { const credentialPath = path.join(tempDir, 'credentials.json') - const store = new XaiGrokCredentialStore(credentialPath) - const auth = new XaiGrokAuth(store, vi.fn()) - + const store = new XaiGrokCredentialStore({ filePath: credentialPath, crypto }) + const auth = new XaiGrokAuth(store, vi.fn(), shell.openExternal) const fetchImpl = vi.fn(async (input, init) => { const url = String(input) if (url.includes('.well-known/openid-configuration')) { @@ -212,27 +209,23 @@ describe('xAI Grok OAuth', () => { throw new Error(`Unexpected fetch: ${url}`) }) vi.stubGlobal('fetch', fetchImpl) - const pending = await auth.startDeviceLogin() expect(pending.state).toBe('pending-device') expect(pending.userCode).toBe('ABCD-EFGH') expect(shell.openExternal).toHaveBeenCalled() - // Wait for background poll to finish - await vi.waitFor(() => { - expect(auth.getStatus().authenticated).toBe(true) + await vi.waitFor(async () => { + expect((await auth.getStatus()).authenticated).toBe(true) }) - - const status = auth.getStatus() + const status = await auth.getStatus() expect(status.state).toBe('authenticated') expect(status.accountLabel).toBe('grok@example.com') expect(await auth.ensureAccessToken()).toBe('access-1') }) - it('refreshes near-expiry access tokens', async () => { const credentialPath = path.join(tempDir, 'credentials.json') - const store = new XaiGrokCredentialStore(credentialPath) - store.save({ + const store = new XaiGrokCredentialStore({ filePath: credentialPath, crypto }) + await store.save({ accessToken: 'access-old', refreshToken: 'refresh-1', tokenType: 'Bearer', @@ -240,8 +233,7 @@ describe('xAI Grok OAuth', () => { tokenEndpoint: 'https://auth.x.ai/oauth2/token', updatedAt: Date.now() }) - const auth = new XaiGrokAuth(store, vi.fn()) - + const auth = new XaiGrokAuth(store, vi.fn(), shell.openExternal) const fetchImpl = vi.fn(async (input, init) => { const url = String(input) expect(url).toBe('https://auth.x.ai/oauth2/token') @@ -253,62 +245,47 @@ describe('xAI Grok OAuth', () => { }) }) vi.stubGlobal('fetch', fetchImpl) - const token = await auth.ensureAccessToken() expect(token).toBe('access-new') - expect(store.load()?.accessToken).toBe('access-new') + expect((await store.load())?.accessToken).toBe('access-new') }) - it('coordinates concurrent forced token refreshes', async () => { const credentialPath = path.join(tempDir, 'credentials.json') - const store = new XaiGrokCredentialStore(credentialPath) - store.save({ + const store = new XaiGrokCredentialStore({ filePath: credentialPath, crypto }) + await store.save({ accessToken: 'access-old', refreshToken: 'refresh-1', tokenType: 'Bearer', - expiresAt: Date.now() + 3600_000, + expiresAt: Date.now() + 3600000, tokenEndpoint: 'https://auth.x.ai/oauth2/token', updatedAt: Date.now() }) - const auth = new XaiGrokAuth(store, vi.fn()) + const auth = new XaiGrokAuth(store, vi.fn(), shell.openExternal) const fetchImpl = vi.fn(async () => jsonResponse({ access_token: 'access-new', expires_in: 3600 }) ) vi.stubGlobal('fetch', fetchImpl) - await expect( Promise.all([auth.forceRefreshAccessToken(), auth.forceRefreshAccessToken()]) ).resolves.toEqual(['access-new', 'access-new']) expect(fetchImpl).toHaveBeenCalledTimes(1) }) - - it('does not expose an expired access token to synchronous request paths', () => { - const store = new XaiGrokCredentialStore(path.join(tempDir, 'credentials.json')) - store.save({ - accessToken: 'access-expired', - refreshToken: 'refresh-1', - tokenType: 'Bearer', - expiresAt: Date.now() - 1000, - tokenEndpoint: 'https://auth.x.ai/oauth2/token', - updatedAt: Date.now() - }) - - expect(new XaiGrokAuth(store, vi.fn()).peekAccessToken()).toBeNull() - }) - - it('reports disabled state when kill switch is set', () => { + it('reports disabled state when kill switch is set', async () => { process.env.DEEPCHAT_XAI_GROK_OAUTH_DISABLED = '1' - const auth = new XaiGrokAuth(new XaiGrokCredentialStore(path.join(tempDir, 'c.json')), vi.fn()) - expect(auth.getStatus()).toMatchObject({ + const auth = new XaiGrokAuth( + new XaiGrokCredentialStore({ filePath: path.join(tempDir, 'c.json'), crypto }), + vi.fn(), + shell.openExternal + ) + expect(await auth.getStatus()).toMatchObject({ state: 'disabled', authenticated: false }) }) - it('prefers the credential-store load error over a stale login error', async () => { const credentialPath = path.join(tempDir, 'credentials.json') - const store = new XaiGrokCredentialStore(credentialPath) - store.save({ + const store = new XaiGrokCredentialStore({ filePath: credentialPath, crypto }) + await store.save({ accessToken: 'access-old', refreshToken: 'refresh-1', tokenType: 'Bearer', @@ -316,32 +293,83 @@ describe('xAI Grok OAuth', () => { tokenEndpoint: 'https://auth.x.ai/oauth2/token', updatedAt: Date.now() }) - const auth = new XaiGrokAuth(store, vi.fn()) + const auth = new XaiGrokAuth(store, vi.fn(), shell.openExternal) vi.stubGlobal( 'fetch', vi.fn(async () => { throw new Error('network down') }) ) - await expect(auth.ensureAccessToken()).rejects.toThrow('network down') - files.set(credentialPath, '{broken') - const status = auth.getStatus() - + const status = await auth.getStatus() expect(status.state).toBe('error') expect(status.error).toContain('not valid JSON') }) - it('rejects access token requests with the credential-store load error', async () => { vi.spyOn(console, 'warn').mockImplementation(() => {}) const credentialPath = path.join(tempDir, 'credentials.json') - const store = new XaiGrokCredentialStore(credentialPath) - const auth = new XaiGrokAuth(store, vi.fn()) - + const store = new XaiGrokCredentialStore({ filePath: credentialPath, crypto }) + const auth = new XaiGrokAuth(store, vi.fn(), shell.openExternal) files.set(credentialPath, '{broken') - await expect(auth.getAccessToken()).rejects.toThrow(/not valid JSON/) await expect(auth.forceRefreshAccessToken()).rejects.toThrow(/not valid JSON/) }) + it('rejects late discovery without canceling the replacement device flow', async () => { + const store = new XaiGrokCredentialStore({ + filePath: path.join(tempDir, 'discovery.json'), + crypto + }) + const auth = new XaiGrokAuth(store, vi.fn(), shell.openExternal) + let finishDiscovery!: (response: Response) => void + let finishPoll!: (response: Response) => void + let discoveryEntered!: () => void + const entered = new Promise((resolve) => { + discoveryEntered = resolve + }) + const discovery = { + device_authorization_endpoint: 'https://auth.x.ai/device', + token_endpoint: 'https://auth.x.ai/token' + } + let discoveries = 0 + vi.stubGlobal( + 'fetch', + vi.fn(async (input) => { + if (String(input).includes('openid-configuration')) { + if (++discoveries === 1) { + discoveryEntered() + return new Promise((resolve) => { + finishDiscovery = resolve + }) + } + return Response.json(discovery) + } + if (String(input).endsWith('/device')) + return Response.json({ + device_code: 'current-code', + user_code: 'CURRENT', + verification_uri: 'https://auth.x.ai/verify', + expires_in: 600, + interval: 5 + }) + return new Promise((resolve) => { + finishPoll = resolve + }) + }) + ) + const first = auth.startDeviceLogin() + const rejected = expect(first).rejects.toMatchObject({ name: 'AbortError' }) + await entered + const current = await auth.startDeviceLogin() + expect(current.userCode).toBe('CURRENT') + finishDiscovery(Response.json(discovery)) + await rejected + expect((await auth.getStatus()).userCode).toBe('CURRENT') + expect(shell.openExternal).toHaveBeenCalledOnce() + const canceled = auth.cancelLogin() + finishPoll(Response.json({ access_token: 'late-device-token', expires_in: 86400 })) + await canceled + expect(await store.load()).toBeNull() + auth.stop() + }) }) diff --git a/test/main/provider/awsBedrockProvider.test.ts b/test/main/provider/awsBedrockProvider.test.ts index 5fe02e1f93..d626615500 100644 --- a/test/main/provider/awsBedrockProvider.test.ts +++ b/test/main/provider/awsBedrockProvider.test.ts @@ -1,3 +1,4 @@ +import { providerTestEnvironment } from '../../helpers/providerEnvironment' import type { ProviderSettingsPort } from '@/provider/settings' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' import type { AWS_BEDROCK_PROVIDER } from '@shared/types/provider' @@ -125,7 +126,8 @@ describe('AiSdkProvider aws-bedrock', () => { createProvider({ credential: undefined }), - createProviderSettings() + createProviderSettings(), + providerTestEnvironment ) await expect(provider.check()).resolves.toEqual({ isOk: true, errorMsg: null }) @@ -144,7 +146,11 @@ describe('AiSdkProvider aws-bedrock', () => { ] }) - const provider = new AiSdkProvider(createProvider(), createProviderSettings()) + const provider = new AiSdkProvider( + createProvider(), + createProviderSettings(), + providerTestEnvironment + ) const models = await provider.fetchModels() expect(models).toEqual([ @@ -163,7 +169,11 @@ describe('AiSdkProvider aws-bedrock', () => { it('falls back to the provider DB snapshot when the Bedrock catalog lookup fails', async () => { mockBedrockSend.mockRejectedValue(new Error('catalog unavailable')) - const provider = new AiSdkProvider(createProvider(), createProviderSettings()) + const provider = new AiSdkProvider( + createProvider(), + createProviderSettings(), + providerTestEnvironment + ) const models = await provider.fetchModels() expect(models).toEqual([ @@ -185,7 +195,11 @@ describe('AiSdkProvider aws-bedrock', () => { ] }) - const provider = new AiSdkProvider(createProvider(), createProviderSettings()) + const provider = new AiSdkProvider( + createProvider(), + createProviderSettings(), + providerTestEnvironment + ) ;(provider as any).isInitialized = true await expect(provider.check()).resolves.toEqual({ diff --git a/test/main/provider/backgroundModelSync.test.ts b/test/main/provider/backgroundModelSync.test.ts index 37fdef7d82..093c0fcf9c 100644 --- a/test/main/provider/backgroundModelSync.test.ts +++ b/test/main/provider/backgroundModelSync.test.ts @@ -1,3 +1,4 @@ +import { providerTestEnvironment } from '../../helpers/providerEnvironment' import type { ProviderSettingsPort } from '@/provider/settings' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' import type { LLM_PROVIDER, MODEL_META } from '@shared/types/provider' @@ -179,11 +180,16 @@ const createProviderRuntime = (providerSettings: ProviderSettingsPort) => { mockSqlitePresenter as never, { newEnvironmentsTable: { listPathsForSession: () => [], syncPath: vi.fn() } - } as never + } as never, + '/home/tester' ) return new ProviderRuntime( providerSettings, - { getLanguage: vi.fn().mockReturnValue('en-US') }, + { + allowInsecureTls: providerTestEnvironment.allowInsecureTls, + defaultHeaders: providerTestEnvironment.defaultHeaders, + getLanguage: vi.fn().mockReturnValue('en-US') + }, {} as never, {} as never, new AcpRuntimeOwner(() => { diff --git a/test/main/provider/baseProvider.test.ts b/test/main/provider/baseProvider.test.ts index 4a334918bf..a263602972 100644 --- a/test/main/provider/baseProvider.test.ts +++ b/test/main/provider/baseProvider.test.ts @@ -1,3 +1,4 @@ +import { providerTestEnvironment } from '../../helpers/providerEnvironment' import type { ProviderSettingsPort } from '@/provider/settings' import { describe, expect, it, vi } from 'vitest' import type { LLM_PROVIDER, MODEL_META, ModelConfig } from '@shared/types/provider' @@ -9,7 +10,8 @@ import { BaseLLMProvider } from '../../../src/main/provider/baseProvider' class TestProvider extends BaseLLMProvider { constructor( providerSettings: ProviderSettingsPort, - private readonly modelFetcher: () => Promise = async () => [] + private readonly modelFetcher: () => Promise = async () => [], + environment = providerTestEnvironment ) { super( { @@ -21,7 +23,8 @@ class TestProvider extends BaseLLMProvider { apiVersion: '', models: [] } as unknown as LLM_PROVIDER, - providerSettings + providerSettings, + environment ) } @@ -93,6 +96,7 @@ class TestProvider extends BaseLLMProvider { describe('BaseLLMProvider tool XML conversion', () => { const providerSettings = { + defaultHeaders: providerTestEnvironment.defaultHeaders, getProviderModels: vi.fn().mockReturnValue([]), getCustomModels: vi.fn().mockReturnValue([]), getLanguage: vi.fn().mockReturnValue('zh-CN'), @@ -230,7 +234,7 @@ describe('BaseLLMProvider tool XML conversion', () => { const error = new Error('model endpoint returned 404') const models = [{ id: 'model-1', providerId: 'test-provider' }] as MODEL_META[] const fetchModels = vi.fn().mockRejectedValueOnce(error).mockResolvedValue(models) - const provider = new TestProvider(providerSettings, fetchModels) + const provider = new TestProvider(providerSettings, fetchModels, providerTestEnvironment) await expect( Promise.allSettled([provider.fetchModels(), provider.fetchModels({ suppressErrors: false })]) @@ -265,7 +269,7 @@ describe('BaseLLMProvider tool XML conversion', () => { .fn() .mockReturnValueOnce(new Promise((resolve) => (resolveFirst = resolve))) .mockReturnValueOnce(new Promise((resolve) => (resolveSecond = resolve))) - const provider = new TestProvider(providerSettings, fetchModels) + const provider = new TestProvider(providerSettings, fetchModels, providerTestEnvironment) const first = provider.fetchModels() provider.updateConfig({ ...provider.getProviderSnapshot(), apiKey: 'updated-key' }) @@ -289,7 +293,7 @@ describe('BaseLLMProvider tool XML conversion', () => { .fn() .mockReturnValueOnce(new Promise((resolve) => (resolveFirst = resolve))) .mockResolvedValue(models) - const provider = new TestProvider(providerSettings, fetchModels) + const provider = new TestProvider(providerSettings, fetchModels, providerTestEnvironment) const first = provider.fetchModels() provider.updateConfig({ ...provider.getProviderSnapshot(), apiKey: 'updated-key' }) @@ -312,7 +316,7 @@ describe('BaseLLMProvider tool XML conversion', () => { } ] const fetchModels = vi.fn().mockResolvedValue(models) - const provider = new TestProvider(providerSettings, fetchModels) + const provider = new TestProvider(providerSettings, fetchModels, providerTestEnvironment) const [first, second] = await Promise.all([provider.fetchModels(), provider.fetchModels()]) const expected = [{ ...models[0], providerId: 'test-provider' }] diff --git a/test/main/provider/basicApiKeyProviders.test.ts b/test/main/provider/basicApiKeyProviders.test.ts index f612baee6f..d3685f3d3f 100644 --- a/test/main/provider/basicApiKeyProviders.test.ts +++ b/test/main/provider/basicApiKeyProviders.test.ts @@ -1,6 +1,8 @@ +import { providerTestEnvironment } from '../../helpers/providerEnvironment' import type { ProviderSettingsPort } from '@/provider/settings' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' import type { LLM_PROVIDER } from '@shared/types/provider' +import { ModelType } from '@shared/model' import { DEFAULT_PROVIDERS } from '../../../src/main/provider/defaults' import { AiSdkProvider } from '../../../src/main/provider/providers/aiSdkProvider' import { resolveAiSdkProviderDefinition } from '../../../src/main/provider/providerRegistry' @@ -98,7 +100,7 @@ describe('basic API-key provider registrations', () => { id: 'fireworks-ai', models: [{ id: modelId, display_name: 'GPT OSS 120B' }] }) - const provider = new AiSdkProvider(defaults, createProviderSettings()) + const provider = new AiSdkProvider(defaults, createProviderSettings(), providerTestEnvironment) expect(await provider.fetchModels({ suppressErrors: false })).toEqual([ { id: modelId, @@ -153,7 +155,7 @@ describe('basic API-key provider registrations', () => { }) ) global.fetch = fetchMock - const provider = new AiSdkProvider(config, createProviderSettings()) + const provider = new AiSdkProvider(config, createProviderSettings(), providerTestEnvironment) expect(await provider.check()).toEqual({ isOk: true, errorMsg: null }) expect(fetchMock).toHaveBeenCalledTimes(1) const [url, init] = fetchMock.mock.calls[0] @@ -265,13 +267,18 @@ describe('basic API-key provider registrations', () => { }) ) global.fetch = fetchMock - const missingToken = new AiSdkProvider(defaults, createProviderSettings()) + const missingToken = new AiSdkProvider( + defaults, + createProviderSettings(), + providerTestEnvironment + ) fetchMock.mockResolvedValueOnce(new Response('Unauthorized', { status: 401 })) expect(await missingToken.check()).toMatchObject({ isOk: false }) const provider = new AiSdkProvider( { ...defaults, apiKey: 'astrlink-test-token', baseUrl: 'http://127.0.0.1:28419/v1' }, - createProviderSettings() + createProviderSettings(), + providerTestEnvironment ) await expect(provider.fetchModels({ suppressErrors: false })).resolves.toEqual([ expect.objectContaining({ @@ -318,7 +325,7 @@ describe('basic API-key provider registrations', () => { ) global.fetch = fetchMock as typeof fetch - const provider = new AiSdkProvider(defaults, createProviderSettings()) + const provider = new AiSdkProvider(defaults, createProviderSettings(), providerTestEnvironment) expect(resolveAiSdkProviderDefinition(defaults)).toMatchObject({ runtimeKind: 'openai-compatible', modelSource: 'openai', @@ -363,7 +370,7 @@ describe('basic API-key provider registrations', () => { global.fetch = fetchMock as unknown as typeof fetch const config = { ...defaults, apiKey: 'test-key' } - const provider = new AiSdkProvider(config, createProviderSettings()) + const provider = new AiSdkProvider(config, createProviderSettings(), providerTestEnvironment) expect(resolveAiSdkProviderDefinition(config)).toMatchObject({ runtimeKind: 'openai-compatible', modelSource: 'openai', @@ -434,7 +441,7 @@ describe('basic API-key provider registrations', () => { global.fetch = fetchMock as unknown as typeof fetch const config = { ...defaults, apiKey: 'test-key' } - const provider = new AiSdkProvider(config, createProviderSettings()) + const provider = new AiSdkProvider(config, createProviderSettings(), providerTestEnvironment) expect(resolveAiSdkProviderDefinition(config)).toMatchObject({ runtimeKind: 'openai-compatible', modelSource: 'requesty', @@ -548,14 +555,18 @@ describe('basic API-key provider registrations', () => { ) global.fetch = fetchMock as unknown as typeof fetch - const provider = new AiSdkProvider({ ...defaults }, createProviderSettings()) + const provider = new AiSdkProvider( + { ...defaults }, + createProviderSettings(), + providerTestEnvironment + ) expect(resolveAiSdkProviderDefinition(defaults)).toMatchObject({ runtimeKind: 'openai-compatible', modelSource: 'litellm', checkStrategy: 'fetch-models', embeddingStrategy: 'openai' }) - expect(resolveAiSdkProviderDefinition(defaults).credentialStrategy).toBeUndefined() + expect(resolveAiSdkProviderDefinition(defaults)!.credentialStrategy).toBeUndefined() await expect(provider.fetchModels({ suppressErrors: false })).resolves.toEqual([ expect.objectContaining({ @@ -610,7 +621,11 @@ describe('basic API-key provider registrations', () => { if (reversed) deployments.reverse() global.fetch = vi.fn().mockImplementation(async () => Response.json({ data: deployments })) - const provider = new AiSdkProvider({ ...defaults }, createProviderSettings()) + const provider = new AiSdkProvider( + { ...defaults }, + createProviderSettings(), + providerTestEnvironment + ) await expect(provider.fetchModels({ suppressErrors: false })).resolves.toEqual([ expect.objectContaining({ id: 'pool', @@ -635,7 +650,8 @@ describe('basic API-key provider registrations', () => { const provider = new AiSdkProvider( { ...defaults, apiKey: 'sk-virtual' }, - createProviderSettings() + createProviderSettings(), + providerTestEnvironment ) await expect(provider.getKeyStatus()).resolves.toEqual({ @@ -660,11 +676,19 @@ describe('basic API-key provider registrations', () => { ) global.fetch = fetchMock as unknown as typeof fetch - const keyless = new AiSdkProvider({ ...defaults }, createProviderSettings()) + const keyless = new AiSdkProvider( + { ...defaults }, + createProviderSettings(), + providerTestEnvironment + ) await expect(keyless.getKeyStatus()).resolves.toBeNull() expect(fetchMock).not.toHaveBeenCalled() - const master = new AiSdkProvider({ ...defaults, apiKey: 'sk-master' }, createProviderSettings()) + const master = new AiSdkProvider( + { ...defaults, apiKey: 'sk-master' }, + createProviderSettings(), + providerTestEnvironment + ) await expect(master.getKeyStatus()).resolves.toBeNull() }) @@ -682,7 +706,8 @@ describe('basic API-key provider registrations', () => { ) const provider = new AiSdkProvider( { ...defaults, apiKey: 'sk-virtual' }, - createProviderSettings() + createProviderSettings(), + providerTestEnvironment ) const result = expect(provider.getKeyStatus()).rejects.toMatchObject({ name: 'AbortError', @@ -727,7 +752,8 @@ describe('basic API-key provider registrations', () => { const provider = new AiSdkProvider( { ...defaults, apiKey: 'sk-virtual' }, - createProviderSettings() + createProviderSettings(), + providerTestEnvironment ) await expect(provider.fetchModels({ suppressErrors: false })).resolves.toEqual([ @@ -755,7 +781,8 @@ describe('basic API-key provider registrations', () => { const provider = new AiSdkProvider( { ...defaults, apiKey: 'sk-virtual' }, - createProviderSettings() + createProviderSettings(), + providerTestEnvironment ) await expect(provider.fetchModels({ suppressErrors: false })).rejects.toThrow( @@ -789,7 +816,7 @@ describe('basic API-key provider registrations', () => { global.fetch = fetchMock as unknown as typeof fetch const config = { ...defaults, apiKey: 'test-key' } - const provider = new AiSdkProvider(config, createProviderSettings()) + const provider = new AiSdkProvider(config, createProviderSettings(), providerTestEnvironment) expect(resolveAiSdkProviderDefinition(config)).toMatchObject({ runtimeKind: 'openai-compatible', modelSource: 'demonroute', @@ -843,7 +870,7 @@ describe('basic API-key provider registrations', () => { global.fetch = fetchMock as unknown as typeof fetch const config = { ...defaults, apiKey: 'test-key' } - const provider = new AiSdkProvider(config, createProviderSettings()) + const provider = new AiSdkProvider(config, createProviderSettings(), providerTestEnvironment) expect(resolveAiSdkProviderDefinition(config)).toMatchObject({ runtimeKind: 'openai-compatible', modelSource: 'openai', @@ -1006,7 +1033,7 @@ describe('basic API-key provider registrations', () => { }) ) global.fetch = fetchMock - const provider = new AiSdkProvider(defaults, createProviderSettings()) + const provider = new AiSdkProvider(defaults, createProviderSettings(), providerTestEnvironment) expect(await provider.fetchModels({ suppressErrors: false })).toEqual([ { @@ -1057,7 +1084,8 @@ describe('basic API-key provider registrations', () => { global.fetch = fetchMock const provider = new AiSdkProvider( { ...defaults, apiKey: 'test-key' }, - createProviderSettings() + createProviderSettings(), + providerTestEnvironment ) expect(await provider.check()).toEqual({ @@ -1159,7 +1187,7 @@ describe('basic API-key provider registrations', () => { global.fetch = fetchMock as unknown as typeof fetch const config = { ...defaults, apiKey: 'test-key' } - const provider = new AiSdkProvider(config, createProviderSettings()) + const provider = new AiSdkProvider(config, createProviderSettings(), providerTestEnvironment) expect(resolveAiSdkProviderDefinition(config)).toMatchObject({ runtimeKind: 'openai-compatible', modelSource: 'openai', @@ -1217,7 +1245,8 @@ describe('basic API-key provider registrations', () => { name: 'AMD GPU Cloud', baseUrl: 'https://developer.amd.com.cn/radeon/api/v1' }), - createProviderSettings() + createProviderSettings(), + providerTestEnvironment ) const models = await provider.fetchModels() @@ -1287,7 +1316,8 @@ describe('basic API-key provider registrations', () => { name: 'OrcaRouter', baseUrl: 'https://api.orcarouter.ai/v1' }), - createProviderSettings() + createProviderSettings(), + providerTestEnvironment ) const models = await provider.fetchModels() @@ -1340,7 +1370,8 @@ describe('basic API-key provider registrations', () => { name: 'Synthorai', baseUrl: 'https://synthorai.io/v1' }), - createProviderSettings() + createProviderSettings(), + providerTestEnvironment ) const models = await provider.fetchModels() @@ -1379,7 +1410,8 @@ describe('basic API-key provider registrations', () => { name: 'Modelsell', baseUrl: 'https://modelsell.com/v1' }), - createProviderSettings() + createProviderSettings(), + providerTestEnvironment ) const models = await provider.fetchModels() @@ -1424,7 +1456,8 @@ describe('basic API-key provider registrations', () => { name: 'OpenRouter', baseUrl: 'https://openrouter.ai/api/v1' }), - createProviderSettings() + createProviderSettings(), + providerTestEnvironment ) const models = await provider.fetchModels() @@ -1474,7 +1507,8 @@ describe('basic API-key provider registrations', () => { name: providerId, baseUrl: `https://${providerId}.example/v1` }), - createProviderSettings() + createProviderSettings(), + providerTestEnvironment ) const models = await provider.fetchModels() @@ -1511,7 +1545,8 @@ describe('basic API-key provider registrations', () => { name: 'GreenPT', baseUrl: 'https://api.greenpt.ai/v1' }), - createProviderSettings() + createProviderSettings(), + providerTestEnvironment ) const models = await provider.fetchModels() @@ -1588,7 +1623,8 @@ describe('basic API-key provider registrations', () => { name: 'OpenCode Go', baseUrl: 'https://opencode.ai/zen/go/v1' }), - createProviderSettings() + createProviderSettings(), + providerTestEnvironment ) const models = await provider.fetchModels() @@ -1653,7 +1689,11 @@ describe('basic API-key provider registrations', () => { ] }) - const provider = new AiSdkProvider(createProvider({}), createProviderSettings()) + const provider = new AiSdkProvider( + createProvider({}), + createProviderSettings(), + providerTestEnvironment + ) const models = await provider.fetchModels() expect(mockGetProvider).toHaveBeenCalledWith('nvidia') @@ -1698,7 +1738,8 @@ describe('basic API-key provider registrations', () => { name: 'StepFun Token Plan', baseUrl: 'https://api.stepfun.com/step_plan/v1' }), - createProviderSettings() + createProviderSettings(), + providerTestEnvironment ) const models = await provider.fetchModels() @@ -1721,7 +1762,8 @@ describe('basic API-key provider registrations', () => { name: 'StepFun Token Plan', baseUrl: 'https://api.stepfun.com/step_plan/v1' }), - createProviderSettings() + createProviderSettings(), + providerTestEnvironment ) ;(provider as any).isInitialized = true @@ -1758,7 +1800,7 @@ describe('basic API-key provider registrations', () => { credentialStrategy: 'api-key', checkModelId: 'deepseek-ai/DeepSeek-V4.1-Flash' }) - const provider = new AiSdkProvider(config, createProviderSettings()) + const provider = new AiSdkProvider(config, createProviderSettings(), providerTestEnvironment) ;(provider as any).isInitialized = true await expect(provider.check()).resolves.toEqual({ isOk: true, errorMsg: null }) @@ -1792,7 +1834,7 @@ describe('basic API-key provider registrations', () => { embeddingStrategy: 'none', checkModelId: 'deepseek-v4.1-flash' }) - const provider = new AiSdkProvider(config, createProviderSettings()) + const provider = new AiSdkProvider(config, createProviderSettings(), providerTestEnvironment) ;(provider as any).isInitialized = true await expect(provider.check()).resolves.toEqual({ isOk: true, errorMsg: null }) @@ -1826,7 +1868,7 @@ describe('basic API-key provider registrations', () => { embeddingStrategy: 'none', checkModelId: 'deepseek-v4.1-flash' }) - const provider = new AiSdkProvider(config, createProviderSettings()) + const provider = new AiSdkProvider(config, createProviderSettings(), providerTestEnvironment) ;(provider as any).isInitialized = true await expect(provider.check()).resolves.toEqual({ isOk: true, errorMsg: null }) @@ -1873,7 +1915,7 @@ describe('basic API-key provider registrations', () => { }) ) global.fetch = fetchMock - const provider = new AiSdkProvider(defaults, createProviderSettings()) + const provider = new AiSdkProvider(defaults, createProviderSettings(), providerTestEnvironment) const models = await provider.fetchModels({ suppressErrors: false }) expect(models.map(({ id }) => id)).toEqual([ @@ -1913,7 +1955,7 @@ describe('basic API-key provider registrations', () => { embeddingStrategy: 'none', checkModelId: 'meta-llama/llama-3.3-70b-instruct' }) - const provider = new AiSdkProvider(config, createProviderSettings()) + const provider = new AiSdkProvider(config, createProviderSettings(), providerTestEnvironment) ;(provider as any).isInitialized = true await expect(provider.check()).resolves.toEqual({ isOk: true, errorMsg: null }) @@ -1953,7 +1995,7 @@ describe('basic API-key provider registrations', () => { }) ) global.fetch = fetchMock - const provider = new AiSdkProvider(defaults, createProviderSettings()) + const provider = new AiSdkProvider(defaults, createProviderSettings(), providerTestEnvironment) expect(await provider.fetchModels({ suppressErrors: false })).toEqual([ { @@ -2018,7 +2060,7 @@ describe('basic API-key provider registrations', () => { }) ) global.fetch = fetchMock - const provider = new AiSdkProvider(defaults, createProviderSettings()) + const provider = new AiSdkProvider(defaults, createProviderSettings(), providerTestEnvironment) expect(await provider.fetchModels({ suppressErrors: false })).toEqual([ { @@ -2049,7 +2091,8 @@ describe('basic API-key provider registrations', () => { name: 'OpenCode Go', baseUrl: 'https://opencode.ai/zen/go/v1' }), - createProviderSettings() + createProviderSettings(), + providerTestEnvironment ) ;(provider as any).isInitialized = true @@ -2082,7 +2125,8 @@ describe('basic API-key provider registrations', () => { name: 'OpenCode Go', baseUrl: 'https://opencode.ai/zen/go/v1' }), - providerSettings + providerSettings, + providerTestEnvironment ) ;(provider as any).isInitialized = true const resolveRouteDecision = vi.spyOn(provider as any, 'resolveRouteDecision') @@ -2112,7 +2156,13 @@ describe('basic API-key provider registrations', () => { endpointType: 'image-generation' }) vi.mocked(imageProviderSettings.getModelConfig).mockReturnValue({ - endpointType: 'image-generation' + endpointType: 'image-generation', + maxTokens: 4096, + contextLength: 32000, + vision: false, + functionCall: false, + reasoning: false, + type: ModelType.Chat }) const imageProvider = new AiSdkProvider( createProvider({ @@ -2121,9 +2171,14 @@ describe('basic API-key provider registrations', () => { apiType: 'new-api', baseUrl: 'https://new-api.example.com' }), - imageProviderSettings + imageProviderSettings, + providerTestEnvironment + ) + const compatibleProvider = new AiSdkProvider( + createProvider({}), + createProviderSettings(), + providerTestEnvironment ) - const compatibleProvider = new AiSdkProvider(createProvider({}), createProviderSettings()) ;(imageProvider as any).isInitialized = false ;(compatibleProvider as any).isInitialized = false @@ -2149,7 +2204,8 @@ describe('basic API-key provider registrations', () => { name: 'OpenCode Go', baseUrl: 'https://opencode.ai/zen/go/v1' }), - createProviderSettings() + createProviderSettings(), + providerTestEnvironment ) ;(provider as any).isInitialized = true @@ -2181,7 +2237,8 @@ describe('basic API-key provider registrations', () => { apiType: 'anthropic', baseUrl: 'https://api.minimax.io/anthropic/v1' }), - createProviderSettings() + createProviderSettings(), + providerTestEnvironment ) ;(provider as any).isInitialized = true diff --git a/test/main/provider/catalog/catalogWriterGuard.test.ts b/test/main/provider/catalog/catalogWriterGuard.test.ts index ec69805173..1662a619ae 100644 --- a/test/main/provider/catalog/catalogWriterGuard.test.ts +++ b/test/main/provider/catalog/catalogWriterGuard.test.ts @@ -30,11 +30,18 @@ const filesCalling = (sourceRoot: string, needle: string): string[] => { describe('provider catalog architecture guard', () => { it('persists provider catalogs only through the catalog service and its settings layer', () => { - expect(filesCalling('src/main', 'setProviderModels(')).toEqual([ - 'src/main/provider/providerModelHelper.ts', - 'src/main/provider/settings.ts' - ]) - expect(filesCalling('src/main', 'applyProviderCatalogUpdate(')).toEqual([ + expect( + [ + ...filesCalling('src/main', 'setProviderModels('), + ...filesCalling('src/harness', 'setProviderModels(') + ].sort() + ).toEqual(['src/main/provider/providerModelHelper.ts', 'src/main/provider/settings.ts']) + expect( + [ + ...filesCalling('src/main', 'applyProviderCatalogUpdate('), + ...filesCalling('src/harness', 'applyProviderCatalogUpdate(') + ].sort() + ).toEqual([ 'src/main/provider/catalog/providerCatalogService.ts', 'src/main/provider/settings.ts' ]) diff --git a/test/main/provider/catalog/providerCatalogService.test.ts b/test/main/provider/catalog/providerCatalogService.test.ts index 98718d9f9c..fab62e4baf 100644 --- a/test/main/provider/catalog/providerCatalogService.test.ts +++ b/test/main/provider/catalog/providerCatalogService.test.ts @@ -4,8 +4,10 @@ import type { ProviderCatalogState, ProviderCatalogStatePatch } from '@shared/ty import { ProviderCatalogService, type ProviderCatalogSettingsPort, - type ProviderCatalogSourcePort + type ProviderCatalogSourcePort, + type ProviderCatalogServiceDeps } from '@/provider/catalog/providerCatalogService' +import { DatabaseRequestGate } from '@/app/databaseRequestGate' type CatalogStateOptions = { bumpRevision?: boolean } @@ -16,6 +18,7 @@ const createHarness = (options: { dbModels?: Record modelStatus?: Record now?: () => number + runRefresh?: ProviderCatalogServiceDeps['runRefresh'] }) => { const providerModels = structuredClone(options.providerModels ?? {}) const customModels = structuredClone(options.customModels ?? {}) @@ -98,7 +101,8 @@ const createHarness = (options: { settings, source, publishEvent: publishEvent as never, - now + now, + runRefresh: options.runRefresh }) return { @@ -135,6 +139,32 @@ const model = (id: string, overrides: Partial = {}): MODEL_META => ( }) describe('ProviderCatalogService', () => { + it('drains detached refresh writes before database maintenance', async () => { + const gate = new DatabaseRequestGate(() => {}) + const harness = createHarness({ + providers: [provider('new-api')], + runRefresh: (operation) => gate.runRequest('providers.catalog.refresh', operation) + }) + let resolveFetch!: (models: MODEL_META[]) => void + vi.mocked(harness.source.fetchCatalog).mockImplementation( + () => new Promise((resolve) => (resolveFetch = resolve)) + ) + expect(harness.service.ensureFresh('new-api')).toBe(true) + gate.beginMaintenance() + let drained = false + const drain = gate.drain().then(() => { + drained = true + }) + await Promise.resolve() + expect(drained).toBe(false) + expect(harness.settings.applyProviderCatalogUpdate).not.toHaveBeenCalled() + resolveFetch([]) + await drain + expect(harness.settings.applyProviderCatalogUpdate).toHaveBeenCalledOnce() + await expect(harness.service.refresh('new-api')).rejects.toThrow('maintenance') + expect(harness.source.fetchCatalog).toHaveBeenCalledOnce() + }) + it('projects merged catalog sources with status and capability defaults', () => { const mistralDbModel = { id: 'mistral-large', diff --git a/test/main/provider/doubaoProvider.test.ts b/test/main/provider/doubaoProvider.test.ts index cf8d5cb9fd..2c815b69c9 100644 --- a/test/main/provider/doubaoProvider.test.ts +++ b/test/main/provider/doubaoProvider.test.ts @@ -1,3 +1,4 @@ +import { providerTestEnvironment } from '../../helpers/providerEnvironment' import type { ProviderSettingsPort } from '@/provider/settings' import { beforeEach, describe, expect, it, vi } from 'vitest' import type { LLM_PROVIDER } from '@shared/types/provider' @@ -88,7 +89,11 @@ describe('AiSdkProvider doubao', () => { ] }) - const provider = new AiSdkProvider(createProvider(), createProviderSettings()) + const provider = new AiSdkProvider( + createProvider(), + createProviderSettings(), + providerTestEnvironment + ) const models = await provider.fetchModels() expect(models).toEqual([ diff --git a/test/main/provider/embeddingResponse.test.ts b/test/main/provider/embeddingResponse.test.ts new file mode 100644 index 0000000000..c909e3234f --- /dev/null +++ b/test/main/provider/embeddingResponse.test.ts @@ -0,0 +1,117 @@ +import { describe, expect, it } from 'vitest' +import { createAzure } from '@ai-sdk/azure' +import { createGoogle } from '@ai-sdk/google' +import { createVertex } from '@ai-sdk/google-vertex' +import { createVertex as createVertexEdge } from '@ai-sdk/google-vertex/edge' +import { createOpenAI } from '@ai-sdk/openai' +import { createOpenAICompatible } from '@ai-sdk/openai-compatible' +import type { EmbeddingModelV4 } from '@ai-sdk/provider' + +const openaiBody = (vector: unknown) => ({ data: [{ embedding: vector }] }) +const googleBody = (vector: unknown) => ({ embedding: { values: vector } }) +const googleBatchBody = (vector: unknown) => ({ + embeddings: [{ values: vector }, { values: vector }] +}) +const vertexBody = (vector: unknown) => ({ + predictions: [{ embeddings: { values: vector, statistics: { token_count: 1 } } }] +}) + +const adapters: { + name: string + create: (fetch: typeof globalThis.fetch) => EmbeddingModelV4 + body: (vector: unknown) => unknown + vectors: (body: any) => number[][] + values?: string[] +}[] = [ + { + name: 'OpenAI', + create: (fetch) => createOpenAI({ apiKey: 'fixture', fetch }).embedding('fixture'), + body: openaiBody, + vectors: (body) => body.data.map((item: any) => item.embedding) + }, + { + name: 'OpenAI compatible', + create: (fetch) => + createOpenAICompatible({ + name: 'fixture', + baseURL: 'https://embedding.example/v1', + fetch + }).embeddingModel('fixture'), + body: openaiBody, + vectors: (body) => body.data.map((item: any) => item.embedding) + }, + { + name: 'Azure OpenAI internal model', + create: (fetch) => + createAzure({ resourceName: 'fixture', apiKey: 'fixture', fetch }).embedding('fixture'), + body: openaiBody, + vectors: (body) => body.data.map((item: any) => item.embedding) + }, + { + name: 'Google single', + create: (fetch) => createGoogle({ apiKey: 'fixture', fetch }).embeddingModel('fixture'), + body: googleBody, + vectors: (body) => [body.embedding.values] + }, + { + name: 'Google batch', + create: (fetch) => createGoogle({ apiKey: 'fixture', fetch }).embeddingModel('fixture'), + body: googleBatchBody, + vectors: (body) => body.embeddings.map((item: any) => item.values), + values: ['first', 'second'] + }, + ...[createVertex, createVertexEdge].flatMap((create, index) => [ + { + name: `Vertex ${index === 0 ? 'Node' : 'edge'} predict`, + create: (fetch: typeof globalThis.fetch) => + create({ apiKey: 'fixture', fetch }).embeddingModel('text-embedding-005'), + body: vertexBody, + vectors: (body: any) => body.predictions.map((item: any) => item.embeddings.values) + }, + { + name: `Vertex ${index === 0 ? 'Node' : 'edge'} embedContent`, + create: (fetch: typeof globalThis.fetch) => + create({ apiKey: 'fixture', fetch }).embeddingModel('gemini-embedding-2'), + body: googleBody, + vectors: (body: any) => [body.embedding.values] + } + ]) +] + +describe.each(adapters)('$name embedding response contract', (adapter) => { + function embed(vector: unknown) { + const body = JSON.stringify(adapter.body(vector)) + .replaceAll('"negative-zero"', '-0') + .replaceAll('"overflow"', '1e309') + const model = adapter.create( + async () => new Response(body, { headers: { 'content-type': 'application/json' } }) + ) + return model.doEmbed({ values: adapter.values ?? ['fixture'] }) + } + + it('preserves finite numeric values and separates parsed vectors from the raw response', async () => { + const vector = [0.25, -0.75, 0, -0, Math.fround(1e-40), 3.4e38, Number.MAX_VALUE] + const result = await embed( + vector.map((value) => (Object.is(value, -0) ? 'negative-zero' : value)) + ) + const rawVectors = adapter.vectors(result.response?.body) + expect(result.embeddings).toEqual(rawVectors.map(() => vector)) + for (const [index, embedding] of result.embeddings.entries()) { + expect(embedding).not.toBe(rawVectors[index]) + expect(Object.is(embedding[3], -0)).toBe(true) + embedding[0] = 7 + expect(rawVectors[index][0]).toBe(0.25) + } + }) + + it('preserves empty numeric arrays', async () => { + const result = await embed([]) + expect(result.embeddings).toEqual(adapter.vectors(result.response?.body).map(() => [])) + }) + + it('rejects nonnumeric, nonfinite and nonarray vectors before returning embeddings', async () => { + for (const vector of [null, {}, 1, 'vector', [null], ['0.25'], [[1]], [true], ['overflow']]) { + await expect(embed(vector)).rejects.toThrow() + } + }) +}) diff --git a/test/main/provider/geminiProvider.test.ts b/test/main/provider/geminiProvider.test.ts index 8a171acd27..684ea7aa5b 100644 --- a/test/main/provider/geminiProvider.test.ts +++ b/test/main/provider/geminiProvider.test.ts @@ -1,3 +1,4 @@ +import { providerTestEnvironment } from '../../helpers/providerEnvironment' import type { ProviderSettingsPort } from '@/provider/settings' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' import type { LLM_PROVIDER } from '@shared/types/provider' @@ -108,7 +109,8 @@ describe('AiSdkProvider gemini', () => { custom: true, baseUrl: 'https://generativelanguage.googleapis.com/v1' }), - createProviderSettings() + createProviderSettings(), + providerTestEnvironment ) const models = await provider.fetchModels() @@ -148,7 +150,8 @@ describe('AiSdkProvider gemini', () => { name: 'Custom Gemini', custom: true }), - createProviderSettings() + createProviderSettings(), + providerTestEnvironment ) await expect(provider.fetchCatalog()).rejects.toThrow('API key not valid') diff --git a/test/main/provider/githubCopilotDeviceFlow.test.ts b/test/main/provider/githubCopilotDeviceFlow.test.ts index 35e194af51..bc1bbe714d 100644 --- a/test/main/provider/githubCopilotDeviceFlow.test.ts +++ b/test/main/provider/githubCopilotDeviceFlow.test.ts @@ -1,24 +1,98 @@ import { afterEach, describe, expect, it, vi } from 'vitest' +import type { EventEmitter } from 'node:events' +import { setImmediate as nextTurn, setTimeout as wait } from 'node:timers/promises' -import { - disposeGlobalGitHubCopilotDeviceFlow, - getGitHubCopilotToken, - getGlobalGitHubCopilotDeviceFlow -} from '@/provider/auth/githubCopilotDeviceFlow' +import { createGitHubCopilotDeviceFlow } from '@/provider/auth/githubCopilotDeviceFlow' +import { GitHubCopilotOAuth } from '@/provider/auth/githubCopilotOAuth' +import { getGitHubCopilotToken } from '@/provider/auth/githubCopilotToken' import { extractProviderFailureMetadata } from '@/provider/providerFailure' -vi.mock('electron', () => ({ - BrowserWindow: vi.fn(function BrowserWindow() {}), - clipboard: { writeText: vi.fn() }, - shell: { openExternal: vi.fn() } +import { clipboard, shell } from 'electron' + +type AuthWindow = { + webContents: EventEmitter & { + executeJavaScript: ReturnType + setWindowOpenHandler: ReturnType + } + loadURL: ReturnType + destroy: ReturnType + finishClose(): void +} +const native = vi.hoisted(() => ({ + windows: [] as AuthWindow[], + closeDataWindow: true, + holdClosed: false })) +const controllers = new Set() +const createController = () => { + const controller = new AbortController() + controllers.add(controller) + return controller +} +vi.mock('electron', async () => { + const { EventEmitter } = await import('node:events') + class Window extends EventEmitter { + private closed = false + webContents = Object.assign(new EventEmitter(), { + executeJavaScript: vi.fn(async () => undefined), + setWindowOpenHandler: vi.fn() + }) + loadURL = vi.fn(async (url: string) => { + if (native.closeDataWindow && url.startsWith('data:')) + queueMicrotask(() => this.finishClose()) + }) + show = vi.fn() + isDestroyed = () => this.closed + destroy = vi.fn(() => { + if (!native.holdClosed) this.finishClose() + }) + finishClose() { + if (this.closed) return + this.closed = true + this.emit('closed') + } + constructor() { + super() + native.windows.push(this) + } + } + return { + BrowserWindow: Window, + clipboard: { writeText: vi.fn() }, + shell: { openExternal: vi.fn(async () => {}) } + } +}) + +const deviceCode = () => + Response.json({ + device_code: 'device-code', + user_code: 'ABCD-EFGH', + verification_uri: 'https://github.com/login/device', + expires_in: 900, + interval: 1 + }) +const browser = () => + new GitHubCopilotOAuth({ + clientId: 'client', + clientSecret: 'secret', + redirectUri: 'https://callback.example/oauth', + scope: 'read:user read:org' + }) describe('GitHub Copilot token exchange', () => { afterEach(() => { + for (const controller of controllers) controller.abort() + controllers.clear() + for (const window of native.windows) window.finishClose() + native.windows.length = 0 + native.holdClosed = false + native.closeDataWindow = true + vi.useRealTimers() vi.unstubAllGlobals() vi.unstubAllEnvs() - disposeGlobalGitHubCopilotDeviceFlow() vi.restoreAllMocks() + vi.mocked(shell.openExternal).mockReset().mockResolvedValue(undefined) + vi.mocked(clipboard.writeText).mockClear() }) it.each([ @@ -30,23 +104,15 @@ describe('GitHub Copilot token exchange', () => { 'requests a device code with the resolved client (%s, %s)', async (override, environment, expected) => { vi.stubEnv('VITE_GITHUB_CLIENT_ID', environment) - const fetchMock = vi.fn().mockResolvedValue( - Response.json({ - device_code: 'device-code', - user_code: 'ABCD-EFGH', - verification_uri: 'https://github.com/login/device', - expires_in: 900, - interval: 5 - }) + const fetchMock = vi.fn(async (url: string) => + url.endsWith('/device/code') ? deviceCode() : Response.json({ access_token: 'oauth-token' }) ) vi.stubGlobal('fetch', fetchMock) - const flow = getGlobalGitHubCopilotDeviceFlow(override) - vi.spyOn(flow as any, 'showUserCodeAndOpenBrowser').mockResolvedValue(undefined) - vi.spyOn(flow as any, 'pollForAccessToken').mockResolvedValue('oauth-token') + const flow = createGitHubCopilotDeviceFlow(override) + await expect(flow.startDeviceFlow(createController().signal)).resolves.toBe('oauth-token') - await expect(flow.startDeviceFlow()).resolves.toBe('oauth-token') - - expect(fetchMock).toHaveBeenCalledWith( + expect(fetchMock).toHaveBeenNthCalledWith( + 1, 'https://github.com/login/device/code', expect.objectContaining({ body: JSON.stringify({ client_id: expected, scope: 'read:user read:org' }) @@ -55,6 +121,179 @@ describe('GitHub Copilot token exchange', () => { } ) + it.each(['browser', 'device'] as const)( + '%s authorization waits for physical window and accepted browser effects after abort', + async (kind) => { + native.closeDataWindow = false + native.holdClosed = true + vi.stubGlobal( + 'fetch', + vi.fn(async () => deviceCode()) + ) + const controller = createController() + const reason = { source: 'owner-retirement' } + const external = Promise.withResolvers() + vi.mocked(shell.openExternal).mockReturnValue(external.promise) + const operation = + kind === 'browser' + ? browser().startLogin(controller.signal) + : createGitHubCopilotDeviceFlow().startDeviceFlow(controller.signal) + const outcome = Promise.allSettled([operation]) + let settled = false + void outcome.then(() => { + settled = true + }) + await vi.waitFor(() => expect(native.windows).toHaveLength(1)) + const window = native.windows[0] + if (kind === 'device') { + window.webContents.executeJavaScript.mockReturnValue(new Promise(() => {})) + window.webContents.emit('dom-ready') + window.webContents.emit( + 'console-message', + {}, + { + message: JSON.stringify({ type: 'open-external', url: 'file:///unapproved' }) + } + ) + window.webContents.emit( + 'console-message', + {}, + 0, + JSON.stringify({ type: 'copy-to-clipboard', text: 'unapproved' }) + ) + window.webContents.emit( + 'console-message', + {}, + 0, + JSON.stringify({ type: 'copy-to-clipboard', text: 'ABCD-EFGH' }) + ) + await vi.waitFor(() => + expect(shell.openExternal).toHaveBeenCalledExactlyOnceWith( + 'https://github.com/login/device' + ) + ) + expect(clipboard.writeText).toHaveBeenCalledExactlyOnceWith('ABCD-EFGH') + } + controller.abort(reason) + await vi.waitFor(() => expect(window.destroy).toHaveBeenCalledOnce()) + await nextTurn() + expect(settled).toBe(false) + window.finishClose() + await nextTurn() + if (kind === 'device') expect(settled).toBe(false) + external.resolve() + expect((await outcome)[0]).toEqual({ status: 'rejected', reason }) + const calls = vi.mocked(shell.openExternal).mock.calls.length + window.webContents.emit('console-message', {}, 0, JSON.stringify({ type: 'open-external' })) + await nextTurn() + expect(vi.mocked(shell.openExternal).mock.calls).toHaveLength(calls) + } + ) + + it.each([false, true])('requires the exact callback and state; accepted=%s', async (accepted) => { + const controller = createController() + const operation = browser().startLogin(controller.signal) + const outcome = Promise.allSettled([operation]) + const window = native.windows[0] + const state = new URL(String(window.loadURL.mock.calls[0][0])).searchParams.get('state')! + const navigation = { preventDefault: vi.fn() } + for (const url of [ + 'https://callback.example/oauth-suffix', + 'https://callback.example.evil/oauth' + ]) + window.webContents.emit('will-redirect', navigation, `${url}?code=foreign&state=${state}`) + expect(navigation.preventDefault).not.toHaveBeenCalled() + expect(window.destroy).not.toHaveBeenCalled() + window.webContents.emit( + 'will-redirect', + navigation, + `https://callback.example/oauth?code=auth-code&state=${accepted ? state : 'foreign'}` + ) + const result = (await outcome)[0] + if (accepted) expect(result).toEqual({ status: 'fulfilled', value: 'auth-code' }) + else + expect(result).toMatchObject({ + status: 'rejected', + reason: { + message: 'Security verification failed: state parameter mismatch' + } + }) + expect(navigation.preventDefault).toHaveBeenCalledOnce() + expect(window.destroy).toHaveBeenCalledOnce() + }) + + it( + 'serializes device polling and applies the server slowdown interval', + { timeout: 20_000 }, + async () => { + const first = Promise.withResolvers() + const polls = vi.fn() + const entered = Promise.withResolvers() + const requests: number[] = [] + vi.stubGlobal( + 'fetch', + vi.fn(async (url: string) => { + if (url.endsWith('/device/code')) return deviceCode() + polls() + requests.push(performance.now()) + entered.resolve() + return polls.mock.calls.length === 1 + ? await first.promise + : Response.json({ access_token: 'oauth-token' }) + }) + ) + const login = createGitHubCopilotDeviceFlow().startDeviceFlow(createController().signal) + void login.catch(() => {}) + await entered.promise + expect(polls).toHaveBeenCalledOnce() + await wait(1200) + expect(polls).toHaveBeenCalledOnce() + const slowdown = performance.now() + first.resolve(Response.json({ error: 'slow_down', interval: 6 })) + await expect(login).resolves.toBe('oauth-token') + expect(polls).toHaveBeenCalledTimes(2) + expect(requests[1] - slowdown).toBeGreaterThanOrEqual(6000) + } + ) + + it('awaits actual response-body cancellation before acknowledging an aborted exchange', async () => { + const canceled = Promise.withResolvers() + const retired = Promise.withResolvers() + const cancel = vi.fn(async () => { + canceled.resolve() + await retired.promise + }) + const response = new Response( + new ReadableStream({ + start(controller) { + controller.enqueue(new TextEncoder().encode('{"access_token":')) + }, + cancel + }) + ) + const fetchMock = vi.fn(async () => response) + vi.stubGlobal('fetch', fetchMock) + const controller = createController() + const reason = { source: 'native-auth-shutdown' } + const outcome = Promise.allSettled([browser().exchangeCodeForToken('code', controller.signal)]) + let settled = false + void outcome.then(() => { + settled = true + }) + await nextTurn() + controller.abort(reason) + await canceled.promise + await nextTurn() + expect(settled).toBe(false) + retired.resolve() + expect((await outcome)[0]).toEqual({ status: 'rejected', reason }) + expect(cancel).toHaveBeenCalledOnce() + expect(fetchMock).toHaveBeenCalledWith( + 'https://github.com/login/oauth/access_token', + expect.objectContaining({ signal: controller.signal }) + ) + }) + it('forwards the exact caller signal to the Copilot token exchange', async () => { const fetchMock = vi.fn().mockImplementation((_url: string, options?: RequestInit) => { return new Promise((_resolve, reject) => { @@ -65,7 +304,7 @@ describe('GitHub Copilot token exchange', () => { }) }) vi.stubGlobal('fetch', fetchMock) - const controller = new AbortController() + const controller = createController() const reason = { source: 'memory-caller' } const token = getGitHubCopilotToken('oauth-token', controller.signal) @@ -82,12 +321,17 @@ describe('GitHub Copilot token exchange', () => { it('does not start a token exchange for a pre-aborted request', async () => { const fetchMock = vi.fn() vi.stubGlobal('fetch', fetchMock) - const controller = new AbortController() + const controller = createController() const reason = new DOMException('Already cancelled', 'AbortError') controller.abort(reason) await expect(getGitHubCopilotToken('oauth-token', controller.signal)).rejects.toBe(reason) + await expect(browser().startLogin(controller.signal)).rejects.toBe(reason) + await expect(createGitHubCopilotDeviceFlow().startDeviceFlow(controller.signal)).rejects.toBe( + reason + ) expect(fetchMock).not.toHaveBeenCalled() + expect(native.windows).toHaveLength(0) }) it('exchanges the current OAuth credential using the GitHub token protocol', async () => { diff --git a/test/main/provider/githubCopilotOAuthService.test.ts b/test/main/provider/githubCopilotOAuthService.test.ts index de0d55168f..43872d5053 100644 --- a/test/main/provider/githubCopilotOAuthService.test.ts +++ b/test/main/provider/githubCopilotOAuthService.test.ts @@ -1,137 +1,226 @@ import { afterEach, describe, expect, it, vi } from 'vitest' -import { OAuthService } from '@/provider/auth' -import type { LLM_PROVIDER } from '@shared/types/provider' +import { setImmediate as nextTurn } from 'node:timers/promises' +import { OAuthService } from '../../../src/harness/core/oauth' +import type { GitHubCopilotSignInTarget } from '@shared/types/oauth' const mocks = vi.hoisted(() => ({ startDeviceFlow: vi.fn(), startLogin: vi.fn(), - exchangeCodeForToken: vi.fn() + exchangeCodeForToken: vi.fn(), + stop: vi.fn() })) -vi.mock('@/provider/auth/githubCopilotDeviceFlow', async (importOriginal) => ({ - ...(await importOriginal()), - getGlobalGitHubCopilotDeviceFlow: () => ({ startDeviceFlow: mocks.startDeviceFlow }) -})) -vi.mock('@/provider/auth/githubCopilotOAuth', () => ({ - createGitHubCopilotOAuth: () => ({ - startLogin: mocks.startLogin, - exchangeCodeForToken: mocks.exchangeCodeForToken +const accepted = () => Response.json({ token: 'copilot-session', expires_at: 2_000_000_000 }) +const setup = () => { + const target: GitHubCopilotSignInTarget = { + providerId: 'github-copilot', + clientId: 'test-client', + configurationId: 'opaque-configuration-identity' + } + const owner = { + getGitHubCopilotSignInTarget: vi.fn(async () => target), + commitGitHubCopilotCredential: vi.fn(async () => {}), + invalidateGitHubCopilotSignIns: vi.fn(async () => {}) + } + mocks.startDeviceFlow.mockResolvedValue('new-oauth-token') + mocks.startLogin.mockResolvedValue('auth-code') + mocks.exchangeCodeForToken.mockResolvedValue('new-oauth-token') + const signedOut = async () => ({ + state: 'signed-out' as const, + authenticated: false, + storage: 'none' as const }) -})) -vi.mock('@/provider/auth/openaiCodex', () => ({ - initializeGlobalOpenAICodexAuth: vi.fn() -})) -vi.mock('@/provider/auth/xaiGrok', () => ({ - initializeGlobalXaiGrokAuth: vi.fn() -})) + const accounts = { + getOpenAICodexStatus: vi.fn(signedOut), + startOpenAICodexBrowserLogin: vi.fn(signedOut), + completeOpenAICodexBrowserLoginFromUrl: vi.fn(async (_url: string) => signedOut()), + cancelOpenAICodexLogin: vi.fn(signedOut), + logoutOpenAICodex: vi.fn(signedOut), + getXaiGrokStatus: vi.fn(signedOut), + startXaiGrokDeviceLogin: vi.fn(signedOut), + cancelXaiGrokLogin: vi.fn(signedOut), + logoutXaiGrok: vi.fn(signedOut), + stop: mocks.stop + } + const service = new OAuthService(owner, accounts, { + device: async () => await mocks.startDeviceFlow(), + browser: async () => { + const code = await mocks.startLogin() + return await mocks.exchangeCodeForToken(code) + } + }) + return { target, owner, service, accounts } +} -describe('GitHub Copilot login validation', () => { +describe('GitHub Copilot login owner commands', () => { afterEach(() => { vi.unstubAllGlobals() vi.resetAllMocks() }) - const setup = () => { - const provider: LLM_PROVIDER = { - id: 'github-copilot', - name: 'GitHub Copilot', - apiType: 'github-copilot', - apiKey: 'saved-oauth-token', - baseUrl: 'https://api.githubcopilot.com', - enable: true + it.each([false, true])( + 'drains accepted authorization and account stop when owner fails=%s', + async (fails) => { + const { owner, service, accounts } = setup() + const stopped = Promise.withResolvers() + const entered = Promise.withResolvers() + const authorization = Promise.withResolvers() + mocks.startDeviceFlow.mockImplementation(async () => { + entered.resolve() + return await authorization.promise + }) + const login = Promise.allSettled([ + service.startGitHubCopilotDeviceFlowLogin('github-copilot') + ]) + await entered.promise + accounts.stop.mockReturnValue(stopped.promise) + let acknowledged = false + const stopping = service.stop() + void stopping.then( + () => { + acknowledged = true + }, + () => { + acknowledged = true + } + ) + const result = fails + ? expect(stopping).rejects.toThrow('Account stop failed') + : expect(stopping).resolves.toBeUndefined() + await Promise.resolve() + expect(acknowledged).toBe(false) + expect(owner.invalidateGitHubCopilotSignIns).toHaveBeenCalledOnce() + if (fails) stopped.reject(new Error('Account stop failed')) + else stopped.resolve() + await nextTurn() + expect(acknowledged).toBe(false) + authorization.resolve('late-token') + expect((await login)[0]).toMatchObject({ + status: 'rejected', + reason: { + message: 'OAuth service is stopped' + } + }) + await result + await expect(service.startGitHubCopilotDeviceFlowLogin('github-copilot')).rejects.toThrow( + 'stopped' + ) + expect(mocks.startDeviceFlow).toHaveBeenCalledOnce() } - mocks.startDeviceFlow.mockResolvedValue('new-oauth-token') - mocks.startLogin.mockResolvedValue('auth-code') - mocks.exchangeCodeForToken.mockResolvedValue('new-oauth-token') - const setProviderById = vi.fn() - const getProviderById = vi.fn((_providerId: string): LLM_PROVIDER | undefined => ({ - ...provider - })) - const service = new OAuthService({ getProviderById, setProviderById }, vi.fn()) - return { provider, service, setProviderById, getProviderById } - } + ) for (const method of ['startGitHubCopilotDeviceFlowLogin', 'startGitHubCopilotLogin'] as const) { - it(`${method} saves credentials only after Copilot accepts them`, async () => { - const { provider, service, setProviderById } = setup() + it(`${method} validates Copilot access before submitting the owner mutation`, async () => { + const { target, owner, service } = setup() const fetchMock = vi.fn(async (_url: string) => { - expect(setProviderById).not.toHaveBeenCalled() - return Response.json({ token: 'copilot-session', expires_at: 2_000_000_000 }) + expect(owner.commitGitHubCopilotCredential).not.toHaveBeenCalled() + return accepted() }) vi.stubGlobal('fetch', fetchMock) - - await expect(service[method](provider.id)).resolves.toBe(true) - - expect(fetchMock).toHaveBeenCalledOnce() + await expect(service[method](target.providerId)).resolves.toBe(true) + expect(owner.getGitHubCopilotSignInTarget).toHaveBeenCalledWith(target.providerId) expect(fetchMock.mock.calls[0][0]).toBe('https://api.github.com/copilot_internal/v2/token') - expect(setProviderById).toHaveBeenCalledWith(provider.id, { - ...provider, - apiKey: 'new-oauth-token' - }) - expect(provider.apiKey).toBe('saved-oauth-token') + expect(owner.commitGitHubCopilotCredential).toHaveBeenCalledWith(target, 'new-oauth-token') }) - it(`${method} surfaces Copilot rejection without replacing saved credentials`, async () => { - const { provider, service, setProviderById } = setup() - const fetchMock = vi.fn().mockResolvedValue( - Response.json( - { error_details: { message: 'Copilot access denied', code: 'not_authorized' } }, - { - status: 403, - statusText: 'Forbidden', - headers: { 'x-github-request-id': 'ABCD:1234' } - } - ) - ) - vi.stubGlobal('fetch', fetchMock) - - await expect(service[method](provider.id)).rejects.toThrow( - 'GitHub Copilot token exchange failed: 403 Forbidden - Copilot access denied; code=not_authorized; request_id=ABCD:1234' + it(`${method} waits for actual credential acknowledgement and propagates persistence failure`, async () => { + const { target, owner, service } = setup() + const writing = Promise.withResolvers() + const entered = Promise.withResolvers() + owner.commitGitHubCopilotCredential.mockImplementation(async () => { + entered.resolve() + await writing.promise + }) + vi.stubGlobal( + 'fetch', + vi.fn(async () => accepted()) ) - expect(fetchMock).toHaveBeenCalledOnce() - expect(setProviderById).not.toHaveBeenCalled() - expect(provider.apiKey).toBe('saved-oauth-token') + let completed = false + const login = service[method](target.providerId).then(() => { + completed = true + }) + const rejected = expect(login).rejects.toThrow('persistence failed') + await entered.promise + expect(completed).toBe(false) + writing.reject(new Error('persistence failed')) + await rejected }) - it.each(['credentials', 'client', 'protocol', 'removal'] as const)( - `${method} rejects stale authentication after a provider change: %s`, - async (change) => { - const { provider, service, setProviderById, getProviderById } = setup() - vi.stubGlobal( - 'fetch', - vi.fn(async () => { - if (change === 'credentials') provider.apiKey = 'newer-oauth-token' - if (change === 'client') provider.copilotClientId = 'new-client' - if (change === 'protocol') provider.apiType = 'openai' - if (change === 'removal') getProviderById.mockReturnValue(undefined) - return Response.json({ token: 'copilot-session', expires_at: 2_000_000_000 }) - }) + it(`${method} preserves saved credentials when Copilot refuses access`, async () => { + const { target, owner, service } = setup() + vi.stubGlobal( + 'fetch', + vi.fn(async () => + Response.json( + { error_details: { message: 'Copilot access denied', code: 'not_authorized' } }, + { + status: 403, + statusText: 'Forbidden', + headers: { 'x-github-request-id': 'ABCD:1234' } + } + ) ) + ) + await expect(service[method](target.providerId)).rejects.toThrow('Copilot access denied') + expect(owner.commitGitHubCopilotCredential).not.toHaveBeenCalled() + }) - await expect(service[method](provider.id)).rejects.toThrow( - 'GitHub Copilot configuration changed during sign-in' - ) - expect(setProviderById).not.toHaveBeenCalled() - } - ) + it(`${method} cannot open a login after a pending owner query is stopped`, async () => { + const { target, owner, service } = setup() + const reading = Promise.withResolvers() + owner.getGitHubCopilotSignInTarget.mockReturnValue(reading.promise) + const login = service[method](target.providerId) + const rejected = expect(login).rejects.toThrow('stopped') + await vi.waitFor(() => expect(owner.getGitHubCopilotSignInTarget).toHaveBeenCalledOnce()) + const stopping = service.stop() + reading.resolve(target) + await rejected + await stopping + expect(owner.invalidateGitHubCopilotSignIns).toHaveBeenCalledOnce() + expect(mocks.startDeviceFlow).not.toHaveBeenCalled() + expect(mocks.startLogin).not.toHaveBeenCalled() + expect(owner.commitGitHubCopilotCredential).not.toHaveBeenCalled() + }) - it(`${method} preserves unrelated settings changed during authentication`, async () => { - const { provider, service, setProviderById } = setup() + it(`${method} aborts token validation and rejects a late transport result after stop`, async () => { + const { target, owner, service } = setup() + const response = Promise.withResolvers() + const entered = Promise.withResolvers() vi.stubGlobal( 'fetch', - vi.fn(async () => { - provider.name = 'Renamed Copilot' - provider.enable = false - provider.customHeaders = { 'X-Custom': 'current' } - return Response.json({ token: 'copilot-session', expires_at: 2_000_000_000 }) + vi.fn(async (_url, init: RequestInit) => { + entered.resolve(init.signal!) + return await response.promise }) ) - - await expect(service[method](provider.id)).resolves.toBe(true) - expect(setProviderById).toHaveBeenCalledWith(provider.id, { - ...provider, - apiKey: 'new-oauth-token' + const login = service[method](target.providerId) + const rejected = Promise.allSettled([login]) + const signal = await entered.promise + const stopping = service.stop() + expect(signal.aborted).toBe(true) + const canceled = Promise.withResolvers() + const retired = Promise.withResolvers() + response.resolve( + new Response( + new ReadableStream({ + cancel: async () => { + canceled.resolve() + await retired.promise + } + }) + ) + ) + await canceled.promise + let settled = false + void stopping.then(() => { + settled = true }) + await nextTurn() + expect(settled).toBe(false) + retired.resolve() + expect((await rejected)[0]).toEqual({ status: 'rejected', reason: signal.reason }) + await stopping + expect(owner.commitGitHubCopilotCredential).not.toHaveBeenCalled() }) } }) diff --git a/test/main/provider/githubCopilotProvider.test.ts b/test/main/provider/githubCopilotProvider.test.ts index 8b575b2853..0eb89d9d4f 100644 --- a/test/main/provider/githubCopilotProvider.test.ts +++ b/test/main/provider/githubCopilotProvider.test.ts @@ -3,12 +3,10 @@ import { afterEach, describe, expect, it, vi } from 'vitest' import { GithubCopilotProvider } from '../../../src/main/provider/providers/githubCopilotProvider' import { extractProviderFailureMetadata } from '@/provider/providerFailure' -import { getGitHubCopilotToken } from '../../../src/main/provider/auth/githubCopilotDeviceFlow' +import { getGitHubCopilotToken } from '../../../src/main/provider/auth/githubCopilotToken' -vi.mock('../../../src/main/provider/auth/githubCopilotDeviceFlow', async (importOriginal) => ({ - ...(await importOriginal< - typeof import('../../../src/main/provider/auth/githubCopilotDeviceFlow') - >()), +vi.mock('../../../src/main/provider/auth/githubCopilotToken', async (importOriginal) => ({ + ...(await importOriginal()), getGitHubCopilotToken: vi.fn() })) diff --git a/test/main/provider/jevProvider.test.ts b/test/main/provider/jevProvider.test.ts index 6ffc53fa10..8a375369b2 100644 --- a/test/main/provider/jevProvider.test.ts +++ b/test/main/provider/jevProvider.test.ts @@ -1,3 +1,4 @@ +import { providerTestEnvironment } from '../../helpers/providerEnvironment' import type { ProviderSettingsPort } from '@/provider/settings' import { ModelType } from '@shared/model' import type { LLM_PROVIDER } from '@shared/types/provider' @@ -67,6 +68,8 @@ const createProviderSettings = (): ProviderSettingsPort => const createProviderInstance = (overrides?: Partial) => new JevProvider(createProvider(overrides), createProviderSettings(), { + allowInsecureTls: providerTestEnvironment.allowInsecureTls, + defaultHeaders: providerTestEnvironment.defaultHeaders, getLanguage: vi.fn().mockReturnValue('en-US') }) @@ -183,6 +186,8 @@ describe('JevProvider', () => { createProvider({ models: bundled }), createProviderSettings(), { + allowInsecureTls: providerTestEnvironment.allowInsecureTls, + defaultHeaders: providerTestEnvironment.defaultHeaders, getLanguage: vi.fn().mockReturnValue('en-US') } ) diff --git a/test/main/provider/kimiForCodingProvider.test.ts b/test/main/provider/kimiForCodingProvider.test.ts index 9a68f30eb2..3265e8b526 100644 --- a/test/main/provider/kimiForCodingProvider.test.ts +++ b/test/main/provider/kimiForCodingProvider.test.ts @@ -1,3 +1,4 @@ +import { providerTestEnvironment } from '../../helpers/providerEnvironment' import type { ProviderSettingsPort } from '@/provider/settings' import { beforeEach, describe, expect, it, vi } from 'vitest' import type { LLM_PROVIDER } from '@shared/types/provider' @@ -137,7 +138,11 @@ describe('AiSdkProvider kimi-for-coding', () => { ] }) - const provider = new AiSdkProvider(createProvider(), createProviderSettings()) + const provider = new AiSdkProvider( + createProvider(), + createProviderSettings(), + providerTestEnvironment + ) const models = await provider.fetchModels() expect(mockGetProvider).toHaveBeenCalledWith('kimi-for-coding') @@ -154,7 +159,11 @@ describe('AiSdkProvider kimi-for-coding', () => { }) it('verifies Kimi For Coding with a small generate-text request', async () => { - const provider = new AiSdkProvider(createProvider(), createProviderSettings()) + const provider = new AiSdkProvider( + createProvider(), + createProviderSettings(), + providerTestEnvironment + ) ;(provider as any).isInitialized = true await expect(provider.check()).resolves.toEqual({ diff --git a/test/main/provider/mistralProvider.test.ts b/test/main/provider/mistralProvider.test.ts index 56b29e17ed..92b2e3a7b9 100644 --- a/test/main/provider/mistralProvider.test.ts +++ b/test/main/provider/mistralProvider.test.ts @@ -1,3 +1,4 @@ +import { providerTestEnvironment } from '../../helpers/providerEnvironment' import type { ProviderSettingsPort } from '@/provider/settings' import { beforeEach, describe, expect, it, vi } from 'vitest' import type { LLM_PROVIDER } from '@shared/types/provider' @@ -128,7 +129,11 @@ describe('AiSdkProvider mistral', () => { ] }) - const provider = new AiSdkProvider(createProvider(), createProviderSettings()) + const provider = new AiSdkProvider( + createProvider(), + createProviderSettings(), + providerTestEnvironment + ) const models = await provider.fetchModels() expect(models).toEqual([ @@ -160,7 +165,8 @@ describe('AiSdkProvider mistral', () => { apiType: 'mistral', custom: true }), - createProviderSettings() + createProviderSettings(), + providerTestEnvironment ) const models = await provider.fetchModels() @@ -178,7 +184,8 @@ describe('AiSdkProvider mistral', () => { createProvider({ apiKey: '' }), - createProviderSettings() + createProviderSettings(), + providerTestEnvironment ) await expect(provider.check()).resolves.toEqual({ @@ -189,7 +196,11 @@ describe('AiSdkProvider mistral', () => { }) it('verifies Mistral with a small generate-text request', async () => { - const provider = new AiSdkProvider(createProvider(), createProviderSettings()) + const provider = new AiSdkProvider( + createProvider(), + createProviderSettings(), + providerTestEnvironment + ) ;(provider as any).isInitialized = true await expect(provider.check()).resolves.toEqual({ diff --git a/test/main/provider/modelConfig.test.ts b/test/main/provider/modelConfig.test.ts index 5fd717f6ac..bfb93fd2e2 100644 --- a/test/main/provider/modelConfig.test.ts +++ b/test/main/provider/modelConfig.test.ts @@ -6,84 +6,23 @@ import type { ModelConfig } from '@shared/types/provider' import { providerDbLoader } from '../../../src/main/provider/providerDbLoader' import { modelCapabilities } from '../../../src/main/provider/modelCapabilities' -// Mock electron-store with in-memory storage -const mockStores = new Map>() +import { createMemoryStore } from '../../helpers/memoryStore' const rebuildModelCapabilities = () => { const capabilities = modelCapabilities as unknown as { rebuildIndexFromDb: () => void } capabilities.rebuildIndexFromDb() } -vi.mock('electron-store', () => { - return { - default: class MockElectronStore { - private storePath: string - private data: Record - - constructor(options: { name: string }) { - this.storePath = options.name - if (!mockStores.has(this.storePath)) { - mockStores.set(this.storePath, {}) - } - this.data = mockStores.get(this.storePath)! - } - - get(key: string) { - return this.data[key] - } - - set(key: string, value: any) { - this.data[key] = value - } - - delete(key: string) { - delete this.data[key] - } - - has(key: string) { - return key in this.data - } - - clear() { - Object.keys(this.data).forEach((key) => delete this.data[key]) - } - - get store() { - return { ...this.data } - } - - get path() { - return `/mock/path/${this.storePath}.json` - } - } - } -}) - describe('ModelConfigHelper', () => { let modelConfigHelper: ModelConfigHelper - let originalStoreData: Map> + let modelStore: ReturnType beforeEach(() => { - // Save original store state for restoration - originalStoreData = new Map() - mockStores.forEach((value, key) => { - originalStoreData.set(key, { ...value }) - }) - - // Clear stores for clean test state - mockStores.clear() - - // Initialize test instances - modelConfigHelper = new ModelConfigHelper() + modelStore = createMemoryStore() + modelConfigHelper = new ModelConfigHelper(modelStore) }) afterEach(() => { - // Restore original store state - mockStores.clear() - originalStoreData.forEach((value, key) => { - mockStores.set(key, value) - }) - vi.restoreAllMocks() rebuildModelCapabilities() }) @@ -414,7 +353,7 @@ describe('ModelConfigHelper', () => { const helperAny = modelConfigHelper as any const userKey = helperAny.generateCacheKey(providerId, userManagedModelId) - const refreshedHelper = new ModelConfigHelper() + const refreshedHelper = new ModelConfigHelper(modelStore) expect(refreshedHelper.hasUserConfig(userManagedModelId, providerId)).toBe(true) expect(refreshedHelper.getModelConfig(userManagedModelId, providerId).maxTokens).toBe( @@ -528,7 +467,10 @@ describe('ModelConfigHelper', () => { const defaults = modelConfigHelper.getModelConfig('qwen3.8-max', 'demo') expect(defaults.reasoningEffort).toBeUndefined() expect(defaults.thinkingBudget).toBeUndefined() - modelConfigHelper.setModelConfig('qwen3.8-max', 'demo', { thinkingBudget: 8192 }) + modelConfigHelper.setModelConfig('qwen3.8-max', 'demo', { + ...defaults, + thinkingBudget: 8192 + }) const budget = modelConfigHelper.getModelConfig('qwen3.8-max', 'demo') expect(budget.thinkingBudget).toBe(8192) expect(budget.reasoningEffort).toBeUndefined() diff --git a/test/main/provider/newApiProvider.test.ts b/test/main/provider/newApiProvider.test.ts index 35415efe05..b00405a0ce 100644 --- a/test/main/provider/newApiProvider.test.ts +++ b/test/main/provider/newApiProvider.test.ts @@ -1,3 +1,4 @@ +import { providerTestEnvironment } from '../../helpers/providerEnvironment' import type { ProviderSettingsPort } from '@/provider/settings' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' import type { LLM_PROVIDER, ModelConfig } from '@shared/types/provider' @@ -120,7 +121,8 @@ describe('NewApiProvider capability routing', () => { 'gpt-4o': { endpointType: 'openai-response' } - }) + }), + providerTestEnvironment ) const routeDecision = (provider as any).resolveRouteDecision('gpt-4o') const runtimeProvider = (provider as any).getRuntimeProvider(routeDecision) as LLM_PROVIDER @@ -137,7 +139,8 @@ describe('NewApiProvider capability routing', () => { 'gemini-model': { endpointType: 'gemini' } - }) + }), + providerTestEnvironment ) const routeDecision = (provider as any).resolveRouteDecision('gemini-model') const runtimeProvider = (provider as any).getRuntimeProvider(routeDecision) as LLM_PROVIDER @@ -156,7 +159,8 @@ describe('NewApiProvider capability routing', () => { 'gemini-model': { endpointType: 'gemini' } - }) + }), + providerTestEnvironment ) ;(provider as any).isInitialized = true @@ -196,7 +200,8 @@ describe('NewApiProvider capability routing', () => { 'claude-model': { endpointType: 'anthropic' } - }) + }), + providerTestEnvironment ) const routeDecision = (provider as any).resolveRouteDecision('claude-model') const runtimeProvider = (provider as any).getRuntimeProvider(routeDecision) as LLM_PROVIDER @@ -233,7 +238,8 @@ describe('NewApiProvider capability routing', () => { } ] } - ) + ), + providerTestEnvironment ) const routeDecision = (provider as any).resolveRouteDecision('claude-opus-4-7') const runtimeProvider = (provider as any).getRuntimeProvider(routeDecision) as LLM_PROVIDER @@ -270,7 +276,7 @@ describe('NewApiProvider capability routing', () => { type: ModelType.Chat, ownedBy: undefined }) - const provider = new AiSdkProvider(createProvider(), providerSettings) + const provider = new AiSdkProvider(createProvider(), providerSettings, providerTestEnvironment) const routeDecision = (provider as any).resolveRouteDecision('cached-model') @@ -350,7 +356,7 @@ describe('NewApiProvider capability routing', () => { vi.stubGlobal('fetch', fetchMock) const providerSettings = createProviderSettings() - const provider = new AiSdkProvider(createProvider(), providerSettings) + const provider = new AiSdkProvider(createProvider(), providerSettings, providerTestEnvironment) const models = await (provider as any).fetchProviderModels() @@ -402,7 +408,7 @@ describe('NewApiProvider capability routing', () => { ) const providerSettings = createProviderSettings() - const provider = new AiSdkProvider(createProvider(), providerSettings) + const provider = new AiSdkProvider(createProvider(), providerSettings, providerTestEnvironment) const models = await (provider as any).fetchProviderModels() expect(models[0]).toMatchObject({ @@ -440,7 +446,7 @@ describe('NewApiProvider capability routing', () => { ) const providerSettings = createProviderSettings() - const provider = new AiSdkProvider(createProvider(), providerSettings) + const provider = new AiSdkProvider(createProvider(), providerSettings, providerTestEnvironment) const models = await (provider as any).fetchProviderModels() expect(models[0]).toMatchObject({ @@ -484,7 +490,8 @@ describe('NewApiProvider capability routing', () => { } ] } - ) + ), + providerTestEnvironment ) const routeDecision = (provider as any).resolveRouteDecision('qwen3.7-max') const runtimeProvider = (provider as any).getRuntimeProvider(routeDecision) as LLM_PROVIDER @@ -549,7 +556,7 @@ describe('NewApiProvider capability routing', () => { endpointType: 'openai' } }) - const provider = new AiSdkProvider(createProvider(), providerSettings) + const provider = new AiSdkProvider(createProvider(), providerSettings, providerTestEnvironment) const getStoredModel = vi.spyOn(provider as any, 'getStoredModel') const resolveCapabilityIdentity = vi.spyOn(provider as any, 'resolveCapabilityIdentity') @@ -603,7 +610,7 @@ describe('NewApiProvider capability routing', () => { ) const providerSettings = createProviderSettings() - const provider = new AiSdkProvider(createProvider(), providerSettings) + const provider = new AiSdkProvider(createProvider(), providerSettings, providerTestEnvironment) const models = await (provider as any).fetchProviderModels() expect(models[0]).toMatchObject({ @@ -621,7 +628,8 @@ describe('NewApiProvider capability routing', () => { { 'new-api': models } - ) + ), + providerTestEnvironment ) const routeDecision = (runtimeProvider as any).resolveRouteDecision('gpt-5.5') const selectedProvider = (runtimeProvider as any).getRuntimeProvider( @@ -657,7 +665,8 @@ describe('NewApiProvider capability routing', () => { } ] } - ) + ), + providerTestEnvironment ) const routeDecision = (provider as any).resolveRouteDecision('gpt-5.5') const runtimeProvider = (provider as any).getRuntimeProvider(routeDecision) as LLM_PROVIDER @@ -687,7 +696,11 @@ describe('NewApiProvider capability routing', () => { }) ) - const provider = new AiSdkProvider(createProvider(), createProviderSettings()) + const provider = new AiSdkProvider( + createProvider(), + createProviderSettings(), + providerTestEnvironment + ) const models = await (provider as any).fetchProviderModels() expect(models[0]).toMatchObject({ @@ -704,7 +717,8 @@ describe('NewApiProvider capability routing', () => { { 'new-api': models } - ) + ), + providerTestEnvironment ) const routeDecision = (runtimeProvider as any).resolveRouteDecision('gpt-4.1') @@ -739,7 +753,11 @@ describe('NewApiProvider capability routing', () => { }) ) - const provider = new AiSdkProvider(createProvider(), createProviderSettings()) + const provider = new AiSdkProvider( + createProvider(), + createProviderSettings(), + providerTestEnvironment + ) const models = await (provider as any).fetchProviderModels() expect(models[0]).toMatchObject({ @@ -775,7 +793,11 @@ describe('NewApiProvider capability routing', () => { }) ) - const provider = new AiSdkProvider(createProvider(), createProviderSettings()) + const provider = new AiSdkProvider( + createProvider(), + createProviderSettings(), + providerTestEnvironment + ) const models = await (provider as any).fetchProviderModels() expect(models[0]).toMatchObject({ @@ -806,7 +828,11 @@ describe('NewApiProvider capability routing', () => { }) ) - const provider = new AiSdkProvider(createProvider(), createProviderSettings()) + const provider = new AiSdkProvider( + createProvider(), + createProviderSettings(), + providerTestEnvironment + ) const models = await (provider as any).fetchProviderModels() expect(models[0]).toMatchObject({ @@ -843,7 +869,11 @@ describe('NewApiProvider capability routing', () => { }) ) - const provider = new AiSdkProvider(createProvider(), createProviderSettings()) + const provider = new AiSdkProvider( + createProvider(), + createProviderSettings(), + providerTestEnvironment + ) const models = await (provider as any).fetchProviderModels() expect(models).toHaveLength(2) @@ -874,7 +904,11 @@ describe('NewApiProvider capability routing', () => { }) ) - const provider = new AiSdkProvider(createProvider(), createProviderSettings()) + const provider = new AiSdkProvider( + createProvider(), + createProviderSettings(), + providerTestEnvironment + ) const models = await (provider as any).fetchProviderModels() expect(models[0]).toMatchObject({ @@ -913,7 +947,11 @@ describe('NewApiProvider capability routing', () => { }) ) - const provider = new AiSdkProvider(createProvider(), createProviderSettings()) + const provider = new AiSdkProvider( + createProvider(), + createProviderSettings(), + providerTestEnvironment + ) const models = await (provider as any).fetchProviderModels() expect(models).toHaveLength(2) @@ -943,7 +981,7 @@ describe('NewApiProvider capability routing', () => { ) const providerSettings = createProviderSettings() - const provider = new AiSdkProvider(createProvider(), providerSettings) + const provider = new AiSdkProvider(createProvider(), providerSettings, providerTestEnvironment) const models = await (provider as any).fetchProviderModels() expect(models[0]).toMatchObject({ @@ -975,7 +1013,8 @@ describe('NewApiProvider capability routing', () => { } ] } - ) + ), + providerTestEnvironment ) const routeDecision = (provider as any).resolveRouteDecision('gpt-5.4') const runtimeProvider = (provider as any).getRuntimeProvider(routeDecision) as LLM_PROVIDER @@ -993,7 +1032,11 @@ describe('NewApiProvider capability routing', () => { apiType: 'openai', baseUrl: 'https://zenmux.ai/api' }) - const provider = new AiSdkProvider(zenmuxProvider, createProviderSettings()) + const provider = new AiSdkProvider( + zenmuxProvider, + createProviderSettings(), + providerTestEnvironment + ) const routeDecision = (provider as any).resolveRouteDecision('anthropic/claude-sonnet-4.5') const runtimeProvider = (provider as any).getRuntimeProvider(routeDecision) as LLM_PROVIDER const runtimeContext = (provider as any).buildRuntimeContext( @@ -1018,7 +1061,8 @@ describe('NewApiProvider capability routing', () => { apiType: 'anthropic', baseUrl: 'https://proxy.example.com/anthropic' }), - createProviderSettings() + createProviderSettings(), + providerTestEnvironment ) const routeDecision = (provider as any).resolveRouteDecision('claude-opus-4-7') const runtimeProvider = (provider as any).getRuntimeProvider(routeDecision) as LLM_PROVIDER @@ -1039,7 +1083,8 @@ describe('NewApiProvider capability routing', () => { apiType: 'anthropic', baseUrl: 'https://api.minimaxi.com/anthropic' }), - createProviderSettings() + createProviderSettings(), + providerTestEnvironment ) const routeDecision = (provider as any).resolveRouteDecision('MiniMax-M2.5') const runtimeProvider = (provider as any).getRuntimeProvider(routeDecision) as LLM_PROVIDER @@ -1060,7 +1105,7 @@ describe('NewApiProvider capability routing', () => { type: ModelType.Chat } }) - const provider = new AiSdkProvider(createProvider(), providerSettings) + const provider = new AiSdkProvider(createProvider(), providerSettings, providerTestEnvironment) ;(provider as any).isInitialized = true const result = await provider.completions( @@ -1080,7 +1125,7 @@ describe('NewApiProvider capability routing', () => { it('preserves the collect-stream image fallback without rereading model config', async () => { const providerSettings = createProviderSettings() - const provider = new AiSdkProvider(createProvider(), providerSettings) + const provider = new AiSdkProvider(createProvider(), providerSettings, providerTestEnvironment) ;(provider as any).isInitialized = true await provider.collectStreamResponse( diff --git a/test/main/provider/ollamaProvider.test.ts b/test/main/provider/ollamaProvider.test.ts index 9b2121a025..e753a5fffd 100644 --- a/test/main/provider/ollamaProvider.test.ts +++ b/test/main/provider/ollamaProvider.test.ts @@ -1,3 +1,4 @@ +import { providerTestEnvironment } from '../../helpers/providerEnvironment' import type { ProviderSettingsPort } from '@/provider/settings' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' import { ModelType } from '../../../src/shared/model' @@ -36,18 +37,6 @@ vi.mock('@shared/logger', () => ({ } })) -vi.mock('@electron-toolkit/utils', () => ({ - is: { - dev: false - } -})) - -vi.mock('../../../src/main/device', () => ({ - DeviceService: { - getDefaultHeaders: () => ({}) - } -})) - const createModel = ( name: string, options?: { @@ -136,7 +125,8 @@ describe('OllamaProvider.fetchModels', () => { apiKey: 'test-key', baseUrl: 'http://localhost:11434/api' }, - providerSettings + providerSettings, + providerTestEnvironment ) const runtimeContext = (ollamaProvider as any).getAiSdkRuntimeContext('local-custom-model') @@ -165,7 +155,8 @@ describe('OllamaProvider.fetchModels', () => { ...provider, customHeaders: { 'X-Tenant-ID': 'team-a' } }, - providerSettings + providerSettings, + providerTestEnvironment ) const options = mockOllamaConstructorOptions.at(-1) as { @@ -178,7 +169,7 @@ describe('OllamaProvider.fetchModels', () => { }) it('merges local and running models, keeps running-only models, and preserves capabilities', async () => { - const ollamaProvider = new OllamaProvider(provider, providerSettings) + const ollamaProvider = new OllamaProvider(provider, providerSettings, providerTestEnvironment) vi.mocked(providerSettings.getProviderModels).mockClear() vi.spyOn(ollamaProvider, 'listModels').mockResolvedValue([ @@ -252,7 +243,7 @@ describe('OllamaProvider.fetchModels', () => { }) it('does not promote catalog fallback capabilities into provider facts', async () => { - const ollamaProvider = new OllamaProvider(provider, providerSettings) + const ollamaProvider = new OllamaProvider(provider, providerSettings, providerTestEnvironment) vi.spyOn(ollamaProvider, 'listModels').mockRejectedValue(new Error('ollama unavailable')) vi.spyOn(ollamaProvider, 'listRunningModels').mockResolvedValue([]) vi.mocked(providerSettings.getDbProviderModels).mockReturnValue([ @@ -286,7 +277,7 @@ describe('OllamaProvider.fetchModels', () => { }) it('uses ollama list output as the local model source when the SDK list is empty', async () => { - const ollamaProvider = new OllamaProvider(provider, providerSettings) + const ollamaProvider = new OllamaProvider(provider, providerSettings, providerTestEnvironment) ;(ollamaProvider as any).ollama = { list: vi.fn(async () => ({ models: [] })), show: vi.fn(async () => { @@ -325,7 +316,7 @@ describe('OllamaProvider.fetchModels', () => { }) it('preserves list model info when the show response is sparse', async () => { - const ollamaProvider = new OllamaProvider(provider, providerSettings) + const ollamaProvider = new OllamaProvider(provider, providerSettings, providerTestEnvironment) ;(ollamaProvider as any).ollama = { list: vi.fn(async () => ({ models: [createModel('qwen3:8b', { family: 'qwen', contextLength: 32768 })] @@ -348,7 +339,7 @@ describe('OllamaProvider.fetchModels', () => { }) it('keeps the running context separate from theoretical model metadata', async () => { - const ollamaProvider = new OllamaProvider(provider, providerSettings) + const ollamaProvider = new OllamaProvider(provider, providerSettings, providerTestEnvironment) let runtimeContextLength: unknown = 8192 const ps = vi.fn(async () => ({ models: [ @@ -397,7 +388,7 @@ describe('OllamaProvider.fetchModels', () => { }) it('distinguishes a stopped model from an unavailable runtime query', async () => { - const ollamaProvider = new OllamaProvider(provider, providerSettings) + const ollamaProvider = new OllamaProvider(provider, providerSettings, providerTestEnvironment) mockOllamaPs .mockResolvedValueOnce({ models: [] }) .mockRejectedValueOnce(new Error('ps unavailable')) @@ -410,7 +401,7 @@ describe('OllamaProvider.fetchModels', () => { }) it('diagnoses a running model from Ollama versions without runtime context metadata', async () => { - const ollamaProvider = new OllamaProvider(provider, providerSettings) + const ollamaProvider = new OllamaProvider(provider, providerSettings, providerTestEnvironment) mockOllamaPs.mockResolvedValue({ models: [{ name: 'qwen3:8b' }] }) @@ -421,7 +412,7 @@ describe('OllamaProvider.fetchModels', () => { }) it('matches the implicit latest tag in either model-name direction', async () => { - const ollamaProvider = new OllamaProvider(provider, providerSettings) + const ollamaProvider = new OllamaProvider(provider, providerSettings, providerTestEnvironment) mockOllamaPs .mockResolvedValueOnce({ models: [{ name: 'qwen3', context_length: 8192 }] @@ -441,7 +432,7 @@ describe('OllamaProvider.fetchModels', () => { it('bounds a stalled runtime context query without loading the model', async () => { vi.useFakeTimers() try { - const ollamaProvider = new OllamaProvider(provider, providerSettings) + const ollamaProvider = new OllamaProvider(provider, providerSettings, providerTestEnvironment) let transportSignal: AbortSignal | undefined const transportFetch = vi.fn( (_input: string | URL | Request, init?: RequestInit) => @@ -475,7 +466,7 @@ describe('OllamaProvider.fetchModels', () => { }) it('propagates cancellation of a stalled runtime context query', async () => { - const ollamaProvider = new OllamaProvider(provider, providerSettings) + const ollamaProvider = new OllamaProvider(provider, providerSettings, providerTestEnvironment) let transportSignal: AbortSignal | undefined const transportFetch = vi.fn( (_input: string | URL | Request, init?: RequestInit) => @@ -506,8 +497,9 @@ describe('OllamaProvider.fetchModels', () => { expect(transportSignal?.aborted).toBe(true) }) - it('confirms pull success against the ollama list model set', async () => { - const ollamaProvider = new OllamaProvider(provider, providerSettings) + it('pulls securely under the host policy and verifies the installed model', async () => { + process.env.DEEPCHAT_ALLOW_INSECURE_TLS = '1' + const ollamaProvider = new OllamaProvider(provider, providerSettings, providerTestEnvironment) ;(ollamaProvider as any).ollama = { pull: vi.fn(async () => ({ async *[Symbol.asyncIterator]() { @@ -541,9 +533,11 @@ describe('OllamaProvider.fetchModels', () => { ) }) - it('only enables insecure pulls behind the explicit TLS debug flag', async () => { - process.env.DEEPCHAT_ALLOW_INSECURE_TLS = '1' - const ollamaProvider = new OllamaProvider(provider, providerSettings) + it('enables insecure pulls only when the host TLS policy allows them', async () => { + const ollamaProvider = new OllamaProvider(provider, providerSettings, { + ...providerTestEnvironment, + allowInsecureTls: true + }) ;(ollamaProvider as any).ollama = { pull: vi.fn(async () => ({ async *[Symbol.asyncIterator]() { @@ -567,7 +561,7 @@ describe('OllamaProvider.fetchModels', () => { }) it('treats latest tags from ollama list as a successful untagged pull', async () => { - const ollamaProvider = new OllamaProvider(provider, providerSettings) + const ollamaProvider = new OllamaProvider(provider, providerSettings, providerTestEnvironment) ;(ollamaProvider as any).ollama = { pull: vi.fn(async () => ({ async *[Symbol.asyncIterator]() { diff --git a/test/main/provider/openAICompatibleProvider.test.ts b/test/main/provider/openAICompatibleProvider.test.ts index 41ae80da27..3645652084 100644 --- a/test/main/provider/openAICompatibleProvider.test.ts +++ b/test/main/provider/openAICompatibleProvider.test.ts @@ -1,6 +1,9 @@ +import { providerTestEnvironment } from '../../helpers/providerEnvironment' import type { ProviderSettingsPort } from '@/provider/settings' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' import type { LLM_PROVIDER, ModelConfig } from '@shared/types/provider' +import type { LLMCoreStreamEvent } from '@shared/types/core/llm-events' +import { ModelType } from '@shared/model' import { AiSdkProvider, normalizeExtractedImageText @@ -21,6 +24,15 @@ const { mockRunAiSdkGenerateText: vi.fn() })) +const grokAuth = vi.hoisted(() => ({ + captureOperation: () => () => undefined, + ensureAccessToken: vi.fn<() => Promise>() +})) + +vi.mock('../../../src/main/provider/auth/xaiGrok', () => ({ + getGlobalXaiGrokAuth: () => grokAuth +})) + vi.mock('electron', () => ({ app: { getName: vi.fn(() => 'DeepChat'), @@ -72,6 +84,12 @@ const createProviderSettings = (): ProviderSettingsPort => getModelStatus: vi.fn().mockReturnValue(true) }) as unknown as ProviderSettingsPort +const locale = { + allowInsecureTls: providerTestEnvironment.allowInsecureTls, + defaultHeaders: providerTestEnvironment.defaultHeaders, + getLanguage: () => 'en-US' +} + describe('AiSdkProvider openai-compatible', () => { beforeEach(() => { vi.clearAllMocks() @@ -92,7 +110,7 @@ describe('AiSdkProvider openai-compatible', () => { it.each(['text-embedding-3-small', 'text-embedding-ada-002', 'text-embedding-3-large'])( 'honors cancellation before returning hard-coded dimensions for %s', async (modelId) => { - const provider = new AiSdkProvider(createProvider(), createProviderSettings()) + const provider = new AiSdkProvider(createProvider(), createProviderSettings(), locale) const controller = new AbortController() controller.abort() @@ -113,7 +131,11 @@ describe('AiSdkProvider openai-compatible', () => { }) vi.stubGlobal('fetch', fetchMock) - const provider = new AiSdkProvider(createProvider({ enable: true }), createProviderSettings()) + const provider = new AiSdkProvider( + createProvider({ enable: true }), + createProviderSettings(), + locale + ) const results = await Promise.all(Array.from({ length: 8 }, () => provider.fetchModels())) expect(fetchMock).toHaveBeenCalledTimes(1) @@ -158,7 +180,7 @@ describe('AiSdkProvider openai-compatible', () => { }) vi.stubGlobal('fetch', fetchMock) - const provider = new AiSdkProvider(createProvider(), createProviderSettings()) + const provider = new AiSdkProvider(createProvider(), createProviderSettings(), locale) await provider.requestProviderJson('https://mock.example.com/v1/models', { method: 'GET', headers @@ -170,8 +192,31 @@ describe('AiSdkProvider openai-compatible', () => { expect(requestHeaders.get('x-tenant-id')).toBe('team-a') }) + it('resolves current Grok OAuth credentials for each direct catalog request', async () => { + grokAuth.ensureAccessToken + .mockResolvedValueOnce('first-oauth-token') + .mockResolvedValueOnce('current-oauth-token') + const fetchMock = vi + .fn() + .mockImplementation(async () => Response.json({ data: [] })) + vi.stubGlobal('fetch', fetchMock) + const provider = new AiSdkProvider( + createProvider({ id: 'grok', baseUrl: 'https://api.x.ai/v1' }), + createProviderSettings(), + locale + ) + + await provider.requestProviderJson('https://api.x.ai/v1/models') + await provider.requestProviderJson('https://api.x.ai/v1/models') + + expect(grokAuth.ensureAccessToken).toHaveBeenCalledTimes(2) + expect( + fetchMock.mock.calls.map(([, options]) => new Headers(options?.headers).get('Authorization')) + ).toEqual(['Bearer first-oauth-token', 'Bearer current-oauth-token']) + }) + it('forwards streaming requests to the AI SDK runtime', async () => { - const provider = new AiSdkProvider(createProvider(), createProviderSettings()) + const provider = new AiSdkProvider(createProvider(), createProviderSettings(), locale) ;(provider as any).isInitialized = true const modelConfig: ModelConfig = { @@ -180,11 +225,11 @@ describe('AiSdkProvider openai-compatible', () => { vision: false, functionCall: true, reasoning: false, - type: 'chat' + type: ModelType.Chat } const signal = new AbortController().signal - const events = [] + const events: LLMCoreStreamEvent[] = [] for await (const event of provider.coreStream( [{ role: 'user', content: 'hello' }], 'gpt-4o', @@ -220,7 +265,7 @@ describe('AiSdkProvider openai-compatible', () => { }) vi.stubGlobal('fetch', fetchMock) - const provider = new AiSdkProvider(createProvider(), createProviderSettings()) + const provider = new AiSdkProvider(createProvider(), createProviderSettings(), locale) ;(provider as any).isInitialized = true const result = await provider.transcribeAudio( @@ -262,7 +307,7 @@ describe('AiSdkProvider openai-compatible', () => { }) ) - const provider = new AiSdkProvider(createProvider(), createProviderSettings()) + const provider = new AiSdkProvider(createProvider(), createProviderSettings(), locale) ;(provider as any).isInitialized = true await expect( @@ -271,7 +316,7 @@ describe('AiSdkProvider openai-compatible', () => { }) it('uses image generation for OpenAI-compatible models declared by type', async () => { - const provider = new AiSdkProvider(createProvider(), createProviderSettings()) + const provider = new AiSdkProvider(createProvider(), createProviderSettings(), locale) ;(provider as any).isInitialized = true const modelConfig = { @@ -318,7 +363,8 @@ describe('AiSdkProvider openai-compatible', () => { apiType: 'openai-completions', baseUrl: 'https://example.openai.azure.com/openai/deployments/deepchat-prod' }), - createProviderSettings() + createProviderSettings(), + locale ) ;(provider as any).isInitialized = true diff --git a/test/main/provider/openAIResponsesProvider.test.ts b/test/main/provider/openAIResponsesProvider.test.ts index 5f92fbbbb9..6ccadef751 100644 --- a/test/main/provider/openAIResponsesProvider.test.ts +++ b/test/main/provider/openAIResponsesProvider.test.ts @@ -1,3 +1,4 @@ +import { providerTestEnvironment } from '../../helpers/providerEnvironment' import type { ProviderSettingsPort } from '@/provider/settings' import { beforeEach, describe, expect, it, vi } from 'vitest' import type { LLM_PROVIDER, ModelConfig } from '@shared/types/provider' @@ -69,7 +70,11 @@ describe('OpenAIResponsesProvider', () => { }) it('uses the responses runtime for official OpenAI providers', async () => { - const provider = new AiSdkProvider(createProvider(), createProviderSettings()) + const provider = new AiSdkProvider( + createProvider(), + createProviderSettings(), + providerTestEnvironment + ) ;(provider as any).isInitialized = true for await (const _event of provider.coreStream( @@ -109,7 +114,8 @@ describe('OpenAIResponsesProvider', () => { name: 'Azure OpenAI', baseUrl: 'https://example.openai.azure.com/openai' }), - createProviderSettings() + createProviderSettings(), + providerTestEnvironment ) ;(provider as any).isInitialized = true @@ -160,7 +166,8 @@ describe('OpenAIResponsesProvider', () => { apiType: 'deepseek', baseUrl: 'https://api.deepseek.com/v1' }), - createProviderSettings() + createProviderSettings(), + providerTestEnvironment ) ;(provider as any).isInitialized = true @@ -200,7 +207,8 @@ describe('OpenAIResponsesProvider', () => { apiType: 'deepseek', baseUrl: 'https://api.deepseek.com/v1' }), - createProviderSettings() + createProviderSettings(), + providerTestEnvironment ) ;(provider as any).isInitialized = true @@ -241,7 +249,8 @@ describe('OpenAIResponsesProvider', () => { apiType: 'deepseek', baseUrl: 'https://api.deepseek.com/v1' }), - createProviderSettings() + createProviderSettings(), + providerTestEnvironment ) ;(provider as any).isInitialized = true @@ -286,7 +295,11 @@ describe('OpenAIResponsesProvider', () => { }) vi.stubGlobal('fetch', fetchMock) - const provider = new AiSdkProvider(createProvider(), createProviderSettings()) + const provider = new AiSdkProvider( + createProvider(), + createProviderSettings(), + providerTestEnvironment + ) ;(provider as any).isInitialized = true const text = await provider.transcribeAudio('gpt-4o-mini-transcribe', 'AQID', 'audio/wav') @@ -313,7 +326,11 @@ describe('OpenAIResponsesProvider', () => { }) ) - const provider = new AiSdkProvider(createProvider(), createProviderSettings()) + const provider = new AiSdkProvider( + createProvider(), + createProviderSettings(), + providerTestEnvironment + ) ;(provider as any).isInitialized = true await expect( diff --git a/test/main/provider/openaiCodexAdapter.test.ts b/test/main/provider/openaiCodexAdapter.test.ts index dfeafb76e0..fb907119dc 100644 --- a/test/main/provider/openaiCodexAdapter.test.ts +++ b/test/main/provider/openaiCodexAdapter.test.ts @@ -1,6 +1,7 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' const authState = vi.hoisted(() => ({ + captureOperation: () => () => undefined, getAccessToken: vi.fn(), forceRefreshAccessToken: vi.fn(), getBackendAuth: vi.fn(), @@ -235,4 +236,37 @@ describe('OpenAI Codex adapter', () => { code: 'openai_codex_bad_request' }) }) + it.each(['request abort', 'auth replacement'] as const)( + 'does not dispatch a token resolved after %s', + async (change) => { + const { createOpenAICodexFetch } = await import('@/provider/openaiCodexAdapter') + let finish!: (value: { accessToken: string }) => void + authState.getBackendAuth.mockReturnValue( + new Promise((resolve) => { + finish = resolve + }) + ) + let current = true + const capture = vi.spyOn(authState, 'captureOperation').mockImplementation(() => () => { + if (!current) throw new DOMException('Auth changed', 'AbortError') + return undefined + }) + try { + const controller = new AbortController() + const fetch = vi.fn() + const pending = createOpenAICodexFetch({}, fetch)( + 'https://chatgpt.com/backend-api/codex/responses', + { signal: controller.signal } + ) + const rejected = expect(pending).rejects.toMatchObject({ name: 'AbortError' }) + if (change === 'request abort') controller.abort() + else current = false + finish({ accessToken: 'late-token' }) + await rejected + expect(fetch).not.toHaveBeenCalled() + } finally { + capture.mockRestore() + } + } + ) }) diff --git a/test/main/provider/openaiCodexCredentialStore.test.ts b/test/main/provider/openaiCodexCredentialStore.test.ts deleted file mode 100644 index b71f867d38..0000000000 --- a/test/main/provider/openaiCodexCredentialStore.test.ts +++ /dev/null @@ -1,203 +0,0 @@ -import * as fs from 'fs' -import { beforeEach, describe, expect, it, vi } from 'vitest' -import { safeStorage } from 'electron' -import { - OpenAICodexCredentialStore, - type OpenAICodexTokenSet -} from '@/provider/auth/openaiCodex/credentialStore' - -const filePath = '/tmp/deepchat-openai-codex/credentials.json' - -const tokens: OpenAICodexTokenSet = { - accessToken: 'access-token', - refreshToken: 'refresh-token', - tokenType: 'Bearer', - expiresAt: Date.now() + 3_600_000, - updatedAt: Date.now() -} - -function fileEnvelope(value: OpenAICodexTokenSet): string { - return JSON.stringify({ version: 1, storage: 'file', tokens: value, updatedAt: 1 }) -} - -describe('OpenAICodexCredentialStore', () => { - let savedContent: string | null = null - - beforeEach(() => { - savedContent = null - vi.mocked(fs.readFileSync).mockImplementation(() => { - if (savedContent === null) { - throw Object.assign(new Error('ENOENT: no such file or directory'), { code: 'ENOENT' }) - } - return savedContent - }) - vi.mocked(fs.writeFileSync).mockImplementation((_, data) => { - savedContent = String(data) - }) - vi.mocked(fs.mkdirSync).mockImplementation(() => undefined as unknown as string) - vi.mocked(fs.renameSync).mockImplementation(() => {}) - vi.mocked(fs.readdirSync).mockImplementation(() => []) - vi.mocked(safeStorage.isEncryptionAvailable).mockReturnValue(false) - }) - - it('returns null without an error when the credential file is missing', () => { - const warn = vi.spyOn(console, 'warn').mockImplementation(() => {}) - const store = new OpenAICodexCredentialStore(filePath) - - expect(store.load()).toBeNull() - expect(store.getLoadError()).toBeNull() - expect(warn).not.toHaveBeenCalled() - }) - - it('reports corrupted JSON instead of treating it as signed out', () => { - const warn = vi.spyOn(console, 'warn').mockImplementation(() => {}) - savedContent = '{broken' - const store = new OpenAICodexCredentialStore(filePath) - - expect(store.load()).toBeNull() - expect(store.getLoadError()).toContain('not valid JSON') - expect(warn).toHaveBeenCalled() - }) - - it('reports decryption failures instead of treating them as signed out', () => { - vi.spyOn(console, 'warn').mockImplementation(() => {}) - vi.mocked(safeStorage.decryptString).mockImplementationOnce(() => { - throw new Error('keyring locked') - }) - savedContent = JSON.stringify({ - version: 1, - storage: 'safeStorage', - wrapped: Buffer.from('ciphertext').toString('base64'), - updatedAt: 1 - }) - const store = new OpenAICodexCredentialStore(filePath) - - expect(store.load()).toBeNull() - expect(store.getLoadError()).toContain('decryption failed') - }) - - it('returns tokens and clears the load error for a healthy envelope', () => { - savedContent = fileEnvelope(tokens) - const store = new OpenAICodexCredentialStore(filePath) - - expect(store.load()?.accessToken).toBe('access-token') - expect(store.getLoadError()).toBeNull() - }) - - it('writes through a temporary file and renames it into place', () => { - const store = new OpenAICodexCredentialStore(filePath) - - store.save(tokens) - - const temporaryPath = String(vi.mocked(fs.openSync).mock.calls[0][0]) - expect(temporaryPath).toMatch(/^\/tmp\/deepchat-openai-codex\/credentials\.json\.tmp-/) - expect(fs.openSync).toHaveBeenCalledWith(temporaryPath, 'w', 0o600) - expect(fs.fsyncSync).toHaveBeenCalled() - expect(fs.renameSync).toHaveBeenCalledWith(temporaryPath, filePath) - expect(fs.rmSync).toHaveBeenCalledWith(temporaryPath, { force: true }) - expect(store.load()?.accessToken).toBe('access-token') - }) - - it('removes the temporary file when the rename fails', () => { - vi.spyOn(console, 'warn').mockImplementation(() => {}) - vi.mocked(fs.renameSync).mockImplementation(() => { - throw new Error('EPERM: operation not permitted') - }) - const store = new OpenAICodexCredentialStore(filePath) - - expect(() => store.save(tokens)).toThrow('EPERM') - const temporaryPath = String(vi.mocked(fs.openSync).mock.calls[0][0]) - expect(fs.rmSync).toHaveBeenCalledWith(temporaryPath, { force: true }) - }) - - it('backs up a corrupted file before overwriting it', () => { - vi.spyOn(console, 'warn').mockImplementation(() => {}) - savedContent = '{broken' - let backupSnapshot: string | null = null - vi.mocked(fs.copyFileSync).mockImplementation(() => { - backupSnapshot = savedContent - }) - const store = new OpenAICodexCredentialStore(filePath) - - store.save(tokens) - - expect(fs.copyFileSync).toHaveBeenCalledWith(filePath, `${filePath}.corrupt`) - expect(fs.renameSync).not.toHaveBeenCalledWith(filePath, `${filePath}.corrupt`) - expect(backupSnapshot).toBe('{broken') - expect(store.getLoadError()).toBeNull() - }) - - it('does not back up an undecryptable file before overwriting it', () => { - vi.spyOn(console, 'warn').mockImplementation(() => {}) - vi.mocked(safeStorage.decryptString).mockImplementation(() => { - throw new Error('keyring locked') - }) - savedContent = JSON.stringify({ - version: 1, - storage: 'safeStorage', - wrapped: Buffer.from('ciphertext').toString('base64'), - updatedAt: 1 - }) - const store = new OpenAICodexCredentialStore(filePath) - - store.save(tokens) - - expect(fs.copyFileSync).not.toHaveBeenCalled() - }) - - it('does not back up a healthy file before overwriting it', () => { - savedContent = fileEnvelope(tokens) - const store = new OpenAICodexCredentialStore(filePath) - - store.save({ ...tokens, accessToken: 'new-access-token' }) - - expect(fs.copyFileSync).not.toHaveBeenCalled() - expect(store.load()?.accessToken).toBe('new-access-token') - }) - - it('clear removes the credential file together with its backup and temp file', () => { - const store = new OpenAICodexCredentialStore(filePath) - - store.clear() - - expect(fs.rmSync).toHaveBeenCalledWith(filePath, { force: true }) - expect(fs.rmSync).toHaveBeenCalledWith(`${filePath}.corrupt`, { force: true }) - expect(fs.rmSync).toHaveBeenCalledWith(`${filePath}.tmp`, { force: true }) - }) - - it('clear still attempts the backup and temp files when the main removal fails', () => { - vi.spyOn(console, 'warn').mockImplementation(() => {}) - vi.mocked(fs.rmSync).mockImplementation((target) => { - if (String(target) === filePath) { - throw new Error('EISDIR: illegal operation on a directory') - } - }) - const store = new OpenAICodexCredentialStore(filePath) - - store.clear() - - expect(fs.rmSync).toHaveBeenCalledWith(`${filePath}.corrupt`, { force: true }) - expect(fs.rmSync).toHaveBeenCalledWith(`${filePath}.tmp`, { force: true }) - }) - - it('clear also removes randomized temporary credential files', () => { - vi.mocked(fs.readdirSync).mockImplementation( - () => - [ - 'credentials.json.tmp-1234-aabbccddeeff', - 'credentials.json.tmp-5678-001122334455', - 'unrelated.txt' - ] as unknown as fs.Dirent>[] - ) - const store = new OpenAICodexCredentialStore(filePath) - - store.clear() - - expect(fs.rmSync).toHaveBeenCalledWith(`${filePath}.tmp-1234-aabbccddeeff`, { force: true }) - expect(fs.rmSync).toHaveBeenCalledWith(`${filePath}.tmp-5678-001122334455`, { force: true }) - expect(fs.rmSync).not.toHaveBeenCalledWith( - expect.stringContaining('unrelated'), - expect.anything() - ) - }) -}) diff --git a/test/main/provider/openaiCodexProvider.test.ts b/test/main/provider/openaiCodexProvider.test.ts index 87e109ba9e..77c1812c62 100644 --- a/test/main/provider/openaiCodexProvider.test.ts +++ b/test/main/provider/openaiCodexProvider.test.ts @@ -1,3 +1,4 @@ +import { providerTestEnvironment } from '../../helpers/providerEnvironment' import path from 'path' import { describe, expect, it, vi } from 'vitest' @@ -208,7 +209,11 @@ describe('OpenAI Codex provider registration', () => { } ] } as any) - const aiSdkProvider = new AiSdkProvider(provider, providerSettings as any) + const aiSdkProvider = new AiSdkProvider( + provider, + providerSettings as any, + providerTestEnvironment + ) const models = await aiSdkProvider.fetchModels() providerDbSpy.mockRestore() diff --git a/test/main/provider/providerDbLoader.test.ts b/test/main/provider/providerDbLoader.test.ts index 7e096b3555..007b70981e 100644 --- a/test/main/provider/providerDbLoader.test.ts +++ b/test/main/provider/providerDbLoader.test.ts @@ -3,10 +3,6 @@ import * as os from 'node:os' import * as path from 'node:path' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' -const state = vi.hoisted(() => ({ - getPath: vi.fn(), - getAppPath: vi.fn() -})) const DEFAULT_PROVIDER_DB_URL = 'https://raw.githubusercontent.com/ThinkInAIXYZ/PublicProviderConf/refs/heads/dev/dist/all.json' @@ -19,21 +15,21 @@ vi.mock('fs', async () => { } }) -vi.mock('electron', () => ({ - app: { - getPath: state.getPath, - getAppPath: state.getAppPath - } -})) - describe('ProviderDbLoader', () => { let tempRoot: string let appRoot: string let userDataRoot: string - const importLoader = async () => { + const importLoaderFactory = async () => { const mod = await import('../../../src/main/provider/providerDbLoader') - return mod.ProviderDbLoader + return () => { + const loader = new mod.ProviderDbLoader() + loader.configure({ + userDataDirectory: userDataRoot, + builtInCatalogPath: path.join(appRoot, 'resources', 'model-db', 'providers.json') + }) + return loader + } } const getCacheDir = () => path.join(userDataRoot, 'provider-db') @@ -88,11 +84,6 @@ describe('ProviderDbLoader', () => { fs.mkdirSync(appRoot, { recursive: true }) fs.mkdirSync(userDataRoot, { recursive: true }) - state.getPath.mockImplementation((name: string) => { - if (name === 'userData') return userDataRoot - return userDataRoot - }) - state.getAppPath.mockReturnValue(appRoot) vi.unstubAllGlobals() delete process.env.PROVIDER_DB_TTL_HOURS delete process.env.PROVIDER_DB_URL @@ -106,14 +97,123 @@ describe('ProviderDbLoader', () => { fs.rmSync(tempRoot, { recursive: true, force: true }) }) - it('resolves DEEPCHAT_E2E_USER_DATA_DIR as provider-db base directory', async () => { + it('keeps imports inert until explicit profile bootstrap and rejects profile replacement', async () => { + const { ProviderDbLoader } = await import('../../../src/main/provider/providerDbLoader') + const loader = new ProviderDbLoader() + expect(loader.getDb()).toBeNull() + expect(fs.existsSync(getCacheDir())).toBe(false) + await expect(loader.refreshIfNeeded(true)).rejects.toThrow('paths are required') + loader.configure({ + userDataDirectory: userDataRoot, + builtInCatalogPath: path.join(appRoot, 'resources', 'model-db', 'providers.json') + }) + expect(() => + loader.configure({ + userDataDirectory: path.join(tempRoot, 'another-profile'), + builtInCatalogPath: path.join(appRoot, 'resources', 'model-db', 'providers.json') + }) + ).toThrow('another profile') + }) + + it('drains a stopped refresh without accepting late response headers', async () => { + writeBuiltInDb(createAggregate(['builtin'])) + let release!: (response: Response) => void + let signal: AbortSignal | undefined + vi.stubGlobal( + 'fetch', + vi.fn((_url, options) => { + signal = options.signal + return new Promise((resolve) => { + release = resolve + }) + }) + ) + const createLoader = await importLoaderFactory() + const loader = createLoader() + expect(loader.getProvider('builtin')).toBeDefined() + const changed = vi.fn() + loader.subscribeCatalogChanges(changed) + const refresh = loader.refreshIfNeeded(true) + loader.stop() + let drained = false + const drain = loader.drain().then(() => { + drained = true + }) + await Promise.resolve() + expect(signal?.aborted).toBe(true) + expect(drained).toBe(false) + release(new Response(JSON.stringify(createAggregate(['late'])))) + expect((await refresh).status).toBe('skipped') + await drain + expect(changed).not.toHaveBeenCalled() + expect(loader.getProvider('late')).toBeUndefined() + expect(fs.existsSync(getCacheFile())).toBe(false) + expect(fs.existsSync(getMetaFile())).toBe(false) + expect((await loader.refreshIfNeeded(true)).status).toBe('skipped') + expect(fetch).toHaveBeenCalledOnce() + }) + + it.each(['shutdown', 'task cancellation'] as const)( + 'rejects a late body after %s without changing existing cache or metadata', + async (reason) => { + writeCachedDb(createAggregate(['cached'])) + const meta = { sourceUrl: DEFAULT_PROVIDER_DB_URL, lastUpdated: 1, ttlHours: 4 } + writeMeta(meta) + const originalMeta = fs.readFileSync(getMetaFile(), 'utf8') + let bodyStarted!: () => void + const started = new Promise((resolve) => { + bodyStarted = resolve + }) + let release!: (value: string) => void + const body = new Promise((resolve) => { + release = resolve + }) + vi.stubGlobal( + 'fetch', + vi.fn(async () => ({ + ok: true, + status: 200, + headers: new Headers({ etag: 'late' }), + body: null, + text: () => { + bodyStarted() + return body + } + })) + ) + const createLoader = await importLoaderFactory() + const loader = createLoader() + const owner = new AbortController() + const refresh = loader.refreshIfNeeded(true, { signal: owner.signal }) + await started + if (reason === 'shutdown') loader.stop() + else owner.abort() + release(JSON.stringify(createAggregate(['late']))) + expect((await refresh).status).toBe('skipped') + await loader.drain() + expect(JSON.parse(fs.readFileSync(getCacheFile(), 'utf8'))).toEqual( + createAggregate(['cached']) + ) + expect(fs.readFileSync(getMetaFile(), 'utf8')).toBe(originalMeta) + if (reason === 'task cancellation') { + vi.stubGlobal( + 'fetch', + vi.fn(async () => new Response(JSON.stringify(createAggregate(['fresh'])))) + ) + expect((await loader.refreshIfNeeded(true)).status).toBe('updated') + expect(loader.getProvider('fresh')).toBeDefined() + } + } + ) + + it('uses the explicit profile instead of the ambient E2E directory', async () => { const oldDir = process.env.DEEPCHAT_E2E_USER_DATA_DIR const e2eUserDataRoot = path.join(tempRoot, 'e2e-user-data') process.env.DEEPCHAT_E2E_USER_DATA_DIR = e2eUserDataRoot - const ProviderDbLoader = await importLoader() - new ProviderDbLoader() - expect(fs.existsSync(path.join(e2eUserDataRoot, 'provider-db'))).toBe(true) - expect(fs.existsSync(getCacheDir())).toBe(false) + const createLoader = await importLoaderFactory() + createLoader() + expect(fs.existsSync(path.join(e2eUserDataRoot, 'provider-db'))).toBe(false) + expect(fs.existsSync(getCacheDir())).toBe(true) if (oldDir === undefined) { delete process.env.DEEPCHAT_E2E_USER_DATA_DIR } else { @@ -121,7 +221,7 @@ describe('ProviderDbLoader', () => { } }) - it('initializes from cache and still triggers a startup refresh when cache is fresh', async () => { + it('loads cache without detaching work and permits an explicit automatic startup refresh', async () => { writeBuiltInDb(createAggregate(['builtin'])) writeCachedDb(createAggregate(['openai'])) const now = Date.now() @@ -142,14 +242,16 @@ describe('ProviderDbLoader', () => { }) vi.stubGlobal('fetch', fetchMock) - const ProviderDbLoader = await importLoader() - const loader = new ProviderDbLoader() + const createLoader = await importLoaderFactory() + const loader = createLoader() const catalogChanged = vi.fn() loader.subscribeCatalogChanges(catalogChanged) await loader.initialize() expect(loader.getDb()?.providers).toHaveProperty('openai') + expect(fetchMock).not.toHaveBeenCalled() + await loader.refreshIfNeeded(true, { automatic: true }) expect(fetchMock).toHaveBeenCalledTimes(1) expect(catalogChanged).toHaveBeenCalledWith({ reason: 'loaded', @@ -164,13 +266,14 @@ describe('ProviderDbLoader', () => { const fetchMock = vi.fn() vi.stubGlobal('fetch', fetchMock) - const ProviderDbLoader = await importLoader() - const loader = new ProviderDbLoader() + const createLoader = await importLoaderFactory() + const loader = createLoader() loader.setPrivacyModeResolver(() => true) await loader.initialize() expect(loader.getDb()?.providers).toHaveProperty('openai') + await loader.refreshIfNeeded(true, { automatic: true }) expect(fetchMock).not.toHaveBeenCalled() }) @@ -187,8 +290,8 @@ describe('ProviderDbLoader', () => { const fetchMock = vi.fn() vi.stubGlobal('fetch', fetchMock) - const ProviderDbLoader = await importLoader() - const loader = new ProviderDbLoader() + const createLoader = await importLoaderFactory() + const loader = createLoader() const result = await loader.refreshIfNeeded(false) @@ -233,8 +336,8 @@ describe('ProviderDbLoader', () => { ) .mockResolvedValueOnce(new Response(null, { status: 304 })) vi.stubGlobal('fetch', fetchMock) - const ProviderDbLoader = await importLoader() - const loader = new ProviderDbLoader() + const createLoader = await importLoaderFactory() + const loader = createLoader() expect((await loader.refreshIfNeeded()).status).toBe('updated') expect(fetchMock.mock.calls[0][1].headers).not.toHaveProperty('If-None-Match') expect( @@ -243,8 +346,7 @@ describe('ProviderDbLoader', () => { expect((await loader.refreshIfNeeded(true)).status).toBe('not-modified') expect(fetchMock.mock.calls[1][1].headers['If-None-Match']).toBe('"same"') expect( - new ProviderDbLoader().getModel('zhipuai', 'glm-5.3')?.extra_capabilities?.reasoning - ?.effort_options + createLoader().getModel('zhipuai', 'glm-5.3')?.extra_capabilities?.reasoning?.effort_options ).toEqual(['low', 'high', 'max']) } ) @@ -269,8 +371,8 @@ describe('ProviderDbLoader', () => { }) vi.stubGlobal('fetch', fetchMock) - const ProviderDbLoader = await importLoader() - const loader = new ProviderDbLoader() + const createLoader = await importLoaderFactory() + const loader = createLoader() const result = await loader.refreshIfNeeded(true) @@ -301,8 +403,8 @@ describe('ProviderDbLoader', () => { }) vi.stubGlobal('fetch', fetchMock) - const ProviderDbLoader = await importLoader() - const loader = new ProviderDbLoader() + const createLoader = await importLoaderFactory() + const loader = createLoader() const catalogChanged = vi.fn() loader.subscribeCatalogChanges(catalogChanged) @@ -332,8 +434,8 @@ describe('ProviderDbLoader', () => { }) vi.stubGlobal('fetch', fetchMock) - const ProviderDbLoader = await importLoader() - const loader = new ProviderDbLoader() + const createLoader = await importLoaderFactory() + const loader = createLoader() await loader.refreshIfNeeded(true) @@ -354,8 +456,8 @@ describe('ProviderDbLoader', () => { }) vi.stubGlobal('fetch', fetchMock) - const ProviderDbLoader = await importLoader() - const loader = new ProviderDbLoader() + const createLoader = await importLoaderFactory() + const loader = createLoader() loader.setPrivacyModeResolver(() => true) const result = await loader.refreshIfNeeded(true) @@ -378,8 +480,8 @@ describe('ProviderDbLoader', () => { vi.stubGlobal('fetch', vi.fn().mockRejectedValue(new Error('network down'))) - const ProviderDbLoader = await importLoader() - const loader = new ProviderDbLoader() + const createLoader = await importLoaderFactory() + const loader = createLoader() const result = await loader.refreshIfNeeded(true) diff --git a/test/main/provider/providerDbModelConfig.test.ts b/test/main/provider/providerDbModelConfig.test.ts index d5dd0f6f12..420d07cf2c 100644 --- a/test/main/provider/providerDbModelConfig.test.ts +++ b/test/main/provider/providerDbModelConfig.test.ts @@ -1,44 +1,9 @@ import { describe, it, expect, beforeEach, vi } from 'vitest' -// Mock electron-store with in-memory storage to persist user configs across helper instances -const state = vi.hoisted(() => ({ - mockStores: new Map>(), - mockDb: null as any -})) -vi.mock('electron-store', () => { - return { - default: class MockElectronStore { - private storePath: string - private data: Record - constructor(options: { name: string }) { - this.storePath = options.name - if (!state.mockStores.has(this.storePath)) state.mockStores.set(this.storePath, {}) - this.data = state.mockStores.get(this.storePath)! - } - get(key: string) { - return this.data[key] - } - set(key: string, value: any) { - this.data[key] = value - } - delete(key: string) { - delete this.data[key] - } - has(key: string) { - return key in this.data - } - clear() { - Object.keys(this.data).forEach((k) => delete this.data[k]) - } - get store() { - return { ...this.data } - } - get path() { - return `/mock/${this.storePath}.json` - } - } - } -}) +import { createMemoryStore } from '../../helpers/memoryStore' + +const state = vi.hoisted(() => ({ mockDb: null as any })) +let modelStore: ReturnType // Mock providerDbLoader with a mutable in-memory aggregate vi.mock('../../../src/main/provider/providerDbLoader', () => { @@ -58,8 +23,7 @@ import { resolveCapabilityIdentity } from '../../../src/main/provider/capability describe('Provider DB strict matching and user overrides', () => { beforeEach(() => { - // Reset stores and mock DB before each test - state.mockStores.clear() + modelStore = createMemoryStore() state.mockDb = { providers: { openai: { @@ -285,7 +249,7 @@ describe('Provider DB strict matching and user overrides', () => { }) it('returns provider DB config on strict provider+model match', () => { - const helper = new ModelConfigHelper() + const helper = new ModelConfigHelper(modelStore) const cfg = helper.getModelConfig('test-model', 'test-provider') expect(cfg.maxTokens).toBe(2000) expect(cfg.contextLength).toBe(10000) @@ -309,7 +273,7 @@ describe('Provider DB strict matching and user overrides', () => { reasoning: { supported: false } }) ;(modelCapabilities as any).rebuildIndexFromDb() - const helper = new ModelConfigHelper() + const helper = new ModelConfigHelper(modelStore) for (const prefix of ['codex', 'cursor', 'custom-source']) { const modelId = `${prefix}/gpt-5.6-sol` @@ -351,7 +315,7 @@ describe('Provider DB strict matching and user overrides', () => { }) it('resolves OpenAI Codex models through the OpenAI catalog identity', () => { - const helper = new ModelConfigHelper() + const helper = new ModelConfigHelper(modelStore) const config = helper.getModelConfig('gpt-5.6-sol', 'openai-codex') @@ -363,7 +327,7 @@ describe('Provider DB strict matching and user overrides', () => { }) it('resolves the OpenAI Codex image model through the OpenAI catalog identity', () => { - const helper = new ModelConfigHelper() + const helper = new ModelConfigHelper(modelStore) const config = helper.getModelConfig('gpt-image-2', 'openai-codex') @@ -380,7 +344,7 @@ describe('Provider DB strict matching and user overrides', () => { it.each(['gpt-image-2.5-sunburst', 'gpt-image-2.5-flare'])( 'routes Codex image model %s without catalog metadata and preserves user settings', (modelId) => { - const helper = new ModelConfigHelper() + const helper = new ModelConfigHelper(modelStore) expect(helper.getModelConfig(modelId, 'openai-codex')).toMatchObject({ type: ModelType.ImageGeneration, apiEndpoint: ApiEndpointType.Image, @@ -408,7 +372,7 @@ describe('Provider DB strict matching and user overrides', () => { ) it('applies partial fallbacks when limit fields are missing', () => { - const helper = new ModelConfigHelper() + const helper = new ModelConfigHelper(modelStore) const cfg1 = helper.getModelConfig('partial-limit', 'test-provider') expect(cfg1.contextLength).toBe(16000) expect(cfg1.maxTokens).toBe(4096) @@ -426,7 +390,7 @@ describe('Provider DB strict matching and user overrides', () => { }) it('caps provider-derived maxTokens defaults at 32000', () => { - const helper = new ModelConfigHelper() + const helper = new ModelConfigHelper(modelStore) const cfg = helper.getModelConfig('large-output', 'test-provider') expect(cfg.contextLength).toBe(200000) @@ -434,7 +398,7 @@ describe('Provider DB strict matching and user overrides', () => { }) it('preserves explicit tool_call=false from provider DB', () => { - const helper = new ModelConfigHelper() + const helper = new ModelConfigHelper(modelStore) const cfg = helper.getModelConfig('tool-call-disabled', 'test-provider') expect(cfg.contextLength).toBe(16000) expect(cfg.maxTokens).toBe(4096) @@ -442,7 +406,7 @@ describe('Provider DB strict matching and user overrides', () => { }) it('falls back to safe defaults when providerId is not provided', () => { - const helper = new ModelConfigHelper() + const helper = new ModelConfigHelper(modelStore) const cfg = helper.getModelConfig('test-model') expect(cfg.contextLength).toBe(16000) expect(cfg.maxTokens).toBe(4096) @@ -451,7 +415,7 @@ describe('Provider DB strict matching and user overrides', () => { }) it('uses capability identity instead of provider iteration order for proxy defaults', () => { - const helper = new ModelConfigHelper() + const helper = new ModelConfigHelper(modelStore) const proxyConfig = helper.getModelConfig('kimi-k3', 'new-api') const directConfig = helper.getModelConfig('kimi-k3', 'moonshot') @@ -473,7 +437,7 @@ describe('Provider DB strict matching and user overrides', () => { }) it('does not inherit globally unique defaults for custom models under a known provider', () => { - const helper = new ModelConfigHelper() + const helper = new ModelConfigHelper(modelStore) const knownProviderConfig = helper.getModelConfig('foreign-only', 'test-provider') const proxyConfig = helper.getModelConfig('foreign-only', 'new-api') @@ -490,7 +454,7 @@ describe('Provider DB strict matching and user overrides', () => { }) it('preserves user ownership when reading route-only configuration', () => { - const helper = new ModelConfigHelper() + const helper = new ModelConfigHelper(modelStore) const helperInternals = helper as any const cacheKey = helperInternals.generateCacheKey('new-api', 'custom-route-model') @@ -526,7 +490,7 @@ describe('Provider DB strict matching and user overrides', () => { }) it('keeps an explicit capability provider override authoritative for proxy defaults', () => { - const helper = new ModelConfigHelper() + const helper = new ModelConfigHelper(modelStore) const config = helper.getModelConfig('kimi-k3', 'new-api', 'capability-team') @@ -537,7 +501,7 @@ describe('Provider DB strict matching and user overrides', () => { }) it('uses the dominant catalog profile when limits differ but capabilities agree', () => { - const helper = new ModelConfigHelper() + const helper = new ModelConfigHelper(modelStore) const cfg = helper.getModelConfig('shared-model', 'new-api') @@ -570,7 +534,7 @@ describe('Provider DB strict matching and user overrides', () => { ...facts, supportedEndpointTypes: [...facts.supportedEndpointTypes] }) - const helper = new ModelConfigHelper() + const helper = new ModelConfigHelper(modelStore) expect(resolveConfig(helper)).toMatchObject({ contextLength: 1_050_000, @@ -591,7 +555,7 @@ describe('Provider DB strict matching and user overrides', () => { maxTokens: 32_000 }) - const restartedHelper = new ModelConfigHelper() + const restartedHelper = new ModelConfigHelper(modelStore) expect(resolveConfig(restartedHelper)).toMatchObject({ contextLength: 1_050_000, maxTokens: 32_000 @@ -620,7 +584,7 @@ describe('Provider DB strict matching and user overrides', () => { ownedBy: 'openai', endpointType: 'openai' }) - const helper = new ModelConfigHelper() + const helper = new ModelConfigHelper(modelStore) const config = helper.getModelConfig(modelId, providerId, undefined, identity, { id: modelId, name: 'Opaque Renderer', @@ -645,7 +609,7 @@ describe('Provider DB strict matching and user overrides', () => { ownedBy: 'openai', endpointType: 'openai' }) - const helper = new ModelConfigHelper() + const helper = new ModelConfigHelper(modelStore) const config = helper.getModelConfig(modelId, providerId, undefined, identity, { id: modelId, name: 'Opaque Renderer', @@ -663,7 +627,7 @@ describe('Provider DB strict matching and user overrides', () => { }) it('prefers user config over provider DB and persists across restart', () => { - const helper1 = new ModelConfigHelper() + const helper1 = new ModelConfigHelper(modelStore) const userCfg = { maxTokens: 64000, contextLength: 128000, @@ -678,18 +642,18 @@ describe('Provider DB strict matching and user overrides', () => { expect(read1).toMatchObject({ ...userCfg, isUserDefined: true }) // Simulate app restart: new helper instance, same version - const helper2 = new ModelConfigHelper() + const helper2 = new ModelConfigHelper(modelStore) const read2 = helper2.getModelConfig('test-model', 'test-provider') expect(read2).toMatchObject({ ...userCfg, isUserDefined: true }) // Simulate version bump: non-user entries would be cleared, user entries remain - const helper3 = new ModelConfigHelper() + const helper3 = new ModelConfigHelper(modelStore) const read3 = helper3.getModelConfig('test-model', 'test-provider') expect(read3).toMatchObject({ ...userCfg, isUserDefined: true }) }) it('rejects provider-derived imports while preserving user values', () => { - const helper = new ModelConfigHelper() + const helper = new ModelConfigHelper(modelStore) const helperAny = helper as any const providerCacheKey = helperAny.generateCacheKey('test-provider', 'large-output') @@ -734,7 +698,7 @@ describe('Provider DB strict matching and user overrides', () => { }) it('matches DB with case-insensitive provider/model IDs for provider data (strictly lowercase in DB)', () => { - const helper = new ModelConfigHelper() + const helper = new ModelConfigHelper(modelStore) const cfg = helper.getModelConfig('TEST-MODEL', 'TEST-PROVIDER') // DB lookup lowercases internally expect(cfg.contextLength).toBe(10000) @@ -742,7 +706,7 @@ describe('Provider DB strict matching and user overrides', () => { }) it('matches mixed-case provider DB model IDs case-insensitively', () => { - const helper = new ModelConfigHelper() + const helper = new ModelConfigHelper(modelStore) const cfg = helper.getModelConfig('minimax-m2.5', 'minimax') @@ -753,7 +717,7 @@ describe('Provider DB strict matching and user overrides', () => { }) it('applies MiniMax-M3 provider defaults when the provider DB cache is stale', () => { - const helper = new ModelConfigHelper() + const helper = new ModelConfigHelper(modelStore) const cfg = helper.getModelConfig('minimax-m3', 'minimax') @@ -766,7 +730,7 @@ describe('Provider DB strict matching and user overrides', () => { }) it('keeps MiniMax-M3 context floor after provider facts merge', () => { - const helper = new ModelConfigHelper() + const helper = new ModelConfigHelper(modelStore) const cfg = helper.getModelConfig('minimax-m3', 'minimax', undefined, undefined, { id: 'minimax-m3', name: 'MiniMax M3', @@ -786,7 +750,7 @@ describe('Provider DB strict matching and user overrides', () => { }) it('prefers portrait defaults over legacy reasoning defaults', () => { - const helper = new ModelConfigHelper() + const helper = new ModelConfigHelper(modelStore) const cfg = helper.getModelConfig('claude-portrait', 'test-provider') @@ -796,7 +760,7 @@ describe('Provider DB strict matching and user overrides', () => { }) it('preserves provider portrait sentinel budgets', () => { - const helper = new ModelConfigHelper() + const helper = new ModelConfigHelper(modelStore) const cfg = helper.getModelConfig('gemini-budget', 'test-provider') @@ -805,7 +769,7 @@ describe('Provider DB strict matching and user overrides', () => { }) it('keeps none as the provider default effort without enabling reasoning', () => { - const helper = new ModelConfigHelper() + const helper = new ModelConfigHelper(modelStore) const cfg = helper.getModelConfig('gpt-5.2', 'test-provider') @@ -815,7 +779,7 @@ describe('Provider DB strict matching and user overrides', () => { }) it('forces Moonshot Kimi defaults to the thinking-enabled temperature when reasoning defaults on', () => { - const helper = new ModelConfigHelper() + const helper = new ModelConfigHelper(modelStore) const cfg = helper.getModelConfig('moonshotai/kimi-k2.6', 'moonshot') @@ -824,7 +788,7 @@ describe('Provider DB strict matching and user overrides', () => { }) it('forces Moonshot Kimi :thinking variants to keep reasoning on and temperature at 1.0', () => { - const helper = new ModelConfigHelper() + const helper = new ModelConfigHelper(modelStore) const cfg = helper.getModelConfig('moonshotai/kimi-k2.6:thinking', 'moonshot') @@ -833,7 +797,7 @@ describe('Provider DB strict matching and user overrides', () => { }) it('merges provider facts without persisting derived configuration', () => { - const helper = new ModelConfigHelper() + const helper = new ModelConfigHelper(modelStore) const cfg = helper.getModelConfig('claude-portrait', 'test-provider', undefined, undefined, { id: 'claude-portrait', diff --git a/test/main/provider/providerHeaders.test.ts b/test/main/provider/providerHeaders.test.ts index 88eb569049..3fe7e76e41 100644 --- a/test/main/provider/providerHeaders.test.ts +++ b/test/main/provider/providerHeaders.test.ts @@ -7,11 +7,25 @@ import { } from '@shared/providerCustomHeaders' import type { LLM_PROVIDER } from '@shared/types/provider' import { + buildDefaultProviderHeaders, buildProviderTraceHeaders, fetchWithProviderHeaders, MASKED_PROVIDER_CUSTOM_HEADER_VALUE } from '@/provider/providerHeaders' +describe('provider application metadata', () => { + it('uses the supplied application version and keeps header snapshots independent', () => { + const headers = buildDefaultProviderHeaders('1.2.3') + expect(headers).toEqual({ + 'HTTP-Referer': 'https://deepchatai.cn', + 'X-Title': 'DeepChat', + 'User-Agent': 'DeepChat/1.2.3' + }) + headers['X-Title'] = 'Changed' + expect(buildDefaultProviderHeaders('2.0.0')['X-Title']).toBe('DeepChat') + }) +}) + const createProvider = (customHeaders?: ProviderCustomHeaders): LLM_PROVIDER => ({ id: 'gateway', name: 'Gateway', @@ -322,15 +336,17 @@ describe('fetchWithProviderHeaders', () => { controller.close() } }) - const request = new Request('https://gateway.example.com/v1/chat/completions', { + const requestInit: RequestInit & { duplex: 'half' } = { method: 'POST', body, duplex: 'half' - }) + } + const request = new Request('https://gateway.example.com/v1/chat/completions', requestInit) await fetchWithProviderHeaders(provider, request, undefined, fetchImpl) - expect(fetchImpl.mock.calls[1]?.[1]?.duplex).toBe('half') + const redirected = fetchImpl.mock.calls[1]?.[1] as RequestInit & { duplex?: string } + expect(redirected.duplex).toBe('half') }) it('strips custom and credential headers on cross-origin redirects', async () => { diff --git a/test/main/provider/providerInstanceManager.acpLifetime.test.ts b/test/main/provider/providerInstanceManager.acpLifetime.test.ts index 8983e61ea9..65e4c4f25c 100644 --- a/test/main/provider/providerInstanceManager.acpLifetime.test.ts +++ b/test/main/provider/providerInstanceManager.acpLifetime.test.ts @@ -1,3 +1,4 @@ +import { providerTestEnvironment } from '../../helpers/providerEnvironment' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' import type { LLM_PROVIDER } from '@shared/types/provider' import type { AcpClientRuntime } from '@/agent/acp/client' @@ -73,7 +74,11 @@ describe('ProviderInstanceManager ACP lifetime', () => { providerSettings: { getProviders: () => [...providers.values()] } as never, - locale: { getLanguage: () => 'en-US' }, + locale: { + allowInsecureTls: providerTestEnvironment.allowInsecureTls, + defaultHeaders: providerTestEnvironment.defaultHeaders, + getLanguage: () => 'en-US' + }, activeStreams: new Map(), rateLimitManager: { syncProviders: vi.fn(), @@ -122,7 +127,11 @@ describe('ProviderInstanceManager ACP lifetime', () => { providerSettings: { getProviders: () => [...providers.values()] } as never, - locale: { getLanguage: () => 'en-US' }, + locale: { + allowInsecureTls: providerTestEnvironment.allowInsecureTls, + defaultHeaders: providerTestEnvironment.defaultHeaders, + getLanguage: () => 'en-US' + }, activeStreams: new Map(), rateLimitManager: { syncProviders: vi.fn(), @@ -158,7 +167,11 @@ describe('ProviderInstanceManager ACP lifetime', () => { providerSettings: { getProviders: () => [...providers.values()] } as never, - locale: { getLanguage: () => 'en-US' }, + locale: { + allowInsecureTls: providerTestEnvironment.allowInsecureTls, + defaultHeaders: providerTestEnvironment.defaultHeaders, + getLanguage: () => 'en-US' + }, activeStreams: new Map([['stream', { providerId: 'acp', abortController } as never]]), rateLimitManager: { syncProviders: vi.fn(), @@ -238,7 +251,11 @@ describe('ProviderInstanceManager ACP lifetime', () => { ) const manager = new ProviderInstanceManager({ providerSettings: providerSettings as never, - locale: { getLanguage: () => 'en-US' }, + locale: { + allowInsecureTls: providerTestEnvironment.allowInsecureTls, + defaultHeaders: providerTestEnvironment.defaultHeaders, + getLanguage: () => 'en-US' + }, activeStreams: new Map(), rateLimitManager, getCurrentProviderId: () => null, diff --git a/test/main/provider/providerModelHelper.test.ts b/test/main/provider/providerModelHelper.test.ts index 36c0242602..df9ec48560 100644 --- a/test/main/provider/providerModelHelper.test.ts +++ b/test/main/provider/providerModelHelper.test.ts @@ -1,68 +1,32 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' -import type { ModelConfig } from '@shared/types/provider' +import type { MODEL_META, ModelConfig } from '@shared/types/provider' import { ApiEndpointType, ModelType } from '../../../src/shared/model' +import { createMemoryStore } from '../../helpers/memoryStore' -const storeStates = vi.hoisted( - () => - new Map< - string, - { - data: Record - get: ReturnType - set: ReturnType - clear: ReturnType - } - >() -) - +const storeStates = new Map>() const publishDeepchatEventMock = vi.hoisted(() => vi.fn()) -vi.mock('electron-store', () => ({ - default: class MockElectronStore { - private readonly state: { - data: Record - get: ReturnType - set: ReturnType - clear: ReturnType - } - - constructor(options: { name: string; defaults?: Record }) { - const existing = storeStates.get(options.name) - if (existing) { - this.state = existing - return - } - - const data = structuredClone(options.defaults ?? {}) - const state = { - data, - get: vi.fn((key: string) => data[key]), - set: vi.fn((key: string, value: unknown) => { - data[key] = value - }), - clear: vi.fn(() => { - Object.keys(data).forEach((key) => { - delete data[key] - }) - }) - } - storeStates.set(options.name, state) - this.state = state - } - - get(key: string) { - return this.state.get(key) - } - - set(key: string, value: unknown) { - this.state.set(key, value) - } - - clear() { - this.state.clear() - } +function createModelStoreData() { + const store = createMemoryStore<{ models: MODEL_META[]; custom_models: MODEL_META[] }>({ + models: [], + custom_models: [] + }) + return { + ...store, + get store() { + return store.store + }, + get: vi.fn(store.get) as typeof store.get & ReturnType, + set: vi.fn(store.set) as typeof store.set & ReturnType, + clear: vi.fn(store.clear) } -})) +} + +function createModelStore(providerId: string) { + const key = `models_${encodeURIComponent(providerId).replace(/\*/g, '%2A')}` + if (!storeStates.has(key)) storeStates.set(key, createModelStoreData()) + return storeStates.get(key)! +} vi.mock('electron', () => ({ app: { @@ -88,6 +52,7 @@ const createBaseModel = (providerId: string, modelId: string) => ({ id: modelId, name: modelId, providerId, + group: 'default', contextLength: 32000, maxTokens: 8000, isCustom: false, @@ -139,7 +104,7 @@ describe('ProviderModelHelper cache', () => { it('reuses provider model snapshots within the ttl window', async () => { const { ProviderModelHelper } = await import('../../../src/main/provider/providerModelHelper') const helper = new ProviderModelHelper({ - userDataPath: 'C:/mock-user-data', + createStore: createModelStore, setModelStatus: vi.fn(), deleteModelStatus: vi.fn(), publishEvent: publishDeepchatEventMock @@ -163,7 +128,7 @@ describe('ProviderModelHelper cache', () => { it('invalidates cached provider models after setProviderModels writes', async () => { const { ProviderModelHelper } = await import('../../../src/main/provider/providerModelHelper') const helper = new ProviderModelHelper({ - userDataPath: 'C:/mock-user-data', + createStore: createModelStore, setModelStatus: vi.fn(), deleteModelStatus: vi.fn(), publishEvent: publishDeepchatEventMock @@ -191,7 +156,7 @@ describe('ProviderModelHelper cache', () => { it('invalidates cached provider models after custom model writes', async () => { const { ProviderModelHelper } = await import('../../../src/main/provider/providerModelHelper') const helper = new ProviderModelHelper({ - userDataPath: 'C:/mock-user-data', + createStore: createModelStore, setModelStatus: vi.fn(), deleteModelStatus: vi.fn(), publishEvent: publishDeepchatEventMock @@ -218,7 +183,7 @@ describe('ProviderModelHelper cache', () => { it('removes stale catalog projections in memory without writing from the read path', async () => { const { ProviderModelHelper } = await import('../../../src/main/provider/providerModelHelper') const helper = new ProviderModelHelper({ - userDataPath: 'C:/mock-user-data', + createStore: createModelStore, setModelStatus: vi.fn(), deleteModelStatus: vi.fn(), publishEvent: publishDeepchatEventMock @@ -260,7 +225,7 @@ describe('ProviderModelHelper cache', () => { it('returns cached NewAPI route facts without deriving selectable endpoints', async () => { const { ProviderModelHelper } = await import('../../../src/main/provider/providerModelHelper') const helper = new ProviderModelHelper({ - userDataPath: 'C:/mock-user-data', + createStore: createModelStore, setModelStatus: vi.fn(), deleteModelStatus: vi.fn(), publishEvent: publishDeepchatEventMock @@ -293,7 +258,7 @@ describe('ProviderModelHelper cache', () => { it('does not materialize missing type fields in the raw cache', async () => { const { ProviderModelHelper } = await import('../../../src/main/provider/providerModelHelper') const helper = new ProviderModelHelper({ - userDataPath: 'C:/mock-user-data', + createStore: createModelStore, setModelStatus: vi.fn(), deleteModelStatus: vi.fn(), publishEvent: publishDeepchatEventMock @@ -326,7 +291,7 @@ describe('ProviderModelHelper cache', () => { it('drops selectable endpoints because they are an effective projection', async () => { const { ProviderModelHelper } = await import('../../../src/main/provider/providerModelHelper') const helper = new ProviderModelHelper({ - userDataPath: 'C:/mock-user-data', + createStore: createModelStore, setModelStatus: vi.fn(), deleteModelStatus: vi.fn(), publishEvent: publishDeepchatEventMock @@ -359,7 +324,7 @@ describe('ProviderModelHelper cache', () => { it('does not project effective types into cached provider facts', async () => { const { ProviderModelHelper } = await import('../../../src/main/provider/providerModelHelper') const helper = new ProviderModelHelper({ - userDataPath: 'C:/mock-user-data', + createStore: createModelStore, setModelStatus: vi.fn(), deleteModelStatus: vi.fn(), publishEvent: publishDeepchatEventMock @@ -398,7 +363,7 @@ describe('ProviderModelHelper cache', () => { it('keeps cached media endpoint metadata sparse', async () => { const { ProviderModelHelper } = await import('../../../src/main/provider/providerModelHelper') const helper = new ProviderModelHelper({ - userDataPath: 'C:/mock-user-data', + createStore: createModelStore, setModelStatus: vi.fn(), deleteModelStatus: vi.fn(), publishEvent: publishDeepchatEventMock @@ -426,7 +391,7 @@ describe('ProviderModelHelper cache', () => { it('does not derive media type from a sparse cached model id', async () => { const { ProviderModelHelper } = await import('../../../src/main/provider/providerModelHelper') const helper = new ProviderModelHelper({ - userDataPath: 'C:/mock-user-data', + createStore: createModelStore, setModelStatus: vi.fn(), deleteModelStatus: vi.fn(), publishEvent: publishDeepchatEventMock @@ -458,7 +423,7 @@ describe('ProviderModelHelper cache', () => { it('does not mix legacy user config into cached provider facts', async () => { const { ProviderModelHelper } = await import('../../../src/main/provider/providerModelHelper') const helper = new ProviderModelHelper({ - userDataPath: 'C:/mock-user-data', + createStore: createModelStore, setModelStatus: vi.fn(), deleteModelStatus: vi.fn(), publishEvent: publishDeepchatEventMock @@ -486,7 +451,7 @@ describe('ProviderModelHelper cache', () => { it('does not mix explicit user type into cached provider facts', async () => { const { ProviderModelHelper } = await import('../../../src/main/provider/providerModelHelper') const helper = new ProviderModelHelper({ - userDataPath: 'C:/mock-user-data', + createStore: createModelStore, setModelStatus: vi.fn(), deleteModelStatus: vi.fn(), publishEvent: publishDeepchatEventMock @@ -516,31 +481,31 @@ describe('ProviderModelHelper cache', () => { it('uses targeted model reads for route metadata when the store supports them', async () => { const { ProviderModelHelper } = await import('../../../src/main/provider/providerModelHelper') + const listModels = vi.fn() + const getProviderModel = vi.fn( + (source: 'provider' | 'custom', modelId: string): MODEL_META | undefined => + source === 'provider' && modelId === 'kimi-k3' + ? { + ...createBaseModel('new-api', modelId), + supportedEndpointTypes: ['openai', 'anthropic'] + } + : undefined + ) const helper = new ProviderModelHelper({ - userDataPath: 'C:/mock-user-data', setModelStatus: vi.fn(), deleteModelStatus: vi.fn(), - publishEvent: publishDeepchatEventMock + publishEvent: publishDeepchatEventMock, + createStore: () => ({ + store: { models: [], custom_models: [] }, + get(_key: string, defaultValue?: TValue): TValue | undefined { + listModels() + return defaultValue + }, + set: vi.fn(), + delete: vi.fn(), + getProviderModel + }) }) - const listModels = vi.fn() - const getProviderModel = vi.fn((source: 'provider' | 'custom', modelId: string) => - source === 'provider' && modelId === 'kimi-k3' - ? { - ...createBaseModel('new-api', modelId), - supportedEndpointTypes: ['openai', 'anthropic'] - } - : undefined - ) - helper.setStoreFactory(() => ({ - store: { models: [], custom_models: [] }, - get(_key: string, defaultValue?: TValue): TValue | undefined { - listModels() - return defaultValue - }, - set: vi.fn(), - delete: vi.fn(), - getProviderModel - })) expect(helper.getProviderModelRouteMetadata('new-api', 'kimi-k3')).toEqual({ endpointType: undefined, @@ -566,7 +531,7 @@ describe('ProviderModelHelper cache', () => { it('keeps stored non-New API model type authoritative in route metadata', async () => { const { ProviderModelHelper } = await import('../../../src/main/provider/providerModelHelper') const helper = new ProviderModelHelper({ - userDataPath: 'C:/mock-user-data', + createStore: createModelStore, setModelStatus: vi.fn(), deleteModelStatus: vi.fn(), publishEvent: publishDeepchatEventMock @@ -606,7 +571,7 @@ describe('ProviderModelHelper cache', () => { it('applies explicit user route intent after provider route facts', async () => { const { ProviderModelHelper } = await import('../../../src/main/provider/providerModelHelper') const helper = new ProviderModelHelper({ - userDataPath: 'C:/mock-user-data', + createStore: createModelStore, setModelStatus: vi.fn(), deleteModelStatus: vi.fn(), publishEvent: publishDeepchatEventMock @@ -640,13 +605,7 @@ describe('ProviderModelHelper cache', () => { it('does not synthesize a chat type for sparse route facts', async () => { const { ProviderModelHelper } = await import('../../../src/main/provider/providerModelHelper') - const helper = new ProviderModelHelper({ - userDataPath: 'C:/mock-user-data', - setModelStatus: vi.fn(), - deleteModelStatus: vi.fn(), - publishEvent: publishDeepchatEventMock - }) - const providerModel = { + const providerModel: MODEL_META = { id: 'aggregated-model', name: 'Aggregated Model', group: 'default', @@ -654,17 +613,22 @@ describe('ProviderModelHelper cache', () => { isCustom: false, endpointType: undefined, ownedBy: undefined, - supportedEndpointTypes: ['openai', 'anthropic'] as const + supportedEndpointTypes: ['openai', 'anthropic'] } - helper.setStoreFactory(() => ({ - store: { models: [providerModel], custom_models: [] }, - get(key: string, defaultValue?: TValue): TValue | undefined { - if (key === 'models') return [providerModel] as TValue - return defaultValue - }, - set: vi.fn(), - delete: vi.fn() - })) + const helper = new ProviderModelHelper({ + setModelStatus: vi.fn(), + deleteModelStatus: vi.fn(), + publishEvent: publishDeepchatEventMock, + createStore: () => ({ + store: { models: [providerModel], custom_models: [] }, + get(key: string, defaultValue?: TValue): TValue | undefined { + if (key === 'models') return [providerModel] as TValue + return defaultValue + }, + set: vi.fn(), + delete: vi.fn() + }) + }) expect(helper.getProviderModels('new-api')[0]).toMatchObject({ endpointType: undefined, @@ -680,12 +644,6 @@ describe('ProviderModelHelper cache', () => { it('checks targeted provider and custom rows after a fresh-cache miss', async () => { const { ProviderModelHelper } = await import('../../../src/main/provider/providerModelHelper') - const helper = new ProviderModelHelper({ - userDataPath: 'C:/mock-user-data', - setModelStatus: vi.fn(), - deleteModelStatus: vi.fn(), - publishEvent: publishDeepchatEventMock - }) const providerModel = createBaseModel('new-api', 'provider-model') const customModel = { ...createBaseModel('new-api', 'custom-model'), @@ -695,17 +653,22 @@ describe('ProviderModelHelper cache', () => { const getProviderModel = vi.fn((source: 'provider' | 'custom', modelId: string) => source === 'custom' && modelId === customModel.id ? customModel : undefined ) - helper.setStoreFactory(() => ({ - store: { models: [providerModel], custom_models: [customModel] }, - get(key: string, defaultValue?: TValue): TValue | undefined { - if (key === 'models') return [providerModel] as TValue - if (key === 'custom_models') return [customModel] as TValue - return defaultValue - }, - set: vi.fn(), - delete: vi.fn(), - getProviderModel - })) + const helper = new ProviderModelHelper({ + setModelStatus: vi.fn(), + deleteModelStatus: vi.fn(), + publishEvent: publishDeepchatEventMock, + createStore: () => ({ + store: { models: [providerModel], custom_models: [customModel] }, + get(key: string, defaultValue?: TValue): TValue | undefined { + if (key === 'models') return [providerModel] as TValue + if (key === 'custom_models') return [customModel] as TValue + return defaultValue + }, + set: vi.fn(), + delete: vi.fn(), + getProviderModel + }) + }) helper.getProviderModels('new-api') expect(helper.getProviderModelRouteMetadata('new-api', customModel.id)).toMatchObject({ @@ -718,12 +681,6 @@ describe('ProviderModelHelper cache', () => { it('finds a provider row inserted after a provider-list snapshot was cached', async () => { const { ProviderModelHelper } = await import('../../../src/main/provider/providerModelHelper') - const helper = new ProviderModelHelper({ - userDataPath: 'C:/mock-user-data', - setModelStatus: vi.fn(), - deleteModelStatus: vi.fn(), - publishEvent: publishDeepchatEventMock - }) const cachedModel = createBaseModel('new-api', 'cached-model') const insertedModel = { ...createBaseModel('new-api', 'inserted-model'), @@ -732,16 +689,21 @@ describe('ProviderModelHelper cache', () => { const getProviderModel = vi.fn((source: 'provider' | 'custom', modelId: string) => source === 'provider' && modelId === insertedModel.id ? insertedModel : undefined ) - helper.setStoreFactory(() => ({ - store: { models: [cachedModel], custom_models: [] }, - get(key: string, defaultValue?: TValue): TValue | undefined { - if (key === 'models') return [cachedModel] as TValue - return defaultValue - }, - set: vi.fn(), - delete: vi.fn(), - getProviderModel - })) + const helper = new ProviderModelHelper({ + setModelStatus: vi.fn(), + deleteModelStatus: vi.fn(), + publishEvent: publishDeepchatEventMock, + createStore: () => ({ + store: { models: [cachedModel], custom_models: [] }, + get(key: string, defaultValue?: TValue): TValue | undefined { + if (key === 'models') return [cachedModel] as TValue + return defaultValue + }, + set: vi.fn(), + delete: vi.fn(), + getProviderModel + }) + }) helper.getProviderModels('new-api') @@ -758,7 +720,7 @@ describe('ProviderModelHelper cache', () => { it('clears persisted provider models and custom models for a removed provider', async () => { const { ProviderModelHelper } = await import('../../../src/main/provider/providerModelHelper') const helper = new ProviderModelHelper({ - userDataPath: 'C:/mock-user-data', + createStore: createModelStore, setModelStatus: vi.fn(), deleteModelStatus: vi.fn(), publishEvent: publishDeepchatEventMock @@ -782,7 +744,7 @@ describe('ProviderModelHelper cache', () => { it('encodes invalid provider id characters before creating store files', async () => { const { ProviderModelHelper } = await import('../../../src/main/provider/providerModelHelper') const helper = new ProviderModelHelper({ - userDataPath: 'C:/mock-user-data', + createStore: createModelStore, setModelStatus: vi.fn(), deleteModelStatus: vi.fn(), publishEvent: publishDeepchatEventMock @@ -831,12 +793,12 @@ describe('ProviderSettings effective model projection', () => { }, publishEvent: publishDeepchatEventMock }) as InstanceType - const presenterWithHelper = presenter as InstanceType & { + const presenterWithHelper = presenter as unknown as { providerModelHelper: InstanceType } presenterWithHelper.providerModelHelper = new ProviderModelHelper({ - userDataPath: 'C:/mock-user-data', + createStore: createModelStore, setModelStatus: vi.fn(), deleteModelStatus: vi.fn(), publishEvent: publishDeepchatEventMock @@ -893,12 +855,12 @@ describe('ProviderSettings effective model projection', () => { }, publishEvent: publishDeepchatEventMock }) as InstanceType - const presenterWithHelper = presenter as InstanceType & { + const presenterWithHelper = presenter as unknown as { providerModelHelper: InstanceType } presenterWithHelper.providerModelHelper = new ProviderModelHelper({ - userDataPath: 'C:/mock-user-data', + createStore: createModelStore, setModelStatus: vi.fn(), deleteModelStatus: vi.fn(), publishEvent: publishDeepchatEventMock diff --git a/test/main/provider/providerRuntime.test.ts b/test/main/provider/providerRuntime.test.ts index f5c6bd0f28..07bff5e6e2 100644 --- a/test/main/provider/providerRuntime.test.ts +++ b/test/main/provider/providerRuntime.test.ts @@ -1,3 +1,4 @@ +import { providerTestEnvironment } from '../../helpers/providerEnvironment' import { describe, it, expect, beforeEach, vi, beforeAll, afterEach } from 'vitest' import { ProviderRuntime } from '../../../src/main/provider/index' import { ProviderSettings } from '../../../src/main/provider/settings' @@ -10,6 +11,7 @@ import { AcpRuntimeOwner } from '@/agent/acp/client' import { AcpSessionPersistence } from '@/agent/acp/runtime' import { DEFAULT_PROVIDERS } from '@/provider/defaults' import { ProviderInstanceManager } from '@/provider/managers/providerInstanceManager' +import type { ProviderCatalogStatePatch } from '@shared/types/providerCatalog' const { mockRunAiSdkCoreStream, @@ -112,15 +114,20 @@ describe('ProviderRuntime Integration Tests', () => { const createProviderRuntime = (providerSettings: ProviderSettings) => { const persistence = new AcpSessionPersistence( - mockSqlitePresenter, + mockSqlitePresenter as never, mockSqlitePresenter as never, { newEnvironmentsTable: { listPathsForSession: () => [], syncPath: vi.fn() } - } as never + } as never, + '/home/tester' ) return new ProviderRuntime( providerSettings, - { getLanguage: vi.fn().mockReturnValue('en-US') }, + { + allowInsecureTls: providerTestEnvironment.allowInsecureTls, + defaultHeaders: providerTestEnvironment.defaultHeaders, + getLanguage: vi.fn().mockReturnValue('en-US') + }, {} as never, {} as never, new AcpRuntimeOwner(() => { @@ -161,6 +168,16 @@ describe('ProviderRuntime Integration Tests', () => { setModelStatus: vi.fn(), updateCustomModel: vi.fn(), setProviderModels: vi.fn(), + setProviderCatalogState: vi.fn((providerId: string, patch: ProviderCatalogStatePatch) => ({ + providerId, + revision: 1, + fetchedAt: null, + source: null, + lastRefreshReason: null, + lastErrorCode: null, + lastErrorAt: null, + ...patch + })), getCustomModels: vi.fn().mockReturnValue([]), getProviderModels: vi.fn().mockReturnValue([]), getModelStatus: vi.fn().mockReturnValue(true), @@ -223,11 +240,116 @@ describe('ProviderRuntime Integration Tests', () => { vi.unstubAllGlobals() }) + describe('Copilot credential settlement', () => { + const setupCopilot = () => { + const provider: LLM_PROVIDER = { + ...mockProvider, + id: 'github-copilot', + apiType: 'github-copilot', + apiKey: 'saved-oauth-token', + copilotClientId: 'client-one', + enable: false + } + mockProviderSettings.getProviderById = vi.fn((id) => + id === provider.id ? { ...provider } : undefined + ) + mockProviderSettings.getProviders = vi.fn(() => [{ ...provider }]) + mockProviderSettings.setProviderById = vi.fn((_id, next) => Object.assign(provider, next)) + return provider + } + + it('returns a secret-free sign-in target and preserves current unrelated settings on commit', async () => { + const provider = setupCopilot() + const target = await providerRuntime.getGitHubCopilotSignInTarget(provider.id) + expect(Object.keys(target).sort()).toEqual(['clientId', 'configurationId', 'providerId']) + expect(JSON.stringify(target)).not.toContain(provider.apiKey) + provider.name = 'Renamed Copilot' + provider.customHeaders = { 'X-Custom': 'current' } + provider.copilotClientId = ' client-one ' + await providerRuntime.commitGitHubCopilotCredential(target, 'new-oauth-token') + expect(mockProviderSettings.setProviderById).toHaveBeenCalledWith(provider.id, { + ...provider, + name: 'Renamed Copilot', + apiKey: 'new-oauth-token', + customHeaders: { 'X-Custom': 'current' } + }) + }) + + it.each(['credentials', 'client', 'protocol', 'removal'] as const)( + 'rejects a stale sign-in target after a provider change: %s', + async (change) => { + const provider = setupCopilot() + const target = await providerRuntime.getGitHubCopilotSignInTarget(provider.id) + if (change === 'credentials') provider.apiKey = 'newer-token' + if (change === 'client') provider.copilotClientId = 'different-client' + if (change === 'protocol') provider.apiType = 'openai-compatible' + if (change === 'removal') + vi.mocked(mockProviderSettings.getProviderById).mockReturnValue(undefined) + await expect( + providerRuntime.commitGitHubCopilotCredential(target, 'new-oauth-token') + ).rejects.toThrow('configuration changed') + expect(mockProviderSettings.setProviderById).not.toHaveBeenCalled() + } + ) + + it('settles concurrent sign-ins against the committed identity without overwriting the winner', async () => { + const provider = setupCopilot() + const target = await providerRuntime.getGitHubCopilotSignInTarget(provider.id) + const results = await Promise.allSettled([ + providerRuntime.commitGitHubCopilotCredential(target, 'first-token'), + providerRuntime.commitGitHubCopilotCredential(target, 'second-token') + ]) + expect(results.map((result) => result.status)).toEqual(['fulfilled', 'rejected']) + expect(provider.apiKey).toBe('first-token') + expect(mockProviderSettings.setProviderById).toHaveBeenCalledOnce() + }) + + it('invalidates outstanding sign-ins across maintenance and rejects work after shutdown', async () => { + const provider = setupCopilot() + const target = await providerRuntime.getGitHubCopilotSignInTarget(provider.id) + await providerRuntime.invalidateGitHubCopilotSignIns() + await expect( + providerRuntime.commitGitHubCopilotCredential(target, 'new-token') + ).rejects.toThrow('configuration changed') + const fresh = await providerRuntime.getGitHubCopilotSignInTarget(provider.id) + await providerRuntime.shutdown() + await expect( + providerRuntime.commitGitHubCopilotCredential(fresh, 'new-token') + ).rejects.toThrow('stopped') + await expect(providerRuntime.getGitHubCopilotSignInTarget(provider.id)).rejects.toThrow( + 'stopped' + ) + expect(mockProviderSettings.setProviderById).not.toHaveBeenCalled() + }) + + it('does not accept a target issued by another runtime or a malformed token', async () => { + const provider = setupCopilot() + const other = createProviderRuntime(mockProviderSettings) + try { + const foreign = await other.getGitHubCopilotSignInTarget(provider.id) + await expect( + providerRuntime.commitGitHubCopilotCredential(foreign, 'new-token') + ).rejects.toThrow('configuration changed') + const target = await providerRuntime.getGitHubCopilotSignInTarget(provider.id) + await expect( + providerRuntime.commitGitHubCopilotCredential(target, 'token\nheader') + ).rejects.toThrow('Invalid') + expect(mockProviderSettings.setProviderById).not.toHaveBeenCalled() + } finally { + await other.shutdown() + } + }) + }) + it('instantiates every built-in profile without an extra special-provider allowlist', () => { const manager = new ProviderInstanceManager({ providerSettings: mockProviderSettings, - locale: { getLanguage: () => 'en-US' }, - agentSettings: { getAcpEnabled: () => false, getAcpAgents: () => [] }, + locale: { + allowInsecureTls: providerTestEnvironment.allowInsecureTls, + defaultHeaders: providerTestEnvironment.defaultHeaders, + getLanguage: () => 'en-US' + }, + agentSettings: { getAcpEnabled: async () => false, getAcpAgents: async () => [] }, activeStreams: new Map(), rateLimitManager: { cleanupProviderRateLimit: vi.fn(), syncProviders: vi.fn() } as never, getCurrentProviderId: () => null, diff --git a/test/main/provider/workersAiProvider.test.ts b/test/main/provider/workersAiProvider.test.ts index 565c1834ce..63315bf089 100644 --- a/test/main/provider/workersAiProvider.test.ts +++ b/test/main/provider/workersAiProvider.test.ts @@ -1,3 +1,4 @@ +import { providerTestEnvironment } from '../../helpers/providerEnvironment' import type { ProviderSettingsPort } from '@/provider/settings' import { ModelType } from '@shared/model' import type { LLM_PROVIDER } from '@shared/types/provider' @@ -75,6 +76,8 @@ const createProviderSettings = (): ProviderSettingsPort => const createProviderInstance = (overrides?: Partial) => new WorkersAiProvider(createProvider(overrides), createProviderSettings(), { + allowInsecureTls: providerTestEnvironment.allowInsecureTls, + defaultHeaders: providerTestEnvironment.defaultHeaders, getLanguage: vi.fn().mockReturnValue('en-US') }) @@ -549,7 +552,11 @@ describe('WorkersAiProvider', () => { const chatOnly = new AiSdkProvider( createProvider({ apiType: 'openai-completions' }), createProviderSettings(), - { getLanguage: vi.fn().mockReturnValue('en-US') } + { + allowInsecureTls: providerTestEnvironment.allowInsecureTls, + defaultHeaders: providerTestEnvironment.defaultHeaders, + getLanguage: vi.fn().mockReturnValue('en-US') + } ) expect(supportsJevJudgment(chatOnly)).toBe(false) diff --git a/test/main/provider/xaiGrokAuthAdapter.test.ts b/test/main/provider/xaiGrokAuthAdapter.test.ts new file mode 100644 index 0000000000..2c322dfa6c --- /dev/null +++ b/test/main/provider/xaiGrokAuthAdapter.test.ts @@ -0,0 +1,110 @@ +import { beforeEach, describe, expect, it, vi } from 'vitest' +import type { LLM_PROVIDER } from '@shared/types/provider' +import { createXaiGrokFetch } from '@/provider/xaiGrokAuthAdapter' + +const auth = vi.hoisted(() => ({ + captureOperation: vi.fn<() => () => void>(), + ensureAccessToken: vi.fn<() => Promise>(), + forceRefreshAccessToken: vi.fn<() => Promise>() +})) +vi.mock('@/provider/auth/xaiGrok', () => ({ getGlobalXaiGrokAuth: () => auth })) +const provider: LLM_PROVIDER = { + id: 'grok', + name: 'Grok', + apiType: 'grok', + apiKey: 'fixture-api-key', + baseUrl: 'https://api.x.ai/v1', + enable: true +} + +describe('Grok per-request credentials', () => { + beforeEach(() => { + vi.resetAllMocks() + auth.captureOperation.mockReturnValue(() => undefined) + auth.ensureAccessToken.mockResolvedValue('oauth-token') + auth.forceRefreshAccessToken.mockResolvedValue('refreshed-token') + }) + + it('prefers OAuth, retries once on 401 and overrides stale headers with the refreshed token', async () => { + const fetch = vi + .fn() + .mockResolvedValueOnce(new Response(null, { status: 401 })) + .mockResolvedValueOnce(Response.json({ ok: true })) + await createXaiGrokFetch( + provider, + { Authorization: 'stale-default' }, + fetch + )('https://api.x.ai/v1/models', { headers: { Authorization: 'stale-request' } }) + expect(fetch).toHaveBeenCalledTimes(2) + expect(new Headers(fetch.mock.calls[0][1]?.headers).get('Authorization')).toBe( + 'Bearer oauth-token' + ) + expect(new Headers(fetch.mock.calls[1][1]?.headers).get('Authorization')).toBe( + 'Bearer refreshed-token' + ) + expect(auth.forceRefreshAccessToken).toHaveBeenCalledOnce() + }) + + it('retains API-key fallback for an ordinary refresh failure', async () => { + auth.ensureAccessToken.mockRejectedValue(new Error('refresh failed')) + const fetch = vi.fn().mockResolvedValue(Response.json({ ok: true })) + await createXaiGrokFetch(provider, {}, fetch)('https://api.x.ai/v1/models') + expect(new Headers(fetch.mock.calls[0][1]?.headers).get('Authorization')).toBe( + 'Bearer fixture-api-key' + ) + }) + + it.each(['request abort', 'auth replacement'] as const)( + 'does not turn %s into API-key fallback', + async (change) => { + let finish!: (value: string) => void + auth.ensureAccessToken.mockReturnValue( + new Promise((resolve) => { + finish = resolve + }) + ) + let current = true + auth.captureOperation.mockReturnValue(() => { + if (!current) throw new DOMException('Auth changed', 'AbortError') + }) + const controller = new AbortController() + const fetch = vi.fn() + const pending = createXaiGrokFetch( + provider, + {}, + fetch + )('https://api.x.ai/v1/models', { signal: controller.signal }) + const rejected = expect(pending).rejects.toMatchObject({ name: 'AbortError' }) + if (change === 'request abort') controller.abort() + else current = false + finish('late-token') + await rejected + expect(fetch).not.toHaveBeenCalled() + } + ) + + it('rejects an interrupted auth owner before falling back to an API key', async () => { + auth.ensureAccessToken.mockRejectedValue(new DOMException('Stopped', 'AbortError')) + const fetch = vi.fn() + await expect( + createXaiGrokFetch(provider, {}, fetch)('https://api.x.ai/v1/models') + ).rejects.toMatchObject({ name: 'AbortError' }) + expect(fetch).not.toHaveBeenCalled() + }) + + it('does not refresh or retry after auth is revoked with a 401 response pending', async () => { + let current = true + auth.captureOperation.mockReturnValue(() => { + if (!current) throw new DOMException('Auth changed', 'AbortError') + }) + const fetch = vi.fn().mockImplementationOnce(async () => { + current = false + return new Response(null, { status: 401 }) + }) + await expect( + createXaiGrokFetch(provider, {}, fetch)('https://api.x.ai/v1/models') + ).rejects.toMatchObject({ name: 'AbortError' }) + expect(fetch).toHaveBeenCalledOnce() + expect(auth.forceRefreshAccessToken).not.toHaveBeenCalled() + }) +}) diff --git a/test/main/provider/xaiGrokCredentialStore.test.ts b/test/main/provider/xaiGrokCredentialStore.test.ts deleted file mode 100644 index a0b96b6e13..0000000000 --- a/test/main/provider/xaiGrokCredentialStore.test.ts +++ /dev/null @@ -1,203 +0,0 @@ -import * as fs from 'fs' -import { beforeEach, describe, expect, it, vi } from 'vitest' -import { safeStorage } from 'electron' -import { - XaiGrokCredentialStore, - type XaiGrokTokenSet -} from '@/provider/auth/xaiGrok/credentialStore' - -const filePath = '/tmp/deepchat-xai-grok/credentials.json' - -const tokens: XaiGrokTokenSet = { - accessToken: 'access-token', - refreshToken: 'refresh-token', - tokenType: 'Bearer', - expiresAt: Date.now() + 3_600_000, - updatedAt: Date.now() -} - -function fileEnvelope(value: XaiGrokTokenSet): string { - return JSON.stringify({ version: 1, storage: 'file', tokens: value, updatedAt: 1 }) -} - -describe('XaiGrokCredentialStore', () => { - let savedContent: string | null = null - - beforeEach(() => { - savedContent = null - vi.mocked(fs.readFileSync).mockImplementation(() => { - if (savedContent === null) { - throw Object.assign(new Error('ENOENT: no such file or directory'), { code: 'ENOENT' }) - } - return savedContent - }) - vi.mocked(fs.writeFileSync).mockImplementation((_, data) => { - savedContent = String(data) - }) - vi.mocked(fs.mkdirSync).mockImplementation(() => undefined as unknown as string) - vi.mocked(fs.renameSync).mockImplementation(() => {}) - vi.mocked(fs.readdirSync).mockImplementation(() => []) - vi.mocked(safeStorage.isEncryptionAvailable).mockReturnValue(false) - }) - - it('returns null without an error when the credential file is missing', () => { - const warn = vi.spyOn(console, 'warn').mockImplementation(() => {}) - const store = new XaiGrokCredentialStore(filePath) - - expect(store.load()).toBeNull() - expect(store.getLoadError()).toBeNull() - expect(warn).not.toHaveBeenCalled() - }) - - it('reports corrupted JSON instead of treating it as signed out', () => { - const warn = vi.spyOn(console, 'warn').mockImplementation(() => {}) - savedContent = '{broken' - const store = new XaiGrokCredentialStore(filePath) - - expect(store.load()).toBeNull() - expect(store.getLoadError()).toContain('not valid JSON') - expect(warn).toHaveBeenCalled() - }) - - it('reports decryption failures instead of treating them as signed out', () => { - vi.spyOn(console, 'warn').mockImplementation(() => {}) - vi.mocked(safeStorage.decryptString).mockImplementationOnce(() => { - throw new Error('keyring locked') - }) - savedContent = JSON.stringify({ - version: 1, - storage: 'safeStorage', - wrapped: Buffer.from('ciphertext').toString('base64'), - updatedAt: 1 - }) - const store = new XaiGrokCredentialStore(filePath) - - expect(store.load()).toBeNull() - expect(store.getLoadError()).toContain('decryption failed') - }) - - it('returns tokens and clears the load error for a healthy envelope', () => { - savedContent = fileEnvelope(tokens) - const store = new XaiGrokCredentialStore(filePath) - - expect(store.load()?.accessToken).toBe('access-token') - expect(store.getLoadError()).toBeNull() - }) - - it('writes through a temporary file and renames it into place', () => { - const store = new XaiGrokCredentialStore(filePath) - - store.save(tokens) - - const temporaryPath = String(vi.mocked(fs.openSync).mock.calls[0][0]) - expect(temporaryPath).toMatch(/^\/tmp\/deepchat-xai-grok\/credentials\.json\.tmp-/) - expect(fs.openSync).toHaveBeenCalledWith(temporaryPath, 'w', 0o600) - expect(fs.fsyncSync).toHaveBeenCalled() - expect(fs.renameSync).toHaveBeenCalledWith(temporaryPath, filePath) - expect(fs.rmSync).toHaveBeenCalledWith(temporaryPath, { force: true }) - expect(store.load()?.accessToken).toBe('access-token') - }) - - it('removes the temporary file when the rename fails', () => { - vi.spyOn(console, 'warn').mockImplementation(() => {}) - vi.mocked(fs.renameSync).mockImplementation(() => { - throw new Error('EPERM: operation not permitted') - }) - const store = new XaiGrokCredentialStore(filePath) - - expect(() => store.save(tokens)).toThrow('EPERM') - const temporaryPath = String(vi.mocked(fs.openSync).mock.calls[0][0]) - expect(fs.rmSync).toHaveBeenCalledWith(temporaryPath, { force: true }) - }) - - it('backs up a corrupted file before overwriting it', () => { - vi.spyOn(console, 'warn').mockImplementation(() => {}) - savedContent = '{broken' - let backupSnapshot: string | null = null - vi.mocked(fs.copyFileSync).mockImplementation(() => { - backupSnapshot = savedContent - }) - const store = new XaiGrokCredentialStore(filePath) - - store.save(tokens) - - expect(fs.copyFileSync).toHaveBeenCalledWith(filePath, `${filePath}.corrupt`) - expect(fs.renameSync).not.toHaveBeenCalledWith(filePath, `${filePath}.corrupt`) - expect(backupSnapshot).toBe('{broken') - expect(store.getLoadError()).toBeNull() - }) - - it('does not back up an undecryptable file before overwriting it', () => { - vi.spyOn(console, 'warn').mockImplementation(() => {}) - vi.mocked(safeStorage.decryptString).mockImplementation(() => { - throw new Error('keyring locked') - }) - savedContent = JSON.stringify({ - version: 1, - storage: 'safeStorage', - wrapped: Buffer.from('ciphertext').toString('base64'), - updatedAt: 1 - }) - const store = new XaiGrokCredentialStore(filePath) - - store.save(tokens) - - expect(fs.copyFileSync).not.toHaveBeenCalled() - }) - - it('does not back up a healthy file before overwriting it', () => { - savedContent = fileEnvelope(tokens) - const store = new XaiGrokCredentialStore(filePath) - - store.save({ ...tokens, accessToken: 'new-access-token' }) - - expect(fs.copyFileSync).not.toHaveBeenCalled() - expect(store.load()?.accessToken).toBe('new-access-token') - }) - - it('clear removes the credential file together with its backup and temp file', () => { - const store = new XaiGrokCredentialStore(filePath) - - store.clear() - - expect(fs.rmSync).toHaveBeenCalledWith(filePath, { force: true }) - expect(fs.rmSync).toHaveBeenCalledWith(`${filePath}.corrupt`, { force: true }) - expect(fs.rmSync).toHaveBeenCalledWith(`${filePath}.tmp`, { force: true }) - }) - - it('clear still attempts the backup and temp files when the main removal fails', () => { - vi.spyOn(console, 'warn').mockImplementation(() => {}) - vi.mocked(fs.rmSync).mockImplementation((target) => { - if (String(target) === filePath) { - throw new Error('EISDIR: illegal operation on a directory') - } - }) - const store = new XaiGrokCredentialStore(filePath) - - store.clear() - - expect(fs.rmSync).toHaveBeenCalledWith(`${filePath}.corrupt`, { force: true }) - expect(fs.rmSync).toHaveBeenCalledWith(`${filePath}.tmp`, { force: true }) - }) - - it('clear also removes randomized temporary credential files', () => { - vi.mocked(fs.readdirSync).mockImplementation( - () => - [ - 'credentials.json.tmp-1234-aabbccddeeff', - 'credentials.json.tmp-5678-001122334455', - 'unrelated.txt' - ] as unknown as fs.Dirent>[] - ) - const store = new XaiGrokCredentialStore(filePath) - - store.clear() - - expect(fs.rmSync).toHaveBeenCalledWith(`${filePath}.tmp-1234-aabbccddeeff`, { force: true }) - expect(fs.rmSync).toHaveBeenCalledWith(`${filePath}.tmp-5678-001122334455`, { force: true }) - expect(fs.rmSync).not.toHaveBeenCalledWith( - expect.stringContaining('unrelated'), - expect.anything() - ) - }) -}) diff --git a/test/main/provider/zenmuxProvider.test.ts b/test/main/provider/zenmuxProvider.test.ts index 78ff16ed07..186867e283 100644 --- a/test/main/provider/zenmuxProvider.test.ts +++ b/test/main/provider/zenmuxProvider.test.ts @@ -1,3 +1,4 @@ +import { providerTestEnvironment } from '../../helpers/providerEnvironment' import type { ProviderSettingsPort } from '@/provider/settings' import { beforeEach, describe, expect, it, vi } from 'vitest' import type { LLM_PROVIDER } from '@shared/types/provider' @@ -53,7 +54,11 @@ describe('AiSdkProvider zenmux', () => { }) it('routes anthropic models through the cache-aware OpenAI-compatible runtime', async () => { - const provider = new AiSdkProvider(createProvider(), createProviderSettings()) + const provider = new AiSdkProvider( + createProvider(), + createProviderSettings(), + providerTestEnvironment + ) const routeDecision = (provider as any).resolveRouteDecision('anthropic/claude-sonnet-4-5') const runtimeProvider = (provider as any).getRuntimeProvider(routeDecision) as LLM_PROVIDER @@ -63,7 +68,11 @@ describe('AiSdkProvider zenmux', () => { }) it('routes non-anthropic models through the openai-compatible runtime', async () => { - const provider = new AiSdkProvider(createProvider(), createProviderSettings()) + const provider = new AiSdkProvider( + createProvider(), + createProviderSettings(), + providerTestEnvironment + ) const routeDecision = (provider as any).resolveRouteDecision('moonshotai/kimi-k2.5') expect(routeDecision.providerKind).toBe('openai-compatible') @@ -77,7 +86,11 @@ describe('AiSdkProvider zenmux', () => { }) }) vi.stubGlobal('fetch', fetchMock) - const provider = new AiSdkProvider(createProvider(), createProviderSettings()) + const provider = new AiSdkProvider( + createProvider(), + createProviderSettings(), + providerTestEnvironment + ) const models = await provider.fetchModels() @@ -91,7 +104,11 @@ describe('AiSdkProvider zenmux', () => { }) it('fails fast for embeddings on anthropic models', async () => { - const provider = new AiSdkProvider(createProvider(), createProviderSettings()) + const provider = new AiSdkProvider( + createProvider(), + createProviderSettings(), + providerTestEnvironment + ) await expect(provider.getEmbeddings('anthropic/claude-sonnet-4-5', ['hello'])).rejects.toThrow( 'Embeddings not supported for Anthropic models: anthropic/claude-sonnet-4-5' @@ -99,7 +116,11 @@ describe('AiSdkProvider zenmux', () => { }) it('fails fast for embedding dimensions on anthropic models', async () => { - const provider = new AiSdkProvider(createProvider(), createProviderSettings()) + const provider = new AiSdkProvider( + createProvider(), + createProviderSettings(), + providerTestEnvironment + ) await expect(provider.getDimensions('anthropic/claude-sonnet-4-5')).rejects.toThrow( 'Embeddings not supported for Anthropic models: anthropic/claude-sonnet-4-5' diff --git a/test/main/remote/channelRequestTimeouts.test.ts b/test/main/remote/channelRequestTimeouts.test.ts index 7ee92ba884..d58b8a8b1c 100644 --- a/test/main/remote/channelRequestTimeouts.test.ts +++ b/test/main/remote/channelRequestTimeouts.test.ts @@ -1,6 +1,7 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' import { DiscordClient } from '@/remote/channels/discord/discordClient' import { QQBotClient } from '@/remote/channels/qqbot/qqbotClient' +import { WeixinIlinkClient } from '@/remote/channels/weixinIlink/weixinIlinkClient' const jsonResponse = (body: unknown, status = 200) => new Response(JSON.stringify(body), { @@ -25,6 +26,40 @@ describe('remote channel request timeouts', () => { vi.unstubAllGlobals() }) + it('cancels an actual Weixin login poll and retires its QR session', async () => { + const controller = new AbortController() + fetchMock.mockResolvedValueOnce( + jsonResponse({ qrcode: 'qr', qrcode_img_content: 'https://liteapp.weixin.qq.com/login' }) + ) + const session = await WeixinIlinkClient.startLogin({ + sessionKey: 'cancel-poll', + signal: controller.signal + }) + const entered = Promise.withResolvers() + fetchMock.mockImplementationOnce( + (_url, init: RequestInit) => + new Promise((_resolve, reject) => { + init.signal!.addEventListener('abort', () => reject(init.signal!.reason), { once: true }) + entered.resolve() + }) + ) + const waiting = WeixinIlinkClient.waitForLogin({ + sessionKey: session.sessionKey, + signal: controller.signal + }) + const rejected = expect(waiting).rejects.toThrow() + await entered.promise + controller.abort() + await rejected + await expect( + WeixinIlinkClient.waitForLogin({ sessionKey: session.sessionKey }) + ).resolves.toMatchObject({ + connected: false, + messageKey: 'settings.remote.weixinIlink.loginSessionExpired' + }) + expect(fetchMock).toHaveBeenCalledTimes(2) + }) + it('arms a timeout on Discord API requests without swallowing the caller signal', async () => { fetchMock.mockResolvedValue(jsonResponse({ id: '123' })) const client = new DiscordClient({ botToken: 'token' }) diff --git a/test/main/remote/remoteConversationRunner.test.ts b/test/main/remote/remoteConversationRunner.test.ts index bcd76f67d1..f7ef7f4995 100644 --- a/test/main/remote/remoteConversationRunner.test.ts +++ b/test/main/remote/remoteConversationRunner.test.ts @@ -3,7 +3,6 @@ import crypto from 'node:crypto' import fs from 'node:fs/promises' import os from 'node:os' import path from 'node:path' -import { app } from 'electron' import { RemoteConversationRunner } from '@/remote/conversation/runner' import type { RemoteSessionAssignmentPort, @@ -24,6 +23,8 @@ const createSession = (overrides: Partial = {}): SessionWithSt isPinned: false, isDraft: false, sessionKind: 'regular', + orchestrationPolicy: 'explicit', + toolModeOverride: null, createdAt: 1, updatedAt: 1, status: 'idle', @@ -88,7 +89,7 @@ const createRemoteSessionPorts = ( const createCatalog = (overrides: Partial = {}): RemoteCatalogPort => ({ getAgentType: vi.fn(async (agentId: string) => (agentId === 'acp-agent' ? 'acp' : 'deepchat')), - listAgents: vi.fn(async () => [ + listAgents: vi.fn(async () => [ { agentId: 'deepchat', agentName: 'DeepChat', @@ -111,18 +112,20 @@ const createWorkspace = ( throw new Error('unused') }) ): RemoteWorkspacePort => ({ - getDefaultProjectPath: vi.fn(() => defaultProjectPath), + getDefaultProjectPath: vi.fn(async () => defaultProjectPath), prepareFile }) +let profileDirectory = '/mock/path' + const createRunner = ( - deps: ConstructorParameters[0], + deps: Omit[0], 'profileDirectory'>, bindingStore: ConstructorParameters[1] ) => - new RemoteConversationRunner(deps, { - getActiveEvent: vi.fn().mockReturnValue(null), - ...bindingStore - } as ConstructorParameters[1]) + new RemoteConversationRunner({ profileDirectory, ...deps }, { + ...bindingStore, + getActiveEvent: bindingStore.getActiveEvent ?? vi.fn().mockReturnValue(null) + } as unknown as ConstructorParameters[1]) const encryptAes128Ecb = (content: Buffer, key: Buffer): Buffer => { const cipher = crypto.createCipheriv('aes-128-ecb', key, null) @@ -132,7 +135,7 @@ const encryptAes128Ecb = (content: Buffer, key: Buffer): Buffer => { describe('RemoteConversationRunner', () => { afterEach(() => { vi.unstubAllGlobals() - vi.mocked(app.getPath).mockImplementation(() => '/mock/path') + profileDirectory = '/mock/path' }) it('cancels an active generation through the Session turn port', async () => { @@ -259,6 +262,7 @@ describe('RemoteConversationRunner', () => { path: path.join(workspace, '.deepchat/remote-assets/telegram/hash/message/note.txt'), mimeType: 'text/plain', content: 'hello file', + token: 1, metadata: { fileName: 'note.txt', fileSize: 10, @@ -276,7 +280,7 @@ describe('RemoteConversationRunner', () => { } }) const fileService = { - prepareFile: vi.fn(async (filePath: string, mimeType: string) => ({ + prepareFile: vi.fn(async (filePath: string, mimeType = 'text/plain') => ({ ...preparedFile, path: filePath, mimeType @@ -351,11 +355,12 @@ describe('RemoteConversationRunner', () => { turn: { sendMessage }, projection: { getSession: vi.fn().mockResolvedValue(session) } }) - const prepareFile = vi.fn(async (filePath: string, mimeType: string) => ({ + const prepareFile = vi.fn(async (filePath: string, mimeType = 'text/plain') => ({ name: 'scan.png', path: filePath, mimeType, content: '', + token: 1, metadata: { fileName: 'scan.png', fileSize: 3, @@ -415,11 +420,12 @@ describe('RemoteConversationRunner', () => { } }) const fileService = { - prepareFile: vi.fn(async (filePath: string, mimeType: string) => ({ + prepareFile: vi.fn(async (filePath: string, mimeType = 'text/plain') => ({ name: path.basename(filePath), path: filePath, mimeType, content: '', + token: 1, metadata: { fileName: path.basename(filePath), fileSize: 0, @@ -501,6 +507,7 @@ describe('RemoteConversationRunner', () => { path: path.join(workspace, '.deepchat/remote-assets/telegram/hash/message/note.txt'), mimeType: 'text/plain', content: 'hello file', + token: 1, metadata: { fileName: 'note.txt', fileSize: 10, @@ -518,7 +525,7 @@ describe('RemoteConversationRunner', () => { } }) const fileService = { - prepareFile: vi.fn(async (filePath: string, mimeType: string) => ({ + prepareFile: vi.fn(async (filePath: string, mimeType = 'text/plain') => ({ ...preparedFile, path: filePath, mimeType @@ -669,6 +676,7 @@ describe('RemoteConversationRunner', () => { path: path.join(workspace, '.deepchat/remote-assets/weixin-ilink/hash/message/image-1.png'), mimeType: 'image/png', content: '', + token: 1, metadata: { fileName: 'image-1.png', fileSize: plainContent.length, @@ -686,7 +694,7 @@ describe('RemoteConversationRunner', () => { } }) const fileService = { - prepareFile: vi.fn(async (filePath: string, mimeType: string) => ({ + prepareFile: vi.fn(async (filePath: string, mimeType = 'text/plain') => ({ ...preparedFile, path: filePath, mimeType @@ -766,9 +774,7 @@ describe('RemoteConversationRunner', () => { const toolOutputImage = Buffer.from('tool output image') await fs.writeFile(path.join(cacheDir, 'generated.png'), cachedImage) await fs.writeFile(path.join(cacheDir, 'screenshot.png'), screenshotImage) - vi.mocked(app.getPath).mockImplementation((name: string) => - name === 'userData' ? userData : '/mock/path' - ) + profileDirectory = userData const assistantMessage = { id: 'assistant-images', @@ -927,9 +933,7 @@ describe('RemoteConversationRunner', () => { it('persists generated remote images in user data when no workspace is available', async () => { const userData = await fs.mkdtemp(path.join(os.tmpdir(), 'deepchat-user-data-')) const image = Buffer.from('generated image without workspace') - vi.mocked(app.getPath).mockImplementation((name: string) => - name === 'userData' ? userData : '/mock/path' - ) + profileDirectory = userData const assistantMessage = { id: 'assistant-images-no-workspace', diff --git a/test/main/remote/remoteService.test.ts b/test/main/remote/remoteService.test.ts index d6d5dda727..409a4d9187 100644 --- a/test/main/remote/remoteService.test.ts +++ b/test/main/remote/remoteService.test.ts @@ -1,5 +1,4 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' -import { BrowserWindow } from 'electron' import * as http from 'node:http' import * as net from 'node:net' import type { TelegramPollerStatusSnapshot } from '@/remote/types' @@ -148,9 +147,13 @@ const createProviderSettings = () => { ]) return { - get: vi.fn((key: string) => store.get(key)), - set: vi.fn((key: string, value: unknown) => { - store.set(key, value) + get(key: string, fallback?: T): T | undefined { + const value = store.get(key) + return value === undefined ? fallback : (value as T) + }, + set: vi.fn((key: string | Record, value?: unknown) => { + if (typeof key === 'string') store.set(key, value) + else for (const [name, entry] of Object.entries(key)) store.set(name, entry) }), getAgentType: vi.fn(async (agentId: string) => (agentId === 'acp-agent' ? 'acp' : 'deepchat')), listAgents: vi.fn().mockResolvedValue([ @@ -183,7 +186,7 @@ const createCatalog = ( }) const createWorkspace = (): RemoteWorkspacePort => ({ - getDefaultProjectPath: vi.fn(() => null), + getDefaultProjectPath: vi.fn(async () => null), prepareFile: vi.fn(async () => { throw new Error('unused') }) @@ -195,8 +198,12 @@ const createNotifications = (): RemoteNotificationPort & { showNotification: vi.fn().mockResolvedValue('remote-delivery-error:feishu') }) +const authWindows = { open: vi.fn(async () => {}), close: vi.fn(async () => {}) } + const createRemoteService = (settings: TestSettings, overrides: Partial = {}) => new RemoteService({ + profileDirectory: '/mock/path', + authWindows, settings, catalog: createCatalog(settings), workspace: createWorkspace(), @@ -209,16 +216,119 @@ describe('RemoteService', () => { pollerInstances.length = 0 telegramClientInstances.length = 0 pollerStartImplementation = async () => {} - Object.assign(BrowserWindow, { - getFocusedWindow: vi.fn(() => null), - getAllWindows: vi.fn(() => []) - }) + authWindows.open.mockClear() + authWindows.close.mockClear() }) afterEach(() => { vi.restoreAllMocks() }) + it('drains and compensates a Feishu window that opens after authorization is stopped', async () => { + const settings = createProviderSettings() + const port = await getFreeLoopbackPort() + const entered = Promise.withResolvers() + const opening = Promise.withResolvers() + const windows = { + open: vi.fn(async () => { + entered.resolve() + await opening.promise + }), + close: vi.fn(async () => {}) + } + const service = createRemoteService(settings, { authWindows: windows }) + const starting = service.startFeishuAuth({ + appId: 'test-app', + appSecret: 'test-secret', + redirectUri: `http://127.0.0.1:${port}/callback` + }) + const rejected = expect(starting).rejects.toThrow() + await entered.promise + const socket = net.connect(port, '127.0.0.1') + const socketClosed = new Promise((resolve) => socket.once('close', () => resolve())) + socket.on('error', () => {}) + await new Promise((resolve) => socket.once('connect', resolve)) + socket.write('GET /callback HTTP/1.1\r\n') + let stopped = false + const stopping = service.destroy().then(() => { + stopped = true + }) + await Promise.resolve() + expect(stopped).toBe(false) + opening.resolve() + await Promise.all([stopping, rejected, socketClosed]) + expect(windows.close).toHaveBeenCalledWith(expect.stringMatching(/^feishu:/)) + const server = net.createServer() + await new Promise((resolve, reject) => { + server.once('error', reject) + server.listen(port, '127.0.0.1', resolve) + }) + await new Promise((resolve) => server.close(() => resolve())) + await expect(service.startFeishuAuth()).rejects.toThrow() + }) + + it('fences a confirmed Weixin login whose request ignores cancellation until after stop', async () => { + const settings = createProviderSettings() + const entered = Promise.withResolvers() + const result = + Promise.withResolvers>>() + vi.spyOn(WeixinIlinkClient, 'waitForLogin').mockImplementation(async () => { + entered.resolve() + return await result.promise + }) + const service = createRemoteService(settings) + const waiting = service.waitForWeixinIlinkLogin({ sessionKey: 'late-login' }) + const rejected = expect(waiting).rejects.toThrow() + await entered.promise + let stopped = false + const stopping = service.destroy().then(() => { + stopped = true + }) + await Promise.resolve() + expect(stopped).toBe(false) + result.resolve({ + connected: true, + accountId: 'late-account', + ownerUserId: 'late-owner', + botToken: 'secret' + }) + await Promise.all([stopping, rejected]) + expect(service.buildWeixinIlinkSettingsSnapshot().accounts).toEqual([]) + }) + + it('drains a late Feishu registration start without publishing a new auth session', async () => { + const entered = Promise.withResolvers() + const response = Promise.withResolvers() + let requestSignal: AbortSignal | undefined + vi.spyOn(globalThis, 'fetch').mockImplementation(async (_url, init) => { + requestSignal = init?.signal ?? undefined + entered.resolve() + return await response.promise + }) + const service = createRemoteService(createProviderSettings()) + const starting = service.startFeishuInstall() + const rejected = expect(starting).rejects.toThrow() + await entered.promise + let stopped = false + const stopping = service.destroy().then(() => { + stopped = true + }) + await Promise.resolve() + expect(requestSignal?.aborted).toBe(true) + expect(stopped).toBe(false) + response.resolve( + Response.json({ + device_code: 'late-device', + verification_uri_complete: 'https://open.feishu.cn/install', + user_code: 'LATE', + expires_in: 300, + interval: 1 + }) + ) + await Promise.all([stopping, rejected]) + await expect(service.startFeishuInstall()).rejects.toThrow() + }) + it('notifies once for a Feishu delivery failure and throttles later failures', async () => { const notifications = createNotifications() const presenter = createRemoteService(createProviderSettings(), { notifications }) @@ -308,11 +418,8 @@ describe('RemoteService', () => { it('reports starting while the poller startup is still in flight', async () => { const providerSettings = createProviderSettings() - let resolveStart: (() => void) | null = null - pollerStartImplementation = () => - new Promise((resolve) => { - resolveStart = resolve - }) + const starting = Promise.withResolvers() + pollerStartImplementation = () => starting.promise const presenter = createRemoteService(providerSettings) @@ -326,7 +433,7 @@ describe('RemoteService', () => { ) }) - resolveStart?.() + starting.resolve() await initializePromise }) @@ -704,14 +811,12 @@ describe('RemoteService', () => { const callbackPromise = requestUrl(`${redirectUri}?code=auth-code&state=${state}`) await tokenRequestStarted - await presenter.cancelFeishuAuth(session.sessionKey) - await expect(waitPromise).resolves.toEqual( - expect.objectContaining({ - authorized: false, - openId: null, - messageKey: 'settings.remote.feishu.authCancelled' - }) - ) + let cancelled = false + const cancelling = presenter.cancelFeishuAuth(session.sessionKey).then(() => { + cancelled = true + }) + await Promise.resolve() + expect(cancelled).toBe(false) resolveToken( new Response( @@ -725,6 +830,12 @@ describe('RemoteService', () => { ) ) await callbackPromise + await cancelling + await expect(waitPromise).resolves.toMatchObject({ + authorized: false, + openId: null, + messageKey: 'settings.remote.feishu.authCancelled' + }) await expect(presenter.getChannelPairingSnapshot('feishu')).resolves.toEqual( expect.objectContaining({ @@ -936,7 +1047,8 @@ describe('RemoteService', () => { const saved = await presenter.saveTelegramSettings({ botToken: 'test-bot-token', remoteEnabled: true, - defaultAgentId: 'deepchat-alt' + defaultAgentId: 'deepchat-alt', + defaultWorkdir: '' }) expect(saved.defaultAgentId).toBe('deepchat') @@ -1027,7 +1139,8 @@ describe('RemoteService', () => { const saved = await presenter.saveTelegramSettings({ botToken: 'test-bot-token', remoteEnabled: true, - defaultAgentId: 'claude-code-acp' + defaultAgentId: 'claude-code-acp', + defaultWorkdir: '' }) expect(saved.defaultAgentId).toBe('deepchat') @@ -1241,9 +1354,12 @@ describe('RemoteService', () => { messageKey: 'settings.remote.weixinIlink.loginWindowOpened', message: undefined }) - expect(BrowserWindow).toHaveBeenCalledTimes(1) - expect(vi.mocked(BrowserWindow).mock.results[0]?.value.loadURL).toHaveBeenCalledWith( - 'https://liteapp.weixin.qq.com/mock-login' + expect(authWindows.open).toHaveBeenCalledTimes(1) + expect(authWindows.open).toHaveBeenCalledWith( + expect.objectContaining({ + kind: 'weixin-ilink', + url: 'https://liteapp.weixin.qq.com/mock-login' + }) ) await expect( diff --git a/test/main/routes/databaseRequestOwnership.test.ts b/test/main/routes/databaseRequestOwnership.test.ts new file mode 100644 index 0000000000..a72b1c319b --- /dev/null +++ b/test/main/routes/databaseRequestOwnership.test.ts @@ -0,0 +1,142 @@ +import { describe, expect, it, vi } from 'vitest' +import { DatabaseRequestGate } from '@/app/databaseRequestGate' +import { StartupWorkloadCoordinator } from '@/app/startupWorkloadCoordinator' +import { createRouteDispatcher, dispatchDeepchatRoute } from '@/routes' +import { + createRendererRouteContext, + createRouteMap, + type DeepchatRouteHandler +} from '@/routes/routeRegistry' + +const deferred = () => { + let resolve!: (value: T) => void + const promise = new Promise((done) => { + resolve = done + }) + return { promise, resolve } +} + +function createRuntime(handler: DeepchatRouteHandler) { + const gate = new DatabaseRequestGate(() => undefined) + const coordinator = new StartupWorkloadCoordinator() + const dispatcher = createRouteDispatcher({ + appDatabaseMaintenance: gate, + routeMaps: [createRouteMap([['models.getProviderCatalog', handler]])], + isSettingsWindow: async (windowId) => windowId === 99, + startupWorkloadCoordinator: coordinator + }) + const dispatch = (providerId = 'fixture', webContentsId = 42) => + dispatchDeepchatRoute( + dispatcher, + 'models.getProviderCatalog', + { providerId }, + createRendererRouteContext(webContentsId, 99) + ) + return { gate, coordinator, dispatch } +} + +describe('database requests scheduled as startup work', () => { + it('keeps concurrent route arguments and callers independent', async () => { + const release = deferred() + const handler = vi.fn(async (input, context) => { + await release.promise + return { input, caller: context.caller } + }) + const { dispatch } = createRuntime(handler) + const requests = [dispatch('alpha'), dispatch('beta'), dispatch('alpha', 43)] + try { + await vi.waitFor(() => expect(handler).toHaveBeenCalledTimes(2)) + } finally { + release.resolve() + } + await expect(Promise.all(requests)).resolves.toEqual( + ( + [ + ['alpha', 42], + ['beta', 42], + ['alpha', 43] + ] as const + ).map(([providerId, webContentsId]) => ({ + input: { providerId }, + caller: createRendererRouteContext(webContentsId, 99).caller + })) + ) + expect(handler).toHaveBeenCalledTimes(3) + }) + + it('rechecks admission after an awaited native window query', async () => { + const gate = new DatabaseRequestGate(() => undefined) + const authority = Promise.withResolvers() + const handler = vi.fn(async () => ({ models: [] })) + const dispatcher = createRouteDispatcher({ + appDatabaseMaintenance: gate, + routeMaps: [createRouteMap([['models.getProviderCatalog', handler]])], + isSettingsWindow: () => authority.promise, + startupWorkloadCoordinator: new StartupWorkloadCoordinator() + }) + const pending = dispatchDeepchatRoute( + dispatcher, + 'models.getProviderCatalog', + { providerId: 'fixture' }, + createRendererRouteContext(42, 99) + ) + const rejected = expect(pending).rejects.toThrow('maintenance is maintenance') + gate.beginMaintenance() + const drain = gate.drain() + expect(handler).not.toHaveBeenCalled() + authority.resolve(false) + await rejected + await drain + expect(handler).not.toHaveBeenCalled() + gate.resume() + }) + + it('rechecks admission before queued work actually executes', async () => { + const handler = vi.fn(async () => ({ models: [] })) + const { gate, coordinator, dispatch } = createRuntime(handler) + const release = deferred() + const blockers = [0, 1].map((index) => + coordinator.scheduleTask({ + id: `blocker-${index}`, + target: 'settings', + phase: 'deferred', + resource: 'io', + labelKey: 'startup.test', + run: () => release.promise + }) + ) + const rejected = expect(dispatch()).rejects.toThrow('maintenance is maintenance') + gate.beginMaintenance() + const drain = gate.drain() + release.resolve() + await Promise.all(blockers) + await rejected + await drain + expect(handler).not.toHaveBeenCalled() + gate.resume() + }) + + it('drains the running handler after cancellation settles its scheduled reply', async () => { + const entered = deferred() + const release = deferred<{ models: never[] }>() + const { gate, coordinator, dispatch } = createRuntime(async () => { + entered.resolve() + return await release.promise + }) + const pending = dispatch() + const rejected = expect(pending).rejects.toMatchObject({ name: 'AbortError' }) + await entered.promise + gate.beginMaintenance() + coordinator.cancelTarget('settings') + await rejected + let drained = false + const drain = gate.drain().then(() => { + drained = true + }) + await Promise.resolve() + expect(drained).toBe(false) + release.resolve({ models: [] }) + await drain + gate.resume() + }) +}) diff --git a/test/main/routes/dispatcher.test.ts b/test/main/routes/dispatcher.test.ts index 627eebc3d4..f7b4228045 100644 --- a/test/main/routes/dispatcher.test.ts +++ b/test/main/routes/dispatcher.test.ts @@ -1,3 +1,4 @@ +import { AppSettingsService } from '@/app/settingsService' import type { ProviderSettingsPort } from '@/provider/settings' import type { FileServicePort } from '@shared/types/file' import type { SkillServicePort } from '@shared/types/skill' @@ -875,6 +876,10 @@ function createRuntime() { } const appDatabaseMaintenance = { assertRouteAllowed: vi.fn(), + async runRequest(routeName: string, run: () => Promise): Promise { + this.assertRouteAllowed(routeName) + return await run() + }, enableDatabaseEncryption: vi.fn().mockResolvedValue(enabledDatabaseSecurityStatus), changeDatabasePassword: vi.fn().mockResolvedValue(enabledDatabaseSecurityStatus), disableDatabaseEncryption: vi.fn().mockResolvedValue({ @@ -893,7 +898,20 @@ function createRuntime() { }) } const startupWorkloadCoordinator = { - scheduleTask: vi.fn(async (task: { run: () => Promise }) => await task.run()), + scheduleTask: vi.fn( + async (task: { + run: (context: { + signal: AbortSignal + reportProgress(progress: number): void + yield(): Promise + }) => Promise + }) => + await task.run({ + signal: new AbortController().signal, + reportProgress: () => undefined, + yield: async () => undefined + }) + ), getRunId: vi.fn(() => 'startup:test'), replayTarget: vi.fn() } @@ -980,7 +998,11 @@ function createRuntime() { } const applyProxyMode = vi.fn() const applyCustomProxyUrl = vi.fn() + const applyContentProtection = vi.fn() const desktopSettings = { + getLanguage: vi.fn(() => 'en-US'), + getCloseToQuit: vi.fn(() => false), + getFloatingButtonBounds: vi.fn(() => null), getCopyWithCotEnabled: vi.fn(() => settings.copyWithCotEnabled), setCopyWithCotEnabled: vi.fn((enabled: boolean) => { settings.copyWithCotEnabled = enabled @@ -1004,10 +1026,12 @@ function createRuntime() { getContentProtectionEnabled: vi.fn(() => settings.contentProtectionEnabled), setContentProtectionEnabled: vi.fn((value: boolean) => { settings.contentProtectionEnabled = value + applyContentProtection(value) }), getFloatingButtonEnabled: vi.fn(() => settings.floatingButtonEnabled), - setFloatingButtonEnabled: vi.fn((value: boolean) => { + setFloatingButtonEnabled: vi.fn(async (value: boolean) => { settings.floatingButtonEnabled = value + await setFloatingButtonEnabled(value) }), getShortcutKeys: vi.fn(() => ({})), setShortcutKeys: vi.fn(), @@ -1024,7 +1048,6 @@ function createRuntime() { }), getSystemFonts: vi.fn().mockResolvedValue(['Inter', 'JetBrains Mono']) } - const applyContentProtection = vi.fn() const setFloatingButtonEnabled = vi.fn() const loggingService = { getEnabled: vi.fn(() => settings.loggingEnabled), @@ -1598,6 +1621,7 @@ function createRuntime() { }) const assertSessionActiveSkillsMutable = vi.fn().mockResolvedValue(undefined) const skillRoutes = createSkillRoutes({ + tempDirectory: '/tmp/deepchat-test', skillService, skillSyncService, skillSettings, @@ -1608,7 +1632,7 @@ function createRuntime() { const mcpRoutes = createMcpRoutes({ mcpService, mcpAppHost: {} as any, - isSettingsWindow: () => true, + isSettingsWindow: async () => true, recordSettingsActivity: (input) => sqlitePresenter.recordSettingsActivity(input) }) const remoteRoutes = createRemoteRoutes(remoteService) @@ -1620,22 +1644,36 @@ function createRuntime() { getTapeInspection: () => tapeInspection, getAuditEntries: () => (sqlitePresenter as any).agentMemoryAuditTable }) + let settingsSnapshotRevision = 0 + const appSettingsService = new AppSettingsService({ + ownerId: 'fixture-owner', + updateSettings: updateSettings as never, + publishNativeSnapshot: vi.fn(), + agentDefaults: agentDefaults as never, + privacy: privacySettings as never, + traceSettings: traceSettings as never, + desktopSettings: desktopSettings as never, + fonts: fontSettings as never, + logging: loggingService as never, + ocr: ocrSettings, + publishEvent: publishDeepchatEvent, + recordActivity: (input) => { + void sqlitePresenter.recordSettingsActivity(input) + }, + nextVersion: () => ++settingsSnapshotRevision, + assertActive: () => {} + }) const desktopRoutes = createDesktopRoutes({ - ownsDraftSession: sessionLifecyclePort.ownsDraftSession, windowPresenter, shortcutPresenter, browserPresenter: yoBrowserPresenter, - computerUsePreviewPresenter, - desktopSessionBinding, tabPresenter, - dialogService: dialogService as unknown as DialogServicePort, - settings: desktopSettings as never, - setFloatingButtonEnabled, - recordActivity: (input) => { - void sqlitePresenter.recordSettingsActivity(input) - } + dialogService: dialogService as unknown as DialogServicePort + }) + const fileRoutes = createFileRoutes(fileService, { + saveImage: vi.fn().mockResolvedValue({ canceled: true }), + copyImage: vi.fn().mockResolvedValue({ copied: true }) }) - const fileRoutes = createFileRoutes(fileService) const knowledgeSettings = { getKnowledgeConfigs: vi.fn(() => knowledgeConfigs), setKnowledgeConfigs: vi.fn((configs: typeof knowledgeConfigs) => { @@ -1672,6 +1710,15 @@ function createRuntime() { lifecycle: sessionLifecyclePort, projection: sessionProjectionPort, desktop: desktopSessionBinding, + openDesktopSession: vi.fn(async () => true), + presentation: { + focusBrowser: async (sessionId, caller) => + yoBrowserPresenter.focusSessionBrowser(sessionId, caller.windowId!), + setComputerUsePreview: async (sessionId, mode, caller) => + await computerUsePreviewPresenter.setPreviewMode(sessionId, mode, caller.windowId!), + dismissComputerUsePreview: async (sessionId, runId) => + computerUsePreviewPresenter.dismissPreview(sessionId, runId) + }, turn: sessionTurnPort, assignment: sessionAssignmentPort, permission: sessionPermissionPort, @@ -1734,7 +1781,7 @@ function createRuntime() { } const acpRoutes = createAcpRoutes({ auth: acpAuth as never, - isMainWindowContext: (caller) => caller.webContentsId === 42, + isMainWindowContext: async (caller) => caller.webContentsId === 42, owner: { peek: () => undefined } as never }) const deviceRoutes = createDeviceRoutes({ @@ -1748,8 +1795,7 @@ function createRuntime() { }) const exporterRoutes = createExporterRoutes(exporter) const upgradeRoutes = createUpgradeRoutes({ - upgrade: {} as never, - settings: updateSettings as never + upgrade: {} as never }) const syncRoutes = createSyncRoutes({ sync: syncService, @@ -1773,19 +1819,11 @@ function createRuntime() { } as never }) const appSettingsRoutes = createAppSettingsRoutes({ + appSettings: appSettingsService, settings: { get: (key) => providerSettings.getSetting(key), set: (key, value) => providerSettings.setSetting(key, value) }, - agentDefaults: agentDefaults as never, - privacy: privacySettings as never, - traceSettings: traceSettings as never, - updateSettings: updateSettings as never, - desktopSettings: desktopSettings as never, - fonts: fontSettings as never, - applyContentProtection, - logging: loggingService as never, - ocr: ocrSettings, commandShell, publishEvent: publishDeepchatEvent, recordActivity: (input) => { @@ -1794,6 +1832,7 @@ function createRuntime() { listActivities: (limit) => sqlitePresenter.listSettingsActivity(limit) }) const appRoutes = createAppRoutes({ + isPackaged: false, logging: loggingService as never, agentSettings: providerSettings, projects: projectPresenter as never, @@ -1848,7 +1887,7 @@ function createRuntime() { appSettingsRoutes, appRoutes ], - settingsWindow: windowPresenter, + isSettingsWindow: async (windowId) => windowPresenter.getSettingsWindowId() === windowId, startupWorkloadCoordinator: startupWorkloadCoordinator as any }) Object.defineProperties(runtime, { diff --git a/test/main/scheduler/runSessionStarter.test.ts b/test/main/scheduler/runSessionStarter.test.ts index 263cf100cf..32560eed13 100644 --- a/test/main/scheduler/runSessionStarter.test.ts +++ b/test/main/scheduler/runSessionStarter.test.ts @@ -16,7 +16,7 @@ const createPorts = () => { cancelGeneration: vi.fn(async () => undefined) } const agentCatalog: CronJobAgentCatalogPort = { - getAgentType: vi.fn(async () => 'deepchat') + getAgentType: vi.fn(async () => 'deepchat' as const) } return { lifecycle, turn, agentCatalog } diff --git a/test/main/scheduler/schedulerService.test.ts b/test/main/scheduler/schedulerService.test.ts index 7946717133..4dac243263 100644 --- a/test/main/scheduler/schedulerService.test.ts +++ b/test/main/scheduler/schedulerService.test.ts @@ -1,11 +1,43 @@ import { EventEmitter } from 'node:events' -import os from 'node:os' -import path from 'node:path' import { describe, expect, it, vi } from 'vitest' -import type { CronJob, CronJobRun, CronJobsSchedulerStatus } from '@shared/cronJobs' +import type { + CronJob, + CronJobRun, + CronJobsSchedulerStatus, + SchedulerCommand +} from '@shared/cronJobs' import { SessionRuntimeEvents } from '@/session/runtimeEvents' +import type { SchedulerHost } from '@/scheduler/schedulerProcessManager' + +function schedulerHost( + host: EventEmitter & { + pid: number + postMessage(command: SchedulerCommand): void + kill(): void + } +): SchedulerHost { + return { + get pid() { + return host.pid + }, + postMessage: (command) => { + host.postMessage(command) + }, + kill: () => { + host.kill() + }, + onMessage: (listener) => { + host.on('message', listener) + }, + onExit: (listener) => { + host.on('exit', listener) + }, + onError: (listener) => { + host.on('error', listener) + } + } +} -const actualFs = await vi.importActual('node:fs') const sqliteModule = await import('better-sqlite3-multiple-ciphers').catch(() => null) const cronJobsTableModule = sqliteModule ? await import('@/scheduler/data/tables/cronJobs').catch(() => null) @@ -829,7 +861,7 @@ describeIfSqlite('Cron Jobs persistence and service', () => { const service = new SchedulerServiceCtor({ ...createRequiredSchedulerDeps(), database: sqlitePresenter as never, - schedulerManager: schedulerManager as never + createSchedulerManager: () => schedulerManager as never }) await expect( @@ -883,7 +915,7 @@ describeIfSqlite('Cron Jobs persistence and service', () => { const service = new SchedulerServiceCtor({ ...createRequiredSchedulerDeps(), database: sqlitePresenter as never, - schedulerManager: schedulerManager as never, + createSchedulerManager: () => schedulerManager as never, agentSettings: agentSettings as never }) @@ -962,7 +994,7 @@ describeIfSqlite('Cron Jobs persistence and service', () => { const service = new SchedulerServiceCtor({ ...createRequiredSchedulerDeps(), database: sqlitePresenter as never, - schedulerManager: schedulerManager as never, + createSchedulerManager: () => schedulerManager as never, deliveryRouter: deliveryRouter as never, runSessionStarter: runSessionStarter as never }) @@ -1037,7 +1069,7 @@ describeIfSqlite('Cron Jobs persistence and service', () => { const service = new SchedulerServiceCtor({ ...createRequiredSchedulerDeps(), database: sqlitePresenter as never, - schedulerManager: schedulerManager as never + createSchedulerManager: () => schedulerManager as never }) service.start() @@ -1053,6 +1085,62 @@ describeIfSqlite('Cron Jobs persistence and service', () => { } }) + it.each([false, true])( + 'fences resume reconciliation after stop, including a restarted service (%s)', + async (restart) => { + const { db, sqlitePresenter } = createHarness() + const status = baseStatus() + const schedulerManager = { + reconcile: vi.fn().mockResolvedValue(status), + stop: vi.fn().mockResolvedValue(status), + getStatus: vi.fn(() => status) + } + const deps = createRequiredSchedulerDeps() + const service = new SchedulerServiceCtor({ + ...deps, + database: sqlitePresenter as never, + createSchedulerManager: () => schedulerManager as never + }) + try { + await service.handleSystemResume() + expect(schedulerManager.reconcile).not.toHaveBeenCalled() + const { job } = await service.upsert({ + name: 'Resume lifecycle', + enabled: true, + cronExpr: '0 0 1 1 *', + timezone: 'UTC', + agentId: 'agent-1', + taskPrompt: 'Summarize issues' + }) + service.start() + await vi.waitFor(() => expect(schedulerManager.reconcile).toHaveBeenCalledWith('startup')) + schedulerManager.reconcile.mockClear() + deps.agentSettings.listAgents.mockClear() + const pending = + Promise.withResolvers>>() + deps.agentSettings.listAgents.mockReturnValueOnce(pending.promise) + const resume = service.handleSystemResume() + await vi.waitFor(() => expect(deps.agentSettings.listAgents).toHaveBeenCalledOnce()) + await service.stop() + if (restart) { + service.start() + await vi.waitFor(() => expect(schedulerManager.reconcile).toHaveBeenCalledWith('startup')) + } + pending.resolve([{ id: 'agent-1', name: 'Test agent', type: 'deepchat', enabled: false }]) + await resume + const repository = new CronJobsRepositoryCtor(sqlitePresenter as never) + expect(repository.getJob(job.id)).toEqual(job) + expect(schedulerManager.reconcile).not.toHaveBeenCalledWith('system-resume') + await service.handleSystemResume() + if (restart) expect(schedulerManager.reconcile).toHaveBeenCalledWith('system-resume') + else expect(schedulerManager.reconcile).not.toHaveBeenCalledWith('system-resume') + } finally { + await service.destroy() + db.close() + } + } + ) + it('creates a fresh session for manual runs when the executor is wired', async () => { const { db, sqlitePresenter } = createHarness() try { @@ -1077,7 +1165,7 @@ describeIfSqlite('Cron Jobs persistence and service', () => { const service = new SchedulerServiceCtor({ ...createRequiredSchedulerDeps(sessionEvents), database: sqlitePresenter as never, - schedulerManager: schedulerManager as never, + createSchedulerManager: () => schedulerManager as never, runSessionStarter: runSessionStarter as never }) @@ -1160,7 +1248,7 @@ describeIfSqlite('Cron Jobs persistence and service', () => { const service = new SchedulerServiceCtor({ ...createRequiredSchedulerDeps(), database: sqlitePresenter as never, - schedulerManager: schedulerManager as never, + createSchedulerManager: () => schedulerManager as never, runSessionStarter: runSessionStarter as never }) @@ -1348,7 +1436,7 @@ describeIfSqlite('Cron Jobs persistence and service', () => { const service = new SchedulerServiceCtor({ ...createRequiredSchedulerDeps(sessionEvents), database: sqlitePresenter as never, - schedulerManager: schedulerManager as never, + createSchedulerManager: () => schedulerManager as never, runSessionStarter: runSessionStarter as never }) @@ -1417,7 +1505,7 @@ describeIfSqlite('Cron Jobs persistence and service', () => { const service = new SchedulerServiceCtor({ ...createRequiredSchedulerDeps(), database: sqlitePresenter as never, - schedulerManager: schedulerManager as never + createSchedulerManager: () => schedulerManager as never }) const stored = sqlitePresenter.cronJobsTable.upsert({ name: 'Legacy indicator', @@ -1440,54 +1528,262 @@ describeIfSqlite('Cron Jobs persistence and service', () => { } }) - it('queues each due scheduled run once and advances next_run_at in the utility host', () => { - const tempDir = actualFs.mkdtempSync(path.join(os.tmpdir(), 'deepchat-cron-jobs-')) - const dbPath = path.join(tempDir, 'agent.db') - const db = new DatabaseCtor(dbPath) - const events: unknown[] = [] - + it('queues each due scheduled run once and advances its head at the Store owner', () => { + const { db, sqlitePresenter } = createHarness() try { - const cronJobsTable = new CronJobsTableCtor(db) - const cronJobRunsTable = new CronJobRunsTableCtor(db) - cronJobsTable.createTable() - cronJobRunsTable.createTable() - const dueAt = Date.now() - 1_000 - const job = cronJobsTable.upsert({ + const repository = new CronJobsRepositoryCtor(sqlitePresenter as never) + const now = Date.now() + const job = repository.upsertJob({ name: 'Due job', enabled: true, cronExpr: '0 9 * * *', timezone: 'UTC', agentId: 'agent-1', - nextRunAt: dueAt, - taskPrompt: 'Summarize issues', - now: 1 + nextRunAt: now - 1_000, + taskPrompt: 'Summarize issues' }) + expect(repository.queueDueRuns(now)).toEqual([ + expect.objectContaining({ jobId: job.id, scheduledAt: now - 1_000, reason: 'scheduled' }) + ]) + expect(repository.queueDueRuns(now)).toEqual([]) + expect(repository.getJob(job.id)!.nextRunAt).toBeGreaterThan(now) + expect(repository.listRunsByJob(job.id)).toHaveLength(1) + } finally { + db.close() + } + }) + + it('rolls back every queued run and head when a due batch cannot advance', () => { + const { db, sqlitePresenter } = createHarness() + try { + const repository = new CronJobsRepositoryCtor(sqlitePresenter as never) + const now = Date.now() + const jobs = [0, 1].map((index) => + repository.upsertJob({ + id: 'atomic-' + index, + name: 'Atomic due job', + enabled: true, + cronExpr: '* * * * *', + timezone: 'UTC', + agentId: 'agent-1', + nextRunAt: now - 1_000, + taskPrompt: 'Summarize issues' + }) + ) + db.exec( + "CREATE TRIGGER reject_schedule BEFORE UPDATE ON cron_jobs WHEN OLD.id = 'atomic-1' BEGIN SELECT RAISE(ABORT, 'forced schedule failure'); END" + ) + expect(() => repository.queueDueRuns(now)).toThrow('forced schedule failure') + for (const job of jobs) { + expect(repository.listRunsByJob(job.id)).toEqual([]) + expect(repository.getJob(job.id)!.nextRunAt).toBe(now - 1_000) + } + } finally { + db.close() + } + }) - const host = new CronJobsSchedulerUtilityHostCtor({ - dbPath, - postMessage: (message) => events.push(message) + it('preserves catch-up limits and skipped misfires at the Store owner', () => { + const { db, sqlitePresenter } = createHarness() + try { + const repository = new CronJobsRepositoryCtor(sqlitePresenter as never) + const now = Date.parse('2026-07-03T10:00:00.000Z') + const due = now - 180_000 + const common = { + enabled: true, + cronExpr: '* * * * *', + timezone: 'UTC', + agentId: 'agent-1', + nextRunAt: due, + taskPrompt: 'Summarize issues' + } + const skipped = repository.upsertJob({ ...common, name: 'Skip', misfirePolicy: 'skip' }) + const catchup = repository.upsertJob({ + ...common, + name: 'Catch up', + misfirePolicy: 'run_once', + maxCatchUpRuns: 2 }) + expect( + repository.queueDueRuns(now).map((run) => ({ jobId: run.jobId, at: run.scheduledAt })) + ).toEqual([ + { jobId: catchup.id, at: due }, + { jobId: catchup.id, at: due + 60_000 } + ]) + expect(repository.listRunsByJob(skipped.id)).toEqual([]) + expect(repository.queueDueRuns(now)).toEqual([]) + } finally { + db.close() + } + }) + + it('limits one durable due scan to 100 jobs without dropping the remainder', () => { + const { db, sqlitePresenter } = createHarness() + try { + const repository = new CronJobsRepositoryCtor(sqlitePresenter as never) + const now = Date.now() + for (let index = 0; index < 101; index++) + repository.upsertJob({ + name: 'Bounded due job', + enabled: true, + cronExpr: '* * * * *', + timezone: 'UTC', + agentId: 'agent-1', + nextRunAt: now - 1_000, + taskPrompt: 'Summarize issues' + }) + const first = repository.queueDueRuns(now) + const second = repository.queueDueRuns(now) + expect(first).toHaveLength(100) + expect(second).toHaveLength(1) + expect(new Set([...first, ...second].map((run) => run.jobId)).size).toBe(101) + } finally { + db.close() + } + }) + + it('bounds timer scans, coalesces reconciliation and ignores stale replies', () => { + vi.useFakeTimers() + const messages: { type: string; requestId?: number }[] = [] + const host = new CronJobsSchedulerUtilityHostCtor({ + postMessage: (message) => messages.push(message) + }) + const scans = () => messages.filter((message) => message.type === 'SCAN_DUE') + try { host.start() host.reconcile() + host.reconcile() + expect(scans()).toHaveLength(1) + const result = { + type: 'SCAN_RESULT' as const, + requestId: 1, + enabledJobCount: 1, + nextRunAt: Date.now() + 10, + now: Date.now() + } + host.applyScanResult({ ...result, requestId: 99 }) + expect(scans()).toHaveLength(1) + host.applyScanResult(result) + expect(scans().map((message) => message.requestId)).toEqual([1, 2]) + host.applyScanResult(result) + host.applyScanResult({ ...result, requestId: 2 }) + vi.advanceTimersByTime(10) + expect(scans().map((message) => message.requestId)).toEqual([1, 2, 3]) + host.shutdown() + host.applyScanResult({ ...result, requestId: 3 }) + vi.advanceTimersByTime(30_000) + expect(scans()).toHaveLength(3) + } finally { host.shutdown() + vi.useRealTimers() + } + }) - expect(events.filter((event) => (event as { type?: string }).type === 'RUN_DUE')).toEqual([ - expect.objectContaining({ - jobId: job.id, - scheduledAt: dueAt, - reason: 'scheduled' - }) - ]) - expect(cronJobsTable.get(job.id)?.next_run_at).toEqual(expect.any(Number)) - expect(db.prepare('SELECT COUNT(*) AS count FROM cron_job_runs').get()).toEqual({ - count: 1 + it('fences a timer whose Store acknowledgement never arrives', () => { + vi.useFakeTimers() + const postMessage = vi.fn() + const host = new CronJobsSchedulerUtilityHostCtor({ postMessage }) + try { + host.start() + vi.advanceTimersByTime(30_000) + expect(postMessage).toHaveBeenCalledWith( + expect.objectContaining({ type: 'ERROR', fatal: true }) + ) + const count = postMessage.mock.calls.length + host.reconcile() + vi.advanceTimersByTime(30_000) + expect(postMessage).toHaveBeenCalledTimes(count) + } finally { + host.shutdown() + vi.useRealTimers() + } + }) + + it('backs off a failed Store scan even when its error message is empty', () => { + vi.useFakeTimers() + const postMessage = vi.fn() + const host = new CronJobsSchedulerUtilityHostCtor({ postMessage }) + try { + host.start() + host.applyScanResult({ + type: 'SCAN_RESULT', + requestId: 1, + enabledJobCount: 1, + nextRunAt: Date.now() - 1, + error: '', + now: Date.now() }) + vi.advanceTimersByTime(29_999) + expect( + postMessage.mock.calls.filter(([message]) => message.type === 'SCAN_DUE') + ).toHaveLength(1) + vi.advanceTimersByTime(1) + expect( + postMessage.mock.calls.filter(([message]) => message.type === 'SCAN_DUE') + ).toHaveLength(2) } finally { - db.close() - actualFs.rmSync(tempDir, { recursive: true, force: true }) + host.shutdown() + vi.useRealTimers() } }) + it('dispatches committed runs before a lost timer reply and rejects replay or stale requests', async () => { + const host = Object.assign(new EventEmitter(), { + pid: 123, + kill: vi.fn(), + postMessage: vi.fn((message: { type: string }) => { + if (message.type === 'SCAN_RESULT') throw new Error('Timer reply lost') + }) + }) + const queueDueRuns = vi.fn(() => [ + { jobId: 'job-1', runId: 'run-1', scheduledAt: 100, reason: 'scheduled' as const } + ]) + const onRunDue = vi.fn() + const manager = new SchedulerProcessManagerCtor({ + getSnapshot: () => ({ enabledJobCount: 1, nextRunAt: 100 }), + queueDueRuns, + onRunDue, + spawnHost: vi.fn(async () => schedulerHost(host)) + }) + await manager.reconcile() + for (const requestId of [NaN, 0, '1']) + host.emit('message', { type: 'SCAN_DUE', requestId, now: Date.now() }) + expect(queueDueRuns).not.toHaveBeenCalled() + host.emit('message', { type: 'SCAN_DUE', requestId: 1, now: Date.now() }) + expect(onRunDue).toHaveBeenCalledWith(queueDueRuns.mock.results[0].value[0]) + expect(host.kill).toHaveBeenCalled() + host.emit('message', { type: 'SCAN_DUE', requestId: 1, now: Date.now() }) + await manager.stop() + host.emit('message', { type: 'SCAN_DUE', requestId: 2, now: Date.now() }) + expect(queueDueRuns).toHaveBeenCalledTimes(1) + expect(onRunDue).toHaveBeenCalledTimes(1) + }) + + it('kills a pending spawn superseded by shutdown without reviving scheduler state', async () => { + const host = Object.assign(new EventEmitter(), { + pid: 123, + kill: vi.fn(), + postMessage: vi.fn() + }) + let resolveHost!: (value: typeof host) => void + const pendingHost = new Promise((resolve) => { + resolveHost = resolve + }) + const manager = new SchedulerProcessManagerCtor({ + getSnapshot: () => ({ enabledJobCount: 1, nextRunAt: 100 }), + queueDueRuns: () => [], + onRunDue: vi.fn(), + spawnHost: () => pendingHost.then(schedulerHost) + }) + const starting = manager.reconcile() + await manager.stop() + resolveHost(host) + await expect(starting).rejects.toThrow('spawn was superseded') + expect(host.kill).toHaveBeenCalledOnce() + expect(host.postMessage).not.toHaveBeenCalled() + expect(manager.getStatus()).toMatchObject({ state: 'stopped', pid: null }) + }) + it('starts the scheduler only for enabled jobs and stops after idle heartbeat', async () => { vi.useFakeTimers() try { @@ -1512,13 +1808,13 @@ describeIfSqlite('Cron Jobs persistence and service', () => { nextRunAt: null as number | null } const host = new FakeHost() - const spawnHost = vi.fn(async () => host) + const spawnHost = vi.fn(async () => schedulerHost(host)) const manager = new SchedulerProcessManagerCtor({ - dbPath: ':memory:', getSnapshot: () => snapshot, + queueDueRuns: () => [], onRunDue: vi.fn(), idleShutdownMs: 10, - spawnHost: spawnHost as never + spawnHost }) expect(await manager.reconcile('initial')).toEqual( @@ -1594,13 +1890,13 @@ describeIfSqlite('Cron Jobs persistence and service', () => { let spawnCount = 0 const spawnHost = vi.fn(async () => { spawnCount += 1 - return spawnCount === 1 ? firstHost : secondHost + return schedulerHost(spawnCount === 1 ? firstHost : secondHost) }) const manager = new SchedulerProcessManagerCtor({ - dbPath: ':memory:', getSnapshot: () => snapshot, + queueDueRuns: () => [], onRunDue: vi.fn(), - spawnHost: spawnHost as never + spawnHost }) await manager.reconcile('initial') @@ -1620,12 +1916,12 @@ describeIfSqlite('Cron Jobs persistence and service', () => { it('records a utility spawn failure in scheduler status', async () => { const manager = new SchedulerProcessManagerCtor({ - dbPath: ':memory:', getSnapshot: () => ({ enabledJobCount: 1, nextRunAt: 100 }), + queueDueRuns: () => [], onRunDue: vi.fn(), spawnHost: vi.fn(async () => { throw new Error('Failed to spawn scheduler host.') - }) as never + }) }) await expect(manager.reconcile('spawn-failure')).rejects.toThrow( @@ -1661,10 +1957,10 @@ describeIfSqlite('Cron Jobs persistence and service', () => { } const host = new FakeHost() const manager = new SchedulerProcessManagerCtor({ - dbPath: ':memory:', getSnapshot: () => snapshot, + queueDueRuns: () => [], onRunDue: vi.fn(), - spawnHost: vi.fn(async () => host) as never + spawnHost: vi.fn(async () => schedulerHost(host)) }) await manager.reconcile('enabled') diff --git a/test/main/scripts/afterPack.test.ts b/test/main/scripts/afterPack.test.ts index 00de867421..b7de6d747f 100644 --- a/test/main/scripts/afterPack.test.ts +++ b/test/main/scripts/afterPack.test.ts @@ -11,7 +11,7 @@ vi.mock('node:fs', async () => { }) const loadAfterPack = async () => { - return (await import('../../../scripts/afterPack.js')).default as (context: { + const afterPack = (await import('../../../scripts/afterPack.js')).default as (context: { targets: Array<{ name: string }> appOutDir: string electronPlatformName: string @@ -23,6 +23,30 @@ const loadAfterPack = async () => { } } }) => Promise + return async (context: Parameters[0]) => { + const resources = + context.electronPlatformName === 'darwin' + ? path.join( + context.appOutDir, + `${context.packager?.appInfo?.productFilename ?? 'DeepChat'}.app`, + 'Contents', + 'Resources' + ) + : path.join(context.appOutDir, 'resources') + const catalogDirectory = path.join(resources, 'app.asar.unpacked', 'resources', 'model-db') + await mkdir(catalogDirectory, { recursive: true }) + await writeFile( + path.join(catalogDirectory, 'providers.json'), + JSON.stringify({ providers: { fixture: { id: 'fixture', models: [{ id: 'fixture' }] } } }) + ) + const registryDirectory = path.join(resources, 'app.asar.unpacked', 'resources', 'acp-registry') + await mkdir(registryDirectory, { recursive: true }) + await writeFile( + path.join(registryDirectory, 'registry.json'), + JSON.stringify({ version: 'fixture', agents: [{ id: 'fixture' }] }) + ) + await afterPack(context) + } } const loadPackageLightOcrAssets = async () => { @@ -409,7 +433,51 @@ describe('afterPack', () => { await rm(tmpDir, { recursive: true, force: true }) }) - it('keeps non-Linux packages unchanged', async () => { + it.each(['darwin', 'win32', 'linux'])( + 'requires a readable unpacked Provider catalog for %s', + async (electronPlatformName) => { + const { validateProviderCatalog } = await import('../../../scripts/afterPack.js') + const context = { appOutDir: tmpDir, electronPlatformName } + await expect(validateProviderCatalog(context)).rejects.toThrow() + const resources = electronPlatformName === 'darwin' + ? path.join(tmpDir, 'DeepChat.app', 'Contents', 'Resources') + : path.join(tmpDir, 'resources') + const directory = path.join(resources, 'app.asar.unpacked', 'resources', 'model-db') + await mkdir(directory, { recursive: true }) + const catalog = path.join(directory, 'providers.json') + await writeFile(catalog, JSON.stringify({ providers: { fixture: { id: 'fixture' } } })) + await expect(validateProviderCatalog(context)).resolves.toBeUndefined() + await writeFile(catalog, JSON.stringify({ providers: {} })) + await expect(validateProviderCatalog(context)).rejects.toThrow('empty or invalid') + } + ) + + it.each(['darwin', 'win32', 'linux'])( + 'requires readable unpacked ACP registry and icon assets for %s', + async (electronPlatformName) => { + const { validateAcpRegistry } = await import('../../../scripts/afterPack.js') + const context = { appOutDir: tmpDir, electronPlatformName } + await expect(validateAcpRegistry(context)).rejects.toThrow() + const resources = + electronPlatformName === 'darwin' + ? path.join(tmpDir, 'DeepChat.app', 'Contents', 'Resources') + : path.join(tmpDir, 'resources') + const directory = path.join(resources, 'app.asar.unpacked', 'resources', 'acp-registry') + await mkdir(path.join(directory, 'icons'), { recursive: true }) + const file = path.join(directory, 'registry.json') + const icon = 'https://cdn.agentclientprotocol.com/registry/v1/latest/fixture.svg' + await writeFile(file, JSON.stringify({ version: '1', agents: [{ id: 'fixture', icon }] })) + await expect(validateAcpRegistry(context)).rejects.toThrow() + await writeFile(path.join(directory, 'icons', 'fixture.svg'), '') + await expect(validateAcpRegistry(context)).resolves.toBeUndefined() + await writeFile(file, JSON.stringify({ version: '1', agents: [{ id: '../escape', icon }] })) + await expect(validateAcpRegistry(context)).rejects.toThrow('invalid agent ID') + await writeFile(file, JSON.stringify({ version: '1', agents: [] })) + await expect(validateAcpRegistry(context)).rejects.toThrow('empty or invalid') + } + ) + + it('keeps non-Linux launchers unchanged', async () => { const afterPack = await loadAfterPack() const launcherPath = path.join(tmpDir, 'DeepChat') await writeFile(launcherPath, 'launcher') diff --git a/test/main/scripts/agentCleanupGuard.test.ts b/test/main/scripts/agentCleanupGuard.test.ts index 2e902f21a6..015746c2ac 100644 --- a/test/main/scripts/agentCleanupGuard.test.ts +++ b/test/main/scripts/agentCleanupGuard.test.ts @@ -142,7 +142,7 @@ describe('agent cleanup guard', () => { expect( findDeepChatRootOwnershipViolations(` // this.pendingInputs.claimQueuedInput('session', 'item') - const diagnostic = 'tryAcquirePendingQueueDrain()' + const diagnostic = 'beginInput()' `) ).toEqual([]) }) diff --git a/test/main/scripts/packageContract.test.ts b/test/main/scripts/packageContract.test.ts index aa11b9acf5..ceb9387ad0 100644 --- a/test/main/scripts/packageContract.test.ts +++ b/test/main/scripts/packageContract.test.ts @@ -92,6 +92,15 @@ describe('CI package contract', () => { } ] }) + for (const harnessPath of [ + 'rust-toolchain.toml', 'scripts/build-harness.mjs', 'crates/deepchat-harness/src/main.rs', + 'crates/deepchat-harness/src/admission.rs', 'src/shared/harness/admission.ts', + 'scripts/smoke-harness-memory.mjs', 'src/main/harness/runtime.ts' + ]) { + expect(classifyPackageImpact([harnessPath])).toMatchObject({ + required: true, windows: true, linux: true, macos: true + }) + } expect(classifyPackageImpact(['.github/workflows/_package-windows.yml'])).toMatchObject({ required: true, windows: true, diff --git a/test/main/scripts/packageWorkflow.test.ts b/test/main/scripts/packageWorkflow.test.ts index f12a8ffd9a..74fb1db3ba 100644 --- a/test/main/scripts/packageWorkflow.test.ts +++ b/test/main/scripts/packageWorkflow.test.ts @@ -161,6 +161,20 @@ const runBashStep = (script: string, env: NodeJS.ProcessEnv) => }) describe('native package reusable workflows', () => { + it('executes the packaged Memory worker on each native runner', () => { + for (const definition of Object.values(reusableWorkflows)) { + const workflow = readWorkflow(definition.name) + const step = getStep(workflow, 'Verify packaged Memory runtime') + expect(step.if).toBeUndefined() + expect(step.run).toContain('pnpm run smoke:harness:memory --') + expect(step.run).toContain('--resources-path "') + expect(step.run).toContain('--executable "') + expect(workflow.jobs.package.steps.indexOf(step)).toBeGreaterThan( + workflow.jobs.package.steps.indexOf(getStep(workflow, 'Verify packaged DuckDB VSS')) + ) + } + }) + it('owns the fixed runner mapping and a shared immutable input contract', () => { for (const definition of Object.values(reusableWorkflows)) { const workflow = readWorkflow(definition.name) diff --git a/test/main/scripts/prcheckWorkflow.test.ts b/test/main/scripts/prcheckWorkflow.test.ts index f4285d761a..3967682dd3 100644 --- a/test/main/scripts/prcheckWorkflow.test.ts +++ b/test/main/scripts/prcheckWorkflow.test.ts @@ -150,6 +150,9 @@ describe('PR Check workflow contracts', () => { ]) expect(getRunCommands(workflow.jobs['test-main'])).toEqual([ 'pnpm install --frozen-lockfile', + 'cargo fmt --check --manifest-path crates/deepchat-harness/Cargo.toml\n' + + 'cargo clippy --locked --manifest-path crates/deepchat-harness/Cargo.toml --all-targets -- -D warnings\n' + + 'cargo test --locked --manifest-path crates/deepchat-harness/Cargo.toml\n', 'pnpm run test:main' ]) expect(getRunCommands(workflow.jobs['test-renderer'])).toEqual([ @@ -178,13 +181,14 @@ describe('PR Check workflow contracts', () => { const orderedStepNames = [ 'Install dependencies', 'Install and verify DuckDB VSS', + 'Build Rutis harness for native integration', 'Validate portable memory behavior', 'Smoke native SQLite', - 'Validate native Tape storage', + 'Validate native Store ownership and Tape', 'Validate encrypted OCR artifact storage', 'Validate native memory storage', 'Validate memory retrieval quality', - 'Validate memory performance bounds', + 'Validate memory workload bounds and report timings', 'Upload memory retrieval report' ] const indexes = orderedStepNames.map((name) => @@ -207,6 +211,7 @@ describe('PR Check workflow contracts', () => { expect(getStep(nativeJob, 'Validate portable memory behavior').run).toBe( 'pnpm run test:memory' ) + expect(getStep(nativeJob, 'Build Rutis harness for native integration').run).toBe('pnpm run harness:build') // better-sqlite3-multiple-ciphers 13+ ships N-API prebuilds; the Node-ABI // rebuild step is gone and must not come back. expect( @@ -217,7 +222,7 @@ describe('PR Check workflow contracts', () => { expect(getStep(nativeJob, 'Smoke native SQLite').run).toBe( 'node scripts/smoke-memory-native-sqlite.js' ) - expect(getStep(nativeJob, 'Validate native Tape storage').run).toContain( + expect(getStep(nativeJob, 'Validate native Store ownership and Tape').run).toContain( 'test/main/tape/traceInspector.test.ts' ) expect(getStep(nativeJob, 'Validate encrypted OCR artifact storage').run).toBe( @@ -239,12 +244,15 @@ describe('PR Check workflow contracts', () => { expect(getStep(nativeJob, 'Validate memory retrieval quality').env).toEqual({ DEEPCHAT_REQUIRE_NATIVE_SQLITE: '1' }) - expect(getStep(nativeJob, 'Validate memory performance bounds').run).toBe( + expect(getStep(nativeJob, 'Validate memory workload bounds and report timings').run).toBe( 'pnpm run test:main:memory-perf' ) - expect(getStep(nativeJob, 'Validate memory performance bounds').env).toEqual({ - DEEPCHAT_REQUIRE_NATIVE_SQLITE: '1' + expect(getStep(nativeJob, 'Validate memory workload bounds and report timings').env).toEqual({ + DEEPCHAT_REQUIRE_NATIVE_SQLITE: '1', + DEEPCHAT_MEMORY_PERF_REPORT_ONLY: '1' }) + expect(nativeJob).not.toHaveProperty('continue-on-error') + for (const step of nativeJob.steps ?? []) expect(step).not.toHaveProperty('continue-on-error') expect(getStep(nativeJob, 'Upload memory retrieval report')).toMatchObject({ if: 'always()', with: { diff --git a/test/main/scripts/testEntrypoints.test.ts b/test/main/scripts/testEntrypoints.test.ts index b15727b2d0..68c20e0e7c 100644 --- a/test/main/scripts/testEntrypoints.test.ts +++ b/test/main/scripts/testEntrypoints.test.ts @@ -16,11 +16,12 @@ const windowsArm64Workflow = fs.readFileSync( describe('test entrypoint contracts', () => { it('keeps complete test suites one-shot and watch mode explicit', () => { expect(packageJson.scripts).toMatchObject({ - test: 'vitest run', - 'test:main': 'vitest run --config vitest.config.ts test/main', + test: 'pnpm run harness:build && vitest run', + 'test:main': 'pnpm run harness:build && vitest run --config vitest.config.ts test/main', 'test:renderer': 'vitest run --config vitest.config.renderer.ts test/renderer', - 'test:coverage': 'vitest run --coverage', - 'test:watch': 'vitest --watch' + 'test:main:memory-perf': 'pnpm run harness:build && cross-env ELECTRON_RUN_AS_NODE=1 electron node_modules/vitest/vitest.mjs --config vitest.config.memory-perf.ts --run', + 'test:coverage': 'pnpm run harness:build && vitest run --coverage', + 'test:watch': 'pnpm run harness:build && vitest --watch' }) }) diff --git a/test/main/session/data/tapeLifecycle.test.ts b/test/main/session/data/tapeLifecycle.test.ts index 87a3493837..5b29a3e11c 100644 --- a/test/main/session/data/tapeLifecycle.test.ts +++ b/test/main/session/data/tapeLifecycle.test.ts @@ -10,7 +10,6 @@ import { SqliteTapeLifecycleAdapter, vi } from './tapeTestHarness' -import { SessionTranscriptMutations } from '@/session/transcriptMutations' function createHarness() { const calls: string[] = [] @@ -269,73 +268,6 @@ describe('SessionTape lifecycle administration', () => { } }) - itIfSqlite( - 'rolls transcript cleanup back with a failed Tape reset on one connection', - async () => { - const db = new DatabaseCtor(':memory:') - try { - db.exec(` - CREATE TABLE clear_pending (session_id TEXT NOT NULL); - CREATE TABLE clear_transcript (session_id TEXT NOT NULL); - INSERT INTO clear_pending (session_id) VALUES ('s1'); - INSERT INTO clear_transcript (session_id) VALUES ('s1'); - `) - const entryStore = new DeepChatTapeEntriesTable(db) - const searchProjection = new DeepChatTapeSearchProjectionTable(db) - entryStore.createTable() - searchProjection.createTable() - entryStore.ensureBootstrapAnchor('s1') - entryStore.appendEvent({ - sessionId: 's1', - name: 'old/generation', - data: { marker: 'old generation' } - }) - const tape = new SessionTape({ - deepchatTapeEntriesTable: entryStore, - tapeLifecycle: new SqliteTapeLifecycleAdapter(db), - deepchatTapeSearchProjectionTable: searchProjection - } as any) - vi.spyOn(searchProjection, 'deleteBySession').mockImplementationOnce(() => { - throw new Error('search cleanup failed') - }) - const runtime = { - prepareClearMessages: vi.fn().mockResolvedValue(undefined), - finishClearMessages: vi.fn() - } - const mutations = new SessionTranscriptMutations({ - pendingInputs: { - deleteBySession: () => - db.prepare('DELETE FROM clear_pending WHERE session_id = ?').run('s1') - }, - transcript: { - deleteBySession: () => - db.prepare('DELETE FROM clear_transcript WHERE session_id = ?').run('s1') - }, - settings: { resetTape: () => tape.resetSessionTape('s1') }, - runtime, - runInTransaction: (operation) => db.transaction(operation)() - } as any) - - await expect(mutations.clearMessages('s1')).rejects.toThrow('search cleanup failed') - - expect(db.prepare('SELECT COUNT(*) AS count FROM clear_pending').get()).toEqual({ - count: 1 - }) - expect(db.prepare('SELECT COUNT(*) AS count FROM clear_transcript').get()).toEqual({ - count: 1 - }) - expect(entryStore.getBySession('s1').map((entry) => entry.name)).toEqual([ - 'session/start', - 'old/generation' - ]) - expect(runtime.finishClearMessages).not.toHaveBeenCalled() - } finally { - vi.restoreAllMocks() - db.close() - } - } - ) - itIfSqlite('drops corrupt FTS state instead of blocking a Tape generation reset', () => { const db = new DatabaseCtor(':memory:') try { diff --git a/test/main/session/data/tapeTestHarness.ts b/test/main/session/data/tapeTestHarness.ts index 48b478c9cf..949949fb6d 100644 --- a/test/main/session/data/tapeTestHarness.ts +++ b/test/main/session/data/tapeTestHarness.ts @@ -4,11 +4,7 @@ import { buildContext } from '@/agent/deepchat/runtime/contextBuilder' import { toAppSessionId } from '@/agent/shared/agentSessionIds' import { SessionTape } from '@/tape/application/sessionTape' import { buildEffectiveTapeView, searchEffectiveTapeRows } from '@/tape/domain/effectiveView' -import { - buildRequestRefs, - createTapeViewManifest, - type TapeViewManifestBuildInput -} from '@/tape/domain/viewManifest' +import { buildRequestRefs, createTapeViewManifest } from '@/tape/domain/viewManifest' import { appendMessageRecordToTape, appendMessageReplacementToTape, diff --git a/test/main/session/data/transcriptAtomicity.test.ts b/test/main/session/data/transcriptAtomicity.test.ts index 8762192c53..07da9c2713 100644 --- a/test/main/session/data/transcriptAtomicity.test.ts +++ b/test/main/session/data/transcriptAtomicity.test.ts @@ -1,17 +1,22 @@ -import { describe, expect, it } from 'vitest' +import { describe, expect, it, vi } from 'vitest' import type { AssistantMessageBlock } from '@shared/types/agent-interface' +import { SessionTranscriptMutations } from '@/session/transcriptMutations' const sqliteModule = await import('better-sqlite3-multiple-ciphers').catch(() => null) const mainDatabaseModule = sqliteModule ? await import('@/data/mainDatabase') : null const sessionDatabaseModule = sqliteModule ? await import('@/session/data/database') : null const sessionTapeModule = sqliteModule ? await import('@/tape/application/sessionTape') : null const transcriptModule = sqliteModule ? await import('@/session/data/transcript') : null +const sessionDataModule = sqliteModule ? await import('@/session/data') : null +const memoryDatabaseModule = sqliteModule ? await import('@/memory/data/database') : null const Database = sqliteModule?.default const MainDatabaseCtor = mainDatabaseModule?.MainDatabase! const SessionDatabaseCtor = sessionDatabaseModule?.SessionDatabase! const SessionTapeCtor = sessionTapeModule?.SessionTape! const SessionTranscriptCtor = transcriptModule?.SessionTranscript! +const createSessionData = sessionDataModule?.createSessionData! +const MemoryDatabaseCtor = memoryDatabaseModule?.MemoryDatabase! let sqliteAvailable = false if (Database) { @@ -39,6 +44,60 @@ const blocks: AssistantMessageBlock[] = [ * roll back with it instead of committing a message the Tape never saw. */ describeIfSqlite('SessionTranscript keeps transcript and Tape writes atomic', () => { + it.each([false, true])( + 'clears transcript, queue and projections together or preserves all data (%s)', + async (failReset) => { + const connection = new MainDatabaseCtor(':memory:') + try { + const projection = new MemoryDatabaseCtor(connection).ingestionProjectionTable + const data = createSessionData(connection, () => projection, { + publishPendingInputsChanged: vi.fn(), + publishMessagesChanged: vi.fn() + }) + data.transcript.createUserMessage('s1', 1, userContent) + data.pendingInputs.queuePendingInput('s1', { text: 'queued' }) + const messages = data.transcript.getMessages('s1') + const incarnation = data.tapeStore.getTapeIncarnationId('s1') + const head = data.tapeStore.getProjectionHead('s1') + const runtime = { + prepareClearMessages: vi.fn().mockResolvedValue(undefined), + finishClearMessages: vi.fn() + } + const mutations = new SessionTranscriptMutations({ + transcript: data.transcript, + clearTranscript: data.clearTranscript, + runtime: runtime as never + }) + if (failReset) { + connection.getDatabase().exec(` + CREATE TRIGGER reject_tape_reset BEFORE DELETE ON deepchat_tape_entries + BEGIN SELECT RAISE(ABORT, 'Tape reset rejected'); END; + `) + await expect(mutations.clearMessages('s1')).rejects.toThrow('Tape reset rejected') + expect(data.transcript.getMessages('s1')).toEqual(messages) + expect(data.pendingInputs.listPendingInputs('s1')).toHaveLength(1) + expect(data.tapeStore.getTapeIncarnationId('s1')).toBe(incarnation) + expect(data.tapeStore.getProjectionHead('s1')).toEqual(head) + expect(runtime.finishClearMessages).not.toHaveBeenCalled() + } else { + await mutations.clearMessages('s1') + expect(data.transcript.getMessages('s1')).toEqual([]) + expect(data.pendingInputs.listPendingInputs('s1')).toEqual([]) + expect(data.tapeStore.getTapeIncarnationId('s1')).not.toBe(incarnation) + expect(runtime.finishClearMessages).toHaveBeenCalledOnce() + } + expect( + connection + .getDatabase() + .prepare('SELECT count(*) AS count FROM deepchat_memory_ingestion_projection') + .get() + ).toEqual({ count: failReset ? 1 : 0 }) + } finally { + connection.close() + } + } + ) + function createTranscript() { const connection = new MainDatabaseCtor(':memory:') const database = new SessionDatabaseCtor(connection) @@ -61,6 +120,7 @@ describeIfSqlite('SessionTranscript keeps transcript and Tape writes atomic', () connection, database, transcript, + tapeFacts, setTapeFailing: (value: boolean) => { failTape = value } @@ -84,6 +144,39 @@ describeIfSqlite('SessionTranscript keeps transcript and Tape writes atomic', () } }) + it('rolls retry truncation back when a later Tape retraction fails', () => { + const { connection, database, transcript, tapeFacts } = createTranscript() + try { + const keptId = transcript.createUserMessage('s1', 1, userContent) + transcript.createUserMessage('s1', 2, { ...userContent, text: 'second' }) + transcript.createUserMessage('s1', 3, { ...userContent, text: 'third' }) + const messages = transcript.getMessages('s1') + const facts = database.deepchatTapeEntriesTable.getBySession('s1') + const appendRetraction = tapeFacts.appendMessageRetraction + let retractions = 0 + const fail = vi.spyOn(tapeFacts, 'appendMessageRetraction').mockImplementation((...args) => { + if (++retractions === 2) throw new Error('Later retraction failed') + return appendRetraction(...args) + }) + const runtime = { invalidateTranscriptFrom: vi.fn() } + const mutations = new SessionTranscriptMutations({ + transcript, + runtime: runtime as never, + clearTranscript: vi.fn() + }) + expect(() => mutations.commitRetryMessage('s1', 2)).toThrow('Later retraction failed') + expect(transcript.getMessages('s1')).toEqual(messages) + expect(database.deepchatTapeEntriesTable.getBySession('s1')).toEqual(facts) + expect(runtime.invalidateTranscriptFrom).not.toHaveBeenCalled() + fail.mockRestore() + mutations.commitRetryMessage('s1', 2) + expect(transcript.getMessages('s1').map((message) => message.id)).toEqual([keptId]) + expect(runtime.invalidateTranscriptFrom).toHaveBeenCalledWith('s1', 2) + } finally { + connection.close() + } + }) + it('keeps the assistant message pending when its terminal Tape fact fails', () => { const { connection, database, transcript, setTapeFailing } = createTranscript() try { diff --git a/test/main/session/runtimeIntegration.test.ts b/test/main/session/runtimeIntegration.test.ts index 3c9aab4573..f2268693a8 100644 --- a/test/main/session/runtimeIntegration.test.ts +++ b/test/main/session/runtimeIntegration.test.ts @@ -9,7 +9,10 @@ import type { ReasoningEffort, Verbosity } from '@shared/types/model-db' import logger from '@shared/logger' import { toAppSessionId } from '@/agent/shared/agentSessionIds' import type { DeepChatActiveGeneration } from '@/agent/deepchat/instance/deepChatAgentInstance' -import { createDeepChatAgentBackendFixture } from '../agent/manager/deepChatAgentBackendFixture' +import { + createBackendControl, + createDeepChatAgentBackendFixture +} from '../agent/manager/deepChatAgentBackendFixture' import { createSessionQueryFixture } from './queryFixture' import { createSessionFixture } from './sessionFixture' import { createSessionData, createSessionDataFromDatabase } from '@/session/data' @@ -969,6 +972,7 @@ function createMockToolService() { function createRuntimeDependencies() { return { + control: createBackendControl(), publishEvent: publishDeepchatEventMock, publishSessionUpdate: vi.fn(), providerCatalogPort: { @@ -1020,7 +1024,7 @@ function createRuntimeDependencies() { resolveSessionAgentId: vi.fn().mockResolvedValue('deepchat'), validateSkillNames: vi .fn() - .mockImplementation(async (...args: unknown[]) => args.at(-1) as string[]), + .mockImplementation(async (_agentId: string, skillNames: string[]) => skillNames), setActiveSkills: vi .fn() .mockImplementation(async (_sessionId: string, skills: string[]) => skills), @@ -1042,10 +1046,8 @@ function createTranscriptMutations( ) { return new SessionTranscriptMutations({ transcript: sessionData.transcript, - settings: sessionData.settings, - pendingInputs: sessionData.pendingInputs, runtime, - runInTransaction: (operation) => sessionData.database.getDatabase().transaction(operation)() + clearTranscript: sessionData.clearTranscript }) } @@ -1076,6 +1078,23 @@ function createDeepChatManager(deepchatAgent: DeepChatAgentHarness, sqlitePresen } } +async function waitForCompletedTurns( + sqlitePresenter: ReturnType, + projection: ReturnType, + sessionId: string, + count: number +): Promise { + await vi.waitFor(async () => { + const assistants = sqlitePresenter.deepchatMessagesTable + .getBySession(sessionId) + .filter((message: any) => message.role === 'assistant') + expect(assistants).toHaveLength(count) + expect(assistants.every((message: any) => message.status === 'sent')).toBe(true) + expect((await projection.getSession(sessionId))?.status).toBe('idle') + }) + await createBackendControl.flush() +} + describe('Integration: createSession end-to-end', () => { let sqlitePresenter: ReturnType let llmProvider: ReturnType @@ -1154,8 +1173,7 @@ describe('Integration: createSession end-to-end', () => { 1 ) - // Wait for non-blocking processMessage to complete - await new Promise((r) => setTimeout(r, 50)) + await waitForCompletedTurns(sqlitePresenter, projection, session.id, 1) // 1. new_sessions row created expect(sqlitePresenter.newSessionsTable.create).toHaveBeenCalledWith( @@ -1226,10 +1244,8 @@ describe('Integration: createSession end-to-end', () => { }) it('session list returns enriched sessions', async () => { - await lifecycle.createSession({ agentId: 'deepchat', message: 'Hello' }, 1) - - // Wait for processMessage to complete - await new Promise((r) => setTimeout(r, 50)) + const session = await lifecycle.createSession({ agentId: 'deepchat', message: 'Hello' }, 1) + await waitForCompletedTurns(sqlitePresenter, projection, session.id, 1) const sessions = await projection.listSessions() expect(sessions).toHaveLength(1) @@ -1240,7 +1256,7 @@ describe('Integration: createSession end-to-end', () => { it('deleteSession cleans up all data', async () => { const session = await lifecycle.createSession({ agentId: 'deepchat', message: 'To delete' }, 1) - await new Promise((r) => setTimeout(r, 50)) + await waitForCompletedTurns(sqlitePresenter, projection, session.id, 1) await lifecycle.deleteSession(session.id) @@ -1252,7 +1268,7 @@ describe('Integration: createSession end-to-end', () => { it('clearSessionMessages clears messages but keeps session row', async () => { const session = await lifecycle.createSession({ agentId: 'deepchat', message: 'To clear' }, 1) - await new Promise((r) => setTimeout(r, 50)) + await waitForCompletedTurns(sqlitePresenter, projection, session.id, 1) await turn.clearSessionMessages(session.id) @@ -1334,7 +1350,12 @@ describe('Integration: ACP hook observer', () => { 1 ) - await new Promise((r) => setTimeout(r, 50)) + await vi.waitFor(() => { + expect(hookDispatcher.dispatchEvent).toHaveBeenCalledWith( + 'SessionEnd', + expect.objectContaining({ conversationId: session.id }) + ) + }) expect(session.agentId).toBe('coder') expect(hookDispatcher.dispatchEvent).toHaveBeenCalledWith( @@ -1442,14 +1463,12 @@ describe('Integration: multi-turn context', () => { 1 ) - // Wait for first processMessage to complete - await new Promise((r) => setTimeout(r, 50)) + await waitForCompletedTurns(sqlitePresenter, projection, session.id, 1) // Send second message await turn.sendMessage(session.id, 'Follow up question') - // Wait for second processMessage to complete - await new Promise((r) => setTimeout(r, 50)) + await waitForCompletedTurns(sqlitePresenter, projection, session.id, 2) // Verify coreStream was called twice const providerInstance = llmProvider.getProviderInstance.mock.results[0].value @@ -1516,16 +1535,16 @@ describe('Integration: multi-turn context', () => { { agentId: 'deepchat', message: 'Hello', projectDir: null }, 1 ) - await new Promise((r) => setTimeout(r, 50)) + await waitForCompletedTurns(sqlitePresenter, projection, session.id, 1) await turn.sendMessage(session.id, 'Follow up (string)') - await new Promise((r) => setTimeout(r, 50)) + await waitForCompletedTurns(sqlitePresenter, projection, session.id, 2) await turn.sendMessage(session.id, { text: 'Follow up (object)', files: [{ name: 'a.md', path: '/tmp/a.md', mimeType: 'text/markdown', content: '# a' } as any] }) - await new Promise((r) => setTimeout(r, 50)) + await waitForCompletedTurns(sqlitePresenter, projection, session.id, 3) const messages = sqlitePresenter.deepchatMessagesTable.getBySession(session.id) const userMessages = messages.filter((m: any) => m.role === 'user') @@ -1537,12 +1556,12 @@ describe('Integration: multi-turn context', () => { }) it('persists the initial user message before the first assistant stream responds', async () => { - let releaseFirstTurn: (() => void) | null = null + const firstProviderEntered = Promise.withResolvers() + const releaseFirstTurn = Promise.withResolvers() const providerInstance = { coreStream: vi.fn().mockImplementationOnce(async function* () { - await new Promise((resolve) => { - releaseFirstTurn = resolve - }) + firstProviderEntered.resolve() + await releaseFirstTurn.promise yield { type: 'text', content: 'First response' } yield { type: 'stop', stop_reason: 'complete' } }) @@ -1553,7 +1572,7 @@ describe('Integration: multi-turn context', () => { { agentId: 'deepchat', message: 'First turn', projectDir: null }, 1 ) - await new Promise((r) => setTimeout(r, 20)) + await firstProviderEntered.promise const messagesBeforeStream = sqlitePresenter.deepchatMessagesTable.getBySession(session.id) const userMessagesBeforeStream = messagesBeforeStream.filter( @@ -1562,12 +1581,13 @@ describe('Integration: multi-turn context', () => { expect(userMessagesBeforeStream).toHaveLength(1) expect(JSON.parse(userMessagesBeforeStream[0].content).text).toBe('First turn') - releaseFirstTurn?.() - await new Promise((r) => setTimeout(r, 80)) + releaseFirstTurn.resolve() + await waitForCompletedTurns(sqlitePresenter, projection, session.id, 1) }) it('keeps immediately runnable sends out of the visible pending queue', async () => { - let releaseSecondTurn: (() => void) | null = null + const secondProviderEntered = Promise.withResolvers() + const releaseSecondTurn = Promise.withResolvers() const providerInstance = { coreStream: vi .fn() @@ -1576,9 +1596,8 @@ describe('Integration: multi-turn context', () => { yield { type: 'stop', stop_reason: 'complete' } }) .mockImplementationOnce(async function* () { - await new Promise((resolve) => { - releaseSecondTurn = resolve - }) + secondProviderEntered.resolve() + await releaseSecondTurn.promise yield { type: 'text', content: 'Second response' } yield { type: 'stop', stop_reason: 'complete' } }) @@ -1589,33 +1608,36 @@ describe('Integration: multi-turn context', () => { { agentId: 'deepchat', message: 'First turn', projectDir: null }, 1 ) - await new Promise((r) => setTimeout(r, 80)) - - await turn.sendMessage(session.id, 'Immediate follow up') - await new Promise((r) => setTimeout(r, 20)) - - await expect(turn.listPendingInputs(session.id)).resolves.toEqual([]) + await waitForCompletedTurns(sqlitePresenter, projection, session.id, 1) - const messagesDuringSecondTurn = sqlitePresenter.deepchatMessagesTable.getBySession(session.id) - const userMessagesDuringSecondTurn = messagesDuringSecondTurn.filter( - (message: any) => message.role === 'user' - ) - expect(userMessagesDuringSecondTurn).toHaveLength(2) - expect(JSON.parse(userMessagesDuringSecondTurn[1].content).text).toBe('Immediate follow up') + try { + await turn.sendMessage(session.id, 'Immediate follow up') + await expect(turn.listPendingInputs(session.id)).resolves.toEqual([]) + await secondProviderEntered.promise - releaseSecondTurn?.() - await new Promise((r) => setTimeout(r, 80)) + const messagesDuringSecondTurn = sqlitePresenter.deepchatMessagesTable.getBySession( + session.id + ) + const userMessagesDuringSecondTurn = messagesDuringSecondTurn.filter( + (message: any) => message.role === 'user' + ) + expect(userMessagesDuringSecondTurn).toHaveLength(2) + expect(JSON.parse(userMessagesDuringSecondTurn[1].content).text).toBe('Immediate follow up') + } finally { + releaseSecondTurn.resolve() + } + await waitForCompletedTurns(sqlitePresenter, projection, session.id, 2) }) it('keeps queued messages out of formal history until the current turn completes', async () => { - let releaseFirstTurn: (() => void) | null = null + const firstProviderEntered = Promise.withResolvers() + const releaseFirstTurn = Promise.withResolvers() const providerInstance = { coreStream: vi .fn() .mockImplementationOnce(async function* () { - await new Promise((resolve) => { - releaseFirstTurn = resolve - }) + firstProviderEntered.resolve() + await releaseFirstTurn.promise yield { type: 'text', content: 'First response' } yield { type: 'stop', stop_reason: 'complete' } }) @@ -1630,7 +1652,7 @@ describe('Integration: multi-turn context', () => { { agentId: 'deepchat', message: 'First turn', projectDir: null }, 1 ) - await new Promise((r) => setTimeout(r, 20)) + await firstProviderEntered.promise await turn.queuePendingInput(session.id, 'Queued follow up') @@ -1643,8 +1665,8 @@ describe('Integration: multi-turn context', () => { expect(beforeUserMessages).toHaveLength(1) expect(JSON.parse(beforeUserMessages[0].content).text).toBe('First turn') - releaseFirstTurn?.() - await new Promise((r) => setTimeout(r, 80)) + releaseFirstTurn.resolve() + await waitForCompletedTurns(sqlitePresenter, projection, session.id, 2) const afterMessages = sqlitePresenter.deepchatMessagesTable.getBySession(session.id) const afterUserMessages = afterMessages.filter((message: any) => message.role === 'user') @@ -1654,14 +1676,14 @@ describe('Integration: multi-turn context', () => { }) it('drains converted steer inputs as visible user messages before queued messages', async () => { - let releaseFirstTurn: (() => void) | null = null + const firstProviderEntered = Promise.withResolvers() + const releaseFirstTurn = Promise.withResolvers() const providerInstance = { coreStream: vi .fn() .mockImplementationOnce(async function* () { - await new Promise((resolve) => { - releaseFirstTurn = resolve - }) + firstProviderEntered.resolve() + await releaseFirstTurn.promise yield { type: 'text', content: 'First response' } yield { type: 'stop', stop_reason: 'complete' } }) @@ -1676,7 +1698,7 @@ describe('Integration: multi-turn context', () => { { agentId: 'deepchat', message: 'Turn one', projectDir: null }, 1 ) - await new Promise((r) => setTimeout(r, 20)) + await firstProviderEntered.promise await turn.queuePendingInput(session.id, 'Steer instruction') await turn.queuePendingInput(session.id, 'Queued target') @@ -1685,10 +1707,8 @@ describe('Integration: multi-turn context', () => { expect(pendingInputs).toHaveLength(2) await turn.convertPendingInputToSteer(session.id, pendingInputs[0].id) - releaseFirstTurn?.() - await vi.waitFor(() => { - expect(providerInstance.coreStream).toHaveBeenCalledTimes(3) - }) + releaseFirstTurn.resolve() + await waitForCompletedTurns(sqlitePresenter, projection, session.id, 3) expect(providerInstance.coreStream).toHaveBeenCalledTimes(3) const secondCallMessages = providerInstance.coreStream.mock.calls[1][0] @@ -1720,7 +1740,8 @@ describe('Integration: multi-turn context', () => { }) it('rebudgets long converted steer inputs as their own visible turn', async () => { - let releaseFirstTurn: (() => void) | null = null + const firstProviderEntered = Promise.withResolvers() + const releaseFirstTurn = Promise.withResolvers() const firstPrompt = 'P'.repeat(2000) const firstResponse = 'R'.repeat(2000) // Sized so prompt + response + steer exceeds the 2560-token context with the @@ -1730,9 +1751,8 @@ describe('Integration: multi-turn context', () => { coreStream: vi .fn() .mockImplementationOnce(async function* () { - await new Promise((resolve) => { - releaseFirstTurn = resolve - }) + firstProviderEntered.resolve() + await releaseFirstTurn.promise yield { type: 'text', content: firstResponse } yield { type: 'stop', stop_reason: 'complete' } }) @@ -1747,7 +1767,7 @@ describe('Integration: multi-turn context', () => { { agentId: 'deepchat', message: firstPrompt, projectDir: null }, 1 ) - await new Promise((r) => setTimeout(r, 20)) + await firstProviderEntered.promise await assignment.updateSessionGenerationSettings(session.id, { contextLength: 2560, @@ -1770,10 +1790,8 @@ describe('Integration: multi-turn context', () => { expect(pendingInputs).toHaveLength(2) await turn.convertPendingInputToSteer(session.id, pendingInputs[0].id) - releaseFirstTurn?.() - await vi.waitFor(() => { - expect(providerInstance.coreStream).toHaveBeenCalledTimes(3) - }) + releaseFirstTurn.resolve() + await waitForCompletedTurns(sqlitePresenter, projection, session.id, 3) expect(providerInstance.coreStream).toHaveBeenCalledTimes(3) const secondCallMessages = providerInstance.coreStream.mock.calls[1][0] @@ -1820,11 +1838,9 @@ describe('Integration: multi-turn context', () => { llmProvider.getProviderInstance.mockReturnValue(providerInstance) await deepchatAgent.initSession('s-follow-up', { providerId: 'openai', modelId: 'gpt-4' }) - const runtimeState = deepchatAgent.deepChatRuntime + await deepchatAgent.deepChatRuntime .getOrHydrate(toAppSessionId('s-follow-up')) - .getRuntimeState() - if (!runtimeState) throw new Error('Missing runtime state for s-follow-up') - runtimeState.status = 'generating' + .transitionControlStatus({ kind: 'orphan' }, 'generating') await deepchatAgent.queuePendingInput('s-follow-up', 'Older queued prompt') expect(await deepchatAgent.listPendingInputs('s-follow-up')).toHaveLength(1) @@ -1863,12 +1879,20 @@ describe('Integration: multi-turn context', () => { }) ).resolves.toEqual({ resumed: false, waitingForUserMessage: true }) - await new Promise((r) => setTimeout(r, 20)) + await createBackendControl.flush() expect(providerInstance.coreStream).not.toHaveBeenCalled() await expect(deepchatAgent.listPendingInputs('s-follow-up')).resolves.toHaveLength(1) await deepchatAgent.queuePendingInput('s-follow-up', 'Actual follow-up answer') - await new Promise((r) => setTimeout(r, 80)) + await vi.waitFor(async () => { + const assistants = sqlitePresenter.deepchatMessagesTable + .getBySession('s-follow-up') + .filter((message: any) => message.role === 'assistant' && message.id !== 'm-follow-up') + expect(assistants).toHaveLength(2) + expect(assistants.every((message: any) => message.status === 'sent')).toBe(true) + expect((await deepchatAgent.getSessionState('s-follow-up'))?.status).toBe('idle') + }) + await createBackendControl.flush() expect(providerInstance.coreStream).toHaveBeenCalledTimes(2) const firstFollowUpCall = providerInstance.coreStream.mock.calls[0][0] @@ -1909,13 +1933,19 @@ describe('Integration: multi-turn context', () => { { agentId: 'deepchat', message: 'Fail first', projectDir: null }, 1 ) - await new Promise((r) => setTimeout(r, 50)) + await vi.waitFor(async () => { + expect((await projection.getSession(session.id))?.status).toBe('error') + }) + await createBackendControl.flush() const failedSession = await projection.getSession(session.id) expect(failedSession?.status).toBe('error') await turn.sendMessage(session.id, 'Recover after error') - await new Promise((r) => setTimeout(r, 80)) + await vi.waitFor(async () => { + expect((await projection.getSession(session.id))?.status).toBe('idle') + }) + await createBackendControl.flush() const recoveredSession = await projection.getSession(session.id) expect(recoveredSession?.status).toBe('idle') @@ -1929,11 +1959,13 @@ describe('Integration: multi-turn context', () => { }) it('drains queued turns when a new message is enqueued after a session error', async () => { + const firstProviderEntered = Promise.withResolvers() let releaseFirstTurn: (() => void) | null = null const providerInstance = { coreStream: vi .fn() .mockImplementationOnce(async function* () { + firstProviderEntered.resolve() await new Promise((resolve) => { releaseFirstTurn = resolve }) @@ -1950,11 +1982,14 @@ describe('Integration: multi-turn context', () => { { agentId: 'deepchat', message: 'Turn that errors', projectDir: null }, 1 ) - await new Promise((r) => setTimeout(r, 20)) + await firstProviderEntered.promise await turn.queuePendingInput(session.id, 'Queued while failing') releaseFirstTurn?.() - await new Promise((r) => setTimeout(r, 80)) + await vi.waitFor(async () => { + expect((await projection.getSession(session.id))?.status).toBe('error') + }) + await createBackendControl.flush() const failedSession = await projection.getSession(session.id) expect(failedSession?.status).toBe('error') @@ -1965,8 +2000,10 @@ describe('Integration: multi-turn context', () => { // Enqueuing from an errored session drains the backlog (no manual resume step). await turn.queuePendingInput(session.id, 'New message after error') - await vi.waitFor(() => { + await vi.waitFor(async () => { expect(providerInstance.coreStream).toHaveBeenCalledTimes(3) + expect((await projection.getSession(session.id))?.status).toBe('idle') + expect(await turn.listPendingInputs(session.id)).toEqual([]) }) const recoveredSession = await projection.getSession(session.id) diff --git a/test/main/session/sessionFixture.ts b/test/main/session/sessionFixture.ts index 2e8a4b2783..55cd176bef 100644 --- a/test/main/session/sessionFixture.ts +++ b/test/main/session/sessionFixture.ts @@ -1,4 +1,3 @@ -import type { ProviderSettingsPort } from '@/provider/settings' import type { AgentManager } from '@/agent/manager/agentManager' import type { SessionStatePort, @@ -7,9 +6,12 @@ import type { } from '@/session/data/contracts' import type { AppSessionService } from '@/agent/shared/appSessionService' import type { SkillServicePort } from '@shared/types/skill' -import type { MainDatabase } from '@/data/mainDatabase' import type { AcpAsLlmProviderSessionControlPort } from '@/provider/ports' -import type { SessionPermissionPort } from '@/session/contracts' +import type { + SessionAssignmentConfigPort, + SessionAssignmentEnvironmentPort, + SessionPermissionPort +} from '@/session/contracts' import { SessionAssignmentPolicy } from '@/session/assignmentPolicy' import { SessionAssignment } from '@/session/assignment' import { SessionDeletion } from '@/session/deletion' @@ -23,8 +25,11 @@ import { SessionDeletionGate } from '@/session/deletionGate' export const createSessionFixture = (input: { agentManager: AgentManager appSessionService: AppSessionService - providerSettings: ProviderSettingsPort - sqlitePresenter: MainDatabase + providerSettings: Pick & + Partial< + Pick + > + sqlitePresenter: { newEnvironmentsTable: SessionAssignmentEnvironmentPort } sharedData: { sessionState: SessionStatePort transcript: SessionTranscriptReadPort @@ -44,7 +49,7 @@ export const createSessionFixture = (input: { deletion: SessionDeletion desktop: DesktopSessionBinding } => { - const desktop = new DesktopSessionBinding(input.projection) + const desktop = new DesktopSessionBinding(input.projection, () => () => {}) const agentLifecycle = new AgentLifecycleGate() const sessionDeletionGate = new SessionDeletionGate() const policy = new SessionAssignmentPolicy( @@ -128,7 +133,10 @@ export const createSessionFixture = (input: { getSnapshot: () => handle.deepchat.getCompactionSnapshot(), compact: () => handle.deepchat.compact() }, - getContextOccupancy: () => handle.deepchat.getContextOccupancy() + getContextOccupancy: () => handle.deepchat.getContextOccupancy(), + isPendingQueueResumeAvailable: () => handle.deepchat.isPendingQueueResumeAvailable(), + resumePendingQueue: () => handle.deepchat.resumePendingQueue(), + retryPendingQueueInput: (itemId) => handle.deepchat.retryPendingQueueInput(itemId) } : { ...base, kind: handle.kind } } diff --git a/test/main/session/transcriptMutations.test.ts b/test/main/session/transcriptMutations.test.ts index b1cedb3d8f..5a434dff43 100644 --- a/test/main/session/transcriptMutations.test.ts +++ b/test/main/session/transcriptMutations.test.ts @@ -71,79 +71,30 @@ describe('SessionTranscriptMutations', () => { }) }) - it('rolls pending inputs and transcript deletion back when Tape reset fails', async () => { - const state = { pendingInputs: 1, transcriptMessages: 2, tapeEntries: 3 } + it('does not finish runtime cleanup when the durable clear command fails', async () => { const runtime = { prepareClearMessages: vi.fn().mockResolvedValue(undefined), finishClearMessages: vi.fn() } - const mutations = new SessionTranscriptMutations({ - pendingInputs: { - deleteBySession: vi.fn(() => { - state.pendingInputs = 0 - }) - }, - transcript: { - deleteBySession: vi.fn(() => { - state.transcriptMessages = 0 - }) - }, - settings: { - resetTape: vi.fn(() => { - state.tapeEntries = 0 - throw new Error('Tape reset failed') - }) - }, - runtime, - runInTransaction: (operation) => { - const snapshot = { ...state } - try { - return operation() - } catch (error) { - Object.assign(state, snapshot) - throw error - } - } - } as any) - + const clearTranscript = vi.fn(() => { + throw new Error('Tape reset failed') + }) + const mutations = new SessionTranscriptMutations({ runtime, clearTranscript } as never) await expect(mutations.clearMessages('s1')).rejects.toThrow('Tape reset failed') - - expect(state).toEqual({ pendingInputs: 1, transcriptMessages: 2, tapeEntries: 3 }) - expect(runtime.prepareClearMessages).toHaveBeenCalledWith('s1') + expect(clearTranscript).toHaveBeenCalledWith('s1') expect(runtime.finishClearMessages).not.toHaveBeenCalled() }) - it('finishes runtime cleanup only after the shared transaction commits', async () => { - const calls: string[] = [] - const mutations = new SessionTranscriptMutations({ - pendingInputs: { deleteBySession: vi.fn(() => calls.push('pending')) }, - transcript: { deleteBySession: vi.fn(() => calls.push('transcript')) }, - settings: { resetTape: vi.fn(() => calls.push('tape')) }, - runtime: { - prepareClearMessages: vi.fn(async () => { - calls.push('prepare') - }), - finishClearMessages: vi.fn(() => calls.push('finish')) - }, - runInTransaction: (operation) => { - calls.push('transaction:start') - const result = operation() - calls.push('transaction:commit') - return result - } - } as any) - - await mutations.clearMessages('s1') - - expect(calls).toEqual([ - 'prepare', - 'transaction:start', - 'pending', - 'transcript', - 'tape', - 'transaction:commit', - 'finish' - ]) + it('does not clear durable data when runtime preparation fails', async () => { + const clearTranscript = vi.fn() + const runtime = { + prepareClearMessages: vi.fn().mockRejectedValue(new Error('Run did not drain')), + finishClearMessages: vi.fn() + } + const mutations = new SessionTranscriptMutations({ runtime, clearTranscript } as never) + await expect(mutations.clearMessages('s1')).rejects.toThrow('Run did not drain') + expect(clearTranscript).not.toHaveBeenCalled() + expect(runtime.finishClearMessages).not.toHaveBeenCalled() }) it('invalidates retry projections only after transcript deletion commits', () => { @@ -157,18 +108,12 @@ describe('SessionTranscriptMutations', () => { calls.push('delete') }) }, - runtime, - runInTransaction: (operation) => { - calls.push('transaction:start') - const result = operation() - calls.push('transaction:commit') - return result - } + runtime } as any) mutations.commitRetryMessage('s1', 7) - expect(calls).toEqual(['transaction:start', 'delete', 'transaction:commit', 'invalidate']) + expect(calls).toEqual(['delete', 'invalidate']) }) it('does not invalidate retry projections when transcript deletion rolls back', () => { @@ -179,8 +124,7 @@ describe('SessionTranscriptMutations', () => { throw new Error('delete failed') }) }, - runtime, - runInTransaction: (operation) => operation() + runtime } as any) expect(() => mutations.commitRetryMessage('s1', 7)).toThrow('delete failed') @@ -362,6 +306,7 @@ describe('SessionTranscriptMutations', () => { const mutations = new SessionTranscriptMutations({ transcript, runtime } as any) const removing = mutations.editUserMessage('s1', 'message-1', 'removed', []) const retaining = mutations.editUserMessage('s1', 'message-1', 'stale', [reference]) + await vi.waitFor(() => expect(cancellations).toHaveLength(2)) cancellations[0]() await removing cancellations[1]() diff --git a/test/main/settings/appSettingsDbStore.test.ts b/test/main/settings/appSettingsDbStore.test.ts index f732e6559d..21b0f11ae3 100644 --- a/test/main/settings/appSettingsDbStore.test.ts +++ b/test/main/settings/appSettingsDbStore.test.ts @@ -96,7 +96,7 @@ describe('settings DB-backed stores', () => { expect(store.has('mcpServers')).toBe(true) }) - it('reads migrated ACP fields from sqlite and keeps agent-owned legacy fields', () => { + it('reads ACP settings from sqlite and an isolated read-only legacy snapshot', () => { const legacy = createLegacyStore({ enabled: true, sharedMcpSelections: ['legacy-server'], @@ -106,13 +106,23 @@ describe('settings DB-backed stores', () => { agentSettings: { enabled: false, version: '4' }, agentSelections: ['sqlite-server'] }) - const store = new AcpDbStore(legacy, () => tables) + const store = new AcpDbStore(legacy.store, () => tables) + legacy.set('manualAgents', [{ id: 'replaced-agent' }]) expect(store.store.enabled).toBe(false) expect(store.store.sharedMcpSelections).toEqual(['sqlite-server']) expect(store.store.manualAgents).toEqual([{ id: 'legacy-agent' }]) expect(store.get('enabled', false)).toBe(false) expect(store.get('sharedMcpSelections', [])).toEqual(['sqlite-server']) + const manualAgents = store.store.manualAgents as Array<{ id: string }> + manualAgents[0].id = 'mutated-agent' + expect(store.get('manualAgents')).toEqual([{ id: 'legacy-agent' }]) + expect(() => store.set('manualAgents', [])).toThrow('Legacy ACP settings are read-only') + expect(() => store.delete('manualAgents')).toThrow('Legacy ACP settings are read-only') + expect(() => store.set({ enabled: true, manualAgents: [] })).toThrow( + 'Legacy ACP settings are read-only' + ) + expect(store.get('enabled')).toBe(false) }) it('keeps provider keys opaque in the app settings store', () => { @@ -181,6 +191,9 @@ function createAppSettingsTable( getMcpSetting: vi.fn((key: string) => mcpSettings[key]), listAgentSettings: vi.fn(() => agentSettings), getAgentSetting: vi.fn((key: string) => agentSettings[key]), + setAgentSetting: vi.fn((key: string, value: unknown) => { + agentSettings[key] = value + }), getAgentMcpSelections: vi.fn(() => overrides.agentSelections ?? []), listAppSettings: vi.fn(() => appSettings), getAppSetting: vi.fn((key: string) => appSettings[key]) diff --git a/test/main/skill/agentSkillImportService.test.ts b/test/main/skill/agentSkillImportService.test.ts index c2a8dcd633..bb12cb8253 100644 --- a/test/main/skill/agentSkillImportService.test.ts +++ b/test/main/skill/agentSkillImportService.test.ts @@ -4,7 +4,7 @@ import path from 'node:path' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' import { SKILL_NAME_MAX_LENGTH, type SkillInstallOptions } from '@shared/types/skill' import type { UnifiedSkillItem } from '@shared/types/skillManagement' -import type { CanonicalSkill, ScanResult } from '@shared/types/skillSync' +import type { CanonicalSkill, ImportPreview, ScanResult } from '@shared/types/skillSync' import { AgentSkillImportService, type AgentSkillImportServiceDependencies @@ -16,14 +16,6 @@ vi.unmock('node:fs') vi.unmock('fs/promises') vi.unmock('node:fs/promises') -const electronState = vi.hoisted(() => ({ tempPath: '/tmp' })) - -vi.mock('electron', () => ({ - app: { - getPath: vi.fn(() => electronState.tempPath) - } -})) - const createExternalScan = (skills: ScanResult['skills']): ScanResult => ({ toolId: 'codex', toolName: 'Codex', @@ -42,8 +34,10 @@ describe('AgentSkillImportService', () => { let tempRoot: string let allSkills: UnifiedSkillItem[] let scans: ScanResult[] - let externalPreviews: Array<{ skill: CanonicalSkill; warnings: string[] }> - let installImportedSkill: ReturnType + let externalPreviews: ImportPreview[] + let installImportedSkill: ReturnType< + typeof vi.fn + > let dependencies: AgentSkillImportServiceDependencies let service: AgentSkillImportService @@ -60,6 +54,7 @@ describe('AgentSkillImportService', () => { 'utf-8' ) allSkills.push({ + agentId: 'deepchat', name: skill.name, description: skill.description, path: path.join(skillRoot, 'SKILL.md'), @@ -87,20 +82,29 @@ describe('AgentSkillImportService', () => { })) ) ] - externalPreviews = skills.map((skill) => ({ skill, warnings: [] })) + externalPreviews = skills.map((skill) => ({ + skill, + warnings: [], + source: { + name: skill.name, + path: `/external/skills/${skill.name}`, + format: 'codex', + lastModified: new Date() + } + })) } beforeEach(async () => { tempRoot = await mkdtemp(path.join(os.tmpdir(), 'deepchat-agent-import-test-')) - electronState.tempPath = tempRoot await mkdir(path.join(tempRoot, 'allSkills'), { recursive: true }) allSkills = [] scans = [] externalPreviews = [] installImportedSkill = vi - .fn() + .fn() .mockResolvedValue({ success: true, skillName: 'installed-skill' }) dependencies = { + tempDirectory: tempRoot, skills: { getAllSkills: vi.fn(async () => allSkills), installImportedSkill diff --git a/test/main/skill/settings.test.ts b/test/main/skill/settings.test.ts index e042f8a473..a927b1c441 100644 --- a/test/main/skill/settings.test.ts +++ b/test/main/skill/settings.test.ts @@ -1,5 +1,4 @@ import { beforeEach, describe, expect, it, vi } from 'vitest' -import { app } from 'electron' import { SkillSettings } from '@/skill/settings' describe('SkillSettings', () => { @@ -12,11 +11,10 @@ describe('SkillSettings', () => { beforeEach(() => { values.clear() vi.clearAllMocks() - vi.mocked(app.getPath).mockReturnValue('/home/tester') }) it('returns the Skill defaults', () => { - const settings = new SkillSettings(store as never) + const settings = new SkillSettings(store as never, '/home/tester') expect(settings.isEnabled()).toBe(true) expect(settings.isDraftSuggestionsEnabled()).toBe(false) @@ -24,10 +22,11 @@ describe('SkillSettings', () => { }) it('reads and writes Skill-owned settings', () => { - const settings = new SkillSettings(store as never) + const settings = new SkillSettings(store as never, '/home/tester') const managementState = { - version: 2 as const, - agents: { deepchat: { skills: {} } } + version: 3 as const, + skills: {}, + agents: { deepchat: { bindings: {} } } } const scanCache = { timestamp: '2026-07-16T00:00:00.000Z', tools: [] } @@ -41,7 +40,7 @@ describe('SkillSettings', () => { }) it('freezes legacy Agent migration targets once while preserving v1 state', () => { - const settings = new SkillSettings(store as never) + const settings = new SkillSettings(store as never, '/home/tester') values.set('skills.managementState', { version: 1, skills: {}, diff --git a/test/main/skill/skillExecutionService.test.ts b/test/main/skill/skillExecutionService.test.ts index 5a379d4d59..4ae7947d25 100644 --- a/test/main/skill/skillExecutionService.test.ts +++ b/test/main/skill/skillExecutionService.test.ts @@ -42,7 +42,7 @@ vi.mock('@/skill/skillExecutionPackageTree', async (importOriginal) => { }) import { spawn } from 'child_process' -import { backgroundExecSessionManager } from '@/agent/shared/process/backgroundExecSessionManager' +import { createBackgroundExecFixture } from '../tool/agentTools/agentToolDependencies' import { terminateProcessTree } from '@/agent/shared/process/processTree' import { materializeSkillExecutionPackageTree } from '@/skill/skillExecutionPackageTree' @@ -53,6 +53,8 @@ class MockWritableStream extends EventEmitter { } describe('SkillExecutionService', () => { + let backgroundExecSessionManager: ReturnType + let service: SkillExecutionService let resolveConversationWorkdir: ReturnType let authority: ResolvedSkillExecutionAuthority @@ -61,6 +63,7 @@ describe('SkillExecutionService', () => { const originalPlatform = Object.getOwnPropertyDescriptor(process, 'platform') beforeEach(() => { + backgroundExecSessionManager = createBackgroundExecFixture() vi.clearAllMocks() vi.mocked(spawn).mockReset() vi.mocked(terminateProcessTree).mockReset() @@ -148,6 +151,7 @@ describe('SkillExecutionService', () => { resolveConversationWorkdir = vi.fn().mockResolvedValue('/workspace/session') service = new SkillExecutionService({ + backgroundExec: backgroundExecSessionManager, resolveConversationWorkdir }) }) diff --git a/test/main/skill/skillService.test.ts b/test/main/skill/skillService.test.ts index e716733dc6..80b3f929d4 100644 --- a/test/main/skill/skillService.test.ts +++ b/test/main/skill/skillService.test.ts @@ -1,7 +1,6 @@ import { describe, it, expect, beforeEach, vi, Mock, afterEach } from 'vitest' import type { SkillSettingsPort } from '@/skill/settings' import type { SkillMetadata } from '../../../src/shared/types/skill' -import { app } from 'electron' const DEFAULT_SKILLS_DIR = '/mock/home/.deepchat/skills' @@ -32,21 +31,6 @@ const skillArchiveMock = vi.hoisted(() => ({ })) // Mock external dependencies -vi.mock('electron', () => ({ - app: { - getPath: vi.fn().mockImplementation((name: string) => { - if (name === 'home') return '/mock/home' - if (name === 'temp') return '/mock/temp' - return '/mock/' + name - }), - getAppPath: vi.fn().mockReturnValue('/mock/app'), - isPackaged: false - }, - shell: { - openPath: vi.fn().mockResolvedValue('') - } -})) - vi.mock('fs', () => { const fsMock = { existsSync: vi.fn(), @@ -198,7 +182,7 @@ import matter from 'gray-matter' import { execFile } from 'node:child_process' import { randomUUID } from 'node:crypto' import logger from '@shared/logger' -import { SKILL_CONFIG, SkillService } from '../../../src/main/skill/index' +import { SKILL_CONFIG, SkillService, type SkillHostPort } from '../../../src/main/skill/index' import type { IFileWatcherService, WatchBatchListener, @@ -335,6 +319,7 @@ function createFakeWatcherService() { describe('SkillService', () => { let skillService: SkillService + let skillHost: SkillHostPort let mockProviderSettings: SkillSettingsPort let fakeWatcherService: ReturnType let configSettings: Map @@ -345,6 +330,13 @@ describe('SkillService', () => { } beforeEach(() => { + skillHost = { + homeDirectory: '/mock/home', + tempDirectory: '/mock/temp', + userDataDirectory: '/mock/userData', + builtinSkillsDirectories: ['/mock/app/resources/skills'], + openSkillsDirectory: vi.fn(async () => undefined) + } vi.clearAllMocks() ;(fs.renameSync as Mock).mockReset() ;(fs.copyFileSync as Mock).mockReset() @@ -444,7 +436,8 @@ describe('SkillService', () => { mockProviderSettings, skillSessionStatePort as any, fakeWatcherService.service, - publishDeepchatEventMock + publishDeepchatEventMock, + skillHost ) ;(skillService as any).skillsDir = DEFAULT_SKILLS_DIR ;(skillService as any).sidecarDir = `${DEFAULT_SKILLS_DIR}/.deepchat-meta` @@ -466,7 +459,8 @@ describe('SkillService', () => { mockProviderSettings, skillSessionStatePort as any, fakeWatcherService.service, - publishDeepchatEventMock + publishDeepchatEventMock, + skillHost ) expect(mockProviderSettings.getPath).toHaveBeenCalled() await expect(presenter.getSkillsDir()).resolves.toBe('/custom/skills/path') @@ -480,7 +474,8 @@ describe('SkillService', () => { mockProviderSettings, skillSessionStatePort as any, fakeWatcherService.service, - publishDeepchatEventMock + publishDeepchatEventMock, + skillHost ) expect(fs.mkdirSync).toHaveBeenCalledWith(expect.any(String), { recursive: true }) presenter.destroy() @@ -494,7 +489,8 @@ describe('SkillService', () => { mockProviderSettings, skillSessionStatePort as any, fakeWatcherService.service, - publishDeepchatEventMock + publishDeepchatEventMock, + skillHost ) expect(fs.mkdirSync).toHaveBeenCalledWith(expect.not.stringContaining('.deepchat-meta'), { @@ -508,17 +504,13 @@ describe('SkillService', () => { it('should repair malformed .deepchat path segments', async () => { ;(mockProviderSettings.getPath as Mock).mockReturnValue('/mock/home.deepchat/skills') - ;(app.getPath as Mock).mockImplementation((name: string) => { - if (name === 'home') return '/mock/home' - if (name === 'temp') return '/mock/temp' - return '/mock/' + name - }) const presenter = new SkillService( mockProviderSettings, skillSessionStatePort as any, fakeWatcherService.service, - publishDeepchatEventMock + publishDeepchatEventMock, + skillHost ) await expect(presenter.getSkillsDir()).resolves.toBe('/mock/home/.deepchat/skills') presenter.destroy() @@ -526,17 +518,13 @@ describe('SkillService', () => { it('should repair stale POSIX default skills paths from another user profile', async () => { ;(mockProviderSettings.getPath as Mock).mockReturnValue('/Users/legacy-user/.deepchat/skills') - ;(app.getPath as Mock).mockImplementation((name: string) => { - if (name === 'home') return '/mock/home' - if (name === 'temp') return '/mock/temp' - return '/mock/' + name - }) const presenter = new SkillService( mockProviderSettings, skillSessionStatePort as any, fakeWatcherService.service, - publishDeepchatEventMock + publishDeepchatEventMock, + skillHost ) await expect(presenter.getSkillsDir()).resolves.toBe('/mock/home/.deepchat/skills') presenter.destroy() @@ -546,17 +534,13 @@ describe('SkillService', () => { ;(mockProviderSettings.getPath as Mock).mockReturnValue( 'C:\\Users\\legacy-user\\.deepchat\\skills\\nested' ) - ;(app.getPath as Mock).mockImplementation((name: string) => { - if (name === 'home') return '/mock/home' - if (name === 'temp') return '/mock/temp' - return '/mock/' + name - }) const presenter = new SkillService( mockProviderSettings, skillSessionStatePort as any, fakeWatcherService.service, - publishDeepchatEventMock + publishDeepchatEventMock, + skillHost ) await expect(presenter.getSkillsDir()).resolves.toBe('/mock/home/.deepchat/skills/nested') presenter.destroy() @@ -571,6 +555,48 @@ describe('SkillService', () => { }) }) + it.each(['resolve', 'reject'] as const)( + 'drains a pending native directory %s before shutdown', + async (outcome) => { + let resolve!: () => void + let reject!: (error: Error) => void + const native = new Promise((complete, fail) => { + resolve = complete + reject = fail + }) + vi.mocked(skillHost.openSkillsDirectory).mockReturnValueOnce(native) + const opening = skillService.openSkillsFolderForAgent('deepchat') + const checked = + outcome === 'reject' + ? expect(opening).rejects.toThrow('native unavailable') + : expect(opening).resolves.toBeUndefined() + await vi.waitFor(() => + expect(skillHost.openSkillsDirectory).toHaveBeenCalledWith(DEFAULT_SKILLS_DIR) + ) + + let stopped = false + const shutdown = skillService.destroy().then(() => { + stopped = true + }) + await Promise.resolve() + expect(stopped).toBe(false) + await expect(skillService.openSkillsFolderForAgent('deepchat')).rejects.toThrow( + 'shutting down' + ) + expect(skillHost.openSkillsDirectory).toHaveBeenCalledOnce() + if (outcome === 'reject') reject(new Error('native unavailable')) + else resolve() + await checked + await shutdown + expect(stopped).toBe(true) + } + ) + + it('rejects an unsafe Agent scope before opening a native directory', async () => { + await expect(skillService.openSkillsFolderForAgent('../escape')).rejects.toThrow() + expect(skillHost.openSkillsDirectory).not.toHaveBeenCalled() + }) + describe('initialize', () => { it('keeps migration authoritative and retries startup after a migration failure', async () => { const error = new Error('Symbolic links are not allowed in Skill snapshots') @@ -1102,7 +1128,8 @@ describe('SkillService', () => { mockProviderSettings, skillSessionStatePort as any, fakeWatcherService.service, - publishDeepchatEventMock + publishDeepchatEventMock, + skillHost ) ;(rehydratedPresenter as any).skillsDir = DEFAULT_SKILLS_DIR ;(rehydratedPresenter as any).sidecarDir = `${DEFAULT_SKILLS_DIR}/.deepchat-meta` @@ -3577,7 +3604,8 @@ describe('SkillService', () => { mockProviderSettings, skillSessionStatePort as any, fakeWatcherService.service, - publishDeepchatEventMock + publishDeepchatEventMock, + skillHost ) ;(rehydratedPresenter as any).skillsDir = DEFAULT_SKILLS_DIR ;(rehydratedPresenter as any).sidecarDir = `${DEFAULT_SKILLS_DIR}/.deepchat-meta` diff --git a/test/main/skill/skillServiceSharedSkills.test.ts b/test/main/skill/skillServiceSharedSkills.test.ts index 3aa3c511e0..0800ce124c 100644 --- a/test/main/skill/skillServiceSharedSkills.test.ts +++ b/test/main/skill/skillServiceSharedSkills.test.ts @@ -1,7 +1,6 @@ import fs from 'node:fs' import os from 'node:os' import path from 'node:path' -import { app } from 'electron' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' import { SkillService, type SkillAgentScopePort } from '@/skill' import type { SkillSettingsPort } from '@/skill/settings' @@ -37,8 +36,6 @@ describe('SkillService shared Skills', () => { let activeSkills: Map let stateWriteFailuresRemaining: number let service: SkillService - let getPathSpy: ReturnType - let getAppPathSpy: ReturnType const watcherService = { watch: vi.fn(async () => ({ close: vi.fn(async () => undefined) })), @@ -100,7 +97,20 @@ describe('SkillService shared Skills', () => { sessions.find((session) => session.id === sessionId)?.projectDir ?? null, listSessions: async () => structuredClone(sessions) } - return new SkillService(settings, sessionState, watcherService, vi.fn(), agentScope) + return new SkillService( + settings, + sessionState, + watcherService, + vi.fn(), + { + homeDirectory: temporaryRoot, + tempDirectory: path.join(temporaryRoot, 'temp'), + userDataDirectory: temporaryRoot, + builtinSkillsDirectories: [path.join(temporaryRoot, 'resources', 'skills')], + openSkillsDirectory: vi.fn(async () => undefined) + }, + agentScope + ) } const migrate = async () => { @@ -116,19 +126,11 @@ describe('SkillService shared Skills', () => { sessions = [] activeSkills = new Map() stateWriteFailuresRemaining = 0 - getPathSpy = vi.spyOn(app, 'getPath').mockImplementation((name: string) => { - if (name === 'home') return temporaryRoot - if (name === 'temp') return path.join(temporaryRoot, 'temp') - return temporaryRoot - }) - getAppPathSpy = vi.spyOn(app, 'getAppPath').mockReturnValue(temporaryRoot) service = createService() }) afterEach(async () => { await service.destroy() - getPathSpy.mockRestore() - getAppPathSpy.mockRestore() fs.rmSync(temporaryRoot, { recursive: true, force: true }) vi.restoreAllMocks() }) diff --git a/test/main/sync/backupConsistency.test.ts b/test/main/sync/backupConsistency.test.ts index a88fd82b0b..a14fb5be56 100644 --- a/test/main/sync/backupConsistency.test.ts +++ b/test/main/sync/backupConsistency.test.ts @@ -23,7 +23,6 @@ Object.assign(fsModule, realFs) ).promises const fs = realFs -const { app } = await import('electron') const { SyncService } = await import('../../../src/main/sync') let userDataDir: string @@ -32,17 +31,12 @@ let dbPath: string let writerDb: Database.Database | null = null let sidecarConnections: Database.Database[] = [] let postCheckpointImages: Buffer[] = [] -let getPathSpy: ReturnType beforeEach(() => { userDataDir = fs.mkdtempSync(path.join(osActual.tmpdir(), 'deepchat-user-')) syncDir = fs.mkdtempSync(path.join(osActual.tmpdir(), 'deepchat-sync-')) dbPath = path.join(userDataDir, 'app_db', 'agent.db') postCheckpointImages = [] - getPathSpy = vi.spyOn(app, 'getPath').mockImplementation((type: string) => { - if (type === 'userData') return userDataDir - return osActual.tmpdir() - }) fs.mkdirSync(path.dirname(dbPath), { recursive: true }) fs.writeFileSync(path.join(userDataDir, 'app-settings.json'), JSON.stringify({ theme: 'dark' })) }) @@ -57,7 +51,6 @@ afterEach(() => { sidecarConnections = [] writerDb?.close() writerDb = null - getPathSpy.mockRestore() fs.rmSync(userDataDir, { recursive: true, force: true }) fs.rmSync(syncDir, { recursive: true, force: true }) }) @@ -115,7 +108,12 @@ function buildService( } } as never, {} as never, - vi.fn() as never + vi.fn() as never, + { + profileDirectory: userDataDir, + temporaryDirectory: osActual.tmpdir(), + openPath: async () => '' + } ) } diff --git a/test/main/sync/peer.test.ts b/test/main/sync/peer.test.ts index 698d4d5793..4cc6d1d142 100644 --- a/test/main/sync/peer.test.ts +++ b/test/main/sync/peer.test.ts @@ -9,7 +9,7 @@ vi.unmock('fs') vi.unmock('node:fs') import { SyncHostService } from '@/sync/host' -import { SyncPeerService } from '@/sync/peer' +import { SyncPeerService, type SyncPeerDeps } from '@/sync/peer' import { createSyncPeerRoutes } from '@/sync/peer/routes' import { SyncHostSnapshotSource } from '@/sync/host/snapshot' import { SYNC_HOST_PREPARE_PATH } from '@shared/contracts/syncHost' @@ -29,18 +29,20 @@ describe('tunnel sync transfer boundary', () => { const key = randomBytes(32) const replicaId = randomUUID() - function createPeer(): SyncPeerService { + function createPeer( + crypto: Partial> = {} + ): SyncPeerService { return new SyncPeerService({ directory: path.join(root, 'peer'), replicaId: () => replicaId, fetch: (input, init) => requestFetch(input, init), - protectToken: (token) => { + protectToken: async (token) => { const iv = randomBytes(12) const cipher = createCipheriv('aes-256-gcm', key, iv) const ciphertext = Buffer.concat([cipher.update(token, 'utf8'), cipher.final()]) return Buffer.concat([iv, cipher.getAuthTag(), ciphertext]).toString('base64') }, - revealToken: (wrapped) => { + revealToken: async (wrapped) => { const data = Buffer.from(wrapped, 'base64') const cipher = createDecipheriv('aes-256-gcm', key, data.subarray(0, 12)) cipher.setAuthTag(data.subarray(12, 28)) @@ -50,7 +52,8 @@ describe('tunnel sync transfer boundary', () => { importSnapshot: async (file) => { imported.push(await readFile(file)) return { success: true, message: 'sync.success.importComplete' } - } + }, + ...crypto }) } @@ -123,6 +126,145 @@ describe('tunnel sync transfer boundary', () => { await rm(root, { recursive: true, force: true }) }) + it('rejects immediate pairing cancellation before requesting native crypto', async () => { + const protectToken = vi.fn(async () => 'opaque') + peer = createPeer({ protectToken }) + const code = host.createPairingCode()! + const pairing = peer.pair({ + hostUrl: 'https://sync.example.test', + hostId: code.hostId, + code: code.code, + deviceName: 'Device' + }) + const rejected = expect(pairing).rejects.toThrow() + await peer.cancel() + await rejected + expect(protectToken).not.toHaveBeenCalled() + expect(host.getPairingCode()?.code).toBe(code.code) + }) + + it.each(['availability', 'token'] as const)( + 'preserves saved pairing when cancelled during %s wrapping', + async (stage) => { + await pair() + const before = await readFile(path.join(root, 'peer', 'pairing.json'), 'utf8') + await peer.stop() + const entered = Promise.withResolvers() + const pending = Promise.withResolvers() + peer = createPeer({ + protectToken: async (value) => { + if (stage === 'availability' || value !== 'availability-check') { + entered.resolve() + return await pending.promise + } + return 'wrapped-check' + } + }) + requests = [] + const code = host.createPairingCode()! + const pairing = peer.pair({ + hostUrl: 'https://sync.example.test', + hostId: code.hostId, + code: code.code, + deviceName: 'Replacement' + }) + const rejected = expect(pairing).rejects.toThrow() + await entered.promise + const stopping = stage === 'token' ? peer.stop() : undefined + if (stage === 'availability') await peer.cancel() + pending.resolve('wrapped-replacement') + await rejected + await stopping + expect(await readFile(path.join(root, 'peer', 'pairing.json'), 'utf8')).toBe(before) + if (stage === 'availability') { + expect(requests).toEqual([]) + expect(host.getPairingCode()?.code).toBe(code.code) + } + } + ) + + it.each(['forget', 'stop', 'pair'] as const)( + 'does not release a pending connection credential after %s', + async (operation) => { + const port = Number(new URL(hostOrigin).port) + await host.setEnabled(true, { port, consent: true, bidirectional: true }) + const code = host.createPairingCode()! + await peer.pair({ + hostUrl: 'https://sync.example.test', + hostId: code.hostId, + code: code.code, + deviceName: 'Device', + bidirectional: true + }) + await peer.stop() + const entered = Promise.withResolvers() + const pending = Promise.withResolvers() + peer = createPeer({ + revealToken: async () => { + entered.resolve() + return await pending.promise + } + }) + const reading = peer.connection() + const rejected = expect(reading).rejects.toThrow('credentialsUnavailable') + await entered.promise + if (operation === 'pair') { + const code = host.createPairingCode()! + await peer.pair({ + hostUrl: 'https://sync.example.test', + hostId: code.hostId, + code: code.code, + deviceName: 'Replacement' + }) + } else await peer[operation]() + pending.resolve('stale-credential') + await rejected + } + ) + + it('does not consume a pairing code when native token protection is unavailable', async () => { + peer = createPeer({ + protectToken: async () => { + throw new Error('sync.error.safeStorageUnavailable') + } + }) + const code = host.createPairingCode()! + await expect( + peer.pair({ + hostUrl: 'https://sync.example.test', + hostId: code.hostId, + code: code.code, + deviceName: 'Device' + }) + ).rejects.toThrow('safeStorageUnavailable') + expect(requests).toEqual([]) + expect(host.getPairingCode()?.code).toBe(code.code) + expect((await peer.getStatus()).paired).toBe(false) + }) + + it('does not authenticate or import after cancelling a pending transfer unwrap', async () => { + await publish() + await pair() + await peer.stop() + const entered = Promise.withResolvers() + const pending = Promise.withResolvers() + peer = createPeer({ + revealToken: async () => { + entered.resolve() + return await pending.promise + } + }) + requests = [] + await peer.pull('increment') + await entered.promise + await peer.cancel() + pending.resolve('cancelled-credential') + await settled() + expect(requests.every((request) => request.path.endsWith('/handshake'))).toBe(true) + expect(imported).toEqual([]) + expect((await peer.getStatus()).phase).toBe('cancelled') + }) + it('rejects non-renderer callers on every peer control route', async () => { const context = { caller: { diff --git a/test/main/sync/settings.test.ts b/test/main/sync/settings.test.ts index 6b7c25d100..fce932efdc 100644 --- a/test/main/sync/settings.test.ts +++ b/test/main/sync/settings.test.ts @@ -2,11 +2,8 @@ import { beforeEach, describe, expect, it, vi } from 'vitest' const publishDeepchatEvent = vi.hoisted(() => vi.fn()) -vi.mock('electron', () => ({ - app: { getPath: vi.fn(() => '/home/test') } -})) - import { SyncSettings } from '@/sync/settings' +import { SecretStore } from '@/config/secretStore' function createSettings(initial: Record = {}) { const values = new Map(Object.entries(initial)) @@ -21,10 +18,28 @@ function createSettings(initial: Record = {}) { describe('SyncSettings', () => { beforeEach(() => vi.clearAllMocks()) + it('uses the supplied home root until an explicit sync folder is selected', () => { + const settings = createSettings() + const service = new SyncSettings( + settings as never, + {} as never, + publishDeepchatEvent, + '/home/test' + ) + expect(service.getFolderPath()).toMatch(/[/\\]home[/\\]test[/\\]DeepchatSync$/) + service.setFolderPath('/selected/sync') + expect(service.getFolderPath()).toBe('/selected/sync') + }) + it('owns local sync settings and publishes their current state', () => { const settings = createSettings({ syncFolderPath: '/sync' }) const secrets = { get: vi.fn(() => ''), isAvailable: vi.fn(() => true) } - const syncSettings = new SyncSettings(settings as never, secrets as never, publishDeepchatEvent) + const syncSettings = new SyncSettings( + settings as never, + secrets as never, + publishDeepchatEvent, + '/home/test' + ) syncSettings.setEnabled(true) @@ -36,24 +51,108 @@ describe('SyncSettings', () => { }) }) - it('restores the previous wrapped secret when writing cloud settings fails', () => { + it('restores the previous wrapped secret when writing cloud settings fails', async () => { const settings = createSettings() settings.set.mockImplementationOnce(() => { throw new Error('write failed') }) const secrets = { + captureOperation: () => () => {}, getWrapped: vi.fn(() => 'old-wrapped'), - wrap: vi.fn(() => 'new-wrapped'), + wrap: vi.fn(async () => 'new-wrapped'), setWrapped: vi.fn(), restoreWrapped: vi.fn(), get: vi.fn(() => 'secret'), isAvailable: vi.fn(() => true) } - const syncSettings = new SyncSettings(settings as never, secrets as never, publishDeepchatEvent) + const syncSettings = new SyncSettings( + settings as never, + secrets as never, + publishDeepchatEvent, + '/home/test' + ) - expect(() => syncSettings.setCloudConfig({ secretAccessKey: 'new-secret' })).toThrow( + await expect(syncSettings.setCloudConfig({ secretAccessKey: 'new-secret' })).rejects.toThrow( 'write failed' ) expect(secrets.restoreWrapped).toHaveBeenCalledWith('cloudSyncSecret', 'old-wrapped') }) + + it('serializes cloud updates across native encryption without losing unrelated fields', async () => { + const settings = createSettings() + const pending = Promise.withResolvers() + const encrypt = vi.fn(() => pending.promise) + const secrets = new SecretStore(settings as never, { + isAvailable: async () => true, + encrypt, + decrypt: async (wrapped) => wrapped.slice('wrapped:'.length) + }) + const service = new SyncSettings(settings as never, secrets, publishDeepchatEvent, '/home/test') + const first = service.setCloudConfig({ + endpoint: 'https://cloud.example', + accessKeyId: 'access', + secretAccessKey: 'key' + }) + const second = service.setCloudConfig({ bucket: 'backups' }) + await vi.waitFor(() => expect(encrypt).toHaveBeenCalledOnce()) + expect(settings.values.size).toBe(0) + pending.resolve('wrapped:key') + await first + expect(await second).toMatchObject({ + endpoint: 'https://cloud.example', + bucket: 'backups', + hasSecret: true + }) + expect(await service.getResolvedCloudConfig()).toMatchObject({ + endpoint: 'https://cloud.example', + bucket: 'backups', + accessKeyId: 'access', + secretAccessKey: 'key' + }) + expect(settings.values.get('cloudSyncSecret')).toBe('wrapped:key') + }) + + it('fences both pending and queued cloud writes when maintenance changes the profile', async () => { + const settings = createSettings() + const pending = Promise.withResolvers() + const encrypt = vi.fn(() => pending.promise) + const secrets = new SecretStore(settings as never, { + isAvailable: async () => true, + encrypt, + decrypt: async () => '' + }) + const service = new SyncSettings(settings as never, secrets, publishDeepchatEvent, '/home/test') + const first = expect(service.setCloudConfig({ secretAccessKey: 'key' })).rejects.toThrow( + 'interrupted' + ) + const second = expect(service.setCloudConfig({ bucket: 'stale' })).rejects.toThrow( + 'interrupted' + ) + await vi.waitFor(() => expect(encrypt).toHaveBeenCalledOnce()) + secrets.pause() + secrets.resume() + pending.resolve('wrapped:key') + await Promise.all([first, second]) + expect(settings.values.size).toBe(0) + expect(await service.setCloudConfig({ bucket: 'current' })).toMatchObject({ bucket: 'current' }) + }) + + it('rechecks the owner immediately before publishing an already wrapped secret', async () => { + const settings = createSettings() + const secrets = new SecretStore(settings as never, { + isAvailable: async () => true, + encrypt: async () => 'wrapped:key', + decrypt: async () => '' + }) + const wrap = secrets.wrap.bind(secrets) + vi.spyOn(secrets, 'wrap').mockImplementation(async (value) => { + const wrapped = await wrap(value) + secrets.pause() + secrets.resume() + return wrapped + }) + const service = new SyncSettings(settings as never, secrets, publishDeepchatEvent, '/home/test') + await expect(service.setCloudConfig({ secretAccessKey: 'key' })).rejects.toThrow('interrupted') + expect(settings.values.size).toBe(0) + }) }) diff --git a/test/main/sync/syncService.test.ts b/test/main/sync/syncService.test.ts index 6a55f127db..27371fd781 100644 --- a/test/main/sync/syncService.test.ts +++ b/test/main/sync/syncService.test.ts @@ -313,7 +313,6 @@ Object.assign(fsMock, realFs) const fs = realFs const path = await vi.importActual('path') -const { app } = await import('electron') const { SyncService } = await import('../../../src/main/sync') const { ImportMode } = await import('../../../src/main/sync') const publishDeepchatEvent = vi.fn() @@ -348,7 +347,7 @@ describe('SyncService backup import', () => { importLegacyChatDb: ReturnType } let dbPragma: ReturnType - let getPathSpy: ReturnType + let openPath: ReturnType beforeEach(() => { configImportMocks.importLegacyConfig.mockClear() @@ -364,15 +363,7 @@ describe('SyncService backup import', () => { tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'deepchat-temp-')) syncDir = fs.mkdtempSync(path.join(os.tmpdir(), 'deepchat-sync-')) - getPathSpy = vi.spyOn(app, 'getPath').mockImplementation((type: string) => { - if (type === 'userData') { - return userDataDir - } - if (type === 'temp') { - return tempDir - } - return os.tmpdir() - }) + openPath = vi.fn(async () => '') dbPragma = vi.fn() sqlitePresenter = { @@ -435,7 +426,8 @@ describe('SyncService backup import', () => { return { listProviders: vi.fn(() => []) } } } as never, - publishDeepchatEvent + publishDeepchatEvent, + { profileDirectory: userDataDir, temporaryDirectory: tempDir, openPath } ) }) @@ -445,12 +437,34 @@ describe('SyncService backup import', () => { ) => service.importFromSync(backupFileName, importMode, importDatabase) afterEach(() => { - getPathSpy.mockRestore() removeDir(syncDir) removeDir(tempDir) removeDir(userDataDir) }) + it('waits for the selected native folder operation before acknowledging success', async () => { + const native = Promise.withResolvers() + openPath.mockReturnValueOnce(native.promise) + removeDir(syncDir) + let completed = false + const opening = service.openSyncFolder().then(() => { + completed = true + }) + await vi.waitFor(() => expect(openPath).toHaveBeenCalledWith(syncDir)) + expect(fs.existsSync(syncDir)).toBe(true) + expect(completed).toBe(false) + native.resolve('') + await opening + expect(completed).toBe(true) + }) + + it('propagates native folder launch failures', async () => { + openPath.mockResolvedValueOnce('folder permission denied') + await expect(service.openSyncFolder()).rejects.toThrow('folder permission denied') + openPath.mockRejectedValueOnce(new Error('native host unavailable')) + await expect(service.openSyncFolder()).rejects.toThrow('native host unavailable') + }) + it('backs up migrated config through agent.db and keeps app-settings lightweight', async () => { createLocalState(userDataDir, { conversations: [{ id: 'conv-1', title: 'Local conversation' }], diff --git a/test/main/sync/tunnel.test.ts b/test/main/sync/tunnel.test.ts index a5acb236c2..bb216481aa 100644 --- a/test/main/sync/tunnel.test.ts +++ b/test/main/sync/tunnel.test.ts @@ -1,6 +1,7 @@ import { mkdtemp, mkdir, writeFile, readFile, rm } from 'node:fs/promises' import path from 'node:path' import os from 'node:os' +import { createServer } from 'node:net' import { afterEach, describe, expect, it, vi } from 'vitest' vi.unmock('fs') @@ -12,7 +13,9 @@ import { SyncHostService } from '@/sync/host' const roots: string[] = [] const tunnels: SyncTunnel[] = [] +const hosts: SyncHostService[] = [] afterEach(async () => { + for (const host of hosts.splice(0)) await host.stop() for (const tunnel of tunnels.splice(0)) await tunnel.stop() for (const root of roots.splice(0)) await rm(root, { recursive: true, force: true }) }) @@ -22,7 +25,130 @@ async function profile() { return root } +async function availablePort() { + const probe = createServer() + await new Promise((resolve) => probe.listen(0, '127.0.0.1', resolve)) + const port = (probe.address() as { port: number }).port + await new Promise((resolve, reject) => + probe.close((error) => (error ? reject(error) : resolve())) + ) + return port +} + describe('sync connector lifecycle', () => { + it.each(['protect', 'reveal'] as const)( + 'does not launch or enable after stop supersedes a pending %s', + async (operation) => { + const root = await profile() + const entered = Promise.withResolvers() + const pending = Promise.withResolvers() + const resolveBinary = vi.fn(() => '/unreachable-connector') + const blocked = async () => { + entered.resolve() + return await pending.promise + } + const host = new SyncHostService({ + getUserDataPath: () => root, + getFolderPath: () => root, + getAppVersion: () => 'test', + createBackup: async () => null, + resolveCloudflared: resolveBinary, + protectToken: operation === 'protect' ? blocked : async () => 'opaque-token', + revealToken: operation === 'reveal' ? blocked : async () => 'fixture-token' + }) + hosts.push(host) + const enabling = host.setEnabled(true, { + port: await availablePort(), + consent: true, + tunnel: { mode: 'named', publicUrl: 'https://sync.example.test', token: 'fixture-token' } + }) + const rejected = expect(enabling).rejects.toThrow('busy') + await entered.promise + const stopping = host.stop() + pending.resolve('opaque-result') + await rejected + await stopping + expect(resolveBinary).not.toHaveBeenCalled() + expect(await host.getStatus()).toMatchObject({ enabled: false, running: false }) + } + ) + + it('does not persist a token when native protection fails', async () => { + const root = await profile() + const host = new SyncHostService({ + getUserDataPath: () => root, + getFolderPath: () => root, + getAppVersion: () => 'test', + createBackup: async () => null, + protectToken: async () => { + throw new Error('sync.error.safeStorageUnavailable') + } + }) + hosts.push(host) + await expect( + host.setEnabled(true, { + port: await availablePort(), + consent: true, + tunnel: { mode: 'named', publicUrl: 'https://sync.example.test', token: 'fixture-token' } + }) + ).rejects.toThrow('safeStorageUnavailable') + expect(await host.getStatus()).toMatchObject({ enabled: false, running: false }) + const state = await readFile(path.join(root, 'sync-host', 'host-state.json'), 'utf8') + expect(JSON.parse(state).wrappedTunnelToken).toBeNull() + expect(state).not.toContain('fixture-token') + }) + + it.skipIf(process.platform === 'win32')( + 'starts and restarts a named connector through async token ports', + async () => { + const root = await profile() + const executable = path.join(root, 'cloudflared') + const observedPath = path.join(root, 'observed.json') + await writeFile( + executable, + `#!${process.execPath}\n` + + `require('fs').writeFileSync(${JSON.stringify(observedPath)}, JSON.stringify({pid:process.pid,token:process.env.TUNNEL_TOKEN}));\n` + + `console.error('Registered tunnel connection connIndex=0'); setInterval(() => {}, 1000)\n`, + { mode: 0o755 } + ) + const revealToken = vi.fn(async (wrapped: string) => + Buffer.from(wrapped, 'base64').toString('utf8') + ) + const host = new SyncHostService({ + getUserDataPath: () => root, + getFolderPath: () => root, + getAppVersion: () => 'test', + createBackup: async () => null, + resolveCloudflared: () => executable, + protectToken: async (token) => Buffer.from(token).toString('base64'), + revealToken + }) + hosts.push(host) + await host.setEnabled(true, { + port: await availablePort(), + consent: true, + tunnel: { + mode: 'named', + publicUrl: 'https://sync.example.test', + token: 'synthetic-host-secret' + } + }) + await vi.waitFor(async () => expect((await host.getStatus()).tunnel.phase).toBe('connected')) + const observed = JSON.parse(await readFile(observedPath, 'utf8')) + expect(observed.token).toBe('synthetic-host-secret') + const statePath = path.join(root, 'sync-host', 'host-state.json') + const before = JSON.parse(await readFile(statePath, 'utf8')) + expect(JSON.stringify(before)).not.toContain('synthetic-host-secret') + await host.stop() + expect(() => process.kill(observed.pid, 0)).toThrow() + await host.startIfEnabled() + await vi.waitFor(async () => expect((await host.getStatus()).tunnel.phase).toBe('connected')) + expect(revealToken).toHaveBeenCalledTimes(2) + expect(JSON.parse(await readFile(statePath, 'utf8')).wrappedTunnelToken).toBe( + before.wrappedTunnelToken + ) + } + ) it.skipIf(process.platform === 'win32')( 'keeps the token out of arguments and launch records, and stops its child', async () => { diff --git a/test/main/tool/agentTools/agentBashHandler.test.ts b/test/main/tool/agentTools/agentBashHandler.test.ts index 81c2bb51f9..2a48533725 100644 --- a/test/main/tool/agentTools/agentBashHandler.test.ts +++ b/test/main/tool/agentTools/agentBashHandler.test.ts @@ -3,7 +3,7 @@ import { spawn } from 'child_process' import fs from 'fs' import path from 'path' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' -import { backgroundExecSessionManager } from '@/agent/shared/process/backgroundExecSessionManager' +import { createBackgroundExecFixture } from './agentToolDependencies' import * as shellEnvironment from '@/agent/shared/process/shellEnvHelper' import { AgentBashHandler, @@ -55,10 +55,13 @@ function createProgrammaticCommandEnvironment(order?: string[]): AgentCommandEnv } describe('AgentBashHandler', () => { + let backgroundExecSessionManager: ReturnType + const workspaceRoot = process.cwd() const externalRoot = path.dirname(workspaceRoot) beforeEach(() => { + backgroundExecSessionManager = createBackgroundExecFixture() vi.mocked(fs.existsSync).mockReturnValue(true) vi.mocked(fs.statSync).mockReturnValue({ isDirectory: () => true } as fs.Stats) }) @@ -72,7 +75,11 @@ describe('AgentBashHandler', () => { PATH: '/shell/bin', NPM_CONFIG_REGISTRY: 'https://registry.example.test' }) - const handler = new AgentBashHandler([workspaceRoot], createPermissionService()) + const handler = new AgentBashHandler( + [workspaceRoot], + createPermissionService(), + backgroundExecSessionManager + ) const command = 'find . -type f -name "*.ts" -o -name "*.vue" | grep "^./src"' const runShellProcess = vi .spyOn(handler as never, 'runShellProcess' as never) @@ -113,7 +120,12 @@ describe('AgentBashHandler', () => { prependPath: ['/bundled/cli'] })) } - const handler = new AgentBashHandler([workspaceRoot], permissionService, commandEnvironment) + const handler = new AgentBashHandler( + [workspaceRoot], + permissionService, + backgroundExecSessionManager, + commandEnvironment + ) const prepareCommand = vi.spyOn(handler as never, 'prepareCommand' as never).mockResolvedValue({ command: 'deepchat model invoke --prompt hello', env: { DEEPCHAT_CLI_AGENT_TOKEN: 'scoped-token' } @@ -167,6 +179,7 @@ describe('AgentBashHandler', () => { const handler = new AgentBashHandler( [workspaceRoot], createPermissionService(), + backgroundExecSessionManager, commandEnvironment ) const prepareCommand = vi.spyOn(handler as never, 'prepareCommand' as never).mockResolvedValue({ @@ -223,6 +236,7 @@ describe('AgentBashHandler', () => { const handler = new AgentBashHandler( [workspaceRoot], createPermissionService(), + backgroundExecSessionManager, commandEnvironment ) const command = 'deepchat tool search --query calendar --limit 4' @@ -277,6 +291,7 @@ describe('AgentBashHandler', () => { const handler = new AgentBashHandler( [workspaceRoot], createPermissionService(), + backgroundExecSessionManager, commandEnvironment ) vi.spyOn(handler as never, 'prepareCommand' as never).mockResolvedValue({ @@ -333,7 +348,11 @@ describe('AgentBashHandler', () => { options: { stdin: '{}', programmatic: true } } ])('rejects owned stdin for $name before shell execution', async ({ args, options }) => { - const handler = new AgentBashHandler([workspaceRoot], createPermissionService()) + const handler = new AgentBashHandler( + [workspaceRoot], + createPermissionService(), + backgroundExecSessionManager + ) const prepareCommand = vi.spyOn(handler as never, 'prepareCommand' as never) await expect( @@ -352,6 +371,7 @@ describe('AgentBashHandler', () => { const handler = new AgentBashHandler( [workspaceRoot], new CommandPermissionService(), + backgroundExecSessionManager, commandEnvironment ) @@ -370,7 +390,11 @@ describe('AgentBashHandler', () => { it('executes approved commands in the background', async () => { const originalCommand = 'find . -type f -name "*.ts" -o -name "*.vue"' const order: string[] = [] - const handler = new AgentBashHandler([workspaceRoot], createPermissionService()) + const handler = new AgentBashHandler( + [workspaceRoot], + createPermissionService(), + backgroundExecSessionManager + ) vi.spyOn(handler as never, 'prepareCommand' as never).mockResolvedValue({ command: originalCommand, @@ -421,7 +445,11 @@ describe('AgentBashHandler', () => { it('allows an external cwd when explicitly enabled', async () => { const externalCwd = externalRoot - const handler = new AgentBashHandler([workspaceRoot], createPermissionService()) + const handler = new AgentBashHandler( + [workspaceRoot], + createPermissionService(), + backgroundExecSessionManager + ) vi.spyOn(handler as never, 'prepareCommand' as never).mockResolvedValue({ command: 'pwd', @@ -460,7 +488,11 @@ describe('AgentBashHandler', () => { it('rejects an external cwd when external access is not enabled', async () => { const externalCwd = externalRoot - const handler = new AgentBashHandler([workspaceRoot], createPermissionService()) + const handler = new AgentBashHandler( + [workspaceRoot], + createPermissionService(), + backgroundExecSessionManager + ) const runShellProcess = vi.spyOn(handler as never, 'runShellProcess' as never) await expect( @@ -479,7 +511,11 @@ describe('AgentBashHandler', () => { it('does not commit dispatch when the resolved cwd is unusable', async () => { vi.mocked(fs.existsSync).mockReturnValue(false) - const handler = new AgentBashHandler([workspaceRoot], createPermissionService()) + const handler = new AgentBashHandler( + [workspaceRoot], + createPermissionService(), + backgroundExecSessionManager + ) const beforeExecute = vi.fn() const runShellProcess = vi.spyOn(handler as never, 'runShellProcess' as never) @@ -498,7 +534,11 @@ describe('AgentBashHandler', () => { }) it('commits the prepared command before starting the shell process', async () => { - const handler = new AgentBashHandler([workspaceRoot], createPermissionService()) + const handler = new AgentBashHandler( + [workspaceRoot], + createPermissionService(), + backgroundExecSessionManager + ) const order: string[] = [] vi.spyOn(handler as never, 'prepareCommand' as never).mockResolvedValue({ command: 'find . -name "*.ts"', @@ -528,7 +568,11 @@ describe('AgentBashHandler', () => { }) it('returns a running session when foreground exec exceeds yieldMs', async () => { - const handler = new AgentBashHandler([workspaceRoot], createPermissionService()) + const handler = new AgentBashHandler( + [workspaceRoot], + createPermissionService(), + backgroundExecSessionManager + ) vi.spyOn(handler as never, 'prepareCommand' as never).mockResolvedValue({ command: 'bun run dev caps gpt-4o', @@ -576,6 +620,7 @@ describe('AgentBashHandler', () => { const handler = new AgentBashHandler( [workspaceRoot], createPermissionService(), + backgroundExecSessionManager, commandEnvironment ) vi.spyOn(handler as never, 'prepareCommand' as never).mockResolvedValue({ @@ -631,6 +676,7 @@ describe('AgentBashHandler', () => { const handler = new AgentBashHandler( [workspaceRoot], createPermissionService(), + backgroundExecSessionManager, commandEnvironment ) vi.spyOn(handler as never, 'prepareCommand' as never).mockResolvedValue({ @@ -670,6 +716,7 @@ describe('AgentBashHandler', () => { const handler = new AgentBashHandler( [workspaceRoot], createPermissionService(), + backgroundExecSessionManager, commandEnvironment ) vi.spyOn(handler as never, 'prepareCommand' as never).mockResolvedValue({ @@ -733,6 +780,7 @@ describe('AgentBashHandler', () => { const handler = new AgentBashHandler( [workspaceRoot], createPermissionService(), + backgroundExecSessionManager, createProgrammaticCommandEnvironment() ) vi.spyOn(handler as never, 'prepareCommand' as never).mockResolvedValue({ @@ -766,7 +814,11 @@ describe('AgentBashHandler', () => { }) it('cleans up completed foreground sessions that finish inside the yield window', async () => { - const handler = new AgentBashHandler([workspaceRoot], createPermissionService()) + const handler = new AgentBashHandler( + [workspaceRoot], + createPermissionService(), + backgroundExecSessionManager + ) vi.spyOn(handler as never, 'prepareCommand' as never).mockResolvedValue({ command: 'pnpm test --help', @@ -808,7 +860,11 @@ describe('AgentBashHandler', () => { }) it('keeps completed foreground sessions when output was offloaded', async () => { - const handler = new AgentBashHandler([workspaceRoot], createPermissionService()) + const handler = new AgentBashHandler( + [workspaceRoot], + createPermissionService(), + backgroundExecSessionManager + ) vi.spyOn(handler as never, 'prepareCommand' as never).mockResolvedValue({ command: 'pnpm test --reporter=json', @@ -871,7 +927,11 @@ describe('AgentBashHandler', () => { pid = 123 } - const handler = new AgentBashHandler([workspaceRoot], createPermissionService()) + const handler = new AgentBashHandler( + [workspaceRoot], + createPermissionService(), + backgroundExecSessionManager + ) const child = new MockChild() vi.mocked(spawn).mockReturnValue(child as never) vi.spyOn(handler as never, 'createOutputFilePath' as never).mockReturnValue('/tmp/exec.log') diff --git a/test/main/tool/agentTools/agentBashHandlerEncoding.test.ts b/test/main/tool/agentTools/agentBashHandlerEncoding.test.ts index 495f05bdc1..a8aa4a8b4b 100644 --- a/test/main/tool/agentTools/agentBashHandlerEncoding.test.ts +++ b/test/main/tool/agentTools/agentBashHandlerEncoding.test.ts @@ -1,3 +1,4 @@ +import { createBackgroundExecFixture } from './agentToolDependencies' import { EventEmitter } from 'events' import { spawn } from 'child_process' import fs from 'fs' @@ -71,7 +72,11 @@ describe('AgentBashHandler output encoding', () => { isDirectory: () => true } as fs.Stats) - const handler = new AgentBashHandler(['/workspace'], new CommandPermissionService()) + const handler = new AgentBashHandler( + ['/workspace'], + new CommandPermissionService(), + createBackgroundExecFixture() + ) const resultPromise = ( handler as unknown as { runDetachedShellProcess: ( diff --git a/test/main/tool/agentTools/agentImageGenerationTool.test.ts b/test/main/tool/agentTools/agentImageGenerationTool.test.ts index 8f991c3650..bb80431a14 100644 --- a/test/main/tool/agentTools/agentImageGenerationTool.test.ts +++ b/test/main/tool/agentTools/agentImageGenerationTool.test.ts @@ -1,22 +1,10 @@ import { beforeEach, describe, expect, it, vi } from 'vitest' -import os from 'os' import { AgentToolManager } from '@/tool/agentTools/agentToolManager' import { IMAGE_GENERATE_TOOL_NAME } from '@/tool/agentTools/agentImageGenerationTool' import { ApiEndpointType, ModelType } from '@shared/model' import { createAgentToolDependencies } from './agentToolDependencies' import { CommandPermissionService } from '@/tool/permission' -vi.mock('electron', () => ({ - app: { - getPath: () => os.tmpdir() - }, - nativeImage: { - createFromPath: () => ({ - getSize: () => ({ width: 128, height: 96 }) - }) - } -})) - describe('Agent image generation tool', () => { let providerSettings: any let generateImageStandalone: ReturnType @@ -54,7 +42,7 @@ describe('Agent image generation tool', () => { generateImageStandalone }, createSettingsWindow: vi.fn(), - sendToWindow: vi.fn().mockReturnValue(true), + getApprovedFilePaths: vi.fn().mockReturnValue([]), consumeSettingsApproval: vi.fn().mockReturnValue(false), ...(cacheImage ? { cacheImage } : {}) diff --git a/test/main/tool/agentTools/agentTapeTools.test.ts b/test/main/tool/agentTools/agentTapeTools.test.ts index d55cbe9515..8427fcb7ce 100644 --- a/test/main/tool/agentTools/agentTapeTools.test.ts +++ b/test/main/tool/agentTools/agentTapeTools.test.ts @@ -5,17 +5,6 @@ import { createAgentToolDependencies } from './agentToolDependencies' import { CommandPermissionService } from '@/tool/permission' import { resolveDeepChatSubagentCapability } from '@shared/lib/deepchatSubagents' -vi.mock('electron', () => ({ - app: { - getPath: () => '/tmp/deepchat-test' - }, - nativeImage: { - createFromPath: () => ({ - getSize: () => ({ width: 1, height: 1 }) - }) - } -})) - const buildRuntimePort = (overrides: Record = {}) => createAgentToolDependencies({ resolveConversationWorkdir: vi.fn().mockResolvedValue('/workspace'), @@ -41,16 +30,6 @@ const buildRuntimePort = (overrides: Record = {}) => slots: [] }) }), - getTapeInfo: vi.fn().mockResolvedValue({ - sessionId: 'conv-1', - entries: 3, - anchors: 1, - lastAnchor: 'session/start', - lastAnchorEntryId: 1, - entriesSinceLastAnchor: 2, - lastTokenUsage: 42, - migrationState: 'ready' - }), searchTape: vi.fn().mockResolvedValue([ { sessionId: 'conv-1', @@ -81,26 +60,6 @@ const buildRuntimePort = (overrides: Record = {}) => } ] }), - listTapeAnchors: vi.fn().mockResolvedValue([ - { - sessionId: 'conv-1', - entryId: 1, - kind: 'anchor', - name: 'session/start', - payload: { state: { owner: 'human' } }, - meta: {}, - createdAt: 1 - } - ]), - handoffTape: vi.fn().mockResolvedValue({ - sessionId: 'conv-1', - entryId: 4, - kind: 'anchor', - name: 'handoff/manual', - payload: { state: { summary: 'done' } }, - meta: { handoff: true }, - createdAt: 20 - }), createSubagentSession: vi.fn(), sendConversationMessage: vi.fn(), cancelConversation: vi.fn(), @@ -131,7 +90,7 @@ const buildRuntimePort = (overrides: Record = {}) => }, cacheImage: vi.fn(), createSettingsWindow: vi.fn(), - sendToWindow: vi.fn(), + getApprovedFilePaths: vi.fn().mockReturnValue([]), consumeSettingsApproval: vi.fn().mockReturnValue(false), ...overrides @@ -342,9 +301,6 @@ describe('Agent tape tools', () => { maxBytesPerEntry: undefined, maxTotalBytes: undefined }) - expect(runtimePort.tape.getTapeInfo).not.toHaveBeenCalled() - expect(runtimePort.tape.listTapeAnchors).not.toHaveBeenCalled() - expect(runtimePort.tape.handoffTape).not.toHaveBeenCalled() }) it('recalls a finalized ACP child through source-qualified linked Tape options', async () => { @@ -402,9 +358,6 @@ describe('Agent tape tools', () => { maxTotalBytes: undefined, sourceSessionId: 'acp-child' }) - expect(runtimePort.tape.getTapeInfo).not.toHaveBeenCalled() - expect(runtimePort.tape.listTapeAnchors).not.toHaveBeenCalled() - expect(runtimePort.tape.handoffTape).not.toHaveBeenCalled() }) it('rejects invalid cross-Tape selectors before calling the runtime', async () => { @@ -458,9 +411,8 @@ describe('Agent tape tools', () => { `Tape tool '${toolName}' is not available to the model.` ) - expect(runtimePort.tape.getTapeInfo).not.toHaveBeenCalled() - expect(runtimePort.tape.listTapeAnchors).not.toHaveBeenCalled() - expect(runtimePort.tape.handoffTape).not.toHaveBeenCalled() + expect(runtimePort.tape.searchTape).not.toHaveBeenCalled() + expect(runtimePort.tape.getTapeContext).not.toHaveBeenCalled() } ) }) diff --git a/test/main/tool/agentTools/agentToolDependencies.ts b/test/main/tool/agentTools/agentToolDependencies.ts index 3d5a751ae3..1e665ed2e4 100644 --- a/test/main/tool/agentTools/agentToolDependencies.ts +++ b/test/main/tool/agentTools/agentToolDependencies.ts @@ -1,11 +1,27 @@ import { vi } from 'vitest' +import os from 'node:os' +import path from 'node:path' import type { AgentToolDependencies } from '@/tool/runtimePorts' -import { AgentInvocationAdmission } from '@/agent/invocationAdmission' +import { BackgroundExecService } from '@/agent/shared/process/backgroundExecSessionManager' + +export const createBackgroundExecFixture = () => + new BackgroundExecService(async () => { + throw new Error('Background helper launch must be supplied by the test') + }) + +const requireAdmission = () => { + throw new Error('Admission must be supplied by the test') +} export const createAgentToolDependencies = ( overrides: Record = {} ): AgentToolDependencies => ({ - agentInvocationAdmission: overrides.agentInvocationAdmission ?? new AgentInvocationAdmission(), + backgroundExec: overrides.backgroundExec ?? createBackgroundExecFixture(), + agentInvocationAdmission: overrides.agentInvocationAdmission ?? { + acquire: vi.fn(requireAdmission), + createLease: vi.fn(requireAdmission), + run: vi.fn(requireAdmission) + }, sessions: { resolveConversationWorkdir: overrides.resolveConversationWorkdir ?? vi.fn().mockResolvedValue(null), @@ -26,11 +42,8 @@ export const createAgentToolDependencies = ( overrides.resolveConversationExecutionAuthority ?? vi.fn().mockResolvedValue(null) }, tape: { - getTapeInfo: overrides.getTapeInfo ?? vi.fn(), searchTape: overrides.searchTape ?? vi.fn(), - getTapeContext: overrides.getTapeContext ?? vi.fn(), - listTapeAnchors: overrides.listTapeAnchors ?? vi.fn(), - handoffTape: overrides.handoffTape ?? vi.fn() + getTapeContext: overrides.getTapeContext ?? vi.fn() }, memory: { isMemoryEnabled: overrides.isMemoryEnabled ?? vi.fn(() => false), @@ -108,10 +121,13 @@ export const createAgentToolDependencies = ( generateCompletionStandalone: vi.fn(), generateImageStandalone: vi.fn() } as any), - desktop: overrides.desktop ?? { + desktop: { + tempDirectory: path.join(os.tmpdir(), 'deepchat-electron-temp'), + profileTempDirectory: path.join(os.tmpdir(), 'deepchat-electron-user-data', 'temp'), + readImageSize: overrides.readImageSize ?? vi.fn(async () => ({ width: 128, height: 96 })), createSettingsWindow: vi.fn(), - sendToWindow: vi.fn().mockReturnValue(true), - sendSettingsNavigation: vi.fn().mockReturnValue(true) + sendSettingsNavigation: vi.fn().mockResolvedValue(true), + ...overrides.desktop }, permissions: { getApprovedFilePaths: overrides.getApprovedFilePaths ?? vi.fn().mockReturnValue([]), diff --git a/test/main/tool/agentTools/agentToolManagerFffSearch.test.ts b/test/main/tool/agentTools/agentToolManagerFffSearch.test.ts index 4105abb3c7..cf26b12ee0 100644 --- a/test/main/tool/agentTools/agentToolManagerFffSearch.test.ts +++ b/test/main/tool/agentTools/agentToolManagerFffSearch.test.ts @@ -33,19 +33,6 @@ vi.mock('fs', async (importOriginal) => { } }) -vi.mock('electron', () => ({ - app: { - getAppPath: () => '/mock/app', - getPath: () => '/tmp', - isPackaged: false - }, - nativeImage: { - createFromPath: vi.fn(() => ({ - getSize: () => ({ width: 0, height: 0 }) - })) - } -})) - function buildRuntimePort() { return createAgentToolDependencies({ resolveConversationWorkdir: vi.fn().mockResolvedValue(null), @@ -76,7 +63,7 @@ function buildRuntimePort() { generateImageStandalone: vi.fn() }, createSettingsWindow: vi.fn(), - sendToWindow: vi.fn().mockReturnValue(true), + getApprovedFilePaths: vi.fn().mockReturnValue([]), consumeSettingsApproval: vi.fn().mockReturnValue(false) }) diff --git a/test/main/tool/agentTools/agentToolManagerRead.test.ts b/test/main/tool/agentTools/agentToolManagerRead.test.ts index 6671ff22f6..fb10c04336 100644 --- a/test/main/tool/agentTools/agentToolManagerRead.test.ts +++ b/test/main/tool/agentTools/agentToolManagerRead.test.ts @@ -6,7 +6,7 @@ import path from 'path' import { AgentToolManager } from '@/tool/agentTools/agentToolManager' import { POSIX_COMMAND_SHELL } from '../../../helpers/commandShell' import { AgentBashHandler } from '@/tool/agentTools/agentBashHandler' -import { backgroundExecSessionManager } from '@/agent/shared/process/backgroundExecSessionManager' +import { createBackgroundExecFixture } from './agentToolDependencies' import * as sessionVisionResolverModule from '@/agent/vision/sessionVisionResolver' import { createAgentToolDependencies } from './agentToolDependencies' import { CommandPermissionService } from '@/tool/permission' @@ -22,29 +22,9 @@ vi.mock('fs', async (importOriginal) => { } }) -vi.mock('electron', () => ({ - app: { - getPath: (name: string) => { - if (name === 'temp') { - return path.join(os.tmpdir(), 'deepchat-electron-temp') - } - if (name === 'home') { - return path.join(os.tmpdir(), 'deepchat-electron-home') - } - if (name === 'userData') { - return path.join(os.tmpdir(), 'deepchat-electron-user-data') - } - return os.tmpdir() - } - }, - nativeImage: { - createFromPath: () => ({ - getSize: () => ({ width: 128, height: 96 }) - }) - } -})) - describe('AgentToolManager read routing', () => { + let backgroundExecSessionManager: ReturnType + let workspaceDir: string let providerSettings: any let manager: AgentToolManager @@ -60,10 +40,13 @@ describe('AgentToolManager read routing', () => { let resolveConversationWorkdir: ReturnType let resolveConversationSessionInfo: ReturnType let settingsGet: ReturnType + let readImageSize: ReturnType + let cacheImage: ReturnType let callToolWithoutCommandShell: AgentToolManager['callTool'] let preCheckWithoutCommandShell: AgentToolManager['preCheckToolPermission'] beforeEach(async () => { + backgroundExecSessionManager = createBackgroundExecFixture() vi.clearAllMocks() workspaceDir = await fs.mkdtemp(path.join(os.tmpdir(), 'deepchat-read-')) fileService = { @@ -92,6 +75,8 @@ describe('AgentToolManager read routing', () => { resolveDeepChatAgentConfig: vi.fn().mockResolvedValue({}) } settingsGet = vi.fn() + readImageSize = vi.fn(async () => ({ width: 128, height: 96 })) + cacheImage = vi.fn(async (data: string) => data) manager = new AgentToolManager({ skillSettings: { isEnabled: () => false } as any, settings: { get: settingsGet }, @@ -100,6 +85,9 @@ describe('AgentToolManager read routing', () => { providerSettings, agentSettings: providerSettings, dependencies: createAgentToolDependencies({ + backgroundExec: backgroundExecSessionManager, + readImageSize, + cacheImage, resolveConversationWorkdir, resolveConversationSessionInfo, skillService: { @@ -121,7 +109,7 @@ describe('AgentToolManager read routing', () => { fileService: fileService, providerRuntime: providerRuntime, createSettingsWindow: vi.fn(), - sendToWindow: vi.fn().mockReturnValue(true), + getApprovedFilePaths: vi.fn().mockReturnValue([]), consumeSettingsApproval: vi.fn().mockReturnValue(false) }) @@ -1280,6 +1268,105 @@ describe('AgentToolManager read routing', () => { ) }) + it('uses only supplied runtime roots and preserves the default workspace', async () => { + const base = await fs.mkdtemp(path.join(os.tmpdir(), 'deepchat-host-roots-')) + const temporary = path.join(base, 'native-temp') + const profileTemporary = path.join(base, 'profile-temp') + const defaultWorkspace = path.join(temporary, 'deepchat-agent', 'workspaces') + try { + await fs.mkdir(defaultWorkspace, { recursive: true }) + await fs.mkdir(profileTemporary, { recursive: true }) + await fs.writeFile(path.join(defaultWorkspace, 'workspace.txt'), 'DEFAULT_WORKSPACE') + await fs.writeFile(path.join(profileTemporary, 'profile.txt'), 'PROFILE_TEMPORARY') + await fs.writeFile(path.join(base, 'denied.txt'), 'UNRELATED_FILE') + fileService.getMimeType.mockResolvedValue('text/plain') + const build = (tempDirectory = temporary) => + new AgentToolManager({ + agentWorkspacePath: null, + settings: { get: settingsGet }, + providerSettings, + agentSettings: providerSettings, + skillSettings: { isEnabled: () => false } as any, + desktopSettings: { applyDisplayChange: vi.fn() }, + commandPermissionHandler: new CommandPermissionService(), + dependencies: createAgentToolDependencies({ + backgroundExec: backgroundExecSessionManager, + fileService, + resolveConversationWorkdir: vi.fn(async () => null), + skillService: { + getSkillsDir: vi.fn(async () => path.join(base, 'skills')), + getActiveSkills: vi.fn(async () => []), + getActiveSkillsAllowedTools: vi.fn(async () => []) + }, + desktop: { tempDirectory, profileTempDirectory: profileTemporary } + }) + }) + const isolated = build() + isolated.syncContext({ chatMode: 'agent', agentWorkspacePath: null }) + const options = { commandShell: POSIX_COMMAND_SHELL, allowExternalFileAccess: false } + await expect( + isolated.callTool('read', { path: 'workspace.txt' }, 'host-session', options) + ).resolves.toMatchObject({ content: expect.stringContaining('DEFAULT_WORKSPACE') }) + await expect( + isolated.callTool( + 'read', + { path: path.join(profileTemporary, 'profile.txt') }, + 'host-session', + options + ) + ).resolves.toMatchObject({ content: expect.stringContaining('PROFILE_TEMPORARY') }) + await expect( + isolated.callTool('read', { path: path.join(base, 'denied.txt') }, 'host-session', options) + ).resolves.toMatchObject({ + rawData: { + requiresPermission: true, + permissionRequest: { permissionType: 'read' } + } + }) + expect(() => build('relative')).toThrow('host directories must be absolute') + } finally { + await fs.rm(base, { recursive: true, force: true }) + } + }) + + it('retains unknown metadata when the supplied image probe fails', async () => { + const filePath = path.join(workspaceDir, 'image-probe-error.png') + await fs.writeFile(filePath, Buffer.from([0, 1, 2, 3])) + fileService.getMimeType.mockResolvedValue('image/png') + readImageSize.mockRejectedValue(new Error('Image decoder unavailable')) + providerSettings.resolveDeepChatAgentConfig.mockResolvedValue({}) + const result = await manager.callTool('read', { path: filePath }, 'conv1') + expect(result.content).toContain('resolution: unknown') + expect(providerRuntime.generateCompletionStandalone).not.toHaveBeenCalled() + }) + + it('fences a late image size after cancellation before cache or Provider work', async () => { + const filePath = path.join(workspaceDir, 'image-probe-abort.png') + await fs.writeFile(filePath, Buffer.from([0, 1, 2, 3])) + fileService.getMimeType.mockResolvedValue('image/png') + const resolvedPath = await fs.realpath(filePath) + let finish!: (size: { width: number; height: number }) => void + readImageSize.mockImplementation( + () => + new Promise((resolve) => { + finish = resolve + }) + ) + const controller = new AbortController() + const execution = manager.callTool('read', { path: filePath }, 'conv1', { + signal: controller.signal + }) + const rejected = expect(execution).rejects.toMatchObject({ name: 'AbortError' }) + await vi.waitFor(() => + expect(readImageSize).toHaveBeenCalledWith(resolvedPath, controller.signal) + ) + controller.abort() + finish({ width: 128, height: 96 }) + await rejected + expect(cacheImage).not.toHaveBeenCalled() + expect(providerRuntime.generateCompletionStandalone).not.toHaveBeenCalled() + }) + it('falls back to image metadata when neither the current model nor the agent can analyze images', async () => { const filePath = path.join(workspaceDir, 'image-no-vision.png') await fs.writeFile(filePath, Buffer.from([9, 8, 7, 6])) diff --git a/test/main/tool/agentTools/agentToolManagerSettings.test.ts b/test/main/tool/agentTools/agentToolManagerSettings.test.ts index 889c4ac8f0..0893d21d2c 100644 --- a/test/main/tool/agentTools/agentToolManagerSettings.test.ts +++ b/test/main/tool/agentTools/agentToolManagerSettings.test.ts @@ -45,13 +45,6 @@ function makeSkillResolution(name: string, content: string, agentId = 'agent-a') } } -vi.mock('electron', () => ({ - app: { - getPath: () => '/tmp', - getAppPath: () => '/mock/app' - } -})) - describe('AgentToolManager DeepChat settings tool gating', () => { const providerSettings = {} as any const skillService = { @@ -85,8 +78,13 @@ describe('AgentToolManager DeepChat settings tool gating', () => { settings: { get: vi.fn() }, commandPermissionHandler: new CommandPermissionService(), desktopSettings: { - getCopyWithCotEnabled: vi.fn(() => true), - setCopyWithCotEnabled: vi.fn() + applyDisplayChange: vi.fn(async (change) => ({ + ok: true, + id: change.id, + value: change.value, + previousValue: true, + appliedAt: 1 + })) } as any, agentWorkspacePath: null, providerSettings, @@ -113,7 +111,7 @@ describe('AgentToolManager DeepChat settings tool gating', () => { generateImageStandalone: vi.fn() }, createSettingsWindow: vi.fn(), - sendToWindow: vi.fn().mockReturnValue(true), + getApprovedFilePaths: vi.fn().mockReturnValue([]), consumeSettingsApproval: vi.fn().mockReturnValue(false) }) diff --git a/test/main/tool/agentTools/agentToolManagerSkillAccess.test.ts b/test/main/tool/agentTools/agentToolManagerSkillAccess.test.ts index 504d19309c..c8d7e7929b 100644 --- a/test/main/tool/agentTools/agentToolManagerSkillAccess.test.ts +++ b/test/main/tool/agentTools/agentToolManagerSkillAccess.test.ts @@ -17,25 +17,6 @@ vi.mock('fs', async (importOriginal) => { } }) -vi.mock('electron', () => ({ - app: { - getPath: (name: string) => { - if (name === 'temp') { - return path.join(os.tmpdir(), 'deepchat-electron-temp') - } - if (name === 'home') { - return path.join(os.tmpdir(), 'deepchat-electron-home') - } - return os.tmpdir() - } - }, - nativeImage: { - createFromPath: () => ({ - getSize: () => ({ width: 128, height: 96 }) - }) - } -})) - describe('AgentToolManager skill file access', () => { const electronHome = path.join(os.tmpdir(), 'deepchat-electron-home') const agentId = `agent-a-${process.pid}` @@ -84,7 +65,7 @@ describe('AgentToolManager skill file access', () => { generateImageStandalone: vi.fn() }, createSettingsWindow: vi.fn(), - sendToWindow: vi.fn().mockReturnValue(true), + getApprovedFilePaths: vi.fn().mockReturnValue([]), consumeSettingsApproval: vi.fn().mockReturnValue(false) }) diff --git a/test/main/tool/agentTools/agentToolManagerYoBrowser.test.ts b/test/main/tool/agentTools/agentToolManagerYoBrowser.test.ts index 3bd372c8c8..addcd89a23 100644 --- a/test/main/tool/agentTools/agentToolManagerYoBrowser.test.ts +++ b/test/main/tool/agentTools/agentToolManagerYoBrowser.test.ts @@ -1,21 +1,9 @@ import { beforeEach, describe, expect, it, vi } from 'vitest' -import os from 'os' import { AgentToolManager } from '@/tool/agentTools/agentToolManager' import { YoBrowserUnavailableError, buildYoBrowserUnavailablePayload } from '@/tool/browser/errors' import { createAgentToolDependencies } from './agentToolDependencies' import { CommandPermissionService } from '@/tool/permission' -vi.mock('electron', () => ({ - app: { - getPath: () => os.tmpdir() - }, - nativeImage: { - createFromPath: () => ({ - getSize: () => ({ width: 128, height: 96 }) - }) - } -})) - describe('AgentToolManager YoBrowser routing', () => { let manager: AgentToolManager let yoBrowserCallTool: ReturnType @@ -27,6 +15,11 @@ describe('AgentToolManager YoBrowser routing', () => { skillSettings: { isEnabled: () => false } as any, settings: { get: vi.fn() }, commandPermissionHandler: new CommandPermissionService(), + desktopSettings: { + async applyDisplayChange() { + throw new Error('Unexpected display mutation') + } + }, agentWorkspacePath: null, agentSettings: { resolveDeepChatAgentConfig: vi.fn().mockResolvedValue({}) @@ -58,7 +51,7 @@ describe('AgentToolManager YoBrowser routing', () => { generateImageStandalone: vi.fn() }, createSettingsWindow: vi.fn(), - sendToWindow: vi.fn().mockReturnValue(true), + getApprovedFilePaths: vi.fn().mockReturnValue([]), consumeSettingsApproval: vi.fn().mockReturnValue(false) }) diff --git a/test/main/tool/agentTools/chatSettingsTools.test.ts b/test/main/tool/agentTools/chatSettingsTools.test.ts index 458b09cf45..29ed61d58e 100644 --- a/test/main/tool/agentTools/chatSettingsTools.test.ts +++ b/test/main/tool/agentTools/chatSettingsTools.test.ts @@ -7,19 +7,7 @@ import { } from '@/tool/agentTools/chatSettingsTools' describe('ChatSettingsToolHandler', () => { - const providerSettings = { - getSetting: vi.fn(), - setSetting: vi.fn(), - setLanguage: vi.fn(), - setTheme: vi.fn() - } as any - - const desktopSettings = { - getCopyWithCotEnabled: vi.fn(), - getTheme: vi.fn(), - setCopyWithCotEnabled: vi.fn(), - setTheme: vi.fn() - } as any + const desktopSettings = { applyDisplayChange: vi.fn() } const skillSettings = { isEnabled: vi.fn() @@ -36,7 +24,6 @@ describe('ChatSettingsToolHandler', () => { const buildHandler = () => new ChatSettingsToolHandler({ - providerSettings, desktopSettings, skillSettings, skillService, @@ -45,14 +32,17 @@ describe('ChatSettingsToolHandler', () => { beforeEach(() => { vi.clearAllMocks() - desktopSettings.getCopyWithCotEnabled.mockReturnValue(true) - desktopSettings.getTheme.mockReturnValue('dark') - providerSettings.getSetting.mockReturnValue('chat') - providerSettings.setTheme.mockResolvedValue(false) + desktopSettings.applyDisplayChange.mockImplementation(async (change) => ({ + ok: true, + id: change.id, + value: change.value, + previousValue: change.id === 'copyWithCotEnabled' ? true : 'dark', + appliedAt: 1 + })) skillSettings.isEnabled.mockReturnValue(true) skillService.getActiveSkills.mockResolvedValue([CHAT_SETTINGS_SKILL_NAME]) windowPresenter.createSettingsWindow.mockResolvedValue(1) - windowPresenter.sendSettingsNavigation.mockReturnValue(true) + windowPresenter.sendSettingsNavigation.mockResolvedValue(true) }) it('rejects toggle when skill is inactive', async () => { @@ -64,7 +54,7 @@ describe('ChatSettingsToolHandler', () => { if (!result.ok) { expect(result.errorCode).toBe('skill_inactive') } - expect(desktopSettings.setCopyWithCotEnabled).not.toHaveBeenCalled() + expect(desktopSettings.applyDisplayChange).not.toHaveBeenCalled() }) it('rejects invalid toggle payloads', async () => { @@ -75,14 +65,17 @@ describe('ChatSettingsToolHandler', () => { if (!result.ok) { expect(result.errorCode).toBe('invalid_request') } - expect(desktopSettings.setCopyWithCotEnabled).not.toHaveBeenCalled() + expect(desktopSettings.applyDisplayChange).not.toHaveBeenCalled() }) it('applies copyWithCotEnabled toggle', async () => { const handler = buildHandler() const result = await handler.toggle({ setting: 'copyWithCotEnabled', enabled: false }, 'conv-1') - expect(desktopSettings.setCopyWithCotEnabled).toHaveBeenCalledWith(false) + expect(desktopSettings.applyDisplayChange).toHaveBeenCalledWith({ + id: 'copyWithCotEnabled', + value: false + }) expect(result.ok).toBe(true) if (result.ok) { expect(result.previousValue).toBe(true) @@ -95,10 +88,43 @@ describe('ChatSettingsToolHandler', () => { const result = await handler.setTheme({ theme: 'light' }, 'conv-1', [CHAT_SETTINGS_SKILL_NAME]) expect(result.ok).toBe(true) - expect(desktopSettings.setTheme).toHaveBeenCalledWith('light') + expect(desktopSettings.applyDisplayChange).toHaveBeenCalledWith({ id: 'theme', value: 'light' }) expect(skillService.getActiveSkills).not.toHaveBeenCalled() }) + it('waits for the native owner and returns its applied receipt', async () => { + const receipt = { + ok: true as const, + id: 'theme' as const, + value: 'light' as const, + previousValue: 'system' as const, + appliedAt: 123 + } + let acknowledge!: (value: typeof receipt) => void + desktopSettings.applyDisplayChange.mockReturnValueOnce( + new Promise((resolve) => { + acknowledge = resolve + }) + ) + const result = buildHandler().setTheme({ theme: 'light' }, 'conv-1', [CHAT_SETTINGS_SKILL_NAME]) + let settled = false + void result.then(() => { + settled = true + }) + await Promise.resolve() + expect(settled).toBe(false) + acknowledge(receipt) + expect(await result).toEqual(receipt) + }) + + it('reports an asynchronous native mutation failure', async () => { + desktopSettings.applyDisplayChange.mockRejectedValueOnce(new Error('native owner stopped')) + expect(await buildHandler().setTheme({ theme: 'light' }, 'conv-1')).toMatchObject({ + ok: false, + errorCode: 'apply_failed' + }) + }) + it('propagates dispatch commit failure without changing settings', async () => { const handler = buildHandler() const journalError = new Error('journal unavailable') @@ -111,20 +137,43 @@ describe('ChatSettingsToolHandler', () => { ).rejects.toBe(journalError) expect(beforeMutation).toHaveBeenCalledWith({ theme: 'light' }) - expect(desktopSettings.setTheme).not.toHaveBeenCalled() + expect(desktopSettings.applyDisplayChange).not.toHaveBeenCalled() }) - it('opens settings and navigates to section', async () => { + it('waits for settings navigation acknowledgement', async () => { + const acknowledgement = Promise.withResolvers() + windowPresenter.sendSettingsNavigation.mockReturnValueOnce(acknowledgement.promise) const handler = buildHandler() - const result = await handler.open({ section: 'mcp' }, 'conv-1') + let settled = false + const opening = handler.open({ section: 'mcp' }, 'conv-1').then((result) => { + settled = true + return result + }) - expect(windowPresenter.createSettingsWindow).toHaveBeenCalled() - expect(windowPresenter.sendSettingsNavigation).toHaveBeenCalledWith(1, { - routeName: 'settings-mcp', - section: 'mcp' + await vi.waitFor(() => { + expect(windowPresenter.sendSettingsNavigation).toHaveBeenCalledWith(1, { + routeName: 'settings-mcp', + section: 'mcp' + }) }) - expect(result.ok).toBe(true) + expect(settled).toBe(false) + acknowledgement.resolve(true) + await expect(opening).resolves.toMatchObject({ ok: true, routeName: 'settings-mcp' }) }) + + it.each([false, new Error('Native window closed')])( + 'reports navigation failure after a negative or failed native receipt (%s)', + async (outcome) => { + windowPresenter.sendSettingsNavigation.mockImplementationOnce(async () => { + if (outcome instanceof Error) throw outcome + return outcome + }) + await expect(buildHandler().open({ section: 'mcp' }, 'conv-1')).resolves.toMatchObject({ + ok: false, + errorCode: 'open_failed' + }) + } + ) }) describe('buildChatSettingsToolDefinitions', () => { diff --git a/test/main/tool/codeMode/runCodeRuntimeManager.test.ts b/test/main/tool/codeMode/runCodeRuntimeManager.test.ts index 0cdd2ee89f..ccd23e0c7a 100644 --- a/test/main/tool/codeMode/runCodeRuntimeManager.test.ts +++ b/test/main/tool/codeMode/runCodeRuntimeManager.test.ts @@ -14,6 +14,7 @@ import { class FakeUtilityProcess extends EventEmitter { readonly pid = 42 + readonly getRssBytes = vi.fn<(signal: AbortSignal) => Promise>(async () => null) readonly messages: RunCodeParentMessage[] = [] readonly kill = vi.fn(() => true) @@ -79,7 +80,7 @@ const createManager = ( host: FakeUtilityProcess, executeNested: RunCodeRuntimeManagerOptions['executeNested'] = vi.fn(async () => ({ content: 'nested result', - rawData: { content: 'nested result' } + rawData: { toolCallId: 'fixture-nested-call', content: 'nested result' } })) ) => new RunCodeRuntimeManager({ @@ -91,6 +92,196 @@ const createManager = ( }) describe('RunCodeRuntimeManager', () => { + const deferred = () => { + let resolve!: (value: T) => void + const promise = new Promise((settle) => { + resolve = settle + }) + return { promise, resolve } + } + const codeInput = () => ({ + sessionId: 'session-1', + toolCallId: 'call-1', + frontend: 'function' as const, + source: 'return true', + executionCatalog: [] as MCPToolDefinition[], + outerDispatch: outerDispatch(), + options: {} + }) + + it('reserves a session during asynchronous startup before a second dispatch can commit', async () => { + const ready = deferred() + const spawnHost = vi.fn(() => ready.promise) + const manager = new RunCodeRuntimeManager({ spawnHost, executeNested: vi.fn() }) + const first = manager.execute(codeInput()) + await vi.waitFor(() => expect(spawnHost).toHaveBeenCalledOnce()) + await expect(manager.execute({ ...codeInput(), toolCallId: 'second' })).rejects.toThrow( + 'already has a code cell starting' + ) + const host = new FakeUtilityProcess(true) + ready.resolve(host) + await vi.waitFor(() => expect(host.listenerCount('message')).toBe(1)) + host.becomeReady() + await expect(first).resolves.toMatchObject({ content: expect.stringContaining('from console') }) + expect(spawnHost).toHaveBeenCalledOnce() + await manager.shutdown() + }) + + it('keeps shutdown pending for an uncancelable physical spawn and never starts its late cell', async () => { + const ready = deferred() + const spawnHost = vi.fn(() => ready.promise) + const manager = new RunCodeRuntimeManager({ spawnHost, executeNested: vi.fn() }) + const rejected = expect(manager.execute(codeInput())).rejects.toMatchObject({ + name: 'AbortError' + }) + await vi.waitFor(() => expect(spawnHost).toHaveBeenCalledOnce()) + let stopped = false + const stop = manager.shutdown().then(() => { + stopped = true + }) + await rejected + expect(stopped).toBe(false) + await expect(manager.execute(codeInput())).rejects.toThrow('shutting down') + const host = new FakeUtilityProcess(false) + ready.resolve(host) + await stop + expect(host.kill).toHaveBeenCalledOnce() + expect(host.messages).toEqual([]) + }) + + it.each(['caller', 'session'])( + 'retains the startup lease after %s cancellation until actual settlement', + async (kind) => { + const ready = deferred() + const next = new FakeUtilityProcess(true) + const spawnHost = vi + .fn() + .mockImplementationOnce(() => ready.promise) + .mockImplementationOnce(async () => { + setTimeout(() => next.becomeReady(), 0) + return next + }) + const manager = new RunCodeRuntimeManager({ spawnHost, executeNested: vi.fn() }) + const caller = new AbortController() + const rejected = expect( + manager.execute({ ...codeInput(), options: { signal: caller.signal } }) + ).rejects.toThrow(kind === 'caller' ? 'aborted' : 'session canceled') + await vi.waitFor(() => expect(spawnHost).toHaveBeenCalledOnce()) + if (kind === 'caller') caller.abort() + else manager.cancelSession('session-1', 'session canceled') + await rejected + await expect(manager.execute(codeInput())).rejects.toThrow('already has a code cell starting') + const old = new FakeUtilityProcess(false) + ready.resolve(old) + await vi.waitFor(() => expect(old.kill).toHaveBeenCalledOnce()) + await expect(manager.execute(codeInput())).resolves.toHaveProperty('content') + expect(old.messages).toEqual([]) + expect(spawnHost).toHaveBeenCalledTimes(2) + await manager.shutdown() + } + ) + + it('drains an uncancelable nested handler after the cell has exited without accepting its late result', async () => { + const host = new FakeUtilityProcess(false) + const entered = deferred(), + release = deferred() + let nestedSignal: AbortSignal | undefined + const manager = createManager(host, async (input) => { + nestedSignal = input.options.signal + entered.resolve() + await release.promise + return { + content: 'late result', + rawData: { toolCallId: 'fixture-nested-call', content: 'late result' } + } + }) + const rejected = expect( + manager.execute({ ...codeInput(), executionCatalog: [tool('exec')] }) + ).rejects.toThrow('shut down') + await vi.waitFor(() => expect(host.messages[0]?.type).toBe('START')) + const start = host.messages[0] as Extract + host.emit('message', { + type: 'NESTED_CALL', + version: RUN_CODE_PROTOCOL_VERSION, + cellId: start.cellId, + callId: 'nested', + bindingId: start.bindings[0].id, + arguments: {} + }) + await entered.promise + let stopped = false + const stop = manager.shutdown().then(() => { + stopped = true + }) + await rejected + expect(nestedSignal?.aborted).toBe(true) + expect(stopped).toBe(false) + release.resolve() + await stop + expect(host.messages.some((message) => message.type === 'NESTED_RESULT')).toBe(false) + }) + + it('coalesces RSS reads and drains a held sample without acting on its late value', async () => { + vi.useFakeTimers() + try { + const host = new FakeUtilityProcess(false), + sample = deferred() + host.getRssBytes.mockReturnValue(sample.promise) + const manager = createManager(host) + const rejected = expect(manager.execute(codeInput())).rejects.toThrow('shut down') + await vi.advanceTimersByTimeAsync(3_000) + expect(host.getRssBytes).toHaveBeenCalledOnce() + let stopped = false + const stop = manager.shutdown().then(() => { + stopped = true + }) + await rejected + expect(host.getRssBytes.mock.calls[0][0].aborted).toBe(true) + expect(stopped).toBe(false) + sample.resolve(513 * 1024 * 1024) + await stop + expect(host.messages.filter((message) => message.type === 'STOP')).toHaveLength(1) + } finally { + vi.useRealTimers() + } + }) + + it('enforces the existing hard RSS limit through the injected sampling capability', async () => { + vi.useFakeTimers() + try { + const host = new FakeUtilityProcess(false) + host.getRssBytes.mockResolvedValue(513 * 1024 * 1024) + const manager = createManager(host) + const rejected = expect(manager.execute(codeInput())).rejects.toThrow('512 MiB RSS limit') + await vi.advanceTimersByTimeAsync(1_001) + await rejected + await manager.shutdown() + } finally { + vi.useRealTimers() + } + }) + + it('keeps the five-second readiness deadline and releases a silent helper', async () => { + vi.useFakeTimers() + try { + const host = new FakeUtilityProcess(false) + const manager = new RunCodeRuntimeManager({ + spawnHost: async () => host, + executeNested: vi.fn() + }) + const rejected = expect(manager.execute(codeInput())).rejects.toThrow('within 5 seconds') + await vi.advanceTimersByTimeAsync(4_999) + expect(host.kill).not.toHaveBeenCalled() + await vi.advanceTimersByTimeAsync(1) + await rejected + expect(host.kill).toHaveBeenCalledOnce() + expect(host.messages).toEqual([]) + await manager.shutdown() + } finally { + vi.useRealTimers() + } + }) + it('rejects partial execution-journal capability wiring before starting a cell', async () => { const host = new FakeUtilityProcess(false) const manager = createManager(host) @@ -236,8 +427,9 @@ describe('RunCodeRuntimeManager', () => { const spawning = new Promise((resolve) => { resolveHost = resolve }) + const spawnHost = vi.fn(() => spawning) const manager = new RunCodeRuntimeManager({ - spawnHost: () => spawning, + spawnHost, executeNested: vi.fn() }) const execution = manager.execute({ @@ -250,6 +442,7 @@ describe('RunCodeRuntimeManager', () => { options: { signal: controller.signal } }) const cancellation = expect(execution).rejects.toMatchObject({ name: 'AbortError' }) + await vi.waitFor(() => expect(spawnHost).toHaveBeenCalledOnce()) if (stage !== 'spawn') { resolveHost(host) await vi.waitFor(() => expect(host.listenerCount('message')).toBe(1)) @@ -345,10 +538,10 @@ describe('RunCodeRuntimeManager', () => { const manager = createManager( host, vi.fn(async () => ({ - content: [{ type: 'text', text: 'rendered fallback' }], + content: [{ type: 'text' as const, text: 'rendered fallback' }], rawData: { toolCallId: 'nested-1', - content: [{ type: 'text', text: 'rendered fallback' }], + content: [{ type: 'text' as const, text: 'rendered fallback' }], structuredContent: { answer: 42 } } })) @@ -409,7 +602,7 @@ describe('RunCodeRuntimeManager', () => { host, vi.fn(async () => ({ content: 'nested failure', - rawData: { content: 'nested failure', isError: true } + rawData: { toolCallId: 'fixture-nested-call', content: 'nested failure', isError: true } })) ) const execution = manager.execute({ @@ -545,7 +738,7 @@ describe('RunCodeRuntimeManager', () => { normalizedArguments: input.arguments as Record, target: { serverName: 'test', originalName: input.definition.function.name } }) - return { content: 'done', rawData: { content: 'done' } } + return { content: 'done', rawData: { toolCallId: 'fixture-nested-call', content: 'done' } } }) const manager = createManager(host, executeNested) const yielded = await manager.execute({ @@ -616,7 +809,7 @@ describe('RunCodeRuntimeManager', () => { target: { serverName: 'test', originalName: input.definition.function.name } }) await nestedPending - return { content: 'done', rawData: { content: 'done' } } + return { content: 'done', rawData: { toolCallId: 'fixture-nested-call', content: 'done' } } }) const manager = createManager(host, executeNested) const execution = manager.execute({ @@ -678,18 +871,21 @@ describe('RunCodeRuntimeManager', () => { .mockResolvedValueOnce({ content: 'permission required', rawData: { + toolCallId: 'fixture-nested-call', content: 'permission required', requiresPermission: true, permissionRequest: { + serverName: 'agent-filesystem', + description: 'Approve fixture command', toolName: 'exec', - permissionType: 'command', + permissionType: 'command' as const, command: 'git status --short' } } }) .mockResolvedValueOnce({ content: 'clean', - rawData: { content: 'clean' } + rawData: { toolCallId: 'fixture-nested-call', content: 'clean' } }) const manager = createManager(host, executeNested) const input = { @@ -720,7 +916,7 @@ describe('RunCodeRuntimeManager', () => { }) await expect(firstExecution).resolves.toMatchObject({ - rawData: { requiresPermission: true } + rawData: { toolCallId: 'fixture-nested-call', requiresPermission: true } }) const retriedExecution = manager.execute({ @@ -758,18 +954,21 @@ describe('RunCodeRuntimeManager', () => { .mockResolvedValueOnce({ content: 'permission required', rawData: { + toolCallId: 'fixture-nested-call', content: 'permission required', requiresPermission: true, permissionRequest: { + serverName: 'agent-filesystem', + description: 'Approve fixture command', toolName: 'exec', - permissionType: 'command', + permissionType: 'command' as const, command: 'git status --short' } } }) .mockResolvedValueOnce({ content: 'nested failure', - rawData: { content: 'nested failure', isError: true } + rawData: { toolCallId: 'fixture-nested-call', content: 'nested failure', isError: true } }) const manager = createManager(host, executeNested) const input = { @@ -799,7 +998,7 @@ describe('RunCodeRuntimeManager', () => { arguments: { command: 'git status --short' } }) await expect(firstExecution).resolves.toMatchObject({ - rawData: { requiresPermission: true } + rawData: { toolCallId: 'fixture-nested-call', requiresPermission: true } }) const retriedExecution = manager.execute({ @@ -837,9 +1036,12 @@ describe('RunCodeRuntimeManager', () => { const executeNested = vi.fn(async () => ({ content: 'permission required', rawData: { + toolCallId: 'fixture-nested-call', content: 'permission required', requiresPermission: true, permissionRequest: { + serverName: 'agent-filesystem', + description: 'Approve fixture command', toolName: 'exec', permissionType: 'command' as const, command: 'git status --short' @@ -872,7 +1074,7 @@ describe('RunCodeRuntimeManager', () => { arguments: { command: 'git status --short' } }) await expect(execution).resolves.toMatchObject({ - rawData: { requiresPermission: true } + rawData: { toolCallId: 'fixture-nested-call', requiresPermission: true } }) const heartbeat = setInterval(() => { @@ -911,7 +1113,10 @@ describe('RunCodeRuntimeManager', () => { await new Promise((resolve) => { finishNested = resolve }) - return { content: 'late result', rawData: { content: 'late result' } } + return { + content: 'late result', + rawData: { toolCallId: 'fixture-nested-call', content: 'late result' } + } }) const manager = createManager(host, executeNested) const execution = manager.execute({ diff --git a/test/main/tool/toolService.test.ts b/test/main/tool/toolService.test.ts index df2bb17261..d85fc900ce 100644 --- a/test/main/tool/toolService.test.ts +++ b/test/main/tool/toolService.test.ts @@ -1,3 +1,4 @@ +import { createPluginToolPolicies } from '../../helpers/pluginToolPolicies' import { EventEmitter } from 'node:events' import { describe, expect, it, vi } from 'vitest' import { TOOL_EXECUTION, type MCPToolDefinition } from '@shared/types/mcp' @@ -61,36 +62,7 @@ import { createToolSurfaceCanaryRunEvidenceRecorder } from '@/agent/deepchat/runtime/toolSurfaceCanaryDiagnostics' -vi.mock('electron', async () => { - const { join } = await import('node:path') - const { tmpdir } = await import('node:os') - const { rmSync } = await import('node:fs') - // Isolate the mocked userData per worker process and clean it up on exit so - // parallel workers never share persistent electron-store state. - const userDataDir = join(tmpdir(), `deepchat-vitest-userdata-toolservice-${process.pid}`) - process.on('exit', () => { - try { - rmSync(userDataDir, { recursive: true, force: true }) - } catch { - // best-effort cleanup - } - }) - const electronModuleMock = { - app: { - getName: () => 'DeepChat', - getVersion: () => '0.0.0-test', - getPath: (type: string) => (type === 'userData' ? userDataDir : '/mock/path') - }, - ipcMain: { - on: () => {}, - handle: () => {} - }, - shell: { - openPath: async () => '' - } - } - return { ...electronModuleMock, default: electronModuleMock } -}) +const pluginToolPolicies = createPluginToolPolicies() const buildToolDefinition = (name: string, serverName: string): MCPToolDefinition => ({ execution: TOOL_EXECUTION.write, @@ -120,6 +92,10 @@ const buildToolDefinition = (name: string, serverName: string): MCPToolDefinitio } }) +const spawnCodeModeHost = async () => { + throw new Error('Code Mode launch must be supplied by the test') +} + const buildAgentToolRuntimeMock = (overrides: Record = {}) => createAgentToolDependencies({ resolveConversationSessionInfo: vi.fn(async (sessionId: string) => ({ @@ -297,6 +273,8 @@ const cronJobRunFixture = { describe('ToolService', () => { it('carries only the active frozen Programmatic capability to native exec', async () => { const toolService = new ToolService({ + spawnCodeModeHost, + pluginToolPolicies, skillSettings: { isEnabled: () => false } as any, mcpService: { getAllToolDefinitions: vi.fn().mockResolvedValue([]), @@ -477,6 +455,8 @@ describe('ToolService', () => { resolveAuthority(sessionId) ) const toolService = new ToolService({ + spawnCodeModeHost, + pluginToolPolicies, skillSettings: { isEnabled: () => false } as any, mcpService: { getAllToolDefinitions, @@ -777,6 +757,8 @@ describe('ToolService', () => { }) } as any const toolService = new ToolService({ + spawnCodeModeHost, + pluginToolPolicies, skillSettings: { isEnabled: () => false } as any, mcpService, agentSettings: { resolveDeepChatAgentConfig: vi.fn(async () => ({})) } as any, @@ -840,6 +822,8 @@ describe('ToolService', () => { }) })! const toolService = new ToolService({ + spawnCodeModeHost, + pluginToolPolicies, skillSettings: { isEnabled: () => false } as any, mcpService: { getAllToolDefinitions: vi.fn(), callTool: vi.fn() } as any, agentSettings: { resolveDeepChatAgentConfig: vi.fn(async () => ({})) } as any, @@ -902,6 +886,8 @@ describe('ToolService', () => { beforeToolExecution: vi.fn() } const toolService = new ToolService({ + spawnCodeModeHost, + pluginToolPolicies, skillSettings: { isEnabled: () => false } as any, mcpService, agentSettings: { resolveDeepChatAgentConfig: vi.fn(async () => ({})) } as any, @@ -960,6 +946,8 @@ describe('ToolService', () => { it('rejects guessed inactive tools before source or target resolution', async () => { const active = buildToolSurfaceExecutionContext() const toolService = new ToolService({ + spawnCodeModeHost, + pluginToolPolicies, skillSettings: { isEnabled: () => false } as any, mcpService: { getAllToolDefinitions: vi.fn(), callTool: vi.fn() } as any, agentSettings: { resolveDeepChatAgentConfig: vi.fn(async () => ({})) } as any, @@ -1033,6 +1021,8 @@ describe('ToolService', () => { }) } as any toolService = new ToolService({ + spawnCodeModeHost, + pluginToolPolicies, skillSettings: { isEnabled: () => false } as any, mcpService, agentSettings: { resolveDeepChatAgentConfig: vi.fn(async () => ({})) } as any, @@ -1095,6 +1085,8 @@ describe('ToolService', () => { }) } as any const toolService = new ToolService({ + spawnCodeModeHost, + pluginToolPolicies, skillSettings: { isEnabled: () => false } as any, mcpService, agentSettings: { resolveDeepChatAgentConfig: vi.fn(async () => ({})) } as any, @@ -1143,6 +1135,8 @@ describe('ToolService', () => { beforeToolExecution: vi.fn() } const toolService = new ToolService({ + spawnCodeModeHost, + pluginToolPolicies, skillSettings: { isEnabled: () => false } as any, mcpService: { getAllToolDefinitions: vi.fn().mockResolvedValue([]), @@ -1233,6 +1227,8 @@ describe('ToolService', () => { const commitDispatch = vi.fn() const registerOutcomeProjection = vi.fn() const toolService = new ToolService({ + spawnCodeModeHost, + pluginToolPolicies, skillSettings: { isEnabled: () => false } as any, mcpService: { getAllToolDefinitions: vi.fn().mockResolvedValue([]), @@ -1286,6 +1282,8 @@ describe('ToolService', () => { ])('$toolName execution timeout', ({ providerId, toolName }) => { const createService = () => { const service = new ToolService({ + spawnCodeModeHost, + pluginToolPolicies, skillSettings: { isEnabled: () => false } as any, mcpService: { getAllToolDefinitions: vi.fn().mockResolvedValue([]) } as any, agentSettings: { resolveDeepChatAgentConfig: vi.fn(async () => ({})) } as any, @@ -1370,6 +1368,8 @@ describe('ToolService', () => { async (mode) => { const mcpService = { getAllToolDefinitions: vi.fn().mockResolvedValue([]) } const toolService = new ToolService({ + spawnCodeModeHost, + pluginToolPolicies, skillSettings: { isEnabled: () => false } as any, mcpService: mcpService as any, agentSettings: { resolveDeepChatAgentConfig: vi.fn(async () => ({})) } as any, @@ -1420,6 +1420,8 @@ describe('ToolService', () => { it('projects one execution catalog into Agent, Code, and Minimal modes', async () => { const toolService = new ToolService({ + spawnCodeModeHost, + pluginToolPolicies, skillSettings: { isEnabled: () => false } as any, mcpService: { getAllToolDefinitions: vi.fn().mockResolvedValue([]) } as any, agentSettings: { resolveDeepChatAgentConfig: vi.fn(async () => ({})) } as any, @@ -1654,6 +1656,8 @@ describe('ToolService', () => { it('fails Code Mode before provider I/O when normalized tool names collide', () => { const toolService = new ToolService({ + spawnCodeModeHost, + pluginToolPolicies, skillSettings: { isEnabled: () => false } as any, mcpService: { getAllToolDefinitions: vi.fn().mockResolvedValue([]) } as any, agentSettings: { resolveDeepChatAgentConfig: vi.fn(async () => ({})) } as any, @@ -1714,6 +1718,19 @@ describe('ToolService', () => { callTool: vi.fn(async () => ({ content: 'remote result', rawData: {} })) } as any const toolService = new ToolService({ + pluginToolPolicies, + spawnCodeModeHost: async () => { + setTimeout( + () => + host.emit('message', { + type: 'READY', + version: RUN_CODE_PROTOCOL_VERSION, + pid: host.pid + }), + 0 + ) + return host + }, skillSettings: { isEnabled: () => false } as any, mcpService, agentSettings: { resolveDeepChatAgentConfig: vi.fn(async () => ({})) } as any, @@ -1742,6 +1759,7 @@ describe('ToolService', () => { const nestedResults: RunCodeParentMessage[] = [] const host = Object.assign(new EventEmitter(), { pid: 42, + getRssBytes: async () => null, kill: vi.fn(() => true), postMessage(message: RunCodeParentMessage) { if (message.type === 'START') { @@ -1775,20 +1793,6 @@ describe('ToolService', () => { } } }) - const runtime = (toolService as any).runCodeRuntime - runtime.managerOptions.spawnHost = async () => { - setTimeout( - () => - host.emit('message', { - type: 'READY', - version: RUN_CODE_PROTOCOL_VERSION, - pid: host.pid - }), - 0 - ) - return host - } - try { await toolService.callTool( { @@ -1848,6 +1852,8 @@ describe('ToolService', () => { })) } as any const toolService = new ToolService({ + spawnCodeModeHost, + pluginToolPolicies, skillSettings: { isEnabled: () => false } as any, mcpService, agentSettings: { resolveDeepChatAgentConfig: vi.fn(async () => ({})) } as any, @@ -1914,6 +1920,8 @@ describe('ToolService', () => { }) } as any const toolService = new ToolService({ + spawnCodeModeHost, + pluginToolPolicies, skillSettings: { isEnabled: () => false } as any, mcpService, agentSettings: { resolveDeepChatAgentConfig: vi.fn(async () => ({})) } as any, @@ -1988,6 +1996,8 @@ describe('ToolService', () => { callTool: vi.fn() } as any const toolService = new ToolService({ + spawnCodeModeHost, + pluginToolPolicies, skillSettings: { isEnabled: () => false } as any, mcpService, agentSettings: { resolveDeepChatAgentConfig: vi.fn(async () => ({})) } as any, @@ -2035,6 +2045,8 @@ describe('ToolService', () => { } })) const toolService = new ToolService({ + spawnCodeModeHost, + pluginToolPolicies, skillSettings: { isEnabled: () => false } as any, mcpService: { getAllToolDefinitions: vi.fn().mockResolvedValue([]) } as any, agentSettings: { resolveDeepChatAgentConfig: vi.fn(async () => ({})) } as any, @@ -2105,6 +2117,8 @@ describe('ToolService', () => { } }) const toolService = new ToolService({ + spawnCodeModeHost, + pluginToolPolicies, skillSettings: { isEnabled: () => false } as any, mcpService: { getAllToolDefinitions: vi.fn().mockResolvedValue([]) } as any, agentSettings: { resolveDeepChatAgentConfig: vi.fn(async () => ({})) } as any, @@ -2174,6 +2188,8 @@ describe('ToolService', () => { }) } as any const toolService = new ToolService({ + spawnCodeModeHost, + pluginToolPolicies, skillSettings: { isEnabled: () => false } as any, mcpService, agentSettings: { resolveDeepChatAgentConfig: vi.fn(async () => ({})) } as any, @@ -2262,6 +2278,8 @@ describe('ToolService', () => { } const toolService = new ToolService({ + spawnCodeModeHost, + pluginToolPolicies, skillSettings: { isEnabled: () => false } as any, mcpService, agentSettings: { resolveDeepChatAgentConfig: vi.fn(async () => ({})) } as any, @@ -2327,6 +2345,8 @@ describe('ToolService', () => { } })) const toolService = new ToolService({ + spawnCodeModeHost, + pluginToolPolicies, skillSettings: { isEnabled: () => false } as any, mcpService, agentSettings: { resolveDeepChatAgentConfig: vi.fn(async () => ({})) } as any, @@ -2451,6 +2471,8 @@ describe('ToolService', () => { it('keeps every Tape capability name reserved against same-name MCP tools', async () => { const tapeToolNames = new Set(Object.values(TAPE_TOOL_NAMES)) const toolService = new ToolService({ + spawnCodeModeHost, + pluginToolPolicies, mcpService: { getAllToolDefinitions: vi .fn() @@ -2478,6 +2500,8 @@ describe('ToolService', () => { it('keeps Skill capabilities reserved, enabled, and fail-closed against MCP shadowing', async () => { const skillNames = new Set(SKILL_AGENT_TOOL_NAMES) const toolService = new ToolService({ + spawnCodeModeHost, + pluginToolPolicies, mcpService: { getAllToolDefinitions: vi .fn() @@ -2528,6 +2552,8 @@ describe('ToolService', () => { it('propagates Agent catalog failures only for fail-closed resolutions', async () => { const toolService = new ToolService({ + spawnCodeModeHost, + pluginToolPolicies, mcpService: { getAllToolDefinitions: vi.fn().mockResolvedValue([]) } as any, @@ -2567,6 +2593,8 @@ describe('ToolService', () => { } const toolService = new ToolService({ + spawnCodeModeHost, + pluginToolPolicies, skillSettings: { isEnabled: () => false } as any, mcpService, agentSettings: { resolveDeepChatAgentConfig: vi.fn(async () => ({})) } as any, @@ -2609,6 +2637,8 @@ describe('ToolService', () => { callTool: vi.fn() } as any const toolService = new ToolService({ + spawnCodeModeHost, + pluginToolPolicies, skillSettings: { isEnabled: () => false } as any, mcpService, agentSettings: { resolveDeepChatAgentConfig: vi.fn(async () => ({})) } as any, @@ -2684,6 +2714,8 @@ describe('ToolService', () => { }) const toolService = new ToolService({ + spawnCodeModeHost, + pluginToolPolicies, skillSettings: { isEnabled: () => false } as any, mcpService, agentSettings: { resolveDeepChatAgentConfig: vi.fn(async () => ({})) } as any, @@ -2758,6 +2790,8 @@ describe('ToolService', () => { const runtimePort = buildAgentToolRuntimeMock() const toolService = new ToolService({ + spawnCodeModeHost, + pluginToolPolicies, skillSettings: { isEnabled: () => false } as any, mcpService, agentSettings: { resolveDeepChatAgentConfig: vi.fn(async () => ({})) } as any, @@ -2800,6 +2834,8 @@ describe('ToolService', () => { callTool: vi.fn() } as any const toolService = new ToolService({ + spawnCodeModeHost, + pluginToolPolicies, skillSettings: { isEnabled: () => false } as any, mcpService, agentSettings: { resolveDeepChatAgentConfig: vi.fn(async () => ({})) } as any, @@ -2904,6 +2940,8 @@ describe('ToolService', () => { const privateError = new Error('private-agent-availability-error') const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => undefined) const toolService = new ToolService({ + spawnCodeModeHost, + pluginToolPolicies, skillSettings: { isEnabled: () => false } as any, mcpService: { getAllToolDefinitions: vi.fn(), @@ -2943,6 +2981,8 @@ describe('ToolService', () => { it('stops a snapshot universe before Agent availability work when aborted', async () => { const resolveConversationSessionInfo = vi.fn() const toolService = new ToolService({ + spawnCodeModeHost, + pluginToolPolicies, skillSettings: { isEnabled: () => false } as any, mcpService: { getAllToolDefinitions: vi.fn(), @@ -2970,6 +3010,8 @@ describe('ToolService', () => { it('stops a snapshot universe during Agent availability work when aborted', async () => { const resolveConversationSessionInfo = vi.fn(() => new Promise(() => undefined)) const toolService = new ToolService({ + spawnCodeModeHost, + pluginToolPolicies, skillSettings: { isEnabled: () => false } as any, mcpService: { getAllToolDefinitions: vi.fn(), @@ -3020,6 +3062,8 @@ describe('ToolService', () => { it('keeps Subagent orchestration outside disabled catalog controls', async () => { const toolService = new ToolService({ + spawnCodeModeHost, + pluginToolPolicies, mcpService: { getAllToolDefinitions: vi.fn().mockResolvedValue([]) } as any, skillSettings: { isEnabled: () => false } as any, agentSettings: { resolveDeepChatAgentConfig: vi.fn(async () => ({})) } as any, @@ -3066,6 +3110,8 @@ describe('ToolService', () => { it('reserves live delegation names and ignores generic disabled-tool state', async () => { const toolService = new ToolService({ + spawnCodeModeHost, + pluginToolPolicies, mcpService: { getAllToolDefinitions: vi .fn() @@ -3135,6 +3181,8 @@ describe('ToolService', () => { const permissionBroker = new ToolPermissionBroker() const liveDelegationConsent = new LiveDelegationConsentAuthority() const toolService = new ToolService({ + spawnCodeModeHost, + pluginToolPolicies, mcpService: { getAllToolDefinitions: vi.fn().mockResolvedValue([]) } as any, skillSettings: { isEnabled: () => false } as any, agentSettings: { resolveDeepChatAgentConfig: vi.fn(async () => ({})) } as any, @@ -3266,6 +3314,8 @@ describe('ToolService', () => { }) } const toolService = new ToolService({ + spawnCodeModeHost, + pluginToolPolicies, mcpService: mcpService as any, skillSettings: { isEnabled: () => false } as any, agentSettings: { @@ -3329,6 +3379,8 @@ describe('ToolService', () => { beforeToolExecution: vi.fn() } const toolService = new ToolService({ + spawnCodeModeHost, + pluginToolPolicies, mcpService: mcpService as any, skillSettings: { isEnabled: () => false } as any, agentSettings: { resolveDeepChatAgentConfig: vi.fn(async () => ({})) } as any, @@ -3366,6 +3418,8 @@ describe('ToolService', () => { it('rejects malformed live-delegation results at the model-facing boundary', async () => { const toolService = new ToolService({ + spawnCodeModeHost, + pluginToolPolicies, mcpService: { getAllToolDefinitions: vi.fn().mockResolvedValue([]) } as any, skillSettings: { isEnabled: () => false } as any, agentSettings: { resolveDeepChatAgentConfig: vi.fn(async () => ({})) } as any, @@ -3417,6 +3471,8 @@ describe('ToolService', () => { it('keeps the recall pair in the runtime catalog despite stale disabled values', async () => { const toolService = new ToolService({ + spawnCodeModeHost, + pluginToolPolicies, mcpService: { getAllToolDefinitions: vi.fn().mockResolvedValue([]) } as any, @@ -3462,6 +3518,8 @@ describe('ToolService', () => { callTool: vi.fn().mockResolvedValue({ content: 'mcp-result' }) } as any const toolService = new ToolService({ + spawnCodeModeHost, + pluginToolPolicies, mcpService, skillSettings: { isEnabled: () => false } as any, agentSettings: { resolveDeepChatAgentConfig: vi.fn(async () => ({})) } as any, @@ -3547,6 +3605,8 @@ describe('ToolService', () => { it('does not fall back to another conversation mapping when a tool is unavailable', async () => { const toolService = new ToolService({ + spawnCodeModeHost, + pluginToolPolicies, mcpService: { getAllToolDefinitions: vi.fn().mockResolvedValue([]) } as any, @@ -3612,6 +3672,8 @@ describe('ToolService', () => { it('keeps a draft-origin mapping available for the first persisted turn', async () => { const toolService = new ToolService({ + spawnCodeModeHost, + pluginToolPolicies, mcpService: { getAllToolDefinitions: vi.fn().mockResolvedValue([]) } as any, @@ -3653,6 +3715,8 @@ describe('ToolService', () => { it('exposes cronjob only when runtime ports are available and the tool is enabled', async () => { const toolService = new ToolService({ + spawnCodeModeHost, + pluginToolPolicies, skillSettings: { isEnabled: () => false } as any, mcpService: { getAllToolDefinitions: vi.fn().mockResolvedValue([]) @@ -3840,6 +3904,8 @@ describe('ToolService', () => { it('requires approval for cronjob write actions', async () => { const upsertCronJob = vi.fn() const toolService = new ToolService({ + spawnCodeModeHost, + pluginToolPolicies, skillSettings: { isEnabled: () => false } as any, mcpService: { getAllToolDefinitions: vi.fn().mockResolvedValue([]), @@ -3894,6 +3960,8 @@ describe('ToolService', () => { } const toolService = new ToolService({ + spawnCodeModeHost, + pluginToolPolicies, skillSettings: { isEnabled: () => false } as any, mcpService, agentSettings: { resolveDeepChatAgentConfig: vi.fn(async () => ({})) } as any, @@ -3927,6 +3995,8 @@ describe('ToolService', () => { callTool: vi.fn() } as any const toolService = new ToolService({ + spawnCodeModeHost, + pluginToolPolicies, skillSettings: { isEnabled: () => false } as any, mcpService, agentSettings: { resolveDeepChatAgentConfig: vi.fn(async () => ({})) } as any, @@ -3968,6 +4038,47 @@ describe('ToolService', () => { expect(mcpService.callTool).not.toHaveBeenCalled() }) + it('observes current plugin decisions and preserves ask under full access', async () => { + const policies = createPluginToolPolicies() + const policy = { + pluginId: 'tool-service-policy', + serverId: 'server-a', + tools: { mcp_only: 'allow' as const }, + enabled: true + } + policies.register(policy) + const mcpService = { + getAllToolDefinitions: vi.fn(async () => [buildToolDefinition('mcp_only', 'server-a')]), + callTool: vi.fn() + } as any + const service = new ToolService({ + spawnCodeModeHost, + pluginToolPolicies: policies, + mcpService, + skillSettings: { isEnabled: () => false } as any, + agentSettings: { resolveDeepChatAgentConfig: vi.fn(async () => ({})) } as any, + providerSettings: { getModelConfig: vi.fn() } as any, + settings: { get: vi.fn() }, + commandPermissionHandler: new CommandPermissionService(), + agentTools: buildAgentToolRuntimeMock() + }) + await service.getAllToolDefinitions({ chatMode: 'agent', conversationId: 'policy-session' }) + const request = { + id: 'current-policy', + type: 'function' as const, + function: { name: 'mcp_only', arguments: '{}' }, + conversationId: 'policy-session' + } + expect(await service.preCheckToolPermission(request, { permissionMode: 'default' })).toBeNull() + policies.register({ ...policy, tools: { mcp_only: 'ask' } }) + await expect( + service.preCheckToolPermission(request, { permissionMode: 'full_access' }) + ).resolves.toMatchObject({ needsPermission: true }) + const result = await service.callTool(request, { permissionMode: 'full_access' }) + expect(result.rawData.requiresPermission).toBe(true) + expect(mcpService.callTool).not.toHaveBeenCalled() + }) + it('rejects an unstable MCP target during pre-check and execution', async () => { const definition = buildToolDefinition('mcp_only', 'server-a') definition.server.id = undefined @@ -3976,6 +4087,8 @@ describe('ToolService', () => { callTool: vi.fn() } as any const toolService = new ToolService({ + spawnCodeModeHost, + pluginToolPolicies, skillSettings: { isEnabled: () => false } as any, mcpService, agentSettings: { resolveDeepChatAgentConfig: vi.fn(async () => ({})) } as any, @@ -4023,6 +4136,8 @@ describe('ToolService', () => { callTool: vi.fn() } as any const toolService = new ToolService({ + spawnCodeModeHost, + pluginToolPolicies, skillSettings: { isEnabled: () => false } as any, mcpService, agentSettings: { resolveDeepChatAgentConfig: vi.fn(async () => ({})) } as any, @@ -4087,6 +4202,8 @@ describe('ToolService', () => { } const toolService = new ToolService({ + spawnCodeModeHost, + pluginToolPolicies, skillSettings: { isEnabled: () => false } as any, mcpService, agentSettings: { resolveDeepChatAgentConfig: vi.fn(async () => ({})) } as any, @@ -4140,6 +4257,8 @@ describe('ToolService', () => { } const toolService = new ToolService({ + spawnCodeModeHost, + pluginToolPolicies, skillSettings: { isEnabled: () => false } as any, mcpService, agentSettings: { resolveDeepChatAgentConfig: vi.fn(async () => ({})) } as any, @@ -4200,6 +4319,8 @@ describe('ToolService', () => { } const toolService = new ToolService({ + spawnCodeModeHost, + pluginToolPolicies, skillSettings: { isEnabled: () => false } as any, mcpService, agentSettings: { resolveDeepChatAgentConfig: vi.fn(async () => ({})) } as any, @@ -4260,6 +4381,8 @@ describe('ToolService', () => { } const toolService = new ToolService({ + spawnCodeModeHost, + pluginToolPolicies, skillSettings: { isEnabled: () => false } as any, mcpService, agentSettings: { resolveDeepChatAgentConfig: vi.fn(async () => ({})) } as any, @@ -4303,6 +4426,8 @@ describe('ToolService', () => { } const toolService = new ToolService({ + spawnCodeModeHost, + pluginToolPolicies, skillSettings: { isEnabled: () => false } as any, mcpService, agentSettings: { resolveDeepChatAgentConfig: vi.fn(async () => ({})) } as any, @@ -4347,6 +4472,8 @@ describe('ToolService', () => { const runtimePort = buildAgentToolRuntimeMock() const toolService = new ToolService({ + spawnCodeModeHost, + pluginToolPolicies, skillSettings: { isEnabled: () => false } as any, mcpService, agentSettings: { resolveDeepChatAgentConfig: vi.fn(async () => ({})) } as any, @@ -4440,6 +4567,8 @@ describe('ToolService', () => { } const toolService = new ToolService({ + spawnCodeModeHost, + pluginToolPolicies, skillSettings: { isEnabled: () => false } as any, mcpService, agentSettings: { resolveDeepChatAgentConfig: vi.fn(async () => ({})) } as any, diff --git a/test/main/workspace/workspaceService.test.ts b/test/main/workspace/workspaceService.test.ts index 5525bbc092..b390ce9e70 100644 --- a/test/main/workspace/workspaceService.test.ts +++ b/test/main/workspace/workspaceService.test.ts @@ -3,6 +3,7 @@ import os from 'node:os' import path from 'node:path' import { pathToFileURL } from 'node:url' import { shell } from 'electron' +import { listFileOpenApps, openFileWithApp } from '../../../src/main/workspace/openInApp' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' import { DEEPCHAT_EVENT_CHANNEL } from '../../../src/shared/contracts/channels' import { createDeepchatEventEnvelope } from '../../../src/shared/contracts/events' @@ -77,7 +78,7 @@ function normalizeForAccess(value: string): string { type FakeWatcher = { request: WatchRequest - close: ReturnType + close: ReturnType Promise>> emit(events: WatcherEvent[], mode?: WatchMode): void emitStatus(status: Partial): void } @@ -88,7 +89,7 @@ function createFakeWatcherService() { watch: vi.fn(async (request, onBatch: WatchBatchListener, onStatus?: WatchStatusListener) => { const watcher: FakeWatcher = { request, - close: vi.fn().mockResolvedValue(undefined), + close: vi.fn<() => Promise>().mockResolvedValue(undefined), emit(events, mode = 'native') { onBatch({ watchId: request.id, @@ -137,18 +138,28 @@ function createWorkspaceService( files: WorkspaceFilePort, watcherService: IFileWatcherService = unusedWatcherService ): WorkspaceService { - return new WorkspaceService(files, watcherService, { - publishInvalidated: (event) => - sendToAllWindowsMock( - DEEPCHAT_EVENT_CHANNEL, - createDeepchatEventEnvelope('workspace.invalidated', event) - ), - publishWatchStatusChanged: (event) => - sendToAllWindowsMock( - DEEPCHAT_EVENT_CHANNEL, - createDeepchatEventEnvelope('workspace.watch.status.changed', event) - ) - }) + return new WorkspaceService( + files, + watcherService, + { + publishInvalidated: (event) => + sendToAllWindowsMock( + DEEPCHAT_EVENT_CHANNEL, + createDeepchatEventEnvelope('workspace.invalidated', event) + ), + publishWatchStatusChanged: (event) => + sendToAllWindowsMock( + DEEPCHAT_EVENT_CHANNEL, + createDeepchatEventEnvelope('workspace.watch.status.changed', event) + ) + }, + { + revealFile: async (filePath) => shell.showItemInFolder(filePath), + openFile: (filePath) => shell.openPath(filePath), + listFileOpenApps, + openFileWithApp + } + ) } beforeEach(() => { @@ -332,6 +343,33 @@ describe('WorkspaceService watchers', () => { expect(gitWatcher.close).toHaveBeenCalledTimes(1) }) + it('revokes preview access and drains accepted desktop work before shutdown completes', async () => { + const filePath = path.join(workspacePath, 'example.html') + fs.writeFileSync(filePath, '

example

') + await presenter.registerWorkspace(workspacePath) + const url = createWorkspacePreviewUrl(workspacePath, filePath)! + expect(await presenter.resolvePreviewRequest(url)).toBe(normalizeForAccess(filePath)) + const opened = Promise.withResolvers() + vi.mocked(shell.openPath).mockReturnValueOnce(opened.promise) + const opening = presenter.openFile(filePath) + await Promise.resolve() + const queued = Promise.allSettled([presenter.openFile(filePath)]) + let closed = false + const closing = presenter.destroy().then(() => { + closed = true + }) + await Promise.resolve() + expect(closed).toBe(false) + expect((await queued)[0].status).toBe('rejected') + expect(shell.openPath).toHaveBeenCalledOnce() + expect(await presenter.resolvePreviewRequest(url)).toBeNull() + await expect(presenter.registerWorkspace(workspacePath)).rejects.toThrow('stopped') + opened.resolve('') + await opening + await closing + expect(resolveWorkspacePreviewRequest(url)).toBeNull() + }) + it('rejects when opening an authorized path returns an error message', async () => { const filePath = path.join(workspacePath, 'example.txt') fs.writeFileSync(filePath, '') @@ -354,6 +392,33 @@ describe('WorkspaceService readFilePreview', () => { fs.rmSync(workspacePath, { recursive: true, force: true }) }) + it('does not restore preview access after revocation during file preparation', async () => { + const filePath = path.join(workspacePath, 'late.html') + fs.writeFileSync(filePath, '

late

') + const prepared = + Promise.withResolvers>>() + const presenter = createWorkspaceService({ + prepareFileCompletely: async () => await prepared.promise + }) + await presenter.registerWorkspace(workspacePath) + const reading = presenter.readFilePreview(filePath) + await presenter.unregisterWorkspace(workspacePath) + prepared.resolve({ + path: filePath, + name: 'late.html', + mimeType: 'text/html', + content: '

late

', + metadata: { + fileName: 'late.html', + fileSize: 11, + fileCreated: new Date(), + fileModified: new Date() + } + }) + await expect(reading).resolves.toBeNull() + await presenter.destroy() + }) + it('classifies html, pdf, and svg files with workspace preview URLs', async () => { const prepareFileCompletely = vi .fn() diff --git a/test/memory-test-scope.json b/test/memory-test-scope.json index 9052157dbe..4e9155fc11 100644 --- a/test/memory-test-scope.json +++ b/test/memory-test-scope.json @@ -1,5 +1,6 @@ { "behavior": [ + "test/main/harness/memoryCapability.test.ts", "test/main/contracts/memoryContractParity.test.ts", "test/main/agent/deepchat/memory/memoryExtractionChunks.test.ts", "test/main/agent/deepchat/memory/memoryRuntimeCoordinator.test.ts", @@ -53,9 +54,14 @@ "test/main/app/memoryProviderBindings.test.ts", "test/main/tool/agentTools/agentMemoryTools.test.ts", "test/main/routes/memoryDto.test.ts", - "test/main/scripts/memoryTestScope.test.ts" + "test/main/scripts/memoryTestScope.test.ts", + "test/main/performance/memory/timing.test.ts" ], "native": [ + "test/main/app/databaseSecurityNative.test.ts", + "test/main/harness/memoryVectorOwner.test.ts", + "test/main/harness/memoryVectorRuntime.test.ts", + "test/main/harness/coreMemoryRuntime.test.ts", "test/main/sync/replicaNative.test.ts", "test/main/memory/agentMemoryAuditRetention.test.ts", "test/main/memory/agentMemoryDirectiveTable.test.ts", @@ -75,6 +81,7 @@ ], "eval": ["test/main/memory/memoryRetrieval.eval.test.ts"], "perf": [ + "test/main/performance/memory/vectorTransportScale.perf.ts", "test/main/performance/memory/clearScale.perf.ts", "test/main/performance/memory/maintenanceScale.perf.ts", "test/main/performance/memory/messageHistoryTraceScale.perf.ts", diff --git a/test/renderer/api/clients.test.ts b/test/renderer/api/clients.test.ts index d00ec5798d..f47bda1bc8 100644 --- a/test/renderer/api/clients.test.ts +++ b/test/renderer/api/clients.test.ts @@ -1316,6 +1316,21 @@ describe('renderer api clients', () => { expect(bridge.on).toHaveBeenCalledWith('settings.commandShell.changed', expect.any(Function)) }) + it('preserves settings receipts and rejects invalid revisions without poisoning later values', async () => { + const bridge = createBridge() + const client = createSettingsClient(bridge) + const receipt = { version: 2, changedKeys: ['fontSizeLevel'], values: { fontSizeLevel: 3 } } + vi.mocked(bridge.invoke).mockResolvedValue(receipt as never) + expect(await client.getSnapshot()).toBe(receipt) + expect(await client.update([{ key: 'fontSizeLevel', value: 3 }])).toBe(receipt) + expect(() => + client.acceptSnapshot({ version: Infinity, values: { fontSizeLevel: 0 } }) + ).toThrow() + expect(client.acceptSnapshot(receipt)).toEqual(receipt.values) + expect(client.acceptSnapshot({ version: 1, values: { fontSizeLevel: 0 } })).toEqual({}) + expect(client.acceptSnapshot(receipt)).toEqual(receipt.values) + }) + it('sends a cloneable history cursor from reactive message state', async () => { const bridge = createBridge() const cursor = reactive({ orderSeq: 101, id: 'message-101' }) diff --git a/test/renderer/components/MemoryInlinePanel.test.ts b/test/renderer/components/MemoryInlinePanel.test.ts index 2305b2b952..ed2da30b21 100644 --- a/test/renderer/components/MemoryInlinePanel.test.ts +++ b/test/renderer/components/MemoryInlinePanel.test.ts @@ -350,8 +350,15 @@ describe('MemoryInlinePanel', () => { memoryClient.remove.mockReturnValueOnce(pending.promise) const consoleError = vi.spyOn(console, 'error').mockImplementation(() => undefined) + await wrapper.get('textarea').setValue('Unsaved memory draft') await wrapper.get('[data-testid="memory-inline-delete-trigger"]').trigger('click') await flushPromises() + await wrapper.setProps({ memory: memory({ importance: 0.5 }) }) + await flushPromises() + expect((wrapper.get('textarea').element as HTMLTextAreaElement).value).toBe( + 'Unsaved memory draft' + ) + expect(document.querySelector('[data-testid="memory-inline-delete-confirm"]')).not.toBeNull() document .querySelector('[data-testid="memory-inline-delete-confirm"]')! .click() diff --git a/test/renderer/components/OcrSettings.test.ts b/test/renderer/components/OcrSettings.test.ts index c32b78b3fe..47a34dd587 100644 --- a/test/renderer/components/OcrSettings.test.ts +++ b/test/renderer/components/OcrSettings.test.ts @@ -2,6 +2,7 @@ import { describe, expect, it, vi } from 'vitest' import { defineComponent, inject, provide, ref } from 'vue' import { flushPromises, mount } from '@vue/test-utils' import type { OcrRuntimeStatus } from '../../../src/shared/contracts/routes/ocr.routes' +import type { SettingsSnapshotValues } from '@shared/contracts/routes' const AVAILABLE_STATUS: OcrRuntimeStatus = { platform: 'darwin', @@ -46,10 +47,18 @@ async function setup( getSnapshot: settingsError ? vi.fn().mockRejectedValue(new Error('settings unavailable')) : vi.fn().mockResolvedValue({ - ocrAutoExtractForNonVisionModels: true, - ocrBackend: 'auto' + version: 1, + values: { ocrAutoExtractForNonVisionModels: true, ocrBackend: 'auto' } }), - update: vi.fn().mockResolvedValue({ values: {} }) + acceptSnapshot: vi.fn((snapshot) => snapshot.values), + onChanged: vi.fn((_listener: (payload: { values: Partial }) => void) => + vi.fn() + ), + update: vi.fn(async (changes) => ({ + version: 2, + changedKeys: changes.map((change) => change.key), + values: Object.fromEntries(changes.map((change) => [change.key, change.value])) + })) } const ocrClient = { getRuntimeStatus: @@ -69,7 +78,13 @@ async function setup( const pausePolling = vi.fn() const documentVisibility = ref('visible') const windowFocused = ref(true) - const useIntervalFn = vi.fn(() => ({ resume: resumePolling, pause: pausePolling })) + const useIntervalFn = vi.fn( + ( + _callback: () => Promise, + _intervalMs: number, + _options: { immediate: boolean; immediateCallback: boolean } + ) => ({ resume: resumePolling, pause: pausePolling }) + ) const notifyRenderer = vi.fn(() => true) vi.doMock('@api/SettingsClient', () => ({ createSettingsClient: () => settingsClient })) @@ -306,6 +321,44 @@ describe('OcrSettings', () => { consoleError.mockRestore() }) + it('receives settings changes and releases its listener on unmount', async () => { + const { wrapper, settingsClient } = await setup() + const listener = settingsClient.onChanged.mock.calls[0][0] + listener({ values: { ocrAutoExtractForNonVisionModels: false, ocrBackend: 'cpu' } }) + await flushPromises() + expect( + wrapper.get('[data-testid="ocr-auto-extract-switch"]').attributes('data-model-value') + ).toBe('false') + await openAdvanced(wrapper) + const select = wrapper.findComponent({ name: 'Select' }) + expect(select.props('modelValue')).toBe('cpu') + wrapper.unmount() + expect(settingsClient.onChanged.mock.results[0].value).toHaveBeenCalledOnce() + }) + + it.each(['resolve', 'reject'] as const)( + 'ignores a late settings %s after unmount', + async (outcome) => { + const { wrapper, settingsClient, notifyRenderer } = await setup() + const pending = deferred>>() + settingsClient.update.mockReturnValueOnce(pending.promise) + await wrapper.get('[data-testid="ocr-auto-extract-switch"]').trigger('click') + notifyRenderer.mockClear() + wrapper.unmount() + if (outcome === 'resolve') { + pending.resolve({ + version: 2, + changedKeys: ['ocrAutoExtractForNonVisionModels'], + values: { ocrAutoExtractForNonVisionModels: false } + }) + } else { + pending.reject(new Error('owner stopped')) + } + await flushPromises() + expect(notifyRenderer).not.toHaveBeenCalled() + } + ) + it('shows unsupported targets explicitly and prevents cache initialization', async () => { const { wrapper, ocrClient } = await setup({ platform: 'linux', @@ -498,7 +551,7 @@ describe('OcrSettings', () => { expect(wrapper.get('[data-testid="ocr-status-stale"]').text()).toContain( 'settings.ocr.statusLoadFailed' ) - expect(wrapper.get('[data-testid="ocr-retry-status"]').exists()).toBe(true) + expect(wrapper.find('[data-testid="ocr-retry-status"]').exists()).toBe(true) await openAdvanced(wrapper) expect(wrapper.get('[data-testid="ocr-clear-cache"]').attributes('disabled')).toBeDefined() expect(wrapper.findAll('[data-testid="ocr-status-stale"]')).toHaveLength(1) @@ -512,7 +565,7 @@ describe('OcrSettings', () => { await pollStatus() await flushPromises() - expect(wrapper.get('[data-testid="ocr-status-stale"]').exists()).toBe(true) + expect(wrapper.find('[data-testid="ocr-status-stale"]').exists()).toBe(true) expect(wrapper.findAll('[data-testid="ocr-status-stale"]')).toHaveLength(1) }) diff --git a/test/renderer/stores/uiSettingsStore.test.ts b/test/renderer/stores/uiSettingsStore.test.ts index 4878d7a15e..2d05a70f8d 100644 --- a/test/renderer/stores/uiSettingsStore.test.ts +++ b/test/renderer/stores/uiSettingsStore.test.ts @@ -11,7 +11,7 @@ describe('uiSettingsStore', () => { const mountStoreHost = async () => { const { createPinia, setActivePinia } = await vi.importActual('pinia') const { useUiSettingsStore } = await import('../../../src/renderer/src/stores/uiSettingsStore') - let store: ReturnType | null = null + let store!: ReturnType const pinia = createPinia() setActivePinia(pinia) @@ -194,7 +194,7 @@ describe('uiSettingsStore', () => { throw new Error(`Unexpected route in test: ${routeName}`) }) - window.deepchat.invoke = invoke + Object.assign(window.deepchat, { invoke }) const { store } = await mountStoreHost() @@ -204,6 +204,67 @@ describe('uiSettingsStore', () => { expect(store.privacyModeEnabled).toBe(false) }) + it('keeps newer event values while hydrating unrelated fields from a delayed snapshot', async () => { + let resolveSnapshot!: (value: { + version: number + values: Partial + }) => void + invoke.mockImplementationOnce( + () => + new Promise((resolve) => { + resolveSnapshot = resolve + }) + ) + const { store } = await mountStoreHost() + const listener = on.mock.calls[0][1] as (payload: { + version: number + changedKeys: string[] + values: Partial + }) => void + + listener({ version: 3, changedKeys: ['fontSizeLevel'], values: { fontSizeLevel: 4 } }) + resolveSnapshot({ version: 1, values: { fontSizeLevel: 0, fontFamily: 'Inter' } }) + await flushPromises() + expect(store.fontSizeLevel).toBe(4) + expect(store.fontFamily).toBe('Inter') + + listener({ + version: 2, + changedKeys: ['fontSizeLevel', 'notificationsEnabled'], + values: { fontSizeLevel: 1, notificationsEnabled: false } + }) + expect(store.fontSizeLevel).toBe(4) + expect(store.notificationsEnabled).toBe(false) + }) + + it('ignores a delayed mutation receipt after a newer acknowledgement', async () => { + const { store } = await mountStoreHost() + await flushPromises() + const pending: Array<(value: unknown) => void> = [] + invoke.mockImplementation( + () => + new Promise((resolve) => { + pending.push(resolve) + }) + ) + + const first = store.setLaunchAtLoginEnabled(true) + const second = store.setLaunchAtLoginEnabled(false) + pending[1]({ + version: 3, + changedKeys: ['launchAtLoginEnabled'], + values: { launchAtLoginEnabled: false } + }) + await second + pending[0]({ + version: 2, + changedKeys: ['launchAtLoginEnabled'], + values: { launchAtLoginEnabled: true } + }) + await first + expect(store.launchAtLoginEnabled).toBe(false) + }) + it('waits for the initial snapshot before applying an update result', async () => { let resolveSnapshot!: (value: { version: number @@ -235,7 +296,7 @@ describe('uiSettingsStore', () => { throw new Error(`Unexpected route in test: ${routeName}`) }) - window.deepchat.invoke = invoke + Object.assign(window.deepchat, { invoke }) const { store } = await mountStoreHost() const loadPromise = store.loadSettings() diff --git a/tsconfig.node.json b/tsconfig.node.json index e6a7f5ceb4..ba8468dac8 100644 --- a/tsconfig.node.json +++ b/tsconfig.node.json @@ -3,6 +3,8 @@ "include": [ "electron.vite.config.*", "src/cli/**/*", + "src/harness/**/*", + "src/headless/**/*", "src/main/**/*", "src/preload/**/*", "src/shared/**/*"